diff --git a/.bazelrc b/.bazelrc index 1ce9174344..5b12464e70 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,32 +1,34 @@ build --action_env=ZERO_AR_DATE=1 -build --apple_platform_type=ios build --enable_platform_specific_config -build --apple_crosstool_top=@local_config_apple_cc//:toolchain -build --crosstool_top=@local_config_apple_cc//:toolchain -build --host_crosstool_top=@local_config_apple_cc//:toolchain -build --per_file_copt=".*\.m$","@-fno-objc-msgsend-selector-stubs" -build --per_file_copt=".*\.mm$","@-fno-objc-msgsend-selector-stubs" - -build --features=debug_prefix_map_pwd_is_dot -build --features=swift.cacheable_swiftmodules -build --features=swift.debug_prefix_map -build --features=swift.enable_vfsoverlays +# macOS-specific settings (auto-applied on macOS via --enable_platform_specific_config) +build:macos --apple_platform_type=ios +build:macos --apple_crosstool_top=@local_config_apple_cc//:toolchain +build:macos --crosstool_top=@local_config_apple_cc//:toolchain +build:macos --host_crosstool_top=@local_config_apple_cc//:toolchain +build:macos --per_file_copt=".*\.m$","@-fno-objc-msgsend-selector-stubs" +build:macos --per_file_copt=".*\.mm$","@-fno-objc-msgsend-selector-stubs" +build:macos --features=debug_prefix_map_pwd_is_dot +build:macos --features=swift.cacheable_swiftmodules +build:macos --features=swift.debug_prefix_map +build:macos --features=swift.enable_vfsoverlays build:dbg --features=swift.emit_swiftsourceinfo +# Linux-specific settings (auto-applied on Linux via --enable_platform_specific_config) +build:linux --action_env=CC +build:linux --action_env=CXX + build --strategy=Genrule=standalone build --spawn_strategy=standalone -build --strategy=SwiftCompile=worker +build:macos --strategy=SwiftCompile=worker -#common --registry=https://raw.githubusercontent.com/bazelbuild/bazel-central-registry/main/ - -# SourceKit BSP: Swift indexing features -common --features=swift.index_while_building -common --features=swift.use_global_index_store -common --features=swift.use_global_module_cache -common --features=oso_prefix_is_pwd +# SourceKit BSP: Swift indexing features (macOS only) +common:macos --features=swift.index_while_building +common:macos --features=swift.use_global_index_store +common:macos --features=swift.use_global_module_cache +common:macos --features=oso_prefix_is_pwd # SourceKit BSP: Index build config (used for background indexing) common:index_build --experimental_convenience_symlinks=ignore diff --git a/.gitignore b/.gitignore index 09a8ec79c5..7a21ba6ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,8 @@ xcode-files .bsp/** .sourcekit-lsp/** /.claude/ +**/.claude/settings.local.json +**/.vscode/launch.json /buildbox/* +**/node_modules/ +tools/sim-watcher/dist/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d30916ab33..1a268ae4e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,11 +21,11 @@ internal: script: - export PATH=/opt/homebrew/opt/ruby/bin:$PATH - export PATH=`gem environment gemdir`/bin:$PATH - - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64 + - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appcenter-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=adhoc --configuration=release_arm64 --embedWatchApp --watchApiId="$TELEGRAM_WATCHOS_APP_ID" --watchApiHash="$TELEGRAM_WATCHOS_APP_HASH" - python3 -u build-system/Make/DeployBuild.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/deploy-configurations/internal-configuration.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" - rm -rf build-input/configuration-repository-workdir - rm -rf build-input/configuration-repository - - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 + - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="$TELEGRAM_PRIVATE_DATA_PATH/build-configurations/enterprise-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=enterprise --configuration=release_arm64 --embedWatchApp --watchApiId="$TELEGRAM_WATCHOS_APP_ID" --watchApiHash="$TELEGRAM_WATCHOS_APP_HASH" - python3 -u build-system/Make/DeployBuild.py --configuration="$TELEGRAM_PRIVATE_DATA_PATH/deploy-configurations/enterprise-configuration.json" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: internal @@ -39,7 +39,7 @@ internal_testflight: except: - tags script: - - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 --embedWatchApp --watchApiId="$TELEGRAM_WATCHOS_APP_ID" --watchApiHash="$TELEGRAM_WATCHOS_APP_HASH" - python3 -u build-system/Make/Make.py remote-deploy-testflight --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --ipa="build/artifacts/Telegram.ipa" --dsyms="build/artifacts/Telegram.DSYMs.zip" environment: name: testflight_llc @@ -115,7 +115,7 @@ beta_testflight: script: - export PATH=/opt/homebrew/opt/ruby/bin:$PATH - export PATH=`gem environment gemdir`/bin:$PATH - - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 + - python3 -u build-system/Make/Make.py remote-build --darwinContainers="$DARWIN_CONTAINERS" --darwinContainersHost="$DARWIN_CONTAINERS_HOST" --cacheHost="$TELEGRAM_BAZEL_CACHE_HOST" --configurationPath="build-system/appstore-configuration.json" --gitCodesigningRepository="$TELEGRAM_GIT_CODESIGNING_REPOSITORY" --gitCodesigningType=appstore --configuration=release_arm64 --embedWatchApp --watchApiId="$TELEGRAM_WATCHOS_APP_ID" --watchApiHash="$TELEGRAM_WATCHOS_APP_HASH" environment: name: testflight_llc artifacts: diff --git a/.xcodebuildmcp/config.yaml b/.xcodebuildmcp/config.yaml new file mode 100644 index 0000000000..fc5cb95e8f --- /dev/null +++ b/.xcodebuildmcp/config.yaml @@ -0,0 +1,2 @@ +schemaVersion: 1 +enabledWorkflows: ["simulator", "debugging", "ui-automation"] diff --git a/CLAUDE.md b/CLAUDE.md index 74412a468f..801a7996bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,7 +3,23 @@ This file provides guidance to AI assistants when working with code in this repository. ## Build -The app is built using Bazel. + +The app is built using Bazel via the `Make.py` wrapper. There is no selective per-module build — the only supported invocation builds the full `Telegram/Telegram` target. + +**Command:** + +```sh +python3 build-system/Make/Make.py --overrideXcodeVersion \ + --cacheDir ~/telegram-bazel-cache \ + build \ + --configurationPath build-system/appstore-configuration.json \ + --gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git \ + --gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64 +``` + +Add `--continueOnError` after `build` (forwards to bazel's `--keep_going`) when verifying changes that may surface errors in many files at once — it lets the full set of errors land in one pass instead of stopping at the first failing target. + +The build needs `TELEGRAM_CODESIGNING_GIT_PASSWORD` in the environment. It is set in `~/.zshrc` but Claude Code's bash tool does NOT source shell config by default. Prefix build commands with `source ~/.zshrc 2>/dev/null;` to pick it up. ## Code Style Guidelines - **Naming**: PascalCase for types, camelCase for variables/methods @@ -17,4 +33,129 @@ The app is built using Bazel. - Core launch and application extensions code is in `Telegram/` directory - Most code is organized into libraries in `submodules/` - External code is located in `third-party/` -- No tests are used at the moment \ No newline at end of file +- No tests are used at the moment + +## Embedded watch app (`Telegram/WatchApp`) + +A standalone watchOS Telegram client (developed in the separate `~/build/tgwatch` repo) is vendored into this repo at `Telegram/WatchApp/` and can be embedded into the **device** IPA under `Telegram.app/Watch/`. It is built by `xcodebuild` (not Bazel) and codesigned by the Bazel build. + +**Build it:** add `--embedWatchApp` to a Make.py **device** build (`--configuration=debug_arm64` or `release_arm64`) together with `--watchApiId`, `--watchApiHash`, `--watchSigningIdentity`, `--watchProvisioningProfile`. Off by default (it adds a ~4-min xcodebuild step); simulator builds never embed, and the default `debug_sim_arm64` build is unaffected. + +**`Telegram/WatchApp/` is a synced snapshot — do not hand-edit it.** The source of truth and dev tooling live in the `tgwatch` repo. To change the watch app, edit it there, then re-sync with `tgwatch/tools/export-sources.sh /abs/path/to/telegram-ios/Telegram/WatchApp` and commit the result. The committed `tgwatch.xcodeproj` is generated (kept via a `!tgwatch.xcodeproj` negation in `Telegram/WatchApp/.gitignore`, since the root `.gitignore` ignores `*.xcodeproj`); `.build`/`.swiftpm`/`xcuserdata` are excluded. + +**How it's wired:** `//Telegram:TelegramWatchApp` (rule in `Telegram/prebuilt_watchos.bzl`) runs in **two actions**: `PrebuiltWatchosCompile` (`Telegram/prebuilt_watchos_compile.sh`) runs xcodebuild on the snapshot in a writable temp copy with PLACEHOLDER version/api values (the bundle ids are baked from the snapshot's pbxproj/Info.plist — `ph.telegra.Telegraph.watchkitapp` / `ph.telegra.Telegraph`), emitting an unsigned `.app`; `PrebuiltWatchosPatchSign` (`Telegram/prebuilt_watchos_patch.sh`) then rewrites **six** per-build Info.plist keys (`CFBundleShortVersionString`, `CFBundleVersion`, `TG_API_ID`, `TG_API_HASH`, `CFBundleIdentifier`, `WKCompanionAppBundleIdentifier`) and codesigns the `.app` + nested `TDLibFramework.framework` (identity + the watchkitapp profile from `--define`s). The result feeds the `Telegram` `ios_application`'s `watch_application` slot (gated by the `//Telegram:embedWatchApp` flag). The rule takes `bundle_id` (set to `"{telegram_bundle_id}.watchkitapp"` in `Telegram/BUILD`) and derives the host bundle id by stripping the `.watchkitapp` suffix; both are passed to the patch worker as args (not action inputs), so the patch action re-runs when the host bundle id changes but the (expensive) compile stays cached. **The compile action's only inputs are the snapshot (+ its worker)** — so changing the version, build number, api id/hash, host bundle id, or signing identity re-runs only the cheap patch+sign action, not xcodebuild; xcodebuild re-runs only when the snapshot changes. This is correct because none of those values reach the compiled binary: each lands only in the Info.plist (via `$(...)` substitution and a runtime `Bundle.main.object(forInfoDictionaryKey:)` lookup in `Secrets.swift`, except for the bundle-id keys which only Info.plist consumers read). + +**Non-obvious invariants** (also in the `.bzl` comments): `AppleBundleInfo`'s public init is banned — use the internal `new_applebundleinfo`; `watch_application` requires BOTH `AppleBundleInfo` (with a non-None `infoplist` File) AND `WatchosApplicationBundleInfo`; the embedded watch app's `CFBundleShortVersionString`/`CFBundleVersion` must exactly equal the host's (sourced from `versions.json['app']` + `--define=buildNumber`); the host does NOT re-sign the embedded watch app, so the worker must sign it; the watch bundle id `ph.telegra.Telegraph.watchkitapp` must track the host `telegram_bundle_id`. + +**Status:** verified with **development** signing on `debug_arm64` only. Open follow-ups before App Store shipping: secure timestamp (drop `codesign --timestamp=none`), distribution profile (`get-task-allow=false`), `release_arm64` + `altool --validate-app`, and committing a `Package.resolved` for hermetic remote-SwiftPM resolution. + +## View frame ownership + +A view does not control its own `frame`. The parent (or a layout system) sets the frame; the view positions its own subviews against `self.bounds` in response. + +This matters in two places specifically: + +- **Reusable components (`UIView`/`ASDisplayNode` subclasses).** Public methods like `update(...)` / `apply(...)` rebuild internal state, mutate child frames, and read `self.bounds` to lay them out — but they do not write `self.frame`. The caller has already chosen the frame; mutating it from inside the component overrides that choice and fights the parent's next layout pass. +- **`asyncLayout`-style content nodes.** The measure pass runs off-main and returns a size; the apply step runs on main and the chat layout system positions the node. A child view that writes `self.frame` from `update()` corrupts the size the parent just measured. + +Rare exceptions: top-level view-controller views integrating with the system's first-responder/inset model. If you find yourself wanting `self.frame = …` from inside a child view, refactor so the parent positions it instead. + +## InstantPage V2 & rich-text messages + +Typed markdown with structure the regular message-entity set can't represent (headings, lists, tables, formulas, nested blockquotes) is sent as a **rich message** — a `RichTextMessageAttribute` carrying an `InstantPage`, drawn by `ChatMessageRichDataBubbleContentNode` via the **InstantPage V2** renderer (with AI-streaming progressive reveal, inline custom emoji, and entity cases). The detailed architecture and non-obvious invariants — streaming reveal, V2 table/text-box layout, custom-emoji & entity round-trips, task-list checkboxes, nested blockquotes, thinking blocks, the markdown send / edit / copy / paste paths, and surfacing rich-message media through the shared-media/gallery/preview pipelines via `Message.effectiveMedia` — live in [`docs/instantpage-richtext.md`](docs/instantpage-richtext.md). + +## Postbox → TelegramEngine refactor (in progress) + +A gradual migration is underway to eliminate direct `import Postbox` from consumer submodules in favor of `TelegramEngine`. + +**Historical record:** Wave-by-wave outcomes, the running tally of Postbox-free modules, the full wave-selection guidance, and the `TelegramEngine.Resources` facade inventory (also authoritatively defined in `submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift`) live in [`docs/superpowers/postbox-refactor-log.md`](docs/superpowers/postbox-refactor-log.md). Read that file when you need wave-specific context, a full worked example of a pattern, or the history of a particular module's migration. + +See the log for per-wave detail; the current wave count and the list of still-open migration opportunities live in the `project_postbox_refactor_next_wave.md` memory file. + +### Rules that apply to every wave + +1. `TelegramCore` does **not** `@_exported import Postbox`. Once a consumer drops `import Postbox`, every remaining Postbox-type reference must use an engine-typealiased equivalent. +2. **Never typealias `Postbox`, `Account`, or `MediaBox`.** These umbrella types rename without encapsulating. Narrow utility typealiases (`MemoryBuffer`, `PostboxDecoder`, `PostboxEncoder`, `AdaptedPostboxDecoder`, `MediaResource`, …) remain allowed and expected. +3. No new engine wrapper **structs** unless the wave's spec explicitly allows — only typealiases and thin forwarding methods. +4. **Discovery first:** before adding any new engine wrapper/typealias, grep `submodules/TelegramCore/Sources/TelegramEngine/` for existing equivalents. Record the search result in the commit message. +5. **Abandonment protocol:** if a module can only be refactored by violating rule 2 or by editing a module outside the current wave's list, mark the task Abandoned with a recorded reason. Do NOT substitute a new module mid-wave. +6. Full project build per module. No unit tests exist in this project. +7. **TelegramCore never imports UIKit/Display.** `TelegramCore` is shared with the Telegram-Mac codebase; its Bazel `deps` and source files must not reference UIKit, Display, or any Apple-UI framework. UIKit-needing helpers (image scaling, rendering, etc.) stay in consumer-side submodules. +8. **Never substitute Postbox protocols (`Media`, `Peer`, `Message`) with `Any` / `AnyObject`** in code that previously used them. Type erasure throws away the domain semantics that the next reader expects. Use the matching engine wrapper (`EngineMedia`, `EnginePeer`, `EngineMessage`) — extending it as needed (e.g. add a missing case-init or convenience). If neither typealias nor wrapper covers the use site, restore the original Postbox import + type for now and flag the case for a future facade. Existing `Any`/`AnyObject` parameters predating the refactor are not in scope for this rule. + +### Engine typealias cheat sheet (existing aliases) + +``` +PeerId → EnginePeer.Id +MessageId → EngineMessage.Id +MessageIndex → EngineMessage.Index +MessageTags → EngineMessage.Tags +MessageAttribute → EngineMessage.Attribute +MessageFlags → EngineMessage.Flags +MessageForwardInfo → EngineMessage.ForwardInfo +MediaId → EngineMedia.Id +PreferencesEntry → EnginePreferencesEntry +TempBox → EngineTempBox +PinnedItemId → EngineChatList.PinnedItem.Id +MemoryBuffer → EngineMemoryBuffer (added 2026-04) +PostboxDecoder → EnginePostboxDecoder (added 2026-04) +PostboxEncoder → EnginePostboxEncoder (added 2026-04) +AdaptedPostboxDecoder → EngineAdaptedPostboxDecoder (added 2026-04) +ItemCollectionId → EngineItemCollectionId (added 2026-04-20) +FetchResourceSourceType → EngineFetchResourceSourceType (added 2026-04-20) +FetchResourceError → EngineFetchResourceError (added 2026-04-20) +StoryId → EngineStoryId (added 2026-05-02) +ChatListIndex → EngineChatListIndex (added 2026-05-03) +TempBoxFile → EngineTempBoxFile (added 2026-05-03) +ItemCollectionItemIndex → EngineItemCollectionItemIndex (added 2026-05-03) +ItemCollectionViewEntryIndex → EngineItemCollectionViewEntryIndex (added 2026-05-03) +ValueBoxEncryptionParameters → EngineValueBoxEncryptionParameters (added 2026-05-03) +MessageAndThreadId → EngineMessageAndThreadId (added 2026-05-03) +PeerStoryStats → EnginePeerStoryStats (added 2026-05-03) +MessageHistoryAnchorIndex → EngineMessageHistoryAnchorIndex (added 2026-05-03) +ChatListTotalUnreadStateCategory → EngineChatListTotalUnreadStateCategory (added 2026-05-03) +ChatListTotalUnreadStateStats → EngineChatListTotalUnreadStateStats (added 2026-05-03) +PeerSummaryCounterTags → EnginePeerSummaryCounterTags (added 2026-05-03) +ChatListTotalUnreadState → EngineChatListTotalUnreadState (added 2026-05-04) +ItemCacheEntryId → EngineItemCacheEntryId (added 2026-05-04) +HashFunctions → EngineHashFunctions (added 2026-05-04 wave 251) +CachedMediaResourceRepresentationResult → EngineCachedMediaResourceRepresentationResult (added 2026-05-04 wave 265) +MediaResourceDataFetchResult → EngineMediaResourceDataFetchResult (added 2026-05-04 wave 266) +MediaResourceDataFetchError → EngineMediaResourceDataFetchError (added 2026-05-04 wave 266) +MediaResourceStatus → EngineMediaResourceStatus (added 2026-05-04 wave 272) +``` + +**Free-function thin forwarders in TelegramCore** (rule 3 allows): +- `engineFileSize(_ path:, useTotalFileAllocatedSize: Bool = false)` — forwards to Postbox's `fileSize(...)` (added 2026-05-04 wave 268) + +**TelegramEngineUnauthorized.resources facade**: `UnauthorizedResources.storeResourceData(id: EngineMediaResource.Id, data:, synchronous:)` — bridges to `account.postbox.mediaBox.storeResourceData` (added 2026-05-04 wave 271) + +For the `MediaResource` Postbox protocol, prefer the TelegramCore subtype `TelegramMediaResource` when the consumer's usage allows (note: `EngineMediaResource` is a wrapper **class**, not a typealias, so it is not interchangeable with the protocol). + +### MediaResource → EngineMediaResource consumer migration + +`EngineMediaResource` is a `final class` in `TelegramCore` wrapping a `MediaResource` value. Unlike the typealiases above it is **not** interchangeable with the protocol, but it does provide wrap/unwrap helpers: + +- `EngineMediaResource(rawResource)` — wrap a raw `MediaResource`. +- `engineResource._asResource()` — unwrap to the raw `MediaResource`. +- `EngineMediaResource.ResourceData(rawResourceData)` — wrap `MediaResourceData`. +- `EngineMediaResource.Id(rawMediaResourceId)` — wrap `MediaResourceId`. + +**Pattern for facade functions:** when a `TelegramEngine.` method leaks raw `MediaResource` in its public signature, **change the facade signature in place** to `EngineMediaResource` (and change any closure parameter types the same way). Bridge inside the facade body by calling the existing `_internal_*` function with `engineResource._asResource()` / wrapping raw inputs from inner closures with `EngineMediaResource(rawResource)`. Update all call sites in the same commit. The `_internal_*` function stays on raw `MediaResource` — it is the Postbox-facing layer. + +Do **not** add opt-in `EngineMediaResource` overloads alongside raw-`MediaResource` overloads. Duplicate signatures fragment the public API and leave the leak in place forever. + +For consumer modules, prefer `EngineMediaResource` as the type in properties, locals, generic arguments and function parameters when the usage is a pure type reference. Do **not** try to use `EngineMediaResource` where a class must conform to `TelegramMediaResource` (Postbox protocol) or override `isEqual(to: MediaResource)` — those remain `import Postbox`. + +## tgcalls Testbench + +This repo includes a tgcalls testbench (CLI tool, Go/Pion SFU, Docker build) layered on top of the iOS source. All testbench code, build instructions, and architecture docs live inside the tgcalls submodule: + +- `submodules/TgVoipWebrtc/tgcalls/CLAUDE.md` — top-level testbench overview, build/run commands +- `submodules/TgVoipWebrtc/tgcalls/tools/cli/CLAUDE.md` — CLI test tool architecture +- `submodules/TgVoipWebrtc/tgcalls/tools/go_sfu/CLAUDE.md` — Go SFU internals +- `submodules/TgVoipWebrtc/CLAUDE.md` — tgcalls library internals + macOS/Linux build patches + +Build the test binary from this directory with: + +`./build-input/bazel-8.4.2 build //submodules/TgVoipWebrtc/tgcalls/tools/cli:tgcalls_cli` diff --git a/MODULE.bazel b/MODULE.bazel index 99b68b09ef..06eeac82fb 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,6 +3,25 @@ http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_ bazel_dep(name = "bazel_features", version = "1.33.0") bazel_dep(name = "bazel_skylib", version = "1.8.1") bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_go", version = "0.60.0", repo_name = "io_bazel_rules_go") + +go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download(version = "1.24.2") + +bazel_dep(name = "gazelle", version = "0.43.0", repo_name = "bazel_gazelle") + +go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//submodules/TgVoipWebrtc/tgcalls/tools/go_sfu:go.mod") +use_repo( + go_deps, + "com_github_pion_datachannel", + "com_github_pion_dtls_v3", + "com_github_pion_ice_v4", + "com_github_pion_logging", + "com_github_pion_rtcp", + "com_github_pion_sctp", + "com_github_pion_srtp_v3", +) bazel_dep(name = "rules_xcodeproj") local_path_override( @@ -30,26 +49,26 @@ local_path_override( http_file( name = "cmake_tar_gz", - urls = ["https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-macos-universal.tar.gz"], sha256 = "3be85f5b999e327b1ac7d804cbc9acd767059e9f603c42ec2765f6ab68fbd367", + urls = ["https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-macos-universal.tar.gz"], ) http_file( name = "meson_tar_gz", - urls = ["https://github.com/mesonbuild/meson/releases/download/1.6.0/meson-1.6.0.tar.gz"], sha256 = "999b65f21c03541cf11365489c1fad22e2418bb0c3d50ca61139f2eec09d5496", + urls = ["https://github.com/mesonbuild/meson/releases/download/1.6.0/meson-1.6.0.tar.gz"], ) http_file( name = "ninja-mac_zip", - urls = ["https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip"], sha256 = "89a287444b5b3e98f88a945afa50ce937b8ffd1dcc59c555ad9b1baf855298c9", + urls = ["https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip"], ) http_file( name = "flatbuffers_zip", - urls = ["https://github.com/google/flatbuffers/archive/refs/tags/v24.12.23.zip"], sha256 = "c5cd6a605ff20350c7faa19d8eeb599df6117ea4aabd16ac58a7eb5ba82df4e7", + urls = ["https://github.com/google/flatbuffers/archive/refs/tags/v24.12.23.zip"], ) provisioning_profile_repository = use_extension("@build_bazel_rules_apple//apple:apple.bzl", "provisioning_profile_repository_extension") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 7cb1fb2643..db07afeb11 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -13,6 +13,7 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/source.json": "d725d73707d01bb46ab3ca59ba408b8e9bd336642ca77a2269d4bfb8bbfd413d", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -46,6 +47,12 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.43.0/MODULE.bazel": "846e1fe396eefc0f9ddad2b33e9bd364dd993fc2f42a88e31590fe0b0eefa3f0", + "https://bcr.bazel.build/modules/gazelle/0.43.0/source.json": "021a77f6625906d9d176e2fa351175e842622a5d45989312f2ad4924aab72df6", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -76,6 +83,8 @@ "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/protobuf/34.0.bcr.1/MODULE.bazel": "74e541b0ba877813da786a11707d4e394433c157841d5111a36be0d44b907931", "https://bcr.bazel.build/modules/protobuf/34.0.bcr.1/source.json": "fc174b3d6215aa14197d1bd779f98bb72d9fd666ee5ec0d6bba6ae986baa4535", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", @@ -106,6 +115,12 @@ "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", + "https://bcr.bazel.build/modules/rules_go/0.60.0/MODULE.bazel": "4a57ff2ffc2a3570e3c5646575c5a4b07287e91bcdac5d1f72383d51502b48cb", + "https://bcr.bazel.build/modules/rules_go/0.60.0/source.json": "1e21368c5e0c3013a110bd79a8fcff8ca46b5bcb2b561713a7273cbfcff7c464", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", @@ -142,6 +157,7 @@ "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", @@ -171,6 +187,7 @@ "https://bcr.bazel.build/modules/swift_argument_parser/1.7.0/source.json": "b9b952cba0c748083b9b891e6ac46d347c92d37e8a92ead96d2a54b966bacd87", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" diff --git a/Random.txt b/Random.txt index 66824569ca..1860059bf9 100644 --- a/Random.txt +++ b/Random.txt @@ -1 +1 @@ -c27f02bf6e413fdc +c27f02cf6e413fdc diff --git a/Telegram/BUILD b/Telegram/BUILD index bdff4b5f74..ef5fa5dbdf 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -35,6 +35,10 @@ load( load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile") +load("//Telegram:prebuilt_watchos.bzl", + "apple_prebuilt_watchos_application", +) + load("//build-system/bazel-utils:plist_fragment.bzl", "plist_fragment", ) @@ -97,6 +101,19 @@ config_setting( }, ) +bool_flag( + name = "embedWatchApp", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "embedWatchAppSetting", + flag_values = { + ":embedWatchApp": "True", + }, +) + config_setting( name = "projectIncludeReleaseSetting", flag_values = { @@ -1570,6 +1587,8 @@ plist_fragment( here-location yandexmaps yandexnavi + yandextaxi + yangoride comgooglemaps youtube twitter @@ -1598,6 +1617,7 @@ plist_fragment( dolphin instagram-stories yangomaps + vivaldi LSRequiresIPhoneOS @@ -1635,7 +1655,6 @@ plist_fragment( UIAppFonts - SFCompactRounded-Semibold.otf AremacFS-Regular.otf AremacFS-Semibold.otf @@ -1725,6 +1744,19 @@ xcode_provisioning_profile( provisioning_profile = ":Telegram_local_profile", ) +apple_prebuilt_watchos_application( + name = "TelegramWatchApp", + # Watch bundle id tracks the host config (".watchkitapp"). The rule passes it + # to xcodebuild as PRODUCT_BUNDLE_IDENTIFIER (baked + signed); the watch Info.plist + # derives WKCompanionAppBundleIdentifier from it via $(PRODUCT_BUNDLE_IDENTIFIER:base), + # so the embedded watch app is correct for any host (ph.telegra.Telegraph, + # org.telegram.TelegramInternal, ...) with no post-build plist patching. + bundle_id = "{telegram_bundle_id}.watchkitapp".format( + telegram_bundle_id = telegram_bundle_id, + ), + tags = ["manual"], +) + ios_application( name = "Telegram", bundle_id = "{telegram_bundle_id}".format( @@ -1773,6 +1805,13 @@ ios_application( ":BroadcastUploadExtension", ], }), + # Embed the in-repo tgwatch watch app (Telegram/WatchApp) under Watch/ only when + # --//Telegram:embedWatchApp is set (Make.py sets it for device configs when + # --embedWatchApp is passed). Off by default, so simulator/dev builds are unaffected. + watch_application = select({ + ":embedWatchAppSetting": ":TelegramWatchApp", + "//conditions:default": None, + }), deps = [ ":Main", ":Lib", diff --git a/Telegram/BroadcastUpload/BroadcastUploadExtension.swift b/Telegram/BroadcastUpload/BroadcastUploadExtension.swift index e2ea000ca3..6482ba9bf2 100644 --- a/Telegram/BroadcastUpload/BroadcastUploadExtension.swift +++ b/Telegram/BroadcastUpload/BroadcastUploadExtension.swift @@ -6,7 +6,7 @@ import SwiftSignalKit import BuildConfig import BroadcastUploadHelpers import AudioToolbox -import Postbox +import TelegramCore import CoreMedia import AVFoundation @@ -163,6 +163,7 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl { enableNoiseSuppression: false, disableAudioInput: true, enableSystemMute: false, + useReferenceImpl: false, prioritizeVP8: false, logPath: "", onMutedSpeechActivityDetected: { _ in }, @@ -331,7 +332,7 @@ private final class EmbeddedBroadcastUploadImpl: BroadcastUploadImpl { let rootPath = rootPathForBasePath(appGroupUrl.path) - TempBox.initializeShared(basePath: rootPath, processType: "share", launchSpecificId: Int64.random(in: Int64.min ... Int64.max)) + EngineTempBox.initializeShared(basePath: rootPath, processType: "share", launchSpecificId: Int64.random(in: Int64.min ... Int64.max)) let logsPath = rootPath + "/logs/broadcast-logs" let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 72292568a5..66f39d8a5b 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -1286,9 +1286,9 @@ private final class NotificationServiceHandler { action = .pollStories(peerId: peerId, content: content, storyId: storyId, isReaction: isReaction) } else { var reportDelivery = false - if let reportDeliveryUntilDate = aps["report_delivery_until_date"] as? Int32 { + if let reportDeliveryUntilDate = aps["report_delivery_until_date"] as? String, let reportDeliveryUntilDateValue = Int32(reportDeliveryUntilDate) { let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - if reportDeliveryUntilDate > currentTime { + if reportDeliveryUntilDateValue > currentTime { reportDelivery = true } } @@ -2958,7 +2958,7 @@ extension Customoji { if let cg = (image as UIImage).cgImage { return cg } var rendered: CGImage? - let work = { rendered = renderCGImage(image as! UIImage) } + let work = { rendered = renderCGImage(image) } if Thread.isMainThread { work() } else { diff --git a/Telegram/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift index 919e1692c8..7e086fa344 100644 --- a/Telegram/Share/ShareRootController.swift +++ b/Telegram/Share/ShareRootController.swift @@ -3,7 +3,6 @@ import TelegramUI import BuildConfig import ShareExtensionContext import SwiftSignalKit -import Postbox import TelegramCore @objc(ShareRootController) @@ -56,10 +55,10 @@ class ShareRootController: UIViewController { appGroupPath: appGroupUrl.path, accountManager: accountManager, appLockContext: appLockContext, - encryptionParameters: ValueBoxEncryptionParameters( + encryptionParameters: EngineValueBoxEncryptionParameters( forceEncryptionIfNoSet: false, - key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.0)!, - salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.1)! + key: EngineValueBoxEncryptionParameters.Key(data: encryptionParameters.0)!, + salt: EngineValueBoxEncryptionParameters.Salt(data: encryptionParameters.1)! ), applicationBindings: applicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, diff --git a/Telegram/Telegram-iOS/Resources/IntroCall.tgs b/Telegram/Telegram-iOS/Resources/IntroCall.tgs deleted file mode 100644 index b819c8302f..0000000000 Binary files a/Telegram/Telegram-iOS/Resources/IntroCall.tgs and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/intro/start_arrow@2x.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow@2x.png deleted file mode 100644 index fe59b3e2b5..0000000000 Binary files a/Telegram/Telegram-iOS/Resources/intro/start_arrow@2x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad.png deleted file mode 100644 index eaa2cd6dd1..0000000000 Binary files a/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png b/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png deleted file mode 100644 index 08a47e44ac..0000000000 Binary files a/Telegram/Telegram-iOS/Resources/intro/start_arrow_ipad@2x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 5fdc64f920..52ad3e640f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2067,6 +2067,7 @@ "StickerPack.Share" = "Share"; "StickerPack.Send" = "Send Sticker"; "StickerPack.AddSticker" = "Add Sticker"; +"StickerPack.RemoveStickerSet" = "Remove Sticker Set"; "StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker"; "StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers"; @@ -2660,6 +2661,8 @@ Unused sets are archived when you add more."; "Channel.EditAdmin.PermissionInviteViaLink" = "Invite Users via Link"; "Channel.EditAdmin.PermissionPinMessages" = "Pin Messages"; "Channel.EditAdmin.PermissionAddAdmins" = "Add New Admins"; +"Channel.EditAdmin.PermissionProcessJoinRequests" = "Process Join Requests"; +"Channel.EditAdmin.PermissionProcessJoinRequestsInfo" = "Allow this bot to approve new members using its own interfaces, such as captchas."; "Channel.EditAdmin.PermissinAddAdminOn" = "This Admin will be able to add new admins with the same (or more limited) permissions."; "Channel.EditAdmin.PermissinAddAdminOff" = "This Admin will not be able to add new admins."; @@ -2706,6 +2709,7 @@ Unused sets are archived when you add more."; "Channel.AdminLog.AddMembers" = "Add Members"; "Channel.AdminLog.SendPolls" = "Send Polls"; "Channel.AdminLog.ManageTopics" = "Manage Topics"; +"Channel.AdminLog.SendReactions" = "Send Reactions"; "Channel.AdminLog.CanChangeInfo" = "Change Info"; "Channel.AdminLog.CanSendMessages" = "Post Messages"; @@ -2872,6 +2876,13 @@ Unused sets are archived when you add more."; "Call.ReportSend" = "Send"; "Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin."; +"Channel.EditAdmin.GuardBotEnableMembersText" = "New members will now need approval from **%@** before they can send messages in this group"; +"Channel.EditAdmin.GuardBotEnableSubscribersText" = "New subscribers will now need approval from **%@** before they can join this channel"; +"Channel.EditAdmin.GuardBotEnable" = "Enable"; +"Channel.EditAdmin.GuardBotReplaceTitle" = "Replace Bot"; +"Channel.EditAdmin.GuardBotReplaceText" = "**%@** is currently processing join requests. Switch to **%@** instead?"; +"Channel.EditAdmin.GuardBotReplaceKeep" = "Keep %@"; +"Channel.EditAdmin.GuardBotReplaceUse" = "Use %@"; "Call.RateCall" = "Rate This Call"; "Call.ShareStats" = "Share Statistics"; @@ -7018,6 +7029,9 @@ Sorry for the inconvenience."; "InviteLink.Create.RequestApprovalOffInfoChannel" = "New users will be able to join the channel without being approved by the admins."; "InviteLink.Create.RequestApprovalOnInfoGroup" = "New users will be able to join the group only after having been approved by the admins."; "InviteLink.Create.RequestApprovalOnInfoChannel" = "New users will be able to join the channel only after having been approved by the admins."; +"InviteLink.Create.RequestApprovalInfo" = "Request admin approval for people joining through this link."; +"InviteLink.Create.RequestApprovalPublicGroupUnavailable" = "This option is unavailable because anyone can join and send messages through the public group link."; +"InviteLink.ApprovalRequired" = "approval required"; "InviteLink.Create.LinkNameTitle" = "Link Name"; "InviteLink.Create.LinkName" = "Link Name (Optional)"; @@ -7604,6 +7618,15 @@ Sorry for the inconvenience."; "Group.Setup.ApproveNewMembers" = "Approve New Members"; "Group.Setup.ApproveNewMembersInfo" = "Turn this on if you want users to be able to send messages only after they are approved by an admin."; +"Group.Setup.ApproveNewMembersManagedBy" = "Managed by %@"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksTitle" = "Apply to existing invite links?"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksText" = "Also %@ **%@** for **%@ existing invite links** in this %@?"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksEnable" = "enable"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksDisable" = "disable"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksPeerGroup" = "group"; +"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksPeerChannel" = "channel"; +"Channel.Setup.ApproveNewSubscribers" = "Approve New Subscribers"; +"Channel.Setup.ApproveNewSubscribersInfo" = "Turn this on if you want users to be able to join only after they are approved by an admin."; "Gallery.GifSaved" = "GIF Saved"; @@ -8274,6 +8297,7 @@ Sorry for the inconvenience."; "Chat.EmptyTopicPlaceholder.Text" = "Send the first message to\nstart this topic."; "Chat.Message.TopicAuthorBadge" = "Topic Author"; +"Chat.RichText.ShowMore" = "Show more"; "Chat.PanelRestartTopic" = "Restart Topic"; "Chat.PanelTopicClosedText" = "The topic is closed by admin"; @@ -16154,6 +16178,41 @@ Error: %8$@"; "TextProcessing.ResultBadge" = "Result"; "TextProcessing.Translate.LanguageStyle" = "%1$@ (%2$@)"; "TextProcessing.StyleTooltip" = "Select Style"; +"TextProcessing.AlertCreatorDeleteStyle.Title" = "Delete Style"; +"TextProcessing.AlertCreatorDeleteStyle.Text" = "Are you sure you want to delete this style? It will be removed for everyone who installed it."; +"TextProcessing.AlertDeleteStyle.Title" = "Delete Style"; +"TextProcessing.AlertDeleteStyle.Text" = "Are you sure you want to delete this style?"; +"TextProcessing.StyleMenu.Edit" = "Edit Style"; +"TextProcessing.StyleMenu.Share" = "Share Style"; +"TextProcessing.StyleMenu.Delete" = "Delete Style"; +"TextProcessing.StyleMenu.ButtonClose" = "Close"; +"TextProcessing.StyleMenu.ButtonAdd" = "Add Style"; +"TextProcessing.StylePreview.ExampleHeader" = "EXAMPLE"; +"TextProcessing.StylePreview.ExampleHeaderRefresh" = "ANOTHER EXAMPLE"; +"TextProcessing.StylePreview.Subtitle" = "Add this style to instantly\nrewrite your messages."; +"TextProcessing.StylePreview.Before" = "Before"; +"TextProcessing.StylePreview.After" = "After"; +"TextProcessing.AlertTooManyStyles.Title" = "Too Many Styles"; +"TextProcessing.AlertTooManyStyles.Text" = "Please delete some of your saved styles to create a new one."; +"TextProcessing.ToastStyleCreated.Title" = "%@ style created!"; +"TextProcessing.ToastStyleCreated.Text" = "Press and hold a style to edit or share the link."; +"TextProcessing.StyleList.Add" = "Add Style"; +"TextProcessing.StyleFooterAuthor" = "Style by [%@]()"; +"TextProcessing.StyleFooterUserCount_1" = "Used by 1 person"; +"TextProcessing.StyleFooterUserCount_any" = "Used by %d people"; +"TextProcessing.StyleFooterCreatedByFormat" = "%1$@. %2$@"; +"TextProcessing.StyleFooterCreatedBy" = "Created by [%@]()"; +"TextProcessing.StyleFooterCreatedBySimpleFormat" = "%@."; +"TextProcessing.EditStyle.NamePlaceholder" = "Style Name (for example, \"Pirate\")"; +"TextProcessing.EditStyle.TextPlaceholder" = "Instructions (for example, \"Write like a swashbuckling pirate. Use arr, ye, matey, and talk about treasure, the sea, and rum\")"; +"TextProcessing.EditStyle.TitleCreate" = "New Style"; +"TextProcessing.EditStyle.TitleEdit" = "Edit Style"; +"TextProcessing.EditStyle.ActionCreate" = "Create"; +"TextProcessing.EditStyle.ActionEdit" = "Save"; +"TextProcessing.EditStyle.Delete" = "Delete Style"; +"TextProcessing.EditStyle.AddLink" = "Add a link to my account"; +"TextProcessing.ToastStyleAdded.Title" = "Style Added"; +"TextProcessing.ToastStyleAdded.Text" = "Tap 'AI' → '%@' when typing your next long message."; "Bot.AlertCanNotCreateBots" = "%@ can't manage other bots."; @@ -16172,7 +16231,145 @@ Error: %8$@"; "CreatePoll.QuestionNeeded" = "Enter a question"; "CreatePoll.OptionsNeeded" = "Add at least two options"; +"CreatePoll.OptionsNeededOne" = "Add at least one option"; "CreatePoll.QuizCorrectOptionNeeded" = "Select a correct option"; "CreatePoll.QuizCorrectOptionNeededMultiple" = "Select at least one correct option"; +"CreatePoll.QuizCountryNeeded" = "Select at least one country"; "Stars.Intro.Transaction.Commission.Title" = "%@ commission"; + +"Conversation.ViewPollStats" = "View Statistics"; +"PollStats.Title" = "Poll Stats"; +"PollStats.GraphHeader" = "VOTE TIMELINE"; + +"CreatePoll.RestrictToSubscribers" = "Restrict to Subscribers"; +"CreatePoll.RestrictToSubscribersInfo" = "Only subscribers who joined 24+ hours ago can vote"; + +"CreatePoll.LimitCountry" = "Limit by Country"; +"CreatePoll.LimitCountryInfo" = "Only users from selected countries can vote"; +"CreatePoll.AllowedCountries" = "Allowed Countries"; +"CreatePoll.AllowedCountries.Countries_1" = "%@ country"; +"CreatePoll.AllowedCountries.Countries_any" = "%@ countries"; + +"Chat.Poll.Restriction.Subscribers" = "Only subscribers of **%@** can vote."; +"Chat.Poll.Restriction.Subscribers.TimeLimit" = "Only subscribers who joined more than **24 hours** ago can vote."; +"Chat.Poll.Restriction.Country" = "Only users from %@ can vote."; +"Chat.Poll.Restriction.SubscribersCountry" = "Only subscribers of **%@** from %@ can vote."; +"Chat.Poll.Restriction.Country.CountriesDelimiter" = ", "; +"Chat.Poll.Restriction.Country.CountriesLastDelimiter" = " and "; + +"Conversation.MessageGuestChatForUser" = "for %@"; + +"Settings.About.PrivacyHelpEmpty" = "A few words about you."; +"Settings.About.PrivacyHelpEveryone" = "Everyone can see your bio. [Change >]()"; +"Settings.About.PrivacyHelpContacts" = "Only your contacts can see your bio. [Change >]()"; +"Settings.About.PrivacyHelpNobody" = "Nobody can see your bio. [Change >]()"; + +"Settings.Birthday.PrivacyHelpEveryone" = "Everyone can see your birthday. [Change >]()"; +"Settings.Birthday.PrivacyHelpContacts" = "Only your contacts can see your birthday. [Change >]()"; +"Settings.Birthday.PrivacyHelpNobody" = "Nobody can see your birthday. [Change >]()"; + +"GroupPermission.NoSendReactions" = "no reactions"; +"Channel.BanUser.PermissionSendReactions" = "Send Reactions"; + +"Chat.AdminAction.ToastReactionsDeletedTitleSingle" = "Reaction Deleted"; +"Chat.AdminAction.ToastReactionsDeletedTextSingle" = "Reaction Deleted."; +"Chat.AdminAction.ToastReactionsDeletedTextMultiple" = "Reactions Deleted."; +"Chat.AdminAction.ToastMessagesAndReactionsDeletedText" = "Messages and reactions deleted."; + +"Premium.SignUp.SignUpNewInfo" = "Get Telegram Premium for %@"; +"Premium.SignUp.SignUpNewInfo.Days_1" = "%@ day"; +"Premium.SignUp.SignUpNewInfo.Days_any" = "%@ days"; +"Premium.SignUp.SignUpNewInfoNone" = "Get Telegram Premium"; + +"Login.Fee.Support.NewText.Days_1" = "%@ day"; +"Login.Fee.Support.NewText.Days_any" = "%@ days"; +"Login.Fee.Support.NewText" = "Sign up for a %@ Telegram Premium subscription to help cover the SMS costs."; +"Login.Fee.Support.NewTextNone" = "Sign up for Telegram Premium subscription to help cover the SMS costs."; + +"Login.Fee.GetPremiumNone" = "Get Telegram Premium"; +"Login.Fee.GetPremiumForDays" = "Get Telegram Premium for %@"; +"Login.Fee.GetPremiumForDays.Days_1" = "%@ day"; +"Login.Fee.GetPremiumForDays.Days_any" = "%@ days"; + +"PeerInfo.DeleteReaction" = "Delete Reaction"; +"Chat.DeleteReactionInfo" = "Tap and hold to delete reaction."; + +"Chat.AdminActionSheet.DeleteReactionTitle" = "Delete 1 Reaction"; +"Chat.AdminActionSheet.DeleteAllMessages" = "Delete All Messages"; +"Chat.AdminActionSheet.DeleteAllReactions" = "Delete All Reactions"; + +"Conversation.CalendarSearch.Title" = "Search"; +"Conversation.CalendarSearch.Done" = "Done"; + +"ScheduleMessage.SilentPosting.YouEnabled" = "You will receive a silent notification"; +"ScheduleMessage.SilentPosting.YouDisabled" = "You will be notified"; +"ScheduleMessage.SilentPosting.UserEnabled" = "%@ will receive a silent notification"; +"ScheduleMessage.SilentPosting.UserDisabled" = "%@ will be notified"; +"ScheduleMessage.SilentPosting.GroupEnabled" = "Members will receive a silent notification"; +"ScheduleMessage.SilentPosting.GroupDisabled" = "Members will be notified"; +"ScheduleMessage.SilentPosting.ChannelEnabled" = "Subscribers will receive a silent notification"; +"ScheduleMessage.SilentPosting.ChannelDisabled" = "Subscribers will be notified"; + +"Settings.ChatAutomation" = "Chat Automation"; +"Settings.ChatAutomationInfo" = "Add a bot to reply to messages on your behalf."; +"Settings.ChatAutomationOff" = "Off"; + +"Chat.SendReactionRestricted" = "You cannot send reactions in this chat."; + +"ChatbotSetup.BotInstalled" = "%@ now manages your account."; + +"ChatbotSetup.SetupNotCompleted.Title" = "No Bot Added"; +"ChatbotSetup.SetupNotCompleted.Text" = "You haven’t added a bot to manage your account. Leave anyway?"; +"ChatbotSetup.SetupNotCompleted.Leave" = "Leave"; + +"Chat.SavedMessagesStatusViewAsChats" = "Tap to view as chats"; +"Chat.ToastVoiceMessageDeviceMuted" = "Device is muted."; + +"VideoChat.StatusPeerJoined" = "%@ joined"; +"VideoChat.StatusPeerLeft" = "%@ left"; + +"ChatList.BotConnectionReview.PanelTitle" = "A bot just got access to your messages!"; +"ChatList.BotConnectionReview.PanelText" = "%1$@ connected to your account from %2$@ (%3$@). Was this you?"; + +"RecentSession.ConnectedBot.Subtitle" = "Chat Automation Bot"; +"RecentSession.ConnectedBot.ConnectedFrom" = "CONNECTED FROM"; +"RecentSession.ConnectedBot.Date" = "Date"; +"RecentSession.ConnectedBot.Device" = "Device"; +"RecentSessions.ConnectedBot.ConnectedTime" = "connected %@"; +"RecentSessions.ConnectedBot.TerminateCheckbox" = "Also terminate %@"; + +"Chat.GuestChatMessageTooltip" = "This bot can't read the chat – only the messages where it was mentioned."; + +"MediaPicker.SetNewGroupPhoto" = "Set new group photo"; +"MediaPicker.SetNewChannelPhoto" = "Set new channel photo"; + +"Attachment.Link" = "Link"; + +"Checkout.PaymentMethod.Proceed" = "Proceed"; +"Checkout.ShippingMethod.Proceed" = "Proceed"; + +"OpenLinkConfirmation.Title" = "Open link?"; +"OpenLinkConfirmation.Open" = "Open"; + +"Chat.ContextMenu.OpenInBrowser" = "Open in Browser"; +"Chat.ContextMenu.OpenInApp" = "Open In-App"; + +"CreatePoll.Link.Description" = "Attach a link to this option."; + +"ChatList.ClearSearchHistory.Confirm" = "Clear"; + +"ChatList.RemoveFolderConfirmationTitle" = "Remove %@?"; + +"SocksProxySetup.QrCode.TgLink" = "tg:// link"; +"SocksProxySetup.QrCode.TMeLink" = "t.me link"; + +"WebBrowser.OpenLinksInfo" = "Open links inside Telegram instead of your default browser for more privacy."; +"WebBrowser.Exceptions.OpenInApp" = "OPEN IN-APP"; +"WebBrowser.Exceptions.DontOpenInApp" = "DON'T OPEN IN-APP"; +"WebBrowser.Exceptions.InAppInfo" = "These sites will still be opened in-app."; +"WebBrowser.Exceptions.DeleteAll" = "Delete All Exceptions"; + +"RichTextPreview.Formula" = "[formula]"; +"RichTextPreview.Table" = "[table]"; +"RichTextPreview.Music" = "Music"; diff --git a/Telegram/WatchApp/.gitignore b/Telegram/WatchApp/.gitignore new file mode 100644 index 0000000000..34652a73b6 --- /dev/null +++ b/Telegram/WatchApp/.gitignore @@ -0,0 +1,8 @@ +**/.build/ +**/DerivedData/ +.DS_Store +**/xcuserdata/ +# The repo root .gitignore ignores *.xcodeproj, but this committed snapshot needs its +# generated project (the Bazel embed build uses it directly). Re-include the project +# directory; xcuserdata inside stays excluded via the rule above. +!tgwatch.xcodeproj diff --git a/Telegram/WatchApp/Assets/SampleStickers/sticker_lottie.tgs b/Telegram/WatchApp/Assets/SampleStickers/sticker_lottie.tgs new file mode 100644 index 0000000000..f7bf37b85f Binary files /dev/null and b/Telegram/WatchApp/Assets/SampleStickers/sticker_lottie.tgs differ diff --git a/Telegram/WatchApp/Assets/SampleStickers/sticker_raster.webp b/Telegram/WatchApp/Assets/SampleStickers/sticker_raster.webp new file mode 100644 index 0000000000..db69e691c4 Binary files /dev/null and b/Telegram/WatchApp/Assets/SampleStickers/sticker_raster.webp differ diff --git a/Telegram/WatchApp/Assets/SampleStickers/sticker_vp9.webm b/Telegram/WatchApp/Assets/SampleStickers/sticker_vp9.webm new file mode 100644 index 0000000000..bf221b630b Binary files /dev/null and b/Telegram/WatchApp/Assets/SampleStickers/sticker_vp9.webm differ diff --git a/Telegram/WatchApp/BUILD b/Telegram/WatchApp/BUILD new file mode 100644 index 0000000000..07877d170a --- /dev/null +++ b/Telegram/WatchApp/BUILD @@ -0,0 +1,19 @@ +# Committed snapshot of the tgwatch watch app (synced from the standalone tgwatch repo +# via tools/export-sources.sh). Built by //Telegram:TelegramWatchApp +# (apple_prebuilt_watchos_application) via xcodebuild. These files are the rule's tracked +# inputs, so the watch build re-runs only when the snapshot changes. +filegroup( + name = "sources", + srcs = glob( + ["**"], + exclude = [ + "**/.build/**", + "**/.swiftpm/**", + "**/xcuserdata/**", + "**/DerivedData/**", + "BUILD", + ".gitignore", + ], + ), + visibility = ["//Telegram:__pkg__"], +) diff --git a/Telegram/WatchApp/Packages/OpusKit/Package.swift b/Telegram/WatchApp/Packages/OpusKit/Package.swift new file mode 100644 index 0000000000..1af9486e89 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Package.swift @@ -0,0 +1,113 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "OpusKit", + platforms: [ + .watchOS(.v10), + .macOS(.v14) + ], + products: [ + .library(name: "OpusKit", targets: ["OpusKit"]), + ], + targets: [ + .target( + name: "COgg", + path: "Sources/COgg", + sources: ["libogg/src"], + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("libogg/include"), + ] + ), + // Vendored: libopus 1.5.2 (https://opus-codec.org). + // Exclude list is version-specific — verify on upgrade. + // arm_celt_map.c and armcpu.c are intentionally kept; both are fully + // wrapped in `#ifdef OPUS_HAVE_RTCD` (never defined here) and compile + // to empty TUs. arm_silk_map.c is excluded because it unconditionally + // includes main_FIX.h from the (excluded) silk/fixed tree. + .target( + name: "COpus", + path: "Sources/COpus", + exclude: [ + // NEON (ARM) SIMD variants — disabled via -U__ARM_NEON__ + "libopus/celt/arm/celt_neon_intr.c", + "libopus/celt/arm/pitch_neon_intr.c", + "libopus/silk/arm/biquad_alt_neon_intr.c", + "libopus/silk/arm/LPC_inv_pred_gain_neon_intr.c", + "libopus/silk/arm/NSQ_del_dec_neon_intr.c", + "libopus/silk/arm/NSQ_neon.c", + // ARM dispatcher — unconditionally includes silk/fixed headers (excluded) + "libopus/silk/arm/arm_silk_map.c", + // (silk/fixed/arm files already covered by the silk/fixed directory exclude below) + // ARM .s assembly (GAS/RVCT) + "libopus/celt/arm/celt_pitch_xcorr_arm.s", + "libopus/celt/arm/celt_pitch_xcorr_arm-gnu.S", + // ARM NE10 FFT (external dep) — not used + "libopus/celt/arm/celt_mdct_ne10.c", + "libopus/celt/arm/celt_fft_ne10.c", + // x86 (SSE/AVX) intrinsic variants + "libopus/celt/x86/celt_lpc_sse4_1.c", + "libopus/celt/x86/pitch_sse.c", + "libopus/celt/x86/pitch_sse2.c", + "libopus/celt/x86/pitch_sse4_1.c", + "libopus/celt/x86/pitch_avx.c", + "libopus/celt/x86/vq_sse2.c", + "libopus/celt/x86/x86_celt_map.c", + "libopus/celt/x86/x86cpu.c", + "libopus/silk/x86/NSQ_del_dec_sse4_1.c", + "libopus/silk/x86/NSQ_del_dec_avx2.c", + "libopus/silk/x86/NSQ_sse4_1.c", + "libopus/silk/x86/VAD_sse4_1.c", + "libopus/silk/x86/VQ_WMat_EC_sse4_1.c", + "libopus/silk/x86/x86_silk_map.c", + "libopus/silk/fixed/x86/burg_modified_FIX_sse4_1.c", + "libopus/silk/fixed/x86/vector_ops_FIX_sse4_1.c", + "libopus/silk/float/x86/inner_product_FLP_avx2.c", + // libopus example / test programs + "libopus/src/opus_compare.c", + "libopus/src/opus_demo.c", + "libopus/src/repacketizer_demo.c", + "libopus/celt/opus_custom_demo.c", + "libopus/celt/tests", + "libopus/silk/tests", + // Fixed-point silk — we're a float build (FIXED_POINT undefined) + "libopus/silk/fixed", + ], + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("libopus"), + .headerSearchPath("libopus/include"), + .headerSearchPath("libopus/celt"), + .headerSearchPath("libopus/silk"), + .headerSearchPath("libopus/silk/float"), + .headerSearchPath("libopus/src"), + .define("HAVE_CONFIG_H"), + .unsafeFlags(["-U__ARM_NEON__", "-U__SSE2__", "-U__SSE4_1__", "-U__AVX2__"]), + ] + ), + .target( + name: "COpusHelpers", + dependencies: ["COpus"], + path: "Sources/COpusHelpers", + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("../COpus/include"), + ] + ), + .target( + name: "OpusKit", + dependencies: ["COgg", "COpus", "COpusHelpers"], + path: "Sources/OpusKit" + ), + .testTarget( + name: "OpusKitTests", + dependencies: ["OpusKit"], + path: "Tests/OpusKitTests", + resources: [ + .copy("Resources/voice_note.ogg"), + ] + ), + ], + cLanguageStandard: .c11 +) diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/COgg/COgg.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/COgg/COgg.h new file mode 100644 index 0000000000..bee3c9dc09 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/COgg/COgg.h @@ -0,0 +1,6 @@ +#ifndef COgg_h +#define COgg_h + +#include + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/module.modulemap b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/module.modulemap new file mode 100644 index 0000000000..f6baa18036 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/module.modulemap @@ -0,0 +1,5 @@ +module COgg { + header "ogg/ogg.h" + header "ogg/os_types.h" + export * +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/ogg.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/ogg.h new file mode 100644 index 0000000000..c4325aa76d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/ogg.h @@ -0,0 +1,209 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2007 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel libogg include + + ********************************************************************/ +#ifndef _OGG_H +#define _OGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + void *iov_base; + size_t iov_len; +} ogg_iovec_t; + +typedef struct { + long endbyte; + int endbit; + + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + unsigned char *header; + long header_len; + unsigned char *body; + long body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + long body_storage; /* storage elements allocated */ + long body_fill; /* elements stored; fill mark */ + long body_returned; /* elements of fill returned */ + + + int *lacing_vals; /* The values that will go to the segment table */ + ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + int header_fill; + + int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + long serialno; + long pageno; + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ + ogg_int64_t granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + unsigned char *packet; + long bytes; + long b_o_s; + long e_o_s; + + ogg_int64_t granulepos; + + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + unsigned char *data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +/* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ + +extern void oggpack_writeinit(oggpack_buffer *b); +extern int oggpack_writecheck(oggpack_buffer *b); +extern void oggpack_writetrunc(oggpack_buffer *b,long bits); +extern void oggpack_writealign(oggpack_buffer *b); +extern void oggpack_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpack_reset(oggpack_buffer *b); +extern void oggpack_writeclear(oggpack_buffer *b); +extern void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpack_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpack_look(oggpack_buffer *b,int bits); +extern long oggpack_look1(oggpack_buffer *b); +extern void oggpack_adv(oggpack_buffer *b,int bits); +extern void oggpack_adv1(oggpack_buffer *b); +extern long oggpack_read(oggpack_buffer *b,int bits); +extern long oggpack_read1(oggpack_buffer *b); +extern long oggpack_bytes(oggpack_buffer *b); +extern long oggpack_bits(oggpack_buffer *b); +extern unsigned char *oggpack_get_buffer(oggpack_buffer *b); + +extern void oggpackB_writeinit(oggpack_buffer *b); +extern int oggpackB_writecheck(oggpack_buffer *b); +extern void oggpackB_writetrunc(oggpack_buffer *b,long bits); +extern void oggpackB_writealign(oggpack_buffer *b); +extern void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpackB_reset(oggpack_buffer *b); +extern void oggpackB_writeclear(oggpack_buffer *b); +extern void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpackB_look(oggpack_buffer *b,int bits); +extern long oggpackB_look1(oggpack_buffer *b); +extern void oggpackB_adv(oggpack_buffer *b,int bits); +extern void oggpackB_adv1(oggpack_buffer *b); +extern long oggpackB_read(oggpack_buffer *b,int bits); +extern long oggpackB_read1(oggpack_buffer *b); +extern long oggpackB_bytes(oggpack_buffer *b); +extern long oggpackB_bits(oggpack_buffer *b); +extern unsigned char *oggpackB_get_buffer(oggpack_buffer *b); + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +extern int ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); +extern int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, + int count, long e_o_s, ogg_int64_t granulepos); +extern int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill); +extern int ogg_stream_flush(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_flush_fill(ogg_stream_state *os, ogg_page *og, int nfill); + +/* Ogg BITSTREAM PRIMITIVES: decoding **************************/ + +extern int ogg_sync_init(ogg_sync_state *oy); +extern int ogg_sync_clear(ogg_sync_state *oy); +extern int ogg_sync_reset(ogg_sync_state *oy); +extern int ogg_sync_destroy(ogg_sync_state *oy); +extern int ogg_sync_check(ogg_sync_state *oy); + +extern char *ogg_sync_buffer(ogg_sync_state *oy, long size); +extern int ogg_sync_wrote(ogg_sync_state *oy, long bytes); +extern long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); +extern int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); +extern int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); +extern int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +extern int ogg_stream_init(ogg_stream_state *os,int serialno); +extern int ogg_stream_clear(ogg_stream_state *os); +extern int ogg_stream_reset(ogg_stream_state *os); +extern int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno); +extern int ogg_stream_destroy(ogg_stream_state *os); +extern int ogg_stream_check(ogg_stream_state *os); +extern int ogg_stream_eos(ogg_stream_state *os); + +extern void ogg_page_checksum_set(ogg_page *og); + +extern int ogg_page_version(const ogg_page *og); +extern int ogg_page_continued(const ogg_page *og); +extern int ogg_page_bos(const ogg_page *og); +extern int ogg_page_eos(const ogg_page *og); +extern ogg_int64_t ogg_page_granulepos(const ogg_page *og); +extern int ogg_page_serialno(const ogg_page *og); +extern long ogg_page_pageno(const ogg_page *og); +extern int ogg_page_packets(const ogg_page *og); + +extern void ogg_packet_clear(ogg_packet *op); + + +#ifdef __cplusplus +} +#endif + +#endif /* _OGG_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/os_types.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/os_types.h new file mode 100644 index 0000000000..2d5d403f5b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/include/ogg/os_types.h @@ -0,0 +1,19 @@ +#ifndef OGG_OS_TYPES_H +#define OGG_OS_TYPES_H + +#include +#include + +typedef int16_t ogg_int16_t; +typedef uint16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef uint32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; +typedef uint64_t ogg_uint64_t; + +#define _ogg_malloc malloc +#define _ogg_calloc calloc +#define _ogg_realloc realloc +#define _ogg_free free + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/ogg.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/ogg.h new file mode 100644 index 0000000000..c4325aa76d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/ogg.h @@ -0,0 +1,209 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2007 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: toplevel libogg include + + ********************************************************************/ +#ifndef _OGG_H +#define _OGG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct { + void *iov_base; + size_t iov_len; +} ogg_iovec_t; + +typedef struct { + long endbyte; + int endbit; + + unsigned char *buffer; + unsigned char *ptr; + long storage; +} oggpack_buffer; + +/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ + +typedef struct { + unsigned char *header; + long header_len; + unsigned char *body; + long body_len; +} ogg_page; + +/* ogg_stream_state contains the current encode/decode state of a logical + Ogg bitstream **********************************************************/ + +typedef struct { + unsigned char *body_data; /* bytes from packet bodies */ + long body_storage; /* storage elements allocated */ + long body_fill; /* elements stored; fill mark */ + long body_returned; /* elements of fill returned */ + + + int *lacing_vals; /* The values that will go to the segment table */ + ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact + this way, but it is simple coupled to the + lacing fifo */ + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + + unsigned char header[282]; /* working space for header encode */ + int header_fill; + + int e_o_s; /* set when we have buffered the last packet in the + logical bitstream */ + int b_o_s; /* set after we've written the initial page + of a logical bitstream */ + long serialno; + long pageno; + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ + ogg_int64_t granulepos; + +} ogg_stream_state; + +/* ogg_packet is used to encapsulate the data and metadata belonging + to a single raw Ogg/Vorbis packet *************************************/ + +typedef struct { + unsigned char *packet; + long bytes; + long b_o_s; + long e_o_s; + + ogg_int64_t granulepos; + + ogg_int64_t packetno; /* sequence number for decode; the framing + knows where there's a hole in the data, + but we need coupling so that the codec + (which is in a separate abstraction + layer) also knows about the gap */ +} ogg_packet; + +typedef struct { + unsigned char *data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state; + +/* Ogg BITSTREAM PRIMITIVES: bitstream ************************/ + +extern void oggpack_writeinit(oggpack_buffer *b); +extern int oggpack_writecheck(oggpack_buffer *b); +extern void oggpack_writetrunc(oggpack_buffer *b,long bits); +extern void oggpack_writealign(oggpack_buffer *b); +extern void oggpack_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpack_reset(oggpack_buffer *b); +extern void oggpack_writeclear(oggpack_buffer *b); +extern void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpack_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpack_look(oggpack_buffer *b,int bits); +extern long oggpack_look1(oggpack_buffer *b); +extern void oggpack_adv(oggpack_buffer *b,int bits); +extern void oggpack_adv1(oggpack_buffer *b); +extern long oggpack_read(oggpack_buffer *b,int bits); +extern long oggpack_read1(oggpack_buffer *b); +extern long oggpack_bytes(oggpack_buffer *b); +extern long oggpack_bits(oggpack_buffer *b); +extern unsigned char *oggpack_get_buffer(oggpack_buffer *b); + +extern void oggpackB_writeinit(oggpack_buffer *b); +extern int oggpackB_writecheck(oggpack_buffer *b); +extern void oggpackB_writetrunc(oggpack_buffer *b,long bits); +extern void oggpackB_writealign(oggpack_buffer *b); +extern void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits); +extern void oggpackB_reset(oggpack_buffer *b); +extern void oggpackB_writeclear(oggpack_buffer *b); +extern void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes); +extern void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits); +extern long oggpackB_look(oggpack_buffer *b,int bits); +extern long oggpackB_look1(oggpack_buffer *b); +extern void oggpackB_adv(oggpack_buffer *b,int bits); +extern void oggpackB_adv1(oggpack_buffer *b); +extern long oggpackB_read(oggpack_buffer *b,int bits); +extern long oggpackB_read1(oggpack_buffer *b); +extern long oggpackB_bytes(oggpack_buffer *b); +extern long oggpackB_bits(oggpack_buffer *b); +extern unsigned char *oggpackB_get_buffer(oggpack_buffer *b); + +/* Ogg BITSTREAM PRIMITIVES: encoding **************************/ + +extern int ogg_stream_packetin(ogg_stream_state *os, ogg_packet *op); +extern int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, + int count, long e_o_s, ogg_int64_t granulepos); +extern int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill); +extern int ogg_stream_flush(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_flush_fill(ogg_stream_state *os, ogg_page *og, int nfill); + +/* Ogg BITSTREAM PRIMITIVES: decoding **************************/ + +extern int ogg_sync_init(ogg_sync_state *oy); +extern int ogg_sync_clear(ogg_sync_state *oy); +extern int ogg_sync_reset(ogg_sync_state *oy); +extern int ogg_sync_destroy(ogg_sync_state *oy); +extern int ogg_sync_check(ogg_sync_state *oy); + +extern char *ogg_sync_buffer(ogg_sync_state *oy, long size); +extern int ogg_sync_wrote(ogg_sync_state *oy, long bytes); +extern long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og); +extern int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og); +extern int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og); +extern int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op); +extern int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op); + +/* Ogg BITSTREAM PRIMITIVES: general ***************************/ + +extern int ogg_stream_init(ogg_stream_state *os,int serialno); +extern int ogg_stream_clear(ogg_stream_state *os); +extern int ogg_stream_reset(ogg_stream_state *os); +extern int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno); +extern int ogg_stream_destroy(ogg_stream_state *os); +extern int ogg_stream_check(ogg_stream_state *os); +extern int ogg_stream_eos(ogg_stream_state *os); + +extern void ogg_page_checksum_set(ogg_page *og); + +extern int ogg_page_version(const ogg_page *og); +extern int ogg_page_continued(const ogg_page *og); +extern int ogg_page_bos(const ogg_page *og); +extern int ogg_page_eos(const ogg_page *og); +extern ogg_int64_t ogg_page_granulepos(const ogg_page *og); +extern int ogg_page_serialno(const ogg_page *og); +extern long ogg_page_pageno(const ogg_page *og); +extern int ogg_page_packets(const ogg_page *og); + +extern void ogg_packet_clear(ogg_packet *op); + + +#ifdef __cplusplus +} +#endif + +#endif /* _OGG_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/os_types.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/os_types.h new file mode 100644 index 0000000000..2d5d403f5b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/include/ogg/os_types.h @@ -0,0 +1,19 @@ +#ifndef OGG_OS_TYPES_H +#define OGG_OS_TYPES_H + +#include +#include + +typedef int16_t ogg_int16_t; +typedef uint16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef uint32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; +typedef uint64_t ogg_uint64_t; + +#define _ogg_malloc malloc +#define _ogg_calloc calloc +#define _ogg_realloc realloc +#define _ogg_free free + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/bitwise.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/bitwise.c new file mode 100644 index 0000000000..f5ef79122e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/bitwise.c @@ -0,0 +1,1087 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE Ogg CONTAINER SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2014 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: packing variable sized words into an octet stream + + ********************************************************************/ + +/* We're 'LSb' endian; if we write a word but read individual bits, + then we'll read the lsb first */ + +#include +#include +#include +#include + +#define BUFFER_INCREMENT 256 + +static const unsigned long mask[]= +{0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f, + 0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff, + 0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff, + 0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff, + 0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff, + 0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff, + 0x3fffffff,0x7fffffff,0xffffffff }; + +static const unsigned int mask8B[]= +{0x00,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff}; + +void oggpack_writeinit(oggpack_buffer *b){ + memset(b,0,sizeof(*b)); + b->ptr=b->buffer=_ogg_malloc(BUFFER_INCREMENT); + b->buffer[0]='\0'; + b->storage=BUFFER_INCREMENT; +} + +void oggpackB_writeinit(oggpack_buffer *b){ + oggpack_writeinit(b); +} + +int oggpack_writecheck(oggpack_buffer *b){ + if(!b->ptr || !b->storage)return -1; + return 0; +} + +int oggpackB_writecheck(oggpack_buffer *b){ + return oggpack_writecheck(b); +} + +void oggpack_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + if(b->ptr){ + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask[bits]; + } +} + +void oggpackB_writetrunc(oggpack_buffer *b,long bits){ + long bytes=bits>>3; + if(b->ptr){ + bits-=bytes*8; + b->ptr=b->buffer+bytes; + b->endbit=bits; + b->endbyte=bytes; + *b->ptr&=mask8B[bits]; + } +} + +/* Takes only up to 32 bits. */ +void oggpack_write(oggpack_buffer *b,unsigned long value,int bits){ + if(bits<0 || bits>32) goto err; + if(b->endbyte>=b->storage-4){ + void *ret; + if(!b->ptr)return; + if(b->storage>LONG_MAX-BUFFER_INCREMENT) goto err; + ret=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + if(!ret) goto err; + b->buffer=ret; + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value&=mask[bits]; + bits+=b->endbit; + + b->ptr[0]|=value<endbit; + + if(bits>=8){ + b->ptr[1]=(unsigned char)(value>>(8-b->endbit)); + if(bits>=16){ + b->ptr[2]=(unsigned char)(value>>(16-b->endbit)); + if(bits>=24){ + b->ptr[3]=(unsigned char)(value>>(24-b->endbit)); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=(unsigned char)(value>>(32-b->endbit)); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; + return; + err: + oggpack_writeclear(b); +} + +/* Takes only up to 32 bits. */ +void oggpackB_write(oggpack_buffer *b,unsigned long value,int bits){ + if(bits<0 || bits>32) goto err; + if(b->endbyte>=b->storage-4){ + void *ret; + if(!b->ptr)return; + if(b->storage>LONG_MAX-BUFFER_INCREMENT) goto err; + ret=_ogg_realloc(b->buffer,b->storage+BUFFER_INCREMENT); + if(!ret) goto err; + b->buffer=ret; + b->storage+=BUFFER_INCREMENT; + b->ptr=b->buffer+b->endbyte; + } + + value=(value&mask[bits])<<(32-bits); + bits+=b->endbit; + + b->ptr[0]|=value>>(24+b->endbit); + + if(bits>=8){ + b->ptr[1]=(unsigned char)(value>>(16+b->endbit)); + if(bits>=16){ + b->ptr[2]=(unsigned char)(value>>(8+b->endbit)); + if(bits>=24){ + b->ptr[3]=(unsigned char)(value>>(b->endbit)); + if(bits>=32){ + if(b->endbit) + b->ptr[4]=(unsigned char)(value<<(8-b->endbit)); + else + b->ptr[4]=0; + } + } + } + } + + b->endbyte+=bits/8; + b->ptr+=bits/8; + b->endbit=bits&7; + return; + err: + oggpack_writeclear(b); +} + +void oggpack_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpack_write(b,0,bits); +} + +void oggpackB_writealign(oggpack_buffer *b){ + int bits=8-b->endbit; + if(bits<8) + oggpackB_write(b,0,bits); +} + +static void oggpack_writecopy_helper(oggpack_buffer *b, + void *source, + long bits, + void (*w)(oggpack_buffer *, + unsigned long, + int), + int msb){ + unsigned char *ptr=(unsigned char *)source; + + long bytes=bits/8; + long pbytes=(b->endbit+bits)/8; + bits-=bytes*8; + + /* expand storage up-front */ + if(b->endbyte+pbytes>=b->storage){ + void *ret; + if(!b->ptr) goto err; + if(b->storage>b->endbyte+pbytes+BUFFER_INCREMENT) goto err; + b->storage=b->endbyte+pbytes+BUFFER_INCREMENT; + ret=_ogg_realloc(b->buffer,b->storage); + if(!ret) goto err; + b->buffer=ret; + b->ptr=b->buffer+b->endbyte; + } + + /* copy whole octets */ + if(b->endbit){ + int i; + /* unaligned copy. Do it the hard way. */ + for(i=0;iptr,source,bytes); + b->ptr+=bytes; + b->endbyte+=bytes; + *b->ptr=0; + } + + /* copy trailing bits */ + if(bits){ + if(msb) + w(b,(unsigned long)(ptr[bytes]>>(8-bits)),bits); + else + w(b,(unsigned long)(ptr[bytes]),bits); + } + return; + err: + oggpack_writeclear(b); +} + +void oggpack_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpack_write,0); +} + +void oggpackB_writecopy(oggpack_buffer *b,void *source,long bits){ + oggpack_writecopy_helper(b,source,bits,oggpackB_write,1); +} + +void oggpack_reset(oggpack_buffer *b){ + if(!b->ptr)return; + b->ptr=b->buffer; + b->buffer[0]=0; + b->endbit=b->endbyte=0; +} + +void oggpackB_reset(oggpack_buffer *b){ + oggpack_reset(b); +} + +void oggpack_writeclear(oggpack_buffer *b){ + if(b->buffer)_ogg_free(b->buffer); + memset(b,0,sizeof(*b)); +} + +void oggpackB_writeclear(oggpack_buffer *b){ + oggpack_writeclear(b); +} + +void oggpack_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + memset(b,0,sizeof(*b)); + b->buffer=b->ptr=buf; + b->storage=bytes; +} + +void oggpackB_readinit(oggpack_buffer *b,unsigned char *buf,int bytes){ + oggpack_readinit(b,buf,bytes); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpack_look(oggpack_buffer *b,int bits){ + unsigned long ret; + unsigned long m; + + if(bits<0 || bits>32) return -1; + m=mask[bits]; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) return -1; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + return(m&ret); +} + +/* Read in bits without advancing the bitptr; bits <= 32 */ +long oggpackB_look(oggpack_buffer *b,int bits){ + unsigned long ret; + int m=32-bits; + + if(m<0 || m>32) return -1; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) return -1; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + return ((ret&0xffffffff)>>(m>>1))>>((m+1)>>1); +} + +long oggpack_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>b->endbit)&1); +} + +long oggpackB_look1(oggpack_buffer *b){ + if(b->endbyte>=b->storage)return(-1); + return((b->ptr[0]>>(7-b->endbit))&1); +} + +void oggpack_adv(oggpack_buffer *b,int bits){ + bits+=b->endbit; + + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; +} + +void oggpackB_adv(oggpack_buffer *b,int bits){ + oggpack_adv(b,bits); +} + +void oggpack_adv1(oggpack_buffer *b){ + if(++(b->endbit)>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } +} + +void oggpackB_adv1(oggpack_buffer *b){ + oggpack_adv1(b); +} + +/* bits <= 32 */ +long oggpack_read(oggpack_buffer *b,int bits){ + long ret; + unsigned long m; + + if(bits<0 || bits>32) goto err; + m=mask[bits]; + bits+=b->endbit; + + if(b->endbyte >= b->storage-4){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]>>b->endbit; + if(bits>8){ + ret|=b->ptr[1]<<(8-b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(16-b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(24-b->endbit); + if(bits>32 && b->endbit){ + ret|=b->ptr[4]<<(32-b->endbit); + } + } + } + } + ret&=m; + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return ret; + + overflow: + err: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +/* bits <= 32 */ +long oggpackB_read(oggpack_buffer *b,int bits){ + long ret; + long m=32-bits; + + if(m<0 || m>32) goto err; + bits+=b->endbit; + + if(b->endbyte+4>=b->storage){ + /* not the main path */ + if(b->endbyte > b->storage-((bits+7)>>3)) goto overflow; + /* special case to avoid reading b->ptr[0], which might be past the end of + the buffer; also skips some useless accounting */ + else if(!bits)return(0L); + } + + ret=b->ptr[0]<<(24+b->endbit); + if(bits>8){ + ret|=b->ptr[1]<<(16+b->endbit); + if(bits>16){ + ret|=b->ptr[2]<<(8+b->endbit); + if(bits>24){ + ret|=b->ptr[3]<<(b->endbit); + if(bits>32 && b->endbit) + ret|=b->ptr[4]>>(8-b->endbit); + } + } + } + ret=((ret&0xffffffffUL)>>(m>>1))>>((m+1)>>1); + + b->ptr+=bits/8; + b->endbyte+=bits/8; + b->endbit=bits&7; + return ret; + + overflow: + err: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpack_read1(oggpack_buffer *b){ + long ret; + + if(b->endbyte >= b->storage) goto overflow; + ret=(b->ptr[0]>>b->endbit)&1; + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return ret; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpackB_read1(oggpack_buffer *b){ + long ret; + + if(b->endbyte >= b->storage) goto overflow; + ret=(b->ptr[0]>>(7-b->endbit))&1; + + b->endbit++; + if(b->endbit>7){ + b->endbit=0; + b->ptr++; + b->endbyte++; + } + return ret; + + overflow: + b->ptr=NULL; + b->endbyte=b->storage; + b->endbit=1; + return -1L; +} + +long oggpack_bytes(oggpack_buffer *b){ + return(b->endbyte+(b->endbit+7)/8); +} + +long oggpack_bits(oggpack_buffer *b){ + return(b->endbyte*8+b->endbit); +} + +long oggpackB_bytes(oggpack_buffer *b){ + return oggpack_bytes(b); +} + +long oggpackB_bits(oggpack_buffer *b){ + return oggpack_bits(b); +} + +unsigned char *oggpack_get_buffer(oggpack_buffer *b){ + return(b->buffer); +} + +unsigned char *oggpackB_get_buffer(oggpack_buffer *b){ + return oggpack_get_buffer(b); +} + +/* Self test of the bitwise routines; everything else is based on + them, so they damned well better be solid. */ + +#ifdef _V_SELFTEST +#include + +static int ilog(unsigned int v){ + int ret=0; + while(v){ + ret++; + v>>=1; + } + return(ret); +} + +oggpack_buffer o; +oggpack_buffer r; + +void report(char *in){ + fprintf(stderr,"%s",in); + exit(1); +} + +void cliptest(unsigned long *b,int vals,int bits,int *comp,int compsize){ + long bytes,i; + unsigned char *buffer; + + oggpack_reset(&o); + for(i=0;i + +static const ogg_uint32_t crc_lookup[8][256]={ +{0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9,0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005, + 0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61,0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd, + 0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9,0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75, + 0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011,0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd, + 0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039,0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5, + 0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81,0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d, + 0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49,0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95, + 0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1,0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d, + 0x34867077,0x30476dc0,0x3d044b19,0x39c556ae,0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072, + 0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16,0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca, + 0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde,0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02, + 0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066,0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba, + 0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e,0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692, + 0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6,0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a, + 0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e,0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2, + 0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686,0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a, + 0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637,0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb, + 0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f,0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53, + 0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47,0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b, + 0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff,0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623, + 0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7,0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b, + 0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f,0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3, + 0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7,0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b, + 0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f,0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3, + 0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640,0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c, + 0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8,0x68860bfd,0x6c47164a,0x61043093,0x65c52d24, + 0x119b4be9,0x155a565e,0x18197087,0x1cd86d30,0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec, + 0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088,0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654, + 0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0,0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c, + 0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18,0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4, + 0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0,0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c, + 0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668,0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4}, + +{0x00000000,0xd219c1dc,0xa0f29e0f,0x72eb5fd3,0x452421a9,0x973de075,0xe5d6bfa6,0x37cf7e7a, + 0x8a484352,0x5851828e,0x2abadd5d,0xf8a31c81,0xcf6c62fb,0x1d75a327,0x6f9efcf4,0xbd873d28, + 0x10519b13,0xc2485acf,0xb0a3051c,0x62bac4c0,0x5575baba,0x876c7b66,0xf58724b5,0x279ee569, + 0x9a19d841,0x4800199d,0x3aeb464e,0xe8f28792,0xdf3df9e8,0x0d243834,0x7fcf67e7,0xadd6a63b, + 0x20a33626,0xf2baf7fa,0x8051a829,0x524869f5,0x6587178f,0xb79ed653,0xc5758980,0x176c485c, + 0xaaeb7574,0x78f2b4a8,0x0a19eb7b,0xd8002aa7,0xefcf54dd,0x3dd69501,0x4f3dcad2,0x9d240b0e, + 0x30f2ad35,0xe2eb6ce9,0x9000333a,0x4219f2e6,0x75d68c9c,0xa7cf4d40,0xd5241293,0x073dd34f, + 0xbabaee67,0x68a32fbb,0x1a487068,0xc851b1b4,0xff9ecfce,0x2d870e12,0x5f6c51c1,0x8d75901d, + 0x41466c4c,0x935fad90,0xe1b4f243,0x33ad339f,0x04624de5,0xd67b8c39,0xa490d3ea,0x76891236, + 0xcb0e2f1e,0x1917eec2,0x6bfcb111,0xb9e570cd,0x8e2a0eb7,0x5c33cf6b,0x2ed890b8,0xfcc15164, + 0x5117f75f,0x830e3683,0xf1e56950,0x23fca88c,0x1433d6f6,0xc62a172a,0xb4c148f9,0x66d88925, + 0xdb5fb40d,0x094675d1,0x7bad2a02,0xa9b4ebde,0x9e7b95a4,0x4c625478,0x3e890bab,0xec90ca77, + 0x61e55a6a,0xb3fc9bb6,0xc117c465,0x130e05b9,0x24c17bc3,0xf6d8ba1f,0x8433e5cc,0x562a2410, + 0xebad1938,0x39b4d8e4,0x4b5f8737,0x994646eb,0xae893891,0x7c90f94d,0x0e7ba69e,0xdc626742, + 0x71b4c179,0xa3ad00a5,0xd1465f76,0x035f9eaa,0x3490e0d0,0xe689210c,0x94627edf,0x467bbf03, + 0xfbfc822b,0x29e543f7,0x5b0e1c24,0x8917ddf8,0xbed8a382,0x6cc1625e,0x1e2a3d8d,0xcc33fc51, + 0x828cd898,0x50951944,0x227e4697,0xf067874b,0xc7a8f931,0x15b138ed,0x675a673e,0xb543a6e2, + 0x08c49bca,0xdadd5a16,0xa83605c5,0x7a2fc419,0x4de0ba63,0x9ff97bbf,0xed12246c,0x3f0be5b0, + 0x92dd438b,0x40c48257,0x322fdd84,0xe0361c58,0xd7f96222,0x05e0a3fe,0x770bfc2d,0xa5123df1, + 0x189500d9,0xca8cc105,0xb8679ed6,0x6a7e5f0a,0x5db12170,0x8fa8e0ac,0xfd43bf7f,0x2f5a7ea3, + 0xa22feebe,0x70362f62,0x02dd70b1,0xd0c4b16d,0xe70bcf17,0x35120ecb,0x47f95118,0x95e090c4, + 0x2867adec,0xfa7e6c30,0x889533e3,0x5a8cf23f,0x6d438c45,0xbf5a4d99,0xcdb1124a,0x1fa8d396, + 0xb27e75ad,0x6067b471,0x128ceba2,0xc0952a7e,0xf75a5404,0x254395d8,0x57a8ca0b,0x85b10bd7, + 0x383636ff,0xea2ff723,0x98c4a8f0,0x4add692c,0x7d121756,0xaf0bd68a,0xdde08959,0x0ff94885, + 0xc3cab4d4,0x11d37508,0x63382adb,0xb121eb07,0x86ee957d,0x54f754a1,0x261c0b72,0xf405caae, + 0x4982f786,0x9b9b365a,0xe9706989,0x3b69a855,0x0ca6d62f,0xdebf17f3,0xac544820,0x7e4d89fc, + 0xd39b2fc7,0x0182ee1b,0x7369b1c8,0xa1707014,0x96bf0e6e,0x44a6cfb2,0x364d9061,0xe45451bd, + 0x59d36c95,0x8bcaad49,0xf921f29a,0x2b383346,0x1cf74d3c,0xceee8ce0,0xbc05d333,0x6e1c12ef, + 0xe36982f2,0x3170432e,0x439b1cfd,0x9182dd21,0xa64da35b,0x74546287,0x06bf3d54,0xd4a6fc88, + 0x6921c1a0,0xbb38007c,0xc9d35faf,0x1bca9e73,0x2c05e009,0xfe1c21d5,0x8cf77e06,0x5eeebfda, + 0xf33819e1,0x2121d83d,0x53ca87ee,0x81d34632,0xb61c3848,0x6405f994,0x16eea647,0xc4f7679b, + 0x79705ab3,0xab699b6f,0xd982c4bc,0x0b9b0560,0x3c547b1a,0xee4dbac6,0x9ca6e515,0x4ebf24c9}, + +{0x00000000,0x01d8ac87,0x03b1590e,0x0269f589,0x0762b21c,0x06ba1e9b,0x04d3eb12,0x050b4795, + 0x0ec56438,0x0f1dc8bf,0x0d743d36,0x0cac91b1,0x09a7d624,0x087f7aa3,0x0a168f2a,0x0bce23ad, + 0x1d8ac870,0x1c5264f7,0x1e3b917e,0x1fe33df9,0x1ae87a6c,0x1b30d6eb,0x19592362,0x18818fe5, + 0x134fac48,0x129700cf,0x10fef546,0x112659c1,0x142d1e54,0x15f5b2d3,0x179c475a,0x1644ebdd, + 0x3b1590e0,0x3acd3c67,0x38a4c9ee,0x397c6569,0x3c7722fc,0x3daf8e7b,0x3fc67bf2,0x3e1ed775, + 0x35d0f4d8,0x3408585f,0x3661add6,0x37b90151,0x32b246c4,0x336aea43,0x31031fca,0x30dbb34d, + 0x269f5890,0x2747f417,0x252e019e,0x24f6ad19,0x21fdea8c,0x2025460b,0x224cb382,0x23941f05, + 0x285a3ca8,0x2982902f,0x2beb65a6,0x2a33c921,0x2f388eb4,0x2ee02233,0x2c89d7ba,0x2d517b3d, + 0x762b21c0,0x77f38d47,0x759a78ce,0x7442d449,0x714993dc,0x70913f5b,0x72f8cad2,0x73206655, + 0x78ee45f8,0x7936e97f,0x7b5f1cf6,0x7a87b071,0x7f8cf7e4,0x7e545b63,0x7c3daeea,0x7de5026d, + 0x6ba1e9b0,0x6a794537,0x6810b0be,0x69c81c39,0x6cc35bac,0x6d1bf72b,0x6f7202a2,0x6eaaae25, + 0x65648d88,0x64bc210f,0x66d5d486,0x670d7801,0x62063f94,0x63de9313,0x61b7669a,0x606fca1d, + 0x4d3eb120,0x4ce61da7,0x4e8fe82e,0x4f5744a9,0x4a5c033c,0x4b84afbb,0x49ed5a32,0x4835f6b5, + 0x43fbd518,0x4223799f,0x404a8c16,0x41922091,0x44996704,0x4541cb83,0x47283e0a,0x46f0928d, + 0x50b47950,0x516cd5d7,0x5305205e,0x52dd8cd9,0x57d6cb4c,0x560e67cb,0x54679242,0x55bf3ec5, + 0x5e711d68,0x5fa9b1ef,0x5dc04466,0x5c18e8e1,0x5913af74,0x58cb03f3,0x5aa2f67a,0x5b7a5afd, + 0xec564380,0xed8eef07,0xefe71a8e,0xee3fb609,0xeb34f19c,0xeaec5d1b,0xe885a892,0xe95d0415, + 0xe29327b8,0xe34b8b3f,0xe1227eb6,0xe0fad231,0xe5f195a4,0xe4293923,0xe640ccaa,0xe798602d, + 0xf1dc8bf0,0xf0042777,0xf26dd2fe,0xf3b57e79,0xf6be39ec,0xf766956b,0xf50f60e2,0xf4d7cc65, + 0xff19efc8,0xfec1434f,0xfca8b6c6,0xfd701a41,0xf87b5dd4,0xf9a3f153,0xfbca04da,0xfa12a85d, + 0xd743d360,0xd69b7fe7,0xd4f28a6e,0xd52a26e9,0xd021617c,0xd1f9cdfb,0xd3903872,0xd24894f5, + 0xd986b758,0xd85e1bdf,0xda37ee56,0xdbef42d1,0xdee40544,0xdf3ca9c3,0xdd555c4a,0xdc8df0cd, + 0xcac91b10,0xcb11b797,0xc978421e,0xc8a0ee99,0xcdaba90c,0xcc73058b,0xce1af002,0xcfc25c85, + 0xc40c7f28,0xc5d4d3af,0xc7bd2626,0xc6658aa1,0xc36ecd34,0xc2b661b3,0xc0df943a,0xc10738bd, + 0x9a7d6240,0x9ba5cec7,0x99cc3b4e,0x981497c9,0x9d1fd05c,0x9cc77cdb,0x9eae8952,0x9f7625d5, + 0x94b80678,0x9560aaff,0x97095f76,0x96d1f3f1,0x93dab464,0x920218e3,0x906bed6a,0x91b341ed, + 0x87f7aa30,0x862f06b7,0x8446f33e,0x859e5fb9,0x8095182c,0x814db4ab,0x83244122,0x82fceda5, + 0x8932ce08,0x88ea628f,0x8a839706,0x8b5b3b81,0x8e507c14,0x8f88d093,0x8de1251a,0x8c39899d, + 0xa168f2a0,0xa0b05e27,0xa2d9abae,0xa3010729,0xa60a40bc,0xa7d2ec3b,0xa5bb19b2,0xa463b535, + 0xafad9698,0xae753a1f,0xac1ccf96,0xadc46311,0xa8cf2484,0xa9178803,0xab7e7d8a,0xaaa6d10d, + 0xbce23ad0,0xbd3a9657,0xbf5363de,0xbe8bcf59,0xbb8088cc,0xba58244b,0xb831d1c2,0xb9e97d45, + 0xb2275ee8,0xb3fff26f,0xb19607e6,0xb04eab61,0xb545ecf4,0xb49d4073,0xb6f4b5fa,0xb72c197d}, + +{0x00000000,0xdc6d9ab7,0xbc1a28d9,0x6077b26e,0x7cf54c05,0xa098d6b2,0xc0ef64dc,0x1c82fe6b, + 0xf9ea980a,0x258702bd,0x45f0b0d3,0x999d2a64,0x851fd40f,0x59724eb8,0x3905fcd6,0xe5686661, + 0xf7142da3,0x2b79b714,0x4b0e057a,0x97639fcd,0x8be161a6,0x578cfb11,0x37fb497f,0xeb96d3c8, + 0x0efeb5a9,0xd2932f1e,0xb2e49d70,0x6e8907c7,0x720bf9ac,0xae66631b,0xce11d175,0x127c4bc2, + 0xeae946f1,0x3684dc46,0x56f36e28,0x8a9ef49f,0x961c0af4,0x4a719043,0x2a06222d,0xf66bb89a, + 0x1303defb,0xcf6e444c,0xaf19f622,0x73746c95,0x6ff692fe,0xb39b0849,0xd3ecba27,0x0f812090, + 0x1dfd6b52,0xc190f1e5,0xa1e7438b,0x7d8ad93c,0x61082757,0xbd65bde0,0xdd120f8e,0x017f9539, + 0xe417f358,0x387a69ef,0x580ddb81,0x84604136,0x98e2bf5d,0x448f25ea,0x24f89784,0xf8950d33, + 0xd1139055,0x0d7e0ae2,0x6d09b88c,0xb164223b,0xade6dc50,0x718b46e7,0x11fcf489,0xcd916e3e, + 0x28f9085f,0xf49492e8,0x94e32086,0x488eba31,0x540c445a,0x8861deed,0xe8166c83,0x347bf634, + 0x2607bdf6,0xfa6a2741,0x9a1d952f,0x46700f98,0x5af2f1f3,0x869f6b44,0xe6e8d92a,0x3a85439d, + 0xdfed25fc,0x0380bf4b,0x63f70d25,0xbf9a9792,0xa31869f9,0x7f75f34e,0x1f024120,0xc36fdb97, + 0x3bfad6a4,0xe7974c13,0x87e0fe7d,0x5b8d64ca,0x470f9aa1,0x9b620016,0xfb15b278,0x277828cf, + 0xc2104eae,0x1e7dd419,0x7e0a6677,0xa267fcc0,0xbee502ab,0x6288981c,0x02ff2a72,0xde92b0c5, + 0xcceefb07,0x108361b0,0x70f4d3de,0xac994969,0xb01bb702,0x6c762db5,0x0c019fdb,0xd06c056c, + 0x3504630d,0xe969f9ba,0x891e4bd4,0x5573d163,0x49f12f08,0x959cb5bf,0xf5eb07d1,0x29869d66, + 0xa6e63d1d,0x7a8ba7aa,0x1afc15c4,0xc6918f73,0xda137118,0x067eebaf,0x660959c1,0xba64c376, + 0x5f0ca517,0x83613fa0,0xe3168dce,0x3f7b1779,0x23f9e912,0xff9473a5,0x9fe3c1cb,0x438e5b7c, + 0x51f210be,0x8d9f8a09,0xede83867,0x3185a2d0,0x2d075cbb,0xf16ac60c,0x911d7462,0x4d70eed5, + 0xa81888b4,0x74751203,0x1402a06d,0xc86f3ada,0xd4edc4b1,0x08805e06,0x68f7ec68,0xb49a76df, + 0x4c0f7bec,0x9062e15b,0xf0155335,0x2c78c982,0x30fa37e9,0xec97ad5e,0x8ce01f30,0x508d8587, + 0xb5e5e3e6,0x69887951,0x09ffcb3f,0xd5925188,0xc910afe3,0x157d3554,0x750a873a,0xa9671d8d, + 0xbb1b564f,0x6776ccf8,0x07017e96,0xdb6ce421,0xc7ee1a4a,0x1b8380fd,0x7bf43293,0xa799a824, + 0x42f1ce45,0x9e9c54f2,0xfeebe69c,0x22867c2b,0x3e048240,0xe26918f7,0x821eaa99,0x5e73302e, + 0x77f5ad48,0xab9837ff,0xcbef8591,0x17821f26,0x0b00e14d,0xd76d7bfa,0xb71ac994,0x6b775323, + 0x8e1f3542,0x5272aff5,0x32051d9b,0xee68872c,0xf2ea7947,0x2e87e3f0,0x4ef0519e,0x929dcb29, + 0x80e180eb,0x5c8c1a5c,0x3cfba832,0xe0963285,0xfc14ccee,0x20795659,0x400ee437,0x9c637e80, + 0x790b18e1,0xa5668256,0xc5113038,0x197caa8f,0x05fe54e4,0xd993ce53,0xb9e47c3d,0x6589e68a, + 0x9d1cebb9,0x4171710e,0x2106c360,0xfd6b59d7,0xe1e9a7bc,0x3d843d0b,0x5df38f65,0x819e15d2, + 0x64f673b3,0xb89be904,0xd8ec5b6a,0x0481c1dd,0x18033fb6,0xc46ea501,0xa419176f,0x78748dd8, + 0x6a08c61a,0xb6655cad,0xd612eec3,0x0a7f7474,0x16fd8a1f,0xca9010a8,0xaae7a2c6,0x768a3871, + 0x93e25e10,0x4f8fc4a7,0x2ff876c9,0xf395ec7e,0xef171215,0x337a88a2,0x530d3acc,0x8f60a07b}, + +{0x00000000,0x490d678d,0x921acf1a,0xdb17a897,0x20f48383,0x69f9e40e,0xb2ee4c99,0xfbe32b14, + 0x41e90706,0x08e4608b,0xd3f3c81c,0x9afeaf91,0x611d8485,0x2810e308,0xf3074b9f,0xba0a2c12, + 0x83d20e0c,0xcadf6981,0x11c8c116,0x58c5a69b,0xa3268d8f,0xea2bea02,0x313c4295,0x78312518, + 0xc23b090a,0x8b366e87,0x5021c610,0x192ca19d,0xe2cf8a89,0xabc2ed04,0x70d54593,0x39d8221e, + 0x036501af,0x4a686622,0x917fceb5,0xd872a938,0x2391822c,0x6a9ce5a1,0xb18b4d36,0xf8862abb, + 0x428c06a9,0x0b816124,0xd096c9b3,0x999bae3e,0x6278852a,0x2b75e2a7,0xf0624a30,0xb96f2dbd, + 0x80b70fa3,0xc9ba682e,0x12adc0b9,0x5ba0a734,0xa0438c20,0xe94eebad,0x3259433a,0x7b5424b7, + 0xc15e08a5,0x88536f28,0x5344c7bf,0x1a49a032,0xe1aa8b26,0xa8a7ecab,0x73b0443c,0x3abd23b1, + 0x06ca035e,0x4fc764d3,0x94d0cc44,0xddddabc9,0x263e80dd,0x6f33e750,0xb4244fc7,0xfd29284a, + 0x47230458,0x0e2e63d5,0xd539cb42,0x9c34accf,0x67d787db,0x2edae056,0xf5cd48c1,0xbcc02f4c, + 0x85180d52,0xcc156adf,0x1702c248,0x5e0fa5c5,0xa5ec8ed1,0xece1e95c,0x37f641cb,0x7efb2646, + 0xc4f10a54,0x8dfc6dd9,0x56ebc54e,0x1fe6a2c3,0xe40589d7,0xad08ee5a,0x761f46cd,0x3f122140, + 0x05af02f1,0x4ca2657c,0x97b5cdeb,0xdeb8aa66,0x255b8172,0x6c56e6ff,0xb7414e68,0xfe4c29e5, + 0x444605f7,0x0d4b627a,0xd65ccaed,0x9f51ad60,0x64b28674,0x2dbfe1f9,0xf6a8496e,0xbfa52ee3, + 0x867d0cfd,0xcf706b70,0x1467c3e7,0x5d6aa46a,0xa6898f7e,0xef84e8f3,0x34934064,0x7d9e27e9, + 0xc7940bfb,0x8e996c76,0x558ec4e1,0x1c83a36c,0xe7608878,0xae6deff5,0x757a4762,0x3c7720ef, + 0x0d9406bc,0x44996131,0x9f8ec9a6,0xd683ae2b,0x2d60853f,0x646de2b2,0xbf7a4a25,0xf6772da8, + 0x4c7d01ba,0x05706637,0xde67cea0,0x976aa92d,0x6c898239,0x2584e5b4,0xfe934d23,0xb79e2aae, + 0x8e4608b0,0xc74b6f3d,0x1c5cc7aa,0x5551a027,0xaeb28b33,0xe7bfecbe,0x3ca84429,0x75a523a4, + 0xcfaf0fb6,0x86a2683b,0x5db5c0ac,0x14b8a721,0xef5b8c35,0xa656ebb8,0x7d41432f,0x344c24a2, + 0x0ef10713,0x47fc609e,0x9cebc809,0xd5e6af84,0x2e058490,0x6708e31d,0xbc1f4b8a,0xf5122c07, + 0x4f180015,0x06156798,0xdd02cf0f,0x940fa882,0x6fec8396,0x26e1e41b,0xfdf64c8c,0xb4fb2b01, + 0x8d23091f,0xc42e6e92,0x1f39c605,0x5634a188,0xadd78a9c,0xe4daed11,0x3fcd4586,0x76c0220b, + 0xccca0e19,0x85c76994,0x5ed0c103,0x17dda68e,0xec3e8d9a,0xa533ea17,0x7e244280,0x3729250d, + 0x0b5e05e2,0x4253626f,0x9944caf8,0xd049ad75,0x2baa8661,0x62a7e1ec,0xb9b0497b,0xf0bd2ef6, + 0x4ab702e4,0x03ba6569,0xd8adcdfe,0x91a0aa73,0x6a438167,0x234ee6ea,0xf8594e7d,0xb15429f0, + 0x888c0bee,0xc1816c63,0x1a96c4f4,0x539ba379,0xa878886d,0xe175efe0,0x3a624777,0x736f20fa, + 0xc9650ce8,0x80686b65,0x5b7fc3f2,0x1272a47f,0xe9918f6b,0xa09ce8e6,0x7b8b4071,0x328627fc, + 0x083b044d,0x413663c0,0x9a21cb57,0xd32cacda,0x28cf87ce,0x61c2e043,0xbad548d4,0xf3d82f59, + 0x49d2034b,0x00df64c6,0xdbc8cc51,0x92c5abdc,0x692680c8,0x202be745,0xfb3c4fd2,0xb231285f, + 0x8be90a41,0xc2e46dcc,0x19f3c55b,0x50fea2d6,0xab1d89c2,0xe210ee4f,0x390746d8,0x700a2155, + 0xca000d47,0x830d6aca,0x581ac25d,0x1117a5d0,0xeaf48ec4,0xa3f9e949,0x78ee41de,0x31e32653}, + +{0x00000000,0x1b280d78,0x36501af0,0x2d781788,0x6ca035e0,0x77883898,0x5af02f10,0x41d82268, + 0xd9406bc0,0xc26866b8,0xef107130,0xf4387c48,0xb5e05e20,0xaec85358,0x83b044d0,0x989849a8, + 0xb641ca37,0xad69c74f,0x8011d0c7,0x9b39ddbf,0xdae1ffd7,0xc1c9f2af,0xecb1e527,0xf799e85f, + 0x6f01a1f7,0x7429ac8f,0x5951bb07,0x4279b67f,0x03a19417,0x1889996f,0x35f18ee7,0x2ed9839f, + 0x684289d9,0x736a84a1,0x5e129329,0x453a9e51,0x04e2bc39,0x1fcab141,0x32b2a6c9,0x299aabb1, + 0xb102e219,0xaa2aef61,0x8752f8e9,0x9c7af591,0xdda2d7f9,0xc68ada81,0xebf2cd09,0xf0dac071, + 0xde0343ee,0xc52b4e96,0xe853591e,0xf37b5466,0xb2a3760e,0xa98b7b76,0x84f36cfe,0x9fdb6186, + 0x0743282e,0x1c6b2556,0x311332de,0x2a3b3fa6,0x6be31dce,0x70cb10b6,0x5db3073e,0x469b0a46, + 0xd08513b2,0xcbad1eca,0xe6d50942,0xfdfd043a,0xbc252652,0xa70d2b2a,0x8a753ca2,0x915d31da, + 0x09c57872,0x12ed750a,0x3f956282,0x24bd6ffa,0x65654d92,0x7e4d40ea,0x53355762,0x481d5a1a, + 0x66c4d985,0x7decd4fd,0x5094c375,0x4bbcce0d,0x0a64ec65,0x114ce11d,0x3c34f695,0x271cfbed, + 0xbf84b245,0xa4acbf3d,0x89d4a8b5,0x92fca5cd,0xd32487a5,0xc80c8add,0xe5749d55,0xfe5c902d, + 0xb8c79a6b,0xa3ef9713,0x8e97809b,0x95bf8de3,0xd467af8b,0xcf4fa2f3,0xe237b57b,0xf91fb803, + 0x6187f1ab,0x7aaffcd3,0x57d7eb5b,0x4cffe623,0x0d27c44b,0x160fc933,0x3b77debb,0x205fd3c3, + 0x0e86505c,0x15ae5d24,0x38d64aac,0x23fe47d4,0x622665bc,0x790e68c4,0x54767f4c,0x4f5e7234, + 0xd7c63b9c,0xccee36e4,0xe196216c,0xfabe2c14,0xbb660e7c,0xa04e0304,0x8d36148c,0x961e19f4, + 0xa5cb3ad3,0xbee337ab,0x939b2023,0x88b32d5b,0xc96b0f33,0xd243024b,0xff3b15c3,0xe41318bb, + 0x7c8b5113,0x67a35c6b,0x4adb4be3,0x51f3469b,0x102b64f3,0x0b03698b,0x267b7e03,0x3d53737b, + 0x138af0e4,0x08a2fd9c,0x25daea14,0x3ef2e76c,0x7f2ac504,0x6402c87c,0x497adff4,0x5252d28c, + 0xcaca9b24,0xd1e2965c,0xfc9a81d4,0xe7b28cac,0xa66aaec4,0xbd42a3bc,0x903ab434,0x8b12b94c, + 0xcd89b30a,0xd6a1be72,0xfbd9a9fa,0xe0f1a482,0xa12986ea,0xba018b92,0x97799c1a,0x8c519162, + 0x14c9d8ca,0x0fe1d5b2,0x2299c23a,0x39b1cf42,0x7869ed2a,0x6341e052,0x4e39f7da,0x5511faa2, + 0x7bc8793d,0x60e07445,0x4d9863cd,0x56b06eb5,0x17684cdd,0x0c4041a5,0x2138562d,0x3a105b55, + 0xa28812fd,0xb9a01f85,0x94d8080d,0x8ff00575,0xce28271d,0xd5002a65,0xf8783ded,0xe3503095, + 0x754e2961,0x6e662419,0x431e3391,0x58363ee9,0x19ee1c81,0x02c611f9,0x2fbe0671,0x34960b09, + 0xac0e42a1,0xb7264fd9,0x9a5e5851,0x81765529,0xc0ae7741,0xdb867a39,0xf6fe6db1,0xedd660c9, + 0xc30fe356,0xd827ee2e,0xf55ff9a6,0xee77f4de,0xafafd6b6,0xb487dbce,0x99ffcc46,0x82d7c13e, + 0x1a4f8896,0x016785ee,0x2c1f9266,0x37379f1e,0x76efbd76,0x6dc7b00e,0x40bfa786,0x5b97aafe, + 0x1d0ca0b8,0x0624adc0,0x2b5cba48,0x3074b730,0x71ac9558,0x6a849820,0x47fc8fa8,0x5cd482d0, + 0xc44ccb78,0xdf64c600,0xf21cd188,0xe934dcf0,0xa8ecfe98,0xb3c4f3e0,0x9ebce468,0x8594e910, + 0xab4d6a8f,0xb06567f7,0x9d1d707f,0x86357d07,0xc7ed5f6f,0xdcc55217,0xf1bd459f,0xea9548e7, + 0x720d014f,0x69250c37,0x445d1bbf,0x5f7516c7,0x1ead34af,0x058539d7,0x28fd2e5f,0x33d52327}, + +{0x00000000,0x4f576811,0x9eaed022,0xd1f9b833,0x399cbdf3,0x76cbd5e2,0xa7326dd1,0xe86505c0, + 0x73397be6,0x3c6e13f7,0xed97abc4,0xa2c0c3d5,0x4aa5c615,0x05f2ae04,0xd40b1637,0x9b5c7e26, + 0xe672f7cc,0xa9259fdd,0x78dc27ee,0x378b4fff,0xdfee4a3f,0x90b9222e,0x41409a1d,0x0e17f20c, + 0x954b8c2a,0xda1ce43b,0x0be55c08,0x44b23419,0xacd731d9,0xe38059c8,0x3279e1fb,0x7d2e89ea, + 0xc824f22f,0x87739a3e,0x568a220d,0x19dd4a1c,0xf1b84fdc,0xbeef27cd,0x6f169ffe,0x2041f7ef, + 0xbb1d89c9,0xf44ae1d8,0x25b359eb,0x6ae431fa,0x8281343a,0xcdd65c2b,0x1c2fe418,0x53788c09, + 0x2e5605e3,0x61016df2,0xb0f8d5c1,0xffafbdd0,0x17cab810,0x589dd001,0x89646832,0xc6330023, + 0x5d6f7e05,0x12381614,0xc3c1ae27,0x8c96c636,0x64f3c3f6,0x2ba4abe7,0xfa5d13d4,0xb50a7bc5, + 0x9488f9e9,0xdbdf91f8,0x0a2629cb,0x457141da,0xad14441a,0xe2432c0b,0x33ba9438,0x7cedfc29, + 0xe7b1820f,0xa8e6ea1e,0x791f522d,0x36483a3c,0xde2d3ffc,0x917a57ed,0x4083efde,0x0fd487cf, + 0x72fa0e25,0x3dad6634,0xec54de07,0xa303b616,0x4b66b3d6,0x0431dbc7,0xd5c863f4,0x9a9f0be5, + 0x01c375c3,0x4e941dd2,0x9f6da5e1,0xd03acdf0,0x385fc830,0x7708a021,0xa6f11812,0xe9a67003, + 0x5cac0bc6,0x13fb63d7,0xc202dbe4,0x8d55b3f5,0x6530b635,0x2a67de24,0xfb9e6617,0xb4c90e06, + 0x2f957020,0x60c21831,0xb13ba002,0xfe6cc813,0x1609cdd3,0x595ea5c2,0x88a71df1,0xc7f075e0, + 0xbadefc0a,0xf589941b,0x24702c28,0x6b274439,0x834241f9,0xcc1529e8,0x1dec91db,0x52bbf9ca, + 0xc9e787ec,0x86b0effd,0x574957ce,0x181e3fdf,0xf07b3a1f,0xbf2c520e,0x6ed5ea3d,0x2182822c, + 0x2dd0ee65,0x62878674,0xb37e3e47,0xfc295656,0x144c5396,0x5b1b3b87,0x8ae283b4,0xc5b5eba5, + 0x5ee99583,0x11befd92,0xc04745a1,0x8f102db0,0x67752870,0x28224061,0xf9dbf852,0xb68c9043, + 0xcba219a9,0x84f571b8,0x550cc98b,0x1a5ba19a,0xf23ea45a,0xbd69cc4b,0x6c907478,0x23c71c69, + 0xb89b624f,0xf7cc0a5e,0x2635b26d,0x6962da7c,0x8107dfbc,0xce50b7ad,0x1fa90f9e,0x50fe678f, + 0xe5f41c4a,0xaaa3745b,0x7b5acc68,0x340da479,0xdc68a1b9,0x933fc9a8,0x42c6719b,0x0d91198a, + 0x96cd67ac,0xd99a0fbd,0x0863b78e,0x4734df9f,0xaf51da5f,0xe006b24e,0x31ff0a7d,0x7ea8626c, + 0x0386eb86,0x4cd18397,0x9d283ba4,0xd27f53b5,0x3a1a5675,0x754d3e64,0xa4b48657,0xebe3ee46, + 0x70bf9060,0x3fe8f871,0xee114042,0xa1462853,0x49232d93,0x06744582,0xd78dfdb1,0x98da95a0, + 0xb958178c,0xf60f7f9d,0x27f6c7ae,0x68a1afbf,0x80c4aa7f,0xcf93c26e,0x1e6a7a5d,0x513d124c, + 0xca616c6a,0x8536047b,0x54cfbc48,0x1b98d459,0xf3fdd199,0xbcaab988,0x6d5301bb,0x220469aa, + 0x5f2ae040,0x107d8851,0xc1843062,0x8ed35873,0x66b65db3,0x29e135a2,0xf8188d91,0xb74fe580, + 0x2c139ba6,0x6344f3b7,0xb2bd4b84,0xfdea2395,0x158f2655,0x5ad84e44,0x8b21f677,0xc4769e66, + 0x717ce5a3,0x3e2b8db2,0xefd23581,0xa0855d90,0x48e05850,0x07b73041,0xd64e8872,0x9919e063, + 0x02459e45,0x4d12f654,0x9ceb4e67,0xd3bc2676,0x3bd923b6,0x748e4ba7,0xa577f394,0xea209b85, + 0x970e126f,0xd8597a7e,0x09a0c24d,0x46f7aa5c,0xae92af9c,0xe1c5c78d,0x303c7fbe,0x7f6b17af, + 0xe4376989,0xab600198,0x7a99b9ab,0x35ced1ba,0xddabd47a,0x92fcbc6b,0x43050458,0x0c526c49}, + +{0x00000000,0x5ba1dcca,0xb743b994,0xece2655e,0x6a466e9f,0x31e7b255,0xdd05d70b,0x86a40bc1, + 0xd48cdd3e,0x8f2d01f4,0x63cf64aa,0x386eb860,0xbecab3a1,0xe56b6f6b,0x09890a35,0x5228d6ff, + 0xadd8a7cb,0xf6797b01,0x1a9b1e5f,0x413ac295,0xc79ec954,0x9c3f159e,0x70dd70c0,0x2b7cac0a, + 0x79547af5,0x22f5a63f,0xce17c361,0x95b61fab,0x1312146a,0x48b3c8a0,0xa451adfe,0xfff07134, + 0x5f705221,0x04d18eeb,0xe833ebb5,0xb392377f,0x35363cbe,0x6e97e074,0x8275852a,0xd9d459e0, + 0x8bfc8f1f,0xd05d53d5,0x3cbf368b,0x671eea41,0xe1bae180,0xba1b3d4a,0x56f95814,0x0d5884de, + 0xf2a8f5ea,0xa9092920,0x45eb4c7e,0x1e4a90b4,0x98ee9b75,0xc34f47bf,0x2fad22e1,0x740cfe2b, + 0x262428d4,0x7d85f41e,0x91679140,0xcac64d8a,0x4c62464b,0x17c39a81,0xfb21ffdf,0xa0802315, + 0xbee0a442,0xe5417888,0x09a31dd6,0x5202c11c,0xd4a6cadd,0x8f071617,0x63e57349,0x3844af83, + 0x6a6c797c,0x31cda5b6,0xdd2fc0e8,0x868e1c22,0x002a17e3,0x5b8bcb29,0xb769ae77,0xecc872bd, + 0x13380389,0x4899df43,0xa47bba1d,0xffda66d7,0x797e6d16,0x22dfb1dc,0xce3dd482,0x959c0848, + 0xc7b4deb7,0x9c15027d,0x70f76723,0x2b56bbe9,0xadf2b028,0xf6536ce2,0x1ab109bc,0x4110d576, + 0xe190f663,0xba312aa9,0x56d34ff7,0x0d72933d,0x8bd698fc,0xd0774436,0x3c952168,0x6734fda2, + 0x351c2b5d,0x6ebdf797,0x825f92c9,0xd9fe4e03,0x5f5a45c2,0x04fb9908,0xe819fc56,0xb3b8209c, + 0x4c4851a8,0x17e98d62,0xfb0be83c,0xa0aa34f6,0x260e3f37,0x7dafe3fd,0x914d86a3,0xcaec5a69, + 0x98c48c96,0xc365505c,0x2f873502,0x7426e9c8,0xf282e209,0xa9233ec3,0x45c15b9d,0x1e608757, + 0x79005533,0x22a189f9,0xce43eca7,0x95e2306d,0x13463bac,0x48e7e766,0xa4058238,0xffa45ef2, + 0xad8c880d,0xf62d54c7,0x1acf3199,0x416eed53,0xc7cae692,0x9c6b3a58,0x70895f06,0x2b2883cc, + 0xd4d8f2f8,0x8f792e32,0x639b4b6c,0x383a97a6,0xbe9e9c67,0xe53f40ad,0x09dd25f3,0x527cf939, + 0x00542fc6,0x5bf5f30c,0xb7179652,0xecb64a98,0x6a124159,0x31b39d93,0xdd51f8cd,0x86f02407, + 0x26700712,0x7dd1dbd8,0x9133be86,0xca92624c,0x4c36698d,0x1797b547,0xfb75d019,0xa0d40cd3, + 0xf2fcda2c,0xa95d06e6,0x45bf63b8,0x1e1ebf72,0x98bab4b3,0xc31b6879,0x2ff90d27,0x7458d1ed, + 0x8ba8a0d9,0xd0097c13,0x3ceb194d,0x674ac587,0xe1eece46,0xba4f128c,0x56ad77d2,0x0d0cab18, + 0x5f247de7,0x0485a12d,0xe867c473,0xb3c618b9,0x35621378,0x6ec3cfb2,0x8221aaec,0xd9807626, + 0xc7e0f171,0x9c412dbb,0x70a348e5,0x2b02942f,0xada69fee,0xf6074324,0x1ae5267a,0x4144fab0, + 0x136c2c4f,0x48cdf085,0xa42f95db,0xff8e4911,0x792a42d0,0x228b9e1a,0xce69fb44,0x95c8278e, + 0x6a3856ba,0x31998a70,0xdd7bef2e,0x86da33e4,0x007e3825,0x5bdfe4ef,0xb73d81b1,0xec9c5d7b, + 0xbeb48b84,0xe515574e,0x09f73210,0x5256eeda,0xd4f2e51b,0x8f5339d1,0x63b15c8f,0x38108045, + 0x9890a350,0xc3317f9a,0x2fd31ac4,0x7472c60e,0xf2d6cdcf,0xa9771105,0x4595745b,0x1e34a891, + 0x4c1c7e6e,0x17bda2a4,0xfb5fc7fa,0xa0fe1b30,0x265a10f1,0x7dfbcc3b,0x9119a965,0xcab875af, + 0x3548049b,0x6ee9d851,0x820bbd0f,0xd9aa61c5,0x5f0e6a04,0x04afb6ce,0xe84dd390,0xb3ec0f5a, + 0xe1c4d9a5,0xba65056f,0x56876031,0x0d26bcfb,0x8b82b73a,0xd0236bf0,0x3cc10eae,0x6760d264}}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/framing.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/framing.c new file mode 100644 index 0000000000..724d116d7f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COgg/libogg/src/framing.c @@ -0,0 +1,2114 @@ +/******************************************************************** + * * + * THIS FILE IS PART OF THE Ogg CONTAINER SOURCE CODE. * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * + * * + * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2018 * + * by the Xiph.Org Foundation http://www.xiph.org/ * + * * + ******************************************************************** + + function: code raw packets into framed OggSquish stream and + decode Ogg streams back into raw packets + + note: The CRC code is directly derived from public domain code by + Ross Williams (ross@guest.adelaide.edu.au). See docs/framing.html + for details. + + ********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +/* A complete description of Ogg framing exists in docs/framing.html */ + +int ogg_page_version(const ogg_page *og){ + return((int)(og->header[4])); +} + +int ogg_page_continued(const ogg_page *og){ + return((int)(og->header[5]&0x01)); +} + +int ogg_page_bos(const ogg_page *og){ + return((int)(og->header[5]&0x02)); +} + +int ogg_page_eos(const ogg_page *og){ + return((int)(og->header[5]&0x04)); +} + +ogg_int64_t ogg_page_granulepos(const ogg_page *og){ + unsigned char *page=og->header; + ogg_uint64_t granulepos=page[13]&(0xff); + granulepos= (granulepos<<8)|(page[12]&0xff); + granulepos= (granulepos<<8)|(page[11]&0xff); + granulepos= (granulepos<<8)|(page[10]&0xff); + granulepos= (granulepos<<8)|(page[9]&0xff); + granulepos= (granulepos<<8)|(page[8]&0xff); + granulepos= (granulepos<<8)|(page[7]&0xff); + granulepos= (granulepos<<8)|(page[6]&0xff); + return((ogg_int64_t)granulepos); +} + +int ogg_page_serialno(const ogg_page *og){ + return((int)((ogg_uint32_t)og->header[14]) | + ((ogg_uint32_t)og->header[15]<<8) | + ((ogg_uint32_t)og->header[16]<<16) | + ((ogg_uint32_t)og->header[17]<<24)); +} + +long ogg_page_pageno(const ogg_page *og){ + return((long)((ogg_uint32_t)og->header[18]) | + ((ogg_uint32_t)og->header[19]<<8) | + ((ogg_uint32_t)og->header[20]<<16) | + ((ogg_uint32_t)og->header[21]<<24)); +} + + + +/* returns the number of packets that are completed on this page (if + the leading packet is begun on a previous page, but ends on this + page, it's counted */ + +/* NOTE: + If a page consists of a packet begun on a previous page, and a new + packet begun (but not completed) on this page, the return will be: + ogg_page_packets(page) ==1, + ogg_page_continued(page) !=0 + + If a page happens to be a single packet that was begun on a + previous page, and spans to the next page (in the case of a three or + more page packet), the return will be: + ogg_page_packets(page) ==0, + ogg_page_continued(page) !=0 +*/ + +int ogg_page_packets(const ogg_page *og){ + int i,n=og->header[26],count=0; + for(i=0;iheader[27+i]<255)count++; + return(count); +} + + +#if 0 +/* helper to initialize lookup for direct-table CRC (illustrative; we + use the static init in crctable.h) */ + +static void _ogg_crc_init(){ + int i, j; + ogg_uint32_t polynomial, crc; + polynomial = 0x04c11db7; /* The same as the ethernet generator + polynomial, although we use an + unreflected alg and an init/final + of 0, not 0xffffffff */ + for (i = 0; i <= 0xFF; i++){ + crc = i << 24; + + for (j = 0; j < 8; j++) + crc = (crc << 1) ^ (crc & (1 << 31) ? polynomial : 0); + + crc_lookup[0][i] = crc; + } + + for (i = 0; i <= 0xFF; i++) + for (j = 1; j < 8; j++) + crc_lookup[j][i] = crc_lookup[0][(crc_lookup[j - 1][i] >> 24) & 0xFF] ^ (crc_lookup[j - 1][i] << 8); +} +#endif + +#include "crctable.h" + +/* init the encode/decode logical stream state */ + +int ogg_stream_init(ogg_stream_state *os,int serialno){ + if(os){ + memset(os,0,sizeof(*os)); + os->body_storage=16*1024; + os->lacing_storage=1024; + + os->body_data=_ogg_malloc(os->body_storage*sizeof(*os->body_data)); + os->lacing_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->lacing_vals)); + os->granule_vals=_ogg_malloc(os->lacing_storage*sizeof(*os->granule_vals)); + + if(!os->body_data || !os->lacing_vals || !os->granule_vals){ + ogg_stream_clear(os); + return -1; + } + + os->serialno=serialno; + + return(0); + } + return(-1); +} + +/* async/delayed error detection for the ogg_stream_state */ +int ogg_stream_check(ogg_stream_state *os){ + if(!os || !os->body_data) return -1; + return 0; +} + +/* _clear does not free os, only the non-flat storage within */ +int ogg_stream_clear(ogg_stream_state *os){ + if(os){ + if(os->body_data)_ogg_free(os->body_data); + if(os->lacing_vals)_ogg_free(os->lacing_vals); + if(os->granule_vals)_ogg_free(os->granule_vals); + + memset(os,0,sizeof(*os)); + } + return(0); +} + +int ogg_stream_destroy(ogg_stream_state *os){ + if(os){ + ogg_stream_clear(os); + _ogg_free(os); + } + return(0); +} + +/* Helpers for ogg_stream_encode; this keeps the structure and + what's happening fairly clear */ + +static int _os_body_expand(ogg_stream_state *os,long needed){ + if(os->body_storage-needed<=os->body_fill){ + long body_storage; + void *ret; + if(os->body_storage>LONG_MAX-needed){ + ogg_stream_clear(os); + return -1; + } + body_storage=os->body_storage+needed; + if(body_storagebody_data,body_storage*sizeof(*os->body_data)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->body_storage=body_storage; + os->body_data=ret; + } + return 0; +} + +static int _os_lacing_expand(ogg_stream_state *os,long needed){ + if(os->lacing_storage-needed<=os->lacing_fill){ + long lacing_storage; + void *ret; + if(os->lacing_storage>LONG_MAX-needed){ + ogg_stream_clear(os); + return -1; + } + lacing_storage=os->lacing_storage+needed; + if(lacing_storagelacing_vals,lacing_storage*sizeof(*os->lacing_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->lacing_vals=ret; + ret=_ogg_realloc(os->granule_vals,lacing_storage* + sizeof(*os->granule_vals)); + if(!ret){ + ogg_stream_clear(os); + return -1; + } + os->granule_vals=ret; + os->lacing_storage=lacing_storage; + } + return 0; +} + +/* checksum the page */ +/* Direct table CRC; note that this will be faster in the future if we + perform the checksum simultaneously with other copies */ + +static ogg_uint32_t _os_update_crc(ogg_uint32_t crc, unsigned char *buffer, int size){ + while (size>=8){ + crc^=((ogg_uint32_t)buffer[0]<<24)|((ogg_uint32_t)buffer[1]<<16)|((ogg_uint32_t)buffer[2]<<8)|((ogg_uint32_t)buffer[3]); + + crc=crc_lookup[7][ crc>>24 ]^crc_lookup[6][(crc>>16)&0xFF]^ + crc_lookup[5][(crc>> 8)&0xFF]^crc_lookup[4][ crc &0xFF]^ + crc_lookup[3][buffer[4] ]^crc_lookup[2][buffer[5] ]^ + crc_lookup[1][buffer[6] ]^crc_lookup[0][buffer[7] ]; + + buffer+=8; + size-=8; + } + + while (size--) + crc=(crc<<8)^crc_lookup[0][((crc >> 24)&0xff)^*buffer++]; + return crc; +} + +void ogg_page_checksum_set(ogg_page *og){ + if(og){ + ogg_uint32_t crc_reg=0; + + /* safety; needed for API behavior, but not framing code */ + og->header[22]=0; + og->header[23]=0; + og->header[24]=0; + og->header[25]=0; + + crc_reg=_os_update_crc(crc_reg,og->header,og->header_len); + crc_reg=_os_update_crc(crc_reg,og->body,og->body_len); + + og->header[22]=(unsigned char)(crc_reg&0xff); + og->header[23]=(unsigned char)((crc_reg>>8)&0xff); + og->header[24]=(unsigned char)((crc_reg>>16)&0xff); + og->header[25]=(unsigned char)((crc_reg>>24)&0xff); + } +} + +/* submit data to the internal buffer of the framing engine */ +int ogg_stream_iovecin(ogg_stream_state *os, ogg_iovec_t *iov, int count, + long e_o_s, ogg_int64_t granulepos){ + + long bytes = 0, lacing_vals; + int i; + + if(ogg_stream_check(os)) return -1; + if(!iov) return 0; + + for (i = 0; i < count; ++i){ + if(iov[i].iov_len>LONG_MAX) return -1; + if(bytes>LONG_MAX-(long)iov[i].iov_len) return -1; + bytes += (long)iov[i].iov_len; + } + lacing_vals=bytes/255+1; + + if(os->body_returned){ + /* advance packet data according to the body_returned pointer. We + had to keep it around to return a pointer into the buffer last + call */ + + os->body_fill-=os->body_returned; + if(os->body_fill) + memmove(os->body_data,os->body_data+os->body_returned, + os->body_fill); + os->body_returned=0; + } + + /* make sure we have the buffer storage */ + if(_os_body_expand(os,bytes) || _os_lacing_expand(os,lacing_vals)) + return -1; + + /* Copy in the submitted packet. Yes, the copy is a waste; this is + the liability of overly clean abstraction for the time being. It + will actually be fairly easy to eliminate the extra copy in the + future */ + + for (i = 0; i < count; ++i) { + memcpy(os->body_data+os->body_fill, iov[i].iov_base, iov[i].iov_len); + os->body_fill += (int)iov[i].iov_len; + } + + /* Store lacing vals for this packet */ + for(i=0;ilacing_vals[os->lacing_fill+i]=255; + os->granule_vals[os->lacing_fill+i]=os->granulepos; + } + os->lacing_vals[os->lacing_fill+i]=bytes%255; + os->granulepos=os->granule_vals[os->lacing_fill+i]=granulepos; + + /* flag the first segment as the beginning of the packet */ + os->lacing_vals[os->lacing_fill]|= 0x100; + + os->lacing_fill+=lacing_vals; + + /* for the sake of completeness */ + os->packetno++; + + if(e_o_s)os->e_o_s=1; + + return(0); +} + +int ogg_stream_packetin(ogg_stream_state *os,ogg_packet *op){ + ogg_iovec_t iov; + iov.iov_base = op->packet; + iov.iov_len = op->bytes; + return ogg_stream_iovecin(os, &iov, 1, op->e_o_s, op->granulepos); +} + +/* Conditionally flush a page; force==0 will only flush nominal-size + pages, force==1 forces us to flush a page regardless of page size + so long as there's any data available at all. */ +static int ogg_stream_flush_i(ogg_stream_state *os,ogg_page *og, int force, int nfill){ + int i; + int vals=0; + int maxvals=(os->lacing_fill>255?255:os->lacing_fill); + int bytes=0; + long acc=0; + ogg_int64_t granule_pos=-1; + + if(ogg_stream_check(os)) return(0); + if(maxvals==0) return(0); + + /* construct a page */ + /* decide how many segments to include */ + + /* If this is the initial header case, the first page must only include + the initial header packet */ + if(os->b_o_s==0){ /* 'initial header page' case */ + granule_pos=0; + for(vals=0;valslacing_vals[vals]&0x0ff)<255){ + vals++; + break; + } + } + }else{ + + /* The extra packets_done, packet_just_done logic here attempts to do two things: + 1) Don't unnecessarily span pages. + 2) Unless necessary, don't flush pages if there are less than four packets on + them; this expands page size to reduce unnecessary overhead if incoming packets + are large. + These are not necessary behaviors, just 'always better than naive flushing' + without requiring an application to explicitly request a specific optimized + behavior. We'll want an explicit behavior setup pathway eventually as well. */ + + int packets_done=0; + int packet_just_done=0; + for(vals=0;valsnfill && packet_just_done>=4){ + force=1; + break; + } + acc+=os->lacing_vals[vals]&0x0ff; + if((os->lacing_vals[vals]&0xff)<255){ + granule_pos=os->granule_vals[vals]; + packet_just_done=++packets_done; + }else + packet_just_done=0; + } + if(vals==255)force=1; + } + + if(!force) return(0); + + /* construct the header in temp storage */ + memcpy(os->header,"OggS",4); + + /* stream structure version */ + os->header[4]=0x00; + + /* continued packet flag? */ + os->header[5]=0x00; + if((os->lacing_vals[0]&0x100)==0)os->header[5]|=0x01; + /* first page flag? */ + if(os->b_o_s==0)os->header[5]|=0x02; + /* last page flag? */ + if(os->e_o_s && os->lacing_fill==vals)os->header[5]|=0x04; + os->b_o_s=1; + + /* 64 bits of PCM position */ + for(i=6;i<14;i++){ + os->header[i]=(unsigned char)(granule_pos&0xff); + granule_pos>>=8; + } + + /* 32 bits of stream serial number */ + { + long serialno=os->serialno; + for(i=14;i<18;i++){ + os->header[i]=(unsigned char)(serialno&0xff); + serialno>>=8; + } + } + + /* 32 bits of page counter (we have both counter and page header + because this val can roll over) */ + if(os->pageno==-1)os->pageno=0; /* because someone called + stream_reset; this would be a + strange thing to do in an + encode stream, but it has + plausible uses */ + { + long pageno=os->pageno++; + for(i=18;i<22;i++){ + os->header[i]=(unsigned char)(pageno&0xff); + pageno>>=8; + } + } + + /* zero for computation; filled in later */ + os->header[22]=0; + os->header[23]=0; + os->header[24]=0; + os->header[25]=0; + + /* segment table */ + os->header[26]=(unsigned char)(vals&0xff); + for(i=0;iheader[i+27]=(unsigned char)(os->lacing_vals[i]&0xff); + + /* set pointers in the ogg_page struct */ + og->header=os->header; + og->header_len=os->header_fill=vals+27; + og->body=os->body_data+os->body_returned; + og->body_len=bytes; + + /* advance the lacing data and set the body_returned pointer */ + + os->lacing_fill-=vals; + memmove(os->lacing_vals,os->lacing_vals+vals,os->lacing_fill*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+vals,os->lacing_fill*sizeof(*os->granule_vals)); + os->body_returned+=bytes; + + /* calculate the checksum */ + + ogg_page_checksum_set(og); + + /* done */ + return(1); +} + +/* This will flush remaining packets into a page (returning nonzero), + even if there is not enough data to trigger a flush normally + (undersized page). If there are no packets or partial packets to + flush, ogg_stream_flush returns 0. Note that ogg_stream_flush will + try to flush a normal sized page like ogg_stream_pageout; a call to + ogg_stream_flush does not guarantee that all packets have flushed. + Only a return value of 0 from ogg_stream_flush indicates all packet + data is flushed into pages. + + since ogg_stream_flush will flush the last page in a stream even if + it's undersized, you almost certainly want to use ogg_stream_pageout + (and *not* ogg_stream_flush) unless you specifically need to flush + a page regardless of size in the middle of a stream. */ + +int ogg_stream_flush(ogg_stream_state *os,ogg_page *og){ + return ogg_stream_flush_i(os,og,1,4096); +} + +/* Like the above, but an argument is provided to adjust the nominal + page size for applications which are smart enough to provide their + own delay based flushing */ + +int ogg_stream_flush_fill(ogg_stream_state *os,ogg_page *og, int nfill){ + return ogg_stream_flush_i(os,og,1,nfill); +} + +/* This constructs pages from buffered packet segments. The pointers +returned are to static buffers; do not free. The returned buffers are +good only until the next call (using the same ogg_stream_state) */ + +int ogg_stream_pageout(ogg_stream_state *os, ogg_page *og){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,4096)); +} + +/* Like the above, but an argument is provided to adjust the nominal +page size for applications which are smart enough to provide their +own delay based flushing */ + +int ogg_stream_pageout_fill(ogg_stream_state *os, ogg_page *og, int nfill){ + int force=0; + if(ogg_stream_check(os)) return 0; + + if((os->e_o_s&&os->lacing_fill) || /* 'were done, now flush' case */ + (os->lacing_fill&&!os->b_o_s)) /* 'initial header page' case */ + force=1; + + return(ogg_stream_flush_i(os,og,force,nfill)); +} + +int ogg_stream_eos(ogg_stream_state *os){ + if(ogg_stream_check(os)) return 1; + return os->e_o_s; +} + +/* DECODING PRIMITIVES: packet streaming layer **********************/ + +/* This has two layers to place more of the multi-serialno and paging + control in the application's hands. First, we expose a data buffer + using ogg_sync_buffer(). The app either copies into the + buffer, or passes it directly to read(), etc. We then call + ogg_sync_wrote() to tell how many bytes we just added. + + Pages are returned (pointers into the buffer in ogg_sync_state) + by ogg_sync_pageout(). The page is then submitted to + ogg_stream_pagein() along with the appropriate + ogg_stream_state* (ie, matching serialno). We then get raw + packets out calling ogg_stream_packetout() with a + ogg_stream_state. */ + +/* initialize the struct to a known state */ +int ogg_sync_init(ogg_sync_state *oy){ + if(oy){ + oy->storage = -1; /* used as a readiness flag */ + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +/* clear non-flat storage within */ +int ogg_sync_clear(ogg_sync_state *oy){ + if(oy){ + if(oy->data)_ogg_free(oy->data); + memset(oy,0,sizeof(*oy)); + } + return(0); +} + +int ogg_sync_destroy(ogg_sync_state *oy){ + if(oy){ + ogg_sync_clear(oy); + _ogg_free(oy); + } + return(0); +} + +int ogg_sync_check(ogg_sync_state *oy){ + if(oy->storage<0) return -1; + return 0; +} + +char *ogg_sync_buffer(ogg_sync_state *oy, long size){ + if(ogg_sync_check(oy)) return NULL; + + /* first, clear out any space that has been previously returned */ + if(oy->returned){ + oy->fill-=oy->returned; + if(oy->fill>0) + memmove(oy->data,oy->data+oy->returned,oy->fill); + oy->returned=0; + } + + if(size>oy->storage-oy->fill){ + /* We need to extend the internal buffer */ + long newsize; + void *ret; + + if(size>INT_MAX-4096-oy->fill){ + ogg_sync_clear(oy); + return NULL; + } + newsize=size+oy->fill+4096; /* an extra page to be nice */ + if(oy->data) + ret=_ogg_realloc(oy->data,newsize); + else + ret=_ogg_malloc(newsize); + if(!ret){ + ogg_sync_clear(oy); + return NULL; + } + oy->data=ret; + oy->storage=newsize; + } + + /* expose a segment at least as large as requested at the fill mark */ + return((char *)oy->data+oy->fill); +} + +int ogg_sync_wrote(ogg_sync_state *oy, long bytes){ + if(ogg_sync_check(oy))return -1; + if(oy->fill+bytes>oy->storage)return -1; + oy->fill+=bytes; + return(0); +} + +/* sync the stream. This is meant to be useful for finding page + boundaries. + + return values for this: + -n) skipped n bytes + 0) page not ready; more data (no bytes skipped) + n) page synced at current location; page length n bytes + +*/ + +long ogg_sync_pageseek(ogg_sync_state *oy,ogg_page *og){ + unsigned char *page=oy->data+oy->returned; + unsigned char *next; + long bytes=oy->fill-oy->returned; + + if(ogg_sync_check(oy))return 0; + + if(oy->headerbytes==0){ + int headerbytes,i; + if(bytes<27)return(0); /* not enough for a header */ + + /* verify capture pattern */ + if(memcmp(page,"OggS",4))goto sync_fail; + + headerbytes=page[26]+27; + if(bytesbodybytes+=page[27+i]; + oy->headerbytes=headerbytes; + } + + if(oy->bodybytes+oy->headerbytes>bytes)return(0); + + /* The whole test page is buffered. Verify the checksum */ + { + /* Grab the checksum bytes, set the header field to zero */ + char chksum[4]; + ogg_page log; + + memcpy(chksum,page+22,4); + memset(page+22,0,4); + + /* set up a temp page struct and recompute the checksum */ + log.header=page; + log.header_len=oy->headerbytes; + log.body=page+oy->headerbytes; + log.body_len=oy->bodybytes; + ogg_page_checksum_set(&log); + + /* Compare */ + if(memcmp(chksum,page+22,4)){ + /* D'oh. Mismatch! Corrupt page (or miscapture and not a page + at all) */ + /* replace the computed checksum with the one actually read in */ + memcpy(page+22,chksum,4); + +#ifndef DISABLE_CRC + /* Bad checksum. Lose sync */ + goto sync_fail; +#endif + } + } + + /* yes, have a whole page all ready to go */ + { + if(og){ + og->header=page; + og->header_len=oy->headerbytes; + og->body=page+oy->headerbytes; + og->body_len=oy->bodybytes; + } + + oy->unsynced=0; + oy->returned+=(bytes=oy->headerbytes+oy->bodybytes); + oy->headerbytes=0; + oy->bodybytes=0; + return(bytes); + } + + sync_fail: + + oy->headerbytes=0; + oy->bodybytes=0; + + /* search for possible capture */ + next=memchr(page+1,'O',bytes-1); + if(!next) + next=oy->data+oy->fill; + + oy->returned=(int)(next-oy->data); + return((long)-(next-page)); +} + +/* sync the stream and get a page. Keep trying until we find a page. + Suppress 'sync errors' after reporting the first. + + return values: + -1) recapture (hole in data) + 0) need more data + 1) page returned + + Returns pointers into buffered data; invalidated by next call to + _stream, _clear, _init, or _buffer */ + +int ogg_sync_pageout(ogg_sync_state *oy, ogg_page *og){ + + if(ogg_sync_check(oy))return 0; + + /* all we need to do is verify a page at the head of the stream + buffer. If it doesn't verify, we look for the next potential + frame */ + + for(;;){ + long ret=ogg_sync_pageseek(oy,og); + if(ret>0){ + /* have a page */ + return(1); + } + if(ret==0){ + /* need more data */ + return(0); + } + + /* head did not start a synced page... skipped some bytes */ + if(!oy->unsynced){ + oy->unsynced=1; + return(-1); + } + + /* loop. keep looking */ + + } +} + +/* add the incoming page to the stream state; we decompose the page + into packet segments here as well. */ + +int ogg_stream_pagein(ogg_stream_state *os, ogg_page *og){ + unsigned char *header=og->header; + unsigned char *body=og->body; + long bodysize=og->body_len; + int segptr=0; + + int version=ogg_page_version(og); + int continued=ogg_page_continued(og); + int bos=ogg_page_bos(og); + int eos=ogg_page_eos(og); + ogg_int64_t granulepos=ogg_page_granulepos(og); + int serialno=ogg_page_serialno(og); + long pageno=ogg_page_pageno(og); + int segments=header[26]; + + if(ogg_stream_check(os)) return -1; + + /* clean up 'returned data' */ + { + long lr=os->lacing_returned; + long br=os->body_returned; + + /* body data */ + if(br){ + os->body_fill-=br; + if(os->body_fill) + memmove(os->body_data,os->body_data+br,os->body_fill); + os->body_returned=0; + } + + if(lr){ + /* segment table */ + if(os->lacing_fill-lr){ + memmove(os->lacing_vals,os->lacing_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->lacing_vals)); + memmove(os->granule_vals,os->granule_vals+lr, + (os->lacing_fill-lr)*sizeof(*os->granule_vals)); + } + os->lacing_fill-=lr; + os->lacing_packet-=lr; + os->lacing_returned=0; + } + } + + /* check the serial number */ + if(serialno!=os->serialno)return(-1); + if(version>0)return(-1); + + if(_os_lacing_expand(os,segments+1)) return -1; + + /* are we in sequence? */ + if(pageno!=os->pageno){ + int i; + + /* unroll previous partial packet (if any) */ + for(i=os->lacing_packet;ilacing_fill;i++) + os->body_fill-=os->lacing_vals[i]&0xff; + os->lacing_fill=os->lacing_packet; + + /* make a note of dropped data in segment table */ + if(os->pageno!=-1){ + os->lacing_vals[os->lacing_fill++]=0x400; + os->lacing_packet++; + } + } + + /* are we a 'continued packet' page? If so, we may need to skip + some segments */ + if(continued){ + if(os->lacing_fill<1 || + (os->lacing_vals[os->lacing_fill-1]&0xff)<255 || + os->lacing_vals[os->lacing_fill-1]==0x400){ + bos=0; + for(;segptrbody_data+os->body_fill,body,bodysize); + os->body_fill+=bodysize; + } + + { + int saved=-1; + while(segptrlacing_vals[os->lacing_fill]=val; + os->granule_vals[os->lacing_fill]=-1; + + if(bos){ + os->lacing_vals[os->lacing_fill]|=0x100; + bos=0; + } + + if(val<255)saved=os->lacing_fill; + + os->lacing_fill++; + segptr++; + + if(val<255)os->lacing_packet=os->lacing_fill; + } + + /* set the granulepos on the last granuleval of the last full packet */ + if(saved!=-1){ + os->granule_vals[saved]=granulepos; + } + + } + + if(eos){ + os->e_o_s=1; + if(os->lacing_fill>0) + os->lacing_vals[os->lacing_fill-1]|=0x200; + } + + os->pageno=pageno+1; + + return(0); +} + +/* clear things to an initial state. Good to call, eg, before seeking */ +int ogg_sync_reset(ogg_sync_state *oy){ + if(ogg_sync_check(oy))return -1; + + oy->fill=0; + oy->returned=0; + oy->unsynced=0; + oy->headerbytes=0; + oy->bodybytes=0; + return(0); +} + +int ogg_stream_reset(ogg_stream_state *os){ + if(ogg_stream_check(os)) return -1; + + os->body_fill=0; + os->body_returned=0; + + os->lacing_fill=0; + os->lacing_packet=0; + os->lacing_returned=0; + + os->header_fill=0; + + os->e_o_s=0; + os->b_o_s=0; + os->pageno=-1; + os->packetno=0; + os->granulepos=0; + + return(0); +} + +int ogg_stream_reset_serialno(ogg_stream_state *os,int serialno){ + if(ogg_stream_check(os)) return -1; + ogg_stream_reset(os); + os->serialno=serialno; + return(0); +} + +static int _packetout(ogg_stream_state *os,ogg_packet *op,int adv){ + + /* The last part of decode. We have the stream broken into packet + segments. Now we need to group them into packets (or return the + out of sync markers) */ + + int ptr=os->lacing_returned; + + if(os->lacing_packet<=ptr)return(0); + + if(os->lacing_vals[ptr]&0x400){ + /* we need to tell the codec there's a gap; it might need to + handle previous packet dependencies. */ + os->lacing_returned++; + os->packetno++; + return(-1); + } + + if(!op && !adv)return(1); /* just using peek as an inexpensive way + to ask if there's a whole packet + waiting */ + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size=os->lacing_vals[ptr]&0xff; + long bytes=size; + int eos=os->lacing_vals[ptr]&0x200; /* last packet of the stream? */ + int bos=os->lacing_vals[ptr]&0x100; /* first packet of the stream? */ + + while(size==255){ + int val=os->lacing_vals[++ptr]; + size=val&0xff; + if(val&0x200)eos=0x200; + bytes+=size; + } + + if(op){ + op->e_o_s=eos; + op->b_o_s=bos; + op->packet=os->body_data+os->body_returned; + op->packetno=os->packetno; + op->granulepos=os->granule_vals[ptr]; + op->bytes=bytes; + } + + if(adv){ + os->body_returned+=bytes; + os->lacing_returned=ptr+1; + os->packetno++; + } + } + return(1); +} + +int ogg_stream_packetout(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; + return _packetout(os,op,1); +} + +int ogg_stream_packetpeek(ogg_stream_state *os,ogg_packet *op){ + if(ogg_stream_check(os)) return 0; + return _packetout(os,op,0); +} + +void ogg_packet_clear(ogg_packet *op) { + _ogg_free(op->packet); + memset(op, 0, sizeof(*op)); +} + +#ifdef _V_SELFTEST +#include + +ogg_stream_state os_en, os_de; +ogg_sync_state oy; + +void checkpacket(ogg_packet *op,long len, int no, long pos){ + long j; + static int sequence=0; + static int lastno=0; + + if(op->bytes!=len){ + fprintf(stderr,"incorrect packet length (%ld != %ld)!\n",op->bytes,len); + exit(1); + } + if(op->granulepos!=pos){ + fprintf(stderr,"incorrect packet granpos (%ld != %ld)!\n",(long)op->granulepos,pos); + exit(1); + } + + /* packet number just follows sequence/gap; adjust the input number + for that */ + if(no==0){ + sequence=0; + }else{ + sequence++; + if(no>lastno+1) + sequence++; + } + lastno=no; + if(op->packetno!=sequence){ + fprintf(stderr,"incorrect packet sequence %ld != %d\n", + (long)(op->packetno),sequence); + exit(1); + } + + /* Test data */ + for(j=0;jbytes;j++) + if(op->packet[j]!=((j+no)&0xff)){ + fprintf(stderr,"body data mismatch (1) at pos %ld: %x!=%lx!\n\n", + j,op->packet[j],(j+no)&0xff); + exit(1); + } +} + +void check_page(unsigned char *data,const int *header,ogg_page *og){ + long j; + /* Test data */ + for(j=0;jbody_len;j++) + if(og->body[j]!=data[j]){ + fprintf(stderr,"body data mismatch (2) at pos %ld: %x!=%x!\n\n", + j,data[j],og->body[j]); + exit(1); + } + + /* Test header */ + for(j=0;jheader_len;j++){ + if(og->header[j]!=header[j]){ + fprintf(stderr,"header content mismatch at pos %ld:\n",j); + for(j=0;jheader[j]); + fprintf(stderr,"\n"); + exit(1); + } + } + if(og->header_len!=header[26]+27){ + fprintf(stderr,"header length incorrect! (%ld!=%d)\n", + og->header_len,header[26]+27); + exit(1); + } +} + +void print_header(ogg_page *og){ + int j; + fprintf(stderr,"\nHEADER:\n"); + fprintf(stderr," capture: %c %c %c %c version: %d flags: %x\n", + og->header[0],og->header[1],og->header[2],og->header[3], + (int)og->header[4],(int)og->header[5]); + + fprintf(stderr," granulepos: %d serialno: %d pageno: %ld\n", + (og->header[9]<<24)|(og->header[8]<<16)| + (og->header[7]<<8)|og->header[6], + (og->header[17]<<24)|(og->header[16]<<16)| + (og->header[15]<<8)|og->header[14], + ((long)(og->header[21])<<24)|(og->header[20]<<16)| + (og->header[19]<<8)|og->header[18]); + + fprintf(stderr," checksum: %02x:%02x:%02x:%02x\n segments: %d (", + (int)og->header[22],(int)og->header[23], + (int)og->header[24],(int)og->header[25], + (int)og->header[26]); + + for(j=27;jheader_len;j++) + fprintf(stderr,"%d ",(int)og->header[j]); + fprintf(stderr,")\n\n"); +} + +void copy_page(ogg_page *og){ + unsigned char *temp=_ogg_malloc(og->header_len); + memcpy(temp,og->header,og->header_len); + og->header=temp; + + temp=_ogg_malloc(og->body_len); + memcpy(temp,og->body,og->body_len); + og->body=temp; +} + +void free_page(ogg_page *og){ + _ogg_free (og->header); + _ogg_free (og->body); +} + +void error(void){ + fprintf(stderr,"error!\n"); + exit(1); +} + +/* 17 only */ +const int head1_0[] = {0x4f,0x67,0x67,0x53,0,0x06, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x15,0xed,0xec,0x91, + 1, + 17}; + +/* 17, 254, 255, 256, 500, 510, 600 byte, pad */ +const int head1_1[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x59,0x10,0x6c,0x2c, + 1, + 17}; +const int head2_1[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x89,0x33,0x85,0xce, + 13, + 254,255,0,255,1,255,245,255,255,0, + 255,255,90}; + +/* nil packets; beginning,middle,end */ +const int head1_2[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; +const int head2_2[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x5c,0x3f,0x66,0xcb, + 17, + 17,254,255,0,0,255,1,0,255,245,255,255,0, + 255,255,90,0}; + +/* large initial packet */ +const int head1_3[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0x01,0x27,0x31,0xaa, + 18, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10}; + +const int head2_3[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x7f,0x4e,0x8a,0xd2, + 4, + 255,4,255,0}; + + +/* continuing packet test */ +const int head1_4[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xf8,0x3c,0x19,0x79, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255}; + +const int head3_4[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x38,0xe6,0xb6,0x28, + 6, + 255,220,255,4,255,0}; + + +/* spill expansion test */ +const int head1_4b[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_4b[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xce,0x8f,0x17,0x1a, + 23, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,10,255,4,255,0,0}; + + +const int head3_4b[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x9b,0xb2,0x50,0xa1, + 1, + 0}; + +/* page with the 255 segment limit */ +const int head1_5[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_5[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0xed,0x2a,0x2e,0xa7, + 255, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10}; + +const int head3_5[] = {0x4f,0x67,0x67,0x53,0,0x04, + 0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0x6c,0x3b,0x82,0x3d, + 1, + 50}; + + +/* packet that overspans over an entire page */ +const int head1_6[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_6[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255}; + +const int head3_6[] = {0x4f,0x67,0x67,0x53,0,0x01, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xf4,0x87,0xba,0xf3, + 255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255}; + +const int head4_6[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,3,0,0,0, + 0xf7,0x2f,0x6c,0x60, + 5, + 254,255,4,255,0}; + +/* packet that overspans over an entire page */ +const int head1_7[] = {0x4f,0x67,0x67,0x53,0,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,0,0,0,0, + 0xff,0x7b,0x23,0x17, + 1, + 0}; + +const int head2_7[] = {0x4f,0x67,0x67,0x53,0,0x00, + 0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,1,0,0,0, + 0x68,0x22,0x7c,0x3d, + 255, + 100, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255, + 255,255,255,255,255,255}; + +const int head3_7[] = {0x4f,0x67,0x67,0x53,0,0x05, + 0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x02,0x03,0x04,2,0,0,0, + 0xd4,0xe0,0x60,0xe5, + 1, + 0}; + +int compare_packet(const ogg_packet *op1, const ogg_packet *op2){ + if(op1->packet!=op2->packet){ + fprintf(stderr,"op1->packet != op2->packet\n"); + return(1); + } + if(op1->bytes!=op2->bytes){ + fprintf(stderr,"op1->bytes != op2->bytes\n"); + return(1); + } + if(op1->b_o_s!=op2->b_o_s){ + fprintf(stderr,"op1->b_o_s != op2->b_o_s\n"); + return(1); + } + if(op1->e_o_s!=op2->e_o_s){ + fprintf(stderr,"op1->e_o_s != op2->e_o_s\n"); + return(1); + } + if(op1->granulepos!=op2->granulepos){ + fprintf(stderr,"op1->granulepos != op2->granulepos\n"); + return(1); + } + if(op1->packetno!=op2->packetno){ + fprintf(stderr,"op1->packetno != op2->packetno\n"); + return(1); + } + return(0); +} + +void test_pack(const int *pl, const int **headers, int byteskip, + int pageskip, int packetskip){ + unsigned char *data=_ogg_malloc(1024*1024); /* for scripted test cases only */ + long inptr=0; + long outptr=0; + long deptr=0; + long depacket=0; + long granule_pos=7,pageno=0; + int i,j,packets,pageout=pageskip; + int eosflag=0; + int bosflag=0; + + int byteskipcount=0; + + ogg_stream_reset(&os_en); + ogg_stream_reset(&os_de); + ogg_sync_reset(&oy); + + for(packets=0;packetsbyteskip){ + memcpy(next,og.header,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + byteskipcount+=og.body_len; + if(byteskipcount>byteskip){ + memcpy(next,og.body,byteskipcount-byteskip); + next+=byteskipcount-byteskip; + byteskipcount=byteskip; + } + + ogg_sync_wrote(&oy,(long)(next-buf)); + + while(1){ + int ret=ogg_sync_pageout(&oy,&og_de); + if(ret==0)break; + if(ret<0)continue; + /* got a page. Happy happy. Verify that it's good. */ + + fprintf(stderr,"(%d), ",pageout); + + check_page(data+deptr,headers[pageout],&og_de); + deptr+=og_de.body_len; + pageout++; + + /* submit it to deconstitution */ + ogg_stream_pagein(&os_de,&og_de); + + /* packets out? */ + while(ogg_stream_packetpeek(&os_de,&op_de2)>0){ + ogg_stream_packetpeek(&os_de,NULL); + ogg_stream_packetout(&os_de,&op_de); /* just catching them all */ + + /* verify peek and out match */ + if(compare_packet(&op_de,&op_de2)){ + fprintf(stderr,"packetout != packetpeek! pos=%ld\n", + depacket); + exit(1); + } + + /* verify the packet! */ + /* check data */ + if(memcmp(data+depacket,op_de.packet,op_de.bytes)){ + fprintf(stderr,"packet data mismatch in decode! pos=%ld\n", + depacket); + exit(1); + } + /* check bos flag */ + if(bosflag==0 && op_de.b_o_s==0){ + fprintf(stderr,"b_o_s flag not set on packet!\n"); + exit(1); + } + if(bosflag && op_de.b_o_s){ + fprintf(stderr,"b_o_s flag incorrectly set on packet!\n"); + exit(1); + } + bosflag=1; + depacket+=op_de.bytes; + + /* check eos flag */ + if(eosflag){ + fprintf(stderr,"Multiple decoded packets with eos flag!\n"); + exit(1); + } + + if(op_de.e_o_s)eosflag=1; + + /* check granulepos flag */ + if(op_de.granulepos!=-1){ + fprintf(stderr," granule:%ld ",(long)op_de.granulepos); + } + } + } + } + } + } + } + _ogg_free(data); + if(headers[pageno]!=NULL){ + fprintf(stderr,"did not write last page!\n"); + exit(1); + } + if(headers[pageout]!=NULL){ + fprintf(stderr,"did not decode last page!\n"); + exit(1); + } + if(inptr!=outptr){ + fprintf(stderr,"encoded page data incomplete!\n"); + exit(1); + } + if(inptr!=deptr){ + fprintf(stderr,"decoded page data incomplete!\n"); + exit(1); + } + if(inptr!=depacket){ + fprintf(stderr,"decoded packet data incomplete!\n"); + exit(1); + } + if(!eosflag){ + fprintf(stderr,"Never got a packet with EOS set!\n"); + exit(1); + } + fprintf(stderr,"ok.\n"); +} + +int main(void){ + + ogg_stream_init(&os_en,0x04030201); + ogg_stream_init(&os_de,0x04030201); + ogg_sync_init(&oy); + + /* Exercise each code path in the framing code. Also verify that + the checksums are working. */ + + { + /* 17 only */ + const int packets[]={17, -1}; + const int *headret[]={head1_0,NULL}; + + fprintf(stderr,"testing single page encoding... "); + test_pack(packets,headret,0,0,0); + } + + { + /* 17, 254, 255, 256, 500, 510, 600 byte, pad */ + const int packets[]={17, 254, 255, 256, 500, 510, 600, -1}; + const int *headret[]={head1_1,head2_1,NULL}; + + fprintf(stderr,"testing basic page encoding... "); + test_pack(packets,headret,0,0,0); + } + + { + /* nil packets; beginning,middle,end */ + const int packets[]={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1}; + const int *headret[]={head1_2,head2_2,NULL}; + + fprintf(stderr,"testing basic nil packets... "); + test_pack(packets,headret,0,0,0); + } + + { + /* large initial packet */ + const int packets[]={4345,259,255,-1}; + const int *headret[]={head1_3,head2_3,NULL}; + + fprintf(stderr,"testing initial-packet lacing > 4k... "); + test_pack(packets,headret,0,0,0); + } + + { + /* continuing packet test; with page spill expansion, we have to + overflow the lacing table. */ + const int packets[]={0,65500,259,255,-1}; + const int *headret[]={head1_4,head2_4,head3_4,NULL}; + + fprintf(stderr,"testing single packet page span... "); + test_pack(packets,headret,0,0,0); + } + + { + /* spill expand packet test */ + const int packets[]={0,4345,259,255,0,0,-1}; + const int *headret[]={head1_4b,head2_4b,head3_4b,NULL}; + + fprintf(stderr,"testing page spill expansion... "); + test_pack(packets,headret,0,0,0); + } + + /* page with the 255 segment limit */ + { + + const int packets[]={0,10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,10, + 10,10,10,10,10,10,10,50,-1}; + const int *headret[]={head1_5,head2_5,head3_5,NULL}; + + fprintf(stderr,"testing max packet segments... "); + test_pack(packets,headret,0,0,0); + } + + { + /* packet that overspans over an entire page */ + const int packets[]={0,100,130049,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing very large packets... "); + test_pack(packets,headret,0,0,0); + } + +#ifndef DISABLE_CRC + { + /* test for the libogg 1.1.1 resync in large continuation bug + found by Josh Coalson) */ + const int packets[]={0,100,130049,259,255,-1}; + const int *headret[]={head1_6,head2_6,head3_6,head4_6,NULL}; + + fprintf(stderr,"testing continuation resync in very large packets... "); + test_pack(packets,headret,100,2,3); + } +#else + fprintf(stderr,"Skipping continuation resync test due to --disable-crc\n"); +#endif + + { + /* term only page. why not? */ + const int packets[]={0,100,64770,-1}; + const int *headret[]={head1_7,head2_7,head3_7,NULL}; + + fprintf(stderr,"testing zero data page (1 nil packet)... "); + test_pack(packets,headret,0,0,0); + } + + + + { + /* build a bunch of pages for testing */ + unsigned char *data=_ogg_malloc(1024*1024); + int pl[]={0, 1,1,98,4079, 1,1,2954,2057, 76,34,912,0,234,1000,1000, 1000,300,-1}; + int inptr=0,i,j; + ogg_page og[5]; + + ogg_stream_reset(&os_en); + + for(i=0;pl[i]!=-1;i++){ + ogg_packet op; + int len=pl[i]; + + op.packet=data+inptr; + op.bytes=len; + op.e_o_s=(pl[i+1]<0?1:0); + op.granulepos=(i+1)*1000; + + for(j=0;j0)error(); + + /* Test fractional page inputs: incomplete fixed header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+3, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete header */ + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+23, + 5); + ogg_sync_wrote(&oy,5); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + /* Test fractional page inputs: incomplete body */ + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+28, + og[1].header_len-28); + ogg_sync_wrote(&oy,og[1].header_len-28); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body,1000); + ogg_sync_wrote(&oy,1000); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body+1000, + og[1].body_len-1000); + ogg_sync_wrote(&oy,og[1].body_len-1000); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test fractional page inputs: page + incomplete capture */ + { + ogg_page og_de; + fprintf(stderr,"Testing sync on 1+partial inputs... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header+20, + og[1].header_len-20); + ogg_sync_wrote(&oy,og[1].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + + /* Test recapture: garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing search for capture... "); + ogg_sync_reset(&oy); + + /* 'garbage' */ + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + 20); + ogg_sync_wrote(&oy,20); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header+20, + og[2].header_len-20); + ogg_sync_wrote(&oy,og[2].header_len-20); + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len); + ogg_sync_wrote(&oy,og[2].body_len); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } + +#ifndef DISABLE_CRC + /* Test recapture: page + garbage + page */ + { + ogg_page og_de; + fprintf(stderr,"Testing recapture... "); + ogg_sync_reset(&oy); + + memcpy(ogg_sync_buffer(&oy,og[1].header_len),og[1].header, + og[1].header_len); + ogg_sync_wrote(&oy,og[1].header_len); + + memcpy(ogg_sync_buffer(&oy,og[1].body_len),og[1].body, + og[1].body_len); + ogg_sync_wrote(&oy,og[1].body_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + memcpy(ogg_sync_buffer(&oy,og[2].header_len),og[2].header, + og[2].header_len); + ogg_sync_wrote(&oy,og[2].header_len); + + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + memcpy(ogg_sync_buffer(&oy,og[2].body_len),og[2].body, + og[2].body_len-5); + ogg_sync_wrote(&oy,og[2].body_len-5); + + memcpy(ogg_sync_buffer(&oy,og[3].header_len),og[3].header, + og[3].header_len); + ogg_sync_wrote(&oy,og[3].header_len); + + memcpy(ogg_sync_buffer(&oy,og[3].body_len),og[3].body, + og[3].body_len); + ogg_sync_wrote(&oy,og[3].body_len); + + if(ogg_sync_pageout(&oy,&og_de)>0)error(); + if(ogg_sync_pageout(&oy,&og_de)<=0)error(); + + fprintf(stderr,"ok.\n"); + } +#else + fprintf(stderr,"Skipping recapture test due to --disable-crc\n"); +#endif + + /* Free page data that was previously copied */ + { + for(i=0;i<5;i++){ + free_page(&og[i]); + } + } + } + ogg_sync_clear(&oy); + ogg_stream_clear(&os_en); + ogg_stream_clear(&os_de); + + return(0); +} + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/COpus/COpus.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/COpus/COpus.h new file mode 100644 index 0000000000..8035b6cca4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/COpus/COpus.h @@ -0,0 +1,7 @@ +#ifndef COpus_h +#define COpus_h + +#include +#include + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/module.modulemap b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/module.modulemap new file mode 100644 index 0000000000..a9c9d33d30 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/module.modulemap @@ -0,0 +1,7 @@ +module COpus { + header "opus.h" + header "opus_defines.h" + header "opus_multistream.h" + header "opus_types.h" + export * +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus.h new file mode 100644 index 0000000000..eadeda75a0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus.h @@ -0,0 +1,1099 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus.h + * @brief Opus reference implementation API + */ + +#ifndef OPUS_H +#define OPUS_H + +#include "opus_types.h" +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @mainpage Opus + * + * The Opus codec is designed for interactive speech and audio transmission over the Internet. + * It is designed by the IETF Codec Working Group and incorporates technology from + * Skype's SILK codec and Xiph.Org's CELT codec. + * + * The Opus codec is designed to handle a wide range of interactive audio applications, + * including Voice over IP, videoconferencing, in-game chat, and even remote live music + * performances. It can scale from low bit-rate narrowband speech to very high quality + * stereo music. Its main features are: + + * @li Sampling rates from 8 to 48 kHz + * @li Bit-rates from 6 kb/s to 510 kb/s + * @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR) + * @li Audio bandwidth from narrowband to full-band + * @li Support for speech and music + * @li Support for mono and stereo + * @li Support for multichannel (up to 255 channels) + * @li Frame sizes from 2.5 ms to 60 ms + * @li Good loss robustness and packet loss concealment (PLC) + * @li Floating point and fixed-point implementation + * + * Documentation sections: + * @li @ref opus_encoder + * @li @ref opus_decoder + * @li @ref opus_repacketizer + * @li @ref opus_multistream + * @li @ref opus_libinfo + * @li @ref opus_custom + */ + +/** @defgroup opus_encoder Opus Encoder + * @{ + * + * @brief This page describes the process and functions used to encode Opus. + * + * Since Opus is a stateful codec, the encoding process starts with creating an encoder + * state. This can be done with: + * + * @code + * int error; + * OpusEncoder *enc; + * enc = opus_encoder_create(Fs, channels, application, &error); + * @endcode + * + * From this point, @c enc can be used for encoding an audio stream. An encoder state + * @b must @b not be used for more than one stream at the same time. Similarly, the encoder + * state @b must @b not be re-initialized for each frame. + * + * While opus_encoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * + * @code + * int size; + * int error; + * OpusEncoder *enc; + * size = opus_encoder_get_size(channels); + * enc = malloc(size); + * error = opus_encoder_init(enc, Fs, channels, application); + * @endcode + * + * where opus_encoder_get_size() returns the required size for the encoder state. Note that + * future versions of this code may change the size, so no assumptions should be made about it. + * + * The encoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * It is possible to change some of the encoder's settings using the opus_encoder_ctl() + * interface. All these settings already default to the recommended value, so they should + * only be changed when necessary. The most common settings one may want to change are: + * + * @code + * opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + * opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); + * opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type)); + * @endcode + * + * where + * + * @arg bitrate is in bits per second (b/s) + * @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest + * @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC + * + * See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream. + * + * To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data: + * @code + * len = opus_encode(enc, audio_frame, frame_size, packet, max_packet); + * @endcode + * + * where + *
    + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). + * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • + *
+ * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, which indicates that an error has occurred. If the return value + * is 2 bytes or less, then the packet does not need to be transmitted (DTX). + * + * Once the encoder state if no longer needed, it can be destroyed with + * + * @code + * opus_encoder_destroy(enc); + * @endcode + * + * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), + * then no action is required aside from potentially freeing the memory that was manually + * allocated for it (calling free(enc) for the example above) + * + */ + +/** Opus encoder state. + * This contains the complete state of an Opus encoder. + * It is position independent and can be freely copied. + * @see opus_encoder_create,opus_encoder_init + */ +typedef struct OpusEncoder OpusEncoder; + +/** Gets the size of an OpusEncoder structure. + * @param[in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); + +/** + */ + +/** Allocates and initializes an encoder state. + * There are three coding modes: + * + * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice + * signals. It enhances the input signal by high-pass filtering and + * emphasizing formants and harmonics. Optionally it includes in-band + * forward error correction to protect against packet loss. Use this + * mode for typical VoIP applications. Because of the enhancement, + * even at high bitrates the output may sound different from the input. + * + * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most + * non-voice signals like music. Use this mode for music and mixed + * (music/voice) content, broadcast, and applications requiring less + * than 15 ms of coding delay. + * + * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that + * disables the speech-optimized mode in exchange for slightly reduced delay. + * This mode can only be set on an newly initialized or freshly reset encoder + * because it changes the codec delay. + * + * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of @ref OPUS_APPLICATION_VOIP, @ref OPUS_APPLICATION_AUDIO, or @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @ref opus_errorcodes + * @note Regardless of the sampling rate and number channels selected, the Opus encoder + * can switch to a lower audio bandwidth or number of channels if the bitrate + * selected is too low. This also means that it is safe to always use 48 kHz stereo input + * and let the encoder optimize the encoding. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( + opus_int32 Fs, + int channels, + int application, + int *error +); + +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_encoder_create(),opus_encoder_get_size() + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, or OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_encoder_init( + OpusEncoder *st, + opus_int32 Fs, + int channels, + int application +) OPUS_ARG_NONNULL(1); + +/** Encodes an Opus frame. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( + OpusEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes an Opus frame from floating point input. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. + * length is frame_size*channels*sizeof(float) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( + OpusEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: State to be freed. + */ +OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); + +/** Perform a CTL function on an Opus encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusEncoder*: Encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_encoderctls. + * @see opus_genericctls + * @see opus_encoderctls + */ +OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); +/**@}*/ + +/** @defgroup opus_decoder Opus Decoder + * @{ + * + * @brief This page describes the process and functions used to decode Opus. + * + * The decoding process also starts with creating a decoder + * state. This can be done with: + * @code + * int error; + * OpusDecoder *dec; + * dec = opus_decoder_create(Fs, channels, &error); + * @endcode + * where + * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 + * @li channels is the number of channels (1 or 2) + * @li error will hold the error code in case of failure (or #OPUS_OK on success) + * @li the return value is a newly created decoder state to be used for decoding + * + * While opus_decoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * @code + * int size; + * int error; + * OpusDecoder *dec; + * size = opus_decoder_get_size(channels); + * dec = malloc(size); + * error = opus_decoder_init(dec, Fs, channels); + * @endcode + * where opus_decoder_get_size() returns the required size for the decoder state. Note that + * future versions of this code may change the size, so no assumptions should be made about it. + * + * The decoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: + * @code + * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); + * @endcode + * where + * + * @li packet is the byte array containing the compressed data + * @li len is the exact number of bytes contained in the packet + * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) + * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array + * + * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. + * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio + * buffer is too small to hold the decoded audio. + * + * Opus is a stateful codec with overlapping blocks and as a result Opus + * packets are not coded independently of each other. Packets must be + * passed into the decoder serially and in the correct order for a correct + * decode. Lost packets can be replaced with loss concealment by calling + * the decoder with a null pointer and zero length for the missing packet. + * + * A single codec state may only be accessed from a single thread at + * a time and any required locking must be performed by the caller. Separate + * streams must be decoded with separate decoder states and can be decoded + * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK + * defined. + * + */ + +/** Opus decoder state. + * This contains the complete state of an Opus decoder. + * It is position independent and can be freely copied. + * @see opus_decoder_create,opus_decoder_init + */ +typedef struct OpusDecoder OpusDecoder; + +/** Opus DRED decoder. + * This contains the complete state of an Opus DRED decoder. + * It is position independent and can be freely copied. + * @see opus_dred_decoder_create,opus_dred_decoder_init + */ +typedef struct OpusDREDDecoder OpusDREDDecoder; + + +/** Opus DRED state. + * This contains the complete state of an Opus DRED packet. + * It is position independent and can be freely copied. + * @see opus_dred_create,opus_dred_init + */ +typedef struct OpusDRED OpusDRED; + +/** Gets the size of an OpusDecoder structure. + * @param [in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); + +/** Allocates and initializes a decoder state. + * @param [in] Fs opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + * + * Internally Opus stores data at 48000 Hz, so that should be the default + * value for Fs. However, the decoder can efficiently decode to buffers + * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use + * data at the full sample rate, or knows the compressed data doesn't + * use the full frequency range, it can request decoding at a reduced + * rate. Likewise, the decoder is capable of filling in either mono or + * interleaved stereo pcm buffers, at the caller's request. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( + opus_int32 Fs, + int channels, + int *error +); + +/** Initializes a previously allocated decoder state. + * The state must be at least the size returned by opus_decoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_init( + OpusDecoder *st, + opus_int32 Fs, + int channels +) OPUS_ARG_NONNULL(1); + +/** Decode an Opus packet. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available, the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an Opus packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusDecoder*: Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); + +/** Gets the size of an OpusDREDDecoder structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_decoder_get_size(void); + +/** Allocates and initializes an OpusDREDDecoder state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDREDDecoder *opus_dred_decoder_create(int *error); + +/** Initializes an OpusDREDDecoder state. + * @param[in] dec OpusDREDDecoder*: State to be initialized. + */ +OPUS_EXPORT int opus_dred_decoder_init(OpusDREDDecoder *dec); + +/** Frees an OpusDREDDecoder allocated by opus_dred_decoder_create(). + * @param[in] dec OpusDREDDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_dred_decoder_destroy(OpusDREDDecoder *dec); + +/** Perform a CTL function on an Opus DRED decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param dred_dec OpusDREDDecoder*: DRED Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...); + +/** Gets the size of an OpusDRED structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_get_size(void); + +/** Allocates and initializes a DRED state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDRED *opus_dred_alloc(int *error); + +/** Frees an OpusDRED allocated by opus_dred_create(). + * @param[in] dec OpusDRED*: State to be freed. + */ +OPUS_EXPORT void opus_dred_free(OpusDRED *dec); + +/** Decode an Opus DRED packet. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] data char*: Input payload + * @param [in] len opus_int32: Number of bytes in payload + * @param [in] max_dred_samples opus_int32: Maximum number of DRED samples that may be needed (if available in the packet). + * @param [in] sampling_rate opus_int32: Sampling rate used for max_dred_samples argument. Needs not match the actual sampling rate of the decoder. + * @param [out] dred_end opus_int32*: Number of non-encoded (silence) samples between the DRED timestamp and the last DRED sample. + * @param [in] defer_processing int: Flag (0 or 1). If set to one, the CPU-intensive part of the DRED decoding is deferred until opus_dred_process() is called. + * @returns Offset (positive) of the first decoded DRED samples, zero if no DRED is present, or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) OPUS_ARG_NONNULL(1); + +/** Finish decoding an Opus DRED packet. The function only needs to be called if opus_dred_parse() was called with defer_processing=1. + * The source and destination will often be the same DRED state. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] src OpusDRED*: Source DRED state to start the processing from. + * @param [out] dst OpusDRED*: Destination DRED state to store the updated state after processing. + * @returns @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size); + + +/** Parse an opus packet into one or more frames. + * Opus_decode will perform this operation internally so most applications do + * not need to use this function. + * This function does not copy the frames, the returned pointers are pointers into + * the input packet. + * @param [in] data char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) + * @returns number of frames + */ +OPUS_EXPORT int opus_packet_parse( + const unsigned char *data, + opus_int32 len, + unsigned char *out_toc, + const unsigned char *frames[48], + opus_int16 size[48], + int *payload_offset +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); + +/** Gets the bandwidth of an Opus packet. + * @param [in] data char*: Opus packet + * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) + * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) + * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) + * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) + * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples per frame from an Opus packet. + * @param [in] data char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples per frame. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Gets the number of channels from an Opus packet. + * @param [in] data char*: Opus packet + * @returns Number of channels + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of frames in an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of frames + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples of an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Checks whether an Opus packet has LBRR. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns 1 is LBRR is present, 0 otherwise + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len); + +/** Gets the number of samples of an Opus packet. + * @param [in] dec OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +/** Applies soft-clipping to bring a float signal within the [-1,1] range. If + * the signal is already in that range, nothing is done. If there are values + * outside of [-1,1], then the signal is clipped as smoothly as possible to + * both fit in the range and avoid creating excessive distortion in the + * process. + * @param [in,out] pcm float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) + */ +OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); + + +/**@}*/ + +/** @defgroup opus_repacketizer Repacketizer + * @{ + * + * The repacketizer can be used to merge multiple Opus packets into a single + * packet or alternatively to split Opus packets that have previously been + * merged. Splitting valid Opus packets is always guaranteed to succeed, + * whereas merging valid packets only succeeds if all frames have the same + * mode, bandwidth, and frame size, and when the total duration of the merged + * packet is no more than 120 ms. The 120 ms limit comes from the + * specification and limits decoder memory requirements at a point where + * framing overhead becomes negligible. + * + * The repacketizer currently only operates on elementary Opus + * streams. It will not manipualte multistream packets successfully, except in + * the degenerate case where they consist of data from a single stream. + * + * The repacketizing process starts with creating a repacketizer state, either + * by calling opus_repacketizer_create() or by allocating the memory yourself, + * e.g., + * @code + * OpusRepacketizer *rp; + * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); + * if (rp != NULL) + * opus_repacketizer_init(rp); + * @endcode + * + * Then the application should submit packets with opus_repacketizer_cat(), + * extract new packets with opus_repacketizer_out() or + * opus_repacketizer_out_range(), and then reset the state for the next set of + * input packets via opus_repacketizer_init(). + * + * For example, to split a sequence of packets into individual frames: + * @code + * unsigned char *data; + * int len; + * while (get_next_packet(&data, &len)) + * { + * unsigned char out[1276]; + * opus_int32 out_len; + * int nb_frames; + * int err; + * int i; + * err = opus_repacketizer_cat(rp, data, len); + * if (err != OPUS_OK) + * { + * release_packet(data); + * return err; + * } + * nb_frames = opus_repacketizer_get_nb_frames(rp); + * for (i = 0; i < nb_frames; i++) + * { + * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packet(data); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * } + * opus_repacketizer_init(rp); + * release_packet(data); + * } + * @endcode + * + * Alternatively, to combine a sequence of frames into packets that each + * contain up to TARGET_DURATION_MS milliseconds of data: + * @code + * // The maximum number of packets with duration TARGET_DURATION_MS occurs + * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) + * // packets. + * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; + * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; + * int nb_packets; + * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; + * opus_int32 out_len; + * int prev_toc; + * nb_packets = 0; + * while (get_next_packet(data+nb_packets, len+nb_packets)) + * { + * int nb_frames; + * int err; + * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); + * if (nb_frames < 1) + * { + * release_packets(data, nb_packets+1); + * return nb_frames; + * } + * nb_frames += opus_repacketizer_get_nb_frames(rp); + * // If adding the next packet would exceed our target, or it has an + * // incompatible TOC sequence, output the packets we already have before + * // submitting it. + * // N.B., The nb_packets > 0 check ensures we've submitted at least one + * // packet since the last call to opus_repacketizer_init(). Otherwise a + * // single packet longer than TARGET_DURATION_MS would cause us to try to + * // output an (invalid) empty packet. It also ensures that prev_toc has + * // been set to a valid value. Additionally, len[nb_packets] > 0 is + * // guaranteed by the call to opus_packet_get_nb_frames() above, so the + * // reference to data[nb_packets][0] should be valid. + * if (nb_packets > 0 && ( + * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || + * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > + * TARGET_DURATION_MS*48)) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packets(data, nb_packets+1); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * opus_repacketizer_init(rp); + * release_packets(data, nb_packets); + * data[0] = data[nb_packets]; + * len[0] = len[nb_packets]; + * nb_packets = 0; + * } + * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); + * if (err != OPUS_OK) + * { + * release_packets(data, nb_packets+1); + * return err; + * } + * prev_toc = data[nb_packets][0]; + * nb_packets++; + * } + * // Output the final, partial packet. + * if (nb_packets > 0) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * release_packets(data, nb_packets); + * if (out_len < 0) + * return (int)out_len; + * output_next_packet(out, out_len); + * } + * @endcode + * + * An alternate way of merging packets is to simply call opus_repacketizer_cat() + * unconditionally until it fails. At that point, the merged packet can be + * obtained with opus_repacketizer_out() and the input packet for which + * opus_repacketizer_cat() needs to be re-added to a newly reinitialized + * repacketizer state. + */ + +typedef struct OpusRepacketizer OpusRepacketizer; + +/** Gets the size of an OpusRepacketizer structure. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); + +/** (Re)initializes a previously allocated repacketizer state. + * The state must be at least the size returned by opus_repacketizer_get_size(). + * This can be used for applications which use their own allocator instead of + * malloc(). + * It must also be called to reset the queue of packets waiting to be + * repacketized, which is necessary if the maximum packet duration of 120 ms + * is reached or if you wish to submit packets with a different Opus + * configuration (coding mode, audio bandwidth, frame size, or channel count). + * Failure to do so will prevent a new packet from being added with + * opus_repacketizer_cat(). + * @see opus_repacketizer_create + * @see opus_repacketizer_get_size + * @see opus_repacketizer_cat + * @param rp OpusRepacketizer*: The repacketizer state to + * (re)initialize. + * @returns A pointer to the same repacketizer state that was passed in. + */ +OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Allocates memory and initializes the new repacketizer with + * opus_repacketizer_init(). + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); + +/** Frees an OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: State to be freed. + */ +OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); + +/** Add a packet to the current repacketizer state. + * This packet must match the configuration of any packets already submitted + * for repacketization since the last call to opus_repacketizer_init(). + * This means that it must have the same coding mode, audio bandwidth, frame + * size, and channel count. + * This can be checked in advance by examining the top 6 bits of the first + * byte of the packet, and ensuring they match the top 6 bits of the first + * byte of any previously submitted packet. + * The total duration of audio in the repacketizer state also must not exceed + * 120 ms, the maximum duration of a single packet, after adding this packet. + * + * The contents of the current repacketizer state can be extracted into new + * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). + * + * In order to add a packet with a different configuration or to add more + * audio beyond 120 ms, you must clear the repacketizer state by calling + * opus_repacketizer_init(). + * If a packet is too large to add to the current repacketizer state, no part + * of it is added, even if it contains multiple frames, some of which might + * fit. + * If you wish to be able to add parts of such packets, you should first use + * another repacketizer to split the packet into pieces and add them + * individually. + * @see opus_repacketizer_out_range + * @see opus_repacketizer_out + * @see opus_repacketizer_init + * @param rp OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: The packet data. + * The application must ensure + * this pointer remains valid + * until the next call to + * opus_repacketizer_init() or + * opus_repacketizer_destroy(). + * @param len opus_int32: The number of bytes in the packet data. + * @returns An error code indicating whether or not the operation succeeded. + * @retval #OPUS_OK The packet's contents have been added to the repacketizer + * state. + * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, + * the packet's TOC sequence was not compatible + * with previously submitted packets (because + * the coding mode, audio bandwidth, frame size, + * or channel count did not match), or adding + * this packet would increase the total amount of + * audio stored in the repacketizer state to more + * than 120 ms. + */ +OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) plus + * the size of all packet data submitted to + * the repacketizer since the last call to + * opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of + * frames (begin < 0, begin >= end, or end > + * opus_repacketizer_get_nb_frames()). + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Return the total number of frames contained in packet data submitted to + * the repacketizer state so far via opus_repacketizer_cat() since the last + * call to opus_repacketizer_init() or opus_repacketizer_create(). + * This defines the valid range of packets that can be extracted with + * opus_repacketizer_out_range() or opus_repacketizer_out(). + * @param rp OpusRepacketizer*: The repacketizer state containing the + * frames. + * @returns The total number of frames contained in the packet data submitted + * to the repacketizer state. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * This is a convenience routine that returns all the data submitted so far + * in a single packet. + * It is equivalent to calling + * @code + * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), + * data, maxlen) + * @endcode + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * plus the size of all packet data + * submitted to the repacketizer since the + * last call to opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); + +/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); + +/** Remove all padding from a given Opus packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); + +/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); + +/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_defines.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_defines.h new file mode 100644 index 0000000000..cd8f4dde86 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_defines.h @@ -0,0 +1,830 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_defines.h + * @brief Opus reference implementation constants + */ + +#ifndef OPUS_DEFINES_H +#define OPUS_DEFINES_H + +#include "opus_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup opus_errorcodes Error codes + * @{ + */ +/** No error @hideinitializer*/ +#define OPUS_OK 0 +/** One or more invalid/out of range arguments @hideinitializer*/ +#define OPUS_BAD_ARG -1 +/** Not enough bytes allocated in the buffer @hideinitializer*/ +#define OPUS_BUFFER_TOO_SMALL -2 +/** An internal error was detected @hideinitializer*/ +#define OPUS_INTERNAL_ERROR -3 +/** The compressed data passed is corrupted @hideinitializer*/ +#define OPUS_INVALID_PACKET -4 +/** Invalid/unsupported request number @hideinitializer*/ +#define OPUS_UNIMPLEMENTED -5 +/** An encoder or decoder structure is invalid or already freed @hideinitializer*/ +#define OPUS_INVALID_STATE -6 +/** Memory allocation has failed @hideinitializer*/ +#define OPUS_ALLOC_FAIL -7 +/**@}*/ + +/** @cond OPUS_INTERNAL_DOC */ +/**Export control for opus functions */ + +#ifndef OPUS_EXPORT +# if defined(_WIN32) +# if defined(OPUS_BUILD) && defined(DLL_EXPORT) +# define OPUS_EXPORT __declspec(dllexport) +# else +# define OPUS_EXPORT +# endif +# elif defined(__GNUC__) && defined(OPUS_BUILD) +# define OPUS_EXPORT __attribute__ ((visibility ("default"))) +# else +# define OPUS_EXPORT +# endif +#endif + +# if !defined(OPUS_GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define OPUS_GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define OPUS_GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(3,0) +# define OPUS_RESTRICT __restrict__ +# elif (defined(_MSC_VER) && _MSC_VER >= 1400) +# define OPUS_RESTRICT __restrict +# else +# define OPUS_RESTRICT +# endif +#else +# define OPUS_RESTRICT restrict +#endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(2,7) +# define OPUS_INLINE __inline__ +# elif (defined(_MSC_VER)) +# define OPUS_INLINE __inline +# else +# define OPUS_INLINE +# endif +#else +# define OPUS_INLINE inline +#endif + +/**Warning attributes for opus functions + * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out + * some paranoid null checks. */ +#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) +#else +# define OPUS_WARN_UNUSED_RESULT +#endif +#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) +#else +# define OPUS_ARG_NONNULL(_x) +#endif + +/** These are the actual Encoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +#define OPUS_SET_APPLICATION_REQUEST 4000 +#define OPUS_GET_APPLICATION_REQUEST 4001 +#define OPUS_SET_BITRATE_REQUEST 4002 +#define OPUS_GET_BITRATE_REQUEST 4003 +#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 +#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 +#define OPUS_SET_VBR_REQUEST 4006 +#define OPUS_GET_VBR_REQUEST 4007 +#define OPUS_SET_BANDWIDTH_REQUEST 4008 +#define OPUS_GET_BANDWIDTH_REQUEST 4009 +#define OPUS_SET_COMPLEXITY_REQUEST 4010 +#define OPUS_GET_COMPLEXITY_REQUEST 4011 +#define OPUS_SET_INBAND_FEC_REQUEST 4012 +#define OPUS_GET_INBAND_FEC_REQUEST 4013 +#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 +#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 +#define OPUS_SET_DTX_REQUEST 4016 +#define OPUS_GET_DTX_REQUEST 4017 +#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 +#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 +#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 +#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 +#define OPUS_SET_SIGNAL_REQUEST 4024 +#define OPUS_GET_SIGNAL_REQUEST 4025 +#define OPUS_GET_LOOKAHEAD_REQUEST 4027 +/* #define OPUS_RESET_STATE 4028 */ +#define OPUS_GET_SAMPLE_RATE_REQUEST 4029 +#define OPUS_GET_FINAL_RANGE_REQUEST 4031 +#define OPUS_GET_PITCH_REQUEST 4033 +#define OPUS_SET_GAIN_REQUEST 4034 +#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ +#define OPUS_SET_LSB_DEPTH_REQUEST 4036 +#define OPUS_GET_LSB_DEPTH_REQUEST 4037 +#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 +#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 +#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 +#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 +#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 +/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ +#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 +#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 +#define OPUS_GET_IN_DTX_REQUEST 4049 +#define OPUS_SET_DRED_DURATION_REQUEST 4050 +#define OPUS_GET_DRED_DURATION_REQUEST 4051 +#define OPUS_SET_DNN_BLOB_REQUEST 4052 +/*#define OPUS_GET_DNN_BLOB_REQUEST 4053 */ + +/** Defines for the presence of extended APIs. */ +#define OPUS_HAVE_OPUS_PROJECTION_H + +/* Macros to trigger compilation errors when the wrong types are provided to a CTL */ +#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) + +#ifdef DISABLE_PTR_CHECK +/* Disable checks to prevent ubsan from complaining about NULL checks + in test_opus_api. */ +#define __opus_check_int_ptr(ptr) (ptr) +#define __opus_check_uint_ptr(ptr) (ptr) +#define __opus_check_uint8_ptr(ptr) (ptr) +#define __opus_check_val16_ptr(ptr) (ptr) +#define __opus_check_void_ptr(ptr) (ptr) +#else +#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) +#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) +#define __opus_check_uint8_ptr(ptr) ((ptr) + ((ptr) - (opus_uint8*)(ptr))) +#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) +#define __opus_check_void_ptr(x) ((void)((void *)0 == (x)), (x)) +#endif +/** @endcond */ + +/** @defgroup opus_ctlvalues Pre-defined values for CTL interface + * @see opus_genericctls, opus_encoderctls + * @{ + */ +/* Values for the various encoder CTLs */ +#define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. + * + * @hideinitializer */ +#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) +/** Gets the encoder's complexity configuration. + * @see OPUS_SET_COMPLEXITY + * @param[out] x opus_int32 *: Returns a value in the range 0-10, + * inclusive. + * @hideinitializer */ +#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) + +/** Configures the bitrate in the encoder. + * Rates from 500 to 512000 bits per second are meaningful, as well as the + * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. + * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much + * rate as it can, which is useful for controlling the rate by adjusting the + * output buffer size. + * @see OPUS_GET_BITRATE + * @param[in] x opus_int32: Bitrate in bits per second. The default + * is determined based on the number of + * channels and the input sampling rate. + * @hideinitializer */ +#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) +/** Gets the encoder's bitrate configuration. + * @see OPUS_SET_BITRATE + * @param[out] x opus_int32 *: Returns the bitrate in bits per second. + * The default is determined based on the + * number of channels and the input + * sampling rate. + * @hideinitializer */ +#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables variable bitrate (VBR) in the encoder. + * The configured bitrate may not be met exactly because frames must + * be an integer number of bytes in length. + * @see OPUS_GET_VBR + * @see OPUS_SET_VBR_CONSTRAINT + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
+ *
1
VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) +/** Determine if variable bitrate (VBR) is enabled in the encoder. + * @see OPUS_SET_VBR + * @see OPUS_GET_VBR_CONSTRAINT + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Hard CBR.
+ *
1
VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables constrained VBR in the encoder. + * This setting is ignored when the encoder is in CBR mode. + * @warning Only the MDCT mode of Opus currently heeds the constraint. + * Speech mode ignores it completely, hybrid mode may fail to obey it + * if the LPC layer uses more bitrate than the constraint would have + * permitted. + * @see OPUS_GET_VBR_CONSTRAINT + * @see OPUS_SET_VBR + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) +/** Determine if constrained VBR is enabled in the encoder. + * @see OPUS_SET_VBR_CONSTRAINT + * @see OPUS_GET_VBR + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default).
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) + +/** Configures mono/stereo forcing in the encoder. + * This can force the encoder to produce packets encoded as either mono or + * stereo, regardless of the format of the input audio. This is useful when + * the caller knows that the input signal is currently a mono source embedded + * in a stereo stream. + * @see OPUS_GET_FORCE_CHANNELS + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) +/** Gets the encoder's forced channel configuration. + * @see OPUS_SET_FORCE_CHANNELS + * @param[out] x opus_int32 *: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) + +/** Configures the maximum bandpass that the encoder will select automatically. + * Applications should normally use this instead of #OPUS_SET_BANDWIDTH + * (leaving that set to the default, #OPUS_AUTO). This allows the + * application to set an upper bound based on the type of input it is + * providing, but still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_MAX_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Gets the encoder's configured maximum allowed bandpass. + * @see OPUS_SET_MAX_BANDWIDTH + * @param[out] x opus_int32 *: Allowed values: + *
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Sets the encoder's bandpass to a specific value. + * This prevents the encoder from automatically selecting the bandpass based + * on the available bitrate. If an application knows the bandpass of the input + * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH + * instead, which still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Configures the type of signal being encoded. + * This is a hint which helps the encoder's mode selection. + * @see OPUS_GET_SIGNAL + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal type. + * @see OPUS_SET_SIGNAL + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) + + +/** Configures the encoder's intended application. + * The initial value is a mandatory argument to the encoder_create function. + * @see OPUS_GET_APPLICATION + * @param[in] x opus_int32: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured application. + * @see OPUS_SET_APPLICATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the total samples of delay added by the entire codec. + * This can be queried by the encoder and then the provided number of samples can be + * skipped on from the start of the decoder's output to provide time aligned input + * and output. From the perspective of a decoding application the real data begins this many + * samples late. + * + * The decoder contribution to this delay is identical for all decoders, but the + * encoder portion of the delay may vary from implementation to implementation, + * version to version, or even depend on the encoder's initial configuration. + * Applications needing delay compensation should call this CTL rather than + * hard-coding a value. + * @param[out] x opus_int32 *: Number of lookahead samples + * @hideinitializer */ +#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of inband forward error correction (FEC). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_INBAND_FEC + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable inband FEC (default).
+ *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
+ *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
+ *
+ * @hideinitializer */ +#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of inband forward error correction. + * @see OPUS_SET_INBAND_FEC + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Inband FEC disabled (default).
+ *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
+ *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
+ *
+ * @hideinitializer */ +#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's expected packet loss percentage. + * Higher values trigger progressively more loss resistant behavior in the encoder + * at the expense of quality at a given bitrate in the absence of packet loss, but + * greater quality under loss. + * @see OPUS_GET_PACKET_LOSS_PERC + * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured packet loss percentage. + * @see OPUS_SET_PACKET_LOSS_PERC + * @param[out] x opus_int32 *: Returns the configured loss percentage + * in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of discontinuous transmission (DTX). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_DTX + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable DTX (default).
+ *
1
Enabled DTX.
+ *
+ * @hideinitializer */ +#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of discontinuous transmission. + * @see OPUS_SET_DTX + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
DTX disabled (default).
+ *
1
DTX enabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) +/** Configures the depth of signal being encoded. + * + * This is a hint which helps the encoder identify silence and near-silence. + * It represents the number of significant bits of linear intensity below + * which the signal contains ignorable quantization or other noise. + * + * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting + * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate + * for 16-bit linear pcm input with opus_encode_float(). + * + * When using opus_encode() instead of opus_encode_float(), or when libopus + * is compiled for fixed-point, the encoder uses the minimum of the value + * set here and the value 16. + * + * @see OPUS_GET_LSB_DEPTH + * @param[in] x opus_int32: Input precision in bits, between 8 and 24 + * (default: 24). + * @hideinitializer */ +#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal depth. + * @see OPUS_SET_LSB_DEPTH + * @param[out] x opus_int32 *: Input precision in bits, between 8 and + * 24 (default: 24). + * @hideinitializer */ +#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of variable duration frames. + * When variable duration is enabled, the encoder is free to use a shorter frame + * size than the one requested in the opus_encode*() call. + * It is then the user's responsibility + * to verify how much audio was encoded by checking the ToC byte of the encoded + * packet. The part of the audio that was not encoded needs to be resent to the + * encoder for the next call. Do not use this option unless you really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured use of variable duration frames. + * @see OPUS_SET_EXPERT_FRAME_DURATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables almost all use of prediction, making frames almost + * completely independent. This reduces quality. + * @see OPUS_GET_PREDICTION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured prediction status. + * @see OPUS_SET_PREDICTION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) + +/** If non-zero, enables Deep Redundancy (DRED) and use the specified maximum number of 10-ms redundant frames + * @hideinitializer */ +#define OPUS_SET_DRED_DURATION(x) OPUS_SET_DRED_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured Deep Redundancy (DRED) maximum number of frames. + * @hideinitializer */ +#define OPUS_GET_DRED_DURATION(x) OPUS_GET_DRED_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Provide external DNN weights from binary object (only when explicitly built without the weights) + * @hideinitializer */ +#define OPUS_SET_DNN_BLOB(data, len) OPUS_SET_DNN_BLOB_REQUEST, __opus_check_void_ptr(data), __opus_check_int(len) + + +/**@}*/ + +/** @defgroup opus_genericctls Generic CTLs + * + * These macros are used with the \c opus_decoder_ctl and + * \c opus_encoder_ctl calls to generate a particular + * request. + * + * When called on an \c OpusDecoder they apply to that + * particular decoder instance. When called on an + * \c OpusEncoder they apply to the corresponding setting + * on that encoder instance, if present. + * + * Some usage examples: + * + * @code + * int ret; + * opus_int32 pitch; + * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); + * if (ret == OPUS_OK) return ret; + * + * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); + * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); + * + * opus_int32 enc_bw, dec_bw; + * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); + * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); + * if (enc_bw != dec_bw) { + * printf("packet bandwidth mismatch!\n"); + * } + * @endcode + * + * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls + * @{ + */ + +/** Resets the codec state to be equivalent to a freshly initialized state. + * This should be called when switching streams in order to prevent + * the back to back decoding from giving different results from + * one at a time decoding. + * @hideinitializer */ +#define OPUS_RESET_STATE 4028 + +/** Gets the final state of the codec's entropy coder. + * This is used for testing purposes, + * The encoder and decoder state should be identical after coding a payload + * (assuming no data corruption or software bugs) + * + * @param[out] x opus_uint32 *: Entropy coder state + * + * @hideinitializer */ +#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) + +/** Gets the encoder's configured bandpass or the decoder's last bandpass. + * @see OPUS_SET_BANDWIDTH + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Gets the sampling rate the encoder or decoder was initialized with. + * This simply returns the Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. + * @hideinitializer + */ +#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables the use of phase inversion for intensity stereo, + * improving the quality of mono downmixes, but slightly reducing normal + * stereo quality. Disabling phase inversion in the decoder does not comply + * with RFC 6716, although it does not cause any interoperability issue and + * is expected to become part of the Opus standard once RFC 6716 is updated + * by draft-ietf-codec-opus-update. + * @see OPUS_GET_PHASE_INVERSION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable phase inversion (default).
+ *
1
Disable phase inversion.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured phase inversion status. + * @see OPUS_SET_PHASE_INVERSION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Stereo phase inversion enabled (default).
+ *
1
Stereo phase inversion disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) +/** Gets the DTX state of the encoder. + * Returns whether the last encoded frame was either a comfort noise update + * during DTX or not encoded because of DTX. + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
The encoder is not in DTX.
+ *
1
The encoder is in DTX.
+ *
+ * @hideinitializer */ +#define OPUS_GET_IN_DTX(x) OPUS_GET_IN_DTX_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_decoderctls Decoder related CTLs + * @see opus_genericctls, opus_encoderctls, opus_decoder + * @{ + */ + +/** Configures decoder gain adjustment. + * Scales the decoded output by a factor specified in Q8 dB units. + * This has a maximum range of -32768 to 32767 inclusive, and returns + * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. + * This setting survives decoder reset. + * + * gain = pow(10, x/(20.0*256)) + * + * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) +/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN + * + * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) + +/** Gets the duration (in samples) of the last packet successfully decoded or concealed. + * @param[out] x opus_int32 *: Number of samples (at current sampling rate). + * @hideinitializer */ +#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the pitch of the last decoded frame, if available. + * This can be used for any post-processing algorithm requiring the use of pitch, + * e.g. time stretching/shortening. If the last frame was not voiced, or if the + * pitch was not coded in the frame, then zero is returned. + * + * This CTL is only implemented for decoder instances. + * + * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) + * + * @hideinitializer */ +#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_libinfo Opus library information functions + * @{ + */ + +/** Converts an opus error code into a human readable string. + * + * @param[in] error int: Error number + * @returns Error string + */ +OPUS_EXPORT const char *opus_strerror(int error); + +/** Gets the libopus version string. + * + * Applications may look for the substring "-fixed" in the version string to + * determine whether they have a fixed-point or floating-point build at + * runtime. + * + * @returns Version string + */ +OPUS_EXPORT const char *opus_get_version_string(void); +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_DEFINES_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_multistream.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_multistream.h new file mode 100644 index 0000000000..824cc55ac5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_multistream.h @@ -0,0 +1,660 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_multistream.h + * @brief Opus reference implementation multistream API + */ + +#ifndef OPUS_MULTISTREAM_H +#define OPUS_MULTISTREAM_H + +#include "opus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** Macros to trigger compilation errors when the wrong types are provided to a + * CTL. */ +/**@{*/ +#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) +#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) +/**@}*/ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 +#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 +/**@}*/ + +/** @endcond */ + +/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and + * @ref opus_decoderctls may be applied to a multistream encoder or decoder as + * well. + * In addition, you may retrieve the encoder or decoder state for an specific + * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or + * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. + */ +/**@{*/ + +/** Gets the encoder state for an individual stream of a multistream encoder. + * @param[in] x opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: Returns a pointer to the given + * encoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) + +/** Gets the decoder state for an individual stream of a multistream decoder. + * @param[in] x opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: Returns a pointer to the given + * decoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) + +/**@}*/ + +/** @defgroup opus_multistream Opus Multistream API + * @{ + * + * The multistream API allows individual Opus streams to be combined into a + * single packet, enabling support for up to 255 channels. Unlike an + * elementary Opus stream, the encoder and decoder must negotiate the channel + * configuration before the decoder can successfully interpret the data in the + * packets produced by the encoder. Some basic information, such as packet + * duration, can be computed without any special negotiation. + * + * The format for multistream Opus packets is defined in + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 when initializing the encoder or + * decoder. + * + * Multistream Opus streams can contain up to 255 elementary Opus streams. + * These may be either "uncoupled" or "coupled", indicating that the decoder + * is configured to decode them to either 1 or 2 channels, respectively. + * The streams are ordered so that all coupled streams appear at the + * beginning. + * + * A mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), unless it has the special + * value 255, in which case it is omitted from the encoding entirely (the + * decoder will reproduce it as silence). Each value i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. A decoder may wish to apply an additional permutation + * to the mapping the encoder used to achieve a different output channel + * order (e.g. for outputting in WAV order). + * + * Each multistream packet contains an Opus packet for each stream, and all of + * the Opus packets in a single multistream packet must have the same + * duration. Therefore the duration of a multistream packet can be extracted + * from the TOC sequence of the first stream, which is located at the + * beginning of the packet, just like an elementary Opus stream: + * + * @code + * int nb_samples; + * int nb_frames; + * nb_frames = opus_packet_get_nb_frames(data, len); + * if (nb_frames < 1) + * return nb_frames; + * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; + * @endcode + * + * The general encoding and decoding process proceeds exactly the same as in + * the normal @ref opus_encoder and @ref opus_decoder APIs. + * See their documentation for an overview of how to use the corresponding + * multistream functions. + */ + +/** Opus multistream encoder state. + * This contains the complete state of a multistream Opus encoder. + * It is position independent and can be freely copied. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_init + */ +typedef struct OpusMSEncoder OpusMSEncoder; + +/** Opus multistream decoder state. + * This contains the complete state of a multistream Opus decoder. + * It is position independent and can be freely copied. + * @see opus_multistream_decoder_create + * @see opus_multistream_decoder_init + */ +typedef struct OpusMSDecoder OpusMSDecoder; + +/**\name Multistream encoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSEncoder structure. + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( + int streams, + int coupled_streams +); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a multistream encoder state. + * Call opus_multistream_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(5); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + +/** Initialize a previously allocated multistream encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +OPUS_EXPORT int opus_multistream_surround_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); + +/** Encodes a multistream Opus frame. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( + OpusMSEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a multistream Opus frame from floating point input. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( + OpusMSEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: Multistream encoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); + +/** Perform a CTL function on a multistream Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +/**\name Multistream decoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( + int streams, + int coupled_streams +); + +/** Allocates and initializes a multistream decoder state. + * Call opus_multistream_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int *error +) OPUS_ARG_NONNULL(5); + +/** Intialize a previously allocated decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_decoder_create + * @see opus_multistream_deocder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_decoder_init( + OpusMSDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +/** Decode a multistream Opus packet. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode a multistream Opus packet with floating point output. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on a multistream Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_types.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_types.h new file mode 100644 index 0000000000..7cf675580f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/include/opus_types.h @@ -0,0 +1,166 @@ +/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ +/* Modified by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* opus_types.h based on ogg_types.h from libogg */ + +/** + @file opus_types.h + @brief Opus reference implementation types +*/ +#ifndef OPUS_TYPES_H +#define OPUS_TYPES_H + +#define opus_int int /* used for counters etc; at least 16 bits */ +#define opus_int64 long long +#define opus_int8 signed char + +#define opus_uint unsigned int /* used for counters etc; at least 16 bits */ +#define opus_uint64 unsigned long long +#define opus_uint8 unsigned char + +/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) +#include +# undef opus_int64 +# undef opus_int8 +# undef opus_uint64 +# undef opus_uint8 + typedef int8_t opus_int8; + typedef uint8_t opus_uint8; + typedef int16_t opus_int16; + typedef uint16_t opus_uint16; + typedef int32_t opus_int32; + typedef uint32_t opus_uint32; + typedef int64_t opus_int64; + typedef uint64_t opus_uint64; +#elif defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t opus_int32; + typedef _G_uint32_t opus_uint32; + typedef _G_int16 opus_int16; + typedef _G_uint16 opus_uint16; +# elif defined(__MINGW32__) + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; +# elif defined(__MWERKS__) + typedef int opus_int32; + typedef unsigned int opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; +# else + /* MSVC/Borland */ + typedef __int32 opus_int32; + typedef unsigned __int32 opus_uint32; + typedef __int16 opus_int16; + typedef unsigned __int16 opus_uint16; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 opus_int16; + typedef UInt16 opus_uint16; + typedef SInt32 opus_int32; + typedef UInt32 opus_uint32; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t opus_int16; + typedef u_int16_t opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16 opus_int16; + typedef u_int16 opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int opus_int32; + typedef unsigned opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short opus_int16; + typedef unsigned short opus_uint16; + typedef signed int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef long opus_int32; + typedef unsigned long opus_uint32; + +#elif defined(CONFIG_TI_C6X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#else + + /* Give up, take a reasonable guess */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#endif + +#endif /* OPUS_TYPES_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/_kiss_fft_guts.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/_kiss_fft_guts.h new file mode 100644 index 0000000000..17392b3e90 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/_kiss_fft_guts.h @@ -0,0 +1,182 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.*/ + +#ifndef KISS_FFT_GUTS_H +#define KISS_FFT_GUTS_H + +#define MIN(a,b) ((a)<(b) ? (a):(b)) +#define MAX(a,b) ((a)>(b) ? (a):(b)) + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#include "arch.h" + + +#define SAMP_MAX 2147483647 +#define TWID_MAX 32767 +#define TRIG_UPSCALE 1 + +#define SAMP_MIN -SAMP_MAX + + +# define S_MUL(a,b) MULT16_32_Q15(b, a) + +# define C_MUL(m,a,b) \ + do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0) + +# define C_MULC(m,a,b) \ + do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = S_MUL( (c).r , s ) ;\ + (c).i = S_MUL( (c).i , s ) ; }while(0) + +# define DIVSCALAR(x,k) \ + (x) = S_MUL( x, (TWID_MAX-((k)>>1))/(k)+1 ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +#define C_ADD( res, a,b)\ + do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \ + }while(0) +#define C_SUB( res, a,b)\ + do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \ + }while(0) +#define C_ADDTO( res , a)\ + do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \ + }while(0) + +#if defined(OPUS_ARM_INLINE_ASM) +#include "arm/kiss_fft_armv4.h" +#endif + +#if defined(OPUS_ARM_INLINE_EDSP) +#include "arm/kiss_fft_armv5e.h" +#endif +#if defined(MIPSr1_ASM) +#include "mips/kiss_fft_mipsr1.h" +#endif + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +#define C_MULC(m,a,b) \ + do{ (m).r = (a).r*(b).r + (a).i*(b).i;\ + (m).i = (a).i*(b).r - (a).r*(b).i; }while(0) + +#define C_MUL4(m,a,b) C_MUL(m,a,b) + +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#ifndef C_ADD +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) +#endif /* C_ADD defined */ + +#ifdef FIXED_POINT +/*# define KISS_FFT_COS(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * cos (phase)))) +# define KISS_FFT_SIN(phase) TRIG_UPSCALE*floor(MIN(32767,MAX(-32767,.5+32768 * sin (phase))))*/ +# define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5f)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5f) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + +#define kf_cexp2(x,phase) \ + do{ \ + (x)->r = TRIG_UPSCALE*celt_cos_norm((phase));\ + (x)->i = TRIG_UPSCALE*celt_cos_norm((phase)-32768);\ +}while(0) + +#endif /* KISS_FFT_GUTS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arch.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arch.h new file mode 100644 index 0000000000..3845c3a083 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arch.h @@ -0,0 +1,291 @@ +/* Copyright (c) 2003-2008 Jean-Marc Valin + Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions for CELT +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ARCH_H +#define ARCH_H + +#include "opus_types.h" +#include "opus_defines.h" + +# if !defined(__GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define __GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define __GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if OPUS_GNUC_PREREQ(3, 0) +#define opus_likely(x) (__builtin_expect(!!(x), 1)) +#define opus_unlikely(x) (__builtin_expect(!!(x), 0)) +#else +#define opus_likely(x) (!!(x)) +#define opus_unlikely(x) (!!(x)) +#endif + +#define CELT_SIG_SCALE 32768.f + +#define CELT_FATAL(str) celt_fatal(str, __FILE__, __LINE__); + +#if defined(ENABLE_ASSERTIONS) || defined(ENABLE_HARDENING) +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +void celt_fatal(const char *str, const char *file, int line); + +#if defined(CELT_C) && !defined(OVERRIDE_celt_fatal) +#include +#include +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +void celt_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); +#if defined(_MSC_VER) + _set_abort_behavior( 0, _WRITE_ABORT_MSG); +#endif + abort(); +} +#endif + +#define celt_assert(cond) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond);}} +#define celt_assert2(cond, message) {if (!(cond)) {CELT_FATAL("assertion failed: " #cond "\n" message);}} +#define MUST_SUCCEED(call) celt_assert((call) == OPUS_OK) +#else +#define celt_assert(cond) +#define celt_assert2(cond, message) +#define MUST_SUCCEED(call) do {if((call) != OPUS_OK) {RESTORE_STACK; return OPUS_INTERNAL_ERROR;} } while (0) +#endif + +#if defined(ENABLE_ASSERTIONS) +#define celt_sig_assert(cond) {if (!(cond)) {CELT_FATAL("signal assertion failed: " #cond);}} +#else +#define celt_sig_assert(cond) +#endif + +#define IMUL32(a,b) ((a)*(b)) + +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */ +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum int value. */ +#define UADD32(a,b) ((a)+(b)) +#define USUB32(a,b) ((a)-(b)) + +/* Set this if opus_int64 is a native type of the CPU. */ +/* Assume that all LP64 architectures have fast 64-bit types; also x86_64 + (which can be ILP32 for x32) and Win64 (which is LLP64). */ +#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64) +#define OPUS_FAST_INT64 1 +#else +#define OPUS_FAST_INT64 0 +#endif + +#define PRINT_MIPS(file) + +#ifdef FIXED_POINT + +typedef opus_int16 opus_val16; +typedef opus_int32 opus_val32; +typedef opus_int64 opus_val64; + +typedef opus_val32 celt_sig; +typedef opus_val16 celt_norm; +typedef opus_val32 celt_ener; + +#define celt_isnan(x) 0 + +#define Q15ONE 32767 + +#define SIG_SHIFT 12 +/* Safe saturation value for 32-bit signals. Should be less than + 2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/ +#define SIG_SAT (300000000) + +#define NORM_SCALING 16384 + +#define DB_SHIFT 10 + +#define EPSILON 1 +#define VERY_SMALL 0 +#define VERY_LARGE16 ((opus_val16)32767) +#define Q15_ONE ((opus_val16)32767) + +#define SCALEIN(a) (a) +#define SCALEOUT(a) (a) + +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) + +static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { + return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x; +} + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/fixed_arm64.h" +#elif defined (OPUS_ARM_INLINE_EDSP) +#include "arm/fixed_armv5e.h" +#elif defined (OPUS_ARM_INLINE_ASM) +#include "arm/fixed_armv4.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#elif defined (TI_C5X_ASM) +#include "fixed_c5x.h" +#elif defined (TI_C6X_ASM) +#include "fixed_c6x.h" +#endif + +#endif + +#else /* FIXED_POINT */ + +typedef float opus_val16; +typedef float opus_val32; +typedef float opus_val64; + +typedef float celt_sig; +typedef float celt_norm; +typedef float celt_ener; + +#ifdef FLOAT_APPROX +/* This code should reliably detect NaN/inf even when -ffast-math is used. + Assumes IEEE 754 format. */ +static OPUS_INLINE int celt_isnan(float x) +{ + union {float f; opus_uint32 i;} in; + in.f = x; + return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0; +} +#else +#ifdef __FAST_MATH__ +#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input +#endif +#define celt_isnan(x) ((x)!=(x)) +#endif + +#define Q15ONE 1.0f + +#define NORM_SCALING 1.f + +#define EPSILON 1e-15f +#define VERY_SMALL 1e-30f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((opus_val16)1.f) + +/* This appears to be the same speed as C99's fabsf() but it's more portable. */ +#define ABS16(x) ((float)fabs(x)) +#define ABS32(x) ((float)fabs(x)) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define NEG32_ovflw(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) +#define SATURATE16(x) (x) + +#define ROUND16(a,shift) (a) +#define SROUND16(a,shift) (a) +#define HALF16(x) (.5f*(x)) +#define HALF32(x) (.5f*(x)) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define ADD32_ovflw(a,b) ((a)+(b)) +#define SUB32_ovflw(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b)) +#define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b)) + +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_Q16(a,b) ((a)*(b)) + +#define MULT32_32_Q31(a,b) ((a)*(b)) + +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b)) + +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q11(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) +#define MULT16_32_P16(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((opus_val32)(a))/(opus_val16)(b)) +#define DIV32(a,b) (((opus_val32)(a))/(opus_val32)(b)) + +#define SCALEIN(a) ((a)*CELT_SIG_SCALE) +#define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE)) + +#define SIG2WORD16(x) (x) + +#endif /* !FIXED_POINT */ + +#ifndef GLOBAL_STACK_SIZE +#ifdef FIXED_POINT +#define GLOBAL_STACK_SIZE 120000 +#else +#define GLOBAL_STACK_SIZE 120000 +#endif +#endif + +#endif /* ARCH_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm2gnu.pl b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm2gnu.pl new file mode 100755 index 0000000000..a2895f7445 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm2gnu.pl @@ -0,0 +1,353 @@ +#!/usr/bin/perl +# Copyright (C) 2002-2013 Xiph.org Foundation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +my $bigend; # little/big endian +my $nxstack; +my $apple = 0; +my $symprefix = ""; + +$nxstack = 0; + +eval 'exec /usr/local/bin/perl -S $0 ${1+"$@"}' + if $running_under_some_shell; + +while ($ARGV[0] =~ /^-/) { + $_ = shift; + last if /^--$/; + if (/^-n$/) { + $nflag++; + next; + } + if (/^--apple$/) { + $apple = 1; + $symprefix = "_"; + next; + } + die "I don't recognize this switch: $_\\n"; +} +$printit++ unless $nflag; + +$\ = "\n"; # automatically add newline on print +$n=0; + +$thumb = 0; # ARM mode by default, not Thumb. +@proc_stack = (); + +printf (" .syntax unified\n"); + +LINE: +while (<>) { + + # For ADRLs we need to add a new line after the substituted one. + $addPadding = 0; + + # First, we do not dare to touch *anything* inside double quotes, do we? + # Second, if you want a dollar character in the string, + # insert two of them -- that's how ARM C and assembler treat strings. + s/^([A-Za-z_]\w*)[ \t]+DCB[ \t]*\"/$1: .ascii \"/ && do { s/\$\$/\$/g; next }; + s/\bDCB\b[ \t]*\"/.ascii \"/ && do { s/\$\$/\$/g; next }; + s/^(\S+)\s+RN\s+(\S+)/$1 .req r$2/ && do { s/\$\$/\$/g; next }; + # If there's nothing on a line but a comment, don't try to apply any further + # substitutions (this is a cheap hack to avoid mucking up the license header) + s/^([ \t]*);/$1@/ && do { s/\$\$/\$/g; next }; + # If substituted -- leave immediately ! + + s/@/,:/; + s/;/@/; + while ( /@.*'/ ) { + s/(@.*)'/$1/g; + } + s/\{FALSE\}/0/g; + s/\{TRUE\}/1/g; + s/\{(\w\w\w\w+)\}/$1/g; + s/\bINCLUDE[ \t]*([^ \t\n]+)/.include \"$1\"/; + s/\bGET[ \t]*([^ \t\n]+)/.include \"${ my $x=$1; $x =~ s|\.s|-gnu.S|; \$x }\"/; + s/\bIMPORT\b/.extern/; + s/\bEXPORT\b\s*/.global $symprefix/; + s/^(\s+)\[/$1IF/; + s/^(\s+)\|/$1ELSE/; + s/^(\s+)\]/$1ENDIF/; + s/IF *:DEF:/ .ifdef/; + s/IF *:LNOT: *:DEF:/ .ifndef/; + s/ELSE/ .else/; + s/ENDIF/ .endif/; + + if( /\bIF\b/ ) { + s/\bIF\b/ .if/; + s/=/==/; + } + if ( $n == 2) { + s/\$/\\/g; + } + if ($n == 1) { + s/\$//g; + s/label//g; + $n = 2; + } + if ( /MACRO/ ) { + s/MACRO *\n/.macro/; + $n=1; + } + if ( /\bMEND\b/ ) { + s/\bMEND\b/.endm/; + $n=0; + } + + # ".rdata" doesn't work in 'as' version 2.13.2, as it is ".rodata" there. + # + if ( /\bAREA\b/ ) { + my $align; + $align = "2"; + if ( /ALIGN=(\d+)/ ) { + $align = $1; + } + if ( /CODE/ ) { + $nxstack = 1; + } + s/^(.+)CODE(.+)READONLY(.*)/ .text/; + s/^(.+)DATA(.+)READONLY(.*)/ .section .rdata/; + s/^(.+)\|\|\.data\|\|(.+)/ .data/; + s/^(.+)\|\|\.bss\|\|(.+)/ .bss/; + s/$/; .p2align $align/; + # Enable NEON instructions but don't produce a binary that requires + # ARMv7. RVCT does not have equivalent directives, so we just do this + # for all CODE areas. + if ( /.text/ ) { + # Separating .arch, .fpu, etc., by semicolons does not work (gas + # thinks the semicolon is part of the arch name, even when there's + # whitespace separating them). Sadly this means our line numbers + # won't match the original source file (we could use the .line + # directive, which is documented to be obsolete, but then gdb will + # show the wrong line in the translated source file). + s/$/; .arch armv7-a\n .fpu neon\n .object_arch armv4t/ unless ($apple); + } + } + + s/\|\|\.constdata\$(\d+)\|\|/.L_CONST$1/; # ||.constdata$3|| + s/\|\|\.bss\$(\d+)\|\|/.L_BSS$1/; # ||.bss$2|| + s/\|\|\.data\$(\d+)\|\|/.L_DATA$1/; # ||.data$2|| + s/\|\|([a-zA-Z0-9_]+)\@([a-zA-Z0-9_]+)\|\|/@ $&/; + s/^(\s+)\%(\s)/ .space $1/; + + s/\|(.+)\.(\d+)\|/\.$1_$2/; # |L80.123| -> .L80_123 + s/\bCODE32\b/.code 32/ && do {$thumb = 0}; + s/\bCODE16\b/.code 16/ && do {$thumb = 1}; + if (/\bPROC\b/) + { + my $prefix; + my $proc; + /^([A-Za-z_\.]\w+)\b/; + $proc = $1; + $prefix = ""; + if ($proc) + { + $prefix = $prefix.sprintf("\t.type\t%s, %%function", $proc) unless ($apple); + # Make sure we $prefix isn't empty here (for the $apple case). + # We handle mangling the label here, make sure it doesn't match + # the label handling below (if $prefix would be empty). + $prefix = $prefix."; "; + push(@proc_stack, $proc); + s/^[A-Za-z_\.]\w+/$symprefix$&:/; + } + $prefix = $prefix."\t.thumb_func; " if ($thumb); + s/\bPROC\b/@ $&/; + $_ = $prefix.$_; + } + s/^(\s*)(S|Q|SH|U|UQ|UH)ASX\b/$1$2ADDSUBX/; + s/^(\s*)(S|Q|SH|U|UQ|UH)SAX\b/$1$2SUBADDX/; + if (/\bENDP\b/) + { + my $proc; + s/\bENDP\b/@ $&/; + $proc = pop(@proc_stack); + $_ = "\t.size $proc, .-$proc".$_ if ($proc && !$apple); + } + s/\bSUBT\b/@ $&/; + s/\bDATA\b/@ $&/; # DATA directive is deprecated -- Asm guide, p.7-25 + s/\bKEEP\b/@ $&/; + s/\bEXPORTAS\b/@ $&/; + s/\|\|(.)+\bEQU\b/@ $&/; + s/\|\|([\w\$]+)\|\|/$1/; + s/\bENTRY\b/@ $&/; + s/\bASSERT\b/@ $&/; + s/\bGBLL\b/@ $&/; + s/\bGBLA\b/@ $&/; + s/^\W+OPT\b/@ $&/; + s/:OR:/|/g; + s/:SHL:/<>/g; + s/:AND:/&/g; + s/:LAND:/&&/g; + s/CPSR/cpsr/; + s/SPSR/spsr/; + s/ALIGN$/.balign 4/; + s/ALIGN\s+([0-9x]+)$/.balign $1/; + s/psr_cxsf/psr_all/; + s/LTORG/.ltorg/; + s/^([A-Za-z_]\w*)[ \t]+EQU/ .set $1,/; + s/^([A-Za-z_]\w*)[ \t]+SETL/ .set $1,/; + s/^([A-Za-z_]\w*)[ \t]+SETA/ .set $1,/; + s/^([A-Za-z_]\w*)[ \t]+\*/ .set $1,/; + + # {PC} + 0xdeadfeed --> . + 0xdeadfeed + s/\{PC\} \+/ \. +/; + + # Single hex constant on the line ! + # + # >>> NOTE <<< + # Double-precision floats in gcc are always mixed-endian, which means + # bytes in two words are little-endian, but words are big-endian. + # So, 0x0000deadfeed0000 would be stored as 0x0000dead at low address + # and 0xfeed0000 at high address. + # + s/\bDCFD\b[ \t]+0x([a-fA-F0-9]{8})([a-fA-F0-9]{8})/.long 0x$1, 0x$2/; + # Only decimal constants on the line, no hex ! + s/\bDCFD\b[ \t]+([0-9\.\-]+)/.double $1/; + + # Single hex constant on the line ! +# s/\bDCFS\b[ \t]+0x([a-f0-9]{8})([a-f0-9]{8})/.long 0x$1, 0x$2/; + # Only decimal constants on the line, no hex ! +# s/\bDCFS\b[ \t]+([0-9\.\-]+)/.double $1/; + s/\bDCFS[ \t]+0x/.word 0x/; + s/\bDCFS\b/.float/; + + s/^([A-Za-z_]\w*)[ \t]+DCD/$1 .word/; + s/\bDCD\b/.word/; + s/^([A-Za-z_]\w*)[ \t]+DCW/$1 .short/; + s/\bDCW\b/.short/; + s/^([A-Za-z_]\w*)[ \t]+DCB/$1 .byte/; + s/\bDCB\b/.byte/; + s/^([A-Za-z_]\w*)[ \t]+\%/.comm $1,/; + s/^[A-Za-z_\.]\w+/$&:/; + s/^(\d+)/$1:/; + s/\%(\d+)/$1b_or_f/; + s/\%[Bb](\d+)/$1b/; + s/\%[Ff](\d+)/$1f/; + s/\%[Ff][Tt](\d+)/$1f/; + s/&([\dA-Fa-f]+)/0x$1/; + if ( /\b2_[01]+\b/ ) { + s/\b2_([01]+)\b/conv$1&&&&/g; + while ( /[01][01][01][01]&&&&/ ) { + s/0000&&&&/&&&&0/g; + s/0001&&&&/&&&&1/g; + s/0010&&&&/&&&&2/g; + s/0011&&&&/&&&&3/g; + s/0100&&&&/&&&&4/g; + s/0101&&&&/&&&&5/g; + s/0110&&&&/&&&&6/g; + s/0111&&&&/&&&&7/g; + s/1000&&&&/&&&&8/g; + s/1001&&&&/&&&&9/g; + s/1010&&&&/&&&&A/g; + s/1011&&&&/&&&&B/g; + s/1100&&&&/&&&&C/g; + s/1101&&&&/&&&&D/g; + s/1110&&&&/&&&&E/g; + s/1111&&&&/&&&&F/g; + } + s/000&&&&/&&&&0/g; + s/001&&&&/&&&&1/g; + s/010&&&&/&&&&2/g; + s/011&&&&/&&&&3/g; + s/100&&&&/&&&&4/g; + s/101&&&&/&&&&5/g; + s/110&&&&/&&&&6/g; + s/111&&&&/&&&&7/g; + s/00&&&&/&&&&0/g; + s/01&&&&/&&&&1/g; + s/10&&&&/&&&&2/g; + s/11&&&&/&&&&3/g; + s/0&&&&/&&&&0/g; + s/1&&&&/&&&&1/g; + s/conv&&&&/0x/g; + } + + if ( /commandline/) + { + if( /-bigend/) + { + $bigend=1; + } + } + + if ( /\bDCDU\b/ ) + { + my $cmd=$_; + my $value; + my $prefix; + my $w1; + my $w2; + my $w3; + my $w4; + + s/\s+DCDU\b/@ $&/; + + $cmd =~ /\bDCDU\b\s+0x(\d+)/; + $value = $1; + $value =~ /(\w\w)(\w\w)(\w\w)(\w\w)/; + $w1 = $1; + $w2 = $2; + $w3 = $3; + $w4 = $4; + + if( $bigend ne "") + { + # big endian + $prefix = "\t.byte\t0x".$w1.";". + "\t.byte\t0x".$w2.";". + "\t.byte\t0x".$w3.";". + "\t.byte\t0x".$w4."; "; + } + else + { + # little endian + $prefix = "\t.byte\t0x".$w4.";". + "\t.byte\t0x".$w3.";". + "\t.byte\t0x".$w2.";". + "\t.byte\t0x".$w1."; "; + } + $_=$prefix.$_; + } + + if ( /\badrl\b/i ) + { + s/\badrl\s+(\w+)\s*,\s*(\w+)/ldr $1,=$2/i; + $addPadding = 1; + } + s/\bEND\b/@ END/; +} continue { + printf ("%s", $_) if $printit; + if ($addPadding != 0) + { + printf (" mov r0,r0\n"); + $addPadding = 0; + } +} +#If we had a code section, mark that this object doesn't need an executable +# stack. +if ($nxstack && !$apple) { + printf (" .section\t.note.GNU-stack,\"\",\%\%progbits\n"); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm_celt_map.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm_celt_map.c new file mode 100644 index 0000000000..cbaea49579 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/arm_celt_map.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pitch.h" +#include "kiss_fft.h" +#include "mdct.h" + +#if defined(OPUS_HAVE_RTCD) + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) +opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y, int N) = { + celt_inner_prod_c, /* ARMv4 */ + celt_inner_prod_c, /* EDSP */ + celt_inner_prod_c, /* Media */ + celt_inner_prod_neon,/* NEON */ + celt_inner_prod_neon /* DOTPROD */ +}; + +void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2) = { + dual_inner_prod_c, /* ARMv4 */ + dual_inner_prod_c, /* EDSP */ + dual_inner_prod_c, /* Media */ + dual_inner_prod_neon,/* NEON */ + dual_inner_prod_neon /* DOTPROD */ +}; +# endif + +# if defined(FIXED_POINT) +# if ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \ + (defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \ + (defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP))) +opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int, int) = { + celt_pitch_xcorr_c, /* ARMv4 */ + MAY_HAVE_EDSP(celt_pitch_xcorr), /* EDSP */ + MAY_HAVE_MEDIA(celt_pitch_xcorr), /* Media */ + MAY_HAVE_NEON(celt_pitch_xcorr), /* NEON */ + MAY_HAVE_NEON(celt_pitch_xcorr) /* DOTPROD */ +}; + +# endif +# else /* !FIXED_POINT */ +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) +void (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int, int) = { + celt_pitch_xcorr_c, /* ARMv4 */ + celt_pitch_xcorr_c, /* EDSP */ + celt_pitch_xcorr_c, /* Media */ + celt_pitch_xcorr_float_neon, /* Neon */ + celt_pitch_xcorr_float_neon /* DOTPROD */ +}; +# endif +# endif /* FIXED_POINT */ + +#if defined(FIXED_POINT) && defined(OPUS_HAVE_RTCD) && \ + defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) + +void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len +) = { + xcorr_kernel_c, /* ARMv4 */ + xcorr_kernel_c, /* EDSP */ + xcorr_kernel_c, /* Media */ + xcorr_kernel_neon_fixed, /* Neon */ + xcorr_kernel_neon_fixed /* DOTPROD */ +}; + +#endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +# if defined(HAVE_ARM_NE10) +# if defined(CUSTOM_MODES) +int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = { + opus_fft_alloc_arch_c, /* ARMv4 */ + opus_fft_alloc_arch_c, /* EDSP */ + opus_fft_alloc_arch_c, /* Media */ + opus_fft_alloc_arm_neon, /* Neon with NE10 library support */ + opus_fft_alloc_arm_neon /* DOTPROD with NE10 library support */ +}; + +void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = { + opus_fft_free_arch_c, /* ARMv4 */ + opus_fft_free_arch_c, /* EDSP */ + opus_fft_free_arch_c, /* Media */ + opus_fft_free_arm_neon, /* Neon with NE10 */ + opus_fft_free_arm_neon /* DOTPROD with NE10 */ +}; +# endif /* CUSTOM_MODES */ + +void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) = { + opus_fft_c, /* ARMv4 */ + opus_fft_c, /* EDSP */ + opus_fft_c, /* Media */ + opus_fft_neon, /* Neon with NE10 */ + opus_fft_neon /* DOTPROD with NE10 */ +}; + +void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) = { + opus_ifft_c, /* ARMv4 */ + opus_ifft_c, /* EDSP */ + opus_ifft_c, /* Media */ + opus_ifft_neon, /* Neon with NE10 */ + opus_ifft_neon /* DOTPROD with NE10 */ +}; + +void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, + int stride, int arch) = { + clt_mdct_forward_c, /* ARMv4 */ + clt_mdct_forward_c, /* EDSP */ + clt_mdct_forward_c, /* Media */ + clt_mdct_forward_neon, /* Neon with NE10 */ + clt_mdct_forward_neon /* DOTPROD with NE10 */ +}; + +void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, + int stride, int arch) = { + clt_mdct_backward_c, /* ARMv4 */ + clt_mdct_backward_c, /* EDSP */ + clt_mdct_backward_c, /* Media */ + clt_mdct_backward_neon, /* Neon with NE10 */ + clt_mdct_backward_neon /* DOTPROD with NE10 */ +}; + +# endif /* HAVE_ARM_NE10 */ +# endif /* OPUS_ARM_MAY_HAVE_NEON_INTR */ + +#endif /* OPUS_HAVE_RTCD */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.c new file mode 100644 index 0000000000..6785121ac9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.c @@ -0,0 +1,291 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Original code from libtheora modified to suit to Opus */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_HAVE_RTCD + +#include "armcpu.h" +#include "cpu_support.h" +#include "os_support.h" +#include "opus_types.h" +#include "arch.h" + +#define OPUS_CPU_ARM_V4_FLAG (1< + +static OPUS_INLINE opus_uint32 opus_cpu_capabilities(void){ + opus_uint32 flags; + flags=0; + /* MSVC has no OPUS_INLINE __asm support for ARM, but it does let you __emit + * instructions via their assembled hex code. + * All of these instructions should be essentially nops. */ +# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + __try{ + /*PLD [r13]*/ + __emit(0xF5DDF000); + flags|=OPUS_CPU_ARM_EDSP_FLAG; + } + __except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){ + /*Ignore exception.*/ + } +# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + __try{ + /*SHADD8 r3,r3,r3*/ + __emit(0xE6333F93); + flags|=OPUS_CPU_ARM_MEDIA_FLAG; + } + __except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){ + /*Ignore exception.*/ + } +# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + __try{ + /*VORR q0,q0,q0*/ + __emit(0xF2200150); + flags|=OPUS_CPU_ARM_NEON_FLAG; + } + __except(GetExceptionCode()==EXCEPTION_ILLEGAL_INSTRUCTION){ + /*Ignore exception.*/ + } +# endif +# endif +# endif + return flags; +} + +#elif defined(__linux__) +/* Linux based */ +#include + +static opus_uint32 opus_cpu_capabilities(void) +{ + opus_uint32 flags = 0; + FILE *cpuinfo; + + /* Reading /proc/self/auxv would be easier, but that doesn't work reliably on + * Android */ + cpuinfo = fopen("/proc/cpuinfo", "r"); + + if(cpuinfo != NULL) + { + /* 512 should be enough for anybody (it's even enough for all the flags that + * x86 has accumulated... so far). */ + char buf[512]; + + while(fgets(buf, 512, cpuinfo) != NULL) + { +# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + /* Search for edsp and neon flag */ + if(memcmp(buf, "Features", 8) == 0) + { + char *p; + p = strstr(buf, " edsp"); + if(p != NULL && (p[5] == ' ' || p[5] == '\n')) + flags |= OPUS_CPU_ARM_EDSP_FLAG; + +# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + p = strstr(buf, " neon"); + if(p != NULL && (p[5] == ' ' || p[5] == '\n')) + flags |= OPUS_CPU_ARM_NEON_FLAG; + p = strstr(buf, " asimd"); + if(p != NULL && (p[6] == ' ' || p[6] == '\n')) + flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG; +# endif +# if defined(OPUS_ARM_MAY_HAVE_DOTPROD) + p = strstr(buf, " asimddp"); + if(p != NULL && (p[8] == ' ' || p[8] == '\n')) + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; +# endif + } +# endif + +# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + /* Search for media capabilities (>= ARMv6) */ + if(memcmp(buf, "CPU architecture:", 17) == 0) + { + int version; + version = atoi(buf+17); + + if(version >= 6) + flags |= OPUS_CPU_ARM_MEDIA_FLAG; + } +# endif + } + +#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR) + flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG; +# if defined(OPUS_ARM_PRESUME_DOTPROD) + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; +# endif +#endif + + fclose(cpuinfo); + } + return flags; +} + +#elif defined(__APPLE__) +#include +#include + +static opus_uint32 opus_cpu_capabilities(void) +{ + opus_uint32 flags = 0; + +#if defined(OPUS_ARM_MAY_HAVE_DOTPROD) + size_t size = sizeof(uint32_t); + uint32_t value = 0; + if (!sysctlbyname("hw.optional.arm.FEAT_DotProd", &value, &size, NULL, 0) && value) + { + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; + } +#endif + +#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR) + flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG; +# if defined(OPUS_ARM_PRESUME_DOTPROD) + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; +# endif +#endif + return flags; +} + +#elif defined(__FreeBSD__) +#include + +static opus_uint32 opus_cpu_capabilities(void) +{ + long hwcap = 0; + opus_uint32 flags = 0; + +# if defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + /* FreeBSD requires armv6+, which always supports media instructions */ + flags |= OPUS_CPU_ARM_MEDIA_FLAG; +# endif + + elf_aux_info(AT_HWCAP, &hwcap, sizeof hwcap); + +# if defined(OPUS_ARM_MAY_HAVE_EDSP) || defined(OPUS_ARM_MAY_HAVE_MEDIA) \ + || defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +# ifdef HWCAP_EDSP + if (hwcap & HWCAP_EDSP) + flags |= OPUS_CPU_ARM_EDSP_FLAG; +# endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +# ifdef HWCAP_NEON + if (hwcap & HWCAP_NEON) + flags |= OPUS_CPU_ARM_NEON_FLAG; +# elif defined(HWCAP_ASIMD) + if (hwcap & HWCAP_ASIMD) + flags |= OPUS_CPU_ARM_NEON_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_EDSP_FLAG; +# endif +# endif +# if defined(OPUS_ARM_MAY_HAVE_DOTPROD) && defined(HWCAP_ASIMDDP) + if (hwcap & HWCAP_ASIMDDP) + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; +# endif +# endif + +#if defined(OPUS_ARM_PRESUME_AARCH64_NEON_INTR) + flags |= OPUS_CPU_ARM_EDSP_FLAG | OPUS_CPU_ARM_MEDIA_FLAG | OPUS_CPU_ARM_NEON_FLAG; +# if defined(OPUS_ARM_PRESUME_DOTPROD) + flags |= OPUS_CPU_ARM_DOTPROD_FLAG; +# endif +#endif + + return (flags); +} + +#else +/* The feature registers which can tell us what the processor supports are + * accessible in priveleged modes only, so we can't have a general user-space + * detection method like on x86.*/ +# error "Configured to use ARM asm but no CPU detection method available for " \ + "your platform. Reconfigure with --disable-rtcd (or send patches)." +#endif + +static int opus_select_arch_impl(void) +{ + opus_uint32 flags = opus_cpu_capabilities(); + int arch = 0; + + if(!(flags & OPUS_CPU_ARM_EDSP_FLAG)) { + /* Asserts ensure arch values are sequential */ + celt_assert(arch == OPUS_ARCH_ARM_V4); + return arch; + } + arch++; + + if(!(flags & OPUS_CPU_ARM_MEDIA_FLAG)) { + celt_assert(arch == OPUS_ARCH_ARM_EDSP); + return arch; + } + arch++; + + if(!(flags & OPUS_CPU_ARM_NEON_FLAG)) { + celt_assert(arch == OPUS_ARCH_ARM_MEDIA); + return arch; + } + arch++; + + if(!(flags & OPUS_CPU_ARM_DOTPROD_FLAG)) { + celt_assert(arch == OPUS_ARCH_ARM_NEON); + return arch; + } + arch++; + + celt_assert(arch == OPUS_ARCH_ARM_DOTPROD); + return arch; +} + +int opus_select_arch(void) { + int arch = opus_select_arch_impl(); +#ifdef FUZZING + arch = rand()%(arch+1); +#endif + return arch; +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.h new file mode 100644 index 0000000000..6d5803d81a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armcpu.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(ARMCPU_H) +# define ARMCPU_H + +# if defined(OPUS_ARM_MAY_HAVE_EDSP) +# define MAY_HAVE_EDSP(name) name ## _edsp +# else +# define MAY_HAVE_EDSP(name) name ## _c +# endif + +# if defined(OPUS_ARM_MAY_HAVE_MEDIA) +# define MAY_HAVE_MEDIA(name) name ## _media +# else +# define MAY_HAVE_MEDIA(name) MAY_HAVE_EDSP(name) +# endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON) +# define MAY_HAVE_NEON(name) name ## _neon +# else +# define MAY_HAVE_NEON(name) MAY_HAVE_MEDIA(name) +# endif + +# if defined(OPUS_ARM_MAY_HAVE_DOTPROD) +# define MAY_HAVE_DOTPROD(name) name ## _dotprod +# else +# define MAY_HAVE_DOTPROD(name) MAY_HAVE_NEON(name) +# endif + +# if defined(OPUS_ARM_PRESUME_EDSP) +# define PRESUME_EDSP(name) name ## _edsp +# else +# define PRESUME_EDSP(name) name ## _c +# endif + +# if defined(OPUS_ARM_PRESUME_MEDIA) +# define PRESUME_MEDIA(name) name ## _media +# else +# define PRESUME_MEDIA(name) PRESUME_EDSP(name) +# endif + +# if defined(OPUS_ARM_PRESUME_NEON) +# define PRESUME_NEON(name) name ## _neon +# else +# define PRESUME_NEON(name) PRESUME_MEDIA(name) +# endif + +# if defined(OPUS_ARM_PRESUME_DOTPROD) +# define PRESUME_DOTPROD(name) name ## _dotprod +# else +# define PRESUME_DOTPROD(name) PRESUME_NEON(name) +# endif + +# if defined(OPUS_HAVE_RTCD) +int opus_select_arch(void); + +#define OPUS_ARCH_ARM_V4 (0) +#define OPUS_ARCH_ARM_EDSP (1) +#define OPUS_ARCH_ARM_MEDIA (2) +#define OPUS_ARCH_ARM_NEON (3) +#define OPUS_ARCH_ARM_DOTPROD (4) + +# endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armopts.s.in b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armopts.s.in new file mode 100644 index 0000000000..3d8aaf2754 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/armopts.s.in @@ -0,0 +1,37 @@ +/* Copyright (C) 2013 Mozilla Corporation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +; Set the following to 1 if we have EDSP instructions +; (LDRD/STRD, etc., ARMv5E and later). +OPUS_ARM_MAY_HAVE_EDSP * @OPUS_ARM_MAY_HAVE_EDSP@ + +; Set the following to 1 if we have ARMv6 media instructions. +OPUS_ARM_MAY_HAVE_MEDIA * @OPUS_ARM_MAY_HAVE_MEDIA@ + +; Set the following to 1 if we have NEON (some ARMv7) +OPUS_ARM_MAY_HAVE_NEON * @OPUS_ARM_MAY_HAVE_NEON@ + +END diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_fft_ne10.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_fft_ne10.c new file mode 100644 index 0000000000..ea5fd7808b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_fft_ne10.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_fft_ne10.c + @brief ARM Neon optimizations for fft using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include +#include "os_support.h" +#include "kiss_fft.h" +#include "stack_alloc.h" + +#if !defined(FIXED_POINT) +# define NE10_FFT_ALLOC_C2C_TYPE_NEON ne10_fft_alloc_c2c_float32_neon +# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_float32_t +# define NE10_FFT_STATE_TYPE_T ne10_fft_state_float32_t +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_float32 +# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_float32_t +# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_float32_neon +#else +# define NE10_FFT_ALLOC_C2C_TYPE_NEON(nfft) ne10_fft_alloc_c2c_int32_neon(nfft) +# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_int32_t +# define NE10_FFT_STATE_TYPE_T ne10_fft_state_int32_t +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32 +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32 +# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_int32_t +# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_int32_neon +#endif + +#if defined(CUSTOM_MODES) + +/* nfft lengths in NE10 that support scaled fft */ +# define NE10_FFTSCALED_SUPPORT_MAX 4 +static const int ne10_fft_scaled_support[NE10_FFTSCALED_SUPPORT_MAX] = { + 480, 240, 120, 60 +}; + +int opus_fft_alloc_arm_neon(kiss_fft_state *st) +{ + int i; + size_t memneeded = sizeof(struct arch_fft_state); + + st->arch_fft = (arch_fft_state *)opus_alloc(memneeded); + if (!st->arch_fft) + return -1; + + for (i = 0; i < NE10_FFTSCALED_SUPPORT_MAX; i++) { + if(st->nfft == ne10_fft_scaled_support[i]) + break; + } + if (i == NE10_FFTSCALED_SUPPORT_MAX) { + /* This nfft length (scaled fft) is not supported in NE10 */ + st->arch_fft->is_supported = 0; + st->arch_fft->priv = NULL; + } + else { + st->arch_fft->is_supported = 1; + st->arch_fft->priv = (void *)NE10_FFT_ALLOC_C2C_TYPE_NEON(st->nfft); + if (st->arch_fft->priv == NULL) { + return -1; + } + } + return 0; +} + +void opus_fft_free_arm_neon(kiss_fft_state *st) +{ + NE10_FFT_CFG_TYPE_T cfg; + + if (!st->arch_fft) + return; + + cfg = (NE10_FFT_CFG_TYPE_T)st->arch_fft->priv; + if (cfg) + NE10_FFT_DESTROY_C2C_TYPE(cfg); + opus_free(st->arch_fft); +} +#endif + +void opus_fft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) +{ + NE10_FFT_STATE_TYPE_T state; + NE10_FFT_CFG_TYPE_T cfg = &state; + VARDECL(NE10_FFT_CPX_TYPE_T, buffer); + SAVE_STACK; + ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T); + + if (!st->arch_fft->is_supported) { + /* This nfft length (scaled fft) not supported in NE10 */ + opus_fft_c(st, fin, fout); + } + else { + memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T)); + state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0]; +#if !defined(FIXED_POINT) + state.is_forward_scaled = 1; + + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 0); +#else + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 0, 1); +#endif + } + RESTORE_STACK; +} + +void opus_ifft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) +{ + NE10_FFT_STATE_TYPE_T state; + NE10_FFT_CFG_TYPE_T cfg = &state; + VARDECL(NE10_FFT_CPX_TYPE_T, buffer); + SAVE_STACK; + ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T); + + if (!st->arch_fft->is_supported) { + /* This nfft length (scaled fft) not supported in NE10 */ + opus_ifft_c(st, fin, fout); + } + else { + memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T)); + state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0]; +#if !defined(FIXED_POINT) + state.is_backward_scaled = 0; + + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 1); +#else + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 1, 0); +#endif + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_mdct_ne10.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_mdct_ne10.c new file mode 100644 index 0000000000..3531d02d10 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_mdct_ne10.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_mdct_ne10.c + @brief ARM Neon optimizations for mdct using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include "mdct.h" +#include "stack_alloc.h" + +void clt_mdct_forward_neon(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + + SAVE_STACK; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + ALLOC(f2, N4, kiss_fft_cpx); + + /* Consider the input to be composed of four blocks: [a, b, c, d] */ + /* Window, shuffle, fold */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1); + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1); + const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1; + for(i=0;i<((overlap+3)>>2);i++) + { + /* Real part arranged as -d-cR, Imag part arranged as -b+aR*/ + *yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2); + *yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]); + xp1+=2; + xp2-=2; + wp1+=2; + wp2-=2; + } + wp1 = window; + wp2 = window+overlap-1; + for(;i>2);i++) + { + /* Real part arranged as a-bR, Imag part arranged as -c-dR */ + *yp++ = *xp2; + *yp++ = *xp1; + xp1+=2; + xp2-=2; + } + for(;ii,t[N4+i]) - S_MUL(fp->r,t[i]); + yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; + yp1 += 2*stride; + yp2 -= 2*stride; + } + } + RESTORE_STACK; +} + +void clt_mdct_backward_neon(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, + int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + const kiss_twiddle_scalar *trig; + const kiss_fft_state *st = l->kfft[shift]; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + + /* Pre-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + for(i=0;i>1)), arch); + + /* Post-rotate and de-shuffle from both ends of the buffer at once to make + it in-place. */ + { + kiss_fft_scalar * yp0 = out+(overlap>>1); + kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; + /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the + middle pair will be computed twice. */ + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + re = yp0[0]; + im = yp0[1]; + t0 = t[i]; + t1 = t[N4+i]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL(re,t0) + S_MUL(im,t1); + yi = S_MUL(re,t1) - S_MUL(im,t0); + re = yp1[0]; + im = yp1[1]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL(re,t0) + S_MUL(im,t1); + yi = S_MUL(re,t1) - S_MUL(im,t0); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + } + } + + /* Mirror on both sides for TDAC */ + { + kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + const opus_val16 * OPUS_RESTRICT wp1 = window; + const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; + + for(i = 0; i < overlap/2; i++) + { + kiss_fft_scalar x1, x2; + x1 = *xp1; + x2 = *yp1; + *yp1++ = MULT16_32_Q15(*wp2, x2) - MULT16_32_Q15(*wp1, x1); + *xp1-- = MULT16_32_Q15(*wp1, x2) + MULT16_32_Q15(*wp2, x1); + wp1++; + wp2--; + } + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_neon_intr.c new file mode 100644 index 0000000000..250f836218 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_neon_intr.c @@ -0,0 +1,272 @@ +/* Copyright (c) 2014-2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_neon_intr.c + @brief ARM Neon Intrinsic optimizations for celt + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "../pitch.h" + +#if defined(FIXED_POINT) +#include + +void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len) +{ + int j; + int32x4_t a = vld1q_s32(sum); + /* Load y[0...3] */ + /* This requires len>0 to always be valid (which we assert in the C code). */ + int16x4_t y0 = vld1_s16(y); + y += 4; + + /* This loop loads one y value more than we actually need. + Therefore we have to stop as soon as there are 8 or fewer samples left + (instead of 7), to avoid reading past the end of the array. */ + for (j = 0; j + 8 < len; j += 8) + { + /* Load x[0...7] */ + int16x8_t xx = vld1q_s16(x); + int16x4_t x0 = vget_low_s16(xx); + int16x4_t x4 = vget_high_s16(xx); + /* Load y[4...11] */ + int16x8_t yy = vld1q_s16(y); + int16x4_t y4 = vget_low_s16(yy); + int16x4_t y8 = vget_high_s16(yy); + int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0); + int32x4_t a1 = vmlal_lane_s16(a0, y4, x4, 0); + + int16x4_t y1 = vext_s16(y0, y4, 1); + int16x4_t y5 = vext_s16(y4, y8, 1); + int32x4_t a2 = vmlal_lane_s16(a1, y1, x0, 1); + int32x4_t a3 = vmlal_lane_s16(a2, y5, x4, 1); + + int16x4_t y2 = vext_s16(y0, y4, 2); + int16x4_t y6 = vext_s16(y4, y8, 2); + int32x4_t a4 = vmlal_lane_s16(a3, y2, x0, 2); + int32x4_t a5 = vmlal_lane_s16(a4, y6, x4, 2); + + int16x4_t y3 = vext_s16(y0, y4, 3); + int16x4_t y7 = vext_s16(y4, y8, 3); + int32x4_t a6 = vmlal_lane_s16(a5, y3, x0, 3); + int32x4_t a7 = vmlal_lane_s16(a6, y7, x4, 3); + + y0 = y8; + a = a7; + x += 8; + y += 8; + } + if (j + 4 < len) { + /* Load x[0...3] */ + int16x4_t x0 = vld1_s16(x); + /* Load y[4...7] */ + int16x4_t y4 = vld1_s16(y); + int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0); + int16x4_t y1 = vext_s16(y0, y4, 1); + int32x4_t a1 = vmlal_lane_s16(a0, y1, x0, 1); + int16x4_t y2 = vext_s16(y0, y4, 2); + int32x4_t a2 = vmlal_lane_s16(a1, y2, x0, 2); + int16x4_t y3 = vext_s16(y0, y4, 3); + int32x4_t a3 = vmlal_lane_s16(a2, y3, x0, 3); + y0 = y4; + a = a3; + x += 4; + y += 4; + j += 4; + } + if (j + 2 < len) { + /* Load x[0...1] */ + int16x4x2_t xx = vld2_dup_s16(x); + int16x4_t x0 = xx.val[0]; + int16x4_t x1 = xx.val[1]; + /* Load y[4...5]. + We would like to use vld1_dup_s32(), but casting the pointer would + break strict aliasing rules and potentially have alignment issues. + Fortunately the compiler seems capable of translating this memcpy() + and vdup_n_s32() into the equivalent vld1_dup_s32().*/ + int32_t yy; + memcpy(&yy, y, sizeof(yy)); + int16x4_t y4 = vreinterpret_s16_s32(vdup_n_s32(yy)); + int32x4_t a0 = vmlal_s16(a, y0, x0); + int16x4_t y1 = vext_s16(y0, y4, 1); + /* Replace bottom copy of {y[5], y[4]} in y4 with {y[3], y[2]} from y0, + using VSRI instead of VEXT, since it's a data-processing + instruction. */ + y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4), + vreinterpret_s64_s16(y0), 32)); + int32x4_t a1 = vmlal_s16(a0, y1, x1); + a = a1; + x += 2; + y += 2; + j += 2; + } + if (j + 1 < len) { + /* Load next x. */ + int16x4_t x0 = vld1_dup_s16(x); + int32x4_t a0 = vmlal_s16(a, y0, x0); + /* Load last y. */ + int16x4_t y4 = vld1_dup_s16(y); + y0 = vreinterpret_s16_s64(vsri_n_s64(vreinterpret_s64_s16(y4), + vreinterpret_s64_s16(y0), 16)); + a = a0; + x++; + } + /* Load last x. */ + int16x4_t x0 = vld1_dup_s16(x); + int32x4_t a0 = vmlal_s16(a, y0, x0); + vst1q_s32(sum, a0); +} + +#else + +#if defined(__ARM_FEATURE_FMA) && defined(__ARM_ARCH_ISA_A64) +/* If we can, force the compiler to use an FMA instruction rather than break + * vmlaq_f32() into fmul/fadd. */ +#ifdef vmlaq_lane_f32 +#undef vmlaq_lane_f32 +#endif +#define vmlaq_lane_f32(a,b,c,lane) vfmaq_lane_f32(a,b,c,lane) +#endif + + +/* + * Function: xcorr_kernel_neon_float + * --------------------------------- + * Computes 4 correlation values and stores them in sum[4] + */ +static void xcorr_kernel_neon_float(const float32_t *x, const float32_t *y, + float32_t sum[4], int len) { + float32x4_t YY[3]; + float32x4_t YEXT[3]; + float32x4_t XX[2]; + float32x2_t XX_2; + float32x4_t SUMM; + const float32_t *xi = x; + const float32_t *yi = y; + + celt_assert(len>0); + + YY[0] = vld1q_f32(yi); + SUMM = vdupq_n_f32(0); + + /* Consume 8 elements in x vector and 12 elements in y + * vector. However, the 12'th element never really gets + * touched in this loop. So, if len == 8, then we only + * must access y[0] to y[10]. y[11] must not be accessed + * hence make sure len > 8 and not len >= 8 + */ + while (len > 8) { + yi += 4; + YY[1] = vld1q_f32(yi); + yi += 4; + YY[2] = vld1q_f32(yi); + + XX[0] = vld1q_f32(xi); + xi += 4; + XX[1] = vld1q_f32(xi); + xi += 4; + + SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0); + YEXT[0] = vextq_f32(YY[0], YY[1], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1); + YEXT[1] = vextq_f32(YY[0], YY[1], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0); + YEXT[2] = vextq_f32(YY[0], YY[1], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1); + + SUMM = vmlaq_lane_f32(SUMM, YY[1], vget_low_f32(XX[1]), 0); + YEXT[0] = vextq_f32(YY[1], YY[2], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[1]), 1); + YEXT[1] = vextq_f32(YY[1], YY[2], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[1]), 0); + YEXT[2] = vextq_f32(YY[1], YY[2], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[1]), 1); + + YY[0] = YY[2]; + len -= 8; + } + + /* Consume 4 elements in x vector and 8 elements in y + * vector. However, the 8'th element in y never really gets + * touched in this loop. So, if len == 4, then we only + * must access y[0] to y[6]. y[7] must not be accessed + * hence make sure len>4 and not len>=4 + */ + if (len > 4) { + yi += 4; + YY[1] = vld1q_f32(yi); + + XX[0] = vld1q_f32(xi); + xi += 4; + + SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0); + YEXT[0] = vextq_f32(YY[0], YY[1], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1); + YEXT[1] = vextq_f32(YY[0], YY[1], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0); + YEXT[2] = vextq_f32(YY[0], YY[1], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1); + + YY[0] = YY[1]; + len -= 4; + } + + while (--len > 0) { + XX_2 = vld1_dup_f32(xi++); + SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0); + YY[0]= vld1q_f32(++yi); + } + + XX_2 = vld1_dup_f32(xi); + SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0); + + vst1q_f32(sum, SUMM); +} + +void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch) { + int i; + (void)arch; + celt_assert(max_pitch > 0); + celt_sig_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0); + + for (i = 0; i < (max_pitch-3); i += 4) { + xcorr_kernel_neon_float((const float32_t *)_x, (const float32_t *)_y+i, + (float32_t *)xcorr+i, len); + } + + /* In case max_pitch isn't a multiple of 4, do non-unrolled version. */ + for (; i < max_pitch; i++) { + xcorr[i] = celt_inner_prod_neon(_x, _y+i, len); + } +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm-gnu.S b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm-gnu.S new file mode 100644 index 0000000000..10668e54a5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm-gnu.S @@ -0,0 +1,555 @@ + .syntax unified +@ Copyright (c) 2007-2008 CSIRO +@ Copyright (c) 2007-2009 Xiph.Org Foundation +@ Copyright (c) 2013 Parrot +@ Written by Aurélien Zanelli +@ +@ Redistribution and use in source and binary forms, with or without +@ modification, are permitted provided that the following conditions +@ are met: +@ +@ - Redistributions of source code must retain the above copyright +@ notice, this list of conditions and the following disclaimer. +@ +@ - Redistributions in binary form must reproduce the above copyright +@ notice, this list of conditions and the following disclaimer in the +@ documentation and/or other materials provided with the distribution. +@ +@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +@ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +@ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +@ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +@ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + .text; .p2align 2; .arch armv7-a + .fpu neon + .object_arch armv4t + + .include "celt/arm/armopts-gnu.S" + + .if OPUS_ARM_MAY_HAVE_EDSP + .global celt_pitch_xcorr_edsp + .endif + + .if OPUS_ARM_MAY_HAVE_NEON + .global celt_pitch_xcorr_neon + .endif + + .if OPUS_ARM_MAY_HAVE_NEON + +@ Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3 + .type xcorr_kernel_neon, %function; xcorr_kernel_neon: @ PROC +xcorr_kernel_neon_start: + @ input: + @ r3 = int len + @ r4 = opus_val16 *x + @ r5 = opus_val16 *y + @ q0 = opus_val32 sum[4] + @ output: + @ q0 = opus_val32 sum[4] + @ preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15 + @ internal usage: + @ r12 = int j + @ d3 = y_3|y_2|y_1|y_0 + @ q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4 + @ q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0 + @ q8 = scratch + @ + @ Load y[0...3] + @ This requires len>0 to always be valid (which we assert in the C code). + VLD1.16 {d5}, [r5]! + SUBS r12, r3, #8 + BLE xcorr_kernel_neon_process4 +@ Process 8 samples at a time. +@ This loop loads one y value more than we actually need. Therefore we have to +@ stop as soon as there are 8 or fewer samples left (instead of 7), to avoid +@ reading past the end of the array. +xcorr_kernel_neon_process8: + @ This loop has 19 total instructions (10 cycles to issue, minimum), with + @ - 2 cycles of ARM insrtuctions, + @ - 10 cycles of load/store/byte permute instructions, and + @ - 9 cycles of data processing instructions. + @ On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the + @ latter two categories, meaning the whole loop should run in 10 cycles per + @ iteration, barring cache misses. + @ + @ Load x[0...7] + VLD1.16 {d6, d7}, [r4]! + @ Unlike VMOV, VAND is a data processsing instruction (and doesn't get + @ assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1. + VAND d3, d5, d5 + SUBS r12, r12, #8 + @ Load y[4...11] + VLD1.16 {d4, d5}, [r5]! + VMLAL.S16 q0, d3, d6[0] + VEXT.16 d16, d3, d4, #1 + VMLAL.S16 q0, d4, d7[0] + VEXT.16 d17, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d3, d4, #2 + VMLAL.S16 q0, d17, d7[1] + VEXT.16 d17, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d3, d4, #3 + VMLAL.S16 q0, d17, d7[2] + VEXT.16 d17, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] + VMLAL.S16 q0, d17, d7[3] + BGT xcorr_kernel_neon_process8 +@ Process 4 samples here if we have > 4 left (still reading one extra y value). +xcorr_kernel_neon_process4: + ADDS r12, r12, #4 + BLE xcorr_kernel_neon_process2 + @ Load x[0...3] + VLD1.16 d6, [r4]! + @ Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #4 + @ Load y[4...7] + VLD1.16 d5, [r5]! + VMLAL.S16 q0, d4, d6[0] + VEXT.16 d16, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] +@ Process 2 samples here if we have > 2 left (still reading one extra y value). +xcorr_kernel_neon_process2: + ADDS r12, r12, #2 + BLE xcorr_kernel_neon_process1 + @ Load x[0...1] + VLD2.16 {d6[],d7[]}, [r4]! + @ Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #2 + @ Load y[4...5] + VLD1.32 {d5[]}, [r5]! + VMLAL.S16 q0, d4, d6 + VEXT.16 d16, d4, d5, #1 + @ Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI + @ instead of VEXT, since it's a data-processing instruction. + VSRI.64 d5, d4, #32 + VMLAL.S16 q0, d16, d7 +@ Process 1 sample using the extra y value we loaded above. +xcorr_kernel_neon_process1: + @ Load next *x + VLD1.16 {d6[]}, [r4]! + ADDS r12, r12, #1 + @ y[0...3] are left in d5 from prior iteration(s) (if any) + VMLAL.S16 q0, d5, d6 + MOVLE pc, lr +@ Now process 1 last sample, not reading ahead. + @ Load last *y + VLD1.16 {d4[]}, [r5]! + VSRI.64 d4, d5, #16 + @ Load last *x + VLD1.16 {d6[]}, [r4]! + VMLAL.S16 q0, d4, d6 + MOV pc, lr + .size xcorr_kernel_neon, .-xcorr_kernel_neon @ ENDP + +@ opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y, +@ opus_val32 *xcorr, int len, int max_pitch, int arch) + .type celt_pitch_xcorr_neon, %function; celt_pitch_xcorr_neon: @ PROC + @ input: + @ r0 = opus_val16 *_x + @ r1 = opus_val16 *_y + @ r2 = opus_val32 *xcorr + @ r3 = int len + @ output: + @ r0 = int maxcorr + @ internal usage: + @ r4 = opus_val16 *x (for xcorr_kernel_neon()) + @ r5 = opus_val16 *y (for xcorr_kernel_neon()) + @ r6 = int max_pitch + @ r12 = int j + @ q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon()) + @ ignored: + @ int arch + STMFD sp!, {r4-r6, lr} + LDR r6, [sp, #16] + VMOV.S32 q15, #1 + @ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + SUBS r6, r6, #4 + BLT celt_pitch_xcorr_neon_process4_done +celt_pitch_xcorr_neon_process4: + @ xcorr_kernel_neon parameters: + @ r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0} + MOV r4, r0 + MOV r5, r1 + VEOR q0, q0, q0 + @ xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3. + @ So we don't save/restore any other registers. + BL xcorr_kernel_neon_start + SUBS r6, r6, #4 + VST1.32 {q0}, [r2]! + @ _y += 4 + ADD r1, r1, #8 + VMAX.S32 q15, q15, q0 + @ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + BGE celt_pitch_xcorr_neon_process4 +@ We have less than 4 sums left to compute. +celt_pitch_xcorr_neon_process4_done: + ADDS r6, r6, #4 + @ Reduce maxcorr to a single value + VMAX.S32 d30, d30, d31 + VPMAX.S32 d30, d30, d30 + @ if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done + BLE celt_pitch_xcorr_neon_done +@ Now compute each remaining sum one at a time. +celt_pitch_xcorr_neon_process_remaining: + MOV r4, r0 + MOV r5, r1 + VMOV.I32 q0, #0 + SUBS r12, r3, #8 + BLT celt_pitch_xcorr_neon_process_remaining4 +@ Sum terms 8 at a time. +celt_pitch_xcorr_neon_process_remaining_loop8: + @ Load x[0...7] + VLD1.16 {q1}, [r4]! + @ Load y[0...7] + VLD1.16 {q2}, [r5]! + SUBS r12, r12, #8 + VMLAL.S16 q0, d4, d2 + VMLAL.S16 q0, d5, d3 + BGE celt_pitch_xcorr_neon_process_remaining_loop8 +@ Sum terms 4 at a time. +celt_pitch_xcorr_neon_process_remaining4: + ADDS r12, r12, #4 + BLT celt_pitch_xcorr_neon_process_remaining4_done + @ Load x[0...3] + VLD1.16 {d2}, [r4]! + @ Load y[0...3] + VLD1.16 {d3}, [r5]! + SUB r12, r12, #4 + VMLAL.S16 q0, d3, d2 +celt_pitch_xcorr_neon_process_remaining4_done: + @ Reduce the sum to a single value. + VADD.S32 d0, d0, d1 + VPADDL.S32 d0, d0 + ADDS r12, r12, #4 + BLE celt_pitch_xcorr_neon_process_remaining_loop_done +@ Sum terms 1 at a time. +celt_pitch_xcorr_neon_process_remaining_loop1: + VLD1.16 {d2[]}, [r4]! + VLD1.16 {d3[]}, [r5]! + SUBS r12, r12, #1 + VMLAL.S16 q0, d2, d3 + BGT celt_pitch_xcorr_neon_process_remaining_loop1 +celt_pitch_xcorr_neon_process_remaining_loop_done: + VST1.32 {d0[0]}, [r2]! + VMAX.S32 d30, d30, d0 + SUBS r6, r6, #1 + @ _y++ + ADD r1, r1, #2 + @ if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining + BGT celt_pitch_xcorr_neon_process_remaining +celt_pitch_xcorr_neon_done: + VMOV.32 r0, d30[0] + LDMFD sp!, {r4-r6, pc} + .size celt_pitch_xcorr_neon, .-celt_pitch_xcorr_neon @ ENDP + + .endif + + .if OPUS_ARM_MAY_HAVE_EDSP + +@ This will get used on ARMv7 devices without NEON, so it has been optimized +@ to take advantage of dual-issuing where possible. + .type xcorr_kernel_edsp, %function; xcorr_kernel_edsp: @ PROC +xcorr_kernel_edsp_start: + @ input: + @ r3 = int len + @ r4 = opus_val16 *_x (must be 32-bit aligned) + @ r5 = opus_val16 *_y (must be 32-bit aligned) + @ r6...r9 = opus_val32 sum[4] + @ output: + @ r6...r9 = opus_val32 sum[4] + @ preserved: r0-r5 + @ internal usage + @ r2 = int j + @ r12,r14 = opus_val16 x[4] + @ r10,r11 = opus_val16 y[4] + STMFD sp!, {r2,r4,r5,lr} + LDR r10, [r5], #4 @ Load y[0...1] + SUBS r2, r3, #4 @ j = len-4 + LDR r11, [r5], #4 @ Load y[2...3] + BLE xcorr_kernel_edsp_process4_done + LDR r12, [r4], #4 @ Load x[0...1] + @ Stall +xcorr_kernel_edsp_process4: + @ The multiplies must issue from pipeline 0, and can't dual-issue with each + @ other. Every other instruction here dual-issues with a multiply, and is + @ thus "free". There should be no stalls in the body of the loop. + SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_0,y_0) + LDR r14, [r4], #4 @ Load x[2...3] + SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x_0,y_1) + SUBS r2, r2, #4 @ j-=4 + SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_0,y_2) + SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x_0,y_3) + SMLATT r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_1,y_1) + LDR r10, [r5], #4 @ Load y[4...5] + SMLATB r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],x_1,y_2) + SMLATT r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_1,y_3) + SMLATB r9, r12, r10, r9 @ sum[3] = MAC16_16(sum[3],x_1,y_4) + LDRGT r12, [r4], #4 @ Load x[0...1] + SMLABB r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_2,y_2) + SMLABT r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x_2,y_3) + SMLABB r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_2,y_4) + SMLABT r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x_2,y_5) + SMLATT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_3,y_3) + LDR r11, [r5], #4 @ Load y[6...7] + SMLATB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],x_3,y_4) + SMLATT r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_3,y_5) + SMLATB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],x_3,y_6) + BGT xcorr_kernel_edsp_process4 +xcorr_kernel_edsp_process4_done: + ADDS r2, r2, #4 + BLE xcorr_kernel_edsp_done + LDRH r12, [r4], #2 @ r12 = *x++ + SUBS r2, r2, #1 @ j-- + @ Stall + SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_0) + LDRHGT r14, [r4], #2 @ r14 = *x++ + SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x,y_1) + SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_2) + SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x,y_3) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_1) + SUBS r2, r2, #1 @ j-- + SMLABB r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x,y_2) + LDRH r10, [r5], #2 @ r10 = y_4 = *y++ + SMLABT r8, r14, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_3) + LDRHGT r12, [r4], #2 @ r12 = *x++ + SMLABB r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x,y_4) + BLE xcorr_kernel_edsp_done + SMLABB r6, r12, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_2) + CMP r2, #1 @ j-- + SMLABT r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_3) + LDRH r2, [r5], #2 @ r2 = y_5 = *y++ + SMLABB r8, r12, r10, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_4) + LDRHGT r14, [r4] @ r14 = *x + SMLABB r9, r12, r2, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_5) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_3) + LDRH r11, [r5] @ r11 = y_6 = *y + SMLABB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_4) + SMLABB r8, r14, r2, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_5) + SMLABB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_6) +xcorr_kernel_edsp_done: + LDMFD sp!, {r2,r4,r5,pc} + .size xcorr_kernel_edsp, .-xcorr_kernel_edsp @ ENDP + + .type celt_pitch_xcorr_edsp, %function; celt_pitch_xcorr_edsp: @ PROC + @ input: + @ r0 = opus_val16 *_x (must be 32-bit aligned) + @ r1 = opus_val16 *_y (only needs to be 16-bit aligned) + @ r2 = opus_val32 *xcorr + @ r3 = int len + @ output: + @ r0 = maxcorr + @ internal usage + @ r4 = opus_val16 *x + @ r5 = opus_val16 *y + @ r6 = opus_val32 sum0 + @ r7 = opus_val32 sum1 + @ r8 = opus_val32 sum2 + @ r9 = opus_val32 sum3 + @ r1 = int max_pitch + @ r12 = int j + @ ignored: + @ int arch + STMFD sp!, {r4-r11, lr} + MOV r5, r1 + LDR r1, [sp, #36] + MOV r4, r0 + TST r5, #3 + @ maxcorr = 1 + MOV r0, #1 + BEQ celt_pitch_xcorr_edsp_process1u_done +@ Compute one sum at the start to make y 32-bit aligned. + SUBS r12, r3, #4 + @ r14 = sum = 0 + MOV r14, #0 + LDRH r8, [r5], #2 + BLE celt_pitch_xcorr_edsp_process1u_loop4_done + LDR r6, [r4], #4 + MOV r8, r8, LSL #16 +celt_pitch_xcorr_edsp_process1u_loop4: + LDR r9, [r5], #4 + SMLABT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + LDR r7, [r4], #4 + SMLATB r14, r6, r9, r14 @ sum = MAC16_16(sum, x_1, y_1) + LDR r8, [r5], #4 + SMLABT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2) + SUBS r12, r12, #4 @ j-=4 + SMLATB r14, r7, r8, r14 @ sum = MAC16_16(sum, x_3, y_3) + LDRGT r6, [r4], #4 + BGT celt_pitch_xcorr_edsp_process1u_loop4 + MOV r8, r8, LSR #16 +celt_pitch_xcorr_edsp_process1u_loop4_done: + ADDS r12, r12, #4 +celt_pitch_xcorr_edsp_process1u_loop1: + LDRHGE r6, [r4], #2 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y) + SUBSGE r12, r12, #1 + LDRHGT r8, [r5], #2 + BGT celt_pitch_xcorr_edsp_process1u_loop1 + @ Restore _x + SUB r4, r4, r3, LSL #1 + @ Restore and advance _y + SUB r5, r5, r3, LSL #1 + @ maxcorr = max(maxcorr, sum) + CMP r0, r14 + ADD r5, r5, #2 + MOVLT r0, r14 + SUBS r1, r1, #1 + @ xcorr[i] = sum + STR r14, [r2], #4 + BLE celt_pitch_xcorr_edsp_done +celt_pitch_xcorr_edsp_process1u_done: + @ if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2 + SUBS r1, r1, #4 + BLT celt_pitch_xcorr_edsp_process2 +celt_pitch_xcorr_edsp_process4: + @ xcorr_kernel_edsp parameters: + @ r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0} + MOV r6, #0 + MOV r7, #0 + MOV r8, #0 + MOV r9, #0 + BL xcorr_kernel_edsp_start @ xcorr_kernel_edsp(_x, _y+i, xcorr+i, len) + @ maxcorr = max(maxcorr, sum0, sum1, sum2, sum3) + CMP r0, r6 + @ _y+=4 + ADD r5, r5, #8 + MOVLT r0, r6 + CMP r0, r7 + MOVLT r0, r7 + CMP r0, r8 + MOVLT r0, r8 + CMP r0, r9 + MOVLT r0, r9 + STMIA r2!, {r6-r9} + SUBS r1, r1, #4 + BGE celt_pitch_xcorr_edsp_process4 +celt_pitch_xcorr_edsp_process2: + ADDS r1, r1, #2 + BLT celt_pitch_xcorr_edsp_process1a + SUBS r12, r3, #4 + @ {r10, r11} = {sum0, sum1} = {0, 0} + MOV r10, #0 + MOV r11, #0 + LDR r8, [r5], #4 + BLE celt_pitch_xcorr_edsp_process2_loop_done + LDR r6, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process2_loop4: + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDR r7, [r4], #4 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + SUBS r12, r12, #4 @ j-=4 + SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1) + LDR r8, [r5], #4 + SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2) + LDRGT r6, [r4], #4 + SMLABB r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_2, y_2) + SMLABT r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_2, y_3) + SMLATT r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_3, y_3) + LDRGT r9, [r5], #4 + SMLATB r11, r7, r8, r11 @ sum1 = MAC16_16(sum1, x_3, y_4) + BGT celt_pitch_xcorr_edsp_process2_loop4 +celt_pitch_xcorr_edsp_process2_loop_done: + ADDS r12, r12, #2 + BLE celt_pitch_xcorr_edsp_process2_1 + LDR r6, [r4], #4 + @ Stall + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDR r9, [r5], #4 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + SUB r12, r12, #2 + SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1) + MOV r8, r9 + SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2) +celt_pitch_xcorr_edsp_process2_1: + LDRH r6, [r4], #2 + ADDS r12, r12, #1 + @ Stall + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDRHGT r7, [r4], #2 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + BLE celt_pitch_xcorr_edsp_process2_done + LDRH r9, [r5], #2 + SMLABT r10, r7, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_1) + SMLABB r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_0, y_2) +celt_pitch_xcorr_edsp_process2_done: + @ Restore _x + SUB r4, r4, r3, LSL #1 + @ Restore and advance _y + SUB r5, r5, r3, LSL #1 + @ maxcorr = max(maxcorr, sum0) + CMP r0, r10 + ADD r5, r5, #2 + MOVLT r0, r10 + SUB r1, r1, #2 + @ maxcorr = max(maxcorr, sum1) + CMP r0, r11 + @ xcorr[i] = sum + STR r10, [r2], #4 + MOVLT r0, r11 + STR r11, [r2], #4 +celt_pitch_xcorr_edsp_process1a: + ADDS r1, r1, #1 + BLT celt_pitch_xcorr_edsp_done + SUBS r12, r3, #4 + @ r14 = sum = 0 + MOV r14, #0 + BLT celt_pitch_xcorr_edsp_process1a_loop_done + LDR r6, [r4], #4 + LDR r8, [r5], #4 + LDR r7, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process1a_loop4: + SMLABB r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + SUBS r12, r12, #4 @ j-=4 + SMLATT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1) + LDRGE r6, [r4], #4 + SMLABB r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2) + LDRGE r8, [r5], #4 + SMLATT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_3, y_3) + LDRGE r7, [r4], #4 + LDRGE r9, [r5], #4 + BGE celt_pitch_xcorr_edsp_process1a_loop4 +celt_pitch_xcorr_edsp_process1a_loop_done: + ADDS r12, r12, #2 + LDRGE r6, [r4], #4 + LDRGE r8, [r5], #4 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + SUBGE r12, r12, #2 + SMLATTGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1) + ADDS r12, r12, #1 + LDRHGE r6, [r4], #2 + LDRHGE r8, [r5], #2 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y) + @ maxcorr = max(maxcorr, sum) + CMP r0, r14 + @ xcorr[i] = sum + STR r14, [r2], #4 + MOVLT r0, r14 +celt_pitch_xcorr_edsp_done: + LDMFD sp!, {r4-r11, pc} + .size celt_pitch_xcorr_edsp, .-celt_pitch_xcorr_edsp @ ENDP + + .endif + +@ END: + .section .note.GNU-stack,"",%progbits diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm.s b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm.s new file mode 100644 index 0000000000..6e873afc37 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/celt_pitch_xcorr_arm.s @@ -0,0 +1,551 @@ +; Copyright (c) 2007-2008 CSIRO +; Copyright (c) 2007-2009 Xiph.Org Foundation +; Copyright (c) 2013 Parrot +; Written by Aurélien Zanelli +; +; Redistribution and use in source and binary forms, with or without +; modification, are permitted provided that the following conditions +; are met: +; +; - Redistributions of source code must retain the above copyright +; notice, this list of conditions and the following disclaimer. +; +; - Redistributions in binary form must reproduce the above copyright +; notice, this list of conditions and the following disclaimer in the +; documentation and/or other materials provided with the distribution. +; +; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +; ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +; OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + AREA |.text|, CODE, READONLY + + GET celt/arm/armopts.s + +IF OPUS_ARM_MAY_HAVE_EDSP + EXPORT celt_pitch_xcorr_edsp +ENDIF + +IF OPUS_ARM_MAY_HAVE_NEON + EXPORT celt_pitch_xcorr_neon +ENDIF + +IF OPUS_ARM_MAY_HAVE_NEON + +; Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3 +xcorr_kernel_neon PROC +xcorr_kernel_neon_start + ; input: + ; r3 = int len + ; r4 = opus_val16 *x + ; r5 = opus_val16 *y + ; q0 = opus_val32 sum[4] + ; output: + ; q0 = opus_val32 sum[4] + ; preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15 + ; internal usage: + ; r12 = int j + ; d3 = y_3|y_2|y_1|y_0 + ; q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4 + ; q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0 + ; q8 = scratch + ; + ; Load y[0...3] + ; This requires len>0 to always be valid (which we assert in the C code). + VLD1.16 {d5}, [r5]! + SUBS r12, r3, #8 + BLE xcorr_kernel_neon_process4 +; Process 8 samples at a time. +; This loop loads one y value more than we actually need. Therefore we have to +; stop as soon as there are 8 or fewer samples left (instead of 7), to avoid +; reading past the end of the array. +xcorr_kernel_neon_process8 + ; This loop has 19 total instructions (10 cycles to issue, minimum), with + ; - 2 cycles of ARM insrtuctions, + ; - 10 cycles of load/store/byte permute instructions, and + ; - 9 cycles of data processing instructions. + ; On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the + ; latter two categories, meaning the whole loop should run in 10 cycles per + ; iteration, barring cache misses. + ; + ; Load x[0...7] + VLD1.16 {d6, d7}, [r4]! + ; Unlike VMOV, VAND is a data processsing instruction (and doesn't get + ; assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1. + VAND d3, d5, d5 + SUBS r12, r12, #8 + ; Load y[4...11] + VLD1.16 {d4, d5}, [r5]! + VMLAL.S16 q0, d3, d6[0] + VEXT.16 d16, d3, d4, #1 + VMLAL.S16 q0, d4, d7[0] + VEXT.16 d17, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d3, d4, #2 + VMLAL.S16 q0, d17, d7[1] + VEXT.16 d17, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d3, d4, #3 + VMLAL.S16 q0, d17, d7[2] + VEXT.16 d17, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] + VMLAL.S16 q0, d17, d7[3] + BGT xcorr_kernel_neon_process8 +; Process 4 samples here if we have > 4 left (still reading one extra y value). +xcorr_kernel_neon_process4 + ADDS r12, r12, #4 + BLE xcorr_kernel_neon_process2 + ; Load x[0...3] + VLD1.16 d6, [r4]! + ; Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #4 + ; Load y[4...7] + VLD1.16 d5, [r5]! + VMLAL.S16 q0, d4, d6[0] + VEXT.16 d16, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] +; Process 2 samples here if we have > 2 left (still reading one extra y value). +xcorr_kernel_neon_process2 + ADDS r12, r12, #2 + BLE xcorr_kernel_neon_process1 + ; Load x[0...1] + VLD2.16 {d6[],d7[]}, [r4]! + ; Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #2 + ; Load y[4...5] + VLD1.32 {d5[]}, [r5]! + VMLAL.S16 q0, d4, d6 + VEXT.16 d16, d4, d5, #1 + ; Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI + ; instead of VEXT, since it's a data-processing instruction. + VSRI.64 d5, d4, #32 + VMLAL.S16 q0, d16, d7 +; Process 1 sample using the extra y value we loaded above. +xcorr_kernel_neon_process1 + ; Load next *x + VLD1.16 {d6[]}, [r4]! + ADDS r12, r12, #1 + ; y[0...3] are left in d5 from prior iteration(s) (if any) + VMLAL.S16 q0, d5, d6 + MOVLE pc, lr +; Now process 1 last sample, not reading ahead. + ; Load last *y + VLD1.16 {d4[]}, [r5]! + VSRI.64 d4, d5, #16 + ; Load last *x + VLD1.16 {d6[]}, [r4]! + VMLAL.S16 q0, d4, d6 + MOV pc, lr + ENDP + +; opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y, +; opus_val32 *xcorr, int len, int max_pitch, int arch) +celt_pitch_xcorr_neon PROC + ; input: + ; r0 = opus_val16 *_x + ; r1 = opus_val16 *_y + ; r2 = opus_val32 *xcorr + ; r3 = int len + ; output: + ; r0 = int maxcorr + ; internal usage: + ; r4 = opus_val16 *x (for xcorr_kernel_neon()) + ; r5 = opus_val16 *y (for xcorr_kernel_neon()) + ; r6 = int max_pitch + ; r12 = int j + ; q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon()) + ; ignored: + ; int arch + STMFD sp!, {r4-r6, lr} + LDR r6, [sp, #16] + VMOV.S32 q15, #1 + ; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + SUBS r6, r6, #4 + BLT celt_pitch_xcorr_neon_process4_done +celt_pitch_xcorr_neon_process4 + ; xcorr_kernel_neon parameters: + ; r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0} + MOV r4, r0 + MOV r5, r1 + VEOR q0, q0, q0 + ; xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3. + ; So we don't save/restore any other registers. + BL xcorr_kernel_neon_start + SUBS r6, r6, #4 + VST1.32 {q0}, [r2]! + ; _y += 4 + ADD r1, r1, #8 + VMAX.S32 q15, q15, q0 + ; if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + BGE celt_pitch_xcorr_neon_process4 +; We have less than 4 sums left to compute. +celt_pitch_xcorr_neon_process4_done + ADDS r6, r6, #4 + ; Reduce maxcorr to a single value + VMAX.S32 d30, d30, d31 + VPMAX.S32 d30, d30, d30 + ; if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done + BLE celt_pitch_xcorr_neon_done +; Now compute each remaining sum one at a time. +celt_pitch_xcorr_neon_process_remaining + MOV r4, r0 + MOV r5, r1 + VMOV.I32 q0, #0 + SUBS r12, r3, #8 + BLT celt_pitch_xcorr_neon_process_remaining4 +; Sum terms 8 at a time. +celt_pitch_xcorr_neon_process_remaining_loop8 + ; Load x[0...7] + VLD1.16 {q1}, [r4]! + ; Load y[0...7] + VLD1.16 {q2}, [r5]! + SUBS r12, r12, #8 + VMLAL.S16 q0, d4, d2 + VMLAL.S16 q0, d5, d3 + BGE celt_pitch_xcorr_neon_process_remaining_loop8 +; Sum terms 4 at a time. +celt_pitch_xcorr_neon_process_remaining4 + ADDS r12, r12, #4 + BLT celt_pitch_xcorr_neon_process_remaining4_done + ; Load x[0...3] + VLD1.16 {d2}, [r4]! + ; Load y[0...3] + VLD1.16 {d3}, [r5]! + SUB r12, r12, #4 + VMLAL.S16 q0, d3, d2 +celt_pitch_xcorr_neon_process_remaining4_done + ; Reduce the sum to a single value. + VADD.S32 d0, d0, d1 + VPADDL.S32 d0, d0 + ADDS r12, r12, #4 + BLE celt_pitch_xcorr_neon_process_remaining_loop_done +; Sum terms 1 at a time. +celt_pitch_xcorr_neon_process_remaining_loop1 + VLD1.16 {d2[]}, [r4]! + VLD1.16 {d3[]}, [r5]! + SUBS r12, r12, #1 + VMLAL.S16 q0, d2, d3 + BGT celt_pitch_xcorr_neon_process_remaining_loop1 +celt_pitch_xcorr_neon_process_remaining_loop_done + VST1.32 {d0[0]}, [r2]! + VMAX.S32 d30, d30, d0 + SUBS r6, r6, #1 + ; _y++ + ADD r1, r1, #2 + ; if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining + BGT celt_pitch_xcorr_neon_process_remaining +celt_pitch_xcorr_neon_done + VMOV.32 r0, d30[0] + LDMFD sp!, {r4-r6, pc} + ENDP + +ENDIF + +IF OPUS_ARM_MAY_HAVE_EDSP + +; This will get used on ARMv7 devices without NEON, so it has been optimized +; to take advantage of dual-issuing where possible. +xcorr_kernel_edsp PROC +xcorr_kernel_edsp_start + ; input: + ; r3 = int len + ; r4 = opus_val16 *_x (must be 32-bit aligned) + ; r5 = opus_val16 *_y (must be 32-bit aligned) + ; r6...r9 = opus_val32 sum[4] + ; output: + ; r6...r9 = opus_val32 sum[4] + ; preserved: r0-r5 + ; internal usage + ; r2 = int j + ; r12,r14 = opus_val16 x[4] + ; r10,r11 = opus_val16 y[4] + STMFD sp!, {r2,r4,r5,lr} + LDR r10, [r5], #4 ; Load y[0...1] + SUBS r2, r3, #4 ; j = len-4 + LDR r11, [r5], #4 ; Load y[2...3] + BLE xcorr_kernel_edsp_process4_done + LDR r12, [r4], #4 ; Load x[0...1] + ; Stall +xcorr_kernel_edsp_process4 + ; The multiplies must issue from pipeline 0, and can't dual-issue with each + ; other. Every other instruction here dual-issues with a multiply, and is + ; thus "free". There should be no stalls in the body of the loop. + SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_0,y_0) + LDR r14, [r4], #4 ; Load x[2...3] + SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x_0,y_1) + SUBS r2, r2, #4 ; j-=4 + SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_0,y_2) + SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x_0,y_3) + SMLATT r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x_1,y_1) + LDR r10, [r5], #4 ; Load y[4...5] + SMLATB r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],x_1,y_2) + SMLATT r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x_1,y_3) + SMLATB r9, r12, r10, r9 ; sum[3] = MAC16_16(sum[3],x_1,y_4) + LDRGT r12, [r4], #4 ; Load x[0...1] + SMLABB r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_2,y_2) + SMLABT r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x_2,y_3) + SMLABB r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_2,y_4) + SMLABT r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x_2,y_5) + SMLATT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],x_3,y_3) + LDR r11, [r5], #4 ; Load y[6...7] + SMLATB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],x_3,y_4) + SMLATT r8, r14, r10, r8 ; sum[2] = MAC16_16(sum[2],x_3,y_5) + SMLATB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],x_3,y_6) + BGT xcorr_kernel_edsp_process4 +xcorr_kernel_edsp_process4_done + ADDS r2, r2, #4 + BLE xcorr_kernel_edsp_done + LDRH r12, [r4], #2 ; r12 = *x++ + SUBS r2, r2, #1 ; j-- + ; Stall + SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_0) + LDRHGT r14, [r4], #2 ; r14 = *x++ + SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x,y_1) + SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_2) + SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x,y_3) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_1) + SUBS r2, r2, #1 ; j-- + SMLABB r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x,y_2) + LDRH r10, [r5], #2 ; r10 = y_4 = *y++ + SMLABT r8, r14, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_3) + LDRHGT r12, [r4], #2 ; r12 = *x++ + SMLABB r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x,y_4) + BLE xcorr_kernel_edsp_done + SMLABB r6, r12, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_2) + CMP r2, #1 ; j-- + SMLABT r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_3) + LDRH r2, [r5], #2 ; r2 = y_5 = *y++ + SMLABB r8, r12, r10, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_4) + LDRHGT r14, [r4] ; r14 = *x + SMLABB r9, r12, r2, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_5) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_3) + LDRH r11, [r5] ; r11 = y_6 = *y + SMLABB r7, r14, r10, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_4) + SMLABB r8, r14, r2, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_5) + SMLABB r9, r14, r11, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_6) +xcorr_kernel_edsp_done + LDMFD sp!, {r2,r4,r5,pc} + ENDP + +celt_pitch_xcorr_edsp PROC + ; input: + ; r0 = opus_val16 *_x (must be 32-bit aligned) + ; r1 = opus_val16 *_y (only needs to be 16-bit aligned) + ; r2 = opus_val32 *xcorr + ; r3 = int len + ; output: + ; r0 = maxcorr + ; internal usage + ; r4 = opus_val16 *x + ; r5 = opus_val16 *y + ; r6 = opus_val32 sum0 + ; r7 = opus_val32 sum1 + ; r8 = opus_val32 sum2 + ; r9 = opus_val32 sum3 + ; r1 = int max_pitch + ; r12 = int j + ; ignored: + ; int arch + STMFD sp!, {r4-r11, lr} + MOV r5, r1 + LDR r1, [sp, #36] + MOV r4, r0 + TST r5, #3 + ; maxcorr = 1 + MOV r0, #1 + BEQ celt_pitch_xcorr_edsp_process1u_done +; Compute one sum at the start to make y 32-bit aligned. + SUBS r12, r3, #4 + ; r14 = sum = 0 + MOV r14, #0 + LDRH r8, [r5], #2 + BLE celt_pitch_xcorr_edsp_process1u_loop4_done + LDR r6, [r4], #4 + MOV r8, r8, LSL #16 +celt_pitch_xcorr_edsp_process1u_loop4 + LDR r9, [r5], #4 + SMLABT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0) + LDR r7, [r4], #4 + SMLATB r14, r6, r9, r14 ; sum = MAC16_16(sum, x_1, y_1) + LDR r8, [r5], #4 + SMLABT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2) + SUBS r12, r12, #4 ; j-=4 + SMLATB r14, r7, r8, r14 ; sum = MAC16_16(sum, x_3, y_3) + LDRGT r6, [r4], #4 + BGT celt_pitch_xcorr_edsp_process1u_loop4 + MOV r8, r8, LSR #16 +celt_pitch_xcorr_edsp_process1u_loop4_done + ADDS r12, r12, #4 +celt_pitch_xcorr_edsp_process1u_loop1 + LDRHGE r6, [r4], #2 + ; Stall + SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y) + SUBSGE r12, r12, #1 + LDRHGT r8, [r5], #2 + BGT celt_pitch_xcorr_edsp_process1u_loop1 + ; Restore _x + SUB r4, r4, r3, LSL #1 + ; Restore and advance _y + SUB r5, r5, r3, LSL #1 + ; maxcorr = max(maxcorr, sum) + CMP r0, r14 + ADD r5, r5, #2 + MOVLT r0, r14 + SUBS r1, r1, #1 + ; xcorr[i] = sum + STR r14, [r2], #4 + BLE celt_pitch_xcorr_edsp_done +celt_pitch_xcorr_edsp_process1u_done + ; if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2 + SUBS r1, r1, #4 + BLT celt_pitch_xcorr_edsp_process2 +celt_pitch_xcorr_edsp_process4 + ; xcorr_kernel_edsp parameters: + ; r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0} + MOV r6, #0 + MOV r7, #0 + MOV r8, #0 + MOV r9, #0 + BL xcorr_kernel_edsp_start ; xcorr_kernel_edsp(_x, _y+i, xcorr+i, len) + ; maxcorr = max(maxcorr, sum0, sum1, sum2, sum3) + CMP r0, r6 + ; _y+=4 + ADD r5, r5, #8 + MOVLT r0, r6 + CMP r0, r7 + MOVLT r0, r7 + CMP r0, r8 + MOVLT r0, r8 + CMP r0, r9 + MOVLT r0, r9 + STMIA r2!, {r6-r9} + SUBS r1, r1, #4 + BGE celt_pitch_xcorr_edsp_process4 +celt_pitch_xcorr_edsp_process2 + ADDS r1, r1, #2 + BLT celt_pitch_xcorr_edsp_process1a + SUBS r12, r3, #4 + ; {r10, r11} = {sum0, sum1} = {0, 0} + MOV r10, #0 + MOV r11, #0 + LDR r8, [r5], #4 + BLE celt_pitch_xcorr_edsp_process2_loop_done + LDR r6, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process2_loop4 + SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0) + LDR r7, [r4], #4 + SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1) + SUBS r12, r12, #4 ; j-=4 + SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1) + LDR r8, [r5], #4 + SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2) + LDRGT r6, [r4], #4 + SMLABB r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_2, y_2) + SMLABT r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_2, y_3) + SMLATT r10, r7, r9, r10 ; sum0 = MAC16_16(sum0, x_3, y_3) + LDRGT r9, [r5], #4 + SMLATB r11, r7, r8, r11 ; sum1 = MAC16_16(sum1, x_3, y_4) + BGT celt_pitch_xcorr_edsp_process2_loop4 +celt_pitch_xcorr_edsp_process2_loop_done + ADDS r12, r12, #2 + BLE celt_pitch_xcorr_edsp_process2_1 + LDR r6, [r4], #4 + ; Stall + SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0) + LDR r9, [r5], #4 + SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1) + SUB r12, r12, #2 + SMLATT r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_1, y_1) + MOV r8, r9 + SMLATB r11, r6, r9, r11 ; sum1 = MAC16_16(sum1, x_1, y_2) +celt_pitch_xcorr_edsp_process2_1 + LDRH r6, [r4], #2 + ADDS r12, r12, #1 + ; Stall + SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0) + LDRHGT r7, [r4], #2 + SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1) + BLE celt_pitch_xcorr_edsp_process2_done + LDRH r9, [r5], #2 + SMLABT r10, r7, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_1) + SMLABB r11, r7, r9, r11 ; sum1 = MAC16_16(sum1, x_0, y_2) +celt_pitch_xcorr_edsp_process2_done + ; Restore _x + SUB r4, r4, r3, LSL #1 + ; Restore and advance _y + SUB r5, r5, r3, LSL #1 + ; maxcorr = max(maxcorr, sum0) + CMP r0, r10 + ADD r5, r5, #2 + MOVLT r0, r10 + SUB r1, r1, #2 + ; maxcorr = max(maxcorr, sum1) + CMP r0, r11 + ; xcorr[i] = sum + STR r10, [r2], #4 + MOVLT r0, r11 + STR r11, [r2], #4 +celt_pitch_xcorr_edsp_process1a + ADDS r1, r1, #1 + BLT celt_pitch_xcorr_edsp_done + SUBS r12, r3, #4 + ; r14 = sum = 0 + MOV r14, #0 + BLT celt_pitch_xcorr_edsp_process1a_loop_done + LDR r6, [r4], #4 + LDR r8, [r5], #4 + LDR r7, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process1a_loop4 + SMLABB r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0) + SUBS r12, r12, #4 ; j-=4 + SMLATT r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1) + LDRGE r6, [r4], #4 + SMLABB r14, r7, r9, r14 ; sum = MAC16_16(sum, x_2, y_2) + LDRGE r8, [r5], #4 + SMLATT r14, r7, r9, r14 ; sum = MAC16_16(sum, x_3, y_3) + LDRGE r7, [r4], #4 + LDRGE r9, [r5], #4 + BGE celt_pitch_xcorr_edsp_process1a_loop4 +celt_pitch_xcorr_edsp_process1a_loop_done + ADDS r12, r12, #2 + LDRGE r6, [r4], #4 + LDRGE r8, [r5], #4 + ; Stall + SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_0, y_0) + SUBGE r12, r12, #2 + SMLATTGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1) + ADDS r12, r12, #1 + LDRHGE r6, [r4], #2 + LDRHGE r8, [r5], #2 + ; Stall + SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y) + ; maxcorr = max(maxcorr, sum) + CMP r0, r14 + ; xcorr[i] = sum + STR r14, [r2], #4 + MOVLT r0, r14 +celt_pitch_xcorr_edsp_done + LDMFD sp!, {r4-r11, pc} + ENDP + +ENDIF + +END diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fft_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fft_arm.h new file mode 100644 index 0000000000..0b78175f3a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fft_arm.h @@ -0,0 +1,71 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file fft_arm.h + @brief ARM Neon Intrinsic optimizations for fft using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#if !defined(FFT_ARM_H) +#define FFT_ARM_H + +#include "kiss_fft.h" + +#if defined(HAVE_ARM_NE10) + +int opus_fft_alloc_arm_neon(kiss_fft_state *st); +void opus_fft_free_arm_neon(kiss_fft_state *st); + +void opus_fft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout); + +void opus_ifft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout); + +#if !defined(OPUS_HAVE_RTCD) +#define OVERRIDE_OPUS_FFT (1) + +#define opus_fft_alloc_arch(_st, arch) \ + ((void)(arch), opus_fft_alloc_arm_neon(_st)) + +#define opus_fft_free_arch(_st, arch) \ + ((void)(arch), opus_fft_free_arm_neon(_st)) + +#define opus_fft(_st, _fin, _fout, arch) \ + ((void)(arch), opus_fft_neon(_st, _fin, _fout)) + +#define opus_ifft(_st, _fin, _fout, arch) \ + ((void)(arch), opus_ifft_neon(_st, _fin, _fout)) + +#endif /* OPUS_HAVE_RTCD */ + +#endif /* HAVE_ARM_NE10 */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_arm64.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_arm64.h new file mode 100644 index 0000000000..c6fbd3db2c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_arm64.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2015 Vidyo */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_ARM64_H +#define FIXED_ARM64_H + +#include + +#undef SIG2WORD16 +#define SIG2WORD16(x) (vqmovns_s32(PSHR32((x), SIG_SHIFT))) + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv4.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv4.h new file mode 100644 index 0000000000..d84888a772 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv4.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2013 Xiph.Org Foundation and contributors */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_ARMv4_H +#define FIXED_ARMv4_H + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#undef MULT16_32_Q16 +static OPUS_INLINE opus_val32 MULT16_32_Q16_armv4(opus_val16 a, opus_val32 b) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#MULT16_32_Q16\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(b),"r"(SHL32(a,16)) + ); + return rd_hi; +} +#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv4(a, b)) + + +/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ +#undef MULT16_32_Q15 +static OPUS_INLINE opus_val32 MULT16_32_Q15_armv4(opus_val16 a, opus_val32 b) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#MULT16_32_Q15\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(b), "r"(SHL32(a,16)) + ); + /*We intentionally don't OR in the high bit of rd_lo for speed.*/ + return SHL32(rd_hi,1); +} +#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv4(a, b)) + + +/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add. + b must fit in 31 bits. + Result fits in 32 bits. */ +#undef MAC16_32_Q15 +#define MAC16_32_Q15(c, a, b) ADD32(c, MULT16_32_Q15(a, b)) + +/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add. + Result fits in 32 bits. */ +#undef MAC16_32_Q16 +#define MAC16_32_Q16(c, a, b) ADD32(c, MULT16_32_Q16(a, b)) + +/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ +#undef MULT32_32_Q31 +#define MULT32_32_Q31(a,b) (opus_val32)((((opus_int64)(a)) * ((opus_int64)(b)))>>31) + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv5e.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv5e.h new file mode 100644 index 0000000000..6bf73cbace --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/fixed_armv5e.h @@ -0,0 +1,151 @@ +/* Copyright (C) 2007-2009 Xiph.Org Foundation + Copyright (C) 2003-2008 Jean-Marc Valin + Copyright (C) 2007-2008 CSIRO + Copyright (C) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_ARMv5E_H +#define FIXED_ARMv5E_H + +#include "fixed_armv4.h" + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#undef MULT16_32_Q16 +static OPUS_INLINE opus_val32 MULT16_32_Q16_armv5e(opus_val16 a, opus_val32 b) +{ + int res; + __asm__( + "#MULT16_32_Q16\n\t" + "smulwb %0, %1, %2\n\t" + : "=r"(res) + : "r"(b),"r"(a) + ); + return res; +} +#define MULT16_32_Q16(a, b) (MULT16_32_Q16_armv5e(a, b)) + + +/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ +#undef MULT16_32_Q15 +static OPUS_INLINE opus_val32 MULT16_32_Q15_armv5e(opus_val16 a, opus_val32 b) +{ + int res; + __asm__( + "#MULT16_32_Q15\n\t" + "smulwb %0, %1, %2\n\t" + : "=r"(res) + : "r"(b), "r"(a) + ); + return SHL32(res,1); +} +#define MULT16_32_Q15(a, b) (MULT16_32_Q15_armv5e(a, b)) + + +/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add. + b must fit in 31 bits. + Result fits in 32 bits. */ +#undef MAC16_32_Q15 +static OPUS_INLINE opus_val32 MAC16_32_Q15_armv5e(opus_val32 c, opus_val16 a, + opus_val32 b) +{ + int res; + __asm__( + "#MAC16_32_Q15\n\t" + "smlawb %0, %1, %2, %3;\n" + : "=r"(res) + : "r"(SHL32(b,1)), "r"(a), "r"(c) + ); + return res; +} +#define MAC16_32_Q15(c, a, b) (MAC16_32_Q15_armv5e(c, a, b)) + +/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add. + Result fits in 32 bits. */ +#undef MAC16_32_Q16 +static OPUS_INLINE opus_val32 MAC16_32_Q16_armv5e(opus_val32 c, opus_val16 a, + opus_val32 b) +{ + int res; + __asm__( + "#MAC16_32_Q16\n\t" + "smlawb %0, %1, %2, %3;\n" + : "=r"(res) + : "r"(b), "r"(a), "r"(c) + ); + return res; +} +#define MAC16_32_Q16(c, a, b) (MAC16_32_Q16_armv5e(c, a, b)) + +/** 16x16 multiply-add where the result fits in 32 bits */ +#undef MAC16_16 +static OPUS_INLINE opus_val32 MAC16_16_armv5e(opus_val32 c, opus_val16 a, + opus_val16 b) +{ + int res; + __asm__( + "#MAC16_16\n\t" + "smlabb %0, %1, %2, %3;\n" + : "=r"(res) + : "r"(a), "r"(b), "r"(c) + ); + return res; +} +#define MAC16_16(c, a, b) (MAC16_16_armv5e(c, a, b)) + +/** 16x16 multiplication where the result fits in 32 bits */ +#undef MULT16_16 +static OPUS_INLINE opus_val32 MULT16_16_armv5e(opus_val16 a, opus_val16 b) +{ + int res; + __asm__( + "#MULT16_16\n\t" + "smulbb %0, %1, %2;\n" + : "=r"(res) + : "r"(a), "r"(b) + ); + return res; +} +#define MULT16_16(a, b) (MULT16_16_armv5e(a, b)) + +#ifdef OPUS_ARM_INLINE_MEDIA + +#undef SIG2WORD16 +static OPUS_INLINE opus_val16 SIG2WORD16_armv6(opus_val32 x) +{ + celt_sig res; + __asm__( + "#SIG2WORD16\n\t" + "ssat %0, #16, %1, ASR #12\n\t" + : "=r"(res) + : "r"(x+2048) + ); + return EXTRACT16(res); +} +#define SIG2WORD16(x) (SIG2WORD16_armv6(x)) + +#endif /* OPUS_ARM_INLINE_MEDIA */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv4.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv4.h new file mode 100644 index 0000000000..e4faad6f2b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv4.h @@ -0,0 +1,121 @@ +/*Copyright (c) 2013, Xiph.Org Foundation and contributors. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.*/ + +#ifndef KISS_FFT_ARMv4_H +#define KISS_FFT_ARMv4_H + +#if !defined(KISS_FFT_GUTS_H) +#error "This file should only be included from _kiss_fft_guts.h" +#endif + +#ifdef FIXED_POINT + +#undef C_MUL +#define C_MUL(m,a,b) \ + do{ \ + int br__; \ + int bi__; \ + int tt__; \ + __asm__ __volatile__( \ + "#C_MUL\n\t" \ + "ldrsh %[br], [%[bp], #0]\n\t" \ + "ldm %[ap], {r0,r1}\n\t" \ + "ldrsh %[bi], [%[bp], #2]\n\t" \ + "smull %[tt], %[mi], r1, %[br]\n\t" \ + "smlal %[tt], %[mi], r0, %[bi]\n\t" \ + "rsb %[bi], %[bi], #0\n\t" \ + "smull %[br], %[mr], r0, %[br]\n\t" \ + "mov %[tt], %[tt], lsr #15\n\t" \ + "smlal %[br], %[mr], r1, %[bi]\n\t" \ + "orr %[mi], %[tt], %[mi], lsl #17\n\t" \ + "mov %[br], %[br], lsr #15\n\t" \ + "orr %[mr], %[br], %[mr], lsl #17\n\t" \ + : [mr]"=r"((m).r), [mi]"=r"((m).i), \ + [br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \ + : [ap]"r"(&(a)), [bp]"r"(&(b)) \ + : "r0", "r1" \ + ); \ + } \ + while(0) + +#undef C_MUL4 +#define C_MUL4(m,a,b) \ + do{ \ + int br__; \ + int bi__; \ + int tt__; \ + __asm__ __volatile__( \ + "#C_MUL4\n\t" \ + "ldrsh %[br], [%[bp], #0]\n\t" \ + "ldm %[ap], {r0,r1}\n\t" \ + "ldrsh %[bi], [%[bp], #2]\n\t" \ + "smull %[tt], %[mi], r1, %[br]\n\t" \ + "smlal %[tt], %[mi], r0, %[bi]\n\t" \ + "rsb %[bi], %[bi], #0\n\t" \ + "smull %[br], %[mr], r0, %[br]\n\t" \ + "mov %[tt], %[tt], lsr #17\n\t" \ + "smlal %[br], %[mr], r1, %[bi]\n\t" \ + "orr %[mi], %[tt], %[mi], lsl #15\n\t" \ + "mov %[br], %[br], lsr #17\n\t" \ + "orr %[mr], %[br], %[mr], lsl #15\n\t" \ + : [mr]"=r"((m).r), [mi]"=r"((m).i), \ + [br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \ + : [ap]"r"(&(a)), [bp]"r"(&(b)) \ + : "r0", "r1" \ + ); \ + } \ + while(0) + +#undef C_MULC +#define C_MULC(m,a,b) \ + do{ \ + int br__; \ + int bi__; \ + int tt__; \ + __asm__ __volatile__( \ + "#C_MULC\n\t" \ + "ldrsh %[br], [%[bp], #0]\n\t" \ + "ldm %[ap], {r0,r1}\n\t" \ + "ldrsh %[bi], [%[bp], #2]\n\t" \ + "smull %[tt], %[mr], r0, %[br]\n\t" \ + "smlal %[tt], %[mr], r1, %[bi]\n\t" \ + "rsb %[bi], %[bi], #0\n\t" \ + "smull %[br], %[mi], r1, %[br]\n\t" \ + "mov %[tt], %[tt], lsr #15\n\t" \ + "smlal %[br], %[mi], r0, %[bi]\n\t" \ + "orr %[mr], %[tt], %[mr], lsl #17\n\t" \ + "mov %[br], %[br], lsr #15\n\t" \ + "orr %[mi], %[br], %[mi], lsl #17\n\t" \ + : [mr]"=r"((m).r), [mi]"=r"((m).i), \ + [br]"=&r"(br__), [bi]"=r"(bi__), [tt]"=r"(tt__) \ + : [ap]"r"(&(a)), [bp]"r"(&(b)) \ + : "r0", "r1" \ + ); \ + } \ + while(0) + +#endif /* FIXED_POINT */ + +#endif /* KISS_FFT_ARMv4_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv5e.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv5e.h new file mode 100644 index 0000000000..9eca183d77 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/kiss_fft_armv5e.h @@ -0,0 +1,118 @@ +/*Copyright (c) 2013, Xiph.Org Foundation and contributors. + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.*/ + +#ifndef KISS_FFT_ARMv5E_H +#define KISS_FFT_ARMv5E_H + +#if !defined(KISS_FFT_GUTS_H) +#error "This file should only be included from _kiss_fft_guts.h" +#endif + +#ifdef FIXED_POINT + +#if defined(__thumb__)||defined(__thumb2__) +#define LDRD_CONS "Q" +#else +#define LDRD_CONS "Uq" +#endif + +#undef C_MUL +#define C_MUL(m,a,b) \ + do{ \ + int mr1__; \ + int mr2__; \ + int mi__; \ + long long aval__; \ + int bval__; \ + __asm__( \ + "#C_MUL\n\t" \ + "ldrd %[aval], %H[aval], %[ap]\n\t" \ + "ldr %[bval], %[bp]\n\t" \ + "smulwb %[mi], %H[aval], %[bval]\n\t" \ + "smulwb %[mr1], %[aval], %[bval]\n\t" \ + "smulwt %[mr2], %H[aval], %[bval]\n\t" \ + "smlawt %[mi], %[aval], %[bval], %[mi]\n\t" \ + : [mr1]"=r"(mr1__), [mr2]"=r"(mr2__), [mi]"=r"(mi__), \ + [aval]"=&r"(aval__), [bval]"=r"(bval__) \ + : [ap]LDRD_CONS(a), [bp]"m"(b) \ + ); \ + (m).r = SHL32(SUB32(mr1__, mr2__), 1); \ + (m).i = SHL32(mi__, 1); \ + } \ + while(0) + +#undef C_MUL4 +#define C_MUL4(m,a,b) \ + do{ \ + int mr1__; \ + int mr2__; \ + int mi__; \ + long long aval__; \ + int bval__; \ + __asm__( \ + "#C_MUL4\n\t" \ + "ldrd %[aval], %H[aval], %[ap]\n\t" \ + "ldr %[bval], %[bp]\n\t" \ + "smulwb %[mi], %H[aval], %[bval]\n\t" \ + "smulwb %[mr1], %[aval], %[bval]\n\t" \ + "smulwt %[mr2], %H[aval], %[bval]\n\t" \ + "smlawt %[mi], %[aval], %[bval], %[mi]\n\t" \ + : [mr1]"=r"(mr1__), [mr2]"=r"(mr2__), [mi]"=r"(mi__), \ + [aval]"=&r"(aval__), [bval]"=r"(bval__) \ + : [ap]LDRD_CONS(a), [bp]"m"(b) \ + ); \ + (m).r = SHR32(SUB32(mr1__, mr2__), 1); \ + (m).i = SHR32(mi__, 1); \ + } \ + while(0) + +#undef C_MULC +#define C_MULC(m,a,b) \ + do{ \ + int mr__; \ + int mi1__; \ + int mi2__; \ + long long aval__; \ + int bval__; \ + __asm__( \ + "#C_MULC\n\t" \ + "ldrd %[aval], %H[aval], %[ap]\n\t" \ + "ldr %[bval], %[bp]\n\t" \ + "smulwb %[mr], %[aval], %[bval]\n\t" \ + "smulwb %[mi1], %H[aval], %[bval]\n\t" \ + "smulwt %[mi2], %[aval], %[bval]\n\t" \ + "smlawt %[mr], %H[aval], %[bval], %[mr]\n\t" \ + : [mr]"=r"(mr__), [mi1]"=r"(mi1__), [mi2]"=r"(mi2__), \ + [aval]"=&r"(aval__), [bval]"=r"(bval__) \ + : [ap]LDRD_CONS(a), [bp]"m"(b) \ + ); \ + (m).r = SHL32(mr__, 1); \ + (m).i = SHL32(SUB32(mi1__, mi2__), 1); \ + } \ + while(0) + +#endif /* FIXED_POINT */ + +#endif /* KISS_FFT_GUTS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/mdct_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/mdct_arm.h new file mode 100644 index 0000000000..14200bac4b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/mdct_arm.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file arm_mdct.h + @brief ARM Neon Intrinsic optimizations for mdct using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(MDCT_ARM_H) +#define MDCT_ARM_H + +#include "mdct.h" + +#if defined(HAVE_ARM_NE10) +/** Compute a forward MDCT and scale by 4/N, trashes the input array */ +void clt_mdct_forward_neon(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +void clt_mdct_backward_neon(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +#if !defined(OPUS_HAVE_RTCD) +#define OVERRIDE_OPUS_MDCT (1) +#define clt_mdct_forward(_l, _in, _out, _window, _int, _shift, _stride, _arch) \ + clt_mdct_forward_neon(_l, _in, _out, _window, _int, _shift, _stride, _arch) +#define clt_mdct_backward(_l, _in, _out, _window, _int, _shift, _stride, _arch) \ + clt_mdct_backward_neon(_l, _in, _out, _window, _int, _shift, _stride, _arch) +#endif /* OPUS_HAVE_RTCD */ +#endif /* HAVE_ARM_NE10 */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_arm.h new file mode 100644 index 0000000000..bed8b04eac --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_arm.h @@ -0,0 +1,160 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(PITCH_ARM_H) +# define PITCH_ARM_H + +# include "armcpu.h" + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +opus_val32 celt_inner_prod_neon(const opus_val16 *x, const opus_val16 *y, int N); +void dual_inner_prod_neon(const opus_val16 *x, const opus_val16 *y01, + const opus_val16 *y02, int N, opus_val32 *xy1, opus_val32 *xy2); + +# if !defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_PRESUME_NEON) +# define OVERRIDE_CELT_INNER_PROD (1) +# define OVERRIDE_DUAL_INNER_PROD (1) +# define celt_inner_prod(x, y, N, arch) ((void)(arch), PRESUME_NEON(celt_inner_prod)(x, y, N)) +# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) ((void)(arch), PRESUME_NEON(dual_inner_prod)(x, y01, y02, N, xy1, xy2)) +# endif +# endif + +# if !defined(OVERRIDE_CELT_INNER_PROD) +# if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern opus_val32 (*const CELT_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, const opus_val16 *y, int N); +# define OVERRIDE_CELT_INNER_PROD (1) +# define celt_inner_prod(x, y, N, arch) ((*CELT_INNER_PROD_IMPL[(arch)&OPUS_ARCHMASK])(x, y, N)) +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_CELT_INNER_PROD (1) +# define celt_inner_prod(x, y, N, arch) ((void)(arch), celt_inner_prod_neon(x, y, N)) +# endif +# endif + +# if !defined(OVERRIDE_DUAL_INNER_PROD) +# if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, + const opus_val16 *y01, const opus_val16 *y02, int N, opus_val32 *xy1, opus_val32 *xy2); +# define OVERRIDE_DUAL_INNER_PROD (1) +# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) ((*DUAL_INNER_PROD_IMPL[(arch)&OPUS_ARCHMASK])(x, y01, y02, N, xy1, xy2)) +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_DUAL_INNER_PROD (1) +# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) ((void)(arch), dual_inner_prod_neon(x, y01, y02, N, xy1, xy2)) +# endif +# endif + +# if defined(FIXED_POINT) + +# if defined(OPUS_ARM_MAY_HAVE_NEON) +opus_val32 celt_pitch_xcorr_neon(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch); +# endif + +# if defined(OPUS_ARM_MAY_HAVE_MEDIA) +# define celt_pitch_xcorr_media MAY_HAVE_EDSP(celt_pitch_xcorr) +# endif + +# if defined(OPUS_ARM_MAY_HAVE_EDSP) +opus_val32 celt_pitch_xcorr_edsp(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch); +# endif + +# if defined(OPUS_HAVE_RTCD) && \ + ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \ + (defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \ + (defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP))) +extern opus_val32 +(*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int, int); +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ + ((*CELT_PITCH_XCORR_IMPL[(arch)&OPUS_ARCHMASK])(_x, _y, \ + xcorr, len, max_pitch, arch)) + +# elif defined(OPUS_ARM_PRESUME_EDSP) || \ + defined(OPUS_ARM_PRESUME_MEDIA) || \ + defined(OPUS_ARM_PRESUME_NEON) +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr (PRESUME_NEON(celt_pitch_xcorr)) + +# endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void xcorr_kernel_neon_fixed( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len); +# endif + +# if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) + +extern void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len); + +# define OVERRIDE_XCORR_KERNEL (1) +# define xcorr_kernel(x, y, sum, len, arch) \ + ((*XCORR_KERNEL_IMPL[(arch) & OPUS_ARCHMASK])(x, y, sum, len)) + +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_XCORR_KERNEL (1) +# define xcorr_kernel(x, y, sum, len, arch) \ + ((void)arch, xcorr_kernel_neon_fixed(x, y, sum, len)) + +# endif + +#else /* Start !FIXED_POINT */ +/* Float case */ +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch); +#endif + +# if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void +(*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int, int); + +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ + ((*CELT_PITCH_XCORR_IMPL[(arch)&OPUS_ARCHMASK])(_x, _y, \ + xcorr, len, max_pitch, arch)) + +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) + +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr celt_pitch_xcorr_float_neon + +# endif + +#endif /* end !FIXED_POINT */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_neon_intr.c new file mode 100644 index 0000000000..43885f528c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/arm/pitch_neon_intr.c @@ -0,0 +1,288 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "pitch.h" + +#ifdef FIXED_POINT + +opus_val32 celt_inner_prod_neon(const opus_val16 *x, const opus_val16 *y, int N) +{ + int i; + opus_val32 xy; + int16x8_t x_s16x8, y_s16x8; + int32x4_t xy_s32x4 = vdupq_n_s32(0); + int64x2_t xy_s64x2; + int64x1_t xy_s64x1; + + for (i = 0; i < N - 7; i += 8) { + x_s16x8 = vld1q_s16(&x[i]); + y_s16x8 = vld1q_s16(&y[i]); + xy_s32x4 = vmlal_s16(xy_s32x4, vget_low_s16 (x_s16x8), vget_low_s16 (y_s16x8)); + xy_s32x4 = vmlal_s16(xy_s32x4, vget_high_s16(x_s16x8), vget_high_s16(y_s16x8)); + } + + if (N - i >= 4) { + const int16x4_t x_s16x4 = vld1_s16(&x[i]); + const int16x4_t y_s16x4 = vld1_s16(&y[i]); + xy_s32x4 = vmlal_s16(xy_s32x4, x_s16x4, y_s16x4); + i += 4; + } + + xy_s64x2 = vpaddlq_s32(xy_s32x4); + xy_s64x1 = vadd_s64(vget_low_s64(xy_s64x2), vget_high_s64(xy_s64x2)); + xy = vget_lane_s32(vreinterpret_s32_s64(xy_s64x1), 0); + + for (; i < N; i++) { + xy = MAC16_16(xy, x[i], y[i]); + } + +#ifdef OPUS_CHECK_ASM + celt_assert(celt_inner_prod_c(x, y, N) == xy); +#endif + + return xy; +} + +void dual_inner_prod_neon(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2) +{ + int i; + opus_val32 xy01, xy02; + int16x8_t x_s16x8, y01_s16x8, y02_s16x8; + int32x4_t xy01_s32x4 = vdupq_n_s32(0); + int32x4_t xy02_s32x4 = vdupq_n_s32(0); + int64x2_t xy01_s64x2, xy02_s64x2; + int64x1_t xy01_s64x1, xy02_s64x1; + + for (i = 0; i < N - 7; i += 8) { + x_s16x8 = vld1q_s16(&x[i]); + y01_s16x8 = vld1q_s16(&y01[i]); + y02_s16x8 = vld1q_s16(&y02[i]); + xy01_s32x4 = vmlal_s16(xy01_s32x4, vget_low_s16 (x_s16x8), vget_low_s16 (y01_s16x8)); + xy02_s32x4 = vmlal_s16(xy02_s32x4, vget_low_s16 (x_s16x8), vget_low_s16 (y02_s16x8)); + xy01_s32x4 = vmlal_s16(xy01_s32x4, vget_high_s16(x_s16x8), vget_high_s16(y01_s16x8)); + xy02_s32x4 = vmlal_s16(xy02_s32x4, vget_high_s16(x_s16x8), vget_high_s16(y02_s16x8)); + } + + if (N - i >= 4) { + const int16x4_t x_s16x4 = vld1_s16(&x[i]); + const int16x4_t y01_s16x4 = vld1_s16(&y01[i]); + const int16x4_t y02_s16x4 = vld1_s16(&y02[i]); + xy01_s32x4 = vmlal_s16(xy01_s32x4, x_s16x4, y01_s16x4); + xy02_s32x4 = vmlal_s16(xy02_s32x4, x_s16x4, y02_s16x4); + i += 4; + } + + xy01_s64x2 = vpaddlq_s32(xy01_s32x4); + xy02_s64x2 = vpaddlq_s32(xy02_s32x4); + xy01_s64x1 = vadd_s64(vget_low_s64(xy01_s64x2), vget_high_s64(xy01_s64x2)); + xy02_s64x1 = vadd_s64(vget_low_s64(xy02_s64x2), vget_high_s64(xy02_s64x2)); + xy01 = vget_lane_s32(vreinterpret_s32_s64(xy01_s64x1), 0); + xy02 = vget_lane_s32(vreinterpret_s32_s64(xy02_s64x1), 0); + + for (; i < N; i++) { + xy01 = MAC16_16(xy01, x[i], y01[i]); + xy02 = MAC16_16(xy02, x[i], y02[i]); + } + *xy1 = xy01; + *xy2 = xy02; + +#ifdef OPUS_CHECK_ASM + { + opus_val32 xy1_c, xy2_c; + dual_inner_prod_c(x, y01, y02, N, &xy1_c, &xy2_c); + celt_assert(xy1_c == *xy1); + celt_assert(xy2_c == *xy2); + } +#endif +} + +#else /* !FIXED_POINT */ + +/* ========================================================================== */ + +#ifdef __ARM_FEATURE_FMA +/* If we can, force the compiler to use an FMA instruction rather than break + vmlaq_f32() into fmul/fadd. */ +#define vmlaq_f32(a,b,c) vfmaq_f32(a,b,c) +#endif + + +#ifdef OPUS_CHECK_ASM + +/* This part of code simulates floating-point NEON operations. */ + +/* celt_inner_prod_neon_float_c_simulation() simulates the floating-point */ +/* operations of celt_inner_prod_neon(), and both functions should have bit */ +/* exact output. */ +static opus_val32 celt_inner_prod_neon_float_c_simulation(const opus_val16 *x, const opus_val16 *y, float *err, int N) +{ + int i; + *err = 0; + opus_val32 xy, xy0 = 0, xy1 = 0, xy2 = 0, xy3 = 0; + for (i = 0; i < N - 3; i += 4) { + xy0 = MAC16_16(xy0, x[i + 0], y[i + 0]); + xy1 = MAC16_16(xy1, x[i + 1], y[i + 1]); + xy2 = MAC16_16(xy2, x[i + 2], y[i + 2]); + xy3 = MAC16_16(xy3, x[i + 3], y[i + 3]); + *err += ABS32(xy0)+ABS32(xy1)+ABS32(xy2)+ABS32(xy3); + } + xy0 += xy2; + xy1 += xy3; + xy = xy0 + xy1; + *err += ABS32(xy1)+ABS32(xy0)+ABS32(xy); + for (; i < N; i++) { + xy = MAC16_16(xy, x[i], y[i]); + *err += ABS32(xy); + } + *err = *err*2e-7 + N*1e-37; + return xy; +} + +/* dual_inner_prod_neon_float_c_simulation() simulates the floating-point */ +/* operations of dual_inner_prod_neon(), and both functions should have bit */ +/* exact output. */ +static void dual_inner_prod_neon_float_c_simulation(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2, float *err) +{ + *xy1 = celt_inner_prod_neon_float_c_simulation(x, y01, &err[0], N); + *xy2 = celt_inner_prod_neon_float_c_simulation(x, y02, &err[1], N); +} + +#endif /* OPUS_CHECK_ASM */ + +/* ========================================================================== */ + +opus_val32 celt_inner_prod_neon(const opus_val16 *x, const opus_val16 *y, int N) +{ + int i; + opus_val32 xy; + float32x4_t xy_f32x4 = vdupq_n_f32(0); + float32x2_t xy_f32x2; + + for (i = 0; i < N - 7; i += 8) { + float32x4_t x_f32x4, y_f32x4; + x_f32x4 = vld1q_f32(&x[i]); + y_f32x4 = vld1q_f32(&y[i]); + xy_f32x4 = vmlaq_f32(xy_f32x4, x_f32x4, y_f32x4); + x_f32x4 = vld1q_f32(&x[i + 4]); + y_f32x4 = vld1q_f32(&y[i + 4]); + xy_f32x4 = vmlaq_f32(xy_f32x4, x_f32x4, y_f32x4); + } + + if (N - i >= 4) { + const float32x4_t x_f32x4 = vld1q_f32(&x[i]); + const float32x4_t y_f32x4 = vld1q_f32(&y[i]); + xy_f32x4 = vmlaq_f32(xy_f32x4, x_f32x4, y_f32x4); + i += 4; + } + + xy_f32x2 = vadd_f32(vget_low_f32(xy_f32x4), vget_high_f32(xy_f32x4)); + xy_f32x2 = vpadd_f32(xy_f32x2, xy_f32x2); + xy = vget_lane_f32(xy_f32x2, 0); + + for (; i < N; i++) { + xy = MAC16_16(xy, x[i], y[i]); + } + +#ifdef OPUS_CHECK_ASM + { + float err, res; + res = celt_inner_prod_neon_float_c_simulation(x, y, &err, N); + /*if (ABS32(res - xy) > err) fprintf(stderr, "%g %g %g\n", res, xy, err);*/ + celt_assert(ABS32(res - xy) <= err); + } +#endif + + return xy; +} + +void dual_inner_prod_neon(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2) +{ + int i; + opus_val32 xy01, xy02; + float32x4_t xy01_f32x4 = vdupq_n_f32(0); + float32x4_t xy02_f32x4 = vdupq_n_f32(0); + float32x2_t xy01_f32x2, xy02_f32x2; + + for (i = 0; i < N - 7; i += 8) { + float32x4_t x_f32x4, y01_f32x4, y02_f32x4; + x_f32x4 = vld1q_f32(&x[i]); + y01_f32x4 = vld1q_f32(&y01[i]); + y02_f32x4 = vld1q_f32(&y02[i]); + xy01_f32x4 = vmlaq_f32(xy01_f32x4, x_f32x4, y01_f32x4); + xy02_f32x4 = vmlaq_f32(xy02_f32x4, x_f32x4, y02_f32x4); + x_f32x4 = vld1q_f32(&x[i + 4]); + y01_f32x4 = vld1q_f32(&y01[i + 4]); + y02_f32x4 = vld1q_f32(&y02[i + 4]); + xy01_f32x4 = vmlaq_f32(xy01_f32x4, x_f32x4, y01_f32x4); + xy02_f32x4 = vmlaq_f32(xy02_f32x4, x_f32x4, y02_f32x4); + } + + if (N - i >= 4) { + const float32x4_t x_f32x4 = vld1q_f32(&x[i]); + const float32x4_t y01_f32x4 = vld1q_f32(&y01[i]); + const float32x4_t y02_f32x4 = vld1q_f32(&y02[i]); + xy01_f32x4 = vmlaq_f32(xy01_f32x4, x_f32x4, y01_f32x4); + xy02_f32x4 = vmlaq_f32(xy02_f32x4, x_f32x4, y02_f32x4); + i += 4; + } + + xy01_f32x2 = vadd_f32(vget_low_f32(xy01_f32x4), vget_high_f32(xy01_f32x4)); + xy02_f32x2 = vadd_f32(vget_low_f32(xy02_f32x4), vget_high_f32(xy02_f32x4)); + xy01_f32x2 = vpadd_f32(xy01_f32x2, xy01_f32x2); + xy02_f32x2 = vpadd_f32(xy02_f32x2, xy02_f32x2); + xy01 = vget_lane_f32(xy01_f32x2, 0); + xy02 = vget_lane_f32(xy02_f32x2, 0); + + for (; i < N; i++) { + xy01 = MAC16_16(xy01, x[i], y01[i]); + xy02 = MAC16_16(xy02, x[i], y02[i]); + } + *xy1 = xy01; + *xy2 = xy02; + +#ifdef OPUS_CHECK_ASM + { + opus_val32 xy1_c, xy2_c; + float err[2]; + dual_inner_prod_neon_float_c_simulation(x, y01, y02, N, &xy1_c, &xy2_c, err); + /*if (ABS32(xy1_c - *xy1) > err[0]) fprintf(stderr, "dual1 fail: %g %g %g\n", xy1_c, *xy1, err[0]); + if (ABS32(xy2_c - *xy2) > err[1]) fprintf(stderr, "dual2 fail: %g %g %g\n", xy2_c, *xy2, err[1]);*/ + celt_assert(ABS32(xy1_c - *xy1) <= err[0]); + celt_assert(ABS32(xy2_c - *xy2) <= err[1]); + } +#endif +} + +#endif /* FIXED_POINT */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/bands.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/bands.c new file mode 100644 index 0000000000..6785e08e02 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/bands.c @@ -0,0 +1,1673 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008-2009 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "bands.h" +#include "modes.h" +#include "vq.h" +#include "cwrs.h" +#include "stack_alloc.h" +#include "os_support.h" +#include "mathops.h" +#include "rate.h" +#include "quant_bands.h" +#include "pitch.h" + +int hysteresis_decision(opus_val16 val, const opus_val16 *thresholds, const opus_val16 *hysteresis, int N, int prev) +{ + int i; + for (i=0;iprev && val < thresholds[prev]+hysteresis[prev]) + i=prev; + if (i thresholds[prev-1]-hysteresis[prev-1]) + i=prev; + return i; +} + +opus_uint32 celt_lcg_rand(opus_uint32 seed) +{ + return 1664525 * seed + 1013904223; +} + +/* This is a cos() approximation designed to be bit-exact on any platform. Bit exactness + with this approximation is important because it has an impact on the bit allocation */ +opus_int16 bitexact_cos(opus_int16 x) +{ + opus_int32 tmp; + opus_int16 x2; + tmp = (4096+((opus_int32)(x)*(x)))>>13; + celt_sig_assert(tmp<=32767); + x2 = tmp; + x2 = (32767-x2) + FRAC_MUL16(x2, (-7651 + FRAC_MUL16(x2, (8277 + FRAC_MUL16(-626, x2))))); + celt_sig_assert(x2<=32766); + return 1+x2; +} + +int bitexact_log2tan(int isin,int icos) +{ + int lc; + int ls; + lc=EC_ILOG(icos); + ls=EC_ILOG(isin); + icos<<=15-lc; + isin<<=15-ls; + return (ls-lc)*(1<<11) + +FRAC_MUL16(isin, FRAC_MUL16(isin, -2597) + 7932) + -FRAC_MUL16(icos, FRAC_MUL16(icos, -2597) + 7932); +} + +#ifdef FIXED_POINT +/* Compute the amplitude (sqrt energy) in each of the bands */ +void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM, int arch) +{ + int i, c, N; + const opus_int16 *eBands = m->eBands; + (void)arch; + N = m->shortMdctSize< 0) + { + int shift = celt_ilog2(maxval) - 14 + (((m->logN[i]>>BITRES)+LM+1)>>1); + j=eBands[i]<0) + { + do { + sum = MAC16_16(sum, EXTRACT16(SHR32(X[j+c*N],shift)), + EXTRACT16(SHR32(X[j+c*N],shift))); + } while (++jnbEBands] = EPSILON+VSHR32(EXTEND32(celt_sqrt(sum)),-shift); + } else { + bandE[i+c*m->nbEBands] = EPSILON; + } + /*printf ("%f ", bandE[i+c*m->nbEBands]);*/ + } + } while (++ceBands; + N = M*m->shortMdctSize; + c=0; do { + i=0; do { + opus_val16 g; + int j,shift; + opus_val16 E; + shift = celt_zlog2(bandE[i+c*m->nbEBands])-13; + E = VSHR32(bandE[i+c*m->nbEBands], shift); + g = EXTRACT16(celt_rcp(SHL32(E,3))); + j=M*eBands[i]; do { + X[j+c*N] = MULT16_16_Q15(VSHR32(freq[j+c*N],shift-1),g); + } while (++jeBands; + N = m->shortMdctSize<nbEBands] = celt_sqrt(sum); + /*printf ("%f ", bandE[i+c*m->nbEBands]);*/ + } + } while (++ceBands; + N = M*m->shortMdctSize; + c=0; do { + for (i=0;inbEBands]); + for (j=M*eBands[i];jeBands; + N = M*m->shortMdctSize; + bound = M*eBands[end]; + if (downsample!=1) + bound = IMIN(bound, N/downsample); + if (silence) + { + bound = 0; + start = end = 0; + } + f = freq; + x = X+M*eBands[start]; + for (i=0;i>DB_SHIFT); + if (shift>31) + { + shift=0; + g=0; + } else { + /* Handle the fractional part. */ + g = celt_exp2_frac(lg&((1< 16384 we'd be likely to overflow, so we're + capping the gain here, which is equivalent to a cap of 18 on lg. + This shouldn't trigger unless the bitstream is already corrupted. */ + if (shift <= -2) + { + g = 16384; + shift = -2; + } + do { + *f++ = SHL32(MULT16_16(*x++, g), -shift); + } while (++jeBands[i+1]-m->eBands[i]; + /* depth in 1/8 bits */ + celt_sig_assert(pulses[i]>=0); + depth = celt_udiv(1+pulses[i], (m->eBands[i+1]-m->eBands[i]))>>LM; + +#ifdef FIXED_POINT + thresh32 = SHR32(celt_exp2(-SHL16(depth, 10-BITRES)),1); + thresh = MULT16_32_Q15(QCONST16(0.5f, 15), MIN32(32767,thresh32)); + { + opus_val32 t; + t = N0<>1; + t = SHL32(t, (7-shift)<<1); + sqrt_1 = celt_rsqrt_norm(t); + } +#else + thresh = .5f*celt_exp2(-.125f*depth); + sqrt_1 = celt_rsqrt(N0<nbEBands+i]; + prev2 = prev2logE[c*m->nbEBands+i]; + if (C==1) + { + prev1 = MAX16(prev1,prev1logE[m->nbEBands+i]); + prev2 = MAX16(prev2,prev2logE[m->nbEBands+i]); + } + Ediff = EXTEND32(logE[c*m->nbEBands+i])-EXTEND32(MIN16(prev1,prev2)); + Ediff = MAX32(0, Ediff); + +#ifdef FIXED_POINT + if (Ediff < 16384) + { + opus_val32 r32 = SHR32(celt_exp2(-EXTRACT16(Ediff)),1); + r = 2*MIN16(16383,r32); + } else { + r = 0; + } + if (LM==3) + r = MULT16_16_Q14(23170, MIN32(23169, r)); + r = SHR16(MIN16(thresh, r),1); + r = SHR32(MULT16_16_Q15(sqrt_1, r),shift); +#else + /* r needs to be multiplied by 2 or 2*sqrt(2) depending on LM because + short blocks don't have the same energy as long */ + r = 2.f*celt_exp2(-Ediff); + if (LM==3) + r *= 1.41421356f; + r = MIN16(thresh, r); + r = r*sqrt_1; +#endif + X = X_+c*size+(m->eBands[i]<nbEBands]))-13; +#endif + left = VSHR32(bandE[i],shift); + right = VSHR32(bandE[i+m->nbEBands],shift); + norm = EPSILON + celt_sqrt(EPSILON+MULT16_16(left,left)+MULT16_16(right,right)); + a1 = DIV32_16(SHL32(EXTEND32(left),14),norm); + a2 = DIV32_16(SHL32(EXTEND32(right),14),norm); + for (j=0;j>1; + kr = celt_ilog2(Er)>>1; +#endif + t = VSHR32(El, (kl-7)<<1); + lgain = celt_rsqrt_norm(t); + t = VSHR32(Er, (kr-7)<<1); + rgain = celt_rsqrt_norm(t); + +#ifdef FIXED_POINT + if (kl < 7) + kl = 7; + if (kr < 7) + kr = 7; +#endif + + for (j=0;jeBands; + int decision; + int hf_sum=0; + + celt_assert(end>0); + + N0 = M*m->shortMdctSize; + + if (M*(eBands[end]-eBands[end-1]) <= 8) + return SPREAD_NONE; + c=0; do { + for (i=0;im->nbEBands-4) + hf_sum += celt_udiv(32*(tcount[1]+tcount[0]), N); + tmp = (2*tcount[2] >= N) + (2*tcount[1] >= N) + (2*tcount[0] >= N); + sum += tmp*spread_weight[i]; + nbBands+=spread_weight[i]; + } + } while (++cnbEBands+end)); + *hf_average = (*hf_average+hf_sum)>>1; + hf_sum = *hf_average; + if (*tapset_decision==2) + hf_sum += 4; + else if (*tapset_decision==0) + hf_sum -= 4; + if (hf_sum > 22) + *tapset_decision=2; + else if (hf_sum > 18) + *tapset_decision=1; + else + *tapset_decision=0; + } + /*printf("%d %d %d\n", hf_sum, *hf_average, *tapset_decision);*/ + celt_assert(nbBands>0); /* end has to be non-zero */ + celt_assert(sum>=0); + sum = celt_udiv((opus_int32)sum<<8, nbBands); + /* Recursive averaging */ + sum = (sum+*average)>>1; + *average = sum; + /* Hysteresis */ + sum = (3*sum + (((3-last_decision)<<7) + 64) + 2)>>2; + if (sum < 80) + { + decision = SPREAD_AGGRESSIVE; + } else if (sum < 256) + { + decision = SPREAD_NORMAL; + } else if (sum < 384) + { + decision = SPREAD_LIGHT; + } else { + decision = SPREAD_NONE; + } +#ifdef FUZZING + decision = rand()&0x3; + *tapset_decision=rand()%3; +#endif + return decision; +} + +/* Indexing table for converting from natural Hadamard to ordery Hadamard + This is essentially a bit-reversed Gray, on top of which we've added + an inversion of the order because we want the DC at the end rather than + the beginning. The lines are for N=2, 4, 8, 16 */ +static const int ordery_table[] = { + 1, 0, + 3, 0, 2, 1, + 7, 0, 4, 3, 6, 1, 5, 2, + 15, 0, 8, 7, 12, 3, 11, 4, 14, 1, 9, 6, 13, 2, 10, 5, +}; + +static void deinterleave_hadamard(celt_norm *X, int N0, int stride, int hadamard) +{ + int i,j; + VARDECL(celt_norm, tmp); + int N; + SAVE_STACK; + N = N0*stride; + ALLOC(tmp, N, celt_norm); + celt_assert(stride>0); + if (hadamard) + { + const int *ordery = ordery_table+stride-2; + for (i=0;i>= 1; + for (i=0;i>1)) { + qn = 1; + } else { + qn = exp2_table8[qb&0x7]>>(14-(qb>>BITRES)); + qn = (qn+1)>>1<<1; + } + celt_assert(qn <= 256); + return qn; +} + +struct band_ctx { + int encode; + int resynth; + const CELTMode *m; + int i; + int intensity; + int spread; + int tf_change; + ec_ctx *ec; + opus_int32 remaining_bits; + const celt_ener *bandE; + opus_uint32 seed; + int arch; + int theta_round; + int disable_inv; + int avoid_split_noise; +}; + +struct split_ctx { + int inv; + int imid; + int iside; + int delta; + int itheta; + int qalloc; +}; + +static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, + celt_norm *X, celt_norm *Y, int N, int *b, int B, int B0, + int LM, + int stereo, int *fill) +{ + int qn; + int itheta=0; + int delta; + int imid, iside; + int qalloc; + int pulse_cap; + int offset; + opus_int32 tell; + int inv=0; + int encode; + const CELTMode *m; + int i; + int intensity; + ec_ctx *ec; + const celt_ener *bandE; + + encode = ctx->encode; + m = ctx->m; + i = ctx->i; + intensity = ctx->intensity; + ec = ctx->ec; + bandE = ctx->bandE; + + /* Decide on the resolution to give to the split parameter theta */ + pulse_cap = m->logN[i]+LM*(1<>1) - (stereo&&N==2 ? QTHETA_OFFSET_TWOPHASE : QTHETA_OFFSET); + qn = compute_qn(N, *b, offset, pulse_cap, stereo); + if (stereo && i>=intensity) + qn = 1; + if (encode) + { + /* theta is the atan() of the ratio between the (normalized) + side and mid. With just that parameter, we can re-scale both + mid and side because we know that 1) they have unit norm and + 2) they are orthogonal. */ + itheta = stereo_itheta(X, Y, stereo, N, ctx->arch); + } + tell = ec_tell_frac(ec); + if (qn!=1) + { + if (encode) + { + if (!stereo || ctx->theta_round == 0) + { + itheta = (itheta*(opus_int32)qn+8192)>>14; + if (!stereo && ctx->avoid_split_noise && itheta > 0 && itheta < qn) + { + /* Check if the selected value of theta will cause the bit allocation + to inject noise on one side. If so, make sure the energy of that side + is zero. */ + int unquantized = celt_udiv((opus_int32)itheta*16384, qn); + imid = bitexact_cos((opus_int16)unquantized); + iside = bitexact_cos((opus_int16)(16384-unquantized)); + delta = FRAC_MUL16((N-1)<<7,bitexact_log2tan(iside,imid)); + if (delta > *b) + itheta = qn; + else if (delta < -*b) + itheta = 0; + } + } else { + int down; + /* Bias quantization towards itheta=0 and itheta=16384. */ + int bias = itheta > 8192 ? 32767/qn : -32767/qn; + down = IMIN(qn-1, IMAX(0, (itheta*(opus_int32)qn + bias)>>14)); + if (ctx->theta_round < 0) + itheta = down; + else + itheta = down+1; + } + } + /* Entropy coding of the angle. We use a uniform pdf for the + time split, a step for stereo, and a triangular one for the rest. */ + if (stereo && N>2) + { + int p0 = 3; + int x = itheta; + int x0 = qn/2; + int ft = p0*(x0+1) + x0; + /* Use a probability of p0 up to itheta=8192 and then use 1 after */ + if (encode) + { + ec_encode(ec,x<=x0?p0*x:(x-1-x0)+(x0+1)*p0,x<=x0?p0*(x+1):(x-x0)+(x0+1)*p0,ft); + } else { + int fs; + fs=ec_decode(ec,ft); + if (fs<(x0+1)*p0) + x=fs/p0; + else + x=x0+1+(fs-(x0+1)*p0); + ec_dec_update(ec,x<=x0?p0*x:(x-1-x0)+(x0+1)*p0,x<=x0?p0*(x+1):(x-x0)+(x0+1)*p0,ft); + itheta = x; + } + } else if (B0>1 || stereo) { + /* Uniform pdf */ + if (encode) + ec_enc_uint(ec, itheta, qn+1); + else + itheta = ec_dec_uint(ec, qn+1); + } else { + int fs=1, ft; + ft = ((qn>>1)+1)*((qn>>1)+1); + if (encode) + { + int fl; + + fs = itheta <= (qn>>1) ? itheta + 1 : qn + 1 - itheta; + fl = itheta <= (qn>>1) ? itheta*(itheta + 1)>>1 : + ft - ((qn + 1 - itheta)*(qn + 2 - itheta)>>1); + + ec_encode(ec, fl, fl+fs, ft); + } else { + /* Triangular pdf */ + int fl=0; + int fm; + fm = ec_decode(ec, ft); + + if (fm < ((qn>>1)*((qn>>1) + 1)>>1)) + { + itheta = (isqrt32(8*(opus_uint32)fm + 1) - 1)>>1; + fs = itheta + 1; + fl = itheta*(itheta + 1)>>1; + } + else + { + itheta = (2*(qn + 1) + - isqrt32(8*(opus_uint32)(ft - fm - 1) + 1))>>1; + fs = qn + 1 - itheta; + fl = ft - ((qn + 1 - itheta)*(qn + 2 - itheta)>>1); + } + + ec_dec_update(ec, fl, fl+fs, ft); + } + } + celt_assert(itheta>=0); + itheta = celt_udiv((opus_int32)itheta*16384, qn); + if (encode && stereo) + { + if (itheta==0) + intensity_stereo(m, X, Y, bandE, i, N); + else + stereo_split(X, Y, N); + } + /* NOTE: Renormalising X and Y *may* help fixed-point a bit at very high rate. + Let's do that at higher complexity */ + } else if (stereo) { + if (encode) + { + inv = itheta > 8192 && !ctx->disable_inv; + if (inv) + { + int j; + for (j=0;j2<remaining_bits > 2<disable_inv) + inv = 0; + itheta = 0; + } + qalloc = ec_tell_frac(ec) - tell; + *b -= qalloc; + + if (itheta == 0) + { + imid = 32767; + iside = 0; + *fill &= (1<inv = inv; + sctx->imid = imid; + sctx->iside = iside; + sctx->delta = delta; + sctx->itheta = itheta; + sctx->qalloc = qalloc; +} +static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, + celt_norm *lowband_out) +{ + int c; + int stereo; + celt_norm *x = X; + int encode; + ec_ctx *ec; + + encode = ctx->encode; + ec = ctx->ec; + + stereo = Y != NULL; + c=0; do { + int sign=0; + if (ctx->remaining_bits>=1<remaining_bits -= 1<resynth) + x[0] = sign ? -NORM_SCALING : NORM_SCALING; + x = Y; + } while (++c<1+stereo); + if (lowband_out) + lowband_out[0] = SHR16(X[0],4); + return 1; +} + +/* This function is responsible for encoding and decoding a mono partition. + It can split the band in two and transmit the energy difference with + the two half-bands. It can be called recursively so bands can end up being + split in 8 parts. */ +static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, + int N, int b, int B, celt_norm *lowband, + int LM, + opus_val16 gain, int fill) +{ + const unsigned char *cache; + int q; + int curr_bits; + int imid=0, iside=0; + int B0=B; + opus_val16 mid=0, side=0; + unsigned cm=0; + celt_norm *Y=NULL; + int encode; + const CELTMode *m; + int i; + int spread; + ec_ctx *ec; + + encode = ctx->encode; + m = ctx->m; + i = ctx->i; + spread = ctx->spread; + ec = ctx->ec; + + /* If we need 1.5 more bit than we can produce, split the band in two. */ + cache = m->cache.bits + m->cache.index[(LM+1)*m->nbEBands+i]; + if (LM != -1 && b > cache[cache[0]]+12 && N>2) + { + int mbits, sbits, delta; + int itheta; + int qalloc; + struct split_ctx sctx; + celt_norm *next_lowband2=NULL; + opus_int32 rebalance; + + N >>= 1; + Y = X+N; + LM -= 1; + if (B==1) + fill = (fill&1)|(fill<<1); + B = (B+1)>>1; + + compute_theta(ctx, &sctx, X, Y, N, &b, B, B0, LM, 0, &fill); + imid = sctx.imid; + iside = sctx.iside; + delta = sctx.delta; + itheta = sctx.itheta; + qalloc = sctx.qalloc; +#ifdef FIXED_POINT + mid = imid; + side = iside; +#else + mid = (1.f/32768)*imid; + side = (1.f/32768)*iside; +#endif + + /* Give more bits to low-energy MDCTs than they would otherwise deserve */ + if (B0>1 && (itheta&0x3fff)) + { + if (itheta > 8192) + /* Rough approximation for pre-echo masking */ + delta -= delta>>(4-LM); + else + /* Corresponds to a forward-masking slope of 1.5 dB per 10 ms */ + delta = IMIN(0, delta + (N<>(5-LM))); + } + mbits = IMAX(0, IMIN(b, (b-delta)/2)); + sbits = b-mbits; + ctx->remaining_bits -= qalloc; + + if (lowband) + next_lowband2 = lowband+N; /* >32-bit split case */ + + rebalance = ctx->remaining_bits; + if (mbits >= sbits) + { + cm = quant_partition(ctx, X, N, mbits, B, lowband, LM, + MULT16_16_P15(gain,mid), fill); + rebalance = mbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<>B)<<(B0>>1); + } else { + cm = quant_partition(ctx, Y, N, sbits, B, next_lowband2, LM, + MULT16_16_P15(gain,side), fill>>B)<<(B0>>1); + rebalance = sbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<remaining_bits -= curr_bits; + + /* Ensures we can never bust the budget */ + while (ctx->remaining_bits < 0 && q > 0) + { + ctx->remaining_bits += curr_bits; + q--; + curr_bits = pulses2bits(m, i, LM, q); + ctx->remaining_bits -= curr_bits; + } + + if (q!=0) + { + int K = get_pulses(q); + + /* Finally do the actual quantization */ + if (encode) + { + cm = alg_quant(X, N, K, spread, B, ec, gain, ctx->resynth, ctx->arch); + } else { + cm = alg_unquant(X, N, K, spread, B, ec, gain); + } + } else { + /* If there's no pulse, fill the band anyway */ + int j; + if (ctx->resynth) + { + unsigned cm_mask; + /* B can be as large as 16, so this shift might overflow an int on a + 16-bit platform; use a long to get defined behavior.*/ + cm_mask = (unsigned)(1UL<seed = celt_lcg_rand(ctx->seed); + X[j] = (celt_norm)((opus_int32)ctx->seed>>20); + } + cm = cm_mask; + } else { + /* Folded spectrum */ + for (j=0;jseed = celt_lcg_rand(ctx->seed); + /* About 48 dB below the "normal" folding level */ + tmp = QCONST16(1.0f/256, 10); + tmp = (ctx->seed)&0x8000 ? tmp : -tmp; + X[j] = lowband[j]+tmp; + } + cm = fill; + } + renormalise_vector(X, N, gain, ctx->arch); + } + } + } + } + + return cm; +} + + +/* This function is responsible for encoding and decoding a band for the mono case. */ +static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, + int N, int b, int B, celt_norm *lowband, + int LM, celt_norm *lowband_out, + opus_val16 gain, celt_norm *lowband_scratch, int fill) +{ + int N0=N; + int N_B=N; + int N_B0; + int B0=B; + int time_divide=0; + int recombine=0; + int longBlocks; + unsigned cm=0; + int k; + int encode; + int tf_change; + + encode = ctx->encode; + tf_change = ctx->tf_change; + + longBlocks = B0==1; + + N_B = celt_udiv(N_B, B); + + /* Special case for one sample */ + if (N==1) + { + return quant_band_n1(ctx, X, NULL, lowband_out); + } + + if (tf_change>0) + recombine = tf_change; + /* Band recombining to increase frequency resolution */ + + if (lowband_scratch && lowband && (recombine || ((N_B&1) == 0 && tf_change<0) || B0>1)) + { + OPUS_COPY(lowband_scratch, lowband, N); + lowband = lowband_scratch; + } + + for (k=0;k>k, 1<>k, 1<>4]<<2; + } + B>>=recombine; + N_B<<=recombine; + + /* Increasing the time resolution */ + while ((N_B&1) == 0 && tf_change<0) + { + if (encode) + haar1(X, N_B, B); + if (lowband) + haar1(lowband, N_B, B); + fill |= fill<>= 1; + time_divide++; + tf_change++; + } + B0=B; + N_B0 = N_B; + + /* Reorganize the samples in time order instead of frequency order */ + if (B0>1) + { + if (encode) + deinterleave_hadamard(X, N_B>>recombine, B0<>recombine, B0<resynth) + { + /* Undo the sample reorganization going from time order to frequency order */ + if (B0>1) + interleave_hadamard(X, N_B>>recombine, B0<>= 1; + N_B <<= 1; + cm |= cm>>B; + haar1(X, N_B, B); + } + + for (k=0;k>k, 1<encode; + ec = ctx->ec; + + /* Special case for one sample */ + if (N==1) + { + return quant_band_n1(ctx, X, Y, lowband_out); + } + + orig_fill = fill; + + compute_theta(ctx, &sctx, X, Y, N, &b, B, B, LM, 1, &fill); + inv = sctx.inv; + imid = sctx.imid; + iside = sctx.iside; + delta = sctx.delta; + itheta = sctx.itheta; + qalloc = sctx.qalloc; +#ifdef FIXED_POINT + mid = imid; + side = iside; +#else + mid = (1.f/32768)*imid; + side = (1.f/32768)*iside; +#endif + + /* This is a special case for N=2 that only works for stereo and takes + advantage of the fact that mid and side are orthogonal to encode + the side with just one bit. */ + if (N==2) + { + int c; + int sign=0; + celt_norm *x2, *y2; + mbits = b; + sbits = 0; + /* Only need one bit for the side. */ + if (itheta != 0 && itheta != 16384) + sbits = 1< 8192; + ctx->remaining_bits -= qalloc+sbits; + + x2 = c ? Y : X; + y2 = c ? X : Y; + if (sbits) + { + if (encode) + { + /* Here we only need to encode a sign for the side. */ + sign = x2[0]*y2[1] - x2[1]*y2[0] < 0; + ec_enc_bits(ec, sign, 1); + } else { + sign = ec_dec_bits(ec, 1); + } + } + sign = 1-2*sign; + /* We use orig_fill here because we want to fold the side, but if + itheta==16384, we'll have cleared the low bits of fill. */ + cm = quant_band(ctx, x2, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, orig_fill); + /* We don't split N=2 bands, so cm is either 1 or 0 (for a fold-collapse), + and there's no need to worry about mixing with the other channel. */ + y2[0] = -sign*x2[1]; + y2[1] = sign*x2[0]; + if (ctx->resynth) + { + celt_norm tmp; + X[0] = MULT16_16_Q15(mid, X[0]); + X[1] = MULT16_16_Q15(mid, X[1]); + Y[0] = MULT16_16_Q15(side, Y[0]); + Y[1] = MULT16_16_Q15(side, Y[1]); + tmp = X[0]; + X[0] = SUB16(tmp,Y[0]); + Y[0] = ADD16(tmp,Y[0]); + tmp = X[1]; + X[1] = SUB16(tmp,Y[1]); + Y[1] = ADD16(tmp,Y[1]); + } + } else { + /* "Normal" split code */ + opus_int32 rebalance; + + mbits = IMAX(0, IMIN(b, (b-delta)/2)); + sbits = b-mbits; + ctx->remaining_bits -= qalloc; + + rebalance = ctx->remaining_bits; + if (mbits >= sbits) + { + /* In stereo mode, we do not apply a scaling to the mid because we need the normalized + mid for folding later. */ + cm = quant_band(ctx, X, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, fill); + rebalance = mbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<>B); + } else { + /* For a stereo split, the high bits of fill are always zero, so no + folding will be done to the side. */ + cm = quant_band(ctx, Y, N, sbits, B, NULL, LM, NULL, side, NULL, fill>>B); + rebalance = sbits - (rebalance-ctx->remaining_bits); + if (rebalance > 3<resynth) + { + if (N!=2) + stereo_merge(X, Y, mid, N, ctx->arch); + if (inv) + { + int j; + for (j=0;jeBands; + n1 = M*(eBands[start+1]-eBands[start]); + n2 = M*(eBands[start+2]-eBands[start+1]); + /* Duplicate enough of the first band folding data to be able to fold the second band. + Copies no data for CELT-only mode. */ + OPUS_COPY(&norm[n1], &norm[2*n1 - n2], n2-n1); + if (dual_stereo) + OPUS_COPY(&norm2[n1], &norm2[2*n1 - n2], n2-n1); +} +#endif + +void quant_all_bands(int encode, const CELTMode *m, int start, int end, + celt_norm *X_, celt_norm *Y_, unsigned char *collapse_masks, + const celt_ener *bandE, int *pulses, int shortBlocks, int spread, + int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, + opus_int32 balance, ec_ctx *ec, int LM, int codedBands, + opus_uint32 *seed, int complexity, int arch, int disable_inv) +{ + int i; + opus_int32 remaining_bits; + const opus_int16 * OPUS_RESTRICT eBands = m->eBands; + celt_norm * OPUS_RESTRICT norm, * OPUS_RESTRICT norm2; + VARDECL(celt_norm, _norm); + VARDECL(celt_norm, _lowband_scratch); + VARDECL(celt_norm, X_save); + VARDECL(celt_norm, Y_save); + VARDECL(celt_norm, X_save2); + VARDECL(celt_norm, Y_save2); + VARDECL(celt_norm, norm_save2); + int resynth_alloc; + celt_norm *lowband_scratch; + int B; + int M; + int lowband_offset; + int update_lowband = 1; + int C = Y_ != NULL ? 2 : 1; + int norm_offset; + int theta_rdo = encode && Y_!=NULL && !dual_stereo && complexity>=8; +#ifdef RESYNTH + int resynth = 1; +#else + int resynth = !encode || theta_rdo; +#endif + struct band_ctx ctx; + SAVE_STACK; + + M = 1<nbEBands-1]-norm_offset), celt_norm); + norm = _norm; + norm2 = norm + M*eBands[m->nbEBands-1]-norm_offset; + + /* For decoding, we can use the last band as scratch space because we don't need that + scratch space for the last band and we don't care about the data there until we're + decoding the last band. */ + if (encode && resynth) + resynth_alloc = M*(eBands[m->nbEBands]-eBands[m->nbEBands-1]); + else + resynth_alloc = ALLOC_NONE; + ALLOC(_lowband_scratch, resynth_alloc, celt_norm); + if (encode && resynth) + lowband_scratch = _lowband_scratch; + else + lowband_scratch = X_+M*eBands[m->effEBands-1]; + ALLOC(X_save, resynth_alloc, celt_norm); + ALLOC(Y_save, resynth_alloc, celt_norm); + ALLOC(X_save2, resynth_alloc, celt_norm); + ALLOC(Y_save2, resynth_alloc, celt_norm); + ALLOC(norm_save2, resynth_alloc, celt_norm); + + lowband_offset = 0; + ctx.bandE = bandE; + ctx.ec = ec; + ctx.encode = encode; + ctx.intensity = intensity; + ctx.m = m; + ctx.seed = *seed; + ctx.spread = spread; + ctx.arch = arch; + ctx.disable_inv = disable_inv; + ctx.resynth = resynth; + ctx.theta_round = 0; + /* Avoid injecting noise in the first band on transients. */ + ctx.avoid_split_noise = B > 1; + for (i=start;i 0); + tell = ec_tell_frac(ec); + + /* Compute how many bits we want to allocate to this band */ + if (i != start) + balance -= tell; + remaining_bits = total_bits-tell-1; + ctx.remaining_bits = remaining_bits; + if (i <= codedBands-1) + { + curr_balance = celt_sudiv(balance, IMIN(3, codedBands-i)); + b = IMAX(0, IMIN(16383, IMIN(remaining_bits+1,pulses[i]+curr_balance))); + } else { + b = 0; + } + +#ifndef DISABLE_UPDATE_DRAFT + if (resynth && (M*eBands[i]-N >= M*eBands[start] || i==start+1) && (update_lowband || lowband_offset==0)) + lowband_offset = i; + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); +#else + if (resynth && M*eBands[i]-N >= M*eBands[start] && (update_lowband || lowband_offset==0)) + lowband_offset = i; +#endif + + tf_change = tf_res[i]; + ctx.tf_change = tf_change; + if (i>=m->effEBands) + { + X=norm; + if (Y_!=NULL) + Y = norm; + lowband_scratch = NULL; + } + if (last && !theta_rdo) + lowband_scratch = NULL; + + /* Get a conservative estimate of the collapse_mask's for the bands we're + going to be folding from. */ + if (lowband_offset != 0 && (spread!=SPREAD_AGGRESSIVE || B>1 || tf_change<0)) + { + int fold_start; + int fold_end; + int fold_i; + /* This ensures we never repeat spectral content within one band */ + effective_lowband = IMAX(0, M*eBands[lowband_offset]-norm_offset-N); + fold_start = lowband_offset; + while(M*eBands[--fold_start] > effective_lowband+norm_offset); + fold_end = lowband_offset-1; +#ifndef DISABLE_UPDATE_DRAFT + while(++fold_end < i && M*eBands[fold_end] < effective_lowband+norm_offset+N); +#else + while(M*eBands[++fold_end] < effective_lowband+norm_offset+N); +#endif + x_cm = y_cm = 0; + fold_i = fold_start; do { + x_cm |= collapse_masks[fold_i*C+0]; + y_cm |= collapse_masks[fold_i*C+C-1]; + } while (++fold_inbEBands], w); + /* Make a copy. */ + cm = x_cm|y_cm; + ec_save = *ec; + ctx_save = ctx; + OPUS_COPY(X_save, X, N); + OPUS_COPY(Y_save, Y, N); + /* Encode and round down. */ + ctx.theta_round = -1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist0 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + + /* Save first result. */ + cm2 = x_cm; + ec_save2 = *ec; + ctx_save2 = ctx; + OPUS_COPY(X_save2, X, N); + OPUS_COPY(Y_save2, Y, N); + if (!last) + OPUS_COPY(norm_save2, norm+M*eBands[i]-norm_offset, N); + nstart_bytes = ec_save.offs; + nend_bytes = ec_save.storage; + bytes_buf = ec_save.buf+nstart_bytes; + save_bytes = nend_bytes-nstart_bytes; + OPUS_COPY(bytes_save, bytes_buf, save_bytes); + + /* Restore */ + *ec = ec_save; + ctx = ctx_save; + OPUS_COPY(X, X_save, N); + OPUS_COPY(Y, Y_save, N); +#ifndef DISABLE_UPDATE_DRAFT + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); +#endif + /* Encode and round up. */ + ctx.theta_round = 1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist1 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + if (dist0 >= dist1) { + x_cm = cm2; + *ec = ec_save2; + ctx = ctx_save2; + OPUS_COPY(X, X_save2, N); + OPUS_COPY(Y, Y_save2, N); + if (!last) + OPUS_COPY(norm+M*eBands[i]-norm_offset, norm_save2, N); + OPUS_COPY(bytes_buf, bytes_save, save_bytes); + } + } else { + ctx.theta_round = 0; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, x_cm|y_cm); + } + } else { + x_cm = quant_band(&ctx, X, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, Q15ONE, lowband_scratch, x_cm|y_cm); + } + y_cm = x_cm; + } + collapse_masks[i*C+0] = (unsigned char)x_cm; + collapse_masks[i*C+C-1] = (unsigned char)y_cm; + balance += pulses[i] + tell; + + /* Update the folding position only as long as we have 1 bit/sample depth. */ + update_lowband = b>(N< +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + +#ifndef PACKAGE_VERSION +#define PACKAGE_VERSION "unknown" +#endif + +#if defined(MIPSr1_ASM) +#include "mips/celt_mipsr1.h" +#endif + + +int resampling_factor(opus_int32 rate) +{ + int ret; + switch (rate) + { + case 48000: + ret = 1; + break; + case 24000: + ret = 2; + break; + case 16000: + ret = 3; + break; + case 12000: + ret = 4; + break; + case 8000: + ret = 6; + break; + default: +#ifndef CUSTOM_MODES + celt_assert(0); +#endif + ret = 0; + break; + } + return ret; +} + +#if !defined(OVERRIDE_COMB_FILTER_CONST) || defined(NON_STATIC_COMB_FILTER_CONST_C) +/* This version should be faster on ARM */ +#ifdef OPUS_ARM_ASM +#ifndef NON_STATIC_COMB_FILTER_CONST_C +static +#endif +void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, + opus_val16 g10, opus_val16 g11, opus_val16 g12) +{ + opus_val32 x0, x1, x2, x3, x4; + int i; + x4 = SHL32(x[-T-2], 1); + x3 = SHL32(x[-T-1], 1); + x2 = SHL32(x[-T], 1); + x1 = SHL32(x[-T+1], 1); + for (i=0;inbEBands;i++) + { + int N; + N=(m->eBands[i+1]-m->eBands[i])<cache.caps[m->nbEBands*(2*LM+C-1)+i]+64)*C*N>>2; + } +} + + + +const char *opus_strerror(int error) +{ + static const char * const error_strings[8] = { + "success", + "invalid argument", + "buffer too small", + "internal error", + "corrupted stream", + "request not implemented", + "invalid state", + "memory allocation failed" + }; + if (error > 0 || error < -7) + return "unknown error"; + else + return error_strings[-error]; +} + +const char *opus_get_version_string(void) +{ + return "libopus " PACKAGE_VERSION + /* Applications may rely on the presence of this substring in the version + string to determine if they have a fixed-point or floating-point build + at runtime. */ +#ifdef FIXED_POINT + "-fixed" +#endif +#ifdef FUZZING + "-fuzzing" +#endif + ; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt.h new file mode 100644 index 0000000000..2f501951d5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt.h @@ -0,0 +1,252 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/** + @file celt.h + @brief Contains all the functions for encoding and decoding audio + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CELT_H +#define CELT_H + +#include "opus_types.h" +#include "opus_defines.h" +#include "opus_custom.h" +#include "entenc.h" +#include "entdec.h" +#include "arch.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define CELTEncoder OpusCustomEncoder +#define CELTDecoder OpusCustomDecoder +#define CELTMode OpusCustomMode + +#define LEAK_BANDS 19 + +typedef struct { + int valid; + float tonality; + float tonality_slope; + float noisiness; + float activity; + float music_prob; + float music_prob_min; + float music_prob_max; + int bandwidth; + float activity_probability; + float max_pitch_ratio; + /* Store as Q6 char to save space. */ + unsigned char leak_boost[LEAK_BANDS]; +} AnalysisInfo; + +typedef struct { + int signalType; + int offset; +} SILKInfo; + +#define __celt_check_mode_ptr_ptr(ptr) ((ptr) + ((ptr) - (const CELTMode**)(ptr))) + +#define __celt_check_analysis_ptr(ptr) ((ptr) + ((ptr) - (const AnalysisInfo*)(ptr))) + +#define __celt_check_silkinfo_ptr(ptr) ((ptr) + ((ptr) - (const SILKInfo*)(ptr))) + +/* Encoder/decoder Requests */ + + +#define CELT_SET_PREDICTION_REQUEST 10002 +/** Controls the use of interframe prediction. + 0=Independent frames + 1=Short term interframe prediction allowed + 2=Long term prediction allowed + */ +#define CELT_SET_PREDICTION(x) CELT_SET_PREDICTION_REQUEST, __opus_check_int(x) + +#define CELT_SET_INPUT_CLIPPING_REQUEST 10004 +#define CELT_SET_INPUT_CLIPPING(x) CELT_SET_INPUT_CLIPPING_REQUEST, __opus_check_int(x) + +#define CELT_GET_AND_CLEAR_ERROR_REQUEST 10007 +#define CELT_GET_AND_CLEAR_ERROR(x) CELT_GET_AND_CLEAR_ERROR_REQUEST, __opus_check_int_ptr(x) + +#define CELT_SET_CHANNELS_REQUEST 10008 +#define CELT_SET_CHANNELS(x) CELT_SET_CHANNELS_REQUEST, __opus_check_int(x) + + +/* Internal */ +#define CELT_SET_START_BAND_REQUEST 10010 +#define CELT_SET_START_BAND(x) CELT_SET_START_BAND_REQUEST, __opus_check_int(x) + +#define CELT_SET_END_BAND_REQUEST 10012 +#define CELT_SET_END_BAND(x) CELT_SET_END_BAND_REQUEST, __opus_check_int(x) + +#define CELT_GET_MODE_REQUEST 10015 +/** Get the CELTMode used by an encoder or decoder */ +#define CELT_GET_MODE(x) CELT_GET_MODE_REQUEST, __celt_check_mode_ptr_ptr(x) + +#define CELT_SET_SIGNALLING_REQUEST 10016 +#define CELT_SET_SIGNALLING(x) CELT_SET_SIGNALLING_REQUEST, __opus_check_int(x) + +#define CELT_SET_TONALITY_REQUEST 10018 +#define CELT_SET_TONALITY(x) CELT_SET_TONALITY_REQUEST, __opus_check_int(x) +#define CELT_SET_TONALITY_SLOPE_REQUEST 10020 +#define CELT_SET_TONALITY_SLOPE(x) CELT_SET_TONALITY_SLOPE_REQUEST, __opus_check_int(x) + +#define CELT_SET_ANALYSIS_REQUEST 10022 +#define CELT_SET_ANALYSIS(x) CELT_SET_ANALYSIS_REQUEST, __celt_check_analysis_ptr(x) + +#define OPUS_SET_LFE_REQUEST 10024 +#define OPUS_SET_LFE(x) OPUS_SET_LFE_REQUEST, __opus_check_int(x) + +#define OPUS_SET_ENERGY_MASK_REQUEST 10026 +#define OPUS_SET_ENERGY_MASK(x) OPUS_SET_ENERGY_MASK_REQUEST, __opus_check_val16_ptr(x) + +#define CELT_SET_SILK_INFO_REQUEST 10028 +#define CELT_SET_SILK_INFO(x) CELT_SET_SILK_INFO_REQUEST, __celt_check_silkinfo_ptr(x) + +/* Encoder stuff */ + +int celt_encoder_get_size(int channels); + +int celt_encode_with_ec(OpusCustomEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes, ec_enc *enc); + +int celt_encoder_init(CELTEncoder *st, opus_int32 sampling_rate, int channels, + int arch); + + + +/* Decoder stuff */ + +int celt_decoder_get_size(int channels); + + +int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels); + +int celt_decode_with_ec_dred(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum +#ifdef ENABLE_DEEP_PLC + ,LPCNetPLCState *lpcnet +#endif + ); + +int celt_decode_with_ec(OpusCustomDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum); + +#define celt_encoder_ctl opus_custom_encoder_ctl +#define celt_decoder_ctl opus_custom_decoder_ctl + + +#ifdef CUSTOM_MODES +#define OPUS_CUSTOM_NOSTATIC +#else +#define OPUS_CUSTOM_NOSTATIC static OPUS_INLINE +#endif + +static const unsigned char trim_icdf[11] = {126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0}; +/* Probs: NONE: 21.875%, LIGHT: 6.25%, NORMAL: 65.625%, AGGRESSIVE: 6.25% */ +static const unsigned char spread_icdf[4] = {25, 23, 2, 0}; + +static const unsigned char tapset_icdf[3]={2,1,0}; + +#ifdef CUSTOM_MODES +static const unsigned char toOpusTable[20] = { + 0xE0, 0xE8, 0xF0, 0xF8, + 0xC0, 0xC8, 0xD0, 0xD8, + 0xA0, 0xA8, 0xB0, 0xB8, + 0x00, 0x00, 0x00, 0x00, + 0x80, 0x88, 0x90, 0x98, +}; + +static const unsigned char fromOpusTable[16] = { + 0x80, 0x88, 0x90, 0x98, + 0x40, 0x48, 0x50, 0x58, + 0x20, 0x28, 0x30, 0x38, + 0x00, 0x08, 0x10, 0x18 +}; + +static OPUS_INLINE int toOpus(unsigned char c) +{ + int ret=0; + if (c<0xA0) + ret = toOpusTable[c>>3]; + if (ret == 0) + return -1; + else + return ret|(c&0x7); +} + +static OPUS_INLINE int fromOpus(unsigned char c) +{ + if (c<0x80) + return -1; + else + return fromOpusTable[(c>>3)-16] | (c&0x7); +} +#endif /* CUSTOM_MODES */ + +#define COMBFILTER_MAXPERIOD 1024 +#define COMBFILTER_MINPERIOD 15 + +extern const signed char tf_select_table[4][8]; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +void validate_celt_decoder(CELTDecoder *st); +#define VALIDATE_CELT_DECODER(st) validate_celt_decoder(st) +#else +#define VALIDATE_CELT_DECODER(st) +#endif + +int resampling_factor(opus_int32 rate); + +void celt_preemphasis(const opus_val16 * OPUS_RESTRICT pcmp, celt_sig * OPUS_RESTRICT inp, + int N, int CC, int upsample, const opus_val16 *coef, celt_sig *mem, int clip); + +void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, + opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, + const opus_val16 *window, int overlap, int arch); + +void init_caps(const CELTMode *m,int *cap,int LM,int C); + +#ifdef RESYNTH +void deemphasis(celt_sig *in[], opus_val16 *pcm, int N, int C, int downsample, const opus_val16 *coef, celt_sig *mem, int accum); +void celt_synthesis(const CELTMode *mode, celt_norm *X, celt_sig * out_syn[], + opus_val16 *oldBandE, int start, int effEnd, int C, int CC, int isTransient, + int LM, int downsample, int silence, int arch); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CELT_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_decoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_decoder.c new file mode 100644 index 0000000000..743c2031bc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_decoder.c @@ -0,0 +1,1591 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2010 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CELT_DECODER_C + +#include "cpu_support.h" +#include "os_support.h" +#include "mdct.h" +#include +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#include "lpcnet_private.h" +#endif + +/* The maximum pitch lag to allow in the pitch-based PLC. It's possible to save + CPU time in the PLC pitch search by making this smaller than MAX_PERIOD. The + current value corresponds to a pitch of 66.67 Hz. */ +#define PLC_PITCH_LAG_MAX (720) +/* The minimum pitch lag to allow in the pitch-based PLC. This corresponds to a + pitch of 480 Hz. */ +#define PLC_PITCH_LAG_MIN (100) + +/**********************************************************************/ +/* */ +/* DECODER */ +/* */ +/**********************************************************************/ +#define DECODE_BUFFER_SIZE 2048 + +#define PLC_UPDATE_FRAMES 4 +#define PLC_UPDATE_SAMPLES (PLC_UPDATE_FRAMES*FRAME_SIZE) + +/** Decoder state + @brief Decoder state + */ +struct OpusCustomDecoder { + const OpusCustomMode *mode; + int overlap; + int channels; + int stream_channels; + + int downsample; + int start, end; + int signalling; + int disable_inv; + int complexity; + int arch; + + /* Everything beyond this point gets cleared on a reset */ +#define DECODER_RESET_START rng + + opus_uint32 rng; + int error; + int last_pitch_index; + int loss_duration; + int skip_plc; + int postfilter_period; + int postfilter_period_old; + opus_val16 postfilter_gain; + opus_val16 postfilter_gain_old; + int postfilter_tapset; + int postfilter_tapset_old; + int prefilter_and_fold; + + celt_sig preemph_memD[2]; + +#ifdef ENABLE_DEEP_PLC + opus_int16 plc_pcm[PLC_UPDATE_SAMPLES]; + int plc_fill; + float plc_preemphasis_mem; +#endif + + celt_sig _decode_mem[1]; /* Size = channels*(DECODE_BUFFER_SIZE+mode->overlap) */ + /* opus_val16 lpc[], Size = channels*CELT_LPC_ORDER */ + /* opus_val16 oldEBands[], Size = 2*mode->nbEBands */ + /* opus_val16 oldLogE[], Size = 2*mode->nbEBands */ + /* opus_val16 oldLogE2[], Size = 2*mode->nbEBands */ + /* opus_val16 backgroundLogE[], Size = 2*mode->nbEBands */ +}; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +/* Make basic checks on the CELT state to ensure we don't end + up writing all over memory. */ +void validate_celt_decoder(CELTDecoder *st) +{ +#ifndef CUSTOM_MODES + celt_assert(st->mode == opus_custom_mode_create(48000, 960, NULL)); + celt_assert(st->overlap == 120); + celt_assert(st->end <= 21); +#else +/* From Section 4.3 in the spec: "The normal CELT layer uses 21 of those bands, + though Opus Custom (see Section 6.2) may use a different number of bands" + + Check if it's within the maximum number of Bark frequency bands instead */ + celt_assert(st->end <= 25); +#endif + celt_assert(st->channels == 1 || st->channels == 2); + celt_assert(st->stream_channels == 1 || st->stream_channels == 2); + celt_assert(st->downsample > 0); + celt_assert(st->start == 0 || st->start == 17); + celt_assert(st->start < st->end); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif + celt_assert(st->last_pitch_index <= PLC_PITCH_LAG_MAX); + celt_assert(st->last_pitch_index >= PLC_PITCH_LAG_MIN || st->last_pitch_index == 0); + celt_assert(st->postfilter_period < MAX_PERIOD); + celt_assert(st->postfilter_period >= COMBFILTER_MINPERIOD || st->postfilter_period == 0); + celt_assert(st->postfilter_period_old < MAX_PERIOD); + celt_assert(st->postfilter_period_old >= COMBFILTER_MINPERIOD || st->postfilter_period_old == 0); + celt_assert(st->postfilter_tapset <= 2); + celt_assert(st->postfilter_tapset >= 0); + celt_assert(st->postfilter_tapset_old <= 2); + celt_assert(st->postfilter_tapset_old >= 0); +} +#endif + +int celt_decoder_get_size(int channels) +{ + const CELTMode *mode = opus_custom_mode_create(48000, 960, NULL); + return opus_custom_decoder_get_size(mode, channels); +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_decoder_get_size(const CELTMode *mode, int channels) +{ + int size = sizeof(struct CELTDecoder) + + (channels*(DECODE_BUFFER_SIZE+mode->overlap)-1)*sizeof(celt_sig) + + channels*CELT_LPC_ORDER*sizeof(opus_val16) + + 4*2*mode->nbEBands*sizeof(opus_val16); + return size; +} + +#ifdef CUSTOM_MODES +CELTDecoder *opus_custom_decoder_create(const CELTMode *mode, int channels, int *error) +{ + int ret; + CELTDecoder *st = (CELTDecoder *)opus_alloc(opus_custom_decoder_get_size(mode, channels)); + ret = opus_custom_decoder_init(st, mode, channels); + if (ret != OPUS_OK) + { + opus_custom_decoder_destroy(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} +#endif /* CUSTOM_MODES */ + +int celt_decoder_init(CELTDecoder *st, opus_int32 sampling_rate, int channels) +{ + int ret; + ret = opus_custom_decoder_init(st, opus_custom_mode_create(48000, 960, NULL), channels); + if (ret != OPUS_OK) + return ret; + st->downsample = resampling_factor(sampling_rate); + if (st->downsample==0) + return OPUS_BAD_ARG; + else + return OPUS_OK; +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_decoder_init(CELTDecoder *st, const CELTMode *mode, int channels) +{ + if (channels < 0 || channels > 2) + return OPUS_BAD_ARG; + + if (st==NULL) + return OPUS_ALLOC_FAIL; + + OPUS_CLEAR((char*)st, opus_custom_decoder_get_size(mode, channels)); + + st->mode = mode; + st->overlap = mode->overlap; + st->stream_channels = st->channels = channels; + + st->downsample = 1; + st->start = 0; + st->end = st->mode->effEBands; + st->signalling = 1; +#ifndef DISABLE_UPDATE_DRAFT + st->disable_inv = channels == 1; +#else + st->disable_inv = 0; +#endif + st->arch = opus_select_arch(); + + opus_custom_decoder_ctl(st, OPUS_RESET_STATE); + + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +void opus_custom_decoder_destroy(CELTDecoder *st) +{ + opus_free(st); +} +#endif /* CUSTOM_MODES */ + +#ifndef CUSTOM_MODES +/* Special case for stereo with no downsampling and no accumulation. This is + quite common and we can make it faster by processing both channels in the + same loop, reducing overhead due to the dependency loop in the IIR filter. */ +static void deemphasis_stereo_simple(celt_sig *in[], opus_val16 *pcm, int N, const opus_val16 coef0, + celt_sig *mem) +{ + celt_sig * OPUS_RESTRICT x0; + celt_sig * OPUS_RESTRICT x1; + celt_sig m0, m1; + int j; + x0=in[0]; + x1=in[1]; + m0 = mem[0]; + m1 = mem[1]; + for (j=0;j1) + { + /* Shortcut for the standard (non-custom modes) case */ + for (j=0;joverlap; + nbEBands = mode->nbEBands; + N = mode->shortMdctSize<shortMdctSize; + shift = mode->maxLM; + } else { + B = 1; + NB = mode->shortMdctSize<maxLM-LM; + } + + if (CC==2&&C==1) + { + /* Copying a mono streams to two channels */ + celt_sig *freq2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Store a temporary copy in the output buffer because the IMDCT destroys its input. */ + freq2 = out_syn[1]+overlap/2; + OPUS_COPY(freq2, freq, N); + for (b=0;bmdct, &freq2[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + for (b=0;bmdct, &freq[b], out_syn[1]+NB*b, mode->window, overlap, shift, B, arch); + } else if (CC==1&&C==2) + { + /* Downmixing a stereo stream to mono */ + celt_sig *freq2; + freq2 = out_syn[0]+overlap/2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Use the output buffer as temp array before downmixing. */ + denormalise_bands(mode, X+N, freq2, oldBandE+nbEBands, start, effEnd, M, + downsample, silence); + for (i=0;imdct, &freq[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + } else { + /* Normal case (mono or stereo) */ + c=0; do { + denormalise_bands(mode, X+c*N, freq, oldBandE+c*nbEBands, start, effEnd, M, + downsample, silence); + for (b=0;bmdct, &freq[b], out_syn[c]+NB*b, mode->window, overlap, shift, B, arch); + } while (++cstorage*8; + tell = ec_tell(dec); + logp = isTransient ? 2 : 4; + tf_select_rsv = LM>0 && tell+logp+1<=budget; + budget -= tf_select_rsv; + tf_changed = curr = 0; + for (i=start;i>1, opus_val16 ); + pitch_downsample(decode_mem, lp_pitch_buf, + DECODE_BUFFER_SIZE, C, arch); + pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, + DECODE_BUFFER_SIZE-PLC_PITCH_LAG_MAX, + PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch); + pitch_index = PLC_PITCH_LAG_MAX-pitch_index; + RESTORE_STACK; + return pitch_index; +} + +static void prefilter_and_fold(CELTDecoder * OPUS_RESTRICT st, int N) +{ + int c; + int CC; + int i; + int overlap; + celt_sig *decode_mem[2]; + const OpusCustomMode *mode; + VARDECL(opus_val32, etmp); + mode = st->mode; + overlap = st->overlap; + CC = st->channels; + ALLOC(etmp, overlap, opus_val32); + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + } while (++cpostfilter_period_old, st->postfilter_period, overlap, + -st->postfilter_gain_old, -st->postfilter_gain, + st->postfilter_tapset_old, st->postfilter_tapset, NULL, 0, st->arch); + + /* Simulate TDAC on the concealed audio so that it blends with the + MDCT of the next frame. */ + for (i=0;iwindow[i], etmp[overlap-1-i]) + + MULT16_32_Q15(mode->window[overlap-i-1], etmp[i]); + } + } while (++cfec_read_pos; + tmp_fec_skip = lpcnet->fec_skip; + for (i=0;ifec_read_pos = tmp_read_post; + lpcnet->fec_skip = tmp_fec_skip; +} +#endif + +static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM +#ifdef ENABLE_DEEP_PLC + ,LPCNetPLCState *lpcnet +#endif + ) +{ + int c; + int i; + const int C = st->channels; + celt_sig *decode_mem[2]; + celt_sig *out_syn[2]; + opus_val16 *lpc; + opus_val16 *oldBandE, *oldLogE, *oldLogE2, *backgroundLogE; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + int start; + int loss_duration; + int noise_based; + const opus_int16 *eBands; + SAVE_STACK; + + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N; + } while (++c_decode_mem+(DECODE_BUFFER_SIZE+overlap)*C); + oldBandE = lpc+C*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*nbEBands; + oldLogE2 = oldLogE + 2*nbEBands; + backgroundLogE = oldLogE2 + 2*nbEBands; + + loss_duration = st->loss_duration; + start = st->start; +#ifdef ENABLE_DEEP_PLC + noise_based = start != 0 || (lpcnet->fec_fill_pos == 0 && (st->skip_plc || loss_duration >= 80)); +#else + noise_based = loss_duration >= 40 || start != 0 || st->skip_plc; +#endif + if (noise_based) + { + /* Noise-based PLC/CNG */ + VARDECL(celt_norm, X); + opus_uint32 seed; + int end; + int effEnd; + opus_val16 decay; + end = st->end; + effEnd = IMAX(start, IMIN(end, mode->effEBands)); + + ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */ + c=0; do { + OPUS_MOVE(decode_mem[c], decode_mem[c]+N, + DECODE_BUFFER_SIZE-N+overlap); + } while (++cprefilter_and_fold) { + prefilter_and_fold(st, N); + } + + /* Energy decay */ + decay = loss_duration==0 ? QCONST16(1.5f, DB_SHIFT) : QCONST16(.5f, DB_SHIFT); + c=0; do + { + for (i=start;irng; + for (c=0;c>20); + } + renormalise_vector(X+boffs, blen, Q15ONE, st->arch); + } + } + st->rng = seed; + + celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd, C, C, 0, LM, st->downsample, 0, st->arch); + st->prefilter_and_fold = 0; + /* Skip regular PLC until we get two consecutive packets. */ + st->skip_plc = 1; + } else { + int exc_length; + /* Pitch-based PLC */ + const opus_val16 *window; + opus_val16 *exc; + opus_val16 fade = Q15ONE; + int pitch_index; + VARDECL(opus_val16, _exc); + VARDECL(opus_val16, fir_tmp); + + if (loss_duration == 0) + { +#ifdef ENABLE_DEEP_PLC + if (lpcnet->loaded) update_plc_state(lpcnet, decode_mem, &st->plc_preemphasis_mem, C); +#endif + st->last_pitch_index = pitch_index = celt_plc_pitch_search(decode_mem, C, st->arch); + } else { + pitch_index = st->last_pitch_index; + fade = QCONST16(.8f,15); + } + + /* We want the excitation for 2 pitch periods in order to look for a + decaying signal, but we can't get more than MAX_PERIOD. */ + exc_length = IMIN(2*pitch_index, MAX_PERIOD); + + ALLOC(_exc, MAX_PERIOD+CELT_LPC_ORDER, opus_val16); + ALLOC(fir_tmp, exc_length, opus_val16); + exc = _exc+CELT_LPC_ORDER; + window = mode->window; + c=0; do { + opus_val16 decay; + opus_val16 attenuation; + opus_val32 S1=0; + celt_sig *buf; + int extrapolation_offset; + int extrapolation_len; + int j; + + buf = decode_mem[c]; + for (i=0;iarch); + /* Add a noise floor of -40 dB. */ +#ifdef FIXED_POINT + ac[0] += SHR32(ac[0],13); +#else + ac[0] *= 1.0001f; +#endif + /* Use lag windowing to stabilize the Levinson-Durbin recursion. */ + for (i=1;i<=CELT_LPC_ORDER;i++) + { + /*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/ +#ifdef FIXED_POINT + ac[i] -= MULT16_32_Q15(2*i*i, ac[i]); +#else + ac[i] -= ac[i]*(0.008f*0.008f)*i*i; +#endif + } + _celt_lpc(lpc+c*CELT_LPC_ORDER, ac, CELT_LPC_ORDER); +#ifdef FIXED_POINT + /* For fixed-point, apply bandwidth expansion until we can guarantee that + no overflow can happen in the IIR filter. This means: + 32768*sum(abs(filter)) < 2^31 */ + while (1) { + opus_val16 tmp=Q15ONE; + opus_val32 sum=QCONST16(1., SIG_SHIFT); + for (i=0;iarch); + OPUS_COPY(exc+MAX_PERIOD-exc_length, fir_tmp, exc_length); + } + + /* Check if the waveform is decaying, and if so how fast. + We do this to avoid adding energy when concealing in a segment + with decaying energy. */ + { + opus_val32 E1=1, E2=1; + int decay_length; +#ifdef FIXED_POINT + int shift = IMAX(0,2*celt_zlog2(celt_maxabs16(&exc[MAX_PERIOD-exc_length], exc_length))-20); +#endif + decay_length = exc_length>>1; + for (i=0;i= pitch_index) { + j -= pitch_index; + attenuation = MULT16_16_Q15(attenuation, decay); + } + buf[DECODE_BUFFER_SIZE-N+i] = + SHL32(EXTEND32(MULT16_16_Q15(attenuation, + exc[extrapolation_offset+j])), SIG_SHIFT); + /* Compute the energy of the previously decoded signal whose + excitation we're copying. */ + tmp = SROUND16( + buf[DECODE_BUFFER_SIZE-MAX_PERIOD-N+extrapolation_offset+j], + SIG_SHIFT); + S1 += SHR32(MULT16_16(tmp, tmp), 10); + } + { + opus_val16 lpc_mem[CELT_LPC_ORDER]; + /* Copy the last decoded samples (prior to the overlap region) to + synthesis filter memory so we can have a continuous signal. */ + for (i=0;iarch); +#ifdef FIXED_POINT + for (i=0; i < extrapolation_len; i++) + buf[DECODE_BUFFER_SIZE-N+i] = SATURATE(buf[DECODE_BUFFER_SIZE-N+i], SIG_SAT); +#endif + } + + /* Check if the synthesis energy is higher than expected, which can + happen with the signal changes during our window. If so, + attenuate. */ + { + opus_val32 S2=0; + for (i=0;i SHR32(S2,2))) +#else + /* The float test is written this way to catch NaNs in the output + of the IIR filter at the same time. */ + if (!(S1 > 0.2f*S2)) +#endif + { + for (i=0;iloaded && (st->complexity >= 5 || lpcnet->fec_fill_pos > 0)) { + float overlap_mem; + int samples_needed16k; + celt_sig *buf; + VARDECL(float, buf_copy); + buf = decode_mem[0]; + ALLOC(buf_copy, C*overlap, float); + c=0; do { + OPUS_COPY(buf_copy+c*overlap, &decode_mem[c][DECODE_BUFFER_SIZE-N], overlap); + } while (++cplc_fill = 0; + } + while (st->plc_fill < samples_needed16k) { + lpcnet_plc_conceal(lpcnet, &st->plc_pcm[st->plc_fill]); + st->plc_fill += FRAME_SIZE; + } + /* Resample to 48 kHz. */ + for (i=0;i<(N+overlap)/3;i++) { + int j; + float sum; + for (sum=0, j=0;j<17;j++) sum += 3*st->plc_pcm[i+j]*sinc_filter[3*j]; + buf[DECODE_BUFFER_SIZE-N+3*i] = sum; + for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+2]; + buf[DECODE_BUFFER_SIZE-N+3*i+1] = sum; + for (sum=0, j=0;j<16;j++) sum += 3*st->plc_pcm[i+j+1]*sinc_filter[3*j+1]; + buf[DECODE_BUFFER_SIZE-N+3*i+2] = sum; + } + OPUS_MOVE(st->plc_pcm, &st->plc_pcm[N/3], st->plc_fill-N/3); + st->plc_fill -= N/3; + for (i=0;iplc_preemphasis_mem; + st->plc_preemphasis_mem = tmp; + } + overlap_mem = st->plc_preemphasis_mem; + for (i=0;iprefilter_and_fold = 1; + } + + /* Saturate to soemthing large to avoid wrap-around. */ + st->loss_duration = IMIN(10000, loss_duration+(1<channels; + int LM, M; + int start; + int end; + int effEnd; + int codedBands; + int alloc_trim; + int postfilter_pitch; + opus_val16 postfilter_gain; + int intensity=0; + int dual_stereo=0; + opus_int32 total_bits; + opus_int32 balance; + opus_int32 tell; + int dynalloc_logp; + int postfilter_tapset; + int anti_collapse_rsv; + int anti_collapse_on=0; + int silence; + int C = st->stream_channels; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + const opus_int16 *eBands; + opus_val16 max_background_increase; + ALLOC_STACK; + + VALIDATE_CELT_DECODER(st); + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + start = st->start; + end = st->end; + frame_size *= st->downsample; + + lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+overlap)*CC); + oldBandE = lpc+CC*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*nbEBands; + oldLogE2 = oldLogE + 2*nbEBands; + backgroundLogE = oldLogE2 + 2*nbEBands; + +#ifdef CUSTOM_MODES + if (st->signalling && data!=NULL) + { + int data0=data[0]; + /* Convert "standard mode" to Opus header */ + if (mode->Fs==48000 && mode->shortMdctSize==120) + { + data0 = fromOpus(data0); + if (data0<0) + return OPUS_INVALID_PACKET; + } + st->end = end = IMAX(1, mode->effEBands-2*(data0>>5)); + LM = (data0>>3)&0x3; + C = 1 + ((data0>>2)&0x1); + data++; + len--; + if (LM>mode->maxLM) + return OPUS_INVALID_PACKET; + if (frame_size < mode->shortMdctSize<shortMdctSize<maxLM;LM++) + if (mode->shortMdctSize<mode->maxLM) + return OPUS_BAD_ARG; + } + M=1<1275 || pcm==NULL) + return OPUS_BAD_ARG; + + N = M*mode->shortMdctSize; + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N; + } while (++c mode->effEBands) + effEnd = mode->effEBands; + + if (data == NULL || len<=1) + { + celt_decode_lost(st, N, LM +#ifdef ENABLE_DEEP_PLC + , lpcnet +#endif + ); + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); + RESTORE_STACK; + return frame_size/st->downsample; + } +#ifdef ENABLE_DEEP_PLC + else { + /* FIXME: This is a bit of a hack just to make sure opus_decode_native() knows we're no longer in PLC. */ + if (lpcnet) lpcnet->blend = 0; + } +#endif + + /* Check if there are at least two packets received consecutively before + * turning on the pitch-based PLC */ + if (st->loss_duration == 0) st->skip_plc = 0; + + if (dec == NULL) + { + ec_dec_init(&_dec,(unsigned char*)data,len); + dec = &_dec; + } + + if (C==1) + { + for (i=0;i= total_bits) + silence = 1; + else if (tell==1) + silence = ec_dec_bit_logp(dec, 15); + else + silence = 0; + if (silence) + { + /* Pretend we've read all the remaining bits */ + tell = len*8; + dec->nbits_total+=tell-ec_tell(dec); + } + + postfilter_gain = 0; + postfilter_pitch = 0; + postfilter_tapset = 0; + if (start==0 && tell+16 <= total_bits) + { + if(ec_dec_bit_logp(dec, 1)) + { + int qg, octave; + octave = ec_dec_uint(dec, 6); + postfilter_pitch = (16< 0 && tell+3 <= total_bits) + { + isTransient = ec_dec_bit_logp(dec, 3); + tell = ec_tell(dec); + } + else + isTransient = 0; + + if (isTransient) + shortBlocks = M; + else + shortBlocks = 0; + + /* Decode the global flags (first symbols in the stream) */ + intra_ener = tell+3<=total_bits ? ec_dec_bit_logp(dec, 3) : 0; + /* If recovering from packet loss, make sure we make the energy prediction safe to reduce the + risk of getting loud artifacts. */ + if (!intra_ener && st->loss_duration != 0) { + c=0; do + { + opus_val16 safety = 0; + int missing = IMIN(10, st->loss_duration>>LM); + if (LM==0) safety = QCONST16(1.5f,DB_SHIFT); + else if (LM==1) safety = QCONST16(.5f,DB_SHIFT); + for (i=start;i0) + dynalloc_logp = IMAX(2, dynalloc_logp-1); + } + + ALLOC(fine_quant, nbEBands, int); + alloc_trim = tell+(6<=2&&bits>=((LM+2)<rng, 0, + st->arch, st->disable_inv); + + if (anti_collapse_rsv > 0) + { + anti_collapse_on = ec_dec_bits(dec, 1); + } + + unquant_energy_finalise(mode, start, end, oldBandE, + fine_quant, fine_priority, len*8-ec_tell(dec), dec, C); + + if (anti_collapse_on) + anti_collapse(mode, X, collapse_masks, LM, C, N, + start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng, st->arch); + + if (silence) + { + for (i=0;iprefilter_and_fold) { + prefilter_and_fold(st, N); + } + celt_synthesis(mode, X, out_syn, oldBandE, start, effEnd, + C, CC, isTransient, LM, st->downsample, silence, st->arch); + + c=0; do { + st->postfilter_period=IMAX(st->postfilter_period, COMBFILTER_MINPERIOD); + st->postfilter_period_old=IMAX(st->postfilter_period_old, COMBFILTER_MINPERIOD); + comb_filter(out_syn[c], out_syn[c], st->postfilter_period_old, st->postfilter_period, mode->shortMdctSize, + st->postfilter_gain_old, st->postfilter_gain, st->postfilter_tapset_old, st->postfilter_tapset, + mode->window, overlap, st->arch); + if (LM!=0) + comb_filter(out_syn[c]+mode->shortMdctSize, out_syn[c]+mode->shortMdctSize, st->postfilter_period, postfilter_pitch, N-mode->shortMdctSize, + st->postfilter_gain, postfilter_gain, st->postfilter_tapset, postfilter_tapset, + mode->window, overlap, st->arch); + + } while (++cpostfilter_period_old = st->postfilter_period; + st->postfilter_gain_old = st->postfilter_gain; + st->postfilter_tapset_old = st->postfilter_tapset; + st->postfilter_period = postfilter_pitch; + st->postfilter_gain = postfilter_gain; + st->postfilter_tapset = postfilter_tapset; + if (LM!=0) + { + st->postfilter_period_old = st->postfilter_period; + st->postfilter_gain_old = st->postfilter_gain; + st->postfilter_tapset_old = st->postfilter_tapset; + } + + if (C==1) + OPUS_COPY(&oldBandE[nbEBands], oldBandE, nbEBands); + + if (!isTransient) + { + OPUS_COPY(oldLogE2, oldLogE, 2*nbEBands); + OPUS_COPY(oldLogE, oldBandE, 2*nbEBands); + } else { + for (i=0;i<2*nbEBands;i++) + oldLogE[i] = MIN16(oldLogE[i], oldBandE[i]); + } + /* In normal circumstances, we only allow the noise floor to increase by + up to 2.4 dB/second, but when we're in DTX we give the weight of + all missing packets to the update packet. */ + max_background_increase = IMIN(160, st->loss_duration+M)*QCONST16(0.001f,DB_SHIFT); + for (i=0;i<2*nbEBands;i++) + backgroundLogE[i] = MIN16(backgroundLogE[i] + max_background_increase, oldBandE[i]); + /* In case start or end were to change */ + c=0; do + { + for (i=0;irng = dec->rng; + + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); + st->loss_duration = 0; + st->prefilter_and_fold = 0; + RESTORE_STACK; + if (ec_tell(dec) > 8*len) + return OPUS_INTERNAL_ERROR; + if(ec_get_error(dec)) + st->error = 1; + return frame_size/st->downsample; +} + +int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum) +{ + return celt_decode_with_ec_dred(st, data, len, pcm, frame_size, dec, accum +#ifdef ENABLE_DEEP_PLC + , NULL +#endif + ); +} + +#ifdef CUSTOM_MODES + +#ifdef FIXED_POINT +int opus_custom_decode(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, opus_int16 * OPUS_RESTRICT pcm, int frame_size) +{ + return celt_decode_with_ec(st, data, len, pcm, frame_size, NULL, 0); +} + +#ifndef DISABLE_FLOAT_API +int opus_custom_decode_float(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, float * OPUS_RESTRICT pcm, int frame_size) +{ + int j, ret, C, N; + VARDECL(opus_int16, out); + ALLOC_STACK; + + if (pcm==NULL) + return OPUS_BAD_ARG; + + C = st->channels; + N = frame_size; + + ALLOC(out, C*N, opus_int16); + ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL, 0); + if (ret>0) + for (j=0;jchannels; + N = frame_size; + ALLOC(out, C*N, celt_sig); + + ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL, 0); + + if (ret>0) + for (j=0;j10) + { + goto bad_arg; + } + st->complexity = value; + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->complexity; + } + break; + case CELT_SET_START_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<0 || value>=st->mode->nbEBands) + goto bad_arg; + st->start = value; + } + break; + case CELT_SET_END_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>st->mode->nbEBands) + goto bad_arg; + st->end = value; + } + break; + case CELT_SET_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>2) + goto bad_arg; + st->stream_channels = value; + } + break; + case CELT_GET_AND_CLEAR_ERROR_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value=st->error; + st->error = 0; + } + break; + case OPUS_GET_LOOKAHEAD_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value = st->overlap/st->downsample; + } + break; + case OPUS_RESET_STATE: + { + int i; + opus_val16 *lpc, *oldBandE, *oldLogE, *oldLogE2; + lpc = (opus_val16*)(st->_decode_mem+(DECODE_BUFFER_SIZE+st->overlap)*st->channels); + oldBandE = lpc+st->channels*CELT_LPC_ORDER; + oldLogE = oldBandE + 2*st->mode->nbEBands; + oldLogE2 = oldLogE + 2*st->mode->nbEBands; + OPUS_CLEAR((char*)&st->DECODER_RESET_START, + opus_custom_decoder_get_size(st->mode, st->channels)- + ((char*)&st->DECODER_RESET_START - (char*)st)); + for (i=0;i<2*st->mode->nbEBands;i++) + oldLogE[i]=oldLogE2[i]=-QCONST16(28.f,DB_SHIFT); + st->skip_plc = 1; + } + break; + case OPUS_GET_PITCH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (value==NULL) + goto bad_arg; + *value = st->postfilter_period; + } + break; + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (value==0) + goto bad_arg; + *value=st->mode; + } + break; + case CELT_SET_SIGNALLING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->signalling = value; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 * value = va_arg(ap, opus_uint32 *); + if (value==0) + goto bad_arg; + *value=st->rng; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; + default: + goto bad_request; + } + va_end(ap); + return OPUS_OK; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +bad_request: + va_end(ap); + return OPUS_UNIMPLEMENTED; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_encoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_encoder.c new file mode 100644 index 0000000000..7f32a801c6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_encoder.c @@ -0,0 +1,2639 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2010 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CELT_ENCODER_C + +#include "cpu_support.h" +#include "os_support.h" +#include "mdct.h" +#include +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + + +/** Encoder state + @brief Encoder state + */ +struct OpusCustomEncoder { + const OpusCustomMode *mode; /**< Mode used by the encoder */ + int channels; + int stream_channels; + + int force_intra; + int clip; + int disable_pf; + int complexity; + int upsample; + int start, end; + + opus_int32 bitrate; + int vbr; + int signalling; + int constrained_vbr; /* If zero, VBR can do whatever it likes with the rate */ + int loss_rate; + int lsb_depth; + int lfe; + int disable_inv; + int arch; + + /* Everything beyond this point gets cleared on a reset */ +#define ENCODER_RESET_START rng + + opus_uint32 rng; + int spread_decision; + opus_val32 delayedIntra; + int tonal_average; + int lastCodedBands; + int hf_average; + int tapset_decision; + + int prefilter_period; + opus_val16 prefilter_gain; + int prefilter_tapset; +#ifdef RESYNTH + int prefilter_period_old; + opus_val16 prefilter_gain_old; + int prefilter_tapset_old; +#endif + int consec_transient; + AnalysisInfo analysis; + SILKInfo silk_info; + + opus_val32 preemph_memE[2]; + opus_val32 preemph_memD[2]; + + /* VBR-related parameters */ + opus_int32 vbr_reservoir; + opus_int32 vbr_drift; + opus_int32 vbr_offset; + opus_int32 vbr_count; + opus_val32 overlap_max; + opus_val16 stereo_saving; + int intensity; + opus_val16 *energy_mask; + opus_val16 spec_avg; + +#ifdef RESYNTH + /* +MAX_PERIOD/2 to make space for overlap */ + celt_sig syn_mem[2][2*MAX_PERIOD+MAX_PERIOD/2]; +#endif + + celt_sig in_mem[1]; /* Size = channels*mode->overlap */ + /* celt_sig prefilter_mem[], Size = channels*COMBFILTER_MAXPERIOD */ + /* opus_val16 oldBandE[], Size = channels*mode->nbEBands */ + /* opus_val16 oldLogE[], Size = channels*mode->nbEBands */ + /* opus_val16 oldLogE2[], Size = channels*mode->nbEBands */ + /* opus_val16 energyError[], Size = channels*mode->nbEBands */ +}; + +int celt_encoder_get_size(int channels) +{ + CELTMode *mode = opus_custom_mode_create(48000, 960, NULL); + return opus_custom_encoder_get_size(mode, channels); +} + +OPUS_CUSTOM_NOSTATIC int opus_custom_encoder_get_size(const CELTMode *mode, int channels) +{ + int size = sizeof(struct CELTEncoder) + + (channels*mode->overlap-1)*sizeof(celt_sig) /* celt_sig in_mem[channels*mode->overlap]; */ + + channels*COMBFILTER_MAXPERIOD*sizeof(celt_sig) /* celt_sig prefilter_mem[channels*COMBFILTER_MAXPERIOD]; */ + + 4*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */ + /* opus_val16 oldLogE[channels*mode->nbEBands]; */ + /* opus_val16 oldLogE2[channels*mode->nbEBands]; */ + /* opus_val16 energyError[channels*mode->nbEBands]; */ + return size; +} + +#ifdef CUSTOM_MODES +CELTEncoder *opus_custom_encoder_create(const CELTMode *mode, int channels, int *error) +{ + int ret; + CELTEncoder *st = (CELTEncoder *)opus_alloc(opus_custom_encoder_get_size(mode, channels)); + /* init will handle the NULL case */ + ret = opus_custom_encoder_init(st, mode, channels); + if (ret != OPUS_OK) + { + opus_custom_encoder_destroy(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} +#endif /* CUSTOM_MODES */ + +static int opus_custom_encoder_init_arch(CELTEncoder *st, const CELTMode *mode, + int channels, int arch) +{ + if (channels < 0 || channels > 2) + return OPUS_BAD_ARG; + + if (st==NULL || mode==NULL) + return OPUS_ALLOC_FAIL; + + OPUS_CLEAR((char*)st, opus_custom_encoder_get_size(mode, channels)); + + st->mode = mode; + st->stream_channels = st->channels = channels; + + st->upsample = 1; + st->start = 0; + st->end = st->mode->effEBands; + st->signalling = 1; + st->arch = arch; + + st->constrained_vbr = 1; + st->clip = 1; + + st->bitrate = OPUS_BITRATE_MAX; + st->vbr = 0; + st->force_intra = 0; + st->complexity = 5; + st->lsb_depth=24; + + opus_custom_encoder_ctl(st, OPUS_RESET_STATE); + + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +int opus_custom_encoder_init(CELTEncoder *st, const CELTMode *mode, int channels) +{ + return opus_custom_encoder_init_arch(st, mode, channels, opus_select_arch()); +} +#endif + +int celt_encoder_init(CELTEncoder *st, opus_int32 sampling_rate, int channels, + int arch) +{ + int ret; + ret = opus_custom_encoder_init_arch(st, + opus_custom_mode_create(48000, 960, NULL), channels, arch); + if (ret != OPUS_OK) + return ret; + st->upsample = resampling_factor(sampling_rate); + return OPUS_OK; +} + +#ifdef CUSTOM_MODES +void opus_custom_encoder_destroy(CELTEncoder *st) +{ + opus_free(st); +} +#endif /* CUSTOM_MODES */ + + +static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int C, + opus_val16 *tf_estimate, int *tf_chan, int allow_weak_transients, + int *weak_transient) +{ + int i; + VARDECL(opus_val16, tmp); + opus_val32 mem0,mem1; + int is_transient = 0; + opus_int32 mask_metric = 0; + int c; + opus_val16 tf_max; + int len2; + /* Forward masking: 6.7 dB/ms. */ +#ifdef FIXED_POINT + int forward_shift = 4; +#else + opus_val16 forward_decay = QCONST16(.0625f,15); +#endif + /* Table of 6*64/x, trained on real data to minimize the average error */ + static const unsigned char inv_table[128] = { + 255,255,156,110, 86, 70, 59, 51, 45, 40, 37, 33, 31, 28, 26, 25, + 23, 22, 21, 20, 19, 18, 17, 16, 16, 15, 15, 14, 13, 13, 12, 12, + 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 9, 9, 8, 8, + 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, + }; + SAVE_STACK; + ALLOC(tmp, len, opus_val16); + + *weak_transient = 0; + /* For lower bitrates, let's be more conservative and have a forward masking + decay of 3.3 dB/ms. This avoids having to code transients at very low + bitrate (mostly for hybrid), which can result in unstable energy and/or + partial collapse. */ + if (allow_weak_transients) + { +#ifdef FIXED_POINT + forward_shift = 5; +#else + forward_decay = QCONST16(.03125f,15); +#endif + } + len2=len/2; + for (c=0;c=0;i--) + { + /* Backward masking: 13.9 dB/ms. */ +#ifdef FIXED_POINT + /* FIXME: Use PSHR16() instead */ + tmp[i] = mem0 + PSHR32(tmp[i]-mem0,3); + mem0 = tmp[i]; + maxE = MAX16(maxE, mem0); +#else + mem0 = tmp[i] + 0.875f*mem0; + tmp[i] = 0.125f*mem0; + maxE = MAX16(maxE, 0.125f*mem0); +#endif + } + /*for (i=0;i>1))); +#else + mean = celt_sqrt(mean * maxE*.5*len2); +#endif + /* Inverse of the mean energy in Q15+6 */ + norm = SHL32(EXTEND32(len2),6+14)/ADD32(EPSILON,SHR32(mean,1)); + /* Compute harmonic mean discarding the unreliable boundaries + The data is smooth, so we only take 1/4th of the samples */ + unmask=0; + /* We should never see NaNs here. If we find any, then something really bad happened and we better abort + before it does any damage later on. If these asserts are disabled (no hardening), then the table + lookup a few lines below (id = ...) is likely to crash dur to an out-of-bounds read. DO NOT FIX + that crash on NaN since it could result in a worse issue later on. */ + celt_assert(!celt_isnan(tmp[0])); + celt_assert(!celt_isnan(norm)); + for (i=12;imask_metric) + { + *tf_chan = c; + mask_metric = unmask; + } + } + is_transient = mask_metric>200; + /* For low bitrates, define "weak transients" that need to be + handled differently to avoid partial collapse. */ + if (allow_weak_transients && is_transient && mask_metric<600) { + is_transient = 0; + *weak_transient = 1; + } + /* Arbitrary metric for VBR boost */ + tf_max = MAX16(0,celt_sqrt(27*mask_metric)-42); + /* *tf_estimate = 1 + MIN16(1, sqrt(MAX16(0, tf_max-30))/20); */ + *tf_estimate = celt_sqrt(MAX32(0, SHL32(MULT16_16(QCONST16(0.0069,14),MIN16(163,tf_max)),14)-QCONST32(0.139,28))); + /*printf("%d %f\n", tf_max, mask_metric);*/ + RESTORE_STACK; +#ifdef FUZZING + is_transient = rand()&0x1; +#endif + /*printf("%d %f %d\n", is_transient, (float)*tf_estimate, tf_max);*/ + return is_transient; +} + +/* Looks for sudden increases of energy to decide whether we need to patch + the transient decision */ +static int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, + int start, int end, int C) +{ + int i, c; + opus_val32 mean_diff=0; + opus_val16 spread_old[26]; + /* Apply an aggressive (-6 dB/Bark) spreading function to the old frame to + avoid false detection caused by irrelevant bands */ + if (C==1) + { + spread_old[start] = oldE[start]; + for (i=start+1;i=start;i--) + spread_old[i] = MAX16(spread_old[i], spread_old[i+1]-QCONST16(1.0f, DB_SHIFT)); + /* Compute mean increase */ + c=0; do { + for (i=IMAX(2,start);i QCONST16(1.f, DB_SHIFT); +} + +/** Apply window and compute the MDCT for all sub-frames and + all channels in a frame */ +static void compute_mdcts(const CELTMode *mode, int shortBlocks, celt_sig * OPUS_RESTRICT in, + celt_sig * OPUS_RESTRICT out, int C, int CC, int LM, int upsample, + int arch) +{ + const int overlap = mode->overlap; + int N; + int B; + int shift; + int i, b, c; + if (shortBlocks) + { + B = shortBlocks; + N = mode->shortMdctSize; + shift = mode->maxLM; + } else { + B = 1; + N = mode->shortMdctSize<maxLM-LM; + } + c=0; do { + for (b=0;bmdct, in+c*(B*N+overlap)+b*N, + &out[b+c*N*B], mode->window, overlap, shift, B, + arch); + } + } while (++ceBands[len]-m->eBands[len-1])<eBands[len]-m->eBands[len-1])<eBands[i+1]-m->eBands[i])<eBands[i+1]-m->eBands[i])==1; + OPUS_COPY(tmp, &X[tf_chan*N0 + (m->eBands[i]<eBands[i]<>LM, 1<>k, 1<=0;i--) + { + if (tf_res[i+1] == 1) + tf_res[i] = path1[i+1]; + else + tf_res[i] = path0[i+1]; + } + /*printf("%d %f\n", *tf_sum, tf_estimate);*/ + RESTORE_STACK; +#ifdef FUZZING + tf_select = rand()&0x1; + tf_res[0] = rand()&0x1; + for (i=1;istorage*8; + tell = ec_tell(enc); + logp = isTransient ? 2 : 4; + /* Reserve space to code the tf_select decision. */ + tf_select_rsv = LM>0 && tell+logp+1 <= budget; + budget -= tf_select_rsv; + curr = tf_changed = 0; + for (i=start;i> 10; + trim = QCONST16(4.f, 8) + QCONST16(1.f/16.f, 8)*frac; + } + if (C==2) + { + opus_val16 sum = 0; /* Q10 */ + opus_val16 minXC; /* Q10 */ + /* Compute inter-channel correlation for low frequencies */ + for (i=0;i<8;i++) + { + opus_val32 partial; + partial = celt_inner_prod(&X[m->eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])<eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])<nbEBands]*(opus_int32)(2+2*i-end); + } + } while (++cvalid) + { + trim -= MAX16(-QCONST16(2.f, 8), MIN16(QCONST16(2.f, 8), + (opus_val16)(QCONST16(2.f, 8)*(analysis->tonality_slope+.05f)))); + } +#else + (void)analysis; +#endif + +#ifdef FIXED_POINT + trim_index = PSHR32(trim, 8); +#else + trim_index = (int)floor(.5f+trim); +#endif + trim_index = IMAX(0, IMIN(10, trim_index)); + /*printf("%d\n", trim_index);*/ +#ifdef FUZZING + trim_index = rand()%11; +#endif + return trim_index; +} + +static int stereo_analysis(const CELTMode *m, const celt_norm *X, + int LM, int N0) +{ + int i; + int thetas; + opus_val32 sumLR = EPSILON, sumMS = EPSILON; + + /* Use the L1 norm to model the entropy of the L/R signal vs the M/S signal */ + for (i=0;i<13;i++) + { + int j; + for (j=m->eBands[i]<eBands[i+1]<eBands[13]<<(LM+1))+thetas, sumMS) + > MULT16_32_Q15(m->eBands[13]<<(LM+1), sumLR); +} + +#define MSWAP(a,b) do {opus_val16 tmp = a;a=b;b=tmp;} while(0) +static opus_val16 median_of_5(const opus_val16 *x) +{ + opus_val16 t0, t1, t2, t3, t4; + t2 = x[2]; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + if (x[3] > x[4]) + { + t3 = x[4]; + t4 = x[3]; + } else { + t3 = x[3]; + t4 = x[4]; + } + if (t0 > t3) + { + MSWAP(t0, t3); + MSWAP(t1, t4); + } + if (t2 > t1) + { + if (t1 < t3) + return MIN16(t2, t3); + else + return MIN16(t4, t1); + } else { + if (t2 < t3) + return MIN16(t1, t3); + else + return MIN16(t2, t4); + } +} + +static opus_val16 median_of_3(const opus_val16 *x) +{ + opus_val16 t0, t1, t2; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + t2 = x[2]; + if (t1 < t2) + return t1; + else if (t0 < t2) + return t2; + else + return t0; +} + +static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 *bandLogE2, const opus_val16 *oldBandE, + int nbEBands, int start, int end, int C, int *offsets, int lsb_depth, const opus_int16 *logN, + int isTransient, int vbr, int constrained_vbr, const opus_int16 *eBands, int LM, + int effectiveBytes, opus_int32 *tot_boost_, int lfe, opus_val16 *surround_dynalloc, + AnalysisInfo *analysis, int *importance, int *spread_weight) +{ + int i, c; + opus_int32 tot_boost=0; + opus_val16 maxDepth; + VARDECL(opus_val16, follower); + VARDECL(opus_val16, noise_floor); + VARDECL(opus_val16, bandLogE3); + SAVE_STACK; + ALLOC(follower, C*nbEBands, opus_val16); + ALLOC(noise_floor, C*nbEBands, opus_val16); + ALLOC(bandLogE3, nbEBands, opus_val16); + OPUS_CLEAR(offsets, nbEBands); + /* Dynamic allocation code */ + maxDepth=-QCONST16(31.9f, DB_SHIFT); + for (i=0;i=0;i--) + mask[i] = MAX16(mask[i], mask[i+1] - QCONST16(3.f, DB_SHIFT)); + for (i=0;i> shift; + } + /*for (i=0;i= (30 + 5*LM) && !lfe) + { + int last=0; + c=0;do + { + opus_val16 offset; + opus_val16 tmp; + opus_val16 *f; + OPUS_COPY(bandLogE3, &bandLogE2[c*nbEBands], end); + if (LM==0) { + /* For 2.5 ms frames, the first 8 bands have just one bin, so the + energy is highly unreliable (high variance). For that reason, + we take the max with the previous energy so that at least 2 bins + are getting used. */ + for (i=0;i bandLogE3[i-1]+QCONST16(.5f,DB_SHIFT)) + last=i; + f[i] = MIN16(f[i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE3[i]); + } + for (i=last-1;i>=0;i--) + f[i] = MIN16(f[i], MIN16(f[i+1]+QCONST16(2.f,DB_SHIFT), bandLogE3[i])); + + /* Combine with a median filter to avoid dynalloc triggering unnecessarily. + The "offset" value controls how conservative we are -- a higher offset + reduces the impact of the median filter and makes dynalloc use more bits. */ + offset = QCONST16(1.f, DB_SHIFT); + for (i=2;i=12) + follower[i] = HALF16(follower[i]); + } +#ifdef DISABLE_FLOAT_API + (void)analysis; +#else + if (analysis->valid) + { + for (i=start;ileak_boost[i]; + } +#endif + for (i=start;i 48) { + boost = (int)SHR32(EXTEND32(follower[i])*8,DB_SHIFT); + boost_bits = (boost*width<>BITRES>>3 > 2*effectiveBytes/3) + { + opus_int32 cap = ((2*effectiveBytes/3)<mode; + overlap = mode->overlap; + ALLOC(_pre, CC*(N+COMBFILTER_MAXPERIOD), celt_sig); + + pre[0] = _pre; + pre[1] = _pre + (N+COMBFILTER_MAXPERIOD); + + + c=0; do { + OPUS_COPY(pre[c], prefilter_mem+c*COMBFILTER_MAXPERIOD, COMBFILTER_MAXPERIOD); + OPUS_COPY(pre[c]+COMBFILTER_MAXPERIOD, in+c*(N+overlap)+overlap, N); + } while (++c>1, opus_val16); + + pitch_downsample(pre, pitch_buf, COMBFILTER_MAXPERIOD+N, CC, st->arch); + /* Don't search for the fir last 1.5 octave of the range because + there's too many false-positives due to short-term correlation */ + pitch_search(pitch_buf+(COMBFILTER_MAXPERIOD>>1), pitch_buf, N, + COMBFILTER_MAXPERIOD-3*COMBFILTER_MINPERIOD, &pitch_index, + st->arch); + pitch_index = COMBFILTER_MAXPERIOD-pitch_index; + + gain1 = remove_doubling(pitch_buf, COMBFILTER_MAXPERIOD, COMBFILTER_MINPERIOD, + N, &pitch_index, st->prefilter_period, st->prefilter_gain, st->arch); + if (pitch_index > COMBFILTER_MAXPERIOD-2) + pitch_index = COMBFILTER_MAXPERIOD-2; + gain1 = MULT16_16_Q15(QCONST16(.7f,15),gain1); + /*printf("%d %d %f %f\n", pitch_change, pitch_index, gain1, st->analysis.tonality);*/ + if (st->loss_rate>2) + gain1 = HALF32(gain1); + if (st->loss_rate>4) + gain1 = HALF32(gain1); + if (st->loss_rate>8) + gain1 = 0; + } else { + gain1 = 0; + pitch_index = COMBFILTER_MINPERIOD; + } +#ifndef DISABLE_FLOAT_API + if (analysis->valid) + gain1 = (opus_val16)(gain1 * analysis->max_pitch_ratio); +#else + (void)analysis; +#endif + /* Gain threshold for enabling the prefilter/postfilter */ + pf_threshold = QCONST16(.2f,15); + + /* Adjusting the threshold based on rate and continuity */ + if (abs(pitch_index-st->prefilter_period)*10>pitch_index) + pf_threshold += QCONST16(.2f,15); + if (nbAvailableBytes<25) + pf_threshold += QCONST16(.1f,15); + if (nbAvailableBytes<35) + pf_threshold += QCONST16(.1f,15); + if (st->prefilter_gain > QCONST16(.4f,15)) + pf_threshold -= QCONST16(.1f,15); + if (st->prefilter_gain > QCONST16(.55f,15)) + pf_threshold -= QCONST16(.1f,15); + + /* Hard threshold at 0.2 */ + pf_threshold = MAX16(pf_threshold, QCONST16(.2f,15)); + if (gain1prefilter_gain)prefilter_gain; + +#ifdef FIXED_POINT + qg = ((gain1+1536)>>10)/3-1; +#else + qg = (int)floor(.5f+gain1*32/3)-1; +#endif + qg = IMAX(0, IMIN(7, qg)); + gain1 = QCONST16(0.09375f,15)*(qg+1); + pf_on = 1; + } + /*printf("%d %f\n", pitch_index, gain1);*/ + + c=0; do { + int offset = mode->shortMdctSize-overlap; + st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); + OPUS_COPY(in+c*(N+overlap), st->in_mem+c*(overlap), overlap); + if (offset) + comb_filter(in+c*(N+overlap)+overlap, pre[c]+COMBFILTER_MAXPERIOD, + st->prefilter_period, st->prefilter_period, offset, -st->prefilter_gain, -st->prefilter_gain, + st->prefilter_tapset, st->prefilter_tapset, NULL, 0, st->arch); + + comb_filter(in+c*(N+overlap)+overlap+offset, pre[c]+COMBFILTER_MAXPERIOD+offset, + st->prefilter_period, pitch_index, N-offset, -st->prefilter_gain, -gain1, + st->prefilter_tapset, prefilter_tapset, mode->window, overlap, st->arch); + OPUS_COPY(st->in_mem+c*(overlap), in+c*(N+overlap)+N, overlap); + + if (N>COMBFILTER_MAXPERIOD) + { + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD, pre[c]+N, COMBFILTER_MAXPERIOD); + } else { + OPUS_MOVE(prefilter_mem+c*COMBFILTER_MAXPERIOD, prefilter_mem+c*COMBFILTER_MAXPERIOD+N, COMBFILTER_MAXPERIOD-N); + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD+COMBFILTER_MAXPERIOD-N, pre[c]+COMBFILTER_MAXPERIOD, N); + } + } while (++cnbEBands; + eBands = mode->eBands; + + coded_bands = lastCodedBands ? lastCodedBands : nbEBands; + coded_bins = eBands[coded_bands]<analysis.activity, st->analysis.tonality, tf_estimate, st->stereo_saving, tot_boost, coded_bands);*/ +#ifndef DISABLE_FLOAT_API + if (analysis->valid && analysis->activity<.4) + target -= (opus_int32)((coded_bins<activity)); +#endif + /* Stereo savings */ + if (C==2) + { + int coded_stereo_bands; + int coded_stereo_dof; + opus_val16 max_frac; + coded_stereo_bands = IMIN(intensity, coded_bands); + coded_stereo_dof = (eBands[coded_stereo_bands]<valid && !lfe) + { + opus_int32 tonal_target; + float tonal; + + /* Tonality boost (compensating for the average). */ + tonal = MAX16(0.f,analysis->tonality-.15f)-0.12f; + tonal_target = target + (opus_int32)((coded_bins<tonality, tonal);*/ + target = tonal_target; + } +#else + (void)analysis; + (void)pitch_change; +#endif + + if (has_surround_mask&&!lfe) + { + opus_int32 surround_target = target + (opus_int32)SHR32(MULT16_16(surround_masking,coded_bins<end, st->intensity, surround_target, target, st->bitrate);*/ + target = IMAX(target/4, surround_target); + } + + { + opus_int32 floor_depth; + int bins; + bins = eBands[nbEBands-2]<>2); + target = IMIN(target, floor_depth); + /*printf("%f %d\n", maxDepth, floor_depth);*/ + } + + /* Make VBR less aggressive for constrained VBR because we can't keep a higher bitrate + for long. Needs tuning. */ + if ((!has_surround_mask||lfe) && constrained_vbr) + { + target = base_target + (opus_int32)MULT16_32_Q15(QCONST16(0.67f, 15), target-base_target); + } + + if (!has_surround_mask && tf_estimate < QCONST16(.2f, 14)) + { + opus_val16 amount; + opus_val16 tvbr_factor; + amount = MULT16_16_Q15(QCONST16(.0000031f, 30), IMAX(0, IMIN(32000, 96000-bitrate))); + tvbr_factor = SHR32(MULT16_16(temporal_vbr, amount), DB_SHIFT); + target += (opus_int32)MULT16_32_Q15(tvbr_factor, target); + } + + /* Don't allow more than doubling the rate */ + target = IMIN(2*base_target, target); + + return target; +} + +int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes, ec_enc *enc) +{ + int i, c, N; + opus_int32 bits; + ec_enc _enc; + VARDECL(celt_sig, in); + VARDECL(celt_sig, freq); + VARDECL(celt_norm, X); + VARDECL(celt_ener, bandE); + VARDECL(opus_val16, bandLogE); + VARDECL(opus_val16, bandLogE2); + VARDECL(int, fine_quant); + VARDECL(opus_val16, error); + VARDECL(int, pulses); + VARDECL(int, cap); + VARDECL(int, offsets); + VARDECL(int, importance); + VARDECL(int, spread_weight); + VARDECL(int, fine_priority); + VARDECL(int, tf_res); + VARDECL(unsigned char, collapse_masks); + celt_sig *prefilter_mem; + opus_val16 *oldBandE, *oldLogE, *oldLogE2, *energyError; + int shortBlocks=0; + int isTransient=0; + const int CC = st->channels; + const int C = st->stream_channels; + int LM, M; + int tf_select; + int nbFilledBytes, nbAvailableBytes; + int start; + int end; + int effEnd; + int codedBands; + int alloc_trim; + int pitch_index=COMBFILTER_MINPERIOD; + opus_val16 gain1 = 0; + int dual_stereo=0; + int effectiveBytes; + int dynalloc_logp; + opus_int32 vbr_rate; + opus_int32 total_bits; + opus_int32 total_boost; + opus_int32 balance; + opus_int32 tell; + opus_int32 tell0_frac; + int prefilter_tapset=0; + int pf_on; + int anti_collapse_rsv; + int anti_collapse_on=0; + int silence=0; + int tf_chan = 0; + opus_val16 tf_estimate; + int pitch_change=0; + opus_int32 tot_boost; + opus_val32 sample_max; + opus_val16 maxDepth; + const OpusCustomMode *mode; + int nbEBands; + int overlap; + const opus_int16 *eBands; + int secondMdct; + int signalBandwidth; + int transient_got_disabled=0; + opus_val16 surround_masking=0; + opus_val16 temporal_vbr=0; + opus_val16 surround_trim = 0; + opus_int32 equiv_rate; + int hybrid; + int weak_transient = 0; + int enable_tf_analysis; + VARDECL(opus_val16, surround_dynalloc); + ALLOC_STACK; + + mode = st->mode; + nbEBands = mode->nbEBands; + overlap = mode->overlap; + eBands = mode->eBands; + start = st->start; + end = st->end; + hybrid = start != 0; + tf_estimate = 0; + if (nbCompressedBytes<2 || pcm==NULL) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + frame_size *= st->upsample; + for (LM=0;LM<=mode->maxLM;LM++) + if (mode->shortMdctSize<mode->maxLM) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + M=1<shortMdctSize; + + prefilter_mem = st->in_mem+CC*(overlap); + oldBandE = (opus_val16*)(st->in_mem+CC*(overlap+COMBFILTER_MAXPERIOD)); + oldLogE = oldBandE + CC*nbEBands; + oldLogE2 = oldLogE + CC*nbEBands; + energyError = oldLogE2 + CC*nbEBands; + + if (enc==NULL) + { + tell0_frac=tell=1; + nbFilledBytes=0; + } else { + tell0_frac=ec_tell_frac(enc); + tell=ec_tell(enc); + nbFilledBytes=(tell+4)>>3; + } + +#ifdef CUSTOM_MODES + if (st->signalling && enc==NULL) + { + int tmp = (mode->effEBands-end)>>1; + end = st->end = IMAX(1, mode->effEBands-tmp); + compressed[0] = tmp<<5; + compressed[0] |= LM<<3; + compressed[0] |= (C==2)<<2; + /* Convert "standard mode" to Opus header */ + if (mode->Fs==48000 && mode->shortMdctSize==120) + { + int c0 = toOpus(compressed[0]); + if (c0<0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + compressed[0] = c0; + } + compressed++; + nbCompressedBytes--; + } +#else + celt_assert(st->signalling==0); +#endif + + /* Can't produce more than 1275 output bytes */ + nbCompressedBytes = IMIN(nbCompressedBytes,1275); + nbAvailableBytes = nbCompressedBytes - nbFilledBytes; + + if (st->vbr && st->bitrate!=OPUS_BITRATE_MAX) + { + opus_int32 den=mode->Fs>>BITRES; + vbr_rate=(st->bitrate*frame_size+(den>>1))/den; +#ifdef CUSTOM_MODES + if (st->signalling) + vbr_rate -= 8<>(3+BITRES); + } else { + opus_int32 tmp; + vbr_rate = 0; + tmp = st->bitrate*frame_size; + if (tell>1) + tmp += tell*mode->Fs; + if (st->bitrate!=OPUS_BITRATE_MAX) + { + nbCompressedBytes = IMAX(2, IMIN(nbCompressedBytes, + (tmp+4*mode->Fs)/(8*mode->Fs)-!!st->signalling)); + ec_enc_shrink(enc, nbCompressedBytes); + } + effectiveBytes = nbCompressedBytes - nbFilledBytes; + } + equiv_rate = ((opus_int32)nbCompressedBytes*8*50 << (3-LM)) - (40*C+20)*((400>>LM) - 50); + if (st->bitrate != OPUS_BITRATE_MAX) + equiv_rate = IMIN(equiv_rate, st->bitrate - (40*C+20)*((400>>LM) - 50)); + + if (enc==NULL) + { + ec_enc_init(&_enc, compressed, nbCompressedBytes); + enc = &_enc; + } + + if (vbr_rate>0) + { + /* Computes the max bit-rate allowed in VBR mode to avoid violating the + target rate and buffering. + We must do this up front so that bust-prevention logic triggers + correctly if we don't have enough bits. */ + if (st->constrained_vbr) + { + opus_int32 vbr_bound; + opus_int32 max_allowed; + /* We could use any multiple of vbr_rate as bound (depending on the + delay). + This is clamped to ensure we use at least two bytes if the encoder + was entirely empty, but to allow 0 in hybrid mode. */ + vbr_bound = vbr_rate; + max_allowed = IMIN(IMAX(tell==1?2:0, + (vbr_rate+vbr_bound-st->vbr_reservoir)>>(BITRES+3)), + nbAvailableBytes); + if(max_allowed < nbAvailableBytes) + { + nbCompressedBytes = nbFilledBytes+max_allowed; + nbAvailableBytes = max_allowed; + ec_enc_shrink(enc, nbCompressedBytes); + } + } + } + total_bits = nbCompressedBytes*8; + + effEnd = end; + if (effEnd > mode->effEBands) + effEnd = mode->effEBands; + + ALLOC(in, CC*(N+overlap), celt_sig); + + sample_max=MAX32(st->overlap_max, celt_maxabs16(pcm, C*(N-overlap)/st->upsample)); + st->overlap_max=celt_maxabs16(pcm+C*(N-overlap)/st->upsample, C*overlap/st->upsample); + sample_max=MAX32(sample_max, st->overlap_max); +#ifdef FIXED_POINT + silence = (sample_max==0); +#else + silence = (sample_max <= (opus_val16)1/(1<lsb_depth)); +#endif +#ifdef FUZZING + if ((rand()&0x3F)==0) + silence = 1; +#endif + if (tell==1) + ec_enc_bit_logp(enc, silence, 15); + else + silence=0; + if (silence) + { + /*In VBR mode there is no need to send more than the minimum. */ + if (vbr_rate>0) + { + effectiveBytes=nbCompressedBytes=IMIN(nbCompressedBytes, nbFilledBytes+2); + total_bits=nbCompressedBytes*8; + nbAvailableBytes=2; + ec_enc_shrink(enc, nbCompressedBytes); + } + /* Pretend we've filled all the remaining bits with zeros + (that's what the initialiser did anyway) */ + tell = nbCompressedBytes*8; + enc->nbits_total+=tell-ec_tell(enc); + } + c=0; do { + int need_clip=0; +#ifndef FIXED_POINT + need_clip = st->clip && sample_max>65536.f; +#endif + celt_preemphasis(pcm+c, in+c*(N+overlap)+overlap, N, CC, st->upsample, + mode->preemph, st->preemph_memE+c, need_clip); + } while (++clfe&&nbAvailableBytes>3) || nbAvailableBytes>12*C) && !hybrid && !silence && !st->disable_pf + && st->complexity >= 5; + + prefilter_tapset = st->tapset_decision; + pf_on = run_prefilter(st, in, prefilter_mem, CC, N, prefilter_tapset, &pitch_index, &gain1, &qg, enabled, nbAvailableBytes, &st->analysis); + if ((gain1 > QCONST16(.4f,15) || st->prefilter_gain > QCONST16(.4f,15)) && (!st->analysis.valid || st->analysis.tonality > .3) + && (pitch_index > 1.26*st->prefilter_period || pitch_index < .79*st->prefilter_period)) + pitch_change = 1; + if (pf_on==0) + { + if(!hybrid && tell+16<=total_bits) + ec_enc_bit_logp(enc, 0, 1); + } else { + /*This block is not gated by a total bits check only because + of the nbAvailableBytes check above.*/ + int octave; + ec_enc_bit_logp(enc, 1, 1); + pitch_index += 1; + octave = EC_ILOG(pitch_index)-5; + ec_enc_uint(enc, octave, 6); + ec_enc_bits(enc, pitch_index-(16<complexity >= 1 && !st->lfe) + { + /* Reduces the likelihood of energy instability on fricatives at low bitrate + in hybrid mode. It seems like we still want to have real transients on vowels + though (small SILK quantization offset value). */ + int allow_weak_transients = hybrid && effectiveBytes<15 && st->silk_info.signalType != 2; + isTransient = transient_analysis(in, N+overlap, CC, + &tf_estimate, &tf_chan, allow_weak_transients, &weak_transient); + } + if (LM>0 && ec_tell(enc)+3<=total_bits) + { + if (isTransient) + shortBlocks = M; + } else { + isTransient = 0; + transient_got_disabled=1; + } + + ALLOC(freq, CC*N, celt_sig); /**< Interleaved signal MDCTs */ + ALLOC(bandE,nbEBands*CC, celt_ener); + ALLOC(bandLogE,nbEBands*CC, opus_val16); + + secondMdct = shortBlocks && st->complexity>=8; + ALLOC(bandLogE2, C*nbEBands, opus_val16); + if (secondMdct) + { + compute_mdcts(mode, 0, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + amp2Log2(mode, effEnd, end, bandE, bandLogE2, C); + for (c=0;cupsample, st->arch); + /* This should catch any NaN in the CELT input. Since we're not supposed to see any (they're filtered + at the Opus layer), just abort. */ + celt_assert(!celt_isnan(freq[0]) && (C==1 || !celt_isnan(freq[N]))); + if (CC==2&&C==1) + tf_chan = 0; + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + + if (st->lfe) + { + for (i=2;ienergy_mask&&!st->lfe) + { + int mask_end; + int midband; + int count_dynalloc; + opus_val32 mask_avg=0; + opus_val32 diff=0; + int count=0; + mask_end = IMAX(2,st->lastCodedBands); + for (c=0;cenergy_mask[nbEBands*c+i], + QCONST16(.25f, DB_SHIFT)), -QCONST16(2.0f, DB_SHIFT)); + if (mask > 0) + mask = HALF16(mask); + mask_avg += MULT16_16(mask, eBands[i+1]-eBands[i]); + count += eBands[i+1]-eBands[i]; + diff += MULT16_16(mask, 1+2*i-mask_end); + } + } + celt_assert(count>0); + mask_avg = DIV32_16(mask_avg,count); + mask_avg += QCONST16(.2f, DB_SHIFT); + diff = diff*6/(C*(mask_end-1)*(mask_end+1)*mask_end); + /* Again, being conservative */ + diff = HALF32(diff); + diff = MAX32(MIN32(diff, QCONST32(.031f, DB_SHIFT)), -QCONST32(.031f, DB_SHIFT)); + /* Find the band that's in the middle of the coded spectrum */ + for (midband=0;eBands[midband+1] < eBands[mask_end]/2;midband++); + count_dynalloc=0; + for(i=0;ienergy_mask[i], st->energy_mask[nbEBands+i]); + else + unmask = st->energy_mask[i]; + unmask = MIN16(unmask, QCONST16(.0f, DB_SHIFT)); + unmask -= lin; + if (unmask > QCONST16(.25f, DB_SHIFT)) + { + surround_dynalloc[i] = unmask - QCONST16(.25f, DB_SHIFT); + count_dynalloc++; + } + } + if (count_dynalloc>=3) + { + /* If we need dynalloc in many bands, it's probably because our + initial masking rate was too low. */ + mask_avg += QCONST16(.25f, DB_SHIFT); + if (mask_avg>0) + { + /* Something went really wrong in the original calculations, + disabling masking. */ + mask_avg = 0; + diff = 0; + OPUS_CLEAR(surround_dynalloc, mask_end); + } else { + for(i=0;ilfe) + { + opus_val16 follow=-QCONST16(10.0f,DB_SHIFT); + opus_val32 frame_avg=0; + opus_val16 offset = shortBlocks?HALF16(SHL16(LM, DB_SHIFT)):0; + for(i=start;ispec_avg); + temporal_vbr = MIN16(QCONST16(3.f, DB_SHIFT), MAX16(-QCONST16(1.5f, DB_SHIFT), temporal_vbr)); + st->spec_avg += MULT16_16_Q15(QCONST16(.02f, 15), temporal_vbr); + } + /*for (i=0;i<21;i++) + printf("%f ", bandLogE[i]); + printf("\n");*/ + + if (!secondMdct) + { + OPUS_COPY(bandLogE2, bandLogE, C*nbEBands); + } + + /* Last chance to catch any transient we might have missed in the + time-domain analysis */ + if (LM>0 && ec_tell(enc)+3<=total_bits && !isTransient && st->complexity>=5 && !st->lfe && !hybrid) + { + if (patch_transient_decision(bandLogE, oldBandE, nbEBands, start, end, C)) + { + isTransient = 1; + shortBlocks = M; + compute_mdcts(mode, shortBlocks, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM, st->arch); + amp2Log2(mode, effEnd, end, bandE, bandLogE, C); + /* Compensate for the scaling of short vs long mdcts */ + for (c=0;c0 && ec_tell(enc)+3<=total_bits) + ec_enc_bit_logp(enc, isTransient, 3); + + ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */ + + /* Band normalisation */ + normalise_bands(mode, freq, X, bandE, effEnd, C, M); + + enable_tf_analysis = effectiveBytes>=15*C && !hybrid && st->complexity>=2 && !st->lfe; + + ALLOC(offsets, nbEBands, int); + ALLOC(importance, nbEBands, int); + ALLOC(spread_weight, nbEBands, int); + + maxDepth = dynalloc_analysis(bandLogE, bandLogE2, oldBandE, nbEBands, start, end, C, offsets, + st->lsb_depth, mode->logN, isTransient, st->vbr, st->constrained_vbr, + eBands, LM, effectiveBytes, &tot_boost, st->lfe, surround_dynalloc, &st->analysis, importance, spread_weight); + + ALLOC(tf_res, nbEBands, int); + /* Disable variable tf resolution for hybrid and at very low bitrate */ + if (enable_tf_analysis) + { + int lambda; + lambda = IMAX(80, 20480/effectiveBytes + 2); + tf_select = tf_analysis(mode, effEnd, isTransient, tf_res, lambda, X, N, LM, tf_estimate, tf_chan, importance); + for (i=effEnd;isilk_info.signalType != 2) + { + /* For low bitrate hybrid, we force temporal resolution to 5 ms rather than 2.5 ms. */ + for (i=0;iforce_intra, + &st->delayedIntra, st->complexity >= 4, st->loss_rate, st->lfe); + + tf_encode(start, end, isTransient, tf_res, LM, tf_select, enc); + + if (ec_tell(enc)+4<=total_bits) + { + if (st->lfe) + { + st->tapset_decision = 0; + st->spread_decision = SPREAD_NORMAL; + } else if (hybrid) + { + if (st->complexity == 0) + st->spread_decision = SPREAD_NONE; + else if (isTransient) + st->spread_decision = SPREAD_NORMAL; + else + st->spread_decision = SPREAD_AGGRESSIVE; + } else if (shortBlocks || st->complexity < 3 || nbAvailableBytes < 10*C) + { + if (st->complexity == 0) + st->spread_decision = SPREAD_NONE; + else + st->spread_decision = SPREAD_NORMAL; + } else { + /* Disable new spreading+tapset estimator until we can show it works + better than the old one. So far it seems like spreading_decision() + works best. */ +#if 0 + if (st->analysis.valid) + { + static const opus_val16 spread_thresholds[3] = {-QCONST16(.6f, 15), -QCONST16(.2f, 15), -QCONST16(.07f, 15)}; + static const opus_val16 spread_histeresis[3] = {QCONST16(.15f, 15), QCONST16(.07f, 15), QCONST16(.02f, 15)}; + static const opus_val16 tapset_thresholds[2] = {QCONST16(.0f, 15), QCONST16(.15f, 15)}; + static const opus_val16 tapset_histeresis[2] = {QCONST16(.1f, 15), QCONST16(.05f, 15)}; + st->spread_decision = hysteresis_decision(-st->analysis.tonality, spread_thresholds, spread_histeresis, 3, st->spread_decision); + st->tapset_decision = hysteresis_decision(st->analysis.tonality_slope, tapset_thresholds, tapset_histeresis, 2, st->tapset_decision); + } else +#endif + { + st->spread_decision = spreading_decision(mode, X, + &st->tonal_average, st->spread_decision, &st->hf_average, + &st->tapset_decision, pf_on&&!shortBlocks, effEnd, C, M, spread_weight); + } + /*printf("%d %d\n", st->tapset_decision, st->spread_decision);*/ + /*printf("%f %d %f %d\n\n", st->analysis.tonality, st->spread_decision, st->analysis.tonality_slope, st->tapset_decision);*/ + } + ec_enc_icdf(enc, st->spread_decision, spread_icdf, 5); + } + + /* For LFE, everything interesting is in the first band */ + if (st->lfe) + offsets[0] = IMIN(8, effectiveBytes/3); + ALLOC(cap, nbEBands, int); + init_caps(mode,cap,LM,C); + + dynalloc_logp = 6; + total_bits<<=BITRES; + total_boost = 0; + tell = ec_tell_frac(enc); + for (i=start;iintensity = hysteresis_decision((opus_val16)(equiv_rate/1000), + intensity_thresholds, intensity_histeresis, 21, st->intensity); + st->intensity = IMIN(end,IMAX(start, st->intensity)); + } + + alloc_trim = 5; + if (tell+(6< 0 || st->lfe) + { + st->stereo_saving = 0; + alloc_trim = 5; + } else { + alloc_trim = alloc_trim_analysis(mode, X, bandLogE, + end, LM, C, N, &st->analysis, &st->stereo_saving, tf_estimate, + st->intensity, surround_trim, equiv_rate, st->arch); + } + ec_enc_icdf(enc, alloc_trim, trim_icdf, 7); + tell = ec_tell_frac(enc); + } + + /* Variable bitrate */ + if (vbr_rate>0) + { + opus_val16 alpha; + opus_int32 delta; + /* The target rate in 8th bits per frame */ + opus_int32 target, base_target; + opus_int32 min_allowed; + int lm_diff = mode->maxLM - LM; + + /* Don't attempt to use more than 510 kb/s, even for frames smaller than 20 ms. + The CELT allocator will just not be able to use more than that anyway. */ + nbCompressedBytes = IMIN(nbCompressedBytes,1275>>(3-LM)); + if (!hybrid) + { + base_target = vbr_rate - ((40*C+20)<constrained_vbr) + base_target += (st->vbr_offset>>lm_diff); + + if (!hybrid) + { + target = compute_vbr(mode, &st->analysis, base_target, LM, equiv_rate, + st->lastCodedBands, C, st->intensity, st->constrained_vbr, + st->stereo_saving, tot_boost, tf_estimate, pitch_change, maxDepth, + st->lfe, st->energy_mask!=NULL, surround_masking, + temporal_vbr); + } else { + target = base_target; + /* Tonal frames (offset<100) need more bits than noisy (offset>100) ones. */ + if (st->silk_info.offset < 100) target += 12 << BITRES >> (3-LM); + if (st->silk_info.offset > 100) target -= 18 << BITRES >> (3-LM); + /* Boosting bitrate on transients and vowels with significant temporal + spikes. */ + target += (opus_int32)MULT16_16_Q14(tf_estimate-QCONST16(.25f,14), (50< QCONST16(.7f,14)) + target = IMAX(target, 50<>(BITRES+3)) + 2; + /* Take into account the 37 bits we need to have left in the packet to + signal a redundant frame in hybrid mode. Creating a shorter packet would + create an entropy coder desync. */ + if (hybrid) + min_allowed = IMAX(min_allowed, (tell0_frac+(37<>(BITRES+3)); + + nbAvailableBytes = (target+(1<<(BITRES+2)))>>(BITRES+3); + nbAvailableBytes = IMAX(min_allowed,nbAvailableBytes); + nbAvailableBytes = IMIN(nbCompressedBytes,nbAvailableBytes); + + /* By how much did we "miss" the target on that frame */ + delta = target - vbr_rate; + + target=nbAvailableBytes<<(BITRES+3); + + /*If the frame is silent we don't adjust our drift, otherwise + the encoder will shoot to very high rates after hitting a + span of silence, but we do allow the bitres to refill. + This means that we'll undershoot our target in CVBR/VBR modes + on files with lots of silence. */ + if(silence) + { + nbAvailableBytes = 2; + target = 2*8<vbr_count < 970) + { + st->vbr_count++; + alpha = celt_rcp(SHL32(EXTEND32(st->vbr_count+20),16)); + } else + alpha = QCONST16(.001f,15); + /* How many bits have we used in excess of what we're allowed */ + if (st->constrained_vbr) + st->vbr_reservoir += target - vbr_rate; + /*printf ("%d\n", st->vbr_reservoir);*/ + + /* Compute the offset we need to apply in order to reach the target */ + if (st->constrained_vbr) + { + st->vbr_drift += (opus_int32)MULT16_32_Q15(alpha,(delta*(1<vbr_offset-st->vbr_drift); + st->vbr_offset = -st->vbr_drift; + } + /*printf ("%d\n", st->vbr_drift);*/ + + if (st->constrained_vbr && st->vbr_reservoir < 0) + { + /* We're under the min value -- increase rate */ + int adjust = (-st->vbr_reservoir)/(8<vbr_reservoir = 0; + /*printf ("+%d\n", adjust);*/ + } + nbCompressedBytes = IMIN(nbCompressedBytes,nbAvailableBytes); + /*printf("%d\n", nbCompressedBytes*50*8);*/ + /* This moves the raw bits to take into account the new compressed size */ + ec_enc_shrink(enc, nbCompressedBytes); + } + + /* Bit allocation */ + ALLOC(fine_quant, nbEBands, int); + ALLOC(pulses, nbEBands, int); + ALLOC(fine_priority, nbEBands, int); + + /* bits = packet size - where we are - safety*/ + bits = (((opus_int32)nbCompressedBytes*8)<=2&&bits>=((LM+2)<analysis.valid) + { + int min_bandwidth; + if (equiv_rate < (opus_int32)32000*C) + min_bandwidth = 13; + else if (equiv_rate < (opus_int32)48000*C) + min_bandwidth = 16; + else if (equiv_rate < (opus_int32)60000*C) + min_bandwidth = 18; + else if (equiv_rate < (opus_int32)80000*C) + min_bandwidth = 19; + else + min_bandwidth = 20; + signalBandwidth = IMAX(st->analysis.bandwidth, min_bandwidth); + } +#endif + if (st->lfe) + signalBandwidth = 1; + codedBands = clt_compute_allocation(mode, start, end, offsets, cap, + alloc_trim, &st->intensity, &dual_stereo, bits, &balance, pulses, + fine_quant, fine_priority, C, LM, enc, 1, st->lastCodedBands, signalBandwidth); + if (st->lastCodedBands) + st->lastCodedBands = IMIN(st->lastCodedBands+1,IMAX(st->lastCodedBands-1,codedBands)); + else + st->lastCodedBands = codedBands; + + quant_fine_energy(mode, start, end, oldBandE, error, fine_quant, enc, C); + + /* Residual quantisation */ + ALLOC(collapse_masks, C*nbEBands, unsigned char); + quant_all_bands(1, mode, start, end, X, C==2 ? X+N : NULL, collapse_masks, + bandE, pulses, shortBlocks, st->spread_decision, + dual_stereo, st->intensity, tf_res, nbCompressedBytes*(8<rng, st->complexity, st->arch, st->disable_inv); + + if (anti_collapse_rsv > 0) + { + anti_collapse_on = st->consec_transient<2; +#ifdef FUZZING + anti_collapse_on = rand()&0x1; +#endif + ec_enc_bits(enc, anti_collapse_on, 1); + } + quant_energy_finalise(mode, start, end, oldBandE, error, fine_quant, fine_priority, nbCompressedBytes*8-ec_tell(enc), enc, C); + OPUS_CLEAR(energyError, nbEBands*CC); + c=0; + do { + for (i=start;irng, st->arch); + } + + c=0; do { + OPUS_MOVE(st->syn_mem[c], st->syn_mem[c]+N, 2*MAX_PERIOD-N+overlap/2); + } while (++csyn_mem[c]+2*MAX_PERIOD-N; + } while (++cupsample, silence, st->arch); + + c=0; do { + st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); + st->prefilter_period_old=IMAX(st->prefilter_period_old, COMBFILTER_MINPERIOD); + comb_filter(out_mem[c], out_mem[c], st->prefilter_period_old, st->prefilter_period, mode->shortMdctSize, + st->prefilter_gain_old, st->prefilter_gain, st->prefilter_tapset_old, st->prefilter_tapset, + mode->window, overlap, st->arch); + if (LM!=0) + comb_filter(out_mem[c]+mode->shortMdctSize, out_mem[c]+mode->shortMdctSize, st->prefilter_period, pitch_index, N-mode->shortMdctSize, + st->prefilter_gain, gain1, st->prefilter_tapset, prefilter_tapset, + mode->window, overlap, st->arch); + } while (++cupsample, mode->preemph, st->preemph_memD, 0); + st->prefilter_period_old = st->prefilter_period; + st->prefilter_gain_old = st->prefilter_gain; + st->prefilter_tapset_old = st->prefilter_tapset; + } +#endif + + st->prefilter_period = pitch_index; + st->prefilter_gain = gain1; + st->prefilter_tapset = prefilter_tapset; +#ifdef RESYNTH + if (LM!=0) + { + st->prefilter_period_old = st->prefilter_period; + st->prefilter_gain_old = st->prefilter_gain; + st->prefilter_tapset_old = st->prefilter_tapset; + } +#endif + + if (CC==2&&C==1) { + OPUS_COPY(&oldBandE[nbEBands], oldBandE, nbEBands); + } + + if (!isTransient) + { + OPUS_COPY(oldLogE2, oldLogE, CC*nbEBands); + OPUS_COPY(oldLogE, oldBandE, CC*nbEBands); + } else { + for (i=0;iconsec_transient++; + else + st->consec_transient=0; + st->rng = enc->rng; + + /* If there's any room left (can only happen for very high rates), + it's already filled with zeros */ + ec_enc_done(enc); + +#ifdef CUSTOM_MODES + if (st->signalling) + nbCompressedBytes++; +#endif + + RESTORE_STACK; + if (ec_get_error(enc)) + return OPUS_INTERNAL_ERROR; + else + return nbCompressedBytes; +} + + +#ifdef CUSTOM_MODES + +#ifdef FIXED_POINT +int opus_custom_encode(CELTEncoder * OPUS_RESTRICT st, const opus_int16 * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes) +{ + return celt_encode_with_ec(st, pcm, frame_size, compressed, nbCompressedBytes, NULL); +} + +#ifndef DISABLE_FLOAT_API +int opus_custom_encode_float(CELTEncoder * OPUS_RESTRICT st, const float * pcm, int frame_size, unsigned char *compressed, int nbCompressedBytes) +{ + int j, ret, C, N; + VARDECL(opus_int16, in); + ALLOC_STACK; + + if (pcm==NULL) + return OPUS_BAD_ARG; + + C = st->channels; + N = frame_size; + ALLOC(in, C*N, opus_int16); + + for (j=0;jchannels; + N=frame_size; + ALLOC(in, C*N, celt_sig); + for (j=0;j10) + goto bad_arg; + st->complexity = value; + } + break; + case CELT_SET_START_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<0 || value>=st->mode->nbEBands) + goto bad_arg; + st->start = value; + } + break; + case CELT_SET_END_BAND_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>st->mode->nbEBands) + goto bad_arg; + st->end = value; + } + break; + case CELT_SET_PREDICTION_REQUEST: + { + int value = va_arg(ap, opus_int32); + if (value<0 || value>2) + goto bad_arg; + st->disable_pf = value<=1; + st->force_intra = value==0; + } + break; + case OPUS_SET_PACKET_LOSS_PERC_REQUEST: + { + int value = va_arg(ap, opus_int32); + if (value<0 || value>100) + goto bad_arg; + st->loss_rate = value; + } + break; + case OPUS_SET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->constrained_vbr = value; + } + break; + case OPUS_SET_VBR_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->vbr = value; + } + break; + case OPUS_SET_BITRATE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<=500 && value!=OPUS_BITRATE_MAX) + goto bad_arg; + value = IMIN(value, 260000*st->channels); + st->bitrate = value; + } + break; + case CELT_SET_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<1 || value>2) + goto bad_arg; + st->stream_channels = value; + } + break; + case OPUS_SET_LSB_DEPTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<8 || value>24) + goto bad_arg; + st->lsb_depth=value; + } + break; + case OPUS_GET_LSB_DEPTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + *value=st->lsb_depth; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; + case OPUS_RESET_STATE: + { + int i; + opus_val16 *oldBandE, *oldLogE, *oldLogE2; + oldBandE = (opus_val16*)(st->in_mem+st->channels*(st->mode->overlap+COMBFILTER_MAXPERIOD)); + oldLogE = oldBandE + st->channels*st->mode->nbEBands; + oldLogE2 = oldLogE + st->channels*st->mode->nbEBands; + OPUS_CLEAR((char*)&st->ENCODER_RESET_START, + opus_custom_encoder_get_size(st->mode, st->channels)- + ((char*)&st->ENCODER_RESET_START - (char*)st)); + for (i=0;ichannels*st->mode->nbEBands;i++) + oldLogE[i]=oldLogE2[i]=-QCONST16(28.f,DB_SHIFT); + st->vbr_offset = 0; + st->delayedIntra = 1; + st->spread_decision = SPREAD_NORMAL; + st->tonal_average = 256; + st->hf_average = 0; + st->tapset_decision = 0; + } + break; +#ifdef CUSTOM_MODES + case CELT_SET_INPUT_CLIPPING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->clip = value; + } + break; +#endif + case CELT_SET_SIGNALLING_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->signalling = value; + } + break; + case CELT_SET_ANALYSIS_REQUEST: + { + AnalysisInfo *info = va_arg(ap, AnalysisInfo *); + if (info) + OPUS_COPY(&st->analysis, info, 1); + } + break; + case CELT_SET_SILK_INFO_REQUEST: + { + SILKInfo *info = va_arg(ap, SILKInfo *); + if (info) + OPUS_COPY(&st->silk_info, info, 1); + } + break; + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (value==0) + goto bad_arg; + *value=st->mode; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 * value = va_arg(ap, opus_uint32 *); + if (value==0) + goto bad_arg; + *value=st->rng; + } + break; + case OPUS_SET_LFE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->lfe = value; + } + break; + case OPUS_SET_ENERGY_MASK_REQUEST: + { + opus_val16 *value = va_arg(ap, opus_val16*); + st->energy_mask = value; + } + break; + default: + goto bad_request; + } + va_end(ap); + return OPUS_OK; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +bad_request: + va_end(ap); + return OPUS_UNIMPLEMENTED; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.c new file mode 100644 index 0000000000..fabca65cb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.c @@ -0,0 +1,363 @@ +/* Copyright (c) 2009-2010 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +void _celt_lpc( + opus_val16 *_lpc, /* out: [0...p-1] LPC coefficients */ +const opus_val32 *ac, /* in: [0...p] autocorrelation values */ +int p +) +{ + int i, j; + opus_val32 r; + opus_val32 error = ac[0]; +#ifdef FIXED_POINT + opus_val32 lpc[CELT_LPC_ORDER]; +#else + float *lpc = _lpc; +#endif + + OPUS_CLEAR(lpc, p); +#ifdef FIXED_POINT + if (ac[0] != 0) +#else + if (ac[0] > 1e-10f) +#endif + { + for (i = 0; i < p; i++) { + /* Sum up this iteration's reflection coefficient */ + opus_val32 rr = 0; + for (j = 0; j < i; j++) + rr += MULT32_32_Q31(lpc[j],ac[i - j]); + rr += SHR32(ac[i + 1],6); + r = -frac_div32(SHL32(rr,6), error); + /* Update LPC coefficients and total error */ + lpc[i] = SHR32(r,6); + for (j = 0; j < (i+1)>>1; j++) + { + opus_val32 tmp1, tmp2; + tmp1 = lpc[j]; + tmp2 = lpc[i-1-j]; + lpc[j] = tmp1 + MULT32_32_Q31(r,tmp2); + lpc[i-1-j] = tmp2 + MULT32_32_Q31(r,tmp1); + } + + error = error - MULT32_32_Q31(MULT32_32_Q31(r,r),error); + /* Bail out once we get 30 dB gain */ +#ifdef FIXED_POINT + if (error<=SHR32(ac[0],10)) + break; +#else + if (error<=.001f*ac[0]) + break; +#endif + } + } +#ifdef FIXED_POINT + { + /* Convert the int32 lpcs to int16 and ensure there are no wrap-arounds. + This reuses the logic in silk_LPC_fit() and silk_bwexpander_32(). Any bug + fixes should also be applied there. */ + int iter, idx = 0; + opus_val32 maxabs, absval, chirp_Q16, chirp_minus_one_Q16; + + for (iter = 0; iter < 10; iter++) { + maxabs = 0; + for (i = 0; i < p; i++) { + absval = ABS32(lpc[i]); + if (absval > maxabs) { + maxabs = absval; + idx = i; + } + } + maxabs = PSHR32(maxabs, 13); /* Q25->Q12 */ + + if (maxabs > 32767) { + maxabs = MIN32(maxabs, 163838); + chirp_Q16 = QCONST32(0.999, 16) - DIV32(SHL32(maxabs - 32767, 14), + SHR32(MULT32_32_32(maxabs, idx + 1), 2)); + chirp_minus_one_Q16 = chirp_Q16 - 65536; + + /* Apply bandwidth expansion. */ + for (i = 0; i < p - 1; i++) { + lpc[i] = MULT32_32_Q16(chirp_Q16, lpc[i]); + chirp_Q16 += PSHR32(MULT32_32_32(chirp_Q16, chirp_minus_one_Q16), 16); + } + lpc[p - 1] = MULT32_32_Q16(chirp_Q16, lpc[p - 1]); + } else { + break; + } + } + + if (iter == 10) { + /* If the coeffs still do not fit into the 16 bit range after 10 iterations, + fall back to the A(z)=1 filter. */ + OPUS_CLEAR(lpc, p); + _lpc[0] = 4096; /* Q12 */ + } else { + for (i = 0; i < p; i++) { + _lpc[i] = EXTRACT16(PSHR32(lpc[i], 13)); /* Q25->Q12 */ + } + } + } +#endif +} + + +void celt_fir_c( + const opus_val16 *x, + const opus_val16 *num, + opus_val16 *y, + int N, + int ord, + int arch) +{ + int i,j; + VARDECL(opus_val16, rnum); + SAVE_STACK; + celt_assert(x != y); + ALLOC(rnum, ord, opus_val16); + for(i=0;i=1;j--) + { + mem[j]=mem[j-1]; + } + mem[0] = SROUND16(sum, SIG_SHIFT); + _y[i] = sum; + } +#else + int i,j; + VARDECL(opus_val16, rden); + VARDECL(opus_val16, y); + SAVE_STACK; + + celt_assert((ord&3)==0); + ALLOC(rden, ord, opus_val16); + ALLOC(y, N+ord, opus_val16); + for(i=0;i0); + celt_assert(overlap>=0); + if (overlap == 0) + { + xptr = x; + } else { + for (i=0;i0) + { + for(i=0;i= 536870912) + { + int shift2=1; + if (ac[0] >= 1073741824) + shift2++; + for (i=0;i<=lag;i++) + ac[i] = SHR32(ac[i], shift2); + shift += shift2; + } +#endif + + RESTORE_STACK; + return shift; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.h new file mode 100644 index 0000000000..97dee82f02 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/celt_lpc.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2009-2010 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef PLC_H +#define PLC_H + +#include "arch.h" +#include "cpu_support.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/celt_lpc_sse.h" +#endif + +#define CELT_LPC_ORDER 24 + +void _celt_lpc(opus_val16 *_lpc, const opus_val32 *ac, int p); + +void celt_fir_c( + const opus_val16 *x, + const opus_val16 *num, + opus_val16 *y, + int N, + int ord, + int arch); + +#if !defined(OVERRIDE_CELT_FIR) +#define celt_fir(x, num, y, N, ord, arch) \ + (celt_fir_c(x, num, y, N, ord, arch)) +#endif + +void celt_iir(const opus_val32 *x, + const opus_val16 *den, + opus_val32 *y, + int N, + int ord, + opus_val16 *mem, + int arch); + +int _celt_autocorr(const opus_val16 *x, opus_val32 *ac, + const opus_val16 *window, int overlap, int lag, int n, int arch); + +#endif /* PLC_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cpu_support.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cpu_support.h new file mode 100644 index 0000000000..9f13d8aecf --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cpu_support.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2010 Xiph.Org Foundation + * Copyright (c) 2013 Parrot */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CPU_SUPPORT_H +#define CPU_SUPPORT_H + +#include "opus_types.h" +#include "opus_defines.h" + +#if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/armcpu.h" + +/* We currently support 5 ARM variants: + * arch[0] -> ARMv4 + * arch[1] -> ARMv5E + * arch[2] -> ARMv6 + * arch[3] -> NEON + * arch[4] -> NEON+DOTPROD + */ +#define OPUS_ARCHMASK 7 + +#elif defined(OPUS_HAVE_RTCD) && \ + ((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \ + (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2))) + +#include "x86/x86cpu.h" +/* We currently support 5 x86 variants: + * arch[0] -> non-sse + * arch[1] -> sse + * arch[2] -> sse2 + * arch[3] -> sse4.1 + * arch[4] -> avx + */ +#define OPUS_ARCHMASK 7 +int opus_select_arch(void); + +#else +#define OPUS_ARCHMASK 0 + +static OPUS_INLINE int opus_select_arch(void) +{ + return 0; +} +#endif +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.c new file mode 100644 index 0000000000..a552e4f0fb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.c @@ -0,0 +1,715 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2007-2009 Timothy B. Terriberry + Written by Timothy B. Terriberry and Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "os_support.h" +#include "cwrs.h" +#include "mathops.h" +#include "arch.h" + +#ifdef CUSTOM_MODES + +/*Guaranteed to return a conservatively large estimate of the binary logarithm + with frac bits of fractional precision. + Tested for all possible 32-bit inputs with frac=4, where the maximum + overestimation is 0.06254243 bits.*/ +int log2_frac(opus_uint32 val, int frac) +{ + int l; + l=EC_ILOG(val); + if(val&(val-1)){ + /*This is (val>>l-16), but guaranteed to round up, even if adding a bias + before the shift would cause overflow (e.g., for 0xFFFFxxxx). + Doesn't work for val=0, but that case fails the test above.*/ + if(l>16)val=((val-1)>>(l-16))+1; + else val<<=16-l; + l=(l-1)<>16); + l+=b<>b; + val=(val*val+0x7FFF)>>15; + } + while(frac-->0); + /*If val is not exactly 0x8000, then we have to round up the remainder.*/ + return l+(val>0x8000); + } + /*Exact powers of two require no rounding.*/ + else return (l-1)<0 ? sum(k=1...K,2**k*choose(N,k)*choose(K-1,k-1)) : 1, + where choose() is the binomial function. + A table of values for N<10 and K<10 looks like: + V[10][10] = { + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {1, 4, 8, 12, 16, 20, 24, 28, 32, 36}, + {1, 6, 18, 38, 66, 102, 146, 198, 258, 326}, + {1, 8, 32, 88, 192, 360, 608, 952, 1408, 1992}, + {1, 10, 50, 170, 450, 1002, 1970, 3530, 5890, 9290}, + {1, 12, 72, 292, 912, 2364, 5336, 10836, 20256, 35436}, + {1, 14, 98, 462, 1666, 4942, 12642, 28814, 59906, 115598}, + {1, 16, 128, 688, 2816, 9424, 27008, 68464, 157184, 332688}, + {1, 18, 162, 978, 4482, 16722, 53154, 148626, 374274, 864146} + }; + + U(N,K) = the number of such combinations wherein N-1 objects are taken at + most K-1 at a time. + This is given by + U(N,K) = sum(k=0...K-1,V(N-1,k)) + = K>0 ? (V(N-1,K-1) + V(N,K-1))/2 : 0. + The latter expression also makes clear that U(N,K) is half the number of such + combinations wherein the first object is taken at least once. + Although it may not be clear from either of these definitions, U(N,K) is the + natural function to work with when enumerating the pulse vector codebooks, + not V(N,K). + U(N,K) is not well-defined for N=0, but with the extension + U(0,K) = K>0 ? 0 : 1, + the function becomes symmetric: U(N,K) = U(K,N), with a similar table: + U[10][10] = { + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 1, 3, 5, 7, 9, 11, 13, 15, 17}, + {0, 1, 5, 13, 25, 41, 61, 85, 113, 145}, + {0, 1, 7, 25, 63, 129, 231, 377, 575, 833}, + {0, 1, 9, 41, 129, 321, 681, 1289, 2241, 3649}, + {0, 1, 11, 61, 231, 681, 1683, 3653, 7183, 13073}, + {0, 1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081}, + {0, 1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545}, + {0, 1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729} + }; + + With this extension, V(N,K) may be written in terms of U(N,K): + V(N,K) = U(N,K) + U(N,K+1) + for all N>=0, K>=0. + Thus U(N,K+1) represents the number of combinations where the first element + is positive or zero, and U(N,K) represents the number of combinations where + it is negative. + With a large enough table of U(N,K) values, we could write O(N) encoding + and O(min(N*log(K),N+K)) decoding routines, but such a table would be + prohibitively large for small embedded devices (K may be as large as 32767 + for small N, and N may be as large as 200). + + Both functions obey the same recurrence relation: + V(N,K) = V(N-1,K) + V(N,K-1) + V(N-1,K-1), + U(N,K) = U(N-1,K) + U(N,K-1) + U(N-1,K-1), + for all N>0, K>0, with different initial conditions at N=0 or K=0. + This allows us to construct a row of one of the tables above given the + previous row or the next row. + Thus we can derive O(NK) encoding and decoding routines with O(K) memory + using only addition and subtraction. + + When encoding, we build up from the U(2,K) row and work our way forwards. + When decoding, we need to start at the U(N,K) row and work our way backwards, + which requires a means of computing U(N,K). + U(N,K) may be computed from two previous values with the same N: + U(N,K) = ((2*N-1)*U(N,K-1) - U(N,K-2))/(K-1) + U(N,K-2) + for all N>1, and since U(N,K) is symmetric, a similar relation holds for two + previous values with the same K: + U(N,K>1) = ((2*K-1)*U(N-1,K) - U(N-2,K))/(N-1) + U(N-2,K) + for all K>1. + This allows us to construct an arbitrary row of the U(N,K) table by starting + with the first two values, which are constants. + This saves roughly 2/3 the work in our O(NK) decoding routine, but costs O(K) + multiplications. + Similar relations can be derived for V(N,K), but are not used here. + + For N>0 and K>0, U(N,K) and V(N,K) take on the form of an (N-1)-degree + polynomial for fixed N. + The first few are + U(1,K) = 1, + U(2,K) = 2*K-1, + U(3,K) = (2*K-2)*K+1, + U(4,K) = (((4*K-6)*K+8)*K-3)/3, + U(5,K) = ((((2*K-4)*K+10)*K-8)*K+3)/3, + and + V(1,K) = 2, + V(2,K) = 4*K, + V(3,K) = 4*K*K+2, + V(4,K) = 8*(K*K+2)*K/3, + V(5,K) = ((4*K*K+20)*K*K+6)/3, + for all K>0. + This allows us to derive O(N) encoding and O(N*log(K)) decoding routines for + small N (and indeed decoding is also O(N) for N<3). + + @ARTICLE{Fis86, + author="Thomas R. Fischer", + title="A Pyramid Vector Quantizer", + journal="IEEE Transactions on Information Theory", + volume="IT-32", + number=4, + pages="568--583", + month=Jul, + year=1986 + }*/ + +#if !defined(SMALL_FOOTPRINT) + +/*U(N,K) = U(K,N) := N>0?K>0?U(N-1,K)+U(N,K-1)+U(N-1,K-1):0:K>0?1:0*/ +# define CELT_PVQ_U(_n,_k) (CELT_PVQ_U_ROW[IMIN(_n,_k)][IMAX(_n,_k)]) +/*V(N,K) := U(N,K)+U(N,K+1) = the number of PVQ codewords for a band of size N + with K pulses allocated to it.*/ +# define CELT_PVQ_V(_n,_k) (CELT_PVQ_U(_n,_k)+CELT_PVQ_U(_n,(_k)+1)) + +/*For each V(N,K) supported, we will access element U(min(N,K+1),max(N,K+1)). + Thus, the number of entries in row I is the larger of the maximum number of + pulses we will ever allocate for a given N=I (K=128, or however many fit in + 32 bits, whichever is smaller), plus one, and the maximum N for which + K=I-1 pulses fit in 32 bits. + The largest band size in an Opus Custom mode is 208. + Otherwise, we can limit things to the set of N which can be achieved by + splitting a band from a standard Opus mode: 176, 144, 96, 88, 72, 64, 48, + 44, 36, 32, 24, 22, 18, 16, 8, 4, 2).*/ +#if defined(CUSTOM_MODES) +static const opus_uint32 CELT_PVQ_U_DATA[1488]={ +#else +static const opus_uint32 CELT_PVQ_U_DATA[1272]={ +#endif + /*N=0, K=0...176:*/ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#if defined(CUSTOM_MODES) + /*...208:*/ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, +#endif + /*N=1, K=1...176:*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +#if defined(CUSTOM_MODES) + /*...208:*/ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, +#endif + /*N=2, K=2...176:*/ + 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, + 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, + 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, + 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, + 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, + 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, + 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, + 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255, 257, 259, 261, 263, + 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, 293, + 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319, 321, 323, + 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351, +#if defined(CUSTOM_MODES) + /*...208:*/ + 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, + 383, 385, 387, 389, 391, 393, 395, 397, 399, 401, 403, 405, 407, 409, 411, + 413, 415, +#endif + /*N=3, K=3...176:*/ + 13, 25, 41, 61, 85, 113, 145, 181, 221, 265, 313, 365, 421, 481, 545, 613, + 685, 761, 841, 925, 1013, 1105, 1201, 1301, 1405, 1513, 1625, 1741, 1861, + 1985, 2113, 2245, 2381, 2521, 2665, 2813, 2965, 3121, 3281, 3445, 3613, 3785, + 3961, 4141, 4325, 4513, 4705, 4901, 5101, 5305, 5513, 5725, 5941, 6161, 6385, + 6613, 6845, 7081, 7321, 7565, 7813, 8065, 8321, 8581, 8845, 9113, 9385, 9661, + 9941, 10225, 10513, 10805, 11101, 11401, 11705, 12013, 12325, 12641, 12961, + 13285, 13613, 13945, 14281, 14621, 14965, 15313, 15665, 16021, 16381, 16745, + 17113, 17485, 17861, 18241, 18625, 19013, 19405, 19801, 20201, 20605, 21013, + 21425, 21841, 22261, 22685, 23113, 23545, 23981, 24421, 24865, 25313, 25765, + 26221, 26681, 27145, 27613, 28085, 28561, 29041, 29525, 30013, 30505, 31001, + 31501, 32005, 32513, 33025, 33541, 34061, 34585, 35113, 35645, 36181, 36721, + 37265, 37813, 38365, 38921, 39481, 40045, 40613, 41185, 41761, 42341, 42925, + 43513, 44105, 44701, 45301, 45905, 46513, 47125, 47741, 48361, 48985, 49613, + 50245, 50881, 51521, 52165, 52813, 53465, 54121, 54781, 55445, 56113, 56785, + 57461, 58141, 58825, 59513, 60205, 60901, 61601, +#if defined(CUSTOM_MODES) + /*...208:*/ + 62305, 63013, 63725, 64441, 65161, 65885, 66613, 67345, 68081, 68821, 69565, + 70313, 71065, 71821, 72581, 73345, 74113, 74885, 75661, 76441, 77225, 78013, + 78805, 79601, 80401, 81205, 82013, 82825, 83641, 84461, 85285, 86113, +#endif + /*N=4, K=4...176:*/ + 63, 129, 231, 377, 575, 833, 1159, 1561, 2047, 2625, 3303, 4089, 4991, 6017, + 7175, 8473, 9919, 11521, 13287, 15225, 17343, 19649, 22151, 24857, 27775, + 30913, 34279, 37881, 41727, 45825, 50183, 54809, 59711, 64897, 70375, 76153, + 82239, 88641, 95367, 102425, 109823, 117569, 125671, 134137, 142975, 152193, + 161799, 171801, 182207, 193025, 204263, 215929, 228031, 240577, 253575, + 267033, 280959, 295361, 310247, 325625, 341503, 357889, 374791, 392217, + 410175, 428673, 447719, 467321, 487487, 508225, 529543, 551449, 573951, + 597057, 620775, 645113, 670079, 695681, 721927, 748825, 776383, 804609, + 833511, 863097, 893375, 924353, 956039, 988441, 1021567, 1055425, 1090023, + 1125369, 1161471, 1198337, 1235975, 1274393, 1313599, 1353601, 1394407, + 1436025, 1478463, 1521729, 1565831, 1610777, 1656575, 1703233, 1750759, + 1799161, 1848447, 1898625, 1949703, 2001689, 2054591, 2108417, 2163175, + 2218873, 2275519, 2333121, 2391687, 2451225, 2511743, 2573249, 2635751, + 2699257, 2763775, 2829313, 2895879, 2963481, 3032127, 3101825, 3172583, + 3244409, 3317311, 3391297, 3466375, 3542553, 3619839, 3698241, 3777767, + 3858425, 3940223, 4023169, 4107271, 4192537, 4278975, 4366593, 4455399, + 4545401, 4636607, 4729025, 4822663, 4917529, 5013631, 5110977, 5209575, + 5309433, 5410559, 5512961, 5616647, 5721625, 5827903, 5935489, 6044391, + 6154617, 6266175, 6379073, 6493319, 6608921, 6725887, 6844225, 6963943, + 7085049, 7207551, +#if defined(CUSTOM_MODES) + /*...208:*/ + 7331457, 7456775, 7583513, 7711679, 7841281, 7972327, 8104825, 8238783, + 8374209, 8511111, 8649497, 8789375, 8930753, 9073639, 9218041, 9363967, + 9511425, 9660423, 9810969, 9963071, 10116737, 10271975, 10428793, 10587199, + 10747201, 10908807, 11072025, 11236863, 11403329, 11571431, 11741177, + 11912575, +#endif + /*N=5, K=5...176:*/ + 321, 681, 1289, 2241, 3649, 5641, 8361, 11969, 16641, 22569, 29961, 39041, + 50049, 63241, 78889, 97281, 118721, 143529, 172041, 204609, 241601, 283401, + 330409, 383041, 441729, 506921, 579081, 658689, 746241, 842249, 947241, + 1061761, 1186369, 1321641, 1468169, 1626561, 1797441, 1981449, 2179241, + 2391489, 2618881, 2862121, 3121929, 3399041, 3694209, 4008201, 4341801, + 4695809, 5071041, 5468329, 5888521, 6332481, 6801089, 7295241, 7815849, + 8363841, 8940161, 9545769, 10181641, 10848769, 11548161, 12280841, 13047849, + 13850241, 14689089, 15565481, 16480521, 17435329, 18431041, 19468809, + 20549801, 21675201, 22846209, 24064041, 25329929, 26645121, 28010881, + 29428489, 30899241, 32424449, 34005441, 35643561, 37340169, 39096641, + 40914369, 42794761, 44739241, 46749249, 48826241, 50971689, 53187081, + 55473921, 57833729, 60268041, 62778409, 65366401, 68033601, 70781609, + 73612041, 76526529, 79526721, 82614281, 85790889, 89058241, 92418049, + 95872041, 99421961, 103069569, 106816641, 110664969, 114616361, 118672641, + 122835649, 127107241, 131489289, 135983681, 140592321, 145317129, 150160041, + 155123009, 160208001, 165417001, 170752009, 176215041, 181808129, 187533321, + 193392681, 199388289, 205522241, 211796649, 218213641, 224775361, 231483969, + 238341641, 245350569, 252512961, 259831041, 267307049, 274943241, 282741889, + 290705281, 298835721, 307135529, 315607041, 324252609, 333074601, 342075401, + 351257409, 360623041, 370174729, 379914921, 389846081, 399970689, 410291241, + 420810249, 431530241, 442453761, 453583369, 464921641, 476471169, 488234561, + 500214441, 512413449, 524834241, 537479489, 550351881, 563454121, 576788929, + 590359041, 604167209, 618216201, 632508801, +#if defined(CUSTOM_MODES) + /*...208:*/ + 647047809, 661836041, 676876329, 692171521, 707724481, 723538089, 739615241, + 755958849, 772571841, 789457161, 806617769, 824056641, 841776769, 859781161, + 878072841, 896654849, 915530241, 934702089, 954173481, 973947521, 994027329, + 1014416041, 1035116809, 1056132801, 1077467201, 1099123209, 1121104041, + 1143412929, 1166053121, 1189027881, 1212340489, 1235994241, +#endif + /*N=6, K=6...96:*/ + 1683, 3653, 7183, 13073, 22363, 36365, 56695, 85305, 124515, 177045, 246047, + 335137, 448427, 590557, 766727, 982729, 1244979, 1560549, 1937199, 2383409, + 2908411, 3522221, 4235671, 5060441, 6009091, 7095093, 8332863, 9737793, + 11326283, 13115773, 15124775, 17372905, 19880915, 22670725, 25765455, + 29189457, 32968347, 37129037, 41699767, 46710137, 52191139, 58175189, + 64696159, 71789409, 79491819, 87841821, 96879431, 106646281, 117185651, + 128542501, 140763503, 153897073, 167993403, 183104493, 199284183, 216588185, + 235074115, 254801525, 275831935, 298228865, 322057867, 347386557, 374284647, + 402823977, 433078547, 465124549, 499040399, 534906769, 572806619, 612825229, + 655050231, 699571641, 746481891, 795875861, 847850911, 902506913, 959946283, + 1020274013, 1083597703, 1150027593, 1219676595, 1292660325, 1369097135, + 1449108145, 1532817275, 1620351277, 1711839767, 1807415257, 1907213187, + 2011371957, 2120032959, +#if defined(CUSTOM_MODES) + /*...109:*/ + 2233340609U, 2351442379U, 2474488829U, 2602633639U, 2736033641U, 2874848851U, + 3019242501U, 3169381071U, 3325434321U, 3487575323U, 3655980493U, 3830829623U, + 4012305913U, +#endif + /*N=7, K=7...54*/ + 8989, 19825, 40081, 75517, 134245, 227305, 369305, 579125, 880685, 1303777, + 1884961, 2668525, 3707509, 5064793, 6814249, 9041957, 11847485, 15345233, + 19665841, 24957661, 31388293, 39146185, 48442297, 59511829, 72616013, + 88043969, 106114625, 127178701, 151620757, 179861305, 212358985, 249612805, + 292164445, 340600625, 395555537, 457713341, 527810725, 606639529, 695049433, + 793950709, 904317037, 1027188385, 1163673953, 1314955181, 1482288821, + 1667010073, 1870535785, 2094367717, +#if defined(CUSTOM_MODES) + /*...60:*/ + 2340095869U, 2609401873U, 2904062449U, 3225952925U, 3577050821U, 3959439497U, +#endif + /*N=8, K=8...37*/ + 48639, 108545, 224143, 433905, 795455, 1392065, 2340495, 3800305, 5984767, + 9173505, 13726991, 20103025, 28875327, 40754369, 56610575, 77500017, + 104692735, 139703809, 184327311, 240673265, 311207743, 398796225, 506750351, + 638878193, 799538175, 993696769, 1226990095, 1505789553, 1837271615, + 2229491905U, +#if defined(CUSTOM_MODES) + /*...40:*/ + 2691463695U, 3233240945U, 3866006015U, +#endif + /*N=9, K=9...28:*/ + 265729, 598417, 1256465, 2485825, 4673345, 8405905, 14546705, 24331777, + 39490049, 62390545, 96220561, 145198913, 214828609, 312193553, 446304145, + 628496897, 872893441, 1196924561, 1621925137, 2173806145U, +#if defined(CUSTOM_MODES) + /*...29:*/ + 2883810113U, +#endif + /*N=10, K=10...24:*/ + 1462563, 3317445, 7059735, 14218905, 27298155, 50250765, 89129247, 152951073, + 254831667, 413442773, 654862247, 1014889769, 1541911931, 2300409629U, + 3375210671U, + /*N=11, K=11...19:*/ + 8097453, 18474633, 39753273, 81270333, 158819253, 298199265, 540279585, + 948062325, 1616336765, +#if defined(CUSTOM_MODES) + /*...20:*/ + 2684641785U, +#endif + /*N=12, K=12...18:*/ + 45046719, 103274625, 224298231, 464387817, 921406335, 1759885185, + 3248227095U, + /*N=13, K=13...16:*/ + 251595969, 579168825, 1267854873, 2653649025U, + /*N=14, K=14:*/ + 1409933619 +}; + +#if defined(CUSTOM_MODES) +static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ + CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 208,CELT_PVQ_U_DATA+ 415, + CELT_PVQ_U_DATA+ 621,CELT_PVQ_U_DATA+ 826,CELT_PVQ_U_DATA+1030, + CELT_PVQ_U_DATA+1233,CELT_PVQ_U_DATA+1336,CELT_PVQ_U_DATA+1389, + CELT_PVQ_U_DATA+1421,CELT_PVQ_U_DATA+1441,CELT_PVQ_U_DATA+1455, + CELT_PVQ_U_DATA+1464,CELT_PVQ_U_DATA+1470,CELT_PVQ_U_DATA+1473 +}; +#else +static const opus_uint32 *const CELT_PVQ_U_ROW[15]={ + CELT_PVQ_U_DATA+ 0,CELT_PVQ_U_DATA+ 176,CELT_PVQ_U_DATA+ 351, + CELT_PVQ_U_DATA+ 525,CELT_PVQ_U_DATA+ 698,CELT_PVQ_U_DATA+ 870, + CELT_PVQ_U_DATA+1041,CELT_PVQ_U_DATA+1131,CELT_PVQ_U_DATA+1178, + CELT_PVQ_U_DATA+1207,CELT_PVQ_U_DATA+1226,CELT_PVQ_U_DATA+1240, + CELT_PVQ_U_DATA+1248,CELT_PVQ_U_DATA+1254,CELT_PVQ_U_DATA+1257 +}; +#endif + +#if defined(CUSTOM_MODES) +void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ + int k; + /*_maxk==0 => there's nothing to do.*/ + celt_assert(_maxk>0); + _bits[0]=0; + for(k=1;k<=_maxk;k++)_bits[k]=log2_frac(CELT_PVQ_V(_n,k),_frac); +} +#endif + +static opus_uint32 icwrs(int _n,const int *_y){ + opus_uint32 i; + int j; + int k; + celt_assert(_n>=2); + j=_n-1; + i=_y[j]<0; + k=abs(_y[j]); + do{ + j--; + i+=CELT_PVQ_U(_n-j,k); + k+=abs(_y[j]); + if(_y[j]<0)i+=CELT_PVQ_U(_n-j,k+1); + } + while(j>0); + return i; +} + +void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ + celt_assert(_k>0); + ec_enc_uint(_enc,icwrs(_n,_y),CELT_PVQ_V(_n,_k)); +} + +static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ + opus_uint32 p; + int s; + int k0; + opus_int16 val; + opus_val32 yy=0; + celt_assert(_k>0); + celt_assert(_n>1); + while(_n>2){ + opus_uint32 q; + /*Lots of pulses case:*/ + if(_k>=_n){ + const opus_uint32 *row; + row=CELT_PVQ_U_ROW[_n]; + /*Are the pulses in this dimension negative?*/ + p=row[_k+1]; + s=-(_i>=p); + _i-=p&s; + /*Count how many pulses were placed in this dimension.*/ + k0=_k; + q=row[_n]; + if(q>_i){ + celt_sig_assert(p>q); + _k=_n; + do p=CELT_PVQ_U_ROW[--_k][_n]; + while(p>_i); + } + else for(p=row[_k];p>_i;p=row[_k])_k--; + _i-=p; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + } + /*Lots of dimensions case:*/ + else{ + /*Are there any pulses in this dimension at all?*/ + p=CELT_PVQ_U_ROW[_k][_n]; + q=CELT_PVQ_U_ROW[_k+1][_n]; + if(p<=_i&&_i=q); + _i-=q&s; + /*Count how many pulses were placed in this dimension.*/ + k0=_k; + do p=CELT_PVQ_U_ROW[--_k][_n]; + while(p>_i); + _i-=p; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + } + } + _n--; + } + /*_n==2*/ + p=2*_k+1; + s=-(_i>=p); + _i-=p&s; + k0=_k; + _k=(_i+1)>>1; + if(_k)_i-=2*_k-1; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); + /*_n==1*/ + s=-(int)_i; + val=(_k+s)^s; + *_y=val; + yy=MAC16_16(yy,val,val); + return yy; +} + +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ + return cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); +} + +#else /* SMALL_FOOTPRINT */ + +/*Computes the next row/column of any recurrence that obeys the relation + u[i][j]=u[i-1][j]+u[i][j-1]+u[i-1][j-1]. + _ui0 is the base case for the new row/column.*/ +static OPUS_INLINE void unext(opus_uint32 *_ui,unsigned _len,opus_uint32 _ui0){ + opus_uint32 ui1; + unsigned j; + /*This do-while will overrun the array if we don't have storage for at least + 2 values.*/ + j=1; do { + ui1=UADD32(UADD32(_ui[j],_ui[j-1]),_ui0); + _ui[j-1]=_ui0; + _ui0=ui1; + } while (++j<_len); + _ui[j-1]=_ui0; +} + +/*Computes the previous row/column of any recurrence that obeys the relation + u[i-1][j]=u[i][j]-u[i][j-1]-u[i-1][j-1]. + _ui0 is the base case for the new row/column.*/ +static OPUS_INLINE void uprev(opus_uint32 *_ui,unsigned _n,opus_uint32 _ui0){ + opus_uint32 ui1; + unsigned j; + /*This do-while will overrun the array if we don't have storage for at least + 2 values.*/ + j=1; do { + ui1=USUB32(USUB32(_ui[j],_ui[j-1]),_ui0); + _ui[j-1]=_ui0; + _ui0=ui1; + } while (++j<_n); + _ui[j-1]=_ui0; +} + +/*Compute V(_n,_k), as well as U(_n,0..._k+1). + _u: On exit, _u[i] contains U(_n,i) for i in [0..._k+1].*/ +static opus_uint32 ncwrs_urow(unsigned _n,unsigned _k,opus_uint32 *_u){ + opus_uint32 um2; + unsigned len; + unsigned k; + len=_k+2; + /*We require storage at least 3 values (e.g., _k>0).*/ + celt_assert(len>=3); + _u[0]=0; + _u[1]=um2=1; + /*If _n==0, _u[0] should be 1 and the rest should be 0.*/ + /*If _n==1, _u[i] should be 1 for i>1.*/ + celt_assert(_n>=2); + /*If _k==0, the following do-while loop will overflow the buffer.*/ + celt_assert(_k>0); + k=2; + do _u[k]=(k<<1)-1; + while(++k0); + j=0; + do{ + opus_uint32 p; + int s; + int yj; + p=_u[_k+1]; + s=-(_i>=p); + _i-=p&s; + yj=_k; + p=_u[_k]; + while(p>_i)p=_u[--_k]; + _i-=p; + yj-=_k; + val=(yj+s)^s; + _y[j]=val; + yy=MAC16_16(yy,val,val); + uprev(_u,_k+2,0); + } + while(++j<_n); + return yy; +} + +/*Returns the index of the given combination of K elements chosen from a set + of size 1 with associated sign bits. + _y: The vector of pulses, whose sum of absolute values is K. + _k: Returns K.*/ +static OPUS_INLINE opus_uint32 icwrs1(const int *_y,int *_k){ + *_k=abs(_y[0]); + return _y[0]<0; +} + +/*Returns the index of the given combination of K elements chosen from a set + of size _n with associated sign bits. + _y: The vector of pulses, whose sum of absolute values must be _k. + _nc: Returns V(_n,_k).*/ +static OPUS_INLINE opus_uint32 icwrs(int _n,int _k,opus_uint32 *_nc,const int *_y, + opus_uint32 *_u){ + opus_uint32 i; + int j; + int k; + /*We can't unroll the first two iterations of the loop unless _n>=2.*/ + celt_assert(_n>=2); + _u[0]=0; + for(k=1;k<=_k+1;k++)_u[k]=(k<<1)-1; + i=icwrs1(_y+_n-1,&k); + j=_n-2; + i+=_u[k]; + k+=abs(_y[j]); + if(_y[j]<0)i+=_u[k+1]; + while(j-->0){ + unext(_u,_k+2,0); + i+=_u[k]; + k+=abs(_y[j]); + if(_y[j]<0)i+=_u[k+1]; + } + *_nc=_u[k]+_u[k+1]; + return i; +} + +#ifdef CUSTOM_MODES +void get_required_bits(opus_int16 *_bits,int _n,int _maxk,int _frac){ + int k; + /*_maxk==0 => there's nothing to do.*/ + celt_assert(_maxk>0); + _bits[0]=0; + if (_n==1) + { + for (k=1;k<=_maxk;k++) + _bits[k] = 1<<_frac; + } + else { + VARDECL(opus_uint32,u); + SAVE_STACK; + ALLOC(u,_maxk+2U,opus_uint32); + ncwrs_urow(_n,_maxk,u); + for(k=1;k<=_maxk;k++) + _bits[k]=log2_frac(u[k]+u[k+1],_frac); + RESTORE_STACK; + } +} +#endif /* CUSTOM_MODES */ + +void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ + opus_uint32 i; + VARDECL(opus_uint32,u); + opus_uint32 nc; + SAVE_STACK; + celt_assert(_k>0); + ALLOC(u,_k+2U,opus_uint32); + i=icwrs(_n,_k,&nc,_y,u); + ec_enc_uint(_enc,i,nc); + RESTORE_STACK; +} + +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ + VARDECL(opus_uint32,u); + int ret; + SAVE_STACK; + celt_assert(_k>0); + ALLOC(u,_k+2U,opus_uint32); + ret = cwrsi(_n,_k,ec_dec_uint(_dec,ncwrs_urow(_n,_k,u)),_y,u); + RESTORE_STACK; + return ret; +} + +#endif /* SMALL_FOOTPRINT */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.h new file mode 100644 index 0000000000..7cd4717459 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/cwrs.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2007-2009 Timothy B. Terriberry + Written by Timothy B. Terriberry and Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CWRS_H +#define CWRS_H + +#include "arch.h" +#include "stack_alloc.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef CUSTOM_MODES +int log2_frac(opus_uint32 val, int frac); +#endif + +void get_required_bits(opus_int16 *bits, int N, int K, int frac); + +void encode_pulses(const int *_y, int N, int K, ec_enc *enc); + +opus_val32 decode_pulses(int *_y, int N, int K, ec_dec *dec); + +#endif /* CWRS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/ecintrin.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/ecintrin.h new file mode 100644 index 0000000000..66a4c36ea4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/ecintrin.h @@ -0,0 +1,91 @@ +/* Copyright (c) 2003-2008 Timothy B. Terriberry + Copyright (c) 2008 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*Some common macros for potential platform-specific optimization.*/ +#include "opus_types.h" +#include +#include +#include "arch.h" +#if !defined(_ecintrin_H) +# define _ecintrin_H (1) + +/*Some specific platforms may have optimized intrinsic or OPUS_INLINE assembly + versions of these functions which can substantially improve performance. + We define macros for them to allow easy incorporation of these non-ANSI + features.*/ + +/*Modern gcc (4.x) can compile the naive versions of min and max with cmov if + given an appropriate architecture, but the branchless bit-twiddling versions + are just as fast, and do not require any special target architecture. + Earlier gcc versions (3.x) compiled both code to the same assembly + instructions, because of the way they represented ((_b)>(_a)) internally.*/ +# define EC_MINI(_a,_b) ((_a)+(((_b)-(_a))&-((_b)<(_a)))) + +/*Count leading zeros. + This macro should only be used for implementing ec_ilog(), if it is defined. + All other code should use EC_ILOG() instead.*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#if defined(_MSC_VER) && (_MSC_VER >= 1910) +# include /* Improve compiler throughput. */ +#else +# include +#endif +/*In _DEBUG mode this is not an intrinsic by default.*/ +# pragma intrinsic(_BitScanReverse) + +static __inline int ec_bsr(unsigned long _x){ + unsigned long ret; + _BitScanReverse(&ret,_x); + return (int)ret; +} +# define EC_CLZ0 (1) +# define EC_CLZ(_x) (-ec_bsr(_x)) +#elif defined(ENABLE_TI_DSPLIB) +# include "dsplib.h" +# define EC_CLZ0 (31) +# define EC_CLZ(_x) (_lnorm(_x)) +#elif __GNUC_PREREQ(3,4) +# if INT_MAX>=2147483647 +# define EC_CLZ0 ((int)sizeof(unsigned)*CHAR_BIT) +# define EC_CLZ(_x) (__builtin_clz(_x)) +# elif LONG_MAX>=2147483647L +# define EC_CLZ0 ((int)sizeof(unsigned long)*CHAR_BIT) +# define EC_CLZ(_x) (__builtin_clzl(_x)) +# endif +#endif + +#if defined(EC_CLZ) +/*Note that __builtin_clz is not defined when _x==0, according to the gcc + documentation (and that of the BSR instruction that implements it on x86). + The majority of the time we can never pass it zero. + When we need to, it can be special cased.*/ +# define EC_ILOG(_x) (EC_CLZ0-EC_CLZ(_x)) +#else +int ec_ilog(opus_uint32 _v); +# define EC_ILOG(_x) (ec_ilog(_x)) +#endif +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.c new file mode 100644 index 0000000000..70f32016ec --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "entcode.h" +#include "arch.h" + +#if !defined(EC_CLZ) +/*This is a fallback for systems where we don't know how to access + a BSR or CLZ instruction (see ecintrin.h). + If you are optimizing Opus on a new platform and it has a native CLZ or + BZR (e.g. cell, MIPS, x86, etc) then making it available to Opus will be + an easy performance win.*/ +int ec_ilog(opus_uint32 _v){ + /*On a Pentium M, this branchless version tested as the fastest on + 1,000,000,000 random 32-bit integers, edging out a similar version with + branches, and a 256-entry LUT version.*/ + int ret; + int m; + ret=!!_v; + m=!!(_v&0xFFFF0000)<<4; + _v>>=m; + ret|=m; + m=!!(_v&0xFF00)<<3; + _v>>=m; + ret|=m; + m=!!(_v&0xF0)<<2; + _v>>=m; + ret|=m; + m=!!(_v&0xC)<<1; + _v>>=m; + ret|=m; + ret+=!!(_v&0x2); + return ret; +} +#endif + +#if 1 +/* This is a faster version of ec_tell_frac() that takes advantage + of the low (1/8 bit) resolution to use just a linear function + followed by a lookup to determine the exact transition thresholds. */ +opus_uint32 ec_tell_frac(ec_ctx *_this){ + static const unsigned correction[8] = + {35733, 38967, 42495, 46340, + 50535, 55109, 60097, 65535}; + opus_uint32 nbits; + opus_uint32 r; + int l; + unsigned b; + nbits=_this->nbits_total<rng); + r=_this->rng>>(l-16); + b = (r>>12)-8; + b += r>correction[b]; + l = (l<<3)+b; + return nbits-l; +} +#else +opus_uint32 ec_tell_frac(ec_ctx *_this){ + opus_uint32 nbits; + opus_uint32 r; + int l; + int i; + /*To handle the non-integral number of bits still left in the encoder/decoder + state, we compute the worst-case number of bits of val that must be + encoded to ensure that the value is inside the range for any possible + subsequent bits. + The computation here is independent of val itself (the decoder does not + even track that value), even though the real number of bits used after + ec_enc_done() may be 1 smaller if rng is a power of two and the + corresponding trailing bits of val are all zeros. + If we did try to track that special case, then coding a value with a + probability of 1/(1<nbits_total<rng); + r=_this->rng>>(l-16); + for(i=BITRES;i-->0;){ + int b; + r=r*r>>15; + b=(int)(r>>16); + l=l<<1|b; + r>>=b; + } + return nbits-l; +} +#endif + +#ifdef USE_SMALL_DIV_TABLE +/* Result of 2^32/(2*i+1), except for i=0. */ +const opus_uint32 SMALL_DIV_TABLE[129] = { + 0xFFFFFFFF, 0x55555555, 0x33333333, 0x24924924, + 0x1C71C71C, 0x1745D174, 0x13B13B13, 0x11111111, + 0x0F0F0F0F, 0x0D79435E, 0x0C30C30C, 0x0B21642C, + 0x0A3D70A3, 0x097B425E, 0x08D3DCB0, 0x08421084, + 0x07C1F07C, 0x07507507, 0x06EB3E45, 0x06906906, + 0x063E7063, 0x05F417D0, 0x05B05B05, 0x0572620A, + 0x05397829, 0x05050505, 0x04D4873E, 0x04A7904A, + 0x047DC11F, 0x0456C797, 0x04325C53, 0x04104104, + 0x03F03F03, 0x03D22635, 0x03B5CC0E, 0x039B0AD1, + 0x0381C0E0, 0x0369D036, 0x03531DEC, 0x033D91D2, + 0x0329161F, 0x03159721, 0x03030303, 0x02F14990, + 0x02E05C0B, 0x02D02D02, 0x02C0B02C, 0x02B1DA46, + 0x02A3A0FD, 0x0295FAD4, 0x0288DF0C, 0x027C4597, + 0x02702702, 0x02647C69, 0x02593F69, 0x024E6A17, + 0x0243F6F0, 0x0239E0D5, 0x02302302, 0x0226B902, + 0x021D9EAD, 0x0214D021, 0x020C49BA, 0x02040810, + 0x01FC07F0, 0x01F44659, 0x01ECC07B, 0x01E573AC, + 0x01DE5D6E, 0x01D77B65, 0x01D0CB58, 0x01CA4B30, + 0x01C3F8F0, 0x01BDD2B8, 0x01B7D6C3, 0x01B20364, + 0x01AC5701, 0x01A6D01A, 0x01A16D3F, 0x019C2D14, + 0x01970E4F, 0x01920FB4, 0x018D3018, 0x01886E5F, + 0x0183C977, 0x017F405F, 0x017AD220, 0x01767DCE, + 0x01724287, 0x016E1F76, 0x016A13CD, 0x01661EC6, + 0x01623FA7, 0x015E75BB, 0x015AC056, 0x01571ED3, + 0x01539094, 0x01501501, 0x014CAB88, 0x0149539E, + 0x01460CBC, 0x0142D662, 0x013FB013, 0x013C995A, + 0x013991C2, 0x013698DF, 0x0133AE45, 0x0130D190, + 0x012E025C, 0x012B404A, 0x01288B01, 0x0125E227, + 0x01234567, 0x0120B470, 0x011E2EF3, 0x011BB4A4, + 0x01194538, 0x0116E068, 0x011485F0, 0x0112358E, + 0x010FEF01, 0x010DB20A, 0x010B7E6E, 0x010953F3, + 0x01073260, 0x0105197F, 0x0103091B, 0x01010101 +}; +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.h new file mode 100644 index 0000000000..3763e3f284 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entcode.h @@ -0,0 +1,152 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "opus_types.h" +#include "opus_defines.h" + +#if !defined(_entcode_H) +# define _entcode_H (1) +# include +# include +# include "ecintrin.h" + +extern const opus_uint32 SMALL_DIV_TABLE[129]; + +#ifdef OPUS_ARM_ASM +#define USE_SMALL_DIV_TABLE +#endif + +/*OPT: ec_window must be at least 32 bits, but if you have fast arithmetic on a + larger type, you can speed up the decoder by using it here.*/ +typedef opus_uint32 ec_window; +typedef struct ec_ctx ec_ctx; +typedef struct ec_ctx ec_enc; +typedef struct ec_ctx ec_dec; + +# define EC_WINDOW_SIZE ((int)sizeof(ec_window)*CHAR_BIT) + +/*The number of bits to use for the range-coded part of unsigned integers.*/ +# define EC_UINT_BITS (8) + +/*The resolution of fractional-precision bit usage measurements, i.e., + 3 => 1/8th bits.*/ +# define BITRES 3 + +/*The entropy encoder/decoder context. + We use the same structure for both, so that common functions like ec_tell() + can be used on either one.*/ +struct ec_ctx{ + /*Buffered input/output.*/ + unsigned char *buf; + /*The size of the buffer.*/ + opus_uint32 storage; + /*The offset at which the last byte containing raw bits was read/written.*/ + opus_uint32 end_offs; + /*Bits that will be read from/written at the end.*/ + ec_window end_window; + /*Number of valid bits in end_window.*/ + int nend_bits; + /*The total number of whole bits read/written. + This does not include partial bits currently in the range coder.*/ + int nbits_total; + /*The offset at which the next range coder byte will be read/written.*/ + opus_uint32 offs; + /*The number of values in the current range.*/ + opus_uint32 rng; + /*In the decoder: the difference between the top of the current range and + the input value, minus one. + In the encoder: the low end of the current range.*/ + opus_uint32 val; + /*In the decoder: the saved normalization factor from ec_decode(). + In the encoder: the number of oustanding carry propagating symbols.*/ + opus_uint32 ext; + /*A buffered input/output symbol, awaiting carry propagation.*/ + int rem; + /*Nonzero if an error occurred.*/ + int error; +}; + +static OPUS_INLINE opus_uint32 ec_range_bytes(ec_ctx *_this){ + return _this->offs; +} + +static OPUS_INLINE unsigned char *ec_get_buffer(ec_ctx *_this){ + return _this->buf; +} + +static OPUS_INLINE int ec_get_error(ec_ctx *_this){ + return _this->error; +} + +/*Returns the number of bits "used" by the encoded or decoded symbols so far. + This same number can be computed in either the encoder or the decoder, and is + suitable for making coding decisions. + Return: The number of bits. + This will always be slightly larger than the exact value (e.g., all + rounding error is in the positive direction).*/ +static OPUS_INLINE int ec_tell(ec_ctx *_this){ + return _this->nbits_total-EC_ILOG(_this->rng); +} + +/*Returns the number of bits "used" by the encoded or decoded symbols so far. + This same number can be computed in either the encoder or the decoder, and is + suitable for making coding decisions. + Return: The number of bits scaled by 2**BITRES. + This will always be slightly larger than the exact value (e.g., all + rounding error is in the positive direction).*/ +opus_uint32 ec_tell_frac(ec_ctx *_this); + +/* Tested exhaustively for all n and for 1<=d<=256 */ +static OPUS_INLINE opus_uint32 celt_udiv(opus_uint32 n, opus_uint32 d) { + celt_sig_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (d>256) + return n/d; + else { + opus_uint32 t, q; + t = EC_ILOG(d&-d); + q = (opus_uint64)SMALL_DIV_TABLE[d>>t]*(n>>(t-1))>>32; + return q+(n-q*d >= d); + } +#else + return n/d; +#endif +} + +static OPUS_INLINE opus_int32 celt_sudiv(opus_int32 n, opus_int32 d) { + celt_sig_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (n<0) + return -(opus_int32)celt_udiv(-n, d); + else + return celt_udiv(n, d); +#else + return n/d; +#endif +} + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.c new file mode 100644 index 0000000000..027aa24bca --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.c @@ -0,0 +1,266 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "os_support.h" +#include "arch.h" +#include "entdec.h" +#include "mfrngcod.h" + +/*A range decoder. + This is an entropy decoder based upon \cite{Mar79}, which is itself a + rediscovery of the FIFO arithmetic code introduced by \cite{Pas76}. + It is very similar to arithmetic encoding, except that encoding is done with + digits in any base, instead of with bits, and so it is faster when using + larger bases (i.e.: a byte). + The author claims an average waste of $\frac{1}{2}\log_b(2b)$ bits, where $b$ + is the base, longer than the theoretical optimum, but to my knowledge there + is no published justification for this claim. + This only seems true when using near-infinite precision arithmetic so that + the process is carried out with no rounding errors. + + An excellent description of implementation details is available at + http://www.arturocampos.com/ac_range.html + A recent work \cite{MNW98} which proposes several changes to arithmetic + encoding for efficiency actually re-discovers many of the principles + behind range encoding, and presents a good theoretical analysis of them. + + End of stream is handled by writing out the smallest number of bits that + ensures that the stream will be correctly decoded regardless of the value of + any subsequent bits. + ec_tell() can be used to determine how many bits were needed to decode + all the symbols thus far; other data can be packed in the remaining bits of + the input buffer. + @PHDTHESIS{Pas76, + author="Richard Clark Pasco", + title="Source coding algorithms for fast data compression", + school="Dept. of Electrical Engineering, Stanford University", + address="Stanford, CA", + month=May, + year=1976 + } + @INPROCEEDINGS{Mar79, + author="Martin, G.N.N.", + title="Range encoding: an algorithm for removing redundancy from a digitised + message", + booktitle="Video & Data Recording Conference", + year=1979, + address="Southampton", + month=Jul + } + @ARTICLE{MNW98, + author="Alistair Moffat and Radford Neal and Ian H. Witten", + title="Arithmetic Coding Revisited", + journal="{ACM} Transactions on Information Systems", + year=1998, + volume=16, + number=3, + pages="256--294", + month=Jul, + URL="http://www.stanford.edu/class/ee398a/handouts/papers/Moffat98ArithmCoding.pdf" + }*/ + +static int ec_read_byte(ec_dec *_this){ + return _this->offs<_this->storage?_this->buf[_this->offs++]:0; +} + +static int ec_read_byte_from_end(ec_dec *_this){ + return _this->end_offs<_this->storage? + _this->buf[_this->storage-++(_this->end_offs)]:0; +} + +/*Normalizes the contents of val and rng so that rng lies entirely in the + high-order symbol.*/ +static void ec_dec_normalize(ec_dec *_this){ + /*If the range is too small, rescale it and input some bits.*/ + while(_this->rng<=EC_CODE_BOT){ + int sym; + _this->nbits_total+=EC_SYM_BITS; + _this->rng<<=EC_SYM_BITS; + /*Use up the remaining bits from our last symbol.*/ + sym=_this->rem; + /*Read the next value from the input.*/ + _this->rem=ec_read_byte(_this); + /*Take the rest of the bits we need from this new symbol.*/ + sym=(sym<rem)>>(EC_SYM_BITS-EC_CODE_EXTRA); + /*And subtract them from val, capped to be less than EC_CODE_TOP.*/ + _this->val=((_this->val<buf=_buf; + _this->storage=_storage; + _this->end_offs=0; + _this->end_window=0; + _this->nend_bits=0; + /*This is the offset from which ec_tell() will subtract partial bits. + The final value after the ec_dec_normalize() call will be the same as in + the encoder, but we have to compensate for the bits that are added there.*/ + _this->nbits_total=EC_CODE_BITS+1 + -((EC_CODE_BITS-EC_CODE_EXTRA)/EC_SYM_BITS)*EC_SYM_BITS; + _this->offs=0; + _this->rng=1U<rem=ec_read_byte(_this); + _this->val=_this->rng-1-(_this->rem>>(EC_SYM_BITS-EC_CODE_EXTRA)); + _this->error=0; + /*Normalize the interval.*/ + ec_dec_normalize(_this); +} + +unsigned ec_decode(ec_dec *_this,unsigned _ft){ + unsigned s; + _this->ext=celt_udiv(_this->rng,_ft); + s=(unsigned)(_this->val/_this->ext); + return _ft-EC_MINI(s+1,_ft); +} + +unsigned ec_decode_bin(ec_dec *_this,unsigned _bits){ + unsigned s; + _this->ext=_this->rng>>_bits; + s=(unsigned)(_this->val/_this->ext); + return (1U<<_bits)-EC_MINI(s+1U,1U<<_bits); +} + +void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ + opus_uint32 s; + s=IMUL32(_this->ext,_ft-_fh); + _this->val-=s; + _this->rng=_fl>0?IMUL32(_this->ext,_fh-_fl):_this->rng-s; + ec_dec_normalize(_this); +} + +/*The probability of having a "one" is 1/(1<<_logp).*/ +int ec_dec_bit_logp(ec_dec *_this,unsigned _logp){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + int ret; + r=_this->rng; + d=_this->val; + s=r>>_logp; + ret=dval=d-s; + _this->rng=ret?s:r-s; + ec_dec_normalize(_this); + return ret; +} + +int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + opus_uint32 t; + int ret; + s=_this->rng; + d=_this->val; + r=s>>_ftb; + ret=-1; + do{ + t=s; + s=IMUL32(r,_icdf[++ret]); + } + while(dval=d-s; + _this->rng=t-s; + ec_dec_normalize(_this); + return ret; +} + +int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb){ + opus_uint32 r; + opus_uint32 d; + opus_uint32 s; + opus_uint32 t; + int ret; + s=_this->rng; + d=_this->val; + r=s>>_ftb; + ret=-1; + do{ + t=s; + s=IMUL32(r,_icdf[++ret]); + } + while(dval=d-s; + _this->rng=t-s; + ec_dec_normalize(_this); + return ret; +} + +opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ + unsigned ft; + unsigned s; + int ftb; + /*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ + celt_assert(_ft>1); + _ft--; + ftb=EC_ILOG(_ft); + if(ftb>EC_UINT_BITS){ + opus_uint32 t; + ftb-=EC_UINT_BITS; + ft=(unsigned)(_ft>>ftb)+1; + s=ec_decode(_this,ft); + ec_dec_update(_this,s,s+1,ft); + t=(opus_uint32)s<error=1; + return _ft; + } + else{ + _ft++; + s=ec_decode(_this,(unsigned)_ft); + ec_dec_update(_this,s,s+1,(unsigned)_ft); + return s; + } +} + +opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _bits){ + ec_window window; + int available; + opus_uint32 ret; + window=_this->end_window; + available=_this->nend_bits; + if((unsigned)available<_bits){ + do{ + window|=(ec_window)ec_read_byte_from_end(_this)<>=_bits; + available-=_bits; + _this->end_window=window; + _this->nend_bits=available; + _this->nbits_total+=_bits; + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.h new file mode 100644 index 0000000000..c81f26fdb2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entdec.h @@ -0,0 +1,110 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(_entdec_H) +# define _entdec_H (1) +# include +# include "entcode.h" + +/*Initializes the decoder. + _buf: The input buffer to use. + Return: 0 on success, or a negative value on error.*/ +void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage); + +/*Calculates the cumulative frequency for the next symbol. + This can then be fed into the probability model to determine what that + symbol is, and the additional frequency information required to advance to + the next symbol. + This function cannot be called more than once without a corresponding call to + ec_dec_update(), or decoding will not proceed correctly. + _ft: The total frequency of the symbols in the alphabet the next symbol was + encoded with. + Return: A cumulative frequency representing the encoded symbol. + If the cumulative frequency of all the symbols before the one that + was encoded was fl, and the cumulative frequency of all the symbols + up to and including the one encoded is fh, then the returned value + will fall in the range [fl,fh).*/ +unsigned ec_decode(ec_dec *_this,unsigned _ft); + +/*Equivalent to ec_decode() with _ft==1<<_bits.*/ +unsigned ec_decode_bin(ec_dec *_this,unsigned _bits); + +/*Advance the decoder past the next symbol using the frequency information the + symbol was encoded with. + Exactly one call to ec_decode() must have been made so that all necessary + intermediate calculations are performed. + _fl: The cumulative frequency of all symbols that come before the symbol + decoded. + _fh: The cumulative frequency of all symbols up to and including the symbol + decoded. + Together with _fl, this defines the range [_fl,_fh) in which the value + returned above must fall. + _ft: The total frequency of the symbols in the alphabet the symbol decoded + was encoded in. + This must be the same as passed to the preceding call to ec_decode().*/ +void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft); + +/* Decode a bit that has a 1/(1<<_logp) probability of being a one */ +int ec_dec_bit_logp(ec_dec *_this,unsigned _logp); + +/*Decodes a symbol given an "inverse" CDF table. + No call to ec_dec_update() is necessary after this call. + _icdf: The "inverse" CDF, such that symbol s falls in the range + [s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution. + Return: The decoded symbol s.*/ +int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb); + +/*Decodes a symbol given an "inverse" CDF table. + No call to ec_dec_update() is necessary after this call. + _icdf: The "inverse" CDF, such that symbol s falls in the range + [s>0?ft-_icdf[s-1]:0,ft-_icdf[s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution. + Return: The decoded symbol s.*/ +int ec_dec_icdf16(ec_dec *_this,const opus_uint16 *_icdf,unsigned _ftb); + +/*Extracts a raw unsigned integer with a non-power-of-2 range from the stream. + The bits must have been encoded with ec_enc_uint(). + No call to ec_dec_update() is necessary after this call. + _ft: The number of integers that can be decoded (one more than the max). + This must be at least 2, and no more than 2**32-1. + Return: The decoded bits.*/ +opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft); + +/*Extracts a sequence of raw bits from the stream. + The bits must have been encoded with ec_enc_bits(). + No call to ec_dec_update() is necessary after this call. + _ftb: The number of bits to extract. + This must be between 0 and 25, inclusive. + Return: The decoded bits.*/ +opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _ftb); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.c new file mode 100644 index 0000000000..69c6f835d0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.c @@ -0,0 +1,305 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if defined(HAVE_CONFIG_H) +# include "config.h" +#endif +#include "os_support.h" +#include "arch.h" +#include "entenc.h" +#include "mfrngcod.h" + +/*A range encoder. + See entdec.c and the references for implementation details \cite{Mar79,MNW98}. + + @INPROCEEDINGS{Mar79, + author="Martin, G.N.N.", + title="Range encoding: an algorithm for removing redundancy from a digitised + message", + booktitle="Video \& Data Recording Conference", + year=1979, + address="Southampton", + month=Jul + } + @ARTICLE{MNW98, + author="Alistair Moffat and Radford Neal and Ian H. Witten", + title="Arithmetic Coding Revisited", + journal="{ACM} Transactions on Information Systems", + year=1998, + volume=16, + number=3, + pages="256--294", + month=Jul, + URL="http://www.stanford.edu/class/ee398/handouts/papers/Moffat98ArithmCoding.pdf" + }*/ + +static int ec_write_byte(ec_enc *_this,unsigned _value){ + if(_this->offs+_this->end_offs>=_this->storage)return -1; + _this->buf[_this->offs++]=(unsigned char)_value; + return 0; +} + +static int ec_write_byte_at_end(ec_enc *_this,unsigned _value){ + if(_this->offs+_this->end_offs>=_this->storage)return -1; + _this->buf[_this->storage-++(_this->end_offs)]=(unsigned char)_value; + return 0; +} + +/*Outputs a symbol, with a carry bit. + If there is a potential to propagate a carry over several symbols, they are + buffered until it can be determined whether or not an actual carry will + occur. + If the counter for the buffered symbols overflows, then the stream becomes + undecodable. + This gives a theoretical limit of a few billion symbols in a single packet on + 32-bit systems. + The alternative is to truncate the range in order to force a carry, but + requires similar carry tracking in the decoder, needlessly slowing it down.*/ +static void ec_enc_carry_out(ec_enc *_this,int _c){ + if(_c!=EC_SYM_MAX){ + /*No further carry propagation possible, flush buffer.*/ + int carry; + carry=_c>>EC_SYM_BITS; + /*Don't output a byte on the first write. + This compare should be taken care of by branch-prediction thereafter.*/ + if(_this->rem>=0)_this->error|=ec_write_byte(_this,_this->rem+carry); + if(_this->ext>0){ + unsigned sym; + sym=(EC_SYM_MAX+carry)&EC_SYM_MAX; + do _this->error|=ec_write_byte(_this,sym); + while(--(_this->ext)>0); + } + _this->rem=_c&EC_SYM_MAX; + } + else _this->ext++; +} + +static OPUS_INLINE void ec_enc_normalize(ec_enc *_this){ + /*If the range is too small, output some bits and rescale it.*/ + while(_this->rng<=EC_CODE_BOT){ + ec_enc_carry_out(_this,(int)(_this->val>>EC_CODE_SHIFT)); + /*Move the next-to-high-order symbol into the high-order position.*/ + _this->val=(_this->val<rng<<=EC_SYM_BITS; + _this->nbits_total+=EC_SYM_BITS; + } +} + +void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size){ + _this->buf=_buf; + _this->end_offs=0; + _this->end_window=0; + _this->nend_bits=0; + /*This is the offset from which ec_tell() will subtract partial bits.*/ + _this->nbits_total=EC_CODE_BITS+1; + _this->offs=0; + _this->rng=EC_CODE_TOP; + _this->rem=-1; + _this->val=0; + _this->ext=0; + _this->storage=_size; + _this->error=0; +} + +void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft){ + opus_uint32 r; + r=celt_udiv(_this->rng,_ft); + if(_fl>0){ + _this->val+=_this->rng-IMUL32(r,(_ft-_fl)); + _this->rng=IMUL32(r,(_fh-_fl)); + } + else _this->rng-=IMUL32(r,(_ft-_fh)); + ec_enc_normalize(_this); +} + +void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits){ + opus_uint32 r; + r=_this->rng>>_bits; + if(_fl>0){ + _this->val+=_this->rng-IMUL32(r,((1U<<_bits)-_fl)); + _this->rng=IMUL32(r,(_fh-_fl)); + } + else _this->rng-=IMUL32(r,((1U<<_bits)-_fh)); + ec_enc_normalize(_this); +} + +/*The probability of having a "one" is 1/(1<<_logp).*/ +void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp){ + opus_uint32 r; + opus_uint32 s; + opus_uint32 l; + r=_this->rng; + l=_this->val; + s=r>>_logp; + r-=s; + if(_val)_this->val=l+r; + _this->rng=_val?s:r; + ec_enc_normalize(_this); +} + +void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb){ + opus_uint32 r; + r=_this->rng>>_ftb; + if(_s>0){ + _this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); + _this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); + } + else _this->rng-=IMUL32(r,_icdf[_s]); + ec_enc_normalize(_this); +} + +void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb){ + opus_uint32 r; + r=_this->rng>>_ftb; + if(_s>0){ + _this->val+=_this->rng-IMUL32(r,_icdf[_s-1]); + _this->rng=IMUL32(r,_icdf[_s-1]-_icdf[_s]); + } + else _this->rng-=IMUL32(r,_icdf[_s]); + ec_enc_normalize(_this); +} + +void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft){ + unsigned ft; + unsigned fl; + int ftb; + /*In order to optimize EC_ILOG(), it is undefined for the value 0.*/ + celt_assert(_ft>1); + _ft--; + ftb=EC_ILOG(_ft); + if(ftb>EC_UINT_BITS){ + ftb-=EC_UINT_BITS; + ft=(_ft>>ftb)+1; + fl=(unsigned)(_fl>>ftb); + ec_encode(_this,fl,fl+1,ft); + ec_enc_bits(_this,_fl&(((opus_uint32)1<end_window; + used=_this->nend_bits; + celt_assert(_bits>0); + if(used+_bits>EC_WINDOW_SIZE){ + do{ + _this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); + window>>=EC_SYM_BITS; + used-=EC_SYM_BITS; + } + while(used>=EC_SYM_BITS); + } + window|=(ec_window)_fl<end_window=window; + _this->nend_bits=used; + _this->nbits_total+=_bits; +} + +void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits){ + int shift; + unsigned mask; + celt_assert(_nbits<=EC_SYM_BITS); + shift=EC_SYM_BITS-_nbits; + mask=((1<<_nbits)-1)<offs>0){ + /*The first byte has been finalized.*/ + _this->buf[0]=(unsigned char)((_this->buf[0]&~mask)|_val<rem>=0){ + /*The first byte is still awaiting carry propagation.*/ + _this->rem=(_this->rem&~mask)|_val<rng<=(EC_CODE_TOP>>_nbits)){ + /*The renormalization loop has never been run.*/ + _this->val=(_this->val&~((opus_uint32)mask<error=-1; +} + +void ec_enc_shrink(ec_enc *_this,opus_uint32 _size){ + celt_assert(_this->offs+_this->end_offs<=_size); + OPUS_MOVE(_this->buf+_size-_this->end_offs, + _this->buf+_this->storage-_this->end_offs,_this->end_offs); + _this->storage=_size; +} + +void ec_enc_done(ec_enc *_this){ + ec_window window; + int used; + opus_uint32 msk; + opus_uint32 end; + int l; + /*We output the minimum number of bits that ensures that the symbols encoded + thus far will be decoded correctly regardless of the bits that follow.*/ + l=EC_CODE_BITS-EC_ILOG(_this->rng); + msk=(EC_CODE_TOP-1)>>l; + end=(_this->val+msk)&~msk; + if((end|msk)>=_this->val+_this->rng){ + l++; + msk>>=1; + end=(_this->val+msk)&~msk; + } + while(l>0){ + ec_enc_carry_out(_this,(int)(end>>EC_CODE_SHIFT)); + end=(end<rem>=0||_this->ext>0)ec_enc_carry_out(_this,0); + /*If we have buffered extra bits, flush them as well.*/ + window=_this->end_window; + used=_this->nend_bits; + while(used>=EC_SYM_BITS){ + _this->error|=ec_write_byte_at_end(_this,(unsigned)window&EC_SYM_MAX); + window>>=EC_SYM_BITS; + used-=EC_SYM_BITS; + } + /*Clear any excess space and add any remaining extra bits to the last byte.*/ + if(!_this->error){ + OPUS_CLEAR(_this->buf+_this->offs, + _this->storage-_this->offs-_this->end_offs); + if(used>0){ + /*If there's no range coder data at all, give up.*/ + if(_this->end_offs>=_this->storage)_this->error=-1; + else{ + l=-l; + /*If we've busted, don't add too many extra bits to the last byte; it + would corrupt the range coder data, and that's more important.*/ + if(_this->offs+_this->end_offs>=_this->storage&&lerror=-1; + } + _this->buf[_this->storage-_this->end_offs-1]|=(unsigned char)window; + } + } + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.h new file mode 100644 index 0000000000..010874bbc1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/entenc.h @@ -0,0 +1,119 @@ +/* Copyright (c) 2001-2011 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(_entenc_H) +# define _entenc_H (1) +# include +# include "entcode.h" + +/*Initializes the encoder. + _buf: The buffer to store output bytes in. + _size: The size of the buffer, in chars.*/ +void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size); +/*Encodes a symbol given its frequency information. + The frequency information must be discernable by the decoder, assuming it + has read only the previous symbols from the stream. + It is allowable to change the frequency information, or even the entire + source alphabet, so long as the decoder can tell from the context of the + previously encoded information that it is supposed to do so as well. + _fl: The cumulative frequency of all symbols that come before the one to be + encoded. + _fh: The cumulative frequency of all symbols up to and including the one to + be encoded. + Together with _fl, this defines the range [_fl,_fh) in which the + decoded value will fall. + _ft: The sum of the frequencies of all the symbols*/ +void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft); + +/*Equivalent to ec_encode() with _ft==1<<_bits.*/ +void ec_encode_bin(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _bits); + +/* Encode a bit that has a 1/(1<<_logp) probability of being a one */ +void ec_enc_bit_logp(ec_enc *_this,int _val,unsigned _logp); + +/*Encodes a symbol given an "inverse" CDF table. + _s: The index of the symbol to encode. + _icdf: The "inverse" CDF, such that symbol _s falls in the range + [_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution.*/ +void ec_enc_icdf(ec_enc *_this,int _s,const unsigned char *_icdf,unsigned _ftb); + +/*Encodes a symbol given an "inverse" CDF table. + _s: The index of the symbol to encode. + _icdf: The "inverse" CDF, such that symbol _s falls in the range + [_s>0?ft-_icdf[_s-1]:0,ft-_icdf[_s]), where ft=1<<_ftb. + The values must be monotonically non-increasing, and the last value + must be 0. + _ftb: The number of bits of precision in the cumulative distribution.*/ +void ec_enc_icdf16(ec_enc *_this,int _s,const opus_uint16 *_icdf,unsigned _ftb); + +/*Encodes a raw unsigned integer in the stream. + _fl: The integer to encode. + _ft: The number of integers that can be encoded (one more than the max). + This must be at least 2, and no more than 2**32-1.*/ +void ec_enc_uint(ec_enc *_this,opus_uint32 _fl,opus_uint32 _ft); + +/*Encodes a sequence of raw bits in the stream. + _fl: The bits to encode. + _ftb: The number of bits to encode. + This must be between 1 and 25, inclusive.*/ +void ec_enc_bits(ec_enc *_this,opus_uint32 _fl,unsigned _ftb); + +/*Overwrites a few bits at the very start of an existing stream, after they + have already been encoded. + This makes it possible to have a few flags up front, where it is easy for + decoders to access them without parsing the whole stream, even if their + values are not determined until late in the encoding process, without having + to buffer all the intermediate symbols in the encoder. + In order for this to work, at least _nbits bits must have already been + encoded using probabilities that are an exact power of two. + The encoder can verify the number of encoded bits is sufficient, but cannot + check this latter condition. + _val: The bits to encode (in the least _nbits significant bits). + They will be decoded in order from most-significant to least. + _nbits: The number of bits to overwrite. + This must be no more than 8.*/ +void ec_enc_patch_initial_bits(ec_enc *_this,unsigned _val,unsigned _nbits); + +/*Compacts the data to fit in the target size. + This moves up the raw bits at the end of the current buffer so they are at + the end of the new buffer size. + The caller must ensure that the amount of data that's already been written + will fit in the new size. + _size: The number of bytes in the new buffer. + This must be large enough to contain the bits already written, and + must be no larger than the existing size.*/ +void ec_enc_shrink(ec_enc *_this,opus_uint32 _size); + +/*Indicates that there are no more symbols to encode. + All reamining output bytes are flushed to the output buffer. + ec_enc_init() must be called before the encoder can be used again.*/ +void ec_enc_done(ec_enc *_this); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_debug.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_debug.h new file mode 100644 index 0000000000..ef2e5d02fb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_debug.h @@ -0,0 +1,836 @@ +/* Copyright (C) 2003-2008 Jean-Marc Valin + Copyright (C) 2007-2012 Xiph.Org Foundation */ +/** + @file fixed_debug.h + @brief Fixed-point operations with debugging +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_DEBUG_H +#define FIXED_DEBUG_H + +#include +#include "opus_defines.h" + +#ifdef CELT_C +OPUS_EXPORT opus_int64 celt_mips=0; +#else +extern opus_int64 celt_mips; +#endif + +#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) +#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL32(MULT16_16(SHR32((a),16),SHR((b),16)),1), SHR32(MULT16_16SU(SHR32((a),16),((b)&0x0000ffff)),15)), SHR32(MULT16_16SU(SHR32((b),16),((a)&0x0000ffff)),15)) + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR32((b),16)), SHR32(MULT16_16SU((a),((b)&0x0000ffff)),16)) + +#define MULT16_32_P16(a,b) MULT16_32_PX(a,b,16) + +#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) +#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_val32)1)<<(bits)))) + +#define VERIFY_SHORT(x) ((x)<=32767&&(x)>=-32768) +#define VERIFY_INT(x) ((x)<=2147483647LL&&(x)>=-2147483648LL) +#define VERIFY_UINT(x) ((x)<=(2147483647LLU<<1)) + +#define SHR(a,b) SHR32(a,b) +#define PSHR(a,b) PSHR32(a,b) + +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ +/** Negate 32-bit value, ignore any overflows */ +#define NEG32_ovflw(a) (celt_mips+=2,(opus_val32)(0-(opus_uint32)(a))) + +static OPUS_INLINE short NEG16(int x) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "NEG16: input is not short: %d\n", (int)x); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = -x; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "NEG16: output is not short: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} +static OPUS_INLINE int NEG32(opus_int64 x) +{ + opus_int64 res; + if (!VERIFY_INT(x)) + { + fprintf (stderr, "NEG16: input is not int: %d\n", (int)x); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = -x; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "NEG16: output is not int: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define EXTRACT16(x) EXTRACT16_(x, __FILE__, __LINE__) +static OPUS_INLINE short EXTRACT16_(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTRACT16: input is not short: %d in %s: line %d\n", x, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = x; + celt_mips++; + return res; +} + +#define EXTEND32(x) EXTEND32_(x, __FILE__, __LINE__) +static OPUS_INLINE int EXTEND32_(int x, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(x)) + { + fprintf (stderr, "EXTEND32: input is not short: %d in %s: line %d\n", x, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = x; + celt_mips++; + return res; +} + +#define SHR16(a, shift) SHR16_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE short SHR16_(int a, int shift, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHR16: inputs are not short: %d >> %d in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a>>shift; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "SHR16: output is not short: %d in %s: line %d\n", res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} +#define SHL16(a, shift) SHL16_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE short SHL16_(int a, int shift, char *file, int line) +{ + opus_int32 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL16: inputs are not short: %d %d in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (opus_int32)((opus_uint32)a<>shift; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SHR32: output is not int: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} +#define SHL32(a, shift) SHL32_(a, shift, __FILE__, __LINE__) +static OPUS_INLINE int SHL32_(opus_int64 a, int shift, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_SHORT(shift)) + { + fprintf (stderr, "SHL32: inputs are not int: %lld %d in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (opus_int64)((opus_uint64)a<>1))),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +#define ROUND16(x,a) (celt_mips--,EXTRACT16(PSHR32((x),(a)))) +#define SROUND16(x,a) (celt_mips--,EXTRACT16(SATURATE(PSHR32(x,a), 32767))); + +#define HALF16(x) (SHR16(x,1)) +#define HALF32(x) (SHR32(x,1)) + +#define ADD16(a, b) ADD16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short ADD16_(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "ADD16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "ADD16: output is not short: %d+%d=%d in %s: line %d\n", a,b,res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define SUB16(a, b) SUB16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short SUB16_(int a, int b, char *file, int line) +{ + int res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "SUB16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a-b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "SUB16: output is not short: %d in %s: line %d\n", res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define ADD32(a, b) ADD32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int ADD32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "ADD32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "ADD32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define SUB32(a, b) SUB32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int SUB32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "SUB32: inputs are not int: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a-b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "SUB32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#undef UADD32 +#define UADD32(a, b) UADD32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE unsigned int UADD32_(opus_uint64 a, opus_uint64 b, char *file, int line) +{ + opus_uint64 res; + if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) + { + fprintf (stderr, "UADD32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a+b; + if (!VERIFY_UINT(res)) + { + fprintf (stderr, "UADD32: output is not uint32: %llu in %s: line %d\n", (unsigned long long)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#undef USUB32 +#define USUB32(a, b) USUB32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE unsigned int USUB32_(opus_uint64 a, opus_uint64 b, char *file, int line) +{ + opus_uint64 res; + if (!VERIFY_UINT(a) || !VERIFY_UINT(b)) + { + fprintf (stderr, "USUB32: inputs are not uint32: %llu %llu in %s: line %d\n", (unsigned long long)a, (unsigned long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (a> 16; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT32_32_Q16: output is not int: %lld*%lld=%lld\n", (long long)a, (long long)b, (long long)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=5; + return res; +} + +#define MULT16_16(a, b) MULT16_16_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_16_(int a, int b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips++; + return res; +} + +#define MAC16_16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_16((a),(b)))) + +#define MULT16_32_QX(a, b, Q) MULT16_32_QX_(a, b, Q, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_32_QX_(int a, opus_int64 b, int Q, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_Q%d: inputs are not short+int: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) + { + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = (((opus_int64)a)*(opus_int64)b) >> Q; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_32_Q%d: output is not int: %d*%d=%d in %s: line %d\n", Q, (int)a, (int)b,(int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (Q==15) + celt_mips+=3; + else + celt_mips+=4; + return res; +} + +#define MULT16_32_PX(a, b, Q) MULT16_32_PX_(a, b, Q, __FILE__, __LINE__) +static OPUS_INLINE int MULT16_32_PX_(int a, opus_int64 b, int Q, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "MULT16_32_P%d: inputs are not short+int: %d %d in %s: line %d\n\n", Q, (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (ABS32(b)>=((opus_int64)(1)<<(16+Q))) + { + fprintf (stderr, "MULT16_32_Q%d: second operand too large: %d %d in %s: line %d\n\n", Q, (int)a, (int)b,file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((((opus_int64)a)*(opus_int64)b) + (((opus_val32)(1)<>1))>> Q; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_32_P%d: output is not int: %d*%d=%d in %s: line %d\n\n", Q, (int)a, (int)b,(int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + if (Q==15) + celt_mips+=4; + else + celt_mips+=5; + return res; +} + +#define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) +#define MAC16_32_Q15(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q15((a),(b)))) +#define MAC16_32_Q16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q16((a),(b)))) + +static OPUS_INLINE int SATURATE(int a, int b) +{ + if (a>b) + a=b; + if (a<-b) + a = -b; + celt_mips+=3; + return a; +} + +static OPUS_INLINE opus_int16 SATURATE16(opus_int32 a) +{ + celt_mips+=3; + if (a>32767) + return 32767; + else if (a<-32768) + return -32768; + else return a; +} + +static OPUS_INLINE int MULT16_16_Q11_32(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q11: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 11; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_Q11: output is not short: %d*%d=%d\n", (int)a, (int)b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} +static OPUS_INLINE short MULT16_16_Q13(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q13: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 13; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q13: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} +static OPUS_INLINE short MULT16_16_Q14(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q14: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 14; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q14: output is not short: %d\n", (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=3; + return res; +} + +#define MULT16_16_Q15(a, b) MULT16_16_Q15_(a, b, __FILE__, __LINE__) +static OPUS_INLINE short MULT16_16_Q15_(int a, int b, char *file, int line) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_Q15: inputs are not short: %d %d in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_Q15: output is not short: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=1; + return res; +} + +static OPUS_INLINE short MULT16_16_P13(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P13: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 4096; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P13: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 13; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P13: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=4; + return res; +} +static OPUS_INLINE short MULT16_16_P14(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P14: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 8192; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P14: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 14; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P14: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=4; + return res; +} +static OPUS_INLINE short MULT16_16_P15(int a, int b) +{ + opus_int64 res; + if (!VERIFY_SHORT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "MULT16_16_P15: inputs are not short: %d %d\n", a, b); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = ((opus_int64)a)*b; + res += 16384; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "MULT16_16_P15: overflow: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res >>= 15; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "MULT16_16_P15: output is not short: %d*%d=%d\n", a, b, (int)res); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=2; + return res; +} + +#define DIV32_16(a, b) DIV32_16_(a, b, __FILE__, __LINE__) + +static OPUS_INLINE int DIV32_16_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (b==0) + { + fprintf(stderr, "DIV32_16: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + return 0; + } + if (!VERIFY_INT(a) || !VERIFY_SHORT(b)) + { + fprintf (stderr, "DIV32_16: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a/b; + if (!VERIFY_SHORT(res)) + { + fprintf (stderr, "DIV32_16: output is not short: %d / %d = %d in %s: line %d\n", (int)a,(int)b,(int)res, file, line); + if (res>32767) + res = 32767; + if (res<-32768) + res = -32768; +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=35; + return res; +} + +#define DIV32(a, b) DIV32_(a, b, __FILE__, __LINE__) +static OPUS_INLINE int DIV32_(opus_int64 a, opus_int64 b, char *file, int line) +{ + opus_int64 res; + if (b==0) + { + fprintf(stderr, "DIV32: divide by zero: %d/%d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + return 0; + } + + if (!VERIFY_INT(a) || !VERIFY_INT(b)) + { + fprintf (stderr, "DIV32: inputs are not int/short: %d %d in %s: line %d\n", (int)a, (int)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + res = a/b; + if (!VERIFY_INT(res)) + { + fprintf (stderr, "DIV32: output is not int: %d in %s: line %d\n", (int)res, file, line); +#ifdef FIXED_DEBUG_ASSERT + celt_assert(0); +#endif + } + celt_mips+=70; + return res; +} + +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + + +#undef PRINT_MIPS +#define PRINT_MIPS(file) do {fprintf (file, "total complexity = %llu MIPS\n", (unsigned long long)celt_mips);} while (0); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_generic.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_generic.h new file mode 100644 index 0000000000..8f29d46bb7 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/fixed_generic.h @@ -0,0 +1,188 @@ +/* Copyright (C) 2007-2009 Xiph.Org Foundation + Copyright (C) 2003-2008 Jean-Marc Valin + Copyright (C) 2007-2008 CSIRO */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +/** Multiply a 16-bit signed value by a 16-bit unsigned value. The result is a 32-bit signed value */ +#define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) + +/** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q16(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),16)) +#else +#define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif + +/** 16x32 multiplication, followed by a 16-bit shift right (round-to-nearest). Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_P16(a,b) ((opus_val32)PSHR((opus_int64)((opus_val16)(a))*(b),16)) +#else +#define MULT16_32_P16(a,b) ADD32(MULT16_16((a),SHR((b),16)), PSHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif + +/** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q15(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),15)) +#else +#define MULT16_32_Q15(a,b) ADD32(SHL(MULT16_16((a),SHR((b),16)),1), SHR(MULT16_16SU((a),((b)&0x0000ffff)),15)) +#endif + +/** 32x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT32_32_Q16(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),16)) +#else +#define MULT32_32_Q16(a,b) (ADD32(ADD32(ADD32((opus_val32)(SHR32(((opus_uint32)((a)&0x0000ffff)*(opus_uint32)((b)&0x0000ffff)),16)), MULT16_16SU(SHR32(a,16),((b)&0x0000ffff))), MULT16_16SU(SHR32(b,16),((a)&0x0000ffff))), SHL32(MULT16_16(SHR32(a,16),SHR32(b,16)),16))) +#endif + +/** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT32_32_Q31(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),31)) +#else +#define MULT32_32_Q31(a,b) ADD32(ADD32(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),15)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),15)) +#endif + +/** Compile-time conversion of float constant to 16-bit value */ +#define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) + +/** Compile-time conversion of float constant to 32-bit value */ +#define QCONST32(x,bits) ((opus_val32)(.5+(x)*(((opus_val32)1)<<(bits)))) + +/** Negate a 16-bit value */ +#define NEG16(x) (-(x)) +/** Negate a 32-bit value */ +#define NEG32(x) (-(x)) + +/** Change a 32-bit value into a 16-bit value. The value is assumed to fit in 16-bit, otherwise the result is undefined */ +#define EXTRACT16(x) ((opus_val16)(x)) +/** Change a 16-bit value into a 32-bit value */ +#define EXTEND32(x) ((opus_val32)(x)) + +/** Arithmetic shift-right of a 16-bit value */ +#define SHR16(a,shift) ((a) >> (shift)) +/** Arithmetic shift-left of a 16-bit value */ +#define SHL16(a,shift) ((opus_int16)((opus_uint16)(a)<<(shift))) +/** Arithmetic shift-right of a 32-bit value */ +#define SHR32(a,shift) ((a) >> (shift)) +/** Arithmetic shift-left of a 32-bit value */ +#define SHL32(a,shift) ((opus_int32)((opus_uint32)(a)<<(shift))) + +/** 32-bit arithmetic shift right with rounding-to-nearest instead of rounding down */ +#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +/** 32-bit arithmetic shift right where the argument can be negative */ +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) + +/** "RAW" macros, should not be used outside of this header file */ +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) SHL32(a,shift) +#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SATURATE16(x) (EXTRACT16((x)>32767 ? 32767 : (x)<-32768 ? -32768 : (x))) + +/** Shift by a and round-to-nearest 32-bit value. Result is a 16-bit value */ +#define ROUND16(x,a) (EXTRACT16(PSHR32((x),(a)))) +/** Shift by a and round-to-nearest 32-bit value. Result is a saturated 16-bit value */ +#define SROUND16(x,a) EXTRACT16(SATURATE(PSHR32(x,a), 32767)); + +/** Divide by two */ +#define HALF16(x) (SHR16(x,1)) +#define HALF32(x) (SHR32(x,1)) + +/** Add two 16-bit values */ +#define ADD16(a,b) ((opus_val16)((opus_val16)(a)+(opus_val16)(b))) +/** Subtract two 16-bit values */ +#define SUB16(a,b) ((opus_val16)(a)-(opus_val16)(b)) +/** Add two 32-bit values */ +#define ADD32(a,b) ((opus_val32)(a)+(opus_val32)(b)) +/** Subtract two 32-bit values */ +#define SUB32(a,b) ((opus_val32)(a)-(opus_val32)(b)) + +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +/* Avoid MSVC warning C4146: unary minus operator applied to unsigned type */ +/** Negate 32-bit value, ignore any overflows */ +#define NEG32_ovflw(a) ((opus_val32)(0-(opus_uint32)(a))) + +/** 16x16 multiplication where the result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((opus_val16)(a))*((opus_val16)(b)))) + +/** 32x32 multiplication where the result fits in 32 bits */ +#define MULT32_32_32(a,b) ((((opus_val32)(a))*((opus_val32)(b)))) + +/* (opus_val32)(opus_val16) gives TI compiler a hint that it's 16x16->32 multiply */ +/** 16x16 multiplication where the result fits in 32 bits */ +#define MULT16_16(a,b) (((opus_val32)(opus_val16)(a))*((opus_val32)(opus_val16)(b))) + +/** 16x16 multiply-add where the result fits in 32 bits */ +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +/** 16x32 multiply, followed by a 15-bit shift right and 32-bit add. + b must fit in 31 bits. + Result fits in 32 bits. */ +#define MAC16_32_Q15(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + +/** 16x32 multiplication, followed by a 16-bit shift right and 32-bit add. + Results fits in 32 bits */ +#define MAC16_32_Q16(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q11(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +/** Divide a 32-bit value by a 16-bit value. Result fits in 16 bits */ +#define DIV32_16(a,b) ((opus_val16)(((opus_val32)(a))/((opus_val16)(b)))) + +/** Divide a 32-bit value by a 32-bit value. Result fits in 32 bits */ +#define DIV32(a,b) (((opus_val32)(a))/((opus_val32)(b))) + +#if defined(MIPSr1_ASM) +#include "mips/fixed_generic_mipsr1.h" +#endif + +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/float_cast.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/float_cast.h new file mode 100644 index 0000000000..8915a5fd7f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/float_cast.h @@ -0,0 +1,152 @@ +/* Copyright (C) 2001 Erik de Castro Lopo */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Version 1.1 */ + +#ifndef FLOAT_CAST_H +#define FLOAT_CAST_H + + +#include "arch.h" + +/*============================================================================ +** On Intel Pentium processors (especially PIII and probably P4), converting +** from float to int is very slow. To meet the C specs, the code produced by +** most C compilers targeting Pentium needs to change the FPU rounding mode +** before the float to int conversion is performed. +** +** Changing the FPU rounding mode causes the FPU pipeline to be flushed. It +** is this flushing of the pipeline which is so slow. +** +** Fortunately the ISO C99 specifications define the functions lrint, lrintf, +** llrint and llrintf which fix this problem as a side effect. +** +** On Unix-like systems, the configure process should have detected the +** presence of these functions. If they weren't found we have to replace them +** here with a standard C cast. +*/ + +/* +** The C99 prototypes for lrint and lrintf are as follows: +** +** long int lrintf (float x) ; +** long int lrint (double x) ; +*/ + +/* The presence of the required functions are detected during the configure +** process and the values HAVE_LRINT and HAVE_LRINTF are set accordingly in +** the config.h file. +*/ + +/* With GCC, when SSE is available, the fastest conversion is cvtss2si. */ +#if defined(__GNUC__) && defined(__SSE__) + +#include +static OPUS_INLINE opus_int32 float2int(float x) {return _mm_cvt_ss2si(_mm_set_ss(x));} + +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)) + + #include + static OPUS_INLINE opus_int32 float2int(float value) + { + /* _mm_load_ss will generate same code as _mm_set_ss + ** in _MSC_VER >= 1914 /02 so keep __mm_load__ss + ** for backward compatibility. + */ + return _mm_cvtss_si32(_mm_load_ss(&value)); + } + +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && defined (_M_IX86) + + #include + + /* Win32 doesn't seem to have these functions. + ** Therefore implement OPUS_INLINE versions of these functions here. + */ + + static OPUS_INLINE opus_int32 + float2int (float flt) + { int intgr; + + _asm + { fld flt + fistp intgr + } ; + + return intgr ; + } + +#elif defined(HAVE_LRINTF) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* These defines enable functionality introduced with the 1999 ISO C +** standard. They must be defined before the inclusion of math.h to +** engage them. If optimisation is enabled, these functions will be +** inlined. With optimisation switched off, you have to link in the +** maths library using -lm. +*/ + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include +#define float2int(x) lrintf(x) + +#elif defined(HAVE_LRINT) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +#define _ISOC9X_SOURCE 1 +#define _ISOC99_SOURCE 1 + +#define __USE_ISOC9X 1 +#define __USE_ISOC99 1 + +#include +#define float2int(x) lrint(x) + +#else + +#if (defined(__GNUC__) && defined(__STDC__) && __STDC__ && __STDC_VERSION__ >= 199901L) + /* supported by gcc in C99 mode, but not by all other compilers */ + #warning "Don't have the functions lrint() and lrintf ()." + #warning "Replacing these functions with a standard C cast." +#endif /* __STDC_VERSION__ >= 199901L */ + #include + #define float2int(flt) ((int)(floor(.5+flt))) +#endif + +#ifndef DISABLE_FLOAT_API +static OPUS_INLINE opus_int16 FLOAT2INT16(float x) +{ + x = x*CELT_SIG_SCALE; + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return (opus_int16)float2int(x); +} +#endif /* DISABLE_FLOAT_API */ + +#endif /* FLOAT_CAST_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.c new file mode 100644 index 0000000000..83775165d8 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.c @@ -0,0 +1,604 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + Lots of modifications by Jean-Marc Valin + Copyright (c) 2005-2007, Xiph.Org Foundation + Copyright (c) 2008, Xiph.Org Foundation, CSIRO + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.*/ + +/* This code is originally from Mark Borgerding's KISS-FFT but has been + heavily modified to better suit Opus */ + +#ifndef SKIP_CONFIG_H +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif +#endif + +#include "_kiss_fft_guts.h" +#include "arch.h" +#include "os_support.h" +#include "mathops.h" +#include "stack_alloc.h" + +/* The guts header contains all the multiplication and addition macros that are defined for + complex numbers. It also delares the kf_ internal functions. +*/ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + int m, + int N + ) +{ + kiss_fft_cpx * Fout2; + int i; + (void)m; +#ifdef CUSTOM_MODES + if (m==1) + { + celt_assert(m==1); + for (i=0;itwiddles; + /* m is guaranteed to be a multiple of 4. */ + for (j=0;jtwiddles[fstride*m]; +#endif + for (i=0;itwiddles; + /* For non-custom modes, m is guaranteed to be a multiple of 4. */ + k=m; + do { + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r)); + Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i)); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i); + Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r); + + Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i); + Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r); + + ++Fout; + } while(--k); + } +} + + +#ifndef OVERRIDE_kf_bfly5 +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_state *st, + int m, + int N, + int mm + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int i, u; + kiss_fft_cpx scratch[13]; + const kiss_twiddle_cpx *tw; + kiss_twiddle_cpx ya,yb; + kiss_fft_cpx * Fout_beg = Fout; + +#ifdef FIXED_POINT + ya.r = 10126; + ya.i = -31164; + yb.r = -26510; + yb.i = -19261; +#else + ya = st->twiddles[fstride*m]; + yb = st->twiddles[fstride*2*m]; +#endif + tw=st->twiddles; + + for (i=0;ir = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r)); + Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i)); + + scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r))); + scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r))); + + scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i)); + scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i))); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r))); + scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r))); + scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i)); + scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i)); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } + } +} +#endif /* OVERRIDE_kf_bfly5 */ + + +#endif + + +#ifdef CUSTOM_MODES + +static +void compute_bitrev_table( + int Fout, + opus_int16 *f, + const size_t fstride, + int in_stride, + opus_int16 * factors, + const kiss_fft_state *st + ) +{ + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + + /*printf ("fft %d %d %d %d %d %d\n", p*m, m, p, s2, fstride*in_stride, N);*/ + if (m==1) + { + int j; + for (j=0;j32000 || (opus_int32)p*(opus_int32)p > n) + p = n; /* no more factors, skip to end */ + } + n /= p; +#ifdef RADIX_TWO_ONLY + if (p!=2 && p != 4) +#else + if (p>5) +#endif + { + return 0; + } + facbuf[2*stages] = p; + if (p==2 && stages > 1) + { + facbuf[2*stages] = 4; + facbuf[2] = 2; + } + stages++; + } while (n > 1); + n = nbak; + /* Reverse the order to get the radix 4 at the end, so we can use the + fast degenerate case. It turns out that reversing the order also + improves the noise behaviour. */ + for (i=0;i= memneeded) + st = (kiss_fft_state*)mem; + *lenmem = memneeded; + } + if (st) { + opus_int16 *bitrev; + kiss_twiddle_cpx *twiddles; + + st->nfft=nfft; +#ifdef FIXED_POINT + st->scale_shift = celt_ilog2(st->nfft); + if (st->nfft == 1<scale_shift) + st->scale = Q15ONE; + else + st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift); +#else + st->scale = 1.f/nfft; +#endif + if (base != NULL) + { + st->twiddles = base->twiddles; + st->shift = 0; + while (st->shift < 32 && nfft<shift != base->nfft) + st->shift++; + if (st->shift>=32) + goto fail; + } else { + st->twiddles = twiddles = (kiss_twiddle_cpx*)KISS_FFT_MALLOC(sizeof(kiss_twiddle_cpx)*nfft); + compute_twiddles(twiddles, nfft); + st->shift = -1; + } + if (!kf_factor(nfft,st->factors)) + { + goto fail; + } + + /* bitrev */ + st->bitrev = bitrev = (opus_int16*)KISS_FFT_MALLOC(sizeof(opus_int16)*nfft); + if (st->bitrev==NULL) + goto fail; + compute_bitrev_table(0, bitrev, 1,1, st->factors,st); + + /* Initialize architecture specific fft parameters */ + if (opus_fft_alloc_arch(st, arch)) + goto fail; + } + return st; +fail: + opus_fft_free(st, arch); + return NULL; +} + +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch) +{ + return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch); +} + +void opus_fft_free_arch_c(kiss_fft_state *st) { + (void)st; +} + +void opus_fft_free(const kiss_fft_state *cfg, int arch) +{ + if (cfg) + { + opus_fft_free_arch((kiss_fft_state *)cfg, arch); + opus_free((opus_int16*)cfg->bitrev); + if (cfg->shift < 0) + opus_free((kiss_twiddle_cpx*)cfg->twiddles); + opus_free((kiss_fft_state*)cfg); + } +} + +#endif /* CUSTOM_MODES */ + +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) +{ + int m2, m; + int p; + int L; + int fstride[MAXFACTORS]; + int i; + int shift; + + /* st->shift can be -1 */ + shift = st->shift>0 ? st->shift : 0; + + fstride[0] = 1; + L=0; + do { + p = st->factors[2*L]; + m = st->factors[2*L+1]; + fstride[L+1] = fstride[L]*p; + L++; + } while(m!=1); + m = st->factors[2*L-1]; + for (i=L-1;i>=0;i--) + { + if (i!=0) + m2 = st->factors[2*i-1]; + else + m2 = 1; + switch (st->factors[2*i]) + { + case 2: + kf_bfly2(fout, m, fstride[i]); + break; + case 4: + kf_bfly4(fout,fstride[i]<scale_shift-1; +#endif + scale = st->scale; + + celt_assert2 (fin != fout, "In-place FFT not supported"); + /* Bit-reverse the input */ + for (i=0;infft;i++) + { + kiss_fft_cpx x = fin[i]; + fout[st->bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift); + fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift); + } + opus_fft_impl(st, fout); +} + + +void opus_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + int i; + celt_assert2 (fin != fout, "In-place FFT not supported"); + /* Bit-reverse the input */ + for (i=0;infft;i++) + fout[st->bitrev[i]] = fin[i]; + for (i=0;infft;i++) + fout[i].i = -fout[i].i; + opus_fft_impl(st, fout); + for (i=0;infft;i++) + fout[i].i = -fout[i].i; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.h new file mode 100644 index 0000000000..267f72f99c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/kiss_fft.h @@ -0,0 +1,210 @@ +/*Copyright (c) 2003-2004, Mark Borgerding + Lots of modifications by Jean-Marc Valin + Copyright (c) 2005-2007, Xiph.Org Foundation + Copyright (c) 2008, Xiph.Org Foundation, CSIRO + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE.*/ + +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include "arch.h" +#include "cpu_support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) memalign(16,nbytes) +#else +#define KISS_FFT_MALLOC opus_alloc +#endif + +#ifdef FIXED_POINT +#include "arch.h" + +# define kiss_fft_scalar opus_int32 +# define kiss_twiddle_scalar opus_int16 + +/* Some 32-bit CPUs would load/store a kiss_twiddle_cpx with a single memory + * access, and could benefit from additional alignment. + */ +# define KISS_TWIDDLE_CPX_ALIGNMENT (sizeof(opus_int32)) + +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# define kiss_twiddle_scalar float +# define KF_SUFFIX _celt_single +# endif +#endif + +#if defined(__GNUC__) && defined(KISS_TWIDDLE_CPX_ALIGNMENT) +#define KISS_TWIDDLE_CPX_ALIGNED __attribute__((aligned(KISS_TWIDDLE_CPX_ALIGNMENT))) +#else +#define KISS_TWIDDLE_CPX_ALIGNED +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct { + kiss_twiddle_scalar r; + kiss_twiddle_scalar i; +} KISS_TWIDDLE_CPX_ALIGNED kiss_twiddle_cpx; + +#define MAXFACTORS 8 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +typedef struct arch_fft_state{ + int is_supported; + void *priv; +} arch_fft_state; + +typedef struct kiss_fft_state{ + int nfft; + opus_val16 scale; +#ifdef FIXED_POINT + int scale_shift; +#endif + int shift; + opus_int16 factors[2*MAXFACTORS]; + const opus_int16 *bitrev; + const kiss_twiddle_cpx *twiddles; + arch_fft_state *arch_fft; +} kiss_fft_state; + +#if defined(HAVE_ARM_NE10) +#include "arm/fft_arm.h" +#endif + +/*typedef struct kiss_fft_state* kiss_fft_cfg;*/ + +/** + * opus_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=opus_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then opus_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch); + +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch); + +/** + * opus_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void opus_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); +void opus_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); +void opus_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); + +void opus_fft_free(const kiss_fft_state *cfg, int arch); + + +void opus_fft_free_arch_c(kiss_fft_state *st); +int opus_fft_alloc_arch_c(kiss_fft_state *st); + +#if !defined(OVERRIDE_OPUS_FFT) +/* Is run-time CPU detection enabled on this platform? */ +#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) + +extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); + +#define opus_fft_alloc_arch(_st, arch) \ + ((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); +#define opus_fft_free_arch(_st, arch) \ + ((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ + +#define opus_fft_alloc_arch(_st, arch) \ + ((void)(arch), opus_fft_alloc_arch_c(_st)) + +#define opus_fft_free_arch(_st, arch) \ + ((void)(arch), opus_fft_free_arch_c(_st)) + +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_fft_c(_cfg, _fin, _fout)) + +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_ifft_c(_cfg, _fin, _fout)) + +#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ +#endif /* end if !defined(OVERRIDE_OPUS_FFT) */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/laplace.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/laplace.c new file mode 100644 index 0000000000..2180966662 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/laplace.c @@ -0,0 +1,235 @@ +/* Copyright (c) 2007 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "laplace.h" +#include "mathops.h" + +/* The minimum probability of an energy delta (out of 32768). */ +#define LAPLACE_LOG_MINP (0) +#define LAPLACE_MINP (1<>15; +} + +void ec_laplace_encode(ec_enc *enc, int *value, unsigned fs, int decay) +{ + unsigned fl; + int val = *value; + fl = 0; + if (val) + { + int s; + int i; + s = -(val<0); + val = (val+s)^s; + fl = fs; + fs = ec_laplace_get_freq1(fs, decay); + /* Search the decaying part of the PDF.*/ + for (i=1; fs > 0 && i < val; i++) + { + fs *= 2; + fl += fs+2*LAPLACE_MINP; + fs = (fs*(opus_int32)decay)>>15; + } + /* Everything beyond that has probability LAPLACE_MINP. */ + if (!fs) + { + int di; + int ndi_max; + ndi_max = (32768-fl+LAPLACE_MINP-1)>>LAPLACE_LOG_MINP; + ndi_max = (ndi_max-s)>>1; + di = IMIN(val - i, ndi_max - 1); + fl += (2*di+1+s)*LAPLACE_MINP; + fs = IMIN(LAPLACE_MINP, 32768-fl); + *value = (i+di+s)^s; + } + else + { + fs += LAPLACE_MINP; + fl += fs&~s; + } + celt_assert(fl+fs<=32768); + celt_assert(fs>0); + } + ec_encode_bin(enc, fl, fl+fs, 15); +} + +int ec_laplace_decode(ec_dec *dec, unsigned fs, int decay) +{ + int val=0; + unsigned fl; + unsigned fm; + fm = ec_decode_bin(dec, 15); + fl = 0; + if (fm >= fs) + { + val++; + fl = fs; + fs = ec_laplace_get_freq1(fs, decay)+LAPLACE_MINP; + /* Search the decaying part of the PDF.*/ + while(fs > LAPLACE_MINP && fm >= fl+2*fs) + { + fs *= 2; + fl += fs; + fs = ((fs-2*LAPLACE_MINP)*(opus_int32)decay)>>15; + fs += LAPLACE_MINP; + val++; + } + /* Everything beyond that has probability LAPLACE_MINP. */ + if (fs <= LAPLACE_MINP) + { + int di; + di = (fm-fl)>>(LAPLACE_LOG_MINP+1); + val += di; + fl += 2*di*LAPLACE_MINP; + } + if (fm < fl+fs) + val = -val; + else + fl += fs; + } + celt_assert(fl<32768); + celt_assert(fs>0); + celt_assert(fl<=fm); + celt_assert(fm 0 ? 1 : 2); + ec_enc_icdf16(enc, s, sign_icdf, 15); + value = abs(value); + if (value) + { + int i; + opus_uint16 icdf[8]; + icdf[0] = IMAX(7, decay); + for (i=1;i<7;i++) + { + icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15); + } + icdf[7] = 0; + value--; + do { + ec_enc_icdf16(enc, IMIN(value, 7), icdf, 15); + value -= 7; + } while (value >= 0); + } +} + +int ec_laplace_decode_p0(ec_dec *dec, opus_uint16 p0, opus_uint16 decay) +{ + int s; + int value; + opus_uint16 sign_icdf[3]; + sign_icdf[0] = 32768-p0; + sign_icdf[1] = sign_icdf[0]/2; + sign_icdf[2] = 0; + s = ec_dec_icdf16(dec, sign_icdf, 15); + if (s==2) s = -1; + if (s != 0) + { + int i; + int v; + opus_uint16 icdf[8]; + icdf[0] = IMAX(7, decay); + for (i=1;i<7;i++) + { + icdf[i] = IMAX(7-i, (icdf[i-1] * (opus_int32)decay) >> 15); + } + icdf[7] = 0; + value = 1; + do { + v = ec_dec_icdf16(dec, icdf, 15); + value += v; + } while (v == 7); + return s*value; + } else return 0; +} + +#if 0 + +#include +#define NB_VALS 10 +#define DATA_SIZE 10000 +int main() { + ec_enc enc; + ec_dec dec; + unsigned char *ptr; + int i; + int decay, p0; + int val[NB_VALS] = {6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + /*for (i=0;i>1; + b=1U<>=1; + bshift--; + } + while(bshift>=0); + return g; +} + +#ifdef FIXED_POINT + +opus_val32 frac_div32(opus_val32 a, opus_val32 b) +{ + opus_val16 rcp; + opus_val32 result, rem; + int shift = celt_ilog2(b)-29; + a = VSHR32(a,shift); + b = VSHR32(b,shift); + /* 16-bit reciprocal */ + rcp = ROUND16(celt_rcp(ROUND16(b,16)),3); + result = MULT16_32_Q15(rcp, a); + rem = PSHR32(a,2)-MULT32_32_Q31(result, b); + result = ADD32(result, SHL32(MULT16_32_Q15(rcp, rem),2)); + if (result >= 536870912) /* 2^29 */ + return 2147483647; /* 2^31 - 1 */ + else if (result <= -536870912) /* -2^29 */ + return -2147483647; /* -2^31 */ + else + return SHL32(result, 2); +} + +/** Reciprocal sqrt approximation in the range [0.25,1) (Q16 in, Q14 out) */ +opus_val16 celt_rsqrt_norm(opus_val32 x) +{ + opus_val16 n; + opus_val16 r; + opus_val16 r2; + opus_val16 y; + /* Range of n is [-16384,32767] ([-0.5,1) in Q15). */ + n = x-32768; + /* Get a rough initial guess for the root. + The optimal minimax quadratic approximation (using relative error) is + r = 1.437799046117536+n*(-0.823394375837328+n*0.4096419668459485). + Coefficients here, and the final result r, are Q14.*/ + r = ADD16(23557, MULT16_16_Q15(n, ADD16(-13490, MULT16_16_Q15(n, 6713)))); + /* We want y = x*r*r-1 in Q15, but x is 32-bit Q16 and r is Q14. + We can compute the result from n and r using Q15 multiplies with some + adjustment, carefully done to avoid overflow. + Range of y is [-1564,1594]. */ + r2 = MULT16_16_Q15(r, r); + y = SHL16(SUB16(ADD16(MULT16_16_Q15(r2, n), r2), 16384), 1); + /* Apply a 2nd-order Householder iteration: r += r*y*(y*0.375-0.5). + This yields the Q14 reciprocal square root of the Q16 x, with a maximum + relative error of 1.04956E-4, a (relative) RMSE of 2.80979E-5, and a + peak absolute error of 2.26591/16384. */ + return ADD16(r, MULT16_16_Q15(r, MULT16_16_Q15(y, + SUB16(MULT16_16_Q15(y, 12288), 16384)))); +} + +/** Sqrt approximation (QX input, QX/2 output) */ +opus_val32 celt_sqrt(opus_val32 x) +{ + int k; + opus_val16 n; + opus_val32 rt; + static const opus_val16 C[5] = {23175, 11561, -3011, 1699, -664}; + if (x==0) + return 0; + else if (x>=1073741824) + return 32767; + k = (celt_ilog2(x)>>1)-7; + x = VSHR32(x, 2*k); + n = x-32768; + rt = ADD16(C[0], MULT16_16_Q15(n, ADD16(C[1], MULT16_16_Q15(n, ADD16(C[2], + MULT16_16_Q15(n, ADD16(C[3], MULT16_16_Q15(n, (C[4]))))))))); + rt = VSHR32(rt,7-k); + return rt; +} + +#define L1 32767 +#define L2 -7651 +#define L3 8277 +#define L4 -626 + +static OPUS_INLINE opus_val16 _celt_cos_pi_2(opus_val16 x) +{ + opus_val16 x2; + + x2 = MULT16_16_P15(x,x); + return ADD16(1,MIN16(32766,ADD32(SUB16(L1,x2), MULT16_16_P15(x2, ADD32(L2, MULT16_16_P15(x2, ADD32(L3, MULT16_16_P15(L4, x2 + )))))))); +} + +#undef L1 +#undef L2 +#undef L3 +#undef L4 + +opus_val16 celt_cos_norm(opus_val32 x) +{ + x = x&0x0001ffff; + if (x>SHL32(EXTEND32(1), 16)) + x = SUB32(SHL32(EXTEND32(1), 17),x); + if (x&0x00007fff) + { + if (x0); + i = celt_ilog2(x); + /* n is Q15 with range [0,1). */ + n = VSHR32(x,i-15)-32768; + /* Start with a linear approximation: + r = 1.8823529411764706-0.9411764705882353*n. + The coefficients and the result are Q14 in the range [15420,30840].*/ + r = ADD16(30840, MULT16_16_Q15(-15420, n)); + /* Perform two Newton iterations: + r -= r*((r*n)-1.Q15) + = r*((r*n)+(r-1.Q15)). */ + r = SUB16(r, MULT16_16_Q15(r, + ADD16(MULT16_16_Q15(r, n), ADD16(r, -32768)))); + /* We subtract an extra 1 in the second iteration to avoid overflow; it also + neatly compensates for truncation error in the rest of the process. */ + r = SUB16(r, ADD16(1, MULT16_16_Q15(r, + ADD16(MULT16_16_Q15(r, n), ADD16(r, -32768))))); + /* r is now the Q15 solution to 2/(n+1), with a maximum relative error + of 7.05346E-5, a (relative) RMSE of 2.14418E-5, and a peak absolute + error of 1.24665/32768. */ + return VSHR32(EXTEND32(r),i-16); +} + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mathops.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mathops.h new file mode 100644 index 0000000000..e2eece2937 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mathops.h @@ -0,0 +1,296 @@ +/* Copyright (c) 2002-2008 Jean-Marc Valin + Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file mathops.h + @brief Various math functions +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MATHOPS_H +#define MATHOPS_H + +#include "arch.h" +#include "entcode.h" +#include "os_support.h" + +#define PI 3.141592653f + +/* Multiplies two 16-bit fractional values. Bit-exactness of this macro is important */ +#define FRAC_MUL16(a,b) ((16384+((opus_int32)(opus_int16)(a)*(opus_int16)(b)))>>15) + +unsigned isqrt32(opus_uint32 _val); + +/* CELT doesn't need it for fixed-point, by analysis.c does. */ +#if !defined(FIXED_POINT) || defined(ANALYSIS_C) +#define cA 0.43157974f +#define cB 0.67848403f +#define cC 0.08595542f +#define cE ((float)PI/2) +static OPUS_INLINE float fast_atan2f(float y, float x) { + float x2, y2; + x2 = x*x; + y2 = y*y; + /* For very small values, we don't care about the answer, so + we can just return 0. */ + if (x2 + y2 < 1e-18f) + { + return 0; + } + if(x2>23)-127; + in.i -= (opus_uint32)integer<<23; + frac = in.f - 1.5f; + frac = -0.41445418f + frac*(0.95909232f + + frac*(-0.33951290f + frac*0.16541097f)); + return 1+integer+frac; +} + +/** Base-2 exponential approximation (2^x). */ +static OPUS_INLINE float celt_exp2(float x) +{ + int integer; + float frac; + union { + float f; + opus_uint32 i; + } res; + integer = (int)floor(x); + if (integer < -50) + return 0; + frac = x-integer; + /* K0 = 1, K1 = log(2), K2 = 3-4*log(2), K3 = 3*log(2) - 2 */ + res.f = 0.99992522f + frac * (0.69583354f + + frac * (0.22606716f + 0.078024523f*frac)); + res.i = (res.i + ((opus_uint32)integer<<23)) & 0x7fffffff; + return res.f; +} + +#else +#define celt_log2(x) ((float)(1.442695040888963387*log(x))) +#define celt_exp2(x) ((float)exp(0.6931471805599453094*(x))) +#endif + +#endif + +#ifdef FIXED_POINT + +#include "os_support.h" + +#ifndef OVERRIDE_CELT_ILOG2 +/** Integer log in base2. Undefined for zero and negative numbers */ +static OPUS_INLINE opus_int16 celt_ilog2(opus_int32 x) +{ + celt_sig_assert(x>0); + return EC_ILOG(x)-1; +} +#endif + + +/** Integer log in base2. Defined for zero, but not for negative numbers */ +static OPUS_INLINE opus_int16 celt_zlog2(opus_val32 x) +{ + return x <= 0 ? 0 : celt_ilog2(x); +} + +opus_val16 celt_rsqrt_norm(opus_val32 x); + +opus_val32 celt_sqrt(opus_val32 x); + +opus_val16 celt_cos_norm(opus_val32 x); + +/** Base-2 logarithm approximation (log2(x)). (Q14 input, Q10 output) */ +static OPUS_INLINE opus_val16 celt_log2(opus_val32 x) +{ + int i; + opus_val16 n, frac; + /* -0.41509302963303146, 0.9609890551383969, -0.31836011537636605, + 0.15530808010959576, -0.08556153059057618 */ + static const opus_val16 C[5] = {-6801+(1<<(13-DB_SHIFT)), 15746, -5217, 2545, -1401}; + if (x==0) + return -32767; + i = celt_ilog2(x); + n = VSHR32(x,i-15)-32768-16384; + frac = ADD16(C[0], MULT16_16_Q15(n, ADD16(C[1], MULT16_16_Q15(n, ADD16(C[2], MULT16_16_Q15(n, ADD16(C[3], MULT16_16_Q15(n, C[4])))))))); + return SHL16(i-13,DB_SHIFT)+SHR16(frac,14-DB_SHIFT); +} + +/* + K0 = 1 + K1 = log(2) + K2 = 3-4*log(2) + K3 = 3*log(2) - 2 +*/ +#define D0 16383 +#define D1 22804 +#define D2 14819 +#define D3 10204 + +static OPUS_INLINE opus_val32 celt_exp2_frac(opus_val16 x) +{ + opus_val16 frac; + frac = SHL16(x, 4); + return ADD16(D0, MULT16_16_Q15(frac, ADD16(D1, MULT16_16_Q15(frac, ADD16(D2 , MULT16_16_Q15(D3,frac)))))); +} + +#undef D0 +#undef D1 +#undef D2 +#undef D3 + +/** Base-2 exponential approximation (2^x). (Q10 input, Q16 output) */ +static OPUS_INLINE opus_val32 celt_exp2(opus_val16 x) +{ + int integer; + opus_val16 frac; + integer = SHR16(x,10); + if (integer>14) + return 0x7f000000; + else if (integer < -15) + return 0; + frac = celt_exp2_frac(x-SHL16(integer,10)); + return VSHR32(EXTEND32(frac), -integer-2); +} + +opus_val32 celt_rcp(opus_val32 x); + +#define celt_div(a,b) MULT32_32_Q31((opus_val32)(a),celt_rcp(b)) + +opus_val32 frac_div32(opus_val32 a, opus_val32 b); + +#define M1 32767 +#define M2 -21 +#define M3 -11943 +#define M4 4936 + +/* Atan approximation using a 4th order polynomial. Input is in Q15 format + and normalized by pi/4. Output is in Q15 format */ +static OPUS_INLINE opus_val16 celt_atan01(opus_val16 x) +{ + return MULT16_16_P15(x, ADD32(M1, MULT16_16_P15(x, ADD32(M2, MULT16_16_P15(x, ADD32(M3, MULT16_16_P15(M4, x))))))); +} + +#undef M1 +#undef M2 +#undef M3 +#undef M4 + +/* atan2() approximation valid for positive input values */ +static OPUS_INLINE opus_val16 celt_atan2p(opus_val16 y, opus_val16 x) +{ + if (y < x) + { + opus_val32 arg; + arg = celt_div(SHL32(EXTEND32(y),15),x); + if (arg >= 32767) + arg = 32767; + return SHR16(celt_atan01(EXTRACT16(arg)),1); + } else { + opus_val32 arg; + arg = celt_div(SHL32(EXTEND32(x),15),y); + if (arg >= 32767) + arg = 32767; + return 25736-SHR16(celt_atan01(EXTRACT16(arg)),1); + } +} + +#endif /* FIXED_POINT */ +#endif /* MATHOPS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.c new file mode 100644 index 0000000000..5c6dab5b75 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This is a simple MDCT implementation that uses a N/4 complex FFT + to do most of the work. It should be relatively straightforward to + plug in pretty much and FFT here. + + This replaces the Vorbis FFT (and uses the exact same API), which + was a bit too messy and that was ending up duplicating code + (might as well use the same FFT everywhere). + + The algorithm is similar to (and inspired from) Fabrice Bellard's + MDCT implementation in FFMPEG, but has differences in signs, ordering + and scaling in many places. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include "mdct.h" +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include +#include "os_support.h" +#include "mathops.h" +#include "stack_alloc.h" + +#if defined(MIPSr1_ASM) +#include "mips/mdct_mipsr1.h" +#endif + + +#ifdef CUSTOM_MODES + +int clt_mdct_init(mdct_lookup *l,int N, int maxshift, int arch) +{ + int i; + kiss_twiddle_scalar *trig; + int shift; + int N2=N>>1; + l->n = N; + l->maxshift = maxshift; + for (i=0;i<=maxshift;i++) + { + if (i==0) + l->kfft[i] = opus_fft_alloc(N>>2>>i, 0, 0, arch); + else + l->kfft[i] = opus_fft_alloc_twiddles(N>>2>>i, 0, 0, l->kfft[0], arch); +#ifndef ENABLE_TI_DSPLIB55 + if (l->kfft[i]==NULL) + return 0; +#endif + } + l->trig = trig = (kiss_twiddle_scalar*)opus_alloc((N-(N2>>maxshift))*sizeof(kiss_twiddle_scalar)); + if (l->trig==NULL) + return 0; + for (shift=0;shift<=maxshift;shift++) + { + /* We have enough points that sine isn't necessary */ +#if defined(FIXED_POINT) +#if 1 + for (i=0;i>= 1; + N >>= 1; + } + return 1; +} + +void clt_mdct_clear(mdct_lookup *l, int arch) +{ + int i; + for (i=0;i<=l->maxshift;i++) + opus_fft_free(l->kfft[i], arch); + opus_free((kiss_twiddle_scalar*)l->trig); +} + +#endif /* CUSTOM_MODES */ + +/* Forward MDCT trashes the input array */ +#ifndef OVERRIDE_clt_mdct_forward +void clt_mdct_forward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + opus_val16 scale; +#ifdef FIXED_POINT + /* Allows us to scale with MULT16_32_Q16(), which is faster than + MULT16_32_Q15() on ARM. */ + int scale_shift = st->scale_shift-1; +#endif + SAVE_STACK; + (void)arch; + scale = st->scale; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + ALLOC(f2, N4, kiss_fft_cpx); + + /* Consider the input to be composed of four blocks: [a, b, c, d] */ + /* Window, shuffle, fold */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1); + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1); + const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1; + for(i=0;i<((overlap+3)>>2);i++) + { + /* Real part arranged as -d-cR, Imag part arranged as -b+aR*/ + *yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2); + *yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]); + xp1+=2; + xp2-=2; + wp1+=2; + wp2-=2; + } + wp1 = window; + wp2 = window+overlap-1; + for(;i>2);i++) + { + /* Real part arranged as a-bR, Imag part arranged as -c-dR */ + *yp++ = *xp2; + *yp++ = *xp1; + xp1+=2; + xp2-=2; + } + for(;ibitrev[i]] = yc; + } + } + + /* N/4 complex FFT, does not downscale anymore */ + opus_fft_impl(st, f2); + + /* Post-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_cpx * OPUS_RESTRICT fp = f2; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1); + const kiss_twiddle_scalar *t = &trig[0]; + /* Temp pointers to make it really clear to the compiler what we're doing */ + for(i=0;ii,t[N4+i]) - S_MUL(fp->r,t[i]); + yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; + yp1 += 2*stride; + yp2 -= 2*stride; + } + } + RESTORE_STACK; +} +#endif /* OVERRIDE_clt_mdct_forward */ + +#ifndef OVERRIDE_clt_mdct_backward +void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + const kiss_twiddle_scalar *trig; + (void) arch; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + /* Pre-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); + kiss_fft_scalar * OPUS_RESTRICT yp = out+(overlap>>1); + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + const opus_int16 * OPUS_RESTRICT bitrev = l->kfft[shift]->bitrev; + for(i=0;ikfft[shift], (kiss_fft_cpx*)(out+(overlap>>1))); + + /* Post-rotate and de-shuffle from both ends of the buffer at once to make + it in-place. */ + { + kiss_fft_scalar * yp0 = out+(overlap>>1); + kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; + /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the + middle pair will be computed twice. */ + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp0[1]; + im = yp0[0]; + t0 = t[i]; + t1 = t[N4+i]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp1[1]; + im = yp1[0]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + } + } + + /* Mirror on both sides for TDAC */ + { + kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + const opus_val16 * OPUS_RESTRICT wp1 = window; + const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; + + for(i = 0; i < overlap/2; i++) + { + kiss_fft_scalar x1, x2; + x1 = *xp1; + x2 = *yp1; + *yp1++ = SUB32_ovflw(MULT16_32_Q15(*wp2, x2), MULT16_32_Q15(*wp1, x1)); + *xp1-- = ADD32_ovflw(MULT16_32_Q15(*wp1, x2), MULT16_32_Q15(*wp2, x1)); + wp1++; + wp2--; + } + } +} +#endif /* OVERRIDE_clt_mdct_backward */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.h new file mode 100644 index 0000000000..160ae4e0f3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mdct.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This is a simple MDCT implementation that uses a N/4 complex FFT + to do most of the work. It should be relatively straightforward to + plug in pretty much and FFT here. + + This replaces the Vorbis FFT (and uses the exact same API), which + was a bit too messy and that was ending up duplicating code + (might as well use the same FFT everywhere). + + The algorithm is similar to (and inspired from) Fabrice Bellard's + MDCT implementation in FFMPEG, but has differences in signs, ordering + and scaling in many places. +*/ + +#ifndef MDCT_H +#define MDCT_H + +#include "opus_defines.h" +#include "kiss_fft.h" +#include "arch.h" + +typedef struct { + int n; + int maxshift; + const kiss_fft_state *kfft[4]; + const kiss_twiddle_scalar * OPUS_RESTRICT trig; +} mdct_lookup; + +#if defined(HAVE_ARM_NE10) +#include "arm/mdct_arm.h" +#endif + + +int clt_mdct_init(mdct_lookup *l,int N, int maxshift, int arch); +void clt_mdct_clear(mdct_lookup *l, int arch); + +/** Compute a forward MDCT and scale by 4/N, trashes the input array */ +void clt_mdct_forward_c(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +/** Compute a backward MDCT (no scaling) and performs weighted overlap-add + (scales implicitly by 1/2) */ +void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, + int overlap, int shift, int stride, int arch); + +#if !defined(OVERRIDE_OPUS_MDCT) +/* Is run-time CPU detection enabled on this platform? */ +#if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) + +extern void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])( + const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, const opus_val16 *window, + int overlap, int shift, int stride, int arch); + +#define clt_mdct_forward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + ((*CLT_MDCT_FORWARD_IMPL[(arch)&OPUS_ARCHMASK])(_l, _in, _out, \ + _window, _overlap, _shift, \ + _stride, _arch)) + +extern void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])( + const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, const opus_val16 *window, + int overlap, int shift, int stride, int arch); + +#define clt_mdct_backward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + (*CLT_MDCT_BACKWARD_IMPL[(arch)&OPUS_ARCHMASK])(_l, _in, _out, \ + _window, _overlap, _shift, \ + _stride, _arch) + +#else /* if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) */ + +#define clt_mdct_forward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + clt_mdct_forward_c(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) + +#define clt_mdct_backward(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) \ + clt_mdct_backward_c(_l, _in, _out, _window, _overlap, _shift, _stride, _arch) + +#endif /* end if defined(OPUS_HAVE_RTCD) && defined(HAVE_ARM_NE10) && !defined(FIXED_POINT) */ +#endif /* end if !defined(OVERRIDE_OPUS_MDCT) */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/meson.build new file mode 100644 index 0000000000..46601ca135 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/meson.build @@ -0,0 +1,64 @@ +celt_sources = sources['CELT_SOURCES'] + +celt_sse_sources = sources['CELT_SOURCES_SSE'] + +celt_sse2_sources = sources['CELT_SOURCES_SSE2'] + +celt_sse4_1_sources = sources['CELT_SOURCES_SSE4_1'] + +celt_avx2_sources = sources['CELT_SOURCES_AVX2'] + +celt_neon_intr_sources = sources['CELT_SOURCES_ARM_NEON_INTR'] + +celt_static_libs = [] + +if host_cpu_family in ['x86', 'x86_64'] and opus_conf.has('OPUS_HAVE_RTCD') + celt_sources += sources['CELT_SOURCES_X86_RTCD'] +endif + +foreach intr_name : ['sse', 'sse2', 'sse4_1', 'avx2', 'neon_intr'] + have_intr = get_variable('have_' + intr_name) + if not have_intr + continue + endif + + intr_sources = get_variable('celt_@0@_sources'.format(intr_name)) + intr_args = get_variable('opus_@0@_args'.format(intr_name), []) + celt_static_libs += static_library('celt_' + intr_name, intr_sources, + c_args: intr_args, + include_directories: opus_includes, + install: false) +endforeach + +have_arm_intrinsics_or_asm = have_arm_ne10 +if (intrinsics_support.length() + asm_optimization.length() + inline_optimization.length()) > 0 + have_arm_intrinsics_or_asm = true +endif + +if host_cpu_family in ['arm', 'aarch64'] and have_arm_intrinsics_or_asm + if opus_conf.has('OPUS_HAVE_RTCD') + celt_sources += sources['CELT_SOURCES_ARM_RTCD'] + endif + if have_arm_ne10 + celt_sources += sources['CELT_SOURCES_ARM_NE10'] + endif + if opus_arm_external_asm + subdir('arm') + celt_static_libs += static_library('celt-armasm', + celt_arm_armopts_s, celt_sources_arm_asm, + install: false) + endif +endif + +celt_c_args = [] +if host_system == 'windows' + celt_c_args += ['-DDLL_EXPORT'] +endif + +celt_lib = static_library('opus-celt', + celt_sources, + c_args: celt_c_args, + include_directories: opus_includes, + link_whole: celt_static_libs, + dependencies: libm, + install: false) diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mfrngcod.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mfrngcod.h new file mode 100644 index 0000000000..809152a59a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mfrngcod.h @@ -0,0 +1,48 @@ +/* Copyright (c) 2001-2008 Timothy B. Terriberry + Copyright (c) 2008-2009 Xiph.Org Foundation */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(_mfrngcode_H) +# define _mfrngcode_H (1) +# include "entcode.h" + +/*Constants used by the entropy encoder/decoder.*/ + +/*The number of bits to output at a time.*/ +# define EC_SYM_BITS (8) +/*The total number of bits in each of the state registers.*/ +# define EC_CODE_BITS (32) +/*The maximum symbol value.*/ +# define EC_SYM_MAX ((1U<>EC_SYM_BITS) +/*The number of bits available for the last, partial symbol in the code field.*/ +# define EC_CODE_EXTRA ((EC_CODE_BITS-2)%EC_SYM_BITS+1) +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/celt_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/celt_mipsr1.h new file mode 100644 index 0000000000..d1b25c204d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/celt_mipsr1.h @@ -0,0 +1,152 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2010 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CELT_MIPSR1_H__ +#define CELT_MIPSR1_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define CELT_C + +#include "os_support.h" +#include "mdct.h" +#include +#include "celt.h" +#include "pitch.h" +#include "bands.h" +#include "modes.h" +#include "entcode.h" +#include "quant_bands.h" +#include "rate.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "float_cast.h" +#include +#include "celt_lpc.h" +#include "vq.h" + +#define OVERRIDE_COMB_FILTER_CONST +#define OVERRIDE_comb_filter +void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, + opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, + const opus_val16 *window, int overlap, int arch) +{ + int i; + opus_val32 x0, x1, x2, x3, x4; + + (void)arch; + + /* printf ("%d %d %f %f\n", T0, T1, g0, g1); */ + opus_val16 g00, g01, g02, g10, g11, g12; + static const opus_val16 gains[3][3] = { + {QCONST16(0.3066406250f, 15), QCONST16(0.2170410156f, 15), QCONST16(0.1296386719f, 15)}, + {QCONST16(0.4638671875f, 15), QCONST16(0.2680664062f, 15), QCONST16(0.f, 15)}, + {QCONST16(0.7998046875f, 15), QCONST16(0.1000976562f, 15), QCONST16(0.f, 15)}}; + + if (g0==0 && g1==0) + { + /* OPT: Happens to work without the OPUS_MOVE(), but only because the current encoder already copies x to y */ + if (x!=y) + OPUS_MOVE(y, x, N); + return; + } + + g00 = MULT16_16_P15(g0, gains[tapset0][0]); + g01 = MULT16_16_P15(g0, gains[tapset0][1]); + g02 = MULT16_16_P15(g0, gains[tapset0][2]); + g10 = MULT16_16_P15(g1, gains[tapset1][0]); + g11 = MULT16_16_P15(g1, gains[tapset1][1]); + g12 = MULT16_16_P15(g1, gains[tapset1][2]); + x1 = x[-T1+1]; + x2 = x[-T1 ]; + x3 = x[-T1-1]; + x4 = x[-T1-2]; + /* If the filter didn't change, we don't need the overlap */ + if (g0==g1 && T0==T1 && tapset0==tapset1) + overlap=0; + + for (i=0;itwiddles[fstride*m]; + yb = st->twiddles[fstride*2*m]; +#endif + + tw=st->twiddles; + + for (i=0;ir += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + scratch[5].r = scratch[0].r + S_MUL_ADD(scratch[7].r,ya.r,scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL_ADD(scratch[7].i,ya.r,scratch[8].i,yb.r); + + scratch[6].r = S_MUL_ADD(scratch[10].i,ya.i,scratch[9].i,yb.i); + scratch[6].i = -S_MUL_ADD(scratch[10].r,ya.i,scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL_ADD(scratch[7].r,yb.r,scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL_ADD(scratch[7].i,yb.r,scratch[8].i,ya.r); + + scratch[12].r = S_MUL_SUB(scratch[9].i,ya.i,scratch[10].i,yb.i); + scratch[12].i = S_MUL_SUB(scratch[10].r,yb.i,scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } + } +} + + +#endif /* KISS_FFT_MIPSR1_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/mdct_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/mdct_mipsr1.h new file mode 100644 index 0000000000..7456c181a5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/mdct_mipsr1.h @@ -0,0 +1,288 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* This is a simple MDCT implementation that uses a N/4 complex FFT + to do most of the work. It should be relatively straightforward to + plug in pretty much and FFT here. + + This replaces the Vorbis FFT (and uses the exact same API), which + was a bit too messy and that was ending up duplicating code + (might as well use the same FFT everywhere). + + The algorithm is similar to (and inspired from) Fabrice Bellard's + MDCT implementation in FFMPEG, but has differences in signs, ordering + and scaling in many places. +*/ +#ifndef MDCT_MIPSR1_H__ +#define MDCT_MIPSR1_H__ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include "mdct.h" +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include +#include "os_support.h" +#include "mathops.h" +#include "stack_alloc.h" + +/* Forward MDCT trashes the input array */ +#define OVERRIDE_clt_mdct_forward +void clt_mdct_forward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + opus_val16 scale; +#ifdef FIXED_POINT + /* Allows us to scale with MULT16_32_Q16(), which is faster than + MULT16_32_Q15() on ARM. */ + int scale_shift = st->scale_shift-1; +#endif + + (void)arch; + + SAVE_STACK; + scale = st->scale; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + ALLOC(f2, N4, kiss_fft_cpx); + + /* Consider the input to be composed of four blocks: [a, b, c, d] */ + /* Window, shuffle, fold */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1); + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1); + const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1; + for(i=0;i<((overlap+3)>>2);i++) + { + /* Real part arranged as -d-cR, Imag part arranged as -b+aR*/ + *yp++ = S_MUL_ADD(*wp2, xp1[N2],*wp1,*xp2); + *yp++ = S_MUL_SUB(*wp1, *xp1,*wp2, xp2[-N2]); + xp1+=2; + xp2-=2; + wp1+=2; + wp2-=2; + } + wp1 = window; + wp2 = window+overlap-1; + for(;i>2);i++) + { + /* Real part arranged as a-bR, Imag part arranged as -c-dR */ + *yp++ = *xp2; + *yp++ = *xp1; + xp1+=2; + xp2-=2; + } + for(;ibitrev[i]] = yc; + } + } + + /* N/4 complex FFT, does not downscale anymore */ + opus_fft_impl(st, f2); + + /* Post-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_cpx * OPUS_RESTRICT fp = f2; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1); + const kiss_twiddle_scalar *t = &trig[0]; + /* Temp pointers to make it really clear to the compiler what we're doing */ + for(i=0;ii,t[N4+i] , fp->r,t[i]); + yi = S_MUL_ADD(fp->r,t[N4+i] ,fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; + yp1 += 2*stride; + yp2 -= 2*stride; + } + } + RESTORE_STACK; +} + +#define OVERRIDE_clt_mdct_backward +void clt_mdct_backward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + const kiss_twiddle_scalar *trig; + + (void)arch; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + /* Pre-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); + kiss_fft_scalar * OPUS_RESTRICT yp = out+(overlap>>1); + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + const opus_int16 * OPUS_RESTRICT bitrev = l->kfft[shift]->bitrev; + for(i=0;ikfft[shift], (kiss_fft_cpx*)(out+(overlap>>1))); + + /* Post-rotate and de-shuffle from both ends of the buffer at once to make + it in-place. */ + { + kiss_fft_scalar * OPUS_RESTRICT yp0 = out+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; + /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the + middle pair will be computed twice. */ + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp0[1]; + im = yp0[0]; + t0 = t[i]; + t1 = t[N4+i]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL_ADD(re,t0 , im,t1); + yi = S_MUL_SUB(re,t1 , im,t0); + /* We swap real and imag because we're using an FFT instead of an IFFT. */ + re = yp1[1]; + im = yp1[0]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL_ADD(re,t0,im,t1); + yi = S_MUL_SUB(re,t1,im,t0); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + } + } + + /* Mirror on both sides for TDAC */ + { + kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + const opus_val16 * OPUS_RESTRICT wp1 = window; + const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; + + for(i = 0; i < overlap/2; i++) + { + kiss_fft_scalar x1, x2; + x1 = *xp1; + x2 = *yp1; + *yp1++ = MULT16_32_Q15(*wp2, x2) - MULT16_32_Q15(*wp1, x1); + *xp1-- = MULT16_32_Q15(*wp1, x2) + MULT16_32_Q15(*wp2, x1); + wp1++; + wp2--; + } + } +} +#endif /* MDCT_MIPSR1_H__ */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/pitch_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/pitch_mipsr1.h new file mode 100644 index 0000000000..a9500aff58 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/mips/pitch_mipsr1.h @@ -0,0 +1,161 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file pitch.h + @brief Pitch analysis + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef PITCH_MIPSR1_H +#define PITCH_MIPSR1_H + +#define OVERRIDE_DUAL_INNER_PROD +static inline void dual_inner_prod(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2, int arch) +{ + int j; + opus_val32 xy01=0; + opus_val32 xy02=0; + + (void)arch; + + asm volatile("MULT $ac1, $0, $0"); + asm volatile("MULT $ac2, $0, $0"); + /* Compute the norm of X+Y and X-Y as |X|^2 + |Y|^2 +/- sum(xy) */ + for (j=0;j=0;i--) + { + celt_norm x1, x2; + x1 = Xptr[0]; + x2 = Xptr[stride]; + Xptr[stride] = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x2), s, x1), 15)); + *Xptr-- = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x1), ms, x2), 15)); + } +} + +#define OVERRIDE_renormalise_vector +void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) +{ + int i; +#ifdef FIXED_POINT + int k; +#endif + opus_val32 E = EPSILON; + opus_val16 g; + opus_val32 t; + celt_norm *xptr = X; + int X0, X1; + + (void)arch; + + asm volatile("mult $ac1, $0, $0"); + asm volatile("MTLO %0, $ac1" : :"r" (E)); + /*if(N %4) + printf("error");*/ + for (i=0;i>1; +#endif + t = VSHR32(E, 2*(k-7)); + g = MULT16_16_P15(celt_rsqrt_norm(t),gain); + + xptr = X; + for (i=0;i= Fs) + break; + + /* Find where the linear part ends (i.e. where the spacing is more than min_width */ + for (lin=0;lin= res) + break; + + low = (bark_freq[lin]+res/2)/res; + high = nBark-lin; + *nbEBands = low+high; + eBands = opus_alloc(sizeof(opus_int16)*(*nbEBands+2)); + + if (eBands==NULL) + return NULL; + + /* Linear spacing (min_width) */ + for (i=0;i0) + offset = eBands[low-1]*res - bark_freq[lin-1]; + /* Spacing follows critical bands */ + for (i=0;i frame_size) + eBands[*nbEBands] = frame_size; + for (i=1;i<*nbEBands-1;i++) + { + if (eBands[i+1]-eBands[i] < eBands[i]-eBands[i-1]) + { + eBands[i] -= (2*eBands[i]-eBands[i-1]-eBands[i+1])/2; + } + } + /* Remove any empty bands. */ + for (i=j=0;i<*nbEBands;i++) + if(eBands[i+1]>eBands[j]) + eBands[++j]=eBands[i+1]; + *nbEBands=j; + + for (i=1;i<*nbEBands;i++) + { + /* Every band must be smaller than the last band. */ + celt_assert(eBands[i]-eBands[i-1]<=eBands[*nbEBands]-eBands[*nbEBands-1]); + /* Each band must be no larger than twice the size of the previous one. */ + celt_assert(eBands[i+1]-eBands[i]<=2*(eBands[i]-eBands[i-1])); + } + + return eBands; +} + +static void compute_allocation_table(CELTMode *mode) +{ + int i, j; + unsigned char *allocVectors; + int maxBands = sizeof(eband5ms)/sizeof(eband5ms[0])-1; + + mode->nbAllocVectors = BITALLOC_SIZE; + allocVectors = opus_alloc(sizeof(unsigned char)*(BITALLOC_SIZE*mode->nbEBands)); + if (allocVectors==NULL) + { + mode->allocVectors = NULL; + return; + } + + /* Check for standard mode */ + if (mode->Fs == 400*(opus_int32)mode->shortMdctSize) + { + for (i=0;inbEBands;i++) + allocVectors[i] = band_allocation[i]; + mode->allocVectors = allocVectors; + return; + } + /* If not the standard mode, interpolate */ + /* Compute per-codec-band allocation from per-critical-band matrix */ + for (i=0;inbEBands;j++) + { + int k; + for (k=0;k mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize) + break; + } + if (k>maxBands-1) + allocVectors[i*mode->nbEBands+j] = band_allocation[i*maxBands + maxBands-1]; + else { + opus_int32 a0, a1; + a1 = mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize - 400*(opus_int32)eband5ms[k-1]; + a0 = 400*(opus_int32)eband5ms[k] - mode->eBands[j]*(opus_int32)mode->Fs/mode->shortMdctSize; + allocVectors[i*mode->nbEBands+j] = (a0*band_allocation[i*maxBands+k-1] + + a1*band_allocation[i*maxBands+k])/(a0+a1); + } + } + } + + /*printf ("\n"); + for (i=0;inbEBands;j++) + printf ("%d ", allocVectors[i*mode->nbEBands+j]); + printf ("\n"); + } + exit(0);*/ + + mode->allocVectors = allocVectors; +} + +#endif /* CUSTOM_MODES */ + +CELTMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error) +{ + int i; +#ifdef CUSTOM_MODES + CELTMode *mode=NULL; + int res; + opus_val16 *window; + opus_int16 *logN; + int LM; + int arch = opus_select_arch(); + ALLOC_STACK; +#if !defined(VAR_ARRAYS) && !defined(USE_ALLOCA) + if (global_stack==NULL) + goto failure; +#endif +#endif + +#ifndef CUSTOM_MODES_ONLY + for (i=0;iFs && + (frame_size<shortMdctSize*static_mode_list[i]->nbShortMdcts) + { + if (error) + *error = OPUS_OK; + return (CELTMode*)static_mode_list[i]; + } + } + } +#endif /* CUSTOM_MODES_ONLY */ + +#ifndef CUSTOM_MODES + if (error) + *error = OPUS_BAD_ARG; + return NULL; +#else + + /* The good thing here is that permutation of the arguments will automatically be invalid */ + + if (Fs < 8000 || Fs > 96000) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + if (frame_size < 40 || frame_size > 1024 || frame_size%2!=0) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + /* Frames of less than 1ms are not supported. */ + if ((opus_int32)frame_size*1000 < Fs) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + + if ((opus_int32)frame_size*75 >= Fs && (frame_size%16)==0) + { + LM = 3; + } else if ((opus_int32)frame_size*150 >= Fs && (frame_size%8)==0) + { + LM = 2; + } else if ((opus_int32)frame_size*300 >= Fs && (frame_size%4)==0) + { + LM = 1; + } else + { + LM = 0; + } + + /* Shorts longer than 3.3ms are not supported. */ + if ((opus_int32)(frame_size>>LM)*300 > Fs) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + + mode = opus_alloc(sizeof(CELTMode)); + if (mode==NULL) + goto failure; + mode->Fs = Fs; + + /* Pre/de-emphasis depends on sampling rate. The "standard" pre-emphasis + is defined as A(z) = 1 - 0.85*z^-1 at 48 kHz. Other rates should + approximate that. */ + if(Fs < 12000) /* 8 kHz */ + { + mode->preemph[0] = QCONST16(0.3500061035f, 15); + mode->preemph[1] = -QCONST16(0.1799926758f, 15); + mode->preemph[2] = QCONST16(0.2719968125f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(3.6765136719f, 13); + } else if(Fs < 24000) /* 16 kHz */ + { + mode->preemph[0] = QCONST16(0.6000061035f, 15); + mode->preemph[1] = -QCONST16(0.1799926758f, 15); + mode->preemph[2] = QCONST16(0.4424998650f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(2.2598876953f, 13); + } else if(Fs < 40000) /* 32 kHz */ + { + mode->preemph[0] = QCONST16(0.7799987793f, 15); + mode->preemph[1] = -QCONST16(0.1000061035f, 15); + mode->preemph[2] = QCONST16(0.7499771125f, SIG_SHIFT); /* exact 1/preemph[3] */ + mode->preemph[3] = QCONST16(1.3333740234f, 13); + } else /* 48 kHz */ + { + mode->preemph[0] = QCONST16(0.8500061035f, 15); + mode->preemph[1] = QCONST16(0.0f, 15); + mode->preemph[2] = QCONST16(1.f, SIG_SHIFT); + mode->preemph[3] = QCONST16(1.f, 13); + } + + mode->maxLM = LM; + mode->nbShortMdcts = 1<shortMdctSize = frame_size/mode->nbShortMdcts; + res = (mode->Fs+mode->shortMdctSize)/(2*mode->shortMdctSize); + + mode->eBands = compute_ebands(Fs, mode->shortMdctSize, res, &mode->nbEBands); + if (mode->eBands==NULL) + goto failure; +#if !defined(SMALL_FOOTPRINT) + /* Make sure we don't allocate a band larger than our PVQ table. + 208 should be enough, but let's be paranoid. */ + if ((mode->eBands[mode->nbEBands] - mode->eBands[mode->nbEBands-1])< + 208) { + goto failure; + } +#endif + + mode->effEBands = mode->nbEBands; + while (mode->eBands[mode->effEBands] > mode->shortMdctSize) + mode->effEBands--; + + /* Overlap must be divisible by 4 */ + mode->overlap = ((mode->shortMdctSize>>2)<<2); + + compute_allocation_table(mode); + if (mode->allocVectors==NULL) + goto failure; + + window = (opus_val16*)opus_alloc(mode->overlap*sizeof(opus_val16)); + if (window==NULL) + goto failure; + +#ifndef FIXED_POINT + for (i=0;ioverlap;i++) + window[i] = Q15ONE*sin(.5*M_PI* sin(.5*M_PI*(i+.5)/mode->overlap) * sin(.5*M_PI*(i+.5)/mode->overlap)); +#else + for (i=0;ioverlap;i++) + window[i] = MIN32(32767,floor(.5+32768.*sin(.5*M_PI* sin(.5*M_PI*(i+.5)/mode->overlap) * sin(.5*M_PI*(i+.5)/mode->overlap)))); +#endif + mode->window = window; + + logN = (opus_int16*)opus_alloc(mode->nbEBands*sizeof(opus_int16)); + if (logN==NULL) + goto failure; + + for (i=0;inbEBands;i++) + logN[i] = log2_frac(mode->eBands[i+1]-mode->eBands[i], BITRES); + mode->logN = logN; + + compute_pulse_cache(mode, mode->maxLM); + + if (clt_mdct_init(&mode->mdct, 2*mode->shortMdctSize*mode->nbShortMdcts, + mode->maxLM, arch) == 0) + goto failure; + + if (error) + *error = OPUS_OK; + + return mode; +failure: + if (error) + *error = OPUS_ALLOC_FAIL; + if (mode!=NULL) + opus_custom_mode_destroy(mode); + return NULL; +#endif /* !CUSTOM_MODES */ +} + +#ifdef CUSTOM_MODES +void opus_custom_mode_destroy(CELTMode *mode) +{ + int arch = opus_select_arch(); + + if (mode == NULL) + return; +#ifndef CUSTOM_MODES_ONLY + { + int i; + for (i=0;ieBands); + opus_free((unsigned char*)mode->allocVectors); + + opus_free((opus_val16*)mode->window); + opus_free((opus_int16*)mode->logN); + + opus_free((opus_int16*)mode->cache.index); + opus_free((unsigned char*)mode->cache.bits); + opus_free((unsigned char*)mode->cache.caps); + clt_mdct_clear(&mode->mdct, arch); + + opus_free((CELTMode *)mode); +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/modes.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/modes.h new file mode 100644 index 0000000000..be813ccc8b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/modes.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MODES_H +#define MODES_H + +#include "opus_types.h" +#include "celt.h" +#include "arch.h" +#include "mdct.h" +#include "entenc.h" +#include "entdec.h" + +#define MAX_PERIOD 1024 + +typedef struct { + int size; + const opus_int16 *index; + const unsigned char *bits; + const unsigned char *caps; +} PulseCache; + +/** Mode definition (opaque) + @brief Mode definition + */ +struct OpusCustomMode { + opus_int32 Fs; + int overlap; + + int nbEBands; + int effEBands; + opus_val16 preemph[4]; + const opus_int16 *eBands; /**< Definition for each "pseudo-critical band" */ + + int maxLM; + int nbShortMdcts; + int shortMdctSize; + + int nbAllocVectors; /**< Number of lines in the matrix below */ + const unsigned char *allocVectors; /**< Number of bits in each band for several rates */ + const opus_int16 *logN; + + const opus_val16 *window; + mdct_lookup mdct; + PulseCache cache; +}; + + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/opus_custom_demo.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/opus_custom_demo.c new file mode 100644 index 0000000000..ae41c0de5a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/opus_custom_demo.c @@ -0,0 +1,210 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_custom.h" +#include "arch.h" +#include +#include +#include +#include + +#define MAX_PACKET 1275 + +int main(int argc, char *argv[]) +{ + int err; + char *inFile, *outFile; + FILE *fin, *fout; + OpusCustomMode *mode=NULL; + OpusCustomEncoder *enc; + OpusCustomDecoder *dec; + int len; + opus_int32 frame_size, channels, rate; + int bytes_per_packet; + unsigned char data[MAX_PACKET]; + int complexity; +#if !(defined (FIXED_POINT) && !defined(CUSTOM_MODES)) && defined(RESYNTH) + int i; + double rmsd = 0; +#endif + int count = 0; + opus_int32 skip; + opus_int16 *in, *out; + if (argc != 9 && argc != 8 && argc != 7) + { + fprintf (stderr, "Usage: test_opus_custom " + " [ [packet loss rate]] " + " \n"); + return 1; + } + + rate = (opus_int32)atol(argv[1]); + channels = atoi(argv[2]); + frame_size = atoi(argv[3]); + mode = opus_custom_mode_create(rate, frame_size, NULL); + if (mode == NULL) + { + fprintf(stderr, "failed to create a mode\n"); + return 1; + } + + bytes_per_packet = atoi(argv[4]); + if (bytes_per_packet < 0 || bytes_per_packet > MAX_PACKET) + { + fprintf (stderr, "bytes per packet must be between 0 and %d\n", + MAX_PACKET); + return 1; + } + + inFile = argv[argc-2]; + fin = fopen(inFile, "rb"); + if (!fin) + { + fprintf (stderr, "Could not open input file %s\n", argv[argc-2]); + return 1; + } + outFile = argv[argc-1]; + fout = fopen(outFile, "wb+"); + if (!fout) + { + fprintf (stderr, "Could not open output file %s\n", argv[argc-1]); + fclose(fin); + return 1; + } + + enc = opus_custom_encoder_create(mode, channels, &err); + if (err != 0) + { + fprintf(stderr, "Failed to create the encoder: %s\n", opus_strerror(err)); + fclose(fin); + fclose(fout); + return 1; + } + dec = opus_custom_decoder_create(mode, channels, &err); + if (err != 0) + { + fprintf(stderr, "Failed to create the decoder: %s\n", opus_strerror(err)); + fclose(fin); + fclose(fout); + return 1; + } + opus_custom_decoder_ctl(dec, OPUS_GET_LOOKAHEAD(&skip)); + + if (argc>7) + { + complexity=atoi(argv[5]); + opus_custom_encoder_ctl(enc,OPUS_SET_COMPLEXITY(complexity)); + } + + in = (opus_int16*)malloc(frame_size*channels*sizeof(opus_int16)); + out = (opus_int16*)malloc(frame_size*channels*sizeof(opus_int16)); + + while (!feof(fin)) + { + int ret; + err = fread(in, sizeof(short), frame_size*channels, fin); + if (feof(fin)) + break; + len = opus_custom_encode(enc, in, frame_size, data, bytes_per_packet); + if (len <= 0) + fprintf (stderr, "opus_custom_encode() failed: %s\n", opus_strerror(len)); + + /* This is for simulating bit errors */ +#if 0 + int errors = 0; + int eid = 0; + /* This simulates random bit error */ + for (i=0;i 0) + { + rmsd = sqrt(rmsd/(1.0*frame_size*channels*count)); + fprintf (stderr, "Error: encoder doesn't match decoder\n"); + fprintf (stderr, "RMS mismatch is %f\n", rmsd); + return 1; + } else { + fprintf (stderr, "Encoder matches decoder!!\n"); + } +#endif + return 0; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/os_support.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/os_support.h new file mode 100644 index 0000000000..7d2d378116 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/os_support.h @@ -0,0 +1,99 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: os_support.h + This is the (tiny) OS abstraction layer. Aside from math.h, this is the + only place where system headers are allowed. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef OS_SUPPORT_H +#define OS_SUPPORT_H + +#ifdef CUSTOM_SUPPORT +# include "custom_support.h" +#endif + +#include "opus_types.h" +#include "opus_defines.h" + +#include +#include + +/** Opus wrapper for malloc(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */ +#ifndef OVERRIDE_OPUS_ALLOC +static OPUS_INLINE void *opus_alloc (size_t size) +{ + return malloc(size); +} +#endif + +#ifndef OVERRIDE_OPUS_REALLOC +static OPUS_INLINE void *opus_realloc (void *ptr, size_t size) +{ + return realloc(ptr, size); +} +#endif + +/** Used only for non-threadsafe pseudostack. + If desired, this can always return the same area of memory rather than allocating a new one every time. */ +#ifndef OVERRIDE_OPUS_ALLOC_SCRATCH +static OPUS_INLINE void *opus_alloc_scratch (size_t size) +{ + /* Scratch space doesn't need to be cleared */ + return opus_alloc(size); +} +#endif + +/** Opus wrapper for free(). To do your own dynamic allocation replace this function, opus_realloc, and opus_free */ +#ifndef OVERRIDE_OPUS_FREE +static OPUS_INLINE void opus_free (void *ptr) +{ + free(ptr); +} +#endif + +/** Copy n elements from src to dst. The 0* term provides compile-time type checking */ +#ifndef OVERRIDE_OPUS_COPY +#define OPUS_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Copy n elements from src to dst, allowing overlapping regions. The 0* term + provides compile-time type checking */ +#ifndef OVERRIDE_OPUS_MOVE +#define OPUS_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) +#endif + +/** Set n elements of dst to zero */ +#ifndef OVERRIDE_OPUS_CLEAR +#define OPUS_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst)))) +#endif + +/*#ifdef __GNUC__ +#pragma GCC poison printf sprintf +#pragma GCC poison malloc free realloc calloc +#endif*/ + +#endif /* OS_SUPPORT_H */ + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/pitch.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/pitch.c new file mode 100644 index 0000000000..e33c60a3bf --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/pitch.c @@ -0,0 +1,555 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/** + @file pitch.c + @brief Pitch analysis + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pitch.h" +#include "os_support.h" +#include "modes.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "celt_lpc.h" + +static void find_best_pitch(opus_val32 *xcorr, opus_val16 *y, int len, + int max_pitch, int *best_pitch +#ifdef FIXED_POINT + , int yshift, opus_val32 maxcorr +#endif + ) +{ + int i, j; + opus_val32 Syy=1; + opus_val16 best_num[2]; + opus_val32 best_den[2]; +#ifdef FIXED_POINT + int xshift; + + xshift = celt_ilog2(maxcorr)-14; +#endif + + best_num[0] = -1; + best_num[1] = -1; + best_den[0] = 0; + best_den[1] = 0; + best_pitch[0] = 0; + best_pitch[1] = 1; + for (j=0;j0) + { + opus_val16 num; + opus_val32 xcorr16; + xcorr16 = EXTRACT16(VSHR32(xcorr[i], xshift)); +#ifndef FIXED_POINT + /* Considering the range of xcorr16, this should avoid both underflows + and overflows (inf) when squaring xcorr16 */ + xcorr16 *= 1e-12f; +#endif + num = MULT16_16_Q15(xcorr16,xcorr16); + if (MULT16_32_Q15(num,best_den[1]) > MULT16_32_Q15(best_num[1],Syy)) + { + if (MULT16_32_Q15(num,best_den[0]) > MULT16_32_Q15(best_num[0],Syy)) + { + best_num[1] = best_num[0]; + best_den[1] = best_den[0]; + best_pitch[1] = best_pitch[0]; + best_num[0] = num; + best_den[0] = Syy; + best_pitch[0] = i; + } else { + best_num[1] = num; + best_den[1] = Syy; + best_pitch[1] = i; + } + } + } + Syy += SHR32(MULT16_16(y[i+len],y[i+len]),yshift) - SHR32(MULT16_16(y[i],y[i]),yshift); + Syy = MAX32(1, Syy); + } +} + +static void celt_fir5(opus_val16 *x, + const opus_val16 *num, + int N) +{ + int i; + opus_val16 num0, num1, num2, num3, num4; + opus_val32 mem0, mem1, mem2, mem3, mem4; + num0=num[0]; + num1=num[1]; + num2=num[2]; + num3=num[3]; + num4=num[4]; + mem0=0; + mem1=0; + mem2=0; + mem3=0; + mem4=0; + for (i=0;i>1;i++) + x_lp[i] = SHR32(x[0][(2*i-1)], shift+2) + SHR32(x[0][(2*i+1)], shift+2) + SHR32(x[0][2*i], shift+1); + x_lp[0] = SHR32(x[0][1], shift+2) + SHR32(x[0][0], shift+1); + if (C==2) + { + for (i=1;i>1;i++) + x_lp[i] += SHR32(x[1][(2*i-1)], shift+2) + SHR32(x[1][(2*i+1)], shift+2) + SHR32(x[1][2*i], shift+1); + x_lp[0] += SHR32(x[1][1], shift+2) + SHR32(x[1][0], shift+1); + } +#else + for (i=1;i>1;i++) + x_lp[i] = .25f*x[0][(2*i-1)] + .25f*x[0][(2*i+1)] + .5f*x[0][2*i]; + x_lp[0] = .25f*x[0][1] + .5f*x[0][0]; + if (C==2) + { + for (i=1;i>1;i++) + x_lp[i] += .25f*x[1][(2*i-1)] + .25f*x[1][(2*i+1)] + .5f*x[1][2*i]; + x_lp[0] += .25f*x[1][1] + .5f*x[1][0]; + } +#endif + _celt_autocorr(x_lp, ac, NULL, 0, + 4, len>>1, arch); + + /* Noise floor -40 dB */ +#ifdef FIXED_POINT + ac[0] += SHR32(ac[0],13); +#else + ac[0] *= 1.0001f; +#endif + /* Lag windowing */ + for (i=1;i<=4;i++) + { + /*ac[i] *= exp(-.5*(2*M_PI*.002*i)*(2*M_PI*.002*i));*/ +#ifdef FIXED_POINT + ac[i] -= MULT16_32_Q15(2*i*i, ac[i]); +#else + ac[i] -= ac[i]*(.008f*i)*(.008f*i); +#endif + } + + _celt_lpc(lpc, ac, 4); + for (i=0;i<4;i++) + { + tmp = MULT16_16_Q15(QCONST16(.9f,15), tmp); + lpc[i] = MULT16_16_Q15(lpc[i], tmp); + } + /* Add a zero */ + lpc2[0] = lpc[0] + QCONST16(.8f,SIG_SHIFT); + lpc2[1] = lpc[1] + MULT16_16_Q15(c1,lpc[0]); + lpc2[2] = lpc[2] + MULT16_16_Q15(c1,lpc[1]); + lpc2[3] = lpc[3] + MULT16_16_Q15(c1,lpc[2]); + lpc2[4] = MULT16_16_Q15(c1,lpc[3]); + celt_fir5(x_lp, lpc2, len>>1); +} + +/* Pure C implementation. */ +#ifdef FIXED_POINT +opus_val32 +#else +void +#endif +celt_pitch_xcorr_c(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch) +{ + +#if 0 /* This is a simple version of the pitch correlation that should work + well on DSPs like Blackfin and TI C5x/C6x */ + int i, j; +#ifdef FIXED_POINT + opus_val32 maxcorr=1; +#endif +#if !defined(OVERRIDE_PITCH_XCORR) + (void)arch; +#endif + for (i=0;i0); + celt_sig_assert(((size_t)_x&3)==0); + for (i=0;i0); + celt_assert(max_pitch>0); + lag = len+max_pitch; + + ALLOC(x_lp4, len>>2, opus_val16); + ALLOC(y_lp4, lag>>2, opus_val16); + ALLOC(xcorr, max_pitch>>1, opus_val32); + + /* Downsample by 2 again */ + for (j=0;j>2;j++) + x_lp4[j] = x_lp[2*j]; + for (j=0;j>2;j++) + y_lp4[j] = y[2*j]; + +#ifdef FIXED_POINT + xmax = celt_maxabs16(x_lp4, len>>2); + ymax = celt_maxabs16(y_lp4, lag>>2); + shift = celt_ilog2(MAX32(1, MAX32(xmax, ymax)))-11; + if (shift>0) + { + for (j=0;j>2;j++) + x_lp4[j] = SHR16(x_lp4[j], shift); + for (j=0;j>2;j++) + y_lp4[j] = SHR16(y_lp4[j], shift); + /* Use double the shift for a MAC */ + shift *= 2; + } else { + shift = 0; + } +#endif + + /* Coarse search with 4x decimation */ + +#ifdef FIXED_POINT + maxcorr = +#endif + celt_pitch_xcorr(x_lp4, y_lp4, xcorr, len>>2, max_pitch>>2, arch); + + find_best_pitch(xcorr, y_lp4, len>>2, max_pitch>>2, best_pitch +#ifdef FIXED_POINT + , 0, maxcorr +#endif + ); + + /* Finer search with 2x decimation */ +#ifdef FIXED_POINT + maxcorr=1; +#endif + for (i=0;i>1;i++) + { + opus_val32 sum; + xcorr[i] = 0; + if (abs(i-2*best_pitch[0])>2 && abs(i-2*best_pitch[1])>2) + continue; +#ifdef FIXED_POINT + sum = 0; + for (j=0;j>1;j++) + sum += SHR32(MULT16_16(x_lp[j],y[i+j]), shift); +#else + sum = celt_inner_prod(x_lp, y+i, len>>1, arch); +#endif + xcorr[i] = MAX32(-1, sum); +#ifdef FIXED_POINT + maxcorr = MAX32(maxcorr, sum); +#endif + } + find_best_pitch(xcorr, y, len>>1, max_pitch>>1, best_pitch +#ifdef FIXED_POINT + , shift+1, maxcorr +#endif + ); + + /* Refine by pseudo-interpolation */ + if (best_pitch[0]>0 && best_pitch[0]<(max_pitch>>1)-1) + { + opus_val32 a, b, c; + a = xcorr[best_pitch[0]-1]; + b = xcorr[best_pitch[0]]; + c = xcorr[best_pitch[0]+1]; + if ((c-a) > MULT16_32_Q15(QCONST16(.7f,15),b-a)) + offset = 1; + else if ((a-c) > MULT16_32_Q15(QCONST16(.7f,15),b-c)) + offset = -1; + else + offset = 0; + } else { + offset = 0; + } + *pitch = 2*best_pitch[0]-offset; + + RESTORE_STACK; +} + +#ifdef FIXED_POINT +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + opus_val32 x2y2; + int sx, sy, shift; + opus_val32 g; + opus_val16 den; + if (xy == 0 || xx == 0 || yy == 0) + return 0; + sx = celt_ilog2(xx)-14; + sy = celt_ilog2(yy)-14; + shift = sx + sy; + x2y2 = SHR32(MULT16_16(VSHR32(xx, sx), VSHR32(yy, sy)), 14); + if (shift & 1) { + if (x2y2 < 32768) + { + x2y2 <<= 1; + shift--; + } else { + x2y2 >>= 1; + shift++; + } + } + den = celt_rsqrt_norm(x2y2); + g = MULT16_32_Q15(den, xy); + g = VSHR32(g, (shift>>1)-1); + return EXTRACT16(MIN32(g, Q15ONE)); +} +#else +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + return xy/celt_sqrt(1+xx*yy); +} +#endif + +static const int second_check[16] = {0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2}; +opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, + int N, int *T0_, int prev_period, opus_val16 prev_gain, int arch) +{ + int k, i, T, T0; + opus_val16 g, g0; + opus_val16 pg; + opus_val32 xy,xx,yy,xy2; + opus_val32 xcorr[3]; + opus_val32 best_xy, best_yy; + int offset; + int minperiod0; + VARDECL(opus_val32, yy_lookup); + SAVE_STACK; + + minperiod0 = minperiod; + maxperiod /= 2; + minperiod /= 2; + *T0_ /= 2; + prev_period /= 2; + N /= 2; + x += maxperiod; + if (*T0_>=maxperiod) + *T0_=maxperiod-1; + + T = T0 = *T0_; + ALLOC(yy_lookup, maxperiod+1, opus_val32); + dual_inner_prod(x, x, x-T0, N, &xx, &xy, arch); + yy_lookup[0] = xx; + yy=xx; + for (i=1;i<=maxperiod;i++) + { + yy = yy+MULT16_16(x[-i],x[-i])-MULT16_16(x[N-i],x[N-i]); + yy_lookup[i] = MAX32(0, yy); + } + yy = yy_lookup[T0]; + best_xy = xy; + best_yy = yy; + g = g0 = compute_pitch_gain(xy, xx, yy); + /* Look for any pitch at T/k */ + for (k=2;k<=15;k++) + { + int T1, T1b; + opus_val16 g1; + opus_val16 cont=0; + opus_val16 thresh; + T1 = celt_udiv(2*T0+k, 2*k); + if (T1 < minperiod) + break; + /* Look for another strong correlation at T1b */ + if (k==2) + { + if (T1+T0>maxperiod) + T1b = T0; + else + T1b = T0+T1; + } else + { + T1b = celt_udiv(2*second_check[k]*T0+k, 2*k); + } + dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2, arch); + xy = HALF32(xy + xy2); + yy = HALF32(yy_lookup[T1] + yy_lookup[T1b]); + g1 = compute_pitch_gain(xy, xx, yy); + if (abs(T1-prev_period)<=1) + cont = prev_gain; + else if (abs(T1-prev_period)<=2 && 5*k*k < T0) + cont = HALF16(prev_gain); + else + cont = 0; + thresh = MAX16(QCONST16(.3f,15), MULT16_16_Q15(QCONST16(.7f,15),g0)-cont); + /* Bias against very high pitch (very short period) to avoid false-positives + due to short-term correlation */ + if (T1<3*minperiod) + thresh = MAX16(QCONST16(.4f,15), MULT16_16_Q15(QCONST16(.85f,15),g0)-cont); + else if (T1<2*minperiod) + thresh = MAX16(QCONST16(.5f,15), MULT16_16_Q15(QCONST16(.9f,15),g0)-cont); + if (g1 > thresh) + { + best_xy = xy; + best_yy = yy; + T = T1; + g = g1; + } + } + best_xy = MAX32(0, best_xy); + if (best_yy <= best_xy) + pg = Q15ONE; + else + pg = SHR32(frac_div32(best_xy,best_yy+1),16); + + for (k=0;k<3;k++) + xcorr[k] = celt_inner_prod(x, x-(T+k-1), N, arch); + if ((xcorr[2]-xcorr[0]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[0])) + offset = 1; + else if ((xcorr[0]-xcorr[2]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[2])) + offset = -1; + else + offset = 0; + if (pg > g) + pg = g; + *T0_ = 2*T+offset; + + if (*T0_=3); + y_3=0; /* gcc doesn't realize that y_3 can't be used uninitialized */ + y_0=*y++; + y_1=*y++; + y_2=*y++; + for (j=0;j +#include "os_support.h" +#include "arch.h" +#include "mathops.h" +#include "stack_alloc.h" +#include "rate.h" + +#ifdef FIXED_POINT +/* Mean energy in each band quantized in Q4 */ +const signed char eMeans[25] = { + 103,100, 92, 85, 81, + 77, 72, 70, 78, 75, + 73, 71, 78, 74, 69, + 72, 70, 74, 76, 71, + 60, 60, 60, 60, 60 +}; +#else +/* Mean energy in each band quantized in Q4 and converted back to float */ +const opus_val16 eMeans[25] = { + 6.437500f, 6.250000f, 5.750000f, 5.312500f, 5.062500f, + 4.812500f, 4.500000f, 4.375000f, 4.875000f, 4.687500f, + 4.562500f, 4.437500f, 4.875000f, 4.625000f, 4.312500f, + 4.500000f, 4.375000f, 4.625000f, 4.750000f, 4.437500f, + 3.750000f, 3.750000f, 3.750000f, 3.750000f, 3.750000f +}; +#endif +/* prediction coefficients: 0.9, 0.8, 0.65, 0.5 */ +#ifdef FIXED_POINT +static const opus_val16 pred_coef[4] = {29440, 26112, 21248, 16384}; +static const opus_val16 beta_coef[4] = {30147, 22282, 12124, 6554}; +static const opus_val16 beta_intra = 4915; +#else +static const opus_val16 pred_coef[4] = {29440/32768., 26112/32768., 21248/32768., 16384/32768.}; +static const opus_val16 beta_coef[4] = {30147/32768., 22282/32768., 12124/32768., 6554/32768.}; +static const opus_val16 beta_intra = 4915/32768.; +#endif + +/*Parameters of the Laplace-like probability models used for the coarse energy. + There is one pair of parameters for each frame size, prediction type + (inter/intra), and band number. + The first number of each pair is the probability of 0, and the second is the + decay rate, both in Q8 precision.*/ +static const unsigned char e_prob_model[4][2][42] = { + /*120 sample frames.*/ + { + /*Inter*/ + { + 72, 127, 65, 129, 66, 128, 65, 128, 64, 128, 62, 128, 64, 128, + 64, 128, 92, 78, 92, 79, 92, 78, 90, 79, 116, 41, 115, 40, + 114, 40, 132, 26, 132, 26, 145, 17, 161, 12, 176, 10, 177, 11 + }, + /*Intra*/ + { + 24, 179, 48, 138, 54, 135, 54, 132, 53, 134, 56, 133, 55, 132, + 55, 132, 61, 114, 70, 96, 74, 88, 75, 88, 87, 74, 89, 66, + 91, 67, 100, 59, 108, 50, 120, 40, 122, 37, 97, 43, 78, 50 + } + }, + /*240 sample frames.*/ + { + /*Inter*/ + { + 83, 78, 84, 81, 88, 75, 86, 74, 87, 71, 90, 73, 93, 74, + 93, 74, 109, 40, 114, 36, 117, 34, 117, 34, 143, 17, 145, 18, + 146, 19, 162, 12, 165, 10, 178, 7, 189, 6, 190, 8, 177, 9 + }, + /*Intra*/ + { + 23, 178, 54, 115, 63, 102, 66, 98, 69, 99, 74, 89, 71, 91, + 73, 91, 78, 89, 86, 80, 92, 66, 93, 64, 102, 59, 103, 60, + 104, 60, 117, 52, 123, 44, 138, 35, 133, 31, 97, 38, 77, 45 + } + }, + /*480 sample frames.*/ + { + /*Inter*/ + { + 61, 90, 93, 60, 105, 42, 107, 41, 110, 45, 116, 38, 113, 38, + 112, 38, 124, 26, 132, 27, 136, 19, 140, 20, 155, 14, 159, 16, + 158, 18, 170, 13, 177, 10, 187, 8, 192, 6, 175, 9, 159, 10 + }, + /*Intra*/ + { + 21, 178, 59, 110, 71, 86, 75, 85, 84, 83, 91, 66, 88, 73, + 87, 72, 92, 75, 98, 72, 105, 58, 107, 54, 115, 52, 114, 55, + 112, 56, 129, 51, 132, 40, 150, 33, 140, 29, 98, 35, 77, 42 + } + }, + /*960 sample frames.*/ + { + /*Inter*/ + { + 42, 121, 96, 66, 108, 43, 111, 40, 117, 44, 123, 32, 120, 36, + 119, 33, 127, 33, 134, 34, 139, 21, 147, 23, 152, 20, 158, 25, + 154, 26, 166, 21, 173, 16, 184, 13, 184, 10, 150, 13, 139, 15 + }, + /*Intra*/ + { + 22, 178, 63, 114, 74, 82, 84, 83, 92, 82, 103, 62, 96, 72, + 96, 67, 101, 73, 107, 72, 113, 55, 118, 52, 125, 52, 118, 52, + 117, 55, 135, 49, 137, 39, 157, 32, 145, 29, 97, 33, 77, 40 + } + } +}; + +static const unsigned char small_energy_icdf[3]={2,1,0}; + +static opus_val32 loss_distortion(const opus_val16 *eBands, opus_val16 *oldEBands, int start, int end, int len, int C) +{ + int c, i; + opus_val32 dist = 0; + c=0; do { + for (i=start;inbEBands]; + oldE = MAX16(-QCONST16(9.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]); +#ifdef FIXED_POINT + f = SHL32(EXTEND32(x),7) - PSHR32(MULT16_16(coef,oldE), 8) - prev[c]; + /* Rounding to nearest integer here is really important! */ + qi = (f+QCONST32(.5f,DB_SHIFT+7))>>(DB_SHIFT+7); + decay_bound = EXTRACT16(MAX32(-QCONST16(28.f,DB_SHIFT), + SUB32((opus_val32)oldEBands[i+c*m->nbEBands],max_decay))); +#else + f = x-coef*oldE-prev[c]; + /* Rounding to nearest integer here is really important! */ + qi = (int)floor(.5f+f); + decay_bound = MAX16(-QCONST16(28.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]) - max_decay; +#endif + /* Prevent the energy from going down too quickly (e.g. for bands + that have just one bin) */ + if (qi < 0 && x < decay_bound) + { + qi += (int)SHR16(SUB16(decay_bound,x), DB_SHIFT); + if (qi > 0) + qi = 0; + } + qi0 = qi; + /* If we don't have enough bits to encode all the energy, just assume + something safe. */ + tell = ec_tell(enc); + bits_left = budget-tell-3*C*(end-i); + if (i!=start && bits_left < 30) + { + if (bits_left < 24) + qi = IMIN(1, qi); + if (bits_left < 16) + qi = IMAX(-1, qi); + } + if (lfe && i>=2) + qi = IMIN(qi, 0); + if (budget-tell >= 15) + { + int pi; + pi = 2*IMIN(i,20); + ec_laplace_encode(enc, &qi, + prob_model[pi]<<7, prob_model[pi+1]<<6); + } + else if(budget-tell >= 2) + { + qi = IMAX(-1, IMIN(qi, 1)); + ec_enc_icdf(enc, 2*qi^-(qi<0), small_energy_icdf, 2); + } + else if(budget-tell >= 1) + { + qi = IMIN(0, qi); + ec_enc_bit_logp(enc, -qi, 1); + } + else + qi = -1; + error[i+c*m->nbEBands] = PSHR32(f,7) - SHL16(qi,DB_SHIFT); + badness += abs(qi0-qi); + q = (opus_val32)SHL32(EXTEND32(qi),DB_SHIFT); + + tmp = PSHR32(MULT16_16(coef,oldE),8) + prev[c] + SHL32(q,7); +#ifdef FIXED_POINT + tmp = MAX32(-QCONST32(28.f, DB_SHIFT+7), tmp); +#endif + oldEBands[i+c*m->nbEBands] = PSHR32(tmp, 7); + prev[c] = prev[c] + SHL32(q,7) - MULT16_16(beta,PSHR32(q,8)); + } while (++c < C); + } + return lfe ? 0 : badness; +} + +void quant_coarse_energy(const CELTMode *m, int start, int end, int effEnd, + const opus_val16 *eBands, opus_val16 *oldEBands, opus_uint32 budget, + opus_val16 *error, ec_enc *enc, int C, int LM, int nbAvailableBytes, + int force_intra, opus_val32 *delayedIntra, int two_pass, int loss_rate, int lfe) +{ + int intra; + opus_val16 max_decay; + VARDECL(opus_val16, oldEBands_intra); + VARDECL(opus_val16, error_intra); + ec_enc enc_start_state; + opus_uint32 tell; + int badness1=0; + opus_int32 intra_bias; + opus_val32 new_distortion; + SAVE_STACK; + + intra = force_intra || (!two_pass && *delayedIntra>2*C*(end-start) && nbAvailableBytes > (end-start)*C); + intra_bias = (opus_int32)((budget**delayedIntra*loss_rate)/(C*512)); + new_distortion = loss_distortion(eBands, oldEBands, start, effEnd, m->nbEBands, C); + + tell = ec_tell(enc); + if (tell+3 > budget) + two_pass = intra = 0; + + max_decay = QCONST16(16.f,DB_SHIFT); + if (end-start>10) + { +#ifdef FIXED_POINT + max_decay = MIN32(max_decay, SHL32(EXTEND32(nbAvailableBytes),DB_SHIFT-3)); +#else + max_decay = MIN32(max_decay, .125f*nbAvailableBytes); +#endif + } + if (lfe) + max_decay = QCONST16(3.f,DB_SHIFT); + enc_start_state = *enc; + + ALLOC(oldEBands_intra, C*m->nbEBands, opus_val16); + ALLOC(error_intra, C*m->nbEBands, opus_val16); + OPUS_COPY(oldEBands_intra, oldEBands, C*m->nbEBands); + + if (two_pass || intra) + { + badness1 = quant_coarse_energy_impl(m, start, end, eBands, oldEBands_intra, budget, + tell, e_prob_model[LM][1], error_intra, enc, C, LM, 1, max_decay, lfe); + } + + if (!intra) + { + unsigned char *intra_buf; + ec_enc enc_intra_state; + opus_int32 tell_intra; + opus_uint32 nstart_bytes; + opus_uint32 nintra_bytes; + opus_uint32 save_bytes; + int badness2; + VARDECL(unsigned char, intra_bits); + + tell_intra = ec_tell_frac(enc); + + enc_intra_state = *enc; + + nstart_bytes = ec_range_bytes(&enc_start_state); + nintra_bytes = ec_range_bytes(&enc_intra_state); + intra_buf = ec_get_buffer(&enc_intra_state) + nstart_bytes; + save_bytes = nintra_bytes-nstart_bytes; + if (save_bytes == 0) + save_bytes = ALLOC_NONE; + ALLOC(intra_bits, save_bytes, unsigned char); + /* Copy bits from intra bit-stream */ + OPUS_COPY(intra_bits, intra_buf, nintra_bytes - nstart_bytes); + + *enc = enc_start_state; + + badness2 = quant_coarse_energy_impl(m, start, end, eBands, oldEBands, budget, + tell, e_prob_model[LM][intra], error, enc, C, LM, 0, max_decay, lfe); + + if (two_pass && (badness1 < badness2 || (badness1 == badness2 && ((opus_int32)ec_tell_frac(enc))+intra_bias > tell_intra))) + { + *enc = enc_intra_state; + /* Copy intra bits to bit-stream */ + OPUS_COPY(intra_buf, intra_bits, nintra_bytes - nstart_bytes); + OPUS_COPY(oldEBands, oldEBands_intra, C*m->nbEBands); + OPUS_COPY(error, error_intra, C*m->nbEBands); + intra = 1; + } + } else { + OPUS_COPY(oldEBands, oldEBands_intra, C*m->nbEBands); + OPUS_COPY(error, error_intra, C*m->nbEBands); + } + + if (intra) + *delayedIntra = new_distortion; + else + *delayedIntra = ADD32(MULT16_32_Q15(MULT16_16_Q15(pred_coef[LM], pred_coef[LM]),*delayedIntra), + new_distortion); + + RESTORE_STACK; +} + +void quant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, ec_enc *enc, int C) +{ + int i, c; + + /* Encode finer resolution */ + for (i=start;inbEBands]+QCONST16(.5f,DB_SHIFT))>>(DB_SHIFT-fine_quant[i]); +#else + q2 = (int)floor((error[i+c*m->nbEBands]+.5f)*frac); +#endif + if (q2 > frac-1) + q2 = frac-1; + if (q2<0) + q2 = 0; + ec_enc_bits(enc, q2, fine_quant[i]); +#ifdef FIXED_POINT + offset = SUB16(SHR32(SHL32(EXTEND32(q2),DB_SHIFT)+QCONST16(.5f,DB_SHIFT),fine_quant[i]),QCONST16(.5f,DB_SHIFT)); +#else + offset = (q2+.5f)*(1<<(14-fine_quant[i]))*(1.f/16384) - .5f; +#endif + oldEBands[i+c*m->nbEBands] += offset; + error[i+c*m->nbEBands] -= offset; + /*printf ("%f ", error[i] - offset);*/ + } while (++c < C); + } +} + +void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, int *fine_priority, int bits_left, ec_enc *enc, int C) +{ + int i, prio, c; + + /* Use up the remaining bits */ + for (prio=0;prio<2;prio++) + { + for (i=start;i=C ;i++) + { + if (fine_quant[i] >= MAX_FINE_BITS || fine_priority[i]!=prio) + continue; + c=0; + do { + int q2; + opus_val16 offset; + q2 = error[i+c*m->nbEBands]<0 ? 0 : 1; + ec_enc_bits(enc, q2, 1); +#ifdef FIXED_POINT + offset = SHR16(SHL16(q2,DB_SHIFT)-QCONST16(.5f,DB_SHIFT),fine_quant[i]+1); +#else + offset = (q2-.5f)*(1<<(14-fine_quant[i]-1))*(1.f/16384); +#endif + oldEBands[i+c*m->nbEBands] += offset; + error[i+c*m->nbEBands] -= offset; + bits_left--; + } while (++c < C); + } + } +} + +void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM) +{ + const unsigned char *prob_model = e_prob_model[LM][intra]; + int i, c; + opus_val32 prev[2] = {0, 0}; + opus_val16 coef; + opus_val16 beta; + opus_int32 budget; + opus_int32 tell; + + if (intra) + { + coef = 0; + beta = beta_intra; + } else { + beta = beta_coef[LM]; + coef = pred_coef[LM]; + } + + budget = dec->storage*8; + + /* Decode at a fixed coarse resolution */ + for (i=start;i=15) + { + int pi; + pi = 2*IMIN(i,20); + qi = ec_laplace_decode(dec, + prob_model[pi]<<7, prob_model[pi+1]<<6); + } + else if(budget-tell>=2) + { + qi = ec_dec_icdf(dec, small_energy_icdf, 2); + qi = (qi>>1)^-(qi&1); + } + else if(budget-tell>=1) + { + qi = -ec_dec_bit_logp(dec, 1); + } + else + qi = -1; + q = (opus_val32)SHL32(EXTEND32(qi),DB_SHIFT); + + oldEBands[i+c*m->nbEBands] = MAX16(-QCONST16(9.f,DB_SHIFT), oldEBands[i+c*m->nbEBands]); + tmp = PSHR32(MULT16_16(coef,oldEBands[i+c*m->nbEBands]),8) + prev[c] + SHL32(q,7); +#ifdef FIXED_POINT + tmp = MAX32(-QCONST32(28.f, DB_SHIFT+7), tmp); +#endif + oldEBands[i+c*m->nbEBands] = PSHR32(tmp, 7); + prev[c] = prev[c] + SHL32(q,7) - MULT16_16(beta,PSHR32(q,8)); + } while (++c < C); + } +} + +void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C) +{ + int i, c; + /* Decode finer resolution */ + for (i=start;inbEBands] += offset; + } while (++c < C); + } +} + +void unquant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, int *fine_priority, int bits_left, ec_dec *dec, int C) +{ + int i, prio, c; + + /* Use up the remaining bits */ + for (prio=0;prio<2;prio++) + { + for (i=start;i=C ;i++) + { + if (fine_quant[i] >= MAX_FINE_BITS || fine_priority[i]!=prio) + continue; + c=0; + do { + int q2; + opus_val16 offset; + q2 = ec_dec_bits(dec, 1); +#ifdef FIXED_POINT + offset = SHR16(SHL16(q2,DB_SHIFT)-QCONST16(.5f,DB_SHIFT),fine_quant[i]+1); +#else + offset = (q2-.5f)*(1<<(14-fine_quant[i]-1))*(1.f/16384); +#endif + oldEBands[i+c*m->nbEBands] += offset; + bits_left--; + } while (++c < C); + } + } +} + +void amp2Log2(const CELTMode *m, int effEnd, int end, + celt_ener *bandE, opus_val16 *bandLogE, int C) +{ + int c, i; + c=0; + do { + for (i=0;inbEBands] = + celt_log2(bandE[i+c*m->nbEBands]) + - SHL16((opus_val16)eMeans[i],6); +#ifdef FIXED_POINT + /* Compensate for bandE[] being Q12 but celt_log2() taking a Q14 input. */ + bandLogE[i+c*m->nbEBands] += QCONST16(2.f, DB_SHIFT); +#endif + } + for (i=effEnd;inbEBands+i] = -QCONST16(14.f,DB_SHIFT); + } while (++c < C); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/quant_bands.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/quant_bands.h new file mode 100644 index 0000000000..0490bca4b4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/quant_bands.h @@ -0,0 +1,66 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef QUANT_BANDS +#define QUANT_BANDS + +#include "arch.h" +#include "modes.h" +#include "entenc.h" +#include "entdec.h" +#include "mathops.h" + +#ifdef FIXED_POINT +extern const signed char eMeans[25]; +#else +extern const opus_val16 eMeans[25]; +#endif + +void amp2Log2(const CELTMode *m, int effEnd, int end, + celt_ener *bandE, opus_val16 *bandLogE, int C); + +void log2Amp(const CELTMode *m, int start, int end, + celt_ener *eBands, const opus_val16 *oldEBands, int C); + +void quant_coarse_energy(const CELTMode *m, int start, int end, int effEnd, + const opus_val16 *eBands, opus_val16 *oldEBands, opus_uint32 budget, + opus_val16 *error, ec_enc *enc, int C, int LM, + int nbAvailableBytes, int force_intra, opus_val32 *delayedIntra, + int two_pass, int loss_rate, int lfe); + +void quant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, ec_enc *enc, int C); + +void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, opus_val16 *error, int *fine_quant, int *fine_priority, int bits_left, ec_enc *enc, int C); + +void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM); + +void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C); + +void unquant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, int *fine_priority, int bits_left, ec_dec *dec, int C); + +#endif /* QUANT_BANDS */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.c new file mode 100644 index 0000000000..7f7ad3fa0f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.c @@ -0,0 +1,646 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "modes.h" +#include "cwrs.h" +#include "arch.h" +#include "os_support.h" + +#include "entcode.h" +#include "rate.h" + +static const unsigned char LOG2_FRAC_TABLE[24]={ + 0, + 8,13, + 16,19,21,23, + 24,26,27,28,29,30,31,32, + 32,33,34,34,35,36,36,37,37 +}; + +#ifdef CUSTOM_MODES + +/*Determines if V(N,K) fits in a 32-bit unsigned integer. + N and K are themselves limited to 15 bits.*/ +static int fits_in32(int _n, int _k) +{ + static const opus_int16 maxN[15] = { + 32767, 32767, 32767, 1476, 283, 109, 60, 40, + 29, 24, 20, 18, 16, 14, 13}; + static const opus_int16 maxK[15] = { + 32767, 32767, 32767, 32767, 1172, 238, 95, 53, + 36, 27, 22, 18, 16, 15, 13}; + if (_n>=14) + { + if (_k>=14) + return 0; + else + return _n <= maxN[_k]; + } else { + return _k <= maxK[_n]; + } +} + +void compute_pulse_cache(CELTMode *m, int LM) +{ + int C; + int i; + int j; + int curr=0; + int nbEntries=0; + int entryN[100], entryK[100], entryI[100]; + const opus_int16 *eBands = m->eBands; + PulseCache *cache = &m->cache; + opus_int16 *cindex; + unsigned char *bits; + unsigned char *cap; + + cindex = (opus_int16 *)opus_alloc(sizeof(cache->index[0])*m->nbEBands*(LM+2)); + cache->index = cindex; + + /* Scan for all unique band sizes */ + for (i=0;i<=LM+1;i++) + { + for (j=0;jnbEBands;j++) + { + int k; + int N = (eBands[j+1]-eBands[j])<>1; + cindex[i*m->nbEBands+j] = -1; + /* Find other bands that have the same size */ + for (k=0;k<=i;k++) + { + int n; + for (n=0;nnbEBands && (k!=i || n>1) + { + cindex[i*m->nbEBands+j] = cindex[k*m->nbEBands+n]; + break; + } + } + } + if (cache->index[i*m->nbEBands+j] == -1 && N!=0) + { + int K; + entryN[nbEntries] = N; + K = 0; + while (fits_in32(N,get_pulses(K+1)) && KnbEBands+j] = curr; + entryI[nbEntries] = curr; + + curr += K+1; + nbEntries++; + } + } + } + bits = (unsigned char *)opus_alloc(sizeof(unsigned char)*curr); + cache->bits = bits; + cache->size = curr; + /* Compute the cache for all unique sizes */ + for (i=0;icaps = cap = (unsigned char *)opus_alloc(sizeof(cache->caps[0])*(LM+1)*2*m->nbEBands); + for (i=0;i<=LM;i++) + { + for (C=1;C<=2;C++) + { + for (j=0;jnbEBands;j++) + { + int N0; + int max_bits; + N0 = m->eBands[j+1]-m->eBands[j]; + /* N=1 bands only have a sign bit and fine bits. */ + if (N0<1 are even, including custom modes.*/ + if (N0 > 2) + { + N0>>=1; + LM0--; + } + /* N0=1 bands can't be split down to N<2. */ + else if (N0 <= 1) + { + LM0=IMIN(i,1); + N0<<=LM0; + } + /* Compute the cost for the lowest-level PVQ of a fully split + band. */ + pcache = bits + cindex[(LM0+1)*m->nbEBands+j]; + max_bits = pcache[pcache[0]]+1; + /* Add in the cost of coding regular splits. */ + N = N0; + for(k=0;klogN[j]+((LM0+k)<>1)-QTHETA_OFFSET; + /* The number of qtheta bits we'll allocate if the remainder + is to be max_bits. + The average measured cost for theta is 0.89701 times qb, + approximated here as 459/512. */ + num=459*(opus_int32)((2*N-1)*offset+max_bits); + den=((opus_int32)(2*N-1)<<9)-459; + qb = IMIN((num+(den>>1))/den, 57); + celt_assert(qb >= 0); + max_bits += qb; + N <<= 1; + } + /* Add in the cost of a stereo split, if necessary. */ + if (C==2) + { + max_bits <<= 1; + offset = ((m->logN[j]+(i<>1)-(N==2?QTHETA_OFFSET_TWOPHASE:QTHETA_OFFSET); + ndof = 2*N-1-(N==2); + /* The average measured cost for theta with the step PDF is + 0.95164 times qb, approximated here as 487/512. */ + num = (N==2?512:487)*(opus_int32)(max_bits+ndof*offset); + den = ((opus_int32)ndof<<9)-(N==2?512:487); + qb = IMIN((num+(den>>1))/den, (N==2?64:61)); + celt_assert(qb >= 0); + max_bits += qb; + } + /* Add the fine bits we'll use. */ + /* Compensate for the extra DoF in stereo */ + ndof = C*N + ((C==2 && N>2) ? 1 : 0); + /* Offset the number of fine bits by log2(N)/2 + FINE_OFFSET + compared to their "fair share" of total/N */ + offset = ((m->logN[j] + (i<>1)-FINE_OFFSET; + /* N=2 is the only point that doesn't match the curve */ + if (N==2) + offset += 1<>2; + /* The number of fine bits we'll allocate if the remainder is + to be max_bits. */ + num = max_bits+ndof*offset; + den = (ndof-1)<>1))/den, MAX_FINE_BITS); + celt_assert(qb >= 0); + max_bits += C*qb<eBands[j+1]-m->eBands[j])<= 0); + celt_assert(max_bits < 256); + *cap++ = (unsigned char)max_bits; + } + } + } +} + +#endif /* CUSTOM_MODES */ + +#define ALLOC_STEPS 6 + +static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, int skip_start, + const int *bits1, const int *bits2, const int *thresh, const int *cap, opus_int32 total, opus_int32 *_balance, + int skip_rsv, int *intensity, int intensity_rsv, int *dual_stereo, int dual_stereo_rsv, int *bits, + int *ebits, int *fine_priority, int C, int LM, ec_ctx *ec, int encode, int prev, int signalBandwidth) +{ + opus_int32 psum; + int lo, hi; + int i, j; + int logM; + int stereo; + int codedBands=-1; + int alloc_floor; + opus_int32 left, percoeff; + int done; + opus_int32 balance; + SAVE_STACK; + + alloc_floor = C<1; + + logM = LM<>1; + psum = 0; + done = 0; + for (j=end;j-->start;) + { + int tmp = bits1[j] + (mid*(opus_int32)bits2[j]>>ALLOC_STEPS); + if (tmp >= thresh[j] || done) + { + done = 1; + /* Don't allocate more than we can actually use */ + psum += IMIN(tmp, cap[j]); + } else { + if (tmp >= alloc_floor) + psum += alloc_floor; + } + } + if (psum > total) + hi = mid; + else + lo = mid; + } + psum = 0; + /*printf ("interp bisection gave %d\n", lo);*/ + done = 0; + for (j=end;j-->start;) + { + int tmp = bits1[j] + ((opus_int32)lo*bits2[j]>>ALLOC_STEPS); + if (tmp < thresh[j] && !done) + { + if (tmp >= alloc_floor) + tmp = alloc_floor; + else + tmp = 0; + } else + done = 1; + /* Don't allocate more than we can actually use */ + tmp = IMIN(tmp, cap[j]); + bits[j] = tmp; + psum += tmp; + } + + /* Decide which bands to skip, working backwards from the end. */ + for (codedBands=end;;codedBands--) + { + int band_width; + int band_bits; + int rem; + j = codedBands-1; + /* Never skip the first band, nor a band that has been boosted by + dynalloc. + In the first case, we'd be coding a bit to signal we're going to waste + all the other bits. + In the second case, we'd be coding a bit to redistribute all the bits + we just signaled should be cocentrated in this band. */ + if (j<=skip_start) + { + /* Give the bit we reserved to end skipping back. */ + total += skip_rsv; + break; + } + /*Figure out how many left-over bits we would be adding to this band. + This can include bits we've stolen back from higher, skipped bands.*/ + left = total-psum; + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); + left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; + rem = IMAX(left-(m->eBands[j]-m->eBands[start]),0); + band_width = m->eBands[codedBands]-m->eBands[j]; + band_bits = (int)(bits[j] + percoeff*band_width + rem); + /*Only code a skip decision if we're above the threshold for this band. + Otherwise it is force-skipped. + This ensures that we have enough bits to code the skip flag.*/ + if (band_bits >= IMAX(thresh[j], alloc_floor+(1< 17) + depth_threshold = j (depth_threshold*band_width<>4 && j<=signalBandwidth)) +#endif + { + ec_enc_bit_logp(ec, 1, 1); + break; + } + ec_enc_bit_logp(ec, 0, 1); + } else if (ec_dec_bit_logp(ec, 1)) { + break; + } + /*We used a bit to skip this band.*/ + psum += 1< 0) + intensity_rsv = LOG2_FRAC_TABLE[j-start]; + psum += intensity_rsv; + if (band_bits >= alloc_floor) + { + /*If we have enough for a fine energy bit per channel, use it.*/ + psum += alloc_floor; + bits[j] = alloc_floor; + } else { + /*Otherwise this band gets nothing at all.*/ + bits[j] = 0; + } + } + + celt_assert(codedBands > start); + /* Code the intensity and dual stereo parameters. */ + if (intensity_rsv > 0) + { + if (encode) + { + *intensity = IMIN(*intensity, codedBands); + ec_enc_uint(ec, *intensity-start, codedBands+1-start); + } + else + *intensity = start+ec_dec_uint(ec, codedBands+1-start); + } + else + *intensity = 0; + if (*intensity <= start) + { + total += dual_stereo_rsv; + dual_stereo_rsv = 0; + } + if (dual_stereo_rsv > 0) + { + if (encode) + ec_enc_bit_logp(ec, *dual_stereo, 1); + else + *dual_stereo = ec_dec_bit_logp(ec, 1); + } + else + *dual_stereo = 0; + + /* Allocate the remaining bits */ + left = total-psum; + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); + left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; + for (j=start;jeBands[j+1]-m->eBands[j])); + for (j=start;jeBands[j+1]-m->eBands[j]); + bits[j] += tmp; + left -= tmp; + } + /*for (j=0;j= 0); + N0 = m->eBands[j+1]-m->eBands[j]; + N=N0<1) + { + excess = MAX32(bit-cap[j],0); + bits[j] = bit-excess; + + /* Compensate for the extra DoF in stereo */ + den=(C*N+ ((C==2 && N>2 && !*dual_stereo && j<*intensity) ? 1 : 0)); + + NClogN = den*(m->logN[j] + logM); + + /* Offset for the number of fine bits by log2(N)/2 + FINE_OFFSET + compared to their "fair share" of total/N */ + offset = (NClogN>>1)-den*FINE_OFFSET; + + /* N=2 is the only point that doesn't match the curve */ + if (N==2) + offset += den<>2; + + /* Changing the offset for allocating the second and third + fine energy bit */ + if (bits[j] + offset < den*2<>2; + else if (bits[j] + offset < den*3<>3; + + /* Divide with rounding */ + ebits[j] = IMAX(0, (bits[j] + offset + (den<<(BITRES-1)))); + ebits[j] = celt_udiv(ebits[j], den)>>BITRES; + + /* Make sure not to bust */ + if (C*ebits[j] > (bits[j]>>BITRES)) + ebits[j] = bits[j] >> stereo >> BITRES; + + /* More than that is useless because that's about as far as PVQ can go */ + ebits[j] = IMIN(ebits[j], MAX_FINE_BITS); + + /* If we rounded down or capped this band, make it a candidate for the + final fine energy pass */ + fine_priority[j] = ebits[j]*(den<= bits[j]+offset; + + /* Remove the allocated fine bits; the rest are assigned to PVQ */ + bits[j] -= C*ebits[j]< 0) + { + int extra_fine; + int extra_bits; + extra_fine = IMIN(excess>>(stereo+BITRES),MAX_FINE_BITS-ebits[j]); + ebits[j] += extra_fine; + extra_bits = extra_fine*C<= excess-balance; + excess -= extra_bits; + } + balance = excess; + + celt_assert(bits[j] >= 0); + celt_assert(ebits[j] >= 0); + } + /* Save any remaining bits over the cap for the rebalancing in + quant_all_bands(). */ + *_balance = balance; + + /* The skipped bands use all their bits for fine energy. */ + for (;j> stereo >> BITRES; + celt_assert(C*ebits[j]<nbEBands; + skip_start = start; + /* Reserve a bit to signal the end of manually skipped bands. */ + skip_rsv = total >= 1<total) + intensity_rsv = 0; + else + { + total -= intensity_rsv; + dual_stereo_rsv = total>=1<eBands[j+1]-m->eBands[j])<>4); + /* Tilt of the allocation curve */ + trim_offset[j] = C*(m->eBands[j+1]-m->eBands[j])*(alloc_trim-5-LM)*(end-j-1) + *(1<<(LM+BITRES))>>6; + /* Giving less resolution to single-coefficient bands because they get + more benefit from having one coarse value per coefficient*/ + if ((m->eBands[j+1]-m->eBands[j])<nbAllocVectors - 1; + do + { + int done = 0; + int psum = 0; + int mid = (lo+hi) >> 1; + for (j=end;j-->start;) + { + int bitsj; + int N = m->eBands[j+1]-m->eBands[j]; + bitsj = C*N*m->allocVectors[mid*len+j]<>2; + if (bitsj > 0) + bitsj = IMAX(0, bitsj + trim_offset[j]); + bitsj += offsets[j]; + if (bitsj >= thresh[j] || done) + { + done = 1; + /* Don't allocate more than we can actually use */ + psum += IMIN(bitsj, cap[j]); + } else { + if (bitsj >= C< total) + hi = mid - 1; + else + lo = mid + 1; + /*printf ("lo = %d, hi = %d\n", lo, hi);*/ + } + while (lo <= hi); + hi = lo--; + /*printf ("interp between %d and %d\n", lo, hi);*/ + for (j=start;jeBands[j+1]-m->eBands[j]; + bits1j = C*N*m->allocVectors[lo*len+j]<>2; + bits2j = hi>=m->nbAllocVectors ? + cap[j] : C*N*m->allocVectors[hi*len+j]<>2; + if (bits1j > 0) + bits1j = IMAX(0, bits1j + trim_offset[j]); + if (bits2j > 0) + bits2j = IMAX(0, bits2j + trim_offset[j]); + if (lo > 0) + bits1j += offsets[j]; + bits2j += offsets[j]; + if (offsets[j]>0) + skip_start = j; + bits2j = IMAX(0,bits2j-bits1j); + bits1[j] = bits1j; + bits2[j] = bits2j; + } + codedBands = interp_bits2pulses(m, start, end, skip_start, bits1, bits2, thresh, cap, + total, balance, skip_rsv, intensity, intensity_rsv, dual_stereo, dual_stereo_rsv, + pulses, ebits, fine_priority, C, LM, ec, encode, prev, signalBandwidth); + RESTORE_STACK; + return codedBands; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.h new file mode 100644 index 0000000000..fad5e412da --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/rate.h @@ -0,0 +1,101 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef RATE_H +#define RATE_H + +#define MAX_PSEUDO 40 +#define LOG_MAX_PSEUDO 6 + +#define CELT_MAX_PULSES 128 + +#define MAX_FINE_BITS 8 + +#define FINE_OFFSET 21 +#define QTHETA_OFFSET 4 +#define QTHETA_OFFSET_TWOPHASE 16 + +#include "cwrs.h" +#include "modes.h" + +void compute_pulse_cache(CELTMode *m, int LM); + +static OPUS_INLINE int get_pulses(int i) +{ + return i<8 ? i : (8 + (i&7)) << ((i>>3)-1); +} + +static OPUS_INLINE int bits2pulses(const CELTMode *m, int band, int LM, int bits) +{ + int i; + int lo, hi; + const unsigned char *cache; + + LM++; + cache = m->cache.bits + m->cache.index[LM*m->nbEBands+band]; + + lo = 0; + hi = cache[0]; + bits--; + for (i=0;i>1; + /* OPT: Make sure this is implemented with a conditional move */ + if ((int)cache[mid] >= bits) + hi = mid; + else + lo = mid; + } + if (bits- (lo == 0 ? -1 : (int)cache[lo]) <= (int)cache[hi]-bits) + return lo; + else + return hi; +} + +static OPUS_INLINE int pulses2bits(const CELTMode *m, int band, int LM, int pulses) +{ + const unsigned char *cache; + + LM++; + cache = m->cache.bits + m->cache.index[LM*m->nbEBands+band]; + return pulses == 0 ? 0 : cache[pulses]+1; +} + +/** Compute the pulse allocation, i.e. how many pulses will go in each + * band. + @param m mode + @param offsets Requested increase or decrease in the number of bits for + each band + @param total Number of bands + @param pulses Number of pulses per band (returned) + @return Total number of bits allocated +*/ +int clt_compute_allocation(const CELTMode *m, int start, int end, const int *offsets, const int *cap, int alloc_trim, int *intensity, int *dual_stereo, + opus_int32 total, opus_int32 *balance, int *pulses, int *ebits, int *fine_priority, int C, int LM, ec_ctx *ec, int encode, int prev, int signalBandwidth); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/stack_alloc.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/stack_alloc.h new file mode 100644 index 0000000000..e2739bdf66 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/stack_alloc.h @@ -0,0 +1,184 @@ +/* Copyright (C) 2002-2003 Jean-Marc Valin + Copyright (C) 2007-2009 Xiph.Org Foundation */ +/** + @file stack_alloc.h + @brief Temporary memory allocation on stack +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef STACK_ALLOC_H +#define STACK_ALLOC_H + +#include "opus_types.h" +#include "opus_defines.h" + +#if (!defined (VAR_ARRAYS) && !defined (USE_ALLOCA) && !defined (NONTHREADSAFE_PSEUDOSTACK)) +#error "Opus requires one of VAR_ARRAYS, USE_ALLOCA, or NONTHREADSAFE_PSEUDOSTACK be defined to select the temporary allocation mode." +#endif + +#ifdef USE_ALLOCA +# ifdef _WIN32 +# include +# else +# ifdef HAVE_ALLOCA_H +# include +# else +# include +# endif +# endif +#endif + +/** + * @def ALIGN(stack, size) + * + * Aligns the stack to a 'size' boundary + * + * @param stack Stack + * @param size New size boundary + */ + +/** + * @def PUSH(stack, size, type) + * + * Allocates 'size' elements of type 'type' on the stack + * + * @param stack Stack + * @param size Number of elements + * @param type Type of element + */ + +/** + * @def VARDECL(var) + * + * Declare variable on stack + * + * @param var Variable to declare + */ + +/** + * @def ALLOC(var, size, type) + * + * Allocate 'size' elements of 'type' on stack + * + * @param var Name of variable to allocate + * @param size Number of elements + * @param type Type of element + */ + +#if defined(VAR_ARRAYS) + +#define VARDECL(type, var) +#define ALLOC(var, size, type) type var[size] +#define SAVE_STACK +#define RESTORE_STACK +#define ALLOC_STACK +/* C99 does not allow VLAs of size zero */ +#define ALLOC_NONE 1 + +#elif defined(USE_ALLOCA) + +#define VARDECL(type, var) type *var + +# ifdef _WIN32 +# define ALLOC(var, size, type) var = ((type*)_alloca(sizeof(type)*(size))) +# else +# define ALLOC(var, size, type) var = ((type*)alloca(sizeof(type)*(size))) +# endif + +#define SAVE_STACK +#define RESTORE_STACK +#define ALLOC_STACK +#define ALLOC_NONE 0 + +#else + +#ifdef CELT_C +char *scratch_ptr=0; +char *global_stack=0; +#else +extern char *global_stack; +extern char *scratch_ptr; +#endif /* CELT_C */ + +#ifdef ENABLE_VALGRIND + +#include + +#ifdef CELT_C +char *global_stack_top=0; +#else +extern char *global_stack_top; +#endif /* CELT_C */ + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) +#define PUSH(stack, size, type) (VALGRIND_MAKE_MEM_NOACCESS(stack, global_stack_top-stack),ALIGN((stack),sizeof(type)/sizeof(char)),VALGRIND_MAKE_MEM_UNDEFINED(stack, ((size)*sizeof(type)/sizeof(char))),(stack)+=(2*(size)*sizeof(type)/sizeof(char)),(type*)((stack)-(2*(size)*sizeof(type)/sizeof(char)))) +#define RESTORE_STACK ((global_stack = _saved_stack),VALGRIND_MAKE_MEM_NOACCESS(global_stack, global_stack_top-global_stack)) +#define ALLOC_STACK char *_saved_stack; ((global_stack = (global_stack==0) ? ((global_stack_top=opus_alloc_scratch(GLOBAL_STACK_SIZE*2)+(GLOBAL_STACK_SIZE*2))-(GLOBAL_STACK_SIZE*2)) : global_stack),VALGRIND_MAKE_MEM_NOACCESS(global_stack, global_stack_top-global_stack)); _saved_stack = global_stack; + +#else + +#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) +#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)/(sizeof(char))),(stack)+=(size)*(sizeof(type)/(sizeof(char))),(type*)((stack)-(size)*(sizeof(type)/(sizeof(char))))) +#if 0 /* Set this to 1 to instrument pseudostack usage */ +#define RESTORE_STACK (printf("%ld %s:%d\n", global_stack-scratch_ptr, __FILE__, __LINE__),global_stack = _saved_stack) +#else +#define RESTORE_STACK (global_stack = _saved_stack) +#endif +#define ALLOC_STACK char *_saved_stack; (global_stack = (global_stack==0) ? (scratch_ptr=opus_alloc_scratch(GLOBAL_STACK_SIZE)) : global_stack); _saved_stack = global_stack; + +#endif /* ENABLE_VALGRIND */ + +#include "os_support.h" +#define VARDECL(type, var) type *var +#define ALLOC(var, size, type) var = PUSH(global_stack, size, type) +#define SAVE_STACK char *_saved_stack = global_stack; +#define ALLOC_NONE 0 + +#endif /* VAR_ARRAYS */ + + +#ifdef ENABLE_VALGRIND + +#include +#define OPUS_CHECK_ARRAY(ptr, len) VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len*sizeof(*ptr)) +#define OPUS_CHECK_VALUE(value) VALGRIND_CHECK_VALUE_IS_DEFINED(value) +#define OPUS_CHECK_ARRAY_COND(ptr, len) VALGRIND_CHECK_MEM_IS_DEFINED(ptr, len*sizeof(*ptr)) +#define OPUS_CHECK_VALUE_COND(value) VALGRIND_CHECK_VALUE_IS_DEFINED(value) +#define OPUS_PRINT_INT(value) do {fprintf(stderr, #value " = %d at %s:%d\n", value, __FILE__, __LINE__);}while(0) +#define OPUS_FPRINTF fprintf + +#else + +static OPUS_INLINE int _opus_false(void) {return 0;} +#define OPUS_CHECK_ARRAY(ptr, len) _opus_false() +#define OPUS_CHECK_VALUE(value) _opus_false() +#define OPUS_PRINT_INT(value) do{}while(0) +#define OPUS_FPRINTF (void) + +#endif + + +#endif /* STACK_ALLOC_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed.h new file mode 100644 index 0000000000..8717d626cb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed.h @@ -0,0 +1,892 @@ +/* The contents of this file was automatically generated by dump_modes.c + with arguments: 48000 960 + It contains static definitions for some pre-defined modes. */ +#include "modes.h" +#include "rate.h" + +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_fixed_arm_ne10.h" +#endif + +#ifndef DEF_WINDOW120 +#define DEF_WINDOW120 +static const opus_val16 window120[120] = { +2, 20, 55, 108, 178, +266, 372, 494, 635, 792, +966, 1157, 1365, 1590, 1831, +2089, 2362, 2651, 2956, 3276, +3611, 3961, 4325, 4703, 5094, +5499, 5916, 6346, 6788, 7241, +7705, 8179, 8663, 9156, 9657, +10167, 10684, 11207, 11736, 12271, +12810, 13353, 13899, 14447, 14997, +15547, 16098, 16648, 17197, 17744, +18287, 18827, 19363, 19893, 20418, +20936, 21447, 21950, 22445, 22931, +23407, 23874, 24330, 24774, 25208, +25629, 26039, 26435, 26819, 27190, +27548, 27893, 28224, 28541, 28845, +29135, 29411, 29674, 29924, 30160, +30384, 30594, 30792, 30977, 31151, +31313, 31463, 31602, 31731, 31849, +31958, 32057, 32148, 32229, 32303, +32370, 32429, 32481, 32528, 32568, +32604, 32634, 32661, 32683, 32701, +32717, 32729, 32740, 32748, 32754, +32758, 32762, 32764, 32766, 32767, +32767, 32767, 32767, 32767, 32767, +}; +#endif + +#ifndef DEF_LOGN400 +#define DEF_LOGN400 +static const opus_int16 logN400[21] = { +0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 21, 21, 24, 29, 34, 36, }; +#endif + +#ifndef DEF_PULSE_CACHE50 +#define DEF_PULSE_CACHE50 +static const opus_int16 cache_index50[105] = { +-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, +82, 82, 123, 164, 200, 222, 0, 0, 0, 0, 0, 0, 0, 0, 41, +41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, +41, 41, 41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, +318, 328, 336, 123, 123, 123, 123, 123, 123, 123, 123, 240, 240, 240, 240, +305, 305, 305, 318, 318, 343, 351, 358, 364, 240, 240, 240, 240, 240, 240, +240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382, 387, +}; +static const unsigned char cache_bits50[392] = { +40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, +31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47, 47, 49, 50, +51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, +66, 67, 68, 69, 70, 71, 71, 40, 20, 33, 41, 48, 53, 57, 61, +64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92, +94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, +124, 126, 128, 40, 23, 39, 51, 60, 67, 73, 79, 83, 87, 91, 94, +97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126, 129, 131, 135, 139, +142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35, +28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, +153, 159, 165, 171, 176, 180, 185, 189, 192, 199, 205, 211, 216, 220, 225, +229, 232, 239, 245, 251, 21, 33, 58, 79, 97, 112, 125, 137, 148, 157, +166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35, 63, +86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, +25, 31, 55, 75, 91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, +185, 190, 200, 208, 215, 222, 229, 235, 240, 245, 255, 16, 36, 65, 89, +110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250, 11, 41, +74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, +163, 186, 207, 227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, +228, 241, 253, 9, 44, 81, 113, 142, 168, 192, 214, 235, 255, 7, 49, +90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7, 47, +87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, +106, 151, 192, 231, 5, 59, 111, 158, 202, 243, 5, 55, 103, 147, 187, +224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175, 224, 4, 67, 127, +182, 234, }; +static const unsigned char cache_caps50[168] = { +224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, +178, 178, 168, 134, 61, 37, 224, 224, 224, 224, 224, 224, 224, 224, 240, +240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40, 160, 160, 160, +160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, +138, 64, 38, 240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, +204, 204, 204, 193, 193, 180, 143, 66, 40, 185, 185, 185, 185, 185, 185, +185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39, +207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, +188, 188, 176, 141, 66, 40, 193, 193, 193, 193, 193, 193, 193, 193, 193, +193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39, 204, 204, 204, +204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, +140, 66, 40, }; +#endif + +#ifndef FFT_TWIDDLES48000_960 +#define FFT_TWIDDLES48000_960 +static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { +{32767, 0}, {32766, -429}, +{32757, -858}, {32743, -1287}, +{32724, -1715}, {32698, -2143}, +{32667, -2570}, {32631, -2998}, +{32588, -3425}, {32541, -3851}, +{32488, -4277}, {32429, -4701}, +{32364, -5125}, {32295, -5548}, +{32219, -5971}, {32138, -6393}, +{32051, -6813}, {31960, -7231}, +{31863, -7650}, {31760, -8067}, +{31652, -8481}, {31539, -8895}, +{31419, -9306}, {31294, -9716}, +{31165, -10126}, {31030, -10532}, +{30889, -10937}, {30743, -11340}, +{30592, -11741}, {30436, -12141}, +{30274, -12540}, {30107, -12935}, +{29936, -13328}, {29758, -13718}, +{29577, -14107}, {29390, -14493}, +{29197, -14875}, {29000, -15257}, +{28797, -15635}, {28590, -16010}, +{28379, -16384}, {28162, -16753}, +{27940, -17119}, {27714, -17484}, +{27482, -17845}, {27246, -18205}, +{27006, -18560}, {26760, -18911}, +{26510, -19260}, {26257, -19606}, +{25997, -19947}, {25734, -20286}, +{25466, -20621}, {25194, -20952}, +{24918, -21281}, {24637, -21605}, +{24353, -21926}, {24063, -22242}, +{23770, -22555}, {23473, -22865}, +{23171, -23171}, {22866, -23472}, +{22557, -23769}, {22244, -24063}, +{21927, -24352}, {21606, -24636}, +{21282, -24917}, {20954, -25194}, +{20622, -25465}, {20288, -25733}, +{19949, -25997}, {19607, -26255}, +{19261, -26509}, {18914, -26760}, +{18561, -27004}, {18205, -27246}, +{17846, -27481}, {17485, -27713}, +{17122, -27940}, {16755, -28162}, +{16385, -28378}, {16012, -28590}, +{15636, -28797}, {15258, -28999}, +{14878, -29197}, {14494, -29389}, +{14108, -29576}, {13720, -29757}, +{13329, -29934}, {12937, -30107}, +{12540, -30274}, {12142, -30435}, +{11744, -30592}, {11342, -30743}, +{10939, -30889}, {10534, -31030}, +{10127, -31164}, {9718, -31294}, +{9307, -31418}, {8895, -31537}, +{8482, -31652}, {8067, -31759}, +{7650, -31862}, {7233, -31960}, +{6815, -32051}, {6393, -32138}, +{5973, -32219}, {5549, -32294}, +{5127, -32364}, {4703, -32429}, +{4278, -32487}, {3852, -32541}, +{3426, -32588}, {2999, -32630}, +{2572, -32667}, {2144, -32698}, +{1716, -32724}, {1287, -32742}, +{860, -32757}, {430, -32766}, +{0, -32767}, {-429, -32766}, +{-858, -32757}, {-1287, -32743}, +{-1715, -32724}, {-2143, -32698}, +{-2570, -32667}, {-2998, -32631}, +{-3425, -32588}, {-3851, -32541}, +{-4277, -32488}, {-4701, -32429}, +{-5125, -32364}, {-5548, -32295}, +{-5971, -32219}, {-6393, -32138}, +{-6813, -32051}, {-7231, -31960}, +{-7650, -31863}, {-8067, -31760}, +{-8481, -31652}, {-8895, -31539}, +{-9306, -31419}, {-9716, -31294}, +{-10126, -31165}, {-10532, -31030}, +{-10937, -30889}, {-11340, -30743}, +{-11741, -30592}, {-12141, -30436}, +{-12540, -30274}, {-12935, -30107}, +{-13328, -29936}, {-13718, -29758}, +{-14107, -29577}, {-14493, -29390}, +{-14875, -29197}, {-15257, -29000}, +{-15635, -28797}, {-16010, -28590}, +{-16384, -28379}, {-16753, -28162}, +{-17119, -27940}, {-17484, -27714}, +{-17845, -27482}, {-18205, -27246}, +{-18560, -27006}, {-18911, -26760}, +{-19260, -26510}, {-19606, -26257}, +{-19947, -25997}, {-20286, -25734}, +{-20621, -25466}, {-20952, -25194}, +{-21281, -24918}, {-21605, -24637}, +{-21926, -24353}, {-22242, -24063}, +{-22555, -23770}, {-22865, -23473}, +{-23171, -23171}, {-23472, -22866}, +{-23769, -22557}, {-24063, -22244}, +{-24352, -21927}, {-24636, -21606}, +{-24917, -21282}, {-25194, -20954}, +{-25465, -20622}, {-25733, -20288}, +{-25997, -19949}, {-26255, -19607}, +{-26509, -19261}, {-26760, -18914}, +{-27004, -18561}, {-27246, -18205}, +{-27481, -17846}, {-27713, -17485}, +{-27940, -17122}, {-28162, -16755}, +{-28378, -16385}, {-28590, -16012}, +{-28797, -15636}, {-28999, -15258}, +{-29197, -14878}, {-29389, -14494}, +{-29576, -14108}, {-29757, -13720}, +{-29934, -13329}, {-30107, -12937}, +{-30274, -12540}, {-30435, -12142}, +{-30592, -11744}, {-30743, -11342}, +{-30889, -10939}, {-31030, -10534}, +{-31164, -10127}, {-31294, -9718}, +{-31418, -9307}, {-31537, -8895}, +{-31652, -8482}, {-31759, -8067}, +{-31862, -7650}, {-31960, -7233}, +{-32051, -6815}, {-32138, -6393}, +{-32219, -5973}, {-32294, -5549}, +{-32364, -5127}, {-32429, -4703}, +{-32487, -4278}, {-32541, -3852}, +{-32588, -3426}, {-32630, -2999}, +{-32667, -2572}, {-32698, -2144}, +{-32724, -1716}, {-32742, -1287}, +{-32757, -860}, {-32766, -430}, +{-32767, 0}, {-32766, 429}, +{-32757, 858}, {-32743, 1287}, +{-32724, 1715}, {-32698, 2143}, +{-32667, 2570}, {-32631, 2998}, +{-32588, 3425}, {-32541, 3851}, +{-32488, 4277}, {-32429, 4701}, +{-32364, 5125}, {-32295, 5548}, +{-32219, 5971}, {-32138, 6393}, +{-32051, 6813}, {-31960, 7231}, +{-31863, 7650}, {-31760, 8067}, +{-31652, 8481}, {-31539, 8895}, +{-31419, 9306}, {-31294, 9716}, +{-31165, 10126}, {-31030, 10532}, +{-30889, 10937}, {-30743, 11340}, +{-30592, 11741}, {-30436, 12141}, +{-30274, 12540}, {-30107, 12935}, +{-29936, 13328}, {-29758, 13718}, +{-29577, 14107}, {-29390, 14493}, +{-29197, 14875}, {-29000, 15257}, +{-28797, 15635}, {-28590, 16010}, +{-28379, 16384}, {-28162, 16753}, +{-27940, 17119}, {-27714, 17484}, +{-27482, 17845}, {-27246, 18205}, +{-27006, 18560}, {-26760, 18911}, +{-26510, 19260}, {-26257, 19606}, +{-25997, 19947}, {-25734, 20286}, +{-25466, 20621}, {-25194, 20952}, +{-24918, 21281}, {-24637, 21605}, +{-24353, 21926}, {-24063, 22242}, +{-23770, 22555}, {-23473, 22865}, +{-23171, 23171}, {-22866, 23472}, +{-22557, 23769}, {-22244, 24063}, +{-21927, 24352}, {-21606, 24636}, +{-21282, 24917}, {-20954, 25194}, +{-20622, 25465}, {-20288, 25733}, +{-19949, 25997}, {-19607, 26255}, +{-19261, 26509}, {-18914, 26760}, +{-18561, 27004}, {-18205, 27246}, +{-17846, 27481}, {-17485, 27713}, +{-17122, 27940}, {-16755, 28162}, +{-16385, 28378}, {-16012, 28590}, +{-15636, 28797}, {-15258, 28999}, +{-14878, 29197}, {-14494, 29389}, +{-14108, 29576}, {-13720, 29757}, +{-13329, 29934}, {-12937, 30107}, +{-12540, 30274}, {-12142, 30435}, +{-11744, 30592}, {-11342, 30743}, +{-10939, 30889}, {-10534, 31030}, +{-10127, 31164}, {-9718, 31294}, +{-9307, 31418}, {-8895, 31537}, +{-8482, 31652}, {-8067, 31759}, +{-7650, 31862}, {-7233, 31960}, +{-6815, 32051}, {-6393, 32138}, +{-5973, 32219}, {-5549, 32294}, +{-5127, 32364}, {-4703, 32429}, +{-4278, 32487}, {-3852, 32541}, +{-3426, 32588}, {-2999, 32630}, +{-2572, 32667}, {-2144, 32698}, +{-1716, 32724}, {-1287, 32742}, +{-860, 32757}, {-430, 32766}, +{0, 32767}, {429, 32766}, +{858, 32757}, {1287, 32743}, +{1715, 32724}, {2143, 32698}, +{2570, 32667}, {2998, 32631}, +{3425, 32588}, {3851, 32541}, +{4277, 32488}, {4701, 32429}, +{5125, 32364}, {5548, 32295}, +{5971, 32219}, {6393, 32138}, +{6813, 32051}, {7231, 31960}, +{7650, 31863}, {8067, 31760}, +{8481, 31652}, {8895, 31539}, +{9306, 31419}, {9716, 31294}, +{10126, 31165}, {10532, 31030}, +{10937, 30889}, {11340, 30743}, +{11741, 30592}, {12141, 30436}, +{12540, 30274}, {12935, 30107}, +{13328, 29936}, {13718, 29758}, +{14107, 29577}, {14493, 29390}, +{14875, 29197}, {15257, 29000}, +{15635, 28797}, {16010, 28590}, +{16384, 28379}, {16753, 28162}, +{17119, 27940}, {17484, 27714}, +{17845, 27482}, {18205, 27246}, +{18560, 27006}, {18911, 26760}, +{19260, 26510}, {19606, 26257}, +{19947, 25997}, {20286, 25734}, +{20621, 25466}, {20952, 25194}, +{21281, 24918}, {21605, 24637}, +{21926, 24353}, {22242, 24063}, +{22555, 23770}, {22865, 23473}, +{23171, 23171}, {23472, 22866}, +{23769, 22557}, {24063, 22244}, +{24352, 21927}, {24636, 21606}, +{24917, 21282}, {25194, 20954}, +{25465, 20622}, {25733, 20288}, +{25997, 19949}, {26255, 19607}, +{26509, 19261}, {26760, 18914}, +{27004, 18561}, {27246, 18205}, +{27481, 17846}, {27713, 17485}, +{27940, 17122}, {28162, 16755}, +{28378, 16385}, {28590, 16012}, +{28797, 15636}, {28999, 15258}, +{29197, 14878}, {29389, 14494}, +{29576, 14108}, {29757, 13720}, +{29934, 13329}, {30107, 12937}, +{30274, 12540}, {30435, 12142}, +{30592, 11744}, {30743, 11342}, +{30889, 10939}, {31030, 10534}, +{31164, 10127}, {31294, 9718}, +{31418, 9307}, {31537, 8895}, +{31652, 8482}, {31759, 8067}, +{31862, 7650}, {31960, 7233}, +{32051, 6815}, {32138, 6393}, +{32219, 5973}, {32294, 5549}, +{32364, 5127}, {32429, 4703}, +{32487, 4278}, {32541, 3852}, +{32588, 3426}, {32630, 2999}, +{32667, 2572}, {32698, 2144}, +{32724, 1716}, {32742, 1287}, +{32757, 860}, {32766, 430}, +}; +#ifndef FFT_BITREV480 +#define FFT_BITREV480 +static const opus_int16 fft_bitrev480[480] = { +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, +}; +#endif + +#ifndef FFT_BITREV240 +#define FFT_BITREV240 +static const opus_int16 fft_bitrev240[240] = { +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, +}; +#endif + +#ifndef FFT_BITREV120 +#define FFT_BITREV120 +static const opus_int16 fft_bitrev120[120] = { +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, +}; +#endif + +#ifndef FFT_BITREV60 +#define FFT_BITREV60 +static const opus_int16 fft_bitrev60[60] = { +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, +}; +#endif + +#ifndef FFT_STATE48000_960_0 +#define FFT_STATE48000_960_0 +static const kiss_fft_state fft_state48000_960_0 = { +480, /* nfft */ +17476, /* scale */ +8, /* scale_shift */ +-1, /* shift */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev480, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_1 +#define FFT_STATE48000_960_1 +static const kiss_fft_state fft_state48000_960_1 = { +240, /* nfft */ +17476, /* scale */ +7, /* scale_shift */ +1, /* shift */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev240, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_2 +#define FFT_STATE48000_960_2 +static const kiss_fft_state fft_state48000_960_2 = { +120, /* nfft */ +17476, /* scale */ +6, /* scale_shift */ +2, /* shift */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev120, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_3 +#define FFT_STATE48000_960_3 +static const kiss_fft_state fft_state48000_960_3 = { +60, /* nfft */ +17476, /* scale */ +5, /* scale_shift */ +3, /* shift */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev60, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif +}; +#endif + +#endif + +#ifndef MDCT_TWIDDLES960 +#define MDCT_TWIDDLES960 +static const opus_val16 mdct_twiddles960[1800] = { +32767, 32767, 32767, 32766, 32765, +32763, 32761, 32759, 32756, 32753, +32750, 32746, 32742, 32738, 32733, +32728, 32722, 32717, 32710, 32704, +32697, 32690, 32682, 32674, 32666, +32657, 32648, 32639, 32629, 32619, +32609, 32598, 32587, 32576, 32564, +32552, 32539, 32526, 32513, 32500, +32486, 32472, 32457, 32442, 32427, +32411, 32395, 32379, 32362, 32345, +32328, 32310, 32292, 32274, 32255, +32236, 32217, 32197, 32177, 32157, +32136, 32115, 32093, 32071, 32049, +32027, 32004, 31981, 31957, 31933, +31909, 31884, 31859, 31834, 31809, +31783, 31756, 31730, 31703, 31676, +31648, 31620, 31592, 31563, 31534, +31505, 31475, 31445, 31415, 31384, +31353, 31322, 31290, 31258, 31226, +31193, 31160, 31127, 31093, 31059, +31025, 30990, 30955, 30920, 30884, +30848, 30812, 30775, 30738, 30701, +30663, 30625, 30587, 30548, 30509, +30470, 30430, 30390, 30350, 30309, +30269, 30227, 30186, 30144, 30102, +30059, 30016, 29973, 29930, 29886, +29842, 29797, 29752, 29707, 29662, +29616, 29570, 29524, 29477, 29430, +29383, 29335, 29287, 29239, 29190, +29142, 29092, 29043, 28993, 28943, +28892, 28842, 28791, 28739, 28688, +28636, 28583, 28531, 28478, 28425, +28371, 28317, 28263, 28209, 28154, +28099, 28044, 27988, 27932, 27876, +27820, 27763, 27706, 27648, 27591, +27533, 27474, 27416, 27357, 27298, +27238, 27178, 27118, 27058, 26997, +26936, 26875, 26814, 26752, 26690, +26628, 26565, 26502, 26439, 26375, +26312, 26247, 26183, 26119, 26054, +25988, 25923, 25857, 25791, 25725, +25658, 25592, 25524, 25457, 25389, +25322, 25253, 25185, 25116, 25047, +24978, 24908, 24838, 24768, 24698, +24627, 24557, 24485, 24414, 24342, +24270, 24198, 24126, 24053, 23980, +23907, 23834, 23760, 23686, 23612, +23537, 23462, 23387, 23312, 23237, +23161, 23085, 23009, 22932, 22856, +22779, 22701, 22624, 22546, 22468, +22390, 22312, 22233, 22154, 22075, +21996, 21916, 21836, 21756, 21676, +21595, 21515, 21434, 21352, 21271, +21189, 21107, 21025, 20943, 20860, +20777, 20694, 20611, 20528, 20444, +20360, 20276, 20192, 20107, 20022, +19937, 19852, 19767, 19681, 19595, +19509, 19423, 19336, 19250, 19163, +19076, 18988, 18901, 18813, 18725, +18637, 18549, 18460, 18372, 18283, +18194, 18104, 18015, 17925, 17835, +17745, 17655, 17565, 17474, 17383, +17292, 17201, 17110, 17018, 16927, +16835, 16743, 16650, 16558, 16465, +16372, 16279, 16186, 16093, 15999, +15906, 15812, 15718, 15624, 15529, +15435, 15340, 15245, 15150, 15055, +14960, 14864, 14769, 14673, 14577, +14481, 14385, 14288, 14192, 14095, +13998, 13901, 13804, 13706, 13609, +13511, 13414, 13316, 13218, 13119, +13021, 12923, 12824, 12725, 12626, +12527, 12428, 12329, 12230, 12130, +12030, 11930, 11831, 11730, 11630, +11530, 11430, 11329, 11228, 11128, +11027, 10926, 10824, 10723, 10622, +10520, 10419, 10317, 10215, 10113, +10011, 9909, 9807, 9704, 9602, +9499, 9397, 9294, 9191, 9088, +8985, 8882, 8778, 8675, 8572, +8468, 8364, 8261, 8157, 8053, +7949, 7845, 7741, 7637, 7532, +7428, 7323, 7219, 7114, 7009, +6905, 6800, 6695, 6590, 6485, +6380, 6274, 6169, 6064, 5958, +5853, 5747, 5642, 5536, 5430, +5325, 5219, 5113, 5007, 4901, +4795, 4689, 4583, 4476, 4370, +4264, 4157, 4051, 3945, 3838, +3732, 3625, 3518, 3412, 3305, +3198, 3092, 2985, 2878, 2771, +2664, 2558, 2451, 2344, 2237, +2130, 2023, 1916, 1809, 1702, +1594, 1487, 1380, 1273, 1166, +1059, 952, 844, 737, 630, +523, 416, 308, 201, 94, +-13, -121, -228, -335, -442, +-550, -657, -764, -871, -978, +-1086, -1193, -1300, -1407, -1514, +-1621, -1728, -1835, -1942, -2049, +-2157, -2263, -2370, -2477, -2584, +-2691, -2798, -2905, -3012, -3118, +-3225, -3332, -3439, -3545, -3652, +-3758, -3865, -3971, -4078, -4184, +-4290, -4397, -4503, -4609, -4715, +-4821, -4927, -5033, -5139, -5245, +-5351, -5457, -5562, -5668, -5774, +-5879, -5985, -6090, -6195, -6301, +-6406, -6511, -6616, -6721, -6826, +-6931, -7036, -7140, -7245, -7349, +-7454, -7558, -7663, -7767, -7871, +-7975, -8079, -8183, -8287, -8390, +-8494, -8597, -8701, -8804, -8907, +-9011, -9114, -9217, -9319, -9422, +-9525, -9627, -9730, -9832, -9934, +-10037, -10139, -10241, -10342, -10444, +-10546, -10647, -10748, -10850, -10951, +-11052, -11153, -11253, -11354, -11455, +-11555, -11655, -11756, -11856, -11955, +-12055, -12155, -12254, -12354, -12453, +-12552, -12651, -12750, -12849, -12947, +-13046, -13144, -13242, -13340, -13438, +-13536, -13633, -13731, -13828, -13925, +-14022, -14119, -14216, -14312, -14409, +-14505, -14601, -14697, -14793, -14888, +-14984, -15079, -15174, -15269, -15364, +-15459, -15553, -15647, -15741, -15835, +-15929, -16023, -16116, -16210, -16303, +-16396, -16488, -16581, -16673, -16766, +-16858, -16949, -17041, -17133, -17224, +-17315, -17406, -17497, -17587, -17678, +-17768, -17858, -17948, -18037, -18127, +-18216, -18305, -18394, -18483, -18571, +-18659, -18747, -18835, -18923, -19010, +-19098, -19185, -19271, -19358, -19444, +-19531, -19617, -19702, -19788, -19873, +-19959, -20043, -20128, -20213, -20297, +-20381, -20465, -20549, -20632, -20715, +-20798, -20881, -20963, -21046, -21128, +-21210, -21291, -21373, -21454, -21535, +-21616, -21696, -21776, -21856, -21936, +-22016, -22095, -22174, -22253, -22331, +-22410, -22488, -22566, -22643, -22721, +-22798, -22875, -22951, -23028, -23104, +-23180, -23256, -23331, -23406, -23481, +-23556, -23630, -23704, -23778, -23852, +-23925, -23998, -24071, -24144, -24216, +-24288, -24360, -24432, -24503, -24574, +-24645, -24716, -24786, -24856, -24926, +-24995, -25064, -25133, -25202, -25270, +-25339, -25406, -25474, -25541, -25608, +-25675, -25742, -25808, -25874, -25939, +-26005, -26070, -26135, -26199, -26264, +-26327, -26391, -26455, -26518, -26581, +-26643, -26705, -26767, -26829, -26891, +-26952, -27013, -27073, -27133, -27193, +-27253, -27312, -27372, -27430, -27489, +-27547, -27605, -27663, -27720, -27777, +-27834, -27890, -27946, -28002, -28058, +-28113, -28168, -28223, -28277, -28331, +-28385, -28438, -28491, -28544, -28596, +-28649, -28701, -28752, -28803, -28854, +-28905, -28955, -29006, -29055, -29105, +-29154, -29203, -29251, -29299, -29347, +-29395, -29442, -29489, -29535, -29582, +-29628, -29673, -29719, -29764, -29808, +-29853, -29897, -29941, -29984, -30027, +-30070, -30112, -30154, -30196, -30238, +-30279, -30320, -30360, -30400, -30440, +-30480, -30519, -30558, -30596, -30635, +-30672, -30710, -30747, -30784, -30821, +-30857, -30893, -30929, -30964, -30999, +-31033, -31068, -31102, -31135, -31168, +-31201, -31234, -31266, -31298, -31330, +-31361, -31392, -31422, -31453, -31483, +-31512, -31541, -31570, -31599, -31627, +-31655, -31682, -31710, -31737, -31763, +-31789, -31815, -31841, -31866, -31891, +-31915, -31939, -31963, -31986, -32010, +-32032, -32055, -32077, -32099, -32120, +-32141, -32162, -32182, -32202, -32222, +-32241, -32260, -32279, -32297, -32315, +-32333, -32350, -32367, -32383, -32399, +-32415, -32431, -32446, -32461, -32475, +-32489, -32503, -32517, -32530, -32542, +-32555, -32567, -32579, -32590, -32601, +-32612, -32622, -32632, -32641, -32651, +-32659, -32668, -32676, -32684, -32692, +-32699, -32706, -32712, -32718, -32724, +-32729, -32734, -32739, -32743, -32747, +-32751, -32754, -32757, -32760, -32762, +-32764, -32765, -32767, -32767, -32767, +32767, 32767, 32765, 32761, 32756, +32750, 32742, 32732, 32722, 32710, +32696, 32681, 32665, 32647, 32628, +32608, 32586, 32562, 32538, 32512, +32484, 32455, 32425, 32393, 32360, +32326, 32290, 32253, 32214, 32174, +32133, 32090, 32046, 32001, 31954, +31906, 31856, 31805, 31753, 31700, +31645, 31588, 31530, 31471, 31411, +31349, 31286, 31222, 31156, 31089, +31020, 30951, 30880, 30807, 30733, +30658, 30582, 30504, 30425, 30345, +30263, 30181, 30096, 30011, 29924, +29836, 29747, 29656, 29564, 29471, +29377, 29281, 29184, 29086, 28987, +28886, 28784, 28681, 28577, 28471, +28365, 28257, 28147, 28037, 27925, +27812, 27698, 27583, 27467, 27349, +27231, 27111, 26990, 26868, 26744, +26620, 26494, 26367, 26239, 26110, +25980, 25849, 25717, 25583, 25449, +25313, 25176, 25038, 24900, 24760, +24619, 24477, 24333, 24189, 24044, +23898, 23751, 23602, 23453, 23303, +23152, 22999, 22846, 22692, 22537, +22380, 22223, 22065, 21906, 21746, +21585, 21423, 21261, 21097, 20933, +20767, 20601, 20434, 20265, 20096, +19927, 19756, 19584, 19412, 19239, +19065, 18890, 18714, 18538, 18361, +18183, 18004, 17824, 17644, 17463, +17281, 17098, 16915, 16731, 16546, +16361, 16175, 15988, 15800, 15612, +15423, 15234, 15043, 14852, 14661, +14469, 14276, 14083, 13889, 13694, +13499, 13303, 13107, 12910, 12713, +12515, 12317, 12118, 11918, 11718, +11517, 11316, 11115, 10913, 10710, +10508, 10304, 10100, 9896, 9691, +9486, 9281, 9075, 8869, 8662, +8455, 8248, 8040, 7832, 7623, +7415, 7206, 6996, 6787, 6577, +6366, 6156, 5945, 5734, 5523, +5311, 5100, 4888, 4675, 4463, +4251, 4038, 3825, 3612, 3399, +3185, 2972, 2758, 2544, 2330, +2116, 1902, 1688, 1474, 1260, +1045, 831, 617, 402, 188, +-27, -241, -456, -670, -885, +-1099, -1313, -1528, -1742, -1956, +-2170, -2384, -2598, -2811, -3025, +-3239, -3452, -3665, -3878, -4091, +-4304, -4516, -4728, -4941, -5153, +-5364, -5576, -5787, -5998, -6209, +-6419, -6629, -6839, -7049, -7258, +-7467, -7676, -7884, -8092, -8300, +-8507, -8714, -8920, -9127, -9332, +-9538, -9743, -9947, -10151, -10355, +-10558, -10761, -10963, -11165, -11367, +-11568, -11768, -11968, -12167, -12366, +-12565, -12762, -12960, -13156, -13352, +-13548, -13743, -13937, -14131, -14324, +-14517, -14709, -14900, -15091, -15281, +-15470, -15659, -15847, -16035, -16221, +-16407, -16593, -16777, -16961, -17144, +-17326, -17508, -17689, -17869, -18049, +-18227, -18405, -18582, -18758, -18934, +-19108, -19282, -19455, -19627, -19799, +-19969, -20139, -20308, -20475, -20642, +-20809, -20974, -21138, -21301, -21464, +-21626, -21786, -21946, -22105, -22263, +-22420, -22575, -22730, -22884, -23037, +-23189, -23340, -23490, -23640, -23788, +-23935, -24080, -24225, -24369, -24512, +-24654, -24795, -24934, -25073, -25211, +-25347, -25482, -25617, -25750, -25882, +-26013, -26143, -26272, -26399, -26526, +-26651, -26775, -26898, -27020, -27141, +-27260, -27379, -27496, -27612, -27727, +-27841, -27953, -28065, -28175, -28284, +-28391, -28498, -28603, -28707, -28810, +-28911, -29012, -29111, -29209, -29305, +-29401, -29495, -29587, -29679, -29769, +-29858, -29946, -30032, -30118, -30201, +-30284, -30365, -30445, -30524, -30601, +-30677, -30752, -30825, -30897, -30968, +-31038, -31106, -31172, -31238, -31302, +-31365, -31426, -31486, -31545, -31602, +-31658, -31713, -31766, -31818, -31869, +-31918, -31966, -32012, -32058, -32101, +-32144, -32185, -32224, -32262, -32299, +-32335, -32369, -32401, -32433, -32463, +-32491, -32518, -32544, -32568, -32591, +-32613, -32633, -32652, -32669, -32685, +-32700, -32713, -32724, -32735, -32744, +-32751, -32757, -32762, -32766, -32767, +32767, 32764, 32755, 32741, 32720, +32694, 32663, 32626, 32583, 32535, +32481, 32421, 32356, 32286, 32209, +32128, 32041, 31948, 31850, 31747, +31638, 31523, 31403, 31278, 31148, +31012, 30871, 30724, 30572, 30415, +30253, 30086, 29913, 29736, 29553, +29365, 29172, 28974, 28771, 28564, +28351, 28134, 27911, 27684, 27452, +27216, 26975, 26729, 26478, 26223, +25964, 25700, 25432, 25159, 24882, +24601, 24315, 24026, 23732, 23434, +23133, 22827, 22517, 22204, 21886, +21565, 21240, 20912, 20580, 20244, +19905, 19563, 19217, 18868, 18516, +18160, 17802, 17440, 17075, 16708, +16338, 15964, 15588, 15210, 14829, +14445, 14059, 13670, 13279, 12886, +12490, 12093, 11693, 11291, 10888, +10482, 10075, 9666, 9255, 8843, +8429, 8014, 7597, 7180, 6760, +6340, 5919, 5496, 5073, 4649, +4224, 3798, 3372, 2945, 2517, +2090, 1661, 1233, 804, 375, +-54, -483, -911, -1340, -1768, +-2197, -2624, -3052, -3479, -3905, +-4330, -4755, -5179, -5602, -6024, +-6445, -6865, -7284, -7702, -8118, +-8533, -8946, -9358, -9768, -10177, +-10584, -10989, -11392, -11793, -12192, +-12589, -12984, -13377, -13767, -14155, +-14541, -14924, -15305, -15683, -16058, +-16430, -16800, -17167, -17531, -17892, +-18249, -18604, -18956, -19304, -19649, +-19990, -20329, -20663, -20994, -21322, +-21646, -21966, -22282, -22595, -22904, +-23208, -23509, -23806, -24099, -24387, +-24672, -24952, -25228, -25499, -25766, +-26029, -26288, -26541, -26791, -27035, +-27275, -27511, -27741, -27967, -28188, +-28405, -28616, -28823, -29024, -29221, +-29412, -29599, -29780, -29957, -30128, +-30294, -30455, -30611, -30761, -30906, +-31046, -31181, -31310, -31434, -31552, +-31665, -31773, -31875, -31972, -32063, +-32149, -32229, -32304, -32373, -32437, +-32495, -32547, -32594, -32635, -32671, +-32701, -32726, -32745, -32758, -32766, +32767, 32754, 32717, 32658, 32577, +32473, 32348, 32200, 32029, 31837, +31624, 31388, 31131, 30853, 30553, +30232, 29891, 29530, 29148, 28746, +28324, 27883, 27423, 26944, 26447, +25931, 25398, 24847, 24279, 23695, +23095, 22478, 21846, 21199, 20538, +19863, 19174, 18472, 17757, 17030, +16291, 15541, 14781, 14010, 13230, +12441, 11643, 10837, 10024, 9204, +8377, 7545, 6708, 5866, 5020, +4171, 3319, 2464, 1608, 751, +-107, -965, -1822, -2678, -3532, +-4383, -5232, -6077, -6918, -7754, +-8585, -9409, -10228, -11039, -11843, +-12639, -13426, -14204, -14972, -15730, +-16477, -17213, -17937, -18648, -19347, +-20033, -20705, -21363, -22006, -22634, +-23246, -23843, -24423, -24986, -25533, +-26062, -26573, -27066, -27540, -27995, +-28431, -28848, -29245, -29622, -29979, +-30315, -30630, -30924, -31197, -31449, +-31679, -31887, -32074, -32239, -32381, +-32501, -32600, -32675, -32729, -32759, +}; +#endif + +static const CELTMode mode48000_960_120 = { +48000, /* Fs */ +120, /* overlap */ +21, /* nbEBands */ +21, /* effEBands */ +{27853, 0, 4096, 8192, }, /* preemph */ +eband5ms, /* eBands */ +3, /* maxLM */ +8, /* nbShortMdcts */ +120, /* shortMdctSize */ +11, /* nbAllocVectors */ +band_allocation, /* allocVectors */ +logN400, /* logN */ +window120, /* window */ +{1920, 3, {&fft_state48000_960_0, &fft_state48000_960_1, &fft_state48000_960_2, &fft_state48000_960_3, }, mdct_twiddles960}, /* mdct */ +{392, cache_index50, cache_bits50, cache_caps50}, /* cache */ +}; + +/* List of all the available modes */ +#define TOTAL_MODES 1 +static const CELTMode * const static_mode_list[TOTAL_MODES] = { +&mode48000_960_120, +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed_arm_ne10.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed_arm_ne10.h new file mode 100644 index 0000000000..7623092192 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_fixed_arm_ne10.h @@ -0,0 +1,388 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_int32_t ne10_twiddles_480[480] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +{2147483647,0}, {2147299667,-28109693}, {2146747758,-56214570}, +{2145828015,-84309815}, {2144540595,-112390613}, {2142885719,-140452154}, +{2140863671,-168489630}, {2138474797,-196498235}, {2135719506,-224473172}, +{2132598271,-252409646}, {2129111626,-280302871}, {2125260168,-308148068}, +{2121044558,-335940465}, {2116465518,-363675300}, {2111523833,-391347822}, +{2106220349,-418953288}, {2100555974,-446486968}, {2094531681,-473944146}, +{2088148500,-501320115}, {2081407525,-528610186}, {2074309912,-555809682}, +{2066856885,-582913912}, {2059049696,-609918325}, {2050889698,-636818231}, +{2042378310,-663608960}, {2033516972,-690285983}, {2024307180,-716844791}, +{2014750533,-743280770}, {2004848691,-769589332}, {1994603329,-795766029}, +{1984016179,-821806435}, {1973089077,-847706028}, {1961823921,-873460313}, +{1950222618,-899064934}, {1938287127,-924515564}, {1926019520,-949807783}, +{1913421927,-974937199}, {1900496481,-999899565}, {1887245364,-1024690661}, +{1873670877,-1049306180}, {1859775377,-1073741851}, {1845561215,-1097993541}, +{1831030826,-1122057097}, {1816186632,-1145928502}, {1801031311,-1169603450}, +{1785567394,-1193077993}, {1769797456,-1216348214}, {1753724345,-1239409914}, +{1737350743,-1262259248}, {1720679456,-1284892300}, {1703713340,-1307305194}, +{1686455222,-1329494189}, {1668908218,-1351455280}, {1651075255,-1373184807}, +{1632959307,-1394679144}, {1614563642,-1415934412}, {1595891331,-1436947067}, +{1576945572,-1457713510}, {1557729613,-1478230181}, {1538246655,-1498493658}, +{1518500216,-1518500282}, {1498493590,-1538246721}, {1478230113,-1557729677}, +{1457713441,-1576945636}, {1436946998,-1595891394}, {1415934341,-1614563704}, +{1394679073,-1632959368}, {1373184735,-1651075315}, {1351455207,-1668908277}, +{1329494115,-1686455280}, {1307305120,-1703713397}, {1284892225,-1720679512}, +{1262259172,-1737350799}, {1239409837,-1753724400}, {1216348136,-1769797510}, +{1193077915,-1785567446}, {1169603371,-1801031362}, {1145928423,-1816186682}, +{1122057017,-1831030875}, {1097993571,-1845561197}, {1073741769,-1859775424}, +{1049305987,-1873670985}, {1024690635,-1887245378}, {999899482,-1900496524}, +{974937230,-1913421912}, {949807699,-1926019561}, {924515422,-1938287195}, +{899064965,-1950222603}, {873460227,-1961823959}, {847705824,-1973089164}, +{821806407,-1984016190}, {795765941,-1994603364}, {769589125,-2004848771}, +{743280682,-2014750566}, {716844642,-2024307233}, {690286016,-2033516961}, +{663608871,-2042378339}, {636818019,-2050889764}, {609918296,-2059049705}, +{582913822,-2066856911}, {555809715,-2074309903}, {528610126,-2081407540}, +{501319962,-2088148536}, {473944148,-2094531680}, {446486876,-2100555994}, +{418953102,-2106220386}, {391347792,-2111523838}, {363675176,-2116465540}, +{335940246,-2121044593}, {308148006,-2125260177}, {280302715,-2129111646}, +{252409648,-2132598271}, {224473078,-2135719516}, {196498046,-2138474814}, +{168489600,-2140863674}, {140452029,-2142885728}, {112390647,-2144540593}, +{84309753,-2145828017}, {56214412,-2146747762}, {28109695,-2147299667}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{-94,-2147483647}, {-56214600,-2146747757}, {-112390835,-2144540584}, +{-168489787,-2140863659}, {-224473265,-2135719496}, {-280302901,-2129111622}, +{-335940431,-2121044564}, {-391347977,-2111523804}, {-446487060,-2100555955}, +{-501320144,-2088148493}, {-555809896,-2074309855}, {-609918476,-2059049651}, +{-663609049,-2042378281}, {-716844819,-2024307170}, {-769589300,-2004848703}, +{-821806581,-1984016118}, {-873460398,-1961823883}, {-924515591,-1938287114}, +{-974937397,-1913421827}, {-1024690575,-1887245411}, {-1073741932,-1859775330}, +{-1122057395,-1831030643}, {-1169603421,-1801031330}, {-1216348291,-1769797403}, +{-1262259116,-1737350839}, {-1307305268,-1703713283}, {-1351455453,-1668908078}, +{-1394679021,-1632959413}, {-1436947137,-1595891268}, {-1478230435,-1557729372}, +{-1518500258,-1518500240}, {-1557729742,-1478230045}, {-1595891628,-1436946738}, +{-1632959429,-1394679001}, {-1668908417,-1351455035}, {-1703713298,-1307305248}, +{-1737350854,-1262259096}, {-1769797708,-1216347848}, {-1801031344,-1169603400}, +{-1831030924,-1122056937}, {-1859775343,-1073741910}, {-1887245423,-1024690552}, +{-1913422071,-974936918}, {-1938287125,-924515568}, {-1961823997,-873460141}, +{-1984016324,-821806084}, {-2004848713,-769589276}, {-2024307264,-716844553}, +{-2042378447,-663608538}, {-2059049731,-609918206}, {-2074309994,-555809377}, +{-2088148499,-501320119}, {-2100556013,-446486785}, {-2111523902,-391347448}, +{-2121044568,-335940406}, {-2129111659,-280302621}, {-2135719499,-224473240}, +{-2140863681,-168489506}, {-2144540612,-112390298}, {-2146747758,-56214574}, +{2147483647,0}, {2145828015,-84309815}, {2140863671,-168489630}, +{2132598271,-252409646}, {2121044558,-335940465}, {2106220349,-418953288}, +{2088148500,-501320115}, {2066856885,-582913912}, {2042378310,-663608960}, +{2014750533,-743280770}, {1984016179,-821806435}, {1950222618,-899064934}, +{1913421927,-974937199}, {1873670877,-1049306180}, {1831030826,-1122057097}, +{1785567394,-1193077993}, {1737350743,-1262259248}, {1686455222,-1329494189}, +{1632959307,-1394679144}, {1576945572,-1457713510}, {1518500216,-1518500282}, +{1457713441,-1576945636}, {1394679073,-1632959368}, {1329494115,-1686455280}, +{1262259172,-1737350799}, {1193077915,-1785567446}, {1122057017,-1831030875}, +{1049305987,-1873670985}, {974937230,-1913421912}, {899064965,-1950222603}, +{821806407,-1984016190}, {743280682,-2014750566}, {663608871,-2042378339}, +{582913822,-2066856911}, {501319962,-2088148536}, {418953102,-2106220386}, +{335940246,-2121044593}, {252409648,-2132598271}, {168489600,-2140863674}, +{84309753,-2145828017}, {-94,-2147483647}, {-84309940,-2145828010}, +{-168489787,-2140863659}, {-252409834,-2132598249}, {-335940431,-2121044564}, +{-418953286,-2106220349}, {-501320144,-2088148493}, {-582914003,-2066856860}, +{-663609049,-2042378281}, {-743280858,-2014750501}, {-821806581,-1984016118}, +{-899065136,-1950222525}, {-974937397,-1913421827}, {-1049306374,-1873670768}, +{-1122057395,-1831030643}, {-1193078284,-1785567199}, {-1262259116,-1737350839}, +{-1329494061,-1686455323}, {-1394679021,-1632959413}, {-1457713485,-1576945595}, +{-1518500258,-1518500240}, {-1576945613,-1457713466}, {-1632959429,-1394679001}, +{-1686455338,-1329494041}, {-1737350854,-1262259096}, {-1785567498,-1193077837}, +{-1831030924,-1122056937}, {-1873671031,-1049305905}, {-1913422071,-974936918}, +{-1950222750,-899064648}, {-1984016324,-821806084}, {-2014750687,-743280354}, +{-2042378447,-663608538}, {-2066856867,-582913978}, {-2088148499,-501320119}, +{-2106220354,-418953261}, {-2121044568,-335940406}, {-2132598282,-252409555}, +{-2140863681,-168489506}, {-2145828021,-84309659}, {-2147483647,188}, +{-2145828006,84310034}, {-2140863651,168489881}, {-2132598237,252409928}, +{-2121044509,335940777}, {-2106220281,418953629}, {-2088148411,501320484}, +{-2066856765,582914339}, {-2042378331,663608895}, {-2014750557,743280706}, +{-1984016181,821806431}, {-1950222593,899064989}, {-1913421900,974937252}, +{-1873670848,1049306232}, {-1831030728,1122057257}, {-1785567289,1193078149}, +{-1737350633,1262259400}, {-1686455106,1329494336}, {-1632959185,1394679287}, +{-1576945358,1457713742}, {-1518499993,1518500506}, {-1457713209,1576945850}, +{-1394678735,1632959656}, {-1329493766,1686455555}, {-1262258813,1737351059}, +{-1193077546,1785567692}, {-1122056638,1831031107}, {-1049305599,1873671202}, +{-974936606,1913422229}, {-899064330,1950222896}, {-821805761,1984016458}, +{-743280025,2014750808}, {-663609179,2042378239}, {-582914134,2066856823}, +{-501320277,2088148461}, {-418953420,2106220322}, {-335940566,2121044542}, +{-252409716,2132598263}, {-168489668,2140863668}, {-84309821,2145828015}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_240[240] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{-94,-2147483647}, {-112390835,-2144540584}, {-224473265,-2135719496}, +{-335940431,-2121044564}, {-446487060,-2100555955}, {-555809896,-2074309855}, +{-663609049,-2042378281}, {-769589300,-2004848703}, {-873460398,-1961823883}, +{-974937397,-1913421827}, {-1073741932,-1859775330}, {-1169603421,-1801031330}, +{-1262259116,-1737350839}, {-1351455453,-1668908078}, {-1436947137,-1595891268}, +{-1518500258,-1518500240}, {-1595891628,-1436946738}, {-1668908417,-1351455035}, +{-1737350854,-1262259096}, {-1801031344,-1169603400}, {-1859775343,-1073741910}, +{-1913422071,-974936918}, {-1961823997,-873460141}, {-2004848713,-769589276}, +{-2042378447,-663608538}, {-2074309994,-555809377}, {-2100556013,-446486785}, +{-2121044568,-335940406}, {-2135719499,-224473240}, {-2144540612,-112390298}, +{2147483647,0}, {2140863671,-168489630}, {2121044558,-335940465}, +{2088148500,-501320115}, {2042378310,-663608960}, {1984016179,-821806435}, +{1913421927,-974937199}, {1831030826,-1122057097}, {1737350743,-1262259248}, +{1632959307,-1394679144}, {1518500216,-1518500282}, {1394679073,-1632959368}, +{1262259172,-1737350799}, {1122057017,-1831030875}, {974937230,-1913421912}, +{821806407,-1984016190}, {663608871,-2042378339}, {501319962,-2088148536}, +{335940246,-2121044593}, {168489600,-2140863674}, {-94,-2147483647}, +{-168489787,-2140863659}, {-335940431,-2121044564}, {-501320144,-2088148493}, +{-663609049,-2042378281}, {-821806581,-1984016118}, {-974937397,-1913421827}, +{-1122057395,-1831030643}, {-1262259116,-1737350839}, {-1394679021,-1632959413}, +{-1518500258,-1518500240}, {-1632959429,-1394679001}, {-1737350854,-1262259096}, +{-1831030924,-1122056937}, {-1913422071,-974936918}, {-1984016324,-821806084}, +{-2042378447,-663608538}, {-2088148499,-501320119}, {-2121044568,-335940406}, +{-2140863681,-168489506}, {-2147483647,188}, {-2140863651,168489881}, +{-2121044509,335940777}, {-2088148411,501320484}, {-2042378331,663608895}, +{-1984016181,821806431}, {-1913421900,974937252}, {-1831030728,1122057257}, +{-1737350633,1262259400}, {-1632959185,1394679287}, {-1518499993,1518500506}, +{-1394678735,1632959656}, {-1262258813,1737351059}, {-1122056638,1831031107}, +{-974936606,1913422229}, {-821805761,1984016458}, {-663609179,2042378239}, +{-501320277,2088148461}, {-335940566,2121044542}, {-168489668,2140863668}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_120[120] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_60[60] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +}; +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_int32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_480[120], +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_int32_t_480, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_int32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_240[60], +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_int32_t_240, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_int32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_120[30], +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_int32_t_120, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_int32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_60[15], +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_int32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float.h new file mode 100644 index 0000000000..e102a38391 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float.h @@ -0,0 +1,888 @@ +/* The contents of this file was automatically generated by dump_modes.c + with arguments: 48000 960 + It contains static definitions for some pre-defined modes. */ +#include "modes.h" +#include "rate.h" + +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_float_arm_ne10.h" +#endif + +#ifndef DEF_WINDOW120 +#define DEF_WINDOW120 +static const opus_val16 window120[120] = { +6.7286966e-05f, 0.00060551348f, 0.0016815970f, 0.0032947962f, 0.0054439943f, +0.0081276923f, 0.011344001f, 0.015090633f, 0.019364886f, 0.024163635f, +0.029483315f, 0.035319905f, 0.041668911f, 0.048525347f, 0.055883718f, +0.063737999f, 0.072081616f, 0.080907428f, 0.090207705f, 0.099974111f, +0.11019769f, 0.12086883f, 0.13197729f, 0.14351214f, 0.15546177f, +0.16781389f, 0.18055550f, 0.19367290f, 0.20715171f, 0.22097682f, +0.23513243f, 0.24960208f, 0.26436860f, 0.27941419f, 0.29472040f, +0.31026818f, 0.32603788f, 0.34200931f, 0.35816177f, 0.37447407f, +0.39092462f, 0.40749142f, 0.42415215f, 0.44088423f, 0.45766484f, +0.47447104f, 0.49127978f, 0.50806798f, 0.52481261f, 0.54149077f, +0.55807973f, 0.57455701f, 0.59090049f, 0.60708841f, 0.62309951f, +0.63891306f, 0.65450896f, 0.66986776f, 0.68497077f, 0.69980010f, +0.71433873f, 0.72857055f, 0.74248043f, 0.75605424f, 0.76927895f, +0.78214257f, 0.79463430f, 0.80674445f, 0.81846456f, 0.82978733f, +0.84070669f, 0.85121779f, 0.86131698f, 0.87100183f, 0.88027111f, +0.88912479f, 0.89756398f, 0.90559094f, 0.91320904f, 0.92042270f, +0.92723738f, 0.93365955f, 0.93969656f, 0.94535671f, 0.95064907f, +0.95558353f, 0.96017067f, 0.96442171f, 0.96834849f, 0.97196334f, +0.97527906f, 0.97830883f, 0.98106616f, 0.98356480f, 0.98581869f, +0.98784191f, 0.98964856f, 0.99125274f, 0.99266849f, 0.99390969f, +0.99499004f, 0.99592297f, 0.99672162f, 0.99739874f, 0.99796667f, +0.99843728f, 0.99882195f, 0.99913147f, 0.99937606f, 0.99956527f, +0.99970802f, 0.99981248f, 0.99988613f, 0.99993565f, 0.99996697f, +0.99998518f, 0.99999457f, 0.99999859f, 0.99999982f, 1.0000000f, +}; +#endif + +#ifndef DEF_LOGN400 +#define DEF_LOGN400 +static const opus_int16 logN400[21] = { +0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 21, 21, 24, 29, 34, 36, }; +#endif + +#ifndef DEF_PULSE_CACHE50 +#define DEF_PULSE_CACHE50 +static const opus_int16 cache_index50[105] = { +-1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, +82, 82, 123, 164, 200, 222, 0, 0, 0, 0, 0, 0, 0, 0, 41, +41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, +41, 41, 41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, +318, 328, 336, 123, 123, 123, 123, 123, 123, 123, 123, 240, 240, 240, 240, +305, 305, 305, 318, 318, 343, 351, 358, 364, 240, 240, 240, 240, 240, 240, +240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382, 387, +}; +static const unsigned char cache_bits50[392] = { +40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, +31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47, 47, 49, 50, +51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, +66, 67, 68, 69, 70, 71, 71, 40, 20, 33, 41, 48, 53, 57, 61, +64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92, +94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, +124, 126, 128, 40, 23, 39, 51, 60, 67, 73, 79, 83, 87, 91, 94, +97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126, 129, 131, 135, 139, +142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35, +28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, +153, 159, 165, 171, 176, 180, 185, 189, 192, 199, 205, 211, 216, 220, 225, +229, 232, 239, 245, 251, 21, 33, 58, 79, 97, 112, 125, 137, 148, 157, +166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35, 63, +86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, +25, 31, 55, 75, 91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, +185, 190, 200, 208, 215, 222, 229, 235, 240, 245, 255, 16, 36, 65, 89, +110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250, 11, 41, +74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, +163, 186, 207, 227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, +228, 241, 253, 9, 44, 81, 113, 142, 168, 192, 214, 235, 255, 7, 49, +90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7, 47, +87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, +106, 151, 192, 231, 5, 59, 111, 158, 202, 243, 5, 55, 103, 147, 187, +224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175, 224, 4, 67, 127, +182, 234, }; +static const unsigned char cache_caps50[168] = { +224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, +178, 178, 168, 134, 61, 37, 224, 224, 224, 224, 224, 224, 224, 224, 240, +240, 240, 240, 207, 207, 207, 198, 198, 183, 144, 66, 40, 160, 160, 160, +160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, 183, 183, 172, +138, 64, 38, 240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, +204, 204, 204, 193, 193, 180, 143, 66, 40, 185, 185, 185, 185, 185, 185, +185, 185, 193, 193, 193, 193, 193, 193, 193, 183, 183, 172, 138, 65, 39, +207, 207, 207, 207, 207, 207, 207, 207, 204, 204, 204, 204, 201, 201, 201, +188, 188, 176, 141, 66, 40, 193, 193, 193, 193, 193, 193, 193, 193, 193, +193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39, 204, 204, 204, +204, 204, 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, +140, 66, 40, }; +#endif + +#ifndef FFT_TWIDDLES48000_960 +#define FFT_TWIDDLES48000_960 +static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { +{1.0000000f, -0.0000000f}, {0.99991433f, -0.013089596f}, +{0.99965732f, -0.026176948f}, {0.99922904f, -0.039259816f}, +{0.99862953f, -0.052335956f}, {0.99785892f, -0.065403129f}, +{0.99691733f, -0.078459096f}, {0.99580493f, -0.091501619f}, +{0.99452190f, -0.10452846f}, {0.99306846f, -0.11753740f}, +{0.99144486f, -0.13052619f}, {0.98965139f, -0.14349262f}, +{0.98768834f, -0.15643447f}, {0.98555606f, -0.16934950f}, +{0.98325491f, -0.18223553f}, {0.98078528f, -0.19509032f}, +{0.97814760f, -0.20791169f}, {0.97534232f, -0.22069744f}, +{0.97236992f, -0.23344536f}, {0.96923091f, -0.24615329f}, +{0.96592583f, -0.25881905f}, {0.96245524f, -0.27144045f}, +{0.95881973f, -0.28401534f}, {0.95501994f, -0.29654157f}, +{0.95105652f, -0.30901699f}, {0.94693013f, -0.32143947f}, +{0.94264149f, -0.33380686f}, {0.93819134f, -0.34611706f}, +{0.93358043f, -0.35836795f}, {0.92880955f, -0.37055744f}, +{0.92387953f, -0.38268343f}, {0.91879121f, -0.39474386f}, +{0.91354546f, -0.40673664f}, {0.90814317f, -0.41865974f}, +{0.90258528f, -0.43051110f}, {0.89687274f, -0.44228869f}, +{0.89100652f, -0.45399050f}, {0.88498764f, -0.46561452f}, +{0.87881711f, -0.47715876f}, {0.87249601f, -0.48862124f}, +{0.86602540f, -0.50000000f}, {0.85940641f, -0.51129309f}, +{0.85264016f, -0.52249856f}, {0.84572782f, -0.53361452f}, +{0.83867057f, -0.54463904f}, {0.83146961f, -0.55557023f}, +{0.82412619f, -0.56640624f}, {0.81664156f, -0.57714519f}, +{0.80901699f, -0.58778525f}, {0.80125381f, -0.59832460f}, +{0.79335334f, -0.60876143f}, {0.78531693f, -0.61909395f}, +{0.77714596f, -0.62932039f}, {0.76884183f, -0.63943900f}, +{0.76040597f, -0.64944805f}, {0.75183981f, -0.65934582f}, +{0.74314483f, -0.66913061f}, {0.73432251f, -0.67880075f}, +{0.72537437f, -0.68835458f}, {0.71630194f, -0.69779046f}, +{0.70710678f, -0.70710678f}, {0.69779046f, -0.71630194f}, +{0.68835458f, -0.72537437f}, {0.67880075f, -0.73432251f}, +{0.66913061f, -0.74314483f}, {0.65934582f, -0.75183981f}, +{0.64944805f, -0.76040597f}, {0.63943900f, -0.76884183f}, +{0.62932039f, -0.77714596f}, {0.61909395f, -0.78531693f}, +{0.60876143f, -0.79335334f}, {0.59832460f, -0.80125381f}, +{0.58778525f, -0.80901699f}, {0.57714519f, -0.81664156f}, +{0.56640624f, -0.82412619f}, {0.55557023f, -0.83146961f}, +{0.54463904f, -0.83867057f}, {0.53361452f, -0.84572782f}, +{0.52249856f, -0.85264016f}, {0.51129309f, -0.85940641f}, +{0.50000000f, -0.86602540f}, {0.48862124f, -0.87249601f}, +{0.47715876f, -0.87881711f}, {0.46561452f, -0.88498764f}, +{0.45399050f, -0.89100652f}, {0.44228869f, -0.89687274f}, +{0.43051110f, -0.90258528f}, {0.41865974f, -0.90814317f}, +{0.40673664f, -0.91354546f}, {0.39474386f, -0.91879121f}, +{0.38268343f, -0.92387953f}, {0.37055744f, -0.92880955f}, +{0.35836795f, -0.93358043f}, {0.34611706f, -0.93819134f}, +{0.33380686f, -0.94264149f}, {0.32143947f, -0.94693013f}, +{0.30901699f, -0.95105652f}, {0.29654157f, -0.95501994f}, +{0.28401534f, -0.95881973f}, {0.27144045f, -0.96245524f}, +{0.25881905f, -0.96592583f}, {0.24615329f, -0.96923091f}, +{0.23344536f, -0.97236992f}, {0.22069744f, -0.97534232f}, +{0.20791169f, -0.97814760f}, {0.19509032f, -0.98078528f}, +{0.18223553f, -0.98325491f}, {0.16934950f, -0.98555606f}, +{0.15643447f, -0.98768834f}, {0.14349262f, -0.98965139f}, +{0.13052619f, -0.99144486f}, {0.11753740f, -0.99306846f}, +{0.10452846f, -0.99452190f}, {0.091501619f, -0.99580493f}, +{0.078459096f, -0.99691733f}, {0.065403129f, -0.99785892f}, +{0.052335956f, -0.99862953f}, {0.039259816f, -0.99922904f}, +{0.026176948f, -0.99965732f}, {0.013089596f, -0.99991433f}, +{6.1230318e-17f, -1.0000000f}, {-0.013089596f, -0.99991433f}, +{-0.026176948f, -0.99965732f}, {-0.039259816f, -0.99922904f}, +{-0.052335956f, -0.99862953f}, {-0.065403129f, -0.99785892f}, +{-0.078459096f, -0.99691733f}, {-0.091501619f, -0.99580493f}, +{-0.10452846f, -0.99452190f}, {-0.11753740f, -0.99306846f}, +{-0.13052619f, -0.99144486f}, {-0.14349262f, -0.98965139f}, +{-0.15643447f, -0.98768834f}, {-0.16934950f, -0.98555606f}, +{-0.18223553f, -0.98325491f}, {-0.19509032f, -0.98078528f}, +{-0.20791169f, -0.97814760f}, {-0.22069744f, -0.97534232f}, +{-0.23344536f, -0.97236992f}, {-0.24615329f, -0.96923091f}, +{-0.25881905f, -0.96592583f}, {-0.27144045f, -0.96245524f}, +{-0.28401534f, -0.95881973f}, {-0.29654157f, -0.95501994f}, +{-0.30901699f, -0.95105652f}, {-0.32143947f, -0.94693013f}, +{-0.33380686f, -0.94264149f}, {-0.34611706f, -0.93819134f}, +{-0.35836795f, -0.93358043f}, {-0.37055744f, -0.92880955f}, +{-0.38268343f, -0.92387953f}, {-0.39474386f, -0.91879121f}, +{-0.40673664f, -0.91354546f}, {-0.41865974f, -0.90814317f}, +{-0.43051110f, -0.90258528f}, {-0.44228869f, -0.89687274f}, +{-0.45399050f, -0.89100652f}, {-0.46561452f, -0.88498764f}, +{-0.47715876f, -0.87881711f}, {-0.48862124f, -0.87249601f}, +{-0.50000000f, -0.86602540f}, {-0.51129309f, -0.85940641f}, +{-0.52249856f, -0.85264016f}, {-0.53361452f, -0.84572782f}, +{-0.54463904f, -0.83867057f}, {-0.55557023f, -0.83146961f}, +{-0.56640624f, -0.82412619f}, {-0.57714519f, -0.81664156f}, +{-0.58778525f, -0.80901699f}, {-0.59832460f, -0.80125381f}, +{-0.60876143f, -0.79335334f}, {-0.61909395f, -0.78531693f}, +{-0.62932039f, -0.77714596f}, {-0.63943900f, -0.76884183f}, +{-0.64944805f, -0.76040597f}, {-0.65934582f, -0.75183981f}, +{-0.66913061f, -0.74314483f}, {-0.67880075f, -0.73432251f}, +{-0.68835458f, -0.72537437f}, {-0.69779046f, -0.71630194f}, +{-0.70710678f, -0.70710678f}, {-0.71630194f, -0.69779046f}, +{-0.72537437f, -0.68835458f}, {-0.73432251f, -0.67880075f}, +{-0.74314483f, -0.66913061f}, {-0.75183981f, -0.65934582f}, +{-0.76040597f, -0.64944805f}, {-0.76884183f, -0.63943900f}, +{-0.77714596f, -0.62932039f}, {-0.78531693f, -0.61909395f}, +{-0.79335334f, -0.60876143f}, {-0.80125381f, -0.59832460f}, +{-0.80901699f, -0.58778525f}, {-0.81664156f, -0.57714519f}, +{-0.82412619f, -0.56640624f}, {-0.83146961f, -0.55557023f}, +{-0.83867057f, -0.54463904f}, {-0.84572782f, -0.53361452f}, +{-0.85264016f, -0.52249856f}, {-0.85940641f, -0.51129309f}, +{-0.86602540f, -0.50000000f}, {-0.87249601f, -0.48862124f}, +{-0.87881711f, -0.47715876f}, {-0.88498764f, -0.46561452f}, +{-0.89100652f, -0.45399050f}, {-0.89687274f, -0.44228869f}, +{-0.90258528f, -0.43051110f}, {-0.90814317f, -0.41865974f}, +{-0.91354546f, -0.40673664f}, {-0.91879121f, -0.39474386f}, +{-0.92387953f, -0.38268343f}, {-0.92880955f, -0.37055744f}, +{-0.93358043f, -0.35836795f}, {-0.93819134f, -0.34611706f}, +{-0.94264149f, -0.33380686f}, {-0.94693013f, -0.32143947f}, +{-0.95105652f, -0.30901699f}, {-0.95501994f, -0.29654157f}, +{-0.95881973f, -0.28401534f}, {-0.96245524f, -0.27144045f}, +{-0.96592583f, -0.25881905f}, {-0.96923091f, -0.24615329f}, +{-0.97236992f, -0.23344536f}, {-0.97534232f, -0.22069744f}, +{-0.97814760f, -0.20791169f}, {-0.98078528f, -0.19509032f}, +{-0.98325491f, -0.18223553f}, {-0.98555606f, -0.16934950f}, +{-0.98768834f, -0.15643447f}, {-0.98965139f, -0.14349262f}, +{-0.99144486f, -0.13052619f}, {-0.99306846f, -0.11753740f}, +{-0.99452190f, -0.10452846f}, {-0.99580493f, -0.091501619f}, +{-0.99691733f, -0.078459096f}, {-0.99785892f, -0.065403129f}, +{-0.99862953f, -0.052335956f}, {-0.99922904f, -0.039259816f}, +{-0.99965732f, -0.026176948f}, {-0.99991433f, -0.013089596f}, +{-1.0000000f, -1.2246064e-16f}, {-0.99991433f, 0.013089596f}, +{-0.99965732f, 0.026176948f}, {-0.99922904f, 0.039259816f}, +{-0.99862953f, 0.052335956f}, {-0.99785892f, 0.065403129f}, +{-0.99691733f, 0.078459096f}, {-0.99580493f, 0.091501619f}, +{-0.99452190f, 0.10452846f}, {-0.99306846f, 0.11753740f}, +{-0.99144486f, 0.13052619f}, {-0.98965139f, 0.14349262f}, +{-0.98768834f, 0.15643447f}, {-0.98555606f, 0.16934950f}, +{-0.98325491f, 0.18223553f}, {-0.98078528f, 0.19509032f}, +{-0.97814760f, 0.20791169f}, {-0.97534232f, 0.22069744f}, +{-0.97236992f, 0.23344536f}, {-0.96923091f, 0.24615329f}, +{-0.96592583f, 0.25881905f}, {-0.96245524f, 0.27144045f}, +{-0.95881973f, 0.28401534f}, {-0.95501994f, 0.29654157f}, +{-0.95105652f, 0.30901699f}, {-0.94693013f, 0.32143947f}, +{-0.94264149f, 0.33380686f}, {-0.93819134f, 0.34611706f}, +{-0.93358043f, 0.35836795f}, {-0.92880955f, 0.37055744f}, +{-0.92387953f, 0.38268343f}, {-0.91879121f, 0.39474386f}, +{-0.91354546f, 0.40673664f}, {-0.90814317f, 0.41865974f}, +{-0.90258528f, 0.43051110f}, {-0.89687274f, 0.44228869f}, +{-0.89100652f, 0.45399050f}, {-0.88498764f, 0.46561452f}, +{-0.87881711f, 0.47715876f}, {-0.87249601f, 0.48862124f}, +{-0.86602540f, 0.50000000f}, {-0.85940641f, 0.51129309f}, +{-0.85264016f, 0.52249856f}, {-0.84572782f, 0.53361452f}, +{-0.83867057f, 0.54463904f}, {-0.83146961f, 0.55557023f}, +{-0.82412619f, 0.56640624f}, {-0.81664156f, 0.57714519f}, +{-0.80901699f, 0.58778525f}, {-0.80125381f, 0.59832460f}, +{-0.79335334f, 0.60876143f}, {-0.78531693f, 0.61909395f}, +{-0.77714596f, 0.62932039f}, {-0.76884183f, 0.63943900f}, +{-0.76040597f, 0.64944805f}, {-0.75183981f, 0.65934582f}, +{-0.74314483f, 0.66913061f}, {-0.73432251f, 0.67880075f}, +{-0.72537437f, 0.68835458f}, {-0.71630194f, 0.69779046f}, +{-0.70710678f, 0.70710678f}, {-0.69779046f, 0.71630194f}, +{-0.68835458f, 0.72537437f}, {-0.67880075f, 0.73432251f}, +{-0.66913061f, 0.74314483f}, {-0.65934582f, 0.75183981f}, +{-0.64944805f, 0.76040597f}, {-0.63943900f, 0.76884183f}, +{-0.62932039f, 0.77714596f}, {-0.61909395f, 0.78531693f}, +{-0.60876143f, 0.79335334f}, {-0.59832460f, 0.80125381f}, +{-0.58778525f, 0.80901699f}, {-0.57714519f, 0.81664156f}, +{-0.56640624f, 0.82412619f}, {-0.55557023f, 0.83146961f}, +{-0.54463904f, 0.83867057f}, {-0.53361452f, 0.84572782f}, +{-0.52249856f, 0.85264016f}, {-0.51129309f, 0.85940641f}, +{-0.50000000f, 0.86602540f}, {-0.48862124f, 0.87249601f}, +{-0.47715876f, 0.87881711f}, {-0.46561452f, 0.88498764f}, +{-0.45399050f, 0.89100652f}, {-0.44228869f, 0.89687274f}, +{-0.43051110f, 0.90258528f}, {-0.41865974f, 0.90814317f}, +{-0.40673664f, 0.91354546f}, {-0.39474386f, 0.91879121f}, +{-0.38268343f, 0.92387953f}, {-0.37055744f, 0.92880955f}, +{-0.35836795f, 0.93358043f}, {-0.34611706f, 0.93819134f}, +{-0.33380686f, 0.94264149f}, {-0.32143947f, 0.94693013f}, +{-0.30901699f, 0.95105652f}, {-0.29654157f, 0.95501994f}, +{-0.28401534f, 0.95881973f}, {-0.27144045f, 0.96245524f}, +{-0.25881905f, 0.96592583f}, {-0.24615329f, 0.96923091f}, +{-0.23344536f, 0.97236992f}, {-0.22069744f, 0.97534232f}, +{-0.20791169f, 0.97814760f}, {-0.19509032f, 0.98078528f}, +{-0.18223553f, 0.98325491f}, {-0.16934950f, 0.98555606f}, +{-0.15643447f, 0.98768834f}, {-0.14349262f, 0.98965139f}, +{-0.13052619f, 0.99144486f}, {-0.11753740f, 0.99306846f}, +{-0.10452846f, 0.99452190f}, {-0.091501619f, 0.99580493f}, +{-0.078459096f, 0.99691733f}, {-0.065403129f, 0.99785892f}, +{-0.052335956f, 0.99862953f}, {-0.039259816f, 0.99922904f}, +{-0.026176948f, 0.99965732f}, {-0.013089596f, 0.99991433f}, +{-1.8369095e-16f, 1.0000000f}, {0.013089596f, 0.99991433f}, +{0.026176948f, 0.99965732f}, {0.039259816f, 0.99922904f}, +{0.052335956f, 0.99862953f}, {0.065403129f, 0.99785892f}, +{0.078459096f, 0.99691733f}, {0.091501619f, 0.99580493f}, +{0.10452846f, 0.99452190f}, {0.11753740f, 0.99306846f}, +{0.13052619f, 0.99144486f}, {0.14349262f, 0.98965139f}, +{0.15643447f, 0.98768834f}, {0.16934950f, 0.98555606f}, +{0.18223553f, 0.98325491f}, {0.19509032f, 0.98078528f}, +{0.20791169f, 0.97814760f}, {0.22069744f, 0.97534232f}, +{0.23344536f, 0.97236992f}, {0.24615329f, 0.96923091f}, +{0.25881905f, 0.96592583f}, {0.27144045f, 0.96245524f}, +{0.28401534f, 0.95881973f}, {0.29654157f, 0.95501994f}, +{0.30901699f, 0.95105652f}, {0.32143947f, 0.94693013f}, +{0.33380686f, 0.94264149f}, {0.34611706f, 0.93819134f}, +{0.35836795f, 0.93358043f}, {0.37055744f, 0.92880955f}, +{0.38268343f, 0.92387953f}, {0.39474386f, 0.91879121f}, +{0.40673664f, 0.91354546f}, {0.41865974f, 0.90814317f}, +{0.43051110f, 0.90258528f}, {0.44228869f, 0.89687274f}, +{0.45399050f, 0.89100652f}, {0.46561452f, 0.88498764f}, +{0.47715876f, 0.87881711f}, {0.48862124f, 0.87249601f}, +{0.50000000f, 0.86602540f}, {0.51129309f, 0.85940641f}, +{0.52249856f, 0.85264016f}, {0.53361452f, 0.84572782f}, +{0.54463904f, 0.83867057f}, {0.55557023f, 0.83146961f}, +{0.56640624f, 0.82412619f}, {0.57714519f, 0.81664156f}, +{0.58778525f, 0.80901699f}, {0.59832460f, 0.80125381f}, +{0.60876143f, 0.79335334f}, {0.61909395f, 0.78531693f}, +{0.62932039f, 0.77714596f}, {0.63943900f, 0.76884183f}, +{0.64944805f, 0.76040597f}, {0.65934582f, 0.75183981f}, +{0.66913061f, 0.74314483f}, {0.67880075f, 0.73432251f}, +{0.68835458f, 0.72537437f}, {0.69779046f, 0.71630194f}, +{0.70710678f, 0.70710678f}, {0.71630194f, 0.69779046f}, +{0.72537437f, 0.68835458f}, {0.73432251f, 0.67880075f}, +{0.74314483f, 0.66913061f}, {0.75183981f, 0.65934582f}, +{0.76040597f, 0.64944805f}, {0.76884183f, 0.63943900f}, +{0.77714596f, 0.62932039f}, {0.78531693f, 0.61909395f}, +{0.79335334f, 0.60876143f}, {0.80125381f, 0.59832460f}, +{0.80901699f, 0.58778525f}, {0.81664156f, 0.57714519f}, +{0.82412619f, 0.56640624f}, {0.83146961f, 0.55557023f}, +{0.83867057f, 0.54463904f}, {0.84572782f, 0.53361452f}, +{0.85264016f, 0.52249856f}, {0.85940641f, 0.51129309f}, +{0.86602540f, 0.50000000f}, {0.87249601f, 0.48862124f}, +{0.87881711f, 0.47715876f}, {0.88498764f, 0.46561452f}, +{0.89100652f, 0.45399050f}, {0.89687274f, 0.44228869f}, +{0.90258528f, 0.43051110f}, {0.90814317f, 0.41865974f}, +{0.91354546f, 0.40673664f}, {0.91879121f, 0.39474386f}, +{0.92387953f, 0.38268343f}, {0.92880955f, 0.37055744f}, +{0.93358043f, 0.35836795f}, {0.93819134f, 0.34611706f}, +{0.94264149f, 0.33380686f}, {0.94693013f, 0.32143947f}, +{0.95105652f, 0.30901699f}, {0.95501994f, 0.29654157f}, +{0.95881973f, 0.28401534f}, {0.96245524f, 0.27144045f}, +{0.96592583f, 0.25881905f}, {0.96923091f, 0.24615329f}, +{0.97236992f, 0.23344536f}, {0.97534232f, 0.22069744f}, +{0.97814760f, 0.20791169f}, {0.98078528f, 0.19509032f}, +{0.98325491f, 0.18223553f}, {0.98555606f, 0.16934950f}, +{0.98768834f, 0.15643447f}, {0.98965139f, 0.14349262f}, +{0.99144486f, 0.13052619f}, {0.99306846f, 0.11753740f}, +{0.99452190f, 0.10452846f}, {0.99580493f, 0.091501619f}, +{0.99691733f, 0.078459096f}, {0.99785892f, 0.065403129f}, +{0.99862953f, 0.052335956f}, {0.99922904f, 0.039259816f}, +{0.99965732f, 0.026176948f}, {0.99991433f, 0.013089596f}, +}; +#ifndef FFT_BITREV480 +#define FFT_BITREV480 +static const opus_int16 fft_bitrev480[480] = { +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, +}; +#endif + +#ifndef FFT_BITREV240 +#define FFT_BITREV240 +static const opus_int16 fft_bitrev240[240] = { +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, +}; +#endif + +#ifndef FFT_BITREV120 +#define FFT_BITREV120 +static const opus_int16 fft_bitrev120[120] = { +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, +}; +#endif + +#ifndef FFT_BITREV60 +#define FFT_BITREV60 +static const opus_int16 fft_bitrev60[60] = { +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, +}; +#endif + +#ifndef FFT_STATE48000_960_0 +#define FFT_STATE48000_960_0 +static const kiss_fft_state fft_state48000_960_0 = { +480, /* nfft */ +0.002083333f, /* scale */ +-1, /* shift */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev480, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_1 +#define FFT_STATE48000_960_1 +static const kiss_fft_state fft_state48000_960_1 = { +240, /* nfft */ +0.004166667f, /* scale */ +1, /* shift */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev240, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_2 +#define FFT_STATE48000_960_2 +static const kiss_fft_state fft_state48000_960_2 = { +120, /* nfft */ +0.008333333f, /* scale */ +2, /* shift */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev120, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif +}; +#endif + +#ifndef FFT_STATE48000_960_3 +#define FFT_STATE48000_960_3 +static const kiss_fft_state fft_state48000_960_3 = { +60, /* nfft */ +0.016666667f, /* scale */ +3, /* shift */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +fft_bitrev60, /* bitrev */ +fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif +}; +#endif + +#endif + +#ifndef MDCT_TWIDDLES960 +#define MDCT_TWIDDLES960 +static const opus_val16 mdct_twiddles960[1800] = { +0.99999994f, 0.99999321f, 0.99997580f, 0.99994773f, 0.99990886f, +0.99985933f, 0.99979913f, 0.99972820f, 0.99964654f, 0.99955416f, +0.99945110f, 0.99933738f, 0.99921292f, 0.99907774f, 0.99893188f, +0.99877530f, 0.99860805f, 0.99843007f, 0.99824142f, 0.99804211f, +0.99783206f, 0.99761140f, 0.99737996f, 0.99713790f, 0.99688518f, +0.99662173f, 0.99634761f, 0.99606287f, 0.99576741f, 0.99546129f, +0.99514455f, 0.99481714f, 0.99447906f, 0.99413031f, 0.99377096f, +0.99340093f, 0.99302030f, 0.99262899f, 0.99222708f, 0.99181455f, +0.99139136f, 0.99095762f, 0.99051321f, 0.99005818f, 0.98959261f, +0.98911643f, 0.98862964f, 0.98813224f, 0.98762429f, 0.98710573f, +0.98657662f, 0.98603696f, 0.98548669f, 0.98492593f, 0.98435456f, +0.98377270f, 0.98318028f, 0.98257732f, 0.98196387f, 0.98133987f, +0.98070538f, 0.98006040f, 0.97940493f, 0.97873890f, 0.97806245f, +0.97737551f, 0.97667813f, 0.97597027f, 0.97525197f, 0.97452319f, +0.97378403f, 0.97303438f, 0.97227436f, 0.97150391f, 0.97072303f, +0.96993178f, 0.96913016f, 0.96831810f, 0.96749574f, 0.96666300f, +0.96581990f, 0.96496642f, 0.96410263f, 0.96322852f, 0.96234411f, +0.96144938f, 0.96054435f, 0.95962906f, 0.95870346f, 0.95776761f, +0.95682150f, 0.95586514f, 0.95489854f, 0.95392174f, 0.95293468f, +0.95193744f, 0.95093000f, 0.94991243f, 0.94888461f, 0.94784665f, +0.94679856f, 0.94574034f, 0.94467193f, 0.94359344f, 0.94250488f, +0.94140619f, 0.94029742f, 0.93917859f, 0.93804967f, 0.93691075f, +0.93576175f, 0.93460274f, 0.93343377f, 0.93225473f, 0.93106574f, +0.92986679f, 0.92865789f, 0.92743903f, 0.92621022f, 0.92497152f, +0.92372292f, 0.92246443f, 0.92119598f, 0.91991776f, 0.91862965f, +0.91733170f, 0.91602397f, 0.91470635f, 0.91337901f, 0.91204184f, +0.91069490f, 0.90933824f, 0.90797186f, 0.90659571f, 0.90520984f, +0.90381432f, 0.90240908f, 0.90099424f, 0.89956969f, 0.89813554f, +0.89669174f, 0.89523834f, 0.89377540f, 0.89230281f, 0.89082074f, +0.88932908f, 0.88782793f, 0.88631725f, 0.88479710f, 0.88326746f, +0.88172835f, 0.88017982f, 0.87862182f, 0.87705445f, 0.87547767f, +0.87389153f, 0.87229604f, 0.87069118f, 0.86907703f, 0.86745358f, +0.86582077f, 0.86417878f, 0.86252749f, 0.86086690f, 0.85919720f, +0.85751826f, 0.85583007f, 0.85413277f, 0.85242635f, 0.85071075f, +0.84898609f, 0.84725231f, 0.84550947f, 0.84375757f, 0.84199661f, +0.84022665f, 0.83844769f, 0.83665979f, 0.83486289f, 0.83305705f, +0.83124226f, 0.82941860f, 0.82758605f, 0.82574469f, 0.82389444f, +0.82203537f, 0.82016748f, 0.81829083f, 0.81640542f, 0.81451124f, +0.81260836f, 0.81069672f, 0.80877650f, 0.80684757f, 0.80490994f, +0.80296379f, 0.80100900f, 0.79904562f, 0.79707366f, 0.79509324f, +0.79310423f, 0.79110676f, 0.78910083f, 0.78708643f, 0.78506362f, +0.78303236f, 0.78099275f, 0.77894479f, 0.77688843f, 0.77482378f, +0.77275085f, 0.77066964f, 0.76858020f, 0.76648247f, 0.76437658f, +0.76226246f, 0.76014024f, 0.75800985f, 0.75587130f, 0.75372469f, +0.75157005f, 0.74940729f, 0.74723655f, 0.74505776f, 0.74287105f, +0.74067634f, 0.73847371f, 0.73626316f, 0.73404479f, 0.73181850f, +0.72958434f, 0.72734243f, 0.72509271f, 0.72283524f, 0.72057003f, +0.71829706f, 0.71601641f, 0.71372813f, 0.71143216f, 0.70912862f, +0.70681745f, 0.70449871f, 0.70217246f, 0.69983864f, 0.69749737f, +0.69514859f, 0.69279242f, 0.69042879f, 0.68805778f, 0.68567938f, +0.68329364f, 0.68090063f, 0.67850029f, 0.67609268f, 0.67367786f, +0.67125577f, 0.66882652f, 0.66639012f, 0.66394657f, 0.66149592f, +0.65903819f, 0.65657341f, 0.65410155f, 0.65162271f, 0.64913690f, +0.64664418f, 0.64414448f, 0.64163786f, 0.63912445f, 0.63660413f, +0.63407701f, 0.63154310f, 0.62900239f, 0.62645501f, 0.62390089f, +0.62134010f, 0.61877263f, 0.61619854f, 0.61361790f, 0.61103064f, +0.60843682f, 0.60583651f, 0.60322970f, 0.60061646f, 0.59799677f, +0.59537065f, 0.59273821f, 0.59009939f, 0.58745426f, 0.58480281f, +0.58214509f, 0.57948118f, 0.57681108f, 0.57413477f, 0.57145232f, +0.56876373f, 0.56606907f, 0.56336832f, 0.56066155f, 0.55794877f, +0.55523002f, 0.55250537f, 0.54977477f, 0.54703826f, 0.54429591f, +0.54154772f, 0.53879374f, 0.53603399f, 0.53326851f, 0.53049731f, +0.52772039f, 0.52493787f, 0.52214974f, 0.51935595f, 0.51655668f, +0.51375180f, 0.51094145f, 0.50812566f, 0.50530440f, 0.50247771f, +0.49964568f, 0.49680826f, 0.49396557f, 0.49111754f, 0.48826426f, +0.48540577f, 0.48254207f, 0.47967321f, 0.47679919f, 0.47392011f, +0.47103590f, 0.46814668f, 0.46525243f, 0.46235323f, 0.45944905f, +0.45653993f, 0.45362595f, 0.45070711f, 0.44778344f, 0.44485497f, +0.44192174f, 0.43898380f, 0.43604112f, 0.43309379f, 0.43014181f, +0.42718524f, 0.42422408f, 0.42125839f, 0.41828820f, 0.41531351f, +0.41233435f, 0.40935081f, 0.40636289f, 0.40337059f, 0.40037400f, +0.39737311f, 0.39436796f, 0.39135858f, 0.38834500f, 0.38532731f, +0.38230544f, 0.37927949f, 0.37624949f, 0.37321547f, 0.37017745f, +0.36713544f, 0.36408952f, 0.36103970f, 0.35798600f, 0.35492846f, +0.35186714f, 0.34880206f, 0.34573323f, 0.34266070f, 0.33958447f, +0.33650464f, 0.33342120f, 0.33033419f, 0.32724363f, 0.32414958f, +0.32105204f, 0.31795108f, 0.31484672f, 0.31173897f, 0.30862790f, +0.30551350f, 0.30239585f, 0.29927495f, 0.29615086f, 0.29302359f, +0.28989318f, 0.28675964f, 0.28362307f, 0.28048345f, 0.27734083f, +0.27419522f, 0.27104670f, 0.26789525f, 0.26474094f, 0.26158381f, +0.25842386f, 0.25526115f, 0.25209570f, 0.24892756f, 0.24575676f, +0.24258332f, 0.23940729f, 0.23622867f, 0.23304754f, 0.22986393f, +0.22667783f, 0.22348931f, 0.22029841f, 0.21710514f, 0.21390954f, +0.21071166f, 0.20751151f, 0.20430915f, 0.20110460f, 0.19789790f, +0.19468907f, 0.19147816f, 0.18826519f, 0.18505022f, 0.18183327f, +0.17861435f, 0.17539354f, 0.17217083f, 0.16894630f, 0.16571994f, +0.16249183f, 0.15926196f, 0.15603039f, 0.15279715f, 0.14956227f, +0.14632578f, 0.14308774f, 0.13984816f, 0.13660708f, 0.13336454f, +0.13012058f, 0.12687522f, 0.12362850f, 0.12038045f, 0.11713112f, +0.11388054f, 0.11062872f, 0.10737573f, 0.10412160f, 0.10086634f, +0.097609997f, 0.094352618f, 0.091094226f, 0.087834857f, 0.084574550f, +0.081313334f, 0.078051247f, 0.074788325f, 0.071524605f, 0.068260118f, +0.064994894f, 0.061728980f, 0.058462404f, 0.055195201f, 0.051927410f, +0.048659060f, 0.045390189f, 0.042120833f, 0.038851023f, 0.035580799f, +0.032310195f, 0.029039243f, 0.025767982f, 0.022496443f, 0.019224664f, +0.015952680f, 0.012680525f, 0.0094082337f, 0.0061358409f, 0.0028633832f, +-0.00040910527f, -0.0036815894f, -0.0069540343f, -0.010226404f, -0.013498665f, +-0.016770782f, -0.020042717f, -0.023314439f, -0.026585912f, -0.029857099f, +-0.033127967f, -0.036398482f, -0.039668605f, -0.042938303f, -0.046207540f, +-0.049476285f, -0.052744497f, -0.056012146f, -0.059279196f, -0.062545612f, +-0.065811358f, -0.069076397f, -0.072340697f, -0.075604223f, -0.078866936f, +-0.082128808f, -0.085389800f, -0.088649876f, -0.091909006f, -0.095167145f, +-0.098424271f, -0.10168034f, -0.10493532f, -0.10818918f, -0.11144188f, +-0.11469338f, -0.11794366f, -0.12119267f, -0.12444039f, -0.12768677f, +-0.13093179f, -0.13417540f, -0.13741758f, -0.14065829f, -0.14389749f, +-0.14713514f, -0.15037122f, -0.15360570f, -0.15683852f, -0.16006967f, +-0.16329910f, -0.16652679f, -0.16975269f, -0.17297678f, -0.17619900f, +-0.17941935f, -0.18263777f, -0.18585424f, -0.18906870f, -0.19228116f, +-0.19549155f, -0.19869985f, -0.20190603f, -0.20511003f, -0.20831184f, +-0.21151142f, -0.21470875f, -0.21790376f, -0.22109644f, -0.22428675f, +-0.22747467f, -0.23066014f, -0.23384315f, -0.23702365f, -0.24020162f, +-0.24337701f, -0.24654980f, -0.24971995f, -0.25288740f, -0.25605217f, +-0.25921419f, -0.26237345f, -0.26552987f, -0.26868346f, -0.27183419f, +-0.27498198f, -0.27812684f, -0.28126872f, -0.28440759f, -0.28754342f, +-0.29067615f, -0.29380578f, -0.29693225f, -0.30005556f, -0.30317566f, +-0.30629250f, -0.30940607f, -0.31251630f, -0.31562322f, -0.31872672f, +-0.32182685f, -0.32492352f, -0.32801670f, -0.33110636f, -0.33419248f, +-0.33727503f, -0.34035397f, -0.34342924f, -0.34650084f, -0.34956875f, +-0.35263291f, -0.35569328f, -0.35874987f, -0.36180258f, -0.36485144f, +-0.36789638f, -0.37093741f, -0.37397444f, -0.37700745f, -0.38003644f, +-0.38306138f, -0.38608220f, -0.38909888f, -0.39211139f, -0.39511973f, +-0.39812380f, -0.40112361f, -0.40411916f, -0.40711036f, -0.41009718f, +-0.41307965f, -0.41605768f, -0.41903123f, -0.42200032f, -0.42496487f, +-0.42792490f, -0.43088034f, -0.43383113f, -0.43677729f, -0.43971881f, +-0.44265559f, -0.44558764f, -0.44851488f, -0.45143735f, -0.45435500f, +-0.45726776f, -0.46017563f, -0.46307856f, -0.46597654f, -0.46886954f, +-0.47175750f, -0.47464043f, -0.47751826f, -0.48039100f, -0.48325855f, +-0.48612097f, -0.48897815f, -0.49183011f, -0.49467680f, -0.49751821f, +-0.50035429f, -0.50318497f, -0.50601029f, -0.50883019f, -0.51164466f, +-0.51445359f, -0.51725709f, -0.52005500f, -0.52284735f, -0.52563411f, +-0.52841520f, -0.53119069f, -0.53396046f, -0.53672451f, -0.53948283f, +-0.54223537f, -0.54498214f, -0.54772300f, -0.55045801f, -0.55318713f, +-0.55591035f, -0.55862761f, -0.56133890f, -0.56404412f, -0.56674337f, +-0.56943649f, -0.57212353f, -0.57480448f, -0.57747924f, -0.58014780f, +-0.58281022f, -0.58546633f, -0.58811617f, -0.59075975f, -0.59339696f, +-0.59602785f, -0.59865236f, -0.60127044f, -0.60388207f, -0.60648727f, +-0.60908598f, -0.61167812f, -0.61426371f, -0.61684275f, -0.61941516f, +-0.62198097f, -0.62454009f, -0.62709254f, -0.62963831f, -0.63217729f, +-0.63470948f, -0.63723493f, -0.63975352f, -0.64226526f, -0.64477009f, +-0.64726806f, -0.64975911f, -0.65224314f, -0.65472025f, -0.65719032f, +-0.65965337f, -0.66210932f, -0.66455823f, -0.66700000f, -0.66943461f, +-0.67186207f, -0.67428231f, -0.67669535f, -0.67910111f, -0.68149966f, +-0.68389088f, -0.68627477f, -0.68865126f, -0.69102043f, -0.69338220f, +-0.69573659f, -0.69808346f, -0.70042288f, -0.70275480f, -0.70507920f, +-0.70739603f, -0.70970529f, -0.71200693f, -0.71430099f, -0.71658736f, +-0.71886611f, -0.72113711f, -0.72340041f, -0.72565591f, -0.72790372f, +-0.73014367f, -0.73237586f, -0.73460019f, -0.73681659f, -0.73902518f, +-0.74122584f, -0.74341851f, -0.74560326f, -0.74778003f, -0.74994880f, +-0.75210953f, -0.75426215f, -0.75640678f, -0.75854325f, -0.76067162f, +-0.76279181f, -0.76490390f, -0.76700771f, -0.76910341f, -0.77119076f, +-0.77326995f, -0.77534080f, -0.77740335f, -0.77945763f, -0.78150350f, +-0.78354102f, -0.78557014f, -0.78759086f, -0.78960317f, -0.79160696f, +-0.79360235f, -0.79558921f, -0.79756755f, -0.79953730f, -0.80149853f, +-0.80345118f, -0.80539525f, -0.80733067f, -0.80925739f, -0.81117553f, +-0.81308490f, -0.81498563f, -0.81687760f, -0.81876087f, -0.82063532f, +-0.82250100f, -0.82435787f, -0.82620591f, -0.82804507f, -0.82987541f, +-0.83169687f, -0.83350939f, -0.83531296f, -0.83710766f, -0.83889335f, +-0.84067005f, -0.84243774f, -0.84419644f, -0.84594607f, -0.84768665f, +-0.84941816f, -0.85114056f, -0.85285389f, -0.85455805f, -0.85625303f, +-0.85793889f, -0.85961550f, -0.86128294f, -0.86294121f, -0.86459017f, +-0.86622989f, -0.86786032f, -0.86948150f, -0.87109333f, -0.87269586f, +-0.87428904f, -0.87587279f, -0.87744725f, -0.87901229f, -0.88056785f, +-0.88211405f, -0.88365078f, -0.88517809f, -0.88669586f, -0.88820416f, +-0.88970292f, -0.89119220f, -0.89267188f, -0.89414203f, -0.89560264f, +-0.89705360f, -0.89849502f, -0.89992678f, -0.90134889f, -0.90276134f, +-0.90416414f, -0.90555727f, -0.90694070f, -0.90831441f, -0.90967834f, +-0.91103262f, -0.91237706f, -0.91371179f, -0.91503674f, -0.91635185f, +-0.91765714f, -0.91895264f, -0.92023826f, -0.92151409f, -0.92277998f, +-0.92403603f, -0.92528218f, -0.92651838f, -0.92774469f, -0.92896110f, +-0.93016750f, -0.93136400f, -0.93255049f, -0.93372697f, -0.93489349f, +-0.93604994f, -0.93719643f, -0.93833286f, -0.93945926f, -0.94057560f, +-0.94168180f, -0.94277799f, -0.94386405f, -0.94494003f, -0.94600588f, +-0.94706154f, -0.94810712f, -0.94914252f, -0.95016778f, -0.95118284f, +-0.95218778f, -0.95318246f, -0.95416695f, -0.95514119f, -0.95610523f, +-0.95705903f, -0.95800257f, -0.95893586f, -0.95985889f, -0.96077162f, +-0.96167403f, -0.96256620f, -0.96344805f, -0.96431959f, -0.96518075f, +-0.96603161f, -0.96687216f, -0.96770233f, -0.96852213f, -0.96933156f, +-0.97013056f, -0.97091925f, -0.97169751f, -0.97246534f, -0.97322279f, +-0.97396982f, -0.97470641f, -0.97543252f, -0.97614825f, -0.97685349f, +-0.97754824f, -0.97823256f, -0.97890645f, -0.97956979f, -0.98022264f, +-0.98086500f, -0.98149687f, -0.98211825f, -0.98272908f, -0.98332942f, +-0.98391914f, -0.98449844f, -0.98506713f, -0.98562527f, -0.98617285f, +-0.98670989f, -0.98723638f, -0.98775226f, -0.98825759f, -0.98875231f, +-0.98923647f, -0.98971003f, -0.99017298f, -0.99062532f, -0.99106705f, +-0.99149817f, -0.99191868f, -0.99232858f, -0.99272782f, -0.99311644f, +-0.99349445f, -0.99386179f, -0.99421853f, -0.99456459f, -0.99489999f, +-0.99522477f, -0.99553883f, -0.99584228f, -0.99613506f, -0.99641716f, +-0.99668860f, -0.99694937f, -0.99719942f, -0.99743885f, -0.99766755f, +-0.99788558f, -0.99809295f, -0.99828959f, -0.99847561f, -0.99865085f, +-0.99881548f, -0.99896932f, -0.99911255f, -0.99924499f, -0.99936682f, +-0.99947786f, -0.99957830f, -0.99966794f, -0.99974692f, -0.99981517f, +-0.99987274f, -0.99991959f, -0.99995571f, -0.99998116f, -0.99999589f, +0.99999964f, 0.99997288f, 0.99990326f, 0.99979085f, 0.99963558f, +0.99943751f, 0.99919659f, 0.99891287f, 0.99858636f, 0.99821711f, +0.99780506f, 0.99735034f, 0.99685282f, 0.99631262f, 0.99572974f, +0.99510419f, 0.99443603f, 0.99372530f, 0.99297196f, 0.99217612f, +0.99133772f, 0.99045694f, 0.98953366f, 0.98856801f, 0.98756003f, +0.98650974f, 0.98541719f, 0.98428243f, 0.98310548f, 0.98188645f, +0.98062533f, 0.97932225f, 0.97797716f, 0.97659022f, 0.97516143f, +0.97369087f, 0.97217858f, 0.97062469f, 0.96902919f, 0.96739221f, +0.96571374f, 0.96399397f, 0.96223283f, 0.96043050f, 0.95858705f, +0.95670253f, 0.95477700f, 0.95281059f, 0.95080340f, 0.94875544f, +0.94666684f, 0.94453770f, 0.94236809f, 0.94015813f, 0.93790787f, +0.93561745f, 0.93328691f, 0.93091643f, 0.92850608f, 0.92605597f, +0.92356616f, 0.92103678f, 0.91846794f, 0.91585976f, 0.91321236f, +0.91052586f, 0.90780038f, 0.90503591f, 0.90223277f, 0.89939094f, +0.89651060f, 0.89359182f, 0.89063478f, 0.88763964f, 0.88460642f, +0.88153529f, 0.87842643f, 0.87527996f, 0.87209594f, 0.86887461f, +0.86561602f, 0.86232042f, 0.85898781f, 0.85561842f, 0.85221243f, +0.84876984f, 0.84529096f, 0.84177583f, 0.83822471f, 0.83463764f, +0.83101481f, 0.82735640f, 0.82366252f, 0.81993335f, 0.81616908f, +0.81236988f, 0.80853581f, 0.80466717f, 0.80076402f, 0.79682660f, +0.79285502f, 0.78884947f, 0.78481019f, 0.78073722f, 0.77663082f, +0.77249116f, 0.76831841f, 0.76411277f, 0.75987434f, 0.75560343f, +0.75130010f, 0.74696463f, 0.74259710f, 0.73819780f, 0.73376691f, +0.72930455f, 0.72481096f, 0.72028631f, 0.71573079f, 0.71114463f, +0.70652801f, 0.70188117f, 0.69720417f, 0.69249737f, 0.68776089f, +0.68299496f, 0.67819971f, 0.67337549f, 0.66852236f, 0.66364062f, +0.65873051f, 0.65379208f, 0.64882571f, 0.64383155f, 0.63880974f, +0.63376063f, 0.62868434f, 0.62358117f, 0.61845124f, 0.61329484f, +0.60811216f, 0.60290343f, 0.59766883f, 0.59240872f, 0.58712316f, +0.58181250f, 0.57647687f, 0.57111657f, 0.56573176f, 0.56032276f, +0.55488980f, 0.54943299f, 0.54395270f, 0.53844911f, 0.53292239f, +0.52737290f, 0.52180082f, 0.51620632f, 0.51058978f, 0.50495136f, +0.49929130f, 0.49360985f, 0.48790723f, 0.48218375f, 0.47643960f, +0.47067502f, 0.46489030f, 0.45908567f, 0.45326138f, 0.44741765f, +0.44155475f, 0.43567297f, 0.42977250f, 0.42385364f, 0.41791660f, +0.41196167f, 0.40598908f, 0.39999911f, 0.39399201f, 0.38796803f, +0.38192743f, 0.37587047f, 0.36979741f, 0.36370850f, 0.35760403f, +0.35148421f, 0.34534934f, 0.33919969f, 0.33303553f, 0.32685706f, +0.32066461f, 0.31445843f, 0.30823877f, 0.30200592f, 0.29576012f, +0.28950164f, 0.28323078f, 0.27694780f, 0.27065292f, 0.26434645f, +0.25802869f, 0.25169984f, 0.24536023f, 0.23901010f, 0.23264973f, +0.22627939f, 0.21989937f, 0.21350993f, 0.20711134f, 0.20070387f, +0.19428782f, 0.18786344f, 0.18143101f, 0.17499080f, 0.16854310f, +0.16208819f, 0.15562633f, 0.14915779f, 0.14268288f, 0.13620184f, +0.12971498f, 0.12322257f, 0.11672486f, 0.11022217f, 0.10371475f, +0.097202882f, 0.090686858f, 0.084166944f, 0.077643424f, 0.071116582f, +0.064586692f, 0.058054037f, 0.051518895f, 0.044981543f, 0.038442269f, +0.031901345f, 0.025359053f, 0.018815678f, 0.012271495f, 0.0057267868f, +-0.00081816671f, -0.0073630852f, -0.013907688f, -0.020451695f, -0.026994826f, +-0.033536803f, -0.040077340f, -0.046616159f, -0.053152986f, -0.059687532f, +-0.066219524f, -0.072748676f, -0.079274714f, -0.085797355f, -0.092316322f, +-0.098831341f, -0.10534211f, -0.11184838f, -0.11834986f, -0.12484626f, +-0.13133731f, -0.13782275f, -0.14430228f, -0.15077563f, -0.15724251f, +-0.16370267f, -0.17015581f, -0.17660165f, -0.18303993f, -0.18947038f, +-0.19589271f, -0.20230664f, -0.20871192f, -0.21510825f, -0.22149536f, +-0.22787298f, -0.23424086f, -0.24059868f, -0.24694622f, -0.25328314f, +-0.25960925f, -0.26592422f, -0.27222782f, -0.27851975f, -0.28479972f, +-0.29106751f, -0.29732284f, -0.30356544f, -0.30979502f, -0.31601134f, +-0.32221413f, -0.32840309f, -0.33457801f, -0.34073856f, -0.34688455f, +-0.35301566f, -0.35913166f, -0.36523229f, -0.37131724f, -0.37738630f, +-0.38343921f, -0.38947567f, -0.39549544f, -0.40149832f, -0.40748394f, +-0.41345215f, -0.41940263f, -0.42533514f, -0.43124944f, -0.43714526f, +-0.44302234f, -0.44888046f, -0.45471936f, -0.46053877f, -0.46633846f, +-0.47211814f, -0.47787762f, -0.48361665f, -0.48933494f, -0.49503228f, +-0.50070840f, -0.50636309f, -0.51199609f, -0.51760709f, -0.52319598f, +-0.52876246f, -0.53430629f, -0.53982723f, -0.54532504f, -0.55079949f, +-0.55625033f, -0.56167740f, -0.56708032f, -0.57245898f, -0.57781315f, +-0.58314258f, -0.58844697f, -0.59372622f, -0.59897995f, -0.60420811f, +-0.60941035f, -0.61458647f, -0.61973625f, -0.62485951f, -0.62995601f, +-0.63502556f, -0.64006782f, -0.64508271f, -0.65007001f, -0.65502942f, +-0.65996075f, -0.66486382f, -0.66973841f, -0.67458433f, -0.67940134f, +-0.68418926f, -0.68894786f, -0.69367695f, -0.69837630f, -0.70304573f, +-0.70768511f, -0.71229410f, -0.71687263f, -0.72142041f, -0.72593731f, +-0.73042315f, -0.73487765f, -0.73930067f, -0.74369204f, -0.74805158f, +-0.75237900f, -0.75667429f, -0.76093709f, -0.76516730f, -0.76936477f, +-0.77352923f, -0.77766061f, -0.78175867f, -0.78582323f, -0.78985411f, +-0.79385114f, -0.79781419f, -0.80174309f, -0.80563760f, -0.80949765f, +-0.81332302f, -0.81711352f, -0.82086903f, -0.82458937f, -0.82827437f, +-0.83192390f, -0.83553779f, -0.83911592f, -0.84265804f, -0.84616417f, +-0.84963393f, -0.85306740f, -0.85646427f, -0.85982448f, -0.86314780f, +-0.86643422f, -0.86968350f, -0.87289548f, -0.87607014f, -0.87920725f, +-0.88230664f, -0.88536829f, -0.88839203f, -0.89137769f, -0.89432514f, +-0.89723432f, -0.90010506f, -0.90293723f, -0.90573072f, -0.90848541f, +-0.91120118f, -0.91387796f, -0.91651553f, -0.91911387f, -0.92167282f, +-0.92419231f, -0.92667222f, -0.92911243f, -0.93151283f, -0.93387336f, +-0.93619382f, -0.93847424f, -0.94071442f, -0.94291431f, -0.94507378f, +-0.94719279f, -0.94927126f, -0.95130903f, -0.95330608f, -0.95526224f, +-0.95717752f, -0.95905179f, -0.96088499f, -0.96267700f, -0.96442777f, +-0.96613729f, -0.96780539f, -0.96943200f, -0.97101706f, -0.97256058f, +-0.97406244f, -0.97552258f, -0.97694093f, -0.97831738f, -0.97965199f, +-0.98094457f, -0.98219514f, -0.98340368f, -0.98457009f, -0.98569429f, +-0.98677629f, -0.98781598f, -0.98881340f, -0.98976845f, -0.99068111f, +-0.99155134f, -0.99237907f, -0.99316430f, -0.99390697f, -0.99460709f, +-0.99526459f, -0.99587947f, -0.99645168f, -0.99698120f, -0.99746799f, +-0.99791211f, -0.99831343f, -0.99867201f, -0.99898779f, -0.99926084f, +-0.99949104f, -0.99967843f, -0.99982297f, -0.99992472f, -0.99998361f, +0.99999869f, 0.99989158f, 0.99961317f, 0.99916345f, 0.99854255f, +0.99775058f, 0.99678761f, 0.99565387f, 0.99434954f, 0.99287480f, +0.99122995f, 0.98941529f, 0.98743105f, 0.98527765f, 0.98295540f, +0.98046476f, 0.97780609f, 0.97497988f, 0.97198665f, 0.96882683f, +0.96550101f, 0.96200979f, 0.95835376f, 0.95453346f, 0.95054960f, +0.94640291f, 0.94209403f, 0.93762374f, 0.93299282f, 0.92820197f, +0.92325211f, 0.91814411f, 0.91287869f, 0.90745693f, 0.90187967f, +0.89614785f, 0.89026248f, 0.88422459f, 0.87803519f, 0.87169534f, +0.86520612f, 0.85856867f, 0.85178405f, 0.84485358f, 0.83777827f, +0.83055943f, 0.82319832f, 0.81569612f, 0.80805415f, 0.80027372f, +0.79235619f, 0.78430289f, 0.77611518f, 0.76779449f, 0.75934225f, +0.75075996f, 0.74204898f, 0.73321080f, 0.72424710f, 0.71515924f, +0.70594883f, 0.69661748f, 0.68716675f, 0.67759830f, 0.66791373f, +0.65811473f, 0.64820296f, 0.63818014f, 0.62804794f, 0.61780810f, +0.60746247f, 0.59701276f, 0.58646071f, 0.57580817f, 0.56505698f, +0.55420899f, 0.54326600f, 0.53222996f, 0.52110273f, 0.50988621f, +0.49858227f, 0.48719296f, 0.47572014f, 0.46416581f, 0.45253196f, +0.44082057f, 0.42903364f, 0.41717321f, 0.40524128f, 0.39323992f, +0.38117120f, 0.36903715f, 0.35683987f, 0.34458145f, 0.33226398f, +0.31988961f, 0.30746040f, 0.29497850f, 0.28244606f, 0.26986524f, +0.25723818f, 0.24456702f, 0.23185398f, 0.21910121f, 0.20631088f, +0.19348522f, 0.18062639f, 0.16773662f, 0.15481812f, 0.14187308f, +0.12890373f, 0.11591230f, 0.10290100f, 0.089872077f, 0.076827750f, +0.063770257f, 0.050701842f, 0.037624735f, 0.024541186f, 0.011453429f, +-0.0016362892f, -0.014725727f, -0.027812643f, -0.040894791f, -0.053969935f, +-0.067035832f, -0.080090240f, -0.093130924f, -0.10615565f, -0.11916219f, +-0.13214831f, -0.14511178f, -0.15805040f, -0.17096193f, -0.18384418f, +-0.19669491f, -0.20951195f, -0.22229309f, -0.23503613f, -0.24773891f, +-0.26039925f, -0.27301496f, -0.28558388f, -0.29810387f, -0.31057280f, +-0.32298848f, -0.33534884f, -0.34765175f, -0.35989508f, -0.37207675f, +-0.38419467f, -0.39624676f, -0.40823093f, -0.42014518f, -0.43198743f, +-0.44375566f, -0.45544785f, -0.46706200f, -0.47859612f, -0.49004826f, +-0.50141639f, -0.51269865f, -0.52389306f, -0.53499764f, -0.54601061f, +-0.55693001f, -0.56775403f, -0.57848072f, -0.58910829f, -0.59963489f, +-0.61005878f, -0.62037814f, -0.63059121f, -0.64069623f, -0.65069145f, +-0.66057515f, -0.67034572f, -0.68000144f, -0.68954057f, -0.69896162f, +-0.70826286f, -0.71744281f, -0.72649974f, -0.73543227f, -0.74423873f, +-0.75291771f, -0.76146764f, -0.76988715f, -0.77817470f, -0.78632891f, +-0.79434842f, -0.80223179f, -0.80997771f, -0.81758487f, -0.82505190f, +-0.83237761f, -0.83956063f, -0.84659988f, -0.85349399f, -0.86024189f, +-0.86684239f, -0.87329435f, -0.87959671f, -0.88574833f, -0.89174819f, +-0.89759529f, -0.90328854f, -0.90882701f, -0.91420978f, -0.91943592f, +-0.92450452f, -0.92941469f, -0.93416560f, -0.93875647f, -0.94318646f, +-0.94745487f, -0.95156091f, -0.95550388f, -0.95928317f, -0.96289814f, +-0.96634805f, -0.96963239f, -0.97275060f, -0.97570217f, -0.97848648f, +-0.98110318f, -0.98355180f, -0.98583186f, -0.98794299f, -0.98988485f, +-0.99165714f, -0.99325943f, -0.99469161f, -0.99595332f, -0.99704438f, +-0.99796462f, -0.99871385f, -0.99929196f, -0.99969882f, -0.99993443f, +0.99999464f, 0.99956632f, 0.99845290f, 0.99665523f, 0.99417448f, +0.99101239f, 0.98717111f, 0.98265326f, 0.97746199f, 0.97160077f, +0.96507365f, 0.95788515f, 0.95004016f, 0.94154406f, 0.93240267f, +0.92262226f, 0.91220951f, 0.90117162f, 0.88951606f, 0.87725091f, +0.86438453f, 0.85092574f, 0.83688372f, 0.82226819f, 0.80708915f, +0.79135692f, 0.77508235f, 0.75827658f, 0.74095112f, 0.72311783f, +0.70478898f, 0.68597710f, 0.66669506f, 0.64695615f, 0.62677377f, +0.60616189f, 0.58513457f, 0.56370622f, 0.54189157f, 0.51970547f, +0.49716324f, 0.47428027f, 0.45107225f, 0.42755505f, 0.40374488f, +0.37965798f, 0.35531086f, 0.33072025f, 0.30590299f, 0.28087607f, +0.25565663f, 0.23026201f, 0.20470956f, 0.17901683f, 0.15320139f, +0.12728097f, 0.10127331f, 0.075196236f, 0.049067631f, 0.022905400f, +-0.0032725304f, -0.029448219f, -0.055603724f, -0.081721120f, -0.10778251f, +-0.13377003f, -0.15966587f, -0.18545228f, -0.21111161f, -0.23662624f, +-0.26197869f, -0.28715160f, -0.31212771f, -0.33688989f, -0.36142120f, +-0.38570482f, -0.40972409f, -0.43346253f, -0.45690393f, -0.48003218f, +-0.50283146f, -0.52528608f, -0.54738069f, -0.56910020f, -0.59042966f, +-0.61135447f, -0.63186026f, -0.65193301f, -0.67155898f, -0.69072473f, +-0.70941705f, -0.72762316f, -0.74533063f, -0.76252723f, -0.77920127f, +-0.79534131f, -0.81093621f, -0.82597536f, -0.84044844f, -0.85434550f, +-0.86765707f, -0.88037395f, -0.89248747f, -0.90398932f, -0.91487163f, +-0.92512697f, -0.93474823f, -0.94372886f, -0.95206273f, -0.95974404f, +-0.96676767f, -0.97312868f, -0.97882277f, -0.98384601f, -0.98819500f, +-0.99186671f, -0.99485862f, -0.99716878f, -0.99879545f, -0.99973762f, +}; +#endif + +static const CELTMode mode48000_960_120 = { +48000, /* Fs */ +120, /* overlap */ +21, /* nbEBands */ +21, /* effEBands */ +{0.85000610f, 0.0000000f, 1.0000000f, 1.0000000f, }, /* preemph */ +eband5ms, /* eBands */ +3, /* maxLM */ +8, /* nbShortMdcts */ +120, /* shortMdctSize */ +11, /* nbAllocVectors */ +band_allocation, /* allocVectors */ +logN400, /* logN */ +window120, /* window */ +{1920, 3, {&fft_state48000_960_0, &fft_state48000_960_1, &fft_state48000_960_2, &fft_state48000_960_3, }, mdct_twiddles960}, /* mdct */ +{392, cache_index50, cache_bits50, cache_caps50}, /* cache */ +}; + +/* List of all the available modes */ +#define TOTAL_MODES 1 +static const CELTMode * const static_mode_list[TOTAL_MODES] = { +&mode48000_960_120, +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float_arm_ne10.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float_arm_ne10.h new file mode 100644 index 0000000000..66e1abb101 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/static_modes_float_arm_ne10.h @@ -0,0 +1,404 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_float32_t ne10_twiddles_480[480] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +{1.0000000f,-0.0000000f}, {0.99991435f,-0.013089596f}, {0.99965733f,-0.026176950f}, +{0.99922901f,-0.039259817f}, {0.99862951f,-0.052335959f}, {0.99785894f,-0.065403134f}, +{0.99691731f,-0.078459099f}, {0.99580491f,-0.091501623f}, {0.99452192f,-0.10452846f}, +{0.99306846f,-0.11753740f}, {0.99144489f,-0.13052620f}, {0.98965138f,-0.14349262f}, +{0.98768836f,-0.15643448f}, {0.98555607f,-0.16934951f}, {0.98325491f,-0.18223552f}, +{0.98078525f,-0.19509032f}, {0.97814763f,-0.20791170f}, {0.97534233f,-0.22069745f}, +{0.97236991f,-0.23344538f}, {0.96923089f,-0.24615330f}, {0.96592581f,-0.25881904f}, +{0.96245521f,-0.27144045f}, {0.95881975f,-0.28401536f}, {0.95501995f,-0.29654160f}, +{0.95105648f,-0.30901700f}, {0.94693011f,-0.32143945f}, {0.94264150f,-0.33380687f}, +{0.93819129f,-0.34611708f}, {0.93358040f,-0.35836795f}, {0.92880952f,-0.37055743f}, +{0.92387956f,-0.38268346f}, {0.91879117f,-0.39474389f}, {0.91354543f,-0.40673664f}, +{0.90814316f,-0.41865975f}, {0.90258527f,-0.43051112f}, {0.89687270f,-0.44228873f}, +{0.89100653f,-0.45399052f}, {0.88498765f,-0.46561453f}, {0.87881708f,-0.47715878f}, +{0.87249601f,-0.48862126f}, {0.86602545f,-0.50000000f}, {0.85940641f,-0.51129311f}, +{0.85264015f,-0.52249855f}, {0.84572786f,-0.53361452f}, {0.83867055f,-0.54463905f}, +{0.83146960f,-0.55557024f}, {0.82412618f,-0.56640625f}, {0.81664151f,-0.57714522f}, +{0.80901700f,-0.58778524f}, {0.80125380f,-0.59832460f}, {0.79335332f,-0.60876143f}, +{0.78531694f,-0.61909395f}, {0.77714598f,-0.62932038f}, {0.76884180f,-0.63943899f}, +{0.76040596f,-0.64944810f}, {0.75183982f,-0.65934587f}, {0.74314475f,-0.66913062f}, +{0.73432249f,-0.67880076f}, {0.72537434f,-0.68835455f}, {0.71630192f,-0.69779050f}, +{0.70710677f,-0.70710683f}, {0.69779044f,-0.71630198f}, {0.68835455f,-0.72537440f}, +{0.67880070f,-0.73432255f}, {0.66913056f,-0.74314487f}, {0.65934581f,-0.75183982f}, +{0.64944804f,-0.76040596f}, {0.63943899f,-0.76884186f}, {0.62932038f,-0.77714598f}, +{0.61909395f,-0.78531694f}, {0.60876137f,-0.79335338f}, {0.59832460f,-0.80125386f}, +{0.58778524f,-0.80901700f}, {0.57714516f,-0.81664151f}, {0.56640625f,-0.82412618f}, +{0.55557019f,-0.83146960f}, {0.54463899f,-0.83867055f}, {0.53361452f,-0.84572786f}, +{0.52249849f,-0.85264015f}, {0.51129311f,-0.85940641f}, {0.49999997f,-0.86602545f}, +{0.48862118f,-0.87249601f}, {0.47715876f,-0.87881708f}, {0.46561447f,-0.88498765f}, +{0.45399052f,-0.89100653f}, {0.44228867f,-0.89687276f}, {0.43051103f,-0.90258533f}, +{0.41865975f,-0.90814316f}, {0.40673661f,-0.91354549f}, {0.39474380f,-0.91879129f}, +{0.38268343f,-0.92387956f}, {0.37055740f,-0.92880958f}, {0.35836786f,-0.93358046f}, +{0.34611705f,-0.93819135f}, {0.33380681f,-0.94264150f}, {0.32143947f,-0.94693011f}, +{0.30901697f,-0.95105654f}, {0.29654151f,-0.95501995f}, {0.28401533f,-0.95881975f}, +{0.27144039f,-0.96245527f}, {0.25881907f,-0.96592581f}, {0.24615327f,-0.96923089f}, +{0.23344530f,-0.97236991f}, {0.22069745f,-0.97534233f}, {0.20791166f,-0.97814763f}, +{0.19509023f,-0.98078531f}, {0.18223552f,-0.98325491f}, {0.16934945f,-0.98555607f}, +{0.15643437f,-0.98768836f}, {0.14349259f,-0.98965138f}, {0.13052613f,-0.99144489f}, +{0.11753740f,-0.99306846f}, {0.10452842f,-0.99452192f}, {0.091501534f,-0.99580491f}, +{0.078459084f,-0.99691731f}, {0.065403074f,-0.99785894f}, {0.052335974f,-0.99862951f}, +{0.039259788f,-0.99922901f}, {0.026176875f,-0.99965733f}, {0.013089597f,-0.99991435f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{-4.3711388e-08f,-1.0000000f}, {-0.026176963f,-0.99965733f}, {-0.052336060f,-0.99862951f}, +{-0.078459173f,-0.99691731f}, {-0.10452851f,-0.99452192f}, {-0.13052621f,-0.99144489f}, +{-0.15643445f,-0.98768836f}, {-0.18223560f,-0.98325491f}, {-0.20791174f,-0.97814757f}, +{-0.23344538f,-0.97236991f}, {-0.25881916f,-0.96592581f}, {-0.28401542f,-0.95881969f}, +{-0.30901703f,-0.95105648f}, {-0.33380687f,-0.94264150f}, {-0.35836795f,-0.93358040f}, +{-0.38268352f,-0.92387950f}, {-0.40673670f,-0.91354543f}, {-0.43051112f,-0.90258527f}, +{-0.45399061f,-0.89100647f}, {-0.47715873f,-0.87881708f}, {-0.50000006f,-0.86602533f}, +{-0.52249867f,-0.85264009f}, {-0.54463905f,-0.83867055f}, {-0.56640631f,-0.82412612f}, +{-0.58778518f,-0.80901700f}, {-0.60876143f,-0.79335332f}, {-0.62932050f,-0.77714586f}, +{-0.64944804f,-0.76040596f}, {-0.66913068f,-0.74314475f}, {-0.68835467f,-0.72537428f}, +{-0.70710677f,-0.70710677f}, {-0.72537446f,-0.68835449f}, {-0.74314493f,-0.66913044f}, +{-0.76040596f,-0.64944804f}, {-0.77714604f,-0.62932026f}, {-0.79335332f,-0.60876143f}, +{-0.80901700f,-0.58778518f}, {-0.82412624f,-0.56640613f}, {-0.83867055f,-0.54463899f}, +{-0.85264021f,-0.52249849f}, {-0.86602539f,-0.50000006f}, {-0.87881714f,-0.47715873f}, +{-0.89100659f,-0.45399037f}, {-0.90258527f,-0.43051112f}, {-0.91354549f,-0.40673658f}, +{-0.92387956f,-0.38268328f}, {-0.93358040f,-0.35836792f}, {-0.94264150f,-0.33380675f}, +{-0.95105654f,-0.30901679f}, {-0.95881975f,-0.28401530f}, {-0.96592587f,-0.25881892f}, +{-0.97236991f,-0.23344538f}, {-0.97814763f,-0.20791161f}, {-0.98325491f,-0.18223536f}, +{-0.98768836f,-0.15643445f}, {-0.99144489f,-0.13052608f}, {-0.99452192f,-0.10452849f}, +{-0.99691737f,-0.078459039f}, {-0.99862957f,-0.052335810f}, {-0.99965733f,-0.026176952f}, +{1.0000000f,-0.0000000f}, {0.99922901f,-0.039259817f}, {0.99691731f,-0.078459099f}, +{0.99306846f,-0.11753740f}, {0.98768836f,-0.15643448f}, {0.98078525f,-0.19509032f}, +{0.97236991f,-0.23344538f}, {0.96245521f,-0.27144045f}, {0.95105648f,-0.30901700f}, +{0.93819129f,-0.34611708f}, {0.92387956f,-0.38268346f}, {0.90814316f,-0.41865975f}, +{0.89100653f,-0.45399052f}, {0.87249601f,-0.48862126f}, {0.85264015f,-0.52249855f}, +{0.83146960f,-0.55557024f}, {0.80901700f,-0.58778524f}, {0.78531694f,-0.61909395f}, +{0.76040596f,-0.64944810f}, {0.73432249f,-0.67880076f}, {0.70710677f,-0.70710683f}, +{0.67880070f,-0.73432255f}, {0.64944804f,-0.76040596f}, {0.61909395f,-0.78531694f}, +{0.58778524f,-0.80901700f}, {0.55557019f,-0.83146960f}, {0.52249849f,-0.85264015f}, +{0.48862118f,-0.87249601f}, {0.45399052f,-0.89100653f}, {0.41865975f,-0.90814316f}, +{0.38268343f,-0.92387956f}, {0.34611705f,-0.93819135f}, {0.30901697f,-0.95105654f}, +{0.27144039f,-0.96245527f}, {0.23344530f,-0.97236991f}, {0.19509023f,-0.98078531f}, +{0.15643437f,-0.98768836f}, {0.11753740f,-0.99306846f}, {0.078459084f,-0.99691731f}, +{0.039259788f,-0.99922901f}, {-4.3711388e-08f,-1.0000000f}, {-0.039259877f,-0.99922901f}, +{-0.078459173f,-0.99691731f}, {-0.11753749f,-0.99306846f}, {-0.15643445f,-0.98768836f}, +{-0.19509032f,-0.98078525f}, {-0.23344538f,-0.97236991f}, {-0.27144048f,-0.96245521f}, +{-0.30901703f,-0.95105648f}, {-0.34611711f,-0.93819129f}, {-0.38268352f,-0.92387950f}, +{-0.41865984f,-0.90814310f}, {-0.45399061f,-0.89100647f}, {-0.48862135f,-0.87249595f}, +{-0.52249867f,-0.85264009f}, {-0.55557036f,-0.83146954f}, {-0.58778518f,-0.80901700f}, +{-0.61909389f,-0.78531694f}, {-0.64944804f,-0.76040596f}, {-0.67880076f,-0.73432249f}, +{-0.70710677f,-0.70710677f}, {-0.73432249f,-0.67880070f}, {-0.76040596f,-0.64944804f}, +{-0.78531694f,-0.61909389f}, {-0.80901700f,-0.58778518f}, {-0.83146966f,-0.55557019f}, +{-0.85264021f,-0.52249849f}, {-0.87249607f,-0.48862115f}, {-0.89100659f,-0.45399037f}, +{-0.90814322f,-0.41865960f}, {-0.92387956f,-0.38268328f}, {-0.93819135f,-0.34611690f}, +{-0.95105654f,-0.30901679f}, {-0.96245521f,-0.27144048f}, {-0.97236991f,-0.23344538f}, +{-0.98078531f,-0.19509031f}, {-0.98768836f,-0.15643445f}, {-0.99306846f,-0.11753736f}, +{-0.99691737f,-0.078459039f}, {-0.99922901f,-0.039259743f}, {-1.0000000f,8.7422777e-08f}, +{-0.99922901f,0.039259918f}, {-0.99691731f,0.078459218f}, {-0.99306846f,0.11753753f}, +{-0.98768830f,0.15643461f}, {-0.98078525f,0.19509049f}, {-0.97236985f,0.23344554f}, +{-0.96245515f,0.27144065f}, {-0.95105654f,0.30901697f}, {-0.93819135f,0.34611705f}, +{-0.92387956f,0.38268346f}, {-0.90814316f,0.41865975f}, {-0.89100653f,0.45399055f}, +{-0.87249601f,0.48862129f}, {-0.85264015f,0.52249861f}, {-0.83146960f,0.55557030f}, +{-0.80901694f,0.58778536f}, {-0.78531688f,0.61909401f}, {-0.76040590f,0.64944816f}, +{-0.73432243f,0.67880082f}, {-0.70710665f,0.70710689f}, {-0.67880058f,0.73432261f}, +{-0.64944792f,0.76040608f}, {-0.61909378f,0.78531706f}, {-0.58778507f,0.80901712f}, +{-0.55557001f,0.83146977f}, {-0.52249837f,0.85264033f}, {-0.48862100f,0.87249613f}, +{-0.45399022f,0.89100665f}, {-0.41865945f,0.90814328f}, {-0.38268313f,0.92387968f}, +{-0.34611672f,0.93819147f}, {-0.30901709f,0.95105648f}, {-0.27144054f,0.96245521f}, +{-0.23344545f,0.97236991f}, {-0.19509038f,0.98078525f}, {-0.15643452f,0.98768830f}, +{-0.11753743f,0.99306846f}, {-0.078459114f,0.99691731f}, {-0.039259821f,0.99922901f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_240[240] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{-4.3711388e-08f,-1.0000000f}, {-0.052336060f,-0.99862951f}, {-0.10452851f,-0.99452192f}, +{-0.15643445f,-0.98768836f}, {-0.20791174f,-0.97814757f}, {-0.25881916f,-0.96592581f}, +{-0.30901703f,-0.95105648f}, {-0.35836795f,-0.93358040f}, {-0.40673670f,-0.91354543f}, +{-0.45399061f,-0.89100647f}, {-0.50000006f,-0.86602533f}, {-0.54463905f,-0.83867055f}, +{-0.58778518f,-0.80901700f}, {-0.62932050f,-0.77714586f}, {-0.66913068f,-0.74314475f}, +{-0.70710677f,-0.70710677f}, {-0.74314493f,-0.66913044f}, {-0.77714604f,-0.62932026f}, +{-0.80901700f,-0.58778518f}, {-0.83867055f,-0.54463899f}, {-0.86602539f,-0.50000006f}, +{-0.89100659f,-0.45399037f}, {-0.91354549f,-0.40673658f}, {-0.93358040f,-0.35836792f}, +{-0.95105654f,-0.30901679f}, {-0.96592587f,-0.25881892f}, {-0.97814763f,-0.20791161f}, +{-0.98768836f,-0.15643445f}, {-0.99452192f,-0.10452849f}, {-0.99862957f,-0.052335810f}, +{1.0000000f,-0.0000000f}, {0.99691731f,-0.078459099f}, {0.98768836f,-0.15643448f}, +{0.97236991f,-0.23344538f}, {0.95105648f,-0.30901700f}, {0.92387956f,-0.38268346f}, +{0.89100653f,-0.45399052f}, {0.85264015f,-0.52249855f}, {0.80901700f,-0.58778524f}, +{0.76040596f,-0.64944810f}, {0.70710677f,-0.70710683f}, {0.64944804f,-0.76040596f}, +{0.58778524f,-0.80901700f}, {0.52249849f,-0.85264015f}, {0.45399052f,-0.89100653f}, +{0.38268343f,-0.92387956f}, {0.30901697f,-0.95105654f}, {0.23344530f,-0.97236991f}, +{0.15643437f,-0.98768836f}, {0.078459084f,-0.99691731f}, {-4.3711388e-08f,-1.0000000f}, +{-0.078459173f,-0.99691731f}, {-0.15643445f,-0.98768836f}, {-0.23344538f,-0.97236991f}, +{-0.30901703f,-0.95105648f}, {-0.38268352f,-0.92387950f}, {-0.45399061f,-0.89100647f}, +{-0.52249867f,-0.85264009f}, {-0.58778518f,-0.80901700f}, {-0.64944804f,-0.76040596f}, +{-0.70710677f,-0.70710677f}, {-0.76040596f,-0.64944804f}, {-0.80901700f,-0.58778518f}, +{-0.85264021f,-0.52249849f}, {-0.89100659f,-0.45399037f}, {-0.92387956f,-0.38268328f}, +{-0.95105654f,-0.30901679f}, {-0.97236991f,-0.23344538f}, {-0.98768836f,-0.15643445f}, +{-0.99691737f,-0.078459039f}, {-1.0000000f,8.7422777e-08f}, {-0.99691731f,0.078459218f}, +{-0.98768830f,0.15643461f}, {-0.97236985f,0.23344554f}, {-0.95105654f,0.30901697f}, +{-0.92387956f,0.38268346f}, {-0.89100653f,0.45399055f}, {-0.85264015f,0.52249861f}, +{-0.80901694f,0.58778536f}, {-0.76040590f,0.64944816f}, {-0.70710665f,0.70710689f}, +{-0.64944792f,0.76040608f}, {-0.58778507f,0.80901712f}, {-0.52249837f,0.85264033f}, +{-0.45399022f,0.89100665f}, {-0.38268313f,0.92387968f}, {-0.30901709f,0.95105648f}, +{-0.23344545f,0.97236991f}, {-0.15643452f,0.98768830f}, {-0.078459114f,0.99691731f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_120[120] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_60[60] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +}; +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_float32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_480[120], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_float32_t_480, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_float32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_240[60], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_float32_t_240, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_float32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_120[30], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_float32_t_120, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_float32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_60[15], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_float32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/meson.build new file mode 100644 index 0000000000..0e6d2e627b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/meson.build @@ -0,0 +1,19 @@ +tests = [ + 'test_unit_types', + 'test_unit_mathops', + 'test_unit_entropy', + 'test_unit_laplace', + 'test_unit_dft', + 'test_unit_mdct', + 'test_unit_rotation', + 'test_unit_cwrs32', +] + +foreach test_name : tests + exe = executable(test_name, '@0@.c'.format(test_name), + include_directories : opus_includes, + link_with : [celt_lib, celt_static_libs], + dependencies : libm, + install : false) + test(test_name, exe) +endforeach diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_cwrs32.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_cwrs32.c new file mode 100644 index 0000000000..f6b8ac4b0e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_cwrs32.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2008-2011 Xiph.Org Foundation, Mozilla Corporation, + Gregory Maxwell + Written by Jean-Marc Valin, Gregory Maxwell, and Timothy B. Terriberry */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#ifndef CUSTOM_MODES +#define CUSTOM_MODES +#else +#define TEST_CUSTOM_MODES +#endif + +#define CELT_C +#include "stack_alloc.h" +#include "entenc.c" +#include "entdec.c" +#include "entcode.c" +#include "cwrs.c" +#include "mathops.c" +#include "rate.h" + +#define NMAX (240) +#define KMAX (128) + +#ifdef TEST_CUSTOM_MODES + +#define NDIMS (44) +static const int pn[NDIMS]={ + 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 18, 20, 22, + 24, 26, 28, 30, 32, 36, 40, 44, 48, + 52, 56, 60, 64, 72, 80, 88, 96, 104, + 112, 120, 128, 144, 160, 176, 192, 208 +}; +static const int pkmax[NDIMS]={ + 128, 128, 128, 128, 88, 52, 36, 26, 22, + 18, 16, 15, 13, 12, 12, 11, 10, 9, + 9, 8, 8, 7, 7, 7, 7, 6, 6, + 6, 6, 6, 5, 5, 5, 5, 5, 5, + 4, 4, 4, 4, 4, 4, 4, 4 +}; + +#else /* TEST_CUSTOM_MODES */ + +#define NDIMS (22) +static const int pn[NDIMS]={ + 2, 3, 4, 6, 8, 9, 11, 12, 16, + 18, 22, 24, 32, 36, 44, 48, 64, 72, + 88, 96, 144, 176 +}; +static const int pkmax[NDIMS]={ + 128, 128, 128, 88, 36, 26, 18, 16, 12, + 11, 9, 9, 7, 7, 6, 6, 5, 5, + 5, 5, 4, 4 +}; + +#endif + +int main(void){ + int t; + int n; + ALLOC_STACK; + for(t=0;tpkmax[t])break; + printf("Testing CWRS with N=%i, K=%i...\n",n,k); +#if defined(SMALL_FOOTPRINT) + nc=ncwrs_urow(n,k,uu); +#else + nc=CELT_PVQ_V(n,k); +#endif + inc=nc/20000; + if(inc<1)inc=1; + for(i=0;i");*/ +#if defined(SMALL_FOOTPRINT) + ii=icwrs(n,k,&v,y,u); +#else + ii=icwrs(n,y); + v=CELT_PVQ_V(n,k); +#endif + if(ii!=i){ + fprintf(stderr,"Combination-index mismatch (%lu!=%lu).\n", + (long)ii,(long)i); + return 1; + } + if(v!=nc){ + fprintf(stderr,"Combination count mismatch (%lu!=%lu).\n", + (long)v,(long)nc); + return 2; + } + /*printf(" %6u\n",i);*/ + } + /*printf("\n");*/ + } + } + RESTORE_STACK; + return 0; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_dft.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_dft.c new file mode 100644 index 0000000000..ad6c60a032 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_dft.c @@ -0,0 +1,181 @@ +/* Copyright (c) 2008 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "stack_alloc.h" +#include "kiss_fft.h" +#include "mathops.h" +#include "modes.h" + +#ifndef M_PI +#define M_PI 3.141592653 +#endif + +int ret = 0; + +void check(kiss_fft_cpx * in,kiss_fft_cpx * out,int nfft,int isinverse) +{ + int bin,k; + double errpow=0,sigpow=0, snr; + + for (bin=0;binmdct.kfft[id]; +#endif + + in = (kiss_fft_cpx*)malloc(buflen); + out = (kiss_fft_cpx*)malloc(buflen); + + for (k=0;k1) { + int k; + for (k=1;k +#include +#include +#include +#define CELT_C +#include "entcode.h" +#include "entenc.h" +#include "entdec.h" +#include + +#include "entenc.c" +#include "entdec.c" +#include "entcode.c" + +#ifndef M_LOG2E +# define M_LOG2E 1.4426950408889634074 +#endif +#define DATA_SIZE 10000000 +#define DATA_SIZE2 10000 + +int main(int _argc,char **_argv){ + ec_enc enc; + ec_dec dec; + long nbits; + long nbits2; + double entropy; + int ft; + int ftb; + int sz; + int i; + int ret; + unsigned int sym; + unsigned int seed; + unsigned char *ptr; + const char *env_seed; + ret=0; + entropy=0; + if (_argc > 2) { + fprintf(stderr, "Usage: %s []\n", _argv[0]); + return 1; + } + env_seed = getenv("SEED"); + if (_argc > 1) + seed = atoi(_argv[1]); + else if (env_seed) + seed = atoi(env_seed); + else + seed = time(NULL); + /*Testing encoding of raw bit values.*/ + ptr = (unsigned char *)malloc(DATA_SIZE); + ec_enc_init(&enc,ptr, DATA_SIZE); + for(ft=2;ft<1024;ft++){ + for(i=0;i>(rand()%11U))+1U)+10; + sz=rand()/((RAND_MAX>>(rand()%9U))+1U); + data=(unsigned *)malloc(sz*sizeof(*data)); + tell=(unsigned *)malloc((sz+1)*sizeof(*tell)); + ec_enc_init(&enc,ptr,DATA_SIZE2); + zeros = rand()%13==0; + tell[0]=ec_tell_frac(&enc); + for(j=0;j>(rand()%9U))+1U); + logp1=(unsigned *)malloc(sz*sizeof(*logp1)); + data=(unsigned *)malloc(sz*sizeof(*data)); + tell=(unsigned *)malloc((sz+1)*sizeof(*tell)); + enc_method=(unsigned *)malloc(sz*sizeof(*enc_method)); + ec_enc_init(&enc,ptr,DATA_SIZE2); + tell[0]=ec_tell_frac(&enc); + for(j=0;j>1)+1); + logp1[j]=(rand()%15)+1; + enc_method[j]=rand()/((RAND_MAX>>2)+1); + switch(enc_method[j]){ + case 0:{ + ec_encode(&enc,data[j]?(1<>2)+1); + switch(dec_method){ + case 0:{ + fs=ec_decode(&dec,1<=(1<=(1< +#include +#define CELT_C +#include "laplace.h" +#include "stack_alloc.h" + +#include "entenc.c" +#include "entdec.c" +#include "entcode.c" +#include "laplace.c" + +#define DATA_SIZE 40000 + +int ec_laplace_get_start_freq(int decay) +{ + opus_uint32 ft = 32768 - LAPLACE_MINP*(2*LAPLACE_NMIN+1); + int fs = (ft*(16384-decay))/(16384+decay); + return fs+LAPLACE_MINP; +} + +int main(void) +{ + int i; + int ret = 0; + ec_enc enc; + ec_dec dec; + unsigned char *ptr; + int val[10000], decay[10000]; + ALLOC_STACK; + ptr = (unsigned char *)malloc(DATA_SIZE); + ec_enc_init(&enc,ptr,DATA_SIZE); + + val[0] = 3; decay[0] = 6000; + val[1] = 0; decay[1] = 5800; + val[2] = -1; decay[2] = 5600; + for (i=3;i<10000;i++) + { + val[i] = rand()%15-7; + decay[i] = rand()%11000+5000; + } + for (i=0;i<10000;i++) + ec_laplace_encode(&enc, &val[i], + ec_laplace_get_start_freq(decay[i]), decay[i]); + + ec_enc_done(&enc); + + ec_dec_init(&dec,ec_get_buffer(&enc),ec_range_bytes(&enc)); + + for (i=0;i<10000;i++) + { + int d = ec_laplace_decode(&dec, + ec_laplace_get_start_freq(decay[i]), decay[i]); + if (d != val[i]) + { + fprintf (stderr, "Got %d instead of %d\n", d, val[i]); + ret = 1; + } + } + + free(ptr); + RESTORE_STACK; + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mathops.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mathops.c new file mode 100644 index 0000000000..0615448db2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mathops.c @@ -0,0 +1,266 @@ +/* Copyright (c) 2008-2011 Xiph.Org Foundation, Mozilla Corporation, + Gregory Maxwell + Written by Jean-Marc Valin, Gregory Maxwell, and Timothy B. Terriberry */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef CUSTOM_MODES +#define CUSTOM_MODES +#endif + +#include +#include +#include "mathops.h" +#include "bands.h" + +#ifdef FIXED_POINT +#define WORD "%d" +#else +#define WORD "%f" +#endif + +int ret = 0; + +void testdiv(void) +{ + opus_int32 i; + for (i=1;i<=327670;i++) + { + double prod; + opus_val32 val; + val = celt_rcp(i); +#ifdef FIXED_POINT + prod = (1./32768./65526.)*val*i; +#else + prod = val*i; +#endif + if (fabs(prod-1) > .00025) + { + fprintf (stderr, "div failed: 1/%d="WORD" (product = %f)\n", i, val, prod); + ret = 1; + } + } +} + +void testsqrt(void) +{ + opus_int32 i; + for (i=1;i<=1000000000;i++) + { + double ratio; + opus_val16 val; + val = celt_sqrt(i); + ratio = val/sqrt(i); + if (fabs(ratio - 1) > .0005 && fabs(val-sqrt(i)) > 2) + { + fprintf (stderr, "sqrt failed: sqrt(%d)="WORD" (ratio = %f)\n", i, val, ratio); + ret = 1; + } + i+= i>>10; + } +} + +void testbitexactcos(void) +{ + int i; + opus_int32 min_d,max_d,last,chk; + chk=max_d=0; + last=min_d=32767; + for(i=64;i<=16320;i++) + { + opus_int32 d; + opus_int32 q=bitexact_cos(i); + chk ^= q*i; + d = last - q; + if (d>max_d)max_d=d; + if (dmax_d)max_d=d; + if (d0.0009) + { + fprintf (stderr, "celt_log2 failed: fabs((1.442695040888963387*log(x))-celt_log2(x))>0.001 (x = %f, error = %f)\n", x,error); + ret = 1; + } + } +} + +void testexp2(void) +{ + float x; + for (x=-11.0;x<24.0;x+=0.0007f) + { + float error = fabs(x-(1.442695040888963387*log(celt_exp2(x)))); + if (error>0.0002) + { + fprintf (stderr, "celt_exp2 failed: fabs(x-(1.442695040888963387*log(celt_exp2(x))))>0.0005 (x = %f, error = %f)\n", x,error); + ret = 1; + } + } +} + +void testexp2log2(void) +{ + float x; + for (x=-11.0;x<24.0;x+=0.0007f) + { + float error = fabs(x-(celt_log2(celt_exp2(x)))); + if (error>0.001) + { + fprintf (stderr, "celt_log2/celt_exp2 failed: fabs(x-(celt_log2(celt_exp2(x))))>0.001 (x = %f, error = %f)\n", x,error); + ret = 1; + } + } +} +#else +void testlog2(void) +{ + opus_val32 x; + for (x=8;x<1073741824;x+=(x>>3)) + { + float error = fabs((1.442695040888963387*log(x/16384.0))-celt_log2(x)/1024.0); + if (error>0.003) + { + fprintf (stderr, "celt_log2 failed: x = %ld, error = %f\n", (long)x,error); + ret = 1; + } + } +} + +void testexp2(void) +{ + opus_val16 x; + for (x=-32768;x<15360;x++) + { + float error1 = fabs(x/1024.0-(1.442695040888963387*log(celt_exp2(x)/65536.0))); + float error2 = fabs(exp(0.6931471805599453094*x/1024.0)-celt_exp2(x)/65536.0); + if (error1>0.0002&&error2>0.00004) + { + fprintf (stderr, "celt_exp2 failed: x = "WORD", error1 = %f, error2 = %f\n", x,error1,error2); + ret = 1; + } + } +} + +void testexp2log2(void) +{ + opus_val32 x; + for (x=8;x<65536;x+=(x>>3)) + { + float error = fabs(x-0.25*celt_exp2(celt_log2(x)))/16384; + if (error>0.004) + { + fprintf (stderr, "celt_log2/celt_exp2 failed: fabs(x-(celt_exp2(celt_log2(x))))>0.001 (x = %ld, error = %f)\n", (long)x,error); + ret = 1; + } + } +} + +void testilog2(void) +{ + opus_val32 x; + for (x=1;x<=268435455;x+=127) + { + opus_val32 lg; + opus_val32 y; + + lg = celt_ilog2(x); + if (lg<0 || lg>=31) + { + printf("celt_ilog2 failed: 0<=celt_ilog2(x)<31 (x = %d, celt_ilog2(x) = %d)\n",x,lg); + ret = 1; + } + y = 1<>1)>=y) + { + printf("celt_ilog2 failed: 2**celt_ilog2(x)<=x<2**(celt_ilog2(x)+1) (x = %d, 2**celt_ilog2(x) = %d)\n",x,y); + ret = 1; + } + } +} +#endif + +int main(void) +{ + testbitexactcos(); + testbitexactlog2tan(); + testdiv(); + testsqrt(); + testlog2(); + testexp2(); + testexp2log2(); +#ifdef FIXED_POINT + testilog2(); +#endif + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mdct.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mdct.c new file mode 100644 index 0000000000..70dc042efc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_mdct.c @@ -0,0 +1,229 @@ +/* Copyright (c) 2008-2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "mdct.h" +#include "stack_alloc.h" +#include "kiss_fft.h" +#include "mdct.h" +#include "modes.h" + +#ifndef M_PI +#define M_PI 3.141592653 +#endif + +int ret = 0; +void check(kiss_fft_scalar * in,kiss_fft_scalar * out,int nfft,int isinverse) +{ + int bin,k; + double errpow=0,sigpow=0; + double snr; + for (bin=0;binmdct; +#endif + + in = (kiss_fft_scalar*)malloc(buflen); + in_copy = (kiss_fft_scalar*)malloc(buflen); + out = (kiss_fft_scalar*)malloc(buflen); + window = (opus_val16*)malloc(sizeof(opus_val16)*nfft/2); + + for (k=0;k1) { + int k; + for (k=1;k +#include +#include "vq.h" +#include "bands.h" +#include "stack_alloc.h" +#include + + +#define MAX_SIZE 100 + +int ret=0; +void test_rotation(int N, int K) +{ + int i; + double err = 0, ener = 0, snr, snr0; + opus_val16 x0[MAX_SIZE]; + opus_val16 x1[MAX_SIZE]; + for (i=0;i 20) + { + fprintf(stderr, "FAIL!\n"); + ret = 1; + } +} + +int main(void) +{ + ALLOC_STACK; + test_rotation(15, 3); + test_rotation(23, 5); + test_rotation(50, 3); + test_rotation(80, 1); + RESTORE_STACK; + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_types.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_types.c new file mode 100644 index 0000000000..67a0fb8ed3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/tests/test_unit_types.c @@ -0,0 +1,50 @@ +/* Copyright (c) 2008-2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_types.h" +#include + +int main(void) +{ + opus_int16 i = 1; + i <<= 14; + if (i>>14 != 1) + { + fprintf(stderr, "opus_int16 isn't 16 bits\n"); + return 1; + } + if (sizeof(opus_int16)*2 != sizeof(opus_int32)) + { + fprintf(stderr, "16*2 != 32\n"); + return 1; + } + return 0; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/vq.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/vq.c new file mode 100644 index 0000000000..8011e22548 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/vq.c @@ -0,0 +1,442 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mathops.h" +#include "cwrs.h" +#include "vq.h" +#include "arch.h" +#include "os_support.h" +#include "bands.h" +#include "rate.h" +#include "pitch.h" + +#if defined(MIPSr1_ASM) +#include "mips/vq_mipsr1.h" +#endif + +#ifndef OVERRIDE_vq_exp_rotation1 +static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_val16 s) +{ + int i; + opus_val16 ms; + celt_norm *Xptr; + Xptr = X; + ms = NEG16(s); + for (i=0;i=0;i--) + { + celt_norm x1, x2; + x1 = Xptr[0]; + x2 = Xptr[stride]; + Xptr[stride] = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x2), s, x1), 15)); + *Xptr-- = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x1), ms, x2), 15)); + } +} +#endif /* OVERRIDE_vq_exp_rotation1 */ + +void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) +{ + static const int SPREAD_FACTOR[3]={15,10,5}; + int i; + opus_val16 c, s; + opus_val16 gain, theta; + int stride2=0; + int factor; + + if (2*K>=len || spread==SPREAD_NONE) + return; + factor = SPREAD_FACTOR[spread-1]; + + gain = celt_div((opus_val32)MULT16_16(Q15_ONE,len),(opus_val32)(len+factor*K)); + theta = HALF16(MULT16_16_Q15(gain,gain)); + + c = celt_cos_norm(EXTEND32(theta)); + s = celt_cos_norm(EXTEND32(SUB16(Q15ONE,theta))); /* sin(theta) */ + + if (len>=8*stride) + { + stride2 = 1; + /* This is just a simple (equivalent) way of computing sqrt(len/stride) with rounding. + It's basically incrementing long as (stride2+0.5)^2 < len/stride. */ + while ((stride2*stride2+stride2)*stride + (stride>>2) < len) + stride2++; + } + /*NOTE: As a minor optimization, we could be passing around log2(B), not B, for both this and for + extract_collapse_mask().*/ + len = celt_udiv(len, stride); + for (i=0;i>1; +#endif + t = VSHR32(Ryy, 2*(k-7)); + g = MULT16_16_P15(celt_rsqrt_norm(t),gain); + + i=0; + do + X[i] = EXTRACT16(PSHR32(MULT16_16(g, iy[i]), k+1)); + while (++i < N); +} + +static unsigned extract_collapse_mask(int *iy, int N, int B) +{ + unsigned collapse_mask; + int N0; + int i; + if (B<=1) + return 1; + /*NOTE: As a minor optimization, we could be passing around log2(B), not B, for both this and for + exp_rotation().*/ + N0 = celt_udiv(N, B); + collapse_mask = 0; + i=0; do { + int j; + unsigned tmp=0; + j=0; do { + tmp |= iy[i*N0+j]; + } while (++j (N>>1)) + { + opus_val16 rcp; + j=0; do { + sum += X[j]; + } while (++j EPSILON && sum < 64)) +#endif + { + X[0] = QCONST16(1.f,14); + j=1; do + X[j]=0; + while (++j=0); + + /* This should never happen, but just in case it does (e.g. on silence) + we fill the first bin with pulses. */ +#ifdef FIXED_POINT_DEBUG + celt_sig_assert(pulsesLeft<=N+3); +#endif + if (pulsesLeft > N+3) + { + opus_val16 tmp = (opus_val16)pulsesLeft; + yy = MAC16_16(yy, tmp, tmp); + yy = MAC16_16(yy, tmp, y[0]); + iy[0] += pulsesLeft; + pulsesLeft=0; + } + + for (i=0;i= best_num/best_den, but that way + we can do it without any division */ + /* OPT: It's not clear whether a cmov is faster than a branch here + since the condition is more often false than true and using + a cmov introduces data dependencies across iterations. The optimal + choice may be architecture-dependent. */ + if (opus_unlikely(MULT16_16(best_den, Rxy) > MULT16_16(Ryy, best_num))) + { + best_den = Ryy; + best_num = Rxy; + best_id = j; + } + } while (++j0, "alg_quant() needs at least one pulse"); + celt_assert2(N>1, "alg_quant() needs at least two dimensions"); + + /* Covers vectorization by up to 4. */ + ALLOC(iy, N+3, int); + + exp_rotation(X, N, 1, B, K, spread); + + yy = op_pvq_search(X, iy, K, N, arch); + + encode_pulses(iy, N, K, enc); + + if (resynth) + { + normalise_residual(iy, X, N, yy, gain); + exp_rotation(X, N, -1, B, K, spread); + } + + collapse_mask = extract_collapse_mask(iy, N, B); + RESTORE_STACK; + return collapse_mask; +} + +/** Decode pulse vector and combine the result with the pitch vector to produce + the final normalised signal in the current band. */ +unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, + ec_dec *dec, opus_val16 gain) +{ + opus_val32 Ryy; + unsigned collapse_mask; + VARDECL(int, iy); + SAVE_STACK; + + celt_assert2(K>0, "alg_unquant() needs at least one pulse"); + celt_assert2(N>1, "alg_unquant() needs at least two dimensions"); + ALLOC(iy, N, int); + Ryy = decode_pulses(iy, N, K, dec); + normalise_residual(iy, X, N, Ryy, gain); + exp_rotation(X, N, -1, B, K, spread); + collapse_mask = extract_collapse_mask(iy, N, B); + RESTORE_STACK; + return collapse_mask; +} + +#ifndef OVERRIDE_renormalise_vector +void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) +{ + int i; +#ifdef FIXED_POINT + int k; +#endif + opus_val32 E; + opus_val16 g; + opus_val32 t; + celt_norm *xptr; + E = EPSILON + celt_inner_prod(X, X, N, arch); +#ifdef FIXED_POINT + k = celt_ilog2(E)>>1; +#endif + t = VSHR32(E, 2*(k-7)); + g = MULT16_16_P15(celt_rsqrt_norm(t),gain); + + xptr = X; + for (i=0;i +#include +#include +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" +#include "x86cpu.h" + +#if defined(FIXED_POINT) + +void celt_fir_sse4_1(const opus_val16 *x, + const opus_val16 *num, + opus_val16 *y, + int N, + int ord, + int arch) +{ + int i,j; + VARDECL(opus_val16, rnum); + + __m128i vecNoA; + opus_int32 noA ; + SAVE_STACK; + + ALLOC(rnum, ord, opus_val16); + for(i=0;i> 1; + vecNoA = _mm_set_epi32(noA, noA, noA, noA); + + for (i=0;i +#include "x86cpu.h" +#include "pitch.h" + +#if defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(FIXED_POINT) + +/* Like the "regular" xcorr_kernel(), but computes 8 results at a time. */ +static void xcorr_kernel_avx(const float *x, const float *y, float sum[8], int len) +{ + __m256 xsum0, xsum1, xsum2, xsum3, xsum4, xsum5, xsum6, xsum7; + xsum7 = xsum6 = xsum5 = xsum4 = xsum3 = xsum2 = xsum1 = xsum0 = _mm256_setzero_ps(); + int i; + __m256 x0; + /* Compute 8 inner products using partial sums. */ + for (i=0;i0); + (void)arch; + for (i=0;i +#include "arch.h" + +void xcorr_kernel_sse(const opus_val16 *x, const opus_val16 *y, opus_val32 sum[4], int len) +{ + int j; + __m128 xsum1, xsum2; + xsum1 = _mm_loadu_ps(sum); + xsum2 = _mm_setzero_ps(); + + for (j = 0; j < len-3; j += 4) + { + __m128 x0 = _mm_loadu_ps(x+j); + __m128 yj = _mm_loadu_ps(y+j); + __m128 y3 = _mm_loadu_ps(y+j+3); + + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x00),yj)); + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x55), + _mm_shuffle_ps(yj,y3,0x49))); + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xaa), + _mm_shuffle_ps(yj,y3,0x9e))); + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xff),y3)); + } + if (j < len) + { + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + if (++j < len) + { + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + if (++j < len) + { + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + } + } + } + _mm_storeu_ps(sum,_mm_add_ps(xsum1,xsum2)); +} + + +void dual_inner_prod_sse(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2) +{ + int i; + __m128 xsum1, xsum2; + xsum1 = _mm_setzero_ps(); + xsum2 = _mm_setzero_ps(); + for (i=0;i +#include + +#include "macros.h" +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE2) && defined(FIXED_POINT) +opus_val32 celt_inner_prod_sse2(const opus_val16 *x, const opus_val16 *y, + int N) +{ + opus_int i, dataSize16; + opus_int32 sum; + + __m128i inVec1_76543210, inVec1_FEDCBA98, acc1; + __m128i inVec2_76543210, inVec2_FEDCBA98, acc2; + + sum = 0; + dataSize16 = N & ~15; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for (i=0;i= 8) + { + inVec1_76543210 = _mm_loadu_si128((__m128i *)(&x[i + 0])); + inVec2_76543210 = _mm_loadu_si128((__m128i *)(&y[i + 0])); + + inVec1_76543210 = _mm_madd_epi16(inVec1_76543210, inVec2_76543210); + + acc1 = _mm_add_epi32(acc1, inVec1_76543210); + i += 8; + } + + acc1 = _mm_add_epi32(acc1, _mm_unpackhi_epi64( acc1, acc1)); + acc1 = _mm_add_epi32(acc1, _mm_shufflelo_epi16( acc1, 0x0E)); + sum += _mm_cvtsi128_si32(acc1); + + for (;i +#include + +#include "macros.h" +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) && defined(FIXED_POINT) +#include +#include "x86cpu.h" + +opus_val32 celt_inner_prod_sse4_1(const opus_val16 *x, const opus_val16 *y, + int N) +{ + opus_int i, dataSize16; + opus_int32 sum; + __m128i inVec1_76543210, inVec1_FEDCBA98, acc1; + __m128i inVec2_76543210, inVec2_FEDCBA98, acc2; + __m128i inVec1_3210, inVec2_3210; + + sum = 0; + dataSize16 = N & ~15; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for (i=0;i= 8) + { + inVec1_76543210 = _mm_loadu_si128((__m128i *)(&x[i + 0])); + inVec2_76543210 = _mm_loadu_si128((__m128i *)(&y[i + 0])); + + inVec1_76543210 = _mm_madd_epi16(inVec1_76543210, inVec2_76543210); + + acc1 = _mm_add_epi32(acc1, inVec1_76543210); + i += 8; + } + + if (N - i >= 4) + { + inVec1_3210 = OP_CVTEPI16_EPI32_M64(&x[i + 0]); + inVec2_3210 = OP_CVTEPI16_EPI32_M64(&y[i + 0]); + + inVec1_3210 = _mm_mullo_epi32(inVec1_3210, inVec2_3210); + + acc1 = _mm_add_epi32(acc1, inVec1_3210); + i += 4; + } + + acc1 = _mm_add_epi32(acc1, _mm_unpackhi_epi64(acc1, acc1)); + acc1 = _mm_add_epi32(acc1, _mm_shufflelo_epi16(acc1, 0x0E)); + + sum += _mm_cvtsi128_si32(acc1); + + for (;i= 3); + + sum0 = _mm_setzero_si128(); + sum1 = _mm_setzero_si128(); + sum2 = _mm_setzero_si128(); + sum3 = _mm_setzero_si128(); + + for (j=0;j<(len-7);j+=8) + { + vecX = _mm_loadu_si128((__m128i *)(&x[j + 0])); + vecY0 = _mm_loadu_si128((__m128i *)(&y[j + 0])); + vecY1 = _mm_loadu_si128((__m128i *)(&y[j + 1])); + vecY2 = _mm_loadu_si128((__m128i *)(&y[j + 2])); + vecY3 = _mm_loadu_si128((__m128i *)(&y[j + 3])); + + sum0 = _mm_add_epi32(sum0, _mm_madd_epi16(vecX, vecY0)); + sum1 = _mm_add_epi32(sum1, _mm_madd_epi16(vecX, vecY1)); + sum2 = _mm_add_epi32(sum2, _mm_madd_epi16(vecX, vecY2)); + sum3 = _mm_add_epi32(sum3, _mm_madd_epi16(vecX, vecY3)); + } + + sum0 = _mm_add_epi32(sum0, _mm_unpackhi_epi64( sum0, sum0)); + sum0 = _mm_add_epi32(sum0, _mm_shufflelo_epi16( sum0, 0x0E)); + + sum1 = _mm_add_epi32(sum1, _mm_unpackhi_epi64( sum1, sum1)); + sum1 = _mm_add_epi32(sum1, _mm_shufflelo_epi16( sum1, 0x0E)); + + sum2 = _mm_add_epi32(sum2, _mm_unpackhi_epi64( sum2, sum2)); + sum2 = _mm_add_epi32(sum2, _mm_shufflelo_epi16( sum2, 0x0E)); + + sum3 = _mm_add_epi32(sum3, _mm_unpackhi_epi64( sum3, sum3)); + sum3 = _mm_add_epi32(sum3, _mm_shufflelo_epi16( sum3, 0x0E)); + + vecSum = _mm_unpacklo_epi64(_mm_unpacklo_epi32(sum0, sum1), + _mm_unpacklo_epi32(sum2, sum3)); + + for (;j<(len-3);j+=4) + { + vecX = OP_CVTEPI16_EPI32_M64(&x[j + 0]); + vecX0 = _mm_shuffle_epi32(vecX, 0x00); + vecX1 = _mm_shuffle_epi32(vecX, 0x55); + vecX2 = _mm_shuffle_epi32(vecX, 0xaa); + vecX3 = _mm_shuffle_epi32(vecX, 0xff); + + vecY0 = OP_CVTEPI16_EPI32_M64(&y[j + 0]); + vecY1 = OP_CVTEPI16_EPI32_M64(&y[j + 1]); + vecY2 = OP_CVTEPI16_EPI32_M64(&y[j + 2]); + vecY3 = OP_CVTEPI16_EPI32_M64(&y[j + 3]); + + sum0 = _mm_mullo_epi32(vecX0, vecY0); + sum1 = _mm_mullo_epi32(vecX1, vecY1); + sum2 = _mm_mullo_epi32(vecX2, vecY2); + sum3 = _mm_mullo_epi32(vecX3, vecY3); + + sum0 = _mm_add_epi32(sum0, sum1); + sum2 = _mm_add_epi32(sum2, sum3); + vecSum = _mm_add_epi32(vecSum, sum0); + vecSum = _mm_add_epi32(vecSum, sum2); + } + + vecX = OP_CVTEPI16_EPI32_M64(&x[len - 4]); + if (len - j == 3) + { + vecX0 = _mm_shuffle_epi32(vecX, 0x55); + vecX1 = _mm_shuffle_epi32(vecX, 0xaa); + vecX2 = _mm_shuffle_epi32(vecX, 0xff); + + vecY0 = OP_CVTEPI16_EPI32_M64(&y[j + 0]); + vecY1 = OP_CVTEPI16_EPI32_M64(&y[j + 1]); + vecY2 = OP_CVTEPI16_EPI32_M64(&y[j + 2]); + + sum0 = _mm_mullo_epi32(vecX0, vecY0); + sum1 = _mm_mullo_epi32(vecX1, vecY1); + sum2 = _mm_mullo_epi32(vecX2, vecY2); + + vecSum = _mm_add_epi32(vecSum, sum0); + vecSum = _mm_add_epi32(vecSum, sum1); + vecSum = _mm_add_epi32(vecSum, sum2); + } + else if (len - j == 2) + { + vecX0 = _mm_shuffle_epi32(vecX, 0xaa); + vecX1 = _mm_shuffle_epi32(vecX, 0xff); + + vecY0 = OP_CVTEPI16_EPI32_M64(&y[j + 0]); + vecY1 = OP_CVTEPI16_EPI32_M64(&y[j + 1]); + + sum0 = _mm_mullo_epi32(vecX0, vecY0); + sum1 = _mm_mullo_epi32(vecX1, vecY1); + + vecSum = _mm_add_epi32(vecSum, sum0); + vecSum = _mm_add_epi32(vecSum, sum1); + } + else if (len - j == 1) + { + vecX0 = _mm_shuffle_epi32(vecX, 0xff); + + vecY0 = OP_CVTEPI16_EPI32_M64(&y[j + 0]); + + sum0 = _mm_mullo_epi32(vecX0, vecY0); + + vecSum = _mm_add_epi32(vecSum, sum0); + } + + initSum = _mm_loadu_si128((__m128i *)(&sum[0])); + initSum = _mm_add_epi32(initSum, vecSum); + _mm_storeu_si128((__m128i *)sum, initSum); + +#ifdef OPUS_CHECK_ASM + celt_assert(!memcmp(sum_c, sum, sizeof(sum_c))); +#endif +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse.h new file mode 100644 index 0000000000..444503b630 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2016 Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VQ_SSE_H +#define VQ_SSE_H + +#if defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(FIXED_POINT) + +opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch); + +#if defined(OPUS_X86_PRESUME_SSE2) + +#define OVERRIDE_OP_PVQ_SEARCH +#define op_pvq_search(x, iy, K, N, arch) \ + (op_pvq_search_sse2(x, iy, K, N, arch)) + +#elif defined(OPUS_HAVE_RTCD) + +#define OVERRIDE_OP_PVQ_SEARCH +extern opus_val16 (*const OP_PVQ_SEARCH_IMPL[OPUS_ARCHMASK + 1])( + celt_norm *_X, int *iy, int K, int N, int arch); + +# define op_pvq_search(X, iy, K, N, arch) \ + ((*OP_PVQ_SEARCH_IMPL[(arch) & OPUS_ARCHMASK])(X, iy, K, N, arch)) + +#endif +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse2.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse2.c new file mode 100644 index 0000000000..4c4ebf8e2d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/vq_sse2.c @@ -0,0 +1,217 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2007-2016 Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "vq.h" +#include "x86cpu.h" + + +#ifndef FIXED_POINT + +opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch) +{ + int i, j; + int pulsesLeft; + float xy, yy; + VARDECL(celt_norm, y); + VARDECL(celt_norm, X); + VARDECL(float, signy); + __m128 signmask; + __m128 sums; + __m128i fours; + SAVE_STACK; + + (void)arch; + /* All bits set to zero, except for the sign bit. */ + signmask = _mm_set_ps1(-0.f); + fours = _mm_set_epi32(4, 4, 4, 4); + ALLOC(y, N+3, celt_norm); + ALLOC(X, N+3, celt_norm); + ALLOC(signy, N+3, float); + + OPUS_COPY(X, _X, N); + X[N] = X[N+1] = X[N+2] = 0; + sums = _mm_setzero_ps(); + for (j=0;j (N>>1)) + { + __m128i pulses_sum; + __m128 yy4, xy4; + __m128 rcp4; + opus_val32 sum = _mm_cvtss_f32(sums); + /* If X is too small, just replace it with a pulse at 0 */ + /* Prevents infinities and NaNs from causing too many pulses + to be allocated. 64 is an approximation of infinity here. */ + if (!(sum > EPSILON && sum < 64)) + { + X[0] = QCONST16(1.f,14); + j=1; do + X[j]=0; + while (++j=0); + + /* This should never happen, but just in case it does (e.g. on silence) + we fill the first bin with pulses. */ + if (pulsesLeft > N+3) + { + opus_val16 tmp = (opus_val16)pulsesLeft; + yy = MAC16_16(yy, tmp, tmp); + yy = MAC16_16(yy, tmp, y[0]); + iy[0] += pulsesLeft; + pulsesLeft=0; + } + + for (i=0;i +static _inline void cpuid(unsigned int CPUInfo[4], unsigned int InfoType) +{ + __cpuid((int*)CPUInfo, InfoType); +} + +#else + +#if defined(CPU_INFO_BY_C) +#include +#endif + +static void cpuid(unsigned int CPUInfo[4], unsigned int InfoType) +{ +#if defined(CPU_INFO_BY_ASM) +#if defined(__i386__) && defined(__PIC__) +/* %ebx is PIC register in 32-bit, so mustn't clobber it. */ + __asm__ __volatile__ ( + "xchg %%ebx, %1\n" + "cpuid\n" + "xchg %%ebx, %1\n": + "=a" (CPUInfo[0]), + "=r" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) : + /* We clear ECX to avoid a valgrind false-positive prior to v3.17.0. */ + "0" (InfoType), "2" (0) + ); +#else + __asm__ __volatile__ ( + "cpuid": + "=a" (CPUInfo[0]), + "=b" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) : + /* We clear ECX to avoid a valgrind false-positive prior to v3.17.0. */ + "0" (InfoType), "2" (0) + ); +#endif +#elif defined(CPU_INFO_BY_C) + /* We use __get_cpuid_count to clear ECX to avoid a valgrind false-positive + prior to v3.17.0.*/ + if (!__get_cpuid_count(InfoType, 0, &(CPUInfo[0]), &(CPUInfo[1]), &(CPUInfo[2]), &(CPUInfo[3]))) { + /* Our function cannot fail, but __get_cpuid{_count} can. + Returning all zeroes will effectively disable all SIMD, which is + what we want on CPUs that don't support CPUID. */ + CPUInfo[3] = CPUInfo[2] = CPUInfo[1] = CPUInfo[0] = 0; + } +#else +# error "Configured to use x86 RTCD, but no CPU detection method available. " \ + "Reconfigure with --disable-rtcd (or send patches)." +#endif +} + +#endif + +typedef struct CPU_Feature{ + /* SIMD: 128-bit */ + int HW_SSE; + int HW_SSE2; + int HW_SSE41; + /* SIMD: 256-bit */ + int HW_AVX2; +} CPU_Feature; + +static void opus_cpu_feature_check(CPU_Feature *cpu_feature) +{ + unsigned int info[4]; + unsigned int nIds = 0; + + cpuid(info, 0); + nIds = info[0]; + + if (nIds >= 1){ + cpuid(info, 1); + cpu_feature->HW_SSE = (info[3] & (1 << 25)) != 0; + cpu_feature->HW_SSE2 = (info[3] & (1 << 26)) != 0; + cpu_feature->HW_SSE41 = (info[2] & (1 << 19)) != 0; + cpu_feature->HW_AVX2 = (info[2] & (1 << 28)) != 0 && (info[2] & (1 << 12)) != 0; + if (cpu_feature->HW_AVX2 && nIds >= 7) { + cpuid(info, 7); + cpu_feature->HW_AVX2 = cpu_feature->HW_AVX2 && (info[1] & (1 << 5)) != 0; + } else { + cpu_feature->HW_AVX2 = 0; + } + } + else { + cpu_feature->HW_SSE = 0; + cpu_feature->HW_SSE2 = 0; + cpu_feature->HW_SSE41 = 0; + cpu_feature->HW_AVX2 = 0; + } +} + +static int opus_select_arch_impl(void) +{ + CPU_Feature cpu_feature; + int arch; + + opus_cpu_feature_check(&cpu_feature); + + arch = 0; + if (!cpu_feature.HW_SSE) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_SSE2) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_SSE41) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_AVX2) + { + return arch; + } + arch++; + + return arch; +} + +int opus_select_arch(void) { + int arch = opus_select_arch_impl(); +#ifdef FUZZING + /* Randomly downgrade the architecture. */ + arch = rand()%(arch+1); +#endif + return arch; +} + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/x86cpu.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/x86cpu.h new file mode 100644 index 0000000000..1e5b6a4cb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/celt/x86/x86cpu.h @@ -0,0 +1,112 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(X86CPU_H) +# define X86CPU_H + +# if defined(OPUS_X86_MAY_HAVE_SSE) +# define MAY_HAVE_SSE(name) name ## _sse +# else +# define MAY_HAVE_SSE(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE2) +# define MAY_HAVE_SSE2(name) name ## _sse2 +# else +# define MAY_HAVE_SSE2(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE4_1) +# define MAY_HAVE_SSE4_1(name) name ## _sse4_1 +# else +# define MAY_HAVE_SSE4_1(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_AVX2) +# define MAY_HAVE_AVX2(name) name ## _avx2 +# else +# define MAY_HAVE_AVX2(name) name ## _c +# endif + +# if defined(OPUS_HAVE_RTCD) && \ + ((defined(OPUS_X86_MAY_HAVE_SSE) && !defined(OPUS_X86_PRESUME_SSE)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE2) && !defined(OPUS_X86_PRESUME_SSE2)) || \ + (defined(OPUS_X86_MAY_HAVE_SSE4_1) && !defined(OPUS_X86_PRESUME_SSE4_1)) || \ + (defined(OPUS_X86_MAY_HAVE_AVX2) && !defined(OPUS_X86_PRESUME_AVX2))) +int opus_select_arch(void); +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE2) +# include "opus_defines.h" + +/*MOVD should not impose any alignment restrictions, but the C standard does, + and UBSan will report errors if we actually make unaligned accesses. + Use this to work around those restrictions (which should hopefully all get + optimized to a single MOVD instruction). + GCC implemented _mm_loadu_si32() since GCC 11; HOWEVER, there is a bug! + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99754 + LLVM implemented _mm_loadu_si32() since Clang 8.0, however the + __clang_major__ version number macro is unreliable, as vendors + (specifically, Apple) will use different numbering schemes than upstream. + Clang's advice is "use feature detection", but they do not provide feature + detection support for specific SIMD functions. + We follow the approach from the SIMDe project and instead detect unrelated + features that should be available in the version we want (see + ).*/ +# if defined(__clang__) +# if __has_warning("-Wextra-semi-stmt") || \ + __has_builtin(__builtin_rotateleft32) +# define OPUS_CLANG_8 (1) +# endif +# endif +# if !defined(_MSC_VER) && !OPUS_GNUC_PREREQ(11,3) && !defined(OPUS_CLANG_8) +# include +# include + +# ifdef _mm_loadu_si32 +# undef _mm_loadu_si32 +# endif +# define _mm_loadu_si32 WORKAROUND_mm_loadu_si32 +static inline __m128i WORKAROUND_mm_loadu_si32(void const* mem_addr) { + int val; + memcpy(&val, mem_addr, sizeof(val)); + return _mm_cvtsi32_si128(val); +} +# elif defined(_MSC_VER) + /* MSVC needs this for _mm_loadu_si32 */ +# include +# endif + +# define OP_CVTEPI8_EPI32_M32(x) \ + (_mm_cvtepi8_epi32(_mm_loadu_si32(x))) + +# define OP_CVTEPI16_EPI32_M64(x) \ + (_mm_cvtepi16_epi32(_mm_loadl_epi64((__m128i *)(void*)(x)))) + +# endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/config.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/config.h new file mode 100644 index 0000000000..27196303eb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/config.h @@ -0,0 +1,17 @@ +#ifndef OPUS_CONFIG_H +#define OPUS_CONFIG_H + +#define OPUS_BUILD 1 +#define USE_ALLOCA 1 +#define HAVE_LRINTF 1 +#define HAVE_LRINT 1 +#define VAR_ARRAYS 1 + +/* Disable SIMD intrinsic dispatch. We exclude the corresponding .c files in + * Package.swift and -U the architecture macros in cSettings, so any NEON/SSE + * dispatch stubs left compiled in fall through to scalar paths. */ + +/* Float build — FIXED_POINT not defined → float build, which matches the + * decoder API (opus_decode_float) the Swift facade exposes. */ + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/meson.build new file mode 100644 index 0000000000..c1fb0b76f9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/meson.build @@ -0,0 +1,13 @@ +opus_headers = [ + 'opus.h', + 'opus_multistream.h', + 'opus_projection.h', + 'opus_types.h', + 'opus_defines.h', +] + +if opt_custom_modes + opus_headers += ['opus_custom.h'] +endif + +install_headers(opus_headers, subdir: 'opus') diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus.h new file mode 100644 index 0000000000..eadeda75a0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus.h @@ -0,0 +1,1099 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus.h + * @brief Opus reference implementation API + */ + +#ifndef OPUS_H +#define OPUS_H + +#include "opus_types.h" +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @mainpage Opus + * + * The Opus codec is designed for interactive speech and audio transmission over the Internet. + * It is designed by the IETF Codec Working Group and incorporates technology from + * Skype's SILK codec and Xiph.Org's CELT codec. + * + * The Opus codec is designed to handle a wide range of interactive audio applications, + * including Voice over IP, videoconferencing, in-game chat, and even remote live music + * performances. It can scale from low bit-rate narrowband speech to very high quality + * stereo music. Its main features are: + + * @li Sampling rates from 8 to 48 kHz + * @li Bit-rates from 6 kb/s to 510 kb/s + * @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR) + * @li Audio bandwidth from narrowband to full-band + * @li Support for speech and music + * @li Support for mono and stereo + * @li Support for multichannel (up to 255 channels) + * @li Frame sizes from 2.5 ms to 60 ms + * @li Good loss robustness and packet loss concealment (PLC) + * @li Floating point and fixed-point implementation + * + * Documentation sections: + * @li @ref opus_encoder + * @li @ref opus_decoder + * @li @ref opus_repacketizer + * @li @ref opus_multistream + * @li @ref opus_libinfo + * @li @ref opus_custom + */ + +/** @defgroup opus_encoder Opus Encoder + * @{ + * + * @brief This page describes the process and functions used to encode Opus. + * + * Since Opus is a stateful codec, the encoding process starts with creating an encoder + * state. This can be done with: + * + * @code + * int error; + * OpusEncoder *enc; + * enc = opus_encoder_create(Fs, channels, application, &error); + * @endcode + * + * From this point, @c enc can be used for encoding an audio stream. An encoder state + * @b must @b not be used for more than one stream at the same time. Similarly, the encoder + * state @b must @b not be re-initialized for each frame. + * + * While opus_encoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * + * @code + * int size; + * int error; + * OpusEncoder *enc; + * size = opus_encoder_get_size(channels); + * enc = malloc(size); + * error = opus_encoder_init(enc, Fs, channels, application); + * @endcode + * + * where opus_encoder_get_size() returns the required size for the encoder state. Note that + * future versions of this code may change the size, so no assumptions should be made about it. + * + * The encoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * It is possible to change some of the encoder's settings using the opus_encoder_ctl() + * interface. All these settings already default to the recommended value, so they should + * only be changed when necessary. The most common settings one may want to change are: + * + * @code + * opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); + * opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); + * opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type)); + * @endcode + * + * where + * + * @arg bitrate is in bits per second (b/s) + * @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest + * @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC + * + * See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream. + * + * To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data: + * @code + * len = opus_encode(enc, audio_frame, frame_size, packet, max_packet); + * @endcode + * + * where + *
    + *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • + *
  • frame_size is the duration of the frame in samples (per channel)
  • + *
  • packet is the byte array to which the compressed data is written
  • + *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). + * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • + *
+ * + * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. + * The return value can be negative, which indicates that an error has occurred. If the return value + * is 2 bytes or less, then the packet does not need to be transmitted (DTX). + * + * Once the encoder state if no longer needed, it can be destroyed with + * + * @code + * opus_encoder_destroy(enc); + * @endcode + * + * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), + * then no action is required aside from potentially freeing the memory that was manually + * allocated for it (calling free(enc) for the example above) + * + */ + +/** Opus encoder state. + * This contains the complete state of an Opus encoder. + * It is position independent and can be freely copied. + * @see opus_encoder_create,opus_encoder_init + */ +typedef struct OpusEncoder OpusEncoder; + +/** Gets the size of an OpusEncoder structure. + * @param[in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); + +/** + */ + +/** Allocates and initializes an encoder state. + * There are three coding modes: + * + * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice + * signals. It enhances the input signal by high-pass filtering and + * emphasizing formants and harmonics. Optionally it includes in-band + * forward error correction to protect against packet loss. Use this + * mode for typical VoIP applications. Because of the enhancement, + * even at high bitrates the output may sound different from the input. + * + * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most + * non-voice signals like music. Use this mode for music and mixed + * (music/voice) content, broadcast, and applications requiring less + * than 15 ms of coding delay. + * + * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that + * disables the speech-optimized mode in exchange for slightly reduced delay. + * This mode can only be set on an newly initialized or freshly reset encoder + * because it changes the codec delay. + * + * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of @ref OPUS_APPLICATION_VOIP, @ref OPUS_APPLICATION_AUDIO, or @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @param [out] error int*: @ref opus_errorcodes + * @note Regardless of the sampling rate and number channels selected, the Opus encoder + * can switch to a lower audio bandwidth or number of channels if the bitrate + * selected is too low. This also means that it is safe to always use 48 kHz stereo input + * and let the encoder optimize the encoding. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( + opus_int32 Fs, + int channels, + int application, + int *error +); + +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_encoder_create(),opus_encoder_get_size() + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) in input signal + * @param [in] application int: Coding mode (one of OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, or OPUS_APPLICATION_RESTRICTED_LOWDELAY) + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_encoder_init( + OpusEncoder *st, + opus_int32 Fs, + int channels, + int application +) OPUS_ARG_NONNULL(1); + +/** Encodes an Opus frame. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( + OpusEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes an Opus frame from floating point input. + * @param [in] st OpusEncoder*: Encoder state + * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. + * length is frame_size*channels*sizeof(float) + * @param [in] frame_size int: Number of samples per channel in the + * input signal. + * This must be an Opus frame size for + * the encoder's sampling rate. + * For example, at 48 kHz the permitted + * values are 120, 240, 480, 960, 1920, + * and 2880. + * Passing in a duration of less than + * 10 ms (480 samples at 48 kHz) will + * prevent the encoder from using the LPC + * or hybrid modes. + * @param [out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( + OpusEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusEncoder allocated by opus_encoder_create(). + * @param[in] st OpusEncoder*: State to be freed. + */ +OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); + +/** Perform a CTL function on an Opus encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusEncoder*: Encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_encoderctls. + * @see opus_genericctls + * @see opus_encoderctls + */ +OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); +/**@}*/ + +/** @defgroup opus_decoder Opus Decoder + * @{ + * + * @brief This page describes the process and functions used to decode Opus. + * + * The decoding process also starts with creating a decoder + * state. This can be done with: + * @code + * int error; + * OpusDecoder *dec; + * dec = opus_decoder_create(Fs, channels, &error); + * @endcode + * where + * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 + * @li channels is the number of channels (1 or 2) + * @li error will hold the error code in case of failure (or #OPUS_OK on success) + * @li the return value is a newly created decoder state to be used for decoding + * + * While opus_decoder_create() allocates memory for the state, it's also possible + * to initialize pre-allocated memory: + * @code + * int size; + * int error; + * OpusDecoder *dec; + * size = opus_decoder_get_size(channels); + * dec = malloc(size); + * error = opus_decoder_init(dec, Fs, channels); + * @endcode + * where opus_decoder_get_size() returns the required size for the decoder state. Note that + * future versions of this code may change the size, so no assumptions should be made about it. + * + * The decoder state is always continuous in memory and only a shallow copy is sufficient + * to copy it (e.g. memcpy()) + * + * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: + * @code + * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); + * @endcode + * where + * + * @li packet is the byte array containing the compressed data + * @li len is the exact number of bytes contained in the packet + * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) + * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array + * + * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. + * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio + * buffer is too small to hold the decoded audio. + * + * Opus is a stateful codec with overlapping blocks and as a result Opus + * packets are not coded independently of each other. Packets must be + * passed into the decoder serially and in the correct order for a correct + * decode. Lost packets can be replaced with loss concealment by calling + * the decoder with a null pointer and zero length for the missing packet. + * + * A single codec state may only be accessed from a single thread at + * a time and any required locking must be performed by the caller. Separate + * streams must be decoded with separate decoder states and can be decoded + * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK + * defined. + * + */ + +/** Opus decoder state. + * This contains the complete state of an Opus decoder. + * It is position independent and can be freely copied. + * @see opus_decoder_create,opus_decoder_init + */ +typedef struct OpusDecoder OpusDecoder; + +/** Opus DRED decoder. + * This contains the complete state of an Opus DRED decoder. + * It is position independent and can be freely copied. + * @see opus_dred_decoder_create,opus_dred_decoder_init + */ +typedef struct OpusDREDDecoder OpusDREDDecoder; + + +/** Opus DRED state. + * This contains the complete state of an Opus DRED packet. + * It is position independent and can be freely copied. + * @see opus_dred_create,opus_dred_init + */ +typedef struct OpusDRED OpusDRED; + +/** Gets the size of an OpusDecoder structure. + * @param [in] channels int: Number of channels. + * This must be 1 or 2. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); + +/** Allocates and initializes a decoder state. + * @param [in] Fs opus_int32: Sample rate to decode at (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + * + * Internally Opus stores data at 48000 Hz, so that should be the default + * value for Fs. However, the decoder can efficiently decode to buffers + * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use + * data at the full sample rate, or knows the compressed data doesn't + * use the full frequency range, it can request decoding at a reduced + * rate. Likewise, the decoder is capable of filling in either mono or + * interleaved stereo pcm buffers, at the caller's request. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( + opus_int32 Fs, + int channels, + int *error +); + +/** Initializes a previously allocated decoder state. + * The state must be at least the size returned by opus_decoder_get_size(). + * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @param [in] st OpusDecoder*: Decoder state. + * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param [in] channels int: Number of channels (1 or 2) to decode + * @retval #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_init( + OpusDecoder *st, + opus_int32 Fs, + int channels +) OPUS_ARG_NONNULL(1); + +/** Decode an Opus packet. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload* + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available, the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an Opus packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len opus_int32: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in \a pcm. + * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will + * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), + * then frame_size needs to be exactly the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and + * FEC cases, frame_size must be a multiple of 2.5 ms. + * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be + * decoded. If no such data is available the frame is decoded as if it were lost. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( + OpusDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param st OpusDecoder*: Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusDecoder allocated by opus_decoder_create(). + * @param[in] st OpusDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); + +/** Gets the size of an OpusDREDDecoder structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_decoder_get_size(void); + +/** Allocates and initializes an OpusDREDDecoder state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDREDDecoder *opus_dred_decoder_create(int *error); + +/** Initializes an OpusDREDDecoder state. + * @param[in] dec OpusDREDDecoder*: State to be initialized. + */ +OPUS_EXPORT int opus_dred_decoder_init(OpusDREDDecoder *dec); + +/** Frees an OpusDREDDecoder allocated by opus_dred_decoder_create(). + * @param[in] dec OpusDREDDecoder*: State to be freed. + */ +OPUS_EXPORT void opus_dred_decoder_destroy(OpusDREDDecoder *dec); + +/** Perform a CTL function on an Opus DRED decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @param dred_dec OpusDREDDecoder*: DRED Decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls or + * @ref opus_decoderctls. + * @see opus_genericctls + * @see opus_decoderctls + */ +OPUS_EXPORT int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...); + +/** Gets the size of an OpusDRED structure. + * @returns The size in bytes. + */ +OPUS_EXPORT int opus_dred_get_size(void); + +/** Allocates and initializes a DRED state. + * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_EXPORT OpusDRED *opus_dred_alloc(int *error); + +/** Frees an OpusDRED allocated by opus_dred_create(). + * @param[in] dec OpusDRED*: State to be freed. + */ +OPUS_EXPORT void opus_dred_free(OpusDRED *dec); + +/** Decode an Opus DRED packet. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] data char*: Input payload + * @param [in] len opus_int32: Number of bytes in payload + * @param [in] max_dred_samples opus_int32: Maximum number of DRED samples that may be needed (if available in the packet). + * @param [in] sampling_rate opus_int32: Sampling rate used for max_dred_samples argument. Needs not match the actual sampling rate of the decoder. + * @param [out] dred_end opus_int32*: Number of non-encoded (silence) samples between the DRED timestamp and the last DRED sample. + * @param [in] defer_processing int: Flag (0 or 1). If set to one, the CPU-intensive part of the DRED decoding is deferred until opus_dred_process() is called. + * @returns Offset (positive) of the first decoded DRED samples, zero if no DRED is present, or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) OPUS_ARG_NONNULL(1); + +/** Finish decoding an Opus DRED packet. The function only needs to be called if opus_dred_parse() was called with defer_processing=1. + * The source and destination will often be the same DRED state. + * @param [in] dred_dec OpusDRED*: DRED Decoder state + * @param [in] src OpusDRED*: Source DRED state to start the processing from. + * @param [out] dst OpusDRED*: Destination DRED state to store the updated state after processing. + * @returns @ref opus_errorcodes + */ +OPUS_EXPORT int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size); + +/** Decode audio from an Opus DRED packet with floating point output. + * @param [in] st OpusDecoder*: Decoder state + * @param [in] dred OpusDRED*: DRED state + * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel to decode in \a pcm. + * frame_size must be a multiple of 2.5 ms. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_EXPORT int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size); + + +/** Parse an opus packet into one or more frames. + * Opus_decode will perform this operation internally so most applications do + * not need to use this function. + * This function does not copy the frames, the returned pointers are pointers into + * the input packet. + * @param [in] data char*: Opus packet to be parsed + * @param [in] len opus_int32: size of data + * @param [out] out_toc char*: TOC pointer + * @param [out] frames char*[48] encapsulated frames + * @param [out] size opus_int16[48] sizes of the encapsulated frames + * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) + * @returns number of frames + */ +OPUS_EXPORT int opus_packet_parse( + const unsigned char *data, + opus_int32 len, + unsigned char *out_toc, + const unsigned char *frames[48], + opus_int16 size[48], + int *payload_offset +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); + +/** Gets the bandwidth of an Opus packet. + * @param [in] data char*: Opus packet + * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) + * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) + * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) + * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) + * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples per frame from an Opus packet. + * @param [in] data char*: Opus packet. + * This must contain at least one byte of + * data. + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples per frame. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Gets the number of channels from an Opus packet. + * @param [in] data char*: Opus packet + * @returns Number of channels + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); + +/** Gets the number of frames in an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of frames + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); + +/** Gets the number of samples of an Opus packet. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @param [in] Fs opus_int32: Sampling rate in Hz. + * This must be a multiple of 400, or + * inaccurate results will be returned. + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); + +/** Checks whether an Opus packet has LBRR. + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns 1 is LBRR is present, 0 otherwise + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len); + +/** Gets the number of samples of an Opus packet. + * @param [in] dec OpusDecoder*: Decoder state + * @param [in] packet char*: Opus packet + * @param [in] len opus_int32: Length of packet + * @returns Number of samples + * @retval OPUS_BAD_ARG Insufficient data was passed to the function + * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +/** Applies soft-clipping to bring a float signal within the [-1,1] range. If + * the signal is already in that range, nothing is done. If there are values + * outside of [-1,1], then the signal is clipped as smoothly as possible to + * both fit in the range and avoid creating excessive distortion in the + * process. + * @param [in,out] pcm float*: Input PCM and modified PCM + * @param [in] frame_size int Number of samples per channel to process + * @param [in] channels int: Number of channels + * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) + */ +OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); + + +/**@}*/ + +/** @defgroup opus_repacketizer Repacketizer + * @{ + * + * The repacketizer can be used to merge multiple Opus packets into a single + * packet or alternatively to split Opus packets that have previously been + * merged. Splitting valid Opus packets is always guaranteed to succeed, + * whereas merging valid packets only succeeds if all frames have the same + * mode, bandwidth, and frame size, and when the total duration of the merged + * packet is no more than 120 ms. The 120 ms limit comes from the + * specification and limits decoder memory requirements at a point where + * framing overhead becomes negligible. + * + * The repacketizer currently only operates on elementary Opus + * streams. It will not manipualte multistream packets successfully, except in + * the degenerate case where they consist of data from a single stream. + * + * The repacketizing process starts with creating a repacketizer state, either + * by calling opus_repacketizer_create() or by allocating the memory yourself, + * e.g., + * @code + * OpusRepacketizer *rp; + * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); + * if (rp != NULL) + * opus_repacketizer_init(rp); + * @endcode + * + * Then the application should submit packets with opus_repacketizer_cat(), + * extract new packets with opus_repacketizer_out() or + * opus_repacketizer_out_range(), and then reset the state for the next set of + * input packets via opus_repacketizer_init(). + * + * For example, to split a sequence of packets into individual frames: + * @code + * unsigned char *data; + * int len; + * while (get_next_packet(&data, &len)) + * { + * unsigned char out[1276]; + * opus_int32 out_len; + * int nb_frames; + * int err; + * int i; + * err = opus_repacketizer_cat(rp, data, len); + * if (err != OPUS_OK) + * { + * release_packet(data); + * return err; + * } + * nb_frames = opus_repacketizer_get_nb_frames(rp); + * for (i = 0; i < nb_frames; i++) + * { + * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packet(data); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * } + * opus_repacketizer_init(rp); + * release_packet(data); + * } + * @endcode + * + * Alternatively, to combine a sequence of frames into packets that each + * contain up to TARGET_DURATION_MS milliseconds of data: + * @code + * // The maximum number of packets with duration TARGET_DURATION_MS occurs + * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) + * // packets. + * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; + * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; + * int nb_packets; + * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; + * opus_int32 out_len; + * int prev_toc; + * nb_packets = 0; + * while (get_next_packet(data+nb_packets, len+nb_packets)) + * { + * int nb_frames; + * int err; + * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); + * if (nb_frames < 1) + * { + * release_packets(data, nb_packets+1); + * return nb_frames; + * } + * nb_frames += opus_repacketizer_get_nb_frames(rp); + * // If adding the next packet would exceed our target, or it has an + * // incompatible TOC sequence, output the packets we already have before + * // submitting it. + * // N.B., The nb_packets > 0 check ensures we've submitted at least one + * // packet since the last call to opus_repacketizer_init(). Otherwise a + * // single packet longer than TARGET_DURATION_MS would cause us to try to + * // output an (invalid) empty packet. It also ensures that prev_toc has + * // been set to a valid value. Additionally, len[nb_packets] > 0 is + * // guaranteed by the call to opus_packet_get_nb_frames() above, so the + * // reference to data[nb_packets][0] should be valid. + * if (nb_packets > 0 && ( + * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || + * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > + * TARGET_DURATION_MS*48)) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * if (out_len < 0) + * { + * release_packets(data, nb_packets+1); + * return (int)out_len; + * } + * output_next_packet(out, out_len); + * opus_repacketizer_init(rp); + * release_packets(data, nb_packets); + * data[0] = data[nb_packets]; + * len[0] = len[nb_packets]; + * nb_packets = 0; + * } + * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); + * if (err != OPUS_OK) + * { + * release_packets(data, nb_packets+1); + * return err; + * } + * prev_toc = data[nb_packets][0]; + * nb_packets++; + * } + * // Output the final, partial packet. + * if (nb_packets > 0) + * { + * out_len = opus_repacketizer_out(rp, out, sizeof(out)); + * release_packets(data, nb_packets); + * if (out_len < 0) + * return (int)out_len; + * output_next_packet(out, out_len); + * } + * @endcode + * + * An alternate way of merging packets is to simply call opus_repacketizer_cat() + * unconditionally until it fails. At that point, the merged packet can be + * obtained with opus_repacketizer_out() and the input packet for which + * opus_repacketizer_cat() needs to be re-added to a newly reinitialized + * repacketizer state. + */ + +typedef struct OpusRepacketizer OpusRepacketizer; + +/** Gets the size of an OpusRepacketizer structure. + * @returns The size in bytes. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); + +/** (Re)initializes a previously allocated repacketizer state. + * The state must be at least the size returned by opus_repacketizer_get_size(). + * This can be used for applications which use their own allocator instead of + * malloc(). + * It must also be called to reset the queue of packets waiting to be + * repacketized, which is necessary if the maximum packet duration of 120 ms + * is reached or if you wish to submit packets with a different Opus + * configuration (coding mode, audio bandwidth, frame size, or channel count). + * Failure to do so will prevent a new packet from being added with + * opus_repacketizer_cat(). + * @see opus_repacketizer_create + * @see opus_repacketizer_get_size + * @see opus_repacketizer_cat + * @param rp OpusRepacketizer*: The repacketizer state to + * (re)initialize. + * @returns A pointer to the same repacketizer state that was passed in. + */ +OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Allocates memory and initializes the new repacketizer with + * opus_repacketizer_init(). + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); + +/** Frees an OpusRepacketizer allocated by + * opus_repacketizer_create(). + * @param[in] rp OpusRepacketizer*: State to be freed. + */ +OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); + +/** Add a packet to the current repacketizer state. + * This packet must match the configuration of any packets already submitted + * for repacketization since the last call to opus_repacketizer_init(). + * This means that it must have the same coding mode, audio bandwidth, frame + * size, and channel count. + * This can be checked in advance by examining the top 6 bits of the first + * byte of the packet, and ensuring they match the top 6 bits of the first + * byte of any previously submitted packet. + * The total duration of audio in the repacketizer state also must not exceed + * 120 ms, the maximum duration of a single packet, after adding this packet. + * + * The contents of the current repacketizer state can be extracted into new + * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). + * + * In order to add a packet with a different configuration or to add more + * audio beyond 120 ms, you must clear the repacketizer state by calling + * opus_repacketizer_init(). + * If a packet is too large to add to the current repacketizer state, no part + * of it is added, even if it contains multiple frames, some of which might + * fit. + * If you wish to be able to add parts of such packets, you should first use + * another repacketizer to split the packet into pieces and add them + * individually. + * @see opus_repacketizer_out_range + * @see opus_repacketizer_out + * @see opus_repacketizer_init + * @param rp OpusRepacketizer*: The repacketizer state to which to + * add the packet. + * @param[in] data const unsigned char*: The packet data. + * The application must ensure + * this pointer remains valid + * until the next call to + * opus_repacketizer_init() or + * opus_repacketizer_destroy(). + * @param len opus_int32: The number of bytes in the packet data. + * @returns An error code indicating whether or not the operation succeeded. + * @retval #OPUS_OK The packet's contents have been added to the repacketizer + * state. + * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, + * the packet's TOC sequence was not compatible + * with previously submitted packets (because + * the coding mode, audio bandwidth, frame size, + * or channel count did not match), or adding + * this packet would increase the total amount of + * audio stored in the repacketizer state to more + * than 120 ms. + */ +OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param begin int: The index of the first frame in the current + * repacketizer state to include in the output. + * @param end int: One past the index of the last frame in the + * current repacketizer state to include in the + * output. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1276 for a single frame, + * or for multiple frames, + * 1277*(end-begin). + * However, 1*(end-begin) plus + * the size of all packet data submitted to + * the repacketizer since the last call to + * opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of + * frames (begin < 0, begin >= end, or end > + * opus_repacketizer_get_nb_frames()). + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Return the total number of frames contained in packet data submitted to + * the repacketizer state so far via opus_repacketizer_cat() since the last + * call to opus_repacketizer_init() or opus_repacketizer_create(). + * This defines the valid range of packets that can be extracted with + * opus_repacketizer_out_range() or opus_repacketizer_out(). + * @param rp OpusRepacketizer*: The repacketizer state containing the + * frames. + * @returns The total number of frames contained in the packet data submitted + * to the repacketizer state. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); + +/** Construct a new packet from data previously submitted to the repacketizer + * state via opus_repacketizer_cat(). + * This is a convenience routine that returns all the data submitted so far + * in a single packet. + * It is equivalent to calling + * @code + * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), + * data, maxlen) + * @endcode + * @param rp OpusRepacketizer*: The repacketizer state from which to + * construct the new packet. + * @param[out] data const unsigned char*: The buffer in which to + * store the output packet. + * @param maxlen opus_int32: The maximum number of bytes to store in + * the output buffer. In order to guarantee + * success, this should be at least + * 1277*opus_repacketizer_get_nb_frames(rp). + * However, + * 1*opus_repacketizer_get_nb_frames(rp) + * plus the size of all packet data + * submitted to the repacketizer since the + * last call to opus_repacketizer_init() or + * opus_repacketizer_create() is also + * sufficient, and possibly much smaller. + * @returns The total size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the + * complete output packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); + +/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); + +/** Remove all padding from a given Opus packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); + +/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to pad. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param new_len opus_int32: The desired size of the packet after padding. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least as large as len. + * @returns an error code + * @retval #OPUS_OK \a on success. + * @retval #OPUS_BAD_ARG \a len was less than 1. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); + +/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to + * minimize space usage. + * @param[in,out] data const unsigned char*: The buffer containing the + * packet to strip. + * @param len opus_int32: The size of the packet. + * This must be at least 1. + * @param nb_streams opus_int32: The number of streams (not channels) in the packet. + * This must be at least 1. + * @returns The new size of the output packet on success, or an error code + * on failure. + * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. + * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_custom.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_custom.h new file mode 100644 index 0000000000..2f22d4b35a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_custom.h @@ -0,0 +1,343 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Copyright (c) 2008-2012 Gregory Maxwell + Written by Jean-Marc Valin and Gregory Maxwell */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + @file opus_custom.h + @brief Opus-Custom reference implementation API + */ + +#ifndef OPUS_CUSTOM_H +#define OPUS_CUSTOM_H + +#include "opus_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CUSTOM_MODES +# define OPUS_CUSTOM_EXPORT OPUS_EXPORT +# define OPUS_CUSTOM_EXPORT_STATIC OPUS_EXPORT +#else +# define OPUS_CUSTOM_EXPORT +# ifdef OPUS_BUILD +# define OPUS_CUSTOM_EXPORT_STATIC static OPUS_INLINE +# else +# define OPUS_CUSTOM_EXPORT_STATIC +# endif +#endif + +/** @defgroup opus_custom Opus Custom + * @{ + * Opus Custom is an optional part of the Opus specification and + * reference implementation which uses a distinct API from the regular + * API and supports frame sizes that are not normally supported.\ Use + * of Opus Custom is discouraged for all but very special applications + * for which a frame size different from 2.5, 5, 10, or 20 ms is needed + * (for either complexity or latency reasons) and where interoperability + * is less important. + * + * In addition to the interoperability limitations the use of Opus custom + * disables a substantial chunk of the codec and generally lowers the + * quality available at a given bitrate. Normally when an application needs + * a different frame size from the codec it should buffer to match the + * sizes but this adds a small amount of delay which may be important + * in some very low latency applications. Some transports (especially + * constant rate RF transports) may also work best with frames of + * particular durations. + * + * Libopus only supports custom modes if they are enabled at compile time. + * + * The Opus Custom API is similar to the regular API but the + * @ref opus_encoder_create and @ref opus_decoder_create calls take + * an additional mode parameter which is a structure produced by + * a call to @ref opus_custom_mode_create. Both the encoder and decoder + * must create a mode using the same sample rate (fs) and frame size + * (frame size) so these parameters must either be signaled out of band + * or fixed in a particular implementation. + * + * Similar to regular Opus the custom modes support on the fly frame size + * switching, but the sizes available depend on the particular frame size in + * use. For some initial frame sizes on a single on the fly size is available. + */ + +/** Contains the state of an encoder. One encoder state is needed + for each stream. It is initialized once at the beginning of the + stream. Do *not* re-initialize the state for every frame. + @brief Encoder state + */ +typedef struct OpusCustomEncoder OpusCustomEncoder; + +/** State of the decoder. One decoder state is needed for each stream. + It is initialized once at the beginning of the stream. Do *not* + re-initialize the state for every frame. + @brief Decoder state + */ +typedef struct OpusCustomDecoder OpusCustomDecoder; + +/** The mode contains all the information necessary to create an + encoder. Both the encoder and decoder need to be initialized + with exactly the same mode, otherwise the output will be + corrupted. The mode MUST NOT BE DESTROYED until the encoders and + decoders that use it are destroyed as well. + @brief Mode configuration + */ +typedef struct OpusCustomMode OpusCustomMode; + +/** Creates a new mode struct. This will be passed to an encoder or + * decoder. The mode MUST NOT BE DESTROYED until the encoders and + * decoders that use it are destroyed as well. + * @param [in] Fs int: Sampling rate (8000 to 96000 Hz) + * @param [in] frame_size int: Number of samples (per channel) to encode in each + * packet (64 - 1024, prime factorization must contain zero or more 2s, 3s, or 5s and no other primes) + * @param [out] error int*: Returned error code (if NULL, no error will be returned) + * @return A newly created mode + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error); + +/** Destroys a mode struct. Only call this after all encoders and + * decoders using this mode are destroyed as well. + * @param [in] mode OpusCustomMode*: Mode to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_mode_destroy(OpusCustomMode *mode); + + +#if !defined(OPUS_BUILD) || defined(CELT_ENCODER_C) + +/* Encoder */ +/** Gets the size of an OpusCustomEncoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_encoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +# ifdef CUSTOM_MODES +/** Initializes a previously allocated encoder state + * The memory pointed to by st must be the size returned by opus_custom_encoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_encoder_create(),opus_custom_encoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_init( + OpusCustomEncoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); +# endif +#endif + + +/** Creates a new encoder state. Each stream needs its own encoder + * state (can't be shared across simultaneous streams). + * @param [in] mode OpusCustomMode*: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * decoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created encoder state. +*/ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomEncoder *opus_custom_encoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + + +/** Destroys an encoder state. + * @param[in] st OpusCustomEncoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_encoder_destroy(OpusCustomEncoder *st); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm float*: PCM audio in float format, with a normal range of +/-1.0. + * Samples with a range beyond +/-1.0 are supported but will + * be clipped by decoders using the integer API and should + * only be used if it is known that the far end supports + * extended dynamic range. There must be exactly + * frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode_float( + OpusCustomEncoder *st, + const float *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a frame of audio. + * @param [in] st OpusCustomEncoder*: Encoder state + * @param [in] pcm opus_int16*: PCM audio in signed 16-bit format (native endian). + * There must be exactly frame_size samples per channel. + * @param [in] frame_size int: Number of samples per frame of input signal + * @param [out] compressed char *: The compressed data is written here. This may not alias pcm and must be at least maxCompressedBytes long. + * @param [in] maxCompressedBytes int: Maximum number of bytes to use for compressing the frame + * (can change from one frame to another) + * @return Number of bytes written to "compressed". + * If negative, an error has occurred (see error codes). It is IMPORTANT that + * the length returned be somehow transmitted to the decoder. Otherwise, no + * decoding is possible. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_encode( + OpusCustomEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *compressed, + int maxCompressedBytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom encoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_encoderctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_encoder_ctl(OpusCustomEncoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + + +#if !defined(OPUS_BUILD) || defined(CELT_DECODER_C) +/* Decoder */ + +/** Gets the size of an OpusCustomDecoder structure. + * @param [in] mode OpusCustomMode *: Mode configuration + * @param [in] channels int: Number of channels + * @returns size + */ +OPUS_CUSTOM_EXPORT_STATIC OPUS_WARN_UNUSED_RESULT int opus_custom_decoder_get_size( + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1); + +/** Initializes a previously allocated decoder state + * The memory pointed to by st must be the size returned by opus_custom_decoder_get_size. + * This is intended for applications which use their own allocator instead of malloc. + * @see opus_custom_decoder_create(),opus_custom_decoder_get_size() + * To reset a previously initialized state use the OPUS_RESET_STATE CTL. + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] mode OpusCustomMode *: Contains all the information about the characteristics of + * the stream (must be the same characteristics as used for the + * encoder) + * @param [in] channels int: Number of channels + * @return OPUS_OK Success or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT_STATIC int opus_custom_decoder_init( + OpusCustomDecoder *st, + const OpusCustomMode *mode, + int channels +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); + +#endif + + +/** Creates a new decoder state. Each stream needs its own decoder state (can't + * be shared across simultaneous streams). + * @param [in] mode OpusCustomMode: Contains all the information about the characteristics of the + * stream (must be the same characteristics as used for the encoder) + * @param [in] channels int: Number of channels + * @param [out] error int*: Returns an error code + * @return Newly created decoder state. + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT OpusCustomDecoder *opus_custom_decoder_create( + const OpusCustomMode *mode, + int channels, + int *error +) OPUS_ARG_NONNULL(1); + +/** Destroys a decoder state. + * @param[in] st OpusCustomDecoder*: State to be freed. + */ +OPUS_CUSTOM_EXPORT void opus_custom_decoder_destroy(OpusCustomDecoder *st); + +/** Decode an opus custom frame with floating point output + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm float*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(float) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode_float( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + float *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode an opus custom frame + * @param [in] st OpusCustomDecoder*: Decoder state + * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss + * @param [in] len int: Number of bytes in payload + * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length + * is frame_size*channels*sizeof(opus_int16) + * @param [in] frame_size Number of samples per channel of available space in *pcm. + * @returns Number of decoded samples or @ref opus_errorcodes + */ +OPUS_CUSTOM_EXPORT OPUS_WARN_UNUSED_RESULT int opus_custom_decode( + OpusCustomDecoder *st, + const unsigned char *data, + int len, + opus_int16 *pcm, + int frame_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on an Opus custom decoder. + * + * Generally the request and subsequent arguments are generated + * by a convenience macro. + * @see opus_genericctls + */ +OPUS_CUSTOM_EXPORT int opus_custom_decoder_ctl(OpusCustomDecoder * OPUS_RESTRICT st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_CUSTOM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_defines.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_defines.h new file mode 100644 index 0000000000..cd8f4dde86 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_defines.h @@ -0,0 +1,830 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_defines.h + * @brief Opus reference implementation constants + */ + +#ifndef OPUS_DEFINES_H +#define OPUS_DEFINES_H + +#include "opus_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup opus_errorcodes Error codes + * @{ + */ +/** No error @hideinitializer*/ +#define OPUS_OK 0 +/** One or more invalid/out of range arguments @hideinitializer*/ +#define OPUS_BAD_ARG -1 +/** Not enough bytes allocated in the buffer @hideinitializer*/ +#define OPUS_BUFFER_TOO_SMALL -2 +/** An internal error was detected @hideinitializer*/ +#define OPUS_INTERNAL_ERROR -3 +/** The compressed data passed is corrupted @hideinitializer*/ +#define OPUS_INVALID_PACKET -4 +/** Invalid/unsupported request number @hideinitializer*/ +#define OPUS_UNIMPLEMENTED -5 +/** An encoder or decoder structure is invalid or already freed @hideinitializer*/ +#define OPUS_INVALID_STATE -6 +/** Memory allocation has failed @hideinitializer*/ +#define OPUS_ALLOC_FAIL -7 +/**@}*/ + +/** @cond OPUS_INTERNAL_DOC */ +/**Export control for opus functions */ + +#ifndef OPUS_EXPORT +# if defined(_WIN32) +# if defined(OPUS_BUILD) && defined(DLL_EXPORT) +# define OPUS_EXPORT __declspec(dllexport) +# else +# define OPUS_EXPORT +# endif +# elif defined(__GNUC__) && defined(OPUS_BUILD) +# define OPUS_EXPORT __attribute__ ((visibility ("default"))) +# else +# define OPUS_EXPORT +# endif +#endif + +# if !defined(OPUS_GNUC_PREREQ) +# if defined(__GNUC__)&&defined(__GNUC_MINOR__) +# define OPUS_GNUC_PREREQ(_maj,_min) \ + ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) +# else +# define OPUS_GNUC_PREREQ(_maj,_min) 0 +# endif +# endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(3,0) +# define OPUS_RESTRICT __restrict__ +# elif (defined(_MSC_VER) && _MSC_VER >= 1400) +# define OPUS_RESTRICT __restrict +# else +# define OPUS_RESTRICT +# endif +#else +# define OPUS_RESTRICT restrict +#endif + +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) +# if OPUS_GNUC_PREREQ(2,7) +# define OPUS_INLINE __inline__ +# elif (defined(_MSC_VER)) +# define OPUS_INLINE __inline +# else +# define OPUS_INLINE +# endif +#else +# define OPUS_INLINE inline +#endif + +/**Warning attributes for opus functions + * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out + * some paranoid null checks. */ +#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) +#else +# define OPUS_WARN_UNUSED_RESULT +#endif +#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) +# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) +#else +# define OPUS_ARG_NONNULL(_x) +#endif + +/** These are the actual Encoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +#define OPUS_SET_APPLICATION_REQUEST 4000 +#define OPUS_GET_APPLICATION_REQUEST 4001 +#define OPUS_SET_BITRATE_REQUEST 4002 +#define OPUS_GET_BITRATE_REQUEST 4003 +#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 +#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 +#define OPUS_SET_VBR_REQUEST 4006 +#define OPUS_GET_VBR_REQUEST 4007 +#define OPUS_SET_BANDWIDTH_REQUEST 4008 +#define OPUS_GET_BANDWIDTH_REQUEST 4009 +#define OPUS_SET_COMPLEXITY_REQUEST 4010 +#define OPUS_GET_COMPLEXITY_REQUEST 4011 +#define OPUS_SET_INBAND_FEC_REQUEST 4012 +#define OPUS_GET_INBAND_FEC_REQUEST 4013 +#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 +#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 +#define OPUS_SET_DTX_REQUEST 4016 +#define OPUS_GET_DTX_REQUEST 4017 +#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 +#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 +#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 +#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 +#define OPUS_SET_SIGNAL_REQUEST 4024 +#define OPUS_GET_SIGNAL_REQUEST 4025 +#define OPUS_GET_LOOKAHEAD_REQUEST 4027 +/* #define OPUS_RESET_STATE 4028 */ +#define OPUS_GET_SAMPLE_RATE_REQUEST 4029 +#define OPUS_GET_FINAL_RANGE_REQUEST 4031 +#define OPUS_GET_PITCH_REQUEST 4033 +#define OPUS_SET_GAIN_REQUEST 4034 +#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ +#define OPUS_SET_LSB_DEPTH_REQUEST 4036 +#define OPUS_GET_LSB_DEPTH_REQUEST 4037 +#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 +#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 +#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 +#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 +#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 +/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ +#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 +#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 +#define OPUS_GET_IN_DTX_REQUEST 4049 +#define OPUS_SET_DRED_DURATION_REQUEST 4050 +#define OPUS_GET_DRED_DURATION_REQUEST 4051 +#define OPUS_SET_DNN_BLOB_REQUEST 4052 +/*#define OPUS_GET_DNN_BLOB_REQUEST 4053 */ + +/** Defines for the presence of extended APIs. */ +#define OPUS_HAVE_OPUS_PROJECTION_H + +/* Macros to trigger compilation errors when the wrong types are provided to a CTL */ +#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) + +#ifdef DISABLE_PTR_CHECK +/* Disable checks to prevent ubsan from complaining about NULL checks + in test_opus_api. */ +#define __opus_check_int_ptr(ptr) (ptr) +#define __opus_check_uint_ptr(ptr) (ptr) +#define __opus_check_uint8_ptr(ptr) (ptr) +#define __opus_check_val16_ptr(ptr) (ptr) +#define __opus_check_void_ptr(ptr) (ptr) +#else +#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) +#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) +#define __opus_check_uint8_ptr(ptr) ((ptr) + ((ptr) - (opus_uint8*)(ptr))) +#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) +#define __opus_check_void_ptr(x) ((void)((void *)0 == (x)), (x)) +#endif +/** @endcond */ + +/** @defgroup opus_ctlvalues Pre-defined values for CTL interface + * @see opus_genericctls, opus_encoderctls + * @{ + */ +/* Values for the various encoder CTLs */ +#define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. + * + * @hideinitializer */ +#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) +/** Gets the encoder's complexity configuration. + * @see OPUS_SET_COMPLEXITY + * @param[out] x opus_int32 *: Returns a value in the range 0-10, + * inclusive. + * @hideinitializer */ +#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) + +/** Configures the bitrate in the encoder. + * Rates from 500 to 512000 bits per second are meaningful, as well as the + * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. + * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much + * rate as it can, which is useful for controlling the rate by adjusting the + * output buffer size. + * @see OPUS_GET_BITRATE + * @param[in] x opus_int32: Bitrate in bits per second. The default + * is determined based on the number of + * channels and the input sampling rate. + * @hideinitializer */ +#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) +/** Gets the encoder's bitrate configuration. + * @see OPUS_SET_BITRATE + * @param[out] x opus_int32 *: Returns the bitrate in bits per second. + * The default is determined based on the + * number of channels and the input + * sampling rate. + * @hideinitializer */ +#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables variable bitrate (VBR) in the encoder. + * The configured bitrate may not be met exactly because frames must + * be an integer number of bytes in length. + * @see OPUS_GET_VBR + * @see OPUS_SET_VBR_CONSTRAINT + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can + * cause noticeable quality degradation.
+ *
1
VBR (default). The exact type of VBR is controlled by + * #OPUS_SET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) +/** Determine if variable bitrate (VBR) is enabled in the encoder. + * @see OPUS_SET_VBR + * @see OPUS_GET_VBR_CONSTRAINT + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Hard CBR.
+ *
1
VBR (default). The exact type of VBR may be retrieved via + * #OPUS_GET_VBR_CONSTRAINT.
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) + +/** Enables or disables constrained VBR in the encoder. + * This setting is ignored when the encoder is in CBR mode. + * @warning Only the MDCT mode of Opus currently heeds the constraint. + * Speech mode ignores it completely, hybrid mode may fail to obey it + * if the LPC layer uses more bitrate than the constraint would have + * permitted. + * @see OPUS_GET_VBR_CONSTRAINT + * @see OPUS_SET_VBR + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default). This creates a maximum of one + * frame of buffering delay assuming a transport with a + * serialization speed of the nominal bitrate.
+ *
+ * @hideinitializer */ +#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) +/** Determine if constrained VBR is enabled in the encoder. + * @see OPUS_SET_VBR_CONSTRAINT + * @see OPUS_GET_VBR + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Unconstrained VBR.
+ *
1
Constrained VBR (default).
+ *
+ * @hideinitializer */ +#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) + +/** Configures mono/stereo forcing in the encoder. + * This can force the encoder to produce packets encoded as either mono or + * stereo, regardless of the format of the input audio. This is useful when + * the caller knows that the input signal is currently a mono source embedded + * in a stereo stream. + * @see OPUS_GET_FORCE_CHANNELS + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) +/** Gets the encoder's forced channel configuration. + * @see OPUS_SET_FORCE_CHANNELS + * @param[out] x opus_int32 *: + *
+ *
#OPUS_AUTO
Not forced (default)
+ *
1
Forced mono
+ *
2
Forced stereo
+ *
+ * @hideinitializer */ +#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) + +/** Configures the maximum bandpass that the encoder will select automatically. + * Applications should normally use this instead of #OPUS_SET_BANDWIDTH + * (leaving that set to the default, #OPUS_AUTO). This allows the + * application to set an upper bound based on the type of input it is + * providing, but still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_MAX_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Gets the encoder's configured maximum allowed bandpass. + * @see OPUS_SET_MAX_BANDWIDTH + * @param[out] x opus_int32 *: Allowed values: + *
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
+ *
+ * @hideinitializer */ +#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Sets the encoder's bandpass to a specific value. + * This prevents the encoder from automatically selecting the bandpass based + * on the available bitrate. If an application knows the bandpass of the input + * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH + * instead, which still gives the encoder the freedom to reduce the bandpass + * when the bitrate becomes too low, for better overall quality. + * @see OPUS_GET_BANDWIDTH + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) + +/** Configures the type of signal being encoded. + * This is a hint which helps the encoder's mode selection. + * @see OPUS_GET_SIGNAL + * @param[in] x opus_int32: Allowed values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal type. + * @see OPUS_SET_SIGNAL + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
+ *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
+ *
+ * @hideinitializer */ +#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) + + +/** Configures the encoder's intended application. + * The initial value is a mandatory argument to the encoder_create function. + * @see OPUS_GET_APPLICATION + * @param[in] x opus_int32: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured application. + * @see OPUS_SET_APPLICATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @hideinitializer */ +#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the total samples of delay added by the entire codec. + * This can be queried by the encoder and then the provided number of samples can be + * skipped on from the start of the decoder's output to provide time aligned input + * and output. From the perspective of a decoding application the real data begins this many + * samples late. + * + * The decoder contribution to this delay is identical for all decoders, but the + * encoder portion of the delay may vary from implementation to implementation, + * version to version, or even depend on the encoder's initial configuration. + * Applications needing delay compensation should call this CTL rather than + * hard-coding a value. + * @param[out] x opus_int32 *: Number of lookahead samples + * @hideinitializer */ +#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of inband forward error correction (FEC). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_INBAND_FEC + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable inband FEC (default).
+ *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
+ *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
+ *
+ * @hideinitializer */ +#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of inband forward error correction. + * @see OPUS_SET_INBAND_FEC + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Inband FEC disabled (default).
+ *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
+ *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
+ *
+ * @hideinitializer */ +#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's expected packet loss percentage. + * Higher values trigger progressively more loss resistant behavior in the encoder + * at the expense of quality at a given bitrate in the absence of packet loss, but + * greater quality under loss. + * @see OPUS_GET_PACKET_LOSS_PERC + * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured packet loss percentage. + * @see OPUS_SET_PACKET_LOSS_PERC + * @param[out] x opus_int32 *: Returns the configured loss percentage + * in the range 0-100, inclusive (default: 0). + * @hideinitializer */ +#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of discontinuous transmission (DTX). + * @note This is only applicable to the LPC layer + * @see OPUS_GET_DTX + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Disable DTX (default).
+ *
1
Enabled DTX.
+ *
+ * @hideinitializer */ +#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) +/** Gets encoder's configured use of discontinuous transmission. + * @see OPUS_SET_DTX + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
DTX disabled (default).
+ *
1
DTX enabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) +/** Configures the depth of signal being encoded. + * + * This is a hint which helps the encoder identify silence and near-silence. + * It represents the number of significant bits of linear intensity below + * which the signal contains ignorable quantization or other noise. + * + * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting + * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate + * for 16-bit linear pcm input with opus_encode_float(). + * + * When using opus_encode() instead of opus_encode_float(), or when libopus + * is compiled for fixed-point, the encoder uses the minimum of the value + * set here and the value 16. + * + * @see OPUS_GET_LSB_DEPTH + * @param[in] x opus_int32: Input precision in bits, between 8 and 24 + * (default: 24). + * @hideinitializer */ +#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured signal depth. + * @see OPUS_SET_LSB_DEPTH + * @param[out] x opus_int32 *: Input precision in bits, between 8 and + * 24 (default: 24). + * @hideinitializer */ +#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) + +/** Configures the encoder's use of variable duration frames. + * When variable duration is enabled, the encoder is free to use a shorter frame + * size than the one requested in the opus_encode*() call. + * It is then the user's responsibility + * to verify how much audio was encoded by checking the ToC byte of the encoded + * packet. The part of the audio that was not encoded needs to be resent to the + * encoder for the next call. Do not use this option unless you really + * know what you are doing. + * @see OPUS_GET_EXPERT_FRAME_DURATION + * @param[in] x opus_int32: Allowed values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured use of variable duration frames. + * @see OPUS_SET_EXPERT_FRAME_DURATION + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
+ *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
+ *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
+ *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
+ *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
+ *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
+ *
+ * @hideinitializer */ +#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables almost all use of prediction, making frames almost + * completely independent. This reduces quality. + * @see OPUS_GET_PREDICTION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured prediction status. + * @see OPUS_SET_PREDICTION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) + +/** If non-zero, enables Deep Redundancy (DRED) and use the specified maximum number of 10-ms redundant frames + * @hideinitializer */ +#define OPUS_SET_DRED_DURATION(x) OPUS_SET_DRED_DURATION_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured Deep Redundancy (DRED) maximum number of frames. + * @hideinitializer */ +#define OPUS_GET_DRED_DURATION(x) OPUS_GET_DRED_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Provide external DNN weights from binary object (only when explicitly built without the weights) + * @hideinitializer */ +#define OPUS_SET_DNN_BLOB(data, len) OPUS_SET_DNN_BLOB_REQUEST, __opus_check_void_ptr(data), __opus_check_int(len) + + +/**@}*/ + +/** @defgroup opus_genericctls Generic CTLs + * + * These macros are used with the \c opus_decoder_ctl and + * \c opus_encoder_ctl calls to generate a particular + * request. + * + * When called on an \c OpusDecoder they apply to that + * particular decoder instance. When called on an + * \c OpusEncoder they apply to the corresponding setting + * on that encoder instance, if present. + * + * Some usage examples: + * + * @code + * int ret; + * opus_int32 pitch; + * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); + * if (ret == OPUS_OK) return ret; + * + * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); + * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); + * + * opus_int32 enc_bw, dec_bw; + * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); + * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); + * if (enc_bw != dec_bw) { + * printf("packet bandwidth mismatch!\n"); + * } + * @endcode + * + * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls + * @{ + */ + +/** Resets the codec state to be equivalent to a freshly initialized state. + * This should be called when switching streams in order to prevent + * the back to back decoding from giving different results from + * one at a time decoding. + * @hideinitializer */ +#define OPUS_RESET_STATE 4028 + +/** Gets the final state of the codec's entropy coder. + * This is used for testing purposes, + * The encoder and decoder state should be identical after coding a payload + * (assuming no data corruption or software bugs) + * + * @param[out] x opus_uint32 *: Entropy coder state + * + * @hideinitializer */ +#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) + +/** Gets the encoder's configured bandpass or the decoder's last bandpass. + * @see OPUS_SET_BANDWIDTH + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
#OPUS_AUTO
(default)
+ *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
+ *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
+ *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
+ *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
+ *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
+ *
+ * @hideinitializer */ +#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) + +/** Gets the sampling rate the encoder or decoder was initialized with. + * This simply returns the Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. + * @hideinitializer + */ +#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables the use of phase inversion for intensity stereo, + * improving the quality of mono downmixes, but slightly reducing normal + * stereo quality. Disabling phase inversion in the decoder does not comply + * with RFC 6716, although it does not cause any interoperability issue and + * is expected to become part of the Opus standard once RFC 6716 is updated + * by draft-ietf-codec-opus-update. + * @see OPUS_GET_PHASE_INVERSION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable phase inversion (default).
+ *
1
Disable phase inversion.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured phase inversion status. + * @see OPUS_SET_PHASE_INVERSION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Stereo phase inversion enabled (default).
+ *
1
Stereo phase inversion disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) +/** Gets the DTX state of the encoder. + * Returns whether the last encoded frame was either a comfort noise update + * during DTX or not encoded because of DTX. + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
The encoder is not in DTX.
+ *
1
The encoder is in DTX.
+ *
+ * @hideinitializer */ +#define OPUS_GET_IN_DTX(x) OPUS_GET_IN_DTX_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_decoderctls Decoder related CTLs + * @see opus_genericctls, opus_encoderctls, opus_decoder + * @{ + */ + +/** Configures decoder gain adjustment. + * Scales the decoded output by a factor specified in Q8 dB units. + * This has a maximum range of -32768 to 32767 inclusive, and returns + * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. + * This setting survives decoder reset. + * + * gain = pow(10, x/(20.0*256)) + * + * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) +/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN + * + * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. + * @hideinitializer */ +#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) + +/** Gets the duration (in samples) of the last packet successfully decoded or concealed. + * @param[out] x opus_int32 *: Number of samples (at current sampling rate). + * @hideinitializer */ +#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the pitch of the last decoded frame, if available. + * This can be used for any post-processing algorithm requiring the use of pitch, + * e.g. time stretching/shortening. If the last frame was not voiced, or if the + * pitch was not coded in the frame, then zero is returned. + * + * This CTL is only implemented for decoder instances. + * + * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) + * + * @hideinitializer */ +#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) + +/**@}*/ + +/** @defgroup opus_libinfo Opus library information functions + * @{ + */ + +/** Converts an opus error code into a human readable string. + * + * @param[in] error int: Error number + * @returns Error string + */ +OPUS_EXPORT const char *opus_strerror(int error); + +/** Gets the libopus version string. + * + * Applications may look for the substring "-fixed" in the version string to + * determine whether they have a fixed-point or floating-point build at + * runtime. + * + * @returns Version string + */ +OPUS_EXPORT const char *opus_get_version_string(void); +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_DEFINES_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_multistream.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_multistream.h new file mode 100644 index 0000000000..824cc55ac5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_multistream.h @@ -0,0 +1,660 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_multistream.h + * @brief Opus reference implementation multistream API + */ + +#ifndef OPUS_MULTISTREAM_H +#define OPUS_MULTISTREAM_H + +#include "opus.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** Macros to trigger compilation errors when the wrong types are provided to a + * CTL. */ +/**@{*/ +#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) +#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) +/**@}*/ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications. + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 +#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 +/**@}*/ + +/** @endcond */ + +/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and + * @ref opus_decoderctls may be applied to a multistream encoder or decoder as + * well. + * In addition, you may retrieve the encoder or decoder state for an specific + * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or + * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. + */ +/**@{*/ + +/** Gets the encoder state for an individual stream of a multistream encoder. + * @param[in] x opus_int32: The index of the stream whose encoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the encoder. + * @param[out] y OpusEncoder**: Returns a pointer to the given + * encoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) + +/** Gets the decoder state for an individual stream of a multistream decoder. + * @param[in] x opus_int32: The index of the stream whose decoder you + * wish to retrieve. + * This must be non-negative and less than + * the streams parameter used + * to initialize the decoder. + * @param[out] y OpusDecoder**: Returns a pointer to the given + * decoder state. + * @retval OPUS_BAD_ARG The index of the requested stream was out of range. + * @hideinitializer + */ +#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) + +/**@}*/ + +/** @defgroup opus_multistream Opus Multistream API + * @{ + * + * The multistream API allows individual Opus streams to be combined into a + * single packet, enabling support for up to 255 channels. Unlike an + * elementary Opus stream, the encoder and decoder must negotiate the channel + * configuration before the decoder can successfully interpret the data in the + * packets produced by the encoder. Some basic information, such as packet + * duration, can be computed without any special negotiation. + * + * The format for multistream Opus packets is defined in + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. + * Normal Opus packets are just a degenerate case of multistream Opus packets, + * and can be encoded or decoded with the multistream API by setting + * streams to 1 when initializing the encoder or + * decoder. + * + * Multistream Opus streams can contain up to 255 elementary Opus streams. + * These may be either "uncoupled" or "coupled", indicating that the decoder + * is configured to decode them to either 1 or 2 channels, respectively. + * The streams are ordered so that all coupled streams appear at the + * beginning. + * + * A mapping table defines which decoded channel i + * should be used for each input/output (I/O) channel j. This table is + * typically provided as an unsigned char array. + * Let i = mapping[j] be the index for I/O channel j. + * If i < 2*coupled_streams, then I/O channel j is + * encoded as the left channel of stream (i/2) if i + * is even, or as the right channel of stream (i/2) if + * i is odd. Otherwise, I/O channel j is encoded as + * mono in stream (i - coupled_streams), unless it has the special + * value 255, in which case it is omitted from the encoding entirely (the + * decoder will reproduce it as silence). Each value i must either + * be the special value 255 or be less than streams + coupled_streams. + * + * The output channels specified by the encoder + * should use the + * Vorbis + * channel ordering. A decoder may wish to apply an additional permutation + * to the mapping the encoder used to achieve a different output channel + * order (e.g. for outputting in WAV order). + * + * Each multistream packet contains an Opus packet for each stream, and all of + * the Opus packets in a single multistream packet must have the same + * duration. Therefore the duration of a multistream packet can be extracted + * from the TOC sequence of the first stream, which is located at the + * beginning of the packet, just like an elementary Opus stream: + * + * @code + * int nb_samples; + * int nb_frames; + * nb_frames = opus_packet_get_nb_frames(data, len); + * if (nb_frames < 1) + * return nb_frames; + * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; + * @endcode + * + * The general encoding and decoding process proceeds exactly the same as in + * the normal @ref opus_encoder and @ref opus_decoder APIs. + * See their documentation for an overview of how to use the corresponding + * multistream functions. + */ + +/** Opus multistream encoder state. + * This contains the complete state of a multistream Opus encoder. + * It is position independent and can be freely copied. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_init + */ +typedef struct OpusMSEncoder OpusMSEncoder; + +/** Opus multistream decoder state. + * This contains the complete state of a multistream Opus decoder. + * It is position independent and can be freely copied. + * @see opus_multistream_decoder_create + * @see opus_multistream_decoder_init + */ +typedef struct OpusMSDecoder OpusMSDecoder; + +/**\name Multistream encoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSEncoder structure. + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( + int streams, + int coupled_streams +); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a multistream encoder state. + * Call opus_multistream_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(5); + +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + +/** Initialize a previously allocated multistream encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_encoder_create + * @see opus_multistream_encoder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param[in] mapping const unsigned char[channels]: Mapping from + * encoded channels to input channels, as described in + * @ref opus_multistream. As an extra constraint, the + * multistream encoder does not allow encoding coupled + * streams for which one channel is unused since this + * is never a good idea. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +OPUS_EXPORT int opus_multistream_surround_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); + +/** Encodes a multistream Opus frame. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( + OpusMSEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Encodes a multistream Opus frame from floating point input. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( + OpusMSEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + +/** Frees an OpusMSEncoder allocated by + * opus_multistream_encoder_create(). + * @param st OpusMSEncoder*: Multistream encoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); + +/** Perform a CTL function on a multistream Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSEncoder*: Multistream encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/**@}*/ + +/**\name Multistream decoder functions */ +/**@{*/ + +/** Gets the size of an OpusMSDecoder structure. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( + int streams, + int coupled_streams +); + +/** Allocates and initializes a multistream decoder state. + * Call opus_multistream_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int *error +) OPUS_ARG_NONNULL(5); + +/** Intialize a previously allocated decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_multistream_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_multistream_decoder_create + * @see opus_multistream_deocder_get_size + * @param st OpusMSEncoder*: Multistream encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] mapping const unsigned char[channels]: Mapping from + * coded channels to output channels, as described in + * @ref opus_multistream. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_multistream_decoder_init( + OpusMSDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + +/** Decode a multistream Opus packet. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Decode a multistream Opus packet with floating point output. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( + OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + +/** Perform a CTL function on a multistream Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusMSDecoder*: Multistream decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, or @ref opus_multistream_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + */ +OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + +/** Frees an OpusMSDecoder allocated by + * opus_multistream_decoder_create(). + * @param st OpusMSDecoder: Multistream decoder state to be freed. + */ +OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_MULTISTREAM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_projection.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_projection.h new file mode 100644 index 0000000000..9dabf4e85c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_projection.h @@ -0,0 +1,568 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file opus_projection.h + * @brief Opus projection reference API + */ + +#ifndef OPUS_PROJECTION_H +#define OPUS_PROJECTION_H + +#include "opus_multistream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @cond OPUS_INTERNAL_DOC */ + +/** These are the actual encoder and decoder CTL ID numbers. + * They should not be used directly by applications.c + * In general, SETs should be even and GETs should be odd.*/ +/**@{*/ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003 +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005 +/**@}*/ + + +/** @endcond */ + +/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs + * + * These are convenience macros that are specific to the + * opus_projection_encoder_ctl() and opus_projection_decoder_ctl() + * interface. + * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, + * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a + * projection encoder or decoder as well. + */ +/**@{*/ + +/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the gain (in dB. S7.8-format) + * of the demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x) + + +/** Gets the size in bytes of the demixing matrix from the encoder. + * @param[out] x opus_int32 *: Returns the size in bytes of the + * demixing matrix. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x) + + +/** Copies the demixing matrix to the supplied pointer location. + * @param[out] x unsigned char *: Returns the demixing matrix to the + * supplied pointer location. + * @param y opus_int32: The size in bytes of the reserved memory at the + * pointer location. + * @hideinitializer + */ +#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y) + + +/**@}*/ + +/** Opus projection encoder state. + * This contains the complete state of a projection Opus encoder. + * It is position independent and can be freely copied. + * @see opus_projection_ambisonics_encoder_create + */ +typedef struct OpusProjectionEncoder OpusProjectionEncoder; + + +/** Opus projection decoder state. + * This contains the complete state of a projection Opus decoder. + * It is position independent and can be freely copied. + * @see opus_projection_decoder_create + * @see opus_projection_decoder_init + */ +typedef struct OpusProjectionDecoder OpusProjectionDecoder; + + +/**\name Projection encoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionEncoder structure. + * @param channels int: The total number of input channels to encode. + * This must be no more than 255. + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size( + int channels, + int mapping_family +); + + +/** Allocates and initializes a projection encoder state. + * Call opus_projection_encoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param mapping_family int: The mapping family to use for selecting + * the appropriate projection. + * @param[out] streams int *: The total number of streams that will + * be encoded from the input. + * @param[out] coupled_streams int *: Number of coupled (2 channel) + * streams that will be encoded from the input. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application, + int *error +) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5); + + +/** Initialize a previously allocated projection encoder state. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_ambisonics_encoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_ambisonics_encoder_create + * @see opus_projection_ambisonics_encoder_get_size + * @param st OpusProjectionEncoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate of the input signal (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels in the input signal. + * This must be at most 255. + * It may be greater than the number of + * coded channels (streams + + * coupled_streams). + * @param streams int: The total number of streams to encode from the + * input. + * This must be no more than the number of channels. + * @param coupled_streams int: Number of coupled (2 channel) streams + * to encode. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * encoded channels (streams + + * coupled_streams) must be no + * more than the number of input channels. + * @param application int: The target encoder application. + * This must be one of the following: + *
+ *
#OPUS_APPLICATION_VOIP
+ *
Process signal for improved speech intelligibility.
+ *
#OPUS_APPLICATION_AUDIO
+ *
Favor faithfulness to the original input.
+ *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
+ *
Configure the minimum possible coding delay by disabling certain modes + * of operation.
+ *
+ * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_ambisonics_encoder_init( + OpusProjectionEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + int application +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); + + +/** Encodes a projection Opus frame. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const opus_int16*: The input signal as interleaved + * samples. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode( + OpusProjectionEncoder *st, + const opus_int16 *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Encodes a projection Opus frame from floating point input. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param[in] pcm const float*: The input signal as interleaved + * samples with a normal range of + * +/-1.0. + * Samples with a range beyond +/-1.0 + * are supported but will be clipped by + * decoders using the integer API and + * should only be used if it is known + * that the far end supports extended + * dynamic range. + * This must contain + * frame_size*channels + * samples. + * @param frame_size int: Number of samples per channel in the input + * signal. + * This must be an Opus frame size for the + * encoder's sampling rate. + * For example, at 48 kHz the permitted values + * are 120, 240, 480, 960, 1920, and 2880. + * Passing in a duration of less than 10 ms + * (480 samples at 48 kHz) will prevent the + * encoder from using the LPC or hybrid modes. + * @param[out] data unsigned char*: Output payload. + * This must contain storage for at + * least \a max_data_bytes. + * @param [in] max_data_bytes opus_int32: Size of the allocated + * memory for the output + * payload. This may be + * used to impose an upper limit on + * the instant bitrate, but should + * not be used as the only bitrate + * control. Use #OPUS_SET_BITRATE to + * control the bitrate. + * @returns The length of the encoded packet (in bytes) on success or a + * negative error code (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float( + OpusProjectionEncoder *st, + const float *pcm, + int frame_size, + unsigned char *data, + opus_int32 max_data_bytes +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); + + +/** Frees an OpusProjectionEncoder allocated by + * opus_projection_ambisonics_encoder_create(). + * @param st OpusProjectionEncoder*: Projection encoder state to be freed. + */ +OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st); + + +/** Perform a CTL function on a projection Opus encoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionEncoder*: Projection encoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_encoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls + * @see opus_genericctls + * @see opus_encoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/**@}*/ + +/**\name Projection decoder functions */ +/**@{*/ + +/** Gets the size of an OpusProjectionDecoder structure. + * @param channels int: The total number of output channels. + * This must be no more than 255. + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @returns The size in bytes on success, or a negative error code + * (see @ref opus_errorcodes) on error. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size( + int channels, + int streams, + int coupled_streams +); + + +/** Allocates and initializes a projection decoder state. + * Call opus_projection_decoder_destroy() to release + * this object when finished. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @param[out] error int *: Returns #OPUS_OK on success, or an error + * code (see @ref opus_errorcodes) on + * failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size, + int *error +) OPUS_ARG_NONNULL(5); + + +/** Intialize a previously allocated projection decoder state object. + * The memory pointed to by \a st must be at least the size returned by + * opus_projection_decoder_get_size(). + * This is intended for applications which use their own allocator instead of + * malloc. + * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. + * @see opus_projection_decoder_create + * @see opus_projection_deocder_get_size + * @param st OpusProjectionDecoder*: Projection encoder state to initialize. + * @param Fs opus_int32: Sampling rate to decode at (in Hz). + * This must be one of 8000, 12000, 16000, + * 24000, or 48000. + * @param channels int: Number of channels to output. + * This must be at most 255. + * It may be different from the number of coded + * channels (streams + + * coupled_streams). + * @param streams int: The total number of streams coded in the + * input. + * This must be no more than 255. + * @param coupled_streams int: Number of streams to decode as coupled + * (2 channel) streams. + * This must be no larger than the total + * number of streams. + * Additionally, The total number of + * coded channels (streams + + * coupled_streams) must be no + * more than 255. + * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix + * that mapping from coded channels to output channels, + * as described in @ref opus_projection and + * @ref opus_projection_ctls. + * @param demixing_matrix_size opus_int32: The size in bytes of the + * demixing matrix, as + * described in @ref + * opus_projection_ctls. + * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) + * on failure. + */ +OPUS_EXPORT int opus_projection_decoder_init( + OpusProjectionDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + unsigned char *demixing_matrix, + opus_int32 demixing_matrix_size +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); + + +/** Decode a projection Opus packet. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + opus_int16 *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Decode a projection Opus packet with floating point output. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param[in] data const unsigned char*: Input payload. + * Use a NULL + * pointer to indicate packet + * loss. + * @param len opus_int32: Number of bytes in payload. + * @param[out] pcm opus_int16*: Output signal, with interleaved + * samples. + * This must contain room for + * frame_size*channels + * samples. + * @param frame_size int: The number of samples per channel of + * available space in \a pcm. + * If this is less than the maximum packet duration + * (120 ms; 5760 for 48kHz), this function will not be capable + * of decoding some packets. In the case of PLC (data==NULL) + * or FEC (decode_fec=1), then frame_size needs to be exactly + * the duration of audio that is missing, otherwise the + * decoder will not be in the optimal state to decode the + * next incoming packet. For the PLC and FEC cases, frame_size + * must be a multiple of 2.5 ms. + * @param decode_fec int: Flag (0 or 1) to request that any in-band + * forward error correction data be decoded. + * If no such data is available, the frame is + * decoded as if it were lost. + * @returns Number of samples decoded on success or a negative error code + * (see @ref opus_errorcodes) on failure. + */ +OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float( + OpusProjectionDecoder *st, + const unsigned char *data, + opus_int32 len, + float *pcm, + int frame_size, + int decode_fec +) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); + + +/** Perform a CTL function on a projection Opus decoder. + * + * Generally the request and subsequent arguments are generated by a + * convenience macro. + * @param st OpusProjectionDecoder*: Projection decoder state. + * @param request This and all remaining parameters should be replaced by one + * of the convenience macros in @ref opus_genericctls, + * @ref opus_decoderctls, @ref opus_multistream_ctls, or + * @ref opus_projection_ctls. + * @see opus_genericctls + * @see opus_decoderctls + * @see opus_multistream_ctls + * @see opus_projection_ctls + */ +OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); + + +/** Frees an OpusProjectionDecoder allocated by + * opus_projection_decoder_create(). + * @param st OpusProjectionDecoder: Projection decoder state to be freed. + */ +OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st); + + +/**@}*/ + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* OPUS_PROJECTION_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_types.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_types.h new file mode 100644 index 0000000000..7cf675580f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/include/opus_types.h @@ -0,0 +1,166 @@ +/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ +/* Modified by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* opus_types.h based on ogg_types.h from libogg */ + +/** + @file opus_types.h + @brief Opus reference implementation types +*/ +#ifndef OPUS_TYPES_H +#define OPUS_TYPES_H + +#define opus_int int /* used for counters etc; at least 16 bits */ +#define opus_int64 long long +#define opus_int8 signed char + +#define opus_uint unsigned int /* used for counters etc; at least 16 bits */ +#define opus_uint64 unsigned long long +#define opus_uint8 unsigned char + +/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) +#include +# undef opus_int64 +# undef opus_int8 +# undef opus_uint64 +# undef opus_uint8 + typedef int8_t opus_int8; + typedef uint8_t opus_uint8; + typedef int16_t opus_int16; + typedef uint16_t opus_uint16; + typedef int32_t opus_int32; + typedef uint32_t opus_uint32; + typedef int64_t opus_int64; + typedef uint64_t opus_uint64; +#elif defined(_WIN32) + +# if defined(__CYGWIN__) +# include <_G_config.h> + typedef _G_int32_t opus_int32; + typedef _G_uint32_t opus_uint32; + typedef _G_int16 opus_int16; + typedef _G_uint16 opus_uint16; +# elif defined(__MINGW32__) + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; +# elif defined(__MWERKS__) + typedef int opus_int32; + typedef unsigned int opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; +# else + /* MSVC/Borland */ + typedef __int32 opus_int32; + typedef unsigned __int32 opus_uint32; + typedef __int16 opus_int16; + typedef unsigned __int16 opus_uint16; +# endif + +#elif defined(__MACOS__) + +# include + typedef SInt16 opus_int16; + typedef UInt16 opus_uint16; + typedef SInt32 opus_int32; + typedef UInt32 opus_uint32; + +#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ + +# include + typedef int16_t opus_int16; + typedef u_int16_t opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined(__BEOS__) + + /* Be */ +# include + typedef int16 opus_int16; + typedef u_int16 opus_uint16; + typedef int32_t opus_int32; + typedef u_int32_t opus_uint32; + +#elif defined (__EMX__) + + /* OS/2 GCC */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined (DJGPP) + + /* DJGPP */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(R5900) + + /* PS2 EE */ + typedef int opus_int32; + typedef unsigned opus_uint32; + typedef short opus_int16; + typedef unsigned short opus_uint16; + +#elif defined(__SYMBIAN32__) + + /* Symbian GCC */ + typedef signed short opus_int16; + typedef unsigned short opus_uint16; + typedef signed int opus_int32; + typedef unsigned int opus_uint32; + +#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef long opus_int32; + typedef unsigned long opus_uint32; + +#elif defined(CONFIG_TI_C6X) + + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#else + + /* Give up, take a reasonable guess */ + typedef short opus_int16; + typedef unsigned short opus_uint16; + typedef int opus_int32; + typedef unsigned int opus_uint32; + +#endif + +#endif /* OPUS_TYPES_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/A2NLSF.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/A2NLSF.c new file mode 100644 index 0000000000..b487686ff9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/A2NLSF.c @@ -0,0 +1,267 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +/* Conversion between prediction filter coefficients and NLSFs */ +/* Requires the order to be an even number */ +/* A piecewise linear approximation maps LSF <-> cos(LSF) */ +/* Therefore the result is not accurate NLSFs, but the two */ +/* functions are accurate inverses of each other */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "tables.h" + +/* Number of binary divisions, when not in low complexity mode */ +#define BIN_DIV_STEPS_A2NLSF_FIX 3 /* must be no higher than 16 - log2( LSF_COS_TAB_SZ_FIX ) */ +#define MAX_ITERATIONS_A2NLSF_FIX 16 + +/* Helper function for A2NLSF(..) */ +/* Transforms polynomials from cos(n*f) to cos(f)^n */ +static OPUS_INLINE void silk_A2NLSF_trans_poly( + opus_int32 *p, /* I/O Polynomial */ + const opus_int dd /* I Polynomial order (= filter order / 2 ) */ +) +{ + opus_int k, n; + + for( k = 2; k <= dd; k++ ) { + for( n = dd; n > k; n-- ) { + p[ n - 2 ] -= p[ n ]; + } + p[ k - 2 ] -= silk_LSHIFT( p[ k ], 1 ); + } +} +/* Helper function for A2NLSF(..) */ +/* Polynomial evaluation */ +static OPUS_INLINE opus_int32 silk_A2NLSF_eval_poly( /* return the polynomial evaluation, in Q16 */ + opus_int32 *p, /* I Polynomial, Q16 */ + const opus_int32 x, /* I Evaluation point, Q12 */ + const opus_int dd /* I Order */ +) +{ + opus_int n; + opus_int32 x_Q16, y32; + + y32 = p[ dd ]; /* Q16 */ + x_Q16 = silk_LSHIFT( x, 4 ); + + if ( opus_likely( 8 == dd ) ) + { + y32 = silk_SMLAWW( p[ 7 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 6 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 5 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 4 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 3 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 2 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 1 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 0 ], y32, x_Q16 ); + } + else + { + for( n = dd - 1; n >= 0; n-- ) { + y32 = silk_SMLAWW( p[ n ], y32, x_Q16 ); /* Q16 */ + } + } + return y32; +} + +static OPUS_INLINE void silk_A2NLSF_init( + const opus_int32 *a_Q16, + opus_int32 *P, + opus_int32 *Q, + const opus_int dd +) +{ + opus_int k; + + /* Convert filter coefs to even and odd polynomials */ + P[dd] = silk_LSHIFT( 1, 16 ); + Q[dd] = silk_LSHIFT( 1, 16 ); + for( k = 0; k < dd; k++ ) { + P[ k ] = -a_Q16[ dd - k - 1 ] - a_Q16[ dd + k ]; /* Q16 */ + Q[ k ] = -a_Q16[ dd - k - 1 ] + a_Q16[ dd + k ]; /* Q16 */ + } + + /* Divide out zeros as we have that for even filter orders, */ + /* z = 1 is always a root in Q, and */ + /* z = -1 is always a root in P */ + for( k = dd; k > 0; k-- ) { + P[ k - 1 ] -= P[ k ]; + Q[ k - 1 ] += Q[ k ]; + } + + /* Transform polynomials from cos(n*f) to cos(f)^n */ + silk_A2NLSF_trans_poly( P, dd ); + silk_A2NLSF_trans_poly( Q, dd ); +} + +/* Compute Normalized Line Spectral Frequencies (NLSFs) from whitening filter coefficients */ +/* If not all roots are found, the a_Q16 coefficients are bandwidth expanded until convergence. */ +void silk_A2NLSF( + opus_int16 *NLSF, /* O Normalized Line Spectral Frequencies in Q15 (0..2^15-1) [d] */ + opus_int32 *a_Q16, /* I/O Monic whitening filter coefficients in Q16 [d] */ + const opus_int d /* I Filter order (must be even) */ +) +{ + opus_int i, k, m, dd, root_ix, ffrac; + opus_int32 xlo, xhi, xmid; + opus_int32 ylo, yhi, ymid, thr; + opus_int32 nom, den; + opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 *PQ[ 2 ]; + opus_int32 *p; + + /* Store pointers to array */ + PQ[ 0 ] = P; + PQ[ 1 ] = Q; + + dd = silk_RSHIFT( d, 1 ); + + silk_A2NLSF_init( a_Q16, P, Q, dd ); + + /* Find roots, alternating between P and Q */ + p = P; /* Pointer to polynomial */ + + xlo = silk_LSFCosTab_FIX_Q12[ 0 ]; /* Q12*/ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + + if( ylo < 0 ) { + /* Set the first NLSF to zero and move on to the next */ + NLSF[ 0 ] = 0; + p = Q; /* Pointer to polynomial */ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + root_ix = 1; /* Index of current root */ + } else { + root_ix = 0; /* Index of current root */ + } + k = 1; /* Loop counter */ + i = 0; /* Counter for bandwidth expansions applied */ + thr = 0; + while( 1 ) { + /* Evaluate polynomial */ + xhi = silk_LSFCosTab_FIX_Q12[ k ]; /* Q12 */ + yhi = silk_A2NLSF_eval_poly( p, xhi, dd ); + + /* Detect zero crossing */ + if( ( ylo <= 0 && yhi >= thr ) || ( ylo >= 0 && yhi <= -thr ) ) { + if( yhi == 0 ) { + /* If the root lies exactly at the end of the current */ + /* interval, look for the next root in the next interval */ + thr = 1; + } else { + thr = 0; + } + /* Binary division */ + ffrac = -256; + for( m = 0; m < BIN_DIV_STEPS_A2NLSF_FIX; m++ ) { + /* Evaluate polynomial */ + xmid = silk_RSHIFT_ROUND( xlo + xhi, 1 ); + ymid = silk_A2NLSF_eval_poly( p, xmid, dd ); + + /* Detect zero crossing */ + if( ( ylo <= 0 && ymid >= 0 ) || ( ylo >= 0 && ymid <= 0 ) ) { + /* Reduce frequency */ + xhi = xmid; + yhi = ymid; + } else { + /* Increase frequency */ + xlo = xmid; + ylo = ymid; + ffrac = silk_ADD_RSHIFT( ffrac, 128, m ); + } + } + + /* Interpolate */ + if( silk_abs( ylo ) < 65536 ) { + /* Avoid dividing by zero */ + den = ylo - yhi; + nom = silk_LSHIFT( ylo, 8 - BIN_DIV_STEPS_A2NLSF_FIX ) + silk_RSHIFT( den, 1 ); + if( den != 0 ) { + ffrac += silk_DIV32( nom, den ); + } + } else { + /* No risk of dividing by zero because abs(ylo - yhi) >= abs(ylo) >= 65536 */ + ffrac += silk_DIV32( ylo, silk_RSHIFT( ylo - yhi, 8 - BIN_DIV_STEPS_A2NLSF_FIX ) ); + } + NLSF[ root_ix ] = (opus_int16)silk_min_32( silk_LSHIFT( (opus_int32)k, 8 ) + ffrac, silk_int16_MAX ); + + silk_assert( NLSF[ root_ix ] >= 0 ); + + root_ix++; /* Next root */ + if( root_ix >= d ) { + /* Found all roots */ + break; + } + /* Alternate pointer to polynomial */ + p = PQ[ root_ix & 1 ]; + + /* Evaluate polynomial */ + xlo = silk_LSFCosTab_FIX_Q12[ k - 1 ]; /* Q12*/ + ylo = silk_LSHIFT( 1 - ( root_ix & 2 ), 12 ); + } else { + /* Increment loop counter */ + k++; + xlo = xhi; + ylo = yhi; + thr = 0; + + if( k > LSF_COS_TAB_SZ_FIX ) { + i++; + if( i > MAX_ITERATIONS_A2NLSF_FIX ) { + /* Set NLSFs to white spectrum and exit */ + NLSF[ 0 ] = (opus_int16)silk_DIV32_16( 1 << 15, d + 1 ); + for( k = 1; k < d; k++ ) { + NLSF[ k ] = (opus_int16)silk_ADD16( NLSF[ k-1 ], NLSF[ 0 ] ); + } + return; + } + + /* Error: Apply progressively more bandwidth expansion and run again */ + silk_bwexpander_32( a_Q16, d, 65536 - silk_LSHIFT( 1, i ) ); + + silk_A2NLSF_init( a_Q16, P, Q, dd ); + p = P; /* Pointer to polynomial */ + xlo = silk_LSFCosTab_FIX_Q12[ 0 ]; /* Q12*/ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + if( ylo < 0 ) { + /* Set the first NLSF to zero and move on to the next */ + NLSF[ 0 ] = 0; + p = Q; /* Pointer to polynomial */ + ylo = silk_A2NLSF_eval_poly( p, xlo, dd ); + root_ix = 1; /* Index of current root */ + } else { + root_ix = 0; /* Index of current root */ + } + k = 1; /* Reset loop counter */ + } + } + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/API.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/API.h new file mode 100644 index 0000000000..878965c73a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/API.h @@ -0,0 +1,156 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_API_H +#define SILK_API_H + +#include "control.h" +#include "typedef.h" +#include "errors.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet_private.h" +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define SILK_MAX_FRAMES_PER_PACKET 3 + +/* Struct for TOC (Table of Contents) */ +typedef struct { + opus_int VADFlag; /* Voice activity for packet */ + opus_int VADFlags[ SILK_MAX_FRAMES_PER_PACKET ]; /* Voice activity for each frame in packet */ + opus_int inbandFECFlag; /* Flag indicating if packet contains in-band FEC */ +} silk_TOC_struct; + +/****************************************/ +/* Encoder functions */ +/****************************************/ + +/***********************************************/ +/* Get size in bytes of the Silk encoder state */ +/***********************************************/ +opus_int silk_Get_Encoder_Size( /* O Returns error code */ + opus_int *encSizeBytes /* O Number of bytes in SILK encoder state */ +); + +/*************************/ +/* Init or reset encoder */ +/*************************/ +opus_int silk_InitEncoder( /* O Returns error code */ + void *encState, /* I/O State */ + int arch, /* I Run-time architecture */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +); + +/**************************/ +/* Encode frame with Silk */ +/**************************/ +/* Note: if prefillFlag is set, the input must contain 10 ms of audio, irrespective of what */ +/* encControl->payloadSize_ms is set to */ +opus_int silk_Encode( /* O Returns error code */ + void *encState, /* I/O State */ + silk_EncControlStruct *encControl, /* I Control status */ + const opus_int16 *samplesIn, /* I Speech sample input vector */ + opus_int nSamplesIn, /* I Number of samples in input vector */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int32 *nBytesOut, /* I/O Number of bytes in payload (input: Max bytes) */ + const opus_int prefillFlag, /* I Flag to indicate prefilling buffers no coding */ + int activity /* I Decision of Opus voice activity detector */ +); + +/****************************************/ +/* Decoder functions */ +/****************************************/ + + +/***********************************************/ +/* Load OSCE models from external data pointer */ +/***********************************************/ +opus_int silk_LoadOSCEModels( + void *decState, /* O I/O State */ + const unsigned char *data, /* I pointer to binary blob */ + int len /* I length of binary blob data */ +); + +/***********************************************/ +/* Get size in bytes of the Silk decoder state */ +/***********************************************/ +opus_int silk_Get_Decoder_Size( /* O Returns error code */ + opus_int *decSizeBytes /* O Number of bytes in SILK decoder state */ +); + +/*************************/ +/* Init and Reset decoder */ +/*************************/ +opus_int silk_ResetDecoder( /* O Returns error code */ + void *decState /* I/O State */ +); + +opus_int silk_InitDecoder( /* O Returns error code */ + void *decState /* I/O State */ +); + +/******************/ +/* Decode a frame */ +/******************/ +opus_int silk_Decode( /* O Returns error code */ + void* decState, /* I/O State */ + silk_DecControlStruct* decControl, /* I/O Control Structure */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 *samplesOut, /* O Decoded output speech vector */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + +#if 0 +/**************************************/ +/* Get table of contents for a packet */ +/**************************************/ +opus_int silk_get_TOC( + const opus_uint8 *payload, /* I Payload data */ + const opus_int nBytesIn, /* I Number of input bytes */ + const opus_int nFramesPerPayload, /* I Number of SILK frames per payload */ + silk_TOC_struct *Silk_TOC /* O Type of content */ +); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/CNG.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/CNG.c new file mode 100644 index 0000000000..2a910099ed --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/CNG.c @@ -0,0 +1,188 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Generates excitation for CNG LPC synthesis */ +static OPUS_INLINE void silk_CNG_exc( + opus_int32 exc_Q14[], /* O CNG excitation signal Q10 */ + opus_int32 exc_buf_Q14[], /* I Random samples buffer Q10 */ + opus_int length, /* I Length */ + opus_int32 *rand_seed /* I/O Seed to random index generator */ +) +{ + opus_int32 seed; + opus_int i, idx, exc_mask; + + exc_mask = CNG_BUF_MASK_MAX; + while( exc_mask > length ) { + exc_mask = silk_RSHIFT( exc_mask, 1 ); + } + + seed = *rand_seed; + for( i = 0; i < length; i++ ) { + seed = silk_RAND( seed ); + idx = (opus_int)( silk_RSHIFT( seed, 24 ) & exc_mask ); + silk_assert( idx >= 0 ); + silk_assert( idx <= CNG_BUF_MASK_MAX ); + exc_Q14[ i ] = exc_buf_Q14[ idx ]; + } + *rand_seed = seed; +} + +void silk_CNG_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +) +{ + opus_int i, NLSF_step_Q15, NLSF_acc_Q15; + + NLSF_step_Q15 = silk_DIV32_16( silk_int16_MAX, psDec->LPC_order + 1 ); + NLSF_acc_Q15 = 0; + for( i = 0; i < psDec->LPC_order; i++ ) { + NLSF_acc_Q15 += NLSF_step_Q15; + psDec->sCNG.CNG_smth_NLSF_Q15[ i ] = NLSF_acc_Q15; + } + psDec->sCNG.CNG_smth_Gain_Q16 = 0; + psDec->sCNG.rand_seed = 3176576; +} + +/* Updates CNG estimate, and applies the CNG when packet was lost */ +void silk_CNG( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O Signal */ + opus_int length /* I Length of residual */ +) +{ + opus_int i, subfr; + opus_int32 LPC_pred_Q10, max_Gain_Q16, gain_Q16, gain_Q10; + opus_int16 A_Q12[ MAX_LPC_ORDER ]; + silk_CNG_struct *psCNG = &psDec->sCNG; + SAVE_STACK; + + if( psDec->fs_kHz != psCNG->fs_kHz ) { + /* Reset state */ + silk_CNG_Reset( psDec ); + + psCNG->fs_kHz = psDec->fs_kHz; + } + if( psDec->lossCnt == 0 && psDec->prevSignalType == TYPE_NO_VOICE_ACTIVITY ) { + /* Update CNG parameters */ + + /* Smoothing of LSF's */ + for( i = 0; i < psDec->LPC_order; i++ ) { + psCNG->CNG_smth_NLSF_Q15[ i ] += silk_SMULWB( (opus_int32)psDec->prevNLSF_Q15[ i ] - (opus_int32)psCNG->CNG_smth_NLSF_Q15[ i ], CNG_NLSF_SMTH_Q16 ); + } + /* Find the subframe with the highest gain */ + max_Gain_Q16 = 0; + subfr = 0; + for( i = 0; i < psDec->nb_subfr; i++ ) { + if( psDecCtrl->Gains_Q16[ i ] > max_Gain_Q16 ) { + max_Gain_Q16 = psDecCtrl->Gains_Q16[ i ]; + subfr = i; + } + } + /* Update CNG excitation buffer with excitation from this subframe */ + silk_memmove( &psCNG->CNG_exc_buf_Q14[ psDec->subfr_length ], psCNG->CNG_exc_buf_Q14, ( psDec->nb_subfr - 1 ) * psDec->subfr_length * sizeof( opus_int32 ) ); + silk_memcpy( psCNG->CNG_exc_buf_Q14, &psDec->exc_Q14[ subfr * psDec->subfr_length ], psDec->subfr_length * sizeof( opus_int32 ) ); + + /* Smooth gains */ + for( i = 0; i < psDec->nb_subfr; i++ ) { + psCNG->CNG_smth_Gain_Q16 += silk_SMULWB( psDecCtrl->Gains_Q16[ i ] - psCNG->CNG_smth_Gain_Q16, CNG_GAIN_SMTH_Q16 ); + /* If the smoothed gain is 3 dB greater than this subframe's gain, use this subframe's gain to adapt faster. */ + if( silk_SMULWW( psCNG->CNG_smth_Gain_Q16, CNG_GAIN_SMTH_THRESHOLD_Q16 ) > psDecCtrl->Gains_Q16[ i ] ) { + psCNG->CNG_smth_Gain_Q16 = psDecCtrl->Gains_Q16[ i ]; + } + } + } + + /* Add CNG when packet is lost or during DTX */ + if( psDec->lossCnt ) { + VARDECL( opus_int32, CNG_sig_Q14 ); + ALLOC( CNG_sig_Q14, length + MAX_LPC_ORDER, opus_int32 ); + + /* Generate CNG excitation */ + gain_Q16 = silk_SMULWW( psDec->sPLC.randScale_Q14, psDec->sPLC.prevGain_Q16[1] ); + if( gain_Q16 >= (1 << 21) || psCNG->CNG_smth_Gain_Q16 > (1 << 23) ) { + gain_Q16 = silk_SMULTT( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULTT( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 16 ); + } else { + gain_Q16 = silk_SMULWW( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULWW( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 8 ); + } + gain_Q10 = silk_RSHIFT( gain_Q16, 6 ); + + silk_CNG_exc( CNG_sig_Q14 + MAX_LPC_ORDER, psCNG->CNG_exc_buf_Q14, length, &psCNG->rand_seed ); + + /* Convert CNG NLSF to filter representation */ + silk_NLSF2A( A_Q12, psCNG->CNG_smth_NLSF_Q15, psDec->LPC_order, psDec->arch ); + + /* Generate CNG signal, by synthesis filtering */ + silk_memcpy( CNG_sig_Q14, psCNG->CNG_synth_state, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + celt_assert( psDec->LPC_order == 10 || psDec->LPC_order == 16 ); + for( i = 0; i < length; i++ ) { + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); + if( psDec->LPC_order == 16 ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 11 ], A_Q12[ 10 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 12 ], A_Q12[ 11 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 13 ], A_Q12[ 12 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 14 ], A_Q12[ 13 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 15 ], A_Q12[ 14 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 16 ], A_Q12[ 15 ] ); + } + + /* Update states */ + CNG_sig_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( CNG_sig_Q14[ MAX_LPC_ORDER + i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); + + /* Scale with Gain and add to input signal */ + frame[ i ] = (opus_int16)silk_ADD_SAT16( frame[ i ], silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( CNG_sig_Q14[ MAX_LPC_ORDER + i ], gain_Q10 ), 8 ) ) ); + + } + silk_memcpy( psCNG->CNG_synth_state, &CNG_sig_Q14[ length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + } else { + silk_memset( psCNG->CNG_synth_state, 0, psDec->LPC_order * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/HP_variable_cutoff.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/HP_variable_cutoff.c new file mode 100644 index 0000000000..bbe10f04ce --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/HP_variable_cutoff.c @@ -0,0 +1,77 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif +#include "tuning_parameters.h" + +/* High-pass filter with cutoff frequency adaptation based on pitch lag statistics */ +void silk_HP_variable_cutoff( + silk_encoder_state_Fxx state_Fxx[] /* I/O Encoder states */ +) +{ + opus_int quality_Q15; + opus_int32 pitch_freq_Hz_Q16, pitch_freq_log_Q7, delta_freq_Q7; + silk_encoder_state *psEncC1 = &state_Fxx[ 0 ].sCmn; + + /* Adaptive cutoff frequency: estimate low end of pitch frequency range */ + if( psEncC1->prevSignalType == TYPE_VOICED ) { + /* difference, in log domain */ + pitch_freq_Hz_Q16 = silk_DIV32_16( silk_LSHIFT( silk_MUL( psEncC1->fs_kHz, 1000 ), 16 ), psEncC1->prevLag ); + pitch_freq_log_Q7 = silk_lin2log( pitch_freq_Hz_Q16 ) - ( 16 << 7 ); + + /* adjustment based on quality */ + quality_Q15 = psEncC1->input_quality_bands_Q15[ 0 ]; + pitch_freq_log_Q7 = silk_SMLAWB( pitch_freq_log_Q7, silk_SMULWB( silk_LSHIFT( -quality_Q15, 2 ), quality_Q15 ), + pitch_freq_log_Q7 - ( silk_lin2log( SILK_FIX_CONST( VARIABLE_HP_MIN_CUTOFF_HZ, 16 ) ) - ( 16 << 7 ) ) ); + + /* delta_freq = pitch_freq_log - psEnc->variable_HP_smth1; */ + delta_freq_Q7 = pitch_freq_log_Q7 - silk_RSHIFT( psEncC1->variable_HP_smth1_Q15, 8 ); + if( delta_freq_Q7 < 0 ) { + /* less smoothing for decreasing pitch frequency, to track something close to the minimum */ + delta_freq_Q7 = silk_MUL( delta_freq_Q7, 3 ); + } + + /* limit delta, to reduce impact of outliers in pitch estimation */ + delta_freq_Q7 = silk_LIMIT_32( delta_freq_Q7, -SILK_FIX_CONST( VARIABLE_HP_MAX_DELTA_FREQ, 7 ), SILK_FIX_CONST( VARIABLE_HP_MAX_DELTA_FREQ, 7 ) ); + + /* update smoother */ + psEncC1->variable_HP_smth1_Q15 = silk_SMLAWB( psEncC1->variable_HP_smth1_Q15, + silk_SMULBB( psEncC1->speech_activity_Q8, delta_freq_Q7 ), SILK_FIX_CONST( VARIABLE_HP_SMTH_COEF1, 16 ) ); + + /* limit frequency range */ + psEncC1->variable_HP_smth1_Q15 = silk_LIMIT_32( psEncC1->variable_HP_smth1_Q15, + silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ), + silk_LSHIFT( silk_lin2log( VARIABLE_HP_MAX_CUTOFF_HZ ), 8 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/Inlines.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/Inlines.h new file mode 100644 index 0000000000..ec986cdfdd --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/Inlines.h @@ -0,0 +1,188 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +/*! \file silk_Inlines.h + * \brief silk_Inlines.h defines OPUS_INLINE signal processing functions. + */ + +#ifndef SILK_FIX_INLINES_H +#define SILK_FIX_INLINES_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* count leading zeros of opus_int64 */ +static OPUS_INLINE opus_int32 silk_CLZ64( opus_int64 in ) +{ + opus_int32 in_upper; + + in_upper = (opus_int32)silk_RSHIFT64(in, 32); + if (in_upper == 0) { + /* Search in the lower 32 bits */ + return 32 + silk_CLZ32( (opus_int32) in ); + } else { + /* Search in the upper 32 bits */ + return silk_CLZ32( in_upper ); + } +} + +/* get number of leading zeros and fractional part (the bits right after the leading one */ +static OPUS_INLINE void silk_CLZ_FRAC( + opus_int32 in, /* I input */ + opus_int32 *lz, /* O number of leading zeros */ + opus_int32 *frac_Q7 /* O the 7 bits right after the leading one */ +) +{ + opus_int32 lzeros = silk_CLZ32(in); + + * lz = lzeros; + * frac_Q7 = silk_ROR32(in, 24 - lzeros) & 0x7f; +} + +/* Approximation of square root */ +/* Accuracy: < +/- 10% for output values > 15 */ +/* < +/- 2.5% for output values > 120 */ +static OPUS_INLINE opus_int32 silk_SQRT_APPROX( opus_int32 x ) +{ + opus_int32 y, lz, frac_Q7; + + if( x <= 0 ) { + return 0; + } + + silk_CLZ_FRAC(x, &lz, &frac_Q7); + + if( lz & 1 ) { + y = 32768; + } else { + y = 46214; /* 46214 = sqrt(2) * 32768 */ + } + + /* get scaling right */ + y >>= silk_RSHIFT(lz, 1); + + /* increment using fractional part of input */ + y = silk_SMLAWB(y, y, silk_SMULBB(213, frac_Q7)); + + return y; +} + +/* Divide two int32 values and return result as int32 in a given Q-domain */ +static OPUS_INLINE opus_int32 silk_DIV32_varQ( /* O returns a good approximation of "(a32 << Qres) / b32" */ + const opus_int32 a32, /* I numerator (Q0) */ + const opus_int32 b32, /* I denominator (Q0) */ + const opus_int Qres /* I Q-domain of result (>= 0) */ +) +{ + opus_int a_headrm, b_headrm, lshift; + opus_int32 b32_inv, a32_nrm, b32_nrm, result; + + silk_assert( b32 != 0 ); + silk_assert( Qres >= 0 ); + + /* Compute number of bits head room and normalize inputs */ + a_headrm = silk_CLZ32( silk_abs(a32) ) - 1; + a32_nrm = silk_LSHIFT(a32, a_headrm); /* Q: a_headrm */ + b_headrm = silk_CLZ32( silk_abs(b32) ) - 1; + b32_nrm = silk_LSHIFT(b32, b_headrm); /* Q: b_headrm */ + + /* Inverse of b32, with 14 bits of precision */ + b32_inv = silk_DIV32_16( silk_int32_MAX >> 2, silk_RSHIFT(b32_nrm, 16) ); /* Q: 29 + 16 - b_headrm */ + + /* First approximation */ + result = silk_SMULWB(a32_nrm, b32_inv); /* Q: 29 + a_headrm - b_headrm */ + + /* Compute residual by subtracting product of denominator and first approximation */ + /* It's OK to overflow because the final value of a32_nrm should always be small */ + a32_nrm = silk_SUB32_ovflw(a32_nrm, silk_LSHIFT_ovflw( silk_SMMUL(b32_nrm, result), 3 )); /* Q: a_headrm */ + + /* Refinement */ + result = silk_SMLAWB(result, a32_nrm, b32_inv); /* Q: 29 + a_headrm - b_headrm */ + + /* Convert to Qres domain */ + lshift = 29 + a_headrm - b_headrm - Qres; + if( lshift < 0 ) { + return silk_LSHIFT_SAT32(result, -lshift); + } else { + if( lshift < 32){ + return silk_RSHIFT(result, lshift); + } else { + /* Avoid undefined result */ + return 0; + } + } +} + +/* Invert int32 value and return result as int32 in a given Q-domain */ +static OPUS_INLINE opus_int32 silk_INVERSE32_varQ( /* O returns a good approximation of "(1 << Qres) / b32" */ + const opus_int32 b32, /* I denominator (Q0) */ + const opus_int Qres /* I Q-domain of result (> 0) */ +) +{ + opus_int b_headrm, lshift; + opus_int32 b32_inv, b32_nrm, err_Q32, result; + + silk_assert( b32 != 0 ); + silk_assert( Qres > 0 ); + + /* Compute number of bits head room and normalize input */ + b_headrm = silk_CLZ32( silk_abs(b32) ) - 1; + b32_nrm = silk_LSHIFT(b32, b_headrm); /* Q: b_headrm */ + + /* Inverse of b32, with 14 bits of precision */ + b32_inv = silk_DIV32_16( silk_int32_MAX >> 2, silk_RSHIFT(b32_nrm, 16) ); /* Q: 29 + 16 - b_headrm */ + + /* First approximation */ + result = silk_LSHIFT(b32_inv, 16); /* Q: 61 - b_headrm */ + + /* Compute residual by subtracting product of denominator and first approximation from one */ + err_Q32 = silk_LSHIFT( ((opus_int32)1<<29) - silk_SMULWB(b32_nrm, b32_inv), 3 ); /* Q32 */ + + /* Refinement */ + result = silk_SMLAWW(result, err_Q32, b32_inv); /* Q: 61 - b_headrm */ + + /* Convert to Qres domain */ + lshift = 61 - b_headrm - Qres; + if( lshift <= 0 ) { + return silk_LSHIFT_SAT32(result, -lshift); + } else { + if( lshift < 32){ + return silk_RSHIFT(result, lshift); + }else{ + /* Avoid undefined result */ + return 0; + } + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_FIX_INLINES_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_analysis_filter.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_analysis_filter.c new file mode 100644 index 0000000000..d34b5eb709 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_analysis_filter.c @@ -0,0 +1,111 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "celt_lpc.h" + +/*******************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first d output samples are set to zero */ +/*******************************************/ + +/* OPT: Using celt_fir() for this function should be faster, but it may cause + integer overflows in intermediate values (not final results), which the + current implementation silences by casting to unsigned. Enabling + this should be safe in pretty much all cases, even though it is not technically + C89-compliant. */ +#define USE_CELT_FIR 0 + +void silk_LPC_analysis_filter( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ +) +{ + opus_int j; +#if defined(FIXED_POINT) && USE_CELT_FIR + opus_int16 num[SILK_MAX_ORDER_LPC]; +#else + int ix; + opus_int32 out32_Q12, out32; + const opus_int16 *in_ptr; +#endif + + celt_assert( d >= 6 ); + celt_assert( (d & 1) == 0 ); + celt_assert( d <= len ); + +#if defined(FIXED_POINT) && USE_CELT_FIR + celt_assert( d <= SILK_MAX_ORDER_LPC ); + for ( j = 0; j < d; j++ ) { + num[ j ] = -B[ j ]; + } + celt_fir( in + d, num, out + d, len - d, d, arch ); + for ( j = 0; j < d; j++ ) { + out[ j ] = 0; + } +#else + (void)arch; + for( ix = d; ix < len; ix++ ) { + in_ptr = &in[ ix - 1 ]; + + out32_Q12 = silk_SMULBB( in_ptr[ 0 ], B[ 0 ] ); + /* Allowing wrap around so that two wraps can cancel each other. The rare + cases where the result wraps around can only be triggered by invalid streams*/ + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -1 ], B[ 1 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -2 ], B[ 2 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -3 ], B[ 3 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -4 ], B[ 4 ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -5 ], B[ 5 ] ); + for( j = 6; j < d; j += 2 ) { + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -j ], B[ j ] ); + out32_Q12 = silk_SMLABB_ovflw( out32_Q12, in_ptr[ -j - 1 ], B[ j + 1 ] ); + } + + /* Subtract prediction */ + out32_Q12 = silk_SUB32_ovflw( silk_LSHIFT( (opus_int32)in_ptr[ 1 ], 12 ), out32_Q12 ); + + /* Scale to Q0 */ + out32 = silk_RSHIFT_ROUND( out32_Q12, 12 ); + + /* Saturate output */ + out[ ix ] = (opus_int16)silk_SAT16( out32 ); + } + + /* Set first d output samples to zero */ + silk_memset( out, 0, d * sizeof( opus_int16 ) ); +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_fit.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_fit.c new file mode 100644 index 0000000000..c0690a1fc9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_fit.c @@ -0,0 +1,82 @@ +/*********************************************************************** +Copyright (c) 2013, Koen Vos. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Convert int32 coefficients to int16 coefs and make sure there's no wrap-around. + This logic is reused in _celt_lpc(). Any bug fixes should also be applied there. */ +void silk_LPC_fit( + opus_int16 *a_QOUT, /* O Output signal */ + opus_int32 *a_QIN, /* I/O Input signal */ + const opus_int QOUT, /* I Input Q domain */ + const opus_int QIN, /* I Input Q domain */ + const opus_int d /* I Filter order */ +) +{ + opus_int i, k, idx = 0; + opus_int32 maxabs, absval, chirp_Q16; + + /* Limit the maximum absolute value of the prediction coefficients, so that they'll fit in int16 */ + for( i = 0; i < 10; i++ ) { + /* Find maximum absolute value and its index */ + maxabs = 0; + for( k = 0; k < d; k++ ) { + absval = silk_abs( a_QIN[k] ); + if( absval > maxabs ) { + maxabs = absval; + idx = k; + } + } + maxabs = silk_RSHIFT_ROUND( maxabs, QIN - QOUT ); + + if( maxabs > silk_int16_MAX ) { + /* Reduce magnitude of prediction coefficients */ + maxabs = silk_min( maxabs, 163838 ); /* ( silk_int32_MAX >> 14 ) + silk_int16_MAX = 163838 */ + chirp_Q16 = SILK_FIX_CONST( 0.999, 16 ) - silk_DIV32( silk_LSHIFT( maxabs - silk_int16_MAX, 14 ), + silk_RSHIFT32( silk_MUL( maxabs, idx + 1), 2 ) ); + silk_bwexpander_32( a_QIN, d, chirp_Q16 ); + } else { + break; + } + } + + if( i == 10 ) { + /* Reached the last iteration, clip the coefficients */ + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ) ); + a_QIN[ k ] = silk_LSHIFT( (opus_int32)a_QOUT[ k ], QIN - QOUT ); + } + } else { + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ); + } + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_inv_pred_gain.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_inv_pred_gain.c new file mode 100644 index 0000000000..a3746a6ef9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LPC_inv_pred_gain.c @@ -0,0 +1,141 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "define.h" + +#define QA 24 +#define A_LIMIT SILK_FIX_CONST( 0.99975, QA ) + +#define MUL32_FRAC_Q(a32, b32, Q) ((opus_int32)(silk_RSHIFT_ROUND64(silk_SMULL(a32, b32), Q))) + +/* Compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +static opus_int32 LPC_inverse_pred_gain_QA_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + opus_int32 A_QA[ SILK_MAX_ORDER_LPC ], /* I Prediction coefficients */ + const opus_int order /* I Prediction order */ +) +{ + opus_int k, n, mult2Q; + opus_int32 invGain_Q30, rc_Q31, rc_mult1_Q30, rc_mult2, tmp1, tmp2; + + invGain_Q30 = SILK_FIX_CONST( 1, 30 ); + for( k = order - 1; k > 0; k-- ) { + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ k ], 31 - QA ); + + /* rc_mult1_Q30 range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + silk_assert( rc_mult1_Q30 > ( 1 << 15 ) ); /* reduce A_LIMIT if fails */ + silk_assert( rc_mult1_Q30 <= ( 1 << 30 ) ); + + /* Update inverse gain */ + /* invGain_Q30 range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + /* rc_mult2 range: [ 2^30 : silk_int32_MAX ] */ + mult2Q = 32 - silk_CLZ32( silk_abs( rc_mult1_Q30 ) ); + rc_mult2 = silk_INVERSE32_varQ( rc_mult1_Q30, mult2Q + 30 ); + + /* Update AR coefficient */ + for( n = 0; n < (k + 1) >> 1; n++ ) { + opus_int64 tmp64; + tmp1 = A_QA[ n ]; + tmp2 = A_QA[ k - n - 1 ]; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp1, + MUL32_FRAC_Q( tmp2, rc_Q31, 31 ) ), rc_mult2 ), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ n ] = ( opus_int32 )tmp64; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp2, + MUL32_FRAC_Q( tmp1, rc_Q31, 31 ) ), rc_mult2), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ k - n - 1 ] = ( opus_int32 )tmp64; + } + } + + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ 0 ], 31 - QA ); + + /* Range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + + /* Update inverse gain */ + /* Range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + return invGain_Q30; +} + +/* For input in Q12 domain */ +opus_int32 silk_LPC_inverse_pred_gain_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +) +{ + opus_int k; + opus_int32 Atmp_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 DC_resp = 0; + + /* Increase Q domain of the AR coefficients */ + for( k = 0; k < order; k++ ) { + DC_resp += (opus_int32)A_Q12[ k ]; + Atmp_QA[ k ] = silk_LSHIFT32( (opus_int32)A_Q12[ k ], QA - 12 ); + } + /* If the DC is unstable, we don't even need to do the full calculations */ + if( DC_resp >= 4096 ) { + return 0; + } + return LPC_inverse_pred_gain_QA_c( Atmp_QA, order ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LP_variable_cutoff.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LP_variable_cutoff.c new file mode 100644 index 0000000000..79112ad354 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/LP_variable_cutoff.c @@ -0,0 +1,135 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + Elliptic/Cauer filters designed with 0.1 dB passband ripple, + 80 dB minimum stopband attenuation, and + [0.95 : 0.15 : 0.35] normalized cut off frequencies. +*/ + +#include "main.h" + +/* Helper function, interpolates the filter taps */ +static OPUS_INLINE void silk_LP_interpolate_filter_taps( + opus_int32 B_Q28[ TRANSITION_NB ], + opus_int32 A_Q28[ TRANSITION_NA ], + const opus_int ind, + const opus_int32 fac_Q16 +) +{ + opus_int nb, na; + + if( ind < TRANSITION_INT_NUM - 1 ) { + if( fac_Q16 > 0 ) { + if( fac_Q16 < 32768 ) { /* fac_Q16 is in range of a 16-bit int */ + /* Piece-wise linear interpolation of B and A */ + for( nb = 0; nb < TRANSITION_NB; nb++ ) { + B_Q28[ nb ] = silk_SMLAWB( + silk_Transition_LP_B_Q28[ ind ][ nb ], + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ] - + silk_Transition_LP_B_Q28[ ind ][ nb ], + fac_Q16 ); + } + for( na = 0; na < TRANSITION_NA; na++ ) { + A_Q28[ na ] = silk_SMLAWB( + silk_Transition_LP_A_Q28[ ind ][ na ], + silk_Transition_LP_A_Q28[ ind + 1 ][ na ] - + silk_Transition_LP_A_Q28[ ind ][ na ], + fac_Q16 ); + } + } else { /* ( fac_Q16 - ( 1 << 16 ) ) is in range of a 16-bit int */ + silk_assert( fac_Q16 - ( 1 << 16 ) == silk_SAT16( fac_Q16 - ( 1 << 16 ) ) ); + /* Piece-wise linear interpolation of B and A */ + for( nb = 0; nb < TRANSITION_NB; nb++ ) { + B_Q28[ nb ] = silk_SMLAWB( + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ], + silk_Transition_LP_B_Q28[ ind + 1 ][ nb ] - + silk_Transition_LP_B_Q28[ ind ][ nb ], + fac_Q16 - ( (opus_int32)1 << 16 ) ); + } + for( na = 0; na < TRANSITION_NA; na++ ) { + A_Q28[ na ] = silk_SMLAWB( + silk_Transition_LP_A_Q28[ ind + 1 ][ na ], + silk_Transition_LP_A_Q28[ ind + 1 ][ na ] - + silk_Transition_LP_A_Q28[ ind ][ na ], + fac_Q16 - ( (opus_int32)1 << 16 ) ); + } + } + } else { + silk_memcpy( B_Q28, silk_Transition_LP_B_Q28[ ind ], TRANSITION_NB * sizeof( opus_int32 ) ); + silk_memcpy( A_Q28, silk_Transition_LP_A_Q28[ ind ], TRANSITION_NA * sizeof( opus_int32 ) ); + } + } else { + silk_memcpy( B_Q28, silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM - 1 ], TRANSITION_NB * sizeof( opus_int32 ) ); + silk_memcpy( A_Q28, silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM - 1 ], TRANSITION_NA * sizeof( opus_int32 ) ); + } +} + +/* Low-pass filter with variable cutoff frequency based on */ +/* piece-wise linear interpolation between elliptic filters */ +/* Start by setting psEncC->mode <> 0; */ +/* Deactivate by setting psEncC->mode = 0; */ +void silk_LP_variable_cutoff( + silk_LP_state *psLP, /* I/O LP filter state */ + opus_int16 *frame, /* I/O Low-pass filtered output signal */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int32 B_Q28[ TRANSITION_NB ], A_Q28[ TRANSITION_NA ], fac_Q16 = 0; + opus_int ind = 0; + + silk_assert( psLP->transition_frame_no >= 0 && psLP->transition_frame_no <= TRANSITION_FRAMES ); + + /* Run filter if needed */ + if( psLP->mode != 0 ) { + /* Calculate index and interpolation factor for interpolation */ +#if( TRANSITION_INT_STEPS == 64 ) + fac_Q16 = silk_LSHIFT( TRANSITION_FRAMES - psLP->transition_frame_no, 16 - 6 ); +#else + fac_Q16 = silk_DIV32_16( silk_LSHIFT( TRANSITION_FRAMES - psLP->transition_frame_no, 16 ), TRANSITION_FRAMES ); +#endif + ind = silk_RSHIFT( fac_Q16, 16 ); + fac_Q16 -= silk_LSHIFT( ind, 16 ); + + silk_assert( ind >= 0 ); + silk_assert( ind < TRANSITION_INT_NUM ); + + /* Interpolate filter coefficients */ + silk_LP_interpolate_filter_taps( B_Q28, A_Q28, ind, fac_Q16 ); + + /* Update transition frame number for next frame */ + psLP->transition_frame_no = silk_LIMIT( psLP->transition_frame_no + psLP->mode, 0, TRANSITION_FRAMES ); + + /* ARMA low-pass filtering */ + silk_assert( TRANSITION_NB == 3 && TRANSITION_NA == 2 ); + silk_biquad_alt_stride1( frame, B_Q28, A_Q28, psLP->In_LP_State, frame, frame_length); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroCount.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroCount.h new file mode 100644 index 0000000000..dab2f57a68 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroCount.h @@ -0,0 +1,710 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SIGPROCFIX_API_MACROCOUNT_H +#define SIGPROCFIX_API_MACROCOUNT_H + +#ifdef silk_MACRO_COUNT +#include +#define varDefine opus_int64 ops_count = 0; + +extern opus_int64 ops_count; + +static OPUS_INLINE opus_int64 silk_SaveCount(){ + return(ops_count); +} + +static OPUS_INLINE opus_int64 silk_SaveResetCount(){ + opus_int64 ret; + + ret = ops_count; + ops_count = 0; + return(ret); +} + +static OPUS_INLINE silk_PrintCount(){ + printf("ops_count = %d \n ", (opus_int32)ops_count); +} + +#undef silk_MUL +static OPUS_INLINE opus_int32 silk_MUL(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 4; + ret = a32 * b32; + return ret; +} + +#undef silk_MUL_uint +static OPUS_INLINE opus_uint32 silk_MUL_uint(opus_uint32 a32, opus_uint32 b32){ + opus_uint32 ret; + ops_count += 4; + ret = a32 * b32; + return ret; +} +#undef silk_MLA +static OPUS_INLINE opus_int32 silk_MLA(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 4; + ret = a32 + b32 * c32; + return ret; +} + +#undef silk_MLA_uint +static OPUS_INLINE opus_int32 silk_MLA_uint(opus_uint32 a32, opus_uint32 b32, opus_uint32 c32){ + opus_uint32 ret; + ops_count += 4; + ret = a32 + b32 * c32; + return ret; +} + +#undef silk_SMULWB +static OPUS_INLINE opus_int32 silk_SMULWB(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 5; + ret = (a32 >> 16) * (opus_int32)((opus_int16)b32) + (((a32 & 0x0000FFFF) * (opus_int32)((opus_int16)b32)) >> 16); + return ret; +} +#undef silk_SMLAWB +static OPUS_INLINE opus_int32 silk_SMLAWB(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 5; + ret = ((a32) + ((((b32) >> 16) * (opus_int32)((opus_int16)(c32))) + ((((b32) & 0x0000FFFF) * (opus_int32)((opus_int16)(c32))) >> 16))); + return ret; +} + +#undef silk_SMULWT +static OPUS_INLINE opus_int32 silk_SMULWT(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 4; + ret = (a32 >> 16) * (b32 >> 16) + (((a32 & 0x0000FFFF) * (b32 >> 16)) >> 16); + return ret; +} +#undef silk_SMLAWT +static OPUS_INLINE opus_int32 silk_SMLAWT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 4; + ret = a32 + ((b32 >> 16) * (c32 >> 16)) + (((b32 & 0x0000FFFF) * ((c32 >> 16)) >> 16)); + return ret; +} + +#undef silk_SMULBB +static OPUS_INLINE opus_int32 silk_SMULBB(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 1; + ret = (opus_int32)((opus_int16)a32) * (opus_int32)((opus_int16)b32); + return ret; +} +#undef silk_SMLABB +static OPUS_INLINE opus_int32 silk_SMLABB(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + (opus_int32)((opus_int16)b32) * (opus_int32)((opus_int16)c32); + return ret; +} + +#undef silk_SMULBT +static OPUS_INLINE opus_int32 silk_SMULBT(opus_int32 a32, opus_int32 b32 ){ + opus_int32 ret; + ops_count += 4; + ret = ((opus_int32)((opus_int16)a32)) * (b32 >> 16); + return ret; +} + +#undef silk_SMLABT +static OPUS_INLINE opus_int32 silk_SMLABT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + ((opus_int32)((opus_int16)b32)) * (c32 >> 16); + return ret; +} + +#undef silk_SMULTT +static OPUS_INLINE opus_int32 silk_SMULTT(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + ops_count += 1; + ret = (a32 >> 16) * (b32 >> 16); + return ret; +} + +#undef silk_SMLATT +static OPUS_INLINE opus_int32 silk_SMLATT(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + ops_count += 1; + ret = a32 + (b32 >> 16) * (c32 >> 16); + return ret; +} + + +/* multiply-accumulate macros that allow overflow in the addition (ie, no asserts in debug mode)*/ +#undef silk_MLA_ovflw +#define silk_MLA_ovflw silk_MLA + +#undef silk_SMLABB_ovflw +#define silk_SMLABB_ovflw silk_SMLABB + +#undef silk_SMLABT_ovflw +#define silk_SMLABT_ovflw silk_SMLABT + +#undef silk_SMLATT_ovflw +#define silk_SMLATT_ovflw silk_SMLATT + +#undef silk_SMLAWB_ovflw +#define silk_SMLAWB_ovflw silk_SMLAWB + +#undef silk_SMLAWT_ovflw +#define silk_SMLAWT_ovflw silk_SMLAWT + +#undef silk_SMULL +static OPUS_INLINE opus_int64 silk_SMULL(opus_int32 a32, opus_int32 b32){ + opus_int64 ret; + ops_count += 8; + ret = ((opus_int64)(a32) * /*(opus_int64)*/(b32)); + return ret; +} + +#undef silk_SMLAL +static OPUS_INLINE opus_int64 silk_SMLAL(opus_int64 a64, opus_int32 b32, opus_int32 c32){ + opus_int64 ret; + ops_count += 8; + ret = a64 + ((opus_int64)(b32) * /*(opus_int64)*/(c32)); + return ret; +} +#undef silk_SMLALBB +static OPUS_INLINE opus_int64 silk_SMLALBB(opus_int64 a64, opus_int16 b16, opus_int16 c16){ + opus_int64 ret; + ops_count += 4; + ret = a64 + ((opus_int64)(b16) * /*(opus_int64)*/(c16)); + return ret; +} + +#undef SigProcFIX_CLZ16 +static OPUS_INLINE opus_int32 SigProcFIX_CLZ16(opus_int16 in16) +{ + opus_int32 out32 = 0; + ops_count += 10; + if( in16 == 0 ) { + return 16; + } + /* test nibbles */ + if( in16 & 0xFF00 ) { + if( in16 & 0xF000 ) { + in16 >>= 12; + } else { + out32 += 4; + in16 >>= 8; + } + } else { + if( in16 & 0xFFF0 ) { + out32 += 8; + in16 >>= 4; + } else { + out32 += 12; + } + } + /* test bits and return */ + if( in16 & 0xC ) { + if( in16 & 0x8 ) + return out32 + 0; + else + return out32 + 1; + } else { + if( in16 & 0xE ) + return out32 + 2; + else + return out32 + 3; + } +} + +#undef SigProcFIX_CLZ32 +static OPUS_INLINE opus_int32 SigProcFIX_CLZ32(opus_int32 in32) +{ + /* test highest 16 bits and convert to opus_int16 */ + ops_count += 2; + if( in32 & 0xFFFF0000 ) { + return SigProcFIX_CLZ16((opus_int16)(in32 >> 16)); + } else { + return SigProcFIX_CLZ16((opus_int16)in32) + 16; + } +} + +#undef silk_DIV32 +static OPUS_INLINE opus_int32 silk_DIV32(opus_int32 a32, opus_int32 b32){ + ops_count += 64; + return a32 / b32; +} + +#undef silk_DIV32_16 +static OPUS_INLINE opus_int32 silk_DIV32_16(opus_int32 a32, opus_int32 b32){ + ops_count += 32; + return a32 / b32; +} + +#undef silk_SAT8 +static OPUS_INLINE opus_int8 silk_SAT8(opus_int64 a){ + opus_int8 tmp; + ops_count += 1; + tmp = (opus_int8)((a) > silk_int8_MAX ? silk_int8_MAX : \ + ((a) < silk_int8_MIN ? silk_int8_MIN : (a))); + return(tmp); +} + +#undef silk_SAT16 +static OPUS_INLINE opus_int16 silk_SAT16(opus_int64 a){ + opus_int16 tmp; + ops_count += 1; + tmp = (opus_int16)((a) > silk_int16_MAX ? silk_int16_MAX : \ + ((a) < silk_int16_MIN ? silk_int16_MIN : (a))); + return(tmp); +} +#undef silk_SAT32 +static OPUS_INLINE opus_int32 silk_SAT32(opus_int64 a){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((a) > silk_int32_MAX ? silk_int32_MAX : \ + ((a) < silk_int32_MIN ? silk_int32_MIN : (a))); + return(tmp); +} +#undef silk_POS_SAT32 +static OPUS_INLINE opus_int32 silk_POS_SAT32(opus_int64 a){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((a) > silk_int32_MAX ? silk_int32_MAX : (a)); + return(tmp); +} + +#undef silk_ADD_POS_SAT8 +static OPUS_INLINE opus_int8 silk_ADD_POS_SAT8(opus_int64 a, opus_int64 b){ + opus_int8 tmp; + ops_count += 1; + tmp = (opus_int8)((((a)+(b)) & 0x80) ? silk_int8_MAX : ((a)+(b))); + return(tmp); +} +#undef silk_ADD_POS_SAT16 +static OPUS_INLINE opus_int16 silk_ADD_POS_SAT16(opus_int64 a, opus_int64 b){ + opus_int16 tmp; + ops_count += 1; + tmp = (opus_int16)((((a)+(b)) & 0x8000) ? silk_int16_MAX : ((a)+(b))); + return(tmp); +} + +#undef silk_ADD_POS_SAT32 +static OPUS_INLINE opus_int32 silk_ADD_POS_SAT32(opus_int64 a, opus_int64 b){ + opus_int32 tmp; + ops_count += 1; + tmp = (opus_int32)((((a)+(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))); + return(tmp); +} + +#undef silk_LSHIFT8 +static OPUS_INLINE opus_int8 silk_LSHIFT8(opus_int8 a, opus_int32 shift){ + opus_int8 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT16 +static OPUS_INLINE opus_int16 silk_LSHIFT16(opus_int16 a, opus_int32 shift){ + opus_int16 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT32 +static OPUS_INLINE opus_int32 silk_LSHIFT32(opus_int32 a, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a << shift; + return ret; +} +#undef silk_LSHIFT64 +static OPUS_INLINE opus_int64 silk_LSHIFT64(opus_int64 a, opus_int shift){ + ops_count += 1; + return a << shift; +} + +#undef silk_LSHIFT_ovflw +static OPUS_INLINE opus_int32 silk_LSHIFT_ovflw(opus_int32 a, opus_int32 shift){ + ops_count += 1; + return a << shift; +} + +#undef silk_LSHIFT_uint +static OPUS_INLINE opus_uint32 silk_LSHIFT_uint(opus_uint32 a, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a << shift; + return ret; +} + +#undef silk_RSHIFT8 +static OPUS_INLINE opus_int8 silk_RSHIFT8(opus_int8 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT16 +static OPUS_INLINE opus_int16 silk_RSHIFT16(opus_int16 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT32 +static OPUS_INLINE opus_int32 silk_RSHIFT32(opus_int32 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} +#undef silk_RSHIFT64 +static OPUS_INLINE opus_int64 silk_RSHIFT64(opus_int64 a, opus_int64 shift){ + ops_count += 1; + return a >> shift; +} + +#undef silk_RSHIFT_uint +static OPUS_INLINE opus_uint32 silk_RSHIFT_uint(opus_uint32 a, opus_int32 shift){ + ops_count += 1; + return a >> shift; +} + +#undef silk_ADD_LSHIFT +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_LSHIFT32 +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_LSHIFT_uint +static OPUS_INLINE opus_uint32 silk_ADD_LSHIFT_uint(opus_uint32 a, opus_uint32 b, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a + (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_ADD_RSHIFT +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_ADD_RSHIFT32 +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_ADD_RSHIFT_uint +static OPUS_INLINE opus_uint32 silk_ADD_RSHIFT_uint(opus_uint32 a, opus_uint32 b, opus_int32 shift){ + opus_uint32 ret; + ops_count += 1; + ret = a + (b >> shift); + return ret; /* shift > 0*/ +} +#undef silk_SUB_LSHIFT32 +static OPUS_INLINE opus_int32 silk_SUB_LSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a - (b << shift); + return ret; /* shift >= 0*/ +} +#undef silk_SUB_RSHIFT32 +static OPUS_INLINE opus_int32 silk_SUB_RSHIFT32(opus_int32 a, opus_int32 b, opus_int32 shift){ + opus_int32 ret; + ops_count += 1; + ret = a - (b >> shift); + return ret; /* shift > 0*/ +} + +#undef silk_RSHIFT_ROUND +static OPUS_INLINE opus_int32 silk_RSHIFT_ROUND(opus_int32 a, opus_int32 shift){ + opus_int32 ret; + ops_count += 3; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +#undef silk_RSHIFT_ROUND64 +static OPUS_INLINE opus_int64 silk_RSHIFT_ROUND64(opus_int64 a, opus_int32 shift){ + opus_int64 ret; + ops_count += 6; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +#undef silk_abs_int64 +static OPUS_INLINE opus_int64 silk_abs_int64(opus_int64 a){ + ops_count += 1; + return (((a) > 0) ? (a) : -(a)); /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN*/ +} + +#undef silk_abs_int32 +static OPUS_INLINE opus_int32 silk_abs_int32(opus_int32 a){ + ops_count += 1; + return silk_abs(a); +} + + +#undef silk_min +static silk_min(a, b){ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_max +static silk_max(a, b){ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_sign +static silk_sign(a){ + ops_count += 1; + return ((a) > 0 ? 1 : ( (a) < 0 ? -1 : 0 )); +} + +#undef silk_ADD16 +static OPUS_INLINE opus_int16 silk_ADD16(opus_int16 a, opus_int16 b){ + opus_int16 ret; + ops_count += 1; + ret = a + b; + return ret; +} + +#undef silk_ADD32 +static OPUS_INLINE opus_int32 silk_ADD32(opus_int32 a, opus_int32 b){ + opus_int32 ret; + ops_count += 1; + ret = a + b; + return ret; +} + +#undef silk_ADD64 +static OPUS_INLINE opus_int64 silk_ADD64(opus_int64 a, opus_int64 b){ + opus_int64 ret; + ops_count += 2; + ret = a + b; + return ret; +} + +#undef silk_SUB16 +static OPUS_INLINE opus_int16 silk_SUB16(opus_int16 a, opus_int16 b){ + opus_int16 ret; + ops_count += 1; + ret = a - b; + return ret; +} + +#undef silk_SUB32 +static OPUS_INLINE opus_int32 silk_SUB32(opus_int32 a, opus_int32 b){ + opus_int32 ret; + ops_count += 1; + ret = a - b; + return ret; +} + +#undef silk_SUB64 +static OPUS_INLINE opus_int64 silk_SUB64(opus_int64 a, opus_int64 b){ + opus_int64 ret; + ops_count += 2; + ret = a - b; + return ret; +} + +#undef silk_ADD_SAT16 +static OPUS_INLINE opus_int16 silk_ADD_SAT16( opus_int16 a16, opus_int16 b16 ) { + opus_int16 res; + /* Nb will be counted in AKP_add32 and silk_SAT16*/ + res = (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a16), (b16) ) ); + return res; +} + +#undef silk_ADD_SAT32 +static OPUS_INLINE opus_int32 silk_ADD_SAT32(opus_int32 a32, opus_int32 b32){ + opus_int32 res; + ops_count += 1; + res = ((((a32) + (b32)) & 0x80000000) == 0 ? \ + ((((a32) & (b32)) & 0x80000000) != 0 ? silk_int32_MIN : (a32)+(b32)) : \ + ((((a32) | (b32)) & 0x80000000) == 0 ? silk_int32_MAX : (a32)+(b32)) ); + return res; +} + +#undef silk_ADD_SAT64 +static OPUS_INLINE opus_int64 silk_ADD_SAT64( opus_int64 a64, opus_int64 b64 ) { + opus_int64 res; + ops_count += 1; + res = ((((a64) + (b64)) & 0x8000000000000000LL) == 0 ? \ + ((((a64) & (b64)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a64)+(b64)) : \ + ((((a64) | (b64)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a64)+(b64)) ); + return res; +} + +#undef silk_SUB_SAT16 +static OPUS_INLINE opus_int16 silk_SUB_SAT16( opus_int16 a16, opus_int16 b16 ) { + opus_int16 res; + silk_assert(0); + /* Nb will be counted in sub-macros*/ + res = (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a16), (b16) ) ); + return res; +} + +#undef silk_SUB_SAT32 +static OPUS_INLINE opus_int32 silk_SUB_SAT32( opus_int32 a32, opus_int32 b32 ) { + opus_int32 res; + ops_count += 1; + res = ((((a32)-(b32)) & 0x80000000) == 0 ? \ + (( (a32) & ((b32)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a32)-(b32)) : \ + ((((a32)^0x80000000) & (b32) & 0x80000000) ? silk_int32_MAX : (a32)-(b32)) ); + return res; +} + +#undef silk_SUB_SAT64 +static OPUS_INLINE opus_int64 silk_SUB_SAT64( opus_int64 a64, opus_int64 b64 ) { + opus_int64 res; + ops_count += 1; + res = ((((a64)-(b64)) & 0x8000000000000000LL) == 0 ? \ + (( (a64) & ((b64)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a64)-(b64)) : \ + ((((a64)^0x8000000000000000LL) & (b64) & 0x8000000000000000LL) ? silk_int64_MAX : (a64)-(b64)) ); + + return res; +} + +#undef silk_SMULWW +static OPUS_INLINE opus_int32 silk_SMULWW(opus_int32 a32, opus_int32 b32){ + opus_int32 ret; + /* Nb will be counted in sub-macros*/ + ret = silk_MLA(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)); + return ret; +} + +#undef silk_SMLAWW +static OPUS_INLINE opus_int32 silk_SMLAWW(opus_int32 a32, opus_int32 b32, opus_int32 c32){ + opus_int32 ret; + /* Nb will be counted in sub-macros*/ + ret = silk_MLA(silk_SMLAWB((a32), (b32), (c32)), (b32), silk_RSHIFT_ROUND((c32), 16)); + return ret; +} + +#undef silk_min_int +static OPUS_INLINE opus_int silk_min_int(opus_int a, opus_int b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} + +#undef silk_min_16 +static OPUS_INLINE opus_int16 silk_min_16(opus_int16 a, opus_int16 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_min_32 +static OPUS_INLINE opus_int32 silk_min_32(opus_int32 a, opus_int32 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} +#undef silk_min_64 +static OPUS_INLINE opus_int64 silk_min_64(opus_int64 a, opus_int64 b) +{ + ops_count += 1; + return (((a) < (b)) ? (a) : (b)); +} + +/* silk_min() versions with typecast in the function call */ +#undef silk_max_int +static OPUS_INLINE opus_int silk_max_int(opus_int a, opus_int b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_max_16 +static OPUS_INLINE opus_int16 silk_max_16(opus_int16 a, opus_int16 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} +#undef silk_max_32 +static OPUS_INLINE opus_int32 silk_max_32(opus_int32 a, opus_int32 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} + +#undef silk_max_64 +static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) +{ + ops_count += 1; + return (((a) > (b)) ? (a) : (b)); +} + + +#undef silk_LIMIT_int +static OPUS_INLINE opus_int silk_LIMIT_int(opus_int a, opus_int limit1, opus_int limit2) +{ + opus_int ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + + return(ret); +} + +#undef silk_LIMIT_16 +static OPUS_INLINE opus_int16 silk_LIMIT_16(opus_int16 a, opus_int16 limit1, opus_int16 limit2) +{ + opus_int16 ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + +return(ret); +} + + +#undef silk_LIMIT_32 +static OPUS_INLINE opus_int32 silk_LIMIT_32(opus_int32 a, opus_int32 limit1, opus_int32 limit2) +{ + opus_int32 ret; + ops_count += 6; + + ret = ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))); + return(ret); +} + +#else +#define varDefine +#define silk_SaveCount() + +#endif +#endif + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroDebug.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroDebug.h new file mode 100644 index 0000000000..3110da9a75 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/MacroDebug.h @@ -0,0 +1,945 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Copyright (C) 2012 Xiph.Org Foundation +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef MACRO_DEBUG_H +#define MACRO_DEBUG_H + +/* Redefine macro functions with extensive assertion in DEBUG mode. + As functions can't be undefined, this file can't work with SigProcFIX_MacroCount.h */ + +#if ( defined (FIXED_DEBUG) || ( 0 && defined (_DEBUG) ) ) && !defined (silk_MACRO_COUNT) + +#undef silk_ADD16 +#define silk_ADD16(a,b) silk_ADD16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_ADD16_(opus_int16 a, opus_int16 b, char *file, int line){ + opus_int16 ret; + + ret = a + b; + if ( ret != silk_ADD_SAT16( a, b ) ) + { + fprintf (stderr, "silk_ADD16(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD32 +#define silk_ADD32(a,b) silk_ADD32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD32_(opus_int32 a, opus_int32 b, char *file, int line){ + opus_int32 ret; + + ret = (opus_int32)((opus_uint32)a + (opus_uint32)b); + if ( ret != silk_ADD_SAT32( a, b ) ) + { + fprintf (stderr, "silk_ADD32(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD64 +#define silk_ADD64(a,b) silk_ADD64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_ADD64_(opus_int64 a, opus_int64 b, char *file, int line){ + opus_int64 ret; + + ret = a + b; + if ( ret != silk_ADD_SAT64( a, b ) ) + { + fprintf (stderr, "silk_ADD64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB16 +#define silk_SUB16(a,b) silk_SUB16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_SUB16_(opus_int16 a, opus_int16 b, char *file, int line){ + opus_int16 ret; + + ret = a - b; + if ( ret != silk_SUB_SAT16( a, b ) ) + { + fprintf (stderr, "silk_SUB16(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB32 +#define silk_SUB32(a,b) silk_SUB32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB32_(opus_int32 a, opus_int32 b, char *file, int line){ + opus_int64 ret; + + ret = a - (opus_int64)b; + if ( ret != silk_SUB_SAT32( a, b ) ) + { + fprintf (stderr, "silk_SUB32(%d, %d) in %s: line %d\n", a, b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SUB64 +#define silk_SUB64(a,b) silk_SUB64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SUB64_(opus_int64 a, opus_int64 b, char *file, int line){ + opus_int64 ret; + + ret = a - b; + if ( ret != silk_SUB_SAT64( a, b ) ) + { + fprintf (stderr, "silk_SUB64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)b, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_ADD_SAT16 +#define silk_ADD_SAT16(a,b) silk_ADD_SAT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_ADD_SAT16_( opus_int16 a16, opus_int16 b16, char *file, int line) { + opus_int16 res; + res = (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a16), (b16) ) ); + if ( res != silk_SAT16( (opus_int32)a16 + (opus_int32)b16 ) ) + { + fprintf (stderr, "silk_ADD_SAT16(%d, %d) in %s: line %d\n", a16, b16, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_ADD_SAT32 +#define silk_ADD_SAT32(a,b) silk_ADD_SAT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_SAT32_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 res; + res = ((((opus_uint32)(a32) + (opus_uint32)(b32)) & 0x80000000) == 0 ? \ + ((((a32) & (b32)) & 0x80000000) != 0 ? silk_int32_MIN : (a32)+(b32)) : \ + ((((a32) | (b32)) & 0x80000000) == 0 ? silk_int32_MAX : (a32)+(b32)) ); + if ( res != silk_SAT32( (opus_int64)a32 + (opus_int64)b32 ) ) + { + fprintf (stderr, "silk_ADD_SAT32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_ADD_SAT64 +#define silk_ADD_SAT64(a,b) silk_ADD_SAT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_ADD_SAT64_( opus_int64 a64, opus_int64 b64, char *file, int line) { + opus_int64 res; + int fail = 0; + res = ((((a64) + (b64)) & 0x8000000000000000LL) == 0 ? \ + ((((a64) & (b64)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a64)+(b64)) : \ + ((((a64) | (b64)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a64)+(b64)) ); + if( res != a64 + b64 ) { + /* Check that we saturated to the correct extreme value */ + if ( !(( res == silk_int64_MAX && ( ( a64 >> 1 ) + ( b64 >> 1 ) > ( silk_int64_MAX >> 3 ) ) ) || + ( res == silk_int64_MIN && ( ( a64 >> 1 ) + ( b64 >> 1 ) < ( silk_int64_MIN >> 3 ) ) ) ) ) + { + fail = 1; + } + } else { + /* Saturation not necessary */ + fail = res != a64 + b64; + } + if ( fail ) + { + fprintf (stderr, "silk_ADD_SAT64(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT16 +#define silk_SUB_SAT16(a,b) silk_SUB_SAT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_SUB_SAT16_( opus_int16 a16, opus_int16 b16, char *file, int line ) { + opus_int16 res; + res = (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a16), (b16) ) ); + if ( res != silk_SAT16( (opus_int32)a16 - (opus_int32)b16 ) ) + { + fprintf (stderr, "silk_SUB_SAT16(%d, %d) in %s: line %d\n", a16, b16, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT32 +#define silk_SUB_SAT32(a,b) silk_SUB_SAT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_SAT32_( opus_int32 a32, opus_int32 b32, char *file, int line ) { + opus_int32 res; + res = ((((opus_uint32)(a32)-(opus_uint32)(b32)) & 0x80000000) == 0 ? \ + (( (a32) & ((b32)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a32)-(b32)) : \ + ((((a32)^0x80000000) & (b32) & 0x80000000) ? silk_int32_MAX : (a32)-(b32)) ); + if ( res != silk_SAT32( (opus_int64)a32 - (opus_int64)b32 ) ) + { + fprintf (stderr, "silk_SUB_SAT32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_SUB_SAT64 +#define silk_SUB_SAT64(a,b) silk_SUB_SAT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SUB_SAT64_( opus_int64 a64, opus_int64 b64, char *file, int line ) { + opus_int64 res; + int fail = 0; + res = ((((a64)-(b64)) & 0x8000000000000000LL) == 0 ? \ + (( (a64) & ((b64)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a64)-(b64)) : \ + ((((a64)^0x8000000000000000LL) & (b64) & 0x8000000000000000LL) ? silk_int64_MAX : (a64)-(b64)) ); + if( res != a64 - b64 ) { + /* Check that we saturated to the correct extreme value */ + if( !(( res == silk_int64_MAX && ( ( a64 >> 1 ) + ( b64 >> 1 ) > ( silk_int64_MAX >> 3 ) ) ) || + ( res == silk_int64_MIN && ( ( a64 >> 1 ) + ( b64 >> 1 ) < ( silk_int64_MIN >> 3 ) ) ) )) + { + fail = 1; + } + } else { + /* Saturation not necessary */ + fail = res != a64 - b64; + } + if ( fail ) + { + fprintf (stderr, "silk_SUB_SAT64(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return res; +} + +#undef silk_MUL +#define silk_MUL(a,b) silk_MUL_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MUL_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + opus_int64 ret64; + ret = (opus_int32)((opus_uint32)a32 * (opus_uint32)b32); + ret64 = (opus_int64)a32 * (opus_int64)b32; + if ( (opus_int64)ret != ret64 ) + { + fprintf (stderr, "silk_MUL(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MUL_uint +#define silk_MUL_uint(a,b) silk_MUL_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_MUL_uint_(opus_uint32 a32, opus_uint32 b32, char *file, int line){ + opus_uint32 ret; + ret = a32 * b32; + if ( (opus_uint64)ret != (opus_uint64)a32 * (opus_uint64)b32 ) + { + fprintf (stderr, "silk_MUL_uint(%u, %u) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MLA +#define silk_MLA(a,b,c) silk_MLA_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MLA_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + b32 * c32; + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int64)c32 ) + { + fprintf (stderr, "silk_MLA(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_MLA_uint +#define silk_MLA_uint(a,b,c) silk_MLA_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_MLA_uint_(opus_uint32 a32, opus_uint32 b32, opus_uint32 c32, char *file, int line){ + opus_uint32 ret; + ret = a32 + b32 * c32; + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int64)c32 ) + { + fprintf (stderr, "silk_MLA_uint(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWB +#define silk_SMULWB(a,b) silk_SMULWB_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWB_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + ret = (a32 >> 16) * (opus_int32)((opus_int16)b32) + (((a32 & 0x0000FFFF) * (opus_int32)((opus_int16)b32)) >> 16); + if ( (opus_int64)ret != ((opus_int64)a32 * (opus_int16)b32) >> 16 ) + { + fprintf (stderr, "silk_SMULWB(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMLAWB +#define silk_SMLAWB(a,b,c) silk_SMLAWB_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWB_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw( a32, silk_SMULWB( b32, c32 ) ); + if ( ret != silk_ADD_SAT32( a32, silk_SMULWB( b32, c32 ) ) ) + { + fprintf (stderr, "silk_SMLAWB(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWT +#define silk_SMULWT(a,b) silk_SMULWT_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWT_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret; + ret = (a32 >> 16) * (b32 >> 16) + (((a32 & 0x0000FFFF) * (b32 >> 16)) >> 16); + if ( (opus_int64)ret != ((opus_int64)a32 * (b32 >> 16)) >> 16 ) + { + fprintf (stderr, "silk_SMULWT(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMLAWT +#define silk_SMLAWT(a,b,c) silk_SMLAWT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + ((b32 >> 16) * (c32 >> 16)) + (((b32 & 0x0000FFFF) * ((c32 >> 16)) >> 16)); + if ( (opus_int64)ret != (opus_int64)a32 + (((opus_int64)b32 * (c32 >> 16)) >> 16) ) + { + fprintf (stderr, "silk_SMLAWT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULL +#define silk_SMULL(a,b) silk_SMULL_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_SMULL_(opus_int64 a64, opus_int64 b64, char *file, int line){ + opus_int64 ret64; + int fail = 0; + ret64 = a64 * b64; + if( b64 != 0 ) { + fail = a64 != (ret64 / b64); + } else if( a64 != 0 ) { + fail = b64 != (ret64 / a64); + } + if ( fail ) + { + fprintf (stderr, "silk_SMULL(%lld, %lld) in %s: line %d\n", (long long)a64, (long long)b64, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret64; +} + +/* no checking needed for silk_SMULBB */ +#undef silk_SMLABB +#define silk_SMLABB(a,b,c) silk_SMLABB_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLABB_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + (opus_int32)((opus_int16)b32) * (opus_int32)((opus_int16)c32); + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (opus_int16)c32 ) + { + fprintf (stderr, "silk_SMLABB(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULBT */ +#undef silk_SMLABT +#define silk_SMLABT(a,b,c) silk_SMLABT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLABT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + ((opus_int32)((opus_int16)b32)) * (c32 >> 16); + if ( (opus_int64)ret != (opus_int64)a32 + (opus_int64)b32 * (c32 >> 16) ) + { + fprintf (stderr, "silk_SMLABT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULTT */ +#undef silk_SMLATT +#define silk_SMLATT(a,b,c) silk_SMLATT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLATT_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret; + ret = a32 + (b32 >> 16) * (c32 >> 16); + if ( (opus_int64)ret != (opus_int64)a32 + (b32 >> 16) * (c32 >> 16) ) + { + fprintf (stderr, "silk_SMLATT(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_SMULWW +#define silk_SMULWW(a,b) silk_SMULWW_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMULWW_(opus_int32 a32, opus_int32 b32, char *file, int line){ + opus_int32 ret, tmp1, tmp2; + opus_int64 ret64; + int fail = 0; + + ret = silk_SMULWB( a32, b32 ); + tmp1 = silk_RSHIFT_ROUND( b32, 16 ); + tmp2 = silk_MUL( a32, tmp1 ); + + fail |= (opus_int64)tmp2 != (opus_int64) a32 * (opus_int64) tmp1; + + tmp1 = ret; + ret = silk_ADD32( tmp1, tmp2 ); + fail |= silk_ADD32( tmp1, tmp2 ) != silk_ADD_SAT32( tmp1, tmp2 ); + + ret64 = silk_RSHIFT64( silk_SMULL( a32, b32 ), 16 ); + fail |= (opus_int64)ret != ret64; + + if ( fail ) + { + fprintf (stderr, "silk_SMULWW(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + + return ret; +} + +#undef silk_SMLAWW +#define silk_SMLAWW(a,b,c) silk_SMLAWW_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SMLAWW_(opus_int32 a32, opus_int32 b32, opus_int32 c32, char *file, int line){ + opus_int32 ret, tmp; + + tmp = silk_SMULWW( b32, c32 ); + ret = silk_ADD32( a32, tmp ); + if ( ret != silk_ADD_SAT32( a32, tmp ) ) + { + fprintf (stderr, "silk_SMLAWW(%d, %d, %d) in %s: line %d\n", a32, b32, c32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +/* no checking needed for silk_SMULL + no checking needed for silk_SMLAL + no checking needed for silk_SMLALBB + no checking needed for SigProcFIX_CLZ16 + no checking needed for SigProcFIX_CLZ32*/ + +#undef silk_DIV32 +#define silk_DIV32(a,b) silk_DIV32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_DIV32_(opus_int32 a32, opus_int32 b32, char *file, int line){ + if ( b32 == 0 ) + { + fprintf (stderr, "silk_DIV32(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a32 / b32; +} + +#undef silk_DIV32_16 +#define silk_DIV32_16(a,b) silk_DIV32_16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_DIV32_16_(opus_int32 a32, opus_int32 b32, char *file, int line){ + int fail = 0; + fail |= b32 == 0; + fail |= b32 > silk_int16_MAX; + fail |= b32 < silk_int16_MIN; + if ( fail ) + { + fprintf (stderr, "silk_DIV32_16(%d, %d) in %s: line %d\n", a32, b32, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a32 / b32; +} + +/* no checking needed for silk_SAT8 + no checking needed for silk_SAT16 + no checking needed for silk_SAT32 + no checking needed for silk_POS_SAT32 + no checking needed for silk_ADD_POS_SAT8 + no checking needed for silk_ADD_POS_SAT16 + no checking needed for silk_ADD_POS_SAT32 */ + +#undef silk_LSHIFT8 +#define silk_LSHIFT8(a,b) silk_LSHIFT8_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_LSHIFT8_(opus_int8 a, opus_int32 shift, char *file, int line){ + opus_int8 ret; + int fail = 0; + ret = (opus_int8)((opus_uint8)a << shift); + fail |= shift < 0; + fail |= shift >= 8; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT8(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT16 +#define silk_LSHIFT16(a,b) silk_LSHIFT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_LSHIFT16_(opus_int16 a, opus_int32 shift, char *file, int line){ + opus_int16 ret; + int fail = 0; + ret = (opus_int16)((opus_uint16)a << shift); + fail |= shift < 0; + fail |= shift >= 16; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT16(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT32 +#define silk_LSHIFT32(a,b) silk_LSHIFT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_LSHIFT32_(opus_int32 a, opus_int32 shift, char *file, int line){ + opus_int32 ret; + int fail = 0; + ret = (opus_int32)((opus_uint32)a << shift); + fail |= shift < 0; + fail |= shift >= 32; + fail |= (opus_int64)ret != (opus_int64)(((opus_uint64)a) << shift); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT32(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT64 +#define silk_LSHIFT64(a,b) silk_LSHIFT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_LSHIFT64_(opus_int64 a, opus_int shift, char *file, int line){ + opus_int64 ret; + int fail = 0; + ret = (opus_int64)((opus_uint64)a << shift); + fail |= shift < 0; + fail |= shift >= 64; + fail |= (ret>>shift) != ((opus_int64)a); + if ( fail ) + { + fprintf (stderr, "silk_LSHIFT64(%lld, %d) in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_LSHIFT_ovflw +#define silk_LSHIFT_ovflw(a,b) silk_LSHIFT_ovflw_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_LSHIFT_ovflw_(opus_int32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift >= 32) ) /* no check for overflow */ + { + fprintf (stderr, "silk_LSHIFT_ovflw(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a << shift; +} + +#undef silk_LSHIFT_uint +#define silk_LSHIFT_uint(a,b) silk_LSHIFT_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_LSHIFT_uint_(opus_uint32 a, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a << shift; + if ( (shift < 0) || ((opus_int64)ret != ((opus_int64)a) << shift)) + { + fprintf (stderr, "silk_LSHIFT_uint(%u, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_RSHIFT8 +#define silk_RSHITF8(a,b) silk_RSHIFT8_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_RSHIFT8_(opus_int8 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=8) ) + { + fprintf (stderr, "silk_RSHITF8(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT16 +#define silk_RSHITF16(a,b) silk_RSHIFT16_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_RSHIFT16_(opus_int16 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=16) ) + { + fprintf (stderr, "silk_RSHITF16(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT32 +#define silk_RSHIFT32(a,b) silk_RSHIFT32_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_RSHIFT32_(opus_int32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>=32) ) + { + fprintf (stderr, "silk_RSHITF32(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT64 +#define silk_RSHIFT64(a,b) silk_RSHIFT64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_RSHIFT64_(opus_int64 a, opus_int64 shift, char *file, int line){ + if ( (shift < 0) || (shift>=64) ) + { + fprintf (stderr, "silk_RSHITF64(%lld, %lld) in %s: line %d\n", (long long)a, (long long)shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_RSHIFT_uint +#define silk_RSHIFT_uint(a,b) silk_RSHIFT_uint_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_RSHIFT_uint_(opus_uint32 a, opus_int32 shift, char *file, int line){ + if ( (shift < 0) || (shift>32) ) + { + fprintf (stderr, "silk_RSHIFT_uint(%u, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return a >> shift; +} + +#undef silk_ADD_LSHIFT +#define silk_ADD_LSHIFT(a,b,c) silk_ADD_LSHIFT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE int silk_ADD_LSHIFT_(int a, int b, int shift, char *file, int line){ + opus_int16 ret; + ret = a + (opus_int16)((opus_uint16)b << shift); + if ( (shift < 0) || (shift>15) || ((opus_int64)ret != (opus_int64)a + (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_LSHIFT32 +#define silk_ADD_LSHIFT32(a,b,c) silk_ADD_LSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_LSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw(a, (opus_int32)((opus_uint32)b << shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a + (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_LSHIFT_uint +#define silk_ADD_LSHIFT_uint(a,b,c) silk_ADD_LSHIFT_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_ADD_LSHIFT_uint_(opus_uint32 a, opus_uint32 b, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a + (b << shift); + if ( (shift < 0) || (shift>32) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) << shift)) ) + { + fprintf (stderr, "silk_ADD_LSHIFT_uint(%u, %u, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_ADD_RSHIFT +#define silk_ADD_RSHIFT(a,b,c) silk_ADD_RSHIFT_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE int silk_ADD_RSHIFT_(int a, int b, int shift, char *file, int line){ + opus_int16 ret; + ret = a + (b >> shift); + if ( (shift < 0) || (shift>15) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_ADD_RSHIFT32 +#define silk_ADD_RSHIFT32(a,b,c) silk_ADD_RSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_ADD_RSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_ADD32_ovflw(a, (b >> shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_ADD_RSHIFT_uint +#define silk_ADD_RSHIFT_uint(a,b,c) silk_ADD_RSHIFT_uint_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_uint32 silk_ADD_RSHIFT_uint_(opus_uint32 a, opus_uint32 b, opus_int32 shift, char *file, int line){ + opus_uint32 ret; + ret = a + (b >> shift); + if ( (shift < 0) || (shift>32) || ((opus_int64)ret != (opus_int64)a + (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_ADD_RSHIFT_uint(%u, %u, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_SUB_LSHIFT32 +#define silk_SUB_LSHIFT32(a,b,c) silk_SUB_LSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_LSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_SUB32_ovflw(a, (opus_int32)((opus_uint32)b << shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a - (opus_int64)(((opus_uint64)b) << shift)) ) + { + fprintf (stderr, "silk_SUB_LSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift >= 0 */ +} + +#undef silk_SUB_RSHIFT32 +#define silk_SUB_RSHIFT32(a,b,c) silk_SUB_RSHIFT32_((a), (b), (c), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_SUB_RSHIFT32_(opus_int32 a, opus_int32 b, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = silk_SUB32_ovflw(a, (b >> shift)); + if ( (shift < 0) || (shift>31) || ((opus_int64)ret != (opus_int64)a - (((opus_int64)b) >> shift)) ) + { + fprintf (stderr, "silk_SUB_RSHIFT32(%d, %d, %d) in %s: line %d\n", a, b, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; /* shift > 0 */ +} + +#undef silk_RSHIFT_ROUND +#define silk_RSHIFT_ROUND(a,b) silk_RSHIFT_ROUND_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_RSHIFT_ROUND_(opus_int32 a, opus_int32 shift, char *file, int line){ + opus_int32 ret; + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + /* the macro definition can't handle a shift of zero */ + if ( (shift <= 0) || (shift>31) || ((opus_int64)ret != ((opus_int64)a + ((opus_int64)1 << (shift - 1))) >> shift) ) + { + fprintf (stderr, "silk_RSHIFT_ROUND(%d, %d) in %s: line %d\n", a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return ret; +} + +#undef silk_RSHIFT_ROUND64 +#define silk_RSHIFT_ROUND64(a,b) silk_RSHIFT_ROUND64_((a), (b), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_RSHIFT_ROUND64_(opus_int64 a, opus_int32 shift, char *file, int line){ + opus_int64 ret; + /* the macro definition can't handle a shift of zero */ + if ( (shift <= 0) || (shift>=64) ) + { + fprintf (stderr, "silk_RSHIFT_ROUND64(%lld, %d) in %s: line %d\n", (long long)a, shift, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + ret = shift == 1 ? (a >> 1) + (a & 1) : ((a >> (shift - 1)) + 1) >> 1; + return ret; +} + +/* silk_abs is used on floats also, so doesn't work... */ +/*#undef silk_abs +static OPUS_INLINE opus_int32 silk_abs(opus_int32 a){ + silk_assert(a != 0x80000000); + return (((a) > 0) ? (a) : -(a)); // Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN +}*/ + +#undef silk_abs_int64 +#define silk_abs_int64(a) silk_abs_int64_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int64 silk_abs_int64_(opus_int64 a, char *file, int line){ + if ( a == silk_int64_MIN ) + { + fprintf (stderr, "silk_abs_int64(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return (((a) > 0) ? (a) : -(a)); /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN */ +} + +#undef silk_abs_int32 +#define silk_abs_int32(a) silk_abs_int32_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_abs_int32_(opus_int32 a, char *file, int line){ + if ( a == silk_int32_MIN ) + { + fprintf (stderr, "silk_abs_int32(%d) in %s: line %d\n", a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return silk_abs(a); +} + +#undef silk_CHECK_FIT8 +#define silk_CHECK_FIT8(a) silk_CHECK_FIT8_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int8 silk_CHECK_FIT8_( opus_int64 a, char *file, int line ){ + opus_int8 ret; + ret = (opus_int8)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT8(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +#undef silk_CHECK_FIT16 +#define silk_CHECK_FIT16(a) silk_CHECK_FIT16_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int16 silk_CHECK_FIT16_( opus_int64 a, char *file, int line ){ + opus_int16 ret; + ret = (opus_int16)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT16(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +#undef silk_CHECK_FIT32 +#define silk_CHECK_FIT32(a) silk_CHECK_FIT32_((a), __FILE__, __LINE__) +static OPUS_INLINE opus_int32 silk_CHECK_FIT32_( opus_int64 a, char *file, int line ){ + opus_int32 ret; + ret = (opus_int32)a; + if ( (opus_int64)ret != a ) + { + fprintf (stderr, "silk_CHECK_FIT32(%lld) in %s: line %d\n", (long long)a, file, line); +#ifdef FIXED_DEBUG_ASSERT + silk_assert( 0 ); +#endif + } + return( ret ); +} + +/* no checking for silk_NSHIFT_MUL_32_32 + no checking for silk_NSHIFT_MUL_16_16 + no checking needed for silk_min + no checking needed for silk_max + no checking needed for silk_sign +*/ + +#endif +#endif /* MACRO_DEBUG_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF2A.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF2A.c new file mode 100644 index 0000000000..d5b7730638 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF2A.c @@ -0,0 +1,141 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* conversion between prediction filter coefficients and LSFs */ +/* order should be even */ +/* a piecewise linear approximation maps LSF <-> cos(LSF) */ +/* therefore the result is not accurate LSFs, but the two */ +/* functions are accurate inverses of each other */ + +#include "SigProc_FIX.h" +#include "tables.h" + +#define QA 16 + +/* helper function for NLSF2A(..) */ +static OPUS_INLINE void silk_NLSF2A_find_poly( + opus_int32 *out, /* O intermediate polynomial, QA [dd+1] */ + const opus_int32 *cLSF, /* I vector of interleaved 2*cos(LSFs), QA [d] */ + opus_int dd /* I polynomial order (= 1/2 * filter order) */ +) +{ + opus_int k, n; + opus_int32 ftmp; + + out[0] = silk_LSHIFT( 1, QA ); + out[1] = -cLSF[0]; + for( k = 1; k < dd; k++ ) { + ftmp = cLSF[2*k]; /* QA*/ + out[k+1] = silk_LSHIFT( out[k-1], 1 ) - (opus_int32)silk_RSHIFT_ROUND64( silk_SMULL( ftmp, out[k] ), QA ); + for( n = k; n > 1; n-- ) { + out[n] += out[n-2] - (opus_int32)silk_RSHIFT_ROUND64( silk_SMULL( ftmp, out[n-1] ), QA ); + } + out[1] -= ftmp; + } +} + +/* compute whitening filter coefficients from normalized line spectral frequencies */ +void silk_NLSF2A( + opus_int16 *a_Q12, /* O monic whitening filter coefficients in Q12, [ d ] */ + const opus_int16 *NLSF, /* I normalized line spectral frequencies in Q15, [ d ] */ + const opus_int d, /* I filter order (should be even) */ + int arch /* I Run-time architecture */ +) +{ + /* This ordering was found to maximize quality. It improves numerical accuracy of + silk_NLSF2A_find_poly() compared to "standard" ordering. */ + static const unsigned char ordering16[16] = { + 0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1 + }; + static const unsigned char ordering10[10] = { + 0, 9, 6, 3, 4, 5, 8, 1, 2, 7 + }; + const unsigned char *ordering; + opus_int k, i, dd; + opus_int32 cos_LSF_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; + opus_int32 Ptmp, Qtmp, f_int, f_frac, cos_val, delta; + opus_int32 a32_QA1[ SILK_MAX_ORDER_LPC ]; + + silk_assert( LSF_COS_TAB_SZ_FIX == 128 ); + celt_assert( d==10 || d==16 ); + + /* convert LSFs to 2*cos(LSF), using piecewise linear curve from table */ + ordering = d == 16 ? ordering16 : ordering10; + for( k = 0; k < d; k++ ) { + silk_assert( NLSF[k] >= 0 ); + + /* f_int on a scale 0-127 (rounded down) */ + f_int = silk_RSHIFT( NLSF[k], 15 - 7 ); + + /* f_frac, range: 0..255 */ + f_frac = NLSF[k] - silk_LSHIFT( f_int, 15 - 7 ); + + silk_assert(f_int >= 0); + silk_assert(f_int < LSF_COS_TAB_SZ_FIX ); + + /* Read start and end value from table */ + cos_val = silk_LSFCosTab_FIX_Q12[ f_int ]; /* Q12 */ + delta = silk_LSFCosTab_FIX_Q12[ f_int + 1 ] - cos_val; /* Q12, with a range of 0..200 */ + + /* Linear interpolation */ + cos_LSF_QA[ordering[k]] = silk_RSHIFT_ROUND( silk_LSHIFT( cos_val, 8 ) + silk_MUL( delta, f_frac ), 20 - QA ); /* QA */ + } + + dd = silk_RSHIFT( d, 1 ); + + /* generate even and odd polynomials using convolution */ + silk_NLSF2A_find_poly( P, &cos_LSF_QA[ 0 ], dd ); + silk_NLSF2A_find_poly( Q, &cos_LSF_QA[ 1 ], dd ); + + /* convert even and odd polynomials to opus_int32 Q12 filter coefs */ + for( k = 0; k < dd; k++ ) { + Ptmp = P[ k+1 ] + P[ k ]; + Qtmp = Q[ k+1 ] - Q[ k ]; + + /* the Ptmp and Qtmp values at this stage need to fit in int32 */ + a32_QA1[ k ] = -Qtmp - Ptmp; /* QA+1 */ + a32_QA1[ d-k-1 ] = Qtmp - Ptmp; /* QA+1 */ + } + + /* Convert int32 coefficients to Q12 int16 coefs */ + silk_LPC_fit( a_Q12, a32_QA1, 12, QA + 1, d ); + + for( i = 0; silk_LPC_inverse_pred_gain( a_Q12, d, arch ) == 0 && i < MAX_LPC_STABILIZE_ITERATIONS; i++ ) { + /* Prediction coefficients are (too close to) unstable; apply bandwidth expansion */ + /* on the unscaled coefficients, convert to Q12 and measure again */ + silk_bwexpander_32( a32_QA1, d, 65536 - silk_LSHIFT( 2, i ) ); + for( k = 0; k < d; k++ ) { + a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ + } + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ.c new file mode 100644 index 0000000000..b83182a79c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ.c @@ -0,0 +1,76 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Compute quantization errors for an LPC_order element input vector for a VQ codebook */ +void silk_NLSF_VQ( + opus_int32 err_Q24[], /* O Quantization errors [K] */ + const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ + const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ + const opus_int K, /* I Number of codebook vectors */ + const opus_int LPC_order /* I Number of LPCs */ +) +{ + opus_int i, m; + opus_int32 diff_Q15, diffw_Q24, sum_error_Q24, pred_Q24; + const opus_int16 *w_Q9_ptr; + const opus_uint8 *cb_Q8_ptr; + + celt_assert( ( LPC_order & 1 ) == 0 ); + + /* Loop over codebook */ + cb_Q8_ptr = pCB_Q8; + w_Q9_ptr = pWght_Q9; + for( i = 0; i < K; i++ ) { + sum_error_Q24 = 0; + pred_Q24 = 0; + for( m = LPC_order-2; m >= 0; m -= 2 ) { + /* Compute weighted absolute predictive quantization error for index m + 1 */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m + 1 ], (opus_int32)cb_Q8_ptr[ m + 1 ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m + 1 ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; + + /* Compute weighted absolute predictive quantization error for index m */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m ], (opus_int32)cb_Q8_ptr[ m ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; + + silk_assert( sum_error_Q24 >= 0 ); + } + err_Q24[ i ] = sum_error_Q24; + cb_Q8_ptr += LPC_order; + w_Q9_ptr += LPC_order; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ_weights_laroia.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ_weights_laroia.c new file mode 100644 index 0000000000..9873bcde10 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_VQ_weights_laroia.c @@ -0,0 +1,80 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "define.h" +#include "SigProc_FIX.h" + +/* +R. Laroia, N. Phamdo and N. Farvardin, "Robust and Efficient Quantization of Speech LSP +Parameters Using Structured Vector Quantization", Proc. IEEE Int. Conf. Acoust., Speech, +Signal Processing, pp. 641-644, 1991. +*/ + +/* Laroia low complexity NLSF weights */ +void silk_NLSF_VQ_weights_laroia( + opus_int16 *pNLSFW_Q_OUT, /* O Pointer to input vector weights [D] */ + const opus_int16 *pNLSF_Q15, /* I Pointer to input vector [D] */ + const opus_int D /* I Input vector dimension (even) */ +) +{ + opus_int k; + opus_int32 tmp1_int, tmp2_int; + + celt_assert( D > 0 ); + celt_assert( ( D & 1 ) == 0 ); + + /* First value */ + tmp1_int = silk_max_int( pNLSF_Q15[ 0 ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + tmp2_int = silk_max_int( pNLSF_Q15[ 1 ] - pNLSF_Q15[ 0 ], 1 ); + tmp2_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp2_int ); + pNLSFW_Q_OUT[ 0 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ 0 ] > 0 ); + + /* Main loop */ + for( k = 1; k < D - 1; k += 2 ) { + tmp1_int = silk_max_int( pNLSF_Q15[ k + 1 ] - pNLSF_Q15[ k ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + pNLSFW_Q_OUT[ k ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ k ] > 0 ); + + tmp2_int = silk_max_int( pNLSF_Q15[ k + 2 ] - pNLSF_Q15[ k + 1 ], 1 ); + tmp2_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp2_int ); + pNLSFW_Q_OUT[ k + 1 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ k + 1 ] > 0 ); + } + + /* Last value */ + tmp1_int = silk_max_int( ( 1 << 15 ) - pNLSF_Q15[ D - 1 ], 1 ); + tmp1_int = silk_DIV32_16( (opus_int32)1 << ( 15 + NLSF_W_Q ), tmp1_int ); + pNLSFW_Q_OUT[ D - 1 ] = (opus_int16)silk_min_int( tmp1_int + tmp2_int, silk_int16_MAX ); + silk_assert( pNLSFW_Q_OUT[ D - 1 ] > 0 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_decode.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_decode.c new file mode 100644 index 0000000000..eeb0ba8c92 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_decode.c @@ -0,0 +1,93 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Predictive dequantizer for NLSF residuals */ +static OPUS_INLINE void silk_NLSF_residual_dequant( /* O Returns RD value in Q30 */ + opus_int16 x_Q10[], /* O Output [ order ] */ + const opus_int8 indices[], /* I Quantization indices [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 order /* I Number of input values */ +) +{ + opus_int i, out_Q10, pred_Q10; + + out_Q10 = 0; + for( i = order-1; i >= 0; i-- ) { + pred_Q10 = silk_RSHIFT( silk_SMULBB( out_Q10, (opus_int16)pred_coef_Q8[ i ] ), 8 ); + out_Q10 = silk_LSHIFT( indices[ i ], 10 ); + if( out_Q10 > 0 ) { + out_Q10 = silk_SUB16( out_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( out_Q10 < 0 ) { + out_Q10 = silk_ADD16( out_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } + out_Q10 = silk_SMLAWB( pred_Q10, (opus_int32)out_Q10, quant_step_size_Q16 ); + x_Q10[ i ] = out_Q10; + } +} + + +/***********************/ +/* NLSF vector decoder */ +/***********************/ +void silk_NLSF_decode( + opus_int16 *pNLSF_Q15, /* O Quantized NLSF vector [ LPC_ORDER ] */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + const silk_NLSF_CB_struct *psNLSF_CB /* I Codebook object */ +) +{ + opus_int i; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_int16 res_Q10[ MAX_LPC_ORDER ]; + opus_int32 NLSF_Q15_tmp; + const opus_uint8 *pCB_element; + const opus_int16 *pCB_Wght_Q9; + + /* Unpack entropy table indices and predictor for current CB1 index */ + silk_NLSF_unpack( ec_ix, pred_Q8, psNLSF_CB, NLSFIndices[ 0 ] ); + + /* Predictive residual dequantizer */ + silk_NLSF_residual_dequant( res_Q10, &NLSFIndices[ 1 ], pred_Q8, psNLSF_CB->quantStepSize_Q16, psNLSF_CB->order ); + + /* Apply inverse square-rooted weights to first stage and add to output */ + pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; + for( i = 0; i < psNLSF_CB->order; i++ ) { + NLSF_Q15_tmp = silk_ADD_LSHIFT32( silk_DIV32_16( silk_LSHIFT( (opus_int32)res_Q10[ i ], 14 ), pCB_Wght_Q9[ i ] ), (opus_int16)pCB_element[ i ], 7 ); + pNLSF_Q15[ i ] = (opus_int16)silk_LIMIT( NLSF_Q15_tmp, 0, 32767 ); + } + + /* NLSF stabilization */ + silk_NLSF_stabilize( pNLSF_Q15, psNLSF_CB->deltaMin_Q15, psNLSF_CB->order ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_del_dec_quant.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_del_dec_quant.c new file mode 100644 index 0000000000..44a16acd0b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_del_dec_quant.c @@ -0,0 +1,215 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Delayed-decision quantizer for NLSF residuals */ +opus_int32 silk_NLSF_del_dec_quant( /* O Returns RD value in Q25 */ + opus_int8 indices[], /* O Quantization indices [ order ] */ + const opus_int16 x_Q10[], /* I Input [ order ] */ + const opus_int16 w_Q5[], /* I Weights [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int16 ec_ix[], /* I Indices to entropy coding tables [ order ] */ + const opus_uint8 ec_rates_Q5[], /* I Rates [] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 inv_quant_step_size_Q6, /* I Inverse quantization step size */ + const opus_int32 mu_Q20, /* I R/D tradeoff */ + const opus_int16 order /* I Number of input values */ +) +{ + opus_int i, j, nStates, ind_tmp, ind_min_max, ind_max_min, in_Q10, res_Q10; + opus_int pred_Q10, diff_Q10, rate0_Q5, rate1_Q5; + opus_int16 out0_Q10, out1_Q10; + opus_int32 RD_tmp_Q25, min_Q25, min_max_Q25, max_min_Q25; + opus_int ind_sort[ NLSF_QUANT_DEL_DEC_STATES ]; + opus_int8 ind[ NLSF_QUANT_DEL_DEC_STATES ][ MAX_LPC_ORDER ]; + opus_int16 prev_out_Q10[ 2 * NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_Q25[ 2 * NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_min_Q25[ NLSF_QUANT_DEL_DEC_STATES ]; + opus_int32 RD_max_Q25[ NLSF_QUANT_DEL_DEC_STATES ]; + const opus_uint8 *rates_Q5; + + opus_int out0_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + opus_int out1_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + + for (i = -NLSF_QUANT_MAX_AMPLITUDE_EXT; i <= NLSF_QUANT_MAX_AMPLITUDE_EXT-1; i++) + { + out0_Q10 = silk_LSHIFT( i, 10 ); + out1_Q10 = silk_ADD16( out0_Q10, 1024 ); + if( i > 0 ) { + out0_Q10 = silk_SUB16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == 0 ) { + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == -1 ) { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_ADD16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } + out0_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out0_Q10, quant_step_size_Q16 ), 16 ); + out1_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out1_Q10, quant_step_size_Q16 ), 16 ); + } + + silk_assert( (NLSF_QUANT_DEL_DEC_STATES & (NLSF_QUANT_DEL_DEC_STATES-1)) == 0 ); /* must be power of two */ + + nStates = 1; + RD_Q25[ 0 ] = 0; + prev_out_Q10[ 0 ] = 0; + for( i = order - 1; i >= 0; i-- ) { + rates_Q5 = &ec_rates_Q5[ ec_ix[ i ] ]; + in_Q10 = x_Q10[ i ]; + for( j = 0; j < nStates; j++ ) { + pred_Q10 = silk_RSHIFT( silk_SMULBB( (opus_int16)pred_coef_Q8[ i ], prev_out_Q10[ j ] ), 8 ); + res_Q10 = silk_SUB16( in_Q10, pred_Q10 ); + ind_tmp = silk_RSHIFT( silk_SMULBB( inv_quant_step_size_Q6, res_Q10 ), 16 ); + ind_tmp = silk_LIMIT( ind_tmp, -NLSF_QUANT_MAX_AMPLITUDE_EXT, NLSF_QUANT_MAX_AMPLITUDE_EXT-1 ); + ind[ j ][ i ] = (opus_int8)ind_tmp; + + /* compute outputs for ind_tmp and ind_tmp + 1 */ + out0_Q10 = out0_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + out1_Q10 = out1_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + + out0_Q10 = silk_ADD16( out0_Q10, pred_Q10 ); + out1_Q10 = silk_ADD16( out1_Q10, pred_Q10 ); + prev_out_Q10[ j ] = out0_Q10; + prev_out_Q10[ j + nStates ] = out1_Q10; + + /* compute RD for ind_tmp and ind_tmp + 1 */ + if( ind_tmp + 1 >= NLSF_QUANT_MAX_AMPLITUDE ) { + if( ind_tmp + 1 == NLSF_QUANT_MAX_AMPLITUDE ) { + rate0_Q5 = rates_Q5[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE ]; + rate1_Q5 = 280; + } else { + rate0_Q5 = silk_SMLABB( 280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, 43, ind_tmp ); + rate1_Q5 = silk_ADD16( rate0_Q5, 43 ); + } + } else if( ind_tmp <= -NLSF_QUANT_MAX_AMPLITUDE ) { + if( ind_tmp == -NLSF_QUANT_MAX_AMPLITUDE ) { + rate0_Q5 = 280; + rate1_Q5 = rates_Q5[ ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE ]; + } else { + rate0_Q5 = silk_SMLABB( 280 - 43 * NLSF_QUANT_MAX_AMPLITUDE, -43, ind_tmp ); + rate1_Q5 = silk_SUB16( rate0_Q5, 43 ); + } + } else { + rate0_Q5 = rates_Q5[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE ]; + rate1_Q5 = rates_Q5[ ind_tmp + 1 + NLSF_QUANT_MAX_AMPLITUDE ]; + } + RD_tmp_Q25 = RD_Q25[ j ]; + diff_Q10 = silk_SUB16( in_Q10, out0_Q10 ); + RD_Q25[ j ] = silk_SMLABB( silk_MLA( RD_tmp_Q25, silk_SMULBB( diff_Q10, diff_Q10 ), w_Q5[ i ] ), mu_Q20, rate0_Q5 ); + diff_Q10 = silk_SUB16( in_Q10, out1_Q10 ); + RD_Q25[ j + nStates ] = silk_SMLABB( silk_MLA( RD_tmp_Q25, silk_SMULBB( diff_Q10, diff_Q10 ), w_Q5[ i ] ), mu_Q20, rate1_Q5 ); + } + + if( nStates <= NLSF_QUANT_DEL_DEC_STATES/2 ) { + /* double number of states and copy */ + for( j = 0; j < nStates; j++ ) { + ind[ j + nStates ][ i ] = ind[ j ][ i ] + 1; + } + nStates = silk_LSHIFT( nStates, 1 ); + for( j = nStates; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + ind[ j ][ i ] = ind[ j - nStates ][ i ]; + } + } else { + /* sort lower and upper half of RD_Q25, pairwise */ + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( RD_Q25[ j ] > RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ] ) { + RD_max_Q25[ j ] = RD_Q25[ j ]; + RD_min_Q25[ j ] = RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ]; + RD_Q25[ j ] = RD_min_Q25[ j ]; + RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ] = RD_max_Q25[ j ]; + /* swap prev_out values */ + out0_Q10 = prev_out_Q10[ j ]; + prev_out_Q10[ j ] = prev_out_Q10[ j + NLSF_QUANT_DEL_DEC_STATES ]; + prev_out_Q10[ j + NLSF_QUANT_DEL_DEC_STATES ] = out0_Q10; + ind_sort[ j ] = j + NLSF_QUANT_DEL_DEC_STATES; + } else { + RD_min_Q25[ j ] = RD_Q25[ j ]; + RD_max_Q25[ j ] = RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ]; + ind_sort[ j ] = j; + } + } + /* compare the highest RD values of the winning half with the lowest one in the losing half, and copy if necessary */ + /* afterwards ind_sort[] will contain the indices of the NLSF_QUANT_DEL_DEC_STATES winning RD values */ + while( 1 ) { + min_max_Q25 = silk_int32_MAX; + max_min_Q25 = 0; + ind_min_max = 0; + ind_max_min = 0; + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( min_max_Q25 > RD_max_Q25[ j ] ) { + min_max_Q25 = RD_max_Q25[ j ]; + ind_min_max = j; + } + if( max_min_Q25 < RD_min_Q25[ j ] ) { + max_min_Q25 = RD_min_Q25[ j ]; + ind_max_min = j; + } + } + if( min_max_Q25 >= max_min_Q25 ) { + break; + } + /* copy ind_min_max to ind_max_min */ + ind_sort[ ind_max_min ] = ind_sort[ ind_min_max ] ^ NLSF_QUANT_DEL_DEC_STATES; + RD_Q25[ ind_max_min ] = RD_Q25[ ind_min_max + NLSF_QUANT_DEL_DEC_STATES ]; + prev_out_Q10[ ind_max_min ] = prev_out_Q10[ ind_min_max + NLSF_QUANT_DEL_DEC_STATES ]; + RD_min_Q25[ ind_max_min ] = 0; + RD_max_Q25[ ind_min_max ] = silk_int32_MAX; + silk_memcpy( ind[ ind_max_min ], ind[ ind_min_max ], MAX_LPC_ORDER * sizeof( opus_int8 ) ); + } + /* increment index if it comes from the upper half */ + for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { + ind[ j ][ i ] += silk_RSHIFT( ind_sort[ j ], NLSF_QUANT_DEL_DEC_STATES_LOG2 ); + } + } + } + + /* last sample: find winner, copy indices and return RD value */ + ind_tmp = 0; + min_Q25 = silk_int32_MAX; + for( j = 0; j < 2 * NLSF_QUANT_DEL_DEC_STATES; j++ ) { + if( min_Q25 > RD_Q25[ j ] ) { + min_Q25 = RD_Q25[ j ]; + ind_tmp = j; + } + } + for( j = 0; j < order; j++ ) { + indices[ j ] = ind[ ind_tmp & ( NLSF_QUANT_DEL_DEC_STATES - 1 ) ][ j ]; + silk_assert( indices[ j ] >= -NLSF_QUANT_MAX_AMPLITUDE_EXT ); + silk_assert( indices[ j ] <= NLSF_QUANT_MAX_AMPLITUDE_EXT ); + } + indices[ 0 ] += silk_RSHIFT( ind_tmp, NLSF_QUANT_DEL_DEC_STATES_LOG2 ); + silk_assert( indices[ 0 ] <= NLSF_QUANT_MAX_AMPLITUDE_EXT ); + silk_assert( min_Q25 >= 0 ); + return min_Q25; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_encode.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_encode.c new file mode 100644 index 0000000000..01ac7db78c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_encode.c @@ -0,0 +1,124 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/***********************/ +/* NLSF vector encoder */ +/***********************/ +opus_int32 silk_NLSF_encode( /* O Returns RD value in Q25 */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + opus_int16 *pNLSF_Q15, /* I/O (Un)quantized NLSF vector [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int16 *pW_Q2, /* I NLSF weight vector [ LPC_ORDER ] */ + const opus_int NLSF_mu_Q20, /* I Rate weight for the RD optimization */ + const opus_int nSurvivors, /* I Max survivors after first stage */ + const opus_int signalType /* I Signal type: 0/1/2 */ +) +{ + opus_int i, s, ind1, bestIndex, prob_Q8, bits_q7; + opus_int32 W_tmp_Q9, ret; + VARDECL( opus_int32, err_Q24 ); + VARDECL( opus_int32, RD_Q25 ); + VARDECL( opus_int, tempIndices1 ); + VARDECL( opus_int8, tempIndices2 ); + opus_int16 res_Q10[ MAX_LPC_ORDER ]; + opus_int16 NLSF_tmp_Q15[ MAX_LPC_ORDER ]; + opus_int16 W_adj_Q5[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + const opus_uint8 *pCB_element, *iCDF_ptr; + const opus_int16 *pCB_Wght_Q9; + SAVE_STACK; + + celt_assert( signalType >= 0 && signalType <= 2 ); + silk_assert( NLSF_mu_Q20 <= 32767 && NLSF_mu_Q20 >= 0 ); + + /* NLSF stabilization */ + silk_NLSF_stabilize( pNLSF_Q15, psNLSF_CB->deltaMin_Q15, psNLSF_CB->order ); + + /* First stage: VQ */ + ALLOC( err_Q24, psNLSF_CB->nVectors, opus_int32 ); + silk_NLSF_VQ( err_Q24, pNLSF_Q15, psNLSF_CB->CB1_NLSF_Q8, psNLSF_CB->CB1_Wght_Q9, psNLSF_CB->nVectors, psNLSF_CB->order ); + + /* Sort the quantization errors */ + ALLOC( tempIndices1, nSurvivors, opus_int ); + silk_insertion_sort_increasing( err_Q24, tempIndices1, psNLSF_CB->nVectors, nSurvivors ); + + ALLOC( RD_Q25, nSurvivors, opus_int32 ); + ALLOC( tempIndices2, nSurvivors * MAX_LPC_ORDER, opus_int8 ); + + /* Loop over survivors */ + for( s = 0; s < nSurvivors; s++ ) { + ind1 = tempIndices1[ s ]; + + /* Residual after first stage */ + pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ ind1 * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ ind1 * psNLSF_CB->order ]; + for( i = 0; i < psNLSF_CB->order; i++ ) { + NLSF_tmp_Q15[ i ] = silk_LSHIFT16( (opus_int16)pCB_element[ i ], 7 ); + W_tmp_Q9 = pCB_Wght_Q9[ i ]; + res_Q10[ i ] = (opus_int16)silk_RSHIFT( silk_SMULBB( pNLSF_Q15[ i ] - NLSF_tmp_Q15[ i ], W_tmp_Q9 ), 14 ); + W_adj_Q5[ i ] = silk_DIV32_varQ( (opus_int32)pW_Q2[ i ], silk_SMULBB( W_tmp_Q9, W_tmp_Q9 ), 21 ); + } + + /* Unpack entropy table indices and predictor for current CB1 index */ + silk_NLSF_unpack( ec_ix, pred_Q8, psNLSF_CB, ind1 ); + + /* Trellis quantizer */ + RD_Q25[ s ] = silk_NLSF_del_dec_quant( &tempIndices2[ s * MAX_LPC_ORDER ], res_Q10, W_adj_Q5, pred_Q8, ec_ix, + psNLSF_CB->ec_Rates_Q5, psNLSF_CB->quantStepSize_Q16, psNLSF_CB->invQuantStepSize_Q6, NLSF_mu_Q20, psNLSF_CB->order ); + + /* Add rate for first stage */ + iCDF_ptr = &psNLSF_CB->CB1_iCDF[ ( signalType >> 1 ) * psNLSF_CB->nVectors ]; + if( ind1 == 0 ) { + prob_Q8 = 256 - iCDF_ptr[ ind1 ]; + } else { + prob_Q8 = iCDF_ptr[ ind1 - 1 ] - iCDF_ptr[ ind1 ]; + } + bits_q7 = ( 8 << 7 ) - silk_lin2log( prob_Q8 ); + RD_Q25[ s ] = silk_SMLABB( RD_Q25[ s ], bits_q7, silk_RSHIFT( NLSF_mu_Q20, 2 ) ); + } + + /* Find the lowest rate-distortion error */ + silk_insertion_sort_increasing( RD_Q25, &bestIndex, nSurvivors, 1 ); + + NLSFIndices[ 0 ] = (opus_int8)tempIndices1[ bestIndex ]; + silk_memcpy( &NLSFIndices[ 1 ], &tempIndices2[ bestIndex * MAX_LPC_ORDER ], psNLSF_CB->order * sizeof( opus_int8 ) ); + + /* Decode */ + silk_NLSF_decode( pNLSF_Q15, NLSFIndices, psNLSF_CB ); + + ret = RD_Q25[ 0 ]; + RESTORE_STACK; + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_stabilize.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_stabilize.c new file mode 100644 index 0000000000..8f3426b91e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_stabilize.c @@ -0,0 +1,142 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* NLSF stabilizer: */ +/* */ +/* - Moves NLSFs further apart if they are too close */ +/* - Moves NLSFs away from borders if they are too close */ +/* - High effort to achieve a modification with minimum */ +/* Euclidean distance to input vector */ +/* - Output are sorted NLSF coefficients */ +/* */ + +#include "SigProc_FIX.h" + +/* Constant Definitions */ +#define MAX_LOOPS 20 + +/* NLSF stabilizer, for a single input data vector */ +void silk_NLSF_stabilize( + opus_int16 *NLSF_Q15, /* I/O Unstable/stabilized normalized LSF vector in Q15 [L] */ + const opus_int16 *NDeltaMin_Q15, /* I Min distance vector, NDeltaMin_Q15[L] must be >= 1 [L+1] */ + const opus_int L /* I Number of NLSF parameters in the input vector */ +) +{ + opus_int i, I=0, k, loops; + opus_int16 center_freq_Q15; + opus_int32 diff_Q15, min_diff_Q15, min_center_Q15, max_center_Q15; + + /* This is necessary to ensure an output within range of a opus_int16 */ + silk_assert( NDeltaMin_Q15[L] >= 1 ); + + for( loops = 0; loops < MAX_LOOPS; loops++ ) { + /**************************/ + /* Find smallest distance */ + /**************************/ + /* First element */ + min_diff_Q15 = NLSF_Q15[0] - NDeltaMin_Q15[0]; + I = 0; + /* Middle elements */ + for( i = 1; i <= L-1; i++ ) { + diff_Q15 = NLSF_Q15[i] - ( NLSF_Q15[i-1] + NDeltaMin_Q15[i] ); + if( diff_Q15 < min_diff_Q15 ) { + min_diff_Q15 = diff_Q15; + I = i; + } + } + /* Last element */ + diff_Q15 = ( 1 << 15 ) - ( NLSF_Q15[L-1] + NDeltaMin_Q15[L] ); + if( diff_Q15 < min_diff_Q15 ) { + min_diff_Q15 = diff_Q15; + I = L; + } + + /***************************************************/ + /* Now check if the smallest distance non-negative */ + /***************************************************/ + if( min_diff_Q15 >= 0 ) { + return; + } + + if( I == 0 ) { + /* Move away from lower limit */ + NLSF_Q15[0] = NDeltaMin_Q15[0]; + + } else if( I == L) { + /* Move away from higher limit */ + NLSF_Q15[L-1] = ( 1 << 15 ) - NDeltaMin_Q15[L]; + + } else { + /* Find the lower extreme for the location of the current center frequency */ + min_center_Q15 = 0; + for( k = 0; k < I; k++ ) { + min_center_Q15 += NDeltaMin_Q15[k]; + } + min_center_Q15 += silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + + /* Find the upper extreme for the location of the current center frequency */ + max_center_Q15 = 1 << 15; + for( k = L; k > I; k-- ) { + max_center_Q15 -= NDeltaMin_Q15[k]; + } + max_center_Q15 -= silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + + /* Move apart, sorted by value, keeping the same center frequency */ + center_freq_Q15 = (opus_int16)silk_LIMIT_32( silk_RSHIFT_ROUND( (opus_int32)NLSF_Q15[I-1] + (opus_int32)NLSF_Q15[I], 1 ), + min_center_Q15, max_center_Q15 ); + NLSF_Q15[I-1] = center_freq_Q15 - silk_RSHIFT( NDeltaMin_Q15[I], 1 ); + NLSF_Q15[I] = NLSF_Q15[I-1] + NDeltaMin_Q15[I]; + } + } + + /* Safe and simple fall back method, which is less ideal than the above */ + if( loops == MAX_LOOPS ) + { + /* Insertion sort (fast for already almost sorted arrays): */ + /* Best case: O(n) for an already sorted array */ + /* Worst case: O(n^2) for an inversely sorted array */ + silk_insertion_sort_increasing_all_values_int16( &NLSF_Q15[0], L ); + + /* First NLSF should be no less than NDeltaMin[0] */ + NLSF_Q15[0] = silk_max_int( NLSF_Q15[0], NDeltaMin_Q15[0] ); + + /* Keep delta_min distance between the NLSFs */ + for( i = 1; i < L; i++ ) + NLSF_Q15[i] = silk_max_int( NLSF_Q15[i], silk_ADD_SAT16( NLSF_Q15[i-1], NDeltaMin_Q15[i] ) ); + + /* Last NLSF should be no higher than 1 - NDeltaMin[L] */ + NLSF_Q15[L-1] = silk_min_int( NLSF_Q15[L-1], (1<<15) - NDeltaMin_Q15[L] ); + + /* Keep NDeltaMin distance between the NLSFs */ + for( i = L-2; i >= 0; i-- ) + NLSF_Q15[i] = silk_min_int( NLSF_Q15[i], NLSF_Q15[i+1] - NDeltaMin_Q15[i+1] ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_unpack.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_unpack.c new file mode 100644 index 0000000000..17bd23f752 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NLSF_unpack.c @@ -0,0 +1,55 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Unpack predictor values and indices for entropy coding tables */ +void silk_NLSF_unpack( + opus_int16 ec_ix[], /* O Indices to entropy tables [ LPC_ORDER ] */ + opus_uint8 pred_Q8[], /* O LSF predictor [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int CB1_index /* I Index of vector in first LSF codebook */ +) +{ + opus_int i; + opus_uint8 entry; + const opus_uint8 *ec_sel_ptr; + + ec_sel_ptr = &psNLSF_CB->ec_sel[ CB1_index * psNLSF_CB->order / 2 ]; + for( i = 0; i < psNLSF_CB->order; i += 2 ) { + entry = *ec_sel_ptr++; + ec_ix [ i ] = silk_SMULBB( silk_RSHIFT( entry, 1 ) & 7, 2 * NLSF_QUANT_MAX_AMPLITUDE + 1 ); + pred_Q8[ i ] = psNLSF_CB->pred_Q8[ i + ( entry & 1 ) * ( psNLSF_CB->order - 1 ) ]; + ec_ix [ i + 1 ] = silk_SMULBB( silk_RSHIFT( entry, 5 ) & 7, 2 * NLSF_QUANT_MAX_AMPLITUDE + 1 ); + pred_Q8[ i + 1 ] = psNLSF_CB->pred_Q8[ i + ( silk_RSHIFT( entry, 4 ) & 1 ) * ( psNLSF_CB->order - 1 ) + 1 ]; + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.c new file mode 100644 index 0000000000..1caa829bbe --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.c @@ -0,0 +1,437 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" + + +static OPUS_INLINE void silk_nsq_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +); + +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +); +#endif + +void silk_NSQ_c +( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int k, lag, start_idx, LSF_interpolation_flag; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + SAVE_STACK; + + NSQ->rand_seed = psIndices->Seed; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + /* Set up pointers to start of sub frame */ + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ (( k >> 1 ) | ( 1 - LSF_interpolation_flag )) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->rewhite_flag = 1; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + } + } + + silk_nsq_scale_states( psEncC, NSQ, x16, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); + + silk_noise_shape_quantizer( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, + offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder, psEncC->arch ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Update lagPrev for next frame */ + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech and noise shaping signals */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/******************************/ +/* silk_noise_shape_quantizer */ +/******************************/ + +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +) +{ + opus_int i; + opus_int32 LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13; + opus_int32 n_LF_Q12, r_Q10, rr_Q10, q1_Q0, q1_Q10, q2_Q10, rd1_Q20, rd2_Q20; + opus_int32 exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *psLPC_Q14, *shp_lag_ptr, *pred_lag_ptr; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + /* Set up short term AR state */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 ]; + +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + + for( i = 0; i < length; i++ ) { + /* Generate dither */ + NSQ->rand_seed = silk_RAND( NSQ->rand_seed ); + + /* Short-term prediction */ + LPC_pred_Q10 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ 0 ], b_Q14[ 0 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -1 ], b_Q14[ 1 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -2 ], b_Q14[ 2 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -3 ], b_Q14[ 3 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + pred_lag_ptr++; + } else { + LTP_pred_Q13 = 0; + } + + /* Noise shape feedback */ + celt_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + n_AR_Q12 = silk_NSQ_noise_shape_feedback_loop(&NSQ->sDiff_shp_Q14, NSQ->sAR2_Q14, AR_shp_Q13, shapingLPCOrder, arch); + + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sLF_AR_shp_Q14, Tilt_Q14 ); + + n_LF_Q12 = silk_SMULWB( NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - 1 ], LF_shp_Q14 ); + n_LF_Q12 = silk_SMLAWT( n_LF_Q12, NSQ->sLF_AR_shp_Q14, LF_shp_Q14 ); + + celt_assert( lag > 0 || signalType != TYPE_VOICED ); + + /* Combine prediction and noise shaping signals */ + tmp1 = silk_SUB32_ovflw( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */ + tmp1 = silk_SUB32_ovflw( tmp1, n_LF_Q12 ); /* Q12 */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q13 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_SMLAWT( n_LTP_Q13, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_LSHIFT( n_LTP_Q13, 1 ); + shp_lag_ptr++; + + tmp2 = silk_SUB32( LTP_pred_Q13, n_LTP_Q13 ); /* Q13 */ + tmp1 = silk_ADD32_ovflw( tmp2, silk_LSHIFT32( tmp1, 1 ) ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 3 ); /* Q10 */ + } else { + tmp1 = silk_RSHIFT_ROUND( tmp1, 2 ); /* Q10 */ + } + + r_Q10 = silk_SUB32( x_sc_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if( NSQ->rand_seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q20 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* Q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q20 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q20 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q20 = silk_SMLABB( rd1_Q20, rr_Q10, rr_Q10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q20 = silk_SMLABB( rd2_Q20, rr_Q10, rr_Q10 ); + + if( rd2_Q20 < rd1_Q20 ) { + q1_Q10 = q2_Q10; + } + + pulses[ i ] = (opus_int8)silk_RSHIFT_ROUND( q1_Q10, 10 ); + + /* Excitation */ + exc_Q14 = silk_LSHIFT( q1_Q10, 4 ); + if ( NSQ->rand_seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD_LSHIFT32( exc_Q14, LTP_pred_Q13, 1 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, silk_LSHIFT32( LPC_pred_Q10, 4 ) ); + + /* Scale XQ back to normal level before saving */ + xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( xq_Q14, Gain_Q10 ), 8 ) ); + + /* Update states */ + psLPC_Q14++; + *psLPC_Q14 = xq_Q14; + NSQ->sDiff_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_sc_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( NSQ->sDiff_shp_Q14, silk_LSHIFT32( n_AR_Q12, 2 ) ); + NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14; + + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB32_ovflw(sLF_AR_shp_Q14, silk_LSHIFT32(n_LF_Q12, 2)); + sLTP_Q15[ NSQ->sLTP_buf_idx ] = silk_LSHIFT( LPC_exc_Q14, 1 ); + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Make dither dependent on quantized signal */ + NSQ->rand_seed = silk_ADD32_ovflw( NSQ->rand_seed, pulses[ i ] ); + } + + /* Update LPC synth buffer */ + silk_memcpy( NSQ->sLPC_Q14, &NSQ->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); +} + +static OPUS_INLINE void silk_nsq_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +) +{ + opus_int i, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + for( i = 0; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + NSQ->sLF_AR_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sLF_AR_shp_Q14 ); + NSQ->sDiff_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sDiff_shp_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + NSQ->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + NSQ->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sAR2_Q14[ i ] ); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.h new file mode 100644 index 0000000000..971832f660 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ.h @@ -0,0 +1,101 @@ +/*********************************************************************** +Copyright (c) 2014 Vidyo. +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifndef SILK_NSQ_H +#define SILK_NSQ_H + +#include "SigProc_FIX.h" + +#undef silk_short_prediction_create_arch_coef + +static OPUS_INLINE opus_int32 silk_noise_shape_quantizer_short_prediction_c(const opus_int32 *buf32, const opus_int16 *coef16, opus_int order) +{ + opus_int32 out; + silk_assert( order == 10 || order == 16 ); + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + out = silk_RSHIFT( order, 1 ); + out = silk_SMLAWB( out, buf32[ 0 ], coef16[ 0 ] ); + out = silk_SMLAWB( out, buf32[ -1 ], coef16[ 1 ] ); + out = silk_SMLAWB( out, buf32[ -2 ], coef16[ 2 ] ); + out = silk_SMLAWB( out, buf32[ -3 ], coef16[ 3 ] ); + out = silk_SMLAWB( out, buf32[ -4 ], coef16[ 4 ] ); + out = silk_SMLAWB( out, buf32[ -5 ], coef16[ 5 ] ); + out = silk_SMLAWB( out, buf32[ -6 ], coef16[ 6 ] ); + out = silk_SMLAWB( out, buf32[ -7 ], coef16[ 7 ] ); + out = silk_SMLAWB( out, buf32[ -8 ], coef16[ 8 ] ); + out = silk_SMLAWB( out, buf32[ -9 ], coef16[ 9 ] ); + + if( order == 16 ) + { + out = silk_SMLAWB( out, buf32[ -10 ], coef16[ 10 ] ); + out = silk_SMLAWB( out, buf32[ -11 ], coef16[ 11 ] ); + out = silk_SMLAWB( out, buf32[ -12 ], coef16[ 12 ] ); + out = silk_SMLAWB( out, buf32[ -13 ], coef16[ 13 ] ); + out = silk_SMLAWB( out, buf32[ -14 ], coef16[ 14 ] ); + out = silk_SMLAWB( out, buf32[ -15 ], coef16[ 15 ] ); + } + return out; +} + +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) ((void)arch,silk_noise_shape_quantizer_short_prediction_c(in, coef, order)) + +static OPUS_INLINE opus_int32 silk_NSQ_noise_shape_feedback_loop_c(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order) +{ + opus_int32 out; + opus_int32 tmp1, tmp2; + opus_int j; + + tmp2 = data0[0]; + tmp1 = data1[0]; + data1[0] = tmp2; + + out = silk_RSHIFT(order, 1); + out = silk_SMLAWB(out, tmp2, coef[0]); + + for (j = 2; j < order; j += 2) { + tmp2 = data1[j - 1]; + data1[j - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[j - 1]); + tmp1 = data1[j + 0]; + data1[j + 0] = tmp2; + out = silk_SMLAWB(out, tmp2, coef[j]); + } + data1[order - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[order - 1]); + /* Q11 -> Q12 */ + out = silk_LSHIFT32( out, 1 ); + return out; +} + +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) ((void)arch,silk_NSQ_noise_shape_feedback_loop_c(data0, data1, coef, order)) + +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +#include "arm/NSQ_neon.h" +#endif + +#endif /* SILK_NSQ_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ_del_dec.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ_del_dec.c new file mode 100644 index 0000000000..e8dadf1591 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/NSQ_del_dec.c @@ -0,0 +1,733 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" + + +typedef struct { + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 RandState[ DECISION_DELAY ]; + opus_int32 Q_Q10[ DECISION_DELAY ]; + opus_int32 Xq_Q14[ DECISION_DELAY ]; + opus_int32 Pred_Q15[ DECISION_DELAY ]; + opus_int32 Shape_Q14[ DECISION_DELAY ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 Seed; + opus_int32 SeedInit; + opus_int32 RD_Q10; +} NSQ_del_dec_struct; + +typedef struct { + opus_int32 Q_Q10; + opus_int32 RD_Q10; + opus_int32 xq_Q14; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 sLTP_shp_Q14; + opus_int32 LPC_exc_Q14; +} NSQ_sample_struct; + +typedef NSQ_sample_struct NSQ_sample_pair[ 2 ]; + +#if defined(MIPSr1_ASM) +#include "mips/NSQ_del_dec_mipsr1.h" +#endif +static OPUS_INLINE void silk_nsq_del_dec_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay, /* I */ + int arch /* I */ +); + +void silk_NSQ_del_dec_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int last_smple_idx, smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 RDmin_Q10, Gain_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + VARDECL( opus_int32, delayedGain_Q10 ); + VARDECL( NSQ_del_dec_struct, psDelDec ); + NSQ_del_dec_struct *psDD; + SAVE_STACK; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + /* Initialize delayed decision states */ + ALLOC( psDelDec, psEncC->nStatesDelayedDecision, NSQ_del_dec_struct ); + silk_memset( psDelDec, 0, psEncC->nStatesDelayedDecision * sizeof( NSQ_del_dec_struct ) ); + for( k = 0; k < psEncC->nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psDD->Seed = ( k + psIndices->Seed ) & 3; + psDD->SeedInit = psDD->Seed; + psDD->RD_Q10 = 0; + psDD->LF_AR_Q14 = NSQ->sLF_AR_shp_Q14; + psDD->Diff_Q14 = NSQ->sDiff_shp_Q14; + psDD->Shape_Q14[ 0 ] = NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ]; + silk_memcpy( psDD->sLPC_Q14, NSQ->sLPC_Q14, NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( psDD->sAR2_Q14, NSQ->sAR2_Q14, sizeof( NSQ->sAR2_Q14 ) ); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int( DECISION_DELAY, psEncC->subfr_length ); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if( psIndices->signalType == TYPE_VOICED ) { + for( k = 0; k < psEncC->nb_subfr; k++ ) { + decisionDelay = silk_min_int( decisionDelay, pitchL[ k ] - LTP_ORDER / 2 - 1 ); + } + } else { + if( lag > 0 ) { + decisionDelay = silk_min_int( decisionDelay, lag - LTP_ORDER / 2 - 1 ); + } + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + if( k == 2 ) { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( i = 1; i < psEncC->nStatesDelayedDecision; i++ ) { + if( psDelDec[ i ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ i ].RD_Q10; + Winner_ind = i; + } + } + for( i = 0; i < psEncC->nStatesDelayedDecision; i++ ) { + if( i != Winner_ind ) { + psDelDec[ i ].RD_Q10 += ( silk_int32_MAX >> 4 ); + silk_assert( psDelDec[ i ].RD_Q10 >= 0 ); + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + last_smple_idx = smpl_buf_idx + decisionDelay; + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gains_Q16[ 1 ] ), 14 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states( psEncC, NSQ, psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, + psEncC->nStatesDelayedDecision, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); + + silk_noise_shape_quantizer_del_dec( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], + Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay, psEncC->arch ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < psEncC->nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ k ].RD_Q10; + Winner_ind = k; + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + psIndices->Seed = psDD->SeedInit; + last_smple_idx = smpl_buf_idx + decisionDelay; + Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gain_Q10 ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + silk_memcpy( NSQ->sLPC_Q14, &psDD->sLPC_Q14[ psEncC->subfr_length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( NSQ->sAR2_Q14, psDD->sAR2_Q14, sizeof( psDD->sAR2_Q14 ) ); + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = psDD->LF_AR_Q14; + NSQ->sDiff_shp_Q14 = psDD->Diff_Q14; + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech signal */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +#ifndef OVERRIDE_silk_noise_shape_quantizer_del_dec +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay, /* I */ + int arch /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14; + opus_int32 n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10; + opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif + + VARDECL( NSQ_sample_pair, psSampleState ); + NSQ_del_dec_struct *psDD; + NSQ_sample_struct *psSS; + SAVE_STACK; + + celt_assert( nStatesDelayedDecision > 0 ); + ALLOC( psSampleState, nStatesDelayedDecision, NSQ_sample_pair ); + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + + for( i = 0; i < length; i++ ) { + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ 0 ], b_Q14[ 0 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -1 ], b_Q14[ 1 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -2 ], b_Q14[ 2 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -3 ], b_Q14[ 3 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + /* Delayed decision state */ + psDD = &psDelDec[ k ]; + + /* Sample state */ + psSS = psSampleState[ k ]; + + /* Generate dither */ + psDD->Seed = silk_RAND( psDD->Seed ); + + /* Pointer used in short term prediction and shaping */ + psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; + /* Short-term prediction */ + LPC_pred_Q14 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); + LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + celt_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + /* Output of lowpass section */ + tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ 0 ] = tmp2; + n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 ); + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] ); + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + /* Output of allpass section */ + tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 0 ], tmp1), warping_Q16 ); + psDD->sAR2_Q14[ j - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ j + 0 ] = tmp2; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] ); + } + psDD->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 1 ); /* Q11 -> Q12 */ + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, psDD->LF_AR_Q14, Tilt_Q14 ); /* Q12 */ + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 2 ); /* Q12 -> Q14 */ + + n_LF_Q14 = silk_SMULWB( psDD->Shape_Q14[ *smpl_buf_idx ], LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_SMLAWT( n_LF_Q14, psDD->LF_AR_Q14, LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_LSHIFT( n_LF_Q14, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1 = silk_ADD_SAT32( n_AR_Q14, n_LF_Q14 ); /* Q14 */ + tmp2 = silk_ADD32_ovflw( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */ + tmp1 = silk_SUB_SAT32( tmp2, tmp1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */ + + r_Q10 = silk_SUB32( x_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if ( psDD->Seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q10 = silk_RSHIFT( silk_SMLABB( rd1_Q10, rr_Q10, rr_Q10 ), 10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q10 = silk_RSHIFT( silk_SMLABB( rd2_Q10, rr_Q10, rr_Q10 ), 10 ); + + if( rd1_Q10 < rd2_Q10 ) { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 0 ].Q_Q10 = q1_Q10; + psSS[ 1 ].Q_Q10 = q2_Q10; + } else { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 0 ].Q_Q10 = q2_Q10; + psSS[ 1 ].Q_Q10 = q1_Q10; + } + + /* Update states for best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 0 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 0 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 0 ].Diff_Q14, n_AR_Q14 ); + psSS[ 0 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 0 ].xq_Q14 = xq_Q14; + + /* Update states for second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 1 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 1 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 1 ].Diff_Q14, n_AR_Q14 ); + psSS[ 1 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 1 ].xq_Q14 = xq_Q14; + } + + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ k ][ 0 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + Winner_ind = k; + } + } + + /* Increase RD values of expired states */ + Winner_rand_state = psDelDec[ Winner_ind ].RandState[ last_smple_idx ]; + for( k = 0; k < nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RandState[ last_smple_idx ] != Winner_rand_state ) { + psSampleState[ k ][ 0 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 0 ].RD_Q10, silk_int32_MAX >> 4 ); + psSampleState[ k ][ 1 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 1 ].RD_Q10, silk_int32_MAX >> 4 ); + silk_assert( psSampleState[ k ][ 0 ].RD_Q10 >= 0 ); + } + } + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + RDmin_Q10 = psSampleState[ 0 ][ 1 ].RD_Q10; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ k ][ 0 ].RD_Q10 > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ k ][ 1 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 1 ].RD_Q10; + RDmin_ind = k; + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + silk_memcpy( ( (opus_int32 *)&psDelDec[ RDmax_ind ] ) + i, + ( (opus_int32 *)&psDelDec[ RDmin_ind ] ) + i, sizeof( NSQ_del_dec_struct ) - i * sizeof( opus_int32) ); + silk_memcpy( &psSampleState[ RDmax_ind ][ 0 ], &psSampleState[ RDmin_ind ][ 1 ], sizeof( NSQ_sample_struct ) ); + } + + /* Write samples from winner to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDD->Shape_Q14[ last_smple_idx ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDD->Pred_Q15[ last_smple_idx ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psSS = &psSampleState[ k ][ 0 ]; + psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->Diff_Q14 = psSS->Diff_Q14; + psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; + psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; + psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; + psDD->Pred_Q15[ *smpl_buf_idx ] = silk_LSHIFT32( psSS->LPC_exc_Q14, 1 ); + psDD->Shape_Q14[ *smpl_buf_idx ] = psSS->sLTP_shp_Q14; + psDD->Seed = silk_ADD32_ovflw( psDD->Seed, silk_RSHIFT_ROUND( psSS->Q_Q10, 10 ) ); + psDD->RandState[ *smpl_buf_idx ] = psDD->Seed; + psDD->RD_Q10 = psSS->RD_Q10; + } + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + silk_memcpy( psDD->sLPC_Q14, &psDD->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} +#endif /* OVERRIDE_silk_noise_shape_quantizer_del_dec */ + +static OPUS_INLINE void silk_nsq_del_dec_scale_states( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + opus_int i, k, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + NSQ_del_dec_struct *psDD; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + for( i = 0; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + + /* Scale scalar states */ + psDD->LF_AR_Q14 = silk_SMULWW( gain_adj_Q16, psDD->LF_AR_Q14 ); + psDD->Diff_Q14 = silk_SMULWW( gain_adj_Q16, psDD->Diff_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + psDD->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + psDD->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sAR2_Q14[ i ] ); + } + for( i = 0; i < DECISION_DELAY; i++ ) { + psDD->Pred_Q15[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Pred_Q15[ i ] ); + psDD->Shape_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Shape_Q14[ i ] ); + } + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.c new file mode 100644 index 0000000000..b35bf750a0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.c @@ -0,0 +1,493 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "PLC.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#endif + +#define NB_ATT 2 +static const opus_int16 HARM_ATT_Q15[NB_ATT] = { 32440, 31130 }; /* 0.99, 0.95 */ +static const opus_int16 PLC_RAND_ATTENUATE_V_Q15[NB_ATT] = { 31130, 26214 }; /* 0.95, 0.8 */ +static const opus_int16 PLC_RAND_ATTENUATE_UV_Q15[NB_ATT] = { 32440, 29491 }; /* 0.99, 0.9 */ + +static OPUS_INLINE void silk_PLC_update( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl /* I/O Decoder control */ +); + +static OPUS_INLINE void silk_PLC_conceal( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* O LPC residual signal */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + + +void silk_PLC_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +) +{ + psDec->sPLC.pitchL_Q8 = silk_LSHIFT( psDec->frame_length, 8 - 1 ); + psDec->sPLC.prevGain_Q16[ 0 ] = SILK_FIX_CONST( 1, 16 ); + psDec->sPLC.prevGain_Q16[ 1 ] = SILK_FIX_CONST( 1, 16 ); + psDec->sPLC.subfr_length = 20; + psDec->sPLC.nb_subfr = 2; +} + +void silk_PLC( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O signal */ + opus_int lost, /* I Loss flag */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + /* PLC control function */ + if( psDec->fs_kHz != psDec->sPLC.fs_kHz ) { + silk_PLC_Reset( psDec ); + psDec->sPLC.fs_kHz = psDec->fs_kHz; + } + + if( lost ) { + /****************************/ + /* Generate Signal */ + /****************************/ + silk_PLC_conceal( psDec, psDecCtrl, frame, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + + psDec->lossCnt++; + } else { + /****************************/ + /* Update state */ + /****************************/ + silk_PLC_update( psDec, psDecCtrl ); +#ifdef ENABLE_DEEP_PLC + if ( lpcnet != NULL && psDec->sPLC.fs_kHz == 16 ) { + int k; + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length ); + } + } +#endif + } +} + +/**************************************************/ +/* Update state of PLC */ +/**************************************************/ +static OPUS_INLINE void silk_PLC_update( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl /* I/O Decoder control */ +) +{ + opus_int32 LTP_Gain_Q14, temp_LTP_Gain_Q14; + opus_int i, j; + silk_PLC_struct *psPLC; + + psPLC = &psDec->sPLC; + + /* Update parameters used in case of packet loss */ + psDec->prevSignalType = psDec->indices.signalType; + LTP_Gain_Q14 = 0; + if( psDec->indices.signalType == TYPE_VOICED ) { + /* Find the parameters for the last subframe which contains a pitch pulse */ + for( j = 0; j * psDec->subfr_length < psDecCtrl->pitchL[ psDec->nb_subfr - 1 ]; j++ ) { + if( j == psDec->nb_subfr ) { + break; + } + temp_LTP_Gain_Q14 = 0; + for( i = 0; i < LTP_ORDER; i++ ) { + temp_LTP_Gain_Q14 += psDecCtrl->LTPCoef_Q14[ ( psDec->nb_subfr - 1 - j ) * LTP_ORDER + i ]; + } + if( temp_LTP_Gain_Q14 > LTP_Gain_Q14 ) { + LTP_Gain_Q14 = temp_LTP_Gain_Q14; + silk_memcpy( psPLC->LTPCoef_Q14, + &psDecCtrl->LTPCoef_Q14[ silk_SMULBB( psDec->nb_subfr - 1 - j, LTP_ORDER ) ], + LTP_ORDER * sizeof( opus_int16 ) ); + + psPLC->pitchL_Q8 = silk_LSHIFT( psDecCtrl->pitchL[ psDec->nb_subfr - 1 - j ], 8 ); + } + } + + silk_memset( psPLC->LTPCoef_Q14, 0, LTP_ORDER * sizeof( opus_int16 ) ); + psPLC->LTPCoef_Q14[ LTP_ORDER / 2 ] = LTP_Gain_Q14; + + /* Limit LT coefs */ + if( LTP_Gain_Q14 < V_PITCH_GAIN_START_MIN_Q14 ) { + opus_int scale_Q10; + opus_int32 tmp; + + tmp = silk_LSHIFT( V_PITCH_GAIN_START_MIN_Q14, 10 ); + scale_Q10 = silk_DIV32( tmp, silk_max( LTP_Gain_Q14, 1 ) ); + for( i = 0; i < LTP_ORDER; i++ ) { + psPLC->LTPCoef_Q14[ i ] = silk_RSHIFT( silk_SMULBB( psPLC->LTPCoef_Q14[ i ], scale_Q10 ), 10 ); + } + } else if( LTP_Gain_Q14 > V_PITCH_GAIN_START_MAX_Q14 ) { + opus_int scale_Q14; + opus_int32 tmp; + + tmp = silk_LSHIFT( V_PITCH_GAIN_START_MAX_Q14, 14 ); + scale_Q14 = silk_DIV32( tmp, silk_max( LTP_Gain_Q14, 1 ) ); + for( i = 0; i < LTP_ORDER; i++ ) { + psPLC->LTPCoef_Q14[ i ] = silk_RSHIFT( silk_SMULBB( psPLC->LTPCoef_Q14[ i ], scale_Q14 ), 14 ); + } + } + } else { + psPLC->pitchL_Q8 = silk_LSHIFT( silk_SMULBB( psDec->fs_kHz, 18 ), 8 ); + silk_memset( psPLC->LTPCoef_Q14, 0, LTP_ORDER * sizeof( opus_int16 )); + } + + /* Save LPC coeficients */ + silk_memcpy( psPLC->prevLPC_Q12, psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order * sizeof( opus_int16 ) ); + psPLC->prevLTP_scale_Q14 = psDecCtrl->LTP_scale_Q14; + + /* Save last two gains */ + silk_memcpy( psPLC->prevGain_Q16, &psDecCtrl->Gains_Q16[ psDec->nb_subfr - 2 ], 2 * sizeof( opus_int32 ) ); + + psPLC->subfr_length = psDec->subfr_length; + psPLC->nb_subfr = psDec->nb_subfr; +} + +static OPUS_INLINE void silk_PLC_energy(opus_int32 *energy1, opus_int *shift1, opus_int32 *energy2, opus_int *shift2, + const opus_int32 *exc_Q14, const opus_int32 *prevGain_Q10, int subfr_length, int nb_subfr) +{ + int i, k; + VARDECL( opus_int16, exc_buf ); + opus_int16 *exc_buf_ptr; + SAVE_STACK; + ALLOC( exc_buf, 2*subfr_length, opus_int16 ); + /* Find random noise component */ + /* Scale previous excitation signal */ + exc_buf_ptr = exc_buf; + for( k = 0; k < 2; k++ ) { + for( i = 0; i < subfr_length; i++ ) { + exc_buf_ptr[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT( + silk_SMULWW( exc_Q14[ i + ( k + nb_subfr - 2 ) * subfr_length ], prevGain_Q10[ k ] ), 8 ) ); + } + exc_buf_ptr += subfr_length; + } + /* Find the subframe with lowest energy of the last two and use that as random noise generator */ + silk_sum_sqr_shift( energy1, shift1, exc_buf, subfr_length ); + silk_sum_sqr_shift( energy2, shift2, &exc_buf[ subfr_length ], subfr_length ); + RESTORE_STACK; +} + +static OPUS_INLINE void silk_PLC_conceal( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* O LPC residual signal */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + opus_int i, j, k; + opus_int lag, idx, sLTP_buf_idx, shift1, shift2; + opus_int32 rand_seed, harm_Gain_Q15, rand_Gain_Q15, inv_gain_Q30; + opus_int32 energy1, energy2, *rand_ptr, *pred_lag_ptr; + opus_int32 LPC_pred_Q10, LTP_pred_Q12; + opus_int16 rand_scale_Q14; + opus_int16 *B_Q14; + opus_int32 *sLPC_Q14_ptr; + opus_int16 A_Q12[ MAX_LPC_ORDER ]; +#ifdef SMALL_FOOTPRINT + opus_int16 *sLTP; +#else + VARDECL( opus_int16, sLTP ); +#endif + VARDECL( opus_int32, sLTP_Q14 ); + silk_PLC_struct *psPLC = &psDec->sPLC; + opus_int32 prevGain_Q10[2]; + SAVE_STACK; + + ALLOC( sLTP_Q14, psDec->ltp_mem_length + psDec->frame_length, opus_int32 ); +#ifdef SMALL_FOOTPRINT + /* Ugly hack that breaks aliasing rules to save stack: put sLTP at the very end of sLTP_Q14. */ + sLTP = ((opus_int16*)&sLTP_Q14[psDec->ltp_mem_length + psDec->frame_length])-psDec->ltp_mem_length; +#else + ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); +#endif + + prevGain_Q10[0] = silk_RSHIFT( psPLC->prevGain_Q16[ 0 ], 6); + prevGain_Q10[1] = silk_RSHIFT( psPLC->prevGain_Q16[ 1 ], 6); + + if( psDec->first_frame_after_reset ) { + silk_memset( psPLC->prevLPC_Q12, 0, sizeof( psPLC->prevLPC_Q12 ) ); + } + + silk_PLC_energy(&energy1, &shift1, &energy2, &shift2, psDec->exc_Q14, prevGain_Q10, psDec->subfr_length, psDec->nb_subfr); + + if( silk_RSHIFT( energy1, shift2 ) < silk_RSHIFT( energy2, shift1 ) ) { + /* First sub-frame has lowest energy */ + rand_ptr = &psDec->exc_Q14[ silk_max_int( 0, ( psPLC->nb_subfr - 1 ) * psPLC->subfr_length - RAND_BUF_SIZE ) ]; + } else { + /* Second sub-frame has lowest energy */ + rand_ptr = &psDec->exc_Q14[ silk_max_int( 0, psPLC->nb_subfr * psPLC->subfr_length - RAND_BUF_SIZE ) ]; + } + + /* Set up Gain to random noise component */ + B_Q14 = psPLC->LTPCoef_Q14; + rand_scale_Q14 = psPLC->randScale_Q14; + + /* Set up attenuation gains */ + harm_Gain_Q15 = HARM_ATT_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + if( psDec->prevSignalType == TYPE_VOICED ) { + rand_Gain_Q15 = PLC_RAND_ATTENUATE_V_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + } else { + rand_Gain_Q15 = PLC_RAND_ATTENUATE_UV_Q15[ silk_min_int( NB_ATT - 1, psDec->lossCnt ) ]; + } + + /* LPC concealment. Apply BWE to previous LPC */ + silk_bwexpander( psPLC->prevLPC_Q12, psDec->LPC_order, SILK_FIX_CONST( BWE_COEF, 16 ) ); + + /* Preload LPC coeficients to array on stack. Gives small performance gain */ + silk_memcpy( A_Q12, psPLC->prevLPC_Q12, psDec->LPC_order * sizeof( opus_int16 ) ); + + /* First Lost frame */ + if( psDec->lossCnt == 0 ) { + rand_scale_Q14 = 1 << 14; + + /* Reduce random noise Gain for voiced frames */ + if( psDec->prevSignalType == TYPE_VOICED ) { + for( i = 0; i < LTP_ORDER; i++ ) { + rand_scale_Q14 -= B_Q14[ i ]; + } + rand_scale_Q14 = silk_max_16( 3277, rand_scale_Q14 ); /* 0.2 */ + rand_scale_Q14 = (opus_int16)silk_RSHIFT( silk_SMULBB( rand_scale_Q14, psPLC->prevLTP_scale_Q14 ), 14 ); + } else { + /* Reduce random noise for unvoiced frames with high LPC gain */ + opus_int32 invGain_Q30, down_scale_Q30; + + invGain_Q30 = silk_LPC_inverse_pred_gain( psPLC->prevLPC_Q12, psDec->LPC_order, arch ); + + down_scale_Q30 = silk_min_32( silk_RSHIFT( (opus_int32)1 << 30, LOG2_INV_LPC_GAIN_HIGH_THRES ), invGain_Q30 ); + down_scale_Q30 = silk_max_32( silk_RSHIFT( (opus_int32)1 << 30, LOG2_INV_LPC_GAIN_LOW_THRES ), down_scale_Q30 ); + down_scale_Q30 = silk_LSHIFT( down_scale_Q30, LOG2_INV_LPC_GAIN_HIGH_THRES ); + + rand_Gain_Q15 = silk_RSHIFT( silk_SMULWB( down_scale_Q30, rand_Gain_Q15 ), 14 ); + } + } + + rand_seed = psPLC->rand_seed; + lag = silk_RSHIFT_ROUND( psPLC->pitchL_Q8, 8 ); + sLTP_buf_idx = psDec->ltp_mem_length; + + /* Rewhiten LTP state */ + idx = psDec->ltp_mem_length - lag - psDec->LPC_order - LTP_ORDER / 2; + celt_assert( idx > 0 ); + silk_LPC_analysis_filter( &sLTP[ idx ], &psDec->outBuf[ idx ], A_Q12, psDec->ltp_mem_length - idx, psDec->LPC_order, arch ); + /* Scale LTP state */ + inv_gain_Q30 = silk_INVERSE32_varQ( psPLC->prevGain_Q16[ 1 ], 46 ); + inv_gain_Q30 = silk_min( inv_gain_Q30, silk_int32_MAX >> 1 ); + for( i = idx + psDec->LPC_order; i < psDec->ltp_mem_length; i++ ) { + sLTP_Q14[ i ] = silk_SMULWB( inv_gain_Q30, sLTP[ i ] ); + } + + /***************************/ + /* LTP synthesis filtering */ + /***************************/ + for( k = 0; k < psDec->nb_subfr; k++ ) { + /* Set up pointer */ + pred_lag_ptr = &sLTP_Q14[ sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q12 = 2; + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ 0 ], B_Q14[ 0 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -1 ], B_Q14[ 1 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -2 ], B_Q14[ 2 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -3 ], B_Q14[ 3 ] ); + LTP_pred_Q12 = silk_SMLAWB( LTP_pred_Q12, pred_lag_ptr[ -4 ], B_Q14[ 4 ] ); + pred_lag_ptr++; + + /* Generate LPC excitation */ + rand_seed = silk_RAND( rand_seed ); + idx = silk_RSHIFT( rand_seed, 25 ) & RAND_BUF_MASK; + sLTP_Q14[ sLTP_buf_idx ] = silk_LSHIFT32( silk_SMLAWB( LTP_pred_Q12, rand_ptr[ idx ], rand_scale_Q14 ), 2 ); + sLTP_buf_idx++; + } + + /* Gradually reduce LTP gain */ + for( j = 0; j < LTP_ORDER; j++ ) { + B_Q14[ j ] = silk_RSHIFT( silk_SMULBB( harm_Gain_Q15, B_Q14[ j ] ), 15 ); + } + /* Gradually reduce excitation gain */ + rand_scale_Q14 = silk_RSHIFT( silk_SMULBB( rand_scale_Q14, rand_Gain_Q15 ), 15 ); + + /* Slowly increase pitch lag */ + psPLC->pitchL_Q8 = silk_SMLAWB( psPLC->pitchL_Q8, psPLC->pitchL_Q8, PITCH_DRIFT_FAC_Q16 ); + psPLC->pitchL_Q8 = silk_min_32( psPLC->pitchL_Q8, silk_LSHIFT( silk_SMULBB( MAX_PITCH_LAG_MS, psDec->fs_kHz ), 8 ) ); + lag = silk_RSHIFT_ROUND( psPLC->pitchL_Q8, 8 ); + } + + /***************************/ + /* LPC synthesis filtering */ + /***************************/ + sLPC_Q14_ptr = &sLTP_Q14[ psDec->ltp_mem_length - MAX_LPC_ORDER ]; + + /* Copy LPC state */ + silk_memcpy( sLPC_Q14_ptr, psDec->sLPC_Q14_buf, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + celt_assert( psDec->LPC_order >= 10 ); /* check that unrolling works */ + for( i = 0; i < psDec->frame_length; i++ ) { + /* partly unrolled */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); + for( j = 10; j < psDec->LPC_order; j++ ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14_ptr[ MAX_LPC_ORDER + i - j - 1 ], A_Q12[ j ] ); + } + + /* Add prediction to LPC excitation */ + sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], + silk_LSHIFT_SAT32( LPC_pred_Q10, 4 )); + + /* Scale with Gain */ + frame[ i ] = (opus_int16)silk_SAT16( silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], prevGain_Q10[ 1 ] ), 8 ) ) ); + } +#ifdef ENABLE_DEEP_PLC + if ( lpcnet != NULL && lpcnet->loaded && psDec->sPLC.fs_kHz == 16 ) { + int run_deep_plc = psDec->sPLC.enable_deep_plc || lpcnet->fec_fill_pos != 0; + if( run_deep_plc ) { + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_conceal( lpcnet, frame + k * psDec->subfr_length ); + } + /* We *should* be able to copy only from psDec->frame_length-MAX_LPC_ORDER, i.e. the last MAX_LPC_ORDER samples. */ + for( i = 0; i < psDec->frame_length; i++ ) { + sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = (int)floor(.5 + frame[ i ] * (float)(1 << 24) / prevGain_Q10[ 1 ] ); + } + } else { + for( k = 0; k < psDec->nb_subfr; k += 2 ) { + lpcnet_plc_update( lpcnet, frame + k * psDec->subfr_length ); + } + } + } +#endif + + /* Save LPC state */ + silk_memcpy( psDec->sLPC_Q14_buf, &sLPC_Q14_ptr[ psDec->frame_length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + /**************************************/ + /* Update states */ + /**************************************/ + psPLC->rand_seed = rand_seed; + psPLC->randScale_Q14 = rand_scale_Q14; + for( i = 0; i < MAX_NB_SUBFR; i++ ) { + psDecCtrl->pitchL[ i ] = lag; + } + RESTORE_STACK; +} + +/* Glues concealed frames with new good received frames */ +void silk_PLC_glue_frames( + silk_decoder_state *psDec, /* I/O decoder state */ + opus_int16 frame[], /* I/O signal */ + opus_int length /* I length of signal */ +) +{ + opus_int i, energy_shift; + opus_int32 energy; + silk_PLC_struct *psPLC; + psPLC = &psDec->sPLC; + + if( psDec->lossCnt ) { + /* Calculate energy in concealed residual */ + silk_sum_sqr_shift( &psPLC->conc_energy, &psPLC->conc_energy_shift, frame, length ); + + psPLC->last_frame_lost = 1; + } else { + if( psDec->sPLC.last_frame_lost ) { + /* Calculate residual in decoded signal if last frame was lost */ + silk_sum_sqr_shift( &energy, &energy_shift, frame, length ); + + /* Normalize energies */ + if( energy_shift > psPLC->conc_energy_shift ) { + psPLC->conc_energy = silk_RSHIFT( psPLC->conc_energy, energy_shift - psPLC->conc_energy_shift ); + } else if( energy_shift < psPLC->conc_energy_shift ) { + energy = silk_RSHIFT( energy, psPLC->conc_energy_shift - energy_shift ); + } + + /* Fade in the energy difference */ + if( energy > psPLC->conc_energy ) { + opus_int32 frac_Q24, LZ; + opus_int32 gain_Q16, slope_Q16; + + LZ = silk_CLZ32( psPLC->conc_energy ); + LZ = LZ - 1; + psPLC->conc_energy = silk_LSHIFT( psPLC->conc_energy, LZ ); + energy = silk_RSHIFT( energy, silk_max_32( 24 - LZ, 0 ) ); + + frac_Q24 = silk_DIV32( psPLC->conc_energy, silk_max( energy, 1 ) ); + + gain_Q16 = silk_LSHIFT( silk_SQRT_APPROX( frac_Q24 ), 4 ); + slope_Q16 = silk_DIV32_16( ( (opus_int32)1 << 16 ) - gain_Q16, length ); + /* Make slope 4x steeper to avoid missing onsets after DTX */ + slope_Q16 = silk_LSHIFT( slope_Q16, 2 ); +#ifdef ENABLE_DEEP_PLC + if ( psDec->sPLC.fs_kHz != 16 ) +#endif + { + for( i = 0; i < length; i++ ) { + frame[ i ] = silk_SMULWB( gain_Q16, frame[ i ] ); + gain_Q16 += slope_Q16; + if( gain_Q16 > (opus_int32)1 << 16 ) { + break; + } + } + } + } + } + psPLC->last_frame_lost = 0; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.h new file mode 100644 index 0000000000..1bebb78638 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/PLC.h @@ -0,0 +1,65 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_PLC_H +#define SILK_PLC_H + +#include "main.h" + +#define BWE_COEF 0.99 +#define V_PITCH_GAIN_START_MIN_Q14 11469 /* 0.7 in Q14 */ +#define V_PITCH_GAIN_START_MAX_Q14 15565 /* 0.95 in Q14 */ +#define MAX_PITCH_LAG_MS 18 +#define RAND_BUF_SIZE 128 +#define RAND_BUF_MASK ( RAND_BUF_SIZE - 1 ) +#define LOG2_INV_LPC_GAIN_HIGH_THRES 3 /* 2^3 = 8 dB LPC gain */ +#define LOG2_INV_LPC_GAIN_LOW_THRES 8 /* 2^8 = 24 dB LPC gain */ +#define PITCH_DRIFT_FAC_Q16 655 /* 0.01 in Q16 */ + +void silk_PLC_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +); + +void silk_PLC( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O signal */ + opus_int lost, /* I Loss flag */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +); + +void silk_PLC_glue_frames( + silk_decoder_state *psDec, /* I/O decoder state */ + opus_int16 frame[], /* I/O signal */ + opus_int length /* I length of signal */ +); + +#endif + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/SigProc_FIX.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/SigProc_FIX.h new file mode 100644 index 0000000000..fbdfa82e26 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/SigProc_FIX.h @@ -0,0 +1,643 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FIX_H +#define SILK_SIGPROC_FIX_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/*#define silk_MACRO_COUNT */ /* Used to enable WMOPS counting */ + +#define SILK_MAX_ORDER_LPC 24 /* max order of the LPC analysis in schur() and k2a() */ + +#include /* for memset(), memcpy(), memmove() */ +#include "typedef.h" +#include "resampler_structs.h" +#include "macros.h" +#include "cpu_support.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/SigProc_FIX_sse.h" +#endif + +#if (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/biquad_alt_arm.h" +#include "arm/LPC_inv_pred_gain_arm.h" +#endif + +/********************************************************************/ +/* SIGNAL PROCESSING FUNCTIONS */ +/********************************************************************/ + +/*! + * Initialize/reset the resampler state for a given pair of input/output sampling rates +*/ +opus_int silk_resampler_init( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */ + opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */ + opus_int forEnc /* I If 1: encoder; if 0: decoder */ +); + +/*! + * Resampler: convert from one sampling rate to another + */ +opus_int silk_resampler( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! +* Downsample 2x, mediocre quality +*/ +void silk_resampler_down2( + opus_int32 *S, /* I/O State vector [ 2 ] */ + opus_int16 *out, /* O Output signal [ len ] */ + const opus_int16 *in, /* I Input signal [ floor(len/2) ] */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! + * Downsample by a factor 2/3, low quality +*/ +void silk_resampler_down2_3( + opus_int32 *S, /* I/O State vector [ 6 ] */ + opus_int16 *out, /* O Output signal [ floor(2*inLen/3) ] */ + const opus_int16 *in, /* I Input signal [ inLen ] */ + opus_int32 inLen /* I Number of input samples */ +); + +/*! + * second order ARMA filter; + * slower than biquad() but uses more precise coefficients + * can handle (slowly) varying coefficients + */ +void silk_biquad_alt_stride1( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +); + +void silk_biquad_alt_stride2_c( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +); + +/* Variable order MA prediction error filter. */ +void silk_LPC_analysis_filter( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ +); + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander( + opus_int16 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor (typically in the range 0 to 1) */ +); + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander_32( + opus_int32 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor in Q16 */ +); + +/* Compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +opus_int32 silk_LPC_inverse_pred_gain_c( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +); + +/* Split signal in two decimated bands using first-order allpass filters */ +void silk_ana_filt_bank_1( + const opus_int16 *in, /* I Input signal [N] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *outL, /* O Low band [N/2] */ + opus_int16 *outH, /* O High band [N/2] */ + const opus_int32 N /* I Number of input samples */ +); + +#if !defined(OVERRIDE_silk_biquad_alt_stride2) +#define silk_biquad_alt_stride2(in, B_Q28, A_Q28, S, out, len, arch) ((void)(arch), silk_biquad_alt_stride2_c(in, B_Q28, A_Q28, S, out, len)) +#endif + +#if !defined(OVERRIDE_silk_LPC_inverse_pred_gain) +#define silk_LPC_inverse_pred_gain(A_Q12, order, arch) ((void)(arch), silk_LPC_inverse_pred_gain_c(A_Q12, order)) +#endif + +/********************************************************************/ +/* SCALAR FUNCTIONS */ +/********************************************************************/ + +/* Approximation of 128 * log2() (exact inverse of approx 2^() below) */ +/* Convert input to a log scale */ +opus_int32 silk_lin2log( + const opus_int32 inLin /* I input in linear scale */ +); + +/* Approximation of a sigmoid function */ +opus_int silk_sigm_Q15( + opus_int in_Q5 /* I */ +); + +/* Approximation of 2^() (exact inverse of approx log2() above) */ +/* Convert input to a linear scale */ +opus_int32 silk_log2lin( + const opus_int32 inLog_Q7 /* I input on log scale */ +); + +/* Compute number of bits to right shift the sum of squares of a vector */ +/* of int16s to make it fit in an int32 */ +void silk_sum_sqr_shift( + opus_int32 *energy, /* O Energy of x, after shifting to the right */ + opus_int *shift, /* O Number of bits right shift applied to energy */ + const opus_int16 *x, /* I Input vector */ + opus_int len /* I Length of input vector */ +); + +/* Calculates the reflection coefficients from the correlation sequence */ +/* Faster than schur64(), but much less accurate. */ +/* uses SMLAWB(), requiring armv5E and higher. */ +opus_int32 silk_schur( /* O Returns residual energy */ + opus_int16 *rc_Q15, /* O reflection coefficients [order] Q15 */ + const opus_int32 *c, /* I correlations [order+1] */ + const opus_int32 order /* I prediction order */ +); + +/* Calculates the reflection coefficients from the correlation sequence */ +/* Slower than schur(), but more accurate. */ +/* Uses SMULL(), available on armv4 */ +opus_int32 silk_schur64( /* O returns residual energy */ + opus_int32 rc_Q16[], /* O Reflection coefficients [order] Q16 */ + const opus_int32 c[], /* I Correlations [order+1] */ + opus_int32 order /* I Prediction order */ +); + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int16 *rc_Q15, /* I Reflection coefficients [order] Q15 */ + const opus_int32 order /* I Prediction order */ +); + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a_Q16( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int32 *rc_Q16, /* I Reflection coefficients [order] Q16 */ + const opus_int32 order /* I Prediction order */ +); + +/* Apply sine window to signal vector. */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +/* every other sample of window is linearly interpolated, for speed */ +void silk_apply_sine_window( + opus_int16 px_win[], /* O Pointer to windowed signal */ + const opus_int16 px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +); + +/* Compute autocorrelation */ +void silk_autocorr( + opus_int32 *results, /* O Result (length correlationCount) */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *inputData, /* I Input data to correlate */ + const opus_int inputDataSize, /* I Length of input */ + const opus_int correlationCount, /* I Number of correlation taps to compute */ + int arch /* I Run-time architecture */ +); + +void silk_decode_pitch( + opus_int16 lagIndex, /* I */ + opus_int8 contourIndex, /* O */ + opus_int pitch_lags[], /* O 4 pitch values */ + const opus_int Fs_kHz, /* I sampling frequency (kHz) */ + const opus_int nb_subfr /* I number of sub frames */ +); + +opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const opus_int16 *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O 4 pitch lag values */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + opus_int *LTPCorr_Q15, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const opus_int32 search_thres1_Q16, /* I First stage threshold for lag candidates 0 - 1 */ + const opus_int search_thres2_Q13, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I Sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I number of 5 ms subframes */ + int arch /* I Run-time architecture */ +); + +/* Compute Normalized Line Spectral Frequencies (NLSFs) from whitening filter coefficients */ +/* If not all roots are found, the a_Q16 coefficients are bandwidth expanded until convergence. */ +void silk_A2NLSF( + opus_int16 *NLSF, /* O Normalized Line Spectral Frequencies in Q15 (0..2^15-1) [d] */ + opus_int32 *a_Q16, /* I/O Monic whitening filter coefficients in Q16 [d] */ + const opus_int d /* I Filter order (must be even) */ +); + +/* compute whitening filter coefficients from normalized line spectral frequencies */ +void silk_NLSF2A( + opus_int16 *a_Q12, /* O monic whitening filter coefficients in Q12, [ d ] */ + const opus_int16 *NLSF, /* I normalized line spectral frequencies in Q15, [ d ] */ + const opus_int d, /* I filter order (should be even) */ + int arch /* I Run-time architecture */ +); + +/* Convert int32 coefficients to int16 coefs and make sure there's no wrap-around */ +void silk_LPC_fit( + opus_int16 *a_QOUT, /* O Output signal */ + opus_int32 *a_QIN, /* I/O Input signal */ + const opus_int QOUT, /* I Input Q domain */ + const opus_int QIN, /* I Input Q domain */ + const opus_int d /* I Filter order */ +); + +void silk_insertion_sort_increasing( + opus_int32 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +void silk_insertion_sort_decreasing_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +void silk_insertion_sort_increasing_all_values_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + const opus_int L /* I Vector length */ +); + +/* NLSF stabilizer, for a single input data vector */ +void silk_NLSF_stabilize( + opus_int16 *NLSF_Q15, /* I/O Unstable/stabilized normalized LSF vector in Q15 [L] */ + const opus_int16 *NDeltaMin_Q15, /* I Min distance vector, NDeltaMin_Q15[L] must be >= 1 [L+1] */ + const opus_int L /* I Number of NLSF parameters in the input vector */ +); + +/* Laroia low complexity NLSF weights */ +void silk_NLSF_VQ_weights_laroia( + opus_int16 *pNLSFW_Q_OUT, /* O Pointer to input vector weights [D] */ + const opus_int16 *pNLSF_Q15, /* I Pointer to input vector [D] */ + const opus_int D /* I Input vector dimension (even) */ +); + +/* Compute reflection coefficients from input signal */ +void silk_burg_modified_c( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +); + +/* Copy and multiply a vector by a constant */ +void silk_scale_copy_vector16( + opus_int16 *data_out, + const opus_int16 *data_in, + opus_int32 gain_Q16, /* I Gain in Q16 */ + const opus_int dataSize /* I Length */ +); + +/* Some for the LTP related function requires Q26 to work.*/ +void silk_scale_vector32_Q26_lshift_18( + opus_int32 *data1, /* I/O Q0/Q18 */ + opus_int32 gain_Q26, /* I Q26 */ + opus_int dataSize /* I length */ +); + +/********************************************************************/ +/* INLINE ARM MATH */ +/********************************************************************/ + +/* return sum( inVec1[i] * inVec2[i] ) */ + +opus_int32 silk_inner_prod_aligned( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int len, /* I vector lengths */ + int arch /* I Run-time architecture */ +); + + +opus_int32 silk_inner_prod_aligned_scale( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int scale, /* I number of bits to shift */ + const opus_int len /* I vector lengths */ +); + +opus_int64 silk_inner_prod16_c( + const opus_int16 *inVec1, /* I input vector 1 */ + const opus_int16 *inVec2, /* I input vector 2 */ + const opus_int len /* I vector lengths */ +); + +/********************************************************************/ +/* MACROS */ +/********************************************************************/ + +/* Rotate a32 right by 'rot' bits. Negative rot values result in rotating + left. Output is 32bit int. + Note: contemporary compilers recognize the C expression below and + compile it into a 'ror' instruction if available. No need for OPUS_INLINE ASM! */ +static OPUS_INLINE opus_int32 silk_ROR32( opus_int32 a32, opus_int rot ) +{ + opus_uint32 x = (opus_uint32) a32; + opus_uint32 r = (opus_uint32) rot; + opus_uint32 m = (opus_uint32) -rot; + if( rot == 0 ) { + return a32; + } else if( rot < 0 ) { + return (opus_int32) ((x << m) | (x >> (32 - m))); + } else { + return (opus_int32) ((x << (32 - r)) | (x >> r)); + } +} + +/* Allocate opus_int16 aligned to 4-byte memory address */ +#if EMBEDDED_ARM +#define silk_DWORD_ALIGN __attribute__((aligned(4))) +#else +#define silk_DWORD_ALIGN +#endif + +/* Useful Macros that can be adjusted to other platforms */ +#define silk_memcpy(dest, src, size) memcpy((dest), (src), (size)) +#define silk_memset(dest, src, size) memset((dest), (src), (size)) +#define silk_memmove(dest, src, size) memmove((dest), (src), (size)) + +/* Fixed point macros */ + +/* (a32 * b32) output have to be 32bit int */ +#define silk_MUL(a32, b32) ((a32) * (b32)) + +/* (a32 * b32) output have to be 32bit uint */ +#define silk_MUL_uint(a32, b32) silk_MUL(a32, b32) + +/* a32 + (b32 * c32) output have to be 32bit int */ +#define silk_MLA(a32, b32, c32) silk_ADD32((a32),((b32) * (c32))) + +/* a32 + (b32 * c32) output have to be 32bit uint */ +#define silk_MLA_uint(a32, b32, c32) silk_MLA(a32, b32, c32) + +/* ((a32 >> 16) * (b32 >> 16)) output have to be 32bit int */ +#define silk_SMULTT(a32, b32) (((a32) >> 16) * ((b32) >> 16)) + +/* a32 + ((a32 >> 16) * (b32 >> 16)) output have to be 32bit int */ +#define silk_SMLATT(a32, b32, c32) silk_ADD32((a32),((b32) >> 16) * ((c32) >> 16)) + +#define silk_SMLALBB(a64, b16, c16) silk_ADD64((a64),(opus_int64)((opus_int32)(b16) * (opus_int32)(c16))) + +/* (a32 * b32) */ +#define silk_SMULL(a32, b32) ((opus_int64)(a32) * /*(opus_int64)*/(b32)) + +/* Adds two signed 32-bit values in a way that can overflow, while not relying on undefined behaviour + (just standard two's complement implementation-specific behaviour) */ +#define silk_ADD32_ovflw(a, b) ((opus_int32)((opus_uint32)(a) + (opus_uint32)(b))) +/* Subtractss two signed 32-bit values in a way that can overflow, while not relying on undefined behaviour + (just standard two's complement implementation-specific behaviour) */ +#define silk_SUB32_ovflw(a, b) ((opus_int32)((opus_uint32)(a) - (opus_uint32)(b))) + +/* Multiply-accumulate macros that allow overflow in the addition (ie, no asserts in debug mode) */ +#define silk_MLA_ovflw(a32, b32, c32) silk_ADD32_ovflw((a32), (opus_uint32)(b32) * (opus_uint32)(c32)) +#define silk_SMLABB_ovflw(a32, b32, c32) (silk_ADD32_ovflw((a32) , ((opus_int32)((opus_int16)(b32))) * (opus_int32)((opus_int16)(c32)))) + +#define silk_DIV32_16(a32, b16) ((opus_int32)((a32) / (b16))) +#define silk_DIV32(a32, b32) ((opus_int32)((a32) / (b32))) + +/* These macros enables checking for overflow in silk_API_Debug.h*/ +#define silk_ADD16(a, b) ((a) + (b)) +#define silk_ADD32(a, b) ((a) + (b)) +#define silk_ADD64(a, b) ((a) + (b)) + +#define silk_SUB16(a, b) ((a) - (b)) +#define silk_SUB32(a, b) ((a) - (b)) +#define silk_SUB64(a, b) ((a) - (b)) + +#define silk_SAT8(a) ((a) > silk_int8_MAX ? silk_int8_MAX : \ + ((a) < silk_int8_MIN ? silk_int8_MIN : (a))) +#define silk_SAT16(a) ((a) > silk_int16_MAX ? silk_int16_MAX : \ + ((a) < silk_int16_MIN ? silk_int16_MIN : (a))) +#define silk_SAT32(a) ((a) > silk_int32_MAX ? silk_int32_MAX : \ + ((a) < silk_int32_MIN ? silk_int32_MIN : (a))) + +#define silk_CHECK_FIT8(a) (a) +#define silk_CHECK_FIT16(a) (a) +#define silk_CHECK_FIT32(a) (a) + +#define silk_ADD_SAT16(a, b) (opus_int16)silk_SAT16( silk_ADD32( (opus_int32)(a), (b) ) ) +#define silk_ADD_SAT64(a, b) ((((a) + (b)) & 0x8000000000000000LL) == 0 ? \ + ((((a) & (b)) & 0x8000000000000000LL) != 0 ? silk_int64_MIN : (a)+(b)) : \ + ((((a) | (b)) & 0x8000000000000000LL) == 0 ? silk_int64_MAX : (a)+(b)) ) + +#define silk_SUB_SAT16(a, b) (opus_int16)silk_SAT16( silk_SUB32( (opus_int32)(a), (b) ) ) +#define silk_SUB_SAT64(a, b) ((((a)-(b)) & 0x8000000000000000LL) == 0 ? \ + (( (a) & ((b)^0x8000000000000000LL) & 0x8000000000000000LL) ? silk_int64_MIN : (a)-(b)) : \ + ((((a)^0x8000000000000000LL) & (b) & 0x8000000000000000LL) ? silk_int64_MAX : (a)-(b)) ) + +/* Saturation for positive input values */ +#define silk_POS_SAT32(a) ((a) > silk_int32_MAX ? silk_int32_MAX : (a)) + +/* Add with saturation for positive input values */ +#define silk_ADD_POS_SAT8(a, b) ((((a)+(b)) & 0x80) ? silk_int8_MAX : ((a)+(b))) +#define silk_ADD_POS_SAT16(a, b) ((((a)+(b)) & 0x8000) ? silk_int16_MAX : ((a)+(b))) +#define silk_ADD_POS_SAT32(a, b) ((((opus_uint32)(a)+(opus_uint32)(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))) + +#define silk_LSHIFT8(a, shift) ((opus_int8)((opus_uint8)(a)<<(shift))) /* shift >= 0, shift < 8 */ +#define silk_LSHIFT16(a, shift) ((opus_int16)((opus_uint16)(a)<<(shift))) /* shift >= 0, shift < 16 */ +#define silk_LSHIFT32(a, shift) ((opus_int32)((opus_uint32)(a)<<(shift))) /* shift >= 0, shift < 32 */ +#define silk_LSHIFT64(a, shift) ((opus_int64)((opus_uint64)(a)<<(shift))) /* shift >= 0, shift < 64 */ +#define silk_LSHIFT(a, shift) silk_LSHIFT32(a, shift) /* shift >= 0, shift < 32 */ + +#define silk_RSHIFT8(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 8 */ +#define silk_RSHIFT16(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 16 */ +#define silk_RSHIFT32(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 32 */ +#define silk_RSHIFT64(a, shift) ((a)>>(shift)) /* shift >= 0, shift < 64 */ +#define silk_RSHIFT(a, shift) silk_RSHIFT32(a, shift) /* shift >= 0, shift < 32 */ + +/* saturates before shifting */ +#define silk_LSHIFT_SAT32(a, shift) (silk_LSHIFT32( silk_LIMIT( (a), silk_RSHIFT32( silk_int32_MIN, (shift) ), \ + silk_RSHIFT32( silk_int32_MAX, (shift) ) ), (shift) )) + +#define silk_LSHIFT_ovflw(a, shift) ((opus_int32)((opus_uint32)(a) << (shift))) /* shift >= 0, allowed to overflow */ +#define silk_LSHIFT_uint(a, shift) ((a) << (shift)) /* shift >= 0 */ +#define silk_RSHIFT_uint(a, shift) ((a) >> (shift)) /* shift >= 0 */ + +#define silk_ADD_LSHIFT(a, b, shift) ((a) + silk_LSHIFT((b), (shift))) /* shift >= 0 */ +#define silk_ADD_LSHIFT32(a, b, shift) silk_ADD32((a), silk_LSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_ADD_LSHIFT_uint(a, b, shift) ((a) + silk_LSHIFT_uint((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT(a, b, shift) ((a) + silk_RSHIFT((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT32(a, b, shift) silk_ADD32((a), silk_RSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_ADD_RSHIFT_uint(a, b, shift) ((a) + silk_RSHIFT_uint((b), (shift))) /* shift >= 0 */ +#define silk_SUB_LSHIFT32(a, b, shift) silk_SUB32((a), silk_LSHIFT32((b), (shift))) /* shift >= 0 */ +#define silk_SUB_RSHIFT32(a, b, shift) silk_SUB32((a), silk_RSHIFT32((b), (shift))) /* shift >= 0 */ + +/* Requires that shift > 0 */ +#define silk_RSHIFT_ROUND(a, shift) ((shift) == 1 ? ((a) >> 1) + ((a) & 1) : (((a) >> ((shift) - 1)) + 1) >> 1) +#define silk_RSHIFT_ROUND64(a, shift) ((shift) == 1 ? ((a) >> 1) + ((a) & 1) : (((a) >> ((shift) - 1)) + 1) >> 1) + +/* Number of rightshift required to fit the multiplication */ +#define silk_NSHIFT_MUL_32_32(a, b) ( -(31- (32-silk_CLZ32(silk_abs(a)) + (32-silk_CLZ32(silk_abs(b))))) ) +#define silk_NSHIFT_MUL_16_16(a, b) ( -(15- (16-silk_CLZ16(silk_abs(a)) + (16-silk_CLZ16(silk_abs(b))))) ) + + +#define silk_min(a, b) (((a) < (b)) ? (a) : (b)) +#define silk_max(a, b) (((a) > (b)) ? (a) : (b)) + +/* Macro to convert floating-point constants to fixed-point */ +#define SILK_FIX_CONST( C, Q ) ((opus_int32)((C) * ((opus_int64)1 << (Q)) + 0.5)) + +/* silk_min() versions with typecast in the function call */ +static OPUS_INLINE opus_int silk_min_int(opus_int a, opus_int b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int16 silk_min_16(opus_int16 a, opus_int16 b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int32 silk_min_32(opus_int32 a, opus_int32 b) +{ + return (((a) < (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int64 silk_min_64(opus_int64 a, opus_int64 b) +{ + return (((a) < (b)) ? (a) : (b)); +} + +/* silk_min() versions with typecast in the function call */ +static OPUS_INLINE opus_int silk_max_int(opus_int a, opus_int b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int16 silk_max_16(opus_int16 a, opus_int16 b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int32 silk_max_32(opus_int32 a, opus_int32 b) +{ + return (((a) > (b)) ? (a) : (b)); +} +static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) +{ + return (((a) > (b)) ? (a) : (b)); +} + +#define silk_LIMIT( a, limit1, limit2) ((limit1) > (limit2) ? ((a) > (limit1) ? (limit1) : ((a) < (limit2) ? (limit2) : (a))) \ + : ((a) > (limit2) ? (limit2) : ((a) < (limit1) ? (limit1) : (a)))) + +#define silk_LIMIT_int silk_LIMIT +#define silk_LIMIT_16 silk_LIMIT +#define silk_LIMIT_32 silk_LIMIT + +#define silk_abs(a) (((a) > 0) ? (a) : -(a)) /* Be careful, silk_abs returns wrong when input equals to silk_intXX_MIN */ +#define silk_abs_int(a) (((a) ^ ((a) >> (8 * sizeof(a) - 1))) - ((a) >> (8 * sizeof(a) - 1))) +#define silk_abs_int32(a) (((a) ^ ((a) >> 31)) - ((a) >> 31)) +#define silk_abs_int64(a) (((a) > 0) ? (a) : -(a)) + +#define silk_sign(a) ((a) > 0 ? 1 : ( (a) < 0 ? -1 : 0 )) + +/* PSEUDO-RANDOM GENERATOR */ +/* Make sure to store the result as the seed for the next call (also in between */ +/* frames), otherwise result won't be random at all. When only using some of the */ +/* bits, take the most significant bits by right-shifting. */ +#define RAND_MULTIPLIER 196314165 +#define RAND_INCREMENT 907633515 +#define silk_RAND(seed) (silk_MLA_ovflw((RAND_INCREMENT), (seed), (RAND_MULTIPLIER))) + +/* Add some multiplication functions that can be easily mapped to ARM. */ + +/* silk_SMMUL: Signed top word multiply. + ARMv6 2 instruction cycles. + ARMv3M+ 3 instruction cycles. use SMULL and ignore LSB registers.(except xM)*/ +/*#define silk_SMMUL(a32, b32) (opus_int32)silk_RSHIFT(silk_SMLAL(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)), 16)*/ +/* the following seems faster on x86 */ +#define silk_SMMUL(a32, b32) (opus_int32)silk_RSHIFT64(silk_SMULL((a32), (b32)), 32) + +#if !defined(OVERRIDE_silk_burg_modified) +#define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((void)(arch), silk_burg_modified_c(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) +#endif + +#if !defined(OVERRIDE_silk_inner_prod16) +#define silk_inner_prod16(inVec1, inVec2, len, arch) \ + ((void)(arch),silk_inner_prod16_c(inVec1, inVec2, len)) +#endif + +#include "Inlines.h" +#include "MacroCount.h" +#include "MacroDebug.h" + +#ifdef OPUS_ARM_INLINE_ASM +#include "arm/SigProc_FIX_armv4.h" +#endif + +#ifdef OPUS_ARM_INLINE_EDSP +#include "arm/SigProc_FIX_armv5e.h" +#endif + +#if defined(MIPSr1_ASM) +#include "mips/sigproc_fix_mipsr1.h" +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_SIGPROC_FIX_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VAD.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VAD.c new file mode 100644 index 0000000000..d0cda52162 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VAD.c @@ -0,0 +1,360 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Silk VAD noise level estimation */ +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); +#endif + +/**********************************/ +/* Initialization of the Silk VAD */ +/**********************************/ +opus_int silk_VAD_Init( /* O Return value, 0 if success */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +) +{ + opus_int b, ret = 0; + + /* reset state memory */ + silk_memset( psSilk_VAD, 0, sizeof( silk_VAD_state ) ); + + /* init noise levels */ + /* Initialize array with approx pink noise levels (psd proportional to inverse of frequency) */ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NoiseLevelBias[ b ] = silk_max_32( silk_DIV32_16( VAD_NOISE_LEVELS_BIAS, b + 1 ), 1 ); + } + + /* Initialize state */ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NL[ b ] = silk_MUL( 100, psSilk_VAD->NoiseLevelBias[ b ] ); + psSilk_VAD->inv_NL[ b ] = silk_DIV32( silk_int32_MAX, psSilk_VAD->NL[ b ] ); + } + psSilk_VAD->counter = 15; + + /* init smoothed energy-to-noise ratio*/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + psSilk_VAD->NrgRatioSmth_Q8[ b ] = 100 * 256; /* 100 * 256 --> 20 dB SNR */ + } + + return( ret ); +} + +/* Weighting factors for tilt measure */ +static const opus_int32 tiltWeights[ VAD_N_BANDS ] = { 30000, 6000, -12000, -12000 }; + +/***************************************/ +/* Get the speech activity level in Q8 */ +/***************************************/ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +) +{ + opus_int SA_Q15, pSNR_dB_Q7, input_tilt; + opus_int decimated_framelength1, decimated_framelength2; + opus_int decimated_framelength; + opus_int dec_subframe_length, dec_subframe_offset, SNR_Q7, i, b, s; + opus_int32 sumSquared, smooth_coef_Q16; + opus_int16 HPstateTmp; + VARDECL( opus_int16, X ); + opus_int32 Xnrg[ VAD_N_BANDS ]; + opus_int32 NrgToNoiseRatio_Q8[ VAD_N_BANDS ]; + opus_int32 speech_nrg, x_tmp; + opus_int X_offset[ VAD_N_BANDS ]; + opus_int ret = 0; + silk_VAD_state *psSilk_VAD = &psEncC->sVAD; + SAVE_STACK; + + /* Safety checks */ + silk_assert( VAD_N_BANDS == 4 ); + celt_assert( MAX_FRAME_LENGTH >= psEncC->frame_length ); + celt_assert( psEncC->frame_length <= 512 ); + celt_assert( psEncC->frame_length == 8 * silk_RSHIFT( psEncC->frame_length, 3 ) ); + + /***********************/ + /* Filter and Decimate */ + /***********************/ + decimated_framelength1 = silk_RSHIFT( psEncC->frame_length, 1 ); + decimated_framelength2 = silk_RSHIFT( psEncC->frame_length, 2 ); + decimated_framelength = silk_RSHIFT( psEncC->frame_length, 3 ); + /* Decimate into 4 bands: + 0 L 3L L 3L 5L + - -- - -- -- + 8 8 2 4 4 + + [0-1 kHz| temp. |1-2 kHz| 2-4 kHz | 4-8 kHz | + + They're arranged to allow the minimal ( frame_length / 4 ) extra + scratch space during the downsampling process */ + X_offset[ 0 ] = 0; + X_offset[ 1 ] = decimated_framelength + decimated_framelength2; + X_offset[ 2 ] = X_offset[ 1 ] + decimated_framelength; + X_offset[ 3 ] = X_offset[ 2 ] + decimated_framelength2; + ALLOC( X, X_offset[ 3 ] + decimated_framelength1, opus_int16 ); + + /* 0-8 kHz to 0-4 kHz and 4-8 kHz */ + silk_ana_filt_bank_1( pIn, &psSilk_VAD->AnaState[ 0 ], + X, &X[ X_offset[ 3 ] ], psEncC->frame_length ); + + /* 0-4 kHz to 0-2 kHz and 2-4 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState1[ 0 ], + X, &X[ X_offset[ 2 ] ], decimated_framelength1 ); + + /* 0-2 kHz to 0-1 kHz and 1-2 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState2[ 0 ], + X, &X[ X_offset[ 1 ] ], decimated_framelength2 ); + + /*********************************************/ + /* HP filter on lowest band (differentiator) */ + /*********************************************/ + X[ decimated_framelength - 1 ] = silk_RSHIFT( X[ decimated_framelength - 1 ], 1 ); + HPstateTmp = X[ decimated_framelength - 1 ]; + for( i = decimated_framelength - 1; i > 0; i-- ) { + X[ i - 1 ] = silk_RSHIFT( X[ i - 1 ], 1 ); + X[ i ] -= X[ i - 1 ]; + } + X[ 0 ] -= psSilk_VAD->HPstate; + psSilk_VAD->HPstate = HPstateTmp; + + /*************************************/ + /* Calculate the energy in each band */ + /*************************************/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Find the decimated framelength in the non-uniformly divided bands */ + decimated_framelength = silk_RSHIFT( psEncC->frame_length, silk_min_int( VAD_N_BANDS - b, VAD_N_BANDS - 1 ) ); + + /* Split length into subframe lengths */ + dec_subframe_length = silk_RSHIFT( decimated_framelength, VAD_INTERNAL_SUBFRAMES_LOG2 ); + dec_subframe_offset = 0; + + /* Compute energy per sub-frame */ + /* initialize with summed energy of last subframe */ + Xnrg[ b ] = psSilk_VAD->XnrgSubfr[ b ]; + for( s = 0; s < VAD_INTERNAL_SUBFRAMES; s++ ) { + sumSquared = 0; + for( i = 0; i < dec_subframe_length; i++ ) { + /* The energy will be less than dec_subframe_length * ( silk_int16_MIN / 8 ) ^ 2. */ + /* Therefore we can accumulate with no risk of overflow (unless dec_subframe_length > 128) */ + x_tmp = silk_RSHIFT( + X[ X_offset[ b ] + i + dec_subframe_offset ], 3 ); + sumSquared = silk_SMLABB( sumSquared, x_tmp, x_tmp ); + + /* Safety check */ + silk_assert( sumSquared >= 0 ); + } + + /* Add/saturate summed energy of current subframe */ + if( s < VAD_INTERNAL_SUBFRAMES - 1 ) { + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], sumSquared ); + } else { + /* Look-ahead subframe */ + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], silk_RSHIFT( sumSquared, 1 ) ); + } + + dec_subframe_offset += dec_subframe_length; + } + psSilk_VAD->XnrgSubfr[ b ] = sumSquared; + } + + /********************/ + /* Noise estimation */ + /********************/ + silk_VAD_GetNoiseLevels( &Xnrg[ 0 ], psSilk_VAD ); + + /***********************************************/ + /* Signal-plus-noise to noise ratio estimation */ + /***********************************************/ + sumSquared = 0; + input_tilt = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + speech_nrg = Xnrg[ b ] - psSilk_VAD->NL[ b ]; + if( speech_nrg > 0 ) { + /* Divide, with sufficient resolution */ + if( ( Xnrg[ b ] & 0xFF800000 ) == 0 ) { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( silk_LSHIFT( Xnrg[ b ], 8 ), psSilk_VAD->NL[ b ] + 1 ); + } else { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( Xnrg[ b ], silk_RSHIFT( psSilk_VAD->NL[ b ], 8 ) + 1 ); + } + + /* Convert to log domain */ + SNR_Q7 = silk_lin2log( NrgToNoiseRatio_Q8[ b ] ) - 8 * 128; + + /* Sum-of-squares */ + sumSquared = silk_SMLABB( sumSquared, SNR_Q7, SNR_Q7 ); /* Q14 */ + + /* Tilt measure */ + if( speech_nrg < ( (opus_int32)1 << 20 ) ) { + /* Scale down SNR value for small subband speech energies */ + SNR_Q7 = silk_SMULWB( silk_LSHIFT( silk_SQRT_APPROX( speech_nrg ), 6 ), SNR_Q7 ); + } + input_tilt = silk_SMLAWB( input_tilt, tiltWeights[ b ], SNR_Q7 ); + } else { + NrgToNoiseRatio_Q8[ b ] = 256; + } + } + + /* Mean-of-squares */ + sumSquared = silk_DIV32_16( sumSquared, VAD_N_BANDS ); /* Q14 */ + + /* Root-mean-square approximation, scale to dBs, and write to output pointer */ + pSNR_dB_Q7 = (opus_int16)( 3 * silk_SQRT_APPROX( sumSquared ) ); /* Q7 */ + + /*********************************/ + /* Speech Probability Estimation */ + /*********************************/ + SA_Q15 = silk_sigm_Q15( silk_SMULWB( VAD_SNR_FACTOR_Q16, pSNR_dB_Q7 ) - VAD_NEGATIVE_OFFSET_Q5 ); + + /**************************/ + /* Frequency Tilt Measure */ + /**************************/ + psEncC->input_tilt_Q15 = silk_LSHIFT( silk_sigm_Q15( input_tilt ) - 16384, 1 ); + + /**************************************************/ + /* Scale the sigmoid output based on power levels */ + /**************************************************/ + speech_nrg = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Accumulate signal-without-noise energies, higher frequency bands have more weight */ + speech_nrg += ( b + 1 ) * silk_RSHIFT( Xnrg[ b ] - psSilk_VAD->NL[ b ], 4 ); + } + + if( psEncC->frame_length == 20 * psEncC->fs_kHz ) { + speech_nrg = silk_RSHIFT32( speech_nrg, 1 ); + } + /* Power scaling */ + if( speech_nrg <= 0 ) { + SA_Q15 = silk_RSHIFT( SA_Q15, 1 ); + } else if( speech_nrg < 16384 ) { + speech_nrg = silk_LSHIFT32( speech_nrg, 16 ); + + /* square-root */ + speech_nrg = silk_SQRT_APPROX( speech_nrg ); + SA_Q15 = silk_SMULWB( 32768 + speech_nrg, SA_Q15 ); + } + + /* Copy the resulting speech activity in Q8 */ + psEncC->speech_activity_Q8 = silk_min_int( silk_RSHIFT( SA_Q15, 7 ), silk_uint8_MAX ); + + /***********************************/ + /* Energy Level and SNR estimation */ + /***********************************/ + /* Smoothing coefficient */ + smooth_coef_Q16 = silk_SMULWB( VAD_SNR_SMOOTH_COEF_Q18, silk_SMULWB( (opus_int32)SA_Q15, SA_Q15 ) ); + + if( psEncC->frame_length == 10 * psEncC->fs_kHz ) { + smooth_coef_Q16 >>= 1; + } + + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* compute smoothed energy-to-noise ratio per band */ + psSilk_VAD->NrgRatioSmth_Q8[ b ] = silk_SMLAWB( psSilk_VAD->NrgRatioSmth_Q8[ b ], + NrgToNoiseRatio_Q8[ b ] - psSilk_VAD->NrgRatioSmth_Q8[ b ], smooth_coef_Q16 ); + + /* signal to noise ratio in dB per band */ + SNR_Q7 = 3 * ( silk_lin2log( psSilk_VAD->NrgRatioSmth_Q8[b] ) - 8 * 128 ); + /* quality = sigmoid( 0.25 * ( SNR_dB - 16 ) ); */ + psEncC->input_quality_bands_Q15[ b ] = silk_sigm_Q15( silk_RSHIFT( SNR_Q7 - 16 * 128, 4 ) ); + } + + RESTORE_STACK; + return( ret ); +} + +/**************************/ +/* Noise level estimation */ +/**************************/ +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +) +{ + opus_int k; + opus_int32 nl, nrg, inv_nrg; + opus_int coef, min_coef; + + /* Initially faster smoothing */ + if( psSilk_VAD->counter < 1000 ) { /* 1000 = 20 sec */ + min_coef = silk_DIV32_16( silk_int16_MAX, silk_RSHIFT( psSilk_VAD->counter, 4 ) + 1 ); + /* Increment frame counter */ + psSilk_VAD->counter++; + } else { + min_coef = 0; + } + + for( k = 0; k < VAD_N_BANDS; k++ ) { + /* Get old noise level estimate for current band */ + nl = psSilk_VAD->NL[ k ]; + silk_assert( nl >= 0 ); + + /* Add bias */ + nrg = silk_ADD_POS_SAT32( pX[ k ], psSilk_VAD->NoiseLevelBias[ k ] ); + silk_assert( nrg > 0 ); + + /* Invert energies */ + inv_nrg = silk_DIV32( silk_int32_MAX, nrg ); + silk_assert( inv_nrg >= 0 ); + + /* Less update when subband energy is high */ + if( nrg > silk_LSHIFT( nl, 3 ) ) { + coef = VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 >> 3; + } else if( nrg < nl ) { + coef = VAD_NOISE_LEVEL_SMOOTH_COEF_Q16; + } else { + coef = silk_SMULWB( silk_SMULWW( inv_nrg, nl ), VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 << 1 ); + } + + /* Initially faster smoothing */ + coef = silk_max_int( coef, min_coef ); + + /* Smooth inverse energies */ + psSilk_VAD->inv_NL[ k ] = silk_SMLAWB( psSilk_VAD->inv_NL[ k ], inv_nrg - psSilk_VAD->inv_NL[ k ], coef ); + silk_assert( psSilk_VAD->inv_NL[ k ] >= 0 ); + + /* Compute noise level by inverting again */ + nl = silk_DIV32( silk_int32_MAX, psSilk_VAD->inv_NL[ k ] ); + silk_assert( nl >= 0 ); + + /* Limit noise levels (guarantee 7 bits of head room) */ + nl = silk_min( nl, 0x00FFFFFF ); + + /* Store as part of state */ + psSilk_VAD->NL[ k ] = nl; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VQ_WMat_EC.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VQ_WMat_EC.c new file mode 100644 index 0000000000..245a7e4b06 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/VQ_WMat_EC.c @@ -0,0 +1,131 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Entropy constrained matrix-weighted VQ, hard-coded to 5-element vectors, for a single input data vector */ +void silk_VQ_WMat_EC_c( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +) +{ + opus_int k, gain_tmp_Q7; + const opus_int8 *cb_row_Q7; + opus_int32 neg_xX_Q24[ 5 ]; + opus_int32 sum1_Q15, sum2_Q24; + opus_int32 bits_res_Q8, bits_tot_Q8; + + /* Negate and convert to new Q domain */ + neg_xX_Q24[ 0 ] = -silk_LSHIFT32( xX_Q17[ 0 ], 7 ); + neg_xX_Q24[ 1 ] = -silk_LSHIFT32( xX_Q17[ 1 ], 7 ); + neg_xX_Q24[ 2 ] = -silk_LSHIFT32( xX_Q17[ 2 ], 7 ); + neg_xX_Q24[ 3 ] = -silk_LSHIFT32( xX_Q17[ 3 ], 7 ); + neg_xX_Q24[ 4 ] = -silk_LSHIFT32( xX_Q17[ 4 ], 7 ); + + /* Loop over codebook */ + *rate_dist_Q8 = silk_int32_MAX; + *res_nrg_Q15 = silk_int32_MAX; + cb_row_Q7 = cb_Q7; + /* If things go really bad, at least *ind is set to something safe. */ + *ind = 0; + for( k = 0; k < L; k++ ) { + opus_int32 penalty; + gain_tmp_Q7 = cb_gain_Q7[k]; + /* Weighted rate */ + /* Quantization error: 1 - 2 * xX * cb + cb' * XX * cb */ + sum1_Q15 = SILK_FIX_CONST( 1.001, 15 ); + + /* Penalty for too large gain */ + penalty = silk_LSHIFT32( silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 11 ); + + /* first row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 0 ], XX_Q17[ 1 ], cb_row_Q7[ 1 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 2 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 3 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 4 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 0 ], cb_row_Q7[ 0 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 0 ] ); + + /* second row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 1 ], XX_Q17[ 7 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 8 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 9 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 6 ], cb_row_Q7[ 1 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 1 ] ); + + /* third row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 2 ], XX_Q17[ 13 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 14 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 12 ], cb_row_Q7[ 2 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 2 ] ); + + /* fourth row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 3 ], XX_Q17[ 19 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 18 ], cb_row_Q7[ 3 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 3 ] ); + + /* last row of XX_Q17 */ + sum2_Q24 = silk_LSHIFT32( neg_xX_Q24[ 4 ], 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 24 ], cb_row_Q7[ 4 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 4 ] ); + + /* find best */ + if( sum1_Q15 >= 0 ) { + /* Translate residual energy to bits using high-rate assumption (6 dB ==> 1 bit/sample) */ + bits_res_Q8 = silk_SMULBB( subfr_len, silk_lin2log( sum1_Q15 + penalty) - (15 << 7) ); + /* In the following line we reduce the codelength component by half ("-1"); seems to slightly improve quality */ + bits_tot_Q8 = silk_ADD_LSHIFT32( bits_res_Q8, cl_Q5[ k ], 3-1 ); + if( bits_tot_Q8 <= *rate_dist_Q8 ) { + *rate_dist_Q8 = bits_tot_Q8; + *res_nrg_Q15 = sum1_Q15 + penalty; + *ind = (opus_int8)k; + *gain_Q7 = gain_tmp_Q7; + } + } + + /* Go to next cbk vector */ + cb_row_Q7 += LTP_ORDER; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/ana_filt_bank_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/ana_filt_bank_1.c new file mode 100644 index 0000000000..24cfb03fdb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/ana_filt_bank_1.c @@ -0,0 +1,74 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Coefficients for 2-band filter bank based on first-order allpass filters */ +static opus_int16 A_fb1_20 = 5394 << 1; +static opus_int16 A_fb1_21 = -24290; /* (opus_int16)(20623 << 1) */ + +/* Split signal into two decimated bands using first-order allpass filters */ +void silk_ana_filt_bank_1( + const opus_int16 *in, /* I Input signal [N] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *outL, /* O Low band [N/2] */ + opus_int16 *outH, /* O High band [N/2] */ + const opus_int32 N /* I Number of input samples */ +) +{ + opus_int k, N2 = silk_RSHIFT( N, 1 ); + opus_int32 in32, X, Y, out_1, out_2; + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < N2; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k ], 10 ); + + /* All-pass section for even input sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMLAWB( Y, Y, A_fb1_21 ); + out_1 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k + 1 ], 10 ); + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = silk_SUB32( in32, S[ 1 ] ); + X = silk_SMULWB( Y, A_fb1_20 ); + out_2 = silk_ADD32( S[ 1 ], X ); + S[ 1 ] = silk_ADD32( in32, X ); + + /* Add/subtract, convert back to int16 and store to output */ + outL[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_ADD32( out_2, out_1 ), 11 ) ); + outH[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SUB32( out_2, out_1 ), 11 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_arm.h new file mode 100644 index 0000000000..9895b555c8 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_arm.h @@ -0,0 +1,57 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_LPC_INV_PRED_GAIN_ARM_H +# define SILK_LPC_INV_PRED_GAIN_ARM_H + +# include "celt/arm/armcpu.h" + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +opus_int32 silk_LPC_inverse_pred_gain_neon( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +); + +# if !defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_PRESUME_NEON) +# define OVERRIDE_silk_LPC_inverse_pred_gain (1) +# define silk_LPC_inverse_pred_gain(A_Q12, order, arch) ((void)(arch), PRESUME_NEON(silk_LPC_inverse_pred_gain)(A_Q12, order)) +# endif +# endif + +# if !defined(OVERRIDE_silk_LPC_inverse_pred_gain) +/*Is run-time CPU detection enabled on this platform?*/ +# if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern opus_int32 (*const SILK_LPC_INVERSE_PRED_GAIN_IMPL[OPUS_ARCHMASK+1])(const opus_int16 *A_Q12, const opus_int order); +# define OVERRIDE_silk_LPC_inverse_pred_gain (1) +# define silk_LPC_inverse_pred_gain(A_Q12, order, arch) ((*SILK_LPC_INVERSE_PRED_GAIN_IMPL[(arch)&OPUS_ARCHMASK])(A_Q12, order)) +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_silk_LPC_inverse_pred_gain (1) +# define silk_LPC_inverse_pred_gain(A_Q12, order, arch) ((void)(arch), silk_LPC_inverse_pred_gain_neon(A_Q12, order)) +# endif +# endif + +#endif /* end SILK_LPC_INV_PRED_GAIN_ARM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_neon_intr.c new file mode 100644 index 0000000000..726e6667b4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/LPC_inv_pred_gain_neon_intr.c @@ -0,0 +1,288 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "SigProc_FIX.h" +#include "define.h" + +#define QA 24 +#define A_LIMIT SILK_FIX_CONST( 0.99975, QA ) + +#define MUL32_FRAC_Q(a32, b32, Q) ((opus_int32)(silk_RSHIFT_ROUND64(silk_SMULL(a32, b32), Q))) + +/* The difficulty is how to judge a 64-bit signed integer tmp64 is 32-bit overflowed, + * since NEON has no 64-bit min, max or comparison instructions. + * A failed idea is to compare the results of vmovn(tmp64) and vqmovn(tmp64) whether they are equal or not. + * However, this idea fails when the tmp64 is something like 0xFFFFFFF980000000. + * Here we know that mult2Q >= 1, so the highest bit (bit 63, sign bit) of tmp64 must equal to bit 62. + * tmp64 was shifted left by 1 and we got tmp64'. If high_half(tmp64') != 0 and high_half(tmp64') != -1, + * then we know that bit 31 to bit 63 of tmp64 can not all be the sign bit, and therefore tmp64 is 32-bit overflowed. + * That is, we judge if tmp64' > 0x00000000FFFFFFFF, or tmp64' <= 0xFFFFFFFF00000000. + * We use narrowing shift right 31 bits to tmp32' to save data bandwidth and instructions. + * That is, we judge if tmp32' > 0x00000000, or tmp32' <= 0xFFFFFFFF. + */ + +/* Compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +static OPUS_INLINE opus_int32 LPC_inverse_pred_gain_QA_neon( /* O Returns inverse prediction gain in energy domain, Q30 */ + opus_int32 A_QA[ SILK_MAX_ORDER_LPC ], /* I Prediction coefficients */ + const opus_int order /* I Prediction order */ +) +{ + opus_int k, n, mult2Q; + opus_int32 invGain_Q30, rc_Q31, rc_mult1_Q30, rc_mult2, tmp1, tmp2; + opus_int32 max, min; + int32x4_t max_s32x4, min_s32x4; + int32x2_t max_s32x2, min_s32x2; + + max_s32x4 = vdupq_n_s32( silk_int32_MIN ); + min_s32x4 = vdupq_n_s32( silk_int32_MAX ); + invGain_Q30 = SILK_FIX_CONST( 1, 30 ); + for( k = order - 1; k > 0; k-- ) { + int32x2_t rc_Q31_s32x2, rc_mult2_s32x2; + int64x2_t mult2Q_s64x2; + + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ k ], 31 - QA ); + + /* rc_mult1_Q30 range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + silk_assert( rc_mult1_Q30 > ( 1 << 15 ) ); /* reduce A_LIMIT if fails */ + silk_assert( rc_mult1_Q30 <= ( 1 << 30 ) ); + + /* Update inverse gain */ + /* invGain_Q30 range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + /* rc_mult2 range: [ 2^30 : silk_int32_MAX ] */ + mult2Q = 32 - silk_CLZ32( silk_abs( rc_mult1_Q30 ) ); + rc_mult2 = silk_INVERSE32_varQ( rc_mult1_Q30, mult2Q + 30 ); + + /* Update AR coefficient */ + rc_Q31_s32x2 = vdup_n_s32( rc_Q31 ); + mult2Q_s64x2 = vdupq_n_s64( -mult2Q ); + rc_mult2_s32x2 = vdup_n_s32( rc_mult2 ); + + for( n = 0; n < ( ( k + 1 ) >> 1 ) - 3; n += 4 ) { + /* We always calculate extra elements of A_QA buffer when ( k % 4 ) != 0, to take the advantage of SIMD parallelization. */ + int32x4_t tmp1_s32x4, tmp2_s32x4, t0_s32x4, t1_s32x4, s0_s32x4, s1_s32x4, t_QA0_s32x4, t_QA1_s32x4; + int64x2_t t0_s64x2, t1_s64x2, t2_s64x2, t3_s64x2; + tmp1_s32x4 = vld1q_s32( A_QA + n ); + tmp2_s32x4 = vld1q_s32( A_QA + k - n - 4 ); + tmp2_s32x4 = vrev64q_s32( tmp2_s32x4 ); + tmp2_s32x4 = vcombine_s32( vget_high_s32( tmp2_s32x4 ), vget_low_s32( tmp2_s32x4 ) ); + t0_s32x4 = vqrdmulhq_lane_s32( tmp2_s32x4, rc_Q31_s32x2, 0 ); + t1_s32x4 = vqrdmulhq_lane_s32( tmp1_s32x4, rc_Q31_s32x2, 0 ); + t_QA0_s32x4 = vqsubq_s32( tmp1_s32x4, t0_s32x4 ); + t_QA1_s32x4 = vqsubq_s32( tmp2_s32x4, t1_s32x4 ); + t0_s64x2 = vmull_s32( vget_low_s32 ( t_QA0_s32x4 ), rc_mult2_s32x2 ); + t1_s64x2 = vmull_s32( vget_high_s32( t_QA0_s32x4 ), rc_mult2_s32x2 ); + t2_s64x2 = vmull_s32( vget_low_s32 ( t_QA1_s32x4 ), rc_mult2_s32x2 ); + t3_s64x2 = vmull_s32( vget_high_s32( t_QA1_s32x4 ), rc_mult2_s32x2 ); + t0_s64x2 = vrshlq_s64( t0_s64x2, mult2Q_s64x2 ); + t1_s64x2 = vrshlq_s64( t1_s64x2, mult2Q_s64x2 ); + t2_s64x2 = vrshlq_s64( t2_s64x2, mult2Q_s64x2 ); + t3_s64x2 = vrshlq_s64( t3_s64x2, mult2Q_s64x2 ); + t0_s32x4 = vcombine_s32( vmovn_s64( t0_s64x2 ), vmovn_s64( t1_s64x2 ) ); + t1_s32x4 = vcombine_s32( vmovn_s64( t2_s64x2 ), vmovn_s64( t3_s64x2 ) ); + s0_s32x4 = vcombine_s32( vshrn_n_s64( t0_s64x2, 31 ), vshrn_n_s64( t1_s64x2, 31 ) ); + s1_s32x4 = vcombine_s32( vshrn_n_s64( t2_s64x2, 31 ), vshrn_n_s64( t3_s64x2, 31 ) ); + max_s32x4 = vmaxq_s32( max_s32x4, s0_s32x4 ); + min_s32x4 = vminq_s32( min_s32x4, s0_s32x4 ); + max_s32x4 = vmaxq_s32( max_s32x4, s1_s32x4 ); + min_s32x4 = vminq_s32( min_s32x4, s1_s32x4 ); + t1_s32x4 = vrev64q_s32( t1_s32x4 ); + t1_s32x4 = vcombine_s32( vget_high_s32( t1_s32x4 ), vget_low_s32( t1_s32x4 ) ); + vst1q_s32( A_QA + n, t0_s32x4 ); + vst1q_s32( A_QA + k - n - 4, t1_s32x4 ); + } + for( ; n < (k + 1) >> 1; n++ ) { + opus_int64 tmp64; + tmp1 = A_QA[ n ]; + tmp2 = A_QA[ k - n - 1 ]; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp1, + MUL32_FRAC_Q( tmp2, rc_Q31, 31 ) ), rc_mult2 ), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ n ] = ( opus_int32 )tmp64; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp2, + MUL32_FRAC_Q( tmp1, rc_Q31, 31 ) ), rc_mult2), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ k - n - 1 ] = ( opus_int32 )tmp64; + } + } + + /* Check for stability */ + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { + return 0; + } + + max_s32x2 = vmax_s32( vget_low_s32( max_s32x4 ), vget_high_s32( max_s32x4 ) ); + min_s32x2 = vmin_s32( vget_low_s32( min_s32x4 ), vget_high_s32( min_s32x4 ) ); + max_s32x2 = vmax_s32( max_s32x2, vreinterpret_s32_s64( vshr_n_s64( vreinterpret_s64_s32( max_s32x2 ), 32 ) ) ); + min_s32x2 = vmin_s32( min_s32x2, vreinterpret_s32_s64( vshr_n_s64( vreinterpret_s64_s32( min_s32x2 ), 32 ) ) ); + max = vget_lane_s32( max_s32x2, 0 ); + min = vget_lane_s32( min_s32x2, 0 ); + if( ( max > 0 ) || ( min < -1 ) ) { + return 0; + } + + /* Set RC equal to negated AR coef */ + rc_Q31 = -silk_LSHIFT( A_QA[ 0 ], 31 - QA ); + + /* Range: [ 1 : 2^30 ] */ + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); + + /* Update inverse gain */ + /* Range: [ 0 : 2^30 ] */ + invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } + + return invGain_Q30; +} + +/* For input in Q12 domain */ +opus_int32 silk_LPC_inverse_pred_gain_neon( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +) +{ +#ifdef OPUS_CHECK_ASM + const opus_int32 invGain_Q30_c = silk_LPC_inverse_pred_gain_c( A_Q12, order ); +#endif + + opus_int32 invGain_Q30; + if( ( SILK_MAX_ORDER_LPC != 24 ) || ( order & 1 )) { + invGain_Q30 = silk_LPC_inverse_pred_gain_c( A_Q12, order ); + } + else { + opus_int32 Atmp_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 DC_resp; + int16x8_t t0_s16x8, t1_s16x8, t2_s16x8; + int32x4_t t0_s32x4; + const opus_int leftover = order & 7; + + /* Increase Q domain of the AR coefficients */ + t0_s16x8 = vld1q_s16( A_Q12 + 0 ); + t1_s16x8 = vld1q_s16( A_Q12 + 8 ); + if ( order > 16 ) { + t2_s16x8 = vld1q_s16( A_Q12 + 16 ); + } + t0_s32x4 = vpaddlq_s16( t0_s16x8 ); + + switch( order - leftover ) + { + case 24: + t0_s32x4 = vpadalq_s16( t0_s32x4, t2_s16x8 ); + vst1q_s32( Atmp_QA + 16, vshll_n_s16( vget_low_s16 ( t2_s16x8 ), QA - 12 ) ); + vst1q_s32( Atmp_QA + 20, vshll_n_s16( vget_high_s16( t2_s16x8 ), QA - 12 ) ); + /* FALLTHROUGH */ + + case 16: + t0_s32x4 = vpadalq_s16( t0_s32x4, t1_s16x8 ); + vst1q_s32( Atmp_QA + 8, vshll_n_s16( vget_low_s16 ( t1_s16x8 ), QA - 12 ) ); + vst1q_s32( Atmp_QA + 12, vshll_n_s16( vget_high_s16( t1_s16x8 ), QA - 12 ) ); + /* FALLTHROUGH */ + + case 8: + { + const int32x2_t t_s32x2 = vpadd_s32( vget_low_s32( t0_s32x4 ), vget_high_s32( t0_s32x4 ) ); + const int64x1_t t_s64x1 = vpaddl_s32( t_s32x2 ); + DC_resp = vget_lane_s32( vreinterpret_s32_s64( t_s64x1 ), 0 ); + vst1q_s32( Atmp_QA + 0, vshll_n_s16( vget_low_s16 ( t0_s16x8 ), QA - 12 ) ); + vst1q_s32( Atmp_QA + 4, vshll_n_s16( vget_high_s16( t0_s16x8 ), QA - 12 ) ); + } + break; + + default: + DC_resp = 0; + break; + } + A_Q12 += order - leftover; + + switch( leftover ) + { + case 6: + DC_resp += (opus_int32)A_Q12[ 5 ]; + DC_resp += (opus_int32)A_Q12[ 4 ]; + Atmp_QA[ order - leftover + 5 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 5 ], QA - 12 ); + Atmp_QA[ order - leftover + 4 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 4 ], QA - 12 ); + /* FALLTHROUGH */ + + case 4: + DC_resp += (opus_int32)A_Q12[ 3 ]; + DC_resp += (opus_int32)A_Q12[ 2 ]; + Atmp_QA[ order - leftover + 3 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 3 ], QA - 12 ); + Atmp_QA[ order - leftover + 2 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 2 ], QA - 12 ); + /* FALLTHROUGH */ + + case 2: + DC_resp += (opus_int32)A_Q12[ 1 ]; + DC_resp += (opus_int32)A_Q12[ 0 ]; + Atmp_QA[ order - leftover + 1 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 1 ], QA - 12 ); + Atmp_QA[ order - leftover + 0 ] = silk_LSHIFT32( (opus_int32)A_Q12[ 0 ], QA - 12 ); + /* FALLTHROUGH */ + + default: + break; + } + + /* If the DC is unstable, we don't even need to do the full calculations */ + if( DC_resp >= 4096 ) { + invGain_Q30 = 0; + } else { + invGain_Q30 = LPC_inverse_pred_gain_QA_neon( Atmp_QA, order ); + } + } + +#ifdef OPUS_CHECK_ASM + silk_assert( invGain_Q30_c == invGain_Q30 ); +#endif + + return invGain_Q30; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_arm.h new file mode 100644 index 0000000000..0c4fcfccb4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_arm.h @@ -0,0 +1,100 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_NSQ_DEL_DEC_ARM_H +#define SILK_NSQ_DEL_DEC_ARM_H + +#include "celt/arm/armcpu.h" + +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void silk_NSQ_del_dec_neon( + const silk_encoder_state *psEncC, silk_nsq_state *NSQ, + SideInfoIndices *psIndices, const opus_int16 x16[], opus_int8 pulses[], + const opus_int16 *PredCoef_Q12, + const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], + const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], + const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], + const opus_int Tilt_Q14[MAX_NB_SUBFR], + const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], + const opus_int32 Gains_Q16[MAX_NB_SUBFR], + const opus_int pitchL[MAX_NB_SUBFR], const opus_int Lambda_Q10, + const opus_int LTP_scale_Q14); + +#if !defined(OPUS_HAVE_RTCD) +#define OVERRIDE_silk_NSQ_del_dec (1) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, \ + LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, \ + LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, \ + LTP_scale_Q14, arch) \ + ((void)(arch), \ + PRESUME_NEON(silk_NSQ_del_dec)( \ + psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, \ + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, \ + Lambda_Q10, LTP_scale_Q14)) +#endif +#endif + +#if !defined(OVERRIDE_silk_NSQ_del_dec) +/*Is run-time CPU detection enabled on this platform?*/ +#if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && \ + !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, silk_nsq_state *NSQ, + SideInfoIndices *psIndices, const opus_int16 x16[], opus_int8 pulses[], + const opus_int16 *PredCoef_Q12, + const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], + const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], + const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], + const opus_int Tilt_Q14[MAX_NB_SUBFR], + const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], + const opus_int32 Gains_Q16[MAX_NB_SUBFR], + const opus_int pitchL[MAX_NB_SUBFR], const opus_int Lambda_Q10, + const opus_int LTP_scale_Q14); +#define OVERRIDE_silk_NSQ_del_dec (1) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, \ + LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, \ + LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, \ + LTP_scale_Q14, arch) \ + ((*SILK_NSQ_DEL_DEC_IMPL[(arch)&OPUS_ARCHMASK])( \ + psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, \ + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, \ + Lambda_Q10, LTP_scale_Q14)) +#elif defined(OPUS_ARM_PRESUME_NEON_INTR) +#define OVERRIDE_silk_NSQ_del_dec (1) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, \ + LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, \ + LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, \ + LTP_scale_Q14, arch) \ + ((void)(arch), \ + silk_NSQ_del_dec_neon(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, \ + LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, \ + LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, \ + LTP_scale_Q14)) +#endif +#endif + +#endif /* end SILK_NSQ_DEL_DEC_ARM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_neon_intr.c new file mode 100644 index 0000000000..668dde6dc4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_del_dec_neon_intr.c @@ -0,0 +1,1138 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef OPUS_CHECK_ASM +# include +#endif +#include "main.h" +#include "stack_alloc.h" +#include "os_support.h" + +/* NEON intrinsics optimization now can only parallelize up to 4 delay decision states. */ +/* If there are more states, C function is called, and this optimization must be expanded. */ +#define NEON_MAX_DEL_DEC_STATES 4 + +typedef struct { + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 RandState[ DECISION_DELAY ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Q_Q10[ DECISION_DELAY ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Xq_Q14[ DECISION_DELAY ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Pred_Q15[ DECISION_DELAY ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Shape_Q14[ DECISION_DELAY ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ][ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 LF_AR_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Diff_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Seed[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 SeedInit[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 RD_Q10[ NEON_MAX_DEL_DEC_STATES ]; +} NSQ_del_decs_struct; + +typedef struct { + opus_int32 Q_Q10[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 RD_Q10[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 xq_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 LF_AR_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 Diff_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 sLTP_shp_Q14[ NEON_MAX_DEL_DEC_STATES ]; + opus_int32 LPC_exc_Q14[ NEON_MAX_DEL_DEC_STATES ]; +} NSQ_samples_struct; + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_neon( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_decs_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_decs_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +); + +static OPUS_INLINE void copy_winner_state_kernel( + const NSQ_del_decs_struct *psDelDec, + const opus_int offset, + const opus_int last_smple_idx, + const opus_int Winner_ind, + const int32x2_t gain_lo_s32x2, + const int32x2_t gain_hi_s32x2, + const int32x4_t shift_s32x4, + int32x4_t t0_s32x4, + int32x4_t t1_s32x4, + opus_int8 *const pulses, + opus_int16 *pxq, + silk_nsq_state *NSQ +) +{ + int16x8_t t_s16x8; + int32x4_t o0_s32x4, o1_s32x4; + + t0_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 0 ][ Winner_ind ], t0_s32x4, 0 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 1 ][ Winner_ind ], t0_s32x4, 1 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 2 ][ Winner_ind ], t0_s32x4, 2 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 3 ][ Winner_ind ], t0_s32x4, 3 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 4 ][ Winner_ind ], t1_s32x4, 0 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 5 ][ Winner_ind ], t1_s32x4, 1 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 6 ][ Winner_ind ], t1_s32x4, 2 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Q_Q10[ last_smple_idx - 7 ][ Winner_ind ], t1_s32x4, 3 ); + t_s16x8 = vcombine_s16( vrshrn_n_s32( t0_s32x4, 10 ), vrshrn_n_s32( t1_s32x4, 10 ) ); + vst1_s8( &pulses[ offset ], vmovn_s16( t_s16x8 ) ); + + t0_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 0 ][ Winner_ind ], t0_s32x4, 0 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 1 ][ Winner_ind ], t0_s32x4, 1 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 2 ][ Winner_ind ], t0_s32x4, 2 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 3 ][ Winner_ind ], t0_s32x4, 3 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 4 ][ Winner_ind ], t1_s32x4, 0 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 5 ][ Winner_ind ], t1_s32x4, 1 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 6 ][ Winner_ind ], t1_s32x4, 2 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Xq_Q14[ last_smple_idx - 7 ][ Winner_ind ], t1_s32x4, 3 ); + o0_s32x4 = vqdmulhq_lane_s32( t0_s32x4, gain_lo_s32x2, 0 ); + o1_s32x4 = vqdmulhq_lane_s32( t1_s32x4, gain_lo_s32x2, 0 ); + o0_s32x4 = vmlaq_lane_s32( o0_s32x4, t0_s32x4, gain_hi_s32x2, 0 ); + o1_s32x4 = vmlaq_lane_s32( o1_s32x4, t1_s32x4, gain_hi_s32x2, 0 ); + o0_s32x4 = vrshlq_s32( o0_s32x4, shift_s32x4 ); + o1_s32x4 = vrshlq_s32( o1_s32x4, shift_s32x4 ); + vst1_s16( &pxq[ offset + 0 ], vqmovn_s32( o0_s32x4 ) ); + vst1_s16( &pxq[ offset + 4 ], vqmovn_s32( o1_s32x4 ) ); + + t0_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 0 ][ Winner_ind ], t0_s32x4, 0 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 1 ][ Winner_ind ], t0_s32x4, 1 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 2 ][ Winner_ind ], t0_s32x4, 2 ); + t0_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 3 ][ Winner_ind ], t0_s32x4, 3 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 4 ][ Winner_ind ], t1_s32x4, 0 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 5 ][ Winner_ind ], t1_s32x4, 1 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 6 ][ Winner_ind ], t1_s32x4, 2 ); + t1_s32x4 = vld1q_lane_s32( &psDelDec->Shape_Q14[ last_smple_idx - 7 ][ Winner_ind ], t1_s32x4, 3 ); + vst1q_s32( &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx + offset + 0 ], t0_s32x4 ); + vst1q_s32( &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx + offset + 4 ], t1_s32x4 ); +} + +static OPUS_INLINE void copy_winner_state( + const NSQ_del_decs_struct *psDelDec, + const opus_int decisionDelay, + const opus_int smpl_buf_idx, + const opus_int Winner_ind, + const opus_int32 gain, + const opus_int32 shift, + opus_int8 *const pulses, + opus_int16 *pxq, + silk_nsq_state *NSQ +) +{ + opus_int i, last_smple_idx; + const int32x2_t gain_lo_s32x2 = vdup_n_s32( silk_LSHIFT32( gain & 0x0000FFFF, 15 ) ); + const int32x2_t gain_hi_s32x2 = vdup_n_s32( gain >> 16 ); + const int32x4_t shift_s32x4 = vdupq_n_s32( -shift ); + int32x4_t t0_s32x4, t1_s32x4; + + t0_s32x4 = t1_s32x4 = vdupq_n_s32( 0 ); /* initialization */ + last_smple_idx = smpl_buf_idx + decisionDelay - 1 + DECISION_DELAY; + if( last_smple_idx >= DECISION_DELAY ) last_smple_idx -= DECISION_DELAY; + if( last_smple_idx >= DECISION_DELAY ) last_smple_idx -= DECISION_DELAY; + + for( i = 0; ( i < ( decisionDelay - 7 ) ) && ( last_smple_idx >= 7 ); i += 8, last_smple_idx -= 8 ) { + copy_winner_state_kernel( psDelDec, i - decisionDelay, last_smple_idx, Winner_ind, gain_lo_s32x2, gain_hi_s32x2, shift_s32x4, t0_s32x4, t1_s32x4, pulses, pxq, NSQ ); + } + for( ; ( i < decisionDelay ) && ( last_smple_idx >= 0 ); i++, last_smple_idx-- ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDelDec->Q_Q10[ last_smple_idx ][ Winner_ind ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psDelDec->Xq_Q14[ last_smple_idx ][ Winner_ind ], gain ), shift ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDelDec->Shape_Q14[ last_smple_idx ][ Winner_ind ]; + } + + last_smple_idx += DECISION_DELAY; + for( ; i < ( decisionDelay - 7 ); i++, last_smple_idx-- ) { + copy_winner_state_kernel( psDelDec, i - decisionDelay, last_smple_idx, Winner_ind, gain_lo_s32x2, gain_hi_s32x2, shift_s32x4, t0_s32x4, t1_s32x4, pulses, pxq, NSQ ); + } + for( ; i < decisionDelay; i++, last_smple_idx-- ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDelDec->Q_Q10[ last_smple_idx ][ Winner_ind ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psDelDec->Xq_Q14[ last_smple_idx ][ Winner_ind ], gain ), shift ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDelDec->Shape_Q14[ last_smple_idx ][ Winner_ind ]; + } +} + +void silk_NSQ_del_dec_neon( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ +#ifdef OPUS_CHECK_ASM + silk_nsq_state NSQ_c; + SideInfoIndices psIndices_c; + opus_int8 pulses_c[ MAX_FRAME_LENGTH ]; + const opus_int8 *const pulses_a = pulses; + + ( void )pulses_a; + silk_memcpy( &NSQ_c, NSQ, sizeof( NSQ_c ) ); + silk_memcpy( &psIndices_c, psIndices, sizeof( psIndices_c ) ); + silk_memcpy( pulses_c, pulses, sizeof( pulses_c ) ); + silk_NSQ_del_dec_c( psEncC, &NSQ_c, &psIndices_c, x16, pulses_c, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, + pitchL, Lambda_Q10, LTP_scale_Q14 ); +#endif + + /* The optimization parallelizes the different delay decision states. */ + if(( psEncC->nStatesDelayedDecision > NEON_MAX_DEL_DEC_STATES ) || ( psEncC->nStatesDelayedDecision <= 2 )) { + /* NEON intrinsics optimization now can only parallelize up to 4 delay decision states. */ + /* If there are more states, C function is called, and this optimization must be expanded. */ + /* When the number of delay decision states is less than 3, there are penalties using this */ + /* optimization, and C function is called. */ + /* When the number of delay decision states is 2, it's better to specialize another */ + /* structure NSQ_del_dec2_struct and optimize with shorter NEON registers. (Low priority) */ + silk_NSQ_del_dec_c( psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, + Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14 ); + } else { + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 RDmin_Q10, Gain_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + VARDECL( opus_int32, delayedGain_Q10 ); + VARDECL( NSQ_del_decs_struct, psDelDec ); + int32x4_t t_s32x4; + SAVE_STACK; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + /* Initialize delayed decision states */ + ALLOC( psDelDec, 1, NSQ_del_decs_struct ); + OPUS_CLEAR(psDelDec, 1); + /* Only RandState and RD_Q10 need to be initialized to 0. */ + silk_memset( psDelDec->RandState, 0, sizeof( psDelDec->RandState ) ); + vst1q_s32( psDelDec->RD_Q10, vdupq_n_s32( 0 ) ); + + for( k = 0; k < psEncC->nStatesDelayedDecision; k++ ) { + psDelDec->SeedInit[ k ] = psDelDec->Seed[ k ] = ( k + psIndices->Seed ) & 3; + } + vst1q_s32( psDelDec->LF_AR_Q14, vld1q_dup_s32( &NSQ->sLF_AR_shp_Q14 ) ); + vst1q_s32( psDelDec->Diff_Q14, vld1q_dup_s32( &NSQ->sDiff_shp_Q14 ) ); + vst1q_s32( psDelDec->Shape_Q14[ 0 ], vld1q_dup_s32( &NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ] ) ); + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + vst1q_s32( psDelDec->sLPC_Q14[ i ], vld1q_dup_s32( &NSQ->sLPC_Q14[ i ] ) ); + } + for( i = 0; i < (opus_int)( sizeof( NSQ->sAR2_Q14 ) / sizeof( NSQ->sAR2_Q14[ 0 ] ) ); i++ ) { + vst1q_s32( psDelDec->sAR2_Q14[ i ], vld1q_dup_s32( &NSQ->sAR2_Q14[ i ] ) ); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int( DECISION_DELAY, psEncC->subfr_length ); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if( psIndices->signalType == TYPE_VOICED ) { + opus_int pitch_min = pitchL[ 0 ]; + for( k = 1; k < psEncC->nb_subfr; k++ ) { + pitch_min = silk_min_int( pitch_min, pitchL[ k ] ); + } + decisionDelay = silk_min_int( decisionDelay, pitch_min - LTP_ORDER / 2 - 1 ); + } else { + if( lag > 0 ) { + decisionDelay = silk_min_int( decisionDelay, lag - LTP_ORDER / 2 - 1 ); + } + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + if( k == 2 ) { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + int32x4_t RD_Q10_s32x4; + RDmin_Q10 = psDelDec->RD_Q10[ 0 ]; + Winner_ind = 0; + for( i = 1; i < psEncC->nStatesDelayedDecision; i++ ) { + if( psDelDec->RD_Q10[ i ] < RDmin_Q10 ) { + RDmin_Q10 = psDelDec->RD_Q10[ i ]; + Winner_ind = i; + } + } + psDelDec->RD_Q10[ Winner_ind ] -= ( silk_int32_MAX >> 4 ); + RD_Q10_s32x4 = vld1q_s32( psDelDec->RD_Q10 ); + RD_Q10_s32x4 = vaddq_s32( RD_Q10_s32x4, vdupq_n_s32( silk_int32_MAX >> 4 ) ); + vst1q_s32( psDelDec->RD_Q10, RD_Q10_s32x4 ); + + /* Copy final part of signals from winner state to output and long-term filter states */ + copy_winner_state( psDelDec, decisionDelay, smpl_buf_idx, Winner_ind, Gains_Q16[ 1 ], 14, pulses, pxq, NSQ ); + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + silk_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states_neon( psEncC, NSQ, psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, + LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); + + silk_noise_shape_quantizer_del_dec_neon( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], + Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = psDelDec->RD_Q10[ 0 ]; + Winner_ind = 0; + for( k = 1; k < psEncC->nStatesDelayedDecision; k++ ) { + if( psDelDec->RD_Q10[ k ] < RDmin_Q10 ) { + RDmin_Q10 = psDelDec->RD_Q10[ k ]; + Winner_ind = k; + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psIndices->Seed = psDelDec->SeedInit[ Winner_ind ]; + Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); + copy_winner_state( psDelDec, decisionDelay, smpl_buf_idx, Winner_ind, Gain_Q10, 8, pulses, pxq, NSQ ); + + t_s32x4 = vdupq_n_s32( 0 ); /* initialization */ + for( i = 0; i < ( NSQ_LPC_BUF_LENGTH - 3 ); i += 4 ) { + t_s32x4 = vld1q_lane_s32( &psDelDec->sLPC_Q14[ i + 0 ][ Winner_ind ], t_s32x4, 0 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sLPC_Q14[ i + 1 ][ Winner_ind ], t_s32x4, 1 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sLPC_Q14[ i + 2 ][ Winner_ind ], t_s32x4, 2 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sLPC_Q14[ i + 3 ][ Winner_ind ], t_s32x4, 3 ); + vst1q_s32( &NSQ->sLPC_Q14[ i ], t_s32x4 ); + } + + for( ; i < NSQ_LPC_BUF_LENGTH; i++ ) { + NSQ->sLPC_Q14[ i ] = psDelDec->sLPC_Q14[ i ][ Winner_ind ]; + } + + for( i = 0; i < (opus_int)( sizeof( NSQ->sAR2_Q14 ) / sizeof( NSQ->sAR2_Q14[ 0 ] ) - 3 ); i += 4 ) { + t_s32x4 = vld1q_lane_s32( &psDelDec->sAR2_Q14[ i + 0 ][ Winner_ind ], t_s32x4, 0 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sAR2_Q14[ i + 1 ][ Winner_ind ], t_s32x4, 1 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sAR2_Q14[ i + 2 ][ Winner_ind ], t_s32x4, 2 ); + t_s32x4 = vld1q_lane_s32( &psDelDec->sAR2_Q14[ i + 3 ][ Winner_ind ], t_s32x4, 3 ); + vst1q_s32( &NSQ->sAR2_Q14[ i ], t_s32x4 ); + } + + for( ; i < (opus_int)( sizeof( NSQ->sAR2_Q14 ) / sizeof( NSQ->sAR2_Q14[ 0 ] ) ); i++ ) { + NSQ->sAR2_Q14[ i ] = psDelDec->sAR2_Q14[ i ][ Winner_ind ]; + } + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = psDelDec->LF_AR_Q14[ Winner_ind ]; + NSQ->sDiff_shp_Q14 = psDelDec->Diff_Q14[ Winner_ind ]; + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech signal */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; + } + +#ifdef OPUS_CHECK_ASM + silk_assert( !memcmp( &NSQ_c, NSQ, sizeof( NSQ_c ) ) ); + silk_assert( !memcmp( &psIndices_c, psIndices, sizeof( psIndices_c ) ) ); + silk_assert( !memcmp( pulses_c, pulses_a, sizeof( pulses_c ) ) ); +#endif +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +/* Note: Function silk_short_prediction_create_arch_coef_neon() defined in NSQ_neon.h is actually a hacking C function. */ +/* Therefore here we append "_local" to the NEON function name to avoid confusion. */ +static OPUS_INLINE void silk_short_prediction_create_arch_coef_neon_local(opus_int32 *out, const opus_int16 *in, opus_int order) +{ + int16x8_t t_s16x8; + int32x4_t t0_s32x4, t1_s32x4, t2_s32x4, t3_s32x4; + silk_assert( order == 10 || order == 16 ); + + t_s16x8 = vld1q_s16( in + 0 ); /* 7 6 5 4 3 2 1 0 */ + t_s16x8 = vrev64q_s16( t_s16x8 ); /* 4 5 6 7 0 1 2 3 */ + t2_s32x4 = vshll_n_s16( vget_high_s16( t_s16x8 ), 15 ); /* 4 5 6 7 */ + t3_s32x4 = vshll_n_s16( vget_low_s16( t_s16x8 ), 15 ); /* 0 1 2 3 */ + + if( order == 16 ) { + t_s16x8 = vld1q_s16( in + 8 ); /* F E D C B A 9 8 */ + t_s16x8 = vrev64q_s16( t_s16x8 ); /* C D E F 8 9 A B */ + t0_s32x4 = vshll_n_s16( vget_high_s16( t_s16x8 ), 15 ); /* C D E F */ + t1_s32x4 = vshll_n_s16( vget_low_s16( t_s16x8 ), 15 ); /* 8 9 A B */ + } else { + int16x4_t t_s16x4; + + t0_s32x4 = vdupq_n_s32( 0 ); /* zero zero zero zero */ + t_s16x4 = vld1_s16( in + 6 ); /* 9 8 7 6 */ + t_s16x4 = vrev64_s16( t_s16x4 ); /* 6 7 8 9 */ + t1_s32x4 = vshll_n_s16( t_s16x4, 15 ); + t1_s32x4 = vcombine_s32( vget_low_s32(t0_s32x4), vget_low_s32( t1_s32x4 ) ); /* 8 9 zero zero */ + } + vst1q_s32( out + 0, t0_s32x4 ); + vst1q_s32( out + 4, t1_s32x4 ); + vst1q_s32( out + 8, t2_s32x4 ); + vst1q_s32( out + 12, t3_s32x4 ); +} + +static OPUS_INLINE int32x4_t silk_SMLAWB_lane0_neon( + const int32x4_t out_s32x4, + const int32x4_t in_s32x4, + const int32x2_t coef_s32x2 +) +{ + return vaddq_s32( out_s32x4, vqdmulhq_lane_s32( in_s32x4, coef_s32x2, 0 ) ); +} + +static OPUS_INLINE int32x4_t silk_SMLAWB_lane1_neon( + const int32x4_t out_s32x4, + const int32x4_t in_s32x4, + const int32x2_t coef_s32x2 +) +{ + return vaddq_s32( out_s32x4, vqdmulhq_lane_s32( in_s32x4, coef_s32x2, 1 ) ); +} + +/* Note: This function has different return value than silk_noise_shape_quantizer_short_prediction_neon(). */ +/* Therefore here we append "_local" to the function name to avoid confusion. */ +static OPUS_INLINE int32x4_t silk_noise_shape_quantizer_short_prediction_neon_local(const opus_int32 *buf32, const opus_int32 *a_Q12_arch, opus_int order) +{ + const int32x4_t a_Q12_arch0_s32x4 = vld1q_s32( a_Q12_arch + 0 ); + const int32x4_t a_Q12_arch1_s32x4 = vld1q_s32( a_Q12_arch + 4 ); + const int32x4_t a_Q12_arch2_s32x4 = vld1q_s32( a_Q12_arch + 8 ); + const int32x4_t a_Q12_arch3_s32x4 = vld1q_s32( a_Q12_arch + 12 ); + int32x4_t LPC_pred_Q14_s32x4; + + silk_assert( order == 10 || order == 16 ); + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q14_s32x4 = vdupq_n_s32( silk_RSHIFT( order, 1 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 0 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch0_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 1 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch0_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 2 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch0_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 3 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch0_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 4 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch1_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 5 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch1_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 6 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch1_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 7 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch1_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 8 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch2_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 9 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch2_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 10 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch2_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 11 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch2_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 12 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch3_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 13 * NEON_MAX_DEL_DEC_STATES ), vget_low_s32( a_Q12_arch3_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane0_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 14 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch3_s32x4 ) ); + LPC_pred_Q14_s32x4 = silk_SMLAWB_lane1_neon( LPC_pred_Q14_s32x4, vld1q_s32( buf32 + 15 * NEON_MAX_DEL_DEC_STATES ), vget_high_s32( a_Q12_arch3_s32x4 ) ); + + return LPC_pred_Q14_s32x4; +} + +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_neon( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_decs_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, n_LTP_Q14; + opus_int32 RDmin_Q10, RDmax_Q10; + opus_int32 Gain_Q10; + opus_int32 *pred_lag_ptr, *shp_lag_ptr; + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; + const int32x2_t warping_Q16_s32x2 = vdup_n_s32( silk_LSHIFT32( warping_Q16, 16 ) >> 1 ); + const opus_int32 LF_shp_Q29 = silk_LSHIFT32( LF_shp_Q14, 16 ) >> 1; + opus_int32 AR_shp_Q28[ MAX_SHAPE_LPC_ORDER ]; + const uint32x4_t rand_multiplier_u32x4 = vdupq_n_u32( RAND_MULTIPLIER ); + const uint32x4_t rand_increment_u32x4 = vdupq_n_u32( RAND_INCREMENT ); + + VARDECL( NSQ_samples_struct, psSampleState ); + SAVE_STACK; + + silk_assert( nStatesDelayedDecision > 0 ); + silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + ALLOC( psSampleState, 2, NSQ_samples_struct ); + OPUS_CLEAR(psSampleState, 2); + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + for( i = 0; i < ( MAX_SHAPE_LPC_ORDER - 7 ); i += 8 ) { + const int16x8_t t_s16x8 = vld1q_s16( AR_shp_Q13 + i ); + vst1q_s32( AR_shp_Q28 + i + 0, vshll_n_s16( vget_low_s16( t_s16x8 ), 15 ) ); + vst1q_s32( AR_shp_Q28 + i + 4, vshll_n_s16( vget_high_s16( t_s16x8 ), 15 ) ); + } + + for( ; i < MAX_SHAPE_LPC_ORDER; i++ ) { + AR_shp_Q28[i] = silk_LSHIFT32( AR_shp_Q13[i], 15 ); + } + + silk_short_prediction_create_arch_coef_neon_local( a_Q12_arch, a_Q12, predictLPCOrder ); + + for( i = 0; i < length; i++ ) { + int32x4_t Seed_s32x4, LPC_pred_Q14_s32x4; + int32x4_t sign_s32x4, tmp1_s32x4, tmp2_s32x4; + int32x4_t n_AR_Q14_s32x4, n_LF_Q14_s32x4; + int32x2_t AR_shp_Q28_s32x2; + int16x4_t r_Q10_s16x4, rr_Q10_s16x4; + + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ 0 ], b_Q14[ 0 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -1 ], b_Q14[ 1 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -2 ], b_Q14[ 2 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -3 ], b_Q14[ 3 ] ); + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + + /* Generate dither */ + Seed_s32x4 = vld1q_s32( psDelDec->Seed ); + Seed_s32x4 = vreinterpretq_s32_u32( vmlaq_u32( rand_increment_u32x4, vreinterpretq_u32_s32( Seed_s32x4 ), rand_multiplier_u32x4 ) ); + vst1q_s32( psDelDec->Seed, Seed_s32x4 ); + + /* Short-term prediction */ + LPC_pred_Q14_s32x4 = silk_noise_shape_quantizer_short_prediction_neon_local(psDelDec->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 16 + i ], a_Q12_arch, predictLPCOrder); + LPC_pred_Q14_s32x4 = vshlq_n_s32( LPC_pred_Q14_s32x4, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + /* Output of lowpass section */ + tmp2_s32x4 = silk_SMLAWB_lane0_neon( vld1q_s32( psDelDec->Diff_Q14 ), vld1q_s32( psDelDec->sAR2_Q14[ 0 ] ), warping_Q16_s32x2 ); + /* Output of allpass section */ + tmp1_s32x4 = vsubq_s32( vld1q_s32( psDelDec->sAR2_Q14[ 1 ] ), tmp2_s32x4 ); + tmp1_s32x4 = silk_SMLAWB_lane0_neon( vld1q_s32( psDelDec->sAR2_Q14[ 0 ] ), tmp1_s32x4, warping_Q16_s32x2 ); + vst1q_s32( psDelDec->sAR2_Q14[ 0 ], tmp2_s32x4 ); + AR_shp_Q28_s32x2 = vld1_s32( AR_shp_Q28 ); + n_AR_Q14_s32x4 = vaddq_s32( vdupq_n_s32( silk_RSHIFT( shapingLPCOrder, 1 ) ), vqdmulhq_lane_s32( tmp2_s32x4, AR_shp_Q28_s32x2, 0 ) ); + + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + /* Output of allpass section */ + tmp2_s32x4 = vsubq_s32( vld1q_s32( psDelDec->sAR2_Q14[ j + 0 ] ), tmp1_s32x4 ); + tmp2_s32x4 = silk_SMLAWB_lane0_neon( vld1q_s32( psDelDec->sAR2_Q14[ j - 1 ] ), tmp2_s32x4, warping_Q16_s32x2 ); + vst1q_s32( psDelDec->sAR2_Q14[ j - 1 ], tmp1_s32x4 ); + n_AR_Q14_s32x4 = vaddq_s32( n_AR_Q14_s32x4, vqdmulhq_lane_s32( tmp1_s32x4, AR_shp_Q28_s32x2, 1 ) ); + /* Output of allpass section */ + tmp1_s32x4 = vsubq_s32( vld1q_s32( psDelDec->sAR2_Q14[ j + 1 ] ), tmp2_s32x4 ); + tmp1_s32x4 = silk_SMLAWB_lane0_neon( vld1q_s32( psDelDec->sAR2_Q14[ j + 0 ] ), tmp1_s32x4, warping_Q16_s32x2 ); + vst1q_s32( psDelDec->sAR2_Q14[ j + 0 ], tmp2_s32x4 ); + AR_shp_Q28_s32x2 = vld1_s32( &AR_shp_Q28[ j ] ); + n_AR_Q14_s32x4 = vaddq_s32( n_AR_Q14_s32x4, vqdmulhq_lane_s32( tmp2_s32x4, AR_shp_Q28_s32x2, 0 ) ); + } + vst1q_s32( psDelDec->sAR2_Q14[ shapingLPCOrder - 1 ], tmp1_s32x4 ); + n_AR_Q14_s32x4 = vaddq_s32( n_AR_Q14_s32x4, vqdmulhq_lane_s32( tmp1_s32x4, AR_shp_Q28_s32x2, 1 ) ); + n_AR_Q14_s32x4 = vshlq_n_s32( n_AR_Q14_s32x4, 1 ); /* Q11 -> Q12 */ + n_AR_Q14_s32x4 = vaddq_s32( n_AR_Q14_s32x4, vqdmulhq_n_s32( vld1q_s32( psDelDec->LF_AR_Q14 ), silk_LSHIFT32( Tilt_Q14, 16 ) >> 1 ) ); /* Q12 */ + n_AR_Q14_s32x4 = vshlq_n_s32( n_AR_Q14_s32x4, 2 ); /* Q12 -> Q14 */ + n_LF_Q14_s32x4 = vqdmulhq_n_s32( vld1q_s32( psDelDec->Shape_Q14[ *smpl_buf_idx ] ), LF_shp_Q29 ); /* Q12 */ + n_LF_Q14_s32x4 = vaddq_s32( n_LF_Q14_s32x4, vqdmulhq_n_s32( vld1q_s32( psDelDec->LF_AR_Q14 ), silk_LSHIFT32( LF_shp_Q14 >> 16 , 15 ) ) ); /* Q12 */ + n_LF_Q14_s32x4 = vshlq_n_s32( n_LF_Q14_s32x4, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1_s32x4 = vaddq_s32( n_AR_Q14_s32x4, n_LF_Q14_s32x4 ); /* Q14 */ + tmp2_s32x4 = vaddq_s32( vdupq_n_s32( n_LTP_Q14 ), LPC_pred_Q14_s32x4 ); /* Q13 */ + tmp1_s32x4 = vsubq_s32( tmp2_s32x4, tmp1_s32x4 ); /* Q13 */ + tmp1_s32x4 = vrshrq_n_s32( tmp1_s32x4, 4 ); /* Q10 */ + tmp1_s32x4 = vsubq_s32( vdupq_n_s32( x_Q10[ i ] ), tmp1_s32x4 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + sign_s32x4 = vreinterpretq_s32_u32( vcltq_s32( Seed_s32x4, vdupq_n_s32( 0 ) ) ); + tmp1_s32x4 = veorq_s32( tmp1_s32x4, sign_s32x4 ); + tmp1_s32x4 = vsubq_s32( tmp1_s32x4, sign_s32x4 ); + tmp1_s32x4 = vmaxq_s32( tmp1_s32x4, vdupq_n_s32( -( 31 << 10 ) ) ); + tmp1_s32x4 = vminq_s32( tmp1_s32x4, vdupq_n_s32( 30 << 10 ) ); + r_Q10_s16x4 = vmovn_s32( tmp1_s32x4 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + { + int16x4_t q1_Q10_s16x4 = vsub_s16( r_Q10_s16x4, vdup_n_s16( offset_Q10 ) ); + int16x4_t q1_Q0_s16x4 = vshr_n_s16( q1_Q10_s16x4, 10 ); + int16x4_t q2_Q10_s16x4; + int32x4_t rd1_Q10_s32x4, rd2_Q10_s32x4; + uint32x4_t t_u32x4; + + if( Lambda_Q10 > 2048 ) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + const int rdo_offset = Lambda_Q10/2 - 512; + const uint16x4_t greaterThanRdo = vcgt_s16( q1_Q10_s16x4, vdup_n_s16( rdo_offset ) ); + const uint16x4_t lessThanMinusRdo = vclt_s16( q1_Q10_s16x4, vdup_n_s16( -rdo_offset ) ); + int16x4_t signed_offset = vbsl_s16( greaterThanRdo, vdup_n_s16( -rdo_offset ), vdup_n_s16( 0 ) ); + signed_offset = vbsl_s16( lessThanMinusRdo, vdup_n_s16( rdo_offset ), signed_offset ); + /* If Lambda_Q10 > 32767, then q1_Q0, q1_Q10 and q2_Q10 must change to 32-bit. */ + silk_assert( Lambda_Q10 <= 32767 ); + + q1_Q0_s16x4 = vreinterpret_s16_u16( vclt_s16( q1_Q10_s16x4, vdup_n_s16( 0 ) ) ); + q1_Q0_s16x4 = vbsl_s16(vorr_u16(greaterThanRdo, lessThanMinusRdo), vadd_s16( q1_Q10_s16x4 , signed_offset), q1_Q0_s16x4); + q1_Q0_s16x4 = vshr_n_s16( q1_Q0_s16x4, 10 ); + } + { + const uint16x4_t equal0_u16x4 = vceq_s16( q1_Q0_s16x4, vdup_n_s16( 0 ) ); + const uint16x4_t equalMinus1_u16x4 = vceq_s16( q1_Q0_s16x4, vdup_n_s16( -1 ) ); + const uint16x4_t lessThanMinus1_u16x4 = vclt_s16( q1_Q0_s16x4, vdup_n_s16( -1 ) ); + int16x4_t tmp1_s16x4, tmp2_s16x4, tmp_summand_s16x4; + + q1_Q10_s16x4 = vshl_n_s16( q1_Q0_s16x4, 10 ); + tmp_summand_s16x4 = vand_s16( vreinterpret_s16_u16(vcge_s16(q1_Q0_s16x4, vdup_n_s16(0))), vdup_n_s16( offset_Q10 - QUANT_LEVEL_ADJUST_Q10 ) ); + tmp1_s16x4 = vadd_s16( q1_Q10_s16x4, tmp_summand_s16x4 ); + tmp_summand_s16x4 = vbsl_s16( lessThanMinus1_u16x4, vdup_n_s16( offset_Q10 + QUANT_LEVEL_ADJUST_Q10 ), vdup_n_s16(0) ); + q1_Q10_s16x4 = vadd_s16( q1_Q10_s16x4, tmp_summand_s16x4); + q1_Q10_s16x4 = vbsl_s16( lessThanMinus1_u16x4, q1_Q10_s16x4, tmp1_s16x4 ); + q1_Q10_s16x4 = vbsl_s16( equal0_u16x4, vdup_n_s16( offset_Q10 ), q1_Q10_s16x4 ); + q1_Q10_s16x4 = vbsl_s16( equalMinus1_u16x4, vdup_n_s16( offset_Q10 - ( 1024 - QUANT_LEVEL_ADJUST_Q10 ) ), q1_Q10_s16x4 ); + q2_Q10_s16x4 = vadd_s16( q1_Q10_s16x4, vdup_n_s16( 1024 ) ); + q2_Q10_s16x4 = vbsl_s16( equal0_u16x4, vdup_n_s16( offset_Q10 + 1024 - QUANT_LEVEL_ADJUST_Q10 ), q2_Q10_s16x4 ); + q2_Q10_s16x4 = vbsl_s16( equalMinus1_u16x4, vdup_n_s16( offset_Q10 ), q2_Q10_s16x4 ); + tmp1_s16x4 = q1_Q10_s16x4; + tmp2_s16x4 = q2_Q10_s16x4; + tmp1_s16x4 = vbsl_s16( vorr_u16( equalMinus1_u16x4, lessThanMinus1_u16x4 ), vneg_s16( tmp1_s16x4 ), tmp1_s16x4 ); + tmp2_s16x4 = vbsl_s16( lessThanMinus1_u16x4, vneg_s16( tmp2_s16x4 ), tmp2_s16x4 ); + rd1_Q10_s32x4 = vmull_s16( tmp1_s16x4, vdup_n_s16( Lambda_Q10 ) ); + rd2_Q10_s32x4 = vmull_s16( tmp2_s16x4, vdup_n_s16( Lambda_Q10 ) ); + } + + rr_Q10_s16x4 = vsub_s16( r_Q10_s16x4, q1_Q10_s16x4 ); + rd1_Q10_s32x4 = vmlal_s16( rd1_Q10_s32x4, rr_Q10_s16x4, rr_Q10_s16x4 ); + rd1_Q10_s32x4 = vshrq_n_s32( rd1_Q10_s32x4, 10 ); + + rr_Q10_s16x4 = vsub_s16( r_Q10_s16x4, q2_Q10_s16x4 ); + rd2_Q10_s32x4 = vmlal_s16( rd2_Q10_s32x4, rr_Q10_s16x4, rr_Q10_s16x4 ); + rd2_Q10_s32x4 = vshrq_n_s32( rd2_Q10_s32x4, 10 ); + + tmp2_s32x4 = vld1q_s32( psDelDec->RD_Q10 ); + tmp1_s32x4 = vaddq_s32( tmp2_s32x4, vminq_s32( rd1_Q10_s32x4, rd2_Q10_s32x4 ) ); + tmp2_s32x4 = vaddq_s32( tmp2_s32x4, vmaxq_s32( rd1_Q10_s32x4, rd2_Q10_s32x4 ) ); + vst1q_s32( psSampleState[ 0 ].RD_Q10, tmp1_s32x4 ); + vst1q_s32( psSampleState[ 1 ].RD_Q10, tmp2_s32x4 ); + t_u32x4 = vcltq_s32( rd1_Q10_s32x4, rd2_Q10_s32x4 ); + tmp1_s32x4 = vbslq_s32( t_u32x4, vmovl_s16( q1_Q10_s16x4 ), vmovl_s16( q2_Q10_s16x4 ) ); + tmp2_s32x4 = vbslq_s32( t_u32x4, vmovl_s16( q2_Q10_s16x4 ), vmovl_s16( q1_Q10_s16x4 ) ); + vst1q_s32( psSampleState[ 0 ].Q_Q10, tmp1_s32x4 ); + vst1q_s32( psSampleState[ 1 ].Q_Q10, tmp2_s32x4 ); + } + + { + /* Update states for best quantization */ + int32x4_t exc_Q14_s32x4, LPC_exc_Q14_s32x4, xq_Q14_s32x4, sLF_AR_shp_Q14_s32x4; + + /* Quantized excitation */ + exc_Q14_s32x4 = vshlq_n_s32( tmp1_s32x4, 4 ); + exc_Q14_s32x4 = veorq_s32( exc_Q14_s32x4, sign_s32x4 ); + exc_Q14_s32x4 = vsubq_s32( exc_Q14_s32x4, sign_s32x4 ); + + /* Add predictions */ + LPC_exc_Q14_s32x4 = vaddq_s32( exc_Q14_s32x4, vdupq_n_s32( LTP_pred_Q14 ) ); + xq_Q14_s32x4 = vaddq_s32( LPC_exc_Q14_s32x4, LPC_pred_Q14_s32x4 ); + + /* Update states */ + tmp1_s32x4 = vsubq_s32( xq_Q14_s32x4, vshlq_n_s32( vdupq_n_s32( x_Q10[ i ] ), 4 ) ); + vst1q_s32( psSampleState[ 0 ].Diff_Q14, tmp1_s32x4 ); + sLF_AR_shp_Q14_s32x4 = vsubq_s32( tmp1_s32x4, n_AR_Q14_s32x4 ); + vst1q_s32( psSampleState[ 0 ].sLTP_shp_Q14, vsubq_s32( sLF_AR_shp_Q14_s32x4, n_LF_Q14_s32x4 ) ); + vst1q_s32( psSampleState[ 0 ].LF_AR_Q14, sLF_AR_shp_Q14_s32x4 ); + vst1q_s32( psSampleState[ 0 ].LPC_exc_Q14, LPC_exc_Q14_s32x4 ); + vst1q_s32( psSampleState[ 0 ].xq_Q14, xq_Q14_s32x4 ); + + /* Quantized excitation */ + exc_Q14_s32x4 = vshlq_n_s32( tmp2_s32x4, 4 ); + exc_Q14_s32x4 = veorq_s32( exc_Q14_s32x4, sign_s32x4 ); + exc_Q14_s32x4 = vsubq_s32( exc_Q14_s32x4, sign_s32x4 ); + + /* Add predictions */ + LPC_exc_Q14_s32x4 = vaddq_s32( exc_Q14_s32x4, vdupq_n_s32( LTP_pred_Q14 ) ); + xq_Q14_s32x4 = vaddq_s32( LPC_exc_Q14_s32x4, LPC_pred_Q14_s32x4 ); + + /* Update states */ + tmp1_s32x4 = vsubq_s32( xq_Q14_s32x4, vshlq_n_s32( vdupq_n_s32( x_Q10[ i ] ), 4 ) ); + vst1q_s32( psSampleState[ 1 ].Diff_Q14, tmp1_s32x4 ); + sLF_AR_shp_Q14_s32x4 = vsubq_s32( tmp1_s32x4, n_AR_Q14_s32x4 ); + vst1q_s32( psSampleState[ 1 ].sLTP_shp_Q14, vsubq_s32( sLF_AR_shp_Q14_s32x4, n_LF_Q14_s32x4 ) ); + vst1q_s32( psSampleState[ 1 ].LF_AR_Q14, sLF_AR_shp_Q14_s32x4 ); + vst1q_s32( psSampleState[ 1 ].LPC_exc_Q14, LPC_exc_Q14_s32x4 ); + vst1q_s32( psSampleState[ 1 ].xq_Q14, xq_Q14_s32x4 ); + } + + *smpl_buf_idx = *smpl_buf_idx ? ( *smpl_buf_idx - 1 ) : ( DECISION_DELAY - 1); + last_smple_idx = *smpl_buf_idx + decisionDelay + DECISION_DELAY; + if( last_smple_idx >= DECISION_DELAY ) last_smple_idx -= DECISION_DELAY; + if( last_smple_idx >= DECISION_DELAY ) last_smple_idx -= DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ].RD_Q10[ 0 ]; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ 0 ].RD_Q10[ k ] < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ 0 ].RD_Q10[ k ]; + Winner_ind = k; + } + } + + /* clear unused part of RD_Q10 to avoid overflows */ + if( nStatesDelayedDecision < NEON_MAX_DEL_DEC_STATES ) + { + OPUS_CLEAR(psSampleState[0].RD_Q10 + nStatesDelayedDecision, NEON_MAX_DEL_DEC_STATES - nStatesDelayedDecision); + OPUS_CLEAR(psSampleState[1].RD_Q10 + nStatesDelayedDecision, NEON_MAX_DEL_DEC_STATES - nStatesDelayedDecision); + } + + /* Increase RD values of expired states */ + { + uint32x4_t t_u32x4; + Winner_rand_state = psDelDec->RandState[ last_smple_idx ][ Winner_ind ]; + t_u32x4 = vceqq_s32( vld1q_s32( psDelDec->RandState[ last_smple_idx ] ), vdupq_n_s32( Winner_rand_state ) ); + t_u32x4 = vmvnq_u32( t_u32x4 ); + t_u32x4 = vshrq_n_u32( t_u32x4, 5 ); + tmp1_s32x4 = vld1q_s32( psSampleState[ 0 ].RD_Q10 ); + tmp2_s32x4 = vld1q_s32( psSampleState[ 1 ].RD_Q10 ); + tmp1_s32x4 = vaddq_s32( tmp1_s32x4, vreinterpretq_s32_u32( t_u32x4 ) ); + tmp2_s32x4 = vaddq_s32( tmp2_s32x4, vreinterpretq_s32_u32( t_u32x4 ) ); + vst1q_s32( psSampleState[ 0 ].RD_Q10, tmp1_s32x4 ); + vst1q_s32( psSampleState[ 1 ].RD_Q10, tmp2_s32x4 ); + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ].RD_Q10[ 0 ]; + RDmin_Q10 = psSampleState[ 1 ].RD_Q10[ 0 ]; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ 0 ].RD_Q10[ k ] > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ 0 ].RD_Q10[ k ]; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ 1 ].RD_Q10[ k ] < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ 1 ].RD_Q10[ k ]; + RDmin_ind = k; + } + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + opus_int32 (*ptr)[NEON_MAX_DEL_DEC_STATES] = psDelDec->RandState; + const int numOthers = (int)( ( sizeof( NSQ_del_decs_struct ) - sizeof( ( (NSQ_del_decs_struct *)0 )->sLPC_Q14 ) ) + / ( NEON_MAX_DEL_DEC_STATES * sizeof( opus_int32 ) ) ); + /* Only ( predictLPCOrder - 1 ) of sLPC_Q14 buffer need to be updated, though the first several */ + /* useless sLPC_Q14[] will be different comparing with C when predictLPCOrder < NSQ_LPC_BUF_LENGTH. */ + /* Here just update constant ( NSQ_LPC_BUF_LENGTH - 1 ) for simplicity. */ + for( j = i + 1; j < i + NSQ_LPC_BUF_LENGTH; j++ ) { + psDelDec->sLPC_Q14[ j ][ RDmax_ind ] = psDelDec->sLPC_Q14[ j ][ RDmin_ind ]; + } + for( j = 0; j < numOthers; j++ ) { + ptr[ j ][ RDmax_ind ] = ptr[ j ][ RDmin_ind ]; + } + + psSampleState[ 0 ].Q_Q10[ RDmax_ind ] = psSampleState[ 1 ].Q_Q10[ RDmin_ind ]; + psSampleState[ 0 ].RD_Q10[ RDmax_ind ] = psSampleState[ 1 ].RD_Q10[ RDmin_ind ]; + psSampleState[ 0 ].xq_Q14[ RDmax_ind ] = psSampleState[ 1 ].xq_Q14[ RDmin_ind ]; + psSampleState[ 0 ].LF_AR_Q14[ RDmax_ind ] = psSampleState[ 1 ].LF_AR_Q14[ RDmin_ind ]; + psSampleState[ 0 ].Diff_Q14[ RDmax_ind ] = psSampleState[ 1 ].Diff_Q14[ RDmin_ind ]; + psSampleState[ 0 ].sLTP_shp_Q14[ RDmax_ind ] = psSampleState[ 1 ].sLTP_shp_Q14[ RDmin_ind ]; + psSampleState[ 0 ].LPC_exc_Q14[ RDmax_ind ] = psSampleState[ 1 ].LPC_exc_Q14[ RDmin_ind ]; + } + + /* Write samples from winner to output and long-term filter states */ + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDelDec->Q_Q10[ last_smple_idx ][ Winner_ind ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDelDec->Xq_Q14[ last_smple_idx ][ Winner_ind ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDelDec->Shape_Q14[ last_smple_idx ][ Winner_ind ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDelDec->Pred_Q15[ last_smple_idx ][ Winner_ind ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + vst1q_s32( psDelDec->LF_AR_Q14, vld1q_s32( psSampleState[ 0 ].LF_AR_Q14 ) ); + vst1q_s32( psDelDec->Diff_Q14, vld1q_s32( psSampleState[ 0 ].Diff_Q14 ) ); + vst1q_s32( psDelDec->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ], vld1q_s32( psSampleState[ 0 ].xq_Q14 ) ); + vst1q_s32( psDelDec->Xq_Q14[ *smpl_buf_idx ], vld1q_s32( psSampleState[ 0 ].xq_Q14 ) ); + tmp1_s32x4 = vld1q_s32( psSampleState[ 0 ].Q_Q10 ); + vst1q_s32( psDelDec->Q_Q10[ *smpl_buf_idx ], tmp1_s32x4 ); + vst1q_s32( psDelDec->Pred_Q15[ *smpl_buf_idx ], vshlq_n_s32( vld1q_s32( psSampleState[ 0 ].LPC_exc_Q14 ), 1 ) ); + vst1q_s32( psDelDec->Shape_Q14[ *smpl_buf_idx ], vld1q_s32( psSampleState[ 0 ].sLTP_shp_Q14 ) ); + tmp1_s32x4 = vrshrq_n_s32( tmp1_s32x4, 10 ); + tmp1_s32x4 = vreinterpretq_s32_u32( vaddq_u32( vreinterpretq_u32_s32( + vld1q_s32( psDelDec->Seed ) ), vreinterpretq_u32_s32( tmp1_s32x4 ) ) ); + vst1q_s32( psDelDec->Seed, tmp1_s32x4 ); + vst1q_s32( psDelDec->RandState[ *smpl_buf_idx ], tmp1_s32x4 ); + vst1q_s32( psDelDec->RD_Q10, vld1q_s32( psSampleState[ 0 ].RD_Q10 ) ); + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + silk_memcpy( psDelDec->sLPC_Q14[ 0 ], psDelDec->sLPC_Q14[ length ], NEON_MAX_DEL_DEC_STATES * NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + + RESTORE_STACK; +} + +static OPUS_INLINE void silk_SMULWB_8_neon( + const opus_int16 *a, + const int32x2_t b, + opus_int32 *o +) +{ + const int16x8_t a_s16x8 = vld1q_s16( a ); + int32x4_t o0_s32x4, o1_s32x4; + + o0_s32x4 = vshll_n_s16( vget_low_s16( a_s16x8 ), 15 ); + o1_s32x4 = vshll_n_s16( vget_high_s16( a_s16x8 ), 15 ); + o0_s32x4 = vqdmulhq_lane_s32( o0_s32x4, b, 0 ); + o1_s32x4 = vqdmulhq_lane_s32( o1_s32x4, b, 0 ); + vst1q_s32( o, o0_s32x4 ); + vst1q_s32( o + 4, o1_s32x4 ); +} + +/* Only works when ( b >= -65536 ) && ( b < 65536 ). */ +static OPUS_INLINE void silk_SMULWW_small_b_4_neon( + opus_int32 *a, + const int32x2_t b_s32x2) +{ + int32x4_t o_s32x4; + + o_s32x4 = vld1q_s32( a ); + o_s32x4 = vqdmulhq_lane_s32( o_s32x4, b_s32x2, 0 ); + vst1q_s32( a, o_s32x4 ); +} + +/* Only works when ( b >= -65536 ) && ( b < 65536 ). */ +static OPUS_INLINE void silk_SMULWW_small_b_8_neon( + opus_int32 *a, + const int32x2_t b_s32x2 +) +{ + int32x4_t o0_s32x4, o1_s32x4; + + o0_s32x4 = vld1q_s32( a ); + o1_s32x4 = vld1q_s32( a + 4 ); + o0_s32x4 = vqdmulhq_lane_s32( o0_s32x4, b_s32x2, 0 ); + o1_s32x4 = vqdmulhq_lane_s32( o1_s32x4, b_s32x2, 0 ); + vst1q_s32( a, o0_s32x4 ); + vst1q_s32( a + 4, o1_s32x4 ); +} + +static OPUS_INLINE void silk_SMULWW_4_neon( + opus_int32 *a, + const int32x2_t b_s32x2) +{ + int32x4_t a_s32x4, o_s32x4; + + a_s32x4 = vld1q_s32( a ); + o_s32x4 = vqdmulhq_lane_s32( a_s32x4, b_s32x2, 0 ); + o_s32x4 = vmlaq_lane_s32( o_s32x4, a_s32x4, b_s32x2, 1 ); + vst1q_s32( a, o_s32x4 ); +} + +static OPUS_INLINE void silk_SMULWW_8_neon( + opus_int32 *a, + const int32x2_t b_s32x2 +) +{ + int32x4_t a0_s32x4, a1_s32x4, o0_s32x4, o1_s32x4; + + a0_s32x4 = vld1q_s32( a ); + a1_s32x4 = vld1q_s32( a + 4 ); + o0_s32x4 = vqdmulhq_lane_s32( a0_s32x4, b_s32x2, 0 ); + o1_s32x4 = vqdmulhq_lane_s32( a1_s32x4, b_s32x2, 0 ); + o0_s32x4 = vmlaq_lane_s32( o0_s32x4, a0_s32x4, b_s32x2, 1 ); + o1_s32x4 = vmlaq_lane_s32( o1_s32x4, a1_s32x4, b_s32x2, 1 ); + vst1q_s32( a, o0_s32x4 ); + vst1q_s32( a + 4, o1_s32x4 ); +} + +static OPUS_INLINE void silk_SMULWW_loop_neon( + const opus_int16 *a, + const opus_int32 b, + opus_int32 *o, + const opus_int loop_num +) +{ + opus_int i; + int32x2_t b_s32x2; + + b_s32x2 = vdup_n_s32( b ); + for( i = 0; i < loop_num - 7; i += 8 ) { + silk_SMULWB_8_neon( a + i, b_s32x2, o + i ); + } + for( ; i < loop_num; i++ ) { + o[ i ] = silk_SMULWW( a[ i ], b ); + } +} + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_neon( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_decs_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + opus_int i, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + silk_SMULWW_loop_neon( x16, inv_gain_Q26, x_sc_Q10, psEncC->subfr_length ); + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + silk_SMULWW_loop_neon( sLTP + NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2, inv_gain_Q31, sLTP_Q15 + NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2, lag + LTP_ORDER / 2 ); + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + int32x2_t gain_adj_Q16_s32x2; + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + if( ( gain_adj_Q16 >= -65536 ) && ( gain_adj_Q16 < 65536 ) ) { + gain_adj_Q16_s32x2 = vdup_n_s32( silk_LSHIFT32( gain_adj_Q16, 15 ) ); + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 7; i += 8 ) { + silk_SMULWW_small_b_8_neon( NSQ->sLTP_shp_Q14 + i, gain_adj_Q16_s32x2 ); + } + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay - 7; i += 8 ) { + silk_SMULWW_small_b_8_neon( sLTP_Q15 + i, gain_adj_Q16_s32x2 ); + } + for( ; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + /* Scale scalar states */ + silk_SMULWW_small_b_4_neon( psDelDec->LF_AR_Q14, gain_adj_Q16_s32x2 ); + silk_SMULWW_small_b_4_neon( psDelDec->Diff_Q14, gain_adj_Q16_s32x2 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + silk_SMULWW_small_b_4_neon( psDelDec->sLPC_Q14[ i ], gain_adj_Q16_s32x2 ); + } + + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + silk_SMULWW_small_b_4_neon( psDelDec->sAR2_Q14[ i ], gain_adj_Q16_s32x2 ); + } + + for( i = 0; i < DECISION_DELAY; i++ ) { + silk_SMULWW_small_b_4_neon( psDelDec->Pred_Q15[ i ], gain_adj_Q16_s32x2 ); + silk_SMULWW_small_b_4_neon( psDelDec->Shape_Q14[ i ], gain_adj_Q16_s32x2 ); + } + } else { + gain_adj_Q16_s32x2 = vdup_n_s32( silk_LSHIFT32( gain_adj_Q16 & 0x0000FFFF, 15 ) ); + gain_adj_Q16_s32x2 = vset_lane_s32( gain_adj_Q16 >> 16, gain_adj_Q16_s32x2, 1 ); + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 7; i += 8 ) { + silk_SMULWW_8_neon( NSQ->sLTP_shp_Q14 + i, gain_adj_Q16_s32x2 ); + } + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay - 7; i += 8 ) { + silk_SMULWW_8_neon( sLTP_Q15 + i, gain_adj_Q16_s32x2 ); + } + for( ; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + /* Scale scalar states */ + silk_SMULWW_4_neon( psDelDec->LF_AR_Q14, gain_adj_Q16_s32x2 ); + silk_SMULWW_4_neon( psDelDec->Diff_Q14, gain_adj_Q16_s32x2 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + silk_SMULWW_4_neon( psDelDec->sLPC_Q14[ i ], gain_adj_Q16_s32x2 ); + } + + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + silk_SMULWW_4_neon( psDelDec->sAR2_Q14[ i ], gain_adj_Q16_s32x2 ); + } + + for( i = 0; i < DECISION_DELAY; i++ ) { + silk_SMULWW_4_neon( psDelDec->Pred_Q15[ i ], gain_adj_Q16_s32x2 ); + silk_SMULWW_4_neon( psDelDec->Shape_Q14[ i ], gain_adj_Q16_s32x2 ); + } + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.c new file mode 100644 index 0000000000..9642529973 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.c @@ -0,0 +1,112 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" +#include "celt/cpu_support.h" +#include "celt/arm/armcpu.h" + +opus_int32 silk_noise_shape_quantizer_short_prediction_neon(const opus_int32 *buf32, const opus_int32 *coef32, opus_int order) +{ + int32x4_t coef0 = vld1q_s32(coef32); + int32x4_t coef1 = vld1q_s32(coef32 + 4); + int32x4_t coef2 = vld1q_s32(coef32 + 8); + int32x4_t coef3 = vld1q_s32(coef32 + 12); + + int32x4_t a0 = vld1q_s32(buf32 - 15); + int32x4_t a1 = vld1q_s32(buf32 - 11); + int32x4_t a2 = vld1q_s32(buf32 - 7); + int32x4_t a3 = vld1q_s32(buf32 - 3); + + int32x4_t b0 = vqdmulhq_s32(coef0, a0); + int32x4_t b1 = vqdmulhq_s32(coef1, a1); + int32x4_t b2 = vqdmulhq_s32(coef2, a2); + int32x4_t b3 = vqdmulhq_s32(coef3, a3); + + int32x4_t c0 = vaddq_s32(b0, b1); + int32x4_t c1 = vaddq_s32(b2, b3); + + int32x4_t d = vaddq_s32(c0, c1); + + int64x2_t e = vpaddlq_s32(d); + + int64x1_t f = vadd_s64(vget_low_s64(e), vget_high_s64(e)); + + opus_int32 out = vget_lane_s32(vreinterpret_s32_s64(f), 0); + + out += silk_RSHIFT( order, 1 ); + + return out; +} + + +opus_int32 silk_NSQ_noise_shape_feedback_loop_neon(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order) +{ + opus_int32 out; + if (order == 8) + { + int32x4_t a00 = vdupq_n_s32(data0[0]); + int32x4_t a01 = vld1q_s32(data1); /* data1[0] ... [3] */ + + int32x4_t a0 = vextq_s32 (a00, a01, 3); /* data0[0] data1[0] ...[2] */ + int32x4_t a1 = vld1q_s32(data1 + 3); /* data1[3] ... [6] */ + + /*TODO: Convert these once in advance instead of once per sample, like + silk_noise_shape_quantizer_short_prediction_neon() does.*/ + int16x8_t coef16 = vld1q_s16(coef); + int32x4_t coef0 = vmovl_s16(vget_low_s16(coef16)); + int32x4_t coef1 = vmovl_s16(vget_high_s16(coef16)); + + /*This is not bit-exact with the C version, since we do not drop the + lower 16 bits of each multiply, but wait until the end to truncate + precision. This is an encoder-specific calculation (and unlike + silk_noise_shape_quantizer_short_prediction_neon(), is not meant to + simulate what the decoder will do). We still could use vqdmulhq_s32() + like silk_noise_shape_quantizer_short_prediction_neon() and save + half the multiplies, but the speed difference is not large, since we + then need two extra adds.*/ + int64x2_t b0 = vmull_s32(vget_low_s32(a0), vget_low_s32(coef0)); + int64x2_t b1 = vmlal_s32(b0, vget_high_s32(a0), vget_high_s32(coef0)); + int64x2_t b2 = vmlal_s32(b1, vget_low_s32(a1), vget_low_s32(coef1)); + int64x2_t b3 = vmlal_s32(b2, vget_high_s32(a1), vget_high_s32(coef1)); + + int64x1_t c = vadd_s64(vget_low_s64(b3), vget_high_s64(b3)); + int64x1_t cS = vrshr_n_s64(c, 15); + int32x2_t d = vreinterpret_s32_s64(cS); + + out = vget_lane_s32(d, 0); + vst1q_s32(data1, a0); + vst1q_s32(data1 + 4, a1); + return out; + } + return silk_NSQ_noise_shape_feedback_loop_c(data0, data1, coef, order); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.h new file mode 100644 index 0000000000..f03d8ddd98 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/NSQ_neon.h @@ -0,0 +1,114 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifndef SILK_NSQ_NEON_H +#define SILK_NSQ_NEON_H + +#include "cpu_support.h" +#include "SigProc_FIX.h" + +#undef silk_short_prediction_create_arch_coef +/* For vectorized calc, reverse a_Q12 coefs, convert to 32-bit, and shift for vqdmulhq_s32. */ +static OPUS_INLINE void silk_short_prediction_create_arch_coef_neon(opus_int32 *out, const opus_int16 *in, opus_int order) +{ + out[15] = silk_LSHIFT32(in[0], 15); + out[14] = silk_LSHIFT32(in[1], 15); + out[13] = silk_LSHIFT32(in[2], 15); + out[12] = silk_LSHIFT32(in[3], 15); + out[11] = silk_LSHIFT32(in[4], 15); + out[10] = silk_LSHIFT32(in[5], 15); + out[9] = silk_LSHIFT32(in[6], 15); + out[8] = silk_LSHIFT32(in[7], 15); + out[7] = silk_LSHIFT32(in[8], 15); + out[6] = silk_LSHIFT32(in[9], 15); + + if (order == 16) + { + out[5] = silk_LSHIFT32(in[10], 15); + out[4] = silk_LSHIFT32(in[11], 15); + out[3] = silk_LSHIFT32(in[12], 15); + out[2] = silk_LSHIFT32(in[13], 15); + out[1] = silk_LSHIFT32(in[14], 15); + out[0] = silk_LSHIFT32(in[15], 15); + } + else + { + out[5] = 0; + out[4] = 0; + out[3] = 0; + out[2] = 0; + out[1] = 0; + out[0] = 0; + } +} + +#if defined(OPUS_ARM_PRESUME_NEON_INTR) + +#define silk_short_prediction_create_arch_coef(out, in, order) \ + (silk_short_prediction_create_arch_coef_neon(out, in, order)) + +#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + +#define silk_short_prediction_create_arch_coef(out, in, order) \ + do { if (arch >= OPUS_ARCH_ARM_NEON) { silk_short_prediction_create_arch_coef_neon(out, in, order); } } while (0) + +#endif + +opus_int32 silk_noise_shape_quantizer_short_prediction_neon(const opus_int32 *buf32, const opus_int32 *coef32, opus_int order); + +opus_int32 silk_NSQ_noise_shape_feedback_loop_neon(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order); + +#if defined(OPUS_ARM_PRESUME_NEON_INTR) +#undef silk_noise_shape_quantizer_short_prediction +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) \ + ((void)arch,silk_noise_shape_quantizer_short_prediction_neon(in, coefRev, order)) + +#undef silk_NSQ_noise_shape_feedback_loop +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) ((void)arch,silk_NSQ_noise_shape_feedback_loop_neon(data0, data1, coef, order)) + +#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + +/* silk_noise_shape_quantizer_short_prediction implementations take different parameters based on arch + (coef vs. coefRev) so can't use the usual IMPL table implementation */ +#undef silk_noise_shape_quantizer_short_prediction +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) \ + (arch >= OPUS_ARCH_ARM_NEON ? \ + silk_noise_shape_quantizer_short_prediction_neon(in, coefRev, order) : \ + silk_noise_shape_quantizer_short_prediction_c(in, coef, order)) + +extern opus_int32 + (*const SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[OPUS_ARCHMASK+1])( + const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, + opus_int order); + +#undef silk_NSQ_noise_shape_feedback_loop +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) \ + (SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[(arch)&OPUS_ARCHMASK](data0, data1, \ + coef, order)) + +#endif + +#endif /* SILK_NSQ_NEON_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv4.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv4.h new file mode 100644 index 0000000000..ff62b1e5d6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv4.h @@ -0,0 +1,47 @@ +/*********************************************************************** +Copyright (C) 2013 Xiph.Org Foundation and contributors +Copyright (c) 2013 Parrot +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FIX_ARMv4_H +#define SILK_SIGPROC_FIX_ARMv4_H + +#undef silk_MLA +static OPUS_INLINE opus_int32 silk_MLA_armv4(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + opus_int32 res; + __asm__( + "#silk_MLA\n\t" + "mla %0, %1, %2, %3\n\t" + : "=&r"(res) + : "r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_MLA(a, b, c) (silk_MLA_armv4(a, b, c)) + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv5e.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv5e.h new file mode 100644 index 0000000000..617a09cab1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/SigProc_FIX_armv5e.h @@ -0,0 +1,61 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Copyright (c) 2013 Parrot +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FIX_ARMv5E_H +#define SILK_SIGPROC_FIX_ARMv5E_H + +#undef silk_SMULTT +static OPUS_INLINE opus_int32 silk_SMULTT_armv5e(opus_int32 a, opus_int32 b) +{ + opus_int32 res; + __asm__( + "#silk_SMULTT\n\t" + "smultt %0, %1, %2\n\t" + : "=r"(res) + : "%r"(a), "r"(b) + ); + return res; +} +#define silk_SMULTT(a, b) (silk_SMULTT_armv5e(a, b)) + +#undef silk_SMLATT +static OPUS_INLINE opus_int32 silk_SMLATT_armv5e(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + opus_int32 res; + __asm__( + "#silk_SMLATT\n\t" + "smlatt %0, %1, %2, %3\n\t" + : "=r"(res) + : "%r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_SMLATT(a, b, c) (silk_SMLATT_armv5e(a, b, c)) + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/arm_silk_map.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/arm_silk_map.c new file mode 100644 index 0000000000..a91f79b59f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/arm_silk_map.c @@ -0,0 +1,128 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "main_FIX.h" +#include "NSQ.h" +#include "SigProc_FIX.h" + +#if defined(OPUS_HAVE_RTCD) + +# if (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && \ + !defined(OPUS_ARM_PRESUME_NEON_INTR)) + +void (*const SILK_BIQUAD_ALT_STRIDE2_IMPL[OPUS_ARCHMASK + 1])( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) = { + silk_biquad_alt_stride2_c, /* ARMv4 */ + silk_biquad_alt_stride2_c, /* EDSP */ + silk_biquad_alt_stride2_c, /* Media */ + silk_biquad_alt_stride2_neon, /* Neon */ + silk_biquad_alt_stride2_neon, /* dotprod */ +}; + +opus_int32 (*const SILK_LPC_INVERSE_PRED_GAIN_IMPL[OPUS_ARCHMASK + 1])( /* O Returns inverse prediction gain in energy domain, Q30 */ + const opus_int16 *A_Q12, /* I Prediction coefficients, Q12 [order] */ + const opus_int order /* I Prediction order */ +) = { + silk_LPC_inverse_pred_gain_c, /* ARMv4 */ + silk_LPC_inverse_pred_gain_c, /* EDSP */ + silk_LPC_inverse_pred_gain_c, /* Media */ + silk_LPC_inverse_pred_gain_neon, /* Neon */ + silk_LPC_inverse_pred_gain_neon, /* dotprod */ +}; + +void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) = { + silk_NSQ_del_dec_c, /* ARMv4 */ + silk_NSQ_del_dec_c, /* EDSP */ + silk_NSQ_del_dec_c, /* Media */ + silk_NSQ_del_dec_neon, /* Neon */ + silk_NSQ_del_dec_neon, /* dotprod */ +}; + +/*There is no table for silk_noise_shape_quantizer_short_prediction because the + NEON version takes different parameters than the C version. + Instead RTCD is done via if statements at the call sites. + See NSQ_neon.h for details.*/ + +opus_int32 + (*const SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[OPUS_ARCHMASK+1])( + const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, + opus_int order) = { + silk_NSQ_noise_shape_feedback_loop_c, /* ARMv4 */ + silk_NSQ_noise_shape_feedback_loop_c, /* EDSP */ + silk_NSQ_noise_shape_feedback_loop_c, /* Media */ + silk_NSQ_noise_shape_feedback_loop_neon, /* NEON */ + silk_NSQ_noise_shape_feedback_loop_neon, /* dotprod */ +}; + +# endif + +# if defined(FIXED_POINT) && \ + defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) + +void (*const SILK_WARPED_AUTOCORRELATION_FIX_IMPL[OPUS_ARCHMASK + 1])( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) = { + silk_warped_autocorrelation_FIX_c, /* ARMv4 */ + silk_warped_autocorrelation_FIX_c, /* EDSP */ + silk_warped_autocorrelation_FIX_c, /* Media */ + silk_warped_autocorrelation_FIX_neon, /* Neon */ + silk_warped_autocorrelation_FIX_neon, /* dotprod */ +}; + +# endif + +#endif /* OPUS_HAVE_RTCD */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_arm.h new file mode 100644 index 0000000000..66ea9f43dd --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_arm.h @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_BIQUAD_ALT_ARM_H +# define SILK_BIQUAD_ALT_ARM_H + +# include "celt/arm/armcpu.h" + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void silk_biquad_alt_stride2_neon( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +); + +# if !defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_PRESUME_NEON) +# define OVERRIDE_silk_biquad_alt_stride2 (1) +# define silk_biquad_alt_stride2(in, B_Q28, A_Q28, S, out, len, arch) ((void)(arch), PRESUME_NEON(silk_biquad_alt_stride2)(in, B_Q28, A_Q28, S, out, len)) +# endif +# endif + +# if !defined(OVERRIDE_silk_biquad_alt_stride2) +/*Is run-time CPU detection enabled on this platform?*/ +# if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void (*const SILK_BIQUAD_ALT_STRIDE2_IMPL[OPUS_ARCHMASK+1])( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ + ); +# define OVERRIDE_silk_biquad_alt_stride2 (1) +# define silk_biquad_alt_stride2(in, B_Q28, A_Q28, S, out, len, arch) ((*SILK_BIQUAD_ALT_STRIDE2_IMPL[(arch)&OPUS_ARCHMASK])(in, B_Q28, A_Q28, S, out, len)) +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_silk_biquad_alt_stride2 (1) +# define silk_biquad_alt_stride2(in, B_Q28, A_Q28, S, out, len, arch) ((void)(arch), silk_biquad_alt_stride2_neon(in, B_Q28, A_Q28, S, out, len)) +# endif +# endif + +#endif /* end SILK_BIQUAD_ALT_ARM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_neon_intr.c new file mode 100644 index 0000000000..9715733185 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/biquad_alt_neon_intr.c @@ -0,0 +1,156 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef OPUS_CHECK_ASM +# include +# include "stack_alloc.h" +#endif +#include "SigProc_FIX.h" + +static inline void silk_biquad_alt_stride2_kernel( const int32x4_t A_L_s32x4, const int32x4_t A_U_s32x4, const int32x4_t B_Q28_s32x4, const int32x2_t t_s32x2, const int32x4_t in_s32x4, int32x4_t *S_s32x4, int32x2_t *out32_Q14_s32x2 ) +{ + int32x4_t t_s32x4, out32_Q14_s32x4; + + *out32_Q14_s32x2 = vadd_s32( vget_low_s32( *S_s32x4 ), t_s32x2 ); /* silk_SMLAWB( S{0,1}, B_Q28[ 0 ], in{0,1} ) */ + *S_s32x4 = vcombine_s32( vget_high_s32( *S_s32x4 ), vdup_n_s32( 0 ) ); /* S{0,1} = S{2,3}; S{2,3} = 0; */ + *out32_Q14_s32x2 = vshl_n_s32( *out32_Q14_s32x2, 2 ); /* out32_Q14_{0,1} = silk_LSHIFT( silk_SMLAWB( S{0,1}, B_Q28[ 0 ], in{0,1} ), 2 ); */ + out32_Q14_s32x4 = vcombine_s32( *out32_Q14_s32x2, *out32_Q14_s32x2 ); /* out32_Q14_{0,1,0,1} */ + t_s32x4 = vqdmulhq_s32( out32_Q14_s32x4, A_L_s32x4 ); /* silk_SMULWB( out32_Q14_{0,1,0,1}, A{0,0,1,1}_L_Q28 ) */ + *S_s32x4 = vrsraq_n_s32( *S_s32x4, t_s32x4, 14 ); /* S{0,1} = S{2,3} + silk_RSHIFT_ROUND(); S{2,3} = silk_RSHIFT_ROUND(); */ + t_s32x4 = vqdmulhq_s32( out32_Q14_s32x4, A_U_s32x4 ); /* silk_SMULWB( out32_Q14_{0,1,0,1}, A{0,0,1,1}_U_Q28 ) */ + *S_s32x4 = vaddq_s32( *S_s32x4, t_s32x4 ); /* S0 = silk_SMLAWB( S{0,1,2,3}, out32_Q14_{0,1,0,1}, A{0,0,1,1}_U_Q28 ); */ + t_s32x4 = vqdmulhq_s32( in_s32x4, B_Q28_s32x4 ); /* silk_SMULWB( B_Q28[ {1,1,2,2} ], in{0,1,0,1} ) */ + *S_s32x4 = vaddq_s32( *S_s32x4, t_s32x4 ); /* S0 = silk_SMLAWB( S0, B_Q28[ {1,1,2,2} ], in{0,1,0,1} ); */ +} + +void silk_biquad_alt_stride2_neon( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k = 0; + const int32x2_t offset_s32x2 = vdup_n_s32( (1<<14) - 1 ); + const int32x4_t offset_s32x4 = vcombine_s32( offset_s32x2, offset_s32x2 ); + int16x4_t in_s16x4 = vdup_n_s16( 0 ); + int16x4_t out_s16x4; + int32x2_t A_Q28_s32x2, A_L_s32x2, A_U_s32x2, B_Q28_s32x2, t_s32x2; + int32x4_t A_L_s32x4, A_U_s32x4, B_Q28_s32x4, S_s32x4, out32_Q14_s32x4; + int32x2x2_t t0_s32x2x2, t1_s32x2x2, t2_s32x2x2, S_s32x2x2; + +#ifdef OPUS_CHECK_ASM + opus_int32 S_c[ 4 ]; + VARDECL( opus_int16, out_c ); + SAVE_STACK; + ALLOC( out_c, 2 * len, opus_int16 ); + + silk_memcpy( &S_c, S, sizeof( S_c ) ); + silk_biquad_alt_stride2_c( in, B_Q28, A_Q28, S_c, out_c, len ); +#endif + + /* Negate A_Q28 values and split in two parts */ + A_Q28_s32x2 = vld1_s32( A_Q28 ); + A_Q28_s32x2 = vneg_s32( A_Q28_s32x2 ); + A_L_s32x2 = vshl_n_s32( A_Q28_s32x2, 18 ); /* ( -A_Q28[] & 0x00003FFF ) << 18 */ + A_L_s32x2 = vreinterpret_s32_u32( vshr_n_u32( vreinterpret_u32_s32( A_L_s32x2 ), 3 ) ); /* ( -A_Q28[] & 0x00003FFF ) << 15 */ + A_U_s32x2 = vshr_n_s32( A_Q28_s32x2, 14 ); /* silk_RSHIFT( -A_Q28[], 14 ) */ + A_U_s32x2 = vshl_n_s32( A_U_s32x2, 16 ); /* silk_RSHIFT( -A_Q28[], 14 ) << 16 (Clip two leading bits to conform to C function.) */ + A_U_s32x2 = vshr_n_s32( A_U_s32x2, 1 ); /* silk_RSHIFT( -A_Q28[], 14 ) << 15 */ + + B_Q28_s32x2 = vld1_s32( B_Q28 ); + t_s32x2 = vld1_s32( B_Q28 + 1 ); + t0_s32x2x2 = vzip_s32( A_L_s32x2, A_L_s32x2 ); + t1_s32x2x2 = vzip_s32( A_U_s32x2, A_U_s32x2 ); + t2_s32x2x2 = vzip_s32( t_s32x2, t_s32x2 ); + A_L_s32x4 = vcombine_s32( t0_s32x2x2.val[ 0 ], t0_s32x2x2.val[ 1 ] ); /* A{0,0,1,1}_L_Q28 */ + A_U_s32x4 = vcombine_s32( t1_s32x2x2.val[ 0 ], t1_s32x2x2.val[ 1 ] ); /* A{0,0,1,1}_U_Q28 */ + B_Q28_s32x4 = vcombine_s32( t2_s32x2x2.val[ 0 ], t2_s32x2x2.val[ 1 ] ); /* B_Q28[ {1,1,2,2} ] */ + S_s32x4 = vld1q_s32( S ); /* S0 = S[ 0 ]; S3 = S[ 3 ]; */ + S_s32x2x2 = vtrn_s32( vget_low_s32( S_s32x4 ), vget_high_s32( S_s32x4 ) ); /* S2 = S[ 1 ]; S1 = S[ 2 ]; */ + S_s32x4 = vcombine_s32( S_s32x2x2.val[ 0 ], S_s32x2x2.val[ 1 ] ); + + for( ; k < len - 1; k += 2 ) { + int32x4_t in_s32x4[ 2 ], t_s32x4; + int32x2_t out32_Q14_s32x2[ 2 ]; + + /* S[ 2 * i + 0 ], S[ 2 * i + 1 ], S[ 2 * i + 2 ], S[ 2 * i + 3 ]: Q12 */ + in_s16x4 = vld1_s16( &in[ 2 * k ] ); /* in{0,1,2,3} = in[ 2 * k + {0,1,2,3} ]; */ + in_s32x4[ 0 ] = vshll_n_s16( in_s16x4, 15 ); /* in{0,1,2,3} << 15 */ + t_s32x4 = vqdmulhq_lane_s32( in_s32x4[ 0 ], B_Q28_s32x2, 0 ); /* silk_SMULWB( B_Q28[ 0 ], in{0,1,2,3} ) */ + in_s32x4[ 1 ] = vcombine_s32( vget_high_s32( in_s32x4[ 0 ] ), vget_high_s32( in_s32x4[ 0 ] ) ); /* in{2,3,2,3} << 15 */ + in_s32x4[ 0 ] = vcombine_s32( vget_low_s32 ( in_s32x4[ 0 ] ), vget_low_s32 ( in_s32x4[ 0 ] ) ); /* in{0,1,0,1} << 15 */ + silk_biquad_alt_stride2_kernel( A_L_s32x4, A_U_s32x4, B_Q28_s32x4, vget_low_s32 ( t_s32x4 ), in_s32x4[ 0 ], &S_s32x4, &out32_Q14_s32x2[ 0 ] ); + silk_biquad_alt_stride2_kernel( A_L_s32x4, A_U_s32x4, B_Q28_s32x4, vget_high_s32( t_s32x4 ), in_s32x4[ 1 ], &S_s32x4, &out32_Q14_s32x2[ 1 ] ); + + /* Scale back to Q0 and saturate */ + out32_Q14_s32x4 = vcombine_s32( out32_Q14_s32x2[ 0 ], out32_Q14_s32x2[ 1 ] ); /* out32_Q14_{0,1,2,3} */ + out32_Q14_s32x4 = vaddq_s32( out32_Q14_s32x4, offset_s32x4 ); /* out32_Q14_{0,1,2,3} + (1<<14) - 1 */ + out_s16x4 = vqshrn_n_s32( out32_Q14_s32x4, 14 ); /* (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14_{0,1,2,3} + (1<<14) - 1, 14 ) ) */ + vst1_s16( &out[ 2 * k ], out_s16x4 ); /* out[ 2 * k + {0,1,2,3} ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14_{0,1,2,3} + (1<<14) - 1, 14 ) ); */ + } + + /* Process leftover. */ + if( k < len ) { + int32x4_t in_s32x4; + int32x2_t out32_Q14_s32x2; + + /* S[ 2 * i + 0 ], S[ 2 * i + 1 ]: Q12 */ + in_s16x4 = vld1_lane_s16( &in[ 2 * k + 0 ], in_s16x4, 0 ); /* in{0,1} = in[ 2 * k + {0,1} ]; */ + in_s16x4 = vld1_lane_s16( &in[ 2 * k + 1 ], in_s16x4, 1 ); /* in{0,1} = in[ 2 * k + {0,1} ]; */ + in_s32x4 = vshll_n_s16( in_s16x4, 15 ); /* in{0,1} << 15 */ + t_s32x2 = vqdmulh_lane_s32( vget_low_s32( in_s32x4 ), B_Q28_s32x2, 0 ); /* silk_SMULWB( B_Q28[ 0 ], in{0,1} ) */ + in_s32x4 = vcombine_s32( vget_low_s32( in_s32x4 ), vget_low_s32( in_s32x4 ) ); /* in{0,1,0,1} << 15 */ + silk_biquad_alt_stride2_kernel( A_L_s32x4, A_U_s32x4, B_Q28_s32x4, t_s32x2, in_s32x4, &S_s32x4, &out32_Q14_s32x2 ); + + /* Scale back to Q0 and saturate */ + out32_Q14_s32x2 = vadd_s32( out32_Q14_s32x2, offset_s32x2 ); /* out32_Q14_{0,1} + (1<<14) - 1 */ + out32_Q14_s32x4 = vcombine_s32( out32_Q14_s32x2, out32_Q14_s32x2 ); /* out32_Q14_{0,1,0,1} + (1<<14) - 1 */ + out_s16x4 = vqshrn_n_s32( out32_Q14_s32x4, 14 ); /* (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14_{0,1,0,1} + (1<<14) - 1, 14 ) ) */ + vst1_lane_s16( &out[ 2 * k + 0 ], out_s16x4, 0 ); /* out[ 2 * k + 0 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14_0 + (1<<14) - 1, 14 ) ); */ + vst1_lane_s16( &out[ 2 * k + 1 ], out_s16x4, 1 ); /* out[ 2 * k + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14_1 + (1<<14) - 1, 14 ) ); */ + } + + vst1q_lane_s32( &S[ 0 ], S_s32x4, 0 ); /* S[ 0 ] = S0; */ + vst1q_lane_s32( &S[ 1 ], S_s32x4, 2 ); /* S[ 1 ] = S2; */ + vst1q_lane_s32( &S[ 2 ], S_s32x4, 1 ); /* S[ 2 ] = S1; */ + vst1q_lane_s32( &S[ 3 ], S_s32x4, 3 ); /* S[ 3 ] = S3; */ + +#ifdef OPUS_CHECK_ASM + silk_assert( !memcmp( S_c, S, sizeof( S_c ) ) ); + silk_assert( !memcmp( out_c, out, 2 * len * sizeof( opus_int16 ) ) ); + RESTORE_STACK; +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_arm64.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_arm64.h new file mode 100644 index 0000000000..ed030413c5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_arm64.h @@ -0,0 +1,39 @@ +/*********************************************************************** +Copyright (C) 2015 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MACROS_ARM64_H +#define SILK_MACROS_ARM64_H + +#include + +#undef silk_ADD_SAT32 +#define silk_ADD_SAT32(a, b) (vqadds_s32((a), (b))) + +#undef silk_SUB_SAT32 +#define silk_SUB_SAT32(a, b) (vqsubs_s32((a), (b))) + +#endif /* SILK_MACROS_ARM64_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv4.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv4.h new file mode 100644 index 0000000000..877eb18dd5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv4.h @@ -0,0 +1,110 @@ +/*********************************************************************** +Copyright (C) 2013 Xiph.Org Foundation and contributors. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MACROS_ARMv4_H +#define SILK_MACROS_ARMv4_H + +/* This macro only avoids the undefined behaviour from a left shift of + a negative value. It should only be used in macros that can't include + SigProc_FIX.h. In other cases, use silk_LSHIFT32(). */ +#define SAFE_SHL(a,b) ((opus_int32)((opus_uint32)(a) << (b))) + +/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +#undef silk_SMULWB +static OPUS_INLINE opus_int32 silk_SMULWB_armv4(opus_int32 a, opus_int16 b) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#silk_SMULWB\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(a), "r"(SAFE_SHL(b,16)) + ); + return rd_hi; +} +#define silk_SMULWB(a, b) (silk_SMULWB_armv4(a, b)) + +/* a32 + (b32 * (opus_int32)((opus_int16)(c32))) >> 16 output have to be 32bit int */ +#undef silk_SMLAWB +#define silk_SMLAWB(a, b, c) ((a) + silk_SMULWB(b, c)) + +/* (a32 * (b32 >> 16)) >> 16 */ +#undef silk_SMULWT +static OPUS_INLINE opus_int32 silk_SMULWT_armv4(opus_int32 a, opus_int32 b) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#silk_SMULWT\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(a), "r"(b&~0xFFFF) + ); + return rd_hi; +} +#define silk_SMULWT(a, b) (silk_SMULWT_armv4(a, b)) + +/* a32 + (b32 * (c32 >> 16)) >> 16 */ +#undef silk_SMLAWT +#define silk_SMLAWT(a, b, c) ((a) + silk_SMULWT(b, c)) + +/* (a32 * b32) >> 16 */ +#undef silk_SMULWW +static OPUS_INLINE opus_int32 silk_SMULWW_armv4(opus_int32 a, opus_int32 b) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#silk_SMULWW\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(a), "r"(b) + ); + return SAFE_SHL(rd_hi,16)+(rd_lo>>16); +} +#define silk_SMULWW(a, b) (silk_SMULWW_armv4(a, b)) + +#undef silk_SMLAWW +static OPUS_INLINE opus_int32 silk_SMLAWW_armv4(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + unsigned rd_lo; + int rd_hi; + __asm__( + "#silk_SMLAWW\n\t" + "smull %0, %1, %2, %3\n\t" + : "=&r"(rd_lo), "=&r"(rd_hi) + : "%r"(b), "r"(c) + ); + return a+SAFE_SHL(rd_hi,16)+(rd_lo>>16); +} +#define silk_SMLAWW(a, b, c) (silk_SMLAWW_armv4(a, b, c)) + +#undef SAFE_SHL + +#endif /* SILK_MACROS_ARMv4_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv5e.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv5e.h new file mode 100644 index 0000000000..b14ec65ddb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/arm/macros_armv5e.h @@ -0,0 +1,220 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Copyright (c) 2013 Parrot +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MACROS_ARMv5E_H +#define SILK_MACROS_ARMv5E_H + +/* This macro only avoids the undefined behaviour from a left shift of + a negative value. It should only be used in macros that can't include + SigProc_FIX.h. In other cases, use silk_LSHIFT32(). */ +#define SAFE_SHL(a,b) ((opus_int32)((opus_uint32)(a) << (b))) + +/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +#undef silk_SMULWB +static OPUS_INLINE opus_int32 silk_SMULWB_armv5e(opus_int32 a, opus_int16 b) +{ + int res; + __asm__( + "#silk_SMULWB\n\t" + "smulwb %0, %1, %2\n\t" + : "=r"(res) + : "r"(a), "r"(b) + ); + return res; +} +#define silk_SMULWB(a, b) (silk_SMULWB_armv5e(a, b)) + +/* a32 + (b32 * (opus_int32)((opus_int16)(c32))) >> 16 output have to be 32bit int */ +#undef silk_SMLAWB +static OPUS_INLINE opus_int32 silk_SMLAWB_armv5e(opus_int32 a, opus_int32 b, + opus_int16 c) +{ + int res; + __asm__( + "#silk_SMLAWB\n\t" + "smlawb %0, %1, %2, %3\n\t" + : "=r"(res) + : "r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_SMLAWB(a, b, c) (silk_SMLAWB_armv5e(a, b, c)) + +/* (a32 * (b32 >> 16)) >> 16 */ +#undef silk_SMULWT +static OPUS_INLINE opus_int32 silk_SMULWT_armv5e(opus_int32 a, opus_int32 b) +{ + int res; + __asm__( + "#silk_SMULWT\n\t" + "smulwt %0, %1, %2\n\t" + : "=r"(res) + : "r"(a), "r"(b) + ); + return res; +} +#define silk_SMULWT(a, b) (silk_SMULWT_armv5e(a, b)) + +/* a32 + (b32 * (c32 >> 16)) >> 16 */ +#undef silk_SMLAWT +static OPUS_INLINE opus_int32 silk_SMLAWT_armv5e(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + int res; + __asm__( + "#silk_SMLAWT\n\t" + "smlawt %0, %1, %2, %3\n\t" + : "=r"(res) + : "r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_SMLAWT(a, b, c) (silk_SMLAWT_armv5e(a, b, c)) + +/* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */ +#undef silk_SMULBB +static OPUS_INLINE opus_int32 silk_SMULBB_armv5e(opus_int32 a, opus_int32 b) +{ + int res; + __asm__( + "#silk_SMULBB\n\t" + "smulbb %0, %1, %2\n\t" + : "=r"(res) + : "%r"(a), "r"(b) + ); + return res; +} +#define silk_SMULBB(a, b) (silk_SMULBB_armv5e(a, b)) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (opus_int32)((opus_int16)(c32)) output have to be 32bit int */ +#undef silk_SMLABB +static OPUS_INLINE opus_int32 silk_SMLABB_armv5e(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + int res; + __asm__( + "#silk_SMLABB\n\t" + "smlabb %0, %1, %2, %3\n\t" + : "=r"(res) + : "%r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_SMLABB(a, b, c) (silk_SMLABB_armv5e(a, b, c)) + +/* (opus_int32)((opus_int16)(a32)) * (b32 >> 16) */ +#undef silk_SMULBT +static OPUS_INLINE opus_int32 silk_SMULBT_armv5e(opus_int32 a, opus_int32 b) +{ + int res; + __asm__( + "#silk_SMULBT\n\t" + "smulbt %0, %1, %2\n\t" + : "=r"(res) + : "r"(a), "r"(b) + ); + return res; +} +#define silk_SMULBT(a, b) (silk_SMULBT_armv5e(a, b)) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (c32 >> 16) */ +#undef silk_SMLABT +static OPUS_INLINE opus_int32 silk_SMLABT_armv5e(opus_int32 a, opus_int32 b, + opus_int32 c) +{ + int res; + __asm__( + "#silk_SMLABT\n\t" + "smlabt %0, %1, %2, %3\n\t" + : "=r"(res) + : "r"(b), "r"(c), "r"(a) + ); + return res; +} +#define silk_SMLABT(a, b, c) (silk_SMLABT_armv5e(a, b, c)) + +/* add/subtract with output saturated */ +#undef silk_ADD_SAT32 +static OPUS_INLINE opus_int32 silk_ADD_SAT32_armv5e(opus_int32 a, opus_int32 b) +{ + int res; + __asm__( + "#silk_ADD_SAT32\n\t" + "qadd %0, %1, %2\n\t" + : "=r"(res) + : "%r"(a), "r"(b) + ); + return res; +} +#define silk_ADD_SAT32(a, b) (silk_ADD_SAT32_armv5e(a, b)) + +#undef silk_SUB_SAT32 +static OPUS_INLINE opus_int32 silk_SUB_SAT32_armv5e(opus_int32 a, opus_int32 b) +{ + int res; + __asm__( + "#silk_SUB_SAT32\n\t" + "qsub %0, %1, %2\n\t" + : "=r"(res) + : "r"(a), "r"(b) + ); + return res; +} +#define silk_SUB_SAT32(a, b) (silk_SUB_SAT32_armv5e(a, b)) + +#undef silk_CLZ16 +static OPUS_INLINE opus_int32 silk_CLZ16_armv5(opus_int16 in16) +{ + int res; + __asm__( + "#silk_CLZ16\n\t" + "clz %0, %1;\n" + : "=r"(res) + : "r"(SAFE_SHL(in16,16)|0x8000) + ); + return res; +} +#define silk_CLZ16(in16) (silk_CLZ16_armv5(in16)) + +#undef silk_CLZ32 +static OPUS_INLINE opus_int32 silk_CLZ32_armv5(opus_int32 in32) +{ + int res; + __asm__( + "#silk_CLZ32\n\t" + "clz %0, %1\n\t" + : "=r"(res) + : "r"(in32) + ); + return res; +} +#define silk_CLZ32(in32) (silk_CLZ32_armv5(in32)) + +#undef SAFE_SHL + +#endif /* SILK_MACROS_ARMv5E_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/biquad_alt.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/biquad_alt.c new file mode 100644 index 0000000000..54566a43c0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/biquad_alt.c @@ -0,0 +1,121 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +/* * + * silk_biquad_alt.c * + * * + * Second order ARMA filter * + * Can handle slowly varying filter coefficients * + * */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Second order ARMA filter, alternative implementation */ +void silk_biquad_alt_stride1( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [2] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_int32 inval, A0_U_Q28, A0_L_Q28, A1_U_Q28, A1_L_Q28, out32_Q14; + + /* Negate A_Q28 values and split in two parts */ + A0_L_Q28 = ( -A_Q28[ 0 ] ) & 0x00003FFF; /* lower part */ + A0_U_Q28 = silk_RSHIFT( -A_Q28[ 0 ], 14 ); /* upper part */ + A1_L_Q28 = ( -A_Q28[ 1 ] ) & 0x00003FFF; /* lower part */ + A1_U_Q28 = silk_RSHIFT( -A_Q28[ 1 ], 14 ); /* upper part */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ]: Q12 */ + inval = in[ k ]; + out32_Q14 = silk_LSHIFT( silk_SMLAWB( S[ 0 ], B_Q28[ 0 ], inval ), 2 ); + + S[ 0 ] = S[1] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14, A0_L_Q28 ), 14 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], out32_Q14, A0_U_Q28 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], B_Q28[ 1 ], inval); + + S[ 1 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14, A1_L_Q28 ), 14 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], out32_Q14, A1_U_Q28 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], B_Q28[ 2 ], inval ); + + /* Scale back to Q0 and saturate */ + out[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14 + (1<<14) - 1, 14 ) ); + } +} + +void silk_biquad_alt_stride2_c( + const opus_int16 *in, /* I input signal */ + const opus_int32 *B_Q28, /* I MA coefficients [3] */ + const opus_int32 *A_Q28, /* I AR coefficients [2] */ + opus_int32 *S, /* I/O State vector [4] */ + opus_int16 *out, /* O output signal */ + const opus_int32 len /* I signal length (must be even) */ +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_int32 A0_U_Q28, A0_L_Q28, A1_U_Q28, A1_L_Q28, out32_Q14[ 2 ]; + + /* Negate A_Q28 values and split in two parts */ + A0_L_Q28 = ( -A_Q28[ 0 ] ) & 0x00003FFF; /* lower part */ + A0_U_Q28 = silk_RSHIFT( -A_Q28[ 0 ], 14 ); /* upper part */ + A1_L_Q28 = ( -A_Q28[ 1 ] ) & 0x00003FFF; /* lower part */ + A1_U_Q28 = silk_RSHIFT( -A_Q28[ 1 ], 14 ); /* upper part */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ], S[ 2 ], S[ 3 ]: Q12 */ + out32_Q14[ 0 ] = silk_LSHIFT( silk_SMLAWB( S[ 0 ], B_Q28[ 0 ], in[ 2 * k + 0 ] ), 2 ); + out32_Q14[ 1 ] = silk_LSHIFT( silk_SMLAWB( S[ 2 ], B_Q28[ 0 ], in[ 2 * k + 1 ] ), 2 ); + + S[ 0 ] = S[ 1 ] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 0 ], A0_L_Q28 ), 14 ); + S[ 2 ] = S[ 3 ] + silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 1 ], A0_L_Q28 ), 14 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], out32_Q14[ 0 ], A0_U_Q28 ); + S[ 2 ] = silk_SMLAWB( S[ 2 ], out32_Q14[ 1 ], A0_U_Q28 ); + S[ 0 ] = silk_SMLAWB( S[ 0 ], B_Q28[ 1 ], in[ 2 * k + 0 ] ); + S[ 2 ] = silk_SMLAWB( S[ 2 ], B_Q28[ 1 ], in[ 2 * k + 1 ] ); + + S[ 1 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 0 ], A1_L_Q28 ), 14 ); + S[ 3 ] = silk_RSHIFT_ROUND( silk_SMULWB( out32_Q14[ 1 ], A1_L_Q28 ), 14 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], out32_Q14[ 0 ], A1_U_Q28 ); + S[ 3 ] = silk_SMLAWB( S[ 3 ], out32_Q14[ 1 ], A1_U_Q28 ); + S[ 1 ] = silk_SMLAWB( S[ 1 ], B_Q28[ 2 ], in[ 2 * k + 0 ] ); + S[ 3 ] = silk_SMLAWB( S[ 3 ], B_Q28[ 2 ], in[ 2 * k + 1 ] ); + + /* Scale back to Q0 and saturate */ + out[ 2 * k + 0 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14[ 0 ] + (1<<14) - 1, 14 ) ); + out[ 2 * k + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT( out32_Q14[ 1 ] + (1<<14) - 1, 14 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander.c new file mode 100644 index 0000000000..afa97907ec --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Chirp (bandwidth expand) LP AR filter */ +void silk_bwexpander( + opus_int16 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor (typically in the range 0 to 1) */ +) +{ + opus_int i; + opus_int32 chirp_minus_one_Q16 = chirp_Q16 - 65536; + + /* NB: Dont use silk_SMULWB, instead of silk_RSHIFT_ROUND( silk_MUL(), 16 ), below. */ + /* Bias in silk_SMULWB can lead to unstable filters */ + for( i = 0; i < d - 1; i++ ) { + ar[ i ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ i ] ), 16 ); + chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); + } + ar[ d - 1 ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ d - 1 ] ), 16 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander_32.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander_32.c new file mode 100644 index 0000000000..0f32b9df1a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/bwexpander_32.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Chirp (bandwidth expand) LP AR filter. + This logic is reused in _celt_lpc(). Any bug fixes should also be applied there. */ +void silk_bwexpander_32( + opus_int32 *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I Length of ar */ + opus_int32 chirp_Q16 /* I Chirp factor in Q16 */ +) +{ + opus_int i; + opus_int32 chirp_minus_one_Q16 = chirp_Q16 - 65536; + + for( i = 0; i < d - 1; i++ ) { + ar[ i ] = silk_SMULWW( chirp_Q16, ar[ i ] ); + chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); + } + ar[ d - 1 ] = silk_SMULWW( chirp_Q16, ar[ d - 1 ] ); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/check_control_input.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/check_control_input.c new file mode 100644 index 0000000000..739fb01f1e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/check_control_input.c @@ -0,0 +1,106 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "control.h" +#include "errors.h" + +/* Check encoder control struct */ +opus_int check_control_input( + silk_EncControlStruct *encControl /* I Control structure */ +) +{ + celt_assert( encControl != NULL ); + + if( ( ( encControl->API_sampleRate != 8000 ) && + ( encControl->API_sampleRate != 12000 ) && + ( encControl->API_sampleRate != 16000 ) && + ( encControl->API_sampleRate != 24000 ) && + ( encControl->API_sampleRate != 32000 ) && + ( encControl->API_sampleRate != 44100 ) && + ( encControl->API_sampleRate != 48000 ) ) || + ( ( encControl->desiredInternalSampleRate != 8000 ) && + ( encControl->desiredInternalSampleRate != 12000 ) && + ( encControl->desiredInternalSampleRate != 16000 ) ) || + ( ( encControl->maxInternalSampleRate != 8000 ) && + ( encControl->maxInternalSampleRate != 12000 ) && + ( encControl->maxInternalSampleRate != 16000 ) ) || + ( ( encControl->minInternalSampleRate != 8000 ) && + ( encControl->minInternalSampleRate != 12000 ) && + ( encControl->minInternalSampleRate != 16000 ) ) || + ( encControl->minInternalSampleRate > encControl->desiredInternalSampleRate ) || + ( encControl->maxInternalSampleRate < encControl->desiredInternalSampleRate ) || + ( encControl->minInternalSampleRate > encControl->maxInternalSampleRate ) ) { + celt_assert( 0 ); + return SILK_ENC_FS_NOT_SUPPORTED; + } + if( encControl->payloadSize_ms != 10 && + encControl->payloadSize_ms != 20 && + encControl->payloadSize_ms != 40 && + encControl->payloadSize_ms != 60 ) { + celt_assert( 0 ); + return SILK_ENC_PACKET_SIZE_NOT_SUPPORTED; + } + if( encControl->packetLossPercentage < 0 || encControl->packetLossPercentage > 100 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_LOSS_RATE; + } + if( encControl->useDTX < 0 || encControl->useDTX > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_DTX_SETTING; + } + if( encControl->useCBR < 0 || encControl->useCBR > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_CBR_SETTING; + } + if( encControl->useInBandFEC < 0 || encControl->useInBandFEC > 1 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_INBAND_FEC_SETTING; + } + if( encControl->nChannelsAPI < 1 || encControl->nChannelsAPI > ENCODER_NUM_CHANNELS ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->nChannelsInternal < 1 || encControl->nChannelsInternal > ENCODER_NUM_CHANNELS ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->nChannelsInternal > encControl->nChannelsAPI ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR; + } + if( encControl->complexity < 0 || encControl->complexity > 10 ) { + celt_assert( 0 ); + return SILK_ENC_INVALID_COMPLEXITY_SETTING; + } + + return SILK_NO_ERROR; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/code_signs.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/code_signs.c new file mode 100644 index 0000000000..dfd1dca9a1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/code_signs.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/*#define silk_enc_map(a) ((a) > 0 ? 1 : 0)*/ +/*#define silk_dec_map(a) ((a) > 0 ? 1 : -1)*/ +/* shifting avoids if-statement */ +#define silk_enc_map(a) ( silk_RSHIFT( (a), 15 ) + 1 ) +#define silk_dec_map(a) ( silk_LSHIFT( (a), 1 ) - 1 ) + +/* Encodes signs of excitation */ +void silk_encode_signs( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + const opus_int8 pulses[], /* I pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +) +{ + opus_int i, j, p; + opus_uint8 icdf[ 2 ]; + const opus_int8 *q_ptr; + const opus_uint8 *icdf_ptr; + + icdf[ 1 ] = 0; + q_ptr = pulses; + i = silk_SMULBB( 7, silk_ADD_LSHIFT( quantOffsetType, signalType, 1 ) ); + icdf_ptr = &silk_sign_iCDF[ i ]; + length = silk_RSHIFT( length + SHELL_CODEC_FRAME_LENGTH/2, LOG2_SHELL_CODEC_FRAME_LENGTH ); + for( i = 0; i < length; i++ ) { + p = sum_pulses[ i ]; + if( p > 0 ) { + icdf[ 0 ] = icdf_ptr[ silk_min( p & 0x1F, 6 ) ]; + for( j = 0; j < SHELL_CODEC_FRAME_LENGTH; j++ ) { + if( q_ptr[ j ] != 0 ) { + ec_enc_icdf( psRangeEnc, silk_enc_map( q_ptr[ j ]), icdf, 8 ); + } + } + } + q_ptr += SHELL_CODEC_FRAME_LENGTH; + } +} + +/* Decodes signs of excitation */ +void silk_decode_signs( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* I/O pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +) +{ + opus_int i, j, p; + opus_uint8 icdf[ 2 ]; + opus_int16 *q_ptr; + const opus_uint8 *icdf_ptr; + + icdf[ 1 ] = 0; + q_ptr = pulses; + i = silk_SMULBB( 7, silk_ADD_LSHIFT( quantOffsetType, signalType, 1 ) ); + icdf_ptr = &silk_sign_iCDF[ i ]; + length = silk_RSHIFT( length + SHELL_CODEC_FRAME_LENGTH/2, LOG2_SHELL_CODEC_FRAME_LENGTH ); + for( i = 0; i < length; i++ ) { + p = sum_pulses[ i ]; + if( p > 0 ) { + icdf[ 0 ] = icdf_ptr[ silk_min( p & 0x1F, 6 ) ]; + for( j = 0; j < SHELL_CODEC_FRAME_LENGTH; j++ ) { + if( q_ptr[ j ] > 0 ) { + /* attach sign */ +#if 0 + /* conditional implementation */ + if( ec_dec_icdf( psRangeDec, icdf, 8 ) == 0 ) { + q_ptr[ j ] = -q_ptr[ j ]; + } +#else + /* implementation with shift, subtraction, multiplication */ + q_ptr[ j ] *= silk_dec_map( ec_dec_icdf( psRangeDec, icdf, 8 ) ); +#endif + } + } + } + q_ptr += SHELL_CODEC_FRAME_LENGTH; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control.h new file mode 100644 index 0000000000..f5633e624e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control.h @@ -0,0 +1,161 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_CONTROL_H +#define SILK_CONTROL_H + +#include "typedef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Decoder API flags */ +#define FLAG_DECODE_NORMAL 0 +#define FLAG_PACKET_LOST 1 +#define FLAG_DECODE_LBRR 2 + +/***********************************************/ +/* Structure for controlling encoder operation */ +/***********************************************/ +typedef struct { + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsAPI; + + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsInternal; + + /* I: Input signal sampling rate in Hertz; 8000/12000/16000/24000/32000/44100/48000 */ + opus_int32 API_sampleRate; + + /* I: Maximum internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 maxInternalSampleRate; + + /* I: Minimum internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 minInternalSampleRate; + + /* I: Soft request for internal sampling rate in Hertz; 8000/12000/16000 */ + opus_int32 desiredInternalSampleRate; + + /* I: Number of samples per packet in milliseconds; 10/20/40/60 */ + opus_int payloadSize_ms; + + /* I: Bitrate during active speech in bits/second; internally limited */ + opus_int32 bitRate; + + /* I: Uplink packet loss in percent (0-100) */ + opus_int packetLossPercentage; + + /* I: Complexity mode; 0 is lowest, 10 is highest complexity */ + opus_int complexity; + + /* I: Flag to enable in-band Forward Error Correction (FEC); 0/1 */ + opus_int useInBandFEC; + + /* I: Flag to enable in-band Deep REDundancy (DRED); 0/1 */ + opus_int useDRED; + + /* I: Flag to actually code in-band Forward Error Correction (FEC) in the current packet; 0/1 */ + opus_int LBRR_coded; + + /* I: Flag to enable discontinuous transmission (DTX); 0/1 */ + opus_int useDTX; + + /* I: Flag to use constant bitrate */ + opus_int useCBR; + + /* I: Maximum number of bits allowed for the frame */ + opus_int maxBits; + + /* I: Causes a smooth downmix to mono */ + opus_int toMono; + + /* I: Opus encoder is allowing us to switch bandwidth */ + opus_int opusCanSwitch; + + /* I: Make frames as independent as possible (but still use LPC) */ + opus_int reducedDependency; + + /* O: Internal sampling rate used, in Hertz; 8000/12000/16000 */ + opus_int32 internalSampleRate; + + /* O: Flag that bandwidth switching is allowed (because low voice activity) */ + opus_int allowBandwidthSwitch; + + /* O: Flag that SILK runs in WB mode without variable LP filter (use for switching between WB/SWB/FB) */ + opus_int inWBmodeWithoutVariableLP; + + /* O: Stereo width */ + opus_int stereoWidth_Q14; + + /* O: Tells the Opus encoder we're ready to switch */ + opus_int switchReady; + + /* O: SILK Signal type */ + opus_int signalType; + + /* O: SILK offset (dithering) */ + opus_int offset; +} silk_EncControlStruct; + +/**************************************************************************/ +/* Structure for controlling decoder operation and reading decoder status */ +/**************************************************************************/ +typedef struct { + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsAPI; + + /* I: Number of channels; 1/2 */ + opus_int32 nChannelsInternal; + + /* I: Output signal sampling rate in Hertz; 8000/12000/16000/24000/32000/44100/48000 */ + opus_int32 API_sampleRate; + + /* I: Internal sampling rate used, in Hertz; 8000/12000/16000 */ + opus_int32 internalSampleRate; + + /* I: Number of samples per packet in milliseconds; 10/20/40/60 */ + opus_int payloadSize_ms; + + /* O: Pitch lag of previous frame (0 if unvoiced), measured in samples at 48 kHz */ + opus_int prevPitchLag; + + /* I: Enable Deep PLC */ + opus_int enable_deep_plc; + +#ifdef ENABLE_OSCE + /* I: OSCE method */ + opus_int osce_method; +#endif +} silk_DecControlStruct; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_SNR.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_SNR.c new file mode 100644 index 0000000000..9a6db27543 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_SNR.c @@ -0,0 +1,113 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +/* These tables hold SNR values divided by 21 (so they fit in 8 bits) + for different target bitrates spaced at 400 bps interval. The first + 10 values are omitted (0-4 kb/s) because they're all zeros. + These tables were obtained by running different SNRs through the + encoder and measuring the active bitrate. */ +static const unsigned char silk_TargetRate_NB_21[117 - 10] = { + 0, 15, 39, 52, 61, 68, + 74, 79, 84, 88, 92, 95, 99,102,105,108,111,114,117,119,122,124, + 126,129,131,133,135,137,139,142,143,145,147,149,151,153,155,157, + 158,160,162,163,165,167,168,170,171,173,174,176,177,179,180,182, + 183,185,186,187,189,190,192,193,194,196,197,199,200,201,203,204, + 205,207,208,209,211,212,213,215,216,217,219,220,221,223,224,225, + 227,228,230,231,232,234,235,236,238,239,241,242,243,245,246,248, + 249,250,252,253,255 +}; + +static const unsigned char silk_TargetRate_MB_21[165 - 10] = { + 0, 0, 28, 43, 52, 59, + 65, 70, 74, 78, 81, 85, 87, 90, 93, 95, 98,100,102,105,107,109, + 111,113,115,116,118,120,122,123,125,127,128,130,131,133,134,136, + 137,138,140,141,143,144,145,147,148,149,151,152,153,154,156,157, + 158,159,160,162,163,164,165,166,167,168,169,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,188,189,190, + 191,192,193,194,195,196,197,198,199,200,201,202,203,203,204,205, + 206,207,208,209,210,211,212,213,214,214,215,216,217,218,219,220, + 221,222,223,224,224,225,226,227,228,229,230,231,232,233,234,235, + 236,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250, + 251,252,253,254,255 +}; + +static const unsigned char silk_TargetRate_WB_21[201 - 10] = { + 0, 0, 0, 8, 29, 41, + 49, 56, 62, 66, 70, 74, 77, 80, 83, 86, 88, 91, 93, 95, 97, 99, + 101,103,105,107,108,110,112,113,115,116,118,119,121,122,123,125, + 126,127,129,130,131,132,134,135,136,137,138,140,141,142,143,144, + 145,146,147,148,149,150,151,152,153,154,156,157,158,159,159,160, + 161,162,163,164,165,166,167,168,169,170,171,171,172,173,174,175, + 176,177,177,178,179,180,181,181,182,183,184,185,185,186,187,188, + 189,189,190,191,192,192,193,194,195,195,196,197,198,198,199,200, + 200,201,202,203,203,204,205,206,206,207,208,209,209,210,211,211, + 212,213,214,214,215,216,216,217,218,219,219,220,221,221,222,223, + 224,224,225,226,226,227,228,229,229,230,231,232,232,233,234,234, + 235,236,237,237,238,239,240,240,241,242,243,243,244,245,246,246, + 247,248,249,249,250,251,252,253,255 +}; + +/* Control SNR of redidual quantizer */ +opus_int silk_control_SNR( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + opus_int32 TargetRate_bps /* I Target max bitrate (bps) */ +) +{ + int id; + int bound; + const unsigned char *snr_table; + + psEncC->TargetRate_bps = TargetRate_bps; + if( psEncC->nb_subfr == 2 ) { + TargetRate_bps -= 2000 + psEncC->fs_kHz/16; + } + if( psEncC->fs_kHz == 8 ) { + bound = sizeof(silk_TargetRate_NB_21); + snr_table = silk_TargetRate_NB_21; + } else if( psEncC->fs_kHz == 12 ) { + bound = sizeof(silk_TargetRate_MB_21); + snr_table = silk_TargetRate_MB_21; + } else { + bound = sizeof(silk_TargetRate_WB_21); + snr_table = silk_TargetRate_WB_21; + } + id = (TargetRate_bps+200)/400; + id = silk_min(id - 10, bound-1); + if( id <= 0 ) { + psEncC->SNR_dB_Q7 = 0; + } else { + psEncC->SNR_dB_Q7 = snr_table[id]*21; + } + return SILK_NO_ERROR; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_audio_bandwidth.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_audio_bandwidth.c new file mode 100644 index 0000000000..f6d22d8395 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_audio_bandwidth.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +/* Control internal sampling rate */ +opus_int silk_control_audio_bandwidth( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl /* I Control structure */ +) +{ + opus_int fs_kHz; + opus_int orig_kHz; + opus_int32 fs_Hz; + + orig_kHz = psEncC->fs_kHz; + /* Handle a bandwidth-switching reset where we need to be aware what the last sampling rate was. */ + if( orig_kHz == 0 ) { + orig_kHz = psEncC->sLP.saved_fs_kHz; + } + fs_kHz = orig_kHz; + fs_Hz = silk_SMULBB( fs_kHz, 1000 ); + if( fs_Hz == 0 ) { + /* Encoder has just been initialized */ + fs_Hz = silk_min( psEncC->desiredInternal_fs_Hz, psEncC->API_fs_Hz ); + fs_kHz = silk_DIV32_16( fs_Hz, 1000 ); + } else if( fs_Hz > psEncC->API_fs_Hz || fs_Hz > psEncC->maxInternal_fs_Hz || fs_Hz < psEncC->minInternal_fs_Hz ) { + /* Make sure internal rate is not higher than external rate or maximum allowed, or lower than minimum allowed */ + fs_Hz = psEncC->API_fs_Hz; + fs_Hz = silk_min( fs_Hz, psEncC->maxInternal_fs_Hz ); + fs_Hz = silk_max( fs_Hz, psEncC->minInternal_fs_Hz ); + fs_kHz = silk_DIV32_16( fs_Hz, 1000 ); + } else { + /* State machine for the internal sampling rate switching */ + if( psEncC->sLP.transition_frame_no >= TRANSITION_FRAMES ) { + /* Stop transition phase */ + psEncC->sLP.mode = 0; + } + if( psEncC->allow_bandwidth_switch || encControl->opusCanSwitch ) { + /* Check if we should switch down */ + if( silk_SMULBB( orig_kHz, 1000 ) > psEncC->desiredInternal_fs_Hz ) + { + /* Switch down */ + if( psEncC->sLP.mode == 0 ) { + /* New transition */ + psEncC->sLP.transition_frame_no = TRANSITION_FRAMES; + + /* Reset transition filter state */ + silk_memset( psEncC->sLP.In_LP_State, 0, sizeof( psEncC->sLP.In_LP_State ) ); + } + if( encControl->opusCanSwitch ) { + /* Stop transition phase */ + psEncC->sLP.mode = 0; + + /* Switch to a lower sample frequency */ + fs_kHz = orig_kHz == 16 ? 12 : 8; + } else { + if( psEncC->sLP.transition_frame_no <= 0 ) { + encControl->switchReady = 1; + /* Make room for redundancy */ + encControl->maxBits -= encControl->maxBits * 5 / ( encControl->payloadSize_ms + 5 ); + } else { + /* Direction: down (at double speed) */ + psEncC->sLP.mode = -2; + } + } + } + else + /* Check if we should switch up */ + if( silk_SMULBB( orig_kHz, 1000 ) < psEncC->desiredInternal_fs_Hz ) + { + /* Switch up */ + if( encControl->opusCanSwitch ) { + /* Switch to a higher sample frequency */ + fs_kHz = orig_kHz == 8 ? 12 : 16; + + /* New transition */ + psEncC->sLP.transition_frame_no = 0; + + /* Reset transition filter state */ + silk_memset( psEncC->sLP.In_LP_State, 0, sizeof( psEncC->sLP.In_LP_State ) ); + + /* Direction: up */ + psEncC->sLP.mode = 1; + } else { + if( psEncC->sLP.mode == 0 ) { + encControl->switchReady = 1; + /* Make room for redundancy */ + encControl->maxBits -= encControl->maxBits * 5 / ( encControl->payloadSize_ms + 5 ); + } else { + /* Direction: up */ + psEncC->sLP.mode = 1; + } + } + } else { + if (psEncC->sLP.mode<0) + psEncC->sLP.mode = 1; + } + } + } + + return fs_kHz; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_codec.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_codec.c new file mode 100644 index 0000000000..784ffe66d6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/control_codec.c @@ -0,0 +1,423 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#define silk_encoder_state_Fxx silk_encoder_state_FIX +#else +#include "main_FLP.h" +#define silk_encoder_state_Fxx silk_encoder_state_FLP +#endif +#include "stack_alloc.h" +#include "tuning_parameters.h" +#include "pitch_est_defines.h" + +static opus_int silk_setup_resamplers( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz /* I */ +); + +static opus_int silk_setup_fs( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz, /* I */ + opus_int PacketSize_ms /* I */ +); + +static opus_int silk_setup_complexity( + silk_encoder_state *psEncC, /* I/O */ + opus_int Complexity /* I */ +); + +static OPUS_INLINE opus_int silk_setup_LBRR( + silk_encoder_state *psEncC, /* I/O */ + const silk_EncControlStruct *encControl /* I */ +); + + +/* Control encoder */ +opus_int silk_control_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl, /* I Control structure */ + const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ + const opus_int channelNb, /* I Channel number */ + const opus_int force_fs_kHz +) +{ + opus_int fs_kHz, ret = 0; + + psEnc->sCmn.useDTX = encControl->useDTX; + psEnc->sCmn.useCBR = encControl->useCBR; + psEnc->sCmn.API_fs_Hz = encControl->API_sampleRate; + psEnc->sCmn.maxInternal_fs_Hz = encControl->maxInternalSampleRate; + psEnc->sCmn.minInternal_fs_Hz = encControl->minInternalSampleRate; + psEnc->sCmn.desiredInternal_fs_Hz = encControl->desiredInternalSampleRate; + psEnc->sCmn.useInBandFEC = encControl->useInBandFEC; + psEnc->sCmn.nChannelsAPI = encControl->nChannelsAPI; + psEnc->sCmn.nChannelsInternal = encControl->nChannelsInternal; + psEnc->sCmn.allow_bandwidth_switch = allow_bw_switch; + psEnc->sCmn.channelNb = channelNb; + + if( psEnc->sCmn.controlled_since_last_payload != 0 && psEnc->sCmn.prefillFlag == 0 ) { + if( psEnc->sCmn.API_fs_Hz != psEnc->sCmn.prev_API_fs_Hz && psEnc->sCmn.fs_kHz > 0 ) { + /* Change in API sampling rate in the middle of encoding a packet */ + ret += silk_setup_resamplers( psEnc, psEnc->sCmn.fs_kHz ); + } + return ret; + } + + /* Beyond this point we know that there are no previously coded frames in the payload buffer */ + + /********************************************/ + /* Determine internal sampling rate */ + /********************************************/ + fs_kHz = silk_control_audio_bandwidth( &psEnc->sCmn, encControl ); + if( force_fs_kHz ) { + fs_kHz = force_fs_kHz; + } + /********************************************/ + /* Prepare resampler and buffered data */ + /********************************************/ + ret += silk_setup_resamplers( psEnc, fs_kHz ); + + /********************************************/ + /* Set internal sampling frequency */ + /********************************************/ + ret += silk_setup_fs( psEnc, fs_kHz, encControl->payloadSize_ms ); + + /********************************************/ + /* Set encoding complexity */ + /********************************************/ + ret += silk_setup_complexity( &psEnc->sCmn, encControl->complexity ); + + /********************************************/ + /* Set packet loss rate measured by farend */ + /********************************************/ + psEnc->sCmn.PacketLoss_perc = encControl->packetLossPercentage; + + /********************************************/ + /* Set LBRR usage */ + /********************************************/ + ret += silk_setup_LBRR( &psEnc->sCmn, encControl ); + + psEnc->sCmn.controlled_since_last_payload = 1; + + return ret; +} + +static opus_int silk_setup_resamplers( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz /* I */ +) +{ + opus_int ret = SILK_NO_ERROR; + SAVE_STACK; + + if( psEnc->sCmn.fs_kHz != fs_kHz || psEnc->sCmn.prev_API_fs_Hz != psEnc->sCmn.API_fs_Hz ) + { + if( psEnc->sCmn.fs_kHz == 0 ) { + /* Initialize the resampler for enc_API.c preparing resampling from API_fs_Hz to fs_kHz */ + ret += silk_resampler_init( &psEnc->sCmn.resampler_state, psEnc->sCmn.API_fs_Hz, fs_kHz * 1000, 1 ); + } else { + VARDECL( opus_int16, x_buf_API_fs_Hz ); + VARDECL( silk_resampler_state_struct, temp_resampler_state ); +#ifdef FIXED_POINT + opus_int16 *x_bufFIX = psEnc->x_buf; +#else + VARDECL( opus_int16, x_bufFIX ); + opus_int32 new_buf_samples; +#endif + opus_int32 api_buf_samples; + opus_int32 old_buf_samples; + opus_int32 buf_length_ms; + + buf_length_ms = silk_LSHIFT( psEnc->sCmn.nb_subfr * 5, 1 ) + LA_SHAPE_MS; + old_buf_samples = buf_length_ms * psEnc->sCmn.fs_kHz; + +#ifndef FIXED_POINT + new_buf_samples = buf_length_ms * fs_kHz; + ALLOC( x_bufFIX, silk_max( old_buf_samples, new_buf_samples ), + opus_int16 ); + silk_float2short_array( x_bufFIX, psEnc->x_buf, old_buf_samples ); +#endif + + /* Initialize resampler for temporary resampling of x_buf data to API_fs_Hz */ + ALLOC( temp_resampler_state, 1, silk_resampler_state_struct ); + ret += silk_resampler_init( temp_resampler_state, silk_SMULBB( psEnc->sCmn.fs_kHz, 1000 ), psEnc->sCmn.API_fs_Hz, 0 ); + + /* Calculate number of samples to temporarily upsample */ + api_buf_samples = buf_length_ms * silk_DIV32_16( psEnc->sCmn.API_fs_Hz, 1000 ); + + /* Temporary resampling of x_buf data to API_fs_Hz */ + ALLOC( x_buf_API_fs_Hz, api_buf_samples, opus_int16 ); + ret += silk_resampler( temp_resampler_state, x_buf_API_fs_Hz, x_bufFIX, old_buf_samples ); + + /* Initialize the resampler for enc_API.c preparing resampling from API_fs_Hz to fs_kHz */ + ret += silk_resampler_init( &psEnc->sCmn.resampler_state, psEnc->sCmn.API_fs_Hz, silk_SMULBB( fs_kHz, 1000 ), 1 ); + + /* Correct resampler state by resampling buffered data from API_fs_Hz to fs_kHz */ + ret += silk_resampler( &psEnc->sCmn.resampler_state, x_bufFIX, x_buf_API_fs_Hz, api_buf_samples ); + +#ifndef FIXED_POINT + silk_short2float_array( psEnc->x_buf, x_bufFIX, new_buf_samples); +#endif + } + } + + psEnc->sCmn.prev_API_fs_Hz = psEnc->sCmn.API_fs_Hz; + + RESTORE_STACK; + return ret; +} + +static opus_int silk_setup_fs( + silk_encoder_state_Fxx *psEnc, /* I/O */ + opus_int fs_kHz, /* I */ + opus_int PacketSize_ms /* I */ +) +{ + opus_int ret = SILK_NO_ERROR; + + /* Set packet size */ + if( PacketSize_ms != psEnc->sCmn.PacketSize_ms ) { + if( ( PacketSize_ms != 10 ) && + ( PacketSize_ms != 20 ) && + ( PacketSize_ms != 40 ) && + ( PacketSize_ms != 60 ) ) { + ret = SILK_ENC_PACKET_SIZE_NOT_SUPPORTED; + } + if( PacketSize_ms <= 10 ) { + psEnc->sCmn.nFramesPerPacket = 1; + psEnc->sCmn.nb_subfr = PacketSize_ms == 10 ? 2 : 1; + psEnc->sCmn.frame_length = silk_SMULBB( PacketSize_ms, fs_kHz ); + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS_2_SF, fs_kHz ); + if( psEnc->sCmn.fs_kHz == 8 ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } else { + psEnc->sCmn.nFramesPerPacket = silk_DIV32_16( PacketSize_ms, MAX_FRAME_LENGTH_MS ); + psEnc->sCmn.nb_subfr = MAX_NB_SUBFR; + psEnc->sCmn.frame_length = silk_SMULBB( 20, fs_kHz ); + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS, fs_kHz ); + if( psEnc->sCmn.fs_kHz == 8 ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_iCDF; + } + } + psEnc->sCmn.PacketSize_ms = PacketSize_ms; + psEnc->sCmn.TargetRate_bps = 0; /* trigger new SNR computation */ + } + + /* Set internal sampling frequency */ + celt_assert( fs_kHz == 8 || fs_kHz == 12 || fs_kHz == 16 ); + celt_assert( psEnc->sCmn.nb_subfr == 2 || psEnc->sCmn.nb_subfr == 4 ); + if( psEnc->sCmn.fs_kHz != fs_kHz ) { + /* reset part of the state */ + silk_memset( &psEnc->sShape, 0, sizeof( psEnc->sShape ) ); + silk_memset( &psEnc->sCmn.sNSQ, 0, sizeof( psEnc->sCmn.sNSQ ) ); + silk_memset( psEnc->sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); + silk_memset( &psEnc->sCmn.sLP.In_LP_State, 0, sizeof( psEnc->sCmn.sLP.In_LP_State ) ); + psEnc->sCmn.inputBufIx = 0; + psEnc->sCmn.nFramesEncoded = 0; + psEnc->sCmn.TargetRate_bps = 0; /* trigger new SNR computation */ + + /* Initialize non-zero parameters */ + psEnc->sCmn.prevLag = 100; + psEnc->sCmn.first_frame_after_reset = 1; + psEnc->sShape.LastGainIndex = 10; + psEnc->sCmn.sNSQ.lagPrev = 100; + psEnc->sCmn.sNSQ.prev_gain_Q16 = 65536; + psEnc->sCmn.prevSignalType = TYPE_NO_VOICE_ACTIVITY; + + psEnc->sCmn.fs_kHz = fs_kHz; + if( psEnc->sCmn.fs_kHz == 8 ) { + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } + } else { + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_iCDF; + } else { + psEnc->sCmn.pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } + if( psEnc->sCmn.fs_kHz == 8 || psEnc->sCmn.fs_kHz == 12 ) { + psEnc->sCmn.predictLPCOrder = MIN_LPC_ORDER; + psEnc->sCmn.psNLSF_CB = &silk_NLSF_CB_NB_MB; + } else { + psEnc->sCmn.predictLPCOrder = MAX_LPC_ORDER; + psEnc->sCmn.psNLSF_CB = &silk_NLSF_CB_WB; + } + psEnc->sCmn.subfr_length = SUB_FRAME_LENGTH_MS * fs_kHz; + psEnc->sCmn.frame_length = silk_SMULBB( psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr ); + psEnc->sCmn.ltp_mem_length = silk_SMULBB( LTP_MEM_LENGTH_MS, fs_kHz ); + psEnc->sCmn.la_pitch = silk_SMULBB( LA_PITCH_MS, fs_kHz ); + psEnc->sCmn.max_pitch_lag = silk_SMULBB( 18, fs_kHz ); + if( psEnc->sCmn.nb_subfr == MAX_NB_SUBFR ) { + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS, fs_kHz ); + } else { + psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS_2_SF, fs_kHz ); + } + if( psEnc->sCmn.fs_kHz == 16 ) { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform8_iCDF; + } else if( psEnc->sCmn.fs_kHz == 12 ) { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform6_iCDF; + } else { + psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform4_iCDF; + } + } + + /* Check that settings are valid */ + celt_assert( ( psEnc->sCmn.subfr_length * psEnc->sCmn.nb_subfr ) == psEnc->sCmn.frame_length ); + + return ret; +} + +static opus_int silk_setup_complexity( + silk_encoder_state *psEncC, /* I/O */ + opus_int Complexity /* I */ +) +{ + opus_int ret = 0; + + /* Set encoding complexity */ + celt_assert( Complexity >= 0 && Complexity <= 10 ); + if( Complexity < 1 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); + psEncC->pitchEstimationLPCOrder = 6; + psEncC->shapingLPCOrder = 12; + psEncC->la_shape = 3 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 1; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 2; + psEncC->warping_Q16 = 0; + } else if( Complexity < 2 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); + psEncC->pitchEstimationLPCOrder = 8; + psEncC->shapingLPCOrder = 14; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 1; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 3; + psEncC->warping_Q16 = 0; + } else if( Complexity < 3 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); + psEncC->pitchEstimationLPCOrder = 6; + psEncC->shapingLPCOrder = 12; + psEncC->la_shape = 3 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 2; + psEncC->warping_Q16 = 0; + } else if( Complexity < 4 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); + psEncC->pitchEstimationLPCOrder = 8; + psEncC->shapingLPCOrder = 14; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 4; + psEncC->warping_Q16 = 0; + } else if( Complexity < 6 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.74, 16 ); + psEncC->pitchEstimationLPCOrder = 10; + psEncC->shapingLPCOrder = 16; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 6; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } else if( Complexity < 8 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.72, 16 ); + psEncC->pitchEstimationLPCOrder = 12; + psEncC->shapingLPCOrder = 20; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 3; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 8; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } else { + psEncC->pitchEstimationComplexity = SILK_PE_MAX_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.7, 16 ); + psEncC->pitchEstimationLPCOrder = 16; + psEncC->shapingLPCOrder = 24; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = MAX_DEL_DEC_STATES; + psEncC->useInterpolatedNLSFs = 1; + psEncC->NLSF_MSVQ_Survivors = 16; + psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); + } + + /* Do not allow higher pitch estimation LPC order than predict LPC order */ + psEncC->pitchEstimationLPCOrder = silk_min_int( psEncC->pitchEstimationLPCOrder, psEncC->predictLPCOrder ); + psEncC->shapeWinLength = SUB_FRAME_LENGTH_MS * psEncC->fs_kHz + 2 * psEncC->la_shape; + psEncC->Complexity = Complexity; + + celt_assert( psEncC->pitchEstimationLPCOrder <= MAX_FIND_PITCH_LPC_ORDER ); + celt_assert( psEncC->shapingLPCOrder <= MAX_SHAPE_LPC_ORDER ); + celt_assert( psEncC->nStatesDelayedDecision <= MAX_DEL_DEC_STATES ); + celt_assert( psEncC->warping_Q16 <= 32767 ); + celt_assert( psEncC->la_shape <= LA_SHAPE_MAX ); + celt_assert( psEncC->shapeWinLength <= SHAPE_LPC_WIN_MAX ); + + return ret; +} + +static OPUS_INLINE opus_int silk_setup_LBRR( + silk_encoder_state *psEncC, /* I/O */ + const silk_EncControlStruct *encControl /* I */ +) +{ + opus_int LBRR_in_previous_packet, ret = SILK_NO_ERROR; + + LBRR_in_previous_packet = psEncC->LBRR_enabled; + psEncC->LBRR_enabled = encControl->LBRR_coded; + if( psEncC->LBRR_enabled ) { + /* Set gain increase for coding LBRR excitation */ + if( LBRR_in_previous_packet == 0 ) { + /* Previous packet did not have LBRR, and was therefore coded at a higher bitrate */ + psEncC->LBRR_GainIncreases = 7; + } else { + psEncC->LBRR_GainIncreases = silk_max_int( 7 - silk_SMULWB( (opus_int32)psEncC->PacketLoss_perc, SILK_FIX_CONST( 0.2, 16 ) ), 3 ); + } + } + + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.c new file mode 100644 index 0000000000..46a24a47e4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.c @@ -0,0 +1,174 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +typedef int prevent_empty_translation_unit_warning; + +#include "debug.h" + +#if SILK_DEBUG || SILK_TIC_TOC +#include "SigProc_FIX.h" +#endif + +#if SILK_TIC_TOC + +#if (defined(_WIN32) || defined(_WINCE)) +#include /* timer */ +#else /* Linux or Mac*/ +#include +#endif + +#ifdef _WIN32 +unsigned long silk_GetHighResolutionTime(void) /* O time in usec*/ +{ + /* Returns a time counter in microsec */ + /* the resolution is platform dependent */ + /* but is typically 1.62 us resolution */ + LARGE_INTEGER lpPerformanceCount; + LARGE_INTEGER lpFrequency; + QueryPerformanceCounter(&lpPerformanceCount); + QueryPerformanceFrequency(&lpFrequency); + return (unsigned long)((1000000*(lpPerformanceCount.QuadPart)) / lpFrequency.QuadPart); +} +#else /* Linux or Mac*/ +unsigned long GetHighResolutionTime(void) /* O time in usec*/ +{ + struct timeval tv; + gettimeofday(&tv, 0); + return((tv.tv_sec*1000000)+(tv.tv_usec)); +} +#endif + +int silk_Timer_nTimers = 0; +int silk_Timer_depth_ctr = 0; +char silk_Timer_tags[silk_NUM_TIMERS_MAX][silk_NUM_TIMERS_MAX_TAG_LEN]; +#ifdef _WIN32 +LARGE_INTEGER silk_Timer_start[silk_NUM_TIMERS_MAX]; +#else +unsigned long silk_Timer_start[silk_NUM_TIMERS_MAX]; +#endif +unsigned int silk_Timer_cnt[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_min[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_sum[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_max[silk_NUM_TIMERS_MAX]; +opus_int64 silk_Timer_depth[silk_NUM_TIMERS_MAX]; + +#ifdef _WIN32 +void silk_TimerSave(char *file_name) +{ + if( silk_Timer_nTimers > 0 ) + { + int k; + FILE *fp; + LARGE_INTEGER lpFrequency; + LARGE_INTEGER lpPerformanceCount1, lpPerformanceCount2; + int del = 0x7FFFFFFF; + double avg, sum_avg; + /* estimate overhead of calling performance counters */ + for( k = 0; k < 1000; k++ ) { + QueryPerformanceCounter(&lpPerformanceCount1); + QueryPerformanceCounter(&lpPerformanceCount2); + lpPerformanceCount2.QuadPart -= lpPerformanceCount1.QuadPart; + if( (int)lpPerformanceCount2.LowPart < del ) + del = lpPerformanceCount2.LowPart; + } + QueryPerformanceFrequency(&lpFrequency); + /* print results to file */ + sum_avg = 0.0f; + for( k = 0; k < silk_Timer_nTimers; k++ ) { + if (silk_Timer_depth[k] == 0) { + sum_avg += (1e6 * silk_Timer_sum[k] / silk_Timer_cnt[k] - del) / lpFrequency.QuadPart * silk_Timer_cnt[k]; + } + } + fp = fopen(file_name, "w"); + fprintf(fp, " min avg %% max count\n"); + for( k = 0; k < silk_Timer_nTimers; k++ ) { + if (silk_Timer_depth[k] == 0) { + fprintf(fp, "%-28s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 1) { + fprintf(fp, " %-27s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 2) { + fprintf(fp, " %-26s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 3) { + fprintf(fp, " %-25s", silk_Timer_tags[k]); + } else { + fprintf(fp, " %-24s", silk_Timer_tags[k]); + } + avg = (1e6 * silk_Timer_sum[k] / silk_Timer_cnt[k] - del) / lpFrequency.QuadPart; + fprintf(fp, "%8.2f", (1e6 * (silk_max_64(silk_Timer_min[k] - del, 0))) / lpFrequency.QuadPart); + fprintf(fp, "%12.2f %6.2f", avg, 100.0 * avg / sum_avg * silk_Timer_cnt[k]); + fprintf(fp, "%12.2f", (1e6 * (silk_max_64(silk_Timer_max[k] - del, 0))) / lpFrequency.QuadPart); + fprintf(fp, "%10d\n", silk_Timer_cnt[k]); + } + fprintf(fp, " microseconds\n"); + fclose(fp); + } +} +#else +void silk_TimerSave(char *file_name) +{ + if( silk_Timer_nTimers > 0 ) + { + int k; + FILE *fp; + /* print results to file */ + fp = fopen(file_name, "w"); + fprintf(fp, " min avg max count\n"); + for( k = 0; k < silk_Timer_nTimers; k++ ) + { + if (silk_Timer_depth[k] == 0) { + fprintf(fp, "%-28s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 1) { + fprintf(fp, " %-27s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 2) { + fprintf(fp, " %-26s", silk_Timer_tags[k]); + } else if (silk_Timer_depth[k] == 3) { + fprintf(fp, " %-25s", silk_Timer_tags[k]); + } else { + fprintf(fp, " %-24s", silk_Timer_tags[k]); + } + fprintf(fp, "%d ", silk_Timer_min[k]); + fprintf(fp, "%f ", (double)silk_Timer_sum[k] / (double)silk_Timer_cnt[k]); + fprintf(fp, "%d ", silk_Timer_max[k]); + fprintf(fp, "%10d\n", silk_Timer_cnt[k]); + } + fprintf(fp, " microseconds\n"); + fclose(fp); + } +} +#endif + +#endif /* SILK_TIC_TOC */ + +#if SILK_DEBUG +FILE *silk_debug_store_fp[ silk_NUM_STORES_MAX ]; +int silk_debug_store_count = 0; +#endif /* SILK_DEBUG */ + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.h new file mode 100644 index 0000000000..36163e478d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/debug.h @@ -0,0 +1,267 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_DEBUG_H +#define SILK_DEBUG_H + +/* Set to 1 to enable DEBUG_STORE_DATA() macros for dumping + * intermediate signals from the codec. + */ +#define SILK_DEBUG 0 + +/* Flag for using timers */ +#define SILK_TIC_TOC 0 + +#if SILK_DEBUG || SILK_TIC_TOC +#include "typedef.h" +#include /* strcpy, strcmp */ +#include /* file writing */ +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if SILK_TIC_TOC + +unsigned long GetHighResolutionTime(void); /* O time in usec*/ + +#if (defined(_WIN32) || defined(_WINCE)) +#include /* timer */ +#else /* Linux or Mac*/ +#include +#endif + +/*********************************/ +/* timer functions for profiling */ +/*********************************/ +/* example: */ +/* */ +/* TIC(LPC) */ +/* do_LPC(in_vec, order, acoef); // do LPC analysis */ +/* TOC(LPC) */ +/* */ +/* and call the following just before exiting (from main) */ +/* */ +/* silk_TimerSave("silk_TimingData.txt"); */ +/* */ +/* results are now in silk_TimingData.txt */ + +void silk_TimerSave(char *file_name); + +/* max number of timers (in different locations) */ +#define silk_NUM_TIMERS_MAX 50 +/* max length of name tags in TIC(..), TOC(..) */ +#define silk_NUM_TIMERS_MAX_TAG_LEN 30 + +extern int silk_Timer_nTimers; +extern int silk_Timer_depth_ctr; +extern char silk_Timer_tags[silk_NUM_TIMERS_MAX][silk_NUM_TIMERS_MAX_TAG_LEN]; +#ifdef _WIN32 +extern LARGE_INTEGER silk_Timer_start[silk_NUM_TIMERS_MAX]; +#else +extern unsigned long silk_Timer_start[silk_NUM_TIMERS_MAX]; +#endif +extern unsigned int silk_Timer_cnt[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_sum[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_max[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_min[silk_NUM_TIMERS_MAX]; +extern opus_int64 silk_Timer_depth[silk_NUM_TIMERS_MAX]; + +/* WARNING: TIC()/TOC can measure only up to 0.1 seconds at a time */ +#ifdef _WIN32 +#define TIC(TAG_NAME) { \ + static int init = 0; \ + static int ID = -1; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + if (ID == -1) { \ + ID = silk_Timer_nTimers; \ + silk_Timer_nTimers++; \ + silk_Timer_depth[ID] = silk_Timer_depth_ctr; \ + strcpy(silk_Timer_tags[ID], #TAG_NAME); \ + silk_Timer_cnt[ID] = 0; \ + silk_Timer_sum[ID] = 0; \ + silk_Timer_min[ID] = 0xFFFFFFFF; \ + silk_Timer_max[ID] = 0; \ + } \ + } \ + silk_Timer_depth_ctr++; \ + QueryPerformanceCounter(&silk_Timer_start[ID]); \ +} +#else +#define TIC(TAG_NAME) { \ + static int init = 0; \ + static int ID = -1; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + if (ID == -1) { \ + ID = silk_Timer_nTimers; \ + silk_Timer_nTimers++; \ + silk_Timer_depth[ID] = silk_Timer_depth_ctr; \ + strcpy(silk_Timer_tags[ID], #TAG_NAME); \ + silk_Timer_cnt[ID] = 0; \ + silk_Timer_sum[ID] = 0; \ + silk_Timer_min[ID] = 0xFFFFFFFF; \ + silk_Timer_max[ID] = 0; \ + } \ + } \ + silk_Timer_depth_ctr++; \ + silk_Timer_start[ID] = GetHighResolutionTime(); \ +} +#endif + +#ifdef _WIN32 +#define TOC(TAG_NAME) { \ + LARGE_INTEGER lpPerformanceCount; \ + static int init = 0; \ + static int ID = 0; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + } \ + QueryPerformanceCounter(&lpPerformanceCount); \ + lpPerformanceCount.QuadPart -= silk_Timer_start[ID].QuadPart; \ + if((lpPerformanceCount.QuadPart < 100000000) && \ + (lpPerformanceCount.QuadPart >= 0)) { \ + silk_Timer_cnt[ID]++; \ + silk_Timer_sum[ID] += lpPerformanceCount.QuadPart; \ + if( lpPerformanceCount.QuadPart > silk_Timer_max[ID] ) \ + silk_Timer_max[ID] = lpPerformanceCount.QuadPart; \ + if( lpPerformanceCount.QuadPart < silk_Timer_min[ID] ) \ + silk_Timer_min[ID] = lpPerformanceCount.QuadPart; \ + } \ + silk_Timer_depth_ctr--; \ +} +#else +#define TOC(TAG_NAME) { \ + unsigned long endTime; \ + static int init = 0; \ + static int ID = 0; \ + if( init == 0 ) \ + { \ + int k; \ + init = 1; \ + for( k = 0; k < silk_Timer_nTimers; k++ ) { \ + if( strcmp(silk_Timer_tags[k], #TAG_NAME) == 0 ) { \ + ID = k; \ + break; \ + } \ + } \ + } \ + endTime = GetHighResolutionTime(); \ + endTime -= silk_Timer_start[ID]; \ + if((endTime < 100000000) && \ + (endTime >= 0)) { \ + silk_Timer_cnt[ID]++; \ + silk_Timer_sum[ID] += endTime; \ + if( endTime > silk_Timer_max[ID] ) \ + silk_Timer_max[ID] = endTime; \ + if( endTime < silk_Timer_min[ID] ) \ + silk_Timer_min[ID] = endTime; \ + } \ + silk_Timer_depth_ctr--; \ +} +#endif + +#else /* SILK_TIC_TOC */ + +/* define macros as empty strings */ +#define TIC(TAG_NAME) +#define TOC(TAG_NAME) +#define silk_TimerSave(FILE_NAME) + +#endif /* SILK_TIC_TOC */ + + +#if SILK_DEBUG +/************************************/ +/* write data to file for debugging */ +/************************************/ +/* Example: DEBUG_STORE_DATA(testfile.pcm, &RIN[0], 160*sizeof(opus_int16)); */ + +#define silk_NUM_STORES_MAX 100 +extern FILE *silk_debug_store_fp[ silk_NUM_STORES_MAX ]; +extern int silk_debug_store_count; + +/* Faster way of storing the data */ +#define DEBUG_STORE_DATA( FILE_NAME, DATA_PTR, N_BYTES ) { \ + static opus_int init = 0, cnt = 0; \ + static FILE **fp; \ + if (init == 0) { \ + init = 1; \ + cnt = silk_debug_store_count++; \ + silk_debug_store_fp[ cnt ] = fopen(#FILE_NAME, "wb"); \ + } \ + fwrite((DATA_PTR), (N_BYTES), 1, silk_debug_store_fp[ cnt ]); \ +} + +/* Call this at the end of main() */ +#define SILK_DEBUG_STORE_CLOSE_FILES { \ + opus_int i; \ + for( i = 0; i < silk_debug_store_count; i++ ) { \ + fclose( silk_debug_store_fp[ i ] ); \ + } \ +} + +#else /* SILK_DEBUG */ + +/* define macros as empty strings */ +#define DEBUG_STORE_DATA(FILE_NAME, DATA_PTR, N_BYTES) +#define SILK_DEBUG_STORE_CLOSE_FILES + +#endif /* SILK_DEBUG */ + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_DEBUG_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/dec_API.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/dec_API.c new file mode 100644 index 0000000000..c1091d13ed --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/dec_API.c @@ -0,0 +1,486 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "API.h" +#include "main.h" +#include "stack_alloc.h" +#include "os_support.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#include "osce_structs.h" +#endif + +/************************/ +/* Decoder Super Struct */ +/************************/ +typedef struct { + silk_decoder_state channel_state[ DECODER_NUM_CHANNELS ]; + stereo_dec_state sStereo; + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int prev_decode_only_middle; +#ifdef ENABLE_OSCE + OSCEModel osce_model; +#endif +} silk_decoder; + +/*********************/ +/* Decoder functions */ +/*********************/ + + + +opus_int silk_LoadOSCEModels(void *decState, const unsigned char *data, int len) +{ +#ifdef ENABLE_OSCE + opus_int ret = SILK_NO_ERROR; + + ret = osce_load_models(&((silk_decoder *)decState)->osce_model, data, len); + ((silk_decoder *)decState)->osce_model.loaded = (ret == 0); + return ret; +#else + (void) decState; + (void) data; + (void) len; + return SILK_NO_ERROR; +#endif +} + +opus_int silk_Get_Decoder_Size( /* O Returns error code */ + opus_int *decSizeBytes /* O Number of bytes in SILK decoder state */ +) +{ + opus_int ret = SILK_NO_ERROR; + + *decSizeBytes = sizeof( silk_decoder ); + + return ret; +} + +/* Reset decoder state */ +opus_int silk_ResetDecoder( /* O Returns error code */ + void *decState /* I/O State */ +) +{ + opus_int n, ret = SILK_NO_ERROR; + silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state; + + for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) { + ret = silk_reset_decoder( &channel_state[ n ] ); + } + silk_memset(&((silk_decoder *)decState)->sStereo, 0, sizeof(((silk_decoder *)decState)->sStereo)); + /* Not strictly needed, but it's cleaner that way */ + ((silk_decoder *)decState)->prev_decode_only_middle = 0; + + return ret; +} + + +opus_int silk_InitDecoder( /* O Returns error code */ + void *decState /* I/O State */ +) +{ + opus_int n, ret = SILK_NO_ERROR; + silk_decoder_state *channel_state = ((silk_decoder *)decState)->channel_state; +#ifdef ENABLE_OSCE + ((silk_decoder *)decState)->osce_model.loaded = 0; +#endif +#ifndef USE_WEIGHTS_FILE + /* load osce models */ + silk_LoadOSCEModels(decState, NULL, 0); +#endif + + for( n = 0; n < DECODER_NUM_CHANNELS; n++ ) { + ret = silk_init_decoder( &channel_state[ n ] ); + } + silk_memset(&((silk_decoder *)decState)->sStereo, 0, sizeof(((silk_decoder *)decState)->sStereo)); + /* Not strictly needed, but it's cleaner that way */ + ((silk_decoder *)decState)->prev_decode_only_middle = 0; + + return ret; +} + +/* Decode a frame */ +opus_int silk_Decode( /* O Returns error code */ + void* decState, /* I/O State */ + silk_DecControlStruct* decControl, /* I/O Control Structure */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 *samplesOut, /* O Decoded output speech vector */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif + int arch /* I Run-time architecture */ +) +{ + opus_int i, n, decode_only_middle = 0, ret = SILK_NO_ERROR; + opus_int32 nSamplesOutDec, LBRR_symbol; + opus_int16 *samplesOut1_tmp[ 2 ]; + VARDECL( opus_int16, samplesOut1_tmp_storage1 ); + VARDECL( opus_int16, samplesOut1_tmp_storage2 ); + VARDECL( opus_int16, samplesOut2_tmp ); + opus_int32 MS_pred_Q13[ 2 ] = { 0 }; + opus_int16 *resample_out_ptr; + silk_decoder *psDec = ( silk_decoder * )decState; + silk_decoder_state *channel_state = psDec->channel_state; + opus_int has_side; + opus_int stereo_to_mono; + int delay_stack_alloc; + SAVE_STACK; + + celt_assert( decControl->nChannelsInternal == 1 || decControl->nChannelsInternal == 2 ); + + /**********************************/ + /* Test if first frame in payload */ + /**********************************/ + if( newPacketFlag ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + channel_state[ n ].nFramesDecoded = 0; /* Used to count frames in packet */ + } + } + + /* If Mono -> Stereo transition in bitstream: init state of second channel */ + if( decControl->nChannelsInternal > psDec->nChannelsInternal ) { + ret += silk_init_decoder( &channel_state[ 1 ] ); + } + + stereo_to_mono = decControl->nChannelsInternal == 1 && psDec->nChannelsInternal == 2 && + ( decControl->internalSampleRate == 1000*channel_state[ 0 ].fs_kHz ); + + if( channel_state[ 0 ].nFramesDecoded == 0 ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + opus_int fs_kHz_dec; + if( decControl->payloadSize_ms == 0 ) { + /* Assuming packet loss, use 10 ms */ + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 2; + } else if( decControl->payloadSize_ms == 10 ) { + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 2; + } else if( decControl->payloadSize_ms == 20 ) { + channel_state[ n ].nFramesPerPacket = 1; + channel_state[ n ].nb_subfr = 4; + } else if( decControl->payloadSize_ms == 40 ) { + channel_state[ n ].nFramesPerPacket = 2; + channel_state[ n ].nb_subfr = 4; + } else if( decControl->payloadSize_ms == 60 ) { + channel_state[ n ].nFramesPerPacket = 3; + channel_state[ n ].nb_subfr = 4; + } else { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_DEC_INVALID_FRAME_SIZE; + } + fs_kHz_dec = ( decControl->internalSampleRate >> 10 ) + 1; + if( fs_kHz_dec != 8 && fs_kHz_dec != 12 && fs_kHz_dec != 16 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_DEC_INVALID_SAMPLING_FREQUENCY; + } + ret += silk_decoder_set_fs( &channel_state[ n ], fs_kHz_dec, decControl->API_sampleRate ); + } + } + + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 2 && ( psDec->nChannelsAPI == 1 || psDec->nChannelsInternal == 1 ) ) { + silk_memset( psDec->sStereo.pred_prev_Q13, 0, sizeof( psDec->sStereo.pred_prev_Q13 ) ); + silk_memset( psDec->sStereo.sSide, 0, sizeof( psDec->sStereo.sSide ) ); + silk_memcpy( &channel_state[ 1 ].resampler_state, &channel_state[ 0 ].resampler_state, sizeof( silk_resampler_state_struct ) ); + } + psDec->nChannelsAPI = decControl->nChannelsAPI; + psDec->nChannelsInternal = decControl->nChannelsInternal; + + if( decControl->API_sampleRate > (opus_int32)MAX_API_FS_KHZ * 1000 || decControl->API_sampleRate < 8000 ) { + ret = SILK_DEC_INVALID_SAMPLING_FREQUENCY; + RESTORE_STACK; + return( ret ); + } + + if( lostFlag != FLAG_PACKET_LOST && channel_state[ 0 ].nFramesDecoded == 0 ) { + /* First decoder call for this payload */ + /* Decode VAD flags and LBRR flag */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + for( i = 0; i < channel_state[ n ].nFramesPerPacket; i++ ) { + channel_state[ n ].VAD_flags[ i ] = ec_dec_bit_logp(psRangeDec, 1); + } + channel_state[ n ].LBRR_flag = ec_dec_bit_logp(psRangeDec, 1); + } + /* Decode LBRR flags */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + silk_memset( channel_state[ n ].LBRR_flags, 0, sizeof( channel_state[ n ].LBRR_flags ) ); + if( channel_state[ n ].LBRR_flag ) { + if( channel_state[ n ].nFramesPerPacket == 1 ) { + channel_state[ n ].LBRR_flags[ 0 ] = 1; + } else { + LBRR_symbol = ec_dec_icdf( psRangeDec, silk_LBRR_flags_iCDF_ptr[ channel_state[ n ].nFramesPerPacket - 2 ], 8 ) + 1; + for( i = 0; i < channel_state[ n ].nFramesPerPacket; i++ ) { + channel_state[ n ].LBRR_flags[ i ] = silk_RSHIFT( LBRR_symbol, i ) & 1; + } + } + } + } + + if( lostFlag == FLAG_DECODE_NORMAL ) { + /* Regular decoding: skip all LBRR data */ + for( i = 0; i < channel_state[ 0 ].nFramesPerPacket; i++ ) { + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + if( channel_state[ n ].LBRR_flags[ i ] ) { + opus_int16 pulses[ MAX_FRAME_LENGTH ]; + opus_int condCoding; + + if( decControl->nChannelsInternal == 2 && n == 0 ) { + silk_stereo_decode_pred( psRangeDec, MS_pred_Q13 ); + if( channel_state[ 1 ].LBRR_flags[ i ] == 0 ) { + silk_stereo_decode_mid_only( psRangeDec, &decode_only_middle ); + } + } + /* Use conditional coding if previous frame available */ + if( i > 0 && channel_state[ n ].LBRR_flags[ i - 1 ] ) { + condCoding = CODE_CONDITIONALLY; + } else { + condCoding = CODE_INDEPENDENTLY; + } + silk_decode_indices( &channel_state[ n ], psRangeDec, i, 1, condCoding ); + silk_decode_pulses( psRangeDec, pulses, channel_state[ n ].indices.signalType, + channel_state[ n ].indices.quantOffsetType, channel_state[ n ].frame_length ); + } + } + } + } + } + + /* Get MS predictor index */ + if( decControl->nChannelsInternal == 2 ) { + if( lostFlag == FLAG_DECODE_NORMAL || + ( lostFlag == FLAG_DECODE_LBRR && channel_state[ 0 ].LBRR_flags[ channel_state[ 0 ].nFramesDecoded ] == 1 ) ) + { + silk_stereo_decode_pred( psRangeDec, MS_pred_Q13 ); + /* For LBRR data, decode mid-only flag only if side-channel's LBRR flag is false */ + if( ( lostFlag == FLAG_DECODE_NORMAL && channel_state[ 1 ].VAD_flags[ channel_state[ 0 ].nFramesDecoded ] == 0 ) || + ( lostFlag == FLAG_DECODE_LBRR && channel_state[ 1 ].LBRR_flags[ channel_state[ 0 ].nFramesDecoded ] == 0 ) ) + { + silk_stereo_decode_mid_only( psRangeDec, &decode_only_middle ); + } else { + decode_only_middle = 0; + } + } else { + for( n = 0; n < 2; n++ ) { + MS_pred_Q13[ n ] = psDec->sStereo.pred_prev_Q13[ n ]; + } + } + } + + /* Reset side channel decoder prediction memory for first frame with side coding */ + if( decControl->nChannelsInternal == 2 && decode_only_middle == 0 && psDec->prev_decode_only_middle == 1 ) { + silk_memset( psDec->channel_state[ 1 ].outBuf, 0, sizeof(psDec->channel_state[ 1 ].outBuf) ); + silk_memset( psDec->channel_state[ 1 ].sLPC_Q14_buf, 0, sizeof(psDec->channel_state[ 1 ].sLPC_Q14_buf) ); + psDec->channel_state[ 1 ].lagPrev = 100; + psDec->channel_state[ 1 ].LastGainIndex = 10; + psDec->channel_state[ 1 ].prevSignalType = TYPE_NO_VOICE_ACTIVITY; + psDec->channel_state[ 1 ].first_frame_after_reset = 1; + } + + /* Check if the temp buffer fits into the output PCM buffer. If it fits, + we can delay allocating the temp buffer until after the SILK peak stack + usage. We need to use a < and not a <= because of the two extra samples. */ + delay_stack_alloc = decControl->internalSampleRate*decControl->nChannelsInternal + < decControl->API_sampleRate*decControl->nChannelsAPI; + ALLOC( samplesOut1_tmp_storage1, delay_stack_alloc ? ALLOC_NONE + : decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ), + opus_int16 ); + if ( delay_stack_alloc ) + { + samplesOut1_tmp[ 0 ] = samplesOut; + samplesOut1_tmp[ 1 ] = samplesOut + channel_state[ 0 ].frame_length + 2; + } else { + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage1; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage1 + channel_state[ 0 ].frame_length + 2; + } + + if( lostFlag == FLAG_DECODE_NORMAL ) { + has_side = !decode_only_middle; + } else { + has_side = !psDec->prev_decode_only_middle + || (decControl->nChannelsInternal == 2 && lostFlag == FLAG_DECODE_LBRR && channel_state[1].LBRR_flags[ channel_state[1].nFramesDecoded ] == 1 ); + } + channel_state[ 0 ].sPLC.enable_deep_plc = decControl->enable_deep_plc; + /* Call decoder for one frame */ + for( n = 0; n < decControl->nChannelsInternal; n++ ) { + if( n == 0 || has_side ) { + opus_int FrameIndex; + opus_int condCoding; + + FrameIndex = channel_state[ 0 ].nFramesDecoded - n; + /* Use independent coding if no previous frame available */ + if( FrameIndex <= 0 ) { + condCoding = CODE_INDEPENDENTLY; + } else if( lostFlag == FLAG_DECODE_LBRR ) { + condCoding = channel_state[ n ].LBRR_flags[ FrameIndex - 1 ] ? CODE_CONDITIONALLY : CODE_INDEPENDENTLY; + } else if( n > 0 && psDec->prev_decode_only_middle ) { + /* If we skipped a side frame in this packet, we don't + need LTP scaling; the LTP state is well-defined. */ + condCoding = CODE_INDEPENDENTLY_NO_LTP_SCALING; + } else { + condCoding = CODE_CONDITIONALLY; + } +#ifdef ENABLE_OSCE + if ( channel_state[n].osce.method != decControl->osce_method ) { + osce_reset( &channel_state[n].osce, decControl->osce_method ); + } +#endif + ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding, +#ifdef ENABLE_DEEP_PLC + n == 0 ? lpcnet : NULL, +#endif +#ifdef ENABLE_OSCE + &psDec->osce_model, +#endif + arch); + } else { + silk_memset( &samplesOut1_tmp[ n ][ 2 ], 0, nSamplesOutDec * sizeof( opus_int16 ) ); + } + channel_state[ n ].nFramesDecoded++; + } + + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 2 ) { + /* Convert Mid/Side to Left/Right */ + silk_stereo_MS_to_LR( &psDec->sStereo, samplesOut1_tmp[ 0 ], samplesOut1_tmp[ 1 ], MS_pred_Q13, channel_state[ 0 ].fs_kHz, nSamplesOutDec ); + } else { + /* Buffering */ + silk_memcpy( samplesOut1_tmp[ 0 ], psDec->sStereo.sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( psDec->sStereo.sMid, &samplesOut1_tmp[ 0 ][ nSamplesOutDec ], 2 * sizeof( opus_int16 ) ); + } + + /* Number of output samples */ + *nSamplesOut = silk_DIV32( nSamplesOutDec * decControl->API_sampleRate, silk_SMULBB( channel_state[ 0 ].fs_kHz, 1000 ) ); + + /* Set up pointers to temp buffers */ + ALLOC( samplesOut2_tmp, + decControl->nChannelsAPI == 2 ? *nSamplesOut : ALLOC_NONE, opus_int16 ); + if( decControl->nChannelsAPI == 2 ) { + resample_out_ptr = samplesOut2_tmp; + } else { + resample_out_ptr = samplesOut; + } + + ALLOC( samplesOut1_tmp_storage2, delay_stack_alloc + ? decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ) + : ALLOC_NONE, + opus_int16 ); + if ( delay_stack_alloc ) { + OPUS_COPY(samplesOut1_tmp_storage2, samplesOut, decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2)); + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage2; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage2 + channel_state[ 0 ].frame_length + 2; + } + for( n = 0; n < silk_min( decControl->nChannelsAPI, decControl->nChannelsInternal ); n++ ) { + + /* Resample decoded signal to API_sampleRate */ + ret += silk_resampler( &channel_state[ n ].resampler_state, resample_out_ptr, &samplesOut1_tmp[ n ][ 1 ], nSamplesOutDec ); + + /* Interleave if stereo output and stereo stream */ + if( decControl->nChannelsAPI == 2 ) { + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ n + 2 * i ] = resample_out_ptr[ i ]; + } + } + } + + /* Create two channel output from mono stream */ + if( decControl->nChannelsAPI == 2 && decControl->nChannelsInternal == 1 ) { + if ( stereo_to_mono ){ + /* Resample right channel for newly collapsed stereo just in case + we weren't doing collapsing when switching to mono */ + ret += silk_resampler( &channel_state[ 1 ].resampler_state, resample_out_ptr, &samplesOut1_tmp[ 0 ][ 1 ], nSamplesOutDec ); + + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ 1 + 2 * i ] = resample_out_ptr[ i ]; + } + } else { + for( i = 0; i < *nSamplesOut; i++ ) { + samplesOut[ 1 + 2 * i ] = samplesOut[ 0 + 2 * i ]; + } + } + } + + /* Export pitch lag, measured at 48 kHz sampling rate */ + if( channel_state[ 0 ].prevSignalType == TYPE_VOICED ) { + int mult_tab[ 3 ] = { 6, 4, 3 }; + decControl->prevPitchLag = channel_state[ 0 ].lagPrev * mult_tab[ ( channel_state[ 0 ].fs_kHz - 8 ) >> 2 ]; + } else { + decControl->prevPitchLag = 0; + } + + if( lostFlag == FLAG_PACKET_LOST ) { + /* On packet loss, remove the gain clamping to prevent having the energy "bounce back" + if we lose packets when the energy is going down */ + for ( i = 0; i < psDec->nChannelsInternal; i++ ) + psDec->channel_state[ i ].LastGainIndex = 10; + } else { + psDec->prev_decode_only_middle = decode_only_middle; + } + RESTORE_STACK; + return ret; +} + +#if 0 +/* Getting table of contents for a packet */ +opus_int silk_get_TOC( + const opus_uint8 *payload, /* I Payload data */ + const opus_int nBytesIn, /* I Number of input bytes */ + const opus_int nFramesPerPayload, /* I Number of SILK frames per payload */ + silk_TOC_struct *Silk_TOC /* O Type of content */ +) +{ + opus_int i, flags, ret = SILK_NO_ERROR; + + if( nBytesIn < 1 ) { + return -1; + } + if( nFramesPerPayload < 0 || nFramesPerPayload > 3 ) { + return -1; + } + + silk_memset( Silk_TOC, 0, sizeof( *Silk_TOC ) ); + + /* For stereo, extract the flags for the mid channel */ + flags = silk_RSHIFT( payload[ 0 ], 7 - nFramesPerPayload ) & ( silk_LSHIFT( 1, nFramesPerPayload + 1 ) - 1 ); + + Silk_TOC->inbandFECFlag = flags & 1; + for( i = nFramesPerPayload - 1; i >= 0 ; i-- ) { + flags = silk_RSHIFT( flags, 1 ); + Silk_TOC->VADFlags[ i ] = flags & 1; + Silk_TOC->VADFlag |= flags & 1; + } + + return ret; +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_core.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_core.c new file mode 100644 index 0000000000..1c352a6522 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_core.c @@ -0,0 +1,237 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/**********************************************************/ +/* Core decoder. Performs inverse NSQ operation LTP + LPC */ +/**********************************************************/ +void silk_decode_core( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I Decoder control */ + opus_int16 xq[], /* O Decoded speech */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, k, lag = 0, start_idx, sLTP_buf_idx, NLSF_interpolation_flag, signalType; + opus_int16 *A_Q12, *B_Q14, *pxq, A_Q12_tmp[ MAX_LPC_ORDER ]; + VARDECL( opus_int16, sLTP ); + VARDECL( opus_int32, sLTP_Q15 ); + opus_int32 LTP_pred_Q13, LPC_pred_Q10, Gain_Q10, inv_gain_Q31, gain_adj_Q16, rand_seed, offset_Q10; + opus_int32 *pred_lag_ptr, *pexc_Q14, *pres_Q14; + VARDECL( opus_int32, res_Q14 ); + VARDECL( opus_int32, sLPC_Q14 ); + SAVE_STACK; + + silk_assert( psDec->prev_gain_Q16 != 0 ); + + ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); + ALLOC( sLTP_Q15, psDec->ltp_mem_length + psDec->frame_length, opus_int32 ); + ALLOC( res_Q14, psDec->subfr_length, opus_int32 ); + ALLOC( sLPC_Q14, psDec->subfr_length + MAX_LPC_ORDER, opus_int32 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psDec->indices.signalType >> 1 ][ psDec->indices.quantOffsetType ]; + + if( psDec->indices.NLSFInterpCoef_Q2 < 1 << 2 ) { + NLSF_interpolation_flag = 1; + } else { + NLSF_interpolation_flag = 0; + } + + /* Decode excitation */ + rand_seed = psDec->indices.Seed; + for( i = 0; i < psDec->frame_length; i++ ) { + rand_seed = silk_RAND( rand_seed ); + psDec->exc_Q14[ i ] = silk_LSHIFT( (opus_int32)pulses[ i ], 14 ); + if( psDec->exc_Q14[ i ] > 0 ) { + psDec->exc_Q14[ i ] -= QUANT_LEVEL_ADJUST_Q10 << 4; + } else + if( psDec->exc_Q14[ i ] < 0 ) { + psDec->exc_Q14[ i ] += QUANT_LEVEL_ADJUST_Q10 << 4; + } + psDec->exc_Q14[ i ] += offset_Q10 << 4; + if( rand_seed < 0 ) { + psDec->exc_Q14[ i ] = -psDec->exc_Q14[ i ]; + } + + rand_seed = silk_ADD32_ovflw( rand_seed, pulses[ i ] ); + } + + /* Copy LPC state */ + silk_memcpy( sLPC_Q14, psDec->sLPC_Q14_buf, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + + pexc_Q14 = psDec->exc_Q14; + pxq = xq; + sLTP_buf_idx = psDec->ltp_mem_length; + /* Loop over subframes */ + for( k = 0; k < psDec->nb_subfr; k++ ) { + pres_Q14 = res_Q14; + A_Q12 = psDecCtrl->PredCoef_Q12[ k >> 1 ]; + + /* Preload LPC coeficients to array on stack. Gives small performance gain */ + silk_memcpy( A_Q12_tmp, A_Q12, psDec->LPC_order * sizeof( opus_int16 ) ); + B_Q14 = &psDecCtrl->LTPCoef_Q14[ k * LTP_ORDER ]; + signalType = psDec->indices.signalType; + + Gain_Q10 = silk_RSHIFT( psDecCtrl->Gains_Q16[ k ], 6 ); + inv_gain_Q31 = silk_INVERSE32_varQ( psDecCtrl->Gains_Q16[ k ], 47 ); + + /* Calculate gain adjustment factor */ + if( psDecCtrl->Gains_Q16[ k ] != psDec->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( psDec->prev_gain_Q16, psDecCtrl->Gains_Q16[ k ], 16 ); + + /* Scale short term state */ + for( i = 0; i < MAX_LPC_ORDER; i++ ) { + sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, sLPC_Q14[ i ] ); + } + } else { + gain_adj_Q16 = (opus_int32)1 << 16; + } + + /* Save inv_gain */ + silk_assert( inv_gain_Q31 != 0 ); + psDec->prev_gain_Q16 = psDecCtrl->Gains_Q16[ k ]; + + /* Avoid abrupt transition from voiced PLC to unvoiced normal decoding */ + if( psDec->lossCnt && psDec->prevSignalType == TYPE_VOICED && + psDec->indices.signalType != TYPE_VOICED && k < MAX_NB_SUBFR/2 ) { + + silk_memset( B_Q14, 0, LTP_ORDER * sizeof( opus_int16 ) ); + B_Q14[ LTP_ORDER/2 ] = SILK_FIX_CONST( 0.25, 14 ); + + signalType = TYPE_VOICED; + psDecCtrl->pitchL[ k ] = psDec->lagPrev; + } + + if( signalType == TYPE_VOICED ) { + /* Voiced */ + lag = psDecCtrl->pitchL[ k ]; + + /* Re-whitening */ + if( k == 0 || ( k == 2 && NLSF_interpolation_flag ) ) { + /* Rewhiten with new A coefs */ + start_idx = psDec->ltp_mem_length - lag - psDec->LPC_order - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + if( k == 2 ) { + silk_memcpy( &psDec->outBuf[ psDec->ltp_mem_length ], xq, 2 * psDec->subfr_length * sizeof( opus_int16 ) ); + } + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &psDec->outBuf[ start_idx + k * psDec->subfr_length ], + A_Q12, psDec->ltp_mem_length - start_idx, psDec->LPC_order, arch ); + + /* After rewhitening the LTP state is unscaled */ + if( k == 0 ) { + /* Do LTP downscaling to reduce inter-packet dependency */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, psDecCtrl->LTP_scale_Q14 ), 2 ); + } + for( i = 0; i < lag + LTP_ORDER/2; i++ ) { + sLTP_Q15[ sLTP_buf_idx - i - 1 ] = silk_SMULWB( inv_gain_Q31, sLTP[ psDec->ltp_mem_length - i - 1 ] ); + } + } else { + /* Update LTP state when Gain changes */ + if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + for( i = 0; i < lag + LTP_ORDER/2; i++ ) { + sLTP_Q15[ sLTP_buf_idx - i - 1 ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ sLTP_buf_idx - i - 1 ] ); + } + } + } + } + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Set up pointer */ + pred_lag_ptr = &sLTP_Q15[ sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ 0 ], B_Q14[ 0 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -1 ], B_Q14[ 1 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -2 ], B_Q14[ 2 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -3 ], B_Q14[ 3 ] ); + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], B_Q14[ 4 ] ); + pred_lag_ptr++; + + /* Generate LPC excitation */ + pres_Q14[ i ] = silk_ADD_LSHIFT32( pexc_Q14[ i ], LTP_pred_Q13, 1 ); + + /* Update states */ + sLTP_Q15[ sLTP_buf_idx ] = silk_LSHIFT( pres_Q14[ i ], 1 ); + sLTP_buf_idx++; + } + } else { + pres_Q14 = pexc_Q14; + } + + for( i = 0; i < psDec->subfr_length; i++ ) { + /* Short-term prediction */ + celt_assert( psDec->LPC_order == 10 || psDec->LPC_order == 16 ); + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 1 ], A_Q12_tmp[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 2 ], A_Q12_tmp[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 3 ], A_Q12_tmp[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 4 ], A_Q12_tmp[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 5 ], A_Q12_tmp[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 6 ], A_Q12_tmp[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 7 ], A_Q12_tmp[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 8 ], A_Q12_tmp[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 9 ], A_Q12_tmp[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 10 ], A_Q12_tmp[ 9 ] ); + if( psDec->LPC_order == 16 ) { + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 11 ], A_Q12_tmp[ 10 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 12 ], A_Q12_tmp[ 11 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 13 ], A_Q12_tmp[ 12 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 14 ], A_Q12_tmp[ 13 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 15 ], A_Q12_tmp[ 14 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, sLPC_Q14[ MAX_LPC_ORDER + i - 16 ], A_Q12_tmp[ 15 ] ); + } + + /* Add prediction to LPC excitation */ + sLPC_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( pres_Q14[ i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); + + /* Scale with gain */ + pxq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14[ MAX_LPC_ORDER + i ], Gain_Q10 ), 8 ) ); + } + + /* Update LPC filter state */ + silk_memcpy( sLPC_Q14, &sLPC_Q14[ psDec->subfr_length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + pexc_Q14 += psDec->subfr_length; + pxq += psDec->subfr_length; + } + + /* Save LPC state */ + silk_memcpy( psDec->sLPC_Q14_buf, sLPC_Q14, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_frame.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_frame.c new file mode 100644 index 0000000000..9bc4ca2b0e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_frame.c @@ -0,0 +1,169 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" +#include "PLC.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +/****************/ +/* Decode frame */ +/****************/ +opus_int silk_decode_frame( + silk_decoder_state *psDec, /* I/O Pointer to Silk decoder state */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pOut[], /* O Pointer to output speech frame */ + opus_int32 *pN, /* O Pointer to size of output frame */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int condCoding, /* I The type of conditional coding to use */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif +#ifdef ENABLE_OSCE + OSCEModel *osce_model, +#endif + int arch /* I Run-time architecture */ +) +{ + VARDECL( silk_decoder_control, psDecCtrl ); + opus_int L, mv_len, ret = 0; + SAVE_STACK; + + L = psDec->frame_length; + ALLOC( psDecCtrl, 1, silk_decoder_control ); + psDecCtrl->LTP_scale_Q14 = 0; + + /* Safety checks */ + celt_assert( L > 0 && L <= MAX_FRAME_LENGTH ); + + if( lostFlag == FLAG_DECODE_NORMAL || + ( lostFlag == FLAG_DECODE_LBRR && psDec->LBRR_flags[ psDec->nFramesDecoded ] == 1 ) ) + { + VARDECL( opus_int16, pulses ); +#ifdef ENABLE_OSCE + opus_int32 ec_start; + ec_start = ec_tell(psRangeDec); +#endif + ALLOC( pulses, (L + SHELL_CODEC_FRAME_LENGTH - 1) & + ~(SHELL_CODEC_FRAME_LENGTH - 1), opus_int16 ); + /*********************************************/ + /* Decode quantization indices of side info */ + /*********************************************/ + silk_decode_indices( psDec, psRangeDec, psDec->nFramesDecoded, lostFlag, condCoding ); + + /*********************************************/ + /* Decode quantization indices of excitation */ + /*********************************************/ + silk_decode_pulses( psRangeDec, pulses, psDec->indices.signalType, + psDec->indices.quantOffsetType, psDec->frame_length ); + + /********************************************/ + /* Decode parameters and pulse signal */ + /********************************************/ + silk_decode_parameters( psDec, psDecCtrl, condCoding ); + + /********************************************************/ + /* Run inverse NSQ */ + /********************************************************/ + silk_decode_core( psDec, psDecCtrl, pOut, pulses, arch ); + + /*************************/ + /* Update output buffer. */ + /*************************/ + celt_assert( psDec->ltp_mem_length >= psDec->frame_length ); + mv_len = psDec->ltp_mem_length - psDec->frame_length; + silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) ); + silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) ); + +#ifdef ENABLE_OSCE + /********************************************************/ + /* Run SILK enhancer */ + /********************************************************/ + osce_enhance_frame( osce_model, psDec, psDecCtrl, pOut, ec_tell(psRangeDec) - ec_start, arch ); +#endif + + /********************************************************/ + /* Update PLC state */ + /********************************************************/ + silk_PLC( psDec, psDecCtrl, pOut, 0, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + + psDec->lossCnt = 0; + psDec->prevSignalType = psDec->indices.signalType; + celt_assert( psDec->prevSignalType >= 0 && psDec->prevSignalType <= 2 ); + + /* A frame has been decoded without errors */ + psDec->first_frame_after_reset = 0; + } else { + /* Handle packet loss by extrapolation */ + silk_PLC( psDec, psDecCtrl, pOut, 1, +#ifdef ENABLE_DEEP_PLC + lpcnet, +#endif + arch ); + +#ifdef ENABLE_OSCE + osce_reset( &psDec->osce, psDec->osce.method ); +#endif + /*************************/ + /* Update output buffer. */ + /*************************/ + celt_assert( psDec->ltp_mem_length >= psDec->frame_length ); + mv_len = psDec->ltp_mem_length - psDec->frame_length; + silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) ); + silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) ); + } + + /************************************************/ + /* Comfort noise generation / estimation */ + /************************************************/ + silk_CNG( psDec, psDecCtrl, pOut, L ); + + /****************************************************************/ + /* Ensure smooth connection of extrapolated and good frames */ + /****************************************************************/ + silk_PLC_glue_frames( psDec, pOut, L ); + + /* Update some decoder state variables */ + psDec->lagPrev = psDecCtrl->pitchL[ psDec->nb_subfr - 1 ]; + + /* Set output frame length */ + *pN = L; + + RESTORE_STACK; + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_indices.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_indices.c new file mode 100644 index 0000000000..0bb4a997a5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_indices.c @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode side-information parameters from payload */ +void silk_decode_indices( + silk_decoder_state *psDec, /* I/O State */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int decode_LBRR, /* I Flag indicating LBRR data is being decoded */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, Ix; + opus_int decode_absolute_lagIndex, delta_lagIndex; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + + /*******************************************/ + /* Decode signal type and quantizer offset */ + /*******************************************/ + if( decode_LBRR || psDec->VAD_flags[ FrameIndex ] ) { + Ix = ec_dec_icdf( psRangeDec, silk_type_offset_VAD_iCDF, 8 ) + 2; + } else { + Ix = ec_dec_icdf( psRangeDec, silk_type_offset_no_VAD_iCDF, 8 ); + } + psDec->indices.signalType = (opus_int8)silk_RSHIFT( Ix, 1 ); + psDec->indices.quantOffsetType = (opus_int8)( Ix & 1 ); + + /****************/ + /* Decode gains */ + /****************/ + /* First subframe */ + if( condCoding == CODE_CONDITIONALLY ) { + /* Conditional coding */ + psDec->indices.GainsIndices[ 0 ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_delta_gain_iCDF, 8 ); + } else { + /* Independent coding, in two stages: MSB bits followed by 3 LSBs */ + psDec->indices.GainsIndices[ 0 ] = (opus_int8)silk_LSHIFT( ec_dec_icdf( psRangeDec, silk_gain_iCDF[ psDec->indices.signalType ], 8 ), 3 ); + psDec->indices.GainsIndices[ 0 ] += (opus_int8)ec_dec_icdf( psRangeDec, silk_uniform8_iCDF, 8 ); + } + + /* Remaining subframes */ + for( i = 1; i < psDec->nb_subfr; i++ ) { + psDec->indices.GainsIndices[ i ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_delta_gain_iCDF, 8 ); + } + + /**********************/ + /* Decode LSF Indices */ + /**********************/ + psDec->indices.NLSFIndices[ 0 ] = (opus_int8)ec_dec_icdf( psRangeDec, &psDec->psNLSF_CB->CB1_iCDF[ ( psDec->indices.signalType >> 1 ) * psDec->psNLSF_CB->nVectors ], 8 ); + silk_NLSF_unpack( ec_ix, pred_Q8, psDec->psNLSF_CB, psDec->indices.NLSFIndices[ 0 ] ); + celt_assert( psDec->psNLSF_CB->order == psDec->LPC_order ); + for( i = 0; i < psDec->psNLSF_CB->order; i++ ) { + Ix = ec_dec_icdf( psRangeDec, &psDec->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + if( Ix == 0 ) { + Ix -= ec_dec_icdf( psRangeDec, silk_NLSF_EXT_iCDF, 8 ); + } else if( Ix == 2 * NLSF_QUANT_MAX_AMPLITUDE ) { + Ix += ec_dec_icdf( psRangeDec, silk_NLSF_EXT_iCDF, 8 ); + } + psDec->indices.NLSFIndices[ i+1 ] = (opus_int8)( Ix - NLSF_QUANT_MAX_AMPLITUDE ); + } + + /* Decode LSF interpolation factor */ + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->indices.NLSFInterpCoef_Q2 = (opus_int8)ec_dec_icdf( psRangeDec, silk_NLSF_interpolation_factor_iCDF, 8 ); + } else { + psDec->indices.NLSFInterpCoef_Q2 = 4; + } + + if( psDec->indices.signalType == TYPE_VOICED ) + { + /*********************/ + /* Decode pitch lags */ + /*********************/ + /* Get lag index */ + decode_absolute_lagIndex = 1; + if( condCoding == CODE_CONDITIONALLY && psDec->ec_prevSignalType == TYPE_VOICED ) { + /* Decode Delta index */ + delta_lagIndex = (opus_int16)ec_dec_icdf( psRangeDec, silk_pitch_delta_iCDF, 8 ); + if( delta_lagIndex > 0 ) { + delta_lagIndex = delta_lagIndex - 9; + psDec->indices.lagIndex = (opus_int16)( psDec->ec_prevLagIndex + delta_lagIndex ); + decode_absolute_lagIndex = 0; + } + } + if( decode_absolute_lagIndex ) { + /* Absolute decoding */ + psDec->indices.lagIndex = (opus_int16)ec_dec_icdf( psRangeDec, silk_pitch_lag_iCDF, 8 ) * silk_RSHIFT( psDec->fs_kHz, 1 ); + psDec->indices.lagIndex += (opus_int16)ec_dec_icdf( psRangeDec, psDec->pitch_lag_low_bits_iCDF, 8 ); + } + psDec->ec_prevLagIndex = psDec->indices.lagIndex; + + /* Get countour index */ + psDec->indices.contourIndex = (opus_int8)ec_dec_icdf( psRangeDec, psDec->pitch_contour_iCDF, 8 ); + + /********************/ + /* Decode LTP gains */ + /********************/ + /* Decode PERIndex value */ + psDec->indices.PERIndex = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTP_per_index_iCDF, 8 ); + + for( k = 0; k < psDec->nb_subfr; k++ ) { + psDec->indices.LTPIndex[ k ] = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTP_gain_iCDF_ptrs[ psDec->indices.PERIndex ], 8 ); + } + + /**********************/ + /* Decode LTP scaling */ + /**********************/ + if( condCoding == CODE_INDEPENDENTLY ) { + psDec->indices.LTP_scaleIndex = (opus_int8)ec_dec_icdf( psRangeDec, silk_LTPscale_iCDF, 8 ); + } else { + psDec->indices.LTP_scaleIndex = 0; + } + } + psDec->ec_prevSignalType = psDec->indices.signalType; + + /***************/ + /* Decode seed */ + /***************/ + psDec->indices.Seed = (opus_int8)ec_dec_icdf( psRangeDec, silk_uniform4_iCDF, 8 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_parameters.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_parameters.c new file mode 100644 index 0000000000..a56a409858 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_parameters.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode parameters from payload */ +void silk_decode_parameters( + silk_decoder_state *psDec, /* I/O State */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, Ix; + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], pNLSF0_Q15[ MAX_LPC_ORDER ]; + const opus_int8 *cbk_ptr_Q7; + + /* Dequant Gains */ + silk_gains_dequant( psDecCtrl->Gains_Q16, psDec->indices.GainsIndices, + &psDec->LastGainIndex, condCoding == CODE_CONDITIONALLY, psDec->nb_subfr ); + + /****************/ + /* Decode NLSFs */ + /****************/ + silk_NLSF_decode( pNLSF_Q15, psDec->indices.NLSFIndices, psDec->psNLSF_CB ); + + /* Convert NLSF parameters to AR prediction filter coefficients */ + silk_NLSF2A( psDecCtrl->PredCoef_Q12[ 1 ], pNLSF_Q15, psDec->LPC_order, psDec->arch ); + + /* If just reset, e.g., because internal Fs changed, do not allow interpolation */ + /* improves the case of packet loss in the first frame after a switch */ + if( psDec->first_frame_after_reset == 1 ) { + psDec->indices.NLSFInterpCoef_Q2 = 4; + } + + if( psDec->indices.NLSFInterpCoef_Q2 < 4 ) { + /* Calculation of the interpolated NLSF0 vector from the interpolation factor, */ + /* the previous NLSF1, and the current NLSF1 */ + for( i = 0; i < psDec->LPC_order; i++ ) { + pNLSF0_Q15[ i ] = psDec->prevNLSF_Q15[ i ] + silk_RSHIFT( silk_MUL( psDec->indices.NLSFInterpCoef_Q2, + pNLSF_Q15[ i ] - psDec->prevNLSF_Q15[ i ] ), 2 ); + } + + /* Convert NLSF parameters to AR prediction filter coefficients */ + silk_NLSF2A( psDecCtrl->PredCoef_Q12[ 0 ], pNLSF0_Q15, psDec->LPC_order, psDec->arch ); + } else { + /* Copy LPC coefficients for first half from second half */ + silk_memcpy( psDecCtrl->PredCoef_Q12[ 0 ], psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order * sizeof( opus_int16 ) ); + } + + silk_memcpy( psDec->prevNLSF_Q15, pNLSF_Q15, psDec->LPC_order * sizeof( opus_int16 ) ); + + /* After a packet loss do BWE of LPC coefs */ + if( psDec->lossCnt ) { + silk_bwexpander( psDecCtrl->PredCoef_Q12[ 0 ], psDec->LPC_order, BWE_AFTER_LOSS_Q16 ); + silk_bwexpander( psDecCtrl->PredCoef_Q12[ 1 ], psDec->LPC_order, BWE_AFTER_LOSS_Q16 ); + } + + if( psDec->indices.signalType == TYPE_VOICED ) { + /*********************/ + /* Decode pitch lags */ + /*********************/ + + /* Decode pitch values */ + silk_decode_pitch( psDec->indices.lagIndex, psDec->indices.contourIndex, psDecCtrl->pitchL, psDec->fs_kHz, psDec->nb_subfr ); + + /* Decode Codebook Index */ + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ psDec->indices.PERIndex ]; /* set pointer to start of codebook */ + + for( k = 0; k < psDec->nb_subfr; k++ ) { + Ix = psDec->indices.LTPIndex[ k ]; + for( i = 0; i < LTP_ORDER; i++ ) { + psDecCtrl->LTPCoef_Q14[ k * LTP_ORDER + i ] = silk_LSHIFT( cbk_ptr_Q7[ Ix * LTP_ORDER + i ], 7 ); + } + } + + /**********************/ + /* Decode LTP scaling */ + /**********************/ + Ix = psDec->indices.LTP_scaleIndex; + psDecCtrl->LTP_scale_Q14 = silk_LTPScales_table_Q14[ Ix ]; + } else { + silk_memset( psDecCtrl->pitchL, 0, psDec->nb_subfr * sizeof( opus_int ) ); + silk_memset( psDecCtrl->LTPCoef_Q14, 0, LTP_ORDER * psDec->nb_subfr * sizeof( opus_int16 ) ); + psDec->indices.PERIndex = 0; + psDecCtrl->LTP_scale_Q14 = 0; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pitch.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pitch.c new file mode 100644 index 0000000000..fd1b6bf551 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pitch.c @@ -0,0 +1,77 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*********************************************************** +* Pitch analyser function +********************************************************** */ +#include "SigProc_FIX.h" +#include "pitch_est_defines.h" + +void silk_decode_pitch( + opus_int16 lagIndex, /* I */ + opus_int8 contourIndex, /* O */ + opus_int pitch_lags[], /* O 4 pitch values */ + const opus_int Fs_kHz, /* I sampling frequency (kHz) */ + const opus_int nb_subfr /* I number of sub frames */ +) +{ + opus_int lag, k, min_lag, max_lag, cbk_size; + const opus_int8 *Lag_CB_ptr; + + if( Fs_kHz == 8 ) { + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_CB_ptr = &silk_CB_lags_stage2[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE2_EXT; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1 ); + Lag_CB_ptr = &silk_CB_lags_stage2_10_ms[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE2_10MS; + } + } else { + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1 ); + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + } + + min_lag = silk_SMULBB( PE_MIN_LAG_MS, Fs_kHz ); + max_lag = silk_SMULBB( PE_MAX_LAG_MS, Fs_kHz ); + lag = min_lag + lagIndex; + + for( k = 0; k < nb_subfr; k++ ) { + pitch_lags[ k ] = lag + matrix_ptr( Lag_CB_ptr, k, contourIndex, cbk_size ); + pitch_lags[ k ] = silk_LIMIT( pitch_lags[ k ], min_lag, max_lag ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pulses.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pulses.c new file mode 100644 index 0000000000..a56d2d3074 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decode_pulses.c @@ -0,0 +1,115 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/*********************************************/ +/* Decode quantization indices of excitation */ +/*********************************************/ +void silk_decode_pulses( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* O Excitation signal */ + const opus_int signalType, /* I Sigtype */ + const opus_int quantOffsetType, /* I quantOffsetType */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int i, j, k, iter, abs_q, nLS, RateLevelIndex; + opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ], nLshifts[ MAX_NB_SHELL_BLOCKS ]; + opus_int16 *pulses_ptr; + const opus_uint8 *cdf_ptr; + + /*********************/ + /* Decode rate level */ + /*********************/ + RateLevelIndex = ec_dec_icdf( psRangeDec, silk_rate_levels_iCDF[ signalType >> 1 ], 8 ); + + /* Calculate number of shell blocks */ + silk_assert( 1 << LOG2_SHELL_CODEC_FRAME_LENGTH == SHELL_CODEC_FRAME_LENGTH ); + iter = silk_RSHIFT( frame_length, LOG2_SHELL_CODEC_FRAME_LENGTH ); + if( iter * SHELL_CODEC_FRAME_LENGTH < frame_length ) { + celt_assert( frame_length == 12 * 10 ); /* Make sure only happens for 10 ms @ 12 kHz */ + iter++; + } + + /***************************************************/ + /* Sum-Weighted-Pulses Decoding */ + /***************************************************/ + cdf_ptr = silk_pulses_per_block_iCDF[ RateLevelIndex ]; + for( i = 0; i < iter; i++ ) { + nLshifts[ i ] = 0; + sum_pulses[ i ] = ec_dec_icdf( psRangeDec, cdf_ptr, 8 ); + + /* LSB indication */ + while( sum_pulses[ i ] == SILK_MAX_PULSES + 1 ) { + nLshifts[ i ]++; + /* When we've already got 10 LSBs, we shift the table to not allow (SILK_MAX_PULSES + 1) */ + sum_pulses[ i ] = ec_dec_icdf( psRangeDec, + silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1] + ( nLshifts[ i ] == 10 ), 8 ); + } + } + + /***************************************************/ + /* Shell decoding */ + /***************************************************/ + for( i = 0; i < iter; i++ ) { + if( sum_pulses[ i ] > 0 ) { + silk_shell_decoder( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], psRangeDec, sum_pulses[ i ] ); + } else { + silk_memset( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof( pulses[0] ) ); + } + } + + /***************************************************/ + /* LSB Decoding */ + /***************************************************/ + for( i = 0; i < iter; i++ ) { + if( nLshifts[ i ] > 0 ) { + nLS = nLshifts[ i ]; + pulses_ptr = &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ]; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_q = pulses_ptr[ k ]; + for( j = 0; j < nLS; j++ ) { + abs_q = silk_LSHIFT( abs_q, 1 ); + abs_q += ec_dec_icdf( psRangeDec, silk_lsb_iCDF, 8 ); + } + pulses_ptr[ k ] = abs_q; + } + /* Mark the number of pulses non-zero for sign decoding. */ + sum_pulses[ i ] |= nLS << 5; + } + } + + /****************************************/ + /* Decode and add signs to pulse signal */ + /****************************************/ + silk_decode_signs( psRangeDec, pulses, frame_length, signalType, quantOffsetType, sum_pulses ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decoder_set_fs.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decoder_set_fs.c new file mode 100644 index 0000000000..d9a13d0f0c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/decoder_set_fs.c @@ -0,0 +1,108 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Set decoder sampling rate */ +opus_int silk_decoder_set_fs( + silk_decoder_state *psDec, /* I/O Decoder state pointer */ + opus_int fs_kHz, /* I Sampling frequency (kHz) */ + opus_int32 fs_API_Hz /* I API Sampling frequency (Hz) */ +) +{ + opus_int frame_length, ret = 0; + + celt_assert( fs_kHz == 8 || fs_kHz == 12 || fs_kHz == 16 ); + celt_assert( psDec->nb_subfr == MAX_NB_SUBFR || psDec->nb_subfr == MAX_NB_SUBFR/2 ); + + /* New (sub)frame length */ + psDec->subfr_length = silk_SMULBB( SUB_FRAME_LENGTH_MS, fs_kHz ); + frame_length = silk_SMULBB( psDec->nb_subfr, psDec->subfr_length ); + + /* Initialize resampler when switching internal or external sampling frequency */ + if( psDec->fs_kHz != fs_kHz || psDec->fs_API_hz != fs_API_Hz ) { + /* Initialize the resampler for dec_API.c preparing resampling from fs_kHz to API_fs_Hz */ + ret += silk_resampler_init( &psDec->resampler_state, silk_SMULBB( fs_kHz, 1000 ), fs_API_Hz, 0 ); + + psDec->fs_API_hz = fs_API_Hz; + } + + if( psDec->fs_kHz != fs_kHz || frame_length != psDec->frame_length ) { + if( fs_kHz == 8 ) { + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->pitch_contour_iCDF = silk_pitch_contour_NB_iCDF; + } else { + psDec->pitch_contour_iCDF = silk_pitch_contour_10_ms_NB_iCDF; + } + } else { + if( psDec->nb_subfr == MAX_NB_SUBFR ) { + psDec->pitch_contour_iCDF = silk_pitch_contour_iCDF; + } else { + psDec->pitch_contour_iCDF = silk_pitch_contour_10_ms_iCDF; + } + } + if( psDec->fs_kHz != fs_kHz ) { + psDec->ltp_mem_length = silk_SMULBB( LTP_MEM_LENGTH_MS, fs_kHz ); + if( fs_kHz == 8 || fs_kHz == 12 ) { + psDec->LPC_order = MIN_LPC_ORDER; + psDec->psNLSF_CB = &silk_NLSF_CB_NB_MB; + } else { + psDec->LPC_order = MAX_LPC_ORDER; + psDec->psNLSF_CB = &silk_NLSF_CB_WB; + } + if( fs_kHz == 16 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform8_iCDF; + } else if( fs_kHz == 12 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform6_iCDF; + } else if( fs_kHz == 8 ) { + psDec->pitch_lag_low_bits_iCDF = silk_uniform4_iCDF; + } else { + /* unsupported sampling rate */ + celt_assert( 0 ); + } + psDec->first_frame_after_reset = 1; + psDec->lagPrev = 100; + psDec->LastGainIndex = 10; + psDec->prevSignalType = TYPE_NO_VOICE_ACTIVITY; + silk_memset( psDec->outBuf, 0, sizeof(psDec->outBuf)); + silk_memset( psDec->sLPC_Q14_buf, 0, sizeof(psDec->sLPC_Q14_buf) ); + } + + psDec->fs_kHz = fs_kHz; + psDec->frame_length = frame_length; + } + + /* Check that settings are valid */ + celt_assert( psDec->frame_length > 0 && psDec->frame_length <= MAX_FRAME_LENGTH ); + + return ret; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/define.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/define.h new file mode 100644 index 0000000000..491c86f33e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/define.h @@ -0,0 +1,235 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_DEFINE_H +#define SILK_DEFINE_H + +#include "errors.h" +#include "typedef.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Max number of encoder channels (1/2) */ +#define ENCODER_NUM_CHANNELS 2 +/* Number of decoder channels (1/2) */ +#define DECODER_NUM_CHANNELS 2 + +#define MAX_FRAMES_PER_PACKET 3 + +/* Limits on bitrate */ +#define MIN_TARGET_RATE_BPS 5000 +#define MAX_TARGET_RATE_BPS 80000 + +/* LBRR thresholds */ +#define LBRR_NB_MIN_RATE_BPS 12000 +#define LBRR_MB_MIN_RATE_BPS 14000 +#define LBRR_WB_MIN_RATE_BPS 16000 + +/* DTX settings */ +#define NB_SPEECH_FRAMES_BEFORE_DTX 10 /* eq 200 ms */ +#define MAX_CONSECUTIVE_DTX 20 /* eq 400 ms */ +#define DTX_ACTIVITY_THRESHOLD 0.1f + +/* VAD decision */ +#define VAD_NO_DECISION -1 +#define VAD_NO_ACTIVITY 0 +#define VAD_ACTIVITY 1 + +/* Maximum sampling frequency */ +#define MAX_FS_KHZ 16 +#define MAX_API_FS_KHZ 48 + +/* Signal types */ +#define TYPE_NO_VOICE_ACTIVITY 0 +#define TYPE_UNVOICED 1 +#define TYPE_VOICED 2 + +/* Conditional coding types */ +#define CODE_INDEPENDENTLY 0 +#define CODE_INDEPENDENTLY_NO_LTP_SCALING 1 +#define CODE_CONDITIONALLY 2 + +/* Settings for stereo processing */ +#define STEREO_QUANT_TAB_SIZE 16 +#define STEREO_QUANT_SUB_STEPS 5 +#define STEREO_INTERP_LEN_MS 8 /* must be even */ +#define STEREO_RATIO_SMOOTH_COEF 0.01 /* smoothing coef for signal norms and stereo width */ + +/* Range of pitch lag estimates */ +#define PITCH_EST_MIN_LAG_MS 2 /* 2 ms -> 500 Hz */ +#define PITCH_EST_MAX_LAG_MS 18 /* 18 ms -> 56 Hz */ + +/* Maximum number of subframes */ +#define MAX_NB_SUBFR 4 + +/* Number of samples per frame */ +#define LTP_MEM_LENGTH_MS 20 +#define SUB_FRAME_LENGTH_MS 5 +#define MAX_SUB_FRAME_LENGTH ( SUB_FRAME_LENGTH_MS * MAX_FS_KHZ ) +#define MAX_FRAME_LENGTH_MS ( SUB_FRAME_LENGTH_MS * MAX_NB_SUBFR ) +#define MAX_FRAME_LENGTH ( MAX_FRAME_LENGTH_MS * MAX_FS_KHZ ) + +/* Milliseconds of lookahead for pitch analysis */ +#define LA_PITCH_MS 2 +#define LA_PITCH_MAX ( LA_PITCH_MS * MAX_FS_KHZ ) + +/* Order of LPC used in find pitch */ +#define MAX_FIND_PITCH_LPC_ORDER 16 + +/* Length of LPC window used in find pitch */ +#define FIND_PITCH_LPC_WIN_MS ( 20 + (LA_PITCH_MS << 1) ) +#define FIND_PITCH_LPC_WIN_MS_2_SF ( 10 + (LA_PITCH_MS << 1) ) +#define FIND_PITCH_LPC_WIN_MAX ( FIND_PITCH_LPC_WIN_MS * MAX_FS_KHZ ) + +/* Milliseconds of lookahead for noise shape analysis */ +#define LA_SHAPE_MS 5 +#define LA_SHAPE_MAX ( LA_SHAPE_MS * MAX_FS_KHZ ) + +/* Maximum length of LPC window used in noise shape analysis */ +#define SHAPE_LPC_WIN_MAX ( 15 * MAX_FS_KHZ ) + +/* dB level of lowest gain quantization level */ +#define MIN_QGAIN_DB 2 +/* dB level of highest gain quantization level */ +#define MAX_QGAIN_DB 88 +/* Number of gain quantization levels */ +#define N_LEVELS_QGAIN 64 +/* Max increase in gain quantization index */ +#define MAX_DELTA_GAIN_QUANT 36 +/* Max decrease in gain quantization index */ +#define MIN_DELTA_GAIN_QUANT -4 + +/* Quantization offsets (multiples of 4) */ +#define OFFSET_VL_Q10 32 +#define OFFSET_VH_Q10 100 +#define OFFSET_UVL_Q10 100 +#define OFFSET_UVH_Q10 240 + +#define QUANT_LEVEL_ADJUST_Q10 80 + +/* Maximum numbers of iterations used to stabilize an LPC vector */ +#define MAX_LPC_STABILIZE_ITERATIONS 16 +#define MAX_PREDICTION_POWER_GAIN 1e4f +#define MAX_PREDICTION_POWER_GAIN_AFTER_RESET 1e2f + +#define MAX_LPC_ORDER 16 +#define MIN_LPC_ORDER 10 + +/* Find Pred Coef defines */ +#define LTP_ORDER 5 + +/* LTP quantization settings */ +#define NB_LTP_CBKS 3 + +/* Flag to use harmonic noise shaping */ +#define USE_HARM_SHAPING 1 + +/* Max LPC order of noise shaping filters */ +#define MAX_SHAPE_LPC_ORDER 24 + +#define HARM_SHAPE_FIR_TAPS 3 + +/* Maximum number of delayed decision states */ +#define MAX_DEL_DEC_STATES 4 + +#define LTP_BUF_LENGTH 512 +#define LTP_MASK ( LTP_BUF_LENGTH - 1 ) + +#define DECISION_DELAY 40 + +/* Number of subframes for excitation entropy coding */ +#define SHELL_CODEC_FRAME_LENGTH 16 +#define LOG2_SHELL_CODEC_FRAME_LENGTH 4 +#define MAX_NB_SHELL_BLOCKS ( MAX_FRAME_LENGTH / SHELL_CODEC_FRAME_LENGTH ) + +/* Number of rate levels, for entropy coding of excitation */ +#define N_RATE_LEVELS 10 + +/* Maximum sum of pulses per shell coding frame */ +#define SILK_MAX_PULSES 16 + +#define MAX_MATRIX_SIZE MAX_LPC_ORDER /* Max of LPC Order and LTP order */ + +# define NSQ_LPC_BUF_LENGTH MAX_LPC_ORDER + +/***************************/ +/* Voice activity detector */ +/***************************/ +#define VAD_N_BANDS 4 + +#define VAD_INTERNAL_SUBFRAMES_LOG2 2 +#define VAD_INTERNAL_SUBFRAMES ( 1 << VAD_INTERNAL_SUBFRAMES_LOG2 ) + +#define VAD_NOISE_LEVEL_SMOOTH_COEF_Q16 1024 /* Must be < 4096 */ +#define VAD_NOISE_LEVELS_BIAS 50 + +/* Sigmoid settings */ +#define VAD_NEGATIVE_OFFSET_Q5 128 /* sigmoid is 0 at -128 */ +#define VAD_SNR_FACTOR_Q16 45000 + +/* smoothing for SNR measurement */ +#define VAD_SNR_SMOOTH_COEF_Q18 4096 + +/* Size of the piecewise linear cosine approximation table for the LSFs */ +#define LSF_COS_TAB_SZ_FIX 128 + +/******************/ +/* NLSF quantizer */ +/******************/ +#define NLSF_W_Q 2 +#define NLSF_VQ_MAX_VECTORS 32 +#define NLSF_QUANT_MAX_AMPLITUDE 4 +#define NLSF_QUANT_MAX_AMPLITUDE_EXT 10 +#define NLSF_QUANT_LEVEL_ADJ 0.1 +#define NLSF_QUANT_DEL_DEC_STATES_LOG2 2 +#define NLSF_QUANT_DEL_DEC_STATES ( 1 << NLSF_QUANT_DEL_DEC_STATES_LOG2 ) + +/* Transition filtering for mode switching */ +#define TRANSITION_TIME_MS 5120 /* 5120 = 64 * FRAME_LENGTH_MS * ( TRANSITION_INT_NUM - 1 ) = 64*(20*4)*/ +#define TRANSITION_NB 3 /* Hardcoded in tables */ +#define TRANSITION_NA 2 /* Hardcoded in tables */ +#define TRANSITION_INT_NUM 5 /* Hardcoded in tables */ +#define TRANSITION_FRAMES ( TRANSITION_TIME_MS / MAX_FRAME_LENGTH_MS ) +#define TRANSITION_INT_STEPS ( TRANSITION_FRAMES / ( TRANSITION_INT_NUM - 1 ) ) + +/* BWE factors to apply after packet loss */ +#define BWE_AFTER_LOSS_Q16 63570 + +/* Defines for CN generation */ +#define CNG_BUF_MASK_MAX 255 /* 2^floor(log2(MAX_FRAME_LENGTH))-1 */ +#define CNG_GAIN_SMTH_Q16 4634 /* 0.25^(1/4) */ +#define CNG_GAIN_SMTH_THRESHOLD_Q16 46396 /* -3 dB */ +#define CNG_NLSF_SMTH_Q16 16348 /* 0.25 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/enc_API.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/enc_API.c new file mode 100644 index 0000000000..369caddd98 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/enc_API.c @@ -0,0 +1,591 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "define.h" +#include "API.h" +#include "control.h" +#include "typedef.h" +#include "stack_alloc.h" +#include "structs.h" +#include "tuning_parameters.h" +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#endif + +/***************************************/ +/* Read control structure from encoder */ +/***************************************/ +static opus_int silk_QueryEncoder( /* O Returns error code */ + const void *encState, /* I State */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +); + +/****************************************/ +/* Encoder functions */ +/****************************************/ + +opus_int silk_Get_Encoder_Size( /* O Returns error code */ + opus_int *encSizeBytes /* O Number of bytes in SILK encoder state */ +) +{ + opus_int ret = SILK_NO_ERROR; + + *encSizeBytes = sizeof( silk_encoder ); + + return ret; +} + +/*************************/ +/* Init or Reset encoder */ +/*************************/ +opus_int silk_InitEncoder( /* O Returns error code */ + void *encState, /* I/O State */ + int arch, /* I Run-time architecture */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +) +{ + silk_encoder *psEnc; + opus_int n, ret = SILK_NO_ERROR; + + psEnc = (silk_encoder *)encState; + + /* Reset encoder */ + silk_memset( psEnc, 0, sizeof( silk_encoder ) ); + for( n = 0; n < ENCODER_NUM_CHANNELS; n++ ) { + if( ret += silk_init_encoder( &psEnc->state_Fxx[ n ], arch ) ) { + celt_assert( 0 ); + } + } + + psEnc->nChannelsAPI = 1; + psEnc->nChannelsInternal = 1; + + /* Read control structure */ + if( ret += silk_QueryEncoder( encState, encStatus ) ) { + celt_assert( 0 ); + } + + return ret; +} + +/***************************************/ +/* Read control structure from encoder */ +/***************************************/ +static opus_int silk_QueryEncoder( /* O Returns error code */ + const void *encState, /* I State */ + silk_EncControlStruct *encStatus /* O Encoder Status */ +) +{ + opus_int ret = SILK_NO_ERROR; + silk_encoder_state_Fxx *state_Fxx; + silk_encoder *psEnc = (silk_encoder *)encState; + + state_Fxx = psEnc->state_Fxx; + + encStatus->nChannelsAPI = psEnc->nChannelsAPI; + encStatus->nChannelsInternal = psEnc->nChannelsInternal; + encStatus->API_sampleRate = state_Fxx[ 0 ].sCmn.API_fs_Hz; + encStatus->maxInternalSampleRate = state_Fxx[ 0 ].sCmn.maxInternal_fs_Hz; + encStatus->minInternalSampleRate = state_Fxx[ 0 ].sCmn.minInternal_fs_Hz; + encStatus->desiredInternalSampleRate = state_Fxx[ 0 ].sCmn.desiredInternal_fs_Hz; + encStatus->payloadSize_ms = state_Fxx[ 0 ].sCmn.PacketSize_ms; + encStatus->bitRate = state_Fxx[ 0 ].sCmn.TargetRate_bps; + encStatus->packetLossPercentage = state_Fxx[ 0 ].sCmn.PacketLoss_perc; + encStatus->complexity = state_Fxx[ 0 ].sCmn.Complexity; + encStatus->useInBandFEC = state_Fxx[ 0 ].sCmn.useInBandFEC; + encStatus->useDTX = state_Fxx[ 0 ].sCmn.useDTX; + encStatus->useCBR = state_Fxx[ 0 ].sCmn.useCBR; + encStatus->internalSampleRate = silk_SMULBB( state_Fxx[ 0 ].sCmn.fs_kHz, 1000 ); + encStatus->allowBandwidthSwitch = state_Fxx[ 0 ].sCmn.allow_bandwidth_switch; + encStatus->inWBmodeWithoutVariableLP = state_Fxx[ 0 ].sCmn.fs_kHz == 16 && state_Fxx[ 0 ].sCmn.sLP.mode == 0; + + return ret; +} + + +/**************************/ +/* Encode frame with Silk */ +/**************************/ +/* Note: if prefillFlag is set, the input must contain 10 ms of audio, irrespective of what */ +/* encControl->payloadSize_ms is set to */ +opus_int silk_Encode( /* O Returns error code */ + void *encState, /* I/O State */ + silk_EncControlStruct *encControl, /* I Control status */ + const opus_int16 *samplesIn, /* I Speech sample input vector */ + opus_int nSamplesIn, /* I Number of samples in input vector */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int32 *nBytesOut, /* I/O Number of bytes in payload (input: Max bytes) */ + const opus_int prefillFlag, /* I Flag to indicate prefilling buffers no coding */ + opus_int activity /* I Decision of Opus voice activity detector */ +) +{ + opus_int n, i, nBits, flags, tmp_payloadSize_ms = 0, tmp_complexity = 0, ret = 0; + opus_int nSamplesToBuffer, nSamplesToBufferMax, nBlocksOf10ms; + opus_int nSamplesFromInput = 0, nSamplesFromInputMax; + opus_int speech_act_thr_for_switch_Q8; + opus_int32 TargetRate_bps, MStargetRates_bps[ 2 ], channelRate_bps, LBRR_symbol, sum; + silk_encoder *psEnc = ( silk_encoder * )encState; + VARDECL( opus_int16, buf ); + opus_int transition, curr_block, tot_blocks; + SAVE_STACK; + + if (encControl->reducedDependency) + { + psEnc->state_Fxx[0].sCmn.first_frame_after_reset = 1; + psEnc->state_Fxx[1].sCmn.first_frame_after_reset = 1; + } + psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded = psEnc->state_Fxx[ 1 ].sCmn.nFramesEncoded = 0; + + /* Check values in encoder control structure */ + if( ( ret = check_control_input( encControl ) ) != 0 ) { + celt_assert( 0 ); + RESTORE_STACK; + return ret; + } + + encControl->switchReady = 0; + + if( encControl->nChannelsInternal > psEnc->nChannelsInternal ) { + /* Mono -> Stereo transition: init state of second channel and stereo state */ + ret += silk_init_encoder( &psEnc->state_Fxx[ 1 ], psEnc->state_Fxx[ 0 ].sCmn.arch ); + silk_memset( psEnc->sStereo.pred_prev_Q13, 0, sizeof( psEnc->sStereo.pred_prev_Q13 ) ); + silk_memset( psEnc->sStereo.sSide, 0, sizeof( psEnc->sStereo.sSide ) ); + psEnc->sStereo.mid_side_amp_Q0[ 0 ] = 0; + psEnc->sStereo.mid_side_amp_Q0[ 1 ] = 1; + psEnc->sStereo.mid_side_amp_Q0[ 2 ] = 0; + psEnc->sStereo.mid_side_amp_Q0[ 3 ] = 1; + psEnc->sStereo.width_prev_Q14 = 0; + psEnc->sStereo.smth_width_Q14 = SILK_FIX_CONST( 1, 14 ); + if( psEnc->nChannelsAPI == 2 ) { + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, sizeof( silk_resampler_state_struct ) ); + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.In_HP_State, &psEnc->state_Fxx[ 0 ].sCmn.In_HP_State, sizeof( psEnc->state_Fxx[ 1 ].sCmn.In_HP_State ) ); + } + } + + transition = (encControl->payloadSize_ms != psEnc->state_Fxx[ 0 ].sCmn.PacketSize_ms) || (psEnc->nChannelsInternal != encControl->nChannelsInternal); + + psEnc->nChannelsAPI = encControl->nChannelsAPI; + psEnc->nChannelsInternal = encControl->nChannelsInternal; + + nBlocksOf10ms = silk_DIV32( 100 * nSamplesIn, encControl->API_sampleRate ); + tot_blocks = ( nBlocksOf10ms > 1 ) ? nBlocksOf10ms >> 1 : 1; + curr_block = 0; + if( prefillFlag ) { + silk_LP_state save_LP; + /* Only accept input length of 10 ms */ + if( nBlocksOf10ms != 1 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + if ( prefillFlag == 2 ) { + save_LP = psEnc->state_Fxx[ 0 ].sCmn.sLP; + /* Save the sampling rate so the bandwidth switching code can keep handling transitions. */ + save_LP.saved_fs_kHz = psEnc->state_Fxx[ 0 ].sCmn.fs_kHz; + } + /* Reset Encoder */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + ret = silk_init_encoder( &psEnc->state_Fxx[ n ], psEnc->state_Fxx[ n ].sCmn.arch ); + /* Restore the variable LP state. */ + if ( prefillFlag == 2 ) { + psEnc->state_Fxx[ n ].sCmn.sLP = save_LP; + } + celt_assert( !ret ); + } + tmp_payloadSize_ms = encControl->payloadSize_ms; + encControl->payloadSize_ms = 10; + tmp_complexity = encControl->complexity; + encControl->complexity = 0; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.prefillFlag = 1; + } + } else { + /* Only accept input lengths that are a multiple of 10 ms */ + if( nBlocksOf10ms * encControl->API_sampleRate != 100 * nSamplesIn || nSamplesIn < 0 ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + /* Make sure no more than one packet can be produced */ + if( 1000 * (opus_int32)nSamplesIn > encControl->payloadSize_ms * encControl->API_sampleRate ) { + celt_assert( 0 ); + RESTORE_STACK; + return SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES; + } + } + + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + /* Force the side channel to the same rate as the mid */ + opus_int force_fs_kHz = (n==1) ? psEnc->state_Fxx[0].sCmn.fs_kHz : 0; + if( ( ret = silk_control_encoder( &psEnc->state_Fxx[ n ], encControl, psEnc->allowBandwidthSwitch, n, force_fs_kHz ) ) != 0 ) { + silk_assert( 0 ); + RESTORE_STACK; + return ret; + } + if( psEnc->state_Fxx[n].sCmn.first_frame_after_reset || transition ) { + for( i = 0; i < psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket; i++ ) { + psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ] = 0; + } + } + psEnc->state_Fxx[ n ].sCmn.inDTX = psEnc->state_Fxx[ n ].sCmn.useDTX; + } + celt_assert( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 0 ].sCmn.fs_kHz == psEnc->state_Fxx[ 1 ].sCmn.fs_kHz ); + + /* Input buffering/resampling and encoding */ + nSamplesToBufferMax = + 10 * nBlocksOf10ms * psEnc->state_Fxx[ 0 ].sCmn.fs_kHz; + nSamplesFromInputMax = + silk_DIV32_16( nSamplesToBufferMax * + psEnc->state_Fxx[ 0 ].sCmn.API_fs_Hz, + psEnc->state_Fxx[ 0 ].sCmn.fs_kHz * 1000 ); + ALLOC( buf, nSamplesFromInputMax, opus_int16 ); + while( 1 ) { + int curr_nBitsUsedLBRR = 0; + nSamplesToBuffer = psEnc->state_Fxx[ 0 ].sCmn.frame_length - psEnc->state_Fxx[ 0 ].sCmn.inputBufIx; + nSamplesToBuffer = silk_min( nSamplesToBuffer, nSamplesToBufferMax ); + nSamplesFromInput = silk_DIV32_16( nSamplesToBuffer * psEnc->state_Fxx[ 0 ].sCmn.API_fs_Hz, psEnc->state_Fxx[ 0 ].sCmn.fs_kHz * 1000 ); + /* Resample and write to buffer */ + if( encControl->nChannelsAPI == 2 && encControl->nChannelsInternal == 2 ) { + opus_int id = psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded; + for( n = 0; n < nSamplesFromInput; n++ ) { + buf[ n ] = samplesIn[ 2 * n ]; + } + /* Making sure to start both resamplers from the same state when switching from mono to stereo */ + if( psEnc->nPrevChannelsInternal == 1 && id==0 ) { + silk_memcpy( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, sizeof(psEnc->state_Fxx[ 1 ].sCmn.resampler_state)); + } + + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + + nSamplesToBuffer = psEnc->state_Fxx[ 1 ].sCmn.frame_length - psEnc->state_Fxx[ 1 ].sCmn.inputBufIx; + nSamplesToBuffer = silk_min( nSamplesToBuffer, 10 * nBlocksOf10ms * psEnc->state_Fxx[ 1 ].sCmn.fs_kHz ); + for( n = 0; n < nSamplesFromInput; n++ ) { + buf[ n ] = samplesIn[ 2 * n + 1 ]; + } + ret += silk_resampler( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + + psEnc->state_Fxx[ 1 ].sCmn.inputBufIx += nSamplesToBuffer; + } else if( encControl->nChannelsAPI == 2 && encControl->nChannelsInternal == 1 ) { + /* Combine left and right channels before resampling */ + for( n = 0; n < nSamplesFromInput; n++ ) { + sum = samplesIn[ 2 * n ] + samplesIn[ 2 * n + 1 ]; + buf[ n ] = (opus_int16)silk_RSHIFT_ROUND( sum, 1 ); + } + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + /* On the first mono frame, average the results for the two resampler states */ + if( psEnc->nPrevChannelsInternal == 2 && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == 0 ) { + ret += silk_resampler( &psEnc->state_Fxx[ 1 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + for( n = 0; n < psEnc->state_Fxx[ 0 ].sCmn.frame_length; n++ ) { + psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx+n+2 ] = + silk_RSHIFT(psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx+n+2 ] + + psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ psEnc->state_Fxx[ 1 ].sCmn.inputBufIx+n+2 ], 1); + } + } + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + } else { + celt_assert( encControl->nChannelsAPI == 1 && encControl->nChannelsInternal == 1 ); + silk_memcpy(buf, samplesIn, nSamplesFromInput*sizeof(opus_int16)); + ret += silk_resampler( &psEnc->state_Fxx[ 0 ].sCmn.resampler_state, + &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.inputBufIx + 2 ], buf, nSamplesFromInput ); + psEnc->state_Fxx[ 0 ].sCmn.inputBufIx += nSamplesToBuffer; + } + + samplesIn += nSamplesFromInput * encControl->nChannelsAPI; + nSamplesIn -= nSamplesFromInput; + + /* Default */ + psEnc->allowBandwidthSwitch = 0; + + /* Silk encoder */ + if( psEnc->state_Fxx[ 0 ].sCmn.inputBufIx >= psEnc->state_Fxx[ 0 ].sCmn.frame_length ) { + /* Enough data in input buffer, so encode */ + celt_assert( psEnc->state_Fxx[ 0 ].sCmn.inputBufIx == psEnc->state_Fxx[ 0 ].sCmn.frame_length ); + celt_assert( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 1 ].sCmn.inputBufIx == psEnc->state_Fxx[ 1 ].sCmn.frame_length ); + + /* Deal with LBRR data */ + if( psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == 0 && !prefillFlag ) { + /* Create space at start of payload for VAD and FEC flags */ + opus_uint8 iCDF[ 2 ] = { 0, 0 }; + iCDF[ 0 ] = 256 - silk_RSHIFT( 256, ( psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket + 1 ) * encControl->nChannelsInternal ); + ec_enc_icdf( psRangeEnc, 0, iCDF, 8 ); + curr_nBitsUsedLBRR = ec_tell( psRangeEnc ); + + /* Encode any LBRR data from previous packet */ + /* Encode LBRR flags */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + LBRR_symbol = 0; + for( i = 0; i < psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket; i++ ) { + LBRR_symbol |= silk_LSHIFT( psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ], i ); + } + psEnc->state_Fxx[ n ].sCmn.LBRR_flag = LBRR_symbol > 0 ? 1 : 0; + if( LBRR_symbol && psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket > 1 ) { + ec_enc_icdf( psRangeEnc, LBRR_symbol - 1, silk_LBRR_flags_iCDF_ptr[ psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket - 2 ], 8 ); + } + } + + /* Code LBRR indices and excitation signals */ + for( i = 0; i < psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket; i++ ) { + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + if( psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i ] ) { + opus_int condCoding; + + if( encControl->nChannelsInternal == 2 && n == 0 ) { + silk_stereo_encode_pred( psRangeEnc, psEnc->sStereo.predIx[ i ] ); + /* For LBRR data there's no need to code the mid-only flag if the side-channel LBRR flag is set */ + if( psEnc->state_Fxx[ 1 ].sCmn.LBRR_flags[ i ] == 0 ) { + silk_stereo_encode_mid_only( psRangeEnc, psEnc->sStereo.mid_only_flags[ i ] ); + } + } + /* Use conditional coding if previous frame available */ + if( i > 0 && psEnc->state_Fxx[ n ].sCmn.LBRR_flags[ i - 1 ] ) { + condCoding = CODE_CONDITIONALLY; + } else { + condCoding = CODE_INDEPENDENTLY; + } + silk_encode_indices( &psEnc->state_Fxx[ n ].sCmn, psRangeEnc, i, 1, condCoding ); + silk_encode_pulses( psRangeEnc, psEnc->state_Fxx[ n ].sCmn.indices_LBRR[i].signalType, psEnc->state_Fxx[ n ].sCmn.indices_LBRR[i].quantOffsetType, + psEnc->state_Fxx[ n ].sCmn.pulses_LBRR[ i ], psEnc->state_Fxx[ n ].sCmn.frame_length ); + } + } + } + + /* Reset LBRR flags */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + silk_memset( psEnc->state_Fxx[ n ].sCmn.LBRR_flags, 0, sizeof( psEnc->state_Fxx[ n ].sCmn.LBRR_flags ) ); + } + curr_nBitsUsedLBRR = ec_tell( psRangeEnc ) - curr_nBitsUsedLBRR; + } + + silk_HP_variable_cutoff( psEnc->state_Fxx ); + + /* Total target bits for packet */ + nBits = silk_DIV32_16( silk_MUL( encControl->bitRate, encControl->payloadSize_ms ), 1000 ); + /* Subtract bits used for LBRR */ + if( !prefillFlag ) { + /* psEnc->nBitsUsedLBRR is an exponential moving average of the LBRR usage, + except that for the first LBRR frame it does no averaging and for the first + frame after after LBRR, it goes back to zero immediately. */ + if ( curr_nBitsUsedLBRR < 10 ) { + psEnc->nBitsUsedLBRR = 0; + } else if ( psEnc->nBitsUsedLBRR < 10) { + psEnc->nBitsUsedLBRR = curr_nBitsUsedLBRR; + } else { + psEnc->nBitsUsedLBRR = ( psEnc->nBitsUsedLBRR + curr_nBitsUsedLBRR ) / 2; + } + nBits -= psEnc->nBitsUsedLBRR; + } + /* Divide by number of uncoded frames left in packet */ + nBits = silk_DIV32_16( nBits, psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket ); + /* Convert to bits/second */ + if( encControl->payloadSize_ms == 10 ) { + TargetRate_bps = silk_SMULBB( nBits, 100 ); + } else { + TargetRate_bps = silk_SMULBB( nBits, 50 ); + } + /* Subtract fraction of bits in excess of target in previous frames and packets */ + TargetRate_bps -= silk_DIV32_16( silk_MUL( psEnc->nBitsExceeded, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + if( !prefillFlag && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded > 0 ) { + /* Compare actual vs target bits so far in this packet */ + opus_int32 bitsBalance = ec_tell( psRangeEnc ) - psEnc->nBitsUsedLBRR - nBits * psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded; + TargetRate_bps -= silk_DIV32_16( silk_MUL( bitsBalance, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + } + /* Never exceed input bitrate */ + TargetRate_bps = silk_LIMIT( TargetRate_bps, encControl->bitRate, 5000 ); + + /* Convert Left/Right to Mid/Side */ + if( encControl->nChannelsInternal == 2 ) { + silk_stereo_LR_to_MS( &psEnc->sStereo, &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ 2 ], &psEnc->state_Fxx[ 1 ].sCmn.inputBuf[ 2 ], + psEnc->sStereo.predIx[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ], &psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ], + MStargetRates_bps, TargetRate_bps, psEnc->state_Fxx[ 0 ].sCmn.speech_activity_Q8, encControl->toMono, + psEnc->state_Fxx[ 0 ].sCmn.fs_kHz, psEnc->state_Fxx[ 0 ].sCmn.frame_length ); + if( psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] == 0 ) { + /* Reset side channel encoder memory for first frame with side coding */ + if( psEnc->prev_decode_only_middle == 1 ) { + silk_memset( &psEnc->state_Fxx[ 1 ].sShape, 0, sizeof( psEnc->state_Fxx[ 1 ].sShape ) ); + silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sNSQ, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sNSQ ) ); + silk_memset( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15 ) ); + silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State ) ); + psEnc->state_Fxx[ 1 ].sCmn.prevLag = 100; + psEnc->state_Fxx[ 1 ].sCmn.sNSQ.lagPrev = 100; + psEnc->state_Fxx[ 1 ].sShape.LastGainIndex = 10; + psEnc->state_Fxx[ 1 ].sCmn.prevSignalType = TYPE_NO_VOICE_ACTIVITY; + psEnc->state_Fxx[ 1 ].sCmn.sNSQ.prev_gain_Q16 = 65536; + psEnc->state_Fxx[ 1 ].sCmn.first_frame_after_reset = 1; + } + silk_encode_do_VAD_Fxx( &psEnc->state_Fxx[ 1 ], activity ); + } else { + psEnc->state_Fxx[ 1 ].sCmn.VAD_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] = 0; + } + if( !prefillFlag ) { + silk_stereo_encode_pred( psRangeEnc, psEnc->sStereo.predIx[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] ); + if( psEnc->state_Fxx[ 1 ].sCmn.VAD_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] == 0 ) { + silk_stereo_encode_mid_only( psRangeEnc, psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ] ); + } + } + } else { + /* Buffering */ + silk_memcpy( psEnc->state_Fxx[ 0 ].sCmn.inputBuf, psEnc->sStereo.sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( psEnc->sStereo.sMid, &psEnc->state_Fxx[ 0 ].sCmn.inputBuf[ psEnc->state_Fxx[ 0 ].sCmn.frame_length ], 2 * sizeof( opus_int16 ) ); + } + silk_encode_do_VAD_Fxx( &psEnc->state_Fxx[ 0 ], activity ); + + /* Encode */ + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + opus_int maxBits, useCBR; + + /* Handling rate constraints */ + maxBits = encControl->maxBits; + if( tot_blocks == 2 && curr_block == 0 ) { + maxBits = maxBits * 3 / 5; + } else if( tot_blocks == 3 ) { + if( curr_block == 0 ) { + maxBits = maxBits * 2 / 5; + } else if( curr_block == 1 ) { + maxBits = maxBits * 3 / 4; + } + } + useCBR = encControl->useCBR && curr_block == tot_blocks - 1; + + if( encControl->nChannelsInternal == 1 ) { + channelRate_bps = TargetRate_bps; + } else { + channelRate_bps = MStargetRates_bps[ n ]; + if( n == 0 && MStargetRates_bps[ 1 ] > 0 ) { + useCBR = 0; + /* Give mid up to 1/2 of the max bits for that frame */ + maxBits -= encControl->maxBits / ( tot_blocks * 2 ); + } + } + + if( channelRate_bps > 0 ) { + opus_int condCoding; + + silk_control_SNR( &psEnc->state_Fxx[ n ].sCmn, channelRate_bps ); + + /* Use independent coding if no previous frame available */ + if( psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded - n <= 0 ) { + condCoding = CODE_INDEPENDENTLY; + } else if( n > 0 && psEnc->prev_decode_only_middle ) { + /* If we skipped a side frame in this packet, we don't + need LTP scaling; the LTP state is well-defined. */ + condCoding = CODE_INDEPENDENTLY_NO_LTP_SCALING; + } else { + condCoding = CODE_CONDITIONALLY; + } + if( ( ret = silk_encode_frame_Fxx( &psEnc->state_Fxx[ n ], nBytesOut, psRangeEnc, condCoding, maxBits, useCBR ) ) != 0 ) { + silk_assert( 0 ); + } + } + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.inputBufIx = 0; + psEnc->state_Fxx[ n ].sCmn.nFramesEncoded++; + } + psEnc->prev_decode_only_middle = psEnc->sStereo.mid_only_flags[ psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded - 1 ]; + + /* Insert VAD and FEC flags at beginning of bitstream */ + if( *nBytesOut > 0 && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded == psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket) { + flags = 0; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + for( i = 0; i < psEnc->state_Fxx[ n ].sCmn.nFramesPerPacket; i++ ) { + flags = silk_LSHIFT( flags, 1 ); + flags |= psEnc->state_Fxx[ n ].sCmn.VAD_flags[ i ]; + } + flags = silk_LSHIFT( flags, 1 ); + flags |= psEnc->state_Fxx[ n ].sCmn.LBRR_flag; + } + if( !prefillFlag ) { + ec_enc_patch_initial_bits( psRangeEnc, flags, ( psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket + 1 ) * encControl->nChannelsInternal ); + } + + /* Return zero bytes if all channels DTXed */ + if( psEnc->state_Fxx[ 0 ].sCmn.inDTX && ( encControl->nChannelsInternal == 1 || psEnc->state_Fxx[ 1 ].sCmn.inDTX ) ) { + *nBytesOut = 0; + } + + psEnc->nBitsExceeded += *nBytesOut * 8; + psEnc->nBitsExceeded -= silk_DIV32_16( silk_MUL( encControl->bitRate, encControl->payloadSize_ms ), 1000 ); + psEnc->nBitsExceeded = silk_LIMIT( psEnc->nBitsExceeded, 0, 10000 ); + + /* Update flag indicating if bandwidth switching is allowed */ + speech_act_thr_for_switch_Q8 = silk_SMLAWB( SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ), + SILK_FIX_CONST( ( 1 - SPEECH_ACTIVITY_DTX_THRES ) / MAX_BANDWIDTH_SWITCH_DELAY_MS, 16 + 8 ), psEnc->timeSinceSwitchAllowed_ms ); + if( psEnc->state_Fxx[ 0 ].sCmn.speech_activity_Q8 < speech_act_thr_for_switch_Q8 ) { + psEnc->allowBandwidthSwitch = 1; + psEnc->timeSinceSwitchAllowed_ms = 0; + } else { + psEnc->allowBandwidthSwitch = 0; + psEnc->timeSinceSwitchAllowed_ms += encControl->payloadSize_ms; + } + } + + if( nSamplesIn == 0 ) { + break; + } + } else { + break; + } + curr_block++; + } + + psEnc->nPrevChannelsInternal = encControl->nChannelsInternal; + + encControl->allowBandwidthSwitch = psEnc->allowBandwidthSwitch; + encControl->inWBmodeWithoutVariableLP = psEnc->state_Fxx[ 0 ].sCmn.fs_kHz == 16 && psEnc->state_Fxx[ 0 ].sCmn.sLP.mode == 0; + encControl->internalSampleRate = silk_SMULBB( psEnc->state_Fxx[ 0 ].sCmn.fs_kHz, 1000 ); + encControl->stereoWidth_Q14 = encControl->toMono ? 0 : psEnc->sStereo.smth_width_Q14; + if( prefillFlag ) { + encControl->payloadSize_ms = tmp_payloadSize_ms; + encControl->complexity = tmp_complexity; + for( n = 0; n < encControl->nChannelsInternal; n++ ) { + psEnc->state_Fxx[ n ].sCmn.controlled_since_last_payload = 0; + psEnc->state_Fxx[ n ].sCmn.prefillFlag = 0; + } + } + + encControl->signalType = psEnc->state_Fxx[0].sCmn.indices.signalType; + encControl->offset = silk_Quantization_Offsets_Q10 + [ psEnc->state_Fxx[0].sCmn.indices.signalType >> 1 ] + [ psEnc->state_Fxx[0].sCmn.indices.quantOffsetType ]; + RESTORE_STACK; + return ret; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_indices.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_indices.c new file mode 100644 index 0000000000..4bcbc3347b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_indices.c @@ -0,0 +1,181 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Encode side-information parameters to payload */ +void silk_encode_indices( + silk_encoder_state *psEncC, /* I/O Encoder state */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int encode_LBRR, /* I Flag indicating LBRR data is being encoded */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i, k, typeOffset; + opus_int encode_absolute_lagIndex, delta_lagIndex; + opus_int16 ec_ix[ MAX_LPC_ORDER ]; + opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; + const SideInfoIndices *psIndices; + + if( encode_LBRR ) { + psIndices = &psEncC->indices_LBRR[ FrameIndex ]; + } else { + psIndices = &psEncC->indices; + } + + /*******************************************/ + /* Encode signal type and quantizer offset */ + /*******************************************/ + typeOffset = 2 * psIndices->signalType + psIndices->quantOffsetType; + celt_assert( typeOffset >= 0 && typeOffset < 6 ); + celt_assert( encode_LBRR == 0 || typeOffset >= 2 ); + if( encode_LBRR || typeOffset >= 2 ) { + ec_enc_icdf( psRangeEnc, typeOffset - 2, silk_type_offset_VAD_iCDF, 8 ); + } else { + ec_enc_icdf( psRangeEnc, typeOffset, silk_type_offset_no_VAD_iCDF, 8 ); + } + + /****************/ + /* Encode gains */ + /****************/ + /* first subframe */ + if( condCoding == CODE_CONDITIONALLY ) { + /* conditional coding */ + silk_assert( psIndices->GainsIndices[ 0 ] >= 0 && psIndices->GainsIndices[ 0 ] < MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ 0 ], silk_delta_gain_iCDF, 8 ); + } else { + /* independent coding, in two stages: MSB bits followed by 3 LSBs */ + silk_assert( psIndices->GainsIndices[ 0 ] >= 0 && psIndices->GainsIndices[ 0 ] < N_LEVELS_QGAIN ); + ec_enc_icdf( psRangeEnc, silk_RSHIFT( psIndices->GainsIndices[ 0 ], 3 ), silk_gain_iCDF[ psIndices->signalType ], 8 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ 0 ] & 7, silk_uniform8_iCDF, 8 ); + } + + /* remaining subframes */ + for( i = 1; i < psEncC->nb_subfr; i++ ) { + silk_assert( psIndices->GainsIndices[ i ] >= 0 && psIndices->GainsIndices[ i ] < MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ); + ec_enc_icdf( psRangeEnc, psIndices->GainsIndices[ i ], silk_delta_gain_iCDF, 8 ); + } + + /****************/ + /* Encode NLSFs */ + /****************/ + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ 0 ], &psEncC->psNLSF_CB->CB1_iCDF[ ( psIndices->signalType >> 1 ) * psEncC->psNLSF_CB->nVectors ], 8 ); + silk_NLSF_unpack( ec_ix, pred_Q8, psEncC->psNLSF_CB, psIndices->NLSFIndices[ 0 ] ); + celt_assert( psEncC->psNLSF_CB->order == psEncC->predictLPCOrder ); + for( i = 0; i < psEncC->psNLSF_CB->order; i++ ) { + if( psIndices->NLSFIndices[ i+1 ] >= NLSF_QUANT_MAX_AMPLITUDE ) { + ec_enc_icdf( psRangeEnc, 2 * NLSF_QUANT_MAX_AMPLITUDE, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ i+1 ] - NLSF_QUANT_MAX_AMPLITUDE, silk_NLSF_EXT_iCDF, 8 ); + } else if( psIndices->NLSFIndices[ i+1 ] <= -NLSF_QUANT_MAX_AMPLITUDE ) { + ec_enc_icdf( psRangeEnc, 0, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + ec_enc_icdf( psRangeEnc, -psIndices->NLSFIndices[ i+1 ] - NLSF_QUANT_MAX_AMPLITUDE, silk_NLSF_EXT_iCDF, 8 ); + } else { + ec_enc_icdf( psRangeEnc, psIndices->NLSFIndices[ i+1 ] + NLSF_QUANT_MAX_AMPLITUDE, &psEncC->psNLSF_CB->ec_iCDF[ ec_ix[ i ] ], 8 ); + } + } + + /* Encode NLSF interpolation factor */ + if( psEncC->nb_subfr == MAX_NB_SUBFR ) { + silk_assert( psIndices->NLSFInterpCoef_Q2 >= 0 && psIndices->NLSFInterpCoef_Q2 < 5 ); + ec_enc_icdf( psRangeEnc, psIndices->NLSFInterpCoef_Q2, silk_NLSF_interpolation_factor_iCDF, 8 ); + } + + if( psIndices->signalType == TYPE_VOICED ) + { + /*********************/ + /* Encode pitch lags */ + /*********************/ + /* lag index */ + encode_absolute_lagIndex = 1; + if( condCoding == CODE_CONDITIONALLY && psEncC->ec_prevSignalType == TYPE_VOICED ) { + /* Delta Encoding */ + delta_lagIndex = psIndices->lagIndex - psEncC->ec_prevLagIndex; + if( delta_lagIndex < -8 || delta_lagIndex > 11 ) { + delta_lagIndex = 0; + } else { + delta_lagIndex = delta_lagIndex + 9; + encode_absolute_lagIndex = 0; /* Only use delta */ + } + silk_assert( delta_lagIndex >= 0 && delta_lagIndex < 21 ); + ec_enc_icdf( psRangeEnc, delta_lagIndex, silk_pitch_delta_iCDF, 8 ); + } + if( encode_absolute_lagIndex ) { + /* Absolute encoding */ + opus_int32 pitch_high_bits, pitch_low_bits; + pitch_high_bits = silk_DIV32_16( psIndices->lagIndex, silk_RSHIFT( psEncC->fs_kHz, 1 ) ); + pitch_low_bits = psIndices->lagIndex - silk_SMULBB( pitch_high_bits, silk_RSHIFT( psEncC->fs_kHz, 1 ) ); + silk_assert( pitch_low_bits < psEncC->fs_kHz / 2 ); + silk_assert( pitch_high_bits < 32 ); + ec_enc_icdf( psRangeEnc, pitch_high_bits, silk_pitch_lag_iCDF, 8 ); + ec_enc_icdf( psRangeEnc, pitch_low_bits, psEncC->pitch_lag_low_bits_iCDF, 8 ); + } + psEncC->ec_prevLagIndex = psIndices->lagIndex; + + /* Countour index */ + silk_assert( psIndices->contourIndex >= 0 ); + silk_assert( ( psIndices->contourIndex < 34 && psEncC->fs_kHz > 8 && psEncC->nb_subfr == 4 ) || + ( psIndices->contourIndex < 11 && psEncC->fs_kHz == 8 && psEncC->nb_subfr == 4 ) || + ( psIndices->contourIndex < 12 && psEncC->fs_kHz > 8 && psEncC->nb_subfr == 2 ) || + ( psIndices->contourIndex < 3 && psEncC->fs_kHz == 8 && psEncC->nb_subfr == 2 ) ); + ec_enc_icdf( psRangeEnc, psIndices->contourIndex, psEncC->pitch_contour_iCDF, 8 ); + + /********************/ + /* Encode LTP gains */ + /********************/ + /* PERIndex value */ + silk_assert( psIndices->PERIndex >= 0 && psIndices->PERIndex < 3 ); + ec_enc_icdf( psRangeEnc, psIndices->PERIndex, silk_LTP_per_index_iCDF, 8 ); + + /* Codebook Indices */ + for( k = 0; k < psEncC->nb_subfr; k++ ) { + silk_assert( psIndices->LTPIndex[ k ] >= 0 && psIndices->LTPIndex[ k ] < ( 8 << psIndices->PERIndex ) ); + ec_enc_icdf( psRangeEnc, psIndices->LTPIndex[ k ], silk_LTP_gain_iCDF_ptrs[ psIndices->PERIndex ], 8 ); + } + + /**********************/ + /* Encode LTP scaling */ + /**********************/ + if( condCoding == CODE_INDEPENDENTLY ) { + silk_assert( psIndices->LTP_scaleIndex >= 0 && psIndices->LTP_scaleIndex < 3 ); + ec_enc_icdf( psRangeEnc, psIndices->LTP_scaleIndex, silk_LTPscale_iCDF, 8 ); + } + silk_assert( !condCoding || psIndices->LTP_scaleIndex == 0 ); + } + + psEncC->ec_prevSignalType = psIndices->signalType; + + /***************/ + /* Encode seed */ + /***************/ + silk_assert( psIndices->Seed >= 0 && psIndices->Seed < 4 ); + ec_enc_icdf( psRangeEnc, psIndices->Seed, silk_uniform4_iCDF, 8 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_pulses.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_pulses.c new file mode 100644 index 0000000000..8a1999138b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/encode_pulses.c @@ -0,0 +1,206 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/*********************************************/ +/* Encode quantization indices of excitation */ +/*********************************************/ + +static OPUS_INLINE opus_int combine_and_check( /* return ok */ + opus_int *pulses_comb, /* O */ + const opus_int *pulses_in, /* I */ + opus_int max_pulses, /* I max value for sum of pulses */ + opus_int len /* I number of output values */ +) +{ + opus_int k, sum; + + for( k = 0; k < len; k++ ) { + sum = pulses_in[ 2 * k ] + pulses_in[ 2 * k + 1 ]; + if( sum > max_pulses ) { + return 1; + } + pulses_comb[ k ] = sum; + } + + return 0; +} + +/* Encode quantization indices of excitation */ +void silk_encode_pulses( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I quantOffsetType */ + opus_int8 pulses[], /* I quantization indices */ + const opus_int frame_length /* I Frame length */ +) +{ + opus_int i, k, j, iter, bit, nLS, scale_down, RateLevelIndex = 0; + opus_int32 abs_q, minSumBits_Q5, sumBits_Q5; + VARDECL( opus_int, abs_pulses ); + VARDECL( opus_int, sum_pulses ); + VARDECL( opus_int, nRshifts ); + opus_int pulses_comb[ 8 ]; + opus_int *abs_pulses_ptr; + const opus_int8 *pulses_ptr; + const opus_uint8 *cdf_ptr; + const opus_uint8 *nBits_ptr; + SAVE_STACK; + + silk_memset( pulses_comb, 0, 8 * sizeof( opus_int ) ); /* Fixing Valgrind reported problem*/ + + /****************************/ + /* Prepare for shell coding */ + /****************************/ + /* Calculate number of shell blocks */ + silk_assert( 1 << LOG2_SHELL_CODEC_FRAME_LENGTH == SHELL_CODEC_FRAME_LENGTH ); + iter = silk_RSHIFT( frame_length, LOG2_SHELL_CODEC_FRAME_LENGTH ); + if( iter * SHELL_CODEC_FRAME_LENGTH < frame_length ) { + celt_assert( frame_length == 12 * 10 ); /* Make sure only happens for 10 ms @ 12 kHz */ + iter++; + silk_memset( &pulses[ frame_length ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof(opus_int8)); + } + + /* Take the absolute value of the pulses */ + ALLOC( abs_pulses, iter * SHELL_CODEC_FRAME_LENGTH, opus_int ); + silk_assert( !( SHELL_CODEC_FRAME_LENGTH & 3 ) ); + for( i = 0; i < iter * SHELL_CODEC_FRAME_LENGTH; i+=4 ) { + abs_pulses[i+0] = ( opus_int )silk_abs( pulses[ i + 0 ] ); + abs_pulses[i+1] = ( opus_int )silk_abs( pulses[ i + 1 ] ); + abs_pulses[i+2] = ( opus_int )silk_abs( pulses[ i + 2 ] ); + abs_pulses[i+3] = ( opus_int )silk_abs( pulses[ i + 3 ] ); + } + + /* Calc sum pulses per shell code frame */ + ALLOC( sum_pulses, iter, opus_int ); + ALLOC( nRshifts, iter, opus_int ); + abs_pulses_ptr = abs_pulses; + for( i = 0; i < iter; i++ ) { + nRshifts[ i ] = 0; + + while( 1 ) { + /* 1+1 -> 2 */ + scale_down = combine_and_check( pulses_comb, abs_pulses_ptr, silk_max_pulses_table[ 0 ], 8 ); + /* 2+2 -> 4 */ + scale_down += combine_and_check( pulses_comb, pulses_comb, silk_max_pulses_table[ 1 ], 4 ); + /* 4+4 -> 8 */ + scale_down += combine_and_check( pulses_comb, pulses_comb, silk_max_pulses_table[ 2 ], 2 ); + /* 8+8 -> 16 */ + scale_down += combine_and_check( &sum_pulses[ i ], pulses_comb, silk_max_pulses_table[ 3 ], 1 ); + + if( scale_down ) { + /* We need to downscale the quantization signal */ + nRshifts[ i ]++; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_pulses_ptr[ k ] = silk_RSHIFT( abs_pulses_ptr[ k ], 1 ); + } + } else { + /* Jump out of while(1) loop and go to next shell coding frame */ + break; + } + } + abs_pulses_ptr += SHELL_CODEC_FRAME_LENGTH; + } + + /**************/ + /* Rate level */ + /**************/ + /* find rate level that leads to fewest bits for coding of pulses per block info */ + minSumBits_Q5 = silk_int32_MAX; + for( k = 0; k < N_RATE_LEVELS - 1; k++ ) { + nBits_ptr = silk_pulses_per_block_BITS_Q5[ k ]; + sumBits_Q5 = silk_rate_levels_BITS_Q5[ signalType >> 1 ][ k ]; + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] > 0 ) { + sumBits_Q5 += nBits_ptr[ SILK_MAX_PULSES + 1 ]; + } else { + sumBits_Q5 += nBits_ptr[ sum_pulses[ i ] ]; + } + } + if( sumBits_Q5 < minSumBits_Q5 ) { + minSumBits_Q5 = sumBits_Q5; + RateLevelIndex = k; + } + } + ec_enc_icdf( psRangeEnc, RateLevelIndex, silk_rate_levels_iCDF[ signalType >> 1 ], 8 ); + + /***************************************************/ + /* Sum-Weighted-Pulses Encoding */ + /***************************************************/ + cdf_ptr = silk_pulses_per_block_iCDF[ RateLevelIndex ]; + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] == 0 ) { + ec_enc_icdf( psRangeEnc, sum_pulses[ i ], cdf_ptr, 8 ); + } else { + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, cdf_ptr, 8 ); + for( k = 0; k < nRshifts[ i ] - 1; k++ ) { + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); + } + ec_enc_icdf( psRangeEnc, sum_pulses[ i ], silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); + } + } + + /******************/ + /* Shell Encoding */ + /******************/ + for( i = 0; i < iter; i++ ) { + if( sum_pulses[ i ] > 0 ) { + silk_shell_encoder( psRangeEnc, &abs_pulses[ i * SHELL_CODEC_FRAME_LENGTH ] ); + } + } + + /****************/ + /* LSB Encoding */ + /****************/ + for( i = 0; i < iter; i++ ) { + if( nRshifts[ i ] > 0 ) { + pulses_ptr = &pulses[ i * SHELL_CODEC_FRAME_LENGTH ]; + nLS = nRshifts[ i ] - 1; + for( k = 0; k < SHELL_CODEC_FRAME_LENGTH; k++ ) { + abs_q = (opus_int8)silk_abs( pulses_ptr[ k ] ); + for( j = nLS; j > 0; j-- ) { + bit = silk_RSHIFT( abs_q, j ) & 1; + ec_enc_icdf( psRangeEnc, bit, silk_lsb_iCDF, 8 ); + } + bit = abs_q & 1; + ec_enc_icdf( psRangeEnc, bit, silk_lsb_iCDF, 8 ); + } + } + } + + /****************/ + /* Encode signs */ + /****************/ + silk_encode_signs( psRangeEnc, pulses, frame_length, signalType, quantOffsetType, sum_pulses ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/errors.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/errors.h new file mode 100644 index 0000000000..45070800f2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/errors.h @@ -0,0 +1,98 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_ERRORS_H +#define SILK_ERRORS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/******************/ +/* Error messages */ +/******************/ +#define SILK_NO_ERROR 0 + +/**************************/ +/* Encoder error messages */ +/**************************/ + +/* Input length is not a multiple of 10 ms, or length is longer than the packet length */ +#define SILK_ENC_INPUT_INVALID_NO_OF_SAMPLES -101 + +/* Sampling frequency not 8000, 12000 or 16000 Hertz */ +#define SILK_ENC_FS_NOT_SUPPORTED -102 + +/* Packet size not 10, 20, 40, or 60 ms */ +#define SILK_ENC_PACKET_SIZE_NOT_SUPPORTED -103 + +/* Allocated payload buffer too short */ +#define SILK_ENC_PAYLOAD_BUF_TOO_SHORT -104 + +/* Loss rate not between 0 and 100 percent */ +#define SILK_ENC_INVALID_LOSS_RATE -105 + +/* Complexity setting not valid, use 0...10 */ +#define SILK_ENC_INVALID_COMPLEXITY_SETTING -106 + +/* Inband FEC setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_INBAND_FEC_SETTING -107 + +/* DTX setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_DTX_SETTING -108 + +/* CBR setting not valid, use 0 or 1 */ +#define SILK_ENC_INVALID_CBR_SETTING -109 + +/* Internal encoder error */ +#define SILK_ENC_INTERNAL_ERROR -110 + +/* Internal encoder error */ +#define SILK_ENC_INVALID_NUMBER_OF_CHANNELS_ERROR -111 + +/**************************/ +/* Decoder error messages */ +/**************************/ + +/* Output sampling frequency lower than internal decoded sampling frequency */ +#define SILK_DEC_INVALID_SAMPLING_FREQUENCY -200 + +/* Payload size exceeded the maximum allowed 1024 bytes */ +#define SILK_DEC_PAYLOAD_TOO_LARGE -201 + +/* Payload has bit errors */ +#define SILK_DEC_PAYLOAD_ERROR -202 + +/* Payload has bit errors */ +#define SILK_DEC_INVALID_FRAME_SIZE -203 + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_analysis_filter_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_analysis_filter_FIX.c new file mode 100644 index 0000000000..5574e7069f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_analysis_filter_FIX.c @@ -0,0 +1,90 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +void silk_LTP_analysis_filter_FIX( + opus_int16 *LTP_res, /* O LTP residual signal of length MAX_NB_SUBFR * ( pre_length + subfr_length ) */ + const opus_int16 *x, /* I Pointer to input signal with at least max( pitchL ) preceding samples */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ],/* I LTP_ORDER LTP coefficients for each MAX_NB_SUBFR subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag, one for each subframe */ + const opus_int32 invGains_Q16[ MAX_NB_SUBFR ], /* I Inverse quantization gains, one for each subframe */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + const opus_int pre_length /* I Length of the preceding samples starting at &x[0] for each subframe */ +) +{ + const opus_int16 *x_ptr, *x_lag_ptr; + opus_int16 Btmp_Q14[ LTP_ORDER ]; + opus_int16 *LTP_res_ptr; + opus_int k, i; + opus_int32 LTP_est; + + x_ptr = x; + LTP_res_ptr = LTP_res; + for( k = 0; k < nb_subfr; k++ ) { + + x_lag_ptr = x_ptr - pitchL[ k ]; + + Btmp_Q14[ 0 ] = LTPCoef_Q14[ k * LTP_ORDER ]; + Btmp_Q14[ 1 ] = LTPCoef_Q14[ k * LTP_ORDER + 1 ]; + Btmp_Q14[ 2 ] = LTPCoef_Q14[ k * LTP_ORDER + 2 ]; + Btmp_Q14[ 3 ] = LTPCoef_Q14[ k * LTP_ORDER + 3 ]; + Btmp_Q14[ 4 ] = LTPCoef_Q14[ k * LTP_ORDER + 4 ]; + + /* LTP analysis FIR filter */ + for( i = 0; i < subfr_length + pre_length; i++ ) { + LTP_res_ptr[ i ] = x_ptr[ i ]; + + /* Long-term prediction */ + LTP_est = silk_SMULBB( x_lag_ptr[ LTP_ORDER / 2 ], Btmp_Q14[ 0 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ 1 ], Btmp_Q14[ 1 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ 0 ], Btmp_Q14[ 2 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ -1 ], Btmp_Q14[ 3 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ -2 ], Btmp_Q14[ 4 ] ); + + LTP_est = silk_RSHIFT_ROUND( LTP_est, 14 ); /* round and -> Q0*/ + + /* Subtract long-term prediction */ + LTP_res_ptr[ i ] = (opus_int16)silk_SAT16( (opus_int32)x_ptr[ i ] - LTP_est ); + + /* Scale residual */ + LTP_res_ptr[ i ] = silk_SMULWB( invGains_Q16[ k ], LTP_res_ptr[ i ] ); + + x_lag_ptr++; + } + + /* Update pointers */ + LTP_res_ptr += subfr_length + pre_length; + x_ptr += subfr_length; + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_scale_ctrl_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_scale_ctrl_FIX.c new file mode 100644 index 0000000000..db1016e0b2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/LTP_scale_ctrl_FIX.c @@ -0,0 +1,58 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +/* Calculation of LTP state scaling */ +void silk_LTP_scale_ctrl_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int round_loss; + + if( condCoding == CODE_INDEPENDENTLY ) { + /* Only scale if first frame in packet */ + round_loss = psEnc->sCmn.PacketLoss_perc * psEnc->sCmn.nFramesPerPacket; + if ( psEnc->sCmn.LBRR_flag ) { + /* LBRR reduces the effective loss. In practice, it does not square the loss because + losses aren't independent, but that still seems to work best. We also never go below 2%. */ + round_loss = 2 + silk_SMULBB( round_loss, round_loss ) / 100; + } + psEnc->sCmn.indices.LTP_scaleIndex = silk_SMULBB( psEncCtrl->LTPredCodGain_Q7, round_loss ) > silk_log2lin( 128*7 + 2900-psEnc->sCmn.SNR_dB_Q7 ); + psEnc->sCmn.indices.LTP_scaleIndex += silk_SMULBB( psEncCtrl->LTPredCodGain_Q7, round_loss ) > silk_log2lin( 128*7 + 3900-psEnc->sCmn.SNR_dB_Q7 ); + } else { + /* Default is minimum scaling */ + psEnc->sCmn.indices.LTP_scaleIndex = 0; + } + psEncCtrl->LTP_scale_Q14 = silk_LTPScales_table_Q14[ psEnc->sCmn.indices.LTP_scaleIndex ]; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/apply_sine_window_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/apply_sine_window_FIX.c new file mode 100644 index 0000000000..03e088a6de --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/apply_sine_window_FIX.c @@ -0,0 +1,101 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Apply sine window to signal vector. */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +/* Every other sample is linearly interpolated, for speed. */ +/* Window length must be between 16 and 120 (incl) and a multiple of 4. */ + +/* Matlab code for table: + for k=16:9*4:16+2*9*4, fprintf(' %7.d,', -round(65536*pi ./ (k:4:k+8*4))); fprintf('\n'); end +*/ +static const opus_int16 freq_table_Q16[ 27 ] = { + 12111, 9804, 8235, 7100, 6239, 5565, 5022, 4575, 4202, + 3885, 3612, 3375, 3167, 2984, 2820, 2674, 2542, 2422, + 2313, 2214, 2123, 2038, 1961, 1889, 1822, 1760, 1702, +}; + +void silk_apply_sine_window( + opus_int16 px_win[], /* O Pointer to windowed signal */ + const opus_int16 px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +) +{ + opus_int k, f_Q16, c_Q16; + opus_int32 S0_Q16, S1_Q16; + + celt_assert( win_type == 1 || win_type == 2 ); + + /* Length must be in a range from 16 to 120 and a multiple of 4 */ + celt_assert( length >= 16 && length <= 120 ); + celt_assert( ( length & 3 ) == 0 ); + + /* Frequency */ + k = ( length >> 2 ) - 4; + celt_assert( k >= 0 && k <= 26 ); + f_Q16 = (opus_int)freq_table_Q16[ k ]; + + /* Factor used for cosine approximation */ + c_Q16 = silk_SMULWB( (opus_int32)f_Q16, -f_Q16 ); + silk_assert( c_Q16 >= -32768 ); + + /* initialize state */ + if( win_type == 1 ) { + /* start from 0 */ + S0_Q16 = 0; + /* approximation of sin(f) */ + S1_Q16 = f_Q16 + silk_RSHIFT( length, 3 ); + } else { + /* start from 1 */ + S0_Q16 = ( (opus_int32)1 << 16 ); + /* approximation of cos(f) */ + S1_Q16 = ( (opus_int32)1 << 16 ) + silk_RSHIFT( c_Q16, 1 ) + silk_RSHIFT( length, 4 ); + } + + /* Uses the recursive equation: sin(n*f) = 2 * cos(f) * sin((n-1)*f) - sin((n-2)*f) */ + /* 4 samples at a time */ + for( k = 0; k < length; k += 4 ) { + px_win[ k ] = (opus_int16)silk_SMULWB( silk_RSHIFT( S0_Q16 + S1_Q16, 1 ), px[ k ] ); + px_win[ k + 1 ] = (opus_int16)silk_SMULWB( S1_Q16, px[ k + 1] ); + S0_Q16 = silk_SMULWB( S1_Q16, c_Q16 ) + silk_LSHIFT( S1_Q16, 1 ) - S0_Q16 + 1; + S0_Q16 = silk_min( S0_Q16, ( (opus_int32)1 << 16 ) ); + + px_win[ k + 2 ] = (opus_int16)silk_SMULWB( silk_RSHIFT( S0_Q16 + S1_Q16, 1 ), px[ k + 2] ); + px_win[ k + 3 ] = (opus_int16)silk_SMULWB( S0_Q16, px[ k + 3 ] ); + S1_Q16 = silk_SMULWB( S0_Q16, c_Q16 ) + silk_LSHIFT( S0_Q16, 1 ) - S1_Q16; + S1_Q16 = silk_min( S1_Q16, ( (opus_int32)1 << 16 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_arm.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_arm.h new file mode 100644 index 0000000000..1992e43288 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_arm.h @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_WARPED_AUTOCORRELATION_FIX_ARM_H +# define SILK_WARPED_AUTOCORRELATION_FIX_ARM_H + +# include "celt/arm/armcpu.h" + +# if defined(FIXED_POINT) + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void silk_warped_autocorrelation_FIX_neon( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +); + +# if !defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_PRESUME_NEON) +# define OVERRIDE_silk_warped_autocorrelation_FIX (1) +# define silk_warped_autocorrelation_FIX(corr, scale, input, warping_Q16, length, order, arch) \ + ((void)(arch), PRESUME_NEON(silk_warped_autocorrelation_FIX)(corr, scale, input, warping_Q16, length, order)) +# endif +# endif + +# if !defined(OVERRIDE_silk_warped_autocorrelation_FIX) +/*Is run-time CPU detection enabled on this platform?*/ +# if defined(OPUS_HAVE_RTCD) && (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void (*const SILK_WARPED_AUTOCORRELATION_FIX_IMPL[OPUS_ARCHMASK+1])(opus_int32*, opus_int*, const opus_int16*, const opus_int, const opus_int, const opus_int); +# define OVERRIDE_silk_warped_autocorrelation_FIX (1) +# define silk_warped_autocorrelation_FIX(corr, scale, input, warping_Q16, length, order, arch) \ + ((*SILK_WARPED_AUTOCORRELATION_FIX_IMPL[(arch)&OPUS_ARCHMASK])(corr, scale, input, warping_Q16, length, order)) +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_silk_warped_autocorrelation_FIX (1) +# define silk_warped_autocorrelation_FIX(corr, scale, input, warping_Q16, length, order, arch) \ + ((void)(arch), silk_warped_autocorrelation_FIX_neon(corr, scale, input, warping_Q16, length, order)) +# endif +# endif + +# endif /* end FIXED_POINT */ + +#endif /* end SILK_WARPED_AUTOCORRELATION_FIX_ARM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_neon_intr.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_neon_intr.c new file mode 100644 index 0000000000..6f3be025cc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/arm/warped_autocorrelation_FIX_neon_intr.c @@ -0,0 +1,265 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc., Jean-Marc Valin +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef OPUS_CHECK_ASM +# include +#endif +#include "stack_alloc.h" +#include "main_FIX.h" + +static OPUS_INLINE void calc_corr( const opus_int32 *const input_QS, opus_int64 *const corr_QC, const opus_int offset, const int32x4_t state_QS_s32x4 ) +{ + int64x2_t corr_QC_s64x2[ 2 ], t_s64x2[ 2 ]; + const int32x4_t input_QS_s32x4 = vld1q_s32( input_QS + offset ); + corr_QC_s64x2[ 0 ] = vld1q_s64( corr_QC + offset + 0 ); + corr_QC_s64x2[ 1 ] = vld1q_s64( corr_QC + offset + 2 ); + t_s64x2[ 0 ] = vmull_s32( vget_low_s32( state_QS_s32x4 ), vget_low_s32( input_QS_s32x4 ) ); + t_s64x2[ 1 ] = vmull_s32( vget_high_s32( state_QS_s32x4 ), vget_high_s32( input_QS_s32x4 ) ); + corr_QC_s64x2[ 0 ] = vsraq_n_s64( corr_QC_s64x2[ 0 ], t_s64x2[ 0 ], 2 * QS - QC ); + corr_QC_s64x2[ 1 ] = vsraq_n_s64( corr_QC_s64x2[ 1 ], t_s64x2[ 1 ], 2 * QS - QC ); + vst1q_s64( corr_QC + offset + 0, corr_QC_s64x2[ 0 ] ); + vst1q_s64( corr_QC + offset + 2, corr_QC_s64x2[ 1 ] ); +} + +static OPUS_INLINE int32x4_t calc_state( const int32x4_t state_QS0_s32x4, const int32x4_t state_QS0_1_s32x4, const int32x4_t state_QS1_1_s32x4, const int32x4_t warping_Q16_s32x4 ) +{ + int32x4_t t_s32x4 = vsubq_s32( state_QS0_s32x4, state_QS0_1_s32x4 ); + t_s32x4 = vqdmulhq_s32( t_s32x4, warping_Q16_s32x4 ); + return vaddq_s32( state_QS1_1_s32x4, t_s32x4 ); +} + +void silk_warped_autocorrelation_FIX_neon( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) +{ + if( ( MAX_SHAPE_LPC_ORDER > 24 ) || ( order < 6 ) ) { + silk_warped_autocorrelation_FIX_c( corr, scale, input, warping_Q16, length, order ); + } else { + opus_int n, i, lsh; + opus_int64 corr_QC[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; /* In reverse order */ + opus_int64 corr_QC_orderT; + int64x2_t lsh_s64x2; + const opus_int orderT = ( order + 3 ) & ~3; + opus_int64 *corr_QCT; + opus_int32 *input_QS; + VARDECL( opus_int32, input_QST ); + VARDECL( opus_int32, state ); + SAVE_STACK; + + /* Order must be even */ + silk_assert( ( order & 1 ) == 0 ); + silk_assert( 2 * QS - QC >= 0 ); + + /* The additional +4 is to ensure a later vld1q_s32 call does not overflow. */ + /* Strictly, only +3 is needed but +4 simplifies initialization using the 4x32 neon load. */ + ALLOC( input_QST, length + 2 * MAX_SHAPE_LPC_ORDER + 4, opus_int32 ); + + input_QS = input_QST; + /* input_QS has zero paddings in the beginning and end. */ + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + + /* Loop over samples */ + for( n = 0; n < length - 7; n += 8, input_QS += 8 ) { + const int16x8_t t0_s16x4 = vld1q_s16( input + n ); + vst1q_s32( input_QS + 0, vshll_n_s16( vget_low_s16( t0_s16x4 ), QS ) ); + vst1q_s32( input_QS + 4, vshll_n_s16( vget_high_s16( t0_s16x4 ), QS ) ); + } + for( ; n < length; n++, input_QS++ ) { + input_QS[ 0 ] = silk_LSHIFT32( (opus_int32)input[ n ], QS ); + } + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS += 4; + vst1q_s32( input_QS, vdupq_n_s32( 0 ) ); + input_QS = input_QST + MAX_SHAPE_LPC_ORDER - orderT; + + /* The following loop runs ( length + order ) times, with ( order ) extra epilogues. */ + /* The zero paddings in input_QS guarantee corr_QC's correctness even with the extra epilogues. */ + /* The values of state_QS will be polluted by the extra epilogues, however they are temporary values. */ + + /* Keep the C code here to help understand the intrinsics optimization. */ + /* + { + opus_int32 state_QS[ 2 ][ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + opus_int32 *state_QST[ 3 ]; + state_QST[ 0 ] = state_QS[ 0 ]; + state_QST[ 1 ] = state_QS[ 1 ]; + for( n = 0; n < length + order; n++, input_QS++ ) { + state_QST[ 0 ][ orderT ] = input_QS[ orderT ]; + for( i = 0; i < orderT; i++ ) { + corr_QC[ i ] += silk_RSHIFT64( silk_SMULL( state_QST[ 0 ][ i ], input_QS[ i ] ), 2 * QS - QC ); + state_QST[ 1 ][ i ] = silk_SMLAWB( state_QST[ 1 ][ i + 1 ], state_QST[ 0 ][ i ] - state_QST[ 0 ][ i + 1 ], warping_Q16 ); + } + state_QST[ 2 ] = state_QST[ 0 ]; + state_QST[ 0 ] = state_QST[ 1 ]; + state_QST[ 1 ] = state_QST[ 2 ]; + } + } + */ + + { + const int32x4_t warping_Q16_s32x4 = vdupq_n_s32( warping_Q16 << 15 ); + const opus_int32 *in = input_QS + orderT; + opus_int o = orderT; + int32x4_t state_QS_s32x4[ 3 ][ 2 ]; + + /* The additional +4 is to ensure a later vld1q_s32 call does not overflow. */ + ALLOC( state, length + order + 4, opus_int32 ); + state_QS_s32x4[ 2 ][ 1 ] = vdupq_n_s32( 0 ); + + /* Calculate 8 taps of all inputs in each loop. */ + do { + state_QS_s32x4[ 0 ][ 0 ] = state_QS_s32x4[ 0 ][ 1 ] = + state_QS_s32x4[ 1 ][ 0 ] = state_QS_s32x4[ 1 ][ 1 ] = vdupq_n_s32( 0 ); + n = 0; + do { + calc_corr( input_QS + n, corr_QC, o - 8, state_QS_s32x4[ 0 ][ 0 ] ); + calc_corr( input_QS + n, corr_QC, o - 4, state_QS_s32x4[ 0 ][ 1 ] ); + state_QS_s32x4[ 2 ][ 1 ] = vld1q_s32( in + n ); + vst1q_lane_s32( state + n, state_QS_s32x4[ 0 ][ 0 ], 0 ); + state_QS_s32x4[ 2 ][ 0 ] = vextq_s32( state_QS_s32x4[ 0 ][ 0 ], state_QS_s32x4[ 0 ][ 1 ], 1 ); + state_QS_s32x4[ 2 ][ 1 ] = vextq_s32( state_QS_s32x4[ 0 ][ 1 ], state_QS_s32x4[ 2 ][ 1 ], 1 ); + state_QS_s32x4[ 0 ][ 0 ] = calc_state( state_QS_s32x4[ 0 ][ 0 ], state_QS_s32x4[ 2 ][ 0 ], state_QS_s32x4[ 1 ][ 0 ], warping_Q16_s32x4 ); + state_QS_s32x4[ 0 ][ 1 ] = calc_state( state_QS_s32x4[ 0 ][ 1 ], state_QS_s32x4[ 2 ][ 1 ], state_QS_s32x4[ 1 ][ 1 ], warping_Q16_s32x4 ); + state_QS_s32x4[ 1 ][ 0 ] = state_QS_s32x4[ 2 ][ 0 ]; + state_QS_s32x4[ 1 ][ 1 ] = state_QS_s32x4[ 2 ][ 1 ]; + } while( ++n < ( length + order ) ); + in = state; + o -= 8; + } while( o > 4 ); + + if( o ) { + /* Calculate the last 4 taps of all inputs. */ + opus_int32 *stateT = state; + silk_assert( o == 4 ); + state_QS_s32x4[ 0 ][ 0 ] = state_QS_s32x4[ 1 ][ 0 ] = vdupq_n_s32( 0 ); + n = length + order; + do { + calc_corr( input_QS, corr_QC, 0, state_QS_s32x4[ 0 ][ 0 ] ); + state_QS_s32x4[ 2 ][ 0 ] = vld1q_s32( stateT ); + vst1q_lane_s32( stateT, state_QS_s32x4[ 0 ][ 0 ], 0 ); + state_QS_s32x4[ 2 ][ 0 ] = vextq_s32( state_QS_s32x4[ 0 ][ 0 ], state_QS_s32x4[ 2 ][ 0 ], 1 ); + state_QS_s32x4[ 0 ][ 0 ] = calc_state( state_QS_s32x4[ 0 ][ 0 ], state_QS_s32x4[ 2 ][ 0 ], state_QS_s32x4[ 1 ][ 0 ], warping_Q16_s32x4 ); + state_QS_s32x4[ 1 ][ 0 ] = state_QS_s32x4[ 2 ][ 0 ]; + input_QS++; + stateT++; + } while( --n ); + } + } + + { + const opus_int16 *inputT = input; + int32x4_t t_s32x4; + int64x1_t t_s64x1; + int64x2_t t_s64x2 = vdupq_n_s64( 0 ); + for( n = 0; n <= length - 8; n += 8 ) { + int16x8_t input_s16x8 = vld1q_s16( inputT ); + t_s32x4 = vmull_s16( vget_low_s16( input_s16x8 ), vget_low_s16( input_s16x8 ) ); + t_s32x4 = vmlal_s16( t_s32x4, vget_high_s16( input_s16x8 ), vget_high_s16( input_s16x8 ) ); + t_s64x2 = vaddw_s32( t_s64x2, vget_low_s32( t_s32x4 ) ); + t_s64x2 = vaddw_s32( t_s64x2, vget_high_s32( t_s32x4 ) ); + inputT += 8; + } + t_s64x1 = vadd_s64( vget_low_s64( t_s64x2 ), vget_high_s64( t_s64x2 ) ); + corr_QC_orderT = vget_lane_s64( t_s64x1, 0 ); + for( ; n < length; n++ ) { + corr_QC_orderT += silk_SMULL( input[ n ], input[ n ] ); + } + corr_QC_orderT = silk_LSHIFT64( corr_QC_orderT, QC ); + corr_QC[ orderT ] = corr_QC_orderT; + } + + corr_QCT = corr_QC + orderT - order; + lsh = silk_CLZ64( corr_QC_orderT ) - 35; + lsh = silk_LIMIT( lsh, -12 - QC, 30 - QC ); + *scale = -( QC + lsh ); + silk_assert( *scale >= -30 && *scale <= 12 ); + lsh_s64x2 = vdupq_n_s64( lsh ); + for( i = 0; i <= order - 3; i += 4 ) { + int32x4_t corr_s32x4; + int64x2_t corr_QC0_s64x2, corr_QC1_s64x2; + corr_QC0_s64x2 = vld1q_s64( corr_QCT + i ); + corr_QC1_s64x2 = vld1q_s64( corr_QCT + i + 2 ); + corr_QC0_s64x2 = vshlq_s64( corr_QC0_s64x2, lsh_s64x2 ); + corr_QC1_s64x2 = vshlq_s64( corr_QC1_s64x2, lsh_s64x2 ); + corr_s32x4 = vcombine_s32( vmovn_s64( corr_QC1_s64x2 ), vmovn_s64( corr_QC0_s64x2 ) ); + corr_s32x4 = vrev64q_s32( corr_s32x4 ); + vst1q_s32( corr + order - i - 3, corr_s32x4 ); + } + if( lsh >= 0 ) { + for( ; i < order + 1; i++ ) { + corr[ order - i ] = (opus_int32)silk_CHECK_FIT32( silk_LSHIFT64( corr_QCT[ i ], lsh ) ); + } + } else { + for( ; i < order + 1; i++ ) { + corr[ order - i ] = (opus_int32)silk_CHECK_FIT32( silk_RSHIFT64( corr_QCT[ i ], -lsh ) ); + } + } + silk_assert( corr_QCT[ order ] >= 0 ); /* If breaking, decrease QC*/ + RESTORE_STACK; + } + +#ifdef OPUS_CHECK_ASM + { + opus_int32 corr_c[ MAX_SHAPE_LPC_ORDER + 1 ]; + opus_int scale_c; + silk_warped_autocorrelation_FIX_c( corr_c, &scale_c, input, warping_Q16, length, order ); + silk_assert( !memcmp( corr_c, corr, sizeof( corr_c[ 0 ] ) * ( order + 1 ) ) ); + silk_assert( scale_c == *scale ); + } +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/autocorr_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/autocorr_FIX.c new file mode 100644 index 0000000000..de95c98693 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/autocorr_FIX.c @@ -0,0 +1,48 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "celt_lpc.h" + +/* Compute autocorrelation */ +void silk_autocorr( + opus_int32 *results, /* O Result (length correlationCount) */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *inputData, /* I Input data to correlate */ + const opus_int inputDataSize, /* I Length of input */ + const opus_int correlationCount, /* I Number of correlation taps to compute */ + int arch /* I Run-time architecture */ +) +{ + opus_int corrCount; + corrCount = silk_min_int( inputDataSize, correlationCount ); + *scale = _celt_autocorr(inputData, results, NULL, 0, corrCount-1, inputDataSize, arch); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/burg_modified_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/burg_modified_FIX.c new file mode 100644 index 0000000000..185a12b178 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/burg_modified_FIX.c @@ -0,0 +1,280 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "define.h" +#include "tuning_parameters.h" +#include "pitch.h" + +#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384 */ + +#define QA 25 +#define N_BITS_HEAD_ROOM 3 +#define MIN_RSHIFTS -16 +#define MAX_RSHIFTS (32 - QA) + +/* Compute reflection coefficients from input signal */ +void silk_burg_modified_c( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +) +{ + opus_int k, n, s, lz, rshifts, reached_max_gain; + opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2; + const opus_int16 *x_ptr; + opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 C_last_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 Af_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; + opus_int64 C0_64; + + celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); + + /* Compute autocorrelations, added over subframes */ + C0_64 = silk_inner_prod16( x, x, subfr_length*nb_subfr, arch ); + lz = silk_CLZ64(C0_64); + rshifts = 32 + 1 + N_BITS_HEAD_ROOM - lz; + if (rshifts > MAX_RSHIFTS) rshifts = MAX_RSHIFTS; + if (rshifts < MIN_RSHIFTS) rshifts = MIN_RSHIFTS; + + if (rshifts > 0) { + C0 = (opus_int32)silk_RSHIFT64(C0_64, rshifts ); + } else { + C0 = silk_LSHIFT32((opus_int32)C0_64, -rshifts ); + } + + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += (opus_int32)silk_RSHIFT64( + silk_inner_prod16( x_ptr, x_ptr + n, subfr_length - n, arch ), rshifts ); + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + int i; + opus_int32 d; + x_ptr = x + s * subfr_length; + celt_pitch_xcorr(x_ptr, x_ptr + 1, xcorr, subfr_length - D, D, arch ); + for( n = 1; n < D + 1; n++ ) { + for ( i = n + subfr_length - D, d = 0; i < subfr_length; i++ ) + d = MAC16_16( d, x_ptr[ i ], x_ptr[ i - n ] ); + xcorr[ n - 1 ] += d; + } + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += silk_LSHIFT32( xcorr[ n - 1 ], -rshifts ); + } + } + } + silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + + /* Initialize */ + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + + invGain_Q30 = (opus_int32)1 << 30; + reached_max_gain = 0; + for( n = 0; n < D; n++ ) { + /* Update first row of correlation matrix (without first element) */ + /* Update last row of correlation matrix (without last element, stored in reversed order) */ + /* Update C * Af */ + /* Update C * flipud(Af) (stored in reversed order) */ + if( rshifts > -2 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], 16 - rshifts ); /* Q(16-rshifts) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 16 - rshifts ); /* Q(16-rshifts) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], QA - 16 ); /* Q(QA-16) */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], QA - 16 ); /* Q(QA-16) */ + for( k = 0; k < n; k++ ) { + C_first_row[ k ] = silk_SMLAWB( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_SMLAWB( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp_QA = Af_QA[ k ]; + tmp1 = silk_SMLAWB( tmp1, Atmp_QA, x_ptr[ n - k - 1 ] ); /* Q(QA-16) */ + tmp2 = silk_SMLAWB( tmp2, Atmp_QA, x_ptr[ subfr_length - n + k ] ); /* Q(QA-16) */ + } + tmp1 = silk_LSHIFT32( -tmp1, 32 - QA - rshifts ); /* Q(16-rshifts) */ + tmp2 = silk_LSHIFT32( -tmp2, 32 - QA - rshifts ); /* Q(16-rshifts) */ + for( k = 0; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWB( CAf[ k ], tmp1, x_ptr[ n - k ] ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWB( CAb[ k ], tmp2, x_ptr[ subfr_length - n + k - 1 ] ); /* Q( -rshift ) */ + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], -rshifts ); /* Q( -rshifts ) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], -rshifts ); /* Q( -rshifts ) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], 17 ); /* Q17 */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 17 ); /* Q17 */ + for( k = 0; k < n; k++ ) { + C_first_row[ k ] = silk_MLA( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_MLA( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 17 ); /* Q17 */ + /* We sometimes get overflows in the multiplications (even beyond +/- 2^32), + but they cancel each other and the real result seems to always fit in a 32-bit + signed integer. This was determined experimentally, not theoretically (unfortunately). */ + tmp1 = silk_MLA_ovflw( tmp1, x_ptr[ n - k - 1 ], Atmp1 ); /* Q17 */ + tmp2 = silk_MLA_ovflw( tmp2, x_ptr[ subfr_length - n + k ], Atmp1 ); /* Q17 */ + } + tmp1 = -tmp1; /* Q17 */ + tmp2 = -tmp2; /* Q17 */ + for( k = 0; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWW( CAf[ k ], tmp1, + silk_LSHIFT32( (opus_int32)x_ptr[ n - k ], -rshifts - 1 ) ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWW( CAb[ k ], tmp2, + silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n + k - 1 ], -rshifts - 1 ) ); /* Q( -rshift ) */ + } + } + } + + /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */ + tmp1 = C_first_row[ n ]; /* Q( -rshifts ) */ + tmp2 = C_last_row[ n ]; /* Q( -rshifts ) */ + num = 0; /* Q( -rshifts ) */ + nrg = silk_ADD32( CAb[ 0 ], CAf[ 0 ] ); /* Q( 1-rshifts ) */ + for( k = 0; k < n; k++ ) { + Atmp_QA = Af_QA[ k ]; + lz = silk_CLZ32( silk_abs( Atmp_QA ) ) - 1; + lz = silk_min( 32 - QA, lz ); + Atmp1 = silk_LSHIFT32( Atmp_QA, lz ); /* Q( QA + lz ) */ + + tmp1 = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( C_last_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + tmp2 = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( C_first_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + num = silk_ADD_LSHIFT32( num, silk_SMMUL( CAb[ n - k ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + nrg = silk_ADD_LSHIFT32( nrg, silk_SMMUL( silk_ADD32( CAb[ k + 1 ], CAf[ k + 1 ] ), + Atmp1 ), 32 - QA - lz ); /* Q( 1-rshifts ) */ + } + CAf[ n + 1 ] = tmp1; /* Q( -rshifts ) */ + CAb[ n + 1 ] = tmp2; /* Q( -rshifts ) */ + num = silk_ADD32( num, tmp2 ); /* Q( -rshifts ) */ + num = silk_LSHIFT32( -num, 1 ); /* Q( 1-rshifts ) */ + + /* Calculate the next order reflection (parcor) coefficient */ + if( silk_abs( num ) < nrg ) { + rc_Q31 = silk_DIV32_varQ( num, nrg, 31 ); + } else { + rc_Q31 = ( num > 0 ) ? silk_int32_MAX : silk_int32_MIN; + } + + /* Update inverse prediction gain */ + tmp1 = ( (opus_int32)1 << 30 ) - silk_SMMUL( rc_Q31, rc_Q31 ); + tmp1 = silk_LSHIFT( silk_SMMUL( invGain_Q30, tmp1 ), 2 ); + if( tmp1 <= minInvGain_Q30 ) { + /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ + tmp2 = ( (opus_int32)1 << 30 ) - silk_DIV32_varQ( minInvGain_Q30, invGain_Q30, 30 ); /* Q30 */ + rc_Q31 = silk_SQRT_APPROX( tmp2 ); /* Q15 */ + if( rc_Q31 > 0 ) { + /* Newton-Raphson iteration */ + rc_Q31 = silk_RSHIFT32( rc_Q31 + silk_DIV32( tmp2, rc_Q31 ), 1 ); /* Q15 */ + rc_Q31 = silk_LSHIFT32( rc_Q31, 16 ); /* Q31 */ + if( num < 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc_Q31 = -rc_Q31; + } + } + invGain_Q30 = minInvGain_Q30; + reached_max_gain = 1; + } else { + invGain_Q30 = tmp1; + } + + /* Update the AR coefficients */ + for( k = 0; k < (n + 1) >> 1; k++ ) { + tmp1 = Af_QA[ k ]; /* QA */ + tmp2 = Af_QA[ n - k - 1 ]; /* QA */ + Af_QA[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* QA */ + Af_QA[ n - k - 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* QA */ + } + Af_QA[ n ] = silk_RSHIFT32( rc_Q31, 31 - QA ); /* QA */ + + if( reached_max_gain ) { + /* Reached max prediction gain; set remaining coefficients to zero and exit loop */ + for( k = n + 1; k < D; k++ ) { + Af_QA[ k ] = 0; + } + break; + } + + /* Update C * Af and C * Ab */ + for( k = 0; k <= n + 1; k++ ) { + tmp1 = CAf[ k ]; /* Q( -rshifts ) */ + tmp2 = CAb[ n - k + 1 ]; /* Q( -rshifts ) */ + CAf[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + CAb[ n - k + 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + } + } + + if( reached_max_gain ) { + for( k = 0; k < D; k++ ) { + /* Scale coefficients */ + A_Q16[ k ] = -silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); + } + /* Subtract energy of preceding samples from C0 */ + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= (opus_int32)silk_RSHIFT64( silk_inner_prod16( x_ptr, x_ptr, D, arch ), rshifts ); + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= silk_LSHIFT32( silk_inner_prod_aligned( x_ptr, x_ptr, D, arch), -rshifts); + } + } + /* Approximate residual energy */ + *res_nrg = silk_LSHIFT( silk_SMMUL( invGain_Q30, C0 ), 2 ); + *res_nrg_Q = -rshifts; + } else { + /* Return residual energy */ + nrg = CAf[ 0 ]; /* Q( -rshifts ) */ + tmp1 = (opus_int32)1 << 16; /* Q16 */ + for( k = 0; k < D; k++ ) { + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); /* Q16 */ + nrg = silk_SMLAWW( nrg, CAf[ k + 1 ], Atmp1 ); /* Q( -rshifts ) */ + tmp1 = silk_SMLAWW( tmp1, Atmp1, Atmp1 ); /* Q16 */ + A_Q16[ k ] = -Atmp1; + } + *res_nrg = silk_SMLAWW( nrg, silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ), -tmp1 );/* Q( -rshifts ) */ + *res_nrg_Q = -rshifts; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/corrMatrix_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/corrMatrix_FIX.c new file mode 100644 index 0000000000..1b4a29c232 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/corrMatrix_FIX.c @@ -0,0 +1,150 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/********************************************************************** + * Correlation Matrix Computations for LS estimate. + **********************************************************************/ + +#include "main_FIX.h" + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FIX( + const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ + const opus_int16 *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vectors */ + const opus_int order, /* I Max lag for correlation */ + opus_int32 *Xt, /* O Pointer to X'*t correlation vector [order] */ + const opus_int rshifts, /* I Right shifts of correlations */ + int arch /* I Run-time architecture */ +) +{ + opus_int lag, i; + const opus_int16 *ptr1, *ptr2; + opus_int32 inner_prod; + + ptr1 = &x[ order - 1 ]; /* Points to first sample of column 0 of X: X[:,0] */ + ptr2 = t; + /* Calculate X'*t */ + if( rshifts > 0 ) { + /* Right shifting used */ + for( lag = 0; lag < order; lag++ ) { + inner_prod = 0; + for( i = 0; i < L; i++ ) { + inner_prod = silk_ADD_RSHIFT32( inner_prod, silk_SMULBB( ptr1[ i ], ptr2[i] ), rshifts ); + } + Xt[ lag ] = inner_prod; /* X[:,lag]'*t */ + ptr1--; /* Go to next column of X */ + } + } else { + silk_assert( rshifts == 0 ); + for( lag = 0; lag < order; lag++ ) { + Xt[ lag ] = silk_inner_prod_aligned( ptr1, ptr2, L, arch ); /* X[:,lag]'*t */ + ptr1--; /* Go to next column of X */ + } + } +} + +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FIX( + const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ + const opus_int L, /* I Length of vectors */ + const opus_int order, /* I Max lag for correlation */ + opus_int32 *XX, /* O Pointer to X'*X correlation matrix [ order x order ] */ + opus_int32 *nrg, /* O Energy of x vector */ + opus_int *rshifts, /* O Right shifts of correlations and energy */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, j, lag; + opus_int32 energy; + const opus_int16 *ptr1, *ptr2; + + /* Calculate energy to find shift used to fit in 32 bits */ + silk_sum_sqr_shift( nrg, rshifts, x, L + order - 1 ); + energy = *nrg; + + /* Calculate energy of first column (0) of X: X[:,0]'*X[:,0] */ + /* Remove contribution of first order - 1 samples */ + for( i = 0; i < order - 1; i++ ) { + energy -= silk_RSHIFT32( silk_SMULBB( x[ i ], x[ i ] ), *rshifts ); + } + + /* Calculate energy of remaining columns of X: X[:,j]'*X[:,j] */ + /* Fill out the diagonal of the correlation matrix */ + matrix_ptr( XX, 0, 0, order ) = energy; + silk_assert( energy >= 0 ); + ptr1 = &x[ order - 1 ]; /* First sample of column 0 of X */ + for( j = 1; j < order; j++ ) { + energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr1[ L - j ] ), *rshifts ) ); + energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr1[ -j ] ), *rshifts ) ); + matrix_ptr( XX, j, j, order ) = energy; + silk_assert( energy >= 0 ); + } + + ptr2 = &x[ order - 2 ]; /* First sample of column 1 of X */ + /* Calculate the remaining elements of the correlation matrix */ + if( *rshifts > 0 ) { + /* Right shifting used */ + for( lag = 1; lag < order; lag++ ) { + /* Inner product of column 0 and column lag: X[:,0]'*X[:,lag] */ + energy = 0; + for( i = 0; i < L; i++ ) { + energy += silk_RSHIFT32( silk_SMULBB( ptr1[ i ], ptr2[i] ), *rshifts ); + } + /* Calculate remaining off diagonal: X[:,j]'*X[:,j + lag] */ + matrix_ptr( XX, lag, 0, order ) = energy; + matrix_ptr( XX, 0, lag, order ) = energy; + for( j = 1; j < ( order - lag ); j++ ) { + energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr2[ L - j ] ), *rshifts ) ); + energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr2[ -j ] ), *rshifts ) ); + matrix_ptr( XX, lag + j, j, order ) = energy; + matrix_ptr( XX, j, lag + j, order ) = energy; + } + ptr2--; /* Update pointer to first sample of next column (lag) in X */ + } + } else { + for( lag = 1; lag < order; lag++ ) { + /* Inner product of column 0 and column lag: X[:,0]'*X[:,lag] */ + energy = silk_inner_prod_aligned( ptr1, ptr2, L, arch ); + matrix_ptr( XX, lag, 0, order ) = energy; + matrix_ptr( XX, 0, lag, order ) = energy; + /* Calculate remaining off diagonal: X[:,j]'*X[:,j + lag] */ + for( j = 1; j < ( order - lag ); j++ ) { + energy = silk_SUB32( energy, silk_SMULBB( ptr1[ L - j ], ptr2[ L - j ] ) ); + energy = silk_SMLABB( energy, ptr1[ -j ], ptr2[ -j ] ); + matrix_ptr( XX, lag + j, j, order ) = energy; + matrix_ptr( XX, j, lag + j, order ) = energy; + } + ptr2--;/* Update pointer to first sample of next column (lag) in X */ + } + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/encode_frame_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/encode_frame_FIX.c new file mode 100644 index 0000000000..7c83360ba3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/encode_frame_FIX.c @@ -0,0 +1,444 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FIX.h" +#include "stack_alloc.h" +#include "tuning_parameters.h" + +/* Low Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode with lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Pointer to Silk FIX encoder control struct */ + const opus_int16 x16[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +); + +void silk_encode_do_VAD_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + opus_int activity /* I Decision of Opus voice activity detector */ +) +{ + const opus_int activity_threshold = SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ); + + /****************************/ + /* Voice Activity Detection */ + /****************************/ + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.arch ); + /* If Opus VAD is inactive and Silk VAD is active: lower Silk VAD to just under the threshold */ + if( activity == VAD_NO_ACTIVITY && psEnc->sCmn.speech_activity_Q8 >= activity_threshold ) { + psEnc->sCmn.speech_activity_Q8 = activity_threshold - 1; + } + + /**************************************************/ + /* Convert speech activity into VAD and DTX flags */ + /**************************************************/ + if( psEnc->sCmn.speech_activity_Q8 < activity_threshold ) { + psEnc->sCmn.indices.signalType = TYPE_NO_VOICE_ACTIVITY; + psEnc->sCmn.noSpeechCounter++; + if( psEnc->sCmn.noSpeechCounter <= NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.inDTX = 0; + } else if( psEnc->sCmn.noSpeechCounter > MAX_CONSECUTIVE_DTX + NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.noSpeechCounter = NB_SPEECH_FRAMES_BEFORE_DTX; + psEnc->sCmn.inDTX = 0; + } + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 0; + } else { + psEnc->sCmn.noSpeechCounter = 0; + psEnc->sCmn.inDTX = 0; + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + } +} + +/****************/ +/* Encode frame */ +/****************/ +opus_int silk_encode_frame_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + opus_int32 *pnBytesOut, /* O Pointer to number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +) +{ + silk_encoder_control_FIX sEncCtrl; + opus_int i, iter, maxIter, found_upper, found_lower, ret = 0; + opus_int16 *x_frame; + ec_enc sRangeEnc_copy, sRangeEnc_copy2; + silk_nsq_state sNSQ_copy, sNSQ_copy2; + opus_int32 seed_copy, nBits, nBits_lower, nBits_upper, gainMult_lower, gainMult_upper; + opus_int32 gainsID, gainsID_lower, gainsID_upper; + opus_int16 gainMult_Q8; + opus_int16 ec_prevLagIndex_copy; + opus_int ec_prevSignalType_copy; + opus_int8 LastGainIndex_copy2; + opus_int gain_lock[ MAX_NB_SUBFR ] = {0}; + opus_int16 best_gain_mult[ MAX_NB_SUBFR ]; + opus_int best_sum[ MAX_NB_SUBFR ]; + opus_int bits_margin; + SAVE_STACK; + + /* For CBR, 5 bits below budget is close enough. For VBR, allow up to 25% below the cap if we initially busted the budget. */ + bits_margin = useCBR ? 5 : maxBits/4; + /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ + LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0; + + psEnc->sCmn.indices.Seed = psEnc->sCmn.frameCounter++ & 3; + + /**************************************************************/ + /* Set up Input Pointers, and insert frame in input buffer */ + /*************************************************************/ + /* start of frame to encode */ + x_frame = psEnc->x_buf + psEnc->sCmn.ltp_mem_length; + + /***************************************/ + /* Ensure smooth bandwidth transitions */ + /***************************************/ + silk_LP_variable_cutoff( &psEnc->sCmn.sLP, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); + + /*******************************************/ + /* Copy new frame to front of input buffer */ + /*******************************************/ + silk_memcpy( x_frame + LA_SHAPE_MS * psEnc->sCmn.fs_kHz, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length * sizeof( opus_int16 ) ); + + if( !psEnc->sCmn.prefillFlag ) { + VARDECL( opus_int16, res_pitch ); + VARDECL( opus_uint8, ec_buf_copy ); + opus_int16 *res_pitch_frame; + + ALLOC( res_pitch, + psEnc->sCmn.la_pitch + psEnc->sCmn.frame_length + + psEnc->sCmn.ltp_mem_length, opus_int16 ); + /* start of pitch LPC residual frame */ + res_pitch_frame = res_pitch + psEnc->sCmn.ltp_mem_length; + + /*****************************************/ + /* Find pitch lags, initial LPC analysis */ + /*****************************************/ + silk_find_pitch_lags_FIX( psEnc, &sEncCtrl, res_pitch, x_frame - psEnc->sCmn.ltp_mem_length, psEnc->sCmn.arch ); + + /************************/ + /* Noise shape analysis */ + /************************/ + silk_noise_shape_analysis_FIX( psEnc, &sEncCtrl, res_pitch_frame, x_frame, psEnc->sCmn.arch ); + + /***************************************************/ + /* Find linear prediction coefficients (LPC + LTP) */ + /***************************************************/ + silk_find_pred_coefs_FIX( psEnc, &sEncCtrl, res_pitch_frame, x_frame, condCoding ); + + /****************************************/ + /* Process gains */ + /****************************************/ + silk_process_gains_FIX( psEnc, &sEncCtrl, condCoding ); + + /****************************************/ + /* Low Bitrate Redundant Encoding */ + /****************************************/ + silk_LBRR_encode_FIX( psEnc, &sEncCtrl, x_frame, condCoding ); + + /* Loop over quantizer and entropy coding to control bitrate */ + maxIter = 6; + gainMult_Q8 = SILK_FIX_CONST( 1, 8 ); + found_lower = 0; + found_upper = 0; + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + gainsID_lower = -1; + gainsID_upper = -1; + /* Copy part of the input state */ + silk_memcpy( &sRangeEnc_copy, psRangeEnc, sizeof( ec_enc ) ); + silk_memcpy( &sNSQ_copy, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + seed_copy = psEnc->sCmn.indices.Seed; + ec_prevLagIndex_copy = psEnc->sCmn.ec_prevLagIndex; + ec_prevSignalType_copy = psEnc->sCmn.ec_prevSignalType; + ALLOC( ec_buf_copy, 1275, opus_uint8 ); + for( iter = 0; ; iter++ ) { + if( gainsID == gainsID_lower ) { + nBits = nBits_lower; + } else if( gainsID == gainsID_upper ) { + nBits = nBits_upper; + } else { + /* Restore part of the input state */ + if( iter > 0 ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy, sizeof( ec_enc ) ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy, sizeof( silk_nsq_state ) ); + psEnc->sCmn.indices.Seed = seed_copy; + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + } + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { + silk_NSQ_del_dec( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, x_frame, psEnc->sCmn.pulses, + sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR_Q13, sEncCtrl.HarmShapeGain_Q14, + sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14, + psEnc->sCmn.arch ); + } else { + silk_NSQ( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, x_frame, psEnc->sCmn.pulses, + sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR_Q13, sEncCtrl.HarmShapeGain_Q14, + sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14, + psEnc->sCmn.arch); + } + + if ( iter == maxIter && !found_lower ) { + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + } + + /****************************************/ + /* Encode Parameters */ + /****************************************/ + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + /****************************************/ + /* Encode Excitation Signal */ + /****************************************/ + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + + /* If we still bust after the last iteration, do some damage control. */ + if ( iter == maxIter && !found_lower && nBits > maxBits ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + + /* Keep gains the same as the last frame. */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + psEnc->sCmn.indices.GainsIndices[ i ] = 4; + } + if (condCoding != CODE_CONDITIONALLY) { + psEnc->sCmn.indices.GainsIndices[ 0 ] = sEncCtrl.lastGainIndexPrev; + } + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + /* Clear all pulses. */ + for ( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + psEnc->sCmn.pulses[ i ] = 0; + } + + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + } + + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { + break; + } + } + + if( iter == maxIter ) { + if( found_lower && ( gainsID == gainsID_lower || nBits > maxBits ) ) { + /* Restore output state from earlier iteration that did meet the bitrate budget */ + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + celt_assert( sRangeEnc_copy2.offs <= 1275 ); + silk_memcpy( psRangeEnc->buf, ec_buf_copy, sRangeEnc_copy2.offs ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy2, sizeof( silk_nsq_state ) ); + psEnc->sShape.LastGainIndex = LastGainIndex_copy2; + } + break; + } + + if( nBits > maxBits ) { + if( found_lower == 0 && iter >= 2 ) { + /* Adjust the quantizer's rate/distortion tradeoff and discard previous "upper" results */ + sEncCtrl.Lambda_Q10 = silk_ADD_RSHIFT32( sEncCtrl.Lambda_Q10, sEncCtrl.Lambda_Q10, 1 ); + found_upper = 0; + gainsID_upper = -1; + } else { + found_upper = 1; + nBits_upper = nBits; + gainMult_upper = gainMult_Q8; + gainsID_upper = gainsID; + } + } else if( nBits < maxBits - bits_margin ) { + found_lower = 1; + nBits_lower = nBits; + gainMult_lower = gainMult_Q8; + if( gainsID != gainsID_lower ) { + gainsID_lower = gainsID; + /* Copy part of the output state */ + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + celt_assert( psRangeEnc->offs <= 1275 ); + silk_memcpy( ec_buf_copy, psRangeEnc->buf, psRangeEnc->offs ); + silk_memcpy( &sNSQ_copy2, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + LastGainIndex_copy2 = psEnc->sShape.LastGainIndex; + } + } else { + /* Close enough */ + break; + } + + if ( !found_lower && nBits > maxBits ) { + int j; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + int sum=0; + for ( j = i*psEnc->sCmn.subfr_length; j < (i+1)*psEnc->sCmn.subfr_length; j++ ) { + sum += abs( psEnc->sCmn.pulses[j] ); + } + if ( iter == 0 || (sum < best_sum[i] && !gain_lock[i]) ) { + best_sum[i] = sum; + best_gain_mult[i] = gainMult_Q8; + } else { + gain_lock[i] = 1; + } + } + } + if( ( found_lower & found_upper ) == 0 ) { + /* Adjust gain according to high-rate rate/distortion curve */ + if( nBits > maxBits ) { + gainMult_Q8 = silk_min_32( 1024, gainMult_Q8*3/2 ); + } else { + gainMult_Q8 = silk_max_32( 64, gainMult_Q8*4/5 ); + } + } else { + /* Adjust gain by interpolating */ + gainMult_Q8 = gainMult_lower + silk_DIV32_16( silk_MUL( gainMult_upper - gainMult_lower, maxBits - nBits_lower ), nBits_upper - nBits_lower ); + /* New gain multplier must be between 25% and 75% of old range (note that gainMult_upper < gainMult_lower) */ + if( gainMult_Q8 > silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ); + } else + if( gainMult_Q8 < silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + opus_int16 tmp; + if ( gain_lock[i] ) { + tmp = best_gain_mult[i]; + } else { + tmp = gainMult_Q8; + } + sEncCtrl.Gains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], tmp ), 8 ); + } + + /* Quantize gains */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, sEncCtrl.Gains_Q16, + &psEnc->sShape.LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Unique identifier of gains vector */ + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + } + } + + /* Update input buffer */ + silk_memmove( psEnc->x_buf, &psEnc->x_buf[ psEnc->sCmn.frame_length ], + ( psEnc->sCmn.ltp_mem_length + LA_SHAPE_MS * psEnc->sCmn.fs_kHz ) * sizeof( opus_int16 ) ); + + /* Exit without entropy coding */ + if( psEnc->sCmn.prefillFlag ) { + /* No payload */ + *pnBytesOut = 0; + RESTORE_STACK; + return ret; + } + + /* Parameters needed for next frame */ + psEnc->sCmn.prevLag = sEncCtrl.pitchL[ psEnc->sCmn.nb_subfr - 1 ]; + psEnc->sCmn.prevSignalType = psEnc->sCmn.indices.signalType; + + /****************************************/ + /* Finalize payload */ + /****************************************/ + psEnc->sCmn.first_frame_after_reset = 0; + /* Payload size */ + *pnBytesOut = silk_RSHIFT( ec_tell( psRangeEnc ) + 7, 3 ); + + RESTORE_STACK; + return ret; +} + +/* Low-Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode excitation at lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Pointer to Silk FIX encoder control struct */ + const opus_int16 x16[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +) +{ + opus_int32 TempGains_Q16[ MAX_NB_SUBFR ]; + SideInfoIndices *psIndices_LBRR = &psEnc->sCmn.indices_LBRR[ psEnc->sCmn.nFramesEncoded ]; + silk_nsq_state sNSQ_LBRR; + + /*******************************************/ + /* Control use of inband LBRR */ + /*******************************************/ + if( psEnc->sCmn.LBRR_enabled && psEnc->sCmn.speech_activity_Q8 > SILK_FIX_CONST( LBRR_SPEECH_ACTIVITY_THRES, 8 ) ) { + psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + + /* Copy noise shaping quantizer state and quantization indices from regular encoding */ + silk_memcpy( &sNSQ_LBRR, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + silk_memcpy( psIndices_LBRR, &psEnc->sCmn.indices, sizeof( SideInfoIndices ) ); + + /* Save original gains */ + silk_memcpy( TempGains_Q16, psEncCtrl->Gains_Q16, psEnc->sCmn.nb_subfr * sizeof( opus_int32 ) ); + + if( psEnc->sCmn.nFramesEncoded == 0 || psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded - 1 ] == 0 ) { + /* First frame in packet or previous frame not LBRR coded */ + psEnc->sCmn.LBRRprevLastGainIndex = psEnc->sShape.LastGainIndex; + + /* Increase Gains to get target LBRR rate */ + psIndices_LBRR->GainsIndices[ 0 ] = psIndices_LBRR->GainsIndices[ 0 ] + psEnc->sCmn.LBRR_GainIncreases; + psIndices_LBRR->GainsIndices[ 0 ] = silk_min_int( psIndices_LBRR->GainsIndices[ 0 ], N_LEVELS_QGAIN - 1 ); + } + + /* Decode to get gains in sync with decoder */ + /* Overwrite unquantized gains with quantized gains */ + silk_gains_dequant( psEncCtrl->Gains_Q16, psIndices_LBRR->GainsIndices, + &psEnc->sCmn.LBRRprevLastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { + silk_NSQ_del_dec( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, x16, + psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], psEncCtrl->PredCoef_Q12[ 0 ], psEncCtrl->LTPCoef_Q14, + psEncCtrl->AR_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, + psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14, psEnc->sCmn.arch ); + } else { + silk_NSQ( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, x16, + psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], psEncCtrl->PredCoef_Q12[ 0 ], psEncCtrl->LTPCoef_Q14, + psEncCtrl->AR_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, + psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14, psEnc->sCmn.arch ); + } + + /* Restore original gains */ + silk_memcpy( psEncCtrl->Gains_Q16, TempGains_Q16, psEnc->sCmn.nb_subfr * sizeof( opus_int32 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LPC_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LPC_FIX.c new file mode 100644 index 0000000000..c762a0f2a2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LPC_FIX.c @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "stack_alloc.h" +#include "tuning_parameters.h" + +/* Finds LPC vector from correlations, and converts to NLSF */ +void silk_find_LPC_FIX( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const opus_int16 x[], /* I Input signal */ + const opus_int32 minInvGain_Q30 /* I Inverse of max prediction gain */ +) +{ + opus_int k, subfr_length; + opus_int32 a_Q16[ MAX_LPC_ORDER ]; + opus_int isInterpLower, shift; + opus_int32 res_nrg0, res_nrg1; + opus_int rshift0, rshift1; + + /* Used only for LSF interpolation */ + opus_int32 a_tmp_Q16[ MAX_LPC_ORDER ], res_nrg_interp, res_nrg, res_tmp_nrg; + opus_int res_nrg_interp_Q, res_nrg_Q, res_tmp_nrg_Q; + opus_int16 a_tmp_Q12[ MAX_LPC_ORDER ]; + opus_int16 NLSF0_Q15[ MAX_LPC_ORDER ]; + SAVE_STACK; + + subfr_length = psEncC->subfr_length + psEncC->predictLPCOrder; + + /* Default: no interpolation */ + psEncC->indices.NLSFInterpCoef_Q2 = 4; + + /* Burg AR analysis for the full frame */ + silk_burg_modified( &res_nrg, &res_nrg_Q, a_Q16, x, minInvGain_Q30, subfr_length, psEncC->nb_subfr, psEncC->predictLPCOrder, psEncC->arch ); + + if( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) { + VARDECL( opus_int16, LPC_res ); + + /* Optimal solution for last 10 ms */ + silk_burg_modified( &res_tmp_nrg, &res_tmp_nrg_Q, a_tmp_Q16, x + 2 * subfr_length, minInvGain_Q30, subfr_length, 2, psEncC->predictLPCOrder, psEncC->arch ); + + /* subtract residual energy here, as that's easier than adding it to the */ + /* residual energy of the first 10 ms in each iteration of the search below */ + shift = res_tmp_nrg_Q - res_nrg_Q; + if( shift >= 0 ) { + if( shift < 32 ) { + res_nrg = res_nrg - silk_RSHIFT( res_tmp_nrg, shift ); + } + } else { + silk_assert( shift > -32 ); + res_nrg = silk_RSHIFT( res_nrg, -shift ) - res_tmp_nrg; + res_nrg_Q = res_tmp_nrg_Q; + } + + /* Convert to NLSFs */ + silk_A2NLSF( NLSF_Q15, a_tmp_Q16, psEncC->predictLPCOrder ); + + ALLOC( LPC_res, 2 * subfr_length, opus_int16 ); + + /* Search over interpolation indices to find the one with lowest residual energy */ + for( k = 3; k >= 0; k-- ) { + /* Interpolate NLSFs for first half */ + silk_interpolate( NLSF0_Q15, psEncC->prev_NLSFq_Q15, NLSF_Q15, k, psEncC->predictLPCOrder ); + + /* Convert to LPC for residual energy evaluation */ + silk_NLSF2A( a_tmp_Q12, NLSF0_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + /* Calculate residual energy with NLSF interpolation */ + silk_LPC_analysis_filter( LPC_res, x, a_tmp_Q12, 2 * subfr_length, psEncC->predictLPCOrder, psEncC->arch ); + + silk_sum_sqr_shift( &res_nrg0, &rshift0, LPC_res + psEncC->predictLPCOrder, subfr_length - psEncC->predictLPCOrder ); + silk_sum_sqr_shift( &res_nrg1, &rshift1, LPC_res + psEncC->predictLPCOrder + subfr_length, subfr_length - psEncC->predictLPCOrder ); + + /* Add subframe energies from first half frame */ + shift = rshift0 - rshift1; + if( shift >= 0 ) { + res_nrg1 = silk_RSHIFT( res_nrg1, shift ); + res_nrg_interp_Q = -rshift0; + } else { + res_nrg0 = silk_RSHIFT( res_nrg0, -shift ); + res_nrg_interp_Q = -rshift1; + } + res_nrg_interp = silk_ADD32( res_nrg0, res_nrg1 ); + + /* Compare with first half energy without NLSF interpolation, or best interpolated value so far */ + shift = res_nrg_interp_Q - res_nrg_Q; + if( shift >= 0 ) { + if( silk_RSHIFT( res_nrg_interp, shift ) < res_nrg ) { + isInterpLower = silk_TRUE; + } else { + isInterpLower = silk_FALSE; + } + } else { + if( -shift < 32 ) { + if( res_nrg_interp < silk_RSHIFT( res_nrg, -shift ) ) { + isInterpLower = silk_TRUE; + } else { + isInterpLower = silk_FALSE; + } + } else { + isInterpLower = silk_FALSE; + } + } + + /* Determine whether current interpolated NLSFs are best so far */ + if( isInterpLower == silk_TRUE ) { + /* Interpolation has lower residual energy */ + res_nrg = res_nrg_interp; + res_nrg_Q = res_nrg_interp_Q; + psEncC->indices.NLSFInterpCoef_Q2 = (opus_int8)k; + } + } + } + + if( psEncC->indices.NLSFInterpCoef_Q2 == 4 ) { + /* NLSF interpolation is currently inactive, calculate NLSFs from full frame AR coefficients */ + silk_A2NLSF( NLSF_Q15, a_Q16, psEncC->predictLPCOrder ); + } + + celt_assert( psEncC->indices.NLSFInterpCoef_Q2 == 4 || ( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LTP_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LTP_FIX.c new file mode 100644 index 0000000000..62d4afb250 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_LTP_FIX.c @@ -0,0 +1,99 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "tuning_parameters.h" + +void silk_find_LTP_FIX( + opus_int32 XXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Correlation matrix */ + opus_int32 xXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER ], /* O Correlation vector */ + const opus_int16 r_ptr[], /* I Residual signal after LPC */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, k, extra_shifts; + opus_int xx_shifts, xX_shifts, XX_shifts; + const opus_int16 *lag_ptr; + opus_int32 *XXLTP_Q17_ptr, *xXLTP_Q17_ptr; + opus_int32 xx, nrg, temp; + + xXLTP_Q17_ptr = xXLTP_Q17; + XXLTP_Q17_ptr = XXLTP_Q17; + for( k = 0; k < nb_subfr; k++ ) { + lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 ); + + silk_sum_sqr_shift( &xx, &xx_shifts, r_ptr, subfr_length + LTP_ORDER ); /* xx in Q( -xx_shifts ) */ + silk_corrMatrix_FIX( lag_ptr, subfr_length, LTP_ORDER, XXLTP_Q17_ptr, &nrg, &XX_shifts, arch ); /* XXLTP_Q17_ptr and nrg in Q( -XX_shifts ) */ + extra_shifts = xx_shifts - XX_shifts; + if( extra_shifts > 0 ) { + /* Shift XX */ + xX_shifts = xx_shifts; + for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { + XXLTP_Q17_ptr[ i ] = silk_RSHIFT32( XXLTP_Q17_ptr[ i ], extra_shifts ); /* Q( -xX_shifts ) */ + } + nrg = silk_RSHIFT32( nrg, extra_shifts ); /* Q( -xX_shifts ) */ + } else if( extra_shifts < 0 ) { + /* Shift xx */ + xX_shifts = XX_shifts; + xx = silk_RSHIFT32( xx, -extra_shifts ); /* Q( -xX_shifts ) */ + } else { + xX_shifts = xx_shifts; + } + silk_corrVector_FIX( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xXLTP_Q17_ptr, xX_shifts, arch ); /* xXLTP_Q17_ptr in Q( -xX_shifts ) */ + + /* At this point all correlations are in Q(-xX_shifts) */ + temp = silk_SMLAWB( 1, nrg, SILK_FIX_CONST( LTP_CORR_INV_MAX, 16 ) ); + temp = silk_max( temp, xx ); +TIC(div) +#if 0 + for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { + XXLTP_Q17_ptr[ i ] = silk_DIV32_varQ( XXLTP_Q17_ptr[ i ], temp, 17 ); + } + for( i = 0; i < LTP_ORDER; i++ ) { + xXLTP_Q17_ptr[ i ] = silk_DIV32_varQ( xXLTP_Q17_ptr[ i ], temp, 17 ); + } +#else + for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { + XXLTP_Q17_ptr[ i ] = (opus_int32)( silk_LSHIFT64( (opus_int64)XXLTP_Q17_ptr[ i ], 17 ) / temp ); + } + for( i = 0; i < LTP_ORDER; i++ ) { + xXLTP_Q17_ptr[ i ] = (opus_int32)( silk_LSHIFT64( (opus_int64)xXLTP_Q17_ptr[ i ], 17 ) / temp ); + } +#endif +TOC(div) + r_ptr += subfr_length; + XXLTP_Q17_ptr += LTP_ORDER * LTP_ORDER; + xXLTP_Q17_ptr += LTP_ORDER; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pitch_lags_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pitch_lags_FIX.c new file mode 100644 index 0000000000..6c3379f2bb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pitch_lags_FIX.c @@ -0,0 +1,143 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "stack_alloc.h" +#include "tuning_parameters.h" + +/* Find pitch lags */ +void silk_find_pitch_lags_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + opus_int16 res[], /* O residual */ + const opus_int16 x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +) +{ + opus_int buf_len, i, scale; + opus_int32 thrhld_Q13, res_nrg; + const opus_int16 *x_ptr; + VARDECL( opus_int16, Wsig ); + opus_int16 *Wsig_ptr; + opus_int32 auto_corr[ MAX_FIND_PITCH_LPC_ORDER + 1 ]; + opus_int16 rc_Q15[ MAX_FIND_PITCH_LPC_ORDER ]; + opus_int32 A_Q24[ MAX_FIND_PITCH_LPC_ORDER ]; + opus_int16 A_Q12[ MAX_FIND_PITCH_LPC_ORDER ]; + SAVE_STACK; + + /******************************************/ + /* Set up buffer lengths etc based on Fs */ + /******************************************/ + buf_len = psEnc->sCmn.la_pitch + psEnc->sCmn.frame_length + psEnc->sCmn.ltp_mem_length; + + /* Safety check */ + celt_assert( buf_len >= psEnc->sCmn.pitch_LPC_win_length ); + + /*************************************/ + /* Estimate LPC AR coefficients */ + /*************************************/ + + /* Calculate windowed signal */ + + ALLOC( Wsig, psEnc->sCmn.pitch_LPC_win_length, opus_int16 ); + + /* First LA_LTP samples */ + x_ptr = x + buf_len - psEnc->sCmn.pitch_LPC_win_length; + Wsig_ptr = Wsig; + silk_apply_sine_window( Wsig_ptr, x_ptr, 1, psEnc->sCmn.la_pitch ); + + /* Middle un - windowed samples */ + Wsig_ptr += psEnc->sCmn.la_pitch; + x_ptr += psEnc->sCmn.la_pitch; + silk_memcpy( Wsig_ptr, x_ptr, ( psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ) ) * sizeof( opus_int16 ) ); + + /* Last LA_LTP samples */ + Wsig_ptr += psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ); + x_ptr += psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ); + silk_apply_sine_window( Wsig_ptr, x_ptr, 2, psEnc->sCmn.la_pitch ); + + /* Calculate autocorrelation sequence */ + silk_autocorr( auto_corr, &scale, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1, arch ); + + /* Add white noise, as fraction of energy */ + auto_corr[ 0 ] = silk_SMLAWB( auto_corr[ 0 ], auto_corr[ 0 ], SILK_FIX_CONST( FIND_PITCH_WHITE_NOISE_FRACTION, 16 ) ) + 1; + + /* Calculate the reflection coefficients using schur */ + res_nrg = silk_schur( rc_Q15, auto_corr, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Prediction gain */ + psEncCtrl->predGain_Q16 = silk_DIV32_varQ( auto_corr[ 0 ], silk_max_int( res_nrg, 1 ), 16 ); + + /* Convert reflection coefficients to prediction coefficients */ + silk_k2a( A_Q24, rc_Q15, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Convert From 32 bit Q24 to 16 bit Q12 coefs */ + for( i = 0; i < psEnc->sCmn.pitchEstimationLPCOrder; i++ ) { + A_Q12[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT( A_Q24[ i ], 12 ) ); + } + + /* Do BWE */ + silk_bwexpander( A_Q12, psEnc->sCmn.pitchEstimationLPCOrder, SILK_FIX_CONST( FIND_PITCH_BANDWIDTH_EXPANSION, 16 ) ); + + /*****************************************/ + /* LPC analysis filtering */ + /*****************************************/ + silk_LPC_analysis_filter( res, x, A_Q12, buf_len, psEnc->sCmn.pitchEstimationLPCOrder, psEnc->sCmn.arch ); + + if( psEnc->sCmn.indices.signalType != TYPE_NO_VOICE_ACTIVITY && psEnc->sCmn.first_frame_after_reset == 0 ) { + /* Threshold for pitch estimator */ + thrhld_Q13 = SILK_FIX_CONST( 0.6, 13 ); + thrhld_Q13 = silk_SMLABB( thrhld_Q13, SILK_FIX_CONST( -0.004, 13 ), psEnc->sCmn.pitchEstimationLPCOrder ); + thrhld_Q13 = silk_SMLAWB( thrhld_Q13, SILK_FIX_CONST( -0.1, 21 ), psEnc->sCmn.speech_activity_Q8 ); + thrhld_Q13 = silk_SMLABB( thrhld_Q13, SILK_FIX_CONST( -0.15, 13 ), silk_RSHIFT( psEnc->sCmn.prevSignalType, 1 ) ); + thrhld_Q13 = silk_SMLAWB( thrhld_Q13, SILK_FIX_CONST( -0.1, 14 ), psEnc->sCmn.input_tilt_Q15 ); + thrhld_Q13 = silk_SAT16( thrhld_Q13 ); + + /*****************************************/ + /* Call pitch estimator */ + /*****************************************/ + if( silk_pitch_analysis_core( res, psEncCtrl->pitchL, &psEnc->sCmn.indices.lagIndex, &psEnc->sCmn.indices.contourIndex, + &psEnc->LTPCorr_Q15, psEnc->sCmn.prevLag, psEnc->sCmn.pitchEstimationThreshold_Q16, + (opus_int)thrhld_Q13, psEnc->sCmn.fs_kHz, psEnc->sCmn.pitchEstimationComplexity, psEnc->sCmn.nb_subfr, + psEnc->sCmn.arch) == 0 ) + { + psEnc->sCmn.indices.signalType = TYPE_VOICED; + } else { + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + } + } else { + silk_memset( psEncCtrl->pitchL, 0, sizeof( psEncCtrl->pitchL ) ); + psEnc->sCmn.indices.lagIndex = 0; + psEnc->sCmn.indices.contourIndex = 0; + psEnc->LTPCorr_Q15 = 0; + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pred_coefs_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pred_coefs_FIX.c new file mode 100644 index 0000000000..ad363fb7bc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/find_pred_coefs_FIX.c @@ -0,0 +1,146 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "stack_alloc.h" + +void silk_find_pred_coefs_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + const opus_int16 res_pitch[], /* I Residual from pitch analysis */ + const opus_int16 x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i; + opus_int32 invGains_Q16[ MAX_NB_SUBFR ], local_gains[ MAX_NB_SUBFR ]; + /* Set to NLSF_Q15 to zero so we don't copy junk to the state. */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ]={0}; + const opus_int16 *x_ptr; + opus_int16 *x_pre_ptr; + VARDECL( opus_int16, LPC_in_pre ); + opus_int32 min_gain_Q16, minInvGain_Q30; + SAVE_STACK; + + /* weighting for weighted least squares */ + min_gain_Q16 = silk_int32_MAX >> 6; + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + min_gain_Q16 = silk_min( min_gain_Q16, psEncCtrl->Gains_Q16[ i ] ); + } + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + /* Divide to Q16 */ + silk_assert( psEncCtrl->Gains_Q16[ i ] > 0 ); + /* Invert and normalize gains, and ensure that maximum invGains_Q16 is within range of a 16 bit int */ + invGains_Q16[ i ] = silk_DIV32_varQ( min_gain_Q16, psEncCtrl->Gains_Q16[ i ], 16 - 2 ); + + /* Limit inverse */ + invGains_Q16[ i ] = silk_max( invGains_Q16[ i ], 100 ); + + /* Square the inverted gains */ + silk_assert( invGains_Q16[ i ] == silk_SAT16( invGains_Q16[ i ] ) ); + + /* Invert the inverted and normalized gains */ + local_gains[ i ] = silk_DIV32( ( (opus_int32)1 << 16 ), invGains_Q16[ i ] ); + } + + ALLOC( LPC_in_pre, + psEnc->sCmn.nb_subfr * psEnc->sCmn.predictLPCOrder + + psEnc->sCmn.frame_length, opus_int16 ); + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + VARDECL( opus_int32, xXLTP_Q17 ); + VARDECL( opus_int32, XXLTP_Q17 ); + + /**********/ + /* VOICED */ + /**********/ + celt_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 ); + + ALLOC( xXLTP_Q17, psEnc->sCmn.nb_subfr * LTP_ORDER, opus_int32 ); + ALLOC( XXLTP_Q17, psEnc->sCmn.nb_subfr * LTP_ORDER * LTP_ORDER, opus_int32 ); + + /* LTP analysis */ + silk_find_LTP_FIX( XXLTP_Q17, xXLTP_Q17, res_pitch, + psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Quantize LTP gain parameters */ + silk_quant_LTP_gains( psEncCtrl->LTPCoef_Q14, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex, + &psEnc->sCmn.sum_log_gain_Q7, &psEncCtrl->LTPredCodGain_Q7, XXLTP_Q17, xXLTP_Q17, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Control LTP scaling */ + silk_LTP_scale_ctrl_FIX( psEnc, psEncCtrl, condCoding ); + + /* Create LTP residual */ + silk_LTP_analysis_filter_FIX( LPC_in_pre, x - psEnc->sCmn.predictLPCOrder, psEncCtrl->LTPCoef_Q14, + psEncCtrl->pitchL, invGains_Q16, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + + } else { + /************/ + /* UNVOICED */ + /************/ + /* Create signal with prepended subframes, scaled by inverse gains */ + x_ptr = x - psEnc->sCmn.predictLPCOrder; + x_pre_ptr = LPC_in_pre; + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + silk_scale_copy_vector16( x_pre_ptr, x_ptr, invGains_Q16[ i ], + psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder ); + x_pre_ptr += psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder; + x_ptr += psEnc->sCmn.subfr_length; + } + + silk_memset( psEncCtrl->LTPCoef_Q14, 0, psEnc->sCmn.nb_subfr * LTP_ORDER * sizeof( opus_int16 ) ); + psEncCtrl->LTPredCodGain_Q7 = 0; + psEnc->sCmn.sum_log_gain_Q7 = 0; + } + + /* Limit on total predictive coding gain */ + if( psEnc->sCmn.first_frame_after_reset ) { + minInvGain_Q30 = SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN_AFTER_RESET, 30 ); + } else { + minInvGain_Q30 = silk_log2lin( silk_SMLAWB( 16 << 7, (opus_int32)psEncCtrl->LTPredCodGain_Q7, SILK_FIX_CONST( 1.0 / 3, 16 ) ) ); /* Q16 */ + minInvGain_Q30 = silk_DIV32_varQ( minInvGain_Q30, + silk_SMULWW( SILK_FIX_CONST( MAX_PREDICTION_POWER_GAIN, 0 ), + silk_SMLAWB( SILK_FIX_CONST( 0.25, 18 ), SILK_FIX_CONST( 0.75, 18 ), psEncCtrl->coding_quality_Q14 ) ), 14 ); + } + + /* LPC_in_pre contains the LTP-filtered input for voiced, and the unfiltered input for unvoiced */ + silk_find_LPC_FIX( &psEnc->sCmn, NLSF_Q15, LPC_in_pre, minInvGain_Q30 ); + + /* Quantize LSFs */ + silk_process_NLSFs( &psEnc->sCmn, psEncCtrl->PredCoef_Q12, NLSF_Q15, psEnc->sCmn.prev_NLSFq_Q15 ); + + /* Calculate residual energy using quantized LPC coefficients */ + silk_residual_energy_FIX( psEncCtrl->ResNrg, psEncCtrl->ResNrgQ, LPC_in_pre, psEncCtrl->PredCoef_Q12, local_gains, + psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder, psEnc->sCmn.arch ); + + /* Copy to prediction struct for use in next frame for interpolation */ + silk_memcpy( psEnc->sCmn.prev_NLSFq_Q15, NLSF_Q15, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_FIX.c new file mode 100644 index 0000000000..549f6eadaa --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_FIX.c @@ -0,0 +1,54 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int16 *rc_Q15, /* I Reflection coefficients [order] Q15 */ + const opus_int32 order /* I Prediction order */ +) +{ + opus_int k, n; + opus_int32 rc, tmp1, tmp2; + + for( k = 0; k < order; k++ ) { + rc = rc_Q15[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A_Q24[ n ]; + tmp2 = A_Q24[ k - n - 1 ]; + A_Q24[ n ] = silk_SMLAWB( tmp1, silk_LSHIFT( tmp2, 1 ), rc ); + A_Q24[ k - n - 1 ] = silk_SMLAWB( tmp2, silk_LSHIFT( tmp1, 1 ), rc ); + } + A_Q24[ k ] = -silk_LSHIFT( (opus_int32)rc_Q15[ k ], 9 ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_Q16_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_Q16_FIX.c new file mode 100644 index 0000000000..1595aa6212 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/k2a_Q16_FIX.c @@ -0,0 +1,54 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a_Q16( + opus_int32 *A_Q24, /* O Prediction coefficients [order] Q24 */ + const opus_int32 *rc_Q16, /* I Reflection coefficients [order] Q16 */ + const opus_int32 order /* I Prediction order */ +) +{ + opus_int k, n; + opus_int32 rc, tmp1, tmp2; + + for( k = 0; k < order; k++ ) { + rc = rc_Q16[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A_Q24[ n ]; + tmp2 = A_Q24[ k - n - 1 ]; + A_Q24[ n ] = silk_SMLAWW( tmp1, tmp2, rc ); + A_Q24[ k - n - 1 ] = silk_SMLAWW( tmp2, tmp1, rc ); + } + A_Q24[ k ] = -silk_LSHIFT( rc, 8 ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/main_FIX.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/main_FIX.h new file mode 100644 index 0000000000..6d2112e511 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/main_FIX.h @@ -0,0 +1,244 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MAIN_FIX_H +#define SILK_MAIN_FIX_H + +#include "SigProc_FIX.h" +#include "structs_FIX.h" +#include "control.h" +#include "main.h" +#include "PLC.h" +#include "debug.h" +#include "entenc.h" + +#if ((defined(OPUS_ARM_ASM) && defined(FIXED_POINT)) \ + || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "fixed/arm/warped_autocorrelation_FIX_arm.h" +#endif + +#ifndef FORCE_CPP_BUILD +#ifdef __cplusplus +extern "C" +{ +#endif +#endif + +#define silk_encoder_state_Fxx silk_encoder_state_FIX +#define silk_encode_do_VAD_Fxx silk_encode_do_VAD_FIX +#define silk_encode_frame_Fxx silk_encode_frame_FIX + +#define QC 10 +#define QS 13 + +/*********************/ +/* Encoder Functions */ +/*********************/ + +/* High-pass filter with cutoff frequency adaptation based on pitch lag statistics */ +void silk_HP_variable_cutoff( + silk_encoder_state_Fxx state_Fxx[] /* I/O Encoder states */ +); + +/* Encoder main function */ +void silk_encode_do_VAD_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + opus_int activity /* I Decision of Opus voice activity detector */ +); + +/* Encoder main function */ +opus_int silk_encode_frame_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ + opus_int32 *pnBytesOut, /* O Pointer to number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +); + +/* Initializes the Silk encoder state */ +opus_int silk_init_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk FIX encoder state */ + int arch /* I Run-time architecture */ +); + +/* Control the Silk encoder */ +opus_int silk_control_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl, /* I Control structure */ + const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ + const opus_int channelNb, /* I Channel number */ + const opus_int force_fs_kHz +); + +/**************************/ +/* Noise shaping analysis */ +/**************************/ +/* Compute noise shaping coefficients and initial gain values */ +void silk_noise_shape_analysis_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Encoder state FIX */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control FIX */ + const opus_int16 *pitch_res, /* I LPC residual from pitch analysis */ + const opus_int16 *x, /* I Input signal [ frame_length + la_shape ] */ + int arch /* I Run-time architecture */ +); + +/* Autocorrelations for a warped frequency axis */ +void silk_warped_autocorrelation_FIX_c( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +); + +#if !defined(OVERRIDE_silk_warped_autocorrelation_FIX) +#define silk_warped_autocorrelation_FIX(corr, scale, input, warping_Q16, length, order, arch) \ + ((void)(arch), silk_warped_autocorrelation_FIX_c(corr, scale, input, warping_Q16, length, order)) +#endif + +/* Calculation of LTP state scaling */ +void silk_LTP_scale_ctrl_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/**********************************************/ +/* Prediction Analysis */ +/**********************************************/ +/* Find pitch lags */ +void silk_find_pitch_lags_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + opus_int16 res[], /* O residual */ + const opus_int16 x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +); + +/* Find LPC and LTP coefficients */ +void silk_find_pred_coefs_FIX( + silk_encoder_state_FIX *psEnc, /* I/O encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O encoder control */ + const opus_int16 res_pitch[], /* I Residual from pitch analysis */ + const opus_int16 x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* LPC analysis */ +void silk_find_LPC_FIX( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const opus_int16 x[], /* I Input signal */ + const opus_int32 minInvGain_Q30 /* I Inverse of max prediction gain */ +); + +/* LTP analysis */ +void silk_find_LTP_FIX( + opus_int32 XXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Correlation matrix */ + opus_int32 xXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER ], /* O Correlation vector */ + const opus_int16 r_lpc[], /* I Residual signal after LPC */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +); + +void silk_LTP_analysis_filter_FIX( + opus_int16 *LTP_res, /* O LTP residual signal of length MAX_NB_SUBFR * ( pre_length + subfr_length ) */ + const opus_int16 *x, /* I Pointer to input signal with at least max( pitchL ) preceding samples */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ],/* I LTP_ORDER LTP coefficients for each MAX_NB_SUBFR subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag, one for each subframe */ + const opus_int32 invGains_Q16[ MAX_NB_SUBFR ], /* I Inverse quantization gains, one for each subframe */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + const opus_int pre_length /* I Length of the preceding samples starting at &x[0] for each subframe */ +); + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FIX( + opus_int32 nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + opus_int nrgsQ[ MAX_NB_SUBFR ], /* O Q value per subframe */ + const opus_int16 x[], /* I Input signal */ + opus_int16 a_Q12[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const opus_int32 gains[ MAX_NB_SUBFR ], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +); + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +opus_int32 silk_residual_energy16_covar_FIX( + const opus_int16 *c, /* I Prediction vector */ + const opus_int32 *wXX, /* I Correlation matrix */ + const opus_int32 *wXx, /* I Correlation vector */ + opus_int32 wxx, /* I Signal energy */ + opus_int D, /* I Dimension */ + opus_int cQ /* I Q value for c vector 0 - 15 */ +); + +/* Processing of gains */ +void silk_process_gains_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/******************/ +/* Linear Algebra */ +/******************/ +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FIX( + const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ + const opus_int L, /* I Length of vectors */ + const opus_int order, /* I Max lag for correlation */ + opus_int32 *XX, /* O Pointer to X'*X correlation matrix [ order x order ] */ + opus_int32 *nrg, /* O Energy of x vector */ + opus_int *rshifts, /* O Right shifts of correlations */ + int arch /* I Run-time architecture */ +); + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FIX( + const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ + const opus_int16 *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vectors */ + const opus_int order, /* I Max lag for correlation */ + opus_int32 *Xt, /* O Pointer to X'*t correlation vector [order] */ + const opus_int rshifts, /* I Right shifts of correlations */ + int arch /* I Run-time architecture */ +); + +#ifndef FORCE_CPP_BUILD +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* FORCE_CPP_BUILD */ +#endif /* SILK_MAIN_FIX_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/noise_shape_analysis_FIX_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/noise_shape_analysis_FIX_mipsr1.h new file mode 100644 index 0000000000..3999b5bd09 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/noise_shape_analysis_FIX_mipsr1.h @@ -0,0 +1,336 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + + +/**************************************************************/ +/* Compute noise shaping coefficients and initial gain values */ +/**************************************************************/ +#define OVERRIDE_silk_noise_shape_analysis_FIX + +void silk_noise_shape_analysis_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Encoder state FIX */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control FIX */ + const opus_int16 *pitch_res, /* I LPC residual from pitch analysis */ + const opus_int16 *x, /* I Input signal [ frame_length + la_shape ] */ + int arch /* I Run-time architecture */ +) +{ + silk_shape_state_FIX *psShapeSt = &psEnc->sShape; + opus_int k, i, nSamples, Qnrg, b_Q14, warping_Q16, scale = 0; + opus_int32 SNR_adj_dB_Q7, HarmBoost_Q16, HarmShapeGain_Q16, Tilt_Q16, tmp32; + opus_int32 nrg, pre_nrg_Q30, log_energy_Q7, log_energy_prev_Q7, energy_variation_Q7; + opus_int32 delta_Q16, BWExp1_Q16, BWExp2_Q16, gain_mult_Q16, gain_add_Q16, strength_Q16, b_Q8; + opus_int32 auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; + opus_int32 refl_coef_Q16[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 AR1_Q24[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 AR2_Q24[ MAX_SHAPE_LPC_ORDER ]; + VARDECL( opus_int16, x_windowed ); + const opus_int16 *x_ptr, *pitch_res_ptr; + SAVE_STACK; + + /* Point to start of first LPC analysis block */ + x_ptr = x - psEnc->sCmn.la_shape; + + /****************/ + /* GAIN CONTROL */ + /****************/ + SNR_adj_dB_Q7 = psEnc->sCmn.SNR_dB_Q7; + + /* Input quality is the average of the quality in the lowest two VAD bands */ + psEncCtrl->input_quality_Q14 = ( opus_int )silk_RSHIFT( (opus_int32)psEnc->sCmn.input_quality_bands_Q15[ 0 ] + + psEnc->sCmn.input_quality_bands_Q15[ 1 ], 2 ); + + /* Coding quality level, between 0.0_Q0 and 1.0_Q0, but in Q14 */ + psEncCtrl->coding_quality_Q14 = silk_RSHIFT( silk_sigm_Q15( silk_RSHIFT_ROUND( SNR_adj_dB_Q7 - + SILK_FIX_CONST( 20.0, 7 ), 4 ) ), 1 ); + + /* Reduce coding SNR during low speech activity */ + if( psEnc->sCmn.useCBR == 0 ) { + b_Q8 = SILK_FIX_CONST( 1.0, 8 ) - psEnc->sCmn.speech_activity_Q8; + b_Q8 = silk_SMULWB( silk_LSHIFT( b_Q8, 8 ), b_Q8 ); + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, + silk_SMULBB( SILK_FIX_CONST( -BG_SNR_DECR_dB, 7 ) >> ( 4 + 1 ), b_Q8 ), /* Q11*/ + silk_SMULWB( SILK_FIX_CONST( 1.0, 14 ) + psEncCtrl->input_quality_Q14, psEncCtrl->coding_quality_Q14 ) ); /* Q12*/ + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce gains for periodic signals */ + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, SILK_FIX_CONST( HARM_SNR_INCR_dB, 8 ), psEnc->LTPCorr_Q15 ); + } else { + /* For unvoiced signals and low-quality input, adjust the quality slower than SNR_dB setting */ + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, + silk_SMLAWB( SILK_FIX_CONST( 6.0, 9 ), -SILK_FIX_CONST( 0.4, 18 ), psEnc->sCmn.SNR_dB_Q7 ), + SILK_FIX_CONST( 1.0, 14 ) - psEncCtrl->input_quality_Q14 ); + } + + /*************************/ + /* SPARSENESS PROCESSING */ + /*************************/ + /* Set quantizer offset */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Initially set to 0; may be overruled in process_gains(..) */ + psEnc->sCmn.indices.quantOffsetType = 0; + psEncCtrl->sparseness_Q8 = 0; + } else { + /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ + nSamples = silk_LSHIFT( psEnc->sCmn.fs_kHz, 1 ); + energy_variation_Q7 = 0; + log_energy_prev_Q7 = 0; + pitch_res_ptr = pitch_res; + for( k = 0; k < silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; k++ ) { + silk_sum_sqr_shift( &nrg, &scale, pitch_res_ptr, nSamples ); + nrg += silk_RSHIFT( nSamples, scale ); /* Q(-scale)*/ + + log_energy_Q7 = silk_lin2log( nrg ); + if( k > 0 ) { + energy_variation_Q7 += silk_abs( log_energy_Q7 - log_energy_prev_Q7 ); + } + log_energy_prev_Q7 = log_energy_Q7; + pitch_res_ptr += nSamples; + } + + psEncCtrl->sparseness_Q8 = silk_RSHIFT( silk_sigm_Q15( silk_SMULWB( energy_variation_Q7 - + SILK_FIX_CONST( 5.0, 7 ), SILK_FIX_CONST( 0.1, 16 ) ) ), 7 ); + + /* Set quantization offset depending on sparseness measure */ + if( psEncCtrl->sparseness_Q8 > SILK_FIX_CONST( SPARSENESS_THRESHOLD_QNT_OFFSET, 8 ) ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + + /* Increase coding SNR for sparse signals */ + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, SILK_FIX_CONST( SPARSE_SNR_INCR_dB, 15 ), psEncCtrl->sparseness_Q8 - SILK_FIX_CONST( 0.5, 8 ) ); + } + + /*******************************/ + /* Control bandwidth expansion */ + /*******************************/ + /* More BWE for signals with high prediction gain */ + strength_Q16 = silk_SMULWB( psEncCtrl->predGain_Q16, SILK_FIX_CONST( FIND_PITCH_WHITE_NOISE_FRACTION, 16 ) ); + BWExp1_Q16 = BWExp2_Q16 = silk_DIV32_varQ( SILK_FIX_CONST( BANDWIDTH_EXPANSION, 16 ), + silk_SMLAWW( SILK_FIX_CONST( 1.0, 16 ), strength_Q16, strength_Q16 ), 16 ); + delta_Q16 = silk_SMULWB( SILK_FIX_CONST( 1.0, 16 ) - silk_SMULBB( 3, psEncCtrl->coding_quality_Q14 ), + SILK_FIX_CONST( LOW_RATE_BANDWIDTH_EXPANSION_DELTA, 16 ) ); + BWExp1_Q16 = silk_SUB32( BWExp1_Q16, delta_Q16 ); + BWExp2_Q16 = silk_ADD32( BWExp2_Q16, delta_Q16 ); + /* BWExp1 will be applied after BWExp2, so make it relative */ + BWExp1_Q16 = silk_DIV32_16( silk_LSHIFT( BWExp1_Q16, 14 ), silk_RSHIFT( BWExp2_Q16, 2 ) ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ + warping_Q16 = silk_SMLAWB( psEnc->sCmn.warping_Q16, (opus_int32)psEncCtrl->coding_quality_Q14, SILK_FIX_CONST( 0.01, 18 ) ); + } else { + warping_Q16 = 0; + } + + /********************************************/ + /* Compute noise shaping AR coefs and gains */ + /********************************************/ + ALLOC( x_windowed, psEnc->sCmn.shapeWinLength, opus_int16 ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Apply window: sine slope followed by flat part followed by cosine slope */ + opus_int shift, slope_part, flat_part; + flat_part = psEnc->sCmn.fs_kHz * 3; + slope_part = silk_RSHIFT( psEnc->sCmn.shapeWinLength - flat_part, 1 ); + + silk_apply_sine_window( x_windowed, x_ptr, 1, slope_part ); + shift = slope_part; + silk_memcpy( x_windowed + shift, x_ptr + shift, flat_part * sizeof(opus_int16) ); + shift += flat_part; + silk_apply_sine_window( x_windowed + shift, x_ptr + shift, 2, slope_part ); + + /* Update pointer: next LPC analysis block */ + x_ptr += psEnc->sCmn.subfr_length; + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Calculate warped auto correlation */ + silk_warped_autocorrelation_FIX( auto_corr, &scale, x_windowed, warping_Q16, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder, arch ); + } else { + /* Calculate regular auto correlation */ + silk_autocorr( auto_corr, &scale, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1, arch ); + } + + /* Add white noise, as a fraction of energy */ + auto_corr[0] = silk_ADD32( auto_corr[0], silk_max_32( silk_SMULWB( silk_RSHIFT( auto_corr[ 0 ], 4 ), + SILK_FIX_CONST( SHAPE_WHITE_NOISE_FRACTION, 20 ) ), 1 ) ); + + /* Calculate the reflection coefficients using schur */ + nrg = silk_schur64( refl_coef_Q16, auto_corr, psEnc->sCmn.shapingLPCOrder ); + silk_assert( nrg >= 0 ); + + /* Convert reflection coefficients to prediction coefficients */ + silk_k2a_Q16( AR2_Q24, refl_coef_Q16, psEnc->sCmn.shapingLPCOrder ); + + Qnrg = -scale; /* range: -12...30*/ + silk_assert( Qnrg >= -12 ); + silk_assert( Qnrg <= 30 ); + + /* Make sure that Qnrg is an even number */ + if( Qnrg & 1 ) { + Qnrg -= 1; + nrg >>= 1; + } + + tmp32 = silk_SQRT_APPROX( nrg ); + Qnrg >>= 1; /* range: -6...15*/ + + psEncCtrl->Gains_Q16[ k ] = (silk_LSHIFT32( silk_LIMIT( (tmp32), silk_RSHIFT32( silk_int32_MIN, (16 - Qnrg) ), \ + silk_RSHIFT32( silk_int32_MAX, (16 - Qnrg) ) ), (16 - Qnrg) )); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Adjust gain for warping */ + gain_mult_Q16 = warped_gain( AR2_Q24, warping_Q16, psEnc->sCmn.shapingLPCOrder ); + silk_assert( psEncCtrl->Gains_Q16[ k ] >= 0 ); + if ( silk_SMULWW( silk_RSHIFT_ROUND( psEncCtrl->Gains_Q16[ k ], 1 ), gain_mult_Q16 ) >= ( silk_int32_MAX >> 1 ) ) { + psEncCtrl->Gains_Q16[ k ] = silk_int32_MAX; + } else { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); + } + } + + /* Bandwidth expansion for synthesis filter shaping */ + silk_bwexpander_32( AR2_Q24, psEnc->sCmn.shapingLPCOrder, BWExp2_Q16 ); + + /* Compute noise shaping filter coefficients */ + silk_memcpy( AR1_Q24, AR2_Q24, psEnc->sCmn.shapingLPCOrder * sizeof( opus_int32 ) ); + + /* Bandwidth expansion for analysis filter shaping */ + silk_assert( BWExp1_Q16 <= SILK_FIX_CONST( 1.0, 16 ) ); + silk_bwexpander_32( AR1_Q24, psEnc->sCmn.shapingLPCOrder, BWExp1_Q16 ); + + /* Ratio of prediction gains, in energy domain */ + pre_nrg_Q30 = silk_LPC_inverse_pred_gain_Q24( AR2_Q24, psEnc->sCmn.shapingLPCOrder, arch ); + nrg = silk_LPC_inverse_pred_gain_Q24( AR1_Q24, psEnc->sCmn.shapingLPCOrder, arch ); + + /*psEncCtrl->GainsPre[ k ] = 1.0f - 0.7f * ( 1.0f - pre_nrg / nrg ) = 0.3f + 0.7f * pre_nrg / nrg;*/ + pre_nrg_Q30 = silk_LSHIFT32( silk_SMULWB( pre_nrg_Q30, SILK_FIX_CONST( 0.7, 15 ) ), 1 ); + psEncCtrl->GainsPre_Q14[ k ] = ( opus_int ) SILK_FIX_CONST( 0.3, 14 ) + silk_DIV32_varQ( pre_nrg_Q30, nrg, 14 ); + + /* Convert to monic warped prediction coefficients and limit absolute values */ + limit_warped_coefs( AR2_Q24, AR1_Q24, warping_Q16, SILK_FIX_CONST( 3.999, 24 ), psEnc->sCmn.shapingLPCOrder ); + + /* Convert from Q24 to Q13 and store in int16 */ + for( i = 0; i < psEnc->sCmn.shapingLPCOrder; i++ ) { + psEncCtrl->AR1_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR1_Q24[ i ], 11 ) ); + psEncCtrl->AR2_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR2_Q24[ i ], 11 ) ); + } + } + + /*****************/ + /* Gain tweaking */ + /*****************/ + /* Increase gains during low speech activity and put lower limit on gains */ + gain_mult_Q16 = silk_log2lin( -silk_SMLAWB( -SILK_FIX_CONST( 16.0, 7 ), SNR_adj_dB_Q7, SILK_FIX_CONST( 0.16, 16 ) ) ); + gain_add_Q16 = silk_log2lin( silk_SMLAWB( SILK_FIX_CONST( 16.0, 7 ), SILK_FIX_CONST( MIN_QGAIN_DB, 7 ), SILK_FIX_CONST( 0.16, 16 ) ) ); + silk_assert( gain_mult_Q16 > 0 ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); + silk_assert( psEncCtrl->Gains_Q16[ k ] >= 0 ); + psEncCtrl->Gains_Q16[ k ] = silk_ADD_POS_SAT32( psEncCtrl->Gains_Q16[ k ], gain_add_Q16 ); + } + + gain_mult_Q16 = SILK_FIX_CONST( 1.0, 16 ) + silk_RSHIFT_ROUND( silk_MLA( SILK_FIX_CONST( INPUT_TILT, 26 ), + psEncCtrl->coding_quality_Q14, SILK_FIX_CONST( HIGH_RATE_INPUT_TILT, 12 ) ), 10 ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->GainsPre_Q14[ k ] = silk_SMULWB( gain_mult_Q16, psEncCtrl->GainsPre_Q14[ k ] ); + } + + /************************************************/ + /* Control low-frequency shaping and noise tilt */ + /************************************************/ + /* Less low frequency shaping for noisy inputs */ + strength_Q16 = silk_MUL( SILK_FIX_CONST( LOW_FREQ_SHAPING, 4 ), silk_SMLAWB( SILK_FIX_CONST( 1.0, 12 ), + SILK_FIX_CONST( LOW_QUALITY_LOW_FREQ_SHAPING_DECR, 13 ), psEnc->sCmn.input_quality_bands_Q15[ 0 ] - SILK_FIX_CONST( 1.0, 15 ) ) ); + strength_Q16 = silk_RSHIFT( silk_MUL( strength_Q16, psEnc->sCmn.speech_activity_Q8 ), 8 ); + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce low frequencies quantization noise for periodic signals, depending on pitch lag */ + /*f = 400; freqz([1, -0.98 + 2e-4 * f], [1, -0.97 + 7e-4 * f], 2^12, Fs); axis([0, 1000, -10, 1])*/ + opus_int fs_kHz_inv = silk_DIV32_16( SILK_FIX_CONST( 0.2, 14 ), psEnc->sCmn.fs_kHz ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + b_Q14 = fs_kHz_inv + silk_DIV32_16( SILK_FIX_CONST( 3.0, 14 ), psEncCtrl->pitchL[ k ] ); + /* Pack two coefficients in one int32 */ + psEncCtrl->LF_shp_Q14[ k ] = silk_LSHIFT( SILK_FIX_CONST( 1.0, 14 ) - b_Q14 - silk_SMULWB( strength_Q16, b_Q14 ), 16 ); + psEncCtrl->LF_shp_Q14[ k ] |= (opus_uint16)( b_Q14 - SILK_FIX_CONST( 1.0, 14 ) ); + } + silk_assert( SILK_FIX_CONST( HARM_HP_NOISE_COEF, 24 ) < SILK_FIX_CONST( 0.5, 24 ) ); /* Guarantees that second argument to SMULWB() is within range of an opus_int16*/ + Tilt_Q16 = - SILK_FIX_CONST( HP_NOISE_COEF, 16 ) - + silk_SMULWB( SILK_FIX_CONST( 1.0, 16 ) - SILK_FIX_CONST( HP_NOISE_COEF, 16 ), + silk_SMULWB( SILK_FIX_CONST( HARM_HP_NOISE_COEF, 24 ), psEnc->sCmn.speech_activity_Q8 ) ); + } else { + b_Q14 = silk_DIV32_16( 21299, psEnc->sCmn.fs_kHz ); /* 1.3_Q0 = 21299_Q14*/ + /* Pack two coefficients in one int32 */ + psEncCtrl->LF_shp_Q14[ 0 ] = silk_LSHIFT( SILK_FIX_CONST( 1.0, 14 ) - b_Q14 - + silk_SMULWB( strength_Q16, silk_SMULWB( SILK_FIX_CONST( 0.6, 16 ), b_Q14 ) ), 16 ); + psEncCtrl->LF_shp_Q14[ 0 ] |= (opus_uint16)( b_Q14 - SILK_FIX_CONST( 1.0, 14 ) ); + for( k = 1; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->LF_shp_Q14[ k ] = psEncCtrl->LF_shp_Q14[ 0 ]; + } + Tilt_Q16 = -SILK_FIX_CONST( HP_NOISE_COEF, 16 ); + } + + /****************************/ + /* HARMONIC SHAPING CONTROL */ + /****************************/ + /* Control boosting of harmonic frequencies */ + HarmBoost_Q16 = silk_SMULWB( silk_SMULWB( SILK_FIX_CONST( 1.0, 17 ) - silk_LSHIFT( psEncCtrl->coding_quality_Q14, 3 ), + psEnc->LTPCorr_Q15 ), SILK_FIX_CONST( LOW_RATE_HARMONIC_BOOST, 16 ) ); + + /* More harmonic boost for noisy input signals */ + HarmBoost_Q16 = silk_SMLAWB( HarmBoost_Q16, + SILK_FIX_CONST( 1.0, 16 ) - silk_LSHIFT( psEncCtrl->input_quality_Q14, 2 ), SILK_FIX_CONST( LOW_INPUT_QUALITY_HARMONIC_BOOST, 16 ) ); + + if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* More harmonic noise shaping for high bitrates or noisy input */ + HarmShapeGain_Q16 = silk_SMLAWB( SILK_FIX_CONST( HARMONIC_SHAPING, 16 ), + SILK_FIX_CONST( 1.0, 16 ) - silk_SMULWB( SILK_FIX_CONST( 1.0, 18 ) - silk_LSHIFT( psEncCtrl->coding_quality_Q14, 4 ), + psEncCtrl->input_quality_Q14 ), SILK_FIX_CONST( HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING, 16 ) ); + + /* Less harmonic noise shaping for less periodic signals */ + HarmShapeGain_Q16 = silk_SMULWB( silk_LSHIFT( HarmShapeGain_Q16, 1 ), + silk_SQRT_APPROX( silk_LSHIFT( psEnc->LTPCorr_Q15, 15 ) ) ); + } else { + HarmShapeGain_Q16 = 0; + } + + /*************************/ + /* Smooth over subframes */ + /*************************/ + for( k = 0; k < MAX_NB_SUBFR; k++ ) { + psShapeSt->HarmBoost_smth_Q16 = + silk_SMLAWB( psShapeSt->HarmBoost_smth_Q16, HarmBoost_Q16 - psShapeSt->HarmBoost_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); + psShapeSt->HarmShapeGain_smth_Q16 = + silk_SMLAWB( psShapeSt->HarmShapeGain_smth_Q16, HarmShapeGain_Q16 - psShapeSt->HarmShapeGain_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); + psShapeSt->Tilt_smth_Q16 = + silk_SMLAWB( psShapeSt->Tilt_smth_Q16, Tilt_Q16 - psShapeSt->Tilt_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); + + psEncCtrl->HarmBoost_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->HarmBoost_smth_Q16, 2 ); + psEncCtrl->HarmShapeGain_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->HarmShapeGain_smth_Q16, 2 ); + psEncCtrl->Tilt_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->Tilt_smth_Q16, 2 ); + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/warped_autocorrelation_FIX_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/warped_autocorrelation_FIX_mipsr1.h new file mode 100644 index 0000000000..66eb2ed26d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/mips/warped_autocorrelation_FIX_mipsr1.h @@ -0,0 +1,165 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef __WARPED_AUTOCORRELATION_FIX_MIPSR1_H__ +#define __WARPED_AUTOCORRELATION_FIX_MIPSR1_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +#undef QC +#define QC 10 + +#undef QS +#define QS 14 + +/* Autocorrelations for a warped frequency axis */ +#define OVERRIDE_silk_warped_autocorrelation_FIX_c +void silk_warped_autocorrelation_FIX_c( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) +{ + opus_int n, i, lsh; + opus_int32 tmp1_QS=0, tmp2_QS=0, tmp3_QS=0, tmp4_QS=0, tmp5_QS=0, tmp6_QS=0, tmp7_QS=0, tmp8_QS=0, start_1=0, start_2=0, start_3=0; + opus_int32 state_QS[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + opus_int64 corr_QC[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + opus_int64 temp64; + + opus_int32 val; + val = 2 * QS - QC; + + /* Order must be even */ + silk_assert( ( order & 1 ) == 0 ); + silk_assert( 2 * QS - QC >= 0 ); + + /* Loop over samples */ + for( n = 0; n < length; n=n+4 ) { + + tmp1_QS = silk_LSHIFT32( (opus_int32)input[ n ], QS ); + start_1 = tmp1_QS; + tmp3_QS = silk_LSHIFT32( (opus_int32)input[ n+1], QS ); + start_2 = tmp3_QS; + tmp5_QS = silk_LSHIFT32( (opus_int32)input[ n+2], QS ); + start_3 = tmp5_QS; + tmp7_QS = silk_LSHIFT32( (opus_int32)input[ n+3], QS ); + + /* Loop over allpass sections */ + for( i = 0; i < order; i += 2 ) { + /* Output of allpass section */ + tmp2_QS = silk_SMLAWB( state_QS[ i ], state_QS[ i + 1 ] - tmp1_QS, warping_Q16 ); + corr_QC[ i ] = __builtin_mips_madd( corr_QC[ i ], tmp1_QS, start_1); + + tmp4_QS = silk_SMLAWB( tmp1_QS, tmp2_QS - tmp3_QS, warping_Q16 ); + corr_QC[ i ] = __builtin_mips_madd( corr_QC[ i ], tmp3_QS, start_2); + + tmp6_QS = silk_SMLAWB( tmp3_QS, tmp4_QS - tmp5_QS, warping_Q16 ); + corr_QC[ i ] = __builtin_mips_madd( corr_QC[ i ], tmp5_QS, start_3); + + tmp8_QS = silk_SMLAWB( tmp5_QS, tmp6_QS - tmp7_QS, warping_Q16 ); + state_QS[ i ] = tmp7_QS; + corr_QC[ i ] = __builtin_mips_madd( corr_QC[ i ], tmp7_QS, state_QS[0]); + + /* Output of allpass section */ + tmp1_QS = silk_SMLAWB( state_QS[ i + 1 ], state_QS[ i + 2 ] - tmp2_QS, warping_Q16 ); + corr_QC[ i+1 ] = __builtin_mips_madd( corr_QC[ i+1 ], tmp2_QS, start_1); + + tmp3_QS = silk_SMLAWB( tmp2_QS, tmp1_QS - tmp4_QS, warping_Q16 ); + corr_QC[ i+1 ] = __builtin_mips_madd( corr_QC[ i+1 ], tmp4_QS, start_2); + + tmp5_QS = silk_SMLAWB( tmp4_QS, tmp3_QS - tmp6_QS, warping_Q16 ); + corr_QC[ i+1 ] = __builtin_mips_madd( corr_QC[ i+1 ], tmp6_QS, start_3); + + tmp7_QS = silk_SMLAWB( tmp6_QS, tmp5_QS - tmp8_QS, warping_Q16 ); + state_QS[ i + 1 ] = tmp8_QS; + corr_QC[ i+1 ] = __builtin_mips_madd( corr_QC[ i+1 ], tmp8_QS, state_QS[ 0 ]); + + } + state_QS[ order ] = tmp7_QS; + + corr_QC[ order ] = __builtin_mips_madd( corr_QC[ order ], tmp1_QS, start_1); + corr_QC[ order ] = __builtin_mips_madd( corr_QC[ order ], tmp3_QS, start_2); + corr_QC[ order ] = __builtin_mips_madd( corr_QC[ order ], tmp5_QS, start_3); + corr_QC[ order ] = __builtin_mips_madd( corr_QC[ order ], tmp7_QS, state_QS[ 0 ]); + } + + for(;n< length; n++ ) { + + tmp1_QS = silk_LSHIFT32( (opus_int32)input[ n ], QS ); + + /* Loop over allpass sections */ + for( i = 0; i < order; i += 2 ) { + + /* Output of allpass section */ + tmp2_QS = silk_SMLAWB( state_QS[ i ], state_QS[ i + 1 ] - tmp1_QS, warping_Q16 ); + state_QS[ i ] = tmp1_QS; + corr_QC[ i ] = __builtin_mips_madd( corr_QC[ i ], tmp1_QS, state_QS[ 0 ]); + + /* Output of allpass section */ + tmp1_QS = silk_SMLAWB( state_QS[ i + 1 ], state_QS[ i + 2 ] - tmp2_QS, warping_Q16 ); + state_QS[ i + 1 ] = tmp2_QS; + corr_QC[ i+1 ] = __builtin_mips_madd( corr_QC[ i+1 ], tmp2_QS, state_QS[ 0 ]); + } + state_QS[ order ] = tmp1_QS; + corr_QC[ order ] = __builtin_mips_madd( corr_QC[ order ], tmp1_QS, state_QS[ 0 ]); + } + + temp64 = corr_QC[ 0 ]; + temp64 = __builtin_mips_shilo(temp64, val); + + lsh = silk_CLZ64( temp64 ) - 35; + lsh = silk_LIMIT( lsh, -12 - QC, 30 - QC ); + *scale = -( QC + lsh ); + silk_assert( *scale >= -30 && *scale <= 12 ); + if( lsh >= 0 ) { + for( i = 0; i < order + 1; i++ ) { + temp64 = corr_QC[ i ]; + //temp64 = __builtin_mips_shilo(temp64, val); + temp64 = (val >= 0) ? (temp64 >> val) : (temp64 << -val); + corr[ i ] = (opus_int32)silk_CHECK_FIT32( __builtin_mips_shilo( temp64, -lsh ) ); + } + } else { + for( i = 0; i < order + 1; i++ ) { + temp64 = corr_QC[ i ]; + //temp64 = __builtin_mips_shilo(temp64, val); + temp64 = (val >= 0) ? (temp64 >> val) : (temp64 << -val); + corr[ i ] = (opus_int32)silk_CHECK_FIT32( __builtin_mips_shilo( temp64, -lsh ) ); + } + } + + corr_QC[ 0 ] = __builtin_mips_shilo(corr_QC[ 0 ], val); + + silk_assert( corr_QC[ 0 ] >= 0 ); /* If breaking, decrease QC*/ +} +#endif /* __WARPED_AUTOCORRELATION_FIX_MIPSR1_H__ */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/noise_shape_analysis_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/noise_shape_analysis_FIX.c new file mode 100644 index 0000000000..85fea0bf09 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/noise_shape_analysis_FIX.c @@ -0,0 +1,407 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "stack_alloc.h" +#include "tuning_parameters.h" + +/* Compute gain to make warped filter coefficients have a zero mean log frequency response on a */ +/* non-warped frequency scale. (So that it can be implemented with a minimum-phase monic filter.) */ +/* Note: A monic filter is one with the first coefficient equal to 1.0. In Silk we omit the first */ +/* coefficient in an array of coefficients, for monic filters. */ +static OPUS_INLINE opus_int32 warped_gain( /* gain in Q16*/ + const opus_int32 *coefs_Q24, + opus_int lambda_Q16, + opus_int order +) { + opus_int i; + opus_int32 gain_Q24; + + lambda_Q16 = -lambda_Q16; + gain_Q24 = coefs_Q24[ order - 1 ]; + for( i = order - 2; i >= 0; i-- ) { + gain_Q24 = silk_SMLAWB( coefs_Q24[ i ], gain_Q24, lambda_Q16 ); + } + gain_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), gain_Q24, -lambda_Q16 ); + return silk_INVERSE32_varQ( gain_Q24, 40 ); +} + +/* Convert warped filter coefficients to monic pseudo-warped coefficients and limit maximum */ +/* amplitude of monic warped coefficients by using bandwidth expansion on the true coefficients */ +static OPUS_INLINE void limit_warped_coefs( + opus_int32 *coefs_Q24, + opus_int lambda_Q16, + opus_int32 limit_Q24, + opus_int order +) { + opus_int i, iter, ind = 0; + opus_int32 tmp, maxabs_Q24, chirp_Q16, gain_Q16; + opus_int32 nom_Q16, den_Q24; + opus_int32 limit_Q20, maxabs_Q20; + + /* Convert to monic coefficients */ + lambda_Q16 = -lambda_Q16; + for( i = order - 1; i > 0; i-- ) { + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); + } + lambda_Q16 = -lambda_Q16; + nom_Q16 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 16 ), -(opus_int32)lambda_Q16, lambda_Q16 ); + den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_Q24[ 0 ], lambda_Q16 ); + gain_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); + for( i = 0; i < order; i++ ) { + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); + } + limit_Q20 = silk_RSHIFT(limit_Q24, 4); + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs_Q24 = -1; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_int32( coefs_Q24[ i ] ); + if( tmp > maxabs_Q24 ) { + maxabs_Q24 = tmp; + ind = i; + } + } + /* Use Q20 to avoid any overflow when multiplying by (ind + 1) later. */ + maxabs_Q20 = silk_RSHIFT(maxabs_Q24, 4); + if( maxabs_Q20 <= limit_Q20 ) { + /* Coefficients are within range - done */ + return; + } + + /* Convert back to true warped coefficients */ + for( i = 1; i < order; i++ ) { + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); + } + gain_Q16 = silk_INVERSE32_varQ( gain_Q16, 32 ); + for( i = 0; i < order; i++ ) { + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); + } + + /* Apply bandwidth expansion */ + chirp_Q16 = SILK_FIX_CONST( 0.99, 16 ) - silk_DIV32_varQ( + silk_SMULWB( maxabs_Q20 - limit_Q20, silk_SMLABB( SILK_FIX_CONST( 0.8, 10 ), SILK_FIX_CONST( 0.1, 10 ), iter ) ), + silk_MUL( maxabs_Q20, ind + 1 ), 22 ); + silk_bwexpander_32( coefs_Q24, order, chirp_Q16 ); + + /* Convert to monic warped coefficients */ + lambda_Q16 = -lambda_Q16; + for( i = order - 1; i > 0; i-- ) { + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); + } + lambda_Q16 = -lambda_Q16; + nom_Q16 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 16 ), -(opus_int32)lambda_Q16, lambda_Q16 ); + den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_Q24[ 0 ], lambda_Q16 ); + gain_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); + for( i = 0; i < order; i++ ) { + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); + } + } + silk_assert( 0 ); +} + +/* Disable MIPS version until it's updated. */ +#if 0 && defined(MIPSr1_ASM) +#include "mips/noise_shape_analysis_FIX_mipsr1.h" +#endif + +/**************************************************************/ +/* Compute noise shaping coefficients and initial gain values */ +/**************************************************************/ +#ifndef OVERRIDE_silk_noise_shape_analysis_FIX +void silk_noise_shape_analysis_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Encoder state FIX */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control FIX */ + const opus_int16 *pitch_res, /* I LPC residual from pitch analysis */ + const opus_int16 *x, /* I Input signal [ frame_length + la_shape ] */ + int arch /* I Run-time architecture */ +) +{ + silk_shape_state_FIX *psShapeSt = &psEnc->sShape; + opus_int k, i, nSamples, nSegs, Qnrg, b_Q14, warping_Q16, scale = 0; + opus_int32 SNR_adj_dB_Q7, HarmShapeGain_Q16, Tilt_Q16, tmp32; + opus_int32 nrg, log_energy_Q7, log_energy_prev_Q7, energy_variation_Q7; + opus_int32 BWExp_Q16, gain_mult_Q16, gain_add_Q16, strength_Q16, b_Q8; + opus_int32 auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; + opus_int32 refl_coef_Q16[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 AR_Q24[ MAX_SHAPE_LPC_ORDER ]; + VARDECL( opus_int16, x_windowed ); + const opus_int16 *x_ptr, *pitch_res_ptr; + SAVE_STACK; + + /* Point to start of first LPC analysis block */ + x_ptr = x - psEnc->sCmn.la_shape; + + /****************/ + /* GAIN CONTROL */ + /****************/ + SNR_adj_dB_Q7 = psEnc->sCmn.SNR_dB_Q7; + + /* Input quality is the average of the quality in the lowest two VAD bands */ + psEncCtrl->input_quality_Q14 = ( opus_int )silk_RSHIFT( (opus_int32)psEnc->sCmn.input_quality_bands_Q15[ 0 ] + + psEnc->sCmn.input_quality_bands_Q15[ 1 ], 2 ); + + /* Coding quality level, between 0.0_Q0 and 1.0_Q0, but in Q14 */ + psEncCtrl->coding_quality_Q14 = silk_RSHIFT( silk_sigm_Q15( silk_RSHIFT_ROUND( SNR_adj_dB_Q7 - + SILK_FIX_CONST( 20.0, 7 ), 4 ) ), 1 ); + + /* Reduce coding SNR during low speech activity */ + if( psEnc->sCmn.useCBR == 0 ) { + b_Q8 = SILK_FIX_CONST( 1.0, 8 ) - psEnc->sCmn.speech_activity_Q8; + b_Q8 = silk_SMULWB( silk_LSHIFT( b_Q8, 8 ), b_Q8 ); + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, + silk_SMULBB( SILK_FIX_CONST( -BG_SNR_DECR_dB, 7 ) >> ( 4 + 1 ), b_Q8 ), /* Q11*/ + silk_SMULWB( SILK_FIX_CONST( 1.0, 14 ) + psEncCtrl->input_quality_Q14, psEncCtrl->coding_quality_Q14 ) ); /* Q12*/ + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce gains for periodic signals */ + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, SILK_FIX_CONST( HARM_SNR_INCR_dB, 8 ), psEnc->LTPCorr_Q15 ); + } else { + /* For unvoiced signals and low-quality input, adjust the quality slower than SNR_dB setting */ + SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, + silk_SMLAWB( SILK_FIX_CONST( 6.0, 9 ), -SILK_FIX_CONST( 0.4, 18 ), psEnc->sCmn.SNR_dB_Q7 ), + SILK_FIX_CONST( 1.0, 14 ) - psEncCtrl->input_quality_Q14 ); + } + + /*************************/ + /* SPARSENESS PROCESSING */ + /*************************/ + /* Set quantizer offset */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Initially set to 0; may be overruled in process_gains(..) */ + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ + nSamples = silk_LSHIFT( psEnc->sCmn.fs_kHz, 1 ); + energy_variation_Q7 = 0; + log_energy_prev_Q7 = 0; + pitch_res_ptr = pitch_res; + nSegs = silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; + for( k = 0; k < nSegs; k++ ) { + silk_sum_sqr_shift( &nrg, &scale, pitch_res_ptr, nSamples ); + nrg += silk_RSHIFT( nSamples, scale ); /* Q(-scale)*/ + + log_energy_Q7 = silk_lin2log( nrg ); + if( k > 0 ) { + energy_variation_Q7 += silk_abs( log_energy_Q7 - log_energy_prev_Q7 ); + } + log_energy_prev_Q7 = log_energy_Q7; + pitch_res_ptr += nSamples; + } + + /* Set quantization offset depending on sparseness measure */ + if( energy_variation_Q7 > SILK_FIX_CONST( ENERGY_VARIATION_THRESHOLD_QNT_OFFSET, 7 ) * (nSegs-1) ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /*******************************/ + /* Control bandwidth expansion */ + /*******************************/ + /* More BWE for signals with high prediction gain */ + strength_Q16 = silk_SMULWB( psEncCtrl->predGain_Q16, SILK_FIX_CONST( FIND_PITCH_WHITE_NOISE_FRACTION, 16 ) ); + BWExp_Q16 = silk_DIV32_varQ( SILK_FIX_CONST( BANDWIDTH_EXPANSION, 16 ), + silk_SMLAWW( SILK_FIX_CONST( 1.0, 16 ), strength_Q16, strength_Q16 ), 16 ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ + warping_Q16 = silk_SMLAWB( psEnc->sCmn.warping_Q16, (opus_int32)psEncCtrl->coding_quality_Q14, SILK_FIX_CONST( 0.01, 18 ) ); + } else { + warping_Q16 = 0; + } + + /********************************************/ + /* Compute noise shaping AR coefs and gains */ + /********************************************/ + ALLOC( x_windowed, psEnc->sCmn.shapeWinLength, opus_int16 ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Apply window: sine slope followed by flat part followed by cosine slope */ + opus_int shift, slope_part, flat_part; + flat_part = psEnc->sCmn.fs_kHz * 3; + slope_part = silk_RSHIFT( psEnc->sCmn.shapeWinLength - flat_part, 1 ); + + silk_apply_sine_window( x_windowed, x_ptr, 1, slope_part ); + shift = slope_part; + silk_memcpy( x_windowed + shift, x_ptr + shift, flat_part * sizeof(opus_int16) ); + shift += flat_part; + silk_apply_sine_window( x_windowed + shift, x_ptr + shift, 2, slope_part ); + + /* Update pointer: next LPC analysis block */ + x_ptr += psEnc->sCmn.subfr_length; + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Calculate warped auto correlation */ + silk_warped_autocorrelation_FIX( auto_corr, &scale, x_windowed, warping_Q16, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder, arch ); + } else { + /* Calculate regular auto correlation */ + silk_autocorr( auto_corr, &scale, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1, arch ); + } + + /* Add white noise, as a fraction of energy */ + auto_corr[0] = silk_ADD32( auto_corr[0], silk_max_32( silk_SMULWB( silk_RSHIFT( auto_corr[ 0 ], 4 ), + SILK_FIX_CONST( SHAPE_WHITE_NOISE_FRACTION, 20 ) ), 1 ) ); + + /* Calculate the reflection coefficients using schur */ + nrg = silk_schur64( refl_coef_Q16, auto_corr, psEnc->sCmn.shapingLPCOrder ); + silk_assert( nrg >= 0 ); + + /* Convert reflection coefficients to prediction coefficients */ + silk_k2a_Q16( AR_Q24, refl_coef_Q16, psEnc->sCmn.shapingLPCOrder ); + + Qnrg = -scale; /* range: -12...30*/ + silk_assert( Qnrg >= -12 ); + silk_assert( Qnrg <= 30 ); + + /* Make sure that Qnrg is an even number */ + if( Qnrg & 1 ) { + Qnrg -= 1; + nrg >>= 1; + } + + tmp32 = silk_SQRT_APPROX( nrg ); + Qnrg >>= 1; /* range: -6...15*/ + + psEncCtrl->Gains_Q16[ k ] = silk_LSHIFT_SAT32( tmp32, 16 - Qnrg ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Adjust gain for warping */ + gain_mult_Q16 = warped_gain( AR_Q24, warping_Q16, psEnc->sCmn.shapingLPCOrder ); + silk_assert( psEncCtrl->Gains_Q16[ k ] > 0 ); + if( psEncCtrl->Gains_Q16[ k ] < SILK_FIX_CONST( 0.25, 16 ) ) { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); + } else { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( silk_RSHIFT_ROUND( psEncCtrl->Gains_Q16[ k ], 1 ), gain_mult_Q16 ); + if ( psEncCtrl->Gains_Q16[ k ] >= ( silk_int32_MAX >> 1 ) ) { + psEncCtrl->Gains_Q16[ k ] = silk_int32_MAX; + } else { + psEncCtrl->Gains_Q16[ k ] = silk_LSHIFT32( psEncCtrl->Gains_Q16[ k ], 1 ); + } + } + silk_assert( psEncCtrl->Gains_Q16[ k ] > 0 ); + } + + /* Bandwidth expansion */ + silk_bwexpander_32( AR_Q24, psEnc->sCmn.shapingLPCOrder, BWExp_Q16 ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Convert to monic warped prediction coefficients and limit absolute values */ + limit_warped_coefs( AR_Q24, warping_Q16, SILK_FIX_CONST( 3.999, 24 ), psEnc->sCmn.shapingLPCOrder ); + + /* Convert from Q24 to Q13 and store in int16 */ + for( i = 0; i < psEnc->sCmn.shapingLPCOrder; i++ ) { + psEncCtrl->AR_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR_Q24[ i ], 11 ) ); + } + } else { + silk_LPC_fit( &psEncCtrl->AR_Q13[ k * MAX_SHAPE_LPC_ORDER ], AR_Q24, 13, 24, psEnc->sCmn.shapingLPCOrder ); + } + } + + /*****************/ + /* Gain tweaking */ + /*****************/ + /* Increase gains during low speech activity and put lower limit on gains */ + gain_mult_Q16 = silk_log2lin( -silk_SMLAWB( -SILK_FIX_CONST( 16.0, 7 ), SNR_adj_dB_Q7, SILK_FIX_CONST( 0.16, 16 ) ) ); + gain_add_Q16 = silk_log2lin( silk_SMLAWB( SILK_FIX_CONST( 16.0, 7 ), SILK_FIX_CONST( MIN_QGAIN_DB, 7 ), SILK_FIX_CONST( 0.16, 16 ) ) ); + silk_assert( gain_mult_Q16 > 0 ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); + silk_assert( psEncCtrl->Gains_Q16[ k ] >= 0 ); + psEncCtrl->Gains_Q16[ k ] = silk_ADD_POS_SAT32( psEncCtrl->Gains_Q16[ k ], gain_add_Q16 ); + } + + + /************************************************/ + /* Control low-frequency shaping and noise tilt */ + /************************************************/ + /* Less low frequency shaping for noisy inputs */ + strength_Q16 = silk_MUL( SILK_FIX_CONST( LOW_FREQ_SHAPING, 4 ), silk_SMLAWB( SILK_FIX_CONST( 1.0, 12 ), + SILK_FIX_CONST( LOW_QUALITY_LOW_FREQ_SHAPING_DECR, 13 ), psEnc->sCmn.input_quality_bands_Q15[ 0 ] - SILK_FIX_CONST( 1.0, 15 ) ) ); + strength_Q16 = silk_RSHIFT( silk_MUL( strength_Q16, psEnc->sCmn.speech_activity_Q8 ), 8 ); + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce low frequencies quantization noise for periodic signals, depending on pitch lag */ + /*f = 400; freqz([1, -0.98 + 2e-4 * f], [1, -0.97 + 7e-4 * f], 2^12, Fs); axis([0, 1000, -10, 1])*/ + opus_int fs_kHz_inv = silk_DIV32_16( SILK_FIX_CONST( 0.2, 14 ), psEnc->sCmn.fs_kHz ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + b_Q14 = fs_kHz_inv + silk_DIV32_16( SILK_FIX_CONST( 3.0, 14 ), psEncCtrl->pitchL[ k ] ); + /* Pack two coefficients in one int32 */ + psEncCtrl->LF_shp_Q14[ k ] = silk_LSHIFT( SILK_FIX_CONST( 1.0, 14 ) - b_Q14 - silk_SMULWB( strength_Q16, b_Q14 ), 16 ); + psEncCtrl->LF_shp_Q14[ k ] |= (opus_uint16)( b_Q14 - SILK_FIX_CONST( 1.0, 14 ) ); + } + silk_assert( SILK_FIX_CONST( HARM_HP_NOISE_COEF, 24 ) < SILK_FIX_CONST( 0.5, 24 ) ); /* Guarantees that second argument to SMULWB() is within range of an opus_int16*/ + Tilt_Q16 = - SILK_FIX_CONST( HP_NOISE_COEF, 16 ) - + silk_SMULWB( SILK_FIX_CONST( 1.0, 16 ) - SILK_FIX_CONST( HP_NOISE_COEF, 16 ), + silk_SMULWB( SILK_FIX_CONST( HARM_HP_NOISE_COEF, 24 ), psEnc->sCmn.speech_activity_Q8 ) ); + } else { + b_Q14 = silk_DIV32_16( 21299, psEnc->sCmn.fs_kHz ); /* 1.3_Q0 = 21299_Q14*/ + /* Pack two coefficients in one int32 */ + psEncCtrl->LF_shp_Q14[ 0 ] = silk_LSHIFT( SILK_FIX_CONST( 1.0, 14 ) - b_Q14 - + silk_SMULWB( strength_Q16, silk_SMULWB( SILK_FIX_CONST( 0.6, 16 ), b_Q14 ) ), 16 ); + psEncCtrl->LF_shp_Q14[ 0 ] |= (opus_uint16)( b_Q14 - SILK_FIX_CONST( 1.0, 14 ) ); + for( k = 1; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->LF_shp_Q14[ k ] = psEncCtrl->LF_shp_Q14[ 0 ]; + } + Tilt_Q16 = -SILK_FIX_CONST( HP_NOISE_COEF, 16 ); + } + + /****************************/ + /* HARMONIC SHAPING CONTROL */ + /****************************/ + if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* More harmonic noise shaping for high bitrates or noisy input */ + HarmShapeGain_Q16 = silk_SMLAWB( SILK_FIX_CONST( HARMONIC_SHAPING, 16 ), + SILK_FIX_CONST( 1.0, 16 ) - silk_SMULWB( SILK_FIX_CONST( 1.0, 18 ) - silk_LSHIFT( psEncCtrl->coding_quality_Q14, 4 ), + psEncCtrl->input_quality_Q14 ), SILK_FIX_CONST( HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING, 16 ) ); + + /* Less harmonic noise shaping for less periodic signals */ + HarmShapeGain_Q16 = silk_SMULWB( silk_LSHIFT( HarmShapeGain_Q16, 1 ), + silk_SQRT_APPROX( silk_LSHIFT( psEnc->LTPCorr_Q15, 15 ) ) ); + } else { + HarmShapeGain_Q16 = 0; + } + + /*************************/ + /* Smooth over subframes */ + /*************************/ + for( k = 0; k < MAX_NB_SUBFR; k++ ) { + psShapeSt->HarmShapeGain_smth_Q16 = + silk_SMLAWB( psShapeSt->HarmShapeGain_smth_Q16, HarmShapeGain_Q16 - psShapeSt->HarmShapeGain_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); + psShapeSt->Tilt_smth_Q16 = + silk_SMLAWB( psShapeSt->Tilt_smth_Q16, Tilt_Q16 - psShapeSt->Tilt_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); + + psEncCtrl->HarmShapeGain_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->HarmShapeGain_smth_Q16, 2 ); + psEncCtrl->Tilt_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->Tilt_smth_Q16, 2 ); + } + RESTORE_STACK; +} +#endif /* OVERRIDE_silk_noise_shape_analysis_FIX */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/pitch_analysis_core_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/pitch_analysis_core_FIX.c new file mode 100644 index 0000000000..14729046d2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/pitch_analysis_core_FIX.c @@ -0,0 +1,721 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/*********************************************************** +* Pitch analyser function +********************************************************** */ +#include "SigProc_FIX.h" +#include "pitch_est_defines.h" +#include "stack_alloc.h" +#include "debug.h" +#include "pitch.h" + +#define SCRATCH_SIZE 22 +#define SF_LENGTH_4KHZ ( PE_SUBFR_LENGTH_MS * 4 ) +#define SF_LENGTH_8KHZ ( PE_SUBFR_LENGTH_MS * 8 ) +#define MIN_LAG_4KHZ ( PE_MIN_LAG_MS * 4 ) +#define MIN_LAG_8KHZ ( PE_MIN_LAG_MS * 8 ) +#define MAX_LAG_4KHZ ( PE_MAX_LAG_MS * 4 ) +#define MAX_LAG_8KHZ ( PE_MAX_LAG_MS * 8 - 1 ) +#define CSTRIDE_4KHZ ( MAX_LAG_4KHZ + 1 - MIN_LAG_4KHZ ) +#define CSTRIDE_8KHZ ( MAX_LAG_8KHZ + 3 - ( MIN_LAG_8KHZ - 2 ) ) +#define D_COMP_MIN ( MIN_LAG_8KHZ - 3 ) +#define D_COMP_MAX ( MAX_LAG_8KHZ + 4 ) +#define D_COMP_STRIDE ( D_COMP_MAX - D_COMP_MIN ) + +typedef opus_int32 silk_pe_stage3_vals[ PE_NB_STAGE3_LAGS ]; + +/************************************************************/ +/* Internally used functions */ +/************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_pe_stage3_vals cross_corr_st3[], /* O 3 DIM correlation array */ + const opus_int16 frame[], /* I vector to correlate */ + opus_int start_lag, /* I lag offset to search around */ + opus_int sf_length, /* I length of a 5 ms subframe */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +); + +static void silk_P_Ana_calc_energy_st3( + silk_pe_stage3_vals energies_st3[], /* O 3 DIM energy array */ + const opus_int16 frame[], /* I vector to calc energy in */ + opus_int start_lag, /* I lag offset to search around */ + opus_int sf_length, /* I length of one 5 ms subframe */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +); + +/*************************************************************/ +/* FIXED POINT CORE PITCH ANALYSIS FUNCTION */ +/*************************************************************/ +opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const opus_int16 *frame_unscaled, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O 4 pitch lag values */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + opus_int *LTPCorr_Q15, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const opus_int32 search_thres1_Q16, /* I First stage threshold for lag candidates 0 - 1 */ + const opus_int search_thres2_Q13, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I Sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I number of 5 ms subframes */ + int arch /* I Run-time architecture */ +) +{ + VARDECL( opus_int16, frame_8kHz_buf ); + VARDECL( opus_int16, frame_4kHz ); + VARDECL( opus_int16, frame_scaled ); + opus_int32 filt_state[ 6 ]; + const opus_int16 *frame, *frame_8kHz; + opus_int i, k, d, j; + VARDECL( opus_int16, C ); + VARDECL( opus_int32, xcorr32 ); + const opus_int16 *target_ptr, *basis_ptr; + opus_int32 cross_corr, normalizer, energy, energy_basis, energy_target; + opus_int d_srch[ PE_D_SRCH_LENGTH ], Cmax, length_d_srch, length_d_comp, shift; + VARDECL( opus_int16, d_comp ); + opus_int32 sum, threshold, lag_counter; + opus_int CBimax, CBimax_new, CBimax_old, lag, start_lag, end_lag, lag_new; + opus_int32 CC[ PE_NB_CBKS_STAGE2_EXT ], CCmax, CCmax_b, CCmax_new_b, CCmax_new; + VARDECL( silk_pe_stage3_vals, energies_st3 ); + VARDECL( silk_pe_stage3_vals, cross_corr_st3 ); + opus_int frame_length, frame_length_8kHz, frame_length_4kHz; + opus_int sf_length; + opus_int min_lag; + opus_int max_lag; + opus_int32 contour_bias_Q15, diff; + opus_int nb_cbk_search, cbk_size; + opus_int32 delta_lag_log2_sqr_Q7, lag_log2_Q7, prevLag_log2_Q7, prev_lag_bias_Q13; + const opus_int8 *Lag_CB_ptr; + SAVE_STACK; + + /* Check for valid sampling frequency */ + celt_assert( Fs_kHz == 8 || Fs_kHz == 12 || Fs_kHz == 16 ); + + /* Check for valid complexity setting */ + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + silk_assert( search_thres1_Q16 >= 0 && search_thres1_Q16 <= (1<<16) ); + silk_assert( search_thres2_Q13 >= 0 && search_thres2_Q13 <= (1<<13) ); + + /* Set up frame lengths max / min lag for the sampling frequency */ + frame_length = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * Fs_kHz; + frame_length_4kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 4; + frame_length_8kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 8; + sf_length = PE_SUBFR_LENGTH_MS * Fs_kHz; + min_lag = PE_MIN_LAG_MS * Fs_kHz; + max_lag = PE_MAX_LAG_MS * Fs_kHz - 1; + + /* Downscale input if necessary */ + silk_sum_sqr_shift( &energy, &shift, frame_unscaled, frame_length ); + shift += 3 - silk_CLZ32( energy ); /* at least two bits headroom */ + ALLOC( frame_scaled, frame_length, opus_int16 ); + if( shift > 0 ) { + shift = silk_RSHIFT( shift + 1, 1 ); + for( i = 0; i < frame_length; i++ ) { + frame_scaled[ i ] = silk_RSHIFT( frame_unscaled[ i ], shift ); + } + frame = frame_scaled; + } else { + frame = frame_unscaled; + } + + ALLOC( frame_8kHz_buf, ( Fs_kHz == 8 ) ? 1 : frame_length_8kHz, opus_int16 ); + /* Resample from input sampled at Fs_kHz to 8 kHz */ + if( Fs_kHz == 16 ) { + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); + silk_resampler_down2( filt_state, frame_8kHz_buf, frame, frame_length ); + frame_8kHz = frame_8kHz_buf; + } else if( Fs_kHz == 12 ) { + silk_memset( filt_state, 0, 6 * sizeof( opus_int32 ) ); + silk_resampler_down2_3( filt_state, frame_8kHz_buf, frame, frame_length ); + frame_8kHz = frame_8kHz_buf; + } else { + celt_assert( Fs_kHz == 8 ); + frame_8kHz = frame; + } + + /* Decimate again to 4 kHz */ + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) );/* Set state to zero */ + ALLOC( frame_4kHz, frame_length_4kHz, opus_int16 ); + silk_resampler_down2( filt_state, frame_4kHz, frame_8kHz, frame_length_8kHz ); + + /* Low-pass filter */ + for( i = frame_length_4kHz - 1; i > 0; i-- ) { + frame_4kHz[ i ] = silk_ADD_SAT16( frame_4kHz[ i ], frame_4kHz[ i - 1 ] ); + } + + + /****************************************************************************** + * FIRST STAGE, operating in 4 khz + ******************************************************************************/ + ALLOC( C, nb_subfr * CSTRIDE_8KHZ, opus_int16 ); + ALLOC( xcorr32, MAX_LAG_4KHZ-MIN_LAG_4KHZ+1, opus_int32 ); + silk_memset( C, 0, (nb_subfr >> 1) * CSTRIDE_4KHZ * sizeof( opus_int16 ) ); + target_ptr = &frame_4kHz[ silk_LSHIFT( SF_LENGTH_4KHZ, 2 ) ]; + for( k = 0; k < nb_subfr >> 1; k++ ) { + /* Check that we are within range of the array */ + celt_assert( target_ptr >= frame_4kHz ); + celt_assert( target_ptr + SF_LENGTH_8KHZ <= frame_4kHz + frame_length_4kHz ); + + basis_ptr = target_ptr - MIN_LAG_4KHZ; + + /* Check that we are within range of the array */ + celt_assert( basis_ptr >= frame_4kHz ); + celt_assert( basis_ptr + SF_LENGTH_8KHZ <= frame_4kHz + frame_length_4kHz ); + + celt_pitch_xcorr( target_ptr, target_ptr - MAX_LAG_4KHZ, xcorr32, SF_LENGTH_8KHZ, MAX_LAG_4KHZ - MIN_LAG_4KHZ + 1, arch ); + + /* Calculate first vector products before loop */ + cross_corr = xcorr32[ MAX_LAG_4KHZ - MIN_LAG_4KHZ ]; + normalizer = silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ, arch ); + normalizer = silk_ADD32( normalizer, silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ) ); + normalizer = silk_ADD32( normalizer, silk_SMULBB( SF_LENGTH_8KHZ, 4000 ) ); + + matrix_ptr( C, k, 0, CSTRIDE_4KHZ ) = + (opus_int16)silk_DIV32_varQ( cross_corr, normalizer, 13 + 1 ); /* Q13 */ + + /* From now on normalizer is computed recursively */ + for( d = MIN_LAG_4KHZ + 1; d <= MAX_LAG_4KHZ; d++ ) { + basis_ptr--; + + /* Check that we are within range of the array */ + silk_assert( basis_ptr >= frame_4kHz ); + silk_assert( basis_ptr + SF_LENGTH_8KHZ <= frame_4kHz + frame_length_4kHz ); + + cross_corr = xcorr32[ MAX_LAG_4KHZ - d ]; + + /* Add contribution of new sample and remove contribution from oldest sample */ + normalizer = silk_ADD32( normalizer, + silk_SMULBB( basis_ptr[ 0 ], basis_ptr[ 0 ] ) - + silk_SMULBB( basis_ptr[ SF_LENGTH_8KHZ ], basis_ptr[ SF_LENGTH_8KHZ ] ) ); + + matrix_ptr( C, k, d - MIN_LAG_4KHZ, CSTRIDE_4KHZ) = + (opus_int16)silk_DIV32_varQ( cross_corr, normalizer, 13 + 1 ); /* Q13 */ + } + /* Update target pointer */ + target_ptr += SF_LENGTH_8KHZ; + } + + /* Combine two subframes into single correlation measure and apply short-lag bias */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + for( i = MAX_LAG_4KHZ; i >= MIN_LAG_4KHZ; i-- ) { + sum = (opus_int32)matrix_ptr( C, 0, i - MIN_LAG_4KHZ, CSTRIDE_4KHZ ) + + (opus_int32)matrix_ptr( C, 1, i - MIN_LAG_4KHZ, CSTRIDE_4KHZ ); /* Q14 */ + sum = silk_SMLAWB( sum, sum, silk_LSHIFT( -i, 4 ) ); /* Q14 */ + C[ i - MIN_LAG_4KHZ ] = (opus_int16)sum; /* Q14 */ + } + } else { + /* Only short-lag bias */ + for( i = MAX_LAG_4KHZ; i >= MIN_LAG_4KHZ; i-- ) { + sum = silk_LSHIFT( (opus_int32)C[ i - MIN_LAG_4KHZ ], 1 ); /* Q14 */ + sum = silk_SMLAWB( sum, sum, silk_LSHIFT( -i, 4 ) ); /* Q14 */ + C[ i - MIN_LAG_4KHZ ] = (opus_int16)sum; /* Q14 */ + } + } + + /* Sort */ + length_d_srch = silk_ADD_LSHIFT32( 4, complexity, 1 ); + celt_assert( 3 * length_d_srch <= PE_D_SRCH_LENGTH ); + silk_insertion_sort_decreasing_int16( C, d_srch, CSTRIDE_4KHZ, + length_d_srch ); + + /* Escape if correlation is very low already here */ + Cmax = (opus_int)C[ 0 ]; /* Q14 */ + if( Cmax < SILK_FIX_CONST( 0.2, 14 ) ) { + silk_memset( pitch_out, 0, nb_subfr * sizeof( opus_int ) ); + *LTPCorr_Q15 = 0; + *lagIndex = 0; + *contourIndex = 0; + RESTORE_STACK; + return 1; + } + + threshold = silk_SMULWB( search_thres1_Q16, Cmax ); + for( i = 0; i < length_d_srch; i++ ) { + /* Convert to 8 kHz indices for the sorted correlation that exceeds the threshold */ + if( C[ i ] > threshold ) { + d_srch[ i ] = silk_LSHIFT( d_srch[ i ] + MIN_LAG_4KHZ, 1 ); + } else { + length_d_srch = i; + break; + } + } + celt_assert( length_d_srch > 0 ); + + ALLOC( d_comp, D_COMP_STRIDE, opus_int16 ); + for( i = D_COMP_MIN; i < D_COMP_MAX; i++ ) { + d_comp[ i - D_COMP_MIN ] = 0; + } + for( i = 0; i < length_d_srch; i++ ) { + d_comp[ d_srch[ i ] - D_COMP_MIN ] = 1; + } + + /* Convolution */ + for( i = D_COMP_MAX - 1; i >= MIN_LAG_8KHZ; i-- ) { + d_comp[ i - D_COMP_MIN ] += + d_comp[ i - 1 - D_COMP_MIN ] + d_comp[ i - 2 - D_COMP_MIN ]; + } + + length_d_srch = 0; + for( i = MIN_LAG_8KHZ; i < MAX_LAG_8KHZ + 1; i++ ) { + if( d_comp[ i + 1 - D_COMP_MIN ] > 0 ) { + d_srch[ length_d_srch ] = i; + length_d_srch++; + } + } + + /* Convolution */ + for( i = D_COMP_MAX - 1; i >= MIN_LAG_8KHZ; i-- ) { + d_comp[ i - D_COMP_MIN ] += d_comp[ i - 1 - D_COMP_MIN ] + + d_comp[ i - 2 - D_COMP_MIN ] + d_comp[ i - 3 - D_COMP_MIN ]; + } + + length_d_comp = 0; + for( i = MIN_LAG_8KHZ; i < D_COMP_MAX; i++ ) { + if( d_comp[ i - D_COMP_MIN ] > 0 ) { + d_comp[ length_d_comp ] = i - 2; + length_d_comp++; + } + } + + /********************************************************************************** + ** SECOND STAGE, operating at 8 kHz, on lag sections with high correlation + *************************************************************************************/ + + /********************************************************************************* + * Find energy of each subframe projected onto its history, for a range of delays + *********************************************************************************/ + silk_memset( C, 0, nb_subfr * CSTRIDE_8KHZ * sizeof( opus_int16 ) ); + + target_ptr = &frame_8kHz[ PE_LTP_MEM_LENGTH_MS * 8 ]; + for( k = 0; k < nb_subfr; k++ ) { + + /* Check that we are within range of the array */ + celt_assert( target_ptr >= frame_8kHz ); + celt_assert( target_ptr + SF_LENGTH_8KHZ <= frame_8kHz + frame_length_8kHz ); + + energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ, arch ), 1 ); + for( j = 0; j < length_d_comp; j++ ) { + d = d_comp[ j ]; + basis_ptr = target_ptr - d; + + /* Check that we are within range of the array */ + silk_assert( basis_ptr >= frame_8kHz ); + silk_assert( basis_ptr + SF_LENGTH_8KHZ <= frame_8kHz + frame_length_8kHz ); + + cross_corr = silk_inner_prod_aligned( target_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ); + if( cross_corr > 0 ) { + energy_basis = silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ); + matrix_ptr( C, k, d - ( MIN_LAG_8KHZ - 2 ), CSTRIDE_8KHZ ) = + (opus_int16)silk_DIV32_varQ( cross_corr, + silk_ADD32( energy_target, + energy_basis ), + 13 + 1 ); /* Q13 */ + } else { + matrix_ptr( C, k, d - ( MIN_LAG_8KHZ - 2 ), CSTRIDE_8KHZ ) = 0; + } + } + target_ptr += SF_LENGTH_8KHZ; + } + + /* search over lag range and lags codebook */ + /* scale factor for lag codebook, as a function of center lag */ + + CCmax = silk_int32_MIN; + CCmax_b = silk_int32_MIN; + + CBimax = 0; /* To avoid returning undefined lag values */ + lag = -1; /* To check if lag with strong enough correlation has been found */ + + if( prevLag > 0 ) { + if( Fs_kHz == 12 ) { + prevLag = silk_DIV32_16( silk_LSHIFT( prevLag, 1 ), 3 ); + } else if( Fs_kHz == 16 ) { + prevLag = silk_RSHIFT( prevLag, 1 ); + } + prevLag_log2_Q7 = silk_lin2log( (opus_int32)prevLag ); + } else { + prevLag_log2_Q7 = 0; + } + silk_assert( search_thres2_Q13 == silk_SAT16( search_thres2_Q13 ) ); + /* Set up stage 2 codebook based on number of subframes */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + cbk_size = PE_NB_CBKS_STAGE2_EXT; + Lag_CB_ptr = &silk_CB_lags_stage2[ 0 ][ 0 ]; + if( Fs_kHz == 8 && complexity > SILK_PE_MIN_COMPLEX ) { + /* If input is 8 khz use a larger codebook here because it is last stage */ + nb_cbk_search = PE_NB_CBKS_STAGE2_EXT; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE2; + } + } else { + cbk_size = PE_NB_CBKS_STAGE2_10MS; + Lag_CB_ptr = &silk_CB_lags_stage2_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE2_10MS; + } + + for( k = 0; k < length_d_srch; k++ ) { + d = d_srch[ k ]; + for( j = 0; j < nb_cbk_search; j++ ) { + CC[ j ] = 0; + for( i = 0; i < nb_subfr; i++ ) { + opus_int d_subfr; + /* Try all codebooks */ + d_subfr = d + matrix_ptr( Lag_CB_ptr, i, j, cbk_size ); + CC[ j ] = CC[ j ] + + (opus_int32)matrix_ptr( C, i, + d_subfr - ( MIN_LAG_8KHZ - 2 ), + CSTRIDE_8KHZ ); + } + } + /* Find best codebook */ + CCmax_new = silk_int32_MIN; + CBimax_new = 0; + for( i = 0; i < nb_cbk_search; i++ ) { + if( CC[ i ] > CCmax_new ) { + CCmax_new = CC[ i ]; + CBimax_new = i; + } + } + + /* Bias towards shorter lags */ + lag_log2_Q7 = silk_lin2log( d ); /* Q7 */ + silk_assert( lag_log2_Q7 == silk_SAT16( lag_log2_Q7 ) ); + silk_assert( nb_subfr * SILK_FIX_CONST( PE_SHORTLAG_BIAS, 13 ) == silk_SAT16( nb_subfr * SILK_FIX_CONST( PE_SHORTLAG_BIAS, 13 ) ) ); + CCmax_new_b = CCmax_new - silk_RSHIFT( silk_SMULBB( nb_subfr * SILK_FIX_CONST( PE_SHORTLAG_BIAS, 13 ), lag_log2_Q7 ), 7 ); /* Q13 */ + + /* Bias towards previous lag */ + silk_assert( nb_subfr * SILK_FIX_CONST( PE_PREVLAG_BIAS, 13 ) == silk_SAT16( nb_subfr * SILK_FIX_CONST( PE_PREVLAG_BIAS, 13 ) ) ); + if( prevLag > 0 ) { + delta_lag_log2_sqr_Q7 = lag_log2_Q7 - prevLag_log2_Q7; + silk_assert( delta_lag_log2_sqr_Q7 == silk_SAT16( delta_lag_log2_sqr_Q7 ) ); + delta_lag_log2_sqr_Q7 = silk_RSHIFT( silk_SMULBB( delta_lag_log2_sqr_Q7, delta_lag_log2_sqr_Q7 ), 7 ); + prev_lag_bias_Q13 = silk_RSHIFT( silk_SMULBB( nb_subfr * SILK_FIX_CONST( PE_PREVLAG_BIAS, 13 ), *LTPCorr_Q15 ), 15 ); /* Q13 */ + prev_lag_bias_Q13 = silk_DIV32( silk_MUL( prev_lag_bias_Q13, delta_lag_log2_sqr_Q7 ), delta_lag_log2_sqr_Q7 + SILK_FIX_CONST( 0.5, 7 ) ); + CCmax_new_b -= prev_lag_bias_Q13; /* Q13 */ + } + + if( CCmax_new_b > CCmax_b && /* Find maximum biased correlation */ + CCmax_new > silk_SMULBB( nb_subfr, search_thres2_Q13 ) && /* Correlation needs to be high enough to be voiced */ + silk_CB_lags_stage2[ 0 ][ CBimax_new ] <= MIN_LAG_8KHZ /* Lag must be in range */ + ) { + CCmax_b = CCmax_new_b; + CCmax = CCmax_new; + lag = d; + CBimax = CBimax_new; + } + } + + if( lag == -1 ) { + /* No suitable candidate found */ + silk_memset( pitch_out, 0, nb_subfr * sizeof( opus_int ) ); + *LTPCorr_Q15 = 0; + *lagIndex = 0; + *contourIndex = 0; + RESTORE_STACK; + return 1; + } + + /* Output normalized correlation */ + *LTPCorr_Q15 = (opus_int)silk_LSHIFT( silk_DIV32_16( CCmax, nb_subfr ), 2 ); + silk_assert( *LTPCorr_Q15 >= 0 ); + + if( Fs_kHz > 8 ) { + /* Search in original signal */ + + CBimax_old = CBimax; + /* Compensate for decimation */ + silk_assert( lag == silk_SAT16( lag ) ); + if( Fs_kHz == 12 ) { + lag = silk_RSHIFT( silk_SMULBB( lag, 3 ), 1 ); + } else if( Fs_kHz == 16 ) { + lag = silk_LSHIFT( lag, 1 ); + } else { + lag = silk_SMULBB( lag, 3 ); + } + + lag = silk_LIMIT_int( lag, min_lag, max_lag ); + start_lag = silk_max_int( lag - 2, min_lag ); + end_lag = silk_min_int( lag + 2, max_lag ); + lag_new = lag; /* to avoid undefined lag */ + CBimax = 0; /* to avoid undefined lag */ + + CCmax = silk_int32_MIN; + /* pitch lags according to second stage */ + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag + 2 * silk_CB_lags_stage2[ k ][ CBimax_old ]; + } + + /* Set up codebook parameters according to complexity setting and frame length */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + nb_cbk_search = (opus_int)silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + } + + /* Calculate the correlations and energies needed in stage 3 */ + ALLOC( energies_st3, nb_subfr * nb_cbk_search, silk_pe_stage3_vals ); + ALLOC( cross_corr_st3, nb_subfr * nb_cbk_search, silk_pe_stage3_vals ); + silk_P_Ana_calc_corr_st3( cross_corr_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); + silk_P_Ana_calc_energy_st3( energies_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); + + lag_counter = 0; + silk_assert( lag == silk_SAT16( lag ) ); + contour_bias_Q15 = silk_DIV32_16( SILK_FIX_CONST( PE_FLATCONTOUR_BIAS, 15 ), lag ); + + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * Fs_kHz ]; + energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, nb_subfr * sf_length, arch ), 1 ); + for( d = start_lag; d <= end_lag; d++ ) { + for( j = 0; j < nb_cbk_search; j++ ) { + cross_corr = 0; + energy = energy_target; + for( k = 0; k < nb_subfr; k++ ) { + cross_corr = silk_ADD32( cross_corr, + matrix_ptr( cross_corr_st3, k, j, + nb_cbk_search )[ lag_counter ] ); + energy = silk_ADD32( energy, + matrix_ptr( energies_st3, k, j, + nb_cbk_search )[ lag_counter ] ); + silk_assert( energy >= 0 ); + } + if( cross_corr > 0 ) { + CCmax_new = silk_DIV32_varQ( cross_corr, energy, 13 + 1 ); /* Q13 */ + /* Reduce depending on flatness of contour */ + diff = silk_int16_MAX - silk_MUL( contour_bias_Q15, j ); /* Q15 */ + silk_assert( diff == silk_SAT16( diff ) ); + CCmax_new = silk_SMULWB( CCmax_new, diff ); /* Q14 */ + } else { + CCmax_new = 0; + } + + if( CCmax_new > CCmax && ( d + silk_CB_lags_stage3[ 0 ][ j ] ) <= max_lag ) { + CCmax = CCmax_new; + lag_new = d; + CBimax = j; + } + } + lag_counter++; + } + + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag_new + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], min_lag, PE_MAX_LAG_MS * Fs_kHz ); + } + *lagIndex = (opus_int16)( lag_new - min_lag); + *contourIndex = (opus_int8)CBimax; + } else { /* Fs_kHz == 8 */ + /* Save Lags */ + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], MIN_LAG_8KHZ, PE_MAX_LAG_MS * 8 ); + } + *lagIndex = (opus_int16)( lag - MIN_LAG_8KHZ ); + *contourIndex = (opus_int8)CBimax; + } + celt_assert( *lagIndex >= 0 ); + /* return as voiced */ + RESTORE_STACK; + return 0; +} + +/*********************************************************************** + * Calculates the correlations used in stage 3 search. In order to cover + * the whole lag codebook for all the searched offset lags (lag +- 2), + * the following correlations are needed in each sub frame: + * + * sf1: lag range [-8,...,7] total 16 correlations + * sf2: lag range [-4,...,4] total 9 correlations + * sf3: lag range [-3,....4] total 8 correltions + * sf4: lag range [-6,....8] total 15 correlations + * + * In total 48 correlations. The direct implementation computed in worst + * case 4*12*5 = 240 correlations, but more likely around 120. + ***********************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_pe_stage3_vals cross_corr_st3[], /* O 3 DIM correlation array */ + const opus_int16 frame[], /* I vector to correlate */ + opus_int start_lag, /* I lag offset to search around */ + opus_int sf_length, /* I length of a 5 ms subframe */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +) +{ + const opus_int16 *target_ptr; + opus_int i, j, k, lag_counter, lag_low, lag_high; + opus_int nb_cbk_search, delta, idx, cbk_size; + VARDECL( opus_int32, scratch_mem ); + VARDECL( opus_int32, xcorr32 ); + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + SAVE_STACK; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + ALLOC( scratch_mem, SCRATCH_SIZE, opus_int32 ); + ALLOC( xcorr32, SCRATCH_SIZE, opus_int32 ); + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; /* Pointer to middle of frame */ + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the correlations for each subframe */ + lag_low = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + lag_high = matrix_ptr( Lag_range_ptr, k, 1, 2 ); + celt_assert(lag_high-lag_low+1 <= SCRATCH_SIZE); + celt_pitch_xcorr( target_ptr, target_ptr - start_lag - lag_high, xcorr32, sf_length, lag_high - lag_low + 1, arch ); + for( j = lag_low; j <= lag_high; j++ ) { + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[ lag_counter ] = xcorr32[ lag_high - j ]; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + matrix_ptr( cross_corr_st3, k, i, nb_cbk_search )[ j ] = + scratch_mem[ idx + j ]; + } + } + target_ptr += sf_length; + } + RESTORE_STACK; +} + +/********************************************************************/ +/* Calculate the energies for first two subframes. The energies are */ +/* calculated recursively. */ +/********************************************************************/ +static void silk_P_Ana_calc_energy_st3( + silk_pe_stage3_vals energies_st3[], /* O 3 DIM energy array */ + const opus_int16 frame[], /* I vector to calc energy in */ + opus_int start_lag, /* I lag offset to search around */ + opus_int sf_length, /* I length of one 5 ms subframe */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +) +{ + const opus_int16 *target_ptr, *basis_ptr; + opus_int32 energy; + opus_int k, i, j, lag_counter; + opus_int nb_cbk_search, delta, idx, cbk_size, lag_diff; + VARDECL( opus_int32, scratch_mem ); + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + SAVE_STACK; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + ALLOC( scratch_mem, SCRATCH_SIZE, opus_int32 ); + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the energy for first lag */ + basis_ptr = target_ptr - ( start_lag + matrix_ptr( Lag_range_ptr, k, 0, 2 ) ); + energy = silk_inner_prod_aligned( basis_ptr, basis_ptr, sf_length, arch ); + silk_assert( energy >= 0 ); + scratch_mem[ lag_counter ] = energy; + lag_counter++; + + lag_diff = ( matrix_ptr( Lag_range_ptr, k, 1, 2 ) - matrix_ptr( Lag_range_ptr, k, 0, 2 ) + 1 ); + for( i = 1; i < lag_diff; i++ ) { + /* remove part outside new window */ + energy -= silk_SMULBB( basis_ptr[ sf_length - i ], basis_ptr[ sf_length - i ] ); + silk_assert( energy >= 0 ); + + /* add part that comes into window */ + energy = silk_ADD_SAT32( energy, silk_SMULBB( basis_ptr[ -i ], basis_ptr[ -i ] ) ); + silk_assert( energy >= 0 ); + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[ lag_counter ] = energy; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + matrix_ptr( energies_st3, k, i, nb_cbk_search )[ j ] = + scratch_mem[ idx + j ]; + silk_assert( + matrix_ptr( energies_st3, k, i, nb_cbk_search )[ j ] >= 0 ); + } + } + target_ptr += sf_length; + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/process_gains_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/process_gains_FIX.c new file mode 100644 index 0000000000..05aba31788 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/process_gains_FIX.c @@ -0,0 +1,117 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "tuning_parameters.h" + +/* Processing of gains */ +void silk_process_gains_FIX( + silk_encoder_state_FIX *psEnc, /* I/O Encoder state */ + silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + silk_shape_state_FIX *psShapeSt = &psEnc->sShape; + opus_int k; + opus_int32 s_Q16, InvMaxSqrVal_Q16, gain, gain_squared, ResNrg, ResNrgPart, quant_offset_Q10; + + /* Gain reduction when LTP coding gain is high */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /*s = -0.5f * silk_sigmoid( 0.25f * ( psEncCtrl->LTPredCodGain - 12.0f ) ); */ + s_Q16 = -silk_sigm_Q15( silk_RSHIFT_ROUND( psEncCtrl->LTPredCodGain_Q7 - SILK_FIX_CONST( 12.0, 7 ), 4 ) ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains_Q16[ k ] = silk_SMLAWB( psEncCtrl->Gains_Q16[ k ], psEncCtrl->Gains_Q16[ k ], s_Q16 ); + } + } + + /* Limit the quantized signal */ + /* InvMaxSqrVal = pow( 2.0f, 0.33f * ( 21.0f - SNR_dB ) ) / subfr_length; */ + InvMaxSqrVal_Q16 = silk_DIV32_16( silk_log2lin( + silk_SMULWB( SILK_FIX_CONST( 21 + 16 / 0.33, 7 ) - psEnc->sCmn.SNR_dB_Q7, SILK_FIX_CONST( 0.33, 16 ) ) ), psEnc->sCmn.subfr_length ); + + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Soft limit on ratio residual energy and squared gains */ + ResNrg = psEncCtrl->ResNrg[ k ]; + ResNrgPart = silk_SMULWW( ResNrg, InvMaxSqrVal_Q16 ); + if( psEncCtrl->ResNrgQ[ k ] > 0 ) { + ResNrgPart = silk_RSHIFT_ROUND( ResNrgPart, psEncCtrl->ResNrgQ[ k ] ); + } else { + if( ResNrgPart >= silk_RSHIFT( silk_int32_MAX, -psEncCtrl->ResNrgQ[ k ] ) ) { + ResNrgPart = silk_int32_MAX; + } else { + ResNrgPart = silk_LSHIFT( ResNrgPart, -psEncCtrl->ResNrgQ[ k ] ); + } + } + gain = psEncCtrl->Gains_Q16[ k ]; + gain_squared = silk_ADD_SAT32( ResNrgPart, silk_SMMUL( gain, gain ) ); + if( gain_squared < silk_int16_MAX ) { + /* recalculate with higher precision */ + gain_squared = silk_SMLAWW( silk_LSHIFT( ResNrgPart, 16 ), gain, gain ); + silk_assert( gain_squared > 0 ); + gain = silk_SQRT_APPROX( gain_squared ); /* Q8 */ + gain = silk_min( gain, silk_int32_MAX >> 8 ); + psEncCtrl->Gains_Q16[ k ] = silk_LSHIFT_SAT32( gain, 8 ); /* Q16 */ + } else { + gain = silk_SQRT_APPROX( gain_squared ); /* Q0 */ + gain = silk_min( gain, silk_int32_MAX >> 16 ); + psEncCtrl->Gains_Q16[ k ] = silk_LSHIFT_SAT32( gain, 16 ); /* Q16 */ + } + } + + /* Save unquantized gains and gain Index */ + silk_memcpy( psEncCtrl->GainsUnq_Q16, psEncCtrl->Gains_Q16, psEnc->sCmn.nb_subfr * sizeof( opus_int32 ) ); + psEncCtrl->lastGainIndexPrev = psShapeSt->LastGainIndex; + + /* Quantize gains */ + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, psEncCtrl->Gains_Q16, + &psShapeSt->LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Set quantizer offset for voiced signals. Larger offset when LTP coding gain is low or tilt is high (ie low-pass) */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + if( psEncCtrl->LTPredCodGain_Q7 + silk_RSHIFT( psEnc->sCmn.input_tilt_Q15, 8 ) > SILK_FIX_CONST( 1.0, 7 ) ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /* Quantizer boundary adjustment */ + quant_offset_Q10 = silk_Quantization_Offsets_Q10[ psEnc->sCmn.indices.signalType >> 1 ][ psEnc->sCmn.indices.quantOffsetType ]; + psEncCtrl->Lambda_Q10 = SILK_FIX_CONST( LAMBDA_OFFSET, 10 ) + + silk_SMULBB( SILK_FIX_CONST( LAMBDA_DELAYED_DECISIONS, 10 ), psEnc->sCmn.nStatesDelayedDecision ) + + silk_SMULWB( SILK_FIX_CONST( LAMBDA_SPEECH_ACT, 18 ), psEnc->sCmn.speech_activity_Q8 ) + + silk_SMULWB( SILK_FIX_CONST( LAMBDA_INPUT_QUALITY, 12 ), psEncCtrl->input_quality_Q14 ) + + silk_SMULWB( SILK_FIX_CONST( LAMBDA_CODING_QUALITY, 12 ), psEncCtrl->coding_quality_Q14 ) + + silk_SMULWB( SILK_FIX_CONST( LAMBDA_QUANT_OFFSET, 16 ), quant_offset_Q10 ); + + silk_assert( psEncCtrl->Lambda_Q10 > 0 ); + silk_assert( psEncCtrl->Lambda_Q10 < SILK_FIX_CONST( 2, 10 ) ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/regularize_correlations_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/regularize_correlations_FIX.c new file mode 100644 index 0000000000..a2836b05f4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/regularize_correlations_FIX.c @@ -0,0 +1,47 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +/* Add noise to matrix diagonal */ +void silk_regularize_correlations_FIX( + opus_int32 *XX, /* I/O Correlation matrices */ + opus_int32 *xx, /* I/O Correlation values */ + opus_int32 noise, /* I Noise to add */ + opus_int D /* I Dimension of XX */ +) +{ + opus_int i; + for( i = 0; i < D; i++ ) { + matrix_ptr( &XX[ 0 ], i, i, D ) = silk_ADD32( matrix_ptr( &XX[ 0 ], i, i, D ), noise ); + } + xx[ 0 ] += noise; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy16_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy16_FIX.c new file mode 100644 index 0000000000..7f130f3d3d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy16_FIX.c @@ -0,0 +1,103 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +opus_int32 silk_residual_energy16_covar_FIX( + const opus_int16 *c, /* I Prediction vector */ + const opus_int32 *wXX, /* I Correlation matrix */ + const opus_int32 *wXx, /* I Correlation vector */ + opus_int32 wxx, /* I Signal energy */ + opus_int D, /* I Dimension */ + opus_int cQ /* I Q value for c vector 0 - 15 */ +) +{ + opus_int i, j, lshifts, Qxtra; + opus_int32 c_max, w_max, tmp, tmp2, nrg; + opus_int cn[ MAX_MATRIX_SIZE ]; + const opus_int32 *pRow; + + /* Safety checks */ + celt_assert( D >= 0 ); + celt_assert( D <= 16 ); + celt_assert( cQ > 0 ); + celt_assert( cQ < 16 ); + + lshifts = 16 - cQ; + Qxtra = lshifts; + + c_max = 0; + for( i = 0; i < D; i++ ) { + c_max = silk_max_32( c_max, silk_abs( (opus_int32)c[ i ] ) ); + } + Qxtra = silk_min_int( Qxtra, silk_CLZ32( c_max ) - 17 ); + + w_max = silk_max_32( wXX[ 0 ], wXX[ D * D - 1 ] ); + Qxtra = silk_min_int( Qxtra, silk_CLZ32( silk_MUL( D, silk_RSHIFT( silk_SMULWB( w_max, c_max ), 4 ) ) ) - 5 ); + Qxtra = silk_max_int( Qxtra, 0 ); + for( i = 0; i < D; i++ ) { + cn[ i ] = silk_LSHIFT( ( opus_int )c[ i ], Qxtra ); + silk_assert( silk_abs(cn[i]) <= ( silk_int16_MAX + 1 ) ); /* Check that silk_SMLAWB can be used */ + } + lshifts -= Qxtra; + + /* Compute wxx - 2 * wXx * c */ + tmp = 0; + for( i = 0; i < D; i++ ) { + tmp = silk_SMLAWB( tmp, wXx[ i ], cn[ i ] ); + } + nrg = silk_RSHIFT( wxx, 1 + lshifts ) - tmp; /* Q: -lshifts - 1 */ + + /* Add c' * wXX * c, assuming wXX is symmetric */ + tmp2 = 0; + for( i = 0; i < D; i++ ) { + tmp = 0; + pRow = &wXX[ i * D ]; + for( j = i + 1; j < D; j++ ) { + tmp = silk_SMLAWB( tmp, pRow[ j ], cn[ j ] ); + } + tmp = silk_SMLAWB( tmp, silk_RSHIFT( pRow[ i ], 1 ), cn[ i ] ); + tmp2 = silk_SMLAWB( tmp2, tmp, cn[ i ] ); + } + nrg = silk_ADD_LSHIFT32( nrg, tmp2, lshifts ); /* Q: -lshifts - 1 */ + + /* Keep one bit free always, because we add them for LSF interpolation */ + if( nrg < 1 ) { + nrg = 1; + } else if( nrg > silk_RSHIFT( silk_int32_MAX, lshifts + 2 ) ) { + nrg = silk_int32_MAX >> 1; + } else { + nrg = silk_LSHIFT( nrg, lshifts + 1 ); /* Q0 */ + } + return nrg; + +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy_FIX.c new file mode 100644 index 0000000000..6c7cade9a0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/residual_energy_FIX.c @@ -0,0 +1,98 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" +#include "stack_alloc.h" + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FIX( + opus_int32 nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + opus_int nrgsQ[ MAX_NB_SUBFR ], /* O Q value per subframe */ + const opus_int16 x[], /* I Input signal */ + opus_int16 a_Q12[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const opus_int32 gains[ MAX_NB_SUBFR ], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +) +{ + opus_int offset, i, j, rshift, lz1, lz2; + opus_int16 *LPC_res_ptr; + VARDECL( opus_int16, LPC_res ); + const opus_int16 *x_ptr; + opus_int32 tmp32; + SAVE_STACK; + + x_ptr = x; + offset = LPC_order + subfr_length; + + /* Filter input to create the LPC residual for each frame half, and measure subframe energies */ + ALLOC( LPC_res, ( MAX_NB_SUBFR >> 1 ) * offset, opus_int16 ); + celt_assert( ( nb_subfr >> 1 ) * ( MAX_NB_SUBFR >> 1 ) == nb_subfr ); + for( i = 0; i < nb_subfr >> 1; i++ ) { + /* Calculate half frame LPC residual signal including preceding samples */ + silk_LPC_analysis_filter( LPC_res, x_ptr, a_Q12[ i ], ( MAX_NB_SUBFR >> 1 ) * offset, LPC_order, arch ); + + /* Point to first subframe of the just calculated LPC residual signal */ + LPC_res_ptr = LPC_res + LPC_order; + for( j = 0; j < ( MAX_NB_SUBFR >> 1 ); j++ ) { + /* Measure subframe energy */ + silk_sum_sqr_shift( &nrgs[ i * ( MAX_NB_SUBFR >> 1 ) + j ], &rshift, LPC_res_ptr, subfr_length ); + + /* Set Q values for the measured energy */ + nrgsQ[ i * ( MAX_NB_SUBFR >> 1 ) + j ] = -rshift; + + /* Move to next subframe */ + LPC_res_ptr += offset; + } + /* Move to next frame half */ + x_ptr += ( MAX_NB_SUBFR >> 1 ) * offset; + } + + /* Apply the squared subframe gains */ + for( i = 0; i < nb_subfr; i++ ) { + /* Fully upscale gains and energies */ + lz1 = silk_CLZ32( nrgs[ i ] ) - 1; + lz2 = silk_CLZ32( gains[ i ] ) - 1; + + tmp32 = silk_LSHIFT32( gains[ i ], lz2 ); + + /* Find squared gains */ + tmp32 = silk_SMMUL( tmp32, tmp32 ); /* Q( 2 * lz2 - 32 )*/ + + /* Scale energies */ + nrgs[ i ] = silk_SMMUL( tmp32, silk_LSHIFT32( nrgs[ i ], lz1 ) ); /* Q( nrgsQ[ i ] + lz1 + 2 * lz2 - 32 - 32 )*/ + nrgsQ[ i ] += lz1 + 2 * lz2 - 32 - 32; + } + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur64_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur64_FIX.c new file mode 100644 index 0000000000..4b7e19ea59 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur64_FIX.c @@ -0,0 +1,93 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Slower than schur(), but more accurate. */ +/* Uses SMULL(), available on armv4 */ +opus_int32 silk_schur64( /* O returns residual energy */ + opus_int32 rc_Q16[], /* O Reflection coefficients [order] Q16 */ + const opus_int32 c[], /* I Correlations [order+1] */ + opus_int32 order /* I Prediction order */ +) +{ + opus_int k, n; + opus_int32 C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; + opus_int32 Ctmp1_Q30, Ctmp2_Q30, rc_tmp_Q31; + + celt_assert( order >= 0 && order <= SILK_MAX_ORDER_LPC ); + + /* Check for invalid input */ + if( c[ 0 ] <= 0 ) { + silk_memset( rc_Q16, 0, order * sizeof( opus_int32 ) ); + return 0; + } + + k = 0; + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = c[ k ]; + } while( ++k <= order ); + + for( k = 0; k < order; k++ ) { + /* Check that we won't be getting an unstable rc, otherwise stop here. */ + if (silk_abs_int32(C[ k + 1 ][ 0 ]) >= C[ 0 ][ 1 ]) { + if ( C[ k + 1 ][ 0 ] > 0 ) { + rc_Q16[ k ] = -SILK_FIX_CONST( .99f, 16 ); + } else { + rc_Q16[ k ] = SILK_FIX_CONST( .99f, 16 ); + } + k++; + break; + } + + /* Get reflection coefficient: divide two Q30 values and get result in Q31 */ + rc_tmp_Q31 = silk_DIV32_varQ( -C[ k + 1 ][ 0 ], C[ 0 ][ 1 ], 31 ); + + /* Save the output */ + rc_Q16[ k ] = silk_RSHIFT_ROUND( rc_tmp_Q31, 15 ); + + /* Update correlations */ + for( n = 0; n < order - k; n++ ) { + Ctmp1_Q30 = C[ n + k + 1 ][ 0 ]; + Ctmp2_Q30 = C[ n ][ 1 ]; + + /* Multiply and add the highest int32 */ + C[ n + k + 1 ][ 0 ] = Ctmp1_Q30 + silk_SMMUL( silk_LSHIFT( Ctmp2_Q30, 1 ), rc_tmp_Q31 ); + C[ n ][ 1 ] = Ctmp2_Q30 + silk_SMMUL( silk_LSHIFT( Ctmp1_Q30, 1 ), rc_tmp_Q31 ); + } + } + + for(; k < order; k++ ) { + rc_Q16[ k ] = 0; + } + + return silk_max_32( 1, C[ 0 ][ 1 ] ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur_FIX.c new file mode 100644 index 0000000000..2840f6b1aa --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/schur_FIX.c @@ -0,0 +1,107 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Faster than schur64(), but much less accurate. */ +/* uses SMLAWB(), requiring armv5E and higher. */ +opus_int32 silk_schur( /* O Returns residual energy */ + opus_int16 *rc_Q15, /* O reflection coefficients [order] Q15 */ + const opus_int32 *c, /* I correlations [order+1] */ + const opus_int32 order /* I prediction order */ +) +{ + opus_int k, n, lz; + opus_int32 C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; + opus_int32 Ctmp1, Ctmp2, rc_tmp_Q15; + + celt_assert( order >= 0 && order <= SILK_MAX_ORDER_LPC ); + + /* Get number of leading zeros */ + lz = silk_CLZ32( c[ 0 ] ); + + /* Copy correlations and adjust level to Q30 */ + k = 0; + if( lz < 2 ) { + /* lz must be 1, so shift one to the right */ + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = silk_RSHIFT( c[ k ], 1 ); + } while( ++k <= order ); + } else if( lz > 2 ) { + /* Shift to the left */ + lz -= 2; + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = silk_LSHIFT( c[ k ], lz ); + } while( ++k <= order ); + } else { + /* No need to shift */ + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = c[ k ]; + } while( ++k <= order ); + } + + for( k = 0; k < order; k++ ) { + /* Check that we won't be getting an unstable rc, otherwise stop here. */ + if (silk_abs_int32(C[ k + 1 ][ 0 ]) >= C[ 0 ][ 1 ]) { + if ( C[ k + 1 ][ 0 ] > 0 ) { + rc_Q15[ k ] = -SILK_FIX_CONST( .99f, 15 ); + } else { + rc_Q15[ k ] = SILK_FIX_CONST( .99f, 15 ); + } + k++; + break; + } + + /* Get reflection coefficient */ + rc_tmp_Q15 = -silk_DIV32_16( C[ k + 1 ][ 0 ], silk_max_32( silk_RSHIFT( C[ 0 ][ 1 ], 15 ), 1 ) ); + + /* Clip (shouldn't happen for properly conditioned inputs) */ + rc_tmp_Q15 = silk_SAT16( rc_tmp_Q15 ); + + /* Store */ + rc_Q15[ k ] = (opus_int16)rc_tmp_Q15; + + /* Update correlations */ + for( n = 0; n < order - k; n++ ) { + Ctmp1 = C[ n + k + 1 ][ 0 ]; + Ctmp2 = C[ n ][ 1 ]; + C[ n + k + 1 ][ 0 ] = silk_SMLAWB( Ctmp1, silk_LSHIFT( Ctmp2, 1 ), rc_tmp_Q15 ); + C[ n ][ 1 ] = silk_SMLAWB( Ctmp2, silk_LSHIFT( Ctmp1, 1 ), rc_tmp_Q15 ); + } + } + + for(; k < order; k++ ) { + rc_Q15[ k ] = 0; + } + + /* return residual energy */ + return silk_max_32( 1, C[ 0 ][ 1 ] ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/structs_FIX.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/structs_FIX.h new file mode 100644 index 0000000000..2774a97b24 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/structs_FIX.h @@ -0,0 +1,116 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_STRUCTS_FIX_H +#define SILK_STRUCTS_FIX_H + +#include "typedef.h" +#include "main.h" +#include "structs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/********************************/ +/* Noise shaping analysis state */ +/********************************/ +typedef struct { + opus_int8 LastGainIndex; + opus_int32 HarmBoost_smth_Q16; + opus_int32 HarmShapeGain_smth_Q16; + opus_int32 Tilt_smth_Q16; +} silk_shape_state_FIX; + +/********************************/ +/* Encoder state FIX */ +/********************************/ +typedef struct { + silk_encoder_state sCmn; /* Common struct, shared with floating-point code */ + silk_shape_state_FIX sShape; /* Shape state */ + + /* Buffer for find pitch and noise shape analysis */ + silk_DWORD_ALIGN opus_int16 x_buf[ 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX ];/* Buffer for find pitch and noise shape analysis */ + opus_int LTPCorr_Q15; /* Normalized correlation from pitch lag estimator */ + opus_int32 resNrgSmth; +} silk_encoder_state_FIX; + +/************************/ +/* Encoder control FIX */ +/************************/ +typedef struct { + /* Prediction and coding parameters */ + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; + opus_int LTP_scale_Q14; + opus_int pitchL[ MAX_NB_SUBFR ]; + + /* Noise shaping parameters */ + /* Testing */ + silk_DWORD_ALIGN opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ]; /* Packs two int16 coefficients per int32 value */ + opus_int Tilt_Q14[ MAX_NB_SUBFR ]; + opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ]; + opus_int Lambda_Q10; + opus_int input_quality_Q14; + opus_int coding_quality_Q14; + + /* measures */ + opus_int32 predGain_Q16; + opus_int LTPredCodGain_Q7; + opus_int32 ResNrg[ MAX_NB_SUBFR ]; /* Residual energy per subframe */ + opus_int ResNrgQ[ MAX_NB_SUBFR ]; /* Q domain for the residual energy > 0 */ + + /* Parameters for CBR mode */ + opus_int32 GainsUnq_Q16[ MAX_NB_SUBFR ]; + opus_int8 lastGainIndexPrev; +} silk_encoder_control_FIX; + +/************************/ +/* Encoder Super Struct */ +/************************/ +typedef struct { + silk_encoder_state_FIX state_Fxx[ ENCODER_NUM_CHANNELS ]; + stereo_enc_state sStereo; + opus_int32 nBitsUsedLBRR; + opus_int32 nBitsExceeded; + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int nPrevChannelsInternal; + opus_int timeSinceSwitchAllowed_ms; + opus_int allowBandwidthSwitch; + opus_int prev_decode_only_middle; +} silk_encoder; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/vector_ops_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/vector_ops_FIX.c new file mode 100644 index 0000000000..dcf84070a6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/vector_ops_FIX.c @@ -0,0 +1,102 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "pitch.h" + +/* Copy and multiply a vector by a constant */ +void silk_scale_copy_vector16( + opus_int16 *data_out, + const opus_int16 *data_in, + opus_int32 gain_Q16, /* I Gain in Q16 */ + const opus_int dataSize /* I Length */ +) +{ + opus_int i; + opus_int32 tmp32; + + for( i = 0; i < dataSize; i++ ) { + tmp32 = silk_SMULWB( gain_Q16, data_in[ i ] ); + data_out[ i ] = (opus_int16)silk_CHECK_FIT16( tmp32 ); + } +} + +/* Multiply a vector by a constant */ +void silk_scale_vector32_Q26_lshift_18( + opus_int32 *data1, /* I/O Q0/Q18 */ + opus_int32 gain_Q26, /* I Q26 */ + opus_int dataSize /* I length */ +) +{ + opus_int i; + + for( i = 0; i < dataSize; i++ ) { + data1[ i ] = (opus_int32)silk_CHECK_FIT32( silk_RSHIFT64( silk_SMULL( data1[ i ], gain_Q26 ), 8 ) ); /* OUTPUT: Q18 */ + } +} + +/* sum = for(i=0;i6, memory access can be reduced by half. */ +opus_int32 silk_inner_prod_aligned( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int len, /* I vector lengths */ + int arch /* I Run-time architecture */ +) +{ +#ifdef FIXED_POINT + return celt_inner_prod(inVec1, inVec2, len, arch); +#else + opus_int i; + opus_int32 sum = 0; + for( i = 0; i < len; i++ ) { + sum = silk_SMLABB( sum, inVec1[ i ], inVec2[ i ] ); + } + return sum; +#endif +} + +opus_int64 silk_inner_prod16_c( + const opus_int16 *inVec1, /* I input vector 1 */ + const opus_int16 *inVec2, /* I input vector 2 */ + const opus_int len /* I vector lengths */ +) +{ + opus_int i; + opus_int64 sum = 0; + for( i = 0; i < len; i++ ) { + sum = silk_SMLALBB( sum, inVec1[ i ], inVec2[ i ] ); + } + return sum; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/warped_autocorrelation_FIX.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/warped_autocorrelation_FIX.c new file mode 100644 index 0000000000..5c79553bc0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/warped_autocorrelation_FIX.c @@ -0,0 +1,92 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FIX.h" + +#if defined(MIPSr1_ASM) +#include "mips/warped_autocorrelation_FIX_mipsr1.h" +#endif + + +/* Autocorrelations for a warped frequency axis */ +#ifndef OVERRIDE_silk_warped_autocorrelation_FIX_c +void silk_warped_autocorrelation_FIX_c( + opus_int32 *corr, /* O Result [order + 1] */ + opus_int *scale, /* O Scaling of the correlation vector */ + const opus_int16 *input, /* I Input data to correlate */ + const opus_int warping_Q16, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) +{ + opus_int n, i, lsh; + opus_int32 tmp1_QS, tmp2_QS; + opus_int32 state_QS[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + opus_int64 corr_QC[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + + /* Order must be even */ + celt_assert( ( order & 1 ) == 0 ); + silk_assert( 2 * QS - QC >= 0 ); + + /* Loop over samples */ + for( n = 0; n < length; n++ ) { + tmp1_QS = silk_LSHIFT32( (opus_int32)input[ n ], QS ); + /* Loop over allpass sections */ + for( i = 0; i < order; i += 2 ) { + /* Output of allpass section */ + tmp2_QS = silk_SMLAWB( state_QS[ i ], state_QS[ i + 1 ] - tmp1_QS, warping_Q16 ); + state_QS[ i ] = tmp1_QS; + corr_QC[ i ] += silk_RSHIFT64( silk_SMULL( tmp1_QS, state_QS[ 0 ] ), 2 * QS - QC ); + /* Output of allpass section */ + tmp1_QS = silk_SMLAWB( state_QS[ i + 1 ], state_QS[ i + 2 ] - tmp2_QS, warping_Q16 ); + state_QS[ i + 1 ] = tmp2_QS; + corr_QC[ i + 1 ] += silk_RSHIFT64( silk_SMULL( tmp2_QS, state_QS[ 0 ] ), 2 * QS - QC ); + } + state_QS[ order ] = tmp1_QS; + corr_QC[ order ] += silk_RSHIFT64( silk_SMULL( tmp1_QS, state_QS[ 0 ] ), 2 * QS - QC ); + } + + lsh = silk_CLZ64( corr_QC[ 0 ] ) - 35; + lsh = silk_LIMIT( lsh, -12 - QC, 30 - QC ); + *scale = -( QC + lsh ); + silk_assert( *scale >= -30 && *scale <= 12 ); + if( lsh >= 0 ) { + for( i = 0; i < order + 1; i++ ) { + corr[ i ] = (opus_int32)silk_CHECK_FIT32( silk_LSHIFT64( corr_QC[ i ], lsh ) ); + } + } else { + for( i = 0; i < order + 1; i++ ) { + corr[ i ] = (opus_int32)silk_CHECK_FIT32( silk_RSHIFT64( corr_QC[ i ], -lsh ) ); + } + } + silk_assert( corr_QC[ 0 ] >= 0 ); /* If breaking, decrease QC*/ +} +#endif /* OVERRIDE_silk_warped_autocorrelation_FIX_c */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/burg_modified_FIX_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/burg_modified_FIX_sse4_1.c new file mode 100644 index 0000000000..e58bf079e5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/burg_modified_FIX_sse4_1.c @@ -0,0 +1,400 @@ +/* Copyright (c) 2014-2020, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang FrancisQuiers + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "SigProc_FIX.h" +#include "define.h" +#include "tuning_parameters.h" +#include "pitch.h" +#include "celt/x86/x86cpu.h" + +#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384 */ + +#define QA 25 +#define N_BITS_HEAD_ROOM 3 +#define MIN_RSHIFTS -16 +#define MAX_RSHIFTS (32 - QA) + +/* Compute reflection coefficients from input signal */ +void silk_burg_modified_sse4_1( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +) +{ + opus_int k, n, s, lz, rshifts, reached_max_gain; + opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2; + const opus_int16 *x_ptr; + opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 C_last_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 Af_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; + opus_int64 C0_64; + + __m128i FIRST_3210, LAST_3210, ATMP_3210, TMP1_3210, TMP2_3210, T1_3210, T2_3210, PTR_3210, SUBFR_3210, X1_3210, X2_3210; + __m128i CONST1 = _mm_set1_epi32(1); + + celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); + + /* Compute autocorrelations, added over subframes */ + C0_64 = silk_inner_prod16( x, x, subfr_length*nb_subfr, arch ); + lz = silk_CLZ64(C0_64); + rshifts = 32 + 1 + N_BITS_HEAD_ROOM - lz; + if (rshifts > MAX_RSHIFTS) rshifts = MAX_RSHIFTS; + if (rshifts < MIN_RSHIFTS) rshifts = MIN_RSHIFTS; + + if (rshifts > 0) { + C0 = (opus_int32)silk_RSHIFT64(C0_64, rshifts ); + } else { + C0 = silk_LSHIFT32((opus_int32)C0_64, -rshifts ); + } + + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += (opus_int32)silk_RSHIFT64( + silk_inner_prod16( x_ptr, x_ptr + n, subfr_length - n, arch ), rshifts ); + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + int i; + opus_int32 d; + x_ptr = x + s * subfr_length; + celt_pitch_xcorr(x_ptr, x_ptr + 1, xcorr, subfr_length - D, D, arch ); + for( n = 1; n < D + 1; n++ ) { + for ( i = n + subfr_length - D, d = 0; i < subfr_length; i++ ) + d = MAC16_16( d, x_ptr[ i ], x_ptr[ i - n ] ); + xcorr[ n - 1 ] += d; + } + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += silk_LSHIFT32( xcorr[ n - 1 ], -rshifts ); + } + } + } + silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + + /* Initialize */ + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + + invGain_Q30 = (opus_int32)1 << 30; + reached_max_gain = 0; + for( n = 0; n < D; n++ ) { + /* Update first row of correlation matrix (without first element) */ + /* Update last row of correlation matrix (without last element, stored in reversed order) */ + /* Update C * Af */ + /* Update C * flipud(Af) (stored in reversed order) */ + if( rshifts > -2 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], 16 - rshifts ); /* Q(16-rshifts) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 16 - rshifts ); /* Q(16-rshifts) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], QA - 16 ); /* Q(QA-16) */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], QA - 16 ); /* Q(QA-16) */ + for( k = 0; k < n; k++ ) { + C_first_row[ k ] = silk_SMLAWB( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_SMLAWB( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp_QA = Af_QA[ k ]; + tmp1 = silk_SMLAWB( tmp1, Atmp_QA, x_ptr[ n - k - 1 ] ); /* Q(QA-16) */ + tmp2 = silk_SMLAWB( tmp2, Atmp_QA, x_ptr[ subfr_length - n + k ] ); /* Q(QA-16) */ + } + tmp1 = silk_LSHIFT32( -tmp1, 32 - QA - rshifts ); /* Q(16-rshifts) */ + tmp2 = silk_LSHIFT32( -tmp2, 32 - QA - rshifts ); /* Q(16-rshifts) */ + for( k = 0; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWB( CAf[ k ], tmp1, x_ptr[ n - k ] ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWB( CAb[ k ], tmp2, x_ptr[ subfr_length - n + k - 1 ] ); /* Q( -rshift ) */ + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], -rshifts ); /* Q( -rshifts ) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], -rshifts ); /* Q( -rshifts ) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], 17 ); /* Q17 */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 17 ); /* Q17 */ + + X1_3210 = _mm_set1_epi32( x1 ); + X2_3210 = _mm_set1_epi32( x2 ); + TMP1_3210 = _mm_setzero_si128(); + TMP2_3210 = _mm_setzero_si128(); + for( k = 0; k < n - 3; k += 4 ) { + PTR_3210 = OP_CVTEPI16_EPI32_M64( &x_ptr[ n - k - 1 - 3 ] ); + SUBFR_3210 = OP_CVTEPI16_EPI32_M64( &x_ptr[ subfr_length - n + k ] ); + FIRST_3210 = _mm_loadu_si128( (__m128i *)&C_first_row[ k ] ); + PTR_3210 = _mm_shuffle_epi32( PTR_3210, _MM_SHUFFLE( 0, 1, 2, 3 ) ); + LAST_3210 = _mm_loadu_si128( (__m128i *)&C_last_row[ k ] ); + ATMP_3210 = _mm_loadu_si128( (__m128i *)&Af_QA[ k ] ); + + T1_3210 = _mm_mullo_epi32( PTR_3210, X1_3210 ); + T2_3210 = _mm_mullo_epi32( SUBFR_3210, X2_3210 ); + + ATMP_3210 = _mm_srai_epi32( ATMP_3210, 7 ); + ATMP_3210 = _mm_add_epi32( ATMP_3210, CONST1 ); + ATMP_3210 = _mm_srai_epi32( ATMP_3210, 1 ); + + FIRST_3210 = _mm_add_epi32( FIRST_3210, T1_3210 ); + LAST_3210 = _mm_add_epi32( LAST_3210, T2_3210 ); + + PTR_3210 = _mm_mullo_epi32( ATMP_3210, PTR_3210 ); + SUBFR_3210 = _mm_mullo_epi32( ATMP_3210, SUBFR_3210 ); + + _mm_storeu_si128( (__m128i *)&C_first_row[ k ], FIRST_3210 ); + _mm_storeu_si128( (__m128i *)&C_last_row[ k ], LAST_3210 ); + + TMP1_3210 = _mm_add_epi32( TMP1_3210, PTR_3210 ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, SUBFR_3210 ); + } + + TMP1_3210 = _mm_add_epi32( TMP1_3210, _mm_unpackhi_epi64(TMP1_3210, TMP1_3210 ) ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, _mm_unpackhi_epi64(TMP2_3210, TMP2_3210 ) ); + TMP1_3210 = _mm_add_epi32( TMP1_3210, _mm_shufflelo_epi16(TMP1_3210, 0x0E ) ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, _mm_shufflelo_epi16(TMP2_3210, 0x0E ) ); + + tmp1 += _mm_cvtsi128_si32( TMP1_3210 ); + tmp2 += _mm_cvtsi128_si32( TMP2_3210 ); + + for( ; k < n; k++ ) { + C_first_row[ k ] = silk_MLA( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_MLA( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 17 ); /* Q17 */ + /* We sometimes get overflows in the multiplications (even beyond +/- 2^32), + but they cancel each other and the real result seems to always fit in a 32-bit + signed integer. This was determined experimentally, not theoretically (unfortunately). */ + tmp1 = silk_MLA_ovflw( tmp1, x_ptr[ n - k - 1 ], Atmp1 ); /* Q17 */ + tmp2 = silk_MLA_ovflw( tmp2, x_ptr[ subfr_length - n + k ], Atmp1 ); /* Q17 */ + } + + tmp1 = -tmp1; /* Q17 */ + tmp2 = -tmp2; /* Q17 */ + + { + __m128i xmm_tmp1, xmm_tmp2; + __m128i xmm_x_ptr_n_k_x2x0, xmm_x_ptr_n_k_x3x1; + __m128i xmm_x_ptr_sub_x2x0, xmm_x_ptr_sub_x3x1; + + xmm_tmp1 = _mm_set1_epi32( tmp1 ); + xmm_tmp2 = _mm_set1_epi32( tmp2 ); + + for( k = 0; k <= n - 3; k += 4 ) { + xmm_x_ptr_n_k_x2x0 = OP_CVTEPI16_EPI32_M64( &x_ptr[ n - k - 3 ] ); + xmm_x_ptr_sub_x2x0 = OP_CVTEPI16_EPI32_M64( &x_ptr[ subfr_length - n + k - 1 ] ); + + xmm_x_ptr_n_k_x2x0 = _mm_shuffle_epi32( xmm_x_ptr_n_k_x2x0, _MM_SHUFFLE( 0, 1, 2, 3 ) ); + + xmm_x_ptr_n_k_x2x0 = _mm_slli_epi32( xmm_x_ptr_n_k_x2x0, -rshifts - 1 ); + xmm_x_ptr_sub_x2x0 = _mm_slli_epi32( xmm_x_ptr_sub_x2x0, -rshifts - 1 ); + + /* equal shift right 4 bytes, xmm_x_ptr_n_k_x3x1 = _mm_srli_si128(xmm_x_ptr_n_k_x2x0, 4)*/ + xmm_x_ptr_n_k_x3x1 = _mm_shuffle_epi32( xmm_x_ptr_n_k_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + xmm_x_ptr_sub_x3x1 = _mm_shuffle_epi32( xmm_x_ptr_sub_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x_ptr_n_k_x2x0 = _mm_mul_epi32( xmm_x_ptr_n_k_x2x0, xmm_tmp1 ); + xmm_x_ptr_n_k_x3x1 = _mm_mul_epi32( xmm_x_ptr_n_k_x3x1, xmm_tmp1 ); + xmm_x_ptr_sub_x2x0 = _mm_mul_epi32( xmm_x_ptr_sub_x2x0, xmm_tmp2 ); + xmm_x_ptr_sub_x3x1 = _mm_mul_epi32( xmm_x_ptr_sub_x3x1, xmm_tmp2 ); + + xmm_x_ptr_n_k_x2x0 = _mm_srli_epi64( xmm_x_ptr_n_k_x2x0, 16 ); + xmm_x_ptr_n_k_x3x1 = _mm_slli_epi64( xmm_x_ptr_n_k_x3x1, 16 ); + xmm_x_ptr_sub_x2x0 = _mm_srli_epi64( xmm_x_ptr_sub_x2x0, 16 ); + xmm_x_ptr_sub_x3x1 = _mm_slli_epi64( xmm_x_ptr_sub_x3x1, 16 ); + + xmm_x_ptr_n_k_x2x0 = _mm_blend_epi16( xmm_x_ptr_n_k_x2x0, xmm_x_ptr_n_k_x3x1, 0xCC ); + xmm_x_ptr_sub_x2x0 = _mm_blend_epi16( xmm_x_ptr_sub_x2x0, xmm_x_ptr_sub_x3x1, 0xCC ); + + X1_3210 = _mm_loadu_si128( (__m128i *)&CAf[ k ] ); + PTR_3210 = _mm_loadu_si128( (__m128i *)&CAb[ k ] ); + + X1_3210 = _mm_add_epi32( X1_3210, xmm_x_ptr_n_k_x2x0 ); + PTR_3210 = _mm_add_epi32( PTR_3210, xmm_x_ptr_sub_x2x0 ); + + _mm_storeu_si128( (__m128i *)&CAf[ k ], X1_3210 ); + _mm_storeu_si128( (__m128i *)&CAb[ k ], PTR_3210 ); + } + + for( ; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWW( CAf[ k ], tmp1, + silk_LSHIFT32( (opus_int32)x_ptr[ n - k ], -rshifts - 1 ) ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWW( CAb[ k ], tmp2, + silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n + k - 1 ], -rshifts - 1 ) ); /* Q( -rshift ) */ + } + } + } + } + + /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */ + tmp1 = C_first_row[ n ]; /* Q( -rshifts ) */ + tmp2 = C_last_row[ n ]; /* Q( -rshifts ) */ + num = 0; /* Q( -rshifts ) */ + nrg = silk_ADD32( CAb[ 0 ], CAf[ 0 ] ); /* Q( 1-rshifts ) */ + for( k = 0; k < n; k++ ) { + Atmp_QA = Af_QA[ k ]; + lz = silk_CLZ32( silk_abs( Atmp_QA ) ) - 1; + lz = silk_min( 32 - QA, lz ); + Atmp1 = silk_LSHIFT32( Atmp_QA, lz ); /* Q( QA + lz ) */ + + tmp1 = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( C_last_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + tmp2 = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( C_first_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + num = silk_ADD_LSHIFT32( num, silk_SMMUL( CAb[ n - k ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + nrg = silk_ADD_LSHIFT32( nrg, silk_SMMUL( silk_ADD32( CAb[ k + 1 ], CAf[ k + 1 ] ), + Atmp1 ), 32 - QA - lz ); /* Q( 1-rshifts ) */ + } + CAf[ n + 1 ] = tmp1; /* Q( -rshifts ) */ + CAb[ n + 1 ] = tmp2; /* Q( -rshifts ) */ + num = silk_ADD32( num, tmp2 ); /* Q( -rshifts ) */ + num = silk_LSHIFT32( -num, 1 ); /* Q( 1-rshifts ) */ + + /* Calculate the next order reflection (parcor) coefficient */ + if( silk_abs( num ) < nrg ) { + rc_Q31 = silk_DIV32_varQ( num, nrg, 31 ); + } else { + rc_Q31 = ( num > 0 ) ? silk_int32_MAX : silk_int32_MIN; + } + + /* Update inverse prediction gain */ + tmp1 = ( (opus_int32)1 << 30 ) - silk_SMMUL( rc_Q31, rc_Q31 ); + tmp1 = silk_LSHIFT( silk_SMMUL( invGain_Q30, tmp1 ), 2 ); + if( tmp1 <= minInvGain_Q30 ) { + /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ + tmp2 = ( (opus_int32)1 << 30 ) - silk_DIV32_varQ( minInvGain_Q30, invGain_Q30, 30 ); /* Q30 */ + rc_Q31 = silk_SQRT_APPROX( tmp2 ); /* Q15 */ + if( rc_Q31 > 0 ) { + /* Newton-Raphson iteration */ + rc_Q31 = silk_RSHIFT32( rc_Q31 + silk_DIV32( tmp2, rc_Q31 ), 1 ); /* Q15 */ + rc_Q31 = silk_LSHIFT32( rc_Q31, 16 ); /* Q31 */ + if( num < 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc_Q31 = -rc_Q31; + } + } + invGain_Q30 = minInvGain_Q30; + reached_max_gain = 1; + } else { + invGain_Q30 = tmp1; + } + + /* Update the AR coefficients */ + for( k = 0; k < (n + 1) >> 1; k++ ) { + tmp1 = Af_QA[ k ]; /* QA */ + tmp2 = Af_QA[ n - k - 1 ]; /* QA */ + Af_QA[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* QA */ + Af_QA[ n - k - 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* QA */ + } + Af_QA[ n ] = silk_RSHIFT32( rc_Q31, 31 - QA ); /* QA */ + + if( reached_max_gain ) { + /* Reached max prediction gain; set remaining coefficients to zero and exit loop */ + for( k = n + 1; k < D; k++ ) { + Af_QA[ k ] = 0; + } + break; + } + + /* Update C * Af and C * Ab */ + for( k = 0; k <= n + 1; k++ ) { + tmp1 = CAf[ k ]; /* Q( -rshifts ) */ + tmp2 = CAb[ n - k + 1 ]; /* Q( -rshifts ) */ + CAf[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + CAb[ n - k + 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + } + } + + if( reached_max_gain ) { + for( k = 0; k < D; k++ ) { + /* Scale coefficients */ + A_Q16[ k ] = -silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); + } + /* Subtract energy of preceding samples from C0 */ + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= (opus_int32)silk_RSHIFT64( silk_inner_prod16( x_ptr, x_ptr, D, arch ), rshifts ); + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= silk_LSHIFT32( silk_inner_prod_aligned( x_ptr, x_ptr, D, arch ), -rshifts ); + } + } + /* Approximate residual energy */ + *res_nrg = silk_LSHIFT( silk_SMMUL( invGain_Q30, C0 ), 2 ); + *res_nrg_Q = -rshifts; + } else { + /* Return residual energy */ + nrg = CAf[ 0 ]; /* Q( -rshifts ) */ + tmp1 = (opus_int32)1 << 16; /* Q16 */ + for( k = 0; k < D; k++ ) { + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); /* Q16 */ + nrg = silk_SMLAWW( nrg, CAf[ k + 1 ], Atmp1 ); /* Q( -rshifts ) */ + tmp1 = silk_SMLAWW( tmp1, Atmp1, Atmp1 ); /* Q16 */ + A_Q16[ k ] = -Atmp1; + } + *res_nrg = silk_SMLAWW( nrg, silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ), -tmp1 );/* Q( -rshifts ) */ + *res_nrg_Q = -rshifts; + } + +#ifdef OPUS_CHECK_ASM + { + opus_int32 res_nrg_c = 0; + opus_int res_nrg_Q_c = 0; + opus_int32 A_Q16_c[ MAX_LPC_ORDER ] = {0}; + + silk_burg_modified_c( + &res_nrg_c, + &res_nrg_Q_c, + A_Q16_c, + x, + minInvGain_Q30, + subfr_length, + nb_subfr, + D, + 0 + ); + + silk_assert( *res_nrg == res_nrg_c ); + silk_assert( *res_nrg_Q == res_nrg_Q_c ); + silk_assert( !memcmp( A_Q16, A_Q16_c, D * sizeof( *A_Q16 ) ) ); + } +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/vector_ops_FIX_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/vector_ops_FIX_sse4_1.c new file mode 100644 index 0000000000..a46289bbc4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/fixed/x86/vector_ops_FIX_sse4_1.c @@ -0,0 +1,93 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" + +#include "SigProc_FIX.h" +#include "pitch.h" +#include "celt/x86/x86cpu.h" + +opus_int64 silk_inner_prod16_sse4_1( + const opus_int16 *inVec1, /* I input vector 1 */ + const opus_int16 *inVec2, /* I input vector 2 */ + const opus_int len /* I vector lengths */ +) +{ + opus_int i, dataSize4; + opus_int64 sum; + + __m128i xmm_prod_20, xmm_prod_31; + __m128i inVec1_3210, acc1; + __m128i inVec2_3210, acc2; + + sum = 0; + dataSize4 = len & ~3; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for( i = 0; i < dataSize4; i += 4 ) { + inVec1_3210 = OP_CVTEPI16_EPI32_M64( &inVec1[i + 0] ); + inVec2_3210 = OP_CVTEPI16_EPI32_M64( &inVec2[i + 0] ); + xmm_prod_20 = _mm_mul_epi32( inVec1_3210, inVec2_3210 ); + + inVec1_3210 = _mm_shuffle_epi32( inVec1_3210, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + inVec2_3210 = _mm_shuffle_epi32( inVec2_3210, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + xmm_prod_31 = _mm_mul_epi32( inVec1_3210, inVec2_3210 ); + + acc1 = _mm_add_epi64( acc1, xmm_prod_20 ); + acc2 = _mm_add_epi64( acc2, xmm_prod_31 ); + } + + acc1 = _mm_add_epi64( acc1, acc2 ); + + /* equal shift right 8 bytes */ + acc2 = _mm_shuffle_epi32( acc1, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + acc1 = _mm_add_epi64( acc1, acc2 ); + + _mm_storel_epi64( (__m128i *)&sum, acc1 ); + + for( ; i < len; i++ ) { + sum = silk_SMLALBB( sum, inVec1[ i ], inVec2[ i ] ); + } + +#ifdef OPUS_CHECK_ASM + { + opus_int64 sum_c = silk_inner_prod16_c( inVec1, inVec2, len ); + silk_assert( sum == sum_c ); + } +#endif + + return sum; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_analysis_filter_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_analysis_filter_FLP.c new file mode 100644 index 0000000000..0e1a1fed0f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_analysis_filter_FLP.c @@ -0,0 +1,249 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" + +/************************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first Order output samples are set to zero */ +/************************************************/ + +/* 16th order LPC analysis filter, does not write first 16 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter16_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 16; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ] + + s_ptr[ -10 ] * PredCoef[ 10 ] + + s_ptr[ -11 ] * PredCoef[ 11 ] + + s_ptr[ -12 ] * PredCoef[ 12 ] + + s_ptr[ -13 ] * PredCoef[ 13 ] + + s_ptr[ -14 ] * PredCoef[ 14 ] + + s_ptr[ -15 ] * PredCoef[ 15 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 12th order LPC analysis filter, does not write first 12 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter12_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 12; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ] + + s_ptr[ -10 ] * PredCoef[ 10 ] + + s_ptr[ -11 ] * PredCoef[ 11 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 10th order LPC analysis filter, does not write first 10 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter10_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 10; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ] + + s_ptr[ -8 ] * PredCoef[ 8 ] + + s_ptr[ -9 ] * PredCoef[ 9 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 8th order LPC analysis filter, does not write first 8 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter8_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 8; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ] + + s_ptr[ -6 ] * PredCoef[ 6 ] + + s_ptr[ -7 ] * PredCoef[ 7 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/* 6th order LPC analysis filter, does not write first 6 samples */ +static OPUS_INLINE void silk_LPC_analysis_filter6_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length /* I Length of input signal */ +) +{ + opus_int ix; + silk_float LPC_pred; + const silk_float *s_ptr; + + for( ix = 6; ix < length; ix++ ) { + s_ptr = &s[ix - 1]; + + /* short-term prediction */ + LPC_pred = s_ptr[ 0 ] * PredCoef[ 0 ] + + s_ptr[ -1 ] * PredCoef[ 1 ] + + s_ptr[ -2 ] * PredCoef[ 2 ] + + s_ptr[ -3 ] * PredCoef[ 3 ] + + s_ptr[ -4 ] * PredCoef[ 4 ] + + s_ptr[ -5 ] * PredCoef[ 5 ]; + + /* prediction error */ + r_LPC[ix] = s_ptr[ 1 ] - LPC_pred; + } +} + +/************************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first Order output samples are set to zero */ +/************************************************/ +void silk_LPC_analysis_filter_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length, /* I Length of input signal */ + const opus_int Order /* I LPC order */ +) +{ + celt_assert( Order <= length ); + + switch( Order ) { + case 6: + silk_LPC_analysis_filter6_FLP( r_LPC, PredCoef, s, length ); + break; + + case 8: + silk_LPC_analysis_filter8_FLP( r_LPC, PredCoef, s, length ); + break; + + case 10: + silk_LPC_analysis_filter10_FLP( r_LPC, PredCoef, s, length ); + break; + + case 12: + silk_LPC_analysis_filter12_FLP( r_LPC, PredCoef, s, length ); + break; + + case 16: + silk_LPC_analysis_filter16_FLP( r_LPC, PredCoef, s, length ); + break; + + default: + celt_assert( 0 ); + break; + } + + /* Set first Order output samples to zero */ + silk_memset( r_LPC, 0, Order * sizeof( silk_float ) ); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_inv_pred_gain_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_inv_pred_gain_FLP.c new file mode 100644 index 0000000000..2be2122d61 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LPC_inv_pred_gain_FLP.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "SigProc_FLP.h" +#include "define.h" + +/* compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +/* this code is based on silk_a2k_FLP() */ +silk_float silk_LPC_inverse_pred_gain_FLP( /* O return inverse prediction gain, energy domain */ + const silk_float *A, /* I prediction coefficients [order] */ + opus_int32 order /* I prediction order */ +) +{ + opus_int k, n; + double invGain, rc, rc_mult1, rc_mult2, tmp1, tmp2; + silk_float Atmp[ SILK_MAX_ORDER_LPC ]; + + silk_memcpy( Atmp, A, order * sizeof(silk_float) ); + + invGain = 1.0; + for( k = order - 1; k > 0; k-- ) { + rc = -Atmp[ k ]; + rc_mult1 = 1.0f - rc * rc; + invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { + return 0.0f; + } + rc_mult2 = 1.0f / rc_mult1; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = Atmp[ n ]; + tmp2 = Atmp[ k - n - 1 ]; + Atmp[ n ] = (silk_float)( ( tmp1 - tmp2 * rc ) * rc_mult2 ); + Atmp[ k - n - 1 ] = (silk_float)( ( tmp2 - tmp1 * rc ) * rc_mult2 ); + } + } + rc = -Atmp[ 0 ]; + rc_mult1 = 1.0f - rc * rc; + invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { + return 0.0f; + } + return (silk_float)invGain; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_analysis_filter_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_analysis_filter_FLP.c new file mode 100644 index 0000000000..849b7c1c52 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_analysis_filter_FLP.c @@ -0,0 +1,75 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +void silk_LTP_analysis_filter_FLP( + silk_float *LTP_res, /* O LTP res MAX_NB_SUBFR*(pre_lgth+subfr_lngth) */ + const silk_float *x, /* I Input signal, with preceding samples */ + const silk_float B[ LTP_ORDER * MAX_NB_SUBFR ], /* I LTP coefficients for each subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const silk_float invGains[ MAX_NB_SUBFR ], /* I Inverse quantization gains */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int pre_length /* I Preceding samples for each subframe */ +) +{ + const silk_float *x_ptr, *x_lag_ptr; + silk_float Btmp[ LTP_ORDER ]; + silk_float *LTP_res_ptr; + silk_float inv_gain; + opus_int k, i, j; + + x_ptr = x; + LTP_res_ptr = LTP_res; + for( k = 0; k < nb_subfr; k++ ) { + x_lag_ptr = x_ptr - pitchL[ k ]; + inv_gain = invGains[ k ]; + for( i = 0; i < LTP_ORDER; i++ ) { + Btmp[ i ] = B[ k * LTP_ORDER + i ]; + } + + /* LTP analysis FIR filter */ + for( i = 0; i < subfr_length + pre_length; i++ ) { + LTP_res_ptr[ i ] = x_ptr[ i ]; + /* Subtract long-term prediction */ + for( j = 0; j < LTP_ORDER; j++ ) { + LTP_res_ptr[ i ] -= Btmp[ j ] * x_lag_ptr[ LTP_ORDER / 2 - j ]; + } + LTP_res_ptr[ i ] *= inv_gain; + x_lag_ptr++; + } + + /* Update pointers */ + LTP_res_ptr += subfr_length + pre_length; + x_ptr += subfr_length; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_scale_ctrl_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_scale_ctrl_FLP.c new file mode 100644 index 0000000000..6f30ff095f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/LTP_scale_ctrl_FLP.c @@ -0,0 +1,58 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +void silk_LTP_scale_ctrl_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int round_loss; + + if( condCoding == CODE_INDEPENDENTLY ) { + /* Only scale if first frame in packet */ + round_loss = psEnc->sCmn.PacketLoss_perc * psEnc->sCmn.nFramesPerPacket; + if ( psEnc->sCmn.LBRR_flag ) { + /* LBRR reduces the effective loss. In practice, it does not square the loss because + losses aren't independent, but that still seems to work best. We also never go below 2%. */ + round_loss = 2 + silk_SMULBB( round_loss, round_loss) / 100; + } + psEnc->sCmn.indices.LTP_scaleIndex = silk_SMULBB( psEncCtrl->LTPredCodGain, round_loss ) > silk_log2lin( 2900 - psEnc->sCmn.SNR_dB_Q7 ); + psEnc->sCmn.indices.LTP_scaleIndex += silk_SMULBB( psEncCtrl->LTPredCodGain, round_loss ) > silk_log2lin( 3900 - psEnc->sCmn.SNR_dB_Q7 ); + } else { + /* Default is minimum scaling */ + psEnc->sCmn.indices.LTP_scaleIndex = 0; + } + + psEncCtrl->LTP_scale = (silk_float)silk_LTPScales_table_Q14[ psEnc->sCmn.indices.LTP_scaleIndex ] / 16384.0f; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/SigProc_FLP.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/SigProc_FLP.h new file mode 100644 index 0000000000..ff9281b852 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/SigProc_FLP.h @@ -0,0 +1,205 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FLP_H +#define SILK_SIGPROC_FLP_H + +#include "SigProc_FIX.h" +#include "float_cast.h" +#include "main.h" +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/********************************************************************/ +/* SIGNAL PROCESSING FUNCTIONS */ +/********************************************************************/ + +/* Chirp (bw expand) LP AR filter */ +void silk_bwexpander_FLP( + silk_float *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I length of ar */ + const silk_float chirp /* I chirp factor (typically in range (0..1) ) */ +); + +/* compute inverse of LPC prediction gain, and */ +/* test if LPC coefficients are stable (all poles within unit circle) */ +/* this code is based on silk_FLP_a2k() */ +silk_float silk_LPC_inverse_pred_gain_FLP( /* O return inverse prediction gain, energy domain */ + const silk_float *A, /* I prediction coefficients [order] */ + opus_int32 order /* I prediction order */ +); + +silk_float silk_schur_FLP( /* O returns residual energy */ + silk_float refl_coef[], /* O reflection coefficients (length order) */ + const silk_float auto_corr[], /* I autocorrelation sequence (length order+1) */ + opus_int order /* I order */ +); + +void silk_k2a_FLP( + silk_float *A, /* O prediction coefficients [order] */ + const silk_float *rc, /* I reflection coefficients [order] */ + opus_int32 order /* I prediction order */ +); + +/* compute autocorrelation */ +void silk_autocorrelation_FLP( + silk_float *results, /* O result (length correlationCount) */ + const silk_float *inputData, /* I input data to correlate */ + opus_int inputDataSize, /* I length of input */ + opus_int correlationCount, /* I number of correlation taps to compute */ + int arch +); + +opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const silk_float *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O Pitch lag values [nb_subfr] */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + silk_float *LTPCorr, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const silk_float search_thres1, /* I First stage threshold for lag candidates 0 - 1 */ + const silk_float search_thres2, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I Number of 5 ms subframes */ + int arch /* I Run-time architecture */ +); + +void silk_insertion_sort_decreasing_FLP( + silk_float *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +); + +/* Compute reflection coefficients from input signal */ +silk_float silk_burg_modified_FLP( /* O returns residual energy */ + silk_float A[], /* O prediction coefficients (length order) */ + const silk_float x[], /* I input signal, length: nb_subfr*(D+L_sub) */ + const silk_float minInvGain, /* I minimum inverse prediction gain */ + const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I number of subframes stacked in x */ + const opus_int D, /* I order */ + int arch +); + +/* multiply a vector by a constant */ +void silk_scale_vector_FLP( + silk_float *data1, + silk_float gain, + opus_int dataSize +); + +/* copy and multiply a vector by a constant */ +void silk_scale_copy_vector_FLP( + silk_float *data_out, + const silk_float *data_in, + silk_float gain, + opus_int dataSize +); + +/* inner product of two silk_float arrays, with result as double */ +double silk_inner_product_FLP_c( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +); + +#ifndef OVERRIDE_inner_product_FLP +#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,silk_inner_product_FLP_c(data1, data2, dataSize)) +#endif + + +/* sum of squares of a silk_float array, with result as double */ +double silk_energy_FLP( + const silk_float *data, + opus_int dataSize +); + +/********************************************************************/ +/* MACROS */ +/********************************************************************/ + +#define PI (3.1415926536f) + +#define silk_min_float( a, b ) (((a) < (b)) ? (a) : (b)) +#define silk_max_float( a, b ) (((a) > (b)) ? (a) : (b)) +#define silk_abs_float( a ) ((silk_float)fabs(a)) + +/* sigmoid function */ +static OPUS_INLINE silk_float silk_sigmoid( silk_float x ) +{ + return (silk_float)(1.0 / (1.0 + exp(-x))); +} + +/* floating-point to integer conversion (rounding) */ +static OPUS_INLINE opus_int32 silk_float2int( silk_float x ) +{ + return (opus_int32)float2int( x ); +} + +/* floating-point to integer conversion (rounding) */ +static OPUS_INLINE void silk_float2short_array( + opus_int16 *out, + const silk_float *in, + opus_int32 length +) +{ + opus_int32 k; + for( k = length - 1; k >= 0; k-- ) { + out[k] = silk_SAT16( (opus_int32)float2int( in[k] ) ); + } +} + +/* integer to floating-point conversion */ +static OPUS_INLINE void silk_short2float_array( + silk_float *out, + const opus_int16 *in, + opus_int32 length +) +{ + opus_int32 k; + for( k = length - 1; k >= 0; k-- ) { + out[k] = (silk_float)in[k]; + } +} + +/* using log2() helps the fixed-point conversion */ +static OPUS_INLINE silk_float silk_log2( double x ) +{ + return ( silk_float )( 3.32192809488736 * log10( x ) ); +} + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_SIGPROC_FLP_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/apply_sine_window_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/apply_sine_window_FLP.c new file mode 100644 index 0000000000..e49e717991 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/apply_sine_window_FLP.c @@ -0,0 +1,81 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Apply sine window to signal vector */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +void silk_apply_sine_window_FLP( + silk_float px_win[], /* O Pointer to windowed signal */ + const silk_float px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +) +{ + opus_int k; + silk_float freq, c, S0, S1; + + celt_assert( win_type == 1 || win_type == 2 ); + + /* Length must be multiple of 4 */ + celt_assert( ( length & 3 ) == 0 ); + + freq = PI / ( length + 1 ); + + /* Approximation of 2 * cos(f) */ + c = 2.0f - freq * freq; + + /* Initialize state */ + if( win_type < 2 ) { + /* Start from 0 */ + S0 = 0.0f; + /* Approximation of sin(f) */ + S1 = freq; + } else { + /* Start from 1 */ + S0 = 1.0f; + /* Approximation of cos(f) */ + S1 = 0.5f * c; + } + + /* Uses the recursive equation: sin(n*f) = 2 * cos(f) * sin((n-1)*f) - sin((n-2)*f) */ + /* 4 samples at a time */ + for( k = 0; k < length; k += 4 ) { + px_win[ k + 0 ] = px[ k + 0 ] * 0.5f * ( S0 + S1 ); + px_win[ k + 1 ] = px[ k + 1 ] * S1; + S0 = c * S1 - S0; + px_win[ k + 2 ] = px[ k + 2 ] * 0.5f * ( S1 + S0 ); + px_win[ k + 3 ] = px[ k + 3 ] * S0; + S1 = c * S0 - S1; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/autocorrelation_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/autocorrelation_FLP.c new file mode 100644 index 0000000000..4253b26ebc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/autocorrelation_FLP.c @@ -0,0 +1,53 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "typedef.h" +#include "SigProc_FLP.h" + +/* compute autocorrelation */ +void silk_autocorrelation_FLP( + silk_float *results, /* O result (length correlationCount) */ + const silk_float *inputData, /* I input data to correlate */ + opus_int inputDataSize, /* I length of input */ + opus_int correlationCount, /* I number of correlation taps to compute */ + int arch +) +{ + opus_int i; + + if( correlationCount > inputDataSize ) { + correlationCount = inputDataSize; + } + + for( i = 0; i < correlationCount; i++ ) { + results[ i ] = (silk_float)silk_inner_product_FLP( inputData, inputData + i, inputDataSize - i, arch ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/burg_modified_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/burg_modified_FLP.c new file mode 100644 index 0000000000..f5bef5ddbe --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/burg_modified_FLP.c @@ -0,0 +1,187 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" +#include "tuning_parameters.h" +#include "define.h" + +#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384*/ + +/* Compute reflection coefficients from input signal */ +silk_float silk_burg_modified_FLP( /* O returns residual energy */ + silk_float A[], /* O prediction coefficients (length order) */ + const silk_float x[], /* I input signal, length: nb_subfr*(D+L_sub) */ + const silk_float minInvGain, /* I minimum inverse prediction gain */ + const opus_int subfr_length, /* I input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I number of subframes stacked in x */ + const opus_int D, /* I order */ + int arch +) +{ + opus_int k, n, s, reached_max_gain; + double C0, invGain, num, nrg_f, nrg_b, rc, Atmp, tmp1, tmp2; + const silk_float *x_ptr; + double C_first_row[ SILK_MAX_ORDER_LPC ], C_last_row[ SILK_MAX_ORDER_LPC ]; + double CAf[ SILK_MAX_ORDER_LPC + 1 ], CAb[ SILK_MAX_ORDER_LPC + 1 ]; + double Af[ SILK_MAX_ORDER_LPC ]; + + celt_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); + + /* Compute autocorrelations, added over subframes */ + C0 = silk_energy_FLP( x, nb_subfr * subfr_length ); + silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( double ) ); + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += silk_inner_product_FLP( x_ptr, x_ptr + n, subfr_length - n, arch ); + } + } + silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( double ) ); + + /* Initialize */ + CAb[ 0 ] = CAf[ 0 ] = C0 + FIND_LPC_COND_FAC * C0 + 1e-9f; + invGain = 1.0f; + reached_max_gain = 0; + for( n = 0; n < D; n++ ) { + /* Update first row of correlation matrix (without first element) */ + /* Update last row of correlation matrix (without last element, stored in reversed order) */ + /* Update C * Af */ + /* Update C * flipud(Af) (stored in reversed order) */ + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + tmp1 = x_ptr[ n ]; + tmp2 = x_ptr[ subfr_length - n - 1 ]; + for( k = 0; k < n; k++ ) { + C_first_row[ k ] -= x_ptr[ n ] * x_ptr[ n - k - 1 ]; + C_last_row[ k ] -= x_ptr[ subfr_length - n - 1 ] * x_ptr[ subfr_length - n + k ]; + Atmp = Af[ k ]; + tmp1 += x_ptr[ n - k - 1 ] * Atmp; + tmp2 += x_ptr[ subfr_length - n + k ] * Atmp; + } + for( k = 0; k <= n; k++ ) { + CAf[ k ] -= tmp1 * x_ptr[ n - k ]; + CAb[ k ] -= tmp2 * x_ptr[ subfr_length - n + k - 1 ]; + } + } + tmp1 = C_first_row[ n ]; + tmp2 = C_last_row[ n ]; + for( k = 0; k < n; k++ ) { + Atmp = Af[ k ]; + tmp1 += C_last_row[ n - k - 1 ] * Atmp; + tmp2 += C_first_row[ n - k - 1 ] * Atmp; + } + CAf[ n + 1 ] = tmp1; + CAb[ n + 1 ] = tmp2; + + /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */ + num = CAb[ n + 1 ]; + nrg_b = CAb[ 0 ]; + nrg_f = CAf[ 0 ]; + for( k = 0; k < n; k++ ) { + Atmp = Af[ k ]; + num += CAb[ n - k ] * Atmp; + nrg_b += CAb[ k + 1 ] * Atmp; + nrg_f += CAf[ k + 1 ] * Atmp; + } + silk_assert( nrg_f > 0.0 ); + silk_assert( nrg_b > 0.0 ); + + /* Calculate the next order reflection (parcor) coefficient */ + rc = -2.0 * num / ( nrg_f + nrg_b ); + silk_assert( rc > -1.0 && rc < 1.0 ); + + /* Update inverse prediction gain */ + tmp1 = invGain * ( 1.0 - rc * rc ); + if( tmp1 <= minInvGain ) { + /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ + rc = sqrt( 1.0 - minInvGain / invGain ); + if( num > 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc = -rc; + } + invGain = minInvGain; + reached_max_gain = 1; + } else { + invGain = tmp1; + } + + /* Update the AR coefficients */ + for( k = 0; k < (n + 1) >> 1; k++ ) { + tmp1 = Af[ k ]; + tmp2 = Af[ n - k - 1 ]; + Af[ k ] = tmp1 + rc * tmp2; + Af[ n - k - 1 ] = tmp2 + rc * tmp1; + } + Af[ n ] = rc; + + if( reached_max_gain ) { + /* Reached max prediction gain; set remaining coefficients to zero and exit loop */ + for( k = n + 1; k < D; k++ ) { + Af[ k ] = 0.0; + } + break; + } + + /* Update C * Af and C * Ab */ + for( k = 0; k <= n + 1; k++ ) { + tmp1 = CAf[ k ]; + CAf[ k ] += rc * CAb[ n - k + 1 ]; + CAb[ n - k + 1 ] += rc * tmp1; + } + } + + if( reached_max_gain ) { + /* Convert to silk_float */ + for( k = 0; k < D; k++ ) { + A[ k ] = (silk_float)( -Af[ k ] ); + } + /* Subtract energy of preceding samples from C0 */ + for( s = 0; s < nb_subfr; s++ ) { + C0 -= silk_energy_FLP( x + s * subfr_length, D ); + } + /* Approximate residual energy */ + nrg_f = C0 * invGain; + } else { + /* Compute residual energy and store coefficients as silk_float */ + nrg_f = CAf[ 0 ]; + tmp1 = 1.0; + for( k = 0; k < D; k++ ) { + Atmp = Af[ k ]; + nrg_f += CAf[ k + 1 ] * Atmp; + tmp1 += Atmp * Atmp; + A[ k ] = (silk_float)(-Atmp); + } + nrg_f -= FIND_LPC_COND_FAC * C0 * tmp1; + } + + /* Return residual energy */ + return (silk_float)nrg_f; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/bwexpander_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/bwexpander_FLP.c new file mode 100644 index 0000000000..d55a4d79ab --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/bwexpander_FLP.c @@ -0,0 +1,49 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* Chirp (bw expand) LP AR filter */ +void silk_bwexpander_FLP( + silk_float *ar, /* I/O AR filter to be expanded (without leading 1) */ + const opus_int d, /* I length of ar */ + const silk_float chirp /* I chirp factor (typically in range (0..1) ) */ +) +{ + opus_int i; + silk_float cfac = chirp; + + for( i = 0; i < d - 1; i++ ) { + ar[ i ] *= cfac; + cfac *= chirp; + } + ar[ d - 1 ] *= cfac; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/corrMatrix_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/corrMatrix_FLP.c new file mode 100644 index 0000000000..eef6e8aa79 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/corrMatrix_FLP.c @@ -0,0 +1,95 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/********************************************************************** + * Correlation matrix computations for LS estimate. + **********************************************************************/ + +#include "main_FLP.h" + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FLP( + const silk_float *x, /* I x vector [L+order-1] used to create X */ + const silk_float *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vecors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *Xt, /* O X'*t correlation vector [order] */ + int arch +) +{ + opus_int lag; + const silk_float *ptr1; + + ptr1 = &x[ Order - 1 ]; /* Points to first sample of column 0 of X: X[:,0] */ + for( lag = 0; lag < Order; lag++ ) { + /* Calculate X[:,lag]'*t */ + Xt[ lag ] = (silk_float)silk_inner_product_FLP( ptr1, t, L, arch ); + ptr1--; /* Next column of X */ + } +} + +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FLP( + const silk_float *x, /* I x vector [ L+order-1 ] used to create X */ + const opus_int L, /* I Length of vectors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *XX, /* O X'*X correlation matrix [order x order] */ + int arch +) +{ + opus_int j, lag; + double energy; + const silk_float *ptr1, *ptr2; + + ptr1 = &x[ Order - 1 ]; /* First sample of column 0 of X */ + energy = silk_energy_FLP( ptr1, L ); /* X[:,0]'*X[:,0] */ + matrix_ptr( XX, 0, 0, Order ) = ( silk_float )energy; + for( j = 1; j < Order; j++ ) { + /* Calculate X[:,j]'*X[:,j] */ + energy += ptr1[ -j ] * ptr1[ -j ] - ptr1[ L - j ] * ptr1[ L - j ]; + matrix_ptr( XX, j, j, Order ) = ( silk_float )energy; + } + + ptr2 = &x[ Order - 2 ]; /* First sample of column 1 of X */ + for( lag = 1; lag < Order; lag++ ) { + /* Calculate X[:,0]'*X[:,lag] */ + energy = silk_inner_product_FLP( ptr1, ptr2, L, arch ); + matrix_ptr( XX, lag, 0, Order ) = ( silk_float )energy; + matrix_ptr( XX, 0, lag, Order ) = ( silk_float )energy; + /* Calculate X[:,j]'*X[:,j + lag] */ + for( j = 1; j < ( Order - lag ); j++ ) { + energy += ptr1[ -j ] * ptr2[ -j ] - ptr1[ L - j ] * ptr2[ L - j ]; + matrix_ptr( XX, lag + j, j, Order ) = ( silk_float )energy; + matrix_ptr( XX, j, lag + j, Order ) = ( silk_float )energy; + } + ptr2--; /* Next column of X */ + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/encode_frame_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/encode_frame_FLP.c new file mode 100644 index 0000000000..8a327c5626 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/encode_frame_FLP.c @@ -0,0 +1,432 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Low Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode with lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float xfw[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +); + +void silk_encode_do_VAD_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int activity /* I Decision of Opus voice activity detector */ +) +{ + const opus_int activity_threshold = SILK_FIX_CONST( SPEECH_ACTIVITY_DTX_THRES, 8 ); + + /****************************/ + /* Voice Activity Detection */ + /****************************/ + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.arch ); + /* If Opus VAD is inactive and Silk VAD is active: lower Silk VAD to just under the threshold */ + if( activity == VAD_NO_ACTIVITY && psEnc->sCmn.speech_activity_Q8 >= activity_threshold ) { + psEnc->sCmn.speech_activity_Q8 = activity_threshold - 1; + } + + /**************************************************/ + /* Convert speech activity into VAD and DTX flags */ + /**************************************************/ + if( psEnc->sCmn.speech_activity_Q8 < activity_threshold ) { + psEnc->sCmn.indices.signalType = TYPE_NO_VOICE_ACTIVITY; + psEnc->sCmn.noSpeechCounter++; + if( psEnc->sCmn.noSpeechCounter <= NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.inDTX = 0; + } else if( psEnc->sCmn.noSpeechCounter > MAX_CONSECUTIVE_DTX + NB_SPEECH_FRAMES_BEFORE_DTX ) { + psEnc->sCmn.noSpeechCounter = NB_SPEECH_FRAMES_BEFORE_DTX; + psEnc->sCmn.inDTX = 0; + } + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 0; + } else { + psEnc->sCmn.noSpeechCounter = 0; + psEnc->sCmn.inDTX = 0; + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + psEnc->sCmn.VAD_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + } +} + +/****************/ +/* Encode frame */ +/****************/ +opus_int silk_encode_frame_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int32 *pnBytesOut, /* O Number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +) +{ + silk_encoder_control_FLP sEncCtrl; + opus_int i, iter, maxIter, found_upper, found_lower, ret = 0; + silk_float *x_frame, *res_pitch_frame; + silk_float res_pitch[ 2 * MAX_FRAME_LENGTH + LA_PITCH_MAX ]; + ec_enc sRangeEnc_copy, sRangeEnc_copy2; + silk_nsq_state sNSQ_copy, sNSQ_copy2; + opus_int32 seed_copy, nBits, nBits_lower, nBits_upper, gainMult_lower, gainMult_upper; + opus_int32 gainsID, gainsID_lower, gainsID_upper; + opus_int16 gainMult_Q8; + opus_int16 ec_prevLagIndex_copy; + opus_int ec_prevSignalType_copy; + opus_int8 LastGainIndex_copy2; + opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; + opus_uint8 ec_buf_copy[ 1275 ]; + opus_int gain_lock[ MAX_NB_SUBFR ] = {0}; + opus_int16 best_gain_mult[ MAX_NB_SUBFR ]; + opus_int best_sum[ MAX_NB_SUBFR ]; + opus_int bits_margin; + + /* For CBR, 5 bits below budget is close enough. For VBR, allow up to 25% below the cap if we initially busted the budget. */ + bits_margin = useCBR ? 5 : maxBits/4; + /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ + LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0; + + psEnc->sCmn.indices.Seed = psEnc->sCmn.frameCounter++ & 3; + + /**************************************************************/ + /* Set up Input Pointers, and insert frame in input buffer */ + /**************************************************************/ + /* pointers aligned with start of frame to encode */ + x_frame = psEnc->x_buf + psEnc->sCmn.ltp_mem_length; /* start of frame to encode */ + res_pitch_frame = res_pitch + psEnc->sCmn.ltp_mem_length; /* start of pitch LPC residual frame */ + + /***************************************/ + /* Ensure smooth bandwidth transitions */ + /***************************************/ + silk_LP_variable_cutoff( &psEnc->sCmn.sLP, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); + + /*******************************************/ + /* Copy new frame to front of input buffer */ + /*******************************************/ + silk_short2float_array( x_frame + LA_SHAPE_MS * psEnc->sCmn.fs_kHz, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length ); + + /* Add tiny signal to avoid high CPU load from denormalized floating point numbers */ + for( i = 0; i < 8; i++ ) { + x_frame[ LA_SHAPE_MS * psEnc->sCmn.fs_kHz + i * ( psEnc->sCmn.frame_length >> 3 ) ] += ( 1 - ( i & 2 ) ) * 1e-6f; + } + + if( !psEnc->sCmn.prefillFlag ) { + /*****************************************/ + /* Find pitch lags, initial LPC analysis */ + /*****************************************/ + silk_find_pitch_lags_FLP( psEnc, &sEncCtrl, res_pitch, x_frame, psEnc->sCmn.arch ); + + /************************/ + /* Noise shape analysis */ + /************************/ + silk_noise_shape_analysis_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame ); + + /***************************************************/ + /* Find linear prediction coefficients (LPC + LTP) */ + /***************************************************/ + silk_find_pred_coefs_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame, condCoding ); + + /****************************************/ + /* Process gains */ + /****************************************/ + silk_process_gains_FLP( psEnc, &sEncCtrl, condCoding ); + + /****************************************/ + /* Low Bitrate Redundant Encoding */ + /****************************************/ + silk_LBRR_encode_FLP( psEnc, &sEncCtrl, x_frame, condCoding ); + + /* Loop over quantizer and entroy coding to control bitrate */ + maxIter = 6; + gainMult_Q8 = SILK_FIX_CONST( 1, 8 ); + found_lower = 0; + found_upper = 0; + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + gainsID_lower = -1; + gainsID_upper = -1; + /* Copy part of the input state */ + silk_memcpy( &sRangeEnc_copy, psRangeEnc, sizeof( ec_enc ) ); + silk_memcpy( &sNSQ_copy, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + seed_copy = psEnc->sCmn.indices.Seed; + ec_prevLagIndex_copy = psEnc->sCmn.ec_prevLagIndex; + ec_prevSignalType_copy = psEnc->sCmn.ec_prevSignalType; + for( iter = 0; ; iter++ ) { + if( gainsID == gainsID_lower ) { + nBits = nBits_lower; + } else if( gainsID == gainsID_upper ) { + nBits = nBits_upper; + } else { + /* Restore part of the input state */ + if( iter > 0 ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy, sizeof( ec_enc ) ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy, sizeof( silk_nsq_state ) ); + psEnc->sCmn.indices.Seed = seed_copy; + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + } + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + silk_NSQ_wrapper_FLP( psEnc, &sEncCtrl, &psEnc->sCmn.indices, &psEnc->sCmn.sNSQ, psEnc->sCmn.pulses, x_frame ); + + if ( iter == maxIter && !found_lower ) { + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + } + + /****************************************/ + /* Encode Parameters */ + /****************************************/ + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + /****************************************/ + /* Encode Excitation Signal */ + /****************************************/ + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + + /* If we still bust after the last iteration, do some damage control. */ + if ( iter == maxIter && !found_lower && nBits > maxBits ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + + /* Keep gains the same as the last frame. */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + psEnc->sCmn.indices.GainsIndices[ i ] = 4; + } + if (condCoding != CODE_CONDITIONALLY) { + psEnc->sCmn.indices.GainsIndices[ 0 ] = sEncCtrl.lastGainIndexPrev; + } + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + /* Clear all pulses. */ + for ( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + psEnc->sCmn.pulses[ i ] = 0; + } + + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + } + + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { + break; + } + } + + if( iter == maxIter ) { + if( found_lower && ( gainsID == gainsID_lower || nBits > maxBits ) ) { + /* Restore output state from earlier iteration that did meet the bitrate budget */ + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + celt_assert( sRangeEnc_copy2.offs <= 1275 ); + silk_memcpy( psRangeEnc->buf, ec_buf_copy, sRangeEnc_copy2.offs ); + silk_memcpy( &psEnc->sCmn.sNSQ, &sNSQ_copy2, sizeof( silk_nsq_state ) ); + psEnc->sShape.LastGainIndex = LastGainIndex_copy2; + } + break; + } + + if( nBits > maxBits ) { + if( found_lower == 0 && iter >= 2 ) { + /* Adjust the quantizer's rate/distortion tradeoff and discard previous "upper" results */ + sEncCtrl.Lambda = silk_max_float(sEncCtrl.Lambda*1.5f, 1.5f); + /* Reducing dithering can help us hit the target. */ + psEnc->sCmn.indices.quantOffsetType = 0; + found_upper = 0; + gainsID_upper = -1; + } else { + found_upper = 1; + nBits_upper = nBits; + gainMult_upper = gainMult_Q8; + gainsID_upper = gainsID; + } + } else if( nBits < maxBits - bits_margin ) { + found_lower = 1; + nBits_lower = nBits; + gainMult_lower = gainMult_Q8; + if( gainsID != gainsID_lower ) { + gainsID_lower = gainsID; + /* Copy part of the output state */ + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + celt_assert( psRangeEnc->offs <= 1275 ); + silk_memcpy( ec_buf_copy, psRangeEnc->buf, psRangeEnc->offs ); + silk_memcpy( &sNSQ_copy2, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + LastGainIndex_copy2 = psEnc->sShape.LastGainIndex; + } + } else { + /* Close enough */ + break; + } + + if ( !found_lower && nBits > maxBits ) { + int j; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + int sum=0; + for ( j = i*psEnc->sCmn.subfr_length; j < (i+1)*psEnc->sCmn.subfr_length; j++ ) { + sum += abs( psEnc->sCmn.pulses[j] ); + } + if ( iter == 0 || (sum < best_sum[i] && !gain_lock[i]) ) { + best_sum[i] = sum; + best_gain_mult[i] = gainMult_Q8; + } else { + gain_lock[i] = 1; + } + } + } + if( ( found_lower & found_upper ) == 0 ) { + /* Adjust gain according to high-rate rate/distortion curve */ + if( nBits > maxBits ) { + gainMult_Q8 = silk_min_32( 1024, gainMult_Q8*3/2 ); + } else { + gainMult_Q8 = silk_max_32( 64, gainMult_Q8*4/5 ); + } + } else { + /* Adjust gain by interpolating */ + gainMult_Q8 = gainMult_lower + ( ( gainMult_upper - gainMult_lower ) * ( maxBits - nBits_lower ) ) / ( nBits_upper - nBits_lower ); + /* New gain multplier must be between 25% and 75% of old range (note that gainMult_upper < gainMult_lower) */ + if( gainMult_Q8 > silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_ADD_RSHIFT32( gainMult_lower, gainMult_upper - gainMult_lower, 2 ); + } else + if( gainMult_Q8 < silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ) ) { + gainMult_Q8 = silk_SUB_RSHIFT32( gainMult_upper, gainMult_upper - gainMult_lower, 2 ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + opus_int16 tmp; + if ( gain_lock[i] ) { + tmp = best_gain_mult[i]; + } else { + tmp = gainMult_Q8; + } + pGains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], tmp ), 8 ); + } + + /* Quantize gains */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, pGains_Q16, + &psEnc->sShape.LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Unique identifier of gains vector */ + gainsID = silk_gains_ID( psEnc->sCmn.indices.GainsIndices, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + sEncCtrl.Gains[ i ] = pGains_Q16[ i ] / 65536.0f; + } + } + } + + /* Update input buffer */ + silk_memmove( psEnc->x_buf, &psEnc->x_buf[ psEnc->sCmn.frame_length ], + ( psEnc->sCmn.ltp_mem_length + LA_SHAPE_MS * psEnc->sCmn.fs_kHz ) * sizeof( silk_float ) ); + + /* Exit without entropy coding */ + if( psEnc->sCmn.prefillFlag ) { + /* No payload */ + *pnBytesOut = 0; + return ret; + } + + /* Parameters needed for next frame */ + psEnc->sCmn.prevLag = sEncCtrl.pitchL[ psEnc->sCmn.nb_subfr - 1 ]; + psEnc->sCmn.prevSignalType = psEnc->sCmn.indices.signalType; + + /****************************************/ + /* Finalize payload */ + /****************************************/ + psEnc->sCmn.first_frame_after_reset = 0; + /* Payload size */ + *pnBytesOut = silk_RSHIFT( ec_tell( psRangeEnc ) + 7, 3 ); + + return ret; +} + +/* Low-Bitrate Redundancy (LBRR) encoding. Reuse all parameters but encode excitation at lower bitrate */ +static OPUS_INLINE void silk_LBRR_encode_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float xfw[], /* I Input signal */ + opus_int condCoding /* I The type of conditional coding used so far for this frame */ +) +{ + opus_int k; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + silk_float TempGains[ MAX_NB_SUBFR ]; + SideInfoIndices *psIndices_LBRR = &psEnc->sCmn.indices_LBRR[ psEnc->sCmn.nFramesEncoded ]; + silk_nsq_state sNSQ_LBRR; + + /*******************************************/ + /* Control use of inband LBRR */ + /*******************************************/ + if( psEnc->sCmn.LBRR_enabled && psEnc->sCmn.speech_activity_Q8 > SILK_FIX_CONST( LBRR_SPEECH_ACTIVITY_THRES, 8 ) ) { + psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded ] = 1; + + /* Copy noise shaping quantizer state and quantization indices from regular encoding */ + silk_memcpy( &sNSQ_LBRR, &psEnc->sCmn.sNSQ, sizeof( silk_nsq_state ) ); + silk_memcpy( psIndices_LBRR, &psEnc->sCmn.indices, sizeof( SideInfoIndices ) ); + + /* Save original gains */ + silk_memcpy( TempGains, psEncCtrl->Gains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); + + if( psEnc->sCmn.nFramesEncoded == 0 || psEnc->sCmn.LBRR_flags[ psEnc->sCmn.nFramesEncoded - 1 ] == 0 ) { + /* First frame in packet or previous frame not LBRR coded */ + psEnc->sCmn.LBRRprevLastGainIndex = psEnc->sShape.LastGainIndex; + + /* Increase Gains to get target LBRR rate */ + psIndices_LBRR->GainsIndices[ 0 ] += psEnc->sCmn.LBRR_GainIncreases; + psIndices_LBRR->GainsIndices[ 0 ] = silk_min_int( psIndices_LBRR->GainsIndices[ 0 ], N_LEVELS_QGAIN - 1 ); + } + + /* Decode to get gains in sync with decoder */ + silk_gains_dequant( Gains_Q16, psIndices_LBRR->GainsIndices, + &psEnc->sCmn.LBRRprevLastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] = Gains_Q16[ k ] * ( 1.0f / 65536.0f ); + } + + /*****************************************/ + /* Noise shaping quantization */ + /*****************************************/ + silk_NSQ_wrapper_FLP( psEnc, psEncCtrl, psIndices_LBRR, &sNSQ_LBRR, + psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], xfw ); + + /* Restore original gains */ + silk_memcpy( psEncCtrl->Gains, TempGains, psEnc->sCmn.nb_subfr * sizeof( silk_float ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/energy_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/energy_FLP.c new file mode 100644 index 0000000000..7bc7173c9c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/energy_FLP.c @@ -0,0 +1,59 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* sum of squares of a silk_float array, with result as double */ +double silk_energy_FLP( + const silk_float *data, + opus_int dataSize +) +{ + opus_int i; + double result; + + /* 4x unrolled loop */ + result = 0.0; + for( i = 0; i < dataSize - 3; i += 4 ) { + result += data[ i + 0 ] * (double)data[ i + 0 ] + + data[ i + 1 ] * (double)data[ i + 1 ] + + data[ i + 2 ] * (double)data[ i + 2 ] + + data[ i + 3 ] * (double)data[ i + 3 ]; + } + + /* add any remaining products */ + for( ; i < dataSize; i++ ) { + result += data[ i ] * (double)data[ i ]; + } + + silk_assert( result >= 0.0 ); + return result; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LPC_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LPC_FLP.c new file mode 100644 index 0000000000..6ccd711dc3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LPC_FLP.c @@ -0,0 +1,105 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "define.h" +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* LPC analysis */ +void silk_find_LPC_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const silk_float x[], /* I Input signal */ + const silk_float minInvGain, /* I Inverse of max prediction gain */ + int arch +) +{ + opus_int k, subfr_length; + silk_float a[ MAX_LPC_ORDER ]; + + /* Used only for NLSF interpolation */ + silk_float res_nrg, res_nrg_2nd, res_nrg_interp; + opus_int16 NLSF0_Q15[ MAX_LPC_ORDER ]; + silk_float a_tmp[ MAX_LPC_ORDER ]; + silk_float LPC_res[ MAX_FRAME_LENGTH + MAX_NB_SUBFR * MAX_LPC_ORDER ]; + + subfr_length = psEncC->subfr_length + psEncC->predictLPCOrder; + + /* Default: No interpolation */ + psEncC->indices.NLSFInterpCoef_Q2 = 4; + + /* Burg AR analysis for the full frame */ + res_nrg = silk_burg_modified_FLP( a, x, minInvGain, subfr_length, psEncC->nb_subfr, psEncC->predictLPCOrder, arch ); + + if( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) { + /* Optimal solution for last 10 ms; subtract residual energy here, as that's easier than */ + /* adding it to the residual energy of the first 10 ms in each iteration of the search below */ + res_nrg -= silk_burg_modified_FLP( a_tmp, x + ( MAX_NB_SUBFR / 2 ) * subfr_length, minInvGain, subfr_length, MAX_NB_SUBFR / 2, psEncC->predictLPCOrder, arch ); + + /* Convert to NLSFs */ + silk_A2NLSF_FLP( NLSF_Q15, a_tmp, psEncC->predictLPCOrder ); + + /* Search over interpolation indices to find the one with lowest residual energy */ + res_nrg_2nd = silk_float_MAX; + for( k = 3; k >= 0; k-- ) { + /* Interpolate NLSFs for first half */ + silk_interpolate( NLSF0_Q15, psEncC->prev_NLSFq_Q15, NLSF_Q15, k, psEncC->predictLPCOrder ); + + /* Convert to LPC for residual energy evaluation */ + silk_NLSF2A_FLP( a_tmp, NLSF0_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + /* Calculate residual energy with LSF interpolation */ + silk_LPC_analysis_filter_FLP( LPC_res, a_tmp, x, 2 * subfr_length, psEncC->predictLPCOrder ); + res_nrg_interp = (silk_float)( + silk_energy_FLP( LPC_res + psEncC->predictLPCOrder, subfr_length - psEncC->predictLPCOrder ) + + silk_energy_FLP( LPC_res + psEncC->predictLPCOrder + subfr_length, subfr_length - psEncC->predictLPCOrder ) ); + + /* Determine whether current interpolated NLSFs are best so far */ + if( res_nrg_interp < res_nrg ) { + /* Interpolation has lower residual energy */ + res_nrg = res_nrg_interp; + psEncC->indices.NLSFInterpCoef_Q2 = (opus_int8)k; + } else if( res_nrg_interp > res_nrg_2nd ) { + /* No reason to continue iterating - residual energies will continue to climb */ + break; + } + res_nrg_2nd = res_nrg_interp; + } + } + + if( psEncC->indices.NLSFInterpCoef_Q2 == 4 ) { + /* NLSF interpolation is currently inactive, calculate NLSFs from full frame AR coefficients */ + silk_A2NLSF_FLP( NLSF_Q15, a, psEncC->predictLPCOrder ); + } + + celt_assert( psEncC->indices.NLSFInterpCoef_Q2 == 4 || + ( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LTP_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LTP_FLP.c new file mode 100644 index 0000000000..90aeeac0b7 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_LTP_FLP.c @@ -0,0 +1,65 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +void silk_find_LTP_FLP( + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + int arch +) +{ + opus_int k; + silk_float *xX_ptr, *XX_ptr; + const silk_float *lag_ptr; + silk_float xx, temp; + + xX_ptr = xX; + XX_ptr = XX; + for( k = 0; k < nb_subfr; k++ ) { + lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 ); + silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, XX_ptr, arch ); + silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xX_ptr, arch ); + xx = ( silk_float )silk_energy_FLP( r_ptr, subfr_length + LTP_ORDER ); + temp = 1.0f / silk_max( xx, LTP_CORR_INV_MAX * 0.5f * ( XX_ptr[ 0 ] + XX_ptr[ 24 ] ) + 1.0f ); + silk_scale_vector_FLP( XX_ptr, temp, LTP_ORDER * LTP_ORDER ); + silk_scale_vector_FLP( xX_ptr, temp, LTP_ORDER ); + + r_ptr += subfr_length; + XX_ptr += LTP_ORDER * LTP_ORDER; + xX_ptr += LTP_ORDER; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pitch_lags_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pitch_lags_FLP.c new file mode 100644 index 0000000000..1f6bd5991c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pitch_lags_FLP.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main_FLP.h" +#include "tuning_parameters.h" + +void silk_find_pitch_lags_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + silk_float res[], /* O Residual */ + const silk_float x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +) +{ + opus_int buf_len; + silk_float thrhld, res_nrg; + const silk_float *x_buf_ptr, *x_buf; + silk_float auto_corr[ MAX_FIND_PITCH_LPC_ORDER + 1 ]; + silk_float A[ MAX_FIND_PITCH_LPC_ORDER ]; + silk_float refl_coef[ MAX_FIND_PITCH_LPC_ORDER ]; + silk_float Wsig[ FIND_PITCH_LPC_WIN_MAX ]; + silk_float *Wsig_ptr; + + /******************************************/ + /* Set up buffer lengths etc based on Fs */ + /******************************************/ + buf_len = psEnc->sCmn.la_pitch + psEnc->sCmn.frame_length + psEnc->sCmn.ltp_mem_length; + + /* Safety check */ + celt_assert( buf_len >= psEnc->sCmn.pitch_LPC_win_length ); + + x_buf = x - psEnc->sCmn.ltp_mem_length; + + /******************************************/ + /* Estimate LPC AR coeficients */ + /******************************************/ + + /* Calculate windowed signal */ + + /* First LA_LTP samples */ + x_buf_ptr = x_buf + buf_len - psEnc->sCmn.pitch_LPC_win_length; + Wsig_ptr = Wsig; + silk_apply_sine_window_FLP( Wsig_ptr, x_buf_ptr, 1, psEnc->sCmn.la_pitch ); + + /* Middle non-windowed samples */ + Wsig_ptr += psEnc->sCmn.la_pitch; + x_buf_ptr += psEnc->sCmn.la_pitch; + silk_memcpy( Wsig_ptr, x_buf_ptr, ( psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ) ) * sizeof( silk_float ) ); + + /* Last LA_LTP samples */ + Wsig_ptr += psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ); + x_buf_ptr += psEnc->sCmn.pitch_LPC_win_length - ( psEnc->sCmn.la_pitch << 1 ); + silk_apply_sine_window_FLP( Wsig_ptr, x_buf_ptr, 2, psEnc->sCmn.la_pitch ); + + /* Calculate autocorrelation sequence */ + silk_autocorrelation_FLP( auto_corr, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1, arch ); + + /* Add white noise, as a fraction of the energy */ + auto_corr[ 0 ] += auto_corr[ 0 ] * FIND_PITCH_WHITE_NOISE_FRACTION + 1; + + /* Calculate the reflection coefficients using Schur */ + res_nrg = silk_schur_FLP( refl_coef, auto_corr, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Prediction gain */ + psEncCtrl->predGain = auto_corr[ 0 ] / silk_max_float( res_nrg, 1.0f ); + + /* Convert reflection coefficients to prediction coefficients */ + silk_k2a_FLP( A, refl_coef, psEnc->sCmn.pitchEstimationLPCOrder ); + + /* Bandwidth expansion */ + silk_bwexpander_FLP( A, psEnc->sCmn.pitchEstimationLPCOrder, FIND_PITCH_BANDWIDTH_EXPANSION ); + + /*****************************************/ + /* LPC analysis filtering */ + /*****************************************/ + silk_LPC_analysis_filter_FLP( res, A, x_buf, buf_len, psEnc->sCmn.pitchEstimationLPCOrder ); + + if( psEnc->sCmn.indices.signalType != TYPE_NO_VOICE_ACTIVITY && psEnc->sCmn.first_frame_after_reset == 0 ) { + /* Threshold for pitch estimator */ + thrhld = 0.6f; + thrhld -= 0.004f * psEnc->sCmn.pitchEstimationLPCOrder; + thrhld -= 0.1f * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + thrhld -= 0.15f * (psEnc->sCmn.prevSignalType >> 1); + thrhld -= 0.1f * psEnc->sCmn.input_tilt_Q15 * ( 1.0f / 32768.0f ); + + /*****************************************/ + /* Call Pitch estimator */ + /*****************************************/ + if( silk_pitch_analysis_core_FLP( res, psEncCtrl->pitchL, &psEnc->sCmn.indices.lagIndex, + &psEnc->sCmn.indices.contourIndex, &psEnc->LTPCorr, psEnc->sCmn.prevLag, psEnc->sCmn.pitchEstimationThreshold_Q16 / 65536.0f, + thrhld, psEnc->sCmn.fs_kHz, psEnc->sCmn.pitchEstimationComplexity, psEnc->sCmn.nb_subfr, arch ) == 0 ) + { + psEnc->sCmn.indices.signalType = TYPE_VOICED; + } else { + psEnc->sCmn.indices.signalType = TYPE_UNVOICED; + } + } else { + silk_memset( psEncCtrl->pitchL, 0, sizeof( psEncCtrl->pitchL ) ); + psEnc->sCmn.indices.lagIndex = 0; + psEnc->sCmn.indices.contourIndex = 0; + psEnc->LTPCorr = 0; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pred_coefs_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pred_coefs_FLP.c new file mode 100644 index 0000000000..f3c54cf474 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/find_pred_coefs_FLP.c @@ -0,0 +1,117 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Find LPC and LTP coefficients */ +void silk_find_pred_coefs_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float res_pitch[], /* I Residual from pitch analysis */ + const silk_float x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + opus_int i; + silk_float XXLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + silk_float xXLTP[ MAX_NB_SUBFR * LTP_ORDER ]; + silk_float invGains[ MAX_NB_SUBFR ]; + /* Set to NLSF_Q15 to zero so we don't copy junk to the state. */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ]={0}; + const silk_float *x_ptr; + silk_float *x_pre_ptr, LPC_in_pre[ MAX_NB_SUBFR * MAX_LPC_ORDER + MAX_FRAME_LENGTH ]; + silk_float minInvGain; + + /* Weighting for weighted least squares */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + silk_assert( psEncCtrl->Gains[ i ] > 0.0f ); + invGains[ i ] = 1.0f / psEncCtrl->Gains[ i ]; + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /**********/ + /* VOICED */ + /**********/ + celt_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 ); + + /* LTP analysis */ + silk_find_LTP_FLP( XXLTP, xXLTP, res_pitch, psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Quantize LTP gain parameters */ + silk_quant_LTP_gains_FLP( psEncCtrl->LTPCoef, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex, + &psEnc->sCmn.sum_log_gain_Q7, &psEncCtrl->LTPredCodGain, XXLTP, xXLTP, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); + + /* Control LTP scaling */ + silk_LTP_scale_ctrl_FLP( psEnc, psEncCtrl, condCoding ); + + /* Create LTP residual */ + silk_LTP_analysis_filter_FLP( LPC_in_pre, x - psEnc->sCmn.predictLPCOrder, psEncCtrl->LTPCoef, + psEncCtrl->pitchL, invGains, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + } else { + /************/ + /* UNVOICED */ + /************/ + /* Create signal with prepended subframes, scaled by inverse gains */ + x_ptr = x - psEnc->sCmn.predictLPCOrder; + x_pre_ptr = LPC_in_pre; + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + silk_scale_copy_vector_FLP( x_pre_ptr, x_ptr, invGains[ i ], + psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder ); + x_pre_ptr += psEnc->sCmn.subfr_length + psEnc->sCmn.predictLPCOrder; + x_ptr += psEnc->sCmn.subfr_length; + } + silk_memset( psEncCtrl->LTPCoef, 0, psEnc->sCmn.nb_subfr * LTP_ORDER * sizeof( silk_float ) ); + psEncCtrl->LTPredCodGain = 0.0f; + psEnc->sCmn.sum_log_gain_Q7 = 0; + } + + /* Limit on total predictive coding gain */ + if( psEnc->sCmn.first_frame_after_reset ) { + minInvGain = 1.0f / MAX_PREDICTION_POWER_GAIN_AFTER_RESET; + } else { + minInvGain = (silk_float)pow( 2, psEncCtrl->LTPredCodGain / 3 ) / MAX_PREDICTION_POWER_GAIN; + minInvGain /= 0.25f + 0.75f * psEncCtrl->coding_quality; + } + + /* LPC_in_pre contains the LTP-filtered input for voiced, and the unfiltered input for unvoiced */ + silk_find_LPC_FLP( &psEnc->sCmn, NLSF_Q15, LPC_in_pre, minInvGain, psEnc->sCmn.arch ); + + /* Quantize LSFs */ + silk_process_NLSFs_FLP( &psEnc->sCmn, psEncCtrl->PredCoef, NLSF_Q15, psEnc->sCmn.prev_NLSFq_Q15 ); + + /* Calculate residual energy using quantized LPC coefficients */ + silk_residual_energy_FLP( psEncCtrl->ResNrg, LPC_in_pre, psEncCtrl->PredCoef, psEncCtrl->Gains, + psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + + /* Copy to prediction struct for use in next frame for interpolation */ + silk_memcpy( psEnc->sCmn.prev_NLSFq_Q15, NLSF_Q15, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/inner_product_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/inner_product_FLP.c new file mode 100644 index 0000000000..88b160ab40 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/inner_product_FLP.c @@ -0,0 +1,59 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* inner product of two silk_float arrays, with result as double */ +double silk_inner_product_FLP_c( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +) +{ + opus_int i; + double result; + + /* 4x unrolled loop */ + result = 0.0; + for( i = 0; i < dataSize - 3; i += 4 ) { + result += data1[ i + 0 ] * (double)data2[ i + 0 ] + + data1[ i + 1 ] * (double)data2[ i + 1 ] + + data1[ i + 2 ] * (double)data2[ i + 2 ] + + data1[ i + 3 ] * (double)data2[ i + 3 ]; + } + + /* add any remaining products */ + for( ; i < dataSize; i++ ) { + result += data1[ i ] * (double)data2[ i ]; + } + + return result; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/k2a_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/k2a_FLP.c new file mode 100644 index 0000000000..1448008dbb --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/k2a_FLP.c @@ -0,0 +1,54 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* step up function, converts reflection coefficients to prediction coefficients */ +void silk_k2a_FLP( + silk_float *A, /* O prediction coefficients [order] */ + const silk_float *rc, /* I reflection coefficients [order] */ + opus_int32 order /* I prediction order */ +) +{ + opus_int k, n; + silk_float rck, tmp1, tmp2; + + for( k = 0; k < order; k++ ) { + rck = rc[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A[ n ]; + tmp2 = A[ k - n - 1 ]; + A[ n ] = tmp1 + tmp2 * rck; + A[ k - n - 1 ] = tmp2 + tmp1 * rck; + } + A[ k ] = -rck; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/main_FLP.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/main_FLP.h new file mode 100644 index 0000000000..2e4435cc68 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/main_FLP.h @@ -0,0 +1,290 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MAIN_FLP_H +#define SILK_MAIN_FLP_H + +#include "SigProc_FLP.h" +#include "SigProc_FIX.h" +#include "structs_FLP.h" +#include "main.h" +#include "define.h" +#include "debug.h" +#include "entenc.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define silk_encoder_state_Fxx silk_encoder_state_FLP +#define silk_encode_do_VAD_Fxx silk_encode_do_VAD_FLP +#define silk_encode_frame_Fxx silk_encode_frame_FLP + +/*********************/ +/* Encoder Functions */ +/*********************/ + +/* High-pass filter with cutoff frequency adaptation based on pitch lag statistics */ +void silk_HP_variable_cutoff( + silk_encoder_state_Fxx state_Fxx[] /* I/O Encoder states */ +); + +/* Encoder main function */ +void silk_encode_do_VAD_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int activity /* I Decision of Opus voice activity detector */ +); + +/* Encoder main function */ +opus_int silk_encode_frame_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + opus_int32 *pnBytesOut, /* O Number of payload bytes; */ + ec_enc *psRangeEnc, /* I/O compressor data structure */ + opus_int condCoding, /* I The type of conditional coding to use */ + opus_int maxBits, /* I If > 0: maximum number of output bits */ + opus_int useCBR /* I Flag to force constant-bitrate operation */ +); + +/* Initializes the Silk encoder state */ +opus_int silk_init_encoder( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + int arch /* I Run-tim architecture */ +); + +/* Control the Silk encoder */ +opus_int silk_control_encoder( + silk_encoder_state_FLP *psEnc, /* I/O Pointer to Silk encoder state FLP */ + silk_EncControlStruct *encControl, /* I Control structure */ + const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ + const opus_int channelNb, /* I Channel number */ + const opus_int force_fs_kHz +); + +/**************************/ +/* Noise shaping analysis */ +/**************************/ +/* Compute noise shaping coefficients and initial gain values */ +void silk_noise_shape_analysis_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float *pitch_res, /* I LPC residual from pitch analysis */ + const silk_float *x /* I Input signal [frame_length + la_shape] */ +); + +/* Autocorrelations for a warped frequency axis */ +void silk_warped_autocorrelation_FLP( + silk_float *corr, /* O Result [order + 1] */ + const silk_float *input, /* I Input data to correlate */ + const silk_float warping, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +); + +/* Calculation of LTP state scaling */ +void silk_LTP_scale_ctrl_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/**********************************************/ +/* Prediction Analysis */ +/**********************************************/ +/* Find pitch lags */ +void silk_find_pitch_lags_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + silk_float res[], /* O Residual */ + const silk_float x[], /* I Speech signal */ + int arch /* I Run-time architecture */ +); + +/* Find LPC and LTP coefficients */ +void silk_find_pred_coefs_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float res_pitch[], /* I Residual from pitch analysis */ + const silk_float x[], /* I Speech signal */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* LPC analysis */ +void silk_find_LPC_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 NLSF_Q15[], /* O NLSFs */ + const silk_float x[], /* I Input signal */ + const silk_float minInvGain, /* I Prediction gain from LTP (dB) */ + int arch +); + +/* LTP analysis */ +void silk_find_LTP_FLP( + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + int arch +); + +void silk_LTP_analysis_filter_FLP( + silk_float *LTP_res, /* O LTP res MAX_NB_SUBFR*(pre_lgth+subfr_lngth) */ + const silk_float *x, /* I Input signal, with preceding samples */ + const silk_float B[ LTP_ORDER * MAX_NB_SUBFR ], /* I LTP coefficients for each subframe */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const silk_float invGains[ MAX_NB_SUBFR ], /* I Inverse quantization gains */ + const opus_int subfr_length, /* I Length of each subframe */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int pre_length /* I Preceding samples for each subframe */ +); + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FLP( + silk_float nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + const silk_float x[], /* I Input signal */ + silk_float a[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const silk_float gains[], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int LPC_order /* I LPC order */ +); + +/* 16th order LPC analysis filter */ +void silk_LPC_analysis_filter_FLP( + silk_float r_LPC[], /* O LPC residual signal */ + const silk_float PredCoef[], /* I LPC coefficients */ + const silk_float s[], /* I Input signal */ + const opus_int length, /* I Length of input signal */ + const opus_int Order /* I LPC order */ +); + +/* LTP tap quantizer */ +void silk_quant_LTP_gains_FLP( + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ + opus_int8 *periodicity_index, /* O Periodicity index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +); + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +silk_float silk_residual_energy_covar_FLP( /* O Weighted residual energy */ + const silk_float *c, /* I Filter coefficients */ + silk_float *wXX, /* I/O Weighted correlation matrix, reg. out */ + const silk_float *wXx, /* I Weighted correlation vector */ + const silk_float wxx, /* I Weighted correlation value */ + const opus_int D /* I Dimension */ +); + +/* Processing of gains */ +void silk_process_gains_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/******************/ +/* Linear Algebra */ +/******************/ +/* Calculates correlation matrix X'*X */ +void silk_corrMatrix_FLP( + const silk_float *x, /* I x vector [ L+order-1 ] used to create X */ + const opus_int L, /* I Length of vectors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *XX, /* O X'*X correlation matrix [order x order] */ + int arch +); + +/* Calculates correlation vector X'*t */ +void silk_corrVector_FLP( + const silk_float *x, /* I x vector [L+order-1] used to create X */ + const silk_float *t, /* I Target vector [L] */ + const opus_int L, /* I Length of vecors */ + const opus_int Order, /* I Max lag for correlation */ + silk_float *Xt, /* O X'*t correlation vector [order] */ + int arch +); + +/* Apply sine window to signal vector. */ +/* Window types: */ +/* 1 -> sine window from 0 to pi/2 */ +/* 2 -> sine window from pi/2 to pi */ +void silk_apply_sine_window_FLP( + silk_float px_win[], /* O Pointer to windowed signal */ + const silk_float px[], /* I Pointer to input signal */ + const opus_int win_type, /* I Selects a window type */ + const opus_int length /* I Window length, multiple of 4 */ +); + +/* Wrapper functions. Call flp / fix code */ + +/* Convert AR filter coefficients to NLSF parameters */ +void silk_A2NLSF_FLP( + opus_int16 *NLSF_Q15, /* O NLSF vector [ LPC_order ] */ + const silk_float *pAR, /* I LPC coefficients [ LPC_order ] */ + const opus_int LPC_order /* I LPC order */ +); + +/* Convert NLSF parameters to AR prediction filter coefficients */ +void silk_NLSF2A_FLP( + silk_float *pAR, /* O LPC coefficients [ LPC_order ] */ + const opus_int16 *NLSF_Q15, /* I NLSF vector [ LPC_order ] */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +); + +/* Limit, stabilize, and quantize NLSFs */ +void silk_process_NLSFs_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSF_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +); + +/* Floating-point Silk NSQ wrapper */ +void silk_NSQ_wrapper_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + SideInfoIndices *psIndices, /* I/O Quantization indices */ + silk_nsq_state *psNSQ, /* I/O Noise Shaping Quantzation state */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const silk_float x[] /* I Prefiltered input signal */ +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/noise_shape_analysis_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/noise_shape_analysis_FLP.c new file mode 100644 index 0000000000..0b5ea95218 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/noise_shape_analysis_FLP.c @@ -0,0 +1,350 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Compute gain to make warped filter coefficients have a zero mean log frequency response on a */ +/* non-warped frequency scale. (So that it can be implemented with a minimum-phase monic filter.) */ +/* Note: A monic filter is one with the first coefficient equal to 1.0. In Silk we omit the first */ +/* coefficient in an array of coefficients, for monic filters. */ +static OPUS_INLINE silk_float warped_gain( + const silk_float *coefs, + silk_float lambda, + opus_int order +) { + opus_int i; + silk_float gain; + + lambda = -lambda; + gain = coefs[ order - 1 ]; + for( i = order - 2; i >= 0; i-- ) { + gain = lambda * gain + coefs[ i ]; + } + return (silk_float)( 1.0f / ( 1.0f - lambda * gain ) ); +} + +/* Convert warped filter coefficients to monic pseudo-warped coefficients and limit maximum */ +/* amplitude of monic warped coefficients by using bandwidth expansion on the true coefficients */ +static OPUS_INLINE void warped_true2monic_coefs( + silk_float *coefs, + silk_float lambda, + silk_float limit, + opus_int order +) { + opus_int i, iter, ind = 0; + silk_float tmp, maxabs, chirp, gain; + + /* Convert to monic coefficients */ + for( i = order - 1; i > 0; i-- ) { + coefs[ i - 1 ] -= lambda * coefs[ i ]; + } + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + + /* Limit */ + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs = -1.0f; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_float( coefs[ i ] ); + if( tmp > maxabs ) { + maxabs = tmp; + ind = i; + } + } + if( maxabs <= limit ) { + /* Coefficients are within range - done */ + return; + } + + /* Convert back to true warped coefficients */ + for( i = 1; i < order; i++ ) { + coefs[ i - 1 ] += lambda * coefs[ i ]; + } + gain = 1.0f / gain; + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + + /* Apply bandwidth expansion */ + chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); + silk_bwexpander_FLP( coefs, order, chirp ); + + /* Convert to monic warped coefficients */ + for( i = order - 1; i > 0; i-- ) { + coefs[ i - 1 ] -= lambda * coefs[ i ]; + } + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); + for( i = 0; i < order; i++ ) { + coefs[ i ] *= gain; + } + } + silk_assert( 0 ); +} + +static OPUS_INLINE void limit_coefs( + silk_float *coefs, + silk_float limit, + opus_int order +) { + opus_int i, iter, ind = 0; + silk_float tmp, maxabs, chirp; + + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs = -1.0f; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_float( coefs[ i ] ); + if( tmp > maxabs ) { + maxabs = tmp; + ind = i; + } + } + if( maxabs <= limit ) { + /* Coefficients are within range - done */ + return; + } + + /* Apply bandwidth expansion */ + chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); + silk_bwexpander_FLP( coefs, order, chirp ); + } + silk_assert( 0 ); +} + +/* Compute noise shaping coefficients and initial gain values */ +void silk_noise_shape_analysis_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + const silk_float *pitch_res, /* I LPC residual from pitch analysis */ + const silk_float *x /* I Input signal [frame_length + la_shape] */ +) +{ + silk_shape_state_FLP *psShapeSt = &psEnc->sShape; + opus_int k, nSamples, nSegs; + silk_float SNR_adj_dB, HarmShapeGain, Tilt; + silk_float nrg, log_energy, log_energy_prev, energy_variation; + silk_float BWExp, gain_mult, gain_add, strength, b, warping; + silk_float x_windowed[ SHAPE_LPC_WIN_MAX ]; + silk_float auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; + silk_float rc[ MAX_SHAPE_LPC_ORDER + 1 ]; + const silk_float *x_ptr, *pitch_res_ptr; + + /* Point to start of first LPC analysis block */ + x_ptr = x - psEnc->sCmn.la_shape; + + /****************/ + /* GAIN CONTROL */ + /****************/ + SNR_adj_dB = psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ); + + /* Input quality is the average of the quality in the lowest two VAD bands */ + psEncCtrl->input_quality = 0.5f * ( psEnc->sCmn.input_quality_bands_Q15[ 0 ] + psEnc->sCmn.input_quality_bands_Q15[ 1 ] ) * ( 1.0f / 32768.0f ); + + /* Coding quality level, between 0.0 and 1.0 */ + psEncCtrl->coding_quality = silk_sigmoid( 0.25f * ( SNR_adj_dB - 20.0f ) ); + + if( psEnc->sCmn.useCBR == 0 ) { + /* Reduce coding SNR during low speech activity */ + b = 1.0f - psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + SNR_adj_dB -= BG_SNR_DECR_dB * psEncCtrl->coding_quality * ( 0.5f + 0.5f * psEncCtrl->input_quality ) * b * b; + } + + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce gains for periodic signals */ + SNR_adj_dB += HARM_SNR_INCR_dB * psEnc->LTPCorr; + } else { + /* For unvoiced signals and low-quality input, adjust the quality slower than SNR_dB setting */ + SNR_adj_dB += ( -0.4f * psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ) + 6.0f ) * ( 1.0f - psEncCtrl->input_quality ); + } + + /*************************/ + /* SPARSENESS PROCESSING */ + /*************************/ + /* Set quantizer offset */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Initially set to 0; may be overruled in process_gains(..) */ + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ + nSamples = 2 * psEnc->sCmn.fs_kHz; + energy_variation = 0.0f; + log_energy_prev = 0.0f; + pitch_res_ptr = pitch_res; + nSegs = silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; + for( k = 0; k < nSegs; k++ ) { + nrg = ( silk_float )nSamples + ( silk_float )silk_energy_FLP( pitch_res_ptr, nSamples ); + log_energy = silk_log2( nrg ); + if( k > 0 ) { + energy_variation += silk_abs_float( log_energy - log_energy_prev ); + } + log_energy_prev = log_energy; + pitch_res_ptr += nSamples; + } + + /* Set quantization offset depending on sparseness measure */ + if( energy_variation > ENERGY_VARIATION_THRESHOLD_QNT_OFFSET * (nSegs-1) ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /*******************************/ + /* Control bandwidth expansion */ + /*******************************/ + /* More BWE for signals with high prediction gain */ + strength = FIND_PITCH_WHITE_NOISE_FRACTION * psEncCtrl->predGain; /* between 0.0 and 1.0 */ + BWExp = BANDWIDTH_EXPANSION / ( 1.0f + strength * strength ); + + /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ + warping = (silk_float)psEnc->sCmn.warping_Q16 / 65536.0f + 0.01f * psEncCtrl->coding_quality; + + /********************************************/ + /* Compute noise shaping AR coefs and gains */ + /********************************************/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Apply window: sine slope followed by flat part followed by cosine slope */ + opus_int shift, slope_part, flat_part; + flat_part = psEnc->sCmn.fs_kHz * 3; + slope_part = ( psEnc->sCmn.shapeWinLength - flat_part ) / 2; + + silk_apply_sine_window_FLP( x_windowed, x_ptr, 1, slope_part ); + shift = slope_part; + silk_memcpy( x_windowed + shift, x_ptr + shift, flat_part * sizeof(silk_float) ); + shift += flat_part; + silk_apply_sine_window_FLP( x_windowed + shift, x_ptr + shift, 2, slope_part ); + + /* Update pointer: next LPC analysis block */ + x_ptr += psEnc->sCmn.subfr_length; + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Calculate warped auto correlation */ + silk_warped_autocorrelation_FLP( auto_corr, x_windowed, warping, + psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder ); + } else { + /* Calculate regular auto correlation */ + silk_autocorrelation_FLP( auto_corr, x_windowed, psEnc->sCmn.shapeWinLength, psEnc->sCmn.shapingLPCOrder + 1, psEnc->sCmn.arch ); + } + + /* Add white noise, as a fraction of energy */ + auto_corr[ 0 ] += auto_corr[ 0 ] * SHAPE_WHITE_NOISE_FRACTION + 1.0f; + + /* Convert correlations to prediction coefficients, and compute residual energy */ + nrg = silk_schur_FLP( rc, auto_corr, psEnc->sCmn.shapingLPCOrder ); + silk_k2a_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], rc, psEnc->sCmn.shapingLPCOrder ); + psEncCtrl->Gains[ k ] = ( silk_float )sqrt( nrg ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Adjust gain for warping */ + psEncCtrl->Gains[ k ] *= warped_gain( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, psEnc->sCmn.shapingLPCOrder ); + } + + /* Bandwidth expansion for synthesis filter shaping */ + silk_bwexpander_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder, BWExp ); + + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Convert to monic warped prediction coefficients and limit absolute values */ + warped_true2monic_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, 3.999f, psEnc->sCmn.shapingLPCOrder ); + } else { + /* Limit absolute values */ + limit_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], 3.999f, psEnc->sCmn.shapingLPCOrder ); + } + } + + /*****************/ + /* Gain tweaking */ + /*****************/ + /* Increase gains during low speech activity */ + gain_mult = (silk_float)pow( 2.0f, -0.16f * SNR_adj_dB ); + gain_add = (silk_float)pow( 2.0f, 0.16f * MIN_QGAIN_DB ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] *= gain_mult; + psEncCtrl->Gains[ k ] += gain_add; + } + + /************************************************/ + /* Control low-frequency shaping and noise tilt */ + /************************************************/ + /* Less low frequency shaping for noisy inputs */ + strength = LOW_FREQ_SHAPING * ( 1.0f + LOW_QUALITY_LOW_FREQ_SHAPING_DECR * ( psEnc->sCmn.input_quality_bands_Q15[ 0 ] * ( 1.0f / 32768.0f ) - 1.0f ) ); + strength *= psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Reduce low frequencies quantization noise for periodic signals, depending on pitch lag */ + /*f = 400; freqz([1, -0.98 + 2e-4 * f], [1, -0.97 + 7e-4 * f], 2^12, Fs); axis([0, 1000, -10, 1])*/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + b = 0.2f / psEnc->sCmn.fs_kHz + 3.0f / psEncCtrl->pitchL[ k ]; + psEncCtrl->LF_MA_shp[ k ] = -1.0f + b; + psEncCtrl->LF_AR_shp[ k ] = 1.0f - b - b * strength; + } + Tilt = - HP_NOISE_COEF - + (1 - HP_NOISE_COEF) * HARM_HP_NOISE_COEF * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ); + } else { + b = 1.3f / psEnc->sCmn.fs_kHz; + psEncCtrl->LF_MA_shp[ 0 ] = -1.0f + b; + psEncCtrl->LF_AR_shp[ 0 ] = 1.0f - b - b * strength * 0.6f; + for( k = 1; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->LF_MA_shp[ k ] = psEncCtrl->LF_MA_shp[ 0 ]; + psEncCtrl->LF_AR_shp[ k ] = psEncCtrl->LF_AR_shp[ 0 ]; + } + Tilt = -HP_NOISE_COEF; + } + + /****************************/ + /* HARMONIC SHAPING CONTROL */ + /****************************/ + if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + /* Harmonic noise shaping */ + HarmShapeGain = HARMONIC_SHAPING; + + /* More harmonic noise shaping for high bitrates or noisy input */ + HarmShapeGain += HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING * + ( 1.0f - ( 1.0f - psEncCtrl->coding_quality ) * psEncCtrl->input_quality ); + + /* Less harmonic noise shaping for less periodic signals */ + HarmShapeGain *= ( silk_float )sqrt( psEnc->LTPCorr ); + } else { + HarmShapeGain = 0.0f; + } + + /*************************/ + /* Smooth over subframes */ + /*************************/ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psShapeSt->HarmShapeGain_smth += SUBFR_SMTH_COEF * ( HarmShapeGain - psShapeSt->HarmShapeGain_smth ); + psEncCtrl->HarmShapeGain[ k ] = psShapeSt->HarmShapeGain_smth; + psShapeSt->Tilt_smth += SUBFR_SMTH_COEF * ( Tilt - psShapeSt->Tilt_smth ); + psEncCtrl->Tilt[ k ] = psShapeSt->Tilt_smth; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/pitch_analysis_core_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/pitch_analysis_core_FLP.c new file mode 100644 index 0000000000..0530a8831a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/pitch_analysis_core_FLP.c @@ -0,0 +1,630 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/***************************************************************************** +* Pitch analyser function +******************************************************************************/ +#include "SigProc_FLP.h" +#include "SigProc_FIX.h" +#include "pitch_est_defines.h" +#include "pitch.h" + +#define SCRATCH_SIZE 22 + +/************************************************************/ +/* Internally used functions */ +/************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +); + +static void silk_P_Ana_calc_energy_st3( + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity /* I Complexity setting */ +); + +/************************************************************/ +/* CORE PITCH ANALYSIS FUNCTION */ +/************************************************************/ +opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, 1 unvoiced */ + const silk_float *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + opus_int *pitch_out, /* O Pitch lag values [nb_subfr] */ + opus_int16 *lagIndex, /* O Lag Index */ + opus_int8 *contourIndex, /* O Pitch contour Index */ + silk_float *LTPCorr, /* I/O Normalized correlation; input: value from previous frame */ + opus_int prevLag, /* I Last lag of previous frame; set to zero is unvoiced */ + const silk_float search_thres1, /* I First stage threshold for lag candidates 0 - 1 */ + const silk_float search_thres2, /* I Final threshold for lag candidates 0 - 1 */ + const opus_int Fs_kHz, /* I sample frequency (kHz) */ + const opus_int complexity, /* I Complexity setting, 0-2, where 2 is highest */ + const opus_int nb_subfr, /* I Number of 5 ms subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, k, d, j; + silk_float frame_8kHz[ PE_MAX_FRAME_LENGTH_MS * 8 ]; + silk_float frame_4kHz[ PE_MAX_FRAME_LENGTH_MS * 4 ]; + opus_int16 frame_8_FIX[ PE_MAX_FRAME_LENGTH_MS * 8 ]; + opus_int16 frame_4_FIX[ PE_MAX_FRAME_LENGTH_MS * 4 ]; + opus_int32 filt_state[ 6 ]; + silk_float threshold, contour_bias; + silk_float C[ PE_MAX_NB_SUBFR][ (PE_MAX_LAG >> 1) + 5 ]; + opus_val32 xcorr[ PE_MAX_LAG_MS * 4 - PE_MIN_LAG_MS * 4 + 1 ]; + silk_float CC[ PE_NB_CBKS_STAGE2_EXT ]; + const silk_float *target_ptr, *basis_ptr; + double cross_corr, normalizer, energy, energy_tmp; + opus_int d_srch[ PE_D_SRCH_LENGTH ]; + opus_int16 d_comp[ (PE_MAX_LAG >> 1) + 5 ]; + opus_int length_d_srch, length_d_comp; + silk_float Cmax, CCmax, CCmax_b, CCmax_new_b, CCmax_new; + opus_int CBimax, CBimax_new, lag, start_lag, end_lag, lag_new; + opus_int cbk_size; + silk_float lag_log2, prevLag_log2, delta_lag_log2_sqr; + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ]; + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ]; + opus_int lag_counter; + opus_int frame_length, frame_length_8kHz, frame_length_4kHz; + opus_int sf_length, sf_length_8kHz, sf_length_4kHz; + opus_int min_lag, min_lag_8kHz, min_lag_4kHz; + opus_int max_lag, max_lag_8kHz, max_lag_4kHz; + opus_int nb_cbk_search; + const opus_int8 *Lag_CB_ptr; + + /* Check for valid sampling frequency */ + celt_assert( Fs_kHz == 8 || Fs_kHz == 12 || Fs_kHz == 16 ); + + /* Check for valid complexity setting */ + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + silk_assert( search_thres1 >= 0.0f && search_thres1 <= 1.0f ); + silk_assert( search_thres2 >= 0.0f && search_thres2 <= 1.0f ); + + /* Set up frame lengths max / min lag for the sampling frequency */ + frame_length = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * Fs_kHz; + frame_length_4kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 4; + frame_length_8kHz = ( PE_LTP_MEM_LENGTH_MS + nb_subfr * PE_SUBFR_LENGTH_MS ) * 8; + sf_length = PE_SUBFR_LENGTH_MS * Fs_kHz; + sf_length_4kHz = PE_SUBFR_LENGTH_MS * 4; + sf_length_8kHz = PE_SUBFR_LENGTH_MS * 8; + min_lag = PE_MIN_LAG_MS * Fs_kHz; + min_lag_4kHz = PE_MIN_LAG_MS * 4; + min_lag_8kHz = PE_MIN_LAG_MS * 8; + max_lag = PE_MAX_LAG_MS * Fs_kHz - 1; + max_lag_4kHz = PE_MAX_LAG_MS * 4; + max_lag_8kHz = PE_MAX_LAG_MS * 8 - 1; + + /* Resample from input sampled at Fs_kHz to 8 kHz */ + if( Fs_kHz == 16 ) { + /* Resample to 16 -> 8 khz */ + opus_int16 frame_16_FIX[ 16 * PE_MAX_FRAME_LENGTH_MS ]; + silk_float2short_array( frame_16_FIX, frame, frame_length ); + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); + silk_resampler_down2( filt_state, frame_8_FIX, frame_16_FIX, frame_length ); + silk_short2float_array( frame_8kHz, frame_8_FIX, frame_length_8kHz ); + } else if( Fs_kHz == 12 ) { + /* Resample to 12 -> 8 khz */ + opus_int16 frame_12_FIX[ 12 * PE_MAX_FRAME_LENGTH_MS ]; + silk_float2short_array( frame_12_FIX, frame, frame_length ); + silk_memset( filt_state, 0, 6 * sizeof( opus_int32 ) ); + silk_resampler_down2_3( filt_state, frame_8_FIX, frame_12_FIX, frame_length ); + silk_short2float_array( frame_8kHz, frame_8_FIX, frame_length_8kHz ); + } else { + celt_assert( Fs_kHz == 8 ); + silk_float2short_array( frame_8_FIX, frame, frame_length_8kHz ); + } + + /* Decimate again to 4 kHz */ + silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); + silk_resampler_down2( filt_state, frame_4_FIX, frame_8_FIX, frame_length_8kHz ); + silk_short2float_array( frame_4kHz, frame_4_FIX, frame_length_4kHz ); + + /* Low-pass filter */ + for( i = frame_length_4kHz - 1; i > 0; i-- ) { + frame_4kHz[ i ] = silk_ADD_SAT16( frame_4kHz[ i ], frame_4kHz[ i - 1 ] ); + } + + /****************************************************************************** + * FIRST STAGE, operating in 4 khz + ******************************************************************************/ + silk_memset(C, 0, sizeof(silk_float) * nb_subfr * ((PE_MAX_LAG >> 1) + 5)); + target_ptr = &frame_4kHz[ silk_LSHIFT( sf_length_4kHz, 2 ) ]; + for( k = 0; k < nb_subfr >> 1; k++ ) { + /* Check that we are within range of the array */ + celt_assert( target_ptr >= frame_4kHz ); + celt_assert( target_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + basis_ptr = target_ptr - min_lag_4kHz; + + /* Check that we are within range of the array */ + celt_assert( basis_ptr >= frame_4kHz ); + celt_assert( basis_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + celt_pitch_xcorr( target_ptr, target_ptr-max_lag_4kHz, xcorr, sf_length_8kHz, max_lag_4kHz - min_lag_4kHz + 1, arch ); + + /* Calculate first vector products before loop */ + cross_corr = xcorr[ max_lag_4kHz - min_lag_4kHz ]; + normalizer = silk_energy_FLP( target_ptr, sf_length_8kHz ) + + silk_energy_FLP( basis_ptr, sf_length_8kHz ) + + sf_length_8kHz * 4000.0f; + + C[ 0 ][ min_lag_4kHz ] += (silk_float)( 2 * cross_corr / normalizer ); + + /* From now on normalizer is computed recursively */ + for( d = min_lag_4kHz + 1; d <= max_lag_4kHz; d++ ) { + basis_ptr--; + + /* Check that we are within range of the array */ + silk_assert( basis_ptr >= frame_4kHz ); + silk_assert( basis_ptr + sf_length_8kHz <= frame_4kHz + frame_length_4kHz ); + + cross_corr = xcorr[ max_lag_4kHz - d ]; + + /* Add contribution of new sample and remove contribution from oldest sample */ + normalizer += + basis_ptr[ 0 ] * (double)basis_ptr[ 0 ] - + basis_ptr[ sf_length_8kHz ] * (double)basis_ptr[ sf_length_8kHz ]; + C[ 0 ][ d ] += (silk_float)( 2 * cross_corr / normalizer ); + } + /* Update target pointer */ + target_ptr += sf_length_8kHz; + } + + /* Apply short-lag bias */ + for( i = max_lag_4kHz; i >= min_lag_4kHz; i-- ) { + C[ 0 ][ i ] -= C[ 0 ][ i ] * i / 4096.0f; + } + + /* Sort */ + length_d_srch = 4 + 2 * complexity; + celt_assert( 3 * length_d_srch <= PE_D_SRCH_LENGTH ); + silk_insertion_sort_decreasing_FLP( &C[ 0 ][ min_lag_4kHz ], d_srch, max_lag_4kHz - min_lag_4kHz + 1, length_d_srch ); + + /* Escape if correlation is very low already here */ + Cmax = C[ 0 ][ min_lag_4kHz ]; + if( Cmax < 0.2f ) { + silk_memset( pitch_out, 0, nb_subfr * sizeof( opus_int ) ); + *LTPCorr = 0.0f; + *lagIndex = 0; + *contourIndex = 0; + return 1; + } + + threshold = search_thres1 * Cmax; + for( i = 0; i < length_d_srch; i++ ) { + /* Convert to 8 kHz indices for the sorted correlation that exceeds the threshold */ + if( C[ 0 ][ min_lag_4kHz + i ] > threshold ) { + d_srch[ i ] = silk_LSHIFT( d_srch[ i ] + min_lag_4kHz, 1 ); + } else { + length_d_srch = i; + break; + } + } + celt_assert( length_d_srch > 0 ); + + for( i = min_lag_8kHz - 5; i < max_lag_8kHz + 5; i++ ) { + d_comp[ i ] = 0; + } + for( i = 0; i < length_d_srch; i++ ) { + d_comp[ d_srch[ i ] ] = 1; + } + + /* Convolution */ + for( i = max_lag_8kHz + 3; i >= min_lag_8kHz; i-- ) { + d_comp[ i ] += d_comp[ i - 1 ] + d_comp[ i - 2 ]; + } + + length_d_srch = 0; + for( i = min_lag_8kHz; i < max_lag_8kHz + 1; i++ ) { + if( d_comp[ i + 1 ] > 0 ) { + d_srch[ length_d_srch ] = i; + length_d_srch++; + } + } + + /* Convolution */ + for( i = max_lag_8kHz + 3; i >= min_lag_8kHz; i-- ) { + d_comp[ i ] += d_comp[ i - 1 ] + d_comp[ i - 2 ] + d_comp[ i - 3 ]; + } + + length_d_comp = 0; + for( i = min_lag_8kHz; i < max_lag_8kHz + 4; i++ ) { + if( d_comp[ i ] > 0 ) { + d_comp[ length_d_comp ] = (opus_int16)( i - 2 ); + length_d_comp++; + } + } + + /********************************************************************************** + ** SECOND STAGE, operating at 8 kHz, on lag sections with high correlation + *************************************************************************************/ + /********************************************************************************* + * Find energy of each subframe projected onto its history, for a range of delays + *********************************************************************************/ + silk_memset( C, 0, PE_MAX_NB_SUBFR*((PE_MAX_LAG >> 1) + 5) * sizeof(silk_float)); + + if( Fs_kHz == 8 ) { + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * 8 ]; + } else { + target_ptr = &frame_8kHz[ PE_LTP_MEM_LENGTH_MS * 8 ]; + } + for( k = 0; k < nb_subfr; k++ ) { + energy_tmp = silk_energy_FLP( target_ptr, sf_length_8kHz ) + 1.0; + for( j = 0; j < length_d_comp; j++ ) { + d = d_comp[ j ]; + basis_ptr = target_ptr - d; + cross_corr = silk_inner_product_FLP( basis_ptr, target_ptr, sf_length_8kHz, arch ); + if( cross_corr > 0.0f ) { + energy = silk_energy_FLP( basis_ptr, sf_length_8kHz ); + C[ k ][ d ] = (silk_float)( 2 * cross_corr / ( energy + energy_tmp ) ); + } else { + C[ k ][ d ] = 0.0f; + } + } + target_ptr += sf_length_8kHz; + } + + /* search over lag range and lags codebook */ + /* scale factor for lag codebook, as a function of center lag */ + + CCmax = 0.0f; /* This value doesn't matter */ + CCmax_b = -1000.0f; + + CBimax = 0; /* To avoid returning undefined lag values */ + lag = -1; /* To check if lag with strong enough correlation has been found */ + + if( prevLag > 0 ) { + if( Fs_kHz == 12 ) { + prevLag = silk_LSHIFT( prevLag, 1 ) / 3; + } else if( Fs_kHz == 16 ) { + prevLag = silk_RSHIFT( prevLag, 1 ); + } + prevLag_log2 = silk_log2( (silk_float)prevLag ); + } else { + prevLag_log2 = 0; + } + + /* Set up stage 2 codebook based on number of subframes */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + cbk_size = PE_NB_CBKS_STAGE2_EXT; + Lag_CB_ptr = &silk_CB_lags_stage2[ 0 ][ 0 ]; + if( Fs_kHz == 8 && complexity > SILK_PE_MIN_COMPLEX ) { + /* If input is 8 khz use a larger codebook here because it is last stage */ + nb_cbk_search = PE_NB_CBKS_STAGE2_EXT; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE2; + } + } else { + cbk_size = PE_NB_CBKS_STAGE2_10MS; + Lag_CB_ptr = &silk_CB_lags_stage2_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE2_10MS; + } + + for( k = 0; k < length_d_srch; k++ ) { + d = d_srch[ k ]; + for( j = 0; j < nb_cbk_search; j++ ) { + CC[j] = 0.0f; + for( i = 0; i < nb_subfr; i++ ) { + /* Try all codebooks */ + CC[ j ] += C[ i ][ d + matrix_ptr( Lag_CB_ptr, i, j, cbk_size )]; + } + } + /* Find best codebook */ + CCmax_new = -1000.0f; + CBimax_new = 0; + for( i = 0; i < nb_cbk_search; i++ ) { + if( CC[ i ] > CCmax_new ) { + CCmax_new = CC[ i ]; + CBimax_new = i; + } + } + + /* Bias towards shorter lags */ + lag_log2 = silk_log2( (silk_float)d ); + CCmax_new_b = CCmax_new - PE_SHORTLAG_BIAS * nb_subfr * lag_log2; + + /* Bias towards previous lag */ + if( prevLag > 0 ) { + delta_lag_log2_sqr = lag_log2 - prevLag_log2; + delta_lag_log2_sqr *= delta_lag_log2_sqr; + CCmax_new_b -= PE_PREVLAG_BIAS * nb_subfr * (*LTPCorr) * delta_lag_log2_sqr / ( delta_lag_log2_sqr + 0.5f ); + } + + if( CCmax_new_b > CCmax_b && /* Find maximum biased correlation */ + CCmax_new > nb_subfr * search_thres2 /* Correlation needs to be high enough to be voiced */ + ) { + CCmax_b = CCmax_new_b; + CCmax = CCmax_new; + lag = d; + CBimax = CBimax_new; + } + } + + if( lag == -1 ) { + /* No suitable candidate found */ + silk_memset( pitch_out, 0, PE_MAX_NB_SUBFR * sizeof(opus_int) ); + *LTPCorr = 0.0f; + *lagIndex = 0; + *contourIndex = 0; + return 1; + } + + /* Output normalized correlation */ + *LTPCorr = (silk_float)( CCmax / nb_subfr ); + silk_assert( *LTPCorr >= 0.0f ); + + if( Fs_kHz > 8 ) { + /* Search in original signal */ + + /* Compensate for decimation */ + silk_assert( lag == silk_SAT16( lag ) ); + if( Fs_kHz == 12 ) { + lag = silk_RSHIFT_ROUND( silk_SMULBB( lag, 3 ), 1 ); + } else { /* Fs_kHz == 16 */ + lag = silk_LSHIFT( lag, 1 ); + } + + lag = silk_LIMIT_int( lag, min_lag, max_lag ); + start_lag = silk_max_int( lag - 2, min_lag ); + end_lag = silk_min_int( lag + 2, max_lag ); + lag_new = lag; /* to avoid undefined lag */ + CBimax = 0; /* to avoid undefined lag */ + + CCmax = -1000.0f; + + /* Calculate the correlations and energies needed in stage 3 */ + silk_P_Ana_calc_corr_st3( cross_corr_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); + silk_P_Ana_calc_energy_st3( energies_st3, frame, start_lag, sf_length, nb_subfr, complexity ); + + lag_counter = 0; + silk_assert( lag == silk_SAT16( lag ) ); + contour_bias = PE_FLATCONTOUR_BIAS / lag; + + /* Set up cbk parameters according to complexity setting and frame length */ + if( nb_subfr == PE_MAX_NB_SUBFR ) { + nb_cbk_search = (opus_int)silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + } else { + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + } + + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * Fs_kHz ]; + energy_tmp = silk_energy_FLP( target_ptr, nb_subfr * sf_length ) + 1.0; + for( d = start_lag; d <= end_lag; d++ ) { + for( j = 0; j < nb_cbk_search; j++ ) { + cross_corr = 0.0; + energy = energy_tmp; + for( k = 0; k < nb_subfr; k++ ) { + cross_corr += cross_corr_st3[ k ][ j ][ lag_counter ]; + energy += energies_st3[ k ][ j ][ lag_counter ]; + } + if( cross_corr > 0.0 ) { + CCmax_new = (silk_float)( 2 * cross_corr / energy ); + /* Reduce depending on flatness of contour */ + CCmax_new *= 1.0f - contour_bias * j; + } else { + CCmax_new = 0.0f; + } + + if( CCmax_new > CCmax && ( d + (opus_int)silk_CB_lags_stage3[ 0 ][ j ] ) <= max_lag ) { + CCmax = CCmax_new; + lag_new = d; + CBimax = j; + } + } + lag_counter++; + } + + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag_new + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], min_lag, PE_MAX_LAG_MS * Fs_kHz ); + } + *lagIndex = (opus_int16)( lag_new - min_lag ); + *contourIndex = (opus_int8)CBimax; + } else { /* Fs_kHz == 8 */ + /* Save Lags */ + for( k = 0; k < nb_subfr; k++ ) { + pitch_out[ k ] = lag + matrix_ptr( Lag_CB_ptr, k, CBimax, cbk_size ); + pitch_out[ k ] = silk_LIMIT( pitch_out[ k ], min_lag_8kHz, PE_MAX_LAG_MS * 8 ); + } + *lagIndex = (opus_int16)( lag - min_lag_8kHz ); + *contourIndex = (opus_int8)CBimax; + } + celt_assert( *lagIndex >= 0 ); + /* return as voiced */ + return 0; +} + +/*********************************************************************** + * Calculates the correlations used in stage 3 search. In order to cover + * the whole lag codebook for all the searched offset lags (lag +- 2), + * the following correlations are needed in each sub frame: + * + * sf1: lag range [-8,...,7] total 16 correlations + * sf2: lag range [-4,...,4] total 9 correlations + * sf3: lag range [-3,....4] total 8 correltions + * sf4: lag range [-6,....8] total 15 correlations + * + * In total 48 correlations. The direct implementation computed in worst + * case 4*12*5 = 240 correlations, but more likely around 120. + ***********************************************************************/ +static void silk_P_Ana_calc_corr_st3( + silk_float cross_corr_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ +) +{ + const silk_float *target_ptr; + opus_int i, j, k, lag_counter, lag_low, lag_high; + opus_int nb_cbk_search, delta, idx, cbk_size; + silk_float scratch_mem[ SCRATCH_SIZE ]; + opus_val32 xcorr[ SCRATCH_SIZE ]; + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; /* Pointer to middle of frame */ + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the correlations for each subframe */ + lag_low = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + lag_high = matrix_ptr( Lag_range_ptr, k, 1, 2 ); + silk_assert(lag_high-lag_low+1 <= SCRATCH_SIZE); + celt_pitch_xcorr( target_ptr, target_ptr - start_lag - lag_high, xcorr, sf_length, lag_high - lag_low + 1, arch ); + for( j = lag_low; j <= lag_high; j++ ) { + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[ lag_counter ] = xcorr[ lag_high - j ]; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + cross_corr_st3[ k ][ i ][ j ] = scratch_mem[ idx + j ]; + } + } + target_ptr += sf_length; + } +} + +/********************************************************************/ +/* Calculate the energies for first two subframes. The energies are */ +/* calculated recursively. */ +/********************************************************************/ +static void silk_P_Ana_calc_energy_st3( + silk_float energies_st3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ][ PE_NB_STAGE3_LAGS ], /* O 3 DIM correlation array */ + const silk_float frame[], /* I vector to correlate */ + opus_int start_lag, /* I start lag */ + opus_int sf_length, /* I sub frame length */ + opus_int nb_subfr, /* I number of subframes */ + opus_int complexity /* I Complexity setting */ +) +{ + const silk_float *target_ptr, *basis_ptr; + double energy; + opus_int k, i, j, lag_counter; + opus_int nb_cbk_search, delta, idx, cbk_size, lag_diff; + silk_float scratch_mem[ SCRATCH_SIZE ]; + const opus_int8 *Lag_range_ptr, *Lag_CB_ptr; + + celt_assert( complexity >= SILK_PE_MIN_COMPLEX ); + celt_assert( complexity <= SILK_PE_MAX_COMPLEX ); + + if( nb_subfr == PE_MAX_NB_SUBFR ) { + Lag_range_ptr = &silk_Lag_range_stage3[ complexity ][ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3[ 0 ][ 0 ]; + nb_cbk_search = silk_nb_cbk_searchs_stage3[ complexity ]; + cbk_size = PE_NB_CBKS_STAGE3_MAX; + } else { + celt_assert( nb_subfr == PE_MAX_NB_SUBFR >> 1); + Lag_range_ptr = &silk_Lag_range_stage3_10_ms[ 0 ][ 0 ]; + Lag_CB_ptr = &silk_CB_lags_stage3_10_ms[ 0 ][ 0 ]; + nb_cbk_search = PE_NB_CBKS_STAGE3_10MS; + cbk_size = PE_NB_CBKS_STAGE3_10MS; + } + + target_ptr = &frame[ silk_LSHIFT( sf_length, 2 ) ]; + for( k = 0; k < nb_subfr; k++ ) { + lag_counter = 0; + + /* Calculate the energy for first lag */ + basis_ptr = target_ptr - ( start_lag + matrix_ptr( Lag_range_ptr, k, 0, 2 ) ); + energy = silk_energy_FLP( basis_ptr, sf_length ) + 1e-3; + silk_assert( energy >= 0.0 ); + scratch_mem[lag_counter] = (silk_float)energy; + lag_counter++; + + lag_diff = ( matrix_ptr( Lag_range_ptr, k, 1, 2 ) - matrix_ptr( Lag_range_ptr, k, 0, 2 ) + 1 ); + for( i = 1; i < lag_diff; i++ ) { + /* remove part outside new window */ + energy -= basis_ptr[sf_length - i] * (double)basis_ptr[sf_length - i]; + silk_assert( energy >= 0.0 ); + + /* add part that comes into window */ + energy += basis_ptr[ -i ] * (double)basis_ptr[ -i ]; + silk_assert( energy >= 0.0 ); + silk_assert( lag_counter < SCRATCH_SIZE ); + scratch_mem[lag_counter] = (silk_float)energy; + lag_counter++; + } + + delta = matrix_ptr( Lag_range_ptr, k, 0, 2 ); + for( i = 0; i < nb_cbk_search; i++ ) { + /* Fill out the 3 dim array that stores the correlations for */ + /* each code_book vector for each start lag */ + idx = matrix_ptr( Lag_CB_ptr, k, i, cbk_size ) - delta; + for( j = 0; j < PE_NB_STAGE3_LAGS; j++ ) { + silk_assert( idx + j < SCRATCH_SIZE ); + silk_assert( idx + j < lag_counter ); + energies_st3[ k ][ i ][ j ] = scratch_mem[ idx + j ]; + silk_assert( energies_st3[ k ][ i ][ j ] >= 0.0f ); + } + } + target_ptr += sf_length; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/process_gains_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/process_gains_FLP.c new file mode 100644 index 0000000000..c0da0dae44 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/process_gains_FLP.c @@ -0,0 +1,103 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" +#include "tuning_parameters.h" + +/* Processing of gains */ +void silk_process_gains_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + opus_int condCoding /* I The type of conditional coding to use */ +) +{ + silk_shape_state_FLP *psShapeSt = &psEnc->sShape; + opus_int k; + opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; + silk_float s, InvMaxSqrVal, gain, quant_offset; + + /* Gain reduction when LTP coding gain is high */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + s = 1.0f - 0.5f * silk_sigmoid( 0.25f * ( psEncCtrl->LTPredCodGain - 12.0f ) ); + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] *= s; + } + } + + /* Limit the quantized signal */ + InvMaxSqrVal = ( silk_float )( pow( 2.0f, 0.33f * ( 21.0f - psEnc->sCmn.SNR_dB_Q7 * ( 1 / 128.0f ) ) ) / psEnc->sCmn.subfr_length ); + + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + /* Soft limit on ratio residual energy and squared gains */ + gain = psEncCtrl->Gains[ k ]; + gain = ( silk_float )sqrt( gain * gain + psEncCtrl->ResNrg[ k ] * InvMaxSqrVal ); + psEncCtrl->Gains[ k ] = silk_min_float( gain, 32767.0f ); + } + + /* Prepare gains for noise shaping quantization */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + pGains_Q16[ k ] = (opus_int32)( psEncCtrl->Gains[ k ] * 65536.0f ); + } + + /* Save unquantized gains and gain Index */ + silk_memcpy( psEncCtrl->GainsUnq_Q16, pGains_Q16, psEnc->sCmn.nb_subfr * sizeof( opus_int32 ) ); + psEncCtrl->lastGainIndexPrev = psShapeSt->LastGainIndex; + + /* Quantize gains */ + silk_gains_quant( psEnc->sCmn.indices.GainsIndices, pGains_Q16, + &psShapeSt->LastGainIndex, condCoding == CODE_CONDITIONALLY, psEnc->sCmn.nb_subfr ); + + /* Overwrite unquantized gains with quantized gains and convert back to Q0 from Q16 */ + for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { + psEncCtrl->Gains[ k ] = pGains_Q16[ k ] / 65536.0f; + } + + /* Set quantizer offset for voiced signals. Larger offset when LTP coding gain is low or tilt is high (ie low-pass) */ + if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { + if( psEncCtrl->LTPredCodGain + psEnc->sCmn.input_tilt_Q15 * ( 1.0f / 32768.0f ) > 1.0f ) { + psEnc->sCmn.indices.quantOffsetType = 0; + } else { + psEnc->sCmn.indices.quantOffsetType = 1; + } + } + + /* Quantizer boundary adjustment */ + quant_offset = silk_Quantization_Offsets_Q10[ psEnc->sCmn.indices.signalType >> 1 ][ psEnc->sCmn.indices.quantOffsetType ] / 1024.0f; + psEncCtrl->Lambda = LAMBDA_OFFSET + + LAMBDA_DELAYED_DECISIONS * psEnc->sCmn.nStatesDelayedDecision + + LAMBDA_SPEECH_ACT * psEnc->sCmn.speech_activity_Q8 * ( 1.0f / 256.0f ) + + LAMBDA_INPUT_QUALITY * psEncCtrl->input_quality + + LAMBDA_CODING_QUALITY * psEncCtrl->coding_quality + + LAMBDA_QUANT_OFFSET * quant_offset; + + silk_assert( psEncCtrl->Lambda > 0.0f ); + silk_assert( psEncCtrl->Lambda < 2.0f ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/regularize_correlations_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/regularize_correlations_FLP.c new file mode 100644 index 0000000000..df4612604c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/regularize_correlations_FLP.c @@ -0,0 +1,48 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Add noise to matrix diagonal */ +void silk_regularize_correlations_FLP( + silk_float *XX, /* I/O Correlation matrices */ + silk_float *xx, /* I/O Correlation values */ + const silk_float noise, /* I Noise energy to add */ + const opus_int D /* I Dimension of XX */ +) +{ + opus_int i; + + for( i = 0; i < D; i++ ) { + matrix_ptr( &XX[ 0 ], i, i, D ) += noise; + } + xx[ 0 ] += noise; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/residual_energy_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/residual_energy_FLP.c new file mode 100644 index 0000000000..1bd07b33a4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/residual_energy_FLP.c @@ -0,0 +1,117 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +#define MAX_ITERATIONS_RESIDUAL_NRG 10 +#define REGULARIZATION_FACTOR 1e-8f + +/* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ +silk_float silk_residual_energy_covar_FLP( /* O Weighted residual energy */ + const silk_float *c, /* I Filter coefficients */ + silk_float *wXX, /* I/O Weighted correlation matrix, reg. out */ + const silk_float *wXx, /* I Weighted correlation vector */ + const silk_float wxx, /* I Weighted correlation value */ + const opus_int D /* I Dimension */ +) +{ + opus_int i, j, k; + silk_float tmp, nrg = 0.0f, regularization; + + /* Safety checks */ + celt_assert( D >= 0 ); + + regularization = REGULARIZATION_FACTOR * ( wXX[ 0 ] + wXX[ D * D - 1 ] ); + for( k = 0; k < MAX_ITERATIONS_RESIDUAL_NRG; k++ ) { + nrg = wxx; + + tmp = 0.0f; + for( i = 0; i < D; i++ ) { + tmp += wXx[ i ] * c[ i ]; + } + nrg -= 2.0f * tmp; + + /* compute c' * wXX * c, assuming wXX is symmetric */ + for( i = 0; i < D; i++ ) { + tmp = 0.0f; + for( j = i + 1; j < D; j++ ) { + tmp += matrix_c_ptr( wXX, i, j, D ) * c[ j ]; + } + nrg += c[ i ] * ( 2.0f * tmp + matrix_c_ptr( wXX, i, i, D ) * c[ i ] ); + } + if( nrg > 0 ) { + break; + } else { + /* Add white noise */ + for( i = 0; i < D; i++ ) { + matrix_c_ptr( wXX, i, i, D ) += regularization; + } + /* Increase noise for next run */ + regularization *= 2.0f; + } + } + if( k == MAX_ITERATIONS_RESIDUAL_NRG ) { + silk_assert( nrg == 0 ); + nrg = 1.0f; + } + + return nrg; +} + +/* Calculates residual energies of input subframes where all subframes have LPC_order */ +/* of preceding samples */ +void silk_residual_energy_FLP( + silk_float nrgs[ MAX_NB_SUBFR ], /* O Residual energy per subframe */ + const silk_float x[], /* I Input signal */ + silk_float a[ 2 ][ MAX_LPC_ORDER ], /* I AR coefs for each frame half */ + const silk_float gains[], /* I Quantization gains */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I number of subframes */ + const opus_int LPC_order /* I LPC order */ +) +{ + opus_int shift; + silk_float *LPC_res_ptr, LPC_res[ ( MAX_FRAME_LENGTH + MAX_NB_SUBFR * MAX_LPC_ORDER ) / 2 ]; + + LPC_res_ptr = LPC_res + LPC_order; + shift = LPC_order + subfr_length; + + /* Filter input to create the LPC residual for each frame half, and measure subframe energies */ + silk_LPC_analysis_filter_FLP( LPC_res, a[ 0 ], x + 0 * shift, 2 * shift, LPC_order ); + nrgs[ 0 ] = ( silk_float )( gains[ 0 ] * gains[ 0 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) ); + nrgs[ 1 ] = ( silk_float )( gains[ 1 ] * gains[ 1 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) ); + + if( nb_subfr == MAX_NB_SUBFR ) { + silk_LPC_analysis_filter_FLP( LPC_res, a[ 1 ], x + 2 * shift, 2 * shift, LPC_order ); + nrgs[ 2 ] = ( silk_float )( gains[ 2 ] * gains[ 2 ] * silk_energy_FLP( LPC_res_ptr + 0 * shift, subfr_length ) ); + nrgs[ 3 ] = ( silk_float )( gains[ 3 ] * gains[ 3 ] * silk_energy_FLP( LPC_res_ptr + 1 * shift, subfr_length ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_copy_vector_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_copy_vector_FLP.c new file mode 100644 index 0000000000..20db32b3b1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_copy_vector_FLP.c @@ -0,0 +1,57 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* copy and multiply a vector by a constant */ +void silk_scale_copy_vector_FLP( + silk_float *data_out, + const silk_float *data_in, + silk_float gain, + opus_int dataSize +) +{ + opus_int i, dataSize4; + + /* 4x unrolled loop */ + dataSize4 = dataSize & 0xFFFC; + for( i = 0; i < dataSize4; i += 4 ) { + data_out[ i + 0 ] = gain * data_in[ i + 0 ]; + data_out[ i + 1 ] = gain * data_in[ i + 1 ]; + data_out[ i + 2 ] = gain * data_in[ i + 2 ]; + data_out[ i + 3 ] = gain * data_in[ i + 3 ]; + } + + /* any remaining elements */ + for( ; i < dataSize; i++ ) { + data_out[ i ] = gain * data_in[ i ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_vector_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_vector_FLP.c new file mode 100644 index 0000000000..108fdcbed5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/scale_vector_FLP.c @@ -0,0 +1,56 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +/* multiply a vector by a constant */ +void silk_scale_vector_FLP( + silk_float *data1, + silk_float gain, + opus_int dataSize +) +{ + opus_int i, dataSize4; + + /* 4x unrolled loop */ + dataSize4 = dataSize & 0xFFFC; + for( i = 0; i < dataSize4; i += 4 ) { + data1[ i + 0 ] *= gain; + data1[ i + 1 ] *= gain; + data1[ i + 2 ] *= gain; + data1[ i + 3 ] *= gain; + } + + /* any remaining elements */ + for( ; i < dataSize; i++ ) { + data1[ i ] *= gain; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/schur_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/schur_FLP.c new file mode 100644 index 0000000000..8526c748d3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/schur_FLP.c @@ -0,0 +1,70 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" + +silk_float silk_schur_FLP( /* O returns residual energy */ + silk_float refl_coef[], /* O reflection coefficients (length order) */ + const silk_float auto_corr[], /* I autocorrelation sequence (length order+1) */ + opus_int order /* I order */ +) +{ + opus_int k, n; + double C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; + double Ctmp1, Ctmp2, rc_tmp; + + celt_assert( order >= 0 && order <= SILK_MAX_ORDER_LPC ); + + /* Copy correlations */ + k = 0; + do { + C[ k ][ 0 ] = C[ k ][ 1 ] = auto_corr[ k ]; + } while( ++k <= order ); + + for( k = 0; k < order; k++ ) { + /* Get reflection coefficient */ + rc_tmp = -C[ k + 1 ][ 0 ] / silk_max_float( C[ 0 ][ 1 ], 1e-9f ); + + /* Save the output */ + refl_coef[ k ] = (silk_float)rc_tmp; + + /* Update correlations */ + for( n = 0; n < order - k; n++ ) { + Ctmp1 = C[ n + k + 1 ][ 0 ]; + Ctmp2 = C[ n ][ 1 ]; + C[ n + k + 1 ][ 0 ] = Ctmp1 + Ctmp2 * rc_tmp; + C[ n ][ 1 ] = Ctmp2 + Ctmp1 * rc_tmp; + } + } + + /* Return residual energy */ + return (silk_float)C[ 0 ][ 1 ]; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/sort_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/sort_FLP.c new file mode 100644 index 0000000000..0e18f31950 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/sort_FLP.c @@ -0,0 +1,83 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Insertion sort (fast for already almost sorted arrays): */ +/* Best case: O(n) for an already sorted array */ +/* Worst case: O(n^2) for an inversely sorted array */ + +#include "typedef.h" +#include "SigProc_FLP.h" + +void silk_insertion_sort_decreasing_FLP( + silk_float *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + silk_float value; + opus_int i, j; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, decreasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value > a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/structs_FLP.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/structs_FLP.h new file mode 100644 index 0000000000..3150b386e4 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/structs_FLP.h @@ -0,0 +1,112 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_STRUCTS_FLP_H +#define SILK_STRUCTS_FLP_H + +#include "typedef.h" +#include "main.h" +#include "structs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/********************************/ +/* Noise shaping analysis state */ +/********************************/ +typedef struct { + opus_int8 LastGainIndex; + silk_float HarmShapeGain_smth; + silk_float Tilt_smth; +} silk_shape_state_FLP; + +/********************************/ +/* Encoder state FLP */ +/********************************/ +typedef struct { + silk_encoder_state sCmn; /* Common struct, shared with fixed-point code */ + silk_shape_state_FLP sShape; /* Noise shaping state */ + + /* Buffer for find pitch and noise shape analysis */ + silk_float x_buf[ 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX ];/* Buffer for find pitch and noise shape analysis */ + silk_float LTPCorr; /* Normalized correlation from pitch lag estimator */ +} silk_encoder_state_FLP; + +/************************/ +/* Encoder control FLP */ +/************************/ +typedef struct { + /* Prediction and coding parameters */ + silk_float Gains[ MAX_NB_SUBFR ]; + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ]; /* holds interpolated and final coefficients */ + silk_float LTPCoef[LTP_ORDER * MAX_NB_SUBFR]; + silk_float LTP_scale; + opus_int pitchL[ MAX_NB_SUBFR ]; + + /* Noise shaping parameters */ + silk_float AR[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + silk_float LF_MA_shp[ MAX_NB_SUBFR ]; + silk_float LF_AR_shp[ MAX_NB_SUBFR ]; + silk_float Tilt[ MAX_NB_SUBFR ]; + silk_float HarmShapeGain[ MAX_NB_SUBFR ]; + silk_float Lambda; + silk_float input_quality; + silk_float coding_quality; + + /* Measures */ + silk_float predGain; + silk_float LTPredCodGain; + silk_float ResNrg[ MAX_NB_SUBFR ]; /* Residual energy per subframe */ + + /* Parameters for CBR mode */ + opus_int32 GainsUnq_Q16[ MAX_NB_SUBFR ]; + opus_int8 lastGainIndexPrev; +} silk_encoder_control_FLP; + +/************************/ +/* Encoder Super Struct */ +/************************/ +typedef struct { + silk_encoder_state_FLP state_Fxx[ ENCODER_NUM_CHANNELS ]; + stereo_enc_state sStereo; + opus_int32 nBitsUsedLBRR; + opus_int32 nBitsExceeded; + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int nPrevChannelsInternal; + opus_int timeSinceSwitchAllowed_ms; + opus_int allowBandwidthSwitch; + opus_int prev_decode_only_middle; +} silk_encoder; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/warped_autocorrelation_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/warped_autocorrelation_FLP.c new file mode 100644 index 0000000000..116dab923f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/warped_autocorrelation_FLP.c @@ -0,0 +1,75 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Autocorrelations for a warped frequency axis */ +void silk_warped_autocorrelation_FLP( + silk_float *corr, /* O Result [order + 1] */ + const silk_float *input, /* I Input data to correlate */ + const silk_float warping, /* I Warping coefficient */ + const opus_int length, /* I Length of input */ + const opus_int order /* I Correlation order (even) */ +) +{ + opus_int n, i; + double tmp1, tmp2; + double state[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + double C[ MAX_SHAPE_LPC_ORDER + 1 ] = { 0 }; + + /* Order must be even */ + celt_assert( ( order & 1 ) == 0 ); + + /* Loop over samples */ + for( n = 0; n < length; n++ ) { + tmp1 = input[ n ]; + /* Loop over allpass sections */ + for( i = 0; i < order; i += 2 ) { + /* Output of allpass section */ + /* We voluntarily use two multiples instead of factoring the expression to + reduce the length of the dependency chain (tmp1->tmp2->tmp1... ). */ + tmp2 = state[ i ] + warping * state[ i + 1 ] - warping * tmp1; + state[ i ] = tmp1; + C[ i ] += state[ 0 ] * tmp1; + /* Output of allpass section */ + tmp1 = state[ i + 1 ] + warping * state[ i + 2 ] - warping * tmp2; + state[ i + 1 ] = tmp2; + C[ i + 1 ] += state[ 0 ] * tmp2; + } + state[ order ] = tmp1; + C[ order ] += state[ 0 ] * tmp1; + } + + /* Copy correlations in silk_float output format */ + for( i = 0; i < order + 1; i++ ) { + corr[ i ] = ( silk_float )C[ i ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/wrappers_FLP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/wrappers_FLP.c new file mode 100644 index 0000000000..c0c183e35a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/wrappers_FLP.c @@ -0,0 +1,209 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main_FLP.h" + +/* Wrappers. Calls flp / fix code */ + +/* Convert AR filter coefficients to NLSF parameters */ +void silk_A2NLSF_FLP( + opus_int16 *NLSF_Q15, /* O NLSF vector [ LPC_order ] */ + const silk_float *pAR, /* I LPC coefficients [ LPC_order ] */ + const opus_int LPC_order /* I LPC order */ +) +{ + opus_int i; + opus_int32 a_fix_Q16[ MAX_LPC_ORDER ]; + + for( i = 0; i < LPC_order; i++ ) { + a_fix_Q16[ i ] = silk_float2int( pAR[ i ] * 65536.0f ); + } + + silk_A2NLSF( NLSF_Q15, a_fix_Q16, LPC_order ); +} + +/* Convert LSF parameters to AR prediction filter coefficients */ +void silk_NLSF2A_FLP( + silk_float *pAR, /* O LPC coefficients [ LPC_order ] */ + const opus_int16 *NLSF_Q15, /* I NLSF vector [ LPC_order ] */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ +) +{ + opus_int i; + opus_int16 a_fix_Q12[ MAX_LPC_ORDER ]; + + silk_NLSF2A( a_fix_Q12, NLSF_Q15, LPC_order, arch ); + + for( i = 0; i < LPC_order; i++ ) { + pAR[ i ] = ( silk_float )a_fix_Q12[ i ] * ( 1.0f / 4096.0f ); + } +} + +/******************************************/ +/* Floating-point NLSF processing wrapper */ +/******************************************/ +void silk_process_NLSFs_FLP( + silk_encoder_state *psEncC, /* I/O Encoder state */ + silk_float PredCoef[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 NLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSF_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +) +{ + opus_int i, j; + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + + silk_process_NLSFs( psEncC, PredCoef_Q12, NLSF_Q15, prev_NLSF_Q15); + + for( j = 0; j < 2; j++ ) { + for( i = 0; i < psEncC->predictLPCOrder; i++ ) { + PredCoef[ j ][ i ] = ( silk_float )PredCoef_Q12[ j ][ i ] * ( 1.0f / 4096.0f ); + } + } +} + +/****************************************/ +/* Floating-point Silk NSQ wrapper */ +/****************************************/ +void silk_NSQ_wrapper_FLP( + silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ + silk_encoder_control_FLP *psEncCtrl, /* I/O Encoder control FLP */ + SideInfoIndices *psIndices, /* I/O Quantization indices */ + silk_nsq_state *psNSQ, /* I/O Noise Shaping Quantzation state */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const silk_float x[] /* I Prefiltered input signal */ +) +{ + opus_int i, j; + opus_int16 x16[ MAX_FRAME_LENGTH ]; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; + opus_int LTP_scale_Q14; + + /* Noise shaping parameters */ + opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ]; /* Packs two int16 coefficients per int32 value */ + opus_int Lambda_Q10; + opus_int Tilt_Q14[ MAX_NB_SUBFR ]; + opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ]; + + /* Convert control struct to fix control struct */ + /* Noise shape parameters */ + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + for( j = 0; j < psEnc->sCmn.shapingLPCOrder; j++ ) { + AR_Q13[ i * MAX_SHAPE_LPC_ORDER + j ] = silk_float2int( psEncCtrl->AR[ i * MAX_SHAPE_LPC_ORDER + j ] * 8192.0f ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + LF_shp_Q14[ i ] = silk_LSHIFT32( silk_float2int( psEncCtrl->LF_AR_shp[ i ] * 16384.0f ), 16 ) | + (opus_uint16)silk_float2int( psEncCtrl->LF_MA_shp[ i ] * 16384.0f ); + Tilt_Q14[ i ] = (opus_int)silk_float2int( psEncCtrl->Tilt[ i ] * 16384.0f ); + HarmShapeGain_Q14[ i ] = (opus_int)silk_float2int( psEncCtrl->HarmShapeGain[ i ] * 16384.0f ); + } + Lambda_Q10 = ( opus_int )silk_float2int( psEncCtrl->Lambda * 1024.0f ); + + /* prediction and coding parameters */ + for( i = 0; i < psEnc->sCmn.nb_subfr * LTP_ORDER; i++ ) { + LTPCoef_Q14[ i ] = (opus_int16)silk_float2int( psEncCtrl->LTPCoef[ i ] * 16384.0f ); + } + + for( j = 0; j < 2; j++ ) { + for( i = 0; i < psEnc->sCmn.predictLPCOrder; i++ ) { + PredCoef_Q12[ j ][ i ] = (opus_int16)silk_float2int( psEncCtrl->PredCoef[ j ][ i ] * 4096.0f ); + } + } + + for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + Gains_Q16[ i ] = silk_float2int( psEncCtrl->Gains[ i ] * 65536.0f ); + silk_assert( Gains_Q16[ i ] > 0 ); + } + + if( psIndices->signalType == TYPE_VOICED ) { + LTP_scale_Q14 = silk_LTPScales_table_Q14[ psIndices->LTP_scaleIndex ]; + } else { + LTP_scale_Q14 = 0; + } + + /* Convert input to fix */ + for( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + x16[ i ] = silk_float2int( x[ i ] ); + } + + /* Call NSQ */ + if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { + silk_NSQ_del_dec( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); + } else { + silk_NSQ( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); + } +} + +/***********************************************/ +/* Floating-point Silk LTP quantiation wrapper */ +/***********************************************/ +void silk_quant_LTP_gains_FLP( + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ + opus_int8 *periodicity_index, /* O Periodicity index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int i, pred_gain_dB_Q7; + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ]; + opus_int32 XX_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + opus_int32 xX_Q17[ MAX_NB_SUBFR * LTP_ORDER ]; + + i = 0; + do { + XX_Q17[ i ] = (opus_int32)silk_float2int( XX[ i ] * 131072.0f ); + } while ( ++i < nb_subfr * LTP_ORDER * LTP_ORDER ); + i = 0; + do { + xX_Q17[ i ] = (opus_int32)silk_float2int( xX[ i ] * 131072.0f ); + } while ( ++i < nb_subfr * LTP_ORDER ); + + silk_quant_LTP_gains( B_Q14, cbk_index, periodicity_index, sum_log_gain_Q7, &pred_gain_dB_Q7, XX_Q17, xX_Q17, subfr_len, nb_subfr, arch ); + + for( i = 0; i < nb_subfr * LTP_ORDER; i++ ) { + B[ i ] = (silk_float)B_Q14[ i ] * ( 1.0f / 16384.0f ); + } + + *pred_gain_dB = (silk_float)pred_gain_dB_Q7 * ( 1.0f / 128.0f ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/x86/inner_product_FLP_avx2.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/x86/inner_product_FLP_avx2.c new file mode 100644 index 0000000000..4a2daaf595 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/float/x86/inner_product_FLP_avx2.c @@ -0,0 +1,85 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. + 2023 Amazon +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FLP.h" +#include + + +/* inner product of two silk_float arrays, with result as double */ +double silk_inner_product_FLP_avx2( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +) +{ + opus_int i; + __m256d accum1, accum2; + double result; + + /* 4x unrolled loop */ + result = 0.0; + accum1 = accum2 = _mm256_setzero_pd(); + for( i = 0; i < dataSize - 7; i += 8 ) { + __m128 x1f, x2f; + __m256d x1d, x2d; + x1f = _mm_loadu_ps( &data1[ i ] ); + x2f = _mm_loadu_ps( &data2[ i ] ); + x1d = _mm256_cvtps_pd( x1f ); + x2d = _mm256_cvtps_pd( x2f ); + accum1 = _mm256_fmadd_pd( x1d, x2d, accum1 ); + x1f = _mm_loadu_ps( &data1[ i + 4 ] ); + x2f = _mm_loadu_ps( &data2[ i + 4 ] ); + x1d = _mm256_cvtps_pd( x1f ); + x2d = _mm256_cvtps_pd( x2f ); + accum2 = _mm256_fmadd_pd( x1d, x2d, accum2 ); + } + for( ; i < dataSize - 3; i += 4 ) { + __m128 x1f, x2f; + __m256d x1d, x2d; + x1f = _mm_loadu_ps( &data1[ i ] ); + x2f = _mm_loadu_ps( &data2[ i ] ); + x1d = _mm256_cvtps_pd( x1f ); + x2d = _mm256_cvtps_pd( x2f ); + accum1 = _mm256_fmadd_pd( x1d, x2d, accum1 ); + } + accum1 = _mm256_add_pd(accum1, accum2); + accum1 = _mm256_add_pd(accum1, _mm256_permute2f128_pd(accum1, accum1, 1)); + accum1 = _mm256_hadd_pd(accum1,accum1); + result = _mm256_cvtsd_f64(accum1); + + /* add any remaining products */ + for( ; i < dataSize; i++ ) { + result += data1[ i ] * (double)data2[ i ]; + } + + return result; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/gain_quant.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/gain_quant.c new file mode 100644 index 0000000000..ee65245aa3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/gain_quant.c @@ -0,0 +1,142 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +#define OFFSET ( ( MIN_QGAIN_DB * 128 ) / 6 + 16 * 128 ) +#define SCALE_Q16 ( ( 65536 * ( N_LEVELS_QGAIN - 1 ) ) / ( ( ( MAX_QGAIN_DB - MIN_QGAIN_DB ) * 128 ) / 6 ) ) +#define INV_SCALE_Q16 ( ( 65536 * ( ( ( MAX_QGAIN_DB - MIN_QGAIN_DB ) * 128 ) / 6 ) ) / ( N_LEVELS_QGAIN - 1 ) ) + +/* Gain scalar quantization with hysteresis, uniform on log scale */ +void silk_gains_quant( + opus_int8 ind[ MAX_NB_SUBFR ], /* O gain indices */ + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* I/O gains (quantized out) */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k, double_step_size_threshold; + + for( k = 0; k < nb_subfr; k++ ) { + /* Convert to log scale, scale, floor() */ + ind[ k ] = silk_SMULWB( SCALE_Q16, silk_lin2log( gain_Q16[ k ] ) - OFFSET ); + + /* Round towards previous quantized gain (hysteresis) */ + if( ind[ k ] < *prev_ind ) { + ind[ k ]++; + } + ind[ k ] = silk_LIMIT_int( ind[ k ], 0, N_LEVELS_QGAIN - 1 ); + + /* Compute delta indices and limit */ + if( k == 0 && conditional == 0 ) { + /* Full index */ + ind[ k ] = silk_LIMIT_int( ind[ k ], *prev_ind + MIN_DELTA_GAIN_QUANT, N_LEVELS_QGAIN - 1 ); + *prev_ind = ind[ k ]; + } else { + /* Delta index */ + ind[ k ] = ind[ k ] - *prev_ind; + + /* Double the quantization step size for large gain increases, so that the max gain level can be reached */ + double_step_size_threshold = 2 * MAX_DELTA_GAIN_QUANT - N_LEVELS_QGAIN + *prev_ind; + if( ind[ k ] > double_step_size_threshold ) { + ind[ k ] = double_step_size_threshold + silk_RSHIFT( ind[ k ] - double_step_size_threshold + 1, 1 ); + } + + ind[ k ] = silk_LIMIT_int( ind[ k ], MIN_DELTA_GAIN_QUANT, MAX_DELTA_GAIN_QUANT ); + + /* Accumulate deltas */ + if( ind[ k ] > double_step_size_threshold ) { + *prev_ind += silk_LSHIFT( ind[ k ], 1 ) - double_step_size_threshold; + *prev_ind = silk_min_int( *prev_ind, N_LEVELS_QGAIN - 1 ); + } else { + *prev_ind += ind[ k ]; + } + + /* Shift to make non-negative */ + ind[ k ] -= MIN_DELTA_GAIN_QUANT; + } + + /* Scale and convert to linear scale */ + gain_Q16[ k ] = silk_log2lin( silk_min_32( silk_SMULWB( INV_SCALE_Q16, *prev_ind ) + OFFSET, 3967 ) ); /* 3967 = 31 in Q7 */ + } +} + +/* Gains scalar dequantization, uniform on log scale */ +void silk_gains_dequant( + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* O quantized gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k, ind_tmp, double_step_size_threshold; + + for( k = 0; k < nb_subfr; k++ ) { + if( k == 0 && conditional == 0 ) { + /* Gain index is not allowed to go down more than 16 steps (~21.8 dB) */ + *prev_ind = silk_max_int( ind[ k ], *prev_ind - 16 ); + } else { + /* Delta index */ + ind_tmp = ind[ k ] + MIN_DELTA_GAIN_QUANT; + + /* Accumulate deltas */ + double_step_size_threshold = 2 * MAX_DELTA_GAIN_QUANT - N_LEVELS_QGAIN + *prev_ind; + if( ind_tmp > double_step_size_threshold ) { + *prev_ind += silk_LSHIFT( ind_tmp, 1 ) - double_step_size_threshold; + } else { + *prev_ind += ind_tmp; + } + } + *prev_ind = silk_LIMIT_int( *prev_ind, 0, N_LEVELS_QGAIN - 1 ); + + /* Scale and convert to linear scale */ + gain_Q16[ k ] = silk_log2lin( silk_min_32( silk_SMULWB( INV_SCALE_Q16, *prev_ind ) + OFFSET, 3967 ) ); /* 3967 = 31 in Q7 */ + } +} + +/* Compute unique identifier of gain indices vector */ +opus_int32 silk_gains_ID( /* O returns unique identifier of gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + const opus_int nb_subfr /* I number of subframes */ +) +{ + opus_int k; + opus_int32 gainsID; + + gainsID = 0; + for( k = 0; k < nb_subfr; k++ ) { + gainsID = silk_ADD_LSHIFT32( ind[ k ], gainsID, 8 ); + } + + return gainsID; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_decoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_decoder.c new file mode 100644 index 0000000000..01bc4b7a12 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_decoder.c @@ -0,0 +1,84 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +#include "structs.h" + +/************************/ +/* Reset Decoder State */ +/************************/ +opus_int silk_reset_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +) +{ + /* Clear the entire encoder state, except anything copied */ + silk_memset( &psDec->SILK_DECODER_STATE_RESET_START, 0, sizeof( silk_decoder_state ) - ((char*) &psDec->SILK_DECODER_STATE_RESET_START - (char*)psDec) ); + + /* Used to deactivate LSF interpolation */ + psDec->first_frame_after_reset = 1; + psDec->prev_gain_Q16 = 65536; + psDec->arch = opus_select_arch(); + + /* Reset CNG state */ + silk_CNG_Reset( psDec ); + + /* Reset PLC state */ + silk_PLC_Reset( psDec ); + +#ifdef ENABLE_OSCE + /* Reset OSCE state and method */ + osce_reset(&psDec->osce, OSCE_DEFAULT_METHOD); +#endif + + return 0; +} + + +/************************/ +/* Init Decoder State */ +/************************/ +opus_int silk_init_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +) +{ + /* Clear the entire encoder state, except anything copied */ + silk_memset( psDec, 0, sizeof( silk_decoder_state ) ); + + silk_reset_decoder( psDec ); + + return(0); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_encoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_encoder.c new file mode 100644 index 0000000000..10d41287fe --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/init_encoder.c @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef FIXED_POINT +#include "main_FIX.h" +#else +#include "main_FLP.h" +#endif +#include "tuning_parameters.h" +#include "cpu_support.h" + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#endif + +/*********************************/ +/* Initialize Silk Encoder state */ +/*********************************/ +opus_int silk_init_encoder( + silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk FIX encoder state */ + int arch /* I Run-time architecture */ +) +{ + opus_int ret = 0; + + /* Clear the entire encoder state */ + silk_memset( psEnc, 0, sizeof( silk_encoder_state_Fxx ) ); + + psEnc->sCmn.arch = arch; + + psEnc->sCmn.variable_HP_smth1_Q15 = silk_LSHIFT( silk_lin2log( SILK_FIX_CONST( VARIABLE_HP_MIN_CUTOFF_HZ, 16 ) ) - ( 16 << 7 ), 8 ); + psEnc->sCmn.variable_HP_smth2_Q15 = psEnc->sCmn.variable_HP_smth1_Q15; + + /* Used to deactivate LSF interpolation, pitch prediction */ + psEnc->sCmn.first_frame_after_reset = 1; + + /* Initialize Silk VAD */ + ret += silk_VAD_Init( &psEnc->sCmn.sVAD ); + + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/inner_prod_aligned.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/inner_prod_aligned.c new file mode 100644 index 0000000000..257ae9e04e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/inner_prod_aligned.c @@ -0,0 +1,47 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +opus_int32 silk_inner_prod_aligned_scale( + const opus_int16 *const inVec1, /* I input vector 1 */ + const opus_int16 *const inVec2, /* I input vector 2 */ + const opus_int scale, /* I number of bits to shift */ + const opus_int len /* I vector lengths */ +) +{ + opus_int i; + opus_int32 sum = 0; + for( i = 0; i < len; i++ ) { + sum = silk_ADD_RSHIFT32( sum, silk_SMULBB( inVec1[ i ], inVec2[ i ] ), scale ); + } + return sum; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/interpolate.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/interpolate.c new file mode 100644 index 0000000000..833c28ef8e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/interpolate.c @@ -0,0 +1,51 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Interpolate two vectors */ +void silk_interpolate( + opus_int16 xi[ MAX_LPC_ORDER ], /* O interpolated vector */ + const opus_int16 x0[ MAX_LPC_ORDER ], /* I first vector */ + const opus_int16 x1[ MAX_LPC_ORDER ], /* I second vector */ + const opus_int ifact_Q2, /* I interp. factor, weight on 2nd vector */ + const opus_int d /* I number of parameters */ +) +{ + opus_int i; + + celt_assert( ifact_Q2 >= 0 ); + celt_assert( ifact_Q2 <= 4 ); + + for( i = 0; i < d; i++ ) { + xi[ i ] = (opus_int16)silk_ADD_RSHIFT( x0[ i ], silk_SMULBB( x1[ i ] - x0[ i ], ifact_Q2 ), 2 ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/lin2log.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/lin2log.c new file mode 100644 index 0000000000..0d5155aa86 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/lin2log.c @@ -0,0 +1,46 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +/* Approximation of 128 * log2() (very close inverse of silk_log2lin()) */ +/* Convert input to a log scale */ +opus_int32 silk_lin2log( + const opus_int32 inLin /* I input in linear scale */ +) +{ + opus_int32 lz, frac_Q7; + + silk_CLZ_FRAC( inLin, &lz, &frac_Q7 ); + + /* Piece-wise parabolic approximation */ + return silk_ADD_LSHIFT32( silk_SMLAWB( frac_Q7, silk_MUL( frac_Q7, 128 - frac_Q7 ), 179 ), 31 - lz, 7 ); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/log2lin.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/log2lin.c new file mode 100644 index 0000000000..b7c48e4740 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/log2lin.c @@ -0,0 +1,58 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Approximation of 2^() (very close inverse of silk_lin2log()) */ +/* Convert input to a linear scale */ +opus_int32 silk_log2lin( + const opus_int32 inLog_Q7 /* I input on log scale */ +) +{ + opus_int32 out, frac_Q7; + + if( inLog_Q7 < 0 ) { + return 0; + } else if ( inLog_Q7 >= 3967 ) { + return silk_int32_MAX; + } + + out = silk_LSHIFT( 1, silk_RSHIFT( inLog_Q7, 7 ) ); + frac_Q7 = inLog_Q7 & 0x7F; + if( inLog_Q7 < 2048 ) { + /* Piece-wise parabolic approximation */ + out = silk_ADD_RSHIFT32( out, silk_MUL( out, silk_SMLAWB( frac_Q7, silk_SMULBB( frac_Q7, 128 - frac_Q7 ), -174 ) ), 7 ); + } else { + /* Piece-wise parabolic approximation */ + out = silk_MLA( out, silk_RSHIFT( out, 7 ), silk_SMLAWB( frac_Q7, silk_SMULBB( frac_Q7, 128 - frac_Q7 ), -174 ) ); + } + return out; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/macros.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/macros.h new file mode 100644 index 0000000000..3c67b6e5d9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/macros.h @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MACROS_H +#define SILK_MACROS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" + +/* This is an OPUS_INLINE header file for general platform. */ + +/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMULWB(a32, b32) ((opus_int32)(((a32) * (opus_int64)((opus_int16)(b32))) >> 16)) +#else +#define silk_SMULWB(a32, b32) ((((a32) >> 16) * (opus_int32)((opus_int16)(b32))) + ((((a32) & 0x0000FFFF) * (opus_int32)((opus_int16)(b32))) >> 16)) +#endif + +/* a32 + (b32 * (opus_int32)((opus_int16)(c32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMLAWB(a32, b32, c32) ((opus_int32)((a32) + (((b32) * (opus_int64)((opus_int16)(c32))) >> 16))) +#else +#define silk_SMLAWB(a32, b32, c32) ((a32) + ((((b32) >> 16) * (opus_int32)((opus_int16)(c32))) + ((((b32) & 0x0000FFFF) * (opus_int32)((opus_int16)(c32))) >> 16))) +#endif + +/* (a32 * (b32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWT(a32, b32) ((opus_int32)(((a32) * (opus_int64)((b32) >> 16)) >> 16)) +#else +#define silk_SMULWT(a32, b32) (((a32) >> 16) * ((b32) >> 16) + ((((a32) & 0x0000FFFF) * ((b32) >> 16)) >> 16)) +#endif + +/* a32 + (b32 * (c32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMLAWT(a32, b32, c32) ((opus_int32)((a32) + (((b32) * ((opus_int64)(c32) >> 16)) >> 16))) +#else +#define silk_SMLAWT(a32, b32, c32) ((a32) + (((b32) >> 16) * ((c32) >> 16)) + ((((b32) & 0x0000FFFF) * ((c32) >> 16)) >> 16)) +#endif + +/* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */ +#define silk_SMULBB(a32, b32) ((opus_int32)((opus_int16)(a32)) * (opus_int32)((opus_int16)(b32))) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (opus_int32)((opus_int16)(c32)) output have to be 32bit int */ +#define silk_SMLABB(a32, b32, c32) ((a32) + ((opus_int32)((opus_int16)(b32))) * (opus_int32)((opus_int16)(c32))) + +/* (opus_int32)((opus_int16)(a32)) * (b32 >> 16) */ +#define silk_SMULBT(a32, b32) ((opus_int32)((opus_int16)(a32)) * ((b32) >> 16)) + +/* a32 + (opus_int32)((opus_int16)(b32)) * (c32 >> 16) */ +#define silk_SMLABT(a32, b32, c32) ((a32) + ((opus_int32)((opus_int16)(b32))) * ((c32) >> 16)) + +/* a64 + (b32 * c32) */ +#define silk_SMLAL(a64, b32, c32) (silk_ADD64((a64), ((opus_int64)(b32) * (opus_int64)(c32)))) + +/* (a32 * b32) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWW(a32, b32) ((opus_int32)(((opus_int64)(a32) * (b32)) >> 16)) +#else +#define silk_SMULWW(a32, b32) silk_MLA(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)) +#endif + +/* a32 + ((b32 * c32) >> 16) */ +#if OPUS_FAST_INT64 +#define silk_SMLAWW(a32, b32, c32) ((opus_int32)((a32) + (((opus_int64)(b32) * (c32)) >> 16))) +#else +#define silk_SMLAWW(a32, b32, c32) silk_MLA(silk_SMLAWB((a32), (b32), (c32)), (b32), silk_RSHIFT_ROUND((c32), 16)) +#endif + +/* add/subtract with output saturated */ +#define silk_ADD_SAT32(a, b) ((((opus_uint32)(a) + (opus_uint32)(b)) & 0x80000000) == 0 ? \ + ((((a) & (b)) & 0x80000000) != 0 ? silk_int32_MIN : (a)+(b)) : \ + ((((a) | (b)) & 0x80000000) == 0 ? silk_int32_MAX : (a)+(b)) ) + +#define silk_SUB_SAT32(a, b) ((((opus_uint32)(a)-(opus_uint32)(b)) & 0x80000000) == 0 ? \ + (( (a) & ((b)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a)-(b)) : \ + ((((a)^0x80000000) & (b) & 0x80000000) ? silk_int32_MAX : (a)-(b)) ) + +#if defined(MIPSr1_ASM) +#include "mips/macros_mipsr1.h" +#endif + +#include "ecintrin.h" +#ifndef OVERRIDE_silk_CLZ16 +static OPUS_INLINE opus_int32 silk_CLZ16(opus_int16 in16) +{ + return 32 - EC_ILOG(in16<<16|0x8000); +} +#endif + +#ifndef OVERRIDE_silk_CLZ32 +static OPUS_INLINE opus_int32 silk_CLZ32(opus_int32 in32) +{ + return in32 ? 32 - EC_ILOG(in32) : 32; +} +#endif + +/* Row based */ +#define matrix_ptr(Matrix_base_adr, row, column, N) \ + (*((Matrix_base_adr) + ((row)*(N)+(column)))) +#define matrix_adr(Matrix_base_adr, row, column, N) \ + ((Matrix_base_adr) + ((row)*(N)+(column))) + +/* Column based */ +#ifndef matrix_c_ptr +# define matrix_c_ptr(Matrix_base_adr, row, column, M) \ + (*((Matrix_base_adr) + ((row)+(M)*(column)))) +#endif + +#ifdef OPUS_ARM_INLINE_ASM +#include "arm/macros_armv4.h" +#endif + +#ifdef OPUS_ARM_INLINE_EDSP +#include "arm/macros_armv5e.h" +#endif + +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/macros_arm64.h" +#endif + +#endif /* SILK_MACROS_H */ + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/main.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/main.h new file mode 100644 index 0000000000..cd576d8cc1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/main.h @@ -0,0 +1,486 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MAIN_H +#define SILK_MAIN_H + +#include "SigProc_FIX.h" +#include "define.h" +#include "structs.h" +#include "tables.h" +#include "PLC.h" +#include "control.h" +#include "debug.h" +#include "entenc.h" +#include "entdec.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/main_sse.h" +#endif + +#if (defined(OPUS_ARM_ASM) || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) +#include "arm/NSQ_del_dec_arm.h" +#endif + +/* Convert Left/Right stereo signal to adaptive Mid/Side representation */ +void silk_stereo_LR_to_MS( + stereo_enc_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + opus_int8 ix[ 2 ][ 3 ], /* O Quantization indices */ + opus_int8 *mid_only_flag, /* O Flag: only mid signal coded */ + opus_int32 mid_side_rates_bps[], /* O Bitrates for mid and side signals */ + opus_int32 total_rate_bps, /* I Total bitrate */ + opus_int prev_speech_act_Q8, /* I Speech activity level in previous frame */ + opus_int toMono, /* I Last frame before a stereo->mono transition */ + opus_int fs_kHz, /* I Sample rate (kHz) */ + opus_int frame_length /* I Number of samples */ +); + +/* Convert adaptive Mid/Side representation to Left/Right stereo signal */ +void silk_stereo_MS_to_LR( + stereo_dec_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + const opus_int32 pred_Q13[], /* I Predictors */ + opus_int fs_kHz, /* I Samples rate (kHz) */ + opus_int frame_length /* I Number of samples */ +); + +/* Find least-squares prediction gain for one signal based on another and quantize it */ +opus_int32 silk_stereo_find_predictor( /* O Returns predictor in Q13 */ + opus_int32 *ratio_Q14, /* O Ratio of residual and mid energies */ + const opus_int16 x[], /* I Basis signal */ + const opus_int16 y[], /* I Target signal */ + opus_int32 mid_res_amp_Q0[], /* I/O Smoothed mid, residual norms */ + opus_int length, /* I Number of samples */ + opus_int smooth_coef_Q16 /* I Smoothing coefficient */ +); + +/* Quantize mid/side predictors */ +void silk_stereo_quant_pred( + opus_int32 pred_Q13[], /* I/O Predictors (out: quantized) */ + opus_int8 ix[ 2 ][ 3 ] /* O Quantization indices */ +); + +/* Entropy code the mid/side quantization indices */ +void silk_stereo_encode_pred( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 ix[ 2 ][ 3 ] /* I Quantization indices */ +); + +/* Entropy code the mid-only flag */ +void silk_stereo_encode_mid_only( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 mid_only_flag +); + +/* Decode mid/side predictors */ +void silk_stereo_decode_pred( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int32 pred_Q13[] /* O Predictors */ +); + +/* Decode mid-only flag */ +void silk_stereo_decode_mid_only( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int *decode_only_mid /* O Flag that only mid channel has been coded */ +); + +/* Encodes signs of excitation */ +void silk_encode_signs( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + const opus_int8 pulses[], /* I pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +); + +/* Decodes signs of excitation */ +void silk_decode_signs( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* I/O pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ +); + +/* Check encoder control struct */ +opus_int check_control_input( + silk_EncControlStruct *encControl /* I Control structure */ +); + +/* Control internal sampling rate */ +opus_int silk_control_audio_bandwidth( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + silk_EncControlStruct *encControl /* I Control structure */ +); + +/* Control SNR of redidual quantizer */ +opus_int silk_control_SNR( + silk_encoder_state *psEncC, /* I/O Pointer to Silk encoder state */ + opus_int32 TargetRate_bps /* I Target max bitrate (bps) */ +); + +/***************/ +/* Shell coder */ +/***************/ + +/* Encode quantization indices of excitation */ +void silk_encode_pulses( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I quantOffsetType */ + opus_int8 pulses[], /* I quantization indices */ + const opus_int frame_length /* I Frame length */ +); + +/* Shell encoder, operates on one shell code frame of 16 pulses */ +void silk_shell_encoder( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int *pulses0 /* I data: nonnegative pulse amplitudes */ +); + +/* Shell decoder, operates on one shell code frame of 16 pulses */ +void silk_shell_decoder( + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int pulses4 /* I number of pulses per pulse-subframe */ +); + +/* Gain scalar quantization with hysteresis, uniform on log scale */ +void silk_gains_quant( + opus_int8 ind[ MAX_NB_SUBFR ], /* O gain indices */ + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* I/O gains (quantized out) */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Gains scalar dequantization, uniform on log scale */ +void silk_gains_dequant( + opus_int32 gain_Q16[ MAX_NB_SUBFR ], /* O quantized gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + opus_int8 *prev_ind, /* I/O last index in previous frame */ + const opus_int conditional, /* I first gain is delta coded if 1 */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Compute unique identifier of gain indices vector */ +opus_int32 silk_gains_ID( /* O returns unique identifier of gains */ + const opus_int8 ind[ MAX_NB_SUBFR ], /* I gain indices */ + const opus_int nb_subfr /* I number of subframes */ +); + +/* Interpolate two vectors */ +void silk_interpolate( + opus_int16 xi[ MAX_LPC_ORDER ], /* O interpolated vector */ + const opus_int16 x0[ MAX_LPC_ORDER ], /* I first vector */ + const opus_int16 x1[ MAX_LPC_ORDER ], /* I second vector */ + const opus_int ifact_Q2, /* I interp. factor, weight on 2nd vector */ + const opus_int d /* I number of parameters */ +); + +/* LTP tap quantizer */ +void silk_quant_LTP_gains( + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ + opus_int8 *periodicity_index, /* O Periodicity Index */ + opus_int32 *sum_gain_dB_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +); + +/* Entropy constrained matrix-weighted VQ, for a single input data vector */ +void silk_VQ_WMat_EC_c( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +); + +#if !defined(OVERRIDE_silk_VQ_WMat_EC) +#define silk_VQ_WMat_EC(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L, arch) \ + ((void)(arch),silk_VQ_WMat_EC_c(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L)) +#endif + +/************************************/ +/* Noise shaping quantization (NSQ) */ +/************************************/ + +void silk_NSQ_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if !defined(OVERRIDE_silk_NSQ) +#define silk_NSQ(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + +/* Noise shaping using delayed decision */ +void silk_NSQ_del_dec_c( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if !defined(OVERRIDE_silk_NSQ_del_dec) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + +/************/ +/* Silk VAD */ +/************/ +/* Initialize the Silk VAD */ +opus_int silk_VAD_Init( /* O Return value, 0 if success */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); + +/* Get speech activity level in Q8 */ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +); + +#if !defined(OVERRIDE_silk_VAD_GetSA_Q8) +#define silk_VAD_GetSA_Q8(psEnC, pIn, arch) ((void)(arch),silk_VAD_GetSA_Q8_c(psEnC, pIn)) +#endif + +/* Low-pass filter with variable cutoff frequency based on */ +/* piece-wise linear interpolation between elliptic filters */ +/* Start by setting transition_frame_no = 1; */ +void silk_LP_variable_cutoff( + silk_LP_state *psLP, /* I/O LP filter state */ + opus_int16 *frame, /* I/O Low-pass filtered output signal */ + const opus_int frame_length /* I Frame length */ +); + +/******************/ +/* NLSF Quantizer */ +/******************/ +/* Limit, stabilize, convert and quantize NLSFs */ +void silk_process_NLSFs( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +); + +opus_int32 silk_NLSF_encode( /* O Returns RD value in Q25 */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + opus_int16 *pNLSF_Q15, /* I/O Quantized NLSF vector [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int16 *pW_QW, /* I NLSF weight vector [ LPC_ORDER ] */ + const opus_int NLSF_mu_Q20, /* I Rate weight for the RD optimization */ + const opus_int nSurvivors, /* I Max survivors after first stage */ + const opus_int signalType /* I Signal type: 0/1/2 */ +); + +/* Compute quantization errors for an LPC_order element input vector for a VQ codebook */ +void silk_NLSF_VQ( + opus_int32 err_Q26[], /* O Quantization errors [K] */ + const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ + const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ + const opus_int K, /* I Number of codebook vectors */ + const opus_int LPC_order /* I Number of LPCs */ +); + +/* Delayed-decision quantizer for NLSF residuals */ +opus_int32 silk_NLSF_del_dec_quant( /* O Returns RD value in Q25 */ + opus_int8 indices[], /* O Quantization indices [ order ] */ + const opus_int16 x_Q10[], /* I Input [ order ] */ + const opus_int16 w_Q5[], /* I Weights [ order ] */ + const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ + const opus_int16 ec_ix[], /* I Indices to entropy coding tables [ order ] */ + const opus_uint8 ec_rates_Q5[], /* I Rates [] */ + const opus_int quant_step_size_Q16, /* I Quantization step size */ + const opus_int16 inv_quant_step_size_Q6, /* I Inverse quantization step size */ + const opus_int32 mu_Q20, /* I R/D tradeoff */ + const opus_int16 order /* I Number of input values */ +); + +/* Unpack predictor values and indices for entropy coding tables */ +void silk_NLSF_unpack( + opus_int16 ec_ix[], /* O Indices to entropy tables [ LPC_ORDER ] */ + opus_uint8 pred_Q8[], /* O LSF predictor [ LPC_ORDER ] */ + const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ + const opus_int CB1_index /* I Index of vector in first LSF codebook */ +); + +/***********************/ +/* NLSF vector decoder */ +/***********************/ +void silk_NLSF_decode( + opus_int16 *pNLSF_Q15, /* O Quantized NLSF vector [ LPC_ORDER ] */ + opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ + const silk_NLSF_CB_struct *psNLSF_CB /* I Codebook object */ +); + +/****************************************************/ +/* Decoder Functions */ +/****************************************************/ +opus_int silk_reset_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +); + +opus_int silk_init_decoder( + silk_decoder_state *psDec /* I/O Decoder state pointer */ +); + +/* Set decoder sampling rate */ +opus_int silk_decoder_set_fs( + silk_decoder_state *psDec, /* I/O Decoder state pointer */ + opus_int fs_kHz, /* I Sampling frequency (kHz) */ + opus_int32 fs_API_Hz /* I API Sampling frequency (Hz) */ +); + +/****************/ +/* Decode frame */ +/****************/ +opus_int silk_decode_frame( + silk_decoder_state *psDec, /* I/O Pointer to Silk decoder state */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pOut[], /* O Pointer to output speech frame */ + opus_int32 *pN, /* O Pointer to size of output frame */ + opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ + opus_int condCoding, /* I The type of conditional coding to use */ +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState *lpcnet, +#endif +#ifdef ENABLE_OSCE + OSCEModel *osce_model, +#endif + int arch /* I Run-time architecture */ +); + +/* Decode indices from bitstream */ +void silk_decode_indices( + silk_decoder_state *psDec, /* I/O State */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int decode_LBRR, /* I Flag indicating LBRR data is being decoded */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* Decode parameters from payload */ +void silk_decode_parameters( + silk_decoder_state *psDec, /* I/O State */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +/* Core decoder. Performs inverse NSQ operation LTP + LPC */ +void silk_decode_core( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I Decoder control */ + opus_int16 xq[], /* O Decoded speech */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ +); + +/* Decode quantization indices of excitation (Shell coding) */ +void silk_decode_pulses( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* O Excitation signal */ + const opus_int signalType, /* I Sigtype */ + const opus_int quantOffsetType, /* I quantOffsetType */ + const opus_int frame_length /* I Frame length */ +); + +/******************/ +/* CNG */ +/******************/ + +/* Reset CNG */ +void silk_CNG_Reset( + silk_decoder_state *psDec /* I/O Decoder state */ +); + +/* Updates CNG estimate, and applies the CNG when packet was lost */ +void silk_CNG( + silk_decoder_state *psDec, /* I/O Decoder state */ + silk_decoder_control *psDecCtrl, /* I/O Decoder control */ + opus_int16 frame[], /* I/O Signal */ + opus_int length /* I Length of residual */ +); + +/* Encoding of various parameters */ +void silk_encode_indices( + silk_encoder_state *psEncC, /* I/O Encoder state */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int FrameIndex, /* I Frame number */ + opus_int encode_LBRR, /* I Flag indicating LBRR data is being encoded */ + opus_int condCoding /* I The type of conditional coding to use */ +); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/meson.build new file mode 100644 index 0000000000..35d955784f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/meson.build @@ -0,0 +1,69 @@ +silk_sources = sources['SILK_SOURCES'] + +silk_sources_sse4_1 = sources['SILK_SOURCES_SSE4_1'] + +silk_sources_avx2 = sources['SILK_SOURCES_AVX2'] + +silk_sources_neon_intr = sources['SILK_SOURCES_ARM_NEON_INTR'] + +silk_sources_fixed_neon_intr = sources['SILK_SOURCES_FIXED_ARM_NEON_INTR'] + +silk_sources_fixed = sources['SILK_SOURCES_FIXED'] + +silk_sources_fixed_sse4_1 = sources['SILK_SOURCES_FIXED_SSE4_1'] + +silk_sources_float_sse4_1 = [] +silk_sources_float_neon_intr = [] +silk_sources_float_avx2 = sources['SILK_SOURCES_FLOAT_AVX2'] + +silk_sources_float = sources['SILK_SOURCES_FLOAT'] + +if opt_fixed_point + silk_sources += silk_sources_fixed +else + silk_sources += silk_sources_float +endif + +silk_includes = [opus_includes, include_directories('float', 'fixed')] +silk_static_libs = [] + +if host_cpu_family in ['x86', 'x86_64'] and opus_conf.has('OPUS_HAVE_RTCD') + silk_sources += sources['SILK_SOURCES_X86_RTCD'] +endif + +if host_cpu_family in ['arm', 'aarch64'] and have_arm_intrinsics_or_asm + if opus_conf.has('OPUS_HAVE_RTCD') + silk_sources += sources['SILK_SOURCES_ARM_RTCD'] + endif +endif + +foreach intr_name : ['sse4_1', 'avx2', 'neon_intr'] + have_intr = get_variable('have_' + intr_name) + if not have_intr + continue + endif + + intr_sources = get_variable('silk_sources_' + intr_name) + if not opt_fixed_point + intr_sources += get_variable('silk_sources_float_' + intr_name) + endif + + intr_args = get_variable('opus_@0@_args'.format(intr_name), []) + silk_static_libs += static_library('silk_' + intr_name, intr_sources, + c_args: intr_args, + include_directories: silk_includes, + install: false) +endforeach + +silk_c_args = [] +if host_machine.system() == 'windows' + silk_c_args += ['-DDLL_EXPORT'] +endif + +silk_lib = static_library('opus-silk', + silk_sources, + c_args: silk_c_args, + include_directories: silk_includes, + link_whole: [silk_static_libs], + dependencies: libm, + install: false) diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/NSQ_del_dec_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/NSQ_del_dec_mipsr1.h new file mode 100644 index 0000000000..85bfb637ef --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/NSQ_del_dec_mipsr1.h @@ -0,0 +1,410 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef NSQ_DEL_DEC_MIPSR1_H__ +#define NSQ_DEL_DEC_MIPSR1_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +#define OVERRIDE_silk_noise_shape_quantizer_del_dec +static inline void silk_noise_shape_quantizer_del_dec( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay, /* I */ + int arch /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14; + opus_int32 n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10; + opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; + NSQ_sample_struct psSampleState[ MAX_DEL_DEC_STATES ][ 2 ]; + NSQ_del_dec_struct *psDD; + NSQ_sample_struct *psSS; + opus_int16 b_Q14_0, b_Q14_1, b_Q14_2, b_Q14_3, b_Q14_4; + opus_int16 a_Q12_0, a_Q12_1, a_Q12_2, a_Q12_3, a_Q12_4, a_Q12_5, a_Q12_6; + opus_int16 a_Q12_7, a_Q12_8, a_Q12_9, a_Q12_10, a_Q12_11, a_Q12_12, a_Q12_13; + opus_int16 a_Q12_14, a_Q12_15; + + opus_int32 cur, prev, next; + + /*Unused.*/ + (void)arch; + + //Intialize b_Q14 variables + b_Q14_0 = b_Q14[ 0 ]; + b_Q14_1 = b_Q14[ 1 ]; + b_Q14_2 = b_Q14[ 2 ]; + b_Q14_3 = b_Q14[ 3 ]; + b_Q14_4 = b_Q14[ 4 ]; + + //Intialize a_Q12 variables + a_Q12_0 = a_Q12[0]; + a_Q12_1 = a_Q12[1]; + a_Q12_2 = a_Q12[2]; + a_Q12_3 = a_Q12[3]; + a_Q12_4 = a_Q12[4]; + a_Q12_5 = a_Q12[5]; + a_Q12_6 = a_Q12[6]; + a_Q12_7 = a_Q12[7]; + a_Q12_8 = a_Q12[8]; + a_Q12_9 = a_Q12[9]; + a_Q12_10 = a_Q12[10]; + a_Q12_11 = a_Q12[11]; + a_Q12_12 = a_Q12[12]; + a_Q12_13 = a_Q12[13]; + a_Q12_14 = a_Q12[14]; + a_Q12_15 = a_Q12[15]; + + long long temp64; + + silk_assert( nStatesDelayedDecision > 0 ); + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + for( i = 0; i < length; i++ ) { + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + temp64 = __builtin_mips_mult(pred_lag_ptr[ 0 ], b_Q14_0 ); + temp64 = __builtin_mips_madd( temp64, pred_lag_ptr[ -1 ], b_Q14_1 ); + temp64 = __builtin_mips_madd( temp64, pred_lag_ptr[ -2 ], b_Q14_2 ); + temp64 = __builtin_mips_madd( temp64, pred_lag_ptr[ -3 ], b_Q14_3 ); + temp64 = __builtin_mips_madd( temp64, pred_lag_ptr[ -4 ], b_Q14_4 ); + temp64 += 32768; + LTP_pred_Q14 = __builtin_mips_extr_w(temp64, 16); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + /* Delayed decision state */ + psDD = &psDelDec[ k ]; + + /* Sample state */ + psSS = psSampleState[ k ]; + + /* Generate dither */ + psDD->Seed = silk_RAND( psDD->Seed ); + + /* Pointer used in short term prediction and shaping */ + psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; + /* Short-term prediction */ + silk_assert( predictLPCOrder == 10 || predictLPCOrder == 16 ); + temp64 = __builtin_mips_mult(psLPC_Q14[ 0 ], a_Q12_0 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -1 ], a_Q12_1 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -2 ], a_Q12_2 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -3 ], a_Q12_3 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -4 ], a_Q12_4 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -5 ], a_Q12_5 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -6 ], a_Q12_6 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -7 ], a_Q12_7 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -8 ], a_Q12_8 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -9 ], a_Q12_9 ); + if( predictLPCOrder == 16 ) { + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -10 ], a_Q12_10 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -11 ], a_Q12_11 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -12 ], a_Q12_12 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -13 ], a_Q12_13 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -14 ], a_Q12_14 ); + temp64 = __builtin_mips_madd( temp64, psLPC_Q14[ -15 ], a_Q12_15 ); + } + temp64 += 32768; + LPC_pred_Q14 = __builtin_mips_extr_w(temp64, 16); + + LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + /* Output of lowpass section */ + tmp2 = silk_SMLAWB( psLPC_Q14[ 0 ], psDD->sAR2_Q14[ 0 ], warping_Q16 ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], psDD->sAR2_Q14[ 1 ] - tmp2, warping_Q16 ); + psDD->sAR2_Q14[ 0 ] = tmp2; + + temp64 = __builtin_mips_mult(tmp2, AR_shp_Q13[ 0 ] ); + + prev = psDD->sAR2_Q14[ 1 ]; + + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + cur = psDD->sAR2_Q14[ j ]; + next = psDD->sAR2_Q14[ j+1 ]; + /* Output of allpass section */ + tmp2 = silk_SMLAWB( prev, cur - tmp1, warping_Q16 ); + psDD->sAR2_Q14[ j - 1 ] = tmp1; + temp64 = __builtin_mips_madd( temp64, tmp1, AR_shp_Q13[ j - 1 ] ); + temp64 = __builtin_mips_madd( temp64, tmp2, AR_shp_Q13[ j ] ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( cur, next - tmp2, warping_Q16 ); + psDD->sAR2_Q14[ j + 0 ] = tmp2; + prev = next; + } + psDD->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; + temp64 = __builtin_mips_madd( temp64, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + temp64 += 32768; + n_AR_Q14 = __builtin_mips_extr_w(temp64, 16); + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 1 ); /* Q11 -> Q12 */ + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, psDD->LF_AR_Q14, Tilt_Q14 ); /* Q12 */ + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 2 ); /* Q12 -> Q14 */ + + n_LF_Q14 = silk_SMULWB( psDD->Shape_Q14[ *smpl_buf_idx ], LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_SMLAWT( n_LF_Q14, psDD->LF_AR_Q14, LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_LSHIFT( n_LF_Q14, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1 = silk_ADD32( n_AR_Q14, n_LF_Q14 ); /* Q14 */ + tmp2 = silk_ADD32( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */ + tmp1 = silk_SUB32( tmp2, tmp1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */ + + r_Q10 = silk_SUB32( x_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if ( psDD->Seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q10 = silk_RSHIFT( silk_SMLABB( rd1_Q10, rr_Q10, rr_Q10 ), 10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q10 = silk_RSHIFT( silk_SMLABB( rd2_Q10, rr_Q10, rr_Q10 ), 10 ); + + if( rd1_Q10 < rd2_Q10 ) { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 0 ].Q_Q10 = q1_Q10; + psSS[ 1 ].Q_Q10 = q2_Q10; + } else { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 0 ].Q_Q10 = q2_Q10; + psSS[ 1 ].Q_Q10 = q1_Q10; + } + + /* Update states for best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 0 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 0 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 0 ].xq_Q14 = xq_Q14; + + /* Update states for second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 1 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 1 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 1 ].xq_Q14 = xq_Q14; + } + + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ k ][ 0 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + Winner_ind = k; + } + } + + /* Increase RD values of expired states */ + Winner_rand_state = psDelDec[ Winner_ind ].RandState[ last_smple_idx ]; + for( k = 0; k < nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RandState[ last_smple_idx ] != Winner_rand_state ) { + psSampleState[ k ][ 0 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 0 ].RD_Q10, silk_int32_MAX >> 4 ); + psSampleState[ k ][ 1 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 1 ].RD_Q10, silk_int32_MAX >> 4 ); + silk_assert( psSampleState[ k ][ 0 ].RD_Q10 >= 0 ); + } + } + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + RDmin_Q10 = psSampleState[ 0 ][ 1 ].RD_Q10; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ k ][ 0 ].RD_Q10 > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ k ][ 1 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 1 ].RD_Q10; + RDmin_ind = k; + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + silk_memcpy( ( (opus_int32 *)&psDelDec[ RDmax_ind ] ) + i, + ( (opus_int32 *)&psDelDec[ RDmin_ind ] ) + i, sizeof( NSQ_del_dec_struct ) - i * sizeof( opus_int32) ); + silk_memcpy( &psSampleState[ RDmax_ind ][ 0 ], &psSampleState[ RDmin_ind ][ 1 ], sizeof( NSQ_sample_struct ) ); + } + + /* Write samples from winner to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDD->Shape_Q14[ last_smple_idx ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDD->Pred_Q15[ last_smple_idx ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psSS = &psSampleState[ k ][ 0 ]; + psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; + psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; + psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; + psDD->Pred_Q15[ *smpl_buf_idx ] = silk_LSHIFT32( psSS->LPC_exc_Q14, 1 ); + psDD->Shape_Q14[ *smpl_buf_idx ] = psSS->sLTP_shp_Q14; + psDD->Seed = silk_ADD32_ovflw( psDD->Seed, silk_RSHIFT_ROUND( psSS->Q_Q10, 10 ) ); + psDD->RandState[ *smpl_buf_idx ] = psDD->Seed; + psDD->RD_Q10 = psSS->RD_Q10; + } + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + silk_memcpy( psDD->sLPC_Q14, &psDD->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + } +} + +#endif /* NSQ_DEL_DEC_MIPSR1_H__ */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/macros_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/macros_mipsr1.h new file mode 100644 index 0000000000..af408802c3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/macros_mipsr1.h @@ -0,0 +1,92 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + + +#ifndef SILK_MACROS_MIPSR1_H__ +#define SILK_MACROS_MIPSR1_H__ + +#define mips_clz(x) __builtin_clz(x) + +#undef silk_SMULWB +static inline int silk_SMULWB(int a, int b) +{ + long long ac; + int c; + + ac = __builtin_mips_mult(a, (opus_int32)(opus_int16)b); + c = __builtin_mips_extr_w(ac, 16); + + return c; +} + +#undef silk_SMLAWB +#define silk_SMLAWB(a32, b32, c32) ((a32) + silk_SMULWB(b32, c32)) + +#undef silk_SMULWW +static inline int silk_SMULWW(int a, int b) +{ + long long ac; + int c; + + ac = __builtin_mips_mult(a, b); + c = __builtin_mips_extr_w(ac, 16); + + return c; +} + +#undef silk_SMLAWW +static inline int silk_SMLAWW(int a, int b, int c) +{ + long long ac; + int res; + + ac = __builtin_mips_mult(b, c); + res = __builtin_mips_extr_w(ac, 16); + res += a; + + return res; +} + +#define OVERRIDE_silk_CLZ16 +static inline opus_int32 silk_CLZ16(opus_int16 in16) +{ + int re32; + opus_int32 in32 = (opus_int32 )in16; + re32 = mips_clz(in32); + re32-=16; + return re32; +} + +#define OVERRIDE_silk_CLZ32 +static inline opus_int32 silk_CLZ32(opus_int32 in32) +{ + int re32; + re32 = mips_clz(in32); + return re32; +} + +#endif /* SILK_MACROS_MIPSR1_H__ */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/sigproc_fix_mipsr1.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/sigproc_fix_mipsr1.h new file mode 100644 index 0000000000..51520c0a6f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/mips/sigproc_fix_mipsr1.h @@ -0,0 +1,60 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_SIGPROC_FIX_MIPSR1_H +#define SILK_SIGPROC_FIX_MIPSR1_H + +#undef silk_SAT16 +static inline short int silk_SAT16(int a) +{ + int c; + c = __builtin_mips_shll_s_w(a, 16); + c = c>>16; + + return c; +} + +#undef silk_LSHIFT_SAT32 +static inline int silk_LSHIFT_SAT32(int a, int shift) +{ + int r; + + r = __builtin_mips_shll_s_w(a, shift); + + return r; +} + +#undef silk_RSHIFT_ROUND +static inline int silk_RSHIFT_ROUND(int a, int shift) +{ + int r; + + r = __builtin_mips_shra_r_w(a, shift); + return r; +} + +#endif /* SILK_SIGPROC_FIX_MIPSR1_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_defines.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_defines.h new file mode 100644 index 0000000000..e1e4b5d768 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_defines.h @@ -0,0 +1,88 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_PE_DEFINES_H +#define SILK_PE_DEFINES_H + +#include "SigProc_FIX.h" + +/********************************************************/ +/* Definitions for pitch estimator */ +/********************************************************/ + +#define PE_MAX_FS_KHZ 16 /* Maximum sampling frequency used */ + +#define PE_MAX_NB_SUBFR 4 +#define PE_SUBFR_LENGTH_MS 5 /* 5 ms */ + +#define PE_LTP_MEM_LENGTH_MS ( 4 * PE_SUBFR_LENGTH_MS ) + +#define PE_MAX_FRAME_LENGTH_MS ( PE_LTP_MEM_LENGTH_MS + PE_MAX_NB_SUBFR * PE_SUBFR_LENGTH_MS ) +#define PE_MAX_FRAME_LENGTH ( PE_MAX_FRAME_LENGTH_MS * PE_MAX_FS_KHZ ) +#define PE_MAX_FRAME_LENGTH_ST_1 ( PE_MAX_FRAME_LENGTH >> 2 ) +#define PE_MAX_FRAME_LENGTH_ST_2 ( PE_MAX_FRAME_LENGTH >> 1 ) + +#define PE_MAX_LAG_MS 18 /* 18 ms -> 56 Hz */ +#define PE_MIN_LAG_MS 2 /* 2 ms -> 500 Hz */ +#define PE_MAX_LAG ( PE_MAX_LAG_MS * PE_MAX_FS_KHZ ) +#define PE_MIN_LAG ( PE_MIN_LAG_MS * PE_MAX_FS_KHZ ) + +#define PE_D_SRCH_LENGTH 24 + +#define PE_NB_STAGE3_LAGS 5 + +#define PE_NB_CBKS_STAGE2 3 +#define PE_NB_CBKS_STAGE2_EXT 11 + +#define PE_NB_CBKS_STAGE3_MAX 34 +#define PE_NB_CBKS_STAGE3_MID 24 +#define PE_NB_CBKS_STAGE3_MIN 16 + +#define PE_NB_CBKS_STAGE3_10MS 12 +#define PE_NB_CBKS_STAGE2_10MS 3 + +#define PE_SHORTLAG_BIAS 0.2f /* for logarithmic weighting */ +#define PE_PREVLAG_BIAS 0.2f /* for logarithmic weighting */ +#define PE_FLATCONTOUR_BIAS 0.05f + +#define SILK_PE_MIN_COMPLEX 0 +#define SILK_PE_MID_COMPLEX 1 +#define SILK_PE_MAX_COMPLEX 2 + +/* Tables for 20 ms frames */ +extern const opus_int8 silk_CB_lags_stage2[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE2_EXT ]; +extern const opus_int8 silk_CB_lags_stage3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ]; +extern const opus_int8 silk_Lag_range_stage3[ SILK_PE_MAX_COMPLEX + 1 ] [ PE_MAX_NB_SUBFR ][ 2 ]; +extern const opus_int8 silk_nb_cbk_searchs_stage3[ SILK_PE_MAX_COMPLEX + 1 ]; + +/* Tables for 10 ms frames */ +extern const opus_int8 silk_CB_lags_stage2_10_ms[ PE_MAX_NB_SUBFR >> 1][ 3 ]; +extern const opus_int8 silk_CB_lags_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 12 ]; +extern const opus_int8 silk_Lag_range_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 2 ]; + +#endif + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_tables.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_tables.c new file mode 100644 index 0000000000..81a8bacaca --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/pitch_est_tables.c @@ -0,0 +1,99 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "typedef.h" +#include "pitch_est_defines.h" + +const opus_int8 silk_CB_lags_stage2_10_ms[ PE_MAX_NB_SUBFR >> 1][ PE_NB_CBKS_STAGE2_10MS ] = +{ + {0, 1, 0}, + {0, 0, 1} +}; + +const opus_int8 silk_CB_lags_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ PE_NB_CBKS_STAGE3_10MS ] = +{ + { 0, 0, 1,-1, 1,-1, 2,-2, 2,-2, 3,-3}, + { 0, 1, 0, 1,-1, 2,-1, 2,-2, 3,-2, 3} +}; + +const opus_int8 silk_Lag_range_stage3_10_ms[ PE_MAX_NB_SUBFR >> 1 ][ 2 ] = +{ + {-3, 7}, + {-2, 7} +}; + +const opus_int8 silk_CB_lags_stage2[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE2_EXT ] = +{ + {0, 2,-1,-1,-1, 0, 0, 1, 1, 0, 1}, + {0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0}, + {0,-1, 2, 1, 0, 1, 1, 0, 0,-1,-1} +}; + +const opus_int8 silk_CB_lags_stage3[ PE_MAX_NB_SUBFR ][ PE_NB_CBKS_STAGE3_MAX ] = +{ + {0, 0, 1,-1, 0, 1,-1, 0,-1, 1,-2, 2,-2,-2, 2,-3, 2, 3,-3,-4, 3,-4, 4, 4,-5, 5,-6,-5, 6,-7, 6, 5, 8,-9}, + {0, 0, 1, 0, 0, 0, 0, 0, 0, 0,-1, 1, 0, 0, 1,-1, 0, 1,-1,-1, 1,-1, 2, 1,-1, 2,-2,-2, 2,-2, 2, 2, 3,-3}, + {0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1,-1, 1, 0, 0, 2, 1,-1, 2,-1,-1, 2,-1, 2, 2,-1, 3,-2,-2,-2, 3}, + {0, 1, 0, 0, 1, 0, 1,-1, 2,-1, 2,-1, 2, 3,-2, 3,-2,-2, 4, 4,-3, 5,-3,-4, 6,-4, 6, 5,-5, 8,-6,-5,-7, 9} +}; + +const opus_int8 silk_Lag_range_stage3[ SILK_PE_MAX_COMPLEX + 1 ] [ PE_MAX_NB_SUBFR ][ 2 ] = +{ + /* Lags to search for low number of stage3 cbks */ + { + {-5,8}, + {-1,6}, + {-1,6}, + {-4,10} + }, + /* Lags to search for middle number of stage3 cbks */ + { + {-6,10}, + {-2,6}, + {-1,6}, + {-5,10} + }, + /* Lags to search for max number of stage3 cbks */ + { + {-9,12}, + {-3,7}, + {-2,7}, + {-7,13} + } +}; + +const opus_int8 silk_nb_cbk_searchs_stage3[ SILK_PE_MAX_COMPLEX + 1 ] = +{ + PE_NB_CBKS_STAGE3_MIN, + PE_NB_CBKS_STAGE3_MID, + PE_NB_CBKS_STAGE3_MAX +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/process_NLSFs.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/process_NLSFs.c new file mode 100644 index 0000000000..d130809541 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/process_NLSFs.c @@ -0,0 +1,107 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Limit, stabilize, convert and quantize NLSFs */ +void silk_process_NLSFs( + silk_encoder_state *psEncC, /* I/O Encoder state */ + opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ], /* O Prediction coefficients */ + opus_int16 pNLSF_Q15[ MAX_LPC_ORDER ], /* I/O Normalized LSFs (quant out) (0 - (2^15-1)) */ + const opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ] /* I Previous Normalized LSFs (0 - (2^15-1)) */ +) +{ + opus_int i, doInterpolate; + opus_int NLSF_mu_Q20; + opus_int16 i_sqr_Q15; + opus_int16 pNLSF0_temp_Q15[ MAX_LPC_ORDER ]; + opus_int16 pNLSFW_QW[ MAX_LPC_ORDER ]; + opus_int16 pNLSFW0_temp_QW[ MAX_LPC_ORDER ]; + + silk_assert( psEncC->speech_activity_Q8 >= 0 ); + silk_assert( psEncC->speech_activity_Q8 <= SILK_FIX_CONST( 1.0, 8 ) ); + celt_assert( psEncC->useInterpolatedNLSFs == 1 || psEncC->indices.NLSFInterpCoef_Q2 == ( 1 << 2 ) ); + + /***********************/ + /* Calculate mu values */ + /***********************/ + /* NLSF_mu = 0.003 - 0.0015 * psEnc->speech_activity; */ + NLSF_mu_Q20 = silk_SMLAWB( SILK_FIX_CONST( 0.003, 20 ), SILK_FIX_CONST( -0.001, 28 ), psEncC->speech_activity_Q8 ); + if( psEncC->nb_subfr == 2 ) { + /* Multiply by 1.5 for 10 ms packets */ + NLSF_mu_Q20 = silk_ADD_RSHIFT( NLSF_mu_Q20, NLSF_mu_Q20, 1 ); + } + + celt_assert( NLSF_mu_Q20 > 0 ); + silk_assert( NLSF_mu_Q20 <= SILK_FIX_CONST( 0.005, 20 ) ); + + /* Calculate NLSF weights */ + silk_NLSF_VQ_weights_laroia( pNLSFW_QW, pNLSF_Q15, psEncC->predictLPCOrder ); + + /* Update NLSF weights for interpolated NLSFs */ + doInterpolate = ( psEncC->useInterpolatedNLSFs == 1 ) && ( psEncC->indices.NLSFInterpCoef_Q2 < 4 ); + if( doInterpolate ) { + /* Calculate the interpolated NLSF vector for the first half */ + silk_interpolate( pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15, + psEncC->indices.NLSFInterpCoef_Q2, psEncC->predictLPCOrder ); + + /* Calculate first half NLSF weights for the interpolated NLSFs */ + silk_NLSF_VQ_weights_laroia( pNLSFW0_temp_QW, pNLSF0_temp_Q15, psEncC->predictLPCOrder ); + + /* Update NLSF weights with contribution from first half */ + i_sqr_Q15 = silk_LSHIFT( silk_SMULBB( psEncC->indices.NLSFInterpCoef_Q2, psEncC->indices.NLSFInterpCoef_Q2 ), 11 ); + for( i = 0; i < psEncC->predictLPCOrder; i++ ) { + pNLSFW_QW[ i ] = silk_ADD16( silk_RSHIFT( pNLSFW_QW[ i ], 1 ), silk_RSHIFT( + silk_SMULBB( pNLSFW0_temp_QW[ i ], i_sqr_Q15 ), 16) ); + silk_assert( pNLSFW_QW[ i ] >= 1 ); + } + } + + silk_NLSF_encode( psEncC->indices.NLSFIndices, pNLSF_Q15, psEncC->psNLSF_CB, pNLSFW_QW, + NLSF_mu_Q20, psEncC->NLSF_MSVQ_Survivors, psEncC->indices.signalType ); + + /* Convert quantized NLSFs back to LPC coefficients */ + silk_NLSF2A( PredCoef_Q12[ 1 ], pNLSF_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + if( doInterpolate ) { + /* Calculate the interpolated, quantized LSF vector for the first half */ + silk_interpolate( pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15, + psEncC->indices.NLSFInterpCoef_Q2, psEncC->predictLPCOrder ); + + /* Convert back to LPC coefficients */ + silk_NLSF2A( PredCoef_Q12[ 0 ], pNLSF0_temp_Q15, psEncC->predictLPCOrder, psEncC->arch ); + + } else { + /* Copy LPC coefficients for first half from second half */ + celt_assert( psEncC->predictLPCOrder <= MAX_LPC_ORDER ); + silk_memcpy( PredCoef_Q12[ 0 ], PredCoef_Q12[ 1 ], psEncC->predictLPCOrder * sizeof( opus_int16 ) ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/quant_LTP_gains.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/quant_LTP_gains.c new file mode 100644 index 0000000000..d6b8eff8d1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/quant_LTP_gains.c @@ -0,0 +1,132 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "tuning_parameters.h" + +void silk_quant_LTP_gains( + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ + opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ + opus_int8 *periodicity_index, /* O Periodicity Index */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ +) +{ + opus_int j, k, cbk_size; + opus_int8 temp_idx[ MAX_NB_SUBFR ]; + const opus_uint8 *cl_ptr_Q5; + const opus_int8 *cbk_ptr_Q7; + const opus_uint8 *cbk_gain_ptr_Q7; + const opus_int32 *XX_Q17_ptr, *xX_Q17_ptr; + opus_int32 res_nrg_Q15_subfr, res_nrg_Q15, rate_dist_Q7_subfr, rate_dist_Q7, min_rate_dist_Q7; + opus_int32 sum_log_gain_tmp_Q7, best_sum_log_gain_Q7, max_gain_Q7; + opus_int gain_Q7; + + /***************************************************/ + /* iterate over different codebooks with different */ + /* rates/distortions, and choose best */ + /***************************************************/ + min_rate_dist_Q7 = silk_int32_MAX; + best_sum_log_gain_Q7 = 0; + for( k = 0; k < 3; k++ ) { + /* Safety margin for pitch gain control, to take into account factors + such as state rescaling/rewhitening. */ + opus_int32 gain_safety = SILK_FIX_CONST( 0.4, 7 ); + + cl_ptr_Q5 = silk_LTP_gain_BITS_Q5_ptrs[ k ]; + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ k ]; + cbk_gain_ptr_Q7 = silk_LTP_vq_gain_ptrs_Q7[ k ]; + cbk_size = silk_LTP_vq_sizes[ k ]; + + /* Set up pointers to first subframe */ + XX_Q17_ptr = XX_Q17; + xX_Q17_ptr = xX_Q17; + + res_nrg_Q15 = 0; + rate_dist_Q7 = 0; + sum_log_gain_tmp_Q7 = *sum_log_gain_Q7; + for( j = 0; j < nb_subfr; j++ ) { + max_gain_Q7 = silk_log2lin( ( SILK_FIX_CONST( MAX_SUM_LOG_GAIN_DB / 6.0, 7 ) - sum_log_gain_tmp_Q7 ) + + SILK_FIX_CONST( 7, 7 ) ) - gain_safety; + silk_VQ_WMat_EC( + &temp_idx[ j ], /* O index of best codebook vector */ + &res_nrg_Q15_subfr, /* O residual energy */ + &rate_dist_Q7_subfr, /* O best weighted quantization error + mu * rate */ + &gain_Q7, /* O sum of absolute LTP coefficients */ + XX_Q17_ptr, /* I correlation matrix */ + xX_Q17_ptr, /* I correlation vector */ + cbk_ptr_Q7, /* I codebook */ + cbk_gain_ptr_Q7, /* I codebook effective gains */ + cl_ptr_Q5, /* I code length for each codebook vector */ + subfr_len, /* I number of samples per subframe */ + max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + cbk_size, /* I number of vectors in codebook */ + arch /* I Run-time architecture */ + ); + + res_nrg_Q15 = silk_ADD_POS_SAT32( res_nrg_Q15, res_nrg_Q15_subfr ); + rate_dist_Q7 = silk_ADD_POS_SAT32( rate_dist_Q7, rate_dist_Q7_subfr ); + sum_log_gain_tmp_Q7 = silk_max(0, sum_log_gain_tmp_Q7 + + silk_lin2log( gain_safety + gain_Q7 ) - SILK_FIX_CONST( 7, 7 )); + + XX_Q17_ptr += LTP_ORDER * LTP_ORDER; + xX_Q17_ptr += LTP_ORDER; + } + + if( rate_dist_Q7 <= min_rate_dist_Q7 ) { + min_rate_dist_Q7 = rate_dist_Q7; + *periodicity_index = (opus_int8)k; + silk_memcpy( cbk_index, temp_idx, nb_subfr * sizeof( opus_int8 ) ); + best_sum_log_gain_Q7 = sum_log_gain_tmp_Q7; + } + } + + cbk_ptr_Q7 = silk_LTP_vq_ptrs_Q7[ *periodicity_index ]; + for( j = 0; j < nb_subfr; j++ ) { + for( k = 0; k < LTP_ORDER; k++ ) { + B_Q14[ j * LTP_ORDER + k ] = silk_LSHIFT( cbk_ptr_Q7[ cbk_index[ j ] * LTP_ORDER + k ], 7 ); + } + } + + if( nb_subfr == 2 ) { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 1 ); + } else { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 2 ); + } + + *sum_log_gain_Q7 = best_sum_log_gain_Q7; + *pred_gain_dB_Q7 = (opus_int)silk_SMULBB( -3, silk_lin2log( res_nrg_Q15 ) - ( 15 << 7 ) ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler.c new file mode 100644 index 0000000000..1f11e50891 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler.c @@ -0,0 +1,215 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* + * Matrix of resampling methods used: + * Fs_out (kHz) + * 8 12 16 24 48 + * + * 8 C UF U UF UF + * 12 AF C UF U UF + * Fs_in (kHz) 16 D AF C UF UF + * 24 AF D AF C U + * 48 AF AF AF D C + * + * C -> Copy (no resampling) + * D -> Allpass-based 2x downsampling + * U -> Allpass-based 2x upsampling + * UF -> Allpass-based 2x upsampling followed by FIR interpolation + * AF -> AR2 filter followed by FIR interpolation + */ + +#include "resampler_private.h" + +/* Tables with delay compensation values to equalize total delay for different modes */ +static const opus_int8 delay_matrix_enc[ 5 ][ 3 ] = { +/* in \ out 8 12 16 */ +/* 8 */ { 6, 0, 3 }, +/* 12 */ { 0, 7, 3 }, +/* 16 */ { 0, 1, 10 }, +/* 24 */ { 0, 2, 6 }, +/* 48 */ { 18, 10, 12 } +}; + +static const opus_int8 delay_matrix_dec[ 3 ][ 5 ] = { +/* in \ out 8 12 16 24 48 */ +/* 8 */ { 4, 0, 2, 0, 0 }, +/* 12 */ { 0, 9, 4, 7, 4 }, +/* 16 */ { 0, 3, 12, 7, 7 } +}; + +/* Simple way to make [8000, 12000, 16000, 24000, 48000] to [0, 1, 2, 3, 4] */ +#define rateID(R) ( ( ( ((R)>>12) - ((R)>16000) ) >> ((R)>24000) ) - 1 ) + +#define USE_silk_resampler_copy (0) +#define USE_silk_resampler_private_up2_HQ_wrapper (1) +#define USE_silk_resampler_private_IIR_FIR (2) +#define USE_silk_resampler_private_down_FIR (3) + +/* Initialize/reset the resampler state for a given pair of input/output sampling rates */ +opus_int silk_resampler_init( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int32 Fs_Hz_in, /* I Input sampling rate (Hz) */ + opus_int32 Fs_Hz_out, /* I Output sampling rate (Hz) */ + opus_int forEnc /* I If 1: encoder; if 0: decoder */ +) +{ + opus_int up2x; + + /* Clear state */ + silk_memset( S, 0, sizeof( silk_resampler_state_struct ) ); + + /* Input checking */ + if( forEnc ) { + if( ( Fs_Hz_in != 8000 && Fs_Hz_in != 12000 && Fs_Hz_in != 16000 && Fs_Hz_in != 24000 && Fs_Hz_in != 48000 ) || + ( Fs_Hz_out != 8000 && Fs_Hz_out != 12000 && Fs_Hz_out != 16000 ) ) { + celt_assert( 0 ); + return -1; + } + S->inputDelay = delay_matrix_enc[ rateID( Fs_Hz_in ) ][ rateID( Fs_Hz_out ) ]; + } else { + if( ( Fs_Hz_in != 8000 && Fs_Hz_in != 12000 && Fs_Hz_in != 16000 ) || + ( Fs_Hz_out != 8000 && Fs_Hz_out != 12000 && Fs_Hz_out != 16000 && Fs_Hz_out != 24000 && Fs_Hz_out != 48000 ) ) { + celt_assert( 0 ); + return -1; + } + S->inputDelay = delay_matrix_dec[ rateID( Fs_Hz_in ) ][ rateID( Fs_Hz_out ) ]; + } + + S->Fs_in_kHz = silk_DIV32_16( Fs_Hz_in, 1000 ); + S->Fs_out_kHz = silk_DIV32_16( Fs_Hz_out, 1000 ); + + /* Number of samples processed per batch */ + S->batchSize = S->Fs_in_kHz * RESAMPLER_MAX_BATCH_SIZE_MS; + + /* Find resampler with the right sampling ratio */ + up2x = 0; + if( Fs_Hz_out > Fs_Hz_in ) { + /* Upsample */ + if( Fs_Hz_out == silk_MUL( Fs_Hz_in, 2 ) ) { /* Fs_out : Fs_in = 2 : 1 */ + /* Special case: directly use 2x upsampler */ + S->resampler_function = USE_silk_resampler_private_up2_HQ_wrapper; + } else { + /* Default resampler */ + S->resampler_function = USE_silk_resampler_private_IIR_FIR; + up2x = 1; + } + } else if ( Fs_Hz_out < Fs_Hz_in ) { + /* Downsample */ + S->resampler_function = USE_silk_resampler_private_down_FIR; + if( silk_MUL( Fs_Hz_out, 4 ) == silk_MUL( Fs_Hz_in, 3 ) ) { /* Fs_out : Fs_in = 3 : 4 */ + S->FIR_Fracs = 3; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR0; + S->Coefs = silk_Resampler_3_4_COEFS; + } else if( silk_MUL( Fs_Hz_out, 3 ) == silk_MUL( Fs_Hz_in, 2 ) ) { /* Fs_out : Fs_in = 2 : 3 */ + S->FIR_Fracs = 2; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR0; + S->Coefs = silk_Resampler_2_3_COEFS; + } else if( silk_MUL( Fs_Hz_out, 2 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 2 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR1; + S->Coefs = silk_Resampler_1_2_COEFS; + } else if( silk_MUL( Fs_Hz_out, 3 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 3 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_3_COEFS; + } else if( silk_MUL( Fs_Hz_out, 4 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 4 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_4_COEFS; + } else if( silk_MUL( Fs_Hz_out, 6 ) == Fs_Hz_in ) { /* Fs_out : Fs_in = 1 : 6 */ + S->FIR_Fracs = 1; + S->FIR_Order = RESAMPLER_DOWN_ORDER_FIR2; + S->Coefs = silk_Resampler_1_6_COEFS; + } else { + /* None available */ + celt_assert( 0 ); + return -1; + } + } else { + /* Input and output sampling rates are equal: copy */ + S->resampler_function = USE_silk_resampler_copy; + } + + /* Ratio of input/output samples */ + S->invRatio_Q16 = silk_LSHIFT32( silk_DIV32( silk_LSHIFT32( Fs_Hz_in, 14 + up2x ), Fs_Hz_out ), 2 ); + /* Make sure the ratio is rounded up */ + while( silk_SMULWW( S->invRatio_Q16, Fs_Hz_out ) < silk_LSHIFT32( Fs_Hz_in, up2x ) ) { + S->invRatio_Q16++; + } + + return 0; +} + +/* Resampler: convert from one sampling rate to another */ +/* Input and output sampling rate are at most 48000 Hz */ +opus_int silk_resampler( + silk_resampler_state_struct *S, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int nSamples; + + /* Need at least 1 ms of input data */ + celt_assert( inLen >= S->Fs_in_kHz ); + /* Delay can't exceed the 1 ms of buffering */ + celt_assert( S->inputDelay <= S->Fs_in_kHz ); + + nSamples = S->Fs_in_kHz - S->inputDelay; + + /* Copy to delay buffer */ + silk_memcpy( &S->delayBuf[ S->inputDelay ], in, nSamples * sizeof( opus_int16 ) ); + + switch( S->resampler_function ) { + case USE_silk_resampler_private_up2_HQ_wrapper: + silk_resampler_private_up2_HQ_wrapper( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_up2_HQ_wrapper( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + case USE_silk_resampler_private_IIR_FIR: + silk_resampler_private_IIR_FIR( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_IIR_FIR( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + case USE_silk_resampler_private_down_FIR: + silk_resampler_private_down_FIR( S, out, S->delayBuf, S->Fs_in_kHz ); + silk_resampler_private_down_FIR( S, &out[ S->Fs_out_kHz ], &in[ nSamples ], inLen - S->Fs_in_kHz ); + break; + default: + silk_memcpy( out, S->delayBuf, S->Fs_in_kHz * sizeof( opus_int16 ) ); + silk_memcpy( &out[ S->Fs_out_kHz ], &in[ nSamples ], ( inLen - S->Fs_in_kHz ) * sizeof( opus_int16 ) ); + } + + /* Copy to delay buffer */ + silk_memcpy( S->delayBuf, &in[ inLen - S->inputDelay ], S->inputDelay * sizeof( opus_int16 ) ); + + return 0; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2.c new file mode 100644 index 0000000000..971d7bfd4a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2.c @@ -0,0 +1,74 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_rom.h" + +/* Downsample by a factor 2 */ +void silk_resampler_down2( + opus_int32 *S, /* I/O State vector [ 2 ] */ + opus_int16 *out, /* O Output signal [ floor(len/2) ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int32 k, len2 = silk_RSHIFT32( inLen, 1 ); + opus_int32 in32, out32, Y, X; + + celt_assert( silk_resampler_down2_0 > 0 ); + celt_assert( silk_resampler_down2_1 < 0 ); + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len2; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k ], 10 ); + + /* All-pass section for even input sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_down2_1 ); + out32 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ 2 * k + 1 ], 10 ); + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = silk_SUB32( in32, S[ 1 ] ); + X = silk_SMULWB( Y, silk_resampler_down2_0 ); + out32 = silk_ADD32( out32, S[ 1 ] ); + out32 = silk_ADD32( out32, X ); + S[ 1 ] = silk_ADD32( in32, X ); + + /* Add, convert back to int16 and store to output */ + out[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32, 11 ) ); + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2_3.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2_3.c new file mode 100644 index 0000000000..4342614dcc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_down2_3.c @@ -0,0 +1,103 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +#define ORDER_FIR 4 + +/* Downsample by a factor 2/3, low quality */ +void silk_resampler_down2_3( + opus_int32 *S, /* I/O State vector [ 6 ] */ + opus_int16 *out, /* O Output signal [ floor(2*inLen/3) ] */ + const opus_int16 *in, /* I Input signal [ inLen ] */ + opus_int32 inLen /* I Number of input samples */ +) +{ + opus_int32 nSamplesIn, counter, res_Q6; + VARDECL( opus_int32, buf ); + opus_int32 *buf_ptr; + SAVE_STACK; + + ALLOC( buf, RESAMPLER_MAX_BATCH_SIZE_IN + ORDER_FIR, opus_int32 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S, ORDER_FIR * sizeof( opus_int32 ) ); + + /* Iterate over blocks of frameSizeIn input samples */ + while( 1 ) { + nSamplesIn = silk_min( inLen, RESAMPLER_MAX_BATCH_SIZE_IN ); + + /* Second-order AR filter (output in Q8) */ + silk_resampler_private_AR2( &S[ ORDER_FIR ], &buf[ ORDER_FIR ], in, + silk_Resampler_2_3_COEFS_LQ, nSamplesIn ); + + /* Interpolate filtered signal */ + buf_ptr = buf; + counter = nSamplesIn; + while( counter > 2 ) { + /* Inner product */ + res_Q6 = silk_SMULWB( buf_ptr[ 0 ], silk_Resampler_2_3_COEFS_LQ[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 1 ], silk_Resampler_2_3_COEFS_LQ[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], silk_Resampler_2_3_COEFS_LQ[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], silk_Resampler_2_3_COEFS_LQ[ 4 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + + res_Q6 = silk_SMULWB( buf_ptr[ 1 ], silk_Resampler_2_3_COEFS_LQ[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], silk_Resampler_2_3_COEFS_LQ[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], silk_Resampler_2_3_COEFS_LQ[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 4 ], silk_Resampler_2_3_COEFS_LQ[ 2 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + + buf_ptr += 3; + counter -= 3; + } + + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 0 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S, &buf[ nSamplesIn ], ORDER_FIR * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private.h new file mode 100644 index 0000000000..422a7d9d95 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private.h @@ -0,0 +1,88 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_RESAMPLER_PRIVATE_H +#define SILK_RESAMPLER_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "SigProc_FIX.h" +#include "resampler_structs.h" +#include "resampler_rom.h" + +/* Number of input samples to process in the inner loop */ +#define RESAMPLER_MAX_BATCH_SIZE_MS 10 +#define RESAMPLER_MAX_FS_KHZ 48 +#define RESAMPLER_MAX_BATCH_SIZE_IN ( RESAMPLER_MAX_BATCH_SIZE_MS * RESAMPLER_MAX_FS_KHZ ) + +/* Description: Hybrid IIR/FIR polyphase implementation of resampling */ +void silk_resampler_private_IIR_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/* Description: Hybrid IIR/FIR polyphase implementation of resampling */ +void silk_resampler_private_down_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +); + +/* Upsample by a factor 2, high quality */ +void silk_resampler_private_up2_HQ_wrapper( + void *SS, /* I/O Resampler state (unused) */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +); + +/* Upsample by a factor 2, high quality */ +void silk_resampler_private_up2_HQ( + opus_int32 *S, /* I/O Resampler state [ 6 ] */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +); + +/* Second order AR filter */ +void silk_resampler_private_AR2( + opus_int32 S[], /* I/O State vector [ 2 ] */ + opus_int32 out_Q8[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + const opus_int16 A_Q14[], /* I AR coefficients, Q14 */ + opus_int32 len /* I Signal length */ +); + +#ifdef __cplusplus +} +#endif +#endif /* SILK_RESAMPLER_PRIVATE_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_AR2.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_AR2.c new file mode 100644 index 0000000000..5fff23714f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_AR2.c @@ -0,0 +1,55 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" + +/* Second order AR filter with single delay elements */ +void silk_resampler_private_AR2( + opus_int32 S[], /* I/O State vector [ 2 ] */ + opus_int32 out_Q8[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + const opus_int16 A_Q14[], /* I AR coefficients, Q14 */ + opus_int32 len /* I Signal length */ +) +{ + opus_int32 k; + opus_int32 out32; + + for( k = 0; k < len; k++ ) { + out32 = silk_ADD_LSHIFT32( S[ 0 ], (opus_int32)in[ k ], 8 ); + out_Q8[ k ] = out32; + out32 = silk_LSHIFT( out32, 2 ); + S[ 0 ] = silk_SMLAWB( S[ 1 ], out32, A_Q14[ 0 ] ); + S[ 1 ] = silk_SMULWB( out32, A_Q14[ 1 ] ); + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_IIR_FIR.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_IIR_FIR.c new file mode 100644 index 0000000000..6b2b3a2e18 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_IIR_FIR.c @@ -0,0 +1,107 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +static OPUS_INLINE opus_int16 *silk_resampler_private_IIR_FIR_INTERPOL( + opus_int16 *out, + opus_int16 *buf, + opus_int32 max_index_Q16, + opus_int32 index_increment_Q16 +) +{ + opus_int32 index_Q16, res_Q15; + opus_int16 *buf_ptr; + opus_int32 table_index; + + /* Interpolate upsampled signal and store in output array */ + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + table_index = silk_SMULWB( index_Q16 & 0xFFFF, 12 ); + buf_ptr = &buf[ index_Q16 >> 16 ]; + + res_Q15 = silk_SMULBB( buf_ptr[ 0 ], silk_resampler_frac_FIR_12[ table_index ][ 0 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 1 ], silk_resampler_frac_FIR_12[ table_index ][ 1 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 2 ], silk_resampler_frac_FIR_12[ table_index ][ 2 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 3 ], silk_resampler_frac_FIR_12[ table_index ][ 3 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 4 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 3 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 5 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 2 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 6 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 1 ] ); + res_Q15 = silk_SMLABB( res_Q15, buf_ptr[ 7 ], silk_resampler_frac_FIR_12[ 11 - table_index ][ 0 ] ); + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q15, 15 ) ); + } + return out; +} +/* Upsample using a combination of allpass-based 2x upsampling and FIR interpolation */ +void silk_resampler_private_IIR_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + opus_int32 nSamplesIn; + opus_int32 max_index_Q16, index_increment_Q16; + VARDECL( opus_int16, buf ); + SAVE_STACK; + + ALLOC( buf, 2 * S->batchSize + RESAMPLER_ORDER_FIR_12, opus_int16 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S->sFIR.i16, RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + + /* Iterate over blocks of frameSizeIn input samples */ + index_increment_Q16 = S->invRatio_Q16; + while( 1 ) { + nSamplesIn = silk_min( inLen, S->batchSize ); + + /* Upsample 2x */ + silk_resampler_private_up2_HQ( S->sIIR, &buf[ RESAMPLER_ORDER_FIR_12 ], in, nSamplesIn ); + + max_index_Q16 = silk_LSHIFT32( nSamplesIn, 16 + 1 ); /* + 1 because 2x upsampling */ + out = silk_resampler_private_IIR_FIR_INTERPOL( out, buf, max_index_Q16, index_increment_Q16 ); + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 0 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn << 1 ], RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S->sFIR.i16, &buf[ nSamplesIn << 1 ], RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_down_FIR.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_down_FIR.c new file mode 100644 index 0000000000..3e8735a35a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_down_FIR.c @@ -0,0 +1,194 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" +#include "stack_alloc.h" + +static OPUS_INLINE opus_int16 *silk_resampler_private_down_FIR_INTERPOL( + opus_int16 *out, + opus_int32 *buf, + const opus_int16 *FIR_Coefs, + opus_int FIR_Order, + opus_int FIR_Fracs, + opus_int32 max_index_Q16, + opus_int32 index_increment_Q16 +) +{ + opus_int32 index_Q16, res_Q6; + opus_int32 *buf_ptr; + opus_int32 interpol_ind; + const opus_int16 *interpol_ptr; + + switch( FIR_Order ) { + case RESAMPLER_DOWN_ORDER_FIR0: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Fractional part gives interpolation coefficients */ + interpol_ind = silk_SMULWB( index_Q16 & 0xFFFF, FIR_Fracs ); + + /* Inner product */ + interpol_ptr = &FIR_Coefs[ RESAMPLER_DOWN_ORDER_FIR0 / 2 * interpol_ind ]; + res_Q6 = silk_SMULWB( buf_ptr[ 0 ], interpol_ptr[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 1 ], interpol_ptr[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 2 ], interpol_ptr[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 3 ], interpol_ptr[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 4 ], interpol_ptr[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 5 ], interpol_ptr[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 6 ], interpol_ptr[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 7 ], interpol_ptr[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 8 ], interpol_ptr[ 8 ] ); + interpol_ptr = &FIR_Coefs[ RESAMPLER_DOWN_ORDER_FIR0 / 2 * ( FIR_Fracs - 1 - interpol_ind ) ]; + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 17 ], interpol_ptr[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 16 ], interpol_ptr[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 15 ], interpol_ptr[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 14 ], interpol_ptr[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 13 ], interpol_ptr[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 12 ], interpol_ptr[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 11 ], interpol_ptr[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 10 ], interpol_ptr[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, buf_ptr[ 9 ], interpol_ptr[ 8 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + case RESAMPLER_DOWN_ORDER_FIR1: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Inner product */ + res_Q6 = silk_SMULWB( silk_ADD32( buf_ptr[ 0 ], buf_ptr[ 23 ] ), FIR_Coefs[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 1 ], buf_ptr[ 22 ] ), FIR_Coefs[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 2 ], buf_ptr[ 21 ] ), FIR_Coefs[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 3 ], buf_ptr[ 20 ] ), FIR_Coefs[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 4 ], buf_ptr[ 19 ] ), FIR_Coefs[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 5 ], buf_ptr[ 18 ] ), FIR_Coefs[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 6 ], buf_ptr[ 17 ] ), FIR_Coefs[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 7 ], buf_ptr[ 16 ] ), FIR_Coefs[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 8 ], buf_ptr[ 15 ] ), FIR_Coefs[ 8 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 9 ], buf_ptr[ 14 ] ), FIR_Coefs[ 9 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 10 ], buf_ptr[ 13 ] ), FIR_Coefs[ 10 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 11 ], buf_ptr[ 12 ] ), FIR_Coefs[ 11 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + case RESAMPLER_DOWN_ORDER_FIR2: + for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { + /* Integer part gives pointer to buffered input */ + buf_ptr = buf + silk_RSHIFT( index_Q16, 16 ); + + /* Inner product */ + res_Q6 = silk_SMULWB( silk_ADD32( buf_ptr[ 0 ], buf_ptr[ 35 ] ), FIR_Coefs[ 0 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 1 ], buf_ptr[ 34 ] ), FIR_Coefs[ 1 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 2 ], buf_ptr[ 33 ] ), FIR_Coefs[ 2 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 3 ], buf_ptr[ 32 ] ), FIR_Coefs[ 3 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 4 ], buf_ptr[ 31 ] ), FIR_Coefs[ 4 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 5 ], buf_ptr[ 30 ] ), FIR_Coefs[ 5 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 6 ], buf_ptr[ 29 ] ), FIR_Coefs[ 6 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 7 ], buf_ptr[ 28 ] ), FIR_Coefs[ 7 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 8 ], buf_ptr[ 27 ] ), FIR_Coefs[ 8 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 9 ], buf_ptr[ 26 ] ), FIR_Coefs[ 9 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 10 ], buf_ptr[ 25 ] ), FIR_Coefs[ 10 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 11 ], buf_ptr[ 24 ] ), FIR_Coefs[ 11 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 12 ], buf_ptr[ 23 ] ), FIR_Coefs[ 12 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 13 ], buf_ptr[ 22 ] ), FIR_Coefs[ 13 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 14 ], buf_ptr[ 21 ] ), FIR_Coefs[ 14 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 15 ], buf_ptr[ 20 ] ), FIR_Coefs[ 15 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 16 ], buf_ptr[ 19 ] ), FIR_Coefs[ 16 ] ); + res_Q6 = silk_SMLAWB( res_Q6, silk_ADD32( buf_ptr[ 17 ], buf_ptr[ 18 ] ), FIR_Coefs[ 17 ] ); + + /* Scale down, saturate and store in output array */ + *out++ = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q6, 6 ) ); + } + break; + default: + celt_assert( 0 ); + } + return out; +} + +/* Resample with a 2nd order AR filter followed by FIR interpolation */ +void silk_resampler_private_down_FIR( + void *SS, /* I/O Resampler state */ + opus_int16 out[], /* O Output signal */ + const opus_int16 in[], /* I Input signal */ + opus_int32 inLen /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + opus_int32 nSamplesIn; + opus_int32 max_index_Q16, index_increment_Q16; + VARDECL( opus_int32, buf ); + const opus_int16 *FIR_Coefs; + SAVE_STACK; + + ALLOC( buf, S->batchSize + S->FIR_Order, opus_int32 ); + + /* Copy buffered samples to start of buffer */ + silk_memcpy( buf, S->sFIR.i32, S->FIR_Order * sizeof( opus_int32 ) ); + + FIR_Coefs = &S->Coefs[ 2 ]; + + /* Iterate over blocks of frameSizeIn input samples */ + index_increment_Q16 = S->invRatio_Q16; + while( 1 ) { + nSamplesIn = silk_min( inLen, S->batchSize ); + + /* Second-order AR filter (output in Q8) */ + silk_resampler_private_AR2( S->sIIR, &buf[ S->FIR_Order ], in, S->Coefs, nSamplesIn ); + + max_index_Q16 = silk_LSHIFT32( nSamplesIn, 16 ); + + /* Interpolate filtered signal */ + out = silk_resampler_private_down_FIR_INTERPOL( out, buf, FIR_Coefs, S->FIR_Order, + S->FIR_Fracs, max_index_Q16, index_increment_Q16 ); + + in += nSamplesIn; + inLen -= nSamplesIn; + + if( inLen > 1 ) { + /* More iterations to do; copy last part of filtered signal to beginning of buffer */ + silk_memcpy( buf, &buf[ nSamplesIn ], S->FIR_Order * sizeof( opus_int32 ) ); + } else { + break; + } + } + + /* Copy last part of filtered signal to the state for the next call */ + silk_memcpy( S->sFIR.i32, &buf[ nSamplesIn ], S->FIR_Order * sizeof( opus_int32 ) ); + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_up2_HQ.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_up2_HQ.c new file mode 100644 index 0000000000..c7ec8de365 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_private_up2_HQ.c @@ -0,0 +1,113 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" +#include "resampler_private.h" + +/* Upsample by a factor 2, high quality */ +/* Uses 2nd order allpass filters for the 2x upsampling, followed by a */ +/* notch filter just above Nyquist. */ +void silk_resampler_private_up2_HQ( + opus_int32 *S, /* I/O Resampler state [ 6 ] */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +) +{ + opus_int32 k; + opus_int32 in32, out32_1, out32_2, Y, X; + + silk_assert( silk_resampler_up2_hq_0[ 0 ] > 0 ); + silk_assert( silk_resampler_up2_hq_0[ 1 ] > 0 ); + silk_assert( silk_resampler_up2_hq_0[ 2 ] < 0 ); + silk_assert( silk_resampler_up2_hq_1[ 0 ] > 0 ); + silk_assert( silk_resampler_up2_hq_1[ 1 ] > 0 ); + silk_assert( silk_resampler_up2_hq_1[ 2 ] < 0 ); + + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len; k++ ) { + /* Convert to Q10 */ + in32 = silk_LSHIFT( (opus_int32)in[ k ], 10 ); + + /* First all-pass section for even output sample */ + Y = silk_SUB32( in32, S[ 0 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_0[ 0 ] ); + out32_1 = silk_ADD32( S[ 0 ], X ); + S[ 0 ] = silk_ADD32( in32, X ); + + /* Second all-pass section for even output sample */ + Y = silk_SUB32( out32_1, S[ 1 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_0[ 1 ] ); + out32_2 = silk_ADD32( S[ 1 ], X ); + S[ 1 ] = silk_ADD32( out32_1, X ); + + /* Third all-pass section for even output sample */ + Y = silk_SUB32( out32_2, S[ 2 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_up2_hq_0[ 2 ] ); + out32_1 = silk_ADD32( S[ 2 ], X ); + S[ 2 ] = silk_ADD32( out32_2, X ); + + /* Apply gain in Q15, convert back to int16 and store to output */ + out[ 2 * k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32_1, 10 ) ); + + /* First all-pass section for odd output sample */ + Y = silk_SUB32( in32, S[ 3 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_1[ 0 ] ); + out32_1 = silk_ADD32( S[ 3 ], X ); + S[ 3 ] = silk_ADD32( in32, X ); + + /* Second all-pass section for odd output sample */ + Y = silk_SUB32( out32_1, S[ 4 ] ); + X = silk_SMULWB( Y, silk_resampler_up2_hq_1[ 1 ] ); + out32_2 = silk_ADD32( S[ 4 ], X ); + S[ 4 ] = silk_ADD32( out32_1, X ); + + /* Third all-pass section for odd output sample */ + Y = silk_SUB32( out32_2, S[ 5 ] ); + X = silk_SMLAWB( Y, Y, silk_resampler_up2_hq_1[ 2 ] ); + out32_1 = silk_ADD32( S[ 5 ], X ); + S[ 5 ] = silk_ADD32( out32_2, X ); + + /* Apply gain in Q15, convert back to int16 and store to output */ + out[ 2 * k + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( out32_1, 10 ) ); + } +} + +void silk_resampler_private_up2_HQ_wrapper( + void *SS, /* I/O Resampler state (unused) */ + opus_int16 *out, /* O Output signal [ 2 * len ] */ + const opus_int16 *in, /* I Input signal [ len ] */ + opus_int32 len /* I Number of input samples */ +) +{ + silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; + silk_resampler_private_up2_HQ( S->sIIR, out, in, len ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.c new file mode 100644 index 0000000000..5e6b04476a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.c @@ -0,0 +1,96 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Filter coefficients for IIR/FIR polyphase resampling * + * Total size: 179 Words (358 Bytes) */ + +#include "resampler_private.h" + +/* Matlab code for the notch filter coefficients: */ +/* B = [1, 0.147, 1]; A = [1, 0.107, 0.89]; G = 0.93; freqz(G * B, A, 2^14, 16e3); axis([0, 8000, -10, 1]) */ +/* fprintf('\t%6d, %6d, %6d, %6d\n', round(B(2)*2^16), round(-A(2)*2^16), round((1-A(3))*2^16), round(G*2^15)) */ +/* const opus_int16 silk_resampler_up2_hq_notch[ 4 ] = { 9634, -7012, 7209, 30474 }; */ + +/* Tables with IIR and FIR coefficients for fractional downsamplers (123 Words) */ +silk_DWORD_ALIGN const opus_int16 silk_Resampler_3_4_COEFS[ 2 + 3 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { + -20694, -13867, + -49, 64, 17, -157, 353, -496, 163, 11047, 22205, + -39, 6, 91, -170, 186, 23, -896, 6336, 19928, + -19, -36, 102, -89, -24, 328, -951, 2568, 15909, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS[ 2 + 2 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { + -14457, -14019, + 64, 128, -122, 36, 310, -768, 584, 9267, 17733, + 12, 128, 18, -142, 288, -117, -865, 4123, 14459, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_2_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR1 / 2 ] = { + 616, -14323, + -10, 39, 58, -46, -84, 120, 184, -315, -541, 1284, 5380, 9024, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_3_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 16102, -15162, + -13, 0, 20, 26, 5, -31, -43, -4, 65, 90, 7, -157, -248, -44, 593, 1583, 2612, 3271, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_4_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 22500, -15099, + 3, -14, -20, -15, 2, 25, 37, 25, -16, -71, -107, -79, 50, 292, 623, 982, 1288, 1464, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_6_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { + 27540, -15257, + 17, 12, 8, 1, -10, -22, -30, -32, -22, 3, 44, 100, 168, 243, 317, 381, 429, 455, +}; + +silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ] = { + -2797, -6507, + 4697, 10739, + 1567, 8276, +}; + +/* Table with interplation fractions of 1/24, 3/24, 5/24, ... , 23/24 : 23/24 (46 Words) */ +silk_DWORD_ALIGN const opus_int16 silk_resampler_frac_FIR_12[ 12 ][ RESAMPLER_ORDER_FIR_12 / 2 ] = { + { 189, -600, 617, 30567 }, + { 117, -159, -1070, 29704 }, + { 52, 221, -2392, 28276 }, + { -4, 529, -3350, 26341 }, + { -48, 758, -3956, 23973 }, + { -80, 905, -4235, 21254 }, + { -99, 972, -4222, 18278 }, + { -107, 967, -3957, 15143 }, + { -103, 896, -3487, 11950 }, + { -91, 773, -2865, 8798 }, + { -71, 611, -2143, 5784 }, + { -46, 425, -1375, 2996 }, +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.h new file mode 100644 index 0000000000..490b3388dc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_rom.h @@ -0,0 +1,68 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_FIX_RESAMPLER_ROM_H +#define SILK_FIX_RESAMPLER_ROM_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "typedef.h" +#include "resampler_structs.h" + +#define RESAMPLER_DOWN_ORDER_FIR0 18 +#define RESAMPLER_DOWN_ORDER_FIR1 24 +#define RESAMPLER_DOWN_ORDER_FIR2 36 +#define RESAMPLER_ORDER_FIR_12 8 + +/* Tables for 2x downsampler */ +static const opus_int16 silk_resampler_down2_0 = 9872; +static const opus_int16 silk_resampler_down2_1 = 39809 - 65536; + +/* Tables for 2x upsampler, high quality */ +static const opus_int16 silk_resampler_up2_hq_0[ 3 ] = { 1746, 14986, 39083 - 65536 }; +static const opus_int16 silk_resampler_up2_hq_1[ 3 ] = { 6854, 25769, 55542 - 65536 }; + +/* Tables with IIR and FIR coefficients for fractional downsamplers */ +extern const opus_int16 silk_Resampler_3_4_COEFS[ 2 + 3 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ]; +extern const opus_int16 silk_Resampler_2_3_COEFS[ 2 + 2 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ]; +extern const opus_int16 silk_Resampler_1_2_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR1 / 2 ]; +extern const opus_int16 silk_Resampler_1_3_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_1_4_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_1_6_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ]; +extern const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ]; + +/* Table with interplation fractions of 1/24, 3/24, ..., 23/24 */ +extern const opus_int16 silk_resampler_frac_FIR_12[ 12 ][ RESAMPLER_ORDER_FIR_12 / 2 ]; + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_FIX_RESAMPLER_ROM_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_structs.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_structs.h new file mode 100644 index 0000000000..9e9457d11c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/resampler_structs.h @@ -0,0 +1,60 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_RESAMPLER_STRUCTS_H +#define SILK_RESAMPLER_STRUCTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SILK_RESAMPLER_MAX_FIR_ORDER 36 +#define SILK_RESAMPLER_MAX_IIR_ORDER 6 + +typedef struct _silk_resampler_state_struct{ + opus_int32 sIIR[ SILK_RESAMPLER_MAX_IIR_ORDER ]; /* this must be the first element of this struct */ + union{ + opus_int32 i32[ SILK_RESAMPLER_MAX_FIR_ORDER ]; + opus_int16 i16[ SILK_RESAMPLER_MAX_FIR_ORDER ]; + } sFIR; + opus_int16 delayBuf[ 48 ]; + opus_int resampler_function; + opus_int batchSize; + opus_int32 invRatio_Q16; + opus_int FIR_Order; + opus_int FIR_Fracs; + opus_int Fs_in_kHz; + opus_int Fs_out_kHz; + opus_int inputDelay; + const opus_int16 *Coefs; +} silk_resampler_state_struct; + +#ifdef __cplusplus +} +#endif +#endif /* SILK_RESAMPLER_STRUCTS_H */ + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/shell_coder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/shell_coder.c new file mode 100644 index 0000000000..4af341474b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/shell_coder.c @@ -0,0 +1,151 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* shell coder; pulse-subframe length is hardcoded */ + +static OPUS_INLINE void combine_pulses( + opus_int *out, /* O combined pulses vector [len] */ + const opus_int *in, /* I input vector [2 * len] */ + const opus_int len /* I number of OUTPUT samples */ +) +{ + opus_int k; + for( k = 0; k < len; k++ ) { + out[ k ] = in[ 2 * k ] + in[ 2 * k + 1 ]; + } +} + +static OPUS_INLINE void encode_split( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int p_child1, /* I pulse amplitude of first child subframe */ + const opus_int p, /* I pulse amplitude of current subframe */ + const opus_uint8 *shell_table /* I table of shell cdfs */ +) +{ + if( p > 0 ) { + ec_enc_icdf( psRangeEnc, p_child1, &shell_table[ silk_shell_code_table_offsets[ p ] ], 8 ); + } +} + +static OPUS_INLINE void decode_split( + opus_int16 *p_child1, /* O pulse amplitude of first child subframe */ + opus_int16 *p_child2, /* O pulse amplitude of second child subframe */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int p, /* I pulse amplitude of current subframe */ + const opus_uint8 *shell_table /* I table of shell cdfs */ +) +{ + if( p > 0 ) { + p_child1[ 0 ] = ec_dec_icdf( psRangeDec, &shell_table[ silk_shell_code_table_offsets[ p ] ], 8 ); + p_child2[ 0 ] = p - p_child1[ 0 ]; + } else { + p_child1[ 0 ] = 0; + p_child2[ 0 ] = 0; + } +} + +/* Shell encoder, operates on one shell code frame of 16 pulses */ +void silk_shell_encoder( + ec_enc *psRangeEnc, /* I/O compressor data structure */ + const opus_int *pulses0 /* I data: nonnegative pulse amplitudes */ +) +{ + opus_int pulses1[ 8 ], pulses2[ 4 ], pulses3[ 2 ], pulses4[ 1 ]; + + /* this function operates on one shell code frame of 16 pulses */ + silk_assert( SHELL_CODEC_FRAME_LENGTH == 16 ); + + /* tree representation per pulse-subframe */ + combine_pulses( pulses1, pulses0, 8 ); + combine_pulses( pulses2, pulses1, 4 ); + combine_pulses( pulses3, pulses2, 2 ); + combine_pulses( pulses4, pulses3, 1 ); + + encode_split( psRangeEnc, pulses3[ 0 ], pulses4[ 0 ], silk_shell_code_table3 ); + + encode_split( psRangeEnc, pulses2[ 0 ], pulses3[ 0 ], silk_shell_code_table2 ); + + encode_split( psRangeEnc, pulses1[ 0 ], pulses2[ 0 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 0 ], pulses1[ 0 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 2 ], pulses1[ 1 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses1[ 2 ], pulses2[ 1 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 4 ], pulses1[ 2 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 6 ], pulses1[ 3 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses2[ 2 ], pulses3[ 1 ], silk_shell_code_table2 ); + + encode_split( psRangeEnc, pulses1[ 4 ], pulses2[ 2 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 8 ], pulses1[ 4 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 10 ], pulses1[ 5 ], silk_shell_code_table0 ); + + encode_split( psRangeEnc, pulses1[ 6 ], pulses2[ 3 ], silk_shell_code_table1 ); + encode_split( psRangeEnc, pulses0[ 12 ], pulses1[ 6 ], silk_shell_code_table0 ); + encode_split( psRangeEnc, pulses0[ 14 ], pulses1[ 7 ], silk_shell_code_table0 ); +} + + +/* Shell decoder, operates on one shell code frame of 16 pulses */ +void silk_shell_decoder( + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + const opus_int pulses4 /* I number of pulses per pulse-subframe */ +) +{ + opus_int16 pulses3[ 2 ], pulses2[ 4 ], pulses1[ 8 ]; + + /* this function operates on one shell code frame of 16 pulses */ + silk_assert( SHELL_CODEC_FRAME_LENGTH == 16 ); + + decode_split( &pulses3[ 0 ], &pulses3[ 1 ], psRangeDec, pulses4, silk_shell_code_table3 ); + + decode_split( &pulses2[ 0 ], &pulses2[ 1 ], psRangeDec, pulses3[ 0 ], silk_shell_code_table2 ); + + decode_split( &pulses1[ 0 ], &pulses1[ 1 ], psRangeDec, pulses2[ 0 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 0 ], &pulses0[ 1 ], psRangeDec, pulses1[ 0 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 2 ], &pulses0[ 3 ], psRangeDec, pulses1[ 1 ], silk_shell_code_table0 ); + + decode_split( &pulses1[ 2 ], &pulses1[ 3 ], psRangeDec, pulses2[ 1 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 4 ], &pulses0[ 5 ], psRangeDec, pulses1[ 2 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 6 ], &pulses0[ 7 ], psRangeDec, pulses1[ 3 ], silk_shell_code_table0 ); + + decode_split( &pulses2[ 2 ], &pulses2[ 3 ], psRangeDec, pulses3[ 1 ], silk_shell_code_table2 ); + + decode_split( &pulses1[ 4 ], &pulses1[ 5 ], psRangeDec, pulses2[ 2 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 8 ], &pulses0[ 9 ], psRangeDec, pulses1[ 4 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 10 ], &pulses0[ 11 ], psRangeDec, pulses1[ 5 ], silk_shell_code_table0 ); + + decode_split( &pulses1[ 6 ], &pulses1[ 7 ], psRangeDec, pulses2[ 3 ], silk_shell_code_table1 ); + decode_split( &pulses0[ 12 ], &pulses0[ 13 ], psRangeDec, pulses1[ 6 ], silk_shell_code_table0 ); + decode_split( &pulses0[ 14 ], &pulses0[ 15 ], psRangeDec, pulses1[ 7 ], silk_shell_code_table0 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sigm_Q15.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sigm_Q15.c new file mode 100644 index 0000000000..3c507d255b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sigm_Q15.c @@ -0,0 +1,76 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Approximate sigmoid function */ + +#include "SigProc_FIX.h" + +/* fprintf(1, '%d, ', round(1024 * ([1 ./ (1 + exp(-(1:5))), 1] - 1 ./ (1 + exp(-(0:5)))))); */ +static const opus_int32 sigm_LUT_slope_Q10[ 6 ] = { + 237, 153, 73, 30, 12, 7 +}; +/* fprintf(1, '%d, ', round(32767 * 1 ./ (1 + exp(-(0:5))))); */ +static const opus_int32 sigm_LUT_pos_Q15[ 6 ] = { + 16384, 23955, 28861, 31213, 32178, 32548 +}; +/* fprintf(1, '%d, ', round(32767 * 1 ./ (1 + exp((0:5))))); */ +static const opus_int32 sigm_LUT_neg_Q15[ 6 ] = { + 16384, 8812, 3906, 1554, 589, 219 +}; + +opus_int silk_sigm_Q15( + opus_int in_Q5 /* I */ +) +{ + opus_int ind; + + if( in_Q5 < 0 ) { + /* Negative input */ + in_Q5 = -in_Q5; + if( in_Q5 >= 6 * 32 ) { + return 0; /* Clip */ + } else { + /* Linear interpolation of look up table */ + ind = silk_RSHIFT( in_Q5, 5 ); + return( sigm_LUT_neg_Q15[ ind ] - silk_SMULBB( sigm_LUT_slope_Q10[ ind ], in_Q5 & 0x1F ) ); + } + } else { + /* Positive input */ + if( in_Q5 >= 6 * 32 ) { + return 32767; /* clip */ + } else { + /* Linear interpolation of look up table */ + ind = silk_RSHIFT( in_Q5, 5 ); + return( sigm_LUT_pos_Q15[ ind ] + silk_SMULBB( sigm_LUT_slope_Q10[ ind ], in_Q5 & 0x1F ) ); + } + } +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sort.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sort.c new file mode 100644 index 0000000000..4fba16f831 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sort.c @@ -0,0 +1,154 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* Insertion sort (fast for already almost sorted arrays): */ +/* Best case: O(n) for an already sorted array */ +/* Worst case: O(n^2) for an inversely sorted array */ +/* */ +/* Shell short: https://en.wikipedia.org/wiki/Shell_sort */ + +#include "SigProc_FIX.h" + +void silk_insertion_sort_increasing( + opus_int32 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + opus_int32 value; + opus_int i, j; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, increasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked for, check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value < a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} + +#ifdef FIXED_POINT +/* This function is only used by the fixed-point build */ +void silk_insertion_sort_decreasing_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + opus_int *idx, /* O Index vector for the sorted elements */ + const opus_int L, /* I Vector length */ + const opus_int K /* I Number of correctly sorted positions */ +) +{ + opus_int i, j; + opus_int value; + + /* Safety checks */ + celt_assert( K > 0 ); + celt_assert( L > 0 ); + celt_assert( L >= K ); + + /* Write start indices in index vector */ + for( i = 0; i < K; i++ ) { + idx[ i ] = i; + } + + /* Sort vector elements by value, decreasing order */ + for( i = 1; i < K; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + + /* If less than L values are asked for, check the remaining values, */ + /* but only spend CPU to ensure that the K first values are correct */ + for( i = K; i < L; i++ ) { + value = a[ i ]; + if( value > a[ K - 1 ] ) { + for( j = K - 2; ( j >= 0 ) && ( value > a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + idx[ j + 1 ] = idx[ j ]; /* Shift index */ + } + a[ j + 1 ] = value; /* Write value */ + idx[ j + 1 ] = i; /* Write index */ + } + } +} +#endif + +void silk_insertion_sort_increasing_all_values_int16( + opus_int16 *a, /* I/O Unsorted / Sorted vector */ + const opus_int L /* I Vector length */ +) +{ + opus_int value; + opus_int i, j; + + /* Safety checks */ + celt_assert( L > 0 ); + + /* Sort vector elements by value, increasing order */ + for( i = 1; i < L; i++ ) { + value = a[ i ]; + for( j = i - 1; ( j >= 0 ) && ( value < a[ j ] ); j-- ) { + a[ j + 1 ] = a[ j ]; /* Shift value */ + } + a[ j + 1 ] = value; /* Write value */ + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_LR_to_MS.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_LR_to_MS.c new file mode 100644 index 0000000000..751452cb19 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_LR_to_MS.c @@ -0,0 +1,229 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" +#include "stack_alloc.h" + +/* Convert Left/Right stereo signal to adaptive Mid/Side representation */ +void silk_stereo_LR_to_MS( + stereo_enc_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + opus_int8 ix[ 2 ][ 3 ], /* O Quantization indices */ + opus_int8 *mid_only_flag, /* O Flag: only mid signal coded */ + opus_int32 mid_side_rates_bps[], /* O Bitrates for mid and side signals */ + opus_int32 total_rate_bps, /* I Total bitrate */ + opus_int prev_speech_act_Q8, /* I Speech activity level in previous frame */ + opus_int toMono, /* I Last frame before a stereo->mono transition */ + opus_int fs_kHz, /* I Sample rate (kHz) */ + opus_int frame_length /* I Number of samples */ +) +{ + opus_int n, is10msFrame, denom_Q16, delta0_Q13, delta1_Q13; + opus_int32 sum, diff, smooth_coef_Q16, pred_Q13[ 2 ], pred0_Q13, pred1_Q13; + opus_int32 LP_ratio_Q14, HP_ratio_Q14, frac_Q16, frac_3_Q16, min_mid_rate_bps, width_Q14, w_Q24, deltaw_Q24; + VARDECL( opus_int16, side ); + VARDECL( opus_int16, LP_mid ); + VARDECL( opus_int16, HP_mid ); + VARDECL( opus_int16, LP_side ); + VARDECL( opus_int16, HP_side ); + opus_int16 *mid = &x1[ -2 ]; + SAVE_STACK; + + ALLOC( side, frame_length + 2, opus_int16 ); + /* Convert to basic mid/side signals */ + for( n = 0; n < frame_length + 2; n++ ) { + sum = x1[ n - 2 ] + (opus_int32)x2[ n - 2 ]; + diff = x1[ n - 2 ] - (opus_int32)x2[ n - 2 ]; + mid[ n ] = (opus_int16)silk_RSHIFT_ROUND( sum, 1 ); + side[ n ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( diff, 1 ) ); + } + + /* Buffering */ + silk_memcpy( mid, state->sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( side, state->sSide, 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sMid, &mid[ frame_length ], 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sSide, &side[ frame_length ], 2 * sizeof( opus_int16 ) ); + + /* LP and HP filter mid signal */ + ALLOC( LP_mid, frame_length, opus_int16 ); + ALLOC( HP_mid, frame_length, opus_int16 ); + for( n = 0; n < frame_length; n++ ) { + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 2 ); + LP_mid[ n ] = sum; + HP_mid[ n ] = mid[ n + 1 ] - sum; + } + + /* LP and HP filter side signal */ + ALLOC( LP_side, frame_length, opus_int16 ); + ALLOC( HP_side, frame_length, opus_int16 ); + for( n = 0; n < frame_length; n++ ) { + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT32( side[ n ] + (opus_int32)side[ n + 2 ], side[ n + 1 ], 1 ), 2 ); + LP_side[ n ] = sum; + HP_side[ n ] = side[ n + 1 ] - sum; + } + + /* Find energies and predictors */ + is10msFrame = frame_length == 10 * fs_kHz; + smooth_coef_Q16 = is10msFrame ? + SILK_FIX_CONST( STEREO_RATIO_SMOOTH_COEF / 2, 16 ) : + SILK_FIX_CONST( STEREO_RATIO_SMOOTH_COEF, 16 ); + smooth_coef_Q16 = silk_SMULWB( silk_SMULBB( prev_speech_act_Q8, prev_speech_act_Q8 ), smooth_coef_Q16 ); + + pred_Q13[ 0 ] = silk_stereo_find_predictor( &LP_ratio_Q14, LP_mid, LP_side, &state->mid_side_amp_Q0[ 0 ], frame_length, smooth_coef_Q16 ); + pred_Q13[ 1 ] = silk_stereo_find_predictor( &HP_ratio_Q14, HP_mid, HP_side, &state->mid_side_amp_Q0[ 2 ], frame_length, smooth_coef_Q16 ); + /* Ratio of the norms of residual and mid signals */ + frac_Q16 = silk_SMLABB( HP_ratio_Q14, LP_ratio_Q14, 3 ); + frac_Q16 = silk_min( frac_Q16, SILK_FIX_CONST( 1, 16 ) ); + + /* Determine bitrate distribution between mid and side, and possibly reduce stereo width */ + total_rate_bps -= is10msFrame ? 1200 : 600; /* Subtract approximate bitrate for coding stereo parameters */ + if( total_rate_bps < 1 ) { + total_rate_bps = 1; + } + min_mid_rate_bps = silk_SMLABB( 2000, fs_kHz, 600 ); + silk_assert( min_mid_rate_bps < 32767 ); + /* Default bitrate distribution: 8 parts for Mid and (5+3*frac) parts for Side. so: mid_rate = ( 8 / ( 13 + 3 * frac ) ) * total_ rate */ + frac_3_Q16 = silk_MUL( 3, frac_Q16 ); + mid_side_rates_bps[ 0 ] = silk_DIV32_varQ( total_rate_bps, SILK_FIX_CONST( 8 + 5, 16 ) + frac_3_Q16, 16+3 ); + /* If Mid bitrate below minimum, reduce stereo width */ + if( mid_side_rates_bps[ 0 ] < min_mid_rate_bps ) { + mid_side_rates_bps[ 0 ] = min_mid_rate_bps; + mid_side_rates_bps[ 1 ] = total_rate_bps - mid_side_rates_bps[ 0 ]; + /* width = 4 * ( 2 * side_rate - min_rate ) / ( ( 1 + 3 * frac ) * min_rate ) */ + width_Q14 = silk_DIV32_varQ( silk_LSHIFT( mid_side_rates_bps[ 1 ], 1 ) - min_mid_rate_bps, + silk_SMULWB( SILK_FIX_CONST( 1, 16 ) + frac_3_Q16, min_mid_rate_bps ), 14+2 ); + width_Q14 = silk_LIMIT( width_Q14, 0, SILK_FIX_CONST( 1, 14 ) ); + } else { + mid_side_rates_bps[ 1 ] = total_rate_bps - mid_side_rates_bps[ 0 ]; + width_Q14 = SILK_FIX_CONST( 1, 14 ); + } + + /* Smoother */ + state->smth_width_Q14 = (opus_int16)silk_SMLAWB( state->smth_width_Q14, width_Q14 - state->smth_width_Q14, smooth_coef_Q16 ); + + /* At very low bitrates or for inputs that are nearly amplitude panned, switch to panned-mono coding */ + *mid_only_flag = 0; + if( toMono ) { + /* Last frame before stereo->mono transition; collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + silk_stereo_quant_pred( pred_Q13, ix ); + } else if( state->width_prev_Q14 == 0 && + ( 8 * total_rate_bps < 13 * min_mid_rate_bps || silk_SMULWB( frac_Q16, state->smth_width_Q14 ) < SILK_FIX_CONST( 0.05, 14 ) ) ) + { + /* Code as panned-mono; previous frame already had zero width */ + /* Scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + /* Collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + mid_side_rates_bps[ 0 ] = total_rate_bps; + mid_side_rates_bps[ 1 ] = 0; + *mid_only_flag = 1; + } else if( state->width_prev_Q14 != 0 && + ( 8 * total_rate_bps < 11 * min_mid_rate_bps || silk_SMULWB( frac_Q16, state->smth_width_Q14 ) < SILK_FIX_CONST( 0.02, 14 ) ) ) + { + /* Transition to zero-width stereo */ + /* Scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + /* Collapse stereo width */ + width_Q14 = 0; + pred_Q13[ 0 ] = 0; + pred_Q13[ 1 ] = 0; + } else if( state->smth_width_Q14 > SILK_FIX_CONST( 0.95, 14 ) ) { + /* Full-width stereo coding */ + silk_stereo_quant_pred( pred_Q13, ix ); + width_Q14 = SILK_FIX_CONST( 1, 14 ); + } else { + /* Reduced-width stereo coding; scale down and quantize predictors */ + pred_Q13[ 0 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 0 ] ), 14 ); + pred_Q13[ 1 ] = silk_RSHIFT( silk_SMULBB( state->smth_width_Q14, pred_Q13[ 1 ] ), 14 ); + silk_stereo_quant_pred( pred_Q13, ix ); + width_Q14 = state->smth_width_Q14; + } + + /* Make sure to keep on encoding until the tapered output has been transmitted */ + if( *mid_only_flag == 1 ) { + state->silent_side_len += frame_length - STEREO_INTERP_LEN_MS * fs_kHz; + if( state->silent_side_len < LA_SHAPE_MS * fs_kHz ) { + *mid_only_flag = 0; + } else { + /* Limit to avoid wrapping around */ + state->silent_side_len = 10000; + } + } else { + state->silent_side_len = 0; + } + + if( *mid_only_flag == 0 && mid_side_rates_bps[ 1 ] < 1 ) { + mid_side_rates_bps[ 1 ] = 1; + mid_side_rates_bps[ 0 ] = silk_max_int( 1, total_rate_bps - mid_side_rates_bps[ 1 ]); + } + + /* Interpolate predictors and subtract prediction from side channel */ + pred0_Q13 = -state->pred_prev_Q13[ 0 ]; + pred1_Q13 = -state->pred_prev_Q13[ 1 ]; + w_Q24 = silk_LSHIFT( state->width_prev_Q14, 10 ); + denom_Q16 = silk_DIV32_16( (opus_int32)1 << 16, STEREO_INTERP_LEN_MS * fs_kHz ); + delta0_Q13 = -silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 0 ] - state->pred_prev_Q13[ 0 ], denom_Q16 ), 16 ); + delta1_Q13 = -silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 1 ] - state->pred_prev_Q13[ 1 ], denom_Q16 ), 16 ); + deltaw_Q24 = silk_LSHIFT( silk_SMULWB( width_Q14 - state->width_prev_Q14, denom_Q16 ), 10 ); + for( n = 0; n < STEREO_INTERP_LEN_MS * fs_kHz; n++ ) { + pred0_Q13 += delta0_Q13; + pred1_Q13 += delta1_Q13; + w_Q24 += deltaw_Q24; + sum = silk_LSHIFT( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + + pred0_Q13 = -pred_Q13[ 0 ]; + pred1_Q13 = -pred_Q13[ 1 ]; + w_Q24 = silk_LSHIFT( width_Q14, 10 ); + for( n = STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++ ) { + sum = silk_LSHIFT( silk_ADD_LSHIFT32( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + state->pred_prev_Q13[ 0 ] = (opus_int16)pred_Q13[ 0 ]; + state->pred_prev_Q13[ 1 ] = (opus_int16)pred_Q13[ 1 ]; + state->width_prev_Q14 = (opus_int16)width_Q14; + RESTORE_STACK; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_MS_to_LR.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_MS_to_LR.c new file mode 100644 index 0000000000..1e01bb6eb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_MS_to_LR.c @@ -0,0 +1,85 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Convert adaptive Mid/Side representation to Left/Right stereo signal */ +void silk_stereo_MS_to_LR( + stereo_dec_state *state, /* I/O State */ + opus_int16 x1[], /* I/O Left input signal, becomes mid signal */ + opus_int16 x2[], /* I/O Right input signal, becomes side signal */ + const opus_int32 pred_Q13[], /* I Predictors */ + opus_int fs_kHz, /* I Samples rate (kHz) */ + opus_int frame_length /* I Number of samples */ +) +{ + opus_int n, denom_Q16, delta0_Q13, delta1_Q13; + opus_int32 sum, diff, pred0_Q13, pred1_Q13; + + /* Buffering */ + silk_memcpy( x1, state->sMid, 2 * sizeof( opus_int16 ) ); + silk_memcpy( x2, state->sSide, 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sMid, &x1[ frame_length ], 2 * sizeof( opus_int16 ) ); + silk_memcpy( state->sSide, &x2[ frame_length ], 2 * sizeof( opus_int16 ) ); + + /* Interpolate predictors and add prediction to side channel */ + pred0_Q13 = state->pred_prev_Q13[ 0 ]; + pred1_Q13 = state->pred_prev_Q13[ 1 ]; + denom_Q16 = silk_DIV32_16( (opus_int32)1 << 16, STEREO_INTERP_LEN_MS * fs_kHz ); + delta0_Q13 = silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 0 ] - state->pred_prev_Q13[ 0 ], denom_Q16 ), 16 ); + delta1_Q13 = silk_RSHIFT_ROUND( silk_SMULBB( pred_Q13[ 1 ] - state->pred_prev_Q13[ 1 ], denom_Q16 ), 16 ); + for( n = 0; n < STEREO_INTERP_LEN_MS * fs_kHz; n++ ) { + pred0_Q13 += delta0_Q13; + pred1_Q13 += delta1_Q13; + sum = silk_LSHIFT( silk_ADD_LSHIFT32( x1[ n ] + (opus_int32)x1[ n + 2 ], x1[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_LSHIFT( (opus_int32)x2[ n + 1 ], 8 ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)x1[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + pred0_Q13 = pred_Q13[ 0 ]; + pred1_Q13 = pred_Q13[ 1 ]; + for( n = STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++ ) { + sum = silk_LSHIFT( silk_ADD_LSHIFT32( x1[ n ] + (opus_int32)x1[ n + 2 ], x1[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_SMLAWB( silk_LSHIFT( (opus_int32)x2[ n + 1 ], 8 ), sum, pred0_Q13 ); /* Q8 */ + sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)x1[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ + x2[ n + 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); + } + state->pred_prev_Q13[ 0 ] = pred_Q13[ 0 ]; + state->pred_prev_Q13[ 1 ] = pred_Q13[ 1 ]; + + /* Convert to left/right signals */ + for( n = 0; n < frame_length; n++ ) { + sum = x1[ n + 1 ] + (opus_int32)x2[ n + 1 ]; + diff = x1[ n + 1 ] - (opus_int32)x2[ n + 1 ]; + x1[ n + 1 ] = (opus_int16)silk_SAT16( sum ); + x2[ n + 1 ] = (opus_int16)silk_SAT16( diff ); + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_decode_pred.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_decode_pred.c new file mode 100644 index 0000000000..56ba3925e8 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_decode_pred.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Decode mid/side predictors */ +void silk_stereo_decode_pred( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int32 pred_Q13[] /* O Predictors */ +) +{ + opus_int n, ix[ 2 ][ 3 ]; + opus_int32 low_Q13, step_Q13; + + /* Entropy decoding */ + n = ec_dec_icdf( psRangeDec, silk_stereo_pred_joint_iCDF, 8 ); + ix[ 0 ][ 2 ] = silk_DIV32_16( n, 5 ); + ix[ 1 ][ 2 ] = n - 5 * ix[ 0 ][ 2 ]; + for( n = 0; n < 2; n++ ) { + ix[ n ][ 0 ] = ec_dec_icdf( psRangeDec, silk_uniform3_iCDF, 8 ); + ix[ n ][ 1 ] = ec_dec_icdf( psRangeDec, silk_uniform5_iCDF, 8 ); + } + + /* Dequantize */ + for( n = 0; n < 2; n++ ) { + ix[ n ][ 0 ] += 3 * ix[ n ][ 2 ]; + low_Q13 = silk_stereo_pred_quant_Q13[ ix[ n ][ 0 ] ]; + step_Q13 = silk_SMULWB( silk_stereo_pred_quant_Q13[ ix[ n ][ 0 ] + 1 ] - low_Q13, + SILK_FIX_CONST( 0.5 / STEREO_QUANT_SUB_STEPS, 16 ) ); + pred_Q13[ n ] = silk_SMLABB( low_Q13, step_Q13, 2 * ix[ n ][ 1 ] + 1 ); + } + + /* Subtract second from first predictor (helps when actually applying these) */ + pred_Q13[ 0 ] -= pred_Q13[ 1 ]; +} + +/* Decode mid-only flag */ +void silk_stereo_decode_mid_only( + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int *decode_only_mid /* O Flag that only mid channel has been coded */ +) +{ + /* Decode flag that only mid channel is coded */ + *decode_only_mid = ec_dec_icdf( psRangeDec, silk_stereo_only_code_mid_iCDF, 8 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_encode_pred.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_encode_pred.c new file mode 100644 index 0000000000..03becb6736 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_encode_pred.c @@ -0,0 +1,62 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Entropy code the mid/side quantization indices */ +void silk_stereo_encode_pred( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 ix[ 2 ][ 3 ] /* I Quantization indices */ +) +{ + opus_int n; + + /* Entropy coding */ + n = 5 * ix[ 0 ][ 2 ] + ix[ 1 ][ 2 ]; + celt_assert( n < 25 ); + ec_enc_icdf( psRangeEnc, n, silk_stereo_pred_joint_iCDF, 8 ); + for( n = 0; n < 2; n++ ) { + celt_assert( ix[ n ][ 0 ] < 3 ); + celt_assert( ix[ n ][ 1 ] < STEREO_QUANT_SUB_STEPS ); + ec_enc_icdf( psRangeEnc, ix[ n ][ 0 ], silk_uniform3_iCDF, 8 ); + ec_enc_icdf( psRangeEnc, ix[ n ][ 1 ], silk_uniform5_iCDF, 8 ); + } +} + +/* Entropy code the mid-only flag */ +void silk_stereo_encode_mid_only( + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + opus_int8 mid_only_flag +) +{ + /* Encode flag that only mid channel is coded */ + ec_enc_icdf( psRangeEnc, mid_only_flag, silk_stereo_only_code_mid_iCDF, 8 ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_find_predictor.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_find_predictor.c new file mode 100644 index 0000000000..e30e90bddc --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_find_predictor.c @@ -0,0 +1,79 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Find least-squares prediction gain for one signal based on another and quantize it */ +opus_int32 silk_stereo_find_predictor( /* O Returns predictor in Q13 */ + opus_int32 *ratio_Q14, /* O Ratio of residual and mid energies */ + const opus_int16 x[], /* I Basis signal */ + const opus_int16 y[], /* I Target signal */ + opus_int32 mid_res_amp_Q0[], /* I/O Smoothed mid, residual norms */ + opus_int length, /* I Number of samples */ + opus_int smooth_coef_Q16 /* I Smoothing coefficient */ +) +{ + opus_int scale, scale1, scale2; + opus_int32 nrgx, nrgy, corr, pred_Q13, pred2_Q10; + + /* Find predictor */ + silk_sum_sqr_shift( &nrgx, &scale1, x, length ); + silk_sum_sqr_shift( &nrgy, &scale2, y, length ); + scale = silk_max_int( scale1, scale2 ); + scale = scale + ( scale & 1 ); /* make even */ + nrgy = silk_RSHIFT32( nrgy, scale - scale2 ); + nrgx = silk_RSHIFT32( nrgx, scale - scale1 ); + nrgx = silk_max_int( nrgx, 1 ); + corr = silk_inner_prod_aligned_scale( x, y, scale, length ); + pred_Q13 = silk_DIV32_varQ( corr, nrgx, 13 ); + pred_Q13 = silk_LIMIT( pred_Q13, -(1 << 14), 1 << 14 ); + pred2_Q10 = silk_SMULWB( pred_Q13, pred_Q13 ); + + /* Faster update for signals with large prediction parameters */ + smooth_coef_Q16 = (opus_int)silk_max_int( smooth_coef_Q16, silk_abs( pred2_Q10 ) ); + + /* Smoothed mid and residual norms */ + silk_assert( smooth_coef_Q16 < 32768 ); + scale = silk_RSHIFT( scale, 1 ); + mid_res_amp_Q0[ 0 ] = silk_SMLAWB( mid_res_amp_Q0[ 0 ], silk_LSHIFT( silk_SQRT_APPROX( nrgx ), scale ) - mid_res_amp_Q0[ 0 ], + smooth_coef_Q16 ); + /* Residual energy = nrgy - 2 * pred * corr + pred^2 * nrgx */ + nrgy = silk_SUB_LSHIFT32( nrgy, silk_SMULWB( corr, pred_Q13 ), 3 + 1 ); + nrgy = silk_ADD_LSHIFT32( nrgy, silk_SMULWB( nrgx, pred2_Q10 ), 6 ); + mid_res_amp_Q0[ 1 ] = silk_SMLAWB( mid_res_amp_Q0[ 1 ], silk_LSHIFT( silk_SQRT_APPROX( nrgy ), scale ) - mid_res_amp_Q0[ 1 ], + smooth_coef_Q16 ); + + /* Ratio of smoothed residual and mid norms */ + *ratio_Q14 = silk_DIV32_varQ( mid_res_amp_Q0[ 1 ], silk_max( mid_res_amp_Q0[ 0 ], 1 ), 14 ); + *ratio_Q14 = silk_LIMIT( *ratio_Q14, 0, 32767 ); + + return pred_Q13; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_quant_pred.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_quant_pred.c new file mode 100644 index 0000000000..d4ced6c3e8 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/stereo_quant_pred.c @@ -0,0 +1,73 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "main.h" + +/* Quantize mid/side predictors */ +void silk_stereo_quant_pred( + opus_int32 pred_Q13[], /* I/O Predictors (out: quantized) */ + opus_int8 ix[ 2 ][ 3 ] /* O Quantization indices */ +) +{ + opus_int i, j, n; + opus_int32 low_Q13, step_Q13, lvl_Q13, err_min_Q13, err_Q13, quant_pred_Q13 = 0; + + /* Quantize */ + for( n = 0; n < 2; n++ ) { + /* Brute-force search over quantization levels */ + err_min_Q13 = silk_int32_MAX; + for( i = 0; i < STEREO_QUANT_TAB_SIZE - 1; i++ ) { + low_Q13 = silk_stereo_pred_quant_Q13[ i ]; + step_Q13 = silk_SMULWB( silk_stereo_pred_quant_Q13[ i + 1 ] - low_Q13, + SILK_FIX_CONST( 0.5 / STEREO_QUANT_SUB_STEPS, 16 ) ); + for( j = 0; j < STEREO_QUANT_SUB_STEPS; j++ ) { + lvl_Q13 = silk_SMLABB( low_Q13, step_Q13, 2 * j + 1 ); + err_Q13 = silk_abs( pred_Q13[ n ] - lvl_Q13 ); + if( err_Q13 < err_min_Q13 ) { + err_min_Q13 = err_Q13; + quant_pred_Q13 = lvl_Q13; + ix[ n ][ 0 ] = i; + ix[ n ][ 1 ] = j; + } else { + /* Error increasing, so we're past the optimum */ + goto done; + } + } + } + done: + ix[ n ][ 2 ] = silk_DIV32_16( ix[ n ][ 0 ], 3 ); + ix[ n ][ 0 ] -= ix[ n ][ 2 ] * 3; + pred_Q13[ n ] = quant_pred_Q13; + } + + /* Subtract second from first predictor (helps when actually applying these) */ + pred_Q13[ 0 ] -= pred_Q13[ 1 ]; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/structs.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/structs.h new file mode 100644 index 0000000000..38243be1ea --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/structs.h @@ -0,0 +1,357 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_STRUCTS_H +#define SILK_STRUCTS_H + +#include "typedef.h" +#include "SigProc_FIX.h" +#include "define.h" +#include "entenc.h" +#include "entdec.h" + +#ifdef ENABLE_DEEP_PLC +#include "lpcnet.h" +#include "lpcnet_private.h" +#endif + +#ifdef ENABLE_DRED +#include "dred_encoder.h" +#include "dred_decoder.h" +#endif + +#ifdef ENABLE_OSCE +#include "osce_config.h" +#include "osce_structs.h" +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/************************************/ +/* Noise shaping quantization state */ +/************************************/ +typedef struct { + opus_int16 xq[ 2 * MAX_FRAME_LENGTH ]; /* Buffer for quantized output signal */ + opus_int32 sLTP_shp_Q14[ 2 * MAX_FRAME_LENGTH ]; + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 sLF_AR_shp_Q14; + opus_int32 sDiff_shp_Q14; + opus_int lagPrev; + opus_int sLTP_buf_idx; + opus_int sLTP_shp_buf_idx; + opus_int32 rand_seed; + opus_int32 prev_gain_Q16; + opus_int rewhite_flag; +} silk_nsq_state; + +/********************************/ +/* VAD state */ +/********************************/ +typedef struct { + opus_int32 AnaState[ 2 ]; /* Analysis filterbank state: 0-8 kHz */ + opus_int32 AnaState1[ 2 ]; /* Analysis filterbank state: 0-4 kHz */ + opus_int32 AnaState2[ 2 ]; /* Analysis filterbank state: 0-2 kHz */ + opus_int32 XnrgSubfr[ VAD_N_BANDS ]; /* Subframe energies */ + opus_int32 NrgRatioSmth_Q8[ VAD_N_BANDS ]; /* Smoothed energy level in each band */ + opus_int16 HPstate; /* State of differentiator in the lowest band */ + opus_int32 NL[ VAD_N_BANDS ]; /* Noise energy level in each band */ + opus_int32 inv_NL[ VAD_N_BANDS ]; /* Inverse noise energy level in each band */ + opus_int32 NoiseLevelBias[ VAD_N_BANDS ]; /* Noise level estimator bias/offset */ + opus_int32 counter; /* Frame counter used in the initial phase */ +} silk_VAD_state; + +/* Variable cut-off low-pass filter state */ +typedef struct { + opus_int32 In_LP_State[ 2 ]; /* Low pass filter state */ + opus_int32 transition_frame_no; /* Counter which is mapped to a cut-off frequency */ + opus_int mode; /* Operating mode, <0: switch down, >0: switch up; 0: do nothing */ + opus_int32 saved_fs_kHz; /* If non-zero, holds the last sampling rate before a bandwidth switching reset. */ +} silk_LP_state; + +/* Structure containing NLSF codebook */ +typedef struct { + const opus_int16 nVectors; + const opus_int16 order; + const opus_int16 quantStepSize_Q16; + const opus_int16 invQuantStepSize_Q6; + const opus_uint8 *CB1_NLSF_Q8; + const opus_int16 *CB1_Wght_Q9; + const opus_uint8 *CB1_iCDF; + const opus_uint8 *pred_Q8; + const opus_uint8 *ec_sel; + const opus_uint8 *ec_iCDF; + const opus_uint8 *ec_Rates_Q5; + const opus_int16 *deltaMin_Q15; +} silk_NLSF_CB_struct; + +typedef struct { + opus_int16 pred_prev_Q13[ 2 ]; + opus_int16 sMid[ 2 ]; + opus_int16 sSide[ 2 ]; + opus_int32 mid_side_amp_Q0[ 4 ]; + opus_int16 smth_width_Q14; + opus_int16 width_prev_Q14; + opus_int16 silent_side_len; + opus_int8 predIx[ MAX_FRAMES_PER_PACKET ][ 2 ][ 3 ]; + opus_int8 mid_only_flags[ MAX_FRAMES_PER_PACKET ]; +} stereo_enc_state; + +typedef struct { + opus_int16 pred_prev_Q13[ 2 ]; + opus_int16 sMid[ 2 ]; + opus_int16 sSide[ 2 ]; +} stereo_dec_state; + +typedef struct { + opus_int8 GainsIndices[ MAX_NB_SUBFR ]; + opus_int8 LTPIndex[ MAX_NB_SUBFR ]; + opus_int8 NLSFIndices[ MAX_LPC_ORDER + 1 ]; + opus_int16 lagIndex; + opus_int8 contourIndex; + opus_int8 signalType; + opus_int8 quantOffsetType; + opus_int8 NLSFInterpCoef_Q2; + opus_int8 PERIndex; + opus_int8 LTP_scaleIndex; + opus_int8 Seed; +} SideInfoIndices; + +/********************************/ +/* Encoder state */ +/********************************/ +typedef struct { + opus_int32 In_HP_State[ 2 ]; /* High pass filter state */ + opus_int32 variable_HP_smth1_Q15; /* State of first smoother */ + opus_int32 variable_HP_smth2_Q15; /* State of second smoother */ + silk_LP_state sLP; /* Low pass filter state */ + silk_VAD_state sVAD; /* Voice activity detector state */ + silk_nsq_state sNSQ; /* Noise Shape Quantizer State */ + opus_int16 prev_NLSFq_Q15[ MAX_LPC_ORDER ]; /* Previously quantized NLSF vector */ + opus_int speech_activity_Q8; /* Speech activity */ + opus_int allow_bandwidth_switch; /* Flag indicating that switching of internal bandwidth is allowed */ + opus_int8 LBRRprevLastGainIndex; + opus_int8 prevSignalType; + opus_int prevLag; + opus_int pitch_LPC_win_length; + opus_int max_pitch_lag; /* Highest possible pitch lag (samples) */ + opus_int32 API_fs_Hz; /* API sampling frequency (Hz) */ + opus_int32 prev_API_fs_Hz; /* Previous API sampling frequency (Hz) */ + opus_int maxInternal_fs_Hz; /* Maximum internal sampling frequency (Hz) */ + opus_int minInternal_fs_Hz; /* Minimum internal sampling frequency (Hz) */ + opus_int desiredInternal_fs_Hz; /* Soft request for internal sampling frequency (Hz) */ + opus_int fs_kHz; /* Internal sampling frequency (kHz) */ + opus_int nb_subfr; /* Number of 5 ms subframes in a frame */ + opus_int frame_length; /* Frame length (samples) */ + opus_int subfr_length; /* Subframe length (samples) */ + opus_int ltp_mem_length; /* Length of LTP memory */ + opus_int la_pitch; /* Look-ahead for pitch analysis (samples) */ + opus_int la_shape; /* Look-ahead for noise shape analysis (samples) */ + opus_int shapeWinLength; /* Window length for noise shape analysis (samples) */ + opus_int32 TargetRate_bps; /* Target bitrate (bps) */ + opus_int PacketSize_ms; /* Number of milliseconds to put in each packet */ + opus_int PacketLoss_perc; /* Packet loss rate measured by farend */ + opus_int32 frameCounter; + opus_int Complexity; /* Complexity setting */ + opus_int nStatesDelayedDecision; /* Number of states in delayed decision quantization */ + opus_int useInterpolatedNLSFs; /* Flag for using NLSF interpolation */ + opus_int shapingLPCOrder; /* Filter order for noise shaping filters */ + opus_int predictLPCOrder; /* Filter order for prediction filters */ + opus_int pitchEstimationComplexity; /* Complexity level for pitch estimator */ + opus_int pitchEstimationLPCOrder; /* Whitening filter order for pitch estimator */ + opus_int32 pitchEstimationThreshold_Q16; /* Threshold for pitch estimator */ + opus_int32 sum_log_gain_Q7; /* Cumulative max prediction gain */ + opus_int NLSF_MSVQ_Survivors; /* Number of survivors in NLSF MSVQ */ + opus_int first_frame_after_reset; /* Flag for deactivating NLSF interpolation, pitch prediction */ + opus_int controlled_since_last_payload; /* Flag for ensuring codec_control only runs once per packet */ + opus_int warping_Q16; /* Warping parameter for warped noise shaping */ + opus_int useCBR; /* Flag to enable constant bitrate */ + opus_int prefillFlag; /* Flag to indicate that only buffers are prefilled, no coding */ + const opus_uint8 *pitch_lag_low_bits_iCDF; /* Pointer to iCDF table for low bits of pitch lag index */ + const opus_uint8 *pitch_contour_iCDF; /* Pointer to iCDF table for pitch contour index */ + const silk_NLSF_CB_struct *psNLSF_CB; /* Pointer to NLSF codebook */ + opus_int input_quality_bands_Q15[ VAD_N_BANDS ]; + opus_int input_tilt_Q15; + opus_int SNR_dB_Q7; /* Quality setting */ + + opus_int8 VAD_flags[ MAX_FRAMES_PER_PACKET ]; + opus_int8 LBRR_flag; + opus_int LBRR_flags[ MAX_FRAMES_PER_PACKET ]; + + SideInfoIndices indices; + opus_int8 pulses[ MAX_FRAME_LENGTH ]; + + int arch; + + /* Input/output buffering */ + opus_int16 inputBuf[ MAX_FRAME_LENGTH + 2 ]; /* Buffer containing input signal */ + opus_int inputBufIx; + opus_int nFramesPerPacket; + opus_int nFramesEncoded; /* Number of frames analyzed in current packet */ + + opus_int nChannelsAPI; + opus_int nChannelsInternal; + opus_int channelNb; + + /* Parameters For LTP scaling Control */ + opus_int frames_since_onset; + + /* Specifically for entropy coding */ + opus_int ec_prevSignalType; + opus_int16 ec_prevLagIndex; + + silk_resampler_state_struct resampler_state; + + /* DTX */ + opus_int useDTX; /* Flag to enable DTX */ + opus_int inDTX; /* Flag to signal DTX period */ + opus_int noSpeechCounter; /* Counts concecutive nonactive frames, used by DTX */ + + /* Inband Low Bitrate Redundancy (LBRR) data */ + opus_int useInBandFEC; /* Saves the API setting for query */ + opus_int LBRR_enabled; /* Depends on useInBandFRC, bitrate and packet loss rate */ + opus_int LBRR_GainIncreases; /* Gains increment for coding LBRR frames */ + SideInfoIndices indices_LBRR[ MAX_FRAMES_PER_PACKET ]; + opus_int8 pulses_LBRR[ MAX_FRAMES_PER_PACKET ][ MAX_FRAME_LENGTH ]; +} silk_encoder_state; + + +#ifdef ENABLE_OSCE +typedef struct { + OSCEFeatureState features; + OSCEState state; + int method; +} silk_OSCE_struct; +#endif + +/* Struct for Packet Loss Concealment */ +typedef struct { + opus_int32 pitchL_Q8; /* Pitch lag to use for voiced concealment */ + opus_int16 LTPCoef_Q14[ LTP_ORDER ]; /* LTP coeficients to use for voiced concealment */ + opus_int16 prevLPC_Q12[ MAX_LPC_ORDER ]; + opus_int last_frame_lost; /* Was previous frame lost */ + opus_int32 rand_seed; /* Seed for unvoiced signal generation */ + opus_int16 randScale_Q14; /* Scaling of unvoiced random signal */ + opus_int32 conc_energy; + opus_int conc_energy_shift; + opus_int16 prevLTP_scale_Q14; + opus_int32 prevGain_Q16[ 2 ]; + opus_int fs_kHz; + opus_int nb_subfr; + opus_int subfr_length; + opus_int enable_deep_plc; +} silk_PLC_struct; + +/* Struct for CNG */ +typedef struct { + opus_int32 CNG_exc_buf_Q14[ MAX_FRAME_LENGTH ]; + opus_int16 CNG_smth_NLSF_Q15[ MAX_LPC_ORDER ]; + opus_int32 CNG_synth_state[ MAX_LPC_ORDER ]; + opus_int32 CNG_smth_Gain_Q16; + opus_int32 rand_seed; + opus_int fs_kHz; +} silk_CNG_struct; + +/********************************/ +/* Decoder state */ +/********************************/ +typedef struct { +#ifdef ENABLE_OSCE + silk_OSCE_struct osce; +#endif +#define SILK_DECODER_STATE_RESET_START prev_gain_Q16 + opus_int32 prev_gain_Q16; + opus_int32 exc_Q14[ MAX_FRAME_LENGTH ]; + opus_int32 sLPC_Q14_buf[ MAX_LPC_ORDER ]; + opus_int16 outBuf[ MAX_FRAME_LENGTH + 2 * MAX_SUB_FRAME_LENGTH ]; /* Buffer for output signal */ + opus_int lagPrev; /* Previous Lag */ + opus_int8 LastGainIndex; /* Previous gain index */ + opus_int fs_kHz; /* Sampling frequency in kHz */ + opus_int32 fs_API_hz; /* API sample frequency (Hz) */ + opus_int nb_subfr; /* Number of 5 ms subframes in a frame */ + opus_int frame_length; /* Frame length (samples) */ + opus_int subfr_length; /* Subframe length (samples) */ + opus_int ltp_mem_length; /* Length of LTP memory */ + opus_int LPC_order; /* LPC order */ + opus_int16 prevNLSF_Q15[ MAX_LPC_ORDER ]; /* Used to interpolate LSFs */ + opus_int first_frame_after_reset; /* Flag for deactivating NLSF interpolation */ + const opus_uint8 *pitch_lag_low_bits_iCDF; /* Pointer to iCDF table for low bits of pitch lag index */ + const opus_uint8 *pitch_contour_iCDF; /* Pointer to iCDF table for pitch contour index */ + + /* For buffering payload in case of more frames per packet */ + opus_int nFramesDecoded; + opus_int nFramesPerPacket; + + /* Specifically for entropy coding */ + opus_int ec_prevSignalType; + opus_int16 ec_prevLagIndex; + + opus_int VAD_flags[ MAX_FRAMES_PER_PACKET ]; + opus_int LBRR_flag; + opus_int LBRR_flags[ MAX_FRAMES_PER_PACKET ]; + + silk_resampler_state_struct resampler_state; + + const silk_NLSF_CB_struct *psNLSF_CB; /* Pointer to NLSF codebook */ + + /* Quantization indices */ + SideInfoIndices indices; + + /* CNG state */ + silk_CNG_struct sCNG; + + /* Stuff used for PLC */ + opus_int lossCnt; + opus_int prevSignalType; + int arch; + + silk_PLC_struct sPLC; + +} silk_decoder_state; + +/************************/ +/* Decoder control */ +/************************/ +typedef struct { + /* Prediction and coding parameters */ + opus_int pitchL[ MAX_NB_SUBFR ]; + opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; + /* Holds interpolated and final coefficients, 4-byte aligned */ + silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; + opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; + opus_int LTP_scale_Q14; +} silk_decoder_control; + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sum_sqr_shift.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sum_sqr_shift.c new file mode 100644 index 0000000000..4fd0c3d7d5 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/sum_sqr_shift.c @@ -0,0 +1,83 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "SigProc_FIX.h" + +/* Compute number of bits to right shift the sum of squares of a vector */ +/* of int16s to make it fit in an int32 */ +void silk_sum_sqr_shift( + opus_int32 *energy, /* O Energy of x, after shifting to the right */ + opus_int *shift, /* O Number of bits right shift applied to energy */ + const opus_int16 *x, /* I Input vector */ + opus_int len /* I Length of input vector */ +) +{ + opus_int i, shft; + opus_uint32 nrg_tmp; + opus_int32 nrg; + + /* Do a first run with the maximum shift we could have. */ + shft = 31-silk_CLZ32(len); + /* Let's be conservative with rounding and start with nrg=len. */ + nrg = len; + for( i = 0; i < len - 1; i += 2 ) { + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + if( i < len ) { + /* One sample left to process */ + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + silk_assert( nrg >= 0 ); + /* Make sure the result will fit in a 32-bit signed integer with two bits + of headroom. */ + shft = silk_max_32(0, shft+3 - silk_CLZ32(nrg)); + nrg = 0; + for( i = 0 ; i < len - 1; i += 2 ) { + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + if( i < len ) { + /* One sample left to process */ + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + + silk_assert( nrg >= 0 ); + + /* Output arguments */ + *shift = shft; + *energy = nrg; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/table_LSF_cos.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/table_LSF_cos.c new file mode 100644 index 0000000000..ec9dc63927 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/table_LSF_cos.c @@ -0,0 +1,70 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +/* Cosine approximation table for LSF conversion */ +/* Q12 values (even) */ +const opus_int16 silk_LSFCosTab_FIX_Q12[ LSF_COS_TAB_SZ_FIX + 1 ] = { + 8192, 8190, 8182, 8170, + 8152, 8130, 8104, 8072, + 8034, 7994, 7946, 7896, + 7840, 7778, 7714, 7644, + 7568, 7490, 7406, 7318, + 7226, 7128, 7026, 6922, + 6812, 6698, 6580, 6458, + 6332, 6204, 6070, 5934, + 5792, 5648, 5502, 5352, + 5198, 5040, 4880, 4718, + 4552, 4382, 4212, 4038, + 3862, 3684, 3502, 3320, + 3136, 2948, 2760, 2570, + 2378, 2186, 1990, 1794, + 1598, 1400, 1202, 1002, + 802, 602, 402, 202, + 0, -202, -402, -602, + -802, -1002, -1202, -1400, + -1598, -1794, -1990, -2186, + -2378, -2570, -2760, -2948, + -3136, -3320, -3502, -3684, + -3862, -4038, -4212, -4382, + -4552, -4718, -4880, -5040, + -5198, -5352, -5502, -5648, + -5792, -5934, -6070, -6204, + -6332, -6458, -6580, -6698, + -6812, -6922, -7026, -7128, + -7226, -7318, -7406, -7490, + -7568, -7644, -7714, -7778, + -7840, -7896, -7946, -7994, + -8034, -8072, -8104, -8130, + -8152, -8170, -8182, -8190, + -8192 +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables.h new file mode 100644 index 0000000000..95230c451a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables.h @@ -0,0 +1,114 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_TABLES_H +#define SILK_TABLES_H + +#include "define.h" +#include "structs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Entropy coding tables (with size in bytes indicated) */ +extern const opus_uint8 silk_gain_iCDF[ 3 ][ N_LEVELS_QGAIN / 8 ]; /* 24 */ +extern const opus_uint8 silk_delta_gain_iCDF[ MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ]; /* 41 */ + +extern const opus_uint8 silk_pitch_lag_iCDF[ 2 * ( PITCH_EST_MAX_LAG_MS - PITCH_EST_MIN_LAG_MS ) ];/* 32 */ +extern const opus_uint8 silk_pitch_delta_iCDF[ 21 ]; /* 21 */ +extern const opus_uint8 silk_pitch_contour_iCDF[ 34 ]; /* 34 */ +extern const opus_uint8 silk_pitch_contour_NB_iCDF[ 11 ]; /* 11 */ +extern const opus_uint8 silk_pitch_contour_10_ms_iCDF[ 12 ]; /* 12 */ +extern const opus_uint8 silk_pitch_contour_10_ms_NB_iCDF[ 3 ]; /* 3 */ + +extern const opus_uint8 silk_pulses_per_block_iCDF[ N_RATE_LEVELS ][ SILK_MAX_PULSES + 2 ]; /* 180 */ +extern const opus_uint8 silk_pulses_per_block_BITS_Q5[ N_RATE_LEVELS - 1 ][ SILK_MAX_PULSES + 2 ]; /* 162 */ + +extern const opus_uint8 silk_rate_levels_iCDF[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ +extern const opus_uint8 silk_rate_levels_BITS_Q5[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ + +extern const opus_uint8 silk_max_pulses_table[ 4 ]; /* 4 */ + +extern const opus_uint8 silk_shell_code_table0[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table1[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table2[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table3[ 152 ]; /* 152 */ +extern const opus_uint8 silk_shell_code_table_offsets[ SILK_MAX_PULSES + 1 ]; /* 17 */ + +extern const opus_uint8 silk_lsb_iCDF[ 2 ]; /* 2 */ + +extern const opus_uint8 silk_sign_iCDF[ 42 ]; /* 42 */ + +extern const opus_uint8 silk_uniform3_iCDF[ 3 ]; /* 3 */ +extern const opus_uint8 silk_uniform4_iCDF[ 4 ]; /* 4 */ +extern const opus_uint8 silk_uniform5_iCDF[ 5 ]; /* 5 */ +extern const opus_uint8 silk_uniform6_iCDF[ 6 ]; /* 6 */ +extern const opus_uint8 silk_uniform8_iCDF[ 8 ]; /* 8 */ + +extern const opus_uint8 silk_NLSF_EXT_iCDF[ 7 ]; /* 7 */ + +extern const opus_uint8 silk_LTP_per_index_iCDF[ 3 ]; /* 3 */ +extern const opus_uint8 * const silk_LTP_gain_iCDF_ptrs[ NB_LTP_CBKS ]; /* 3 */ +extern const opus_uint8 * const silk_LTP_gain_BITS_Q5_ptrs[ NB_LTP_CBKS ]; /* 3 */ +extern const opus_int8 * const silk_LTP_vq_ptrs_Q7[ NB_LTP_CBKS ]; /* 168 */ +extern const opus_uint8 * const silk_LTP_vq_gain_ptrs_Q7[NB_LTP_CBKS]; +extern const opus_int8 silk_LTP_vq_sizes[ NB_LTP_CBKS ]; /* 3 */ + +extern const opus_uint8 silk_LTPscale_iCDF[ 3 ]; /* 4 */ +extern const opus_int16 silk_LTPScales_table_Q14[ 3 ]; /* 6 */ + +extern const opus_uint8 silk_type_offset_VAD_iCDF[ 4 ]; /* 4 */ +extern const opus_uint8 silk_type_offset_no_VAD_iCDF[ 2 ]; /* 2 */ + +extern const opus_int16 silk_stereo_pred_quant_Q13[ STEREO_QUANT_TAB_SIZE ]; /* 32 */ +extern const opus_uint8 silk_stereo_pred_joint_iCDF[ 25 ]; /* 25 */ +extern const opus_uint8 silk_stereo_only_code_mid_iCDF[ 2 ]; /* 2 */ + +extern const opus_uint8 * const silk_LBRR_flags_iCDF_ptr[ 2 ]; /* 10 */ + +extern const opus_uint8 silk_NLSF_interpolation_factor_iCDF[ 5 ]; /* 5 */ + +extern const silk_NLSF_CB_struct silk_NLSF_CB_WB; /* 1040 */ +extern const silk_NLSF_CB_struct silk_NLSF_CB_NB_MB; /* 728 */ + +/* Quantization offsets */ +extern const opus_int16 silk_Quantization_Offsets_Q10[ 2 ][ 2 ]; /* 8 */ + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +extern const opus_int32 silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NB ]; /* 60 */ +extern const opus_int32 silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NA ]; /* 60 */ + +/* Rom table with cosine values */ +extern const opus_int16 silk_LSFCosTab_FIX_Q12[ LSF_COS_TAB_SZ_FIX + 1 ]; /* 258 */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_LTP.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_LTP.c new file mode 100644 index 0000000000..5e12c8643e --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_LTP.c @@ -0,0 +1,294 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_LTP_per_index_iCDF[3] = { + 179, 99, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_0[8] = { + 71, 56, 43, 30, 21, 12, 6, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_1[16] = { + 199, 165, 144, 124, 109, 96, 84, 71, + 61, 51, 42, 32, 23, 15, 8, 0 +}; + +static const opus_uint8 silk_LTP_gain_iCDF_2[32] = { + 241, 225, 211, 199, 187, 175, 164, 153, + 142, 132, 123, 114, 105, 96, 88, 80, + 72, 64, 57, 50, 44, 38, 33, 29, + 24, 20, 16, 12, 9, 5, 2, 0 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_0[8] = { + 15, 131, 138, 138, 155, 155, 173, 173 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_1[16] = { + 69, 93, 115, 118, 131, 138, 141, 138, + 150, 150, 155, 150, 155, 160, 166, 160 +}; + +static const opus_uint8 silk_LTP_gain_BITS_Q5_2[32] = { + 131, 128, 134, 141, 141, 141, 145, 145, + 145, 150, 155, 155, 155, 155, 160, 160, + 160, 160, 166, 166, 173, 173, 182, 192, + 182, 192, 192, 192, 205, 192, 205, 224 +}; + +const opus_uint8 * const silk_LTP_gain_iCDF_ptrs[NB_LTP_CBKS] = { + silk_LTP_gain_iCDF_0, + silk_LTP_gain_iCDF_1, + silk_LTP_gain_iCDF_2 +}; + +const opus_uint8 * const silk_LTP_gain_BITS_Q5_ptrs[NB_LTP_CBKS] = { + silk_LTP_gain_BITS_Q5_0, + silk_LTP_gain_BITS_Q5_1, + silk_LTP_gain_BITS_Q5_2 +}; + +static const opus_int8 silk_LTP_gain_vq_0[8][5] = +{ +{ + 4, 6, 24, 7, 5 +}, +{ + 0, 0, 2, 0, 0 +}, +{ + 12, 28, 41, 13, -4 +}, +{ + -9, 15, 42, 25, 14 +}, +{ + 1, -2, 62, 41, -9 +}, +{ + -10, 37, 65, -4, 3 +}, +{ + -6, 4, 66, 7, -8 +}, +{ + 16, 14, 38, -3, 33 +} +}; + +static const opus_int8 silk_LTP_gain_vq_1[16][5] = +{ +{ + 13, 22, 39, 23, 12 +}, +{ + -1, 36, 64, 27, -6 +}, +{ + -7, 10, 55, 43, 17 +}, +{ + 1, 1, 8, 1, 1 +}, +{ + 6, -11, 74, 53, -9 +}, +{ + -12, 55, 76, -12, 8 +}, +{ + -3, 3, 93, 27, -4 +}, +{ + 26, 39, 59, 3, -8 +}, +{ + 2, 0, 77, 11, 9 +}, +{ + -8, 22, 44, -6, 7 +}, +{ + 40, 9, 26, 3, 9 +}, +{ + -7, 20, 101, -7, 4 +}, +{ + 3, -8, 42, 26, 0 +}, +{ + -15, 33, 68, 2, 23 +}, +{ + -2, 55, 46, -2, 15 +}, +{ + 3, -1, 21, 16, 41 +} +}; + +static const opus_int8 silk_LTP_gain_vq_2[32][5] = +{ +{ + -6, 27, 61, 39, 5 +}, +{ + -11, 42, 88, 4, 1 +}, +{ + -2, 60, 65, 6, -4 +}, +{ + -1, -5, 73, 56, 1 +}, +{ + -9, 19, 94, 29, -9 +}, +{ + 0, 12, 99, 6, 4 +}, +{ + 8, -19, 102, 46, -13 +}, +{ + 3, 2, 13, 3, 2 +}, +{ + 9, -21, 84, 72, -18 +}, +{ + -11, 46, 104, -22, 8 +}, +{ + 18, 38, 48, 23, 0 +}, +{ + -16, 70, 83, -21, 11 +}, +{ + 5, -11, 117, 22, -8 +}, +{ + -6, 23, 117, -12, 3 +}, +{ + 3, -8, 95, 28, 4 +}, +{ + -10, 15, 77, 60, -15 +}, +{ + -1, 4, 124, 2, -4 +}, +{ + 3, 38, 84, 24, -25 +}, +{ + 2, 13, 42, 13, 31 +}, +{ + 21, -4, 56, 46, -1 +}, +{ + -1, 35, 79, -13, 19 +}, +{ + -7, 65, 88, -9, -14 +}, +{ + 20, 4, 81, 49, -29 +}, +{ + 20, 0, 75, 3, -17 +}, +{ + 5, -9, 44, 92, -8 +}, +{ + 1, -3, 22, 69, 31 +}, +{ + -6, 95, 41, -12, 5 +}, +{ + 39, 67, 16, -4, 1 +}, +{ + 0, -6, 120, 55, -36 +}, +{ + -13, 44, 122, 4, -24 +}, +{ + 81, 5, 11, 3, 7 +}, +{ + 2, 0, 9, 10, 88 +} +}; + +const opus_int8 * const silk_LTP_vq_ptrs_Q7[NB_LTP_CBKS] = { + (opus_int8 *)&silk_LTP_gain_vq_0[0][0], + (opus_int8 *)&silk_LTP_gain_vq_1[0][0], + (opus_int8 *)&silk_LTP_gain_vq_2[0][0] +}; + +/* Maximum frequency-dependent response of the pitch taps above, + computed as max(abs(freqz(taps))) */ +static const opus_uint8 silk_LTP_gain_vq_0_gain[8] = { + 46, 2, 90, 87, 93, 91, 82, 98 +}; + +static const opus_uint8 silk_LTP_gain_vq_1_gain[16] = { + 109, 120, 118, 12, 113, 115, 117, 119, + 99, 59, 87, 111, 63, 111, 112, 80 +}; + +static const opus_uint8 silk_LTP_gain_vq_2_gain[32] = { + 126, 124, 125, 124, 129, 121, 126, 23, + 132, 127, 127, 127, 126, 127, 122, 133, + 130, 134, 101, 118, 119, 145, 126, 86, + 124, 120, 123, 119, 170, 173, 107, 109 +}; + +const opus_uint8 * const silk_LTP_vq_gain_ptrs_Q7[NB_LTP_CBKS] = { + &silk_LTP_gain_vq_0_gain[0], + &silk_LTP_gain_vq_1_gain[0], + &silk_LTP_gain_vq_2_gain[0] +}; + +const opus_int8 silk_LTP_vq_sizes[NB_LTP_CBKS] = { + 8, 16, 32 +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_NB_MB.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_NB_MB.c new file mode 100644 index 0000000000..195d5b95bd --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_NB_MB.c @@ -0,0 +1,195 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +static const opus_uint8 silk_NLSF_CB1_NB_MB_Q8[ 320 ] = { + 12, 35, 60, 83, 108, 132, 157, 180, + 206, 228, 15, 32, 55, 77, 101, 125, + 151, 175, 201, 225, 19, 42, 66, 89, + 114, 137, 162, 184, 209, 230, 12, 25, + 50, 72, 97, 120, 147, 172, 200, 223, + 26, 44, 69, 90, 114, 135, 159, 180, + 205, 225, 13, 22, 53, 80, 106, 130, + 156, 180, 205, 228, 15, 25, 44, 64, + 90, 115, 142, 168, 196, 222, 19, 24, + 62, 82, 100, 120, 145, 168, 190, 214, + 22, 31, 50, 79, 103, 120, 151, 170, + 203, 227, 21, 29, 45, 65, 106, 124, + 150, 171, 196, 224, 30, 49, 75, 97, + 121, 142, 165, 186, 209, 229, 19, 25, + 52, 70, 93, 116, 143, 166, 192, 219, + 26, 34, 62, 75, 97, 118, 145, 167, + 194, 217, 25, 33, 56, 70, 91, 113, + 143, 165, 196, 223, 21, 34, 51, 72, + 97, 117, 145, 171, 196, 222, 20, 29, + 50, 67, 90, 117, 144, 168, 197, 221, + 22, 31, 48, 66, 95, 117, 146, 168, + 196, 222, 24, 33, 51, 77, 116, 134, + 158, 180, 200, 224, 21, 28, 70, 87, + 106, 124, 149, 170, 194, 217, 26, 33, + 53, 64, 83, 117, 152, 173, 204, 225, + 27, 34, 65, 95, 108, 129, 155, 174, + 210, 225, 20, 26, 72, 99, 113, 131, + 154, 176, 200, 219, 34, 43, 61, 78, + 93, 114, 155, 177, 205, 229, 23, 29, + 54, 97, 124, 138, 163, 179, 209, 229, + 30, 38, 56, 89, 118, 129, 158, 178, + 200, 231, 21, 29, 49, 63, 85, 111, + 142, 163, 193, 222, 27, 48, 77, 103, + 133, 158, 179, 196, 215, 232, 29, 47, + 74, 99, 124, 151, 176, 198, 220, 237, + 33, 42, 61, 76, 93, 121, 155, 174, + 207, 225, 29, 53, 87, 112, 136, 154, + 170, 188, 208, 227, 24, 30, 52, 84, + 131, 150, 166, 186, 203, 229, 37, 48, + 64, 84, 104, 118, 156, 177, 201, 230 +}; + +static const opus_int16 silk_NLSF_CB1_Wght_Q9[ 320 ] = { + 2897, 2314, 2314, 2314, 2287, 2287, 2314, 2300, 2327, 2287, + 2888, 2580, 2394, 2367, 2314, 2274, 2274, 2274, 2274, 2194, + 2487, 2340, 2340, 2314, 2314, 2314, 2340, 2340, 2367, 2354, + 3216, 2766, 2340, 2340, 2314, 2274, 2221, 2207, 2261, 2194, + 2460, 2474, 2367, 2394, 2394, 2394, 2394, 2367, 2407, 2314, + 3479, 3056, 2127, 2207, 2274, 2274, 2274, 2287, 2314, 2261, + 3282, 3141, 2580, 2394, 2247, 2221, 2207, 2194, 2194, 2114, + 4096, 3845, 2221, 2620, 2620, 2407, 2314, 2394, 2367, 2074, + 3178, 3244, 2367, 2221, 2553, 2434, 2340, 2314, 2167, 2221, + 3338, 3488, 2726, 2194, 2261, 2460, 2354, 2367, 2207, 2101, + 2354, 2420, 2327, 2367, 2394, 2420, 2420, 2420, 2460, 2367, + 3779, 3629, 2434, 2527, 2367, 2274, 2274, 2300, 2207, 2048, + 3254, 3225, 2713, 2846, 2447, 2327, 2300, 2300, 2274, 2127, + 3263, 3300, 2753, 2806, 2447, 2261, 2261, 2247, 2127, 2101, + 2873, 2981, 2633, 2367, 2407, 2354, 2194, 2247, 2247, 2114, + 3225, 3197, 2633, 2580, 2274, 2181, 2247, 2221, 2221, 2141, + 3178, 3310, 2740, 2407, 2274, 2274, 2274, 2287, 2194, 2114, + 3141, 3272, 2460, 2061, 2287, 2500, 2367, 2487, 2434, 2181, + 3507, 3282, 2314, 2700, 2647, 2474, 2367, 2394, 2340, 2127, + 3423, 3535, 3038, 3056, 2300, 1950, 2221, 2274, 2274, 2274, + 3404, 3366, 2087, 2687, 2873, 2354, 2420, 2274, 2474, 2540, + 3760, 3488, 1950, 2660, 2897, 2527, 2394, 2367, 2460, 2261, + 3028, 3272, 2740, 2888, 2740, 2154, 2127, 2287, 2234, 2247, + 3695, 3657, 2025, 1969, 2660, 2700, 2580, 2500, 2327, 2367, + 3207, 3413, 2354, 2074, 2888, 2888, 2340, 2487, 2247, 2167, + 3338, 3366, 2846, 2780, 2327, 2154, 2274, 2287, 2114, 2061, + 2327, 2300, 2181, 2167, 2181, 2367, 2633, 2700, 2700, 2553, + 2407, 2434, 2221, 2261, 2221, 2221, 2340, 2420, 2607, 2700, + 3038, 3244, 2806, 2888, 2474, 2074, 2300, 2314, 2354, 2380, + 2221, 2154, 2127, 2287, 2500, 2793, 2793, 2620, 2580, 2367, + 3676, 3713, 2234, 1838, 2181, 2753, 2726, 2673, 2513, 2207, + 2793, 3160, 2726, 2553, 2846, 2513, 2181, 2394, 2221, 2181 +}; + +static const opus_uint8 silk_NLSF_CB1_iCDF_NB_MB[ 64 ] = { + 212, 178, 148, 129, 108, 96, 85, 82, + 79, 77, 61, 59, 57, 56, 51, 49, + 48, 45, 42, 41, 40, 38, 36, 34, + 31, 30, 21, 12, 10, 3, 1, 0, + 255, 245, 244, 236, 233, 225, 217, 203, + 190, 176, 175, 161, 149, 136, 125, 114, + 102, 91, 81, 71, 60, 52, 43, 35, + 28, 20, 19, 18, 12, 11, 5, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_SELECT_NB_MB[ 160 ] = { + 16, 0, 0, 0, 0, 99, 66, 36, + 36, 34, 36, 34, 34, 34, 34, 83, + 69, 36, 52, 34, 116, 102, 70, 68, + 68, 176, 102, 68, 68, 34, 65, 85, + 68, 84, 36, 116, 141, 152, 139, 170, + 132, 187, 184, 216, 137, 132, 249, 168, + 185, 139, 104, 102, 100, 68, 68, 178, + 218, 185, 185, 170, 244, 216, 187, 187, + 170, 244, 187, 187, 219, 138, 103, 155, + 184, 185, 137, 116, 183, 155, 152, 136, + 132, 217, 184, 184, 170, 164, 217, 171, + 155, 139, 244, 169, 184, 185, 170, 164, + 216, 223, 218, 138, 214, 143, 188, 218, + 168, 244, 141, 136, 155, 170, 168, 138, + 220, 219, 139, 164, 219, 202, 216, 137, + 168, 186, 246, 185, 139, 116, 185, 219, + 185, 138, 100, 100, 134, 100, 102, 34, + 68, 68, 100, 68, 168, 203, 221, 218, + 168, 167, 154, 136, 104, 70, 164, 246, + 171, 137, 139, 137, 155, 218, 219, 139 +}; + +static const opus_uint8 silk_NLSF_CB2_iCDF_NB_MB[ 72 ] = { + 255, 254, 253, 238, 14, 3, 2, 1, + 0, 255, 254, 252, 218, 35, 3, 2, + 1, 0, 255, 254, 250, 208, 59, 4, + 2, 1, 0, 255, 254, 246, 194, 71, + 10, 2, 1, 0, 255, 252, 236, 183, + 82, 8, 2, 1, 0, 255, 252, 235, + 180, 90, 17, 2, 1, 0, 255, 248, + 224, 171, 97, 30, 4, 1, 0, 255, + 254, 236, 173, 95, 37, 7, 1, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_BITS_NB_MB_Q5[ 72 ] = { + 255, 255, 255, 131, 6, 145, 255, 255, + 255, 255, 255, 236, 93, 15, 96, 255, + 255, 255, 255, 255, 194, 83, 25, 71, + 221, 255, 255, 255, 255, 162, 73, 34, + 66, 162, 255, 255, 255, 210, 126, 73, + 43, 57, 173, 255, 255, 255, 201, 125, + 71, 48, 58, 130, 255, 255, 255, 166, + 110, 73, 57, 62, 104, 210, 255, 255, + 251, 123, 65, 55, 68, 100, 171, 255 +}; + +static const opus_uint8 silk_NLSF_PRED_NB_MB_Q8[ 18 ] = { + 179, 138, 140, 148, 151, 149, 153, 151, + 163, 116, 67, 82, 59, 92, 72, 100, + 89, 92 +}; + +static const opus_int16 silk_NLSF_DELTA_MIN_NB_MB_Q15[ 11 ] = { + 250, 3, 6, 3, 3, 3, 4, 3, + 3, 3, 461 +}; + +const silk_NLSF_CB_struct silk_NLSF_CB_NB_MB = +{ + 32, + 10, + SILK_FIX_CONST( 0.18, 16 ), + SILK_FIX_CONST( 1.0 / 0.18, 6 ), + silk_NLSF_CB1_NB_MB_Q8, + silk_NLSF_CB1_Wght_Q9, + silk_NLSF_CB1_iCDF_NB_MB, + silk_NLSF_PRED_NB_MB_Q8, + silk_NLSF_CB2_SELECT_NB_MB, + silk_NLSF_CB2_iCDF_NB_MB, + silk_NLSF_CB2_BITS_NB_MB_Q5, + silk_NLSF_DELTA_MIN_NB_MB_Q15, +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_WB.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_WB.c new file mode 100644 index 0000000000..5cc9f57bff --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_NLSF_CB_WB.c @@ -0,0 +1,234 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +static const opus_uint8 silk_NLSF_CB1_WB_Q8[ 512 ] = { + 7, 23, 38, 54, 69, 85, 100, 116, + 131, 147, 162, 178, 193, 208, 223, 239, + 13, 25, 41, 55, 69, 83, 98, 112, + 127, 142, 157, 171, 187, 203, 220, 236, + 15, 21, 34, 51, 61, 78, 92, 106, + 126, 136, 152, 167, 185, 205, 225, 240, + 10, 21, 36, 50, 63, 79, 95, 110, + 126, 141, 157, 173, 189, 205, 221, 237, + 17, 20, 37, 51, 59, 78, 89, 107, + 123, 134, 150, 164, 184, 205, 224, 240, + 10, 15, 32, 51, 67, 81, 96, 112, + 129, 142, 158, 173, 189, 204, 220, 236, + 8, 21, 37, 51, 65, 79, 98, 113, + 126, 138, 155, 168, 179, 192, 209, 218, + 12, 15, 34, 55, 63, 78, 87, 108, + 118, 131, 148, 167, 185, 203, 219, 236, + 16, 19, 32, 36, 56, 79, 91, 108, + 118, 136, 154, 171, 186, 204, 220, 237, + 11, 28, 43, 58, 74, 89, 105, 120, + 135, 150, 165, 180, 196, 211, 226, 241, + 6, 16, 33, 46, 60, 75, 92, 107, + 123, 137, 156, 169, 185, 199, 214, 225, + 11, 19, 30, 44, 57, 74, 89, 105, + 121, 135, 152, 169, 186, 202, 218, 234, + 12, 19, 29, 46, 57, 71, 88, 100, + 120, 132, 148, 165, 182, 199, 216, 233, + 17, 23, 35, 46, 56, 77, 92, 106, + 123, 134, 152, 167, 185, 204, 222, 237, + 14, 17, 45, 53, 63, 75, 89, 107, + 115, 132, 151, 171, 188, 206, 221, 240, + 9, 16, 29, 40, 56, 71, 88, 103, + 119, 137, 154, 171, 189, 205, 222, 237, + 16, 19, 36, 48, 57, 76, 87, 105, + 118, 132, 150, 167, 185, 202, 218, 236, + 12, 17, 29, 54, 71, 81, 94, 104, + 126, 136, 149, 164, 182, 201, 221, 237, + 15, 28, 47, 62, 79, 97, 115, 129, + 142, 155, 168, 180, 194, 208, 223, 238, + 8, 14, 30, 45, 62, 78, 94, 111, + 127, 143, 159, 175, 192, 207, 223, 239, + 17, 30, 49, 62, 79, 92, 107, 119, + 132, 145, 160, 174, 190, 204, 220, 235, + 14, 19, 36, 45, 61, 76, 91, 108, + 121, 138, 154, 172, 189, 205, 222, 238, + 12, 18, 31, 45, 60, 76, 91, 107, + 123, 138, 154, 171, 187, 204, 221, 236, + 13, 17, 31, 43, 53, 70, 83, 103, + 114, 131, 149, 167, 185, 203, 220, 237, + 17, 22, 35, 42, 58, 78, 93, 110, + 125, 139, 155, 170, 188, 206, 224, 240, + 8, 15, 34, 50, 67, 83, 99, 115, + 131, 146, 162, 178, 193, 209, 224, 239, + 13, 16, 41, 66, 73, 86, 95, 111, + 128, 137, 150, 163, 183, 206, 225, 241, + 17, 25, 37, 52, 63, 75, 92, 102, + 119, 132, 144, 160, 175, 191, 212, 231, + 19, 31, 49, 65, 83, 100, 117, 133, + 147, 161, 174, 187, 200, 213, 227, 242, + 18, 31, 52, 68, 88, 103, 117, 126, + 138, 149, 163, 177, 192, 207, 223, 239, + 16, 29, 47, 61, 76, 90, 106, 119, + 133, 147, 161, 176, 193, 209, 224, 240, + 15, 21, 35, 50, 61, 73, 86, 97, + 110, 119, 129, 141, 175, 198, 218, 237 +}; + +static const opus_int16 silk_NLSF_CB1_WB_Wght_Q9[ 512 ] = { + 3657, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2963, 2963, 2925, 2846, + 3216, 3085, 2972, 3056, 3056, 3010, 3010, 3010, 2963, 2963, 3010, 2972, 2888, 2846, 2846, 2726, + 3920, 4014, 2981, 3207, 3207, 2934, 3056, 2846, 3122, 3244, 2925, 2846, 2620, 2553, 2780, 2925, + 3516, 3197, 3010, 3103, 3019, 2888, 2925, 2925, 2925, 2925, 2888, 2888, 2888, 2888, 2888, 2753, + 5054, 5054, 2934, 3573, 3385, 3056, 3085, 2793, 3160, 3160, 2972, 2846, 2513, 2540, 2753, 2888, + 4428, 4149, 2700, 2753, 2972, 3010, 2925, 2846, 2981, 3019, 2925, 2925, 2925, 2925, 2888, 2726, + 3620, 3019, 2972, 3056, 3056, 2873, 2806, 3056, 3216, 3047, 2981, 3291, 3291, 2981, 3310, 2991, + 5227, 5014, 2540, 3338, 3526, 3385, 3197, 3094, 3376, 2981, 2700, 2647, 2687, 2793, 2846, 2673, + 5081, 5174, 4615, 4428, 2460, 2897, 3047, 3207, 3169, 2687, 2740, 2888, 2846, 2793, 2846, 2700, + 3122, 2888, 2963, 2925, 2925, 2925, 2925, 2963, 2963, 2963, 2963, 2925, 2925, 2963, 2963, 2963, + 4202, 3207, 2981, 3103, 3010, 2888, 2888, 2925, 2972, 2873, 2916, 3019, 2972, 3010, 3197, 2873, + 3760, 3760, 3244, 3103, 2981, 2888, 2925, 2888, 2972, 2934, 2793, 2793, 2846, 2888, 2888, 2660, + 3854, 4014, 3207, 3122, 3244, 2934, 3047, 2963, 2963, 3085, 2846, 2793, 2793, 2793, 2793, 2580, + 3845, 4080, 3357, 3516, 3094, 2740, 3010, 2934, 3122, 3085, 2846, 2846, 2647, 2647, 2846, 2806, + 5147, 4894, 3225, 3845, 3441, 3169, 2897, 3413, 3451, 2700, 2580, 2673, 2740, 2846, 2806, 2753, + 4109, 3789, 3291, 3160, 2925, 2888, 2888, 2925, 2793, 2740, 2793, 2740, 2793, 2846, 2888, 2806, + 5081, 5054, 3047, 3545, 3244, 3056, 3085, 2944, 3103, 2897, 2740, 2740, 2740, 2846, 2793, 2620, + 4309, 4309, 2860, 2527, 3207, 3376, 3376, 3075, 3075, 3376, 3056, 2846, 2647, 2580, 2726, 2753, + 3056, 2916, 2806, 2888, 2740, 2687, 2897, 3103, 3150, 3150, 3216, 3169, 3056, 3010, 2963, 2846, + 4375, 3882, 2925, 2888, 2846, 2888, 2846, 2846, 2888, 2888, 2888, 2846, 2888, 2925, 2888, 2846, + 2981, 2916, 2916, 2981, 2981, 3056, 3122, 3216, 3150, 3056, 3010, 2972, 2972, 2972, 2925, 2740, + 4229, 4149, 3310, 3347, 2925, 2963, 2888, 2981, 2981, 2846, 2793, 2740, 2846, 2846, 2846, 2793, + 4080, 4014, 3103, 3010, 2925, 2925, 2925, 2888, 2925, 2925, 2846, 2846, 2846, 2793, 2888, 2780, + 4615, 4575, 3169, 3441, 3207, 2981, 2897, 3038, 3122, 2740, 2687, 2687, 2687, 2740, 2793, 2700, + 4149, 4269, 3789, 3657, 2726, 2780, 2888, 2888, 3010, 2972, 2925, 2846, 2687, 2687, 2793, 2888, + 4215, 3554, 2753, 2846, 2846, 2888, 2888, 2888, 2925, 2925, 2888, 2925, 2925, 2925, 2963, 2888, + 5174, 4921, 2261, 3432, 3789, 3479, 3347, 2846, 3310, 3479, 3150, 2897, 2460, 2487, 2753, 2925, + 3451, 3685, 3122, 3197, 3357, 3047, 3207, 3207, 2981, 3216, 3085, 2925, 2925, 2687, 2540, 2434, + 2981, 3010, 2793, 2793, 2740, 2793, 2846, 2972, 3056, 3103, 3150, 3150, 3150, 3103, 3010, 3010, + 2944, 2873, 2687, 2726, 2780, 3010, 3432, 3545, 3357, 3244, 3056, 3010, 2963, 2925, 2888, 2846, + 3019, 2944, 2897, 3010, 3010, 2972, 3019, 3103, 3056, 3056, 3010, 2888, 2846, 2925, 2925, 2888, + 3920, 3967, 3010, 3197, 3357, 3216, 3291, 3291, 3479, 3704, 3441, 2726, 2181, 2460, 2580, 2607 +}; + +static const opus_uint8 silk_NLSF_CB1_iCDF_WB[ 64 ] = { + 225, 204, 201, 184, 183, 175, 158, 154, + 153, 135, 119, 115, 113, 110, 109, 99, + 98, 95, 79, 68, 52, 50, 48, 45, + 43, 32, 31, 27, 18, 10, 3, 0, + 255, 251, 235, 230, 212, 201, 196, 182, + 167, 166, 163, 151, 138, 124, 110, 104, + 90, 78, 76, 70, 69, 57, 45, 34, + 24, 21, 11, 6, 5, 4, 3, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_SELECT_WB[ 256 ] = { + 0, 0, 0, 0, 0, 0, 0, 1, + 100, 102, 102, 68, 68, 36, 34, 96, + 164, 107, 158, 185, 180, 185, 139, 102, + 64, 66, 36, 34, 34, 0, 1, 32, + 208, 139, 141, 191, 152, 185, 155, 104, + 96, 171, 104, 166, 102, 102, 102, 132, + 1, 0, 0, 0, 0, 16, 16, 0, + 80, 109, 78, 107, 185, 139, 103, 101, + 208, 212, 141, 139, 173, 153, 123, 103, + 36, 0, 0, 0, 0, 0, 0, 1, + 48, 0, 0, 0, 0, 0, 0, 32, + 68, 135, 123, 119, 119, 103, 69, 98, + 68, 103, 120, 118, 118, 102, 71, 98, + 134, 136, 157, 184, 182, 153, 139, 134, + 208, 168, 248, 75, 189, 143, 121, 107, + 32, 49, 34, 34, 34, 0, 17, 2, + 210, 235, 139, 123, 185, 137, 105, 134, + 98, 135, 104, 182, 100, 183, 171, 134, + 100, 70, 68, 70, 66, 66, 34, 131, + 64, 166, 102, 68, 36, 2, 1, 0, + 134, 166, 102, 68, 34, 34, 66, 132, + 212, 246, 158, 139, 107, 107, 87, 102, + 100, 219, 125, 122, 137, 118, 103, 132, + 114, 135, 137, 105, 171, 106, 50, 34, + 164, 214, 141, 143, 185, 151, 121, 103, + 192, 34, 0, 0, 0, 0, 0, 1, + 208, 109, 74, 187, 134, 249, 159, 137, + 102, 110, 154, 118, 87, 101, 119, 101, + 0, 2, 0, 36, 36, 66, 68, 35, + 96, 164, 102, 100, 36, 0, 2, 33, + 167, 138, 174, 102, 100, 84, 2, 2, + 100, 107, 120, 119, 36, 197, 24, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_iCDF_WB[ 72 ] = { + 255, 254, 253, 244, 12, 3, 2, 1, + 0, 255, 254, 252, 224, 38, 3, 2, + 1, 0, 255, 254, 251, 209, 57, 4, + 2, 1, 0, 255, 254, 244, 195, 69, + 4, 2, 1, 0, 255, 251, 232, 184, + 84, 7, 2, 1, 0, 255, 254, 240, + 186, 86, 14, 2, 1, 0, 255, 254, + 239, 178, 91, 30, 5, 1, 0, 255, + 248, 227, 177, 100, 19, 2, 1, 0 +}; + +static const opus_uint8 silk_NLSF_CB2_BITS_WB_Q5[ 72 ] = { + 255, 255, 255, 156, 4, 154, 255, 255, + 255, 255, 255, 227, 102, 15, 92, 255, + 255, 255, 255, 255, 213, 83, 24, 72, + 236, 255, 255, 255, 255, 150, 76, 33, + 63, 214, 255, 255, 255, 190, 121, 77, + 43, 55, 185, 255, 255, 255, 245, 137, + 71, 43, 59, 139, 255, 255, 255, 255, + 131, 66, 50, 66, 107, 194, 255, 255, + 166, 116, 76, 55, 53, 125, 255, 255 +}; + +static const opus_uint8 silk_NLSF_PRED_WB_Q8[ 30 ] = { + 175, 148, 160, 176, 178, 173, 174, 164, + 177, 174, 196, 182, 198, 192, 182, 68, + 62, 66, 60, 72, 117, 85, 90, 118, + 136, 151, 142, 160, 142, 155 +}; + +static const opus_int16 silk_NLSF_DELTA_MIN_WB_Q15[ 17 ] = { + 100, 3, 40, 3, 3, 3, 5, 14, + 14, 10, 11, 3, 8, 9, 7, 3, + 347 +}; + +const silk_NLSF_CB_struct silk_NLSF_CB_WB = +{ + 32, + 16, + SILK_FIX_CONST( 0.15, 16 ), + SILK_FIX_CONST( 1.0 / 0.15, 6 ), + silk_NLSF_CB1_WB_Q8, + silk_NLSF_CB1_WB_Wght_Q9, + silk_NLSF_CB1_iCDF_WB, + silk_NLSF_PRED_WB_Q8, + silk_NLSF_CB2_SELECT_WB, + silk_NLSF_CB2_iCDF_WB, + silk_NLSF_CB2_BITS_WB_Q5, + silk_NLSF_DELTA_MIN_WB_Q15, +}; + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_gain.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_gain.c new file mode 100644 index 0000000000..37e41d890c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_gain.c @@ -0,0 +1,63 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +const opus_uint8 silk_gain_iCDF[ 3 ][ N_LEVELS_QGAIN / 8 ] = +{ +{ + 224, 112, 44, 15, 3, 2, 1, 0 +}, +{ + 254, 237, 192, 132, 70, 23, 4, 0 +}, +{ + 255, 252, 226, 155, 61, 11, 2, 0 +} +}; + +const opus_uint8 silk_delta_gain_iCDF[ MAX_DELTA_GAIN_QUANT - MIN_DELTA_GAIN_QUANT + 1 ] = { + 250, 245, 234, 203, 71, 50, 42, 38, + 35, 33, 31, 29, 28, 27, 26, 25, + 24, 23, 22, 21, 20, 19, 18, 17, + 16, 15, 14, 13, 12, 11, 10, 9, + 8, 7, 6, 5, 4, 3, 2, 1, + 0 +}; + +#ifdef __cplusplus +} +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_other.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_other.c new file mode 100644 index 0000000000..e34d90777b --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_other.c @@ -0,0 +1,124 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "structs.h" +#include "define.h" +#include "tables.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Tables for stereo predictor coding */ +const opus_int16 silk_stereo_pred_quant_Q13[ STEREO_QUANT_TAB_SIZE ] = { + -13732, -10050, -8266, -7526, -6500, -5000, -2950, -820, + 820, 2950, 5000, 6500, 7526, 8266, 10050, 13732 +}; +const opus_uint8 silk_stereo_pred_joint_iCDF[ 25 ] = { + 249, 247, 246, 245, 244, + 234, 210, 202, 201, 200, + 197, 174, 82, 59, 56, + 55, 54, 46, 22, 12, + 11, 10, 9, 7, 0 +}; +const opus_uint8 silk_stereo_only_code_mid_iCDF[ 2 ] = { 64, 0 }; + +/* Tables for LBRR flags */ +static const opus_uint8 silk_LBRR_flags_2_iCDF[ 3 ] = { 203, 150, 0 }; +static const opus_uint8 silk_LBRR_flags_3_iCDF[ 7 ] = { 215, 195, 166, 125, 110, 82, 0 }; +const opus_uint8 * const silk_LBRR_flags_iCDF_ptr[ 2 ] = { + silk_LBRR_flags_2_iCDF, + silk_LBRR_flags_3_iCDF +}; + +/* Table for LSB coding */ +const opus_uint8 silk_lsb_iCDF[ 2 ] = { 120, 0 }; + +/* Tables for LTPScale */ +const opus_uint8 silk_LTPscale_iCDF[ 3 ] = { 128, 64, 0 }; + +/* Tables for signal type and offset coding */ +const opus_uint8 silk_type_offset_VAD_iCDF[ 4 ] = { + 232, 158, 10, 0 +}; +const opus_uint8 silk_type_offset_no_VAD_iCDF[ 2 ] = { + 230, 0 +}; + +/* Tables for NLSF interpolation factor */ +const opus_uint8 silk_NLSF_interpolation_factor_iCDF[ 5 ] = { 243, 221, 192, 181, 0 }; + +/* Quantization offsets */ +const opus_int16 silk_Quantization_Offsets_Q10[ 2 ][ 2 ] = { + { OFFSET_UVL_Q10, OFFSET_UVH_Q10 }, { OFFSET_VL_Q10, OFFSET_VH_Q10 } +}; + +/* Table for LTPScale */ +const opus_int16 silk_LTPScales_table_Q14[ 3 ] = { 15565, 12288, 8192 }; + +/* Uniform entropy tables */ +const opus_uint8 silk_uniform3_iCDF[ 3 ] = { 171, 85, 0 }; +const opus_uint8 silk_uniform4_iCDF[ 4 ] = { 192, 128, 64, 0 }; +const opus_uint8 silk_uniform5_iCDF[ 5 ] = { 205, 154, 102, 51, 0 }; +const opus_uint8 silk_uniform6_iCDF[ 6 ] = { 213, 171, 128, 85, 43, 0 }; +const opus_uint8 silk_uniform8_iCDF[ 8 ] = { 224, 192, 160, 128, 96, 64, 32, 0 }; + +const opus_uint8 silk_NLSF_EXT_iCDF[ 7 ] = { 100, 40, 16, 7, 3, 1, 0 }; + +/* Elliptic/Cauer filters designed with 0.1 dB passband ripple, + 80 dB minimum stopband attenuation, and + [0.95 : 0.15 : 0.35] normalized cut off frequencies. */ + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +const opus_int32 silk_Transition_LP_B_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NB ] = +{ +{ 250767114, 501534038, 250767114 }, +{ 209867381, 419732057, 209867381 }, +{ 170987846, 341967853, 170987846 }, +{ 131531482, 263046905, 131531482 }, +{ 89306658, 178584282, 89306658 } +}; + +/* Interpolation points for filter coefficients used in the bandwidth transition smoother */ +const opus_int32 silk_Transition_LP_A_Q28[ TRANSITION_INT_NUM ][ TRANSITION_NA ] = +{ +{ 506393414, 239854379 }, +{ 411067935, 169683996 }, +{ 306733530, 116694253 }, +{ 185807084, 77959395 }, +{ 35497197, 57401098 } +}; + +#ifdef __cplusplus +} +#endif + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pitch_lag.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pitch_lag.c new file mode 100644 index 0000000000..e80cc59a27 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pitch_lag.c @@ -0,0 +1,69 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_pitch_lag_iCDF[ 2 * ( PITCH_EST_MAX_LAG_MS - PITCH_EST_MIN_LAG_MS ) ] = { + 253, 250, 244, 233, 212, 182, 150, 131, + 120, 110, 98, 85, 72, 60, 49, 40, + 32, 25, 19, 15, 13, 11, 9, 8, + 7, 6, 5, 4, 3, 2, 1, 0 +}; + +const opus_uint8 silk_pitch_delta_iCDF[21] = { + 210, 208, 206, 203, 199, 193, 183, 168, + 142, 104, 74, 52, 37, 27, 20, 14, + 10, 6, 4, 2, 0 +}; + +const opus_uint8 silk_pitch_contour_iCDF[34] = { + 223, 201, 183, 167, 152, 138, 124, 111, + 98, 88, 79, 70, 62, 56, 50, 44, + 39, 35, 31, 27, 24, 21, 18, 16, + 14, 12, 10, 8, 6, 4, 3, 2, + 1, 0 +}; + +const opus_uint8 silk_pitch_contour_NB_iCDF[11] = { + 188, 176, 155, 138, 119, 97, 67, 43, + 26, 10, 0 +}; + +const opus_uint8 silk_pitch_contour_10_ms_iCDF[12] = { + 165, 119, 80, 61, 47, 35, 27, 20, + 14, 9, 4, 0 +}; + +const opus_uint8 silk_pitch_contour_10_ms_NB_iCDF[3] = { + 113, 63, 0 +}; + + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pulses_per_block.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pulses_per_block.c new file mode 100644 index 0000000000..c7c01c8893 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tables_pulses_per_block.c @@ -0,0 +1,264 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "tables.h" + +const opus_uint8 silk_max_pulses_table[ 4 ] = { + 8, 10, 12, 16 +}; + +const opus_uint8 silk_pulses_per_block_iCDF[ 10 ][ 18 ] = { +{ + 125, 51, 26, 18, 15, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 198, 105, 45, 22, 15, 12, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 213, 162, 116, 83, 59, 43, 32, 24, + 18, 15, 12, 9, 7, 6, 5, 3, + 2, 0 +}, +{ + 239, 187, 116, 59, 28, 16, 11, 10, + 9, 8, 7, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 250, 229, 188, 135, 86, 51, 30, 19, + 13, 10, 8, 6, 5, 4, 3, 2, + 1, 0 +}, +{ + 249, 235, 213, 185, 156, 128, 103, 83, + 66, 53, 42, 33, 26, 21, 17, 13, + 10, 0 +}, +{ + 254, 249, 235, 206, 164, 118, 77, 46, + 27, 16, 10, 7, 5, 4, 3, 2, + 1, 0 +}, +{ + 255, 253, 249, 239, 220, 191, 156, 119, + 85, 57, 37, 23, 15, 10, 6, 4, + 2, 0 +}, +{ + 255, 253, 251, 246, 237, 223, 203, 179, + 152, 124, 98, 75, 55, 40, 29, 21, + 15, 0 +}, +{ + 255, 254, 253, 247, 220, 162, 106, 67, + 42, 28, 18, 12, 9, 6, 4, 3, + 2, 0 +} +}; + +const opus_uint8 silk_pulses_per_block_BITS_Q5[ 9 ][ 18 ] = { +{ + 31, 57, 107, 160, 205, 205, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 69, 47, 67, 111, 166, 205, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 82, 74, 79, 95, 109, 128, 145, 160, + 173, 205, 205, 205, 224, 255, 255, 224, + 255, 224 +}, +{ + 125, 74, 59, 69, 97, 141, 182, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255 +}, +{ + 173, 115, 85, 73, 76, 92, 115, 145, + 173, 205, 224, 224, 255, 255, 255, 255, + 255, 255 +}, +{ + 166, 134, 113, 102, 101, 102, 107, 118, + 125, 138, 145, 155, 166, 182, 192, 192, + 205, 150 +}, +{ + 224, 182, 134, 101, 83, 79, 85, 97, + 120, 145, 173, 205, 224, 255, 255, 255, + 255, 255 +}, +{ + 255, 224, 192, 150, 120, 101, 92, 89, + 93, 102, 118, 134, 160, 182, 192, 224, + 224, 224 +}, +{ + 255, 224, 224, 182, 155, 134, 118, 109, + 104, 102, 106, 111, 118, 131, 145, 160, + 173, 131 +} +}; + +const opus_uint8 silk_rate_levels_iCDF[ 2 ][ 9 ] = +{ +{ + 241, 190, 178, 132, 87, 74, 41, 14, + 0 +}, +{ + 223, 193, 157, 140, 106, 57, 39, 18, + 0 +} +}; + +const opus_uint8 silk_rate_levels_BITS_Q5[ 2 ][ 9 ] = +{ +{ + 131, 74, 141, 79, 80, 138, 95, 104, + 134 +}, +{ + 95, 99, 91, 125, 93, 76, 123, 115, + 123 +} +}; + +const opus_uint8 silk_shell_code_table0[ 152 ] = { + 128, 0, 214, 42, 0, 235, 128, 21, + 0, 244, 184, 72, 11, 0, 248, 214, + 128, 42, 7, 0, 248, 225, 170, 80, + 25, 5, 0, 251, 236, 198, 126, 54, + 18, 3, 0, 250, 238, 211, 159, 82, + 35, 15, 5, 0, 250, 231, 203, 168, + 128, 88, 53, 25, 6, 0, 252, 238, + 216, 185, 148, 108, 71, 40, 18, 4, + 0, 253, 243, 225, 199, 166, 128, 90, + 57, 31, 13, 3, 0, 254, 246, 233, + 212, 183, 147, 109, 73, 44, 23, 10, + 2, 0, 255, 250, 240, 223, 198, 166, + 128, 90, 58, 33, 16, 6, 1, 0, + 255, 251, 244, 231, 210, 181, 146, 110, + 75, 46, 25, 12, 5, 1, 0, 255, + 253, 248, 238, 221, 196, 164, 128, 92, + 60, 35, 18, 8, 3, 1, 0, 255, + 253, 249, 242, 229, 208, 180, 146, 110, + 76, 48, 27, 14, 7, 3, 1, 0 +}; + +const opus_uint8 silk_shell_code_table1[ 152 ] = { + 129, 0, 207, 50, 0, 236, 129, 20, + 0, 245, 185, 72, 10, 0, 249, 213, + 129, 42, 6, 0, 250, 226, 169, 87, + 27, 4, 0, 251, 233, 194, 130, 62, + 20, 4, 0, 250, 236, 207, 160, 99, + 47, 17, 3, 0, 255, 240, 217, 182, + 131, 81, 41, 11, 1, 0, 255, 254, + 233, 201, 159, 107, 61, 20, 2, 1, + 0, 255, 249, 233, 206, 170, 128, 86, + 50, 23, 7, 1, 0, 255, 250, 238, + 217, 186, 148, 108, 70, 39, 18, 6, + 1, 0, 255, 252, 243, 226, 200, 166, + 128, 90, 56, 30, 13, 4, 1, 0, + 255, 252, 245, 231, 209, 180, 146, 110, + 76, 47, 25, 11, 4, 1, 0, 255, + 253, 248, 237, 219, 194, 163, 128, 93, + 62, 37, 19, 8, 3, 1, 0, 255, + 254, 250, 241, 226, 205, 177, 145, 111, + 79, 51, 30, 15, 6, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table2[ 152 ] = { + 129, 0, 203, 54, 0, 234, 129, 23, + 0, 245, 184, 73, 10, 0, 250, 215, + 129, 41, 5, 0, 252, 232, 173, 86, + 24, 3, 0, 253, 240, 200, 129, 56, + 15, 2, 0, 253, 244, 217, 164, 94, + 38, 10, 1, 0, 253, 245, 226, 189, + 132, 71, 27, 7, 1, 0, 253, 246, + 231, 203, 159, 105, 56, 23, 6, 1, + 0, 255, 248, 235, 213, 179, 133, 85, + 47, 19, 5, 1, 0, 255, 254, 243, + 221, 194, 159, 117, 70, 37, 12, 2, + 1, 0, 255, 254, 248, 234, 208, 171, + 128, 85, 48, 22, 8, 2, 1, 0, + 255, 254, 250, 240, 220, 189, 149, 107, + 67, 36, 16, 6, 2, 1, 0, 255, + 254, 251, 243, 227, 201, 166, 128, 90, + 55, 29, 13, 5, 2, 1, 0, 255, + 254, 252, 246, 234, 213, 183, 147, 109, + 73, 43, 22, 10, 4, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table3[ 152 ] = { + 130, 0, 200, 58, 0, 231, 130, 26, + 0, 244, 184, 76, 12, 0, 249, 214, + 130, 43, 6, 0, 252, 232, 173, 87, + 24, 3, 0, 253, 241, 203, 131, 56, + 14, 2, 0, 254, 246, 221, 167, 94, + 35, 8, 1, 0, 254, 249, 232, 193, + 130, 65, 23, 5, 1, 0, 255, 251, + 239, 211, 162, 99, 45, 15, 4, 1, + 0, 255, 251, 243, 223, 186, 131, 74, + 33, 11, 3, 1, 0, 255, 252, 245, + 230, 202, 158, 105, 57, 24, 8, 2, + 1, 0, 255, 253, 247, 235, 214, 179, + 132, 84, 44, 19, 7, 2, 1, 0, + 255, 254, 250, 240, 223, 196, 159, 112, + 69, 36, 15, 6, 2, 1, 0, 255, + 254, 253, 245, 231, 209, 176, 136, 93, + 55, 27, 11, 3, 2, 1, 0, 255, + 254, 253, 252, 239, 221, 194, 158, 117, + 76, 42, 18, 4, 3, 2, 1, 0 +}; + +const opus_uint8 silk_shell_code_table_offsets[ 17 ] = { + 0, 0, 2, 5, 9, 14, 20, 27, + 35, 44, 54, 65, 77, 90, 104, 119, + 135 +}; + +const opus_uint8 silk_sign_iCDF[ 42 ] = { + 254, 49, 67, 77, 82, 93, 99, + 198, 11, 18, 24, 31, 36, 45, + 255, 46, 66, 78, 87, 94, 104, + 208, 14, 21, 32, 42, 51, 66, + 255, 94, 104, 109, 112, 115, 118, + 248, 53, 69, 80, 88, 95, 102 +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/meson.build new file mode 100644 index 0000000000..b7c70f75f6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/meson.build @@ -0,0 +1,8 @@ +exe = executable('test_unit_LPC_inv_pred_gain', + 'test_unit_LPC_inv_pred_gain.c', '../LPC_inv_pred_gain.c', + include_directories: opus_includes, + link_with: [celt_lib, celt_static_libs, silk_lib, silk_static_libs], + dependencies: libm, + install: false) + +test(test_name, exe) diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/test_unit_LPC_inv_pred_gain.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/test_unit_LPC_inv_pred_gain.c new file mode 100644 index 0000000000..acdd31af97 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tests/test_unit_LPC_inv_pred_gain.c @@ -0,0 +1,130 @@ +/*********************************************************************** +Copyright (c) 2017 Google Inc., Jean-Marc Valin +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "celt/stack_alloc.h" +#include "cpu_support.h" +#include "SigProc_FIX.h" + +/* Computes the impulse response of the filter so we + can catch filters that are definitely unstable. Some + unstable filters may be classified as stable, but not + the other way around. */ +int check_stability(opus_int16 *A_Q12, int order) { + int i; + int j; + int sum_a, sum_abs_a; + double y[SILK_MAX_ORDER_LPC] = {0}; + sum_a = sum_abs_a = 0; + for( j = 0; j < order; j++ ) { + sum_a += A_Q12[ j ]; + sum_abs_a += silk_abs( A_Q12[ j ] ); + } + /* Check DC stability. */ + if( sum_a >= 4096 ) { + return 0; + } + /* If the sum of absolute values is less than 1, the filter + has to be stable. */ + if( sum_abs_a < 4096 ) { + return 1; + } + y[0] = 1; + for( i = 0; i < 10000; i++ ) { + double sum = 0; + for( j = 0; j < order; j++ ) { + sum += y[ j ]*A_Q12[ j ]; + } + for( j = order - 1; j > 0; j-- ) { + y[ j ] = y[ j - 1 ]; + } + y[ 0 ] = sum*(1./4096); + /* If impulse response reaches +/- 10000, the filter + is definitely unstable. */ + if( !(y[ 0 ] < 10000 && y[ 0 ] > -10000) ) { + return 0; + } + /* Test every 8 sample for low amplitude. */ + if( ( i & 0x7 ) == 0 ) { + double amp = 0; + for( j = 0; j < order; j++ ) { + amp += fabs(y[j]); + } + if( amp < 0.00001 ) { + return 1; + } + } + } + return 1; +} + +int main(void) { + const int arch = opus_select_arch(); + /* Set to 10000 so all branches in C function are triggered */ + const int loop_num = 10000; + int count = 0; + ALLOC_STACK; + + /* FIXME: Make the seed random (with option to set it explicitly) + so we get wider coverage. */ + srand(0); + + printf("Testing silk_LPC_inverse_pred_gain() optimization ...\n"); + for( count = 0; count < loop_num; count++ ) { + unsigned int i; + opus_int order; + unsigned int shift; + opus_int16 A_Q12[ SILK_MAX_ORDER_LPC ]; + opus_int32 gain; + + for( order = 2; order <= SILK_MAX_ORDER_LPC; order += 2 ) { /* order must be even. */ + for( shift = 0; shift < 16; shift++ ) { /* Different dynamic range. */ + for( i = 0; i < SILK_MAX_ORDER_LPC; i++ ) { + A_Q12[i] = ((opus_int16)rand()) >> shift; + } + gain = silk_LPC_inverse_pred_gain(A_Q12, order, arch); + /* Look for filters that silk_LPC_inverse_pred_gain() thinks are + stable but definitely aren't. */ + if( gain != 0 && !check_stability(A_Q12, order) ) { + fprintf(stderr, "**Loop %4d failed!**\n", count); + return 1; + } + } + } + if( !(count % 500) ) { + printf("Loop %4d passed\n", count); + } + } + printf("silk_LPC_inverse_pred_gain() optimization passed\n"); + RESTORE_STACK; + return 0; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tuning_parameters.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tuning_parameters.h new file mode 100644 index 0000000000..d70275fd8f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/tuning_parameters.h @@ -0,0 +1,155 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_TUNING_PARAMETERS_H +#define SILK_TUNING_PARAMETERS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Decay time for bitreservoir */ +#define BITRESERVOIR_DECAY_TIME_MS 500 + +/*******************/ +/* Pitch estimator */ +/*******************/ + +/* Level of noise floor for whitening filter LPC analysis in pitch analysis */ +#define FIND_PITCH_WHITE_NOISE_FRACTION 1e-3f + +/* Bandwidth expansion for whitening filter in pitch analysis */ +#define FIND_PITCH_BANDWIDTH_EXPANSION 0.99f + +/*********************/ +/* Linear prediction */ +/*********************/ + +/* LPC analysis regularization */ +#define FIND_LPC_COND_FAC 1e-5f + +/* Max cumulative LTP gain */ +#define MAX_SUM_LOG_GAIN_DB 250.0f + +/* LTP analysis defines */ +#define LTP_CORR_INV_MAX 0.03f + +/***********************/ +/* High pass filtering */ +/***********************/ + +/* Smoothing parameters for low end of pitch frequency range estimation */ +#define VARIABLE_HP_SMTH_COEF1 0.1f +#define VARIABLE_HP_SMTH_COEF2 0.015f +#define VARIABLE_HP_MAX_DELTA_FREQ 0.4f + +/* Min and max cut-off frequency values (-3 dB points) */ +#define VARIABLE_HP_MIN_CUTOFF_HZ 60 +#define VARIABLE_HP_MAX_CUTOFF_HZ 100 + +/***********/ +/* Various */ +/***********/ + +/* VAD threshold */ +#define SPEECH_ACTIVITY_DTX_THRES 0.05f + +/* Speech Activity LBRR enable threshold */ +#define LBRR_SPEECH_ACTIVITY_THRES 0.3f + +/*************************/ +/* Perceptual parameters */ +/*************************/ + +/* reduction in coding SNR during low speech activity */ +#define BG_SNR_DECR_dB 2.0f + +/* factor for reducing quantization noise during voiced speech */ +#define HARM_SNR_INCR_dB 2.0f + +/* factor for reducing quantization noise for unvoiced sparse signals */ +#define SPARSE_SNR_INCR_dB 2.0f + +/* threshold for sparseness measure above which to use lower quantization offset during unvoiced */ +#define ENERGY_VARIATION_THRESHOLD_QNT_OFFSET 0.6f + +/* warping control */ +#define WARPING_MULTIPLIER 0.015f + +/* fraction added to first autocorrelation value */ +#define SHAPE_WHITE_NOISE_FRACTION 3e-5f + +/* noise shaping filter chirp factor */ +#define BANDWIDTH_EXPANSION 0.94f + +/* harmonic noise shaping */ +#define HARMONIC_SHAPING 0.3f + +/* extra harmonic noise shaping for high bitrates or noisy input */ +#define HIGH_RATE_OR_LOW_QUALITY_HARMONIC_SHAPING 0.2f + +/* parameter for shaping noise towards higher frequencies */ +#define HP_NOISE_COEF 0.25f + +/* parameter for shaping noise even more towards higher frequencies during voiced speech */ +#define HARM_HP_NOISE_COEF 0.35f + +/* parameter for applying a high-pass tilt to the input signal */ +#define INPUT_TILT 0.05f + +/* parameter for extra high-pass tilt to the input signal at high rates */ +#define HIGH_RATE_INPUT_TILT 0.1f + +/* parameter for reducing noise at the very low frequencies */ +#define LOW_FREQ_SHAPING 4.0f + +/* less reduction of noise at the very low frequencies for signals with low SNR at low frequencies */ +#define LOW_QUALITY_LOW_FREQ_SHAPING_DECR 0.5f + +/* subframe smoothing coefficient for HarmBoost, HarmShapeGain, Tilt (lower -> more smoothing) */ +#define SUBFR_SMTH_COEF 0.4f + +/* parameters defining the R/D tradeoff in the residual quantizer */ +#define LAMBDA_OFFSET 1.2f +#define LAMBDA_SPEECH_ACT -0.2f +#define LAMBDA_DELAYED_DECISIONS -0.05f +#define LAMBDA_INPUT_QUALITY -0.1f +#define LAMBDA_CODING_QUALITY -0.2f +#define LAMBDA_QUANT_OFFSET 0.8f + +/* Compensation in bitrate calculations for 10 ms modes */ +#define REDUCE_BITRATE_10_MS_BPS 2200 + +/* Maximum time before allowing a bandwidth transition */ +#define MAX_BANDWIDTH_SWITCH_DELAY_MS 5000 + +#ifdef __cplusplus +} +#endif + +#endif /* SILK_TUNING_PARAMETERS_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/typedef.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/typedef.h new file mode 100644 index 0000000000..793d2c0c1d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/typedef.h @@ -0,0 +1,81 @@ +/*********************************************************************** +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_TYPEDEF_H +#define SILK_TYPEDEF_H + +#include "opus_types.h" +#include "opus_defines.h" + +#ifndef FIXED_POINT +# include +# define silk_float float +# define silk_float_MAX FLT_MAX +#endif + +#define silk_int64_MAX ((opus_int64)0x7FFFFFFFFFFFFFFFLL) /* 2^63 - 1 */ +#define silk_int64_MIN ((opus_int64)0x8000000000000000LL) /* -2^63 */ +#define silk_int32_MAX 0x7FFFFFFF /* 2^31 - 1 = 2147483647 */ +#define silk_int32_MIN ((opus_int32)0x80000000) /* -2^31 = -2147483648 */ +#define silk_int16_MAX 0x7FFF /* 2^15 - 1 = 32767 */ +#define silk_int16_MIN ((opus_int16)0x8000) /* -2^15 = -32768 */ +#define silk_int8_MAX 0x7F /* 2^7 - 1 = 127 */ +#define silk_int8_MIN ((opus_int8)0x80) /* -2^7 = -128 */ +#define silk_uint8_MAX 0xFF /* 2^8 - 1 = 255 */ + +#define silk_TRUE 1 +#define silk_FALSE 0 + +/* assertions */ +#if (defined _WIN32 && !defined _WINCE && !defined(__GNUC__) && !defined(NO_ASSERTS)) +# ifndef silk_assert +# include /* ASSERTE() */ +# define silk_assert(COND) _ASSERTE(COND) +# endif +#else +# ifdef ENABLE_ASSERTIONS +# include +# include +#define silk_fatal(str) _silk_fatal(str, __FILE__, __LINE__); +#ifdef __GNUC__ +__attribute__((noreturn)) +#endif +static OPUS_INLINE void _silk_fatal(const char *str, const char *file, int line) +{ + fprintf (stderr, "Fatal (internal) error in %s, line %d: %s\n", file, line, str); +#if defined(_MSC_VER) + _set_abort_behavior( 0, _WRITE_ABORT_MSG); +#endif + abort(); +} +# define silk_assert(COND) {if (!(COND)) {silk_fatal("assertion failed: " #COND);}} +# else +# define silk_assert(COND) +# endif +#endif + +#endif /* SILK_TYPEDEF_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_avx2.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_avx2.c new file mode 100644 index 0000000000..21f00c2dad --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_avx2.c @@ -0,0 +1,1074 @@ +/*********************************************************************** +Copyright (c) 2021 Google Inc. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_CHECK_ASM +#include +#endif + +#include "opus_defines.h" +#include + +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" +#include "celt/x86/x86cpu.h" + +/* Returns TRUE if all assumptions met */ +static OPUS_INLINE int verify_assumptions(const silk_encoder_state *psEncC) +{ + /* This optimization is based on these assumptions */ + /* These assumptions are fundamental and hence assert are */ + /* used. Should any assert triggers, we have to re-visit */ + /* all related code to make sure it still functions the */ + /* same as the C implementation. */ + silk_assert(MAX_DEL_DEC_STATES <= 4 && + MAX_FRAME_LENGTH % 4 == 0 && + MAX_SUB_FRAME_LENGTH % 4 == 0 && + LTP_MEM_LENGTH_MS % 4 == 0 ); + silk_assert(psEncC->fs_kHz == 8 || + psEncC->fs_kHz == 12 || + psEncC->fs_kHz == 16 ); + silk_assert(psEncC->nb_subfr <= MAX_NB_SUBFR && + psEncC->nb_subfr > 0 ); + silk_assert(psEncC->nStatesDelayedDecision <= MAX_DEL_DEC_STATES && + psEncC->nStatesDelayedDecision > 0 ); + silk_assert(psEncC->ltp_mem_length == psEncC->fs_kHz * LTP_MEM_LENGTH_MS); + + /* Regressions were observed on certain AMD Zen CPUs when */ + /* nStatesDelayedDecision is 1 or 2. Ideally we should detect */ + /* these CPUs and enable this optimization on others; however, */ + /* there is no good way to do so under current OPUS framework. */ + return psEncC->nStatesDelayedDecision == 3 || + psEncC->nStatesDelayedDecision == 4; +} + +/* Intrinsics not defined on MSVC */ +#ifdef _MSC_VER +#include +static inline int __builtin_sadd_overflow(opus_int32 a, opus_int32 b, opus_int32* res) +{ + *res = a+b; + return (*res ^ a) & (*res ^ b) & 0x80000000; +} +static inline int __builtin_ctz(unsigned int x) +{ + DWORD res = 0; + return _BitScanForward(&res, x) ? res : 32; +} +#endif + +static OPUS_INLINE __m128i silk_cvtepi64_epi32_high(__m256i num) +{ + return _mm256_castsi256_si128(_mm256_permutevar8x32_epi32(num, _mm256_set_epi32(0, 0, 0, 0, 7, 5, 3, 1))); +} + +static OPUS_INLINE opus_int16 silk_sat16(opus_int32 num) +{ + num = num > silk_int16_MAX ? silk_int16_MAX : num; + num = num < silk_int16_MIN ? silk_int16_MIN : num; + return num; +} + +static OPUS_INLINE opus_int32 silk_sar_round_32(opus_int32 a, int bits) +{ + silk_assert(bits > 0 && bits < 31); + a += 1 << (bits-1); + return a >> bits; +} + +static OPUS_INLINE opus_int64 silk_sar_round_smulww(opus_int32 a, opus_int32 b, int bits) +{ + silk_assert(bits > 0 && bits < 63); +#ifdef OPUS_CHECK_ASM + return silk_RSHIFT_ROUND(silk_SMULWW(a, b), bits); +#else + /* This code is more correct, but it won't overflow like the C code in some rare cases. */ + silk_assert(bits > 0 && bits < 63); + opus_int64 t = ((opus_int64)a) * ((opus_int64)b); + bits += 16; + t += 1ull << (bits-1); + return t >> bits; +#endif +} + +static OPUS_INLINE opus_int32 silk_add_sat32(opus_int32 a, opus_int32 b) +{ + opus_int32 sum; + if (__builtin_sadd_overflow(a, b, &sum)) + { + return a >= 0 ? silk_int32_MAX : silk_int32_MIN; + } + return sum; +} + +static OPUS_INLINE __m128i silk_mm_srai_round_epi32(__m128i a, int bits) +{ + silk_assert(bits > 0 && bits < 31); + return _mm_srai_epi32(_mm_add_epi32(a, _mm_set1_epi32(1 << (bits - 1))), bits); +} + +/* add/subtract with output saturated */ +static OPUS_INLINE __m128i silk_mm_add_sat_epi32(__m128i a, __m128i b) +{ + __m128i r = _mm_add_epi32(a, b); + __m128i OF = _mm_and_si128(_mm_xor_si128(a, r), _mm_xor_si128(b, r)); /* OF = (sum ^ a) & (sum ^ b) */ + __m128i SAT = _mm_add_epi32(_mm_srli_epi32(a, 31), _mm_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */ + return _mm_blendv_epi8(r, SAT, _mm_srai_epi32(OF, 31)); +} +static OPUS_INLINE __m128i silk_mm_sub_sat_epi32(__m128i a, __m128i b) +{ + __m128i r = _mm_sub_epi32(a, b); + __m128i OF = _mm_andnot_si128(_mm_xor_si128(b, r), _mm_xor_si128(a, r)); /* OF = (sum ^ a) & (sum ^ ~b) = (sum ^ a) & ~(sum ^ b) */ + __m128i SAT = _mm_add_epi32(_mm_srli_epi32(a, 31), _mm_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */ + return _mm_blendv_epi8(r, SAT, _mm_srai_epi32(OF, 31)); +} +static OPUS_INLINE __m256i silk_mm256_sub_sat_epi32(__m256i a, __m256i b) +{ + __m256i r = _mm256_sub_epi32(a, b); + __m256i OF = _mm256_andnot_si256(_mm256_xor_si256(b, r), _mm256_xor_si256(a, r)); /* OF = (sum ^ a) & (sum ^ ~b) = (sum ^ a) & ~(sum ^ b) */ + __m256i SAT = _mm256_add_epi32(_mm256_srli_epi32(a, 31), _mm256_set1_epi32(0x7FFFFFFF)); /* SAT = (a >> 31) + 0x7FFFFFFF */ + return _mm256_blendv_epi8(r, SAT, _mm256_srai_epi32(OF, 31)); +} + +static OPUS_INLINE __m128i silk_mm_limit_epi32(__m128i num, opus_int32 limit1, opus_int32 limit2) +{ + opus_int32 lo = limit1 < limit2 ? limit1 : limit2; + opus_int32 hi = limit1 > limit2 ? limit1 : limit2; + + num = _mm_min_epi32(num, _mm_set1_epi32(hi)); + num = _mm_max_epi32(num, _mm_set1_epi32(lo)); + return num; +} + +/* cond < 0 ? -num : num */ +static OPUS_INLINE __m128i silk_mm_sign_epi32(__m128i num, __m128i cond) +{ + return _mm_sign_epi32(num, _mm_or_si128(cond, _mm_set1_epi32(1))); +} +static OPUS_INLINE __m256i silk_mm256_sign_epi32(__m256i num, __m256i cond) +{ + return _mm256_sign_epi32(num, _mm256_or_si256(cond, _mm256_set1_epi32(1))); +} + +/* (a32 * b32) >> 16 */ +static OPUS_INLINE __m128i silk_mm_smulww_epi32(__m128i a, opus_int32 b) +{ + return silk_cvtepi64_epi32_high(_mm256_slli_epi64(_mm256_mul_epi32(_mm256_cvtepi32_epi64(a), _mm256_set1_epi32(b)), 16)); +} + +/* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +static OPUS_INLINE __m128i silk_mm_smulwb_epi32(__m128i a, opus_int32 b) +{ + return silk_cvtepi64_epi32_high(_mm256_mul_epi32(_mm256_cvtepi32_epi64(a), _mm256_set1_epi32(silk_LSHIFT(b, 16)))); +} + +/* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */ +static OPUS_INLINE __m256i silk_mm256_smulbb_epi32(__m256i a, __m256i b) +{ + const char FF = (char)0xFF; + __m256i msk = _mm256_set_epi8( + FF, FF, FF, FF, FF, FF, FF, FF, 13, 12, 9, 8, 5, 4, 1, 0, + FF, FF, FF, FF, FF, FF, FF, FF, 13, 12, 9, 8, 5, 4, 1, 0); + __m256i lo = _mm256_mullo_epi16(a, b); + __m256i hi = _mm256_mulhi_epi16(a, b); + lo = _mm256_shuffle_epi8(lo, msk); + hi = _mm256_shuffle_epi8(hi, msk); + return _mm256_unpacklo_epi16(lo, hi); +} + +static OPUS_INLINE __m256i silk_mm256_reverse_epi32(__m256i v) +{ + v = _mm256_shuffle_epi32(v, 0x1B); + v = _mm256_permute4x64_epi64(v, 0x4E); + return v; +} + +static OPUS_INLINE opus_int32 silk_mm256_hsum_epi32(__m256i v) +{ + __m128i sum = _mm_add_epi32(_mm256_extracti128_si256(v, 1), _mm256_extracti128_si256(v, 0)); + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); + return _mm_cvtsi128_si32(sum); +} + +static OPUS_INLINE __m128i silk_mm_hmin_epi32(__m128i num) +{ + num = _mm_min_epi32(num, _mm_shuffle_epi32(num, 0x4E)); /* 0123 -> 2301 */ + num = _mm_min_epi32(num, _mm_shuffle_epi32(num, 0xB1)); /* 0123 -> 1032 */ + return num; +} + +static OPUS_INLINE __m128i silk_mm_hmax_epi32(__m128i num) +{ + num = _mm_max_epi32(num, _mm_shuffle_epi32(num, 0x4E)); /* 0123 -> 2310 */ + num = _mm_max_epi32(num, _mm_shuffle_epi32(num, 0xB1)); /* 0123 -> 1032 */ + return num; +} + +static OPUS_INLINE __m128i silk_mm_mask_hmin_epi32(__m128i num, __m128i mask) +{ + num = _mm_blendv_epi8(num, _mm_set1_epi32(silk_int32_MAX), mask); + return silk_mm_hmin_epi32(num); +} + +static OPUS_INLINE __m128i silk_mm_mask_hmax_epi32(__m128i num, __m128i mask) +{ + num = _mm_blendv_epi8(num, _mm_set1_epi32(silk_int32_MIN), mask); + return silk_mm_hmax_epi32(num); +} + +static OPUS_INLINE __m128i silk_mm256_rand_epi32(__m128i seed) +{ + seed = _mm_mullo_epi32(seed, _mm_set1_epi32(RAND_MULTIPLIER)); + seed = _mm_add_epi32(seed, _mm_set1_epi32(RAND_INCREMENT)); + return seed; +} + +static OPUS_INLINE opus_int32 silk_index_of_first_equal_epi32(__m128i a, __m128i b) +{ + unsigned int mask = _mm_movemask_epi8(_mm_cmpeq_epi32(a, b)) & 0x1111; + silk_assert(mask != 0); + return __builtin_ctz(mask) >> 2; +} + +static __m128i silk_index_to_selector(opus_int32 index) +{ + silk_assert(index < 4); + index <<= 2; + return _mm_set_epi8( + index + 3, index + 2, index + 1, index + 0, + index + 3, index + 2, index + 1, index + 0, + index + 3, index + 2, index + 1, index + 0, + index + 3, index + 2, index + 1, index + 0); +} + +static opus_int32 silk_select_winner(__m128i num, __m128i selector) +{ + return _mm_cvtsi128_si32(_mm_shuffle_epi8(num, selector)); +} + +typedef struct +{ + __m128i RandState; + __m128i Q_Q10; + __m128i Xq_Q14; + __m128i Pred_Q15; + __m128i Shape_Q14; +} NSQ_del_dec_sample_struct; + +typedef struct +{ + __m128i sLPC_Q14[MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH]; + __m128i LF_AR_Q14; + __m128i Seed; + __m128i SeedInit; + __m128i RD_Q10; + __m128i Diff_Q14; + __m128i sAR2_Q14[MAX_SHAPE_LPC_ORDER]; + NSQ_del_dec_sample_struct Samples[DECISION_DELAY]; +} NSQ_del_dec_struct; + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_avx2( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I */ + const opus_int pitchL[MAX_NB_SUBFR], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/*******************************************/ +/* LPC analysis filter */ +/* NB! State is kept internally and the */ +/* filter always starts with zero state */ +/* first d output samples are set to zero */ +/*******************************************/ +static OPUS_INLINE void silk_LPC_analysis_filter_avx2( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 order /* I Filter order */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_avx2( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[DECISION_DELAY], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + __m128i MaskDelDec, /* I Mask of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +); + +void silk_NSQ_del_dec_avx2( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[MAX_NB_SUBFR], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I Quantization step sizes */ + const opus_int32 pitchL[MAX_NB_SUBFR], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ +#ifdef OPUS_CHECK_ASM + silk_nsq_state NSQ_c; + SideInfoIndices psIndices_c; + opus_int8 pulses_c[MAX_FRAME_LENGTH]; + const opus_int8 *const pulses_a = pulses; + + silk_memcpy(&NSQ_c, NSQ, sizeof(NSQ_c)); + silk_memcpy(&psIndices_c, psIndices, sizeof(psIndices_c)); + silk_memcpy(pulses_c, pulses, sizeof(pulses_c)); + silk_NSQ_del_dec_c(psEncC, &NSQ_c, &psIndices_c, x16, pulses_c, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, + pitchL, Lambda_Q10, LTP_scale_Q14); +#endif + + if (!verify_assumptions(psEncC)) + { + silk_NSQ_del_dec_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14); + return; + } + + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int last_smple_idx, smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL(opus_int32, sLTP_Q15); + VARDECL(opus_int16, sLTP); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 Gain_Q10; + opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH]; + opus_int32 delayedGain_Q10[DECISION_DELAY]; + NSQ_del_dec_struct psDelDec = {0}; + NSQ_del_dec_sample_struct *psSample; + __m128i RDmin_Q10, MaskDelDec, Winner_selector; + SAVE_STACK; + + MaskDelDec = _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFFFFF00ul << ((psEncC->nStatesDelayedDecision - 1) << 3))); + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert(NSQ->prev_gain_Q16 != 0); + psDelDec.Seed = _mm_and_si128( + _mm_add_epi32(_mm_set_epi32(3, 2, 1, 0), _mm_set1_epi32(psIndices->Seed)), + _mm_set1_epi32(3)); + psDelDec.SeedInit = psDelDec.Seed; + psDelDec.RD_Q10 = _mm_setzero_si128(); + psDelDec.LF_AR_Q14 = _mm_set1_epi32(NSQ->sLF_AR_shp_Q14); + psDelDec.Diff_Q14 = _mm_set1_epi32(NSQ->sDiff_shp_Q14); + psDelDec.Samples[0].Shape_Q14 = _mm_set1_epi32(NSQ->sLTP_shp_Q14[psEncC->ltp_mem_length - 1]); + for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++) + { + psDelDec.sLPC_Q14[i] = _mm_set1_epi32(NSQ->sLPC_Q14[i]); + } + for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++) + { + psDelDec.sAR2_Q14[i] = _mm_set1_epi32(NSQ->sAR2_Q14[i]); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[psIndices->signalType >> 1][psIndices->quantOffsetType]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int(DECISION_DELAY, psEncC->subfr_length); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if (psIndices->signalType == TYPE_VOICED) + { + for (k = 0; k < psEncC->nb_subfr; k++) + { + decisionDelay = silk_min_int(decisionDelay, pitchL[k] - LTP_ORDER / 2 - 1); + } + } + else + { + if (lag > 0) + { + decisionDelay = silk_min_int(decisionDelay, lag - LTP_ORDER / 2 - 1); + } + } + + if (psIndices->NLSFInterpCoef_Q2 == 4) + { + LSF_interpolation_flag = 0; + } + else + { + LSF_interpolation_flag = 1; + } + + ALLOC(sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32); + ALLOC(sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[psEncC->ltp_mem_length]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for (k = 0; k < psEncC->nb_subfr; k++) + { + A_Q12 = &PredCoef_Q12[((k >> 1) | (1 ^ LSF_interpolation_flag)) * MAX_LPC_ORDER]; + B_Q14 = <PCoef_Q14[k * LTP_ORDER]; + AR_shp_Q13 = &AR_Q13[k * MAX_SHAPE_LPC_ORDER]; + + /* Noise shape parameters */ + silk_assert(HarmShapeGain_Q14[k] >= 0); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if (psIndices->signalType == TYPE_VOICED) + { + /* Voiced */ + lag = pitchL[k]; + + /* Re-whitening */ + if ((k & (3 ^ (LSF_interpolation_flag << 1))) == 0) + { + if (k == 2) + { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + RDmin_Q10 = silk_mm_mask_hmin_epi32(psDelDec.RD_Q10, MaskDelDec); + Winner_ind = silk_index_of_first_equal_epi32(RDmin_Q10, psDelDec.RD_Q10); + Winner_selector = silk_index_to_selector(Winner_ind); + psDelDec.RD_Q10 = _mm_add_epi32( + psDelDec.RD_Q10, + _mm_blendv_epi8( + _mm_set1_epi32(silk_int32_MAX >> 4), + _mm_setzero_si128(), + _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFU << (unsigned)(Winner_ind << 3))))); + + /* Copy final part of signals from winner state to output and long-term filter states */ + last_smple_idx = smpl_buf_idx + decisionDelay; + for (i = 0; i < decisionDelay; i++) + { + last_smple_idx = (last_smple_idx + DECISION_DELAY - 1) % DECISION_DELAY; + psSample = &psDelDec.Samples[last_smple_idx]; + pulses[i - decisionDelay] = + (opus_int8)silk_sar_round_32(silk_select_winner(psSample->Q_Q10, Winner_selector), 10); + pxq[i - decisionDelay] = + silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psSample->Xq_Q14, Winner_selector), Gains_Q16[1], 14)); + NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay + i] = + silk_select_winner(psSample->Shape_Q14, Winner_selector); + } + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + silk_assert(start_idx > 0); + + silk_LPC_analysis_filter_avx2(&sLTP[start_idx], &NSQ->xq[start_idx + k * psEncC->subfr_length], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states_avx2(psEncC, NSQ, &psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, + LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay); + + silk_noise_shape_quantizer_del_dec_avx2(NSQ, &psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[k], LF_shp_Q14[k], + Gains_Q16[k], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, MaskDelDec, &smpl_buf_idx, decisionDelay); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = silk_mm_mask_hmin_epi32(psDelDec.RD_Q10, MaskDelDec); + Winner_selector = silk_index_to_selector(silk_index_of_first_equal_epi32(RDmin_Q10, psDelDec.RD_Q10)); + + /* Copy final part of signals from winner state to output and long-term filter states */ + psIndices->Seed = silk_select_winner(psDelDec.SeedInit, Winner_selector); + last_smple_idx = smpl_buf_idx + decisionDelay; + Gain_Q10 = Gains_Q16[psEncC->nb_subfr - 1] >> 6; + for (i = 0; i < decisionDelay; i++) + { + last_smple_idx = (last_smple_idx + DECISION_DELAY - 1) % DECISION_DELAY; + psSample = &psDelDec.Samples[last_smple_idx]; + + pulses[i - decisionDelay] = + (opus_int8)silk_sar_round_32(silk_select_winner(psSample->Q_Q10, Winner_selector), 10); + pxq[i - decisionDelay] = + silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psSample->Xq_Q14, Winner_selector), Gain_Q10, 8)); + NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay + i] = + silk_select_winner(psSample->Shape_Q14, Winner_selector); + } + for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++) + { + NSQ->sLPC_Q14[i] = silk_select_winner(psDelDec.sLPC_Q14[i], Winner_selector); + } + for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++) + { + NSQ->sAR2_Q14[i] = silk_select_winner(psDelDec.sAR2_Q14[i], Winner_selector); + } + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = silk_select_winner(psDelDec.LF_AR_Q14, Winner_selector); + NSQ->sDiff_shp_Q14 = silk_select_winner(psDelDec.Diff_Q14, Winner_selector); + NSQ->lagPrev = pitchL[psEncC->nb_subfr - 1]; + + /* Save quantized speech signal */ + silk_memmove(NSQ->xq, &NSQ->xq[psEncC->frame_length], psEncC->ltp_mem_length * sizeof(opus_int16)); + silk_memmove(NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[psEncC->frame_length], psEncC->ltp_mem_length * sizeof(opus_int32)); + +#ifdef OPUS_CHECK_ASM + silk_assert(!memcmp(&NSQ_c, NSQ, sizeof(NSQ_c))); + silk_assert(!memcmp(&psIndices_c, psIndices, sizeof(psIndices_c))); + silk_assert(!memcmp(pulses_c, pulses_a, sizeof(pulses_c))); +#endif + + RESTORE_STACK; +} + +static OPUS_INLINE __m128i silk_noise_shape_quantizer_short_prediction_x4(const __m128i *buf32, const opus_int16 *coef16, opus_int order) +{ + __m256i out; + silk_assert(order == 10 || order == 16); + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + out = _mm256_set1_epi32(order >> 1); + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-0]), _mm256_set1_epi32(silk_LSHIFT(coef16[0], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-1]), _mm256_set1_epi32(silk_LSHIFT(coef16[1], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-2]), _mm256_set1_epi32(silk_LSHIFT(coef16[2], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-3]), _mm256_set1_epi32(silk_LSHIFT(coef16[3], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-4]), _mm256_set1_epi32(silk_LSHIFT(coef16[4], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-5]), _mm256_set1_epi32(silk_LSHIFT(coef16[5], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-6]), _mm256_set1_epi32(silk_LSHIFT(coef16[6], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-7]), _mm256_set1_epi32(silk_LSHIFT(coef16[7], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-8]), _mm256_set1_epi32(silk_LSHIFT(coef16[8], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-9]), _mm256_set1_epi32(silk_LSHIFT(coef16[9], 16)))); /* High DWORD */ + + if (order == 16) + { + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-10]), _mm256_set1_epi32(silk_LSHIFT(coef16[10], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-11]), _mm256_set1_epi32(silk_LSHIFT(coef16[11], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-12]), _mm256_set1_epi32(silk_LSHIFT(coef16[12], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-13]), _mm256_set1_epi32(silk_LSHIFT(coef16[13], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-14]), _mm256_set1_epi32(silk_LSHIFT(coef16[14], 16)))); /* High DWORD */ + out = _mm256_add_epi32(out, _mm256_mul_epi32(_mm256_cvtepi32_epi64(buf32[-15]), _mm256_set1_epi32(silk_LSHIFT(coef16[15], 16)))); /* High DWORD */ + } + return silk_cvtepi64_epi32_high(out); +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_avx2( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[DECISION_DELAY], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + __m128i MaskDelDec, /* I Mask of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +) +{ + int i; + opus_int32 *shp_lag_ptr = &NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2]; + opus_int32 *pred_lag_ptr = &sLTP_Q15[NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2]; + opus_int32 Gain_Q10 = Gain_Q16 >> 6; + + for (i = 0; i < length; i++) + { + /* Perform common calculations used in all states */ + /* NSQ_sample_struct */ + /* Low 128 bits => 1st set */ + /* High 128 bits => 2nd set */ + int j; + __m256i SS_Q_Q10; + __m256i SS_RD_Q10; + __m256i SS_xq_Q14; + __m256i SS_LF_AR_Q14; + __m256i SS_Diff_Q14; + __m256i SS_sLTP_shp_Q14; + __m256i SS_LPC_exc_Q14; + __m256i exc_Q14; + __m256i q_Q10, rr_Q10, rd_Q10; + __m256i mask; + __m128i LPC_pred_Q14, n_AR_Q14; + __m128i RDmin_Q10, RDmax_Q10; + __m128i n_LF_Q14; + __m128i r_Q10, q1_Q0, q1_Q10, q2_Q10; + __m128i Winner_rand_state, Winner_selector; + __m128i tmp0, tmp1; + NSQ_del_dec_sample_struct *psLastSample, *psSample; + opus_int32 RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 LTP_pred_Q14, n_LTP_Q14; + + /* Long-term prediction */ + if (signalType == TYPE_VOICED) + { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-0], b_Q14[0]); + LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-1], b_Q14[1]); + LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-2], b_Q14[2]); + LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-3], b_Q14[3]); + LTP_pred_Q14 += silk_SMULWB(pred_lag_ptr[-4], b_Q14[4]); + LTP_pred_Q14 = silk_LSHIFT(LTP_pred_Q14, 1); /* Q13 -> Q14 */ + pred_lag_ptr++; + } + else + { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if (lag > 0) + { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_add_sat32(shp_lag_ptr[0], shp_lag_ptr[-2]); + n_LTP_Q14 = silk_SMULWB(n_LTP_Q14, HarmShapeFIRPacked_Q14); + n_LTP_Q14 = n_LTP_Q14 + silk_SMULWT(shp_lag_ptr[-1], HarmShapeFIRPacked_Q14); + n_LTP_Q14 = LTP_pred_Q14 - (silk_LSHIFT(n_LTP_Q14, 2)); /* Q12 -> Q14 */ + shp_lag_ptr++; + } + else + { + n_LTP_Q14 = 0; + } + + /* BEGIN Updating Delayed Decision States */ + + /* Generate dither */ + psDelDec->Seed = silk_mm256_rand_epi32(psDelDec->Seed); + + /* Short-term prediction */ + LPC_pred_Q14 = silk_noise_shape_quantizer_short_prediction_x4(&psDelDec->sLPC_Q14[NSQ_LPC_BUF_LENGTH - 1 + i], a_Q12, predictLPCOrder); + LPC_pred_Q14 = _mm_slli_epi32(LPC_pred_Q14, 4); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + silk_assert(shapingLPCOrder > 0); + silk_assert((shapingLPCOrder & 1) == 0); /* check that order is even */ + /* Output of lowpass section */ + tmp0 = _mm_add_epi32(psDelDec->Diff_Q14, silk_mm_smulwb_epi32(psDelDec->sAR2_Q14[0], warping_Q16)); + n_AR_Q14 = _mm_set1_epi32(shapingLPCOrder >> 1); + for (j = 0; j < shapingLPCOrder - 1; j++) + { + /* Output of allpass section */ + tmp1 = psDelDec->sAR2_Q14[j]; + psDelDec->sAR2_Q14[j] = tmp0; + n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(tmp0, AR_shp_Q13[j])); + tmp0 = _mm_add_epi32(tmp1, silk_mm_smulwb_epi32(_mm_sub_epi32(psDelDec->sAR2_Q14[j + 1], tmp0), warping_Q16)); + } + psDelDec->sAR2_Q14[shapingLPCOrder - 1] = tmp0; + n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(tmp0, AR_shp_Q13[shapingLPCOrder - 1])); + + n_AR_Q14 = _mm_slli_epi32(n_AR_Q14, 1); /* Q11 -> Q12 */ + n_AR_Q14 = _mm_add_epi32(n_AR_Q14, silk_mm_smulwb_epi32(psDelDec->LF_AR_Q14, Tilt_Q14)); /* Q12 */ + n_AR_Q14 = _mm_slli_epi32(n_AR_Q14, 2); /* Q12 -> Q14 */ + + tmp0 = silk_mm_smulwb_epi32(psDelDec->Samples[*smpl_buf_idx].Shape_Q14, LF_shp_Q14); /* Q12 */ + tmp1 = silk_mm_smulwb_epi32(psDelDec->LF_AR_Q14, LF_shp_Q14 >> 16); /* Q12 */ + n_LF_Q14 = _mm_add_epi32(tmp0, tmp1); /* Q12 */ + n_LF_Q14 = _mm_slli_epi32(n_LF_Q14, 2); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp0 = silk_mm_add_sat_epi32(n_AR_Q14, n_LF_Q14); /* Q14 */ + tmp1 = _mm_add_epi32(_mm_set1_epi32(n_LTP_Q14), LPC_pred_Q14); /* Q13 */ + tmp0 = silk_mm_sub_sat_epi32(tmp1, tmp0); /* Q13 */ + tmp0 = silk_mm_srai_round_epi32(tmp0, 4); /* Q10 */ + + r_Q10 = _mm_sub_epi32(_mm_set1_epi32(x_Q10[i]), tmp0); /* residual error Q10 */ + + /* Flip sign depending on dither */ + r_Q10 = silk_mm_sign_epi32(r_Q10, psDelDec->Seed); + r_Q10 = silk_mm_limit_epi32(r_Q10, -(31 << 10), 30 << 10); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = _mm_sub_epi32(r_Q10, _mm_set1_epi32(offset_Q10)); + q1_Q0 = _mm_srai_epi32(q1_Q10, 10); + if (Lambda_Q10 > 2048) + { + /* For aggressive RDO, the bias becomes more than one pulse. */ + tmp0 = _mm_sub_epi32(_mm_abs_epi32(q1_Q10), _mm_set1_epi32(Lambda_Q10 / 2 - 512)); /* rdo_offset */ + q1_Q0 = _mm_srai_epi32(q1_Q10, 31); + tmp1 = _mm_cmpgt_epi32(tmp0, _mm_setzero_si128()); + tmp0 = _mm_srai_epi32(silk_mm_sign_epi32(tmp0, q1_Q10), 10); + q1_Q0 = _mm_blendv_epi8(q1_Q0, tmp0, tmp1); + } + + tmp0 = _mm_sign_epi32(_mm_set1_epi32(QUANT_LEVEL_ADJUST_Q10), q1_Q0); + q1_Q10 = _mm_sub_epi32(_mm_slli_epi32(q1_Q0, 10), tmp0); + q1_Q10 = _mm_add_epi32(q1_Q10, _mm_set1_epi32(offset_Q10)); + + /* check if q1_Q0 is 0 or -1 */ + tmp0 = _mm_add_epi32(_mm_srli_epi32(q1_Q0, 31), q1_Q0); + tmp1 = _mm_cmpeq_epi32(tmp0, _mm_setzero_si128()); + tmp0 = _mm_blendv_epi8(_mm_set1_epi32(1024), _mm_set1_epi32(1024 - QUANT_LEVEL_ADJUST_Q10), tmp1); + q2_Q10 = _mm_add_epi32(q1_Q10, tmp0); + q_Q10 = _mm256_set_m128i(q2_Q10, q1_Q10); + + rr_Q10 = _mm256_sub_epi32(_mm256_broadcastsi128_si256(r_Q10), q_Q10); + rd_Q10 = _mm256_abs_epi32(q_Q10); + rr_Q10 = silk_mm256_smulbb_epi32(rr_Q10, rr_Q10); + rd_Q10 = silk_mm256_smulbb_epi32(rd_Q10, _mm256_set1_epi32(Lambda_Q10)); + rd_Q10 = _mm256_add_epi32(rd_Q10, rr_Q10); + rd_Q10 = _mm256_srai_epi32(rd_Q10, 10); + + mask = _mm256_broadcastsi128_si256(_mm_cmplt_epi32(_mm256_extracti128_si256(rd_Q10, 0), _mm256_extracti128_si256(rd_Q10, 1))); + SS_RD_Q10 = _mm256_add_epi32( + _mm256_broadcastsi128_si256(psDelDec->RD_Q10), + _mm256_blendv_epi8( + _mm256_permute2x128_si256(rd_Q10, rd_Q10, 0x1), + rd_Q10, + mask)); + SS_Q_Q10 = _mm256_blendv_epi8( + _mm256_permute2x128_si256(q_Q10, q_Q10, 0x1), + q_Q10, + mask); + + /* Update states for best and second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_mm256_sign_epi32(_mm256_slli_epi32(SS_Q_Q10, 4), _mm256_broadcastsi128_si256(psDelDec->Seed)); + + /* Add predictions */ + exc_Q14 = _mm256_add_epi32(exc_Q14, _mm256_set1_epi32(LTP_pred_Q14)); + SS_LPC_exc_Q14 = _mm256_slli_epi32(exc_Q14, 1); + SS_xq_Q14 = _mm256_add_epi32(exc_Q14, _mm256_broadcastsi128_si256(LPC_pred_Q14)); + + /* Update states */ + SS_Diff_Q14 = _mm256_sub_epi32(SS_xq_Q14, _mm256_set1_epi32(silk_LSHIFT(x_Q10[i], 4))); + SS_LF_AR_Q14 = _mm256_sub_epi32(SS_Diff_Q14, _mm256_broadcastsi128_si256(n_AR_Q14)); + SS_sLTP_shp_Q14 = silk_mm256_sub_sat_epi32(SS_LF_AR_Q14, _mm256_broadcastsi128_si256(n_LF_Q14)); + + /* END Updating Delayed Decision States */ + + *smpl_buf_idx = (*smpl_buf_idx + DECISION_DELAY - 1) % DECISION_DELAY; + last_smple_idx = (*smpl_buf_idx + decisionDelay) % DECISION_DELAY; + psLastSample = &psDelDec->Samples[last_smple_idx]; + + /* Find winner */ + RDmin_Q10 = silk_mm_mask_hmin_epi32(_mm256_castsi256_si128(SS_RD_Q10), MaskDelDec); + Winner_selector = silk_index_to_selector(silk_index_of_first_equal_epi32(RDmin_Q10, _mm256_castsi256_si128(SS_RD_Q10))); + + /* Increase RD values of expired states */ + Winner_rand_state = _mm_shuffle_epi8(psLastSample->RandState, Winner_selector); + + SS_RD_Q10 = _mm256_blendv_epi8( + _mm256_add_epi32(SS_RD_Q10, _mm256_set1_epi32(silk_int32_MAX >> 4)), + SS_RD_Q10, + _mm256_broadcastsi128_si256(_mm_cmpeq_epi32(psLastSample->RandState, Winner_rand_state))); + + /* find worst in first set */ + RDmax_Q10 = silk_mm_mask_hmax_epi32(_mm256_extracti128_si256(SS_RD_Q10, 0), MaskDelDec); + /* find best in second set */ + RDmin_Q10 = silk_mm_mask_hmin_epi32(_mm256_extracti128_si256(SS_RD_Q10, 1), MaskDelDec); + + /* Replace a state if best from second set outperforms worst in first set */ + tmp0 = _mm_cmplt_epi32(RDmin_Q10, RDmax_Q10); + if (!_mm_test_all_zeros(tmp0, tmp0)) + { + int t; + RDmax_ind = silk_index_of_first_equal_epi32(RDmax_Q10, _mm256_extracti128_si256(SS_RD_Q10, 0)); + RDmin_ind = silk_index_of_first_equal_epi32(RDmin_Q10, _mm256_extracti128_si256(SS_RD_Q10, 1)); + tmp1 = _mm_cvtepi8_epi32(_mm_cvtsi32_si128(0xFFU << (unsigned)(RDmax_ind << 3))); + tmp0 = _mm_blendv_epi8( + _mm_set_epi8(0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0), + silk_index_to_selector(RDmin_ind), + tmp1); + for (t = i; t < MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH; t++) + { + psDelDec->sLPC_Q14[t] = _mm_shuffle_epi8(psDelDec->sLPC_Q14[t], tmp0); + } + psDelDec->Seed = _mm_shuffle_epi8(psDelDec->Seed, tmp0); + psDelDec->SeedInit = _mm_shuffle_epi8(psDelDec->SeedInit, tmp0); + for (t = 0; t < MAX_SHAPE_LPC_ORDER; t++) + { + psDelDec->sAR2_Q14[t] = _mm_shuffle_epi8(psDelDec->sAR2_Q14[t], tmp0); + } + for (t = 0; t < DECISION_DELAY; t++) + { + psDelDec->Samples[t].RandState = _mm_shuffle_epi8(psDelDec->Samples[t].RandState, tmp0); + psDelDec->Samples[t].Q_Q10 = _mm_shuffle_epi8(psDelDec->Samples[t].Q_Q10, tmp0); + psDelDec->Samples[t].Xq_Q14 = _mm_shuffle_epi8(psDelDec->Samples[t].Xq_Q14, tmp0); + psDelDec->Samples[t].Pred_Q15 = _mm_shuffle_epi8(psDelDec->Samples[t].Pred_Q15, tmp0); + psDelDec->Samples[t].Shape_Q14 = _mm_shuffle_epi8(psDelDec->Samples[t].Shape_Q14, tmp0); + } + mask = _mm256_castsi128_si256(_mm_blendv_epi8(_mm_set_epi32(0x3, 0x2, 0x1, 0x0), _mm_set1_epi32(RDmin_ind + 4), tmp1)); + SS_Q_Q10 = _mm256_permutevar8x32_epi32(SS_Q_Q10, mask); + SS_RD_Q10 = _mm256_permutevar8x32_epi32(SS_RD_Q10, mask); + SS_xq_Q14 = _mm256_permutevar8x32_epi32(SS_xq_Q14, mask); + SS_LF_AR_Q14 = _mm256_permutevar8x32_epi32(SS_LF_AR_Q14, mask); + SS_Diff_Q14 = _mm256_permutevar8x32_epi32(SS_Diff_Q14, mask); + SS_sLTP_shp_Q14 = _mm256_permutevar8x32_epi32(SS_sLTP_shp_Q14, mask); + SS_LPC_exc_Q14 = _mm256_permutevar8x32_epi32(SS_LPC_exc_Q14, mask); + } + + /* Write samples from winner to output and long-term filter states */ + if (subfr > 0 || i >= decisionDelay) + { + pulses[i - decisionDelay] = + (opus_int8)silk_sar_round_32(silk_select_winner(psLastSample->Q_Q10, Winner_selector), 10); + xq[i - decisionDelay] = + silk_sat16((opus_int32)silk_sar_round_smulww(silk_select_winner(psLastSample->Xq_Q14, Winner_selector), delayedGain_Q10[last_smple_idx], 8)); + NSQ->sLTP_shp_Q14[NSQ->sLTP_shp_buf_idx - decisionDelay] = + silk_select_winner(psLastSample->Shape_Q14, Winner_selector); + sLTP_Q15[NSQ->sLTP_buf_idx - decisionDelay] = + silk_select_winner(psLastSample->Pred_Q15, Winner_selector); + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + psSample = &psDelDec->Samples[*smpl_buf_idx]; + psDelDec->Seed = _mm_add_epi32(psDelDec->Seed, silk_mm_srai_round_epi32(_mm256_castsi256_si128(SS_Q_Q10), 10)); + psDelDec->LF_AR_Q14 = _mm256_castsi256_si128(SS_LF_AR_Q14); + psDelDec->Diff_Q14 = _mm256_castsi256_si128(SS_Diff_Q14); + psDelDec->sLPC_Q14[i + NSQ_LPC_BUF_LENGTH] = _mm256_castsi256_si128(SS_xq_Q14); + psDelDec->RD_Q10 = _mm256_castsi256_si128(SS_RD_Q10); + psSample->Xq_Q14 = _mm256_castsi256_si128(SS_xq_Q14); + psSample->Q_Q10 = _mm256_castsi256_si128(SS_Q_Q10); + psSample->Pred_Q15 = _mm256_castsi256_si128(SS_LPC_exc_Q14); + psSample->Shape_Q14 = _mm256_castsi256_si128(SS_sLTP_shp_Q14); + psSample->RandState = psDelDec->Seed; + delayedGain_Q10[*smpl_buf_idx] = Gain_Q10; + } + /* Update LPC states */ + for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++) + { + psDelDec->sLPC_Q14[i] = (&psDelDec->sLPC_Q14[length])[i]; + } +} + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_avx2( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct *psDelDec, /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[MAX_SUB_FRAME_LENGTH], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I */ + const opus_int pitchL[MAX_NB_SUBFR], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + int i; + opus_int lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + NSQ_del_dec_sample_struct *psSample; + + lag = pitchL[subfr]; + inv_gain_Q31 = silk_INVERSE32_varQ(silk_max(Gains_Q16[subfr], 1), 47); + silk_assert(inv_gain_Q31 != 0); + + /* Scale input */ + inv_gain_Q26 = silk_sar_round_32(inv_gain_Q31, 5); + for (i = 0; i < psEncC->subfr_length; i+=4) + { + __m256i x = _mm256_cvtepi16_epi64(_mm_loadu_si64(&x16[i])); + x = _mm256_slli_epi64(_mm256_mul_epi32(x, _mm256_set1_epi32(inv_gain_Q26)), 16); + _mm_storeu_si128((__m128i*)&x_sc_Q10[i], silk_cvtepi64_epi32_high(x)); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if (NSQ->rewhite_flag) + { + if (subfr == 0) + { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT(silk_SMULWB(inv_gain_Q31, LTP_scale_Q14), 2); + } + for (i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++) + { + silk_assert(i < MAX_FRAME_LENGTH); + sLTP_Q15[i] = silk_SMULWB(inv_gain_Q31, sLTP[i]); + } + } + + /* Adjust for changing gain */ + if (Gains_Q16[subfr] != NSQ->prev_gain_Q16) + { + gain_adj_Q16 = silk_DIV32_varQ(NSQ->prev_gain_Q16, Gains_Q16[subfr], 16); + + /* Scale long-term shaping state */ + for (i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i+=4) + { + opus_int32 *p = &NSQ->sLTP_shp_Q14[i]; + _mm_storeu_si128((__m128i*)p, silk_mm_smulww_epi32(_mm_loadu_si128((__m128i*)p), gain_adj_Q16)); + } + + /* Scale long-term prediction state */ + if (signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0) + { + for (i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++) + { + sLTP_Q15[i] = ((opus_int64)sLTP_Q15[i]) * ((opus_int64)gain_adj_Q16) >> 16; + } + } + + /* Scale scalar states */ + psDelDec->LF_AR_Q14 = silk_mm_smulww_epi32(psDelDec->LF_AR_Q14, gain_adj_Q16); + psDelDec->Diff_Q14 = silk_mm_smulww_epi32(psDelDec->Diff_Q14, gain_adj_Q16); + + /* Scale short-term prediction and shaping states */ + for (i = 0; i < NSQ_LPC_BUF_LENGTH; i++) + { + psDelDec->sLPC_Q14[i] = silk_mm_smulww_epi32(psDelDec->sLPC_Q14[i], gain_adj_Q16); + } + for (i = 0; i < DECISION_DELAY; i++) + { + psSample = &psDelDec->Samples[i]; + psSample->Pred_Q15 = silk_mm_smulww_epi32(psSample->Pred_Q15, gain_adj_Q16); + psSample->Shape_Q14 = silk_mm_smulww_epi32(psSample->Shape_Q14, gain_adj_Q16); + } + for (i = 0; i < MAX_SHAPE_LPC_ORDER; i++) + { + psDelDec->sAR2_Q14[i] = silk_mm_smulww_epi32(psDelDec->sAR2_Q14[i], gain_adj_Q16); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[subfr]; + } +} + +static OPUS_INLINE void silk_LPC_analysis_filter_avx2( + opus_int16 *out, /* O Output signal */ + const opus_int16 *in, /* I Input signal */ + const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ + const opus_int32 len, /* I Signal length */ + const opus_int32 order /* I Filter order */ +) +{ + int i; + opus_int32 out32_Q12, out32; + silk_assert(order == 10 || order == 16); + + for(i = order; i < len; i++ ) + { + const opus_int16 *in_ptr = &in[ i ]; + /* Allowing wrap around so that two wraps can cancel each other. The rare + cases where the result wraps around can only be triggered by invalid streams*/ + + __m256i in_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i*)&in_ptr[-8])); + __m256i B_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i*)& B[0])); + __m256i sum = _mm256_mullo_epi32(in_v, silk_mm256_reverse_epi32(B_v)); + if (order > 10) + { + in_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i*)&in_ptr[-16])); + B_v = _mm256_cvtepi16_epi32(_mm_loadu_si128((__m128i*)&B [8])); + B_v = silk_mm256_reverse_epi32(B_v); + } + else + { + in_v = _mm256_cvtepi16_epi32(_mm_loadu_si32(&in_ptr[-10])); + B_v = _mm256_cvtepi16_epi32(_mm_loadu_si32(&B [8])); + B_v = _mm256_shuffle_epi32(B_v, 0x01); + } + sum = _mm256_add_epi32(sum, _mm256_mullo_epi32(in_v, B_v)); + + out32_Q12 = silk_mm256_hsum_epi32(sum); + + /* Subtract prediction */ + out32_Q12 = silk_SUB32_ovflw( silk_LSHIFT( (opus_int32)*in_ptr, 12 ), out32_Q12 ); + + /* Scale to Q0 */ + out32 = silk_sar_round_32(out32_Q12, 12); + + /* Saturate output */ + out[ i ] = silk_sat16(out32); + } + + /* Set first d output samples to zero */ + silk_memset( out, 0, order * sizeof( opus_int16 ) ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_sse4_1.c new file mode 100644 index 0000000000..5937682d2a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_del_dec_sse4_1.c @@ -0,0 +1,915 @@ +/* Copyright (c) 2014-2020, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang FrancisQuiers + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" + +#include "stack_alloc.h" + +typedef struct { + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 RandState[ DECISION_DELAY ]; + opus_int32 Q_Q10[ DECISION_DELAY ]; + opus_int32 Xq_Q14[ DECISION_DELAY ]; + opus_int32 Pred_Q15[ DECISION_DELAY ]; + opus_int32 Shape_Q14[ DECISION_DELAY ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 Seed; + opus_int32 SeedInit; + opus_int32 RD_Q10; +} NSQ_del_dec_struct; + +typedef struct { + opus_int32 Q_Q10; + opus_int32 RD_Q10; + opus_int32 xq_Q14; + opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; + opus_int32 sLTP_shp_Q14; + opus_int32 LPC_exc_Q14; +} NSQ_sample_struct; + +typedef NSQ_sample_struct NSQ_sample_pair[ 2 ]; + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +); + +void silk_NSQ_del_dec_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int last_smple_idx, smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 RDmin_Q10, Gain_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + VARDECL( opus_int32, delayedGain_Q10 ); + VARDECL( NSQ_del_dec_struct, psDelDec ); + NSQ_del_dec_struct *psDD; +#ifdef OPUS_CHECK_ASM + silk_nsq_state NSQ_c; + SideInfoIndices psIndices_c; + opus_int8 pulses_c[ MAX_FRAME_LENGTH ]; + const opus_int8 *const pulses_a = pulses; +#endif + SAVE_STACK; + +#ifdef OPUS_CHECK_ASM + ( void )pulses_a; + silk_memcpy( &NSQ_c, NSQ, sizeof( NSQ_c ) ); + silk_memcpy( &psIndices_c, psIndices, sizeof( psIndices_c ) ); + silk_assert( psEncC->nb_subfr * psEncC->subfr_length <= MAX_FRAME_LENGTH ); + silk_memcpy( pulses_c, pulses, psEncC->nb_subfr * psEncC->subfr_length * sizeof( pulses[0] ) ); + silk_NSQ_del_dec_c( + psEncC, + &NSQ_c, + &psIndices_c, + x16, + pulses_c, + PredCoef_Q12, + LTPCoef_Q14, + AR_Q13, + HarmShapeGain_Q14, + Tilt_Q14, + LF_shp_Q14, + Gains_Q16, + pitchL, + Lambda_Q10, + LTP_scale_Q14 + ); +#endif + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + /* Initialize delayed decision states */ + ALLOC( psDelDec, psEncC->nStatesDelayedDecision, NSQ_del_dec_struct ); + silk_memset( psDelDec, 0, psEncC->nStatesDelayedDecision * sizeof( NSQ_del_dec_struct ) ); + for( k = 0; k < psEncC->nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psDD->Seed = ( k + psIndices->Seed ) & 3; + psDD->SeedInit = psDD->Seed; + psDD->RD_Q10 = 0; + psDD->LF_AR_Q14 = NSQ->sLF_AR_shp_Q14; + psDD->Diff_Q14 = NSQ->sDiff_shp_Q14; + psDD->Shape_Q14[ 0 ] = NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ]; + silk_memcpy( psDD->sLPC_Q14, NSQ->sLPC_Q14, NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( psDD->sAR2_Q14, NSQ->sAR2_Q14, sizeof( NSQ->sAR2_Q14 ) ); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int( DECISION_DELAY, psEncC->subfr_length ); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if( psIndices->signalType == TYPE_VOICED ) { + for( k = 0; k < psEncC->nb_subfr; k++ ) { + decisionDelay = silk_min_int( decisionDelay, pitchL[ k ] - LTP_ORDER / 2 - 1 ); + } + } else { + if( lag > 0 ) { + decisionDelay = silk_min_int( decisionDelay, lag - LTP_ORDER / 2 - 1 ); + } + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + if( k == 2 ) { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( i = 1; i < psEncC->nStatesDelayedDecision; i++ ) { + if( psDelDec[ i ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ i ].RD_Q10; + Winner_ind = i; + } + } + for( i = 0; i < psEncC->nStatesDelayedDecision; i++ ) { + if( i != Winner_ind ) { + psDelDec[ i ].RD_Q10 += ( silk_int32_MAX >> 4 ); + silk_assert( psDelDec[ i ].RD_Q10 >= 0 ); + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + last_smple_idx = smpl_buf_idx + decisionDelay; + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gains_Q16[ 1 ] ), 14 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states_sse4_1( psEncC, NSQ, psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, + psEncC->nStatesDelayedDecision, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); + + silk_noise_shape_quantizer_del_dec_sse4_1( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], + Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay ); + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < psEncC->nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ k ].RD_Q10; + Winner_ind = k; + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + psIndices->Seed = psDD->SeedInit; + last_smple_idx = smpl_buf_idx + decisionDelay; + Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gain_Q10 ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + silk_memcpy( NSQ->sLPC_Q14, &psDD->sLPC_Q14[ psEncC->subfr_length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( NSQ->sAR2_Q14, psDD->sAR2_Q14, sizeof( psDD->sAR2_Q14 ) ); + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = psDD->LF_AR_Q14; + NSQ->sDiff_shp_Q14 = psDD->Diff_Q14; + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech signal */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + +#ifdef OPUS_CHECK_ASM + silk_assert( !memcmp( &NSQ_c, NSQ, sizeof( NSQ_c ) ) ); + silk_assert( !memcmp( &psIndices_c, psIndices, sizeof( psIndices_c ) ) ); + silk_assert( !memcmp( pulses_c, pulses_a, psEncC->nb_subfr * psEncC->subfr_length * sizeof( pulses[0] ) ) ); +#endif + + RESTORE_STACK; +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I/O Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14; + opus_int32 n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10; + opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; + int rdo_offset; + + VARDECL( NSQ_sample_pair, psSampleState ); + NSQ_del_dec_struct *psDD; + NSQ_sample_struct *psSS; + + __m128i a_Q12_0123, a_Q12_4567, a_Q12_89AB, a_Q12_CDEF; + __m128i b_Q12_0123, b_sr_Q12_0123; + SAVE_STACK; + + celt_assert( nStatesDelayedDecision > 0 ); + ALLOC( psSampleState, nStatesDelayedDecision, NSQ_sample_pair ); + + rdo_offset = (Lambda_Q10 >> 1) - 512; + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + a_Q12_0123 = OP_CVTEPI16_EPI32_M64( a_Q12 ); + a_Q12_4567 = OP_CVTEPI16_EPI32_M64( a_Q12 + 4 ); + + if( opus_likely( predictLPCOrder == 16 ) ) { + a_Q12_89AB = OP_CVTEPI16_EPI32_M64( a_Q12 + 8 ); + a_Q12_CDEF = OP_CVTEPI16_EPI32_M64( a_Q12 + 12 ); + } + + if( signalType == TYPE_VOICED ){ + b_Q12_0123 = OP_CVTEPI16_EPI32_M64( b_Q14 ); + b_sr_Q12_0123 = _mm_shuffle_epi32( b_Q12_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + } + for( i = 0; i < length; i++ ) { + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + { + __m128i tmpa, tmpb, pred_lag_ptr_tmp; + pred_lag_ptr_tmp = _mm_loadu_si128( (__m128i *)(void*)(&pred_lag_ptr[ -3 ] ) ); + pred_lag_ptr_tmp = _mm_shuffle_epi32( pred_lag_ptr_tmp, 0x1B ); + tmpa = _mm_mul_epi32( pred_lag_ptr_tmp, b_Q12_0123 ); + tmpa = _mm_srli_si128( tmpa, 2 ); + + pred_lag_ptr_tmp = _mm_shuffle_epi32( pred_lag_ptr_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) );/* equal shift right 4 bytes */ + pred_lag_ptr_tmp = _mm_mul_epi32( pred_lag_ptr_tmp, b_sr_Q12_0123 ); + pred_lag_ptr_tmp = _mm_srli_si128( pred_lag_ptr_tmp, 2 ); + pred_lag_ptr_tmp = _mm_add_epi32( pred_lag_ptr_tmp, tmpa ); + + tmpb = _mm_shuffle_epi32( pred_lag_ptr_tmp, _MM_SHUFFLE( 0, 0, 3, 2 ) );/* equal shift right 8 bytes */ + pred_lag_ptr_tmp = _mm_add_epi32( pred_lag_ptr_tmp, tmpb ); + LTP_pred_Q14 += _mm_cvtsi128_si32( pred_lag_ptr_tmp ); + + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + { + __m128i tmpa, tmpb, psLPC_Q14_tmp, a_Q12_tmp; + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + /* Delayed decision state */ + psDD = &psDelDec[ k ]; + + /* Sample state */ + psSS = psSampleState[ k ]; + + /* Generate dither */ + psDD->Seed = silk_RAND( psDD->Seed ); + + /* Pointer used in short term prediction and shaping */ + psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; + /* Short-term prediction */ + silk_assert( predictLPCOrder == 10 || predictLPCOrder == 16 ); + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q14 = silk_RSHIFT( predictLPCOrder, 1 ); + + tmpb = _mm_setzero_si128(); + + /* step 1 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -3 ] ) ); /* -3, -2 , -1, 0 */ + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); /* 0, -1, -2, -3 */ + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_0123 ); /* 0, -1, -2, -3 * 0123 -> 0*0, 2*-2 */ + + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_0123, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); /* 1*-1, 3*-3 */ + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* step 2 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -7 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_4567 ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_4567, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + if ( opus_likely( predictLPCOrder == 16 ) ) + { + /* step 3 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -11 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_89AB ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_89AB, _MM_SHUFFLE(0, 3, 2, 1 ) );/* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* step 4 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -15 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_CDEF ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_CDEF, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* add at last */ + /* equal shift right 8 bytes*/ + tmpa = _mm_shuffle_epi32( tmpb, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + LPC_pred_Q14 += _mm_cvtsi128_si32( tmpb ); + } + else + { + /* add at last */ + tmpa = _mm_shuffle_epi32( tmpb, _MM_SHUFFLE( 0, 0, 3, 2 ) ); /* equal shift right 8 bytes*/ + tmpb = _mm_add_epi32( tmpb, tmpa ); + LPC_pred_Q14 += _mm_cvtsi128_si32( tmpb ); + + LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -8 ], a_Q12[ 8 ] ); + LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -9 ], a_Q12[ 9 ] ); + } + + LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + celt_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + /* Output of lowpass section */ + tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ 0 ] = tmp2; + n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 ); + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] ); + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + /* Output of allpass section */ + tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 0 ], tmp1), warping_Q16 ); + psDD->sAR2_Q14[ j - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], silk_SUB32_ovflw(psDD->sAR2_Q14[ j + 1 ], tmp2), warping_Q16 ); + psDD->sAR2_Q14[ j + 0 ] = tmp2; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] ); + } + psDD->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 1 ); /* Q11 -> Q12 */ + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, psDD->LF_AR_Q14, Tilt_Q14 ); /* Q12 */ + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 2 ); /* Q12 -> Q14 */ + + n_LF_Q14 = silk_SMULWB( psDD->Shape_Q14[ *smpl_buf_idx ], LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_SMLAWT( n_LF_Q14, psDD->LF_AR_Q14, LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_LSHIFT( n_LF_Q14, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1 = silk_ADD_SAT32( n_AR_Q14, n_LF_Q14 ); /* Q14 */ + tmp2 = silk_ADD32_ovflw( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */ + tmp1 = silk_SUB_SAT32( tmp2, tmp1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */ + + r_Q10 = silk_SUB32( x_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if ( psDD->Seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q10 = silk_RSHIFT( silk_SMLABB( rd1_Q10, rr_Q10, rr_Q10 ), 10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q10 = silk_RSHIFT( silk_SMLABB( rd2_Q10, rr_Q10, rr_Q10 ), 10 ); + + if( rd1_Q10 < rd2_Q10 ) { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 0 ].Q_Q10 = q1_Q10; + psSS[ 1 ].Q_Q10 = q2_Q10; + } else { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 0 ].Q_Q10 = q2_Q10; + psSS[ 1 ].Q_Q10 = q1_Q10; + } + + /* Update states for best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 0 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 0 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 0 ].Diff_Q14, n_AR_Q14 ); + psSS[ 0 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 0 ].xq_Q14 = xq_Q14; + + /* Update states for second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 1 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32_ovflw( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + psSS[ 1 ].Diff_Q14 = silk_SUB32_ovflw( xq_Q14, silk_LSHIFT32( x_Q10[ i ], 4 ) ); + sLF_AR_shp_Q14 = silk_SUB32_ovflw( psSS[ 1 ].Diff_Q14, n_AR_Q14 ); + psSS[ 1 ].sLTP_shp_Q14 = silk_SUB_SAT32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 1 ].xq_Q14 = xq_Q14; + } + } + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ k ][ 0 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + Winner_ind = k; + } + } + + /* Increase RD values of expired states */ + Winner_rand_state = psDelDec[ Winner_ind ].RandState[ last_smple_idx ]; + for( k = 0; k < nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RandState[ last_smple_idx ] != Winner_rand_state ) { + psSampleState[ k ][ 0 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 0 ].RD_Q10, silk_int32_MAX >> 4 ); + psSampleState[ k ][ 1 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 1 ].RD_Q10, silk_int32_MAX >> 4 ); + silk_assert( psSampleState[ k ][ 0 ].RD_Q10 >= 0 ); + } + } + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + RDmin_Q10 = psSampleState[ 0 ][ 1 ].RD_Q10; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ k ][ 0 ].RD_Q10 > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ k ][ 1 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 1 ].RD_Q10; + RDmin_ind = k; + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + silk_memcpy( ( (opus_int32 *)&psDelDec[ RDmax_ind ] ) + i, + ( (opus_int32 *)&psDelDec[ RDmin_ind ] ) + i, sizeof( NSQ_del_dec_struct ) - i * sizeof( opus_int32) ); + silk_memcpy( &psSampleState[ RDmax_ind ][ 0 ], &psSampleState[ RDmin_ind ][ 1 ], sizeof( NSQ_sample_struct ) ); + } + + /* Write samples from winner to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDD->Shape_Q14[ last_smple_idx ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDD->Pred_Q15[ last_smple_idx ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psSS = &psSampleState[ k ][ 0 ]; + psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->Diff_Q14 = psSS->Diff_Q14; + psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; + psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; + psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; + psDD->Pred_Q15[ *smpl_buf_idx ] = silk_LSHIFT32( psSS->LPC_exc_Q14, 1 ); + psDD->Shape_Q14[ *smpl_buf_idx ] = psSS->sLTP_shp_Q14; + psDD->Seed = silk_ADD32_ovflw( psDD->Seed, silk_RSHIFT_ROUND( psSS->Q_Q10, 10 ) ); + psDD->RandState[ *smpl_buf_idx ] = psDD->Seed; + psDD->RD_Q10 = psSS->RD_Q10; + } + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + silk_memcpy( psDD->sLPC_Q14, &psDD->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int16 x16[], /* I Input */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + opus_int i, k, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + NSQ_del_dec_struct *psDD; + __m128i xmm_inv_gain_Q26, xmm_x16_x2x0, xmm_x16_x3x1; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + + /* prepare inv_gain_Q26 in packed 4 32-bits */ + xmm_inv_gain_Q26 = _mm_set1_epi32(inv_gain_Q26); + + for( i = 0; i < psEncC->subfr_length - 3; i += 4 ) { + xmm_x16_x2x0 = OP_CVTEPI16_EPI32_M64( &(x16[ i ] ) ); + + /* equal shift right 4 bytes*/ + xmm_x16_x3x1 = _mm_shuffle_epi32( xmm_x16_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x16_x2x0 = _mm_mul_epi32( xmm_x16_x2x0, xmm_inv_gain_Q26 ); + xmm_x16_x3x1 = _mm_mul_epi32( xmm_x16_x3x1, xmm_inv_gain_Q26 ); + + xmm_x16_x2x0 = _mm_srli_epi64( xmm_x16_x2x0, 16 ); + xmm_x16_x3x1 = _mm_slli_epi64( xmm_x16_x3x1, 16 ); + + xmm_x16_x2x0 = _mm_blend_epi16( xmm_x16_x2x0, xmm_x16_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(void*)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 ); + } + + for( ; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + { + __m128i xmm_gain_adj_Q16, xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1; + + /* prepare gain_adj_Q16 in packed 4 32-bits */ + xmm_gain_adj_Q16 = _mm_set1_epi32( gain_adj_Q16 ); + + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 ) + { + xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ) ); + /* equal shift right 4 bytes*/ + xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x2x0, xmm_gain_adj_Q16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x3x1, xmm_gain_adj_Q16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_srli_epi64( xmm_sLTP_shp_Q14_x2x0, 16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_slli_epi64( xmm_sLTP_shp_Q14_x3x1, 16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 ); + } + + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + + /* Scale scalar states */ + psDD->LF_AR_Q14 = silk_SMULWW( gain_adj_Q16, psDD->LF_AR_Q14 ); + psDD->Diff_Q14 = silk_SMULWW( gain_adj_Q16, psDD->Diff_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + psDD->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + psDD->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sAR2_Q14[ i ] ); + } + for( i = 0; i < DECISION_DELAY; i++ ) { + psDD->Pred_Q15[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Pred_Q15[ i ] ); + psDD->Shape_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Shape_Q14[ i ] ); + } + } + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_sse4_1.c new file mode 100644 index 0000000000..3c9aca7ba1 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/NSQ_sse4_1.c @@ -0,0 +1,772 @@ +/* Copyright (c) 2014-2020, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang FrancisQuiers + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" +#include "stack_alloc.h" + +static OPUS_INLINE void silk_nsq_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +); + +static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int32 table[][4] /* I */ +); + +void silk_NSQ_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int k, lag, start_idx, LSF_interpolation_flag; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + + opus_int32 table[ 64 ][ 4 ]; + opus_int32 tmp1; + opus_int32 q1_Q10, q2_Q10, rd1_Q20, rd2_Q20; + +#ifdef OPUS_CHECK_ASM + silk_nsq_state NSQ_c; + SideInfoIndices psIndices_c; + opus_int8 pulses_c[ MAX_FRAME_LENGTH ]; + const opus_int8 *const pulses_a = pulses; +#endif + + SAVE_STACK; + +#ifdef OPUS_CHECK_ASM + ( void )pulses_a; + silk_memcpy( &NSQ_c, NSQ, sizeof( NSQ_c ) ); + silk_memcpy( &psIndices_c, psIndices, sizeof( psIndices_c ) ); + silk_assert( psEncC->nb_subfr * psEncC->subfr_length <= MAX_FRAME_LENGTH ); + silk_memcpy( pulses_c, pulses, psEncC->nb_subfr * psEncC->subfr_length * sizeof( pulses[0] ) ); + + silk_NSQ_c( + psEncC, + &NSQ_c, + &psIndices_c, + x16, + pulses_c, + PredCoef_Q12, + LTPCoef_Q14, + AR_Q13, + HarmShapeGain_Q14, + Tilt_Q14, + LF_shp_Q14, + Gains_Q16, + pitchL, + Lambda_Q10, + LTP_scale_Q14 + ); +#endif + + NSQ->rand_seed = psIndices->Seed; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + + /* 0 */ + q1_Q10 = offset_Q10; + q2_Q10 = offset_Q10 + ( 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 32 ][ 0 ] = q1_Q10; + table[ 32 ][ 1 ] = q2_Q10; + table[ 32 ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + + /* -1 */ + q1_Q10 = offset_Q10 - ( 1024 - QUANT_LEVEL_ADJUST_Q10 ); + q2_Q10 = offset_Q10; + rd1_Q20 = - q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 31 ][ 0 ] = q1_Q10; + table[ 31 ][ 1 ] = q2_Q10; + table[ 31 ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 31 ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + + /* > 0 */ + for (k = 1; k <= 31; k++) + { + tmp1 = offset_Q10 + silk_LSHIFT( k, 10 ); + + q1_Q10 = tmp1 - QUANT_LEVEL_ADJUST_Q10; + q2_Q10 = tmp1 - QUANT_LEVEL_ADJUST_Q10 + 1024; + rd1_Q20 = q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 32 + k ][ 0 ] = q1_Q10; + table[ 32 + k ][ 1 ] = q2_Q10; + table[ 32 + k ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 + k ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + } + + /* < -1 */ + for (k = -32; k <= -2; k++) + { + tmp1 = offset_Q10 + silk_LSHIFT( k, 10 ); + + q1_Q10 = tmp1 + QUANT_LEVEL_ADJUST_Q10; + q2_Q10 = tmp1 + QUANT_LEVEL_ADJUST_Q10 + 1024; + rd1_Q20 = - q1_Q10 * Lambda_Q10; + rd2_Q20 = - q2_Q10 * Lambda_Q10; + + table[ 32 + k ][ 0 ] = q1_Q10; + table[ 32 + k ][ 1 ] = q2_Q10; + table[ 32 + k ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 + k ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + /* Set up pointers to start of sub frame */ + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ (( k >> 1 ) | ( 1 - LSF_interpolation_flag )) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + celt_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->rewhite_flag = 1; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + } + } + + silk_nsq_scale_states_sse4_1( psEncC, NSQ, x16, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); + + if ( opus_likely( ( 10 == psEncC->shapingLPCOrder ) && ( 16 == psEncC->predictLPCOrder) ) ) + { + silk_noise_shape_quantizer_10_16_sse4_1( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, + offset_Q10, psEncC->subfr_length, &(table[32]) ); + } + else + { + silk_noise_shape_quantizer( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, + offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder, psEncC->arch ); + } + + x16 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Update lagPrev for next frame */ + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech and noise shaping signals */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + +#ifdef OPUS_CHECK_ASM + silk_assert( !memcmp( &NSQ_c, NSQ, sizeof( NSQ_c ) ) ); + silk_assert( !memcmp( &psIndices_c, psIndices, sizeof( psIndices_c ) ) ); + silk_assert( !memcmp( pulses_c, pulses_a, psEncC->nb_subfr * psEncC->subfr_length * sizeof( pulses[0] ) ) ); +#endif + + RESTORE_STACK; +} + +/************************************/ +/* silk_noise_shape_quantizer_10_16 */ +/************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int32 table[][4] /* I */ +) +{ + opus_int i; + opus_int32 LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13; + opus_int32 n_LF_Q12, r_Q10, q1_Q0, q1_Q10, q2_Q10; + opus_int32 exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10, sDiff_shp_Q14; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *psLPC_Q14, *shp_lag_ptr, *pred_lag_ptr; + + __m128i xmm_tempa, xmm_tempb; + + __m128i xmm_one; + + __m128i psLPC_Q14_hi_01234567, psLPC_Q14_hi_89ABCDEF; + __m128i psLPC_Q14_lo_01234567, psLPC_Q14_lo_89ABCDEF; + __m128i a_Q12_01234567, a_Q12_89ABCDEF; + + __m128i sAR2_Q14_hi_76543210, sAR2_Q14_lo_76543210; + __m128i AR_shp_Q13_76543210; + + int rdo_offset = (Lambda_Q10 >> 1) - 512; + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + /* Set up short term AR state */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 ]; + + sLF_AR_shp_Q14 = NSQ->sLF_AR_shp_Q14; + xq_Q14 = psLPC_Q14[ 0 ]; + sDiff_shp_Q14 = NSQ->sDiff_shp_Q14; + LTP_pred_Q13 = 0; + + /* load a_Q12 */ + xmm_one = _mm_set_epi8( 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 ); + + /* load a_Q12[0] - a_Q12[7] */ + a_Q12_01234567 = _mm_loadu_si128( (__m128i *)(void*)(&a_Q12[ 0 ] ) ); + /* load a_Q12[ 8 ] - a_Q12[ 15 ] */ + a_Q12_89ABCDEF = _mm_loadu_si128( (__m128i *)(void*)(&a_Q12[ 8 ] ) ); + + a_Q12_01234567 = _mm_shuffle_epi8( a_Q12_01234567, xmm_one ); + a_Q12_89ABCDEF = _mm_shuffle_epi8( a_Q12_89ABCDEF, xmm_one ); + + /* load AR_shp_Q13 */ + AR_shp_Q13_76543210 = _mm_loadu_si128( (__m128i *)(void*)(&AR_shp_Q13[0] ) ); + + /* load psLPC_Q14 */ + xmm_one = _mm_set_epi8(15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0 ); + + xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[-16]) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[-12]) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + psLPC_Q14_hi_89ABCDEF = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + psLPC_Q14_lo_89ABCDEF = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -8 ]) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&psLPC_Q14[ -4 ]) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + psLPC_Q14_hi_01234567 = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + psLPC_Q14_lo_01234567 = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + /* load sAR2_Q14 */ + xmm_tempa = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sAR2_Q14[ 0 ]) ) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sAR2_Q14[ 4 ]) ) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + sAR2_Q14_hi_76543210 = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + sAR2_Q14_lo_76543210 = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + /* prepare 1 in 8 * 16bit */ + xmm_one = _mm_set1_epi16(1); + + for( i = 0; i < length; i++ ) + { + /* Short-term prediction */ + __m128i xmm_hi_07, xmm_hi_8F, xmm_lo_07, xmm_lo_8F; + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = 8; /* silk_RSHIFT( predictLPCOrder, 1 ); */ + + /* shift psLPC_Q14 */ + psLPC_Q14_hi_89ABCDEF = _mm_alignr_epi8( psLPC_Q14_hi_01234567, psLPC_Q14_hi_89ABCDEF, 2 ); + psLPC_Q14_lo_89ABCDEF = _mm_alignr_epi8( psLPC_Q14_lo_01234567, psLPC_Q14_lo_89ABCDEF, 2 ); + + psLPC_Q14_hi_01234567 = _mm_srli_si128( psLPC_Q14_hi_01234567, 2 ); + psLPC_Q14_lo_01234567 = _mm_srli_si128( psLPC_Q14_lo_01234567, 2 ); + + psLPC_Q14_hi_01234567 = _mm_insert_epi16( psLPC_Q14_hi_01234567, (xq_Q14 >> 16), 7 ); + psLPC_Q14_lo_01234567 = _mm_insert_epi16( psLPC_Q14_lo_01234567, (xq_Q14), 7 ); + + /* high part, use pmaddwd, results in 4 32-bit */ + xmm_hi_07 = _mm_madd_epi16( psLPC_Q14_hi_01234567, a_Q12_01234567 ); + xmm_hi_8F = _mm_madd_epi16( psLPC_Q14_hi_89ABCDEF, a_Q12_89ABCDEF ); + + /* low part, use pmulhw, results in 8 16-bit, note we need simulate unsigned * signed, _mm_srai_epi16(psLPC_Q14_lo_01234567, 15) */ + xmm_tempa = _mm_cmpgt_epi16( _mm_setzero_si128(), psLPC_Q14_lo_01234567 ); + xmm_tempb = _mm_cmpgt_epi16( _mm_setzero_si128(), psLPC_Q14_lo_89ABCDEF ); + + xmm_tempa = _mm_and_si128( xmm_tempa, a_Q12_01234567 ); + xmm_tempb = _mm_and_si128( xmm_tempb, a_Q12_89ABCDEF ); + + xmm_lo_07 = _mm_mulhi_epi16( psLPC_Q14_lo_01234567, a_Q12_01234567 ); + xmm_lo_8F = _mm_mulhi_epi16( psLPC_Q14_lo_89ABCDEF, a_Q12_89ABCDEF ); + + xmm_lo_07 = _mm_add_epi16( xmm_lo_07, xmm_tempa ); + xmm_lo_8F = _mm_add_epi16( xmm_lo_8F, xmm_tempb ); + + xmm_lo_07 = _mm_madd_epi16( xmm_lo_07, xmm_one ); + xmm_lo_8F = _mm_madd_epi16( xmm_lo_8F, xmm_one ); + + /* accumulate */ + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_hi_8F ); + xmm_lo_07 = _mm_add_epi32( xmm_lo_07, xmm_lo_8F ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_lo_07 ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_unpackhi_epi64(xmm_hi_07, xmm_hi_07 ) ); + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_shufflelo_epi16(xmm_hi_07, 0x0E ) ); + + LPC_pred_Q10 += _mm_cvtsi128_si32( xmm_hi_07 ); + + /* Long-term prediction */ + if ( opus_likely( signalType == TYPE_VOICED ) ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + { + __m128i b_Q14_3210, b_Q14_0123, pred_lag_ptr_0123; + + b_Q14_3210 = OP_CVTEPI16_EPI32_M64( b_Q14 ); + b_Q14_0123 = _mm_shuffle_epi32( b_Q14_3210, 0x1B ); + + /* loaded: [0] [-1] [-2] [-3] */ + pred_lag_ptr_0123 = _mm_loadu_si128( (__m128i *)(void*)(&pred_lag_ptr[ -3 ] ) ); + /* shuffle to [-3] [-2] [-1] [0] and to new xmm */ + xmm_tempa = _mm_shuffle_epi32( pred_lag_ptr_0123, 0x1B ); + /*64-bit multiply, a[2] * b[-2], a[0] * b[0] */ + xmm_tempa = _mm_mul_epi32( xmm_tempa, b_Q14_3210 ); + /* right shift 2 bytes (16 bits), zero extended */ + xmm_tempa = _mm_srli_si128( xmm_tempa, 2 ); + + /* a[1] * b[-1], a[3] * b[-3] */ + pred_lag_ptr_0123 = _mm_mul_epi32( pred_lag_ptr_0123, b_Q14_0123 ); + pred_lag_ptr_0123 = _mm_srli_si128( pred_lag_ptr_0123, 2 ); + + pred_lag_ptr_0123 = _mm_add_epi32( pred_lag_ptr_0123, xmm_tempa ); + /* equal shift right 8 bytes*/ + xmm_tempa = _mm_shuffle_epi32( pred_lag_ptr_0123, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + xmm_tempa = _mm_add_epi32( xmm_tempa, pred_lag_ptr_0123 ); + + LTP_pred_Q13 += _mm_cvtsi128_si32( xmm_tempa ); + + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + pred_lag_ptr++; + } + } + + /* Noise shape feedback */ + NSQ->sAR2_Q14[ 9 ] = NSQ->sAR2_Q14[ 8 ]; + NSQ->sAR2_Q14[ 8 ] = _mm_cvtsi128_si32( _mm_srli_si128(_mm_unpackhi_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ), 12 ) ); + + sAR2_Q14_hi_76543210 = _mm_slli_si128( sAR2_Q14_hi_76543210, 2 ); + sAR2_Q14_lo_76543210 = _mm_slli_si128( sAR2_Q14_lo_76543210, 2 ); + + sAR2_Q14_hi_76543210 = _mm_insert_epi16( sAR2_Q14_hi_76543210, (sDiff_shp_Q14 >> 16), 0 ); + sAR2_Q14_lo_76543210 = _mm_insert_epi16( sAR2_Q14_lo_76543210, (sDiff_shp_Q14), 0 ); + + /* high part, use pmaddwd, results in 4 32-bit */ + xmm_hi_07 = _mm_madd_epi16( sAR2_Q14_hi_76543210, AR_shp_Q13_76543210 ); + + /* low part, use pmulhw, results in 8 16-bit, note we need simulate unsigned * signed,_mm_srai_epi16(sAR2_Q14_lo_76543210, 15) */ + xmm_tempa = _mm_cmpgt_epi16( _mm_setzero_si128(), sAR2_Q14_lo_76543210 ); + xmm_tempa = _mm_and_si128( xmm_tempa, AR_shp_Q13_76543210 ); + + xmm_lo_07 = _mm_mulhi_epi16( sAR2_Q14_lo_76543210, AR_shp_Q13_76543210 ); + xmm_lo_07 = _mm_add_epi16( xmm_lo_07, xmm_tempa ); + + xmm_lo_07 = _mm_madd_epi16( xmm_lo_07, xmm_one ); + + /* accumulate */ + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_lo_07 ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_unpackhi_epi64(xmm_hi_07, xmm_hi_07 ) ); + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_shufflelo_epi16(xmm_hi_07, 0x0E ) ); + + n_AR_Q12 = 5 + _mm_cvtsi128_si32( xmm_hi_07 ); + + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sAR2_Q14[ 8 ], AR_shp_Q13[ 8 ] ); + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sAR2_Q14[ 9 ], AR_shp_Q13[ 9 ] ); + + n_AR_Q12 = silk_LSHIFT32( n_AR_Q12, 1 ); /* Q11 -> Q12 */ + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, sLF_AR_shp_Q14, Tilt_Q14 ); + + n_LF_Q12 = silk_SMULWB( NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - 1 ], LF_shp_Q14 ); + n_LF_Q12 = silk_SMLAWT( n_LF_Q12, sLF_AR_shp_Q14, LF_shp_Q14 ); + + celt_assert( lag > 0 || signalType != TYPE_VOICED ); + + /* Combine prediction and noise shaping signals */ + tmp1 = silk_SUB32( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */ + tmp1 = silk_SUB32( tmp1, n_LF_Q12 ); /* Q12 */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q13 = silk_SMULWB( silk_ADD_SAT32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_SMLAWT( n_LTP_Q13, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_LSHIFT( n_LTP_Q13, 1 ); + shp_lag_ptr++; + + tmp2 = silk_SUB32( LTP_pred_Q13, n_LTP_Q13 ); /* Q13 */ + tmp1 = silk_ADD_LSHIFT32( tmp2, tmp1, 1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 3 ); /* Q10 */ + } else { + tmp1 = silk_RSHIFT_ROUND( tmp1, 2 ); /* Q10 */ + } + + r_Q10 = silk_SUB32( x_sc_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Generate dither */ + NSQ->rand_seed = silk_RAND( NSQ->rand_seed ); + + /* Flip sign depending on dither */ + tmp2 = -r_Q10; + if ( NSQ->rand_seed < 0 ) r_Q10 = tmp2; + + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } + + q1_Q10 = table[q1_Q0][0]; + q2_Q10 = table[q1_Q0][1]; + + if (r_Q10 * table[q1_Q0][2] - table[q1_Q0][3] < 0) + { + q1_Q10 = q2_Q10; + } + + pulses[ i ] = (opus_int8)silk_RSHIFT_ROUND( q1_Q10, 10 ); + + /* Excitation */ + exc_Q14 = silk_LSHIFT( q1_Q10, 4 ); + + tmp2 = -exc_Q14; + if ( NSQ->rand_seed < 0 ) exc_Q14 = tmp2; + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD_LSHIFT32( exc_Q14, LTP_pred_Q13, 1 ); + xq_Q14 = silk_ADD_LSHIFT32( LPC_exc_Q14, LPC_pred_Q10, 4 ); + + /* Update states */ + psLPC_Q14++; + *psLPC_Q14 = xq_Q14; + NSQ->sDiff_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_sc_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB_LSHIFT32( NSQ->sDiff_shp_Q14, n_AR_Q12, 2 ); + + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB_LSHIFT32( sLF_AR_shp_Q14, n_LF_Q12, 2 ); + sLTP_Q15[ NSQ->sLTP_buf_idx ] = silk_LSHIFT( LPC_exc_Q14, 1 ); + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Make dither dependent on quantized signal */ + NSQ->rand_seed = silk_ADD32_ovflw( NSQ->rand_seed, pulses[ i ] ); + } + + NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14; + + /* Scale XQ back to normal level before saving */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH ]; + + /* write back sAR2_Q14 */ + xmm_tempa = _mm_unpackhi_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ); + xmm_tempb = _mm_unpacklo_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ); + _mm_storeu_si128( (__m128i *)(void*)(&NSQ->sAR2_Q14[ 4 ]), xmm_tempa ); + _mm_storeu_si128( (__m128i *)(void*)(&NSQ->sAR2_Q14[ 0 ]), xmm_tempb ); + + /* xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psLPC_Q14[ i ], Gain_Q10 ), 8 ) ); */ + { + __m128i xmm_Gain_Q10; + __m128i xmm_xq_Q14_3210, xmm_xq_Q14_x3x1, xmm_xq_Q14_7654, xmm_xq_Q14_x7x5; + + /* prepare (1 << 7) in packed 4 32-bits */ + xmm_tempa = _mm_set1_epi32( (1 << 7) ); + + /* prepare Gain_Q10 in packed 4 32-bits */ + xmm_Gain_Q10 = _mm_set1_epi32( Gain_Q10 ); + + /* process xq */ + for (i = 0; i < length - 7; i += 8) + { + xmm_xq_Q14_3210 = _mm_loadu_si128( (__m128i *)(void*)(&(psLPC_Q14[ i + 0 ] ) ) ); + xmm_xq_Q14_7654 = _mm_loadu_si128( (__m128i *)(void*)(&(psLPC_Q14[ i + 4 ] ) ) ); + + /* equal shift right 4 bytes*/ + xmm_xq_Q14_x3x1 = _mm_shuffle_epi32( xmm_xq_Q14_3210, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + /* equal shift right 4 bytes*/ + xmm_xq_Q14_x7x5 = _mm_shuffle_epi32( xmm_xq_Q14_7654, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_xq_Q14_3210 = _mm_mul_epi32( xmm_xq_Q14_3210, xmm_Gain_Q10 ); + xmm_xq_Q14_x3x1 = _mm_mul_epi32( xmm_xq_Q14_x3x1, xmm_Gain_Q10 ); + xmm_xq_Q14_7654 = _mm_mul_epi32( xmm_xq_Q14_7654, xmm_Gain_Q10 ); + xmm_xq_Q14_x7x5 = _mm_mul_epi32( xmm_xq_Q14_x7x5, xmm_Gain_Q10 ); + + xmm_xq_Q14_3210 = _mm_srli_epi64( xmm_xq_Q14_3210, 16 ); + xmm_xq_Q14_x3x1 = _mm_slli_epi64( xmm_xq_Q14_x3x1, 16 ); + xmm_xq_Q14_7654 = _mm_srli_epi64( xmm_xq_Q14_7654, 16 ); + xmm_xq_Q14_x7x5 = _mm_slli_epi64( xmm_xq_Q14_x7x5, 16 ); + + xmm_xq_Q14_3210 = _mm_blend_epi16( xmm_xq_Q14_3210, xmm_xq_Q14_x3x1, 0xCC ); + xmm_xq_Q14_7654 = _mm_blend_epi16( xmm_xq_Q14_7654, xmm_xq_Q14_x7x5, 0xCC ); + + /* silk_RSHIFT_ROUND(xq, 8) */ + xmm_xq_Q14_3210 = _mm_add_epi32( xmm_xq_Q14_3210, xmm_tempa ); + xmm_xq_Q14_7654 = _mm_add_epi32( xmm_xq_Q14_7654, xmm_tempa ); + + xmm_xq_Q14_3210 = _mm_srai_epi32( xmm_xq_Q14_3210, 8 ); + xmm_xq_Q14_7654 = _mm_srai_epi32( xmm_xq_Q14_7654, 8 ); + + /* silk_SAT16 */ + xmm_xq_Q14_3210 = _mm_packs_epi32( xmm_xq_Q14_3210, xmm_xq_Q14_7654 ); + + /* save to xq */ + _mm_storeu_si128( (__m128i *)(void*)(&xq[ i ] ), xmm_xq_Q14_3210 ); + } + } + for ( ; i < length; i++) + { + xq[i] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psLPC_Q14[ i ], Gain_Q10 ), 8 ) ); + } + + /* Update LPC synth buffer */ + silk_memcpy( NSQ->sLPC_Q14, &NSQ->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); +} + +static OPUS_INLINE void silk_nsq_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int16 x16[], /* I input */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +) +{ + opus_int i, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; + __m128i xmm_inv_gain_Q26, xmm_x16_x2x0, xmm_x16_x3x1; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Scale input */ + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); + + /* prepare inv_gain_Q26 in packed 4 32-bits */ + xmm_inv_gain_Q26 = _mm_set1_epi32(inv_gain_Q26); + + for( i = 0; i < psEncC->subfr_length - 3; i += 4 ) { + xmm_x16_x2x0 = OP_CVTEPI16_EPI32_M64( &(x16[ i ] ) ); + + /* equal shift right 4 bytes*/ + xmm_x16_x3x1 = _mm_shuffle_epi32( xmm_x16_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x16_x2x0 = _mm_mul_epi32( xmm_x16_x2x0, xmm_inv_gain_Q26 ); + xmm_x16_x3x1 = _mm_mul_epi32( xmm_x16_x3x1, xmm_inv_gain_Q26 ); + + xmm_x16_x2x0 = _mm_srli_epi64( xmm_x16_x2x0, 16 ); + xmm_x16_x3x1 = _mm_slli_epi64( xmm_x16_x3x1, 16 ); + + xmm_x16_x2x0 = _mm_blend_epi16( xmm_x16_x2x0, xmm_x16_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(void*)(&(x_sc_Q10[ i ] ) ), xmm_x16_x2x0 ); + } + + for( ; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); + } + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + __m128i xmm_gain_adj_Q16, xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1; + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + + /* Scale long-term shaping state */ + + /* prepare gain_adj_Q16 in packed 4 32-bits */ + xmm_gain_adj_Q16 = _mm_set1_epi32(gain_adj_Q16); + + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 ) + { + xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ) ); + /* equal shift right 4 bytes*/ + xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x2x0, xmm_gain_adj_Q16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x3x1, xmm_gain_adj_Q16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_srli_epi64( xmm_sLTP_shp_Q14_x2x0, 16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_slli_epi64( xmm_sLTP_shp_Q14_x3x1, 16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(void*)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 ); + } + + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + NSQ->sLF_AR_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sLF_AR_shp_Q14 ); + NSQ->sDiff_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sDiff_shp_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + NSQ->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + NSQ->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sAR2_Q14[ i ] ); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/SigProc_FIX_sse.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/SigProc_FIX_sse.h new file mode 100644 index 0000000000..89a5ec888a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/SigProc_FIX_sse.h @@ -0,0 +1,99 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIGPROC_FIX_SSE_H +# define SIGPROC_FIX_SSE_H + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE4_1) +void silk_burg_modified_sse4_1( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +); + +# if defined(OPUS_X86_PRESUME_SSE4_1) + +# define OVERRIDE_silk_burg_modified +# define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((void)(arch), silk_burg_modified_sse4_1(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) + +# elif defined(OPUS_HAVE_RTCD) + +extern void (*const SILK_BURG_MODIFIED_IMPL[OPUS_ARCHMASK + 1])( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */); + +# define OVERRIDE_silk_burg_modified +# define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((*SILK_BURG_MODIFIED_IMPL[(arch) & OPUS_ARCHMASK])(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) + +# endif + +opus_int64 silk_inner_prod16_sse4_1( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len +); + + +# if defined(OPUS_X86_PRESUME_SSE4_1) + +# define OVERRIDE_silk_inner_prod16 +# define silk_inner_prod16(inVec1, inVec2, len, arch) \ + ((void)(arch),silk_inner_prod16_sse4_1(inVec1, inVec2, len)) + +# elif defined(OPUS_HAVE_RTCD) + +extern opus_int64 (*const SILK_INNER_PROD16_IMPL[OPUS_ARCHMASK + 1])( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len); + +# define OVERRIDE_silk_inner_prod16 +# define silk_inner_prod16(inVec1, inVec2, len, arch) \ + ((*SILK_INNER_PROD16_IMPL[(arch) & OPUS_ARCHMASK])(inVec1, inVec2, len)) + +# endif +# endif +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VAD_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VAD_sse4_1.c new file mode 100644 index 0000000000..9e06bc79d0 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VAD_sse4_1.c @@ -0,0 +1,289 @@ +/* Copyright (c) 2014-2020, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang FrancisQuiers + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "main.h" +#include "stack_alloc.h" + +/* Weighting factors for tilt measure */ +static const opus_int32 tiltWeights[ VAD_N_BANDS ] = { 30000, 6000, -12000, -12000 }; + +/***************************************/ +/* Get the speech activity level in Q8 */ +/***************************************/ +opus_int silk_VAD_GetSA_Q8_sse4_1( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +) +{ + opus_int SA_Q15, pSNR_dB_Q7, input_tilt; + opus_int decimated_framelength1, decimated_framelength2; + opus_int decimated_framelength; + opus_int dec_subframe_length, dec_subframe_offset, SNR_Q7, i, b, s; + opus_int32 sumSquared, smooth_coef_Q16; + opus_int16 HPstateTmp; + VARDECL( opus_int16, X ); + opus_int32 Xnrg[ VAD_N_BANDS ]; + opus_int32 NrgToNoiseRatio_Q8[ VAD_N_BANDS ]; + opus_int32 speech_nrg, x_tmp; + opus_int X_offset[ VAD_N_BANDS ]; + opus_int ret = 0; + silk_VAD_state *psSilk_VAD = &psEncC->sVAD; + + SAVE_STACK; + +#ifdef OPUS_CHECK_ASM + silk_encoder_state psEncC_c; + opus_int ret_c; + + silk_memcpy( &psEncC_c, psEncC, sizeof( psEncC_c ) ); + ret_c = silk_VAD_GetSA_Q8_c( &psEncC_c, pIn ); +#endif + + /* Safety checks */ + silk_assert( VAD_N_BANDS == 4 ); + celt_assert( MAX_FRAME_LENGTH >= psEncC->frame_length ); + celt_assert( psEncC->frame_length <= 512 ); + celt_assert( psEncC->frame_length == 8 * silk_RSHIFT( psEncC->frame_length, 3 ) ); + + /***********************/ + /* Filter and Decimate */ + /***********************/ + decimated_framelength1 = silk_RSHIFT( psEncC->frame_length, 1 ); + decimated_framelength2 = silk_RSHIFT( psEncC->frame_length, 2 ); + decimated_framelength = silk_RSHIFT( psEncC->frame_length, 3 ); + /* Decimate into 4 bands: + 0 L 3L L 3L 5L + - -- - -- -- + 8 8 2 4 4 + + [0-1 kHz| temp. |1-2 kHz| 2-4 kHz | 4-8 kHz | + + They're arranged to allow the minimal ( frame_length / 4 ) extra + scratch space during the downsampling process */ + X_offset[ 0 ] = 0; + X_offset[ 1 ] = decimated_framelength + decimated_framelength2; + X_offset[ 2 ] = X_offset[ 1 ] + decimated_framelength; + X_offset[ 3 ] = X_offset[ 2 ] + decimated_framelength2; + ALLOC( X, X_offset[ 3 ] + decimated_framelength1, opus_int16 ); + + /* 0-8 kHz to 0-4 kHz and 4-8 kHz */ + silk_ana_filt_bank_1( pIn, &psSilk_VAD->AnaState[ 0 ], + X, &X[ X_offset[ 3 ] ], psEncC->frame_length ); + + /* 0-4 kHz to 0-2 kHz and 2-4 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState1[ 0 ], + X, &X[ X_offset[ 2 ] ], decimated_framelength1 ); + + /* 0-2 kHz to 0-1 kHz and 1-2 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState2[ 0 ], + X, &X[ X_offset[ 1 ] ], decimated_framelength2 ); + + /*********************************************/ + /* HP filter on lowest band (differentiator) */ + /*********************************************/ + X[ decimated_framelength - 1 ] = silk_RSHIFT( X[ decimated_framelength - 1 ], 1 ); + HPstateTmp = X[ decimated_framelength - 1 ]; + for( i = decimated_framelength - 1; i > 0; i-- ) { + X[ i - 1 ] = silk_RSHIFT( X[ i - 1 ], 1 ); + X[ i ] -= X[ i - 1 ]; + } + X[ 0 ] -= psSilk_VAD->HPstate; + psSilk_VAD->HPstate = HPstateTmp; + + /*************************************/ + /* Calculate the energy in each band */ + /*************************************/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Find the decimated framelength in the non-uniformly divided bands */ + decimated_framelength = silk_RSHIFT( psEncC->frame_length, silk_min_int( VAD_N_BANDS - b, VAD_N_BANDS - 1 ) ); + + /* Split length into subframe lengths */ + dec_subframe_length = silk_RSHIFT( decimated_framelength, VAD_INTERNAL_SUBFRAMES_LOG2 ); + dec_subframe_offset = 0; + + /* Compute energy per sub-frame */ + /* initialize with summed energy of last subframe */ + Xnrg[ b ] = psSilk_VAD->XnrgSubfr[ b ]; + for( s = 0; s < VAD_INTERNAL_SUBFRAMES; s++ ) { + __m128i xmm_X, xmm_acc; + sumSquared = 0; + + xmm_acc = _mm_setzero_si128(); + + for( i = 0; i < dec_subframe_length - 7; i += 8 ) + { + xmm_X = _mm_loadu_si128( (__m128i *)(void*)&(X[ X_offset[ b ] + i + dec_subframe_offset ] ) ); + xmm_X = _mm_srai_epi16( xmm_X, 3 ); + xmm_X = _mm_madd_epi16( xmm_X, xmm_X ); + xmm_acc = _mm_add_epi32( xmm_acc, xmm_X ); + } + + xmm_acc = _mm_add_epi32( xmm_acc, _mm_unpackhi_epi64( xmm_acc, xmm_acc ) ); + xmm_acc = _mm_add_epi32( xmm_acc, _mm_shufflelo_epi16( xmm_acc, 0x0E ) ); + + sumSquared += _mm_cvtsi128_si32( xmm_acc ); + + for( ; i < dec_subframe_length; i++ ) { + /* The energy will be less than dec_subframe_length * ( silk_int16_MIN / 8 ) ^ 2. */ + /* Therefore we can accumulate with no risk of overflow (unless dec_subframe_length > 128) */ + x_tmp = silk_RSHIFT( + X[ X_offset[ b ] + i + dec_subframe_offset ], 3 ); + sumSquared = silk_SMLABB( sumSquared, x_tmp, x_tmp ); + + /* Safety check */ + silk_assert( sumSquared >= 0 ); + } + + /* Add/saturate summed energy of current subframe */ + if( s < VAD_INTERNAL_SUBFRAMES - 1 ) { + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], sumSquared ); + } else { + /* Look-ahead subframe */ + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], silk_RSHIFT( sumSquared, 1 ) ); + } + + dec_subframe_offset += dec_subframe_length; + } + psSilk_VAD->XnrgSubfr[ b ] = sumSquared; + } + + /********************/ + /* Noise estimation */ + /********************/ + silk_VAD_GetNoiseLevels( &Xnrg[ 0 ], psSilk_VAD ); + + /***********************************************/ + /* Signal-plus-noise to noise ratio estimation */ + /***********************************************/ + sumSquared = 0; + input_tilt = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + speech_nrg = Xnrg[ b ] - psSilk_VAD->NL[ b ]; + if( speech_nrg > 0 ) { + /* Divide, with sufficient resolution */ + if( ( Xnrg[ b ] & 0xFF800000 ) == 0 ) { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( silk_LSHIFT( Xnrg[ b ], 8 ), psSilk_VAD->NL[ b ] + 1 ); + } else { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( Xnrg[ b ], silk_RSHIFT( psSilk_VAD->NL[ b ], 8 ) + 1 ); + } + + /* Convert to log domain */ + SNR_Q7 = silk_lin2log( NrgToNoiseRatio_Q8[ b ] ) - 8 * 128; + + /* Sum-of-squares */ + sumSquared = silk_SMLABB( sumSquared, SNR_Q7, SNR_Q7 ); /* Q14 */ + + /* Tilt measure */ + if( speech_nrg < ( (opus_int32)1 << 20 ) ) { + /* Scale down SNR value for small subband speech energies */ + SNR_Q7 = silk_SMULWB( silk_LSHIFT( silk_SQRT_APPROX( speech_nrg ), 6 ), SNR_Q7 ); + } + input_tilt = silk_SMLAWB( input_tilt, tiltWeights[ b ], SNR_Q7 ); + } else { + NrgToNoiseRatio_Q8[ b ] = 256; + } + } + + /* Mean-of-squares */ + sumSquared = silk_DIV32_16( sumSquared, VAD_N_BANDS ); /* Q14 */ + + /* Root-mean-square approximation, scale to dBs, and write to output pointer */ + pSNR_dB_Q7 = (opus_int16)( 3 * silk_SQRT_APPROX( sumSquared ) ); /* Q7 */ + + /*********************************/ + /* Speech Probability Estimation */ + /*********************************/ + SA_Q15 = silk_sigm_Q15( silk_SMULWB( VAD_SNR_FACTOR_Q16, pSNR_dB_Q7 ) - VAD_NEGATIVE_OFFSET_Q5 ); + + /**************************/ + /* Frequency Tilt Measure */ + /**************************/ + psEncC->input_tilt_Q15 = silk_LSHIFT( silk_sigm_Q15( input_tilt ) - 16384, 1 ); + + /**************************************************/ + /* Scale the sigmoid output based on power levels */ + /**************************************************/ + speech_nrg = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Accumulate signal-without-noise energies, higher frequency bands have more weight */ + speech_nrg += ( b + 1 ) * silk_RSHIFT( Xnrg[ b ] - psSilk_VAD->NL[ b ], 4 ); + } + + if( psEncC->frame_length == 20 * psEncC->fs_kHz ) { + speech_nrg = silk_RSHIFT32( speech_nrg, 1 ); + } + /* Power scaling */ + if( speech_nrg <= 0 ) { + SA_Q15 = silk_RSHIFT( SA_Q15, 1 ); + } else if( speech_nrg < 16384 ) { + speech_nrg = silk_LSHIFT32( speech_nrg, 16 ); + + /* square-root */ + speech_nrg = silk_SQRT_APPROX( speech_nrg ); + SA_Q15 = silk_SMULWB( 32768 + speech_nrg, SA_Q15 ); + } + + /* Copy the resulting speech activity in Q8 */ + psEncC->speech_activity_Q8 = silk_min_int( silk_RSHIFT( SA_Q15, 7 ), silk_uint8_MAX ); + + /***********************************/ + /* Energy Level and SNR estimation */ + /***********************************/ + /* Smoothing coefficient */ + smooth_coef_Q16 = silk_SMULWB( VAD_SNR_SMOOTH_COEF_Q18, silk_SMULWB( (opus_int32)SA_Q15, SA_Q15 ) ); + + if( psEncC->frame_length == 10 * psEncC->fs_kHz ) { + smooth_coef_Q16 >>= 1; + } + + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* compute smoothed energy-to-noise ratio per band */ + psSilk_VAD->NrgRatioSmth_Q8[ b ] = silk_SMLAWB( psSilk_VAD->NrgRatioSmth_Q8[ b ], + NrgToNoiseRatio_Q8[ b ] - psSilk_VAD->NrgRatioSmth_Q8[ b ], smooth_coef_Q16 ); + + /* signal to noise ratio in dB per band */ + SNR_Q7 = 3 * ( silk_lin2log( psSilk_VAD->NrgRatioSmth_Q8[b] ) - 8 * 128 ); + /* quality = sigmoid( 0.25 * ( SNR_dB - 16 ) ); */ + psEncC->input_quality_bands_Q15[ b ] = silk_sigm_Q15( silk_RSHIFT( SNR_Q7 - 16 * 128, 4 ) ); + } + +#ifdef OPUS_CHECK_ASM + silk_assert( ret == ret_c ); + silk_assert( !memcmp( &psEncC_c, psEncC, sizeof( psEncC_c ) ) ); +#endif + + RESTORE_STACK; + return( ret ); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VQ_WMat_EC_sse4_1.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VQ_WMat_EC_sse4_1.c new file mode 100644 index 0000000000..df4626b60a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/VQ_WMat_EC_sse4_1.c @@ -0,0 +1,173 @@ +/* Copyright (c) 2014-2020, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang FrancisQuiers + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" + +/* Entropy constrained matrix-weighted VQ, hard-coded to 5-element vectors, for a single input data vector */ +void silk_VQ_WMat_EC_sse4_1( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +) +{ + opus_int k, gain_tmp_Q7; + const opus_int8 *cb_row_Q7; + opus_int32 neg_xX_Q24[ 5 ]; + opus_int32 sum1_Q15, sum2_Q24; + opus_int32 bits_res_Q8, bits_tot_Q8; + __m128i v_XX_31_Q17, v_XX_42_Q17, v_cb_row_31_Q7, v_cb_row_42_Q7, v_acc1_Q24, v_acc2_Q24; + + /* Negate and convert to new Q domain */ + neg_xX_Q24[ 0 ] = -silk_LSHIFT32( xX_Q17[ 0 ], 7 ); + neg_xX_Q24[ 1 ] = -silk_LSHIFT32( xX_Q17[ 1 ], 7 ); + neg_xX_Q24[ 2 ] = -silk_LSHIFT32( xX_Q17[ 2 ], 7 ); + neg_xX_Q24[ 3 ] = -silk_LSHIFT32( xX_Q17[ 3 ], 7 ); + neg_xX_Q24[ 4 ] = -silk_LSHIFT32( xX_Q17[ 4 ], 7 ); + + v_XX_31_Q17 = _mm_loadu_si128( (__m128i *)(void*)(&XX_Q17[ 1 ] ) ); + v_XX_42_Q17 = _mm_shuffle_epi32( v_XX_31_Q17, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + /* Loop over codebook */ + *rate_dist_Q8 = silk_int32_MAX; + *res_nrg_Q15 = silk_int32_MAX; + cb_row_Q7 = cb_Q7; + /* If things go really bad, at least *ind is set to something safe. */ + *ind = 0; + for( k = 0; k < L; k++ ) { + opus_int32 penalty; + gain_tmp_Q7 = cb_gain_Q7[k]; + /* Weighted rate */ + /* Quantization error: 1 - 2 * xX * cb + cb' * XX * cb */ + sum1_Q15 = SILK_FIX_CONST( 1.001, 15 ); + + /* Penalty for too large gain */ + penalty = silk_LSHIFT32( silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 11 ); + + /* first row of XX_Q17 */ + v_cb_row_31_Q7 = OP_CVTEPI8_EPI32_M32( &cb_row_Q7[ 1 ] ); + v_cb_row_42_Q7 = _mm_shuffle_epi32( v_cb_row_31_Q7, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + v_cb_row_31_Q7 = _mm_mul_epi32( v_XX_31_Q17, v_cb_row_31_Q7 ); + v_cb_row_42_Q7 = _mm_mul_epi32( v_XX_42_Q17, v_cb_row_42_Q7 ); + v_acc1_Q24 = _mm_add_epi64( v_cb_row_31_Q7, v_cb_row_42_Q7); + v_acc2_Q24 = _mm_shuffle_epi32( v_acc1_Q24, _MM_SHUFFLE( 1, 0, 3, 2 ) ); + v_acc1_Q24 = _mm_add_epi64( v_acc1_Q24, v_acc2_Q24); + sum2_Q24 = _mm_cvtsi128_si32( v_acc1_Q24 ); + sum2_Q24 = silk_ADD32( neg_xX_Q24[ 0 ], sum2_Q24 ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 0 ], cb_row_Q7[ 0 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 0 ] ); + + /* second row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 1 ], XX_Q17[ 7 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 8 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 9 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 6 ], cb_row_Q7[ 1 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 1 ] ); + + /* third row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 2 ], XX_Q17[ 13 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 14 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 12 ], cb_row_Q7[ 2 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 2 ] ); + + /* fourth row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 3 ], XX_Q17[ 19 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 18 ], cb_row_Q7[ 3 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 3 ] ); + + /* last row of XX_Q17 */ + sum2_Q24 = silk_LSHIFT32( neg_xX_Q24[ 4 ], 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 24 ], cb_row_Q7[ 4 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 4 ] ); + + /* find best */ + if( sum1_Q15 >= 0 ) { + /* Translate residual energy to bits using high-rate assumption (6 dB ==> 1 bit/sample) */ + bits_res_Q8 = silk_SMULBB( subfr_len, silk_lin2log( sum1_Q15 + penalty) - (15 << 7) ); + /* In the following line we reduce the codelength component by half ("-1"); seems to slightly improve quality */ + bits_tot_Q8 = silk_ADD_LSHIFT32( bits_res_Q8, cl_Q5[ k ], 3-1 ); + if( bits_tot_Q8 <= *rate_dist_Q8 ) { + *rate_dist_Q8 = bits_tot_Q8; + *res_nrg_Q15 = sum1_Q15 + penalty; + *ind = (opus_int8)k; + *gain_Q7 = gain_tmp_Q7; + } + } + + /* Go to next cbk vector */ + cb_row_Q7 += LTP_ORDER; + } + +#ifdef OPUS_CHECK_ASM + { + opus_int8 ind_c = 0; + opus_int32 res_nrg_Q15_c = 0; + opus_int32 rate_dist_Q8_c = 0; + opus_int gain_Q7_c = 0; + + silk_VQ_WMat_EC_c( + &ind_c, + &res_nrg_Q15_c, + &rate_dist_Q8_c, + &gain_Q7_c, + XX_Q17, + xX_Q17, + cb_Q7, + cb_gain_Q7, + cl_Q5, + subfr_len, + max_gain_Q7, + L + ); + + silk_assert( *ind == ind_c ); + silk_assert( *res_nrg_Q15 == res_nrg_Q15_c ); + silk_assert( *rate_dist_Q8 == rate_dist_Q8_c ); + silk_assert( *gain_Q7 == gain_Q7_c ); + } +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/main_sse.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/main_sse.h new file mode 100644 index 0000000000..b254d53e7a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/main_sse.h @@ -0,0 +1,299 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MAIN_SSE_H +# define MAIN_SSE_H + +# ifdef HAVE_CONFIG_H +# include "config.h" +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE4_1) + +void silk_VQ_WMat_EC_sse4_1( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +); + +# if defined OPUS_X86_PRESUME_SSE4_1 + +# define OVERRIDE_silk_VQ_WMat_EC +# define silk_VQ_WMat_EC(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, \ + subfr_len, max_gain_Q7, L, arch) \ + ((void)(arch),silk_VQ_WMat_EC_sse4_1(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, \ + subfr_len, max_gain_Q7, L)) + +# elif defined(OPUS_HAVE_RTCD) + +extern void (*const SILK_VQ_WMAT_EC_IMPL[OPUS_ARCHMASK + 1])( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +); + +# define OVERRIDE_silk_VQ_WMat_EC +# define silk_VQ_WMat_EC(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, \ + subfr_len, max_gain_Q7, L, arch) \ + ((*SILK_VQ_WMAT_EC_IMPL[(arch) & OPUS_ARCHMASK])(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, \ + subfr_len, max_gain_Q7, L)) + +# endif + +void silk_NSQ_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# if defined OPUS_X86_PRESUME_SSE4_1 + +# define OVERRIDE_silk_NSQ +# define silk_NSQ(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_sse4_1(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +# elif defined(OPUS_HAVE_RTCD) + +extern void (*const SILK_NSQ_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# define OVERRIDE_silk_NSQ +# define silk_NSQ(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((*SILK_NSQ_IMPL[(arch) & OPUS_ARCHMASK])(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +# endif + +void silk_NSQ_del_dec_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +void silk_NSQ_del_dec_avx2( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[LTP_ORDER * MAX_NB_SUBFR], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[MAX_NB_SUBFR], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[MAX_NB_SUBFR], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[MAX_NB_SUBFR], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[MAX_NB_SUBFR], /* I Quantization step sizes */ + const opus_int32 pitchL[MAX_NB_SUBFR], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# if defined (OPUS_X86_PRESUME_AVX2) + +# define OVERRIDE_silk_NSQ_del_dec +# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_avx2(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +# elif defined (OPUS_X86_PRESUME_SSE4_1) && !defined(OPUS_X86_MAY_HAVE_AVX2) + +# define OVERRIDE_silk_NSQ_del_dec +# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_sse4_1(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +# elif defined(OPUS_HAVE_RTCD) + +extern void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# define OVERRIDE_silk_NSQ_del_dec +# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((*SILK_NSQ_DEL_DEC_IMPL[(arch) & OPUS_ARCHMASK])(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +# endif + +void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +); + +/**************************/ +/* Noise level estimation */ +/**************************/ +void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); + +opus_int silk_VAD_GetSA_Q8_sse4_1( + silk_encoder_state *psEnC, + const opus_int16 pIn[] +); + +# if defined(OPUS_X86_PRESUME_SSE4_1) + +# define OVERRIDE_silk_VAD_GetSA_Q8 +# define silk_VAD_GetSA_Q8(psEnC, pIn, arch) ((void)(arch),silk_VAD_GetSA_Q8_sse4_1(psEnC, pIn)) + +# elif defined(OPUS_HAVE_RTCD) + +extern opus_int (*const SILK_VAD_GETSA_Q8_IMPL[OPUS_ARCHMASK + 1])( + silk_encoder_state *psEnC, + const opus_int16 pIn[]); + +# define OVERRIDE_silk_VAD_GetSA_Q8 +# define silk_VAD_GetSA_Q8(psEnC, pIn, arch) \ + ((*SILK_VAD_GETSA_Q8_IMPL[(arch) & OPUS_ARCHMASK])(psEnC, pIn)) + +# endif + +#ifndef FIXED_POINT +double silk_inner_product_FLP_avx2( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +); + +#if defined (OPUS_X86_PRESUME_AVX2) + +#define OVERRIDE_inner_product_FLP +#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,silk_inner_product_FLP_avx2(data1, data2, dataSize)) + +#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_X86_MAY_HAVE_AVX2) + +#define OVERRIDE_inner_product_FLP +extern double (*const SILK_INNER_PRODUCT_FLP_IMPL[OPUS_ARCHMASK + 1])( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +); + +#define silk_inner_product_FLP(data1, data2, dataSize, arch) ((void)arch,(*SILK_INNER_PRODUCT_FLP_IMPL[(arch) & OPUS_ARCHMASK])(data1, data2, dataSize)) + +#endif +#endif + +# endif +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/x86_silk_map.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/x86_silk_map.c new file mode 100644 index 0000000000..39ad75276c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/silk/x86/x86_silk_map.c @@ -0,0 +1,179 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include "celt/x86/x86cpu.h" +#include "structs.h" +#include "SigProc_FIX.h" +#ifndef FIXED_POINT +#include "SigProc_FLP.h" +#endif +#include "pitch.h" +#include "main.h" + +#if defined(OPUS_HAVE_RTCD) && !defined(OPUS_X86_PRESUME_AVX2) + +#if defined(FIXED_POINT) + +#include "fixed/main_FIX.h" + +opus_int64 (*const SILK_INNER_PROD16_IMPL[ OPUS_ARCHMASK + 1 ] )( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len +) = { + silk_inner_prod16_c, /* non-sse */ + silk_inner_prod16_c, + silk_inner_prod16_c, + MAY_HAVE_SSE4_1( silk_inner_prod16 ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_inner_prod16 ) /* avx */ +}; + +#endif + +opus_int (*const SILK_VAD_GETSA_Q8_IMPL[ OPUS_ARCHMASK + 1 ] )( + silk_encoder_state *psEncC, + const opus_int16 pIn[] +) = { + silk_VAD_GetSA_Q8_c, /* non-sse */ + silk_VAD_GetSA_Q8_c, + silk_VAD_GetSA_Q8_c, + MAY_HAVE_SSE4_1( silk_VAD_GetSA_Q8 ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_VAD_GetSA_Q8 ) /* avx */ +}; + +void (*const SILK_NSQ_IMPL[ OPUS_ARCHMASK + 1 ] )( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) = { + silk_NSQ_c, /* non-sse */ + silk_NSQ_c, + silk_NSQ_c, + MAY_HAVE_SSE4_1( silk_NSQ ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_NSQ ) /* avx */ +}; + +void (*const SILK_VQ_WMAT_EC_IMPL[ OPUS_ARCHMASK + 1 ] )( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int subfr_len, /* I number of samples per subframe */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + const opus_int L /* I number of vectors in codebook */ +) = { + silk_VQ_WMat_EC_c, /* non-sse */ + silk_VQ_WMat_EC_c, + silk_VQ_WMat_EC_c, + MAY_HAVE_SSE4_1( silk_VQ_WMat_EC ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_VQ_WMat_EC ) /* avx */ +}; + +void (*const SILK_NSQ_DEL_DEC_IMPL[ OPUS_ARCHMASK + 1 ] )( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int16 x16[], /* I Input */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 *PredCoef_Q12, /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) = { + silk_NSQ_del_dec_c, /* non-sse */ + silk_NSQ_del_dec_c, + silk_NSQ_del_dec_c, + MAY_HAVE_SSE4_1( silk_NSQ_del_dec ), /* sse4.1 */ + MAY_HAVE_AVX2( silk_NSQ_del_dec ) /* avx */ +}; + +#if defined(FIXED_POINT) + +void (*const SILK_BURG_MODIFIED_IMPL[ OPUS_ARCHMASK + 1 ] )( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +) = { + silk_burg_modified_c, /* non-sse */ + silk_burg_modified_c, + silk_burg_modified_c, + MAY_HAVE_SSE4_1( silk_burg_modified ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_burg_modified ) /* avx */ +}; + +#endif + +#ifndef FIXED_POINT + +double (*const SILK_INNER_PRODUCT_FLP_IMPL[ OPUS_ARCHMASK + 1 ] )( + const silk_float *data1, + const silk_float *data2, + opus_int dataSize +) = { + silk_inner_product_FLP_c, /* non-sse */ + silk_inner_product_FLP_c, + silk_inner_product_FLP_c, + silk_inner_product_FLP_c, /* sse4.1 */ + MAY_HAVE_AVX2( silk_inner_product_FLP ) /* avx */ +}; + +#endif + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.c new file mode 100644 index 0000000000..1f58013812 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.c @@ -0,0 +1,983 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define ANALYSIS_C + +#ifdef MLP_TRAINING +#include +#endif + +#include "mathops.h" +#include "kiss_fft.h" +#include "celt.h" +#include "modes.h" +#include "arch.h" +#include "quant_bands.h" +#include "analysis.h" +#include "mlp.h" +#include "stack_alloc.h" +#include "float_cast.h" + +#ifndef M_PI +#define M_PI 3.141592653 +#endif + +#ifndef DISABLE_FLOAT_API + +#define TRANSITION_PENALTY 10 + +static const float dct_table[128] = { + 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, + 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, 0.250000f, + 0.351851f, 0.338330f, 0.311806f, 0.273300f, 0.224292f, 0.166664f, 0.102631f, 0.034654f, + -0.034654f,-0.102631f,-0.166664f,-0.224292f,-0.273300f,-0.311806f,-0.338330f,-0.351851f, + 0.346760f, 0.293969f, 0.196424f, 0.068975f,-0.068975f,-0.196424f,-0.293969f,-0.346760f, + -0.346760f,-0.293969f,-0.196424f,-0.068975f, 0.068975f, 0.196424f, 0.293969f, 0.346760f, + 0.338330f, 0.224292f, 0.034654f,-0.166664f,-0.311806f,-0.351851f,-0.273300f,-0.102631f, + 0.102631f, 0.273300f, 0.351851f, 0.311806f, 0.166664f,-0.034654f,-0.224292f,-0.338330f, + 0.326641f, 0.135299f,-0.135299f,-0.326641f,-0.326641f,-0.135299f, 0.135299f, 0.326641f, + 0.326641f, 0.135299f,-0.135299f,-0.326641f,-0.326641f,-0.135299f, 0.135299f, 0.326641f, + 0.311806f, 0.034654f,-0.273300f,-0.338330f,-0.102631f, 0.224292f, 0.351851f, 0.166664f, + -0.166664f,-0.351851f,-0.224292f, 0.102631f, 0.338330f, 0.273300f,-0.034654f,-0.311806f, + 0.293969f,-0.068975f,-0.346760f,-0.196424f, 0.196424f, 0.346760f, 0.068975f,-0.293969f, + -0.293969f, 0.068975f, 0.346760f, 0.196424f,-0.196424f,-0.346760f,-0.068975f, 0.293969f, + 0.273300f,-0.166664f,-0.338330f, 0.034654f, 0.351851f, 0.102631f,-0.311806f,-0.224292f, + 0.224292f, 0.311806f,-0.102631f,-0.351851f,-0.034654f, 0.338330f, 0.166664f,-0.273300f, +}; + +static const float analysis_window[240] = { + 0.000043f, 0.000171f, 0.000385f, 0.000685f, 0.001071f, 0.001541f, 0.002098f, 0.002739f, + 0.003466f, 0.004278f, 0.005174f, 0.006156f, 0.007222f, 0.008373f, 0.009607f, 0.010926f, + 0.012329f, 0.013815f, 0.015385f, 0.017037f, 0.018772f, 0.020590f, 0.022490f, 0.024472f, + 0.026535f, 0.028679f, 0.030904f, 0.033210f, 0.035595f, 0.038060f, 0.040604f, 0.043227f, + 0.045928f, 0.048707f, 0.051564f, 0.054497f, 0.057506f, 0.060591f, 0.063752f, 0.066987f, + 0.070297f, 0.073680f, 0.077136f, 0.080665f, 0.084265f, 0.087937f, 0.091679f, 0.095492f, + 0.099373f, 0.103323f, 0.107342f, 0.111427f, 0.115579f, 0.119797f, 0.124080f, 0.128428f, + 0.132839f, 0.137313f, 0.141849f, 0.146447f, 0.151105f, 0.155823f, 0.160600f, 0.165435f, + 0.170327f, 0.175276f, 0.180280f, 0.185340f, 0.190453f, 0.195619f, 0.200838f, 0.206107f, + 0.211427f, 0.216797f, 0.222215f, 0.227680f, 0.233193f, 0.238751f, 0.244353f, 0.250000f, + 0.255689f, 0.261421f, 0.267193f, 0.273005f, 0.278856f, 0.284744f, 0.290670f, 0.296632f, + 0.302628f, 0.308658f, 0.314721f, 0.320816f, 0.326941f, 0.333097f, 0.339280f, 0.345492f, + 0.351729f, 0.357992f, 0.364280f, 0.370590f, 0.376923f, 0.383277f, 0.389651f, 0.396044f, + 0.402455f, 0.408882f, 0.415325f, 0.421783f, 0.428254f, 0.434737f, 0.441231f, 0.447736f, + 0.454249f, 0.460770f, 0.467298f, 0.473832f, 0.480370f, 0.486912f, 0.493455f, 0.500000f, + 0.506545f, 0.513088f, 0.519630f, 0.526168f, 0.532702f, 0.539230f, 0.545751f, 0.552264f, + 0.558769f, 0.565263f, 0.571746f, 0.578217f, 0.584675f, 0.591118f, 0.597545f, 0.603956f, + 0.610349f, 0.616723f, 0.623077f, 0.629410f, 0.635720f, 0.642008f, 0.648271f, 0.654508f, + 0.660720f, 0.666903f, 0.673059f, 0.679184f, 0.685279f, 0.691342f, 0.697372f, 0.703368f, + 0.709330f, 0.715256f, 0.721144f, 0.726995f, 0.732807f, 0.738579f, 0.744311f, 0.750000f, + 0.755647f, 0.761249f, 0.766807f, 0.772320f, 0.777785f, 0.783203f, 0.788573f, 0.793893f, + 0.799162f, 0.804381f, 0.809547f, 0.814660f, 0.819720f, 0.824724f, 0.829673f, 0.834565f, + 0.839400f, 0.844177f, 0.848895f, 0.853553f, 0.858151f, 0.862687f, 0.867161f, 0.871572f, + 0.875920f, 0.880203f, 0.884421f, 0.888573f, 0.892658f, 0.896677f, 0.900627f, 0.904508f, + 0.908321f, 0.912063f, 0.915735f, 0.919335f, 0.922864f, 0.926320f, 0.929703f, 0.933013f, + 0.936248f, 0.939409f, 0.942494f, 0.945503f, 0.948436f, 0.951293f, 0.954072f, 0.956773f, + 0.959396f, 0.961940f, 0.964405f, 0.966790f, 0.969096f, 0.971321f, 0.973465f, 0.975528f, + 0.977510f, 0.979410f, 0.981228f, 0.982963f, 0.984615f, 0.986185f, 0.987671f, 0.989074f, + 0.990393f, 0.991627f, 0.992778f, 0.993844f, 0.994826f, 0.995722f, 0.996534f, 0.997261f, + 0.997902f, 0.998459f, 0.998929f, 0.999315f, 0.999615f, 0.999829f, 0.999957f, 1.000000f, +}; + +static const int tbands[NB_TBANDS+1] = { + 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 136, 160, 192, 240 +}; + +#define NB_TONAL_SKIP_BANDS 9 + +static opus_val32 silk_resampler_down2_hp( + opus_val32 *S, /* I/O State vector [ 2 ] */ + opus_val32 *out, /* O Output signal [ floor(len/2) ] */ + const opus_val32 *in, /* I Input signal [ len ] */ + int inLen /* I Number of input samples */ +) +{ + int k, len2 = inLen/2; + opus_val32 in32, out32, out32_hp, Y, X; + opus_val64 hp_ener = 0; + /* Internal variables and state are in Q10 format */ + for( k = 0; k < len2; k++ ) { + /* Convert to Q10 */ + in32 = in[ 2 * k ]; + + /* All-pass section for even input sample */ + Y = SUB32( in32, S[ 0 ] ); + X = MULT16_32_Q15(QCONST16(0.6074371f, 15), Y); + out32 = ADD32( S[ 0 ], X ); + S[ 0 ] = ADD32( in32, X ); + out32_hp = out32; + /* Convert to Q10 */ + in32 = in[ 2 * k + 1 ]; + + /* All-pass section for odd input sample, and add to output of previous section */ + Y = SUB32( in32, S[ 1 ] ); + X = MULT16_32_Q15(QCONST16(0.15063f, 15), Y); + out32 = ADD32( out32, S[ 1 ] ); + out32 = ADD32( out32, X ); + S[ 1 ] = ADD32( in32, X ); + + Y = SUB32( -in32, S[ 2 ] ); + X = MULT16_32_Q15(QCONST16(0.15063f, 15), Y); + out32_hp = ADD32( out32_hp, S[ 2 ] ); + out32_hp = ADD32( out32_hp, X ); + S[ 2 ] = ADD32( -in32, X ); + + hp_ener += out32_hp*(opus_val64)out32_hp; + /* Add, convert back to int16 and store to output */ + out[ k ] = HALF32(out32); + } +#ifdef FIXED_POINT + /* len2 can be up to 480, so we shift by 8 more to make it fit. */ + hp_ener = hp_ener >> (2*SIG_SHIFT + 8); +#endif + return (opus_val32)hp_ener; +} + +static opus_val32 downmix_and_resample(downmix_func downmix, const void *_x, opus_val32 *y, opus_val32 S[3], int subframe, int offset, int c1, int c2, int C, int Fs) +{ + VARDECL(opus_val32, tmp); + opus_val32 scale; + int j; + opus_val32 ret = 0; + SAVE_STACK; + + if (subframe==0) return 0; + if (Fs == 48000) + { + subframe *= 2; + offset *= 2; + } else if (Fs == 16000) { + subframe = subframe*2/3; + offset = offset*2/3; + } + ALLOC(tmp, subframe, opus_val32); + + downmix(_x, tmp, subframe, offset, c1, c2, C); +#ifdef FIXED_POINT + scale = (1<-1) + scale /= 2; + for (j=0;jarch = opus_select_arch(); + tonal->Fs = Fs; + /* Clear remaining fields. */ + tonality_analysis_reset(tonal); +} + +void tonality_analysis_reset(TonalityAnalysisState *tonal) +{ + /* Clear non-reusable fields. */ + char *start = (char*)&tonal->TONALITY_ANALYSIS_RESET_START; + OPUS_CLEAR(start, sizeof(TonalityAnalysisState) - (start - (char*)tonal)); +} + +void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len) +{ + int pos; + int curr_lookahead; + float tonality_max; + float tonality_avg; + int tonality_count; + int i; + int pos0; + float prob_avg; + float prob_count; + float prob_min, prob_max; + float vad_prob; + int mpos, vpos; + int bandwidth_span; + + pos = tonal->read_pos; + curr_lookahead = tonal->write_pos-tonal->read_pos; + if (curr_lookahead<0) + curr_lookahead += DETECT_SIZE; + + tonal->read_subframe += len/(tonal->Fs/400); + while (tonal->read_subframe>=8) + { + tonal->read_subframe -= 8; + tonal->read_pos++; + } + if (tonal->read_pos>=DETECT_SIZE) + tonal->read_pos-=DETECT_SIZE; + + /* On long frames, look at the second analysis window rather than the first. */ + if (len > tonal->Fs/50 && pos != tonal->write_pos) + { + pos++; + if (pos==DETECT_SIZE) + pos=0; + } + if (pos == tonal->write_pos) + pos--; + if (pos<0) + pos = DETECT_SIZE-1; + pos0 = pos; + OPUS_COPY(info_out, &tonal->info[pos], 1); + if (!info_out->valid) + return; + tonality_max = tonality_avg = info_out->tonality; + tonality_count = 1; + /* Look at the neighbouring frames and pick largest bandwidth found (to be safe). */ + bandwidth_span = 6; + /* If possible, look ahead for a tone to compensate for the delay in the tone detector. */ + for (i=0;i<3;i++) + { + pos++; + if (pos==DETECT_SIZE) + pos = 0; + if (pos == tonal->write_pos) + break; + tonality_max = MAX32(tonality_max, tonal->info[pos].tonality); + tonality_avg += tonal->info[pos].tonality; + tonality_count++; + info_out->bandwidth = IMAX(info_out->bandwidth, tonal->info[pos].bandwidth); + bandwidth_span--; + } + pos = pos0; + /* Look back in time to see if any has a wider bandwidth than the current frame. */ + for (i=0;iwrite_pos) + break; + info_out->bandwidth = IMAX(info_out->bandwidth, tonal->info[pos].bandwidth); + } + info_out->tonality = MAX32(tonality_avg/tonality_count, tonality_max-.2f); + + mpos = vpos = pos0; + /* If we have enough look-ahead, compensate for the ~5-frame delay in the music prob and + ~1 frame delay in the VAD prob. */ + if (curr_lookahead > 15) + { + mpos += 5; + if (mpos>=DETECT_SIZE) + mpos -= DETECT_SIZE; + vpos += 1; + if (vpos>=DETECT_SIZE) + vpos -= DETECT_SIZE; + } + + /* The following calculations attempt to minimize a "badness function" + for the transition. When switching from speech to music, the badness + of switching at frame k is + b_k = S*v_k + \sum_{i=0}^{k-1} v_i*(p_i - T) + where + v_i is the activity probability (VAD) at frame i, + p_i is the music probability at frame i + T is the probability threshold for switching + S is the penalty for switching during active audio rather than silence + the current frame has index i=0 + + Rather than apply badness to directly decide when to switch, what we compute + instead is the threshold for which the optimal switching point is now. When + considering whether to switch now (frame 0) or at frame k, we have: + S*v_0 = S*v_k + \sum_{i=0}^{k-1} v_i*(p_i - T) + which gives us: + T = ( \sum_{i=0}^{k-1} v_i*p_i + S*(v_k-v_0) ) / ( \sum_{i=0}^{k-1} v_i ) + We take the min threshold across all positive values of k (up to the maximum + amount of lookahead we have) to give us the threshold for which the current + frame is the optimal switch point. + + The last step is that we need to consider whether we want to switch at all. + For that we use the average of the music probability over the entire window. + If the threshold is higher than that average we're not going to + switch, so we compute a min with the average as well. The result of all these + min operations is music_prob_min, which gives the threshold for switching to music + if we're currently encoding for speech. + + We do the exact opposite to compute music_prob_max which is used for switching + from music to speech. + */ + prob_min = 1.f; + prob_max = 0.f; + vad_prob = tonal->info[vpos].activity_probability; + prob_count = MAX16(.1f, vad_prob); + prob_avg = MAX16(.1f, vad_prob)*tonal->info[mpos].music_prob; + while (1) + { + float pos_vad; + mpos++; + if (mpos==DETECT_SIZE) + mpos = 0; + if (mpos == tonal->write_pos) + break; + vpos++; + if (vpos==DETECT_SIZE) + vpos = 0; + if (vpos == tonal->write_pos) + break; + pos_vad = tonal->info[vpos].activity_probability; + prob_min = MIN16((prob_avg - TRANSITION_PENALTY*(vad_prob - pos_vad))/prob_count, prob_min); + prob_max = MAX16((prob_avg + TRANSITION_PENALTY*(vad_prob - pos_vad))/prob_count, prob_max); + prob_count += MAX16(.1f, pos_vad); + prob_avg += MAX16(.1f, pos_vad)*tonal->info[mpos].music_prob; + } + info_out->music_prob = prob_avg/prob_count; + prob_min = MIN16(prob_avg/prob_count, prob_min); + prob_max = MAX16(prob_avg/prob_count, prob_max); + prob_min = MAX16(prob_min, 0.f); + prob_max = MIN16(prob_max, 1.f); + + /* If we don't have enough look-ahead, do our best to make a decent decision. */ + if (curr_lookahead < 10) + { + float pmin, pmax; + pmin = prob_min; + pmax = prob_max; + pos = pos0; + /* Look for min/max in the past. */ + for (i=0;icount-1, 15);i++) + { + pos--; + if (pos < 0) + pos = DETECT_SIZE-1; + pmin = MIN16(pmin, tonal->info[pos].music_prob); + pmax = MAX16(pmax, tonal->info[pos].music_prob); + } + /* Bias against switching on active audio. */ + pmin = MAX16(0.f, pmin - .1f*vad_prob); + pmax = MIN16(1.f, pmax + .1f*vad_prob); + prob_min += (1.f-.1f*curr_lookahead)*(pmin - prob_min); + prob_max += (1.f-.1f*curr_lookahead)*(pmax - prob_max); + } + info_out->music_prob_min = prob_min; + info_out->music_prob_max = prob_max; + + /* printf("%f %f %f %f %f\n", prob_min, prob_max, prob_avg/prob_count, vad_prob, info_out->music_prob); */ +} + +static const float std_feature_bias[9] = { + 5.684947f, 3.475288f, 1.770634f, 1.599784f, 3.773215f, + 2.163313f, 1.260756f, 1.116868f, 1.918795f +}; + +#define LEAKAGE_OFFSET 2.5f +#define LEAKAGE_SLOPE 2.f + +#ifdef FIXED_POINT +/* For fixed-point, the input is +/-2^15 shifted up by SIG_SHIFT, so we need to + compensate for that in the energy. */ +#define SCALE_COMPENS (1.f/((opus_int32)1<<(15+SIG_SHIFT))) +#define SCALE_ENER(e) ((SCALE_COMPENS*SCALE_COMPENS)*(e)) +#else +#define SCALE_ENER(e) (e) +#endif + +#ifdef FIXED_POINT +static int is_digital_silence32(const opus_val32* pcm, int frame_size, int channels, int lsb_depth) +{ + int silence = 0; + opus_val32 sample_max = 0; +#ifdef MLP_TRAINING + return 0; +#endif + sample_max = celt_maxabs32(pcm, frame_size*channels); + + silence = (sample_max == 0); + (void)lsb_depth; + return silence; +} +#else +#define is_digital_silence32(pcm, frame_size, channels, lsb_depth) is_digital_silence(pcm, frame_size, channels, lsb_depth) +#endif + +static void tonality_analysis(TonalityAnalysisState *tonal, const CELTMode *celt_mode, const void *x, int len, int offset, int c1, int c2, int C, int lsb_depth, downmix_func downmix) +{ + int i, b; + const kiss_fft_state *kfft; + VARDECL(kiss_fft_cpx, in); + VARDECL(kiss_fft_cpx, out); + int N = 480, N2=240; + float * OPUS_RESTRICT A = tonal->angle; + float * OPUS_RESTRICT dA = tonal->d_angle; + float * OPUS_RESTRICT d2A = tonal->d2_angle; + VARDECL(float, tonality); + VARDECL(float, noisiness); + float band_tonality[NB_TBANDS]; + float logE[NB_TBANDS]; + float BFCC[8]; + float features[25]; + float frame_tonality; + float max_frame_tonality; + /*float tw_sum=0;*/ + float frame_noisiness; + const float pi4 = (float)(M_PI*M_PI*M_PI*M_PI); + float slope=0; + float frame_stationarity; + float relativeE; + float frame_probs[2]; + float alpha, alphaE, alphaE2; + float frame_loudness; + float bandwidth_mask; + int is_masked[NB_TBANDS+1]; + int bandwidth=0; + float maxE = 0; + float noise_floor; + int remaining; + AnalysisInfo *info; + float hp_ener; + float tonality2[240]; + float midE[8]; + float spec_variability=0; + float band_log2[NB_TBANDS+1]; + float leakage_from[NB_TBANDS+1]; + float leakage_to[NB_TBANDS+1]; + float layer_out[MAX_NEURONS]; + float below_max_pitch; + float above_max_pitch; + int is_silence; + SAVE_STACK; + + if (!tonal->initialized) + { + tonal->mem_fill = 240; + tonal->initialized = 1; + } + alpha = 1.f/IMIN(10, 1+tonal->count); + alphaE = 1.f/IMIN(25, 1+tonal->count); + /* Noise floor related decay for bandwidth detection: -2.2 dB/second */ + alphaE2 = 1.f/IMIN(100, 1+tonal->count); + if (tonal->count <= 1) alphaE2 = 1; + + if (tonal->Fs == 48000) + { + /* len and offset are now at 24 kHz. */ + len/= 2; + offset /= 2; + } else if (tonal->Fs == 16000) { + len = 3*len/2; + offset = 3*offset/2; + } + + kfft = celt_mode->mdct.kfft[0]; + tonal->hp_ener_accum += (float)downmix_and_resample(downmix, x, + &tonal->inmem[tonal->mem_fill], tonal->downmix_state, + IMIN(len, ANALYSIS_BUF_SIZE-tonal->mem_fill), offset, c1, c2, C, tonal->Fs); + if (tonal->mem_fill+len < ANALYSIS_BUF_SIZE) + { + tonal->mem_fill += len; + /* Don't have enough to update the analysis */ + RESTORE_STACK; + return; + } + hp_ener = tonal->hp_ener_accum; + info = &tonal->info[tonal->write_pos++]; + if (tonal->write_pos>=DETECT_SIZE) + tonal->write_pos-=DETECT_SIZE; + + is_silence = is_digital_silence32(tonal->inmem, ANALYSIS_BUF_SIZE, 1, lsb_depth); + + ALLOC(in, 480, kiss_fft_cpx); + ALLOC(out, 480, kiss_fft_cpx); + ALLOC(tonality, 240, float); + ALLOC(noisiness, 240, float); + for (i=0;iinmem[i]); + in[i].i = (kiss_fft_scalar)(w*tonal->inmem[N2+i]); + in[N-i-1].r = (kiss_fft_scalar)(w*tonal->inmem[N-i-1]); + in[N-i-1].i = (kiss_fft_scalar)(w*tonal->inmem[N+N2-i-1]); + } + OPUS_MOVE(tonal->inmem, tonal->inmem+ANALYSIS_BUF_SIZE-240, 240); + remaining = len - (ANALYSIS_BUF_SIZE-tonal->mem_fill); + tonal->hp_ener_accum = (float)downmix_and_resample(downmix, x, + &tonal->inmem[240], tonal->downmix_state, remaining, + offset+ANALYSIS_BUF_SIZE-tonal->mem_fill, c1, c2, C, tonal->Fs); + tonal->mem_fill = 240 + remaining; + if (is_silence) + { + /* On silence, copy the previous analysis. */ + int prev_pos = tonal->write_pos-2; + if (prev_pos < 0) + prev_pos += DETECT_SIZE; + OPUS_COPY(info, &tonal->info[prev_pos], 1); + RESTORE_STACK; + return; + } + opus_fft(kfft, in, out, tonal->arch); +#ifndef FIXED_POINT + /* If there's any NaN on the input, the entire output will be NaN, so we only need to check one value. */ + if (celt_isnan(out[0].r)) + { + info->valid = 0; + RESTORE_STACK; + return; + } +#endif + + for (i=1;iactivity = 0; + frame_noisiness = 0; + frame_stationarity = 0; + if (!tonal->count) + { + for (b=0;blowE[b] = 1e10; + tonal->highE[b] = -1e10; + } + } + relativeE = 0; + frame_loudness = 0; + /* The energy of the very first band is special because of DC. */ + { + float E = 0; + float X1r, X2r; + X1r = 2*(float)out[0].r; + X2r = 2*(float)out[0].i; + E = X1r*X1r + X2r*X2r; + for (i=1;i<4;i++) + { + float binE = out[i].r*(float)out[i].r + out[N-i].r*(float)out[N-i].r + + out[i].i*(float)out[i].i + out[N-i].i*(float)out[N-i].i; + E += binE; + } + E = SCALE_ENER(E); + band_log2[0] = .5f*1.442695f*(float)log(E+1e-10f); + } + for (b=0;bvalid = 0; + RESTORE_STACK; + return; + } +#endif + + tonal->E[tonal->E_count][b] = E; + frame_noisiness += nE/(1e-15f+E); + + frame_loudness += (float)sqrt(E+1e-10f); + logE[b] = (float)log(E+1e-10f); + band_log2[b+1] = .5f*1.442695f*(float)log(E+1e-10f); + tonal->logE[tonal->E_count][b] = logE[b]; + if (tonal->count==0) + tonal->highE[b] = tonal->lowE[b] = logE[b]; + if (tonal->highE[b] > tonal->lowE[b] + 7.5) + { + if (tonal->highE[b] - logE[b] > logE[b] - tonal->lowE[b]) + tonal->highE[b] -= .01f; + else + tonal->lowE[b] += .01f; + } + if (logE[b] > tonal->highE[b]) + { + tonal->highE[b] = logE[b]; + tonal->lowE[b] = MAX32(tonal->highE[b]-15, tonal->lowE[b]); + } else if (logE[b] < tonal->lowE[b]) + { + tonal->lowE[b] = logE[b]; + tonal->highE[b] = MIN32(tonal->lowE[b]+15, tonal->highE[b]); + } + relativeE += (logE[b]-tonal->lowE[b])/(1e-5f + (tonal->highE[b]-tonal->lowE[b])); + + L1=L2=0; + for (i=0;iE[i][b]); + L2 += tonal->E[i][b]; + } + + stationarity = MIN16(0.99f,L1/(float)sqrt(1e-15+NB_FRAMES*L2)); + stationarity *= stationarity; + stationarity *= stationarity; + frame_stationarity += stationarity; + /*band_tonality[b] = tE/(1e-15+E)*/; + band_tonality[b] = MAX16(tE/(1e-15f+E), stationarity*tonal->prev_band_tonality[b]); +#if 0 + if (b>=NB_TONAL_SKIP_BANDS) + { + frame_tonality += tweight[b]*band_tonality[b]; + tw_sum += tweight[b]; + } +#else + frame_tonality += band_tonality[b]; + if (b>=NB_TBANDS-NB_TONAL_SKIP_BANDS) + frame_tonality -= band_tonality[b-NB_TBANDS+NB_TONAL_SKIP_BANDS]; +#endif + max_frame_tonality = MAX16(max_frame_tonality, (1.f+.03f*(b-NB_TBANDS))*frame_tonality); + slope += band_tonality[b]*(b-8); + /*printf("%f %f ", band_tonality[b], stationarity);*/ + tonal->prev_band_tonality[b] = band_tonality[b]; + } + + leakage_from[0] = band_log2[0]; + leakage_to[0] = band_log2[0] - LEAKAGE_OFFSET; + for (b=1;b=0;b--) + { + float leak_slope = LEAKAGE_SLOPE*(tbands[b+1]-tbands[b])/4; + leakage_from[b] = MIN16(leakage_from[b+1]+leak_slope, leakage_from[b]); + leakage_to[b] = MAX16(leakage_to[b+1]-leak_slope, leakage_to[b]); + } + celt_assert(NB_TBANDS+1 <= LEAK_BANDS); + for (b=0;bleak_boost[b] = IMIN(255, (int)floor(.5 + 64.f*boost)); + } + for (;bleak_boost[b] = 0; + + for (i=0;ilogE[i][k] - tonal->logE[j][k]; + dist += tmp*tmp; + } + if (j!=i) + mindist = MIN32(mindist, dist); + } + spec_variability += mindist; + } + spec_variability = (float)sqrt(spec_variability/NB_FRAMES/NB_TBANDS); + bandwidth_mask = 0; + bandwidth = 0; + maxE = 0; + noise_floor = 5.7e-4f/(1<<(IMAX(0,lsb_depth-8))); + noise_floor *= noise_floor; + below_max_pitch=0; + above_max_pitch=0; + for (b=0;bmeanE[b] = MAX32((1-alphaE2)*tonal->meanE[b], E); + Em = MAX32(E, tonal->meanE[b]); + /* Consider the band "active" only if all these conditions are met: + 1) less than 90 dB below the peak band (maximal masking possible considering + both the ATH and the loudness-dependent slope of the spreading function) + 2) above the PCM quantization noise floor + We use b+1 because the first CELT band isn't included in tbands[] + */ + if (E*1e9f > maxE && (Em > 3*noise_floor*(band_end-band_start) || E > noise_floor*(band_end-band_start))) + bandwidth = b+1; + /* Check if the band is masked (see below). */ + is_masked[b] = E < (tonal->prev_bandwidth >= b+1 ? .01f : .05f)*bandwidth_mask; + /* Use a simple follower with 13 dB/Bark slope for spreading function. */ + bandwidth_mask = MAX32(.05f*bandwidth_mask, E); + } + /* Special case for the last two bands, for which we don't have spectrum but only + the energy above 12 kHz. The difficulty here is that the high-pass we use + leaks some LF energy, so we need to increase the threshold without accidentally cutting + off the band. */ + if (tonal->Fs == 48000) { + float noise_ratio; + float Em; + float E = hp_ener*(1.f/(60*60)); + noise_ratio = tonal->prev_bandwidth==20 ? 10.f : 30.f; + +#ifdef FIXED_POINT + /* silk_resampler_down2_hp() shifted right by an extra 8 bits. */ + E *= 256.f*(1.f/Q15ONE)*(1.f/Q15ONE); +#endif + above_max_pitch += E; + tonal->meanE[b] = MAX32((1-alphaE2)*tonal->meanE[b], E); + Em = MAX32(E, tonal->meanE[b]); + if (Em > 3*noise_ratio*noise_floor*160 || E > noise_ratio*noise_floor*160) + bandwidth = 20; + /* Check if the band is masked (see below). */ + is_masked[b] = E < (tonal->prev_bandwidth == 20 ? .01f : .05f)*bandwidth_mask; + } + if (above_max_pitch > below_max_pitch) + info->max_pitch_ratio = below_max_pitch/above_max_pitch; + else + info->max_pitch_ratio = 1; + /* In some cases, resampling aliasing can create a small amount of energy in the first band + being cut. So if the last band is masked, we don't include it. */ + if (bandwidth == 20 && is_masked[NB_TBANDS]) + bandwidth-=2; + else if (bandwidth > 0 && bandwidth <= NB_TBANDS && is_masked[bandwidth-1]) + bandwidth--; + if (tonal->count<=2) + bandwidth = 20; + frame_loudness = 20*(float)log10(frame_loudness); + tonal->Etracker = MAX32(tonal->Etracker-.003f, frame_loudness); + tonal->lowECount *= (1-alphaE); + if (frame_loudness < tonal->Etracker-30) + tonal->lowECount += alphaE; + + for (i=0;i<8;i++) + { + float sum=0; + for (b=0;b<16;b++) + sum += dct_table[i*16+b]*logE[b]; + BFCC[i] = sum; + } + for (i=0;i<8;i++) + { + float sum=0; + for (b=0;b<16;b++) + sum += dct_table[i*16+b]*.5f*(tonal->highE[b]+tonal->lowE[b]); + midE[i] = sum; + } + + frame_stationarity /= NB_TBANDS; + relativeE /= NB_TBANDS; + if (tonal->count<10) + relativeE = .5f; + frame_noisiness /= NB_TBANDS; +#if 1 + info->activity = frame_noisiness + (1-frame_noisiness)*relativeE; +#else + info->activity = .5*(1+frame_noisiness-frame_stationarity); +#endif + frame_tonality = (max_frame_tonality/(NB_TBANDS-NB_TONAL_SKIP_BANDS)); + frame_tonality = MAX16(frame_tonality, tonal->prev_tonality*.8f); + tonal->prev_tonality = frame_tonality; + + slope /= 8*8; + info->tonality_slope = slope; + + tonal->E_count = (tonal->E_count+1)%NB_FRAMES; + tonal->count = IMIN(tonal->count+1, ANALYSIS_COUNT_MAX); + info->tonality = frame_tonality; + + for (i=0;i<4;i++) + features[i] = -0.12299f*(BFCC[i]+tonal->mem[i+24]) + 0.49195f*(tonal->mem[i]+tonal->mem[i+16]) + 0.69693f*tonal->mem[i+8] - 1.4349f*tonal->cmean[i]; + + for (i=0;i<4;i++) + tonal->cmean[i] = (1-alpha)*tonal->cmean[i] + alpha*BFCC[i]; + + for (i=0;i<4;i++) + features[4+i] = 0.63246f*(BFCC[i]-tonal->mem[i+24]) + 0.31623f*(tonal->mem[i]-tonal->mem[i+16]); + for (i=0;i<3;i++) + features[8+i] = 0.53452f*(BFCC[i]+tonal->mem[i+24]) - 0.26726f*(tonal->mem[i]+tonal->mem[i+16]) -0.53452f*tonal->mem[i+8]; + + if (tonal->count > 5) + { + for (i=0;i<9;i++) + tonal->std[i] = (1-alpha)*tonal->std[i] + alpha*features[i]*features[i]; + } + for (i=0;i<4;i++) + features[i] = BFCC[i]-midE[i]; + + for (i=0;i<8;i++) + { + tonal->mem[i+24] = tonal->mem[i+16]; + tonal->mem[i+16] = tonal->mem[i+8]; + tonal->mem[i+8] = tonal->mem[i]; + tonal->mem[i] = BFCC[i]; + } + for (i=0;i<9;i++) + features[11+i] = (float)sqrt(tonal->std[i]) - std_feature_bias[i]; + features[18] = spec_variability - 0.78f; + features[20] = info->tonality - 0.154723f; + features[21] = info->activity - 0.724643f; + features[22] = frame_stationarity - 0.743717f; + features[23] = info->tonality_slope + 0.069216f; + features[24] = tonal->lowECount - 0.067930f; + + analysis_compute_dense(&layer0, layer_out, features); + analysis_compute_gru(&layer1, tonal->rnn_state, layer_out); + analysis_compute_dense(&layer2, frame_probs, tonal->rnn_state); + + /* Probability of speech or music vs noise */ + info->activity_probability = frame_probs[1]; + info->music_prob = frame_probs[0]; + + /*printf("%f %f %f\n", frame_probs[0], frame_probs[1], info->music_prob);*/ +#ifdef MLP_TRAINING + for (i=0;i<25;i++) + printf("%f ", features[i]); + printf("\n"); +#endif + + info->bandwidth = bandwidth; + tonal->prev_bandwidth = bandwidth; + /*printf("%d %d\n", info->bandwidth, info->opus_bandwidth);*/ + info->noisiness = frame_noisiness; + info->valid = 1; + RESTORE_STACK; +} + +void run_analysis(TonalityAnalysisState *analysis, const CELTMode *celt_mode, const void *analysis_pcm, + int analysis_frame_size, int frame_size, int c1, int c2, int C, opus_int32 Fs, + int lsb_depth, downmix_func downmix, AnalysisInfo *analysis_info) +{ + int offset; + int pcm_len; + + analysis_frame_size -= analysis_frame_size&1; + if (analysis_pcm != NULL) + { + /* Avoid overflow/wrap-around of the analysis buffer */ + analysis_frame_size = IMIN((DETECT_SIZE-5)*Fs/50, analysis_frame_size); + + pcm_len = analysis_frame_size - analysis->analysis_offset; + offset = analysis->analysis_offset; + while (pcm_len>0) { + tonality_analysis(analysis, celt_mode, analysis_pcm, IMIN(Fs/50, pcm_len), offset, c1, c2, C, lsb_depth, downmix); + offset += Fs/50; + pcm_len -= Fs/50; + } + analysis->analysis_offset = analysis_frame_size; + + analysis->analysis_offset -= frame_size; + } + + tonality_get_info(analysis, analysis_info, frame_size); +} + +#endif /* DISABLE_FLOAT_API */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.h new file mode 100644 index 0000000000..0b66555f21 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/analysis.h @@ -0,0 +1,103 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ANALYSIS_H +#define ANALYSIS_H + +#include "celt.h" +#include "opus_private.h" +#include "mlp.h" + +#define NB_FRAMES 8 +#define NB_TBANDS 18 +#define ANALYSIS_BUF_SIZE 720 /* 30 ms at 24 kHz */ + +/* At that point we can stop counting frames because it no longer matters. */ +#define ANALYSIS_COUNT_MAX 10000 + +#define DETECT_SIZE 100 + +/* Uncomment this to print the MLP features on stdout. */ +/*#define MLP_TRAINING*/ + +typedef struct { + int arch; + int application; + opus_int32 Fs; +#define TONALITY_ANALYSIS_RESET_START angle + float angle[240]; + float d_angle[240]; + float d2_angle[240]; + opus_val32 inmem[ANALYSIS_BUF_SIZE]; + int mem_fill; /* number of usable samples in the buffer */ + float prev_band_tonality[NB_TBANDS]; + float prev_tonality; + int prev_bandwidth; + float E[NB_FRAMES][NB_TBANDS]; + float logE[NB_FRAMES][NB_TBANDS]; + float lowE[NB_TBANDS]; + float highE[NB_TBANDS]; + float meanE[NB_TBANDS+1]; + float mem[32]; + float cmean[8]; + float std[9]; + float Etracker; + float lowECount; + int E_count; + int count; + int analysis_offset; + int write_pos; + int read_pos; + int read_subframe; + float hp_ener_accum; + int initialized; + float rnn_state[MAX_NEURONS]; + opus_val32 downmix_state[3]; + AnalysisInfo info[DETECT_SIZE]; +} TonalityAnalysisState; + +/** Initialize a TonalityAnalysisState struct. + * + * This performs some possibly slow initialization steps which should + * not be repeated every analysis step. No allocated memory is retained + * by the state struct, so no cleanup call is required. + */ +void tonality_analysis_init(TonalityAnalysisState *analysis, opus_int32 Fs); + +/** Reset a TonalityAnalysisState stuct. + * + * Call this when there's a discontinuity in the data. + */ +void tonality_analysis_reset(TonalityAnalysisState *analysis); + +void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len); + +void run_analysis(TonalityAnalysisState *analysis, const CELTMode *celt_mode, const void *analysis_pcm, + int analysis_frame_size, int frame_size, int c1, int c2, int C, opus_int32 Fs, + int lsb_depth, downmix_func downmix, AnalysisInfo *analysis_info); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/extensions.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/extensions.c new file mode 100644 index 0000000000..bb6c0b0268 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/extensions.c @@ -0,0 +1,315 @@ +/* Copyright (c) 2022 Amazon */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" +#include "os_support.h" +#include "opus_private.h" + + +/* Given an extension payload, advance data to the next extension and return the + length of the remaining extensions. */ +opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size) +{ + int id, L; + if (len==0) + return 0; + id = **data>>1; + L = **data&1; + if (id == 0 && L == 1) + { + *header_size = 1; + if (len < 1) + return -1; + (*data)++; + len--; + return len; + } else if (id > 0 && id < 32) + { + if (len < 1+L) + return -1; + *data += 1+L; + len -= 1+L; + *header_size = 1; + return len; + } else { + if (L==0) + { + *data += len; + *header_size = 1; + return 0; + } else { + opus_int32 bytes=0; + *header_size = 1; + do { + (*data)++; + len--; + if (len == 0) + return -1; + bytes += **data; + (*header_size)++; + } while (**data == 255); + (*data)++; + len--; + if (bytes <= len) + { + len -= bytes; + *data += bytes; + } else { + return -1; + } + return len; + } + } +} + +/* Count the number of extensions, excluding real padding and separators. */ +opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len) +{ + opus_int32 curr_len; + opus_int32 count=0; + const unsigned char *curr_data = data; + + celt_assert(len >= 0); + celt_assert(data != NULL || len == 0); + + curr_len = len; + while (curr_len > 0) + { + int id; + opus_int32 header_size; + id = *curr_data>>1; + curr_len = skip_extension(&curr_data, curr_len, &header_size); + if (curr_len < 0) + return OPUS_INVALID_PACKET; + if (id > 1) + count++; + } + return count; +} + +/* Extract extensions from Opus padding (excluding real padding and separators) */ +opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions) +{ + const unsigned char *curr_data; + opus_int32 curr_len; + int curr_frame=0; + opus_int32 count=0; + + celt_assert(len >= 0); + celt_assert(data != NULL || len == 0); + celt_assert(nb_extensions != NULL); + celt_assert(extensions != NULL || *nb_extensions == 0); + + curr_data = data; + curr_len = len; + while (curr_len > 0) + { + int id; + opus_int32 header_size; + opus_extension_data curr_ext; + id = *curr_data>>1; + if (id > 1) + { + curr_ext.id = id; + curr_ext.frame = curr_frame; + curr_ext.data = curr_data; + } else if (id == 1) + { + int L = *curr_data&1; + if (L==0) + curr_frame++; + else { + if (curr_len >= 2) + curr_frame += curr_data[1]; + /* Else we're at the end and it doesn't matter. */ + } + if (curr_frame >= 48) + { + *nb_extensions = count; + return OPUS_INVALID_PACKET; + } + } + curr_len = skip_extension(&curr_data, curr_len, &header_size); + /* printf("curr_len = %d, header_size = %d\n", curr_len, header_size); */ + if (curr_len < 0) + { + *nb_extensions = count; + return OPUS_INVALID_PACKET; + } + celt_assert(curr_data - data == len - curr_len); + if (id > 1) + { + if (count == *nb_extensions) + { + return OPUS_BUFFER_TOO_SMALL; + } + curr_ext.len = curr_data - curr_ext.data - header_size; + curr_ext.data += header_size; + extensions[count++] = curr_ext; + } + } + celt_assert(curr_len == 0); + *nb_extensions = count; + return OPUS_OK; +} + +opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, opus_int32 nb_extensions, int pad) +{ + int max_frame=0; + opus_int32 i; + int frame; + int curr_frame = 0; + opus_int32 pos = 0; + opus_int32 written = 0; + + celt_assert(len >= 0); + + for (i=0;i 127) + return OPUS_BAD_ARG; + } + if (max_frame >= 48) return OPUS_BAD_ARG; + for (frame=0;frame<=max_frame;frame++) + { + for (i=0;i 1) + return OPUS_BAD_ARG; + if (len-pos < extensions[i].len+1) + return OPUS_BUFFER_TOO_SMALL; + if (data) data[pos] = (extensions[i].id<<1) + extensions[i].len; + pos++; + if (extensions[i].len > 0) { + if (data) data[pos] = extensions[i].data[0]; + pos++; + } + } else { + int last; + opus_int32 length_bytes; + if (extensions[i].len < 0) + return OPUS_BAD_ARG; + last = (written == nb_extensions - 1); + length_bytes = 1 + extensions[i].len/255; + if (last) + length_bytes = 0; + if (len-pos < 1 + length_bytes + extensions[i].len) + return OPUS_BUFFER_TOO_SMALL; + if (data) data[pos] = (extensions[i].id<<1) + !last; + pos++; + if (!last) + { + opus_int32 j; + for (j=0;j +int main() +{ + opus_extension_data ext[] = {{2, 0, (const unsigned char *)"a", 1}, + {32, 10, (const unsigned char *)"DRED", 4}, + {33, 1, (const unsigned char *)"NOT DRED", 8}, + {3, 4, (const unsigned char *)NULL, 0} + }; + opus_extension_data ext2[10]; + int i, len; + int nb_ext = 10; + unsigned char packet[10000]; + len = opus_packet_extensions_generate(packet, 32, ext, 4, 1); + for (i=0;i 255 || cols > 255) + return 0; + size = rows * (opus_int32)cols * sizeof(opus_int16); + if (size > 65004) + return 0; + + return align(sizeof(MappingMatrix)) + align(size); +} + +opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (opus_int16*)(void*)((char*)matrix + align(sizeof(MappingMatrix))); +} + +void mapping_matrix_init(MappingMatrix * const matrix, + int rows, int cols, int gain, const opus_int16 *data, opus_int32 data_size) +{ + int i; + opus_int16 *ptr; + +#if !defined(ENABLE_ASSERTIONS) + (void)data_size; +#endif + celt_assert(align(data_size) == align(rows * cols * sizeof(opus_int16))); + + matrix->rows = rows; + matrix->cols = cols; + matrix->gain = gain; + ptr = mapping_matrix_get_data(matrix); + for (i = 0; i < rows * cols; i++) + { + ptr[i] = data[i]; + } +} + +#ifndef DISABLE_FLOAT_API +void mapping_matrix_multiply_channel_in_float( + const MappingMatrix *matrix, + const float *input, + int input_rows, + opus_val16 *output, + int output_row, + int output_rows, + int frame_size) +{ + /* Matrix data is ordered col-wise. */ + opus_int16* matrix_data; + int i, col; + + celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows); + + matrix_data = mapping_matrix_get_data(matrix); + + for (i = 0; i < frame_size; i++) + { + float tmp = 0; + for (col = 0; col < input_rows; col++) + { + tmp += + matrix_data[MATRIX_INDEX(matrix->rows, output_row, col)] * + input[MATRIX_INDEX(input_rows, col, i)]; + } +#if defined(FIXED_POINT) + output[output_rows * i] = FLOAT2INT16((1/32768.f)*tmp); +#else + output[output_rows * i] = (1/32768.f)*tmp; +#endif + } +} + +void mapping_matrix_multiply_channel_out_float( + const MappingMatrix *matrix, + const opus_val16 *input, + int input_row, + int input_rows, + float *output, + int output_rows, + int frame_size +) +{ + /* Matrix data is ordered col-wise. */ + opus_int16* matrix_data; + int i, row; + float input_sample; + + celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows); + + matrix_data = mapping_matrix_get_data(matrix); + + for (i = 0; i < frame_size; i++) + { +#if defined(FIXED_POINT) + input_sample = (1/32768.f)*input[input_rows * i]; +#else + input_sample = input[input_rows * i]; +#endif + for (row = 0; row < output_rows; row++) + { + float tmp = + (1/32768.f)*matrix_data[MATRIX_INDEX(matrix->rows, row, input_row)] * + input_sample; + output[MATRIX_INDEX(output_rows, row, i)] += tmp; + } + } +} +#endif /* DISABLE_FLOAT_API */ + +void mapping_matrix_multiply_channel_in_short( + const MappingMatrix *matrix, + const opus_int16 *input, + int input_rows, + opus_val16 *output, + int output_row, + int output_rows, + int frame_size) +{ + /* Matrix data is ordered col-wise. */ + opus_int16* matrix_data; + int i, col; + + celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows); + + matrix_data = mapping_matrix_get_data(matrix); + + for (i = 0; i < frame_size; i++) + { + opus_val32 tmp = 0; + for (col = 0; col < input_rows; col++) + { +#if defined(FIXED_POINT) + tmp += + ((opus_int32)matrix_data[MATRIX_INDEX(matrix->rows, output_row, col)] * + (opus_int32)input[MATRIX_INDEX(input_rows, col, i)]) >> 8; +#else + tmp += + matrix_data[MATRIX_INDEX(matrix->rows, output_row, col)] * + input[MATRIX_INDEX(input_rows, col, i)]; +#endif + } +#if defined(FIXED_POINT) + output[output_rows * i] = (opus_int16)((tmp + 64) >> 7); +#else + output[output_rows * i] = (1/(32768.f*32768.f))*tmp; +#endif + } +} + +void mapping_matrix_multiply_channel_out_short( + const MappingMatrix *matrix, + const opus_val16 *input, + int input_row, + int input_rows, + opus_int16 *output, + int output_rows, + int frame_size) +{ + /* Matrix data is ordered col-wise. */ + opus_int16* matrix_data; + int i, row; + opus_int32 input_sample; + + celt_assert(input_rows <= matrix->cols && output_rows <= matrix->rows); + + matrix_data = mapping_matrix_get_data(matrix); + + for (i = 0; i < frame_size; i++) + { +#if defined(FIXED_POINT) + input_sample = (opus_int32)input[input_rows * i]; +#else + input_sample = (opus_int32)FLOAT2INT16(input[input_rows * i]); +#endif + for (row = 0; row < output_rows; row++) + { + opus_int32 tmp = + (opus_int32)matrix_data[MATRIX_INDEX(matrix->rows, row, input_row)] * + input_sample; + output[MATRIX_INDEX(output_rows, row, i)] += (tmp + 16384) >> 15; + } + } +} + +const MappingMatrix mapping_matrix_foa_mixing = { 6, 6, 0 }; +const opus_int16 mapping_matrix_foa_mixing_data[36] = { + 16384, 0, -16384, 23170, 0, 0, 16384, 23170, + 16384, 0, 0, 0, 16384, 0, -16384, -23170, + 0, 0, 16384, -23170, 16384, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 32767 +}; + +const MappingMatrix mapping_matrix_soa_mixing = { 11, 11, 0 }; +const opus_int16 mapping_matrix_soa_mixing_data[121] = { + 10923, 7723, 13377, -13377, 11585, 9459, 7723, -16384, + -6689, 0, 0, 10923, 7723, 13377, 13377, -11585, + 9459, 7723, 16384, -6689, 0, 0, 10923, -15447, + 13377, 0, 0, -18919, 7723, 0, 13377, 0, + 0, 10923, 7723, -13377, -13377, 11585, -9459, 7723, + 16384, -6689, 0, 0, 10923, -7723, 0, 13377, + -16384, 0, -15447, 0, 9459, 0, 0, 10923, + -7723, 0, -13377, 16384, 0, -15447, 0, 9459, + 0, 0, 10923, 15447, 0, 0, 0, 0, + -15447, 0, -18919, 0, 0, 10923, 7723, -13377, + 13377, -11585, -9459, 7723, -16384, -6689, 0, 0, + 10923, -15447, -13377, 0, 0, 18919, 7723, 0, + 13377, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767 +}; + +const MappingMatrix mapping_matrix_toa_mixing = { 18, 18, 0 }; +const opus_int16 mapping_matrix_toa_mixing_data[324] = { + 8208, 0, -881, 14369, 0, 0, -8192, -4163, + 13218, 0, 0, 0, 11095, -8836, -6218, 14833, + 0, 0, 8208, -10161, 881, 10161, -13218, -2944, + -8192, 2944, 0, -10488, -6218, 6248, -11095, -6248, + 0, -10488, 0, 0, 8208, 10161, 881, -10161, + -13218, 2944, -8192, -2944, 0, 10488, -6218, -6248, + -11095, 6248, 0, 10488, 0, 0, 8176, 5566, + -11552, 5566, 9681, -11205, 8192, -11205, 0, 4920, + -15158, 9756, -3334, 9756, 0, -4920, 0, 0, + 8176, 7871, 11552, 0, 0, 15846, 8192, 0, + -9681, -6958, 0, 13797, 3334, 0, -15158, 0, + 0, 0, 8176, 0, 11552, 7871, 0, 0, + 8192, 15846, 9681, 0, 0, 0, 3334, 13797, + 15158, 6958, 0, 0, 8176, 5566, -11552, -5566, + -9681, -11205, 8192, 11205, 0, 4920, 15158, 9756, + -3334, -9756, 0, 4920, 0, 0, 8208, 14369, + -881, 0, 0, -4163, -8192, 0, -13218, -14833, + 0, -8836, 11095, 0, 6218, 0, 0, 0, + 8208, 10161, 881, 10161, 13218, 2944, -8192, 2944, + 0, 10488, 6218, -6248, -11095, -6248, 0, -10488, + 0, 0, 8208, -14369, -881, 0, 0, 4163, + -8192, 0, -13218, 14833, 0, 8836, 11095, 0, + 6218, 0, 0, 0, 8208, 0, -881, -14369, + 0, 0, -8192, 4163, 13218, 0, 0, 0, + 11095, 8836, -6218, -14833, 0, 0, 8176, -5566, + -11552, 5566, -9681, 11205, 8192, -11205, 0, -4920, + 15158, -9756, -3334, 9756, 0, -4920, 0, 0, + 8176, 0, 11552, -7871, 0, 0, 8192, -15846, + 9681, 0, 0, 0, 3334, -13797, 15158, -6958, + 0, 0, 8176, -7871, 11552, 0, 0, -15846, + 8192, 0, -9681, 6958, 0, -13797, 3334, 0, + -15158, 0, 0, 0, 8176, -5566, -11552, -5566, + 9681, 11205, 8192, 11205, 0, -4920, -15158, -9756, + -3334, -9756, 0, 4920, 0, 0, 8208, -10161, + 881, -10161, 13218, -2944, -8192, -2944, 0, -10488, + 6218, 6248, -11095, 6248, 0, 10488, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767 +}; + +const MappingMatrix mapping_matrix_fourthoa_mixing = { 27, 27, 0 }; +const opus_int16 mapping_matrix_fourthoa_mixing_data[729] = { + 9243, 0, 16010, 0, 0, 0, 20669, 0, + 0, 0, 0, 0, 24456, 0, 0, 0, + 0, 0, 0, 0, 27731, 0, 0, 0, + 0, 0, 0, 9243, 0, 10884, 11741, 0, + 0, 3995, 17849, 9626, 0, 0, 0, -5727, + 14399, 17315, 7625, 0, 0, 0, 0, -11747, + 2574, 18637, 15552, 5930, 0, 0, 9243, -14302, + -2682, -6677, 13337, 5357, -9464, 2501, -11170, 4770, + -5911, 11501, 5858, 5369, 4951, 17901, -19071, -2397, + -9281, -9198, 7576, -4294, 7773, -8997, -3399, 0, + 0, 9243, 9940, 11991, -3705, -5144, 16647, 7057, + -6206, -5941, -2698, -10194, 16781, -1788, -6256, -11772, + 4935, 3912, -6062, -13039, 9446, -9758, -3521, -15058, + 11089, 565, 0, 0, 9243, -15376, 3720, 2461, + -5285, -7989, -8660, 1278, -16087, 15811, -3249, 10500, + -7757, -1680, -9890, -8153, 10884, 11022, 2847, 12828, + 5137, -2053, 8666, -5684, 14776, 0, 0, 9243, + -10577, 10304, -6186, 9139, -15222, 2507, -8902, -5140, + -145, 15562, -10596, -7311, -6197, -8753, 8667, -6014, + -281, 15033, 938, -11859, 548, -8456, 16735, -3654, + 0, 0, 9243, 8974, 4839, -12343, -15472, 6066, + -7501, -8343, 5015, 15920, -12374, -4559, -9400, 6271, + 4011, 5191, -9932, 14438, 4828, -8768, 1909, 12059, + -1565, 4707, -13711, 0, 0, 9243, 15799, 2085, + -1534, -3386, 4602, -9808, -447, -17267, -18054, -1167, + -13525, -4644, 1313, -5951, 5397, 7485, -7056, 2584, + -8120, 8669, 788, 13177, 2109, 18349, 0, 0, + 9243, 12371, -10036, 1597, 2760, -17341, 1848, -2239, + -10509, -8474, -4577, 11164, 7935, 1441, 17430, -3436, + -3713, 15936, 4184, 2647, -11730, 341, -15934, 6462, + 6581, 0, 0, 9243, -8963, 2184, 13084, -16381, + -2734, -9757, 3991, 6345, -18297, -5912, 7604, -4849, + -11100, 2290, -4304, -13305, -7488, 12338, 4805, 8505, + -7014, -4779, -1761, -14597, 0, 0, 9243, 1301, + -15498, 3799, 690, -2816, 18718, -8223, 889, 255, + -1768, 4485, -19951, 13097, -2278, 167, 78, -740, + 3324, -6139, 19488, -17925, 4283, -486, 20, 0, + 0, 9243, -13470, -6719, 5452, -10257, 12641, -4873, + -5116, -10595, 5856, 11389, 1502, 10876, -608, 11765, + -13218, 13911, -7373, -2070, -13679, -4154, 5536, -2138, + 16643, 451, 0, 0, 9243, 2455, -3679, -15387, + -5277, -1261, -8697, 7906, 16112, 8147, 3208, -1690, + 7687, 10593, -9796, -15852, -10884, -5616, 2881, 2032, + 5246, -12735, -8796, 10928, 14833, 0, 0, 9243, + -6849, 2775, -14202, 13586, -2655, -9402, -5505, 10809, + -18013, 6231, 5444, -6041, 11288, 4958, -4078, 18799, + -9368, -9291, 4535, 7383, 9405, -7391, -2121, -4336, + 0, 0, 9243, 6423, -9040, 11548, 10359, -8109, + -450, -14580, 6431, 10857, -15475, 3569, 9707, 6416, + -9607, 521, 8528, -18391, 11049, 3815, -10423, 6860, + 6860, -883, -4221, 0, 0, 9243, 11932, -5968, + -8850, -14749, -9946, -6026, 7377, -4472, 5206, 14547, + -3406, 10508, 2526, 4411, 14543, 8444, -5822, 347, + 12347, -1709, -9158, 105, -16265, -12642, 0, 0, + 9243, 13044, -150, 9282, 16910, -274, -10332, -194, + -5864, 5428, -420, -12196, 344, -8679, 145, -18554, + -12695, -152, -14635, 503, 10389, 358, 5076, 522, + -16100, 0, 0, 9243, -8374, -13590, -1221, 1428, + 15896, 12005, 2318, -4793, 2590, -3209, -20390, -6256, + -2974, 10766, 1202, -876, -6597, 5004, 19896, -1541, + 2902, -16788, -3062, 1340, 0, 0, 9243, 9879, + 10267, 7300, 10073, 14167, 2416, 10469, -3094, 2899, + 17092, 9762, -7400, 7214, -5250, -8238, -3989, 5578, + 16392, -1050, -11848, -776, -5034, -15850, -5882, 0, + 0, 9243, -4974, -9068, 12221, -8490, 6299, -388, + -15478, 8702, -9920, 12723, -2810, 9668, 6905, -13040, + 4325, -9456, 16856, -9159, -2909, -10476, 7149, 9387, + -7350, 233, 0, 0, 9243, 3627, -13823, -7218, + -3656, -7002, 12776, 13935, 2719, 2446, 8352, 9252, + -7676, -18413, -6212, -429, -1272, -6335, -13356, -9510, + 295, 18926, 9934, 1112, -382, 0, 0, 9243, + -6383, -9343, -11326, 10097, 8329, 223, 14780, 6114, + -10348, -15590, -4195, 9257, -7445, -9439, -323, 7902, + 18117, 12101, -3142, -10944, -5577, 7327, 566, -4133, + 0, 0, 9243, 2626, 865, 15769, 5783, 317, + -10244, 1905, 16884, 9144, 826, -2420, -1972, -14536, + 2413, 16939, 12500, 1482, -4906, -578, 10096, -3476, + -14323, 2745, 16105, 0, 0, 9243, -8975, 12086, + 5450, -6832, -15149, 7333, 9200, -3550, -362, -13645, + -15525, -1391, 9428, -7091, -5442, 3105, -820, -17685, + -9175, -9462, 5572, -9191, -12325, -2180, 0, 0, + 9243, -114, 11576, -11058, 177, -185, 5875, -17880, + 8539, -198, 339, -173, -3411, -16698, 16336, -6369, + 193, -430, 408, -75, -10806, -7225, 19670, -13817, + 4665, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767 +}; + +const MappingMatrix mapping_matrix_fifthoa_mixing = { 38, 38, 0 }; +const opus_int16 mapping_matrix_fifthoa_mixing_data[1444] = { + 9243, 0, 16010, 0, 0, 0, 20669, 0, + 0, 0, 0, 0, 24456, 0, 0, 0, + 0, 0, 0, 0, 27731, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 30657, 0, + 0, 0, 0, 0, 0, 0, 9243, 0, + -7023, 14387, 0, 0, -4369, -14112, 14455, 0, + 0, 0, 10931, -510, -16777, 14031, 0, 0, + 0, 0, -5118, 14286, 4343, -18465, 13374, 0, + 0, 0, 0, 0, -6494, -12221, 11761, 8513, + -19458, 12605, 0, 0, 9243, -14128, 5093, 5547, + -10946, -10050, -7197, 3945, -11790, 7142, -9213, 6529, + -9701, -2563, -9923, -14846, 16521, 6816, 2764, 14103, + 1118, -5537, 2977, -14168, 1228, 4866, 17430, -528, + 10639, 2641, 10437, -1037, 11460, 1098, 1296, 15737, + 0, 0, 9243, 1128, -14775, 6062, 955, -2329, + 16069, -12511, 2477, 579, -2333, 3440, -14197, 18478, + -6050, 940, 303, -1604, 4106, -4223, 9829, -22688, + 10647, -2604, 334, 145, -927, 3203, -6017, 4507, + -3812, 24212, -15600, 5198, -1023, 110, 0, 0, + 9243, 1158, 12997, 9277, 1501, 2103, 10097, 16840, + 5916, 1402, 3225, 2488, 2929, 19916, 12706, 3585, + 1137, 3415, 4698, 2078, -5442, 16634, 18511, 8731, + 2095, 850, 3061, 5733, 5225, 960, -11728, 7689, + 20588, 14659, 5642, 1187, 0, 0, 9243, -4663, + -3081, -15003, 9771, 2007, -9185, 6457, 14199, -14357, + -4976, 3554, 6625, 11434, -7231, -11297, 17760, 8291, + -6267, -3368, 6712, -10837, -9107, 6524, 6793, -19531, + -11338, 7934, 7335, -2205, -9215, -7094, 10659, 6243, + -4337, -1250, 0, 0, 9243, -13515, 7679, -3831, + 7232, -14496, -3201, -4109, -11731, 8828, 9178, -1901, + -10848, -539, -14888, 9626, -10860, 12703, 3824, 12334, + -7104, 3496, -6203, 13852, 5461, -2109, -17277, 7837, + -4714, 13901, 4097, 3940, 7647, 8546, 8688, -10986, + 0, 0, 9243, 8113, -9860, 9657, 10943, -11174, + 1426, -13300, 1915, 8178, -17833, 6805, 8309, 8100, + -3121, -4742, 2683, -15111, 15688, 2358, -11590, 2807, + 2746, 8762, -7430, -2251, -5481, 16370, -4081, -9694, + 5872, -11539, -714, -9492, 15177, -6126, 0, 0, + 9243, 9933, -9215, -8528, -11831, -12785, -62, 10976, + -1811, 5593, 18018, 6100, 9455, -5237, 2758, 8971, + 2743, -9659, -13517, 5330, -10737, -4576, -2069, -15491, + -8749, -7226, -5237, 9191, -181, -12277, 2815, 10540, + -27, 14741, 16703, 3103, 0, 0, 9243, -10067, + -8881, -8723, 12265, 12487, -793, 10821, -1762, -6021, + -18002, -5072, 9912, -4395, 2587, 9368, -2767, 10021, + 12259, -6468, -10113, -5605, -1761, -15590, -9430, 7800, + 5092, -8835, 2293, 12314, 1222, 10671, -329, 13745, + 17349, 3563, 0, 0, 9243, -6485, 12991, -6743, + 6108, -11768, 10080, -12236, 238, -2883, 13115, -13907, + 2900, -14460, 511, 2564, 186, -7019, 19094, -11597, + -5472, -12058, 744, 6243, -2384, 930, 501, -11778, + 21214, -5330, -11746, -5542, 827, 10475, -6418, 1132, + 0, 0, 9243, 3862, 5238, -14627, -7891, 2826, + -7015, -10701, 13900, 11410, -6831, -1679, -9861, 6359, + 12032, -11660, -14041, 11199, 1713, -3895, 657, 14749, + -3017, -11445, 8380, 15575, -15236, -346, 7690, -923, + 10317, 3498, -13545, 354, 9093, -4476, 0, 0, + 9243, -8417, 13183, 3418, -4018, -15498, 10685, 6294, + -4132, 1419, -8755, -18818, 3926, 7642, -9001, -3235, + 2125, 3506, -13037, -16570, -4337, 6729, -13404, -7991, + 59, 443, 5804, 6005, -15011, -9060, -11044, 3679, + -15434, -13685, 161, 1185, 0, 0, 9243, -5288, + 6773, -13508, 9977, -5002, -4784, -12780, 10790, -12942, + 11168, 519, -10890, 1326, 12078, -6274, 13780, -16427, + 2186, 5352, -4328, 13671, 2364, -7963, 1080, -12568, + 19336, -6557, -8574, 4084, 7277, 10433, -9273, -3178, + 1516, 3817, 0, 0, 9243, 9660, 7817, 10093, + 13619, 10548, -2942, 11021, 597, 9663, 17594, 1736, + -10794, 1814, 771, -8469, 1041, 14155, 7891, -8597, + -7498, -8982, 346, -12407, -11848, -6809, 1686, 9181, + -8306, -10247, 3538, -10706, -364, -8047, -19188, -8493, + 0, 0, 9243, -7163, -1020, 14282, -14289, 1021, + -10208, -2036, 10660, -18919, 2410, 6564, 2323, -13088, + -1798, 3365, -19498, 3619, 12022, -1858, 9978, 3705, + -8969, -643, -5794, -15523, 4123, 15113, -3949, -6265, + -3596, 12490, 2946, -2688, 1225, -14570, 0, 0, + 9243, -12187, 772, -10354, 17623, -1314, -10262, -1117, + -2885, -9937, 2249, 11267, -1763, 9572, -368, 16506, + -6510, -1438, -15014, 2402, 10157, 2041, 2458, 2389, + -19346, 19860, -1041, 8067, -3704, -10931, 2743, -9286, + 606, -13399, -3095, 7924, 0, 0, 9243, 15545, + -2367, -3011, -6538, -5139, -9657, 995, -16242, -15706, + 2557, -12952, 5226, 2508, 6353, 10156, 13593, 6966, + 4795, 8960, 8183, -1735, 11914, -4504, 14149, 11727, + -6665, 10460, -3962, 10145, -7648, -1965, -9845, -6764, + -6938, -16633, 0, 0, 9243, 3098, 12983, -8841, + -3826, 5618, 10053, -16031, 4787, 3283, -8209, 6632, + 2856, -18922, 10272, -2055, -2344, 7987, -11939, 5516, + -5520, -15739, 14940, -5001, 530, 1465, -6306, 13388, + -13243, 2513, -11772, -7170, 16572, -8384, 1426, 168, + 0, 0, 9243, -15767, -2008, -1916, 4220, 4422, + -9846, 537, -17105, 17650, -1400, 13589, 4481, 1651, + 5677, 6701, -9241, -6642, -3252, -7827, 8792, -951, + 13182, -2522, 17586, -17005, 3845, -12562, 2213, -11472, + -6688, -1394, -8970, -4769, -7316, -11753, 0, 0, + 9243, -13344, -3829, 7975, -14863, 7136, -8561, -4265, + -7992, -801, 9405, 8912, 7937, -5326, 5057, -17681, + 15207, 575, 7717, -11360, 4847, 6789, 4150, 12686, + -10050, 16730, -12063, 322, -12920, -3313, -10267, 1980, + -6948, 7112, 7972, 8042, 0, 0, 9243, 7791, + -1021, 13949, 15180, -1111, -10208, -1989, 9348, 19199, + -2561, -7140, 2323, -12782, -1577, 817, 18164, -3673, + -12771, 2022, 9978, 3620, -7865, -156, -9155, 11924, + -3842, -15336, 4196, 6814, -3596, 12199, 2583, -652, + 1936, -17637, 0, 0, 9243, -4810, -15144, -1958, + 1315, 10175, 17406, 4142, -1348, 263, -3292, -15632, + -17046, -6363, 3374, 605, -227, -748, 5997, 20334, + 14481, 8277, -6146, -1717, 5, 27, 712, 1542, + -9197, -23572, -10163, -9595, 9425, 3539, -17, -72, + 0, 0, 9243, -7366, 8261, 11568, -11901, -8499, + -2079, 13347, 5556, -12049, -16247, -2282, -10529, 3584, + 7585, -1577, -8464, -18652, -8902, 5913, -8688, -9287, + 4156, -2442, -7089, -2993, -14485, -13949, 5422, 8459, + 1638, -13285, -2531, -1826, -12132, -9456, 0, 0, + 9243, 11716, 698, -10889, -17818, 1143, -10275, -1062, + -1305, 12057, -2057, -10855, -1595, 10088, -150, 15043, + 2978, 1578, 15225, -2090, 10201, 1943, 1115, 1969, + -20211, -17636, 430, -9826, 3391, 10572, 2485, -9826, + 248, -12259, -2924, 12131, 0, 0, 9243, 4361, + -4594, -14703, -8956, -2798, -7781, 9434, 13769, 12936, + 6800, -2400, 9082, 8091, -10453, -11023, -15786, -11136, + 3285, 4153, 2658, -14002, -5051, 9489, 7000, 17206, + 15024, -2777, -8491, -42, -10626, 141, 13053, 2366, + -6662, -2231, 0, 0, 9243, -752, -11933, -10646, + 1119, 1254, 6890, 17745, 7875, -1203, -2207, -1251, + 2024, -17706, -15532, -5600, 1128, 2691, 2800, 683, + -9927, 9661, 19706, 12522, 3889, -978, -2789, -3992, + -2440, 206, 12695, 2921, -17173, -18575, -9616, -2657, + 0, 0, 9243, 4791, -15001, -2887, -1931, -10037, + 16885, 6048, -1020, 46, 4789, 15191, -15922, -9154, + 2530, 823, 252, -130, -8608, -19335, 12613, 11651, + -4549, -2314, -172, -101, -784, 265, 12975, 21741, + -7551, -13101, 6856, 4710, 535, -46, 0, 0, + 9243, -12153, -10395, 754, -1281, 17644, 2735, -1095, + -10274, 8359, 2200, -12593, 7083, 782, 17650, -1573, + 1685, -16282, -2164, -530, -11878, 32, -17359, 3065, + 6651, -5212, -3628, 19365, 965, 13180, 8243, -818, + 7746, -3645, -14323, 1670, 0, 0, 9243, -6961, + -11198, 9081, -8829, 10887, 4833, -14202, 2374, -6524, + 16339, -9417, 4737, 12284, -4394, -2691, -2683, 13690, + -18539, 2830, -11438, -3692, 4985, 5648, -4628, 514, + 6225, -18409, 12672, 5311, 11170, -6928, -3407, -7595, + 10737, -3977, 0, 0, 9243, 12099, -10405, 1294, + 2187, -17582, 2760, -1880, -10105, -8058, -3760, 12583, + 7058, 1346, 17376, -2667, -2829, 15710, 3705, 468, + -11880, 50, -17123, 5201, 6230, 4698, 6098, -18716, + -1665, -13088, 8285, -1400, 7696, -6196, -13429, 2770, + 0, 0, 9243, 8602, 13392, 1722, 2070, 16090, + 11359, 3222, -4960, -2638, 4581, 20106, 5099, 4026, + -10978, -1778, -1314, -6620, 6988, 18701, -2965, 3745, + -16745, -4461, 1300, 584, -3646, -11588, 8350, 11847, + -10050, 2372, -20010, -7809, 3608, 887, 0, 0, + 9243, 14252, -1958, 7026, 13986, -3899, -9870, -1922, + -10736, -3693, -4527, -12333, 4376, -6080, 3475, -18537, + -19222, 1355, -10843, 6913, 8869, 3408, 8323, 6804, + -5141, -13648, 7800, 2649, 7171, 10505, -6548, 5179, + -5505, 13299, 2086, 15579, 0, 0, 9243, 11323, + 9021, -6835, -10810, 14267, -489, -8613, -5689, 639, + -16117, 6224, -9731, -3757, -8482, 10882, 7873, 1080, + -11447, -6791, -10388, 4099, -6025, 18396, -5407, -7536, + 14714, 984, 1267, -13940, -1889, 8416, 666, 16762, + -10106, -3418, 0, 0, 9243, 871, 4833, 15238, + 1855, 588, -7508, 10287, 16162, 2857, 1481, -443, + -9392, -7758, 12910, 16506, 3837, 2588, -581, -851, + 1928, -14879, -5066, 14950, 16498, 4773, 3842, -425, + -1785, -82, 10578, -1435, -15554, -2459, 16520, 16250, + 0, 0, 9243, 14762, 5967, 1673, 3450, 12303, + -6027, 1394, -15022, -14571, 3402, -4217, -10507, -478, + -14813, -5131, -6634, -16293, -82, -15276, -1705, -1731, + 358, -5738, 13681, 12503, -8200, -3023, -3290, -7384, + 9272, -837, 14328, -1064, 16913, 7915, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767 +}; + +const MappingMatrix mapping_matrix_foa_demixing = { 6, 6, 0 }; +const opus_int16 mapping_matrix_foa_demixing_data[36] = { + 16384, 16384, 16384, 16384, 0, 0, 0, 23170, + 0, -23170, 0, 0, -16384, 16384, -16384, 16384, + 0, 0, 23170, 0, -23170, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 32767 +}; + +const MappingMatrix mapping_matrix_soa_demixing = { 11, 11, 3050 }; +const opus_int16 mapping_matrix_soa_demixing_data[121] = { + 2771, 2771, 2771, 2771, 2771, 2771, 2771, 2771, + 2771, 0, 0, 10033, 10033, -20066, 10033, 14189, + 14189, -28378, 10033, -20066, 0, 0, 3393, 3393, + 3393, -3393, 0, 0, 0, -3393, -3393, 0, + 0, -17378, 17378, 0, -17378, -24576, 24576, 0, + 17378, 0, 0, 0, -14189, 14189, 0, -14189, + -28378, 28378, 0, 14189, 0, 0, 0, 2399, + 2399, -4799, -2399, 0, 0, 0, -2399, 4799, + 0, 0, 1959, 1959, 1959, 1959, -3918, -3918, + -3918, 1959, 1959, 0, 0, -4156, 4156, 0, + 4156, 0, 0, 0, -4156, 0, 0, 0, + 8192, 8192, -16384, 8192, 16384, 16384, -32768, 8192, + -16384, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 8312, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 8312 +}; + +const MappingMatrix mapping_matrix_toa_demixing = { 18, 18, 0 }; +const opus_int16 mapping_matrix_toa_demixing_data[324] = { + 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, + 8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192, + 0, 0, 0, -9779, 9779, 6263, 8857, 0, + 6263, 13829, 9779, -13829, 0, -6263, 0, -8857, + -6263, -9779, 0, 0, -3413, 3413, 3413, -11359, + 11359, 11359, -11359, -3413, 3413, -3413, -3413, -11359, + 11359, 11359, -11359, 3413, 0, 0, 13829, 9779, + -9779, 6263, 0, 8857, -6263, 0, 9779, 0, + -13829, 6263, -8857, 0, -6263, -9779, 0, 0, + 0, -15617, -15617, 6406, 0, 0, -6406, 0, + 15617, 0, 0, -6406, 0, 0, 6406, 15617, + 0, 0, 0, -5003, 5003, -10664, 15081, 0, + -10664, -7075, 5003, 7075, 0, 10664, 0, -15081, + 10664, -5003, 0, 0, -8176, -8176, -8176, 8208, + 8208, 8208, 8208, -8176, -8176, -8176, -8176, 8208, + 8208, 8208, 8208, -8176, 0, 0, -7075, 5003, + -5003, -10664, 0, 15081, 10664, 0, 5003, 0, + 7075, -10664, -15081, 0, 10664, -5003, 0, 0, + 15617, 0, 0, 0, -6406, 6406, 0, -15617, + 0, -15617, 15617, 0, 6406, -6406, 0, 0, + 0, 0, 0, -11393, 11393, 2993, -4233, 0, + 2993, -16112, 11393, 16112, 0, -2993, 0, 4233, + -2993, -11393, 0, 0, 0, -9974, -9974, -13617, + 0, 0, 13617, 0, 9974, 0, 0, 13617, + 0, 0, -13617, 9974, 0, 0, 0, 5579, + -5579, 10185, 14403, 0, 10185, -7890, -5579, 7890, + 0, -10185, 0, -14403, -10185, 5579, 0, 0, + 11826, -11826, -11826, -901, 901, 901, -901, 11826, + -11826, 11826, 11826, -901, 901, 901, -901, -11826, + 0, 0, -7890, -5579, 5579, 10185, 0, 14403, + -10185, 0, -5579, 0, 7890, 10185, -14403, 0, + -10185, 5579, 0, 0, -9974, 0, 0, 0, + -13617, 13617, 0, 9974, 0, 9974, -9974, 0, + 13617, -13617, 0, 0, 0, 0, 16112, -11393, + 11393, -2993, 0, 4233, 2993, 0, -11393, 0, + -16112, -2993, -4233, 0, 2993, 11393, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767 +}; + +const MappingMatrix mapping_matrix_fourthoa_demixing = { 27, 27, 0 }; +const opus_int16 mapping_matrix_fourthoa_demixing_data[729] = { + 4870, 4484, 4870, 4347, 4440, 4726, 4683, 4821, + 4883, 4842, 4603, 4484, 4683, 4698, 4234, 4368, + 4603, 4783, 4783, 4820, 4821, 4347, 4820, 4440, + 4698, 0, 0, 101, 84, -7818, 4640, -7178, + -5492, 4629, 8384, 6547, -4966, 617, -6345, 1061, + -3241, 2939, 5549, 6390, -4434, 4994, -2610, 1993, + -2873, 1547, -4356, -164, 0, 0, 8797, 5074, + -1553, 5383, 1906, 5297, 2722, 1158, -5226, 1311, + -7760, -3327, -1940, 1586, -4093, -2951, -214, -6873, + 5450, -4875, -7193, -4438, 558, 5593, 5607, 0, + 0, -26, 5761, -3723, -1460, 1195, -3065, -6357, + -1175, 608, 6965, 2310, 2759, -8023, -7138, 5162, + -3624, 5006, -809, 3592, 6209, -4159, -4968, 8150, + 2513, -5702, 0, 0, 301, 109, 7161, -2462, + -2443, 5044, -7125, -2256, 1967, -9107, 259, -4928, + -2592, 6514, 4111, -7236, 8695, 635, 5009, -4025, + -1937, 4794, 3420, -3507, -400, 0, 0, -134, + 85, 2771, 7842, -3649, -8225, 2866, 2586, -9200, + -1945, -1563, 6155, -720, -1061, -3494, -4513, -487, + 8389, 7317, 3348, -3721, 3806, 371, -6896, 70, + 0, 0, 10919, 2072, -4867, 3472, -4429, 1721, + -4066, -5193, 1032, -5253, 9501, -2017, -3971, -5261, + -306, -2737, -5137, 5713, 1237, -8, 6387, 364, + -5423, 3364, 2888, 0, 0, -48, 8946, 1048, + -2691, 602, -4332, -4302, -514, -1730, 2459, -4328, + -2156, 3335, -2748, -6029, 4023, 155, 897, 5268, + -8380, 7625, 7395, 508, 3945, -8951, 0, 0, + 39, 4151, -5965, -3398, -7006, -3534, 2697, -8989, + -5237, 2913, 46, -5540, 8196, 5766, 2711, -2520, + -3043, -2146, -948, 4965, 1806, 2472, 8988, -1266, + 4840, 0, 0, -407, -189, 2179, -1627, 6516, + 259, 7196, -9449, -4905, -9766, 561, 4021, 3371, + -8650, 5032, 3329, 2534, 641, 2224, -5747, 1047, + -4074, 5252, -24, 674, 0, 0, 664, 237, + -2837, -4072, -1205, 8252, -5875, -1670, -2743, -3984, + 381, 5059, 1765, 2666, -8295, 7403, 1154, -2086, + 7622, 7105, 3677, -6943, 1050, -6632, -694, 0, + 0, 382, -133, 5699, 7650, 5154, -5713, -1645, + -6902, 6181, 4450, 1151, 410, -993, 3829, 2444, + -2405, -6618, -9514, 5366, -1896, 5844, -2886, -1524, + -7321, -1007, 0, 0, 12767, -2530, 3183, -1409, + -4015, -2894, -5155, -1710, 3841, -2107, -10274, 5119, + 3979, -4010, 5550, 4822, -746, -2507, -3080, 4289, + -3675, 4333, -1416, -1230, -1122, 0, 0, 17, + 8048, 2398, -2167, -73, -3606, 3125, 398, 731, + -5973, 5705, -1032, 4679, 7305, 3134, 1301, -3858, + -89, 2938, 4359, -9155, -4805, -8407, 3673, -8645, + 0, 0, 187, 7355, 3145, -6719, -4432, -5939, + 2541, -2810, 9723, 778, -1105, 5687, -4174, 2534, + -4461, 1017, -244, 5481, -1655, -6765, -3350, -4894, + 1592, -2318, 8827, 0, 0, 196, 3588, 9631, + 3063, -4564, 6043, 2683, 2595, -2488, -2186, 173, + -6059, -8270, -2386, 409, 7441, -8608, 376, -4364, + 2321, -280, 97, 8331, -3022, -4721, 0, 0, + 117, -748, -10833, 1533, 4200, -2875, -997, -109, + -3661, -6119, 4454, 8808, -9189, 8294, 1521, 7265, + -2348, -5094, -948, -5400, -3193, 8914, 5763, 1716, + -1070, 0, 0, 2497, 399, -5201, -2038, 7843, + -376, 7567, -5073, 7616, -5537, 2086, -3453, -5544, + -56, -11648, -1314, 3546, -3432, -117, 8694, -4245, + 9621, 3098, -2582, -2351, 0, 0, 4386, -3104, + -3132, -10512, 566, 5217, 5128, 4967, 1348, 7035, + -1470, 91, -125, -3548, 8244, -3029, -10033, 2186, + 9745, -6440, -2074, 3638, -1477, -7045, -562, 0, + 0, 2154, 8116, -6102, 6570, 12998, -712, -4126, + -4996, 30, 1571, -6393, -12794, 425, 5036, 1190, + 5763, 5653, 12933, -6671, 5197, -2964, -3316, -6354, + -10554, -2652, 0, 0, 12618, -3737, 93, -5901, + 4262, -3364, 4444, 3103, -2767, 3403, 4925, -2584, + -989, 4977, -3714, -1965, 3076, 326, -2946, -2568, + 1026, -2980, 3362, -6132, -5966, 0, 0, 6001, + 48, -1979, -7275, 3476, -2096, 10591, 3793, 2629, + -447, -14747, -3689, -5525, 8358, 6883, -9703, -4556, + 7471, 2965, 4056, 13221, -7327, -3073, -2353, -6720, + 0, 0, 621, 11034, -44, -2828, 5978, -1850, + -1772, 3894, -7471, -1397, 945, -2028, -2928, -2240, + 3172, 2222, 4544, -4243, -5645, 3745, 2573, 3511, + -8206, -7286, 5700, 0, 0, 321, 10818, -4982, + 7813, -749, 9907, 1360, -1443, 568, -1103, 2305, + 6045, 2270, -1063, -1920, -3073, 5893, -3476, -11346, + -1657, -588, 2957, -2287, -8527, -8041, 0, 0, + 119, -268, 2372, -3040, 4979, -3789, -5630, 10619, + 5900, -5109, -4585, -3862, 10467, -3527, -385, -10034, + -9991, 4860, 984, 2362, 2311, -6804, 6324, 433, + 5291, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32767 +}; + +const MappingMatrix mapping_matrix_fifthoa_demixing = { 38, 38, 0 }; +const opus_int16 mapping_matrix_fifthoa_demixing_data[1444] = { + 3188, 3247, 3268, 3368, 3368, 3138, 3268, 3099, + 3211, 3368, 3099, 3247, 3211, 3368, 3368, 3368, + 3149, 3268, 3247, 3211, 3099, 3188, 3138, 3149, + 3099, 3188, 3368, 3149, 3188, 3247, 3268, 3138, + 3211, 3368, 3138, 3149, 0, 0, 118, -47, + -5011, 282, 333, -1497, -4584, 2908, 3388, -3647, + -2493, 1139, -2882, -1719, 3604, -2543, -4328, 5443, + 1286, -5498, -4583, 2510, -1743, -2556, 4168, 1446, + -290, 1812, -4074, -2377, 4152, 2847, 4991, 3980, + 393, 5072, 0, 0, 5489, -2235, 1507, -5326, + 4609, -1096, 2926, -3427, -3301, -3078, 4226, 1730, + 4627, 2561, 2966, -592, 143, -677, 4617, -755, + -956, -433, -5138, 3037, 157, -1394, -4498, -4984, + -3661, -4112, -3756, 4628, -570, 3356, 1605, 1803, + 0, 0, -162, 5162, 2132, 2392, 3556, -5141, + -1536, 2975, -3001, -3350, -2231, -5230, 1294, -4965, + 3494, 5230, -3292, -1359, -2945, -773, 2670, 4867, + -660, 3720, -3415, -5112, -3700, -1211, 407, 3013, + 763, 591, 2481, -2657, 5210, 784, 0, 0, + -156, 338, -4246, 510, 462, 3296, 2846, 3333, + -4292, 4574, 1940, -2986, -1275, 3701, 5022, -5250, + 5780, -2676, -1180, 1516, -4852, 4877, 342, -3923, + -5703, -2920, 379, -657, -361, -3346, 1044, 795, + 5257, -4004, 698, 1115, 0, 0, 47, -140, + -3292, -1097, 652, 855, -5260, -3691, -4470, 4521, + -3863, 1093, -5552, -2016, 3831, 334, -456, -1532, + 2068, 1788, 2054, -295, 3668, -2820, 328, -994, + 295, -3301, 5770, 4282, -6353, 5632, -1371, 5005, + 238, 4041, 0, 0, 6764, -1659, -2730, 5726, + 3715, -3216, -933, 531, -52, -345, 3022, -2818, + 4005, -1617, -1189, -3748, -3403, -3592, 4040, -3553, + -2806, -3444, 6023, -711, -3298, -2503, 2548, 5564, + 940, 1848, 1207, 4010, -3488, -358, -2511, -1966, + 0, 0, -64, -5039, 1403, -4455, 6240, 2189, + -1716, -4348, 4183, 3951, -4042, -3606, 2399, -4563, + 4050, -612, -395, 348, -5791, 391, -1440, -735, + 1398, 4359, -518, 2969, 6556, 1951, -518, -4993, + -925, 998, -569, -2934, 3460, 420, 0, 0, + 16, 5482, -4122, 770, 2082, 5020, -3961, 485, + -584, -793, 3, 5222, -1416, 3673, 78, 3549, + -937, -5723, 1673, -6162, -2540, 3082, -355, 1838, + -615, 4601, 2832, -359, -3346, 668, -3393, -1583, + -3774, -2206, 5754, -4961, 0, 0, -328, 299, + 2470, 317, 525, -4494, 2805, 2617, 2383, -2363, + -1037, 4085, 895, -4622, 3218, -6607, -3381, -5933, + 1397, 6394, -446, 5694, 14, -4510, 4329, 3690, + -334, 0, 2932, -2478, -2944, -577, -599, -230, + 1553, -4736, 0, 0, -324, 142, -3252, -867, + 1111, -1882, 3378, -6055, 6502, -6840, 4280, -2694, + -2876, 4190, 6454, 655, 1061, 626, -2669, -798, + 3192, -985, -898, -5482, -548, 2315, -558, 1302, + 900, 5747, -1325, 1599, -1384, -5749, 624, 1110, + 0, 0, 321, 312, 2188, 1322, 237, 708, + -304, 2463, 1500, -1094, -5112, -1010, -6799, 646, + 992, 1969, 3423, -3996, 2628, 4451, 3432, -2833, + -6101, -330, -3768, -3, -707, 5961, -4037, -3736, + 4080, 7254, -4113, 2151, 54, -2150, 0, 0, + 7735, 4064, -3884, -5240, 577, 2229, -3947, 2914, + 3555, 4011, 774, -3519, 1985, -3701, -3824, 330, + -905, 2085, 1155, 2176, 3006, 340, -5533, -3264, + -902, 3114, 344, -5060, 1524, 1805, 1926, 2350, + 1905, -3203, -2762, -4162, 0, 0, 193, -151, + -1434, 6289, 7354, 4234, 169, 2868, -1977, -1375, + -4987, 2345, 2742, 599, 939, -4837, 2688, 991, + -6907, 716, -1542, -4346, -1833, 1493, 3134, 2903, + -7019, -2835, 93, 4395, 621, 870, -2357, -975, + -2933, -127, 0, 0, -616, -5968, -3479, -1651, + 4932, -2445, -5512, -1451, 691, 739, 479, 4227, + -2886, 3853, 8, -501, 188, 1990, 3842, 2270, + 1662, -174, 1290, 2456, 67, -3267, -5535, 483, + 5721, -1642, 6501, -3432, 1184, -3246, 4101, -4880, + 0, 0, -465, 5264, -4812, 682, 1683, -4539, + 2916, -1985, 2899, 3324, 1060, -4398, -745, -2137, + -3827, 1044, 6225, 3609, -532, 1980, -6001, 564, + -209, -1299, 5336, -3605, -1484, 37, 19, -1295, + -665, -385, -6773, 3651, 6153, -1291, 0, 0, + 193, -415, 5166, -110, 626, 6743, -2860, 1425, + 1101, -1341, 80, -4533, 249, 4231, -119, -6009, + -2970, 5170, -822, -2610, 4527, 5948, 182, -2589, + 837, -5471, 371, -43, 373, -665, -1233, -626, + -7353, 2606, 1339, -1398, 0, 0, -533, 147, + 2075, -672, 1043, 3503, 4402, -4971, -3287, 3731, + -2606, 3817, 1972, -5603, 5114, 1185, -1318, 1906, + 3018, -1999, 343, -1943, 207, -6744, 913, -4060, + 645, -349, -5667, 4766, 5575, -1733, 1116, 160, + 1534, -5690, 0, 0, -137, -36, 1556, 1325, + 1553, -2230, 1188, 5296, -5104, 4673, 6295, 498, + -4723, 933, 2994, 4067, -4700, 1758, -4116, -1252, + 2444, -4092, 1653, -2802, 5069, 1133, 790, -2355, + -934, -6304, 1642, 2045, -4259, -3873, -213, 215, + 0, 0, -364, 423, 4888, -1316, 118, -950, + 4027, 114, 2961, -3136, -3012, -883, -6192, 1340, + -3210, -1193, 1376, 3128, 1596, -2994, -3194, 533, + 8502, 2487, -1485, 1032, 301, -8007, -577, 887, + 297, 7778, 3121, -1901, -94, -6401, 0, 0, + 9260, -1845, 668, 2787, -2255, 2699, -2512, -3737, + -3675, -3601, -1803, 210, -1701, -1442, -2700, 3457, + 2868, 2079, -2113, 3178, 1277, 3578, 5240, -2482, + 3324, 1020, -4027, 3835, -3758, -3633, -3170, -1310, + 2509, -3110, 713, 174, 0, 0, -399, 4969, + -2321, -7744, 6494, -3776, 1478, 758, -1794, -2233, + -4059, 4932, 2770, 4761, -3475, 1243, 829, -651, + -5358, -436, 2381, 1360, 2561, -3118, 858, -4366, + 3933, 3646, -43, -1310, -16, 924, 1197, 1415, + -5036, -376, 0, 0, 100, 1410, 1290, 3199, + 7091, -3638, -2641, 1118, 45, -441, 794, -974, + -5033, 889, 438, -3102, 895, 3555, 4672, 4795, + 1129, -2408, -2153, 1742, 159, -2040, 7578, -2006, + -5737, 1986, -5568, -6413, 2428, -1387, -2441, 667, + 0, 0, -37, -6031, -4434, -904, 3290, 1806, + 4736, 2516, -5905, -5927, 1754, -4300, -2468, -2203, + -4836, -672, 1444, -1591, -1631, -1789, 4311, -153, + -688, -1222, 1058, 3139, 4659, -353, 1543, 1838, + 2180, -1448, 2432, 6277, 5304, -1692, 0, 0, + -280, 4506, 807, -477, 823, 3550, 1427, -1856, + -3003, -3501, -1203, 2679, 933, 778, -4954, -1977, + -7458, 4687, 435, 7045, -4053, -3130, 257, -3917, + -6165, 1889, 927, 235, 1889, -1097, 1985, 630, + -2172, -2130, 7080, 4810, 0, 0, -300, 496, + 2808, 279, 667, -7179, -2661, -526, -2832, 1751, + 2849, 4829, -906, -4151, -1124, -3062, 8166, 5361, + -1656, -6017, 3265, 2551, -864, -432, -6966, 6295, + -168, 901, 442, -582, 269, 236, -3574, 799, + 472, 565, 0, 0, 805, -2466, 6208, -4592, + -170, -6701, -5610, 3678, -4242, 4561, -724, -5534, + 2415, 7354, 2761, 2699, -349, 3822, -2372, 1756, + -5523, -3445, -588, -5749, -3986, 9804, -3871, 5375, + -2308, 5504, -2766, -1651, 1472, 6832, 2705, -5104, + 0, 0, -700, -1179, 4402, 400, 1383, 939, + -1342, 6013, 2577, -3472, 472, 2883, 1450, -3917, + 2849, 5084, 4990, 5392, 342, -4925, -3329, -5372, + -2674, -6035, -5072, -836, 179, 2506, 7987, -3647, + -8202, -1437, 1891, 2400, 1607, -3611, 0, 0, + -4706, -4003, 9928, -379, 5557, 3738, -8789, 685, + 1937, -5157, 13388, 7995, -4119, -9909, -5079, 4804, + 5586, 774, -5430, 299, -9943, 3264, -3690, -3901, + -1133, -6199, 3182, 1544, 5467, 3686, -2639, 4068, + 1163, -185, -1299, -506, 0, 0, 843, 1005, + -1059, 467, -1279, -2259, 6057, -1694, -5885, 5342, + -5160, -3748, -1382, 4420, -697, -2000, -3808, 3100, + 2685, -4073, 531, 318, -7822, 2414, 2901, 3399, + -1340, 8449, 3685, 463, -3341, 2423, 2304, -2723, + 84, -2622, 0, 0, 12088, -265, 2562, -435, + -4348, -2426, 3538, 1552, 1279, 883, -4166, 2634, + -6130, 2994, 3729, -1570, -601, -1753, -5124, -2788, + -2096, -1920, -2649, 2793, -1079, -1952, 2983, -1530, + 2499, 1769, 1492, -6757, -2108, 2841, 1466, 2597, + 0, 0, -3830, -4093, 2448, 12720, 7737, -665, + -832, -9257, 2971, -2400, 791, 1873, 1072, -587, + -7440, 8055, 1531, -4736, 616, -1782, -2982, 9663, + -5057, -5926, 1610, -4489, 7033, -8658, 6010, -5673, + 5648, 812, -271, -1802, -4500, 4392, 0, 0, + -888, -327, 3373, -1084, 7959, 2430, 1898, -2360, + -1820, -1377, -1090, -4436, -3422, -1106, -3230, 3876, + -41, -5128, 6375, -1848, -3824, 5844, 617, -1957, + 4232, 1345, -1439, -83, 3046, -214, 5458, -5566, + -4387, -3738, -5740, 8657, 0, 0, 6978, 6239, + -3686, -981, -2854, 78, 5859, -357, 4618, 7391, + -138, 971, -5799, 2135, 4478, -7004, -5949, 1668, + -6933, -1163, 7010, -5624, 2990, 6192, -8075, 3567, + -8308, 2236, -5098, -2120, -4355, -4238, 4955, 10230, + 692, -5606, 0, 0, -1348, -7069, -12, -4927, + 1211, 651, 1360, 7744, 3404, 5069, -2438, -105, + 2332, 1494, -4686, 1336, -3628, -881, 2474, 1736, + -26, -257, 2135, -4452, 446, -641, -4704, 2605, + -6436, 6662, -4939, 990, -1100, -3782, 5028, 4753, + 0, 0, -2875, 6410, 3518, 3950, 1271, 869, + -2842, -5837, 1532, -2899, 1140, -597, 1712, -1988, + -4819, -4783, 4773, -8796, 2240, -4596, 3565, -4853, + -556, -3974, 7366, -4370, 3113, -3548, 3552, -5450, + 3869, 2514, 6736, -4570, 6074, 3151, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 32767, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 32767 +}; diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mapping_matrix.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mapping_matrix.h new file mode 100644 index 0000000000..53646cb19f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mapping_matrix.h @@ -0,0 +1,145 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file mapping_matrix.h + * @brief Opus reference implementation mapping matrix API + */ + +#ifndef MAPPING_MATRIX_H +#define MAPPING_MATRIX_H + +#include "opus_types.h" +#include "opus_projection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MappingMatrix +{ + int rows; /* number of channels outputted from matrix. */ + int cols; /* number of channels inputted to matrix. */ + int gain; /* in dB. S7.8-format. */ + /* Matrix cell data goes here using col-wise ordering. */ +} MappingMatrix; + +opus_int32 mapping_matrix_get_size(int rows, int cols); + +opus_int16 *mapping_matrix_get_data(const MappingMatrix *matrix); + +void mapping_matrix_init( + MappingMatrix * const matrix, + int rows, + int cols, + int gain, + const opus_int16 *data, + opus_int32 data_size +); + +#ifndef DISABLE_FLOAT_API +void mapping_matrix_multiply_channel_in_float( + const MappingMatrix *matrix, + const float *input, + int input_rows, + opus_val16 *output, + int output_row, + int output_rows, + int frame_size +); + +void mapping_matrix_multiply_channel_out_float( + const MappingMatrix *matrix, + const opus_val16 *input, + int input_row, + int input_rows, + float *output, + int output_rows, + int frame_size +); +#endif /* DISABLE_FLOAT_API */ + +void mapping_matrix_multiply_channel_in_short( + const MappingMatrix *matrix, + const opus_int16 *input, + int input_rows, + opus_val16 *output, + int output_row, + int output_rows, + int frame_size +); + +void mapping_matrix_multiply_channel_out_short( + const MappingMatrix *matrix, + const opus_val16 *input, + int input_row, + int input_rows, + opus_int16 *output, + int output_rows, + int frame_size +); + +/* Pre-computed mixing and demixing matrices for 1st to 3rd-order ambisonics. + * foa: first-order ambisonics + * soa: second-order ambisonics + * toa: third-order ambisonics + */ +extern const MappingMatrix mapping_matrix_foa_mixing; +extern const opus_int16 mapping_matrix_foa_mixing_data[36]; + +extern const MappingMatrix mapping_matrix_soa_mixing; +extern const opus_int16 mapping_matrix_soa_mixing_data[121]; + +extern const MappingMatrix mapping_matrix_toa_mixing; +extern const opus_int16 mapping_matrix_toa_mixing_data[324]; + +extern const MappingMatrix mapping_matrix_fourthoa_mixing; +extern const opus_int16 mapping_matrix_fourthoa_mixing_data[729]; + +extern const MappingMatrix mapping_matrix_fifthoa_mixing; +extern const opus_int16 mapping_matrix_fifthoa_mixing_data[1444]; + +extern const MappingMatrix mapping_matrix_foa_demixing; +extern const opus_int16 mapping_matrix_foa_demixing_data[36]; + +extern const MappingMatrix mapping_matrix_soa_demixing; +extern const opus_int16 mapping_matrix_soa_demixing_data[121]; + +extern const MappingMatrix mapping_matrix_toa_demixing; +extern const opus_int16 mapping_matrix_toa_demixing_data[324]; + +extern const MappingMatrix mapping_matrix_fourthoa_demixing; +extern const opus_int16 mapping_matrix_fourthoa_demixing_data[729]; + +extern const MappingMatrix mapping_matrix_fifthoa_demixing; +extern const opus_int16 mapping_matrix_fifthoa_demixing_data[1444]; + +#ifdef __cplusplus +} +#endif + +#endif /* MAPPING_MATRIX_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/meson.build b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/meson.build new file mode 100644 index 0000000000..5537c67cef --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/meson.build @@ -0,0 +1,45 @@ +opus_sources = sources['OPUS_SOURCES'] + +opus_sources_float = sources['OPUS_SOURCES_FLOAT'] + +if not disable_float_api + opus_sources += opus_sources_float +endif + +opus_lib_c_args = [] +if host_machine.system() == 'windows' + opus_lib_c_args += ['-DDLL_EXPORT'] +endif + +opus_lib = library('opus', + opus_sources, + version: libversion, + darwin_versions: macosversion, + c_args: opus_lib_c_args, + include_directories: opus_includes, + link_whole: [celt_lib, silk_lib, dnn_lib], + dependencies: libm, + install: true) + +opus_dep = declare_dependency(link_with: opus_lib, + include_directories: opus_public_includes) + +# Extra uninstalled Opus programs +if not extra_programs.disabled() + foreach prog : ['opus_compare', 'opus_demo', 'repacketizer_demo'] + executable(prog, '@0@.c'.format(prog), + include_directories: opus_includes, + link_with: opus_lib, + dependencies: libm, + install: false) + endforeach + + if opt_custom_modes + executable('opus_custom_demo', '../celt/opus_custom_demo.c', + include_directories: opus_includes, + link_with: opus_lib, + dependencies: libm, + install: false) + endif + +endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mlp.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mlp.c new file mode 100644 index 0000000000..e658ccde0d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/mlp.c @@ -0,0 +1,132 @@ +/* Copyright (c) 2008-2011 Octasic Inc. + 2012-2017 Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "opus_types.h" +#include "opus_defines.h" +#include "arch.h" +#include "mlp.h" + +#define fmadd(a, b, c) ((a)*(b)+(c)) +static OPUS_INLINE float tansig_approx(float x) +{ + const float N0 = 952.52801514f; + const float N1 = 96.39235687f; + const float N2 = 0.60863042f; + const float D0 = 952.72399902f; + const float D1 = 413.36801147f; + const float D2 = 11.88600922f; + float X2, num, den; + X2 = x*x; + num = fmadd(fmadd(N2, X2, N1), X2, N0); + den = fmadd(fmadd(D2, X2, D1), X2, D0); + num = num*x/den; + return MAX32(-1.f, MIN32(1.f, num)); +} + +static OPUS_INLINE float sigmoid_approx(float x) +{ + return .5f + .5f*tansig_approx(.5f*x); +} + +static void gemm_accum(float *out, const opus_int8 *weights, int rows, int cols, int col_stride, const float *x) +{ + int i, j; + for (i=0;inb_inputs; + N = layer->nb_neurons; + stride = N; + for (i=0;ibias[i]; + gemm_accum(output, layer->input_weights, N, M, stride, input); + for (i=0;isigmoid) { + for (i=0;inb_inputs; + N = gru->nb_neurons; + stride = 3*N; + /* Compute update gate. */ + for (i=0;ibias[i]; + gemm_accum(z, gru->input_weights, N, M, stride, input); + gemm_accum(z, gru->recurrent_weights, N, N, stride, state); + for (i=0;ibias[N + i]; + gemm_accum(r, &gru->input_weights[N], N, M, stride, input); + gemm_accum(r, &gru->recurrent_weights[N], N, N, stride, state); + for (i=0;ibias[2*N + i]; + for (i=0;iinput_weights[2*N], N, M, stride, input); + gemm_accum(h, &gru->recurrent_weights[2*N], N, N, stride, tmp); + for (i=0;i=0) + break; + x[i*C] = x[i*C]+a*x[i*C]*x[i*C]; + } + + curr=0; + x0 = x[0]; + while(1) + { + int start, end; + float maxval; + int special=0; + int peak_pos; + for (i=curr;i1 || x[i*C]<-1) + break; + } + if (i==N) + { + a=0; + break; + } + peak_pos = i; + start=end=i; + maxval=ABS16(x[i*C]); + /* Look for first zero crossing before clipping */ + while (start>0 && x[i*C]*x[(start-1)*C]>=0) + start--; + /* Look for first zero crossing after clipping */ + while (end=0) + { + /* Look for other peaks until the next zero-crossing. */ + if (ABS16(x[end*C])>maxval) + { + maxval = ABS16(x[end*C]); + peak_pos = end; + } + end++; + } + /* Detect the special case where we clip before the first zero crossing */ + special = (start==0 && x[i*C]*x[0]>=0); + + /* Compute a such that maxval + a*maxval^2 = 1 */ + a=(maxval-1)/(maxval*maxval); + /* Slightly boost "a" by 2^-22. This is just enough to ensure -ffast-math + does not cause output values larger than +/-1, but small enough not + to matter even for 24-bit output. */ + a += a*2.4e-7f; + if (x[i*C]>0) + a = -a; + /* Apply soft clipping */ + for (i=start;i=2) + { + /* Add a linear ramp from the first sample to the signal peak. + This avoids a discontinuity at the beginning of the frame. */ + float delta; + float offset = x0-x[0]; + delta = offset / peak_pos; + for (i=curr;i>2; + return 2; + } +} + +static int parse_size(const unsigned char *data, opus_int32 len, opus_int16 *size) +{ + if (len<1) + { + *size = -1; + return -1; + } else if (data[0]<252) + { + *size = data[0]; + return 1; + } else if (len<2) + { + *size = -1; + return -1; + } else { + *size = 4*data[1] + data[0]; + return 2; + } +} + +int opus_packet_get_samples_per_frame(const unsigned char *data, + opus_int32 Fs) +{ + int audiosize; + if (data[0]&0x80) + { + audiosize = ((data[0]>>3)&0x3); + audiosize = (Fs<>3)&0x3); + if (audiosize == 3) + audiosize = Fs*60/1000; + else + audiosize = (Fs< len) + return OPUS_INVALID_PACKET; + data += bytes; + last_size = len-size[0]; + break; + /* Multiple CBR/VBR frames (from 0 to 120 ms) */ + default: /*case 3:*/ + if (len<1) + return OPUS_INVALID_PACKET; + /* Number of frames encoded in bits 0 to 5 */ + ch = *data++; + count = ch&0x3F; + if (count <= 0 || framesize*(opus_int32)count > 5760) + return OPUS_INVALID_PACKET; + len--; + /* Padding flag is bit 6 */ + if (ch&0x40) + { + int p; + do { + int tmp; + if (len<=0) + return OPUS_INVALID_PACKET; + p = *data++; + len--; + tmp = p==255 ? 254: p; + len -= tmp; + pad += tmp; + } while (p==255); + } + if (len<0) + return OPUS_INVALID_PACKET; + /* VBR flag is bit 7 */ + cbr = !(ch&0x80); + if (!cbr) + { + /* VBR case */ + last_size = len; + for (i=0;i len) + return OPUS_INVALID_PACKET; + data += bytes; + last_size -= bytes+size[i]; + } + if (last_size<0) + return OPUS_INVALID_PACKET; + } else if (!self_delimited) + { + /* CBR case */ + last_size = len/count; + if (last_size*count!=len) + return OPUS_INVALID_PACKET; + for (i=0;i len) + return OPUS_INVALID_PACKET; + data += bytes; + /* For CBR packets, apply the size to all the frames. */ + if (cbr) + { + if (size[count-1]*count > len) + return OPUS_INVALID_PACKET; + for (i=0;i last_size) + return OPUS_INVALID_PACKET; + } else + { + /* Because it's not encoded explicitly, it's possible the size of the + last packet (or all the packets, for the CBR case) is larger than + 1275. Reject them here.*/ + if (last_size > 1275) + return OPUS_INVALID_PACKET; + size[count-1] = (opus_int16)last_size; + } + + if (payload_offset) + *payload_offset = (int)(data-data0); + + for (i=0;i +#include +#include +#include + +#define OPUS_PI (3.14159265F) + +#define OPUS_COSF(_x) ((float)cos(_x)) +#define OPUS_SINF(_x) ((float)sin(_x)) + +static void *check_alloc(void *_ptr){ + if(_ptr==NULL){ + fprintf(stderr,"Out of memory.\n"); + exit(EXIT_FAILURE); + } + return _ptr; +} + +static void *opus_malloc(size_t _size){ + return check_alloc(malloc(_size)); +} + +static void *opus_realloc(void *_ptr,size_t _size){ + return check_alloc(realloc(_ptr,_size)); +} + +static size_t read_pcm16(float **_samples,FILE *_fin,int _nchannels){ + unsigned char buf[1024]; + float *samples; + size_t nsamples; + size_t csamples; + size_t xi; + size_t nread; + samples=NULL; + nsamples=csamples=0; + for(;;){ + nread=fread(buf,2*_nchannels,1024/(2*_nchannels),_fin); + if(nread<=0)break; + if(nsamples+nread>csamples){ + do csamples=csamples<<1|1; + while(nsamples+nread>csamples); + samples=(float *)opus_realloc(samples, + _nchannels*csamples*sizeof(*samples)); + } + for(xi=0;xi=_window_sz)ti-=_window_sz; + } + re*=_downsample; + im*=_downsample; + _ps[(xi*ps_sz+xj)*_nchannels+ci]=re*re+im*im+100000; + p[ci]+=_ps[(xi*ps_sz+xj)*_nchannels+ci]; + } + } + if(_out){ + _out[(xi*_nbands+bi)*_nchannels]=p[0]/(_bands[bi+1]-_bands[bi]); + if(_nchannels==2){ + _out[(xi*_nbands+bi)*_nchannels+1]=p[1]/(_bands[bi+1]-_bands[bi]); + } + } + } + } + free(window); +} + +#define NBANDS (21) +#define NFREQS (240) + +/*Bands on which we compute the pseudo-NMR (Bark-derived + CELT bands).*/ +static const int BANDS[NBANDS+1]={ + 0,2,4,6,8,10,12,14,16,20,24,28,32,40,48,56,68,80,96,120,156,200 +}; + +#define TEST_WIN_SIZE (480) +#define TEST_WIN_STEP (120) + +int main(int _argc,const char **_argv){ + FILE *fin1; + FILE *fin2; + float *x; + float *y; + float *xb; + float *X; + float *Y; + double err; + float Q; + size_t xlength; + size_t ylength; + size_t nframes; + size_t xi; + int ci; + int xj; + int bi; + int nchannels; + unsigned rate; + int downsample; + int ybands; + int yfreqs; + int max_compare; + if(_argc<3||_argc>6){ + fprintf(stderr,"Usage: %s [-s] [-r rate2] \n", + _argv[0]); + return EXIT_FAILURE; + } + nchannels=1; + if(strcmp(_argv[1],"-s")==0){ + nchannels=2; + _argv++; + } + rate=48000; + ybands=NBANDS; + yfreqs=NFREQS; + downsample=1; + if(strcmp(_argv[1],"-r")==0){ + rate=atoi(_argv[2]); + if(rate!=8000&&rate!=12000&&rate!=16000&&rate!=24000&&rate!=48000){ + fprintf(stderr, + "Sampling rate must be 8000, 12000, 16000, 24000, or 48000\n"); + return EXIT_FAILURE; + } + downsample=48000/rate; + switch(rate){ + case 8000:ybands=13;break; + case 12000:ybands=15;break; + case 16000:ybands=17;break; + case 24000:ybands=19;break; + } + yfreqs=NFREQS/downsample; + _argv+=2; + } + fin1=fopen(_argv[1],"rb"); + if(fin1==NULL){ + fprintf(stderr,"Error opening '%s'.\n",_argv[1]); + return EXIT_FAILURE; + } + fin2=fopen(_argv[2],"rb"); + if(fin2==NULL){ + fprintf(stderr,"Error opening '%s'.\n",_argv[2]); + fclose(fin1); + return EXIT_FAILURE; + } + /*Read in the data and allocate scratch space.*/ + xlength=read_pcm16(&x,fin1,2); + if(nchannels==1){ + for(xi=0;xi0;){ + for(ci=0;ci0){ + /*Temporal masking: -3 dB/2.5ms slope.*/ + for(bi=0;bi=79&&xj<=81)im*=0.1F; + if(xj==80)im*=0.1F; + Eb+=im; + } + } + Eb /= (BANDS[bi+1]-BANDS[bi])*nchannels; + Ef += Eb*Eb; + } + /*Using a fixed normalization value means we're willing to accept slightly + lower quality for lower sampling rates.*/ + Ef/=NBANDS; + Ef*=Ef; + err+=Ef*Ef; + } + free(xb); + free(X); + free(Y); + err=pow(err/nframes,1.0/16); + Q=100*(1-0.5*log(1+err)/log(1.13)); + if(Q<0){ + fprintf(stderr,"Test vector FAILS\n"); + fprintf(stderr,"Internal weighted error is %f\n",err); + return EXIT_FAILURE; + } + else{ + fprintf(stderr,"Test vector PASSES\n"); + fprintf(stderr, + "Opus quality metric: %.1f %% (internal weighted error is %f)\n",Q,err); + return EXIT_SUCCESS; + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_decoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_decoder.c new file mode 100644 index 0000000000..b57c809434 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_decoder.c @@ -0,0 +1,1508 @@ +/* Copyright (c) 2010 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifndef OPUS_BUILD +# error "OPUS_BUILD _MUST_ be defined to build Opus. This probably means you need other defines as well, as in a config.h. See the included build files for details." +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 2) && !defined(__OPTIMIZE__) && !defined(OPUS_WILL_BE_SLOW) +# pragma message "You appear to be compiling without optimization, if so opus will be very slow." +#endif + +#include +#include "celt.h" +#include "opus.h" +#include "entdec.h" +#include "modes.h" +#include "API.h" +#include "stack_alloc.h" +#include "float_cast.h" +#include "opus_private.h" +#include "os_support.h" +#include "structs.h" +#include "define.h" +#include "mathops.h" +#include "cpu_support.h" + +#ifdef ENABLE_DEEP_PLC +#include "dred_rdovae_dec_data.h" +#include "dred_rdovae_dec.h" +#endif + +#ifdef ENABLE_OSCE +#include "osce.h" +#endif + +struct OpusDecoder { + int celt_dec_offset; + int silk_dec_offset; + int channels; + opus_int32 Fs; /** Sampling rate (at the API level) */ + silk_DecControlStruct DecControl; + int decode_gain; + int complexity; + int arch; +#ifdef ENABLE_DEEP_PLC + LPCNetPLCState lpcnet; +#endif + + /* Everything beyond this point gets cleared on a reset */ +#define OPUS_DECODER_RESET_START stream_channels + int stream_channels; + + int bandwidth; + int mode; + int prev_mode; + int frame_size; + int prev_redundancy; + int last_packet_duration; +#ifndef FIXED_POINT + opus_val16 softclip_mem[2]; +#endif + + opus_uint32 rangeFinal; +}; + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +static void validate_opus_decoder(OpusDecoder *st) +{ + celt_assert(st->channels == 1 || st->channels == 2); + celt_assert(st->Fs == 48000 || st->Fs == 24000 || st->Fs == 16000 || st->Fs == 12000 || st->Fs == 8000); + celt_assert(st->DecControl.API_sampleRate == st->Fs); + celt_assert(st->DecControl.internalSampleRate == 0 || st->DecControl.internalSampleRate == 16000 || st->DecControl.internalSampleRate == 12000 || st->DecControl.internalSampleRate == 8000); + celt_assert(st->DecControl.nChannelsAPI == st->channels); + celt_assert(st->DecControl.nChannelsInternal == 0 || st->DecControl.nChannelsInternal == 1 || st->DecControl.nChannelsInternal == 2); + celt_assert(st->DecControl.payloadSize_ms == 0 || st->DecControl.payloadSize_ms == 10 || st->DecControl.payloadSize_ms == 20 || st->DecControl.payloadSize_ms == 40 || st->DecControl.payloadSize_ms == 60); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif + celt_assert(st->stream_channels == 1 || st->stream_channels == 2); +} +#define VALIDATE_OPUS_DECODER(st) validate_opus_decoder(st) +#else +#define VALIDATE_OPUS_DECODER(st) +#endif + +int opus_decoder_get_size(int channels) +{ + int silkDecSizeBytes, celtDecSizeBytes; + int ret; + if (channels<1 || channels > 2) + return 0; + ret = silk_Get_Decoder_Size( &silkDecSizeBytes ); + if(ret) + return 0; + silkDecSizeBytes = align(silkDecSizeBytes); + celtDecSizeBytes = celt_decoder_get_size(channels); + return align(sizeof(OpusDecoder))+silkDecSizeBytes+celtDecSizeBytes; +} + +int opus_decoder_init(OpusDecoder *st, opus_int32 Fs, int channels) +{ + void *silk_dec; + CELTDecoder *celt_dec; + int ret, silkDecSizeBytes; + + if ((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000) + || (channels!=1&&channels!=2)) + return OPUS_BAD_ARG; + + OPUS_CLEAR((char*)st, opus_decoder_get_size(channels)); + /* Initialize SILK decoder */ + ret = silk_Get_Decoder_Size(&silkDecSizeBytes); + if (ret) + return OPUS_INTERNAL_ERROR; + + silkDecSizeBytes = align(silkDecSizeBytes); + st->silk_dec_offset = align(sizeof(OpusDecoder)); + st->celt_dec_offset = st->silk_dec_offset+silkDecSizeBytes; + silk_dec = (char*)st+st->silk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + st->stream_channels = st->channels = channels; + st->complexity = 0; + + st->Fs = Fs; + st->DecControl.API_sampleRate = st->Fs; + st->DecControl.nChannelsAPI = st->channels; + + /* Reset decoder */ + ret = silk_InitDecoder( silk_dec ); + if(ret)return OPUS_INTERNAL_ERROR; + + /* Initialize CELT decoder */ + ret = celt_decoder_init(celt_dec, Fs, channels); + if(ret!=OPUS_OK)return OPUS_INTERNAL_ERROR; + + celt_decoder_ctl(celt_dec, CELT_SET_SIGNALLING(0)); + + st->prev_mode = 0; + st->frame_size = Fs/400; +#ifdef ENABLE_DEEP_PLC + lpcnet_plc_init( &st->lpcnet); +#endif + st->arch = opus_select_arch(); + return OPUS_OK; +} + +OpusDecoder *opus_decoder_create(opus_int32 Fs, int channels, int *error) +{ + int ret; + OpusDecoder *st; + if ((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000) + || (channels!=1&&channels!=2)) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + st = (OpusDecoder *)opus_alloc(opus_decoder_get_size(channels)); + if (st == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_decoder_init(st, Fs, channels); + if (error) + *error = ret; + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + return st; +} + +static void smooth_fade(const opus_val16 *in1, const opus_val16 *in2, + opus_val16 *out, int overlap, int channels, + const opus_val16 *window, opus_int32 Fs) +{ + int i, c; + int inc = 48000/Fs; + for (c=0;csilk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + F20 = st->Fs/50; + F10 = F20>>1; + F5 = F10>>1; + F2_5 = F5>>1; + if (frame_size < F2_5) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + /* Limit frame_size to avoid excessive stack allocations. */ + frame_size = IMIN(frame_size, st->Fs/25*3); + /* Payloads of 1 (2 including ToC) or 0 trigger the PLC/DTX */ + if (len<=1) + { + data = NULL; + /* In that case, don't conceal more than what the ToC says */ + frame_size = IMIN(frame_size, st->frame_size); + } + if (data != NULL) + { + audiosize = st->frame_size; + mode = st->mode; + bandwidth = st->bandwidth; + ec_dec_init(&dec,(unsigned char*)data,len); + } else { + audiosize = frame_size; + /* Run PLC using last used mode (CELT if we ended with CELT redundancy) */ + mode = st->prev_redundancy ? MODE_CELT_ONLY : st->prev_mode; + bandwidth = 0; + + if (mode == 0) + { + /* If we haven't got any packet yet, all we can do is return zeros */ + for (i=0;ichannels;i++) + pcm[i] = 0; + RESTORE_STACK; + return audiosize; + } + + /* Avoids trying to run the PLC on sizes other than 2.5 (CELT), 5 (CELT), + 10, or 20 (e.g. 12.5 or 30 ms). */ + if (audiosize > F20) + { + do { + int ret = opus_decode_frame(st, NULL, 0, pcm, IMIN(audiosize, F20), 0); + if (ret<0) + { + RESTORE_STACK; + return ret; + } + pcm += ret*st->channels; + audiosize -= ret; + } while (audiosize > 0); + RESTORE_STACK; + return frame_size; + } else if (audiosize < F20) + { + if (audiosize > F10) + audiosize = F10; + else if (mode != MODE_SILK_ONLY && audiosize > F5 && audiosize < F10) + audiosize = F5; + } + } + + /* In fixed-point, we can tell CELT to do the accumulation on top of the + SILK PCM buffer. This saves some stack space. */ +#ifdef FIXED_POINT + celt_accum = (mode != MODE_CELT_ONLY) && (frame_size >= F10); +#else + celt_accum = 0; +#endif + + pcm_transition_silk_size = ALLOC_NONE; + pcm_transition_celt_size = ALLOC_NONE; + if (data!=NULL && st->prev_mode > 0 && ( + (mode == MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY && !st->prev_redundancy) + || (mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) ) + ) + { + transition = 1; + /* Decide where to allocate the stack memory for pcm_transition */ + if (mode == MODE_CELT_ONLY) + pcm_transition_celt_size = F5*st->channels; + else + pcm_transition_silk_size = F5*st->channels; + } + ALLOC(pcm_transition_celt, pcm_transition_celt_size, opus_val16); + if (transition && mode == MODE_CELT_ONLY) + { + pcm_transition = pcm_transition_celt; + opus_decode_frame(st, NULL, 0, pcm_transition, IMIN(F5, audiosize), 0); + } + if (audiosize > frame_size) + { + /*fprintf(stderr, "PCM buffer too small: %d vs %d (mode = %d)\n", audiosize, frame_size, mode);*/ + RESTORE_STACK; + return OPUS_BAD_ARG; + } else { + frame_size = audiosize; + } + + /* Don't allocate any memory when in CELT-only mode */ + pcm_silk_size = (mode != MODE_CELT_ONLY && !celt_accum) ? IMAX(F10, frame_size)*st->channels : ALLOC_NONE; + ALLOC(pcm_silk, pcm_silk_size, opus_int16); + + /* SILK processing */ + if (mode != MODE_CELT_ONLY) + { + int lost_flag, decoded_samples; + opus_int16 *pcm_ptr; +#ifdef FIXED_POINT + if (celt_accum) + pcm_ptr = pcm; + else +#endif + pcm_ptr = pcm_silk; + + if (st->prev_mode==MODE_CELT_ONLY) + silk_ResetDecoder( silk_dec ); + + /* The SILK PLC cannot produce frames of less than 10 ms */ + st->DecControl.payloadSize_ms = IMAX(10, 1000 * audiosize / st->Fs); + + if (data != NULL) + { + st->DecControl.nChannelsInternal = st->stream_channels; + if( mode == MODE_SILK_ONLY ) { + if( bandwidth == OPUS_BANDWIDTH_NARROWBAND ) { + st->DecControl.internalSampleRate = 8000; + } else if( bandwidth == OPUS_BANDWIDTH_MEDIUMBAND ) { + st->DecControl.internalSampleRate = 12000; + } else if( bandwidth == OPUS_BANDWIDTH_WIDEBAND ) { + st->DecControl.internalSampleRate = 16000; + } else { + st->DecControl.internalSampleRate = 16000; + celt_assert( 0 ); + } + } else { + /* Hybrid mode */ + st->DecControl.internalSampleRate = 16000; + } + } + st->DecControl.enable_deep_plc = st->complexity >= 5; +#ifdef ENABLE_OSCE + st->DecControl.osce_method = OSCE_METHOD_NONE; +#ifndef DISABLE_LACE + if (st->complexity >= 6) {st->DecControl.osce_method = OSCE_METHOD_LACE;} +#endif +#ifndef DISABLE_NOLACE + if (st->complexity >= 7) {st->DecControl.osce_method = OSCE_METHOD_NOLACE;} +#endif +#endif + + lost_flag = data == NULL ? 1 : 2 * !!decode_fec; + decoded_samples = 0; + do { + /* Call SILK decoder */ + int first_frame = decoded_samples == 0; + silk_ret = silk_Decode( silk_dec, &st->DecControl, + lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size, +#ifdef ENABLE_DEEP_PLC + &st->lpcnet, +#endif + st->arch ); + if( silk_ret ) { + if (lost_flag) { + /* PLC failure should not be fatal */ + silk_frame_size = frame_size; + for (i=0;ichannels;i++) + pcm_ptr[i] = 0; + } else { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + } + pcm_ptr += silk_frame_size * st->channels; + decoded_samples += silk_frame_size; + } while( decoded_samples < frame_size ); + } + + start_band = 0; + if (!decode_fec && mode != MODE_CELT_ONLY && data != NULL + && ec_tell(&dec)+17+20*(mode == MODE_HYBRID) <= 8*len) + { + /* Check if we have a redundant 0-8 kHz band */ + if (mode == MODE_HYBRID) + redundancy = ec_dec_bit_logp(&dec, 12); + else + redundancy = 1; + if (redundancy) + { + celt_to_silk = ec_dec_bit_logp(&dec, 1); + /* redundancy_bytes will be at least two, in the non-hybrid + case due to the ec_tell() check above */ + redundancy_bytes = mode==MODE_HYBRID ? + (opus_int32)ec_dec_uint(&dec, 256)+2 : + len-((ec_tell(&dec)+7)>>3); + len -= redundancy_bytes; + /* This is a sanity check. It should never happen for a valid + packet, so the exact behaviour is not normative. */ + if (len*8 < ec_tell(&dec)) + { + len = 0; + redundancy_bytes = 0; + redundancy = 0; + } + /* Shrink decoder because of raw bits */ + dec.storage -= redundancy_bytes; + } + } + if (mode != MODE_CELT_ONLY) + start_band = 17; + + if (redundancy) + { + transition = 0; + pcm_transition_silk_size=ALLOC_NONE; + } + + ALLOC(pcm_transition_silk, pcm_transition_silk_size, opus_val16); + + if (transition && mode != MODE_CELT_ONLY) + { + pcm_transition = pcm_transition_silk; + opus_decode_frame(st, NULL, 0, pcm_transition, IMIN(F5, audiosize), 0); + } + + + if (bandwidth) + { + int endband=21; + + switch(bandwidth) + { + case OPUS_BANDWIDTH_NARROWBAND: + endband = 13; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + case OPUS_BANDWIDTH_WIDEBAND: + endband = 17; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + endband = 19; + break; + case OPUS_BANDWIDTH_FULLBAND: + endband = 21; + break; + default: + celt_assert(0); + break; + } + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_END_BAND(endband))); + } + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_CHANNELS(st->stream_channels))); + + /* Only allocation memory for redundancy if/when needed */ + redundant_audio_size = redundancy ? F5*st->channels : ALLOC_NONE; + ALLOC(redundant_audio, redundant_audio_size, opus_val16); + + /* 5 ms redundant frame for CELT->SILK*/ + if (redundancy && celt_to_silk) + { + /* If the previous frame did not use CELT (the first redundancy frame in + a transition from SILK may have been lost) then the CELT decoder is + stale at this point and the redundancy audio is not useful, however + the final range is still needed (for testing), so the redundancy is + always decoded but the decoded audio may not be used */ + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, + redundant_audio, F5, NULL, 0); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng))); + } + + /* MUST be after PLC */ + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(start_band))); + + if (mode != MODE_SILK_ONLY) + { + int celt_frame_size = IMIN(F20, frame_size); + /* Make sure to discard any previous CELT state */ + if (mode != st->prev_mode && st->prev_mode > 0 && !st->prev_redundancy) + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_RESET_STATE)); + /* Decode CELT */ + celt_ret = celt_decode_with_ec_dred(celt_dec, decode_fec ? NULL : data, + len, pcm, celt_frame_size, &dec, celt_accum +#ifdef ENABLE_DEEP_PLC + , &st->lpcnet +#endif + ); + } else { + unsigned char silence[2] = {0xFF, 0xFF}; + if (!celt_accum) + { + for (i=0;ichannels;i++) + pcm[i] = 0; + } + /* For hybrid -> SILK transitions, we let the CELT MDCT + do a fade-out by decoding a silence frame */ + if (st->prev_mode == MODE_HYBRID && !(redundancy && celt_to_silk && st->prev_redundancy) ) + { + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + celt_decode_with_ec(celt_dec, silence, 2, pcm, F2_5, NULL, celt_accum); + } + } + + if (mode != MODE_CELT_ONLY && !celt_accum) + { +#ifdef FIXED_POINT + for (i=0;ichannels;i++) + pcm[i] = SAT16(ADD32(pcm[i], pcm_silk[i])); +#else + for (i=0;ichannels;i++) + pcm[i] = pcm[i] + (opus_val16)((1.f/32768.f)*pcm_silk[i]); +#endif + } + + { + const CELTMode *celt_mode; + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_GET_MODE(&celt_mode))); + window = celt_mode->window; + } + + /* 5 ms redundant frame for SILK->CELT */ + if (redundancy && !celt_to_silk) + { + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_RESET_STATE)); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0))); + + celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, redundant_audio, F5, NULL, 0); + MUST_SUCCEED(celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng))); + smooth_fade(pcm+st->channels*(frame_size-F2_5), redundant_audio+st->channels*F2_5, + pcm+st->channels*(frame_size-F2_5), F2_5, st->channels, window, st->Fs); + } + /* 5ms redundant frame for CELT->SILK; ignore if the previous frame did not + use CELT (the first redundancy frame in a transition from SILK may have + been lost) */ + if (redundancy && celt_to_silk && (st->prev_mode != MODE_SILK_ONLY || st->prev_redundancy)) + { + for (c=0;cchannels;c++) + { + for (i=0;ichannels*i+c] = redundant_audio[st->channels*i+c]; + } + smooth_fade(redundant_audio+st->channels*F2_5, pcm+st->channels*F2_5, + pcm+st->channels*F2_5, F2_5, st->channels, window, st->Fs); + } + if (transition) + { + if (audiosize >= F5) + { + for (i=0;ichannels*F2_5;i++) + pcm[i] = pcm_transition[i]; + smooth_fade(pcm_transition+st->channels*F2_5, pcm+st->channels*F2_5, + pcm+st->channels*F2_5, F2_5, + st->channels, window, st->Fs); + } else { + /* Not enough time to do a clean transition, but we do it anyway + This will not preserve amplitude perfectly and may introduce + a bit of temporal aliasing, but it shouldn't be too bad and + that's pretty much the best we can do. In any case, generating this + transition it pretty silly in the first place */ + smooth_fade(pcm_transition, pcm, + pcm, F2_5, + st->channels, window, st->Fs); + } + } + + if(st->decode_gain) + { + opus_val32 gain; + gain = celt_exp2(MULT16_16_P15(QCONST16(6.48814081e-4f, 25), st->decode_gain)); + for (i=0;ichannels;i++) + { + opus_val32 x; + x = MULT16_32_P16(pcm[i],gain); + pcm[i] = SATURATE(x, 32767); + } + } + + if (len <= 1) + st->rangeFinal = 0; + else + st->rangeFinal = dec.rng ^ redundant_rng; + + st->prev_mode = mode; + st->prev_redundancy = redundancy && !celt_to_silk; + + if (celt_ret>=0) + { + if (OPUS_CHECK_ARRAY(pcm, audiosize*st->channels)) + OPUS_PRINT_INT(audiosize); + } + + RESTORE_STACK; + return celt_ret < 0 ? celt_ret : audiosize; + +} + +int opus_decode_native(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec, + int self_delimited, opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset) +{ + int i, nb_samples; + int count, offset; + unsigned char toc; + int packet_frame_size, packet_bandwidth, packet_mode, packet_stream_channels; + /* 48 x 2.5 ms = 120 ms */ + opus_int16 size[48]; + VALIDATE_OPUS_DECODER(st); + if (decode_fec<0 || decode_fec>1) + return OPUS_BAD_ARG; + /* For FEC/PLC, frame_size has to be to have a multiple of 2.5 ms */ + if ((decode_fec || len==0 || data==NULL) && frame_size%(st->Fs/400)!=0) + return OPUS_BAD_ARG; +#ifdef ENABLE_DRED + if (dred != NULL && dred->process_stage == 2) { + int F10; + int features_per_frame; + int needed_feature_frames; + int init_frames; + lpcnet_plc_fec_clear(&st->lpcnet); + F10 = st->Fs/100; + /* if blend==0, the last PLC call was "update" and we need to feed two extra 10-ms frames. */ + init_frames = (st->lpcnet.blend == 0) ? 2 : 0; + features_per_frame = IMAX(1, frame_size/F10); + needed_feature_frames = init_frames + features_per_frame; + lpcnet_plc_fec_clear(&st->lpcnet); + for (i=0;idred_offset*F10/4)/F10); + if (feature_offset <= 4*dred->nb_latents-1 && feature_offset >= 0) { + lpcnet_plc_fec_add(&st->lpcnet, dred->fec_features+feature_offset*DRED_NUM_FEATURES); + } else { + if (feature_offset >= 0) lpcnet_plc_fec_add(&st->lpcnet, NULL); + } + + } + } +#else + (void)dred; + (void)dred_offset; +#endif + if (len==0 || data==NULL) + { + int pcm_count=0; + do { + int ret; + ret = opus_decode_frame(st, NULL, 0, pcm+pcm_count*st->channels, frame_size-pcm_count, 0); + if (ret<0) + return ret; + pcm_count += ret; + } while (pcm_count < frame_size); + celt_assert(pcm_count == frame_size); + if (OPUS_CHECK_ARRAY(pcm, pcm_count*st->channels)) + OPUS_PRINT_INT(pcm_count); + st->last_packet_duration = pcm_count; + return pcm_count; + } else if (len<0) + return OPUS_BAD_ARG; + + packet_mode = opus_packet_get_mode(data); + packet_bandwidth = opus_packet_get_bandwidth(data); + packet_frame_size = opus_packet_get_samples_per_frame(data, st->Fs); + packet_stream_channels = opus_packet_get_nb_channels(data); + + count = opus_packet_parse_impl(data, len, self_delimited, &toc, NULL, + size, &offset, packet_offset, NULL, NULL); + if (count<0) + return count; + + data += offset; + + if (decode_fec) + { + int duration_copy; + int ret; + /* If no FEC can be present, run the PLC (recursive call) */ + if (frame_size < packet_frame_size || packet_mode == MODE_CELT_ONLY || st->mode == MODE_CELT_ONLY) + return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, soft_clip, NULL, 0); + /* Otherwise, run the PLC on everything except the size for which we might have FEC */ + duration_copy = st->last_packet_duration; + if (frame_size-packet_frame_size!=0) + { + ret = opus_decode_native(st, NULL, 0, pcm, frame_size-packet_frame_size, 0, 0, NULL, soft_clip, NULL, 0); + if (ret<0) + { + st->last_packet_duration = duration_copy; + return ret; + } + celt_assert(ret==frame_size-packet_frame_size); + } + /* Complete with FEC */ + st->mode = packet_mode; + st->bandwidth = packet_bandwidth; + st->frame_size = packet_frame_size; + st->stream_channels = packet_stream_channels; + ret = opus_decode_frame(st, data, size[0], pcm+st->channels*(frame_size-packet_frame_size), + packet_frame_size, 1); + if (ret<0) + return ret; + else { + if (OPUS_CHECK_ARRAY(pcm, frame_size*st->channels)) + OPUS_PRINT_INT(frame_size); + st->last_packet_duration = frame_size; + return frame_size; + } + } + + if (count*packet_frame_size > frame_size) + return OPUS_BUFFER_TOO_SMALL; + + /* Update the state as the last step to avoid updating it on an invalid packet */ + st->mode = packet_mode; + st->bandwidth = packet_bandwidth; + st->frame_size = packet_frame_size; + st->stream_channels = packet_stream_channels; + + nb_samples=0; + for (i=0;ichannels, frame_size-nb_samples, 0); + if (ret<0) + return ret; + celt_assert(ret==packet_frame_size); + data += size[i]; + nb_samples += ret; + } + st->last_packet_duration = nb_samples; + if (OPUS_CHECK_ARRAY(pcm, nb_samples*st->channels)) + OPUS_PRINT_INT(nb_samples); +#ifndef FIXED_POINT + if (soft_clip) + opus_pcm_soft_clip(pcm, nb_samples, st->channels, st->softclip_mem); + else + st->softclip_mem[0]=st->softclip_mem[1]=0; +#endif + return nb_samples; +} + +#ifdef FIXED_POINT + +int opus_decode(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) +{ + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0); +} + +#ifndef DISABLE_FLOAT_API +int opus_decode_float(OpusDecoder *st, const unsigned char *data, + opus_int32 len, float *pcm, int frame_size, int decode_fec) +{ + VARDECL(opus_int16, out); + int ret, i; + int nb_samples; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, opus_int16); + + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0, NULL, 0); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = (1.f/32768.f)*(out[i]); + } + RESTORE_STACK; + return ret; +} +#endif + + +#else +int opus_decode(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec) +{ + VARDECL(float, out); + int ret, i; + int nb_samples; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, float); + + ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1, NULL, 0); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = FLOAT2INT16(out[i]); + } + RESTORE_STACK; + return ret; +} + +int opus_decode_float(OpusDecoder *st, const unsigned char *data, + opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec) +{ + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, data, len, pcm, frame_size, decode_fec, 0, NULL, 0, NULL, 0); +} + +#endif + +int opus_decoder_ctl(OpusDecoder *st, int request, ...) +{ + int ret = OPUS_OK; + va_list ap; + void *silk_dec; + CELTDecoder *celt_dec; + + silk_dec = (char*)st+st->silk_dec_offset; + celt_dec = (CELTDecoder*)((char*)st+st->celt_dec_offset); + + + va_start(ap, request); + + switch (request) + { + case OPUS_GET_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->bandwidth; + } + break; + case OPUS_SET_COMPLEXITY_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>10) + { + goto bad_arg; + } + st->complexity = value; + celt_decoder_ctl(celt_dec, OPUS_SET_COMPLEXITY(value)); + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->complexity; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 *value = va_arg(ap, opus_uint32*); + if (!value) + { + goto bad_arg; + } + *value = st->rangeFinal; + } + break; + case OPUS_RESET_STATE: + { + OPUS_CLEAR((char*)&st->OPUS_DECODER_RESET_START, + sizeof(OpusDecoder)- + ((char*)&st->OPUS_DECODER_RESET_START - (char*)st)); + + celt_decoder_ctl(celt_dec, OPUS_RESET_STATE); + silk_ResetDecoder( silk_dec ); + st->stream_channels = st->channels; + st->frame_size = st->Fs/400; +#ifdef ENABLE_DEEP_PLC + lpcnet_plc_reset( &st->lpcnet ); +#endif + } + break; + case OPUS_GET_SAMPLE_RATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs; + } + break; + case OPUS_GET_PITCH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + if (st->prev_mode == MODE_CELT_ONLY) + ret = celt_decoder_ctl(celt_dec, OPUS_GET_PITCH(value)); + else + *value = st->DecControl.prevPitchLag; + } + break; + case OPUS_GET_GAIN_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->decode_gain; + } + break; + case OPUS_SET_GAIN_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<-32768 || value>32767) + { + goto bad_arg; + } + st->decode_gain = value; + } + break; + case OPUS_GET_LAST_PACKET_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->last_packet_duration; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + ret = celt_decoder_ctl(celt_dec, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + ret = celt_decoder_ctl(celt_dec, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; +#ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } + ret = lpcnet_plc_load_model(&st->lpcnet, data, len); + ret = silk_LoadOSCEModels(silk_dec, data, len) || ret; + } + break; +#endif + default: + /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + + va_end(ap); + return ret; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +} + +void opus_decoder_destroy(OpusDecoder *st) +{ + opus_free(st); +} + + +int opus_packet_get_bandwidth(const unsigned char *data) +{ + int bandwidth; + if (data[0]&0x80) + { + bandwidth = OPUS_BANDWIDTH_MEDIUMBAND + ((data[0]>>5)&0x3); + if (bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + bandwidth = OPUS_BANDWIDTH_NARROWBAND; + } else if ((data[0]&0x60) == 0x60) + { + bandwidth = (data[0]&0x10) ? OPUS_BANDWIDTH_FULLBAND : + OPUS_BANDWIDTH_SUPERWIDEBAND; + } else { + bandwidth = OPUS_BANDWIDTH_NARROWBAND + ((data[0]>>5)&0x3); + } + return bandwidth; +} + +int opus_packet_get_nb_channels(const unsigned char *data) +{ + return (data[0]&0x4) ? 2 : 1; +} + +int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) +{ + int count; + if (len<1) + return OPUS_BAD_ARG; + count = packet[0]&0x3; + if (count==0) + return 1; + else if (count!=3) + return 2; + else if (len<2) + return OPUS_INVALID_PACKET; + else + return packet[1]&0x3F; +} + +int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, + opus_int32 Fs) +{ + int samples; + int count = opus_packet_get_nb_frames(packet, len); + + if (count<0) + return count; + + samples = count*opus_packet_get_samples_per_frame(packet, Fs); + /* Can't have more than 120 ms */ + if (samples*25 > Fs*3) + return OPUS_INVALID_PACKET; + else + return samples; +} + +int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len) +{ + int ret; + const unsigned char *frames[48]; + opus_int16 size[48]; + int packet_mode, packet_frame_size, packet_stream_channels; + int nb_frames=1; + int lbrr; + + packet_mode = opus_packet_get_mode(packet); + if (packet_mode == MODE_CELT_ONLY) + return 0; + packet_frame_size = opus_packet_get_samples_per_frame(packet, 48000); + if (packet_frame_size > 960) + nb_frames = packet_frame_size/960; + packet_stream_channels = opus_packet_get_nb_channels(packet); + ret = opus_packet_parse(packet, len, NULL, frames, size, NULL); + if (ret <= 0) + return ret; + lbrr = (frames[0][0] >> (7-nb_frames)) & 0x1; + if (packet_stream_channels == 2) + lbrr = lbrr || ((frames[0][0] >> (6-2*nb_frames)) & 0x1); + return lbrr; +} + +int opus_decoder_get_nb_samples(const OpusDecoder *dec, + const unsigned char packet[], opus_int32 len) +{ + return opus_packet_get_nb_samples(packet, len, dec->Fs); +} + +struct OpusDREDDecoder { +#ifdef ENABLE_DRED + RDOVAEDec model; +#endif + int loaded; + int arch; + opus_uint32 magic; +}; + +#if defined(ENABLE_DRED) && (defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS)) +static void validate_dred_decoder(OpusDREDDecoder *st) +{ + celt_assert(st->magic == 0xD8EDDEC0); +#ifdef OPUS_ARCHMASK + celt_assert(st->arch >= 0); + celt_assert(st->arch <= OPUS_ARCHMASK); +#endif +} +#define VALIDATE_DRED_DECODER(st) validate_dred_decoder(st) +#else +#define VALIDATE_DRED_DECODER(st) +#endif + + +int opus_dred_decoder_get_size(void) +{ + return sizeof(OpusDREDDecoder); +} + +#ifdef ENABLE_DRED +int dred_decoder_load_model(OpusDREDDecoder *dec, const unsigned char *data, int len) +{ + WeightArray *list; + int ret; + parse_weights(&list, data, len); + ret = init_rdovaedec(&dec->model, list); + opus_free(list); + if (ret == 0) dec->loaded = 1; + return (ret == 0) ? OPUS_OK : OPUS_BAD_ARG; +} +#endif + +int opus_dred_decoder_init(OpusDREDDecoder *dec) +{ + int ret = 0; + dec->loaded = 0; +#if defined(ENABLE_DRED) && !defined(USE_WEIGHTS_FILE) + ret = init_rdovaedec(&dec->model, rdovaedec_arrays); + if (ret == 0) dec->loaded = 1; +#endif + dec->arch = opus_select_arch(); + /* To make sure nobody forgets to init, use a magic number. */ + dec->magic = 0xD8EDDEC0; + return (ret == 0) ? OPUS_OK : OPUS_UNIMPLEMENTED; +} + +OpusDREDDecoder *opus_dred_decoder_create(int *error) +{ + int ret; + OpusDREDDecoder *dec; + dec = (OpusDREDDecoder *)opus_alloc(opus_dred_decoder_get_size()); + if (dec == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_dred_decoder_init(dec); + if (error) + *error = ret; + if (ret != OPUS_OK) + { + opus_free(dec); + dec = NULL; + } + return dec; +} + +void opus_dred_decoder_destroy(OpusDREDDecoder *dec) +{ + if (dec) dec->magic = 0xDE57801D; + opus_free(dec); +} + +int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...) +{ +#ifdef ENABLE_DRED + int ret = OPUS_OK; + va_list ap; + + va_start(ap, request); + (void)dred_dec; + switch (request) + { +# ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } + return dred_decoder_load_model(dred_dec, data, len); + } + break; +# endif + default: + /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + va_end(ap); + return ret; +# ifdef USE_WEIGHTS_FILE +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +# endif +#else + (void)dred_dec; + (void)request; + return OPUS_UNIMPLEMENTED; +#endif +} + +#ifdef ENABLE_DRED +static int dred_find_payload(const unsigned char *data, opus_int32 len, const unsigned char **payload, int *dred_frame_offset) +{ + const unsigned char *data0; + int len0; + int frame = 0; + int ret; + const unsigned char *frames[48]; + opus_int16 size[48]; + int frame_size; + + *payload = NULL; + /* Get the padding section of the packet. */ + ret = opus_packet_parse_impl(data, len, 0, NULL, frames, size, NULL, NULL, &data0, &len0); + if (ret < 0) + return ret; + frame_size = opus_packet_get_samples_per_frame(data, 48000); + data = data0; + len = len0; + /* Scan extensions in order until we find the earliest frame with DRED data. */ + while (len > 0) + { + opus_int32 header_size; + int id, L; + len0 = len; + data0 = data; + id = *data0 >> 1; + L = *data0 & 0x1; + len = skip_extension(&data, len, &header_size); + if (len < 0) + break; + if (id == 1) + { + if (L==0) + { + frame++; + } else { + frame += data0[1]; + } + } else if (id == DRED_EXTENSION_ID) + { + const unsigned char *curr_payload; + opus_int32 curr_payload_len; + curr_payload = data0+header_size; + curr_payload_len = (data-data0)-header_size; + /* DRED position in the packet, in units of 2.5 ms like for the signaled DRED offset. */ + *dred_frame_offset = frame*frame_size/120; +#ifdef DRED_EXPERIMENTAL_VERSION + /* Check that temporary extension type and version match. + This check will be removed once extension is finalized. */ + if (curr_payload_len > DRED_EXPERIMENTAL_BYTES && curr_payload[0] == 'D' && curr_payload[1] == DRED_EXPERIMENTAL_VERSION) { + *payload = curr_payload+2; + return curr_payload_len-2; + } +#else + if (curr_payload_len > 0) { + *payload = curr_payload; + return curr_payload_len; + } +#endif + } + } + return 0; +} +#endif + +int opus_dred_get_size(void) +{ +#ifdef ENABLE_DRED + return sizeof(OpusDRED); +#else + return 0; +#endif +} + +OpusDRED *opus_dred_alloc(int *error) +{ +#ifdef ENABLE_DRED + OpusDRED *dec; + dec = (OpusDRED *)opus_alloc(opus_dred_get_size()); + if (dec == NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + return dec; +#else + if (error) + *error = OPUS_UNIMPLEMENTED; + return NULL; +#endif +} + +void opus_dred_free(OpusDRED *dec) +{ +#ifdef ENABLE_DRED + opus_free(dec); +#else + (void)dec; +#endif +} + +int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) +{ +#ifdef ENABLE_DRED + const unsigned char *payload; + opus_int32 payload_len; + int dred_frame_offset=0; + VALIDATE_DRED_DECODER(dred_dec); + if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED; + dred->process_stage = -1; + payload_len = dred_find_payload(data, len, &payload, &dred_frame_offset); + if (payload_len < 0) + return payload_len; + if (payload != NULL) + { + int offset; + int min_feature_frames; + offset = 100*max_dred_samples/sampling_rate; + min_feature_frames = IMIN(2 + offset, 2*DRED_NUM_REDUNDANCY_FRAMES); + dred_ec_decode(dred, payload, payload_len, min_feature_frames, dred_frame_offset); + if (!defer_processing) + opus_dred_process(dred_dec, dred, dred); + if (dred_end) *dred_end = IMAX(0, -dred->dred_offset*sampling_rate/400); + return IMAX(0, dred->nb_latents*sampling_rate/25 - dred->dred_offset* sampling_rate/400); + } + if (dred_end) *dred_end = 0; + return 0; +#else + (void)dred_dec; + (void)dred; + (void)data; + (void)len; + (void)max_dred_samples; + (void)sampling_rate; + (void)defer_processing; + (void)dred_end; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst) +{ +#ifdef ENABLE_DRED + if (dred_dec == NULL || src == NULL || dst == NULL || (src->process_stage != 1 && src->process_stage != 2)) + return OPUS_BAD_ARG; + VALIDATE_DRED_DECODER(dred_dec); + if (!dred_dec->loaded) return OPUS_UNIMPLEMENTED; + if (src != dst) + OPUS_COPY(dst, src, 1); + if (dst->process_stage == 2) + return OPUS_OK; + DRED_rdovae_decode_all(&dred_dec->model, dst->fec_features, dst->state, dst->latents, dst->nb_latents, dred_dec->arch); + dst->process_stage = 2; + return OPUS_OK; +#else + (void)dred_dec; + (void)src; + (void)dst; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size) +{ +#ifdef ENABLE_DRED + VARDECL(float, out); + int ret, i; + ALLOC_STACK; + + if(frame_size<=0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + celt_assert(st->channels == 1 || st->channels == 2); + ALLOC(out, frame_size*st->channels, float); + + ret = opus_decode_native(st, NULL, 0, out, frame_size, 0, 0, NULL, 1, dred, dred_offset); + if (ret > 0) + { + for (i=0;ichannels;i++) + pcm[i] = FLOAT2INT16(out[i]); + } + RESTORE_STACK; + return ret; +#else + (void)st; + (void)dred; + (void)dred_offset; + (void)pcm; + (void)frame_size; + return OPUS_UNIMPLEMENTED; +#endif +} + +int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size) +{ +#ifdef ENABLE_DRED + if(frame_size<=0) + return OPUS_BAD_ARG; + return opus_decode_native(st, NULL, 0, pcm, frame_size, 0, 0, NULL, 0, dred, dred_offset); +#else + (void)st; + (void)dred; + (void)dred_offset; + (void)pcm; + (void)frame_size; + return OPUS_UNIMPLEMENTED; +#endif +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_demo.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_demo.c new file mode 100644 index 0000000000..2876fff8ca --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_demo.c @@ -0,0 +1,1127 @@ +/* Copyright (c) 2007-2008 CSIRO + Copyright (c) 2007-2009 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include "opus.h" +#include "debug.h" +#include "opus_types.h" +#include "opus_private.h" +#include "opus_multistream.h" +#ifdef ENABLE_LOSSGEN +#include "lossgen.h" +#endif + +#define MAX_PACKET 1500 + +#ifdef USE_WEIGHTS_FILE +# if __unix__ +# include +# include +# include +# include +/* When available, mmap() is preferable to reading the file, as it leads to + better resource utilization, especially if multiple processes are using the same + file (mapping will be shared in cache). */ +void *load_blob(const char *filename, int *len) { + int fd; + void *data; + struct stat st; + if (stat(filename, &st)) { + *len = 0; + return NULL; + } + *len = st.st_size; + fd = open(filename, O_RDONLY); + if (fd<0) { + *len = 0; + return NULL; + } + data = mmap(NULL, *len, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + *len = 0; + data = NULL; + } + close(fd); + return data; +} +void free_blob(void *blob, int len) { + if (blob) munmap(blob, len); +} +# else +void *load_blob(const char *filename, int *len) { + FILE *file; + void *data; + file = fopen(filename, "r"); + if (file == NULL) + { + perror("could not open blob file"); + *len = 0; + return NULL; + } + fseek(file, 0L, SEEK_END); + *len = ftell(file); + fseek(file, 0L, SEEK_SET); + if (*len <= 0) { + *len = 0; + return NULL; + } + data = malloc(*len); + if (!data) { + *len = 0; + return NULL; + } + *len = fread(data, 1, *len, file); + return data; +} +void free_blob(void *blob, int len) { + free(blob); + (void)len; +} +# endif +#endif + + +void print_usage( char* argv[] ) +{ + fprintf(stderr, "Usage: %s [-e] " + " [options] \n", argv[0]); + fprintf(stderr, " %s -d " + "[options] \n\n", argv[0]); + fprintf(stderr, "application: voip | audio | restricted-lowdelay\n" ); + fprintf(stderr, "options:\n" ); + fprintf(stderr, "-e : only runs the encoder (output the bit-stream)\n" ); + fprintf(stderr, "-d : only runs the decoder (reads the bit-stream as input)\n" ); + fprintf(stderr, "-cbr : enable constant bitrate; default: variable bitrate\n" ); + fprintf(stderr, "-cvbr : enable constrained variable bitrate; default: unconstrained\n" ); + fprintf(stderr, "-delayed-decision : use look-ahead for speech/music detection (experts only); default: disabled\n" ); + fprintf(stderr, "-bandwidth : audio bandwidth (from narrowband to fullband); default: sampling rate\n" ); + fprintf(stderr, "-framesize <2.5|5|10|20|40|60|80|100|120> : frame size in ms; default: 20 \n" ); + fprintf(stderr, "-max_payload : maximum payload size in bytes, default: 1024\n" ); + fprintf(stderr, "-complexity : encoder complexity, 0 (lowest) ... 10 (highest); default: 10\n" ); + fprintf(stderr, "-dec_complexity : decoder complexity, 0 (lowest) ... 10 (highest); default: 0\n" ); + fprintf(stderr, "-inbandfec : enable SILK inband FEC\n" ); + fprintf(stderr, "-forcemono : force mono encoding, even for stereo input\n" ); + fprintf(stderr, "-dtx : enable SILK DTX\n" ); + fprintf(stderr, "-loss : optimize for loss percentage and simulate packet loss, in percent (0-100); default: 0\n" ); +#ifdef ENABLE_LOSSGEN + fprintf(stderr, "-sim_loss : simulate realistic (bursty) packet loss from percentage, using generative model\n" ); +#endif + fprintf(stderr, "-lossfile : simulate packet loss, reading loss from file\n" ); + fprintf(stderr, "-dred : add Deep REDundancy (in units of 10-ms frames)\n" ); +} + +static void int_to_char(opus_uint32 i, unsigned char ch[4]) +{ + ch[0] = i>>24; + ch[1] = (i>>16)&0xFF; + ch[2] = (i>>8)&0xFF; + ch[3] = i&0xFF; +} + +static opus_uint32 char_to_int(unsigned char ch[4]) +{ + return ((opus_uint32)ch[0]<<24) | ((opus_uint32)ch[1]<<16) + | ((opus_uint32)ch[2]<< 8) | (opus_uint32)ch[3]; +} + +#define check_encoder_option(decode_only, opt) do {if (decode_only) {fprintf(stderr, "option %s is only for encoding\n", opt); goto failure;}} while(0) +#define check_decoder_option(encode_only, opt) do {if (encode_only) {fprintf(stderr, "option %s is only for decoding\n", opt); goto failure;}} while(0) + +static const int silk8_test[][4] = { + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960*3, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960*2, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 480, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960*3, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960*2, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_NARROWBAND, 480, 2} +}; + +static const int silk12_test[][4] = { + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960*3, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960*2, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 480, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960*3, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960*2, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 960, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_MEDIUMBAND, 480, 2} +}; + +static const int silk16_test[][4] = { + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960*3, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960*2, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 480, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960*3, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960*2, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_WIDEBAND, 480, 2} +}; + +static const int hybrid24_test[][4] = { + {MODE_SILK_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 960, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 480, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 960, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 480, 2} +}; + +static const int hybrid48_test[][4] = { + {MODE_SILK_ONLY, OPUS_BANDWIDTH_FULLBAND, 960, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_FULLBAND, 480, 1}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_FULLBAND, 960, 2}, + {MODE_SILK_ONLY, OPUS_BANDWIDTH_FULLBAND, 480, 2} +}; + +static const int celt_test[][4] = { + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 960, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 960, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960, 1}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 480, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 480, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 480, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 480, 1}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 240, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 240, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 240, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 240, 1}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 120, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 120, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 120, 1}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 120, 1}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 960, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 960, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 960, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 960, 2}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 480, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 480, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 480, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 480, 2}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 240, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 240, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 240, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 240, 2}, + + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 120, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_SUPERWIDEBAND, 120, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_WIDEBAND, 120, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_NARROWBAND, 120, 2}, + +}; + +static const int celt_hq_test[][4] = { + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 960, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 480, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 240, 2}, + {MODE_CELT_ONLY, OPUS_BANDWIDTH_FULLBAND, 120, 2}, +}; + +#if 0 /* This is a hack that replaces the normal encoder/decoder with the multistream version */ +#define OpusEncoder OpusMSEncoder +#define OpusDecoder OpusMSDecoder +#define opus_encode opus_multistream_encode +#define opus_decode opus_multistream_decode +#define opus_encoder_ctl opus_multistream_encoder_ctl +#define opus_decoder_ctl opus_multistream_decoder_ctl +#define opus_encoder_create ms_opus_encoder_create +#define opus_decoder_create ms_opus_decoder_create +#define opus_encoder_destroy opus_multistream_encoder_destroy +#define opus_decoder_destroy opus_multistream_decoder_destroy + +static OpusEncoder *ms_opus_encoder_create(opus_int32 Fs, int channels, int application, int *error) +{ + int streams, coupled_streams; + unsigned char mapping[256]; + return (OpusEncoder *)opus_multistream_surround_encoder_create(Fs, channels, 1, &streams, &coupled_streams, mapping, application, error); +} +static OpusDecoder *ms_opus_decoder_create(opus_int32 Fs, int channels, int *error) +{ + int streams; + int coupled_streams; + unsigned char mapping[256]={0,1}; + streams = 1; + coupled_streams = channels==2; + return (OpusDecoder *)opus_multistream_decoder_create(Fs, channels, streams, coupled_streams, mapping, error); +} +#endif + + +#ifdef ENABLE_OSCE_TRAINING_DATA +#define COMPLEXITY_MIN 0 +#define COMPLEXITY_MAX 10 + +#define PACKET_LOSS_PERC_MIN 0 +#define PACKET_LOSS_PERC_MAX 50 +#define PACKET_LOSS_PERC_STEP 5 + +#define CBR_BITRATE_LIMIT 8000 + +#define NUM_BITRATES 102 +static int bitrates[NUM_BITRATES] = { + 6000, 6060, 6120, 6180, 6240, 6300, 6360, 6420, 6480, + 6525, 6561, 6598, 6634, 6670, 6707, 6743, 6780, 6816, + 6853, 6889, 6926, 6962, 6999, 7042, 7085, 7128, 7171, + 7215, 7258, 7301, 7344, 7388, 7431, 7474, 7512, 7541, + 7570, 7599, 7628, 7657, 7686, 7715, 7744, 7773, 7802, + 7831, 7860, 7889, 7918, 7947, 7976, 8013, 8096, 8179, + 8262, 8344, 8427, 8511, 8605, 8699, 8792, 8886, 8980, + 9100, 9227, 9354, 9480, 9561, 9634, 9706, 9779, 9851, + 9924, 9996, 10161, 10330, 10499, 10698, 10898, 11124, 11378, + 11575, 11719, 11862, 12014, 12345, 12751, 13195, 13561, 13795, + 14069, 14671, 15403, 15790, 16371, 17399, 17968, 19382, 20468, + 22000, 32000, 64000 +}; + +static int randint(int min, int max, int step) +{ + double r = ((double) rand())/ (RAND_MAX + 1.); + int d; + + d = ((int) ((max + 1 - min) * r / step) * step) + min; + + return d; +} + +static void new_random_setting(OpusEncoder *enc) +{ + int bitrate_bps; + int complexity; + int packet_loss_perc; + int use_vbr; + + bitrate_bps = bitrates[randint(0, NUM_BITRATES - 1, 1)]; + complexity = randint(COMPLEXITY_MIN, COMPLEXITY_MAX, 1); + packet_loss_perc = randint(PACKET_LOSS_PERC_MIN, PACKET_LOSS_PERC_MAX, PACKET_LOSS_PERC_STEP); + use_vbr = bitrate_bps < CBR_BITRATE_LIMIT ? 1 : randint(0, 1, 1); + + if (1) + { + printf("changing settings to %d\t%d\t%d\t%d\n", bitrate_bps, complexity, packet_loss_perc, use_vbr); + } + + opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps)); + opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); + opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc)); + opus_encoder_ctl(enc, OPUS_SET_VBR(use_vbr)); +} + +#endif + +int main(int argc, char *argv[]) +{ + int err; + char *inFile, *outFile; + FILE *fin=NULL; + FILE *fout=NULL; + OpusEncoder *enc=NULL; + OpusDecoder *dec=NULL; + OpusDRED *dred=NULL; + OpusDREDDecoder *dred_dec=NULL; + int args; + int len; + int frame_size, channels; + opus_int32 bitrate_bps=0; + unsigned char *data = NULL; + unsigned char *fbytes=NULL; + opus_int32 sampling_rate; + int use_vbr; + int max_payload_bytes; + int complexity; + int dec_complexity; + int use_inbandfec; + int use_dtx; + int forcechannels; + int cvbr = 0; + int packet_loss_perc; +#ifdef ENABLE_LOSSGEN + float lossgen_perc = -1.f; + LossGenState lossgen; +#endif + opus_int32 count=0, count_act=0; + int k; + opus_int32 skip=0; + int stop=0; + short *in=NULL; + short *out=NULL; + int application=OPUS_APPLICATION_AUDIO; + double bits=0.0, bits_max=0.0, bits_act=0.0, bits2=0.0, nrg; + double tot_samples=0; + opus_uint64 tot_in, tot_out; + int bandwidth=OPUS_AUTO; + const char *bandwidth_string; + int lost = 0, lost_prev = 1; + opus_uint32 enc_final_range; + opus_uint32 dec_final_range; + int encode_only=0, decode_only=0; + int max_frame_size = 48000*2; + size_t num_read; + int curr_read=0; + int sweep_bps = 0; + int random_framesize=0, newsize=0, delayed_celt=0; + int sweep_max=0, sweep_min=0; + int random_fec=0; + const int (*mode_list)[4]=NULL; + int nb_modes_in_list=0; + int curr_mode=0; + int curr_mode_count=0; + int mode_switch_time = 48000; + int nb_encoded=0; + int remaining=0; + int variable_duration=OPUS_FRAMESIZE_ARG; + int delayed_decision=0; + int ret = EXIT_FAILURE; + int lost_count=0; + FILE *packet_loss_file=NULL; + int dred_duration=0; +#ifdef ENABLE_OSCE_TRAINING_DATA + int silk_random_switching = 0; + int silk_frame_counter = 0; +#endif +#ifdef USE_WEIGHTS_FILE + int blob_len; + void *blob_data; + const char *filename = "weights_blob.bin"; + blob_data = load_blob(filename, &blob_len); +#endif + + if (argc < 5 ) + { + print_usage( argv ); + goto failure; + } + + tot_in=tot_out=0; + fprintf(stderr, "%s\n", opus_get_version_string()); + + args = 1; + if (strcmp(argv[args], "-e")==0) + { + encode_only = 1; + args++; + } else if (strcmp(argv[args], "-d")==0) + { + decode_only = 1; + args++; + } + if (!decode_only && argc < 7 ) + { + print_usage( argv ); + goto failure; + } + + if (!decode_only) + { + if (strcmp(argv[args], "voip")==0) + application = OPUS_APPLICATION_VOIP; + else if (strcmp(argv[args], "restricted-lowdelay")==0) + application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; + else if (strcmp(argv[args], "audio")!=0) { + fprintf(stderr, "unknown application: %s\n", argv[args]); + print_usage(argv); + goto failure; + } + args++; + } + sampling_rate = (opus_int32)atol(argv[args]); + args++; + + if (sampling_rate != 8000 && sampling_rate != 12000 + && sampling_rate != 16000 && sampling_rate != 24000 + && sampling_rate != 48000) + { + fprintf(stderr, "Supported sampling rates are 8000, 12000, " + "16000, 24000 and 48000.\n"); + goto failure; + } + frame_size = sampling_rate/50; + + channels = atoi(argv[args]); + args++; + + if (channels < 1 || channels > 2) + { + fprintf(stderr, "Opus_demo supports only 1 or 2 channels.\n"); + goto failure; + } + + if (!decode_only) + { + bitrate_bps = (opus_int32)atol(argv[args]); + args++; + } + + /* defaults: */ + use_vbr = 1; + max_payload_bytes = MAX_PACKET; + complexity = 10; + dec_complexity = 0; + use_inbandfec = 0; + forcechannels = OPUS_AUTO; + use_dtx = 0; + packet_loss_perc = 0; + + while( args < argc - 2 ) { + /* process command line options */ + if( strcmp( argv[ args ], "-cbr" ) == 0 ) { + check_encoder_option(decode_only, "-cbr"); + use_vbr = 0; + args++; + } else if( strcmp( argv[ args ], "-bandwidth" ) == 0 ) { + check_encoder_option(decode_only, "-bandwidth"); + if (strcmp(argv[ args + 1 ], "NB")==0) + bandwidth = OPUS_BANDWIDTH_NARROWBAND; + else if (strcmp(argv[ args + 1 ], "MB")==0) + bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + else if (strcmp(argv[ args + 1 ], "WB")==0) + bandwidth = OPUS_BANDWIDTH_WIDEBAND; + else if (strcmp(argv[ args + 1 ], "SWB")==0) + bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + else if (strcmp(argv[ args + 1 ], "FB")==0) + bandwidth = OPUS_BANDWIDTH_FULLBAND; + else { + fprintf(stderr, "Unknown bandwidth %s. " + "Supported are NB, MB, WB, SWB, FB.\n", + argv[ args + 1 ]); + goto failure; + } + args += 2; + } else if( strcmp( argv[ args ], "-framesize" ) == 0 ) { + check_encoder_option(decode_only, "-framesize"); + if (strcmp(argv[ args + 1 ], "2.5")==0) + frame_size = sampling_rate/400; + else if (strcmp(argv[ args + 1 ], "5")==0) + frame_size = sampling_rate/200; + else if (strcmp(argv[ args + 1 ], "10")==0) + frame_size = sampling_rate/100; + else if (strcmp(argv[ args + 1 ], "20")==0) + frame_size = sampling_rate/50; + else if (strcmp(argv[ args + 1 ], "40")==0) + frame_size = sampling_rate/25; + else if (strcmp(argv[ args + 1 ], "60")==0) + frame_size = 3*sampling_rate/50; + else if (strcmp(argv[ args + 1 ], "80")==0) + frame_size = 4*sampling_rate/50; + else if (strcmp(argv[ args + 1 ], "100")==0) + frame_size = 5*sampling_rate/50; + else if (strcmp(argv[ args + 1 ], "120")==0) + frame_size = 6*sampling_rate/50; + else { + fprintf(stderr, "Unsupported frame size: %s ms. " + "Supported are 2.5, 5, 10, 20, 40, 60, 80, 100, 120.\n", + argv[ args + 1 ]); + goto failure; + } + args += 2; + } else if( strcmp( argv[ args ], "-max_payload" ) == 0 ) { + check_encoder_option(decode_only, "-max_payload"); + max_payload_bytes = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-complexity" ) == 0 ) { + check_encoder_option(decode_only, "-complexity"); + complexity = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-dec_complexity" ) == 0 ) { + check_decoder_option(encode_only, "-dec_complexity"); + dec_complexity = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-inbandfec" ) == 0 ) { + use_inbandfec = 1; + args++; + } else if( strcmp( argv[ args ], "-forcemono" ) == 0 ) { + check_encoder_option(decode_only, "-forcemono"); + forcechannels = 1; + args++; + } else if( strcmp( argv[ args ], "-cvbr" ) == 0 ) { + check_encoder_option(decode_only, "-cvbr"); + cvbr = 1; + args++; + } else if( strcmp( argv[ args ], "-delayed-decision" ) == 0 ) { + check_encoder_option(decode_only, "-delayed-decision"); + delayed_decision = 1; + args++; + } else if( strcmp( argv[ args ], "-dtx") == 0 ) { + check_encoder_option(decode_only, "-dtx"); + use_dtx = 1; + args++; + } else if( strcmp( argv[ args ], "-loss" ) == 0 ) { + packet_loss_perc = atoi( argv[ args + 1 ] ); + args += 2; +#ifdef ENABLE_LOSSGEN + } else if( strcmp( argv[ args ], "-sim_loss" ) == 0 ) { + lossgen_perc = atof( argv[ args + 1 ] ); + lossgen_init(&lossgen); + args += 2; +#endif + } else if( strcmp( argv[ args ], "-lossfile" ) == 0 ) { + packet_loss_file = fopen( argv[ args + 1 ], "r" ); + if (packet_loss_file == NULL) { + fprintf(stderr, "failed to open loss file %s\n", argv[ args + 1 ] ); + exit(1); + } + args += 2; + } else if( strcmp( argv[ args ], "-dred" ) == 0 ) { + dred_duration = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-sweep" ) == 0 ) { + check_encoder_option(decode_only, "-sweep"); + sweep_bps = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-random_framesize" ) == 0 ) { + check_encoder_option(decode_only, "-random_framesize"); + random_framesize = 1; + args++; + } else if( strcmp( argv[ args ], "-sweep_max" ) == 0 ) { + check_encoder_option(decode_only, "-sweep_max"); + sweep_max = atoi( argv[ args + 1 ] ); + args += 2; + } else if( strcmp( argv[ args ], "-random_fec" ) == 0 ) { + check_encoder_option(decode_only, "-random_fec"); + random_fec = 1; + args++; + } else if( strcmp( argv[ args ], "-silk8k_test" ) == 0 ) { + check_encoder_option(decode_only, "-silk8k_test"); + mode_list = silk8_test; + nb_modes_in_list = 8; + args++; + } else if( strcmp( argv[ args ], "-silk12k_test" ) == 0 ) { + check_encoder_option(decode_only, "-silk12k_test"); + mode_list = silk12_test; + nb_modes_in_list = 8; + args++; + } else if( strcmp( argv[ args ], "-silk16k_test" ) == 0 ) { + check_encoder_option(decode_only, "-silk16k_test"); + mode_list = silk16_test; + nb_modes_in_list = 8; + args++; + } else if( strcmp( argv[ args ], "-hybrid24k_test" ) == 0 ) { + check_encoder_option(decode_only, "-hybrid24k_test"); + mode_list = hybrid24_test; + nb_modes_in_list = 4; + args++; + } else if( strcmp( argv[ args ], "-hybrid48k_test" ) == 0 ) { + check_encoder_option(decode_only, "-hybrid48k_test"); + mode_list = hybrid48_test; + nb_modes_in_list = 4; + args++; + } else if( strcmp( argv[ args ], "-celt_test" ) == 0 ) { + check_encoder_option(decode_only, "-celt_test"); + mode_list = celt_test; + nb_modes_in_list = 32; + args++; + } else if( strcmp( argv[ args ], "-celt_hq_test" ) == 0 ) { + check_encoder_option(decode_only, "-celt_hq_test"); + mode_list = celt_hq_test; + nb_modes_in_list = 4; + args++; +#ifdef ENABLE_OSCE_TRAINING_DATA + } else if( strcmp( argv[ args ], "-silk_random_switching" ) == 0 ){ + silk_random_switching = atoi( argv[ args + 1 ] ); + printf("switching encoding parameters every %dth frame\n", silk_random_switching); + args += 2; +#endif + } else { + printf( "Error: unrecognized setting: %s\n\n", argv[ args ] ); + print_usage( argv ); + goto failure; + } + } + + if (sweep_max) + sweep_min = bitrate_bps; + + if (max_payload_bytes < 0 || max_payload_bytes > MAX_PACKET) + { + fprintf (stderr, "max_payload_bytes must be between 0 and %d\n", + MAX_PACKET); + goto failure; + } + + inFile = argv[argc-2]; + fin = fopen(inFile, "rb"); + if (!fin) + { + fprintf (stderr, "Could not open input file %s\n", argv[argc-2]); + goto failure; + } + if (mode_list) + { + int size; + fseek(fin, 0, SEEK_END); + size = ftell(fin); + fprintf(stderr, "File size is %d bytes\n", size); + fseek(fin, 0, SEEK_SET); + mode_switch_time = size/sizeof(short)/channels/nb_modes_in_list; + fprintf(stderr, "Switching mode every %d samples\n", mode_switch_time); + } + + outFile = argv[argc-1]; + fout = fopen(outFile, "wb+"); + if (!fout) + { + fprintf (stderr, "Could not open output file %s\n", argv[argc-1]); + goto failure; + } + + if (!decode_only) + { + enc = opus_encoder_create(sampling_rate, channels, application, &err); + if (err != OPUS_OK) + { + fprintf(stderr, "Cannot create encoder: %s\n", opus_strerror(err)); + goto failure; + } + opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps)); + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(bandwidth)); + opus_encoder_ctl(enc, OPUS_SET_VBR(use_vbr)); + opus_encoder_ctl(enc, OPUS_SET_VBR_CONSTRAINT(cvbr)); + opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); + opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(use_inbandfec)); + opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(forcechannels)); + opus_encoder_ctl(enc, OPUS_SET_DTX(use_dtx)); + opus_encoder_ctl(enc, OPUS_SET_PACKET_LOSS_PERC(packet_loss_perc)); + + opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&skip)); + opus_encoder_ctl(enc, OPUS_SET_LSB_DEPTH(16)); + opus_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(variable_duration)); + if (dred_duration > 0) + { + opus_encoder_ctl(enc, OPUS_SET_DRED_DURATION(dred_duration)); + } + } + if (!encode_only) + { + dec = opus_decoder_create(sampling_rate, channels, &err); + if (err != OPUS_OK) + { + fprintf(stderr, "Cannot create decoder: %s\n", opus_strerror(err)); + goto failure; + } + opus_decoder_ctl(dec, OPUS_SET_COMPLEXITY(dec_complexity)); + } + switch(bandwidth) + { + case OPUS_BANDWIDTH_NARROWBAND: + bandwidth_string = "narrowband"; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + bandwidth_string = "mediumband"; + break; + case OPUS_BANDWIDTH_WIDEBAND: + bandwidth_string = "wideband"; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + bandwidth_string = "superwideband"; + break; + case OPUS_BANDWIDTH_FULLBAND: + bandwidth_string = "fullband"; + break; + case OPUS_AUTO: + bandwidth_string = "auto bandwidth"; + break; + default: + bandwidth_string = "unknown"; + break; + } + + if (decode_only) + fprintf(stderr, "Decoding with %ld Hz output (%d channels)\n", + (long)sampling_rate, channels); + else + fprintf(stderr, "Encoding %ld Hz input at %.3f kb/s " + "in %s with %d-sample frames.\n", + (long)sampling_rate, bitrate_bps*0.001, + bandwidth_string, frame_size); + + in = (short*)malloc(max_frame_size*channels*sizeof(short)); + out = (short*)malloc(max_frame_size*channels*sizeof(short)); + /* We need to allocate for 16-bit PCM data, but we store it as unsigned char. */ + fbytes = (unsigned char*)malloc(max_frame_size*channels*sizeof(short)); + data = (unsigned char*)calloc(max_payload_bytes,sizeof(unsigned char)); + if(delayed_decision) + { + if (frame_size==sampling_rate/400) + variable_duration = OPUS_FRAMESIZE_2_5_MS; + else if (frame_size==sampling_rate/200) + variable_duration = OPUS_FRAMESIZE_5_MS; + else if (frame_size==sampling_rate/100) + variable_duration = OPUS_FRAMESIZE_10_MS; + else if (frame_size==sampling_rate/50) + variable_duration = OPUS_FRAMESIZE_20_MS; + else if (frame_size==sampling_rate/25) + variable_duration = OPUS_FRAMESIZE_40_MS; + else if (frame_size==3*sampling_rate/50) + variable_duration = OPUS_FRAMESIZE_60_MS; + else if (frame_size==4*sampling_rate/50) + variable_duration = OPUS_FRAMESIZE_80_MS; + else if (frame_size==5*sampling_rate/50) + variable_duration = OPUS_FRAMESIZE_100_MS; + else + variable_duration = OPUS_FRAMESIZE_120_MS; + opus_encoder_ctl(enc, OPUS_SET_EXPERT_FRAME_DURATION(variable_duration)); + frame_size = 2*48000; + } + dred_dec = opus_dred_decoder_create(&err); + dred = opus_dred_alloc(&err); +#ifdef USE_WEIGHTS_FILE + if (enc) opus_encoder_ctl(enc, OPUS_SET_DNN_BLOB(blob_data, blob_len)); + if (dec) opus_decoder_ctl(dec, OPUS_SET_DNN_BLOB(blob_data, blob_len)); + if (dred_dec) opus_dred_decoder_ctl(dred_dec, OPUS_SET_DNN_BLOB(blob_data, blob_len)); +#endif + while (!stop) + { + if (delayed_celt) + { + frame_size = newsize; + delayed_celt = 0; + } else if (random_framesize && rand()%20==0) + { + newsize = rand()%6; + switch(newsize) + { + case 0: newsize=sampling_rate/400; break; + case 1: newsize=sampling_rate/200; break; + case 2: newsize=sampling_rate/100; break; + case 3: newsize=sampling_rate/50; break; + case 4: newsize=sampling_rate/25; break; + case 5: newsize=3*sampling_rate/50; break; + } + while (newsize < sampling_rate/25 && bitrate_bps-abs(sweep_bps) <= 3*12*sampling_rate/newsize) + newsize*=2; + if (newsize < sampling_rate/100 && frame_size >= sampling_rate/100) + { + opus_encoder_ctl(enc, OPUS_SET_FORCE_MODE(MODE_CELT_ONLY)); + delayed_celt=1; + } else { + frame_size = newsize; + } + } + if (random_fec && rand()%30==0) + { + opus_encoder_ctl(enc, OPUS_SET_INBAND_FEC(rand()%4==0)); + } + if (decode_only) + { + unsigned char ch[4]; + num_read = fread(ch, 1, 4, fin); + if (num_read!=4) + break; + len = char_to_int(ch); + if (len>max_payload_bytes || len<0) + { + fprintf(stderr, "Invalid payload length: %d\n",len); + break; + } + num_read = fread(ch, 1, 4, fin); + if (num_read!=4) + break; + enc_final_range = char_to_int(ch); + num_read = fread(data, 1, len, fin); + if (num_read!=(size_t)len) + { + fprintf(stderr, "Ran out of input, " + "expecting %d bytes got %d\n", + len,(int)num_read); + break; + } + } else { + int i; + if (mode_list!=NULL) + { + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(mode_list[curr_mode][1])); + opus_encoder_ctl(enc, OPUS_SET_FORCE_MODE(mode_list[curr_mode][0])); + opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(mode_list[curr_mode][3])); + frame_size = mode_list[curr_mode][2]; + } +#ifdef ENABLE_OSCE_TRAINING_DATA + if (silk_random_switching) + { + silk_frame_counter += 1; + if (silk_frame_counter % silk_random_switching == 0) { + new_random_setting(enc); + } + } +#endif + num_read = fread(fbytes, sizeof(short)*channels, frame_size-remaining, fin); + curr_read = (int)num_read; + tot_in += curr_read; + for(i=0;i sweep_max) + sweep_bps = -sweep_bps; + else if (bitrate_bps < sweep_min) + sweep_bps = -sweep_bps; + } + /* safety */ + if (bitrate_bps<1000) + bitrate_bps = 1000; + opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate_bps)); + } + opus_encoder_ctl(enc, OPUS_GET_FINAL_RANGE(&enc_final_range)); + curr_mode_count += frame_size; + if (curr_mode_count > mode_switch_time && curr_mode < nb_modes_in_list-1) + { + curr_mode++; + curr_mode_count = 0; + } + } + +#if 0 /* This is for testing the padding code, do not enable by default */ + if (len<1275) + { + int new_len = len+rand()%(max_payload_bytes-len); + if ((err = opus_packet_pad(data, len, new_len)) != OPUS_OK) + { + fprintf(stderr, "padding failed: %s\n", opus_strerror(err)); + goto failure; + } + len = new_len; + } +#endif + if (encode_only) + { + unsigned char int_field[4]; + int_to_char(len, int_field); + if (fwrite(int_field, 1, 4, fout) != 4) { + fprintf(stderr, "Error writing.\n"); + goto failure; + } + int_to_char(enc_final_range, int_field); + if (fwrite(int_field, 1, 4, fout) != 4) { + fprintf(stderr, "Error writing.\n"); + goto failure; + } + if (fwrite(data, 1, len, fout) != (unsigned)len) { + fprintf(stderr, "Error writing.\n"); + goto failure; + } + tot_samples += nb_encoded; + } else { + int fr; + int run_decoder; + int dred_input=0; + int dred_end=0; + if (packet_loss_file != NULL) { + if ( fscanf(packet_loss_file, "%d", &lost) != 1) { + lost = 0; + } +#ifdef ENABLE_LOSSGEN + } else if (lossgen_perc >= 0) { + lost = sample_loss(&lossgen, lossgen_perc*.01f); +#endif + } else { + lost = (packet_loss_perc>0) && (rand()%100 < packet_loss_perc); + } + if (len == 0) lost = 1; + if (lost) + { + lost_count++; + run_decoder = 0; + } else { + run_decoder= 1; + } + if (run_decoder) + run_decoder += lost_count; + if (!lost && lost_count > 0) { + opus_int32 output_samples=0; + opus_decoder_ctl(dec, OPUS_GET_LAST_PACKET_DURATION(&output_samples)); + dred_input = lost_count*output_samples; + /* Only decode the amount we need to fill in the gap. */ + ret = opus_dred_parse(dred_dec, dred, data, len, IMIN(48000, IMAX(0, dred_input)), sampling_rate, &dred_end, 0); + dred_input = ret > 0 ? ret : 0; + } + /* FIXME: Figure out how to trigger the decoder when the last packet of the file is lost. */ + for (fr=0;fr 0) + output_samples = opus_decoder_dred_decode(dec, dred, (lost_count-fr)*output_samples, out, output_samples); + else + output_samples = opus_decode(dec, NULL, 0, out, output_samples, 0); + } else { + output_samples = max_frame_size; + output_samples = opus_decode(dec, data, len, out, output_samples, 0); + } + if (output_samples>0) + { + if (!decode_only && tot_out + output_samples > tot_in) + { + stop=1; + output_samples = (opus_int32)(tot_in - tot_out); + } + if (output_samples>skip) { + int i; + for(i=0;i<(output_samples-skip)*channels;i++) + { + short s; + s=out[i+(skip*channels)]; + fbytes[2*i]=s&0xFF; + fbytes[2*i+1]=(s>>8)&0xFF; + } + if (fwrite(fbytes, sizeof(short)*channels, output_samples-skip, fout) != (unsigned)(output_samples-skip)){ + fprintf(stderr, "Error writing.\n"); + goto failure; + } + tot_out += output_samples-skip; + } + if (output_samples= use_inbandfec ) { + /* count bits */ + bits += len*8; + bits_max = ( len*8 > bits_max ) ? len*8 : bits_max; + bits2 += len*len*64; + if (!decode_only) + { + nrg = 0.0; + for ( k = 0; k < frame_size * channels; k++ ) { + nrg += in[ k ] * (double)in[ k ]; + } + nrg /= frame_size * channels; + if( nrg > 1e5 ) { + bits_act += len*8; + count_act++; + } + } + } + count++; + } + + if(decode_only && count > 0) + frame_size = (int)(tot_samples / count); + count -= use_inbandfec; + if (tot_samples >= 1 && count > 0 && frame_size) + { + /* Print out bitrate statistics */ + double var; + fprintf (stderr, "average bitrate: %7.3f kb/s\n", + 1e-3*bits*sampling_rate/tot_samples); + fprintf (stderr, "maximum bitrate: %7.3f kb/s\n", + 1e-3*bits_max*sampling_rate/frame_size); + if (!decode_only) + fprintf (stderr, "active bitrate: %7.3f kb/s\n", + 1e-3*bits_act*sampling_rate/(1e-15+frame_size*(double)count_act)); + var = bits2/count - bits*bits/(count*(double)count); + if (var < 0) + var = 0; + fprintf (stderr, "bitrate standard deviation: %7.3f kb/s\n", + 1e-3*sqrt(var)*sampling_rate/frame_size); + } else { + fprintf(stderr, "bitrate statistics are undefined\n"); + } + silk_TimerSave("opus_timing.txt"); + ret = EXIT_SUCCESS; +failure: + opus_encoder_destroy(enc); + opus_decoder_destroy(dec); + opus_dred_free(dred); + opus_dred_decoder_destroy(dred_dec); + free(data); + if (fin) + fclose(fin); + if (fout) + fclose(fout); + free(in); + free(out); + free(fbytes); +#ifdef USE_WEIGHTS_FILE + free_blob(blob_data, blob_len); +#endif + return ret; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_encoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_encoder.c new file mode 100644 index 0000000000..d18d582f03 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_encoder.c @@ -0,0 +1,3089 @@ +/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited + Written by Jean-Marc Valin and Koen Vos */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "celt.h" +#include "entenc.h" +#include "modes.h" +#include "API.h" +#include "stack_alloc.h" +#include "float_cast.h" +#include "opus.h" +#include "arch.h" +#include "pitch.h" +#include "opus_private.h" +#include "os_support.h" +#include "cpu_support.h" +#include "analysis.h" +#include "mathops.h" +#include "tuning_parameters.h" + +#ifdef ENABLE_DRED +#include "dred_coding.h" +#endif + +#ifdef FIXED_POINT +#include "fixed/structs_FIX.h" +#else +#include "float/structs_FLP.h" +#endif +#ifdef ENABLE_OSCE_TRAINING_DATA +#include +#endif + +#define MAX_ENCODER_BUFFER 480 + +#ifndef DISABLE_FLOAT_API +#define PSEUDO_SNR_THRESHOLD 316.23f /* 10^(25/10) */ +#endif + +typedef struct { + opus_val32 XX, XY, YY; + opus_val16 smoothed_width; + opus_val16 max_follower; +} StereoWidthState; + +struct OpusEncoder { + int celt_enc_offset; + int silk_enc_offset; + silk_EncControlStruct silk_mode; +#ifdef ENABLE_DRED + DREDEnc dred_encoder; +#endif + int application; + int channels; + int delay_compensation; + int force_channels; + int signal_type; + int user_bandwidth; + int max_bandwidth; + int user_forced_mode; + int voice_ratio; + opus_int32 Fs; + int use_vbr; + int vbr_constraint; + int variable_duration; + opus_int32 bitrate_bps; + opus_int32 user_bitrate_bps; + int lsb_depth; + int encoder_buffer; + int lfe; + int arch; + int use_dtx; /* general DTX for both SILK and CELT */ + int fec_config; +#ifndef DISABLE_FLOAT_API + TonalityAnalysisState analysis; +#endif + +#define OPUS_ENCODER_RESET_START stream_channels + int stream_channels; + opus_int16 hybrid_stereo_width_Q14; + opus_int32 variable_HP_smth2_Q15; + opus_val16 prev_HB_gain; + opus_val32 hp_mem[4]; + int mode; + int prev_mode; + int prev_channels; + int prev_framesize; + int bandwidth; + /* Bandwidth determined automatically from the rate (before any other adjustment) */ + int auto_bandwidth; + int silk_bw_switch; + /* Sampling rate (at the API level) */ + int first; + opus_val16 * energy_masking; + StereoWidthState width_mem; + opus_val16 delay_buffer[MAX_ENCODER_BUFFER*2]; +#ifndef DISABLE_FLOAT_API + int detected_bandwidth; + int nb_no_activity_ms_Q1; + opus_val32 peak_signal_energy; +#endif +#ifdef ENABLE_DRED + int dred_duration; + int dred_q0; + int dred_dQ; + int dred_qmax; + int dred_target_chunks; + unsigned char activity_mem[DRED_MAX_FRAMES*4]; /* 2.5ms resolution*/ +#endif + int nonfinal_frame; /* current frame is not the final in a packet */ + opus_uint32 rangeFinal; +}; + +/* Transition tables for the voice and music. First column is the + middle (memoriless) threshold. The second column is the hysteresis + (difference with the middle) */ +static const opus_int32 mono_voice_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ +}; +static const opus_int32 mono_music_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 11000, 1000, /* WB<->SWB */ + 12000, 2000, /* SWB<->FB */ +}; +static const opus_int32 stereo_voice_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ +}; +static const opus_int32 stereo_music_bandwidth_thresholds[8] = { + 9000, 700, /* NB<->MB */ + 9000, 700, /* MB<->WB */ + 11000, 1000, /* WB<->SWB */ + 12000, 2000, /* SWB<->FB */ +}; +/* Threshold bit-rates for switching between mono and stereo */ +static const opus_int32 stereo_voice_threshold = 19000; +static const opus_int32 stereo_music_threshold = 17000; + +/* Threshold bit-rate for switching between SILK/hybrid and CELT-only */ +static const opus_int32 mode_thresholds[2][2] = { + /* voice */ /* music */ + { 64000, 10000}, /* mono */ + { 44000, 10000}, /* stereo */ +}; + +static const opus_int32 fec_thresholds[] = { + 12000, 1000, /* NB */ + 14000, 1000, /* MB */ + 16000, 1000, /* WB */ + 20000, 1000, /* SWB */ + 22000, 1000, /* FB */ +}; + +int opus_encoder_get_size(int channels) +{ + int silkEncSizeBytes, celtEncSizeBytes; + int ret; + if (channels<1 || channels > 2) + return 0; + ret = silk_Get_Encoder_Size( &silkEncSizeBytes ); + if (ret) + return 0; + silkEncSizeBytes = align(silkEncSizeBytes); + celtEncSizeBytes = celt_encoder_get_size(channels); + return align(sizeof(OpusEncoder))+silkEncSizeBytes+celtEncSizeBytes; +} + +int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int application) +{ + void *silk_enc; + CELTEncoder *celt_enc; + int err; + int ret, silkEncSizeBytes; + + if((Fs!=48000&&Fs!=24000&&Fs!=16000&&Fs!=12000&&Fs!=8000)||(channels!=1&&channels!=2)|| + (application != OPUS_APPLICATION_VOIP && application != OPUS_APPLICATION_AUDIO + && application != OPUS_APPLICATION_RESTRICTED_LOWDELAY)) + return OPUS_BAD_ARG; + + OPUS_CLEAR((char*)st, opus_encoder_get_size(channels)); + /* Create SILK encoder */ + ret = silk_Get_Encoder_Size( &silkEncSizeBytes ); + if (ret) + return OPUS_BAD_ARG; + silkEncSizeBytes = align(silkEncSizeBytes); + st->silk_enc_offset = align(sizeof(OpusEncoder)); + st->celt_enc_offset = st->silk_enc_offset+silkEncSizeBytes; + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + st->stream_channels = st->channels = channels; + + st->Fs = Fs; + + st->arch = opus_select_arch(); + + ret = silk_InitEncoder( silk_enc, st->arch, &st->silk_mode ); + if(ret)return OPUS_INTERNAL_ERROR; + + /* default SILK parameters */ + st->silk_mode.nChannelsAPI = channels; + st->silk_mode.nChannelsInternal = channels; + st->silk_mode.API_sampleRate = st->Fs; + st->silk_mode.maxInternalSampleRate = 16000; + st->silk_mode.minInternalSampleRate = 8000; + st->silk_mode.desiredInternalSampleRate = 16000; + st->silk_mode.payloadSize_ms = 20; + st->silk_mode.bitRate = 25000; + st->silk_mode.packetLossPercentage = 0; + st->silk_mode.complexity = 9; + st->silk_mode.useInBandFEC = 0; + st->silk_mode.useDRED = 0; + st->silk_mode.useDTX = 0; + st->silk_mode.useCBR = 0; + st->silk_mode.reducedDependency = 0; + + /* Create CELT encoder */ + /* Initialize CELT encoder */ + err = celt_encoder_init(celt_enc, Fs, channels, st->arch); + if(err!=OPUS_OK)return OPUS_INTERNAL_ERROR; + + celt_encoder_ctl(celt_enc, CELT_SET_SIGNALLING(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_COMPLEXITY(st->silk_mode.complexity)); + +#ifdef ENABLE_DRED + /* Initialize DRED Encoder */ + dred_encoder_init( &st->dred_encoder, Fs, channels ); +#endif + + st->use_vbr = 1; + /* Makes constrained VBR the default (safer for real-time use) */ + st->vbr_constraint = 1; + st->user_bitrate_bps = OPUS_AUTO; + st->bitrate_bps = 3000+Fs*channels; + st->application = application; + st->signal_type = OPUS_AUTO; + st->user_bandwidth = OPUS_AUTO; + st->max_bandwidth = OPUS_BANDWIDTH_FULLBAND; + st->force_channels = OPUS_AUTO; + st->user_forced_mode = OPUS_AUTO; + st->voice_ratio = -1; + st->encoder_buffer = st->Fs/100; + st->lsb_depth = 24; + st->variable_duration = OPUS_FRAMESIZE_ARG; + + /* Delay compensation of 4 ms (2.5 ms for SILK's extra look-ahead + + 1.5 ms for SILK resamplers and stereo prediction) */ + st->delay_compensation = st->Fs/250; + + st->hybrid_stereo_width_Q14 = 1 << 14; + st->prev_HB_gain = Q15ONE; + st->variable_HP_smth2_Q15 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + st->first = 1; + st->mode = MODE_HYBRID; + st->bandwidth = OPUS_BANDWIDTH_FULLBAND; + +#ifndef DISABLE_FLOAT_API + tonality_analysis_init(&st->analysis, st->Fs); + st->analysis.application = st->application; +#endif + + return OPUS_OK; +} + +static unsigned char gen_toc(int mode, int framerate, int bandwidth, int channels) +{ + int period; + unsigned char toc; + period = 0; + while (framerate < 400) + { + framerate <<= 1; + period++; + } + if (mode == MODE_SILK_ONLY) + { + toc = (bandwidth-OPUS_BANDWIDTH_NARROWBAND)<<5; + toc |= (period-2)<<3; + } else if (mode == MODE_CELT_ONLY) + { + int tmp = bandwidth-OPUS_BANDWIDTH_MEDIUMBAND; + if (tmp < 0) + tmp = 0; + toc = 0x80; + toc |= tmp << 5; + toc |= period<<3; + } else /* Hybrid */ + { + toc = 0x60; + toc |= (bandwidth-OPUS_BANDWIDTH_SUPERWIDEBAND)<<4; + toc |= (period-2)<<3; + } + toc |= (channels==2)<<2; + return toc; +} + +#ifndef FIXED_POINT +static void silk_biquad_float( + const opus_val16 *in, /* I: Input signal */ + const opus_int32 *B_Q28, /* I: MA coefficients [3] */ + const opus_int32 *A_Q28, /* I: AR coefficients [2] */ + opus_val32 *S, /* I/O: State vector [2] */ + opus_val16 *out, /* O: Output signal */ + const opus_int32 len, /* I: Signal length (must be even) */ + int stride +) +{ + /* DIRECT FORM II TRANSPOSED (uses 2 element state vector) */ + opus_int k; + opus_val32 vout; + opus_val32 inval; + opus_val32 A[2], B[3]; + + A[0] = (opus_val32)(A_Q28[0] * (1.f/((opus_int32)1<<28))); + A[1] = (opus_val32)(A_Q28[1] * (1.f/((opus_int32)1<<28))); + B[0] = (opus_val32)(B_Q28[0] * (1.f/((opus_int32)1<<28))); + B[1] = (opus_val32)(B_Q28[1] * (1.f/((opus_int32)1<<28))); + B[2] = (opus_val32)(B_Q28[2] * (1.f/((opus_int32)1<<28))); + + /* Negate A_Q28 values and split in two parts */ + + for( k = 0; k < len; k++ ) { + /* S[ 0 ], S[ 1 ]: Q12 */ + inval = in[ k*stride ]; + vout = S[ 0 ] + B[0]*inval; + + S[ 0 ] = S[1] - vout*A[0] + B[1]*inval; + + S[ 1 ] = - vout*A[1] + B[2]*inval + VERY_SMALL; + + /* Scale back to Q0 and saturate */ + out[ k*stride ] = vout; + } +} +#endif + +static void hp_cutoff(const opus_val16 *in, opus_int32 cutoff_Hz, opus_val16 *out, opus_val32 *hp_mem, int len, int channels, opus_int32 Fs, int arch) +{ + opus_int32 B_Q28[ 3 ], A_Q28[ 2 ]; + opus_int32 Fc_Q19, r_Q28, r_Q22; + (void)arch; + + silk_assert( cutoff_Hz <= silk_int32_MAX / SILK_FIX_CONST( 1.5 * 3.14159 / 1000, 19 ) ); + Fc_Q19 = silk_DIV32_16( silk_SMULBB( SILK_FIX_CONST( 1.5 * 3.14159 / 1000, 19 ), cutoff_Hz ), Fs/1000 ); + silk_assert( Fc_Q19 > 0 && Fc_Q19 < 32768 ); + + r_Q28 = SILK_FIX_CONST( 1.0, 28 ) - silk_MUL( SILK_FIX_CONST( 0.92, 9 ), Fc_Q19 ); + + /* b = r * [ 1; -2; 1 ]; */ + /* a = [ 1; -2 * r * ( 1 - 0.5 * Fc^2 ); r^2 ]; */ + B_Q28[ 0 ] = r_Q28; + B_Q28[ 1 ] = silk_LSHIFT( -r_Q28, 1 ); + B_Q28[ 2 ] = r_Q28; + + /* -r * ( 2 - Fc * Fc ); */ + r_Q22 = silk_RSHIFT( r_Q28, 6 ); + A_Q28[ 0 ] = silk_SMULWW( r_Q22, silk_SMULWW( Fc_Q19, Fc_Q19 ) - SILK_FIX_CONST( 2.0, 22 ) ); + A_Q28[ 1 ] = silk_SMULWW( r_Q22, r_Q22 ); + +#ifdef FIXED_POINT + if( channels == 1 ) { + silk_biquad_alt_stride1( in, B_Q28, A_Q28, hp_mem, out, len ); + } else { + silk_biquad_alt_stride2( in, B_Q28, A_Q28, hp_mem, out, len, arch ); + } +#else + silk_biquad_float( in, B_Q28, A_Q28, hp_mem, out, len, channels ); + if( channels == 2 ) { + silk_biquad_float( in+1, B_Q28, A_Q28, hp_mem+2, out+1, len, channels ); + } +#endif +} + +#ifdef FIXED_POINT +static void dc_reject(const opus_val16 *in, opus_int32 cutoff_Hz, opus_val16 *out, opus_val32 *hp_mem, int len, int channels, opus_int32 Fs) +{ + int c, i; + int shift; + + /* Approximates -round(log2(6.3*cutoff_Hz/Fs)) */ + shift=celt_ilog2(Fs/(cutoff_Hz*4)); + for (c=0;csilk_mode.useInBandFEC) { + dred_frac = MIN16(.7f, 3.f*st->silk_mode.packetLossPercentage/100.f); + bitrate_offset = 20000; + } else { + if (st->silk_mode.packetLossPercentage > 5) { + dred_frac = MIN16(.8f, .55f + st->silk_mode.packetLossPercentage/100.f); + } else { + dred_frac = 12*st->silk_mode.packetLossPercentage/100.f; + } + bitrate_offset = 12000; + } + /* Account for the fact that longer packets require less redundancy. */ + dred_frac = dred_frac/(dred_frac + (1-dred_frac)*(frame_size*50.f)/st->Fs); + /* Approximate fit based on a few experiments. Could probably be improved. */ + q0 = IMIN(15, IMAX(4, 51 - 3*EC_ILOG(IMAX(1, bitrate_bps-bitrate_offset)))); + dQ = bitrate_bps-bitrate_offset > 36000 ? 3 : 5; + qmax = 15; + target_dred_bitrate = IMAX(0, (int)(dred_frac*(bitrate_bps-bitrate_offset))); + if (st->dred_duration > 0) { + opus_int32 target_bits = target_dred_bitrate*frame_size/st->Fs; + max_dred_bits = estimate_dred_bitrate(q0, dQ, qmax, st->dred_duration, target_bits, &target_chunks); + } else { + max_dred_bits = 0; + target_chunks=0; + } + dred_bitrate = IMIN(target_dred_bitrate, max_dred_bits*st->Fs/frame_size); + /* If we can't afford enough bits, don't bother with DRED at all. */ + if (target_chunks < 2) + dred_bitrate = 0; + st->dred_q0 = q0; + st->dred_dQ = dQ; + st->dred_qmax = qmax; + st->dred_target_chunks = target_chunks; + return dred_bitrate; +} +#endif + +static opus_int32 user_bitrate_to_bitrate(OpusEncoder *st, int frame_size, int max_data_bytes) +{ + if(!frame_size)frame_size=st->Fs/400; + if (st->user_bitrate_bps==OPUS_AUTO) + return 60*st->Fs/frame_size + st->Fs*st->channels; + else if (st->user_bitrate_bps==OPUS_BITRATE_MAX) + return max_data_bytes*8*st->Fs/frame_size; + else + return st->user_bitrate_bps; +} + +#ifndef DISABLE_FLOAT_API +#ifdef FIXED_POINT +#define PCM2VAL(x) FLOAT2INT16(x) +#else +#define PCM2VAL(x) SCALEIN(x) +#endif + +void downmix_float(const void *_x, opus_val32 *y, int subframe, int offset, int c1, int c2, int C) +{ + const float *x; + int j; + + x = (const float *)_x; + for (j=0;j-1) + { + for (j=0;j-1) + { + for (j=0;j= OPUS_FRAMESIZE_2_5_MS && variable_duration <= OPUS_FRAMESIZE_120_MS) + { + if (variable_duration <= OPUS_FRAMESIZE_40_MS) + new_size = (Fs/400)<<(variable_duration-OPUS_FRAMESIZE_2_5_MS); + else + new_size = (variable_duration-OPUS_FRAMESIZE_2_5_MS-2)*Fs/50; + } + else + return -1; + if (new_size>frame_size) + return -1; + if (400*new_size!=Fs && 200*new_size!=Fs && 100*new_size!=Fs && + 50*new_size!=Fs && 25*new_size!=Fs && 50*new_size!=3*Fs && + 50*new_size!=4*Fs && 50*new_size!=5*Fs && 50*new_size!=6*Fs) + return -1; + return new_size; +} + +opus_val16 compute_stereo_width(const opus_val16 *pcm, int frame_size, opus_int32 Fs, StereoWidthState *mem) +{ + opus_val32 xx, xy, yy; + opus_val16 sqrt_xx, sqrt_yy; + opus_val16 qrrt_xx, qrrt_yy; + int frame_rate; + int i; + opus_val16 short_alpha; + + frame_rate = Fs/frame_size; + short_alpha = Q15ONE - MULT16_16(25, Q15ONE)/IMAX(50,frame_rate); + xx=xy=yy=0; + /* Unroll by 4. The frame size is always a multiple of 4 *except* for + 2.5 ms frames at 12 kHz. Since this setting is very rare (and very + stupid), we just discard the last two samples. */ + for (i=0;iXX += MULT16_32_Q15(short_alpha, xx-mem->XX); + mem->XY += MULT16_32_Q15(short_alpha, xy-mem->XY); + mem->YY += MULT16_32_Q15(short_alpha, yy-mem->YY); + mem->XX = MAX32(0, mem->XX); + mem->XY = MAX32(0, mem->XY); + mem->YY = MAX32(0, mem->YY); + if (MAX32(mem->XX, mem->YY)>QCONST16(8e-4f, 18)) + { + opus_val16 corr; + opus_val16 ldiff; + opus_val16 width; + sqrt_xx = celt_sqrt(mem->XX); + sqrt_yy = celt_sqrt(mem->YY); + qrrt_xx = celt_sqrt(sqrt_xx); + qrrt_yy = celt_sqrt(sqrt_yy); + /* Inter-channel correlation */ + mem->XY = MIN32(mem->XY, sqrt_xx*sqrt_yy); + corr = SHR32(frac_div32(mem->XY,EPSILON+MULT16_16(sqrt_xx,sqrt_yy)),16); + /* Approximate loudness difference */ + ldiff = MULT16_16(Q15ONE, ABS16(qrrt_xx-qrrt_yy))/(EPSILON+qrrt_xx+qrrt_yy); + width = MULT16_16_Q15(celt_sqrt(QCONST32(1.f,30)-MULT16_16(corr,corr)), ldiff); + /* Smoothing over one second */ + mem->smoothed_width += (width-mem->smoothed_width)/frame_rate; + /* Peak follower */ + mem->max_follower = MAX16(mem->max_follower-QCONST16(.02f,15)/frame_rate, mem->smoothed_width); + } + /*printf("%f %f %f %f %f ", corr/(float)Q15ONE, ldiff/(float)Q15ONE, width/(float)Q15ONE, mem->smoothed_width/(float)Q15ONE, mem->max_follower/(float)Q15ONE);*/ + return EXTRACT16(MIN32(Q15ONE, MULT16_16(20, mem->max_follower))); +} + +static int decide_fec(int useInBandFEC, int PacketLoss_perc, int last_fec, int mode, int *bandwidth, opus_int32 rate) +{ + int orig_bandwidth; + if (!useInBandFEC || PacketLoss_perc == 0 || mode == MODE_CELT_ONLY) + return 0; + orig_bandwidth = *bandwidth; + for (;;) + { + opus_int32 hysteresis; + opus_int32 LBRR_rate_thres_bps; + /* Compute threshold for using FEC at the current bandwidth setting */ + LBRR_rate_thres_bps = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND)]; + hysteresis = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND) + 1]; + if (last_fec == 1) LBRR_rate_thres_bps -= hysteresis; + if (last_fec == 0) LBRR_rate_thres_bps += hysteresis; + LBRR_rate_thres_bps = silk_SMULWB( silk_MUL( LBRR_rate_thres_bps, + 125 - silk_min( PacketLoss_perc, 25 ) ), SILK_FIX_CONST( 0.01, 16 ) ); + /* If loss <= 5%, we look at whether we have enough rate to enable FEC. + If loss > 5%, we decrease the bandwidth until we can enable FEC. */ + if (rate > LBRR_rate_thres_bps) + return 1; + else if (PacketLoss_perc <= 5) + return 0; + else if (*bandwidth > OPUS_BANDWIDTH_NARROWBAND) + (*bandwidth)--; + else + break; + } + /* Couldn't find any bandwidth to enable FEC, keep original bandwidth. */ + *bandwidth = orig_bandwidth; + return 0; +} + +static int compute_silk_rate_for_hybrid(int rate, int bandwidth, int frame20ms, int vbr, int fec, int channels) { + int entry; + int i; + int N; + int silk_rate; + static int rate_table[][5] = { + /* |total| |-------- SILK------------| + |-- No FEC -| |--- FEC ---| + 10ms 20ms 10ms 20ms */ + { 0, 0, 0, 0, 0}, + {12000, 10000, 10000, 11000, 11000}, + {16000, 13500, 13500, 15000, 15000}, + {20000, 16000, 16000, 18000, 18000}, + {24000, 18000, 18000, 21000, 21000}, + {32000, 22000, 22000, 28000, 28000}, + {64000, 38000, 38000, 50000, 50000} + }; + /* Do the allocation per-channel. */ + rate /= channels; + entry = 1 + frame20ms + 2*fec; + N = sizeof(rate_table)/sizeof(rate_table[0]); + for (i=1;i rate) break; + } + if (i == N) + { + silk_rate = rate_table[i-1][entry]; + /* For now, just give 50% of the extra bits to SILK. */ + silk_rate += (rate-rate_table[i-1][0])/2; + } else { + opus_int32 lo, hi, x0, x1; + lo = rate_table[i-1][entry]; + hi = rate_table[i][entry]; + x0 = rate_table[i-1][0]; + x1 = rate_table[i][0]; + silk_rate = (lo*(x1-rate) + hi*(rate-x0))/(x1-x0); + } + if (!vbr) + { + /* Tiny boost to SILK for CBR. We should probably tune this better. */ + silk_rate += 100; + } + if (bandwidth==OPUS_BANDWIDTH_SUPERWIDEBAND) + silk_rate += 300; + silk_rate *= channels; + /* Small adjustment for stereo (calibrated for 32 kb/s, haven't tried other bitrates). */ + if (channels == 2 && rate >= 12000) + silk_rate -= 1000; + return silk_rate; +} + +/* Returns the equivalent bitrate corresponding to 20 ms frames, + complexity 10 VBR operation. */ +static opus_int32 compute_equiv_rate(opus_int32 bitrate, int channels, + int frame_rate, int vbr, int mode, int complexity, int loss) +{ + opus_int32 equiv; + equiv = bitrate; + /* Take into account overhead from smaller frames. */ + if (frame_rate > 50) + equiv -= (40*channels+20)*(frame_rate - 50); + /* CBR is about a 8% penalty for both SILK and CELT. */ + if (!vbr) + equiv -= equiv/12; + /* Complexity makes about 10% difference (from 0 to 10) in general. */ + equiv = equiv * (90+complexity)/100; + if (mode == MODE_SILK_ONLY || mode == MODE_HYBRID) + { + /* SILK complexity 0-1 uses the non-delayed-decision NSQ, which + costs about 20%. */ + if (complexity<2) + equiv = equiv*4/5; + equiv -= equiv*loss/(6*loss + 10); + } else if (mode == MODE_CELT_ONLY) { + /* CELT complexity 0-4 doesn't have the pitch filter, which costs + about 10%. */ + if (complexity<5) + equiv = equiv*9/10; + } else { + /* Mode not known yet */ + /* Half the SILK loss*/ + equiv -= equiv*loss/(12*loss + 20); + } + return equiv; +} + +#ifndef DISABLE_FLOAT_API + +int is_digital_silence(const opus_val16* pcm, int frame_size, int channels, int lsb_depth) +{ + int silence = 0; + opus_val32 sample_max = 0; +#ifdef MLP_TRAINING + return 0; +#endif + sample_max = celt_maxabs16(pcm, frame_size*channels); + +#ifdef FIXED_POINT + silence = (sample_max == 0); + (void)lsb_depth; +#else + silence = (sample_max <= (opus_val16) 1 / (1 << lsb_depth)); +#endif + + return silence; +} + +#ifdef FIXED_POINT +static opus_val32 compute_frame_energy(const opus_val16 *pcm, int frame_size, int channels, int arch) +{ + int i; + opus_val32 sample_max; + int max_shift; + int shift; + opus_val32 energy = 0; + int len = frame_size*channels; + (void)arch; + /* Max amplitude in the signal */ + sample_max = celt_maxabs16(pcm, len); + + /* Compute the right shift required in the MAC to avoid an overflow */ + max_shift = celt_ilog2(len); + shift = IMAX(0, (celt_ilog2(1+sample_max) << 1) + max_shift - 28); + + /* Compute the energy */ + for (i=0; i NB_SPEECH_FRAMES_BEFORE_DTX*20*2) + { + if (*nb_no_activity_ms_Q1 <= (NB_SPEECH_FRAMES_BEFORE_DTX + MAX_CONSECUTIVE_DTX)*20*2) + /* Valid frame for DTX! */ + return 1; + else + (*nb_no_activity_ms_Q1) = NB_SPEECH_FRAMES_BEFORE_DTX*20*2; + } + } else + (*nb_no_activity_ms_Q1) = 0; + + return 0; +} + +#endif + +static int compute_redundancy_bytes(opus_int32 max_data_bytes, opus_int32 bitrate_bps, int frame_rate, int channels) +{ + int redundancy_bytes_cap; + int redundancy_bytes; + opus_int32 redundancy_rate; + int base_bits; + opus_int32 available_bits; + base_bits = (40*channels+20); + + /* Equivalent rate for 5 ms frames. */ + redundancy_rate = bitrate_bps + base_bits*(200 - frame_rate); + /* For VBR, further increase the bitrate if we can afford it. It's pretty short + and we'll avoid artefacts. */ + redundancy_rate = 3*redundancy_rate/2; + redundancy_bytes = redundancy_rate/1600; + + /* Compute the max rate we can use given CBR or VBR with cap. */ + available_bits = max_data_bytes*8 - 2*base_bits; + redundancy_bytes_cap = (available_bits*240/(240+48000/frame_rate) + base_bits)/8; + redundancy_bytes = IMIN(redundancy_bytes, redundancy_bytes_cap); + /* It we can't get enough bits for redundancy to be worth it, rely on the decoder PLC. */ + if (redundancy_bytes > 4 + 8*channels) + redundancy_bytes = IMIN(257, redundancy_bytes); + else + redundancy_bytes = 0; + return redundancy_bytes; +} + +static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 max_data_bytes, + int float_api, int first_frame, +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + AnalysisInfo *analysis_info, int is_silence, +#endif + int redundancy, int celt_to_silk, int prefill, + opus_int32 equiv_rate, int to_celt); + +opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api) +{ + void *silk_enc; + CELTEncoder *celt_enc; + int i; + int ret=0; + int prefill=0; + int redundancy = 0; + int celt_to_silk = 0; + int to_celt = 0; + int voice_est; /* Probability of voice in Q7 */ + opus_int32 equiv_rate; + int frame_rate; + opus_int32 max_rate; /* Max bitrate we're allowed to use */ + int curr_bandwidth; + opus_int32 max_data_bytes; /* Max number of bytes we're allowed to use */ + opus_int32 cbr_bytes=-1; + opus_val16 stereo_width; + const CELTMode *celt_mode; +#ifndef DISABLE_FLOAT_API + AnalysisInfo analysis_info; + int analysis_read_pos_bak=-1; + int analysis_read_subframe_bak=-1; + int is_silence = 0; +#endif +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps; +#endif + ALLOC_STACK; + + max_data_bytes = IMIN(1276, out_data_bytes); + + st->rangeFinal = 0; + if (frame_size <= 0 || max_data_bytes <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + /* Cannot encode 100 ms in 1 byte */ + if (max_data_bytes==1 && st->Fs==(frame_size*10)) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + lsb_depth = IMIN(lsb_depth, st->lsb_depth); + + celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode)); +#ifndef DISABLE_FLOAT_API + analysis_info.valid = 0; +#ifdef FIXED_POINT + if (st->silk_mode.complexity >= 10 && st->Fs>=16000) +#else + if (st->silk_mode.complexity >= 7 && st->Fs>=16000) +#endif + { + is_silence = is_digital_silence(pcm, frame_size, st->channels, lsb_depth); + analysis_read_pos_bak = st->analysis.read_pos; + analysis_read_subframe_bak = st->analysis.read_subframe; + run_analysis(&st->analysis, celt_mode, analysis_pcm, analysis_size, frame_size, + c1, c2, analysis_channels, st->Fs, + lsb_depth, downmix, &analysis_info); + + /* Track the peak signal energy */ + if (!is_silence && analysis_info.activity_probability > DTX_ACTIVITY_THRESHOLD) + st->peak_signal_energy = MAX32(MULT16_32_Q15(QCONST16(0.999f, 15), st->peak_signal_energy), + compute_frame_energy(pcm, frame_size, st->channels, st->arch)); + } else if (st->analysis.initialized) { + tonality_analysis_reset(&st->analysis); + } +#else + (void)analysis_pcm; + (void)analysis_size; + (void)c1; + (void)c2; + (void)analysis_channels; + (void)downmix; +#endif + +#ifndef DISABLE_FLOAT_API + /* Reset voice_ratio if this frame is not silent or if analysis is disabled. + * Otherwise, preserve voice_ratio from the last non-silent frame */ + if (!is_silence) + st->voice_ratio = -1; + + st->detected_bandwidth = 0; + if (analysis_info.valid) + { + int analysis_bandwidth; + if (st->signal_type == OPUS_AUTO) + { + float prob; + if (st->prev_mode == 0) + prob = analysis_info.music_prob; + else if (st->prev_mode == MODE_CELT_ONLY) + prob = analysis_info.music_prob_max; + else + prob = analysis_info.music_prob_min; + st->voice_ratio = (int)floor(.5+100*(1-prob)); + } + + analysis_bandwidth = analysis_info.bandwidth; + if (analysis_bandwidth<=12) + st->detected_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + else if (analysis_bandwidth<=14) + st->detected_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + else if (analysis_bandwidth<=16) + st->detected_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + else if (analysis_bandwidth<=18) + st->detected_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + else + st->detected_bandwidth = OPUS_BANDWIDTH_FULLBAND; + } +#else + st->voice_ratio = -1; +#endif + + if (st->channels==2 && st->force_channels!=1) + stereo_width = compute_stereo_width(pcm, frame_size, st->Fs, &st->width_mem); + else + stereo_width = 0; + st->bitrate_bps = user_bitrate_to_bitrate(st, frame_size, max_data_bytes); + + frame_rate = st->Fs/frame_size; + if (!st->use_vbr) + { + /* Multiply by 12 to make sure the division is exact. */ + int frame_rate12 = 12*st->Fs/frame_size; + /* We need to make sure that "int" values always fit in 16 bits. */ + cbr_bytes = IMIN( (12*st->bitrate_bps/8 + frame_rate12/2)/frame_rate12, max_data_bytes); + st->bitrate_bps = cbr_bytes*(opus_int32)frame_rate12*8/12; + /* Make sure we provide at least one byte to avoid failing. */ + max_data_bytes = IMAX(1, cbr_bytes); + } +#ifdef ENABLE_DRED + /* Allocate some of the bits to DRED if needed. */ + dred_bitrate_bps = compute_dred_bitrate(st, st->bitrate_bps, frame_size); + st->bitrate_bps -= dred_bitrate_bps; +#endif + if (max_data_bytes<3 || st->bitrate_bps < 3*frame_rate*8 + || (frame_rate<50 && (max_data_bytes*frame_rate<300 || st->bitrate_bps < 2400))) + { + /*If the space is too low to do something useful, emit 'PLC' frames.*/ + int tocmode = st->mode; + int bw = st->bandwidth == 0 ? OPUS_BANDWIDTH_NARROWBAND : st->bandwidth; + int packet_code = 0; + int num_multiframes = 0; + + if (tocmode==0) + tocmode = MODE_SILK_ONLY; + if (frame_rate>100) + tocmode = MODE_CELT_ONLY; + /* 40 ms -> 2 x 20 ms if in CELT_ONLY or HYBRID mode */ + if (frame_rate==25 && tocmode!=MODE_SILK_ONLY) + { + frame_rate = 50; + packet_code = 1; + } + + /* >= 60 ms frames */ + if (frame_rate<=16) + { + /* 1 x 60 ms, 2 x 40 ms, 2 x 60 ms */ + if (out_data_bytes==1 || (tocmode==MODE_SILK_ONLY && frame_rate!=10)) + { + tocmode = MODE_SILK_ONLY; + + packet_code = frame_rate <= 12; + frame_rate = frame_rate == 12 ? 25 : 16; + } + else + { + num_multiframes = 50/frame_rate; + frame_rate = 50; + packet_code = 3; + } + } + + if(tocmode==MODE_SILK_ONLY&&bw>OPUS_BANDWIDTH_WIDEBAND) + bw=OPUS_BANDWIDTH_WIDEBAND; + else if (tocmode==MODE_CELT_ONLY&&bw==OPUS_BANDWIDTH_MEDIUMBAND) + bw=OPUS_BANDWIDTH_NARROWBAND; + else if (tocmode==MODE_HYBRID&&bw<=OPUS_BANDWIDTH_SUPERWIDEBAND) + bw=OPUS_BANDWIDTH_SUPERWIDEBAND; + + data[0] = gen_toc(tocmode, frame_rate, bw, st->stream_channels); + data[0] |= packet_code; + + ret = packet_code <= 1 ? 1 : 2; + + max_data_bytes = IMAX(max_data_bytes, ret); + + if (packet_code==3) + data[1] = num_multiframes; + + if (!st->use_vbr) + { + ret = opus_packet_pad(data, ret, max_data_bytes); + if (ret == OPUS_OK) + ret = max_data_bytes; + else + ret = OPUS_INTERNAL_ERROR; + } + RESTORE_STACK; + return ret; + } + max_rate = frame_rate*max_data_bytes*8; + + /* Equivalent 20-ms rate for mode/channel/bandwidth decisions */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + if (st->signal_type == OPUS_SIGNAL_VOICE) + voice_est = 127; + else if (st->signal_type == OPUS_SIGNAL_MUSIC) + voice_est = 0; + else if (st->voice_ratio >= 0) + { + voice_est = st->voice_ratio*327>>8; + /* For AUDIO, never be more than 90% confident of having speech */ + if (st->application == OPUS_APPLICATION_AUDIO) + voice_est = IMIN(voice_est, 115); + } else if (st->application == OPUS_APPLICATION_VOIP) + voice_est = 115; + else + voice_est = 48; + + if (st->force_channels!=OPUS_AUTO && st->channels == 2) + { + st->stream_channels = st->force_channels; + } else { +#ifdef FUZZING + (void)stereo_music_threshold; + (void)stereo_voice_threshold; + /* Random mono/stereo decision */ + if (st->channels == 2 && (rand()&0x1F)==0) + st->stream_channels = 3-st->stream_channels; +#else + /* Rate-dependent mono-stereo decision */ + if (st->channels == 2) + { + opus_int32 stereo_threshold; + stereo_threshold = stereo_music_threshold + ((voice_est*voice_est*(stereo_voice_threshold-stereo_music_threshold))>>14); + if (st->stream_channels == 2) + stereo_threshold -= 1000; + else + stereo_threshold += 1000; + st->stream_channels = (equiv_rate > stereo_threshold) ? 2 : 1; + } else { + st->stream_channels = st->channels; + } +#endif + } + /* Update equivalent rate for channels decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + /* Allow SILK DTX if DTX is enabled but the generalized DTX cannot be used, + e.g. because of the complexity setting or sample rate. */ +#ifndef DISABLE_FLOAT_API + st->silk_mode.useDTX = st->use_dtx && !(analysis_info.valid || is_silence); +#else + st->silk_mode.useDTX = st->use_dtx; +#endif + + /* Mode selection depending on application and signal type */ + if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) + { + st->mode = MODE_CELT_ONLY; + } else if (st->user_forced_mode == OPUS_AUTO) + { +#ifdef FUZZING + (void)stereo_width; + (void)mode_thresholds; + /* Random mode switching */ + if ((rand()&0xF)==0) + { + if ((rand()&0x1)==0) + st->mode = MODE_CELT_ONLY; + else + st->mode = MODE_SILK_ONLY; + } else { + if (st->prev_mode==MODE_CELT_ONLY) + st->mode = MODE_CELT_ONLY; + else + st->mode = MODE_SILK_ONLY; + } +#else + opus_int32 mode_voice, mode_music; + opus_int32 threshold; + + /* Interpolate based on stereo width */ + mode_voice = (opus_int32)(MULT16_32_Q15(Q15ONE-stereo_width,mode_thresholds[0][0]) + + MULT16_32_Q15(stereo_width,mode_thresholds[1][0])); + mode_music = (opus_int32)(MULT16_32_Q15(Q15ONE-stereo_width,mode_thresholds[1][1]) + + MULT16_32_Q15(stereo_width,mode_thresholds[1][1])); + /* Interpolate based on speech/music probability */ + threshold = mode_music + ((voice_est*voice_est*(mode_voice-mode_music))>>14); + /* Bias towards SILK for VoIP because of some useful features */ + if (st->application == OPUS_APPLICATION_VOIP) + threshold += 8000; + + /*printf("%f %d\n", stereo_width/(float)Q15ONE, threshold);*/ + /* Hysteresis */ + if (st->prev_mode == MODE_CELT_ONLY) + threshold -= 4000; + else if (st->prev_mode>0) + threshold += 4000; + + st->mode = (equiv_rate >= threshold) ? MODE_CELT_ONLY: MODE_SILK_ONLY; + + /* When FEC is enabled and there's enough packet loss, use SILK. + Unless the FEC is set to 2, in which case we don't switch to SILK if we're confident we have music. */ + if (st->silk_mode.useInBandFEC && st->silk_mode.packetLossPercentage > (128-voice_est)>>4 && (st->fec_config != 2 || voice_est > 25)) + st->mode = MODE_SILK_ONLY; + /* When encoding voice and DTX is enabled but the generalized DTX cannot be used, + use SILK in order to make use of its DTX. */ + if (st->silk_mode.useDTX && voice_est > 100) + st->mode = MODE_SILK_ONLY; +#endif + + /* If max_data_bytes represents less than 6 kb/s, switch to CELT-only mode */ + if (max_data_bytes < (frame_rate > 50 ? 9000 : 6000)*frame_size / (st->Fs * 8)) + st->mode = MODE_CELT_ONLY; + } else { + st->mode = st->user_forced_mode; + } + + /* Override the chosen mode to make sure we meet the requested frame size */ + if (st->mode != MODE_CELT_ONLY && frame_size < st->Fs/100) + st->mode = MODE_CELT_ONLY; + if (st->lfe) + st->mode = MODE_CELT_ONLY; + + if (st->prev_mode > 0 && + ((st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) || + (st->mode == MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY))) + { + redundancy = 1; + celt_to_silk = (st->mode != MODE_CELT_ONLY); + if (!celt_to_silk) + { + /* Switch to SILK/hybrid if frame size is 10 ms or more*/ + if (frame_size >= st->Fs/100) + { + st->mode = st->prev_mode; + to_celt = 1; + } else { + redundancy=0; + } + } + } + + /* When encoding multiframes, we can ask for a switch to CELT only in the last frame. This switch + * is processed above as the requested mode shouldn't interrupt stereo->mono transition. */ + if (st->stream_channels == 1 && st->prev_channels ==2 && st->silk_mode.toMono==0 + && st->mode != MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY) + { + /* Delay stereo->mono transition by two frames so that SILK can do a smooth downmix */ + st->silk_mode.toMono = 1; + st->stream_channels = 2; + } else { + st->silk_mode.toMono = 0; + } + + /* Update equivalent rate with mode decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, st->mode, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + + if (st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) + { + silk_EncControlStruct dummy; + silk_InitEncoder( silk_enc, st->arch, &dummy); + prefill=1; + } + + /* Automatic (rate-dependent) bandwidth selection */ + if (st->mode == MODE_CELT_ONLY || st->first || st->silk_mode.allowBandwidthSwitch) + { + const opus_int32 *voice_bandwidth_thresholds, *music_bandwidth_thresholds; + opus_int32 bandwidth_thresholds[8]; + int bandwidth = OPUS_BANDWIDTH_FULLBAND; + + if (st->channels==2 && st->force_channels!=1) + { + voice_bandwidth_thresholds = stereo_voice_bandwidth_thresholds; + music_bandwidth_thresholds = stereo_music_bandwidth_thresholds; + } else { + voice_bandwidth_thresholds = mono_voice_bandwidth_thresholds; + music_bandwidth_thresholds = mono_music_bandwidth_thresholds; + } + /* Interpolate bandwidth thresholds depending on voice estimation */ + for (i=0;i<8;i++) + { + bandwidth_thresholds[i] = music_bandwidth_thresholds[i] + + ((voice_est*voice_est*(voice_bandwidth_thresholds[i]-music_bandwidth_thresholds[i]))>>14); + } + do { + int threshold, hysteresis; + threshold = bandwidth_thresholds[2*(bandwidth-OPUS_BANDWIDTH_MEDIUMBAND)]; + hysteresis = bandwidth_thresholds[2*(bandwidth-OPUS_BANDWIDTH_MEDIUMBAND)+1]; + if (!st->first) + { + if (st->auto_bandwidth >= bandwidth) + threshold -= hysteresis; + else + threshold += hysteresis; + } + if (equiv_rate >= threshold) + break; + } while (--bandwidth>OPUS_BANDWIDTH_NARROWBAND); + /* We don't use mediumband anymore, except when explicitly requested or during + mode transitions. */ + if (bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + bandwidth = OPUS_BANDWIDTH_WIDEBAND; + st->bandwidth = st->auto_bandwidth = bandwidth; + /* Prevents any transition to SWB/FB until the SILK layer has fully + switched to WB mode and turned the variable LP filter off */ + if (!st->first && st->mode != MODE_CELT_ONLY && !st->silk_mode.inWBmodeWithoutVariableLP && st->bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + } + + if (st->bandwidth>st->max_bandwidth) + st->bandwidth = st->max_bandwidth; + + if (st->user_bandwidth != OPUS_AUTO) + st->bandwidth = st->user_bandwidth; + + /* This prevents us from using hybrid at unsafe CBR/max rates */ + if (st->mode != MODE_CELT_ONLY && max_rate < 15000) + { + st->bandwidth = IMIN(st->bandwidth, OPUS_BANDWIDTH_WIDEBAND); + } + + /* Prevents Opus from wasting bits on frequencies that are above + the Nyquist rate of the input signal */ + if (st->Fs <= 24000 && st->bandwidth > OPUS_BANDWIDTH_SUPERWIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + if (st->Fs <= 16000 && st->bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + if (st->Fs <= 12000 && st->bandwidth > OPUS_BANDWIDTH_MEDIUMBAND) + st->bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + if (st->Fs <= 8000 && st->bandwidth > OPUS_BANDWIDTH_NARROWBAND) + st->bandwidth = OPUS_BANDWIDTH_NARROWBAND; +#ifndef DISABLE_FLOAT_API + /* Use detected bandwidth to reduce the encoded bandwidth. */ + if (st->detected_bandwidth && st->user_bandwidth == OPUS_AUTO) + { + int min_detected_bandwidth; + /* Makes bandwidth detection more conservative just in case the detector + gets it wrong when we could have coded a high bandwidth transparently. + When operating in SILK/hybrid mode, we don't go below wideband to avoid + more complicated switches that require redundancy. */ + if (equiv_rate <= 18000*st->stream_channels && st->mode == MODE_CELT_ONLY) + min_detected_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + else if (equiv_rate <= 24000*st->stream_channels && st->mode == MODE_CELT_ONLY) + min_detected_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + else if (equiv_rate <= 30000*st->stream_channels) + min_detected_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + else if (equiv_rate <= 44000*st->stream_channels) + min_detected_bandwidth = OPUS_BANDWIDTH_SUPERWIDEBAND; + else + min_detected_bandwidth = OPUS_BANDWIDTH_FULLBAND; + + st->detected_bandwidth = IMAX(st->detected_bandwidth, min_detected_bandwidth); + st->bandwidth = IMIN(st->bandwidth, st->detected_bandwidth); + } +#endif + st->silk_mode.LBRR_coded = decide_fec(st->silk_mode.useInBandFEC, st->silk_mode.packetLossPercentage, + st->silk_mode.LBRR_coded, st->mode, &st->bandwidth, equiv_rate); + celt_encoder_ctl(celt_enc, OPUS_SET_LSB_DEPTH(lsb_depth)); + + /* CELT mode doesn't support mediumband, use wideband instead */ + if (st->mode == MODE_CELT_ONLY && st->bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + st->bandwidth = OPUS_BANDWIDTH_WIDEBAND; + if (st->lfe) + st->bandwidth = OPUS_BANDWIDTH_NARROWBAND; + + curr_bandwidth = st->bandwidth; + + /* Chooses the appropriate mode for speech + *NEVER* switch to/from CELT-only mode here as this will invalidate some assumptions */ + if (st->mode == MODE_SILK_ONLY && curr_bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_HYBRID; + if (st->mode == MODE_HYBRID && curr_bandwidth <= OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_SILK_ONLY; + + /* Can't support higher than >60 ms frames, and >20 ms when in Hybrid or CELT-only modes */ + if ((frame_size > st->Fs/50 && (st->mode != MODE_SILK_ONLY)) || frame_size > 3*st->Fs/50) + { + int enc_frame_size; + int nb_frames; + VARDECL(unsigned char, tmp_data); + VARDECL(OpusRepacketizer, rp); + int max_header_bytes; + opus_int32 repacketize_len; + opus_int32 max_len_sum; + opus_int32 tot_size=0; + unsigned char *curr_data; + int tmp_len; + int dtx_count = 0; + + if (st->mode == MODE_SILK_ONLY) + { + if (frame_size == 2*st->Fs/25) /* 80 ms -> 2x 40 ms */ + enc_frame_size = st->Fs/25; + else if (frame_size == 3*st->Fs/25) /* 120 ms -> 2x 60 ms */ + enc_frame_size = 3*st->Fs/50; + else /* 100 ms -> 5x 20 ms */ + enc_frame_size = st->Fs/50; + } + else + enc_frame_size = st->Fs/50; + + nb_frames = frame_size/enc_frame_size; + +#ifndef DISABLE_FLOAT_API + if (analysis_read_pos_bak!= -1) + { + /* Reset analysis position to the beginning of the first frame so we + can use it one frame at a time. */ + st->analysis.read_pos = analysis_read_pos_bak; + st->analysis.read_subframe = analysis_read_subframe_bak; + } +#endif + + /* Worst cases: + * 2 frames: Code 2 with different compressed sizes + * >2 frames: Code 3 VBR */ + max_header_bytes = nb_frames == 2 ? 3 : (2+(nb_frames-1)*2); + + if (st->use_vbr || st->user_bitrate_bps==OPUS_BITRATE_MAX) + repacketize_len = out_data_bytes; + else { + celt_assert(cbr_bytes>=0); + repacketize_len = IMIN(cbr_bytes, out_data_bytes); + } + max_len_sum = nb_frames + repacketize_len - max_header_bytes; + + ALLOC(tmp_data, max_len_sum, unsigned char); + curr_data = tmp_data; + ALLOC(rp, 1, OpusRepacketizer); + opus_repacketizer_init(rp); + + + int bak_to_mono = st->silk_mode.toMono; + if (bak_to_mono) + st->force_channels = 1; + else + st->prev_channels = st->stream_channels; + + for (i=0;isilk_mode.toMono = 0; + st->nonfinal_frame = i<(nb_frames-1); + + /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */ + frame_to_celt = to_celt && i==nb_frames-1; + frame_redundancy = redundancy && (frame_to_celt || (!to_celt && i==0)); + + curr_max = IMIN(3*st->bitrate_bps/(3*8*st->Fs/enc_frame_size), max_len_sum/nb_frames); +#ifdef ENABLE_DRED + curr_max = IMIN(curr_max, (max_len_sum-3*dred_bitrate_bps/(3*8*st->Fs/frame_size))/nb_frames); + if (first_frame) curr_max += 3*dred_bitrate_bps/(3*8*st->Fs/frame_size); +#endif + curr_max = IMIN(max_len_sum-tot_size, curr_max); +#ifndef DISABLE_FLOAT_API + if (analysis_read_pos_bak != -1) { + is_silence = is_digital_silence(pcm, frame_size, st->channels, lsb_depth); + /* Get analysis for current frame. */ + tonality_get_info(&st->analysis, &analysis_info, enc_frame_size); + } +#endif + + tmp_len = opus_encode_frame_native(st, pcm+i*(st->channels*enc_frame_size), enc_frame_size, curr_data, curr_max, float_api, first_frame, +#ifdef ENABLE_DRED + dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + &analysis_info, + is_silence, +#endif + frame_redundancy, celt_to_silk, prefill, + equiv_rate, frame_to_celt + ); + if (tmp_len<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } else if (tmp_len==1) { + dtx_count++; + } + ret = opus_repacketizer_cat(rp, curr_data, tmp_len); + + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + tot_size += tmp_len; + curr_data += tmp_len; + } + ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr && (dtx_count != nb_frames), NULL, 0); + if (ret<0) + { + ret = OPUS_INTERNAL_ERROR; + } + st->silk_mode.toMono = bak_to_mono; + RESTORE_STACK; + return ret; + } else { + ret = opus_encode_frame_native(st, pcm, frame_size, data, max_data_bytes, float_api, 1, +#ifdef ENABLE_DRED + dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + &analysis_info, + is_silence, +#endif + redundancy, celt_to_silk, prefill, + equiv_rate, to_celt + ); + RESTORE_STACK; + return ret; + } +} + +static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 max_data_bytes, + int float_api, int first_frame, +#ifdef ENABLE_DRED + opus_int32 dred_bitrate_bps, +#endif +#ifndef DISABLE_FLOAT_API + AnalysisInfo *analysis_info, int is_silence, +#endif + int redundancy, int celt_to_silk, int prefill, + opus_int32 equiv_rate, int to_celt) +{ + void *silk_enc; + CELTEncoder *celt_enc; + const CELTMode *celt_mode; + int i; + int ret=0; + opus_int32 nBytes; + ec_enc enc; + int bytes_target; + int start_band = 0; + int redundancy_bytes = 0; /* Number of bytes to use for redundancy frame */ + int nb_compr_bytes; + opus_uint32 redundant_rng = 0; + int cutoff_Hz; + int hp_freq_smth1; + opus_val16 HB_gain; + int apply_padding; + int frame_rate; + int curr_bandwidth; + int delay_compensation; + int total_buffer; + opus_int activity = VAD_NO_DECISION; + VARDECL(opus_val16, pcm_buf); + VARDECL(opus_val16, tmp_prefill); + SAVE_STACK; + + st->rangeFinal = 0; + silk_enc = (char*)st+st->silk_enc_offset; + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode)); + curr_bandwidth = st->bandwidth; + if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) + delay_compensation = 0; + else + delay_compensation = st->delay_compensation; + total_buffer = delay_compensation; + + frame_rate = st->Fs/frame_size; + +#ifndef DISABLE_FLOAT_API + if (is_silence) + { + activity = !is_silence; + } else if (analysis_info->valid) + { + activity = analysis_info->activity_probability >= DTX_ACTIVITY_THRESHOLD; + if (!activity) + { + /* Mark as active if this noise frame is sufficiently loud */ + opus_val32 noise_energy = compute_frame_energy(pcm, frame_size, st->channels, st->arch); + activity = st->peak_signal_energy < (PSEUDO_SNR_THRESHOLD * noise_energy); + } + } +#endif + + /* For the first frame at a new SILK bandwidth */ + if (st->silk_bw_switch) + { + redundancy = 1; + celt_to_silk = 1; + st->silk_bw_switch = 0; + /* Do a prefill without resetting the sampling rate control. */ + prefill=2; + } + + /* If we decided to go with CELT, make sure redundancy is off, no matter what + we decided earlier. */ + if (st->mode == MODE_CELT_ONLY) + redundancy = 0; + + if (redundancy) + { + redundancy_bytes = compute_redundancy_bytes(max_data_bytes, st->bitrate_bps, frame_rate, st->stream_channels); + if (redundancy_bytes == 0) + redundancy = 0; + } + + /* printf("%d %d %d %d\n", st->bitrate_bps, st->stream_channels, st->mode, curr_bandwidth); */ + bytes_target = IMIN(max_data_bytes-redundancy_bytes, st->bitrate_bps * frame_size / (st->Fs * 8)) - 1; + + data += 1; + + ec_enc_init(&enc, data, max_data_bytes-1); + + ALLOC(pcm_buf, (total_buffer+frame_size)*st->channels, opus_val16); + OPUS_COPY(pcm_buf, &st->delay_buffer[(st->encoder_buffer-total_buffer)*st->channels], total_buffer*st->channels); + + if (st->mode == MODE_CELT_ONLY) + hp_freq_smth1 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + else + hp_freq_smth1 = ((silk_encoder*)silk_enc)->state_Fxx[0].sCmn.variable_HP_smth1_Q15; + + st->variable_HP_smth2_Q15 = silk_SMLAWB( st->variable_HP_smth2_Q15, + hp_freq_smth1 - st->variable_HP_smth2_Q15, SILK_FIX_CONST( VARIABLE_HP_SMTH_COEF2, 16 ) ); + + /* convert from log scale to Hertz */ + cutoff_Hz = silk_log2lin( silk_RSHIFT( st->variable_HP_smth2_Q15, 8 ) ); + + if (st->application == OPUS_APPLICATION_VOIP) + { + hp_cutoff(pcm, cutoff_Hz, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs, st->arch); + +#ifdef ENABLE_OSCE_TRAINING_DATA + /* write out high pass filtered clean signal*/ + static FILE *fout =NULL; + if (fout == NULL) + { + fout = fopen("clean_hp.s16", "wb"); + } + + { + int idx; + opus_int16 tmp; + for (idx = 0; idx < frame_size; idx++) + { + tmp = (opus_int16) (32768 * pcm_buf[total_buffer + idx] + 0.5f); + fwrite(&tmp, sizeof(tmp), 1, fout); + } + } +#endif + } else { + dc_reject(pcm, 3, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs); + } +#ifndef FIXED_POINT + if (float_api) + { + opus_val32 sum; + sum = celt_inner_prod(&pcm_buf[total_buffer*st->channels], &pcm_buf[total_buffer*st->channels], frame_size*st->channels, st->arch); + /* This should filter out both NaNs and ridiculous signals that could + cause NaNs further down. */ + if (!(sum < 1e9f) || celt_isnan(sum)) + { + OPUS_CLEAR(&pcm_buf[total_buffer*st->channels], frame_size*st->channels); + st->hp_mem[0] = st->hp_mem[1] = st->hp_mem[2] = st->hp_mem[3] = 0; + } + } +#else + (void)float_api; +#endif + +#ifdef ENABLE_DRED + if ( st->dred_duration > 0 && st->dred_encoder.loaded ) { + int frame_size_400Hz; + /* DRED Encoder */ + dred_compute_latents( &st->dred_encoder, &pcm_buf[total_buffer*st->channels], frame_size, total_buffer, st->arch ); + frame_size_400Hz = frame_size*400/st->Fs; + OPUS_MOVE(&st->activity_mem[frame_size_400Hz], st->activity_mem, 4*DRED_MAX_FRAMES-frame_size_400Hz); + for (i=0;iactivity_mem[i] = activity; + } else { + st->dred_encoder.latents_buffer_fill = 0; + OPUS_CLEAR(st->activity_mem, DRED_MAX_FRAMES); + } +#endif + + /* SILK processing */ + HB_gain = Q15ONE; + if (st->mode != MODE_CELT_ONLY) + { + opus_int32 total_bitRate, celt_rate; +#ifdef FIXED_POINT + const opus_int16 *pcm_silk; +#else + VARDECL(opus_int16, pcm_silk); + ALLOC(pcm_silk, st->channels*frame_size, opus_int16); +#endif + + /* Distribute bits between SILK and CELT */ + total_bitRate = 8 * bytes_target * frame_rate; + if( st->mode == MODE_HYBRID ) { + /* Base rate for SILK */ + st->silk_mode.bitRate = compute_silk_rate_for_hybrid(total_bitRate, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded, + st->stream_channels); + if (!st->energy_masking) + { + /* Increasingly attenuate high band when it gets allocated fewer bits */ + celt_rate = total_bitRate - st->silk_mode.bitRate; + HB_gain = Q15ONE - SHR32(celt_exp2(-celt_rate * QCONST16(1.f/1024, 10)), 1); + } + } else { + /* SILK gets all bits */ + st->silk_mode.bitRate = total_bitRate; + } + + /* Surround masking for SILK */ + if (st->energy_masking && st->use_vbr && !st->lfe) + { + opus_val32 mask_sum=0; + opus_val16 masking_depth; + opus_int32 rate_offset; + int c; + int end = 17; + opus_int16 srate = 16000; + if (st->bandwidth == OPUS_BANDWIDTH_NARROWBAND) + { + end = 13; + srate = 8000; + } else if (st->bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) + { + end = 15; + srate = 12000; + } + for (c=0;cchannels;c++) + { + for(i=0;ienergy_masking[21*c+i], + QCONST16(.5f, DB_SHIFT)), -QCONST16(2.0f, DB_SHIFT)); + if (mask > 0) + mask = HALF16(mask); + mask_sum += mask; + } + } + /* Conservative rate reduction, we cut the masking in half */ + masking_depth = mask_sum / end*st->channels; + masking_depth += QCONST16(.2f, DB_SHIFT); + rate_offset = (opus_int32)PSHR32(MULT16_16(srate, masking_depth), DB_SHIFT); + rate_offset = MAX32(rate_offset, -2*st->silk_mode.bitRate/3); + /* Split the rate change between the SILK and CELT part for hybrid. */ + if (st->bandwidth==OPUS_BANDWIDTH_SUPERWIDEBAND || st->bandwidth==OPUS_BANDWIDTH_FULLBAND) + st->silk_mode.bitRate += 3*rate_offset/5; + else + st->silk_mode.bitRate += rate_offset; + } + + st->silk_mode.payloadSize_ms = 1000 * frame_size / st->Fs; + st->silk_mode.nChannelsAPI = st->channels; + st->silk_mode.nChannelsInternal = st->stream_channels; + if (curr_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.desiredInternalSampleRate = 8000; + } else if (curr_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.desiredInternalSampleRate = 12000; + } else { + celt_assert( st->mode == MODE_HYBRID || curr_bandwidth == OPUS_BANDWIDTH_WIDEBAND ); + st->silk_mode.desiredInternalSampleRate = 16000; + } + if( st->mode == MODE_HYBRID ) { + /* Don't allow bandwidth reduction at lowest bitrates in hybrid mode */ + st->silk_mode.minInternalSampleRate = 16000; + } else { + st->silk_mode.minInternalSampleRate = 8000; + } + + st->silk_mode.maxInternalSampleRate = 16000; + if (st->mode == MODE_SILK_ONLY) + { + opus_int32 effective_max_rate = frame_rate*max_data_bytes*8; + if (frame_rate > 50) + effective_max_rate = effective_max_rate*2/3; + if (effective_max_rate < 8000) + { + st->silk_mode.maxInternalSampleRate = 12000; + st->silk_mode.desiredInternalSampleRate = IMIN(12000, st->silk_mode.desiredInternalSampleRate); + } + if (effective_max_rate < 7000) + { + st->silk_mode.maxInternalSampleRate = 8000; + st->silk_mode.desiredInternalSampleRate = IMIN(8000, st->silk_mode.desiredInternalSampleRate); + } + } + + st->silk_mode.useCBR = !st->use_vbr; + + /* Call SILK encoder for the low band */ + + /* Max bits for SILK, counting ToC, redundancy bytes, and optionally redundancy. */ + st->silk_mode.maxBits = (max_data_bytes-1)*8; + if (redundancy && redundancy_bytes >= 2) + { + /* Counting 1 bit for redundancy position and 20 bits for flag+size (only for hybrid). */ + st->silk_mode.maxBits -= redundancy_bytes*8 + 1; + if (st->mode == MODE_HYBRID) + st->silk_mode.maxBits -= 20; + } + if (st->silk_mode.useCBR) + { + /* When we're in CBR mode, but we have non-SILK data to encode, switch SILK to VBR with cap to + save on complexity. Any variations will be absorbed by CELT and/or DRED and we can still + produce a constant bitrate without wasting bits. */ +#ifdef ENABLE_DRED + if (st->mode == MODE_HYBRID || dred_bitrate_bps > 0) +#else + if (st->mode == MODE_HYBRID) +#endif + { + /* Allow SILK to steal up to 25% of the remaining bits */ + opus_int16 other_bits = IMAX(0, st->silk_mode.maxBits - st->silk_mode.bitRate * frame_size / st->Fs); + st->silk_mode.maxBits = IMAX(0, st->silk_mode.maxBits - other_bits*3/4); + st->silk_mode.useCBR = 0; + } + } else { + /* Constrained VBR. */ + if (st->mode == MODE_HYBRID) + { + /* Compute SILK bitrate corresponding to the max total bits available */ + opus_int32 maxBitRate = compute_silk_rate_for_hybrid(st->silk_mode.maxBits*st->Fs / frame_size, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded, + st->stream_channels); + st->silk_mode.maxBits = maxBitRate * frame_size / st->Fs; + } + } + + if (prefill) + { + opus_int32 zero=0; + int prefill_offset; + /* Use a smooth onset for the SILK prefill to avoid the encoder trying to encode + a discontinuity. The exact location is what we need to avoid leaving any "gap" + in the audio when mixing with the redundant CELT frame. Here we can afford to + overwrite st->delay_buffer because the only thing that uses it before it gets + rewritten is tmp_prefill[] and even then only the part after the ramp really + gets used (rather than sent to the encoder and discarded) */ + prefill_offset = st->channels*(st->encoder_buffer-st->delay_compensation-st->Fs/400); + gain_fade(st->delay_buffer+prefill_offset, st->delay_buffer+prefill_offset, + 0, Q15ONE, celt_mode->overlap, st->Fs/400, st->channels, celt_mode->window, st->Fs); + OPUS_CLEAR(st->delay_buffer, prefill_offset); +#ifdef FIXED_POINT + pcm_silk = st->delay_buffer; +#else + for (i=0;iencoder_buffer*st->channels;i++) + pcm_silk[i] = FLOAT2INT16(st->delay_buffer[i]); +#endif + silk_Encode( silk_enc, &st->silk_mode, pcm_silk, st->encoder_buffer, NULL, &zero, prefill, activity ); + /* Prevent a second switch in the real encode call. */ + st->silk_mode.opusCanSwitch = 0; + } + +#ifdef FIXED_POINT + pcm_silk = pcm_buf+total_buffer*st->channels; +#else + for (i=0;ichannels;i++) + pcm_silk[i] = FLOAT2INT16(pcm_buf[total_buffer*st->channels + i]); +#endif + ret = silk_Encode( silk_enc, &st->silk_mode, pcm_silk, frame_size, &enc, &nBytes, 0, activity ); + if( ret ) { + /*fprintf (stderr, "SILK encode error: %d\n", ret);*/ + /* Handle error */ + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + + /* Extract SILK internal bandwidth for signaling in first byte */ + if( st->mode == MODE_SILK_ONLY ) { + if( st->silk_mode.internalSampleRate == 8000 ) { + curr_bandwidth = OPUS_BANDWIDTH_NARROWBAND; + } else if( st->silk_mode.internalSampleRate == 12000 ) { + curr_bandwidth = OPUS_BANDWIDTH_MEDIUMBAND; + } else if( st->silk_mode.internalSampleRate == 16000 ) { + curr_bandwidth = OPUS_BANDWIDTH_WIDEBAND; + } + } else { + celt_assert( st->silk_mode.internalSampleRate == 16000 ); + } + + st->silk_mode.opusCanSwitch = st->silk_mode.switchReady && !st->nonfinal_frame; + + if (nBytes==0) + { + st->rangeFinal = 0; + data[-1] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + + /* FIXME: How do we allocate the redundancy for CBR? */ + if (st->silk_mode.opusCanSwitch) + { + redundancy_bytes = compute_redundancy_bytes(max_data_bytes, st->bitrate_bps, frame_rate, st->stream_channels); + redundancy = (redundancy_bytes != 0); + celt_to_silk = 0; + st->silk_bw_switch = 1; + } + } + + /* CELT processing */ + { + int endband=21; + + switch(curr_bandwidth) + { + case OPUS_BANDWIDTH_NARROWBAND: + endband = 13; + break; + case OPUS_BANDWIDTH_MEDIUMBAND: + case OPUS_BANDWIDTH_WIDEBAND: + endband = 17; + break; + case OPUS_BANDWIDTH_SUPERWIDEBAND: + endband = 19; + break; + case OPUS_BANDWIDTH_FULLBAND: + endband = 21; + break; + } + celt_encoder_ctl(celt_enc, CELT_SET_END_BAND(endband)); + celt_encoder_ctl(celt_enc, CELT_SET_CHANNELS(st->stream_channels)); + } + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + if (st->mode != MODE_SILK_ONLY) + { + opus_val32 celt_pred=2; + /* We may still decide to disable prediction later */ + if (st->silk_mode.reducedDependency) + celt_pred = 0; + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(celt_pred)); + } + + ALLOC(tmp_prefill, st->channels*st->Fs/400, opus_val16); + if (st->mode != MODE_SILK_ONLY && st->mode != st->prev_mode && st->prev_mode > 0) + { + OPUS_COPY(tmp_prefill, &st->delay_buffer[(st->encoder_buffer-total_buffer-st->Fs/400)*st->channels], st->channels*st->Fs/400); + } + + if (st->channels*(st->encoder_buffer-(frame_size+total_buffer)) > 0) + { + OPUS_MOVE(st->delay_buffer, &st->delay_buffer[st->channels*frame_size], st->channels*(st->encoder_buffer-frame_size-total_buffer)); + OPUS_COPY(&st->delay_buffer[st->channels*(st->encoder_buffer-frame_size-total_buffer)], + &pcm_buf[0], + (frame_size+total_buffer)*st->channels); + } else { + OPUS_COPY(st->delay_buffer, &pcm_buf[(frame_size+total_buffer-st->encoder_buffer)*st->channels], st->encoder_buffer*st->channels); + } + /* gain_fade() and stereo_fade() need to be after the buffer copying + because we don't want any of this to affect the SILK part */ + if( st->prev_HB_gain < Q15ONE || HB_gain < Q15ONE ) { + gain_fade(pcm_buf, pcm_buf, + st->prev_HB_gain, HB_gain, celt_mode->overlap, frame_size, st->channels, celt_mode->window, st->Fs); + } + st->prev_HB_gain = HB_gain; + if (st->mode != MODE_HYBRID || st->stream_channels==1) + { + if (equiv_rate > 32000) + st->silk_mode.stereoWidth_Q14 = 16384; + else if (equiv_rate < 16000) + st->silk_mode.stereoWidth_Q14 = 0; + else + st->silk_mode.stereoWidth_Q14 = 16384 - 2048*(opus_int32)(32000-equiv_rate)/(equiv_rate-14000); + } + if( !st->energy_masking && st->channels == 2 ) { + /* Apply stereo width reduction (at low bitrates) */ + if( st->hybrid_stereo_width_Q14 < (1 << 14) || st->silk_mode.stereoWidth_Q14 < (1 << 14) ) { + opus_val16 g1, g2; + g1 = st->hybrid_stereo_width_Q14; + g2 = (opus_val16)(st->silk_mode.stereoWidth_Q14); +#ifdef FIXED_POINT + g1 = g1==16384 ? Q15ONE : SHL16(g1,1); + g2 = g2==16384 ? Q15ONE : SHL16(g2,1); +#else + g1 *= (1.f/16384); + g2 *= (1.f/16384); +#endif + stereo_fade(pcm_buf, pcm_buf, g1, g2, celt_mode->overlap, + frame_size, st->channels, celt_mode->window, st->Fs); + st->hybrid_stereo_width_Q14 = st->silk_mode.stereoWidth_Q14; + } + } + + if ( st->mode != MODE_CELT_ONLY && ec_tell(&enc)+17+20*(st->mode == MODE_HYBRID) <= 8*(max_data_bytes-1)) + { + /* For SILK mode, the redundancy is inferred from the length */ + if (st->mode == MODE_HYBRID) + ec_enc_bit_logp(&enc, redundancy, 12); + if (redundancy) + { + int max_redundancy; + ec_enc_bit_logp(&enc, celt_to_silk, 1); + if (st->mode == MODE_HYBRID) + { + /* Reserve the 8 bits needed for the redundancy length, + and at least a few bits for CELT if possible */ + max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+8+3+7)>>3); + } + else + max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+7)>>3); + /* Target the same bit-rate for redundancy as for the rest, + up to a max of 257 bytes */ + redundancy_bytes = IMIN(max_redundancy, redundancy_bytes); + redundancy_bytes = IMIN(257, IMAX(2, redundancy_bytes)); + if (st->mode == MODE_HYBRID) + ec_enc_uint(&enc, redundancy_bytes-2, 256); + } + } else { + redundancy = 0; + } + + if (!redundancy) + { + st->silk_bw_switch = 0; + redundancy_bytes = 0; + } + if (st->mode != MODE_CELT_ONLY)start_band=17; + + if (st->mode == MODE_SILK_ONLY) + { + ret = (ec_tell(&enc)+7)>>3; + ec_enc_done(&enc); + nb_compr_bytes = ret; + } else { + nb_compr_bytes = (max_data_bytes-1)-redundancy_bytes; +#ifdef ENABLE_DRED + if (st->dred_duration > 0) + { + int max_celt_bytes; + opus_int32 dred_bytes = dred_bitrate_bps/(frame_rate*8); + /* Allow CELT to steal up to 25% of the remaining bits. */ + max_celt_bytes = nb_compr_bytes - dred_bytes*3/4; + /* But try to give CELT at least 5 bytes to prevent a mismatch with + the redundancy signaling. */ + max_celt_bytes = IMAX((ec_tell(&enc)+7)/8 + 5, max_celt_bytes); + /* Subject to the original max. */ + nb_compr_bytes = IMIN(nb_compr_bytes, max_celt_bytes); + } +#endif + ec_enc_shrink(&enc, nb_compr_bytes); + } + +#ifndef DISABLE_FLOAT_API + if (redundancy || st->mode != MODE_SILK_ONLY) + celt_encoder_ctl(celt_enc, CELT_SET_ANALYSIS(analysis_info)); +#endif + if (st->mode == MODE_HYBRID) { + SILKInfo info; + info.signalType = st->silk_mode.signalType; + info.offset = st->silk_mode.offset; + celt_encoder_ctl(celt_enc, CELT_SET_SILK_INFO(&info)); + } + + /* 5 ms redundant frame for CELT->SILK */ + if (redundancy && celt_to_silk) + { + int err; + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + err = celt_encode_with_ec(celt_enc, pcm_buf, st->Fs/200, data+nb_compr_bytes, redundancy_bytes, NULL); + if (err < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + celt_encoder_ctl(celt_enc, OPUS_GET_FINAL_RANGE(&redundant_rng)); + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + } + + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(start_band)); + + if (st->mode != MODE_SILK_ONLY) + { + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(st->use_vbr)); + if (st->mode == MODE_HYBRID) + { + if( st->use_vbr ) { + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0)); + } + } else { + if (st->use_vbr) + { + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(st->vbr_constraint)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps)); + } + } +#ifdef ENABLE_DRED + /* When Using DRED CBR, we can actually make the CELT part VBR and have DRED pick up the slack. */ + if (!st->use_vbr && st->dred_duration > 0) + { + opus_int32 celt_bitrate = st->bitrate_bps; + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0)); + if (st->mode == MODE_HYBRID) { + celt_bitrate -= st->silk_mode.bitRate; + } + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(celt_bitrate)); + } +#endif + if (st->mode != st->prev_mode && st->prev_mode > 0) + { + unsigned char dummy[2]; + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + + /* Prefilling */ + celt_encode_with_ec(celt_enc, tmp_prefill, st->Fs/400, dummy, 2, NULL); + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); + } + /* If false, we already busted the budget and we'll end up with a "PLC frame" */ + if (ec_tell(&enc) <= 8*nb_compr_bytes) + { + ret = celt_encode_with_ec(celt_enc, pcm_buf, frame_size, NULL, nb_compr_bytes, &enc); + if (ret < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + /* Put CELT->SILK redundancy data in the right place. */ + if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && nb_compr_bytes != ret) + { + OPUS_MOVE(data+ret, data+nb_compr_bytes, redundancy_bytes); + nb_compr_bytes = ret+redundancy_bytes; + } + } + } + + /* 5 ms redundant frame for SILK->CELT */ + if (redundancy && !celt_to_silk) + { + int err; + unsigned char dummy[2]; + int N2, N4; + N2 = st->Fs/200; + N4 = st->Fs/400; + + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); + celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + + if (st->mode == MODE_HYBRID) + { + /* Shrink packet to what the encoder actually used. */ + nb_compr_bytes = ret; + ec_enc_shrink(&enc, nb_compr_bytes); + } + /* NOTE: We could speed this up slightly (at the expense of code size) by just adding a function that prefills the buffer */ + celt_encode_with_ec(celt_enc, pcm_buf+st->channels*(frame_size-N2-N4), N4, dummy, 2, NULL); + + err = celt_encode_with_ec(celt_enc, pcm_buf+st->channels*(frame_size-N2), N2, data+nb_compr_bytes, redundancy_bytes, NULL); + if (err < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + celt_encoder_ctl(celt_enc, OPUS_GET_FINAL_RANGE(&redundant_rng)); + } + + + + /* Signalling the mode in the first byte */ + data--; + data[0] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + + st->rangeFinal = enc.rng ^ redundant_rng; + + if (to_celt) + st->prev_mode = MODE_CELT_ONLY; + else + st->prev_mode = st->mode; + st->prev_channels = st->stream_channels; + st->prev_framesize = frame_size; + + st->first = 0; + + /* DTX decision */ +#ifndef DISABLE_FLOAT_API + if (st->use_dtx && (analysis_info->valid || is_silence)) + { + if (decide_dtx_mode(activity, &st->nb_no_activity_ms_Q1, 2*1000*frame_size/st->Fs)) + { + st->rangeFinal = 0; + data[0] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + } else { + st->nb_no_activity_ms_Q1 = 0; + } +#endif + + /* In the unlikely case that the SILK encoder busted its target, tell + the decoder to call the PLC */ + if (ec_tell(&enc) > (max_data_bytes-1)*8) + { + if (max_data_bytes < 2) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + data[1] = 0; + ret = 1; + st->rangeFinal = 0; + } else if (st->mode==MODE_SILK_ONLY&&!redundancy) + { + /*When in LPC only mode it's perfectly + reasonable to strip off trailing zero bytes as + the required range decoder behavior is to + fill these in. This can't be done when the MDCT + modes are used because the decoder needs to know + the actual length for allocation purposes.*/ + while(ret>2&&data[ret]==0)ret--; + } + /* Count ToC and redundancy */ + ret += 1+redundancy_bytes; + apply_padding = !st->use_vbr; +#ifdef ENABLE_DRED + if (st->dred_duration > 0 && st->dred_encoder.loaded && first_frame) { + opus_extension_data extension; + unsigned char buf[DRED_MAX_DATA_SIZE]; + int dred_chunks; + int dred_bytes_left; + dred_chunks = IMIN((st->dred_duration+5)/4, DRED_NUM_REDUNDANCY_FRAMES/2); + if (st->use_vbr) dred_chunks = IMIN(dred_chunks, st->dred_target_chunks); + /* Remaining space for DRED, accounting for cost the 3 extra bytes for code 3, padding length, and extension number. */ + dred_bytes_left = IMIN(DRED_MAX_DATA_SIZE, max_data_bytes-ret-3); + /* Account for the extra bytes required to signal large padding length. */ + dred_bytes_left -= (dred_bytes_left+1+DRED_EXPERIMENTAL_BYTES)/255; + /* Check whether we actually have something to encode. */ + if (dred_chunks >= 1 && dred_bytes_left >= DRED_MIN_BYTES+DRED_EXPERIMENTAL_BYTES) { + int dred_bytes; +#ifdef DRED_EXPERIMENTAL_VERSION + /* Add temporary extension type and version. + These bytes will be removed once extension is finalized. */ + buf[0] = 'D'; + buf[1] = DRED_EXPERIMENTAL_VERSION; +#endif + dred_bytes = dred_encode_silk_frame(&st->dred_encoder, buf+DRED_EXPERIMENTAL_BYTES, dred_chunks, dred_bytes_left-DRED_EXPERIMENTAL_BYTES, + st->dred_q0, st->dred_dQ, st->dred_qmax, st->activity_mem, st->arch); + if (dred_bytes > 0) { + dred_bytes += DRED_EXPERIMENTAL_BYTES; + celt_assert(dred_bytes <= dred_bytes_left); + extension.id = DRED_EXTENSION_ID; + extension.frame = 0; + extension.data = buf; + extension.len = dred_bytes; + ret = opus_packet_pad_impl(data, ret, max_data_bytes, !st->use_vbr, &extension, 1); + if (ret < 0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + apply_padding = 0; + } + } + } +#else + (void)first_frame; /* Avoids a warning about first_frame being unused. */ +#endif + if (apply_padding) + { + if (opus_packet_pad(data, ret, max_data_bytes) != OPUS_OK) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + ret = max_data_bytes; + } + RESTORE_STACK; + return ret; +} + +#ifdef FIXED_POINT + +#ifndef DISABLE_FLOAT_API +opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 max_data_bytes) +{ + int i, ret; + int frame_size; + VARDECL(opus_int16, in); + ALLOC_STACK; + + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + if (frame_size <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + ALLOC(in, frame_size*st->channels, opus_int16); + + for (i=0;ichannels;i++) + in[i] = FLOAT2INT16(pcm[i]); + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); + RESTORE_STACK; + return ret; +} +#endif + +opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 out_data_bytes) +{ + int frame_size; + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); +} + +#else +opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 max_data_bytes) +{ + int i, ret; + int frame_size; + VARDECL(float, in); + ALLOC_STACK; + + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + if (frame_size <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + ALLOC(in, frame_size*st->channels, float); + + for (i=0;ichannels;i++) + in[i] = (1.0f/32768)*pcm[i]; + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); + RESTORE_STACK; + return ret; +} +opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_frame_size, + unsigned char *data, opus_int32 out_data_bytes) +{ + int frame_size; + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, st->Fs); + return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 24, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); +} +#endif + + +int opus_encoder_ctl(OpusEncoder *st, int request, ...) +{ + int ret; + CELTEncoder *celt_enc; + va_list ap; + + ret = OPUS_OK; + va_start(ap, request); + + celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); + + switch (request) + { + case OPUS_SET_APPLICATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ( (value != OPUS_APPLICATION_VOIP && value != OPUS_APPLICATION_AUDIO + && value != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + || (!st->first && st->application != value)) + { + ret = OPUS_BAD_ARG; + break; + } + st->application = value; +#ifndef DISABLE_FLOAT_API + st->analysis.application = value; +#endif + } + break; + case OPUS_GET_APPLICATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->application; + } + break; + case OPUS_SET_BITRATE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value != OPUS_AUTO && value != OPUS_BITRATE_MAX) + { + if (value <= 0) + goto bad_arg; + else if (value <= 500) + value = 500; + else if (value > (opus_int32)300000*st->channels) + value = (opus_int32)300000*st->channels; + } + st->user_bitrate_bps = value; + } + break; + case OPUS_GET_BITRATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = user_bitrate_to_bitrate(st, st->prev_framesize, 1276); + } + break; + case OPUS_SET_FORCE_CHANNELS_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if((value<1 || value>st->channels) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->force_channels = value; + } + break; + case OPUS_GET_FORCE_CHANNELS_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->force_channels; + } + break; + case OPUS_SET_MAX_BANDWIDTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) + { + goto bad_arg; + } + st->max_bandwidth = value; + if (st->max_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.maxInternalSampleRate = 8000; + } else if (st->max_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.maxInternalSampleRate = 12000; + } else { + st->silk_mode.maxInternalSampleRate = 16000; + } + } + break; + case OPUS_GET_MAX_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->max_bandwidth; + } + break; + case OPUS_SET_BANDWIDTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ((value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->user_bandwidth = value; + if (st->user_bandwidth == OPUS_BANDWIDTH_NARROWBAND) { + st->silk_mode.maxInternalSampleRate = 8000; + } else if (st->user_bandwidth == OPUS_BANDWIDTH_MEDIUMBAND) { + st->silk_mode.maxInternalSampleRate = 12000; + } else { + st->silk_mode.maxInternalSampleRate = 16000; + } + } + break; + case OPUS_GET_BANDWIDTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->bandwidth; + } + break; + case OPUS_SET_DTX_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->use_dtx = value; + } + break; + case OPUS_GET_DTX_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->use_dtx; + } + break; + case OPUS_SET_COMPLEXITY_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>10) + { + goto bad_arg; + } + st->silk_mode.complexity = value; + celt_encoder_ctl(celt_enc, OPUS_SET_COMPLEXITY(value)); + } + break; + case OPUS_GET_COMPLEXITY_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->silk_mode.complexity; + } + break; + case OPUS_SET_INBAND_FEC_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>2) + { + goto bad_arg; + } + st->fec_config = value; + st->silk_mode.useInBandFEC = (value != 0); + } + break; + case OPUS_GET_INBAND_FEC_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->fec_config; + } + break; + case OPUS_SET_PACKET_LOSS_PERC_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value < 0 || value > 100) + { + goto bad_arg; + } + st->silk_mode.packetLossPercentage = value; + celt_encoder_ctl(celt_enc, OPUS_SET_PACKET_LOSS_PERC(value)); + } + break; + case OPUS_GET_PACKET_LOSS_PERC_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->silk_mode.packetLossPercentage; + } + break; + case OPUS_SET_VBR_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->use_vbr = value; + st->silk_mode.useCBR = 1-value; + } + break; + case OPUS_GET_VBR_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->use_vbr; + } + break; + case OPUS_SET_VOICE_RATIO_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<-1 || value>100) + { + goto bad_arg; + } + st->voice_ratio = value; + } + break; + case OPUS_GET_VOICE_RATIO_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->voice_ratio; + } + break; + case OPUS_SET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->vbr_constraint = value; + } + break; + case OPUS_GET_VBR_CONSTRAINT_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->vbr_constraint; + } + break; + case OPUS_SET_SIGNAL_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value!=OPUS_AUTO && value!=OPUS_SIGNAL_VOICE && value!=OPUS_SIGNAL_MUSIC) + { + goto bad_arg; + } + st->signal_type = value; + } + break; + case OPUS_GET_SIGNAL_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->signal_type; + } + break; + case OPUS_GET_LOOKAHEAD_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs/400; + if (st->application != OPUS_APPLICATION_RESTRICTED_LOWDELAY) + *value += st->delay_compensation; + } + break; + case OPUS_GET_SAMPLE_RATE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->Fs; + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + opus_uint32 *value = va_arg(ap, opus_uint32*); + if (!value) + { + goto bad_arg; + } + *value = st->rangeFinal; + } + break; + case OPUS_SET_LSB_DEPTH_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value<8 || value>24) + { + goto bad_arg; + } + st->lsb_depth=value; + } + break; + case OPUS_GET_LSB_DEPTH_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->lsb_depth; + } + break; + case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value != OPUS_FRAMESIZE_ARG && value != OPUS_FRAMESIZE_2_5_MS && + value != OPUS_FRAMESIZE_5_MS && value != OPUS_FRAMESIZE_10_MS && + value != OPUS_FRAMESIZE_20_MS && value != OPUS_FRAMESIZE_40_MS && + value != OPUS_FRAMESIZE_60_MS && value != OPUS_FRAMESIZE_80_MS && + value != OPUS_FRAMESIZE_100_MS && value != OPUS_FRAMESIZE_120_MS) + { + goto bad_arg; + } + st->variable_duration = value; + } + break; + case OPUS_GET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->variable_duration; + } + break; + case OPUS_SET_PREDICTION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if (value > 1 || value < 0) + goto bad_arg; + st->silk_mode.reducedDependency = value; + } + break; + case OPUS_GET_PREDICTION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + goto bad_arg; + *value = st->silk_mode.reducedDependency; + } + break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; +#ifdef ENABLE_DRED + case OPUS_SET_DRED_DURATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>DRED_MAX_FRAMES) + { + goto bad_arg; + } + st->dred_duration = value; + st->silk_mode.useDRED = !!value; + } + break; + case OPUS_GET_DRED_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->dred_duration; + } + break; +#endif + case OPUS_RESET_STATE: + { + void *silk_enc; + silk_EncControlStruct dummy; + char *start; + silk_enc = (char*)st+st->silk_enc_offset; +#ifndef DISABLE_FLOAT_API + tonality_analysis_reset(&st->analysis); +#endif + + start = (char*)&st->OPUS_ENCODER_RESET_START; + OPUS_CLEAR(start, sizeof(OpusEncoder) - (start - (char*)st)); + + celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); + silk_InitEncoder( silk_enc, st->arch, &dummy ); +#ifdef ENABLE_DRED + /* Initialize DRED Encoder */ + dred_encoder_reset( &st->dred_encoder ); +#endif + st->stream_channels = st->channels; + st->hybrid_stereo_width_Q14 = 1 << 14; + st->prev_HB_gain = Q15ONE; + st->first = 1; + st->mode = MODE_HYBRID; + st->bandwidth = OPUS_BANDWIDTH_FULLBAND; + st->variable_HP_smth2_Q15 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); + } + break; + case OPUS_SET_FORCE_MODE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if ((value < MODE_SILK_ONLY || value > MODE_CELT_ONLY) && value != OPUS_AUTO) + { + goto bad_arg; + } + st->user_forced_mode = value; + } + break; + case OPUS_SET_LFE_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->lfe = value; + ret = celt_encoder_ctl(celt_enc, OPUS_SET_LFE(value)); + } + break; + case OPUS_SET_ENERGY_MASK_REQUEST: + { + opus_val16 *value = va_arg(ap, opus_val16*); + st->energy_masking = value; + ret = celt_encoder_ctl(celt_enc, OPUS_SET_ENERGY_MASK(value)); + } + break; + case OPUS_GET_IN_DTX_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + if (st->silk_mode.useDTX && (st->prev_mode == MODE_SILK_ONLY || st->prev_mode == MODE_HYBRID)) { + /* DTX determined by Silk. */ + silk_encoder *silk_enc = (silk_encoder*)(void *)((char*)st+st->silk_enc_offset); + *value = silk_enc->state_Fxx[0].sCmn.noSpeechCounter >= NB_SPEECH_FRAMES_BEFORE_DTX; + /* Stereo: check second channel unless only the middle channel was encoded. */ + if(*value == 1 && st->silk_mode.nChannelsInternal == 2 && silk_enc->prev_decode_only_middle == 0) { + *value = silk_enc->state_Fxx[1].sCmn.noSpeechCounter >= NB_SPEECH_FRAMES_BEFORE_DTX; + } + } +#ifndef DISABLE_FLOAT_API + else if (st->use_dtx) { + /* DTX determined by Opus. */ + *value = st->nb_no_activity_ms_Q1 >= NB_SPEECH_FRAMES_BEFORE_DTX*20*2; + } +#endif + else { + *value = 0; + } + } + break; +#ifdef USE_WEIGHTS_FILE + case OPUS_SET_DNN_BLOB_REQUEST: + { + const unsigned char *data = va_arg(ap, const unsigned char *); + opus_int32 len = va_arg(ap, opus_int32); + if(len<0 || data == NULL) + { + goto bad_arg; + } +#ifdef ENABLE_DRED + ret = dred_encoder_load_model(&st->dred_encoder, data, len); +#endif + } + break; +#endif + case CELT_GET_MODE_REQUEST: + { + const CELTMode ** value = va_arg(ap, const CELTMode**); + if (!value) + { + goto bad_arg; + } + ret = celt_encoder_ctl(celt_enc, CELT_GET_MODE(value)); + } + break; + default: + /* fprintf(stderr, "unknown opus_encoder_ctl() request: %d", request);*/ + ret = OPUS_UNIMPLEMENTED; + break; + } + va_end(ap); + return ret; +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +} + +void opus_encoder_destroy(OpusEncoder *st) +{ + opus_free(st); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream.c new file mode 100644 index 0000000000..09c3639b7f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream.c @@ -0,0 +1,92 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_multistream.h" +#include "opus.h" +#include "opus_private.h" +#include "stack_alloc.h" +#include +#include "float_cast.h" +#include "os_support.h" + + +int validate_layout(const ChannelLayout *layout) +{ + int i, max_channel; + + max_channel = layout->nb_streams+layout->nb_coupled_streams; + if (max_channel>255) + return 0; + for (i=0;inb_channels;i++) + { + if (layout->mapping[i] >= max_channel && layout->mapping[i] != 255) + return 0; + } + return 1; +} + + +int get_left_channel(const ChannelLayout *layout, int stream_id, int prev) +{ + int i; + i = (prev<0) ? 0 : prev+1; + for (;inb_channels;i++) + { + if (layout->mapping[i]==stream_id*2) + return i; + } + return -1; +} + +int get_right_channel(const ChannelLayout *layout, int stream_id, int prev) +{ + int i; + i = (prev<0) ? 0 : prev+1; + for (;inb_channels;i++) + { + if (layout->mapping[i]==stream_id*2+1) + return i; + } + return -1; +} + +int get_mono_channel(const ChannelLayout *layout, int stream_id, int prev) +{ + int i; + i = (prev<0) ? 0 : prev+1; + for (;inb_channels;i++) + { + if (layout->mapping[i]==stream_id+layout->nb_coupled_streams) + return i; + } + return -1; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_decoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_decoder.c new file mode 100644 index 0000000000..4ae877a759 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_decoder.c @@ -0,0 +1,552 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_multistream.h" +#include "opus.h" +#include "opus_private.h" +#include "stack_alloc.h" +#include +#include "float_cast.h" +#include "os_support.h" + +/* DECODER */ + +#if defined(ENABLE_HARDENING) || defined(ENABLE_ASSERTIONS) +static void validate_ms_decoder(OpusMSDecoder *st) +{ + validate_layout(&st->layout); +} +#define VALIDATE_MS_DECODER(st) validate_ms_decoder(st) +#else +#define VALIDATE_MS_DECODER(st) +#endif + + +opus_int32 opus_multistream_decoder_get_size(int nb_streams, int nb_coupled_streams) +{ + int coupled_size; + int mono_size; + + if(nb_streams<1||nb_coupled_streams>nb_streams||nb_coupled_streams<0)return 0; + coupled_size = opus_decoder_get_size(2); + mono_size = opus_decoder_get_size(1); + return align(sizeof(OpusMSDecoder)) + + nb_coupled_streams * align(coupled_size) + + (nb_streams-nb_coupled_streams) * align(mono_size); +} + +int opus_multistream_decoder_init( + OpusMSDecoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping +) +{ + int coupled_size; + int mono_size; + int i, ret; + char *ptr; + + if ((channels>255) || (channels<1) || (coupled_streams>streams) || + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) + return OPUS_BAD_ARG; + + st->layout.nb_channels = channels; + st->layout.nb_streams = streams; + st->layout.nb_coupled_streams = coupled_streams; + + for (i=0;ilayout.nb_channels;i++) + st->layout.mapping[i] = mapping[i]; + if (!validate_layout(&st->layout)) + return OPUS_BAD_ARG; + + ptr = (char*)st + align(sizeof(OpusMSDecoder)); + coupled_size = opus_decoder_get_size(2); + mono_size = opus_decoder_get_size(1); + + for (i=0;ilayout.nb_coupled_streams;i++) + { + ret=opus_decoder_init((OpusDecoder*)ptr, Fs, 2); + if(ret!=OPUS_OK)return ret; + ptr += align(coupled_size); + } + for (;ilayout.nb_streams;i++) + { + ret=opus_decoder_init((OpusDecoder*)ptr, Fs, 1); + if(ret!=OPUS_OK)return ret; + ptr += align(mono_size); + } + return OPUS_OK; +} + + +OpusMSDecoder *opus_multistream_decoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int *error +) +{ + int ret; + OpusMSDecoder *st; + if ((channels>255) || (channels<1) || (coupled_streams>streams) || + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + st = (OpusMSDecoder *)opus_alloc(opus_multistream_decoder_get_size(streams, coupled_streams)); + if (st==NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_multistream_decoder_init(st, Fs, channels, streams, coupled_streams, mapping); + if (error) + *error = ret; + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + return st; +} + +static int opus_multistream_packet_validate(const unsigned char *data, + opus_int32 len, int nb_streams, opus_int32 Fs) +{ + int s; + int count; + unsigned char toc; + opus_int16 size[48]; + int samples=0; + opus_int32 packet_offset; + + for (s=0;slayout.nb_streams-1) + { + RESTORE_STACK; + return OPUS_INVALID_PACKET; + } + if (!do_plc) + { + int ret = opus_multistream_packet_validate(data, len, st->layout.nb_streams, Fs); + if (ret < 0) + { + RESTORE_STACK; + return ret; + } else if (ret > frame_size) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + } + for (s=0;slayout.nb_streams;s++) + { + OpusDecoder *dec; + opus_int32 packet_offset; + int ret; + + dec = (OpusDecoder*)ptr; + ptr += (s < st->layout.nb_coupled_streams) ? align(coupled_size) : align(mono_size); + + if (!do_plc && len<=0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + packet_offset = 0; + ret = opus_decode_native(dec, data, len, buf, frame_size, decode_fec, s!=st->layout.nb_streams-1, &packet_offset, soft_clip, NULL, 0); + if (!do_plc) + { + data += packet_offset; + len -= packet_offset; + } + if (ret <= 0) + { + RESTORE_STACK; + return ret; + } + frame_size = ret; + if (s < st->layout.nb_coupled_streams) + { + int chan, prev; + prev = -1; + /* Copy "left" audio to the channel(s) where it belongs */ + while ( (chan = get_left_channel(&st->layout, s, prev)) != -1) + { + (*copy_channel_out)(pcm, st->layout.nb_channels, chan, + buf, 2, frame_size, user_data); + prev = chan; + } + prev = -1; + /* Copy "right" audio to the channel(s) where it belongs */ + while ( (chan = get_right_channel(&st->layout, s, prev)) != -1) + { + (*copy_channel_out)(pcm, st->layout.nb_channels, chan, + buf+1, 2, frame_size, user_data); + prev = chan; + } + } else { + int chan, prev; + prev = -1; + /* Copy audio to the channel(s) where it belongs */ + while ( (chan = get_mono_channel(&st->layout, s, prev)) != -1) + { + (*copy_channel_out)(pcm, st->layout.nb_channels, chan, + buf, 1, frame_size, user_data); + prev = chan; + } + } + } + /* Handle muted channels */ + for (c=0;clayout.nb_channels;c++) + { + if (st->layout.mapping[c] == 255) + { + (*copy_channel_out)(pcm, st->layout.nb_channels, c, + NULL, 0, frame_size, user_data); + } + } + RESTORE_STACK; + return frame_size; +} + +#if !defined(DISABLE_FLOAT_API) +static void opus_copy_channel_out_float( + void *dst, + int dst_stride, + int dst_channel, + const opus_val16 *src, + int src_stride, + int frame_size, + void *user_data +) +{ + float *float_dst; + opus_int32 i; + (void)user_data; + float_dst = (float*)dst; + if (src != NULL) + { + for (i=0;ilayout.nb_streams;s++) + { + OpusDecoder *dec; + dec = (OpusDecoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_decoder_ctl(dec, request, &tmp); + if (ret != OPUS_OK) break; + *value ^= tmp; + } + } + break; + case OPUS_RESET_STATE: + { + int s; + for (s=0;slayout.nb_streams;s++) + { + OpusDecoder *dec; + + dec = (OpusDecoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_decoder_ctl(dec, OPUS_RESET_STATE); + if (ret != OPUS_OK) + break; + } + } + break; + case OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST: + { + int s; + opus_int32 stream_id; + OpusDecoder **value; + stream_id = va_arg(ap, opus_int32); + if (stream_id<0 || stream_id >= st->layout.nb_streams) + goto bad_arg; + value = va_arg(ap, OpusDecoder**); + if (!value) + { + goto bad_arg; + } + for (s=0;slayout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + } + *value = (OpusDecoder*)ptr; + } + break; + case OPUS_SET_GAIN_REQUEST: + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + int s; + /* This works for int32 params */ + opus_int32 value = va_arg(ap, opus_int32); + for (s=0;slayout.nb_streams;s++) + { + OpusDecoder *dec; + + dec = (OpusDecoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_decoder_ctl(dec, request, value); + if (ret != OPUS_OK) + break; + } + } + break; + default: + ret = OPUS_UNIMPLEMENTED; + break; + } + return ret; +bad_arg: + return OPUS_BAD_ARG; +} + +int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) +{ + int ret; + va_list ap; + va_start(ap, request); + ret = opus_multistream_decoder_ctl_va_list(st, request, ap); + va_end(ap); + return ret; +} + +void opus_multistream_decoder_destroy(OpusMSDecoder *st) +{ + opus_free(st); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_encoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_encoder.c new file mode 100644 index 0000000000..1725ade75a --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_multistream_encoder.c @@ -0,0 +1,1329 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus_multistream.h" +#include "opus.h" +#include "opus_private.h" +#include "stack_alloc.h" +#include +#include "float_cast.h" +#include "os_support.h" +#include "mathops.h" +#include "mdct.h" +#include "modes.h" +#include "bands.h" +#include "quant_bands.h" +#include "pitch.h" + +typedef struct { + int nb_streams; + int nb_coupled_streams; + unsigned char mapping[8]; +} VorbisLayout; + +/* Index is nb_channel-1*/ +static const VorbisLayout vorbis_mappings[8] = { + {1, 0, {0}}, /* 1: mono */ + {1, 1, {0, 1}}, /* 2: stereo */ + {2, 1, {0, 2, 1}}, /* 3: 1-d surround */ + {2, 2, {0, 1, 2, 3}}, /* 4: quadraphonic surround */ + {3, 2, {0, 4, 1, 2, 3}}, /* 5: 5-channel surround */ + {4, 2, {0, 4, 1, 2, 3, 5}}, /* 6: 5.1 surround */ + {4, 3, {0, 4, 1, 2, 3, 5, 6}}, /* 7: 6.1 surround */ + {5, 3, {0, 6, 1, 2, 3, 4, 5, 7}}, /* 8: 7.1 surround */ +}; + +static opus_val32 *ms_get_preemph_mem(OpusMSEncoder *st) +{ + int s; + char *ptr; + int coupled_size, mono_size; + + coupled_size = opus_encoder_get_size(2); + mono_size = opus_encoder_get_size(1); + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + for (s=0;slayout.nb_streams;s++) + { + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + } + /* void* cast avoids clang -Wcast-align warning */ + return (opus_val32*)(void*)(ptr+st->layout.nb_channels*120*sizeof(opus_val32)); +} + +static opus_val32 *ms_get_window_mem(OpusMSEncoder *st) +{ + int s; + char *ptr; + int coupled_size, mono_size; + + coupled_size = opus_encoder_get_size(2); + mono_size = opus_encoder_get_size(1); + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + for (s=0;slayout.nb_streams;s++) + { + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + } + /* void* cast avoids clang -Wcast-align warning */ + return (opus_val32*)(void*)ptr; +} + +static int validate_ambisonics(int nb_channels, int *nb_streams, int *nb_coupled_streams) +{ + int order_plus_one; + int acn_channels; + int nondiegetic_channels; + + if (nb_channels < 1 || nb_channels > 227) + return 0; + + order_plus_one = isqrt32(nb_channels); + acn_channels = order_plus_one * order_plus_one; + nondiegetic_channels = nb_channels - acn_channels; + + if (nondiegetic_channels != 0 && nondiegetic_channels != 2) + return 0; + + if (nb_streams) + *nb_streams = acn_channels + (nondiegetic_channels != 0); + if (nb_coupled_streams) + *nb_coupled_streams = nondiegetic_channels != 0; + return 1; +} + +static int validate_encoder_layout(const ChannelLayout *layout) +{ + int s; + for (s=0;snb_streams;s++) + { + if (s < layout->nb_coupled_streams) + { + if (get_left_channel(layout, s, -1)==-1) + return 0; + if (get_right_channel(layout, s, -1)==-1) + return 0; + } else { + if (get_mono_channel(layout, s, -1)==-1) + return 0; + } + } + return 1; +} + +static void channel_pos(int channels, int pos[8]) +{ + /* Position in the mix: 0 don't mix, 1: left, 2: center, 3:right */ + if (channels==4) + { + pos[0]=1; + pos[1]=3; + pos[2]=1; + pos[3]=3; + } else if (channels==3||channels==5||channels==6) + { + pos[0]=1; + pos[1]=2; + pos[2]=3; + pos[3]=1; + pos[4]=3; + pos[5]=0; + } else if (channels==7) + { + pos[0]=1; + pos[1]=2; + pos[2]=3; + pos[3]=1; + pos[4]=3; + pos[5]=2; + pos[6]=0; + } else if (channels==8) + { + pos[0]=1; + pos[1]=2; + pos[2]=3; + pos[3]=1; + pos[4]=3; + pos[5]=1; + pos[6]=3; + pos[7]=0; + } +} + +#if 1 +/* Computes a rough approximation of log2(2^a + 2^b) */ +static opus_val16 logSum(opus_val16 a, opus_val16 b) +{ + opus_val16 max; + opus_val32 diff; + opus_val16 frac; + static const opus_val16 diff_table[17] = { + QCONST16(0.5000000f, DB_SHIFT), QCONST16(0.2924813f, DB_SHIFT), QCONST16(0.1609640f, DB_SHIFT), QCONST16(0.0849625f, DB_SHIFT), + QCONST16(0.0437314f, DB_SHIFT), QCONST16(0.0221971f, DB_SHIFT), QCONST16(0.0111839f, DB_SHIFT), QCONST16(0.0056136f, DB_SHIFT), + QCONST16(0.0028123f, DB_SHIFT) + }; + int low; + if (a>b) + { + max = a; + diff = SUB32(EXTEND32(a),EXTEND32(b)); + } else { + max = b; + diff = SUB32(EXTEND32(b),EXTEND32(a)); + } + if (!(diff < QCONST16(8.f, DB_SHIFT))) /* inverted to catch NaNs */ + return max; +#ifdef FIXED_POINT + low = SHR32(diff, DB_SHIFT-1); + frac = SHL16(diff - SHL16(low, DB_SHIFT-1), 16-DB_SHIFT); +#else + low = (int)floor(2*diff); + frac = 2*diff - low; +#endif + return max + diff_table[low] + MULT16_16_Q15(frac, SUB16(diff_table[low+1], diff_table[low])); +} +#else +opus_val16 logSum(opus_val16 a, opus_val16 b) +{ + return log2(pow(4, a)+ pow(4, b))/2; +} +#endif + +void surround_analysis(const CELTMode *celt_mode, const void *pcm, opus_val16 *bandLogE, opus_val32 *mem, opus_val32 *preemph_mem, + int len, int overlap, int channels, int rate, opus_copy_channel_in_func copy_channel_in, int arch +) +{ + int c; + int i; + int LM; + int pos[8] = {0}; + int upsample; + int frame_size; + int freq_size; + opus_val16 channel_offset; + opus_val32 bandE[21]; + opus_val16 maskLogE[3][21]; + VARDECL(opus_val32, in); + VARDECL(opus_val16, x); + VARDECL(opus_val32, freq); + SAVE_STACK; + + upsample = resampling_factor(rate); + frame_size = len*upsample; + freq_size = IMIN(960, frame_size); + + /* LM = log2(frame_size / 120) */ + for (LM=0;LMmaxLM;LM++) + if (celt_mode->shortMdctSize<preemph, preemph_mem+c, 0); +#ifndef FIXED_POINT + { + opus_val32 sum; + sum = celt_inner_prod(in, in, frame_size+overlap, 0); + /* This should filter out both NaNs and ridiculous signals that could + cause NaNs further down. */ + if (!(sum < 1e18f) || celt_isnan(sum)) + { + OPUS_CLEAR(in, frame_size+overlap); + preemph_mem[c] = 0; + } + } +#endif + OPUS_CLEAR(bandE, 21); + for (frame=0;framemdct, in+960*frame, freq, celt_mode->window, + overlap, celt_mode->maxLM-LM, 1, arch); + if (upsample != 1) + { + int bound = freq_size/upsample; + for (i=0;i=0;i--) + bandLogE[21*c+i] = MAX16(bandLogE[21*c+i], bandLogE[21*c+i+1]-QCONST16(2.f, DB_SHIFT)); + if (pos[c]==1) + { + for (i=0;i<21;i++) + maskLogE[0][i] = logSum(maskLogE[0][i], bandLogE[21*c+i]); + } else if (pos[c]==3) + { + for (i=0;i<21;i++) + maskLogE[2][i] = logSum(maskLogE[2][i], bandLogE[21*c+i]); + } else if (pos[c]==2) + { + for (i=0;i<21;i++) + { + maskLogE[0][i] = logSum(maskLogE[0][i], bandLogE[21*c+i]-QCONST16(.5f, DB_SHIFT)); + maskLogE[2][i] = logSum(maskLogE[2][i], bandLogE[21*c+i]-QCONST16(.5f, DB_SHIFT)); + } + } +#if 0 + for (i=0;i<21;i++) + printf("%f ", bandLogE[21*c+i]); + float sum=0; + for (i=0;i<21;i++) + sum += bandLogE[21*c+i]; + printf("%f ", sum/21); +#endif + OPUS_COPY(mem+c*overlap, in+frame_size, overlap); + } + for (i=0;i<21;i++) + maskLogE[1][i] = MIN32(maskLogE[0][i],maskLogE[2][i]); + channel_offset = HALF16(celt_log2(QCONST32(2.f,14)/(channels-1))); + for (c=0;c<3;c++) + for (i=0;i<21;i++) + maskLogE[c][i] += channel_offset; +#if 0 + for (c=0;c<3;c++) + { + for (i=0;i<21;i++) + printf("%f ", maskLogE[c][i]); + } +#endif + for (c=0;cnb_streams||nb_coupled_streams<0)return 0; + coupled_size = opus_encoder_get_size(2); + mono_size = opus_encoder_get_size(1); + return align(sizeof(OpusMSEncoder)) + + nb_coupled_streams * align(coupled_size) + + (nb_streams-nb_coupled_streams) * align(mono_size); +} + +opus_int32 opus_multistream_surround_encoder_get_size(int channels, int mapping_family) +{ + int nb_streams; + int nb_coupled_streams; + opus_int32 size; + + if (mapping_family==0) + { + if (channels==1) + { + nb_streams=1; + nb_coupled_streams=0; + } else if (channels==2) + { + nb_streams=1; + nb_coupled_streams=1; + } else + return 0; + } else if (mapping_family==1 && channels<=8 && channels>=1) + { + nb_streams=vorbis_mappings[channels-1].nb_streams; + nb_coupled_streams=vorbis_mappings[channels-1].nb_coupled_streams; + } else if (mapping_family==255) + { + nb_streams=channels; + nb_coupled_streams=0; + } else if (mapping_family==2) + { + if (!validate_ambisonics(channels, &nb_streams, &nb_coupled_streams)) + return 0; + } else + return 0; + size = opus_multistream_encoder_get_size(nb_streams, nb_coupled_streams); + if (channels>2) + { + size += channels*(120*sizeof(opus_val32) + sizeof(opus_val32)); + } + return size; +} + +static int opus_multistream_encoder_init_impl( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + MappingType mapping_type +) +{ + int coupled_size; + int mono_size; + int i, ret; + char *ptr; + + if ((channels>255) || (channels<1) || (coupled_streams>streams) || + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams) || + (streams+coupled_streams>channels)) + return OPUS_BAD_ARG; + + st->arch = opus_select_arch(); + st->layout.nb_channels = channels; + st->layout.nb_streams = streams; + st->layout.nb_coupled_streams = coupled_streams; + if (mapping_type != MAPPING_TYPE_SURROUND) + st->lfe_stream = -1; + st->bitrate_bps = OPUS_AUTO; + st->application = application; + st->variable_duration = OPUS_FRAMESIZE_ARG; + for (i=0;ilayout.nb_channels;i++) + st->layout.mapping[i] = mapping[i]; + if (!validate_layout(&st->layout)) + return OPUS_BAD_ARG; + if (!validate_encoder_layout(&st->layout)) + return OPUS_BAD_ARG; + if (mapping_type == MAPPING_TYPE_AMBISONICS && + !validate_ambisonics(st->layout.nb_channels, NULL, NULL)) + return OPUS_BAD_ARG; + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + coupled_size = opus_encoder_get_size(2); + mono_size = opus_encoder_get_size(1); + + for (i=0;ilayout.nb_coupled_streams;i++) + { + ret = opus_encoder_init((OpusEncoder*)ptr, Fs, 2, application); + if(ret!=OPUS_OK)return ret; + if (i==st->lfe_stream) + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_SET_LFE(1)); + ptr += align(coupled_size); + } + for (;ilayout.nb_streams;i++) + { + ret = opus_encoder_init((OpusEncoder*)ptr, Fs, 1, application); + if (i==st->lfe_stream) + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_SET_LFE(1)); + if(ret!=OPUS_OK)return ret; + ptr += align(mono_size); + } + if (mapping_type == MAPPING_TYPE_SURROUND) + { + OPUS_CLEAR(ms_get_preemph_mem(st), channels); + OPUS_CLEAR(ms_get_window_mem(st), channels*120); + } + st->mapping_type = mapping_type; + return OPUS_OK; +} + +int opus_multistream_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application +) +{ + return opus_multistream_encoder_init_impl(st, Fs, channels, streams, + coupled_streams, mapping, + application, MAPPING_TYPE_NONE); +} + +int opus_multistream_surround_encoder_init( + OpusMSEncoder *st, + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application +) +{ + MappingType mapping_type; + + if ((channels>255) || (channels<1)) + return OPUS_BAD_ARG; + st->lfe_stream = -1; + if (mapping_family==0) + { + if (channels==1) + { + *streams=1; + *coupled_streams=0; + mapping[0]=0; + } else if (channels==2) + { + *streams=1; + *coupled_streams=1; + mapping[0]=0; + mapping[1]=1; + } else + return OPUS_UNIMPLEMENTED; + } else if (mapping_family==1 && channels<=8 && channels>=1) + { + int i; + *streams=vorbis_mappings[channels-1].nb_streams; + *coupled_streams=vorbis_mappings[channels-1].nb_coupled_streams; + for (i=0;i=6) + st->lfe_stream = *streams-1; + } else if (mapping_family==255) + { + int i; + *streams=channels; + *coupled_streams=0; + for(i=0;i2 && mapping_family==1) { + mapping_type = MAPPING_TYPE_SURROUND; + } else if (mapping_family==2) + { + mapping_type = MAPPING_TYPE_AMBISONICS; + } else + { + mapping_type = MAPPING_TYPE_NONE; + } + return opus_multistream_encoder_init_impl(st, Fs, channels, *streams, + *coupled_streams, mapping, + application, mapping_type); +} + +OpusMSEncoder *opus_multistream_encoder_create( + opus_int32 Fs, + int channels, + int streams, + int coupled_streams, + const unsigned char *mapping, + int application, + int *error +) +{ + int ret; + OpusMSEncoder *st; + if ((channels>255) || (channels<1) || (coupled_streams>streams) || + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams) || + (streams+coupled_streams>channels)) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + st = (OpusMSEncoder *)opus_alloc(opus_multistream_encoder_get_size(streams, coupled_streams)); + if (st==NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_multistream_encoder_init(st, Fs, channels, streams, coupled_streams, mapping, application); + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} + +OpusMSEncoder *opus_multistream_surround_encoder_create( + opus_int32 Fs, + int channels, + int mapping_family, + int *streams, + int *coupled_streams, + unsigned char *mapping, + int application, + int *error +) +{ + int ret; + opus_int32 size; + OpusMSEncoder *st; + if ((channels>255) || (channels<1)) + { + if (error) + *error = OPUS_BAD_ARG; + return NULL; + } + size = opus_multistream_surround_encoder_get_size(channels, mapping_family); + if (!size) + { + if (error) + *error = OPUS_UNIMPLEMENTED; + return NULL; + } + st = (OpusMSEncoder *)opus_alloc(size); + if (st==NULL) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + ret = opus_multistream_surround_encoder_init(st, Fs, channels, mapping_family, streams, coupled_streams, mapping, application); + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} + +static void surround_rate_allocation( + OpusMSEncoder *st, + opus_int32 *rate, + int frame_size, + opus_int32 Fs + ) +{ + int i; + opus_int32 channel_rate; + int stream_offset; + int lfe_offset; + int coupled_ratio; /* Q8 */ + int lfe_ratio; /* Q8 */ + int nb_lfe; + int nb_uncoupled; + int nb_coupled; + int nb_normal; + opus_int32 channel_offset; + opus_int32 bitrate; + int total; + + nb_lfe = (st->lfe_stream!=-1); + nb_coupled = st->layout.nb_coupled_streams; + nb_uncoupled = st->layout.nb_streams-nb_coupled-nb_lfe; + nb_normal = 2*nb_coupled + nb_uncoupled; + + /* Give each non-LFE channel enough bits per channel for coding band energy. */ + channel_offset = 40*IMAX(50, Fs/frame_size); + + if (st->bitrate_bps==OPUS_AUTO) + { + bitrate = nb_normal*(channel_offset + Fs + 10000) + 8000*nb_lfe; + } else if (st->bitrate_bps==OPUS_BITRATE_MAX) + { + bitrate = nb_normal*300000 + nb_lfe*128000; + } else { + bitrate = st->bitrate_bps; + } + + /* Give LFE some basic stream_channel allocation but never exceed 1/20 of the + total rate for the non-energy part to avoid problems at really low rate. */ + lfe_offset = IMIN(bitrate/20, 3000) + 15*IMAX(50, Fs/frame_size); + + /* We give each stream (coupled or uncoupled) a starting bitrate. + This models the main saving of coupled channels over uncoupled. */ + stream_offset = (bitrate - channel_offset*nb_normal - lfe_offset*nb_lfe)/nb_normal/2; + stream_offset = IMAX(0, IMIN(20000, stream_offset)); + + /* Coupled streams get twice the mono rate after the offset is allocated. */ + coupled_ratio = 512; + /* Should depend on the bitrate, for now we assume LFE gets 1/8 the bits of mono */ + lfe_ratio = 32; + + total = (nb_uncoupled<<8) /* mono */ + + coupled_ratio*nb_coupled /* stereo */ + + nb_lfe*lfe_ratio; + channel_rate = 256*(opus_int64)(bitrate - lfe_offset*nb_lfe - stream_offset*(nb_coupled+nb_uncoupled) - channel_offset*nb_normal)/total; + + for (i=0;ilayout.nb_streams;i++) + { + if (ilayout.nb_coupled_streams) + rate[i] = 2*channel_offset + IMAX(0, stream_offset+(channel_rate*coupled_ratio>>8)); + else if (i!=st->lfe_stream) + rate[i] = channel_offset + IMAX(0, stream_offset + channel_rate); + else + rate[i] = IMAX(0, lfe_offset+(channel_rate*lfe_ratio>>8)); + } +} + +static void ambisonics_rate_allocation( + OpusMSEncoder *st, + opus_int32 *rate, + int frame_size, + opus_int32 Fs + ) +{ + int i; + opus_int32 total_rate; + opus_int32 per_stream_rate; + + const int nb_channels = st->layout.nb_streams + st->layout.nb_coupled_streams; + + if (st->bitrate_bps==OPUS_AUTO) + { + total_rate = (st->layout.nb_coupled_streams + st->layout.nb_streams) * + (Fs+60*Fs/frame_size) + st->layout.nb_streams * (opus_int32)15000; + } else if (st->bitrate_bps==OPUS_BITRATE_MAX) + { + total_rate = nb_channels * 320000; + } else + { + total_rate = st->bitrate_bps; + } + + /* Allocate equal number of bits to Ambisonic (uncoupled) and non-diegetic + * (coupled) streams */ + per_stream_rate = total_rate / st->layout.nb_streams; + for (i = 0; i < st->layout.nb_streams; i++) + { + rate[i] = per_stream_rate; + } +} + +static opus_int32 rate_allocation( + OpusMSEncoder *st, + opus_int32 *rate, + int frame_size + ) +{ + int i; + opus_int32 rate_sum=0; + opus_int32 Fs; + char *ptr; + + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_SAMPLE_RATE(&Fs)); + + if (st->mapping_type == MAPPING_TYPE_AMBISONICS) { + ambisonics_rate_allocation(st, rate, frame_size, Fs); + } else + { + surround_rate_allocation(st, rate, frame_size, Fs); + } + + for (i=0;ilayout.nb_streams;i++) + { + rate[i] = IMAX(rate[i], 500); + rate_sum += rate[i]; + } + return rate_sum; +} + +/* Max size in case the encoder decides to return six frames (6 x 20 ms = 120 ms) */ +#define MS_FRAME_TMP (6*1275+12) +int opus_multistream_encode_native +( + OpusMSEncoder *st, + opus_copy_channel_in_func copy_channel_in, + const void *pcm, + int analysis_frame_size, + unsigned char *data, + opus_int32 max_data_bytes, + int lsb_depth, + downmix_func downmix, + int float_api, + void *user_data +) +{ + opus_int32 Fs; + int coupled_size; + int mono_size; + int s; + char *ptr; + int tot_size; + VARDECL(opus_val16, buf); + VARDECL(opus_val16, bandSMR); + unsigned char tmp_data[MS_FRAME_TMP]; + OpusRepacketizer rp; + opus_int32 vbr; + const CELTMode *celt_mode; + opus_int32 bitrates[256]; + opus_val16 bandLogE[42]; + opus_val32 *mem = NULL; + opus_val32 *preemph_mem=NULL; + int frame_size; + opus_int32 rate_sum; + opus_int32 smallest_packet; + ALLOC_STACK; + + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + preemph_mem = ms_get_preemph_mem(st); + mem = ms_get_window_mem(st); + } + + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_SAMPLE_RATE(&Fs)); + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_VBR(&vbr)); + opus_encoder_ctl((OpusEncoder*)ptr, CELT_GET_MODE(&celt_mode)); + + frame_size = frame_size_select(analysis_frame_size, st->variable_duration, Fs); + if (frame_size <= 0) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + /* Smallest packet the encoder can produce. */ + smallest_packet = st->layout.nb_streams*2-1; + /* 100 ms needs an extra byte per stream for the ToC. */ + if (Fs/frame_size == 10) + smallest_packet += st->layout.nb_streams; + if (max_data_bytes < smallest_packet) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + ALLOC(buf, 2*frame_size, opus_val16); + coupled_size = opus_encoder_get_size(2); + mono_size = opus_encoder_get_size(1); + + ALLOC(bandSMR, 21*st->layout.nb_channels, opus_val16); + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + surround_analysis(celt_mode, pcm, bandSMR, mem, preemph_mem, frame_size, 120, st->layout.nb_channels, Fs, copy_channel_in, st->arch); + } + + /* Compute bitrate allocation between streams (this could be a lot better) */ + rate_sum = rate_allocation(st, bitrates, frame_size); + + if (!vbr) + { + if (st->bitrate_bps == OPUS_AUTO) + { + max_data_bytes = IMIN(max_data_bytes, 3*rate_sum/(3*8*Fs/frame_size)); + } else if (st->bitrate_bps != OPUS_BITRATE_MAX) + { + max_data_bytes = IMIN(max_data_bytes, IMAX(smallest_packet, + 3*st->bitrate_bps/(3*8*Fs/frame_size))); + } + } + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + for (s=0;slayout.nb_streams;s++) + { + OpusEncoder *enc; + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrates[s])); + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + opus_int32 equiv_rate; + equiv_rate = st->bitrate_bps; + if (frame_size*50 < Fs) + equiv_rate -= 60*(Fs/frame_size - 50)*st->layout.nb_channels; + if (equiv_rate > 10000*st->layout.nb_channels) + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); + else if (equiv_rate > 7000*st->layout.nb_channels) + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND)); + else if (equiv_rate > 5000*st->layout.nb_channels) + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND)); + else + opus_encoder_ctl(enc, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); + if (s < st->layout.nb_coupled_streams) + { + /* To preserve the spatial image, force stereo CELT on coupled streams */ + opus_encoder_ctl(enc, OPUS_SET_FORCE_MODE(MODE_CELT_ONLY)); + opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(2)); + } + } + else if (st->mapping_type == MAPPING_TYPE_AMBISONICS) { + opus_encoder_ctl(enc, OPUS_SET_FORCE_MODE(MODE_CELT_ONLY)); + } + } + + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + /* Counting ToC */ + tot_size = 0; + for (s=0;slayout.nb_streams;s++) + { + OpusEncoder *enc; + int len; + int curr_max; + int c1, c2; + int ret; + + opus_repacketizer_init(&rp); + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + { + int i; + int left, right; + left = get_left_channel(&st->layout, s, -1); + right = get_right_channel(&st->layout, s, -1); + (*copy_channel_in)(buf, 2, + pcm, st->layout.nb_channels, left, frame_size, user_data); + (*copy_channel_in)(buf+1, 2, + pcm, st->layout.nb_channels, right, frame_size, user_data); + ptr += align(coupled_size); + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + for (i=0;i<21;i++) + { + bandLogE[i] = bandSMR[21*left+i]; + bandLogE[21+i] = bandSMR[21*right+i]; + } + } + c1 = left; + c2 = right; + } else { + int i; + int chan = get_mono_channel(&st->layout, s, -1); + (*copy_channel_in)(buf, 1, + pcm, st->layout.nb_channels, chan, frame_size, user_data); + ptr += align(mono_size); + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + for (i=0;i<21;i++) + bandLogE[i] = bandSMR[21*chan+i]; + } + c1 = chan; + c2 = -1; + } + if (st->mapping_type == MAPPING_TYPE_SURROUND) + opus_encoder_ctl(enc, OPUS_SET_ENERGY_MASK(bandLogE)); + /* number of bytes left (+Toc) */ + curr_max = max_data_bytes - tot_size; + /* Reserve one byte for the last stream and two for the others */ + curr_max -= IMAX(0,2*(st->layout.nb_streams-s-1)-1); + /* For 100 ms, reserve an extra byte per stream for the ToC */ + if (Fs/frame_size == 10) + curr_max -= st->layout.nb_streams-s-1; + curr_max = IMIN(curr_max,MS_FRAME_TMP); + /* Repacketizer will add one or two bytes for self-delimited frames */ + if (s != st->layout.nb_streams-1) curr_max -= curr_max>253 ? 2 : 1; + if (!vbr && s == st->layout.nb_streams-1) + opus_encoder_ctl(enc, OPUS_SET_BITRATE(curr_max*(8*Fs/frame_size))); + len = opus_encode_native(enc, buf, frame_size, tmp_data, curr_max, lsb_depth, + pcm, analysis_frame_size, c1, c2, st->layout.nb_channels, downmix, float_api); + if (len<0) + { + RESTORE_STACK; + return len; + } + /* We need to use the repacketizer to add the self-delimiting lengths + while taking into account the fact that the encoder can now return + more than one frame at a time (e.g. 60 ms CELT-only) */ + ret = opus_repacketizer_cat(&rp, tmp_data, len); + /* If the opus_repacketizer_cat() fails, then something's seriously wrong + with the encoder. */ + if (ret != OPUS_OK) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + len = opus_repacketizer_out_range_impl(&rp, 0, opus_repacketizer_get_nb_frames(&rp), + data, max_data_bytes-tot_size, s != st->layout.nb_streams-1, !vbr && s == st->layout.nb_streams-1, NULL, 0); + data += len; + tot_size += len; + } + /*printf("\n");*/ + RESTORE_STACK; + return tot_size; +} + +#if !defined(DISABLE_FLOAT_API) +static void opus_copy_channel_in_float( + opus_val16 *dst, + int dst_stride, + const void *src, + int src_stride, + int src_channel, + int frame_size, + void *user_data +) +{ + const float *float_src; + opus_int32 i; + (void)user_data; + float_src = (const float *)src; + for (i=0;ilayout.nb_channels, IMAX(500*st->layout.nb_channels, value)); + } + st->bitrate_bps = value; + } + break; + case OPUS_GET_BITRATE_REQUEST: + { + int s; + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = 0; + for (s=0;slayout.nb_streams;s++) + { + opus_int32 rate; + OpusEncoder *enc; + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + opus_encoder_ctl(enc, request, &rate); + *value += rate; + } + } + break; + case OPUS_GET_LSB_DEPTH_REQUEST: + case OPUS_GET_VBR_REQUEST: + case OPUS_GET_APPLICATION_REQUEST: + case OPUS_GET_BANDWIDTH_REQUEST: + case OPUS_GET_COMPLEXITY_REQUEST: + case OPUS_GET_PACKET_LOSS_PERC_REQUEST: + case OPUS_GET_DTX_REQUEST: + case OPUS_GET_VOICE_RATIO_REQUEST: + case OPUS_GET_VBR_CONSTRAINT_REQUEST: + case OPUS_GET_SIGNAL_REQUEST: + case OPUS_GET_LOOKAHEAD_REQUEST: + case OPUS_GET_SAMPLE_RATE_REQUEST: + case OPUS_GET_INBAND_FEC_REQUEST: + case OPUS_GET_FORCE_CHANNELS_REQUEST: + case OPUS_GET_PREDICTION_DISABLED_REQUEST: + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + OpusEncoder *enc; + /* For int32* GET params, just query the first stream */ + opus_int32 *value = va_arg(ap, opus_int32*); + enc = (OpusEncoder*)ptr; + ret = opus_encoder_ctl(enc, request, value); + } + break; + case OPUS_GET_FINAL_RANGE_REQUEST: + { + int s; + opus_uint32 *value = va_arg(ap, opus_uint32*); + opus_uint32 tmp; + if (!value) + { + goto bad_arg; + } + *value=0; + for (s=0;slayout.nb_streams;s++) + { + OpusEncoder *enc; + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_encoder_ctl(enc, request, &tmp); + if (ret != OPUS_OK) break; + *value ^= tmp; + } + } + break; + case OPUS_SET_LSB_DEPTH_REQUEST: + case OPUS_SET_COMPLEXITY_REQUEST: + case OPUS_SET_VBR_REQUEST: + case OPUS_SET_VBR_CONSTRAINT_REQUEST: + case OPUS_SET_MAX_BANDWIDTH_REQUEST: + case OPUS_SET_BANDWIDTH_REQUEST: + case OPUS_SET_SIGNAL_REQUEST: + case OPUS_SET_APPLICATION_REQUEST: + case OPUS_SET_INBAND_FEC_REQUEST: + case OPUS_SET_PACKET_LOSS_PERC_REQUEST: + case OPUS_SET_DTX_REQUEST: + case OPUS_SET_FORCE_MODE_REQUEST: + case OPUS_SET_FORCE_CHANNELS_REQUEST: + case OPUS_SET_PREDICTION_DISABLED_REQUEST: + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + int s; + /* This works for int32 params */ + opus_int32 value = va_arg(ap, opus_int32); + for (s=0;slayout.nb_streams;s++) + { + OpusEncoder *enc; + + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_encoder_ctl(enc, request, value); + if (ret != OPUS_OK) + break; + } + } + break; + case OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST: + { + int s; + opus_int32 stream_id; + OpusEncoder **value; + stream_id = va_arg(ap, opus_int32); + if (stream_id<0 || stream_id >= st->layout.nb_streams) + goto bad_arg; + value = va_arg(ap, OpusEncoder**); + if (!value) + { + goto bad_arg; + } + for (s=0;slayout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + } + *value = (OpusEncoder*)ptr; + } + break; + case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + st->variable_duration = value; + } + break; + case OPUS_GET_EXPERT_FRAME_DURATION_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->variable_duration; + } + break; + case OPUS_RESET_STATE: + { + int s; + if (st->mapping_type == MAPPING_TYPE_SURROUND) + { + OPUS_CLEAR(ms_get_preemph_mem(st), st->layout.nb_channels); + OPUS_CLEAR(ms_get_window_mem(st), st->layout.nb_channels*120); + } + for (s=0;slayout.nb_streams;s++) + { + OpusEncoder *enc; + enc = (OpusEncoder*)ptr; + if (s < st->layout.nb_coupled_streams) + ptr += align(coupled_size); + else + ptr += align(mono_size); + ret = opus_encoder_ctl(enc, OPUS_RESET_STATE); + if (ret != OPUS_OK) + break; + } + } + break; + default: + ret = OPUS_UNIMPLEMENTED; + break; + } + return ret; +bad_arg: + return OPUS_BAD_ARG; +} + +int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) +{ + int ret; + va_list ap; + va_start(ap, request); + ret = opus_multistream_encoder_ctl_va_list(st, request, ap); + va_end(ap); + return ret; +} + +void opus_multistream_encoder_destroy(OpusMSEncoder *st) +{ + opus_free(st); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_private.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_private.h new file mode 100644 index 0000000000..279f5f95f6 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_private.h @@ -0,0 +1,223 @@ +/* Copyright (c) 2012 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef OPUS_PRIVATE_H +#define OPUS_PRIVATE_H + +#include "arch.h" +#include "opus.h" +#include "celt.h" + +#include /* va_list */ +#include /* offsetof */ + +struct OpusRepacketizer { + unsigned char toc; + int nb_frames; + const unsigned char *frames[48]; + opus_int16 len[48]; + int framesize; + const unsigned char *paddings[48]; + opus_int32 padding_len[48]; +}; + +typedef struct { + int id; + int frame; + const unsigned char *data; + opus_int32 len; +} opus_extension_data; + +typedef struct ChannelLayout { + int nb_channels; + int nb_streams; + int nb_coupled_streams; + unsigned char mapping[256]; +} ChannelLayout; + +typedef enum { + MAPPING_TYPE_NONE, + MAPPING_TYPE_SURROUND, + MAPPING_TYPE_AMBISONICS +} MappingType; + +struct OpusMSEncoder { + ChannelLayout layout; + int arch; + int lfe_stream; + int application; + int variable_duration; + MappingType mapping_type; + opus_int32 bitrate_bps; + /* Encoder states go here */ + /* then opus_val32 window_mem[channels*120]; */ + /* then opus_val32 preemph_mem[channels]; */ +}; + +struct OpusMSDecoder { + ChannelLayout layout; + /* Decoder states go here */ +}; + +int opus_multistream_encoder_ctl_va_list(struct OpusMSEncoder *st, int request, + va_list ap); +int opus_multistream_decoder_ctl_va_list(struct OpusMSDecoder *st, int request, + va_list ap); + +int validate_layout(const ChannelLayout *layout); +int get_left_channel(const ChannelLayout *layout, int stream_id, int prev); +int get_right_channel(const ChannelLayout *layout, int stream_id, int prev); +int get_mono_channel(const ChannelLayout *layout, int stream_id, int prev); + +typedef void (*opus_copy_channel_in_func)( + opus_val16 *dst, + int dst_stride, + const void *src, + int src_stride, + int src_channel, + int frame_size, + void *user_data +); + +typedef void (*opus_copy_channel_out_func)( + void *dst, + int dst_stride, + int dst_channel, + const opus_val16 *src, + int src_stride, + int frame_size, + void *user_data +); + +#define MODE_SILK_ONLY 1000 +#define MODE_HYBRID 1001 +#define MODE_CELT_ONLY 1002 + +#define OPUS_SET_VOICE_RATIO_REQUEST 11018 +#define OPUS_GET_VOICE_RATIO_REQUEST 11019 + +/** Configures the encoder's expected percentage of voice + * opposed to music or other signals. + * + * @note This interface is currently more aspiration than actuality. It's + * ultimately expected to bias an automatic signal classifier, but it currently + * just shifts the static bitrate to mode mapping around a little bit. + * + * @param[in] x int: Voice percentage in the range 0-100, inclusive. + * @hideinitializer */ +#define OPUS_SET_VOICE_RATIO(x) OPUS_SET_VOICE_RATIO_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured voice ratio value, @see OPUS_SET_VOICE_RATIO + * + * @param[out] x int*: Voice percentage in the range 0-100, inclusive. + * @hideinitializer */ +#define OPUS_GET_VOICE_RATIO(x) OPUS_GET_VOICE_RATIO_REQUEST, __opus_check_int_ptr(x) + + +#define OPUS_SET_FORCE_MODE_REQUEST 11002 +#define OPUS_SET_FORCE_MODE(x) OPUS_SET_FORCE_MODE_REQUEST, __opus_check_int(x) + +typedef void (*downmix_func)(const void *, opus_val32 *, int, int, int, int, int); +void downmix_float(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); +void downmix_int(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); +int is_digital_silence(const opus_val16* pcm, int frame_size, int channels, int lsb_depth); + +int encode_size(int size, unsigned char *data); + +opus_int32 frame_size_select(opus_int32 frame_size, int variable_duration, opus_int32 Fs); + +opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, + unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api); + +int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len, + opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited, + opus_int32 *packet_offset, int soft_clip, const OpusDRED *dred, opus_int32 dred_offset); + +/* Make sure everything is properly aligned. */ +static OPUS_INLINE int align(int i) +{ + struct foo {char c; union { void* p; opus_int32 i; opus_val32 v; } u;}; + + unsigned int alignment = offsetof(struct foo, u); + + /* Optimizing compilers should optimize div and multiply into and + for all sensible alignment values. */ + return ((i + alignment - 1) / alignment) * alignment; +} + +/* More than that is ridiculous for now (3 * max frames per packet)*/ +opus_int32 skip_extension(const unsigned char **data, opus_int32 len, opus_int32 *header_size); + +int opus_packet_parse_impl(const unsigned char *data, opus_int32 len, + int self_delimited, unsigned char *out_toc, + const unsigned char *frames[48], opus_int16 size[48], + int *payload_offset, opus_int32 *packet_offset, + const unsigned char **padding, opus_int32 *padding_len); + +opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end, + unsigned char *data, opus_int32 maxlen, int self_delimited, int pad, + const opus_extension_data *extensions, int nb_extensions); + +int pad_frame(unsigned char *data, opus_int32 len, opus_int32 new_len); + +int opus_multistream_encode_native +( + struct OpusMSEncoder *st, + opus_copy_channel_in_func copy_channel_in, + const void *pcm, + int analysis_frame_size, + unsigned char *data, + opus_int32 max_data_bytes, + int lsb_depth, + downmix_func downmix, + int float_api, + void *user_data +); + +int opus_multistream_decode_native( + struct OpusMSDecoder *st, + const unsigned char *data, + opus_int32 len, + void *pcm, + opus_copy_channel_out_func copy_channel_out, + int frame_size, + int decode_fec, + int soft_clip, + void *user_data +); + +opus_int32 opus_packet_extensions_parse(const unsigned char *data, opus_int32 len, opus_extension_data *extensions, opus_int32 *nb_extensions); + +opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data *extensions, opus_int32 nb_extensions, int pad); + +opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len); + +opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions); + +#endif /* OPUS_PRIVATE_H */ diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_decoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_decoder.c new file mode 100644 index 0000000000..c2e07d5bcf --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_decoder.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mathops.h" +#include "os_support.h" +#include "opus_private.h" +#include "opus_defines.h" +#include "opus_projection.h" +#include "opus_multistream.h" +#include "mapping_matrix.h" +#include "stack_alloc.h" + +struct OpusProjectionDecoder +{ + opus_int32 demixing_matrix_size_in_bytes; + /* Encoder states go here */ +}; + +#if !defined(DISABLE_FLOAT_API) +static void opus_projection_copy_channel_out_float( + void *dst, + int dst_stride, + int dst_channel, + const opus_val16 *src, + int src_stride, + int frame_size, + void *user_data) +{ + float *float_dst; + const MappingMatrix *matrix; + float_dst = (float *)dst; + matrix = (const MappingMatrix *)user_data; + + if (dst_channel == 0) + OPUS_CLEAR(float_dst, frame_size * dst_stride); + + if (src != NULL) + mapping_matrix_multiply_channel_out_float(matrix, src, dst_channel, + src_stride, float_dst, dst_stride, frame_size); +} +#endif + +static void opus_projection_copy_channel_out_short( + void *dst, + int dst_stride, + int dst_channel, + const opus_val16 *src, + int src_stride, + int frame_size, + void *user_data) +{ + opus_int16 *short_dst; + const MappingMatrix *matrix; + short_dst = (opus_int16 *)dst; + matrix = (const MappingMatrix *)user_data; + if (dst_channel == 0) + OPUS_CLEAR(short_dst, frame_size * dst_stride); + + if (src != NULL) + mapping_matrix_multiply_channel_out_short(matrix, src, dst_channel, + src_stride, short_dst, dst_stride, frame_size); +} + +static MappingMatrix *get_dec_demixing_matrix(OpusProjectionDecoder *st) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (MappingMatrix*)(void*)((char*)st + + align(sizeof(OpusProjectionDecoder))); +} + +static OpusMSDecoder *get_multistream_decoder(OpusProjectionDecoder *st) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (OpusMSDecoder*)(void*)((char*)st + + align(sizeof(OpusProjectionDecoder) + + st->demixing_matrix_size_in_bytes)); +} + +opus_int32 opus_projection_decoder_get_size(int channels, int streams, + int coupled_streams) +{ + opus_int32 matrix_size; + opus_int32 decoder_size; + + matrix_size = + mapping_matrix_get_size(streams + coupled_streams, channels); + if (!matrix_size) + return 0; + + decoder_size = opus_multistream_decoder_get_size(streams, coupled_streams); + if (!decoder_size) + return 0; + + return align(sizeof(OpusProjectionDecoder)) + matrix_size + decoder_size; +} + +int opus_projection_decoder_init(OpusProjectionDecoder *st, opus_int32 Fs, + int channels, int streams, int coupled_streams, + unsigned char *demixing_matrix, opus_int32 demixing_matrix_size) +{ + int nb_input_streams; + opus_int32 expected_matrix_size; + int i, ret; + unsigned char mapping[255]; + VARDECL(opus_int16, buf); + ALLOC_STACK; + + /* Verify supplied matrix size. */ + nb_input_streams = streams + coupled_streams; + expected_matrix_size = nb_input_streams * channels * sizeof(opus_int16); + if (expected_matrix_size != demixing_matrix_size) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + /* Convert demixing matrix input into internal format. */ + ALLOC(buf, nb_input_streams * channels, opus_int16); + for (i = 0; i < nb_input_streams * channels; i++) + { + int s = demixing_matrix[2*i + 1] << 8 | demixing_matrix[2*i]; + s = ((s & 0xFFFF) ^ 0x8000) - 0x8000; + buf[i] = (opus_int16)s; + } + + /* Assign demixing matrix. */ + st->demixing_matrix_size_in_bytes = + mapping_matrix_get_size(channels, nb_input_streams); + if (!st->demixing_matrix_size_in_bytes) + { + RESTORE_STACK; + return OPUS_BAD_ARG; + } + + mapping_matrix_init(get_dec_demixing_matrix(st), channels, nb_input_streams, 0, + buf, demixing_matrix_size); + + /* Set trivial mapping so each input channel pairs with a matrix column. */ + for (i = 0; i < channels; i++) + mapping[i] = i; + + ret = opus_multistream_decoder_init( + get_multistream_decoder(st), Fs, channels, streams, coupled_streams, mapping); + RESTORE_STACK; + return ret; +} + +OpusProjectionDecoder *opus_projection_decoder_create( + opus_int32 Fs, int channels, int streams, int coupled_streams, + unsigned char *demixing_matrix, opus_int32 demixing_matrix_size, int *error) +{ + int size; + int ret; + OpusProjectionDecoder *st; + + /* Allocate space for the projection decoder. */ + size = opus_projection_decoder_get_size(channels, streams, coupled_streams); + if (!size) { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + st = (OpusProjectionDecoder *)opus_alloc(size); + if (!st) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + + /* Initialize projection decoder with provided settings. */ + ret = opus_projection_decoder_init(st, Fs, channels, streams, coupled_streams, + demixing_matrix, demixing_matrix_size); + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} + +#ifdef FIXED_POINT +int opus_projection_decode(OpusProjectionDecoder *st, const unsigned char *data, + opus_int32 len, opus_int16 *pcm, int frame_size, + int decode_fec) +{ + return opus_multistream_decode_native(get_multistream_decoder(st), data, len, + pcm, opus_projection_copy_channel_out_short, frame_size, decode_fec, 0, + get_dec_demixing_matrix(st)); +} +#else +int opus_projection_decode(OpusProjectionDecoder *st, const unsigned char *data, + opus_int32 len, opus_int16 *pcm, int frame_size, + int decode_fec) +{ + return opus_multistream_decode_native(get_multistream_decoder(st), data, len, + pcm, opus_projection_copy_channel_out_short, frame_size, decode_fec, 1, + get_dec_demixing_matrix(st)); +} +#endif + +#ifndef DISABLE_FLOAT_API +int opus_projection_decode_float(OpusProjectionDecoder *st, const unsigned char *data, + opus_int32 len, float *pcm, int frame_size, int decode_fec) +{ + return opus_multistream_decode_native(get_multistream_decoder(st), data, len, + pcm, opus_projection_copy_channel_out_float, frame_size, decode_fec, 0, + get_dec_demixing_matrix(st)); +} +#endif + +int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) +{ + va_list ap; + int ret = OPUS_OK; + + va_start(ap, request); + ret = opus_multistream_decoder_ctl_va_list(get_multistream_decoder(st), + request, ap); + va_end(ap); + return ret; +} + +void opus_projection_decoder_destroy(OpusProjectionDecoder *st) +{ + opus_free(st); +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_encoder.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_encoder.c new file mode 100644 index 0000000000..92813ad01f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/opus_projection_encoder.c @@ -0,0 +1,510 @@ +/* Copyright (c) 2017 Google Inc. + Written by Andrew Allen */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mathops.h" +#include "os_support.h" +#include "opus_private.h" +#include "opus_defines.h" +#include "opus_projection.h" +#include "opus_multistream.h" +#include "stack_alloc.h" +#include "mapping_matrix.h" + +struct OpusProjectionEncoder +{ + opus_int32 mixing_matrix_size_in_bytes; + opus_int32 demixing_matrix_size_in_bytes; + /* Encoder states go here */ +}; + +#if !defined(DISABLE_FLOAT_API) +static void opus_projection_copy_channel_in_float( + opus_val16 *dst, + int dst_stride, + const void *src, + int src_stride, + int src_channel, + int frame_size, + void *user_data +) +{ + mapping_matrix_multiply_channel_in_float((const MappingMatrix*)user_data, + (const float*)src, src_stride, dst, src_channel, dst_stride, frame_size); +} +#endif + +static void opus_projection_copy_channel_in_short( + opus_val16 *dst, + int dst_stride, + const void *src, + int src_stride, + int src_channel, + int frame_size, + void *user_data +) +{ + mapping_matrix_multiply_channel_in_short((const MappingMatrix*)user_data, + (const opus_int16*)src, src_stride, dst, src_channel, dst_stride, frame_size); +} + +static int get_order_plus_one_from_channels(int channels, int *order_plus_one) +{ + int order_plus_one_; + int acn_channels; + int nondiegetic_channels; + + /* Allowed numbers of channels: + * (1 + n)^2 + 2j, for n = 0...14 and j = 0 or 1. + */ + if (channels < 1 || channels > 227) + return OPUS_BAD_ARG; + + order_plus_one_ = isqrt32(channels); + acn_channels = order_plus_one_ * order_plus_one_; + nondiegetic_channels = channels - acn_channels; + if (nondiegetic_channels != 0 && nondiegetic_channels != 2) + return OPUS_BAD_ARG; + + if (order_plus_one) + *order_plus_one = order_plus_one_; + return OPUS_OK; +} + +static int get_streams_from_channels(int channels, int mapping_family, + int *streams, int *coupled_streams, + int *order_plus_one) +{ + if (mapping_family == 3) + { + if (get_order_plus_one_from_channels(channels, order_plus_one) != OPUS_OK) + return OPUS_BAD_ARG; + if (streams) + *streams = (channels + 1) / 2; + if (coupled_streams) + *coupled_streams = channels / 2; + return OPUS_OK; + } + return OPUS_BAD_ARG; +} + +static MappingMatrix *get_mixing_matrix(OpusProjectionEncoder *st) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (MappingMatrix *)(void*)((char*)st + + align(sizeof(OpusProjectionEncoder))); +} + +static MappingMatrix *get_enc_demixing_matrix(OpusProjectionEncoder *st) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (MappingMatrix *)(void*)((char*)st + + align(sizeof(OpusProjectionEncoder) + + st->mixing_matrix_size_in_bytes)); +} + +static OpusMSEncoder *get_multistream_encoder(OpusProjectionEncoder *st) +{ + /* void* cast avoids clang -Wcast-align warning */ + return (OpusMSEncoder *)(void*)((char*)st + + align(sizeof(OpusProjectionEncoder) + + st->mixing_matrix_size_in_bytes + + st->demixing_matrix_size_in_bytes)); +} + +opus_int32 opus_projection_ambisonics_encoder_get_size(int channels, + int mapping_family) +{ + int nb_streams; + int nb_coupled_streams; + int order_plus_one; + int mixing_matrix_rows, mixing_matrix_cols; + int demixing_matrix_rows, demixing_matrix_cols; + opus_int32 mixing_matrix_size, demixing_matrix_size; + opus_int32 encoder_size; + int ret; + + ret = get_streams_from_channels(channels, mapping_family, &nb_streams, + &nb_coupled_streams, &order_plus_one); + if (ret != OPUS_OK) + return 0; + + if (order_plus_one == 2) + { + mixing_matrix_rows = mapping_matrix_foa_mixing.rows; + mixing_matrix_cols = mapping_matrix_foa_mixing.cols; + demixing_matrix_rows = mapping_matrix_foa_demixing.rows; + demixing_matrix_cols = mapping_matrix_foa_demixing.cols; + } + else if (order_plus_one == 3) + { + mixing_matrix_rows = mapping_matrix_soa_mixing.rows; + mixing_matrix_cols = mapping_matrix_soa_mixing.cols; + demixing_matrix_rows = mapping_matrix_soa_demixing.rows; + demixing_matrix_cols = mapping_matrix_soa_demixing.cols; + } + else if (order_plus_one == 4) + { + mixing_matrix_rows = mapping_matrix_toa_mixing.rows; + mixing_matrix_cols = mapping_matrix_toa_mixing.cols; + demixing_matrix_rows = mapping_matrix_toa_demixing.rows; + demixing_matrix_cols = mapping_matrix_toa_demixing.cols; + } + else if (order_plus_one == 5) + { + mixing_matrix_rows = mapping_matrix_fourthoa_mixing.rows; + mixing_matrix_cols = mapping_matrix_fourthoa_mixing.cols; + demixing_matrix_rows = mapping_matrix_fourthoa_demixing.rows; + demixing_matrix_cols = mapping_matrix_fourthoa_demixing.cols; + } + else if (order_plus_one == 6) + { + mixing_matrix_rows = mapping_matrix_fifthoa_mixing.rows; + mixing_matrix_cols = mapping_matrix_fifthoa_mixing.cols; + demixing_matrix_rows = mapping_matrix_fifthoa_demixing.rows; + demixing_matrix_cols = mapping_matrix_fifthoa_demixing.cols; + } + else + return 0; + + mixing_matrix_size = + mapping_matrix_get_size(mixing_matrix_rows, mixing_matrix_cols); + if (!mixing_matrix_size) + return 0; + + demixing_matrix_size = + mapping_matrix_get_size(demixing_matrix_rows, demixing_matrix_cols); + if (!demixing_matrix_size) + return 0; + + encoder_size = + opus_multistream_encoder_get_size(nb_streams, nb_coupled_streams); + if (!encoder_size) + return 0; + + return align(sizeof(OpusProjectionEncoder)) + + mixing_matrix_size + demixing_matrix_size + encoder_size; +} + +int opus_projection_ambisonics_encoder_init(OpusProjectionEncoder *st, opus_int32 Fs, + int channels, int mapping_family, + int *streams, int *coupled_streams, + int application) +{ + MappingMatrix *mixing_matrix; + MappingMatrix *demixing_matrix; + OpusMSEncoder *ms_encoder; + int i; + int ret; + int order_plus_one; + unsigned char mapping[255]; + + if (streams == NULL || coupled_streams == NULL) { + return OPUS_BAD_ARG; + } + + if (get_streams_from_channels(channels, mapping_family, streams, + coupled_streams, &order_plus_one) != OPUS_OK) + return OPUS_BAD_ARG; + + if (mapping_family == 3) + { + /* Assign mixing matrix based on available pre-computed matrices. */ + mixing_matrix = get_mixing_matrix(st); + if (order_plus_one == 2) + { + mapping_matrix_init(mixing_matrix, mapping_matrix_foa_mixing.rows, + mapping_matrix_foa_mixing.cols, mapping_matrix_foa_mixing.gain, + mapping_matrix_foa_mixing_data, + sizeof(mapping_matrix_foa_mixing_data)); + } + else if (order_plus_one == 3) + { + mapping_matrix_init(mixing_matrix, mapping_matrix_soa_mixing.rows, + mapping_matrix_soa_mixing.cols, mapping_matrix_soa_mixing.gain, + mapping_matrix_soa_mixing_data, + sizeof(mapping_matrix_soa_mixing_data)); + } + else if (order_plus_one == 4) + { + mapping_matrix_init(mixing_matrix, mapping_matrix_toa_mixing.rows, + mapping_matrix_toa_mixing.cols, mapping_matrix_toa_mixing.gain, + mapping_matrix_toa_mixing_data, + sizeof(mapping_matrix_toa_mixing_data)); + } + else if (order_plus_one == 5) + { + mapping_matrix_init(mixing_matrix, mapping_matrix_fourthoa_mixing.rows, + mapping_matrix_fourthoa_mixing.cols, mapping_matrix_fourthoa_mixing.gain, + mapping_matrix_fourthoa_mixing_data, + sizeof(mapping_matrix_fourthoa_mixing_data)); + } + else if (order_plus_one == 6) + { + mapping_matrix_init(mixing_matrix, mapping_matrix_fifthoa_mixing.rows, + mapping_matrix_fifthoa_mixing.cols, mapping_matrix_fifthoa_mixing.gain, + mapping_matrix_fifthoa_mixing_data, + sizeof(mapping_matrix_fifthoa_mixing_data)); + } + else + return OPUS_BAD_ARG; + + st->mixing_matrix_size_in_bytes = mapping_matrix_get_size( + mixing_matrix->rows, mixing_matrix->cols); + if (!st->mixing_matrix_size_in_bytes) + return OPUS_BAD_ARG; + + /* Assign demixing matrix based on available pre-computed matrices. */ + demixing_matrix = get_enc_demixing_matrix(st); + if (order_plus_one == 2) + { + mapping_matrix_init(demixing_matrix, mapping_matrix_foa_demixing.rows, + mapping_matrix_foa_demixing.cols, mapping_matrix_foa_demixing.gain, + mapping_matrix_foa_demixing_data, + sizeof(mapping_matrix_foa_demixing_data)); + } + else if (order_plus_one == 3) + { + mapping_matrix_init(demixing_matrix, mapping_matrix_soa_demixing.rows, + mapping_matrix_soa_demixing.cols, mapping_matrix_soa_demixing.gain, + mapping_matrix_soa_demixing_data, + sizeof(mapping_matrix_soa_demixing_data)); + } + else if (order_plus_one == 4) + { + mapping_matrix_init(demixing_matrix, mapping_matrix_toa_demixing.rows, + mapping_matrix_toa_demixing.cols, mapping_matrix_toa_demixing.gain, + mapping_matrix_toa_demixing_data, + sizeof(mapping_matrix_toa_demixing_data)); + } + else if (order_plus_one == 5) + { + mapping_matrix_init(demixing_matrix, mapping_matrix_fourthoa_demixing.rows, + mapping_matrix_fourthoa_demixing.cols, mapping_matrix_fourthoa_demixing.gain, + mapping_matrix_fourthoa_demixing_data, + sizeof(mapping_matrix_fourthoa_demixing_data)); + } + else if (order_plus_one == 6) + { + mapping_matrix_init(demixing_matrix, mapping_matrix_fifthoa_demixing.rows, + mapping_matrix_fifthoa_demixing.cols, mapping_matrix_fifthoa_demixing.gain, + mapping_matrix_fifthoa_demixing_data, + sizeof(mapping_matrix_fifthoa_demixing_data)); + } + else + return OPUS_BAD_ARG; + + st->demixing_matrix_size_in_bytes = mapping_matrix_get_size( + demixing_matrix->rows, demixing_matrix->cols); + if (!st->demixing_matrix_size_in_bytes) + return OPUS_BAD_ARG; + } + else + return OPUS_UNIMPLEMENTED; + + /* Ensure matrices are large enough for desired coding scheme. */ + if (*streams + *coupled_streams > mixing_matrix->rows || + channels > mixing_matrix->cols || + channels > demixing_matrix->rows || + *streams + *coupled_streams > demixing_matrix->cols) + return OPUS_BAD_ARG; + + /* Set trivial mapping so each input channel pairs with a matrix column. */ + for (i = 0; i < channels; i++) + mapping[i] = i; + + /* Initialize multistream encoder with provided settings. */ + ms_encoder = get_multistream_encoder(st); + ret = opus_multistream_encoder_init(ms_encoder, Fs, channels, *streams, + *coupled_streams, mapping, application); + return ret; +} + +OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( + opus_int32 Fs, int channels, int mapping_family, int *streams, + int *coupled_streams, int application, int *error) +{ + int size; + int ret; + OpusProjectionEncoder *st; + + /* Allocate space for the projection encoder. */ + size = opus_projection_ambisonics_encoder_get_size(channels, mapping_family); + if (!size) { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + st = (OpusProjectionEncoder *)opus_alloc(size); + if (!st) + { + if (error) + *error = OPUS_ALLOC_FAIL; + return NULL; + } + + /* Initialize projection encoder with provided settings. */ + ret = opus_projection_ambisonics_encoder_init(st, Fs, channels, + mapping_family, streams, coupled_streams, application); + if (ret != OPUS_OK) + { + opus_free(st); + st = NULL; + } + if (error) + *error = ret; + return st; +} + +int opus_projection_encode(OpusProjectionEncoder *st, const opus_int16 *pcm, + int frame_size, unsigned char *data, + opus_int32 max_data_bytes) +{ + return opus_multistream_encode_native(get_multistream_encoder(st), + opus_projection_copy_channel_in_short, pcm, frame_size, data, + max_data_bytes, 16, downmix_int, 0, get_mixing_matrix(st)); +} + +#ifndef DISABLE_FLOAT_API +#ifdef FIXED_POINT +int opus_projection_encode_float(OpusProjectionEncoder *st, const float *pcm, + int frame_size, unsigned char *data, + opus_int32 max_data_bytes) +{ + return opus_multistream_encode_native(get_multistream_encoder(st), + opus_projection_copy_channel_in_float, pcm, frame_size, data, + max_data_bytes, 16, downmix_float, 1, get_mixing_matrix(st)); +} +#else +int opus_projection_encode_float(OpusProjectionEncoder *st, const float *pcm, + int frame_size, unsigned char *data, + opus_int32 max_data_bytes) +{ + return opus_multistream_encode_native(get_multistream_encoder(st), + opus_projection_copy_channel_in_float, pcm, frame_size, data, + max_data_bytes, 24, downmix_float, 1, get_mixing_matrix(st)); +} +#endif +#endif + +void opus_projection_encoder_destroy(OpusProjectionEncoder *st) +{ + opus_free(st); +} + +int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) +{ + va_list ap; + MappingMatrix *demixing_matrix; + OpusMSEncoder *ms_encoder; + int ret = OPUS_OK; + + ms_encoder = get_multistream_encoder(st); + demixing_matrix = get_enc_demixing_matrix(st); + + va_start(ap, request); + switch(request) + { + case OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = + ms_encoder->layout.nb_channels * (ms_encoder->layout.nb_streams + + ms_encoder->layout.nb_coupled_streams) * sizeof(opus_int16); + } + break; + case OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = demixing_matrix->gain; + } + break; + case OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST: + { + int i, j, k, l; + int nb_input_streams; + int nb_output_streams; + unsigned char *external_char; + opus_int16 *internal_short; + opus_int32 external_size; + opus_int32 internal_size; + + /* (I/O is in relation to the decoder's perspective). */ + nb_input_streams = ms_encoder->layout.nb_streams + + ms_encoder->layout.nb_coupled_streams; + nb_output_streams = ms_encoder->layout.nb_channels; + + external_char = va_arg(ap, unsigned char *); + external_size = va_arg(ap, opus_int32); + if (!external_char) + { + goto bad_arg; + } + internal_short = mapping_matrix_get_data(demixing_matrix); + internal_size = nb_input_streams * nb_output_streams * sizeof(opus_int16); + if (external_size != internal_size) + { + goto bad_arg; + } + + /* Copy demixing matrix subset to output destination. */ + l = 0; + for (i = 0; i < nb_input_streams; i++) { + for (j = 0; j < nb_output_streams; j++) { + k = demixing_matrix->rows * i + j; + external_char[2*l] = (unsigned char)internal_short[k]; + external_char[2*l+1] = (unsigned char)(internal_short[k] >> 8); + l++; + } + } + } + break; + default: + { + ret = opus_multistream_encoder_ctl_va_list(ms_encoder, request, ap); + } + break; + } + va_end(ap); + return ret; + +bad_arg: + va_end(ap); + return OPUS_BAD_ARG; +} + diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/repacketizer.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/repacketizer.c new file mode 100644 index 0000000000..79798b0217 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpus/libopus/src/repacketizer.c @@ -0,0 +1,464 @@ +/* Copyright (c) 2011 Xiph.Org Foundation + Written by Jean-Marc Valin */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "opus.h" +#include "opus_private.h" +#include "os_support.h" +#include "stack_alloc.h" + + +int opus_repacketizer_get_size(void) +{ + return sizeof(OpusRepacketizer); +} + +OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) +{ + rp->nb_frames = 0; + return rp; +} + +OpusRepacketizer *opus_repacketizer_create(void) +{ + OpusRepacketizer *rp; + rp=(OpusRepacketizer *)opus_alloc(opus_repacketizer_get_size()); + if(rp==NULL)return NULL; + return opus_repacketizer_init(rp); +} + +void opus_repacketizer_destroy(OpusRepacketizer *rp) +{ + opus_free(rp); +} + +static int opus_repacketizer_cat_impl(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len, int self_delimited) +{ + unsigned char tmp_toc; + int curr_nb_frames,ret; + /* Set of check ToC */ + if (len<1) return OPUS_INVALID_PACKET; + if (rp->nb_frames == 0) + { + rp->toc = data[0]; + rp->framesize = opus_packet_get_samples_per_frame(data, 8000); + } else if ((rp->toc&0xFC) != (data[0]&0xFC)) + { + /*fprintf(stderr, "toc mismatch: 0x%x vs 0x%x\n", rp->toc, data[0]);*/ + return OPUS_INVALID_PACKET; + } + curr_nb_frames = opus_packet_get_nb_frames(data, len); + if(curr_nb_frames<1) return OPUS_INVALID_PACKET; + + /* Check the 120 ms maximum packet size */ + if ((curr_nb_frames+rp->nb_frames)*rp->framesize > 960) + { + return OPUS_INVALID_PACKET; + } + + ret=opus_packet_parse_impl(data, len, self_delimited, &tmp_toc, &rp->frames[rp->nb_frames], &rp->len[rp->nb_frames], + NULL, NULL, &rp->paddings[rp->nb_frames], &rp->padding_len[rp->nb_frames]); + if(ret<1)return ret; + + /* set padding length to zero for all but the first frame */ + while (curr_nb_frames > 1) + { + rp->nb_frames++; + rp->padding_len[rp->nb_frames] = 0; + rp->paddings[rp->nb_frames] = NULL; + curr_nb_frames--; + } + rp->nb_frames++; + return OPUS_OK; +} + +int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) +{ + return opus_repacketizer_cat_impl(rp, data, len, 0); +} + +int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) +{ + return rp->nb_frames; +} + +opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int end, + unsigned char *data, opus_int32 maxlen, int self_delimited, int pad, const opus_extension_data *extensions, int nb_extensions) +{ + int i, count; + opus_int32 tot_size; + opus_int16 *len; + const unsigned char **frames; + unsigned char * ptr; + int ones_begin=0, ones_end=0; + int ext_begin=0, ext_len=0; + int ext_count, total_ext_count; + VARDECL(opus_extension_data, all_extensions); + SAVE_STACK; + + if (begin<0 || begin>=end || end>rp->nb_frames) + { + /*fprintf(stderr, "%d %d %d\n", begin, end, rp->nb_frames);*/ + RESTORE_STACK; + return OPUS_BAD_ARG; + } + count = end-begin; + + len = rp->len+begin; + frames = rp->frames+begin; + if (self_delimited) + tot_size = 1 + (len[count-1]>=252); + else + tot_size = 0; + + /* figure out total number of extensions */ + total_ext_count = nb_extensions; + for (i=begin;ipaddings[i], rp->padding_len[i]); + if (n > 0) total_ext_count += n; + } + ALLOC(all_extensions, total_ext_count ? total_ext_count : ALLOC_NONE, opus_extension_data); + /* copy over any extensions that were passed in */ + for (ext_count=0;ext_countpaddings[i], rp->padding_len[i], + &all_extensions[ext_count], &frame_ext_count); + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + /* renumber the extension frame numbers */ + for (j=0;j maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = rp->toc&0xFC; + } else if (count==2) + { + if (len[1] == len[0]) + { + /* Code 1 */ + tot_size += 2*len[0]+1; + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x1; + } else { + /* Code 2 */ + tot_size += len[0]+len[1]+2+(len[0]>=252); + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x2; + ptr += encode_size(len[0], ptr); + } + } + if (count > 2 || (pad && tot_size < maxlen) || ext_count > 0) + { + /* Code 3 */ + int vbr; + int pad_amount=0; + + /* Restart the process for the padding case */ + ptr = data; + if (self_delimited) + tot_size = 1 + (len[count-1]>=252); + else + tot_size = 0; + vbr = 0; + for (i=1;i=252) + len[i]; + tot_size += len[count-1]; + + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x3; + *ptr++ = count | 0x80; + } else { + tot_size += count*len[0]+2; + if (tot_size > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + *ptr++ = (rp->toc&0xFC) | 0x3; + *ptr++ = count; + } + pad_amount = pad ? (maxlen-tot_size) : 0; + if (ext_count>0) + { + /* figure out how much space we need for the extensions */ + ext_len = opus_packet_extensions_generate(NULL, maxlen-tot_size, all_extensions, ext_count, 0); + if (ext_len < 0) return ext_len; + if (!pad) + pad_amount = ext_len + ext_len/254 + 1; + } + if (pad_amount != 0) + { + int nb_255s; + data[1] |= 0x40; + nb_255s = (pad_amount-1)/255; + if (tot_size + ext_len + nb_255s + 1 > maxlen) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + ext_begin = tot_size+pad_amount-ext_len; + /* Prepend 0x01 padding */ + ones_begin = tot_size+nb_255s+1; + ones_end = tot_size+pad_amount-ext_len; + for (i=0;i 0) { + int ret = opus_packet_extensions_generate(&data[ext_begin], ext_len, all_extensions, ext_count, 0); + celt_assert(ret == ext_len); + } + for (i=ones_begin;inb_frames, data, maxlen, 0, 0, NULL, 0); +} + +opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data *extensions, int nb_extensions) +{ + OpusRepacketizer rp; + opus_int32 ret; + VARDECL(unsigned char, copy); + SAVE_STACK; + if (len < 1) + return OPUS_BAD_ARG; + if (len==new_len) + return OPUS_OK; + else if (len > new_len) + return OPUS_BAD_ARG; + ALLOC(copy, len, unsigned char); + opus_repacketizer_init(&rp); + /* Moving payload to the end of the packet so we can do in-place padding */ + OPUS_COPY(copy, data, len); + ret = opus_repacketizer_cat(&rp, copy, len); + if (ret != OPUS_OK) + return ret; + ret = opus_repacketizer_out_range_impl(&rp, 0, rp.nb_frames, data, new_len, 0, pad, extensions, nb_extensions); + RESTORE_STACK; + return ret; +} + +int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len) +{ + opus_int32 ret; + ALLOC_STACK; + ret = opus_packet_pad_impl(data, len, new_len, 1, NULL, 0); + RESTORE_STACK; + if (ret > 0) + return OPUS_OK; + else + return ret; +} + +opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len) +{ + OpusRepacketizer rp; + opus_int32 ret; + int i; + if (len < 1) + return OPUS_BAD_ARG; + opus_repacketizer_init(&rp); + ret = opus_repacketizer_cat(&rp, data, len); + if (ret < 0) + return ret; + /* Discard all padding and extensions. */ + for (i=0;i 0 && ret <= len); + return ret; +} + +int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams) +{ + int s; + int count; + unsigned char toc; + opus_int16 size[48]; + opus_int32 packet_offset; + opus_int32 amount; + + if (len < 1) + return OPUS_BAD_ARG; + if (len==new_len) + return OPUS_OK; + else if (len > new_len) + return OPUS_BAD_ARG; + amount = new_len - len; + /* Seek to last stream */ + for (s=0;s +#include +#include + +#define MAX_PACKETOUT 32000 + +void usage(char *argv0) +{ + fprintf(stderr, "usage: %s [options] input_file output_file\n", argv0); +} + +static void int_to_char(opus_uint32 i, unsigned char ch[4]) +{ + ch[0] = i>>24; + ch[1] = (i>>16)&0xFF; + ch[2] = (i>>8)&0xFF; + ch[3] = i&0xFF; +} + +static opus_uint32 char_to_int(unsigned char ch[4]) +{ + return ((opus_uint32)ch[0]<<24) | ((opus_uint32)ch[1]<<16) + | ((opus_uint32)ch[2]<< 8) | (opus_uint32)ch[3]; +} + +int main(int argc, char *argv[]) +{ + int i, eof=0; + FILE *fin, *fout; + unsigned char packets[48][1500]; + int len[48]; + int rng[48]; + OpusRepacketizer *rp; + unsigned char output_packet[MAX_PACKETOUT]; + int merge = 1, split=0; + + if (argc < 3) + { + usage(argv[0]); + return EXIT_FAILURE; + } + for (i=1;i48) + { + fprintf(stderr, "-merge parameter must be less than 48.\n"); + return EXIT_FAILURE; + } + i++; + } else if (strcmp(argv[i], "-split")==0) + split = 1; + else + { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + usage(argv[0]); + return EXIT_FAILURE; + } + } + fin = fopen(argv[argc-2], "r"); + if(fin==NULL) + { + fprintf(stderr, "Error opening input file: %s\n", argv[argc-2]); + return EXIT_FAILURE; + } + fout = fopen(argv[argc-1], "w"); + if(fout==NULL) + { + fprintf(stderr, "Error opening output file: %s\n", argv[argc-1]); + fclose(fin); + return EXIT_FAILURE; + } + + rp = opus_repacketizer_create(); + while (!eof) + { + int err; + int nb_packets=merge; + opus_repacketizer_init(rp); + for (i=0;i1500 || len[i]<0) + { + if (feof(fin)) + { + eof = 1; + } else { + fprintf(stderr, "Invalid payload length\n"); + fclose(fin); + fclose(fout); + return EXIT_FAILURE; + } + break; + } + if (fread(ch, 1, 4, fin)!=4) + { + if (feof(fin)) + { + eof = 1; + } else { + fprintf(stderr, "Error reading.\n"); + fclose(fin); + fclose(fout); + return EXIT_FAILURE; + } + break; + } + rng[i] = char_to_int(ch); + if (fread(packets[i], len[i], 1, fin)!=1) { + if (feof(fin)) + { + eof = 1; + } else { + fprintf(stderr, "Error reading packet of %u bytes.\n", len[i]); + fclose(fin); + fclose(fout); + return EXIT_FAILURE; + } + break; + } + err = opus_repacketizer_cat(rp, packets[i], len[i]); + if (err!=OPUS_OK) + { + fprintf(stderr, "opus_repacketizer_cat() failed: %s\n", opus_strerror(err)); + break; + } + } + nb_packets = i; + + if (eof) + break; + + if (!split) + { + err = opus_repacketizer_out(rp, output_packet, MAX_PACKETOUT); + if (err>0) { + unsigned char int_field[4]; + int_to_char(err, int_field); + if(fwrite(int_field, 1, 4, fout)!=4){ + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + int_to_char(rng[nb_packets-1], int_field); + if (fwrite(int_field, 1, 4, fout)!=4) { + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + if (fwrite(output_packet, 1, err, fout)!=(unsigned)err) { + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + /*fprintf(stderr, "out len = %d\n", err);*/ + } else { + fprintf(stderr, "opus_repacketizer_out() failed: %s\n", opus_strerror(err)); + } + } else { + int nb_frames = opus_repacketizer_get_nb_frames(rp); + for (i=0;i0) { + unsigned char int_field[4]; + int_to_char(err, int_field); + if (fwrite(int_field, 1, 4, fout)!=4) { + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + if (i==nb_frames-1) + int_to_char(rng[nb_packets-1], int_field); + else + int_to_char(0, int_field); + if (fwrite(int_field, 1, 4, fout)!=4) { + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + if (fwrite(output_packet, 1, err, fout)!=(unsigned)err) { + fprintf(stderr, "Error writing.\n"); + return EXIT_FAILURE; + } + /*fprintf(stderr, "out len = %d\n", err);*/ + } else { + fprintf(stderr, "opus_repacketizer_out() failed: %s\n", opus_strerror(err)); + } + + } + } + } + + fclose(fin); + fclose(fout); + return EXIT_SUCCESS; +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/copus_helpers.c b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/copus_helpers.c new file mode 100644 index 0000000000..0e48652fd9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/copus_helpers.c @@ -0,0 +1,17 @@ +#include "copus_helpers.h" + +int copus_set_bitrate(OpusEncoder *st, opus_int32 bitrate) { + return opus_encoder_ctl(st, OPUS_SET_BITRATE_REQUEST, bitrate); +} + +int copus_set_vbr(OpusEncoder *st, int onoff) { + return opus_encoder_ctl(st, OPUS_SET_VBR_REQUEST, onoff); +} + +int copus_set_complexity(OpusEncoder *st, opus_int32 complexity) { + return opus_encoder_ctl(st, OPUS_SET_COMPLEXITY_REQUEST, complexity); +} + +int copus_set_signal(OpusEncoder *st, opus_int32 signal) { + return opus_encoder_ctl(st, OPUS_SET_SIGNAL_REQUEST, signal); +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/COpusHelpers/COpusHelpers.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/COpusHelpers/COpusHelpers.h new file mode 100644 index 0000000000..372601e5d2 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/COpusHelpers/COpusHelpers.h @@ -0,0 +1,6 @@ +#ifndef COpusHelpers_h +#define COpusHelpers_h + +#include + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/copus_helpers.h b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/copus_helpers.h new file mode 100644 index 0000000000..6619876d44 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/copus_helpers.h @@ -0,0 +1,14 @@ +#ifndef COPUS_HELPERS_H +#define COPUS_HELPERS_H + +#include + +/* Non-variadic wrappers around opus_encoder_ctl, which is C-variadic and thus + not callable from Swift. Each returns the underlying ctl status (OPUS_OK on + success). */ +int copus_set_bitrate(OpusEncoder *st, opus_int32 bitrate); +int copus_set_vbr(OpusEncoder *st, int onoff); +int copus_set_complexity(OpusEncoder *st, opus_int32 complexity); +int copus_set_signal(OpusEncoder *st, opus_int32 signal); + +#endif diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/module.modulemap b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/module.modulemap new file mode 100644 index 0000000000..75705a9618 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/COpusHelpers/include/module.modulemap @@ -0,0 +1,4 @@ +module COpusHelpers { + header "copus_helpers.h" + export * +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OggOpusWriter.swift b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OggOpusWriter.swift new file mode 100644 index 0000000000..5975efdc7d --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OggOpusWriter.swift @@ -0,0 +1,127 @@ +import COgg +import Foundation + +public enum OggWriterError: Swift.Error { case cantCreateFile } + +/// Streaming Ogg container writer for a single Opus logical bitstream. Emits the +/// two mandatory header pages (OpusHead, OpusTags) on init, one audio packet per +/// `writePacket`, and a final flushed page on `finish`. Pages are written to disk +/// as they emit, so peak memory is bounded to roughly one Ogg page. +/// +/// EOS is set on the last real audio packet (not an extra empty packet), so the +/// decoder sees exactly the samples provided via `writePacket`. +public final class OggOpusWriter { + private var stream = ogg_stream_state() + private let handle: FileHandle + private var packetNo: Int64 = 0 + private var granulePos: Int64 = 0 + private let preSkip = 312 // 6.5 ms @ 48 kHz, Opus default + + // Buffer for the most-recent audio packet so finish() can mark it EOS. + private var pendingBytes: [UInt8] = [] + private var pendingGranule: Int64 = 0 + private var hasPending = false + + public init(url: URL) throws { + FileManager.default.createFile(atPath: url.path, contents: nil) + guard let h = try? FileHandle(forWritingTo: url) else { + throw OggWriterError.cantCreateFile + } + handle = h + ogg_stream_init(&stream, Int32.random(in: 1...Int32.max)) + writeOpusHead() + writeOpusTags() + } + + /// Appends one encoded audio packet. `samples` is the decoded sample count this + /// packet represents (960 for a 20 ms frame), used to advance granulepos. + public func writePacket(_ bytes: Data, samples: Int) { + granulePos += Int64(samples) + // Flush the previously-buffered packet (not EOS) before buffering this one. + if hasPending { + submitBytes(pendingBytes, granule: pendingGranule, bos: false, eos: false, flush: false) + } + pendingBytes = [UInt8](bytes) + pendingGranule = granulePos + hasPending = true + } + + /// Marks the last buffered audio packet as EOS, flushes all pending pages, and + /// closes the file. Must be called exactly once after all `writePacket` calls. + public func finish() { + if hasPending { + // EOS goes on the last real audio packet — no extra empty packet. + submitBytes(pendingBytes, granule: pendingGranule, bos: false, eos: true, flush: true) + hasPending = false + } else { + // No audio packets were written (e.g. record-then-instant-stop). Emit a + // zero-length EOS packet so the file still terminates with an EOS-flagged + // page — an Ogg Opus stream MUST end with one (RFC 7845 §3); otherwise a + // decoder treats the file as truncated. + submitBytes([], granule: 0, bos: false, eos: true, flush: true) + } + try? handle.close() + ogg_stream_clear(&stream) + } + + // MARK: - Header packets + + private func writeOpusHead() { + var h = [UInt8]() + h.append(contentsOf: Array("OpusHead".utf8)) // magic (8) + h.append(1) // version + h.append(1) // channel count (mono) + h.append(UInt8(preSkip & 0xFF)); h.append(UInt8((preSkip >> 8) & 0xFF)) // pre-skip LE + appendLE32(&h, 48_000) // input sample rate LE + h.append(0); h.append(0) // output gain + h.append(0) // channel mapping family + submitBytes(h, granule: 0, bos: true, eos: false, flush: true) + } + + private func writeOpusTags() { + var t = [UInt8]() + t.append(contentsOf: Array("OpusTags".utf8)) // magic (8) + let vendor = Array("tgwatch".utf8) + appendLE32(&t, UInt32(vendor.count)); t.append(contentsOf: vendor) + appendLE32(&t, 0) // user comment list length + submitBytes(t, granule: 0, bos: false, eos: false, flush: true) + } + + // MARK: - libogg plumbing + + private func submitBytes(_ bytes: [UInt8], granule: Int64, bos: Bool, eos: Bool, flush: Bool) { + var buf = bytes + let byteCount = buf.count + buf.withUnsafeMutableBufferPointer { p in + var pkt = ogg_packet() + pkt.packet = p.baseAddress // libogg copies this immediately + pkt.bytes = byteCount + pkt.b_o_s = bos ? 1 : 0 + pkt.e_o_s = eos ? 1 : 0 + pkt.granulepos = granule + pkt.packetno = packetNo + ogg_stream_packetin(&stream, &pkt) + } + packetNo += 1 + drain(flush: flush) + } + + private func drain(flush: Bool) { + var page = ogg_page() + while true { + let r = flush ? ogg_stream_flush(&stream, &page) : ogg_stream_pageout(&stream, &page) + if r == 0 { break } + write(page) + } + } + + private func write(_ page: ogg_page) { + handle.write(Data(bytes: page.header!, count: page.header_len)) + handle.write(Data(bytes: page.body!, count: page.body_len)) + } + + private func appendLE32(_ arr: inout [UInt8], _ v: UInt32) { + arr.append(UInt8(v & 0xFF)); arr.append(UInt8((v >> 8) & 0xFF)) + arr.append(UInt8((v >> 16) & 0xFF)); arr.append(UInt8((v >> 24) & 0xFF)) + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusDecoder.swift b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusDecoder.swift new file mode 100644 index 0000000000..e53588a39c --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusDecoder.swift @@ -0,0 +1,214 @@ +import AVFoundation +import COgg +import COpus + +public enum OpusDecoderError: Swift.Error { + case fileNotFound + case notOggOpus + case decode(String) +} + +/// The decoded PCM buffer. +/// +/// `@unchecked Sendable` because `AVAudioPCMBuffer` is a reference type that +/// isn't Sendable in the SDK; we treat the buffer as immutable after construction +/// and never hand a `DecodedOpus` to a callee that mutates it. +public struct DecodedOpus: @unchecked Sendable { + public let pcm: AVAudioPCMBuffer + + public init(pcm: AVAudioPCMBuffer) { + self.pcm = pcm + } +} + +public enum OpusDecoder { + + public static func decodePCM(url: URL) throws -> DecodedOpus { + guard FileManager.default.fileExists(atPath: url.path) else { + throw OpusDecoderError.fileNotFound + } + let data: Data + do { + data = try Data(contentsOf: url, options: .alwaysMapped) + } catch { + throw OpusDecoderError.fileNotFound + } + guard data.count >= 4, data[0] == 0x4F, data[1] == 0x67, + data[2] == 0x67, data[3] == 0x53 else { + throw OpusDecoderError.notOggOpus + } + return try data.withUnsafeBytes { raw -> DecodedOpus in + guard let base = raw.baseAddress else { + throw OpusDecoderError.notOggOpus + } + return try decodeFromMappedBytes(base, count: raw.count) + } + } + + // MARK: - Implementation + + private static func decodeFromMappedBytes( + _ base: UnsafeRawPointer, + count: Int + ) throws -> DecodedOpus { + var sync = ogg_sync_state() + ogg_sync_init(&sync) + defer { ogg_sync_clear(&sync) } + + guard let dst = ogg_sync_buffer(&sync, count) else { + throw OpusDecoderError.decode("ogg_sync_buffer failed") + } + memcpy(dst, base, count) + ogg_sync_wrote(&sync, count) + + var stream = ogg_stream_state() + var streamInitialized = false + defer { if streamInitialized { ogg_stream_clear(&stream) } } + + var page = ogg_page() + var packet = ogg_packet() + + var decoder: OpaquePointer? = nil + defer { if let d = decoder { opus_decoder_destroy(d) } } + + var channelCount: Int32 = 0 + let sampleRate: Int32 = 48000 + var preSkip: Int32 = 0 + + // Output PCM accumulators (deinterleaved). Sized generously; will grow + // if needed. 30 s of voice at 48 kHz is ~1.44 M samples — fine in memory. + var leftSamples: [Float] = [] + var rightSamples: [Float] = [] + leftSamples.reserveCapacity(48_000 * 30) + rightSamples.reserveCapacity(48_000 * 30) + + // Per-packet decode buffer; sized for 120 ms × 48 kHz × stereo. + let maxFramesPerPacket = 5760 + var packetBuf = [Float](repeating: 0, count: maxFramesPerPacket * 2) + + var packetIndex = 0 // 0 = ID header, 1 = comment header, ≥2 = audio + + // libogg pageout: 1 = page extracted, 0 = need more data (never here — fully buffered), + // -1 = sync lost / hole. On sync loss, skip and continue per libogg convention rather + // than treating it as EOF (which would silently truncate a partially-corrupted file). + pageLoop: while true { + let pr = ogg_sync_pageout(&sync, &page) + if pr == 0 { break pageLoop } + if pr < 0 { continue pageLoop } + if !streamInitialized { + let serial = ogg_page_serialno(&page) + ogg_stream_init(&stream, serial) + streamInitialized = true + } + ogg_stream_pagein(&stream, &page) + + while ogg_stream_packetout(&stream, &packet) == 1 { + guard let pktPtr = packet.packet else { + throw OpusDecoderError.notOggOpus + } + let pktBytes = UnsafeBufferPointer( + start: pktPtr, count: Int(packet.bytes) + ) + + if packetIndex == 0 { + // OpusHead: "OpusHead" magic (8), version (1), channelCount (1), + // preSkip (2 LE), inputSampleRate (4 LE), outputGain (2), + // channelMappingFamily (1), … + guard pktBytes.count >= 19, + pktBytes[0] == 0x4F, pktBytes[1] == 0x70, + pktBytes[2] == 0x75, pktBytes[3] == 0x73, + pktBytes[4] == 0x48, pktBytes[5] == 0x65, + pktBytes[6] == 0x61, pktBytes[7] == 0x64 else { + throw OpusDecoderError.notOggOpus + } + channelCount = Int32(pktBytes[9]) + preSkip = Int32(pktBytes[10]) | (Int32(pktBytes[11]) << 8) + // The standard Opus decoder supports mono and stereo only; + // multichannel uses opus_multistream_decoder which we don't expose. + // The deinterleave branch below assumes channelCount ∈ {1, 2}. + guard channelCount == 1 || channelCount == 2 else { + throw OpusDecoderError.notOggOpus + } + + var err: Int32 = 0 + decoder = opus_decoder_create(sampleRate, channelCount, &err) + guard err == OPUS_OK, decoder != nil else { + throw OpusDecoderError.decode("opus_decoder_create err=\(err)") + } + packetIndex += 1 + continue + } + if packetIndex == 1 { + // OpusTags comment header — skipped. + packetIndex += 1 + continue + } + guard let d = decoder else { + throw OpusDecoderError.decode("no decoder") + } + let nframes = packetBuf.withUnsafeMutableBufferPointer { outBuf -> Int32 in + opus_decode_float( + d, + packet.packet, + Int32(packet.bytes), + outBuf.baseAddress!, + Int32(maxFramesPerPacket), + 0 + ) + } + if nframes < 0 { + throw OpusDecoderError.decode("opus_decode_float err=\(nframes)") + } + if channelCount == 1 { + for f in 0..= 2, channelCount > 0 else { + throw OpusDecoderError.notOggOpus + } + + // Drop pre-skip frames per Opus spec. + let skip = min(Int(preSkip), leftSamples.count) + leftSamples.removeFirst(skip) + if channelCount == 2 { + rightSamples.removeFirst(min(skip, rightSamples.count)) + } + + guard let format = AVAudioFormat( + commonFormat: .pcmFormatFloat32, + sampleRate: Double(sampleRate), + channels: AVAudioChannelCount(channelCount), + interleaved: false + ), + let pcm = AVAudioPCMBuffer( + pcmFormat: format, + frameCapacity: AVAudioFrameCount(leftSamples.count) + ) else { + throw OpusDecoderError.decode("AVAudioPCMBuffer alloc failed") + } + pcm.frameLength = AVAudioFrameCount(leftSamples.count) + guard let channels = pcm.floatChannelData else { + throw OpusDecoderError.decode("pcm.floatChannelData nil") + } + _ = leftSamples.withUnsafeBufferPointer { src in + memcpy(channels[0], src.baseAddress, leftSamples.count * MemoryLayout.size) + } + if channelCount == 2 { + _ = rightSamples.withUnsafeBufferPointer { src in + memcpy(channels[1], src.baseAddress, rightSamples.count * MemoryLayout.size) + } + } + + return DecodedOpus(pcm: pcm) + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusEncoder.swift b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusEncoder.swift new file mode 100644 index 0000000000..ea905b390f --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Sources/OpusKit/OpusEncoder.swift @@ -0,0 +1,54 @@ +import COpus +import COpusHelpers +import Foundation + +public enum OpusEncoderError: Swift.Error, Equatable { + case createFailed(Int32) + case wrongFrameSize(Int) + case encodeFailed(Int32) +} + +/// Encodes 48 kHz mono Float32 PCM to Opus packets — one packet per 20 ms frame +/// (960 samples). Configured for Telegram's voice-note profile: VOIP application, +/// 16 kbps VBR, complexity 8, voice signal bias. +/// +/// Not thread-safe: holds a stateful encoder handle + a reused packet buffer, so +/// `encode(_:)` must be called serially (the recording sink confines it to one queue). +public final class OpusEncoder { + public static let sampleRate: Int32 = 48_000 + public static let frameSize = 960 // 20 ms @ 48 kHz + private static let maxPacketBytes = 4_000 + + private let enc: OpaquePointer + private var packetBuf = [UInt8](repeating: 0, count: maxPacketBytes) + + public init(bitrate: Int32 = 16_000, complexity: Int32 = 8) throws { + var err: Int32 = 0 + guard let e = opus_encoder_create(Self.sampleRate, 1, OPUS_APPLICATION_VOIP, &err), + err == OPUS_OK else { + throw OpusEncoderError.createFailed(err) + } + enc = e + _ = copus_set_bitrate(enc, bitrate) + _ = copus_set_vbr(enc, 1) + _ = copus_set_complexity(enc, complexity) + _ = copus_set_signal(enc, OPUS_SIGNAL_VOICE) + } + + deinit { opus_encoder_destroy(enc) } + + /// Encodes exactly `frameSize` (960) Float32 samples. Returns the encoded packet. + public func encode(_ frame: [Float]) throws -> Data { + guard frame.count == Self.frameSize else { + throw OpusEncoderError.wrongFrameSize(frame.count) + } + let n = frame.withUnsafeBufferPointer { src in + packetBuf.withUnsafeMutableBufferPointer { dst in + opus_encode_float(enc, src.baseAddress!, Int32(Self.frameSize), + dst.baseAddress!, Int32(Self.maxPacketBytes)) + } + } + if n < 0 { throw OpusEncoderError.encodeFailed(n) } + return Data(packetBuf[0.. URL { + guard let url = Bundle.module.url(forResource: name, withExtension: ext) else { + throw XCTSkip("fixture not bundled: \(name).\(ext)") + } + return url + } + + func testDecodesBundledVoiceNote() throws { + let url = try fixtureURL("voice_note", ext: "ogg") + let decoded = try OpusDecoder.decodePCM(url: url) + XCTAssertEqual(decoded.pcm.format.sampleRate, 48000) + XCTAssertGreaterThan(decoded.pcm.format.channelCount, 0) + XCTAssertGreaterThan(decoded.pcm.frameLength, 0) + // 18 s fixture at 48 kHz → ~864000 frames; allow ±5%. + let expected: AVAudioFrameCount = 864_000 + let lower = AVAudioFrameCount(Double(expected) * 0.95) + let upper = AVAudioFrameCount(Double(expected) * 1.05) + XCTAssertGreaterThanOrEqual(decoded.pcm.frameLength, lower, + "got frameLength=\(decoded.pcm.frameLength), expected ~\(expected)") + XCTAssertLessThanOrEqual(decoded.pcm.frameLength, upper, + "got frameLength=\(decoded.pcm.frameLength), expected ~\(expected)") + } + + func testRejectsMissingFile() { + let bogus = URL(fileURLWithPath: "/nonexistent/path/voice.ogg") + XCTAssertThrowsError(try OpusDecoder.decodePCM(url: bogus)) { err in + guard case OpusDecoderError.fileNotFound = err else { + XCTFail("expected fileNotFound, got \(err)"); return + } + } + } + + func testRejectsNonOggFile() throws { + let tmp = FileManager.default.temporaryDirectory.appendingPathComponent("not-ogg.bin") + try Data([0x89, 0x50, 0x4E, 0x47]).write(to: tmp) + defer { try? FileManager.default.removeItem(at: tmp) } + XCTAssertThrowsError(try OpusDecoder.decodePCM(url: tmp)) { err in + guard case OpusDecoderError.notOggOpus = err else { + XCTFail("expected notOggOpus, got \(err)"); return + } + } + } + + func testRejectsEmptyFile() throws { + let tmp = FileManager.default.temporaryDirectory.appendingPathComponent("empty.ogg") + try Data().write(to: tmp) + defer { try? FileManager.default.removeItem(at: tmp) } + XCTAssertThrowsError(try OpusDecoder.decodePCM(url: tmp)) { err in + guard case OpusDecoderError.notOggOpus = err else { + XCTFail("expected notOggOpus, got \(err)"); return + } + } + } +} diff --git a/Telegram/WatchApp/Packages/OpusKit/Tests/OpusKitTests/OpusEncoderTests.swift b/Telegram/WatchApp/Packages/OpusKit/Tests/OpusKitTests/OpusEncoderTests.swift new file mode 100644 index 0000000000..14e05ddcc9 --- /dev/null +++ b/Telegram/WatchApp/Packages/OpusKit/Tests/OpusKitTests/OpusEncoderTests.swift @@ -0,0 +1,75 @@ +import XCTest +import AVFoundation +@testable import OpusKit + +final class OpusEncoderTests: XCTestCase { + + /// Generates `seconds` of a mono 48 kHz sine at `freq` Hz, amplitude 0.5. + private func sine(seconds: Double, freq: Double = 440, rate: Double = 48000) -> [Float] { + let n = Int(seconds * rate) + return (0.. + +#include "rlottie.h" + +#include +#include + +@interface LottieInstance () { + std::unique_ptr _animation; +} +@end + +@implementation LottieInstance + +- (instancetype _Nullable)initWithData:(NSData *)data cacheKey:(NSString *)cacheKey { + self = [super init]; + if (self == nil) { return nil; } + + std::string jsonStr(reinterpret_cast(data.bytes), data.length); + std::string keyStr(cacheKey.UTF8String ?: ""); + + // Empty cache key disables rlottie's per-path cache; opt in only when caller + // provides a real key. + _animation = rlottie::Animation::loadFromData( + jsonStr, + keyStr, + /* resourcePath */ "", + /* cachePolicy */ keyStr.length() != 0 + ); + + if (_animation == nullptr) { + return nil; + } + + int32_t frameCount = static_cast(_animation->totalFrame()); + int32_t frameRate = static_cast(_animation->frameRate()); + if (frameCount < 1) { frameCount = 1; } + if (frameRate < 1) { frameRate = 1; } + if (frameRate > 360) { return nil; } + if (_animation->duration() > 9.0) { return nil; } + + size_t width = 0, height = 0; + _animation->size(width, height); + if (width == 0) { width = 1; } + if (height == 0) { height = 1; } + if (width > 1536 || height > 1536) { return nil; } + + _frameCount = frameCount; + _frameRate = frameRate; + _dimensions = CGSizeMake((CGFloat)width, (CGFloat)height); + return self; +} + +- (void)renderFrameWithIndex:(int32_t)index + into:(uint8_t *)buffer + width:(int32_t)width + height:(int32_t)height + bytesPerRow:(int32_t)bytesPerRow { + if (width <= 0 || height <= 0 || bytesPerRow < width * 4) { + return; + } + + rlottie::Surface surface( + reinterpret_cast(buffer), + static_cast(width), + static_cast(height), + static_cast(bytesPerRow) + ); + _animation->renderSync(static_cast(index), surface); +} + +@end diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/include/RLottieBinding/LottieInstance.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/include/RLottieBinding/LottieInstance.h new file mode 100644 index 0000000000..68fa328481 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/include/RLottieBinding/LottieInstance.h @@ -0,0 +1,32 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// Thin wrapper around `rlottie::Animation`. Holds one parsed lottie animation in memory. +/// rlottie's internal cache (keyed by `cacheKey`) keeps parsed paths warm across frames. +/// +/// Defensive caps mirroring telegram-iOS: rejects animations with width/height > 1536px, +/// frameRate > 360, or duration > 9 seconds — these are typically malformed/abusive inputs. +@interface LottieInstance : NSObject + +@property (nonatomic, readonly) int32_t frameCount; +@property (nonatomic, readonly) int32_t frameRate; +@property (nonatomic, readonly) CGSize dimensions; + +/// Initializes from inflated lottie JSON bytes (TGS files are gzipped; gunzip first). +/// `cacheKey` is used by rlottie to share parsed paths across frames; pass a unique +/// stable string (e.g. the file's last path component). +- (instancetype _Nullable)initWithData:(NSData *)data cacheKey:(NSString *)cacheKey; + +/// Renders `index` into `buffer`, which must be at least `bytesPerRow * height` bytes. +/// Pixel format is BGRA-premultiplied (rlottie::Surface's native layout). +- (void)renderFrameWithIndex:(int32_t)index + into:(uint8_t *)buffer + width:(int32_t)width + height:(int32_t)height + bytesPerRow:(int32_t)bytesPerRow; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING new file mode 100644 index 0000000000..e0825e7f80 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING @@ -0,0 +1,17 @@ +Licensing + +rlottie basically comes with MIT license(licenses/COPYING.MIT) +but some parts of shared code are covered by different licenses. Listed +below are the folder names and the license file covering it. Note that this +license would cover all of the source invovled in each folders, unless +specifically noted otherwise. So please refer to these imported project sources +for details. + +Dependencies and licenses: + +src/vector/ licenses/COPYING.MIT, COPYING.SKIA +src/vector/freetype licenses/COPYING.FTL +src/vector/pixman licenses/COPYING.PIX +src/vector/stb licenses/COPYING.STB +src/lottie/rapidjson licenses/COPYING.RPD + diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING.MIT b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING.MIT new file mode 100644 index 0000000000..8becbac1df --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/COPYING.MIT @@ -0,0 +1,7 @@ +Copyright 2020 (see AUTHORS) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie.h new file mode 100644 index 0000000000..12c413bf68 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie.h @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _RLOTTIE_H_ +#define _RLOTTIE_H_ + +#include +#include +#include + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __declspec(dllexport) + #else + #define RLOTTIE_API __declspec(dllimport) + #endif +#else + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __attribute__ ((visibility ("default"))) + #else + #define RLOTTIE_API + #endif +#endif + +class AnimationImpl; +struct LOTNode; +struct LOTLayerNode; + +namespace rlottie { + +/** + * @brief Configures rlottie model cache policy. + * + * Provides Library level control to configure model cache + * policy. Setting it to 0 will disable + * the cache as well as flush all the previously cached content. + * + * @param[in] cacheSize Maximum Model Cache size. + * + * @note to disable Caching configure with 0 size. + * @note to flush the current Cache content configure it with 0 and + * then reconfigure with the new size. + * + * @internal + */ +RLOTTIE_API void configureModelCacheSize(size_t cacheSize); + +struct Color { + Color() = default; + Color(float r, float g , float b):_r(r), _g(g), _b(b){} + float r() const {return _r;} + float g() const {return _g;} + float b() const {return _b;} +private: + float _r{0}; + float _g{0}; + float _b{0}; +}; + +struct Size { + Size() = default; + Size(float w, float h):_w(w), _h(h){} + float w() const {return _w;} + float h() const {return _h;} +private: + float _w{0}; + float _h{0}; +}; + +struct Point { + Point() = default; + Point(float x, float y):_x(x), _y(y){} + float x() const {return _x;} + float y() const {return _y;} +private: + float _x{0}; + float _y{0}; +}; + +struct FrameInfo { + explicit FrameInfo(uint32_t frame): _frameNo(frame){} + uint32_t curFrame() const {return _frameNo;} +private: + uint32_t _frameNo; +}; + +enum class Property { + FillColor, /*!< Color property of Fill object , value type is rlottie::Color */ + FillOpacity, /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */ + StrokeColor, /*!< Color property of Stroke object , value type is rlottie::Color */ + StrokeOpacity, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */ + StrokeWidth, /*!< stroke with property of Stroke object , value type is float */ + TrAnchor, /*!< Transform Anchor property of Layer and Group object , value type is rlottie::Point */ + TrPosition, /*!< Transform Position property of Layer and Group object , value type is rlottie::Point */ + TrScale, /*!< Transform Scale property of Layer and Group object , value type is rlottie::Size. range[0 ..100] */ + TrRotation, /*!< Transform Scale property of Layer and Group object , value type is float. range[0 .. 360] in degrees*/ + TrOpacity /*!< Transform Opacity property of Layer and Group object , value type is float [ 0 .. 100] */ +}; + +struct Color_Type{}; +struct Point_Type{}; +struct Size_Type{}; +struct Float_Type{}; +template struct MapType; + +class RLOTTIE_API Surface { +public: + /** + * @brief Surface object constructor. + * + * @param[in] buffer surface buffer. + * @param[in] width surface width. + * @param[in] height surface height. + * @param[in] bytesPerLine number of bytes in a surface scanline. + * + * @note Default surface format is ARGB32_Premultiplied. + * + * @internal + */ + Surface(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine); + + /** + * @brief Sets the Draw Area available on the Surface. + * + * Lottie will use the draw region size to generate frame image + * and will update only the draw rgion of the surface. + * + * @param[in] x region area x position. + * @param[in] y region area y position. + * @param[in] width region area width. + * @param[in] height region area height. + * + * @note Default surface format is ARGB32_Premultiplied. + * @note Default draw region area is [ 0 , 0, surface width , surface height] + * + * @internal + */ + void setDrawRegion(size_t x, size_t y, size_t width, size_t height); + + /** + * @brief Returns width of the surface. + * + * @return surface width + * + * @internal + * + */ + size_t width() const {return mWidth;} + + /** + * @brief Returns height of the surface. + * + * @return surface height + * + * @internal + */ + size_t height() const {return mHeight;} + + /** + * @brief Returns number of bytes in the surface scanline. + * + * @return number of bytes in scanline. + * + * @internal + */ + size_t bytesPerLine() const {return mBytesPerLine;} + + /** + * @brief Returns buffer attached tp the surface. + * + * @return buffer attaced to the Surface. + * + * @internal + */ + uint32_t *buffer() const {return mBuffer;} + + /** + * @brief Returns drawable area width of the surface. + * + * @return drawable area width + * + * @note Default value is width() of the surface + * + * @internal + * + */ + size_t drawRegionWidth() const {return mDrawArea.w;} + + /** + * @brief Returns drawable area height of the surface. + * + * @return drawable area height + * + * @note Default value is height() of the surface + * + * @internal + */ + size_t drawRegionHeight() const {return mDrawArea.h;} + + /** + * @brief Returns drawable area's x position of the surface. + * + * @return drawable area's x potition. + * + * @note Default value is 0 + * + * @internal + */ + size_t drawRegionPosX() const {return mDrawArea.x;} + + /** + * @brief Returns drawable area's y position of the surface. + * + * @return drawable area's y potition. + * + * @note Default value is 0 + * + * @internal + */ + size_t drawRegionPosY() const {return mDrawArea.y;} + + /** + * @brief Default constructor. + */ + Surface() = default; +private: + uint32_t *mBuffer{nullptr}; + size_t mWidth{0}; + size_t mHeight{0}; + size_t mBytesPerLine{0}; + struct { + size_t x{0}; + size_t y{0}; + size_t w{0}; + size_t h{0}; + }mDrawArea; +}; + +using MarkerList = std::vector>; +/** + * @brief https://helpx.adobe.com/after-effects/using/layer-markers-composition-markers.html + * Markers exported form AE are used to describe a segmnet of an animation {comment/tag , startFrame, endFrame} + * Marker can be use to devide a resource in to separate animations by tagging the segment with comment string , + * start frame and duration of that segment. + */ + +using LayerInfoList = std::vector>; + + +using ColorFilter = std::function; + +class RLOTTIE_API Animation { +public: + + /** + * @brief Constructs an animation object from file path. + * + * @param[in] path Lottie resource file path + * @param[in] cachePolicy whether to cache or not the model data. + * use only when need to explicit disabl caching for a + * particular resource. To disable caching at library level + * use @see configureModelCacheSize() instead. + * + * @return Animation object that can render the contents of the + * Lottie resource represented by file path. + * + * @internal + */ + static std::unique_ptr + loadFromFile(const std::string &path, bool cachePolicy=true); + + /** + * @brief Constructs an animation object from JSON string data. + * + * @param[in] jsonData The JSON string data. + * @param[in] key the string that will be used to cache the JSON string data. + * @param[in] resourcePath the path will be used to search for external resource. + * @param[in] cachePolicy whether to cache or not the model data. + * use only when need to explicit disabl caching for a + * particular resource. To disable caching at library level + * use @see configureModelCacheSize() instead. + * + * @return Animation object that can render the contents of the + * Lottie resource represented by JSON string data. + * + * @internal + */ + static std::unique_ptr + loadFromData(std::string jsonData, const std::string &key, + const std::string &resourcePath="", bool cachePolicy=true); + + /** + * @brief Constructs an animation object from JSON string data and update. + * the color properties using ColorFilter. + + * @param[in] jsonData The JSON string data. + * @param[in] resourcePath the path will be used to search for external resource. + * @param[in] filter The color filter that will be applied for each color property + * found during parsing. + + * @return Animation object that can render the contents of the + * Lottie resource represented by JSON string data. + * + * @internal + */ + static std::unique_ptr + loadFromData(std::string jsonData, std::string resourcePath, ColorFilter filter); + + /** + * @brief Returns default framerate of the Lottie resource. + * + * @return framerate of the Lottie resource + * + * @internal + * + */ + double frameRate() const; + + /** + * @brief Returns total number of frames present in the Lottie resource. + * + * @return frame count of the Lottie resource. + * + * @note frame number starts with 0. + * + * @internal + */ + size_t totalFrame() const; + + /** + * @brief Returns default viewport size of the Lottie resource. + * + * @param[out] width default width of the viewport. + * @param[out] height default height of the viewport. + * + * @internal + * + */ + void size(size_t &width, size_t &height) const; + + /** + * @brief Returns total animation duration of Lottie resource in second. + * it uses totalFrame() and frameRate() to calculate the duration. + * duration = totalFrame() / frameRate(). + * + * @return total animation duration in second. + * @retval 0 if the Lottie resource has no animation. + * + * @see totalFrame() + * @see frameRate() + * + * @internal + */ + double duration() const; + + /** + * @brief Returns frame number for a given position. + * this function helps to map the position value retuned + * by the animator to a frame number in side the Lottie resource. + * frame_number = lerp(start_frame, endframe, pos); + * + * @param[in] pos normalized position value [0 ... 1] + * + * @return frame numer maps to the position value [startFrame .... endFrame] + * + * @internal + */ + size_t frameAtPos(double pos); + + /** + * @brief Renders the content to surface Asynchronously. + * it gives a future in return to get the result of the + * rendering at a future point. + * To get best performance user has to start rendering as soon as + * it finds that content at {frameNo} has to be rendered and get the + * result from the future at the last moment when the surface is needed + * to draw into the screen. + * + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn + * @param[in] surface Surface in which content will be drawn + * @param[in] keepAspectRatio whether to keep the aspect ratio while scaling the content. + * + * @return future that will hold the result when rendering finished. + * + * for Synchronus rendering @see renderSync + * + * @see Surface + * @internal + */ + std::future render(size_t frameNo, Surface surface, bool keepAspectRatio=true); + + /** + * @brief Renders the content to surface synchronously. + * for performance use the async rendering @see render + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn + * @param[in] surface Surface in which content will be drawn + * @param[in] keepAspectRatio whether to keep the aspect ratio while scaling the content. + * + * @internal + */ + void renderSync(size_t frameNo, Surface surface, bool keepAspectRatio=true); + + /** + * @brief Returns root layer of the composition updated with + * content of the Lottie resource at frame number @p frameNo. + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be extracted. + * @param[in] width content viewbox width + * @param[in] height content viewbox height + * + * @return Root layer node. + * + * @internal + */ + const LOTLayerNode * renderTree(size_t frameNo, size_t width, size_t height) const; + + /** + * @brief Returns Composition Markers. + * + * + * @return returns MarkerList of the Composition. + * + * @see MarkerList + * @internal + */ + const MarkerList& markers() const; + + /** + * @brief Returns Layer information{name, inFrame, outFrame} of all the child layers of the composition. + * + * + * @return List of Layer Information of the Composition. + * + * @see LayerInfoList + * @internal + */ + const LayerInfoList& layers() const; + + /** + * @brief Sets property value for the specified {@link KeyPath}. This {@link KeyPath} can resolve + * to multiple contents. In that case, the callback's value will apply to all of them. + * + * Keypath should conatin object names separated by (.) and can handle globe(**) or wildchar(*). + * + * @usage + * To change fillcolor property of fill1 object in the layer1->group1->fill1 hirarchy to RED color + * + * player->setValue("layer1.group1.fill1", rlottie::Color(1, 0, 0); + * + * if all the color property inside group1 needs to be changed to GREEN color + * + * player->setValue("**.group1.**", rlottie::Color(0, 1, 0); + * + * @internal + */ + template + void setValue(const std::string &keypath, AnyValue value) + { + setValue(MapType>{}, prop, keypath, value); + } + + /** + * @brief default destructor + * + * @internal + */ + ~Animation(); + +private: + void setValue(Color_Type, Property, const std::string &, Color); + void setValue(Float_Type, Property, const std::string &, float); + void setValue(Size_Type, Property, const std::string &, Size); + void setValue(Point_Type, Property, const std::string &, Point); + + void setValue(Color_Type, Property, const std::string &, std::function &&); + void setValue(Float_Type, Property, const std::string &, std::function &&); + void setValue(Size_Type, Property, const std::string &, std::function &&); + void setValue(Point_Type, Property, const std::string &, std::function &&); + /** + * @brief default constructor + * + * @internal + */ + Animation(); + + std::unique_ptr d; +}; + +//Map Property to Value type +template<> struct MapType>: Color_Type{}; +template<> struct MapType>: Color_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Float_Type{}; +template<> struct MapType>: Point_Type{}; +template<> struct MapType>: Point_Type{}; +template<> struct MapType>: Size_Type{}; + + +} // namespace lotplayer + +#endif // _RLOTTIE_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie_capi.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie_capi.h new file mode 100644 index 0000000000..9bdf336f82 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottie_capi.h @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _RLOTTIE_CAPI_H_ +#define _RLOTTIE_CAPI_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + LOTTIE_ANIMATION_PROPERTY_FILLCOLOR, /*!< Color property of Fill object , value type is float [0 ... 1] */ + LOTTIE_ANIMATION_PROPERTY_FILLOPACITY, /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */ + LOTTIE_ANIMATION_PROPERTY_STROKECOLOR, /*!< Color property of Stroke object , value type is float [0 ... 1] */ + LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */ + LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH, /*!< stroke with property of Stroke object , value type is float */ + LOTTIE_ANIMATION_PROPERTY_TR_ANCHOR, /*!< Transform Anchor property of Layer and Group object , value type is int */ + LOTTIE_ANIMATION_PROPERTY_TR_POSITION, /*!< Transform Position property of Layer and Group object , value type is int */ + LOTTIE_ANIMATION_PROPERTY_TR_SCALE, /*!< Transform Scale property of Layer and Group object , value type is float range[0 ..100] */ + LOTTIE_ANIMATION_PROPERTY_TR_ROTATION, /*!< Transform Scale property of Layer and Group object , value type is float. range[0 .. 360] in degrees*/ + LOTTIE_ANIMATION_PROPERTY_TR_OPACITY /*!< Transform Opacity property of Layer and Group object , value type is float [ 0 .. 100] */ +}Lottie_Animation_Property; + +typedef struct Lottie_Animation_S Lottie_Animation; + +/** + * @brief Constructs an animation object from file path. + * + * @param[in] path Lottie resource file path + * + * @return Animation object that can build the contents of the + * Lottie resource represented by file path. + * + * @see lottie_animation_destroy() + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API Lottie_Animation *lottie_animation_from_file(const char *path); + +/** + * @brief Constructs an animation object from JSON string data. + * + * @param[in] data The JSON string data. + * @param[in] key the string that will be used to cache the JSON string data. + * @param[in] resource_path the path that will be used to load external resource needed by the JSON data. + * + * @return Animation object that can build the contents of the + * Lottie resource represented by JSON string data. + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path); + +/** + * @brief Free given Animation object resource. + * + * @param[in] animation Animation object to free. + * + * @see lottie_animation_from_file() + * @see lottie_animation_from_data() + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API void lottie_animation_destroy(Lottie_Animation *animation); + +/** + * @brief Returns default viewport size of the Lottie resource. + * + * @param[in] animation Animation object. + * @param[out] w default width of the viewport. + * @param[out] h default height of the viewport. + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API void lottie_animation_get_size(const Lottie_Animation *animation, size_t *width, size_t *height); + +/** + * @brief Returns total animation duration of Lottie resource in second. + * it uses totalFrame() and frameRate() to calculate the duration. + * duration = totalFrame() / frameRate(). + * + * @param[in] animation Animation object. + * + * @return total animation duration in second. + * @c 0 if the Lottie resource has no animation. + * + * @see lottie_animation_get_totalframe() + * @see lottie_animation_get_framerate() + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API double lottie_animation_get_duration(const Lottie_Animation *animation); + +/** + * @brief Returns total number of frames present in the Lottie resource. + * + * @param[in] animation Animation object. + * + * @return frame count of the Lottie resource.* + * + * @note frame number starts with 0. + * + * @see lottie_animation_get_duration() + * @see lottie_animation_get_framerate() + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API size_t lottie_animation_get_totalframe(const Lottie_Animation *animation); + +/** + * @brief Returns default framerate of the Lottie resource. + * + * @param[in] animation Animation object. + * + * @return framerate of the Lottie resource + * + * @ingroup Lottie_Animation + * @internal + * + */ +RLOTTIE_API double lottie_animation_get_framerate(const Lottie_Animation *animation); + +/** + * @brief Get the render tree which contains the snapshot of the animation object + * at frame = @c frame_num, the content of the animation in that frame number. + * + * @param[in] animation Animation object. + * @param[in] frame_num Content corresponds to the @p frame_num needs to be drawn + * @param[in] width requested snapshot viewport width. + * @param[in] height requested snapshot viewport height. + * + * @return Animation snapshot tree. + * + * @note: User has to traverse the tree for rendering. + * + * @see LOTLayerNode + * @see LOTNode + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API const LOTLayerNode *lottie_animation_render_tree(Lottie_Animation *animation, size_t frame_num, size_t width, size_t height); + +/** + * @brief Maps position to frame number and returns it. + * + * @param[in] animation Animation object. + * @param[in] pos position in the range [ 0.0 .. 1.0 ]. + * + * @return mapped frame numbe in the range [ start_frame .. end_frame ]. + * @c 0 if the Lottie resource has no animation. + * + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API size_t lottie_animation_get_frame_at_pos(const Lottie_Animation *animation, float pos); + +/** + * @brief Request to render the content of the frame @p frame_num to buffer @p buffer. + * + * @param[in] animation Animation object. + * @param[in] frame_num the frame number needs to be rendered. + * @param[in] buffer surface buffer use for rendering. + * @param[in] width width of the surface + * @param[in] height height of the surface + * @param[in] bytes_per_line stride of the surface in bytes. + * + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API void lottie_animation_render(Lottie_Animation *animation, size_t frame_num, uint32_t *buffer, size_t width, size_t height, size_t bytes_per_line); + +/** + * @brief Request to render the content of the frame @p frame_num to buffer @p buffer asynchronously. + * + * @param[in] animation Animation object. + * @param[in] frame_num the frame number needs to be rendered. + * @param[in] buffer surface buffer use for rendering. + * @param[in] width width of the surface + * @param[in] height height of the surface + * @param[in] bytes_per_line stride of the surface in bytes. + * + * @note user must call lottie_animation_render_flush() to make sure render is finished. + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API void lottie_animation_render_async(Lottie_Animation *animation, size_t frame_num, uint32_t *buffer, size_t width, size_t height, size_t bytes_per_line); + +/** + * @brief Request to finish the current async renderer job for this animation object. + * If render is finished then this call returns immidiately. + * If not, it waits till render job finish and then return. + * + * @param[in] animation Animation object. + * + * @warning User must call lottie_animation_render_async() and lottie_animation_render_flush() + * in pair to get the benefit of async rendering. + * + * @return the pixel buffer it finished rendering. + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API uint32_t *lottie_animation_render_flush(Lottie_Animation *animation); + + +/** + * @brief Request to change the properties of this animation object. + * Keypath should conatin object names separated by (.) and can handle globe(**) or wildchar(*) + * + * @usage + * To change fillcolor property of fill1 object in the layer1->group1->fill1 hirarchy to RED color + * + * lottie_animation_property_override(animation, LOTTIE_ANIMATION_PROPERTY_FILLCOLOR, "layer1.group1.fill1", 1.0, 0.0, 0.0); + * + * if all the color property inside group1 needs to be changed to GREEN color + * + * lottie_animation_property_override(animation, LOTTIE_ANIMATION_PROPERTY_FILLCOLOR, "**.group1.**", 1.0, 0.0, 0.0); + * + * @param[in] animation Animation object. + * @param[in] type Property type. (@p Lottie_Animation_Property) + * @param[in] keypath Specific content of target. + * @param[in] ... Property values. + * + * @ingroup Lottie_Animation + * @internal + * */ +RLOTTIE_API void lottie_animation_property_override(Lottie_Animation *animation, const Lottie_Animation_Property type, const char *keypath, ...); + + +/** + * @brief Returns list of markers in the Lottie resource + * @p LOTMarkerList has a @p LOTMarker list and size of list + * @p LOTMarker has the marker's name, start frame, and end frame. + * + * @param[in] animation Animation object. + * + * @return The list of marker. If there is no marker, return null. + * + * @ingroup Lottie_Animation + * @internal + * */ +RLOTTIE_API const LOTMarkerList* lottie_animation_get_markerlist(Lottie_Animation *animation); + +#ifdef __cplusplus +} +#endif + +#endif //_RLOTTIE_CAPI_H_ + diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottiecommon.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottiecommon.h new file mode 100644 index 0000000000..784fbe2803 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/inc/rlottiecommon.h @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _RLOTTIE_COMMON_H_ +#define _RLOTTIE_COMMON_H_ + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __declspec(dllexport) + #else + #define RLOTTIE_API __declspec(dllimport) + #endif +#else + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __attribute__ ((visibility ("default"))) + #else + #define RLOTTIE_API + #endif +#endif + + +/** + * @defgroup Lottie_Animation Lottie_Animation + * + * Lottie Animation is a modern style vector based animation design. Its animation + * resource(within json format) could be generated by Adobe After Effect using + * bodymovin plugin. You can find a good examples in Lottie Community which + * shares many free resources(see: www.lottiefiles.com). + * + * This Lottie_Animation is a common engine to manipulate, control Lottie + * Animation from the Lottie resource - json file. It provides a scene-graph + * node tree per frames by user demand as well as rasterized frame images. + * + */ + +/** + * @ingroup Lottie_Animation + */ + +typedef enum +{ + BrushSolid = 0, + BrushGradient +} LOTBrushType; + +typedef enum +{ + FillEvenOdd = 0, + FillWinding +} LOTFillRule; + +typedef enum +{ + JoinMiter = 0, + JoinBevel, + JoinRound +} LOTJoinStyle; + +typedef enum +{ + CapFlat = 0, + CapSquare, + CapRound +} LOTCapStyle; + +typedef enum +{ + GradientLinear = 0, + GradientRadial +} LOTGradientType; + +typedef struct LOTGradientStop +{ + float pos; + unsigned char r, g, b, a; +} LOTGradientStop; + +typedef enum +{ + MaskAdd = 0, + MaskSubstract, + MaskIntersect, + MaskDifference +} LOTMaskType; + +typedef struct LOTMask { + struct { + const float *ptPtr; + size_t ptCount; + const char* elmPtr; + size_t elmCount; + } mPath; + LOTMaskType mMode; + unsigned char mAlpha; +}LOTMask; + +typedef enum +{ + MatteNone = 0, + MatteAlpha, + MatteAlphaInv, + MatteLuma, + MatteLumaInv +} LOTMatteType; + +typedef struct LOTMarker { + char *name; + size_t startframe; + size_t endframe; +} LOTMarker; + +typedef struct LOTMarkerList { + LOTMarker *ptr; + size_t size; +} LOTMarkerList; + +typedef struct LOTNode { + +#define ChangeFlagNone 0x0000 +#define ChangeFlagPath 0x0001 +#define ChangeFlagPaint 0x0010 +#define ChangeFlagAll (ChangeFlagPath & ChangeFlagPaint) + + struct { + const float *ptPtr; + size_t ptCount; + const char *elmPtr; + size_t elmCount; + } mPath; + + struct { + unsigned char r, g, b, a; + } mColor; + + struct { + unsigned char enable; + float width; + LOTCapStyle cap; + LOTJoinStyle join; + float miterLimit; + float *dashArray; + int dashArraySize; + } mStroke; + + struct { + LOTGradientType type; + LOTGradientStop *stopPtr; + size_t stopCount; + struct { + float x, y; + } start, end, center, focal; + float cradius; + float fradius; + } mGradient; + + struct { + unsigned char *data; + size_t width; + size_t height; + unsigned char mAlpha; + struct { + float m11; float m12; float m13; + float m21; float m22; float m23; + float m31; float m32; float m33; + } mMatrix; + } mImageInfo; + + int mFlag; + LOTBrushType mBrushType; + LOTFillRule mFillRule; + + const char *keypath; +} LOTNode; + + + +typedef struct LOTLayerNode { + + struct { + LOTMask *ptr; + size_t size; + } mMaskList; + + struct { + const float *ptPtr; + size_t ptCount; + const char *elmPtr; + size_t elmCount; + } mClipPath; + + struct { + struct LOTLayerNode **ptr; + size_t size; + } mLayerList; + + struct { + LOTNode **ptr; + size_t size; + } mNodeList; + + LOTMatteType mMatte; + int mVisible; + unsigned char mAlpha; + const char *keypath; + +} LOTLayerNode; + +/** + * @} + */ + +#endif // _RLOTTIE_COMMON_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/binding/c/lottieanimation_capi.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/binding/c/lottieanimation_capi.cpp new file mode 100644 index 0000000000..2434b5faac --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/binding/c/lottieanimation_capi.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "rlottie.h" +#include "rlottie_capi.h" +#include "vdebug.h" + +using namespace rlottie; + +extern "C" { +#include +#include + +struct Lottie_Animation_S +{ + std::unique_ptr mAnimation; + std::future mRenderTask; + uint32_t *mBufferRef; + LOTMarkerList *mMarkerList; +}; + +RLOTTIE_API Lottie_Animation_S *lottie_animation_from_file(const char *path) +{ + if (auto animation = Animation::loadFromFile(path) ) { + Lottie_Animation_S *handle = new Lottie_Animation_S(); + handle->mAnimation = std::move(animation); + return handle; + } else { + return nullptr; + } +} + +RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, const char *key, const char *resourcePath) +{ + if (auto animation = Animation::loadFromData(data, key, resourcePath) ) { + Lottie_Animation_S *handle = new Lottie_Animation_S(); + handle->mAnimation = std::move(animation); + return handle; + } else { + return nullptr; + } +} + +RLOTTIE_API void lottie_animation_destroy(Lottie_Animation_S *animation) +{ + if (animation) { + if (animation->mMarkerList) { + for(size_t i = 0; i < animation->mMarkerList->size; i++) { + if (animation->mMarkerList->ptr[i].name) free(animation->mMarkerList->ptr[i].name); + } + delete[] animation->mMarkerList->ptr; + delete animation->mMarkerList; + } + + if (animation->mRenderTask.valid()) { + animation->mRenderTask.get(); + } + animation->mAnimation = nullptr; + delete animation; + } +} + +RLOTTIE_API void lottie_animation_get_size(const Lottie_Animation_S *animation, size_t *width, size_t *height) +{ + if (!animation) return; + + animation->mAnimation->size(*width, *height); +} + +RLOTTIE_API double lottie_animation_get_duration(const Lottie_Animation_S *animation) +{ + if (!animation) return 0; + + return animation->mAnimation->duration(); +} + +RLOTTIE_API size_t lottie_animation_get_totalframe(const Lottie_Animation_S *animation) +{ + if (!animation) return 0; + + return animation->mAnimation->totalFrame(); +} + + +RLOTTIE_API double lottie_animation_get_framerate(const Lottie_Animation_S *animation) +{ + if (!animation) return 0; + + return animation->mAnimation->frameRate(); +} + +RLOTTIE_API const LOTLayerNode * lottie_animation_render_tree(Lottie_Animation_S *animation, size_t frame_num, size_t width, size_t height) +{ + if (!animation) return nullptr; + + return animation->mAnimation->renderTree(frame_num, width, height); +} + +RLOTTIE_API size_t +lottie_animation_get_frame_at_pos(const Lottie_Animation_S *animation, float pos) +{ + if (!animation) return 0; + + return animation->mAnimation->frameAtPos(pos); +} + +RLOTTIE_API void +lottie_animation_render(Lottie_Animation_S *animation, + size_t frame_number, + uint32_t *buffer, + size_t width, + size_t height, + size_t bytes_per_line) +{ + if (!animation) return; + + rlottie::Surface surface(buffer, width, height, bytes_per_line); + animation->mAnimation->renderSync(frame_number, surface); +} + +RLOTTIE_API void +lottie_animation_render_async(Lottie_Animation_S *animation, + size_t frame_number, + uint32_t *buffer, + size_t width, + size_t height, + size_t bytes_per_line) +{ + if (!animation) return; + + rlottie::Surface surface(buffer, width, height, bytes_per_line); + animation->mRenderTask = animation->mAnimation->render(frame_number, surface); + animation->mBufferRef = buffer; +} + +RLOTTIE_API uint32_t * +lottie_animation_render_flush(Lottie_Animation_S *animation) +{ + if (!animation) return nullptr; + + if (animation->mRenderTask.valid()) { + animation->mRenderTask.get(); + } + + return animation->mBufferRef; +} + +RLOTTIE_API void +lottie_animation_property_override(Lottie_Animation_S *animation, + const Lottie_Animation_Property type, + const char *keypath, + ...) +{ + va_list prop; + va_start(prop, keypath); + const int arg_count = [type](){ + switch (type) { + case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR: + case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR: + return 3; + case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY: + case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY: + case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH: + case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION: + return 1; + case LOTTIE_ANIMATION_PROPERTY_TR_POSITION: + case LOTTIE_ANIMATION_PROPERTY_TR_SCALE: + return 2; + default: + return 0; + } + }(); + double v[3] = {0}; + for (int i = 0; i < arg_count ; i++) { + v[i] = va_arg(prop, double); + } + va_end(prop); + + switch(type) { + case LOTTIE_ANIMATION_PROPERTY_FILLCOLOR: { + double r = v[0]; + double g = v[1]; + double b = v[2]; + if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break; + animation->mAnimation->setValue(keypath, rlottie::Color(r, g, b)); + break; + } + case LOTTIE_ANIMATION_PROPERTY_FILLOPACITY: { + double opacity = v[0]; + if (opacity > 100 || opacity < 0) break; + animation->mAnimation->setValue(keypath, (float)opacity); + break; + } + case LOTTIE_ANIMATION_PROPERTY_STROKECOLOR: { + double r = v[0]; + double g = v[1]; + double b = v[2]; + if (r > 1 || r < 0 || g > 1 || g < 0 || b > 1 || b < 0) break; + animation->mAnimation->setValue(keypath, rlottie::Color(r, g, b)); + break; + } + case LOTTIE_ANIMATION_PROPERTY_STROKEOPACITY: { + double opacity = v[0]; + if (opacity > 100 || opacity < 0) break; + animation->mAnimation->setValue(keypath, (float)opacity); + break; + } + case LOTTIE_ANIMATION_PROPERTY_STROKEWIDTH: { + double width = v[0]; + if (width < 0) break; + animation->mAnimation->setValue(keypath, (float)width); + break; + } + case LOTTIE_ANIMATION_PROPERTY_TR_POSITION: { + double x = v[0]; + double y = v[1]; + animation->mAnimation->setValue(keypath, rlottie::Point((float)x, (float)y)); + break; + } + case LOTTIE_ANIMATION_PROPERTY_TR_SCALE: { + double w = v[0]; + double h = v[1]; + animation->mAnimation->setValue(keypath, rlottie::Size((float)w, (float)h)); + break; + } + case LOTTIE_ANIMATION_PROPERTY_TR_ROTATION: { + double r = v[0]; + animation->mAnimation->setValue(keypath, (float)r); + break; + } + case LOTTIE_ANIMATION_PROPERTY_TR_ANCHOR: + case LOTTIE_ANIMATION_PROPERTY_TR_OPACITY: + //@TODO handle propery update. + break; + } +} + +RLOTTIE_API const LOTMarkerList* +lottie_animation_get_markerlist(Lottie_Animation_S *animation) +{ + if (!animation) return nullptr; + + auto markers = animation->mAnimation->markers(); + if (markers.size() == 0) return nullptr; + if (animation->mMarkerList) return (const LOTMarkerList*)animation->mMarkerList; + + animation->mMarkerList = new LOTMarkerList(); + animation->mMarkerList->size = markers.size(); + animation->mMarkerList->ptr = new LOTMarker[markers.size()](); + + for(size_t i = 0; i < markers.size(); i++) { + animation->mMarkerList->ptr[i].name = strdup(std::get<0>(markers[i]).c_str()); + animation->mMarkerList->ptr[i].startframe= std::get<1>(markers[i]); + animation->mMarkerList->ptr[i].endframe= std::get<2>(markers[i]); + } + return (const LOTMarkerList*)animation->mMarkerList; +} + +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/config.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/config.h new file mode 100644 index 0000000000..9ea44e13ab --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/config.h @@ -0,0 +1,15 @@ +// Manually authored config.h for vendored rlottie build via Swift Package Manager. +// Replaces the cmake-generated config.h (from cmake/config.h.in). +// We disable all optional features that would require extra dependencies or +// platform-specific setup: +// - LOTTIE_THREAD_SUPPORT: requires std::thread / TaskQueue wiring; disabled +// to keep the rendering synchronous (caller drives frame requests). +// - LOTTIE_CACHE_SUPPORT: model cache; disabled for simplicity. +// - LOTTIE_IMAGE_MODULE_SUPPORT: dynamic-library plugin for image decoding; +// disabled (stb_image is vendored and used unconditionally instead). +// - LOTTIE_LOGGING_SUPPORT: debug logging to stderr; disabled. + +// #define LOTTIE_THREAD_SUPPORT +// #define LOTTIE_CACHE_SUPPORT +// #define LOTTIE_IMAGE_MODULE_SUPPORT +// #define LOTTIE_LOGGING_SUPPORT diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieanimation.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieanimation.cpp new file mode 100644 index 0000000000..601c065c3d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieanimation.cpp @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "config.h" +#include "lottieitem.h" +#include "lottiemodel.h" +#include "rlottie.h" + +#include + +using namespace rlottie; +using namespace rlottie::internal; + +RLOTTIE_API void rlottie::configureModelCacheSize(size_t cacheSize) +{ + internal::model::configureModelCacheSize(cacheSize); +} + +struct RenderTask { + RenderTask() { receiver = sender.get_future(); } + std::promise sender; + std::future receiver; + AnimationImpl * playerImpl{nullptr}; + size_t frameNo{0}; + Surface surface; + bool keepAspectRatio{true}; +}; +using SharedRenderTask = std::shared_ptr; + +class AnimationImpl { +public: + void init(std::shared_ptr composition); + bool update(size_t frameNo, const VSize &size, bool keepAspectRatio); + VSize size() const { return mModel->size(); } + double duration() const { return mModel->duration(); } + double frameRate() const { return mModel->frameRate(); } + size_t totalFrame() const { return mModel->totalFrame(); } + size_t frameAtPos(double pos) const { return mModel->frameAtPos(pos); } + Surface render(size_t frameNo, const Surface &surface, + bool keepAspectRatio); + std::future renderAsync(size_t frameNo, Surface &&surface, + bool keepAspectRatio); + const LOTLayerNode * renderTree(size_t frameNo, const VSize &size); + + const LayerInfoList &layerInfoList() const + { + if (mLayerList.empty()) { + mLayerList = mModel->layerInfoList(); + } + return mLayerList; + } + const MarkerList &markers() const { return mModel->markers(); } + void setValue(const std::string &keypath, LOTVariant &&value); + void removeFilter(const std::string &keypath, Property prop); + +private: + mutable LayerInfoList mLayerList; + model::Composition * mModel; + SharedRenderTask mTask; + std::atomic mRenderInProgress; + std::unique_ptr mRenderer{nullptr}; +}; + +void AnimationImpl::setValue(const std::string &keypath, LOTVariant &&value) +{ + if (keypath.empty()) return; + mRenderer->setValue(keypath, value); +} + +const LOTLayerNode *AnimationImpl::renderTree(size_t frameNo, const VSize &size) +{ + if (update(frameNo, size, true)) { + mRenderer->buildRenderTree(); + } + return mRenderer->renderTree(); +} + +bool AnimationImpl::update(size_t frameNo, const VSize &size, + bool keepAspectRatio) +{ + frameNo += mModel->startFrame(); + + if (frameNo > mModel->endFrame()) frameNo = mModel->endFrame(); + + if (frameNo < mModel->startFrame()) frameNo = mModel->startFrame(); + + return mRenderer->update(int(frameNo), size, keepAspectRatio); +} + +Surface AnimationImpl::render(size_t frameNo, const Surface &surface, + bool keepAspectRatio) +{ + bool renderInProgress = mRenderInProgress.load(); + if (renderInProgress) { + vCritical << "Already Rendering Scheduled for this Animation"; + return surface; + } + + mRenderInProgress.store(true); + update( + frameNo, + VSize(int(surface.drawRegionWidth()), int(surface.drawRegionHeight())), + keepAspectRatio); + mRenderer->render(surface); + mRenderInProgress.store(false); + + return surface; +} + +void AnimationImpl::init(std::shared_ptr composition) +{ + mModel = composition.get(); + mRenderer = std::make_unique(composition); + mRenderInProgress = false; +} + +#ifdef LOTTIE_THREAD_SUPPORT + +#include +#include "vtaskqueue.h" + +/* + * Implement a task stealing schduler to perform render task + * As each player draws into its own buffer we can delegate this + * task to a slave thread. The scheduler creates a threadpool depending + * on the number of cores available in the system and does a simple fair + * scheduling by assigning the task in a round-robin fashion. Each thread + * in the threadpool has its own queue. once it finishes all the task on its + * own queue it goes through rest of the queue and looks for task if it founds + * one it steals the task from it and executes. if it couldn't find one then it + * just waits for new task on its own queue. + */ +class RenderTaskScheduler { + const unsigned _count{std::thread::hardware_concurrency()}; + std::vector _threads; + std::vector> _q{_count}; + std::atomic _index{0}; + + void run(unsigned i) + { + while (true) { + bool success = false; + SharedRenderTask task; + for (unsigned n = 0; n != _count * 2; ++n) { + if (_q[(i + n) % _count].try_pop(task)) { + success = true; + break; + } + } + if (!success && !_q[i].pop(task)) break; + + auto result = task->playerImpl->render(task->frameNo, task->surface, + task->keepAspectRatio); + task->sender.set_value(result); + } + } + + RenderTaskScheduler() + { + for (unsigned n = 0; n != _count; ++n) { + _threads.emplace_back([&, n] { run(n); }); + } + } + +public: + static RenderTaskScheduler &instance() + { + static RenderTaskScheduler singleton; + return singleton; + } + + ~RenderTaskScheduler() + { + for (auto &e : _q) e.done(); + + for (auto &e : _threads) e.join(); + } + + std::future process(SharedRenderTask task) + { + auto receiver = std::move(task->receiver); + auto i = _index++; + + for (unsigned n = 0; n != _count; ++n) { + if (_q[(i + n) % _count].try_push(std::move(task))) return receiver; + } + + if (_count > 0) { + _q[i % _count].push(std::move(task)); + } + + return receiver; + } +}; + +#else +class RenderTaskScheduler { +public: + static RenderTaskScheduler &instance() + { + static RenderTaskScheduler singleton; + return singleton; + } + + std::future process(SharedRenderTask task) + { + auto result = task->playerImpl->render(task->frameNo, task->surface, + task->keepAspectRatio); + task->sender.set_value(result); + return std::move(task->receiver); + } +}; +#endif + +std::future AnimationImpl::renderAsync(size_t frameNo, + Surface &&surface, + bool keepAspectRatio) +{ + if (!mTask) { + mTask = std::make_shared(); + } else { + mTask->sender = std::promise(); + mTask->receiver = mTask->sender.get_future(); + } + mTask->playerImpl = this; + mTask->frameNo = frameNo; + mTask->surface = std::move(surface); + mTask->keepAspectRatio = keepAspectRatio; + + return RenderTaskScheduler::instance().process(mTask); +} + +/** + * \breif Brief abput the Api. + * Description about the setFilePath Api + * @param path add the details + */ +std::unique_ptr Animation::loadFromData( + std::string jsonData, const std::string &key, + const std::string &resourcePath, bool cachePolicy) +{ + if (jsonData.empty()) { + vWarning << "jason data is empty"; + return nullptr; + } + + auto composition = model::loadFromData(std::move(jsonData), key, + resourcePath, cachePolicy); + if (composition) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(std::move(composition)); + return animation; + } + + return nullptr; +} + +std::unique_ptr Animation::loadFromData(std::string jsonData, + std::string resourcePath, + ColorFilter filter) +{ + if (jsonData.empty()) { + vWarning << "jason data is empty"; + return nullptr; + } + + auto composition = model::loadFromData( + std::move(jsonData), std::move(resourcePath), std::move(filter)); + if (composition) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(std::move(composition)); + return animation; + } + return nullptr; +} + +std::unique_ptr Animation::loadFromFile(const std::string &path, + bool cachePolicy) +{ + if (path.empty()) { + vWarning << "File path is empty"; + return nullptr; + } + + auto composition = model::loadFromFile(path, cachePolicy); + if (composition) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(std::move(composition)); + return animation; + } + return nullptr; +} + +void Animation::size(size_t &width, size_t &height) const +{ + VSize sz = d->size(); + + width = sz.width(); + height = sz.height(); +} + +double Animation::duration() const +{ + return d->duration(); +} + +double Animation::frameRate() const +{ + return d->frameRate(); +} + +size_t Animation::totalFrame() const +{ + return d->totalFrame(); +} + +size_t Animation::frameAtPos(double pos) +{ + return d->frameAtPos(pos); +} + +const LOTLayerNode *Animation::renderTree(size_t frameNo, size_t width, + size_t height) const +{ + return d->renderTree(frameNo, VSize(int(width), int(height))); +} + +std::future Animation::render(size_t frameNo, Surface surface, + bool keepAspectRatio) +{ + return d->renderAsync(frameNo, std::move(surface), keepAspectRatio); +} + +void Animation::renderSync(size_t frameNo, Surface surface, + bool keepAspectRatio) +{ + d->render(frameNo, surface, keepAspectRatio); +} + +const LayerInfoList &Animation::layers() const +{ + return d->layerInfoList(); +} + +const MarkerList &Animation::markers() const +{ + return d->markers(); +} + +void Animation::setValue(Color_Type, Property prop, const std::string &keypath, + Color value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Float_Type, Property prop, const std::string &keypath, + float value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Size_Type, Property prop, const std::string &keypath, + Size value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Point_Type, Property prop, const std::string &keypath, + Point value) +{ + d->setValue(keypath, + LOTVariant(prop, [value](const FrameInfo &) { return value; })); +} + +void Animation::setValue(Color_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Float_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Size_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +void Animation::setValue(Point_Type, Property prop, const std::string &keypath, + std::function &&value) +{ + d->setValue(keypath, LOTVariant(prop, value)); +} + +Animation::~Animation() = default; +Animation::Animation() : d(std::make_unique()) {} + +Surface::Surface(uint32_t *buffer, size_t width, size_t height, + size_t bytesPerLine) + : mBuffer(buffer), + mWidth(width), + mHeight(height), + mBytesPerLine(bytesPerLine) +{ + mDrawArea.w = mWidth; + mDrawArea.h = mHeight; +} + +void Surface::setDrawRegion(size_t x, size_t y, size_t width, size_t height) +{ + if ((x + width > mWidth) || (y + height > mHeight)) return; + + mDrawArea.x = x; + mDrawArea.y = y; + mDrawArea.w = width; + mDrawArea.h = height; +} + +#ifdef LOTTIE_LOGGING_SUPPORT +void initLogging() +{ +#if defined(__ARM_NEON__) + set_log_level(LogLevel::OFF); +#else + initialize(GuaranteedLogger(), "/tmp/", "rlottie", 1); + set_log_level(LogLevel::INFO); +#endif +} + +V_CONSTRUCTOR_FUNCTION(initLogging) +#endif diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiefiltermodel.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiefiltermodel.h new file mode 100644 index 0000000000..b2b98b1900 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiefiltermodel.h @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LOTTIEFILTERMODEL_H +#define LOTTIEFILTERMODEL_H + +#include +#include +#include +#include "lottiemodel.h" +#include "rlottie.h" + +using namespace rlottie::internal; +// Naive way to implement std::variant +// refactor it when we move to c++17 +// users should make sure proper combination +// of id and value are passed while creating the object. +class LOTVariant { +public: + using ValueFunc = std::function; + using ColorFunc = std::function; + using PointFunc = std::function; + using SizeFunc = std::function; + + LOTVariant(rlottie::Property prop, const ValueFunc& v) + : mPropery(prop), mTag(Value) + { + construct(impl.valueFunc, v); + } + + LOTVariant(rlottie::Property prop, ValueFunc&& v) + : mPropery(prop), mTag(Value) + { + moveConstruct(impl.valueFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const ColorFunc& v) + : mPropery(prop), mTag(Color) + { + construct(impl.colorFunc, v); + } + + LOTVariant(rlottie::Property prop, ColorFunc&& v) + : mPropery(prop), mTag(Color) + { + moveConstruct(impl.colorFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const PointFunc& v) + : mPropery(prop), mTag(Point) + { + construct(impl.pointFunc, v); + } + + LOTVariant(rlottie::Property prop, PointFunc&& v) + : mPropery(prop), mTag(Point) + { + moveConstruct(impl.pointFunc, std::move(v)); + } + + LOTVariant(rlottie::Property prop, const SizeFunc& v) + : mPropery(prop), mTag(Size) + { + construct(impl.sizeFunc, v); + } + + LOTVariant(rlottie::Property prop, SizeFunc&& v) + : mPropery(prop), mTag(Size) + { + moveConstruct(impl.sizeFunc, std::move(v)); + } + + rlottie::Property property() const { return mPropery; } + + const ColorFunc& color() const + { + assert(mTag == Color); + return impl.colorFunc; + } + + const ValueFunc& value() const + { + assert(mTag == Value); + return impl.valueFunc; + } + + const PointFunc& point() const + { + assert(mTag == Point); + return impl.pointFunc; + } + + const SizeFunc& size() const + { + assert(mTag == Size); + return impl.sizeFunc; + } + + LOTVariant() = default; + ~LOTVariant() noexcept { Destroy(); } + LOTVariant(const LOTVariant& other) { Copy(other); } + LOTVariant(LOTVariant&& other) noexcept { Move(std::move(other)); } + LOTVariant& operator=(LOTVariant&& other) + { + Destroy(); + Move(std::move(other)); + return *this; + } + LOTVariant& operator=(const LOTVariant& other) + { + Destroy(); + Copy(other); + return *this; + } + +private: + template + void construct(T& member, const T& val) + { + new (&member) T(val); + } + + template + void moveConstruct(T& member, T&& val) + { + new (&member) T(std::move(val)); + } + + void Move(LOTVariant&& other) + { + switch (other.mTag) { + case Type::Value: + moveConstruct(impl.valueFunc, std::move(other.impl.valueFunc)); + break; + case Type::Color: + moveConstruct(impl.colorFunc, std::move(other.impl.colorFunc)); + break; + case Type::Point: + moveConstruct(impl.pointFunc, std::move(other.impl.pointFunc)); + break; + case Type::Size: + moveConstruct(impl.sizeFunc, std::move(other.impl.sizeFunc)); + break; + default: + break; + } + mTag = other.mTag; + mPropery = other.mPropery; + other.mTag = MonoState; + } + + void Copy(const LOTVariant& other) + { + switch (other.mTag) { + case Type::Value: + construct(impl.valueFunc, other.impl.valueFunc); + break; + case Type::Color: + construct(impl.colorFunc, other.impl.colorFunc); + break; + case Type::Point: + construct(impl.pointFunc, other.impl.pointFunc); + break; + case Type::Size: + construct(impl.sizeFunc, other.impl.sizeFunc); + break; + default: + break; + } + mTag = other.mTag; + mPropery = other.mPropery; + } + + void Destroy() + { + switch (mTag) { + case MonoState: { + break; + } + case Value: { + impl.valueFunc.~ValueFunc(); + break; + } + case Color: { + impl.colorFunc.~ColorFunc(); + break; + } + case Point: { + impl.pointFunc.~PointFunc(); + break; + } + case Size: { + impl.sizeFunc.~SizeFunc(); + break; + } + } + } + + enum Type { MonoState, Value, Color, Point, Size }; + rlottie::Property mPropery; + Type mTag{MonoState}; + union details { + ColorFunc colorFunc; + ValueFunc valueFunc; + PointFunc pointFunc; + SizeFunc sizeFunc; + details() {} + ~details() noexcept {} + } impl; +}; + +namespace rlottie { + +namespace internal { + +namespace model { + +class FilterData { +public: + void addValue(LOTVariant& value) + { + uint index = static_cast(value.property()); + if (mBitset.test(index)) { + std::replace_if(mFilters.begin(), mFilters.end(), + [&value](const LOTVariant& e) { + return e.property() == value.property(); + }, + value); + } else { + mBitset.set(index); + mFilters.push_back(value); + } + } + + void removeValue(LOTVariant& value) + { + uint index = static_cast(value.property()); + if (mBitset.test(index)) { + mBitset.reset(index); + mFilters.erase(std::remove_if(mFilters.begin(), mFilters.end(), + [&value](const LOTVariant& e) { + return e.property() == + value.property(); + }), + mFilters.end()); + } + } + bool hasFilter(rlottie::Property prop) const + { + return mBitset.test(static_cast(prop)); + } + model::Color color(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + rlottie::Color col = data(prop).color()(info); + return model::Color(col.r(), col.g(), col.b()); + } + VPointF point(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + rlottie::Point pt = data(prop).point()(info); + return VPointF(pt.x(), pt.y()); + } + VSize scale(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + rlottie::Size sz = data(prop).size()(info); + return VSize(sz.w(), sz.h()); + } + float opacity(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + float val = data(prop).value()(info); + return val / 100; + } + float value(rlottie::Property prop, int frame) const + { + rlottie::FrameInfo info(frame); + return data(prop).value()(info); + } + +private: + const LOTVariant& data(rlottie::Property prop) const + { + auto result = std::find_if( + mFilters.begin(), mFilters.end(), + [prop](const LOTVariant& e) { return e.property() == prop; }); + return *result; + } + std::bitset<32> mBitset{0}; + std::vector mFilters; +}; + +template +struct FilterBase +{ + FilterBase(T *model): model_(model){} + + const char* name() const { return model_->name(); } + + FilterData* filter() { + if (!filterData_) filterData_ = std::make_unique(); + return filterData_.get(); + } + + const FilterData * filter() const { return filterData_.get(); } + const T* model() const { return model_;} + + bool hasFilter(rlottie::Property prop) const { + return filterData_ ? filterData_->hasFilter(prop) + : false; + } + + T* model_{nullptr}; + std::unique_ptr filterData_{nullptr}; +}; + + +template +class Filter : public FilterBase { +public: + Filter(T* model): FilterBase(model){} + model::Color color(int frame) const + { + if (this->hasFilter(rlottie::Property::StrokeColor)) { + return this->filter()->color(rlottie::Property::StrokeColor, frame); + } + return this->model()->color(frame); + } + float opacity(int frame) const + { + if (this->hasFilter(rlottie::Property::StrokeOpacity)) { + return this->filter()->opacity(rlottie::Property::StrokeOpacity, frame); + } + return this->model()->opacity(frame); + } + + float strokeWidth(int frame) const + { + if (this->hasFilter(rlottie::Property::StrokeWidth)) { + return this->filter()->value(rlottie::Property::StrokeWidth, frame); + } + return this->model()->strokeWidth(frame); + } + + float miterLimit() const { return this->model()->miterLimit(); } + CapStyle capStyle() const { return this->model()->capStyle(); } + JoinStyle joinStyle() const { return this->model()->joinStyle(); } + bool hasDashInfo() const { return this->model()->hasDashInfo(); } + void getDashInfo(int frameNo, std::vector& result) const + { + return this->model()->getDashInfo(frameNo, result); + } +}; + + +template <> +class Filter: public FilterBase +{ +public: + Filter(model::Fill* model) : FilterBase(model) {} + + model::Color color(int frame) const + { + if (this->hasFilter(rlottie::Property::FillColor)) { + return this->filter()->color(rlottie::Property::FillColor, frame); + } + return this->model()->color(frame); + } + + float opacity(int frame) const + { + if (this->hasFilter(rlottie::Property::FillOpacity)) { + return this->filter()->opacity(rlottie::Property::FillOpacity, frame); + } + return this->model()->opacity(frame); + } + + FillRule fillRule() const { return this->model()->fillRule(); } +}; + +template <> +class Filter : public FilterBase +{ +public: + Filter(model::Group* model = nullptr) : FilterBase(model) {} + + bool hasModel() const { return this->model() ? true : false; } + + model::Transform* transform() const { return this->model() ? this->model()->mTransform : nullptr; } + VMatrix matrix(int frame) const + { + VMatrix mS, mR, mT; + if (this->hasFilter(rlottie::Property::TrScale)) { + VSize s = this->filter()->scale(rlottie::Property::TrScale, frame); + mS.scale(s.width() / 100.0, s.height() / 100.0); + } + if (this->hasFilter(rlottie::Property::TrRotation)) { + mR.rotate(this->filter()->value(rlottie::Property::TrRotation, frame)); + } + if (this->hasFilter(rlottie::Property::TrPosition)) { + mT.translate(this->filter()->point(rlottie::Property::TrPosition, frame)); + } + + return this->model()->mTransform->matrix(frame) * mS * mR * mT; + } +}; + + +} // namespace model + +} // namespace internal + +} // namespace rlottie + +#endif // LOTTIEFILTERMODEL_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.cpp new file mode 100644 index 0000000000..7cd25ccd7d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.cpp @@ -0,0 +1,1481 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "lottieitem.h" +#include +#include +#include +#include "lottiekeypath.h" +#include "vbitmap.h" +#include "vpainter.h" +#include "vraster.h" + +/* Lottie Layer Rules + * 1. time stretch is pre calculated and applied to all the properties of the + * lottilayer model and all its children + * 2. The frame property could be reversed using,time-reverse layer property in + * AE. which means (start frame > endFrame) 3. + */ + +static bool transformProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::TrAnchor: + case rlottie::Property::TrScale: + case rlottie::Property::TrOpacity: + case rlottie::Property::TrPosition: + case rlottie::Property::TrRotation: + return true; + default: + return false; + } +} +static bool fillProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::FillColor: + case rlottie::Property::FillOpacity: + return true; + default: + return false; + } +} + +static bool strokeProp(rlottie::Property prop) +{ + switch (prop) { + case rlottie::Property::StrokeColor: + case rlottie::Property::StrokeOpacity: + case rlottie::Property::StrokeWidth: + return true; + default: + return false; + } +} + +static renderer::Layer *createLayerItem(model::Layer *layerData, + VArenaAlloc * allocator) +{ + switch (layerData->mLayerType) { + case model::Layer::Type::Precomp: { + return allocator->make(layerData, allocator); + } + case model::Layer::Type::Solid: { + return allocator->make(layerData); + } + case model::Layer::Type::Shape: { + return allocator->make(layerData, allocator); + } + case model::Layer::Type::Null: { + return allocator->make(layerData); + } + case model::Layer::Type::Image: { + return allocator->make(layerData); + } + default: + return nullptr; + break; + } +} + +renderer::Composition::Composition(std::shared_ptr model) + : mCurFrameNo(-1) +{ + mModel = std::move(model); + mRootLayer = createLayerItem(mModel->mRootLayer, &mAllocator); + mRootLayer->setComplexContent(false); + mViewSize = mModel->size(); +} + +void renderer::Composition::setValue(const std::string &keypath, + LOTVariant & value) +{ + LOTKeyPath key(keypath); + mRootLayer->resolveKeyPath(key, 0, value); +} + +bool renderer::Composition::update(int frameNo, const VSize &size, + bool keepAspectRatio) +{ + // check if cached frame is same as requested frame. + if ((mViewSize == size) && (mCurFrameNo == frameNo) && + (mKeepAspectRatio == keepAspectRatio)) + return false; + + mViewSize = size; + mCurFrameNo = frameNo; + mKeepAspectRatio = keepAspectRatio; + + /* + * if viewbox dosen't scale exactly to the viewport + * we scale the viewbox keeping AspectRatioPreserved and then align the + * viewbox to the viewport using AlignCenter rule. + */ + VMatrix m; + VSize viewPort = mViewSize; + VSize viewBox = mModel->size(); + float sx = float(viewPort.width()) / viewBox.width(); + float sy = float(viewPort.height()) / viewBox.height(); + if (mKeepAspectRatio) { + float scale = std::min(sx, sy); + float tx = (viewPort.width() - viewBox.width() * scale) * 0.5f; + float ty = (viewPort.height() - viewBox.height() * scale) * 0.5f; + m.translate(tx, ty).scale(scale, scale); + } else { + m.scale(sx, sy); + } + mRootLayer->update(frameNo, m, 1.0); + return true; +} + +bool renderer::Composition::render(const rlottie::Surface &surface) +{ + mSurface.reset(reinterpret_cast(surface.buffer()), + uint(surface.width()), uint(surface.height()), + uint(surface.bytesPerLine()), + VBitmap::Format::ARGB32_Premultiplied); + + /* schedule all preprocess task for this frame at once. + */ + VRect clip(0, 0, int(surface.drawRegionWidth()), + int(surface.drawRegionHeight())); + mRootLayer->preprocess(clip); + + VPainter painter(&mSurface); + // set sub surface area for drawing. + painter.setDrawRegion( + VRect(int(surface.drawRegionPosX()), int(surface.drawRegionPosY()), + int(surface.drawRegionWidth()), int(surface.drawRegionHeight()))); + mRootLayer->render(&painter, {}, {}, mSurfaceCache); + painter.end(); + return true; +} + +void renderer::Mask::update(int frameNo, const VMatrix &parentMatrix, + float /*parentAlpha*/, const DirtyFlag &flag) +{ + bool dirtyPath = false; + + if (flag.testFlag(DirtyFlagBit::None) && mData->isStatic()) return; + + if (mData->mShape.isStatic()) { + if (mLocalPath.empty()) { + dirtyPath = true; + mData->mShape.value(frameNo, mLocalPath); + } + } else { + dirtyPath = true; + mData->mShape.value(frameNo, mLocalPath); + } + /* mask item dosen't inherit opacity */ + mCombinedAlpha = mData->opacity(frameNo); + + if ( flag.testFlag(DirtyFlagBit::Matrix) || dirtyPath ) { + mFinalPath.clone(mLocalPath); + mFinalPath.transform(parentMatrix); + mRasterRequest = true; + } +} + +VRle renderer::Mask::rle() +{ + if (!vCompare(mCombinedAlpha, 1.0f)) { + VRle obj = mRasterizer.rle(); + obj *= uchar(mCombinedAlpha * 255); + return obj; + } else { + return mRasterizer.rle(); + } +} + +void renderer::Mask::preprocess(const VRect &clip) +{ + if (mRasterRequest) + mRasterizer.rasterize(mFinalPath, FillRule::Winding, clip); +} + +void renderer::Layer::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle, SurfaceCache &) +{ + auto renderlist = renderList(); + + if (renderlist.empty()) return; + + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + for (auto &i : renderlist) { + painter->setBrush(i->mBrush); + VRle rle = i->rle(); + if (matteRle.empty()) { + if (mask.empty()) { + // no mask no matte + painter->drawRle(VPoint(), rle); + } else { + // only mask + painter->drawRle(rle, mask); + } + + } else { + if (!mask.empty()) rle = rle & mask; + + if (rle.empty()) continue; + if (matteType() == model::MatteType::AlphaInv) { + rle = rle - matteRle; + painter->drawRle(VPoint(), rle); + } else { + // render with matteRle as clip. + painter->drawRle(rle, matteRle); + } + } + } +} + +void renderer::LayerMask::preprocess(const VRect &clip) +{ + for (auto &i : mMasks) { + i.preprocess(clip); + } +} + +renderer::LayerMask::LayerMask(model::Layer *layerData) +{ + if (!layerData->mExtra) return; + + mMasks.reserve(layerData->mExtra->mMasks.size()); + + for (auto &i : layerData->mExtra->mMasks) { + mMasks.emplace_back(i); + mStatic &= i->isStatic(); + } +} + +void renderer::LayerMask::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + if (flag.testFlag(DirtyFlagBit::None) && isStatic()) return; + + for (auto &i : mMasks) { + i.update(frameNo, parentMatrix, parentAlpha, flag); + } + mDirty = true; +} + +VRle renderer::LayerMask::maskRle(const VRect &clipRect) +{ + if (!mDirty) return mRle; + + VRle rle; + for (auto &e : mMasks) { + const auto cur = [&]() { + if (e.inverted()) + return clipRect - e.rle(); + else + return e.rle(); + }(); + + switch (e.maskMode()) { + case model::Mask::Mode::Add: { + rle = rle + cur; + break; + } + case model::Mask::Mode::Substarct: { + if (rle.empty() && !clipRect.empty()) + rle = clipRect - cur; + else + rle = rle - cur; + break; + } + case model::Mask::Mode::Intersect: { + if (rle.empty() && !clipRect.empty()) + rle = clipRect & cur; + else + rle = rle & cur; + break; + } + case model::Mask::Mode::Difference: { + rle = rle ^ cur; + break; + } + default: + break; + } + } + + if (!rle.empty() && !rle.unique()) { + mRle.clone(rle); + } else { + mRle = rle; + } + mDirty = false; + return mRle; +} + +renderer::Layer::Layer(model::Layer *layerData) : mLayerData(layerData) +{ + if (mLayerData->mHasMask) + mLayerMask = std::make_unique(mLayerData); +} + +bool renderer::Layer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(name(), depth)) { + return false; + } + + if (!keyPath.skip(name())) { + if (keyPath.fullyResolvesTo(name(), depth) && + transformProp(value.property())) { + //@TODO handle propery update. + } + } + return true; +} + +bool renderer::ShapeLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + mRoot->resolveKeyPath(keyPath, newDepth, value); + } + return true; + } + return false; +} + +bool renderer::CompLayer::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (renderer::Layer::resolveKeyPath(keyPath, depth, value)) { + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (const auto &layer : mLayers) { + layer->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; + } + return false; +} + +void renderer::Layer::update(int frameNumber, const VMatrix &parentMatrix, + float parentAlpha) +{ + mFrameNo = frameNumber; + // 1. check if the layer is part of the current frame + if (!visible()) return; + + float alpha = parentAlpha * opacity(frameNo()); + if (vIsZero(alpha)) { + mCombinedAlpha = 0; + return; + } + + // 2. calculate the parent matrix and alpha + VMatrix m = matrix(frameNo()); + m *= parentMatrix; + + // 3. update the dirty flag based on the change + if (mCombinedMatrix != m) { + mDirtyFlag |= DirtyFlagBit::Matrix; + mCombinedMatrix = m; + } + + if (!vCompare(mCombinedAlpha, alpha)) { + mDirtyFlag |= DirtyFlagBit::Alpha; + mCombinedAlpha = alpha; + } + + // 4. update the mask + if (mLayerMask) { + mLayerMask->update(frameNo(), mCombinedMatrix, mCombinedAlpha, + mDirtyFlag); + } + + // 5. if no parent property change and layer is static then nothing to do. + if (!mLayerData->precompLayer() && flag().testFlag(DirtyFlagBit::None) && + isStatic()) + return; + + // 6. update the content of the layer + updateContent(); + + // 7. reset the dirty flag + mDirtyFlag = DirtyFlagBit::None; +} + +VMatrix renderer::Layer::matrix(int frameNo) const +{ + return mParentLayer + ? (mLayerData->matrix(frameNo) * mParentLayer->matrix(frameNo)) + : mLayerData->matrix(frameNo); +} + +bool renderer::Layer::visible() const +{ + return (frameNo() >= mLayerData->inFrame() && + frameNo() < mLayerData->outFrame()); +} + +void renderer::Layer::preprocess(const VRect &clip) +{ + // layer dosen't contribute to the frame + if (skipRendering()) return; + + // preprocess layer masks + if (mLayerMask) mLayerMask->preprocess(clip); + + preprocessStage(clip); +} + +renderer::CompLayer::CompLayer(model::Layer *layerModel, VArenaAlloc *allocator) + : renderer::Layer(layerModel) +{ + if (!mLayerData->mChildren.empty()) + mLayers.reserve(mLayerData->mChildren.size()); + + // 1. keep the layer in back-to-front order. + // as lottie model keeps the data in front-toback-order. + for (auto it = mLayerData->mChildren.crbegin(); + it != mLayerData->mChildren.rend(); ++it) { + auto model = static_cast(*it); + auto item = createLayerItem(model, allocator); + if (item) mLayers.push_back(item); + } + + // 2. update parent layer + for (const auto &layer : mLayers) { + int id = layer->parentId(); + if (id >= 0) { + auto search = + std::find_if(mLayers.begin(), mLayers.end(), + [id](const auto &val) { return val->id() == id; }); + if (search != mLayers.end()) layer->setParentLayer(*search); + } + } + + // 4. check if its a nested composition + if (!layerModel->layerSize().empty()) { + mClipper = std::make_unique(layerModel->layerSize()); + } + + if (mLayers.size() > 1) setComplexContent(true); +} + +void renderer::CompLayer::render(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle, SurfaceCache &cache) +{ + if (vIsZero(combinedAlpha())) return; + + if (vCompare(combinedAlpha(), 1.0)) { + renderHelper(painter, inheritMask, matteRle, cache); + } else { + if (complexContent()) { + VSize size = painter->clipBoundingRect().size(); + VPainter srcPainter; + VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); + srcPainter.begin(&srcBitmap); + renderHelper(&srcPainter, inheritMask, matteRle, cache); + srcPainter.end(); + painter->drawBitmap(VPoint(), srcBitmap, + uchar(combinedAlpha() * 255.0f)); + cache.release_surface(srcBitmap); + } else { + renderHelper(painter, inheritMask, matteRle, cache); + } + } +} + +void renderer::CompLayer::renderHelper(VPainter * painter, + const VRle & inheritMask, + const VRle & matteRle, + SurfaceCache &cache) +{ + VRle mask; + if (mLayerMask) { + mask = mLayerMask->maskRle(painter->clipBoundingRect()); + if (!inheritMask.empty()) mask = mask & inheritMask; + // if resulting mask is empty then return. + if (mask.empty()) return; + } else { + mask = inheritMask; + } + + if (mClipper) { + mask = mClipper->rle(mask); + if (mask.empty()) return; + } + + renderer::Layer *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer; + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) + renderMatteLayer(painter, mask, matteRle, matte, layer, + cache); + } else { + layer->render(painter, mask, matteRle, cache); + } + } + matte = nullptr; + } + } +} + +void renderer::CompLayer::renderMatteLayer(VPainter *painter, const VRle &mask, + const VRle & matteRle, + renderer::Layer *layer, + renderer::Layer *src, + SurfaceCache & cache) +{ + VSize size = painter->clipBoundingRect().size(); + // Decide if we can use fast matte. + // 1. draw src layer to matte buffer + VPainter srcPainter; + VBitmap srcBitmap = cache.make_surface(size.width(), size.height()); + srcPainter.begin(&srcBitmap); + src->render(&srcPainter, mask, matteRle, cache); + srcPainter.end(); + + // 2. draw layer to layer buffer + VPainter layerPainter; + VBitmap layerBitmap = cache.make_surface(size.width(), size.height()); + layerPainter.begin(&layerBitmap); + layer->render(&layerPainter, mask, matteRle, cache); + + // 2.1update composition mode + switch (layer->matteType()) { + case model::MatteType::Alpha: + case model::MatteType::Luma: { + layerPainter.setBlendMode(BlendMode::DestIn); + break; + } + case model::MatteType::AlphaInv: + case model::MatteType::LumaInv: { + layerPainter.setBlendMode(BlendMode::DestOut); + break; + } + default: + break; + } + + // 2.2 update srcBuffer if the matte is luma type + if (layer->matteType() == model::MatteType::Luma || + layer->matteType() == model::MatteType::LumaInv) { + srcBitmap.updateLuma(); + } + + // 2.3 draw src buffer as mask + layerPainter.drawBitmap(VPoint(), srcBitmap); + layerPainter.end(); + // 3. draw the result buffer into painter + painter->drawBitmap(VPoint(), layerBitmap); + + cache.release_surface(srcBitmap); + cache.release_surface(layerBitmap); +} + +void renderer::Clipper::update(const VMatrix &matrix) +{ + mPath.reset(); + mPath.addRect(VRectF(0, 0, mSize.width(), mSize.height())); + mPath.transform(matrix); + mRasterRequest = true; +} + +void renderer::Clipper::preprocess(const VRect &clip) +{ + if (mRasterRequest) mRasterizer.rasterize(mPath, FillRule::Winding, clip); + + mRasterRequest = false; +} + +VRle renderer::Clipper::rle(const VRle &mask) +{ + if (mask.empty()) return mRasterizer.rle(); + + mMaskedRle.clone(mask); + mMaskedRle &= mRasterizer.rle(); + return mMaskedRle; +} + +void renderer::CompLayer::updateContent() +{ + if (mClipper && flag().testFlag(DirtyFlagBit::Matrix)) { + mClipper->update(combinedMatrix()); + } + int mappedFrame = mLayerData->timeRemap(frameNo()); + float alpha = combinedAlpha(); + if (complexContent()) alpha = 1; + for (const auto &layer : mLayers) { + layer->update(mappedFrame, combinedMatrix(), alpha); + } +} + +void renderer::CompLayer::preprocessStage(const VRect &clip) +{ + // if layer has clipper + if (mClipper) mClipper->preprocess(clip); + + renderer::Layer *matte = nullptr; + for (const auto &layer : mLayers) { + if (layer->hasMatte()) { + matte = layer; + } else { + if (layer->visible()) { + if (matte) { + if (matte->visible()) { + layer->preprocess(clip); + matte->preprocess(clip); + } + } else { + layer->preprocess(clip); + } + } + matte = nullptr; + } + } +} + +renderer::SolidLayer::SolidLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ + mDrawableList = &mRenderNode; +} + +void renderer::SolidLayer::updateContent() +{ + if (flag() & DirtyFlagBit::Matrix) { + mPath.reset(); + mPath.addRect(VRectF(0, 0, mLayerData->layerSize().width(), + mLayerData->layerSize().height())); + mPath.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = mPath; + } + if (flag() & DirtyFlagBit::Alpha) { + model::Color color = mLayerData->solidColor(); + VBrush brush(color.toColor(combinedAlpha())); + mRenderNode.setBrush(brush); + mRenderNode.mFlag |= VDrawable::DirtyState::Brush; + } +} + +void renderer::SolidLayer::preprocessStage(const VRect &clip) +{ + mRenderNode.preprocess(clip); +} + +renderer::DrawableList renderer::SolidLayer::renderList() +{ + if (skipRendering()) return {}; + + return {&mDrawableList, 1}; +} + +renderer::ImageLayer::ImageLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ + mDrawableList = &mRenderNode; + + if (!mLayerData->asset()) return; + + mTexture.mBitmap = mLayerData->asset()->bitmap(); + VBrush brush(&mTexture); + mRenderNode.setBrush(brush); +} + +void renderer::ImageLayer::updateContent() +{ + if (!mLayerData->asset()) return; + + if (flag() & DirtyFlagBit::Matrix) { + mPath.reset(); + mPath.addRect(VRectF(0, 0, mLayerData->asset()->mWidth, + mLayerData->asset()->mHeight)); + mPath.transform(combinedMatrix()); + mRenderNode.mFlag |= VDrawable::DirtyState::Path; + mRenderNode.mPath = mPath; + mTexture.mMatrix = combinedMatrix(); + } + + if (flag() & DirtyFlagBit::Alpha) { + mTexture.mAlpha = int(combinedAlpha() * 255); + } +} + +void renderer::ImageLayer::preprocessStage(const VRect &clip) +{ + mRenderNode.preprocess(clip); +} + +renderer::DrawableList renderer::ImageLayer::renderList() +{ + if (skipRendering()) return {}; + + return {&mDrawableList, 1}; +} + +renderer::NullLayer::NullLayer(model::Layer *layerData) + : renderer::Layer(layerData) +{ +} +void renderer::NullLayer::updateContent() {} + +static renderer::Object *createContentItem(model::Object *contentData, + VArenaAlloc * allocator) +{ + switch (contentData->type()) { + case model::Object::Type::Group: { + return allocator->make( + static_cast(contentData), allocator); + } + case model::Object::Type::Rect: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Ellipse: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Path: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Polystar: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Fill: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::GFill: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Stroke: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::GStroke: { + return allocator->make( + static_cast(contentData)); + } + case model::Object::Type::Repeater: { + return allocator->make( + static_cast(contentData), allocator); + } + case model::Object::Type::Trim: { + return allocator->make( + static_cast(contentData)); + } + default: + return nullptr; + break; + } +} + +renderer::ShapeLayer::ShapeLayer(model::Layer *layerData, + VArenaAlloc * allocator) + : renderer::Layer(layerData), + mRoot(allocator->make(nullptr, allocator)) +{ + mRoot->addChildren(layerData, allocator); + + std::vector list; + mRoot->processPaintItems(list); + + if (layerData->hasPathOperator()) { + list.clear(); + mRoot->processTrimItems(list); + } +} + +void renderer::ShapeLayer::updateContent() +{ + mRoot->update(frameNo(), combinedMatrix(), combinedAlpha(), flag()); + + if (mLayerData->hasPathOperator()) { + mRoot->applyTrim(); + } +} + +void renderer::ShapeLayer::preprocessStage(const VRect &clip) +{ + mDrawableList.clear(); + mRoot->renderList(mDrawableList); + + for (auto &drawable : mDrawableList) drawable->preprocess(clip); +} + +renderer::DrawableList renderer::ShapeLayer::renderList() +{ + if (skipRendering()) return {}; + + mDrawableList.clear(); + mRoot->renderList(mDrawableList); + + if (mDrawableList.empty()) return {}; + + return {mDrawableList.data(), mDrawableList.size()}; +} + +bool renderer::Group::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.skip(name())) { + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (!keyPath.skip(mModel.name())) { + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + transformProp(value.property())) { + mModel.filter()->addValue(value); + } + } + } + + if (keyPath.propagate(name(), depth)) { + uint newDepth = keyPath.nextDepth(name(), depth); + for (auto &child : mContents) { + child->resolveKeyPath(keyPath, newDepth, value); + } + } + return true; +} + +bool renderer::Fill::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + fillProp(value.property())) { + mModel.filter()->addValue(value); + return true; + } + return false; +} + +bool renderer::Stroke::resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) +{ + if (!keyPath.matches(mModel.name(), depth)) { + return false; + } + + if (keyPath.fullyResolvesTo(mModel.name(), depth) && + strokeProp(value.property())) { + mModel.filter()->addValue(value); + return true; + } + return false; +} + +renderer::Group::Group(model::Group *data, VArenaAlloc *allocator) + : mModel(data) +{ + addChildren(data, allocator); +} + +void renderer::Group::addChildren(model::Group *data, VArenaAlloc *allocator) +{ + if (!data) return; + + if (!data->mChildren.empty()) mContents.reserve(data->mChildren.size()); + + // keep the content in back-to-front order. + // as lottie model keeps it in front-to-back order. + for (auto it = data->mChildren.crbegin(); it != data->mChildren.rend(); + ++it) { + auto content = createContentItem(*it, allocator); + if (content) { + mContents.push_back(content); + } + } +} + +void renderer::Group::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + DirtyFlag newFlag = flag; + float alpha; + + if (mModel.hasModel() && mModel.transform()) { + VMatrix m = mModel.matrix(frameNo); + + m *= parentMatrix; + if (!(flag & DirtyFlagBit::Matrix) && !mModel.transform()->isStatic() && + (m != mMatrix)) { + newFlag |= DirtyFlagBit::Matrix; + } + + mMatrix = m; + + alpha = parentAlpha * mModel.transform()->opacity(frameNo); + if (!vCompare(alpha, parentAlpha)) { + newFlag |= DirtyFlagBit::Alpha; + } + } else { + mMatrix = parentMatrix; + alpha = parentAlpha; + } + + for (const auto &content : mContents) { + content->update(frameNo, matrix(), alpha, newFlag); + } +} + +void renderer::Group::applyTrim() +{ + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + switch (content->type()) { + case renderer::Object::Type::Trim: { + static_cast(content)->update(); + break; + } + case renderer::Object::Type::Group: { + static_cast(content)->applyTrim(); + break; + } + default: + break; + } + } +} + +void renderer::Group::renderList(std::vector &list) +{ + for (const auto &content : mContents) { + content->renderList(list); + } +} + +void renderer::Group::processPaintItems(std::vector &list) +{ + size_t curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + switch (content->type()) { + case renderer::Object::Type::Shape: { + auto pathItem = static_cast(content); + pathItem->setParent(this); + list.push_back(pathItem); + break; + } + case renderer::Object::Type::Paint: { + static_cast(content)->addPathItems(list, + curOpCount); + break; + } + case renderer::Object::Type::Group: { + static_cast(content)->processPaintItems(list); + break; + } + default: + break; + } + } +} + +void renderer::Group::processTrimItems(std::vector &list) +{ + size_t curOpCount = list.size(); + for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { + auto content = (*i); + + switch (content->type()) { + case renderer::Object::Type::Shape: { + list.push_back(static_cast(content)); + break; + } + case renderer::Object::Type::Trim: { + static_cast(content)->addPathItems(list, + curOpCount); + break; + } + case renderer::Object::Type::Group: { + static_cast(content)->processTrimItems(list); + break; + } + default: + break; + } + } +} + +/* + * renderer::Shape uses 2 path objects for path object reuse. + * mLocalPath - keeps track of the local path of the item before + * applying path operation and transformation. + * mTemp - keeps a referece to the mLocalPath and can be updated by the + * path operation objects(trim, merge path), + * We update the DirtyPath flag if the path needs to be updated again + * beacuse of local path or matrix or some path operation has changed which + * affects the final path. + * The PaintObject queries the dirty flag to check if it needs to compute the + * final path again and calls finalPath() api to do the same. + * finalPath() api passes a result Object so that we keep only one copy of + * the path object in the paintItem (for memory efficiency). + * NOTE: As path objects are COW objects we have to be + * carefull about the refcount so that we don't generate deep copy while + * modifying the path objects. + */ +void renderer::Shape::update(int frameNo, const VMatrix &, float, + const DirtyFlag &flag) +{ + mDirtyPath = false; + + // 1. update the local path if needed + if (hasChanged(frameNo)) { + // loose the reference to mLocalPath if any + // from the last frame update. + mTemp = VPath(); + + updatePath(mLocalPath, frameNo); + mDirtyPath = true; + } + // 2. keep a reference path in temp in case there is some + // path operation like trim which will update the path. + // we don't want to update the local path. + mTemp = mLocalPath; + + // 3. mark the path dirty if matrix has changed. + if (flag & DirtyFlagBit::Matrix) { + mDirtyPath = true; + } +} + +void renderer::Shape::finalPath(VPath &result) +{ + result.addPath(mTemp, static_cast(parent())->matrix()); +} + +renderer::Rect::Rect(model::Rect *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Rect::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + float roundness = mData->mRound.value(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addRoundRect(r, roundness, mData->direction()); +} + +renderer::Ellipse::Ellipse(model::Ellipse *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Ellipse::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + VPointF size = mData->mSize.value(frameNo); + VRectF r(pos.x() - size.x() / 2, pos.y() - size.y() / 2, size.x(), + size.y()); + + path.reset(); + path.addOval(r, mData->direction()); +} + +renderer::Path::Path(model::Path *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Path::updatePath(VPath &path, int frameNo) +{ + mData->mShape.value(frameNo, path); +} + +renderer::Polystar::Polystar(model::Polystar *data) + : renderer::Shape(data->isStatic()), mData(data) +{ +} + +void renderer::Polystar::updatePath(VPath &path, int frameNo) +{ + VPointF pos = mData->mPos.value(frameNo); + float points = mData->mPointCount.value(frameNo); + float innerRadius = mData->mInnerRadius.value(frameNo); + float outerRadius = mData->mOuterRadius.value(frameNo); + float innerRoundness = mData->mInnerRoundness.value(frameNo); + float outerRoundness = mData->mOuterRoundness.value(frameNo); + float rotation = mData->mRotation.value(frameNo); + + path.reset(); + VMatrix m; + + if (mData->mPolyType == model::Polystar::PolyType::Star) { + path.addPolystar(points, innerRadius, outerRadius, innerRoundness, + outerRoundness, 0.0, 0.0, 0.0, mData->direction()); + } else { + path.addPolygon(points, outerRadius, outerRoundness, 0.0, 0.0, 0.0, + mData->direction()); + } + + m.translate(pos.x(), pos.y()).rotate(rotation); + m.rotate(rotation); + path.transform(m); +} + +/* + * PaintData Node handling + * + */ +renderer::Paint::Paint(bool staticContent) : mStaticContent(staticContent) {} + +void renderer::Paint::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag & /*flag*/) +{ + mRenderNodeUpdate = true; + mContentToRender = updateContent(frameNo, parentMatrix, parentAlpha); +} + +void renderer::Paint::updateRenderNode() +{ + bool dirty = false; + for (auto &i : mPathItems) { + if (i->dirty()) { + dirty = true; + break; + } + } + + if (dirty) { + mPath.reset(); + for (const auto &i : mPathItems) { + i->finalPath(mPath); + } + mDrawable.setPath(mPath); + } else { + if (mDrawable.mFlag & VDrawable::DirtyState::Path) + mDrawable.mPath = mPath; + } +} + +void renderer::Paint::renderList(std::vector &list) +{ + if (mRenderNodeUpdate) { + updateRenderNode(); + mRenderNodeUpdate = false; + } + + // Q: Why we even update the final path if we don't have content + // to render ? + // Ans: We update the render nodes because we will loose the + // dirty path information at end of this frame. + // so if we return early without updating the final path. + // in the subsequent frame when we have content to render but + // we may not able to update our final path properly as we + // don't know what paths got changed in between. + if (mContentToRender) list.push_back(&mDrawable); +} + +void renderer::Paint::addPathItems(std::vector &list, + size_t startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +renderer::Fill::Fill(model::Fill *data) + : renderer::Paint(data->isStatic()), mModel(data) +{ + mDrawable.setName(mModel.name()); +} + +bool renderer::Fill::updateContent(int frameNo, const VMatrix &, float alpha) +{ + auto combinedAlpha = alpha * mModel.opacity(frameNo); + auto color = mModel.color(frameNo).toColor(combinedAlpha); + + VBrush brush(color); + mDrawable.setBrush(brush); + mDrawable.setFillRule(mModel.fillRule()); + + return !color.isTransparent(); +} + +renderer::GradientFill::GradientFill(model::GradientFill *data) + : renderer::Paint(data->isStatic()), mData(data) +{ + mDrawable.setName(mData->name()); +} + +bool renderer::GradientFill::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + float combinedAlpha = alpha * mData->opacity(frameNo); + + mData->update(mGradient, frameNo); + mGradient->setAlpha(combinedAlpha); + mGradient->mMatrix = matrix; + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setFillRule(mData->fillRule()); + + return !vIsZero(combinedAlpha); +} + +renderer::Stroke::Stroke(model::Stroke *data) + : renderer::Paint(data->isStatic()), mModel(data) +{ + mDrawable.setName(mModel.name()); + if (mModel.hasDashInfo()) { + mDrawable.setType(VDrawable::Type::StrokeWithDash); + } else { + mDrawable.setType(VDrawable::Type::Stroke); + } +} + +static vthread_local std::vector Dash_Vector; + +bool renderer::Stroke::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + auto combinedAlpha = alpha * mModel.opacity(frameNo); + auto color = mModel.color(frameNo).toColor(combinedAlpha); + + VBrush brush(color); + mDrawable.setBrush(brush); + float scale = matrix.scale(); + mDrawable.setStrokeInfo(mModel.capStyle(), mModel.joinStyle(), + mModel.miterLimit(), + mModel.strokeWidth(frameNo) * scale); + + if (mModel.hasDashInfo()) { + Dash_Vector.clear(); + mModel.getDashInfo(frameNo, Dash_Vector); + if (!Dash_Vector.empty()) { + for (auto &elm : Dash_Vector) elm *= scale; + mDrawable.setDashInfo(Dash_Vector); + } + } + + return !color.isTransparent(); +} + +renderer::GradientStroke::GradientStroke(model::GradientStroke *data) + : renderer::Paint(data->isStatic()), mData(data) +{ + mDrawable.setName(mData->name()); + if (mData->hasDashInfo()) { + mDrawable.setType(VDrawable::Type::StrokeWithDash); + } else { + mDrawable.setType(VDrawable::Type::Stroke); + } +} + +bool renderer::GradientStroke::updateContent(int frameNo, const VMatrix &matrix, + float alpha) +{ + float combinedAlpha = alpha * mData->opacity(frameNo); + + mData->update(mGradient, frameNo); + mGradient->setAlpha(combinedAlpha); + mGradient->mMatrix = matrix; + auto scale = mGradient->mMatrix.scale(); + mDrawable.setBrush(VBrush(mGradient.get())); + mDrawable.setStrokeInfo(mData->capStyle(), mData->joinStyle(), + mData->miterLimit(), mData->width(frameNo) * scale); + + if (mData->hasDashInfo()) { + Dash_Vector.clear(); + mData->getDashInfo(frameNo, Dash_Vector); + if (!Dash_Vector.empty()) { + for (auto &elm : Dash_Vector) elm *= scale; + mDrawable.setDashInfo(Dash_Vector); + } + } + + return !vIsZero(combinedAlpha); +} + +void renderer::Trim::update(int frameNo, const VMatrix & /*parentMatrix*/, + float /*parentAlpha*/, const DirtyFlag & /*flag*/) +{ + mDirty = false; + + if (mCache.mFrameNo == frameNo) return; + + model::Trim::Segment segment = mData->segment(frameNo); + + if (!(vCompare(mCache.mSegment.start, segment.start) && + vCompare(mCache.mSegment.end, segment.end))) { + mDirty = true; + mCache.mSegment = segment; + } + mCache.mFrameNo = frameNo; +} + +void renderer::Trim::update() +{ + // when both path and trim are not dirty + if (!(mDirty || pathDirty())) return; + + if (vCompare(mCache.mSegment.start, mCache.mSegment.end)) { + for (auto &i : mPathItems) { + i->updatePath(VPath()); + } + return; + } + + if (vCompare(std::fabs(mCache.mSegment.start - mCache.mSegment.end), 1)) { + for (auto &i : mPathItems) { + i->updatePath(i->localPath()); + } + return; + } + + if (mData->type() == model::Trim::TrimType::Simultaneously) { + for (auto &i : mPathItems) { + mPathMesure.setRange(mCache.mSegment.start, mCache.mSegment.end); + i->updatePath(mPathMesure.trim(i->localPath())); + } + } else { // model::Trim::TrimType::Individually + float totalLength = 0.0; + for (auto &i : mPathItems) { + totalLength += i->localPath().length(); + } + float start = totalLength * mCache.mSegment.start; + float end = totalLength * mCache.mSegment.end; + + if (start < end) { + float curLen = 0.0; + for (auto &i : mPathItems) { + if (curLen > end) { + // update with empty path. + i->updatePath(VPath()); + continue; + } + float len = i->localPath().length(); + + if (curLen < start && curLen + len < start) { + curLen += len; + // update with empty path. + i->updatePath(VPath()); + continue; + } else if (start <= curLen && end >= curLen + len) { + // inside segment + curLen += len; + continue; + } else { + float local_start = start > curLen ? start - curLen : 0; + local_start /= len; + float local_end = curLen + len < end ? len : end - curLen; + local_end /= len; + mPathMesure.setRange(local_start, local_end); + i->updatePath(mPathMesure.trim(i->localPath())); + curLen += len; + } + } + } + } +} + +void renderer::Trim::addPathItems(std::vector &list, + size_t startOffset) +{ + std::copy(list.begin() + startOffset, list.end(), + back_inserter(mPathItems)); +} + +renderer::Repeater::Repeater(model::Repeater *data, VArenaAlloc *allocator) + : mRepeaterData(data) +{ + assert(mRepeaterData->content()); + + mCopies = mRepeaterData->maxCopies(); + + for (int i = 0; i < mCopies; i++) { + auto content = allocator->make( + mRepeaterData->content(), allocator); + // content->setParent(this); + mContents.push_back(content); + } +} + +void renderer::Repeater::update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) +{ + DirtyFlag newFlag = flag; + + float copies = mRepeaterData->copies(frameNo); + int visibleCopies = int(copies); + + if (visibleCopies == 0) { + mHidden = true; + return; + } + + mHidden = false; + + if (!mRepeaterData->isStatic()) newFlag |= DirtyFlagBit::Matrix; + + float offset = mRepeaterData->offset(frameNo); + float startOpacity = mRepeaterData->mTransform.startOpacity(frameNo); + float endOpacity = mRepeaterData->mTransform.endOpacity(frameNo); + + newFlag |= DirtyFlagBit::Alpha; + + for (int i = 0; i < mCopies; ++i) { + float newAlpha = + parentAlpha * lerp(startOpacity, endOpacity, i / copies); + + // hide rest of the copies , @TODO find a better solution. + if (i >= visibleCopies) newAlpha = 0; + + VMatrix result = mRepeaterData->mTransform.matrix(frameNo, i + offset) * + parentMatrix; + mContents[i]->update(frameNo, result, newAlpha, newFlag); + } +} + +void renderer::Repeater::renderList(std::vector &list) +{ + if (mHidden) return; + return renderer::Group::renderList(list); +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.h new file mode 100644 index 0000000000..1fcb561b94 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem.h @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LOTTIEITEM_H +#define LOTTIEITEM_H + +#include +#include + +#include "lottiekeypath.h" +#include "lottiefiltermodel.h" +#include "rlottie.h" +#include "rlottiecommon.h" +#include "varenaalloc.h" +#include "vdrawable.h" +#include "vmatrix.h" +#include "vpainter.h" +#include "vpath.h" +#include "vpathmesure.h" +#include "vpoint.h" + +V_USE_NAMESPACE + +namespace rlottie { + +namespace internal { + +template +class VSpan { +public: + using reference = T &; + using pointer = T *; + using const_pointer = T const *; + using const_reference = T const &; + using index_type = size_t; + + using iterator = pointer; + using const_iterator = const_pointer; + + VSpan() = default; + VSpan(pointer data, index_type size) : _data(data), _size(size) {} + + constexpr pointer data() const noexcept { return _data; } + constexpr index_type size() const noexcept { return _size; } + constexpr bool empty() const noexcept { return size() == 0; } + constexpr iterator begin() const noexcept { return data(); } + constexpr iterator end() const noexcept { return data() + size(); } + constexpr const_iterator cbegin() const noexcept { return data(); } + constexpr const_iterator cend() const noexcept { return data() + size(); } + constexpr reference operator[](index_type idx) const + { + return *(data() + idx); + } + +private: + pointer _data{nullptr}; + index_type _size{0}; +}; + +namespace renderer { + +using DrawableList = VSpan; + +enum class DirtyFlagBit : uchar { + None = 0x00, + Matrix = 0x01, + Alpha = 0x02, + All = (Matrix | Alpha) +}; +typedef vFlag DirtyFlag; + +class SurfaceCache { +public: + SurfaceCache() { mCache.reserve(10); } + + VBitmap make_surface( + size_t width, size_t height, + VBitmap::Format format = VBitmap::Format::ARGB32_Premultiplied) + { + if (mCache.empty()) return {width, height, format}; + + auto surface = mCache.back(); + surface.reset(width, height, format); + + mCache.pop_back(); + return surface; + } + + void release_surface(VBitmap &surface) { mCache.push_back(surface); } + +private: + std::vector mCache; +}; + +class Drawable final : public VDrawable { +public: + void sync(); + +public: + std::unique_ptr mCNode{nullptr}; + + ~Drawable() noexcept + { + if (mCNode && mCNode->mGradient.stopPtr) + free(mCNode->mGradient.stopPtr); + } +}; + +struct CApiData { + CApiData(); + LOTLayerNode mLayer; + std::vector mMasks; + std::vector mLayers; + std::vector mCNodeList; +}; + +class Clipper { +public: + explicit Clipper(VSize size) : mSize(size) {} + void update(const VMatrix &matrix); + void preprocess(const VRect &clip); + VRle rle(const VRle &mask); + +public: + VSize mSize; + VPath mPath; + VRle mMaskedRle; + VRasterizer mRasterizer; + bool mRasterRequest{false}; +}; + +class Mask { +public: + explicit Mask(model::Mask *data) : mData(data) {} + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag); + model::Mask::Mode maskMode() const { return mData->mMode; } + VRle rle(); + void preprocess(const VRect &clip); + bool inverted() const { return mData->mInv; } +public: + model::Mask *mData{nullptr}; + VPath mLocalPath; + VPath mFinalPath; + VRasterizer mRasterizer; + float mCombinedAlpha{0}; + bool mRasterRequest{false}; +}; + +/* + * Handels mask property of a layer item + */ +class LayerMask { +public: + explicit LayerMask(model::Layer *layerData); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag); + bool isStatic() const { return mStatic; } + VRle maskRle(const VRect &clipRect); + void preprocess(const VRect &clip); + +public: + std::vector mMasks; + VRle mRle; + bool mStatic{true}; + bool mDirty{true}; +}; + +class Layer; + +class Composition { +public: + explicit Composition(std::shared_ptr composition); + bool update(int frameNo, const VSize &size, bool keepAspectRatio); + VSize size() const { return mViewSize; } + void buildRenderTree(); + const LOTLayerNode *renderTree() const; + bool render(const rlottie::Surface &surface); + void setValue(const std::string &keypath, LOTVariant &value); + +private: + SurfaceCache mSurfaceCache; + VBitmap mSurface; + VMatrix mScaleMatrix; + VSize mViewSize; + std::shared_ptr mModel; + Layer * mRootLayer{nullptr}; + VArenaAlloc mAllocator{2048}; + int mCurFrameNo; + bool mKeepAspectRatio{true}; +}; + +class Layer { +public: + virtual ~Layer() = default; + Layer &operator=(Layer &&) noexcept = delete; + Layer(model::Layer *layerData); + int id() const { return mLayerData->id(); } + int parentId() const { return mLayerData->parentId(); } + void setParentLayer(Layer *parent) { mParentLayer = parent; } + void setComplexContent(bool value) { mComplexContent = value; } + bool complexContent() const { return mComplexContent; } + virtual void update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha); + VMatrix matrix(int frameNo) const; + void preprocess(const VRect &clip); + virtual DrawableList renderList() { return {}; } + virtual void render(VPainter *painter, const VRle &mask, + const VRle &matteRle, SurfaceCache &cache); + bool hasMatte() + { + if (mLayerData->mMatteType == model::MatteType::None) return false; + return true; + } + model::MatteType matteType() const { return mLayerData->mMatteType; } + bool visible() const; + virtual void buildLayerNode(); + LOTLayerNode & clayer() { return mCApiData->mLayer; } + std::vector &clayers() { return mCApiData->mLayers; } + std::vector & cmasks() { return mCApiData->mMasks; } + std::vector & cnodes() { return mCApiData->mCNodeList; } + const char * name() const { return mLayerData->name(); } + virtual bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value); + +protected: + virtual void preprocessStage(const VRect &clip) = 0; + virtual void updateContent() = 0; + inline VMatrix combinedMatrix() const { return mCombinedMatrix; } + inline int frameNo() const { return mFrameNo; } + inline float combinedAlpha() const { return mCombinedAlpha; } + inline bool isStatic() const { return mLayerData->isStatic(); } + float opacity(int frameNo) const { return mLayerData->opacity(frameNo); } + inline DirtyFlag flag() const { return mDirtyFlag; } + bool skipRendering() const + { + return (!visible() || vIsZero(combinedAlpha())); + } + +protected: + std::unique_ptr mLayerMask; + model::Layer * mLayerData{nullptr}; + Layer * mParentLayer{nullptr}; + VMatrix mCombinedMatrix; + float mCombinedAlpha{0.0}; + int mFrameNo{-1}; + DirtyFlag mDirtyFlag{DirtyFlagBit::All}; + bool mComplexContent{false}; + std::unique_ptr mCApiData; +}; + +class CompLayer final : public Layer { +public: + explicit CompLayer(model::Layer *layerData, VArenaAlloc *allocator); + + void render(VPainter *painter, const VRle &mask, const VRle &matteRle, + SurfaceCache &cache) final; + void buildLayerNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) override; + +protected: + void preprocessStage(const VRect &clip) final; + void updateContent() final; + +private: + void renderHelper(VPainter *painter, const VRle &mask, const VRle &matteRle, + SurfaceCache &cache); + void renderMatteLayer(VPainter *painter, const VRle &inheritMask, + const VRle &matteRle, Layer *layer, Layer *src, + SurfaceCache &cache); + +private: + std::vector mLayers; + std::unique_ptr mClipper; +}; + +class SolidLayer final : public Layer { +public: + explicit SolidLayer(model::Layer *layerData); + void buildLayerNode() final; + DrawableList renderList() final; + +protected: + void preprocessStage(const VRect &clip) final; + void updateContent() final; + +private: + Drawable mRenderNode; + VPath mPath; + VDrawable *mDrawableList{nullptr}; // to work with the Span api +}; + +class Group; + +class ShapeLayer final : public Layer { +public: + explicit ShapeLayer(model::Layer *layerData, VArenaAlloc *allocator); + DrawableList renderList() final; + void buildLayerNode() final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) override; + +protected: + void preprocessStage(const VRect &clip) final; + void updateContent() final; + std::vector mDrawableList; + Group * mRoot{nullptr}; +}; + +class NullLayer final : public Layer { +public: + explicit NullLayer(model::Layer *layerData); + +protected: + void preprocessStage(const VRect &) final {} + void updateContent() final; +}; + +class ImageLayer final : public Layer { +public: + explicit ImageLayer(model::Layer *layerData); + void buildLayerNode() final; + DrawableList renderList() final; + +protected: + void preprocessStage(const VRect &clip) final; + void updateContent() final; + +private: + Drawable mRenderNode; + VTexture mTexture; + VPath mPath; + VDrawable *mDrawableList{nullptr}; // to work with the Span api +}; + +class Object { +public: + enum class Type : uchar { Unknown, Group, Shape, Paint, Trim }; + virtual ~Object() = default; + Object & operator=(Object &&) noexcept = delete; + virtual void update(int frameNo, const VMatrix &parentMatrix, + float parentAlpha, const DirtyFlag &flag) = 0; + virtual void renderList(std::vector &) {} + virtual bool resolveKeyPath(LOTKeyPath &, uint, LOTVariant &) + { + return false; + } + virtual Object::Type type() const { return Object::Type::Unknown; } +}; + +class Shape; +class Group : public Object { +public: + Group() = default; + explicit Group(model::Group *data, VArenaAlloc *allocator); + void addChildren(model::Group *data, VArenaAlloc *allocator); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag) override; + void applyTrim(); + void processTrimItems(std::vector &list); + void processPaintItems(std::vector &list); + void renderList(std::vector &list) override; + Object::Type type() const final { return Object::Type::Group; } + const VMatrix &matrix() const { return mMatrix; } + const char * name() const + { + static const char *TAG = "__"; + return mModel.hasModel() ? mModel.name() : TAG; + } + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) override; + +protected: + std::vector mContents; + VMatrix mMatrix; + +private: + model::Filter mModel; +}; + +class Shape : public Object { +public: + Shape(bool staticPath) : mStaticPath(staticPath) {} + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag) final; + Object::Type type() const final { return Object::Type::Shape; } + bool dirty() const { return mDirtyPath; } + const VPath &localPath() const { return mTemp; } + void finalPath(VPath &result); + void updatePath(const VPath &path) + { + mTemp = path; + mDirtyPath = true; + } + bool staticPath() const { return mStaticPath; } + void setParent(Group *parent) { mParent = parent; } + Group *parent() const { return mParent; } + +protected: + virtual void updatePath(VPath &path, int frameNo) = 0; + virtual bool hasChanged(int prevFrame, int curFrame) = 0; + +private: + bool hasChanged(int frameNo) + { + int prevFrame = mFrameNo; + mFrameNo = frameNo; + if (prevFrame == -1) return true; + if (mStaticPath || (prevFrame == frameNo)) return false; + return hasChanged(prevFrame, frameNo); + } + Group *mParent{nullptr}; + VPath mLocalPath; + VPath mTemp; + int mFrameNo{-1}; + bool mDirtyPath{true}; + bool mStaticPath; +}; + +class Rect final : public Shape { +public: + explicit Rect(model::Rect *data); + +protected: + void updatePath(VPath &path, int frameNo) final; + model::Rect *mData{nullptr}; + + bool hasChanged(int prevFrame, int curFrame) final + { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mSize.changed(prevFrame, curFrame) || + mData->mRound.changed(prevFrame, curFrame)); + } +}; + +class Ellipse final : public Shape { +public: + explicit Ellipse(model::Ellipse *data); + +private: + void updatePath(VPath &path, int frameNo) final; + model::Ellipse *mData{nullptr}; + bool hasChanged(int prevFrame, int curFrame) final + { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mSize.changed(prevFrame, curFrame)); + } +}; + +class Path final : public Shape { +public: + explicit Path(model::Path *data); + +private: + void updatePath(VPath &path, int frameNo) final; + model::Path *mData{nullptr}; + bool hasChanged(int prevFrame, int curFrame) final + { + return mData->mShape.changed(prevFrame, curFrame); + } +}; + +class Polystar final : public Shape { +public: + explicit Polystar(model::Polystar *data); + +private: + void updatePath(VPath &path, int frameNo) final; + model::Polystar *mData{nullptr}; + + bool hasChanged(int prevFrame, int curFrame) final + { + return (mData->mPos.changed(prevFrame, curFrame) || + mData->mPointCount.changed(prevFrame, curFrame) || + mData->mInnerRadius.changed(prevFrame, curFrame) || + mData->mOuterRadius.changed(prevFrame, curFrame) || + mData->mInnerRoundness.changed(prevFrame, curFrame) || + mData->mOuterRoundness.changed(prevFrame, curFrame) || + mData->mRotation.changed(prevFrame, curFrame)); + } +}; + +class Paint : public Object { +public: + Paint(bool staticContent); + void addPathItems(std::vector &list, size_t startOffset); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag) override; + void renderList(std::vector &list) final; + Object::Type type() const final { return Object::Type::Paint; } + +protected: + virtual bool updateContent(int frameNo, const VMatrix &matrix, + float alpha) = 0; + +private: + void updateRenderNode(); + +protected: + std::vector mPathItems; + Drawable mDrawable; + VPath mPath; + DirtyFlag mFlag; + bool mStaticContent; + bool mRenderNodeUpdate{true}; + bool mContentToRender{true}; +}; + +class Fill final : public Paint { +public: + explicit Fill(model::Fill *data); + +protected: + bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) final; + +private: + model::Filter mModel; +}; + +class GradientFill final : public Paint { +public: + explicit GradientFill(model::GradientFill *data); + +protected: + bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; + +private: + model::GradientFill * mData{nullptr}; + std::unique_ptr mGradient; +}; + +class Stroke : public Paint { +public: + explicit Stroke(model::Stroke *data); + +protected: + bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; + bool resolveKeyPath(LOTKeyPath &keyPath, uint depth, + LOTVariant &value) final; + +private: + model::Filter mModel; +}; + +class GradientStroke final : public Paint { +public: + explicit GradientStroke(model::GradientStroke *data); + +protected: + bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final; + +private: + model::GradientStroke * mData{nullptr}; + std::unique_ptr mGradient; +}; + +class Trim final : public Object { +public: + explicit Trim(model::Trim *data) : mData(data) {} + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag) final; + Object::Type type() const final { return Object::Type::Trim; } + void update(); + void addPathItems(std::vector &list, size_t startOffset); + +private: + bool pathDirty() const + { + for (auto &i : mPathItems) { + if (i->dirty()) return true; + } + return false; + } + struct Cache { + int mFrameNo{-1}; + model::Trim::Segment mSegment{}; + }; + Cache mCache; + std::vector mPathItems; + model::Trim * mData{nullptr}; + VPathMesure mPathMesure; + bool mDirty{true}; +}; + +class Repeater final : public Group { +public: + explicit Repeater(model::Repeater *data, VArenaAlloc *allocator); + void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, + const DirtyFlag &flag) final; + void renderList(std::vector &list) final; + +private: + model::Repeater *mRepeaterData{nullptr}; + bool mHidden{false}; + int mCopies{0}; +}; + +} // namespace renderer + +} // namespace internal + +} // namespace rlottie + +#endif // LOTTIEITEM_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem_capi.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem_capi.cpp new file mode 100644 index 0000000000..a4d8546f5d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieitem_capi.cpp @@ -0,0 +1,339 @@ +/* + * Implements LottieItem functions needed + * to support renderTree() api. + * Moving all those implementation to its own + * file make clear separation as well easy of + * maintenance. + */ + +#include "lottieitem.h" +#include "vdasher.h" + +using namespace rlottie::internal; + +renderer::CApiData::CApiData() +{ + mLayer.mMaskList.ptr = nullptr; + mLayer.mMaskList.size = 0; + mLayer.mLayerList.ptr = nullptr; + mLayer.mLayerList.size = 0; + mLayer.mNodeList.ptr = nullptr; + mLayer.mNodeList.size = 0; + mLayer.mMatte = MatteNone; + mLayer.mVisible = 0; + mLayer.mAlpha = 255; + mLayer.mClipPath.ptPtr = nullptr; + mLayer.mClipPath.elmPtr = nullptr; + mLayer.mClipPath.ptCount = 0; + mLayer.mClipPath.elmCount = 0; + mLayer.keypath = nullptr; +} + +void renderer::Composition::buildRenderTree() +{ + mRootLayer->buildLayerNode(); +} + +const LOTLayerNode *renderer::Composition::renderTree() const +{ + return &mRootLayer->clayer(); +} + +void renderer::CompLayer::buildLayerNode() +{ + renderer::Layer::buildLayerNode(); + if (mClipper) { + const auto &elm = mClipper->mPath.elements(); + const auto &pts = mClipper->mPath.points(); + auto ptPtr = reinterpret_cast(pts.data()); + auto elmPtr = reinterpret_cast(elm.data()); + clayer().mClipPath.ptPtr = ptPtr; + clayer().mClipPath.elmPtr = elmPtr; + clayer().mClipPath.ptCount = 2 * pts.size(); + clayer().mClipPath.elmCount = elm.size(); + } + if (mLayers.size() != clayers().size()) { + for (const auto &layer : mLayers) { + layer->buildLayerNode(); + clayers().push_back(&layer->clayer()); + } + clayer().mLayerList.ptr = clayers().data(); + clayer().mLayerList.size = clayers().size(); + } else { + for (const auto &layer : mLayers) { + layer->buildLayerNode(); + } + } +} + +void renderer::ShapeLayer::buildLayerNode() +{ + renderer::Layer::buildLayerNode(); + + auto renderlist = renderList(); + + cnodes().clear(); + for (auto &i : renderlist) { + auto lotDrawable = static_cast(i); + lotDrawable->sync(); + cnodes().push_back(lotDrawable->mCNode.get()); + } + clayer().mNodeList.ptr = cnodes().data(); + clayer().mNodeList.size = cnodes().size(); +} + +void renderer::Layer::buildLayerNode() +{ + if (!mCApiData) { + mCApiData = std::make_unique(); + clayer().keypath = name(); + } + if (complexContent()) clayer().mAlpha = uchar(combinedAlpha() * 255.f); + clayer().mVisible = visible(); + // update matte + if (hasMatte()) { + switch (mLayerData->mMatteType) { + case model::MatteType::Alpha: + clayer().mMatte = MatteAlpha; + break; + case model::MatteType::AlphaInv: + clayer().mMatte = MatteAlphaInv; + break; + case model::MatteType::Luma: + clayer().mMatte = MatteLuma; + break; + case model::MatteType::LumaInv: + clayer().mMatte = MatteLumaInv; + break; + default: + clayer().mMatte = MatteNone; + break; + } + } + if (mLayerMask) { + cmasks().clear(); + cmasks().resize(mLayerMask->mMasks.size()); + size_t i = 0; + for (const auto &mask : mLayerMask->mMasks) { + auto & cNode = cmasks()[i++]; + const auto &elm = mask.mFinalPath.elements(); + const auto &pts = mask.mFinalPath.points(); + auto ptPtr = reinterpret_cast(pts.data()); + auto elmPtr = reinterpret_cast(elm.data()); + cNode.mPath.ptPtr = ptPtr; + cNode.mPath.ptCount = pts.size(); + cNode.mPath.elmPtr = elmPtr; + cNode.mPath.elmCount = elm.size(); + cNode.mAlpha = uchar(mask.mCombinedAlpha * 255.0f); + switch (mask.maskMode()) { + case model::Mask::Mode::Add: + cNode.mMode = MaskAdd; + break; + case model::Mask::Mode::Substarct: + cNode.mMode = MaskSubstract; + break; + case model::Mask::Mode::Intersect: + cNode.mMode = MaskIntersect; + break; + case model::Mask::Mode::Difference: + cNode.mMode = MaskDifference; + break; + default: + cNode.mMode = MaskAdd; + break; + } + } + clayer().mMaskList.ptr = cmasks().data(); + clayer().mMaskList.size = cmasks().size(); + } +} + +void renderer::SolidLayer::buildLayerNode() +{ + renderer::Layer::buildLayerNode(); + + auto renderlist = renderList(); + + cnodes().clear(); + for (auto &i : renderlist) { + auto lotDrawable = static_cast(i); + lotDrawable->sync(); + cnodes().push_back(lotDrawable->mCNode.get()); + } + clayer().mNodeList.ptr = cnodes().data(); + clayer().mNodeList.size = cnodes().size(); +} + +void renderer::ImageLayer::buildLayerNode() +{ + renderer::Layer::buildLayerNode(); + + auto renderlist = renderList(); + + cnodes().clear(); + for (auto &i : renderlist) { + auto lotDrawable = static_cast(i); + lotDrawable->sync(); + + lotDrawable->mCNode->mImageInfo.data = + lotDrawable->mBrush.mTexture->mBitmap.data(); + lotDrawable->mCNode->mImageInfo.width = + int(lotDrawable->mBrush.mTexture->mBitmap.width()); + lotDrawable->mCNode->mImageInfo.height = + int(lotDrawable->mBrush.mTexture->mBitmap.height()); + + lotDrawable->mCNode->mImageInfo.mMatrix.m11 = combinedMatrix().m_11(); + lotDrawable->mCNode->mImageInfo.mMatrix.m12 = combinedMatrix().m_12(); + lotDrawable->mCNode->mImageInfo.mMatrix.m13 = combinedMatrix().m_13(); + + lotDrawable->mCNode->mImageInfo.mMatrix.m21 = combinedMatrix().m_21(); + lotDrawable->mCNode->mImageInfo.mMatrix.m22 = combinedMatrix().m_22(); + lotDrawable->mCNode->mImageInfo.mMatrix.m23 = combinedMatrix().m_23(); + + lotDrawable->mCNode->mImageInfo.mMatrix.m31 = combinedMatrix().m_tx(); + lotDrawable->mCNode->mImageInfo.mMatrix.m32 = combinedMatrix().m_ty(); + lotDrawable->mCNode->mImageInfo.mMatrix.m33 = combinedMatrix().m_33(); + + // Alpha calculation already combined. + lotDrawable->mCNode->mImageInfo.mAlpha = + uchar(lotDrawable->mBrush.mTexture->mAlpha); + + cnodes().push_back(lotDrawable->mCNode.get()); + } + clayer().mNodeList.ptr = cnodes().data(); + clayer().mNodeList.size = cnodes().size(); +} + +static void updateGStops(LOTNode *n, const VGradient *grad) +{ + if (grad->mStops.size() != n->mGradient.stopCount) { + if (n->mGradient.stopCount) free(n->mGradient.stopPtr); + n->mGradient.stopCount = grad->mStops.size(); + n->mGradient.stopPtr = (LOTGradientStop *)malloc( + n->mGradient.stopCount * sizeof(LOTGradientStop)); + } + + LOTGradientStop *ptr = n->mGradient.stopPtr; + for (const auto &i : grad->mStops) { + ptr->pos = i.first; + ptr->a = uchar(i.second.alpha() * grad->alpha()); + ptr->r = i.second.red(); + ptr->g = i.second.green(); + ptr->b = i.second.blue(); + ptr++; + } +} + +void renderer::Drawable::sync() +{ + if (!mCNode) { + mCNode = std::make_unique(); + mCNode->mGradient.stopPtr = nullptr; + mCNode->mGradient.stopCount = 0; + } + + mCNode->mFlag = ChangeFlagNone; + if (mFlag & DirtyState::None) return; + + if (mFlag & DirtyState::Path) { + applyDashOp(); + const std::vector &elm = mPath.elements(); + const std::vector & pts = mPath.points(); + const float *ptPtr = reinterpret_cast(pts.data()); + const char * elmPtr = reinterpret_cast(elm.data()); + mCNode->mPath.elmPtr = elmPtr; + mCNode->mPath.elmCount = elm.size(); + mCNode->mPath.ptPtr = ptPtr; + mCNode->mPath.ptCount = 2 * pts.size(); + mCNode->mFlag |= ChangeFlagPath; + mCNode->keypath = name(); + } + + if (mStrokeInfo) { + mCNode->mStroke.width = mStrokeInfo->width; + mCNode->mStroke.miterLimit = mStrokeInfo->miterLimit; + mCNode->mStroke.enable = 1; + + switch (mStrokeInfo->cap) { + case CapStyle::Flat: + mCNode->mStroke.cap = LOTCapStyle::CapFlat; + break; + case CapStyle::Square: + mCNode->mStroke.cap = LOTCapStyle::CapSquare; + break; + case CapStyle::Round: + mCNode->mStroke.cap = LOTCapStyle::CapRound; + break; + } + + switch (mStrokeInfo->join) { + case JoinStyle::Miter: + mCNode->mStroke.join = LOTJoinStyle::JoinMiter; + break; + case JoinStyle::Bevel: + mCNode->mStroke.join = LOTJoinStyle::JoinBevel; + break; + case JoinStyle::Round: + mCNode->mStroke.join = LOTJoinStyle::JoinRound; + break; + default: + mCNode->mStroke.join = LOTJoinStyle::JoinMiter; + break; + } + } else { + mCNode->mStroke.enable = 0; + } + + switch (mFillRule) { + case FillRule::EvenOdd: + mCNode->mFillRule = LOTFillRule::FillEvenOdd; + break; + default: + mCNode->mFillRule = LOTFillRule::FillWinding; + break; + } + + switch (mBrush.type()) { + case VBrush::Type::Solid: + mCNode->mBrushType = LOTBrushType::BrushSolid; + mCNode->mColor.r = mBrush.mColor.r; + mCNode->mColor.g = mBrush.mColor.g; + mCNode->mColor.b = mBrush.mColor.b; + mCNode->mColor.a = mBrush.mColor.a; + break; + case VBrush::Type::LinearGradient: { + mCNode->mBrushType = LOTBrushType::BrushGradient; + mCNode->mGradient.type = LOTGradientType::GradientLinear; + VPointF s = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->linear.x1, mBrush.mGradient->linear.y1}); + VPointF e = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->linear.x2, mBrush.mGradient->linear.y2}); + mCNode->mGradient.start.x = s.x(); + mCNode->mGradient.start.y = s.y(); + mCNode->mGradient.end.x = e.x(); + mCNode->mGradient.end.y = e.y(); + updateGStops(mCNode.get(), mBrush.mGradient); + break; + } + case VBrush::Type::RadialGradient: { + mCNode->mBrushType = LOTBrushType::BrushGradient; + mCNode->mGradient.type = LOTGradientType::GradientRadial; + VPointF c = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->radial.cx, mBrush.mGradient->radial.cy}); + VPointF f = mBrush.mGradient->mMatrix.map( + {mBrush.mGradient->radial.fx, mBrush.mGradient->radial.fy}); + mCNode->mGradient.center.x = c.x(); + mCNode->mGradient.center.y = c.y(); + mCNode->mGradient.focal.x = f.x(); + mCNode->mGradient.focal.y = f.y(); + + float scale = mBrush.mGradient->mMatrix.scale(); + mCNode->mGradient.cradius = mBrush.mGradient->radial.cradius * scale; + mCNode->mGradient.fradius = mBrush.mGradient->radial.fradius * scale; + updateGStops(mCNode.get(), mBrush.mGradient); + break; + } + default: + break; + } +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.cpp new file mode 100644 index 0000000000..e8b6c6aefc --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.cpp @@ -0,0 +1,86 @@ +#include "lottiekeypath.h" + +#include + +LOTKeyPath::LOTKeyPath(const std::string &keyPath) +{ + std::stringstream ss(keyPath); + std::string item; + + while (getline(ss, item, '.')) { + mKeys.push_back(item); + } +} + +bool LOTKeyPath::matches(const std::string &key, uint depth) +{ + if (skip(key)) { + // This is an object we programatically create. + return true; + } + if (depth > size()) { + return false; + } + if ((mKeys[depth] == key) || (mKeys[depth] == "*") || + (mKeys[depth] == "**")) { + return true; + } + return false; +} + +uint LOTKeyPath::nextDepth(const std::string key, uint depth) +{ + if (skip(key)) { + // If it's a container then we added programatically and it isn't a part + // of the keypath. + return depth; + } + if (mKeys[depth] != "**") { + // If it's not a globstar then it is part of the keypath. + return depth + 1; + } + if (depth == size()) { + // The last key is a globstar. + return depth; + } + if (mKeys[depth + 1] == key) { + // We are a globstar and the next key is our current key so consume + // both. + return depth + 2; + } + return depth; +} + +bool LOTKeyPath::fullyResolvesTo(const std::string key, uint depth) +{ + if (depth > mKeys.size()) { + return false; + } + + bool isLastDepth = (depth == size()); + + if (!isGlobstar(depth)) { + bool matches = (mKeys[depth] == key) || isGlob(depth); + return (isLastDepth || (depth == size() - 1 && endsWithGlobstar())) && + matches; + } + + bool isGlobstarButNextKeyMatches = !isLastDepth && mKeys[depth + 1] == key; + if (isGlobstarButNextKeyMatches) { + return depth == size() - 1 || + (depth == size() - 2 && endsWithGlobstar()); + } + + if (isLastDepth) { + return true; + } + + if (depth + 1 < size()) { + // We are a globstar but there is more than 1 key after the globstar we + // we can't fully match. + return false; + } + // Return whether the next key (which we now know is the last one) is the + // same as the current key. + return mKeys[depth + 1] == key; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.h new file mode 100644 index 0000000000..0b59b161f1 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiekeypath.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LOTTIEKEYPATH_H +#define LOTTIEKEYPATH_H + +#include +#include +#include "vglobal.h" + +class LOTKeyPath { +public: + LOTKeyPath(const std::string &keyPath); + bool matches(const std::string &key, uint depth); + uint nextDepth(const std::string key, uint depth); + bool fullyResolvesTo(const std::string key, uint depth); + + bool propagate(const std::string key, uint depth) + { + return skip(key) ? true : (depth < size()) || (mKeys[depth] == "**"); + } + bool skip(const std::string &key) const { return key == "__"; } + +private: + bool isGlobstar(uint depth) const { return mKeys[depth] == "**"; } + bool isGlob(uint depth) const { return mKeys[depth] == "*"; } + bool endsWithGlobstar() const { return mKeys.back() == "**"; } + size_t size() const { return mKeys.size() - 1; } + +private: + std::vector mKeys; +}; + +#endif // LOTTIEKEYPATH_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieloader.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieloader.cpp new file mode 100644 index 0000000000..8a52949e71 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieloader.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include "lottiemodel.h" + +using namespace rlottie::internal; + +#ifdef LOTTIE_CACHE_SUPPORT + +#include +#include + +class ModelCache { +public: + static ModelCache &instance() + { + static ModelCache singleton; + return singleton; + } + std::shared_ptr find(const std::string &key) + { + std::lock_guard guard(mMutex); + + if (!mcacheSize) return nullptr; + + auto search = mHash.find(key); + + return (search != mHash.end()) ? search->second : nullptr; + } + void add(const std::string &key, std::shared_ptr value) + { + std::lock_guard guard(mMutex); + + if (!mcacheSize) return; + + //@TODO just remove the 1st element + // not the best of LRU logic + if (mcacheSize == mHash.size()) mHash.erase(mHash.cbegin()); + + mHash[key] = std::move(value); + } + + void configureCacheSize(size_t cacheSize) + { + std::lock_guard guard(mMutex); + mcacheSize = cacheSize; + + if (!mcacheSize) mHash.clear(); + } + +private: + ModelCache() = default; + + std::unordered_map> mHash; + std::mutex mMutex; + size_t mcacheSize{10}; +}; + +#else + +class ModelCache { +public: + static ModelCache &instance() + { + static ModelCache singleton; + return singleton; + } + std::shared_ptr find(const std::string &) + { + return nullptr; + } + void add(const std::string &, std::shared_ptr) {} + void configureCacheSize(size_t) {} +}; + +#endif + +static std::string dirname(const std::string &path) +{ + const char *ptr = strrchr(path.c_str(), '/'); +#ifdef _WIN32 + if (ptr) ptr = strrchr(ptr + 1, '\\'); +#endif + int len = int(ptr + 1 - path.c_str()); // +1 to include '/' + return std::string(path, 0, len); +} + +void model::configureModelCacheSize(size_t cacheSize) +{ + ModelCache::instance().configureCacheSize(cacheSize); +} + +std::shared_ptr model::loadFromFile(const std::string &path, + bool cachePolicy) +{ + if (cachePolicy) { + auto obj = ModelCache::instance().find(path); + if (obj) return obj; + } + + std::ifstream f; + f.open(path); + + if (!f.is_open()) { + vCritical << "failed to open file = " << path.c_str(); + return {}; + } else { + std::string content; + + std::getline(f, content, '\0'); + f.close(); + + if (content.empty()) return {}; + + auto obj = internal::model::parse(const_cast(content.c_str()), + dirname(path)); + + if (obj && cachePolicy) ModelCache::instance().add(path, obj); + + return obj; + } +} + +std::shared_ptr model::loadFromData( + std::string jsonData, const std::string &key, std::string resourcePath, + bool cachePolicy) +{ + if (cachePolicy) { + auto obj = ModelCache::instance().find(key); + if (obj) return obj; + } + + auto obj = internal::model::parse(const_cast(jsonData.c_str()), + std::move(resourcePath)); + + if (obj && cachePolicy) ModelCache::instance().add(key, obj); + + return obj; +} + +std::shared_ptr model::loadFromData( + std::string jsonData, std::string resourcePath, model::ColorFilter filter) +{ + return internal::model::parse(const_cast(jsonData.c_str()), + std::move(resourcePath), std::move(filter)); +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.cpp new file mode 100644 index 0000000000..e2b48e64bd --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "lottiemodel.h" +#include +#include +#include +#include "vimageloader.h" +#include "vline.h" + +using namespace rlottie::internal; + +/* + * We process the iterator objects in the children list + * by iterating from back to front. when we find a repeater object + * we remove the objects from satrt till repeater object and then place + * under a new shape group object which we add it as children to the repeater + * object. + * Then we visit the childrens of the newly created shape group object to + * process the remaining repeater object(when children list contains more than + * one repeater). + * + */ +class LottieRepeaterProcesser { +public: + void visitChildren(model::Group *obj) + { + for (auto i = obj->mChildren.rbegin(); i != obj->mChildren.rend(); + ++i) { + auto child = (*i); + if (child->type() == model::Object::Type::Repeater) { + model::Repeater *repeater = + static_cast(child); + // check if this repeater is already processed + // can happen if the layer is an asset and referenced by + // multiple layer. + if (repeater->processed()) continue; + + repeater->markProcessed(); + + auto content = repeater->content(); + // 1. increment the reverse iterator to point to the + // object before the repeater + ++i; + // 2. move all the children till repater to the group + std::move(obj->mChildren.begin(), i.base(), + back_inserter(content->mChildren)); + // 3. erase the objects from the original children list + obj->mChildren.erase(obj->mChildren.begin(), i.base()); + + // 5. visit newly created group to process remaining repeater + // object. + visitChildren(content); + // 6. exit the loop as the current iterators are invalid + break; + } + visit(child); + } + } + + void visit(model::Object *obj) + { + switch (obj->type()) { + case model::Object::Type::Group: + case model::Object::Type::Layer: { + visitChildren(static_cast(obj)); + break; + } + default: + break; + } + } +}; + +class LottieUpdateStatVisitor { + model::Composition::Stats *stat; + +public: + explicit LottieUpdateStatVisitor(model::Composition::Stats *s) : stat(s) {} + void visitChildren(model::Group *obj) + { + for (const auto &child : obj->mChildren) { + if (child) visit(child); + } + } + void visitLayer(model::Layer *layer) + { + switch (layer->mLayerType) { + case model::Layer::Type::Precomp: + stat->precompLayerCount++; + break; + case model::Layer::Type::Null: + stat->nullLayerCount++; + break; + case model::Layer::Type::Shape: + stat->shapeLayerCount++; + break; + case model::Layer::Type::Solid: + stat->solidLayerCount++; + break; + case model::Layer::Type::Image: + stat->imageLayerCount++; + break; + default: + break; + } + visitChildren(layer); + } + void visit(model::Object *obj) + { + switch (obj->type()) { + case model::Object::Type::Layer: { + visitLayer(static_cast(obj)); + break; + } + case model::Object::Type::Repeater: { + visitChildren(static_cast(obj)->content()); + break; + } + case model::Object::Type::Group: { + visitChildren(static_cast(obj)); + break; + } + default: + break; + } + } +}; + +void model::Composition::processRepeaterObjects() +{ + LottieRepeaterProcesser visitor; + visitor.visit(mRootLayer); +} + +void model::Composition::updateStats() +{ + LottieUpdateStatVisitor visitor(&mStats); + visitor.visit(mRootLayer); +} + +VMatrix model::Repeater::Transform::matrix(int frameNo, float multiplier) const +{ + VPointF scale = mScale.value(frameNo) / 100.f; + scale.setX(std::pow(scale.x(), multiplier)); + scale.setY(std::pow(scale.y(), multiplier)); + VMatrix m; + m.translate(mPosition.value(frameNo) * multiplier) + .translate(mAnchor.value(frameNo)) + .scale(scale) + .rotate(mRotation.value(frameNo) * multiplier) + .translate(-mAnchor.value(frameNo)); + + return m; +} + +VMatrix model::Transform::Data::matrix(int frameNo, bool autoOrient) const +{ + VMatrix m; + VPointF position; + if (mExtra && mExtra->mSeparate) { + position.setX(mExtra->mSeparateX.value(frameNo)); + position.setY(mExtra->mSeparateY.value(frameNo)); + } else { + position = mPosition.value(frameNo); + } + + float angle = autoOrient ? mPosition.angle(frameNo) : 0; + if (mExtra && mExtra->m3DData) { + m.translate(position) + .rotate(mExtra->m3DRz.value(frameNo) + angle) + .rotate(mExtra->m3DRy.value(frameNo), VMatrix::Axis::Y) + .rotate(mExtra->m3DRx.value(frameNo), VMatrix::Axis::X) + .scale(mScale.value(frameNo) / 100.f) + .translate(-mAnchor.value(frameNo)); + } else { + m.translate(position) + .rotate(mRotation.value(frameNo) + angle) + .scale(mScale.value(frameNo) / 100.f) + .translate(-mAnchor.value(frameNo)); + } + return m; +} + +void model::Dash::getDashInfo(int frameNo, std::vector &result) const +{ + result.clear(); + + if (mData.empty()) return; + + if (result.capacity() < mData.size()) result.reserve(mData.size() + 1); + + for (const auto &elm : mData) result.push_back(elm.value(frameNo)); + + // if the size is even then we are missing last + // gap information which is same as the last dash value + // copy it from the last dash value. + // NOTE: last value is the offset and last-1 is the last dash value. + auto size = result.size(); + if ((size % 2) == 0) { + // copy offset value to end. + result.push_back(result.back()); + // copy dash value to gap. + result[size - 1] = result[size - 2]; + } +} + +/** + * Both the color stops and opacity stops are in the same array. + * There are {@link #colorPoints} colors sequentially as: + * [ + * ..., + * position, + * red, + * green, + * blue, + * ... + * ] + * + * The remainder of the array is the opacity stops sequentially as: + * [ + * ..., + * position, + * opacity, + * ... + * ] + */ +void model::Gradient::populate(VGradientStops &stops, int frameNo) +{ + model::Gradient::Data gradData = mGradient.value(frameNo); + auto size = gradData.mGradient.size(); + float * ptr = gradData.mGradient.data(); + int colorPoints = mColorPoints; + if (colorPoints == -1) { // for legacy bodymovin (ref: lottie-android) + colorPoints = int(size / 4); + } + auto opacityArraySize = size - colorPoints * 4; + float *opacityPtr = ptr + (colorPoints * 4); + stops.clear(); + size_t j = 0; + for (int i = 0; i < colorPoints; i++) { + float colorStop = ptr[0]; + model::Color color = model::Color(ptr[1], ptr[2], ptr[3]); + if (opacityArraySize) { + if (j == opacityArraySize) { + // already reached the end + float stop1 = opacityPtr[j - 4]; + float op1 = opacityPtr[j - 3]; + float stop2 = opacityPtr[j - 2]; + float op2 = opacityPtr[j - 1]; + if (colorStop > stop2) { + stops.push_back( + std::make_pair(colorStop, color.toColor(op2))); + } else { + float progress = (colorStop - stop1) / (stop2 - stop1); + float opacity = op1 + progress * (op2 - op1); + stops.push_back( + std::make_pair(colorStop, color.toColor(opacity))); + } + continue; + } + for (; j < opacityArraySize; j += 2) { + float opacityStop = opacityPtr[j]; + if (opacityStop < colorStop) { + // add a color using opacity stop + stops.push_back(std::make_pair( + opacityStop, color.toColor(opacityPtr[j + 1]))); + continue; + } + // add a color using color stop + if (j == 0) { + stops.push_back(std::make_pair( + colorStop, color.toColor(opacityPtr[j + 1]))); + } else { + float progress = (colorStop - opacityPtr[j - 2]) / + (opacityPtr[j] - opacityPtr[j - 2]); + float opacity = + opacityPtr[j - 1] + + progress * (opacityPtr[j + 1] - opacityPtr[j - 1]); + stops.push_back( + std::make_pair(colorStop, color.toColor(opacity))); + } + j += 2; + break; + } + } else { + stops.push_back(std::make_pair(colorStop, color.toColor())); + } + ptr += 4; + } +} + +void model::Gradient::update(std::unique_ptr &grad, int frameNo) +{ + bool init = false; + if (!grad) { + if (mGradientType == 1) + grad = std::make_unique(VGradient::Type::Linear); + else + grad = std::make_unique(VGradient::Type::Radial); + grad->mSpread = VGradient::Spread::Pad; + init = true; + } + + if (!mGradient.isStatic() || init) { + populate(grad->mStops, frameNo); + } + + if (mGradientType == 1) { // linear gradient + VPointF start = mStartPoint.value(frameNo); + VPointF end = mEndPoint.value(frameNo); + grad->linear.x1 = start.x(); + grad->linear.y1 = start.y(); + grad->linear.x2 = end.x(); + grad->linear.y2 = end.y(); + } else { // radial gradient + VPointF start = mStartPoint.value(frameNo); + VPointF end = mEndPoint.value(frameNo); + grad->radial.cx = start.x(); + grad->radial.cy = start.y(); + grad->radial.cradius = + VLine::length(start.x(), start.y(), end.x(), end.y()); + /* + * Focal point is the point lives in highlight length distance from + * center along the line (start, end) and rotated by highlight angle. + * below calculation first finds the quadrant(angle) on which the point + * lives by applying inverse slope formula then adds the rotation angle + * to find the final angle. then point is retrived using circle equation + * of center, angle and distance. + */ + float progress = mHighlightLength.value(frameNo) / 100.0f; + if (vCompare(progress, 1.0f)) progress = 0.99f; + float startAngle = VLine(start, end).angle(); + float highlightAngle = mHighlightAngle.value(frameNo); + static constexpr float K_PI = 3.1415926f; + float angle = (startAngle + highlightAngle) * (K_PI / 180.0f); + grad->radial.fx = + grad->radial.cx + std::cos(angle) * progress * grad->radial.cradius; + grad->radial.fy = + grad->radial.cy + std::sin(angle) * progress * grad->radial.cradius; + // Lottie dosen't have any focal radius concept. + grad->radial.fradius = 0; + } +} + +void model::Asset::loadImageData(std::string data) +{ + if (!data.empty()) + mBitmap = VImageLoader::instance().load(data.c_str(), data.length()); +} + +void model::Asset::loadImagePath(std::string path) +{ + if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str()); +} + +std::vector model::Composition::layerInfoList() const +{ + if (!mRootLayer || mRootLayer->mChildren.empty()) return {}; + + std::vector result; + + result.reserve(mRootLayer->mChildren.size()); + + for (auto it : mRootLayer->mChildren) { + auto layer = static_cast(it); + result.emplace_back(layer->name(), layer->mInFrame, layer->mOutFrame); + } + + return result; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.h new file mode 100644 index 0000000000..8513414762 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottiemodel.h @@ -0,0 +1,1121 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LOTModel_H +#define LOTModel_H + +#include +#include +#include +#include +#include +#include +#include +#include "varenaalloc.h" +#include "vbezier.h" +#include "vbrush.h" +#include "vinterpolator.h" +#include "vmatrix.h" +#include "vpath.h" +#include "vpoint.h" +#include "vrect.h" + +V_USE_NAMESPACE + +namespace rlottie { + +namespace internal { + +using Marker = std::tuple; + +using LayerInfo = Marker; + +template +inline T lerp(const T &start, const T &end, float t) +{ + return start + t * (end - start); +} + +namespace model { + +enum class MatteType : uchar { None = 0, Alpha = 1, AlphaInv, Luma, LumaInv }; + +enum class BlendMode : uchar { + Normal = 0, + Multiply = 1, + Screen = 2, + OverLay = 3 +}; + +class Color { +public: + Color() = default; + Color(float red, float green, float blue) : r(red), g(green), b(blue) {} + VColor toColor(float a = 1) + { + return VColor(uchar(255 * r), uchar(255 * g), uchar(255 * b), + uchar(255 * a)); + } + friend inline Color operator+(const Color &c1, const Color &c2); + friend inline Color operator-(const Color &c1, const Color &c2); + +public: + float r{1}; + float g{1}; + float b{1}; +}; + +inline Color operator-(const Color &c1, const Color &c2) +{ + return Color(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b); +} +inline Color operator+(const Color &c1, const Color &c2) +{ + return Color(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b); +} + +inline const Color operator*(const Color &c, float m) +{ + return Color(c.r * m, c.g * m, c.b * m); +} + +inline const Color operator*(float m, const Color &c) +{ + return Color(c.r * m, c.g * m, c.b * m); +} + +struct PathData { + std::vector mPoints; + bool mClosed = false; /* "c" */ + void reserve(size_t size) { mPoints.reserve(mPoints.size() + size); } + static void lerp(const PathData &start, const PathData &end, float t, + VPath &result) + { + result.reset(); + auto size = std::min(start.mPoints.size(), end.mPoints.size()); + /* reserve exact memory requirement at once + * ptSize = size + 1(size + close) + * elmSize = size/3 cubic + 1 move + 1 close + */ + result.reserve(size + 1, size / 3 + 2); + result.moveTo(start.mPoints[0] + + t * (end.mPoints[0] - start.mPoints[0])); + for (size_t i = 1; i < size; i += 3) { + result.cubicTo( + start.mPoints[i] + t * (end.mPoints[i] - start.mPoints[i]), + start.mPoints[i + 1] + + t * (end.mPoints[i + 1] - start.mPoints[i + 1]), + start.mPoints[i + 2] + + t * (end.mPoints[i + 2] - start.mPoints[i + 2])); + } + if (start.mClosed) result.close(); + } + void toPath(VPath &path) const + { + path.reset(); + + if (mPoints.empty()) return; + + auto size = mPoints.size(); + auto points = mPoints.data(); + /* reserve exact memory requirement at once + * ptSize = size + 1(size + close) + * elmSize = size/3 cubic + 1 move + 1 close + */ + path.reserve(size + 1, size / 3 + 2); + path.moveTo(points[0]); + for (size_t i = 1; i < size; i += 3) { + path.cubicTo(points[i], points[i + 1], points[i + 2]); + } + if (mClosed) path.close(); + } +}; + +template +struct Value { + T start_; + T end_; + T at(float t) const { return lerp(start_, end_, t); } + float angle(float) const { return 0; } + void cache() {} +}; + +struct Position; + +template +struct Value { + T start_; + T end_; + T inTangent_; + T outTangent_; + float length_{0}; + bool hasTangent_{false}; + + void cache() + { + if (hasTangent_) { + inTangent_ = end_ + inTangent_; + outTangent_ = start_ + outTangent_; + length_ = VBezier::fromPoints(start_, outTangent_, inTangent_, end_) + .length(); + if (vIsZero(length_)) { + // this segment has zero length. + // so disable expensive path computaion. + hasTangent_ = false; + } + } + } + + T at(float t) const + { + if (hasTangent_) { + /* + * position along the path calcualated + * using bezier at progress length (t * bezlen) + */ + VBezier b = + VBezier::fromPoints(start_, outTangent_, inTangent_, end_); + return b.pointAt(b.tAtLength(t * length_, length_)); + } + return lerp(start_, end_, t); + } + + float angle(float t) const + { + if (hasTangent_) { + VBezier b = + VBezier::fromPoints(start_, outTangent_, inTangent_, end_); + return b.angleAt(b.tAtLength(t * length_, length_)); + } + return 0; + } +}; + +template +class KeyFrames { +public: + struct Frame { + float progress(int frameNo) const + { + return interpolator_ ? interpolator_->value((frameNo - start_) / + (end_ - start_)) + : 0; + } + T value(int frameNo) const { return value_.at(progress(frameNo)); } + float angle(int frameNo) const + { + return value_.angle(progress(frameNo)); + } + + float start_{0}; + float end_{0}; + VInterpolator *interpolator_{nullptr}; + Value value_; + }; + + T value(int frameNo) const + { + if (frames_.front().start_ >= frameNo) + return frames_.front().value_.start_; + if (frames_.back().end_ <= frameNo) return frames_.back().value_.end_; + + for (const auto &keyFrame : frames_) { + if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) + return keyFrame.value(frameNo); + } + return {}; + } + + float angle(int frameNo) const + { + if ((frames_.front().start_ >= frameNo) || + (frames_.back().end_ <= frameNo)) + return 0; + + for (const auto &frame : frames_) { + if (frameNo >= frame.start_ && frameNo < frame.end_) + return frame.angle(frameNo); + } + return 0; + } + + bool changed(int prevFrame, int curFrame) const + { + auto first = frames_.front().start_; + auto last = frames_.back().end_; + + return !((first > prevFrame && first > curFrame) || + (last < prevFrame && last < curFrame)); + } + void cache() + { + for (auto &e : frames_) e.value_.cache(); + } + +public: + std::vector frames_; +}; + +template +class Property { +public: + using Animation = KeyFrames; + + Property() { construct(impl_.value_, {}); } + explicit Property(T value) { construct(impl_.value_, std::move(value)); } + + const Animation &animation() const { return *(impl_.animation_.get()); } + const T & value() const { return impl_.value_; } + + Animation &animation() + { + if (isValue_) { + destroy(); + construct(impl_.animation_, std::make_unique()); + isValue_ = false; + } + return *(impl_.animation_.get()); + } + + T &value() + { + assert(isValue_); + return impl_.value_; + } + + Property(Property &&other) noexcept + { + if (!other.isValue_) { + construct(impl_.animation_, std::move(other.impl_.animation_)); + isValue_ = false; + } else { + construct(impl_.value_, std::move(other.impl_.value_)); + isValue_ = true; + } + } + // delete special member functions + Property(const Property &) = delete; + Property &operator=(const Property &) = delete; + Property &operator=(Property &&) = delete; + + ~Property() { destroy(); } + + bool isStatic() const { return isValue_; } + + T value(int frameNo) const + { + return isStatic() ? value() : animation().value(frameNo); + } + + // special function only for type T=PathData + template + auto value(int frameNo, VPath &path) const -> + typename std::enable_if_t::value, void> + { + if (isStatic()) { + value().toPath(path); + } else { + const auto &vec = animation().frames_; + if (vec.front().start_ >= frameNo) + return vec.front().value_.start_.toPath(path); + if (vec.back().end_ <= frameNo) + return vec.back().value_.end_.toPath(path); + + for (const auto &keyFrame : vec) { + if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) { + T::lerp(keyFrame.value_.start_, keyFrame.value_.end_, + keyFrame.progress(frameNo), path); + } + } + } + } + + float angle(int frameNo) const + { + return isStatic() ? 0 : animation().angle(frameNo); + } + + bool changed(int prevFrame, int curFrame) const + { + return isStatic() ? false : animation().changed(prevFrame, curFrame); + } + void cache() + { + if (!isStatic()) animation().cache(); + } + +private: + template + void construct(Tp &member, Tp &&val) + { + new (&member) Tp(std::move(val)); + } + + void destroy() + { + if (isValue_) { + impl_.value_.~T(); + } else { + using std::unique_ptr; + impl_.animation_.~unique_ptr(); + } + } + union details { + std::unique_ptr animation_; + T value_; + details(){}; + details(const details &) = delete; + details(details &&) = delete; + details &operator=(details &&) = delete; + details &operator=(const details &) = delete; + ~details() noexcept {}; + } impl_; + bool isValue_{true}; +}; + +class Path; +struct PathData; +struct Dash { + std::vector> mData; + bool empty() const { return mData.empty(); } + size_t size() const { return mData.size(); } + bool isStatic() const + { + for (const auto &elm : mData) + if (!elm.isStatic()) return false; + return true; + } + void getDashInfo(int frameNo, std::vector &result) const; +}; + +class Mask { +public: + enum class Mode { None, Add, Substarct, Intersect, Difference }; + float opacity(int frameNo) const + { + return mOpacity.value(frameNo) / 100.0f; + } + bool isStatic() const { return mIsStatic; } + +public: + Property mShape; + Property mOpacity{100}; + bool mInv{false}; + bool mIsStatic{true}; + Mask::Mode mMode; +}; + +class Object { +public: + enum class Type : unsigned char { + Composition = 1, + Layer, + Group, + Transform, + Fill, + Stroke, + GFill, + GStroke, + Rect, + Ellipse, + Path, + Polystar, + Trim, + Repeater + }; + + explicit Object(Object::Type type) : mPtr(nullptr) + { + mData._type = type; + mData._static = true; + mData._shortString = true; + mData._hidden = false; + } + ~Object() noexcept + { + if (!shortString() && mPtr) free(mPtr); + } + Object(const Object &) = delete; + Object &operator=(const Object &) = delete; + + void setStatic(bool value) { mData._static = value; } + bool isStatic() const { return mData._static; } + bool hidden() const { return mData._hidden; } + void setHidden(bool value) { mData._hidden = value; } + void setType(Object::Type type) { mData._type = type; } + Object::Type type() const { return mData._type; } + void setName(const char *name) + { + if (name) { + auto len = strlen(name); + if (len < maxShortStringLength) { + setShortString(true); + strncpy(mData._buffer, name, len + 1); + } else { + setShortString(false); + mPtr = strdup(name); + } + } + } + const char *name() const { return shortString() ? mData._buffer : mPtr; } + +private: + static constexpr unsigned char maxShortStringLength = 14; + void setShortString(bool value) { mData._shortString = value; } + bool shortString() const { return mData._shortString; } + struct Data { + char _buffer[maxShortStringLength]; + Object::Type _type; + bool _static : 1; + bool _hidden : 1; + bool _shortString : 1; + }; + union { + Data mData; + char *mPtr{nullptr}; + }; +}; + +struct Asset { + enum class Type : unsigned char { Precomp, Image, Char }; + bool isStatic() const { return mStatic; } + void setStatic(bool value) { mStatic = value; } + VBitmap bitmap() const { return mBitmap; } + void loadImageData(std::string data); + void loadImagePath(std::string Path); + Type mAssetType{Type::Precomp}; + bool mStatic{true}; + std::string mRefId; // ref id + std::vector mLayers; + // image asset data + int mWidth{0}; + int mHeight{0}; + VBitmap mBitmap; +}; + +class Layer; + +class Composition : public Object { +public: + Composition() : Object(Object::Type::Composition) {} + std::vector layerInfoList() const; + const std::vector &markers() const { return mMarkers; } + double duration() const + { + return frameDuration() / frameRate(); // in second + } + size_t frameAtPos(double pos) const + { + if (pos < 0) pos = 0; + if (pos > 1) pos = 1; + return size_t(round(pos * frameDuration())); + } + long frameAtTime(double timeInSec) const + { + return long(frameAtPos(timeInSec / duration())); + } + size_t totalFrame() const { return mEndFrame - mStartFrame; } + long frameDuration() const { return mEndFrame - mStartFrame - 1; } + float frameRate() const { return mFrameRate; } + size_t startFrame() const { return mStartFrame; } + size_t endFrame() const { return mEndFrame; } + VSize size() const { return mSize; } + void processRepeaterObjects(); + void updateStats(); + +public: + struct Stats { + uint16_t precompLayerCount{0}; + uint16_t solidLayerCount{0}; + uint16_t shapeLayerCount{0}; + uint16_t imageLayerCount{0}; + uint16_t nullLayerCount{0}; + }; + +public: + std::string mVersion; + VSize mSize; + long mStartFrame{0}; + long mEndFrame{0}; + float mFrameRate{60}; + BlendMode mBlendMode{BlendMode::Normal}; + Layer * mRootLayer{nullptr}; + std::unordered_map mAssets; + + std::vector mMarkers; + VArenaAlloc mArenaAlloc{2048}; + Stats mStats; +}; + +class Transform : public Object { +public: + struct Data { + struct Extra { + Property m3DRx{0}; + Property m3DRy{0}; + Property m3DRz{0}; + Property mSeparateX{0}; + Property mSeparateY{0}; + bool mSeparate{false}; + bool m3DData{false}; + }; + VMatrix matrix(int frameNo, bool autoOrient = false) const; + float opacity(int frameNo) const + { + return mOpacity.value(frameNo) / 100.0f; + } + void createExtraData() + { + if (!mExtra) mExtra = std::make_unique(); + } + Property mRotation{0}; /* "r" */ + Property mScale{{100, 100}}; /* "s" */ + Property mPosition; /* "p" */ + Property mAnchor; /* "a" */ + Property mOpacity{100}; /* "o" */ + std::unique_ptr mExtra; + }; + + Transform() : Object(Object::Type::Transform) {} + void set(Transform::Data *data, bool staticFlag) + { + setStatic(staticFlag); + if (isStatic()) { + new (&impl.mStaticData) + StaticData(data->matrix(0), data->opacity(0)); + } else { + impl.mData = data; + } + } + VMatrix matrix(int frameNo, bool autoOrient = false) const + { + if (isStatic()) return impl.mStaticData.mMatrix; + return impl.mData->matrix(frameNo, autoOrient); + } + float opacity(int frameNo) const + { + if (isStatic()) return impl.mStaticData.mOpacity; + return impl.mData->opacity(frameNo); + } + Transform(const Transform &) = delete; + Transform(Transform &&) = delete; + Transform &operator=(Transform &) = delete; + Transform &operator=(Transform &&) = delete; + ~Transform() noexcept { destroy(); } + +private: + void destroy() + { + if (isStatic()) { + impl.mStaticData.~StaticData(); + } + } + struct StaticData { + StaticData(VMatrix &&m, float opacity) + : mOpacity(opacity), mMatrix(std::move(m)) + { + } + float mOpacity; + VMatrix mMatrix; + }; + union details { + Data * mData{nullptr}; + StaticData mStaticData; + details(){}; + details(const details &) = delete; + details(details &&) = delete; + details &operator=(details &&) = delete; + details &operator=(const details &) = delete; + ~details() noexcept {}; + } impl; +}; + +class Group : public Object { +public: + Group() : Object(Object::Type::Group) {} + explicit Group(Object::Type type) : Object(type) {} + +public: + std::vector mChildren; + Transform * mTransform{nullptr}; +}; + +class Layer : public Group { +public: + enum class Type : uchar { + Precomp = 0, + Solid = 1, + Image = 2, + Null = 3, + Shape = 4, + Text = 5 + }; + Layer() : Group(Object::Type::Layer) {} + bool hasPathOperator() const noexcept { return mHasPathOperator; } + bool hasGradient() const noexcept { return mHasGradient; } + bool hasMask() const noexcept { return mHasMask; } + bool hasRepeater() const noexcept { return mHasRepeater; } + int id() const noexcept { return mId; } + int parentId() const noexcept { return mParentId; } + bool hasParent() const noexcept { return mParentId != -1; } + int inFrame() const noexcept { return mInFrame; } + int outFrame() const noexcept { return mOutFrame; } + int startFrame() const noexcept { return mStartFrame; } + Color solidColor() const noexcept { return mExtra->mSolidColor; } + bool autoOrient() const noexcept { return mAutoOrient; } + int timeRemap(int frameNo) const; + VSize layerSize() const { return mLayerSize; } + bool precompLayer() const { return mLayerType == Type::Precomp; } + VMatrix matrix(int frameNo) const + { + return mTransform ? mTransform->matrix(frameNo, autoOrient()) + : VMatrix{}; + } + float opacity(int frameNo) const + { + return mTransform ? mTransform->opacity(frameNo) : 1.0f; + } + Asset *asset() const + { + return (mExtra && mExtra->mAsset) ? mExtra->mAsset : nullptr; + } + struct Extra { + Color mSolidColor; + std::string mPreCompRefId; + Property mTimeRemap; /* "tm" */ + Composition * mCompRef{nullptr}; + Asset * mAsset{nullptr}; + std::vector mMasks; + }; + + Layer::Extra *extra() + { + if (!mExtra) mExtra = std::make_unique(); + return mExtra.get(); + } + +public: + MatteType mMatteType{MatteType::None}; + Type mLayerType{Layer::Type::Null}; + BlendMode mBlendMode{BlendMode::Normal}; + bool mHasPathOperator{false}; + bool mHasMask{false}; + bool mHasRepeater{false}; + bool mHasGradient{false}; + bool mAutoOrient{false}; + VSize mLayerSize; + int mParentId{-1}; // Lottie the id of the parent in the composition + int mId{-1}; // Lottie the group id used for parenting. + float mTimeStreatch{1.0f}; + int mInFrame{0}; + int mOutFrame{0}; + int mStartFrame{0}; + std::unique_ptr mExtra{nullptr}; +}; + +/** + * TimeRemap has the value in time domain(in sec) + * To get the proper mapping first we get the mapped time at the current frame + * Number then we need to convert mapped time to frame number using the + * composition time line Ex: at frame 10 the mappend time is 0.5(500 ms) which + * will be convert to frame number 30 if the frame rate is 60. or will result to + * frame number 15 if the frame rate is 30. + */ +inline int Layer::timeRemap(int frameNo) const +{ + /* + * only consider startFrame() when there is no timeRemap. + * when a layer has timeremap bodymovin updates the startFrame() + * of all child layer so we don't have to take care of it. + */ + if (!mExtra || mExtra->mTimeRemap.isStatic()) + frameNo = frameNo - startFrame(); + else + frameNo = + mExtra->mCompRef->frameAtTime(mExtra->mTimeRemap.value(frameNo)); + /* Apply time streatch if it has any. + * Time streatch is just a factor by which the animation will speedup or + * slow down with respect to the overal animation. Time streach factor is + * already applied to the layers inFrame and outFrame. + * @TODO need to find out if timestreatch also affects the in and out frame + * of the child layers or not. */ + return int(frameNo / mTimeStreatch); +} + +class Stroke : public Object { +public: + Stroke() : Object(Object::Type::Stroke) {} + Color color(int frameNo) const { return mColor.value(frameNo); } + float opacity(int frameNo) const + { + return mOpacity.value(frameNo) / 100.0f; + } + float strokeWidth(int frameNo) const { return mWidth.value(frameNo); } + CapStyle capStyle() const { return mCapStyle; } + JoinStyle joinStyle() const { return mJoinStyle; } + float miterLimit() const { return mMiterLimit; } + bool hasDashInfo() const { return !mDash.empty(); } + void getDashInfo(int frameNo, std::vector &result) const + { + return mDash.getDashInfo(frameNo, result); + } + +public: + Property mColor; /* "c" */ + Property mOpacity{100}; /* "o" */ + Property mWidth{0}; /* "w" */ + CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ + JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ + float mMiterLimit{0}; /* "ml" */ + Dash mDash; + bool mEnabled{true}; /* "fillEnabled" */ +}; + +class Gradient : public Object { +public: + class Data { + public: + friend inline Gradient::Data operator+(const Gradient::Data &g1, + const Gradient::Data &g2); + friend inline Gradient::Data operator-(const Gradient::Data &g1, + const Gradient::Data &g2); + friend inline Gradient::Data operator*(float m, + const Gradient::Data &g); + + public: + std::vector mGradient; + }; + explicit Gradient(Object::Type type) : Object(type) {} + inline float opacity(int frameNo) const + { + return mOpacity.value(frameNo) / 100.0f; + } + void update(std::unique_ptr &grad, int frameNo); + +private: + void populate(VGradientStops &stops, int frameNo); + +public: + int mGradientType{1}; /* "t" Linear=1 , Radial = 2*/ + Property mStartPoint; /* "s" */ + Property mEndPoint; /* "e" */ + Property mHighlightLength{0}; /* "h" */ + Property mHighlightAngle{0}; /* "a" */ + Property mOpacity{100}; /* "o" */ + Property mGradient; /* "g" */ + int mColorPoints{-1}; + bool mEnabled{true}; /* "fillEnabled" */ +}; + +class GradientStroke : public Gradient { +public: + GradientStroke() : Gradient(Object::Type::GStroke) {} + float width(int frameNo) const { return mWidth.value(frameNo); } + CapStyle capStyle() const { return mCapStyle; } + JoinStyle joinStyle() const { return mJoinStyle; } + float miterLimit() const { return mMiterLimit; } + bool hasDashInfo() const { return !mDash.empty(); } + void getDashInfo(int frameNo, std::vector &result) const + { + return mDash.getDashInfo(frameNo, result); + } + +public: + Property mWidth; /* "w" */ + CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ + JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ + float mMiterLimit{0}; /* "ml" */ + Dash mDash; +}; + +class GradientFill : public Gradient { +public: + GradientFill() : Gradient(Object::Type::GFill) {} + FillRule fillRule() const { return mFillRule; } + +public: + FillRule mFillRule{FillRule::Winding}; /* "r" */ +}; + +class Fill : public Object { +public: + Fill() : Object(Object::Type::Fill) {} + Color color(int frameNo) const { return mColor.value(frameNo); } + float opacity(int frameNo) const + { + return mOpacity.value(frameNo) / 100.0f; + } + FillRule fillRule() const { return mFillRule; } + +public: + FillRule mFillRule{FillRule::Winding}; /* "r" */ + bool mEnabled{true}; /* "fillEnabled" */ + Property mColor; /* "c" */ + Property mOpacity{100}; /* "o" */ +}; + +class Shape : public Object { +public: + explicit Shape(Object::Type type) : Object(type) {} + VPath::Direction direction() + { + return (mDirection == 3) ? VPath::Direction::CCW : VPath::Direction::CW; + } + +public: + int mDirection{1}; +}; + +class Path : public Shape { +public: + Path() : Shape(Object::Type::Path) {} + +public: + Property mShape; +}; + +class Rect : public Shape { +public: + Rect() : Shape(Object::Type::Rect) {} + +public: + Property mPos; + Property mSize; + Property mRound{0}; +}; + +class Ellipse : public Shape { +public: + Ellipse() : Shape(Object::Type::Ellipse) {} + +public: + Property mPos; + Property mSize; +}; + +class Polystar : public Shape { +public: + enum class PolyType { Star = 1, Polygon = 2 }; + Polystar() : Shape(Object::Type::Polystar) {} + +public: + Polystar::PolyType mPolyType{PolyType::Polygon}; + Property mPos; + Property mPointCount{0}; + Property mInnerRadius{0}; + Property mOuterRadius{0}; + Property mInnerRoundness{0}; + Property mOuterRoundness{0}; + Property mRotation{0}; +}; + +class Repeater : public Object { +public: + struct Transform { + VMatrix matrix(int frameNo, float multiplier) const; + float startOpacity(int frameNo) const + { + return mStartOpacity.value(frameNo) / 100; + } + float endOpacity(int frameNo) const + { + return mEndOpacity.value(frameNo) / 100; + } + bool isStatic() const + { + return mRotation.isStatic() && mScale.isStatic() && + mPosition.isStatic() && mAnchor.isStatic() && + mStartOpacity.isStatic() && mEndOpacity.isStatic(); + } + Property mRotation{0}; /* "r" */ + Property mScale{{100, 100}}; /* "s" */ + Property mPosition; /* "p" */ + Property mAnchor; /* "a" */ + Property mStartOpacity{100}; /* "so" */ + Property mEndOpacity{100}; /* "eo" */ + }; + Repeater() : Object(Object::Type::Repeater) {} + Group *content() const { return mContent ? mContent : nullptr; } + void setContent(Group *content) { mContent = content; } + int maxCopies() const { return int(mMaxCopies); } + float copies(int frameNo) const { return mCopies.value(frameNo); } + float offset(int frameNo) const { return mOffset.value(frameNo); } + bool processed() const { return mProcessed; } + void markProcessed() { mProcessed = true; } + +public: + Group * mContent{nullptr}; + Transform mTransform; + Property mCopies{0}; + Property mOffset{0}; + float mMaxCopies{0.0}; + bool mProcessed{false}; +}; + +class Trim : public Object { +public: + struct Segment { + float start{0}; + float end{0}; + Segment() = default; + explicit Segment(float s, float e) : start(s), end(e) {} + }; + enum class TrimType { Simultaneously, Individually }; + Trim() : Object(Object::Type::Trim) {} + /* + * if start > end vector trims the path as a loop ( 2 segment) + * if start < end vector trims the path without loop ( 1 segment). + * if no offset then there is no loop. + */ + Segment segment(int frameNo) const + { + float start = mStart.value(frameNo) / 100.0f; + float end = mEnd.value(frameNo) / 100.0f; + float offset = std::fmod(mOffset.value(frameNo), 360.0f) / 360.0f; + + float diff = std::abs(start - end); + if (vCompare(diff, 0.0f)) return Segment(0, 0); + if (vCompare(diff, 1.0f)) return Segment(0, 1); + + if (offset > 0) { + start += offset; + end += offset; + if (start <= 1 && end <= 1) { + return noloop(start, end); + } else if (start > 1 && end > 1) { + return noloop(start - 1, end - 1); + } else { + return (start > 1) ? loop(start - 1, end) + : loop(start, end - 1); + } + } else { + start += offset; + end += offset; + if (start >= 0 && end >= 0) { + return noloop(start, end); + } else if (start < 0 && end < 0) { + return noloop(1 + start, 1 + end); + } else { + return (start < 0) ? loop(1 + start, end) + : loop(start, 1 + end); + } + } + } + Trim::TrimType type() const { return mTrimType; } + +private: + Segment noloop(float start, float end) const + { + assert(start >= 0); + assert(end >= 0); + Segment s; + s.start = std::min(start, end); + s.end = std::max(start, end); + return s; + } + Segment loop(float start, float end) const + { + assert(start >= 0); + assert(end >= 0); + Segment s; + s.start = std::max(start, end); + s.end = std::min(start, end); + return s; + } + +public: + Property mStart{0}; + Property mEnd{0}; + Property mOffset{0}; + Trim::TrimType mTrimType{TrimType::Simultaneously}; +}; + +inline Gradient::Data operator+(const Gradient::Data &g1, + const Gradient::Data &g2) +{ + if (g1.mGradient.size() != g2.mGradient.size()) return g1; + + Gradient::Data newG; + newG.mGradient = g1.mGradient; + + auto g2It = g2.mGradient.begin(); + for (auto &i : newG.mGradient) { + i = i + *g2It; + g2It++; + } + + return newG; +} + +inline Gradient::Data operator-(const Gradient::Data &g1, + const Gradient::Data &g2) +{ + if (g1.mGradient.size() != g2.mGradient.size()) return g1; + Gradient::Data newG; + newG.mGradient = g1.mGradient; + + auto g2It = g2.mGradient.begin(); + for (auto &i : newG.mGradient) { + i = i - *g2It; + g2It++; + } + + return newG; +} + +inline Gradient::Data operator*(float m, const Gradient::Data &g) +{ + Gradient::Data newG; + newG.mGradient = g.mGradient; + + for (auto &i : newG.mGradient) { + i = i * m; + } + return newG; +} + +using ColorFilter = std::function; + +void configureModelCacheSize(size_t cacheSize); + +std::shared_ptr loadFromFile(const std::string &filePath, + bool cachePolicy); + +std::shared_ptr loadFromData(std::string jsonData, + const std::string &key, + std::string resourcePath, + bool cachePolicy); + +std::shared_ptr loadFromData(std::string jsonData, + std::string resourcePath, + ColorFilter filter); + +std::shared_ptr parse(char *str, std::string dir_path, + ColorFilter filter = {}); + +} // namespace model + +} // namespace internal + +} // namespace rlottie + +#endif // LOTModel_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieparser.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieparser.cpp new file mode 100644 index 0000000000..e5ef037742 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieparser.cpp @@ -0,0 +1,2382 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +//#define DEBUG_PARSER + +// This parser implements JSON token-by-token parsing with an API that is +// more direct; we don't have to create handler object and +// callbacks. Instead, we retrieve values from the JSON stream by calling +// GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures +// by calling EnterObject() and EnterArray(), and skip over unwanted data by +// calling SkipValue(). As we know the lottie file structure this way will be +// the efficient way of parsing the file. +// +// If you aren't sure of what's next in the JSON data, you can use PeekType() +// and PeekValue() to look ahead to the next object before reading it. +// +// If you call the wrong retrieval method--e.g. GetInt when the next JSON token +// is not an int, EnterObject or EnterArray when there isn't actually an object +// or array to read--the stream parsing will end immediately and no more data +// will be delivered. +// +// After calling EnterObject, you retrieve keys via NextObjectKey() and values +// via the normal getters. When NextObjectKey() returns null, you have exited +// the object, or you can call SkipObject() to skip to the end of the object +// immediately. If you fetch the entire object (i.e. NextObjectKey() returned +// null), you should not call SkipObject(). +// +// After calling EnterArray(), you must alternate between calling +// NextArrayValue() to see if the array has more data, and then retrieving +// values via the normal getters. You can call SkipArray() to skip to the end of +// the array immediately. If you fetch the entire array (i.e. NextArrayValue() +// returned null), you should not call SkipArray(). +// +// This parser uses in-situ strings, so the JSON buffer will be altered during +// the parse. + +#include + +#include "lottiemodel.h" +#include "rapidjson/document.h" + +RAPIDJSON_DIAG_PUSH +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif + +using namespace rapidjson; + +using namespace rlottie::internal; + +class LookaheadParserHandler { +public: + bool Null() + { + st_ = kHasNull; + v_.SetNull(); + return true; + } + bool Bool(bool b) + { + st_ = kHasBool; + v_.SetBool(b); + return true; + } + bool Int(int i) + { + st_ = kHasNumber; + v_.SetInt(i); + return true; + } + bool Uint(unsigned u) + { + st_ = kHasNumber; + v_.SetUint(u); + return true; + } + bool Int64(int64_t i) + { + st_ = kHasNumber; + v_.SetInt64(i); + return true; + } + bool Uint64(int64_t u) + { + st_ = kHasNumber; + v_.SetUint64(u); + return true; + } + bool Double(double d) + { + st_ = kHasNumber; + v_.SetDouble(d); + return true; + } + bool RawNumber(const char *, SizeType, bool) { return false; } + bool String(const char *str, SizeType length, bool) + { + st_ = kHasString; + v_.SetString(str, length); + return true; + } + bool StartObject() + { + st_ = kEnteringObject; + return true; + } + bool Key(const char *str, SizeType length, bool) + { + st_ = kHasKey; + v_.SetString(str, length); + return true; + } + bool EndObject(SizeType) + { + st_ = kExitingObject; + return true; + } + bool StartArray() + { + st_ = kEnteringArray; + return true; + } + bool EndArray(SizeType) + { + st_ = kExitingArray; + return true; + } + +protected: + explicit LookaheadParserHandler(char *str); + +protected: + enum LookaheadParsingState { + kInit, + kError, + kHasNull, + kHasBool, + kHasNumber, + kHasString, + kHasKey, + kEnteringObject, + kExitingObject, + kEnteringArray, + kExitingArray + }; + + Value v_; + LookaheadParsingState st_; + Reader r_; + InsituStringStream ss_; + + static const int parseFlags = kParseDefaultFlags | kParseInsituFlag; +}; + +class LottieParserImpl : public LookaheadParserHandler { +public: + LottieParserImpl(char *str, std::string dir_path, model::ColorFilter filter) + : LookaheadParserHandler(str), + mColorFilter(std::move(filter)), + mDirPath(std::move(dir_path)) + { + } + bool VerifyType(); + bool ParseNext(); + +public: + VArenaAlloc &allocator() { return compRef->mArenaAlloc; } + bool EnterObject(); + bool EnterArray(); + const char * NextObjectKey(); + bool NextArrayValue(); + int GetInt(); + double GetDouble(); + const char * GetString(); + bool GetBool(); + void GetNull(); + + void SkipObject(); + void SkipArray(); + void SkipValue(); + Value *PeekValue(); + int PeekType() const; + bool IsValid() { return st_ != kError; } + + void Skip(const char *key); + model::BlendMode getBlendMode(); + CapStyle getLineCap(); + JoinStyle getLineJoin(); + FillRule getFillRule(); + model::Trim::TrimType getTrimType(); + model::MatteType getMatteType(); + model::Layer::Type getLayerType(); + + std::shared_ptr composition() const + { + return mComposition; + } + void parseComposition(); + void parseMarkers(); + void parseMarker(); + void parseAssets(model::Composition *comp); + model::Asset * parseAsset(); + void parseLayers(model::Composition *comp); + model::Layer * parseLayer(); + void parseMaskProperty(model::Layer *layer); + void parseShapesAttr(model::Layer *layer); + void parseObject(model::Group *parent); + model::Mask * parseMaskObject(); + model::Object * parseObjectTypeAttr(); + model::Object * parseGroupObject(); + model::Rect * parseRectObject(); + model::Ellipse * parseEllipseObject(); + model::Path * parseShapeObject(); + model::Polystar *parsePolystarObject(); + + model::Transform * parseTransformObject(bool ddd = false); + model::Fill * parseFillObject(); + model::GradientFill * parseGFillObject(); + model::Stroke * parseStrokeObject(); + model::GradientStroke *parseGStrokeObject(); + model::Trim * parseTrimObject(); + model::Repeater * parseReapeaterObject(); + + void parseGradientProperty(model::Gradient *gradient, const char *key); + + VPointF parseInperpolatorPoint(); + + void getValue(VPointF &pt); + void getValue(float &fval); + void getValue(model::Color &color); + void getValue(int &ival); + void getValue(model::PathData &shape); + void getValue(model::Gradient::Data &gradient); + void getValue(std::vector &v); + void getValue(model::Repeater::Transform &); + + template + bool parseKeyFrameValue(const char *, model::Value &) + { + return false; + } + + template + bool parseKeyFrameValue(const char * key, + model::Value &value); + template + void parseKeyFrame(model::KeyFrames &obj); + template + void parseProperty(model::Property &obj); + template + void parsePropertyHelper(model::Property &obj); + + void parseShapeProperty(model::Property &obj); + void parseDashProperty(model::Dash &dash); + + VInterpolator *interpolator(VPointF, VPointF, std::string); + + model::Color toColor(const char *str); + + void resolveLayerRefs(); + void parsePathInfo(); + +private: + model::ColorFilter mColorFilter; + struct { + std::vector mInPoint; /* "i" */ + std::vector mOutPoint; /* "o" */ + std::vector mVertices; /* "v" */ + std::vector mResult; + bool mClosed{false}; + + void convert() + { + // shape data could be empty. + if (mInPoint.empty() || mOutPoint.empty() || mVertices.empty()) { + mResult.clear(); + return; + } + + /* + * Convert the AE shape format to + * list of bazier curves + * The final structure will be Move +size*Cubic + Cubic (if the path + * is closed one) + */ + if (mInPoint.size() != mOutPoint.size() || + mInPoint.size() != mVertices.size()) { + mResult.clear(); + } else { + auto size = mVertices.size(); + mResult.push_back(mVertices[0]); + for (size_t i = 1; i < size; i++) { + mResult.push_back( + mVertices[i - 1] + + mOutPoint[i - 1]); // CP1 = start + outTangent + mResult.push_back(mVertices[i] + + mInPoint[i]); // CP2 = end + inTangent + mResult.push_back(mVertices[i]); // end point + } + + if (mClosed) { + mResult.push_back( + mVertices[size - 1] + + mOutPoint[size - 1]); // CP1 = start + outTangent + mResult.push_back(mVertices[0] + + mInPoint[0]); // CP2 = end + inTangent + mResult.push_back(mVertices[0]); // end point + } + } + } + void reset() + { + mInPoint.clear(); + mOutPoint.clear(); + mVertices.clear(); + mResult.clear(); + mClosed = false; + } + void updatePath(VPath &out) + { + if (mResult.empty()) return; + + auto size = mResult.size(); + auto points = mResult.data(); + /* reserve exact memory requirement at once + * ptSize = size + 1(size + close) + * elmSize = size/3 cubic + 1 move + 1 close + */ + out.reserve(size + 1, size / 3 + 2); + out.moveTo(points[0]); + for (size_t i = 1; i < size; i += 3) { + out.cubicTo(points[i], points[i + 1], points[i + 2]); + } + if (mClosed) out.close(); + } + } mPathInfo; + +protected: + std::unordered_map mInterpolatorCache; + std::shared_ptr mComposition; + model::Composition * compRef{nullptr}; + model::Layer * curLayerRef{nullptr}; + std::vector mLayersToUpdate; + std::string mDirPath; + void SkipOut(int depth); +}; + +LookaheadParserHandler::LookaheadParserHandler(char *str) + : v_(), st_(kInit), ss_(str) +{ + r_.IterativeParseInit(); +} + +bool LottieParserImpl::VerifyType() +{ + /* Verify the media type is lottie json. + Could add more strict check. */ + return ParseNext(); +} + +bool LottieParserImpl::ParseNext() +{ + if (r_.HasParseError()) { + st_ = kError; + return false; + } + + if (!r_.IterativeParseNext(ss_, *this)) { + vCritical << "Lottie file parsing error"; + st_ = kError; + return false; + } + return true; +} + +bool LottieParserImpl::EnterObject() +{ + if (st_ != kEnteringObject) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return false; + } + + ParseNext(); + return true; +} + +bool LottieParserImpl::EnterArray() +{ + if (st_ != kEnteringArray) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return false; + } + + ParseNext(); + return true; +} + +const char *LottieParserImpl::NextObjectKey() +{ + if (st_ == kHasKey) { + const char *result = v_.GetString(); + ParseNext(); + return result; + } + + /* SPECIAL CASE + * The parser works with a prdefined rule that it will be only + * while (NextObjectKey()) for each object but in case of our nested group + * object we can call multiple time NextObjectKey() while exiting the object + * so ignore those and don't put parser in the error state. + * */ + if (st_ == kExitingArray || st_ == kEnteringObject) { + // #ifdef DEBUG_PARSER + // vDebug<<"Object: Exiting nested loop"; + // #endif + return nullptr; + } + + if (st_ != kExitingObject) { + RAPIDJSON_ASSERT(false); + st_ = kError; + return nullptr; + } + + ParseNext(); + return nullptr; +} + +bool LottieParserImpl::NextArrayValue() +{ + if (st_ == kExitingArray) { + ParseNext(); + return false; + } + + /* SPECIAL CASE + * same as NextObjectKey() + */ + if (st_ == kExitingObject) { + return false; + } + + if (st_ == kError || st_ == kHasKey) { + RAPIDJSON_ASSERT(false); + st_ = kError; + return false; + } + + return true; +} + +int LottieParserImpl::GetInt() +{ + if (st_ != kHasNumber || !v_.IsInt()) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return 0; + } + + int result = v_.GetInt(); + ParseNext(); + return result; +} + +double LottieParserImpl::GetDouble() +{ + if (st_ != kHasNumber) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return 0.; + } + + double result = v_.GetDouble(); + ParseNext(); + return result; +} + +bool LottieParserImpl::GetBool() +{ + if (st_ != kHasBool) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return false; + } + + bool result = v_.GetBool(); + ParseNext(); + return result; +} + +void LottieParserImpl::GetNull() +{ + if (st_ != kHasNull) { + st_ = kError; + return; + } + + ParseNext(); +} + +const char *LottieParserImpl::GetString() +{ + if (st_ != kHasString) { + st_ = kError; + RAPIDJSON_ASSERT(false); + return nullptr; + } + + const char *result = v_.GetString(); + ParseNext(); + return result; +} + +void LottieParserImpl::SkipOut(int depth) +{ + do { + if (st_ == kEnteringArray || st_ == kEnteringObject) { + ++depth; + } else if (st_ == kExitingArray || st_ == kExitingObject) { + --depth; + } else if (st_ == kError) { + RAPIDJSON_ASSERT(false); + return; + } + + ParseNext(); + } while (depth > 0); +} + +void LottieParserImpl::SkipValue() +{ + SkipOut(0); +} + +void LottieParserImpl::SkipArray() +{ + SkipOut(1); +} + +void LottieParserImpl::SkipObject() +{ + SkipOut(1); +} + +Value *LottieParserImpl::PeekValue() +{ + if (st_ >= kHasNull && st_ <= kHasKey) { + return &v_; + } + + return nullptr; +} + +// returns a rapidjson::Type, or -1 for no value (at end of +// object/array) +int LottieParserImpl::PeekType() const +{ + if (st_ >= kHasNull && st_ <= kHasKey) { + return v_.GetType(); + } + + if (st_ == kEnteringArray) { + return kArrayType; + } + + if (st_ == kEnteringObject) { + return kObjectType; + } + + return -1; +} + +void LottieParserImpl::Skip(const char * /*key*/) +{ + if (PeekType() == kArrayType) { + EnterArray(); + SkipArray(); + } else if (PeekType() == kObjectType) { + EnterObject(); + SkipObject(); + } else { + SkipValue(); + } +} + +model::BlendMode LottieParserImpl::getBlendMode() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + auto mode = model::BlendMode::Normal; + + switch (GetInt()) { + case 1: + mode = model::BlendMode::Multiply; + break; + case 2: + mode = model::BlendMode::Screen; + break; + case 3: + mode = model::BlendMode::OverLay; + break; + default: + break; + } + return mode; +} + +void LottieParserImpl::resolveLayerRefs() +{ + for (const auto &layer : mLayersToUpdate) { + auto search = compRef->mAssets.find(layer->extra()->mPreCompRefId); + if (search != compRef->mAssets.end()) { + if (layer->mLayerType == model::Layer::Type::Image) { + layer->extra()->mAsset = search->second; + } else if (layer->mLayerType == model::Layer::Type::Precomp) { + layer->mChildren = search->second->mLayers; + layer->setStatic(layer->isStatic() && + search->second->isStatic()); + } + } + } +} + +void LottieParserImpl::parseComposition() +{ + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + std::shared_ptr sharedComposition = + std::make_shared(); + model::Composition *comp = sharedComposition.get(); + compRef = comp; + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "v")) { + RAPIDJSON_ASSERT(PeekType() == kStringType); + comp->mVersion = std::string(GetString()); + } else if (0 == strcmp(key, "w")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + comp->mSize.setWidth(GetInt()); + } else if (0 == strcmp(key, "h")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + comp->mSize.setHeight(GetInt()); + } else if (0 == strcmp(key, "ip")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + comp->mStartFrame = GetDouble(); + } else if (0 == strcmp(key, "op")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + comp->mEndFrame = GetDouble(); + } else if (0 == strcmp(key, "fr")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + comp->mFrameRate = GetDouble(); + } else if (0 == strcmp(key, "assets")) { + parseAssets(comp); + } else if (0 == strcmp(key, "layers")) { + parseLayers(comp); + } else if (0 == strcmp(key, "markers")) { + parseMarkers(); + } else { +#ifdef DEBUG_PARSER + vWarning << "Composition Attribute Skipped : " << key; +#endif + Skip(key); + } + } + + if (comp->mVersion.empty() || !comp->mRootLayer) { + // don't have a valid bodymovin header + return; + } + if (!IsValid()) { + return; + } + + resolveLayerRefs(); + comp->setStatic(comp->mRootLayer->isStatic()); + comp->mRootLayer->mInFrame = comp->mStartFrame; + comp->mRootLayer->mOutFrame = comp->mEndFrame; + + mComposition = sharedComposition; +} + +void LottieParserImpl::parseMarker() +{ + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + std::string comment; + int timeframe{0}; + int duration{0}; + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "cm")) { + RAPIDJSON_ASSERT(PeekType() == kStringType); + comment = std::string(GetString()); + } else if (0 == strcmp(key, "tm")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + timeframe = GetDouble(); + } else if (0 == strcmp(key, "dr")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + duration = GetDouble(); + + } else { +#ifdef DEBUG_PARSER + vWarning << "Marker Attribute Skipped : " << key; +#endif + Skip(key); + } + } + compRef->mMarkers.emplace_back(std::move(comment), timeframe, + timeframe + duration); +} + +void LottieParserImpl::parseMarkers() +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + parseMarker(); + } + // update the precomp layers with the actual layer object +} + +void LottieParserImpl::parseAssets(model::Composition *composition) +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + auto asset = parseAsset(); + composition->mAssets[asset->mRefId] = asset; + } + // update the precomp layers with the actual layer object +} + +static constexpr const unsigned char B64index[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51}; + +std::string b64decode(const char *data, const size_t len) +{ + auto p = reinterpret_cast(data); + int pad = len > 0 && (len % 4 || p[len - 1] == '='); + const size_t L = ((len + 3) / 4 - pad) * 4; + std::string str(L / 4 * 3 + pad, '\0'); + + for (size_t i = 0, j = 0; i < L; i += 4) { + int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | + B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; + str[j++] = n >> 16; + str[j++] = n >> 8 & 0xFF; + str[j++] = n & 0xFF; + } + if (pad) { + int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; + str[str.size() - 1] = n >> 16; + + if (len > L + 2 && p[L + 2] != '=') { + n |= B64index[p[L + 2]] << 6; + str.push_back(n >> 8 & 0xFF); + } + } + return str; +} + +static std::string convertFromBase64(const std::string &str) +{ + // usual header look like "data:image/png;base64," + // so need to skip till ','. + size_t startIndex = str.find(",", 0); + startIndex += 1; // skip "," + size_t length = str.length() - startIndex; + + const char *b64Data = str.c_str() + startIndex; + + return b64decode(b64Data, length); +} + +/* + * std::to_string() function is missing in VS2017 + * so this is workaround for windows build + */ +#include +template +static std::string toString(const T &value) +{ + std::ostringstream os; + os << value; + return os.str(); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +model::Asset *LottieParserImpl::parseAsset() +{ + RAPIDJSON_ASSERT(PeekType() == kObjectType); + + auto asset = allocator().make(); + std::string filename; + std::string relativePath; + bool embededResource = false; + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "w")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + asset->mWidth = GetInt(); + } else if (0 == strcmp(key, "h")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + asset->mHeight = GetInt(); + } else if (0 == strcmp(key, "p")) { /* image name */ + asset->mAssetType = model::Asset::Type::Image; + RAPIDJSON_ASSERT(PeekType() == kStringType); + filename = std::string(GetString()); + } else if (0 == strcmp(key, "u")) { /* relative image path */ + RAPIDJSON_ASSERT(PeekType() == kStringType); + relativePath = std::string(GetString()); + } else if (0 == strcmp(key, "e")) { /* relative image path */ + embededResource = GetInt(); + } else if (0 == strcmp(key, "id")) { /* reference id*/ + if (PeekType() == kStringType) { + asset->mRefId = std::string(GetString()); + } else { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + asset->mRefId = toString(GetInt()); + } + } else if (0 == strcmp(key, "layers")) { + asset->mAssetType = model::Asset::Type::Precomp; + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + bool staticFlag = true; + while (NextArrayValue()) { + auto layer = parseLayer(); + if (layer) { + staticFlag = staticFlag && layer->isStatic(); + asset->mLayers.push_back(layer); + } + } + asset->setStatic(staticFlag); + } else { +#ifdef DEBUG_PARSER + vWarning << "Asset Attribute Skipped : " << key; +#endif + Skip(key); + } + } + + if (asset->mAssetType == model::Asset::Type::Image) { + if (embededResource) { + // embeder resource should start with "data:" + if (filename.compare(0, 5, "data:") == 0) { + asset->loadImageData(convertFromBase64(filename)); + } + } else { + asset->loadImagePath(mDirPath + relativePath + filename); + } + } + + return asset; +} + +void LottieParserImpl::parseLayers(model::Composition *comp) +{ + comp->mRootLayer = allocator().make(); + comp->mRootLayer->mLayerType = model::Layer::Type::Precomp; + comp->mRootLayer->setName("__"); + bool staticFlag = true; + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + auto layer = parseLayer(); + if (layer) { + staticFlag = staticFlag && layer->isStatic(); + comp->mRootLayer->mChildren.push_back(layer); + } + } + comp->mRootLayer->setStatic(staticFlag); +} + +model::Color LottieParserImpl::toColor(const char *str) +{ + model::Color color; + auto len = strlen(str); + + // some resource has empty color string + // return a default color for those cases. + if (len != 7 || str[0] != '#') return color; + + char tmp[3] = {'\0', '\0', '\0'}; + tmp[0] = str[1]; + tmp[1] = str[2]; + color.r = std::strtol(tmp, nullptr, 16) / 255.0f; + + tmp[0] = str[3]; + tmp[1] = str[4]; + color.g = std::strtol(tmp, nullptr, 16) / 255.0f; + + tmp[0] = str[5]; + tmp[1] = str[6]; + color.b = std::strtol(tmp, nullptr, 16) / 255.0f; + + return color; +} + +model::MatteType LottieParserImpl::getMatteType() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 1: + return model::MatteType::Alpha; + break; + case 2: + return model::MatteType::AlphaInv; + break; + case 3: + return model::MatteType::Luma; + break; + case 4: + return model::MatteType::LumaInv; + break; + default: + return model::MatteType::None; + break; + } +} + +model::Layer::Type LottieParserImpl::getLayerType() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 0: + return model::Layer::Type::Precomp; + break; + case 1: + return model::Layer::Type::Solid; + break; + case 2: + return model::Layer::Type::Image; + break; + case 3: + return model::Layer::Type::Null; + break; + case 4: + return model::Layer::Type::Shape; + break; + case 5: + return model::Layer::Type::Text; + break; + default: + return model::Layer::Type::Null; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +model::Layer *LottieParserImpl::parseLayer() +{ + RAPIDJSON_ASSERT(PeekType() == kObjectType); + model::Layer *layer = allocator().make(); + curLayerRef = layer; + bool ddd = true; + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "ty")) { /* Type of layer*/ + layer->mLayerType = getLayerType(); + } else if (0 == strcmp(key, "nm")) { /*Layer name*/ + RAPIDJSON_ASSERT(PeekType() == kStringType); + layer->setName(GetString()); + } else if (0 == strcmp(key, "ind")) { /*Layer index in AE. Used for + parenting and expressions.*/ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mId = GetInt(); + } else if (0 == strcmp(key, "ddd")) { /*3d layer */ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + ddd = GetInt(); + } else if (0 == + strcmp(key, + "parent")) { /*Layer Parent. Uses "ind" of parent.*/ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mParentId = GetInt(); + } else if (0 == strcmp(key, "refId")) { /*preComp Layer reference id*/ + RAPIDJSON_ASSERT(PeekType() == kStringType); + layer->extra()->mPreCompRefId = std::string(GetString()); + layer->mHasGradient = true; + mLayersToUpdate.push_back(layer); + } else if (0 == strcmp(key, "sr")) { // "Layer Time Stretching" + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mTimeStreatch = GetDouble(); + } else if (0 == strcmp(key, "tm")) { // time remapping + parseProperty(layer->extra()->mTimeRemap); + } else if (0 == strcmp(key, "ip")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mInFrame = std::lround(GetDouble()); + } else if (0 == strcmp(key, "op")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mOutFrame = std::lround(GetDouble()); + } else if (0 == strcmp(key, "st")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + layer->mStartFrame = GetDouble(); + } else if (0 == strcmp(key, "bm")) { + layer->mBlendMode = getBlendMode(); + } else if (0 == strcmp(key, "ks")) { + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + layer->mTransform = parseTransformObject(ddd); + } else if (0 == strcmp(key, "shapes")) { + parseShapesAttr(layer); + } else if (0 == strcmp(key, "w")) { + layer->mLayerSize.setWidth(GetInt()); + } else if (0 == strcmp(key, "h")) { + layer->mLayerSize.setHeight(GetInt()); + } else if (0 == strcmp(key, "sw")) { + layer->mLayerSize.setWidth(GetInt()); + } else if (0 == strcmp(key, "sh")) { + layer->mLayerSize.setHeight(GetInt()); + } else if (0 == strcmp(key, "sc")) { + layer->extra()->mSolidColor = toColor(GetString()); + } else if (0 == strcmp(key, "tt")) { + layer->mMatteType = getMatteType(); + } else if (0 == strcmp(key, "hasMask")) { + layer->mHasMask = GetBool(); + } else if (0 == strcmp(key, "masksProperties")) { + parseMaskProperty(layer); + } else if (0 == strcmp(key, "ao")) { + layer->mAutoOrient = GetInt(); + } else if (0 == strcmp(key, "hd")) { + layer->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vWarning << "Layer Attribute Skipped : " << key; +#endif + Skip(key); + } + } + + if (!layer->mTransform) { + // not a valid layer + return nullptr; + } + + // make sure layer data is not corrupted. + if (layer->hasParent() && (layer->id() == layer->parentId())) + return nullptr; + + if (layer->mExtra) layer->mExtra->mCompRef = compRef; + + if (layer->hidden()) { + // if layer is hidden, only data that is usefull is its + // transform matrix(when it is a parent of some other layer) + // so force it to be a Null Layer and release all resource. + layer->setStatic(layer->mTransform->isStatic()); + layer->mLayerType = model::Layer::Type::Null; + layer->mChildren = {}; + return layer; + } + + // update the static property of layer + bool staticFlag = true; + for (const auto &child : layer->mChildren) { + staticFlag &= child->isStatic(); + } + + if (layer->hasMask()) { + for (const auto &mask : layer->mExtra->mMasks) { + staticFlag &= mask->isStatic(); + } + } + + layer->setStatic(staticFlag && layer->mTransform->isStatic()); + + return layer; +} + +void LottieParserImpl::parseMaskProperty(model::Layer *layer) +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + layer->extra()->mMasks.push_back(parseMaskObject()); + } +} + +model::Mask *LottieParserImpl::parseMaskObject() +{ + auto obj = allocator().make(); + + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "inv")) { + obj->mInv = GetBool(); + } else if (0 == strcmp(key, "mode")) { + const char *str = GetString(); + if (!str) { + obj->mMode = model::Mask::Mode::None; + continue; + } + switch (str[0]) { + case 'n': + obj->mMode = model::Mask::Mode::None; + break; + case 'a': + obj->mMode = model::Mask::Mode::Add; + break; + case 's': + obj->mMode = model::Mask::Mode::Substarct; + break; + case 'i': + obj->mMode = model::Mask::Mode::Intersect; + break; + case 'f': + obj->mMode = model::Mask::Mode::Difference; + break; + default: + obj->mMode = model::Mask::Mode::None; + break; + } + } else if (0 == strcmp(key, "pt")) { + parseShapeProperty(obj->mShape); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else { + Skip(key); + } + } + obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic(); + return obj; +} + +void LottieParserImpl::parseShapesAttr(model::Layer *layer) +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + parseObject(layer); + } +} + +model::Object *LottieParserImpl::parseObjectTypeAttr() +{ + RAPIDJSON_ASSERT(PeekType() == kStringType); + const char *type = GetString(); + if (0 == strcmp(type, "gr")) { + return parseGroupObject(); + } else if (0 == strcmp(type, "rc")) { + return parseRectObject(); + } else if (0 == strcmp(type, "el")) { + return parseEllipseObject(); + } else if (0 == strcmp(type, "tr")) { + return parseTransformObject(); + } else if (0 == strcmp(type, "fl")) { + return parseFillObject(); + } else if (0 == strcmp(type, "st")) { + return parseStrokeObject(); + } else if (0 == strcmp(type, "gf")) { + curLayerRef->mHasGradient = true; + return parseGFillObject(); + } else if (0 == strcmp(type, "gs")) { + curLayerRef->mHasGradient = true; + return parseGStrokeObject(); + } else if (0 == strcmp(type, "sh")) { + return parseShapeObject(); + } else if (0 == strcmp(type, "sr")) { + return parsePolystarObject(); + } else if (0 == strcmp(type, "tm")) { + curLayerRef->mHasPathOperator = true; + return parseTrimObject(); + } else if (0 == strcmp(type, "rp")) { + curLayerRef->mHasRepeater = true; + return parseReapeaterObject(); + } else if (0 == strcmp(type, "mm")) { + vWarning << "Merge Path is not supported yet"; + return nullptr; + } else { +#ifdef DEBUG_PARSER + vDebug << "The Object Type not yet handled = " << type; +#endif + return nullptr; + } +} + +void LottieParserImpl::parseObject(model::Group *parent) +{ + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "ty")) { + auto child = parseObjectTypeAttr(); + if (child && !child->hidden()) parent->mChildren.push_back(child); + } else { + Skip(key); + } + } +} + +model::Object *LottieParserImpl::parseGroupObject() +{ + auto group = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + group->setName(GetString()); + } else if (0 == strcmp(key, "it")) { + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + RAPIDJSON_ASSERT(PeekType() == kObjectType); + parseObject(group); + } + if (group->mChildren.back()->type() == + model::Object::Type::Transform) { + group->mTransform = + static_cast(group->mChildren.back()); + group->mChildren.pop_back(); + } + } else { + Skip(key); + } + } + bool staticFlag = true; + for (const auto &child : group->mChildren) { + staticFlag &= child->isStatic(); + } + + if (group->mTransform) { + group->setStatic(staticFlag && group->mTransform->isStatic()); + } + + return group; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json + */ +model::Rect *LottieParserImpl::parseRectObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mSize); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRound); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { + Skip(key); + } + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() && + obj->mRound.isStatic()); + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json + */ +model::Ellipse *LottieParserImpl::parseEllipseObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mSize); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { + Skip(key); + } + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic()); + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json + */ +model::Path *LottieParserImpl::parseShapeObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "ks")) { + parseShapeProperty(obj->mShape); + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vDebug << "Shape property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mShape.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json + */ +model::Polystar *LottieParserImpl::parsePolystarObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj->mPos); + } else if (0 == strcmp(key, "pt")) { + parseProperty(obj->mPointCount); + } else if (0 == strcmp(key, "ir")) { + parseProperty(obj->mInnerRadius); + } else if (0 == strcmp(key, "is")) { + parseProperty(obj->mInnerRoundness); + } else if (0 == strcmp(key, "or")) { + parseProperty(obj->mOuterRadius); + } else if (0 == strcmp(key, "os")) { + parseProperty(obj->mOuterRoundness); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRotation); + } else if (0 == strcmp(key, "sy")) { + int starType = GetInt(); + if (starType == 1) obj->mPolyType = model::Polystar::PolyType::Star; + if (starType == 2) + obj->mPolyType = model::Polystar::PolyType::Polygon; + } else if (0 == strcmp(key, "d")) { + obj->mDirection = GetInt(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vDebug << "Polystar property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic( + obj->mPos.isStatic() && obj->mPointCount.isStatic() && + obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() && + obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() && + obj->mRotation.isStatic()); + + return obj; +} + +model::Trim::TrimType LottieParserImpl::getTrimType() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 1: + return model::Trim::TrimType::Simultaneously; + break; + case 2: + return model::Trim::TrimType::Individually; + break; + default: + RAPIDJSON_ASSERT(0); + return model::Trim::TrimType::Simultaneously; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json + */ +model::Trim *LottieParserImpl::parseTrimObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mStart); + } else if (0 == strcmp(key, "e")) { + parseProperty(obj->mEnd); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOffset); + } else if (0 == strcmp(key, "m")) { + obj->mTrimType = getTrimType(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vDebug << "Trim property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() && + obj->mOffset.isStatic()); + return obj; +} + +void LottieParserImpl::getValue(model::Repeater::Transform &obj) +{ + EnterObject(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "a")) { + parseProperty(obj.mAnchor); + } else if (0 == strcmp(key, "p")) { + parseProperty(obj.mPosition); + } else if (0 == strcmp(key, "r")) { + parseProperty(obj.mRotation); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj.mScale); + } else if (0 == strcmp(key, "so")) { + parseProperty(obj.mStartOpacity); + } else if (0 == strcmp(key, "eo")) { + parseProperty(obj.mEndOpacity); + } else { + Skip(key); + } + } +} + +model::Repeater *LottieParserImpl::parseReapeaterObject() +{ + auto obj = allocator().make(); + + obj->setContent(allocator().make()); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mCopies); + float maxCopy = 0.0; + if (!obj->mCopies.isStatic()) { + for (auto &keyFrame : obj->mCopies.animation().frames_) { + if (maxCopy < keyFrame.value_.start_) + maxCopy = keyFrame.value_.start_; + if (maxCopy < keyFrame.value_.end_) + maxCopy = keyFrame.value_.end_; + } + } else { + maxCopy = obj->mCopies.value(); + } + obj->mMaxCopies = maxCopy; + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOffset); + } else if (0 == strcmp(key, "tr")) { + getValue(obj->mTransform); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vDebug << "Repeater property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() && + obj->mTransform.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json + */ +model::Transform *LottieParserImpl::parseTransformObject(bool ddd) +{ + auto objT = allocator().make(); + + auto obj = allocator().make(); + if (ddd) { + obj->createExtraData(); + obj->mExtra->m3DData = true; + } + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + objT->setName(GetString()); + } else if (0 == strcmp(key, "a")) { + parseProperty(obj->mAnchor); + } else if (0 == strcmp(key, "p")) { + EnterObject(); + bool separate = false; + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + parsePropertyHelper(obj->mPosition); + } else if (0 == strcmp(key, "s")) { + obj->createExtraData(); + obj->mExtra->mSeparate = GetBool(); + separate = true; + } else if (separate && (0 == strcmp(key, "x"))) { + parseProperty(obj->mExtra->mSeparateX); + } else if (separate && (0 == strcmp(key, "y"))) { + parseProperty(obj->mExtra->mSeparateY); + } else { + Skip(key); + } + } + } else if (0 == strcmp(key, "r")) { + parseProperty(obj->mRotation); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mScale); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "hd")) { + objT->setHidden(GetBool()); + } else if (0 == strcmp(key, "rx")) { + parseProperty(obj->mExtra->m3DRx); + } else if (0 == strcmp(key, "ry")) { + parseProperty(obj->mExtra->m3DRy); + } else if (0 == strcmp(key, "rz")) { + parseProperty(obj->mExtra->m3DRz); + } else { + Skip(key); + } + } + bool isStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() && + obj->mRotation.isStatic() && obj->mScale.isStatic() && + obj->mOpacity.isStatic(); + if (obj->mExtra) { + isStatic = isStatic && obj->mExtra->m3DRx.isStatic() && + obj->mExtra->m3DRy.isStatic() && + obj->mExtra->m3DRz.isStatic() && + obj->mExtra->mSeparateX.isStatic() && + obj->mExtra->mSeparateY.isStatic(); + } + + objT->set(obj, isStatic); + + return objT; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json + */ +model::Fill *LottieParserImpl::parseFillObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mColor); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "fillEnabled")) { + obj->mEnabled = GetBool(); + } else if (0 == strcmp(key, "r")) { + obj->mFillRule = getFillRule(); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vWarning << "Fill property skipped = " << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json + */ +CapStyle LottieParserImpl::getLineCap() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 1: + return CapStyle::Flat; + break; + case 2: + return CapStyle::Round; + break; + default: + return CapStyle::Square; + break; + } +} + +FillRule LottieParserImpl::getFillRule() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 1: + return FillRule::Winding; + break; + case 2: + return FillRule::EvenOdd; + break; + default: + return FillRule::Winding; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json + */ +JoinStyle LottieParserImpl::getLineJoin() +{ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + switch (GetInt()) { + case 1: + return JoinStyle::Miter; + break; + case 2: + return JoinStyle::Round; + break; + default: + return JoinStyle::Bevel; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json + */ +model::Stroke *LottieParserImpl::parseStrokeObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "c")) { + parseProperty(obj->mColor); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "w")) { + parseProperty(obj->mWidth); + } else if (0 == strcmp(key, "fillEnabled")) { + obj->mEnabled = GetBool(); + } else if (0 == strcmp(key, "lc")) { + obj->mCapStyle = getLineCap(); + } else if (0 == strcmp(key, "lj")) { + obj->mJoinStyle = getLineJoin(); + } else if (0 == strcmp(key, "ml")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + obj->mMiterLimit = GetDouble(); + } else if (0 == strcmp(key, "d")) { + parseDashProperty(obj->mDash); + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vWarning << "Stroke property skipped = " << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() && + obj->mWidth.isStatic() && obj->mDash.isStatic()); + return obj; +} + +void LottieParserImpl::parseGradientProperty(model::Gradient *obj, + const char * key) +{ + if (0 == strcmp(key, "t")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + obj->mGradientType = GetInt(); + } else if (0 == strcmp(key, "o")) { + parseProperty(obj->mOpacity); + } else if (0 == strcmp(key, "s")) { + parseProperty(obj->mStartPoint); + } else if (0 == strcmp(key, "e")) { + parseProperty(obj->mEndPoint); + } else if (0 == strcmp(key, "h")) { + parseProperty(obj->mHighlightLength); + } else if (0 == strcmp(key, "a")) { + parseProperty(obj->mHighlightAngle); + } else if (0 == strcmp(key, "g")) { + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + parseProperty(obj->mGradient); + } else if (0 == strcmp(key, "p")) { + obj->mColorPoints = GetInt(); + } else { + Skip(nullptr); + } + } + } else if (0 == strcmp(key, "hd")) { + obj->setHidden(GetBool()); + } else { +#ifdef DEBUG_PARSER + vWarning << "Gradient property skipped = " << key; +#endif + Skip(key); + } + obj->setStatic( + obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() && + obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() && + obj->mHighlightLength.isStatic() && obj->mGradient.isStatic()); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json + */ +model::GradientFill *LottieParserImpl::parseGFillObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "r")) { + obj->mFillRule = getFillRule(); + } else { + parseGradientProperty(obj, key); + } + } + return obj; +} + +void LottieParserImpl::parseDashProperty(model::Dash &dash) +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "v")) { + dash.mData.emplace_back(); + parseProperty(dash.mData.back()); + } else { + Skip(key); + } + } + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json + */ +model::GradientStroke *LottieParserImpl::parseGStrokeObject() +{ + auto obj = allocator().make(); + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "nm")) { + obj->setName(GetString()); + } else if (0 == strcmp(key, "w")) { + parseProperty(obj->mWidth); + } else if (0 == strcmp(key, "lc")) { + obj->mCapStyle = getLineCap(); + } else if (0 == strcmp(key, "lj")) { + obj->mJoinStyle = getLineJoin(); + } else if (0 == strcmp(key, "ml")) { + RAPIDJSON_ASSERT(PeekType() == kNumberType); + obj->mMiterLimit = GetDouble(); + } else if (0 == strcmp(key, "d")) { + parseDashProperty(obj->mDash); + } else { + parseGradientProperty(obj, key); + } + } + + obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() && + obj->mDash.isStatic()); + return obj; +} + +void LottieParserImpl::getValue(std::vector &v) +{ + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + VPointF pt; + getValue(pt); + v.push_back(pt); + } +} + +void LottieParserImpl::getValue(VPointF &pt) +{ + float val[4] = {0.f}; + int i = 0; + + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + const auto value = GetDouble(); + if (i < 4) { + val[i++] = value; + } + } + pt.setX(val[0]); + pt.setY(val[1]); +} + +void LottieParserImpl::getValue(float &val) +{ + if (PeekType() == kArrayType) { + EnterArray(); + if (NextArrayValue()) val = GetDouble(); + // discard rest + while (NextArrayValue()) { + GetDouble(); + } + } else if (PeekType() == kNumberType) { + val = GetDouble(); + } else { + RAPIDJSON_ASSERT(0); + } +} + +void LottieParserImpl::getValue(model::Color &color) +{ + float val[4] = {0.f}; + int i = 0; + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + const auto value = GetDouble(); + if (i < 4) { + val[i++] = value; + } + } + + if (mColorFilter) mColorFilter(val[0], val[1], val[2]); + + color.r = val[0]; + color.g = val[1]; + color.b = val[2]; +} + +void LottieParserImpl::getValue(model::Gradient::Data &grad) +{ + if (PeekType() == kArrayType) EnterArray(); + + while (NextArrayValue()) { + grad.mGradient.push_back(GetDouble()); + } +} + +void LottieParserImpl::getValue(int &val) +{ + if (PeekType() == kArrayType) { + EnterArray(); + while (NextArrayValue()) { + val = GetInt(); + } + } else if (PeekType() == kNumberType) { + val = GetInt(); + } else { + RAPIDJSON_ASSERT(0); + } +} + +void LottieParserImpl::parsePathInfo() +{ + mPathInfo.reset(); + + /* + * The shape object could be wrapped by a array + * if its part of the keyframe object + */ + bool arrayWrapper = (PeekType() == kArrayType); + if (arrayWrapper) EnterArray(); + + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "i")) { + getValue(mPathInfo.mInPoint); + } else if (0 == strcmp(key, "o")) { + getValue(mPathInfo.mOutPoint); + } else if (0 == strcmp(key, "v")) { + getValue(mPathInfo.mVertices); + } else if (0 == strcmp(key, "c")) { + mPathInfo.mClosed = GetBool(); + } else { + RAPIDJSON_ASSERT(0); + Skip(nullptr); + } + } + // exit properly from the array + if (arrayWrapper) NextArrayValue(); + + mPathInfo.convert(); +} + +void LottieParserImpl::getValue(model::PathData &obj) +{ + parsePathInfo(); + obj.mPoints = mPathInfo.mResult; + obj.mClosed = mPathInfo.mClosed; +} + +VPointF LottieParserImpl::parseInperpolatorPoint() +{ + VPointF cp; + RAPIDJSON_ASSERT(PeekType() == kObjectType); + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "x")) { + getValue(cp.rx()); + } + if (0 == strcmp(key, "y")) { + getValue(cp.ry()); + } + } + return cp; +} + +template +bool LottieParserImpl::parseKeyFrameValue( + const char *key, model::Value &value) +{ + if (0 == strcmp(key, "ti")) { + value.hasTangent_ = true; + getValue(value.inTangent_); + } else if (0 == strcmp(key, "to")) { + value.hasTangent_ = true; + getValue(value.outTangent_); + } else { + return false; + } + return true; +} + +VInterpolator *LottieParserImpl::interpolator(VPointF inTangent, + VPointF outTangent, + std::string key) +{ + if (key.empty()) { + std::array temp; + snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(), + inTangent.y(), outTangent.x(), outTangent.y()); + key = temp.data(); + } + + auto search = mInterpolatorCache.find(key); + + if (search != mInterpolatorCache.end()) { + return search->second; + } + + auto obj = allocator().make(outTangent, inTangent); + mInterpolatorCache[std::move(key)] = obj; + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json + */ +template +void LottieParserImpl::parseKeyFrame(model::KeyFrames &obj) +{ + struct ParsedField { + std::string interpolatorKey; + bool interpolator{false}; + bool value{false}; + bool hold{false}; + bool noEndValue{true}; + }; + + EnterObject(); + ParsedField parsed; + typename model::KeyFrames::Frame keyframe; + VPointF inTangent; + VPointF outTangent; + + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "i")) { + parsed.interpolator = true; + inTangent = parseInperpolatorPoint(); + } else if (0 == strcmp(key, "o")) { + outTangent = parseInperpolatorPoint(); + } else if (0 == strcmp(key, "t")) { + keyframe.start_ = GetDouble(); + } else if (0 == strcmp(key, "s")) { + parsed.value = true; + getValue(keyframe.value_.start_); + continue; + } else if (0 == strcmp(key, "e")) { + parsed.noEndValue = false; + getValue(keyframe.value_.end_); + continue; + } else if (0 == strcmp(key, "n")) { + if (PeekType() == kStringType) { + parsed.interpolatorKey = GetString(); + } else { + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + RAPIDJSON_ASSERT(PeekType() == kStringType); + if (parsed.interpolatorKey.empty()) { + parsed.interpolatorKey = GetString(); + } else { + // skip rest of the string + GetString(); + } + } + } + continue; + } else if (parseKeyFrameValue(key, keyframe.value_)) { + continue; + } else if (0 == strcmp(key, "h")) { + parsed.hold = GetInt(); + continue; + } else { +#ifdef DEBUG_PARSER + vDebug << "key frame property skipped = " << key; +#endif + Skip(key); + } + } + + auto &list = obj.frames_; + if (!list.empty()) { + // update the endFrame value of current keyframe + list.back().end_ = keyframe.start_; + // if no end value provided, copy start value to previous frame + if (parsed.value && parsed.noEndValue) { + list.back().value_.end_ = keyframe.value_.start_; + } + } + + if (parsed.hold) { + keyframe.value_.end_ = keyframe.value_.start_; + keyframe.end_ = keyframe.start_; + list.push_back(std::move(keyframe)); + } else if (parsed.interpolator) { + keyframe.interpolator_ = interpolator( + inTangent, outTangent, std::move(parsed.interpolatorKey)); + list.push_back(std::move(keyframe)); + } else { + // its the last frame discard. + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json + */ + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json + */ +void LottieParserImpl::parseShapeProperty(model::Property &obj) +{ + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + if (PeekType() == kArrayType) { + EnterArray(); + while (NextArrayValue()) { + RAPIDJSON_ASSERT(PeekType() == kObjectType); + parseKeyFrame(obj.animation()); + } + } else { + if (!obj.isStatic()) { + RAPIDJSON_ASSERT(false); + st_ = kError; + return; + } + getValue(obj.value()); + } + } else { +#ifdef DEBUG_PARSER + vDebug << "shape property ignored = " << key; +#endif + Skip(nullptr); + } + } + obj.cache(); +} + +template +void LottieParserImpl::parsePropertyHelper(model::Property &obj) +{ + if (PeekType() == kNumberType) { + if (!obj.isStatic()) { + RAPIDJSON_ASSERT(false); + st_ = kError; + return; + } + /*single value property with no animation*/ + getValue(obj.value()); + } else { + RAPIDJSON_ASSERT(PeekType() == kArrayType); + EnterArray(); + while (NextArrayValue()) { + /* property with keyframe info*/ + if (PeekType() == kObjectType) { + parseKeyFrame(obj.animation()); + } else { + /* Read before modifying. + * as there is no way of knowing if the + * value of the array is either array of numbers + * or array of object without entering the array + * thats why this hack is there + */ + RAPIDJSON_ASSERT(PeekType() == kNumberType); + if (!obj.isStatic()) { + RAPIDJSON_ASSERT(false); + st_ = kError; + return; + } + /*multi value property with no animation*/ + getValue(obj.value()); + /*break here as we already reached end of array*/ + break; + } + } + obj.cache(); + } +} + +/* + * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties + */ +template +void LottieParserImpl::parseProperty(model::Property &obj) +{ + EnterObject(); + while (const char *key = NextObjectKey()) { + if (0 == strcmp(key, "k")) { + parsePropertyHelper(obj); + } else { + Skip(key); + } + } +} + +#ifdef LOTTIE_DUMP_TREE_SUPPORT + +class ObjectInspector { +public: + void visit(model::Composition *obj, std::string level) + { + vDebug << " { " << level << "Composition:: a: " << !obj->isStatic() + << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame() + << ", endFm: " << obj->endFrame() + << ", W: " << obj->size().width() + << ", H: " << obj->size().height() << "\n"; + level.append("\t"); + visit(obj->mRootLayer, level); + level.erase(level.end() - 1, level.end()); + vDebug << " } " << level << "Composition End\n"; + } + void visit(model::Layer *obj, std::string level) + { + vDebug << level << "{ " << layerType(obj->mLayerType) + << ", name: " << obj->name() << ", id:" << obj->mId + << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic() + << ", " << matteType(obj->mMatteType) + << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame + << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame + << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient() + << ", W:" << obj->layerSize().width() + << ", H:" << obj->layerSize().height(); + + if (obj->mLayerType == model::Layer::Type::Image) + vDebug << level << "\t{ " + << "ImageInfo:" + << " W :" << obj->extra()->mAsset->mWidth + << ", H :" << obj->extra()->mAsset->mHeight << " }" + << "\n"; + else { + vDebug << level; + } + visitChildren(static_cast(obj), level); + vDebug << level << "} " << layerType(obj->mLayerType).c_str() + << ", id: " << obj->mId << "\n"; + } + void visitChildren(model::Group *obj, std::string level) + { + level.append("\t"); + for (const auto &child : obj->mChildren) visit(child, level); + if (obj->mTransform) visit(obj->mTransform, level); + } + + void visit(model::Object *obj, std::string level) + { + switch (obj->type()) { + case model::Object::Type::Repeater: { + auto r = static_cast(obj); + vDebug << level << "{ Repeater: name: " << obj->name() + << " , a:" << !obj->isStatic() + << ", copies:" << r->maxCopies() + << ", offset:" << r->offset(0); + visitChildren(r->mContent, level); + vDebug << level << "} Repeater"; + break; + } + case model::Object::Type::Group: { + vDebug << level << "{ Group: name: " << obj->name() + << " , a:" << !obj->isStatic(); + visitChildren(static_cast(obj), level); + vDebug << level << "} Group"; + break; + } + case model::Object::Type::Layer: { + visit(static_cast(obj), level); + break; + } + case model::Object::Type::Trim: { + vDebug << level << "{ Trim: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Rect: { + vDebug << level << "{ Rect: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Ellipse: { + vDebug << level << "{ Ellipse: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Path: { + vDebug << level << "{ Shape: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Polystar: { + vDebug << level << "{ Polystar: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Transform: { + vDebug << level << "{ Transform: name: " << obj->name() + << " , a: " << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Stroke: { + vDebug << level << "{ Stroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::GStroke: { + vDebug << level << "{ GStroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Fill: { + vDebug << level << "{ Fill: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::GFill: { + auto f = static_cast(obj); + vDebug << level << "{ GFill: name: " << obj->name() + << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType + << ", s:" << f->mStartPoint.value(0) + << ", e:" << f->mEndPoint.value(0) << " }"; + break; + } + default: + break; + } + } + + std::string matteType(model::MatteType type) + { + switch (type) { + case model::MatteType::None: + return "Matte::None"; + break; + case model::MatteType::Alpha: + return "Matte::Alpha"; + break; + case model::MatteType::AlphaInv: + return "Matte::AlphaInv"; + break; + case model::MatteType::Luma: + return "Matte::Luma"; + break; + case model::MatteType::LumaInv: + return "Matte::LumaInv"; + break; + default: + return "Matte::Unknown"; + break; + } + } + std::string layerType(model::Layer::Type type) + { + switch (type) { + case model::Layer::Type::Precomp: + return "Layer::Precomp"; + break; + case model::Layer::Type::Null: + return "Layer::Null"; + break; + case model::Layer::Type::Shape: + return "Layer::Shape"; + break; + case model::Layer::Type::Solid: + return "Layer::Solid"; + break; + case model::Layer::Type::Image: + return "Layer::Image"; + break; + case model::Layer::Type::Text: + return "Layer::Text"; + break; + default: + return "Layer::Unknown"; + break; + } + } +}; + +#endif + +std::shared_ptr model::parse(char * str, + std::string dir_path, + model::ColorFilter filter) +{ + LottieParserImpl obj(str, std::move(dir_path), std::move(filter)); + + if (obj.VerifyType()) { + obj.parseComposition(); + auto composition = obj.composition(); + if (composition) { + composition->processRepeaterObjects(); + composition->updateStats(); + +#ifdef LOTTIE_DUMP_TREE_SUPPORT + ObjectInspector inspector; + inspector.visit(composition.get(), ""); +#endif + + return composition; + } + } + + vWarning << "Input data is not Lottie format!"; + return {}; +} + +RAPIDJSON_DIAG_POP diff --git a/submodules/SvgRendering/Sources/SvgParser.swift b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieproxymodel.cpp similarity index 100% rename from submodules/SvgRendering/Sources/SvgParser.swift rename to Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/lottieproxymodel.cpp diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/allocators.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/allocators.h new file mode 100644 index 0000000000..0b8f5e14ca --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/allocators.h @@ -0,0 +1,284 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ALLOCATORS_H_ +#define RAPIDJSON_ALLOCATORS_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Allocator + +/*! \class rapidjson::Allocator + \brief Concept for allocating, resizing and freeing memory block. + + Note that Malloc() and Realloc() are non-static but Free() is static. + + So if an allocator need to support Free(), it needs to put its pointer in + the header of memory block. + +\code +concept Allocator { + static const bool kNeedFree; //!< Whether this allocator needs to call Free(). + + // Allocate a memory block. + // \param size of the memory block in bytes. + // \returns pointer to the memory block. + void* Malloc(size_t size); + + // Resize a memory block. + // \param originalPtr The pointer to current memory block. Null pointer is permitted. + // \param originalSize The current size in bytes. (Design issue: since some allocator may not book-keep this, explicitly pass to it can save memory.) + // \param newSize the new size in bytes. + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize); + + // Free a memory block. + // \param pointer to the memory block. Null pointer is permitted. + static void Free(void *ptr); +}; +\endcode +*/ + + +/*! \def RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User-defined kDefaultChunkCapacity definition. + + User can define this as any \c size that is a power of 2. +*/ + +#ifndef RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY +#define RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY (64 * 1024) +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// CrtAllocator + +//! C-runtime library allocator. +/*! This class is just wrapper for standard C library memory routines. + \note implements Allocator concept +*/ +class CrtAllocator { +public: + static const bool kNeedFree = true; + void* Malloc(size_t size) { + if (size) // behavior of malloc(0) is implementation defined. + return RAPIDJSON_MALLOC(size); + else + return NULL; // standardize to returning NULL. + } + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + (void)originalSize; + if (newSize == 0) { + RAPIDJSON_FREE(originalPtr); + return NULL; + } + return RAPIDJSON_REALLOC(originalPtr, newSize); + } + static void Free(void *ptr) { RAPIDJSON_FREE(ptr); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryPoolAllocator + +//! Default memory allocator used by the parser and DOM. +/*! This allocator allocate memory blocks from pre-allocated memory chunks. + + It does not free memory blocks. And Realloc() only allocate new memory. + + The memory chunks are allocated by BaseAllocator, which is CrtAllocator by default. + + User may also supply a buffer as the first chunk. + + If the user-buffer is full then additional chunks are allocated by BaseAllocator. + + The user-buffer is not deallocated by this allocator. + + \tparam BaseAllocator the allocator type for allocating memory chunks. Default is CrtAllocator. + \note implements Allocator concept +*/ +template +class MemoryPoolAllocator { +public: + static const bool kNeedFree = false; //!< Tell users that no need to call Free() with this allocator. (concept Allocator) + + //! Constructor with chunkSize. + /*! \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(0), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + } + + //! Constructor with user-supplied buffer. + /*! The user buffer will be used firstly. When it is full, memory pool allocates new chunk with chunk size. + + The user buffer will not be deallocated when this allocator is destructed. + + \param buffer User supplied buffer. + \param size Size of the buffer in bytes. It must at least larger than sizeof(ChunkHeader). + \param chunkSize The size of memory chunk. The default is kDefaultChunkSize. + \param baseAllocator The allocator for allocating memory chunks. + */ + MemoryPoolAllocator(void *buffer, size_t size, size_t chunkSize = kDefaultChunkCapacity, BaseAllocator* baseAllocator = 0) : + chunkHead_(0), chunk_capacity_(chunkSize), userBuffer_(buffer), baseAllocator_(baseAllocator), ownBaseAllocator_(0) + { + RAPIDJSON_ASSERT(buffer != 0); + RAPIDJSON_ASSERT(size > sizeof(ChunkHeader)); + chunkHead_ = reinterpret_cast(buffer); + chunkHead_->capacity = size - sizeof(ChunkHeader); + chunkHead_->size = 0; + chunkHead_->next = 0; + } + + //! Destructor. + /*! This deallocates all memory chunks, excluding the user-supplied buffer. + */ + ~MemoryPoolAllocator() { + Clear(); + RAPIDJSON_DELETE(ownBaseAllocator_); + } + + //! Deallocates all memory chunks, excluding the user-supplied buffer. + void Clear() { + while (chunkHead_ && chunkHead_ != userBuffer_) { + ChunkHeader* next = chunkHead_->next; + baseAllocator_->Free(chunkHead_); + chunkHead_ = next; + } + if (chunkHead_ && chunkHead_ == userBuffer_) + chunkHead_->size = 0; // Clear user buffer + } + + //! Computes the total capacity of allocated memory chunks. + /*! \return total capacity in bytes. + */ + size_t Capacity() const { + size_t capacity = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + capacity += c->capacity; + return capacity; + } + + //! Computes the memory blocks allocated. + /*! \return total used bytes. + */ + size_t Size() const { + size_t size = 0; + for (ChunkHeader* c = chunkHead_; c != 0; c = c->next) + size += c->size; + return size; + } + + //! Allocates a memory block. (concept Allocator) + void* Malloc(size_t size) { + if (!size) + return NULL; + + size = RAPIDJSON_ALIGN(size); + if (chunkHead_ == 0 || chunkHead_->size + size > chunkHead_->capacity) + if (!AddChunk(chunk_capacity_ > size ? chunk_capacity_ : size)) + return NULL; + + void *buffer = reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size; + chunkHead_->size += size; + return buffer; + } + + //! Resizes a memory block (concept Allocator) + void* Realloc(void* originalPtr, size_t originalSize, size_t newSize) { + if (originalPtr == 0) + return Malloc(newSize); + + if (newSize == 0) + return NULL; + + originalSize = RAPIDJSON_ALIGN(originalSize); + newSize = RAPIDJSON_ALIGN(newSize); + + // Do not shrink if new size is smaller than original + if (originalSize >= newSize) + return originalPtr; + + // Simply expand it if it is the last allocation and there is sufficient space + if (originalPtr == reinterpret_cast(chunkHead_) + RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + chunkHead_->size - originalSize) { + size_t increment = static_cast(newSize - originalSize); + if (chunkHead_->size + increment <= chunkHead_->capacity) { + chunkHead_->size += increment; + return originalPtr; + } + } + + // Realloc process: allocate and copy memory, do not free original buffer. + if (void* newBuffer = Malloc(newSize)) { + if (originalSize) + std::memcpy(newBuffer, originalPtr, originalSize); + return newBuffer; + } + else + return NULL; + } + + //! Frees a memory block (concept Allocator) + static void Free(void *ptr) { (void)ptr; } // Do nothing + +private: + //! Copy constructor is not permitted. + MemoryPoolAllocator(const MemoryPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + MemoryPoolAllocator& operator=(const MemoryPoolAllocator& rhs) /* = delete */; + + //! Creates a new chunk. + /*! \param capacity Capacity of the chunk in bytes. + \return true if success. + */ + bool AddChunk(size_t capacity) { + if (!baseAllocator_) + ownBaseAllocator_ = baseAllocator_ = RAPIDJSON_NEW(BaseAllocator)(); + if (ChunkHeader* chunk = reinterpret_cast(baseAllocator_->Malloc(RAPIDJSON_ALIGN(sizeof(ChunkHeader)) + capacity))) { + chunk->capacity = capacity; + chunk->size = 0; + chunk->next = chunkHead_; + chunkHead_ = chunk; + return true; + } + else + return false; + } + + static const int kDefaultChunkCapacity = RAPIDJSON_ALLOCATOR_DEFAULT_CHUNK_CAPACITY; //!< Default chunk capacity. + + //! Chunk header for perpending to each chunk. + /*! Chunks are stored as a singly linked list. + */ + struct ChunkHeader { + size_t capacity; //!< Capacity of the chunk in bytes (excluding the header itself). + size_t size; //!< Current size of allocated memory in bytes. + ChunkHeader *next; //!< Next chunk in the linked list. + }; + + ChunkHeader *chunkHead_; //!< Head of the chunk linked-list. Only the head chunk serves allocation. + size_t chunk_capacity_; //!< The minimum capacity of chunk when they are allocated. + void *userBuffer_; //!< User supplied buffer. + BaseAllocator* baseAllocator_; //!< base allocator for allocating memory chunks. + BaseAllocator* ownBaseAllocator_; //!< base allocator created by this object. +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h new file mode 100644 index 0000000000..52c11a7c01 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/cursorstreamwrapper.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CURSORSTREAMWRAPPER_H_ +#define RAPIDJSON_CURSORSTREAMWRAPPER_H_ + +#include "stream.h" + +#if defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + + +//! Cursor stream wrapper for counting line and column number if error exists. +/*! + \tparam InputStream Any stream that implements Stream Concept +*/ +template > +class CursorStreamWrapper : public GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + + CursorStreamWrapper(InputStream& is): + GenericStreamWrapper(is), line_(1), col_(0) {} + + // counting line and column number + Ch Take() { + Ch ch = this->is_.Take(); + if(ch == '\n') { + line_ ++; + col_ = 0; + } else { + col_ ++; + } + return ch; + } + + //! Get the error line number, if error exists. + size_t GetLine() const { return line_; } + //! Get the error column number, if error exists. + size_t GetColumn() const { return col_; } + +private: + size_t line_; //!< Current Line + size_t col_; //!< Current Column +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +#if defined(__GNUC__) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CURSORSTREAMWRAPPER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/document.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/document.h new file mode 100644 index 0000000000..68aaae7e6b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/document.h @@ -0,0 +1,2732 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_DOCUMENT_H_ +#define RAPIDJSON_DOCUMENT_H_ + +/*! \file document.h */ + +#include "reader.h" +#include "internal/meta.h" +#include "internal/strfunc.h" +#include "memorystream.h" +#include "encodedstream.h" +#include // placement new +#include +#ifdef __cpp_lib_three_way_comparison +#include +#endif + +RAPIDJSON_DIAG_PUSH +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4244) // conversion from kXxxFlags to 'uint16_t', possible loss of data +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_OFF(effc++) +#endif // __GNUC__ + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS +#include // std::random_access_iterator_tag +#endif + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +// Forward declaration. +template +class GenericValue; + +template +class GenericDocument; + +/*! \def RAPIDJSON_DEFAULT_ALLOCATOR + \ingroup RAPIDJSON_CONFIG + \brief Allows to choose default allocator. + + User can define this to use CrtAllocator or MemoryPoolAllocator. +*/ +#ifndef RAPIDJSON_DEFAULT_ALLOCATOR +#define RAPIDJSON_DEFAULT_ALLOCATOR MemoryPoolAllocator +#endif + +/*! \def RAPIDJSON_DEFAULT_STACK_ALLOCATOR + \ingroup RAPIDJSON_CONFIG + \brief Allows to choose default stack allocator for Document. + + User can define this to use CrtAllocator or MemoryPoolAllocator. +*/ +#ifndef RAPIDJSON_DEFAULT_STACK_ALLOCATOR +#define RAPIDJSON_DEFAULT_STACK_ALLOCATOR CrtAllocator +#endif + +/*! \def RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User defined kDefaultObjectCapacity value. + + User can define this as any natural number. +*/ +#ifndef RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY +// number of objects that rapidjson::Value allocates memory for by default +#define RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY 16 +#endif + +/*! \def RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY + \ingroup RAPIDJSON_CONFIG + \brief User defined kDefaultArrayCapacity value. + + User can define this as any natural number. +*/ +#ifndef RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY +// number of array elements that rapidjson::Value allocates memory for by default +#define RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY 16 +#endif + +//! Name-value pair in a JSON object value. +/*! + This class was internal to GenericValue. It used to be a inner struct. + But a compiler (IBM XL C/C++ for AIX) have reported to have problem with that so it moved as a namespace scope struct. + https://code.google.com/p/rapidjson/issues/detail?id=64 +*/ +template +class GenericMember { +public: + GenericValue name; //!< name of member (must be a string) + GenericValue value; //!< value of member. + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericMember(GenericMember&& rhs) RAPIDJSON_NOEXCEPT + : name(std::move(rhs.name)), + value(std::move(rhs.value)) + { + } + + //! Move assignment in C++11 + GenericMember& operator=(GenericMember&& rhs) RAPIDJSON_NOEXCEPT { + return *this = static_cast(rhs); + } +#endif + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. Its name and value will become a null value after assignment. + */ + GenericMember& operator=(GenericMember& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + name = rhs.name; + value = rhs.value; + } + return *this; + } + + // swap() for std::sort() and other potential use in STL. + friend inline void swap(GenericMember& a, GenericMember& b) RAPIDJSON_NOEXCEPT { + a.name.Swap(b.name); + a.value.Swap(b.value); + } + +private: + //! Copy constructor is not permitted. + GenericMember(const GenericMember& rhs); +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericMemberIterator + +#ifndef RAPIDJSON_NOMEMBERITERATORCLASS + +//! (Constant) member iterator for a JSON object value +/*! + \tparam Const Is this a constant iterator? + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. + + This class implements a Random Access Iterator for GenericMember elements + of a GenericValue, see ISO/IEC 14882:2003(E) C++ standard, 24.1 [lib.iterator.requirements]. + + \note This iterator implementation is mainly intended to avoid implicit + conversions from iterator values to \c NULL, + e.g. from GenericValue::FindMember. + + \note Define \c RAPIDJSON_NOMEMBERITERATORCLASS to fall back to a + pointer-based implementation, if your platform doesn't provide + the C++ header. + + \see GenericMember, GenericValue::MemberIterator, GenericValue::ConstMemberIterator + */ +template +class GenericMemberIterator { + + friend class GenericValue; + template friend class GenericMemberIterator; + + typedef GenericMember PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + +public: + //! Iterator type itself + typedef GenericMemberIterator Iterator; + //! Constant iterator type + typedef GenericMemberIterator ConstIterator; + //! Non-constant iterator type + typedef GenericMemberIterator NonConstIterator; + + /** \name std::iterator_traits support */ + //@{ + typedef ValueType value_type; + typedef ValueType * pointer; + typedef ValueType & reference; + typedef std::ptrdiff_t difference_type; + typedef std::random_access_iterator_tag iterator_category; + //@} + + //! Pointer to (const) GenericMember + typedef pointer Pointer; + //! Reference to (const) GenericMember + typedef reference Reference; + //! Signed integer type (e.g. \c ptrdiff_t) + typedef difference_type DifferenceType; + + //! Default constructor (singular value) + /*! Creates an iterator pointing to no element. + \note All operations, except for comparisons, are undefined on such values. + */ + GenericMemberIterator() : ptr_() {} + + //! Iterator conversions to more const + /*! + \param it (Non-const) iterator to copy from + + Allows the creation of an iterator from another GenericMemberIterator + that is "less const". Especially, creating a non-constant iterator + from a constant iterator are disabled: + \li const -> non-const (not ok) + \li const -> const (ok) + \li non-const -> const (ok) + \li non-const -> non-const (ok) + + \note If the \c Const template parameter is already \c false, this + constructor effectively defines a regular copy-constructor. + Otherwise, the copy constructor is implicitly defined. + */ + GenericMemberIterator(const NonConstIterator & it) : ptr_(it.ptr_) {} + Iterator& operator=(const NonConstIterator & it) { ptr_ = it.ptr_; return *this; } + + //! @name stepping + //@{ + Iterator& operator++(){ ++ptr_; return *this; } + Iterator& operator--(){ --ptr_; return *this; } + Iterator operator++(int){ Iterator old(*this); ++ptr_; return old; } + Iterator operator--(int){ Iterator old(*this); --ptr_; return old; } + //@} + + //! @name increment/decrement + //@{ + Iterator operator+(DifferenceType n) const { return Iterator(ptr_+n); } + Iterator operator-(DifferenceType n) const { return Iterator(ptr_-n); } + + Iterator& operator+=(DifferenceType n) { ptr_+=n; return *this; } + Iterator& operator-=(DifferenceType n) { ptr_-=n; return *this; } + //@} + + //! @name relations + //@{ + template bool operator==(const GenericMemberIterator& that) const { return ptr_ == that.ptr_; } + template bool operator!=(const GenericMemberIterator& that) const { return ptr_ != that.ptr_; } + template bool operator<=(const GenericMemberIterator& that) const { return ptr_ <= that.ptr_; } + template bool operator>=(const GenericMemberIterator& that) const { return ptr_ >= that.ptr_; } + template bool operator< (const GenericMemberIterator& that) const { return ptr_ < that.ptr_; } + template bool operator> (const GenericMemberIterator& that) const { return ptr_ > that.ptr_; } + +#ifdef __cpp_lib_three_way_comparison + template std::strong_ordering operator<=>(const GenericMemberIterator& that) const { return ptr_ <=> that.ptr_; } +#endif + //@} + + //! @name dereference + //@{ + Reference operator*() const { return *ptr_; } + Pointer operator->() const { return ptr_; } + Reference operator[](DifferenceType n) const { return ptr_[n]; } + //@} + + //! Distance + DifferenceType operator-(ConstIterator that) const { return ptr_-that.ptr_; } + +private: + //! Internal constructor from plain pointer + explicit GenericMemberIterator(Pointer p) : ptr_(p) {} + + Pointer ptr_; //!< raw pointer +}; + +#else // RAPIDJSON_NOMEMBERITERATORCLASS + +// class-based member iterator implementation disabled, use plain pointers + +template +class GenericMemberIterator; + +//! non-const GenericMemberIterator +template +class GenericMemberIterator { + //! use plain pointer as iterator type + typedef GenericMember* Iterator; +}; +//! const GenericMemberIterator +template +class GenericMemberIterator { + //! use plain const pointer as iterator type + typedef const GenericMember* Iterator; +}; + +#endif // RAPIDJSON_NOMEMBERITERATORCLASS + +/////////////////////////////////////////////////////////////////////////////// +// GenericStringRef + +//! Reference to a constant string (not taking a copy) +/*! + \tparam CharType character type of the string + + This helper class is used to automatically infer constant string + references for string literals, especially from \c const \b (!) + character arrays. + + The main use is for creating JSON string values without copying the + source string via an \ref Allocator. This requires that the referenced + string pointers have a sufficient lifetime, which exceeds the lifetime + of the associated GenericValue. + + \b Example + \code + Value v("foo"); // ok, no need to copy & calculate length + const char foo[] = "foo"; + v.SetString(foo); // ok + + const char* bar = foo; + // Value x(bar); // not ok, can't rely on bar's lifetime + Value x(StringRef(bar)); // lifetime explicitly guaranteed by user + Value y(StringRef(bar, 3)); // ok, explicitly pass length + \endcode + + \see StringRef, GenericValue::SetString +*/ +template +struct GenericStringRef { + typedef CharType Ch; //!< character type of the string + + //! Create string reference from \c const character array +#ifndef __clang__ // -Wdocumentation + /*! + This constructor implicitly creates a constant string reference from + a \c const character array. It has better performance than + \ref StringRef(const CharType*) by inferring the string \ref length + from the array length, and also supports strings containing null + characters. + + \tparam N length of the string, automatically inferred + + \param str Constant character array, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note Constant complexity. + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + template + GenericStringRef(const CharType (&str)[N]) RAPIDJSON_NOEXCEPT + : s(str), length(N-1) {} + + //! Explicitly create string reference from \c const character pointer +#ifndef __clang__ // -Wdocumentation + /*! + This constructor can be used to \b explicitly create a reference to + a constant string pointer. + + \see StringRef(const CharType*) + + \param str Constant character pointer, lifetime assumed to be longer + than the use of the string in e.g. a GenericValue + + \post \ref s == str + + \note There is a hidden, private overload to disallow references to + non-const character arrays to be created via this constructor. + By this, e.g. function-scope arrays used to be filled via + \c snprintf are excluded from consideration. + In such cases, the referenced string should be \b copied to the + GenericValue instead. + */ +#endif + explicit GenericStringRef(const CharType* str) + : s(str), length(NotNullStrLen(str)) {} + + //! Create constant string reference from pointer and length +#ifndef __clang__ // -Wdocumentation + /*! \param str constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param len length of the string, excluding the trailing NULL terminator + + \post \ref s == str && \ref length == len + \note Constant complexity. + */ +#endif + GenericStringRef(const CharType* str, SizeType len) + : s(RAPIDJSON_LIKELY(str) ? str : emptyString), length(len) { RAPIDJSON_ASSERT(str != 0 || len == 0u); } + + GenericStringRef(const GenericStringRef& rhs) : s(rhs.s), length(rhs.length) {} + + //! implicit conversion to plain CharType pointer + operator const Ch *() const { return s; } + + const Ch* const s; //!< plain CharType pointer + const SizeType length; //!< length of the string (excluding the trailing NULL terminator) + +private: + SizeType NotNullStrLen(const CharType* str) { + RAPIDJSON_ASSERT(str != 0); + return internal::StrLen(str); + } + + /// Empty string - used when passing in a NULL pointer + static const Ch emptyString[]; + + //! Disallow construction from non-const array + template + GenericStringRef(CharType (&str)[N]) /* = delete */; + //! Copy assignment operator not permitted - immutable type + GenericStringRef& operator=(const GenericStringRef& rhs) /* = delete */; +}; + +template +const CharType GenericStringRef::emptyString[] = { CharType() }; + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + \tparam CharType Character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + + \see GenericValue::GenericValue(StringRefType), GenericValue::operator=(StringRefType), GenericValue::SetString(StringRefType), GenericValue::PushBack(StringRefType, Allocator&), GenericValue::AddMember +*/ +template +inline GenericStringRef StringRef(const CharType* str) { + return GenericStringRef(str); +} + +//! Mark a character pointer as constant string +/*! Mark a plain character pointer as a "string literal". This function + can be used to avoid copying a character string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + This version has better performance with supplied length, and also + supports string containing null characters. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \param length The length of source string. + \return GenericStringRef string reference object + \relatesalso GenericStringRef +*/ +template +inline GenericStringRef StringRef(const CharType* str, size_t length) { + return GenericStringRef(str, SizeType(length)); +} + +#if RAPIDJSON_HAS_STDSTRING +//! Mark a string object as constant string +/*! Mark a string object (e.g. \c std::string) as a "string literal". + This function can be used to avoid copying a string to be referenced as a + value in a JSON GenericValue object, if the string's lifetime is known + to be valid long enough. + + \tparam CharType character type of the string + \param str Constant string, lifetime assumed to be longer than the use of the string in e.g. a GenericValue + \return GenericStringRef string reference object + \relatesalso GenericStringRef + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. +*/ +template +inline GenericStringRef StringRef(const std::basic_string& str) { + return GenericStringRef(str.data(), SizeType(str.size())); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue type traits +namespace internal { + +template +struct IsGenericValueImpl : FalseType {}; + +// select candidates according to nested encoding and allocator types +template struct IsGenericValueImpl::Type, typename Void::Type> + : IsBaseOf, T>::Type {}; + +// helper to match arbitrary GenericValue instantiations, including derived classes +template struct IsGenericValue : IsGenericValueImpl::Type {}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// TypeHelper + +namespace internal { + +template +struct TypeHelper {}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsBool(); } + static bool Get(const ValueType& v) { return v.GetBool(); } + static ValueType& Set(ValueType& v, bool data) { return v.SetBool(data); } + static ValueType& Set(ValueType& v, bool data, typename ValueType::AllocatorType&) { return v.SetBool(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static int Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, int data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, int data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; + +#ifdef _MSC_VER +RAPIDJSON_STATIC_ASSERT(sizeof(long) == sizeof(int)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt(); } + static long Get(const ValueType& v) { return v.GetInt(); } + static ValueType& Set(ValueType& v, long data) { return v.SetInt(data); } + static ValueType& Set(ValueType& v, long data, typename ValueType::AllocatorType&) { return v.SetInt(data); } +}; + +RAPIDJSON_STATIC_ASSERT(sizeof(unsigned long) == sizeof(unsigned)); +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint(); } + static unsigned long Get(const ValueType& v) { return v.GetUint(); } + static ValueType& Set(ValueType& v, unsigned long data) { return v.SetUint(data); } + static ValueType& Set(ValueType& v, unsigned long data, typename ValueType::AllocatorType&) { return v.SetUint(data); } +}; +#endif + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsInt64(); } + static int64_t Get(const ValueType& v) { return v.GetInt64(); } + static ValueType& Set(ValueType& v, int64_t data) { return v.SetInt64(data); } + static ValueType& Set(ValueType& v, int64_t data, typename ValueType::AllocatorType&) { return v.SetInt64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsUint64(); } + static uint64_t Get(const ValueType& v) { return v.GetUint64(); } + static ValueType& Set(ValueType& v, uint64_t data) { return v.SetUint64(data); } + static ValueType& Set(ValueType& v, uint64_t data, typename ValueType::AllocatorType&) { return v.SetUint64(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsDouble(); } + static double Get(const ValueType& v) { return v.GetDouble(); } + static ValueType& Set(ValueType& v, double data) { return v.SetDouble(data); } + static ValueType& Set(ValueType& v, double data, typename ValueType::AllocatorType&) { return v.SetDouble(data); } +}; + +template +struct TypeHelper { + static bool Is(const ValueType& v) { return v.IsFloat(); } + static float Get(const ValueType& v) { return v.GetFloat(); } + static ValueType& Set(ValueType& v, float data) { return v.SetFloat(data); } + static ValueType& Set(ValueType& v, float data, typename ValueType::AllocatorType&) { return v.SetFloat(data); } +}; + +template +struct TypeHelper { + typedef const typename ValueType::Ch* StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return v.GetString(); } + static ValueType& Set(ValueType& v, const StringType data) { return v.SetString(typename ValueType::StringRefType(data)); } + static ValueType& Set(ValueType& v, const StringType data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; + +#if RAPIDJSON_HAS_STDSTRING +template +struct TypeHelper > { + typedef std::basic_string StringType; + static bool Is(const ValueType& v) { return v.IsString(); } + static StringType Get(const ValueType& v) { return StringType(v.GetString(), v.GetStringLength()); } + static ValueType& Set(ValueType& v, const StringType& data, typename ValueType::AllocatorType& a) { return v.SetString(data, a); } +}; +#endif + +template +struct TypeHelper { + typedef typename ValueType::Array ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(ValueType& v) { return v.GetArray(); } + static ValueType& Set(ValueType& v, ArrayType data) { return v = data; } + static ValueType& Set(ValueType& v, ArrayType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstArray ArrayType; + static bool Is(const ValueType& v) { return v.IsArray(); } + static ArrayType Get(const ValueType& v) { return v.GetArray(); } +}; + +template +struct TypeHelper { + typedef typename ValueType::Object ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(ValueType& v) { return v.GetObject(); } + static ValueType& Set(ValueType& v, ObjectType data) { return v = data; } + static ValueType& Set(ValueType& v, ObjectType data, typename ValueType::AllocatorType&) { return v = data; } +}; + +template +struct TypeHelper { + typedef typename ValueType::ConstObject ObjectType; + static bool Is(const ValueType& v) { return v.IsObject(); } + static ObjectType Get(const ValueType& v) { return v.GetObject(); } +}; + +} // namespace internal + +// Forward declarations +template class GenericArray; +template class GenericObject; + +/////////////////////////////////////////////////////////////////////////////// +// GenericValue + +//! Represents a JSON value. Use Value for UTF8 encoding and default allocator. +/*! + A JSON value can be one of 7 types. This class is a variant type supporting + these types. + + Use the Value if UTF8 and default allocator + + \tparam Encoding Encoding of the value. (Even non-string values need to have the same encoding in a document) + \tparam Allocator Allocator type for allocating memory of object, array and string. +*/ +template +class GenericValue { +public: + //! Name-value pair in an object. + typedef GenericMember Member; + typedef Encoding EncodingType; //!< Encoding type from template parameter. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericStringRef StringRefType; //!< Reference to a constant string + typedef typename GenericMemberIterator::Iterator MemberIterator; //!< Member iterator for iterating in object. + typedef typename GenericMemberIterator::Iterator ConstMemberIterator; //!< Constant member iterator for iterating in object. + typedef GenericValue* ValueIterator; //!< Value iterator for iterating in array. + typedef const GenericValue* ConstValueIterator; //!< Constant value iterator for iterating in array. + typedef GenericValue ValueType; //!< Value type of itself. + typedef GenericArray Array; + typedef GenericArray ConstArray; + typedef GenericObject Object; + typedef GenericObject ConstObject; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor creates a null value. + GenericValue() RAPIDJSON_NOEXCEPT : data_() { data_.f.flags = kNullFlag; } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericValue(GenericValue&& rhs) RAPIDJSON_NOEXCEPT : data_(rhs.data_) { + rhs.data_.f.flags = kNullFlag; // give up contents + } +#endif + +private: + //! Copy constructor is not permitted. + GenericValue(const GenericValue& rhs); + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Moving from a GenericDocument is not permitted. + template + GenericValue(GenericDocument&& rhs); + + //! Move assignment from a GenericDocument is not permitted. + template + GenericValue& operator=(GenericDocument&& rhs); +#endif + +public: + + //! Constructor with JSON value type. + /*! This creates a Value of specified type with default content. + \param type Type of the value. + \note Default content for number is zero. + */ + explicit GenericValue(Type type) RAPIDJSON_NOEXCEPT : data_() { + static const uint16_t defaultFlags[] = { + kNullFlag, kFalseFlag, kTrueFlag, kObjectFlag, kArrayFlag, kShortStringFlag, + kNumberAnyFlag + }; + RAPIDJSON_NOEXCEPT_ASSERT(type >= kNullType && type <= kNumberType); + data_.f.flags = defaultFlags[type]; + + // Use ShortString to store empty string. + if (type == kStringType) + data_.ss.SetLength(0); + } + + //! Explicit copy constructor (with allocator) + /*! Creates a copy of a Value by using the given Allocator + \tparam SourceAllocator allocator of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator for allocating copied elements and buffers. Commonly use GenericDocument::GetAllocator(). + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + \see CopyFrom() + */ + template + GenericValue(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + switch (rhs.GetType()) { + case kObjectType: { + SizeType count = rhs.data_.o.size; + Member* lm = reinterpret_cast(allocator.Malloc(count * sizeof(Member))); + const typename GenericValue::Member* rm = rhs.GetMembersPointer(); + for (SizeType i = 0; i < count; i++) { + new (&lm[i].name) GenericValue(rm[i].name, allocator, copyConstStrings); + new (&lm[i].value) GenericValue(rm[i].value, allocator, copyConstStrings); + } + data_.f.flags = kObjectFlag; + data_.o.size = data_.o.capacity = count; + SetMembersPointer(lm); + } + break; + case kArrayType: { + SizeType count = rhs.data_.a.size; + GenericValue* le = reinterpret_cast(allocator.Malloc(count * sizeof(GenericValue))); + const GenericValue* re = rhs.GetElementsPointer(); + for (SizeType i = 0; i < count; i++) + new (&le[i]) GenericValue(re[i], allocator, copyConstStrings); + data_.f.flags = kArrayFlag; + data_.a.size = data_.a.capacity = count; + SetElementsPointer(le); + } + break; + case kStringType: + if (rhs.data_.f.flags == kConstStringFlag && !copyConstStrings) { + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + } + else + SetStringRaw(StringRef(rhs.GetString(), rhs.GetStringLength()), allocator); + break; + default: + data_.f.flags = rhs.data_.f.flags; + data_ = *reinterpret_cast(&rhs.data_); + break; + } + } + + //! Constructor for boolean value. + /*! \param b Boolean value + \note This constructor is limited to \em real boolean values and rejects + implicitly converted types like arbitrary pointers. Use an explicit cast + to \c bool, if you want to construct a boolean JSON value in such cases. + */ +#ifndef RAPIDJSON_DOXYGEN_RUNNING // hide SFINAE from Doxygen + template + explicit GenericValue(T b, RAPIDJSON_ENABLEIF((internal::IsSame))) RAPIDJSON_NOEXCEPT // See #472 +#else + explicit GenericValue(bool b) RAPIDJSON_NOEXCEPT +#endif + : data_() { + // safe-guard against failing SFINAE + RAPIDJSON_STATIC_ASSERT((internal::IsSame::Value)); + data_.f.flags = b ? kTrueFlag : kFalseFlag; + } + + //! Constructor for int value. + explicit GenericValue(int i) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i; + data_.f.flags = (i >= 0) ? (kNumberIntFlag | kUintFlag | kUint64Flag) : kNumberIntFlag; + } + + //! Constructor for unsigned value. + explicit GenericValue(unsigned u) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u; + data_.f.flags = (u & 0x80000000) ? kNumberUintFlag : (kNumberUintFlag | kIntFlag | kInt64Flag); + } + + //! Constructor for int64_t value. + explicit GenericValue(int64_t i64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.i64 = i64; + data_.f.flags = kNumberInt64Flag; + if (i64 >= 0) { + data_.f.flags |= kNumberUint64Flag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(static_cast(i64) & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + else if (i64 >= static_cast(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for uint64_t value. + explicit GenericValue(uint64_t u64) RAPIDJSON_NOEXCEPT : data_() { + data_.n.u64 = u64; + data_.f.flags = kNumberUint64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0x80000000, 0x00000000))) + data_.f.flags |= kInt64Flag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x00000000))) + data_.f.flags |= kUintFlag; + if (!(u64 & RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0x80000000))) + data_.f.flags |= kIntFlag; + } + + //! Constructor for double value. + explicit GenericValue(double d) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = d; data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for float value. + explicit GenericValue(float f) RAPIDJSON_NOEXCEPT : data_() { data_.n.d = static_cast(f); data_.f.flags = kNumberDoubleFlag; } + + //! Constructor for constant string (i.e. do not make a copy of string) + GenericValue(const Ch* s, SizeType length) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(StringRef(s, length)); } + + //! Constructor for constant string (i.e. do not make a copy of string) + explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch* s, SizeType length, Allocator& allocator) : data_() { SetStringRaw(StringRef(s, length), allocator); } + + //! Constructor for copy-string (i.e. do make a copy of string) + GenericValue(const Ch*s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor for copy-string from a string object (i.e. do make a copy of string) + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue(const std::basic_string& s, Allocator& allocator) : data_() { SetStringRaw(StringRef(s), allocator); } +#endif + + //! Constructor for Array. + /*! + \param a An array obtained by \c GetArray(). + \note \c Array is always pass-by-value. + \note the source array is moved into this value and the sourec array becomes empty. + */ + GenericValue(Array a) RAPIDJSON_NOEXCEPT : data_(a.value_.data_) { + a.value_.data_ = Data(); + a.value_.data_.f.flags = kArrayFlag; + } + + //! Constructor for Object. + /*! + \param o An object obtained by \c GetObject(). + \note \c Object is always pass-by-value. + \note the source object is moved into this value and the sourec object becomes empty. + */ + GenericValue(Object o) RAPIDJSON_NOEXCEPT : data_(o.value_.data_) { + o.value_.data_ = Data(); + o.value_.data_.f.flags = kObjectFlag; + } + + //! Destructor. + /*! Need to destruct elements of array, members of object, or copy-string. + */ + ~GenericValue() { + if (Allocator::kNeedFree) { // Shortcut by Allocator's trait + switch(data_.f.flags) { + case kArrayFlag: + { + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + Allocator::Free(e); + } + break; + + case kObjectFlag: + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + Allocator::Free(GetMembersPointer()); + break; + + case kCopyStringFlag: + Allocator::Free(const_cast(GetStringPointer())); + break; + + default: + break; // Do nothing for other types. + } + } + } + + //@} + + //!@name Assignment operators + //@{ + + //! Assignment with move semantics. + /*! \param rhs Source of the assignment. It will become a null value after assignment. + */ + GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + if (RAPIDJSON_LIKELY(this != &rhs)) { + this->~GenericValue(); + RawAssign(rhs); + } + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericValue& operator=(GenericValue&& rhs) RAPIDJSON_NOEXCEPT { + return *this = rhs.Move(); + } +#endif + + //! Assignment of constant string reference (no copy) + /*! \param str Constant string reference to be assigned + \note This overload is needed to avoid clashes with the generic primitive type assignment overload below. + \see GenericStringRef, operator=(T) + */ + GenericValue& operator=(StringRefType str) RAPIDJSON_NOEXCEPT { + GenericValue s(str); + return *this = s; + } + + //! Assignment with primitive types. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value The value to be assigned. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref SetString(const Ch*, Allocator&) (for copying) or + \ref StringRef() (to explicitly mark the pointer as constant) instead. + All other pointer types would implicitly convert to \c bool, + use \ref SetBool() instead. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::IsPointer), (GenericValue&)) + operator=(T value) { + GenericValue v(value); + return *this = v; + } + + //! Deep-copy assignment from Value + /*! Assigns a \b copy of the Value to the current Value object + \tparam SourceAllocator Allocator type of \c rhs + \param rhs Value to copy from (read-only) + \param allocator Allocator to use for copying + \param copyConstStrings Force copying of constant strings (e.g. referencing an in-situ buffer) + */ + template + GenericValue& CopyFrom(const GenericValue& rhs, Allocator& allocator, bool copyConstStrings = false) { + RAPIDJSON_ASSERT(static_cast(this) != static_cast(&rhs)); + this->~GenericValue(); + new (this) GenericValue(rhs, allocator, copyConstStrings); + return *this; + } + + //! Exchange the contents of this value with those of other. + /*! + \param other Another value. + \note Constant complexity. + */ + GenericValue& Swap(GenericValue& other) RAPIDJSON_NOEXCEPT { + GenericValue temp; + temp.RawAssign(*this); + RawAssign(other); + other.RawAssign(temp); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.value, b.value); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericValue& a, GenericValue& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Prepare Value for move semantics + /*! \return *this */ + GenericValue& Move() RAPIDJSON_NOEXCEPT { return *this; } + //@} + + //!@name Equal-to and not-equal-to operators + //@{ + //! Equal-to operator + /*! + \note If an object contains duplicated named member, comparing equality with any object is always \c false. + \note Complexity is quadratic in Object's member number and linear for the rest (number of all values in the subtree and total lengths of all strings). + */ + template + bool operator==(const GenericValue& rhs) const { + typedef GenericValue RhsType; + if (GetType() != rhs.GetType()) + return false; + + switch (GetType()) { + case kObjectType: // Warning: O(n^2) inner-loop + if (data_.o.size != rhs.data_.o.size) + return false; + for (ConstMemberIterator lhsMemberItr = MemberBegin(); lhsMemberItr != MemberEnd(); ++lhsMemberItr) { + typename RhsType::ConstMemberIterator rhsMemberItr = rhs.FindMember(lhsMemberItr->name); + if (rhsMemberItr == rhs.MemberEnd() || lhsMemberItr->value != rhsMemberItr->value) + return false; + } + return true; + + case kArrayType: + if (data_.a.size != rhs.data_.a.size) + return false; + for (SizeType i = 0; i < data_.a.size; i++) + if ((*this)[i] != rhs[i]) + return false; + return true; + + case kStringType: + return StringEqual(rhs); + + case kNumberType: + if (IsDouble() || rhs.IsDouble()) { + double a = GetDouble(); // May convert from integer to double. + double b = rhs.GetDouble(); // Ditto + return a >= b && a <= b; // Prevent -Wfloat-equal + } + else + return data_.n.u64 == rhs.data_.n.u64; + + default: + return true; + } + } + + //! Equal-to operator with const C-string pointer + bool operator==(const Ch* rhs) const { return *this == GenericValue(StringRef(rhs)); } + +#if RAPIDJSON_HAS_STDSTRING + //! Equal-to operator with string object + /*! \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + bool operator==(const std::basic_string& rhs) const { return *this == GenericValue(StringRef(rhs)); } +#endif + + //! Equal-to operator with primitive types + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c true, \c false + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr,internal::IsGenericValue >), (bool)) operator==(const T& rhs) const { return *this == GenericValue(rhs); } + + //! Not-equal-to operator + /*! \return !(*this == rhs) + */ + template + bool operator!=(const GenericValue& rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with const C-string pointer + bool operator!=(const Ch* rhs) const { return !(*this == rhs); } + + //! Not-equal-to operator with arbitrary types + /*! \return !(*this == rhs) + */ + template RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& rhs) const { return !(*this == rhs); } + + //! Equal-to operator with arbitrary types (symmetric version) + /*! \return (rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator==(const T& lhs, const GenericValue& rhs) { return rhs == lhs; } + + //! Not-Equal-to operator with arbitrary types (symmetric version) + /*! \return !(rhs == lhs) + */ + template friend RAPIDJSON_DISABLEIF_RETURN((internal::IsGenericValue), (bool)) operator!=(const T& lhs, const GenericValue& rhs) { return !(rhs == lhs); } + //@} + + //!@name Type + //@{ + + Type GetType() const { return static_cast(data_.f.flags & kTypeMask); } + bool IsNull() const { return data_.f.flags == kNullFlag; } + bool IsFalse() const { return data_.f.flags == kFalseFlag; } + bool IsTrue() const { return data_.f.flags == kTrueFlag; } + bool IsBool() const { return (data_.f.flags & kBoolFlag) != 0; } + bool IsObject() const { return data_.f.flags == kObjectFlag; } + bool IsArray() const { return data_.f.flags == kArrayFlag; } + bool IsNumber() const { return (data_.f.flags & kNumberFlag) != 0; } + bool IsInt() const { return (data_.f.flags & kIntFlag) != 0; } + bool IsUint() const { return (data_.f.flags & kUintFlag) != 0; } + bool IsInt64() const { return (data_.f.flags & kInt64Flag) != 0; } + bool IsUint64() const { return (data_.f.flags & kUint64Flag) != 0; } + bool IsDouble() const { return (data_.f.flags & kDoubleFlag) != 0; } + bool IsString() const { return (data_.f.flags & kStringFlag) != 0; } + + // Checks whether a number can be losslessly converted to a double. + bool IsLosslessDouble() const { + if (!IsNumber()) return false; + if (IsUint64()) { + uint64_t u = GetUint64(); + volatile double d = static_cast(u); + return (d >= 0.0) + && (d < static_cast((std::numeric_limits::max)())) + && (u == static_cast(d)); + } + if (IsInt64()) { + int64_t i = GetInt64(); + volatile double d = static_cast(i); + return (d >= static_cast((std::numeric_limits::min)())) + && (d < static_cast((std::numeric_limits::max)())) + && (i == static_cast(d)); + } + return true; // double, int, uint are always lossless + } + + // Checks whether a number is a float (possible lossy). + bool IsFloat() const { + if ((data_.f.flags & kDoubleFlag) == 0) + return false; + double d = GetDouble(); + return d >= -3.4028234e38 && d <= 3.4028234e38; + } + // Checks whether a number can be losslessly converted to a float. + bool IsLosslessFloat() const { + if (!IsNumber()) return false; + double a = GetDouble(); + if (a < static_cast(-(std::numeric_limits::max)()) + || a > static_cast((std::numeric_limits::max)())) + return false; + double b = static_cast(static_cast(a)); + return a >= b && a <= b; // Prevent -Wfloat-equal + } + + //@} + + //!@name Null + //@{ + + GenericValue& SetNull() { this->~GenericValue(); new (this) GenericValue(); return *this; } + + //@} + + //!@name Bool + //@{ + + bool GetBool() const { RAPIDJSON_ASSERT(IsBool()); return data_.f.flags == kTrueFlag; } + //!< Set boolean value + /*! \post IsBool() == true */ + GenericValue& SetBool(bool b) { this->~GenericValue(); new (this) GenericValue(b); return *this; } + + //@} + + //!@name Object + //@{ + + //! Set this value as an empty object. + /*! \post IsObject() == true */ + GenericValue& SetObject() { this->~GenericValue(); new (this) GenericValue(kObjectType); return *this; } + + //! Get the number of members in the object. + SizeType MemberCount() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size; } + + //! Get the capacity of object. + SizeType MemberCapacity() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.capacity; } + + //! Check whether the object is empty. + bool ObjectEmpty() const { RAPIDJSON_ASSERT(IsObject()); return data_.o.size == 0; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam T Either \c Ch or \c const \c Ch (template used for disambiguation with \ref operator[](SizeType)) + \note In version 0.1x, if the member is not found, this function returns a null value. This makes issue 7. + Since 0.2, if the name is not correct, it will assert. + If user is unsure whether a member exists, user should use HasMember() first. + A better approach is to use FindMember(). + \note Linear time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(GenericValue&)) operator[](T* name) { + GenericValue n(StringRef(name)); + return (*this)[n]; + } + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >),(const GenericValue&)) operator[](T* name) const { return const_cast(*this)[name]; } + + //! Get a value from an object associated with the name. + /*! \pre IsObject() == true + \tparam SourceAllocator Allocator of the \c name value + + \note Compared to \ref operator[](T*), this version is faster because it does not need a StrLen(). + And it can also handle strings with embedded null characters. + + \note Linear time complexity. + */ + template + GenericValue& operator[](const GenericValue& name) { + MemberIterator member = FindMember(name); + if (member != MemberEnd()) + return member->value; + else { + RAPIDJSON_ASSERT(false); // see above note + + // This will generate -Wexit-time-destructors in clang + // static GenericValue NullValue; + // return NullValue; + + // Use static buffer and placement-new to prevent destruction + static char buffer[sizeof(GenericValue)]; + return *new (buffer) GenericValue(); + } + } + template + const GenericValue& operator[](const GenericValue& name) const { return const_cast(*this)[name]; } + +#if RAPIDJSON_HAS_STDSTRING + //! Get a value from an object associated with name (string object). + GenericValue& operator[](const std::basic_string& name) { return (*this)[GenericValue(StringRef(name))]; } + const GenericValue& operator[](const std::basic_string& name) const { return (*this)[GenericValue(StringRef(name))]; } +#endif + + //! Const member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberBegin() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer()); } + //! Const \em past-the-end member iterator + /*! \pre IsObject() == true */ + ConstMemberIterator MemberEnd() const { RAPIDJSON_ASSERT(IsObject()); return ConstMemberIterator(GetMembersPointer() + data_.o.size); } + //! Member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberBegin() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer()); } + //! \em Past-the-end member iterator + /*! \pre IsObject() == true */ + MemberIterator MemberEnd() { RAPIDJSON_ASSERT(IsObject()); return MemberIterator(GetMembersPointer() + data_.o.size); } + + //! Request the object to have enough capacity to store members. + /*! \param newCapacity The capacity that the object at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& MemberReserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsObject()); + if (newCapacity > data_.o.capacity) { + SetMembersPointer(reinterpret_cast(allocator.Realloc(GetMembersPointer(), data_.o.capacity * sizeof(Member), newCapacity * sizeof(Member)))); + data_.o.capacity = newCapacity; + } + return *this; + } + + //! Check whether a member exists in the object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const Ch* name) const { return FindMember(name) != MemberEnd(); } + +#if RAPIDJSON_HAS_STDSTRING + //! Check whether a member exists in the object with string object. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + bool HasMember(const std::basic_string& name) const { return FindMember(name) != MemberEnd(); } +#endif + + //! Check whether a member exists in the object with GenericValue name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Whether a member with that name exists. + \note It is better to use FindMember() directly if you need the obtain the value as well. + \note Linear time complexity. + */ + template + bool HasMember(const GenericValue& name) const { return FindMember(name) != MemberEnd(); } + + //! Find member by name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + MemberIterator FindMember(const Ch* name) { + GenericValue n(StringRef(name)); + return FindMember(n); + } + + ConstMemberIterator FindMember(const Ch* name) const { return const_cast(*this).FindMember(name); } + + //! Find member by name. + /*! + This version is faster because it does not need a StrLen(). It can also handle string with null character. + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + + \note Earlier versions of Rapidjson returned a \c NULL pointer, in case + the requested member doesn't exist. For consistency with e.g. + \c std::map, this has been changed to MemberEnd() now. + \note Linear time complexity. + */ + template + MemberIterator FindMember(const GenericValue& name) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + MemberIterator member = MemberBegin(); + for ( ; member != MemberEnd(); ++member) + if (name.StringEqual(member->name)) + break; + return member; + } + template ConstMemberIterator FindMember(const GenericValue& name) const { return const_cast(*this).FindMember(name); } + +#if RAPIDJSON_HAS_STDSTRING + //! Find member by string object name. + /*! + \param name Member name to be searched. + \pre IsObject() == true + \return Iterator to member, if it exists. + Otherwise returns \ref MemberEnd(). + */ + MemberIterator FindMember(const std::basic_string& name) { return FindMember(GenericValue(StringRef(name))); } + ConstMemberIterator FindMember(const std::basic_string& name) const { return FindMember(GenericValue(StringRef(name))); } +#endif + + //! Add a member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c name and \c value will be transferred to this object on success. + \pre IsObject() && name.IsString() + \post name.IsNull() && value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(name.IsString()); + + ObjectData& o = data_.o; + if (o.size >= o.capacity) + MemberReserve(o.capacity == 0 ? kDefaultObjectCapacity : (o.capacity + (o.capacity + 1) / 2), allocator); + Member* members = GetMembersPointer(); + members[o.size].name.RawAssign(name); + members[o.size].value.RawAssign(value); + o.size++; + return *this; + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Add a string object as member (name-value pair) to the object. + /*! \param name A string value as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(GenericValue&,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(GenericValue& name, std::basic_string& value, Allocator& allocator) { + GenericValue v(value, allocator); + return AddMember(name, v, allocator); + } +#endif + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A string value as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(GenericValue& name, T value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& AddMember(GenericValue&& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue&& name, GenericValue& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(GenericValue& name, GenericValue&& value, Allocator& allocator) { + return AddMember(name, value, allocator); + } + GenericValue& AddMember(StringRefType name, GenericValue&& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + + //! Add a member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value Value of any type. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this object on success. + \pre IsObject() + \post value.IsNull() + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, GenericValue& value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Add a constant string value as member (name-value pair) to the object. + /*! \param name A constant string reference as name of member. + \param value constant string reference as value of member. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + \note This overload is needed to avoid clashes with the generic primitive type AddMember(StringRefType,T,Allocator&) overload below. + \note Amortized Constant time complexity. + */ + GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { + GenericValue v(value); + return AddMember(name, v, allocator); + } + + //! Add any primitive value as member (name-value pair) to the object. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param name A constant string reference as name of member. + \param value Value of primitive type \c T as value of member + \param allocator Allocator for reallocating memory. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \pre IsObject() + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref AddMember(StringRefType, GenericValue&, Allocator&) or \ref + AddMember(StringRefType, StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized Constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + AddMember(StringRefType name, T value, Allocator& allocator) { + GenericValue n(name); + return AddMember(n, value, allocator); + } + + //! Remove all members in the object. + /*! This function do not deallocate memory in the object, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void RemoveAllMembers() { + RAPIDJSON_ASSERT(IsObject()); + for (MemberIterator m = MemberBegin(); m != MemberEnd(); ++m) + m->~Member(); + data_.o.size = 0; + } + + //! Remove a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Linear time complexity. + */ + bool RemoveMember(const Ch* name) { + GenericValue n(StringRef(name)); + return RemoveMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) { return RemoveMember(GenericValue(StringRef(name))); } +#endif + + template + bool RemoveMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + RemoveMember(m); + return true; + } + else + return false; + } + + //! Remove a member in object by iterator. + /*! \param m member iterator (obtained by FindMember() or MemberBegin()). + \return the new iterator after removal. + \note This function may reorder the object members. Use \ref + EraseMember(ConstMemberIterator) if you need to preserve the + relative order of the remaining members. + \note Constant time complexity. + */ + MemberIterator RemoveMember(MemberIterator m) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(m >= MemberBegin() && m < MemberEnd()); + + MemberIterator last(GetMembersPointer() + (data_.o.size - 1)); + if (data_.o.size > 1 && m != last) + *m = *last; // Move the last one to this place + else + m->~Member(); // Only one left, just destroy + --data_.o.size; + return m; + } + + //! Remove a member from an object by iterator. + /*! \param pos iterator to the member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c pos < \ref MemberEnd() + \return Iterator following the removed element. + If the iterator \c pos refers to the last element, the \ref MemberEnd() iterator is returned. + \note This function preserves the relative order of the remaining object + members. If you do not need this, use the more efficient \ref RemoveMember(MemberIterator). + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator pos) { + return EraseMember(pos, pos +1); + } + + //! Remove members in the range [first, last) from an object. + /*! \param first iterator to the first member to remove + \param last iterator following the last member to remove + \pre IsObject() == true && \ref MemberBegin() <= \c first <= \c last <= \ref MemberEnd() + \return Iterator following the last removed element. + \note This function preserves the relative order of the remaining object + members. + \note Linear time complexity. + */ + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) { + RAPIDJSON_ASSERT(IsObject()); + RAPIDJSON_ASSERT(data_.o.size > 0); + RAPIDJSON_ASSERT(GetMembersPointer() != 0); + RAPIDJSON_ASSERT(first >= MemberBegin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= MemberEnd()); + + MemberIterator pos = MemberBegin() + (first - MemberBegin()); + for (MemberIterator itr = pos; itr != last; ++itr) + itr->~Member(); + std::memmove(static_cast(&*pos), &*last, static_cast(MemberEnd() - last) * sizeof(Member)); + data_.o.size -= static_cast(last - first); + return pos; + } + + //! Erase a member in object by its name. + /*! \param name Name of member to be removed. + \return Whether the member existed. + \note Linear time complexity. + */ + bool EraseMember(const Ch* name) { + GenericValue n(StringRef(name)); + return EraseMember(n); + } + +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) { return EraseMember(GenericValue(StringRef(name))); } +#endif + + template + bool EraseMember(const GenericValue& name) { + MemberIterator m = FindMember(name); + if (m != MemberEnd()) { + EraseMember(m); + return true; + } + else + return false; + } + + Object GetObject() { RAPIDJSON_ASSERT(IsObject()); return Object(*this); } + ConstObject GetObject() const { RAPIDJSON_ASSERT(IsObject()); return ConstObject(*this); } + + //@} + + //!@name Array + //@{ + + //! Set this value as an empty array. + /*! \post IsArray == true */ + GenericValue& SetArray() { this->~GenericValue(); new (this) GenericValue(kArrayType); return *this; } + + //! Get the number of elements in array. + SizeType Size() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size; } + + //! Get the capacity of array. + SizeType Capacity() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.capacity; } + + //! Check whether the array is empty. + bool Empty() const { RAPIDJSON_ASSERT(IsArray()); return data_.a.size == 0; } + + //! Remove all elements in the array. + /*! This function do not deallocate memory in the array, i.e. the capacity is unchanged. + \note Linear time complexity. + */ + void Clear() { + RAPIDJSON_ASSERT(IsArray()); + GenericValue* e = GetElementsPointer(); + for (GenericValue* v = e; v != e + data_.a.size; ++v) + v->~GenericValue(); + data_.a.size = 0; + } + + //! Get an element from array by index. + /*! \pre IsArray() == true + \param index Zero-based index of element. + \see operator[](T*) + */ + GenericValue& operator[](SizeType index) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(index < data_.a.size); + return GetElementsPointer()[index]; + } + const GenericValue& operator[](SizeType index) const { return const_cast(*this)[index]; } + + //! Element iterator + /*! \pre IsArray() == true */ + ValueIterator Begin() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer(); } + //! \em Past-the-end element iterator + /*! \pre IsArray() == true */ + ValueIterator End() { RAPIDJSON_ASSERT(IsArray()); return GetElementsPointer() + data_.a.size; } + //! Constant element iterator + /*! \pre IsArray() == true */ + ConstValueIterator Begin() const { return const_cast(*this).Begin(); } + //! Constant \em past-the-end element iterator + /*! \pre IsArray() == true */ + ConstValueIterator End() const { return const_cast(*this).End(); } + + //! Request the array to have enough capacity to store elements. + /*! \param newCapacity The capacity that the array at least need to have. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \note Linear time complexity. + */ + GenericValue& Reserve(SizeType newCapacity, Allocator &allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (newCapacity > data_.a.capacity) { + SetElementsPointer(reinterpret_cast(allocator.Realloc(GetElementsPointer(), data_.a.capacity * sizeof(GenericValue), newCapacity * sizeof(GenericValue)))); + data_.a.capacity = newCapacity; + } + return *this; + } + + //! Append a GenericValue at the end of the array. + /*! \param value Value to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \post value.IsNull() == true + \return The value itself for fluent API. + \note The ownership of \c value will be transferred to this array on success. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + */ + GenericValue& PushBack(GenericValue& value, Allocator& allocator) { + RAPIDJSON_ASSERT(IsArray()); + if (data_.a.size >= data_.a.capacity) + Reserve(data_.a.capacity == 0 ? kDefaultArrayCapacity : (data_.a.capacity + (data_.a.capacity + 1) / 2), allocator); + GetElementsPointer()[data_.a.size++].RawAssign(value); + return *this; + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericValue& PushBack(GenericValue&& value, Allocator& allocator) { + return PushBack(value, allocator); + } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + + //! Append a constant string reference at the end of the array. + /*! \param value Constant string reference to be appended. + \param allocator Allocator for reallocating memory. It must be the same one used previously. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + \note Amortized constant time complexity. + \see GenericStringRef + */ + GenericValue& PushBack(StringRefType value, Allocator& allocator) { + return (*this).template PushBack(value, allocator); + } + + //! Append a primitive value at the end of the array. + /*! \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t + \param value Value of primitive type T to be appended. + \param allocator Allocator for reallocating memory. It must be the same one as used before. Commonly use GenericDocument::GetAllocator(). + \pre IsArray() == true + \return The value itself for fluent API. + \note If the number of elements to be appended is known, calls Reserve() once first may be more efficient. + + \note The source type \c T explicitly disallows all pointer types, + especially (\c const) \ref Ch*. This helps avoiding implicitly + referencing character strings with insufficient lifetime, use + \ref PushBack(GenericValue&, Allocator&) or \ref + PushBack(StringRefType, Allocator&). + All other pointer types would implicitly convert to \c bool, + use an explicit cast instead, if needed. + \note Amortized constant time complexity. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericValue&)) + PushBack(T value, Allocator& allocator) { + GenericValue v(value); + return PushBack(v, allocator); + } + + //! Remove the last element in the array. + /*! + \note Constant time complexity. + */ + GenericValue& PopBack() { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(!Empty()); + GetElementsPointer()[--data_.a.size].~GenericValue(); + return *this; + } + + //! Remove an element of array by iterator. + /*! + \param pos iterator to the element to remove + \pre IsArray() == true && \ref Begin() <= \c pos < \ref End() + \return Iterator following the removed element. If the iterator pos refers to the last element, the End() iterator is returned. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator pos) { + return Erase(pos, pos + 1); + } + + //! Remove elements in the range [first, last) of the array. + /*! + \param first iterator to the first element to remove + \param last iterator following the last element to remove + \pre IsArray() == true && \ref Begin() <= \c first <= \c last <= \ref End() + \return Iterator following the last removed element. + \note Linear time complexity. + */ + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) { + RAPIDJSON_ASSERT(IsArray()); + RAPIDJSON_ASSERT(data_.a.size > 0); + RAPIDJSON_ASSERT(GetElementsPointer() != 0); + RAPIDJSON_ASSERT(first >= Begin()); + RAPIDJSON_ASSERT(first <= last); + RAPIDJSON_ASSERT(last <= End()); + ValueIterator pos = Begin() + (first - Begin()); + for (ValueIterator itr = pos; itr != last; ++itr) + itr->~GenericValue(); + std::memmove(static_cast(pos), last, static_cast(End() - last) * sizeof(GenericValue)); + data_.a.size -= static_cast(last - first); + return pos; + } + + Array GetArray() { RAPIDJSON_ASSERT(IsArray()); return Array(*this); } + ConstArray GetArray() const { RAPIDJSON_ASSERT(IsArray()); return ConstArray(*this); } + + //@} + + //!@name Number + //@{ + + int GetInt() const { RAPIDJSON_ASSERT(data_.f.flags & kIntFlag); return data_.n.i.i; } + unsigned GetUint() const { RAPIDJSON_ASSERT(data_.f.flags & kUintFlag); return data_.n.u.u; } + int64_t GetInt64() const { RAPIDJSON_ASSERT(data_.f.flags & kInt64Flag); return data_.n.i64; } + uint64_t GetUint64() const { RAPIDJSON_ASSERT(data_.f.flags & kUint64Flag); return data_.n.u64; } + + //! Get the value as double type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessDouble() to check whether the converison is lossless. + */ + double GetDouble() const { + RAPIDJSON_ASSERT(IsNumber()); + if ((data_.f.flags & kDoubleFlag) != 0) return data_.n.d; // exact type, no conversion. + if ((data_.f.flags & kIntFlag) != 0) return data_.n.i.i; // int -> double + if ((data_.f.flags & kUintFlag) != 0) return data_.n.u.u; // unsigned -> double + if ((data_.f.flags & kInt64Flag) != 0) return static_cast(data_.n.i64); // int64_t -> double (may lose precision) + RAPIDJSON_ASSERT((data_.f.flags & kUint64Flag) != 0); return static_cast(data_.n.u64); // uint64_t -> double (may lose precision) + } + + //! Get the value as float type. + /*! \note If the value is 64-bit integer type, it may lose precision. Use \c IsLosslessFloat() to check whether the converison is lossless. + */ + float GetFloat() const { + return static_cast(GetDouble()); + } + + GenericValue& SetInt(int i) { this->~GenericValue(); new (this) GenericValue(i); return *this; } + GenericValue& SetUint(unsigned u) { this->~GenericValue(); new (this) GenericValue(u); return *this; } + GenericValue& SetInt64(int64_t i64) { this->~GenericValue(); new (this) GenericValue(i64); return *this; } + GenericValue& SetUint64(uint64_t u64) { this->~GenericValue(); new (this) GenericValue(u64); return *this; } + GenericValue& SetDouble(double d) { this->~GenericValue(); new (this) GenericValue(d); return *this; } + GenericValue& SetFloat(float f) { this->~GenericValue(); new (this) GenericValue(static_cast(f)); return *this; } + + //@} + + //!@name String + //@{ + + const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return (data_.f.flags & kInlineStrFlag) ? data_.ss.str : GetStringPointer(); } + + //! Get the length of string. + /*! Since rapidjson permits "\\u0000" in the json string, strlen(v.GetString()) may not equal to v.GetStringLength(). + */ + SizeType GetStringLength() const { RAPIDJSON_ASSERT(IsString()); return ((data_.f.flags & kInlineStrFlag) ? (data_.ss.GetLength()) : data_.s.length); } + + //! Set this value as a string without copying source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string pointer. + \param length The length of source string, excluding the trailing null terminator. + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == length + \see SetString(StringRefType) + */ + GenericValue& SetString(const Ch* s, SizeType length) { return SetString(StringRef(s, length)); } + + //! Set this value as a string without copying source string. + /*! \param s source string reference + \return The value itself for fluent API. + \post IsString() == true && GetString() == s && GetStringLength() == s.length + */ + GenericValue& SetString(StringRefType s) { this->~GenericValue(); SetStringRaw(s); return *this; } + + //! Set this value as a string by copying from source string. + /*! This version has better performance with supplied length, and also support string containing null character. + \param s source string. + \param length The length of source string, excluding the trailing null terminator. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, SizeType length, Allocator& allocator) { return SetString(StringRef(s, length), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(const Ch* s, Allocator& allocator) { return SetString(StringRef(s), allocator); } + + //! Set this value as a string by copying from source string. + /*! \param s source string reference + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.s && strcmp(GetString(),s) == 0 && GetStringLength() == length + */ + GenericValue& SetString(StringRefType s, Allocator& allocator) { this->~GenericValue(); SetStringRaw(s, allocator); return *this; } + +#if RAPIDJSON_HAS_STDSTRING + //! Set this value as a string by copying from source string. + /*! \param s source string. + \param allocator Allocator for allocating copied buffer. Commonly use GenericDocument::GetAllocator(). + \return The value itself for fluent API. + \post IsString() == true && GetString() != s.data() && strcmp(GetString(),s.data() == 0 && GetStringLength() == s.size() + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + GenericValue& SetString(const std::basic_string& s, Allocator& allocator) { return SetString(StringRef(s), allocator); } +#endif + + //@} + + //!@name Array + //@{ + + //! Templated version for checking whether this value is type T. + /*! + \tparam T Either \c bool, \c int, \c unsigned, \c int64_t, \c uint64_t, \c double, \c float, \c const \c char*, \c std::basic_string + */ + template + bool Is() const { return internal::TypeHelper::Is(*this); } + + template + T Get() const { return internal::TypeHelper::Get(*this); } + + template + T Get() { return internal::TypeHelper::Get(*this); } + + template + ValueType& Set(const T& data) { return internal::TypeHelper::Set(*this, data); } + + template + ValueType& Set(const T& data, AllocatorType& allocator) { return internal::TypeHelper::Set(*this, data, allocator); } + + //@} + + //! Generate events of this value to a Handler. + /*! This function adopts the GoF visitor pattern. + Typical usage is to output this JSON value as JSON text via Writer, which is a Handler. + It can also be used to deep clone this value via GenericDocument, which is also a Handler. + \tparam Handler type of handler. + \param handler An object implementing concept Handler. + */ + template + bool Accept(Handler& handler) const { + switch(GetType()) { + case kNullType: return handler.Null(); + case kFalseType: return handler.Bool(false); + case kTrueType: return handler.Bool(true); + + case kObjectType: + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + return false; + for (ConstMemberIterator m = MemberBegin(); m != MemberEnd(); ++m) { + RAPIDJSON_ASSERT(m->name.IsString()); // User may change the type of name by MemberIterator. + if (RAPIDJSON_UNLIKELY(!handler.Key(m->name.GetString(), m->name.GetStringLength(), (m->name.data_.f.flags & kCopyFlag) != 0))) + return false; + if (RAPIDJSON_UNLIKELY(!m->value.Accept(handler))) + return false; + } + return handler.EndObject(data_.o.size); + + case kArrayType: + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + return false; + for (const GenericValue* v = Begin(); v != End(); ++v) + if (RAPIDJSON_UNLIKELY(!v->Accept(handler))) + return false; + return handler.EndArray(data_.a.size); + + case kStringType: + return handler.String(GetString(), GetStringLength(), (data_.f.flags & kCopyFlag) != 0); + + default: + RAPIDJSON_ASSERT(GetType() == kNumberType); + if (IsDouble()) return handler.Double(data_.n.d); + else if (IsInt()) return handler.Int(data_.n.i.i); + else if (IsUint()) return handler.Uint(data_.n.u.u); + else if (IsInt64()) return handler.Int64(data_.n.i64); + else return handler.Uint64(data_.n.u64); + } + } + +private: + template friend class GenericValue; + template friend class GenericDocument; + + enum { + kBoolFlag = 0x0008, + kNumberFlag = 0x0010, + kIntFlag = 0x0020, + kUintFlag = 0x0040, + kInt64Flag = 0x0080, + kUint64Flag = 0x0100, + kDoubleFlag = 0x0200, + kStringFlag = 0x0400, + kCopyFlag = 0x0800, + kInlineStrFlag = 0x1000, + + // Initial flags of different types. + kNullFlag = kNullType, + kTrueFlag = kTrueType | kBoolFlag, + kFalseFlag = kFalseType | kBoolFlag, + kNumberIntFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag, + kNumberUintFlag = kNumberType | kNumberFlag | kUintFlag | kUint64Flag | kInt64Flag, + kNumberInt64Flag = kNumberType | kNumberFlag | kInt64Flag, + kNumberUint64Flag = kNumberType | kNumberFlag | kUint64Flag, + kNumberDoubleFlag = kNumberType | kNumberFlag | kDoubleFlag, + kNumberAnyFlag = kNumberType | kNumberFlag | kIntFlag | kInt64Flag | kUintFlag | kUint64Flag | kDoubleFlag, + kConstStringFlag = kStringType | kStringFlag, + kCopyStringFlag = kStringType | kStringFlag | kCopyFlag, + kShortStringFlag = kStringType | kStringFlag | kCopyFlag | kInlineStrFlag, + kObjectFlag = kObjectType, + kArrayFlag = kArrayType, + + kTypeMask = 0x07 + }; + + static const SizeType kDefaultArrayCapacity = RAPIDJSON_VALUE_DEFAULT_ARRAY_CAPACITY; + static const SizeType kDefaultObjectCapacity = RAPIDJSON_VALUE_DEFAULT_OBJECT_CAPACITY; + + struct Flag { +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION + char payload[sizeof(SizeType) * 2 + 6]; // 2 x SizeType + lower 48-bit pointer +#elif RAPIDJSON_64BIT + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 6]; // 6 padding bytes +#else + char payload[sizeof(SizeType) * 2 + sizeof(void*) + 2]; // 2 padding bytes +#endif + uint16_t flags; + }; + + struct String { + SizeType length; + SizeType hashcode; //!< reserved + const Ch* str; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // implementation detail: ShortString can represent zero-terminated strings up to MaxSize chars + // (excluding the terminating zero) and store a value to determine the length of the contained + // string in the last character str[LenPos] by storing "MaxSize - length" there. If the string + // to store has the maximal length of MaxSize then str[LenPos] will be 0 and therefore act as + // the string terminator as well. For getting the string length back from that value just use + // "MaxSize - str[LenPos]". + // This allows to store 13-chars strings in 32-bit mode, 21-chars strings in 64-bit mode, + // 13-chars strings for RAPIDJSON_48BITPOINTER_OPTIMIZATION=1 inline (for `UTF8`-encoded strings). + struct ShortString { + enum { MaxChars = sizeof(static_cast(0)->payload) / sizeof(Ch), MaxSize = MaxChars - 1, LenPos = MaxSize }; + Ch str[MaxChars]; + + inline static bool Usable(SizeType len) { return (MaxSize >= len); } + inline void SetLength(SizeType len) { str[LenPos] = static_cast(MaxSize - len); } + inline SizeType GetLength() const { return static_cast(MaxSize - str[LenPos]); } + }; // at most as many bytes as "String" above => 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + // By using proper binary layout, retrieval of different integer types do not need conversions. + union Number { +#if RAPIDJSON_ENDIAN == RAPIDJSON_LITTLEENDIAN + struct I { + int i; + char padding[4]; + }i; + struct U { + unsigned u; + char padding2[4]; + }u; +#else + struct I { + char padding[4]; + int i; + }i; + struct U { + char padding2[4]; + unsigned u; + }u; +#endif + int64_t i64; + uint64_t u64; + double d; + }; // 8 bytes + + struct ObjectData { + SizeType size; + SizeType capacity; + Member* members; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + struct ArrayData { + SizeType size; + SizeType capacity; + GenericValue* elements; + }; // 12 bytes in 32-bit mode, 16 bytes in 64-bit mode + + union Data { + String s; + ShortString ss; + Number n; + ObjectData o; + ArrayData a; + Flag f; + }; // 16 bytes in 32-bit mode, 24 bytes in 64-bit mode, 16 bytes in 64-bit with RAPIDJSON_48BITPOINTER_OPTIMIZATION + + RAPIDJSON_FORCEINLINE const Ch* GetStringPointer() const { return RAPIDJSON_GETPOINTER(Ch, data_.s.str); } + RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); } + RAPIDJSON_FORCEINLINE GenericValue* GetElementsPointer() const { return RAPIDJSON_GETPOINTER(GenericValue, data_.a.elements); } + RAPIDJSON_FORCEINLINE GenericValue* SetElementsPointer(GenericValue* elements) { return RAPIDJSON_SETPOINTER(GenericValue, data_.a.elements, elements); } + RAPIDJSON_FORCEINLINE Member* GetMembersPointer() const { return RAPIDJSON_GETPOINTER(Member, data_.o.members); } + RAPIDJSON_FORCEINLINE Member* SetMembersPointer(Member* members) { return RAPIDJSON_SETPOINTER(Member, data_.o.members, members); } + + // Initialize this value as array with initial data, without calling destructor. + void SetArrayRaw(GenericValue* values, SizeType count, Allocator& allocator) { + data_.f.flags = kArrayFlag; + if (count) { + GenericValue* e = static_cast(allocator.Malloc(count * sizeof(GenericValue))); + SetElementsPointer(e); + std::memcpy(static_cast(e), values, count * sizeof(GenericValue)); + } + else + SetElementsPointer(0); + data_.a.size = data_.a.capacity = count; + } + + //! Initialize this value as object with initial data, without calling destructor. + void SetObjectRaw(Member* members, SizeType count, Allocator& allocator) { + data_.f.flags = kObjectFlag; + if (count) { + Member* m = static_cast(allocator.Malloc(count * sizeof(Member))); + SetMembersPointer(m); + std::memcpy(static_cast(m), members, count * sizeof(Member)); + } + else + SetMembersPointer(0); + data_.o.size = data_.o.capacity = count; + } + + //! Initialize this value as constant string, without calling destructor. + void SetStringRaw(StringRefType s) RAPIDJSON_NOEXCEPT { + data_.f.flags = kConstStringFlag; + SetStringPointer(s); + data_.s.length = s.length; + } + + //! Initialize this value as copy string with initial data, without calling destructor. + void SetStringRaw(StringRefType s, Allocator& allocator) { + Ch* str = 0; + if (ShortString::Usable(s.length)) { + data_.f.flags = kShortStringFlag; + data_.ss.SetLength(s.length); + str = data_.ss.str; + } else { + data_.f.flags = kCopyStringFlag; + data_.s.length = s.length; + str = static_cast(allocator.Malloc((s.length + 1) * sizeof(Ch))); + SetStringPointer(str); + } + std::memcpy(str, s, s.length * sizeof(Ch)); + str[s.length] = '\0'; + } + + //! Assignment without calling destructor + void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { + data_ = rhs.data_; + // data_.f.flags = rhs.data_.f.flags; + rhs.data_.f.flags = kNullFlag; + } + + template + bool StringEqual(const GenericValue& rhs) const { + RAPIDJSON_ASSERT(IsString()); + RAPIDJSON_ASSERT(rhs.IsString()); + + const SizeType len1 = GetStringLength(); + const SizeType len2 = rhs.GetStringLength(); + if(len1 != len2) { return false; } + + const Ch* const str1 = GetString(); + const Ch* const str2 = rhs.GetString(); + if(str1 == str2) { return true; } // fast path for constant string + + return (std::memcmp(str1, str2, sizeof(Ch) * len1) == 0); + } + + Data data_; +}; + +//! GenericValue with UTF8 encoding +typedef GenericValue > Value; + +/////////////////////////////////////////////////////////////////////////////// +// GenericDocument + +//! A document for parsing JSON text as DOM. +/*! + \note implements Handler concept + \tparam Encoding Encoding for both parsing and string storage. + \tparam Allocator Allocator for allocating memory for the DOM + \tparam StackAllocator Allocator for allocating memory for stack during parsing. + \warning Although GenericDocument inherits from GenericValue, the API does \b not provide any virtual functions, especially no virtual destructor. To avoid memory leaks, do not \c delete a GenericDocument object via a pointer to a GenericValue. +*/ +template +class GenericDocument : public GenericValue { +public: + typedef typename Encoding::Ch Ch; //!< Character type derived from Encoding. + typedef GenericValue ValueType; //!< Value type of the document. + typedef Allocator AllocatorType; //!< Allocator type from template parameter. + + //! Constructor + /*! Creates an empty document of specified type. + \param type Mandatory type of object to create. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + explicit GenericDocument(Type type, Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + GenericValue(type), allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + + //! Constructor + /*! Creates an empty document which type is Null. + \param allocator Optional allocator for allocating memory. + \param stackCapacity Optional initial capacity of stack in bytes. + \param stackAllocator Optional allocator for allocating memory for stack. + */ + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity, StackAllocator* stackAllocator = 0) : + allocator_(allocator), ownAllocator_(0), stack_(stackAllocator, stackCapacity), parseResult_() + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericDocument(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + : ValueType(std::forward(rhs)), // explicit cast to avoid prohibited move from Document + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(std::move(rhs.stack_)), + parseResult_(rhs.parseResult_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + } +#endif + + ~GenericDocument() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move assignment in C++11 + GenericDocument& operator=(GenericDocument&& rhs) RAPIDJSON_NOEXCEPT + { + // The cast to ValueType is necessary here, because otherwise it would + // attempt to call GenericValue's templated assignment operator. + ValueType::operator=(std::forward(rhs)); + + // Calling the destructor here would prematurely call stack_'s destructor + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = std::move(rhs.stack_); + parseResult_ = rhs.parseResult_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.parseResult_ = ParseResult(); + + return *this; + } +#endif + + //! Exchange the contents of this document with those of another. + /*! + \param rhs Another document. + \note Constant complexity. + \see GenericValue::Swap + */ + GenericDocument& Swap(GenericDocument& rhs) RAPIDJSON_NOEXCEPT { + ValueType::Swap(rhs); + stack_.Swap(rhs.stack_); + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(parseResult_, rhs.parseResult_); + return *this; + } + + // Allow Swap with ValueType. + // Refer to Effective C++ 3rd Edition/Item 33: Avoid hiding inherited names. + using ValueType::Swap; + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.doc, b.doc); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericDocument& a, GenericDocument& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //! Populate this document by a generator which produces SAX events. + /*! \tparam Generator A functor with bool f(Handler) prototype. + \param g Generator functor which sends SAX events to the parameter. + \return The document itself for fluent API. + */ + template + GenericDocument& Populate(Generator& g) { + ClearStackOnExit scope(*this); + if (g(*this)) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //!@name Parse from stream + //!@{ + + //! Parse JSON text from an input stream (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam SourceEncoding Encoding of input stream + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + GenericReader reader( + stack_.HasAllocator() ? &stack_.GetAllocator() : 0); + ClearStackOnExit scope(*this); + parseResult_ = reader.template Parse(is, *this); + if (parseResult_) { + RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object + ValueType::operator=(*stack_.template Pop(1));// Move value from stack to document + } + return *this; + } + + //! Parse JSON text from an input stream + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + + //! Parse JSON text from an input stream (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \param is Input stream to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseStream(InputStream& is) { + return ParseStream(is); + } + //!@} + + //!@name Parse in-place from mutable string + //!@{ + + //! Parse JSON text from a mutable string + /*! \tparam parseFlags Combination of \ref ParseFlag. + \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + template + GenericDocument& ParseInsitu(Ch* str) { + GenericInsituStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a mutable string (with \ref kParseDefaultFlags) + /*! \param str Mutable zero-terminated string to be parsed. + \return The document itself for fluent API. + */ + GenericDocument& ParseInsitu(Ch* str) { + return ParseInsitu(str); + } + //!@} + + //!@name Parse from read-only string + //!@{ + + //! Parse JSON text from a read-only string (with Encoding conversion) + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \tparam SourceEncoding Transcoding from input Encoding + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + GenericStringStream s(str); + return ParseStream(s); + } + + //! Parse JSON text from a read-only string + /*! \tparam parseFlags Combination of \ref ParseFlag (must not contain \ref kParseInsituFlag). + \param str Read-only zero-terminated string to be parsed. + */ + template + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + //! Parse JSON text from a read-only string (with \ref kParseDefaultFlags) + /*! \param str Read-only zero-terminated string to be parsed. + */ + GenericDocument& Parse(const Ch* str) { + return Parse(str); + } + + template + GenericDocument& Parse(const typename SourceEncoding::Ch* str, size_t length) { + RAPIDJSON_ASSERT(!(parseFlags & kParseInsituFlag)); + MemoryStream ms(reinterpret_cast(str), length * sizeof(typename SourceEncoding::Ch)); + EncodedInputStream is(ms); + ParseStream(is); + return *this; + } + + template + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + + GenericDocument& Parse(const Ch* str, size_t length) { + return Parse(str, length); + } + +#if RAPIDJSON_HAS_STDSTRING + template + GenericDocument& Parse(const std::basic_string& str) { + // c_str() is constant complexity according to standard. Should be faster than Parse(const char*, size_t) + return Parse(str.c_str()); + } + + template + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str.c_str()); + } + + GenericDocument& Parse(const std::basic_string& str) { + return Parse(str); + } +#endif // RAPIDJSON_HAS_STDSTRING + + //!@} + + //!@name Handling parse errors + //!@{ + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseError() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + + //! Implicit conversion to get the last parse result +#ifndef __clang // -Wdocumentation + /*! \return \ref ParseResult of the last parse operation + + \code + Document doc; + ParseResult ok = doc.Parse(json); + if (!ok) + printf( "JSON parse error: %s (%u)\n", GetParseError_En(ok.Code()), ok.Offset()); + \endcode + */ +#endif + operator ParseResult() const { return parseResult_; } + //!@} + + //! Get the allocator of this document. + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + //! Get the capacity of stack in bytes. + size_t GetStackCapacity() const { return stack_.GetCapacity(); } + +private: + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericDocument& d) : d_(d) {} + ~ClearStackOnExit() { d_.ClearStack(); } + private: + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + GenericDocument& d_; + }; + + // callers of the following private Handler functions + // template friend class GenericReader; // for parsing + template friend class GenericValue; // for deep copying + +public: + // Implementation of Handler + bool Null() { new (stack_.template Push()) ValueType(); return true; } + bool Bool(bool b) { new (stack_.template Push()) ValueType(b); return true; } + bool Int(int i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint(unsigned i) { new (stack_.template Push()) ValueType(i); return true; } + bool Int64(int64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Uint64(uint64_t i) { new (stack_.template Push()) ValueType(i); return true; } + bool Double(double d) { new (stack_.template Push()) ValueType(d); return true; } + + bool RawNumber(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool String(const Ch* str, SizeType length, bool copy) { + if (copy) + new (stack_.template Push()) ValueType(str, length, GetAllocator()); + else + new (stack_.template Push()) ValueType(str, length); + return true; + } + + bool StartObject() { new (stack_.template Push()) ValueType(kObjectType); return true; } + + bool Key(const Ch* str, SizeType length, bool copy) { return String(str, length, copy); } + + bool EndObject(SizeType memberCount) { + typename ValueType::Member* members = stack_.template Pop(memberCount); + stack_.template Top()->SetObjectRaw(members, memberCount, GetAllocator()); + return true; + } + + bool StartArray() { new (stack_.template Push()) ValueType(kArrayType); return true; } + + bool EndArray(SizeType elementCount) { + ValueType* elements = stack_.template Pop(elementCount); + stack_.template Top()->SetArrayRaw(elements, elementCount, GetAllocator()); + return true; + } + +private: + //! Prohibit copying + GenericDocument(const GenericDocument&); + //! Prohibit assignment + GenericDocument& operator=(const GenericDocument&); + + void ClearStack() { + if (Allocator::kNeedFree) + while (stack_.GetSize() > 0) // Here assumes all elements in stack array are GenericValue (Member is actually 2 GenericValue objects) + (stack_.template Pop(1))->~ValueType(); + else + stack_.Clear(); + stack_.ShrinkToFit(); + } + + void Destroy() { + RAPIDJSON_DELETE(ownAllocator_); + } + + static const size_t kDefaultStackCapacity = 1024; + Allocator* allocator_; + Allocator* ownAllocator_; + internal::Stack stack_; + ParseResult parseResult_; +}; + +//! GenericDocument with UTF8 encoding +typedef GenericDocument > Document; + + +//! Helper class for accessing Value of array type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetArray(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericArray { +public: + typedef GenericArray ConstArray; + typedef GenericArray Array; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef ValueType* ValueIterator; // This may be const or non-const iterator + typedef const ValueT* ConstValueIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + + template + friend class GenericValue; + + GenericArray(const GenericArray& rhs) : value_(rhs.value_) {} + GenericArray& operator=(const GenericArray& rhs) { value_ = rhs.value_; return *this; } + ~GenericArray() {} + + SizeType Size() const { return value_.Size(); } + SizeType Capacity() const { return value_.Capacity(); } + bool Empty() const { return value_.Empty(); } + void Clear() const { value_.Clear(); } + ValueType& operator[](SizeType index) const { return value_[index]; } + ValueIterator Begin() const { return value_.Begin(); } + ValueIterator End() const { return value_.End(); } + GenericArray Reserve(SizeType newCapacity, AllocatorType &allocator) const { value_.Reserve(newCapacity, allocator); return *this; } + GenericArray PushBack(ValueType& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(ValueType&& value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericArray PushBack(StringRefType value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (const GenericArray&)) PushBack(T value, AllocatorType& allocator) const { value_.PushBack(value, allocator); return *this; } + GenericArray PopBack() const { value_.PopBack(); return *this; } + ValueIterator Erase(ConstValueIterator pos) const { return value_.Erase(pos); } + ValueIterator Erase(ConstValueIterator first, ConstValueIterator last) const { return value_.Erase(first, last); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + ValueIterator begin() const { return value_.Begin(); } + ValueIterator end() const { return value_.End(); } +#endif + +private: + GenericArray(); + GenericArray(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +//! Helper class for accessing Value of object type. +/*! + Instance of this helper class is obtained by \c GenericValue::GetObject(). + In addition to all APIs for array type, it provides range-based for loop if \c RAPIDJSON_HAS_CXX11_RANGE_FOR=1. +*/ +template +class GenericObject { +public: + typedef GenericObject ConstObject; + typedef GenericObject Object; + typedef ValueT PlainType; + typedef typename internal::MaybeAddConst::Type ValueType; + typedef GenericMemberIterator MemberIterator; // This may be const or non-const iterator + typedef GenericMemberIterator ConstMemberIterator; + typedef typename ValueType::AllocatorType AllocatorType; + typedef typename ValueType::StringRefType StringRefType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename ValueType::Ch Ch; + + template + friend class GenericValue; + + GenericObject(const GenericObject& rhs) : value_(rhs.value_) {} + GenericObject& operator=(const GenericObject& rhs) { value_ = rhs.value_; return *this; } + ~GenericObject() {} + + SizeType MemberCount() const { return value_.MemberCount(); } + SizeType MemberCapacity() const { return value_.MemberCapacity(); } + bool ObjectEmpty() const { return value_.ObjectEmpty(); } + template ValueType& operator[](T* name) const { return value_[name]; } + template ValueType& operator[](const GenericValue& name) const { return value_[name]; } +#if RAPIDJSON_HAS_STDSTRING + ValueType& operator[](const std::basic_string& name) const { return value_[name]; } +#endif + MemberIterator MemberBegin() const { return value_.MemberBegin(); } + MemberIterator MemberEnd() const { return value_.MemberEnd(); } + GenericObject MemberReserve(SizeType newCapacity, AllocatorType &allocator) const { value_.MemberReserve(newCapacity, allocator); return *this; } + bool HasMember(const Ch* name) const { return value_.HasMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool HasMember(const std::basic_string& name) const { return value_.HasMember(name); } +#endif + template bool HasMember(const GenericValue& name) const { return value_.HasMember(name); } + MemberIterator FindMember(const Ch* name) const { return value_.FindMember(name); } + template MemberIterator FindMember(const GenericValue& name) const { return value_.FindMember(name); } +#if RAPIDJSON_HAS_STDSTRING + MemberIterator FindMember(const std::basic_string& name) const { return value_.FindMember(name); } +#endif + GenericObject AddMember(ValueType& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_STDSTRING + GenericObject AddMember(ValueType& name, std::basic_string& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) AddMember(ValueType& name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(ValueType&& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType&& name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(ValueType& name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, ValueType&& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericObject AddMember(StringRefType name, ValueType& value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + GenericObject AddMember(StringRefType name, StringRefType value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + template RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (GenericObject)) AddMember(StringRefType name, T value, AllocatorType& allocator) const { value_.AddMember(name, value, allocator); return *this; } + void RemoveAllMembers() { value_.RemoveAllMembers(); } + bool RemoveMember(const Ch* name) const { return value_.RemoveMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool RemoveMember(const std::basic_string& name) const { return value_.RemoveMember(name); } +#endif + template bool RemoveMember(const GenericValue& name) const { return value_.RemoveMember(name); } + MemberIterator RemoveMember(MemberIterator m) const { return value_.RemoveMember(m); } + MemberIterator EraseMember(ConstMemberIterator pos) const { return value_.EraseMember(pos); } + MemberIterator EraseMember(ConstMemberIterator first, ConstMemberIterator last) const { return value_.EraseMember(first, last); } + bool EraseMember(const Ch* name) const { return value_.EraseMember(name); } +#if RAPIDJSON_HAS_STDSTRING + bool EraseMember(const std::basic_string& name) const { return EraseMember(ValueType(StringRef(name))); } +#endif + template bool EraseMember(const GenericValue& name) const { return value_.EraseMember(name); } + +#if RAPIDJSON_HAS_CXX11_RANGE_FOR + MemberIterator begin() const { return value_.MemberBegin(); } + MemberIterator end() const { return value_.MemberEnd(); } +#endif + +private: + GenericObject(); + GenericObject(ValueType& value) : value_(value) {} + ValueType& value_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_DOCUMENT_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodedstream.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodedstream.h new file mode 100644 index 0000000000..223601c059 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodedstream.h @@ -0,0 +1,299 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODEDSTREAM_H_ +#define RAPIDJSON_ENCODEDSTREAM_H_ + +#include "stream.h" +#include "memorystream.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Input byte stream wrapper with a statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam InputByteStream Type of input byte stream. For example, FileReadStream. +*/ +template +class EncodedInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedInputStream(InputByteStream& is) : is_(is) { + current_ = Encoding::TakeBOM(is_); + } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = Encoding::Take(is_); return c; } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); + + InputByteStream& is_; + Ch current_; +}; + +//! Specialized for UTF8 MemoryStream. +template <> +class EncodedInputStream, MemoryStream> { +public: + typedef UTF8<>::Ch Ch; + + EncodedInputStream(MemoryStream& is) : is_(is) { + if (static_cast(is_.Peek()) == 0xEFu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBBu) is_.Take(); + if (static_cast(is_.Peek()) == 0xBFu) is_.Take(); + } + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() const { return is_.Tell(); } + + // Not implemented + void Put(Ch) {} + void Flush() {} + Ch* PutBegin() { return 0; } + size_t PutEnd(Ch*) { return 0; } + + MemoryStream& is_; + +private: + EncodedInputStream(const EncodedInputStream&); + EncodedInputStream& operator=(const EncodedInputStream&); +}; + +//! Output byte stream wrapper with statically bound encoding. +/*! + \tparam Encoding The interpretation of encoding of the stream. Either UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE. + \tparam OutputByteStream Type of input byte stream. For example, FileWriteStream. +*/ +template +class EncodedOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef typename Encoding::Ch Ch; + + EncodedOutputStream(OutputByteStream& os, bool putBOM = true) : os_(os) { + if (putBOM) + Encoding::PutBOM(os_); + } + + void Put(Ch c) { Encoding::Put(os_, c); } + void Flush() { os_.Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + EncodedOutputStream(const EncodedOutputStream&); + EncodedOutputStream& operator=(const EncodedOutputStream&); + + OutputByteStream& os_; +}; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + +//! Input stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for reading. + \tparam InputByteStream type of input byte stream to be wrapped. +*/ +template +class AutoUTFInputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param is input stream to be wrapped. + \param type UTF encoding type if it is not detected from the stream. + */ + AutoUTFInputStream(InputByteStream& is, UTFType type = kUTF8) : is_(&is), type_(type), hasBOM_(false) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + DetectType(); + static const TakeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Take) }; + takeFunc_ = f[type_]; + current_ = takeFunc_(*is_); + } + + UTFType GetType() const { return type_; } + bool HasBOM() const { return hasBOM_; } + + Ch Peek() const { return current_; } + Ch Take() { Ch c = current_; current_ = takeFunc_(*is_); return c; } + size_t Tell() const { return is_->Tell(); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFInputStream(const AutoUTFInputStream&); + AutoUTFInputStream& operator=(const AutoUTFInputStream&); + + // Detect encoding type with BOM or RFC 4627 + void DetectType() { + // BOM (Byte Order Mark): + // 00 00 FE FF UTF-32BE + // FF FE 00 00 UTF-32LE + // FE FF UTF-16BE + // FF FE UTF-16LE + // EF BB BF UTF-8 + + const unsigned char* c = reinterpret_cast(is_->Peek4()); + if (!c) + return; + + unsigned bom = static_cast(c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24)); + hasBOM_ = false; + if (bom == 0xFFFE0000) { type_ = kUTF32BE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if (bom == 0x0000FEFF) { type_ = kUTF32LE; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFFFE) { type_ = kUTF16BE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFF) == 0xFEFF) { type_ = kUTF16LE; hasBOM_ = true; is_->Take(); is_->Take(); } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) { type_ = kUTF8; hasBOM_ = true; is_->Take(); is_->Take(); is_->Take(); } + + // RFC 4627: Section 3 + // "Since the first two characters of a JSON text will always be ASCII + // characters [RFC0020], it is possible to determine whether an octet + // stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking + // at the pattern of nulls in the first four octets." + // 00 00 00 xx UTF-32BE + // 00 xx 00 xx UTF-16BE + // xx 00 00 00 UTF-32LE + // xx 00 xx 00 UTF-16LE + // xx xx xx xx UTF-8 + + if (!hasBOM_) { + int pattern = (c[0] ? 1 : 0) | (c[1] ? 2 : 0) | (c[2] ? 4 : 0) | (c[3] ? 8 : 0); + switch (pattern) { + case 0x08: type_ = kUTF32BE; break; + case 0x0A: type_ = kUTF16BE; break; + case 0x01: type_ = kUTF32LE; break; + case 0x05: type_ = kUTF16LE; break; + case 0x0F: type_ = kUTF8; break; + default: break; // Use type defined by user. + } + } + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + } + + typedef Ch (*TakeFunc)(InputByteStream& is); + InputByteStream* is_; + UTFType type_; + Ch current_; + TakeFunc takeFunc_; + bool hasBOM_; +}; + +//! Output stream wrapper with dynamically bound encoding and automatic encoding detection. +/*! + \tparam CharType Type of character for writing. + \tparam OutputByteStream type of output byte stream to be wrapped. +*/ +template +class AutoUTFOutputStream { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); +public: + typedef CharType Ch; + + //! Constructor. + /*! + \param os output stream to be wrapped. + \param type UTF encoding type. + \param putBOM Whether to write BOM at the beginning of the stream. + */ + AutoUTFOutputStream(OutputByteStream& os, UTFType type, bool putBOM) : os_(&os), type_(type) { + RAPIDJSON_ASSERT(type >= kUTF8 && type <= kUTF32BE); + + // Runtime check whether the size of character type is sufficient. It only perform checks with assertion. + if (type_ == kUTF16LE || type_ == kUTF16BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 2); + if (type_ == kUTF32LE || type_ == kUTF32BE) RAPIDJSON_ASSERT(sizeof(Ch) >= 4); + + static const PutFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Put) }; + putFunc_ = f[type_]; + + if (putBOM) + PutBOM(); + } + + UTFType GetType() const { return type_; } + + void Put(Ch c) { putFunc_(*os_, c); } + void Flush() { os_->Flush(); } + + // Not implemented + Ch Peek() const { RAPIDJSON_ASSERT(false); return 0;} + Ch Take() { RAPIDJSON_ASSERT(false); return 0;} + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + AutoUTFOutputStream(const AutoUTFOutputStream&); + AutoUTFOutputStream& operator=(const AutoUTFOutputStream&); + + void PutBOM() { + typedef void (*PutBOMFunc)(OutputByteStream&); + static const PutBOMFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(PutBOM) }; + f[type_](*os_); + } + + typedef void (*PutFunc)(OutputByteStream&, Ch); + + OutputByteStream* os_; + UTFType type_; + PutFunc putFunc_; +}; + +#undef RAPIDJSON_ENCODINGS_FUNC + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodings.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodings.h new file mode 100644 index 0000000000..0b24467950 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/encodings.h @@ -0,0 +1,716 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ENCODINGS_H_ +#define RAPIDJSON_ENCODINGS_H_ + +#include "rapidjson.h" + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4244) // conversion from 'type1' to 'type2', possible loss of data +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#elif defined(__GNUC__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(overflow) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Encoding + +/*! \class rapidjson::Encoding + \brief Concept for encoding of Unicode characters. + +\code +concept Encoding { + typename Ch; //! Type of character. A "character" is actually a code unit in unicode's definition. + + enum { supportUnicode = 1 }; // or 0 if not supporting unicode + + //! \brief Encode a Unicode codepoint to an output stream. + //! \param os Output stream. + //! \param codepoint An unicode codepoint, ranging from 0x0 to 0x10FFFF inclusively. + template + static void Encode(OutputStream& os, unsigned codepoint); + + //! \brief Decode a Unicode codepoint from an input stream. + //! \param is Input stream. + //! \param codepoint Output of the unicode codepoint. + //! \return true if a valid codepoint can be decoded from the stream. + template + static bool Decode(InputStream& is, unsigned* codepoint); + + //! \brief Validate one Unicode codepoint from an encoded stream. + //! \param is Input stream to obtain codepoint. + //! \param os Output for copying one codepoint. + //! \return true if it is valid. + //! \note This function just validating and copying the codepoint without actually decode it. + template + static bool Validate(InputStream& is, OutputStream& os); + + // The following functions are deal with byte streams. + + //! Take a character from input byte stream, skip BOM if exist. + template + static CharType TakeBOM(InputByteStream& is); + + //! Take a character from input byte stream. + template + static Ch Take(InputByteStream& is); + + //! Put BOM to output byte stream. + template + static void PutBOM(OutputByteStream& os); + + //! Put a character to output byte stream. + template + static void Put(OutputByteStream& os, Ch c); +}; +\endcode +*/ + +/////////////////////////////////////////////////////////////////////////////// +// UTF8 + +//! UTF-8 encoding. +/*! http://en.wikipedia.org/wiki/UTF-8 + http://tools.ietf.org/html/rfc3629 + \tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char. + \note implements Encoding concept +*/ +template +struct UTF8 { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + os.Put(static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + os.Put(static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + os.Put(static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + os.Put(static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + os.Put(static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + os.Put(static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + if (codepoint <= 0x7F) + PutUnsafe(os, static_cast(codepoint & 0xFF)); + else if (codepoint <= 0x7FF) { + PutUnsafe(os, static_cast(0xC0 | ((codepoint >> 6) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint & 0x3F)))); + } + else if (codepoint <= 0xFFFF) { + PutUnsafe(os, static_cast(0xE0 | ((codepoint >> 12) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, static_cast(0xF0 | ((codepoint >> 18) & 0xFF))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + PutUnsafe(os, static_cast(0x80 | (codepoint & 0x3F))); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { +#define RAPIDJSON_COPY() c = is.Take(); *codepoint = (*codepoint << 6) | (static_cast(c) & 0x3Fu) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + typename InputStream::Ch c = is.Take(); + if (!(c & 0x80)) { + *codepoint = static_cast(c); + return true; + } + + unsigned char type = GetRange(static_cast(c)); + if (type >= 32) { + *codepoint = 0; + } else { + *codepoint = (0xFFu >> type) & static_cast(c); + } + bool result = true; + switch (type) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + template + static bool Validate(InputStream& is, OutputStream& os) { +#define RAPIDJSON_COPY() os.Put(c = is.Take()) +#define RAPIDJSON_TRANS(mask) result &= ((GetRange(static_cast(c)) & mask) != 0) +#define RAPIDJSON_TAIL() RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x70) + Ch c; + RAPIDJSON_COPY(); + if (!(c & 0x80)) + return true; + + bool result = true; + switch (GetRange(static_cast(c))) { + case 2: RAPIDJSON_TAIL(); return result; + case 3: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 4: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x50); RAPIDJSON_TAIL(); return result; + case 5: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x10); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 6: RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + case 10: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x20); RAPIDJSON_TAIL(); return result; + case 11: RAPIDJSON_COPY(); RAPIDJSON_TRANS(0x60); RAPIDJSON_TAIL(); RAPIDJSON_TAIL(); return result; + default: return false; + } +#undef RAPIDJSON_COPY +#undef RAPIDJSON_TRANS +#undef RAPIDJSON_TAIL + } + + static unsigned char GetRange(unsigned char c) { + // Referring to DFA of http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + // With new mapping 1 -> 0x10, 7 -> 0x20, 9 -> 0x40, such that AND operation can test multiple types. + static const unsigned char type[] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, + }; + return type[c]; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + typename InputByteStream::Ch c = Take(is); + if (static_cast(c) != 0xEFu) return c; + c = is.Take(); + if (static_cast(c) != 0xBBu) return c; + c = is.Take(); + if (static_cast(c) != 0xBFu) return c; + c = is.Take(); + return c; + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xEFu)); + os.Put(static_cast(0xBBu)); + os.Put(static_cast(0xBFu)); + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF16 + +//! UTF-16 encoding. +/*! http://en.wikipedia.org/wiki/UTF-16 + http://tools.ietf.org/html/rfc2781 + \tparam CharType Type for storing 16-bit UTF-16 data. Default is wchar_t. C++11 may use char16_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF16LE and UTF16BE, which handle endianness. +*/ +template +struct UTF16 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 2); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + os.Put(static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + os.Put(static_cast((v >> 10) | 0xD800)); + os.Put(static_cast((v & 0x3FF) | 0xDC00)); + } + } + + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + if (codepoint <= 0xFFFF) { + RAPIDJSON_ASSERT(codepoint < 0xD800 || codepoint > 0xDFFF); // Code point itself cannot be surrogate pair + PutUnsafe(os, static_cast(codepoint)); + } + else { + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + unsigned v = codepoint - 0x10000; + PutUnsafe(os, static_cast((v >> 10) | 0xD800)); + PutUnsafe(os, static_cast((v & 0x3FF) | 0xDC00)); + } + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + typename InputStream::Ch c = is.Take(); + if (c < 0xD800 || c > 0xDFFF) { + *codepoint = static_cast(c); + return true; + } + else if (c <= 0xDBFF) { + *codepoint = (static_cast(c) & 0x3FF) << 10; + c = is.Take(); + *codepoint |= (static_cast(c) & 0x3FF); + *codepoint += 0x10000; + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 2); + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 2); + typename InputStream::Ch c; + os.Put(static_cast(c = is.Take())); + if (c < 0xD800 || c > 0xDFFF) + return true; + else if (c <= 0xDBFF) { + os.Put(c = is.Take()); + return c >= 0xDC00 && c <= 0xDFFF; + } + return false; + } +}; + +//! UTF-16 little endian encoding. +template +struct UTF16LE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(static_cast(c) & 0xFFu)); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + } +}; + +//! UTF-16 big endian encoding. +template +struct UTF16BE : UTF16 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0xFEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((static_cast(c) >> 8) & 0xFFu)); + os.Put(static_cast(static_cast(c) & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// UTF32 + +//! UTF-32 encoding. +/*! http://en.wikipedia.org/wiki/UTF-32 + \tparam CharType Type for storing 32-bit UTF-32 data. Default is unsigned. C++11 may use char32_t instead. + \note implements Encoding concept + + \note For in-memory access, no need to concern endianness. The code units and code points are represented by CPU's endianness. + For streaming, use UTF32LE and UTF32BE, which handle endianness. +*/ +template +struct UTF32 { + typedef CharType Ch; + RAPIDJSON_STATIC_ASSERT(sizeof(Ch) >= 4); + + enum { supportUnicode = 1 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + os.Put(codepoint); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputStream::Ch) >= 4); + RAPIDJSON_ASSERT(codepoint <= 0x10FFFF); + PutUnsafe(os, codepoint); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c = is.Take(); + *codepoint = c; + return c <= 0x10FFFF; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputStream::Ch) >= 4); + Ch c; + os.Put(c = is.Take()); + return c <= 0x10FFFF; + } +}; + +//! UTF-32 little endian enocoding. +template +struct UTF32LE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(is.Take()); + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 24; + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0xFFu)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 24) & 0xFFu)); + } +}; + +//! UTF-32 big endian encoding. +template +struct UTF32BE : UTF32 { + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + CharType c = Take(is); + return static_cast(c) == 0x0000FEFFu ? Take(is) : c; + } + + template + static CharType Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + unsigned c = static_cast(static_cast(is.Take())) << 24; + c |= static_cast(static_cast(is.Take())) << 16; + c |= static_cast(static_cast(is.Take())) << 8; + c |= static_cast(static_cast(is.Take())); + return static_cast(c); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0x00u)); + os.Put(static_cast(0xFEu)); + os.Put(static_cast(0xFFu)); + } + + template + static void Put(OutputByteStream& os, CharType c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast((c >> 24) & 0xFFu)); + os.Put(static_cast((c >> 16) & 0xFFu)); + os.Put(static_cast((c >> 8) & 0xFFu)); + os.Put(static_cast(c & 0xFFu)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// ASCII + +//! ASCII encoding. +/*! http://en.wikipedia.org/wiki/ASCII + \tparam CharType Code unit for storing 7-bit ASCII data. Default is char. + \note implements Encoding concept +*/ +template +struct ASCII { + typedef CharType Ch; + + enum { supportUnicode = 0 }; + + template + static void Encode(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + os.Put(static_cast(codepoint & 0xFF)); + } + + template + static void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + RAPIDJSON_ASSERT(codepoint <= 0x7F); + PutUnsafe(os, static_cast(codepoint & 0xFF)); + } + + template + static bool Decode(InputStream& is, unsigned* codepoint) { + uint8_t c = static_cast(is.Take()); + *codepoint = c; + return c <= 0X7F; + } + + template + static bool Validate(InputStream& is, OutputStream& os) { + uint8_t c = static_cast(is.Take()); + os.Put(static_cast(c)); + return c <= 0x7F; + } + + template + static CharType TakeBOM(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + uint8_t c = static_cast(Take(is)); + return static_cast(c); + } + + template + static Ch Take(InputByteStream& is) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename InputByteStream::Ch) == 1); + return static_cast(is.Take()); + } + + template + static void PutBOM(OutputByteStream& os) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + (void)os; + } + + template + static void Put(OutputByteStream& os, Ch c) { + RAPIDJSON_STATIC_ASSERT(sizeof(typename OutputByteStream::Ch) == 1); + os.Put(static_cast(c)); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// AutoUTF + +//! Runtime-specified UTF encoding type of a stream. +enum UTFType { + kUTF8 = 0, //!< UTF-8. + kUTF16LE = 1, //!< UTF-16 little endian. + kUTF16BE = 2, //!< UTF-16 big endian. + kUTF32LE = 3, //!< UTF-32 little endian. + kUTF32BE = 4 //!< UTF-32 big endian. +}; + +//! Dynamically select encoding according to stream's runtime-specified UTF encoding type. +/*! \note This class can be used with AutoUTFInputtStream and AutoUTFOutputStream, which provides GetType(). +*/ +template +struct AutoUTF { + typedef CharType Ch; + + enum { supportUnicode = 1 }; + +#define RAPIDJSON_ENCODINGS_FUNC(x) UTF8::x, UTF16LE::x, UTF16BE::x, UTF32LE::x, UTF32BE::x + + template + static RAPIDJSON_FORCEINLINE void Encode(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Encode) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE void EncodeUnsafe(OutputStream& os, unsigned codepoint) { + typedef void (*EncodeFunc)(OutputStream&, unsigned); + static const EncodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(EncodeUnsafe) }; + (*f[os.GetType()])(os, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Decode(InputStream& is, unsigned* codepoint) { + typedef bool (*DecodeFunc)(InputStream&, unsigned*); + static const DecodeFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Decode) }; + return (*f[is.GetType()])(is, codepoint); + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + typedef bool (*ValidateFunc)(InputStream&, OutputStream&); + static const ValidateFunc f[] = { RAPIDJSON_ENCODINGS_FUNC(Validate) }; + return (*f[is.GetType()])(is, os); + } + +#undef RAPIDJSON_ENCODINGS_FUNC +}; + +/////////////////////////////////////////////////////////////////////////////// +// Transcoder + +//! Encoding conversion. +template +struct Transcoder { + //! Take one Unicode codepoint from source encoding, convert it to target encoding and put it to the output stream. + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::Encode(os, codepoint); + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + unsigned codepoint; + if (!SourceEncoding::Decode(is, &codepoint)) + return false; + TargetEncoding::EncodeUnsafe(os, codepoint); + return true; + } + + //! Validate one Unicode codepoint from an encoded stream. + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Transcode(is, os); // Since source/target encoding is different, must transcode. + } +}; + +// Forward declaration. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c); + +//! Specialization of Transcoder with same source and target encoding. +template +struct Transcoder { + template + static RAPIDJSON_FORCEINLINE bool Transcode(InputStream& is, OutputStream& os) { + os.Put(is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool TranscodeUnsafe(InputStream& is, OutputStream& os) { + PutUnsafe(os, is.Take()); // Just copy one code unit. This semantic is different from primary template class. + return true; + } + + template + static RAPIDJSON_FORCEINLINE bool Validate(InputStream& is, OutputStream& os) { + return Encoding::Validate(is, os); // source/target encoding are the same + } +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || (defined(_MSC_VER) && !defined(__clang__)) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ENCODINGS_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/en.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/en.h new file mode 100644 index 0000000000..2db838bff2 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/en.h @@ -0,0 +1,74 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_EN_H_ +#define RAPIDJSON_ERROR_EN_H_ + +#include "error.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +RAPIDJSON_DIAG_OFF(covered-switch-default) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Maps error code of parsing into error message. +/*! + \ingroup RAPIDJSON_ERRORS + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not be followed by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoding in string."); + + case kParseErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + case kParseErrorTermination: return RAPIDJSON_ERROR_STRING("Terminate parsing due to Handler error."); + case kParseErrorUnspecificSyntaxError: return RAPIDJSON_ERROR_STRING("Unspecific syntax error."); + + default: return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_EN_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/error.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/error.h new file mode 100644 index 0000000000..9311d2f03b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/error/error.h @@ -0,0 +1,161 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ERROR_ERROR_H_ +#define RAPIDJSON_ERROR_ERROR_H_ + +#include "../rapidjson.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +/*! \file error.h */ + +/*! \defgroup RAPIDJSON_ERRORS RapidJSON error handling */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! \ingroup RAPIDJSON_ERRORS + The default character type is \c char. + On Windows, user can define this macro as \c TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to \ref RAPIDJSON_ERROR_CHARTYPE[]. +/*! \ingroup RAPIDJSON_ERRORS + By default this conversion macro does nothing. + On Windows, user can define this macro as \c _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseErrorCode + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericReader::Parse, GenericReader::GetParseErrorCode +*/ +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoding in string. + + kParseErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent, //!< Miss exponent in number. + + kParseErrorTermination, //!< Parsing was terminated. + kParseErrorUnspecificSyntaxError //!< Unspecific syntax error. +}; + +//! Result of parsing (wraps ParseErrorCode) +/*! + \ingroup RAPIDJSON_ERRORS + \code + Document doc; + ParseResult ok = doc.Parse("[42]"); + if (!ok) { + fprintf(stderr, "JSON parse error: %s (%u)", + GetParseError_En(ok.Code()), ok.Offset()); + exit(EXIT_FAILURE); + } + \endcode + \see GenericReader::Parse, GenericDocument::Parse +*/ +struct ParseResult { + //!! Unspecified boolean type + typedef bool (ParseResult::*BooleanType)() const; +public: + //! Default constructor, no error. + ParseResult() : code_(kParseErrorNone), offset_(0) {} + //! Constructor to set an error. + ParseResult(ParseErrorCode code, size_t offset) : code_(code), offset_(offset) {} + + //! Get the error code. + ParseErrorCode Code() const { return code_; } + //! Get the error offset, if \ref IsError(), 0 otherwise. + size_t Offset() const { return offset_; } + + //! Explicit conversion to \c bool, returns \c true, iff !\ref IsError(). + operator BooleanType() const { return !IsError() ? &ParseResult::IsError : NULL; } + //! Whether the result is an error. + bool IsError() const { return code_ != kParseErrorNone; } + + bool operator==(const ParseResult& that) const { return code_ == that.code_; } + bool operator==(ParseErrorCode code) const { return code_ == code; } + friend bool operator==(ParseErrorCode code, const ParseResult & err) { return code == err.code_; } + + bool operator!=(const ParseResult& that) const { return !(*this == that); } + bool operator!=(ParseErrorCode code) const { return !(*this == code); } + friend bool operator!=(ParseErrorCode code, const ParseResult & err) { return err != code; } + + //! Reset error code. + void Clear() { Set(kParseErrorNone); } + //! Update error code and offset. + void Set(ParseErrorCode code, size_t offset = 0) { code_ = code; offset_ = offset; } + +private: + ParseErrorCode code_; + size_t offset_; +}; + +//! Function pointer type of GetParseError(). +/*! \ingroup RAPIDJSON_ERRORS + + This is the prototype for \c GetParseError_X(), where \c X is a locale. + User can dynamically change locale in runtime, e.g.: +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_ERROR_ERROR_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filereadstream.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filereadstream.h new file mode 100644 index 0000000000..6b343707ad --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filereadstream.h @@ -0,0 +1,99 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEREADSTREAM_H_ +#define RAPIDJSON_FILEREADSTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! File byte stream for input using fread(). +/*! + \note implements Stream concept +*/ +class FileReadStream { +public: + typedef char Ch; //!< Character type (byte). + + //! Constructor. + /*! + \param fp File pointer opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + FileReadStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(fp_ != 0); + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = std::fread(buffer_, 1, bufferSize_, fp_); + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (readCount_ < bufferSize_) { + buffer_[readCount_] = '\0'; + ++bufferLast_; + eof_ = true; + } + } + } + + std::FILE* fp_; + Ch *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filewritestream.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filewritestream.h new file mode 100644 index 0000000000..8b48fee197 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/filewritestream.h @@ -0,0 +1,104 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FILEWRITESTREAM_H_ +#define RAPIDJSON_FILEWRITESTREAM_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of C file stream for output using fwrite(). +/*! + \note implements Stream concept +*/ +class FileWriteStream { +public: + typedef char Ch; //!< Character type. Only support char. + + FileWriteStream(std::FILE* fp, char* buffer, size_t bufferSize) : fp_(fp), buffer_(buffer), bufferEnd_(buffer + bufferSize), current_(buffer_) { + RAPIDJSON_ASSERT(fp_ != 0); + } + + void Put(char c) { + if (current_ >= bufferEnd_) + Flush(); + + *current_++ = c; + } + + void PutN(char c, size_t n) { + size_t avail = static_cast(bufferEnd_ - current_); + while (n > avail) { + std::memset(current_, c, avail); + current_ += avail; + Flush(); + n -= avail; + avail = static_cast(bufferEnd_ - current_); + } + + if (n > 0) { + std::memset(current_, c, n); + current_ += n; + } + } + + void Flush() { + if (current_ != buffer_) { + size_t result = std::fwrite(buffer_, 1, static_cast(current_ - buffer_), fp_); + if (result < static_cast(current_ - buffer_)) { + // failure deliberately ignored at this time + // added to avoid warn_unused_result build errors + } + current_ = buffer_; + } + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + // Prohibit copy constructor & assignment operator. + FileWriteStream(const FileWriteStream&); + FileWriteStream& operator=(const FileWriteStream&); + + std::FILE* fp_; + char *buffer_; + char *bufferEnd_; + char *current_; +}; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(FileWriteStream& stream, char c, size_t n) { + stream.PutN(c, n); +} + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_FILESTREAM_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/fwd.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/fwd.h new file mode 100644 index 0000000000..b74a2b8198 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/fwd.h @@ -0,0 +1,151 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_FWD_H_ +#define RAPIDJSON_FWD_H_ + +#include "rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN + +// encodings.h + +template struct UTF8; +template struct UTF16; +template struct UTF16BE; +template struct UTF16LE; +template struct UTF32; +template struct UTF32BE; +template struct UTF32LE; +template struct ASCII; +template struct AutoUTF; + +template +struct Transcoder; + +// allocators.h + +class CrtAllocator; + +template +class MemoryPoolAllocator; + +// stream.h + +template +struct GenericStringStream; + +typedef GenericStringStream > StringStream; + +template +struct GenericInsituStringStream; + +typedef GenericInsituStringStream > InsituStringStream; + +// stringbuffer.h + +template +class GenericStringBuffer; + +typedef GenericStringBuffer, CrtAllocator> StringBuffer; + +// filereadstream.h + +class FileReadStream; + +// filewritestream.h + +class FileWriteStream; + +// memorybuffer.h + +template +struct GenericMemoryBuffer; + +typedef GenericMemoryBuffer MemoryBuffer; + +// memorystream.h + +struct MemoryStream; + +// reader.h + +template +struct BaseReaderHandler; + +template +class GenericReader; + +typedef GenericReader, UTF8, CrtAllocator> Reader; + +// writer.h + +template +class Writer; + +// prettywriter.h + +template +class PrettyWriter; + +// document.h + +template +class GenericMember; + +template +class GenericMemberIterator; + +template +struct GenericStringRef; + +template +class GenericValue; + +typedef GenericValue, MemoryPoolAllocator > Value; + +template +class GenericDocument; + +typedef GenericDocument, MemoryPoolAllocator, CrtAllocator> Document; + +// pointer.h + +template +class GenericPointer; + +typedef GenericPointer Pointer; + +// schema.h + +template +class IGenericRemoteSchemaDocumentProvider; + +template +class GenericSchemaDocument; + +typedef GenericSchemaDocument SchemaDocument; +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +template < + typename SchemaDocumentType, + typename OutputHandler, + typename StateAllocator> +class GenericSchemaValidator; + +typedef GenericSchemaValidator, void>, CrtAllocator> SchemaValidator; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSONFWD_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/biginteger.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/biginteger.h new file mode 100644 index 0000000000..8eb87c7c0f --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/biginteger.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_BIGINTEGER_H_ +#define RAPIDJSON_BIGINTEGER_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && defined(_M_AMD64) +#include // for _umul128 +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class BigInteger { +public: + typedef uint64_t Type; + + BigInteger(const BigInteger& rhs) : count_(rhs.count_) { + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + + explicit BigInteger(uint64_t u) : count_(1) { + digits_[0] = u; + } + + BigInteger(const char* decimals, size_t length) : count_(1) { + RAPIDJSON_ASSERT(length > 0); + digits_[0] = 0; + size_t i = 0; + const size_t kMaxDigitPerIteration = 19; // 2^64 = 18446744073709551616 > 10^19 + while (length >= kMaxDigitPerIteration) { + AppendDecimal64(decimals + i, decimals + i + kMaxDigitPerIteration); + length -= kMaxDigitPerIteration; + i += kMaxDigitPerIteration; + } + + if (length > 0) + AppendDecimal64(decimals + i, decimals + i + length); + } + + BigInteger& operator=(const BigInteger &rhs) + { + if (this != &rhs) { + count_ = rhs.count_; + std::memcpy(digits_, rhs.digits_, count_ * sizeof(Type)); + } + return *this; + } + + BigInteger& operator=(uint64_t u) { + digits_[0] = u; + count_ = 1; + return *this; + } + + BigInteger& operator+=(uint64_t u) { + Type backup = digits_[0]; + digits_[0] += u; + for (size_t i = 0; i < count_ - 1; i++) { + if (digits_[i] >= backup) + return *this; // no carry + backup = digits_[i + 1]; + digits_[i + 1] += 1; + } + + // Last carry + if (digits_[count_ - 1] < backup) + PushBack(1); + + return *this; + } + + BigInteger& operator*=(uint64_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + uint64_t hi; + digits_[i] = MulAdd64(digits_[i], u, k, &hi); + k = hi; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator*=(uint32_t u) { + if (u == 0) return *this = 0; + if (u == 1) return *this; + if (*this == 1) return *this = u; + + uint64_t k = 0; + for (size_t i = 0; i < count_; i++) { + const uint64_t c = digits_[i] >> 32; + const uint64_t d = digits_[i] & 0xFFFFFFFF; + const uint64_t uc = u * c; + const uint64_t ud = u * d; + const uint64_t p0 = ud + k; + const uint64_t p1 = uc + (p0 >> 32); + digits_[i] = (p0 & 0xFFFFFFFF) | (p1 << 32); + k = p1 >> 32; + } + + if (k > 0) + PushBack(k); + + return *this; + } + + BigInteger& operator<<=(size_t shift) { + if (IsZero() || shift == 0) return *this; + + size_t offset = shift / kTypeBit; + size_t interShift = shift % kTypeBit; + RAPIDJSON_ASSERT(count_ + offset <= kCapacity); + + if (interShift == 0) { + std::memmove(digits_ + offset, digits_, count_ * sizeof(Type)); + count_ += offset; + } + else { + digits_[count_] = 0; + for (size_t i = count_; i > 0; i--) + digits_[i + offset] = (digits_[i] << interShift) | (digits_[i - 1] >> (kTypeBit - interShift)); + digits_[offset] = digits_[0] << interShift; + count_ += offset; + if (digits_[count_]) + count_++; + } + + std::memset(digits_, 0, offset * sizeof(Type)); + + return *this; + } + + bool operator==(const BigInteger& rhs) const { + return count_ == rhs.count_ && std::memcmp(digits_, rhs.digits_, count_ * sizeof(Type)) == 0; + } + + bool operator==(const Type rhs) const { + return count_ == 1 && digits_[0] == rhs; + } + + BigInteger& MultiplyPow5(unsigned exp) { + static const uint32_t kPow5[12] = { + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + if (exp == 0) return *this; + for (; exp >= 27; exp -= 27) *this *= RAPIDJSON_UINT64_C2(0X6765C793, 0XFA10079D); // 5^27 + for (; exp >= 13; exp -= 13) *this *= static_cast(1220703125u); // 5^13 + if (exp > 0) *this *= kPow5[exp - 1]; + return *this; + } + + // Compute absolute difference of this and rhs. + // Assume this != rhs + bool Difference(const BigInteger& rhs, BigInteger* out) const { + int cmp = Compare(rhs); + RAPIDJSON_ASSERT(cmp != 0); + const BigInteger *a, *b; // Makes a > b + bool ret; + if (cmp < 0) { a = &rhs; b = this; ret = true; } + else { a = this; b = &rhs; ret = false; } + + Type borrow = 0; + for (size_t i = 0; i < a->count_; i++) { + Type d = a->digits_[i] - borrow; + if (i < b->count_) + d -= b->digits_[i]; + borrow = (d > a->digits_[i]) ? 1 : 0; + out->digits_[i] = d; + if (d != 0) + out->count_ = i + 1; + } + + return ret; + } + + int Compare(const BigInteger& rhs) const { + if (count_ != rhs.count_) + return count_ < rhs.count_ ? -1 : 1; + + for (size_t i = count_; i-- > 0;) + if (digits_[i] != rhs.digits_[i]) + return digits_[i] < rhs.digits_[i] ? -1 : 1; + + return 0; + } + + size_t GetCount() const { return count_; } + Type GetDigit(size_t index) const { RAPIDJSON_ASSERT(index < count_); return digits_[index]; } + bool IsZero() const { return count_ == 1 && digits_[0] == 0; } + +private: + void AppendDecimal64(const char* begin, const char* end) { + uint64_t u = ParseUint64(begin, end); + if (IsZero()) + *this = u; + else { + unsigned exp = static_cast(end - begin); + (MultiplyPow5(exp) <<= exp) += u; // *this = *this * 10^exp + u + } + } + + void PushBack(Type digit) { + RAPIDJSON_ASSERT(count_ < kCapacity); + digits_[count_++] = digit; + } + + static uint64_t ParseUint64(const char* begin, const char* end) { + uint64_t r = 0; + for (const char* p = begin; p != end; ++p) { + RAPIDJSON_ASSERT(*p >= '0' && *p <= '9'); + r = r * 10u + static_cast(*p - '0'); + } + return r; + } + + // Assume a * b + k < 2^128 + static uint64_t MulAdd64(uint64_t a, uint64_t b, uint64_t k, uint64_t* outHigh) { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t low = _umul128(a, b, outHigh) + k; + if (low < k) + (*outHigh)++; + return low; +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(a) * static_cast(b); + p += k; + *outHigh = static_cast(p >> 64); + return static_cast(p); +#else + const uint64_t a0 = a & 0xFFFFFFFF, a1 = a >> 32, b0 = b & 0xFFFFFFFF, b1 = b >> 32; + uint64_t x0 = a0 * b0, x1 = a0 * b1, x2 = a1 * b0, x3 = a1 * b1; + x1 += (x0 >> 32); // can't give carry + x1 += x2; + if (x1 < x2) + x3 += (static_cast(1) << 32); + uint64_t lo = (x1 << 32) + (x0 & 0xFFFFFFFF); + uint64_t hi = x3 + (x1 >> 32); + + lo += k; + if (lo < k) + hi++; + *outHigh = hi; + return lo; +#endif + } + + static const size_t kBitCount = 3328; // 64bit * 54 > 10^1000 + static const size_t kCapacity = kBitCount / sizeof(Type); + static const size_t kTypeBit = sizeof(Type) * 8; + + Type digits_[kCapacity]; + size_t count_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_BIGINTEGER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/clzll.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/clzll.h new file mode 100644 index 0000000000..47bb7ab16d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/clzll.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_CLZLL_H_ +#define RAPIDJSON_CLZLL_H_ + +#include "../rapidjson.h" + +#if defined(_MSC_VER) && !defined(UNDER_CE) +#include +#if defined(_WIN64) +#pragma intrinsic(_BitScanReverse64) +#else +#pragma intrinsic(_BitScanReverse) +#endif +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline uint32_t clzll(uint64_t x) { + // Passing 0 to __builtin_clzll is UB in GCC and results in an + // infinite loop in the software implementation. + RAPIDJSON_ASSERT(x != 0); + +#if defined(_MSC_VER) && !defined(UNDER_CE) + unsigned long r = 0; +#if defined(_WIN64) + _BitScanReverse64(&r, x); +#else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x & 0xFFFFFFFF)); +#endif // _WIN64 + + return 63 - r; +#elif (defined(__GNUC__) && __GNUC__ >= 4) || RAPIDJSON_HAS_BUILTIN(__builtin_clzll) + // __builtin_clzll wrapper + return static_cast(__builtin_clzll(x)); +#else + // naive version + uint32_t r = 0; + while (!(x & (static_cast(1) << 63))) { + x <<= 1; + ++r; + } + + return r; +#endif // _MSC_VER +} + +#define RAPIDJSON_CLZLL RAPIDJSON_NAMESPACE::internal::clzll + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_CLZLL_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/diyfp.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/diyfp.h new file mode 100644 index 0000000000..8f7d853a35 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/diyfp.h @@ -0,0 +1,257 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DIYFP_H_ +#define RAPIDJSON_DIYFP_H_ + +#include "../rapidjson.h" +#include "clzll.h" +#include + +#if defined(_MSC_VER) && defined(_M_AMD64) && !defined(__INTEL_COMPILER) +#include +#pragma intrinsic(_umul128) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +struct DiyFp { + DiyFp() : f(), e() {} + + DiyFp(uint64_t fp, int exp) : f(fp), e(exp) {} + + explicit DiyFp(double d) { + union { + double d; + uint64_t u64; + } u = { d }; + + int biased_e = static_cast((u.u64 & kDpExponentMask) >> kDpSignificandSize); + uint64_t significand = (u.u64 & kDpSignificandMask); + if (biased_e != 0) { + f = significand + kDpHiddenBit; + e = biased_e - kDpExponentBias; + } + else { + f = significand; + e = kDpMinExponent + 1; + } + } + + DiyFp operator-(const DiyFp& rhs) const { + return DiyFp(f - rhs.f, e); + } + + DiyFp operator*(const DiyFp& rhs) const { +#if defined(_MSC_VER) && defined(_M_AMD64) + uint64_t h; + uint64_t l = _umul128(f, rhs.f, &h); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__) + __extension__ typedef unsigned __int128 uint128; + uint128 p = static_cast(f) * static_cast(rhs.f); + uint64_t h = static_cast(p >> 64); + uint64_t l = static_cast(p); + if (l & (uint64_t(1) << 63)) // rounding + h++; + return DiyFp(h, e + rhs.e + 64); +#else + const uint64_t M32 = 0xFFFFFFFF; + const uint64_t a = f >> 32; + const uint64_t b = f & M32; + const uint64_t c = rhs.f >> 32; + const uint64_t d = rhs.f & M32; + const uint64_t ac = a * c; + const uint64_t bc = b * c; + const uint64_t ad = a * d; + const uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64); +#endif + } + + DiyFp Normalize() const { + int s = static_cast(clzll(f)); + return DiyFp(f << s, e - s); + } + + DiyFp NormalizeBoundary() const { + DiyFp res = *this; + while (!(res.f & (kDpHiddenBit << 1))) { + res.f <<= 1; + res.e--; + } + res.f <<= (kDiySignificandSize - kDpSignificandSize - 2); + res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2); + return res; + } + + void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const { + DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary(); + DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1); + mi.f <<= mi.e - pl.e; + mi.e = pl.e; + *plus = pl; + *minus = mi; + } + + double ToDouble() const { + union { + double d; + uint64_t u64; + }u; + RAPIDJSON_ASSERT(f <= kDpHiddenBit + kDpSignificandMask); + if (e < kDpDenormalExponent) { + // Underflow. + return 0.0; + } + if (e >= kDpMaxExponent) { + // Overflow. + return std::numeric_limits::infinity(); + } + const uint64_t be = (e == kDpDenormalExponent && (f & kDpHiddenBit) == 0) ? 0 : + static_cast(e + kDpExponentBias); + u.u64 = (f & kDpSignificandMask) | (be << kDpSignificandSize); + return u.d; + } + + static const int kDiySignificandSize = 64; + static const int kDpSignificandSize = 52; + static const int kDpExponentBias = 0x3FF + kDpSignificandSize; + static const int kDpMaxExponent = 0x7FF - kDpExponentBias; + static const int kDpMinExponent = -kDpExponentBias; + static const int kDpDenormalExponent = -kDpExponentBias + 1; + static const uint64_t kDpExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kDpSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kDpHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + uint64_t f; + int e; +}; + +inline DiyFp GetCachedPowerByIndex(size_t index) { + // 10^-348, 10^-340, ..., 10^340 + static const uint64_t kCachedPowers_F[] = { + RAPIDJSON_UINT64_C2(0xfa8fd5a0, 0x081c0288), RAPIDJSON_UINT64_C2(0xbaaee17f, 0xa23ebf76), + RAPIDJSON_UINT64_C2(0x8b16fb20, 0x3055ac76), RAPIDJSON_UINT64_C2(0xcf42894a, 0x5dce35ea), + RAPIDJSON_UINT64_C2(0x9a6bb0aa, 0x55653b2d), RAPIDJSON_UINT64_C2(0xe61acf03, 0x3d1a45df), + RAPIDJSON_UINT64_C2(0xab70fe17, 0xc79ac6ca), RAPIDJSON_UINT64_C2(0xff77b1fc, 0xbebcdc4f), + RAPIDJSON_UINT64_C2(0xbe5691ef, 0x416bd60c), RAPIDJSON_UINT64_C2(0x8dd01fad, 0x907ffc3c), + RAPIDJSON_UINT64_C2(0xd3515c28, 0x31559a83), RAPIDJSON_UINT64_C2(0x9d71ac8f, 0xada6c9b5), + RAPIDJSON_UINT64_C2(0xea9c2277, 0x23ee8bcb), RAPIDJSON_UINT64_C2(0xaecc4991, 0x4078536d), + RAPIDJSON_UINT64_C2(0x823c1279, 0x5db6ce57), RAPIDJSON_UINT64_C2(0xc2109436, 0x4dfb5637), + RAPIDJSON_UINT64_C2(0x9096ea6f, 0x3848984f), RAPIDJSON_UINT64_C2(0xd77485cb, 0x25823ac7), + RAPIDJSON_UINT64_C2(0xa086cfcd, 0x97bf97f4), RAPIDJSON_UINT64_C2(0xef340a98, 0x172aace5), + RAPIDJSON_UINT64_C2(0xb23867fb, 0x2a35b28e), RAPIDJSON_UINT64_C2(0x84c8d4df, 0xd2c63f3b), + RAPIDJSON_UINT64_C2(0xc5dd4427, 0x1ad3cdba), RAPIDJSON_UINT64_C2(0x936b9fce, 0xbb25c996), + RAPIDJSON_UINT64_C2(0xdbac6c24, 0x7d62a584), RAPIDJSON_UINT64_C2(0xa3ab6658, 0x0d5fdaf6), + RAPIDJSON_UINT64_C2(0xf3e2f893, 0xdec3f126), RAPIDJSON_UINT64_C2(0xb5b5ada8, 0xaaff80b8), + RAPIDJSON_UINT64_C2(0x87625f05, 0x6c7c4a8b), RAPIDJSON_UINT64_C2(0xc9bcff60, 0x34c13053), + RAPIDJSON_UINT64_C2(0x964e858c, 0x91ba2655), RAPIDJSON_UINT64_C2(0xdff97724, 0x70297ebd), + RAPIDJSON_UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), RAPIDJSON_UINT64_C2(0xf8a95fcf, 0x88747d94), + RAPIDJSON_UINT64_C2(0xb9447093, 0x8fa89bcf), RAPIDJSON_UINT64_C2(0x8a08f0f8, 0xbf0f156b), + RAPIDJSON_UINT64_C2(0xcdb02555, 0x653131b6), RAPIDJSON_UINT64_C2(0x993fe2c6, 0xd07b7fac), + RAPIDJSON_UINT64_C2(0xe45c10c4, 0x2a2b3b06), RAPIDJSON_UINT64_C2(0xaa242499, 0x697392d3), + RAPIDJSON_UINT64_C2(0xfd87b5f2, 0x8300ca0e), RAPIDJSON_UINT64_C2(0xbce50864, 0x92111aeb), + RAPIDJSON_UINT64_C2(0x8cbccc09, 0x6f5088cc), RAPIDJSON_UINT64_C2(0xd1b71758, 0xe219652c), + RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), RAPIDJSON_UINT64_C2(0xe8d4a510, 0x00000000), + RAPIDJSON_UINT64_C2(0xad78ebc5, 0xac620000), RAPIDJSON_UINT64_C2(0x813f3978, 0xf8940984), + RAPIDJSON_UINT64_C2(0xc097ce7b, 0xc90715b3), RAPIDJSON_UINT64_C2(0x8f7e32ce, 0x7bea5c70), + RAPIDJSON_UINT64_C2(0xd5d238a4, 0xabe98068), RAPIDJSON_UINT64_C2(0x9f4f2726, 0x179a2245), + RAPIDJSON_UINT64_C2(0xed63a231, 0xd4c4fb27), RAPIDJSON_UINT64_C2(0xb0de6538, 0x8cc8ada8), + RAPIDJSON_UINT64_C2(0x83c7088e, 0x1aab65db), RAPIDJSON_UINT64_C2(0xc45d1df9, 0x42711d9a), + RAPIDJSON_UINT64_C2(0x924d692c, 0xa61be758), RAPIDJSON_UINT64_C2(0xda01ee64, 0x1a708dea), + RAPIDJSON_UINT64_C2(0xa26da399, 0x9aef774a), RAPIDJSON_UINT64_C2(0xf209787b, 0xb47d6b85), + RAPIDJSON_UINT64_C2(0xb454e4a1, 0x79dd1877), RAPIDJSON_UINT64_C2(0x865b8692, 0x5b9bc5c2), + RAPIDJSON_UINT64_C2(0xc83553c5, 0xc8965d3d), RAPIDJSON_UINT64_C2(0x952ab45c, 0xfa97a0b3), + RAPIDJSON_UINT64_C2(0xde469fbd, 0x99a05fe3), RAPIDJSON_UINT64_C2(0xa59bc234, 0xdb398c25), + RAPIDJSON_UINT64_C2(0xf6c69a72, 0xa3989f5c), RAPIDJSON_UINT64_C2(0xb7dcbf53, 0x54e9bece), + RAPIDJSON_UINT64_C2(0x88fcf317, 0xf22241e2), RAPIDJSON_UINT64_C2(0xcc20ce9b, 0xd35c78a5), + RAPIDJSON_UINT64_C2(0x98165af3, 0x7b2153df), RAPIDJSON_UINT64_C2(0xe2a0b5dc, 0x971f303a), + RAPIDJSON_UINT64_C2(0xa8d9d153, 0x5ce3b396), RAPIDJSON_UINT64_C2(0xfb9b7cd9, 0xa4a7443c), + RAPIDJSON_UINT64_C2(0xbb764c4c, 0xa7a44410), RAPIDJSON_UINT64_C2(0x8bab8eef, 0xb6409c1a), + RAPIDJSON_UINT64_C2(0xd01fef10, 0xa657842c), RAPIDJSON_UINT64_C2(0x9b10a4e5, 0xe9913129), + RAPIDJSON_UINT64_C2(0xe7109bfb, 0xa19c0c9d), RAPIDJSON_UINT64_C2(0xac2820d9, 0x623bf429), + RAPIDJSON_UINT64_C2(0x80444b5e, 0x7aa7cf85), RAPIDJSON_UINT64_C2(0xbf21e440, 0x03acdd2d), + RAPIDJSON_UINT64_C2(0x8e679c2f, 0x5e44ff8f), RAPIDJSON_UINT64_C2(0xd433179d, 0x9c8cb841), + RAPIDJSON_UINT64_C2(0x9e19db92, 0xb4e31ba9), RAPIDJSON_UINT64_C2(0xeb96bf6e, 0xbadf77d9), + RAPIDJSON_UINT64_C2(0xaf87023b, 0x9bf0ee6b) + }; + static const int16_t kCachedPowers_E[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, + -954, -927, -901, -874, -847, -821, -794, -768, -741, -715, + -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, + -422, -396, -369, -343, -316, -289, -263, -236, -210, -183, + -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, + 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, + 375, 402, 428, 455, 481, 508, 534, 561, 588, 614, + 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, + 907, 933, 960, 986, 1013, 1039, 1066 + }; + RAPIDJSON_ASSERT(index < 87); + return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]); +} + +inline DiyFp GetCachedPower(int e, int* K) { + + //int k = static_cast(ceil((-61 - e) * 0.30102999566398114)) + 374; + double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive + int k = static_cast(dk); + if (dk - k > 0.0) + k++; + + unsigned index = static_cast((k >> 3) + 1); + *K = -(-348 + static_cast(index << 3)); // decimal exponent no need lookup table + + return GetCachedPowerByIndex(index); +} + +inline DiyFp GetCachedPower10(int exp, int *outExp) { + RAPIDJSON_ASSERT(exp >= -348); + unsigned index = static_cast(exp + 348) / 8u; + *outExp = -348 + static_cast(index) * 8; + return GetCachedPowerByIndex(index); +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +RAPIDJSON_DIAG_OFF(padded) +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DIYFP_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/dtoa.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/dtoa.h new file mode 100644 index 0000000000..bf2e9b2e59 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/dtoa.h @@ -0,0 +1,245 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// This is a C++ header-only implementation of Grisu2 algorithm from the publication: +// Loitsch, Florian. "Printing floating-point numbers quickly and accurately with +// integers." ACM Sigplan Notices 45.6 (2010): 233-243. + +#ifndef RAPIDJSON_DTOA_ +#define RAPIDJSON_DTOA_ + +#include "itoa.h" // GetDigitsLut() +#include "diyfp.h" +#include "ieee754.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +RAPIDJSON_DIAG_OFF(array-bounds) // some gcc versions generate wrong warnings https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 +#endif + +inline void GrisuRound(char* buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) { + while (rest < wp_w && delta - rest >= ten_kappa && + (rest + ten_kappa < wp_w || /// closer + wp_w - rest > rest + ten_kappa - wp_w)) { + buffer[len - 1]--; + rest += ten_kappa; + } +} + +inline int CountDecimalDigit32(uint32_t n) { + // Simple pure C++ implementation was faster than __builtin_clz version in this situation. + if (n < 10) return 1; + if (n < 100) return 2; + if (n < 1000) return 3; + if (n < 10000) return 4; + if (n < 100000) return 5; + if (n < 1000000) return 6; + if (n < 10000000) return 7; + if (n < 100000000) return 8; + // Will not reach 10 digits in DigitGen() + //if (n < 1000000000) return 9; + //return 10; + return 9; +} + +inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buffer, int* len, int* K) { + static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + const DiyFp one(uint64_t(1) << -Mp.e, Mp.e); + const DiyFp wp_w = Mp - W; + uint32_t p1 = static_cast(Mp.f >> -one.e); + uint64_t p2 = Mp.f & (one.f - 1); + int kappa = CountDecimalDigit32(p1); // kappa in [0, 9] + *len = 0; + + while (kappa > 0) { + uint32_t d = 0; + switch (kappa) { + case 9: d = p1 / 100000000; p1 %= 100000000; break; + case 8: d = p1 / 10000000; p1 %= 10000000; break; + case 7: d = p1 / 1000000; p1 %= 1000000; break; + case 6: d = p1 / 100000; p1 %= 100000; break; + case 5: d = p1 / 10000; p1 %= 10000; break; + case 4: d = p1 / 1000; p1 %= 1000; break; + case 3: d = p1 / 100; p1 %= 100; break; + case 2: d = p1 / 10; p1 %= 10; break; + case 1: d = p1; p1 = 0; break; + default:; + } + if (d || *len) + buffer[(*len)++] = static_cast('0' + static_cast(d)); + kappa--; + uint64_t tmp = (static_cast(p1) << -one.e) + p2; + if (tmp <= delta) { + *K += kappa; + GrisuRound(buffer, *len, delta, tmp, static_cast(kPow10[kappa]) << -one.e, wp_w.f); + return; + } + } + + // kappa = 0 + for (;;) { + p2 *= 10; + delta *= 10; + char d = static_cast(p2 >> -one.e); + if (d || *len) + buffer[(*len)++] = static_cast('0' + d); + p2 &= one.f - 1; + kappa--; + if (p2 < delta) { + *K += kappa; + int index = -kappa; + GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[index] : 0)); + return; + } + } +} + +inline void Grisu2(double value, char* buffer, int* length, int* K) { + const DiyFp v(value); + DiyFp w_m, w_p; + v.NormalizedBoundaries(&w_m, &w_p); + + const DiyFp c_mk = GetCachedPower(w_p.e, K); + const DiyFp W = v.Normalize() * c_mk; + DiyFp Wp = w_p * c_mk; + DiyFp Wm = w_m * c_mk; + Wm.f++; + Wp.f--; + DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K); +} + +inline char* WriteExponent(int K, char* buffer) { + if (K < 0) { + *buffer++ = '-'; + K = -K; + } + + if (K >= 100) { + *buffer++ = static_cast('0' + static_cast(K / 100)); + K %= 100; + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else if (K >= 10) { + const char* d = GetDigitsLut() + K * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + else + *buffer++ = static_cast('0' + static_cast(K)); + + return buffer; +} + +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { + const int kk = length + k; // 10^(kk-1) <= v < 10^kk + + if (0 <= k && kk <= 21) { + // 1234e7 -> 12340000000 + for (int i = length; i < kk; i++) + buffer[i] = '0'; + buffer[kk] = '.'; + buffer[kk + 1] = '0'; + return &buffer[kk + 2]; + } + else if (0 < kk && kk <= 21) { + // 1234e-2 -> 12.34 + std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); + buffer[kk] = '.'; + if (0 > k + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; + } + else if (-6 < kk && kk <= 0) { + // 1234e-6 -> 0.001234 + const int offset = 2 - kk; + std::memmove(&buffer[offset], &buffer[0], static_cast(length)); + buffer[0] = '0'; + buffer[1] = '.'; + for (int i = 2; i < offset; i++) + buffer[i] = '0'; + if (length - kk > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else if (length == 1) { + // 1e30 + buffer[1] = 'e'; + return WriteExponent(kk - 1, &buffer[2]); + } + else { + // 1234e30 -> 1.234e33 + std::memmove(&buffer[2], &buffer[1], static_cast(length - 1)); + buffer[1] = '.'; + buffer[length + 1] = 'e'; + return WriteExponent(kk - 1, &buffer[0 + length + 2]); + } +} + +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); + Double d(value); + if (d.IsZero()) { + if (d.Sign()) + *buffer++ = '-'; // -0.0, Issue #289 + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; + } + else { + if (value < 0) { + *buffer++ = '-'; + value = -value; + } + int length, K; + Grisu2(value, buffer, &length, &K); + return Prettify(buffer, length, K, maxDecimalPlaces); + } +} + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_DTOA_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/ieee754.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/ieee754.h new file mode 100644 index 0000000000..c2684ba2a3 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/ieee754.h @@ -0,0 +1,78 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_IEEE754_ +#define RAPIDJSON_IEEE754_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +class Double { +public: + Double() {} + Double(double d) : d_(d) {} + Double(uint64_t u) : u_(u) {} + + double Value() const { return d_; } + uint64_t Uint64Value() const { return u_; } + + double NextPositiveDouble() const { + RAPIDJSON_ASSERT(!Sign()); + return Double(u_ + 1).Value(); + } + + bool Sign() const { return (u_ & kSignMask) != 0; } + uint64_t Significand() const { return u_ & kSignificandMask; } + int Exponent() const { return static_cast(((u_ & kExponentMask) >> kSignificandSize) - kExponentBias); } + + bool IsNan() const { return (u_ & kExponentMask) == kExponentMask && Significand() != 0; } + bool IsInf() const { return (u_ & kExponentMask) == kExponentMask && Significand() == 0; } + bool IsNanOrInf() const { return (u_ & kExponentMask) == kExponentMask; } + bool IsNormal() const { return (u_ & kExponentMask) != 0 || Significand() == 0; } + bool IsZero() const { return (u_ & (kExponentMask | kSignificandMask)) == 0; } + + uint64_t IntegerSignificand() const { return IsNormal() ? Significand() | kHiddenBit : Significand(); } + int IntegerExponent() const { return (IsNormal() ? Exponent() : kDenormalExponent) - kSignificandSize; } + uint64_t ToBias() const { return (u_ & kSignMask) ? ~u_ + 1 : u_ | kSignMask; } + + static int EffectiveSignificandSize(int order) { + if (order >= -1021) + return 53; + else if (order <= -1074) + return 0; + else + return order + 1074; + } + +private: + static const int kSignificandSize = 52; + static const int kExponentBias = 0x3FF; + static const int kDenormalExponent = 1 - kExponentBias; + static const uint64_t kSignMask = RAPIDJSON_UINT64_C2(0x80000000, 0x00000000); + static const uint64_t kExponentMask = RAPIDJSON_UINT64_C2(0x7FF00000, 0x00000000); + static const uint64_t kSignificandMask = RAPIDJSON_UINT64_C2(0x000FFFFF, 0xFFFFFFFF); + static const uint64_t kHiddenBit = RAPIDJSON_UINT64_C2(0x00100000, 0x00000000); + + union { + double d_; + uint64_t u_; + }; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_IEEE754_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/itoa.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/itoa.h new file mode 100644 index 0000000000..9b1c45cc1b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/itoa.h @@ -0,0 +1,308 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ITOA_ +#define RAPIDJSON_ITOA_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline const char* GetDigitsLut() { + static const char cDigitsLut[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' + }; + return cDigitsLut; +} + +inline char* u32toa(uint32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + + const char* cDigitsLut = GetDigitsLut(); + + if (value < 10000) { + const uint32_t d1 = (value / 100) << 1; + const uint32_t d2 = (value % 100) << 1; + + if (value >= 1000) + *buffer++ = cDigitsLut[d1]; + if (value >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else if (value < 100000000) { + // value = bbbbcccc + const uint32_t b = value / 10000; + const uint32_t c = value % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + else { + // value = aabbbbcccc in decimal + + const uint32_t a = value / 100000000; // 1 to 42 + value %= 100000000; + + if (a >= 10) { + const unsigned i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else + *buffer++ = static_cast('0' + static_cast(a)); + + const uint32_t b = value / 10000; // 0 to 9999 + const uint32_t c = value % 10000; // 0 to 9999 + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + return buffer; +} + +inline char* i32toa(int32_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint32_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u32toa(u, buffer); +} + +inline char* u64toa(uint64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + const char* cDigitsLut = GetDigitsLut(); + const uint64_t kTen8 = 100000000; + const uint64_t kTen9 = kTen8 * 10; + const uint64_t kTen10 = kTen8 * 100; + const uint64_t kTen11 = kTen8 * 1000; + const uint64_t kTen12 = kTen8 * 10000; + const uint64_t kTen13 = kTen8 * 100000; + const uint64_t kTen14 = kTen8 * 1000000; + const uint64_t kTen15 = kTen8 * 10000000; + const uint64_t kTen16 = kTen8 * kTen8; + + if (value < kTen8) { + uint32_t v = static_cast(value); + if (v < 10000) { + const uint32_t d1 = (v / 100) << 1; + const uint32_t d2 = (v % 100) << 1; + + if (v >= 1000) + *buffer++ = cDigitsLut[d1]; + if (v >= 100) + *buffer++ = cDigitsLut[d1 + 1]; + if (v >= 10) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + } + else { + // value = bbbbcccc + const uint32_t b = v / 10000; + const uint32_t c = v % 10000; + + const uint32_t d1 = (b / 100) << 1; + const uint32_t d2 = (b % 100) << 1; + + const uint32_t d3 = (c / 100) << 1; + const uint32_t d4 = (c % 100) << 1; + + if (value >= 10000000) + *buffer++ = cDigitsLut[d1]; + if (value >= 1000000) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= 100000) + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + } + } + else if (value < kTen16) { + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + if (value >= kTen15) + *buffer++ = cDigitsLut[d1]; + if (value >= kTen14) + *buffer++ = cDigitsLut[d1 + 1]; + if (value >= kTen13) + *buffer++ = cDigitsLut[d2]; + if (value >= kTen12) + *buffer++ = cDigitsLut[d2 + 1]; + if (value >= kTen11) + *buffer++ = cDigitsLut[d3]; + if (value >= kTen10) + *buffer++ = cDigitsLut[d3 + 1]; + if (value >= kTen9) + *buffer++ = cDigitsLut[d4]; + + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + else { + const uint32_t a = static_cast(value / kTen16); // 1 to 1844 + value %= kTen16; + + if (a < 10) + *buffer++ = static_cast('0' + static_cast(a)); + else if (a < 100) { + const uint32_t i = a << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else if (a < 1000) { + *buffer++ = static_cast('0' + static_cast(a / 100)); + + const uint32_t i = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + } + else { + const uint32_t i = (a / 100) << 1; + const uint32_t j = (a % 100) << 1; + *buffer++ = cDigitsLut[i]; + *buffer++ = cDigitsLut[i + 1]; + *buffer++ = cDigitsLut[j]; + *buffer++ = cDigitsLut[j + 1]; + } + + const uint32_t v0 = static_cast(value / kTen8); + const uint32_t v1 = static_cast(value % kTen8); + + const uint32_t b0 = v0 / 10000; + const uint32_t c0 = v0 % 10000; + + const uint32_t d1 = (b0 / 100) << 1; + const uint32_t d2 = (b0 % 100) << 1; + + const uint32_t d3 = (c0 / 100) << 1; + const uint32_t d4 = (c0 % 100) << 1; + + const uint32_t b1 = v1 / 10000; + const uint32_t c1 = v1 % 10000; + + const uint32_t d5 = (b1 / 100) << 1; + const uint32_t d6 = (b1 % 100) << 1; + + const uint32_t d7 = (c1 / 100) << 1; + const uint32_t d8 = (c1 % 100) << 1; + + *buffer++ = cDigitsLut[d1]; + *buffer++ = cDigitsLut[d1 + 1]; + *buffer++ = cDigitsLut[d2]; + *buffer++ = cDigitsLut[d2 + 1]; + *buffer++ = cDigitsLut[d3]; + *buffer++ = cDigitsLut[d3 + 1]; + *buffer++ = cDigitsLut[d4]; + *buffer++ = cDigitsLut[d4 + 1]; + *buffer++ = cDigitsLut[d5]; + *buffer++ = cDigitsLut[d5 + 1]; + *buffer++ = cDigitsLut[d6]; + *buffer++ = cDigitsLut[d6 + 1]; + *buffer++ = cDigitsLut[d7]; + *buffer++ = cDigitsLut[d7 + 1]; + *buffer++ = cDigitsLut[d8]; + *buffer++ = cDigitsLut[d8 + 1]; + } + + return buffer; +} + +inline char* i64toa(int64_t value, char* buffer) { + RAPIDJSON_ASSERT(buffer != 0); + uint64_t u = static_cast(value); + if (value < 0) { + *buffer++ = '-'; + u = ~u + 1; + } + + return u64toa(u, buffer); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ITOA_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/meta.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/meta.h new file mode 100644 index 0000000000..d401edf851 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/meta.h @@ -0,0 +1,186 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_META_H_ +#define RAPIDJSON_INTERNAL_META_H_ + +#include "../rapidjson.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(6334) +#endif + +#if RAPIDJSON_HAS_CXX11_TYPETRAITS +#include +#endif + +//@cond RAPIDJSON_INTERNAL +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +// Helper to wrap/convert arbitrary types to void, useful for arbitrary type matching +template struct Void { typedef void Type; }; + +/////////////////////////////////////////////////////////////////////////////// +// BoolType, TrueType, FalseType +// +template struct BoolType { + static const bool Value = Cond; + typedef BoolType Type; +}; +typedef BoolType TrueType; +typedef BoolType FalseType; + + +/////////////////////////////////////////////////////////////////////////////// +// SelectIf, BoolExpr, NotExpr, AndExpr, OrExpr +// + +template struct SelectIfImpl { template struct Apply { typedef T1 Type; }; }; +template <> struct SelectIfImpl { template struct Apply { typedef T2 Type; }; }; +template struct SelectIfCond : SelectIfImpl::template Apply {}; +template struct SelectIf : SelectIfCond {}; + +template struct AndExprCond : FalseType {}; +template <> struct AndExprCond : TrueType {}; +template struct OrExprCond : TrueType {}; +template <> struct OrExprCond : FalseType {}; + +template struct BoolExpr : SelectIf::Type {}; +template struct NotExpr : SelectIf::Type {}; +template struct AndExpr : AndExprCond::Type {}; +template struct OrExpr : OrExprCond::Type {}; + + +/////////////////////////////////////////////////////////////////////////////// +// AddConst, MaybeAddConst, RemoveConst +template struct AddConst { typedef const T Type; }; +template struct MaybeAddConst : SelectIfCond {}; +template struct RemoveConst { typedef T Type; }; +template struct RemoveConst { typedef T Type; }; + + +/////////////////////////////////////////////////////////////////////////////// +// IsSame, IsConst, IsMoreConst, IsPointer +// +template struct IsSame : FalseType {}; +template struct IsSame : TrueType {}; + +template struct IsConst : FalseType {}; +template struct IsConst : TrueType {}; + +template +struct IsMoreConst + : AndExpr::Type, typename RemoveConst::Type>, + BoolType::Value >= IsConst::Value> >::Type {}; + +template struct IsPointer : FalseType {}; +template struct IsPointer : TrueType {}; + +/////////////////////////////////////////////////////////////////////////////// +// IsBaseOf +// +#if RAPIDJSON_HAS_CXX11_TYPETRAITS + +template struct IsBaseOf + : BoolType< ::std::is_base_of::value> {}; + +#else // simplified version adopted from Boost + +template struct IsBaseOfImpl { + RAPIDJSON_STATIC_ASSERT(sizeof(B) != 0); + RAPIDJSON_STATIC_ASSERT(sizeof(D) != 0); + + typedef char (&Yes)[1]; + typedef char (&No) [2]; + + template + static Yes Check(const D*, T); + static No Check(const B*, int); + + struct Host { + operator const B*() const; + operator const D*(); + }; + + enum { Value = (sizeof(Check(Host(), 0)) == sizeof(Yes)) }; +}; + +template struct IsBaseOf + : OrExpr, BoolExpr > >::Type {}; + +#endif // RAPIDJSON_HAS_CXX11_TYPETRAITS + + +////////////////////////////////////////////////////////////////////////// +// EnableIf / DisableIf +// +template struct EnableIfCond { typedef T Type; }; +template struct EnableIfCond { /* empty */ }; + +template struct DisableIfCond { typedef T Type; }; +template struct DisableIfCond { /* empty */ }; + +template +struct EnableIf : EnableIfCond {}; + +template +struct DisableIf : DisableIfCond {}; + +// SFINAE helpers +struct SfinaeTag {}; +template struct RemoveSfinaeTag; +template struct RemoveSfinaeTag { typedef T Type; }; + +#define RAPIDJSON_REMOVEFPTR_(type) \ + typename ::RAPIDJSON_NAMESPACE::internal::RemoveSfinaeTag \ + < ::RAPIDJSON_NAMESPACE::internal::SfinaeTag&(*) type>::Type + +#define RAPIDJSON_ENABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type * = NULL + +#define RAPIDJSON_DISABLEIF(cond) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type * = NULL + +#define RAPIDJSON_ENABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::EnableIf \ + ::Type + +#define RAPIDJSON_DISABLEIF_RETURN(cond,returntype) \ + typename ::RAPIDJSON_NAMESPACE::internal::DisableIf \ + ::Type + +} // namespace internal +RAPIDJSON_NAMESPACE_END +//@endcond + +#if defined(_MSC_VER) && !defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_META_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/pow10.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/pow10.h new file mode 100644 index 0000000000..02f475d705 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/pow10.h @@ -0,0 +1,55 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POW10_ +#define RAPIDJSON_POW10_ + +#include "../rapidjson.h" + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Computes integer powers of 10 in double (10.0^n). +/*! This function uses lookup table for fast and accurate results. + \param n non-negative exponent. Must <= 308. + \return 10.0^n +*/ +inline double Pow10(int n) { + static const double e[] = { // 1e-0...1e308: 309 * 8 bytes = 2472 bytes + 1e+0, + 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11, 1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, + 1e+21, 1e+22, 1e+23, 1e+24, 1e+25, 1e+26, 1e+27, 1e+28, 1e+29, 1e+30, 1e+31, 1e+32, 1e+33, 1e+34, 1e+35, 1e+36, 1e+37, 1e+38, 1e+39, 1e+40, + 1e+41, 1e+42, 1e+43, 1e+44, 1e+45, 1e+46, 1e+47, 1e+48, 1e+49, 1e+50, 1e+51, 1e+52, 1e+53, 1e+54, 1e+55, 1e+56, 1e+57, 1e+58, 1e+59, 1e+60, + 1e+61, 1e+62, 1e+63, 1e+64, 1e+65, 1e+66, 1e+67, 1e+68, 1e+69, 1e+70, 1e+71, 1e+72, 1e+73, 1e+74, 1e+75, 1e+76, 1e+77, 1e+78, 1e+79, 1e+80, + 1e+81, 1e+82, 1e+83, 1e+84, 1e+85, 1e+86, 1e+87, 1e+88, 1e+89, 1e+90, 1e+91, 1e+92, 1e+93, 1e+94, 1e+95, 1e+96, 1e+97, 1e+98, 1e+99, 1e+100, + 1e+101,1e+102,1e+103,1e+104,1e+105,1e+106,1e+107,1e+108,1e+109,1e+110,1e+111,1e+112,1e+113,1e+114,1e+115,1e+116,1e+117,1e+118,1e+119,1e+120, + 1e+121,1e+122,1e+123,1e+124,1e+125,1e+126,1e+127,1e+128,1e+129,1e+130,1e+131,1e+132,1e+133,1e+134,1e+135,1e+136,1e+137,1e+138,1e+139,1e+140, + 1e+141,1e+142,1e+143,1e+144,1e+145,1e+146,1e+147,1e+148,1e+149,1e+150,1e+151,1e+152,1e+153,1e+154,1e+155,1e+156,1e+157,1e+158,1e+159,1e+160, + 1e+161,1e+162,1e+163,1e+164,1e+165,1e+166,1e+167,1e+168,1e+169,1e+170,1e+171,1e+172,1e+173,1e+174,1e+175,1e+176,1e+177,1e+178,1e+179,1e+180, + 1e+181,1e+182,1e+183,1e+184,1e+185,1e+186,1e+187,1e+188,1e+189,1e+190,1e+191,1e+192,1e+193,1e+194,1e+195,1e+196,1e+197,1e+198,1e+199,1e+200, + 1e+201,1e+202,1e+203,1e+204,1e+205,1e+206,1e+207,1e+208,1e+209,1e+210,1e+211,1e+212,1e+213,1e+214,1e+215,1e+216,1e+217,1e+218,1e+219,1e+220, + 1e+221,1e+222,1e+223,1e+224,1e+225,1e+226,1e+227,1e+228,1e+229,1e+230,1e+231,1e+232,1e+233,1e+234,1e+235,1e+236,1e+237,1e+238,1e+239,1e+240, + 1e+241,1e+242,1e+243,1e+244,1e+245,1e+246,1e+247,1e+248,1e+249,1e+250,1e+251,1e+252,1e+253,1e+254,1e+255,1e+256,1e+257,1e+258,1e+259,1e+260, + 1e+261,1e+262,1e+263,1e+264,1e+265,1e+266,1e+267,1e+268,1e+269,1e+270,1e+271,1e+272,1e+273,1e+274,1e+275,1e+276,1e+277,1e+278,1e+279,1e+280, + 1e+281,1e+282,1e+283,1e+284,1e+285,1e+286,1e+287,1e+288,1e+289,1e+290,1e+291,1e+292,1e+293,1e+294,1e+295,1e+296,1e+297,1e+298,1e+299,1e+300, + 1e+301,1e+302,1e+303,1e+304,1e+305,1e+306,1e+307,1e+308 + }; + RAPIDJSON_ASSERT(n >= 0 && n <= 308); + return e[n]; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_POW10_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/regex.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/regex.h new file mode 100644 index 0000000000..af7e06de51 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/regex.h @@ -0,0 +1,739 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_REGEX_H_ +#define RAPIDJSON_INTERNAL_REGEX_H_ + +#include "../allocators.h" +#include "../stream.h" +#include "stack.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifndef RAPIDJSON_REGEX_VERBOSE +#define RAPIDJSON_REGEX_VERBOSE 0 +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// DecodedStream + +template +class DecodedStream { +public: + DecodedStream(SourceStream& ss) : ss_(ss), codepoint_() { Decode(); } + unsigned Peek() { return codepoint_; } + unsigned Take() { + unsigned c = codepoint_; + if (c) // No further decoding when '\0' + Decode(); + return c; + } + +private: + void Decode() { + if (!Encoding::Decode(ss_, &codepoint_)) + codepoint_ = 0; + } + + SourceStream& ss_; + unsigned codepoint_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericRegex + +static const SizeType kRegexInvalidState = ~SizeType(0); //!< Represents an invalid index in GenericRegex::State::out, out1 +static const SizeType kRegexInvalidRange = ~SizeType(0); + +template +class GenericRegexSearch; + +//! Regular expression engine with subset of ECMAscript grammar. +/*! + Supported regular expression syntax: + - \c ab Concatenation + - \c a|b Alternation + - \c a? Zero or one + - \c a* Zero or more + - \c a+ One or more + - \c a{3} Exactly 3 times + - \c a{3,} At least 3 times + - \c a{3,5} 3 to 5 times + - \c (ab) Grouping + - \c ^a At the beginning + - \c a$ At the end + - \c . Any character + - \c [abc] Character classes + - \c [a-c] Character class range + - \c [a-z0-9_] Character class combination + - \c [^abc] Negated character classes + - \c [^a-c] Negated character class range + - \c [\b] Backspace (U+0008) + - \c \\| \\\\ ... Escape characters + - \c \\f Form feed (U+000C) + - \c \\n Line feed (U+000A) + - \c \\r Carriage return (U+000D) + - \c \\t Tab (U+0009) + - \c \\v Vertical tab (U+000B) + + \note This is a Thompson NFA engine, implemented with reference to + Cox, Russ. "Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, PHP, Python, Ruby,...).", + https://swtch.com/~rsc/regexp/regexp1.html +*/ +template +class GenericRegex { +public: + typedef Encoding EncodingType; + typedef typename Encoding::Ch Ch; + template friend class GenericRegexSearch; + + GenericRegex(const Ch* source, Allocator* allocator = 0) : + ownAllocator_(allocator ? 0 : RAPIDJSON_NEW(Allocator)()), allocator_(allocator ? allocator : ownAllocator_), + states_(allocator_, 256), ranges_(allocator_, 256), root_(kRegexInvalidState), stateCount_(), rangeCount_(), + anchorBegin_(), anchorEnd_() + { + GenericStringStream ss(source); + DecodedStream, Encoding> ds(ss); + Parse(ds); + } + + ~GenericRegex() + { + RAPIDJSON_DELETE(ownAllocator_); + } + + bool IsValid() const { + return root_ != kRegexInvalidState; + } + +private: + enum Operator { + kZeroOrOne, + kZeroOrMore, + kOneOrMore, + kConcatenation, + kAlternation, + kLeftParenthesis + }; + + static const unsigned kAnyCharacterClass = 0xFFFFFFFF; //!< For '.' + static const unsigned kRangeCharacterClass = 0xFFFFFFFE; + static const unsigned kRangeNegationFlag = 0x80000000; + + struct Range { + unsigned start; // + unsigned end; + SizeType next; + }; + + struct State { + SizeType out; //!< Equals to kInvalid for matching state + SizeType out1; //!< Equals to non-kInvalid for split + SizeType rangeStart; + unsigned codepoint; + }; + + struct Frag { + Frag(SizeType s, SizeType o, SizeType m) : start(s), out(o), minIndex(m) {} + SizeType start; + SizeType out; //!< link-list of all output states + SizeType minIndex; + }; + + State& GetState(SizeType index) { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + const State& GetState(SizeType index) const { + RAPIDJSON_ASSERT(index < stateCount_); + return states_.template Bottom()[index]; + } + + Range& GetRange(SizeType index) { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + const Range& GetRange(SizeType index) const { + RAPIDJSON_ASSERT(index < rangeCount_); + return ranges_.template Bottom()[index]; + } + + template + void Parse(DecodedStream& ds) { + Stack operandStack(allocator_, 256); // Frag + Stack operatorStack(allocator_, 256); // Operator + Stack atomCountStack(allocator_, 256); // unsigned (Atom per parenthesis) + + *atomCountStack.template Push() = 0; + + unsigned codepoint; + while (ds.Peek() != 0) { + switch (codepoint = ds.Take()) { + case '^': + anchorBegin_ = true; + break; + + case '$': + anchorEnd_ = true; + break; + + case '|': + while (!operatorStack.Empty() && *operatorStack.template Top() < kAlternation) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + *operatorStack.template Push() = kAlternation; + *atomCountStack.template Top() = 0; + break; + + case '(': + *operatorStack.template Push() = kLeftParenthesis; + *atomCountStack.template Push() = 0; + break; + + case ')': + while (!operatorStack.Empty() && *operatorStack.template Top() != kLeftParenthesis) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + if (operatorStack.Empty()) + return; + operatorStack.template Pop(1); + atomCountStack.template Pop(1); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '?': + if (!Eval(operandStack, kZeroOrOne)) + return; + break; + + case '*': + if (!Eval(operandStack, kZeroOrMore)) + return; + break; + + case '+': + if (!Eval(operandStack, kOneOrMore)) + return; + break; + + case '{': + { + unsigned n, m; + if (!ParseUnsigned(ds, &n)) + return; + + if (ds.Peek() == ',') { + ds.Take(); + if (ds.Peek() == '}') + m = kInfinityQuantifier; + else if (!ParseUnsigned(ds, &m) || m < n) + return; + } + else + m = n; + + if (!EvalQuantifier(operandStack, n, m) || ds.Peek() != '}') + return; + ds.Take(); + } + break; + + case '.': + PushOperand(operandStack, kAnyCharacterClass); + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '[': + { + SizeType range; + if (!ParseRange(ds, &range)) + return; + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, kRangeCharacterClass); + GetState(s).rangeStart = range; + *operandStack.template Push() = Frag(s, s, s); + } + ImplicitConcatenation(atomCountStack, operatorStack); + break; + + case '\\': // Escape character + if (!CharacterEscape(ds, &codepoint)) + return; // Unsupported escape character + // fall through to default + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + default: // Pattern character + PushOperand(operandStack, codepoint); + ImplicitConcatenation(atomCountStack, operatorStack); + } + } + + while (!operatorStack.Empty()) + if (!Eval(operandStack, *operatorStack.template Pop(1))) + return; + + // Link the operand to matching state. + if (operandStack.GetSize() == sizeof(Frag)) { + Frag* e = operandStack.template Pop(1); + Patch(e->out, NewState(kRegexInvalidState, kRegexInvalidState, 0)); + root_ = e->start; + +#if RAPIDJSON_REGEX_VERBOSE + printf("root: %d\n", root_); + for (SizeType i = 0; i < stateCount_ ; i++) { + State& s = GetState(i); + printf("[%2d] out: %2d out1: %2d c: '%c'\n", i, s.out, s.out1, (char)s.codepoint); + } + printf("\n"); +#endif + } + } + + SizeType NewState(SizeType out, SizeType out1, unsigned codepoint) { + State* s = states_.template Push(); + s->out = out; + s->out1 = out1; + s->codepoint = codepoint; + s->rangeStart = kRegexInvalidRange; + return stateCount_++; + } + + void PushOperand(Stack& operandStack, unsigned codepoint) { + SizeType s = NewState(kRegexInvalidState, kRegexInvalidState, codepoint); + *operandStack.template Push() = Frag(s, s, s); + } + + void ImplicitConcatenation(Stack& atomCountStack, Stack& operatorStack) { + if (*atomCountStack.template Top()) + *operatorStack.template Push() = kConcatenation; + (*atomCountStack.template Top())++; + } + + SizeType Append(SizeType l1, SizeType l2) { + SizeType old = l1; + while (GetState(l1).out != kRegexInvalidState) + l1 = GetState(l1).out; + GetState(l1).out = l2; + return old; + } + + void Patch(SizeType l, SizeType s) { + for (SizeType next; l != kRegexInvalidState; l = next) { + next = GetState(l).out; + GetState(l).out = s; + } + } + + bool Eval(Stack& operandStack, Operator op) { + switch (op) { + case kConcatenation: + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag) * 2); + { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + Patch(e1.out, e2.start); + *operandStack.template Push() = Frag(e1.start, e2.out, Min(e1.minIndex, e2.minIndex)); + } + return true; + + case kAlternation: + if (operandStack.GetSize() >= sizeof(Frag) * 2) { + Frag e2 = *operandStack.template Pop(1); + Frag e1 = *operandStack.template Pop(1); + SizeType s = NewState(e1.start, e2.start, 0); + *operandStack.template Push() = Frag(s, Append(e1.out, e2.out), Min(e1.minIndex, e2.minIndex)); + return true; + } + return false; + + case kZeroOrOne: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + *operandStack.template Push() = Frag(s, Append(e.out, s), e.minIndex); + return true; + } + return false; + + case kZeroOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(s, s, e.minIndex); + return true; + } + return false; + + case kOneOrMore: + if (operandStack.GetSize() >= sizeof(Frag)) { + Frag e = *operandStack.template Pop(1); + SizeType s = NewState(kRegexInvalidState, e.start, 0); + Patch(e.out, s); + *operandStack.template Push() = Frag(e.start, s, e.minIndex); + return true; + } + return false; + + default: + // syntax error (e.g. unclosed kLeftParenthesis) + return false; + } + } + + bool EvalQuantifier(Stack& operandStack, unsigned n, unsigned m) { + RAPIDJSON_ASSERT(n <= m); + RAPIDJSON_ASSERT(operandStack.GetSize() >= sizeof(Frag)); + + if (n == 0) { + if (m == 0) // a{0} not support + return false; + else if (m == kInfinityQuantifier) + Eval(operandStack, kZeroOrMore); // a{0,} -> a* + else { + Eval(operandStack, kZeroOrOne); // a{0,5} -> a? + for (unsigned i = 0; i < m - 1; i++) + CloneTopOperand(operandStack); // a{0,5} -> a? a? a? a? a? + for (unsigned i = 0; i < m - 1; i++) + Eval(operandStack, kConcatenation); // a{0,5} -> a?a?a?a?a? + } + return true; + } + + for (unsigned i = 0; i < n - 1; i++) // a{3} -> a a a + CloneTopOperand(operandStack); + + if (m == kInfinityQuantifier) + Eval(operandStack, kOneOrMore); // a{3,} -> a a a+ + else if (m > n) { + CloneTopOperand(operandStack); // a{3,5} -> a a a a + Eval(operandStack, kZeroOrOne); // a{3,5} -> a a a a? + for (unsigned i = n; i < m - 1; i++) + CloneTopOperand(operandStack); // a{3,5} -> a a a a? a? + for (unsigned i = n; i < m; i++) + Eval(operandStack, kConcatenation); // a{3,5} -> a a aa?a? + } + + for (unsigned i = 0; i < n - 1; i++) + Eval(operandStack, kConcatenation); // a{3} -> aaa, a{3,} -> aaa+, a{3.5} -> aaaa?a? + + return true; + } + + static SizeType Min(SizeType a, SizeType b) { return a < b ? a : b; } + + void CloneTopOperand(Stack& operandStack) { + const Frag src = *operandStack.template Top(); // Copy constructor to prevent invalidation + SizeType count = stateCount_ - src.minIndex; // Assumes top operand contains states in [src->minIndex, stateCount_) + State* s = states_.template Push(count); + memcpy(s, &GetState(src.minIndex), count * sizeof(State)); + for (SizeType j = 0; j < count; j++) { + if (s[j].out != kRegexInvalidState) + s[j].out += count; + if (s[j].out1 != kRegexInvalidState) + s[j].out1 += count; + } + *operandStack.template Push() = Frag(src.start + count, src.out + count, src.minIndex + count); + stateCount_ += count; + } + + template + bool ParseUnsigned(DecodedStream& ds, unsigned* u) { + unsigned r = 0; + if (ds.Peek() < '0' || ds.Peek() > '9') + return false; + while (ds.Peek() >= '0' && ds.Peek() <= '9') { + if (r >= 429496729 && ds.Peek() > '5') // 2^32 - 1 = 4294967295 + return false; // overflow + r = r * 10 + (ds.Take() - '0'); + } + *u = r; + return true; + } + + template + bool ParseRange(DecodedStream& ds, SizeType* range) { + bool isBegin = true; + bool negate = false; + int step = 0; + SizeType start = kRegexInvalidRange; + SizeType current = kRegexInvalidRange; + unsigned codepoint; + while ((codepoint = ds.Take()) != 0) { + if (isBegin) { + isBegin = false; + if (codepoint == '^') { + negate = true; + continue; + } + } + + switch (codepoint) { + case ']': + if (start == kRegexInvalidRange) + return false; // Error: nothing inside [] + if (step == 2) { // Add trailing '-' + SizeType r = NewRange('-'); + RAPIDJSON_ASSERT(current != kRegexInvalidRange); + GetRange(current).next = r; + } + if (negate) + GetRange(start).start |= kRangeNegationFlag; + *range = start; + return true; + + case '\\': + if (ds.Peek() == 'b') { + ds.Take(); + codepoint = 0x0008; // Escape backspace character + } + else if (!CharacterEscape(ds, &codepoint)) + return false; + // fall through to default + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + default: + switch (step) { + case 1: + if (codepoint == '-') { + step++; + break; + } + // fall through to step 0 for other characters + RAPIDJSON_DELIBERATE_FALLTHROUGH; + + case 0: + { + SizeType r = NewRange(codepoint); + if (current != kRegexInvalidRange) + GetRange(current).next = r; + if (start == kRegexInvalidRange) + start = r; + current = r; + } + step = 1; + break; + + default: + RAPIDJSON_ASSERT(step == 2); + GetRange(current).end = codepoint; + step = 0; + } + } + } + return false; + } + + SizeType NewRange(unsigned codepoint) { + Range* r = ranges_.template Push(); + r->start = r->end = codepoint; + r->next = kRegexInvalidRange; + return rangeCount_++; + } + + template + bool CharacterEscape(DecodedStream& ds, unsigned* escapedCodepoint) { + unsigned codepoint; + switch (codepoint = ds.Take()) { + case '^': + case '$': + case '|': + case '(': + case ')': + case '?': + case '*': + case '+': + case '.': + case '[': + case ']': + case '{': + case '}': + case '\\': + *escapedCodepoint = codepoint; return true; + case 'f': *escapedCodepoint = 0x000C; return true; + case 'n': *escapedCodepoint = 0x000A; return true; + case 'r': *escapedCodepoint = 0x000D; return true; + case 't': *escapedCodepoint = 0x0009; return true; + case 'v': *escapedCodepoint = 0x000B; return true; + default: + return false; // Unsupported escape character + } + } + + Allocator* ownAllocator_; + Allocator* allocator_; + Stack states_; + Stack ranges_; + SizeType root_; + SizeType stateCount_; + SizeType rangeCount_; + + static const unsigned kInfinityQuantifier = ~0u; + + // For SearchWithAnchoring() + bool anchorBegin_; + bool anchorEnd_; +}; + +template +class GenericRegexSearch { +public: + typedef typename RegexType::EncodingType Encoding; + typedef typename Encoding::Ch Ch; + + GenericRegexSearch(const RegexType& regex, Allocator* allocator = 0) : + regex_(regex), allocator_(allocator), ownAllocator_(0), + state0_(allocator, 0), state1_(allocator, 0), stateSet_() + { + RAPIDJSON_ASSERT(regex_.IsValid()); + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + stateSet_ = static_cast(allocator_->Malloc(GetStateSetSize())); + state0_.template Reserve(regex_.stateCount_); + state1_.template Reserve(regex_.stateCount_); + } + + ~GenericRegexSearch() { + Allocator::Free(stateSet_); + RAPIDJSON_DELETE(ownAllocator_); + } + + template + bool Match(InputStream& is) { + return SearchWithAnchoring(is, true, true); + } + + bool Match(const Ch* s) { + GenericStringStream is(s); + return Match(is); + } + + template + bool Search(InputStream& is) { + return SearchWithAnchoring(is, regex_.anchorBegin_, regex_.anchorEnd_); + } + + bool Search(const Ch* s) { + GenericStringStream is(s); + return Search(is); + } + +private: + typedef typename RegexType::State State; + typedef typename RegexType::Range Range; + + template + bool SearchWithAnchoring(InputStream& is, bool anchorBegin, bool anchorEnd) { + DecodedStream ds(is); + + state0_.Clear(); + Stack *current = &state0_, *next = &state1_; + const size_t stateSetSize = GetStateSetSize(); + std::memset(stateSet_, 0, stateSetSize); + + bool matched = AddState(*current, regex_.root_); + unsigned codepoint; + while (!current->Empty() && (codepoint = ds.Take()) != 0) { + std::memset(stateSet_, 0, stateSetSize); + next->Clear(); + matched = false; + for (const SizeType* s = current->template Bottom(); s != current->template End(); ++s) { + const State& sr = regex_.GetState(*s); + if (sr.codepoint == codepoint || + sr.codepoint == RegexType::kAnyCharacterClass || + (sr.codepoint == RegexType::kRangeCharacterClass && MatchRange(sr.rangeStart, codepoint))) + { + matched = AddState(*next, sr.out) || matched; + if (!anchorEnd && matched) + return true; + } + if (!anchorBegin) + AddState(*next, regex_.root_); + } + internal::Swap(current, next); + } + + return matched; + } + + size_t GetStateSetSize() const { + return (regex_.stateCount_ + 31) / 32 * 4; + } + + // Return whether the added states is a match state + bool AddState(Stack& l, SizeType index) { + RAPIDJSON_ASSERT(index != kRegexInvalidState); + + const State& s = regex_.GetState(index); + if (s.out1 != kRegexInvalidState) { // Split + bool matched = AddState(l, s.out); + return AddState(l, s.out1) || matched; + } + else if (!(stateSet_[index >> 5] & (1u << (index & 31)))) { + stateSet_[index >> 5] |= (1u << (index & 31)); + *l.template PushUnsafe() = index; + } + return s.out == kRegexInvalidState; // by using PushUnsafe() above, we can ensure s is not validated due to reallocation. + } + + bool MatchRange(SizeType rangeIndex, unsigned codepoint) const { + bool yes = (regex_.GetRange(rangeIndex).start & RegexType::kRangeNegationFlag) == 0; + while (rangeIndex != kRegexInvalidRange) { + const Range& r = regex_.GetRange(rangeIndex); + if (codepoint >= (r.start & ~RegexType::kRangeNegationFlag) && codepoint <= r.end) + return yes; + rangeIndex = r.next; + } + return !yes; + } + + const RegexType& regex_; + Allocator* allocator_; + Allocator* ownAllocator_; + Stack state0_; + Stack state1_; + uint32_t* stateSet_; +}; + +typedef GenericRegex > Regex; +typedef GenericRegexSearch RegexSearch; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_REGEX_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/stack.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/stack.h new file mode 100644 index 0000000000..45dca6a8b0 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/stack.h @@ -0,0 +1,232 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STACK_H_ +#define RAPIDJSON_INTERNAL_STACK_H_ + +#include "../allocators.h" +#include "swap.h" +#include + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +/////////////////////////////////////////////////////////////////////////////// +// Stack + +//! A type-unsafe stack for storing different types of data. +/*! \tparam Allocator Allocator for allocating stack memory. +*/ +template +class Stack { +public: + // Optimization note: Do not allocate memory for stack_ in constructor. + // Do it lazily when first Push() -> Expand() -> Resize(). + Stack(Allocator* allocator, size_t stackCapacity) : allocator_(allocator), ownAllocator_(0), stack_(0), stackTop_(0), stackEnd_(0), initialCapacity_(stackCapacity) { + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack(Stack&& rhs) + : allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + stack_(rhs.stack_), + stackTop_(rhs.stackTop_), + stackEnd_(rhs.stackEnd_), + initialCapacity_(rhs.initialCapacity_) + { + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } +#endif + + ~Stack() { + Destroy(); + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Stack& operator=(Stack&& rhs) { + if (&rhs != this) + { + Destroy(); + + allocator_ = rhs.allocator_; + ownAllocator_ = rhs.ownAllocator_; + stack_ = rhs.stack_; + stackTop_ = rhs.stackTop_; + stackEnd_ = rhs.stackEnd_; + initialCapacity_ = rhs.initialCapacity_; + + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.stack_ = 0; + rhs.stackTop_ = 0; + rhs.stackEnd_ = 0; + rhs.initialCapacity_ = 0; + } + return *this; + } +#endif + + void Swap(Stack& rhs) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, rhs.allocator_); + internal::Swap(ownAllocator_, rhs.ownAllocator_); + internal::Swap(stack_, rhs.stack_); + internal::Swap(stackTop_, rhs.stackTop_); + internal::Swap(stackEnd_, rhs.stackEnd_); + internal::Swap(initialCapacity_, rhs.initialCapacity_); + } + + void Clear() { stackTop_ = stack_; } + + void ShrinkToFit() { + if (Empty()) { + // If the stack is empty, completely deallocate the memory. + Allocator::Free(stack_); // NOLINT (+clang-analyzer-unix.Malloc) + stack_ = 0; + stackTop_ = 0; + stackEnd_ = 0; + } + else + Resize(GetSize()); + } + + // Optimization note: try to minimize the size of this function for force inline. + // Expansion is run very infrequently, so it is moved to another (probably non-inline) function. + template + RAPIDJSON_FORCEINLINE void Reserve(size_t count = 1) { + // Expand the stack if needed + if (RAPIDJSON_UNLIKELY(static_cast(sizeof(T) * count) > (stackEnd_ - stackTop_))) + Expand(count); + } + + template + RAPIDJSON_FORCEINLINE T* Push(size_t count = 1) { + Reserve(count); + return PushUnsafe(count); + } + + template + RAPIDJSON_FORCEINLINE T* PushUnsafe(size_t count = 1) { + RAPIDJSON_ASSERT(stackTop_); + RAPIDJSON_ASSERT(static_cast(sizeof(T) * count) <= (stackEnd_ - stackTop_)); + T* ret = reinterpret_cast(stackTop_); + stackTop_ += sizeof(T) * count; + return ret; + } + + template + T* Pop(size_t count) { + RAPIDJSON_ASSERT(GetSize() >= count * sizeof(T)); + stackTop_ -= count * sizeof(T); + return reinterpret_cast(stackTop_); + } + + template + T* Top() { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + const T* Top() const { + RAPIDJSON_ASSERT(GetSize() >= sizeof(T)); + return reinterpret_cast(stackTop_ - sizeof(T)); + } + + template + T* End() { return reinterpret_cast(stackTop_); } + + template + const T* End() const { return reinterpret_cast(stackTop_); } + + template + T* Bottom() { return reinterpret_cast(stack_); } + + template + const T* Bottom() const { return reinterpret_cast(stack_); } + + bool HasAllocator() const { + return allocator_ != 0; + } + + Allocator& GetAllocator() { + RAPIDJSON_ASSERT(allocator_); + return *allocator_; + } + + bool Empty() const { return stackTop_ == stack_; } + size_t GetSize() const { return static_cast(stackTop_ - stack_); } + size_t GetCapacity() const { return static_cast(stackEnd_ - stack_); } + +private: + template + void Expand(size_t count) { + // Only expand the capacity if the current stack exists. Otherwise just create a stack with initial capacity. + size_t newCapacity; + if (stack_ == 0) { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + newCapacity = initialCapacity_; + } else { + newCapacity = GetCapacity(); + newCapacity += (newCapacity + 1) / 2; + } + size_t newSize = GetSize() + sizeof(T) * count; + if (newCapacity < newSize) + newCapacity = newSize; + + Resize(newCapacity); + } + + void Resize(size_t newCapacity) { + const size_t size = GetSize(); // Backup the current size + stack_ = static_cast(allocator_->Realloc(stack_, GetCapacity(), newCapacity)); + stackTop_ = stack_ + size; + stackEnd_ = stack_ + newCapacity; + } + + void Destroy() { + Allocator::Free(stack_); + RAPIDJSON_DELETE(ownAllocator_); // Only delete if it is owned by the stack + } + + // Prohibit copy constructor & assignment operator. + Stack(const Stack&); + Stack& operator=(const Stack&); + + Allocator* allocator_; + Allocator* ownAllocator_; + char *stack_; + char *stackTop_; + char *stackEnd_; + size_t initialCapacity_; +}; + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STACK_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strfunc.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strfunc.h new file mode 100644 index 0000000000..226439a767 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strfunc.h @@ -0,0 +1,69 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_STRFUNC_H_ +#define RAPIDJSON_INTERNAL_STRFUNC_H_ + +#include "../stream.h" +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom strlen() which works on different character types. +/*! \tparam Ch Character type (e.g. char, wchar_t, short) + \param s Null-terminated input string. + \return Number of characters in the string. + \note This has the same semantics as strlen(), the return value is not number of Unicode codepoints. +*/ +template +inline SizeType StrLen(const Ch* s) { + RAPIDJSON_ASSERT(s != 0); + const Ch* p = s; + while (*p) ++p; + return SizeType(p - s); +} + +template <> +inline SizeType StrLen(const char* s) { + return SizeType(std::strlen(s)); +} + +template <> +inline SizeType StrLen(const wchar_t* s) { + return SizeType(std::wcslen(s)); +} + +//! Returns number of code points in a encoded string. +template +bool CountStringCodePoint(const typename Encoding::Ch* s, SizeType length, SizeType* outCount) { + RAPIDJSON_ASSERT(s != 0); + RAPIDJSON_ASSERT(outCount != 0); + GenericStringStream is(s); + const typename Encoding::Ch* end = s + length; + SizeType count = 0; + while (is.src_ < end) { + unsigned codepoint; + if (!Encoding::Decode(is, &codepoint)) + return false; + count++; + } + *outCount = count; + return true; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_INTERNAL_STRFUNC_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strtod.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strtod.h new file mode 100644 index 0000000000..dfca22b65a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/strtod.h @@ -0,0 +1,290 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRTOD_ +#define RAPIDJSON_STRTOD_ + +#include "ieee754.h" +#include "biginteger.h" +#include "diyfp.h" +#include "pow10.h" +#include +#include + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +inline double FastPath(double significand, int exp) { + if (exp < -308) + return 0.0; + else if (exp >= 0) + return significand * internal::Pow10(exp); + else + return significand / internal::Pow10(-exp); +} + +inline double StrtodNormalPrecision(double d, int p) { + if (p < -308) { + // Prevent expSum < -308, making Pow10(p) = 0 + d = FastPath(d, -308); + d = FastPath(d, p + 308); + } + else + d = FastPath(d, p); + return d; +} + +template +inline T Min3(T a, T b, T c) { + T m = a; + if (m > b) m = b; + if (m > c) m = c; + return m; +} + +inline int CheckWithinHalfULP(double b, const BigInteger& d, int dExp) { + const Double db(b); + const uint64_t bInt = db.IntegerSignificand(); + const int bExp = db.IntegerExponent(); + const int hExp = bExp - 1; + + int dS_Exp2 = 0, dS_Exp5 = 0, bS_Exp2 = 0, bS_Exp5 = 0, hS_Exp2 = 0, hS_Exp5 = 0; + + // Adjust for decimal exponent + if (dExp >= 0) { + dS_Exp2 += dExp; + dS_Exp5 += dExp; + } + else { + bS_Exp2 -= dExp; + bS_Exp5 -= dExp; + hS_Exp2 -= dExp; + hS_Exp5 -= dExp; + } + + // Adjust for binary exponent + if (bExp >= 0) + bS_Exp2 += bExp; + else { + dS_Exp2 -= bExp; + hS_Exp2 -= bExp; + } + + // Adjust for half ulp exponent + if (hExp >= 0) + hS_Exp2 += hExp; + else { + dS_Exp2 -= hExp; + bS_Exp2 -= hExp; + } + + // Remove common power of two factor from all three scaled values + int common_Exp2 = Min3(dS_Exp2, bS_Exp2, hS_Exp2); + dS_Exp2 -= common_Exp2; + bS_Exp2 -= common_Exp2; + hS_Exp2 -= common_Exp2; + + BigInteger dS = d; + dS.MultiplyPow5(static_cast(dS_Exp5)) <<= static_cast(dS_Exp2); + + BigInteger bS(bInt); + bS.MultiplyPow5(static_cast(bS_Exp5)) <<= static_cast(bS_Exp2); + + BigInteger hS(1); + hS.MultiplyPow5(static_cast(hS_Exp5)) <<= static_cast(hS_Exp2); + + BigInteger delta(0); + dS.Difference(bS, &delta); + + return delta.Compare(hS); +} + +inline bool StrtodFast(double d, int p, double* result) { + // Use fast path for string-to-double conversion if possible + // see http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + if (p > 22 && p < 22 + 16) { + // Fast Path Cases In Disguise + d *= internal::Pow10(p - 22); + p = 22; + } + + if (p >= -22 && p <= 22 && d <= 9007199254740991.0) { // 2^53 - 1 + *result = FastPath(d, p); + return true; + } + else + return false; +} + +// Compute an approximation and see if it is within 1/2 ULP +inline bool StrtodDiyFp(const char* decimals, int dLen, int dExp, double* result) { + uint64_t significand = 0; + int i = 0; // 2^64 - 1 = 18446744073709551615, 1844674407370955161 = 0x1999999999999999 + for (; i < dLen; i++) { + if (significand > RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || + (significand == RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) && decimals[i] > '5')) + break; + significand = significand * 10u + static_cast(decimals[i] - '0'); + } + + if (i < dLen && decimals[i] >= '5') // Rounding + significand++; + + int remaining = dLen - i; + const int kUlpShift = 3; + const int kUlp = 1 << kUlpShift; + int64_t error = (remaining == 0) ? 0 : kUlp / 2; + + DiyFp v(significand, 0); + v = v.Normalize(); + error <<= -v.e; + + dExp += remaining; + + int actualExp; + DiyFp cachedPower = GetCachedPower10(dExp, &actualExp); + if (actualExp != dExp) { + static const DiyFp kPow10[] = { + DiyFp(RAPIDJSON_UINT64_C2(0xa0000000, 0x00000000), -60), // 10^1 + DiyFp(RAPIDJSON_UINT64_C2(0xc8000000, 0x00000000), -57), // 10^2 + DiyFp(RAPIDJSON_UINT64_C2(0xfa000000, 0x00000000), -54), // 10^3 + DiyFp(RAPIDJSON_UINT64_C2(0x9c400000, 0x00000000), -50), // 10^4 + DiyFp(RAPIDJSON_UINT64_C2(0xc3500000, 0x00000000), -47), // 10^5 + DiyFp(RAPIDJSON_UINT64_C2(0xf4240000, 0x00000000), -44), // 10^6 + DiyFp(RAPIDJSON_UINT64_C2(0x98968000, 0x00000000), -40) // 10^7 + }; + int adjustment = dExp - actualExp; + RAPIDJSON_ASSERT(adjustment >= 1 && adjustment < 8); + v = v * kPow10[adjustment - 1]; + if (dLen + adjustment > 19) // has more digits than decimal digits in 64-bit + error += kUlp / 2; + } + + v = v * cachedPower; + + error += kUlp + (error == 0 ? 0 : 1); + + const int oldExp = v.e; + v = v.Normalize(); + error <<= oldExp - v.e; + + const int effectiveSignificandSize = Double::EffectiveSignificandSize(64 + v.e); + int precisionSize = 64 - effectiveSignificandSize; + if (precisionSize + kUlpShift >= 64) { + int scaleExp = (precisionSize + kUlpShift) - 63; + v.f >>= scaleExp; + v.e += scaleExp; + error = (error >> scaleExp) + 1 + kUlp; + precisionSize -= scaleExp; + } + + DiyFp rounded(v.f >> precisionSize, v.e + precisionSize); + const uint64_t precisionBits = (v.f & ((uint64_t(1) << precisionSize) - 1)) * kUlp; + const uint64_t halfWay = (uint64_t(1) << (precisionSize - 1)) * kUlp; + if (precisionBits >= halfWay + static_cast(error)) { + rounded.f++; + if (rounded.f & (DiyFp::kDpHiddenBit << 1)) { // rounding overflows mantissa (issue #340) + rounded.f >>= 1; + rounded.e++; + } + } + + *result = rounded.ToDouble(); + + return halfWay - static_cast(error) >= precisionBits || precisionBits >= halfWay + static_cast(error); +} + +inline double StrtodBigInteger(double approx, const char* decimals, int dLen, int dExp) { + RAPIDJSON_ASSERT(dLen >= 0); + const BigInteger dInt(decimals, static_cast(dLen)); + Double a(approx); + int cmp = CheckWithinHalfULP(a.Value(), dInt, dExp); + if (cmp < 0) + return a.Value(); // within half ULP + else if (cmp == 0) { + // Round towards even + if (a.Significand() & 1) + return a.NextPositiveDouble(); + else + return a.Value(); + } + else // adjustment + return a.NextPositiveDouble(); +} + +inline double StrtodFullPrecision(double d, int p, const char* decimals, size_t length, size_t decimalPosition, int exp) { + RAPIDJSON_ASSERT(d >= 0.0); + RAPIDJSON_ASSERT(length >= 1); + + double result = 0.0; + if (StrtodFast(d, p, &result)) + return result; + + RAPIDJSON_ASSERT(length <= INT_MAX); + int dLen = static_cast(length); + + RAPIDJSON_ASSERT(length >= decimalPosition); + RAPIDJSON_ASSERT(length - decimalPosition <= INT_MAX); + int dExpAdjust = static_cast(length - decimalPosition); + + RAPIDJSON_ASSERT(exp >= INT_MIN + dExpAdjust); + int dExp = exp - dExpAdjust; + + // Make sure length+dExp does not overflow + RAPIDJSON_ASSERT(dExp <= INT_MAX - dLen); + + // Trim leading zeros + while (dLen > 0 && *decimals == '0') { + dLen--; + decimals++; + } + + // Trim trailing zeros + while (dLen > 0 && decimals[dLen - 1] == '0') { + dLen--; + dExp++; + } + + if (dLen == 0) { // Buffer only contains zeros. + return 0.0; + } + + // Trim right-most digits + const int kMaxDecimalDigit = 767 + 1; + if (dLen > kMaxDecimalDigit) { + dExp += dLen - kMaxDecimalDigit; + dLen = kMaxDecimalDigit; + } + + // If too small, underflow to zero. + // Any x <= 10^-324 is interpreted as zero. + if (dLen + dExp <= -324) + return 0.0; + + // If too large, overflow to infinity. + // Any x >= 10^309 is interpreted as +infinity. + if (dLen + dExp > 309) + return std::numeric_limits::infinity(); + + if (StrtodDiyFp(decimals, dLen, dExp, &result)) + return result; + + // Use approximation from StrtodDiyFp and make adjustment with BigInteger comparison + return StrtodBigInteger(result, decimals, dLen, dExp); +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STRTOD_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/swap.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/swap.h new file mode 100644 index 0000000000..666e49f97b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/internal/swap.h @@ -0,0 +1,46 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_INTERNAL_SWAP_H_ +#define RAPIDJSON_INTERNAL_SWAP_H_ + +#include "../rapidjson.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN +namespace internal { + +//! Custom swap() to avoid dependency on C++ header +/*! \tparam T Type of the arguments to swap, should be instantiated with primitive C++ types only. + \note This has the same semantics as std::swap(). +*/ +template +inline void Swap(T& a, T& b) RAPIDJSON_NOEXCEPT { + T tmp = a; + a = b; + b = tmp; +} + +} // namespace internal +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_INTERNAL_SWAP_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/istreamwrapper.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/istreamwrapper.h new file mode 100644 index 0000000000..c4950b9dcf --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/istreamwrapper.h @@ -0,0 +1,128 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_ISTREAMWRAPPER_H_ +#define RAPIDJSON_ISTREAMWRAPPER_H_ + +#include "stream.h" +#include +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4351) // new behavior: elements of array 'array' will be default initialized +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_istream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::istringstream + - \c std::stringstream + - \c std::wistringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wifstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_istream. +*/ + +template +class BasicIStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + + //! Constructor. + /*! + \param stream stream opened for read. + */ + BasicIStreamWrapper(StreamType &stream) : stream_(stream), buffer_(peekBuffer_), bufferSize_(4), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + Read(); + } + + //! Constructor. + /*! + \param stream stream opened for read. + \param buffer user-supplied buffer. + \param bufferSize size of buffer in bytes. Must >=4 bytes. + */ + BasicIStreamWrapper(StreamType &stream, char* buffer, size_t bufferSize) : stream_(stream), buffer_(buffer), bufferSize_(bufferSize), bufferLast_(0), current_(buffer_), readCount_(0), count_(0), eof_(false) { + RAPIDJSON_ASSERT(bufferSize >= 4); + Read(); + } + + Ch Peek() const { return *current_; } + Ch Take() { Ch c = *current_; Read(); return c; } + size_t Tell() const { return count_ + static_cast(current_ - buffer_); } + + // Not implemented + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return (current_ + 4 - !eof_ <= bufferLast_) ? current_ : 0; + } + +private: + BasicIStreamWrapper(); + BasicIStreamWrapper(const BasicIStreamWrapper&); + BasicIStreamWrapper& operator=(const BasicIStreamWrapper&); + + void Read() { + if (current_ < bufferLast_) + ++current_; + else if (!eof_) { + count_ += readCount_; + readCount_ = bufferSize_; + bufferLast_ = buffer_ + readCount_ - 1; + current_ = buffer_; + + if (!stream_.read(buffer_, static_cast(bufferSize_))) { + readCount_ = static_cast(stream_.gcount()); + *(bufferLast_ = buffer_ + readCount_) = '\0'; + eof_ = true; + } + } + } + + StreamType &stream_; + Ch peekBuffer_[4], *buffer_; + size_t bufferSize_; + Ch *bufferLast_; + Ch *current_; + size_t readCount_; + size_t count_; //!< Number of characters read + bool eof_; +}; + +typedef BasicIStreamWrapper IStreamWrapper; +typedef BasicIStreamWrapper WIStreamWrapper; + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_ISTREAMWRAPPER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorybuffer.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorybuffer.h new file mode 100644 index 0000000000..39bee1dec1 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorybuffer.h @@ -0,0 +1,70 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYBUFFER_H_ +#define RAPIDJSON_MEMORYBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output byte stream. +/*! + This class is mainly for being wrapped by EncodedOutputStream or AutoUTFOutputStream. + + It is similar to FileWriteBuffer but the destination is an in-memory buffer instead of a file. + + Differences between MemoryBuffer and StringBuffer: + 1. StringBuffer has Encoding but MemoryBuffer is only a byte buffer. + 2. StringBuffer::GetString() returns a null-terminated string. MemoryBuffer::GetBuffer() returns a buffer without terminator. + + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +struct GenericMemoryBuffer { + typedef char Ch; // byte + + GenericMemoryBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + + void Put(Ch c) { *stack_.template Push() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { stack_.ShrinkToFit(); } + Ch* Push(size_t count) { return stack_.template Push(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetBuffer() const { + return stack_.template Bottom(); + } + + size_t GetSize() const { return stack_.GetSize(); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; +}; + +typedef GenericMemoryBuffer<> MemoryBuffer; + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(MemoryBuffer& memoryBuffer, char c, size_t n) { + std::memset(memoryBuffer.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorystream.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorystream.h new file mode 100644 index 0000000000..1d71d8a4f0 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/memorystream.h @@ -0,0 +1,71 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_MEMORYSTREAM_H_ +#define RAPIDJSON_MEMORYSTREAM_H_ + +#include "stream.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(missing-noreturn) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory input byte stream. +/*! + This class is mainly for being wrapped by EncodedInputStream or AutoUTFInputStream. + + It is similar to FileReadBuffer but the source is an in-memory buffer instead of a file. + + Differences between MemoryStream and StringStream: + 1. StringStream has encoding but MemoryStream is a byte stream. + 2. MemoryStream needs size of the source buffer and the buffer don't need to be null terminated. StringStream assume null-terminated string as source. + 3. MemoryStream supports Peek4() for encoding detection. StringStream is specified with an encoding so it should not have Peek4(). + \note implements Stream concept +*/ +struct MemoryStream { + typedef char Ch; // byte + + MemoryStream(const Ch *src, size_t size) : src_(src), begin_(src), end_(src + size), size_(size) {} + + Ch Peek() const { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_; } + Ch Take() { return RAPIDJSON_UNLIKELY(src_ == end_) ? '\0' : *src_++; } + size_t Tell() const { return static_cast(src_ - begin_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + // For encoding detection only. + const Ch* Peek4() const { + return Tell() + 4 <= size_ ? src_ : 0; + } + + const Ch* src_; //!< Current read position. + const Ch* begin_; //!< Original head of the string. + const Ch* end_; //!< End of stream. + size_t size_; //!< Size of the stream. +}; + +RAPIDJSON_NAMESPACE_END + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_MEMORYBUFFER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h new file mode 100644 index 0000000000..18111286bf --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/inttypes.h @@ -0,0 +1,316 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include "stdint.h" + +// miloyip: VC supports inttypes.h since VC2013 +#if _MSC_VER >= 1800 +#include +#else + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + +#endif // _MSC_VER >= 1800 + +#endif // _MSC_INTTYPES_H_ ] diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/stdint.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/stdint.h new file mode 100644 index 0000000000..3d4477b9a0 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/msinttypes/stdint.h @@ -0,0 +1,300 @@ +// ISO C9x compliant stdint.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006-2013 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +/////////////////////////////////////////////////////////////////////////////// + +// The above software in this distribution may have been modified by +// THL A29 Limited ("Tencent Modifications"). +// All Tencent Modifications are Copyright (C) 2015 THL A29 Limited. + +#ifndef _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_STDINT_H_ // [ +#define _MSC_STDINT_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +// miloyip: Originally Visual Studio 2010 uses its own stdint.h. However it generates warning with INT64_C(), so change to use this file for vs2010. +#if _MSC_VER >= 1600 // [ +#include + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +#undef INT8_C +#undef INT16_C +#undef INT32_C +#undef INT64_C +#undef UINT8_C +#undef UINT16_C +#undef UINT32_C +#undef UINT64_C + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#else // ] _MSC_VER >= 1700 [ + +#include + +// For Visual Studio 6 in C++ mode and for many Visual Studio versions when +// compiling for ARM we have to wrap include with 'extern "C++" {}' +// or compiler would give many errors like this: +// error C2733: second C linkage of overloaded function 'wmemchr' not allowed +#if defined(__cplusplus) && !defined(_M_ARM) +extern "C" { +#endif +# include +#if defined(__cplusplus) && !defined(_M_ARM) +} +#endif + +// Define _W64 macros to mark types changing their size, like intptr_t. +#ifndef _W64 +# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 +# define _W64 __w64 +# else +# define _W64 +# endif +#endif + + +// 7.18.1 Integer types + +// 7.18.1.1 Exact-width integer types + +// Visual Studio 6 and Embedded Visual C++ 4 doesn't +// realize that, e.g. char has the same size as __int8 +// so we give up on __intX for them. +#if (_MSC_VER < 1300) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; +#else + typedef signed __int8 int8_t; + typedef signed __int16 int16_t; + typedef signed __int32 int32_t; + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +#endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + + +// 7.18.1.2 Minimum-width integer types +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +// 7.18.1.3 Fastest minimum-width integer types +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + +// 7.18.1.4 Integer types capable of holding object pointers +#ifdef _WIN64 // [ + typedef signed __int64 intptr_t; + typedef unsigned __int64 uintptr_t; +#else // _WIN64 ][ + typedef _W64 signed int intptr_t; + typedef _W64 unsigned int uintptr_t; +#endif // _WIN64 ] + +// 7.18.1.5 Greatest-width integer types +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + + +// 7.18.2 Limits of specified-width integer types + +#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 + +// 7.18.2.1 Limits of exact-width integer types +#define INT8_MIN ((int8_t)_I8_MIN) +#define INT8_MAX _I8_MAX +#define INT16_MIN ((int16_t)_I16_MIN) +#define INT16_MAX _I16_MAX +#define INT32_MIN ((int32_t)_I32_MIN) +#define INT32_MAX _I32_MAX +#define INT64_MIN ((int64_t)_I64_MIN) +#define INT64_MAX _I64_MAX +#define UINT8_MAX _UI8_MAX +#define UINT16_MAX _UI16_MAX +#define UINT32_MAX _UI32_MAX +#define UINT64_MAX _UI64_MAX + +// 7.18.2.2 Limits of minimum-width integer types +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MIN INT64_MIN +#define INT_LEAST64_MAX INT64_MAX +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +// 7.18.2.3 Limits of fastest minimum-width integer types +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST8_MAX INT8_MAX +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST16_MAX INT16_MAX +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST32_MAX INT32_MAX +#define INT_FAST64_MIN INT64_MIN +#define INT_FAST64_MAX INT64_MAX +#define UINT_FAST8_MAX UINT8_MAX +#define UINT_FAST16_MAX UINT16_MAX +#define UINT_FAST32_MAX UINT32_MAX +#define UINT_FAST64_MAX UINT64_MAX + +// 7.18.2.4 Limits of integer types capable of holding object pointers +#ifdef _WIN64 // [ +# define INTPTR_MIN INT64_MIN +# define INTPTR_MAX INT64_MAX +# define UINTPTR_MAX UINT64_MAX +#else // _WIN64 ][ +# define INTPTR_MIN INT32_MIN +# define INTPTR_MAX INT32_MAX +# define UINTPTR_MAX UINT32_MAX +#endif // _WIN64 ] + +// 7.18.2.5 Limits of greatest-width integer types +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +// 7.18.3 Limits of other integer types + +#ifdef _WIN64 // [ +# define PTRDIFF_MIN _I64_MIN +# define PTRDIFF_MAX _I64_MAX +#else // _WIN64 ][ +# define PTRDIFF_MIN _I32_MIN +# define PTRDIFF_MAX _I32_MAX +#endif // _WIN64 ] + +#define SIG_ATOMIC_MIN INT_MIN +#define SIG_ATOMIC_MAX INT_MAX + +#ifndef SIZE_MAX // [ +# ifdef _WIN64 // [ +# define SIZE_MAX _UI64_MAX +# else // _WIN64 ][ +# define SIZE_MAX _UI32_MAX +# endif // _WIN64 ] +#endif // SIZE_MAX ] + +// WCHAR_MIN and WCHAR_MAX are also defined in +#ifndef WCHAR_MIN // [ +# define WCHAR_MIN 0 +#endif // WCHAR_MIN ] +#ifndef WCHAR_MAX // [ +# define WCHAR_MAX _UI16_MAX +#endif // WCHAR_MAX ] + +#define WINT_MIN 0 +#define WINT_MAX _UI16_MAX + +#endif // __STDC_LIMIT_MACROS ] + + +// 7.18.4 Limits of other integer types + +#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 + +// 7.18.4.1 Macros for minimum-width integer constants + +#define INT8_C(val) val##i8 +#define INT16_C(val) val##i16 +#define INT32_C(val) val##i32 +#define INT64_C(val) val##i64 + +#define UINT8_C(val) val##ui8 +#define UINT16_C(val) val##ui16 +#define UINT32_C(val) val##ui32 +#define UINT64_C(val) val##ui64 + +// 7.18.4.2 Macros for greatest-width integer constants +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] + +#endif // __STDC_CONSTANT_MACROS ] + +#endif // _MSC_VER >= 1600 ] + +#endif // _MSC_STDINT_H_ ] diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/ostreamwrapper.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/ostreamwrapper.h new file mode 100644 index 0000000000..6f4667c08a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/ostreamwrapper.h @@ -0,0 +1,81 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_OSTREAMWRAPPER_H_ +#define RAPIDJSON_OSTREAMWRAPPER_H_ + +#include "stream.h" +#include + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Wrapper of \c std::basic_ostream into RapidJSON's Stream concept. +/*! + The classes can be wrapped including but not limited to: + + - \c std::ostringstream + - \c std::stringstream + - \c std::wpstringstream + - \c std::wstringstream + - \c std::ifstream + - \c std::fstream + - \c std::wofstream + - \c std::wfstream + + \tparam StreamType Class derived from \c std::basic_ostream. +*/ + +template +class BasicOStreamWrapper { +public: + typedef typename StreamType::char_type Ch; + BasicOStreamWrapper(StreamType& stream) : stream_(stream) {} + + void Put(Ch c) { + stream_.put(c); + } + + void Flush() { + stream_.flush(); + } + + // Not implemented + char Peek() const { RAPIDJSON_ASSERT(false); return 0; } + char Take() { RAPIDJSON_ASSERT(false); return 0; } + size_t Tell() const { RAPIDJSON_ASSERT(false); return 0; } + char* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + size_t PutEnd(char*) { RAPIDJSON_ASSERT(false); return 0; } + +private: + BasicOStreamWrapper(const BasicOStreamWrapper&); + BasicOStreamWrapper& operator=(const BasicOStreamWrapper&); + + StreamType& stream_; +}; + +typedef BasicOStreamWrapper OStreamWrapper; +typedef BasicOStreamWrapper WOStreamWrapper; + +#ifdef __clang__ +RAPIDJSON_DIAG_POP +#endif + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_OSTREAMWRAPPER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/pointer.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/pointer.h new file mode 100644 index 0000000000..b8143b6380 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/pointer.h @@ -0,0 +1,1415 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_POINTER_H_ +#define RAPIDJSON_POINTER_H_ + +#include "document.h" +#include "internal/itoa.h" + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +static const SizeType kPointerInvalidIndex = ~SizeType(0); //!< Represents an invalid index in GenericPointer::Token + +//! Error code of parsing. +/*! \ingroup RAPIDJSON_ERRORS + \see GenericPointer::GenericPointer, GenericPointer::GetParseErrorCode +*/ +enum PointerParseErrorCode { + kPointerParseErrorNone = 0, //!< The parse is successful + + kPointerParseErrorTokenMustBeginWithSolidus, //!< A token must begin with a '/' + kPointerParseErrorInvalidEscape, //!< Invalid escape + kPointerParseErrorInvalidPercentEncoding, //!< Invalid percent encoding in URI fragment + kPointerParseErrorCharacterMustPercentEncode //!< A character must percent encoded in URI fragment +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericPointer + +//! Represents a JSON Pointer. Use Pointer for UTF8 encoding and default allocator. +/*! + This class implements RFC 6901 "JavaScript Object Notation (JSON) Pointer" + (https://tools.ietf.org/html/rfc6901). + + A JSON pointer is for identifying a specific value in a JSON document + (GenericDocument). It can simplify coding of DOM tree manipulation, because it + can access multiple-level depth of DOM tree with single API call. + + After it parses a string representation (e.g. "/foo/0" or URI fragment + representation (e.g. "#/foo/0") into its internal representation (tokens), + it can be used to resolve a specific value in multiple documents, or sub-tree + of documents. + + Contrary to GenericValue, Pointer can be copy constructed and copy assigned. + Apart from assignment, a Pointer cannot be modified after construction. + + Although Pointer is very convenient, please aware that constructing Pointer + involves parsing and dynamic memory allocation. A special constructor with user- + supplied tokens eliminates these. + + GenericPointer depends on GenericDocument and GenericValue. + + \tparam ValueType The value type of the DOM tree. E.g. GenericValue > + \tparam Allocator The allocator type for allocating memory for internal representation. + + \note GenericPointer uses same encoding of ValueType. + However, Allocator of GenericPointer is independent of Allocator of Value. +*/ +template +class GenericPointer { +public: + typedef typename ValueType::EncodingType EncodingType; //!< Encoding type from Value + typedef typename ValueType::Ch Ch; //!< Character type from Value + + //! A token is the basic units of internal representation. + /*! + A JSON pointer string representation "/foo/123" is parsed to two tokens: + "foo" and 123. 123 will be represented in both numeric form and string form. + They are resolved according to the actual value type (object or array). + + For token that are not numbers, or the numeric value is out of bound + (greater than limits of SizeType), they are only treated as string form + (i.e. the token's index will be equal to kPointerInvalidIndex). + + This struct is public so that user can create a Pointer without parsing and + allocation, using a special constructor. + */ + struct Token { + const Ch* name; //!< Name of the token. It has null character at the end but it can contain null character. + SizeType length; //!< Length of the name. + SizeType index; //!< A valid array index, if it is not equal to kPointerInvalidIndex. + }; + + //!@name Constructors and destructor. + //@{ + + //! Default constructor. + GenericPointer(Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A null-terminated, string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + */ + explicit GenericPointer(const Ch* source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, internal::StrLen(source)); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Constructor that parses a string or URI fragment representation. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Requires the definition of the preprocessor symbol \ref RAPIDJSON_HAS_STDSTRING. + */ + explicit GenericPointer(const std::basic_string& source, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source.c_str(), source.size()); + } +#endif + + //! Constructor that parses a string or URI fragment representation, with length of the source string. + /*! + \param source A string or URI fragment representation of JSON pointer. + \param length Length of source. + \param allocator User supplied allocator for this pointer. If no allocator is provided, it creates a self-owned one. + \note Slightly faster than the overload without length. + */ + GenericPointer(const Ch* source, size_t length, Allocator* allocator = 0) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + Parse(source, length); + } + + //! Constructor with user-supplied tokens. + /*! + This constructor let user supplies const array of tokens. + This prevents the parsing process and eliminates allocation. + This is preferred for memory constrained environments. + + \param tokens An constant array of tokens representing the JSON pointer. + \param tokenCount Number of tokens. + + \b Example + \code + #define NAME(s) { s, sizeof(s) / sizeof(s[0]) - 1, kPointerInvalidIndex } + #define INDEX(i) { #i, sizeof(#i) - 1, i } + + static const Pointer::Token kTokens[] = { NAME("foo"), INDEX(123) }; + static const Pointer p(kTokens, sizeof(kTokens) / sizeof(kTokens[0])); + // Equivalent to static const Pointer p("/foo/123"); + + #undef NAME + #undef INDEX + \endcode + */ + GenericPointer(const Token* tokens, size_t tokenCount) : allocator_(), ownAllocator_(), nameBuffer_(), tokens_(const_cast(tokens)), tokenCount_(tokenCount), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) {} + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs) : allocator_(rhs.allocator_), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Copy constructor. + GenericPointer(const GenericPointer& rhs, Allocator* allocator) : allocator_(allocator), ownAllocator_(), nameBuffer_(), tokens_(), tokenCount_(), parseErrorOffset_(), parseErrorCode_(kPointerParseErrorNone) { + *this = rhs; + } + + //! Destructor. + ~GenericPointer() { + if (nameBuffer_) // If user-supplied tokens constructor is used, nameBuffer_ is nullptr and tokens_ are not deallocated. + Allocator::Free(tokens_); + RAPIDJSON_DELETE(ownAllocator_); + } + + //! Assignment operator. + GenericPointer& operator=(const GenericPointer& rhs) { + if (this != &rhs) { + // Do not delete ownAllcator + if (nameBuffer_) + Allocator::Free(tokens_); + + tokenCount_ = rhs.tokenCount_; + parseErrorOffset_ = rhs.parseErrorOffset_; + parseErrorCode_ = rhs.parseErrorCode_; + + if (rhs.nameBuffer_) + CopyFromRaw(rhs); // Normally parsed tokens. + else { + tokens_ = rhs.tokens_; // User supplied const tokens. + nameBuffer_ = 0; + } + } + return *this; + } + + //! Swap the content of this pointer with an other. + /*! + \param other The pointer to swap with. + \note Constant complexity. + */ + GenericPointer& Swap(GenericPointer& other) RAPIDJSON_NOEXCEPT { + internal::Swap(allocator_, other.allocator_); + internal::Swap(ownAllocator_, other.ownAllocator_); + internal::Swap(nameBuffer_, other.nameBuffer_); + internal::Swap(tokens_, other.tokens_); + internal::Swap(tokenCount_, other.tokenCount_); + internal::Swap(parseErrorOffset_, other.parseErrorOffset_); + internal::Swap(parseErrorCode_, other.parseErrorCode_); + return *this; + } + + //! free-standing swap function helper + /*! + Helper function to enable support for common swap implementation pattern based on \c std::swap: + \code + void swap(MyClass& a, MyClass& b) { + using std::swap; + swap(a.pointer, b.pointer); + // ... + } + \endcode + \see Swap() + */ + friend inline void swap(GenericPointer& a, GenericPointer& b) RAPIDJSON_NOEXCEPT { a.Swap(b); } + + //@} + + //!@name Append token + //@{ + + //! Append a token and return a new Pointer + /*! + \param token Token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Token& token, Allocator* allocator = 0) const { + GenericPointer r; + r.allocator_ = allocator; + Ch *p = r.CopyFromRaw(*this, 1, token.length + 1); + std::memcpy(p, token.name, (token.length + 1) * sizeof(Ch)); + r.tokens_[tokenCount_].name = p; + r.tokens_[tokenCount_].length = token.length; + r.tokens_[tokenCount_].index = token.index; + return r; + } + + //! Append a name token with length, and return a new Pointer + /*! + \param name Name to be appended. + \param length Length of name. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const Ch* name, SizeType length, Allocator* allocator = 0) const { + Token token = { name, length, kPointerInvalidIndex }; + return Append(token, allocator); + } + + //! Append a name token without length, and return a new Pointer + /*! + \param name Name (const Ch*) to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr::Type, Ch> >), (GenericPointer)) + Append(T* name, Allocator* allocator = 0) const { + return Append(name, internal::StrLen(name), allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Append a name token, and return a new Pointer + /*! + \param name Name to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const std::basic_string& name, Allocator* allocator = 0) const { + return Append(name.c_str(), static_cast(name.size()), allocator); + } +#endif + + //! Append a index token, and return a new Pointer + /*! + \param index Index to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(SizeType index, Allocator* allocator = 0) const { + char buffer[21]; + char* end = sizeof(SizeType) == 4 ? internal::u32toa(index, buffer) : internal::u64toa(index, buffer); + SizeType length = static_cast(end - buffer); + buffer[length] = '\0'; + + if (sizeof(Ch) == 1) { + Token token = { reinterpret_cast(buffer), length, index }; + return Append(token, allocator); + } + else { + Ch name[21]; + for (size_t i = 0; i <= length; i++) + name[i] = static_cast(buffer[i]); + Token token = { name, length, index }; + return Append(token, allocator); + } + } + + //! Append a token by value, and return a new Pointer + /*! + \param token token to be appended. + \param allocator Allocator for the newly return Pointer. + \return A new Pointer with appended token. + */ + GenericPointer Append(const ValueType& token, Allocator* allocator = 0) const { + if (token.IsString()) + return Append(token.GetString(), token.GetStringLength(), allocator); + else { + RAPIDJSON_ASSERT(token.IsUint64()); + RAPIDJSON_ASSERT(token.GetUint64() <= SizeType(~0)); + return Append(static_cast(token.GetUint64()), allocator); + } + } + + //!@name Handling Parse Error + //@{ + + //! Check whether this is a valid pointer. + bool IsValid() const { return parseErrorCode_ == kPointerParseErrorNone; } + + //! Get the parsing error offset in code unit. + size_t GetParseErrorOffset() const { return parseErrorOffset_; } + + //! Get the parsing error code. + PointerParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + + //@} + + //! Get the allocator of this pointer. + Allocator& GetAllocator() { return *allocator_; } + + //!@name Tokens + //@{ + + //! Get the token array (const version only). + const Token* GetTokens() const { return tokens_; } + + //! Get the number of tokens. + size_t GetTokenCount() const { return tokenCount_; } + + //@} + + //!@name Equality/inequality operators + //@{ + + //! Equality operator. + /*! + \note When any pointers are invalid, always returns false. + */ + bool operator==(const GenericPointer& rhs) const { + if (!IsValid() || !rhs.IsValid() || tokenCount_ != rhs.tokenCount_) + return false; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index || + tokens_[i].length != rhs.tokens_[i].length || + (tokens_[i].length != 0 && std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch)* tokens_[i].length) != 0)) + { + return false; + } + } + + return true; + } + + //! Inequality operator. + /*! + \note When any pointers are invalid, always returns true. + */ + bool operator!=(const GenericPointer& rhs) const { return !(*this == rhs); } + + //! Less than operator. + /*! + \note Invalid pointers are always greater than valid ones. + */ + bool operator<(const GenericPointer& rhs) const { + if (!IsValid()) + return false; + if (!rhs.IsValid()) + return true; + + if (tokenCount_ != rhs.tokenCount_) + return tokenCount_ < rhs.tokenCount_; + + for (size_t i = 0; i < tokenCount_; i++) { + if (tokens_[i].index != rhs.tokens_[i].index) + return tokens_[i].index < rhs.tokens_[i].index; + + if (tokens_[i].length != rhs.tokens_[i].length) + return tokens_[i].length < rhs.tokens_[i].length; + + if (int cmp = std::memcmp(tokens_[i].name, rhs.tokens_[i].name, sizeof(Ch) * tokens_[i].length)) + return cmp < 0; + } + + return false; + } + + //@} + + //!@name Stringify + //@{ + + //! Stringify the pointer into string representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + return Stringify(os); + } + + //! Stringify the pointer into URI fragment representation. + /*! + \tparam OutputStream Type of output stream. + \param os The output stream. + */ + template + bool StringifyUriFragment(OutputStream& os) const { + return Stringify(os); + } + + //@} + + //!@name Create value + //@{ + + //! Create a value in a subtree. + /*! + If the value is not exist, it creates all parent values and a JSON Null value. + So it always succeed and return the newly created or existing value. + + Remind that it may change types of parents according to tokens, so it + potentially removes previously stored values. For example, if a document + was an array, and "/foo" is used to create a value, then the document + will be changed to an object, and all existing array elements are lost. + + \param root Root value of a DOM subtree to be resolved. It can be any value other than document root. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created (a JSON Null value), or already exists value. + */ + ValueType& Create(ValueType& root, typename ValueType::AllocatorType& allocator, bool* alreadyExist = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + bool exist = true; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + if (v->IsArray() && t->name[0] == '-' && t->length == 1) { + v->PushBack(ValueType().Move(), allocator); + v = &((*v)[v->Size() - 1]); + exist = false; + } + else { + if (t->index == kPointerInvalidIndex) { // must be object name + if (!v->IsObject()) + v->SetObject(); // Change to Object + } + else { // object name or array index + if (!v->IsArray() && !v->IsObject()) + v->SetArray(); // Change to Array + } + + if (v->IsArray()) { + if (t->index >= v->Size()) { + v->Reserve(t->index + 1, allocator); + while (t->index >= v->Size()) + v->PushBack(ValueType().Move(), allocator); + exist = false; + } + v = &((*v)[t->index]); + } + else { + typename ValueType::MemberIterator m = v->FindMember(GenericValue(GenericStringRef(t->name, t->length))); + if (m == v->MemberEnd()) { + v->AddMember(ValueType(t->name, t->length, allocator).Move(), ValueType().Move(), allocator); + m = v->MemberEnd(); + v = &(--m)->value; // Assumes AddMember() appends at the end + exist = false; + } + else + v = &m->value; + } + } + } + + if (alreadyExist) + *alreadyExist = exist; + + return *v; + } + + //! Creates a value in a document. + /*! + \param document A document to be resolved. + \param alreadyExist If non-null, it stores whether the resolved value is already exist. + \return The resolved newly created, or already exists value. + */ + template + ValueType& Create(GenericDocument& document, bool* alreadyExist = 0) const { + return Create(document, document.GetAllocator(), alreadyExist); + } + + //@} + + //!@name Query value + //@{ + + //! Query a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param unresolvedTokenIndex If the pointer cannot resolve a token in the pointer, this parameter can obtain the index of unresolved token. + \return Pointer to the value if it can be resolved. Otherwise null. + + \note + There are only 3 situations when a value cannot be resolved: + 1. A value in the path is not an array nor object. + 2. An object value does not contain the token. + 3. A token is out of range of an array value. + + Use unresolvedTokenIndex to retrieve the token index. + */ + ValueType* Get(ValueType& root, size_t* unresolvedTokenIndex = 0) const { + RAPIDJSON_ASSERT(IsValid()); + ValueType* v = &root; + for (const Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericValue(GenericStringRef(t->name, t->length))); + if (m == v->MemberEnd()) + break; + v = &m->value; + } + continue; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + break; + v = &((*v)[t->index]); + continue; + default: + break; + } + + // Error: unresolved token + if (unresolvedTokenIndex) + *unresolvedTokenIndex = static_cast(t - tokens_); + return 0; + } + return v; + } + + //! Query a const value in a const subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Pointer to the value if it can be resolved. Otherwise null. + */ + const ValueType* Get(const ValueType& root, size_t* unresolvedTokenIndex = 0) const { + return Get(const_cast(root), unresolvedTokenIndex); + } + + //@} + + //!@name Query a value with default + //@{ + + //! Query a value in a subtree with default value. + /*! + Similar to Get(), but if the specified value do not exists, it creates all parents and clone the default value. + So that this function always succeed. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param defaultValue Default value to be cloned if the value was not exists. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& GetWithDefault(ValueType& root, const ValueType& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.CopyFrom(defaultValue, allocator); + } + + //! Query a value in a subtree with default null-terminated string. + ValueType& GetWithDefault(ValueType& root, const Ch* defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a subtree with default std::basic_string. + ValueType& GetWithDefault(ValueType& root, const std::basic_string& defaultValue, typename ValueType::AllocatorType& allocator) const { + bool alreadyExist; + ValueType& v = Create(root, allocator, &alreadyExist); + return alreadyExist ? v : v.SetString(defaultValue, allocator); + } +#endif + + //! Query a value in a subtree with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(ValueType& root, T defaultValue, typename ValueType::AllocatorType& allocator) const { + return GetWithDefault(root, ValueType(defaultValue).Move(), allocator); + } + + //! Query a value in a document with default value. + template + ValueType& GetWithDefault(GenericDocument& document, const ValueType& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //! Query a value in a document with default null-terminated string. + template + ValueType& GetWithDefault(GenericDocument& document, const Ch* defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Query a value in a document with default std::basic_string. + template + ValueType& GetWithDefault(GenericDocument& document, const std::basic_string& defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } +#endif + + //! Query a value in a document with default primitive value. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + GetWithDefault(GenericDocument& document, T defaultValue) const { + return GetWithDefault(document, defaultValue, document.GetAllocator()); + } + + //@} + + //!@name Set a value + //@{ + + //! Set a value in a subtree, with move semantics. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be set. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Set(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = value; + } + + //! Set a value in a subtree, with copy semantics. + ValueType& Set(ValueType& root, const ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).CopyFrom(value, allocator); + } + + //! Set a null-terminated string in a subtree. + ValueType& Set(ValueType& root, const Ch* value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Set a std::basic_string in a subtree. + ValueType& Set(ValueType& root, const std::basic_string& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value, allocator).Move(); + } +#endif + + //! Set a primitive value in a subtree. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(ValueType& root, T value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator) = ValueType(value).Move(); + } + + //! Set a value in a document, with move semantics. + template + ValueType& Set(GenericDocument& document, ValueType& value) const { + return Create(document) = value; + } + + //! Set a value in a document, with copy semantics. + template + ValueType& Set(GenericDocument& document, const ValueType& value) const { + return Create(document).CopyFrom(value, document.GetAllocator()); + } + + //! Set a null-terminated string in a document. + template + ValueType& Set(GenericDocument& document, const Ch* value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } + +#if RAPIDJSON_HAS_STDSTRING + //! Sets a std::basic_string in a document. + template + ValueType& Set(GenericDocument& document, const std::basic_string& value) const { + return Create(document) = ValueType(value, document.GetAllocator()).Move(); + } +#endif + + //! Set a primitive value in a document. + /*! + \tparam T Either \ref Type, \c int, \c unsigned, \c int64_t, \c uint64_t, \c bool + */ + template + RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (ValueType&)) + Set(GenericDocument& document, T value) const { + return Create(document) = value; + } + + //@} + + //!@name Swap a value + //@{ + + //! Swap a value with a value in a subtree. + /*! + It creates all parents if they are not exist or types are different to the tokens. + So this function always succeeds but potentially remove existing values. + + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \param value Value to be swapped. + \param allocator Allocator for creating the values if the specified value or its parents are not exist. + \see Create() + */ + ValueType& Swap(ValueType& root, ValueType& value, typename ValueType::AllocatorType& allocator) const { + return Create(root, allocator).Swap(value); + } + + //! Swap a value with a value in a document. + template + ValueType& Swap(GenericDocument& document, ValueType& value) const { + return Create(document).Swap(value); + } + + //@} + + //! Erase a value in a subtree. + /*! + \param root Root value of a DOM sub-tree to be resolved. It can be any value other than document root. + \return Whether the resolved value is found and erased. + + \note Erasing with an empty pointer \c Pointer(""), i.e. the root, always fail and return false. + */ + bool Erase(ValueType& root) const { + RAPIDJSON_ASSERT(IsValid()); + if (tokenCount_ == 0) // Cannot erase the root + return false; + + ValueType* v = &root; + const Token* last = tokens_ + (tokenCount_ - 1); + for (const Token *t = tokens_; t != last; ++t) { + switch (v->GetType()) { + case kObjectType: + { + typename ValueType::MemberIterator m = v->FindMember(GenericValue(GenericStringRef(t->name, t->length))); + if (m == v->MemberEnd()) + return false; + v = &m->value; + } + break; + case kArrayType: + if (t->index == kPointerInvalidIndex || t->index >= v->Size()) + return false; + v = &((*v)[t->index]); + break; + default: + return false; + } + } + + switch (v->GetType()) { + case kObjectType: + return v->EraseMember(GenericStringRef(last->name, last->length)); + case kArrayType: + if (last->index == kPointerInvalidIndex || last->index >= v->Size()) + return false; + v->Erase(v->Begin() + last->index); + return true; + default: + return false; + } + } + +private: + //! Clone the content from rhs to this. + /*! + \param rhs Source pointer. + \param extraToken Extra tokens to be allocated. + \param extraNameBufferSize Extra name buffer size (in number of Ch) to be allocated. + \return Start of non-occupied name buffer, for storing extra names. + */ + Ch* CopyFromRaw(const GenericPointer& rhs, size_t extraToken = 0, size_t extraNameBufferSize = 0) { + if (!allocator_) // allocator is independently owned. + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + size_t nameBufferSize = rhs.tokenCount_; // null terminators for tokens + for (Token *t = rhs.tokens_; t != rhs.tokens_ + rhs.tokenCount_; ++t) + nameBufferSize += t->length; + + tokenCount_ = rhs.tokenCount_ + extraToken; + tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch))); + nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + if (rhs.tokenCount_ > 0) { + std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token)); + } + if (nameBufferSize > 0) { + std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch)); + } + + // Adjust pointers to name buffer + std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_; + for (Token *t = tokens_; t != tokens_ + rhs.tokenCount_; ++t) + t->name += diff; + + return nameBuffer_ + nameBufferSize; + } + + //! Check whether a character should be percent-encoded. + /*! + According to RFC 3986 2.3 Unreserved Characters. + \param c The character (code unit) to be tested. + */ + bool NeedPercentEncode(Ch c) const { + return !((c >= '0' && c <= '9') || (c >= 'A' && c <='Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '.' || c == '_' || c =='~'); + } + + //! Parse a JSON String or its URI fragment representation into tokens. +#ifndef __clang__ // -Wdocumentation + /*! + \param source Either a JSON Pointer string, or its URI fragment representation. Not need to be null terminated. + \param length Length of the source string. + \note Source cannot be JSON String Representation of JSON Pointer, e.g. In "/\u0000", \u0000 will not be unescaped. + */ +#endif + void Parse(const Ch* source, size_t length) { + RAPIDJSON_ASSERT(source != NULL); + RAPIDJSON_ASSERT(nameBuffer_ == 0); + RAPIDJSON_ASSERT(tokens_ == 0); + + // Create own allocator if user did not supply. + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + // Count number of '/' as tokenCount + tokenCount_ = 0; + for (const Ch* s = source; s != source + length; s++) + if (*s == '/') + tokenCount_++; + + Token* token = tokens_ = static_cast(allocator_->Malloc(tokenCount_ * sizeof(Token) + length * sizeof(Ch))); + Ch* name = nameBuffer_ = reinterpret_cast(tokens_ + tokenCount_); + size_t i = 0; + + // Detect if it is a URI fragment + bool uriFragment = false; + if (source[i] == '#') { + uriFragment = true; + i++; + } + + if (i != length && source[i] != '/') { + parseErrorCode_ = kPointerParseErrorTokenMustBeginWithSolidus; + goto error; + } + + while (i < length) { + RAPIDJSON_ASSERT(source[i] == '/'); + i++; // consumes '/' + + token->name = name; + bool isNumber = true; + + while (i < length && source[i] != '/') { + Ch c = source[i]; + if (uriFragment) { + // Decoding percent-encoding for URI fragment + if (c == '%') { + PercentDecodeStream is(&source[i], source + length); + GenericInsituStringStream os(name); + Ch* begin = os.PutBegin(); + if (!Transcoder, EncodingType>().Validate(is, os) || !is.IsValid()) { + parseErrorCode_ = kPointerParseErrorInvalidPercentEncoding; + goto error; + } + size_t len = os.PutEnd(begin); + i += is.Tell() - 1; + if (len == 1) + c = *name; + else { + name += len; + isNumber = false; + i++; + continue; + } + } + else if (NeedPercentEncode(c)) { + parseErrorCode_ = kPointerParseErrorCharacterMustPercentEncode; + goto error; + } + } + + i++; + + // Escaping "~0" -> '~', "~1" -> '/' + if (c == '~') { + if (i < length) { + c = source[i]; + if (c == '0') c = '~'; + else if (c == '1') c = '/'; + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + i++; + } + else { + parseErrorCode_ = kPointerParseErrorInvalidEscape; + goto error; + } + } + + // First check for index: all of characters are digit + if (c < '0' || c > '9') + isNumber = false; + + *name++ = c; + } + token->length = static_cast(name - token->name); + if (token->length == 0) + isNumber = false; + *name++ = '\0'; // Null terminator + + // Second check for index: more than one digit cannot have leading zero + if (isNumber && token->length > 1 && token->name[0] == '0') + isNumber = false; + + // String to SizeType conversion + SizeType n = 0; + if (isNumber) { + for (size_t j = 0; j < token->length; j++) { + SizeType m = n * 10 + static_cast(token->name[j] - '0'); + if (m < n) { // overflow detection + isNumber = false; + break; + } + n = m; + } + } + + token->index = isNumber ? n : kPointerInvalidIndex; + token++; + } + + RAPIDJSON_ASSERT(name <= nameBuffer_ + length); // Should not overflow buffer + parseErrorCode_ = kPointerParseErrorNone; + return; + + error: + Allocator::Free(tokens_); + nameBuffer_ = 0; + tokens_ = 0; + tokenCount_ = 0; + parseErrorOffset_ = i; + return; + } + + //! Stringify to string or URI fragment representation. + /*! + \tparam uriFragment True for stringifying to URI fragment representation. False for string representation. + \tparam OutputStream type of output stream. + \param os The output stream. + */ + template + bool Stringify(OutputStream& os) const { + RAPIDJSON_ASSERT(IsValid()); + + if (uriFragment) + os.Put('#'); + + for (Token *t = tokens_; t != tokens_ + tokenCount_; ++t) { + os.Put('/'); + for (size_t j = 0; j < t->length; j++) { + Ch c = t->name[j]; + if (c == '~') { + os.Put('~'); + os.Put('0'); + } + else if (c == '/') { + os.Put('~'); + os.Put('1'); + } + else if (uriFragment && NeedPercentEncode(c)) { + // Transcode to UTF8 sequence + GenericStringStream source(&t->name[j]); + PercentEncodeStream target(os); + if (!Transcoder >().Validate(source, target)) + return false; + j += source.Tell() - 1; + } + else + os.Put(c); + } + } + return true; + } + + //! A helper stream for decoding a percent-encoded sequence into code unit. + /*! + This stream decodes %XY triplet into code unit (0-255). + If it encounters invalid characters, it sets output code unit as 0 and + mark invalid, and to be checked by IsValid(). + */ + class PercentDecodeStream { + public: + typedef typename ValueType::Ch Ch; + + //! Constructor + /*! + \param source Start of the stream + \param end Past-the-end of the stream. + */ + PercentDecodeStream(const Ch* source, const Ch* end) : src_(source), head_(source), end_(end), valid_(true) {} + + Ch Take() { + if (*src_ != '%' || src_ + 3 > end_) { // %XY triplet + valid_ = false; + return 0; + } + src_++; + Ch c = 0; + for (int j = 0; j < 2; j++) { + c = static_cast(c << 4); + Ch h = *src_; + if (h >= '0' && h <= '9') c = static_cast(c + h - '0'); + else if (h >= 'A' && h <= 'F') c = static_cast(c + h - 'A' + 10); + else if (h >= 'a' && h <= 'f') c = static_cast(c + h - 'a' + 10); + else { + valid_ = false; + return 0; + } + src_++; + } + return c; + } + + size_t Tell() const { return static_cast(src_ - head_); } + bool IsValid() const { return valid_; } + + private: + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. + const Ch* end_; //!< Past-the-end position. + bool valid_; //!< Whether the parsing is valid. + }; + + //! A helper stream to encode character (UTF-8 code unit) into percent-encoded sequence. + template + class PercentEncodeStream { + public: + PercentEncodeStream(OutputStream& os) : os_(os) {} + void Put(char c) { // UTF-8 must be byte + unsigned char u = static_cast(c); + static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + os_.Put('%'); + os_.Put(static_cast(hexDigits[u >> 4])); + os_.Put(static_cast(hexDigits[u & 15])); + } + private: + OutputStream& os_; + }; + + Allocator* allocator_; //!< The current allocator. It is either user-supplied or equal to ownAllocator_. + Allocator* ownAllocator_; //!< Allocator owned by this Pointer. + Ch* nameBuffer_; //!< A buffer containing all names in tokens. + Token* tokens_; //!< A list of tokens. + size_t tokenCount_; //!< Number of tokens in tokens_. + size_t parseErrorOffset_; //!< Offset in code unit when parsing fail. + PointerParseErrorCode parseErrorCode_; //!< Parsing error code. +}; + +//! GenericPointer for Value (UTF-8, default allocator). +typedef GenericPointer Pointer; + +//!@name Helper functions for GenericPointer +//@{ + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& CreateValueByPointer(T& root, const GenericPointer& pointer, typename T::AllocatorType& a) { + return pointer.Create(root, a); +} + +template +typename T::ValueType& CreateValueByPointer(T& root, const CharType(&source)[N], typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Create(root, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const GenericPointer& pointer) { + return pointer.Create(document); +} + +template +typename DocumentType::ValueType& CreateValueByPointer(DocumentType& document, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Create(document); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType* GetValueByPointer(T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const GenericPointer& pointer, size_t* unresolvedTokenIndex = 0) { + return pointer.Get(root, unresolvedTokenIndex); +} + +template +typename T::ValueType* GetValueByPointer(T& root, const CharType (&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +template +const typename T::ValueType* GetValueByPointer(const T& root, const CharType(&source)[N], size_t* unresolvedTokenIndex = 0) { + return GenericPointer(source, N - 1).Get(root, unresolvedTokenIndex); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const GenericPointer& pointer, T2 defaultValue, typename T::AllocatorType& a) { + return pointer.GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::ValueType& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const typename T::Ch* defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& GetValueByPointerWithDefault(T& root, const CharType(&source)[N], const std::basic_string& defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +GetValueByPointerWithDefault(T& root, const CharType(&source)[N], T2 defaultValue, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).GetWithDefault(root, defaultValue, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, const std::basic_string& defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const GenericPointer& pointer, T2 defaultValue) { + return pointer.GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], const std::basic_string& defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +GetValueByPointerWithDefault(DocumentType& document, const CharType(&source)[N], T2 defaultValue) { + return GenericPointer(source, N - 1).GetWithDefault(document, defaultValue); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const typename T::Ch* value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const GenericPointer& pointer, const std::basic_string& value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const GenericPointer& pointer, T2 value, typename T::AllocatorType& a) { + return pointer.Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const typename T::Ch* value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename T::ValueType& SetValueByPointer(T& root, const CharType(&source)[N], const std::basic_string& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename T::ValueType&)) +SetValueByPointer(T& root, const CharType(&source)[N], T2 value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Set(root, value, a); +} + +// No allocator parameter + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::ValueType& value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const typename DocumentType::Ch* value) { + return pointer.Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const GenericPointer& pointer, const std::basic_string& value) { + return pointer.Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const GenericPointer& pointer, T2 value) { + return pointer.Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const typename DocumentType::Ch* value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +#if RAPIDJSON_HAS_STDSTRING +template +typename DocumentType::ValueType& SetValueByPointer(DocumentType& document, const CharType(&source)[N], const std::basic_string& value) { + return GenericPointer(source, N - 1).Set(document, value); +} +#endif + +template +RAPIDJSON_DISABLEIF_RETURN((internal::OrExpr, internal::IsGenericValue >), (typename DocumentType::ValueType&)) +SetValueByPointer(DocumentType& document, const CharType(&source)[N], T2 value) { + return GenericPointer(source, N - 1).Set(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +typename T::ValueType& SwapValueByPointer(T& root, const GenericPointer& pointer, typename T::ValueType& value, typename T::AllocatorType& a) { + return pointer.Swap(root, value, a); +} + +template +typename T::ValueType& SwapValueByPointer(T& root, const CharType(&source)[N], typename T::ValueType& value, typename T::AllocatorType& a) { + return GenericPointer(source, N - 1).Swap(root, value, a); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const GenericPointer& pointer, typename DocumentType::ValueType& value) { + return pointer.Swap(document, value); +} + +template +typename DocumentType::ValueType& SwapValueByPointer(DocumentType& document, const CharType(&source)[N], typename DocumentType::ValueType& value) { + return GenericPointer(source, N - 1).Swap(document, value); +} + +////////////////////////////////////////////////////////////////////////////// + +template +bool EraseValueByPointer(T& root, const GenericPointer& pointer) { + return pointer.Erase(root); +} + +template +bool EraseValueByPointer(T& root, const CharType(&source)[N]) { + return GenericPointer(source, N - 1).Erase(root); +} + +//@} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_POINTER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/prettywriter.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/prettywriter.h new file mode 100644 index 0000000000..94eeb69db3 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/prettywriter.h @@ -0,0 +1,277 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_PRETTYWRITER_H_ +#define RAPIDJSON_PRETTYWRITER_H_ + +#include "writer.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Combination of PrettyWriter format flags. +/*! \see PrettyWriter::SetFormatOptions + */ +enum PrettyFormatOptions { + kFormatDefault = 0, //!< Default pretty formatting. + kFormatSingleLineArray = 1 //!< Format arrays on a single line. +}; + +//! Writer with indentation and spacing. +/*! + \tparam OutputStream Type of output os. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class PrettyWriter : public Writer { +public: + typedef Writer Base; + typedef typename Base::Ch Ch; + + //! Constructor + /*! \param os Output stream. + \param allocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit PrettyWriter(OutputStream& os, StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + + + explicit PrettyWriter(StackAllocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : + Base(allocator, levelDepth), indentChar_(' '), indentCharCount_(4), formatOptions_(kFormatDefault) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + PrettyWriter(PrettyWriter&& rhs) : + Base(std::forward(rhs)), indentChar_(rhs.indentChar_), indentCharCount_(rhs.indentCharCount_), formatOptions_(rhs.formatOptions_) {} +#endif + + //! Set custom indentation. + /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). + \param indentCharCount Number of indent characters for each indentation level. + \note The default indentation is 4 spaces. + */ + PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount) { + RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r'); + indentChar_ = indentChar; + indentCharCount_ = indentCharCount; + return *this; + } + + //! Set pretty writer formatting options. + /*! \param options Formatting options. + */ + PrettyWriter& SetFormatOptions(PrettyFormatOptions options) { + formatOptions_ = options; + return *this; + } + + /*! @name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { PrettyPrefix(kNullType); return Base::EndValue(Base::WriteNull()); } + bool Bool(bool b) { PrettyPrefix(b ? kTrueType : kFalseType); return Base::EndValue(Base::WriteBool(b)); } + bool Int(int i) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt(i)); } + bool Uint(unsigned u) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint(u)); } + bool Int64(int64_t i64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteInt64(i64)); } + bool Uint64(uint64_t u64) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteUint64(u64)); } + bool Double(double d) { PrettyPrefix(kNumberType); return Base::EndValue(Base::WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kNumberType); + return Base::EndValue(Base::WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + PrettyPrefix(kStringType); + return Base::EndValue(Base::WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + PrettyPrefix(kObjectType); + new (Base::level_stack_.template Push()) typename Base::Level(false); + return Base::WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); // not inside an Object + RAPIDJSON_ASSERT(!Base::level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == Base::level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndObject()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + bool StartArray() { + PrettyPrefix(kArrayType); + new (Base::level_stack_.template Push()) typename Base::Level(true); + return Base::WriteStartArray(); + } + + bool EndArray(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level)); + RAPIDJSON_ASSERT(Base::level_stack_.template Top()->inArray); + bool empty = Base::level_stack_.template Pop(1)->valueCount == 0; + + if (!empty && !(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + bool ret = Base::EndValue(Base::WriteEndArray()); + (void)ret; + RAPIDJSON_ASSERT(ret == true); + if (Base::level_stack_.Empty()) // end of json text + Base::Flush(); + return true; + } + + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + PrettyPrefix(type); + return Base::EndValue(Base::WriteRawValue(json, length)); + } + +protected: + void PrettyPrefix(Type type) { + (void)type; + if (Base::level_stack_.GetSize() != 0) { // this value is not at root + typename Base::Level* level = Base::level_stack_.template Top(); + + if (level->inArray) { + if (level->valueCount > 0) { + Base::os_->Put(','); // add comma if it is not the first element in array + if (formatOptions_ & kFormatSingleLineArray) + Base::os_->Put(' '); + } + + if (!(formatOptions_ & kFormatSingleLineArray)) { + Base::os_->Put('\n'); + WriteIndent(); + } + } + else { // in object + if (level->valueCount > 0) { + if (level->valueCount % 2 == 0) { + Base::os_->Put(','); + Base::os_->Put('\n'); + } + else { + Base::os_->Put(':'); + Base::os_->Put(' '); + } + } + else + Base::os_->Put('\n'); + + if (level->valueCount % 2 == 0) + WriteIndent(); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!Base::hasRoot_); // Should only has one and only one root. + Base::hasRoot_ = true; + } + } + + void WriteIndent() { + size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_; + PutN(*Base::os_, static_cast(indentChar_), count); + } + + Ch indentChar_; + unsigned indentCharCount_; + PrettyFormatOptions formatOptions_; + +private: + // Prohibit copy constructor & assignment operator. + PrettyWriter(const PrettyWriter&); + PrettyWriter& operator=(const PrettyWriter&); +}; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/rapidjson.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/rapidjson.h new file mode 100644 index 0000000000..c5f4d65f76 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/rapidjson.h @@ -0,0 +1,692 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_RAPIDJSON_H_ +#define RAPIDJSON_RAPIDJSON_H_ + +/*!\file rapidjson.h + \brief common definitions and configuration + + \see RAPIDJSON_CONFIG + */ + +/*! \defgroup RAPIDJSON_CONFIG RapidJSON configuration + \brief Configuration macros for library features + + Some RapidJSON features are configurable to adapt the library to a wide + variety of platforms, environments and usage scenarios. Most of the + features can be configured in terms of overridden or predefined + preprocessor macros at compile-time. + + Some additional customization is available in the \ref RAPIDJSON_ERRORS APIs. + + \note These macros should be given on the compiler command-line + (where applicable) to avoid inconsistent values when compiling + different translation units of a single application. + */ + +#include // malloc(), realloc(), free(), size_t +#include // memset(), memcpy(), memmove(), memcmp() + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_VERSION_STRING +// +// ALWAYS synchronize the following 3 macros with corresponding variables in /CMakeLists.txt. +// + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +// token stringification +#define RAPIDJSON_STRINGIFY(x) RAPIDJSON_DO_STRINGIFY(x) +#define RAPIDJSON_DO_STRINGIFY(x) #x + +// token concatenation +#define RAPIDJSON_JOIN(X, Y) RAPIDJSON_DO_JOIN(X, Y) +#define RAPIDJSON_DO_JOIN(X, Y) RAPIDJSON_DO_JOIN2(X, Y) +#define RAPIDJSON_DO_JOIN2(X, Y) X##Y +//!@endcond + +/*! \def RAPIDJSON_MAJOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Major version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_MINOR_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Minor version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_PATCH_VERSION + \ingroup RAPIDJSON_CONFIG + \brief Patch version of RapidJSON in integer. +*/ +/*! \def RAPIDJSON_VERSION_STRING + \ingroup RAPIDJSON_CONFIG + \brief Version of RapidJSON in ".." string format. +*/ +#define RAPIDJSON_MAJOR_VERSION 1 +#define RAPIDJSON_MINOR_VERSION 1 +#define RAPIDJSON_PATCH_VERSION 0 +#define RAPIDJSON_VERSION_STRING \ + RAPIDJSON_STRINGIFY(RAPIDJSON_MAJOR_VERSION.RAPIDJSON_MINOR_VERSION.RAPIDJSON_PATCH_VERSION) + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NAMESPACE_(BEGIN|END) +/*! \def RAPIDJSON_NAMESPACE + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace + + In order to avoid symbol clashes and/or "One Definition Rule" errors + between multiple inclusions of (different versions of) RapidJSON in + a single binary, users can customize the name of the main RapidJSON + namespace. + + In case of a single nesting level, defining \c RAPIDJSON_NAMESPACE + to a custom name (e.g. \c MyRapidJSON) is sufficient. If multiple + levels are needed, both \ref RAPIDJSON_NAMESPACE_BEGIN and \ref + RAPIDJSON_NAMESPACE_END need to be defined as well: + + \code + // in some .cpp file + #define RAPIDJSON_NAMESPACE my::rapidjson + #define RAPIDJSON_NAMESPACE_BEGIN namespace my { namespace rapidjson { + #define RAPIDJSON_NAMESPACE_END } } + #include "rapidjson/..." + \endcode + + \see rapidjson + */ +/*! \def RAPIDJSON_NAMESPACE_BEGIN + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (opening expression) + \see RAPIDJSON_NAMESPACE +*/ +/*! \def RAPIDJSON_NAMESPACE_END + \ingroup RAPIDJSON_CONFIG + \brief provide custom rapidjson namespace (closing expression) + \see RAPIDJSON_NAMESPACE +*/ +#ifndef RAPIDJSON_NAMESPACE +#define RAPIDJSON_NAMESPACE rapidjson +#endif +#ifndef RAPIDJSON_NAMESPACE_BEGIN +#define RAPIDJSON_NAMESPACE_BEGIN namespace RAPIDJSON_NAMESPACE { +#endif +#ifndef RAPIDJSON_NAMESPACE_END +#define RAPIDJSON_NAMESPACE_END } +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_HAS_STDSTRING + +#ifndef RAPIDJSON_HAS_STDSTRING +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_HAS_STDSTRING 1 // force generation of documentation +#else +#define RAPIDJSON_HAS_STDSTRING 0 // no std::string support by default +#endif +/*! \def RAPIDJSON_HAS_STDSTRING + \ingroup RAPIDJSON_CONFIG + \brief Enable RapidJSON support for \c std::string + + By defining this preprocessor symbol to \c 1, several convenience functions for using + \ref rapidjson::GenericValue with \c std::string are enabled, especially + for construction and comparison. + + \hideinitializer +*/ +#endif // !defined(RAPIDJSON_HAS_STDSTRING) + +#if RAPIDJSON_HAS_STDSTRING +#include +#endif // RAPIDJSON_HAS_STDSTRING + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_INT64DEFINE + +/*! \def RAPIDJSON_NO_INT64DEFINE + \ingroup RAPIDJSON_CONFIG + \brief Use external 64-bit integer types. + + RapidJSON requires the 64-bit integer types \c int64_t and \c uint64_t types + to be available at global scope. + + If users have their own definition, define RAPIDJSON_NO_INT64DEFINE to + prevent RapidJSON from defining its own types. +*/ +#ifndef RAPIDJSON_NO_INT64DEFINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && (_MSC_VER < 1800) // Visual Studio 2013 +#include "msinttypes/stdint.h" +#include "msinttypes/inttypes.h" +#else +// Other compilers should have this. +#include +#include +#endif +//!@endcond +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_INT64DEFINE +#endif +#endif // RAPIDJSON_NO_INT64TYPEDEF + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_FORCEINLINE + +#ifndef RAPIDJSON_FORCEINLINE +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#if defined(_MSC_VER) && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __forceinline +#elif defined(__GNUC__) && __GNUC__ >= 4 && defined(NDEBUG) +#define RAPIDJSON_FORCEINLINE __attribute__((always_inline)) +#else +#define RAPIDJSON_FORCEINLINE +#endif +//!@endcond +#endif // RAPIDJSON_FORCEINLINE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ENDIAN +#define RAPIDJSON_LITTLEENDIAN 0 //!< Little endian machine +#define RAPIDJSON_BIGENDIAN 1 //!< Big endian machine + +//! Endianness of the machine. +/*! + \def RAPIDJSON_ENDIAN + \ingroup RAPIDJSON_CONFIG + + GCC 4.6 provided macro for detecting endianness of the target machine. But other + compilers may not have this. User can define RAPIDJSON_ENDIAN to either + \ref RAPIDJSON_LITTLEENDIAN or \ref RAPIDJSON_BIGENDIAN. + + Default detection implemented with reference to + \li https://gcc.gnu.org/onlinedocs/gcc-4.6.0/cpp/Common-Predefined-Macros.html + \li http://www.boost.org/doc/libs/1_42_0/boost/detail/endian.hpp +*/ +#ifndef RAPIDJSON_ENDIAN +// Detect with GCC 4.6's macro +# ifdef __BYTE_ORDER__ +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __BYTE_ORDER__ +// Detect with GLIBC's endian.h +# elif defined(__GLIBC__) +# include +# if (__BYTE_ORDER == __LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif (__BYTE_ORDER == __BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif // __GLIBC__ +// Detect with _LITTLE_ENDIAN and _BIG_ENDIAN macro +# elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +// Detect with architecture macros +# elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +# define RAPIDJSON_ENDIAN RAPIDJSON_BIGENDIAN +# elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || defined(__bfin__) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64)) +# define RAPIDJSON_ENDIAN RAPIDJSON_LITTLEENDIAN +# elif defined(RAPIDJSON_DOXYGEN_RUNNING) +# define RAPIDJSON_ENDIAN +# else +# error Unknown machine endianness detected. User needs to define RAPIDJSON_ENDIAN. +# endif +#endif // RAPIDJSON_ENDIAN + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_64BIT + +//! Whether using 64-bit architecture +#ifndef RAPIDJSON_64BIT +#if defined(__LP64__) || (defined(__x86_64__) && defined(__ILP32__)) || defined(_WIN64) || defined(__EMSCRIPTEN__) +#define RAPIDJSON_64BIT 1 +#else +#define RAPIDJSON_64BIT 0 +#endif +#endif // RAPIDJSON_64BIT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ALIGN + +//! Data alignment of the machine. +/*! \ingroup RAPIDJSON_CONFIG + \param x pointer to align + + Some machines require strict data alignment. The default is 8 bytes. + User can customize by defining the RAPIDJSON_ALIGN function macro. +*/ +#ifndef RAPIDJSON_ALIGN +#define RAPIDJSON_ALIGN(x) (((x) + static_cast(7u)) & ~static_cast(7u)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_UINT64_C2 + +//! Construct a 64-bit literal by a pair of 32-bit integer. +/*! + 64-bit literal with or without ULL suffix is prone to compiler warnings. + UINT64_C() is C macro which cause compilation problems. + Use this macro to define 64-bit constants by a pair of 32-bit integer. +*/ +#ifndef RAPIDJSON_UINT64_C2 +#define RAPIDJSON_UINT64_C2(high32, low32) ((static_cast(high32) << 32) | static_cast(low32)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_48BITPOINTER_OPTIMIZATION + +//! Use only lower 48-bit address for some pointers. +/*! + \ingroup RAPIDJSON_CONFIG + + This optimization uses the fact that current X86-64 architecture only implement lower 48-bit virtual address. + The higher 16-bit can be used for storing other data. + \c GenericValue uses this optimization to reduce its size form 24 bytes to 16 bytes in 64-bit architecture. +*/ +#ifndef RAPIDJSON_48BITPOINTER_OPTIMIZATION +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 1 +#else +#define RAPIDJSON_48BITPOINTER_OPTIMIZATION 0 +#endif +#endif // RAPIDJSON_48BITPOINTER_OPTIMIZATION + +#if RAPIDJSON_48BITPOINTER_OPTIMIZATION == 1 +#if RAPIDJSON_64BIT != 1 +#error RAPIDJSON_48BITPOINTER_OPTIMIZATION can only be set to 1 when RAPIDJSON_64BIT=1 +#endif +#define RAPIDJSON_SETPOINTER(type, p, x) (p = reinterpret_cast((reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0xFFFF0000, 0x00000000))) | reinterpret_cast(reinterpret_cast(x)))) +#define RAPIDJSON_GETPOINTER(type, p) (reinterpret_cast(reinterpret_cast(p) & static_cast(RAPIDJSON_UINT64_C2(0x0000FFFF, 0xFFFFFFFF)))) +#else +#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x)) +#define RAPIDJSON_GETPOINTER(type, p) (p) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_SSE2/RAPIDJSON_SSE42/RAPIDJSON_NEON/RAPIDJSON_SIMD + +/*! \def RAPIDJSON_SIMD + \ingroup RAPIDJSON_CONFIG + \brief Enable SSE2/SSE4.2/Neon optimization. + + RapidJSON supports optimized implementations for some parsing operations + based on the SSE2, SSE4.2 or NEon SIMD extensions on modern Intel + or ARM compatible processors. + + To enable these optimizations, three different symbols can be defined; + \code + // Enable SSE2 optimization. + #define RAPIDJSON_SSE2 + + // Enable SSE4.2 optimization. + #define RAPIDJSON_SSE42 + \endcode + + // Enable ARM Neon optimization. + #define RAPIDJSON_NEON + \endcode + + \c RAPIDJSON_SSE42 takes precedence over SSE2, if both are defined. + + If any of these symbols is defined, RapidJSON defines the macro + \c RAPIDJSON_SIMD to indicate the availability of the optimized code. +*/ +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) \ + || defined(RAPIDJSON_NEON) || defined(RAPIDJSON_DOXYGEN_RUNNING) +#define RAPIDJSON_SIMD +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NO_SIZETYPEDEFINE + +#ifndef RAPIDJSON_NO_SIZETYPEDEFINE +/*! \def RAPIDJSON_NO_SIZETYPEDEFINE + \ingroup RAPIDJSON_CONFIG + \brief User-provided \c SizeType definition. + + In order to avoid using 32-bit size types for indexing strings and arrays, + define this preprocessor symbol and provide the type rapidjson::SizeType + before including RapidJSON: + \code + #define RAPIDJSON_NO_SIZETYPEDEFINE + namespace rapidjson { typedef ::std::size_t SizeType; } + #include "rapidjson/..." + \endcode + + \see rapidjson::SizeType +*/ +#ifdef RAPIDJSON_DOXYGEN_RUNNING +#define RAPIDJSON_NO_SIZETYPEDEFINE +#endif +RAPIDJSON_NAMESPACE_BEGIN +//! Size type (for string lengths, array sizes, etc.) +/*! RapidJSON uses 32-bit array/string indices even on 64-bit platforms, + instead of using \c size_t. Users may override the SizeType by defining + \ref RAPIDJSON_NO_SIZETYPEDEFINE. +*/ +typedef unsigned SizeType; +RAPIDJSON_NAMESPACE_END +#endif + +// always import std::size_t to rapidjson namespace +RAPIDJSON_NAMESPACE_BEGIN +using std::size_t; +RAPIDJSON_NAMESPACE_END + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ASSERT + +//! Assertion. +/*! \ingroup RAPIDJSON_CONFIG + By default, rapidjson uses C \c assert() for internal assertions. + User can override it by defining RAPIDJSON_ASSERT(x) macro. + + \note Parsing errors are handled and can be customized by the + \ref RAPIDJSON_ERRORS APIs. +*/ +#ifndef RAPIDJSON_ASSERT +#include +#define RAPIDJSON_ASSERT(x) assert(x) +#endif // RAPIDJSON_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_STATIC_ASSERT + +// Prefer C++11 static_assert, if available +#ifndef RAPIDJSON_STATIC_ASSERT +#if __cplusplus >= 201103L || ( defined(_MSC_VER) && _MSC_VER >= 1800 ) +#define RAPIDJSON_STATIC_ASSERT(x) \ + static_assert(x, RAPIDJSON_STRINGIFY(x)) +#endif // C++11 +#endif // RAPIDJSON_STATIC_ASSERT + +// Adopt C++03 implementation from boost +#ifndef RAPIDJSON_STATIC_ASSERT +#ifndef __clang__ +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#endif +RAPIDJSON_NAMESPACE_BEGIN +template struct STATIC_ASSERTION_FAILURE; +template <> struct STATIC_ASSERTION_FAILURE { enum { value = 1 }; }; +template struct StaticAssertTest {}; +RAPIDJSON_NAMESPACE_END + +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE __attribute__((unused)) +#else +#define RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif +#ifndef __clang__ +//!@endcond +#endif + +/*! \def RAPIDJSON_STATIC_ASSERT + \brief (Internal) macro to check for conditions at compile-time + \param x compile-time condition + \hideinitializer + */ +#define RAPIDJSON_STATIC_ASSERT(x) \ + typedef ::RAPIDJSON_NAMESPACE::StaticAssertTest< \ + sizeof(::RAPIDJSON_NAMESPACE::STATIC_ASSERTION_FAILURE)> \ + RAPIDJSON_JOIN(StaticAssertTypedef, __LINE__) RAPIDJSON_STATIC_ASSERT_UNUSED_ATTRIBUTE +#endif // RAPIDJSON_STATIC_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_LIKELY, RAPIDJSON_UNLIKELY + +//! Compiler branching hint for expression with high probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression likely to be true. +*/ +#ifndef RAPIDJSON_LIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define RAPIDJSON_LIKELY(x) (x) +#endif +#endif + +//! Compiler branching hint for expression with low probability to be true. +/*! + \ingroup RAPIDJSON_CONFIG + \param x Boolean expression unlikely to be true. +*/ +#ifndef RAPIDJSON_UNLIKELY +#if defined(__GNUC__) || defined(__clang__) +#define RAPIDJSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define RAPIDJSON_UNLIKELY(x) (x) +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN + +#define RAPIDJSON_MULTILINEMACRO_BEGIN do { +#define RAPIDJSON_MULTILINEMACRO_END \ +} while((void)0, 0) + +// adopted from Boost +#define RAPIDJSON_VERSION_CODE(x,y,z) \ + (((x)*100000) + ((y)*100) + (z)) + +#if defined(__has_builtin) +#define RAPIDJSON_HAS_BUILTIN(x) __has_builtin(x) +#else +#define RAPIDJSON_HAS_BUILTIN(x) 0 +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_DIAG_PUSH/POP, RAPIDJSON_DIAG_OFF + +#if defined(__GNUC__) +#define RAPIDJSON_GNUC \ + RAPIDJSON_VERSION_CODE(__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__) +#endif + +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,2,0)) + +#define RAPIDJSON_PRAGMA(x) _Pragma(RAPIDJSON_STRINGIFY(x)) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(GCC diagnostic x) +#define RAPIDJSON_DIAG_OFF(x) \ + RAPIDJSON_DIAG_PRAGMA(ignored RAPIDJSON_STRINGIFY(RAPIDJSON_JOIN(-W,x))) + +// push/pop support in Clang and GCC>=4.6 +#if defined(__clang__) || (defined(RAPIDJSON_GNUC) && RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) +#else // GCC >= 4.2, < 4.6 +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ +#endif + +#elif defined(_MSC_VER) + +// pragma (MSVC specific) +#define RAPIDJSON_PRAGMA(x) __pragma(x) +#define RAPIDJSON_DIAG_PRAGMA(x) RAPIDJSON_PRAGMA(warning(x)) + +#define RAPIDJSON_DIAG_OFF(x) RAPIDJSON_DIAG_PRAGMA(disable: x) +#define RAPIDJSON_DIAG_PUSH RAPIDJSON_DIAG_PRAGMA(push) +#define RAPIDJSON_DIAG_POP RAPIDJSON_DIAG_PRAGMA(pop) + +#else + +#define RAPIDJSON_DIAG_OFF(x) /* ignored */ +#define RAPIDJSON_DIAG_PUSH /* ignored */ +#define RAPIDJSON_DIAG_POP /* ignored */ + +#endif // RAPIDJSON_DIAG_* + +/////////////////////////////////////////////////////////////////////////////// +// C++11 features + +#ifndef RAPIDJSON_HAS_CXX11_RVALUE_REFS +#if defined(__clang__) +#if __has_feature(cxx_rvalue_references) && \ + (defined(_MSC_VER) || defined(_LIBCPP_VERSION) || defined(__GLIBCXX__) && __GLIBCXX__ >= 20080306) +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,3,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1600) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) + +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 1 +#else +#define RAPIDJSON_HAS_CXX11_RVALUE_REFS 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RVALUE_REFS + +#ifndef RAPIDJSON_HAS_CXX11_NOEXCEPT +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT __has_feature(cxx_noexcept) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 1 +#else +#define RAPIDJSON_HAS_CXX11_NOEXCEPT 0 +#endif +#endif +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT noexcept +#else +#define RAPIDJSON_NOEXCEPT /* noexcept */ +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT + +// no automatic detection, yet +#ifndef RAPIDJSON_HAS_CXX11_TYPETRAITS +#if (defined(_MSC_VER) && _MSC_VER >= 1700) +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 1 +#else +#define RAPIDJSON_HAS_CXX11_TYPETRAITS 0 +#endif +#endif + +#ifndef RAPIDJSON_HAS_CXX11_RANGE_FOR +#if defined(__clang__) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR __has_feature(cxx_range_for) +#elif (defined(RAPIDJSON_GNUC) && (RAPIDJSON_GNUC >= RAPIDJSON_VERSION_CODE(4,6,0)) && defined(__GXX_EXPERIMENTAL_CXX0X__)) || \ + (defined(_MSC_VER) && _MSC_VER >= 1700) || \ + (defined(__SUNPRO_CC) && __SUNPRO_CC >= 0x5140 && defined(__GXX_EXPERIMENTAL_CXX0X__)) +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 1 +#else +#define RAPIDJSON_HAS_CXX11_RANGE_FOR 0 +#endif +#endif // RAPIDJSON_HAS_CXX11_RANGE_FOR + +/////////////////////////////////////////////////////////////////////////////// +// C++17 features + +#if defined(__has_cpp_attribute) +# if __has_cpp_attribute(fallthrough) +# define RAPIDJSON_DELIBERATE_FALLTHROUGH [[fallthrough]] +# else +# define RAPIDJSON_DELIBERATE_FALLTHROUGH +# endif +#else +# define RAPIDJSON_DELIBERATE_FALLTHROUGH +#endif + +//!@endcond + +//! Assertion (in non-throwing contexts). + /*! \ingroup RAPIDJSON_CONFIG + Some functions provide a \c noexcept guarantee, if the compiler supports it. + In these cases, the \ref RAPIDJSON_ASSERT macro cannot be overridden to + throw an exception. This macro adds a separate customization point for + such cases. + + Defaults to C \c assert() (as \ref RAPIDJSON_ASSERT), if \c noexcept is + supported, and to \ref RAPIDJSON_ASSERT otherwise. + */ + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_NOEXCEPT_ASSERT + +#ifndef RAPIDJSON_NOEXCEPT_ASSERT +#ifdef RAPIDJSON_ASSERT_THROWS +#if RAPIDJSON_HAS_CXX11_NOEXCEPT +#define RAPIDJSON_NOEXCEPT_ASSERT(x) +#else +#include +#define RAPIDJSON_NOEXCEPT_ASSERT(x) assert(x) +#endif // RAPIDJSON_HAS_CXX11_NOEXCEPT +#else +#define RAPIDJSON_NOEXCEPT_ASSERT(x) RAPIDJSON_ASSERT(x) +#endif // RAPIDJSON_ASSERT_THROWS +#endif // RAPIDJSON_NOEXCEPT_ASSERT + +/////////////////////////////////////////////////////////////////////////////// +// malloc/realloc/free + +#ifndef RAPIDJSON_MALLOC +///! customization point for global \c malloc +#define RAPIDJSON_MALLOC(size) std::malloc(size) +#endif +#ifndef RAPIDJSON_REALLOC +///! customization point for global \c realloc +#define RAPIDJSON_REALLOC(ptr, new_size) std::realloc(ptr, new_size) +#endif +#ifndef RAPIDJSON_FREE +///! customization point for global \c free +#define RAPIDJSON_FREE(ptr) std::free(ptr) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// new/delete + +#ifndef RAPIDJSON_NEW +///! customization point for global \c new +#define RAPIDJSON_NEW(TypeName) new TypeName +#endif +#ifndef RAPIDJSON_DELETE +///! customization point for global \c delete +#define RAPIDJSON_DELETE(x) delete x +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Type + +/*! \namespace rapidjson + \brief main RapidJSON namespace + \see RAPIDJSON_NAMESPACE +*/ +RAPIDJSON_NAMESPACE_BEGIN + +//! Type of JSON value +enum Type { + kNullType = 0, //!< null + kFalseType = 1, //!< false + kTrueType = 2, //!< true + kObjectType = 3, //!< object + kArrayType = 4, //!< array + kStringType = 5, //!< string + kNumberType = 6 //!< number +}; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/reader.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/reader.h new file mode 100644 index 0000000000..30e45e1f65 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/reader.h @@ -0,0 +1,2244 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_READER_H_ +#define RAPIDJSON_READER_H_ + +/*! \file reader.h */ + +#include "allocators.h" +#include "stream.h" +#include "encodedstream.h" +#include "internal/clzll.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strtod.h" +#include + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(old-style-cast) +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(switch-enum) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +RAPIDJSON_DIAG_OFF(4702) // unreachable code +#endif + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(effc++) +#endif + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define RAPIDJSON_NOTHING /* deliberately empty */ +#ifndef RAPIDJSON_PARSE_ERROR_EARLY_RETURN +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN(value) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + if (RAPIDJSON_UNLIKELY(HasParseError())) { return value; } \ + RAPIDJSON_MULTILINEMACRO_END +#endif +#define RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(RAPIDJSON_NOTHING) +//!@endcond + +/*! \def RAPIDJSON_PARSE_ERROR_NORETURN + \ingroup RAPIDJSON_ERRORS + \brief Macro to indicate a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + This macros can be used as a customization point for the internal + error handling mechanism of RapidJSON. + + A common usage model is to throw an exception instead of requiring the + caller to explicitly check the \ref rapidjson::GenericReader::Parse's + return value: + + \code + #define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode,offset) \ + throw ParseException(parseErrorCode, #parseErrorCode, offset) + + #include // std::runtime_error + #include "rapidjson/error/error.h" // rapidjson::ParseResult + + struct ParseException : std::runtime_error, rapidjson::ParseResult { + ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) + : std::runtime_error(msg), ParseResult(code, offset) {} + }; + + #include "rapidjson/reader.h" + \endcode + + \see RAPIDJSON_PARSE_ERROR, rapidjson::GenericReader::Parse + */ +#ifndef RAPIDJSON_PARSE_ERROR_NORETURN +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + SetParseError(parseErrorCode, offset); \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +/*! \def RAPIDJSON_PARSE_ERROR + \ingroup RAPIDJSON_ERRORS + \brief (Internal) macro to indicate and handle a parse error. + \param parseErrorCode \ref rapidjson::ParseErrorCode of the error + \param offset position of the error in JSON input (\c size_t) + + Invokes RAPIDJSON_PARSE_ERROR_NORETURN and stops the parsing. + + \see RAPIDJSON_PARSE_ERROR_NORETURN + \hideinitializer + */ +#ifndef RAPIDJSON_PARSE_ERROR +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ + RAPIDJSON_MULTILINEMACRO_BEGIN \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; \ + RAPIDJSON_MULTILINEMACRO_END +#endif + +#include "error/error.h" // ParseErrorCode, ParseResult + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// ParseFlag + +/*! \def RAPIDJSON_PARSE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kParseDefaultFlags definition. + + User can define this as any \c ParseFlag combinations. +*/ +#ifndef RAPIDJSON_PARSE_DEFAULT_FLAGS +#define RAPIDJSON_PARSE_DEFAULT_FLAGS kParseNoFlags +#endif + +//! Combination of parseFlags +/*! \see Reader::Parse, Document::Parse, Document::ParseInsitu, Document::ParseStream + */ +enum ParseFlag { + kParseNoFlags = 0, //!< No flags are set. + kParseInsituFlag = 1, //!< In-situ(destructive) parsing. + kParseValidateEncodingFlag = 2, //!< Validate encoding of JSON strings. + kParseIterativeFlag = 4, //!< Iterative(constant complexity in terms of function call stack size) parsing. + kParseStopWhenDoneFlag = 8, //!< After parsing a complete JSON root from stream, stop further processing the rest of stream. When this flag is used, parser will not generate kParseErrorDocumentRootNotSingular error. + kParseFullPrecisionFlag = 16, //!< Parse number in full precision (but slower). + kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments. + kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings. + kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays. + kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles. + kParseEscapedApostropheFlag = 512, //!< Allow escaped apostrophe in strings. + kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS +}; + +/////////////////////////////////////////////////////////////////////////////// +// Handler + +/*! \class rapidjson::Handler + \brief Concept for receiving events from GenericReader upon parsing. + The functions return true if no error occurs. If they return false, + the event publisher should terminate the process. +\code +concept Handler { + typename Ch; + + bool Null(); + bool Bool(bool b); + bool Int(int i); + bool Uint(unsigned i); + bool Int64(int64_t i); + bool Uint64(uint64_t i); + bool Double(double d); + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType length, bool copy); + bool String(const Ch* str, SizeType length, bool copy); + bool StartObject(); + bool Key(const Ch* str, SizeType length, bool copy); + bool EndObject(SizeType memberCount); + bool StartArray(); + bool EndArray(SizeType elementCount); +}; +\endcode +*/ +/////////////////////////////////////////////////////////////////////////////// +// BaseReaderHandler + +//! Default implementation of Handler. +/*! This can be used as base class of any reader handler. + \note implements Handler concept +*/ +template, typename Derived = void> +struct BaseReaderHandler { + typedef typename Encoding::Ch Ch; + + typedef typename internal::SelectIf, BaseReaderHandler, Derived>::Type Override; + + bool Default() { return true; } + bool Null() { return static_cast(*this).Default(); } + bool Bool(bool) { return static_cast(*this).Default(); } + bool Int(int) { return static_cast(*this).Default(); } + bool Uint(unsigned) { return static_cast(*this).Default(); } + bool Int64(int64_t) { return static_cast(*this).Default(); } + bool Uint64(uint64_t) { return static_cast(*this).Default(); } + bool Double(double) { return static_cast(*this).Default(); } + /// enabled via kParseNumbersAsStringsFlag, string is not null-terminated (use length) + bool RawNumber(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool String(const Ch*, SizeType, bool) { return static_cast(*this).Default(); } + bool StartObject() { return static_cast(*this).Default(); } + bool Key(const Ch* str, SizeType len, bool copy) { return static_cast(*this).String(str, len, copy); } + bool EndObject(SizeType) { return static_cast(*this).Default(); } + bool StartArray() { return static_cast(*this).Default(); } + bool EndArray(SizeType) { return static_cast(*this).Default(); } +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamLocalCopy + +namespace internal { + +template::copyOptimization> +class StreamLocalCopy; + +//! Do copy optimization. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original), original_(original) {} + ~StreamLocalCopy() { original_ = s; } + + Stream s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; + + Stream& original_; +}; + +//! Keep reference. +template +class StreamLocalCopy { +public: + StreamLocalCopy(Stream& original) : s(original) {} + + Stream& s; + +private: + StreamLocalCopy& operator=(const StreamLocalCopy&) /* = delete */; +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// SkipWhitespace + +//! Skip the JSON white spaces in a stream. +/*! \param is A input stream for skipping white spaces. + \note This function has SSE2/SSE4.2 specialization. +*/ +template +void SkipWhitespace(InputStream& is) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + typename InputStream::Ch c; + while ((c = s.Peek()) == ' ' || c == '\n' || c == '\r' || c == '\t') + s.Take(); +} + +inline const char* SkipWhitespace(const char* p, const char* end) { + while (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + return p; +} + +#ifdef RAPIDJSON_SSE42 +//! Skip whitespace with SSE 4.2 pcmpistrm instruction, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The middle of string using SIMD + static const char whitespace[16] = " \n\r\t"; + const __m128i w = _mm_loadu_si128(reinterpret_cast(&whitespace[0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + const int r = _mm_cmpistri(w, s, _SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT | _SIDD_NEGATIVE_POLARITY); + if (r != 16) // some of characters is non-whitespace + return p + r; + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_SSE2) + +//! Skip whitespace with SSE2 instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + // The rest of string + #define C16(c) { c, c, c, c, c, c, c, c, c, c, c, c, c, c, c, c } + static const char whitespaces[4][16] = { C16(' '), C16('\n'), C16('\r'), C16('\t') }; + #undef C16 + + const __m128i w0 = _mm_loadu_si128(reinterpret_cast(&whitespaces[0][0])); + const __m128i w1 = _mm_loadu_si128(reinterpret_cast(&whitespaces[1][0])); + const __m128i w2 = _mm_loadu_si128(reinterpret_cast(&whitespaces[2][0])); + const __m128i w3 = _mm_loadu_si128(reinterpret_cast(&whitespaces[3][0])); + + for (; p <= end - 16; p += 16) { + const __m128i s = _mm_loadu_si128(reinterpret_cast(p)); + __m128i x = _mm_cmpeq_epi8(s, w0); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w1)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w2)); + x = _mm_or_si128(x, _mm_cmpeq_epi8(s, w3)); + unsigned short r = static_cast(~_mm_movemask_epi8(x)); + if (r != 0) { // some of characters may be non-whitespace +#ifdef _MSC_VER // Find the index of first non-whitespace + unsigned long offset; + _BitScanForward(&offset, r); + return p + offset; +#else + return p + __builtin_ffs(r) - 1; +#endif + } + } + + return SkipWhitespace(p, end); +} + +#elif defined(RAPIDJSON_NEON) + +//! Skip whitespace with ARM Neon instructions, testing 16 8-byte characters at once. +inline const char *SkipWhitespace_SIMD(const char* p) { + // Fast return for single non-whitespace + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + // 16-byte align to the next boundary + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t') + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + return p + 8 + (lz >> 3); + } + } else { + uint32_t lz = internal::clzll(low); + return p + (lz >> 3); + } + } +} + +inline const char *SkipWhitespace_SIMD(const char* p, const char* end) { + // Fast return for single non-whitespace + if (p != end && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) + ++p; + else + return p; + + const uint8x16_t w0 = vmovq_n_u8(' '); + const uint8x16_t w1 = vmovq_n_u8('\n'); + const uint8x16_t w2 = vmovq_n_u8('\r'); + const uint8x16_t w3 = vmovq_n_u8('\t'); + + for (; p <= end - 16; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, w0); + x = vorrq_u8(x, vceqq_u8(s, w1)); + x = vorrq_u8(x, vceqq_u8(s, w2)); + x = vorrq_u8(x, vceqq_u8(s, w3)); + + x = vmvnq_u8(x); // Negate + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + return p + 8 + (lz >> 3); + } + } else { + uint32_t lz = internal::clzll(low); + return p + (lz >> 3); + } + } + + return SkipWhitespace(p, end); +} + +#endif // RAPIDJSON_NEON + +#ifdef RAPIDJSON_SIMD +//! Template function specialization for InsituStringStream +template<> inline void SkipWhitespace(InsituStringStream& is) { + is.src_ = const_cast(SkipWhitespace_SIMD(is.src_)); +} + +//! Template function specialization for StringStream +template<> inline void SkipWhitespace(StringStream& is) { + is.src_ = SkipWhitespace_SIMD(is.src_); +} + +template<> inline void SkipWhitespace(EncodedInputStream, MemoryStream>& is) { + is.is_.src_ = SkipWhitespace_SIMD(is.is_.src_, is.is_.end_); +} +#endif // RAPIDJSON_SIMD + +/////////////////////////////////////////////////////////////////////////////// +// GenericReader + +//! SAX-style JSON parser. Use \ref Reader for UTF8 encoding and default allocator. +/*! GenericReader parses JSON text from a stream, and send events synchronously to an + object implementing Handler concept. + + It needs to allocate a stack for storing a single decoded string during + non-destructive parsing. + + For in-situ parsing, the decoded string is directly written to the source + text string, no temporary buffer is required. + + A GenericReader object can be reused for parsing multiple JSON text. + + \tparam SourceEncoding Encoding of the input stream. + \tparam TargetEncoding Encoding of the parse output. + \tparam StackAllocator Allocator type for stack. +*/ +template +class GenericReader { +public: + typedef typename SourceEncoding::Ch Ch; //!< SourceEncoding character type + + //! Constructor. + /*! \param stackAllocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) + \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) + */ + GenericReader(StackAllocator* stackAllocator = 0, size_t stackCapacity = kDefaultStackCapacity) : + stack_(stackAllocator, stackCapacity), parseResult_(), state_(IterativeParsingStartState) {} + + //! Parse JSON text. + /*! \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + if (parseFlags & kParseIterativeFlag) + return IterativeParse(is, handler); + + parseResult_.Clear(); + + ClearStackOnExit scope(*this); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + else { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (!(parseFlags & kParseStopWhenDoneFlag)) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + + if (RAPIDJSON_UNLIKELY(is.Peek() != '\0')) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + } + } + + return parseResult_; + } + + //! Parse JSON text (with \ref kParseDefaultFlags) + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + ParseResult Parse(InputStream& is, Handler& handler) { + return Parse(is, handler); + } + + //! Initialize JSON text token-by-token parsing + /*! + */ + void IterativeParseInit() { + parseResult_.Clear(); + state_ = IterativeParsingStartState; + } + + //! Parse one token from JSON text + /*! \tparam InputStream Type of input stream, implementing Stream concept + \tparam Handler Type of handler, implementing Handler concept. + \param is Input stream to be parsed. + \param handler The handler to receive events. + \return Whether the parsing is successful. + */ + template + bool IterativeParseNext(InputStream& is, Handler& handler) { + while (RAPIDJSON_LIKELY(is.Peek() != '\0')) { + SkipWhitespaceAndComments(is); + + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state_, t); + IterativeParsingState d = Transit(state_, t, n, is, handler); + + // If we've finished or hit an error... + if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) { + // Report errors. + if (d == IterativeParsingErrorState) { + HandleError(state_, is); + return false; + } + + // Transition to the finish state. + RAPIDJSON_ASSERT(d == IterativeParsingFinishState); + state_ = d; + + // If StopWhenDone is not set... + if (!(parseFlags & kParseStopWhenDoneFlag)) { + // ... and extra non-whitespace data is found... + SkipWhitespaceAndComments(is); + if (is.Peek() != '\0') { + // ... this is considered an error. + HandleError(state_, is); + return false; + } + } + + // Success! We are done! + return true; + } + + // Transition to the new state. + state_ = d; + + // If we parsed anything other than a delimiter, we invoked the handler, so we can return true now. + if (!IsIterativeParsingDelimiterState(n)) + return true; + } + + // We reached the end of file. + stack_.Clear(); + + if (state_ != IterativeParsingFinishState) { + HandleError(state_, is); + return false; + } + + return true; + } + + //! Check if token-by-token parsing JSON text is complete + /*! \return Whether the JSON has been fully decoded. + */ + RAPIDJSON_FORCEINLINE bool IterativeParseComplete() const { + return IsIterativeParsingCompleteState(state_); + } + + //! Whether a parse error has occurred in the last parsing. + bool HasParseError() const { return parseResult_.IsError(); } + + //! Get the \ref ParseErrorCode of last parsing. + ParseErrorCode GetParseErrorCode() const { return parseResult_.Code(); } + + //! Get the position of last parsing error in input, 0 otherwise. + size_t GetErrorOffset() const { return parseResult_.Offset(); } + +protected: + void SetParseError(ParseErrorCode code, size_t offset) { parseResult_.Set(code, offset); } + +private: + // Prohibit copy constructor & assignment operator. + GenericReader(const GenericReader&); + GenericReader& operator=(const GenericReader&); + + void ClearStack() { stack_.Clear(); } + + // clear stack on any exit from ParseStream, e.g. due to exception + struct ClearStackOnExit { + explicit ClearStackOnExit(GenericReader& r) : r_(r) {} + ~ClearStackOnExit() { r_.ClearStack(); } + private: + GenericReader& r_; + ClearStackOnExit(const ClearStackOnExit&); + ClearStackOnExit& operator=(const ClearStackOnExit&); + }; + + template + void SkipWhitespaceAndComments(InputStream& is) { + SkipWhitespace(is); + + if (parseFlags & kParseCommentsFlag) { + while (RAPIDJSON_UNLIKELY(Consume(is, '/'))) { + if (Consume(is, '*')) { + while (true) { + if (RAPIDJSON_UNLIKELY(is.Peek() == '\0')) + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + else if (Consume(is, '*')) { + if (Consume(is, '/')) + break; + } + else + is.Take(); + } + } + else if (RAPIDJSON_LIKELY(Consume(is, '/'))) + while (is.Peek() != '\0' && is.Take() != '\n') {} + else + RAPIDJSON_PARSE_ERROR(kParseErrorUnspecificSyntaxError, is.Tell()); + + SkipWhitespace(is); + } + } + } + + // Parse object: { string : value, ... } + template + void ParseObject(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '{'); + is.Take(); // Skip '{' + + if (RAPIDJSON_UNLIKELY(!handler.StartObject())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, '}')) { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(0))) // empty object + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType memberCount = 0;;) { + if (RAPIDJSON_UNLIKELY(is.Peek() != '"')) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); + + ParseString(is, handler, true); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (RAPIDJSON_UNLIKELY(!Consume(is, ':'))) + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++memberCount; + + switch (is.Peek()) { + case ',': + is.Take(); + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + break; + case '}': + is.Take(); + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + default: + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); break; // This useless break is only for making warning and coverage happy + } + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == '}') { + if (RAPIDJSON_UNLIKELY(!handler.EndObject(memberCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + // Parse array: [ value, ... ] + template + void ParseArray(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == '['); + is.Take(); // Skip '[' + + if (RAPIDJSON_UNLIKELY(!handler.StartArray())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(0))) // empty array + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + + for (SizeType elementCount = 0;;) { + ParseValue(is, handler); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + ++elementCount; + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + + if (Consume(is, ',')) { + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + } + else if (Consume(is, ']')) { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + return; + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); + + if (parseFlags & kParseTrailingCommasFlag) { + if (is.Peek() == ']') { + if (RAPIDJSON_UNLIKELY(!handler.EndArray(elementCount))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + is.Take(); + return; + } + } + } + } + + template + void ParseNull(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'n'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'u') && Consume(is, 'l') && Consume(is, 'l'))) { + if (RAPIDJSON_UNLIKELY(!handler.Null())) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseTrue(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 't'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'r') && Consume(is, 'u') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(true))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + void ParseFalse(InputStream& is, Handler& handler) { + RAPIDJSON_ASSERT(is.Peek() == 'f'); + is.Take(); + + if (RAPIDJSON_LIKELY(Consume(is, 'a') && Consume(is, 'l') && Consume(is, 's') && Consume(is, 'e'))) { + if (RAPIDJSON_UNLIKELY(!handler.Bool(false))) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, is.Tell()); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); + } + + template + RAPIDJSON_FORCEINLINE static bool Consume(InputStream& is, typename InputStream::Ch expect) { + if (RAPIDJSON_LIKELY(is.Peek() == expect)) { + is.Take(); + return true; + } + else + return false; + } + + // Helper function to parse four hexadecimal digits in \uXXXX in ParseString(). + template + unsigned ParseHex4(InputStream& is, size_t escapeOffset) { + unsigned codepoint = 0; + for (int i = 0; i < 4; i++) { + Ch c = is.Peek(); + codepoint <<= 4; + codepoint += static_cast(c); + if (c >= '0' && c <= '9') + codepoint -= '0'; + else if (c >= 'A' && c <= 'F') + codepoint -= 'A' - 10; + else if (c >= 'a' && c <= 'f') + codepoint -= 'a' - 10; + else { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(0); + } + is.Take(); + } + return codepoint; + } + + template + class StackStream { + public: + typedef CharType Ch; + + StackStream(internal::Stack& stack) : stack_(stack), length_(0) {} + RAPIDJSON_FORCEINLINE void Put(Ch c) { + *stack_.template Push() = c; + ++length_; + } + + RAPIDJSON_FORCEINLINE void* Push(SizeType count) { + length_ += count; + return stack_.template Push(count); + } + + size_t Length() const { return length_; } + + Ch* Pop() { + return stack_.template Pop(length_); + } + + private: + StackStream(const StackStream&); + StackStream& operator=(const StackStream&); + + internal::Stack& stack_; + SizeType length_; + }; + + // Parse string and generate String event. Different code paths for kParseInsituFlag. + template + void ParseString(InputStream& is, Handler& handler, bool isKey = false) { + internal::StreamLocalCopy copy(is); + InputStream& s(copy.s); + + RAPIDJSON_ASSERT(s.Peek() == '\"'); + s.Take(); // Skip '\"' + + bool success = false; + if (parseFlags & kParseInsituFlag) { + typename InputStream::Ch *head = s.PutBegin(); + ParseStringToStream(s, s); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + size_t length = s.PutEnd(head) - 1; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + success = (isKey ? handler.Key(str, SizeType(length), false) : handler.String(str, SizeType(length), false)); + } + else { + StackStream stackStream(stack_); + ParseStringToStream(s, stackStream); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + SizeType length = static_cast(stackStream.Length()) - 1; + const typename TargetEncoding::Ch* const str = stackStream.Pop(); + success = (isKey ? handler.Key(str, length, true) : handler.String(str, length, true)); + } + if (RAPIDJSON_UNLIKELY(!success)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, s.Tell()); + } + + // Parse string to an output is + // This function handles the prefix/suffix double quotes, escaping, and optional encoding validation. + template + RAPIDJSON_FORCEINLINE void ParseStringToStream(InputStream& is, OutputStream& os) { +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + static const char escape[256] = { + Z16, Z16, 0, 0,'\"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '/', + Z16, Z16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, + 0, 0,'\b', 0, 0, 0,'\f', 0, 0, 0, 0, 0, 0, 0,'\n', 0, + 0, 0,'\r', 0,'\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 + }; +#undef Z16 +//!@endcond + + for (;;) { + // Scan and copy string before "\\\"" or < 0x20. This is an optional optimzation. + if (!(parseFlags & kParseValidateEncodingFlag)) + ScanCopyUnescapedString(is, os); + + Ch c = is.Peek(); + if (RAPIDJSON_UNLIKELY(c == '\\')) { // Escape + size_t escapeOffset = is.Tell(); // For invalid escaping, report the initial '\\' as error offset + is.Take(); + Ch e = is.Peek(); + if ((sizeof(Ch) == 1 || unsigned(e) < 256) && RAPIDJSON_LIKELY(escape[static_cast(e)])) { + is.Take(); + os.Put(static_cast(escape[static_cast(e)])); + } + else if ((parseFlags & kParseEscapedApostropheFlag) && RAPIDJSON_LIKELY(e == '\'')) { // Allow escaped apostrophe + is.Take(); + os.Put('\''); + } + else if (RAPIDJSON_LIKELY(e == 'u')) { // Unicode + is.Take(); + unsigned codepoint = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint >= 0xD800 && codepoint <= 0xDFFF)) { + // high surrogate, check if followed by valid low surrogate + if (RAPIDJSON_LIKELY(codepoint <= 0xDBFF)) { + // Handle UTF-16 surrogate pair + if (RAPIDJSON_UNLIKELY(!Consume(is, '\\') || !Consume(is, 'u'))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + unsigned codepoint2 = ParseHex4(is, escapeOffset); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN_VOID; + if (RAPIDJSON_UNLIKELY(codepoint2 < 0xDC00 || codepoint2 > 0xDFFF)) + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; + } + // single low surrogate + else + { + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, escapeOffset); + } + } + TEncoding::Encode(os, codepoint); + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, escapeOffset); + } + else if (RAPIDJSON_UNLIKELY(c == '"')) { // Closing double quote + is.Take(); + os.Put('\0'); // null-terminate the string + return; + } + else if (RAPIDJSON_UNLIKELY(static_cast(c) < 0x20)) { // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + if (c == '\0') + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell()); + else + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); + } + else { + size_t offset = is.Tell(); + if (RAPIDJSON_UNLIKELY((parseFlags & kParseValidateEncodingFlag ? + !Transcoder::Validate(is, os) : + !Transcoder::Transcode(is, os)))) + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, offset); + } + } + } + + template + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InputStream&, OutputStream&) { + // Do nothing for generic version + } + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType length; + #ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; + #else + length = static_cast(__builtin_ffs(r) - 1); + #endif + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16, q += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + for (const char* pend = p + length; p != pend; ) + *q++ = *p++; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (;; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + size_t length; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + length = offset; +#else + length = static_cast(__builtin_ffs(r) - 1); +#endif + p += length; + break; + } + } + + is.src_ = is.dst_ = p; + } +#elif defined(RAPIDJSON_NEON) + // StringStream -> StackStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(StringStream& is, StackStream& os) { + const char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + return; + } + else + os.Put(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + if (length != 0) { + char* q = reinterpret_cast(os.Push(length)); + for (size_t i = 0; i < length; i++) + q[i] = p[i]; + + p += length; + } + break; + } + vst1q_u8(reinterpret_cast(os.Push(16)), s); + } + + is.src_ = p; + } + + // InsituStringStream -> InsituStringStream + static RAPIDJSON_FORCEINLINE void ScanCopyUnescapedString(InsituStringStream& is, InsituStringStream& os) { + RAPIDJSON_ASSERT(&is == &os); + (void)os; + + if (is.src_ == is.dst_) { + SkipUnescapedString(is); + return; + } + + char* p = is.src_; + char *q = is.dst_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + while (p != nextAligned) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = p; + is.dst_ = q; + return; + } + else + *q++ = *p++; + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16, q += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType length = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + length = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + length = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + for (const char* pend = p + length; p != pend; ) { + *q++ = *p++; + } + break; + } + vst1q_u8(reinterpret_cast(q), s); + } + + is.src_ = p; + is.dst_ = q; + } + + // When read/write pointers are the same for insitu stream, just skip unescaped characters + static RAPIDJSON_FORCEINLINE void SkipUnescapedString(InsituStringStream& is) { + RAPIDJSON_ASSERT(is.src_ == is.dst_); + char* p = is.src_; + + // Scan one by one until alignment (unaligned load may cross page boundary and cause crash) + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + for (; p != nextAligned; p++) + if (RAPIDJSON_UNLIKELY(*p == '\"') || RAPIDJSON_UNLIKELY(*p == '\\') || RAPIDJSON_UNLIKELY(static_cast(*p) < 0x20)) { + is.src_ = is.dst_ = p; + return; + } + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (;; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + p += 8 + (lz >> 3); + break; + } + } else { + uint32_t lz = internal::clzll(low); + p += lz >> 3; + break; + } + } + + is.src_ = is.dst_ = p; + } +#endif // RAPIDJSON_NEON + + template + class NumberStream; + + template + class NumberStream { + public: + typedef typename InputStream::Ch Ch; + + NumberStream(GenericReader& reader, InputStream& s) : is(s) { (void)reader; } + + RAPIDJSON_FORCEINLINE Ch Peek() const { return is.Peek(); } + RAPIDJSON_FORCEINLINE Ch TakePush() { return is.Take(); } + RAPIDJSON_FORCEINLINE Ch Take() { return is.Take(); } + RAPIDJSON_FORCEINLINE void Push(char) {} + + size_t Tell() { return is.Tell(); } + size_t Length() { return 0; } + const char* Pop() { return 0; } + + protected: + NumberStream& operator=(const NumberStream&); + + InputStream& is; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is), stackStream(reader.stack_) {} + + RAPIDJSON_FORCEINLINE Ch TakePush() { + stackStream.Put(static_cast(Base::is.Peek())); + return Base::is.Take(); + } + + RAPIDJSON_FORCEINLINE void Push(char c) { + stackStream.Put(c); + } + + size_t Length() { return stackStream.Length(); } + + const char* Pop() { + stackStream.Put('\0'); + return stackStream.Pop(); + } + + private: + StackStream stackStream; + }; + + template + class NumberStream : public NumberStream { + typedef NumberStream Base; + public: + NumberStream(GenericReader& reader, InputStream& is) : Base(reader, is) {} + + RAPIDJSON_FORCEINLINE Ch Take() { return Base::TakePush(); } + }; + + template + void ParseNumber(InputStream& is, Handler& handler) { + internal::StreamLocalCopy copy(is); + NumberStream s(*this, copy.s); + + size_t startOffset = s.Tell(); + double d = 0.0; + bool useNanOrInf = false; + + // Parse minus + bool minus = Consume(s, '-'); + + // Parse int: zero / ( digit1-9 *DIGIT ) + unsigned i = 0; + uint64_t i64 = 0; + bool use64bit = false; + int significandDigit = 0; + if (RAPIDJSON_UNLIKELY(s.Peek() == '0')) { + i = 0; + s.TakePush(); + } + else if (RAPIDJSON_LIKELY(s.Peek() >= '1' && s.Peek() <= '9')) { + i = static_cast(s.TakePush() - '0'); + + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 214748364)) { // 2^31 = 2147483648 + if (RAPIDJSON_LIKELY(i != 214748364 || s.Peek() > '8')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i >= 429496729)) { // 2^32 - 1 = 4294967295 + if (RAPIDJSON_LIKELY(i != 429496729 || s.Peek() > '5')) { + i64 = i; + use64bit = true; + break; + } + } + i = i * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + // Parse NaN or Infinity here + else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) { + if (Consume(s, 'N')) { + if (Consume(s, 'a') && Consume(s, 'N')) { + d = std::numeric_limits::quiet_NaN(); + useNanOrInf = true; + } + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) { + if (Consume(s, 'n') && Consume(s, 'f')) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()); + useNanOrInf = true; + + if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n') + && Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + } + + if (RAPIDJSON_UNLIKELY(!useNanOrInf)) { + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell()); + + // Parse 64bit int + bool useDouble = false; + if (use64bit) { + if (minus) + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC))) // 2^63 = 9223372036854775808 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x0CCCCCCC, 0xCCCCCCCC) || s.Peek() > '8')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + else + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (RAPIDJSON_UNLIKELY(i64 >= RAPIDJSON_UINT64_C2(0x19999999, 0x99999999))) // 2^64 - 1 = 18446744073709551615 + if (RAPIDJSON_LIKELY(i64 != RAPIDJSON_UINT64_C2(0x19999999, 0x99999999) || s.Peek() > '5')) { + d = static_cast(i64); + useDouble = true; + break; + } + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + significandDigit++; + } + } + + // Force double for big integer + if (useDouble) { + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + d = d * 10 + (s.TakePush() - '0'); + } + } + + // Parse frac = decimal-point 1*DIGIT + int expFrac = 0; + size_t decimalPosition; + if (Consume(s, '.')) { + decimalPosition = s.Length(); + + if (RAPIDJSON_UNLIKELY(!(s.Peek() >= '0' && s.Peek() <= '9'))) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, s.Tell()); + + if (!useDouble) { +#if RAPIDJSON_64BIT + // Use i64 to store significand in 64-bit architecture + if (!use64bit) + i64 = i; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (i64 > RAPIDJSON_UINT64_C2(0x1FFFFF, 0xFFFFFFFF)) // 2^53 - 1 for fast path + break; + else { + i64 = i64 * 10 + static_cast(s.TakePush() - '0'); + --expFrac; + if (i64 != 0) + significandDigit++; + } + } + + d = static_cast(i64); +#else + // Use double to store significand in 32-bit architecture + d = static_cast(use64bit ? i64 : i); +#endif + useDouble = true; + } + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + if (significandDigit < 17) { + d = d * 10.0 + (s.TakePush() - '0'); + --expFrac; + if (RAPIDJSON_LIKELY(d > 0.0)) + significandDigit++; + } + else + s.TakePush(); + } + } + else + decimalPosition = s.Length(); // decimal position at the end of integer. + + // Parse exp = e [ minus / plus ] 1*DIGIT + int exp = 0; + if (Consume(s, 'e') || Consume(s, 'E')) { + if (!useDouble) { + d = static_cast(use64bit ? i64 : i); + useDouble = true; + } + + bool expMinus = false; + if (Consume(s, '+')) + ; + else if (Consume(s, '-')) + expMinus = true; + + if (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = static_cast(s.Take() - '0'); + if (expMinus) { + // (exp + expFrac) must not underflow int => we're detecting when -exp gets + // dangerously close to INT_MIN (a pessimistic next digit 9 would push it into + // underflow territory): + // + // -(exp * 10 + 9) + expFrac >= INT_MIN + // <=> exp <= (expFrac - INT_MIN - 9) / 10 + RAPIDJSON_ASSERT(expFrac <= 0); + int maxExp = (expFrac + 2147483639) / 10; + + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) { + while (RAPIDJSON_UNLIKELY(s.Peek() >= '0' && s.Peek() <= '9')) // Consume the rest of exponent + s.Take(); + } + } + } + else { // positive exp + int maxExp = 308 - expFrac; + while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) { + exp = exp * 10 + static_cast(s.Take() - '0'); + if (RAPIDJSON_UNLIKELY(exp > maxExp)) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + } + } + else + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); + + if (expMinus) + exp = -exp; + } + + // Finish parsing, call event according to the type of number. + bool cont = true; + + if (parseFlags & kParseNumbersAsStringsFlag) { + if (parseFlags & kParseInsituFlag) { + s.Pop(); // Pop stack no matter if it will be used or not. + typename InputStream::Ch* head = is.PutBegin(); + const size_t length = s.Tell() - startOffset; + RAPIDJSON_ASSERT(length <= 0xFFFFFFFF); + // unable to insert the \0 character here, it will erase the comma after this number + const typename TargetEncoding::Ch* const str = reinterpret_cast(head); + cont = handler.RawNumber(str, SizeType(length), false); + } + else { + SizeType numCharsToCopy = static_cast(s.Length()); + StringStream srcStream(s.Pop()); + StackStream dstStream(stack_); + while (numCharsToCopy--) { + Transcoder, TargetEncoding>::Transcode(srcStream, dstStream); + } + dstStream.Put('\0'); + const typename TargetEncoding::Ch* str = dstStream.Pop(); + const SizeType length = static_cast(dstStream.Length()) - 1; + cont = handler.RawNumber(str, SizeType(length), true); + } + } + else { + size_t length = s.Length(); + const char* decimal = s.Pop(); // Pop stack no matter if it will be used or not. + + if (useDouble) { + int p = exp + expFrac; + if (parseFlags & kParseFullPrecisionFlag) + d = internal::StrtodFullPrecision(d, p, decimal, length, decimalPosition, exp); + else + d = internal::StrtodNormalPrecision(d, p); + + // Use > max, instead of == inf, to fix bogus warning -Wfloat-equal + if (d > (std::numeric_limits::max)()) { + // Overflow + // TODO: internal::StrtodX should report overflow (or underflow) + RAPIDJSON_PARSE_ERROR(kParseErrorNumberTooBig, startOffset); + } + + cont = handler.Double(minus ? -d : d); + } + else if (useNanOrInf) { + cont = handler.Double(d); + } + else { + if (use64bit) { + if (minus) + cont = handler.Int64(static_cast(~i64 + 1)); + else + cont = handler.Uint64(i64); + } + else { + if (minus) + cont = handler.Int(static_cast(~i + 1)); + else + cont = handler.Uint(i); + } + } + } + if (RAPIDJSON_UNLIKELY(!cont)) + RAPIDJSON_PARSE_ERROR(kParseErrorTermination, startOffset); + } + + // Parse any JSON value + template + void ParseValue(InputStream& is, Handler& handler) { + switch (is.Peek()) { + case 'n': ParseNull (is, handler); break; + case 't': ParseTrue (is, handler); break; + case 'f': ParseFalse (is, handler); break; + case '"': ParseString(is, handler); break; + case '{': ParseObject(is, handler); break; + case '[': ParseArray (is, handler); break; + default : + ParseNumber(is, handler); + break; + + } + } + + // Iterative Parsing + + // States + enum IterativeParsingState { + IterativeParsingFinishState = 0, // sink states at top + IterativeParsingErrorState, // sink states at top + IterativeParsingStartState, + + // Object states + IterativeParsingObjectInitialState, + IterativeParsingMemberKeyState, + IterativeParsingMemberValueState, + IterativeParsingObjectFinishState, + + // Array states + IterativeParsingArrayInitialState, + IterativeParsingElementState, + IterativeParsingArrayFinishState, + + // Single value state + IterativeParsingValueState, + + // Delimiter states (at bottom) + IterativeParsingElementDelimiterState, + IterativeParsingMemberDelimiterState, + IterativeParsingKeyValueDelimiterState, + + cIterativeParsingStateCount + }; + + // Tokens + enum Token { + LeftBracketToken = 0, + RightBracketToken, + + LeftCurlyBracketToken, + RightCurlyBracketToken, + + CommaToken, + ColonToken, + + StringToken, + FalseToken, + TrueToken, + NullToken, + NumberToken, + + kTokenCount + }; + + RAPIDJSON_FORCEINLINE Token Tokenize(Ch c) const { + +//!@cond RAPIDJSON_HIDDEN_FROM_DOXYGEN +#define N NumberToken +#define N16 N,N,N,N,N,N,N,N,N,N,N,N,N,N,N,N + // Maps from ASCII to Token + static const unsigned char tokenMap[256] = { + N16, // 00~0F + N16, // 10~1F + N, N, StringToken, N, N, N, N, N, N, N, N, N, CommaToken, N, N, N, // 20~2F + N, N, N, N, N, N, N, N, N, N, ColonToken, N, N, N, N, N, // 30~3F + N16, // 40~4F + N, N, N, N, N, N, N, N, N, N, N, LeftBracketToken, N, RightBracketToken, N, N, // 50~5F + N, N, N, N, N, N, FalseToken, N, N, N, N, N, N, N, NullToken, N, // 60~6F + N, N, N, N, TrueToken, N, N, N, N, N, N, LeftCurlyBracketToken, N, RightCurlyBracketToken, N, N, // 70~7F + N16, N16, N16, N16, N16, N16, N16, N16 // 80~FF + }; +#undef N +#undef N16 +//!@endcond + + if (sizeof(Ch) == 1 || static_cast(c) < 256) + return static_cast(tokenMap[static_cast(c)]); + else + return NumberToken; + } + + RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) const { + // current state x one lookahead token -> new state + static const char G[cIterativeParsingStateCount][kTokenCount] = { + // Finish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Error(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Start + { + IterativeParsingArrayInitialState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingValueState, // String + IterativeParsingValueState, // False + IterativeParsingValueState, // True + IterativeParsingValueState, // Null + IterativeParsingValueState // Number + }, + // ObjectInitial + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberKey + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingKeyValueDelimiterState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // MemberValue + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingMemberDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ObjectFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ArrayInitial + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // Element + { + IterativeParsingErrorState, // Left bracket + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingErrorState, // Right curly bracket + IterativeParsingElementDelimiterState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingErrorState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // ArrayFinish(sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // Single Value (sink state) + { + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, + IterativeParsingErrorState + }, + // ElementDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push Element state) + IterativeParsingArrayFinishState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push Element state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingElementState, // String + IterativeParsingElementState, // False + IterativeParsingElementState, // True + IterativeParsingElementState, // Null + IterativeParsingElementState // Number + }, + // MemberDelimiter + { + IterativeParsingErrorState, // Left bracket + IterativeParsingErrorState, // Right bracket + IterativeParsingErrorState, // Left curly bracket + IterativeParsingObjectFinishState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberKeyState, // String + IterativeParsingErrorState, // False + IterativeParsingErrorState, // True + IterativeParsingErrorState, // Null + IterativeParsingErrorState // Number + }, + // KeyValueDelimiter + { + IterativeParsingArrayInitialState, // Left bracket(push MemberValue state) + IterativeParsingErrorState, // Right bracket + IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state) + IterativeParsingErrorState, // Right curly bracket + IterativeParsingErrorState, // Comma + IterativeParsingErrorState, // Colon + IterativeParsingMemberValueState, // String + IterativeParsingMemberValueState, // False + IterativeParsingMemberValueState, // True + IterativeParsingMemberValueState, // Null + IterativeParsingMemberValueState // Number + }, + }; // End of G + + return static_cast(G[state][token]); + } + + // Make an advance in the token stream and state based on the candidate destination state which was returned by Transit(). + // May return a new state on state pop. + template + RAPIDJSON_FORCEINLINE IterativeParsingState Transit(IterativeParsingState src, Token token, IterativeParsingState dst, InputStream& is, Handler& handler) { + (void)token; + + switch (dst) { + case IterativeParsingErrorState: + return dst; + + case IterativeParsingObjectInitialState: + case IterativeParsingArrayInitialState: + { + // Push the state(Element or MemeberValue) if we are nested in another array or value of member. + // In this way we can get the correct state on ObjectFinish or ArrayFinish by frame pop. + IterativeParsingState n = src; + if (src == IterativeParsingArrayInitialState || src == IterativeParsingElementDelimiterState) + n = IterativeParsingElementState; + else if (src == IterativeParsingKeyValueDelimiterState) + n = IterativeParsingMemberValueState; + // Push current state. + *stack_.template Push(1) = n; + // Initialize and push the member/element count. + *stack_.template Push(1) = 0; + // Call handler + bool hr = (dst == IterativeParsingObjectInitialState) ? handler.StartObject() : handler.StartArray(); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return dst; + } + } + + case IterativeParsingMemberKeyState: + ParseString(is, handler, true); + if (HasParseError()) + return IterativeParsingErrorState; + else + return dst; + + case IterativeParsingKeyValueDelimiterState: + RAPIDJSON_ASSERT(token == ColonToken); + is.Take(); + return dst; + + case IterativeParsingMemberValueState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingElementState: + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return dst; + + case IterativeParsingMemberDelimiterState: + case IterativeParsingElementDelimiterState: + is.Take(); + // Update member/element count. + *stack_.template Top() = *stack_.template Top() + 1; + return dst; + + case IterativeParsingObjectFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingMemberDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorObjectMissName, is.Tell()); + return IterativeParsingErrorState; + } + // Get member count. + SizeType c = *stack_.template Pop(1); + // If the object is not empty, count the last member. + if (src == IterativeParsingMemberValueState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndObject(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + case IterativeParsingArrayFinishState: + { + // Transit from delimiter is only allowed when trailing commas are enabled + if (!(parseFlags & kParseTrailingCommasFlag) && src == IterativeParsingElementDelimiterState) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorValueInvalid, is.Tell()); + return IterativeParsingErrorState; + } + // Get element count. + SizeType c = *stack_.template Pop(1); + // If the array is not empty, count the last element. + if (src == IterativeParsingElementState) + ++c; + // Restore the state. + IterativeParsingState n = static_cast(*stack_.template Pop(1)); + // Transit to Finish state if this is the topmost scope. + if (n == IterativeParsingStartState) + n = IterativeParsingFinishState; + // Call handler + bool hr = handler.EndArray(c); + // On handler short circuits the parsing. + if (!hr) { + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorTermination, is.Tell()); + return IterativeParsingErrorState; + } + else { + is.Take(); + return n; + } + } + + default: + // This branch is for IterativeParsingValueState actually. + // Use `default:` rather than + // `case IterativeParsingValueState:` is for code coverage. + + // The IterativeParsingStartState is not enumerated in this switch-case. + // It is impossible for that case. And it can be caught by following assertion. + + // The IterativeParsingFinishState is not enumerated in this switch-case either. + // It is a "derivative" state which cannot triggered from Predict() directly. + // Therefore it cannot happen here. And it can be caught by following assertion. + RAPIDJSON_ASSERT(dst == IterativeParsingValueState); + + // Must be non-compound value. Or it would be ObjectInitial or ArrayInitial state. + ParseValue(is, handler); + if (HasParseError()) { + return IterativeParsingErrorState; + } + return IterativeParsingFinishState; + } + } + + template + void HandleError(IterativeParsingState src, InputStream& is) { + if (HasParseError()) { + // Error flag has been set. + return; + } + + switch (src) { + case IterativeParsingStartState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentEmpty, is.Tell()); return; + case IterativeParsingFinishState: RAPIDJSON_PARSE_ERROR(kParseErrorDocumentRootNotSingular, is.Tell()); return; + case IterativeParsingObjectInitialState: + case IterativeParsingMemberDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); return; + case IterativeParsingMemberKeyState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); return; + case IterativeParsingMemberValueState: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); return; + case IterativeParsingKeyValueDelimiterState: + case IterativeParsingArrayInitialState: + case IterativeParsingElementDelimiterState: RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); return; + default: RAPIDJSON_ASSERT(src == IterativeParsingElementState); RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); return; + } + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) const { + return s >= IterativeParsingElementDelimiterState; + } + + RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) const { + return s <= IterativeParsingErrorState; + } + + template + ParseResult IterativeParse(InputStream& is, Handler& handler) { + parseResult_.Clear(); + ClearStackOnExit scope(*this); + IterativeParsingState state = IterativeParsingStartState; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + while (is.Peek() != '\0') { + Token t = Tokenize(is.Peek()); + IterativeParsingState n = Predict(state, t); + IterativeParsingState d = Transit(state, t, n, is, handler); + + if (d == IterativeParsingErrorState) { + HandleError(state, is); + break; + } + + state = d; + + // Do not further consume streams if a root JSON has been parsed. + if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState) + break; + + SkipWhitespaceAndComments(is); + RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_); + } + + // Handle the end of file. + if (state != IterativeParsingFinishState) + HandleError(state, is); + + return parseResult_; + } + + static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. + internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. + ParseResult parseResult_; + IterativeParsingState state_; +}; // class GenericReader + +//! Reader with UTF8 encoding and default allocator. +typedef GenericReader, UTF8<> > Reader; + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) || defined(_MSC_VER) +RAPIDJSON_DIAG_POP +#endif + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_READER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/schema.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/schema.h new file mode 100644 index 0000000000..fc39d06c5f --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/schema.h @@ -0,0 +1,2496 @@ +// Tencent is pleased to support the open source community by making RapidJSON available-> +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip-> All rights reserved-> +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License-> You may obtain a copy of the License at +// +// http://opensource->org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied-> See the License for the +// specific language governing permissions and limitations under the License-> + +#ifndef RAPIDJSON_SCHEMA_H_ +#define RAPIDJSON_SCHEMA_H_ + +#include "document.h" +#include "pointer.h" +#include "stringbuffer.h" +#include // abs, floor + +#if !defined(RAPIDJSON_SCHEMA_USE_INTERNALREGEX) +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_INTERNALREGEX 0 +#endif + +#if !RAPIDJSON_SCHEMA_USE_INTERNALREGEX && defined(RAPIDJSON_SCHEMA_USE_STDREGEX) && (__cplusplus >=201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)) +#define RAPIDJSON_SCHEMA_USE_STDREGEX 1 +#else +#define RAPIDJSON_SCHEMA_USE_STDREGEX 0 +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX +#include "internal/regex.h" +#elif RAPIDJSON_SCHEMA_USE_STDREGEX +#include +#endif + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX || RAPIDJSON_SCHEMA_USE_STDREGEX +#define RAPIDJSON_SCHEMA_HAS_REGEX 1 +#else +#define RAPIDJSON_SCHEMA_HAS_REGEX 0 +#endif + +#ifndef RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_VERBOSE 0 +#endif + +#if RAPIDJSON_SCHEMA_VERBOSE +#include "stringbuffer.h" +#endif + +RAPIDJSON_DIAG_PUSH + +#if defined(__GNUC__) +RAPIDJSON_DIAG_OFF(effc++) +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_OFF(weak-vtables) +RAPIDJSON_DIAG_OFF(exit-time-destructors) +RAPIDJSON_DIAG_OFF(c++98-compat-pedantic) +RAPIDJSON_DIAG_OFF(variadic-macros) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Verbose Utilities + +#if RAPIDJSON_SCHEMA_VERBOSE + +namespace internal { + +inline void PrintInvalidKeyword(const char* keyword) { + printf("Fail keyword: %s\n", keyword); +} + +inline void PrintInvalidKeyword(const wchar_t* keyword) { + wprintf(L"Fail keyword: %ls\n", keyword); +} + +inline void PrintInvalidDocument(const char* document) { + printf("Fail document: %s\n\n", document); +} + +inline void PrintInvalidDocument(const wchar_t* document) { + wprintf(L"Fail document: %ls\n\n", document); +} + +inline void PrintValidatorPointers(unsigned depth, const char* s, const char* d) { + printf("S: %*s%s\nD: %*s%s\n\n", depth * 4, " ", s, depth * 4, " ", d); +} + +inline void PrintValidatorPointers(unsigned depth, const wchar_t* s, const wchar_t* d) { + wprintf(L"S: %*ls%ls\nD: %*ls%ls\n\n", depth * 4, L" ", s, depth * 4, L" ", d); +} + +} // namespace internal + +#endif // RAPIDJSON_SCHEMA_VERBOSE + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_INVALID_KEYWORD_RETURN + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) internal::PrintInvalidKeyword(keyword) +#else +#define RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword) +#endif + +#define RAPIDJSON_INVALID_KEYWORD_RETURN(keyword)\ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + context.invalidKeyword = keyword.GetString();\ + RAPIDJSON_INVALID_KEYWORD_VERBOSE(keyword.GetString());\ + return false;\ +RAPIDJSON_MULTILINEMACRO_END + +/////////////////////////////////////////////////////////////////////////////// +// Forward declarations + +template +class GenericSchemaDocument; + +namespace internal { + +template +class Schema; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaValidator + +class ISchemaValidator { +public: + virtual ~ISchemaValidator() {} + virtual bool IsValid() const = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// ISchemaStateFactory + +template +class ISchemaStateFactory { +public: + virtual ~ISchemaStateFactory() {} + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType&) = 0; + virtual void DestroySchemaValidator(ISchemaValidator* validator) = 0; + virtual void* CreateHasher() = 0; + virtual uint64_t GetHashCode(void* hasher) = 0; + virtual void DestroryHasher(void* hasher) = 0; + virtual void* MallocState(size_t size) = 0; + virtual void FreeState(void* p) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// IValidationErrorHandler + +template +class IValidationErrorHandler { +public: + typedef typename SchemaType::Ch Ch; + typedef typename SchemaType::SValue SValue; + + virtual ~IValidationErrorHandler() {} + + virtual void NotMultipleOf(int64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(uint64_t actual, const SValue& expected) = 0; + virtual void NotMultipleOf(double actual, const SValue& expected) = 0; + virtual void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void AboveMaximum(double actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) = 0; + virtual void BelowMinimum(double actual, const SValue& expected, bool exclusive) = 0; + + virtual void TooLong(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void TooShort(const Ch* str, SizeType length, SizeType expected) = 0; + virtual void DoesNotMatch(const Ch* str, SizeType length) = 0; + + virtual void DisallowedItem(SizeType index) = 0; + virtual void TooFewItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooManyItems(SizeType actualCount, SizeType expectedCount) = 0; + virtual void DuplicateItems(SizeType index1, SizeType index2) = 0; + + virtual void TooManyProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void TooFewProperties(SizeType actualCount, SizeType expectedCount) = 0; + virtual void StartMissingProperties() = 0; + virtual void AddMissingProperty(const SValue& name) = 0; + virtual bool EndMissingProperties() = 0; + virtual void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void DisallowedProperty(const Ch* name, SizeType length) = 0; + + virtual void StartDependencyErrors() = 0; + virtual void StartMissingDependentProperties() = 0; + virtual void AddMissingDependentProperty(const SValue& targetName) = 0; + virtual void EndMissingDependentProperties(const SValue& sourceName) = 0; + virtual void AddDependencySchemaError(const SValue& souceName, ISchemaValidator* subvalidator) = 0; + virtual bool EndDependencyErrors() = 0; + + virtual void DisallowedValue() = 0; + virtual void StartDisallowedType() = 0; + virtual void AddExpectedType(const typename SchemaType::ValueType& expectedType) = 0; + virtual void EndDisallowedType(const typename SchemaType::ValueType& actualType) = 0; + virtual void NotAllOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NoneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void NotOneOf(ISchemaValidator** subvalidators, SizeType count) = 0; + virtual void Disallowed() = 0; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Hasher + +// For comparison of compound value +template +class Hasher { +public: + typedef typename Encoding::Ch Ch; + + Hasher(Allocator* allocator = 0, size_t stackCapacity = kDefaultSize) : stack_(allocator, stackCapacity) {} + + bool Null() { return WriteType(kNullType); } + bool Bool(bool b) { return WriteType(b ? kTrueType : kFalseType); } + bool Int(int i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint(unsigned u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Int64(int64_t i) { Number n; n.u.i = i; n.d = static_cast(i); return WriteNumber(n); } + bool Uint64(uint64_t u) { Number n; n.u.u = u; n.d = static_cast(u); return WriteNumber(n); } + bool Double(double d) { + Number n; + if (d < 0) n.u.i = static_cast(d); + else n.u.u = static_cast(d); + n.d = d; + return WriteNumber(n); + } + + bool RawNumber(const Ch* str, SizeType len, bool) { + WriteBuffer(kNumberType, str, len * sizeof(Ch)); + return true; + } + + bool String(const Ch* str, SizeType len, bool) { + WriteBuffer(kStringType, str, len * sizeof(Ch)); + return true; + } + + bool StartObject() { return true; } + bool Key(const Ch* str, SizeType len, bool copy) { return String(str, len, copy); } + bool EndObject(SizeType memberCount) { + uint64_t h = Hash(0, kObjectType); + uint64_t* kv = stack_.template Pop(memberCount * 2); + for (SizeType i = 0; i < memberCount; i++) + h ^= Hash(kv[i * 2], kv[i * 2 + 1]); // Use xor to achieve member order insensitive + *stack_.template Push() = h; + return true; + } + + bool StartArray() { return true; } + bool EndArray(SizeType elementCount) { + uint64_t h = Hash(0, kArrayType); + uint64_t* e = stack_.template Pop(elementCount); + for (SizeType i = 0; i < elementCount; i++) + h = Hash(h, e[i]); // Use hash to achieve element order sensitive + *stack_.template Push() = h; + return true; + } + + bool IsValid() const { return stack_.GetSize() == sizeof(uint64_t); } + + uint64_t GetHashCode() const { + RAPIDJSON_ASSERT(IsValid()); + return *stack_.template Top(); + } + +private: + static const size_t kDefaultSize = 256; + struct Number { + union U { + uint64_t u; + int64_t i; + }u; + double d; + }; + + bool WriteType(Type type) { return WriteBuffer(type, 0, 0); } + + bool WriteNumber(const Number& n) { return WriteBuffer(kNumberType, &n, sizeof(n)); } + + bool WriteBuffer(Type type, const void* data, size_t len) { + // FNV-1a from http://isthe.com/chongo/tech/comp/fnv/ + uint64_t h = Hash(RAPIDJSON_UINT64_C2(0x84222325, 0xcbf29ce4), type); + const unsigned char* d = static_cast(data); + for (size_t i = 0; i < len; i++) + h = Hash(h, d[i]); + *stack_.template Push() = h; + return true; + } + + static uint64_t Hash(uint64_t h, uint64_t d) { + static const uint64_t kPrime = RAPIDJSON_UINT64_C2(0x00000100, 0x000001b3); + h ^= d; + h *= kPrime; + return h; + } + + Stack stack_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidationContext + +template +struct SchemaValidationContext { + typedef Schema SchemaType; + typedef ISchemaStateFactory SchemaValidatorFactoryType; + typedef IValidationErrorHandler ErrorHandlerType; + typedef typename SchemaType::ValueType ValueType; + typedef typename ValueType::Ch Ch; + + enum PatternValidatorType { + kPatternValidatorOnly, + kPatternValidatorWithProperty, + kPatternValidatorWithAdditionalProperty + }; + + SchemaValidationContext(SchemaValidatorFactoryType& f, ErrorHandlerType& eh, const SchemaType* s) : + factory(f), + error_handler(eh), + schema(s), + valueSchema(), + invalidKeyword(), + hasher(), + arrayElementHashCodes(), + validators(), + validatorCount(), + patternPropertiesValidators(), + patternPropertiesValidatorCount(), + patternPropertiesSchemas(), + patternPropertiesSchemaCount(), + valuePatternValidatorType(kPatternValidatorOnly), + propertyExist(), + inArray(false), + valueUniqueness(false), + arrayUniqueness(false) + { + } + + ~SchemaValidationContext() { + if (hasher) + factory.DestroryHasher(hasher); + if (validators) { + for (SizeType i = 0; i < validatorCount; i++) + factory.DestroySchemaValidator(validators[i]); + factory.FreeState(validators); + } + if (patternPropertiesValidators) { + for (SizeType i = 0; i < patternPropertiesValidatorCount; i++) + factory.DestroySchemaValidator(patternPropertiesValidators[i]); + factory.FreeState(patternPropertiesValidators); + } + if (patternPropertiesSchemas) + factory.FreeState(patternPropertiesSchemas); + if (propertyExist) + factory.FreeState(propertyExist); + } + + SchemaValidatorFactoryType& factory; + ErrorHandlerType& error_handler; + const SchemaType* schema; + const SchemaType* valueSchema; + const Ch* invalidKeyword; + void* hasher; // Only validator access + void* arrayElementHashCodes; // Only validator access this + ISchemaValidator** validators; + SizeType validatorCount; + ISchemaValidator** patternPropertiesValidators; + SizeType patternPropertiesValidatorCount; + const SchemaType** patternPropertiesSchemas; + SizeType patternPropertiesSchemaCount; + PatternValidatorType valuePatternValidatorType; + PatternValidatorType objectPatternValidatorType; + SizeType arrayElementIndex; + bool* propertyExist; + bool inArray; + bool valueUniqueness; + bool arrayUniqueness; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Schema + +template +class Schema { +public: + typedef typename SchemaDocumentType::ValueType ValueType; + typedef typename SchemaDocumentType::AllocatorType AllocatorType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef SchemaValidationContext Context; + typedef Schema SchemaType; + typedef GenericValue SValue; + typedef IValidationErrorHandler ErrorHandler; + friend class GenericSchemaDocument; + + Schema(SchemaDocumentType* schemaDocument, const PointerType& p, const ValueType& value, const ValueType& document, AllocatorType* allocator) : + allocator_(allocator), + uri_(schemaDocument->GetURI(), *allocator), + pointer_(p, allocator), + typeless_(schemaDocument->GetTypeless()), + enum_(), + enumCount_(), + not_(), + type_((1 << kTotalSchemaType) - 1), // typeless + validatorCount_(), + notValidatorIndex_(), + properties_(), + additionalPropertiesSchema_(), + patternProperties_(), + patternPropertyCount_(), + propertyCount_(), + minProperties_(), + maxProperties_(SizeType(~0)), + additionalProperties_(true), + hasDependencies_(), + hasRequired_(), + hasSchemaDependencies_(), + additionalItemsSchema_(), + itemsList_(), + itemsTuple_(), + itemsTupleCount_(), + minItems_(), + maxItems_(SizeType(~0)), + additionalItems_(true), + uniqueItems_(false), + pattern_(), + minLength_(0), + maxLength_(~SizeType(0)), + exclusiveMinimum_(false), + exclusiveMaximum_(false), + defaultValueLength_(0) + { + typedef typename ValueType::ConstValueIterator ConstValueIterator; + typedef typename ValueType::ConstMemberIterator ConstMemberIterator; + + if (!value.IsObject()) + return; + + if (const ValueType* v = GetMember(value, GetTypeString())) { + type_ = 0; + if (v->IsString()) + AddType(*v); + else if (v->IsArray()) + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) + AddType(*itr); + } + + if (const ValueType* v = GetMember(value, GetEnumString())) + if (v->IsArray() && v->Size() > 0) { + enum_ = static_cast(allocator_->Malloc(sizeof(uint64_t) * v->Size())); + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr) { + typedef Hasher > EnumHasherType; + char buffer[256u + 24]; + MemoryPoolAllocator<> hasherAllocator(buffer, sizeof(buffer)); + EnumHasherType h(&hasherAllocator, 256); + itr->Accept(h); + enum_[enumCount_++] = h.GetHashCode(); + } + } + + if (schemaDocument) { + AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document); + AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document); + AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document); + } + + if (const ValueType* v = GetMember(value, GetNotString())) { + schemaDocument->CreateSchema(¬_, p.Append(GetNotString(), allocator_), *v, document); + notValidatorIndex_ = validatorCount_; + validatorCount_++; + } + + // Object + + const ValueType* properties = GetMember(value, GetPropertiesString()); + const ValueType* required = GetMember(value, GetRequiredString()); + const ValueType* dependencies = GetMember(value, GetDependenciesString()); + { + // Gather properties from properties/required/dependencies + SValue allProperties(kArrayType); + + if (properties && properties->IsObject()) + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) + AddUniqueElement(allProperties, itr->name); + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) + AddUniqueElement(allProperties, *itr); + + if (dependencies && dependencies->IsObject()) + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + AddUniqueElement(allProperties, itr->name); + if (itr->value.IsArray()) + for (ConstValueIterator i = itr->value.Begin(); i != itr->value.End(); ++i) + if (i->IsString()) + AddUniqueElement(allProperties, *i); + } + + if (allProperties.Size() > 0) { + propertyCount_ = allProperties.Size(); + properties_ = static_cast(allocator_->Malloc(sizeof(Property) * propertyCount_)); + for (SizeType i = 0; i < propertyCount_; i++) { + new (&properties_[i]) Property(); + properties_[i].name = allProperties[i]; + properties_[i].schema = typeless_; + } + } + } + + if (properties && properties->IsObject()) { + PointerType q = p.Append(GetPropertiesString(), allocator_); + for (ConstMemberIterator itr = properties->MemberBegin(); itr != properties->MemberEnd(); ++itr) { + SizeType index; + if (FindPropertyIndex(itr->name, &index)) + schemaDocument->CreateSchema(&properties_[index].schema, q.Append(itr->name, allocator_), itr->value, document); + } + } + + if (const ValueType* v = GetMember(value, GetPatternPropertiesString())) { + PointerType q = p.Append(GetPatternPropertiesString(), allocator_); + patternProperties_ = static_cast(allocator_->Malloc(sizeof(PatternProperty) * v->MemberCount())); + patternPropertyCount_ = 0; + + for (ConstMemberIterator itr = v->MemberBegin(); itr != v->MemberEnd(); ++itr) { + new (&patternProperties_[patternPropertyCount_]) PatternProperty(); + patternProperties_[patternPropertyCount_].pattern = CreatePattern(itr->name); + schemaDocument->CreateSchema(&patternProperties_[patternPropertyCount_].schema, q.Append(itr->name, allocator_), itr->value, document); + patternPropertyCount_++; + } + } + + if (required && required->IsArray()) + for (ConstValueIterator itr = required->Begin(); itr != required->End(); ++itr) + if (itr->IsString()) { + SizeType index; + if (FindPropertyIndex(*itr, &index)) { + properties_[index].required = true; + hasRequired_ = true; + } + } + + if (dependencies && dependencies->IsObject()) { + PointerType q = p.Append(GetDependenciesString(), allocator_); + hasDependencies_ = true; + for (ConstMemberIterator itr = dependencies->MemberBegin(); itr != dependencies->MemberEnd(); ++itr) { + SizeType sourceIndex; + if (FindPropertyIndex(itr->name, &sourceIndex)) { + if (itr->value.IsArray()) { + properties_[sourceIndex].dependencies = static_cast(allocator_->Malloc(sizeof(bool) * propertyCount_)); + std::memset(properties_[sourceIndex].dependencies, 0, sizeof(bool)* propertyCount_); + for (ConstValueIterator targetItr = itr->value.Begin(); targetItr != itr->value.End(); ++targetItr) { + SizeType targetIndex; + if (FindPropertyIndex(*targetItr, &targetIndex)) + properties_[sourceIndex].dependencies[targetIndex] = true; + } + } + else if (itr->value.IsObject()) { + hasSchemaDependencies_ = true; + schemaDocument->CreateSchema(&properties_[sourceIndex].dependenciesSchema, q.Append(itr->name, allocator_), itr->value, document); + properties_[sourceIndex].dependenciesValidatorIndex = validatorCount_; + validatorCount_++; + } + } + } + } + + if (const ValueType* v = GetMember(value, GetAdditionalPropertiesString())) { + if (v->IsBool()) + additionalProperties_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalPropertiesSchema_, p.Append(GetAdditionalPropertiesString(), allocator_), *v, document); + } + + AssignIfExist(minProperties_, value, GetMinPropertiesString()); + AssignIfExist(maxProperties_, value, GetMaxPropertiesString()); + + // Array + if (const ValueType* v = GetMember(value, GetItemsString())) { + PointerType q = p.Append(GetItemsString(), allocator_); + if (v->IsObject()) // List validation + schemaDocument->CreateSchema(&itemsList_, q, *v, document); + else if (v->IsArray()) { // Tuple validation + itemsTuple_ = static_cast(allocator_->Malloc(sizeof(const Schema*) * v->Size())); + SizeType index = 0; + for (ConstValueIterator itr = v->Begin(); itr != v->End(); ++itr, index++) + schemaDocument->CreateSchema(&itemsTuple_[itemsTupleCount_++], q.Append(index, allocator_), *itr, document); + } + } + + AssignIfExist(minItems_, value, GetMinItemsString()); + AssignIfExist(maxItems_, value, GetMaxItemsString()); + + if (const ValueType* v = GetMember(value, GetAdditionalItemsString())) { + if (v->IsBool()) + additionalItems_ = v->GetBool(); + else if (v->IsObject()) + schemaDocument->CreateSchema(&additionalItemsSchema_, p.Append(GetAdditionalItemsString(), allocator_), *v, document); + } + + AssignIfExist(uniqueItems_, value, GetUniqueItemsString()); + + // String + AssignIfExist(minLength_, value, GetMinLengthString()); + AssignIfExist(maxLength_, value, GetMaxLengthString()); + + if (const ValueType* v = GetMember(value, GetPatternString())) + pattern_ = CreatePattern(*v); + + // Number + if (const ValueType* v = GetMember(value, GetMinimumString())) + if (v->IsNumber()) + minimum_.CopyFrom(*v, *allocator_); + + if (const ValueType* v = GetMember(value, GetMaximumString())) + if (v->IsNumber()) + maximum_.CopyFrom(*v, *allocator_); + + AssignIfExist(exclusiveMinimum_, value, GetExclusiveMinimumString()); + AssignIfExist(exclusiveMaximum_, value, GetExclusiveMaximumString()); + + if (const ValueType* v = GetMember(value, GetMultipleOfString())) + if (v->IsNumber() && v->GetDouble() > 0.0) + multipleOf_.CopyFrom(*v, *allocator_); + + // Default + if (const ValueType* v = GetMember(value, GetDefaultValueString())) + if (v->IsString()) + defaultValueLength_ = v->GetStringLength(); + + } + + ~Schema() { + AllocatorType::Free(enum_); + if (properties_) { + for (SizeType i = 0; i < propertyCount_; i++) + properties_[i].~Property(); + AllocatorType::Free(properties_); + } + if (patternProperties_) { + for (SizeType i = 0; i < patternPropertyCount_; i++) + patternProperties_[i].~PatternProperty(); + AllocatorType::Free(patternProperties_); + } + AllocatorType::Free(itemsTuple_); +#if RAPIDJSON_SCHEMA_HAS_REGEX + if (pattern_) { + pattern_->~RegexType(); + AllocatorType::Free(pattern_); + } +#endif + } + + const SValue& GetURI() const { + return uri_; + } + + const PointerType& GetPointer() const { + return pointer_; + } + + bool BeginValue(Context& context) const { + if (context.inArray) { + if (uniqueItems_) + context.valueUniqueness = true; + + if (itemsList_) + context.valueSchema = itemsList_; + else if (itemsTuple_) { + if (context.arrayElementIndex < itemsTupleCount_) + context.valueSchema = itemsTuple_[context.arrayElementIndex]; + else if (additionalItemsSchema_) + context.valueSchema = additionalItemsSchema_; + else if (additionalItems_) + context.valueSchema = typeless_; + else { + context.error_handler.DisallowedItem(context.arrayElementIndex); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetItemsString()); + } + } + else + context.valueSchema = typeless_; + + context.arrayElementIndex++; + } + return true; + } + + RAPIDJSON_FORCEINLINE bool EndValue(Context& context) const { + if (context.patternPropertiesValidatorCount > 0) { + bool otherValid = false; + SizeType count = context.patternPropertiesValidatorCount; + if (context.objectPatternValidatorType != Context::kPatternValidatorOnly) + otherValid = context.patternPropertiesValidators[--count]->IsValid(); + + bool patternValid = true; + for (SizeType i = 0; i < count; i++) + if (!context.patternPropertiesValidators[i]->IsValid()) { + patternValid = false; + break; + } + + if (context.objectPatternValidatorType == Context::kPatternValidatorOnly) { + if (!patternValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (context.objectPatternValidatorType == Context::kPatternValidatorWithProperty) { + if (!patternValid || !otherValid) { + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + else if (!patternValid && !otherValid) { // kPatternValidatorWithAdditionalProperty) + context.error_handler.PropertyViolations(context.patternPropertiesValidators, count + 1); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternPropertiesString()); + } + } + + if (enum_) { + const uint64_t h = context.factory.GetHashCode(context.hasher); + for (SizeType i = 0; i < enumCount_; i++) + if (enum_[i] == h) + goto foundEnum; + context.error_handler.DisallowedValue(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetEnumString()); + foundEnum:; + } + + if (allOf_.schemas) + for (SizeType i = allOf_.begin; i < allOf_.begin + allOf_.count; i++) + if (!context.validators[i]->IsValid()) { + context.error_handler.NotAllOf(&context.validators[allOf_.begin], allOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAllOfString()); + } + + if (anyOf_.schemas) { + for (SizeType i = anyOf_.begin; i < anyOf_.begin + anyOf_.count; i++) + if (context.validators[i]->IsValid()) + goto foundAny; + context.error_handler.NoneOf(&context.validators[anyOf_.begin], anyOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAnyOfString()); + foundAny:; + } + + if (oneOf_.schemas) { + bool oneValid = false; + for (SizeType i = oneOf_.begin; i < oneOf_.begin + oneOf_.count; i++) + if (context.validators[i]->IsValid()) { + if (oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } else + oneValid = true; + } + if (!oneValid) { + context.error_handler.NotOneOf(&context.validators[oneOf_.begin], oneOf_.count); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetOneOfString()); + } + } + + if (not_ && context.validators[notValidatorIndex_]->IsValid()) { + context.error_handler.Disallowed(); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetNotString()); + } + + return true; + } + + bool Null(Context& context) const { + if (!(type_ & (1 << kNullSchemaType))) { + DisallowedType(context, GetNullString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Bool(Context& context, bool) const { + if (!(type_ & (1 << kBooleanSchemaType))) { + DisallowedType(context, GetBooleanString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + return CreateParallelValidator(context); + } + + bool Int(Context& context, int i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint(Context& context, unsigned u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Int64(Context& context, int64_t i) const { + if (!CheckInt(context, i)) + return false; + return CreateParallelValidator(context); + } + + bool Uint64(Context& context, uint64_t u) const { + if (!CheckUint(context, u)) + return false; + return CreateParallelValidator(context); + } + + bool Double(Context& context, double d) const { + if (!(type_ & (1 << kNumberSchemaType))) { + DisallowedType(context, GetNumberString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull() && !CheckDoubleMinimum(context, d)) + return false; + + if (!maximum_.IsNull() && !CheckDoubleMaximum(context, d)) + return false; + + if (!multipleOf_.IsNull() && !CheckDoubleMultipleOf(context, d)) + return false; + + return CreateParallelValidator(context); + } + + bool String(Context& context, const Ch* str, SizeType length, bool) const { + if (!(type_ & (1 << kStringSchemaType))) { + DisallowedType(context, GetStringString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (minLength_ != 0 || maxLength_ != SizeType(~0)) { + SizeType count; + if (internal::CountStringCodePoint(str, length, &count)) { + if (count < minLength_) { + context.error_handler.TooShort(str, length, minLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinLengthString()); + } + if (count > maxLength_) { + context.error_handler.TooLong(str, length, maxLength_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxLengthString()); + } + } + } + + if (pattern_ && !IsPatternMatch(pattern_, str, length)) { + context.error_handler.DoesNotMatch(str, length); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetPatternString()); + } + + return CreateParallelValidator(context); + } + + bool StartObject(Context& context) const { + if (!(type_ & (1 << kObjectSchemaType))) { + DisallowedType(context, GetObjectString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (hasDependencies_ || hasRequired_) { + context.propertyExist = static_cast(context.factory.MallocState(sizeof(bool) * propertyCount_)); + std::memset(context.propertyExist, 0, sizeof(bool) * propertyCount_); + } + + if (patternProperties_) { // pre-allocate schema array + SizeType count = patternPropertyCount_ + 1; // extra for valuePatternValidatorType + context.patternPropertiesSchemas = static_cast(context.factory.MallocState(sizeof(const SchemaType*) * count)); + context.patternPropertiesSchemaCount = 0; + std::memset(context.patternPropertiesSchemas, 0, sizeof(SchemaType*) * count); + } + + return CreateParallelValidator(context); + } + + bool Key(Context& context, const Ch* str, SizeType len, bool) const { + if (patternProperties_) { + context.patternPropertiesSchemaCount = 0; + for (SizeType i = 0; i < patternPropertyCount_; i++) + if (patternProperties_[i].pattern && IsPatternMatch(patternProperties_[i].pattern, str, len)) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = patternProperties_[i].schema; + context.valueSchema = typeless_; + } + } + + SizeType index = 0; + if (FindPropertyIndex(ValueType(str, len).Move(), &index)) { + if (context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = properties_[index].schema; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithProperty; + } + else + context.valueSchema = properties_[index].schema; + + if (context.propertyExist) + context.propertyExist[index] = true; + + return true; + } + + if (additionalPropertiesSchema_) { + if (additionalPropertiesSchema_ && context.patternPropertiesSchemaCount > 0) { + context.patternPropertiesSchemas[context.patternPropertiesSchemaCount++] = additionalPropertiesSchema_; + context.valueSchema = typeless_; + context.valuePatternValidatorType = Context::kPatternValidatorWithAdditionalProperty; + } + else + context.valueSchema = additionalPropertiesSchema_; + return true; + } + else if (additionalProperties_) { + context.valueSchema = typeless_; + return true; + } + + if (context.patternPropertiesSchemaCount == 0) { // patternProperties are not additional properties + context.error_handler.DisallowedProperty(str, len); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetAdditionalPropertiesString()); + } + + return true; + } + + bool EndObject(Context& context, SizeType memberCount) const { + if (hasRequired_) { + context.error_handler.StartMissingProperties(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].required && !context.propertyExist[index]) + if (properties_[index].schema->defaultValueLength_ == 0 ) + context.error_handler.AddMissingProperty(properties_[index].name); + if (context.error_handler.EndMissingProperties()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetRequiredString()); + } + + if (memberCount < minProperties_) { + context.error_handler.TooFewProperties(memberCount, minProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinPropertiesString()); + } + + if (memberCount > maxProperties_) { + context.error_handler.TooManyProperties(memberCount, maxProperties_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxPropertiesString()); + } + + if (hasDependencies_) { + context.error_handler.StartDependencyErrors(); + for (SizeType sourceIndex = 0; sourceIndex < propertyCount_; sourceIndex++) { + const Property& source = properties_[sourceIndex]; + if (context.propertyExist[sourceIndex]) { + if (source.dependencies) { + context.error_handler.StartMissingDependentProperties(); + for (SizeType targetIndex = 0; targetIndex < propertyCount_; targetIndex++) + if (source.dependencies[targetIndex] && !context.propertyExist[targetIndex]) + context.error_handler.AddMissingDependentProperty(properties_[targetIndex].name); + context.error_handler.EndMissingDependentProperties(source.name); + } + else if (source.dependenciesSchema) { + ISchemaValidator* dependenciesValidator = context.validators[source.dependenciesValidatorIndex]; + if (!dependenciesValidator->IsValid()) + context.error_handler.AddDependencySchemaError(source.name, dependenciesValidator); + } + } + } + if (context.error_handler.EndDependencyErrors()) + RAPIDJSON_INVALID_KEYWORD_RETURN(GetDependenciesString()); + } + + return true; + } + + bool StartArray(Context& context) const { + if (!(type_ & (1 << kArraySchemaType))) { + DisallowedType(context, GetArrayString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + context.arrayElementIndex = 0; + context.inArray = true; + + return CreateParallelValidator(context); + } + + bool EndArray(Context& context, SizeType elementCount) const { + context.inArray = false; + + if (elementCount < minItems_) { + context.error_handler.TooFewItems(elementCount, minItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinItemsString()); + } + + if (elementCount > maxItems_) { + context.error_handler.TooManyItems(elementCount, maxItems_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaxItemsString()); + } + + return true; + } + + // Generate functions for string literal according to Ch +#define RAPIDJSON_STRING_(name, ...) \ + static const ValueType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const ValueType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1));\ + return v;\ + } + + RAPIDJSON_STRING_(Null, 'n', 'u', 'l', 'l') + RAPIDJSON_STRING_(Boolean, 'b', 'o', 'o', 'l', 'e', 'a', 'n') + RAPIDJSON_STRING_(Object, 'o', 'b', 'j', 'e', 'c', 't') + RAPIDJSON_STRING_(Array, 'a', 'r', 'r', 'a', 'y') + RAPIDJSON_STRING_(String, 's', 't', 'r', 'i', 'n', 'g') + RAPIDJSON_STRING_(Number, 'n', 'u', 'm', 'b', 'e', 'r') + RAPIDJSON_STRING_(Integer, 'i', 'n', 't', 'e', 'g', 'e', 'r') + RAPIDJSON_STRING_(Type, 't', 'y', 'p', 'e') + RAPIDJSON_STRING_(Enum, 'e', 'n', 'u', 'm') + RAPIDJSON_STRING_(AllOf, 'a', 'l', 'l', 'O', 'f') + RAPIDJSON_STRING_(AnyOf, 'a', 'n', 'y', 'O', 'f') + RAPIDJSON_STRING_(OneOf, 'o', 'n', 'e', 'O', 'f') + RAPIDJSON_STRING_(Not, 'n', 'o', 't') + RAPIDJSON_STRING_(Properties, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Required, 'r', 'e', 'q', 'u', 'i', 'r', 'e', 'd') + RAPIDJSON_STRING_(Dependencies, 'd', 'e', 'p', 'e', 'n', 'd', 'e', 'n', 'c', 'i', 'e', 's') + RAPIDJSON_STRING_(PatternProperties, 'p', 'a', 't', 't', 'e', 'r', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(AdditionalProperties, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MinProperties, 'm', 'i', 'n', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(MaxProperties, 'm', 'a', 'x', 'P', 'r', 'o', 'p', 'e', 'r', 't', 'i', 'e', 's') + RAPIDJSON_STRING_(Items, 'i', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinItems, 'm', 'i', 'n', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MaxItems, 'm', 'a', 'x', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(AdditionalItems, 'a', 'd', 'd', 'i', 't', 'i', 'o', 'n', 'a', 'l', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(UniqueItems, 'u', 'n', 'i', 'q', 'u', 'e', 'I', 't', 'e', 'm', 's') + RAPIDJSON_STRING_(MinLength, 'm', 'i', 'n', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(MaxLength, 'm', 'a', 'x', 'L', 'e', 'n', 'g', 't', 'h') + RAPIDJSON_STRING_(Pattern, 'p', 'a', 't', 't', 'e', 'r', 'n') + RAPIDJSON_STRING_(Minimum, 'm', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(Maximum, 'm', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMinimum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'i', 'n', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(ExclusiveMaximum, 'e', 'x', 'c', 'l', 'u', 's', 'i', 'v', 'e', 'M', 'a', 'x', 'i', 'm', 'u', 'm') + RAPIDJSON_STRING_(MultipleOf, 'm', 'u', 'l', 't', 'i', 'p', 'l', 'e', 'O', 'f') + RAPIDJSON_STRING_(DefaultValue, 'd', 'e', 'f', 'a', 'u', 'l', 't') + +#undef RAPIDJSON_STRING_ + +private: + enum SchemaValueType { + kNullSchemaType, + kBooleanSchemaType, + kObjectSchemaType, + kArraySchemaType, + kStringSchemaType, + kNumberSchemaType, + kIntegerSchemaType, + kTotalSchemaType + }; + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + typedef internal::GenericRegex RegexType; +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + typedef std::basic_regex RegexType; +#else + typedef char RegexType; +#endif + + struct SchemaArray { + SchemaArray() : schemas(), count() {} + ~SchemaArray() { AllocatorType::Free(schemas); } + const SchemaType** schemas; + SizeType begin; // begin index of context.validators + SizeType count; + }; + + template + void AddUniqueElement(V1& a, const V2& v) { + for (typename V1::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr) + if (*itr == v) + return; + V1 c(v, *allocator_); + a.PushBack(c, *allocator_); + } + + static const ValueType* GetMember(const ValueType& value, const ValueType& name) { + typename ValueType::ConstMemberIterator itr = value.FindMember(name); + return itr != value.MemberEnd() ? &(itr->value) : 0; + } + + static void AssignIfExist(bool& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsBool()) + out = v->GetBool(); + } + + static void AssignIfExist(SizeType& out, const ValueType& value, const ValueType& name) { + if (const ValueType* v = GetMember(value, name)) + if (v->IsUint64() && v->GetUint64() <= SizeType(~0)) + out = static_cast(v->GetUint64()); + } + + void AssignIfExist(SchemaArray& out, SchemaDocumentType& schemaDocument, const PointerType& p, const ValueType& value, const ValueType& name, const ValueType& document) { + if (const ValueType* v = GetMember(value, name)) { + if (v->IsArray() && v->Size() > 0) { + PointerType q = p.Append(name, allocator_); + out.count = v->Size(); + out.schemas = static_cast(allocator_->Malloc(out.count * sizeof(const Schema*))); + memset(out.schemas, 0, sizeof(Schema*)* out.count); + for (SizeType i = 0; i < out.count; i++) + schemaDocument.CreateSchema(&out.schemas[i], q.Append(i, allocator_), (*v)[i], document); + out.begin = validatorCount_; + validatorCount_ += out.count; + } + } + } + +#if RAPIDJSON_SCHEMA_USE_INTERNALREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType* r = new (allocator_->Malloc(sizeof(RegexType))) RegexType(value.GetString(), allocator_); + if (!r->IsValid()) { + r->~RegexType(); + AllocatorType::Free(r); + r = 0; + } + return r; + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType) { + GenericRegexSearch rs(*pattern); + return rs.Search(str); + } +#elif RAPIDJSON_SCHEMA_USE_STDREGEX + template + RegexType* CreatePattern(const ValueType& value) { + if (value.IsString()) { + RegexType *r = static_cast(allocator_->Malloc(sizeof(RegexType))); + try { + return new (r) RegexType(value.GetString(), std::size_t(value.GetStringLength()), std::regex_constants::ECMAScript); + } + catch (const std::regex_error&) { + AllocatorType::Free(r); + } + } + return 0; + } + + static bool IsPatternMatch(const RegexType* pattern, const Ch *str, SizeType length) { + std::match_results r; + return std::regex_search(str, str + length, r, *pattern); + } +#else + template + RegexType* CreatePattern(const ValueType&) { return 0; } + + static bool IsPatternMatch(const RegexType*, const Ch *, SizeType) { return true; } +#endif // RAPIDJSON_SCHEMA_USE_STDREGEX + + void AddType(const ValueType& type) { + if (type == GetNullString() ) type_ |= 1 << kNullSchemaType; + else if (type == GetBooleanString()) type_ |= 1 << kBooleanSchemaType; + else if (type == GetObjectString() ) type_ |= 1 << kObjectSchemaType; + else if (type == GetArrayString() ) type_ |= 1 << kArraySchemaType; + else if (type == GetStringString() ) type_ |= 1 << kStringSchemaType; + else if (type == GetIntegerString()) type_ |= 1 << kIntegerSchemaType; + else if (type == GetNumberString() ) type_ |= (1 << kNumberSchemaType) | (1 << kIntegerSchemaType); + } + + bool CreateParallelValidator(Context& context) const { + if (enum_ || context.arrayUniqueness) + context.hasher = context.factory.CreateHasher(); + + if (validatorCount_) { + RAPIDJSON_ASSERT(context.validators == 0); + context.validators = static_cast(context.factory.MallocState(sizeof(ISchemaValidator*) * validatorCount_)); + context.validatorCount = validatorCount_; + + if (allOf_.schemas) + CreateSchemaValidators(context, allOf_); + + if (anyOf_.schemas) + CreateSchemaValidators(context, anyOf_); + + if (oneOf_.schemas) + CreateSchemaValidators(context, oneOf_); + + if (not_) + context.validators[notValidatorIndex_] = context.factory.CreateSchemaValidator(*not_); + + if (hasSchemaDependencies_) { + for (SizeType i = 0; i < propertyCount_; i++) + if (properties_[i].dependenciesSchema) + context.validators[properties_[i].dependenciesValidatorIndex] = context.factory.CreateSchemaValidator(*properties_[i].dependenciesSchema); + } + } + + return true; + } + + void CreateSchemaValidators(Context& context, const SchemaArray& schemas) const { + for (SizeType i = 0; i < schemas.count; i++) + context.validators[schemas.begin + i] = context.factory.CreateSchemaValidator(*schemas.schemas[i]); + } + + // O(n) + bool FindPropertyIndex(const ValueType& name, SizeType* outIndex) const { + SizeType len = name.GetStringLength(); + const Ch* str = name.GetString(); + for (SizeType index = 0; index < propertyCount_; index++) + if (properties_[index].name.GetStringLength() == len && + (std::memcmp(properties_[index].name.GetString(), str, sizeof(Ch) * len) == 0)) + { + *outIndex = index; + return true; + } + return false; + } + + bool CheckInt(Context& context, int64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsInt64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetInt64() : i < minimum_.GetInt64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); // i <= max(int64_t) < minimum.GetUint64() + } + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsInt64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetInt64() : i > maximum_.GetInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsUint64()) { } + /* do nothing */ // i <= max(int64_t) < maximum_.GetUint64() + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (static_cast(i >= 0 ? i : -i) % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckUint(Context& context, uint64_t i) const { + if (!(type_ & ((1 << kIntegerSchemaType) | (1 << kNumberSchemaType)))) { + DisallowedType(context, GetIntegerString()); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetTypeString()); + } + + if (!minimum_.IsNull()) { + if (minimum_.IsUint64()) { + if (exclusiveMinimum_ ? i <= minimum_.GetUint64() : i < minimum_.GetUint64()) { + context.error_handler.BelowMinimum(i, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + } + else if (minimum_.IsInt64()) + /* do nothing */; // i >= 0 > minimum.Getint64() + else if (!CheckDoubleMinimum(context, static_cast(i))) + return false; + } + + if (!maximum_.IsNull()) { + if (maximum_.IsUint64()) { + if (exclusiveMaximum_ ? i >= maximum_.GetUint64() : i > maximum_.GetUint64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + } + else if (maximum_.IsInt64()) { + context.error_handler.AboveMaximum(i, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); // i >= 0 > maximum_ + } + else if (!CheckDoubleMaximum(context, static_cast(i))) + return false; + } + + if (!multipleOf_.IsNull()) { + if (multipleOf_.IsUint64()) { + if (i % multipleOf_.GetUint64() != 0) { + context.error_handler.NotMultipleOf(i, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + } + else if (!CheckDoubleMultipleOf(context, static_cast(i))) + return false; + } + + return true; + } + + bool CheckDoubleMinimum(Context& context, double d) const { + if (exclusiveMinimum_ ? d <= minimum_.GetDouble() : d < minimum_.GetDouble()) { + context.error_handler.BelowMinimum(d, minimum_, exclusiveMinimum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMinimumString()); + } + return true; + } + + bool CheckDoubleMaximum(Context& context, double d) const { + if (exclusiveMaximum_ ? d >= maximum_.GetDouble() : d > maximum_.GetDouble()) { + context.error_handler.AboveMaximum(d, maximum_, exclusiveMaximum_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMaximumString()); + } + return true; + } + + bool CheckDoubleMultipleOf(Context& context, double d) const { + double a = std::abs(d), b = std::abs(multipleOf_.GetDouble()); + double q = std::floor(a / b); + double r = a - q * b; + if (r > 0.0) { + context.error_handler.NotMultipleOf(d, multipleOf_); + RAPIDJSON_INVALID_KEYWORD_RETURN(GetMultipleOfString()); + } + return true; + } + + void DisallowedType(Context& context, const ValueType& actualType) const { + ErrorHandler& eh = context.error_handler; + eh.StartDisallowedType(); + + if (type_ & (1 << kNullSchemaType)) eh.AddExpectedType(GetNullString()); + if (type_ & (1 << kBooleanSchemaType)) eh.AddExpectedType(GetBooleanString()); + if (type_ & (1 << kObjectSchemaType)) eh.AddExpectedType(GetObjectString()); + if (type_ & (1 << kArraySchemaType)) eh.AddExpectedType(GetArrayString()); + if (type_ & (1 << kStringSchemaType)) eh.AddExpectedType(GetStringString()); + + if (type_ & (1 << kNumberSchemaType)) eh.AddExpectedType(GetNumberString()); + else if (type_ & (1 << kIntegerSchemaType)) eh.AddExpectedType(GetIntegerString()); + + eh.EndDisallowedType(actualType); + } + + struct Property { + Property() : schema(), dependenciesSchema(), dependenciesValidatorIndex(), dependencies(), required(false) {} + ~Property() { AllocatorType::Free(dependencies); } + SValue name; + const SchemaType* schema; + const SchemaType* dependenciesSchema; + SizeType dependenciesValidatorIndex; + bool* dependencies; + bool required; + }; + + struct PatternProperty { + PatternProperty() : schema(), pattern() {} + ~PatternProperty() { + if (pattern) { + pattern->~RegexType(); + AllocatorType::Free(pattern); + } + } + const SchemaType* schema; + RegexType* pattern; + }; + + AllocatorType* allocator_; + SValue uri_; + PointerType pointer_; + const SchemaType* typeless_; + uint64_t* enum_; + SizeType enumCount_; + SchemaArray allOf_; + SchemaArray anyOf_; + SchemaArray oneOf_; + const SchemaType* not_; + unsigned type_; // bitmask of kSchemaType + SizeType validatorCount_; + SizeType notValidatorIndex_; + + Property* properties_; + const SchemaType* additionalPropertiesSchema_; + PatternProperty* patternProperties_; + SizeType patternPropertyCount_; + SizeType propertyCount_; + SizeType minProperties_; + SizeType maxProperties_; + bool additionalProperties_; + bool hasDependencies_; + bool hasRequired_; + bool hasSchemaDependencies_; + + const SchemaType* additionalItemsSchema_; + const SchemaType* itemsList_; + const SchemaType** itemsTuple_; + SizeType itemsTupleCount_; + SizeType minItems_; + SizeType maxItems_; + bool additionalItems_; + bool uniqueItems_; + + RegexType* pattern_; + SizeType minLength_; + SizeType maxLength_; + + SValue minimum_; + SValue maximum_; + SValue multipleOf_; + bool exclusiveMinimum_; + bool exclusiveMaximum_; + + SizeType defaultValueLength_; +}; + +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + *documentStack.template Push() = '/'; + char buffer[21]; + size_t length = static_cast((sizeof(SizeType) == 4 ? u32toa(index, buffer) : u64toa(index, buffer)) - buffer); + for (size_t i = 0; i < length; i++) + *documentStack.template Push() = static_cast(buffer[i]); + } +}; + +// Partial specialized version for char to prevent buffer copying. +template +struct TokenHelper { + RAPIDJSON_FORCEINLINE static void AppendIndexToken(Stack& documentStack, SizeType index) { + if (sizeof(SizeType) == 4) { + char *buffer = documentStack.template Push(1 + 10); // '/' + uint + *buffer++ = '/'; + const char* end = internal::u32toa(index, buffer); + documentStack.template Pop(static_cast(10 - (end - buffer))); + } + else { + char *buffer = documentStack.template Push(1 + 20); // '/' + uint64 + *buffer++ = '/'; + const char* end = internal::u64toa(index, buffer); + documentStack.template Pop(static_cast(20 - (end - buffer))); + } + } +}; + +} // namespace internal + +/////////////////////////////////////////////////////////////////////////////// +// IGenericRemoteSchemaDocumentProvider + +template +class IGenericRemoteSchemaDocumentProvider { +public: + typedef typename SchemaDocumentType::Ch Ch; + + virtual ~IGenericRemoteSchemaDocumentProvider() {} + virtual const SchemaDocumentType* GetRemoteDocument(const Ch* uri, SizeType length) = 0; +}; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaDocument + +//! JSON schema document. +/*! + A JSON schema document is a compiled version of a JSON schema. + It is basically a tree of internal::Schema. + + \note This is an immutable class (i.e. its instance cannot be modified after construction). + \tparam ValueT Type of JSON value (e.g. \c Value ), which also determine the encoding. + \tparam Allocator Allocator type for allocating memory of this document. +*/ +template +class GenericSchemaDocument { +public: + typedef ValueT ValueType; + typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProviderType; + typedef Allocator AllocatorType; + typedef typename ValueType::EncodingType EncodingType; + typedef typename EncodingType::Ch Ch; + typedef internal::Schema SchemaType; + typedef GenericPointer PointerType; + typedef GenericValue URIType; + friend class internal::Schema; + template + friend class GenericSchemaValidator; + + //! Constructor. + /*! + Compile a JSON document into schema document. + + \param document A JSON document as source. + \param uri The base URI of this schema document for purposes of violation reporting. + \param uriLength Length of \c name, in code points. + \param remoteProvider An optional remote schema document provider for resolving remote reference. Can be null. + \param allocator An optional allocator instance for allocating memory. Can be null. + */ + explicit GenericSchemaDocument(const ValueType& document, const Ch* uri = 0, SizeType uriLength = 0, + IRemoteSchemaDocumentProviderType* remoteProvider = 0, Allocator* allocator = 0) : + remoteProvider_(remoteProvider), + allocator_(allocator), + ownAllocator_(), + root_(), + typeless_(), + schemaMap_(allocator, kInitialSchemaMapSize), + schemaRef_(allocator, kInitialSchemaRefSize) + { + if (!allocator_) + ownAllocator_ = allocator_ = RAPIDJSON_NEW(Allocator)(); + + Ch noUri[1] = {0}; + uri_.SetString(uri ? uri : noUri, uriLength, *allocator_); + + typeless_ = static_cast(allocator_->Malloc(sizeof(SchemaType))); + new (typeless_) SchemaType(this, PointerType(), ValueType(kObjectType).Move(), ValueType(kObjectType).Move(), allocator_); + + // Generate root schema, it will call CreateSchema() to create sub-schemas, + // And call AddRefSchema() if there are $ref. + CreateSchemaRecursive(&root_, PointerType(), document, document); + + // Resolve $ref + while (!schemaRef_.Empty()) { + SchemaRefEntry* refEntry = schemaRef_.template Pop(1); + if (const SchemaType* s = GetSchema(refEntry->target)) { + if (refEntry->schema) + *refEntry->schema = s; + + // Create entry in map if not exist + if (!GetSchema(refEntry->source)) { + new (schemaMap_.template Push()) SchemaEntry(refEntry->source, const_cast(s), false, allocator_); + } + } + else if (refEntry->schema) + *refEntry->schema = typeless_; + + refEntry->~SchemaRefEntry(); + } + + RAPIDJSON_ASSERT(root_ != 0); + + schemaRef_.ShrinkToFit(); // Deallocate all memory for ref + } + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + //! Move constructor in C++11 + GenericSchemaDocument(GenericSchemaDocument&& rhs) RAPIDJSON_NOEXCEPT : + remoteProvider_(rhs.remoteProvider_), + allocator_(rhs.allocator_), + ownAllocator_(rhs.ownAllocator_), + root_(rhs.root_), + typeless_(rhs.typeless_), + schemaMap_(std::move(rhs.schemaMap_)), + schemaRef_(std::move(rhs.schemaRef_)), + uri_(std::move(rhs.uri_)) + { + rhs.remoteProvider_ = 0; + rhs.allocator_ = 0; + rhs.ownAllocator_ = 0; + rhs.typeless_ = 0; + } +#endif + + //! Destructor + ~GenericSchemaDocument() { + while (!schemaMap_.Empty()) + schemaMap_.template Pop(1)->~SchemaEntry(); + + if (typeless_) { + typeless_->~SchemaType(); + Allocator::Free(typeless_); + } + + RAPIDJSON_DELETE(ownAllocator_); + } + + const URIType& GetURI() const { return uri_; } + + //! Get the root schema. + const SchemaType& GetRoot() const { return *root_; } + +private: + //! Prohibit copying + GenericSchemaDocument(const GenericSchemaDocument&); + //! Prohibit assignment + GenericSchemaDocument& operator=(const GenericSchemaDocument&); + + struct SchemaRefEntry { + SchemaRefEntry(const PointerType& s, const PointerType& t, const SchemaType** outSchema, Allocator *allocator) : source(s, allocator), target(t, allocator), schema(outSchema) {} + PointerType source; + PointerType target; + const SchemaType** schema; + }; + + struct SchemaEntry { + SchemaEntry(const PointerType& p, SchemaType* s, bool o, Allocator* allocator) : pointer(p, allocator), schema(s), owned(o) {} + ~SchemaEntry() { + if (owned) { + schema->~SchemaType(); + Allocator::Free(schema); + } + } + PointerType pointer; + SchemaType* schema; + bool owned; + }; + + void CreateSchemaRecursive(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + if (schema) + *schema = typeless_; + + if (v.GetType() == kObjectType) { + const SchemaType* s = GetSchema(pointer); + if (!s) + CreateSchema(schema, pointer, v, document); + + for (typename ValueType::ConstMemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) + CreateSchemaRecursive(0, pointer.Append(itr->name, allocator_), itr->value, document); + } + else if (v.GetType() == kArrayType) + for (SizeType i = 0; i < v.Size(); i++) + CreateSchemaRecursive(0, pointer.Append(i, allocator_), v[i], document); + } + + void CreateSchema(const SchemaType** schema, const PointerType& pointer, const ValueType& v, const ValueType& document) { + RAPIDJSON_ASSERT(pointer.IsValid()); + if (v.IsObject()) { + if (!HandleRefSchema(pointer, schema, v, document)) { + SchemaType* s = new (allocator_->Malloc(sizeof(SchemaType))) SchemaType(this, pointer, v, document, allocator_); + new (schemaMap_.template Push()) SchemaEntry(pointer, s, true, allocator_); + if (schema) + *schema = s; + } + } + } + + bool HandleRefSchema(const PointerType& source, const SchemaType** schema, const ValueType& v, const ValueType& document) { + static const Ch kRefString[] = { '$', 'r', 'e', 'f', '\0' }; + static const ValueType kRefValue(kRefString, 4); + + typename ValueType::ConstMemberIterator itr = v.FindMember(kRefValue); + if (itr == v.MemberEnd()) + return false; + + if (itr->value.IsString()) { + SizeType len = itr->value.GetStringLength(); + if (len > 0) { + const Ch* s = itr->value.GetString(); + SizeType i = 0; + while (i < len && s[i] != '#') // Find the first # + i++; + + if (i > 0) { // Remote reference, resolve immediately + if (remoteProvider_) { + if (const GenericSchemaDocument* remoteDocument = remoteProvider_->GetRemoteDocument(s, i)) { + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const SchemaType* sc = remoteDocument->GetSchema(pointer)) { + if (schema) + *schema = sc; + new (schemaMap_.template Push()) SchemaEntry(source, const_cast(sc), false, allocator_); + return true; + } + } + } + } + } + else if (s[i] == '#') { // Local reference, defer resolution + PointerType pointer(&s[i], len - i, allocator_); + if (pointer.IsValid()) { + if (const ValueType* nv = pointer.Get(document)) + if (HandleRefSchema(source, schema, *nv, document)) + return true; + + new (schemaRef_.template Push()) SchemaRefEntry(source, pointer, schema, allocator_); + return true; + } + } + } + } + return false; + } + + const SchemaType* GetSchema(const PointerType& pointer) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (pointer == target->pointer) + return target->schema; + return 0; + } + + PointerType GetPointer(const SchemaType* schema) const { + for (const SchemaEntry* target = schemaMap_.template Bottom(); target != schemaMap_.template End(); ++target) + if (schema == target->schema) + return target->pointer; + return PointerType(); + } + + const SchemaType* GetTypeless() const { return typeless_; } + + static const size_t kInitialSchemaMapSize = 64; + static const size_t kInitialSchemaRefSize = 64; + + IRemoteSchemaDocumentProviderType* remoteProvider_; + Allocator *allocator_; + Allocator *ownAllocator_; + const SchemaType* root_; //!< Root schema. + SchemaType* typeless_; + internal::Stack schemaMap_; // Stores created Pointer -> Schemas + internal::Stack schemaRef_; // Stores Pointer from $ref and schema which holds the $ref + URIType uri_; +}; + +//! GenericSchemaDocument using Value type. +typedef GenericSchemaDocument SchemaDocument; +//! IGenericRemoteSchemaDocumentProvider using SchemaDocument. +typedef IGenericRemoteSchemaDocumentProvider IRemoteSchemaDocumentProvider; + +/////////////////////////////////////////////////////////////////////////////// +// GenericSchemaValidator + +//! JSON Schema Validator. +/*! + A SAX style JSON schema validator. + It uses a \c GenericSchemaDocument to validate SAX events. + It delegates the incoming SAX events to an output handler. + The default output handler does nothing. + It can be reused multiple times by calling \c Reset(). + + \tparam SchemaDocumentType Type of schema document. + \tparam OutputHandler Type of output handler. Default handler does nothing. + \tparam StateAllocator Allocator for storing the internal validation states. +*/ +template < + typename SchemaDocumentType, + typename OutputHandler = BaseReaderHandler, + typename StateAllocator = CrtAllocator> +class GenericSchemaValidator : + public internal::ISchemaStateFactory, + public internal::ISchemaValidator, + public internal::IValidationErrorHandler +{ +public: + typedef typename SchemaDocumentType::SchemaType SchemaType; + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename SchemaType::EncodingType EncodingType; + typedef typename SchemaType::SValue SValue; + typedef typename EncodingType::Ch Ch; + typedef GenericStringRef StringRefType; + typedef GenericValue ValueType; + + //! Constructor without output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Constructor with output handler. + /*! + \param schemaDocument The schema document to conform to. + \param allocator Optional allocator for storing internal validation states. + \param schemaStackCapacity Optional initial capacity of schema path stack. + \param documentStackCapacity Optional initial capacity of document path stack. + */ + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + OutputHandler& outputHandler, + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(schemaDocument.GetRoot()), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(&outputHandler), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(0) +#endif + { + } + + //! Destructor. + ~GenericSchemaValidator() { + Reset(); + RAPIDJSON_DELETE(ownStateAllocator_); + } + + //! Reset the internal states. + void Reset() { + while (!schemaStack_.Empty()) + PopSchema(); + documentStack_.Clear(); + error_.SetObject(); + currentError_.SetNull(); + missingDependents_.SetNull(); + valid_ = true; + } + + //! Checks whether the current state is valid. + // Implementation of ISchemaValidator + virtual bool IsValid() const { return valid_; } + + //! Gets the error object. + ValueType& GetError() { return error_; } + const ValueType& GetError() const { return error_; } + + //! Gets the JSON pointer pointed to the invalid schema. + PointerType GetInvalidSchemaPointer() const { + return schemaStack_.Empty() ? PointerType() : CurrentSchema().GetPointer(); + } + + //! Gets the keyword of invalid schema. + const Ch* GetInvalidSchemaKeyword() const { + return schemaStack_.Empty() ? 0 : CurrentContext().invalidKeyword; + } + + //! Gets the JSON pointer pointed to the invalid value. + PointerType GetInvalidDocumentPointer() const { + if (documentStack_.Empty()) { + return PointerType(); + } + else { + return PointerType(documentStack_.template Bottom(), documentStack_.GetSize() / sizeof(Ch)); + } + } + + void NotMultipleOf(int64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(uint64_t actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void NotMultipleOf(double actual, const SValue& expected) { + AddNumberError(SchemaType::GetMultipleOfString(), ValueType(actual).Move(), expected); + } + void AboveMaximum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void AboveMaximum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMaximumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMaximumString : 0); + } + void BelowMinimum(int64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(uint64_t actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + void BelowMinimum(double actual, const SValue& expected, bool exclusive) { + AddNumberError(SchemaType::GetMinimumString(), ValueType(actual).Move(), expected, + exclusive ? &SchemaType::GetExclusiveMinimumString : 0); + } + + void TooLong(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMaxLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void TooShort(const Ch* str, SizeType length, SizeType expected) { + AddNumberError(SchemaType::GetMinLengthString(), + ValueType(str, length, GetStateAllocator()).Move(), SValue(expected).Move()); + } + void DoesNotMatch(const Ch* str, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), ValueType(str, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetPatternString()); + } + + void DisallowedItem(SizeType index) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(index).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalItemsString(), true); + } + void TooFewItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooManyItems(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxItemsString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void DuplicateItems(SizeType index1, SizeType index2) { + ValueType duplicates(kArrayType); + duplicates.PushBack(index1, GetStateAllocator()); + duplicates.PushBack(index2, GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetDuplicatesString(), duplicates, GetStateAllocator()); + AddCurrentError(SchemaType::GetUniqueItemsString(), true); + } + + void TooManyProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMaxPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void TooFewProperties(SizeType actualCount, SizeType expectedCount) { + AddNumberError(SchemaType::GetMinPropertiesString(), + ValueType(actualCount).Move(), SValue(expectedCount).Move()); + } + void StartMissingProperties() { + currentError_.SetArray(); + } + void AddMissingProperty(const SValue& name) { + currentError_.PushBack(ValueType(name, GetStateAllocator()).Move(), GetStateAllocator()); + } + bool EndMissingProperties() { + if (currentError_.Empty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetMissingString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetRequiredString()); + return true; + } + void PropertyViolations(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) + MergeError(static_cast(subvalidators[i])->GetError()); + } + void DisallowedProperty(const Ch* name, SizeType length) { + currentError_.SetObject(); + currentError_.AddMember(GetDisallowedString(), ValueType(name, length, GetStateAllocator()).Move(), GetStateAllocator()); + AddCurrentError(SchemaType::GetAdditionalPropertiesString(), true); + } + + void StartDependencyErrors() { + currentError_.SetObject(); + } + void StartMissingDependentProperties() { + missingDependents_.SetArray(); + } + void AddMissingDependentProperty(const SValue& targetName) { + missingDependents_.PushBack(ValueType(targetName, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndMissingDependentProperties(const SValue& sourceName) { + if (!missingDependents_.Empty()) + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + missingDependents_, GetStateAllocator()); + } + void AddDependencySchemaError(const SValue& sourceName, ISchemaValidator* subvalidator) { + currentError_.AddMember(ValueType(sourceName, GetStateAllocator()).Move(), + static_cast(subvalidator)->GetError(), GetStateAllocator()); + } + bool EndDependencyErrors() { + if (currentError_.ObjectEmpty()) + return false; + ValueType error(kObjectType); + error.AddMember(GetErrorsString(), currentError_, GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetDependenciesString()); + return true; + } + + void DisallowedValue() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetEnumString()); + } + void StartDisallowedType() { + currentError_.SetArray(); + } + void AddExpectedType(const typename SchemaType::ValueType& expectedType) { + currentError_.PushBack(ValueType(expectedType, GetStateAllocator()).Move(), GetStateAllocator()); + } + void EndDisallowedType(const typename SchemaType::ValueType& actualType) { + ValueType error(kObjectType); + error.AddMember(GetExpectedString(), currentError_, GetStateAllocator()); + error.AddMember(GetActualString(), ValueType(actualType, GetStateAllocator()).Move(), GetStateAllocator()); + currentError_ = error; + AddCurrentError(SchemaType::GetTypeString()); + } + void NotAllOf(ISchemaValidator** subvalidators, SizeType count) { + for (SizeType i = 0; i < count; ++i) { + MergeError(static_cast(subvalidators[i])->GetError()); + } + } + void NoneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetAnyOfString(), subvalidators, count); + } + void NotOneOf(ISchemaValidator** subvalidators, SizeType count) { + AddErrorArray(SchemaType::GetOneOfString(), subvalidators, count); + } + void Disallowed() { + currentError_.SetObject(); + AddCurrentError(SchemaType::GetNotString()); + } + +#define RAPIDJSON_STRING_(name, ...) \ + static const StringRefType& Get##name##String() {\ + static const Ch s[] = { __VA_ARGS__, '\0' };\ + static const StringRefType v(s, static_cast(sizeof(s) / sizeof(Ch) - 1)); \ + return v;\ + } + + RAPIDJSON_STRING_(InstanceRef, 'i', 'n', 's', 't', 'a', 'n', 'c', 'e', 'R', 'e', 'f') + RAPIDJSON_STRING_(SchemaRef, 's', 'c', 'h', 'e', 'm', 'a', 'R', 'e', 'f') + RAPIDJSON_STRING_(Expected, 'e', 'x', 'p', 'e', 'c', 't', 'e', 'd') + RAPIDJSON_STRING_(Actual, 'a', 'c', 't', 'u', 'a', 'l') + RAPIDJSON_STRING_(Disallowed, 'd', 'i', 's', 'a', 'l', 'l', 'o', 'w', 'e', 'd') + RAPIDJSON_STRING_(Missing, 'm', 'i', 's', 's', 'i', 'n', 'g') + RAPIDJSON_STRING_(Errors, 'e', 'r', 'r', 'o', 'r', 's') + RAPIDJSON_STRING_(Duplicates, 'd', 'u', 'p', 'l', 'i', 'c', 'a', 't', 'e', 's') + +#undef RAPIDJSON_STRING_ + +#if RAPIDJSON_SCHEMA_VERBOSE +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() \ +RAPIDJSON_MULTILINEMACRO_BEGIN\ + *documentStack_.template Push() = '\0';\ + documentStack_.template Pop(1);\ + internal::PrintInvalidDocument(documentStack_.template Bottom());\ +RAPIDJSON_MULTILINEMACRO_END +#else +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_() +#endif + +#define RAPIDJSON_SCHEMA_HANDLE_BEGIN_(method, arg1)\ + if (!valid_) return false; \ + if (!BeginValue() || !CurrentSchema().method arg1) {\ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_();\ + return valid_ = false;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2)\ + for (Context* context = schemaStack_.template Bottom(); context != schemaStack_.template End(); context++) {\ + if (context->hasher)\ + static_cast(context->hasher)->method arg2;\ + if (context->validators)\ + for (SizeType i_ = 0; i_ < context->validatorCount; i_++)\ + static_cast(context->validators[i_])->method arg2;\ + if (context->patternPropertiesValidators)\ + for (SizeType i_ = 0; i_ < context->patternPropertiesValidatorCount; i_++)\ + static_cast(context->patternPropertiesValidators[i_])->method arg2;\ + } + +#define RAPIDJSON_SCHEMA_HANDLE_END_(method, arg2)\ + return valid_ = EndValue() && (!outputHandler_ || outputHandler_->method arg2) + +#define RAPIDJSON_SCHEMA_HANDLE_VALUE_(method, arg1, arg2) \ + RAPIDJSON_SCHEMA_HANDLE_BEGIN_ (method, arg1);\ + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(method, arg2);\ + RAPIDJSON_SCHEMA_HANDLE_END_ (method, arg2) + + bool Null() { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Null, (CurrentContext()), ( )); } + bool Bool(bool b) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Bool, (CurrentContext(), b), (b)); } + bool Int(int i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int, (CurrentContext(), i), (i)); } + bool Uint(unsigned u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint, (CurrentContext(), u), (u)); } + bool Int64(int64_t i) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Int64, (CurrentContext(), i), (i)); } + bool Uint64(uint64_t u) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Uint64, (CurrentContext(), u), (u)); } + bool Double(double d) { RAPIDJSON_SCHEMA_HANDLE_VALUE_(Double, (CurrentContext(), d), (d)); } + bool RawNumber(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + bool String(const Ch* str, SizeType length, bool copy) + { RAPIDJSON_SCHEMA_HANDLE_VALUE_(String, (CurrentContext(), str, length, copy), (str, length, copy)); } + + bool StartObject() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartObject, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartObject, ()); + return valid_ = !outputHandler_ || outputHandler_->StartObject(); + } + + bool Key(const Ch* str, SizeType len, bool copy) { + if (!valid_) return false; + AppendToken(str, len); + if (!CurrentSchema().Key(CurrentContext(), str, len, copy)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(Key, (str, len, copy)); + return valid_ = !outputHandler_ || outputHandler_->Key(str, len, copy); + } + + bool EndObject(SizeType memberCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndObject, (memberCount)); + if (!CurrentSchema().EndObject(CurrentContext(), memberCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndObject, (memberCount)); + } + + bool StartArray() { + RAPIDJSON_SCHEMA_HANDLE_BEGIN_(StartArray, (CurrentContext())); + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(StartArray, ()); + return valid_ = !outputHandler_ || outputHandler_->StartArray(); + } + + bool EndArray(SizeType elementCount) { + if (!valid_) return false; + RAPIDJSON_SCHEMA_HANDLE_PARALLEL_(EndArray, (elementCount)); + if (!CurrentSchema().EndArray(CurrentContext(), elementCount)) return valid_ = false; + RAPIDJSON_SCHEMA_HANDLE_END_(EndArray, (elementCount)); + } + +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_VERBOSE_ +#undef RAPIDJSON_SCHEMA_HANDLE_BEGIN_ +#undef RAPIDJSON_SCHEMA_HANDLE_PARALLEL_ +#undef RAPIDJSON_SCHEMA_HANDLE_VALUE_ + + // Implementation of ISchemaStateFactory + virtual ISchemaValidator* CreateSchemaValidator(const SchemaType& root) { + return new (GetStateAllocator().Malloc(sizeof(GenericSchemaValidator))) GenericSchemaValidator(*schemaDocument_, root, documentStack_.template Bottom(), documentStack_.GetSize(), +#if RAPIDJSON_SCHEMA_VERBOSE + depth_ + 1, +#endif + &GetStateAllocator()); + } + + virtual void DestroySchemaValidator(ISchemaValidator* validator) { + GenericSchemaValidator* v = static_cast(validator); + v->~GenericSchemaValidator(); + StateAllocator::Free(v); + } + + virtual void* CreateHasher() { + return new (GetStateAllocator().Malloc(sizeof(HasherType))) HasherType(&GetStateAllocator()); + } + + virtual uint64_t GetHashCode(void* hasher) { + return static_cast(hasher)->GetHashCode(); + } + + virtual void DestroryHasher(void* hasher) { + HasherType* h = static_cast(hasher); + h->~HasherType(); + StateAllocator::Free(h); + } + + virtual void* MallocState(size_t size) { + return GetStateAllocator().Malloc(size); + } + + virtual void FreeState(void* p) { + StateAllocator::Free(p); + } + +private: + typedef typename SchemaType::Context Context; + typedef GenericValue, StateAllocator> HashCodeArray; + typedef internal::Hasher HasherType; + + GenericSchemaValidator( + const SchemaDocumentType& schemaDocument, + const SchemaType& root, + const char* basePath, size_t basePathSize, +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth, +#endif + StateAllocator* allocator = 0, + size_t schemaStackCapacity = kDefaultSchemaStackCapacity, + size_t documentStackCapacity = kDefaultDocumentStackCapacity) + : + schemaDocument_(&schemaDocument), + root_(root), + stateAllocator_(allocator), + ownStateAllocator_(0), + schemaStack_(allocator, schemaStackCapacity), + documentStack_(allocator, documentStackCapacity), + outputHandler_(0), + error_(kObjectType), + currentError_(), + missingDependents_(), + valid_(true) +#if RAPIDJSON_SCHEMA_VERBOSE + , depth_(depth) +#endif + { + if (basePath && basePathSize) + memcpy(documentStack_.template Push(basePathSize), basePath, basePathSize); + } + + StateAllocator& GetStateAllocator() { + if (!stateAllocator_) + stateAllocator_ = ownStateAllocator_ = RAPIDJSON_NEW(StateAllocator)(); + return *stateAllocator_; + } + + bool BeginValue() { + if (schemaStack_.Empty()) + PushSchema(root_); + else { + if (CurrentContext().inArray) + internal::TokenHelper, Ch>::AppendIndexToken(documentStack_, CurrentContext().arrayElementIndex); + + if (!CurrentSchema().BeginValue(CurrentContext())) + return false; + + SizeType count = CurrentContext().patternPropertiesSchemaCount; + const SchemaType** sa = CurrentContext().patternPropertiesSchemas; + typename Context::PatternValidatorType patternValidatorType = CurrentContext().valuePatternValidatorType; + bool valueUniqueness = CurrentContext().valueUniqueness; + RAPIDJSON_ASSERT(CurrentContext().valueSchema); + PushSchema(*CurrentContext().valueSchema); + + if (count > 0) { + CurrentContext().objectPatternValidatorType = patternValidatorType; + ISchemaValidator**& va = CurrentContext().patternPropertiesValidators; + SizeType& validatorCount = CurrentContext().patternPropertiesValidatorCount; + va = static_cast(MallocState(sizeof(ISchemaValidator*) * count)); + for (SizeType i = 0; i < count; i++) + va[validatorCount++] = CreateSchemaValidator(*sa[i]); + } + + CurrentContext().arrayUniqueness = valueUniqueness; + } + return true; + } + + bool EndValue() { + if (!CurrentSchema().EndValue(CurrentContext())) + return false; + +#if RAPIDJSON_SCHEMA_VERBOSE + GenericStringBuffer sb; + schemaDocument_->GetPointer(&CurrentSchema()).Stringify(sb); + + *documentStack_.template Push() = '\0'; + documentStack_.template Pop(1); + internal::PrintValidatorPointers(depth_, sb.GetString(), documentStack_.template Bottom()); +#endif + + uint64_t h = CurrentContext().arrayUniqueness ? static_cast(CurrentContext().hasher)->GetHashCode() : 0; + + PopSchema(); + + if (!schemaStack_.Empty()) { + Context& context = CurrentContext(); + if (context.valueUniqueness) { + HashCodeArray* a = static_cast(context.arrayElementHashCodes); + if (!a) + CurrentContext().arrayElementHashCodes = a = new (GetStateAllocator().Malloc(sizeof(HashCodeArray))) HashCodeArray(kArrayType); + for (typename HashCodeArray::ConstValueIterator itr = a->Begin(); itr != a->End(); ++itr) + if (itr->GetUint64() == h) { + DuplicateItems(static_cast(itr - a->Begin()), a->Size()); + RAPIDJSON_INVALID_KEYWORD_RETURN(SchemaType::GetUniqueItemsString()); + } + a->PushBack(h, GetStateAllocator()); + } + } + + // Remove the last token of document pointer + while (!documentStack_.Empty() && *documentStack_.template Pop(1) != '/') + ; + + return true; + } + + void AppendToken(const Ch* str, SizeType len) { + documentStack_.template Reserve(1 + len * 2); // worst case all characters are escaped as two characters + *documentStack_.template PushUnsafe() = '/'; + for (SizeType i = 0; i < len; i++) { + if (str[i] == '~') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '0'; + } + else if (str[i] == '/') { + *documentStack_.template PushUnsafe() = '~'; + *documentStack_.template PushUnsafe() = '1'; + } + else + *documentStack_.template PushUnsafe() = str[i]; + } + } + + RAPIDJSON_FORCEINLINE void PushSchema(const SchemaType& schema) { new (schemaStack_.template Push()) Context(*this, *this, &schema); } + + RAPIDJSON_FORCEINLINE void PopSchema() { + Context* c = schemaStack_.template Pop(1); + if (HashCodeArray* a = static_cast(c->arrayElementHashCodes)) { + a->~HashCodeArray(); + StateAllocator::Free(a); + } + c->~Context(); + } + + void AddErrorLocation(ValueType& result, bool parent) { + GenericStringBuffer sb; + PointerType instancePointer = GetInvalidDocumentPointer(); + ((parent && instancePointer.GetTokenCount() > 0) + ? PointerType(instancePointer.GetTokens(), instancePointer.GetTokenCount() - 1) + : instancePointer).StringifyUriFragment(sb); + ValueType instanceRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetInstanceRefString(), instanceRef, GetStateAllocator()); + sb.Clear(); + memcpy(sb.Push(CurrentSchema().GetURI().GetStringLength()), + CurrentSchema().GetURI().GetString(), + CurrentSchema().GetURI().GetStringLength() * sizeof(Ch)); + GetInvalidSchemaPointer().StringifyUriFragment(sb); + ValueType schemaRef(sb.GetString(), static_cast(sb.GetSize() / sizeof(Ch)), + GetStateAllocator()); + result.AddMember(GetSchemaRefString(), schemaRef, GetStateAllocator()); + } + + void AddError(ValueType& keyword, ValueType& error) { + typename ValueType::MemberIterator member = error_.FindMember(keyword); + if (member == error_.MemberEnd()) + error_.AddMember(keyword, error, GetStateAllocator()); + else { + if (member->value.IsObject()) { + ValueType errors(kArrayType); + errors.PushBack(member->value, GetStateAllocator()); + member->value = errors; + } + member->value.PushBack(error, GetStateAllocator()); + } + } + + void AddCurrentError(const typename SchemaType::ValueType& keyword, bool parent = false) { + AddErrorLocation(currentError_, parent); + AddError(ValueType(keyword, GetStateAllocator(), false).Move(), currentError_); + } + + void MergeError(ValueType& other) { + for (typename ValueType::MemberIterator it = other.MemberBegin(), end = other.MemberEnd(); it != end; ++it) { + AddError(it->name, it->value); + } + } + + void AddNumberError(const typename SchemaType::ValueType& keyword, ValueType& actual, const SValue& expected, + const typename SchemaType::ValueType& (*exclusive)() = 0) { + currentError_.SetObject(); + currentError_.AddMember(GetActualString(), actual, GetStateAllocator()); + currentError_.AddMember(GetExpectedString(), ValueType(expected, GetStateAllocator()).Move(), GetStateAllocator()); + if (exclusive) + currentError_.AddMember(ValueType(exclusive(), GetStateAllocator()).Move(), true, GetStateAllocator()); + AddCurrentError(keyword); + } + + void AddErrorArray(const typename SchemaType::ValueType& keyword, + ISchemaValidator** subvalidators, SizeType count) { + ValueType errors(kArrayType); + for (SizeType i = 0; i < count; ++i) + errors.PushBack(static_cast(subvalidators[i])->GetError(), GetStateAllocator()); + currentError_.SetObject(); + currentError_.AddMember(GetErrorsString(), errors, GetStateAllocator()); + AddCurrentError(keyword); + } + + const SchemaType& CurrentSchema() const { return *schemaStack_.template Top()->schema; } + Context& CurrentContext() { return *schemaStack_.template Top(); } + const Context& CurrentContext() const { return *schemaStack_.template Top(); } + + static const size_t kDefaultSchemaStackCapacity = 1024; + static const size_t kDefaultDocumentStackCapacity = 256; + const SchemaDocumentType* schemaDocument_; + const SchemaType& root_; + StateAllocator* stateAllocator_; + StateAllocator* ownStateAllocator_; + internal::Stack schemaStack_; //!< stack to store the current path of schema (BaseSchemaType *) + internal::Stack documentStack_; //!< stack to store the current path of validating document (Ch) + OutputHandler* outputHandler_; + ValueType error_; + ValueType currentError_; + ValueType missingDependents_; + bool valid_; +#if RAPIDJSON_SCHEMA_VERBOSE + unsigned depth_; +#endif +}; + +typedef GenericSchemaValidator SchemaValidator; + +/////////////////////////////////////////////////////////////////////////////// +// SchemaValidatingReader + +//! A helper class for parsing with validation. +/*! + This helper class is a functor, designed as a parameter of \ref GenericDocument::Populate(). + + \tparam parseFlags Combination of \ref ParseFlag. + \tparam InputStream Type of input stream, implementing Stream concept. + \tparam SourceEncoding Encoding of the input stream. + \tparam SchemaDocumentType Type of schema document. + \tparam StackAllocator Allocator type for stack. +*/ +template < + unsigned parseFlags, + typename InputStream, + typename SourceEncoding, + typename SchemaDocumentType = SchemaDocument, + typename StackAllocator = CrtAllocator> +class SchemaValidatingReader { +public: + typedef typename SchemaDocumentType::PointerType PointerType; + typedef typename InputStream::Ch Ch; + typedef GenericValue ValueType; + + //! Constructor + /*! + \param is Input stream. + \param sd Schema document. + */ + SchemaValidatingReader(InputStream& is, const SchemaDocumentType& sd) : is_(is), sd_(sd), invalidSchemaKeyword_(), error_(kObjectType), isValid_(true) {} + + template + bool operator()(Handler& handler) { + GenericReader reader; + GenericSchemaValidator validator(sd_, handler); + parseResult_ = reader.template Parse(is_, validator); + + isValid_ = validator.IsValid(); + if (isValid_) { + invalidSchemaPointer_ = PointerType(); + invalidSchemaKeyword_ = 0; + invalidDocumentPointer_ = PointerType(); + error_.SetObject(); + } + else { + invalidSchemaPointer_ = validator.GetInvalidSchemaPointer(); + invalidSchemaKeyword_ = validator.GetInvalidSchemaKeyword(); + invalidDocumentPointer_ = validator.GetInvalidDocumentPointer(); + error_.CopyFrom(validator.GetError(), allocator_); + } + + return parseResult_; + } + + const ParseResult& GetParseResult() const { return parseResult_; } + bool IsValid() const { return isValid_; } + const PointerType& GetInvalidSchemaPointer() const { return invalidSchemaPointer_; } + const Ch* GetInvalidSchemaKeyword() const { return invalidSchemaKeyword_; } + const PointerType& GetInvalidDocumentPointer() const { return invalidDocumentPointer_; } + const ValueType& GetError() const { return error_; } + +private: + InputStream& is_; + const SchemaDocumentType& sd_; + + ParseResult parseResult_; + PointerType invalidSchemaPointer_; + const Ch* invalidSchemaKeyword_; + PointerType invalidDocumentPointer_; + StackAllocator allocator_; + ValueType error_; + bool isValid_; +}; + +RAPIDJSON_NAMESPACE_END +RAPIDJSON_DIAG_POP + +#endif // RAPIDJSON_SCHEMA_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stream.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stream.h new file mode 100644 index 0000000000..7f2643e481 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stream.h @@ -0,0 +1,223 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "rapidjson.h" + +#ifndef RAPIDJSON_STREAM_H_ +#define RAPIDJSON_STREAM_H_ + +#include "encodings.h" + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// Stream + +/*! \class rapidjson::Stream + \brief Concept for reading and writing characters. + + For read-only stream, no need to implement PutBegin(), Put(), Flush() and PutEnd(). + + For write-only stream, only need to implement Put() and Flush(). + +\code +concept Stream { + typename Ch; //!< Character type of the stream. + + //! Read the current character from stream without moving the read cursor. + Ch Peek() const; + + //! Read the current character from stream and moving the read cursor to next character. + Ch Take(); + + //! Get the current read cursor. + //! \return Number of characters read from start. + size_t Tell(); + + //! Begin writing operation at the current read pointer. + //! \return The begin writer pointer. + Ch* PutBegin(); + + //! Write a character. + void Put(Ch c); + + //! Flush the buffer. + void Flush(); + + //! End the writing operation. + //! \param begin The begin write pointer returned by PutBegin(). + //! \return Number of characters written. + size_t PutEnd(Ch* begin); +} +\endcode +*/ + +//! Provides additional information for stream. +/*! + By using traits pattern, this type provides a default configuration for stream. + For custom stream, this type can be specialized for other configuration. + See TEST(Reader, CustomStringStream) in readertest.cpp for example. +*/ +template +struct StreamTraits { + //! Whether to make local copy of stream for optimization during parsing. + /*! + By default, for safety, streams do not use local copy optimization. + Stream that can be copied fast should specialize this, like StreamTraits. + */ + enum { copyOptimization = 0 }; +}; + +//! Reserve n characters for writing to a stream. +template +inline void PutReserve(Stream& stream, size_t count) { + (void)stream; + (void)count; +} + +//! Write character to a stream, presuming buffer is reserved. +template +inline void PutUnsafe(Stream& stream, typename Stream::Ch c) { + stream.Put(c); +} + +//! Put N copies of a character to a stream. +template +inline void PutN(Stream& stream, Ch c, size_t n) { + PutReserve(stream, n); + for (size_t i = 0; i < n; i++) + PutUnsafe(stream, c); +} + +/////////////////////////////////////////////////////////////////////////////// +// GenericStreamWrapper + +//! A Stream Wrapper +/*! \tThis string stream is a wrapper for any stream by just forwarding any + \treceived message to the origin stream. + \note implements Stream concept +*/ + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4702) // unreachable code +RAPIDJSON_DIAG_OFF(4512) // assignment operator could not be generated +#endif + +template > +class GenericStreamWrapper { +public: + typedef typename Encoding::Ch Ch; + GenericStreamWrapper(InputStream& is): is_(is) {} + + Ch Peek() const { return is_.Peek(); } + Ch Take() { return is_.Take(); } + size_t Tell() { return is_.Tell(); } + Ch* PutBegin() { return is_.PutBegin(); } + void Put(Ch ch) { is_.Put(ch); } + void Flush() { is_.Flush(); } + size_t PutEnd(Ch* ch) { return is_.PutEnd(ch); } + + // wrapper for MemoryStream + const Ch* Peek4() const { return is_.Peek4(); } + + // wrapper for AutoUTFInputStream + UTFType GetType() const { return is_.GetType(); } + bool HasBOM() const { return is_.HasBOM(); } + +protected: + InputStream& is_; +}; + +#if defined(_MSC_VER) && _MSC_VER <= 1800 +RAPIDJSON_DIAG_POP +#endif + +/////////////////////////////////////////////////////////////////////////////// +// StringStream + +//! Read-only string stream. +/*! \note implements Stream concept +*/ +template +struct GenericStringStream { + typedef typename Encoding::Ch Ch; + + GenericStringStream(const Ch *src) : src_(src), head_(src) {} + + Ch Peek() const { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() const { return static_cast(src_ - head_); } + + Ch* PutBegin() { RAPIDJSON_ASSERT(false); return 0; } + void Put(Ch) { RAPIDJSON_ASSERT(false); } + void Flush() { RAPIDJSON_ASSERT(false); } + size_t PutEnd(Ch*) { RAPIDJSON_ASSERT(false); return 0; } + + const Ch* src_; //!< Current read position. + const Ch* head_; //!< Original head of the string. +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! String stream with UTF8 encoding. +typedef GenericStringStream > StringStream; + +/////////////////////////////////////////////////////////////////////////////// +// InsituStringStream + +//! A read-write string stream. +/*! This string stream is particularly designed for in-situ parsing. + \note implements Stream concept +*/ +template +struct GenericInsituStringStream { + typedef typename Encoding::Ch Ch; + + GenericInsituStringStream(Ch *src) : src_(src), dst_(0), head_(src) {} + + // Read + Ch Peek() { return *src_; } + Ch Take() { return *src_++; } + size_t Tell() { return static_cast(src_ - head_); } + + // Write + void Put(Ch c) { RAPIDJSON_ASSERT(dst_ != 0); *dst_++ = c; } + + Ch* PutBegin() { return dst_ = src_; } + size_t PutEnd(Ch* begin) { return static_cast(dst_ - begin); } + void Flush() {} + + Ch* Push(size_t count) { Ch* begin = dst_; dst_ += count; return begin; } + void Pop(size_t count) { dst_ -= count; } + + Ch* src_; + Ch* dst_; + Ch* head_; +}; + +template +struct StreamTraits > { + enum { copyOptimization = 1 }; +}; + +//! Insitu string stream with UTF8 encoding. +typedef GenericInsituStringStream > InsituStringStream; + +RAPIDJSON_NAMESPACE_END + +#endif // RAPIDJSON_STREAM_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stringbuffer.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stringbuffer.h new file mode 100644 index 0000000000..4e38b82c3d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/stringbuffer.h @@ -0,0 +1,121 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_STRINGBUFFER_H_ +#define RAPIDJSON_STRINGBUFFER_H_ + +#include "stream.h" +#include "internal/stack.h" + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS +#include // std::move +#endif + +#include "internal/stack.h" + +#if defined(__clang__) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(c++98-compat) +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +//! Represents an in-memory output stream. +/*! + \tparam Encoding Encoding of the stream. + \tparam Allocator type for allocating memory buffer. + \note implements Stream concept +*/ +template +class GenericStringBuffer { +public: + typedef typename Encoding::Ch Ch; + + GenericStringBuffer(Allocator* allocator = 0, size_t capacity = kDefaultCapacity) : stack_(allocator, capacity) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + GenericStringBuffer(GenericStringBuffer&& rhs) : stack_(std::move(rhs.stack_)) {} + GenericStringBuffer& operator=(GenericStringBuffer&& rhs) { + if (&rhs != this) + stack_ = std::move(rhs.stack_); + return *this; + } +#endif + + void Put(Ch c) { *stack_.template Push() = c; } + void PutUnsafe(Ch c) { *stack_.template PushUnsafe() = c; } + void Flush() {} + + void Clear() { stack_.Clear(); } + void ShrinkToFit() { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.ShrinkToFit(); + stack_.template Pop(1); + } + + void Reserve(size_t count) { stack_.template Reserve(count); } + Ch* Push(size_t count) { return stack_.template Push(count); } + Ch* PushUnsafe(size_t count) { return stack_.template PushUnsafe(count); } + void Pop(size_t count) { stack_.template Pop(count); } + + const Ch* GetString() const { + // Push and pop a null terminator. This is safe. + *stack_.template Push() = '\0'; + stack_.template Pop(1); + + return stack_.template Bottom(); + } + + //! Get the size of string in bytes in the string buffer. + size_t GetSize() const { return stack_.GetSize(); } + + //! Get the length of string in Ch in the string buffer. + size_t GetLength() const { return stack_.GetSize() / sizeof(Ch); } + + static const size_t kDefaultCapacity = 256; + mutable internal::Stack stack_; + +private: + // Prohibit copy constructor & assignment operator. + GenericStringBuffer(const GenericStringBuffer&); + GenericStringBuffer& operator=(const GenericStringBuffer&); +}; + +//! String buffer with UTF8 encoding +typedef GenericStringBuffer > StringBuffer; + +template +inline void PutReserve(GenericStringBuffer& stream, size_t count) { + stream.Reserve(count); +} + +template +inline void PutUnsafe(GenericStringBuffer& stream, typename Encoding::Ch c) { + stream.PutUnsafe(c); +} + +//! Implement specialized version of PutN() with memset() for better performance. +template<> +inline void PutN(GenericStringBuffer >& stream, char c, size_t n) { + std::memset(stream.stack_.Push(n), c, n * sizeof(c)); +} + +RAPIDJSON_NAMESPACE_END + +#if defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_STRINGBUFFER_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/writer.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/writer.h new file mode 100644 index 0000000000..51dd86d584 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/lottie/rapidjson/writer.h @@ -0,0 +1,710 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef RAPIDJSON_WRITER_H_ +#define RAPIDJSON_WRITER_H_ + +#include "stream.h" +#include "internal/clzll.h" +#include "internal/meta.h" +#include "internal/stack.h" +#include "internal/strfunc.h" +#include "internal/dtoa.h" +#include "internal/itoa.h" +#include "stringbuffer.h" +#include // placement new + +#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) +#include +#pragma intrinsic(_BitScanForward) +#endif +#ifdef RAPIDJSON_SSE42 +#include +#elif defined(RAPIDJSON_SSE2) +#include +#elif defined(RAPIDJSON_NEON) +#include +#endif + +#ifdef __clang__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(padded) +RAPIDJSON_DIAG_OFF(unreachable-code) +RAPIDJSON_DIAG_OFF(c++98-compat) +#elif defined(_MSC_VER) +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(4127) // conditional expression is constant +#endif + +RAPIDJSON_NAMESPACE_BEGIN + +/////////////////////////////////////////////////////////////////////////////// +// WriteFlag + +/*! \def RAPIDJSON_WRITE_DEFAULT_FLAGS + \ingroup RAPIDJSON_CONFIG + \brief User-defined kWriteDefaultFlags definition. + + User can define this as any \c WriteFlag combinations. +*/ +#ifndef RAPIDJSON_WRITE_DEFAULT_FLAGS +#define RAPIDJSON_WRITE_DEFAULT_FLAGS kWriteNoFlags +#endif + +//! Combination of writeFlags +enum WriteFlag { + kWriteNoFlags = 0, //!< No flags are set. + kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings. + kWriteNanAndInfFlag = 2, //!< Allow writing of Infinity, -Infinity and NaN. + kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS +}; + +//! JSON writer +/*! Writer implements the concept Handler. + It generates JSON text by events to an output os. + + User may programmatically calls the functions of a writer to generate JSON text. + + On the other side, a writer can also be passed to objects that generates events, + + for example Reader::Parse() and Document::Accept(). + + \tparam OutputStream Type of output stream. + \tparam SourceEncoding Encoding of source string. + \tparam TargetEncoding Encoding of output stream. + \tparam StackAllocator Type of allocator for allocating memory of stack. + \note implements Handler concept +*/ +template, typename TargetEncoding = UTF8<>, typename StackAllocator = CrtAllocator, unsigned writeFlags = kWriteDefaultFlags> +class Writer { +public: + typedef typename SourceEncoding::Ch Ch; + + static const int kDefaultMaxDecimalPlaces = 324; + + //! Constructor + /*! \param os Output stream. + \param stackAllocator User supplied allocator. If it is null, it will create a private one. + \param levelDepth Initial capacity of stack. + */ + explicit + Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + + explicit + Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} + +#if RAPIDJSON_HAS_CXX11_RVALUE_REFS + Writer(Writer&& rhs) : + os_(rhs.os_), level_stack_(std::move(rhs.level_stack_)), maxDecimalPlaces_(rhs.maxDecimalPlaces_), hasRoot_(rhs.hasRoot_) { + rhs.os_ = 0; + } +#endif + + //! Reset the writer with a new stream. + /*! + This function reset the writer with a new stream and default settings, + in order to make a Writer object reusable for output multiple JSONs. + + \param os New output stream. + \code + Writer writer(os1); + writer.StartObject(); + // ... + writer.EndObject(); + + writer.Reset(os2); + writer.StartObject(); + // ... + writer.EndObject(); + \endcode + */ + void Reset(OutputStream& os) { + os_ = &os; + hasRoot_ = false; + level_stack_.Clear(); + } + + //! Checks whether the output is a complete JSON. + /*! + A complete JSON has a complete root object or array. + */ + bool IsComplete() const { + return hasRoot_ && level_stack_.Empty(); + } + + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + + /*!@name Implementation of Handler + \see Handler + */ + //@{ + + bool Null() { Prefix(kNullType); return EndValue(WriteNull()); } + bool Bool(bool b) { Prefix(b ? kTrueType : kFalseType); return EndValue(WriteBool(b)); } + bool Int(int i) { Prefix(kNumberType); return EndValue(WriteInt(i)); } + bool Uint(unsigned u) { Prefix(kNumberType); return EndValue(WriteUint(u)); } + bool Int64(int64_t i64) { Prefix(kNumberType); return EndValue(WriteInt64(i64)); } + bool Uint64(uint64_t u64) { Prefix(kNumberType); return EndValue(WriteUint64(u64)); } + + //! Writes the given \c double value to the stream + /*! + \param d The value to be written. + \return Whether it is succeed. + */ + bool Double(double d) { Prefix(kNumberType); return EndValue(WriteDouble(d)); } + + bool RawNumber(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kNumberType); + return EndValue(WriteString(str, length)); + } + + bool String(const Ch* str, SizeType length, bool copy = false) { + RAPIDJSON_ASSERT(str != 0); + (void)copy; + Prefix(kStringType); + return EndValue(WriteString(str, length)); + } + +#if RAPIDJSON_HAS_STDSTRING + bool String(const std::basic_string& str) { + return String(str.data(), SizeType(str.size())); + } +#endif + + bool StartObject() { + Prefix(kObjectType); + new (level_stack_.template Push()) Level(false); + return WriteStartObject(); + } + + bool Key(const Ch* str, SizeType length, bool copy = false) { return String(str, length, copy); } + +#if RAPIDJSON_HAS_STDSTRING + bool Key(const std::basic_string& str) + { + return Key(str.data(), SizeType(str.size())); + } +#endif + + bool EndObject(SizeType memberCount = 0) { + (void)memberCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); // not inside an Object + RAPIDJSON_ASSERT(!level_stack_.template Top()->inArray); // currently inside an Array, not Object + RAPIDJSON_ASSERT(0 == level_stack_.template Top()->valueCount % 2); // Object has a Key without a Value + level_stack_.template Pop(1); + return EndValue(WriteEndObject()); + } + + bool StartArray() { + Prefix(kArrayType); + new (level_stack_.template Push()) Level(true); + return WriteStartArray(); + } + + bool EndArray(SizeType elementCount = 0) { + (void)elementCount; + RAPIDJSON_ASSERT(level_stack_.GetSize() >= sizeof(Level)); + RAPIDJSON_ASSERT(level_stack_.template Top()->inArray); + level_stack_.template Pop(1); + return EndValue(WriteEndArray()); + } + //@} + + /*! @name Convenience extensions */ + //@{ + + //! Simpler but slower overload. + bool String(const Ch* const& str) { return String(str, internal::StrLen(str)); } + bool Key(const Ch* const& str) { return Key(str, internal::StrLen(str)); } + + //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { + RAPIDJSON_ASSERT(json != 0); + Prefix(type); + return EndValue(WriteRawValue(json, length)); + } + + //! Flush the output stream. + /*! + Allows the user to flush the output stream immediately. + */ + void Flush() { + os_->Flush(); + } + + static const size_t kDefaultLevelDepth = 32; + +protected: + //! Information for each nested level + struct Level { + Level(bool inArray_) : valueCount(0), inArray(inArray_) {} + size_t valueCount; //!< number of values in this level + bool inArray; //!< true if in array, otherwise in object + }; + + bool WriteNull() { + PutReserve(*os_, 4); + PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 'l'); return true; + } + + bool WriteBool(bool b) { + if (b) { + PutReserve(*os_, 4); + PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'r'); PutUnsafe(*os_, 'u'); PutUnsafe(*os_, 'e'); + } + else { + PutReserve(*os_, 5); + PutUnsafe(*os_, 'f'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'l'); PutUnsafe(*os_, 's'); PutUnsafe(*os_, 'e'); + } + return true; + } + + bool WriteInt(int i) { + char buffer[11]; + const char* end = internal::i32toa(i, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint(unsigned u) { + char buffer[10]; + const char* end = internal::u32toa(u, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteInt64(int64_t i64) { + char buffer[21]; + const char* end = internal::i64toa(i64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (const char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteUint64(uint64_t u64) { + char buffer[20]; + char* end = internal::u64toa(u64, buffer); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + if (!(writeFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char buffer[25]; + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + PutReserve(*os_, static_cast(end - buffer)); + for (char* p = buffer; p != end; ++p) + PutUnsafe(*os_, static_cast(*p)); + return true; + } + + bool WriteString(const Ch* str, SizeType length) { + static const typename OutputStream::Ch hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + static const char escape[256] = { +#define Z16 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'b', 't', 'n', 'u', 'f', 'r', 'u', 'u', // 00 + 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', 'u', // 10 + 0, 0, '"', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + Z16, Z16, // 30~4F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0, // 50 + Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16, Z16 // 60~FF +#undef Z16 + }; + + if (TargetEncoding::supportUnicode) + PutReserve(*os_, 2 + length * 6); // "\uxxxx..." + else + PutReserve(*os_, 2 + length * 12); // "\uxxxx\uyyyy..." + + PutUnsafe(*os_, '\"'); + GenericStringStream is(str); + while (ScanWriteUnescapedString(is, length)) { + const Ch c = is.Peek(); + if (!TargetEncoding::supportUnicode && static_cast(c) >= 0x80) { + // Unicode escaping + unsigned codepoint; + if (RAPIDJSON_UNLIKELY(!SourceEncoding::Decode(is, &codepoint))) + return false; + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + if (codepoint <= 0xD7FF || (codepoint >= 0xE000 && codepoint <= 0xFFFF)) { + PutUnsafe(*os_, hexDigits[(codepoint >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(codepoint ) & 15]); + } + else { + RAPIDJSON_ASSERT(codepoint >= 0x010000 && codepoint <= 0x10FFFF); + // Surrogate pair + unsigned s = codepoint - 0x010000; + unsigned lead = (s >> 10) + 0xD800; + unsigned trail = (s & 0x3FF) + 0xDC00; + PutUnsafe(*os_, hexDigits[(lead >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(lead >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(lead ) & 15]); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, 'u'); + PutUnsafe(*os_, hexDigits[(trail >> 12) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 8) & 15]); + PutUnsafe(*os_, hexDigits[(trail >> 4) & 15]); + PutUnsafe(*os_, hexDigits[(trail ) & 15]); + } + } + else if ((sizeof(Ch) == 1 || static_cast(c) < 256) && RAPIDJSON_UNLIKELY(escape[static_cast(c)])) { + is.Take(); + PutUnsafe(*os_, '\\'); + PutUnsafe(*os_, static_cast(escape[static_cast(c)])); + if (escape[static_cast(c)] == 'u') { + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, '0'); + PutUnsafe(*os_, hexDigits[static_cast(c) >> 4]); + PutUnsafe(*os_, hexDigits[static_cast(c) & 0xF]); + } + } + else if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + PutUnsafe(*os_, '\"'); + return true; + } + + bool ScanWriteUnescapedString(GenericStringStream& is, size_t length) { + return RAPIDJSON_LIKELY(is.Tell() < length); + } + + bool WriteStartObject() { os_->Put('{'); return true; } + bool WriteEndObject() { os_->Put('}'); return true; } + bool WriteStartArray() { os_->Put('['); return true; } + bool WriteEndArray() { os_->Put(']'); return true; } + + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + GenericStringStream is(json); + while (RAPIDJSON_LIKELY(is.Tell() < length)) { + RAPIDJSON_ASSERT(is.Peek() != '\0'); + if (RAPIDJSON_UNLIKELY(!(writeFlags & kWriteValidateEncodingFlag ? + Transcoder::Validate(is, *os_) : + Transcoder::TranscodeUnsafe(is, *os_)))) + return false; + } + return true; + } + + void Prefix(Type type) { + (void)type; + if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root + Level* level = level_stack_.template Top(); + if (level->valueCount > 0) { + if (level->inArray) + os_->Put(','); // add comma if it is not the first element in array + else // in object + os_->Put((level->valueCount % 2 == 0) ? ',' : ':'); + } + if (!level->inArray && level->valueCount % 2 == 0) + RAPIDJSON_ASSERT(type == kStringType); // if it's in object, then even number should be a name + level->valueCount++; + } + else { + RAPIDJSON_ASSERT(!hasRoot_); // Should only has one and only one root. + hasRoot_ = true; + } + } + + // Flush the value if it is the top level one. + bool EndValue(bool ret) { + if (RAPIDJSON_UNLIKELY(level_stack_.Empty())) // end of json text + Flush(); + return ret; + } + + OutputStream* os_; + internal::Stack level_stack_; + int maxDecimalPlaces_; + bool hasRoot_; + +private: + // Prohibit copy constructor & assignment operator. + Writer(const Writer&); + Writer& operator=(const Writer&); +}; + +// Full specialization for StringStream to prevent memory copying + +template<> +inline bool Writer::WriteInt(int i) { + char *buffer = os_->Push(11); + const char* end = internal::i32toa(i, buffer); + os_->Pop(static_cast(11 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint(unsigned u) { + char *buffer = os_->Push(10); + const char* end = internal::u32toa(u, buffer); + os_->Pop(static_cast(10 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteInt64(int64_t i64) { + char *buffer = os_->Push(21); + const char* end = internal::i64toa(i64, buffer); + os_->Pop(static_cast(21 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteUint64(uint64_t u) { + char *buffer = os_->Push(20); + const char* end = internal::u64toa(u, buffer); + os_->Pop(static_cast(20 - (end - buffer))); + return true; +} + +template<> +inline bool Writer::WriteDouble(double d) { + if (internal::Double(d).IsNanOrInf()) { + // Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag). + if (!(kWriteDefaultFlags & kWriteNanAndInfFlag)) + return false; + if (internal::Double(d).IsNan()) { + PutReserve(*os_, 3); + PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N'); + return true; + } + if (internal::Double(d).Sign()) { + PutReserve(*os_, 9); + PutUnsafe(*os_, '-'); + } + else + PutReserve(*os_, 8); + PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f'); + PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y'); + return true; + } + + char *buffer = os_->Push(25); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); + os_->Pop(static_cast(25 - (end - buffer))); + return true; +} + +#if defined(RAPIDJSON_SSE2) || defined(RAPIDJSON_SSE42) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + static const char dquote[16] = { '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"', '\"' }; + static const char bslash[16] = { '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\', '\\' }; + static const char space[16] = { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }; + const __m128i dq = _mm_loadu_si128(reinterpret_cast(&dquote[0])); + const __m128i bs = _mm_loadu_si128(reinterpret_cast(&bslash[0])); + const __m128i sp = _mm_loadu_si128(reinterpret_cast(&space[0])); + + for (; p != endAligned; p += 16) { + const __m128i s = _mm_load_si128(reinterpret_cast(p)); + const __m128i t1 = _mm_cmpeq_epi8(s, dq); + const __m128i t2 = _mm_cmpeq_epi8(s, bs); + const __m128i t3 = _mm_cmpeq_epi8(_mm_max_epu8(s, sp), sp); // s < 0x20 <=> max(s, 0x1F) == 0x1F + const __m128i x = _mm_or_si128(_mm_or_si128(t1, t2), t3); + unsigned short r = static_cast(_mm_movemask_epi8(x)); + if (RAPIDJSON_UNLIKELY(r != 0)) { // some of characters is escaped + SizeType len; +#ifdef _MSC_VER // Find the index of first escaped + unsigned long offset; + _BitScanForward(&offset, r); + len = offset; +#else + len = static_cast(__builtin_ffs(r) - 1); +#endif + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + _mm_storeu_si128(reinterpret_cast<__m128i *>(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#elif defined(RAPIDJSON_NEON) +template<> +inline bool Writer::ScanWriteUnescapedString(StringStream& is, size_t length) { + if (length < 16) + return RAPIDJSON_LIKELY(is.Tell() < length); + + if (!RAPIDJSON_LIKELY(is.Tell() < length)) + return false; + + const char* p = is.src_; + const char* end = is.head_ + length; + const char* nextAligned = reinterpret_cast((reinterpret_cast(p) + 15) & static_cast(~15)); + const char* endAligned = reinterpret_cast(reinterpret_cast(end) & static_cast(~15)); + if (nextAligned > end) + return true; + + while (p != nextAligned) + if (*p < 0x20 || *p == '\"' || *p == '\\') { + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); + } + else + os_->PutUnsafe(*p++); + + // The rest of string using SIMD + const uint8x16_t s0 = vmovq_n_u8('"'); + const uint8x16_t s1 = vmovq_n_u8('\\'); + const uint8x16_t s2 = vmovq_n_u8('\b'); + const uint8x16_t s3 = vmovq_n_u8(32); + + for (; p != endAligned; p += 16) { + const uint8x16_t s = vld1q_u8(reinterpret_cast(p)); + uint8x16_t x = vceqq_u8(s, s0); + x = vorrq_u8(x, vceqq_u8(s, s1)); + x = vorrq_u8(x, vceqq_u8(s, s2)); + x = vorrq_u8(x, vcltq_u8(s, s3)); + + x = vrev64q_u8(x); // Rev in 64 + uint64_t low = vgetq_lane_u64(vreinterpretq_u64_u8(x), 0); // extract + uint64_t high = vgetq_lane_u64(vreinterpretq_u64_u8(x), 1); // extract + + SizeType len = 0; + bool escaped = false; + if (low == 0) { + if (high != 0) { + uint32_t lz = internal::clzll(high); + len = 8 + (lz >> 3); + escaped = true; + } + } else { + uint32_t lz = internal::clzll(low); + len = lz >> 3; + escaped = true; + } + if (RAPIDJSON_UNLIKELY(escaped)) { // some of characters is escaped + char* q = reinterpret_cast(os_->PushUnsafe(len)); + for (size_t i = 0; i < len; i++) + q[i] = p[i]; + + p += len; + break; + } + vst1q_u8(reinterpret_cast(os_->PushUnsafe(16)), s); + } + + is.src_ = p; + return RAPIDJSON_LIKELY(is.Tell() < length); +} +#endif // RAPIDJSON_NEON + +RAPIDJSON_NAMESPACE_END + +#if defined(_MSC_VER) || defined(__clang__) +RAPIDJSON_DIAG_POP +#endif + +#endif // RAPIDJSON_RAPIDJSON_H_ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.cpp new file mode 100644 index 0000000000..a3f0af2e4d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.cpp @@ -0,0 +1,461 @@ +/***************************************************************************/ +/* */ +/* fttrigon.c */ +/* */ +/* FreeType trigonometric functions (body). */ +/* */ +/* Copyright 2001-2005, 2012-2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_math.h" +#include + +//form https://github.com/chromium/chromium/blob/59afd8336009c9d97c22854c52e0382b62b3aa5e/third_party/abseil-cpp/absl/base/internal/bits.h + +#if defined(_MSC_VER) +#include +static unsigned int __inline clz(unsigned int x) { + unsigned long r = 0; + if (x != 0) + { + _BitScanReverse(&r, x); + } + return r; +} +#define SW_FT_MSB(x) (clz(x)) +#elif defined(__GNUC__) +#define SW_FT_MSB(x) (31 - __builtin_clz(x)) +#else +static unsigned int __inline clz(unsigned int x) { + int c = 31; + x &= ~x + 1; + if (n & 0x0000FFFF) c -= 16; + if (n & 0x00FF00FF) c -= 8; + if (n & 0x0F0F0F0F) c -= 4; + if (n & 0x33333333) c -= 2; + if (n & 0x55555555) c -= 1; + return c; +} +#define SW_FT_MSB(x) (clz(x)) +#endif + + + + + +#define SW_FT_PAD_FLOOR(x, n) ((x) & ~((n)-1)) +#define SW_FT_PAD_ROUND(x, n) SW_FT_PAD_FLOOR((x) + ((n) / 2), n) +#define SW_FT_PAD_CEIL(x, n) SW_FT_PAD_FLOOR((x) + ((n)-1), n) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) +/* transfer sign leaving a positive number */ +#define SW_FT_MOVE_SIGN(x, s) \ + SW_FT_BEGIN_STMNT \ + if (x < 0) { \ + x = -x; \ + s = -s; \ + } \ + SW_FT_END_STMNT + +SW_FT_Long SW_FT_MulFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long c; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + c = (SW_FT_Long)(((SW_FT_Int64)a * b + 0x8000L) >> 16); + + return (s > 0) ? c : -c; +} + +SW_FT_Long SW_FT_MulDiv(SW_FT_Long a, SW_FT_Long b, SW_FT_Long c) +{ + SW_FT_Int s = 1; + SW_FT_Long d; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + SW_FT_MOVE_SIGN(c, s); + + d = (SW_FT_Long)(c > 0 ? ((SW_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL); + + return (s > 0) ? d : -d; +} + +SW_FT_Long SW_FT_DivFix(SW_FT_Long a, SW_FT_Long b) +{ + SW_FT_Int s = 1; + SW_FT_Long q; + + SW_FT_MOVE_SIGN(a, s); + SW_FT_MOVE_SIGN(b, s); + + q = (SW_FT_Long)(b > 0 ? (((SW_FT_UInt64)a << 16) + (b >> 1)) / b + : 0x7FFFFFFFL); + + return (s < 0 ? -q : q); +} + +/*************************************************************************/ +/* */ +/* This is a fixed-point CORDIC implementation of trigonometric */ +/* functions as well as transformations between Cartesian and polar */ +/* coordinates. The angles are represented as 16.16 fixed-point values */ +/* in degrees, i.e., the angular resolution is 2^-16 degrees. Note that */ +/* only vectors longer than 2^16*180/pi (or at least 22 bits) on a */ +/* discrete Cartesian grid can have the same or better angular */ +/* resolution. Therefore, to maintain this precision, some functions */ +/* require an interim upscaling of the vectors, whereas others operate */ +/* with 24-bit long vectors directly. */ +/* */ +/*************************************************************************/ + +/* the Cordic shrink factor 0.858785336480436 * 2^32 */ +#define SW_FT_TRIG_SCALE 0xDBD95B16UL + +/* the highest bit in overflow-safe vector components, */ +/* MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ +#define SW_FT_TRIG_SAFE_MSB 29 + +/* this table was generated for SW_FT_PI = 180L << 16, i.e. degrees */ +#define SW_FT_TRIG_MAX_ITERS 23 + +static const SW_FT_Fixed ft_trig_arctan_table[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L, + 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, 57L, + 29L, 14L, 7L, 4L, 2L, 1L}; + +/* multiply a given value by the CORDIC shrink factor */ +static SW_FT_Fixed ft_trig_downscale(SW_FT_Fixed val) +{ + SW_FT_Fixed s; + SW_FT_Int64 v; + + s = val; + val = SW_FT_ABS(val); + + v = (val * (SW_FT_Int64)SW_FT_TRIG_SCALE) + 0x100000000UL; + val = (SW_FT_Fixed)(v >> 32); + + return (s >= 0) ? val : -val; +} + +/* undefined and never called for zero vector */ +static SW_FT_Int ft_trig_prenorm(SW_FT_Vector* vec) +{ + SW_FT_Pos x, y; + SW_FT_Int shift; + + x = vec->x; + y = vec->y; + + shift = SW_FT_MSB(SW_FT_ABS(x) | SW_FT_ABS(y)); + + if (shift <= SW_FT_TRIG_SAFE_MSB) { + shift = SW_FT_TRIG_SAFE_MSB - shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)y << shift); + } else { + shift -= SW_FT_TRIG_SAFE_MSB; + vec->x = x >> shift; + vec->y = y >> shift; + shift = -shift; + } + + return shift; +} + +static void ft_trig_pseudo_rotate(SW_FT_Vector* vec, SW_FT_Angle theta) +{ + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Rotate inside [-PI/4,PI/4] sector */ + while (theta < -SW_FT_ANGLE_PI4) { + xtemp = y; + y = -x; + x = xtemp; + theta += SW_FT_ANGLE_PI2; + } + + while (theta > SW_FT_ANGLE_PI4) { + xtemp = -y; + y = x; + x = xtemp; + theta -= SW_FT_ANGLE_PI2; + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + SW_FT_Fixed v1 = ((y + b) >> i); + SW_FT_Fixed v2 = ((x + b) >> i); + if (theta < 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + vec->x = x; + vec->y = y; +} + +static void ft_trig_pseudo_polarize(SW_FT_Vector* vec) +{ + SW_FT_Angle theta; + SW_FT_Int i; + SW_FT_Fixed x, y, xtemp, b; + const SW_FT_Fixed* arctanptr; + + x = vec->x; + y = vec->y; + + /* Get the vector into [-PI/4,PI/4] sector */ + if (y > x) { + if (y > -x) { + theta = SW_FT_ANGLE_PI2; + xtemp = y; + y = -x; + x = xtemp; + } else { + theta = y > 0 ? SW_FT_ANGLE_PI : -SW_FT_ANGLE_PI; + x = -x; + y = -y; + } + } else { + if (y < -x) { + theta = -SW_FT_ANGLE_PI2; + xtemp = -y; + y = x; + x = xtemp; + } else { + theta = 0; + } + } + + arctanptr = ft_trig_arctan_table; + + /* Pseudorotations, with right shifts */ + for (i = 1, b = 1; i < SW_FT_TRIG_MAX_ITERS; b <<= 1, i++) { + SW_FT_Fixed v1 = ((y + b) >> i); + SW_FT_Fixed v2 = ((x + b) >> i); + if (y > 0) { + xtemp = x + v1; + y = y - v2; + x = xtemp; + theta += *arctanptr++; + } else { + xtemp = x - v1; + y = y + v2; + x = xtemp; + theta -= *arctanptr++; + } + } + + /* round theta */ + if (theta >= 0) + theta = SW_FT_PAD_ROUND(theta, 32); + else + theta = -SW_FT_PAD_ROUND(-theta, 32); + + vec->x = x; + vec->y = theta; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Cos(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return (v.x + 0x80L) >> 8; +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Sin(SW_FT_Angle angle) +{ + return SW_FT_Cos(SW_FT_ANGLE_PI2 - angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Tan(SW_FT_Angle angle) +{ + SW_FT_Vector v; + + v.x = SW_FT_TRIG_SCALE >> 8; + v.y = 0; + ft_trig_pseudo_rotate(&v, angle); + + return SW_FT_DivFix(v.y, v.x); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Atan2(SW_FT_Fixed dx, SW_FT_Fixed dy) +{ + SW_FT_Vector v; + + if (dx == 0 && dy == 0) return 0; + + v.x = dx; + v.y = dy; + ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + return v.y; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Unit(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + vec->x = SW_FT_TRIG_SCALE >> 8; + vec->y = 0; + ft_trig_pseudo_rotate(vec, angle); + vec->x = (vec->x + 0x80L) >> 8; + vec->y = (vec->y + 0x80L) >> 8; +} + +/* these macros return 0 for positive numbers, + and -1 for negative ones */ +#define SW_FT_SIGN_LONG(x) ((x) >> (SW_FT_SIZEOF_LONG * 8 - 1)) +#define SW_FT_SIGN_INT(x) ((x) >> (SW_FT_SIZEOF_INT * 8 - 1)) +#define SW_FT_SIGN_INT32(x) ((x) >> 31) +#define SW_FT_SIGN_INT16(x) ((x) >> 15) + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Rotate(SW_FT_Vector* vec, SW_FT_Angle angle) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v.x = vec->x; + v.y = vec->y; + + if (angle && (v.x != 0 || v.y != 0)) { + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_rotate(&v, angle); + v.x = ft_trig_downscale(v.x); + v.y = ft_trig_downscale(v.y); + + if (shift > 0) { + SW_FT_Int32 half = (SW_FT_Int32)1L << (shift - 1); + + vec->x = (v.x + half + SW_FT_SIGN_LONG(v.x)) >> shift; + vec->y = (v.y + half + SW_FT_SIGN_LONG(v.y)) >> shift; + } else { + shift = -shift; + vec->x = (SW_FT_Pos)((SW_FT_ULong)v.x << shift); + vec->y = (SW_FT_Pos)((SW_FT_ULong)v.y << shift); + } + } +} + +/* documentation is in fttrigon.h */ + +SW_FT_Fixed SW_FT_Vector_Length(SW_FT_Vector* vec) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v = *vec; + + /* handle trivial cases */ + if (v.x == 0) { + return SW_FT_ABS(v.y); + } else if (v.y == 0) { + return SW_FT_ABS(v.x); + } + + /* general case */ + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + if (shift > 0) return (v.x + (1 << (shift - 1))) >> shift; + + return (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_Polarize(SW_FT_Vector* vec, SW_FT_Fixed* length, + SW_FT_Angle* angle) +{ + SW_FT_Int shift; + SW_FT_Vector v; + + v = *vec; + + if (v.x == 0 && v.y == 0) return; + + shift = ft_trig_prenorm(&v); + ft_trig_pseudo_polarize(&v); + + v.x = ft_trig_downscale(v.x); + + *length = (shift >= 0) ? (v.x >> shift) + : (SW_FT_Fixed)((SW_FT_UInt32)v.x << -shift); + *angle = v.y; +} + +/* documentation is in fttrigon.h */ + +void SW_FT_Vector_From_Polar(SW_FT_Vector* vec, SW_FT_Fixed length, + SW_FT_Angle angle) +{ + vec->x = length; + vec->y = 0; + + SW_FT_Vector_Rotate(vec, angle); +} + +/* documentation is in fttrigon.h */ + +SW_FT_Angle SW_FT_Angle_Diff( SW_FT_Angle angle1, SW_FT_Angle angle2 ) +{ + SW_FT_Angle delta = angle2 - angle1; + + while ( delta <= -SW_FT_ANGLE_PI ) + delta += SW_FT_ANGLE_2PI; + + while ( delta > SW_FT_ANGLE_PI ) + delta -= SW_FT_ANGLE_2PI; + + return delta; +} + +/* END */ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.h new file mode 100644 index 0000000000..b4611d8d44 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_math.h @@ -0,0 +1,438 @@ +#ifndef V_FT_MATH_H +#define V_FT_MATH_H + +/***************************************************************************/ +/* */ +/* fttrigon.h */ +/* */ +/* FreeType trigonometric functions (specification). */ +/* */ +/* Copyright 2001, 2003, 2005, 2007, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_types.h" + + +/*************************************************************************/ +/* */ +/* The min and max functions missing in C. As usual, be careful not to */ +/* write things like SW_FT_MIN( a++, b++ ) to avoid side effects. */ +/* */ +#define SW_FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) ) +#define SW_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) + +#define SW_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define SW_FT_HYPOT( x, y ) \ + ( x = SW_FT_ABS( x ), \ + y = SW_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*b)/0x10000' with maximum accuracy. Most of the time this is */ +/* used to multiply a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. Use a 16.16 factor here whenever */ +/* possible (see note below). */ +/* */ +/* */ +/* The result of `(a*b)/0x10000'. */ +/* */ +/* */ +/* This function has been optimized for the case where the absolute */ +/* value of `a' is less than 2048, and `b' is a 16.16 scaling factor. */ +/* As this happens mainly when scaling from notional units to */ +/* fractional pixels in FreeType, it resulted in noticeable speed */ +/* improvements between versions 2.x and 1.x. */ +/* */ +/* As a conclusion, always try to place a 16.16 factor as the */ +/* _second_ argument of this function; this can make a great */ +/* difference. */ +/* */ +SW_FT_Long +SW_FT_MulFix( SW_FT_Long a, + SW_FT_Long b ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_MulDiv */ +/* */ +/* */ +/* A very simple function used to perform the computation `(a*b)/c' */ +/* with maximum accuracy (it uses a 64-bit intermediate integer */ +/* whenever necessary). */ +/* */ +/* This function isn't necessarily as fast as some processor specific */ +/* operations, but is at least completely portable. */ +/* */ +/* */ +/* a :: The first multiplier. */ +/* b :: The second multiplier. */ +/* c :: The divisor. */ +/* */ +/* */ +/* The result of `(a*b)/c'. This function never traps when trying to */ +/* divide by zero; it simply returns `MaxInt' or `MinInt' depending */ +/* on the signs of `a' and `b'. */ +/* */ +SW_FT_Long +SW_FT_MulDiv( SW_FT_Long a, + SW_FT_Long b, + SW_FT_Long c ); + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_DivFix */ +/* */ +/* */ +/* A very simple function used to perform the computation */ +/* `(a*0x10000)/b' with maximum accuracy. Most of the time, this is */ +/* used to divide a given value by a 16.16 fixed-point factor. */ +/* */ +/* */ +/* a :: The numerator. */ +/* b :: The denominator. Use a 16.16 factor here. */ +/* */ +/* */ +/* The result of `(a*0x10000)/b'. */ +/* */ +SW_FT_Long +SW_FT_DivFix( SW_FT_Long a, + SW_FT_Long b ); + + + + /*************************************************************************/ + /* */ + /*
*/ + /* computations */ + /* */ + /*************************************************************************/ + + + /************************************************************************* + * + * @type: + * SW_FT_Angle + * + * @description: + * This type is used to model angle values in FreeType. Note that the + * angle is a 16.16 fixed-point value expressed in degrees. + * + */ + typedef SW_FT_Fixed SW_FT_Angle; + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI + * + * @description: + * The angle pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI ( 180L << 16 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_2PI + * + * @description: + * The angle 2*pi expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_2PI ( SW_FT_ANGLE_PI * 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI2 + * + * @description: + * The angle pi/2 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI2 ( SW_FT_ANGLE_PI / 2 ) + + + /************************************************************************* + * + * @macro: + * SW_FT_ANGLE_PI4 + * + * @description: + * The angle pi/4 expressed in @SW_FT_Angle units. + * + */ +#define SW_FT_ANGLE_PI4 ( SW_FT_ANGLE_PI / 4 ) + + + /************************************************************************* + * + * @function: + * SW_FT_Sin + * + * @description: + * Return the sinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The sinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Sin( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Cos + * + * @description: + * Return the cosinus of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The cosinus value. + * + * @note: + * If you need both the sinus and cosinus for a given angle, use the + * function @SW_FT_Vector_Unit. + * + */ + SW_FT_Fixed + SW_FT_Cos( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Tan + * + * @description: + * Return the tangent of a given angle in fixed-point format. + * + * @input: + * angle :: + * The input angle. + * + * @return: + * The tangent value. + * + */ + SW_FT_Fixed + SW_FT_Tan( SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Atan2 + * + * @description: + * Return the arc-tangent corresponding to a given vector (x,y) in + * the 2d plane. + * + * @input: + * x :: + * The horizontal vector coordinate. + * + * y :: + * The vertical vector coordinate. + * + * @return: + * The arc-tangent value (i.e. angle). + * + */ + SW_FT_Angle + SW_FT_Atan2( SW_FT_Fixed x, + SW_FT_Fixed y ); + + + /************************************************************************* + * + * @function: + * SW_FT_Angle_Diff + * + * @description: + * Return the difference between two angles. The result is always + * constrained to the ]-PI..PI] interval. + * + * @input: + * angle1 :: + * First angle. + * + * angle2 :: + * Second angle. + * + * @return: + * Constrained value of `value2-value1'. + * + */ + SW_FT_Angle + SW_FT_Angle_Diff( SW_FT_Angle angle1, + SW_FT_Angle angle2 ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Unit + * + * @description: + * Return the unit vector corresponding to a given angle. After the + * call, the value of `vec.x' will be `sin(angle)', and the value of + * `vec.y' will be `cos(angle)'. + * + * This function is useful to retrieve both the sinus and cosinus of a + * given angle quickly. + * + * @output: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Unit( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Rotate + * + * @description: + * Rotate a vector by a given angle. + * + * @inout: + * vec :: + * The address of target vector. + * + * @input: + * angle :: + * The input angle. + * + */ + void + SW_FT_Vector_Rotate( SW_FT_Vector* vec, + SW_FT_Angle angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Length + * + * @description: + * Return the length of a given vector. + * + * @input: + * vec :: + * The address of target vector. + * + * @return: + * The vector length, expressed in the same units that the original + * vector coordinates. + * + */ + SW_FT_Fixed + SW_FT_Vector_Length( SW_FT_Vector* vec ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_Polarize + * + * @description: + * Compute both the length and angle of a given vector. + * + * @input: + * vec :: + * The address of source vector. + * + * @output: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_Polarize( SW_FT_Vector* vec, + SW_FT_Fixed *length, + SW_FT_Angle *angle ); + + + /************************************************************************* + * + * @function: + * SW_FT_Vector_From_Polar + * + * @description: + * Compute vector coordinates from a length and angle. + * + * @output: + * vec :: + * The address of source vector. + * + * @input: + * length :: + * The vector length. + * + * angle :: + * The vector angle. + * + */ + void + SW_FT_Vector_From_Polar( SW_FT_Vector* vec, + SW_FT_Fixed length, + SW_FT_Angle angle ); + + +#endif // V_FT_MATH_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.cpp new file mode 100644 index 0000000000..e48ad1b107 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.cpp @@ -0,0 +1,1423 @@ +/***************************************************************************/ +/* */ +/* ftgrays.c */ +/* */ +/* A new `perfect' anti-aliasing renderer (body). */ +/* */ +/* Copyright 2000-2003, 2005-2014 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +/*************************************************************************/ +/* */ +/* This is a new anti-aliasing scan-converter for FreeType 2. The */ +/* algorithm used here is _very_ different from the one in the standard */ +/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */ +/* coverage of the outline on each pixel cell. */ +/* */ +/* It is based on ideas that I initially found in Raph Levien's */ +/* excellent LibArt graphics library (see http://www.levien.com/libart */ +/* for more information, though the web pages do not tell anything */ +/* about the renderer; you'll have to dive into the source code to */ +/* understand how it works). */ +/* */ +/* Note, however, that this is a _very_ different implementation */ +/* compared to Raph's. Coverage information is stored in a very */ +/* different way, and I don't use sorted vector paths. Also, it doesn't */ +/* use floating point values. */ +/* */ +/* This renderer has the following advantages: */ +/* */ +/* - It doesn't need an intermediate bitmap. Instead, one can supply a */ +/* callback function that will be called by the renderer to draw gray */ +/* spans on any target surface. You can thus do direct composition on */ +/* any kind of bitmap, provided that you give the renderer the right */ +/* callback. */ +/* */ +/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */ +/* each pixel cell. */ +/* */ +/* - It performs a single pass on the outline (the `standard' FT2 */ +/* renderer makes two passes). */ +/* */ +/* - It can easily be modified to render to _any_ number of gray levels */ +/* cheaply. */ +/* */ +/* - For small (< 20) pixel sizes, it is faster than the standard */ +/* renderer. */ +/* */ +/*************************************************************************/ + +#include "v_ft_raster.h" +#include "v_ft_math.h" + +/* Auxiliary macros for token concatenation. */ +#define SW_FT_ERR_XCAT(x, y) x##y +#define SW_FT_ERR_CAT(x, y) SW_FT_ERR_XCAT(x, y) + +#define SW_FT_BEGIN_STMNT do { +#define SW_FT_END_STMNT \ + } \ + while (0) + +#include +#include +#include +#include +#define SW_FT_UINT_MAX UINT_MAX +#define SW_FT_INT_MAX INT_MAX +#define SW_FT_ULONG_MAX ULONG_MAX +#define SW_FT_CHAR_BIT CHAR_BIT + +#define ft_memset memset + +#define ft_setjmp setjmp +#define ft_longjmp longjmp +#define ft_jmp_buf jmp_buf + +typedef ptrdiff_t SW_FT_PtrDist; + +#define ErrRaster_Invalid_Mode -2 +#define ErrRaster_Invalid_Outline -1 +#define ErrRaster_Invalid_Argument -3 +#define ErrRaster_Memory_Overflow -4 + +#define SW_FT_BEGIN_HEADER +#define SW_FT_END_HEADER + +/* This macro is used to indicate that a function parameter is unused. */ +/* Its purpose is simply to reduce compiler warnings. Note also that */ +/* simply defining it as `(void)x' doesn't avoid warnings with certain */ +/* ANSI compilers (e.g. LCC). */ +#define SW_FT_UNUSED(x) (x) = (x) + +#define SW_FT_THROW(e) SW_FT_ERR_CAT(ErrRaster_, e) + +/* The size in bytes of the render pool used by the scan-line converter */ +/* to do all of its work. */ +#define SW_FT_RENDER_POOL_SIZE 16384L + +typedef int (*SW_FT_Outline_MoveToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_MoveTo_Func SW_FT_Outline_MoveToFunc + +typedef int (*SW_FT_Outline_LineToFunc)(const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_LineTo_Func SW_FT_Outline_LineToFunc + +typedef int (*SW_FT_Outline_ConicToFunc)(const SW_FT_Vector* control, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_ConicTo_Func SW_FT_Outline_ConicToFunc + +typedef int (*SW_FT_Outline_CubicToFunc)(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to, void* user); + +#define SW_FT_Outline_CubicTo_Func SW_FT_Outline_CubicToFunc + +typedef struct SW_FT_Outline_Funcs_ { + SW_FT_Outline_MoveToFunc move_to; + SW_FT_Outline_LineToFunc line_to; + SW_FT_Outline_ConicToFunc conic_to; + SW_FT_Outline_CubicToFunc cubic_to; + + int shift; + SW_FT_Pos delta; + +} SW_FT_Outline_Funcs; + +#define SW_FT_DEFINE_OUTLINE_FUNCS(class_, move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_) \ + static const SW_FT_Outline_Funcs class_ = {move_to_, line_to_, conic_to_, \ + cubic_to_, shift_, delta_}; + +#define SW_FT_DEFINE_RASTER_FUNCS(class_, raster_new_, raster_reset_, \ + raster_render_, raster_done_) \ + const SW_FT_Raster_Funcs class_ = {raster_new_, raster_reset_, \ + raster_render_, raster_done_}; + +#ifndef SW_FT_MEM_SET +#define SW_FT_MEM_SET(d, s, c) ft_memset(d, s, c) +#endif + +#ifndef SW_FT_MEM_ZERO +#define SW_FT_MEM_ZERO(dest, count) SW_FT_MEM_SET(dest, 0, count) +#endif + +/* as usual, for the speed hungry :-) */ + +#undef RAS_ARG +#undef RAS_ARG_ +#undef RAS_VAR +#undef RAS_VAR_ + +#ifndef SW_FT_STATIC_RASTER + +#define RAS_ARG gray_PWorker worker +#define RAS_ARG_ gray_PWorker worker, + +#define RAS_VAR worker +#define RAS_VAR_ worker, + +#else /* SW_FT_STATIC_RASTER */ + +#define RAS_ARG /* empty */ +#define RAS_ARG_ /* empty */ +#define RAS_VAR /* empty */ +#define RAS_VAR_ /* empty */ + +#endif /* SW_FT_STATIC_RASTER */ + +/* must be at least 6 bits! */ +#define PIXEL_BITS 8 + +#undef FLOOR +#undef CEILING +#undef TRUNC +#undef SCALED + +#define ONE_PIXEL (1L << PIXEL_BITS) +#define PIXEL_MASK (-1L << PIXEL_BITS) +#define TRUNC(x) ((TCoord)((x) >> PIXEL_BITS)) +#define SUBPIXELS(x) ((TPos)(x) << PIXEL_BITS) +#define FLOOR(x) ((x) & -ONE_PIXEL) +#define CEILING(x) (((x) + ONE_PIXEL - 1) & -ONE_PIXEL) +#define ROUND(x) (((x) + ONE_PIXEL / 2) & -ONE_PIXEL) + +#if PIXEL_BITS >= 6 +#define UPSCALE(x) ((x) << (PIXEL_BITS - 6)) +#define DOWNSCALE(x) ((x) >> (PIXEL_BITS - 6)) +#else +#define UPSCALE(x) ((x) >> (6 - PIXEL_BITS)) +#define DOWNSCALE(x) ((x) << (6 - PIXEL_BITS)) +#endif + +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) % (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT + +#ifdef __arm__ +/* Work around a bug specific to GCC which make the compiler fail to */ +/* optimize a division and modulo operation on the same parameters */ +/* into a single call to `__aeabi_idivmod'. See */ +/* */ +/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */ +#undef SW_FT_DIV_MOD +#define SW_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \ + SW_FT_BEGIN_STMNT(quotient) = (type)((dividend) / (divisor)); \ + (remainder) = (type)((dividend) - (quotient) * (divisor)); \ + if ((remainder) < 0) { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ + SW_FT_END_STMNT +#endif /* __arm__ */ + +/* These macros speed up repetitive divisions by replacing them */ +/* with multiplications and right shifts. */ +#define SW_FT_UDIVPREP(b) \ + long b##_r = (long)(SW_FT_ULONG_MAX >> PIXEL_BITS) / (b) +#define SW_FT_UDIV(a, b) \ + (((unsigned long)(a) * (unsigned long)(b##_r)) >> \ + (sizeof(long) * SW_FT_CHAR_BIT - PIXEL_BITS)) + +/*************************************************************************/ +/* */ +/* TYPE DEFINITIONS */ +/* */ + +/* don't change the following types to SW_FT_Int or SW_FT_Pos, since we might */ +/* need to define them to "float" or "double" when experimenting with */ +/* new algorithms */ + +typedef long TCoord; /* integer scanline/pixel coordinate */ +typedef long TPos; /* sub-pixel coordinate */ + +/* determine the type used to store cell areas. This normally takes at */ +/* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ +/* `long' instead of `int', otherwise bad things happen */ + +#if PIXEL_BITS <= 7 + +typedef int TArea; + +#else /* PIXEL_BITS >= 8 */ + +/* approximately determine the size of integers using an ANSI-C header */ +#if SW_FT_UINT_MAX == 0xFFFFU +typedef long TArea; +#else +typedef int TArea; +#endif + +#endif /* PIXEL_BITS >= 8 */ + +/* maximum number of gray spans in a call to the span callback */ +#define SW_FT_MAX_GRAY_SPANS 256 + +typedef struct TCell_* PCell; + +typedef struct TCell_ { + TPos x; /* same with gray_TWorker.ex */ + TCoord cover; /* same with gray_TWorker.cover */ + TArea area; + PCell next; + +} TCell; + +#if defined(_MSC_VER) /* Visual C++ (and Intel C++) */ +/* We disable the warning `structure was padded due to */ +/* __declspec(align())' in order to compile cleanly with */ +/* the maximum level of warnings. */ +#pragma warning(push) +#pragma warning(disable : 4324) +#endif /* _MSC_VER */ + +typedef struct gray_TWorker_ { + TCoord ex, ey; + TPos min_ex, max_ex; + TPos min_ey, max_ey; + TPos count_ex, count_ey; + + TArea area; + TCoord cover; + int invalid; + + PCell cells; + SW_FT_PtrDist max_cells; + SW_FT_PtrDist num_cells; + + TPos x, y; + + SW_FT_Vector bez_stack[32 * 3 + 1]; + int lev_stack[32]; + + SW_FT_Outline outline; + SW_FT_BBox clip_box; + + int bound_left; + int bound_top; + int bound_right; + int bound_bottom; + + SW_FT_Span gray_spans[SW_FT_MAX_GRAY_SPANS]; + int num_gray_spans; + + SW_FT_Raster_Span_Func render_span; + void* render_span_data; + + int band_size; + int band_shoot; + + ft_jmp_buf jump_buffer; + + void* buffer; + long buffer_size; + + PCell* ycells; + TPos ycount; + +} gray_TWorker, *gray_PWorker; + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#ifndef SW_FT_STATIC_RASTER +#define ras (*worker) +#else +static gray_TWorker ras; +#endif + +typedef struct gray_TRaster_ { + void* memory; + +} gray_TRaster, *gray_PRaster; + +/*************************************************************************/ +/* */ +/* Initialize the cells table. */ +/* */ +static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size) +{ + ras.buffer = buffer; + ras.buffer_size = byte_size; + + ras.ycells = (PCell*)buffer; + ras.cells = NULL; + ras.max_cells = 0; + ras.num_cells = 0; + ras.area = 0; + ras.cover = 0; + ras.invalid = 1; + + ras.bound_left = INT_MAX; + ras.bound_top = INT_MAX; + ras.bound_right = INT_MIN; + ras.bound_bottom = INT_MIN; +} + +/*************************************************************************/ +/* */ +/* Compute the outline bounding box. */ +/* */ +static void gray_compute_cbox(RAS_ARG) +{ + SW_FT_Outline* outline = &ras.outline; + SW_FT_Vector* vec = outline->points; + SW_FT_Vector* limit = vec + outline->n_points; + + if (outline->n_points <= 0) { + ras.min_ex = ras.max_ex = 0; + ras.min_ey = ras.max_ey = 0; + return; + } + + ras.min_ex = ras.max_ex = vec->x; + ras.min_ey = ras.max_ey = vec->y; + + vec++; + + for (; vec < limit; vec++) { + TPos x = vec->x; + TPos y = vec->y; + + if (x < ras.min_ex) ras.min_ex = x; + if (x > ras.max_ex) ras.max_ex = x; + if (y < ras.min_ey) ras.min_ey = y; + if (y > ras.max_ey) ras.max_ey = y; + } + + /* truncate the bounding box to integer pixels */ + ras.min_ex = ras.min_ex >> 6; + ras.min_ey = ras.min_ey >> 6; + ras.max_ex = (ras.max_ex + 63) >> 6; + ras.max_ey = (ras.max_ey + 63) >> 6; +} + +/*************************************************************************/ +/* */ +/* Record the current cell in the table. */ +/* */ +static PCell gray_find_cell(RAS_ARG) +{ + PCell *pcell, cell; + TPos x = ras.ex; + + if (x > ras.count_ex) x = ras.count_ex; + + pcell = &ras.ycells[ras.ey]; + for (;;) { + cell = *pcell; + if (cell == NULL || cell->x > x) break; + + if (cell->x == x) goto Exit; + + pcell = &cell->next; + } + + if (ras.num_cells >= ras.max_cells) ft_longjmp(ras.jump_buffer, 1); + + cell = ras.cells + ras.num_cells++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + + cell->next = *pcell; + *pcell = cell; + +Exit: + return cell; +} + +static void gray_record_cell(RAS_ARG) +{ + if (ras.area | ras.cover) { + PCell cell = gray_find_cell(RAS_VAR); + + cell->area += ras.area; + cell->cover += ras.cover; + } +} + +/*************************************************************************/ +/* */ +/* Set the current cell to a new position. */ +/* */ +static void gray_set_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the */ + /* min_ex - 1 horizontal position. */ + ey -= ras.min_ey; + + if (ex > ras.max_ex) ex = ras.max_ex; + + ex -= ras.min_ex; + if (ex < 0) ex = -1; + + /* are we moving to a different cell ? */ + if (ex != ras.ex || ey != ras.ey) { + /* record the current one if it is valid */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex; + ras.ey = ey; + } + + ras.invalid = + ((unsigned)ey >= (unsigned)ras.count_ey || ex >= ras.count_ex); +} + +/*************************************************************************/ +/* */ +/* Start a new contour at a given cell. */ +/* */ +static void gray_start_cell(RAS_ARG_ TCoord ex, TCoord ey) +{ + if (ex > ras.max_ex) ex = (TCoord)(ras.max_ex); + + if (ex < ras.min_ex) ex = (TCoord)(ras.min_ex - 1); + + ras.area = 0; + ras.cover = 0; + ras.ex = ex - ras.min_ex; + ras.ey = ey - ras.min_ey; + ras.invalid = 0; + + gray_set_cell(RAS_VAR_ ex, ey); +} + +/*************************************************************************/ +/* */ +/* Render a straight line across multiple cells in any direction. */ +/* */ +static void gray_render_line(RAS_ARG_ TPos to_x, TPos to_y) +{ + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + ex1 = TRUNC(ras.x); + ex2 = TRUNC(to_x); + ey1 = TRUNC(ras.y); + ey2 = TRUNC(to_y); + + /* perform vertical clipping */ + if ((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || + (ey1 < ras.min_ey && ey2 < ras.min_ey)) + goto End; + + dx = to_x - ras.x; + dy = to_y - ras.y; + + fx1 = ras.x - SUBPIXELS(ex1); + fy1 = ras.y - SUBPIXELS(ey1); + + if (ex1 == ex2 && ey1 == ey2) /* inside one cell */ + ; + else if (dy == 0) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell(RAS_VAR_ ex1, ey1); + } else if (dx == 0) { + if (dy > 0) /* vertical line up */ + do { + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + else /* vertical line down */ + do { + fy2 = 0; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ey1 != ey2); + } else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + SW_FT_UDIVPREP(dx); + SW_FT_UDIVPREP(dy); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + if (prod <= 0 && prod - dx * ONE_PIXEL > 0) /* left */ + { + fx2 = 0; + fy2 = (TPos)SW_FT_UDIV(-prod, -dx); + prod -= dy * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } else if (prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)SW_FT_UDIV(-prod, dy); + fy2 = ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = 0; + ey1++; + } else if (prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)SW_FT_UDIV(prod, dx); + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = 0; + fy1 = fy2; + ex1++; + } else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)SW_FT_UDIV(prod, -dy); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell(RAS_VAR_ ex1, ey1); + } while (ex1 != ex2 || ey1 != ey2); + } + + fx2 = to_x - SUBPIXELS(ex2); + fy2 = to_y - SUBPIXELS(ey2); + + ras.cover += (fy2 - fy1); + ras.area += (fy2 - fy1) * (fx1 + fx2); + +End: + ras.x = to_x; + ras.y = to_y; +} + +static void gray_split_conic(SW_FT_Vector* base) +{ + TPos a, b; + + base[4].x = base[2].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + base[3].x = b >> 1; + base[2].x = ( a + b ) >> 2; + base[1].x = a >> 1; + + base[4].y = base[2].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + base[3].y = b >> 1; + base[2].y = ( a + b ) >> 2; + base[1].y = a >> 1; +} + +static void gray_render_conic(RAS_ARG_ const SW_FT_Vector* control, + const SW_FT_Vector* to) +{ + TPos dx, dy; + TPos min, max, y; + int top, level; + int* levels; + SW_FT_Vector* arc; + + levels = ras.lev_stack; + + arc = ras.bez_stack; + arc[0].x = UPSCALE(to->x); + arc[0].y = UPSCALE(to->y); + arc[1].x = UPSCALE(control->x); + arc[1].y = UPSCALE(control->y); + arc[2].x = ras.x; + arc[2].y = ras.y; + top = 0; + + dx = SW_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x); + dy = SW_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y); + if (dx < dy) dx = dy; + + if (dx < ONE_PIXEL / 4) goto Draw; + + /* short-cut the arc that crosses the current band */ + min = max = arc[0].y; + + y = arc[1].y; + if (y < min) min = y; + if (y > max) max = y; + + y = arc[2].y; + if (y < min) min = y; + if (y > max) max = y; + + if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw; + + level = 0; + do { + dx >>= 2; + level++; + } while (dx > ONE_PIXEL / 4); + + levels[0] = level; + + do { + level = levels[top]; + if (level > 0) { + gray_split_conic(arc); + arc += 2; + top++; + levels[top] = levels[top - 1] = level - 1; + continue; + } + + Draw: + gray_render_line(RAS_VAR_ arc[0].x, arc[0].y); + top--; + arc -= 2; + + } while (top >= 0); +} + +static void gray_split_cubic(SW_FT_Vector* base) +{ + TPos a, b, c; + + + base[6].x = base[3].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + c = base[2].x + base[3].x; + base[5].x = c >> 1; + c += b; + base[4].x = c >> 2; + base[1].x = a >> 1; + a += b; + base[2].x = a >> 2; + base[3].x = ( a + c ) >> 3; + + base[6].y = base[3].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + c = base[2].y + base[3].y; + base[5].y = c >> 1; + c += b; + base[4].y = c >> 2; + base[1].y = a >> 1; + a += b; + base[2].y = a >> 2; + base[3].y = ( a + c ) >> 3; +} + + +static void +gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1, + const SW_FT_Vector* control2, + const SW_FT_Vector* to) +{ + SW_FT_Vector* arc = ras.bez_stack; + + arc[0].x = UPSCALE( to->x ); + arc[0].y = UPSCALE( to->y ); + arc[1].x = UPSCALE( control2->x ); + arc[1].y = UPSCALE( control2->y ); + arc[2].x = UPSCALE( control1->x ); + arc[2].y = UPSCALE( control1->y ); + arc[3].x = ras.x; + arc[3].y = ras.y; + + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey && + TRUNC( arc[3].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey && + TRUNC( arc[3].y ) < ras.min_ey ) ) + { + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } + + for (;;) + { + /* with each split, control points quickly converge towards */ + /* chord trisection points and the vanishing distances below */ + /* indicate when the segment is flat enough to draw */ + if ( SW_FT_ABS( 2 * arc[0].x - 3 * arc[1].x + arc[3].x ) > ONE_PIXEL / 2 || + SW_FT_ABS( 2 * arc[0].y - 3 * arc[1].y + arc[3].y ) > ONE_PIXEL / 2 || + SW_FT_ABS( arc[0].x - 3 * arc[2].x + 2 * arc[3].x ) > ONE_PIXEL / 2 || + SW_FT_ABS( arc[0].y - 3 * arc[2].y + 2 * arc[3].y ) > ONE_PIXEL / 2 ) + goto Split; + + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == ras.bez_stack ) + return; + + arc -= 3; + continue; + + Split: + gray_split_cubic( arc ); + arc += 3; + } +} + +static int gray_move_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + TPos x, y; + + /* record current cell, if any */ + if (!ras.invalid) gray_record_cell(RAS_VAR); + + /* start to a new position */ + x = UPSCALE(to->x); + y = UPSCALE(to->y); + + gray_start_cell(RAS_VAR_ TRUNC(x), TRUNC(y)); + + worker->x = x; + worker->y = y; + return 0; +} + +static int gray_line_to(const SW_FT_Vector* to, gray_PWorker worker) +{ + gray_render_line(RAS_VAR_ UPSCALE(to->x), UPSCALE(to->y)); + return 0; +} + +static int gray_conic_to(const SW_FT_Vector* control, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_conic(RAS_VAR_ control, to); + return 0; +} + +static int gray_cubic_to(const SW_FT_Vector* control1, + const SW_FT_Vector* control2, const SW_FT_Vector* to, + gray_PWorker worker) +{ + gray_render_cubic(RAS_VAR_ control1, control2, to); + return 0; +} + +static void gray_hline(RAS_ARG_ TCoord x, TCoord y, TPos area, TCoord acount) +{ + int coverage; + + /* compute the coverage line's coverage, depending on the */ + /* outline fill rule */ + /* */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + /* */ + coverage = (int)(area >> (PIXEL_BITS * 2 + 1 - 8)); + /* use range 0..256 */ + if (coverage < 0) coverage = -coverage; + + if (ras.outline.flags & SW_FT_OUTLINE_EVEN_ODD_FILL) { + coverage &= 511; + + if (coverage > 256) + coverage = 512 - coverage; + else if (coverage == 256) + coverage = 255; + } else { + /* normal non-zero winding rule */ + if (coverage >= 256) coverage = 255; + } + + y += (TCoord)ras.min_ey; + x += (TCoord)ras.min_ex; + + /* SW_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ + if (x >= 32767) x = 32767; + + /* SW_FT_Span.y is an integer, so limit our coordinates appropriately */ + if (y >= SW_FT_INT_MAX) y = SW_FT_INT_MAX; + + if (coverage) { + SW_FT_Span* span; + int count; + + // update bounding box. + if (x < ras.bound_left) ras.bound_left = x; + if (y < ras.bound_top) ras.bound_top = y; + if (y > ras.bound_bottom) ras.bound_bottom = y; + if (x + acount > ras.bound_right) ras.bound_right = x + acount; + + /* see whether we can add this span to the current list */ + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if (count > 0 && span->y == y && (int)span->x + span->len == (int)x && + span->coverage == coverage) { + span->len = (unsigned short)(span->len + acount); + return; + } + + if (count >= SW_FT_MAX_GRAY_SPANS) { + if (ras.render_span && count > 0) + ras.render_span(count, ras.gray_spans, ras.render_span_data); + +#ifdef DEBUG_GRAYS + + if (1) { + int n; + + fprintf(stderr, "count = %3d ", count); + span = ras.gray_spans; + for (n = 0; n < count; n++, span++) + fprintf(stderr, "[%d , %d..%d] : %d ", span->y, span->x, + span->x + span->len - 1, span->coverage); + fprintf(stderr, "\n"); + } + +#endif /* DEBUG_GRAYS */ + + ras.num_gray_spans = 0; + + span = ras.gray_spans; + } else + span++; + + /* add a gray span to the current list */ + span->x = (short)x; + span->y = (short)y; + span->len = (unsigned short)acount; + span->coverage = (unsigned char)coverage; + + ras.num_gray_spans++; + } +} + +static void gray_sweep(RAS_ARG) +{ + int yindex; + + if (ras.num_cells == 0) return; + + ras.num_gray_spans = 0; + + for (yindex = 0; yindex < ras.ycount; yindex++) { + PCell cell = ras.ycells[yindex]; + TCoord cover = 0; + TCoord x = 0; + + for (; cell != NULL; cell = cell->next) { + TPos area; + + if (cell->x > x && cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + cell->x - x); + + cover += cell->cover; + area = cover * (ONE_PIXEL * 2) - cell->area; + + if (area != 0 && cell->x >= 0) + gray_hline(RAS_VAR_ cell->x, yindex, area, 1); + + x = cell->x + 1; + } + + if (cover != 0) + gray_hline(RAS_VAR_ x, yindex, cover * (ONE_PIXEL * 2), + ras.count_ex - x); + } + + if (ras.render_span && ras.num_gray_spans > 0) + ras.render_span(ras.num_gray_spans, ras.gray_spans, + ras.render_span_data); +} + +/*************************************************************************/ +/* */ +/* The following function should only compile in stand-alone mode, */ +/* i.e., when building this component without the rest of FreeType. */ +/* */ +/*************************************************************************/ + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Decompose */ +/* */ +/* */ +/* Walk over an outline's structure to decompose it into individual */ +/* segments and Bézier arcs. This function is also able to emit */ +/* `move to' and `close to' operations to indicate the start and end */ +/* of new contours in the outline. */ +/* */ +/* */ +/* outline :: A pointer to the source target. */ +/* */ +/* func_interface :: A table of `emitters', i.e., function pointers */ +/* called during decomposition to indicate path */ +/* operations. */ +/* */ +/* */ +/* user :: A typeless pointer which is passed to each */ +/* emitter during the decomposition. It can be */ +/* used to store the state during the */ +/* decomposition. */ +/* */ +/* */ +/* Error code. 0 means success. */ +/* */ +static int SW_FT_Outline_Decompose(const SW_FT_Outline* outline, + const SW_FT_Outline_Funcs* func_interface, + void* user) +{ +#undef SCALED +#define SCALED(x) (((x) << shift) - delta) + + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + int error; + + int n; /* index of contour in outline */ + int first; /* index of first point in contour */ + char tag; /* current point's state */ + + int shift; + TPos delta; + + if (!outline || !func_interface) return SW_FT_THROW(Invalid_Argument); + + shift = func_interface->shift; + delta = func_interface->delta; + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + int last; /* index of last point in contour */ + + last = outline->contours[n]; + if (last < 0) goto Invalid_Outline; + limit = outline->points + last; + + v_start = outline->points[first]; + v_start.x = SCALED(v_start.x); + v_start.y = SCALED(v_start.y); + + v_last = outline->points[last]; + v_last.x = SCALED(v_last.x); + v_last.y = SCALED(v_last.y); + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* first point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle and record its position */ + /* for closure */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = func_interface->move_to(&v_start, user); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->line_to(&vec, user); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = SCALED(point->x); + v_control.y = SCALED(point->y); + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + func_interface->conic_to(&v_control, &vec, user); + if (error) goto Exit; + continue; + } + + if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + func_interface->conic_to(&v_control, &v_middle, user); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = func_interface->conic_to(&v_control, &v_start, user); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1.x = SCALED(point[-2].x); + vec1.y = SCALED(point[-2].y); + + vec2.x = SCALED(point[-1].x); + vec2.y = SCALED(point[-1].y); + + if (point <= limit) { + SW_FT_Vector vec; + + vec.x = SCALED(point->x); + vec.y = SCALED(point->y); + + error = func_interface->cubic_to(&vec1, &vec2, &vec, user); + if (error) goto Exit; + continue; + } + + error = func_interface->cubic_to(&vec1, &vec2, &v_start, user); + goto Close; + } + } + } + + /* close the contour with a line segment */ + error = func_interface->line_to(&v_start, user); + + Close: + if (error) goto Exit; + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return SW_FT_THROW(Invalid_Outline); +} + +typedef struct gray_TBand_ { + TPos min, max; + +} gray_TBand; + +SW_FT_DEFINE_OUTLINE_FUNCS(func_interface, + (SW_FT_Outline_MoveTo_Func)gray_move_to, + (SW_FT_Outline_LineTo_Func)gray_line_to, + (SW_FT_Outline_ConicTo_Func)gray_conic_to, + (SW_FT_Outline_CubicTo_Func)gray_cubic_to, 0, 0) + +static int gray_convert_glyph_inner(RAS_ARG) +{ + volatile int error = 0; + + if (ft_setjmp(ras.jump_buffer) == 0) { + error = SW_FT_Outline_Decompose(&ras.outline, &func_interface, &ras); + if (!ras.invalid) gray_record_cell(RAS_VAR); + } else + error = SW_FT_THROW(Memory_Overflow); + + return error; +} + +static int gray_convert_glyph(RAS_ARG) +{ + gray_TBand bands[40]; + gray_TBand* volatile band; + int volatile n, num_bands; + TPos volatile min, max, max_y; + SW_FT_BBox* clip; + + /* Set up state in the raster object */ + gray_compute_cbox(RAS_VAR); + + /* clip to target bitmap, exit if nothing to do */ + clip = &ras.clip_box; + + if (ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || + ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax) + return 0; + + if (ras.min_ex < clip->xMin) ras.min_ex = clip->xMin; + if (ras.min_ey < clip->yMin) ras.min_ey = clip->yMin; + + if (ras.max_ex > clip->xMax) ras.max_ex = clip->xMax; + if (ras.max_ey > clip->yMax) ras.max_ey = clip->yMax; + + ras.count_ex = ras.max_ex - ras.min_ex; + ras.count_ey = ras.max_ey - ras.min_ey; + + /* set up vertical bands */ + num_bands = (int)((ras.max_ey - ras.min_ey) / ras.band_size); + if (num_bands == 0) num_bands = 1; + if (num_bands >= 39) num_bands = 39; + + ras.band_shoot = 0; + + min = ras.min_ey; + max_y = ras.max_ey; + + for (n = 0; n < num_bands; n++, min = max) { + max = min + ras.band_size; + if (n == num_bands - 1 || max > max_y) max = max_y; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + TPos bottom, top, middle; + int error; + + { + PCell cells_max; + int yindex; + long cell_start, cell_end, cell_mod; + + ras.ycells = (PCell*)ras.buffer; + ras.ycount = band->max - band->min; + + cell_start = sizeof(PCell) * ras.ycount; + cell_mod = cell_start % sizeof(TCell); + if (cell_mod > 0) cell_start += sizeof(TCell) - cell_mod; + + cell_end = ras.buffer_size; + cell_end -= cell_end % sizeof(TCell); + + cells_max = (PCell)((char*)ras.buffer + cell_end); + ras.cells = (PCell)((char*)ras.buffer + cell_start); + if (ras.cells >= cells_max) goto ReduceBands; + + ras.max_cells = cells_max - ras.cells; + if (ras.max_cells < 2) goto ReduceBands; + + for (yindex = 0; yindex < ras.ycount; yindex++) + ras.ycells[yindex] = NULL; + } + + ras.num_cells = 0; + ras.invalid = 1; + ras.min_ey = band->min; + ras.max_ey = band->max; + ras.count_ey = band->max - band->min; + + error = gray_convert_glyph_inner(RAS_VAR); + + if (!error) { + gray_sweep(RAS_VAR); + band--; + continue; + } else if (error != ErrRaster_Memory_Overflow) + return 1; + + ReduceBands: + /* render pool overflow; we will reduce the render band by half */ + bottom = band->min; + top = band->max; + middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must */ + /* be some problems. */ + if (middle == bottom) { + return 1; + } + + if (bottom - top >= ras.band_size) ras.band_shoot++; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + band++; + } + } + + if (ras.band_shoot > 8 && ras.band_size > 16) + ras.band_size = ras.band_size / 2; + + return 0; +} + +static int gray_raster_render(gray_PRaster raster, + const SW_FT_Raster_Params* params) +{ + SW_FT_UNUSED(raster); + const SW_FT_Outline* outline = (const SW_FT_Outline*)params->source; + + gray_TWorker worker[1]; + + TCell buffer[SW_FT_RENDER_POOL_SIZE / sizeof(TCell)]; + long buffer_size = sizeof(buffer); + int band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8)); + + if (!outline) return SW_FT_THROW(Invalid_Outline); + + /* return immediately if the outline is empty */ + if (outline->n_points == 0 || outline->n_contours <= 0) return 0; + + if (!outline->contours || !outline->points) + return SW_FT_THROW(Invalid_Outline); + + if (outline->n_points != outline->contours[outline->n_contours - 1] + 1) + return SW_FT_THROW(Invalid_Outline); + + /* this version does not support monochrome rendering */ + if (!(params->flags & SW_FT_RASTER_FLAG_AA)) + return SW_FT_THROW(Invalid_Mode); + + if (params->flags & SW_FT_RASTER_FLAG_CLIP) + ras.clip_box = params->clip_box; + else { + ras.clip_box.xMin = -32768L; + ras.clip_box.yMin = -32768L; + ras.clip_box.xMax = 32767L; + ras.clip_box.yMax = 32767L; + } + + gray_init_cells(RAS_VAR_ buffer, buffer_size); + + ras.outline = *outline; + ras.num_cells = 0; + ras.invalid = 1; + ras.band_size = band_size; + ras.num_gray_spans = 0; + + ras.render_span = (SW_FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + gray_convert_glyph(RAS_VAR); + params->bbox_cb(ras.bound_left, ras.bound_top, + ras.bound_right - ras.bound_left, + ras.bound_bottom - ras.bound_top + 1, params->user); + return 1; +} + +/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/ +/**** a static object. *****/ + +static int gray_raster_new(SW_FT_Raster* araster) +{ + static gray_TRaster the_raster; + + *araster = (SW_FT_Raster)&the_raster; + SW_FT_MEM_ZERO(&the_raster, sizeof(the_raster)); + + return 0; +} + +static void gray_raster_done(SW_FT_Raster raster) +{ + /* nothing */ + SW_FT_UNUSED(raster); +} + +static void gray_raster_reset(SW_FT_Raster raster, char* pool_base, + long pool_size) +{ + SW_FT_UNUSED(raster); + SW_FT_UNUSED(pool_base); + SW_FT_UNUSED(pool_size); +} + +SW_FT_DEFINE_RASTER_FUNCS(sw_ft_grays_raster, + + (SW_FT_Raster_New_Func)gray_raster_new, + (SW_FT_Raster_Reset_Func)gray_raster_reset, + (SW_FT_Raster_Render_Func)gray_raster_render, + (SW_FT_Raster_Done_Func)gray_raster_done) + +/* END */ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.h new file mode 100644 index 0000000000..7897289fc5 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_raster.h @@ -0,0 +1,607 @@ +#ifndef V_FT_IMG_H +#define V_FT_IMG_H +/***************************************************************************/ +/* */ +/* ftimage.h */ +/* */ +/* FreeType glyph image formats and default raster interface */ +/* (specification). */ +/* */ +/* Copyright 1996-2010, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + + /*************************************************************************/ + /* */ + /* Note: A `raster' is simply a scan-line converter, used to render */ + /* SW_FT_Outlines into SW_FT_Bitmaps. */ + /* */ + /*************************************************************************/ + +#include "v_ft_types.h" + + /*************************************************************************/ + /* */ + /* */ + /* FT_BBox */ + /* */ + /* */ + /* A structure used to hold an outline's bounding box, i.e., the */ + /* coordinates of its extrema in the horizontal and vertical */ + /* directions. */ + /* */ + /* */ + /* xMin :: The horizontal minimum (left-most). */ + /* */ + /* yMin :: The vertical minimum (bottom-most). */ + /* */ + /* xMax :: The horizontal maximum (right-most). */ + /* */ + /* yMax :: The vertical maximum (top-most). */ + /* */ + /* */ + /* The bounding box is specified with the coordinates of the lower */ + /* left and the upper right corner. In PostScript, those values are */ + /* often called (llx,lly) and (urx,ury), respectively. */ + /* */ + /* If `yMin' is negative, this value gives the glyph's descender. */ + /* Otherwise, the glyph doesn't descend below the baseline. */ + /* Similarly, if `ymax' is positive, this value gives the glyph's */ + /* ascender. */ + /* */ + /* `xMin' gives the horizontal distance from the glyph's origin to */ + /* the left edge of the glyph's bounding box. If `xMin' is negative, */ + /* the glyph extends to the left of the origin. */ + /* */ + typedef struct SW_FT_BBox_ + { + SW_FT_Pos xMin, yMin; + SW_FT_Pos xMax, yMax; + + } SW_FT_BBox; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline */ +/* */ +/* */ +/* This structure is used to describe an outline to the scan-line */ +/* converter. */ +/* */ +/* */ +/* n_contours :: The number of contours in the outline. */ +/* */ +/* n_points :: The number of points in the outline. */ +/* */ +/* points :: A pointer to an array of `n_points' @SW_FT_Vector */ +/* elements, giving the outline's point coordinates. */ +/* */ +/* tags :: A pointer to an array of `n_points' chars, giving */ +/* each outline point's type. */ +/* */ +/* If bit~0 is unset, the point is `off' the curve, */ +/* i.e., a Bézier control point, while it is `on' if */ +/* set. */ +/* */ +/* Bit~1 is meaningful for `off' points only. If set, */ +/* it indicates a third-order Bézier arc control point; */ +/* and a second-order control point if unset. */ +/* */ +/* If bit~2 is set, bits 5-7 contain the drop-out mode */ +/* (as defined in the OpenType specification; the value */ +/* is the same as the argument to the SCANMODE */ +/* instruction). */ +/* */ +/* Bits 3 and~4 are reserved for internal purposes. */ +/* */ +/* contours :: An array of `n_contours' shorts, giving the end */ +/* point of each contour within the outline. For */ +/* example, the first contour is defined by the points */ +/* `0' to `contours[0]', the second one is defined by */ +/* the points `contours[0]+1' to `contours[1]', etc. */ +/* */ +/* flags :: A set of bit flags used to characterize the outline */ +/* and give hints to the scan-converter and hinter on */ +/* how to convert/grid-fit it. See @SW_FT_OUTLINE_FLAGS.*/ +/* */ +typedef struct SW_FT_Outline_ +{ + short n_contours; /* number of contours in glyph */ + short n_points; /* number of points in the glyph */ + + SW_FT_Vector* points; /* the outline's points */ + char* tags; /* the points flags */ + short* contours; /* the contour end points */ + char* contours_flag; /* the contour open flags */ + + int flags; /* outline masks */ + +} SW_FT_Outline; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_OUTLINE_FLAGS */ + /* */ + /* */ + /* A list of bit-field constants use for the flags in an outline's */ + /* `flags' field. */ + /* */ + /* */ + /* SW_FT_OUTLINE_NONE :: */ + /* Value~0 is reserved. */ + /* */ + /* SW_FT_OUTLINE_OWNER :: */ + /* If set, this flag indicates that the outline's field arrays */ + /* (i.e., `points', `flags', and `contours') are `owned' by the */ + /* outline object, and should thus be freed when it is destroyed. */ + /* */ + /* SW_FT_OUTLINE_EVEN_ODD_FILL :: */ + /* By default, outlines are filled using the non-zero winding rule. */ + /* If set to 1, the outline will be filled using the even-odd fill */ + /* rule (only works with the smooth rasterizer). */ + /* */ + /* SW_FT_OUTLINE_REVERSE_FILL :: */ + /* By default, outside contours of an outline are oriented in */ + /* clock-wise direction, as defined in the TrueType specification. */ + /* This flag is set if the outline uses the opposite direction */ + /* (typically for Type~1 fonts). This flag is ignored by the scan */ + /* converter. */ + /* */ + /* */ + /* */ + /* There exists a second mechanism to pass the drop-out mode to the */ + /* B/W rasterizer; see the `tags' field in @SW_FT_Outline. */ + /* */ + /* Please refer to the description of the `SCANTYPE' instruction in */ + /* the OpenType specification (in file `ttinst1.doc') how simple */ + /* drop-outs, smart drop-outs, and stubs are defined. */ + /* */ +#define SW_FT_OUTLINE_NONE 0x0 +#define SW_FT_OUTLINE_OWNER 0x1 +#define SW_FT_OUTLINE_EVEN_ODD_FILL 0x2 +#define SW_FT_OUTLINE_REVERSE_FILL 0x4 + + /* */ + +#define SW_FT_CURVE_TAG( flag ) ( flag & 3 ) + +#define SW_FT_CURVE_TAG_ON 1 +#define SW_FT_CURVE_TAG_CONIC 0 +#define SW_FT_CURVE_TAG_CUBIC 2 + + +#define SW_FT_Curve_Tag_On SW_FT_CURVE_TAG_ON +#define SW_FT_Curve_Tag_Conic SW_FT_CURVE_TAG_CONIC +#define SW_FT_Curve_Tag_Cubic SW_FT_CURVE_TAG_CUBIC + + /*************************************************************************/ + /* */ + /* A raster is a scan converter, in charge of rendering an outline into */ + /* a a bitmap. This section contains the public API for rasters. */ + /* */ + /* Note that in FreeType 2, all rasters are now encapsulated within */ + /* specific modules called `renderers'. See `ftrender.h' for more */ + /* details on renderers. */ + /* */ + /*************************************************************************/ + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster */ + /* */ + /* */ + /* A handle (pointer) to a raster object. Each object can be used */ + /* independently to convert an outline into a bitmap or pixmap. */ + /* */ + typedef struct SW_FT_RasterRec_* SW_FT_Raster; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Span */ + /* */ + /* */ + /* A structure used to model a single span of gray (or black) pixels */ + /* when rendering a monochrome or anti-aliased bitmap. */ + /* */ + /* */ + /* x :: The span's horizontal start position. */ + /* */ + /* len :: The span's length in pixels. */ + /* */ + /* coverage :: The span color/coverage, ranging from 0 (background) */ + /* to 255 (foreground). Only used for anti-aliased */ + /* rendering. */ + /* */ + /* */ + /* This structure is used by the span drawing callback type named */ + /* @SW_FT_SpanFunc that takes the y~coordinate of the span as a */ + /* parameter. */ + /* */ + /* The coverage value is always between 0 and 255. If you want less */ + /* gray values, the callback function has to reduce them. */ + /* */ + typedef struct SW_FT_Span_ + { + short x; + short y; + unsigned short len; + unsigned char coverage; + + } SW_FT_Span; + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_SpanFunc */ + /* */ + /* */ + /* A function used as a call-back by the anti-aliased renderer in */ + /* order to let client applications draw themselves the gray pixel */ + /* spans on each scan line. */ + /* */ + /* */ + /* y :: The scanline's y~coordinate. */ + /* */ + /* count :: The number of spans to draw on this scanline. */ + /* */ + /* spans :: A table of `count' spans to draw on the scanline. */ + /* */ + /* user :: User-supplied data that is passed to the callback. */ + /* */ + /* */ + /* This callback allows client applications to directly render the */ + /* gray spans of the anti-aliased bitmap to any kind of surfaces. */ + /* */ + /* This can be used to write anti-aliased outlines directly to a */ + /* given background bitmap, and even perform translucency. */ + /* */ + /* Note that the `count' field cannot be greater than a fixed value */ + /* defined by the `SW_FT_MAX_GRAY_SPANS' configuration macro in */ + /* `ftoption.h'. By default, this value is set to~32, which means */ + /* that if there are more than 32~spans on a given scanline, the */ + /* callback is called several times with the same `y' parameter in */ + /* order to draw all callbacks. */ + /* */ + /* Otherwise, the callback is only called once per scan-line, and */ + /* only for those scanlines that do have `gray' pixels on them. */ + /* */ + typedef void + (*SW_FT_SpanFunc)( int count, + const SW_FT_Span* spans, + void* user ); + + typedef void + (*SW_FT_BboxFunc)( int x, int y, int w, int h, + void* user); + +#define SW_FT_Raster_Span_Func SW_FT_SpanFunc + + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_XXX */ + /* */ + /* */ + /* A list of bit flag constants as used in the `flags' field of a */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* */ + /* SW_FT_RASTER_FLAG_DEFAULT :: This value is 0. */ + /* */ + /* SW_FT_RASTER_FLAG_AA :: This flag is set to indicate that an */ + /* anti-aliased glyph image should be */ + /* generated. Otherwise, it will be */ + /* monochrome (1-bit). */ + /* */ + /* SW_FT_RASTER_FLAG_DIRECT :: This flag is set to indicate direct */ + /* rendering. In this mode, client */ + /* applications must provide their own span */ + /* callback. This lets them directly */ + /* draw or compose over an existing bitmap. */ + /* If this bit is not set, the target */ + /* pixmap's buffer _must_ be zeroed before */ + /* rendering. */ + /* */ + /* Note that for now, direct rendering is */ + /* only possible with anti-aliased glyphs. */ + /* */ + /* SW_FT_RASTER_FLAG_CLIP :: This flag is only used in direct */ + /* rendering mode. If set, the output will */ + /* be clipped to a box specified in the */ + /* `clip_box' field of the */ + /* @SW_FT_Raster_Params structure. */ + /* */ + /* Note that by default, the glyph bitmap */ + /* is clipped to the target pixmap, except */ + /* in direct rendering mode where all spans */ + /* are generated if no clipping box is set. */ + /* */ +#define SW_FT_RASTER_FLAG_DEFAULT 0x0 +#define SW_FT_RASTER_FLAG_AA 0x1 +#define SW_FT_RASTER_FLAG_DIRECT 0x2 +#define SW_FT_RASTER_FLAG_CLIP 0x4 + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Params */ + /* */ + /* */ + /* A structure to hold the arguments used by a raster's render */ + /* function. */ + /* */ + /* */ + /* target :: The target bitmap. */ + /* */ + /* source :: A pointer to the source glyph image (e.g., an */ + /* @SW_FT_Outline). */ + /* */ + /* flags :: The rendering flags. */ + /* */ + /* gray_spans :: The gray span drawing callback. */ + /* */ + /* black_spans :: The black span drawing callback. UNIMPLEMENTED! */ + /* */ + /* bit_test :: The bit test callback. UNIMPLEMENTED! */ + /* */ + /* bit_set :: The bit set callback. UNIMPLEMENTED! */ + /* */ + /* user :: User-supplied data that is passed to each drawing */ + /* callback. */ + /* */ + /* clip_box :: An optional clipping box. It is only used in */ + /* direct rendering mode. Note that coordinates here */ + /* should be expressed in _integer_ pixels (and not in */ + /* 26.6 fixed-point units). */ + /* */ + /* */ + /* An anti-aliased glyph bitmap is drawn if the @SW_FT_RASTER_FLAG_AA */ + /* bit flag is set in the `flags' field, otherwise a monochrome */ + /* bitmap is generated. */ + /* */ + /* If the @SW_FT_RASTER_FLAG_DIRECT bit flag is set in `flags', the */ + /* raster will call the `gray_spans' callback to draw gray pixel */ + /* spans, in the case of an aa glyph bitmap, it will call */ + /* `black_spans', and `bit_test' and `bit_set' in the case of a */ + /* monochrome bitmap. This allows direct composition over a */ + /* pre-existing bitmap through user-provided callbacks to perform the */ + /* span drawing/composition. */ + /* */ + /* Note that the `bit_test' and `bit_set' callbacks are required when */ + /* rendering a monochrome bitmap, as they are crucial to implement */ + /* correct drop-out control as defined in the TrueType specification. */ + /* */ + typedef struct SW_FT_Raster_Params_ + { + const void* source; + int flags; + SW_FT_SpanFunc gray_spans; + SW_FT_BboxFunc bbox_cb; + void* user; + SW_FT_BBox clip_box; + + } SW_FT_Raster_Params; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Check */ +/* */ +/* */ +/* Check the contents of an outline descriptor. */ +/* */ +/* */ +/* outline :: A handle to a source outline. */ +/* */ +/* */ +/* FreeType error code. 0~means success. */ +/* */ +SW_FT_Error +SW_FT_Outline_Check( SW_FT_Outline* outline ); + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Outline_Get_CBox */ +/* */ +/* */ +/* Return an outline's `control box'. The control box encloses all */ +/* the outline's points, including Bézier control points. Though it */ +/* coincides with the exact bounding box for most glyphs, it can be */ +/* slightly larger in some situations (like when rotating an outline */ +/* that contains Bézier outside arcs). */ +/* */ +/* Computing the control box is very fast, while getting the bounding */ +/* box can take much more time as it needs to walk over all segments */ +/* and arcs in the outline. To get the latter, you can use the */ +/* `ftbbox' component, which is dedicated to this single task. */ +/* */ +/* */ +/* outline :: A pointer to the source outline descriptor. */ +/* */ +/* */ +/* acbox :: The outline's control box. */ +/* */ +/* */ +/* See @SW_FT_Glyph_Get_CBox for a discussion of tricky fonts. */ +/* */ +void +SW_FT_Outline_Get_CBox( const SW_FT_Outline* outline, + SW_FT_BBox *acbox ); + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_NewFunc */ + /* */ + /* */ + /* A function used to create a new raster object. */ + /* */ + /* */ + /* memory :: A handle to the memory allocator. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The `memory' parameter is a typeless pointer in order to avoid */ + /* un-wanted dependencies on the rest of the FreeType code. In */ + /* practice, it is an @SW_FT_Memory object, i.e., a handle to the */ + /* standard FreeType memory allocator. However, this field can be */ + /* completely ignored by a given raster implementation. */ + /* */ + typedef int + (*SW_FT_Raster_NewFunc)( SW_FT_Raster* raster ); + +#define SW_FT_Raster_New_Func SW_FT_Raster_NewFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_DoneFunc */ + /* */ + /* */ + /* A function used to destroy a given raster object. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + typedef void + (*SW_FT_Raster_DoneFunc)( SW_FT_Raster raster ); + +#define SW_FT_Raster_Done_Func SW_FT_Raster_DoneFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_ResetFunc */ + /* */ + /* */ + /* FreeType provides an area of memory called the `render pool', */ + /* available to all registered rasters. This pool can be freely used */ + /* during a given scan-conversion but is shared by all rasters. Its */ + /* content is thus transient. */ + /* */ + /* This function is called each time the render pool changes, or just */ + /* after a new raster object is created. */ + /* */ + /* */ + /* raster :: A handle to the new raster object. */ + /* */ + /* pool_base :: The address in memory of the render pool. */ + /* */ + /* pool_size :: The size in bytes of the render pool. */ + /* */ + /* */ + /* Rasters can ignore the render pool and rely on dynamic memory */ + /* allocation if they want to (a handle to the memory allocator is */ + /* passed to the raster constructor). However, this is not */ + /* recommended for efficiency purposes. */ + /* */ + typedef void + (*SW_FT_Raster_ResetFunc)( SW_FT_Raster raster, + unsigned char* pool_base, + unsigned long pool_size ); + +#define SW_FT_Raster_Reset_Func SW_FT_Raster_ResetFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_RenderFunc */ + /* */ + /* */ + /* Invoke a given raster to scan-convert a given glyph image into a */ + /* target bitmap. */ + /* */ + /* */ + /* raster :: A handle to the raster object. */ + /* */ + /* params :: A pointer to an @SW_FT_Raster_Params structure used to */ + /* store the rendering parameters. */ + /* */ + /* */ + /* Error code. 0~means success. */ + /* */ + /* */ + /* The exact format of the source image depends on the raster's glyph */ + /* format defined in its @SW_FT_Raster_Funcs structure. It can be an */ + /* @SW_FT_Outline or anything else in order to support a large array of */ + /* glyph formats. */ + /* */ + /* Note also that the render function can fail and return a */ + /* `SW_FT_Err_Unimplemented_Feature' error code if the raster used does */ + /* not support direct composition. */ + /* */ + /* XXX: For now, the standard raster doesn't support direct */ + /* composition but this should change for the final release (see */ + /* the files `demos/src/ftgrays.c' and `demos/src/ftgrays2.c' */ + /* for examples of distinct implementations that support direct */ + /* composition). */ + /* */ + typedef int + (*SW_FT_Raster_RenderFunc)( SW_FT_Raster raster, + const SW_FT_Raster_Params* params ); + +#define SW_FT_Raster_Render_Func SW_FT_Raster_RenderFunc + + + /*************************************************************************/ + /* */ + /* */ + /* SW_FT_Raster_Funcs */ + /* */ + /* */ + /* A structure used to describe a given raster class to the library. */ + /* */ + /* */ + /* glyph_format :: The supported glyph format for this raster. */ + /* */ + /* raster_new :: The raster constructor. */ + /* */ + /* raster_reset :: Used to reset the render pool within the raster. */ + /* */ + /* raster_render :: A function to render a glyph into a given bitmap. */ + /* */ + /* raster_done :: The raster destructor. */ + /* */ + typedef struct SW_FT_Raster_Funcs_ + { + SW_FT_Raster_NewFunc raster_new; + SW_FT_Raster_ResetFunc raster_reset; + SW_FT_Raster_RenderFunc raster_render; + SW_FT_Raster_DoneFunc raster_done; + + } SW_FT_Raster_Funcs; + + +extern const SW_FT_Raster_Funcs sw_ft_grays_raster; + +#endif // V_FT_IMG_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.cpp new file mode 100644 index 0000000000..3160f84f9c --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.cpp @@ -0,0 +1,1936 @@ + +/***************************************************************************/ +/* */ +/* ftstroke.c */ +/* */ +/* FreeType path stroker (body). */ +/* */ +/* Copyright 2002-2006, 2008-2011, 2013 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_stroker.h" +#include +#include +#include +#include "v_ft_math.h" + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** BEZIER COMPUTATIONS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SMALL_CONIC_THRESHOLD (SW_FT_ANGLE_PI / 6) +#define SW_FT_SMALL_CUBIC_THRESHOLD (SW_FT_ANGLE_PI / 8) + +#define SW_FT_EPSILON 2 + +#define SW_FT_IS_SMALL(x) ((x) > -SW_FT_EPSILON && (x) < SW_FT_EPSILON) + +static SW_FT_Pos ft_pos_abs(SW_FT_Pos x) +{ + return x >= 0 ? x : -x; +} + +static void ft_conic_split(SW_FT_Vector* base) +{ + SW_FT_Pos a, b; + + base[4].x = base[2].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + base[3].x = b >> 1; + base[2].x = ( a + b ) >> 2; + base[1].x = a >> 1; + + base[4].y = base[2].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + base[3].y = b >> 1; + base[2].y = ( a + b ) >> 2; + base[1].y = a >> 1; +} + +static SW_FT_Bool ft_conic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2; + SW_FT_Angle theta; + SW_FT_Int close1, close2; + + d1.x = base[1].x - base[2].x; + d1.y = base[1].y - base[2].y; + d2.x = base[0].x - base[1].x; + d2.y = base[0].y - base[1].y; + + close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + + if (close1) { + if (close2) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else { + *angle_in = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } else /* !close1 */ + { + if (close2) { + *angle_in = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d2.x, d2.y); + } + } + + theta = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_out)); + + return SW_FT_BOOL(theta < SW_FT_SMALL_CONIC_THRESHOLD); +} + +static void ft_cubic_split(SW_FT_Vector* base) +{ + SW_FT_Pos a, b, c; + + base[6].x = base[3].x; + a = base[0].x + base[1].x; + b = base[1].x + base[2].x; + c = base[2].x + base[3].x; + base[5].x = c >> 1; + c += b; + base[4].x = c >> 2; + base[1].x = a >> 1; + a += b; + base[2].x = a >> 2; + base[3].x = ( a + c ) >> 3; + + base[6].y = base[3].y; + a = base[0].y + base[1].y; + b = base[1].y + base[2].y; + c = base[2].y + base[3].y; + base[5].y = c >> 1; + c += b; + base[4].y = c >> 2; + base[1].y = a >> 1; + a += b; + base[2].y = a >> 2; + base[3].y = ( a + c ) >> 3; +} + +/* Return the average of `angle1' and `angle2'. */ +/* This gives correct result even if `angle1' and `angle2' */ +/* have opposite signs. */ +static SW_FT_Angle ft_angle_mean(SW_FT_Angle angle1, SW_FT_Angle angle2) +{ + return angle1 + SW_FT_Angle_Diff(angle1, angle2) / 2; +} + +static SW_FT_Bool ft_cubic_is_small_enough(SW_FT_Vector* base, + SW_FT_Angle* angle_in, + SW_FT_Angle* angle_mid, + SW_FT_Angle* angle_out) +{ + SW_FT_Vector d1, d2, d3; + SW_FT_Angle theta1, theta2; + SW_FT_Int close1, close2, close3; + + d1.x = base[2].x - base[3].x; + d1.y = base[2].y - base[3].y; + d2.x = base[1].x - base[2].x; + d2.y = base[1].y - base[2].y; + d3.x = base[0].x - base[1].x; + d3.y = base[0].y - base[1].y; + + close1 = SW_FT_IS_SMALL(d1.x) && SW_FT_IS_SMALL(d1.y); + close2 = SW_FT_IS_SMALL(d2.x) && SW_FT_IS_SMALL(d2.y); + close3 = SW_FT_IS_SMALL(d3.x) && SW_FT_IS_SMALL(d3.y); + + if (close1) { + if (close2) { + if (close3) { + /* basically a point; */ + /* do nothing to retain original direction */ + } else /* !close3 */ + { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } else /* !close1 */ + { + if (close2) { + if (close3) { + *angle_in = *angle_mid = *angle_out = SW_FT_Atan2(d1.x, d1.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + *angle_mid = ft_angle_mean(*angle_in, *angle_out); + } + } else /* !close2 */ + { + if (close3) { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = *angle_out = SW_FT_Atan2(d2.x, d2.y); + } else /* !close3 */ + { + *angle_in = SW_FT_Atan2(d1.x, d1.y); + *angle_mid = SW_FT_Atan2(d2.x, d2.y); + *angle_out = SW_FT_Atan2(d3.x, d3.y); + } + } + } + + theta1 = ft_pos_abs(SW_FT_Angle_Diff(*angle_in, *angle_mid)); + theta2 = ft_pos_abs(SW_FT_Angle_Diff(*angle_mid, *angle_out)); + + return SW_FT_BOOL(theta1 < SW_FT_SMALL_CUBIC_THRESHOLD && + theta2 < SW_FT_SMALL_CUBIC_THRESHOLD); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKE BORDERS *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +typedef enum SW_FT_StrokeTags_ { + SW_FT_STROKE_TAG_ON = 1, /* on-curve point */ + SW_FT_STROKE_TAG_CUBIC = 2, /* cubic off-point */ + SW_FT_STROKE_TAG_BEGIN = 4, /* sub-path start */ + SW_FT_STROKE_TAG_END = 8 /* sub-path end */ + +} SW_FT_StrokeTags; + +#define SW_FT_STROKE_TAG_BEGIN_END \ + (SW_FT_STROKE_TAG_BEGIN | SW_FT_STROKE_TAG_END) + +typedef struct SW_FT_StrokeBorderRec_ { + SW_FT_UInt num_points; + SW_FT_UInt max_points; + SW_FT_Vector* points; + SW_FT_Byte* tags; + SW_FT_Bool movable; /* TRUE for ends of lineto borders */ + SW_FT_Int start; /* index of current sub-path start point */ + SW_FT_Bool valid; + +} SW_FT_StrokeBorderRec, *SW_FT_StrokeBorder; + +SW_FT_Error SW_FT_Outline_Check(SW_FT_Outline* outline) +{ + if (outline) { + SW_FT_Int n_points = outline->n_points; + SW_FT_Int n_contours = outline->n_contours; + SW_FT_Int end0, end; + SW_FT_Int n; + + /* empty glyph? */ + if (n_points == 0 && n_contours == 0) return 0; + + /* check point and contour counts */ + if (n_points <= 0 || n_contours <= 0) goto Bad; + + end0 = end = -1; + for (n = 0; n < n_contours; n++) { + end = outline->contours[n]; + + /* note that we don't accept empty contours */ + if (end <= end0 || end >= n_points) goto Bad; + + end0 = end; + } + + if (end != n_points - 1) goto Bad; + + /* XXX: check the tags array */ + return 0; + } + +Bad: + return -1; // SW_FT_THROW( Invalid_Argument ); +} + +void SW_FT_Outline_Get_CBox(const SW_FT_Outline* outline, SW_FT_BBox* acbox) +{ + SW_FT_Pos xMin, yMin, xMax, yMax; + + if (outline && acbox) { + if (outline->n_points == 0) { + xMin = 0; + yMin = 0; + xMax = 0; + yMax = 0; + } else { + SW_FT_Vector* vec = outline->points; + SW_FT_Vector* limit = vec + outline->n_points; + + xMin = xMax = vec->x; + yMin = yMax = vec->y; + vec++; + + for (; vec < limit; vec++) { + SW_FT_Pos x, y; + + x = vec->x; + if (x < xMin) xMin = x; + if (x > xMax) xMax = x; + + y = vec->y; + if (y < yMin) yMin = y; + if (y > yMax) yMax = y; + } + } + acbox->xMin = xMin; + acbox->xMax = xMax; + acbox->yMin = yMin; + acbox->yMax = yMax; + } +} + +static SW_FT_Error ft_stroke_border_grow(SW_FT_StrokeBorder border, + SW_FT_UInt new_points) +{ + SW_FT_UInt old_max = border->max_points; + SW_FT_UInt new_max = border->num_points + new_points; + SW_FT_Error error = 0; + + if (new_max > old_max) { + SW_FT_UInt cur_max = old_max; + + while (cur_max < new_max) cur_max += (cur_max >> 1) + 16; + + border->points = (SW_FT_Vector*)realloc(border->points, + cur_max * sizeof(SW_FT_Vector)); + border->tags = + (SW_FT_Byte*)realloc(border->tags, cur_max * sizeof(SW_FT_Byte)); + + if (!border->points || !border->tags) goto Exit; + + border->max_points = cur_max; + } + +Exit: + return error; +} + +static void ft_stroke_border_close(SW_FT_StrokeBorder border, + SW_FT_Bool reverse) +{ + SW_FT_UInt start = border->start; + SW_FT_UInt count = border->num_points; + + assert(border->start >= 0); + + /* don't record empty paths! */ + if (count <= start + 1U) + border->num_points = start; + else { + /* copy the last point to the start of this sub-path, since */ + /* it contains the `adjusted' starting coordinates */ + border->num_points = --count; + border->points[start] = border->points[count]; + + if (reverse) { + /* reverse the points */ + { + SW_FT_Vector* vec1 = border->points + start + 1; + SW_FT_Vector* vec2 = border->points + count - 1; + + for (; vec1 < vec2; vec1++, vec2--) { + SW_FT_Vector tmp; + + tmp = *vec1; + *vec1 = *vec2; + *vec2 = tmp; + } + } + + /* then the tags */ + { + SW_FT_Byte* tag1 = border->tags + start + 1; + SW_FT_Byte* tag2 = border->tags + count - 1; + + for (; tag1 < tag2; tag1++, tag2--) { + SW_FT_Byte tmp; + + tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + } + } + } + + border->tags[start] |= SW_FT_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_FT_STROKE_TAG_END; + } + + border->start = -1; + border->movable = FALSE; +} + +static SW_FT_Error ft_stroke_border_lineto(SW_FT_StrokeBorder border, + SW_FT_Vector* to, SW_FT_Bool movable) +{ + SW_FT_Error error = 0; + + assert(border->start >= 0); + + if (border->movable) { + /* move last point */ + border->points[border->num_points - 1] = *to; + } else { + /* don't add zero-length lineto */ + if (border->num_points > 0 && + SW_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) && + SW_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y)) + return error; + + /* add one point */ + error = ft_stroke_border_grow(border, 1); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *to; + tag[0] = SW_FT_STROKE_TAG_ON; + + border->num_points += 1; + } + } + border->movable = movable; + return error; +} + +static SW_FT_Error ft_stroke_border_conicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 2); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control; + vec[1] = *to; + + tag[0] = 0; + tag[1] = SW_FT_STROKE_TAG_ON; + + border->num_points += 2; + } + + border->movable = FALSE; + + return error; +} + +static SW_FT_Error ft_stroke_border_cubicto(SW_FT_StrokeBorder border, + SW_FT_Vector* control1, + SW_FT_Vector* control2, + SW_FT_Vector* to) +{ + SW_FT_Error error; + + assert(border->start >= 0); + + error = ft_stroke_border_grow(border, 3); + if (!error) { + SW_FT_Vector* vec = border->points + border->num_points; + SW_FT_Byte* tag = border->tags + border->num_points; + + vec[0] = *control1; + vec[1] = *control2; + vec[2] = *to; + + tag[0] = SW_FT_STROKE_TAG_CUBIC; + tag[1] = SW_FT_STROKE_TAG_CUBIC; + tag[2] = SW_FT_STROKE_TAG_ON; + + border->num_points += 3; + } + + border->movable = FALSE; + + return error; +} + +#define SW_FT_ARC_CUBIC_ANGLE (SW_FT_ANGLE_PI / 2) + + +static SW_FT_Error +ft_stroke_border_arcto( SW_FT_StrokeBorder border, + SW_FT_Vector* center, + SW_FT_Fixed radius, + SW_FT_Angle angle_start, + SW_FT_Angle angle_diff ) +{ + SW_FT_Fixed coef; + SW_FT_Vector a0, a1, a2, a3; + SW_FT_Int i, arcs = 1; + SW_FT_Error error = 0; + + + /* number of cubic arcs to draw */ + while ( angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs || + -angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs ) + arcs++; + + /* control tangents */ + coef = SW_FT_Tan( angle_diff / ( 4 * arcs ) ); + coef += coef / 3; + + /* compute start and first control point */ + SW_FT_Vector_From_Polar( &a0, radius, angle_start ); + a1.x = SW_FT_MulFix( -a0.y, coef ); + a1.y = SW_FT_MulFix( a0.x, coef ); + + a0.x += center->x; + a0.y += center->y; + a1.x += a0.x; + a1.y += a0.y; + + for ( i = 1; i <= arcs; i++ ) + { + /* compute end and second control point */ + SW_FT_Vector_From_Polar( &a3, radius, + angle_start + i * angle_diff / arcs ); + a2.x = SW_FT_MulFix( a3.y, coef ); + a2.y = SW_FT_MulFix( -a3.x, coef ); + + a3.x += center->x; + a3.y += center->y; + a2.x += a3.x; + a2.y += a3.y; + + /* add cubic arc */ + error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 ); + if ( error ) + break; + + /* a0 = a3; */ + a1.x = a3.x - a2.x + a3.x; + a1.y = a3.y - a2.y + a3.y; + } + + return error; +} + +static SW_FT_Error ft_stroke_border_moveto(SW_FT_StrokeBorder border, + SW_FT_Vector* to) +{ + /* close current open path if any ? */ + if (border->start >= 0) ft_stroke_border_close(border, FALSE); + + border->start = border->num_points; + border->movable = FALSE; + + return ft_stroke_border_lineto(border, to, FALSE); +} + +static void ft_stroke_border_init(SW_FT_StrokeBorder border) +{ + border->points = NULL; + border->tags = NULL; + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_reset(SW_FT_StrokeBorder border) +{ + border->num_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static void ft_stroke_border_done(SW_FT_StrokeBorder border) +{ + free(border->points); + free(border->tags); + + border->num_points = 0; + border->max_points = 0; + border->start = -1; + border->valid = FALSE; +} + +static SW_FT_Error ft_stroke_border_get_counts(SW_FT_StrokeBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_Error error = 0; + SW_FT_UInt num_points = 0; + SW_FT_UInt num_contours = 0; + + SW_FT_UInt count = border->num_points; + SW_FT_Vector* point = border->points; + SW_FT_Byte* tags = border->tags; + SW_FT_Int in_contour = 0; + + for (; count > 0; count--, num_points++, point++, tags++) { + if (tags[0] & SW_FT_STROKE_TAG_BEGIN) { + if (in_contour != 0) goto Fail; + + in_contour = 1; + } else if (in_contour == 0) + goto Fail; + + if (tags[0] & SW_FT_STROKE_TAG_END) { + in_contour = 0; + num_contours++; + } + } + + if (in_contour != 0) goto Fail; + + border->valid = TRUE; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; + +Fail: + num_points = 0; + num_contours = 0; + goto Exit; +} + +static void ft_stroke_border_export(SW_FT_StrokeBorder border, + SW_FT_Outline* outline) +{ + /* copy point locations */ + memcpy(outline->points + outline->n_points, border->points, + border->num_points * sizeof(SW_FT_Vector)); + + /* copy tags */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* read = border->tags; + SW_FT_Byte* write = (SW_FT_Byte*)outline->tags + outline->n_points; + + for (; count > 0; count--, read++, write++) { + if (*read & SW_FT_STROKE_TAG_ON) + *write = SW_FT_CURVE_TAG_ON; + else if (*read & SW_FT_STROKE_TAG_CUBIC) + *write = SW_FT_CURVE_TAG_CUBIC; + else + *write = SW_FT_CURVE_TAG_CONIC; + } + } + + /* copy contours */ + { + SW_FT_UInt count = border->num_points; + SW_FT_Byte* tags = border->tags; + SW_FT_Short* write = outline->contours + outline->n_contours; + SW_FT_Short idx = (SW_FT_Short)outline->n_points; + + for (; count > 0; count--, tags++, idx++) { + if (*tags & SW_FT_STROKE_TAG_END) { + *write++ = idx; + outline->n_contours++; + } + } + } + + outline->n_points = (short)(outline->n_points + border->num_points); + + assert(SW_FT_Outline_Check(outline) == 0); +} + +/*************************************************************************/ +/*************************************************************************/ +/***** *****/ +/***** STROKER *****/ +/***** *****/ +/*************************************************************************/ +/*************************************************************************/ + +#define SW_FT_SIDE_TO_ROTATE(s) (SW_FT_ANGLE_PI2 - (s)*SW_FT_ANGLE_PI) + +typedef struct SW_FT_StrokerRec_ { + SW_FT_Angle angle_in; /* direction into curr join */ + SW_FT_Angle angle_out; /* direction out of join */ + SW_FT_Vector center; /* current position */ + SW_FT_Fixed line_length; /* length of last lineto */ + SW_FT_Bool first_point; /* is this the start? */ + SW_FT_Bool subpath_open; /* is the subpath open? */ + SW_FT_Angle subpath_angle; /* subpath start direction */ + SW_FT_Vector subpath_start; /* subpath start position */ + SW_FT_Fixed subpath_line_length; /* subpath start lineto len */ + SW_FT_Bool handle_wide_strokes; /* use wide strokes logic? */ + + SW_FT_Stroker_LineCap line_cap; + SW_FT_Stroker_LineJoin line_join; + SW_FT_Stroker_LineJoin line_join_saved; + SW_FT_Fixed miter_limit; + SW_FT_Fixed radius; + + SW_FT_StrokeBorderRec borders[2]; +} SW_FT_StrokerRec; + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_New(SW_FT_Stroker* astroker) +{ + SW_FT_Error error = 0; /* assigned in SW_FT_NEW */ + SW_FT_Stroker stroker = NULL; + + stroker = (SW_FT_StrokerRec*)calloc(1, sizeof(SW_FT_StrokerRec)); + if (stroker) { + ft_stroke_border_init(&stroker->borders[0]); + ft_stroke_border_init(&stroker->borders[1]); + } + + *astroker = stroker; + + return error; +} + +void SW_FT_Stroker_Rewind(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_reset(&stroker->borders[0]); + ft_stroke_border_reset(&stroker->borders[1]); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Set(SW_FT_Stroker stroker, SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit) +{ + stroker->radius = radius; + stroker->line_cap = line_cap; + stroker->line_join = line_join; + stroker->miter_limit = miter_limit; + + /* ensure miter limit has sensible value */ + if (stroker->miter_limit < 0x10000) stroker->miter_limit = 0x10000; + + /* save line join style: */ + /* line join style can be temporarily changed when stroking curves */ + stroker->line_join_saved = line_join; + + SW_FT_Stroker_Rewind(stroker); +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Done(SW_FT_Stroker stroker) +{ + if (stroker) { + ft_stroke_border_done(&stroker->borders[0]); + ft_stroke_border_done(&stroker->borders[1]); + + free(stroker); + } +} + +/* create a circular arc at a corner or cap */ +static SW_FT_Error ft_stroker_arcto(SW_FT_Stroker stroker, SW_FT_Int side) +{ + SW_FT_Angle total, rotate; + SW_FT_Fixed radius = stroker->radius; + SW_FT_Error error = 0; + SW_FT_StrokeBorder border = stroker->borders + side; + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + total = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + if (total == SW_FT_ANGLE_PI) total = -rotate * 2; + + error = ft_stroke_border_arcto(border, &stroker->center, radius, + stroker->angle_in + rotate, total); + border->movable = FALSE; + return error; +} + +/* add a cap at the end of an opened path */ +static SW_FT_Error +ft_stroker_cap(SW_FT_Stroker stroker, + SW_FT_Angle angle, + SW_FT_Int side) +{ + SW_FT_Error error = 0; + + if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND) + { + /* add a round cap */ + stroker->angle_in = angle; + stroker->angle_out = angle + SW_FT_ANGLE_PI; + + error = ft_stroker_arcto(stroker, side); + } + else + { + /* add a square or butt cap */ + SW_FT_Vector middle, delta; + SW_FT_Fixed radius = stroker->radius; + SW_FT_StrokeBorder border = stroker->borders + side; + + /* compute middle point and first angle point */ + SW_FT_Vector_From_Polar( &middle, radius, angle ); + delta.x = side ? middle.y : -middle.y; + delta.y = side ? -middle.x : middle.x; + + if ( stroker->line_cap == SW_FT_STROKER_LINECAP_SQUARE ) + { + middle.x += stroker->center.x; + middle.y += stroker->center.y; + } + else /* SW_FT_STROKER_LINECAP_BUTT */ + { + middle.x = stroker->center.x; + middle.y = stroker->center.y; + } + + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + +Exit: + return error; +} + +/* process an inside corner, i.e. compute intersection */ +static SW_FT_Error ft_stroker_inside(SW_FT_Stroker stroker, SW_FT_Int side, + SW_FT_Fixed line_length) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Angle phi, theta, rotate; + SW_FT_Fixed length; + SW_FT_Vector sigma, delta; + SW_FT_Error error = 0; + SW_FT_Bool intersect; /* use intersection of lines? */ + + rotate = SW_FT_SIDE_TO_ROTATE(side); + + theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2; + + /* Only intersect borders if between two lineto's and both */ + /* lines are long enough (line_length is zero for curves). */ + if (!border->movable || line_length == 0 || + theta > 0x59C000 || theta < -0x59C000 ) + intersect = FALSE; + else { + /* compute minimum required length of lines */ + SW_FT_Fixed min_length; + + + SW_FT_Vector_Unit( &sigma, theta ); + min_length = + ft_pos_abs( SW_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) ); + + intersect = SW_FT_BOOL( min_length && + stroker->line_length >= min_length && + line_length >= min_length ); + } + + if (!intersect) { + SW_FT_Vector_From_Polar(&delta, stroker->radius, + stroker->angle_out + rotate); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + } else { + /* compute median angle */ + phi = stroker->angle_in + theta + rotate; + + length = SW_FT_DivFix( stroker->radius, sigma.x ); + + SW_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + } + + error = ft_stroke_border_lineto(border, &delta, FALSE); + + return error; +} + + /* process an outside corner, i.e. compute bevel/miter/round */ +static SW_FT_Error +ft_stroker_outside( SW_FT_Stroker stroker, + SW_FT_Int side, + SW_FT_Fixed line_length ) +{ + SW_FT_StrokeBorder border = stroker->borders + side; + SW_FT_Error error; + SW_FT_Angle rotate; + + + if ( stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND ) + error = ft_stroker_arcto( stroker, side ); + else + { + /* this is a mitered (pointed) or beveled (truncated) corner */ + SW_FT_Fixed radius = stroker->radius; + SW_FT_Vector sigma; + SW_FT_Angle theta = 0, phi = 0; + SW_FT_Bool bevel, fixed_bevel; + + + rotate = SW_FT_SIDE_TO_ROTATE( side ); + + bevel = + SW_FT_BOOL( stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL ); + + fixed_bevel = + SW_FT_BOOL( stroker->line_join != SW_FT_STROKER_LINEJOIN_MITER_VARIABLE ); + + /* check miter limit first */ + if ( !bevel ) + { + theta = SW_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2; + + if ( theta == SW_FT_ANGLE_PI2 ) + theta = -rotate; + + phi = stroker->angle_in + theta + rotate; + + SW_FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta ); + + /* is miter limit exceeded? */ + if ( sigma.x < 0x10000L ) + { + /* don't create variable bevels for very small deviations; */ + /* FT_Sin(x) = 0 for x <= 57 */ + if ( fixed_bevel || ft_pos_abs( theta ) > 57 ) + bevel = TRUE; + } + } + + if ( bevel ) /* this is a bevel (broken angle) */ + { + if ( fixed_bevel ) + { + /* the outer corners are simply joined together */ + SW_FT_Vector delta; + + + /* add bevel */ + SW_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + border->movable = FALSE; + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + else /* variable bevel or clipped miter */ + { + /* the miter is truncated */ + SW_FT_Vector middle, delta; + SW_FT_Fixed coef; + + + /* compute middle point and first angle point */ + SW_FT_Vector_From_Polar( &middle, + SW_FT_MulFix( radius, stroker->miter_limit ), + phi ); + + coef = SW_FT_DivFix( 0x10000L - sigma.x, sigma.y ); + delta.x = SW_FT_MulFix( middle.y, coef ); + delta.y = SW_FT_MulFix( -middle.x, coef ); + + middle.x += stroker->center.x; + middle.y += stroker->center.y; + delta.x += middle.x; + delta.y += middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* compute second angle point */ + delta.x = middle.x - delta.x + middle.x; + delta.y = middle.y - delta.y + middle.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* finally, add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + SW_FT_Vector_From_Polar( &delta, + radius, + stroker->angle_out + rotate ); + + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + else /* this is a miter (intersection) */ + { + SW_FT_Fixed length; + SW_FT_Vector delta; + + + length = SW_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x ); + + SW_FT_Vector_From_Polar( &delta, length, phi ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + if ( error ) + goto Exit; + + /* now add an end point; only needed if not lineto */ + /* (line_length is zero for curves) */ + if ( line_length == 0 ) + { + SW_FT_Vector_From_Polar( &delta, + stroker->radius, + stroker->angle_out + rotate ); + delta.x += stroker->center.x; + delta.y += stroker->center.y; + + error = ft_stroke_border_lineto( border, &delta, FALSE ); + } + } + } + + Exit: + return error; +} + +static SW_FT_Error ft_stroker_process_corner(SW_FT_Stroker stroker, + SW_FT_Fixed line_length) +{ + SW_FT_Error error = 0; + SW_FT_Angle turn; + SW_FT_Int inside_side; + + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn == 0) goto Exit; + + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + /* process the inside side */ + error = ft_stroker_inside(stroker, inside_side, line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, line_length); + +Exit: + return error; +} + +/* add two points to the left and right borders corresponding to the */ +/* start of the subpath */ +static SW_FT_Error ft_stroker_subpath_start(SW_FT_Stroker stroker, + SW_FT_Angle start_angle, + SW_FT_Fixed line_length) +{ + SW_FT_Vector delta; + SW_FT_Vector point; + SW_FT_Error error; + SW_FT_StrokeBorder border; + + SW_FT_Vector_From_Polar(&delta, stroker->radius, + start_angle + SW_FT_ANGLE_PI2); + + point.x = stroker->center.x + delta.x; + point.y = stroker->center.y + delta.y; + + border = stroker->borders; + error = ft_stroke_border_moveto(border, &point); + if (error) goto Exit; + + point.x = stroker->center.x - delta.x; + point.y = stroker->center.y - delta.y; + + border++; + error = ft_stroke_border_moveto(border, &point); + + /* save angle, position, and line length for last join */ + /* (line_length is zero for curves) */ + stroker->subpath_angle = start_angle; + stroker->first_point = FALSE; + stroker->subpath_line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_LineTo(SW_FT_Stroker stroker, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_StrokeBorder border; + SW_FT_Vector delta; + SW_FT_Angle angle; + SW_FT_Int side; + SW_FT_Fixed line_length; + + delta.x = to->x - stroker->center.x; + delta.y = to->y - stroker->center.y; + + /* a zero-length lineto is a no-op; avoid creating a spurious corner */ + if (delta.x == 0 && delta.y == 0) goto Exit; + + /* compute length of line */ + line_length = SW_FT_Vector_Length(&delta); + + angle = SW_FT_Atan2(delta.x, delta.y); + SW_FT_Vector_From_Polar(&delta, stroker->radius, angle + SW_FT_ANGLE_PI2); + + /* process corner if necessary */ + if (stroker->first_point) { + /* This is the first segment of a subpath. We need to */ + /* add a point to each border at their respective starting */ + /* point locations. */ + error = ft_stroker_subpath_start(stroker, angle, line_length); + if (error) goto Exit; + } else { + /* process the current corner */ + stroker->angle_out = angle; + error = ft_stroker_process_corner(stroker, line_length); + if (error) goto Exit; + } + + /* now add a line segment to both the `inside' and `outside' paths */ + for (border = stroker->borders, side = 1; side >= 0; side--, border++) { + SW_FT_Vector point; + + point.x = to->x + delta.x; + point.y = to->y + delta.y; + + /* the ends of lineto borders are movable */ + error = ft_stroke_border_lineto(border, &point, TRUE); + if (error) goto Exit; + + delta.x = -delta.x; + delta.y = -delta.y; + } + + stroker->angle_in = angle; + stroker->center = *to; + stroker->line_length = line_length; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_ConicTo(SW_FT_Stroker stroker, SW_FT_Vector* control, + SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[34]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 30; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control->x) && + SW_FT_IS_SMALL(stroker->center.y - control->y) && + SW_FT_IS_SMALL(control->x - to->x) && + SW_FT_IS_SMALL(control->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control; + arc[2] = stroker->center; + + while (arc >= bez_stack) { + SW_FT_Angle angle_in, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = stroker->angle_in; + + if (arc < limit && + !ft_conic_is_small_enough(arc, &angle_in, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_conic_split(arc); + arc += 2; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CONIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[2]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl, end; + SW_FT_Angle theta, phi, rotate, alpha0 = 0; + SW_FT_Fixed length; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta = SW_FT_Angle_Diff(angle_in, angle_out) / 2; + phi = angle_in + theta; + length = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_FT_Atan2(arc[0].x - arc[2].x, arc[0].y - arc[2].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control point */ + SW_FT_Vector_From_Polar(&ctrl, length, phi + rotate); + ctrl.x += arc[1].x; + ctrl.y += arc[1].y; + + /* compute end point */ + SW_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y); + gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_conicto(border, &ctrl, &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_conicto(border, &ctrl, &end); + if (error) goto Exit; + } + } + + arc -= 2; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_CubicTo(SW_FT_Stroker stroker, SW_FT_Vector* control1, + SW_FT_Vector* control2, SW_FT_Vector* to) +{ + SW_FT_Error error = 0; + SW_FT_Vector bez_stack[37]; + SW_FT_Vector* arc; + SW_FT_Vector* limit = bez_stack + 32; + SW_FT_Bool first_arc = TRUE; + + /* if all control points are coincident, this is a no-op; */ + /* avoid creating a spurious corner */ + if (SW_FT_IS_SMALL(stroker->center.x - control1->x) && + SW_FT_IS_SMALL(stroker->center.y - control1->y) && + SW_FT_IS_SMALL(control1->x - control2->x) && + SW_FT_IS_SMALL(control1->y - control2->y) && + SW_FT_IS_SMALL(control2->x - to->x) && + SW_FT_IS_SMALL(control2->y - to->y)) { + stroker->center = *to; + goto Exit; + } + + arc = bez_stack; + arc[0] = *to; + arc[1] = *control2; + arc[2] = *control1; + arc[3] = stroker->center; + + while (arc >= bez_stack) { + SW_FT_Angle angle_in, angle_mid, angle_out; + + /* initialize with current direction */ + angle_in = angle_out = angle_mid = stroker->angle_in; + + if (arc < limit && + !ft_cubic_is_small_enough(arc, &angle_in, &angle_mid, &angle_out)) { + if (stroker->first_point) stroker->angle_in = angle_in; + + ft_cubic_split(arc); + arc += 3; + continue; + } + + if (first_arc) { + first_arc = FALSE; + + /* process corner if necessary */ + if (stroker->first_point) + error = ft_stroker_subpath_start(stroker, angle_in, 0); + else { + stroker->angle_out = angle_in; + error = ft_stroker_process_corner(stroker, 0); + } + } else if (ft_pos_abs(SW_FT_Angle_Diff(stroker->angle_in, angle_in)) > + SW_FT_SMALL_CUBIC_THRESHOLD / 4) { + /* if the deviation from one arc to the next is too great, */ + /* add a round corner */ + stroker->center = arc[3]; + stroker->angle_out = angle_in; + stroker->line_join = SW_FT_STROKER_LINEJOIN_ROUND; + + error = ft_stroker_process_corner(stroker, 0); + + /* reinstate line join style */ + stroker->line_join = stroker->line_join_saved; + } + + if (error) goto Exit; + + /* the arc's angle is small enough; we can add it directly to each */ + /* border */ + { + SW_FT_Vector ctrl1, ctrl2, end; + SW_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0; + SW_FT_Fixed length1, length2; + SW_FT_StrokeBorder border; + SW_FT_Int side; + + theta1 = SW_FT_Angle_Diff(angle_in, angle_mid) / 2; + theta2 = SW_FT_Angle_Diff(angle_mid, angle_out) / 2; + phi1 = ft_angle_mean(angle_in, angle_mid); + phi2 = ft_angle_mean(angle_mid, angle_out); + length1 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta1)); + length2 = SW_FT_DivFix(stroker->radius, SW_FT_Cos(theta2)); + + /* compute direction of original arc */ + if (stroker->handle_wide_strokes) + alpha0 = SW_FT_Atan2(arc[0].x - arc[3].x, arc[0].y - arc[3].y); + + for (border = stroker->borders, side = 0; side <= 1; + side++, border++) { + rotate = SW_FT_SIDE_TO_ROTATE(side); + + /* compute control points */ + SW_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate); + ctrl1.x += arc[2].x; + ctrl1.y += arc[2].y; + + SW_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate); + ctrl2.x += arc[1].x; + ctrl2.y += arc[1].y; + + /* compute end point */ + SW_FT_Vector_From_Polar(&end, stroker->radius, + angle_out + rotate); + end.x += arc[0].x; + end.y += arc[0].y; + + if (stroker->handle_wide_strokes) { + SW_FT_Vector start; + SW_FT_Angle alpha1; + + /* determine whether the border radius is greater than the + */ + /* radius of curvature of the original arc */ + start = border->points[border->num_points - 1]; + + alpha1 = SW_FT_Atan2(end.x - start.x, end.y - start.y); + + /* is the direction of the border arc opposite to */ + /* that of the original arc? */ + if (ft_pos_abs(SW_FT_Angle_Diff(alpha0, alpha1)) > + SW_FT_ANGLE_PI / 2) { + SW_FT_Angle beta, gamma; + SW_FT_Vector bvec, delta; + SW_FT_Fixed blen, sinA, sinB, alen; + + /* use the sine rule to find the intersection point */ + beta = + SW_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y); + gamma = SW_FT_Atan2(arc[0].x - end.x, arc[0].y - end.y); + + bvec.x = end.x - start.x; + bvec.y = end.y - start.y; + + blen = SW_FT_Vector_Length(&bvec); + + sinA = ft_pos_abs(SW_FT_Sin(alpha1 - gamma)); + sinB = ft_pos_abs(SW_FT_Sin(beta - gamma)); + + alen = SW_FT_MulDiv(blen, sinA, sinB); + + SW_FT_Vector_From_Polar(&delta, alen, beta); + delta.x += start.x; + delta.y += start.y; + + /* circumnavigate the negative sector backwards */ + border->movable = FALSE; + error = ft_stroke_border_lineto(border, &delta, FALSE); + if (error) goto Exit; + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + error = ft_stroke_border_cubicto(border, &ctrl2, &ctrl1, + &start); + if (error) goto Exit; + /* and then move to the endpoint */ + error = ft_stroke_border_lineto(border, &end, FALSE); + if (error) goto Exit; + + continue; + } + + /* else fall through */ + } + + /* simply add an arc */ + error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end); + if (error) goto Exit; + } + } + + arc -= 3; + + stroker->angle_in = angle_out; + } + + stroker->center = *to; + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_BeginSubPath(SW_FT_Stroker stroker, SW_FT_Vector* to, + SW_FT_Bool open) +{ + /* We cannot process the first point, because there is not enough */ + /* information regarding its corner/cap. The latter will be processed */ + /* in the `SW_FT_Stroker_EndSubPath' routine. */ + /* */ + stroker->first_point = TRUE; + stroker->center = *to; + stroker->subpath_open = open; + + /* Determine if we need to check whether the border radius is greater */ + /* than the radius of curvature of a curve, to handle this case */ + /* specially. This is only required if bevel joins or butt caps may */ + /* be created, because round & miter joins and round & square caps */ + /* cover the negative sector created with wide strokes. */ + stroker->handle_wide_strokes = + SW_FT_BOOL(stroker->line_join != SW_FT_STROKER_LINEJOIN_ROUND || + (stroker->subpath_open && + stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT)); + + /* record the subpath start point for each border */ + stroker->subpath_start = *to; + + stroker->angle_in = 0; + + return 0; +} + +static SW_FT_Error ft_stroker_add_reverse_left(SW_FT_Stroker stroker, + SW_FT_Bool open) +{ + SW_FT_StrokeBorder right = stroker->borders + 0; + SW_FT_StrokeBorder left = stroker->borders + 1; + SW_FT_Int new_points; + SW_FT_Error error = 0; + + assert(left->start >= 0); + + new_points = left->num_points - left->start; + if (new_points > 0) { + error = ft_stroke_border_grow(right, (SW_FT_UInt)new_points); + if (error) goto Exit; + + { + SW_FT_Vector* dst_point = right->points + right->num_points; + SW_FT_Byte* dst_tag = right->tags + right->num_points; + SW_FT_Vector* src_point = left->points + left->num_points - 1; + SW_FT_Byte* src_tag = left->tags + left->num_points - 1; + + while (src_point >= left->points + left->start) { + *dst_point = *src_point; + *dst_tag = *src_tag; + + if (open) + dst_tag[0] &= ~SW_FT_STROKE_TAG_BEGIN_END; + else { + SW_FT_Byte ttag = + (SW_FT_Byte)(dst_tag[0] & SW_FT_STROKE_TAG_BEGIN_END); + + /* switch begin/end tags if necessary */ + if (ttag == SW_FT_STROKE_TAG_BEGIN || + ttag == SW_FT_STROKE_TAG_END) + dst_tag[0] ^= SW_FT_STROKE_TAG_BEGIN_END; + } + + src_point--; + src_tag--; + dst_point++; + dst_tag++; + } + } + + left->num_points = left->start; + right->num_points += new_points; + + right->movable = FALSE; + left->movable = FALSE; + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +/* there's a lot of magic in this function! */ +SW_FT_Error SW_FT_Stroker_EndSubPath(SW_FT_Stroker stroker) +{ + SW_FT_Error error = 0; + + if (stroker->subpath_open) { + SW_FT_StrokeBorder right = stroker->borders; + + /* All right, this is an opened path, we need to add a cap between */ + /* right & left, add the reverse of left, then add a final cap */ + /* between left & right. */ + error = ft_stroker_cap(stroker, stroker->angle_in, 0); + if (error) goto Exit; + + /* add reversed points from `left' to `right' */ + error = ft_stroker_add_reverse_left(stroker, TRUE); + if (error) goto Exit; + + /* now add the final cap */ + stroker->center = stroker->subpath_start; + error = + ft_stroker_cap(stroker, stroker->subpath_angle + SW_FT_ANGLE_PI, 0); + if (error) goto Exit; + + /* Now end the right subpath accordingly. The left one is */ + /* rewind and doesn't need further processing. */ + ft_stroke_border_close(right, FALSE); + } else { + SW_FT_Angle turn; + SW_FT_Int inside_side; + + /* close the path if needed */ + if (stroker->center.x != stroker->subpath_start.x || + stroker->center.y != stroker->subpath_start.y) { + error = SW_FT_Stroker_LineTo(stroker, &stroker->subpath_start); + if (error) goto Exit; + } + + /* process the corner */ + stroker->angle_out = stroker->subpath_angle; + turn = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out); + + /* no specific corner processing is required if the turn is 0 */ + if (turn != 0) { + /* when we turn to the right, the inside side is 0 */ + inside_side = 0; + + /* otherwise, the inside side is 1 */ + if (turn < 0) inside_side = 1; + + error = ft_stroker_inside(stroker, inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + + /* process the outside side */ + error = ft_stroker_outside(stroker, 1 - inside_side, + stroker->subpath_line_length); + if (error) goto Exit; + } + + /* then end our two subpaths */ + ft_stroke_border_close(stroker->borders + 0, FALSE); + ft_stroke_border_close(stroker->borders + 1, TRUE); + } + +Exit: + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetBorderCounts(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt num_points = 0, num_contours = 0; + SW_FT_Error error; + + if (!stroker || border > 1) { + error = -1; // SW_FT_THROW( Invalid_Argument ); + goto Exit; + } + + error = ft_stroke_border_get_counts(stroker->borders + border, &num_points, + &num_contours); +Exit: + if (anum_points) *anum_points = num_points; + + if (anum_contours) *anum_contours = num_contours; + + return error; +} + +/* documentation is in ftstroke.h */ + +SW_FT_Error SW_FT_Stroker_GetCounts(SW_FT_Stroker stroker, + SW_FT_UInt* anum_points, + SW_FT_UInt* anum_contours) +{ + SW_FT_UInt count1, count2, num_points = 0; + SW_FT_UInt count3, count4, num_contours = 0; + SW_FT_Error error; + + error = ft_stroke_border_get_counts(stroker->borders + 0, &count1, &count2); + if (error) goto Exit; + + error = ft_stroke_border_get_counts(stroker->borders + 1, &count3, &count4); + if (error) goto Exit; + + num_points = count1 + count3; + num_contours = count2 + count4; + +Exit: + *anum_points = num_points; + *anum_contours = num_contours; + return error; +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_ExportBorder(SW_FT_Stroker stroker, + SW_FT_StrokerBorder border, + SW_FT_Outline* outline) +{ + if (border == SW_FT_STROKER_BORDER_LEFT || + border == SW_FT_STROKER_BORDER_RIGHT) { + SW_FT_StrokeBorder sborder = &stroker->borders[border]; + + if (sborder->valid) ft_stroke_border_export(sborder, outline); + } +} + +/* documentation is in ftstroke.h */ + +void SW_FT_Stroker_Export(SW_FT_Stroker stroker, SW_FT_Outline* outline) +{ + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_LEFT, outline); + SW_FT_Stroker_ExportBorder(stroker, SW_FT_STROKER_BORDER_RIGHT, outline); +} + +/* documentation is in ftstroke.h */ + +/* + * The following is very similar to SW_FT_Outline_Decompose, except + * that we do support opened paths, and do not scale the outline. + */ +SW_FT_Error SW_FT_Stroker_ParseOutline(SW_FT_Stroker stroker, + const SW_FT_Outline* outline) +{ + SW_FT_Vector v_last; + SW_FT_Vector v_control; + SW_FT_Vector v_start; + + SW_FT_Vector* point; + SW_FT_Vector* limit; + char* tags; + + SW_FT_Error error; + + SW_FT_Int n; /* index of contour in outline */ + SW_FT_UInt first; /* index of first point in contour */ + SW_FT_Int tag; /* current point's state */ + + if (!outline || !stroker) return -1; // SW_FT_THROW( Invalid_Argument ); + + SW_FT_Stroker_Rewind(stroker); + + first = 0; + + for (n = 0; n < outline->n_contours; n++) { + SW_FT_UInt last; /* index of last point in contour */ + + last = outline->contours[n]; + limit = outline->points + last; + + /* skip empty points; we don't stroke these */ + if (last <= first) { + first = last + 1; + continue; + } + + v_start = outline->points[first]; + v_last = outline->points[last]; + + v_control = v_start; + + point = outline->points + first; + tags = outline->tags + first; + tag = SW_FT_CURVE_TAG(tags[0]); + + /* A contour cannot start with a cubic control point! */ + if (tag == SW_FT_CURVE_TAG_CUBIC) goto Invalid_Outline; + + /* check first point to determine origin */ + if (tag == SW_FT_CURVE_TAG_CONIC) { + /* First point is conic control. Yes, this happens. */ + if (SW_FT_CURVE_TAG(outline->tags[last]) == SW_FT_CURVE_TAG_ON) { + /* start at last point if it is on the curve */ + v_start = v_last; + limit--; + } else { + /* if both first and last points are conic, */ + /* start at their middle */ + v_start.x = (v_start.x + v_last.x) / 2; + v_start.y = (v_start.y + v_last.y) / 2; + } + point--; + tags--; + } + + error = SW_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]); + if (error) goto Exit; + + while (point < limit) { + point++; + tags++; + + tag = SW_FT_CURVE_TAG(tags[0]); + switch (tag) { + case SW_FT_CURVE_TAG_ON: /* emit a single line_to */ + { + SW_FT_Vector vec; + + vec.x = point->x; + vec.y = point->y; + + error = SW_FT_Stroker_LineTo(stroker, &vec); + if (error) goto Exit; + continue; + } + + case SW_FT_CURVE_TAG_CONIC: /* consume conic arcs */ + v_control.x = point->x; + v_control.y = point->y; + + Do_Conic: + if (point < limit) { + SW_FT_Vector vec; + SW_FT_Vector v_middle; + + point++; + tags++; + tag = SW_FT_CURVE_TAG(tags[0]); + + vec = point[0]; + + if (tag == SW_FT_CURVE_TAG_ON) { + error = + SW_FT_Stroker_ConicTo(stroker, &v_control, &vec); + if (error) goto Exit; + continue; + } + + if (tag != SW_FT_CURVE_TAG_CONIC) goto Invalid_Outline; + + v_middle.x = (v_control.x + vec.x) / 2; + v_middle.y = (v_control.y + vec.y) / 2; + + error = + SW_FT_Stroker_ConicTo(stroker, &v_control, &v_middle); + if (error) goto Exit; + + v_control = vec; + goto Do_Conic; + } + + error = SW_FT_Stroker_ConicTo(stroker, &v_control, &v_start); + goto Close; + + default: /* SW_FT_CURVE_TAG_CUBIC */ + { + SW_FT_Vector vec1, vec2; + + if (point + 1 > limit || + SW_FT_CURVE_TAG(tags[1]) != SW_FT_CURVE_TAG_CUBIC) + goto Invalid_Outline; + + point += 2; + tags += 2; + + vec1 = point[-2]; + vec2 = point[-1]; + + if (point <= limit) { + SW_FT_Vector vec; + + vec = point[0]; + + error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec); + if (error) goto Exit; + continue; + } + + error = SW_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start); + goto Close; + } + } + } + + Close: + if (error) goto Exit; + + /* don't try to end the path if no segments have been generated */ + if (!stroker->first_point) { + error = SW_FT_Stroker_EndSubPath(stroker); + if (error) goto Exit; + } + + first = last + 1; + } + + return 0; + +Exit: + return error; + +Invalid_Outline: + return -2; // SW_FT_THROW( Invalid_Outline ); +} + +/* END */ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.h new file mode 100644 index 0000000000..1514cf3d0a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_stroker.h @@ -0,0 +1,319 @@ +#ifndef V_FT_STROKER_H +#define V_FT_STROKER_H +/***************************************************************************/ +/* */ +/* ftstroke.h */ +/* */ +/* FreeType path stroker (specification). */ +/* */ +/* Copyright 2002-2006, 2008, 2009, 2011-2012 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include "v_ft_raster.h" + + /************************************************************** + * + * @type: + * SW_FT_Stroker + * + * @description: + * Opaque handler to a path stroker object. + */ + typedef struct SW_FT_StrokerRec_* SW_FT_Stroker; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineJoin + * + * @description: + * These values determine how two joining lines are rendered + * in a stroker. + * + * @values: + * SW_FT_STROKER_LINEJOIN_ROUND :: + * Used to render rounded line joins. Circular arcs are used + * to join two lines smoothly. + * + * SW_FT_STROKER_LINEJOIN_BEVEL :: + * Used to render beveled line joins. The outer corner of + * the joined lines is filled by enclosing the triangular + * region of the corner with a straight line between the + * outer corners of each stroke. + * + * SW_FT_STROKER_LINEJOIN_MITER_FIXED :: + * Used to render mitered line joins, with fixed bevels if the + * miter limit is exceeded. The outer edges of the strokes + * for the two segments are extended until they meet at an + * angle. If the segments meet at too sharp an angle (such + * that the miter would extend from the intersection of the + * segments a distance greater than the product of the miter + * limit value and the border radius), then a bevel join (see + * above) is used instead. This prevents long spikes being + * created. SW_FT_STROKER_LINEJOIN_MITER_FIXED generates a miter + * line join as used in PostScript and PDF. + * + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE :: + * SW_FT_STROKER_LINEJOIN_MITER :: + * Used to render mitered line joins, with variable bevels if + * the miter limit is exceeded. The intersection of the + * strokes is clipped at a line perpendicular to the bisector + * of the angle between the strokes, at the distance from the + * intersection of the segments equal to the product of the + * miter limit value and the border radius. This prevents + * long spikes being created. + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE generates a mitered line + * join as used in XPS. SW_FT_STROKER_LINEJOIN_MITER is an alias + * for SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, retained for + * backwards compatibility. + */ + typedef enum SW_FT_Stroker_LineJoin_ + { + SW_FT_STROKER_LINEJOIN_ROUND = 0, + SW_FT_STROKER_LINEJOIN_BEVEL = 1, + SW_FT_STROKER_LINEJOIN_MITER_VARIABLE = 2, + SW_FT_STROKER_LINEJOIN_MITER = SW_FT_STROKER_LINEJOIN_MITER_VARIABLE, + SW_FT_STROKER_LINEJOIN_MITER_FIXED = 3 + + } SW_FT_Stroker_LineJoin; + + + /************************************************************** + * + * @enum: + * SW_FT_Stroker_LineCap + * + * @description: + * These values determine how the end of opened sub-paths are + * rendered in a stroke. + * + * @values: + * SW_FT_STROKER_LINECAP_BUTT :: + * The end of lines is rendered as a full stop on the last + * point itself. + * + * SW_FT_STROKER_LINECAP_ROUND :: + * The end of lines is rendered as a half-circle around the + * last point. + * + * SW_FT_STROKER_LINECAP_SQUARE :: + * The end of lines is rendered as a square around the + * last point. + */ + typedef enum SW_FT_Stroker_LineCap_ + { + SW_FT_STROKER_LINECAP_BUTT = 0, + SW_FT_STROKER_LINECAP_ROUND, + SW_FT_STROKER_LINECAP_SQUARE + + } SW_FT_Stroker_LineCap; + + + /************************************************************** + * + * @enum: + * SW_FT_StrokerBorder + * + * @description: + * These values are used to select a given stroke border + * in @SW_FT_Stroker_GetBorderCounts and @SW_FT_Stroker_ExportBorder. + * + * @values: + * SW_FT_STROKER_BORDER_LEFT :: + * Select the left border, relative to the drawing direction. + * + * SW_FT_STROKER_BORDER_RIGHT :: + * Select the right border, relative to the drawing direction. + * + * @note: + * Applications are generally interested in the `inside' and `outside' + * borders. However, there is no direct mapping between these and the + * `left' and `right' ones, since this really depends on the glyph's + * drawing orientation, which varies between font formats. + * + * You can however use @SW_FT_Outline_GetInsideBorder and + * @SW_FT_Outline_GetOutsideBorder to get these. + */ + typedef enum SW_FT_StrokerBorder_ + { + SW_FT_STROKER_BORDER_LEFT = 0, + SW_FT_STROKER_BORDER_RIGHT + + } SW_FT_StrokerBorder; + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_New + * + * @description: + * Create a new stroker object. + * + * @input: + * library :: + * FreeType library handle. + * + * @output: + * astroker :: + * A new stroker object handle. NULL in case of error. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_New( SW_FT_Stroker *astroker ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Set + * + * @description: + * Reset a stroker object's attributes. + * + * @input: + * stroker :: + * The target stroker handle. + * + * radius :: + * The border radius. + * + * line_cap :: + * The line cap style. + * + * line_join :: + * The line join style. + * + * miter_limit :: + * The miter limit for the SW_FT_STROKER_LINEJOIN_MITER_FIXED and + * SW_FT_STROKER_LINEJOIN_MITER_VARIABLE line join styles, + * expressed as 16.16 fixed-point value. + * + * @note: + * The radius is expressed in the same units as the outline + * coordinates. + */ + void + SW_FT_Stroker_Set( SW_FT_Stroker stroker, + SW_FT_Fixed radius, + SW_FT_Stroker_LineCap line_cap, + SW_FT_Stroker_LineJoin line_join, + SW_FT_Fixed miter_limit ); + + /************************************************************** + * + * @function: + * SW_FT_Stroker_ParseOutline + * + * @description: + * A convenience function used to parse a whole outline with + * the stroker. The resulting outline(s) can be retrieved + * later by functions like @SW_FT_Stroker_GetCounts and @SW_FT_Stroker_Export. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The source outline. + * + * + * @return: + * FreeType error code. 0~means success. + * + * @note: + * If `opened' is~0 (the default), the outline is treated as a closed + * path, and the stroker generates two distinct `border' outlines. + * + * + * This function calls @SW_FT_Stroker_Rewind automatically. + */ + SW_FT_Error + SW_FT_Stroker_ParseOutline( SW_FT_Stroker stroker, + const SW_FT_Outline* outline); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_GetCounts + * + * @description: + * Call this function once you have finished parsing your paths + * with the stroker. It returns the number of points and + * contours necessary to export all points/borders from the stroked + * outline/path. + * + * @input: + * stroker :: + * The target stroker handle. + * + * @output: + * anum_points :: + * The number of points. + * + * anum_contours :: + * The number of contours. + * + * @return: + * FreeType error code. 0~means success. + */ + SW_FT_Error + SW_FT_Stroker_GetCounts( SW_FT_Stroker stroker, + SW_FT_UInt *anum_points, + SW_FT_UInt *anum_contours ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Export + * + * @description: + * Call this function after @SW_FT_Stroker_GetBorderCounts to + * export all borders to your own @SW_FT_Outline structure. + * + * Note that this function appends the border points and + * contours to your outline, but does not try to resize its + * arrays. + * + * @input: + * stroker :: + * The target stroker handle. + * + * outline :: + * The target outline handle. + */ + void + SW_FT_Stroker_Export( SW_FT_Stroker stroker, + SW_FT_Outline* outline ); + + + /************************************************************** + * + * @function: + * SW_FT_Stroker_Done + * + * @description: + * Destroy a stroker object. + * + * @input: + * stroker :: + * A stroker handle. Can be NULL. + */ + void + SW_FT_Stroker_Done( SW_FT_Stroker stroker ); + + +#endif // V_FT_STROKER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_types.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_types.h new file mode 100644 index 0000000000..a01c4f2872 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/freetype/v_ft_types.h @@ -0,0 +1,160 @@ +#ifndef V_FT_TYPES_H +#define V_FT_TYPES_H + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Fixed */ +/* */ +/* */ +/* This type is used to store 16.16 fixed-point values, like scaling */ +/* values or matrix coefficients. */ +/* */ +typedef signed long SW_FT_Fixed; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Int */ +/* */ +/* */ +/* A typedef for the int type. */ +/* */ +typedef signed int SW_FT_Int; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_UInt */ +/* */ +/* */ +/* A typedef for the unsigned int type. */ +/* */ +typedef unsigned int SW_FT_UInt; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Long */ +/* */ +/* */ +/* A typedef for signed long. */ +/* */ +typedef signed long SW_FT_Long; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_ULong */ +/* */ +/* */ +/* A typedef for unsigned long. */ +/* */ +typedef unsigned long SW_FT_ULong; + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Short */ +/* */ +/* */ +/* A typedef for signed short. */ +/* */ +typedef signed short SW_FT_Short; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Byte */ +/* */ +/* */ +/* A simple typedef for the _unsigned_ char type. */ +/* */ +typedef unsigned char SW_FT_Byte; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Bool */ +/* */ +/* */ +/* A typedef of unsigned char, used for simple booleans. As usual, */ +/* values 1 and~0 represent true and false, respectively. */ +/* */ +typedef unsigned char SW_FT_Bool; + + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Error */ +/* */ +/* */ +/* The FreeType error code type. A value of~0 is always interpreted */ +/* as a successful operation. */ +/* */ +typedef int SW_FT_Error; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Pos */ +/* */ +/* */ +/* The type SW_FT_Pos is used to store vectorial coordinates. Depending */ +/* on the context, these can represent distances in integer font */ +/* units, or 16.16, or 26.6 fixed-point pixel coordinates. */ +/* */ +typedef signed long SW_FT_Pos; + + +/*************************************************************************/ +/* */ +/* */ +/* SW_FT_Vector */ +/* */ +/* */ +/* A simple structure used to store a 2D vector; coordinates are of */ +/* the SW_FT_Pos type. */ +/* */ +/* */ +/* x :: The horizontal coordinate. */ +/* y :: The vertical coordinate. */ +/* */ +typedef struct SW_FT_Vector_ +{ + SW_FT_Pos x; + SW_FT_Pos y; + +} SW_FT_Vector; + + +typedef long long int SW_FT_Int64; +typedef unsigned long long int SW_FT_UInt64; + +typedef signed int SW_FT_Int32; +typedef unsigned int SW_FT_UInt32; + + +#define SW_FT_BOOL( x ) ( (SW_FT_Bool)( x ) ) + +#define SW_FT_SIZEOF_LONG 4 + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +#endif // V_FT_TYPES_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.S b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.S new file mode 100644 index 0000000000..f2203c21cf --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.S @@ -0,0 +1,497 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains implementations of NEON optimized pixel processing + * functions. There is no full and detailed tutorial, but some functions + * (those which are exposing some new or interesting features) are + * extensively commented and can be used as examples. + * + * You may want to have a look at the comments for following functions: + * - pixman_composite_over_8888_0565_asm_neon + * - pixman_composite_over_n_8_0565_asm_neon + */ + +/* Prevent the stack from becoming executable for no reason... */ +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif + + .text + .fpu neon + .arch armv7a + .object_arch armv4 + .eabi_attribute 10, 0 /* suppress Tag_FP_arch */ + .eabi_attribute 12, 0 /* suppress Tag_Advanced_SIMD_arch */ + .arm + .altmacro + .p2align 2 + + +//#include "pixman-arm-asm.h" +/* Supplementary macro for setting function attributes */ +.macro pixman_asm_function fname + .func fname + .global fname +#ifdef __ELF__ + .hidden fname + .type fname, %function +#endif +fname: +.endm + +//#include "pixman-private.h" +/* + * The defines which are shared between C and assembly code + */ + +/* bilinear interpolation precision (must be < 8) */ +#define BILINEAR_INTERPOLATION_BITS 7 +#define BILINEAR_INTERPOLATION_RANGE (1 << BILINEAR_INTERPOLATION_BITS) + +#include "pixman-arm-neon-asm.h" + +/* Global configuration options and preferences */ + +/* + * The code can optionally make use of unaligned memory accesses to improve + * performance of handling leading/trailing pixels for each scanline. + * Configuration variable RESPECT_STRICT_ALIGNMENT can be set to 0 for + * example in linux if unaligned memory accesses are not configured to + * generate.exceptions. + */ +.set RESPECT_STRICT_ALIGNMENT, 1 + +/* + * Set default prefetch type. There is a choice between the following options: + * + * PREFETCH_TYPE_NONE (may be useful for the ARM cores where PLD is set to work + * as NOP to workaround some HW bugs or for whatever other reason) + * + * PREFETCH_TYPE_SIMPLE (may be useful for simple single-issue ARM cores where + * advanced prefetch intruduces heavy overhead) + * + * PREFETCH_TYPE_ADVANCED (useful for superscalar cores such as ARM Cortex-A8 + * which can run ARM and NEON instructions simultaneously so that extra ARM + * instructions do not add (many) extra cycles, but improve prefetch efficiency) + * + * Note: some types of function can't support advanced prefetch and fallback + * to simple one (those which handle 24bpp pixels) + */ +.set PREFETCH_TYPE_DEFAULT, PREFETCH_TYPE_ADVANCED + +/* Prefetch distance in pixels for simple prefetch */ +.set PREFETCH_DISTANCE_SIMPLE, 64 + +/* + * Implementation of pixman_composite_over_8888_0565_asm_neon + * + * This function takes a8r8g8b8 source buffer, r5g6b5 destination buffer and + * performs OVER compositing operation. Function fast_composite_over_8888_0565 + * from pixman-fast-path.c does the same in C and can be used as a reference. + * + * First we need to have some NEON assembly code which can do the actual + * operation on the pixels and provide it to the template macro. + * + * Template macro quite conveniently takes care of emitting all the necessary + * code for memory reading and writing (including quite tricky cases of + * handling unaligned leading/trailing pixels), so we only need to deal with + * the data in NEON registers. + * + * NEON registers allocation in general is recommented to be the following: + * d0, d1, d2, d3 - contain loaded source pixel data + * d4, d5, d6, d7 - contain loaded destination pixels (if they are needed) + * d24, d25, d26, d27 - contain loading mask pixel data (if mask is used) + * d28, d29, d30, d31 - place for storing the result (destination pixels) + * + * As can be seen above, four 64-bit NEON registers are used for keeping + * intermediate pixel data and up to 8 pixels can be processed in one step + * for 32bpp formats (16 pixels for 16bpp, 32 pixels for 8bpp). + * + * This particular function uses the following registers allocation: + * d0, d1, d2, d3 - contain loaded source pixel data + * d4, d5 - contain loaded destination pixels (they are needed) + * d28, d29 - place for storing the result (destination pixels) + */ + +/* + * Step one. We need to have some code to do some arithmetics on pixel data. + * This is implemented as a pair of macros: '*_head' and '*_tail'. When used + * back-to-back, they take pixel data from {d0, d1, d2, d3} and {d4, d5}, + * perform all the needed calculations and write the result to {d28, d29}. + * The rationale for having two macros and not just one will be explained + * later. In practice, any single monolitic function which does the work can + * be split into two parts in any arbitrary way without affecting correctness. + * + * There is one special trick here too. Common template macro can optionally + * make our life a bit easier by doing R, G, B, A color components + * deinterleaving for 32bpp pixel formats (and this feature is used in + * 'pixman_composite_over_8888_0565_asm_neon' function). So it means that + * instead of having 8 packed pixels in {d0, d1, d2, d3} registers, we + * actually use d0 register for blue channel (a vector of eight 8-bit + * values), d1 register for green, d2 for red and d3 for alpha. This + * simple conversion can be also done with a few NEON instructions: + * + * Packed to planar conversion: + * vuzp.8 d0, d1 + * vuzp.8 d2, d3 + * vuzp.8 d1, d3 + * vuzp.8 d0, d2 + * + * Planar to packed conversion: + * vzip.8 d0, d2 + * vzip.8 d1, d3 + * vzip.8 d2, d3 + * vzip.8 d0, d1 + * + * But pixel can be loaded directly in planar format using VLD4.8 NEON + * instruction. It is 1 cycle slower than VLD1.32, so this is not always + * desirable, that's why deinterleaving is optional. + * + * But anyway, here is the code: + */ + +/* + * OK, now we got almost everything that we need. Using the above two + * macros, the work can be done right. But now we want to optimize + * it a bit. ARM Cortex-A8 is an in-order core, and benefits really + * a lot from good code scheduling and software pipelining. + * + * Let's construct some code, which will run in the core main loop. + * Some pseudo-code of the main loop will look like this: + * head + * while (...) { + * tail + * head + * } + * tail + * + * It may look a bit weird, but this setup allows to hide instruction + * latencies better and also utilize dual-issue capability more + * efficiently (make pairs of load-store and ALU instructions). + * + * So what we need now is a '*_tail_head' macro, which will be used + * in the core main loop. A trivial straightforward implementation + * of this macro would look like this: + * + * pixman_composite_over_8888_0565_process_pixblock_tail + * vst1.16 {d28, d29}, [DST_W, :128]! + * vld1.16 {d4, d5}, [DST_R, :128]! + * vld4.32 {d0, d1, d2, d3}, [SRC]! + * pixman_composite_over_8888_0565_process_pixblock_head + * cache_preload 8, 8 + * + * Now it also got some VLD/VST instructions. We simply can't move from + * processing one block of pixels to the other one with just arithmetics. + * The previously processed data needs to be written to memory and new + * data needs to be fetched. Fortunately, this main loop does not deal + * with partial leading/trailing pixels and can load/store a full block + * of pixels in a bulk. Additionally, destination buffer is already + * 16 bytes aligned here (which is good for performance). + * + * New things here are DST_R, DST_W, SRC and MASK identifiers. These + * are the aliases for ARM registers which are used as pointers for + * accessing data. We maintain separate pointers for reading and writing + * destination buffer (DST_R and DST_W). + * + * Another new thing is 'cache_preload' macro. It is used for prefetching + * data into CPU L2 cache and improve performance when dealing with large + * images which are far larger than cache size. It uses one argument + * (actually two, but they need to be the same here) - number of pixels + * in a block. Looking into 'pixman-arm-neon-asm.h' can provide some + * details about this macro. Moreover, if good performance is needed + * the code from this macro needs to be copied into '*_tail_head' macro + * and mixed with the rest of code for optimal instructions scheduling. + * We are actually doing it below. + * + * Now after all the explanations, here is the optimized code. + * Different instruction streams (originaling from '*_head', '*_tail' + * and 'cache_preload' macro) use different indentation levels for + * better readability. Actually taking the code from one of these + * indentation levels and ignoring a few VLD/VST instructions would + * result in exactly the code from '*_head', '*_tail' or 'cache_preload' + * macro! + */ + +/* + * And now the final part. We are using 'generate_composite_function' macro + * to put all the stuff together. We are specifying the name of the function + * which we want to get, number of bits per pixel for the source, mask and + * destination (0 if unused, like mask in this case). Next come some bit + * flags: + * FLAG_DST_READWRITE - tells that the destination buffer is both read + * and written, for write-only buffer we would use + * FLAG_DST_WRITEONLY flag instead + * FLAG_DEINTERLEAVE_32BPP - tells that we prefer to work with planar data + * and separate color channels for 32bpp format. + * The next things are: + * - the number of pixels processed per iteration (8 in this case, because + * that's the maximum what can fit into four 64-bit NEON registers). + * - prefetch distance, measured in pixel blocks. In this case it is 5 times + * by 8 pixels. That would be 40 pixels, or up to 160 bytes. Optimal + * prefetch distance can be selected by running some benchmarks. + * + * After that we specify some macros, these are 'default_init', + * 'default_cleanup' here which are empty (but it is possible to have custom + * init/cleanup macros to be able to save/restore some extra NEON registers + * like d8-d15 or do anything else) followed by + * 'pixman_composite_over_8888_0565_process_pixblock_head', + * 'pixman_composite_over_8888_0565_process_pixblock_tail' and + * 'pixman_composite_over_8888_0565_process_pixblock_tail_head' + * which we got implemented above. + * + * The last part is the NEON registers allocation scheme. + */ + +/******************************************************************************/ + +/******************************************************************************/ + .macro pixman_composite_out_reverse_8888_8888_process_pixblock_head + vmvn.8 d24, d3 /* get inverted alpha */ + /* do alpha blending */ + vmull.u8 q8, d24, d4 + vmull.u8 q9, d24, d5 + vmull.u8 q10, d24, d6 + vmull.u8 q11, d24, d7 + .endm + + .macro pixman_composite_out_reverse_8888_8888_process_pixblock_tail + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q12, q10, #8 + vrshr.u16 q13, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q12, q10 + vraddhn.u16 d31, q13, q11 + .endm + +/******************************************************************************/ + +.macro pixman_composite_over_8888_8888_process_pixblock_head + pixman_composite_out_reverse_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail + pixman_composite_out_reverse_8888_8888_process_pixblock_tail + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 +.endm + +.macro pixman_composite_over_8888_8888_process_pixblock_tail_head + vld4.8 {d4, d5, d6, d7}, [DST_R, :128]! + vrshr.u16 q14, q8, #8 + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0xF + vrshr.u16 q15, q9, #8 + vrshr.u16 q12, q10, #8 + vrshr.u16 q13, q11, #8 + PF addne PF_X, PF_X, #8 + PF subne PF_CTL, PF_CTL, #1 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + PF cmp PF_X, ORIG_W + vraddhn.u16 d30, q12, q10 + vraddhn.u16 d31, q13, q11 + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 + fetch_src_pixblock + PF pld, [PF_SRC, PF_X, lsl #src_bpp_shift] + vmvn.8 d22, d3 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] + vst4.8 {d28, d29, d30, d31}, [DST_W, :128]! + PF subge PF_X, PF_X, ORIG_W + vmull.u8 q8, d22, d4 + PF subges PF_CTL, PF_CTL, #0x10 + vmull.u8 q9, d22, d5 + PF ldrgeb DUMMY, [PF_SRC, SRC_STRIDE, lsl #src_bpp_shift]! + vmull.u8 q10, d22, d6 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! + vmull.u8 q11, d22, d7 +.endm + +generate_composite_function \ + pixman_composite_over_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +generate_composite_function_single_scanline \ + pixman_composite_scanline_over_asm_neon, 32, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + default_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_8888_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_over_n_8888_process_pixblock_head + /* deinterleaved source pixels in {d0, d1, d2, d3} */ + /* inverted alpha in {d24} */ + /* destination pixels in {d4, d5, d6, d7} */ + vmull.u8 q8, d24, d4 + vmull.u8 q9, d24, d5 + vmull.u8 q10, d24, d6 + vmull.u8 q11, d24, d7 +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q2, q10, #8 + vrshr.u16 q3, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q2, q10 + vraddhn.u16 d31, q3, q11 + vqadd.u8 q14, q0, q14 + vqadd.u8 q15, q1, q15 +.endm + +.macro pixman_composite_over_n_8888_process_pixblock_tail_head + vrshr.u16 q14, q8, #8 + vrshr.u16 q15, q9, #8 + vrshr.u16 q2, q10, #8 + vrshr.u16 q3, q11, #8 + vraddhn.u16 d28, q14, q8 + vraddhn.u16 d29, q15, q9 + vraddhn.u16 d30, q2, q10 + vraddhn.u16 d31, q3, q11 + vld4.8 {d4, d5, d6, d7}, [DST_R, :128]! + vqadd.u8 q14, q0, q14 + PF add PF_X, PF_X, #8 + PF tst PF_CTL, #0x0F + PF addne PF_X, PF_X, #8 + PF subne PF_CTL, PF_CTL, #1 + vqadd.u8 q15, q1, q15 + PF cmp PF_X, ORIG_W + vmull.u8 q8, d24, d4 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] + vmull.u8 q9, d24, d5 + PF subge PF_X, PF_X, ORIG_W + vmull.u8 q10, d24, d6 + PF subges PF_CTL, PF_CTL, #0x10 + vmull.u8 q11, d24, d7 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! + vst4.8 {d28, d29, d30, d31}, [DST_W, :128]! +.endm + +.macro pixman_composite_over_n_8888_init + add DUMMY, sp, #ARGS_STACK_OFFSET + vld1.32 {d3[0]}, [DUMMY] + vdup.8 d0, d3[0] + vdup.8 d1, d3[1] + vdup.8 d2, d3[2] + vdup.8 d3, d3[3] + vmvn.8 d24, d3 /* get inverted alpha */ +.endm + +generate_composite_function \ + pixman_composite_over_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_READWRITE | FLAG_DEINTERLEAVE_32BPP, \ + 8, /* number of pixels, processed in a single block */ \ + 5, /* prefetch distance */ \ + pixman_composite_over_n_8888_init, \ + default_cleanup, \ + pixman_composite_over_8888_8888_process_pixblock_head, \ + pixman_composite_over_8888_8888_process_pixblock_tail, \ + pixman_composite_over_n_8888_process_pixblock_tail_head + +/******************************************************************************/ + +.macro pixman_composite_src_n_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_n_8888_process_pixblock_tail_head + vst1.32 {d0, d1, d2, d3}, [DST_W, :128]! +.endm + +.macro pixman_composite_src_n_8888_init + add DUMMY, sp, #ARGS_STACK_OFFSET + vld1.32 {d0[0]}, [DUMMY] + vsli.u64 d0, d0, #32 + vorr d1, d0, d0 + vorr q1, q0, q0 +.endm + +.macro pixman_composite_src_n_8888_cleanup +.endm + +generate_composite_function \ + pixman_composite_src_n_8888_asm_neon, 0, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 0, /* prefetch distance */ \ + pixman_composite_src_n_8888_init, \ + pixman_composite_src_n_8888_cleanup, \ + pixman_composite_src_n_8888_process_pixblock_head, \ + pixman_composite_src_n_8888_process_pixblock_tail, \ + pixman_composite_src_n_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ + +.macro pixman_composite_src_8888_8888_process_pixblock_head +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail +.endm + +.macro pixman_composite_src_8888_8888_process_pixblock_tail_head + vst1.32 {d0, d1, d2, d3}, [DST_W, :128]! + fetch_src_pixblock + cache_preload 8, 8 +.endm + +generate_composite_function \ + pixman_composite_src_8888_8888_asm_neon, 32, 0, 32, \ + FLAG_DST_WRITEONLY, \ + 8, /* number of pixels, processed in a single block */ \ + 10, /* prefetch distance */ \ + default_init, \ + default_cleanup, \ + pixman_composite_src_8888_8888_process_pixblock_head, \ + pixman_composite_src_8888_8888_process_pixblock_tail, \ + pixman_composite_src_8888_8888_process_pixblock_tail_head, \ + 0, /* dst_w_basereg */ \ + 0, /* dst_r_basereg */ \ + 0, /* src_basereg */ \ + 0 /* mask_basereg */ + +/******************************************************************************/ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.h new file mode 100644 index 0000000000..6add220a18 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/pixman/pixman-arm-neon-asm.h @@ -0,0 +1,1126 @@ +/* + * Copyright © 2009 Nokia Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Author: Siarhei Siamashka (siarhei.siamashka@nokia.com) + */ + +/* + * This file contains a macro ('generate_composite_function') which can + * construct 2D image processing functions, based on a common template. + * Any combinations of source, destination and mask images with 8bpp, + * 16bpp, 24bpp, 32bpp color formats are supported. + * + * This macro takes care of: + * - handling of leading and trailing unaligned pixels + * - doing most of the work related to L2 cache preload + * - encourages the use of software pipelining for better instructions + * scheduling + * + * The user of this macro has to provide some configuration parameters + * (bit depths for the images, prefetch distance, etc.) and a set of + * macros, which should implement basic code chunks responsible for + * pixels processing. See 'pixman-arm-neon-asm.S' file for the usage + * examples. + * + * TODO: + * - try overlapped pixel method (from Ian Rickards) when processing + * exactly two blocks of pixels + * - maybe add an option to do reverse scanline processing + */ + +/* + * Bit flags for 'generate_composite_function' macro which are used + * to tune generated functions behavior. + */ +.set FLAG_DST_WRITEONLY, 0 +.set FLAG_DST_READWRITE, 1 +.set FLAG_DEINTERLEAVE_32BPP, 2 + +/* + * Offset in stack where mask and source pointer/stride can be accessed + * from 'init' macro. This is useful for doing special handling for solid mask. + */ +.set ARGS_STACK_OFFSET, 40 + +/* + * Constants for selecting preferable prefetch type. + */ +.set PREFETCH_TYPE_NONE, 0 /* No prefetch at all */ +.set PREFETCH_TYPE_SIMPLE, 1 /* A simple, fixed-distance-ahead prefetch */ +.set PREFETCH_TYPE_ADVANCED, 2 /* Advanced fine-grained prefetch */ + +/* + * Definitions of supplementary pixld/pixst macros (for partial load/store of + * pixel data). + */ + +.macro pixldst1 op, elem_size, reg1, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1}, [&mem_operand&]! +.endif +.endm + +.macro pixldst2 op, elem_size, reg1, reg2, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1, d®2}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1, d®2}, [&mem_operand&]! +.endif +.endm + +.macro pixldst4 op, elem_size, reg1, reg2, reg3, reg4, mem_operand, abits +.if abits > 0 + op&.&elem_size {d®1, d®2, d®3, d®4}, [&mem_operand&, :&abits&]! +.else + op&.&elem_size {d®1, d®2, d®3, d®4}, [&mem_operand&]! +.endif +.endm + +.macro pixldst0 op, elem_size, reg1, idx, mem_operand, abits + op&.&elem_size {d®1[idx]}, [&mem_operand&]! +.endm + +.macro pixldst3 op, elem_size, reg1, reg2, reg3, mem_operand + op&.&elem_size {d®1, d®2, d®3}, [&mem_operand&]! +.endm + +.macro pixldst30 op, elem_size, reg1, reg2, reg3, idx, mem_operand + op&.&elem_size {d®1[idx], d®2[idx], d®3[idx]}, [&mem_operand&]! +.endm + +.macro pixldst numbytes, op, elem_size, basereg, mem_operand, abits +.if numbytes == 32 + pixldst4 op, elem_size, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif numbytes == 16 + pixldst2 op, elem_size, %(basereg+2), %(basereg+3), mem_operand, abits +.elseif numbytes == 8 + pixldst1 op, elem_size, %(basereg+1), mem_operand, abits +.elseif numbytes == 4 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 32) + pixldst0 op, 32, %(basereg+0), 1, mem_operand, abits + .elseif elem_size == 16 + pixldst0 op, 16, %(basereg+0), 2, mem_operand, abits + pixldst0 op, 16, %(basereg+0), 3, mem_operand, abits + .else + pixldst0 op, 8, %(basereg+0), 4, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 5, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 6, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 7, mem_operand, abits + .endif +.elseif numbytes == 2 + .if !RESPECT_STRICT_ALIGNMENT || (elem_size == 16) + pixldst0 op, 16, %(basereg+0), 1, mem_operand, abits + .else + pixldst0 op, 8, %(basereg+0), 2, mem_operand, abits + pixldst0 op, 8, %(basereg+0), 3, mem_operand, abits + .endif +.elseif numbytes == 1 + pixldst0 op, 8, %(basereg+0), 1, mem_operand, abits +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 vld4, 8, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 vld3, 8, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 vld3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), vld1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixst numpix, bpp, basereg, mem_operand, abits=0 +.if bpp > 0 +.if (bpp == 32) && (numpix == 8) && (DEINTERLEAVE_32BPP_ENABLED != 0) + pixldst4 vst4, 8, %(basereg+4), %(basereg+5), \ + %(basereg+6), %(basereg+7), mem_operand, abits +.elseif (bpp == 24) && (numpix == 8) + pixldst3 vst3, 8, %(basereg+3), %(basereg+4), %(basereg+5), mem_operand +.elseif (bpp == 24) && (numpix == 4) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 4, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 5, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 6, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 7, mem_operand +.elseif (bpp == 24) && (numpix == 2) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 2, mem_operand + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 3, mem_operand +.elseif (bpp == 24) && (numpix == 1) + pixldst30 vst3, 8, %(basereg+0), %(basereg+1), %(basereg+2), 1, mem_operand +.else + pixldst %(numpix * bpp / 8), vst1, %(bpp), basereg, mem_operand, abits +.endif +.endif +.endm + +.macro pixld_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixld numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixld numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +.macro pixst_a numpix, bpp, basereg, mem_operand +.if (bpp * numpix) <= 128 + pixst numpix, bpp, basereg, mem_operand, %(bpp * numpix) +.else + pixst numpix, bpp, basereg, mem_operand, 128 +.endif +.endm + +/* + * Pixel fetcher for nearest scaling (needs TMP1, TMP2, VX, UNIT_X register + * aliases to be defined) + */ +.macro pixld1_s elem_size, reg1, mem_operand +.if elem_size == 16 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #1 + vld1.16 {d®1&[0]}, [TMP1, :16] + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + vld1.16 {d®1&[1]}, [TMP2, :16] + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #1 + vld1.16 {d®1&[2]}, [TMP1, :16] + vld1.16 {d®1&[3]}, [TMP2, :16] +.elseif elem_size == 32 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #2 + mov TMP2, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[0]}, [TMP1, :32] + vld1.32 {d®1&[1]}, [TMP2, :32] +.else + .error "unsupported" +.endif +.endm + +.macro pixld2_s elem_size, reg1, reg2, mem_operand +.if 0 /* elem_size == 32 */ + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + mov TMP2, VX, asr #16 + sub VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[0]}, [TMP1, :32] + mov TMP1, VX, asr #16 + add VX, VX, UNIT_X, asl #1 + add TMP1, mem_operand, TMP1, asl #2 + vld1.32 {d®2&[0]}, [TMP2, :32] + mov TMP2, VX, asr #16 + add VX, VX, UNIT_X + add TMP2, mem_operand, TMP2, asl #2 + vld1.32 {d®1&[1]}, [TMP1, :32] + vld1.32 {d®2&[1]}, [TMP2, :32] +.else + pixld1_s elem_size, reg1, mem_operand + pixld1_s elem_size, reg2, mem_operand +.endif +.endm + +.macro pixld0_s elem_size, reg1, idx, mem_operand +.if elem_size == 16 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #1 + vld1.16 {d®1&[idx]}, [TMP1, :16] +.elseif elem_size == 32 + mov TMP1, VX, asr #16 + adds VX, VX, UNIT_X +5: subpls VX, VX, SRC_WIDTH_FIXED + bpl 5b + add TMP1, mem_operand, TMP1, asl #2 + vld1.32 {d®1&[idx]}, [TMP1, :32] +.endif +.endm + +.macro pixld_s_internal numbytes, elem_size, basereg, mem_operand +.if numbytes == 32 + pixld2_s elem_size, %(basereg+4), %(basereg+5), mem_operand + pixld2_s elem_size, %(basereg+6), %(basereg+7), mem_operand + pixdeinterleave elem_size, %(basereg+4) +.elseif numbytes == 16 + pixld2_s elem_size, %(basereg+2), %(basereg+3), mem_operand +.elseif numbytes == 8 + pixld1_s elem_size, %(basereg+1), mem_operand +.elseif numbytes == 4 + .if elem_size == 32 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .elseif elem_size == 16 + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 4, mem_operand + pixld0_s elem_size, %(basereg+0), 5, mem_operand + pixld0_s elem_size, %(basereg+0), 6, mem_operand + pixld0_s elem_size, %(basereg+0), 7, mem_operand + .endif +.elseif numbytes == 2 + .if elem_size == 16 + pixld0_s elem_size, %(basereg+0), 1, mem_operand + .else + pixld0_s elem_size, %(basereg+0), 2, mem_operand + pixld0_s elem_size, %(basereg+0), 3, mem_operand + .endif +.elseif numbytes == 1 + pixld0_s elem_size, %(basereg+0), 1, mem_operand +.else + .error "unsupported size: numbytes" +.endif +.endm + +.macro pixld_s numpix, bpp, basereg, mem_operand +.if bpp > 0 + pixld_s_internal %(numpix * bpp / 8), %(bpp), basereg, mem_operand +.endif +.endm + +.macro vuzp8 reg1, reg2 + vuzp.8 d®1, d®2 +.endm + +.macro vzip8 reg1, reg2 + vzip.8 d®1, d®2 +.endm + +/* deinterleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixdeinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vuzp8 %(basereg+0), %(basereg+1) + vuzp8 %(basereg+2), %(basereg+3) + vuzp8 %(basereg+1), %(basereg+3) + vuzp8 %(basereg+0), %(basereg+2) +.endif +.endm + +/* interleave B, G, R, A channels for eight 32bpp pixels in 4 registers */ +.macro pixinterleave bpp, basereg +.if (bpp == 32) && (DEINTERLEAVE_32BPP_ENABLED != 0) + vzip8 %(basereg+0), %(basereg+2) + vzip8 %(basereg+1), %(basereg+3) + vzip8 %(basereg+2), %(basereg+3) + vzip8 %(basereg+0), %(basereg+1) +.endif +.endm + +/* + * This is a macro for implementing cache preload. The main idea is that + * cache preload logic is mostly independent from the rest of pixels + * processing code. It starts at the top left pixel and moves forward + * across pixels and can jump across scanlines. Prefetch distance is + * handled in an 'incremental' way: it starts from 0 and advances to the + * optimal distance over time. After reaching optimal prefetch distance, + * it is kept constant. There are some checks which prevent prefetching + * unneeded pixel lines below the image (but it still can prefetch a bit + * more data on the right side of the image - not a big issue and may + * be actually helpful when rendering text glyphs). Additional trick is + * the use of LDR instruction for prefetch instead of PLD when moving to + * the next line, the point is that we have a high chance of getting TLB + * miss in this case, and PLD would be useless. + * + * This sounds like it may introduce a noticeable overhead (when working with + * fully cached data). But in reality, due to having a separate pipeline and + * instruction queue for NEON unit in ARM Cortex-A8, normal ARM code can + * execute simultaneously with NEON and be completely shadowed by it. Thus + * we get no performance overhead at all (*). This looks like a very nice + * feature of Cortex-A8, if used wisely. We don't have a hardware prefetcher, + * but still can implement some rather advanced prefetch logic in software + * for almost zero cost! + * + * (*) The overhead of the prefetcher is visible when running some trivial + * pixels processing like simple copy. Anyway, having prefetch is a must + * when working with the graphics data. + */ +.macro PF a, x:vararg +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_ADVANCED) + a x +.endif +.endm + +.macro cache_preload std_increment, boost_increment +.if (src_bpp_shift >= 0) || (dst_r_bpp != 0) || (mask_bpp_shift >= 0) +.if regs_shortage + PF ldr ORIG_W, [sp] /* If we are short on regs, ORIG_W is kept on stack */ +.endif +.if std_increment != 0 + PF add PF_X, PF_X, #std_increment +.endif + PF tst PF_CTL, #0xF + PF addne PF_X, PF_X, #boost_increment + PF subne PF_CTL, PF_CTL, #1 + PF cmp PF_X, ORIG_W +.if src_bpp_shift >= 0 + PF pld, [PF_SRC, PF_X, lsl #src_bpp_shift] +.endif +.if dst_r_bpp != 0 + PF pld, [PF_DST, PF_X, lsl #dst_bpp_shift] +.endif +.if mask_bpp_shift >= 0 + PF pld, [PF_MASK, PF_X, lsl #mask_bpp_shift] +.endif + PF subge PF_X, PF_X, ORIG_W + PF subges PF_CTL, PF_CTL, #0x10 +.if src_bpp_shift >= 0 + PF ldrgeb DUMMY, [PF_SRC, SRC_STRIDE, lsl #src_bpp_shift]! +.endif +.if dst_r_bpp != 0 + PF ldrgeb DUMMY, [PF_DST, DST_STRIDE, lsl #dst_bpp_shift]! +.endif +.if mask_bpp_shift >= 0 + PF ldrgeb DUMMY, [PF_MASK, MASK_STRIDE, lsl #mask_bpp_shift]! +.endif +.endif +.endm + +.macro cache_preload_simple +.if (PREFETCH_TYPE_CURRENT == PREFETCH_TYPE_SIMPLE) +.if src_bpp > 0 + pld [SRC, #(PREFETCH_DISTANCE_SIMPLE * src_bpp / 8)] +.endif +.if dst_r_bpp > 0 + pld [DST_R, #(PREFETCH_DISTANCE_SIMPLE * dst_r_bpp / 8)] +.endif +.if mask_bpp > 0 + pld [MASK, #(PREFETCH_DISTANCE_SIMPLE * mask_bpp / 8)] +.endif +.endif +.endm + +.macro fetch_mask_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK +.endm + +/* + * Macro which is used to process leading pixels until destination + * pointer is properly aligned (at 16 bytes boundary). When destination + * buffer uses 16bpp format, this is unnecessary, or even pointless. + */ +.macro ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head +.if dst_w_bpp != 24 + tst DST_R, #0xF + beq 2f + +.irp lowbit, 1, 2, 4, 8, 16 +local skip1 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_R, #lowbit + beq 1f +.endif + pixld_src (lowbit * 8 / dst_w_bpp), src_bpp, src_basereg, SRC + pixld (lowbit * 8 / dst_w_bpp), mask_bpp, mask_basereg, MASK +.if dst_r_bpp > 0 + pixld_a (lowbit * 8 / dst_r_bpp), dst_r_bpp, dst_r_basereg, DST_R +.else + add DST_R, DST_R, #lowbit +.endif + PF add PF_X, PF_X, #(lowbit * 8 / dst_w_bpp) + sub W, W, #(lowbit * 8 / dst_w_bpp) +1: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + process_pixblock_tail + + pixinterleave dst_w_bpp, dst_w_basereg +.irp lowbit, 1, 2, 4, 8, 16 +.if (dst_w_bpp <= (lowbit * 8)) && ((lowbit * 8) < (pixblock_size * dst_w_bpp)) +.if lowbit < 16 /* we don't need more than 16-byte alignment */ + tst DST_W, #lowbit + beq 1f +.endif + pixst_a (lowbit * 8 / dst_w_bpp), dst_w_bpp, dst_w_basereg, DST_W +1: +.endif +.endr +.endif +2: +.endm + +/* + * Special code for processing up to (pixblock_size - 1) remaining + * trailing pixels. As SIMD processing performs operation on + * pixblock_size pixels, anything smaller than this has to be loaded + * and stored in a special way. Loading and storing of pixel data is + * performed in such a way that we fill some 'slots' in the NEON + * registers (some slots naturally are unused), then perform compositing + * operation as usual. In the end, the data is taken from these 'slots' + * and saved to memory. + * + * cache_preload_flag - allows to suppress prefetch if + * set to 0 + * dst_aligned_flag - selects whether destination buffer + * is aligned + */ +.macro process_trailing_pixels cache_preload_flag, \ + dst_aligned_flag, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + tst W, #(pixblock_size - 1) + beq 2f +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 1f + pixld_src chunk_size, src_bpp, src_basereg, SRC + pixld chunk_size, mask_bpp, mask_basereg, MASK +.if dst_aligned_flag != 0 + pixld_a chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.else + pixld chunk_size, dst_r_bpp, dst_r_basereg, DST_R +.endif +.if cache_preload_flag != 0 + PF add PF_X, PF_X, #chunk_size +.endif +1: +.endif +.endr + pixdeinterleave src_bpp, src_basereg + pixdeinterleave mask_bpp, mask_basereg + pixdeinterleave dst_r_bpp, dst_r_basereg + + process_pixblock_head +.if cache_preload_flag != 0 + cache_preload 0, pixblock_size + cache_preload_simple +.endif + process_pixblock_tail + pixinterleave dst_w_bpp, dst_w_basereg +.irp chunk_size, 16, 8, 4, 2, 1 +.if pixblock_size > chunk_size + tst W, #chunk_size + beq 1f +.if dst_aligned_flag != 0 + pixst_a chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.else + pixst chunk_size, dst_w_bpp, dst_w_basereg, DST_W +.endif +1: +.endif +.endr +2: +.endm + +/* + * Macro, which performs all the needed operations to switch to the next + * scanline and start the next loop iteration unless all the scanlines + * are already processed. + */ +.macro advance_to_next_scanline start_of_loop_label +.if regs_shortage + ldrd W, [sp] /* load W and H (width and height) from stack */ +.else + mov W, ORIG_W +.endif + add DST_W, DST_W, DST_STRIDE, lsl #dst_bpp_shift +.if src_bpp != 0 + add SRC, SRC, SRC_STRIDE, lsl #src_bpp_shift +.endif +.if mask_bpp != 0 + add MASK, MASK, MASK_STRIDE, lsl #mask_bpp_shift +.endif +.if (dst_w_bpp != 24) + sub DST_W, DST_W, W, lsl #dst_bpp_shift +.endif +.if (src_bpp != 24) && (src_bpp != 0) + sub SRC, SRC, W, lsl #src_bpp_shift +.endif +.if (mask_bpp != 24) && (mask_bpp != 0) + sub MASK, MASK, W, lsl #mask_bpp_shift +.endif + subs H, H, #1 + mov DST_R, DST_W +.if regs_shortage + str H, [sp, #4] /* save updated height to stack */ +.endif + bge start_of_loop_label +.endm + +/* + * Registers are allocated in the following way by default: + * d0, d1, d2, d3 - reserved for loading source pixel data + * d4, d5, d6, d7 - reserved for loading destination pixel data + * d24, d25, d26, d27 - reserved for loading mask pixel data + * d28, d29, d30, d31 - final destination pixel data for writeback to memory + */ +.macro generate_composite_function fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + prefetch_distance, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + + push {r4-r12, lr} /* save all registers */ + +/* + * Select prefetch type for this function. If prefetch distance is + * set to 0 or one of the color formats is 24bpp, SIMPLE prefetch + * has to be used instead of ADVANCED. + */ + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_DEFAULT +.if prefetch_distance == 0 + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE +.elseif (PREFETCH_TYPE_CURRENT > PREFETCH_TYPE_SIMPLE) && \ + ((src_bpp_ == 24) || (mask_bpp_ == 24) || (dst_w_bpp_ == 24)) + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_SIMPLE +.endif + +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + + .macro pixld_src x:vararg + pixld x + .endm + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm +/* + * Assign symbolic names to registers + */ + W .req r0 /* width (is updated during processing) */ + H .req r1 /* height (is updated during processing) */ + DST_W .req r2 /* destination buffer pointer for writes */ + DST_STRIDE .req r3 /* destination image stride */ + SRC .req r4 /* source buffer pointer */ + SRC_STRIDE .req r5 /* source image stride */ + DST_R .req r6 /* destination buffer pointer for reads */ + + MASK .req r7 /* mask pointer */ + MASK_STRIDE .req r8 /* mask stride */ + + PF_CTL .req r9 /* combined lines counter and prefetch */ + /* distance increment counter */ + PF_X .req r10 /* pixel index in a scanline for current */ + /* pretetch position */ + PF_SRC .req r11 /* pointer to source scanline start */ + /* for prefetch purposes */ + PF_DST .req r12 /* pointer to destination scanline start */ + /* for prefetch purposes */ + PF_MASK .req r14 /* pointer to mask scanline start */ + /* for prefetch purposes */ +/* + * Check whether we have enough registers for all the local variables. + * If we don't have enough registers, original width and height are + * kept on top of stack (and 'regs_shortage' variable is set to indicate + * this for the rest of code). Even if there are enough registers, the + * allocation scheme may be a bit different depending on whether source + * or mask is not used. + */ +.if (PREFETCH_TYPE_CURRENT < PREFETCH_TYPE_ADVANCED) + ORIG_W .req r10 /* saved original width */ + DUMMY .req r12 /* temporary register */ + .set regs_shortage, 0 +.elseif mask_bpp == 0 + ORIG_W .req r7 /* saved original width */ + DUMMY .req r8 /* temporary register */ + .set regs_shortage, 0 +.elseif src_bpp == 0 + ORIG_W .req r4 /* saved original width */ + DUMMY .req r5 /* temporary register */ + .set regs_shortage, 0 +.else + ORIG_W .req r1 /* saved original width */ + DUMMY .req r1 /* temporary register */ + .set regs_shortage, 1 +.endif + + .set mask_bpp_shift, -1 +.if src_bpp == 32 + .set src_bpp_shift, 2 +.elseif src_bpp == 24 + .set src_bpp_shift, 0 +.elseif src_bpp == 16 + .set src_bpp_shift, 1 +.elseif src_bpp == 8 + .set src_bpp_shift, 0 +.elseif src_bpp == 0 + .set src_bpp_shift, -1 +.else + .error "requested src bpp (src_bpp) is not supported" +.endif +.if mask_bpp == 32 + .set mask_bpp_shift, 2 +.elseif mask_bpp == 24 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 8 + .set mask_bpp_shift, 0 +.elseif mask_bpp == 0 + .set mask_bpp_shift, -1 +.else + .error "requested mask bpp (mask_bpp) is not supported" +.endif +.if dst_w_bpp == 32 + .set dst_bpp_shift, 2 +.elseif dst_w_bpp == 24 + .set dst_bpp_shift, 0 +.elseif dst_w_bpp == 16 + .set dst_bpp_shift, 1 +.elseif dst_w_bpp == 8 + .set dst_bpp_shift, 0 +.else + .error "requested dst bpp (dst_w_bpp) is not supported" +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + +.if prefetch_distance < 0 || prefetch_distance > 15 + .error "invalid prefetch distance (prefetch_distance)" +.endif + +.if src_bpp > 0 + ldr SRC, [sp, #40] +.endif +.if mask_bpp > 0 + ldr MASK, [sp, #48] +.endif + PF mov PF_X, #0 +.if src_bpp > 0 + ldr SRC_STRIDE, [sp, #44] +.endif +.if mask_bpp > 0 + ldr MASK_STRIDE, [sp, #52] +.endif + mov DST_R, DST_W + +.if src_bpp == 24 + sub SRC_STRIDE, SRC_STRIDE, W + sub SRC_STRIDE, SRC_STRIDE, W, lsl #1 +.endif +.if mask_bpp == 24 + sub MASK_STRIDE, MASK_STRIDE, W + sub MASK_STRIDE, MASK_STRIDE, W, lsl #1 +.endif +.if dst_w_bpp == 24 + sub DST_STRIDE, DST_STRIDE, W + sub DST_STRIDE, DST_STRIDE, W, lsl #1 +.endif + +/* + * Setup advanced prefetcher initial state + */ + PF mov PF_SRC, SRC + PF mov PF_DST, DST_R + PF mov PF_MASK, MASK + /* PF_CTL = prefetch_distance | ((h - 1) << 4) */ + PF mov PF_CTL, H, lsl #4 + PF add PF_CTL, #(prefetch_distance - 0x10) + + init +.if regs_shortage + push {r0, r1} +.endif + subs H, H, #1 +.if regs_shortage + str H, [sp, #4] /* save updated height to stack */ +.else + mov ORIG_W, W +.endif + blt 9f + cmp W, #(pixblock_size * 2) + blt 8f +/* + * This is the start of the pipelined loop, which if optimized for + * long scanlines + */ +0: + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + PF add PF_X, PF_X, #pixblock_size + process_pixblock_head + cache_preload 0, pixblock_size + cache_preload_simple + subs W, W, #(pixblock_size * 2) + blt 2f +1: + process_pixblock_tail_head + cache_preload_simple + subs W, W, #pixblock_size + bge 1b +2: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W + + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 1, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 0b + +.if regs_shortage + pop {r0, r1} +.endif + cleanup + pop {r4-r12, pc} /* exit */ +/* + * This is the start of the loop, designed to process images with small width + * (less than pixblock_size * 2 pixels). In this case neither pipelining + * nor prefetch are used. + */ +8: + /* Process exactly pixblock_size pixels if needed */ + tst W, #pixblock_size + beq 1f + pixld pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + process_pixblock_tail + pixst pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +1: + /* Process the remaining trailing pixels in the scanline */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + advance_to_next_scanline 8b +9: +.if regs_shortage + pop {r0, r1} +.endif + cleanup + pop {r4-r12, pc} /* exit */ + + .purgem fetch_src_pixblock + .purgem pixld_src + + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq ORIG_W + .unreq W + .unreq H + .unreq SRC_STRIDE + .unreq DST_STRIDE + .unreq MASK_STRIDE + .unreq PF_CTL + .unreq PF_X + .unreq PF_SRC + .unreq PF_DST + .unreq PF_MASK + .unreq DUMMY + .endfunc +.endm + +/* + * A simplified variant of function generation template for a single + * scanline processing (for implementing pixman combine functions) + */ +.macro generate_composite_function_scanline use_nearest_scaling, \ + fname, \ + src_bpp_, \ + mask_bpp_, \ + dst_w_bpp_, \ + flags, \ + pixblock_size_, \ + init, \ + cleanup, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head, \ + dst_w_basereg_ = 28, \ + dst_r_basereg_ = 4, \ + src_basereg_ = 0, \ + mask_basereg_ = 24 + + pixman_asm_function fname + + .set PREFETCH_TYPE_CURRENT, PREFETCH_TYPE_NONE +/* + * Make some macro arguments globally visible and accessible + * from other macros + */ + .set src_bpp, src_bpp_ + .set mask_bpp, mask_bpp_ + .set dst_w_bpp, dst_w_bpp_ + .set pixblock_size, pixblock_size_ + .set dst_w_basereg, dst_w_basereg_ + .set dst_r_basereg, dst_r_basereg_ + .set src_basereg, src_basereg_ + .set mask_basereg, mask_basereg_ + +.if use_nearest_scaling != 0 + /* + * Assign symbolic names to registers for nearest scaling + */ + W .req r0 + DST_W .req r1 + SRC .req r2 + VX .req r3 + UNIT_X .req ip + MASK .req lr + TMP1 .req r4 + TMP2 .req r5 + DST_R .req r6 + SRC_WIDTH_FIXED .req r7 + + .macro pixld_src x:vararg + pixld_s x + .endm + + ldr UNIT_X, [sp] + push {r4-r8, lr} + ldr SRC_WIDTH_FIXED, [sp, #(24 + 4)] + .if mask_bpp != 0 + ldr MASK, [sp, #(24 + 8)] + .endif +.else + /* + * Assign symbolic names to registers + */ + W .req r0 /* width (is updated during processing) */ + DST_W .req r1 /* destination buffer pointer for writes */ + SRC .req r2 /* source buffer pointer */ + DST_R .req ip /* destination buffer pointer for reads */ + MASK .req r3 /* mask pointer */ + + .macro pixld_src x:vararg + pixld x + .endm +.endif + +.if (((flags) & FLAG_DST_READWRITE) != 0) + .set dst_r_bpp, dst_w_bpp +.else + .set dst_r_bpp, 0 +.endif +.if (((flags) & FLAG_DEINTERLEAVE_32BPP) != 0) + .set DEINTERLEAVE_32BPP_ENABLED, 1 +.else + .set DEINTERLEAVE_32BPP_ENABLED, 0 +.endif + + .macro fetch_src_pixblock + pixld_src pixblock_size, src_bpp, \ + (src_basereg - pixblock_size * src_bpp / 64), SRC + .endm + + init + mov DST_R, DST_W + + cmp W, #pixblock_size + blt 8f + + ensure_destination_ptr_alignment process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + subs W, W, #pixblock_size + blt 7f + + /* Implement "head (tail_head) ... (tail_head) tail" loop pattern */ + pixld_a pixblock_size, dst_r_bpp, \ + (dst_r_basereg - pixblock_size * dst_r_bpp / 64), DST_R + fetch_src_pixblock + pixld pixblock_size, mask_bpp, \ + (mask_basereg - pixblock_size * mask_bpp / 64), MASK + process_pixblock_head + subs W, W, #pixblock_size + blt 2f +1: + process_pixblock_tail_head + subs W, W, #pixblock_size + bge 1b +2: + process_pixblock_tail + pixst_a pixblock_size, dst_w_bpp, \ + (dst_w_basereg - pixblock_size * dst_w_bpp / 64), DST_W +7: + /* Process the remaining trailing pixels in the scanline (dst aligned) */ + process_trailing_pixels 0, 1, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup +.if use_nearest_scaling != 0 + pop {r4-r8, pc} /* exit */ +.else + bx lr /* exit */ +.endif +8: + /* Process the remaining trailing pixels in the scanline (dst unaligned) */ + process_trailing_pixels 0, 0, \ + process_pixblock_head, \ + process_pixblock_tail, \ + process_pixblock_tail_head + + cleanup + +.if use_nearest_scaling != 0 + pop {r4-r8, pc} /* exit */ + + .unreq DST_R + .unreq SRC + .unreq W + .unreq VX + .unreq UNIT_X + .unreq TMP1 + .unreq TMP2 + .unreq DST_W + .unreq MASK + .unreq SRC_WIDTH_FIXED + +.else + bx lr /* exit */ + + .unreq SRC + .unreq MASK + .unreq DST_R + .unreq DST_W + .unreq W +.endif + + .purgem fetch_src_pixblock + .purgem pixld_src + + .endfunc +.endm + +.macro generate_composite_function_single_scanline x:vararg + generate_composite_function_scanline 0, x +.endm + +.macro generate_composite_function_nearest_scanline x:vararg + generate_composite_function_scanline 1, x +.endm + +/* Default prologue/epilogue, nothing special needs to be done */ + +.macro default_init +.endm + +.macro default_cleanup +.endm + +/* + * Prologue/epilogue variant which additionally saves/restores d8-d15 + * registers (they need to be saved/restored by callee according to ABI). + * This is required if the code needs to use all the NEON registers. + */ + +.macro default_init_need_all_regs + vpush {d8-d15} +.endm + +.macro default_cleanup_need_all_regs + vpop {d8-d15} +.endm + +/******************************************************************************/ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.cpp new file mode 100644 index 0000000000..2c4933e812 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.cpp @@ -0,0 +1,59 @@ +/* + * configure stb_image about + * the image we will support + */ +#define STB_IMAGE_IMPLEMENTATION + +#define STBI_ONLY_JPEG +#define STBI_ONLY_PNG +#define STBI_NO_HDR +#define STBI_NO_LINEAR +#define STBI_NO_GIF +#define STBI_NO_PIC + +#include "stb_image.h" + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __declspec(dllexport) + #else + #define RLOTTIE_API __declspec(dllimport) + #endif +#else + #ifdef RLOTTIE_BUILD + #define RLOTTIE_API __attribute__ ((visibility ("default"))) + #else + #define RLOTTIE_API + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * exported function wrapper from the library + */ + +RLOTTIE_API unsigned char *lottie_image_load(char const *filename, int *x, + int *y, int *comp, int req_comp) +{ + return stbi_load(filename, x, y, comp, req_comp); +} + +RLOTTIE_API unsigned char *lottie_image_load_from_data(const char *imageData, + int len, int *x, int *y, + int *comp, int req_comp) +{ + unsigned char *data = (unsigned char *)imageData; + return stbi_load_from_memory(data, len, x, y, comp, req_comp); +} + +RLOTTIE_API void lottie_image_free(unsigned char *data) +{ + stbi_image_free(data); +} + +#ifdef __cplusplus +} +#endif diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.h new file mode 100644 index 0000000000..ae0c1e5297 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/stb/stb_image.h @@ -0,0 +1,7481 @@ +/* stb_image - v2.19 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine + John-Mark Allen + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan + Dave Moore Roy Eltham Hayaki Saito Nathan Reed + Won Chun Luke Graham Johan Duparc Nick Verigakis + the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar + Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex + Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 + Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw + Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus + Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo + Christian Floisand Kevin Schmidt github:darealshinji + Blazej Dariusz Roszkowski github:Michaelangel007 +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + if (fseek((FILE*) user, n, SEEK_CUR) == -1) + stbi__err("fseek() error"); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int /*bpc*/) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 8) { + STBI_ASSERT(ri.bits_per_channel == 16); + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + if (ri.bits_per_channel != 16) { + STBI_ASSERT(ri.bits_per_channel == 8); + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) || !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + if (fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR) == -1) { + stbi_image_free(result); + return stbi__errpuc("fseek() error", "File Seek Fail"); + } + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + if (fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR) == -1) { + stbi_image_free(result); + return (stbi__uint16 *) stbi__errpuc("fseek() error", "File Seek Fail"); + } + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + if (fseek(f, pos, SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) { + stbi__errpuc("outofmem", "Out of memory"); + return 0; + } + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = stbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[288] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + a->out = NULL; + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v >= 0 && v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->background = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + g->history = (stbi_uc *) stbi__malloc(g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "tranparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to teh color that was there the previous frame. + memset( g->out, 0x00, 4 * g->w * g->h ); + memset( g->background, 0x00, 4 * g->w * g->h ); // state of the background (starts transparent) + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispoase of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + out = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + if (delays) { + *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + (void) stbi__get32be(s); + (void) stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + if (pos >= 0) { + if (fseek(f,pos,SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); + } + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + if (pos >= 0) { + if (fseek(f,pos,SEEK_SET) == -1) return stbi__err("fseek() error", "File Seek Fail"); + } + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.cpp new file mode 100644 index 0000000000..16af387386 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.cpp @@ -0,0 +1,166 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "varenaalloc.h" +#include +#include + +static char* end_chain(char*) { return nullptr; } + +static uint32_t first_allocated_block(uint32_t blockSize, uint32_t firstHeapAllocation) { + return firstHeapAllocation > 0 ? firstHeapAllocation : + blockSize > 0 ? blockSize : 1024; +} + +VArenaAlloc::VArenaAlloc(char* block, size_t size, size_t firstHeapAllocation) + : fDtorCursor {block} + , fCursor {block} + , fEnd {block + ToU32(size)} + , fFirstBlock {block} + , fFirstSize {ToU32(size)} + , fFirstHeapAllocationSize {first_allocated_block(ToU32(size), ToU32(firstHeapAllocation))} +{ + if (size < sizeof(Footer)) { + fEnd = fCursor = fDtorCursor = nullptr; + } + + if (fCursor != nullptr) { + this->installFooter(end_chain, 0); + } +} + +VArenaAlloc::~VArenaAlloc() { + RunDtorsOnBlock(fDtorCursor); +} + +void VArenaAlloc::reset() { + this->~VArenaAlloc(); + new (this) VArenaAlloc{fFirstBlock, fFirstSize, fFirstHeapAllocationSize}; +} + +void VArenaAlloc::installFooter(FooterAction* action, uint32_t padding) { + assert(padding < 64); + int64_t actionInt = (int64_t)(intptr_t)action; + + // The top 14 bits should be either all 0s or all 1s. Check this. + assert((actionInt << 6) >> 6 == actionInt); + Footer encodedFooter = (actionInt << 6) | padding; + memmove(fCursor, &encodedFooter, sizeof(Footer)); + fCursor += sizeof(Footer); + fDtorCursor = fCursor; +} + +void VArenaAlloc::installPtrFooter(FooterAction* action, char* ptr, uint32_t padding) { + memmove(fCursor, &ptr, sizeof(char*)); + fCursor += sizeof(char*); + this->installFooter(action, padding); +} + +char* VArenaAlloc::SkipPod(char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(int32_t)); + int32_t skip; + memmove(&skip, objEnd, sizeof(int32_t)); + return objEnd - skip; +} + +void VArenaAlloc::RunDtorsOnBlock(char* footerEnd) { + while (footerEnd != nullptr) { + Footer footer; + memcpy(&footer, footerEnd - sizeof(Footer), sizeof(Footer)); + + FooterAction* action = (FooterAction*)(footer >> 6); + ptrdiff_t padding = footer & 63; + + footerEnd = action(footerEnd) - padding; + } +} + +char* VArenaAlloc::NextBlock(char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(char*)); + char* next; + memmove(&next, objEnd, sizeof(char*)); + RunDtorsOnBlock(next); + delete [] objEnd; + return nullptr; +} + +void VArenaAlloc::installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding) { + memmove(fCursor, &value, sizeof(uint32_t)); + fCursor += sizeof(uint32_t); + this->installFooter(action, padding); +} + +void VArenaAlloc::ensureSpace(uint32_t size, uint32_t alignment) { + constexpr uint32_t headerSize = sizeof(Footer) + sizeof(ptrdiff_t); + // The chrome c++ library we use does not define std::max_align_t. + // This must be conservative to add the right amount of extra memory to handle the alignment + // padding. + constexpr uint32_t alignof_max_align_t = 8; + constexpr uint32_t maxSize = std::numeric_limits::max(); + constexpr uint32_t overhead = headerSize + sizeof(Footer); + AssertRelease(size <= maxSize - overhead); + uint32_t objSizeAndOverhead = size + overhead; + if (alignment > alignof_max_align_t) { + uint32_t alignmentOverhead = alignment - 1; + AssertRelease(objSizeAndOverhead <= maxSize - alignmentOverhead); + objSizeAndOverhead += alignmentOverhead; + } + + uint32_t minAllocationSize; + if (fFirstHeapAllocationSize <= maxSize / fFib0) { + minAllocationSize = fFirstHeapAllocationSize * fFib0; + fFib0 += fFib1; + std::swap(fFib0, fFib1); + } else { + minAllocationSize = maxSize; + } + uint32_t allocationSize = std::max(objSizeAndOverhead, minAllocationSize); + + // Round up to a nice size. If > 32K align to 4K boundary else up to max_align_t. The > 32K + // heuristic is from the JEMalloc behavior. + { + uint32_t mask = allocationSize > (1 << 15) ? (1 << 12) - 1 : 16 - 1; + AssertRelease(allocationSize <= maxSize - mask); + allocationSize = (allocationSize + mask) & ~mask; + } + + char* newBlock = new char[allocationSize]; + + auto previousDtor = fDtorCursor; + fCursor = newBlock; + fDtorCursor = newBlock; + fEnd = fCursor + allocationSize; + this->installPtrFooter(NextBlock, previousDtor, 0); +} + +char* VArenaAlloc::allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment) { + uintptr_t mask = alignment - 1; + +restart: + uint32_t skipOverhead = 0; + bool needsSkipFooter = fCursor != fDtorCursor; + if (needsSkipFooter) { + skipOverhead = sizeof(Footer) + sizeof(uint32_t); + } + char* objStart = (char*)((uintptr_t)(fCursor + skipOverhead + mask) & ~mask); + uint32_t totalSize = sizeIncludingFooter + skipOverhead; + //std::cout<<"non POD object size = "< fEnd - objStart) { + this->ensureSpace(totalSize, alignment); + goto restart; + } + + AssertRelease((ptrdiff_t)totalSize <= fEnd - objStart); + + // Install a skip footer if needed, thus terminating a run of POD data. The calling code is + // responsible for installing the footer after the object. + if (needsSkipFooter) { + this->installUint32Footer(SkipPod, ToU32(fCursor - fDtorCursor), 0); + } + + return objStart; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.h new file mode 100644 index 0000000000..ed03b53f21 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/varenaalloc.h @@ -0,0 +1,232 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef VARENAALLOC_H +#define VARENAALLOC_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed +// to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an +// (optional) user-provided block of memory, and when that's exhausted it allocates on the heap, +// starting with an allocation of firstHeapAllocation bytes. If your data (plus a small overhead) +// fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in +// firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for +// firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used. +// +// Examples: +// +// char block[mostCasesSize]; +// SkArenaAlloc arena(block, mostCasesSize); +// +// If mostCasesSize is too large for the stack, you can use the following pattern. +// +// std::unique_ptr block{new char[mostCasesSize]}; +// SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize); +// +// If the program only sometimes allocates memory, use the following pattern. +// +// SkArenaAlloc arena(nullptr, 0, almostAllCasesSize); +// +// The storage does not necessarily need to be on the stack. Embedding the storage in a class also +// works. +// +// class Foo { +// char storage[mostCasesSize]; +// SkArenaAlloc arena (storage, mostCasesSize); +// }; +// +// In addition, the system is optimized to handle POD data including arrays of PODs (where +// POD is really data with no destructors). For POD data it has zero overhead per item, and a +// typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4 +// bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There +// is an addition overhead when switching from POD data to non-POD data of typically 8 bytes. +// +// If additional blocks are needed they are increased exponentially. This strategy bounds the +// recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using +// the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48 +// there are 71 allocations. +class VArenaAlloc { +public: + VArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation); + + explicit VArenaAlloc(size_t firstHeapAllocation) + : VArenaAlloc(nullptr, 0, firstHeapAllocation) + {} + + ~VArenaAlloc(); + + template + T* make(Args&&... args) { + uint32_t size = ToU32(sizeof(T)); + uint32_t alignment = ToU32(alignof(T)); + char* objStart; + if (std::is_trivially_destructible::value) { + objStart = this->allocObject(size, alignment); + fCursor = objStart + size; + } else { + objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment); + // Can never be UB because max value is alignof(T). + uint32_t padding = ToU32(objStart - fCursor); + + // Advance to end of object to install footer. + fCursor = objStart + size; + FooterAction* releaser = [](char* objEnd) { + char* objStart = objEnd - (sizeof(T) + sizeof(Footer)); + ((T*)objStart)->~T(); + return objStart; + }; + this->installFooter(releaser, padding); + } + + // This must be last to make objects with nested use of this allocator work. + return new(objStart) T(std::forward(args)...); + } + + template + T* makeArrayDefault(size_t count) { + uint32_t safeCount = ToU32(count); + T* array = (T*)this->commonArrayAlloc(safeCount); + + // If T is primitive then no initialization takes place. + for (size_t i = 0; i < safeCount; i++) { + new (&array[i]) T; + } + return array; + } + + template + T* makeArray(size_t count) { + uint32_t safeCount = ToU32(count); + T* array = (T*)this->commonArrayAlloc(safeCount); + + // If T is primitive then the memory is initialized. For example, an array of chars will + // be zeroed. + for (size_t i = 0; i < safeCount; i++) { + new (&array[i]) T(); + } + return array; + } + + // Only use makeBytesAlignedTo if none of the typed variants are impractical to use. + void* makeBytesAlignedTo(size_t size, size_t align) { + auto objStart = this->allocObject(ToU32(size), ToU32(align)); + fCursor = objStart + size; + return objStart; + } + + // Destroy all allocated objects, free any heap allocations. + void reset(); + +private: + static void AssertRelease(bool cond) { if (!cond) { ::abort(); } } + static uint32_t ToU32(size_t v) { + return (uint32_t)v; + } + + using Footer = int64_t; + using FooterAction = char* (char*); + + static char* SkipPod(char* footerEnd); + static void RunDtorsOnBlock(char* footerEnd); + static char* NextBlock(char* footerEnd); + + void installFooter(FooterAction* releaser, uint32_t padding); + void installUint32Footer(FooterAction* action, uint32_t value, uint32_t padding); + void installPtrFooter(FooterAction* action, char* ptr, uint32_t padding); + + void ensureSpace(uint32_t size, uint32_t alignment); + + char* allocObject(uint32_t size, uint32_t alignment) { + uintptr_t mask = alignment - 1; + uintptr_t alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; + uintptr_t totalSize = size + alignedOffset; + AssertRelease(totalSize >= size); + + if (totalSize > static_cast(fEnd - fCursor)) { + this->ensureSpace(size, alignment); + alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; + } + return fCursor + alignedOffset; + } + + char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment); + + template + char* commonArrayAlloc(uint32_t count) { + char* objStart; + AssertRelease(count <= std::numeric_limits::max() / sizeof(T)); + uint32_t arraySize = ToU32(count * sizeof(T)); + uint32_t alignment = ToU32(alignof(T)); + + if (std::is_trivially_destructible::value) { + objStart = this->allocObject(arraySize, alignment); + fCursor = objStart + arraySize; + } else { + constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t); + AssertRelease(arraySize <= std::numeric_limits::max() - overhead); + uint32_t totalSize = arraySize + overhead; + objStart = this->allocObjectWithFooter(totalSize, alignment); + + // Can never be UB because max value is alignof(T). + uint32_t padding = ToU32(objStart - fCursor); + + // Advance to end of array to install footer.? + fCursor = objStart + arraySize; + this->installUint32Footer( + [](char* footerEnd) { + char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t)); + uint32_t count; + memmove(&count, objEnd, sizeof(uint32_t)); + char* objStart = objEnd - count * sizeof(T); + T* array = (T*) objStart; + for (uint32_t i = 0; i < count; i++) { + array[i].~T(); + } + return objStart; + }, + ToU32(count), + padding); + } + + return objStart; + } + + char* fDtorCursor; + char* fCursor; + char* fEnd; + char* const fFirstBlock; + const uint32_t fFirstSize; + const uint32_t fFirstHeapAllocationSize; + + // Use the Fibonacci sequence as the growth factor for block size. The size of the block + // allocated is fFib0 * fFirstHeapAllocationSize. Using 2 ^ n * fFirstHeapAllocationSize + // had too much slop for Android. + uint32_t fFib0 {1}, fFib1 {1}; +}; + +// Helper for defining allocators with inline/reserved storage. +// For argument declarations, stick to the base type (SkArenaAlloc). +template +class VSTArenaAlloc : public VArenaAlloc { +public: + explicit VSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize) + : VArenaAlloc(fInlineStorage, InlineStorageSize, firstHeapAllocation) {} + +private: + char fInlineStorage[InlineStorageSize]; +}; + +#endif // VARENAALLOC_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.cpp new file mode 100644 index 0000000000..797d56440b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vbezier.h" +#include +#include "vline.h" + +V_BEGIN_NAMESPACE + +VBezier VBezier::fromPoints(const VPointF &p1, const VPointF &p2, + const VPointF &p3, const VPointF &p4) +{ + VBezier b; + b.x1 = p1.x(); + b.y1 = p1.y(); + b.x2 = p2.x(); + b.y2 = p2.y(); + b.x3 = p3.x(); + b.y3 = p3.y(); + b.x4 = p4.x(); + b.y4 = p4.y(); + return b; +} + +float VBezier::length() const +{ + const auto len = VLine::length(x1, y1, x2, y2) + + VLine::length(x2, y2, x3, y3) + + VLine::length(x3, y3, x4, y4); + + const auto chord = VLine::length(x1, y1, x4, y4); + + if ((len - chord) > 0.01) { + VBezier left, right; + split(&left, &right); + return left.length() + right.length(); + } + + return len; +} + +VBezier VBezier::onInterval(float t0, float t1) const +{ + if (t0 == 0 && t1 == 1) return *this; + + VBezier bezier = *this; + + VBezier result; + bezier.parameterSplitLeft(t0, &result); + float trueT = (t1 - t0) / (1 - t0); + bezier.parameterSplitLeft(trueT, &result); + + return result; +} + +float VBezier::tAtLength(float l, float totalLength) const +{ + float t = 1.0; + const float error = 0.01f; + if (l > totalLength || vCompare(l, totalLength)) return t; + + t *= 0.5; + + float lastBigger = 1.0; + while (1) { + VBezier right = *this; + VBezier left; + right.parameterSplitLeft(t, &left); + float lLen = left.length(); + if (fabs(lLen - l) < error) break; + + if (lLen < l) { + t += (lastBigger - t) * 0.5f; + } else { + lastBigger = t; + t -= t * 0.5f; + } + } + return t; +} + +void VBezier::splitAtLength(float len, VBezier *left, VBezier *right) +{ + float t; + + *right = *this; + t = right->tAtLength(len); + right->parameterSplitLeft(t, left); +} + +VPointF VBezier::derivative(float t) const +{ + // p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 * + // t^2) * p2 + t^2 * p3) + + float m_t = 1.0f - t; + + float d = t * t; + float a = -m_t * m_t; + float b = 1 - 4 * t + 3 * d; + float c = 2 * t - 3 * d; + + return 3 * VPointF(a * x1 + b * x2 + c * x3 + d * x4, + a * y1 + b * y2 + c * y3 + d * y4); +} + +float VBezier::angleAt(float t) const +{ + if (t < 0 || t > 1) { + return 0; + } + return VLine({}, derivative(t)).angle(); +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.h new file mode 100644 index 0000000000..18b7c596d1 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbezier.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VBEZIER_H +#define VBEZIER_H + +#include + +V_BEGIN_NAMESPACE + +class VBezier { +public: + VBezier() = default; + VPointF pointAt(float t) const; + float angleAt(float t) const; + VBezier onInterval(float t0, float t1) const; + float length() const; + static void coefficients(float t, float &a, float &b, float &c, float &d); + static VBezier fromPoints(const VPointF &start, const VPointF &cp1, + const VPointF &cp2, const VPointF &end); + inline void parameterSplitLeft(float t, VBezier *left); + inline void split(VBezier *firstHalf, VBezier *secondHalf) const; + float tAtLength(float len) const { return tAtLength(len , length());} + float tAtLength(float len, float totalLength) const; + void splitAtLength(float len, VBezier *left, VBezier *right); + VPointF pt1() const { return {x1, y1}; } + VPointF pt2() const { return {x2, y2}; } + VPointF pt3() const { return {x3, y3}; } + VPointF pt4() const { return {x4, y4}; } + +private: + VPointF derivative(float t) const; + float x1, y1, x2, y2, x3, y3, x4, y4; +}; + +inline void VBezier::coefficients(float t, float &a, float &b, float &c, + float &d) +{ + float m_t = 1.0f - t; + b = m_t * m_t; + c = t * t; + d = c * t; + a = b * m_t; + b *= 3.0f * t; + c *= 3.0f * m_t; +} + +inline VPointF VBezier::pointAt(float t) const +{ + // numerically more stable: + float x, y; + + float m_t = 1.0f - t; + { + float a = x1 * m_t + x2 * t; + float b = x2 * m_t + x3 * t; + float c = x3 * m_t + x4 * t; + a = a * m_t + b * t; + b = b * m_t + c * t; + x = a * m_t + b * t; + } + { + float a = y1 * m_t + y2 * t; + float b = y2 * m_t + y3 * t; + float c = y3 * m_t + y4 * t; + a = a * m_t + b * t; + b = b * m_t + c * t; + y = a * m_t + b * t; + } + return {x, y}; +} + +inline void VBezier::parameterSplitLeft(float t, VBezier *left) +{ + left->x1 = x1; + left->y1 = y1; + + left->x2 = x1 + t * (x2 - x1); + left->y2 = y1 + t * (y2 - y1); + + left->x3 = x2 + t * (x3 - x2); // temporary holding spot + left->y3 = y2 + t * (y3 - y2); // temporary holding spot + + x3 = x3 + t * (x4 - x3); + y3 = y3 + t * (y4 - y3); + + x2 = left->x3 + t * (x3 - left->x3); + y2 = left->y3 + t * (y3 - left->y3); + + left->x3 = left->x2 + t * (left->x3 - left->x2); + left->y3 = left->y2 + t * (left->y3 - left->y2); + + left->x4 = x1 = left->x3 + t * (x2 - left->x3); + left->y4 = y1 = left->y3 + t * (y2 - left->y3); +} + +inline void VBezier::split(VBezier *firstHalf, VBezier *secondHalf) const +{ + float c = (x2 + x3) * 0.5f; + firstHalf->x2 = (x1 + x2) * 0.5f; + secondHalf->x3 = (x3 + x4) * 0.5f; + firstHalf->x1 = x1; + secondHalf->x4 = x4; + firstHalf->x3 = (firstHalf->x2 + c) * 0.5f; + secondHalf->x2 = (secondHalf->x3 + c) * 0.5f; + firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2) * 0.5f; + + c = (y2 + y3) / 2; + firstHalf->y2 = (y1 + y2) * 0.5f; + secondHalf->y3 = (y3 + y4) * 0.5f; + firstHalf->y1 = y1; + secondHalf->y4 = y4; + firstHalf->y3 = (firstHalf->y2 + c) * 0.5f; + secondHalf->y2 = (secondHalf->y3 + c) * 0.5f; + firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2) * 0.5f; +} + +V_END_NAMESPACE + +#endif // VBEZIER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.cpp new file mode 100644 index 0000000000..22cb24346a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vbitmap.h" +#include +#include +#include "vdrawhelper.h" +#include "vglobal.h" + +V_BEGIN_NAMESPACE + +void VBitmap::Impl::reset(size_t width, size_t height, VBitmap::Format format) +{ + mRoData = nullptr; + mWidth = uint(width); + mHeight = uint(height); + mFormat = format; + + mDepth = depth(format); + mStride = ((mWidth * mDepth + 31) >> 5) + << 2; // bytes per scanline (must be multiple of 4) + mOwnData = std::make_unique(mStride * mHeight); +} + +void VBitmap::Impl::reset(uchar *data, size_t width, size_t height, size_t bytesPerLine, + VBitmap::Format format) +{ + mRoData = data; + mWidth = uint(width); + mHeight = uint(height); + mStride = uint(bytesPerLine); + mFormat = format; + mDepth = depth(format); + mOwnData = nullptr; +} + +uchar VBitmap::Impl::depth(VBitmap::Format format) +{ + uchar depth = 1; + switch (format) { + case VBitmap::Format::Alpha8: + depth = 8; + break; + case VBitmap::Format::ARGB32: + case VBitmap::Format::ARGB32_Premultiplied: + depth = 32; + break; + default: + break; + } + return depth; +} + +void VBitmap::Impl::fill(uint /*pixel*/) +{ + //@TODO +} + +void VBitmap::Impl::updateLuma() +{ + if (mFormat != VBitmap::Format::ARGB32_Premultiplied) return; + auto dataPtr = data(); + for (uint col = 0; col < mHeight; col++) { + uint *pixel = (uint *)(dataPtr + mStride * col); + for (uint row = 0; row < mWidth; row++) { + int alpha = vAlpha(*pixel); + if (alpha == 0) { + pixel++; + continue; + } + + int red = vRed(*pixel); + int green = vGreen(*pixel); + int blue = vBlue(*pixel); + + if (alpha != 255) { + // un multiply + red = (red * 255) / alpha; + green = (green * 255) / alpha; + blue = (blue * 255) / alpha; + } + int luminosity = int(0.299f * red + 0.587f * green + 0.114f * blue); + *pixel = luminosity << 24; + pixel++; + } + } +} + +VBitmap::VBitmap(size_t width, size_t height, VBitmap::Format format) +{ + if (width <= 0 || height <= 0 || format == Format::Invalid) return; + + mImpl = rc_ptr(width, height, format); +} + +VBitmap::VBitmap(uchar *data, size_t width, size_t height, size_t bytesPerLine, + VBitmap::Format format) +{ + if (!data || width <= 0 || height <= 0 || bytesPerLine <= 0 || + format == Format::Invalid) + return; + + mImpl = rc_ptr(data, width, height, bytesPerLine, format); +} + +void VBitmap::reset(uchar *data, size_t w, size_t h, size_t bytesPerLine, + VBitmap::Format format) +{ + if (mImpl) { + mImpl->reset(data, w, h, bytesPerLine, format); + } else { + mImpl = rc_ptr(data, w, h, bytesPerLine, format); + } +} + +void VBitmap::reset(size_t w, size_t h, VBitmap::Format format) +{ + if (mImpl) { + if (w == mImpl->width() && h == mImpl->height() && + format == mImpl->format()) { + return; + } + mImpl->reset(w, h, format); + } else { + mImpl = rc_ptr(w, h, format); + } +} + +size_t VBitmap::stride() const +{ + return mImpl ? mImpl->stride() : 0; +} + +size_t VBitmap::width() const +{ + return mImpl ? mImpl->width() : 0; +} + +size_t VBitmap::height() const +{ + return mImpl ? mImpl->height() : 0; +} + +size_t VBitmap::depth() const +{ + return mImpl ? mImpl->mDepth : 0; +} + +uchar *VBitmap::data() +{ + return mImpl ? mImpl->data() : nullptr; +} + +uchar *VBitmap::data() const +{ + return mImpl ? mImpl->data() : nullptr; +} + +VRect VBitmap::rect() const +{ + return mImpl ? mImpl->rect() : VRect(); +} + +VSize VBitmap::size() const +{ + return mImpl ? mImpl->size() : VSize(); +} + +bool VBitmap::valid() const +{ + return mImpl; +} + +VBitmap::Format VBitmap::format() const +{ + return mImpl ? mImpl->format() : VBitmap::Format::Invalid; +} + +void VBitmap::fill(uint pixel) +{ + if (mImpl) mImpl->fill(pixel); +} + +/* + * This is special function which converts + * RGB value to Luminosity and stores it in + * the Alpha component of the pixel. + * After this conversion the bitmap data is no more + * in RGB space. but the Alpha component contains the + * Luminosity value of the pixel in HSL color space. + * NOTE: this api has its own special usecase + * make sure you know what you are doing before using + * this api. + */ +void VBitmap::updateLuma() +{ + if (mImpl) mImpl->updateLuma(); +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.h new file mode 100644 index 0000000000..a068d7446a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbitmap.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VBITMAP_H +#define VBITMAP_H + +#include "vrect.h" +#include + +V_BEGIN_NAMESPACE + +class VBitmap { +public: + enum class Format: uchar { + Invalid, + Alpha8, + ARGB32, + ARGB32_Premultiplied + }; + + VBitmap() = default; + VBitmap(size_t w, size_t h, VBitmap::Format format); + VBitmap(uchar *data, size_t w, size_t h, size_t bytesPerLine, VBitmap::Format format); + void reset(uchar *data, size_t w, size_t h, size_t stride, VBitmap::Format format); + void reset(size_t w, size_t h, VBitmap::Format format=Format::ARGB32_Premultiplied); + size_t stride() const; + size_t width() const; + size_t height() const; + size_t depth() const; + VBitmap::Format format() const; + bool valid() const; + uchar * data(); + uchar * data() const; + VRect rect() const; + VSize size() const; + void fill(uint pixel); + void updateLuma(); +private: + struct Impl { + std::unique_ptr mOwnData{nullptr}; + uchar * mRoData{nullptr}; + uint mWidth{0}; + uint mHeight{0}; + uint mStride{0}; + uchar mDepth{0}; + VBitmap::Format mFormat{VBitmap::Format::Invalid}; + + explicit Impl(size_t width, size_t height, VBitmap::Format format) + { + reset(width, height, format); + } + explicit Impl(uchar *data, size_t w, size_t h, size_t bytesPerLine, VBitmap::Format format) + { + reset(data, w, h, bytesPerLine, format); + } + VRect rect() const { return VRect(0, 0, mWidth, mHeight);} + VSize size() const { return VSize(mWidth, mHeight); } + size_t stride() const { return mStride; } + size_t width() const { return mWidth; } + size_t height() const { return mHeight; } + uchar * data() { return mRoData ? mRoData : mOwnData.get(); } + VBitmap::Format format() const { return mFormat; } + void reset(uchar *, size_t, size_t, size_t, VBitmap::Format); + void reset(size_t, size_t, VBitmap::Format); + static uchar depth(VBitmap::Format format); + void fill(uint); + void updateLuma(); + }; + + rc_ptr mImpl; +}; + +V_END_NAMESPACE + +#endif // VBITMAP_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.cpp new file mode 100644 index 0000000000..fc0902ba80 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vbrush.h" + +V_BEGIN_NAMESPACE + +VGradient::VGradient(VGradient::Type type) + : mType(type) +{ + if (mType == Type::Linear) + linear.x1 = linear.y1 = linear.x2 = linear.y2 = 0.0f; + else + radial.cx = radial.cy = radial.fx = + radial.fy = radial.cradius = radial.fradius = 0.0f; +} + +void VGradient::setStops(const VGradientStops &stops) +{ + mStops = stops; +} + +VBrush::VBrush(const VColor &color) : mType(VBrush::Type::Solid), mColor(color) +{ +} + +VBrush::VBrush(uchar r, uchar g, uchar b, uchar a) + : mType(VBrush::Type::Solid), mColor(r, g, b, a) + +{ +} + +VBrush::VBrush(const VGradient *gradient) +{ + if (!gradient) return; + + mGradient = gradient; + + if (gradient->mType == VGradient::Type::Linear) { + mType = VBrush::Type::LinearGradient; + } else if (gradient->mType == VGradient::Type::Radial) { + mType = VBrush::Type::RadialGradient; + } +} + +VBrush::VBrush(const VTexture *texture):mType(VBrush::Type::Texture), mTexture(texture) +{ +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.h new file mode 100644 index 0000000000..74f8dcafe4 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vbrush.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VBRUSH_H +#define VBRUSH_H + +#include +#include "vglobal.h" +#include "vmatrix.h" +#include "vpoint.h" +#include "vbitmap.h" + +V_BEGIN_NAMESPACE + +using VGradientStop = std::pair; +using VGradientStops = std::vector; +class VGradient { +public: + enum class Mode { Absolute, Relative }; + enum class Spread { Pad, Repeat, Reflect }; + enum class Type { Linear, Radial }; + explicit VGradient(VGradient::Type type); + void setStops(const VGradientStops &stops); + void setAlpha(float alpha) {mAlpha = alpha;} + float alpha() const {return mAlpha;} + +public: + static constexpr int colorTableSize = 1024; + VGradient::Type mType{Type::Linear}; + VGradient::Spread mSpread{Spread::Pad}; + VGradient::Mode mMode{Mode::Absolute}; + VGradientStops mStops; + float mAlpha{1.0}; + struct Linear{ + float x1{0}, y1{0}, x2{0}, y2{0}; + }; + struct Radial{ + float cx{0}, cy{0}, fx{0}, fy{0}, cradius{0}, fradius{0}; + }; + union { + Linear linear; + Radial radial; + }; + VMatrix mMatrix; +}; + +struct VTexture { + VBitmap mBitmap; + VMatrix mMatrix; + int mAlpha{255}; +}; + +class VBrush { +public: + enum class Type { NoBrush, Solid, LinearGradient, RadialGradient, Texture }; + VBrush():mType(Type::NoBrush),mColor(){}; + explicit VBrush(const VColor &color); + explicit VBrush(const VGradient *gradient); + explicit VBrush(uchar r, uchar g, uchar b, uchar a); + explicit VBrush(const VTexture *texture); + inline VBrush::Type type() const { return mType; } +public: + VBrush::Type mType{Type::NoBrush}; + union { + VColor mColor{}; + const VGradient *mGradient; + const VTexture *mTexture; + }; +}; + +V_END_NAMESPACE + +#endif // VBRUSH_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vcowptr.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vcowptr.h new file mode 100644 index 0000000000..af2c7f248d --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vcowptr.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VCOWPTR_H +#define VCOWPTR_H + +#include +#include + +template +class vcow_ptr { + struct model { + std::atomic mRef{1}; + + model() = default; + + template + explicit model(Args&&... args) : mValue(std::forward(args)...){} + explicit model(const T& other) : mValue(other){} + + T mValue; + }; + model* mModel; + +public: + using element_type = T; + + vcow_ptr() + { + static model default_s; + mModel = &default_s; + ++mModel->mRef; + } + + ~vcow_ptr() + { + if (mModel && (--mModel->mRef == 0)) delete mModel; + } + + template + explicit vcow_ptr(Args&&... args) : mModel(new model(std::forward(args)...)) + { + } + + vcow_ptr(const vcow_ptr& x) noexcept : mModel(x.mModel) + { + assert(mModel); + ++mModel->mRef; + } + vcow_ptr(vcow_ptr&& x) noexcept : mModel(x.mModel) + { + assert(mModel); + x.mModel = nullptr; + } + + auto operator=(const vcow_ptr& x) noexcept -> vcow_ptr& + { + *this = vcow_ptr(x); + return *this; + } + + auto operator=(vcow_ptr&& x) noexcept -> vcow_ptr& + { + auto tmp = std::move(x); + swap(*this, tmp); + return *this; + } + + auto operator*() const noexcept -> const element_type& { return read(); } + + auto operator-> () const noexcept -> const element_type* { return &read(); } + + std::size_t refCount() const noexcept + { + assert(mModel); + + return mModel->mRef; + } + + bool unique() const noexcept + { + assert(mModel); + + return mModel->mRef == 1; + } + + auto write() -> element_type& + { + if (!unique()) *this = vcow_ptr(read()); + + return mModel->mValue; + } + + auto read() const noexcept -> const element_type& + { + assert(mModel); + + return mModel->mValue; + } + + friend inline void swap(vcow_ptr& x, vcow_ptr& y) noexcept + { + std::swap(x.mModel, y.mModel); + } +}; + +#endif // VCOWPTR_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.cpp new file mode 100644 index 0000000000..759e30ac21 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "vbezier.h" + +#include + +#include "vdasher.h" +#include "vline.h" + +V_BEGIN_NAMESPACE + +static constexpr float tolerance = 0.1f; +VDasher::VDasher(const float *dashArray, size_t size) +{ + mDashArray = reinterpret_cast(dashArray); + mArraySize = size / 2; + if (size % 2) mDashOffset = dashArray[size - 1]; + mIndex = 0; + mCurrentLength = 0; + mDiscard = false; + //if the dash array contains ZERO length + // segments or ZERO lengths gaps we could + // optimize those usecase. + for (size_t i = 0; i < mArraySize; i++) { + if (!vCompare(mDashArray[i].length, 0.0f)) + mNoLength = false; + if (!vCompare(mDashArray[i].gap, 0.0f)) + mNoGap = false; + } +} + +void VDasher::moveTo(const VPointF &p) +{ + mDiscard = false; + mStartNewSegment = true; + mCurPt = p; + mIndex = 0; + + if (!vCompare(mDashOffset, 0.0f)) { + float totalLength = 0.0; + for (size_t i = 0; i < mArraySize; i++) { + totalLength = mDashArray[i].length + mDashArray[i].gap; + } + float normalizeLen = std::fmod(mDashOffset, totalLength); + if (normalizeLen < 0.0f) { + normalizeLen = totalLength + normalizeLen; + } + // now the length is less than total length and +ve + // findout the current dash index , dashlength and gap. + for (size_t i = 0; i < mArraySize; i++) { + if (normalizeLen < mDashArray[i].length) { + mIndex = i; + mCurrentLength = mDashArray[i].length - normalizeLen; + mDiscard = false; + break; + } + normalizeLen -= mDashArray[i].length; + if (normalizeLen < mDashArray[i].gap) { + mIndex = i; + mCurrentLength = mDashArray[i].gap - normalizeLen; + mDiscard = true; + break; + } + normalizeLen -= mDashArray[i].gap; + } + } else { + mCurrentLength = mDashArray[mIndex].length; + } + if (vIsZero(mCurrentLength)) updateActiveSegment(); +} + +void VDasher::addLine(const VPointF &p) +{ + if (mDiscard) return; + + if (mStartNewSegment) { + mResult->moveTo(mCurPt); + mStartNewSegment = false; + } + mResult->lineTo(p); +} + +void VDasher::updateActiveSegment() +{ + mStartNewSegment = true; + + if (mDiscard) { + mDiscard = false; + mIndex = (mIndex + 1) % mArraySize; + mCurrentLength = mDashArray[mIndex].length; + } else { + mDiscard = true; + mCurrentLength = mDashArray[mIndex].gap; + } + if (vIsZero(mCurrentLength)) updateActiveSegment(); +} + +void VDasher::lineTo(const VPointF &p) +{ + VLine left, right; + VLine line(mCurPt, p); + float length = line.length(); + + if (length <= mCurrentLength) { + mCurrentLength -= length; + addLine(p); + } else { + while (length > mCurrentLength) { + length -= mCurrentLength; + line.splitAtLength(mCurrentLength, left, right); + + addLine(left.p2()); + updateActiveSegment(); + + line = right; + mCurPt = line.p1(); + } + // handle remainder + if (length > tolerance) { + mCurrentLength -= length; + addLine(line.p2()); + } + } + + if (mCurrentLength < tolerance) updateActiveSegment(); + + mCurPt = p; +} + +void VDasher::addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e) +{ + if (mDiscard) return; + + if (mStartNewSegment) { + mResult->moveTo(mCurPt); + mStartNewSegment = false; + } + mResult->cubicTo(cp1, cp2, e); +} + +void VDasher::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e) +{ + VBezier left, right; + VBezier b = VBezier::fromPoints(mCurPt, cp1, cp2, e); + float bezLen = b.length(); + + if (bezLen <= mCurrentLength) { + mCurrentLength -= bezLen; + addCubic(cp1, cp2, e); + } else { + while (bezLen > mCurrentLength) { + bezLen -= mCurrentLength; + b.splitAtLength(mCurrentLength, &left, &right); + + addCubic(left.pt2(), left.pt3(), left.pt4()); + updateActiveSegment(); + + b = right; + mCurPt = b.pt1(); + } + // handle remainder + if (bezLen > tolerance) { + mCurrentLength -= bezLen; + addCubic(b.pt2(), b.pt3(), b.pt4()); + } + } + + if (mCurrentLength < tolerance) updateActiveSegment(); + + mCurPt = e; +} + +void VDasher::dashHelper(const VPath &path, VPath &result) +{ + mResult = &result; + mResult->reserve(path.points().size(), path.elements().size()); + mIndex = 0; + const std::vector &elms = path.elements(); + const std::vector & pts = path.points(); + const VPointF * ptPtr = pts.data(); + + for (auto &i : elms) { + switch (i) { + case VPath::Element::MoveTo: { + moveTo(*ptPtr++); + break; + } + case VPath::Element::LineTo: { + lineTo(*ptPtr++); + break; + } + case VPath::Element::CubicTo: { + cubicTo(*ptPtr, *(ptPtr + 1), *(ptPtr + 2)); + ptPtr += 3; + break; + } + case VPath::Element::Close: { + // The point is already joined to start point in VPath + // no need to do anything here. + break; + } + } + } + mResult = nullptr; +} + +void VDasher::dashed(const VPath &path, VPath &result) +{ + if (mNoLength && mNoGap) return result.reset(); + + if (path.empty() || mNoLength) return result.reset(); + + if (mNoGap) return result.clone(path); + + result.reset(); + + dashHelper(path, result); +} + +VPath VDasher::dashed(const VPath &path) +{ + if (mNoLength && mNoGap) return path; + + if (path.empty() || mNoLength) return VPath(); + + if (mNoGap) return path; + + VPath result; + + dashHelper(path, result); + + return result; +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.h new file mode 100644 index 0000000000..d9d62c2fb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdasher.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VDASHER_H +#define VDASHER_H +#include "vpath.h" + +V_BEGIN_NAMESPACE + +class VDasher { +public: + VDasher(const float *dashArray, size_t size); + VPath dashed(const VPath &path); + void dashed(const VPath &path, VPath &result); + +private: + void moveTo(const VPointF &p); + void lineTo(const VPointF &p); + void cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e); + void close(); + void addLine(const VPointF &p); + void addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e); + void updateActiveSegment(); + +private: + void dashHelper(const VPath &path, VPath &result); + struct Dash { + float length; + float gap; + }; + const VDasher::Dash *mDashArray; + size_t mArraySize{0}; + VPointF mCurPt; + size_t mIndex{0}; /* index to the dash Array */ + float mCurrentLength; + float mDashOffset{0}; + VPath *mResult{nullptr}; + bool mDiscard{false}; + bool mStartNewSegment{true}; + bool mNoLength{true}; + bool mNoGap{true}; +}; + +V_END_NAMESPACE + +#endif // VDASHER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.cpp new file mode 100644 index 0000000000..03196ef373 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.cpp @@ -0,0 +1,758 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vdebug.h" + +#ifdef LOTTIE_LOGGING_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/* Returns microseconds since epoch */ +uint64_t timestamp_now() +{ + return std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); +} + +/* I want [2016-10-13 00:01:23.528514] */ +void format_timestamp(std::ostream& os, uint64_t timestamp) +{ + // The next 3 lines do not work on MSVC! + // auto duration = std::chrono::microseconds(timestamp); + // std::chrono::high_resolution_clock::time_point time_point(duration); + // std::time_t time_t = + // std::chrono::high_resolution_clock::to_time_t(time_point); + std::time_t time_t = timestamp / 1000000; + auto gmtime = std::gmtime(&time_t); + char buffer[32]; + strftime(buffer, 32, "%Y-%m-%d %T.", gmtime); + char microseconds[7]; + snprintf(microseconds, 7, "%06llu", + (long long unsigned int)timestamp % 1000000); + os << '[' << buffer << microseconds << ']'; +} + +std::thread::id this_thread_id() +{ + static thread_local const std::thread::id id = std::this_thread::get_id(); + return id; +} + +template +struct TupleIndex; + +template +struct TupleIndex > { + static constexpr const std::size_t value = 0; +}; + +template +struct TupleIndex > { + static constexpr const std::size_t value = + 1 + TupleIndex >::value; +}; + +} // anonymous namespace + +typedef std::tuple + SupportedTypes; + +char const* to_string(LogLevel loglevel) +{ + switch (loglevel) { + case LogLevel::OFF: + return "OFF"; + case LogLevel::INFO: + return "INFO"; + case LogLevel::WARN: + return "WARN"; + case LogLevel::CRIT: + return "CRIT"; + } + return "XXXX"; +} + +template +void VDebug::encode(Arg arg) +{ + *reinterpret_cast(buffer()) = arg; + m_bytes_used += sizeof(Arg); +} + +template +void VDebug::encode(Arg arg, uint8_t type_id) +{ + resize_buffer_if_needed(sizeof(Arg) + sizeof(uint8_t)); + encode(type_id); + encode(arg); +} + +VDebug::VDebug(LogLevel level, char const* file, char const* function, + uint32_t line) + : m_bytes_used(0), m_buffer_size(sizeof(m_stack_buffer)) +{ + encode(timestamp_now()); + encode(this_thread_id()); + encode(string_literal_t(file)); + encode(string_literal_t(function)); + encode(line); + encode(level); + if (level == LogLevel::INFO) { + m_logAll = false; + } else { + m_logAll = true; + } +} + +VDebug::~VDebug() = default; + +void VDebug::stringify(std::ostream& os) +{ + char* b = !m_heap_buffer ? m_stack_buffer : m_heap_buffer.get(); + char const* const end = b + m_bytes_used; + uint64_t timestamp = *reinterpret_cast(b); + b += sizeof(uint64_t); + std::thread::id threadid = *reinterpret_cast(b); + b += sizeof(std::thread::id); + string_literal_t file = *reinterpret_cast(b); + b += sizeof(string_literal_t); + string_literal_t function = *reinterpret_cast(b); + b += sizeof(string_literal_t); + uint32_t line = *reinterpret_cast(b); + b += sizeof(uint32_t); + LogLevel loglevel = *reinterpret_cast(b); + b += sizeof(LogLevel); + if (m_logAll) { + format_timestamp(os, timestamp); + + os << '[' << to_string(loglevel) << ']' << '[' << threadid << ']' << '[' + << file.m_s << ':' << function.m_s << ':' << line << "] "; + } + + stringify(os, b, end); + os << std::endl; + + if (loglevel >= LogLevel::CRIT) os.flush(); +} + +template +char* decode(std::ostream& os, char* b, Arg* /*dummy*/) +{ + Arg arg = *reinterpret_cast(b); + os << arg; + return b + sizeof(Arg); +} + +template <> +char* decode(std::ostream& os, char* b, VDebug::string_literal_t* /*dummy*/) +{ + VDebug::string_literal_t s = + *reinterpret_cast(b); + os << s.m_s; + return b + sizeof(VDebug::string_literal_t); +} + +template <> +char* decode(std::ostream& os, char* b, char** /*dummy*/) +{ + while (*b != '\0') { + os << *b; + ++b; + } + return ++b; +} + +void VDebug::stringify(std::ostream& os, char* start, char const* const end) +{ + if (start == end) return; + + int type_id = static_cast(*start); + start++; + + switch (type_id) { + case 0: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 1: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 2: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 3: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 4: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 5: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 6: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + case 7: + stringify( + os, + decode(os, start, + static_cast::type*>( + nullptr)), + end); + return; + } +} + +char* VDebug::buffer() +{ + return !m_heap_buffer ? &m_stack_buffer[m_bytes_used] + : &(m_heap_buffer.get())[m_bytes_used]; +} + +void VDebug::resize_buffer_if_needed(size_t additional_bytes) +{ + size_t const required_size = m_bytes_used + additional_bytes; + + if (required_size <= m_buffer_size) return; + + if (!m_heap_buffer) { + m_buffer_size = std::max(static_cast(512), required_size); + m_heap_buffer = std::make_unique(m_buffer_size); + memcpy(m_heap_buffer.get(), m_stack_buffer, m_bytes_used); + return; + } else { + m_buffer_size = + std::max(static_cast(2 * m_buffer_size), required_size); + std::unique_ptr new_heap_buffer(new char[m_buffer_size]); + memcpy(new_heap_buffer.get(), m_heap_buffer.get(), m_bytes_used); + m_heap_buffer.swap(new_heap_buffer); + } +} + +void VDebug::encode(char const* arg) +{ + if (arg != nullptr) encode_c_string(arg, strlen(arg)); +} + +void VDebug::encode(char* arg) +{ + if (arg != nullptr) encode_c_string(arg, strlen(arg)); +} + +void VDebug::encode_c_string(char const* arg, size_t length) +{ + if (length == 0) return; + + resize_buffer_if_needed(1 + length + 1); + char* b = buffer(); + auto type_id = TupleIndex::value; + *reinterpret_cast(b++) = static_cast(type_id); + memcpy(b, arg, length + 1); + m_bytes_used += 1 + length + 1; +} + +void VDebug::encode(string_literal_t arg) +{ + encode( + arg, TupleIndex::value); +} + +VDebug& VDebug::operator<<(std::string const& arg) +{ + encode_c_string(arg.c_str(), arg.length()); + return *this; +} + +VDebug& VDebug::operator<<(int32_t arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(uint32_t arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +// VDebug& VDebug::operator<<(int64_t arg) +// { +// encode < int64_t >(arg, TupleIndex < int64_t, SupportedTypes >::value); +// return *this; +// } + +// VDebug& VDebug::operator<<(uint64_t arg) +// { +// encode < uint64_t >(arg, TupleIndex < uint64_t, SupportedTypes >::value); +// return *this; +// } +VDebug& VDebug::operator<<(unsigned long arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(long arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(double arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +VDebug& VDebug::operator<<(char arg) +{ + encode(arg, TupleIndex::value); + return *this; +} + +struct BufferBase { + virtual ~BufferBase() = default; + virtual void push(VDebug&& logline) = 0; + virtual bool try_pop(VDebug& logline) = 0; +}; + +struct SpinLock { + SpinLock(std::atomic_flag& flag) : m_flag(flag) + { + while (m_flag.test_and_set(std::memory_order_acquire)) + ; + } + + ~SpinLock() { m_flag.clear(std::memory_order_release); } + +private: + std::atomic_flag& m_flag; +}; + +/* Multi Producer Single Consumer Ring Buffer */ +class RingBuffer : public BufferBase { +public: + struct alignas(64) Item { + Item() + : flag(), written(0), logline(LogLevel::INFO, nullptr, nullptr, 0) + { + } + + std::atomic_flag flag; + char written; + char padding[256 - sizeof(std::atomic_flag) - sizeof(char) - + sizeof(VDebug)]; + VDebug logline; + }; + + RingBuffer(size_t const size) + : m_size(size), + m_ring(static_cast(std::malloc(size * sizeof(Item)))), + m_write_index(0), + m_read_index(0) + { + for (size_t i = 0; i < m_size; ++i) { + new (&m_ring[i]) Item(); + } + static_assert(sizeof(Item) == 256, "Unexpected size != 256"); + } + + ~RingBuffer() override + { + for (size_t i = 0; i < m_size; ++i) { + m_ring[i].~Item(); + } + std::free(m_ring); + } + + void push(VDebug&& logline) override + { + unsigned int write_index = + m_write_index.fetch_add(1, std::memory_order_relaxed) % m_size; + Item& item = m_ring[write_index]; + SpinLock spinlock(item.flag); + item.logline = std::move(logline); + item.written = 1; + } + + bool try_pop(VDebug& logline) override + { + Item& item = m_ring[m_read_index % m_size]; + SpinLock spinlock(item.flag); + if (item.written == 1) { + logline = std::move(item.logline); + item.written = 0; + ++m_read_index; + return true; + } + return false; + } + + RingBuffer(RingBuffer const&) = delete; + RingBuffer& operator=(RingBuffer const&) = delete; + +private: + size_t const m_size; + Item* m_ring; + std::atomic m_write_index; + +public: + char pad[64]; + +private: + unsigned int m_read_index; +}; + +class Buffer { +public: + struct Item { + Item(VDebug&& logline) : logline(std::move(logline)) {} + char padding[256 - sizeof(VDebug)]; + VDebug logline; + }; + + static constexpr const size_t size = + 32768; // 8MB. Helps reduce memory fragmentation + + Buffer() : m_buffer(static_cast(std::malloc(size * sizeof(Item)))) + { + for (size_t i = 0; i <= size; ++i) { + m_write_state[i].store(0, std::memory_order_relaxed); + } + static_assert(sizeof(Item) == 256, "Unexpected size != 256"); + } + + ~Buffer() + { + unsigned int write_count = m_write_state[size].load(); + for (size_t i = 0; i < write_count; ++i) { + m_buffer[i].~Item(); + } + std::free(m_buffer); + } + + // Returns true if we need to switch to next buffer + bool push(VDebug&& logline, unsigned int const write_index) + { + new (&m_buffer[write_index]) Item(std::move(logline)); + m_write_state[write_index].store(1, std::memory_order_release); + return m_write_state[size].fetch_add(1, std::memory_order_acquire) + + 1 == + size; + } + + bool try_pop(VDebug& logline, unsigned int const read_index) + { + if (m_write_state[read_index].load(std::memory_order_acquire)) { + Item& item = m_buffer[read_index]; + logline = std::move(item.logline); + return true; + } + return false; + } + + Buffer(Buffer const&) = delete; + Buffer& operator=(Buffer const&) = delete; + +private: + Item* m_buffer; + std::atomic m_write_state[size + 1]; +}; + +class QueueBuffer : public BufferBase { +public: + QueueBuffer(QueueBuffer const&) = delete; + QueueBuffer& operator=(QueueBuffer const&) = delete; + + QueueBuffer() + : m_current_read_buffer{nullptr}, + m_write_index(0), + m_flag(), + m_read_index(0) + { + setup_next_write_buffer(); + } + + void push(VDebug&& logline) override + { + unsigned int write_index = + m_write_index.fetch_add(1, std::memory_order_relaxed); + if (write_index < Buffer::size) { + if (m_current_write_buffer.load(std::memory_order_acquire) + ->push(std::move(logline), write_index)) { + setup_next_write_buffer(); + } + } else { + while (m_write_index.load(std::memory_order_acquire) >= + Buffer::size) + ; + push(std::move(logline)); + } + } + + bool try_pop(VDebug& logline) override + { + if (m_current_read_buffer == nullptr) + m_current_read_buffer = get_next_read_buffer(); + + Buffer* read_buffer = m_current_read_buffer; + + if (read_buffer == nullptr) return false; + + if (read_buffer->try_pop(logline, m_read_index)) { + m_read_index++; + if (m_read_index == Buffer::size) { + m_read_index = 0; + m_current_read_buffer = nullptr; + SpinLock spinlock(m_flag); + m_buffers.pop(); + } + return true; + } + + return false; + } + +private: + void setup_next_write_buffer() + { + std::unique_ptr next_write_buffer(new Buffer()); + m_current_write_buffer.store(next_write_buffer.get(), + std::memory_order_release); + SpinLock spinlock(m_flag); + m_buffers.push(std::move(next_write_buffer)); + m_write_index.store(0, std::memory_order_relaxed); + } + + Buffer* get_next_read_buffer() + { + SpinLock spinlock(m_flag); + return m_buffers.empty() ? nullptr : m_buffers.front().get(); + } + +private: + std::queue > m_buffers; + std::atomic m_current_write_buffer; + Buffer* m_current_read_buffer; + std::atomic m_write_index; + std::atomic_flag m_flag; + unsigned int m_read_index; +}; + +class FileWriter { +public: + FileWriter(std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_log_file_roll_size_bytes(log_file_roll_size_mb * 1024 * 1024), + m_name(log_directory + log_file_name) + { + roll_file(); + } + + void write(VDebug& logline) + { + auto pos = m_os->tellp(); + logline.stringify(*m_os); + m_bytes_written += m_os->tellp() - pos; + if (m_bytes_written > m_log_file_roll_size_bytes) { + roll_file(); + } + } + +private: + void roll_file() + { + if (m_os) { + m_os->flush(); + m_os->close(); + } + + m_bytes_written = 0; + m_os = std::make_unique(); + // TODO Optimize this part. Does it even matter ? + std::string log_file_name = m_name; + log_file_name.append("."); + log_file_name.append(std::to_string(++m_file_number)); + log_file_name.append(".txt"); + m_os->open(log_file_name, std::ofstream::out | std::ofstream::trunc); + } + +private: + uint32_t m_file_number = 0; + std::streamoff m_bytes_written = 0; + uint32_t const m_log_file_roll_size_bytes; + std::string const m_name; + std::unique_ptr m_os; +}; + +class NanoLogger { +public: + NanoLogger(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_state(State::INIT), + m_buffer_base( + new RingBuffer(std::max(1u, ngl.ring_buffer_size_mb) * 1024 * 4)), + m_file_writer(log_directory, log_file_name, + std::max(1u, log_file_roll_size_mb)), + m_thread(&NanoLogger::pop, this) + { + m_state.store(State::READY, std::memory_order_release); + } + + NanoLogger(GuaranteedLogger /*gl*/, std::string const& log_directory, + std::string const& log_file_name, uint32_t log_file_roll_size_mb) + : m_state(State::INIT), + m_buffer_base(new QueueBuffer()), + m_file_writer(log_directory, log_file_name, + std::max(1u, log_file_roll_size_mb)), + m_thread(&NanoLogger::pop, this) + { + m_state.store(State::READY, std::memory_order_release); + } + + ~NanoLogger() + { + m_state.store(State::SHUTDOWN); + m_thread.join(); + } + + void add(VDebug&& logline) { m_buffer_base->push(std::move(logline)); } + + void pop() + { + // Wait for constructor to complete and pull all stores done there to + // this thread / core. + while (m_state.load(std::memory_order_acquire) == State::INIT) + std::this_thread::sleep_for(std::chrono::microseconds(50)); + + VDebug logline(LogLevel::INFO, nullptr, nullptr, 0); + + while (m_state.load() == State::READY) { + if (m_buffer_base->try_pop(logline)) + m_file_writer.write(logline); + else + std::this_thread::sleep_for(std::chrono::microseconds(50)); + } + + // Pop and log all remaining entries + while (m_buffer_base->try_pop(logline)) { + m_file_writer.write(logline); + } + } + +private: + enum class State { INIT, READY, SHUTDOWN }; + + std::atomic m_state; + std::unique_ptr m_buffer_base; + FileWriter m_file_writer; + std::thread m_thread; +}; + +std::unique_ptr nanologger; +std::atomic atomic_nanologger; + +bool VDebugServer::operator==(VDebug& logline) +{ + atomic_nanologger.load(std::memory_order_acquire)->add(std::move(logline)); + return true; +} + +void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb) +{ + nanologger = std::make_unique(ngl, log_directory, log_file_name, + log_file_roll_size_mb); + atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); +} + +void initialize(GuaranteedLogger gl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb) +{ + nanologger = std::make_unique(gl, log_directory, log_file_name, + log_file_roll_size_mb); + atomic_nanologger.store(nanologger.get(), std::memory_order_seq_cst); +} + +std::atomic loglevel = {0}; + +void set_log_level(LogLevel level) +{ + loglevel.store(static_cast(level), std::memory_order_release); +} + +bool is_logged(LogLevel level) +{ + return static_cast(level) >= + loglevel.load(std::memory_order_relaxed); +} + +#endif // LOTTIE_LOGGING_SUPPORT diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.h new file mode 100644 index 0000000000..5b6bef5b1a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdebug.h @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VDEBUG_H +#define VDEBUG_H + +#include "config.h" + +#ifdef LOTTIE_LOGGING_SUPPORT + +#include +#include +#include +#include +#include + +enum class LogLevel : uint8_t { INFO, WARN, CRIT, OFF }; + +class VDebug { +public: + VDebug(); + VDebug& debug() { return *this; } + VDebug(LogLevel level, char const* file, char const* function, + uint32_t line); + ~VDebug(); + + VDebug(VDebug&&) = default; + VDebug& operator=(VDebug&&) = default; + + void stringify(std::ostream& os); + + VDebug& operator<<(char arg); + VDebug& operator<<(int32_t arg); + VDebug& operator<<(uint32_t arg); + // VDebug& operator<<(int64_t arg); + // VDebug& operator<<(uint64_t arg); + + VDebug& operator<<(long arg); + VDebug& operator<<(unsigned long arg); + VDebug& operator<<(double arg); + VDebug& operator<<(std::string const& arg); + + template + VDebug& operator<<(const char (&arg)[N]) + { + encode(string_literal_t(arg)); + return *this; + } + + template + typename std::enable_if::value, + VDebug&>::type + operator<<(Arg const& arg) + { + encode(arg); + return *this; + } + + template + typename std::enable_if::value, VDebug&>::type + operator<<(Arg const& arg) + { + encode(arg); + return *this; + } + + struct string_literal_t { + explicit string_literal_t(char const* s) : m_s(s) {} + char const* m_s; + }; + +private: + char* buffer(); + + template + void encode(Arg arg); + + template + void encode(Arg arg, uint8_t type_id); + + void encode(char* arg); + void encode(char const* arg); + void encode(string_literal_t arg); + void encode_c_string(char const* arg, size_t length); + void resize_buffer_if_needed(size_t additional_bytes); + void stringify(std::ostream& os, char* start, char const* const end); + +private: + size_t m_bytes_used{0}; + size_t m_buffer_size{0}; + std::unique_ptr m_heap_buffer; + bool m_logAll; + char m_stack_buffer[256 - sizeof(bool) - 2 * sizeof(size_t) - + sizeof(decltype(m_heap_buffer)) - 8 /* Reserved */]; +}; + +struct VDebugServer { + /* + * Ideally this should have been operator+= + * Could not get that to compile, so here we are... + */ + bool operator==(VDebug&); +}; + +void set_log_level(LogLevel level); + +bool is_logged(LogLevel level); + +/* + * Non guaranteed logging. Uses a ring buffer to hold log lines. + * When the ring gets full, the previous log line in the slot will be dropped. + * Does not block producer even if the ring buffer is full. + * ring_buffer_size_mb - LogLines are pushed into a mpsc ring buffer whose size + * is determined by this parameter. Since each LogLine is 256 bytes, + * ring_buffer_size = ring_buffer_size_mb * 1024 * 1024 / 256 + */ +struct NonGuaranteedLogger { + NonGuaranteedLogger(uint32_t ring_buffer_size_mb_) + : ring_buffer_size_mb(ring_buffer_size_mb_) + { + } + uint32_t ring_buffer_size_mb; +}; + +/* + * Provides a guarantee log lines will not be dropped. + */ +struct GuaranteedLogger { +}; + +/* + * Ensure initialize() is called prior to any log statements. + * log_directory - where to create the logs. For example - "/tmp/" + * log_file_name - root of the file name. For example - "nanolog" + * This will create log files of the form - + * /tmp/nanolog.1.txt + * /tmp/nanolog.2.txt + * etc. + * log_file_roll_size_mb - mega bytes after which we roll to next log file. + */ +void initialize(GuaranteedLogger gl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb); +void initialize(NonGuaranteedLogger ngl, std::string const& log_directory, + std::string const& log_file_name, + uint32_t log_file_roll_size_mb); + +#define VDEBUG_LOG(LEVEL) \ + VDebugServer() == VDebug(LEVEL, __FILE__, __func__, __LINE__).debug() +#define vDebug is_logged(LogLevel::INFO) && VDEBUG_LOG(LogLevel::INFO) +#define vWarning is_logged(LogLevel::WARN) && VDEBUG_LOG(LogLevel::WARN) +#define vCritical is_logged(LogLevel::CRIT) && VDEBUG_LOG(LogLevel::CRIT) + +#else + +struct VDebug +{ + template + VDebug& operator<<(const Args &){return *this;} +}; + +#define vDebug VDebug() +#define vWarning VDebug() +#define vCritical VDebug() + +#endif //LOTTIE_LOGGING_SUPPORT + +#endif // VDEBUG_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.cpp new file mode 100644 index 0000000000..c8ff02bc71 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vdrawable.h" +#include "vdasher.h" +#include "vraster.h" + +VDrawable::VDrawable(VDrawable::Type type) +{ + setType(type); +} + +VDrawable::~VDrawable() noexcept +{ + if (mStrokeInfo) { + if (mType == Type::StrokeWithDash) { + delete static_cast(mStrokeInfo); + } else { + delete mStrokeInfo; + } + } +} + +void VDrawable::setType(VDrawable::Type type) +{ + mType = type; + if (mType == VDrawable::Type::Stroke) { + mStrokeInfo = new StrokeInfo(); + } else if (mType == VDrawable::Type::StrokeWithDash) { + mStrokeInfo = new StrokeWithDashInfo(); + } +} + +void VDrawable::applyDashOp() +{ + if (mStrokeInfo && (mType == Type::StrokeWithDash)) { + auto obj = static_cast(mStrokeInfo); + if (!obj->mDash.empty()) { + VDasher dasher(obj->mDash.data(), obj->mDash.size()); + mPath.clone(dasher.dashed(mPath)); + } + } +} + +void VDrawable::preprocess(const VRect &clip) +{ + if (mFlag & (DirtyState::Path)) { + if (mType == Type::Fill) { + mRasterizer.rasterize(std::move(mPath), mFillRule, clip); + } else { + applyDashOp(); + mRasterizer.rasterize(std::move(mPath), mStrokeInfo->cap, mStrokeInfo->join, + mStrokeInfo->width, mStrokeInfo->miterLimit, clip); + } + mPath = {}; + mFlag &= ~DirtyFlag(DirtyState::Path); + } +} + +VRle VDrawable::rle() +{ + return mRasterizer.rle(); +} + +void VDrawable::setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, + float strokeWidth) +{ + assert(mStrokeInfo); + if ((mStrokeInfo->cap == cap) && (mStrokeInfo->join == join) && + vCompare(mStrokeInfo->miterLimit, miterLimit) && + vCompare(mStrokeInfo->width, strokeWidth)) + return; + + mStrokeInfo->cap = cap; + mStrokeInfo->join = join; + mStrokeInfo->miterLimit = miterLimit; + mStrokeInfo->width = strokeWidth; + mFlag |= DirtyState::Path; +} + +void VDrawable::setDashInfo(std::vector &dashInfo) +{ + assert(mStrokeInfo); + assert(mType == VDrawable::Type::StrokeWithDash); + + auto obj = static_cast(mStrokeInfo); + bool hasChanged = false; + + if (obj->mDash.size() == dashInfo.size()) { + for (uint i = 0; i < dashInfo.size(); ++i) { + if (!vCompare(obj->mDash[i], dashInfo[i])) { + hasChanged = true; + break; + } + } + } else { + hasChanged = true; + } + + if (!hasChanged) return; + + obj->mDash = dashInfo; + + mFlag |= DirtyState::Path; +} + +void VDrawable::setPath(const VPath &path) +{ + mPath = path; + mFlag |= DirtyState::Path; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.h new file mode 100644 index 0000000000..357a69fdb2 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawable.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VDRAWABLE_H +#define VDRAWABLE_H +#include +#include +#include "vbrush.h" +#include "vpath.h" +#include "vrle.h" +#include "vraster.h" + +class VDrawable { +public: + enum class DirtyState : unsigned char { + None = 1<<0, + Path = 1<<1, + Stroke = 1<<2, + Brush = 1<<3, + All = (Path | Stroke | Brush) + }; + + enum class Type : unsigned char{ + Fill, + Stroke, + StrokeWithDash + }; + + explicit VDrawable(VDrawable::Type type = Type::Fill); + void setType(VDrawable::Type type); + ~VDrawable() noexcept; + + typedef vFlag DirtyFlag; + void setPath(const VPath &path); + void setFillRule(FillRule rule) { mFillRule = rule; } + void setBrush(const VBrush &brush) { mBrush = brush; } + void setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, + float strokeWidth); + void setDashInfo(std::vector &dashInfo); + void preprocess(const VRect &clip); + void applyDashOp(); + VRle rle(); + void setName(const char *name) + { + mName = name; + } + const char* name() const { return mName; } + +public: + struct StrokeInfo { + float width{0.0}; + float miterLimit{10}; + CapStyle cap{CapStyle::Flat}; + JoinStyle join{JoinStyle::Bevel}; + }; + + struct StrokeWithDashInfo : public StrokeInfo{ + std::vector mDash; + }; + +public: + VPath mPath; + VBrush mBrush; + VRasterizer mRasterizer; + StrokeInfo *mStrokeInfo{nullptr}; + + DirtyFlag mFlag{DirtyState::All}; + FillRule mFillRule{FillRule::Winding}; + VDrawable::Type mType{Type::Fill}; + + const char *mName{nullptr}; +}; + +#endif // VDRAWABLE_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.cpp new file mode 100644 index 0000000000..4594ae52cd --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.cpp @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vdrawhelper.h" +#include +#include +#include +#include +#include +#include + +static RenderFuncTable RenderTable; + +void VTextureData::setClip(const VRect &clip) +{ + left = clip.left(); + top = clip.top(); + right = std::min(clip.right(), int(width())) - 1; + bottom = std::min(clip.bottom(), int(height())) - 1; +} + +class VGradientCache { +public: + struct CacheInfo : public VColorTable { + inline CacheInfo(VGradientStops s) : stops(std::move(s)) {} + VGradientStops stops; + }; + using VCacheData = std::shared_ptr; + using VCacheKey = int64_t; + using VGradientColorTableHash = + std::unordered_multimap; + + bool generateGradientColorTable(const VGradientStops &stops, float alpha, + uint32_t *colorTable, int size); + VCacheData getBuffer(const VGradient &gradient) + { + VCacheKey hash_val = 0; + VCacheData info; + const VGradientStops &stops = gradient.mStops; + for (uint i = 0; i < stops.size() && i <= 2; i++) + hash_val += + VCacheKey(stops[i].second.premulARGB() * gradient.alpha()); + + { + std::lock_guard guard(mMutex); + + size_t count = mCache.count(hash_val); + if (!count) { + // key is not present in the hash + info = addCacheElement(hash_val, gradient); + } else if (count == 1) { + auto search = mCache.find(hash_val); + if (search->second->stops == stops) { + info = search->second; + } else { + // didn't find an exact match + info = addCacheElement(hash_val, gradient); + } + } else { + // we have a multiple data with same key + auto range = mCache.equal_range(hash_val); + for (auto it = range.first; it != range.second; ++it) { + if (it->second->stops == stops) { + info = it->second; + break; + } + } + if (!info) { + // didn't find an exact match + info = addCacheElement(hash_val, gradient); + } + } + } + return info; + } + + static VGradientCache &instance() + { + static VGradientCache CACHE; + return CACHE; + } + +protected: + uint maxCacheSize() const { return 60; } + VCacheData addCacheElement(VCacheKey hash_val, const VGradient &gradient) + { + if (mCache.size() == maxCacheSize()) { + uint count = maxCacheSize() / 10; + while (count--) { + mCache.erase(mCache.begin()); + } + } + auto cache_entry = std::make_shared(gradient.mStops); + cache_entry->alpha = generateGradientColorTable( + gradient.mStops, gradient.alpha(), cache_entry->buffer32, + VGradient::colorTableSize); + mCache.insert(std::make_pair(hash_val, cache_entry)); + return cache_entry; + } + +private: + VGradientCache() = default; + + VGradientColorTableHash mCache; + std::mutex mMutex; +}; + +bool VGradientCache::generateGradientColorTable(const VGradientStops &stops, + float opacity, + uint32_t *colorTable, int size) +{ + int dist, idist, pos = 0; + size_t i; + bool alpha = false; + size_t stopCount = stops.size(); + const VGradientStop *curr, *next, *start; + uint32_t curColor, nextColor; + float delta, t, incr, fpos; + + if (!vCompare(opacity, 1.0f)) alpha = true; + + start = stops.data(); + curr = start; + if (!curr->second.isOpaque()) alpha = true; + curColor = curr->second.premulARGB(opacity); + incr = 1.0f / (float)size; + fpos = 1.5f * incr; + + colorTable[pos++] = curColor; + + while (fpos <= curr->first) { + colorTable[pos] = colorTable[pos - 1]; + pos++; + fpos += incr; + } + + for (i = 0; i < stopCount - 1; ++i) { + curr = (start + i); + next = (start + i + 1); + delta = 1 / (next->first - curr->first); + if (!next->second.isOpaque()) alpha = true; + nextColor = next->second.premulARGB(opacity); + while (fpos < next->first && pos < size) { + t = (fpos - curr->first) * delta; + dist = (int)(255 * t); + idist = 255 - dist; + colorTable[pos] = + interpolate_pixel(curColor, idist, nextColor, dist); + ++pos; + fpos += incr; + } + curColor = nextColor; + } + + for (; pos < size; ++pos) colorTable[pos] = curColor; + + // Make sure the last color stop is represented at the end of the table + colorTable[size - 1] = curColor; + return alpha; +} + +void VRasterBuffer::clear() +{ + memset(mBuffer, 0, mHeight * mBytesPerLine); +} + +VBitmap::Format VRasterBuffer::prepare(const VBitmap *image) +{ + mBuffer = image->data(); + mWidth = image->width(); + mHeight = image->height(); + mBytesPerPixel = 4; + mBytesPerLine = image->stride(); + + mFormat = image->format(); + return mFormat; +} + +void VSpanData::init(VRasterBuffer *image) +{ + mRasterBuffer = image; + setDrawRegion(VRect(0, 0, int(image->width()), int(image->height()))); + mType = VSpanData::Type::None; + mBlendFunc = nullptr; + mUnclippedBlendFunc = nullptr; +} + +/* + * Gradient Draw routines + * + */ + +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1 << FIXPT_BITS) +static inline void getLinearGradientValues(LinearGradientValues *v, + const VSpanData * data) +{ + const VGradientData *grad = &data->mGradient; + v->dx = grad->linear.x2 - grad->linear.x1; + v->dy = grad->linear.y2 - grad->linear.y1; + v->l = v->dx * v->dx + v->dy * v->dy; + v->off = 0; + if (v->l != 0) { + v->dx /= v->l; + v->dy /= v->l; + v->off = -v->dx * grad->linear.x1 - v->dy * grad->linear.y1; + } +} + +static inline void getRadialGradientValues(RadialGradientValues *v, + const VSpanData * data) +{ + const VGradientData &gradient = data->mGradient; + v->dx = gradient.radial.cx - gradient.radial.fx; + v->dy = gradient.radial.cy - gradient.radial.fy; + + v->dr = gradient.radial.cradius - gradient.radial.fradius; + v->sqrfr = gradient.radial.fradius * gradient.radial.fradius; + + v->a = v->dr * v->dr - v->dx * v->dx - v->dy * v->dy; + v->inv2a = 1 / (2 * v->a); + + v->extended = !vIsZero(gradient.radial.fradius) || v->a <= 0; +} + +static inline int gradientClamp(const VGradientData *grad, int ipos) +{ + int limit; + + if (grad->mSpread == VGradient::Spread::Repeat) { + ipos = ipos % VGradient::colorTableSize; + ipos = ipos < 0 ? VGradient::colorTableSize + ipos : ipos; + } else if (grad->mSpread == VGradient::Spread::Reflect) { + limit = VGradient::colorTableSize * 2; + ipos = ipos % limit; + ipos = ipos < 0 ? limit + ipos : ipos; + ipos = ipos >= VGradient::colorTableSize ? limit - 1 - ipos : ipos; + } else { + if (ipos < 0) + ipos = 0; + else if (ipos >= VGradient::colorTableSize) + ipos = VGradient::colorTableSize - 1; + } + return ipos; +} + +static uint32_t gradientPixelFixed(const VGradientData *grad, int fixed_pos) +{ + int ipos = (fixed_pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + + return grad->mColorTable[gradientClamp(grad, ipos)]; +} + +static inline uint32_t gradientPixel(const VGradientData *grad, float pos) +{ + int ipos = (int)(pos * (VGradient::colorTableSize - 1) + (float)(0.5)); + + return grad->mColorTable[gradientClamp(grad, ipos)]; +} + +void fetch_linear_gradient(uint32_t *buffer, const Operator *op, + const VSpanData *data, int y, int x, int length) +{ + float t, inc; + const VGradientData *gradient = &data->mGradient; + + bool affine = true; + float rx = 0, ry = 0; + if (op->linear.l == 0) { + t = inc = 0; + } else { + rx = data->m21 * (y + float(0.5)) + data->m11 * (x + float(0.5)) + + data->dx; + ry = data->m22 * (y + float(0.5)) + data->m12 * (x + float(0.5)) + + data->dy; + t = op->linear.dx * rx + op->linear.dy * ry + op->linear.off; + inc = op->linear.dx * data->m11 + op->linear.dy * data->m12; + affine = !data->m13 && !data->m23; + + if (affine) { + t *= (VGradient::colorTableSize - 1); + inc *= (VGradient::colorTableSize - 1); + } + } + + const uint32_t *end = buffer + length; + if (affine) { + if (inc > float(-1e-5) && inc < float(1e-5)) { + memfill32(buffer, gradientPixelFixed(gradient, int(t * FIXPT_SIZE)), + length); + } else { + if (t + inc * length < float(INT_MAX >> (FIXPT_BITS + 1)) && + t + inc * length > float(INT_MIN >> (FIXPT_BITS + 1))) { + // we can use fixed point math + int t_fixed = int(t * FIXPT_SIZE); + int inc_fixed = int(inc * FIXPT_SIZE); + while (buffer < end) { + *buffer = gradientPixelFixed(gradient, t_fixed); + t_fixed += inc_fixed; + ++buffer; + } + } else { + // we have to fall back to float math + while (buffer < end) { + *buffer = + gradientPixel(gradient, t / VGradient::colorTableSize); + t += inc; + ++buffer; + } + } + } + } else { // fall back to float math here as well + float rw = data->m23 * (y + float(0.5)) + data->m13 * (x + float(0.5)) + + data->m33; + while (buffer < end) { + float xt = rx / rw; + float yt = ry / rw; + t = (op->linear.dx * xt + op->linear.dy * yt) + op->linear.off; + + *buffer = gradientPixel(gradient, t); + rx += data->m11; + ry += data->m12; + rw += data->m13; + if (!rw) { + rw += data->m13; + } + ++buffer; + } + } +} + +static inline float radialDeterminant(float a, float b, float c) +{ + return (b * b) - (4 * a * c); +} + +static void fetch(uint32_t *buffer, uint32_t *end, const Operator *op, + const VSpanData *data, float det, float delta_det, + float delta_delta_det, float b, float delta_b) +{ + if (op->radial.extended) { + while (buffer < end) { + uint32_t result = 0; + if (det >= 0) { + float w = std::sqrt(det) - b; + if (data->mGradient.radial.fradius + op->radial.dr * w >= 0) + result = gradientPixel(&data->mGradient, w); + } + + *buffer = result; + + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + + ++buffer; + } + } else { + while (buffer < end) { + *buffer++ = gradientPixel(&data->mGradient, std::sqrt(det) - b); + + det += delta_det; + delta_det += delta_delta_det; + b += delta_b; + } + } +} + +void fetch_radial_gradient(uint32_t *buffer, const Operator *op, + const VSpanData *data, int y, int x, int length) +{ + // avoid division by zero + if (vIsZero(op->radial.a)) { + memfill32(buffer, 0, length); + return; + } + + float rx = + data->m21 * (y + float(0.5)) + data->dx + data->m11 * (x + float(0.5)); + float ry = + data->m22 * (y + float(0.5)) + data->dy + data->m12 * (x + float(0.5)); + bool affine = !data->m13 && !data->m23; + + uint32_t *end = buffer + length; + if (affine) { + rx -= data->mGradient.radial.fx; + ry -= data->mGradient.radial.fy; + + float inv_a = 1 / float(2 * op->radial.a); + + const float delta_rx = data->m11; + const float delta_ry = data->m12; + + float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + + rx * op->radial.dx + ry * op->radial.dy); + float delta_b = + 2 * (delta_rx * op->radial.dx + delta_ry * op->radial.dy); + const float b_delta_b = 2 * b * delta_b; + const float delta_b_delta_b = 2 * delta_b * delta_b; + + const float bb = b * b; + const float delta_bb = delta_b * delta_b; + + b *= inv_a; + delta_b *= inv_a; + + const float rxrxryry = rx * rx + ry * ry; + const float delta_rxrxryry = delta_rx * delta_rx + delta_ry * delta_ry; + const float rx_plus_ry = 2 * (rx * delta_rx + ry * delta_ry); + const float delta_rx_plus_ry = 2 * delta_rxrxryry; + + inv_a *= inv_a; + + float det = + (bb - 4 * op->radial.a * (op->radial.sqrfr - rxrxryry)) * inv_a; + float delta_det = (b_delta_b + delta_bb + + 4 * op->radial.a * (rx_plus_ry + delta_rxrxryry)) * + inv_a; + const float delta_delta_det = + (delta_b_delta_b + 4 * op->radial.a * delta_rx_plus_ry) * inv_a; + + fetch(buffer, end, op, data, det, delta_det, delta_delta_det, b, + delta_b); + } else { + float rw = data->m23 * (y + float(0.5)) + data->m33 + + data->m13 * (x + float(0.5)); + + while (buffer < end) { + if (rw == 0) { + *buffer = 0; + } else { + float invRw = 1 / rw; + float gx = rx * invRw - data->mGradient.radial.fx; + float gy = ry * invRw - data->mGradient.radial.fy; + float b = 2 * (op->radial.dr * data->mGradient.radial.fradius + + gx * op->radial.dx + gy * op->radial.dy); + float det = radialDeterminant( + op->radial.a, b, op->radial.sqrfr - (gx * gx + gy * gy)); + + uint32_t result = 0; + if (det >= 0) { + float detSqrt = std::sqrt(det); + + float s0 = (-b - detSqrt) * op->radial.inv2a; + float s1 = (-b + detSqrt) * op->radial.inv2a; + + float s = vMax(s0, s1); + + if (data->mGradient.radial.fradius + op->radial.dr * s >= 0) + result = gradientPixel(&data->mGradient, s); + } + + *buffer = result; + } + + rx += data->m11; + ry += data->m12; + rw += data->m13; + + ++buffer; + } + } +} + +static inline Operator getOperator(const VSpanData *data) +{ + Operator op; + bool solidSource = false; + + switch (data->mType) { + case VSpanData::Type::Solid: + solidSource = (vAlpha(data->mSolid) == 255); + op.srcFetch = nullptr; + break; + case VSpanData::Type::LinearGradient: + solidSource = false; + getLinearGradientValues(&op.linear, data); + op.srcFetch = &fetch_linear_gradient; + break; + case VSpanData::Type::RadialGradient: + solidSource = false; + getRadialGradientValues(&op.radial, data); + op.srcFetch = &fetch_radial_gradient; + break; + default: + op.srcFetch = nullptr; + break; + } + + op.mode = data->mBlendMode; + if (op.mode == BlendMode::SrcOver && solidSource) op.mode = BlendMode::Src; + + op.funcSolid = RenderTable.color(op.mode); + op.func = RenderTable.src(op.mode); + + return op; +} + +static void blend_color(size_t size, const VRle::Span *array, void *userData) +{ + VSpanData *data = (VSpanData *)(userData); + Operator op = getOperator(data); + const uint color = data->mSolid; + + for (size_t i = 0 ; i < size; ++i) { + const auto &span = array[i]; + op.funcSolid(data->buffer(span.x, span.y), span.len, color, span.coverage); + } +} + +// Signature of Process Object +// void Pocess(uint* scratchBuffer, size_t x, size_t y, uchar cov) +template +static inline void process_in_chunk(const VRle::Span *array, size_t size, + Process process) +{ + std::array buf; + for (size_t i = 0; i < size; i++) { + const auto &span = array[i]; + size_t len = span.len; + size_t x = span.x; + while (len) { + auto l = std::min(len, buf.size()); + process(buf.data(), x, span.y, l, span.coverage); + x += l; + len -= l; + } + } +} + +static void blend_gradient(size_t size, const VRle::Span *array, + void *userData) +{ + VSpanData *data = (VSpanData *)(userData); + Operator op = getOperator(data); + + if (!op.srcFetch) return; + + process_in_chunk( + array, size, + [&](uint *scratch, size_t x, size_t y, size_t len, uchar cov) { + op.srcFetch(scratch, &op, data, (int)y, (int)x, (int)len); + op.func(data->buffer((int)x, (int)y), (int)len, scratch, cov); + }); +} + +template +constexpr const T &clamp(const T &v, const T &lo, const T &hi) +{ + return v < lo ? lo : hi < v ? hi : v; +} + +static constexpr inline uchar alpha_mul(uchar a, uchar b) +{ + return ((a * b) >> 8); +} + +static void blend_image_xform(size_t size, const VRle::Span *array, + void *userData) +{ + const auto data = reinterpret_cast(userData); + const auto &src = data->texture(); + + if (src.format() != VBitmap::Format::ARGB32_Premultiplied && + src.format() != VBitmap::Format::ARGB32) { + //@TODO other formats not yet handled. + return; + } + + Operator op = getOperator(data); + + process_in_chunk( + array, size, + [&](uint *scratch, size_t x, size_t y, size_t len, uchar cov) { + const auto coverage = (cov * src.alpha()) >> 8; + const float xfactor = y * data->m21 + data->dx + data->m11; + const float yfactor = y * data->m22 + data->dy + data->m12; + for (size_t i = 0; i < len; i++) { + const float fx = (x + i) * data->m11 + xfactor; + const float fy = (x + i) * data->m12 + yfactor; + const int px = clamp(int(fx), src.left, src.right); + const int py = clamp(int(fy), src.top, src.bottom); + scratch[i] = src.pixel(px, py); + } + op.func(data->buffer((int)x, (int)y), (int)len, scratch, coverage); + }); +} + +static void blend_image(size_t size, const VRle::Span *array, void *userData) +{ + const auto data = reinterpret_cast(userData); + const auto &src = data->texture(); + + if (src.format() != VBitmap::Format::ARGB32_Premultiplied && + src.format() != VBitmap::Format::ARGB32) { + //@TODO other formats not yet handled. + return; + } + + Operator op = getOperator(data); + + for (size_t i = 0; i < size; i++) { + const auto &span = array[i]; + int x = span.x; + int length = span.len; + int sx = x + int(data->dx); + int sy = span.y + int(data->dy); + + // notyhing to copy. + if (sy < 0 || sy >= int(src.height()) || sx >= int(src.width()) || + (sx + length) <= 0) + continue; + + // intersecting left edge of image + if (sx < 0) { + x -= sx; + length += sx; + sx = 0; + } + // intersecting right edge of image + if (sx + length > int(src.width())) length = (int)src.width() - sx; + + op.func(data->buffer(x, span.y), length, src.pixelRef(sx, sy), + alpha_mul(span.coverage, src.alpha())); + } +} + +void VSpanData::setup(const VBrush &brush, BlendMode /*mode*/, int /*alpha*/) +{ + transformType = VMatrix::MatrixType::None; + + switch (brush.type()) { + case VBrush::Type::NoBrush: + mType = VSpanData::Type::None; + break; + case VBrush::Type::Solid: + mType = VSpanData::Type::Solid; + mSolid = brush.mColor.premulARGB(); + break; + case VBrush::Type::LinearGradient: { + mType = VSpanData::Type::LinearGradient; + mColorTable = VGradientCache::instance().getBuffer(*brush.mGradient); + mGradient.mColorTable = mColorTable->buffer32; + mGradient.mColorTableAlpha = mColorTable->alpha; + mGradient.linear.x1 = brush.mGradient->linear.x1; + mGradient.linear.y1 = brush.mGradient->linear.y1; + mGradient.linear.x2 = brush.mGradient->linear.x2; + mGradient.linear.y2 = brush.mGradient->linear.y2; + mGradient.mSpread = brush.mGradient->mSpread; + setupMatrix(brush.mGradient->mMatrix); + break; + } + case VBrush::Type::RadialGradient: { + mType = VSpanData::Type::RadialGradient; + mColorTable = VGradientCache::instance().getBuffer(*brush.mGradient); + mGradient.mColorTable = mColorTable->buffer32; + mGradient.mColorTableAlpha = mColorTable->alpha; + mGradient.radial.cx = brush.mGradient->radial.cx; + mGradient.radial.cy = brush.mGradient->radial.cy; + mGradient.radial.fx = brush.mGradient->radial.fx; + mGradient.radial.fy = brush.mGradient->radial.fy; + mGradient.radial.cradius = brush.mGradient->radial.cradius; + mGradient.radial.fradius = brush.mGradient->radial.fradius; + mGradient.mSpread = brush.mGradient->mSpread; + setupMatrix(brush.mGradient->mMatrix); + break; + } + case VBrush::Type::Texture: { + mType = VSpanData::Type::Texture; + initTexture(&brush.mTexture->mBitmap, brush.mTexture->mAlpha, + brush.mTexture->mBitmap.rect()); + setupMatrix(brush.mTexture->mMatrix); + break; + } + default: + break; + } + updateSpanFunc(); +} + +void VSpanData::setupMatrix(const VMatrix &matrix) +{ + VMatrix inv = matrix.inverted(); + m11 = inv.m11; + m12 = inv.m12; + m13 = inv.m13; + m21 = inv.m21; + m22 = inv.m22; + m23 = inv.m23; + m33 = inv.m33; + dx = inv.mtx; + dy = inv.mty; + transformType = inv.type(); + + const bool affine = inv.isAffine(); + const float f1 = m11 * m11 + m21 * m21; + const float f2 = m12 * m12 + m22 * m22; + fast_matrix = affine && f1 < 1e4 && f2 < 1e4 && f1 > (1.0 / 65536) && + f2 > (1.0 / 65536) && fabs(dx) < 1e4 && fabs(dy) < 1e4; +} + +void VSpanData::initTexture(const VBitmap *bitmap, int alpha, + const VRect &sourceRect) +{ + mType = VSpanData::Type::Texture; + mTexture.prepare(bitmap); + mTexture.setClip(sourceRect); + mTexture.setAlpha(alpha); + updateSpanFunc(); +} + +void VSpanData::updateSpanFunc() +{ + switch (mType) { + case VSpanData::Type::None: + mUnclippedBlendFunc = nullptr; + break; + case VSpanData::Type::Solid: + mUnclippedBlendFunc = &blend_color; + break; + case VSpanData::Type::LinearGradient: + case VSpanData::Type::RadialGradient: { + mUnclippedBlendFunc = &blend_gradient; + break; + } + case VSpanData::Type::Texture: { + //@TODO update proper image function. + if (transformType <= VMatrix::MatrixType::Translate) { + mUnclippedBlendFunc = &blend_image; + } else { + mUnclippedBlendFunc = &blend_image_xform; + } + break; + } + } +} + +#if !defined(__SSE2__) && !defined(__ARM_NEON__) +void memfill32(uint32_t *dest, uint32_t value, int length) +{ + // let compiler do the auto vectorization. + for (int i = 0 ; i < length; i++) { + *dest++ = value; + } +} +#endif + diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.h new file mode 100644 index 0000000000..e654e93bca --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VDRAWHELPER_H +#define VDRAWHELPER_H + +#include +#include +#include "assert.h" +#include "vbitmap.h" +#include "vbrush.h" +#include "vrect.h" +#include "vrle.h" + +V_USE_NAMESPACE + +struct VSpanData; +struct Operator; + +struct RenderFunc +{ + using Color = void (*)(uint32_t *dest, int length, uint32_t color, uint32_t alpha); + using Src = void (*)(uint32_t *dest, int length, const uint32_t *src, uint32_t alpha); + enum class Type { + Invalid, + Color, + Src, + }; + RenderFunc() = default; + RenderFunc(Type t, Color f):type_(t), color_(f){assert(t == Type::Color);} + RenderFunc(Type t, Src f):type_(t), src_(f){ assert(t == Type::Src);} + + Type type_{Type::Invalid}; + union { + Color color_; + Src src_; + }; +}; + +class RenderFuncTable +{ +public: + RenderFuncTable(); + RenderFunc::Color color(BlendMode mode) const + { + return colorTable[uint32_t(mode)].color_; + } + RenderFunc::Src src(BlendMode mode) const + { + return srcTable[uint32_t(mode)].src_; + } +private: + void neon(); + void sse(); + void updateColor(BlendMode mode, RenderFunc::Color f) + { + colorTable[uint32_t(mode)] = {RenderFunc::Type::Color, f}; + } + void updateSrc(BlendMode mode, RenderFunc::Src f) + { + srcTable[uint32_t(mode)] = {RenderFunc::Type::Src, f}; + } +private: + std::array colorTable; + std::array srcTable; +}; + +typedef void (*SourceFetchProc)(uint32_t *buffer, const Operator *o, + const VSpanData *data, int y, int x, + int length); +typedef void (*ProcessRleSpan)(size_t count, const VRle::Span *spans, + void *userData); + +extern void memfill32(uint32_t *dest, uint32_t value, int count); + +struct LinearGradientValues { + float dx; + float dy; + float l; + float off; +}; + +struct RadialGradientValues { + float dx; + float dy; + float dr; + float sqrfr; + float a; + float inv2a; + bool extended; +}; + +struct Operator { + BlendMode mode; + SourceFetchProc srcFetch; + RenderFunc::Color funcSolid; + RenderFunc::Src func; + union { + LinearGradientValues linear; + RadialGradientValues radial; + }; +}; + +class VRasterBuffer { +public: + VBitmap::Format prepare(const VBitmap *image); + void clear(); + + void resetBuffer(int val = 0); + + inline uchar *scanLine(int y) + { + assert(y >= 0); + assert(size_t(y) < mHeight); + return mBuffer + y * mBytesPerLine; + } + uint32_t *pixelRef(int x, int y) const + { + return (uint32_t *)(mBuffer + y * mBytesPerLine + x * mBytesPerPixel); + } + + size_t width() const { return mWidth; } + size_t height() const { return mHeight; } + size_t bytesPerLine() const { return mBytesPerLine; } + size_t bytesPerPixel() const { return mBytesPerPixel; } + VBitmap::Format format() const { return mFormat; } + +private: + VBitmap::Format mFormat{VBitmap::Format::ARGB32_Premultiplied}; + size_t mWidth{0}; + size_t mHeight{0}; + size_t mBytesPerLine{0}; + size_t mBytesPerPixel{0}; + mutable uchar * mBuffer{nullptr}; +}; + +struct VGradientData { + VGradient::Spread mSpread; + struct Linear { + float x1, y1, x2, y2; + }; + struct Radial { + float cx, cy, fx, fy, cradius, fradius; + }; + union { + Linear linear; + Radial radial; + }; + const uint32_t *mColorTable; + bool mColorTableAlpha; +}; + +struct VTextureData : public VRasterBuffer { + uint32_t pixel(int x, int y) const { return *pixelRef(x, y); }; + uchar alpha() const { return mAlpha; } + void setAlpha(uchar alpha) { mAlpha = alpha; } + void setClip(const VRect &clip); + // clip rect + int left; + int right; + int top; + int bottom; + bool hasAlpha; + uchar mAlpha; +}; + +struct VColorTable { + uint32_t buffer32[VGradient::colorTableSize]; + bool alpha{true}; +}; + +struct VSpanData { + enum class Type { None, Solid, LinearGradient, RadialGradient, Texture }; + + void updateSpanFunc(); + void init(VRasterBuffer *image); + void setup(const VBrush &brush, BlendMode mode = BlendMode::SrcOver, + int alpha = 255); + void setupMatrix(const VMatrix &matrix); + + VRect clipRect() const + { + return VRect(0, 0, mDrawableSize.width(), mDrawableSize.height()); + } + + void setDrawRegion(const VRect ®ion) + { + mOffset = VPoint(region.left(), region.top()); + mDrawableSize = VSize(region.width(), region.height()); + } + + uint *buffer(int x, int y) const + { + return mRasterBuffer->pixelRef(x + mOffset.x(), y + mOffset.y()); + } + void initTexture(const VBitmap *image, int alpha, const VRect &sourceRect); + const VTextureData &texture() const { return mTexture; } + + BlendMode mBlendMode{BlendMode::SrcOver}; + VRasterBuffer * mRasterBuffer; + ProcessRleSpan mBlendFunc; + ProcessRleSpan mUnclippedBlendFunc; + VSpanData::Type mType; + std::shared_ptr mColorTable{nullptr}; + VPoint mOffset; // offset to the subsurface + VSize mDrawableSize; // suburface size + uint32_t mSolid; + VGradientData mGradient; + VTextureData mTexture; + + float m11, m12, m13, m21, m22, m23, m33, dx, dy; // inverse xform matrix + bool fast_matrix{true}; + VMatrix::MatrixType transformType{VMatrix::MatrixType::None}; +}; + +#define BYTE_MUL(c, a) \ + ((((((c) >> 8) & 0x00ff00ff) * (a)) & 0xff00ff00) + \ + (((((c)&0x00ff00ff) * (a)) >> 8) & 0x00ff00ff)) + +inline constexpr int vRed(uint32_t c) +{ + return ((c >> 16) & 0xff); +} + +inline constexpr int vGreen(uint32_t c) +{ + return ((c >> 8) & 0xff); +} + +inline constexpr int vBlue(uint32_t c) +{ + return (c & 0xff); +} + +inline constexpr int vAlpha(uint32_t c) +{ + return c >> 24; +} + +static inline uint32_t interpolate_pixel(uint x, uint a, uint y, uint b) +{ + uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b; + t >>= 8; + t &= 0xff00ff; + x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b; + x &= 0xff00ff00; + x |= t; + return x; +} + +#endif // QDRAWHELPER_P_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_common.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_common.cpp new file mode 100644 index 0000000000..b2eb6db011 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_common.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "vdrawhelper.h" + +/* +result = s +dest = s * ca + d * cia +*/ +static void color_Source(uint32_t *dest, int length, uint32_t color, + uint32_t alpha) +{ + int ialpha, i; + + if (alpha == 255) { + memfill32(dest, color, length); + } else { + ialpha = 255 - alpha; + color = BYTE_MUL(color, alpha); + for (i = 0; i < length; ++i) + dest[i] = color + BYTE_MUL(dest[i], ialpha); + } +} + +/* + r = s + d * sia + dest = r * ca + d * cia + = (s + d * sia) * ca + d * cia + = s * ca + d * (sia * ca + cia) + = s * ca + d * (1 - sa*ca) + = s' + d ( 1 - s'a) +*/ +static void color_SourceOver(uint32_t *dest, int length, uint32_t color, + uint32_t alpha) +{ + int ialpha, i; + + if (alpha != 255) color = BYTE_MUL(color, alpha); + ialpha = 255 - vAlpha(color); + for (i = 0; i < length; ++i) dest[i] = color + BYTE_MUL(dest[i], ialpha); +} + +/* + result = d * sa + dest = d * sa * ca + d * cia + = d * (sa * ca + cia) +*/ +static void color_DestinationIn(uint *dest, int length, uint color, + uint alpha) +{ + uint a = vAlpha(color); + if (alpha != 255) { + a = BYTE_MUL(a, alpha) + 255 - alpha; + } + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +/* + result = d * sia + dest = d * sia * ca + d * cia + = d * (sia * ca + cia) +*/ +static void color_DestinationOut(uint *dest, int length, uint color, + uint alpha) +{ + uint a = vAlpha(~color); + if (alpha != 255) a = BYTE_MUL(a, alpha) + 255 - alpha; + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], a); + } +} + +static void src_Source(uint32_t *dest, int length, const uint32_t *src, + uint32_t alpha) +{ + if (alpha == 255) { + memcpy(dest, src, size_t(length) * sizeof(uint)); + } else { + uint ialpha = 255 - alpha; + for (int i = 0; i < length; ++i) { + dest[i] = + interpolate_pixel(src[i], alpha, dest[i], ialpha); + } + } +} + +/* s' = s * ca + * d' = s' + d (1 - s'a) + */ +static void src_SourceOver(uint32_t *dest, int length, const uint32_t *src, + uint32_t alpha) +{ + uint s, sia; + + if (alpha == 255) { + for (int i = 0; i < length; ++i) { + s = src[i]; + if (s >= 0xff000000) + dest[i] = s; + else if (s != 0) { + sia = vAlpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } + } else { + /* source' = source * const_alpha + * dest = source' + dest ( 1- source'a) + */ + for (int i = 0; i < length; ++i) { + s = BYTE_MUL(src[i], alpha); + sia = vAlpha(~s); + dest[i] = s + BYTE_MUL(dest[i], sia); + } + } +} + +static void src_DestinationIn(uint *dest, int length, const uint *src, + uint alpha) +{ + if (alpha == 255) { + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], vAlpha(src[i])); + } + } else { + uint cia = 255 - alpha; + for (int i = 0; i < length; ++i) { + uint a = BYTE_MUL(vAlpha(src[i]), alpha) + cia; + dest[i] = BYTE_MUL(dest[i], a); + } + } +} + +static void src_DestinationOut(uint *dest, int length, const uint *src, + uint alpha) +{ + if (alpha == 255) { + for (int i = 0; i < length; ++i) { + dest[i] = BYTE_MUL(dest[i], vAlpha(~src[i])); + } + } else { + uint cia = 255 - alpha; + for (int i = 0; i < length; ++i) { + uint sia = BYTE_MUL(vAlpha(~src[i]), alpha) + cia; + dest[i] = BYTE_MUL(dest[i], sia); + } + } +} + +RenderFuncTable::RenderFuncTable() +{ + updateColor(BlendMode::Src, color_Source); + updateColor(BlendMode::SrcOver, color_SourceOver); + updateColor(BlendMode::DestIn, color_DestinationIn); + updateColor(BlendMode::DestOut, color_DestinationOut); + + updateSrc(BlendMode::Src, src_Source); + updateSrc(BlendMode::SrcOver, src_SourceOver); + updateSrc(BlendMode::DestIn, src_DestinationIn); + updateSrc(BlendMode::DestOut, src_DestinationOut); + +#if defined(__ARM_NEON__) + neon(); +#endif +#if defined(__SSE2__) + sse(); +#endif +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_neon.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_neon.cpp new file mode 100644 index 0000000000..681eabbc7f --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_neon.cpp @@ -0,0 +1,33 @@ +#if defined(__ARM_NEON__) + +#include "vdrawhelper.h" + +extern "C" void pixman_composite_src_n_8888_asm_neon(int32_t w, int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint32_t src); + +extern "C" void pixman_composite_over_n_8888_asm_neon(int32_t w, int32_t h, + uint32_t *dst, + int32_t dst_stride, + uint32_t src); + +void memfill32(uint32_t *dest, uint32_t value, int length) +{ + pixman_composite_src_n_8888_asm_neon(length, 1, dest, length, value); +} + +static void color_SourceOver(uint32_t *dest, int length, + uint32_t color, + uint32_t const_alpha) +{ + if (const_alpha != 255) color = BYTE_MUL(color, const_alpha); + + pixman_composite_over_n_8888_asm_neon(length, 1, dest, length, color); +} + +void RenderFuncTable::neon() +{ + updateColor(BlendMode::Src , color_SourceOver); +} +#endif diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_sse2.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_sse2.cpp new file mode 100644 index 0000000000..fd9b711e5c --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vdrawhelper_sse2.cpp @@ -0,0 +1,261 @@ +#if defined(__SSE2__) + +#include +#include /* for SSE2 intrinsics */ +#include /* for _mm_shuffle_pi16 and _MM_SHUFFLE */ + +#include "vdrawhelper.h" +// Each 32bits components of alphaChannel must be in the form 0x00AA00AA +inline static __m128i v4_byte_mul_sse2(__m128i c, __m128i a) +{ + const __m128i ag_mask = _mm_set1_epi32(0xFF00FF00); + const __m128i rb_mask = _mm_set1_epi32(0x00FF00FF); + + /* for AG */ + __m128i v_ag = _mm_and_si128(ag_mask, c); + v_ag = _mm_srli_epi32(v_ag, 8); + v_ag = _mm_mullo_epi16(a, v_ag); + v_ag = _mm_and_si128(ag_mask, v_ag); + + /* for RB */ + __m128i v_rb = _mm_and_si128(rb_mask, c); + v_rb = _mm_mullo_epi16(a, v_rb); + v_rb = _mm_srli_epi32(v_rb, 8); + v_rb = _mm_and_si128(rb_mask, v_rb); + + /* combine */ + return _mm_add_epi32(v_ag, v_rb); +} + +static inline __m128i v4_interpolate_color_sse2(__m128i a, __m128i c0, + __m128i c1) +{ + const __m128i rb_mask = _mm_set1_epi32(0xFF00FF00); + const __m128i zero = _mm_setzero_si128(); + + __m128i a_l = a; + __m128i a_h = a; + a_l = _mm_unpacklo_epi16(a_l, a_l); + a_h = _mm_unpackhi_epi16(a_h, a_h); + + __m128i a_t = _mm_slli_epi64(a_l, 32); + __m128i a_t0 = _mm_slli_epi64(a_h, 32); + + a_l = _mm_add_epi32(a_l, a_t); + a_h = _mm_add_epi32(a_h, a_t0); + + __m128i c0_l = c0; + __m128i c0_h = c0; + + c0_l = _mm_unpacklo_epi8(c0_l, zero); + c0_h = _mm_unpackhi_epi8(c0_h, zero); + + __m128i c1_l = c1; + __m128i c1_h = c1; + + c1_l = _mm_unpacklo_epi8(c1_l, zero); + c1_h = _mm_unpackhi_epi8(c1_h, zero); + + __m128i cl_sub = _mm_sub_epi16(c0_l, c1_l); + __m128i ch_sub = _mm_sub_epi16(c0_h, c1_h); + + cl_sub = _mm_mullo_epi16(cl_sub, a_l); + ch_sub = _mm_mullo_epi16(ch_sub, a_h); + + __m128i c1ls = _mm_slli_epi16(c1_l, 8); + __m128i c1hs = _mm_slli_epi16(c1_h, 8); + + cl_sub = _mm_add_epi16(cl_sub, c1ls); + ch_sub = _mm_add_epi16(ch_sub, c1hs); + + cl_sub = _mm_and_si128(cl_sub, rb_mask); + ch_sub = _mm_and_si128(ch_sub, rb_mask); + + cl_sub = _mm_srli_epi64(cl_sub, 8); + ch_sub = _mm_srli_epi64(ch_sub, 8); + + cl_sub = _mm_packus_epi16(cl_sub, cl_sub); + ch_sub = _mm_packus_epi16(ch_sub, ch_sub); + + return (__m128i)_mm_shuffle_ps((__m128)cl_sub, (__m128)ch_sub, 0x44); +} + +// Load src and dest vector +#define V4_FETCH_SRC_DEST \ + __m128i v_src = _mm_loadu_si128((__m128i*)src); \ + __m128i v_dest = _mm_load_si128((__m128i*)dest); + +#define V4_FETCH_SRC __m128i v_src = _mm_loadu_si128((__m128i*)src); + +#define V4_STORE_DEST _mm_store_si128((__m128i*)dest, v_src); + +#define V4_SRC_DEST_LEN_INC \ + dest += 4; \ + src += 4; \ + length -= 4; + +// Multiply src color with const_alpha +#define V4_ALPHA_MULTIPLY v_src = v4_byte_mul_sse2(v_src, v_alpha); + + +// dest = src + dest * sia +#define V4_COMP_OP_SRC \ + v_src = v4_interpolate_color_sse2(v_alpha, v_src, v_dest); + +#define LOOP_ALIGNED_U1_A4(DEST, LENGTH, UOP, A4OP) \ + { \ + while ((uintptr_t)DEST & 0xF && LENGTH) \ + UOP \ + \ + while (LENGTH) \ + { \ + switch (LENGTH) { \ + case 3: \ + case 2: \ + case 1: \ + UOP break; \ + default: \ + A4OP break; \ + } \ + } \ + } + +void memfill32(uint32_t* dest, uint32_t value, int length) +{ + __m128i vector_data = _mm_set_epi32(value, value, value, value); + + // run till memory alligned to 16byte memory + while (length && ((uintptr_t)dest & 0xf)) { + *dest++ = value; + length--; + } + + while (length >= 32) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + _mm_store_si128((__m128i*)(dest + 8), vector_data); + _mm_store_si128((__m128i*)(dest + 12), vector_data); + _mm_store_si128((__m128i*)(dest + 16), vector_data); + _mm_store_si128((__m128i*)(dest + 20), vector_data); + _mm_store_si128((__m128i*)(dest + 24), vector_data); + _mm_store_si128((__m128i*)(dest + 28), vector_data); + + dest += 32; + length -= 32; + } + + if (length >= 16) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + _mm_store_si128((__m128i*)(dest + 8), vector_data); + _mm_store_si128((__m128i*)(dest + 12), vector_data); + + dest += 16; + length -= 16; + } + + if (length >= 8) { + _mm_store_si128((__m128i*)(dest), vector_data); + _mm_store_si128((__m128i*)(dest + 4), vector_data); + + dest += 8; + length -= 8; + } + + if (length >= 4) { + _mm_store_si128((__m128i*)(dest), vector_data); + + dest += 4; + length -= 4; + } + + while (length) { + *dest++ = value; + length--; + } +} + +// dest = color + (dest * alpha) +inline static void copy_helper_sse2(uint32_t* dest, int length, + uint32_t color, uint32_t alpha) +{ + const __m128i v_color = _mm_set1_epi32(color); + const __m128i v_a = _mm_set1_epi16(alpha); + + LOOP_ALIGNED_U1_A4(dest, length, + { /* UOP */ + *dest = color + BYTE_MUL(*dest, alpha); + dest++; + length--; + }, + { /* A4OP */ + __m128i v_dest = _mm_load_si128((__m128i*)dest); + + v_dest = v4_byte_mul_sse2(v_dest, v_a); + v_dest = _mm_add_epi32(v_dest, v_color); + + _mm_store_si128((__m128i*)dest, v_dest); + + dest += 4; + length -= 4; + }) +} + +static void color_Source(uint32_t* dest, int length, uint32_t color, + uint32_t const_alpha) +{ + if (const_alpha == 255) { + memfill32(dest, color, length); + } else { + int ialpha; + + ialpha = 255 - const_alpha; + color = BYTE_MUL(color, const_alpha); + copy_helper_sse2(dest, length, color, ialpha); + } +} + +static void color_SourceOver(uint32_t* dest, int length, + uint32_t color, + uint32_t const_alpha) +{ + int ialpha; + + if (const_alpha != 255) color = BYTE_MUL(color, const_alpha); + ialpha = 255 - vAlpha(color); + copy_helper_sse2(dest, length, color, ialpha); +} + +static void src_Source(uint32_t* dest, int length, const uint32_t* src, + uint32_t const_alpha) +{ + int ialpha; + if (const_alpha == 255) { + memcpy(dest, src, length * sizeof(uint32_t)); + } else { + ialpha = 255 - const_alpha; + __m128i v_alpha = _mm_set1_epi32(const_alpha); + + LOOP_ALIGNED_U1_A4(dest, length, + { /* UOP */ + *dest = interpolate_pixel(*src, const_alpha, + *dest, ialpha); + dest++; + src++; + length--; + }, + {/* A4OP */ + V4_FETCH_SRC_DEST V4_COMP_OP_SRC V4_STORE_DEST + V4_SRC_DEST_LEN_INC}) + } +} + +void RenderFuncTable::sse() +{ + updateColor(BlendMode::Src , color_Source); + updateColor(BlendMode::SrcOver , color_SourceOver); + + updateSrc(BlendMode::Src , src_Source); +} + +#endif diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.cpp new file mode 100644 index 0000000000..42aa452953 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "velapsedtimer.h" + +void VElapsedTimer::start() +{ + clock = std::chrono::high_resolution_clock::now(); + m_valid = true; +} + +double VElapsedTimer::restart() +{ + double elapsedTime = elapsed(); + start(); + return elapsedTime; +} + +double VElapsedTimer::elapsed() const +{ + if (!isValid()) return 0; + return std::chrono::duration( + std::chrono::high_resolution_clock::now() - clock) + .count(); +} + +bool VElapsedTimer::hasExpired(double time) +{ + double elapsedTime = elapsed(); + if (elapsedTime > time) return true; + return false; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.h new file mode 100644 index 0000000000..d53d38ad6a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/velapsedtimer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VELAPSEDTIMER_H +#define VELAPSEDTIMER_H + +#include +#include "vglobal.h" + +class VElapsedTimer { +public: + double elapsed() const; + bool hasExpired(double millsec); + void start(); + double restart(); + inline bool isValid() const { return m_valid; } + +private: + std::chrono::high_resolution_clock::time_point clock; + bool m_valid{false}; +}; +#endif // VELAPSEDTIMER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vglobal.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vglobal.h new file mode 100644 index 0000000000..45e95e3352 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vglobal.h @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VGLOBAL_H +#define VGLOBAL_H + +#include +#include +#include +#include +#include + +using uint = uint32_t; +using ushort = uint16_t; +using uchar = uint8_t; + +#if !defined(V_NAMESPACE) + +#define V_USE_NAMESPACE +#define V_BEGIN_NAMESPACE +#define V_END_NAMESPACE + +#else /* user namespace */ + +#define V_USE_NAMESPACE using namespace ::V_NAMESPACE; +#define V_BEGIN_NAMESPACE namespace V_NAMESPACE { +#define V_END_NAMESPACE } + +#endif + +#ifndef __has_attribute +# define __has_attribute(x) 0 +#endif /* !__has_attribute */ + +#if __has_attribute(unused) +# define V_UNUSED __attribute__((__unused__)) +#else +# define V_UNUSED +#endif /* V_UNUSED */ + +#if __has_attribute(warn_unused_result) +# define V_REQUIRED_RESULT __attribute__((__warn_unused_result__)) +#else +# define V_REQUIRED_RESULT +#endif /* V_REQUIRED_RESULT */ + +#define V_CONSTEXPR constexpr +#define V_NOTHROW noexcept + +#include "vdebug.h" + +#if __GNUC__ >= 7 +#define VECTOR_FALLTHROUGH __attribute__ ((fallthrough)); +#else +#define VECTOR_FALLTHROUGH +#endif + +#ifdef LOTTIE_THREAD_SUPPORT +#define vthread_local thread_local +#else +#define vthread_local +#endif + +#if defined(_MSC_VER) + #define V_ALWAYS_INLINE __forceinline +#else + #define V_ALWAYS_INLINE __attribute__((always_inline)) +#endif + +template +V_CONSTEXPR inline const T &vMin(const T &a, const T &b) +{ + return (a < b) ? a : b; +} +template +V_CONSTEXPR inline const T &vMax(const T &a, const T &b) +{ + return (a < b) ? b : a; +} + +static const double EPSILON_DOUBLE = 0.000000000001f; +static const float EPSILON_FLOAT = 0.000001f; + +static inline bool vCompare(float p1, float p2) +{ + return (std::abs(p1 - p2) < EPSILON_FLOAT); +} + +static inline bool vIsZero(float f) +{ + return (std::abs(f) <= EPSILON_FLOAT); +} + +static inline bool vIsZero(double f) +{ + return (std::abs(f) <= EPSILON_DOUBLE); +} + +class vFlagHelper { + int i; + +public: + explicit constexpr inline vFlagHelper(int ai) noexcept : i(ai) {} + constexpr inline operator int() const noexcept { return i; } + + explicit constexpr inline vFlagHelper(uint ai) noexcept : i(int(ai)) {} + explicit constexpr inline vFlagHelper(short ai) noexcept : i(int(ai)) {} + explicit constexpr inline vFlagHelper(ushort ai) noexcept : i(int(uint(ai))) {} + constexpr inline operator uint() const noexcept { return uint(i); } +}; + +template +class vFlag { +public: + static_assert( + (sizeof(Enum) <= sizeof(int)), + "vFlag only supports int as storage so bigger type will overflow"); + static_assert((std::is_enum::value), + "vFlag is only usable on enumeration types."); + + using Int = typename std::conditional< + std::is_unsigned::type>::value, + unsigned int, signed int>::type; + + using enum_type = Enum; + // compiler-generated copy/move ctor/assignment operators are fine! + + vFlag() = default; + constexpr vFlag(Enum f) noexcept : i(Int(f)) {} + explicit constexpr vFlag(vFlagHelper f) noexcept : i(f) {} + + inline vFlag &operator&=(int mask) noexcept + { + i &= mask; + return *this; + } + inline vFlag &operator&=(uint mask) noexcept + { + i &= mask; + return *this; + } + inline vFlag &operator&=(Enum mask) noexcept + { + i &= Int(mask); + return *this; + } + inline vFlag &operator|=(vFlag f) noexcept + { + i |= f.i; + return *this; + } + inline vFlag &operator|=(Enum f) noexcept + { + i |= Int(f); + return *this; + } + inline vFlag &operator^=(vFlag f) noexcept + { + i ^= f.i; + return *this; + } + inline vFlag &operator^=(Enum f) noexcept + { + i ^= Int(f); + return *this; + } + + constexpr inline operator Int() const noexcept { return i; } + + constexpr inline vFlag operator|(vFlag f) const + { + return vFlag(vFlagHelper(i | f.i)); + } + constexpr inline vFlag operator|(Enum f) const noexcept + { + return vFlag(vFlagHelper(i | Int(f))); + } + constexpr inline vFlag operator^(vFlag f) const noexcept + { + return vFlag(vFlagHelper(i ^ f.i)); + } + constexpr inline vFlag operator^(Enum f) const noexcept + { + return vFlag(vFlagHelper(i ^ Int(f))); + } + constexpr inline vFlag operator&(int mask) const noexcept + { + return vFlag(vFlagHelper(i & mask)); + } + constexpr inline vFlag operator&(uint mask) const noexcept + { + return vFlag(vFlagHelper(i & mask)); + } + constexpr inline vFlag operator&(Enum f) const noexcept + { + return vFlag(vFlagHelper(i & Int(f))); + } + constexpr inline vFlag operator~() const noexcept + { + return vFlag(vFlagHelper(~i)); + } + + constexpr inline bool operator!() const noexcept { return !i; } + + constexpr inline bool testFlag(Enum f) const noexcept + { + return (i & Int(f)) == Int(f) && (Int(f) != 0 || i == Int(f)); + } + inline vFlag &setFlag(Enum f, bool on = true) noexcept + { + return on ? (*this |= f) : (*this &= ~f); + } + + Int i{0}; +}; + +class VColor { +public: + VColor() = default; + explicit VColor(uchar red, uchar green, uchar blue, uchar alpha = 255) noexcept + :a(alpha), r(red), g(green), b(blue){} + inline uchar red() const noexcept { return r; } + inline uchar green() const noexcept { return g; } + inline uchar blue() const noexcept { return b; } + inline uchar alpha() const noexcept { return a; } + inline void setRed(uchar red) noexcept { r = red; } + inline void setGreen(uchar green) noexcept { g = green; } + inline void setBlue(uchar blue) noexcept { b = blue; } + inline void setAlpha(uchar alpha) noexcept { a = alpha; } + inline bool isOpaque() const { return a == 255; } + inline bool isTransparent() const { return a == 0; } + inline bool operator==(const VColor &o) const + { + return ((a == o.a) && (r == o.r) && (g == o.g) && (b == o.b)); + } + uint premulARGB() const + { + int pr = (r * a) / 255; + int pg = (g * a) / 255; + int pb = (b * a) / 255; + return uint((a << 24) | (pr << 16) | (pg << 8) | (pb)); + } + + uint premulARGB(float opacity) const + { + int alpha = int(a * opacity); + int pr = (r * alpha) / 255; + int pg = (g * alpha) / 255; + int pb = (b * alpha) / 255; + return uint((alpha << 24) | (pr << 16) | (pg << 8) | (pb)); + } + +public: + uchar a{0}; + uchar r{0}; + uchar g{0}; + uchar b{0}; +}; + +enum class FillRule: unsigned char { EvenOdd, Winding }; +enum class JoinStyle: unsigned char { Miter, Bevel, Round }; +enum class CapStyle: unsigned char { Flat, Square, Round }; + +enum class BlendMode { + Src, + SrcOver, + DestIn, + DestOut, + Last, +}; + +#ifndef V_CONSTRUCTOR_FUNCTION +#define V_CONSTRUCTOR_FUNCTION0(AFUNC) \ + namespace { \ + static const struct AFUNC##_ctor_class_ { \ + inline AFUNC##_ctor_class_() { AFUNC(); } \ + } AFUNC##_ctor_instance_; \ + } + +#define V_CONSTRUCTOR_FUNCTION(AFUNC) V_CONSTRUCTOR_FUNCTION0(AFUNC) +#endif + +#endif // VGLOBAL_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.cpp new file mode 100644 index 0000000000..c2446be95a --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.cpp @@ -0,0 +1,220 @@ +#include "vimageloader.h" +#include "config.h" +#include "vdebug.h" +#include + +#ifdef _WIN32 +# include +#else +# include +#endif // _WIN32 + +using lottie_image_load_f = unsigned char *(*)(const char *filename, int *x, + int *y, int *comp, int req_comp); +using lottie_image_load_data_f = unsigned char *(*)(const char *data, int len, + int *x, int *y, int *comp, + int req_comp); +using lottie_image_free_f = void (*)(unsigned char *); + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned char *lottie_image_load(char const *filename, int *x, int *y, + int *comp, int req_comp); +extern unsigned char *lottie_image_load_from_data(const char *imageData, + int len, int *x, int *y, + int *comp, int req_comp); +extern void lottie_image_free(unsigned char *data); + +#ifdef __cplusplus +} +#endif + +struct VImageLoader::Impl { + lottie_image_load_f imageLoad{nullptr}; + lottie_image_free_f imageFree{nullptr}; + lottie_image_load_data_f imageFromData{nullptr}; + +#ifdef LOTTIE_IMAGE_MODULE_SUPPORT +# ifdef _WIN32 + HMODULE dl_handle{nullptr}; + bool moduleLoad() + { + dl_handle = LoadLibraryA(LOTTIE_IMAGE_MODULE_PLUGIN); + return (dl_handle == nullptr); + } + void moduleFree() + { + if (dl_handle) FreeLibrary(dl_handle); + } + void init() + { + imageLoad = reinterpret_cast( + GetProcAddress(dl_handle, "lottie_image_load")); + imageFree = reinterpret_cast( + GetProcAddress(dl_handle, "lottie_image_free")); + imageFromData = reinterpret_cast( + GetProcAddress(dl_handle, "lottie_image_load_from_data")); + } +# else // _WIN32 + void *dl_handle{nullptr}; + void init() + { + imageLoad = reinterpret_cast( + dlsym(dl_handle, "lottie_image_load")); + imageFree = reinterpret_cast( + dlsym(dl_handle, "lottie_image_free")); + imageFromData = reinterpret_cast( + dlsym(dl_handle, "lottie_image_load_from_data")); + } + + void moduleFree() + { + if (dl_handle) dlclose(dl_handle); + } + bool moduleLoad() + { + dl_handle = dlopen(LOTTIE_IMAGE_MODULE_PLUGIN, RTLD_LAZY); + return (dl_handle == nullptr); + } +# endif // _WIN32 +#else // LOTTIE_IMAGE_MODULE_SUPPORT + void init() + { + imageLoad = lottie_image_load; + imageFree = lottie_image_free; + imageFromData = lottie_image_load_from_data; + } + void moduleFree() {} + bool moduleLoad() { return false; } +#endif // LOTTIE_IMAGE_MODULE_SUPPORT + + Impl() + { + if (moduleLoad()) { + vWarning << "Failed to dlopen librlottie-image-loader library"; + return; + } + + init(); + + if (!imageLoad) + vWarning << "Failed to find symbol lottie_image_load in " + "librlottie-image-loader library"; + + if (!imageFree) + vWarning << "Failed to find symbol lottie_image_free in " + "librlottie-image-loader library"; + + if (!imageFromData) + vWarning << "Failed to find symbol lottie_image_load_data in " + "librlottie-image-loader library"; + } + + ~Impl() { moduleFree(); } + + VBitmap createBitmap(unsigned char *data, int width, int height, + int channel) + { + // premultiply alpha + if (channel == 4) + convertToBGRAPremul(data, width, height); + else + convertToBGRA(data, width, height); + + // create a bitmap of same size. + VBitmap result = + VBitmap(width, height, VBitmap::Format::ARGB32_Premultiplied); + + // copy the data to bitmap buffer + memcpy(result.data(), data, width * height * 4); + + // free the image data + imageFree(data); + + return result; + } + + VBitmap load(const char *fileName) + { + if (!imageLoad) return VBitmap(); + + int width, height, n; + unsigned char *data = imageLoad(fileName, &width, &height, &n, 4); + + if (!data) { + return VBitmap(); + } + + return createBitmap(data, width, height, n); + } + + VBitmap load(const char *imageData, size_t len) + { + if (!imageFromData) return VBitmap(); + + int width, height, n; + unsigned char *data = + imageFromData(imageData, static_cast(len), &width, &height, &n, 4); + + if (!data) { + return VBitmap(); + } + + return createBitmap(data, width, height, n); + } + /* + * convert from RGBA to BGRA and premultiply + */ + void convertToBGRAPremul(unsigned char *bits, int width, int height) + { + int pixelCount = width * height; + unsigned char *pix = bits; + for (int i = 0; i < pixelCount; i++) { + unsigned char r = pix[0]; + unsigned char g = pix[1]; + unsigned char b = pix[2]; + unsigned char a = pix[3]; + + r = (r * a) / 255; + g = (g * a) / 255; + b = (b * a) / 255; + + pix[0] = b; + pix[1] = g; + pix[2] = r; + + pix += 4; + } + } + /* + * convert from RGBA to BGRA + */ + void convertToBGRA(unsigned char *bits, int width, int height) + { + int pixelCount = width * height; + unsigned char *pix = bits; + for (int i = 0; i < pixelCount; i++) { + unsigned char r = pix[0]; + unsigned char b = pix[2]; + pix[0] = b; + pix[2] = r; + pix += 4; + } + } +}; + +VImageLoader::VImageLoader() : mImpl(std::make_unique()) {} + +VImageLoader::~VImageLoader() {} + +VBitmap VImageLoader::load(const char *fileName) +{ + return mImpl->load(fileName); +} + +VBitmap VImageLoader::load(const char *data, size_t len) +{ + return mImpl->load(data, int(len)); +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.h new file mode 100644 index 0000000000..4d96a7dc07 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vimageloader.h @@ -0,0 +1,26 @@ +#ifndef VIMAGELOADER_H +#define VIMAGELOADER_H + +#include + +#include "vbitmap.h" + +class VImageLoader +{ +public: + static VImageLoader& instance() + { + static VImageLoader singleton; + return singleton; + } + + VBitmap load(const char *fileName); + VBitmap load(const char *data, size_t len); + ~VImageLoader(); +private: + VImageLoader(); + struct Impl; + std::unique_ptr mImpl; +}; + +#endif // VIMAGELOADER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.cpp new file mode 100644 index 0000000000..fca0784676 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vinterpolator.h" +#include + +V_BEGIN_NAMESPACE + +#define NEWTON_ITERATIONS 4 +#define NEWTON_MIN_SLOPE 0.02 +#define SUBDIVISION_PRECISION 0.0000001 +#define SUBDIVISION_MAX_ITERATIONS 10 + +const float VInterpolator::kSampleStepSize = + 1.0f / float(VInterpolator::kSplineTableSize - 1); + +void VInterpolator::init(float aX1, float aY1, float aX2, float aY2) +{ + mX1 = aX1; + mY1 = aY1; + mX2 = aX2; + mY2 = aY2; + + if (mX1 != mY1 || mX2 != mY2) CalcSampleValues(); +} + +/*static*/ float VInterpolator::CalcBezier(float aT, float aA1, float aA2) +{ + // use Horner's scheme to evaluate the Bezier polynomial + return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; +} + +void VInterpolator::CalcSampleValues() +{ + for (int i = 0; i < kSplineTableSize; ++i) { + mSampleValues[i] = CalcBezier(float(i) * kSampleStepSize, mX1, mX2); + } +} + +float VInterpolator::GetSlope(float aT, float aA1, float aA2) +{ + return 3.0f * A(aA1, aA2) * aT * aT + 2.0f * B(aA1, aA2) * aT + C(aA1); +} + +float VInterpolator::value(float aX) const +{ + if (mX1 == mY1 && mX2 == mY2) return aX; + + return CalcBezier(GetTForX(aX), mY1, mY2); +} + +float VInterpolator::GetTForX(float aX) const +{ + // Find interval where t lies + float intervalStart = 0.0; + const float* currentSample = &mSampleValues[1]; + const float* const lastSample = &mSampleValues[kSplineTableSize - 1]; + for (; currentSample != lastSample && *currentSample <= aX; + ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; // t now lies between *currentSample and *currentSample+1 + + // Interpolate to provide an initial guess for t + float dist = + (aX - *currentSample) / (*(currentSample + 1) - *currentSample); + float guessForT = intervalStart + dist * kSampleStepSize; + + // Check the slope to see what strategy to use. If the slope is too small + // Newton-Raphson iteration won't converge on a root so we use bisection + // instead. + float initialSlope = GetSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return NewtonRaphsonIterate(aX, guessForT); + } else if (initialSlope == 0.0) { + return guessForT; + } else { + return BinarySubdivide(aX, intervalStart, + intervalStart + kSampleStepSize); + } +} + +float VInterpolator::NewtonRaphsonIterate(float aX, float aGuessT) const +{ + // Refine guess with Newton-Raphson iteration + for (int i = 0; i < NEWTON_ITERATIONS; ++i) { + // We're trying to find where f(t) = aX, + // so we're actually looking for a root for: CalcBezier(t) - aX + float currentX = CalcBezier(aGuessT, mX1, mX2) - aX; + float currentSlope = GetSlope(aGuessT, mX1, mX2); + + if (currentSlope == 0.0) return aGuessT; + + aGuessT -= currentX / currentSlope; + } + + return aGuessT; +} + +float VInterpolator::BinarySubdivide(float aX, float aA, float aB) const +{ + float currentX; + float currentT; + int i = 0; + + do { + currentT = aA + (aB - aA) / 2.0f; + currentX = CalcBezier(currentT, mX1, mX2) - aX; + + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (fabs(currentX) > SUBDIVISION_PRECISION && + ++i < SUBDIVISION_MAX_ITERATIONS); + + return currentT; +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.h new file mode 100644 index 0000000000..0218eab5fd --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vinterpolator.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VINTERPOLATOR_H +#define VINTERPOLATOR_H + +#include "vpoint.h" + +V_BEGIN_NAMESPACE + +class VInterpolator { +public: + VInterpolator() + { /* caller must call Init later */ + } + + VInterpolator(float aX1, float aY1, float aX2, float aY2) + { + init(aX1, aY1, aX2, aY2); + } + + VInterpolator(VPointF pt1, VPointF pt2) + { + init(pt1.x(), pt1.y(), pt2.x(), pt2.y()); + } + + void init(float aX1, float aY1, float aX2, float aY2); + + float value(float aX) const; + + void GetSplineDerivativeValues(float aX, float& aDX, float& aDY) const; + +private: + void CalcSampleValues(); + + /** + * Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. + */ + static float CalcBezier(float aT, float aA1, float aA2); + + /** + * Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. + */ + static float GetSlope(float aT, float aA1, float aA2); + + float GetTForX(float aX) const; + + float NewtonRaphsonIterate(float aX, float aGuessT) const; + + float BinarySubdivide(float aX, float aA, float aB) const; + + static float A(float aA1, float aA2) { return 1.0f - 3.0f * aA2 + 3.0f * aA1; } + + static float B(float aA1, float aA2) { return 3.0f * aA2 - 6.0f * aA1; } + + static float C(float aA1) { return 3.0f * aA1; } + + float mX1; + float mY1; + float mX2; + float mY2; + enum { kSplineTableSize = 11 }; + float mSampleValues[kSplineTableSize]; + static const float kSampleStepSize; +}; + +V_END_NAMESPACE + +#endif // VINTERPOLATOR_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vline.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vline.h new file mode 100644 index 0000000000..ee8b4a567b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vline.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VLINE_H +#define VLINE_H + +#include "vglobal.h" +#include "vpoint.h" + +V_BEGIN_NAMESPACE + +class VLine { +public: + VLine() = default; + VLine(float x1, float y1, float x2, float y2) + : mX1(x1), mY1(y1), mX2(x2), mY2(y2) + { + } + VLine(const VPointF &p1, const VPointF &p2) + : mX1(p1.x()), mY1(p1.y()), mX2(p2.x()), mY2(p2.y()) + { + } + float length() const { return length(mX1, mY1, mX2, mY2);} + void splitAtLength(float length, VLine &left, VLine &right) const; + VPointF p1() const { return {mX1, mY1}; } + VPointF p2() const { return {mX2, mY2}; } + float angle() const; + static float length(float x1, float y1, float x2, float y2); + +private: + float mX1{0}; + float mY1{0}; + float mX2{0}; + float mY2{0}; +}; + +inline float VLine::angle() const +{ + static constexpr float K_PI = 3.141592f; + const float dx = mX2 - mX1; + const float dy = mY2 - mY1; + + const float theta = std::atan2(dy, dx) * 180.0f / K_PI; + return theta; +} + +// approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. +// With alpha = 1, beta = 3/8, giving results with the largest error less +// than 7% compared to the exact value. +inline V_ALWAYS_INLINE float VLine::length(float x1, float y1, float x2, float y2) +{ + float x = x2 - x1; + float y = y2 - y1; + + x = x < 0 ? -x : x; + y = y < 0 ? -y : y; + + return (x > y ? x + 0.375f * y : y + 0.375f * x); +} + +inline void VLine::splitAtLength(float lengthAt, VLine &left, VLine &right) const +{ + float len = length(); + float dx = ((mX2 - mX1) / len) * lengthAt; + float dy = ((mY2 - mY1) / len) * lengthAt; + + left.mX1 = mX1; + left.mY1 = mY1; + left.mX2 = left.mX1 + dx; + left.mY2 = left.mY1 + dy; + + right.mX1 = left.mX2; + right.mY1 = left.mY2; + right.mX2 = mX2; + right.mY2 = mY2; +} + +#endif //VLINE_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.cpp new file mode 100644 index 0000000000..7efed61bb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.cpp @@ -0,0 +1,684 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vmatrix.h" +#include +#include +#include + +V_BEGIN_NAMESPACE + +/* m11 m21 mtx + * m12 m22 mty + * m13 m23 m33 + */ + +inline float VMatrix::determinant() const +{ + return m11 * (m33 * m22 - mty * m23) - m21 * (m33 * m12 - mty * m13) + + mtx * (m23 * m12 - m22 * m13); +} + +bool VMatrix::isAffine() const +{ + return type() < MatrixType::Project; +} + +bool VMatrix::isIdentity() const +{ + return type() == MatrixType::None; +} + +bool VMatrix::isInvertible() const +{ + return !vIsZero(determinant()); +} + +bool VMatrix::isScaling() const +{ + return type() >= MatrixType::Scale; +} +bool VMatrix::isRotating() const +{ + return type() >= MatrixType::Rotate; +} + +bool VMatrix::isTranslating() const +{ + return type() >= MatrixType::Translate; +} + +VMatrix &VMatrix::operator*=(float num) +{ + if (num == 1.) return *this; + + m11 *= num; + m12 *= num; + m13 *= num; + m21 *= num; + m22 *= num; + m23 *= num; + mtx *= num; + mty *= num; + m33 *= num; + if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; + + return *this; +} + +VMatrix &VMatrix::operator/=(float div) +{ + if (div == 0) return *this; + + div = 1 / div; + return operator*=(div); +} + +VMatrix::MatrixType VMatrix::type() const +{ + if (dirty == MatrixType::None || dirty < mType) return mType; + + switch (dirty) { + case MatrixType::Project: + if (!vIsZero(m13) || !vIsZero(m23) || !vIsZero(m33 - 1)) { + mType = MatrixType::Project; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Shear: + case MatrixType::Rotate: + if (!vIsZero(m12) || !vIsZero(m21)) { + const float dot = m11 * m12 + m21 * m22; + if (vIsZero(dot)) + mType = MatrixType::Rotate; + else + mType = MatrixType::Shear; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Scale: + if (!vIsZero(m11 - 1) || !vIsZero(m22 - 1)) { + mType = MatrixType::Scale; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::Translate: + if (!vIsZero(mtx) || !vIsZero(mty)) { + mType = MatrixType::Translate; + break; + } + VECTOR_FALLTHROUGH + case MatrixType::None: + mType = MatrixType::None; + break; + } + + dirty = MatrixType::None; + return mType; +} + +VMatrix &VMatrix::translate(float dx, float dy) +{ + if (dx == 0 && dy == 0) return *this; + + switch (type()) { + case MatrixType::None: + mtx = dx; + mty = dy; + break; + case MatrixType::Translate: + mtx += dx; + mty += dy; + break; + case MatrixType::Scale: + mtx += dx * m11; + mty += dy * m22; + break; + case MatrixType::Project: + m33 += dx * m13 + dy * m23; + VECTOR_FALLTHROUGH + case MatrixType::Shear: + case MatrixType::Rotate: + mtx += dx * m11 + dy * m21; + mty += dy * m22 + dx * m12; + break; + } + if (dirty < MatrixType::Translate) dirty = MatrixType::Translate; + return *this; +} + +VMatrix &VMatrix::scale(float sx, float sy) +{ + if (sx == 1 && sy == 1) return *this; + + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m11 = sx; + m22 = sy; + break; + case MatrixType::Project: + m13 *= sx; + m23 *= sy; + VECTOR_FALLTHROUGH + case MatrixType::Rotate: + case MatrixType::Shear: + m12 *= sx; + m21 *= sy; + VECTOR_FALLTHROUGH + case MatrixType::Scale: + m11 *= sx; + m22 *= sy; + break; + } + if (dirty < MatrixType::Scale) dirty = MatrixType::Scale; + return *this; +} + +VMatrix &VMatrix::shear(float sh, float sv) +{ + if (sh == 0 && sv == 0) return *this; + + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m12 = sv; + m21 = sh; + break; + case MatrixType::Scale: + m12 = sv * m22; + m21 = sh * m11; + break; + case MatrixType::Project: { + float tm13 = sv * m23; + float tm23 = sh * m13; + m13 += tm13; + m23 += tm23; + VECTOR_FALLTHROUGH + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float tm11 = sv * m21; + float tm22 = sh * m12; + float tm12 = sv * m22; + float tm21 = sh * m11; + m11 += tm11; + m12 += tm12; + m21 += tm21; + m22 += tm22; + break; + } + } + if (dirty < MatrixType::Shear) dirty = MatrixType::Shear; + return *this; +} + +static const float deg2rad = float(0.017453292519943295769); // pi/180 +static const float inv_dist_to_plane = 1. / 1024.; + +VMatrix &VMatrix::rotate(float a, Axis axis) +{ + if (a == 0) return *this; + + float sina = 0; + float cosa = 0; + if (a == 90. || a == -270.) + sina = 1.; + else if (a == 270. || a == -90.) + sina = -1.; + else if (a == 180.) + cosa = -1.; + else { + float b = deg2rad * a; // convert to radians + sina = std::sin(b); // fast and convenient + cosa = std::cos(b); + } + + if (axis == Axis::Z) { + switch (type()) { + case MatrixType::None: + case MatrixType::Translate: + m11 = cosa; + m12 = sina; + m21 = -sina; + m22 = cosa; + break; + case MatrixType::Scale: { + float tm11 = cosa * m11; + float tm12 = sina * m22; + float tm21 = -sina * m11; + float tm22 = cosa * m22; + m11 = tm11; + m12 = tm12; + m21 = tm21; + m22 = tm22; + break; + } + case MatrixType::Project: { + float tm13 = cosa * m13 + sina * m23; + float tm23 = -sina * m13 + cosa * m23; + m13 = tm13; + m23 = tm23; + VECTOR_FALLTHROUGH + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float tm11 = cosa * m11 + sina * m21; + float tm12 = cosa * m12 + sina * m22; + float tm21 = -sina * m11 + cosa * m21; + float tm22 = -sina * m12 + cosa * m22; + m11 = tm11; + m12 = tm12; + m21 = tm21; + m22 = tm22; + break; + } + } + if (dirty < MatrixType::Rotate) dirty = MatrixType::Rotate; + } else { + VMatrix result; + if (axis == Axis::Y) { + result.m11 = cosa; + result.m13 = -sina * inv_dist_to_plane; + } else { + result.m22 = cosa; + result.m23 = -sina * inv_dist_to_plane; + } + result.mType = MatrixType::Project; + *this = result * *this; + } + + return *this; +} + +VMatrix VMatrix::operator*(const VMatrix &m) const +{ + const MatrixType otherType = m.type(); + if (otherType == MatrixType::None) return *this; + + const MatrixType thisType = type(); + if (thisType == MatrixType::None) return m; + + VMatrix t; + MatrixType type = vMax(thisType, otherType); + switch (type) { + case MatrixType::None: + break; + case MatrixType::Translate: + t.mtx = mtx + m.mtx; + t.mty += mty + m.mty; + break; + case MatrixType::Scale: { + float m11v = m11 * m.m11; + float m22v = m22 * m.m22; + + float m31v = mtx * m.m11 + m.mtx; + float m32v = mty * m.m22 + m.mty; + + t.m11 = m11v; + t.m22 = m22v; + t.mtx = m31v; + t.mty = m32v; + break; + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float m11v = m11 * m.m11 + m12 * m.m21; + float m12v = m11 * m.m12 + m12 * m.m22; + + float m21v = m21 * m.m11 + m22 * m.m21; + float m22v = m21 * m.m12 + m22 * m.m22; + + float m31v = mtx * m.m11 + mty * m.m21 + m.mtx; + float m32v = mtx * m.m12 + mty * m.m22 + m.mty; + + t.m11 = m11v; + t.m12 = m12v; + t.m21 = m21v; + t.m22 = m22v; + t.mtx = m31v; + t.mty = m32v; + break; + } + case MatrixType::Project: { + float m11v = m11 * m.m11 + m12 * m.m21 + m13 * m.mtx; + float m12v = m11 * m.m12 + m12 * m.m22 + m13 * m.mty; + float m13v = m11 * m.m13 + m12 * m.m23 + m13 * m.m33; + + float m21v = m21 * m.m11 + m22 * m.m21 + m23 * m.mtx; + float m22v = m21 * m.m12 + m22 * m.m22 + m23 * m.mty; + float m23v = m21 * m.m13 + m22 * m.m23 + m23 * m.m33; + + float m31v = mtx * m.m11 + mty * m.m21 + m33 * m.mtx; + float m32v = mtx * m.m12 + mty * m.m22 + m33 * m.mty; + float m33v = mtx * m.m13 + mty * m.m23 + m33 * m.m33; + + t.m11 = m11v; + t.m12 = m12v; + t.m13 = m13v; + t.m21 = m21v; + t.m22 = m22v; + t.m23 = m23v; + t.mtx = m31v; + t.mty = m32v; + t.m33 = m33v; + } + } + + t.dirty = type; + t.mType = type; + + return t; +} + +VMatrix &VMatrix::operator*=(const VMatrix &o) +{ + const MatrixType otherType = o.type(); + if (otherType == MatrixType::None) return *this; + + const MatrixType thisType = type(); + if (thisType == MatrixType::None) return operator=(o); + + MatrixType t = vMax(thisType, otherType); + switch (t) { + case MatrixType::None: + break; + case MatrixType::Translate: + mtx += o.mtx; + mty += o.mty; + break; + case MatrixType::Scale: { + float m11v = m11 * o.m11; + float m22v = m22 * o.m22; + + float m31v = mtx * o.m11 + o.mtx; + float m32v = mty * o.m22 + o.mty; + + m11 = m11v; + m22 = m22v; + mtx = m31v; + mty = m32v; + break; + } + case MatrixType::Rotate: + case MatrixType::Shear: { + float m11v = m11 * o.m11 + m12 * o.m21; + float m12v = m11 * o.m12 + m12 * o.m22; + + float m21v = m21 * o.m11 + m22 * o.m21; + float m22v = m21 * o.m12 + m22 * o.m22; + + float m31v = mtx * o.m11 + mty * o.m21 + o.mtx; + float m32v = mtx * o.m12 + mty * o.m22 + o.mty; + + m11 = m11v; + m12 = m12v; + m21 = m21v; + m22 = m22v; + mtx = m31v; + mty = m32v; + break; + } + case MatrixType::Project: { + float m11v = m11 * o.m11 + m12 * o.m21 + m13 * o.mtx; + float m12v = m11 * o.m12 + m12 * o.m22 + m13 * o.mty; + float m13v = m11 * o.m13 + m12 * o.m23 + m13 * o.m33; + + float m21v = m21 * o.m11 + m22 * o.m21 + m23 * o.mtx; + float m22v = m21 * o.m12 + m22 * o.m22 + m23 * o.mty; + float m23v = m21 * o.m13 + m22 * o.m23 + m23 * o.m33; + + float m31v = mtx * o.m11 + mty * o.m21 + m33 * o.mtx; + float m32v = mtx * o.m12 + mty * o.m22 + m33 * o.mty; + float m33v = mtx * o.m13 + mty * o.m23 + m33 * o.m33; + + m11 = m11v; + m12 = m12v; + m13 = m13v; + m21 = m21v; + m22 = m22v; + m23 = m23v; + mtx = m31v; + mty = m32v; + m33 = m33v; + } + } + + dirty = t; + mType = t; + + return *this; +} + +VMatrix VMatrix::adjoint() const +{ + float h11, h12, h13, h21, h22, h23, h31, h32, h33; + h11 = m22 * m33 - m23 * mty; + h21 = m23 * mtx - m21 * m33; + h31 = m21 * mty - m22 * mtx; + h12 = m13 * mty - m12 * m33; + h22 = m11 * m33 - m13 * mtx; + h32 = m12 * mtx - m11 * mty; + h13 = m12 * m23 - m13 * m22; + h23 = m13 * m21 - m11 * m23; + h33 = m11 * m22 - m12 * m21; + + VMatrix res; + res.m11 = h11; + res.m12 = h12; + res.m13 = h13; + res.m21 = h21; + res.m22 = h22; + res.m23 = h23; + res.mtx = h31; + res.mty = h32; + res.m33 = h33; + res.mType = MatrixType::None; + res.dirty = MatrixType::Project; + + return res; +} + +VMatrix VMatrix::inverted(bool *invertible) const +{ + VMatrix invert; + bool inv = true; + + switch (type()) { + case MatrixType::None: + break; + case MatrixType::Translate: + invert.mtx = -mtx; + invert.mty = -mty; + break; + case MatrixType::Scale: + inv = !vIsZero(m11); + inv &= !vIsZero(m22); + if (inv) { + invert.m11 = 1.0f / m11; + invert.m22 = 1.0f / m22; + invert.mtx = -mtx * invert.m11; + invert.mty = -mty * invert.m22; + } + break; + default: + // general case + float det = determinant(); + inv = !vIsZero(det); + if (inv) invert = (adjoint() /= det); + // TODO Test above line + break; + } + + if (invertible) *invertible = inv; + + if (inv) { + // inverting doesn't change the type + invert.mType = mType; + invert.dirty = dirty; + } + + return invert; +} + +bool VMatrix::operator==(const VMatrix &o) const +{ + return fuzzyCompare(o); +} + +bool VMatrix::operator!=(const VMatrix &o) const +{ + return !operator==(o); +} + +bool VMatrix::fuzzyCompare(const VMatrix &o) const +{ + return vCompare(m11, o.m11) && vCompare(m12, o.m12) && + vCompare(m21, o.m21) && vCompare(m22, o.m22) && + vCompare(mtx, o.mtx) && vCompare(mty, o.mty); +} + +#define V_NEAR_CLIP 0.000001f +#ifdef MAP +#undef MAP +#endif +#define MAP(x, y, nx, ny) \ + do { \ + float FX_ = x; \ + float FY_ = y; \ + switch (t) { \ + case MatrixType::None: \ + nx = FX_; \ + ny = FY_; \ + break; \ + case MatrixType::Translate: \ + nx = FX_ + mtx; \ + ny = FY_ + mty; \ + break; \ + case MatrixType::Scale: \ + nx = m11 * FX_ + mtx; \ + ny = m22 * FY_ + mty; \ + break; \ + case MatrixType::Rotate: \ + case MatrixType::Shear: \ + case MatrixType::Project: \ + nx = m11 * FX_ + m21 * FY_ + mtx; \ + ny = m12 * FX_ + m22 * FY_ + mty; \ + if (t == MatrixType::Project) { \ + float w = (m13 * FX_ + m23 * FY_ + m33); \ + if (w < V_NEAR_CLIP) w = V_NEAR_CLIP; \ + w = 1. / w; \ + nx *= w; \ + ny *= w; \ + } \ + } \ + } while (0) + +VRect VMatrix::map(const VRect &rect) const +{ + VMatrix::MatrixType t = type(); + if (t <= MatrixType::Translate) + return rect.translated(std::lround(mtx), std::lround(mty)); + + if (t <= MatrixType::Scale) { + int x = std::lround(m11 * rect.x() + mtx); + int y = std::lround(m22 * rect.y() + mty); + int w = std::lround(m11 * rect.width()); + int h = std::lround(m22 * rect.height()); + if (w < 0) { + w = -w; + x -= w; + } + if (h < 0) { + h = -h; + y -= h; + } + return {x, y, w, h}; + } else if (t < MatrixType::Project) { + // see mapToPolygon for explanations of the algorithm. + float x = 0, y = 0; + MAP(rect.left(), rect.top(), x, y); + float xmin = x; + float ymin = y; + float xmax = x; + float ymax = y; + MAP(rect.right() + 1, rect.top(), x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + MAP(rect.right() + 1, rect.bottom() + 1, x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + MAP(rect.left(), rect.bottom() + 1, x, y); + xmin = vMin(xmin, x); + ymin = vMin(ymin, y); + xmax = vMax(xmax, x); + ymax = vMax(ymax, y); + return VRect(std::lround(xmin), std::lround(ymin), + std::lround(xmax) - std::lround(xmin), + std::lround(ymax) - std::lround(ymin)); + } else { + // Not supported + assert(0); + return {}; + } +} + +VPointF VMatrix::map(const VPointF &p) const +{ + float fx = p.x(); + float fy = p.y(); + + float x = 0, y = 0; + + VMatrix::MatrixType t = type(); + switch (t) { + case MatrixType::None: + x = fx; + y = fy; + break; + case MatrixType::Translate: + x = fx + mtx; + y = fy + mty; + break; + case MatrixType::Scale: + x = m11 * fx + mtx; + y = m22 * fy + mty; + break; + case MatrixType::Rotate: + case MatrixType::Shear: + case MatrixType::Project: + x = m11 * fx + m21 * fy + mtx; + y = m12 * fx + m22 * fy + mty; + if (t == MatrixType::Project) { + float w = 1.0f / (m13 * fx + m23 * fy + m33); + x *= w; + y *= w; + } + } + return {x, y}; +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.h new file mode 100644 index 0000000000..941e16e9f9 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vmatrix.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VMATRIX_H +#define VMATRIX_H +#include "vglobal.h" +#include "vpoint.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +struct VMatrixData; +class VMatrix { +public: + enum class Axis { X, Y, Z }; + enum class MatrixType: unsigned char { + None = 0x00, + Translate = 0x01, + Scale = 0x02, + Rotate = 0x04, + Shear = 0x08, + Project = 0x10 + }; + VMatrix() = default; + bool isAffine() const; + bool isIdentity() const; + bool isInvertible() const; + bool isScaling() const; + bool isRotating() const; + bool isTranslating() const; + MatrixType type() const; + inline float determinant() const; + + float m_11() const { return m11;} + float m_12() const { return m12;} + float m_13() const { return m13;} + + float m_21() const { return m21;} + float m_22() const { return m22;} + float m_23() const { return m23;} + + float m_tx() const { return mtx;} + float m_ty() const { return mty;} + float m_33() const { return m33;} + + VMatrix &translate(VPointF pos) { return translate(pos.x(), pos.y()); } + VMatrix &translate(float dx, float dy); + VMatrix &scale(VPointF s) { return scale(s.x(), s.y()); } + VMatrix &scale(float sx, float sy); + VMatrix &shear(float sh, float sv); + VMatrix &rotate(float a, Axis axis = VMatrix::Axis::Z); + VMatrix &rotateRadians(float a, Axis axis = VMatrix::Axis::Z); + + VPointF map(const VPointF &p) const; + inline VPointF map(float x, float y) const; + VRect map(const VRect &r) const; + + V_REQUIRED_RESULT VMatrix inverted(bool *invertible = nullptr) const; + V_REQUIRED_RESULT VMatrix adjoint() const; + + VMatrix operator*(const VMatrix &o) const; + VMatrix & operator*=(const VMatrix &); + VMatrix & operator*=(float mul); + VMatrix & operator/=(float div); + bool operator==(const VMatrix &) const; + bool operator!=(const VMatrix &) const; + bool fuzzyCompare(const VMatrix &) const; + float scale() const; +private: + friend struct VSpanData; + float m11{1}, m12{0}, m13{0}; + float m21{0}, m22{1}, m23{0}; + float mtx{0}, mty{0}, m33{1}; + mutable MatrixType mType{MatrixType::None}; + mutable MatrixType dirty{MatrixType::None}; +}; + +inline float VMatrix::scale() const +{ + constexpr float SQRT_2 = 1.41421f; + VPointF p1(0, 0); + VPointF p2(SQRT_2, SQRT_2); + p1 = map(p1); + p2 = map(p2); + VPointF final = p2 - p1; + + return std::sqrt(final.x() * final.x() + final.y() * final.y()) / 2.0f; +} + +inline VPointF VMatrix::map(float x, float y) const +{ + return map(VPointF(x, y)); +} + +V_END_NAMESPACE + +#endif // VMATRIX_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.cpp new file mode 100644 index 0000000000..4ebbc5cb6c --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vpainter.h" +#include + + +V_BEGIN_NAMESPACE + + +void VPainter::drawRle(const VPoint &, const VRle &rle) +{ + if (rle.empty()) return; + // mSpanData.updateSpanFunc(); + + if (!mSpanData.mUnclippedBlendFunc) return; + + // do draw after applying clip. + rle.intersect(mSpanData.clipRect(), mSpanData.mUnclippedBlendFunc, + &mSpanData); +} + +void VPainter::drawRle(const VRle &rle, const VRle &clip) +{ + if (rle.empty() || clip.empty()) return; + + if (!mSpanData.mUnclippedBlendFunc) return; + + rle.intersect(clip, mSpanData.mUnclippedBlendFunc, &mSpanData); +} + +static void fillRect(const VRect &r, VSpanData *data) +{ + auto x1 = std::max(r.x(), 0); + auto x2 = std::min(r.x() + r.width(), data->mDrawableSize.width()); + auto y1 = std::max(r.y(), 0); + auto y2 = std::min(r.y() + r.height(), data->mDrawableSize.height()); + + if (x2 <= x1 || y2 <= y1) return; + + const int nspans = 256; + VRle::Span spans[nspans]; + + int y = y1; + while (y < y2) { + int n = std::min(nspans, y2 - y); + int i = 0; + while (i < n) { + spans[i].x = short(x1); + spans[i].len = ushort(x2 - x1); + spans[i].y = short(y + i); + spans[i].coverage = 255; + ++i; + } + + data->mUnclippedBlendFunc(n, spans, data); + y += n; + } +} + +void VPainter::drawBitmapUntransform(const VRect & target, + const VBitmap &bitmap, + const VRect & source, + uint8_t const_alpha) +{ + mSpanData.initTexture(&bitmap, const_alpha, source); + if (!mSpanData.mUnclippedBlendFunc) return; + mSpanData.dx = float(-target.x()); + mSpanData.dy = float(-target.y()); + + VRect rr = source.translated(target.x(), target.y()); + + fillRect(rr, &mSpanData); +} + +VPainter::VPainter(VBitmap *buffer) +{ + begin(buffer); +} +bool VPainter::begin(VBitmap *buffer) +{ + mBuffer.prepare(buffer); + mSpanData.init(&mBuffer); + // TODO find a better api to clear the surface + mBuffer.clear(); + return true; +} +void VPainter::end() {} + +void VPainter::setDrawRegion(const VRect ®ion) +{ + mSpanData.setDrawRegion(region); +} + +void VPainter::setBrush(const VBrush &brush) +{ + mSpanData.setup(brush); +} + +void VPainter::setBlendMode(BlendMode mode) +{ + mSpanData.mBlendMode = mode; +} + +VRect VPainter::clipBoundingRect() const +{ + return mSpanData.clipRect(); +} + +void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(VRect(point, bitmap.size()), + bitmap, source, const_alpha); +} + +void VPainter::drawBitmap(const VRect &target, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + // clear any existing brush data. + setBrush(VBrush()); + + if (target.size() == source.size()) { + drawBitmapUntransform(target, bitmap, source, const_alpha); + } else { + // @TODO scaling + } +} + +void VPainter::drawBitmap(const VPoint &point, const VBitmap &bitmap, + uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(VRect(point, bitmap.size()), + bitmap, bitmap.rect(), + const_alpha); +} + +void VPainter::drawBitmap(const VRect &rect, const VBitmap &bitmap, + uint8_t const_alpha) +{ + if (!bitmap.valid()) return; + + drawBitmap(rect, bitmap, bitmap.rect(), + const_alpha); +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.h new file mode 100644 index 0000000000..68abd70f64 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpainter.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VPAINTER_H +#define VPAINTER_H + +#include "vbrush.h" +#include "vpoint.h" +#include "vrle.h" +#include "vdrawhelper.h" + +V_BEGIN_NAMESPACE + +class VBitmap; +class VPainter { +public: + VPainter() = default; + explicit VPainter(VBitmap *buffer); + bool begin(VBitmap *buffer); + void end(); + void setDrawRegion(const VRect ®ion); // sub surface rendering area. + void setBrush(const VBrush &brush); + void setBlendMode(BlendMode mode); + void drawRle(const VPoint &pos, const VRle &rle); + void drawRle(const VRle &rle, const VRle &clip); + VRect clipBoundingRect() const; + + void drawBitmap(const VPoint &point, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); + void drawBitmap(const VRect &target, const VBitmap &bitmap, const VRect &source, uint8_t const_alpha = 255); + void drawBitmap(const VPoint &point, const VBitmap &bitmap, uint8_t const_alpha = 255); + void drawBitmap(const VRect &rect, const VBitmap &bitmap, uint8_t const_alpha = 255); +private: + void drawBitmapUntransform(const VRect &target, const VBitmap &bitmap, + const VRect &source, uint8_t const_alpha); + VRasterBuffer mBuffer; + VSpanData mSpanData; +}; + +V_END_NAMESPACE + +#endif // VPAINTER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.cpp new file mode 100644 index 0000000000..f0dbec8ea1 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.cpp @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "vpath.h" +#include +#include +#include +#include "vbezier.h" +#include "vdebug.h" +#include "vline.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +void VPath::VPathData::transform(const VMatrix &m) +{ + for (auto &i : m_points) { + i = m.map(i); + } + mLengthDirty = true; +} + +float VPath::VPathData::length() const +{ + if (!mLengthDirty) return mLength; + + mLengthDirty = false; + mLength = 0.0; + + size_t i = 0; + for (auto e : m_elements) { + switch (e) { + case VPath::Element::MoveTo: + i++; + break; + case VPath::Element::LineTo: { + mLength += VLine(m_points[i - 1], m_points[i]).length(); + i++; + break; + } + case VPath::Element::CubicTo: { + mLength += VBezier::fromPoints(m_points[i - 1], m_points[i], + m_points[i + 1], m_points[i + 2]) + .length(); + i += 3; + break; + } + case VPath::Element::Close: + break; + } + } + + return mLength; +} + +void VPath::VPathData::checkNewSegment() +{ + if (mNewSegment) { + moveTo(0, 0); + mNewSegment = false; + } +} + +void VPath::VPathData::moveTo(float x, float y) +{ + mStartPoint = {x, y}; + mNewSegment = false; + m_elements.emplace_back(VPath::Element::MoveTo); + m_points.emplace_back(x, y); + m_segments++; + mLengthDirty = true; +} + +void VPath::VPathData::lineTo(float x, float y) +{ + checkNewSegment(); + m_elements.emplace_back(VPath::Element::LineTo); + m_points.emplace_back(x, y); + mLengthDirty = true; +} + +void VPath::VPathData::cubicTo(float cx1, float cy1, float cx2, float cy2, + float ex, float ey) +{ + checkNewSegment(); + m_elements.emplace_back(VPath::Element::CubicTo); + m_points.emplace_back(cx1, cy1); + m_points.emplace_back(cx2, cy2); + m_points.emplace_back(ex, ey); + mLengthDirty = true; +} + +void VPath::VPathData::close() +{ + if (empty()) return; + + const VPointF &lastPt = m_points.back(); + if (!fuzzyCompare(mStartPoint, lastPt)) { + lineTo(mStartPoint.x(), mStartPoint.y()); + } + m_elements.push_back(VPath::Element::Close); + mNewSegment = true; + mLengthDirty = true; +} + +void VPath::VPathData::reset() +{ + if (empty()) return; + + m_elements.clear(); + m_points.clear(); + m_segments = 0; + mLength = 0; + mLengthDirty = false; +} + +size_t VPath::VPathData::segments() const +{ + return m_segments; +} + +void VPath::VPathData::reserve(size_t pts, size_t elms) +{ + if (m_points.capacity() < m_points.size() + pts) + m_points.reserve(m_points.size() + pts); + if (m_elements.capacity() < m_elements.size() + elms) + m_elements.reserve(m_elements.size() + elms); +} + +static VPointF curvesForArc(const VRectF &, float, float, VPointF *, size_t *); +static constexpr float PATH_KAPPA = 0.5522847498f; +static constexpr float K_PI = 3.141592f; + +void VPath::VPathData::arcTo(const VRectF &rect, float startAngle, + float sweepLength, bool forceMoveTo) +{ + size_t point_count = 0; + VPointF pts[15]; + VPointF curve_start = + curvesForArc(rect, startAngle, sweepLength, pts, &point_count); + + reserve(point_count + 1, point_count / 3 + 1); + if (empty() || forceMoveTo) { + moveTo(curve_start.x(), curve_start.y()); + } else { + lineTo(curve_start.x(), curve_start.y()); + } + for (size_t i = 0; i < point_count; i += 3) { + cubicTo(pts[i].x(), pts[i].y(), pts[i + 1].x(), pts[i + 1].y(), + pts[i + 2].x(), pts[i + 2].y()); + } +} + +void VPath::VPathData::addCircle(float cx, float cy, float radius, + VPath::Direction dir) +{ + addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir); +} + +void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir) +{ + if (rect.empty()) return; + + float x = rect.x(); + float y = rect.y(); + + float w = rect.width(); + float w2 = rect.width() / 2; + float w2k = w2 * PATH_KAPPA; + + float h = rect.height(); + float h2 = rect.height() / 2; + float h2k = h2 * PATH_KAPPA; + + reserve(13, 6); // 1Move + 4Cubic + 1Close + if (dir == VPath::Direction::CW) { + // moveto 12 o'clock. + moveTo(x + w2, y); + // 12 -> 3 o'clock + cubicTo(x + w2 + w2k, y, x + w, y + h2 - h2k, x + w, y + h2); + // 3 -> 6 o'clock + cubicTo(x + w, y + h2 + h2k, x + w2 + w2k, y + h, x + w2, y + h); + // 6 -> 9 o'clock + cubicTo(x + w2 - w2k, y + h, x, y + h2 + h2k, x, y + h2); + // 9 -> 12 o'clock + cubicTo(x, y + h2 - h2k, x + w2 - w2k, y, x + w2, y); + } else { + // moveto 12 o'clock. + moveTo(x + w2, y); + // 12 -> 9 o'clock + cubicTo(x + w2 - w2k, y, x, y + h2 - h2k, x, y + h2); + // 9 -> 6 o'clock + cubicTo(x, y + h2 + h2k, x + w2 - w2k, y + h, x + w2, y + h); + // 6 -> 3 o'clock + cubicTo(x + w2 + w2k, y + h, x + w, y + h2 + h2k, x + w, y + h2); + // 3 -> 12 o'clock + cubicTo(x + w, y + h2 - h2k, x + w2 + w2k, y, x + w2, y); + } + close(); +} + +void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir) +{ + if (rect.empty()) return; + + float x = rect.x(); + float y = rect.y(); + float w = rect.width(); + float h = rect.height(); + + reserve(5, 6); // 1Move + 4Line + 1Close + if (dir == VPath::Direction::CW) { + moveTo(x + w, y); + lineTo(x + w, y + h); + lineTo(x, y + h); + lineTo(x, y); + close(); + } else { + moveTo(x + w, y); + lineTo(x, y); + lineTo(x, y + h); + lineTo(x + w, y + h); + close(); + } +} + +void VPath::VPathData::addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir) +{ + if (2 * roundness > rect.width()) roundness = rect.width() / 2.0f; + if (2 * roundness > rect.height()) roundness = rect.height() / 2.0f; + addRoundRect(rect, roundness, roundness, dir); +} + +void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir) +{ + if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) { + addRect(rect, dir); + return; + } + + float x = rect.x(); + float y = rect.y(); + float w = rect.width(); + float h = rect.height(); + // clamp the rx and ry radius value. + rx = 2 * rx; + ry = 2 * ry; + if (rx > w) rx = w; + if (ry > h) ry = h; + + reserve(17, 10); // 1Move + 4Cubic + 1Close + if (dir == VPath::Direction::CW) { + moveTo(x + w, y + ry / 2.f); + arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false); + arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false); + arcTo(VRectF(x, y, rx, ry), -180, -90, false); + arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false); + close(); + } else { + moveTo(x + w, y + ry / 2.f); + arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false); + arcTo(VRectF(x, y, rx, ry), 90, 90, false); + arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false); + arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false); + close(); + } +} + +static float tForArcAngle(float angle); +void findEllipseCoords(const VRectF &r, float angle, float length, + VPointF *startPoint, VPointF *endPoint) +{ + if (r.empty()) { + if (startPoint) *startPoint = VPointF(); + if (endPoint) *endPoint = VPointF(); + return; + } + + float w2 = r.width() / 2; + float h2 = r.height() / 2; + + float angles[2] = {angle, angle + length}; + VPointF *points[2] = {startPoint, endPoint}; + + for (int i = 0; i < 2; ++i) { + if (!points[i]) continue; + + float theta = angles[i] - 360 * floorf(angles[i] / 360); + float t = theta / 90; + // truncate + int quadrant = int(t); + t -= quadrant; + + t = tForArcAngle(90 * t); + + // swap x and y? + if (quadrant & 1) t = 1 - t; + + float a, b, c, d; + VBezier::coefficients(t, a, b, c, d); + VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA); + + // left quadrants + if (quadrant == 1 || quadrant == 2) p.rx() = -p.x(); + + // top quadrants + if (quadrant == 0 || quadrant == 1) p.ry() = -p.y(); + + *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y()); + } +} + +static float tForArcAngle(float angle) +{ + float radians, cos_angle, sin_angle, tc, ts, t; + + if (vCompare(angle, 0.f)) return 0; + if (vCompare(angle, 90.0f)) return 1; + + radians = (angle / 180) * K_PI; + + cos_angle = cosf(radians); + sin_angle = sinf(radians); + + // initial guess + tc = angle / 90; + + // do some iterations of newton's method to approximate cos_angle + // finds the zero of the function b.pointAt(tc).x() - cos_angle + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - + cos_angle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * + tc); // derivative + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - + cos_angle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * + tc); // derivative + + // initial guess + ts = tc; + // do some iterations of newton's method to approximate sin_angle + // finds the zero of the function b.pointAt(tc).y() - sin_angle + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + + 3 * PATH_KAPPA) * + ts - + sin_angle) / + (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + + 3 * PATH_KAPPA); + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + + 3 * PATH_KAPPA) * + ts - + sin_angle) / + (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + + 3 * PATH_KAPPA); + + // use the average of the t that best approximates cos_angle + // and the t that best approximates sin_angle + t = 0.5f * (tc + ts); + return t; +} + +// The return value is the starting point of the arc +static VPointF curvesForArc(const VRectF &rect, float startAngle, + float sweepLength, VPointF *curves, + size_t *point_count) +{ + if (rect.empty()) { + return {}; + } + + float x = rect.x(); + float y = rect.y(); + + float w = rect.width(); + float w2 = rect.width() / 2; + float w2k = w2 * PATH_KAPPA; + + float h = rect.height(); + float h2 = rect.height() / 2; + float h2k = h2 * PATH_KAPPA; + + VPointF points[16] = { + // start point + VPointF(x + w, y + h2), + + // 0 -> 270 degrees + VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h), + VPointF(x + w2, y + h), + + // 270 -> 180 degrees + VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k), + VPointF(x, y + h2), + + // 180 -> 90 degrees + VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y), + + // 90 -> 0 degrees + VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k), + VPointF(x + w, y + h2)}; + + if (sweepLength > 360) + sweepLength = 360; + else if (sweepLength < -360) + sweepLength = -360; + + // Special case fast paths + if (startAngle == 0.0f) { + if (vCompare(sweepLength, 360)) { + for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i]; + return points[12]; + } else if (vCompare(sweepLength, -360)) { + for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i]; + return points[0]; + } + } + + int startSegment = int(floorf(startAngle / 90.0f)); + int endSegment = int(floorf((startAngle + sweepLength) / 90.0f)); + + float startT = (startAngle - startSegment * 90) / 90; + float endT = (startAngle + sweepLength - endSegment * 90) / 90; + + int delta = sweepLength > 0 ? 1 : -1; + if (delta < 0) { + startT = 1 - startT; + endT = 1 - endT; + } + + // avoid empty start segment + if (vIsZero(startT - float(1))) { + startT = 0; + startSegment += delta; + } + + // avoid empty end segment + if (vIsZero(endT)) { + endT = 1; + endSegment -= delta; + } + + startT = tForArcAngle(startT * 90); + endT = tForArcAngle(endT * 90); + + const bool splitAtStart = !vIsZero(startT); + const bool splitAtEnd = !vIsZero(endT - float(1)); + + const int end = endSegment + delta; + + // empty arc? + if (startSegment == end) { + const int quadrant = 3 - ((startSegment % 4) + 4) % 4; + const int j = 3 * quadrant; + return delta > 0 ? points[j + 3] : points[j]; + } + + VPointF startPoint, endPoint; + findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint); + + for (int i = startSegment; i != end; i += delta) { + const int quadrant = 3 - ((i % 4) + 4) % 4; + const int j = 3 * quadrant; + + VBezier b; + if (delta > 0) + b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], + points[j]); + else + b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2], + points[j + 3]); + + // empty arc? + if (startSegment == endSegment && vCompare(startT, endT)) + return startPoint; + + if (i == startSegment) { + if (i == endSegment && splitAtEnd) + b = b.onInterval(startT, endT); + else if (splitAtStart) + b = b.onInterval(startT, 1); + } else if (i == endSegment && splitAtEnd) { + b = b.onInterval(0, endT); + } + + // push control points + curves[(*point_count)++] = b.pt2(); + curves[(*point_count)++] = b.pt3(); + curves[(*point_count)++] = b.pt4(); + } + + curves[*(point_count)-1] = endPoint; + + return startPoint; +} + +void VPath::VPathData::addPolystar(float points, float innerRadius, + float outerRadius, float innerRoundness, + float outerRoundness, float startAngle, + float cx, float cy, VPath::Direction dir) +{ + const static float POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; + float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; + float x; + float y; + float partialPointRadius = 0; + float anglePerPoint = (2.0f * K_PI / points); + float halfAnglePerPoint = anglePerPoint / 2.0f; + float partialPointAmount = points - floorf(points); + bool longSegment = false; + size_t numPoints = size_t(ceilf(points) * 2); + float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f); + bool hasRoundness = false; + + innerRoundness /= 100.0f; + outerRoundness /= 100.0f; + + if (!vCompare(partialPointAmount, 0)) { + currentAngle += + halfAnglePerPoint * (1.0f - partialPointAmount) * angleDir; + } + + if (!vCompare(partialPointAmount, 0)) { + partialPointRadius = + innerRadius + partialPointAmount * (outerRadius - innerRadius); + x = partialPointRadius * cosf(currentAngle); + y = partialPointRadius * sinf(currentAngle); + currentAngle += anglePerPoint * partialPointAmount / 2.0f * angleDir; + } else { + x = outerRadius * cosf(currentAngle); + y = outerRadius * sinf(currentAngle); + currentAngle += halfAnglePerPoint * angleDir; + } + + if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) { + reserve(numPoints + 2, numPoints + 3); + } else { + reserve(numPoints * 3 + 2, numPoints + 3); + hasRoundness = true; + } + + moveTo(x + cx, y + cy); + + for (size_t i = 0; i < numPoints; i++) { + float radius = longSegment ? outerRadius : innerRadius; + float dTheta = halfAnglePerPoint; + if (!vIsZero(partialPointRadius) && i == numPoints - 2) { + dTheta = anglePerPoint * partialPointAmount / 2.0f; + } + if (!vIsZero(partialPointRadius) && i == numPoints - 1) { + radius = partialPointRadius; + } + float previousX = x; + float previousY = y; + x = radius * cosf(currentAngle); + y = radius * sinf(currentAngle); + + if (hasRoundness) { + float cp1Theta = + (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); + float cp1Dx = cosf(cp1Theta); + float cp1Dy = sinf(cp1Theta); + float cp2Theta = (atan2f(y, x) - K_PI / 2.0f * angleDir); + float cp2Dx = cosf(cp2Theta); + float cp2Dy = sinf(cp2Theta); + + float cp1Roundness = longSegment ? innerRoundness : outerRoundness; + float cp2Roundness = longSegment ? outerRoundness : innerRoundness; + float cp1Radius = longSegment ? innerRadius : outerRadius; + float cp2Radius = longSegment ? outerRadius : innerRadius; + + float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * + cp1Dx / points; + float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER * + cp1Dy / points; + float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * + cp2Dx / points; + float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER * + cp2Dy / points; + + if (!vIsZero(partialPointAmount) && + ((i == 0) || (i == numPoints - 1))) { + cp1x *= partialPointAmount; + cp1y *= partialPointAmount; + cp2x *= partialPointAmount; + cp2y *= partialPointAmount; + } + + cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, + y + cp2y + cy, x + cx, y + cy); + } else { + lineTo(x + cx, y + cy); + } + + currentAngle += dTheta * angleDir; + longSegment = !longSegment; + } + + close(); +} + +void VPath::VPathData::addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir) +{ + // TODO: Need to support floating point number for number of points + const static float POLYGON_MAGIC_NUMBER = 0.25; + float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f; + float x; + float y; + float anglePerPoint = 2.0f * K_PI / floorf(points); + size_t numPoints = size_t(floorf(points)); + float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f); + bool hasRoundness = false; + + roundness /= 100.0f; + + currentAngle = (currentAngle - 90.0f) * K_PI / 180.0f; + x = radius * cosf(currentAngle); + y = radius * sinf(currentAngle); + currentAngle += anglePerPoint * angleDir; + + if (vIsZero(roundness)) { + reserve(numPoints + 2, numPoints + 3); + } else { + reserve(numPoints * 3 + 2, numPoints + 3); + hasRoundness = true; + } + + moveTo(x + cx, y + cy); + + for (size_t i = 0; i < numPoints; i++) { + float previousX = x; + float previousY = y; + x = (radius * cosf(currentAngle)); + y = (radius * sinf(currentAngle)); + + if (hasRoundness) { + float cp1Theta = + (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir); + float cp1Dx = cosf(cp1Theta); + float cp1Dy = sinf(cp1Theta); + float cp2Theta = atan2f(y, x) - K_PI / 2.0f * angleDir; + float cp2Dx = cosf(cp2Theta); + float cp2Dy = sinf(cp2Theta); + + float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx; + float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy; + float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx; + float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy; + + cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx, + y + cp2y + cy, x, y); + } else { + lineTo(x + cx, y + cy); + } + + currentAngle += anglePerPoint * angleDir; + } + + close(); +} + +void VPath::VPathData::addPath(const VPathData &path, const VMatrix *m) +{ + size_t segment = path.segments(); + + // make sure enough memory available + if (m_points.capacity() < m_points.size() + path.m_points.size()) + m_points.reserve(m_points.size() + path.m_points.size()); + + if (m_elements.capacity() < m_elements.size() + path.m_elements.size()) + m_elements.reserve(m_elements.size() + path.m_elements.size()); + + if (m) { + for (const auto &i : path.m_points) { + m_points.push_back(m->map(i)); + } + } else { + std::copy(path.m_points.begin(), path.m_points.end(), + back_inserter(m_points)); + } + + std::copy(path.m_elements.begin(), path.m_elements.end(), + back_inserter(m_elements)); + + m_segments += segment; + mLengthDirty = true; +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.h new file mode 100644 index 0000000000..90c2c3e53f --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpath.h @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VPATH_H +#define VPATH_H +#include +#include "vcowptr.h" +#include "vmatrix.h" +#include "vpoint.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +struct VPathData; +class VPath { +public: + enum class Direction { CCW, CW }; + + enum class Element : uchar { MoveTo, LineTo, CubicTo, Close }; + bool empty() const; + bool null() const; + void moveTo(const VPointF &p); + void moveTo(float x, float y); + void lineTo(const VPointF &p); + void lineTo(float x, float y); + void cubicTo(const VPointF &c1, const VPointF &c2, const VPointF &e); + void cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, + float ey); + void arcTo(const VRectF &rect, float startAngle, float sweepLength, + bool forceMoveTo); + void close(); + void reset(); + void reserve(size_t pts, size_t elms); + size_t segments() const; + void addCircle(float cx, float cy, float radius, + VPath::Direction dir = Direction::CW); + void addOval(const VRectF &rect, VPath::Direction dir = Direction::CW); + void addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir = Direction::CW); + void addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir = Direction::CW); + void addRect(const VRectF &rect, VPath::Direction dir = Direction::CW); + void addPolystar(float points, float innerRadius, float outerRadius, + float innerRoundness, float outerRoundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPath(const VPath &path); + void addPath(const VPath &path, const VMatrix &m); + void transform(const VMatrix &m); + float length() const; + const std::vector &elements() const; + const std::vector & points() const; + void clone(const VPath &srcPath); + bool unique() const { return d.unique();} + size_t refCount() const { return d.refCount();} + +private: + struct VPathData { + bool empty() const { return m_elements.empty(); } + bool null() const { return empty() && !m_elements.capacity();} + void moveTo(float x, float y); + void lineTo(float x, float y); + void cubicTo(float cx1, float cy1, float cx2, float cy2, float ex, float ey); + void close(); + void reset(); + void reserve(size_t, size_t); + void checkNewSegment(); + size_t segments() const; + void transform(const VMatrix &m); + float length() const; + void addRoundRect(const VRectF &, float, float, VPath::Direction); + void addRoundRect(const VRectF &, float, VPath::Direction); + void addRect(const VRectF &, VPath::Direction); + void arcTo(const VRectF &, float, float, bool); + void addCircle(float, float, float, VPath::Direction); + void addOval(const VRectF &, VPath::Direction); + void addPolystar(float points, float innerRadius, float outerRadius, + float innerRoundness, float outerRoundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir = Direction::CW); + void addPath(const VPathData &path, const VMatrix *m = nullptr); + void clone(const VPath::VPathData &o) { *this = o;} + const std::vector &elements() const + { + return m_elements; + } + const std::vector &points() const { return m_points; } + std::vector m_points; + std::vector m_elements; + size_t m_segments; + VPointF mStartPoint; + mutable float mLength{0}; + mutable bool mLengthDirty{true}; + bool mNewSegment; + }; + + vcow_ptr d; +}; + +inline bool VPath::empty() const +{ + return d->empty(); +} + +/* + * path is empty as well as null(no memory for data allocated yet). + */ +inline bool VPath::null() const +{ + return d->null(); +} + +inline void VPath::moveTo(const VPointF &p) +{ + d.write().moveTo(p.x(), p.y()); +} + +inline void VPath::lineTo(const VPointF &p) +{ + d.write().lineTo(p.x(), p.y()); +} + +inline void VPath::close() +{ + d.write().close(); +} + +inline void VPath::reset() +{ + d.write().reset(); +} + +inline void VPath::reserve(size_t pts, size_t elms) +{ + d.write().reserve(pts, elms); +} + +inline size_t VPath::segments() const +{ + return d->segments(); +} + +inline float VPath::length() const +{ + return d->length(); +} + +inline void VPath::cubicTo(const VPointF &c1, const VPointF &c2, + const VPointF &e) +{ + d.write().cubicTo(c1.x(), c1.y(), c2.x(), c2.y(), e.x(), e.y()); +} + +inline void VPath::lineTo(float x, float y) +{ + d.write().lineTo(x, y); +} + +inline void VPath::moveTo(float x, float y) +{ + d.write().moveTo(x, y); +} + +inline void VPath::cubicTo(float c1x, float c1y, float c2x, float c2y, float ex, + float ey) +{ + d.write().cubicTo(c1x, c1y, c2x, c2y, ex, ey); +} + +inline void VPath::transform(const VMatrix &m) +{ + d.write().transform(m); +} + +inline void VPath::arcTo(const VRectF &rect, float startAngle, + float sweepLength, bool forceMoveTo) +{ + d.write().arcTo(rect, startAngle, sweepLength, forceMoveTo); +} + +inline void VPath::addRect(const VRectF &rect, VPath::Direction dir) +{ + d.write().addRect(rect, dir); +} + +inline void VPath::addRoundRect(const VRectF &rect, float rx, float ry, + VPath::Direction dir) +{ + d.write().addRoundRect(rect, rx, ry, dir); +} + +inline void VPath::addRoundRect(const VRectF &rect, float roundness, + VPath::Direction dir) +{ + d.write().addRoundRect(rect, roundness, dir); +} + +inline void VPath::addCircle(float cx, float cy, float radius, + VPath::Direction dir) +{ + d.write().addCircle(cx, cy, radius, dir); +} + +inline void VPath::addOval(const VRectF &rect, VPath::Direction dir) +{ + d.write().addOval(rect, dir); +} + +inline void VPath::addPolystar(float points, float innerRadius, + float outerRadius, float innerRoundness, + float outerRoundness, float startAngle, float cx, + float cy, VPath::Direction dir) +{ + d.write().addPolystar(points, innerRadius, outerRadius, innerRoundness, + outerRoundness, startAngle, cx, cy, dir); +} + +inline void VPath::addPolygon(float points, float radius, float roundness, + float startAngle, float cx, float cy, + VPath::Direction dir) +{ + d.write().addPolygon(points, radius, roundness, startAngle, cx, cy, dir); +} + +inline void VPath::addPath(const VPath &path) +{ + if (path.empty()) return; + + if (null()) { + *this = path; + } else { + d.write().addPath(path.d.read()); + } +} + +inline void VPath::addPath(const VPath &path, const VMatrix &m) +{ + if (path.empty()) return; + + d.write().addPath(path.d.read(), &m); +} + +inline const std::vector &VPath::elements() const +{ + return d->elements(); +} + +inline const std::vector &VPath::points() const +{ + return d->points(); +} + +inline void VPath::clone(const VPath &o) +{ + d.write().clone(o.d.read()); +} + +V_END_NAMESPACE + +#endif // VPATH_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.cpp new file mode 100644 index 0000000000..5d44987ca5 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vpathmesure.h" +#include +#include "vbezier.h" +#include "vdasher.h" + +V_BEGIN_NAMESPACE + +/* + * start and end value must be normalized to [0 - 1] + * Path mesure trims the path from [start --> end] + * if start > end it treates as a loop and trims as two segment + * [0-->end] and [start --> 1] + */ +VPath VPathMesure::trim(const VPath &path) +{ + if (vCompare(mStart, mEnd)) return VPath(); + + if ((vCompare(mStart, 0.0f) && (vCompare(mEnd, 1.0f))) || + (vCompare(mStart, 1.0f) && (vCompare(mEnd, 0.0f)))) + return path; + + float length = path.length(); + + if (mStart < mEnd) { + float array[4] = { + 0.0f, length * mStart, // 1st segment + (mEnd - mStart) * length, + std::numeric_limits::max(), // 2nd segment + }; + VDasher dasher(array, 4); + dasher.dashed(path, mScratchObject); + return mScratchObject; + } else { + float array[4] = { + length * mEnd, (mStart - mEnd) * length, // 1st segment + (1 - mStart) * length, + std::numeric_limits::max(), // 2nd segment + }; + VDasher dasher(array, 4); + dasher.dashed(path, mScratchObject); + return mScratchObject; + } +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.h new file mode 100644 index 0000000000..2a4adb9769 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpathmesure.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VPATHMESURE_H +#define VPATHMESURE_H + +#include "vpath.h" + +V_BEGIN_NAMESPACE + +class VPathMesure { +public: + void setRange(float start, float end) {mStart = start; mEnd = end;} + void setStart(float start){mStart = start;} + void setEnd(float end){mEnd = end;} + VPath trim(const VPath &path); +private: + float mStart{0.0f}; + float mEnd{1.0f}; + VPath mScratchObject; +}; + +V_END_NAMESPACE + +#endif // VPATHMESURE_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpoint.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpoint.h new file mode 100644 index 0000000000..da1bb2f550 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vpoint.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef VPOINT_H +#define VPOINT_H + +#include "vglobal.h" + +V_BEGIN_NAMESPACE + +class VPointF { +public: + VPointF() = default; + constexpr inline VPointF(float x, float y) noexcept : mx(x), my(y) {} + constexpr inline float x() const noexcept { return mx; } + constexpr inline float y() const noexcept { return my; } + inline float & rx() noexcept { return mx; } + inline float & ry() noexcept { return my; } + inline void setX(float x) { mx = x; } + inline void setY(float y) { my = y; } + inline VPointF operator-() noexcept { return {-mx, -my}; } + inline VPointF & operator+=(const VPointF &p) noexcept; + inline VPointF & operator-=(const VPointF &p) noexcept; + friend const VPointF operator+(const VPointF &p1, const VPointF &p2) + { + return VPointF(p1.mx + p2.mx, p1.my + p2.my); + } + inline friend bool fuzzyCompare(const VPointF &p1, const VPointF &p2); + inline friend VDebug & operator<<(VDebug &os, const VPointF &o); + + friend inline VPointF operator-(const VPointF &p1, const VPointF &p2); + friend inline const VPointF operator*(const VPointF &, float); + friend inline const VPointF operator*(float, const VPointF &); + friend inline const VPointF operator/(const VPointF &, float); + friend inline const VPointF operator/(float, const VPointF &); + +private: + float mx{0}; + float my{0}; +}; + +inline bool fuzzyCompare(const VPointF &p1, const VPointF &p2) +{ + return (vCompare(p1.mx, p2.mx) && vCompare(p1.my, p2.my)); +} + +inline VPointF operator-(const VPointF &p1, const VPointF &p2) +{ + return {p1.mx - p2.mx, p1.my - p2.my}; +} + +inline const VPointF operator*(const VPointF &p, float c) +{ + return VPointF(p.mx * c, p.my * c); +} + +inline const VPointF operator*(float c, const VPointF &p) +{ + return VPointF(p.mx * c, p.my * c); +} + +inline const VPointF operator/(const VPointF &p, float c) +{ + return VPointF(p.mx / c, p.my / c); +} + +inline const VPointF operator/(float c, const VPointF &p) +{ + return VPointF(p.mx / c, p.my / c); +} + +inline VDebug &operator<<(VDebug &os, const VPointF &o) +{ + os << "{P " << o.x() << "," << o.y() << "}"; + return os; +} + +inline VPointF &VPointF::operator+=(const VPointF &p) noexcept +{ + mx += p.mx; + my += p.my; + return *this; +} + +inline VPointF &VPointF::operator-=(const VPointF &p) noexcept +{ + mx -= p.mx; + my -= p.my; + return *this; +} + +class VPoint { +public: + VPoint() = default; + constexpr inline VPoint(int x, int y) noexcept : mx(x), my(y) {} + constexpr inline int x() const noexcept { return mx; } + constexpr inline int y() const noexcept { return my; } + inline void setX(int x) { mx = x; } + inline void setY(int y) { my = y; } + inline VPoint & operator+=(const VPoint &p) noexcept; + inline VPoint & operator-=(const VPoint &p) noexcept; + constexpr inline bool operator==(const VPoint &o) const; + constexpr inline bool operator!=(const VPoint &o) const + { + return !(operator==(o)); + } + friend inline VPoint operator-(const VPoint &p1, const VPoint &p2); + inline friend VDebug &operator<<(VDebug &os, const VPoint &o); + +private: + int mx{0}; + int my{0}; +}; +inline VDebug &operator<<(VDebug &os, const VPoint &o) +{ + os << "{P " << o.x() << "," << o.y() << "}"; + return os; +} + +inline VPoint operator-(const VPoint &p1, const VPoint &p2) +{ + return {p1.mx - p2.mx, p1.my - p2.my}; +} + +constexpr inline bool VPoint::operator==(const VPoint &o) const +{ + return (mx == o.x() && my == o.y()); +} + +inline VPoint &VPoint::operator+=(const VPoint &p) noexcept +{ + mx += p.mx; + my += p.my; + return *this; +} + +inline VPoint &VPoint::operator-=(const VPoint &p) noexcept +{ + mx -= p.mx; + my -= p.my; + return *this; +} + +class VSize { +public: + VSize() = default; + constexpr inline VSize(int w, int h) noexcept : mw(w), mh(h) {} + bool empty() const {return (mw <= 0 || mh <= 0);} + constexpr inline int width() const noexcept { return mw; } + constexpr inline int height() const noexcept { return mh; } + inline void setWidth(int w) { mw = w; } + inline void setHeight(int h) { mh = h; } + inline VSize & operator+=(const VSize &p) noexcept; + inline VSize & operator-=(const VSize &p) noexcept; + constexpr inline bool operator==(const VSize &o) const; + constexpr inline bool operator!=(const VSize &o) const + { + return !(operator==(o)); + } + inline friend VDebug &operator<<(VDebug &os, const VSize &o); + +private: + int mw{0}; + int mh{0}; +}; +inline VDebug &operator<<(VDebug &os, const VSize &o) +{ + os << "{P " << o.width() << "," << o.height() << "}"; + return os; +} +constexpr inline bool VSize::operator==(const VSize &o) const +{ + return (mw == o.width() && mh == o.height()); +} + +inline VSize &VSize::operator+=(const VSize &p) noexcept +{ + mw += p.mw; + mh += p.mh; + return *this; +} + +inline VSize &VSize::operator-=(const VSize &p) noexcept +{ + mw -= p.mw; + mh -= p.mh; + return *this; +} + +V_END_NAMESPACE + +#endif // VPOINT_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.cpp new file mode 100644 index 0000000000..062431532b --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.cpp @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "vraster.h" +#include +#include +#include +#include "config.h" +#include "v_ft_raster.h" +#include "v_ft_stroker.h" +#include "vdebug.h" +#include "vmatrix.h" +#include "vpath.h" +#include "vrle.h" + +V_BEGIN_NAMESPACE + +template +class dyn_array { +public: + explicit dyn_array(size_t size) + : mCapacity(size), mData(std::make_unique(mCapacity)) + { + } + void reserve(size_t size) + { + if (mCapacity > size) return; + mCapacity = size; + mData = std::make_unique(mCapacity); + } + T * data() const { return mData.get(); } + dyn_array &operator=(dyn_array &&) noexcept = delete; + +private: + size_t mCapacity{0}; + std::unique_ptr mData{nullptr}; +}; + +struct FTOutline { +public: + void reset(); + void grow(size_t, size_t); + void convert(const VPath &path); + void convert(CapStyle, JoinStyle, float, float); + void moveTo(const VPointF &pt); + void lineTo(const VPointF &pt); + void cubicTo(const VPointF &ctr1, const VPointF &ctr2, const VPointF end); + void close(); + void end(); + void transform(const VMatrix &m); + SW_FT_Pos TO_FT_COORD(float x) + { + return SW_FT_Pos(x * 64); + } // to freetype 26.6 coordinate. + SW_FT_Outline ft; + bool closed{false}; + SW_FT_Stroker_LineCap ftCap; + SW_FT_Stroker_LineJoin ftJoin; + SW_FT_Fixed ftWidth; + SW_FT_Fixed ftMiterLimit; + dyn_array mPointMemory{100}; + dyn_array mTagMemory{100}; + dyn_array mContourMemory{10}; + dyn_array mContourFlagMemory{10}; +}; + +void FTOutline::reset() +{ + ft.n_points = ft.n_contours = 0; + ft.flags = 0x0; +} + +void FTOutline::grow(size_t points, size_t segments) +{ + reset(); + mPointMemory.reserve(points + segments); + mTagMemory.reserve(points + segments); + mContourMemory.reserve(segments); + mContourFlagMemory.reserve(segments); + + ft.points = mPointMemory.data(); + ft.tags = mTagMemory.data(); + ft.contours = mContourMemory.data(); + ft.contours_flag = mContourFlagMemory.data(); +} + +void FTOutline::convert(const VPath &path) +{ + const std::vector &elements = path.elements(); + const std::vector & points = path.points(); + + grow(points.size(), path.segments()); + + size_t index = 0; + for (auto element : elements) { + switch (element) { + case VPath::Element::MoveTo: + moveTo(points[index]); + index++; + break; + case VPath::Element::LineTo: + lineTo(points[index]); + index++; + break; + case VPath::Element::CubicTo: + cubicTo(points[index], points[index + 1], points[index + 2]); + index = index + 3; + break; + case VPath::Element::Close: + close(); + break; + } + } + end(); +} + +void FTOutline::convert(CapStyle cap, JoinStyle join, float width, + float miterLimit) +{ + // map strokeWidth to freetype. It uses as the radius of the pen not the + // diameter + width = width / 2.0f; + // convert to freetype co-ordinate + // IMP: stroker takes radius in 26.6 co-ordinate + ftWidth = SW_FT_Fixed(width * (1 << 6)); + // IMP: stroker takes meterlimit in 16.16 co-ordinate + ftMiterLimit = SW_FT_Fixed(miterLimit * (1 << 16)); + + // map to freetype capstyle + switch (cap) { + case CapStyle::Square: + ftCap = SW_FT_STROKER_LINECAP_SQUARE; + break; + case CapStyle::Round: + ftCap = SW_FT_STROKER_LINECAP_ROUND; + break; + default: + ftCap = SW_FT_STROKER_LINECAP_BUTT; + break; + } + switch (join) { + case JoinStyle::Bevel: + ftJoin = SW_FT_STROKER_LINEJOIN_BEVEL; + break; + case JoinStyle::Round: + ftJoin = SW_FT_STROKER_LINEJOIN_ROUND; + break; + default: + ftJoin = SW_FT_STROKER_LINEJOIN_MITER_FIXED; + break; + } +} + +void FTOutline::moveTo(const VPointF &pt) +{ + assert(ft.n_points <= SHRT_MAX - 1); + + ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); + ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + if (ft.n_points) { + ft.contours[ft.n_contours] = ft.n_points - 1; + ft.n_contours++; + } + // mark the current contour as open + // will be updated if ther is a close tag at the end. + ft.contours_flag[ft.n_contours] = 1; + + ft.n_points++; +} + +void FTOutline::lineTo(const VPointF &pt) +{ + assert(ft.n_points <= SHRT_MAX - 1); + + ft.points[ft.n_points].x = TO_FT_COORD(pt.x()); + ft.points[ft.n_points].y = TO_FT_COORD(pt.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} + +void FTOutline::cubicTo(const VPointF &cp1, const VPointF &cp2, + const VPointF ep) +{ + assert(ft.n_points <= SHRT_MAX - 3); + + ft.points[ft.n_points].x = TO_FT_COORD(cp1.x()); + ft.points[ft.n_points].y = TO_FT_COORD(cp1.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; + ft.n_points++; + + ft.points[ft.n_points].x = TO_FT_COORD(cp2.x()); + ft.points[ft.n_points].y = TO_FT_COORD(cp2.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_CUBIC; + ft.n_points++; + + ft.points[ft.n_points].x = TO_FT_COORD(ep.x()); + ft.points[ft.n_points].y = TO_FT_COORD(ep.y()); + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} +void FTOutline::close() +{ + assert(ft.n_points <= SHRT_MAX - 1); + + // mark the contour as a close path. + ft.contours_flag[ft.n_contours] = 0; + + int index; + if (ft.n_contours) { + index = ft.contours[ft.n_contours - 1] + 1; + } else { + index = 0; + } + + // make sure atleast 1 point exists in the segment. + if (ft.n_points == index) { + closed = false; + return; + } + + ft.points[ft.n_points].x = ft.points[index].x; + ft.points[ft.n_points].y = ft.points[index].y; + ft.tags[ft.n_points] = SW_FT_CURVE_TAG_ON; + ft.n_points++; +} + +void FTOutline::end() +{ + assert(ft.n_contours <= SHRT_MAX - 1); + + if (ft.n_points) { + ft.contours[ft.n_contours] = ft.n_points - 1; + ft.n_contours++; + } +} + +static void rleGenerationCb(int count, const SW_FT_Span *spans, void *user) +{ + VRle *rle = static_cast(user); + auto *rleSpan = reinterpret_cast(spans); + rle->addSpan(rleSpan, count); +} + +static void bboxCb(int x, int y, int w, int h, void *user) +{ + VRle *rle = static_cast(user); + rle->setBoundingRect({x, y, w, h}); +} + +class SharedRle { +public: + SharedRle() = default; + VRle &unsafe() { return _rle; } + void notify() + { + { + std::lock_guard lock(_mutex); + _ready = true; + } + _cv.notify_one(); + } + void wait() + { + if (!_pending) return; + + { + std::unique_lock lock(_mutex); + while (!_ready) _cv.wait(lock); + } + + _pending = false; + } + + VRle &get() + { + wait(); + return _rle; + } + + void reset() + { + wait(); + _ready = false; + _pending = true; + } + +private: + VRle _rle; + std::mutex _mutex; + std::condition_variable _cv; + bool _ready{true}; + bool _pending{false}; +}; + +struct VRleTask { + SharedRle mRle; + VPath mPath; + float mStrokeWidth; + float mMiterLimit; + VRect mClip; + FillRule mFillRule; + CapStyle mCap; + JoinStyle mJoin; + bool mGenerateStroke; + + VRle &rle() { return mRle.get(); } + + void update(VPath path, FillRule fillRule, const VRect &clip) + { + mRle.reset(); + mPath = std::move(path); + mFillRule = fillRule; + mClip = clip; + mGenerateStroke = false; + } + + void update(VPath path, CapStyle cap, JoinStyle join, float width, + float miterLimit, const VRect &clip) + { + mRle.reset(); + mPath = std::move(path); + mCap = cap; + mJoin = join; + mStrokeWidth = width; + mMiterLimit = miterLimit; + mClip = clip; + mGenerateStroke = true; + } + void render(FTOutline &outRef) + { + SW_FT_Raster_Params params; + + mRle.unsafe().reset(); + + params.flags = SW_FT_RASTER_FLAG_DIRECT | SW_FT_RASTER_FLAG_AA; + params.gray_spans = &rleGenerationCb; + params.bbox_cb = &bboxCb; + params.user = &mRle.unsafe(); + params.source = &outRef.ft; + + if (!mClip.empty()) { + params.flags |= SW_FT_RASTER_FLAG_CLIP; + + params.clip_box.xMin = mClip.left(); + params.clip_box.yMin = mClip.top(); + params.clip_box.xMax = mClip.right(); + params.clip_box.yMax = mClip.bottom(); + } + // compute rle + sw_ft_grays_raster.raster_render(nullptr, ¶ms); + } + + void operator()(FTOutline &outRef, SW_FT_Stroker &stroker) + { + if (mPath.points().size() > SHRT_MAX || + mPath.points().size() + mPath.segments() > SHRT_MAX) { + return; + } + + if (mGenerateStroke) { // Stroke Task + outRef.convert(mPath); + outRef.convert(mCap, mJoin, mStrokeWidth, mMiterLimit); + + uint points, contors; + + SW_FT_Stroker_Set(stroker, outRef.ftWidth, outRef.ftCap, + outRef.ftJoin, outRef.ftMiterLimit); + SW_FT_Stroker_ParseOutline(stroker, &outRef.ft); + SW_FT_Stroker_GetCounts(stroker, &points, &contors); + + outRef.grow(points, contors); + + SW_FT_Stroker_Export(stroker, &outRef.ft); + + } else { // Fill Task + outRef.convert(mPath); + int fillRuleFlag = SW_FT_OUTLINE_NONE; + switch (mFillRule) { + case FillRule::EvenOdd: + fillRuleFlag = SW_FT_OUTLINE_EVEN_ODD_FILL; + break; + default: + fillRuleFlag = SW_FT_OUTLINE_NONE; + break; + } + outRef.ft.flags = fillRuleFlag; + } + + render(outRef); + + mPath = VPath(); + + mRle.notify(); + } +}; + +using VTask = std::shared_ptr; + +#ifdef LOTTIE_THREAD_SUPPORT + +#include +#include "vtaskqueue.h" + +class RleTaskScheduler { + const unsigned _count{std::thread::hardware_concurrency()}; + std::vector _threads; + std::vector> _q{_count}; + std::atomic _index{0}; + + void run(unsigned i) + { + /* + * initalize per thread objects. + */ + FTOutline outlineRef; + SW_FT_Stroker stroker; + SW_FT_Stroker_New(&stroker); + + // Task Loop + VTask task; + while (true) { + bool success = false; + + for (unsigned n = 0; n != _count * 2; ++n) { + if (_q[(i + n) % _count].try_pop(task)) { + success = true; + break; + } + } + + if (!success && !_q[i].pop(task)) break; + + (*task)(outlineRef, stroker); + } + + // cleanup + SW_FT_Stroker_Done(stroker); + } + + RleTaskScheduler() + { + for (unsigned n = 0; n != _count; ++n) { + _threads.emplace_back([&, n] { run(n); }); + } + } + +public: + static RleTaskScheduler &instance() + { + static RleTaskScheduler singleton; + return singleton; + } + + ~RleTaskScheduler() + { + for (auto &e : _q) e.done(); + + for (auto &e : _threads) e.join(); + } + + void process(VTask task) + { + auto i = _index++; + + for (unsigned n = 0; n != _count; ++n) { + if (_q[(i + n) % _count].try_push(std::move(task))) return; + } + + if (_count > 0) { + _q[i % _count].push(std::move(task)); + } + } +}; + +#else + +class RleTaskScheduler { +public: + FTOutline outlineRef{}; + SW_FT_Stroker stroker; + +public: + static RleTaskScheduler &instance() + { + static RleTaskScheduler singleton; + return singleton; + } + + RleTaskScheduler() { SW_FT_Stroker_New(&stroker); } + + ~RleTaskScheduler() { SW_FT_Stroker_Done(stroker); } + + void process(VTask task) { (*task)(outlineRef, stroker); } +}; +#endif + +struct VRasterizer::VRasterizerImpl { + VRleTask mTask; + + VRle & rle() { return mTask.rle(); } + VRleTask &task() { return mTask; } +}; + +VRle VRasterizer::rle() +{ + if (!d) return VRle(); + return d->rle(); +} + +void VRasterizer::init() +{ + if (!d) d = std::make_shared(); +} + +void VRasterizer::updateRequest() +{ + VTask taskObj = VTask(d, &d->task()); + RleTaskScheduler::instance().process(std::move(taskObj)); +} + +void VRasterizer::rasterize(VPath path, FillRule fillRule, const VRect &clip) +{ + init(); + if (path.empty()) { + d->rle().reset(); + return; + } + d->task().update(std::move(path), fillRule, clip); + updateRequest(); +} + +void VRasterizer::rasterize(VPath path, CapStyle cap, JoinStyle join, + float width, float miterLimit, const VRect &clip) +{ + init(); + if (path.empty() || vIsZero(width)) { + d->rle().reset(); + return; + } + d->task().update(std::move(path), cap, join, width, miterLimit, clip); + updateRequest(); +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.h new file mode 100644 index 0000000000..fec84135b1 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vraster.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VRASTER_H +#define VRASTER_H +#include +#include "vglobal.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +class VPath; +class VRle; + +class VRasterizer +{ +public: + void rasterize(VPath path, FillRule fillRule = FillRule::Winding, const VRect &clip = VRect()); + void rasterize(VPath path, CapStyle cap, JoinStyle join, float width, + float miterLimit, const VRect &clip = VRect()); + VRle rle(); +private: + struct VRasterizerImpl; + void init(); + void updateRequest(); + std::shared_ptr d{nullptr}; +}; + +V_END_NAMESPACE + +#endif // VRASTER_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.cpp new file mode 100644 index 0000000000..ff219c67de --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vrect.h" +#include + +VRect VRect::operator&(const VRect &r) const +{ + if (empty()) return VRect(); + + int l1 = x1; + int r1 = x1; + if (x2 - x1 + 1 < 0) + l1 = x2; + else + r1 = x2; + + int l2 = r.x1; + int r2 = r.x1; + if (r.x2 - r.x1 + 1 < 0) + l2 = r.x2; + else + r2 = r.x2; + + if (l1 > r2 || l2 > r1) return VRect(); + + int t1 = y1; + int b1 = y1; + if (y2 - y1 + 1 < 0) + t1 = y2; + else + b1 = y2; + + int t2 = r.y1; + int b2 = r.y1; + if (r.y2 - r.y1 + 1 < 0) + t2 = r.y2; + else + b2 = r.y2; + + if (t1 > b2 || t2 > b1) return VRect(); + + VRect tmp; + tmp.x1 = std::max(l1, l2); + tmp.x2 = std::min(r1, r2); + tmp.y1 = std::max(t1, t2); + tmp.y2 = std::min(b1, b2); + return tmp; +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.h new file mode 100644 index 0000000000..f1c673e1ab --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrect.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VRECT_H +#define VRECT_H +#include "vglobal.h" +#include "vpoint.h" + +V_BEGIN_NAMESPACE +class VRectF; + +class VRect { +public: + VRect() = default; + VRect(int x, int y, int w, int h):x1(x),y1(y),x2(x+w),y2(y+h){} + explicit VRect(VPoint pt, VSize sz):VRect(pt.x(), pt.y(), sz.width(), sz.height()){} + operator VRectF() const; + V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} + V_CONSTEXPR int left() const {return x1;} + V_CONSTEXPR int top() const {return y1;} + V_CONSTEXPR int right() const {return x2;} + V_CONSTEXPR int bottom() const {return y2;} + V_CONSTEXPR int width() const {return x2-x1;} + V_CONSTEXPR int height() const {return y2-y1;} + V_CONSTEXPR int x() const {return x1;} + V_CONSTEXPR int y() const {return y1;} + VSize size() const {return {width(), height()};} + void setLeft(int l) { x1 = l; } + void setTop(int t) { y1 = t; } + void setRight(int r) { x2 = r; } + void setBottom(int b) { y2 = b; } + void setWidth(int w) { x2 = x1 + w; } + void setHeight(int h) { y2 = y1 + h; } + VRect translated(int dx, int dy) const; + void translate(int dx, int dy); + bool contains(const VRect &r, bool proper = false) const; + bool intersects(const VRect &r); + friend V_CONSTEXPR inline bool operator==(const VRect &, + const VRect &) noexcept; + friend V_CONSTEXPR inline bool operator!=(const VRect &, + const VRect &) noexcept; + friend VDebug & operator<<(VDebug &os, const VRect &o); + + VRect intersected(const VRect &r) const; + VRect operator&(const VRect &r) const; + +private: + int x1{0}; + int y1{0}; + int x2{0}; + int y2{0}; +}; + +inline VRect VRect::intersected(const VRect &r) const +{ + return *this & r; +} + +inline bool VRect::intersects(const VRect &r) +{ + return (right() > r.left() && left() < r.right() && bottom() > r.top() && + top() < r.bottom()); +} + +inline VDebug &operator<<(VDebug &os, const VRect &o) +{ + os << "{R " << o.x() << "," << o.y() << "," << o.width() << "," + << o.height() << "}"; + return os; +} +V_CONSTEXPR inline bool operator==(const VRect &r1, const VRect &r2) noexcept +{ + return r1.x1 == r2.x1 && r1.x2 == r2.x2 && r1.y1 == r2.y1 && r1.y2 == r2.y2; +} + +V_CONSTEXPR inline bool operator!=(const VRect &r1, const VRect &r2) noexcept +{ + return r1.x1 != r2.x1 || r1.x2 != r2.x2 || r1.y1 != r2.y1 || r1.y2 != r2.y2; +} + +inline VRect VRect::translated(int dx, int dy) const +{ + return {x1 + dx, y1 + dy, x2 - x1, y2 - y1}; +} + +inline void VRect::translate(int dx, int dy) +{ + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; +} + +inline bool VRect::contains(const VRect &r, bool proper) const +{ + return proper ? + ((x1 < r.x1) && (x2 > r.x2) && (y1 < r.y1) && (y2 > r.y2)) : + ((x1 <= r.x1) && (x2 >= r.x2) && (y1 <= r.y1) && (y2 >= r.y2)); +} + +class VRectF { +public: + VRectF() = default; + + VRectF(double x, double y, double w, double h): + x1(float(x)),y1(float(y)), + x2(float(x+w)),y2(float(y+h)){} + operator VRect() const { + return {int(left()), int(right()), int(width()), int(height())}; + } + + V_CONSTEXPR bool empty() const {return x1 >= x2 || y1 >= y2;} + V_CONSTEXPR float left() const {return x1;} + V_CONSTEXPR float top() const {return y1;} + V_CONSTEXPR float right() const {return x2;} + V_CONSTEXPR float bottom() const {return y2;} + V_CONSTEXPR float width() const {return x2-x1;} + V_CONSTEXPR float height() const {return y2-y1;} + V_CONSTEXPR float x() const {return x1;} + V_CONSTEXPR float y() const {return y1;} + V_CONSTEXPR inline VPointF center() const + { + return {x1 + (x2 - x1) / 2.f, y1 + (y2 - y1) / 2.f}; + } + void setLeft(float l) { x1 = l; } + void setTop(float t) { y1 = t; } + void setRight(float r) { x2 = r; } + void setBottom(float b) { y2 = b; } + void setWidth(float w) { x2 = x1 + w; } + void setHeight(float h) { y2 = y1 + h; } + void translate(float dx, float dy) + { + x1 += dx; + y1 += dy; + x2 += dx; + y2 += dy; + } + +private: + float x1{0}; + float y1{0}; + float x2{0}; + float y2{0}; +}; + +inline VRect::operator VRectF() const +{ + return {double(left()), double(right()), double(width()), double(height())}; +} + +V_END_NAMESPACE + +#endif // VRECT_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.cpp new file mode 100644 index 0000000000..c7a520f598 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.cpp @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "vrle.h" +#include +#include +#include +#include +#include +#include +#include "vdebug.h" +#include "vglobal.h" + +V_BEGIN_NAMESPACE + +using Result = std::array; +using rle_view = VRle::View; +static size_t _opGeneric(rle_view &a, rle_view &b, Result &result, + VRle::Data::Op op); +static size_t _opIntersect(const VRect &, rle_view &, Result &); +static size_t _opIntersect(rle_view &, rle_view &, Result &); + +static inline uchar divBy255(int x) +{ + return (x + (x >> 8) + 0x80) >> 8; +} + +inline static void copy(const VRle::Span *span, size_t count, + std::vector &v) +{ + // make sure enough memory available + if (v.capacity() < v.size() + count) v.reserve(v.size() + count); + std::copy(span, span + count, back_inserter(v)); +} + +void VRle::Data::addSpan(const VRle::Span *span, size_t count) +{ + copy(span, count, mSpans); + mBboxDirty = true; +} + +VRect VRle::Data::bbox() const +{ + updateBbox(); + return mBbox; +} + +void VRle::Data::setBbox(const VRect &bbox) const +{ + mBboxDirty = false; + mBbox = bbox; +} + +void VRle::Data::reset() +{ + mSpans.clear(); + mBbox = VRect(); + mOffset = VPoint(); + mBboxDirty = false; +} + +void VRle::Data::clone(const VRle::Data &o) +{ + *this = o; +} + +void VRle::Data::translate(const VPoint &p) +{ + // take care of last offset if applied + mOffset = p - mOffset; + int x = mOffset.x(); + int y = mOffset.y(); + for (auto &i : mSpans) { + i.x = i.x + x; + i.y = i.y + y; + } + updateBbox(); + mBbox.translate(mOffset.x(), mOffset.y()); +} + +void VRle::Data::addRect(const VRect &rect) +{ + int x = rect.left(); + int y = rect.top(); + int width = rect.width(); + int height = rect.height(); + + mSpans.reserve(size_t(height)); + + VRle::Span span; + for (int i = 0; i < height; i++) { + span.x = x; + span.y = y + i; + span.len = width; + span.coverage = 255; + mSpans.push_back(span); + } + mBbox = rect; +} + +void VRle::Data::updateBbox() const +{ + if (!mBboxDirty) return; + + mBboxDirty = false; + + int l = std::numeric_limits::max(); + const VRle::Span *span = mSpans.data(); + + mBbox = VRect(); + size_t sz = mSpans.size(); + if (sz) { + int t = span[0].y; + int b = span[sz - 1].y; + int r = 0; + for (size_t i = 0; i < sz; i++) { + if (span[i].x < l) l = span[i].x; + if (span[i].x + span[i].len > r) r = span[i].x + span[i].len; + } + mBbox = VRect(l, t, r - l, b - t + 1); + } +} + +void VRle::Data::operator*=(uchar alpha) +{ + for (auto &i : mSpans) { + i.coverage = divBy255(i.coverage * alpha); + } +} + +void VRle::Data::opIntersect(const VRect &r, VRle::VRleSpanCb cb, + void *userData) const +{ + if (empty()) return; + + if (r.contains(bbox())) { + cb(mSpans.size(), mSpans.data(), userData); + return; + } + + auto obj = view(); + Result result; + // run till all the spans are processed + while (obj.size()) { + auto count = _opIntersect(r, obj, result); + if (count) cb(count, result.data(), userData); + } +} + +// res = a - b; +void VRle::Data::opSubstract(const VRle::Data &aObj, const VRle::Data &bObj) +{ + // if two rle are disjoint + if (!aObj.bbox().intersects(bObj.bbox())) { + mSpans = aObj.mSpans; + } else { + auto a = aObj.view(); + auto b = bObj.view(); + + auto aPtr = a.data(); + auto aEnd = a.data() + a.size(); + auto bPtr = b.data(); + auto bEnd = b.data() + b.size(); + + // 1. forward a till it intersects with b + while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; + auto count = aPtr - a.data(); + if (count) copy(a.data(), count, mSpans); + + // 2. forward b till it intersects with a + if (aPtr != aEnd) + while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; + + // update a and b object + a = {aPtr, size_t(aEnd - aPtr)}; + b = {bPtr, size_t(bEnd - bPtr)}; + + // 3. calculate the intersect region + Result result; + + // run till all the spans are processed + while (a.size() && b.size()) { + auto count = _opGeneric(a, b, result, Op::Substract); + if (count) copy(result.data(), count, mSpans); + } + + // 4. copy the rest of a + if (a.size()) copy(a.data(), a.size(), mSpans); + } + + mBboxDirty = true; +} + +void VRle::Data::opGeneric(const VRle::Data &aObj, const VRle::Data &bObj, + Op op) +{ + // This routine assumes, obj1(span_y) < obj2(span_y). + + auto a = aObj.view(); + auto b = bObj.view(); + + // reserve some space for the result vector. + mSpans.reserve(a.size() + b.size()); + + // if two rle are disjoint + if (!aObj.bbox().intersects(aObj.bbox())) { + if (a.data()[0].y < b.data()[0].y) { + copy(a.data(), a.size(), mSpans); + copy(b.data(), b.size(), mSpans); + } else { + copy(b.data(), b.size(), mSpans); + copy(a.data(), a.size(), mSpans); + } + } else { + auto aPtr = a.data(); + auto aEnd = a.data() + a.size(); + auto bPtr = b.data(); + auto bEnd = b.data() + b.size(); + + // 1. forward a till it intersects with b + while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; + + auto count = aPtr - a.data(); + if (count) copy(a.data(), count, mSpans); + + // 2. forward b till it intersects with a + if (aPtr != aEnd) + while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; + + count = bPtr - b.data(); + if (count) copy(b.data(), count, mSpans); + + // update a and b object + a = {aPtr, size_t(aEnd - aPtr)}; + b = {bPtr, size_t(bEnd - bPtr)}; + + // 3. calculate the intersect region + Result result; + + // run till all the spans are processed + while (a.size() && b.size()) { + auto count = _opGeneric(a, b, result, op); + if (count) copy(result.data(), count, mSpans); + } + // 3. copy the rest + if (b.size()) copy(b.data(), b.size(), mSpans); + if (a.size()) copy(a.data(), a.size(), mSpans); + } + + mBboxDirty = true; +} + +static inline V_ALWAYS_INLINE void _opIntersectPrepare(VRle::View &a, + VRle::View &b) +{ + auto aPtr = a.data(); + auto aEnd = a.data() + a.size(); + auto bPtr = b.data(); + auto bEnd = b.data() + b.size(); + + // 1. advance a till it intersects with b + while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++; + + // 2. advance b till it intersects with a + if (aPtr != aEnd) + while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++; + + // update a and b object + a = {aPtr, size_t(aEnd - aPtr)}; + b = {bPtr, size_t(bEnd - bPtr)}; +} + +void VRle::Data::opIntersect(VRle::View a, VRle::View b) +{ + _opIntersectPrepare(a, b); + Result result; + while (a.size()) { + auto count = _opIntersect(a, b, result); + if (count) copy(result.data(), count, mSpans); + } + + updateBbox(); +} + +static void _opIntersect(rle_view a, rle_view b, VRle::VRleSpanCb cb, + void *userData) +{ + if (!cb) return; + + _opIntersectPrepare(a, b); + Result result; + while (a.size()) { + auto count = _opIntersect(a, b, result); + if (count) cb(count, result.data(), userData); + } +} + +/* + * This function will clip a rle list with another rle object + * tmp_clip : The rle list that will be use to clip the rle + * tmp_obj : holds the list of spans that has to be clipped + * result : will hold the result after the processing + * NOTE: if the algorithm runs out of the result buffer list + * it will stop and update the tmp_obj with the span list + * that are yet to be processed as well as the tpm_clip object + * with the unprocessed clip spans. + */ + +static size_t _opIntersect(rle_view &obj, rle_view &clip, Result &result) +{ + auto out = result.data(); + auto available = result.max_size(); + auto spans = obj.data(); + auto end = obj.data() + obj.size(); + auto clipSpans = clip.data(); + auto clipEnd = clip.data() + clip.size(); + int sx1, sx2, cx1, cx2, x, len; + + while (available && spans < end) { + if (clipSpans >= clipEnd) { + spans = end; + break; + } + if (clipSpans->y > spans->y) { + ++spans; + continue; + } + if (spans->y != clipSpans->y) { + ++clipSpans; + continue; + } + // assert(spans->y == (clipSpans->y + clip_offset_y)); + sx1 = spans->x; + sx2 = sx1 + spans->len; + cx1 = clipSpans->x; + cx2 = cx1 + clipSpans->len; + + if (cx1 < sx1 && cx2 < sx1) { + ++clipSpans; + continue; + } else if (sx1 < cx1 && sx2 < cx1) { + ++spans; + continue; + } + x = std::max(sx1, cx1); + len = std::min(sx2, cx2) - x; + if (len) { + out->x = std::max(sx1, cx1); + out->len = (std::min(sx2, cx2) - out->x); + out->y = spans->y; + out->coverage = divBy255(spans->coverage * clipSpans->coverage); + ++out; + --available; + } + if (sx2 < cx2) { + ++spans; + } else { + ++clipSpans; + } + } + + // update the obj view yet to be processed + obj = {spans, size_t(end - spans)}; + + // update the clip view yet to be processed + clip = {clipSpans, size_t(clipEnd - clipSpans)}; + + return result.max_size() - available; +} + +/* + * This function will clip a rle list with a given rect + * clip : The clip rect that will be use to clip the rle + * tmp_obj : holds the list of spans that has to be clipped + * result : will hold the result after the processing + * NOTE: if the algorithm runs out of the result buffer list + * it will stop and update the tmp_obj with the span list + * that are yet to be processed + */ +static size_t _opIntersect(const VRect &clip, rle_view &obj, Result &result) +{ + auto out = result.data(); + auto available = result.max_size(); + auto ptr = obj.data(); + auto end = obj.data() + obj.size(); + + const auto minx = clip.left(); + const auto miny = clip.top(); + const auto maxx = clip.right() - 1; + const auto maxy = clip.bottom() - 1; + + while (available && ptr < end) { + const auto &span = *ptr; + if (span.y > maxy) { + ptr = end; // update spans so that we can breakout + break; + } + if (span.y < miny || span.x > maxx || span.x + span.len <= minx) { + ++ptr; + continue; + } + if (span.x < minx) { + out->len = std::min(span.len - (minx - span.x), maxx - minx + 1); + out->x = minx; + } else { + out->x = span.x; + out->len = std::min(span.len, ushort(maxx - span.x + 1)); + } + if (out->len != 0) { + out->y = span.y; + out->coverage = span.coverage; + ++out; + --available; + } + ++ptr; + } + + // update the span list that yet to be processed + obj = {ptr, size_t(end - ptr)}; + + return result.max_size() - available; +} + +static void blitXor(VRle::Span *spans, int count, uchar *buffer, int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + int da = *ptr; + *ptr = divBy255((255 - spans->coverage) * (da) + + spans->coverage * (255 - da)); + ptr++; + } + spans++; + } +} + +static void blitDestinationOut(VRle::Span *spans, int count, uchar *buffer, + int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = divBy255((255 - spans->coverage) * (*ptr)); + ptr++; + } + spans++; + } +} + +static void blitSrcOver(VRle::Span *spans, int count, uchar *buffer, + int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = spans->coverage + divBy255((255 - spans->coverage) * (*ptr)); + ptr++; + } + spans++; + } +} + +void blitSrc(VRle::Span *spans, int count, uchar *buffer, int offsetX) +{ + while (count--) { + int x = spans->x + offsetX; + int l = spans->len; + uchar *ptr = buffer + x; + while (l--) { + *ptr = std::max(spans->coverage, *ptr); + ptr++; + } + spans++; + } +} + +size_t bufferToRle(uchar *buffer, int size, int offsetX, int y, VRle::Span *out) +{ + size_t count = 0; + uchar value = buffer[0]; + int curIndex = 0; + + // size = offsetX < 0 ? size + offsetX : size; + for (int i = 0; i < size; i++) { + uchar curValue = buffer[0]; + if (value != curValue) { + if (value) { + out->y = y; + out->x = offsetX + curIndex; + out->len = i - curIndex; + out->coverage = value; + out++; + count++; + } + curIndex = i; + value = curValue; + } + buffer++; + } + if (value) { + out->y = y; + out->x = offsetX + curIndex; + out->len = size - curIndex; + out->coverage = value; + count++; + } + return count; +} + +struct SpanMerger { + explicit SpanMerger(VRle::Data::Op op) + { + switch (op) { + case VRle::Data::Op::Add: + _blitter = &blitSrcOver; + break; + case VRle::Data::Op::Xor: + _blitter = &blitXor; + break; + case VRle::Data::Op::Substract: + _blitter = &blitDestinationOut; + break; + } + } + using blitter = void (*)(VRle::Span *, int, uchar *, int); + blitter _blitter; + std::array _result; + std::array _buffer; + VRle::Span * _aStart{nullptr}; + VRle::Span * _bStart{nullptr}; + + void revert(VRle::Span *&aPtr, VRle::Span *&bPtr) + { + aPtr = _aStart; + bPtr = _bStart; + } + VRle::Span *data() { return _result.data(); } + size_t merge(VRle::Span *&aPtr, const VRle::Span *aEnd, VRle::Span *&bPtr, + const VRle::Span *bEnd); +}; + +size_t SpanMerger::merge(VRle::Span *&aPtr, const VRle::Span *aEnd, + VRle::Span *&bPtr, const VRle::Span *bEnd) +{ + assert(aPtr->y == bPtr->y); + + _aStart = aPtr; + _bStart = bPtr; + int lb = std::min(aPtr->x, bPtr->x); + int y = aPtr->y; + + while (aPtr < aEnd && aPtr->y == y) aPtr++; + while (bPtr < bEnd && bPtr->y == y) bPtr++; + + int ub = std::max((aPtr - 1)->x + (aPtr - 1)->len, + (bPtr - 1)->x + (bPtr - 1)->len); + int length = (lb < 0) ? ub + lb : ub - lb; + + if (length <= 0 || size_t(length) >= _buffer.max_size()) { + // can't handle merge . skip + return 0; + } + + // clear buffer + memset(_buffer.data(), 0, length); + + // blit a to buffer + blitSrc(_aStart, aPtr - _aStart, _buffer.data(), -lb); + + // blit b to buffer + _blitter(_bStart, bPtr - _bStart, _buffer.data(), -lb); + + // convert buffer to span + return bufferToRle(_buffer.data(), length, lb, y, _result.data()); +} + +static size_t _opGeneric(rle_view &a, rle_view &b, Result &result, + VRle::Data::Op op) +{ + SpanMerger merger{op}; + + auto out = result.data(); + size_t available = result.max_size(); + auto aPtr = a.data(); + auto aEnd = a.data() + a.size(); + auto bPtr = b.data(); + auto bEnd = b.data() + b.size(); + + // only logic change for substract operation. + const bool keep = op != (VRle::Data::Op::Substract); + + while (available && aPtr < aEnd && bPtr < bEnd) { + if (aPtr->y < bPtr->y) { + *out++ = *aPtr++; + available--; + } else if (bPtr->y < aPtr->y) { + if (keep) { + *out++ = *bPtr; + available--; + } + bPtr++; + } else { // same y + auto count = merger.merge(aPtr, aEnd, bPtr, bEnd); + if (available >= count) { + if (count) { + memcpy(out, merger.data(), count * sizeof(VRle::Span)); + out += count; + available -= count; + } + } else { + // not enough space try next time. + merger.revert(aPtr, bPtr); + break; + } + } + } + // update the span list that yet to be processed + a = {aPtr, size_t(aEnd - aPtr)}; + b = {bPtr, size_t(bEnd - bPtr)}; + + return result.max_size() - available; +} + +/* + * this api makes use of thread_local temporary + * buffer to avoid creating intermediate temporary rle buffer + * the scratch buffer object will grow its size on demand + * so that future call won't need any more memory allocation. + * this function is thread safe as it uses thread_local variable + * which is unique per thread. + */ +static vthread_local VRle::Data Scratch_Object; + +VRle VRle::opGeneric(const VRle &o, Data::Op op) const +{ + if (empty()) return o; + if (o.empty()) return *this; + + Scratch_Object.reset(); + Scratch_Object.opGeneric(d.read(), o.d.read(), op); + + VRle result; + result.d.write() = Scratch_Object; + + return result; +} + +VRle VRle::operator-(const VRle &o) const +{ + if (empty()) return {}; + if (o.empty()) return *this; + + Scratch_Object.reset(); + Scratch_Object.opSubstract(d.read(), o.d.read()); + + VRle result; + result.d.write() = Scratch_Object; + + return result; +} + +VRle VRle::operator&(const VRle &o) const +{ + if (empty() || o.empty()) return {}; + + Scratch_Object.reset(); + Scratch_Object.opIntersect(d.read().view(), o.d.read().view()); + + VRle result; + result.d.write() = Scratch_Object; + + return result; +} + +void VRle::operator&=(const VRle &o) +{ + if (empty()) return; + if (o.empty()) { + reset(); + return; + } + Scratch_Object.reset(); + Scratch_Object.opIntersect(d.read().view(), o.d.read().view()); + d.write() = Scratch_Object; +} + +VRle operator-(const VRect &rect, const VRle &o) +{ + if (rect.empty()) return {}; + + Scratch_Object.reset(); + Scratch_Object.addRect(rect); + + VRle result; + result.d.write().opSubstract(Scratch_Object, o.d.read()); + + return result; +} + +VRle operator&(const VRect &rect, const VRle &o) +{ + if (rect.empty() || o.empty()) return {}; + + Scratch_Object.reset(); + Scratch_Object.addRect(rect); + + VRle result; + result.d.write().opIntersect(Scratch_Object.view(), o.d.read().view()); + + return result; +} + +void VRle::intersect(const VRle &clip, VRleSpanCb cb, void *userData) const +{ + if (empty() || clip.empty()) return; + + _opIntersect(d.read().view(), clip.d.read().view(), cb, userData); +} + +V_END_NAMESPACE diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.h new file mode 100644 index 0000000000..761e5d1bf3 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vrle.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VRLE_H +#define VRLE_H + +#include +#include "vcowptr.h" +#include "vglobal.h" +#include "vpoint.h" +#include "vrect.h" + +V_BEGIN_NAMESPACE + +class VRle { +public: + struct Span { + short x{0}; + short y{0}; + ushort len{0}; + uchar coverage{0}; + }; + using VRleSpanCb = void (*)(size_t count, const VRle::Span *spans, + void *userData); + bool empty() const { return d->empty(); } + VRect boundingRect() const { return d->bbox(); } + void setBoundingRect(const VRect &bbox) { d->setBbox(bbox); } + void addSpan(const VRle::Span *span, size_t count) + { + d.write().addSpan(span, count); + } + + void reset() { d.write().reset(); } + void translate(const VPoint &p) { d.write().translate(p); } + + void operator*=(uchar alpha) { d.write() *= alpha; } + + void intersect(const VRect &r, VRleSpanCb cb, void *userData) const; + void intersect(const VRle &rle, VRleSpanCb cb, void *userData) const; + + void operator&=(const VRle &o); + VRle operator&(const VRle &o) const; + VRle operator-(const VRle &o) const; + VRle operator+(const VRle &o) const { return opGeneric(o, Data::Op::Add); } + VRle operator^(const VRle &o) const { return opGeneric(o, Data::Op::Xor); } + + friend VRle operator-(const VRect &rect, const VRle &o); + friend VRle operator&(const VRect &rect, const VRle &o); + + bool unique() const { return d.unique(); } + size_t refCount() const { return d.refCount(); } + void clone(const VRle &o) { d.write().clone(o.d.read()); } + +public: + struct View { + Span * _data; + size_t _size; + View(const Span *data, size_t sz) : _data((Span *)data), _size(sz) {} + Span * data() { return _data; } + size_t size() { return _size; } + }; + struct Data { + enum class Op { Add, Xor, Substract }; + VRle::View view() const + { + return VRle::View(mSpans.data(), mSpans.size()); + } + bool empty() const { return mSpans.empty(); } + void addSpan(const VRle::Span *span, size_t count); + void updateBbox() const; + VRect bbox() const; + void setBbox(const VRect &bbox) const; + void reset(); + void translate(const VPoint &p); + void operator*=(uchar alpha); + void opGeneric(const VRle::Data &, const VRle::Data &, Op code); + void opSubstract(const VRle::Data &, const VRle::Data &); + void opIntersect(VRle::View a, VRle::View b); + void opIntersect(const VRect &, VRle::VRleSpanCb, void *) const; + void addRect(const VRect &rect); + void clone(const VRle::Data &); + + std::vector mSpans; + VPoint mOffset; + mutable VRect mBbox; + mutable bool mBboxDirty = true; + }; + +private: + VRle opGeneric(const VRle &o, Data::Op opcode) const; + + vcow_ptr d; +}; + +inline void VRle::intersect(const VRect &r, VRleSpanCb cb, void *userData) const +{ + d->opIntersect(r, cb, userData); +} + +V_END_NAMESPACE + +#endif // VRLE_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vsharedptr.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vsharedptr.h new file mode 100644 index 0000000000..fc0c419c96 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vsharedptr.h @@ -0,0 +1,123 @@ +#ifndef VSHAREDPTR_H +#define VSHAREDPTR_H + +#include +#include +#include + +template +class vshared_ptr { + struct model { + Rc mRef{1}; + + model() = default; + + template + explicit model(Args&&... args) : mValue(std::forward(args)...){} + explicit model(const T& other) : mValue(other){} + + T mValue; + }; + model* mModel{nullptr}; + +public: + using element_type = T; + + vshared_ptr() = default; + + ~vshared_ptr() + { + unref(); + } + + template + explicit vshared_ptr(Args&&... args) : mModel(new model(std::forward(args)...)) + { + } + + vshared_ptr(const vshared_ptr& x) noexcept : vshared_ptr() + { + if (x.mModel) { + mModel = x.mModel; + ++mModel->mRef; + } + } + + vshared_ptr(vshared_ptr&& x) noexcept : vshared_ptr() + { + if (x.mModel) { + mModel = x.mModel; + x.mModel = nullptr; + } + } + + auto operator=(const vshared_ptr& x) noexcept -> vshared_ptr& + { + unref(); + mModel = x.mModel; + ref(); + return *this; + } + + auto operator=(vshared_ptr&& x) noexcept -> vshared_ptr& + { + unref(); + mModel = x.mModel; + x.mModel = nullptr; + return *this; + } + + operator bool() const noexcept { + return mModel != nullptr; + } + + auto operator*() const noexcept -> element_type& { return read(); } + + auto operator-> () const noexcept -> element_type* { return &read(); } + + std::size_t refCount() const noexcept + { + assert(mModel); + + return mModel->mRef; + } + + bool unique() const noexcept + { + assert(mModel); + + return mModel->mRef == 1; + } + +private: + + auto read() const noexcept -> element_type& + { + assert(mModel); + + return mModel->mValue; + } + + void ref() + { + if (mModel) ++mModel->mRef; + } + + void unref() + { + if (mModel && (--mModel->mRef == 0)) { + delete mModel; + mModel = nullptr; + } + } +}; + +// atomic ref counted pointer implementation. +template < typename T> +using arc_ptr = vshared_ptr>; + +// ref counter pointer implementation. +template < typename T> +using rc_ptr = vshared_ptr; + +#endif // VSHAREDPTR_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vstackallocator.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vstackallocator.h new file mode 100644 index 0000000000..a305b7393c --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vstackallocator.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VSTACK_ALLOCATOR_H +#define VSTACK_ALLOCATOR_H + +#include +#include + +template +class arena +{ + alignas(alignment) char buf_[N]; + char* ptr_; + +public: + ~arena() {ptr_ = nullptr;} + arena() noexcept : ptr_(buf_) {} + arena(const arena&) = delete; + arena& operator=(const arena&) = delete; + + template char* allocate(std::size_t n); + void deallocate(char* p, std::size_t n) noexcept; + + static constexpr std::size_t size() noexcept {return N;} + std::size_t used() const noexcept {return static_cast(ptr_ - buf_);} + void reset() noexcept {ptr_ = buf_;} + +private: + static + std::size_t + align_up(std::size_t n) noexcept + {return (n + (alignment-1)) & ~(alignment-1);} + + bool + pointer_in_buffer(char* p) noexcept + {return buf_ <= p && p <= buf_ + N;} +}; + +template +template +char* +arena::allocate(std::size_t n) +{ + static_assert(ReqAlign <= alignment, "alignment is too small for this arena"); + assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); + auto const aligned_n = align_up(n); + if (static_cast(buf_ + N - ptr_) >= aligned_n) + { + char* r = ptr_; + ptr_ += aligned_n; + return r; + } + + static_assert(alignment <= alignof(std::max_align_t), "you've chosen an " + "alignment that is larger than alignof(std::max_align_t), and " + "cannot be guaranteed by normal operator new"); + return static_cast(::operator new(n)); +} + +template +void +arena::deallocate(char* p, std::size_t n) noexcept +{ + assert(pointer_in_buffer(ptr_) && "stack_alloc has outlived arena"); + if (pointer_in_buffer(p)) + { + n = align_up(n); + if (p + n == ptr_) + ptr_ = p; + } + else + ::operator delete(p); +} + +template +class stack_alloc +{ +public: + using value_type = T; + static auto constexpr alignment = Align; + static auto constexpr size = N; + using arena_type = arena; + +private: + arena_type& a_; + +public: + stack_alloc(const stack_alloc&) = default; + stack_alloc& operator=(const stack_alloc&) = delete; + + stack_alloc(arena_type& a) noexcept : a_(a) + { + static_assert(size % alignment == 0, + "size N needs to be a multiple of alignment Align"); + } + template + stack_alloc(const stack_alloc& a) noexcept + : a_(a.a_) {} + + template struct rebind {using other = stack_alloc<_Up, N, alignment>;}; + + T* allocate(std::size_t n) + { + return reinterpret_cast(a_.template allocate(n*sizeof(T))); + } + void deallocate(T* p, std::size_t n) noexcept + { + a_.deallocate(reinterpret_cast(p), n*sizeof(T)); + } + + template + friend + bool + operator==(const stack_alloc& x, const stack_alloc& y) noexcept; + + template friend class stack_alloc; +}; + +template +inline +bool +operator==(const stack_alloc& x, const stack_alloc& y) noexcept +{ + return N == M && A1 == A2 && &x.a_ == &y.a_; +} + +template +inline +bool +operator!=(const stack_alloc& x, const stack_alloc& y) noexcept +{ + return !(x == y); +} + +#endif // VSTACK_ALLOCATOR_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vtaskqueue.h b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vtaskqueue.h new file mode 100644 index 0000000000..e505c2f4c2 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/vector/vtaskqueue.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef VTASKQUEUE_H +#define VTASKQUEUE_H + +#include + +template +class TaskQueue { + using lock_t = std::unique_lock; + std::deque _q; + bool _done{false}; + std::mutex _mutex; + std::condition_variable _ready; + +public: + bool try_pop(Task &task) + { + lock_t lock{_mutex, std::try_to_lock}; + if (!lock || _q.empty()) return false; + task = std::move(_q.front()); + _q.pop_front(); + return true; + } + + bool try_push(Task &&task) + { + { + lock_t lock{_mutex, std::try_to_lock}; + if (!lock) return false; + _q.push_back(std::move(task)); + } + _ready.notify_one(); + return true; + } + + void done() + { + { + lock_t lock{_mutex}; + _done = true; + } + _ready.notify_all(); + } + + bool pop(Task &task) + { + lock_t lock{_mutex}; + while (_q.empty() && !_done) _ready.wait(lock); + if (_q.empty()) return false; + task = std::move(_q.front()); + _q.pop_front(); + return true; + } + + void push(Task &&task) + { + { + lock_t lock{_mutex}; + _q.push_back(std::move(task)); + } + _ready.notify_one(); + } + +}; + +#endif // VTASKQUEUE_H diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/wasm/rlottiewasm.cpp b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/wasm/rlottiewasm.cpp new file mode 100644 index 0000000000..4c759cc768 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieCore/rlottie/src/wasm/rlottiewasm.cpp @@ -0,0 +1,109 @@ +#include +#include + +#include "rlottie.h" + +#include + +using namespace emscripten; + +const char *resource = "{\"v\":\"5.1.8\",\"fr\":60,\"ip\":3,\"op\":140,\"w\":500,\"h\":500,\"nm\":\"ANUB\",\"ddd\":0,\"assets\":[],\"layers\":[{\"ddd\":0,\"ind\":1,\"ty\":4,\"nm\":\"a\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":-3.113,\"ix\":10},\"p\":{\"a\":0,\"k\":[327.214,315.758,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-17,77,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[-100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":1,\"k\":[{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-20.2,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-13.001,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-3.399,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":2.999,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":13.4,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":20.599,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":32.599,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":42.2,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":49.999,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":58.999,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":66.2,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":78.2,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":87.8,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":94.999,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":104.599,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":111.8,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":123.8,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":133.999,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"t\":138.99921875}],\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"st\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.133333333333,0.109803921569,1],\"ix\":3},\"o\":{\"a\":0,\"k\":100,\"ix\":4},\"w\":{\"a\":0,\"k\":15,\"ix\":5},\"lc\":2,\"lj\":1,\"ml\":4,\"nm\":\"Stroke 1\",\"mn\":\"ADBE Vector Graphic - Stroke\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-22,82],\"ix\":2},\"a\":{\"a\":0,\"k\":[-22,82],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":-3,\"op\":289,\"st\":-20.2,\"bm\":0},{\"ddd\":0,\"ind\":2,\"ty\":4,\"nm\":\"b\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":-6,\"ix\":10},\"p\":{\"a\":0,\"k\":[221.537,318.968,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-17,77,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":1,\"k\":[{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-2.4,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":3,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":14.401,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":21.6,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":31.2,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":43.2,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":50.401,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":60,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":67.2,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":76.799,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":88.799,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":96,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":105.6,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":112.799,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":122.401,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":132,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"t\":139.000390625}],\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"st\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.133333333333,0.109803921569,1],\"ix\":3},\"o\":{\"a\":0,\"k\":100,\"ix\":4},\"w\":{\"a\":0,\"k\":15,\"ix\":5},\"lc\":2,\"lj\":1,\"ml\":4,\"nm\":\"Stroke 1\",\"mn\":\"ADBE Vector Graphic - Stroke\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-22,82],\"ix\":2},\"a\":{\"a\":0,\"k\":[-22,82],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":288,\"st\":-2.4,\"bm\":0},{\"ddd\":0,\"ind\":3,\"ty\":4,\"nm\":\"c\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":-3.113,\"ix\":10},\"p\":{\"a\":0,\"k\":[361.652,319.398,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-17,77,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[-100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":1,\"k\":[{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-14.4,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-7.201,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-0.002,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-3.791,-1.166],[-4.083,-10.915]],\"o\":[[3.791,1.166],[3.33,8.322]],\"v\":[[-17,75],[5.704,158.168]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":3,\"s\":[{\"i\":[[-3.791,-1.166],[-4.083,-10.915]],\"o\":[[3.791,1.166],[3.33,8.322]],\"v\":[[-17,75],[5.704,158.168]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":11.998,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":21.6,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":28.799,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":38.399,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":45.6,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":57.6,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":67.2,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":74.399,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":83.998,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":91.2,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":103.2,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":112.799,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":119.998,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":130,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[-3.791,-1.166],[-4.083,-10.915]],\"o\":[[3.791,1.166],[3.33,8.322]],\"v\":[[-17,75],[5.704,158.168]],\"c\":false}]},{\"t\":139.000390625}],\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"st\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.133333333333,0.109803921569,1],\"ix\":3},\"o\":{\"a\":0,\"k\":100,\"ix\":4},\"w\":{\"a\":0,\"k\":15,\"ix\":5},\"lc\":2,\"lj\":1,\"ml\":4,\"nm\":\"Stroke 1\",\"mn\":\"ADBE Vector Graphic - Stroke\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-22,82],\"ix\":2},\"a\":{\"a\":0,\"k\":[-22,82],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":-20,\"op\":279,\"st\":-14.4,\"bm\":0},{\"ddd\":0,\"ind\":4,\"ty\":4,\"nm\":\"d\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":-6,\"ix\":10},\"p\":{\"a\":0,\"k\":[254.302,326.574,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-17,77,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":1,\"k\":[{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-28.8,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-21.601,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-11.999,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":-4.8,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":2.8,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":16.8,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":24.001,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":33.6,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":40.8,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":50.399,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":62.399,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":69.6,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":79.2,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":86.399,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":96.001,\"s\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}],\"e\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":106.001,\"s\":[{\"i\":[[-6.5,-2],[-4.5,-15.5]],\"o\":[[6.5,2],[3.209,11.054]],\"v\":[[-17,75],[18,154]],\"c\":false}],\"e\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":113.2,\"s\":[{\"i\":[[-4.161,-1.28],[50.642,-32.142]],\"o\":[[4.161,1.28],[-33.86,21.491]],\"v\":[[-17,75],[1.858,129.142]],\"c\":false}],\"e\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":121.001,\"s\":[{\"i\":[[0,0],[29.75,-14.5]],\"o\":[[0,0],[-29.75,14.5]],\"v\":[[-17,75],[-56.25,145.5]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":130.001,\"s\":[{\"i\":[[0,0],[-7.5,-34]],\"o\":[[0,0],[7.5,34]],\"v\":[[-17,75],[-61,161]],\"c\":false}],\"e\":[{\"i\":[[0,0],[-3.5,-4.5]],\"o\":[[0,0],[3.5,4.5]],\"v\":[[-17,75],[-11.5,164]],\"c\":false}]},{\"t\":139.00078125}],\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"st\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.133333333333,0.109803921569,1],\"ix\":3},\"o\":{\"a\":0,\"k\":100,\"ix\":4},\"w\":{\"a\":0,\"k\":15,\"ix\":5},\"lc\":2,\"lj\":1,\"ml\":4,\"nm\":\"Stroke 1\",\"mn\":\"ADBE Vector Graphic - Stroke\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-22,82],\"ix\":2},\"a\":{\"a\":0,\"k\":[-22,82],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":266,\"st\":-28.8,\"bm\":0},{\"ddd\":0,\"ind\":5,\"ty\":4,\"nm\":\"b2\",\"parent\":7,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.25],\"y\":[1]},\"o\":{\"x\":[0.75],\"y\":[0]},\"n\":[\"0p25_1_0p75_0\"],\"t\":0,\"s\":[-81],\"e\":[-97]},{\"i\":{\"x\":[0.25],\"y\":[1]},\"o\":{\"x\":[0.75],\"y\":[0]},\"n\":[\"0p25_1_0p75_0\"],\"t\":67.199,\"s\":[-97],\"e\":[-81]},{\"t\":136.80078125}],\"ix\":10},\"p\":{\"a\":0,\"k\":[-13.573,-61.514,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-6,48,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[73.171,73.171,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[82,82],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-6,48],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":6,\"ty\":4,\"nm\":\"b1\",\"parent\":7,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":-87,\"ix\":10},\"p\":{\"a\":0,\"k\":[-27.177,43.546,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-6,48,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":2,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[82,82],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-6,48],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 1\",\"np\":3,\"cix\":2,\"ix\":2,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":7,\"ty\":4,\"nm\":\"b\",\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":0,\"k\":83,\"ix\":10},\"p\":{\"a\":1,\"k\":[{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":5,\"s\":[355.857,302.207,0],\"e\":[355.857,308.207,0],\"to\":[0,1,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":16.801,\"s\":[355.857,308.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":33.6,\"s\":[355.857,302.207,0],\"e\":[355.857,308.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":50.4,\"s\":[355.857,308.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":67.199,\"s\":[355.857,302.207,0],\"e\":[355.857,308.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":84,\"s\":[355.857,308.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":100.801,\"s\":[355.857,302.207,0],\"e\":[355.857,308.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":120,\"s\":[355.857,308.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,1,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":139,\"s\":[355.857,302.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":180,\"s\":[355.857,302.207,0],\"e\":[355.857,308.207,0],\"to\":[0,1,0],\"ti\":[0,0,0]},{\"i\":{\"x\":0.833,\"y\":0.833},\"o\":{\"x\":0.167,\"y\":0.167},\"n\":\"0p833_0p833_0p167_0p167\",\"t\":196.801,\"s\":[355.857,308.207,0],\"e\":[355.857,302.207,0],\"to\":[0,0,0],\"ti\":[0,1,0]},{\"t\":216}],\"ix\":2},\"a\":{\"a\":0,\"k\":[-5.706,-66.449,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[5.021,11.991],[1.75,-16.75],[3.593,-23.13],[3.492,-8.369],[0,23.5]],\"o\":[[-6.242,-14.906],[-1.081,10.349],[-2.222,14.305],[-14.312,34.299],[0,-55.082]],\"v\":[[14.5,-63],[-40.44,-62.693],[-44.148,-3.184],[-56.34,28.612],[12.218,41.438]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":8,\"ty\":4,\"nm\":\"ne\",\"parent\":6,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":0,\"s\":[0],\"e\":[20]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":67.199,\"s\":[20],\"e\":[0]},{\"t\":134.400390625}],\"ix\":10},\"p\":{\"a\":0,\"k\":[-12,50,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-12,50,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[0.59,12.987],[1.75,-16.75],[-1.434,-15.164],[4.955,22.972]],\"o\":[[-1,-22],[-1.75,16.75],[3.5,37],[-11,-51]],\"v\":[[3.5,-51.5],[-31.25,-48.25],[-45,45],[12,36.5]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":9,\"ty\":4,\"nm\":\"ta\",\"parent\":5,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":0,\"s\":[7],\"e\":[75]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":22.801,\"s\":[75],\"e\":[7]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":45.6,\"s\":[7],\"e\":[75]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":68.4,\"s\":[75],\"e\":[7]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":91.199,\"s\":[7],\"e\":[75]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":114,\"s\":[75],\"e\":[7]},{\"t\":136.80078125}],\"ix\":10},\"p\":{\"a\":0,\"k\":[15.301,27.13,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[21.895,-104.814,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[136.667,136.667,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[-4.827,-5.636],[1.374,-48.171],[-2.647,2.438]],\"o\":[[33.061,38.596],[-0.068,2.375],[34.628,-76.569]],\"v\":[[-18.205,-190.23],[15.659,-106.249],[25.25,-102.254]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":10,\"ty\":4,\"nm\":\"el\",\"parent\":11,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":0,\"s\":[-17],\"e\":[41]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":62.4,\"s\":[41],\"e\":[-17]},{\"t\":129.599609375}],\"ix\":10},\"p\":{\"a\":0,\"k\":[-90.831,16.442,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[16.5,-109.52,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[140.26,140.26,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[-0.618,-7.395],[-2.323,-2.379],[-2.647,2.438]],\"o\":[[6.75,80.75],[1.66,1.7],[38.998,-68.667]],\"v\":[[-1.601,-210.907],[11.871,-104.272],[20.531,-99.66]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":11,\"ty\":4,\"nm\":\"hea\",\"parent\":8,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":0,\"s\":[-15],\"e\":[8]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":67.199,\"s\":[8],\"e\":[-15]},{\"t\":134.400390625}],\"ix\":10},\"p\":{\"a\":0,\"k\":[-7.5,-46.5,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[-110.468,94.987,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[71.296,71.296,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 2\",\"np\":2,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[14.727,19.286],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-175.409,43.844],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[59.914,100],\"ix\":3},\"r\":{\"a\":0,\"k\":-168.452,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 5\",\"np\":3,\"cix\":2,\"ix\":2,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[14.727,19.286],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-122.039,45.221],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 3\",\"np\":3,\"cix\":2,\"ix\":3,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[-10.966,2.431],[-1.181,-4.133],[1.601,-0.057],[-0.599,3.554]],\"o\":[[1.746,-0.387],[1.277,4.469],[-14.816,0.529],[0.406,-2.406]],\"v\":[[-8.421,-5.275],[2.908,3.703],[-0.161,10.282],[-18.683,3.926]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.933333333333,0.874509803922,0.650980392157,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"eye1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-181.299,40.461],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":38.4,\"s\":[100,92.381],\"e\":[100,-0.256]},{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":55.199,\"s\":[100,-0.256],\"e\":[100,-0.256]},{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":62.4,\"s\":[100,-0.256],\"e\":[100,92.381]},{\"t\":79.19921875}],\"ix\":3},\"r\":{\"a\":0,\"k\":-128.667,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 6\",\"np\":3,\"cix\":2,\"ix\":4,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[40.675,16.831],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.933333333333,0.874509803922,0.650980392157,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"eye2\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-123.091,45.195],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":38.4,\"s\":[100,92.381],\"e\":[100,-0.256]},{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":55.199,\"s\":[100,-0.256],\"e\":[100,-0.256]},{\"i\":{\"x\":[0.667,0.667],\"y\":[1,1]},\"o\":{\"x\":[0.333,0.333],\"y\":[0,0]},\"n\":[\"0p667_1_0p333_0\",\"0p667_1_0p333_0\"],\"t\":62.4,\"s\":[100,-0.256],\"e\":[100,92.381]},{\"t\":79.19921875}],\"ix\":3},\"r\":{\"a\":0,\"k\":-19.654,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 2\",\"np\":3,\"cix\":2,\"ix\":5,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[0,0],[10.344,0.175],[-115.817,6.255]],\"o\":[[0,0],[-7.047,-0.119],[34.894,-1.884]],\"v\":[[-167.279,58.669],[-254.234,57.818],[-149.783,98.993]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-162,72.234],\"ix\":2},\"a\":{\"a\":0,\"k\":[-164.104,75.74],\"ix\":1},\"s\":{\"a\":0,\"k\":[100.434,93.355],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":6,\"mn\":\"ADBE Vector Group\",\"hd\":false},{\"ty\":\"gr\",\"it\":[{\"d\":1,\"ty\":\"el\",\"s\":{\"a\":0,\"k\":[108,108],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,0],\"ix\":3},\"nm\":\"Ellipse Path 1\",\"mn\":\"ADBE Vector Shape - Ellipse\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[-128,48],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Ellipse 1\",\"np\":3,\"cix\":2,\"ix\":7,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0},{\"ddd\":0,\"ind\":12,\"ty\":4,\"nm\":\"er\",\"parent\":11,\"sr\":1,\"ks\":{\"o\":{\"a\":0,\"k\":100,\"ix\":11},\"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":7.199,\"s\":[-17],\"e\":[41]},{\"i\":{\"x\":[0.667],\"y\":[1]},\"o\":{\"x\":[0.333],\"y\":[0]},\"n\":[\"0p667_1_0p333_0\"],\"t\":67.199,\"s\":[41],\"e\":[-17]},{\"t\":134.400390625}],\"ix\":10},\"p\":{\"a\":0,\"k\":[-151.699,7.848,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[16.5,-109.52,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[131.332,131.332,100],\"ix\":6}},\"ao\":0,\"shapes\":[{\"ty\":\"gr\",\"it\":[{\"ind\":0,\"ty\":\"sh\",\"ix\":1,\"ks\":{\"a\":0,\"k\":{\"i\":[[-0.618,-7.395],[-2.323,-2.379],[-2.647,2.438]],\"o\":[[6.75,80.75],[1.66,1.7],[26.148,-64.813]],\"v\":[[-1.601,-210.907],[9.521,-107.006],[20.155,-104.319]],\"c\":true},\"ix\":2},\"nm\":\"Path 1\",\"mn\":\"ADBE Vector Shape - Group\",\"hd\":false},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0.149019607843,0.132484795065,0.111034221275,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"nm\":\"Fill 1\",\"mn\":\"ADBE Vector Graphic - Fill\",\"hd\":false},{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}],\"nm\":\"Shape 1\",\"np\":3,\"cix\":2,\"ix\":1,\"mn\":\"ADBE Vector Group\",\"hd\":false}],\"ip\":0,\"op\":141.6,\"st\":0,\"bm\":0}],\"markers\":[]}"; + +typedef unsigned char uint8_t; + +class __attribute__((visibility("default"))) RlottieWasm { +public: + static std::unique_ptr create() + { + return std::unique_ptr(new RlottieWasm(resource)); + } + int frames() const { return mFrameCount; } + + bool load(std::string jsonData) + { + mPlayer = rlottie::Animation::loadFromData(std::move(jsonData), "", "", + false); + mFrameCount = mPlayer ? mPlayer->totalFrame() : 0; + return mPlayer ? true : false; + } + + // canvas pixel pix[0] pix[1] pix[2] pix[3] {B G R A} + // lottie pixel pix[0] pix[1] pix[2] pix[3] {R G B A} + val render(int frame, int width, int height) + { + if (!mPlayer) return val(typed_memory_view(0, nullptr)); + + resize(width, height); + mPlayer->renderSync( + frame, rlottie::Surface((uint32_t *)mBuffer.get(), mWidth, mHeight, + mWidth * 4)); + convertToCanvasFormat(); + + return val(typed_memory_view(mWidth * mHeight * 4, mBuffer.get())); + } + ~RlottieWasm() {} + +private: + void resize(int width, int height) + { + if (width == mWidth && height == mHeight) return; + + mWidth = width; + mHeight = height; + + mBuffer = std::make_unique(mWidth * mHeight * 4); + } + + explicit RlottieWasm(const char *data) + { + mPlayer = rlottie::Animation::loadFromData(data, "", "", false); + mFrameCount = mPlayer ? mPlayer->totalFrame() : 0; + } + + void convertToCanvasFormat() + { + int totalBytes = mWidth * mHeight * 4; + uint8_t *buffer = mBuffer.get(); + for (int i = 0; i < totalBytes; i += 4) { + unsigned char a = buffer[i + 3]; + // compute only if alpha is non zero + if (a) { + unsigned char r = buffer[i + 2]; + unsigned char g = buffer[i + 1]; + unsigned char b = buffer[i]; + + if (a != 255) { // un premultiply + r = (r * 255) / a; + g = (g * 255) / a; + b = (b * 255) / a; + + buffer[i] = r; + buffer[i + 1] = g; + buffer[i + 2] = b; + + } else { + // only swizzle r and b + buffer[i] = r; + buffer[i + 2] = b; + } + } + } + } + +private: + int mWidth{0}; + int mHeight{0}; + int mFrameCount{0}; + std::unique_ptr mBuffer; + std::unique_ptr mPlayer; +}; + +// Binding code +EMSCRIPTEN_BINDINGS(rlottie_bindings) +{ + class_("RlottieWasm") + .constructor(&RlottieWasm::create) + .function("load", &RlottieWasm::load, allow_raw_pointers()) + .function("frames", &RlottieWasm::frames) + .function("render", &RlottieWasm::render); +} diff --git a/third-party/AppCenter/BUILD b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/.gitkeep similarity index 100% rename from third-party/AppCenter/BUILD rename to Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/.gitkeep diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/LottieAnimation.swift b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/LottieAnimation.swift new file mode 100644 index 0000000000..1883991ca5 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/LottieAnimation.swift @@ -0,0 +1,67 @@ +import CoreGraphics +import Foundation +import RLottieCore + +/// Swift facade for `LottieInstance` plus a CGImage renderer. Owns the per-animation +/// memory; release the instance and the backing rlottie buffers come with it. +public final class LottieAnimation { + private let instance: LottieInstance + + public var dimensions: CGSize { instance.dimensions } + public var frameCount: Int { Int(instance.frameCount) } + public var frameRate: Int { Int(instance.frameRate) } + public var duration: TimeInterval { Double(frameCount) / Double(max(1, frameRate)) } + + public init?(tgsFileURL url: URL) { + guard let gzipped = try? Data(contentsOf: url), + let json = try? decompressTgs(gzipped), + let inst = LottieInstance(data: json, cacheKey: url.lastPathComponent) else { + return nil + } + self.instance = inst + } + + /// Renders frame `index` into a fresh CGImage sized `size × scale`. Returns nil if + /// allocation fails. Caller is responsible for using the image promptly — backing + /// pixels are owned by an internal `Data` retained by the CGDataProvider. + public func renderFrame(index: Int, size: CGSize, scale: CGFloat) -> CGImage? { + let pixelWidth = max(1, Int(size.width * scale)) + let pixelHeight = max(1, Int(size.height * scale)) + let bytesPerRow = pixelWidth * 4 + let bufferSize = bytesPerRow * pixelHeight + + var buffer = [UInt8](repeating: 0, count: bufferSize) + buffer.withUnsafeMutableBufferPointer { ptr in + instance.renderFrame( + with: Int32(index), + into: ptr.baseAddress!, + width: Int32(pixelWidth), + height: Int32(pixelHeight), + bytesPerRow: Int32(bytesPerRow) + ) + } + + let data = Data(buffer) + guard let provider = CGDataProvider(data: data as CFData) else { return nil } + + // rlottie writes BGRA-premultiplied; CGBitmapInfo.byteOrder32Little + premultipliedFirst + // lets CoreGraphics consume it without a swizzle pass. + let bitmapInfo = CGBitmapInfo(rawValue: + CGImageAlphaInfo.premultipliedFirst.rawValue | + CGBitmapInfo.byteOrder32Little.rawValue) + let colorSpace = CGColorSpaceCreateDeviceRGB() + return CGImage( + width: pixelWidth, + height: pixelHeight, + bitsPerComponent: 8, + bitsPerPixel: 32, + bytesPerRow: bytesPerRow, + space: colorSpace, + bitmapInfo: bitmapInfo, + provider: provider, + decode: nil, + shouldInterpolate: true, + intent: .defaultIntent + ) + } +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/TgsDecompress.swift b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/TgsDecompress.swift new file mode 100644 index 0000000000..682375f989 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Sources/RLottieKit/TgsDecompress.swift @@ -0,0 +1,46 @@ +import Foundation +import zlib + +public enum TgsDecompressError: Error { + case inflateFailed(Int32) + case emptyInput +} + +/// Inflates a TGS (gzipped lottie JSON) blob into the raw JSON bytes rlottie expects. +/// Uses libz with `windowBits = 15 + 32` so the auto-detect path handles both gzip and +/// plain zlib wrappers (defensive — TGS files in the wild are always gzip). +public func decompressTgs(_ data: Data) throws -> Data { + guard !data.isEmpty else { throw TgsDecompressError.emptyInput } + + var stream = z_stream() + var status = inflateInit2_(&stream, 15 + 32, ZLIB_VERSION, Int32(MemoryLayout.size)) + guard status == Z_OK else { throw TgsDecompressError.inflateFailed(status) } + defer { inflateEnd(&stream) } + + var output = Data() + let bufferSize = 64 * 1024 + var buffer = [UInt8](repeating: 0, count: bufferSize) + + try data.withUnsafeBytes { (inPtr: UnsafeRawBufferPointer) in + let basePtr = inPtr.bindMemory(to: UInt8.self).baseAddress! + stream.next_in = UnsafeMutablePointer(mutating: basePtr) + stream.avail_in = UInt32(data.count) + + repeat { + try buffer.withUnsafeMutableBufferPointer { outBuf in + stream.next_out = outBuf.baseAddress + stream.avail_out = UInt32(bufferSize) + status = inflate(&stream, Z_NO_FLUSH) + if status != Z_OK && status != Z_STREAM_END { + throw TgsDecompressError.inflateFailed(status) + } + let produced = bufferSize - Int(stream.avail_out) + if produced > 0 { + output.append(outBuf.baseAddress!, count: produced) + } + } + } while status != Z_STREAM_END + } + + return output +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/LottieAnimationDecodeTests.swift b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/LottieAnimationDecodeTests.swift new file mode 100644 index 0000000000..15dca4699e --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/LottieAnimationDecodeTests.swift @@ -0,0 +1,38 @@ +import XCTest +@testable import RLottieKit +import CoreGraphics + +final class LottieAnimationDecodeTests: XCTestCase { + func testValidTgsLoads() throws { + let url = Bundle.module.url(forResource: "tiny", withExtension: "tgs")! + let animation = try XCTUnwrap(LottieAnimation(tgsFileURL: url)) + XCTAssertEqual(animation.dimensions, CGSize(width: 100, height: 100)) + XCTAssertEqual(animation.frameRate, 30) + XCTAssertEqual(animation.frameCount, 30) + XCTAssertEqual(animation.duration, 1.0, accuracy: 0.0001) + } + + func testRandomBytesReturnsNil() throws { + let dir = FileManager.default.temporaryDirectory + let url = dir.appendingPathComponent("bogus-\(UUID().uuidString).tgs") + let bogus = Data((0..<256).map { _ in UInt8.random(in: 0...255) }) + try bogus.write(to: url) + defer { try? FileManager.default.removeItem(at: url) } + XCTAssertNil(LottieAnimation(tgsFileURL: url)) + } + + func testRawJsonReturnsNil() throws { + // We accept TGS (gzipped) only — raw JSON should fail the gunzip step. + let url = Bundle.module.url(forResource: "tiny", withExtension: "json")! + XCTAssertNil(LottieAnimation(tgsFileURL: url)) + } + + func testRenderFrameProducesNonEmptyImage() throws { + let url = Bundle.module.url(forResource: "tiny", withExtension: "tgs")! + let animation = try XCTUnwrap(LottieAnimation(tgsFileURL: url)) + let image = animation.renderFrame(index: 0, size: CGSize(width: 32, height: 32), scale: 1.0) + XCTAssertNotNil(image) + XCTAssertEqual(image?.width, 32) + XCTAssertEqual(image?.height, 32) + } +} diff --git a/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.json b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.json new file mode 100644 index 0000000000..d3ce7dbbc0 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.json @@ -0,0 +1 @@ +{"v":"5.7.6","fr":30,"ip":0,"op":30,"w":100,"h":100,"nm":"tiny","ddd":0,"assets":[],"layers":[]} \ No newline at end of file diff --git a/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.tgs b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.tgs new file mode 100644 index 0000000000..cb88985fa7 Binary files /dev/null and b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/Resources/tiny.tgs differ diff --git a/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/TgsDecompressTests.swift b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/TgsDecompressTests.swift new file mode 100644 index 0000000000..d8c82e1ea6 --- /dev/null +++ b/Telegram/WatchApp/Packages/RLottieKit/Tests/RLottieKitTests/TgsDecompressTests.swift @@ -0,0 +1,24 @@ +import XCTest +@testable import RLottieKit + +final class TgsDecompressTests: XCTestCase { + func testGzippedTgsRoundtripsToOriginalJson() throws { + let tgsURL = Bundle.module.url(forResource: "tiny", withExtension: "tgs")! + let jsonURL = Bundle.module.url(forResource: "tiny", withExtension: "json")! + let tgsData = try Data(contentsOf: tgsURL) + let expectedJson = try Data(contentsOf: jsonURL) + + let decompressed = try decompressTgs(tgsData) + + XCTAssertEqual(decompressed, expectedJson) + } + + func testRandomBytesThrows() { + let randomBytes = Data((0..<256).map { _ in UInt8.random(in: 0...255) }) + XCTAssertThrowsError(try decompressTgs(randomBytes)) + } + + func testEmptyDataThrows() { + XCTAssertThrowsError(try decompressTgs(Data())) + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Package.resolved b/Telegram/WatchApp/Packages/TDShim/Package.resolved new file mode 100644 index 0000000000..a9ff954e27 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "tdlibframework", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Swiftgram/TDLibFramework", + "state" : { + "revision" : "0fbc375942d5af2effd42699664cd04045b2b754", + "version" : "1.8.64-49b3bcbb" + } + } + ], + "version" : 2 +} diff --git a/Telegram/WatchApp/Packages/TDShim/Package.swift b/Telegram/WatchApp/Packages/TDShim/Package.swift new file mode 100644 index 0000000000..f51f00daca --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "TDShim", + platforms: [ + .iOS(.v17), + .macOS(.v12), + .watchOS(.v9), + ], + products: [ + .library(name: "TDShim", targets: ["TDShim"]), + ], + dependencies: [ + .package(url: "https://github.com/Swiftgram/TDLibFramework", .exact("1.8.64-49b3bcbb")), + ], + targets: [ + .target( + name: "TDShim", + dependencies: [ + .product(name: "TDLibFramework", package: "TDLibFramework"), + ] + ), + .testTarget( + name: "TDShimTests", + dependencies: ["TDShim"] + ), + ] +) diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TDLibApi.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TDLibApi.swift new file mode 100644 index 0000000000..3104fce156 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TDLibApi.swift @@ -0,0 +1,835 @@ +// +// TDLibApi.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Must be subclassed with `send` and `execute` TDLib functions implementation +public class TDLibApi { + + public let encoder = JSONEncoder() + public let decoder = JSONDecoder() + + public init() { + self.encoder.keyEncodingStrategy = .convertToSnakeCase + self.decoder.keyDecodingStrategy = .convertFromSnakeCase + } + + + /// Sends request to the TDLib client. + public func send(query: TdQuery, completion: ((Data) -> Void)? = nil) throws { + fatalError("send() not implemented") + } + + /// Synchronously executes TDLib request. + public func execute(query: TdQuery) throws -> [String:Any]? { + fatalError("execute() not implemented") + } + + + /// Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters + /// - Parameter apiHash: Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org + /// - Parameter apiId: Application identifier for Telegram API access, which can be obtained at https://my.telegram.org + /// - Parameter applicationVersion: Application version; must be non-empty + /// - Parameter databaseDirectory: The path to the directory for the persistent database; if empty, the current working directory will be used + /// - Parameter databaseEncryptionKey: Encryption key for the database. If the encryption key is invalid, then an error with code 401 will be returned + /// - Parameter deviceModel: Model of the device the application is being run on; must be non-empty + /// - Parameter filesDirectory: The path to the directory for storing files; if empty, database_directory will be used + /// - Parameter systemLanguageCode: IETF language tag of the user's operating system language; must be non-empty + /// - Parameter systemVersion: Version of the operating system the application is being run on. If empty, the version is automatically detected by TDLib + /// - Parameter useChatInfoDatabase: Pass true to keep cache of users, basic groups, supergroups, channels and secret chats between restarts. Implies use_file_database + /// - Parameter useFileDatabase: Pass true to keep information about downloaded and uploaded files between application restarts + /// - Parameter useMessageDatabase: Pass true to keep cache of chats and messages between restarts. Implies use_chat_info_database + /// - Parameter useSecretChats: Pass true to enable support for secret chats + /// - Parameter useTestDc: Pass true to use Telegram test environment instead of the production environment + public final func setTdlibParameters( + apiHash: String?, + apiId: Int?, + applicationVersion: String?, + databaseDirectory: String?, + databaseEncryptionKey: Data?, + deviceModel: String?, + filesDirectory: String?, + systemLanguageCode: String?, + systemVersion: String?, + useChatInfoDatabase: Bool?, + useFileDatabase: Bool?, + useMessageDatabase: Bool?, + useSecretChats: Bool?, + useTestDc: Bool?, + completion: @escaping (Result) -> Void + ) throws { + let query = SetTdlibParameters( + apiHash: apiHash, + apiId: apiId, + applicationVersion: applicationVersion, + databaseDirectory: databaseDirectory, + databaseEncryptionKey: databaseEncryptionKey, + deviceModel: deviceModel, + filesDirectory: filesDirectory, + systemLanguageCode: systemLanguageCode, + systemVersion: systemVersion, + useChatInfoDatabase: useChatInfoDatabase, + useFileDatabase: useFileDatabase, + useMessageDatabase: useMessageDatabase, + useSecretChats: useSecretChats, + useTestDc: useTestDc + ) + self.run(query: query, completion: completion) + } + + /// Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters + /// - Parameter apiHash: Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org + /// - Parameter apiId: Application identifier for Telegram API access, which can be obtained at https://my.telegram.org + /// - Parameter applicationVersion: Application version; must be non-empty + /// - Parameter databaseDirectory: The path to the directory for the persistent database; if empty, the current working directory will be used + /// - Parameter databaseEncryptionKey: Encryption key for the database. If the encryption key is invalid, then an error with code 401 will be returned + /// - Parameter deviceModel: Model of the device the application is being run on; must be non-empty + /// - Parameter filesDirectory: The path to the directory for storing files; if empty, database_directory will be used + /// - Parameter systemLanguageCode: IETF language tag of the user's operating system language; must be non-empty + /// - Parameter systemVersion: Version of the operating system the application is being run on. If empty, the version is automatically detected by TDLib + /// - Parameter useChatInfoDatabase: Pass true to keep cache of users, basic groups, supergroups, channels and secret chats between restarts. Implies use_file_database + /// - Parameter useFileDatabase: Pass true to keep information about downloaded and uploaded files between application restarts + /// - Parameter useMessageDatabase: Pass true to keep cache of chats and messages between restarts. Implies use_chat_info_database + /// - Parameter useSecretChats: Pass true to enable support for secret chats + /// - Parameter useTestDc: Pass true to use Telegram test environment instead of the production environment + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func setTdlibParameters( + apiHash: String?, + apiId: Int?, + applicationVersion: String?, + databaseDirectory: String?, + databaseEncryptionKey: Data?, + deviceModel: String?, + filesDirectory: String?, + systemLanguageCode: String?, + systemVersion: String?, + useChatInfoDatabase: Bool?, + useFileDatabase: Bool?, + useMessageDatabase: Bool?, + useSecretChats: Bool?, + useTestDc: Bool? + ) async throws -> Ok { + let query = SetTdlibParameters( + apiHash: apiHash, + apiId: apiId, + applicationVersion: applicationVersion, + databaseDirectory: databaseDirectory, + databaseEncryptionKey: databaseEncryptionKey, + deviceModel: deviceModel, + filesDirectory: filesDirectory, + systemLanguageCode: systemLanguageCode, + systemVersion: systemVersion, + useChatInfoDatabase: useChatInfoDatabase, + useFileDatabase: useFileDatabase, + useMessageDatabase: useMessageDatabase, + useSecretChats: useSecretChats, + useTestDc: useTestDc + ) + return try await self.run(query: query) + } + + /// Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber, or if there is no pending authentication query and the current authorization state is authorizationStateWaitPremiumPurchase, authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode, authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword + /// - Parameter otherUserIds: List of user identifiers of other users currently using the application + public final func requestQrCodeAuthentication( + otherUserIds: [Int64]?, + completion: @escaping (Result) -> Void + ) throws { + let query = RequestQrCodeAuthentication( + otherUserIds: otherUserIds + ) + self.run(query: query, completion: completion) + } + + /// Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber, or if there is no pending authentication query and the current authorization state is authorizationStateWaitPremiumPurchase, authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode, authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword + /// - Parameter otherUserIds: List of user identifiers of other users currently using the application + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func requestQrCodeAuthentication(otherUserIds: [Int64]?) async throws -> Ok { + let query = RequestQrCodeAuthentication( + otherUserIds: otherUserIds + ) + return try await self.run(query: query) + } + + /// Checks the 2-step verification password for correctness. Works only when the current authorization state is authorizationStateWaitPassword + /// - Parameter password: The 2-step verification password to check + public final func checkAuthenticationPassword( + password: String?, + completion: @escaping (Result) -> Void + ) throws { + let query = CheckAuthenticationPassword( + password: password + ) + self.run(query: query, completion: completion) + } + + /// Checks the 2-step verification password for correctness. Works only when the current authorization state is authorizationStateWaitPassword + /// - Parameter password: The 2-step verification password to check + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func checkAuthenticationPassword(password: String?) async throws -> Ok { + let query = CheckAuthenticationPassword( + password: password + ) + return try await self.run(query: query) + } + + /// Closes the TDLib instance after a proper logout. Requires an available network connection. All local data will be destroyed. After the logout completes, updateAuthorizationState with authorizationStateClosed will be sent + public final func logOut(completion: @escaping (Result) -> Void) throws { + let query = LogOut() + self.run(query: query, completion: completion) + } + + /// Closes the TDLib instance after a proper logout. Requires an available network connection. All local data will be destroyed. After the logout completes, updateAuthorizationState with authorizationStateClosed will be sent + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func logOut() async throws -> Ok { + let query = LogOut() + return try await self.run(query: query) + } + + /// Closes the TDLib instance. All databases will be flushed to disk and properly closed. After the close completes, updateAuthorizationState with authorizationStateClosed will be sent. Can be called before initialization + public final func close(completion: @escaping (Result) -> Void) throws { + let query = Close() + self.run(query: query, completion: completion) + } + + /// Closes the TDLib instance. All databases will be flushed to disk and properly closed. After the close completes, updateAuthorizationState with authorizationStateClosed will be sent. Can be called before initialization + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func close() async throws -> Ok { + let query = Close() + return try await self.run(query: query) + } + + /// Closes the TDLib instance, destroying all local data without a proper logout. The current user session will remain in the list of all active sessions. All local data will be destroyed. After the destruction completes updateAuthorizationState with authorizationStateClosed will be sent. Can be called before authorization + public final func destroy(completion: @escaping (Result) -> Void) throws { + let query = Destroy() + self.run(query: query, completion: completion) + } + + /// Closes the TDLib instance, destroying all local data without a proper logout. The current user session will remain in the list of all active sessions. All local data will be destroyed. After the destruction completes updateAuthorizationState with authorizationStateClosed will be sent. Can be called before authorization + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func destroy() async throws -> Ok { + let query = Destroy() + return try await self.run(query: query) + } + + /// Returns the current user + /// - Returns: The current user + public final func getMe(completion: @escaping (Result) -> Void) throws { + let query = GetMe() + self.run(query: query, completion: completion) + } + + /// Returns the current user + /// - Returns: The current user + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getMe() async throws -> User { + let query = GetMe() + return try await self.run(query: query) + } + + /// Loads more chats from a chat list. The loaded chats and their positions in the chat list will be sent through updates. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. Returns a 404 error if all chats have been loaded + /// - Parameter chatList: The chat list in which to load chats; pass null to load chats from the main chat list + /// - Parameter limit: The maximum number of chats to be loaded. For optimal performance, the number of loaded chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached + /// - Returns: A 404 error if all chats have been loaded + public final func loadChats( + chatList: ChatList?, + limit: Int?, + completion: @escaping (Result) -> Void + ) throws { + let query = LoadChats( + chatList: chatList, + limit: limit + ) + self.run(query: query, completion: completion) + } + + /// Loads more chats from a chat list. The loaded chats and their positions in the chat list will be sent through updates. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. Returns a 404 error if all chats have been loaded + /// - Parameter chatList: The chat list in which to load chats; pass null to load chats from the main chat list + /// - Parameter limit: The maximum number of chats to be loaded. For optimal performance, the number of loaded chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached + /// - Returns: A 404 error if all chats have been loaded + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func loadChats( + chatList: ChatList?, + limit: Int? + ) async throws -> Ok { + let query = LoadChats( + chatList: chatList, + limit: limit + ) + return try await self.run(query: query) + } + + /// Returns messages in a chat. The messages are returned in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib. This is an offline method if only_local is true + /// - Parameter chatId: Chat identifier + /// - Parameter fromMessageId: Identifier of the message starting from which history must be fetched; use 0 to get results from the last message + /// - Parameter limit: The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, then the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit + /// - Parameter offset: Specify 0 to get results from exactly the message from_message_id or a negative number from -99 to -1 to get additionally -offset newer messages + /// - Parameter onlyLocal: Pass true to get only messages that are available without sending network requests + /// - Returns: Messages in a chat. The messages are returned in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib + public final func getChatHistory( + chatId: Int64?, + fromMessageId: Int64?, + limit: Int?, + offset: Int?, + onlyLocal: Bool?, + completion: @escaping (Result) -> Void + ) throws { + let query = GetChatHistory( + chatId: chatId, + fromMessageId: fromMessageId, + limit: limit, + offset: offset, + onlyLocal: onlyLocal + ) + self.run(query: query, completion: completion) + } + + /// Returns messages in a chat. The messages are returned in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib. This is an offline method if only_local is true + /// - Parameter chatId: Chat identifier + /// - Parameter fromMessageId: Identifier of the message starting from which history must be fetched; use 0 to get results from the last message + /// - Parameter limit: The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, then the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit + /// - Parameter offset: Specify 0 to get results from exactly the message from_message_id or a negative number from -99 to -1 to get additionally -offset newer messages + /// - Parameter onlyLocal: Pass true to get only messages that are available without sending network requests + /// - Returns: Messages in a chat. The messages are returned in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getChatHistory( + chatId: Int64?, + fromMessageId: Int64?, + limit: Int?, + offset: Int?, + onlyLocal: Bool? + ) async throws -> Messages { + let query = GetChatHistory( + chatId: chatId, + fromMessageId: fromMessageId, + limit: limit, + offset: offset, + onlyLocal: onlyLocal + ) + return try await self.run(query: query) + } + + /// Sends a message. Returns the sent message + /// - Parameter chatId: Target chat + /// - Parameter inputMessageContent: The content of the message to be sent + /// - Parameter options: Options to be used to send the message; pass null to use default options + /// - Parameter replyMarkup: Markup for replying to the message; pass null if none; for bots only + /// - Parameter replyTo: Information about the message or story to be replied; pass null if none + /// - Parameter topicId: Topic in which the message will be sent; pass null if none + /// - Returns: The sent message + public final func sendMessage( + chatId: Int64?, + inputMessageContent: InputMessageContent?, + options: MessageSendOptions?, + replyMarkup: ReplyMarkup?, + replyTo: InputMessageReplyTo?, + topicId: MessageTopic?, + completion: @escaping (Result) -> Void + ) throws { + let query = SendMessage( + chatId: chatId, + inputMessageContent: inputMessageContent, + options: options, + replyMarkup: replyMarkup, + replyTo: replyTo, + topicId: topicId + ) + self.run(query: query, completion: completion) + } + + /// Sends a message. Returns the sent message + /// - Parameter chatId: Target chat + /// - Parameter inputMessageContent: The content of the message to be sent + /// - Parameter options: Options to be used to send the message; pass null to use default options + /// - Parameter replyMarkup: Markup for replying to the message; pass null if none; for bots only + /// - Parameter replyTo: Information about the message or story to be replied; pass null if none + /// - Parameter topicId: Topic in which the message will be sent; pass null if none + /// - Returns: The sent message + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func sendMessage( + chatId: Int64?, + inputMessageContent: InputMessageContent?, + options: MessageSendOptions?, + replyMarkup: ReplyMarkup?, + replyTo: InputMessageReplyTo?, + topicId: MessageTopic? + ) async throws -> Message { + let query = SendMessage( + chatId: chatId, + inputMessageContent: inputMessageContent, + options: options, + replyMarkup: replyMarkup, + replyTo: replyTo, + topicId: topicId + ) + return try await self.run(query: query) + } + + /// Changes the user answer to a poll + /// - Parameter chatId: Identifier of the chat to which the poll belongs + /// - Parameter messageId: Identifier of the message containing the poll + /// - Parameter optionIds: 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers + public final func setPollAnswer( + chatId: Int64?, + messageId: Int64?, + optionIds: [Int]?, + completion: @escaping (Result) -> Void + ) throws { + let query = SetPollAnswer( + chatId: chatId, + messageId: messageId, + optionIds: optionIds + ) + self.run(query: query, completion: completion) + } + + /// Changes the user answer to a poll + /// - Parameter chatId: Identifier of the chat to which the poll belongs + /// - Parameter messageId: Identifier of the message containing the poll + /// - Parameter optionIds: 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func setPollAnswer( + chatId: Int64?, + messageId: Int64?, + optionIds: [Int]? + ) async throws -> Ok { + let query = SetPollAnswer( + chatId: chatId, + messageId: messageId, + optionIds: optionIds + ) + return try await self.run(query: query) + } + + /// Informs TDLib that the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) + /// - Parameter chatId: Chat identifier + public final func openChat( + chatId: Int64?, + completion: @escaping (Result) -> Void + ) throws { + let query = OpenChat( + chatId: chatId + ) + self.run(query: query, completion: completion) + } + + /// Informs TDLib that the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) + /// - Parameter chatId: Chat identifier + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func openChat(chatId: Int64?) async throws -> Ok { + let query = OpenChat( + chatId: chatId + ) + return try await self.run(query: query) + } + + /// Informs TDLib that the chat is closed by the user. Many useful activities depend on the chat being opened or closed + /// - Parameter chatId: Chat identifier + public final func closeChat( + chatId: Int64?, + completion: @escaping (Result) -> Void + ) throws { + let query = CloseChat( + chatId: chatId + ) + self.run(query: query, completion: completion) + } + + /// Informs TDLib that the chat is closed by the user. Many useful activities depend on the chat being opened or closed + /// - Parameter chatId: Chat identifier + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func closeChat(chatId: Int64?) async throws -> Ok { + let query = CloseChat( + chatId: chatId + ) + return try await self.run(query: query) + } + + /// Informs TDLib that messages are being viewed by the user. Sponsored messages must be marked as viewed only when the entire text of the message is shown on the screen (excluding the button). Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels) + /// - Parameter chatId: Chat identifier + /// - Parameter forceRead: Pass true to mark as read the specified messages even if the chat is closed + /// - Parameter messageIds: The identifiers of the messages being viewed + /// - Parameter source: Source of the message view; pass null to guess the source based on chat open state + public final func viewMessages( + chatId: Int64?, + forceRead: Bool?, + messageIds: [Int64]?, + source: MessageSource?, + completion: @escaping (Result) -> Void + ) throws { + let query = ViewMessages( + chatId: chatId, + forceRead: forceRead, + messageIds: messageIds, + source: source + ) + self.run(query: query, completion: completion) + } + + /// Informs TDLib that messages are being viewed by the user. Sponsored messages must be marked as viewed only when the entire text of the message is shown on the screen (excluding the button). Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels) + /// - Parameter chatId: Chat identifier + /// - Parameter forceRead: Pass true to mark as read the specified messages even if the chat is closed + /// - Parameter messageIds: The identifiers of the messages being viewed + /// - Parameter source: Source of the message view; pass null to guess the source based on chat open state + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func viewMessages( + chatId: Int64?, + forceRead: Bool?, + messageIds: [Int64]?, + source: MessageSource? + ) async throws -> Ok { + let query = ViewMessages( + chatId: chatId, + forceRead: forceRead, + messageIds: messageIds, + source: source + ) + return try await self.run(query: query) + } + + /// Changes the draft message in a chat or a topic + /// - Parameter chatId: Chat identifier + /// - Parameter draftMessage: New draft message; pass null to remove the draft. All files in draft message content must be of the type inputFileLocal. Media thumbnails and captions are ignored + /// - Parameter topicId: Topic in which the draft will be changed; pass null to change the draft for the chat itself + public final func setChatDraftMessage( + chatId: Int64?, + draftMessage: DraftMessage?, + topicId: MessageTopic?, + completion: @escaping (Result) -> Void + ) throws { + let query = SetChatDraftMessage( + chatId: chatId, + draftMessage: draftMessage, + topicId: topicId + ) + self.run(query: query, completion: completion) + } + + /// Changes the draft message in a chat or a topic + /// - Parameter chatId: Chat identifier + /// - Parameter draftMessage: New draft message; pass null to remove the draft. All files in draft message content must be of the type inputFileLocal. Media thumbnails and captions are ignored + /// - Parameter topicId: Topic in which the draft will be changed; pass null to change the draft for the chat itself + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func setChatDraftMessage( + chatId: Int64?, + draftMessage: DraftMessage?, + topicId: MessageTopic? + ) async throws -> Ok { + let query = SetChatDraftMessage( + chatId: chatId, + draftMessage: draftMessage, + topicId: topicId + ) + return try await self.run(query: query) + } + + /// Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates + /// - Parameter fileId: Identifier of the file to download + /// - Parameter limit: Number of bytes which need to be downloaded starting from the "offset" position before the download will automatically be canceled; use 0 to download without a limit + /// - Parameter offset: The starting position from which the file needs to be downloaded + /// - Parameter priority: Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first + /// - Parameter synchronous: Pass true to return response only after the file download has succeeded, has failed, has been canceled, or a new downloadFile request with different offset/limit parameters was sent; pass false to return file state immediately, just after the download has been started + public final func downloadFile( + fileId: Int?, + limit: Int64?, + offset: Int64?, + priority: Int?, + synchronous: Bool?, + completion: @escaping (Result) -> Void + ) throws { + let query = DownloadFile( + fileId: fileId, + limit: limit, + offset: offset, + priority: priority, + synchronous: synchronous + ) + self.run(query: query, completion: completion) + } + + /// Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates + /// - Parameter fileId: Identifier of the file to download + /// - Parameter limit: Number of bytes which need to be downloaded starting from the "offset" position before the download will automatically be canceled; use 0 to download without a limit + /// - Parameter offset: The starting position from which the file needs to be downloaded + /// - Parameter priority: Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first + /// - Parameter synchronous: Pass true to return response only after the file download has succeeded, has failed, has been canceled, or a new downloadFile request with different offset/limit parameters was sent; pass false to return file state immediately, just after the download has been started + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func downloadFile( + fileId: Int?, + limit: Int64?, + offset: Int64?, + priority: Int?, + synchronous: Bool? + ) async throws -> File { + let query = DownloadFile( + fileId: fileId, + limit: limit, + offset: offset, + priority: priority, + synchronous: synchronous + ) + return try await self.run(query: query) + } + + /// Stops the downloading of a file. If a file has already been downloaded, does nothing + /// - Parameter fileId: Identifier of a file to stop downloading + /// - Parameter onlyIfPending: Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server + public final func cancelDownloadFile( + fileId: Int?, + onlyIfPending: Bool?, + completion: @escaping (Result) -> Void + ) throws { + let query = CancelDownloadFile( + fileId: fileId, + onlyIfPending: onlyIfPending + ) + self.run(query: query, completion: completion) + } + + /// Stops the downloading of a file. If a file has already been downloaded, does nothing + /// - Parameter fileId: Identifier of a file to stop downloading + /// - Parameter onlyIfPending: Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func cancelDownloadFile( + fileId: Int?, + onlyIfPending: Bool? + ) async throws -> Ok { + let query = CancelDownloadFile( + fileId: fileId, + onlyIfPending: onlyIfPending + ) + return try await self.run(query: query) + } + + /// Returns a list of installed sticker sets + /// - Parameter stickerType: Type of the sticker sets to return + /// - Returns: A list of installed sticker sets + public final func getInstalledStickerSets( + stickerType: StickerType?, + completion: @escaping (Result) -> Void + ) throws { + let query = GetInstalledStickerSets( + stickerType: stickerType + ) + self.run(query: query, completion: completion) + } + + /// Returns a list of installed sticker sets + /// - Parameter stickerType: Type of the sticker sets to return + /// - Returns: A list of installed sticker sets + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getInstalledStickerSets(stickerType: StickerType?) async throws -> StickerSets { + let query = GetInstalledStickerSets( + stickerType: stickerType + ) + return try await self.run(query: query) + } + + /// Returns information about a sticker set by its identifier + /// - Parameter setId: Identifier of the sticker set + /// - Returns: Information about a sticker set by its identifier + public final func getStickerSet( + setId: TdInt64?, + completion: @escaping (Result) -> Void + ) throws { + let query = GetStickerSet( + setId: setId + ) + self.run(query: query, completion: completion) + } + + /// Returns information about a sticker set by its identifier + /// - Parameter setId: Identifier of the sticker set + /// - Returns: Information about a sticker set by its identifier + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getStickerSet(setId: TdInt64?) async throws -> StickerSet { + let query = GetStickerSet( + setId: setId + ) + return try await self.run(query: query) + } + + /// Returns a list of recently used stickers + /// - Parameter isAttached: Pass true to return stickers and masks that were recently attached to photos or video files; pass false to return recently sent stickers + /// - Returns: A list of recently used stickers + public final func getRecentStickers( + isAttached: Bool?, + completion: @escaping (Result) -> Void + ) throws { + let query = GetRecentStickers( + isAttached: isAttached + ) + self.run(query: query, completion: completion) + } + + /// Returns a list of recently used stickers + /// - Parameter isAttached: Pass true to return stickers and masks that were recently attached to photos or video files; pass false to return recently sent stickers + /// - Returns: A list of recently used stickers + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getRecentStickers(isAttached: Bool?) async throws -> Stickers { + let query = GetRecentStickers( + isAttached: isAttached + ) + return try await self.run(query: query) + } + + /// Returns favorite stickers + /// - Returns: Favorite stickers + public final func getFavoriteStickers(completion: @escaping (Result) -> Void) throws { + let query = GetFavoriteStickers() + self.run(query: query, completion: completion) + } + + /// Returns favorite stickers + /// - Returns: Favorite stickers + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getFavoriteStickers() async throws -> Stickers { + let query = GetFavoriteStickers() + return try await self.run(query: query) + } + + /// Returns the value of an option by its name. (Check the list of available options on https://core.telegram.org/tdlib/options.) Can be called before authorization. Can be called synchronously for options "version" and "commit_hash" + /// - Parameter name: The name of the option + /// - Returns: The value of an option by its name + public final func getOption( + name: String?, + completion: @escaping (Result) -> Void + ) throws { + let query = GetOption( + name: name + ) + self.run(query: query, completion: completion) + } + + /// Returns the value of an option by its name. (Check the list of available options on https://core.telegram.org/tdlib/options.) Can be called before authorization. Can be called synchronously for options "version" and "commit_hash" + /// - Parameter name: The name of the option + /// - Returns: The value of an option by its name + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + public final func getOption(name: String?) async throws -> OptionValue { + let query = GetOption( + name: name + ) + return try await self.run(query: query) + } + + /// Sets the value of an option. (Check the list of available options on https://core.telegram.org/tdlib/options.) Only writable options can be set. Can be called before authorization + /// - Parameter name: The name of the option + /// - Parameter value: The new value of the option; pass null to reset option value to a default value + public final func setOption( + name: String?, + value: OptionValue?, + completion: @escaping (Result) -> Void + ) throws { + let query = SetOption( + name: name, + value: value + ) + self.run(query: query, completion: completion) + } + + /// Sets the value of an option. (Check the list of available options on https://core.telegram.org/tdlib/options.) Only writable options can be set. Can be called before authorization + /// - Parameter name: The name of the option + /// - Parameter value: The new value of the option; pass null to reset option value to a default value + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func setOption( + name: String?, + value: OptionValue? + ) async throws -> Ok { + let query = SetOption( + name: name, + value: value + ) + return try await self.run(query: query) + } + + /// Sets the verbosity level of the internal logging of TDLib. Can be called synchronously + /// - Parameter newVerbosityLevel: New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging + public final func setLogVerbosityLevel( + newVerbosityLevel: Int?, + completion: @escaping (Result) -> Void + ) throws { + let query = SetLogVerbosityLevel( + newVerbosityLevel: newVerbosityLevel + ) + self.run(query: query, completion: completion) + } + + /// Sets the verbosity level of the internal logging of TDLib. Can be called synchronously + /// - Parameter newVerbosityLevel: New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + @discardableResult + public final func setLogVerbosityLevel(newVerbosityLevel: Int?) async throws -> Ok { + let query = SetLogVerbosityLevel( + newVerbosityLevel: newVerbosityLevel + ) + return try await self.run(query: query) + } + + + private final func run( + query: Q, + completion: @escaping (Result) -> Void) + where Q: Codable, R: Codable { + + let dto = DTO(query, encoder: self.encoder) + do { + try self.send(query: dto) { [weak self] result in + guard let strongSelf = self else { return } + if let error = try? strongSelf.decoder.decode(DTO.self, from: result) { + completion(.failure(error.payload)) + } else { + let response = strongSelf.decoder.tryDecode(DTO.self, from: result) + completion(response.map { $0.payload }) + } + } + } catch let err as TDError { + completion( .failure(err)) + } catch let any { + let err = TDError(code: 500, message: any.localizedDescription) + completion( .failure(err)) + } + } + + + @available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) + private final func run(query: Q) async throws -> R where Q: Codable, R: Codable { + let dto = DTO(query, encoder: self.encoder) + return try await withCheckedThrowingContinuation { continuation in + do { + try self.send(query: dto) { result in + if let error = try? self.decoder.decode(DTO.self, from: result) { + continuation.resume(with: .failure(error.payload)) + } else { + let response = self.decoder.tryDecode(DTO.self, from: result) + continuation.resume(with: response.map { $0.payload }) + } + } + } catch let err as TDError { + continuation.resume(with: .failure(err)) + } catch let any { + let err = TDError(code: 500, message: any.localizedDescription) + continuation.resume(with: .failure(err)) + } + } + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TdClient.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TdClient.swift new file mode 100644 index 0000000000..fe124766c1 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/API/TdClient.swift @@ -0,0 +1,35 @@ +// +// TdClient.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Basic protocol for communicate with TdLib. +public protocol TdClient { + + /// Receives incoming updates and request responses from the TDLib client + func run(updateHandler: @escaping (Data) -> Void) + + /// Sends request to the TDLib client. + func send(query: TdQuery, completion: ((Data) -> Void)?) throws + + /// Synchronously executes TDLib request. Only a few requests can be executed synchronously. + func execute(query: TdQuery) throws -> [String:Any]? + + /// Close connection with TDLib. + func close() + +} + + +public protocol TdQuery { + + func make(with extra: String?) throws -> Data + +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AccountInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AccountInfo.swift new file mode 100644 index 0000000000..e24a219b20 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AccountInfo.swift @@ -0,0 +1,46 @@ +// +// AccountInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains basic information about another user who started a chat with the current user +public struct AccountInfo: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the user changed name last time; 0 if unknown + public let lastNameChangeDate: Int + + /// Point in time (Unix timestamp) when the user changed photo last time; 0 if unknown + public let lastPhotoChangeDate: Int + + /// A two-letter ISO 3166-1 alpha-2 country code based on the phone number of the user; may be empty if unknown + public let phoneNumberCountryCode: String + + /// Month when the user was registered in Telegram; 0-12; may be 0 if unknown + public let registrationMonth: Int + + /// Year when the user was registered in Telegram; 0-9999; may be 0 if unknown + public let registrationYear: Int + + + public init( + lastNameChangeDate: Int, + lastPhotoChangeDate: Int, + phoneNumberCountryCode: String, + registrationMonth: Int, + registrationYear: Int + ) { + self.lastNameChangeDate = lastNameChangeDate + self.lastPhotoChangeDate = lastPhotoChangeDate + self.phoneNumberCountryCode = phoneNumberCountryCode + self.registrationMonth = registrationMonth + self.registrationYear = registrationYear + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ActiveStoryState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ActiveStoryState.swift new file mode 100644 index 0000000000..d3a92c7378 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ActiveStoryState.swift @@ -0,0 +1,79 @@ +// +// ActiveStoryState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes state of active stories posted by a chat +public indirect enum ActiveStoryState: Codable, Equatable, Hashable { + + /// The chat has an active live story + case activeStoryStateLive(ActiveStoryStateLive) + + /// The chat has some unread active stories + case activeStoryStateUnread + + /// The chat has active stories, all of which were read + case activeStoryStateRead + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case activeStoryStateLive + case activeStoryStateUnread + case activeStoryStateRead + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .activeStoryStateLive: + let value = try ActiveStoryStateLive(from: decoder) + self = .activeStoryStateLive(value) + case .activeStoryStateUnread: + self = .activeStoryStateUnread + case .activeStoryStateRead: + self = .activeStoryStateRead + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .activeStoryStateLive(let value): + try container.encode(Kind.activeStoryStateLive, forKey: .type) + try value.encode(to: encoder) + case .activeStoryStateUnread: + try container.encode(Kind.activeStoryStateUnread, forKey: .type) + case .activeStoryStateRead: + try container.encode(Kind.activeStoryStateRead, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The chat has an active live story +public struct ActiveStoryStateLive: Codable, Equatable, Hashable { + + /// Identifier of the active live story + public let storyId: Int + + + public init(storyId: Int) { + self.storyId = storyId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Address.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Address.swift new file mode 100644 index 0000000000..72bfe0f862 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Address.swift @@ -0,0 +1,51 @@ +// +// Address.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an address +public struct Address: Codable, Equatable, Hashable { + + /// City + public let city: String + + /// A two-letter ISO 3166-1 alpha-2 country code + public let countryCode: String + + /// Address postal code + public let postalCode: String + + /// State, if applicable + public let state: String + + /// First line of the address + public let streetLine1: String + + /// Second line of the address + public let streetLine2: String + + + public init( + city: String, + countryCode: String, + postalCode: String, + state: String, + streetLine1: String, + streetLine2: String + ) { + self.city = city + self.countryCode = countryCode + self.postalCode = postalCode + self.state = state + self.streetLine1 = streetLine1 + self.streetLine2 = streetLine2 + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AlternativeVideo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AlternativeVideo.swift new file mode 100644 index 0000000000..67b2953f01 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AlternativeVideo.swift @@ -0,0 +1,51 @@ +// +// AlternativeVideo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an alternative re-encoded quality of a video file +public struct AlternativeVideo: Codable, Equatable, Hashable, Identifiable { + + /// Codec used for video file encoding, for example, "h264", "h265", "av1", or "av01" + public let codec: String + + /// Video height + public let height: Int + + /// HLS file describing the video + public let hlsFile: File + + /// Unique identifier of the alternative video, which is used in the HLS file + public let id: TdInt64 + + /// File containing the video + public let video: File + + /// Video width + public let width: Int + + + public init( + codec: String, + height: Int, + hlsFile: File, + id: TdInt64, + video: File, + width: Int + ) { + self.codec = codec + self.height = height + self.hlsFile = hlsFile + self.id = id + self.video = video + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedChatPhoto.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedChatPhoto.swift new file mode 100644 index 0000000000..2e9d5ff558 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedChatPhoto.swift @@ -0,0 +1,36 @@ +// +// AnimatedChatPhoto.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Animated variant of a chat photo in MPEG4 format +public struct AnimatedChatPhoto: Codable, Equatable, Hashable { + + /// Information about the animation file + public let file: File + + /// Animation width and height + public let length: Int + + /// Timestamp of the frame, used as a static chat photo + public let mainFrameTimestamp: Double + + + public init( + file: File, + length: Int, + mainFrameTimestamp: Double + ) { + self.file = file + self.length = length + self.mainFrameTimestamp = mainFrameTimestamp + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedEmoji.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedEmoji.swift new file mode 100644 index 0000000000..c969838b44 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AnimatedEmoji.swift @@ -0,0 +1,46 @@ +// +// AnimatedEmoji.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an animated or custom representation of an emoji +public struct AnimatedEmoji: Codable, Equatable, Hashable { + + /// Emoji modifier fitzpatrick type; 0-6; 0 if none + public let fitzpatrickType: Int + + /// File containing the sound to be played when the sticker is clicked; may be null. The sound is encoded with the Opus codec, and stored inside an OGG container + public let sound: File? + + /// Sticker for the emoji; may be null if yet unknown for a custom emoji. If the sticker is a custom emoji, then it can have arbitrary format + public let sticker: Sticker? + + /// Expected height of the sticker, which can be used if the sticker is null + public let stickerHeight: Int + + /// Expected width of the sticker, which can be used if the sticker is null + public let stickerWidth: Int + + + public init( + fitzpatrickType: Int, + sound: File?, + sticker: Sticker?, + stickerHeight: Int, + stickerWidth: Int + ) { + self.fitzpatrickType = fitzpatrickType + self.sound = sound + self.sticker = sticker + self.stickerHeight = stickerHeight + self.stickerWidth = stickerWidth + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Animation.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Animation.swift new file mode 100644 index 0000000000..83bc07595e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Animation.swift @@ -0,0 +1,66 @@ +// +// Animation.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an animation file. The animation must be encoded in GIF or MPEG4 format +public struct Animation: Codable, Equatable, Hashable { + + /// File containing the animation + public let animation: File + + /// Duration of the animation, in seconds; as defined by the sender + public let duration: Int + + /// Original name of the file; as defined by the sender + public let fileName: String + + /// True, if stickers were added to the animation. The list of corresponding sticker set can be received using getAttachedStickerSets + public let hasStickers: Bool + + /// Height of the animation + public let height: Int + + /// MIME type of the file, usually "image/gif" or "video/mp4" + public let mimeType: String + + /// Animation minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Animation thumbnail in JPEG or MPEG4 format; may be null + public let thumbnail: Thumbnail? + + /// Width of the animation + public let width: Int + + + public init( + animation: File, + duration: Int, + fileName: String, + hasStickers: Bool, + height: Int, + mimeType: String, + minithumbnail: Minithumbnail?, + thumbnail: Thumbnail?, + width: Int + ) { + self.animation = animation + self.duration = duration + self.fileName = fileName + self.hasStickers = hasStickers + self.height = height + self.mimeType = mimeType + self.minithumbnail = minithumbnail + self.thumbnail = thumbnail + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Audio.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Audio.swift new file mode 100644 index 0000000000..ab9ded9993 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Audio.swift @@ -0,0 +1,66 @@ +// +// Audio.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an audio file. Audio is usually in MP3 or M4A format +public struct Audio: Codable, Equatable, Hashable { + + /// The minithumbnail of the album cover; may be null + public let albumCoverMinithumbnail: Minithumbnail? + + /// The thumbnail of the album cover in JPEG format; as defined by the sender. The full size thumbnail is expected to be extracted from the downloaded audio file; may be null + public let albumCoverThumbnail: Thumbnail? + + /// File containing the audio + public let audio: File + + /// Duration of the audio, in seconds; as defined by the sender + public let duration: Int + + /// Album cover variants to use if the downloaded audio file contains no album cover. Provided thumbnail dimensions are approximate + public let externalAlbumCovers: [Thumbnail] + + /// Original name of the file; as defined by the sender + public let fileName: String + + /// The MIME type of the file; as defined by the sender + public let mimeType: String + + /// Performer of the audio; as defined by the sender + public let performer: String + + /// Title of the audio; as defined by the sender + public let title: String + + + public init( + albumCoverMinithumbnail: Minithumbnail?, + albumCoverThumbnail: Thumbnail?, + audio: File, + duration: Int, + externalAlbumCovers: [Thumbnail], + fileName: String, + mimeType: String, + performer: String, + title: String + ) { + self.albumCoverMinithumbnail = albumCoverMinithumbnail + self.albumCoverThumbnail = albumCoverThumbnail + self.audio = audio + self.duration = duration + self.externalAlbumCovers = externalAlbumCovers + self.fileName = fileName + self.mimeType = mimeType + self.performer = performer + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeInfo.swift new file mode 100644 index 0000000000..4e21729f01 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeInfo.swift @@ -0,0 +1,41 @@ +// +// AuthenticationCodeInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Information about the authentication code that was sent +public struct AuthenticationCodeInfo: Codable, Equatable, Hashable { + + /// The way the next code will be sent to the user; may be null + public let nextType: AuthenticationCodeType? + + /// A phone number that is being authenticated + public let phoneNumber: String + + /// Timeout before the code can be re-sent, in seconds + public let timeout: Int + + /// The way the code was sent to the user + public let type: AuthenticationCodeType + + + public init( + nextType: AuthenticationCodeType?, + phoneNumber: String, + timeout: Int, + type: AuthenticationCodeType + ) { + self.nextType = nextType + self.phoneNumber = phoneNumber + self.timeout = timeout + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeType.swift new file mode 100644 index 0000000000..25c342f60a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthenticationCodeType.swift @@ -0,0 +1,294 @@ +// +// AuthenticationCodeType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Provides information about the method by which an authentication code is delivered to the user +public indirect enum AuthenticationCodeType: Codable, Equatable, Hashable { + + /// A digit-only authentication code is delivered via a private Telegram message, which can be viewed from another active session + case authenticationCodeTypeTelegramMessage(AuthenticationCodeTypeTelegramMessage) + + /// A digit-only authentication code is delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code + case authenticationCodeTypeSms(AuthenticationCodeTypeSms) + + /// An authentication code is a word delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code + case authenticationCodeTypeSmsWord(AuthenticationCodeTypeSmsWord) + + /// An authentication code is a phrase from multiple words delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code + case authenticationCodeTypeSmsPhrase(AuthenticationCodeTypeSmsPhrase) + + /// A digit-only authentication code is delivered via a phone call to the specified phone number + case authenticationCodeTypeCall(AuthenticationCodeTypeCall) + + /// An authentication code is delivered by an immediately canceled call to the specified phone number. The phone number that calls is the code that must be entered automatically + case authenticationCodeTypeFlashCall(AuthenticationCodeTypeFlashCall) + + /// An authentication code is delivered by an immediately canceled call to the specified phone number. The last digits of the phone number that calls are the code that must be entered manually by the user + case authenticationCodeTypeMissedCall(AuthenticationCodeTypeMissedCall) + + /// A digit-only authentication code is delivered to https://fragment.com. The user must be logged in there via a wallet owning the phone number's NFT + case authenticationCodeTypeFragment(AuthenticationCodeTypeFragment) + + /// A digit-only authentication code is delivered via Firebase Authentication to the official Android application + case authenticationCodeTypeFirebaseAndroid(AuthenticationCodeTypeFirebaseAndroid) + + /// A digit-only authentication code is delivered via Firebase Authentication to the official iOS application + case authenticationCodeTypeFirebaseIos(AuthenticationCodeTypeFirebaseIos) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case authenticationCodeTypeTelegramMessage + case authenticationCodeTypeSms + case authenticationCodeTypeSmsWord + case authenticationCodeTypeSmsPhrase + case authenticationCodeTypeCall + case authenticationCodeTypeFlashCall + case authenticationCodeTypeMissedCall + case authenticationCodeTypeFragment + case authenticationCodeTypeFirebaseAndroid + case authenticationCodeTypeFirebaseIos + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .authenticationCodeTypeTelegramMessage: + let value = try AuthenticationCodeTypeTelegramMessage(from: decoder) + self = .authenticationCodeTypeTelegramMessage(value) + case .authenticationCodeTypeSms: + let value = try AuthenticationCodeTypeSms(from: decoder) + self = .authenticationCodeTypeSms(value) + case .authenticationCodeTypeSmsWord: + let value = try AuthenticationCodeTypeSmsWord(from: decoder) + self = .authenticationCodeTypeSmsWord(value) + case .authenticationCodeTypeSmsPhrase: + let value = try AuthenticationCodeTypeSmsPhrase(from: decoder) + self = .authenticationCodeTypeSmsPhrase(value) + case .authenticationCodeTypeCall: + let value = try AuthenticationCodeTypeCall(from: decoder) + self = .authenticationCodeTypeCall(value) + case .authenticationCodeTypeFlashCall: + let value = try AuthenticationCodeTypeFlashCall(from: decoder) + self = .authenticationCodeTypeFlashCall(value) + case .authenticationCodeTypeMissedCall: + let value = try AuthenticationCodeTypeMissedCall(from: decoder) + self = .authenticationCodeTypeMissedCall(value) + case .authenticationCodeTypeFragment: + let value = try AuthenticationCodeTypeFragment(from: decoder) + self = .authenticationCodeTypeFragment(value) + case .authenticationCodeTypeFirebaseAndroid: + let value = try AuthenticationCodeTypeFirebaseAndroid(from: decoder) + self = .authenticationCodeTypeFirebaseAndroid(value) + case .authenticationCodeTypeFirebaseIos: + let value = try AuthenticationCodeTypeFirebaseIos(from: decoder) + self = .authenticationCodeTypeFirebaseIos(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .authenticationCodeTypeTelegramMessage(let value): + try container.encode(Kind.authenticationCodeTypeTelegramMessage, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeSms(let value): + try container.encode(Kind.authenticationCodeTypeSms, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeSmsWord(let value): + try container.encode(Kind.authenticationCodeTypeSmsWord, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeSmsPhrase(let value): + try container.encode(Kind.authenticationCodeTypeSmsPhrase, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeCall(let value): + try container.encode(Kind.authenticationCodeTypeCall, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeFlashCall(let value): + try container.encode(Kind.authenticationCodeTypeFlashCall, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeMissedCall(let value): + try container.encode(Kind.authenticationCodeTypeMissedCall, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeFragment(let value): + try container.encode(Kind.authenticationCodeTypeFragment, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeFirebaseAndroid(let value): + try container.encode(Kind.authenticationCodeTypeFirebaseAndroid, forKey: .type) + try value.encode(to: encoder) + case .authenticationCodeTypeFirebaseIos(let value): + try container.encode(Kind.authenticationCodeTypeFirebaseIos, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A digit-only authentication code is delivered via a private Telegram message, which can be viewed from another active session +public struct AuthenticationCodeTypeTelegramMessage: Codable, Equatable, Hashable { + + /// Length of the code + public let length: Int + + + public init(length: Int) { + self.length = length + } +} + +/// A digit-only authentication code is delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code +public struct AuthenticationCodeTypeSms: Codable, Equatable, Hashable { + + /// Length of the code + public let length: Int + + + public init(length: Int) { + self.length = length + } +} + +/// An authentication code is a word delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code +public struct AuthenticationCodeTypeSmsWord: Codable, Equatable, Hashable { + + /// The first letters of the word if known + public let firstLetter: String + + + public init(firstLetter: String) { + self.firstLetter = firstLetter + } +} + +/// An authentication code is a phrase from multiple words delivered via an SMS message to the specified phone number; non-official applications may not receive this type of code +public struct AuthenticationCodeTypeSmsPhrase: Codable, Equatable, Hashable { + + /// The first word of the phrase if known + public let firstWord: String + + + public init(firstWord: String) { + self.firstWord = firstWord + } +} + +/// A digit-only authentication code is delivered via a phone call to the specified phone number +public struct AuthenticationCodeTypeCall: Codable, Equatable, Hashable { + + /// Length of the code + public let length: Int + + + public init(length: Int) { + self.length = length + } +} + +/// An authentication code is delivered by an immediately canceled call to the specified phone number. The phone number that calls is the code that must be entered automatically +public struct AuthenticationCodeTypeFlashCall: Codable, Equatable, Hashable { + + /// Pattern of the phone number from which the call will be made + public let pattern: String + + + public init(pattern: String) { + self.pattern = pattern + } +} + +/// An authentication code is delivered by an immediately canceled call to the specified phone number. The last digits of the phone number that calls are the code that must be entered manually by the user +public struct AuthenticationCodeTypeMissedCall: Codable, Equatable, Hashable { + + /// Number of digits in the code, excluding the prefix + public let length: Int + + /// Prefix of the phone number from which the call will be made + public let phoneNumberPrefix: String + + + public init( + length: Int, + phoneNumberPrefix: String + ) { + self.length = length + self.phoneNumberPrefix = phoneNumberPrefix + } +} + +/// A digit-only authentication code is delivered to https://fragment.com. The user must be logged in there via a wallet owning the phone number's NFT +public struct AuthenticationCodeTypeFragment: Codable, Equatable, Hashable { + + /// Length of the code + public let length: Int + + /// URL to open to receive the code + public let url: String + + + public init( + length: Int, + url: String + ) { + self.length = length + self.url = url + } +} + +/// A digit-only authentication code is delivered via Firebase Authentication to the official Android application +public struct AuthenticationCodeTypeFirebaseAndroid: Codable, Equatable, Hashable { + + /// Parameters to be used for device verification + public let deviceVerificationParameters: FirebaseDeviceVerificationParameters + + /// Length of the code + public let length: Int + + + public init( + deviceVerificationParameters: FirebaseDeviceVerificationParameters, + length: Int + ) { + self.deviceVerificationParameters = deviceVerificationParameters + self.length = length + } +} + +/// A digit-only authentication code is delivered via Firebase Authentication to the official iOS application +public struct AuthenticationCodeTypeFirebaseIos: Codable, Equatable, Hashable { + + /// Length of the code + public let length: Int + + /// Time after the next authentication method is expected to be used if verification push notification isn't received, in seconds + public let pushTimeout: Int + + /// Receipt of successful application token validation to compare with receipt from push notification + public let receipt: String + + + public init( + length: Int, + pushTimeout: Int, + receipt: String + ) { + self.length = length + self.pushTimeout = pushTimeout + self.receipt = receipt + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthorizationState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthorizationState.swift new file mode 100644 index 0000000000..4abb3bb4a4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/AuthorizationState.swift @@ -0,0 +1,301 @@ +// +// AuthorizationState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents the current authorization state of the TDLib client +public indirect enum AuthorizationState: Codable, Equatable, Hashable { + + /// Initialization parameters are needed. Call setTdlibParameters to provide them + case authorizationStateWaitTdlibParameters + + /// TDLib needs the user's phone number to authorize. Call setAuthenticationPhoneNumber to provide the phone number, or use requestQrCodeAuthentication, getAuthenticationPasskeyParameters, or checkAuthenticationBotToken for other authentication options + case authorizationStateWaitPhoneNumber + + /// The user must buy Telegram Premium as an in-store purchase to log in. Call checkAuthenticationPremiumPurchase and then setAuthenticationPremiumPurchaseTransaction + case authorizationStateWaitPremiumPurchase(AuthorizationStateWaitPremiumPurchase) + + /// TDLib needs the user's email address to authorize. Call setAuthenticationEmailAddress to provide the email address, or directly call checkAuthenticationEmailCode with Apple ID/Google ID token if allowed + case authorizationStateWaitEmailAddress(AuthorizationStateWaitEmailAddress) + + /// TDLib needs the user's authentication code sent to an email address to authorize. Call checkAuthenticationEmailCode to provide the code + case authorizationStateWaitEmailCode(AuthorizationStateWaitEmailCode) + + /// TDLib needs the user's authentication code to authorize. Call checkAuthenticationCode to check the code + case authorizationStateWaitCode(AuthorizationStateWaitCode) + + /// The user needs to confirm authorization on another logged in device by scanning a QR code with the provided link + case authorizationStateWaitOtherDeviceConfirmation(AuthorizationStateWaitOtherDeviceConfirmation) + + /// The user is unregistered and needs to accept terms of service and enter their first name and last name to finish registration. Call registerUser to accept the terms of service and provide the data + case authorizationStateWaitRegistration(AuthorizationStateWaitRegistration) + + /// The user has been authorized, but needs to enter a 2-step verification password to start using the application. Call checkAuthenticationPassword to provide the password, or requestAuthenticationPasswordRecovery to recover the password, or deleteAccount to delete the account after a week + case authorizationStateWaitPassword(AuthorizationStateWaitPassword) + + /// The user has been successfully authorized. TDLib is now ready to answer general requests + case authorizationStateReady + + /// The user is currently logging out + case authorizationStateLoggingOut + + /// TDLib is closing, all subsequent queries will be answered with the error 500. Note that closing TDLib can take a while. All resources will be freed only after authorizationStateClosed has been received + case authorizationStateClosing + + /// TDLib client is in its final state. All databases are closed and all resources are released. No other updates will be received after this. All queries will be responded to with error code 500. To continue working, one must create a new instance of the TDLib client + case authorizationStateClosed + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case authorizationStateWaitTdlibParameters + case authorizationStateWaitPhoneNumber + case authorizationStateWaitPremiumPurchase + case authorizationStateWaitEmailAddress + case authorizationStateWaitEmailCode + case authorizationStateWaitCode + case authorizationStateWaitOtherDeviceConfirmation + case authorizationStateWaitRegistration + case authorizationStateWaitPassword + case authorizationStateReady + case authorizationStateLoggingOut + case authorizationStateClosing + case authorizationStateClosed + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .authorizationStateWaitTdlibParameters: + self = .authorizationStateWaitTdlibParameters + case .authorizationStateWaitPhoneNumber: + self = .authorizationStateWaitPhoneNumber + case .authorizationStateWaitPremiumPurchase: + let value = try AuthorizationStateWaitPremiumPurchase(from: decoder) + self = .authorizationStateWaitPremiumPurchase(value) + case .authorizationStateWaitEmailAddress: + let value = try AuthorizationStateWaitEmailAddress(from: decoder) + self = .authorizationStateWaitEmailAddress(value) + case .authorizationStateWaitEmailCode: + let value = try AuthorizationStateWaitEmailCode(from: decoder) + self = .authorizationStateWaitEmailCode(value) + case .authorizationStateWaitCode: + let value = try AuthorizationStateWaitCode(from: decoder) + self = .authorizationStateWaitCode(value) + case .authorizationStateWaitOtherDeviceConfirmation: + let value = try AuthorizationStateWaitOtherDeviceConfirmation(from: decoder) + self = .authorizationStateWaitOtherDeviceConfirmation(value) + case .authorizationStateWaitRegistration: + let value = try AuthorizationStateWaitRegistration(from: decoder) + self = .authorizationStateWaitRegistration(value) + case .authorizationStateWaitPassword: + let value = try AuthorizationStateWaitPassword(from: decoder) + self = .authorizationStateWaitPassword(value) + case .authorizationStateReady: + self = .authorizationStateReady + case .authorizationStateLoggingOut: + self = .authorizationStateLoggingOut + case .authorizationStateClosing: + self = .authorizationStateClosing + case .authorizationStateClosed: + self = .authorizationStateClosed + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .authorizationStateWaitTdlibParameters: + try container.encode(Kind.authorizationStateWaitTdlibParameters, forKey: .type) + case .authorizationStateWaitPhoneNumber: + try container.encode(Kind.authorizationStateWaitPhoneNumber, forKey: .type) + case .authorizationStateWaitPremiumPurchase(let value): + try container.encode(Kind.authorizationStateWaitPremiumPurchase, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitEmailAddress(let value): + try container.encode(Kind.authorizationStateWaitEmailAddress, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitEmailCode(let value): + try container.encode(Kind.authorizationStateWaitEmailCode, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitCode(let value): + try container.encode(Kind.authorizationStateWaitCode, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitOtherDeviceConfirmation(let value): + try container.encode(Kind.authorizationStateWaitOtherDeviceConfirmation, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitRegistration(let value): + try container.encode(Kind.authorizationStateWaitRegistration, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateWaitPassword(let value): + try container.encode(Kind.authorizationStateWaitPassword, forKey: .type) + try value.encode(to: encoder) + case .authorizationStateReady: + try container.encode(Kind.authorizationStateReady, forKey: .type) + case .authorizationStateLoggingOut: + try container.encode(Kind.authorizationStateLoggingOut, forKey: .type) + case .authorizationStateClosing: + try container.encode(Kind.authorizationStateClosing, forKey: .type) + case .authorizationStateClosed: + try container.encode(Kind.authorizationStateClosed, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The user must buy Telegram Premium as an in-store purchase to log in. Call checkAuthenticationPremiumPurchase and then setAuthenticationPremiumPurchaseTransaction +public struct AuthorizationStateWaitPremiumPurchase: Codable, Equatable, Hashable { + + /// Duration of the Telegram Premium subscription after the purchase; may be 0 if Telegram Premium subscription will not be granted + public let premiumDayCount: Int + + /// Identifier of the store product that must be bought + public let storeProductId: String + + /// Email address to use for support if the user has issues with Telegram Premium purchase + public let supportEmailAddress: String + + /// Subject for the email sent to the support email address + public let supportEmailSubject: String + + + public init( + premiumDayCount: Int, + storeProductId: String, + supportEmailAddress: String, + supportEmailSubject: String + ) { + self.premiumDayCount = premiumDayCount + self.storeProductId = storeProductId + self.supportEmailAddress = supportEmailAddress + self.supportEmailSubject = supportEmailSubject + } +} + +/// TDLib needs the user's email address to authorize. Call setAuthenticationEmailAddress to provide the email address, or directly call checkAuthenticationEmailCode with Apple ID/Google ID token if allowed +public struct AuthorizationStateWaitEmailAddress: Codable, Equatable, Hashable { + + /// True, if authorization through Apple ID is allowed + public let allowAppleId: Bool + + /// True, if authorization through Google ID is allowed + public let allowGoogleId: Bool + + + public init( + allowAppleId: Bool, + allowGoogleId: Bool + ) { + self.allowAppleId = allowAppleId + self.allowGoogleId = allowGoogleId + } +} + +/// TDLib needs the user's authentication code sent to an email address to authorize. Call checkAuthenticationEmailCode to provide the code +public struct AuthorizationStateWaitEmailCode: Codable, Equatable, Hashable { + + /// True, if authorization through Apple ID is allowed + public let allowAppleId: Bool + + /// True, if authorization through Google ID is allowed + public let allowGoogleId: Bool + + /// Information about the sent authentication code + public let codeInfo: EmailAddressAuthenticationCodeInfo + + /// Reset state of the email address; may be null if the email address can't be reset + public let emailAddressResetState: EmailAddressResetState? + + + public init( + allowAppleId: Bool, + allowGoogleId: Bool, + codeInfo: EmailAddressAuthenticationCodeInfo, + emailAddressResetState: EmailAddressResetState? + ) { + self.allowAppleId = allowAppleId + self.allowGoogleId = allowGoogleId + self.codeInfo = codeInfo + self.emailAddressResetState = emailAddressResetState + } +} + +/// TDLib needs the user's authentication code to authorize. Call checkAuthenticationCode to check the code +public struct AuthorizationStateWaitCode: Codable, Equatable, Hashable { + + /// Information about the authorization code that was sent + public let codeInfo: AuthenticationCodeInfo + + + public init(codeInfo: AuthenticationCodeInfo) { + self.codeInfo = codeInfo + } +} + +/// The user needs to confirm authorization on another logged in device by scanning a QR code with the provided link +public struct AuthorizationStateWaitOtherDeviceConfirmation: Codable, Equatable, Hashable { + + /// A tg:// URL for the QR code. The link will be updated frequently + public let link: String + + + public init(link: String) { + self.link = link + } +} + +/// The user is unregistered and needs to accept terms of service and enter their first name and last name to finish registration. Call registerUser to accept the terms of service and provide the data +public struct AuthorizationStateWaitRegistration: Codable, Equatable, Hashable { + + /// Telegram terms of service + public let termsOfService: TermsOfService + + + public init(termsOfService: TermsOfService) { + self.termsOfService = termsOfService + } +} + +/// The user has been authorized, but needs to enter a 2-step verification password to start using the application. Call checkAuthenticationPassword to provide the password, or requestAuthenticationPasswordRecovery to recover the password, or deleteAccount to delete the account after a week +public struct AuthorizationStateWaitPassword: Codable, Equatable, Hashable { + + /// True, if some Telegram Passport elements were saved + public let hasPassportData: Bool + + /// True, if a recovery email address has been set up + public let hasRecoveryEmailAddress: Bool + + /// Hint for the password; may be empty + public let passwordHint: String + + /// Pattern of the email address to which the recovery email was sent; empty until a recovery email has been sent + public let recoveryEmailAddressPattern: String + + + public init( + hasPassportData: Bool, + hasRecoveryEmailAddress: Bool, + passwordHint: String, + recoveryEmailAddressPattern: String + ) { + self.hasPassportData = hasPassportData + self.hasRecoveryEmailAddress = hasRecoveryEmailAddress + self.passwordHint = passwordHint + self.recoveryEmailAddressPattern = recoveryEmailAddressPattern + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Background.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Background.swift new file mode 100644 index 0000000000..2c905e908f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Background.swift @@ -0,0 +1,51 @@ +// +// Background.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a chat background +public struct Background: Codable, Equatable, Hashable, Identifiable { + + /// Document with the background; may be null. Null only for filled and chat theme backgrounds + public let document: Document? + + /// Unique background identifier + public let id: TdInt64 + + /// True, if the background is dark and is recommended to be used with dark theme + public let isDark: Bool + + /// True, if this is one of default backgrounds + public let isDefault: Bool + + /// Unique background name + public let name: String + + /// Type of the background + public let type: BackgroundType + + + public init( + document: Document?, + id: TdInt64, + isDark: Bool, + isDefault: Bool, + name: String, + type: BackgroundType + ) { + self.document = document + self.id = id + self.isDark = isDark + self.isDefault = isDefault + self.name = name + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundFill.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundFill.swift new file mode 100644 index 0000000000..7a92e11bbe --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundFill.swift @@ -0,0 +1,119 @@ +// +// BackgroundFill.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a fill of a background +public indirect enum BackgroundFill: Codable, Equatable, Hashable { + + /// Describes a solid fill of a background + case backgroundFillSolid(BackgroundFillSolid) + + /// Describes a gradient fill of a background + case backgroundFillGradient(BackgroundFillGradient) + + /// Describes a freeform gradient fill of a background + case backgroundFillFreeformGradient(BackgroundFillFreeformGradient) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case backgroundFillSolid + case backgroundFillGradient + case backgroundFillFreeformGradient + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .backgroundFillSolid: + let value = try BackgroundFillSolid(from: decoder) + self = .backgroundFillSolid(value) + case .backgroundFillGradient: + let value = try BackgroundFillGradient(from: decoder) + self = .backgroundFillGradient(value) + case .backgroundFillFreeformGradient: + let value = try BackgroundFillFreeformGradient(from: decoder) + self = .backgroundFillFreeformGradient(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .backgroundFillSolid(let value): + try container.encode(Kind.backgroundFillSolid, forKey: .type) + try value.encode(to: encoder) + case .backgroundFillGradient(let value): + try container.encode(Kind.backgroundFillGradient, forKey: .type) + try value.encode(to: encoder) + case .backgroundFillFreeformGradient(let value): + try container.encode(Kind.backgroundFillFreeformGradient, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Describes a solid fill of a background +public struct BackgroundFillSolid: Codable, Equatable, Hashable { + + /// A color of the background in the RGB format + public let color: Int + + + public init(color: Int) { + self.color = color + } +} + +/// Describes a gradient fill of a background +public struct BackgroundFillGradient: Codable, Equatable, Hashable { + + /// A bottom color of the background in the RGB format + public let bottomColor: Int + + /// Clockwise rotation angle of the gradient, in degrees; 0-359. Must always be divisible by 45 + public let rotationAngle: Int + + /// A top color of the background in the RGB format + public let topColor: Int + + + public init( + bottomColor: Int, + rotationAngle: Int, + topColor: Int + ) { + self.bottomColor = bottomColor + self.rotationAngle = rotationAngle + self.topColor = topColor + } +} + +/// Describes a freeform gradient fill of a background +public struct BackgroundFillFreeformGradient: Codable, Equatable, Hashable { + + /// A list of 3 or 4 colors of the freeform gradient in the RGB format + public let colors: [Int] + + + public init(colors: [Int]) { + self.colors = colors + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundType.swift new file mode 100644 index 0000000000..35a708d02c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BackgroundType.swift @@ -0,0 +1,153 @@ +// +// BackgroundType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of background +public indirect enum BackgroundType: Codable, Equatable, Hashable { + + /// A wallpaper in JPEG format + case backgroundTypeWallpaper(BackgroundTypeWallpaper) + + /// A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user + case backgroundTypePattern(BackgroundTypePattern) + + /// A filled background + case backgroundTypeFill(BackgroundTypeFill) + + /// A background from a chat theme based on an emoji; can be used only as a chat background in channels + case backgroundTypeChatTheme(BackgroundTypeChatTheme) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case backgroundTypeWallpaper + case backgroundTypePattern + case backgroundTypeFill + case backgroundTypeChatTheme + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .backgroundTypeWallpaper: + let value = try BackgroundTypeWallpaper(from: decoder) + self = .backgroundTypeWallpaper(value) + case .backgroundTypePattern: + let value = try BackgroundTypePattern(from: decoder) + self = .backgroundTypePattern(value) + case .backgroundTypeFill: + let value = try BackgroundTypeFill(from: decoder) + self = .backgroundTypeFill(value) + case .backgroundTypeChatTheme: + let value = try BackgroundTypeChatTheme(from: decoder) + self = .backgroundTypeChatTheme(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .backgroundTypeWallpaper(let value): + try container.encode(Kind.backgroundTypeWallpaper, forKey: .type) + try value.encode(to: encoder) + case .backgroundTypePattern(let value): + try container.encode(Kind.backgroundTypePattern, forKey: .type) + try value.encode(to: encoder) + case .backgroundTypeFill(let value): + try container.encode(Kind.backgroundTypeFill, forKey: .type) + try value.encode(to: encoder) + case .backgroundTypeChatTheme(let value): + try container.encode(Kind.backgroundTypeChatTheme, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A wallpaper in JPEG format +public struct BackgroundTypeWallpaper: Codable, Equatable, Hashable { + + /// True, if the wallpaper must be downscaled to fit in 450x450 square and then box-blurred with radius 12 + public let isBlurred: Bool + + /// True, if the background needs to be slightly moved when device is tilted + public let isMoving: Bool + + + public init( + isBlurred: Bool, + isMoving: Bool + ) { + self.isBlurred = isBlurred + self.isMoving = isMoving + } +} + +/// A PNG or TGV (gzipped subset of SVG with MIME type "application/x-tgwallpattern") pattern to be combined with the background fill chosen by the user +public struct BackgroundTypePattern: Codable, Equatable, Hashable { + + /// Fill of the background + public let fill: BackgroundFill + + /// Intensity of the pattern when it is shown above the filled background; 0-100 + public let intensity: Int + + /// True, if the background fill must be applied only to the pattern itself. All other pixels are black in this case. For dark themes only + public let isInverted: Bool + + /// True, if the background needs to be slightly moved when device is tilted + public let isMoving: Bool + + + public init( + fill: BackgroundFill, + intensity: Int, + isInverted: Bool, + isMoving: Bool + ) { + self.fill = fill + self.intensity = intensity + self.isInverted = isInverted + self.isMoving = isMoving + } +} + +/// A filled background +public struct BackgroundTypeFill: Codable, Equatable, Hashable { + + /// The background fill + public let fill: BackgroundFill + + + public init(fill: BackgroundFill) { + self.fill = fill + } +} + +/// A background from a chat theme based on an emoji; can be used only as a chat background in channels +public struct BackgroundTypeChatTheme: Codable, Equatable, Hashable { + + /// Name of the emoji chat theme + public let themeName: String + + + public init(themeName: String) { + self.themeName = themeName + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Birthdate.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Birthdate.swift new file mode 100644 index 0000000000..99276bd8a3 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Birthdate.swift @@ -0,0 +1,36 @@ +// +// Birthdate.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a birthdate of a user +public struct Birthdate: Codable, Equatable, Hashable { + + /// Day of the month; 1-31 + public let day: Int + + /// Month of the year; 1-12 + public let month: Int + + /// Birth year; 0 if unknown + public let year: Int + + + public init( + day: Int, + month: Int, + year: Int + ) { + self.day = day + self.month = month + self.year = year + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BlockList.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BlockList.swift new file mode 100644 index 0000000000..153de9734f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BlockList.swift @@ -0,0 +1,57 @@ +// +// BlockList.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of block list +public indirect enum BlockList: Codable, Equatable, Hashable { + + /// The main block list that disallows writing messages to the current user, receiving their status and photo, viewing of stories, and some other actions + case blockListMain + + /// The block list that disallows viewing of stories of the current user + case blockListStories + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case blockListMain + case blockListStories + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .blockListMain: + self = .blockListMain + case .blockListStories: + self = .blockListStories + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .blockListMain: + try container.encode(Kind.blockListMain, forKey: .type) + case .blockListStories: + try container.encode(Kind.blockListStories, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BotWriteAccessAllowReason.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BotWriteAccessAllowReason.swift new file mode 100644 index 0000000000..5f8c0b846f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BotWriteAccessAllowReason.swift @@ -0,0 +1,101 @@ +// +// BotWriteAccessAllowReason.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a reason why a bot was allowed to write messages to the current user +public indirect enum BotWriteAccessAllowReason: Codable, Equatable, Hashable { + + /// The user connected a website by logging in using Telegram Login Widget on it + case botWriteAccessAllowReasonConnectedWebsite(BotWriteAccessAllowReasonConnectedWebsite) + + /// The user added the bot to attachment or side menu using toggleBotIsAddedToAttachmentMenu + case botWriteAccessAllowReasonAddedToAttachmentMenu + + /// The user launched a Web App using getWebAppLinkUrl + case botWriteAccessAllowReasonLaunchedWebApp(BotWriteAccessAllowReasonLaunchedWebApp) + + /// The user accepted bot's request to send messages with allowBotToSendMessages + case botWriteAccessAllowReasonAcceptedRequest + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case botWriteAccessAllowReasonConnectedWebsite + case botWriteAccessAllowReasonAddedToAttachmentMenu + case botWriteAccessAllowReasonLaunchedWebApp + case botWriteAccessAllowReasonAcceptedRequest + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .botWriteAccessAllowReasonConnectedWebsite: + let value = try BotWriteAccessAllowReasonConnectedWebsite(from: decoder) + self = .botWriteAccessAllowReasonConnectedWebsite(value) + case .botWriteAccessAllowReasonAddedToAttachmentMenu: + self = .botWriteAccessAllowReasonAddedToAttachmentMenu + case .botWriteAccessAllowReasonLaunchedWebApp: + let value = try BotWriteAccessAllowReasonLaunchedWebApp(from: decoder) + self = .botWriteAccessAllowReasonLaunchedWebApp(value) + case .botWriteAccessAllowReasonAcceptedRequest: + self = .botWriteAccessAllowReasonAcceptedRequest + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .botWriteAccessAllowReasonConnectedWebsite(let value): + try container.encode(Kind.botWriteAccessAllowReasonConnectedWebsite, forKey: .type) + try value.encode(to: encoder) + case .botWriteAccessAllowReasonAddedToAttachmentMenu: + try container.encode(Kind.botWriteAccessAllowReasonAddedToAttachmentMenu, forKey: .type) + case .botWriteAccessAllowReasonLaunchedWebApp(let value): + try container.encode(Kind.botWriteAccessAllowReasonLaunchedWebApp, forKey: .type) + try value.encode(to: encoder) + case .botWriteAccessAllowReasonAcceptedRequest: + try container.encode(Kind.botWriteAccessAllowReasonAcceptedRequest, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The user connected a website by logging in using Telegram Login Widget on it +public struct BotWriteAccessAllowReasonConnectedWebsite: Codable, Equatable, Hashable { + + /// Domain name of the connected website + public let domainName: String + + + public init(domainName: String) { + self.domainName = domainName + } +} + +/// The user launched a Web App using getWebAppLinkUrl +public struct BotWriteAccessAllowReasonLaunchedWebApp: Codable, Equatable, Hashable { + + /// Information about the Web App + public let webApp: WebApp + + + public init(webApp: WebApp) { + self.webApp = webApp + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BuiltInTheme.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BuiltInTheme.swift new file mode 100644 index 0000000000..977a1ab88e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BuiltInTheme.swift @@ -0,0 +1,81 @@ +// +// BuiltInTheme.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a built-in theme of an official application +public indirect enum BuiltInTheme: Codable, Equatable, Hashable { + + /// Classic light theme + case builtInThemeClassic + + /// Regular light theme + case builtInThemeDay + + /// Regular dark theme + case builtInThemeNight + + /// Tinted dark theme + case builtInThemeTinted + + /// Arctic light theme + case builtInThemeArctic + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case builtInThemeClassic + case builtInThemeDay + case builtInThemeNight + case builtInThemeTinted + case builtInThemeArctic + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .builtInThemeClassic: + self = .builtInThemeClassic + case .builtInThemeDay: + self = .builtInThemeDay + case .builtInThemeNight: + self = .builtInThemeNight + case .builtInThemeTinted: + self = .builtInThemeTinted + case .builtInThemeArctic: + self = .builtInThemeArctic + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .builtInThemeClassic: + try container.encode(Kind.builtInThemeClassic, forKey: .type) + case .builtInThemeDay: + try container.encode(Kind.builtInThemeDay, forKey: .type) + case .builtInThemeNight: + try container.encode(Kind.builtInThemeNight, forKey: .type) + case .builtInThemeTinted: + try container.encode(Kind.builtInThemeTinted, forKey: .type) + case .builtInThemeArctic: + try container.encode(Kind.builtInThemeArctic, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BusinessBotManageBar.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BusinessBotManageBar.swift new file mode 100644 index 0000000000..b25ad743ea --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/BusinessBotManageBar.swift @@ -0,0 +1,41 @@ +// +// BusinessBotManageBar.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a business bot that manages the chat +public struct BusinessBotManageBar: Codable, Equatable, Hashable { + + /// User identifier of the bot + public let botUserId: Int64 + + /// True, if the bot can reply + public let canBotReply: Bool + + /// True, if the bot is paused. Use toggleBusinessConnectedBotChatIsPaused to change the value of the field + public let isBotPaused: Bool + + /// URL to be opened to manage the bot + public let manageUrl: String + + + public init( + botUserId: Int64, + canBotReply: Bool, + isBotPaused: Bool, + manageUrl: String + ) { + self.botUserId = botUserId + self.canBotReply = canBotReply + self.isBotPaused = isBotPaused + self.manageUrl = manageUrl + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ButtonStyle.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ButtonStyle.swift new file mode 100644 index 0000000000..6b0c83b497 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ButtonStyle.swift @@ -0,0 +1,73 @@ +// +// ButtonStyle.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes style of a button +public indirect enum ButtonStyle: Codable, Equatable, Hashable { + + /// The button has default style + case buttonStyleDefault + + /// The button has dark blue color + case buttonStylePrimary + + /// The button has red color + case buttonStyleDanger + + /// The button has green color + case buttonStyleSuccess + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case buttonStyleDefault + case buttonStylePrimary + case buttonStyleDanger + case buttonStyleSuccess + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .buttonStyleDefault: + self = .buttonStyleDefault + case .buttonStylePrimary: + self = .buttonStylePrimary + case .buttonStyleDanger: + self = .buttonStyleDanger + case .buttonStyleSuccess: + self = .buttonStyleSuccess + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .buttonStyleDefault: + try container.encode(Kind.buttonStyleDefault, forKey: .type) + case .buttonStylePrimary: + try container.encode(Kind.buttonStylePrimary, forKey: .type) + case .buttonStyleDanger: + try container.encode(Kind.buttonStyleDanger, forKey: .type) + case .buttonStyleSuccess: + try container.encode(Kind.buttonStyleSuccess, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CallDiscardReason.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CallDiscardReason.swift new file mode 100644 index 0000000000..0eb1d7979b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CallDiscardReason.swift @@ -0,0 +1,103 @@ +// +// CallDiscardReason.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the reason why a call was discarded +public indirect enum CallDiscardReason: Codable, Equatable, Hashable { + + /// The call wasn't discarded, or the reason is unknown + case callDiscardReasonEmpty + + /// The call was ended before the conversation started. It was canceled by the caller or missed by the other party + case callDiscardReasonMissed + + /// The call was ended before the conversation started. It was declined by the other party + case callDiscardReasonDeclined + + /// The call was ended during the conversation because the users were disconnected + case callDiscardReasonDisconnected + + /// The call was ended because one of the parties hung up + case callDiscardReasonHungUp + + /// The call was ended because it has been upgraded to a group call + case callDiscardReasonUpgradeToGroupCall(CallDiscardReasonUpgradeToGroupCall) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case callDiscardReasonEmpty + case callDiscardReasonMissed + case callDiscardReasonDeclined + case callDiscardReasonDisconnected + case callDiscardReasonHungUp + case callDiscardReasonUpgradeToGroupCall + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .callDiscardReasonEmpty: + self = .callDiscardReasonEmpty + case .callDiscardReasonMissed: + self = .callDiscardReasonMissed + case .callDiscardReasonDeclined: + self = .callDiscardReasonDeclined + case .callDiscardReasonDisconnected: + self = .callDiscardReasonDisconnected + case .callDiscardReasonHungUp: + self = .callDiscardReasonHungUp + case .callDiscardReasonUpgradeToGroupCall: + let value = try CallDiscardReasonUpgradeToGroupCall(from: decoder) + self = .callDiscardReasonUpgradeToGroupCall(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .callDiscardReasonEmpty: + try container.encode(Kind.callDiscardReasonEmpty, forKey: .type) + case .callDiscardReasonMissed: + try container.encode(Kind.callDiscardReasonMissed, forKey: .type) + case .callDiscardReasonDeclined: + try container.encode(Kind.callDiscardReasonDeclined, forKey: .type) + case .callDiscardReasonDisconnected: + try container.encode(Kind.callDiscardReasonDisconnected, forKey: .type) + case .callDiscardReasonHungUp: + try container.encode(Kind.callDiscardReasonHungUp, forKey: .type) + case .callDiscardReasonUpgradeToGroupCall(let value): + try container.encode(Kind.callDiscardReasonUpgradeToGroupCall, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The call was ended because it has been upgraded to a group call +public struct CallDiscardReasonUpgradeToGroupCall: Codable, Equatable, Hashable { + + /// Invite link for the group call + public let inviteLink: String + + + public init(inviteLink: String) { + self.inviteLink = inviteLink + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CancelDownloadFile.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CancelDownloadFile.swift new file mode 100644 index 0000000000..4fc2aa0094 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CancelDownloadFile.swift @@ -0,0 +1,31 @@ +// +// CancelDownloadFile.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Stops the downloading of a file. If a file has already been downloaded, does nothing +public struct CancelDownloadFile: Codable, Equatable, Hashable { + + /// Identifier of a file to stop downloading + public let fileId: Int? + + /// Pass true to stop downloading only if it hasn't been started, i.e. request hasn't been sent to server + public let onlyIfPending: Bool? + + + public init( + fileId: Int?, + onlyIfPending: Bool? + ) { + self.fileId = fileId + self.onlyIfPending = onlyIfPending + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Chat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Chat.swift new file mode 100644 index 0000000000..23b9711c44 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Chat.swift @@ -0,0 +1,236 @@ +// +// Chat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// A chat. (Can be a private chat, basic group, supergroup, or secret chat) +public struct Chat: Codable, Equatable, Hashable, Identifiable { + + /// Identifier of the accent color for message sender name, and backgrounds of chat photo, reply header, and link preview + public let accentColorId: Int + + /// Information about actions which must be possible to do through the chat action bar; may be null if none + public let actionBar: ChatActionBar? + + /// Types of reaction, available in the chat + public let availableReactions: ChatAvailableReactions + + /// Background set for the chat; may be null if none + public let background: ChatBackground? + + /// Identifier of a custom emoji to be shown on the reply header and link preview background for messages sent by the chat; 0 if none + public let backgroundCustomEmojiId: TdInt64 + + /// Block list to which the chat is added; may be null if none + public let blockList: BlockList? + + /// Information about bar for managing a business bot in the chat; may be null if none + public let businessBotManageBar: BusinessBotManageBar? + + /// True, if the chat messages can be deleted for all users + public let canBeDeletedForAllUsers: Bool + + /// True, if the chat messages can be deleted only for the current user while other users will continue to see the messages + public let canBeDeletedOnlyForSelf: Bool + + /// True, if the chat can be reported to Telegram moderators through reportChat or reportChatPhoto + public let canBeReported: Bool + + /// Chat lists to which the chat belongs. A chat can have a non-zero position in a chat list even if it doesn't belong to the chat list and have no position in a chat list even if it belongs to the chat list + public let chatLists: [ChatList] + + /// Application-specific data associated with the chat. (For example, the chat scroll position or local chat notification settings can be stored here.) Persistent if the message database is used + public let clientData: String + + /// Default value of the disable_notification parameter, used when a message is sent to the chat + public let defaultDisableNotification: Bool + + /// A draft of a message in the chat; may be null if none + public let draftMessage: DraftMessage? + + /// Emoji status to be shown along with chat title; may be null + public let emojiStatus: EmojiStatus? + + /// True, if chat content can't be saved locally, forwarded, or copied + public let hasProtectedContent: Bool + + /// True, if the chat has scheduled messages + public let hasScheduledMessages: Bool + + /// Chat unique identifier + public let id: Int64 + + /// True, if the chat is marked as unread + public let isMarkedAsUnread: Bool + + /// True, if translation of all messages in the chat must be suggested to the user + public let isTranslatable: Bool + + /// Last message in the chat; may be null if none or unknown + public let lastMessage: Message? + + /// Identifier of the last read incoming message + public let lastReadInboxMessageId: Int64 + + /// Identifier of the last read outgoing message + public let lastReadOutboxMessageId: Int64 + + /// Current message auto-delete or self-destruct timer setting for the chat, in seconds; 0 if disabled. Self-destruct timer in secret chats starts after the message or its content is viewed. Auto-delete timer in other chats starts from the send date + public let messageAutoDeleteTime: Int + + /// Identifier of a user or chat that is selected to send messages in the chat; may be null if the user can't change message sender + public let messageSenderId: MessageSender? + + /// Notification settings for the chat + public let notificationSettings: ChatNotificationSettings + + /// Information about pending join requests; may be null if none + public let pendingJoinRequests: ChatJoinRequestsInfo? + + /// Actions that non-administrator chat members are allowed to take in the chat + public let permissions: ChatPermissions + + /// Chat photo; may be null + public let photo: ChatPhotoInfo? + + /// Positions of the chat in chat lists + public let positions: [ChatPosition] + + /// Identifier of the profile accent color for the chat's profile; -1 if none + public let profileAccentColorId: Int + + /// Identifier of a custom emoji to be shown on the background of the chat's profile; 0 if none + public let profileBackgroundCustomEmojiId: TdInt64 + + /// Identifier of the message from which reply markup needs to be used; 0 if there is no reply markup in the chat + public let replyMarkupMessageId: Int64 + + /// Theme set for the chat; may be null if none + public let theme: ChatTheme? + + /// Chat title + public let title: String + + /// Type of the chat + public let type: ChatType + + /// Number of unread messages in the chat + public let unreadCount: Int + + /// Number of unread messages with a mention/reply in the chat + public let unreadMentionCount: Int + + /// Number of messages with unread poll votes in the chat + public let unreadPollVoteCount: Int + + /// Number of messages with unread reactions in the chat + public let unreadReactionCount: Int + + /// Color scheme based on an upgraded gift to be used for the chat instead of accent_color_id and background_custom_emoji_id; may be null if none + public let upgradedGiftColors: UpgradedGiftColors? + + /// Information about video chat of the chat + public let videoChat: VideoChat + + /// True, if the chat is a forum supergroup that must be shown in the "View as topics" mode, or Saved Messages chat that must be shown in the "View as chats" + public let viewAsTopics: Bool + + + public init( + accentColorId: Int, + actionBar: ChatActionBar?, + availableReactions: ChatAvailableReactions, + background: ChatBackground?, + backgroundCustomEmojiId: TdInt64, + blockList: BlockList?, + businessBotManageBar: BusinessBotManageBar?, + canBeDeletedForAllUsers: Bool, + canBeDeletedOnlyForSelf: Bool, + canBeReported: Bool, + chatLists: [ChatList], + clientData: String, + defaultDisableNotification: Bool, + draftMessage: DraftMessage?, + emojiStatus: EmojiStatus?, + hasProtectedContent: Bool, + hasScheduledMessages: Bool, + id: Int64, + isMarkedAsUnread: Bool, + isTranslatable: Bool, + lastMessage: Message?, + lastReadInboxMessageId: Int64, + lastReadOutboxMessageId: Int64, + messageAutoDeleteTime: Int, + messageSenderId: MessageSender?, + notificationSettings: ChatNotificationSettings, + pendingJoinRequests: ChatJoinRequestsInfo?, + permissions: ChatPermissions, + photo: ChatPhotoInfo?, + positions: [ChatPosition], + profileAccentColorId: Int, + profileBackgroundCustomEmojiId: TdInt64, + replyMarkupMessageId: Int64, + theme: ChatTheme?, + title: String, + type: ChatType, + unreadCount: Int, + unreadMentionCount: Int, + unreadPollVoteCount: Int, + unreadReactionCount: Int, + upgradedGiftColors: UpgradedGiftColors?, + videoChat: VideoChat, + viewAsTopics: Bool + ) { + self.accentColorId = accentColorId + self.actionBar = actionBar + self.availableReactions = availableReactions + self.background = background + self.backgroundCustomEmojiId = backgroundCustomEmojiId + self.blockList = blockList + self.businessBotManageBar = businessBotManageBar + self.canBeDeletedForAllUsers = canBeDeletedForAllUsers + self.canBeDeletedOnlyForSelf = canBeDeletedOnlyForSelf + self.canBeReported = canBeReported + self.chatLists = chatLists + self.clientData = clientData + self.defaultDisableNotification = defaultDisableNotification + self.draftMessage = draftMessage + self.emojiStatus = emojiStatus + self.hasProtectedContent = hasProtectedContent + self.hasScheduledMessages = hasScheduledMessages + self.id = id + self.isMarkedAsUnread = isMarkedAsUnread + self.isTranslatable = isTranslatable + self.lastMessage = lastMessage + self.lastReadInboxMessageId = lastReadInboxMessageId + self.lastReadOutboxMessageId = lastReadOutboxMessageId + self.messageAutoDeleteTime = messageAutoDeleteTime + self.messageSenderId = messageSenderId + self.notificationSettings = notificationSettings + self.pendingJoinRequests = pendingJoinRequests + self.permissions = permissions + self.photo = photo + self.positions = positions + self.profileAccentColorId = profileAccentColorId + self.profileBackgroundCustomEmojiId = profileBackgroundCustomEmojiId + self.replyMarkupMessageId = replyMarkupMessageId + self.theme = theme + self.title = title + self.type = type + self.unreadCount = unreadCount + self.unreadMentionCount = unreadMentionCount + self.unreadPollVoteCount = unreadPollVoteCount + self.unreadReactionCount = unreadReactionCount + self.upgradedGiftColors = upgradedGiftColors + self.videoChat = videoChat + self.viewAsTopics = viewAsTopics + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatActionBar.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatActionBar.swift new file mode 100644 index 0000000000..c7753c0280 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatActionBar.swift @@ -0,0 +1,150 @@ +// +// ChatActionBar.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes actions which must be possible to do through a chat action bar +public indirect enum ChatActionBar: Codable, Equatable, Hashable { + + /// The chat can be reported as spam using the method reportChat with an empty option_id and message_ids. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown + case chatActionBarReportSpam(ChatActionBarReportSpam) + + /// The chat is a recently created group chat to which new members can be invited + case chatActionBarInviteMembers + + /// The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method setMessageSenderBlockList, or the other user can be added to the contact list using the method addContact. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown + case chatActionBarReportAddBlock(ChatActionBarReportAddBlock) + + /// The chat is a private or secret chat and the other user can be added to the contact list using the method addContact + case chatActionBarAddContact + + /// The chat is a private or secret chat with a mutual contact and the user's phone number can be shared with the other user using the method sharePhoneNumber + case chatActionBarSharePhoneNumber + + /// The chat is a private chat with an administrator of a chat to which the user sent join request + case chatActionBarJoinRequest(ChatActionBarJoinRequest) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatActionBarReportSpam + case chatActionBarInviteMembers + case chatActionBarReportAddBlock + case chatActionBarAddContact + case chatActionBarSharePhoneNumber + case chatActionBarJoinRequest + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatActionBarReportSpam: + let value = try ChatActionBarReportSpam(from: decoder) + self = .chatActionBarReportSpam(value) + case .chatActionBarInviteMembers: + self = .chatActionBarInviteMembers + case .chatActionBarReportAddBlock: + let value = try ChatActionBarReportAddBlock(from: decoder) + self = .chatActionBarReportAddBlock(value) + case .chatActionBarAddContact: + self = .chatActionBarAddContact + case .chatActionBarSharePhoneNumber: + self = .chatActionBarSharePhoneNumber + case .chatActionBarJoinRequest: + let value = try ChatActionBarJoinRequest(from: decoder) + self = .chatActionBarJoinRequest(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatActionBarReportSpam(let value): + try container.encode(Kind.chatActionBarReportSpam, forKey: .type) + try value.encode(to: encoder) + case .chatActionBarInviteMembers: + try container.encode(Kind.chatActionBarInviteMembers, forKey: .type) + case .chatActionBarReportAddBlock(let value): + try container.encode(Kind.chatActionBarReportAddBlock, forKey: .type) + try value.encode(to: encoder) + case .chatActionBarAddContact: + try container.encode(Kind.chatActionBarAddContact, forKey: .type) + case .chatActionBarSharePhoneNumber: + try container.encode(Kind.chatActionBarSharePhoneNumber, forKey: .type) + case .chatActionBarJoinRequest(let value): + try container.encode(Kind.chatActionBarJoinRequest, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The chat can be reported as spam using the method reportChat with an empty option_id and message_ids. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown +public struct ChatActionBarReportSpam: Codable, Equatable, Hashable { + + /// If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings + public let canUnarchive: Bool + + + public init(canUnarchive: Bool) { + self.canUnarchive = canUnarchive + } +} + +/// The chat is a private or secret chat, which can be reported using the method reportChat, or the other user can be blocked using the method setMessageSenderBlockList, or the other user can be added to the contact list using the method addContact. If the chat is a private chat with a user with an emoji status, then a notice about emoji status usage must be shown +public struct ChatActionBarReportAddBlock: Codable, Equatable, Hashable { + + /// Basic information about the other user in the chat; may be null if unknown + public let accountInfo: AccountInfo? + + /// If true, the chat was automatically archived and can be moved back to the main chat list using addChatToList simultaneously with setting chat notification settings to default using setChatNotificationSettings + public let canUnarchive: Bool + + + public init( + accountInfo: AccountInfo?, + canUnarchive: Bool + ) { + self.accountInfo = accountInfo + self.canUnarchive = canUnarchive + } +} + +/// The chat is a private chat with an administrator of a chat to which the user sent join request +public struct ChatActionBarJoinRequest: Codable, Equatable, Hashable { + + /// True, if the join request was sent to a channel chat + public let isChannel: Bool + + /// Point in time (Unix timestamp) when the join request was sent + public let requestDate: Int + + /// Title of the chat to which the join request was sent + public let title: String + + + public init( + isChannel: Bool, + requestDate: Int, + title: String + ) { + self.isChannel = isChannel + self.requestDate = requestDate + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAdministratorRights.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAdministratorRights.swift new file mode 100644 index 0000000000..9721d3237f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAdministratorRights.swift @@ -0,0 +1,106 @@ +// +// ChatAdministratorRights.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes rights of the administrator +public struct ChatAdministratorRights: Codable, Equatable, Hashable { + + /// True, if the administrator can change the chat title, photo, and other settings + public let canChangeInfo: Bool + + /// True, if the administrator can delete messages of other users + public let canDeleteMessages: Bool + + /// True, if the administrator can delete stories posted by other users; applicable to supergroups and channels only + public let canDeleteStories: Bool + + /// True, if the administrator can edit messages of other users and pin messages; applicable to channels only + public let canEditMessages: Bool + + /// True, if the administrator can edit stories posted by other users, post stories to the chat page, pin chat stories, and access story archive; applicable to supergroups and channels only + public let canEditStories: Bool + + /// True, if the administrator can invite new users to the chat + public let canInviteUsers: Bool + + /// True, if the administrator can access the chat event log, get boost list, see hidden supergroup and channel members, report supergroup spam messages, ignore slow mode, and send messages to the chat without paying Telegram Stars. Implied by any other privilege; applicable to supergroups and channels only + public let canManageChat: Bool + + /// True, if the administrator can answer to channel direct messages; applicable to channels only + public let canManageDirectMessages: Bool + + /// True, if the administrator can change tags of other users; applicable to basic groups and supergroups only + public let canManageTags: Bool + + /// True, if the administrator can create, rename, close, reopen, hide, and unhide forum topics; applicable to forum supergroups only + public let canManageTopics: Bool + + /// True, if the administrator can manage video chats + public let canManageVideoChats: Bool + + /// True, if the administrator can pin messages; applicable to basic groups and supergroups only + public let canPinMessages: Bool + + /// True, if the administrator can create channel posts, approve suggested channel posts, or view channel statistics; applicable to channels only + public let canPostMessages: Bool + + /// True, if the administrator can create new chat stories, or edit and delete posted stories; applicable to supergroups and channels only + public let canPostStories: Bool + + /// True, if the administrator can add new administrators with a subset of their own privileges or demote administrators that were directly or indirectly promoted by them + public let canPromoteMembers: Bool + + /// True, if the administrator can restrict, ban, or unban chat members or view supergroup statistics + public let canRestrictMembers: Bool + + /// True, if the administrator isn't shown in the chat member list and sends messages anonymously; applicable to supergroups only + public let isAnonymous: Bool + + + public init( + canChangeInfo: Bool, + canDeleteMessages: Bool, + canDeleteStories: Bool, + canEditMessages: Bool, + canEditStories: Bool, + canInviteUsers: Bool, + canManageChat: Bool, + canManageDirectMessages: Bool, + canManageTags: Bool, + canManageTopics: Bool, + canManageVideoChats: Bool, + canPinMessages: Bool, + canPostMessages: Bool, + canPostStories: Bool, + canPromoteMembers: Bool, + canRestrictMembers: Bool, + isAnonymous: Bool + ) { + self.canChangeInfo = canChangeInfo + self.canDeleteMessages = canDeleteMessages + self.canDeleteStories = canDeleteStories + self.canEditMessages = canEditMessages + self.canEditStories = canEditStories + self.canInviteUsers = canInviteUsers + self.canManageChat = canManageChat + self.canManageDirectMessages = canManageDirectMessages + self.canManageTags = canManageTags + self.canManageTopics = canManageTopics + self.canManageVideoChats = canManageVideoChats + self.canPinMessages = canPinMessages + self.canPostMessages = canPostMessages + self.canPostStories = canPostStories + self.canPromoteMembers = canPromoteMembers + self.canRestrictMembers = canRestrictMembers + self.isAnonymous = isAnonymous + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAvailableReactions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAvailableReactions.swift new file mode 100644 index 0000000000..a30a34ac33 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatAvailableReactions.swift @@ -0,0 +1,92 @@ +// +// ChatAvailableReactions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes reactions available in the chat +public indirect enum ChatAvailableReactions: Codable, Equatable, Hashable { + + /// All reactions are available in the chat, excluding the paid reaction and custom reactions in channel chats + case chatAvailableReactionsAll(ChatAvailableReactionsAll) + + /// Only specific reactions are available in the chat + case chatAvailableReactionsSome(ChatAvailableReactionsSome) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatAvailableReactionsAll + case chatAvailableReactionsSome + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatAvailableReactionsAll: + let value = try ChatAvailableReactionsAll(from: decoder) + self = .chatAvailableReactionsAll(value) + case .chatAvailableReactionsSome: + let value = try ChatAvailableReactionsSome(from: decoder) + self = .chatAvailableReactionsSome(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatAvailableReactionsAll(let value): + try container.encode(Kind.chatAvailableReactionsAll, forKey: .type) + try value.encode(to: encoder) + case .chatAvailableReactionsSome(let value): + try container.encode(Kind.chatAvailableReactionsSome, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// All reactions are available in the chat, excluding the paid reaction and custom reactions in channel chats +public struct ChatAvailableReactionsAll: Codable, Equatable, Hashable { + + /// The maximum allowed number of reactions per message; 1-11 + public let maxReactionCount: Int + + + public init(maxReactionCount: Int) { + self.maxReactionCount = maxReactionCount + } +} + +/// Only specific reactions are available in the chat +public struct ChatAvailableReactionsSome: Codable, Equatable, Hashable { + + /// The maximum allowed number of reactions per message; 1-11 + public let maxReactionCount: Int + + /// The list of reactions + public let reactions: [ReactionType] + + + public init( + maxReactionCount: Int, + reactions: [ReactionType] + ) { + self.maxReactionCount = maxReactionCount + self.reactions = reactions + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatBackground.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatBackground.swift new file mode 100644 index 0000000000..981df35b15 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatBackground.swift @@ -0,0 +1,31 @@ +// +// ChatBackground.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a background set for a specific chat +public struct ChatBackground: Codable, Equatable, Hashable { + + /// The background + public let background: Background + + /// Dimming of the background in dark themes, as a percentage; 0-100. Applied only to Wallpaper and Fill types of background + public let darkThemeDimming: Int + + + public init( + background: Background, + darkThemeDimming: Int + ) { + self.background = background + self.darkThemeDimming = darkThemeDimming + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderIcon.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderIcon.swift new file mode 100644 index 0000000000..b67ea39dc4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderIcon.swift @@ -0,0 +1,24 @@ +// +// ChatFolderIcon.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents an icon for a chat folder +public struct ChatFolderIcon: Codable, Equatable, Hashable { + + /// The chosen icon name for short folder representation; one of "All", "Unread", "Unmuted", "Bots", "Channels", "Groups", "Private", "Custom", "Setup", "Cat", "Crown", "Favorite", "Flower", "Game", "Home", "Love", "Mask", "Party", "Sport", "Study", "Trade", "Travel", "Work", "Airplane", "Book", "Light", "Like", "Money", "Note", "Palette" + public let name: String + + + public init(name: String) { + self.name = name + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderInfo.swift new file mode 100644 index 0000000000..74fc09da9d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderInfo.swift @@ -0,0 +1,51 @@ +// +// ChatFolderInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains basic information about a chat folder +public struct ChatFolderInfo: Codable, Equatable, Hashable, Identifiable { + + /// The identifier of the chosen color for the chat folder icon; from -1 to 6. If -1, then color is disabled + public let colorId: Int + + /// True, if the chat folder has invite links created by the current user + public let hasMyInviteLinks: Bool + + /// The chosen or default icon for the chat folder + public let icon: ChatFolderIcon + + /// Unique chat folder identifier + public let id: Int + + /// True, if at least one link has been created for the folder + public let isShareable: Bool + + /// The name of the folder + public let name: ChatFolderName + + + public init( + colorId: Int, + hasMyInviteLinks: Bool, + icon: ChatFolderIcon, + id: Int, + isShareable: Bool, + name: ChatFolderName + ) { + self.colorId = colorId + self.hasMyInviteLinks = hasMyInviteLinks + self.icon = icon + self.id = id + self.isShareable = isShareable + self.name = name + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderName.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderName.swift new file mode 100644 index 0000000000..4b3bde14e0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatFolderName.swift @@ -0,0 +1,31 @@ +// +// ChatFolderName.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes name of a chat folder +public struct ChatFolderName: Codable, Equatable, Hashable { + + /// True, if custom emoji in the name must be animated + public let animateCustomEmoji: Bool + + /// The text of the chat folder name; 1-12 characters without line feeds. May contain only CustomEmoji entities + public let text: FormattedText + + + public init( + animateCustomEmoji: Bool, + text: FormattedText + ) { + self.animateCustomEmoji = animateCustomEmoji + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatJoinRequestsInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatJoinRequestsInfo.swift new file mode 100644 index 0000000000..7a982f652b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatJoinRequestsInfo.swift @@ -0,0 +1,31 @@ +// +// ChatJoinRequestsInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about pending join requests for a chat +public struct ChatJoinRequestsInfo: Codable, Equatable, Hashable { + + /// Total number of pending join requests + public let totalCount: Int + + /// Identifiers of at most 3 users sent the newest pending join requests + public let userIds: [Int64] + + + public init( + totalCount: Int, + userIds: [Int64] + ) { + self.totalCount = totalCount + self.userIds = userIds + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatList.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatList.swift new file mode 100644 index 0000000000..68719004ff --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatList.swift @@ -0,0 +1,79 @@ +// +// ChatList.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a list of chats +public indirect enum ChatList: Codable, Equatable, Hashable { + + /// A main list of chats + case chatListMain + + /// A list of chats usually located at the top of the main chat list. Unmuted chats are automatically moved from the Archive to the Main chat list when a new message arrives + case chatListArchive + + /// A list of chats added to a chat folder + case chatListFolder(ChatListFolder) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatListMain + case chatListArchive + case chatListFolder + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatListMain: + self = .chatListMain + case .chatListArchive: + self = .chatListArchive + case .chatListFolder: + let value = try ChatListFolder(from: decoder) + self = .chatListFolder(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatListMain: + try container.encode(Kind.chatListMain, forKey: .type) + case .chatListArchive: + try container.encode(Kind.chatListArchive, forKey: .type) + case .chatListFolder(let value): + try container.encode(Kind.chatListFolder, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A list of chats added to a chat folder +public struct ChatListFolder: Codable, Equatable, Hashable { + + /// Chat folder identifier + public let chatFolderId: Int + + + public init(chatFolderId: Int) { + self.chatFolderId = chatFolderId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatNotificationSettings.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatNotificationSettings.swift new file mode 100644 index 0000000000..3e373e35cc --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatNotificationSettings.swift @@ -0,0 +1,101 @@ +// +// ChatNotificationSettings.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about notification settings for a chat or a forum topic +public struct ChatNotificationSettings: Codable, Equatable, Hashable { + + /// If true, notifications for messages with mentions will be created as for an ordinary unread message + public let disableMentionNotifications: Bool + + /// If true, notifications for incoming pinned messages will be created as for an ordinary unread message + public let disablePinnedMessageNotifications: Bool + + /// Time left before notifications will be unmuted, in seconds + public let muteFor: Int + + /// True, if story notifications are disabled for the chat + public let muteStories: Bool + + /// True, if message content must be displayed in notifications + public let showPreview: Bool + + /// True, if the chat that posted a story must be displayed in notifications + public let showStoryPoster: Bool + + /// Identifier of the notification sound to be played for messages; 0 if sound is disabled + public let soundId: TdInt64 + + /// Identifier of the notification sound to be played for stories; 0 if sound is disabled + public let storySoundId: TdInt64 + + /// If true, the value for the relevant type of chat or the forum chat is used instead of disable_mention_notifications + public let useDefaultDisableMentionNotifications: Bool + + /// If true, the value for the relevant type of chat or the forum chat is used instead of disable_pinned_message_notifications + public let useDefaultDisablePinnedMessageNotifications: Bool + + /// If true, the value for the relevant type of chat or the forum chat is used instead of mute_for + public let useDefaultMuteFor: Bool + + /// If true, the value for the relevant type of chat is used instead of mute_stories + public let useDefaultMuteStories: Bool + + /// If true, the value for the relevant type of chat or the forum chat is used instead of show_preview + public let useDefaultShowPreview: Bool + + /// If true, the value for the relevant type of chat is used instead of show_story_poster + public let useDefaultShowStoryPoster: Bool + + /// If true, the value for the relevant type of chat or the forum chat is used instead of sound_id + public let useDefaultSound: Bool + + /// If true, the value for the relevant type of chat is used instead of story_sound_id + public let useDefaultStorySound: Bool + + + public init( + disableMentionNotifications: Bool, + disablePinnedMessageNotifications: Bool, + muteFor: Int, + muteStories: Bool, + showPreview: Bool, + showStoryPoster: Bool, + soundId: TdInt64, + storySoundId: TdInt64, + useDefaultDisableMentionNotifications: Bool, + useDefaultDisablePinnedMessageNotifications: Bool, + useDefaultMuteFor: Bool, + useDefaultMuteStories: Bool, + useDefaultShowPreview: Bool, + useDefaultShowStoryPoster: Bool, + useDefaultSound: Bool, + useDefaultStorySound: Bool + ) { + self.disableMentionNotifications = disableMentionNotifications + self.disablePinnedMessageNotifications = disablePinnedMessageNotifications + self.muteFor = muteFor + self.muteStories = muteStories + self.showPreview = showPreview + self.showStoryPoster = showStoryPoster + self.soundId = soundId + self.storySoundId = storySoundId + self.useDefaultDisableMentionNotifications = useDefaultDisableMentionNotifications + self.useDefaultDisablePinnedMessageNotifications = useDefaultDisablePinnedMessageNotifications + self.useDefaultMuteFor = useDefaultMuteFor + self.useDefaultMuteStories = useDefaultMuteStories + self.useDefaultShowPreview = useDefaultShowPreview + self.useDefaultShowStoryPoster = useDefaultShowStoryPoster + self.useDefaultSound = useDefaultSound + self.useDefaultStorySound = useDefaultStorySound + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPermissions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPermissions.swift new file mode 100644 index 0000000000..45331472d5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPermissions.swift @@ -0,0 +1,101 @@ +// +// ChatPermissions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes actions that a user is allowed to take in a chat +public struct ChatPermissions: Codable, Equatable, Hashable { + + /// True, if the user may add a link preview to their messages + public let canAddLinkPreviews: Bool + + /// True, if the user can change the chat title, photo, and other settings + public let canChangeInfo: Bool + + /// True, if the user can create topics + public let canCreateTopics: Bool + + /// True, if the user may change the tag of self + public let canEditTag: Bool + + /// True, if the user can invite new users to the chat + public let canInviteUsers: Bool + + /// True, if the user can pin messages + public let canPinMessages: Bool + + /// True, if the user can react to messages + public let canReactToMessages: Bool + + /// True, if the user can send music files + public let canSendAudios: Bool + + /// True, if the user can send text messages, contacts, giveaways, giveaway winners, invoices, locations, and venues + public let canSendBasicMessages: Bool + + /// True, if the user can send documents + public let canSendDocuments: Bool + + /// True, if the user can send animations, games, stickers, and dice and use inline bots + public let canSendOtherMessages: Bool + + /// True, if the user can send photos + public let canSendPhotos: Bool + + /// True, if the user can send polls and checklists + public let canSendPolls: Bool + + /// True, if the user can send video notes + public let canSendVideoNotes: Bool + + /// True, if the user can send videos + public let canSendVideos: Bool + + /// True, if the user can send voice notes + public let canSendVoiceNotes: Bool + + + public init( + canAddLinkPreviews: Bool, + canChangeInfo: Bool, + canCreateTopics: Bool, + canEditTag: Bool, + canInviteUsers: Bool, + canPinMessages: Bool, + canReactToMessages: Bool, + canSendAudios: Bool, + canSendBasicMessages: Bool, + canSendDocuments: Bool, + canSendOtherMessages: Bool, + canSendPhotos: Bool, + canSendPolls: Bool, + canSendVideoNotes: Bool, + canSendVideos: Bool, + canSendVoiceNotes: Bool + ) { + self.canAddLinkPreviews = canAddLinkPreviews + self.canChangeInfo = canChangeInfo + self.canCreateTopics = canCreateTopics + self.canEditTag = canEditTag + self.canInviteUsers = canInviteUsers + self.canPinMessages = canPinMessages + self.canReactToMessages = canReactToMessages + self.canSendAudios = canSendAudios + self.canSendBasicMessages = canSendBasicMessages + self.canSendDocuments = canSendDocuments + self.canSendOtherMessages = canSendOtherMessages + self.canSendPhotos = canSendPhotos + self.canSendPolls = canSendPolls + self.canSendVideoNotes = canSendVideoNotes + self.canSendVideos = canSendVideos + self.canSendVoiceNotes = canSendVoiceNotes + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhoto.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhoto.swift new file mode 100644 index 0000000000..6c78ad4160 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhoto.swift @@ -0,0 +1,56 @@ +// +// ChatPhoto.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a chat or user profile photo +public struct ChatPhoto: Codable, Equatable, Hashable, Identifiable { + + /// Point in time (Unix timestamp) when the photo has been added + public let addedDate: Int + + /// A big (up to 1280x1280) animated variant of the photo in MPEG4 format; may be null + public let animation: AnimatedChatPhoto? + + /// Unique photo identifier + public let id: TdInt64 + + /// Photo minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Available variants of the photo in JPEG format, in different size + public let sizes: [PhotoSize] + + /// A small (160x160) animated variant of the photo in MPEG4 format; may be null even if the big animation is available + public let smallAnimation: AnimatedChatPhoto? + + /// Sticker-based version of the chat photo; may be null + public let sticker: ChatPhotoSticker? + + + public init( + addedDate: Int, + animation: AnimatedChatPhoto?, + id: TdInt64, + minithumbnail: Minithumbnail?, + sizes: [PhotoSize], + smallAnimation: AnimatedChatPhoto?, + sticker: ChatPhotoSticker? + ) { + self.addedDate = addedDate + self.animation = animation + self.id = id + self.minithumbnail = minithumbnail + self.sizes = sizes + self.smallAnimation = smallAnimation + self.sticker = sticker + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoInfo.swift new file mode 100644 index 0000000000..c82bd590ac --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoInfo.swift @@ -0,0 +1,46 @@ +// +// ChatPhotoInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains basic information about the photo of a chat +public struct ChatPhotoInfo: Codable, Equatable, Hashable { + + /// A big (640x640) chat photo variant in JPEG format. The file can be downloaded only before the photo is changed + public let big: File + + /// True, if the photo has animated variant + public let hasAnimation: Bool + + /// True, if the photo is visible only for the current user + public let isPersonal: Bool + + /// Chat photo minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// A small (160x160) chat photo variant in JPEG format. The file can be downloaded only before the photo is changed + public let small: File + + + public init( + big: File, + hasAnimation: Bool, + isPersonal: Bool, + minithumbnail: Minithumbnail?, + small: File + ) { + self.big = big + self.hasAnimation = hasAnimation + self.isPersonal = isPersonal + self.minithumbnail = minithumbnail + self.small = small + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoSticker.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoSticker.swift new file mode 100644 index 0000000000..9d1944bbe8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoSticker.swift @@ -0,0 +1,31 @@ +// +// ChatPhotoSticker.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Information about the sticker, which was used to create the chat photo. The sticker is shown at the center of the photo and occupies at most 67% of it +public struct ChatPhotoSticker: Codable, Equatable, Hashable { + + /// The fill to be used as background for the sticker; rotation angle in backgroundFillGradient isn't supported + public let backgroundFill: BackgroundFill + + /// Type of the sticker + public let type: ChatPhotoStickerType + + + public init( + backgroundFill: BackgroundFill, + type: ChatPhotoStickerType + ) { + self.backgroundFill = backgroundFill + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoStickerType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoStickerType.swift new file mode 100644 index 0000000000..1af796d2da --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPhotoStickerType.swift @@ -0,0 +1,92 @@ +// +// ChatPhotoStickerType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of sticker, which was used to create a chat photo +public indirect enum ChatPhotoStickerType: Codable, Equatable, Hashable { + + /// Information about the sticker, which was used to create the chat photo + case chatPhotoStickerTypeRegularOrMask(ChatPhotoStickerTypeRegularOrMask) + + /// Information about the custom emoji, which was used to create the chat photo + case chatPhotoStickerTypeCustomEmoji(ChatPhotoStickerTypeCustomEmoji) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatPhotoStickerTypeRegularOrMask + case chatPhotoStickerTypeCustomEmoji + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatPhotoStickerTypeRegularOrMask: + let value = try ChatPhotoStickerTypeRegularOrMask(from: decoder) + self = .chatPhotoStickerTypeRegularOrMask(value) + case .chatPhotoStickerTypeCustomEmoji: + let value = try ChatPhotoStickerTypeCustomEmoji(from: decoder) + self = .chatPhotoStickerTypeCustomEmoji(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatPhotoStickerTypeRegularOrMask(let value): + try container.encode(Kind.chatPhotoStickerTypeRegularOrMask, forKey: .type) + try value.encode(to: encoder) + case .chatPhotoStickerTypeCustomEmoji(let value): + try container.encode(Kind.chatPhotoStickerTypeCustomEmoji, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Information about the sticker, which was used to create the chat photo +public struct ChatPhotoStickerTypeRegularOrMask: Codable, Equatable, Hashable { + + /// Identifier of the sticker in the set + public let stickerId: TdInt64 + + /// Sticker set identifier + public let stickerSetId: TdInt64 + + + public init( + stickerId: TdInt64, + stickerSetId: TdInt64 + ) { + self.stickerId = stickerId + self.stickerSetId = stickerSetId + } +} + +/// Information about the custom emoji, which was used to create the chat photo +public struct ChatPhotoStickerTypeCustomEmoji: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji + public let customEmojiId: TdInt64 + + + public init(customEmojiId: TdInt64) { + self.customEmojiId = customEmojiId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPosition.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPosition.swift new file mode 100644 index 0000000000..8d9e48197c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatPosition.swift @@ -0,0 +1,41 @@ +// +// ChatPosition.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a position of a chat in a chat list +public struct ChatPosition: Codable, Equatable, Hashable { + + /// True, if the chat is pinned in the chat list + public let isPinned: Bool + + /// The chat list + public let list: ChatList + + /// A parameter used to determine order of the chat in the chat list. Chats must be sorted by the pair (order, chat.id) in descending order + public let order: TdInt64 + + /// Source of the chat in the chat list; may be null + public let source: ChatSource? + + + public init( + isPinned: Bool, + list: ChatList, + order: TdInt64, + source: ChatSource? + ) { + self.isPinned = isPinned + self.list = list + self.order = order + self.source = source + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatSource.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatSource.swift new file mode 100644 index 0000000000..7469364bf7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatSource.swift @@ -0,0 +1,78 @@ +// +// ChatSource.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a reason why an external chat is shown in a chat list +public indirect enum ChatSource: Codable, Equatable, Hashable { + + /// The chat is sponsored by the user's MTProxy server + case chatSourceMtprotoProxy + + /// The chat contains a public service announcement + case chatSourcePublicServiceAnnouncement(ChatSourcePublicServiceAnnouncement) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatSourceMtprotoProxy + case chatSourcePublicServiceAnnouncement + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatSourceMtprotoProxy: + self = .chatSourceMtprotoProxy + case .chatSourcePublicServiceAnnouncement: + let value = try ChatSourcePublicServiceAnnouncement(from: decoder) + self = .chatSourcePublicServiceAnnouncement(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatSourceMtprotoProxy: + try container.encode(Kind.chatSourceMtprotoProxy, forKey: .type) + case .chatSourcePublicServiceAnnouncement(let value): + try container.encode(Kind.chatSourcePublicServiceAnnouncement, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The chat contains a public service announcement +public struct ChatSourcePublicServiceAnnouncement: Codable, Equatable, Hashable { + + /// The text of the announcement + public let text: String + + /// The type of the announcement + public let type: String + + + public init( + text: String, + type: String + ) { + self.text = text + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatTheme.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatTheme.swift new file mode 100644 index 0000000000..73161e2511 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatTheme.swift @@ -0,0 +1,85 @@ +// +// ChatTheme.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a chat theme +public indirect enum ChatTheme: Codable, Equatable, Hashable { + + /// A chat theme based on an emoji + case chatThemeEmoji(ChatThemeEmoji) + + /// A chat theme based on an upgraded gift + case chatThemeGift(ChatThemeGift) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatThemeEmoji + case chatThemeGift + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatThemeEmoji: + let value = try ChatThemeEmoji(from: decoder) + self = .chatThemeEmoji(value) + case .chatThemeGift: + let value = try ChatThemeGift(from: decoder) + self = .chatThemeGift(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatThemeEmoji(let value): + try container.encode(Kind.chatThemeEmoji, forKey: .type) + try value.encode(to: encoder) + case .chatThemeGift(let value): + try container.encode(Kind.chatThemeGift, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A chat theme based on an emoji +public struct ChatThemeEmoji: Codable, Equatable, Hashable { + + /// Name of the theme; full theme description is received through updateEmojiChatThemes + public let name: String + + + public init(name: String) { + self.name = name + } +} + +/// A chat theme based on an upgraded gift +public struct ChatThemeGift: Codable, Equatable, Hashable { + + /// The chat theme + public let giftTheme: GiftChatTheme + + + public init(giftTheme: GiftChatTheme) { + self.giftTheme = giftTheme + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatType.swift new file mode 100644 index 0000000000..8b32bb502b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChatType.swift @@ -0,0 +1,143 @@ +// +// ChatType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of chat +public indirect enum ChatType: Codable, Equatable, Hashable { + + /// An ordinary chat with a user + case chatTypePrivate(ChatTypePrivate) + + /// A basic group (a chat with 0-200 other users) + case chatTypeBasicGroup(ChatTypeBasicGroup) + + /// A supergroup or channel (with unlimited members) + case chatTypeSupergroup(ChatTypeSupergroup) + + /// A secret chat with a user + case chatTypeSecret(ChatTypeSecret) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case chatTypePrivate + case chatTypeBasicGroup + case chatTypeSupergroup + case chatTypeSecret + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .chatTypePrivate: + let value = try ChatTypePrivate(from: decoder) + self = .chatTypePrivate(value) + case .chatTypeBasicGroup: + let value = try ChatTypeBasicGroup(from: decoder) + self = .chatTypeBasicGroup(value) + case .chatTypeSupergroup: + let value = try ChatTypeSupergroup(from: decoder) + self = .chatTypeSupergroup(value) + case .chatTypeSecret: + let value = try ChatTypeSecret(from: decoder) + self = .chatTypeSecret(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .chatTypePrivate(let value): + try container.encode(Kind.chatTypePrivate, forKey: .type) + try value.encode(to: encoder) + case .chatTypeBasicGroup(let value): + try container.encode(Kind.chatTypeBasicGroup, forKey: .type) + try value.encode(to: encoder) + case .chatTypeSupergroup(let value): + try container.encode(Kind.chatTypeSupergroup, forKey: .type) + try value.encode(to: encoder) + case .chatTypeSecret(let value): + try container.encode(Kind.chatTypeSecret, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// An ordinary chat with a user +public struct ChatTypePrivate: Codable, Equatable, Hashable { + + /// User identifier + public let userId: Int64 + + + public init(userId: Int64) { + self.userId = userId + } +} + +/// A basic group (a chat with 0-200 other users) +public struct ChatTypeBasicGroup: Codable, Equatable, Hashable { + + /// Basic group identifier + public let basicGroupId: Int64 + + + public init(basicGroupId: Int64) { + self.basicGroupId = basicGroupId + } +} + +/// A supergroup or channel (with unlimited members) +public struct ChatTypeSupergroup: Codable, Equatable, Hashable { + + /// True, if the supergroup is a channel + public let isChannel: Bool + + /// Supergroup or channel identifier + public let supergroupId: Int64 + + + public init( + isChannel: Bool, + supergroupId: Int64 + ) { + self.isChannel = isChannel + self.supergroupId = supergroupId + } +} + +/// A secret chat with a user +public struct ChatTypeSecret: Codable, Equatable, Hashable { + + /// Secret chat identifier + public let secretChatId: Int + + /// User identifier of the other user in the secret chat + public let userId: Int64 + + + public init( + secretChatId: Int, + userId: Int64 + ) { + self.secretChatId = secretChatId + self.userId = userId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CheckAuthenticationPassword.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CheckAuthenticationPassword.swift new file mode 100644 index 0000000000..7cdeeb4c7d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CheckAuthenticationPassword.swift @@ -0,0 +1,24 @@ +// +// CheckAuthenticationPassword.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Checks the 2-step verification password for correctness. Works only when the current authorization state is authorizationStateWaitPassword +public struct CheckAuthenticationPassword: Codable, Equatable, Hashable { + + /// The 2-step verification password to check + public let password: String? + + + public init(password: String?) { + self.password = password + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Checklist.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Checklist.swift new file mode 100644 index 0000000000..c0831081c4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Checklist.swift @@ -0,0 +1,51 @@ +// +// Checklist.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a checklist +public struct Checklist: Codable, Equatable, Hashable { + + /// True, if the current user can add tasks to the list if they have Telegram Premium subscription + public let canAddTasks: Bool + + /// True, if the current user can mark tasks as done or not done if they have Telegram Premium subscription + public let canMarkTasksAsDone: Bool + + /// True, if users other than creator of the list can add tasks to the list + public let othersCanAddTasks: Bool + + /// True, if users other than creator of the list can mark tasks as done or not done. If true, then the checklist is called "group checklist" + public let othersCanMarkTasksAsDone: Bool + + /// List of tasks in the checklist + public let tasks: [ChecklistTask] + + /// Title of the checklist; may contain only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, and DateTime entities + public let title: FormattedText + + + public init( + canAddTasks: Bool, + canMarkTasksAsDone: Bool, + othersCanAddTasks: Bool, + othersCanMarkTasksAsDone: Bool, + tasks: [ChecklistTask], + title: FormattedText + ) { + self.canAddTasks = canAddTasks + self.canMarkTasksAsDone = canMarkTasksAsDone + self.othersCanAddTasks = othersCanAddTasks + self.othersCanMarkTasksAsDone = othersCanMarkTasksAsDone + self.tasks = tasks + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChecklistTask.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChecklistTask.swift new file mode 100644 index 0000000000..803e2d613b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ChecklistTask.swift @@ -0,0 +1,41 @@ +// +// ChecklistTask.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a task in a checklist +public struct ChecklistTask: Codable, Equatable, Hashable, Identifiable { + + /// Identifier of the user or chat that completed the task; may be null if the task isn't completed yet + public let completedBy: MessageSender? + + /// Point in time (Unix timestamp) when the task was completed; 0 if the task isn't completed + public let completionDate: Int + + /// Unique identifier of the task + public let id: Int + + /// Text of the task; may contain only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, DateTime and automatically found entities + public let text: FormattedText + + + public init( + completedBy: MessageSender?, + completionDate: Int, + id: Int, + text: FormattedText + ) { + self.completedBy = completedBy + self.completionDate = completionDate + self.id = id + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Close.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Close.swift new file mode 100644 index 0000000000..e522fcd0c3 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Close.swift @@ -0,0 +1,19 @@ +// +// Close.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Closes the TDLib instance. All databases will be flushed to disk and properly closed. After the close completes, updateAuthorizationState with authorizationStateClosed will be sent. Can be called before initialization +public struct Close: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CloseChat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CloseChat.swift new file mode 100644 index 0000000000..2c8461d2ce --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/CloseChat.swift @@ -0,0 +1,24 @@ +// +// CloseChat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Informs TDLib that the chat is closed by the user. Many useful activities depend on the chat being opened or closed +public struct CloseChat: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64? + + + public init(chatId: Int64?) { + self.chatId = chatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ClosedVectorPath.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ClosedVectorPath.swift new file mode 100644 index 0000000000..9663ce1b2f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ClosedVectorPath.swift @@ -0,0 +1,24 @@ +// +// ClosedVectorPath.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a closed vector path. The path begins at the end point of the last command. The coordinate system origin is in the upper-left corner +public struct ClosedVectorPath: Codable, Equatable, Hashable { + + /// List of vector path commands + public let commands: [VectorPathCommand] + + + public init(commands: [VectorPathCommand]) { + self.commands = commands + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Contact.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Contact.swift new file mode 100644 index 0000000000..54df65d920 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Contact.swift @@ -0,0 +1,46 @@ +// +// Contact.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a contact of a user +public struct Contact: Codable, Equatable, Hashable { + + /// First name of the user; 1-64 characters + public let firstName: String + + /// Last name of the user; 0-64 characters + public let lastName: String + + /// Phone number of the user + public let phoneNumber: String + + /// Identifier of the user, if known; 0 otherwise + public let userId: Int64 + + /// Additional data about the user in a form of vCard; 0-2048 bytes in length + public let vcard: String + + + public init( + firstName: String, + lastName: String, + phoneNumber: String, + userId: Int64, + vcard: String + ) { + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + self.userId = userId + self.vcard = vcard + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimeFormattingType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimeFormattingType.swift new file mode 100644 index 0000000000..ac2113c30c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimeFormattingType.swift @@ -0,0 +1,83 @@ +// +// DateTimeFormattingType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes date and time formatting +public indirect enum DateTimeFormattingType: Codable, Equatable, Hashable { + + /// The time must be shown relative to the current time ([in ] X seconds, minutes, hours, days, months, years [ago]) + case dateTimeFormattingTypeRelative + + /// The date and time must be shown as absolute timestamps + case dateTimeFormattingTypeAbsolute(DateTimeFormattingTypeAbsolute) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case dateTimeFormattingTypeRelative + case dateTimeFormattingTypeAbsolute + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .dateTimeFormattingTypeRelative: + self = .dateTimeFormattingTypeRelative + case .dateTimeFormattingTypeAbsolute: + let value = try DateTimeFormattingTypeAbsolute(from: decoder) + self = .dateTimeFormattingTypeAbsolute(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .dateTimeFormattingTypeRelative: + try container.encode(Kind.dateTimeFormattingTypeRelative, forKey: .type) + case .dateTimeFormattingTypeAbsolute(let value): + try container.encode(Kind.dateTimeFormattingTypeAbsolute, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The date and time must be shown as absolute timestamps +public struct DateTimeFormattingTypeAbsolute: Codable, Equatable, Hashable { + + /// The precision with which the date is shown + public let datePrecision: DateTimePartPrecision + + /// True, if the day of week must be shown + public let showDayOfWeek: Bool + + /// The precision with which hours, minutes and seconds are shown + public let timePrecision: DateTimePartPrecision + + + public init( + datePrecision: DateTimePartPrecision, + showDayOfWeek: Bool, + timePrecision: DateTimePartPrecision + ) { + self.datePrecision = datePrecision + self.showDayOfWeek = showDayOfWeek + self.timePrecision = timePrecision + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimePartPrecision.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimePartPrecision.swift new file mode 100644 index 0000000000..8297b49046 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DateTimePartPrecision.swift @@ -0,0 +1,65 @@ +// +// DateTimePartPrecision.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes precision with which to show a date or a time +public indirect enum DateTimePartPrecision: Codable, Equatable, Hashable { + + /// Don't show the date or time + case dateTimePartPrecisionNone + + /// Show the date or time in a short way (17.03.22 or 22:45) + case dateTimePartPrecisionShort + + /// Show the date or time in a long way (March 17, 2022 or 22:45:00) + case dateTimePartPrecisionLong + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case dateTimePartPrecisionNone + case dateTimePartPrecisionShort + case dateTimePartPrecisionLong + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .dateTimePartPrecisionNone: + self = .dateTimePartPrecisionNone + case .dateTimePartPrecisionShort: + self = .dateTimePartPrecisionShort + case .dateTimePartPrecisionLong: + self = .dateTimePartPrecisionLong + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .dateTimePartPrecisionNone: + try container.encode(Kind.dateTimePartPrecisionNone, forKey: .type) + case .dateTimePartPrecisionShort: + try container.encode(Kind.dateTimePartPrecisionShort, forKey: .type) + case .dateTimePartPrecisionLong: + try container.encode(Kind.dateTimePartPrecisionLong, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Destroy.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Destroy.swift new file mode 100644 index 0000000000..a96c5f5cd3 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Destroy.swift @@ -0,0 +1,19 @@ +// +// Destroy.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Closes the TDLib instance, destroying all local data without a proper logout. The current user session will remain in the list of all active sessions. All local data will be destroyed. After the destruction completes updateAuthorizationState with authorizationStateClosed will be sent. Can be called before authorization +public struct Destroy: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DiceStickers.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DiceStickers.swift new file mode 100644 index 0000000000..7889bdb3bc --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DiceStickers.swift @@ -0,0 +1,107 @@ +// +// DiceStickers.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains animated stickers which must be used for dice animation rendering +public indirect enum DiceStickers: Codable, Equatable, Hashable { + + /// A regular animated sticker + case diceStickersRegular(DiceStickersRegular) + + /// Animated stickers to be combined into a slot machine + case diceStickersSlotMachine(DiceStickersSlotMachine) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case diceStickersRegular + case diceStickersSlotMachine + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .diceStickersRegular: + let value = try DiceStickersRegular(from: decoder) + self = .diceStickersRegular(value) + case .diceStickersSlotMachine: + let value = try DiceStickersSlotMachine(from: decoder) + self = .diceStickersSlotMachine(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .diceStickersRegular(let value): + try container.encode(Kind.diceStickersRegular, forKey: .type) + try value.encode(to: encoder) + case .diceStickersSlotMachine(let value): + try container.encode(Kind.diceStickersSlotMachine, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A regular animated sticker +public struct DiceStickersRegular: Codable, Equatable, Hashable { + + /// The animated sticker with the dice animation + public let sticker: Sticker + + + public init(sticker: Sticker) { + self.sticker = sticker + } +} + +/// Animated stickers to be combined into a slot machine +public struct DiceStickersSlotMachine: Codable, Equatable, Hashable { + + /// The animated sticker with the slot machine background. The background animation must start playing after all reel animations finish + public let background: Sticker + + /// The animated sticker with the center reel + public let centerReel: Sticker + + /// The animated sticker with the left reel + public let leftReel: Sticker + + /// The animated sticker with the lever animation. The lever animation must play once in the initial dice state + public let lever: Sticker + + /// The animated sticker with the right reel + public let rightReel: Sticker + + + public init( + background: Sticker, + centerReel: Sticker, + leftReel: Sticker, + lever: Sticker, + rightReel: Sticker + ) { + self.background = background + self.centerReel = centerReel + self.leftReel = leftReel + self.lever = lever + self.rightReel = rightReel + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Document.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Document.swift new file mode 100644 index 0000000000..b4481660ec --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Document.swift @@ -0,0 +1,46 @@ +// +// Document.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a document of any type +public struct Document: Codable, Equatable, Hashable { + + /// File containing the document + public let document: File + + /// Original name of the file; as defined by the sender + public let fileName: String + + /// MIME type of the file; as defined by the sender + public let mimeType: String + + /// Document minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Document thumbnail in JPEG or PNG format (PNG will be used only for background patterns); as defined by the sender; may be null + public let thumbnail: Thumbnail? + + + public init( + document: File, + fileName: String, + mimeType: String, + minithumbnail: Minithumbnail?, + thumbnail: Thumbnail? + ) { + self.document = document + self.fileName = fileName + self.mimeType = mimeType + self.minithumbnail = minithumbnail + self.thumbnail = thumbnail + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DownloadFile.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DownloadFile.swift new file mode 100644 index 0000000000..f67b8bb867 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DownloadFile.swift @@ -0,0 +1,46 @@ +// +// DownloadFile.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Downloads a file from the cloud. Download progress and completion of the download will be notified through updateFile updates +public struct DownloadFile: Codable, Equatable, Hashable { + + /// Identifier of the file to download + public let fileId: Int? + + /// Number of bytes which need to be downloaded starting from the "offset" position before the download will automatically be canceled; use 0 to download without a limit + public let limit: Int64? + + /// The starting position from which the file needs to be downloaded + public let offset: Int64? + + /// Priority of the download (1-32). The higher the priority, the earlier the file will be downloaded. If the priorities of two files are equal, then the last one for which downloadFile/addFileToDownloads was called will be downloaded first + public let priority: Int? + + /// Pass true to return response only after the file download has succeeded, has failed, has been canceled, or a new downloadFile request with different offset/limit parameters was sent; pass false to return file state immediately, just after the download has been started + public let synchronous: Bool? + + + public init( + fileId: Int?, + limit: Int64?, + offset: Int64?, + priority: Int?, + synchronous: Bool? + ) { + self.fileId = fileId + self.limit = limit + self.offset = offset + self.priority = priority + self.synchronous = synchronous + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DraftMessage.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DraftMessage.swift new file mode 100644 index 0000000000..c4386b78a7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/DraftMessage.swift @@ -0,0 +1,46 @@ +// +// DraftMessage.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a message draft +public struct DraftMessage: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the draft was created + public let date: Int + + /// Identifier of the effect to apply to the message when it is sent; 0 if none + public let effectId: TdInt64 + + /// Content of the message draft; must be of the type inputMessageText, inputMessageVideoNote, or inputMessageVoiceNote + public let inputMessageText: InputMessageContent + + /// Information about the message to be replied; inputMessageReplyToStory is unsupported; may be null if none + public let replyTo: InputMessageReplyTo? + + /// Information about the suggested post; may be null if none + public let suggestedPostInfo: InputSuggestedPostInfo? + + + public init( + date: Int, + effectId: TdInt64, + inputMessageText: InputMessageContent, + replyTo: InputMessageReplyTo?, + suggestedPostInfo: InputSuggestedPostInfo? + ) { + self.date = date + self.effectId = effectId + self.inputMessageText = inputMessageText + self.replyTo = replyTo + self.suggestedPostInfo = suggestedPostInfo + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressAuthenticationCodeInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressAuthenticationCodeInfo.swift new file mode 100644 index 0000000000..9acfbd26bc --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressAuthenticationCodeInfo.swift @@ -0,0 +1,31 @@ +// +// EmailAddressAuthenticationCodeInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Information about the email address authentication code that was sent +public struct EmailAddressAuthenticationCodeInfo: Codable, Equatable, Hashable { + + /// Pattern of the email address to which an authentication code was sent + public let emailAddressPattern: String + + /// Length of the code; 0 if unknown + public let length: Int + + + public init( + emailAddressPattern: String, + length: Int + ) { + self.emailAddressPattern = emailAddressPattern + self.length = length + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressResetState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressResetState.swift new file mode 100644 index 0000000000..b837417592 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmailAddressResetState.swift @@ -0,0 +1,85 @@ +// +// EmailAddressResetState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes reset state of an email address +public indirect enum EmailAddressResetState: Codable, Equatable, Hashable { + + /// Email address can be reset after the given period. Call resetAuthenticationEmailAddress to reset it and allow the user to authorize with a code sent to the user's phone number + case emailAddressResetStateAvailable(EmailAddressResetStateAvailable) + + /// Email address reset has already been requested. Call resetAuthenticationEmailAddress to check whether immediate reset is possible + case emailAddressResetStatePending(EmailAddressResetStatePending) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case emailAddressResetStateAvailable + case emailAddressResetStatePending + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .emailAddressResetStateAvailable: + let value = try EmailAddressResetStateAvailable(from: decoder) + self = .emailAddressResetStateAvailable(value) + case .emailAddressResetStatePending: + let value = try EmailAddressResetStatePending(from: decoder) + self = .emailAddressResetStatePending(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .emailAddressResetStateAvailable(let value): + try container.encode(Kind.emailAddressResetStateAvailable, forKey: .type) + try value.encode(to: encoder) + case .emailAddressResetStatePending(let value): + try container.encode(Kind.emailAddressResetStatePending, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Email address can be reset after the given period. Call resetAuthenticationEmailAddress to reset it and allow the user to authorize with a code sent to the user's phone number +public struct EmailAddressResetStateAvailable: Codable, Equatable, Hashable { + + /// Time required to wait before the email address can be reset; 0 if the user is subscribed to Telegram Premium + public let waitPeriod: Int + + + public init(waitPeriod: Int) { + self.waitPeriod = waitPeriod + } +} + +/// Email address reset has already been requested. Call resetAuthenticationEmailAddress to check whether immediate reset is possible +public struct EmailAddressResetStatePending: Codable, Equatable, Hashable { + + /// Left time before the email address will be reset, in seconds. updateAuthorizationState is not sent when this field changes + public let resetIn: Int + + + public init(resetIn: Int) { + self.resetIn = resetIn + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatus.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatus.swift new file mode 100644 index 0000000000..1ca97141f5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatus.swift @@ -0,0 +1,31 @@ +// +// EmojiStatus.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an emoji to be shown instead of the Telegram Premium badge +public struct EmojiStatus: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the status will expire; 0 if never + public let expirationDate: Int + + /// Type of the emoji status + public let type: EmojiStatusType + + + public init( + expirationDate: Int, + type: EmojiStatusType + ) { + self.expirationDate = expirationDate + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatusType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatusType.swift new file mode 100644 index 0000000000..110b4db6be --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/EmojiStatusType.swift @@ -0,0 +1,112 @@ +// +// EmojiStatusType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of emoji status +public indirect enum EmojiStatusType: Codable, Equatable, Hashable { + + /// A custom emoji set as emoji status + case emojiStatusTypeCustomEmoji(EmojiStatusTypeCustomEmoji) + + /// An upgraded gift set as emoji status + case emojiStatusTypeUpgradedGift(EmojiStatusTypeUpgradedGift) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case emojiStatusTypeCustomEmoji + case emojiStatusTypeUpgradedGift + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .emojiStatusTypeCustomEmoji: + let value = try EmojiStatusTypeCustomEmoji(from: decoder) + self = .emojiStatusTypeCustomEmoji(value) + case .emojiStatusTypeUpgradedGift: + let value = try EmojiStatusTypeUpgradedGift(from: decoder) + self = .emojiStatusTypeUpgradedGift(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .emojiStatusTypeCustomEmoji(let value): + try container.encode(Kind.emojiStatusTypeCustomEmoji, forKey: .type) + try value.encode(to: encoder) + case .emojiStatusTypeUpgradedGift(let value): + try container.encode(Kind.emojiStatusTypeUpgradedGift, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A custom emoji set as emoji status +public struct EmojiStatusTypeCustomEmoji: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji in stickerFormatTgs format + public let customEmojiId: TdInt64 + + + public init(customEmojiId: TdInt64) { + self.customEmojiId = customEmojiId + } +} + +/// An upgraded gift set as emoji status +public struct EmojiStatusTypeUpgradedGift: Codable, Equatable, Hashable { + + /// Colors of the backdrop of the upgraded gift + public let backdropColors: UpgradedGiftBackdropColors + + /// Unique name of the upgraded gift that can be used with internalLinkTypeUpgradedGift + public let giftName: String + + /// The title of the upgraded gift + public let giftTitle: String + + /// Custom emoji identifier of the model of the upgraded gift + public let modelCustomEmojiId: TdInt64 + + /// Custom emoji identifier of the symbol of the upgraded gift + public let symbolCustomEmojiId: TdInt64 + + /// Identifier of the upgraded gift + public let upgradedGiftId: TdInt64 + + + public init( + backdropColors: UpgradedGiftBackdropColors, + giftName: String, + giftTitle: String, + modelCustomEmojiId: TdInt64, + symbolCustomEmojiId: TdInt64, + upgradedGiftId: TdInt64 + ) { + self.backdropColors = backdropColors + self.giftName = giftName + self.giftTitle = giftTitle + self.modelCustomEmojiId = modelCustomEmojiId + self.symbolCustomEmojiId = symbolCustomEmojiId + self.upgradedGiftId = upgradedGiftId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Emojis.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Emojis.swift new file mode 100644 index 0000000000..8f16c706cd --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Emojis.swift @@ -0,0 +1,24 @@ +// +// Emojis.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a list of emojis +public struct Emojis: Codable, Equatable, Hashable { + + /// List of emojis + public let emojis: [String] + + + public init(emojis: [String]) { + self.emojis = emojis + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FactCheck.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FactCheck.swift new file mode 100644 index 0000000000..435262b545 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FactCheck.swift @@ -0,0 +1,31 @@ +// +// FactCheck.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a fact-check added to the message by an independent checker +public struct FactCheck: Codable, Equatable, Hashable { + + /// A two-letter ISO 3166-1 alpha-2 country code of the country for which the fact-check is shown + public let countryCode: String + + /// Text of the fact-check + public let text: FormattedText + + + public init( + countryCode: String, + text: FormattedText + ) { + self.countryCode = countryCode + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/File.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/File.swift new file mode 100644 index 0000000000..825d3bc546 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/File.swift @@ -0,0 +1,46 @@ +// +// File.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a file +public struct File: Codable, Equatable, Hashable, Identifiable { + + /// Approximate file size in bytes in case the exact file size is unknown. Can be used to show download/upload progress + public let expectedSize: Int64 + + /// Unique file identifier + public let id: Int + + /// Information about the local copy of the file + public let local: LocalFile + + /// Information about the remote copy of the file + public let remote: RemoteFile + + /// File size, in bytes; 0 if unknown + public let size: Int64 + + + public init( + expectedSize: Int64, + id: Int, + local: LocalFile, + remote: RemoteFile, + size: Int64 + ) { + self.expectedSize = expectedSize + self.id = id + self.local = local + self.remote = remote + self.size = size + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FirebaseDeviceVerificationParameters.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FirebaseDeviceVerificationParameters.swift new file mode 100644 index 0000000000..af21e50d8c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FirebaseDeviceVerificationParameters.swift @@ -0,0 +1,92 @@ +// +// FirebaseDeviceVerificationParameters.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes parameters to be used for device verification +public indirect enum FirebaseDeviceVerificationParameters: Codable, Equatable, Hashable { + + /// Device verification must be performed with the SafetyNet Attestation API + case firebaseDeviceVerificationParametersSafetyNet(FirebaseDeviceVerificationParametersSafetyNet) + + /// Device verification must be performed with the classic Play Integrity verification (https://developer.android.com/google/play/integrity/classic) + case firebaseDeviceVerificationParametersPlayIntegrity(FirebaseDeviceVerificationParametersPlayIntegrity) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case firebaseDeviceVerificationParametersSafetyNet + case firebaseDeviceVerificationParametersPlayIntegrity + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .firebaseDeviceVerificationParametersSafetyNet: + let value = try FirebaseDeviceVerificationParametersSafetyNet(from: decoder) + self = .firebaseDeviceVerificationParametersSafetyNet(value) + case .firebaseDeviceVerificationParametersPlayIntegrity: + let value = try FirebaseDeviceVerificationParametersPlayIntegrity(from: decoder) + self = .firebaseDeviceVerificationParametersPlayIntegrity(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .firebaseDeviceVerificationParametersSafetyNet(let value): + try container.encode(Kind.firebaseDeviceVerificationParametersSafetyNet, forKey: .type) + try value.encode(to: encoder) + case .firebaseDeviceVerificationParametersPlayIntegrity(let value): + try container.encode(Kind.firebaseDeviceVerificationParametersPlayIntegrity, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Device verification must be performed with the SafetyNet Attestation API +public struct FirebaseDeviceVerificationParametersSafetyNet: Codable, Equatable, Hashable { + + /// Nonce to pass to the SafetyNet Attestation API + public let nonce: Data + + + public init(nonce: Data) { + self.nonce = nonce + } +} + +/// Device verification must be performed with the classic Play Integrity verification (https://developer.android.com/google/play/integrity/classic) +public struct FirebaseDeviceVerificationParametersPlayIntegrity: Codable, Equatable, Hashable { + + /// Cloud project number to pass to the Play Integrity API + public let cloudProjectNumber: TdInt64 + + /// Base64url-encoded nonce to pass to the Play Integrity API + public let nonce: String + + + public init( + cloudProjectNumber: TdInt64, + nonce: String + ) { + self.cloudProjectNumber = cloudProjectNumber + self.nonce = nonce + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FormattedText.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FormattedText.swift new file mode 100644 index 0000000000..cdc3610f04 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/FormattedText.swift @@ -0,0 +1,31 @@ +// +// FormattedText.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// A text with some entities +public struct FormattedText: Codable, Equatable, Hashable { + + /// Entities contained in the text. Entities can be nested, but must not mutually intersect with each other. Pre, Code, PreCode, and DateTime entities can't contain other entities. BlockQuote entities can't contain other BlockQuote entities. Bold, Italic, Underline, Strikethrough, and Spoiler entities can contain and can be part of any other entities. All other entities can't contain each other + public let entities: [TextEntity] + + /// The text + public let text: String + + + public init( + entities: [TextEntity], + text: String + ) { + self.entities = entities + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForumTopicIcon.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForumTopicIcon.swift new file mode 100644 index 0000000000..7decea7860 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForumTopicIcon.swift @@ -0,0 +1,31 @@ +// +// ForumTopicIcon.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a forum topic icon +public struct ForumTopicIcon: Codable, Equatable, Hashable { + + /// Color of the topic icon in RGB format + public let color: Int + + /// Unique identifier of the custom emoji shown on the topic icon; 0 if none + public let customEmojiId: TdInt64 + + + public init( + color: Int, + customEmojiId: TdInt64 + ) { + self.color = color + self.customEmojiId = customEmojiId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForwardSource.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForwardSource.swift new file mode 100644 index 0000000000..4324157f00 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ForwardSource.swift @@ -0,0 +1,51 @@ +// +// ForwardSource.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the last message from which a new message was forwarded last time +public struct ForwardSource: Codable, Equatable, Hashable { + + /// Identifier of the chat to which the message that was forwarded belonged; may be 0 if unknown + public let chatId: Int64 + + /// Point in time (Unix timestamp) when the message is sent; 0 if unknown + public let date: Int + + /// True, if the message that was forwarded is outgoing; always false if sender is unknown + public let isOutgoing: Bool + + /// Identifier of the message; may be 0 if unknown + public let messageId: Int64 + + /// Identifier of the sender of the message; may be null if unknown or the new message was forwarded not to Saved Messages + public let senderId: MessageSender? + + /// Name of the sender of the message if the sender is hidden by their privacy settings + public let senderName: String + + + public init( + chatId: Int64, + date: Int, + isOutgoing: Bool, + messageId: Int64, + senderId: MessageSender?, + senderName: String + ) { + self.chatId = chatId + self.date = date + self.isOutgoing = isOutgoing + self.messageId = messageId + self.senderId = senderId + self.senderName = senderName + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Game.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Game.swift new file mode 100644 index 0000000000..a72b20f3f9 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Game.swift @@ -0,0 +1,55 @@ +// +// Game.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a game. Use getInternalLink with internalLinkTypeGame to share the game +public struct Game: Codable, Equatable, Hashable, Identifiable { + + /// Game animation; may be null + public let animation: Animation? + + public let description: String + + /// Unique game identifier + public let id: TdInt64 + + /// Game photo + public let photo: Photo + + /// Game short name + public let shortName: String + + /// Game text, usually containing scoreboards for a game + public let text: FormattedText + + /// Game title + public let title: String + + + public init( + animation: Animation?, + description: String, + id: TdInt64, + photo: Photo, + shortName: String, + text: FormattedText, + title: String + ) { + self.animation = animation + self.description = description + self.id = id + self.photo = photo + self.shortName = shortName + self.text = text + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetChatHistory.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetChatHistory.swift new file mode 100644 index 0000000000..d19b569256 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetChatHistory.swift @@ -0,0 +1,46 @@ +// +// GetChatHistory.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns messages in a chat. The messages are returned in reverse chronological order (i.e., in order of decreasing message_id). For optimal performance, the number of returned messages is chosen by TDLib. This is an offline method if only_local is true +public struct GetChatHistory: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64? + + /// Identifier of the message starting from which history must be fetched; use 0 to get results from the last message + public let fromMessageId: Int64? + + /// The maximum number of messages to be returned; must be positive and can't be greater than 100. If the offset is negative, then the limit must be greater than or equal to -offset. For optimal performance, the number of returned messages is chosen by TDLib and can be smaller than the specified limit + public let limit: Int? + + /// Specify 0 to get results from exactly the message from_message_id or a negative number from -99 to -1 to get additionally -offset newer messages + public let offset: Int? + + /// Pass true to get only messages that are available without sending network requests + public let onlyLocal: Bool? + + + public init( + chatId: Int64?, + fromMessageId: Int64?, + limit: Int?, + offset: Int?, + onlyLocal: Bool? + ) { + self.chatId = chatId + self.fromMessageId = fromMessageId + self.limit = limit + self.offset = offset + self.onlyLocal = onlyLocal + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetFavoriteStickers.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetFavoriteStickers.swift new file mode 100644 index 0000000000..852cae8113 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetFavoriteStickers.swift @@ -0,0 +1,19 @@ +// +// GetFavoriteStickers.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns favorite stickers +public struct GetFavoriteStickers: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetInstalledStickerSets.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetInstalledStickerSets.swift new file mode 100644 index 0000000000..c0cf666422 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetInstalledStickerSets.swift @@ -0,0 +1,24 @@ +// +// GetInstalledStickerSets.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns a list of installed sticker sets +public struct GetInstalledStickerSets: Codable, Equatable, Hashable { + + /// Type of the sticker sets to return + public let stickerType: StickerType? + + + public init(stickerType: StickerType?) { + self.stickerType = stickerType + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetMe.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetMe.swift new file mode 100644 index 0000000000..86552f74a1 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetMe.swift @@ -0,0 +1,19 @@ +// +// GetMe.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns the current user +public struct GetMe: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetOption.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetOption.swift new file mode 100644 index 0000000000..89b571fc6f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetOption.swift @@ -0,0 +1,24 @@ +// +// GetOption.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns the value of an option by its name. (Check the list of available options on https://core.telegram.org/tdlib/options.) Can be called before authorization. Can be called synchronously for options "version" and "commit_hash" +public struct GetOption: Codable, Equatable, Hashable { + + /// The name of the option + public let name: String? + + + public init(name: String?) { + self.name = name + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetRecentStickers.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetRecentStickers.swift new file mode 100644 index 0000000000..5b32ee8fe4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetRecentStickers.swift @@ -0,0 +1,24 @@ +// +// GetRecentStickers.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns a list of recently used stickers +public struct GetRecentStickers: Codable, Equatable, Hashable { + + /// Pass true to return stickers and masks that were recently attached to photos or video files; pass false to return recently sent stickers + public let isAttached: Bool? + + + public init(isAttached: Bool?) { + self.isAttached = isAttached + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetStickerSet.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetStickerSet.swift new file mode 100644 index 0000000000..9d94d91de8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GetStickerSet.swift @@ -0,0 +1,24 @@ +// +// GetStickerSet.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Returns information about a sticker set by its identifier +public struct GetStickerSet: Codable, Equatable, Hashable { + + /// Identifier of the sticker set + public let setId: TdInt64? + + + public init(setId: TdInt64?) { + self.setId = setId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Gift.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Gift.swift new file mode 100644 index 0000000000..d39be3a1d8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Gift.swift @@ -0,0 +1,106 @@ +// +// Gift.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a gift that can be sent to another user or channel chat +public struct Gift: Codable, Equatable, Hashable, Identifiable { + + /// Information about the auction on which the gift can be purchased; may be null if the gift can be purchased directly + public let auctionInfo: GiftAuction? + + /// Background of the gift + public let background: GiftBackground + + /// Number of Telegram Stars that can be claimed by the receiver instead of the regular gift by default. If the gift was paid with just bought Telegram Stars, then full value can be claimed + public let defaultSellStarCount: Int64 + + /// Point in time (Unix timestamp) when the gift was send for the first time; for sold out gifts only + public let firstSendDate: Int + + /// True, if the gift can be used to customize the user's name, and backgrounds of profile photo, reply header, and link preview + public let hasColors: Bool + + /// Unique identifier of the gift + public let id: TdInt64 + + /// True, if the gift is a birthday gift + public let isForBirthday: Bool + + /// True, if the gift can be bought only by Telegram Premium subscribers + public let isPremium: Bool + + /// Point in time (Unix timestamp) when the gift was send for the last time; for sold out gifts only + public let lastSendDate: Int + + /// Point in time (Unix timestamp) when the gift can be sent next time by the current user; may be 0 or a date in the past. If the date is in the future, then call canSendGift to get the reason, why the gift can't be sent now + public let nextSendDate: Int + + /// Number of times the gift can be purchased all users; may be null if not limited + public let overallLimits: GiftPurchaseLimits? + + /// Identifier of the chat that published the gift; 0 if none + public let publisherChatId: Int64 + + /// Number of Telegram Stars that must be paid for the gift + public let starCount: Int64 + + /// The sticker representing the gift + public let sticker: Sticker + + /// Number of Telegram Stars that must be paid to upgrade the gift; 0 if upgrade isn't possible + public let upgradeStarCount: Int64 + + /// Number of unique gift variants that are available for the upgraded gift; 0 if unknown + public let upgradeVariantCount: Int + + /// Number of times the gift can be purchased by the current user; may be null if not limited + public let userLimits: GiftPurchaseLimits? + + + public init( + auctionInfo: GiftAuction?, + background: GiftBackground, + defaultSellStarCount: Int64, + firstSendDate: Int, + hasColors: Bool, + id: TdInt64, + isForBirthday: Bool, + isPremium: Bool, + lastSendDate: Int, + nextSendDate: Int, + overallLimits: GiftPurchaseLimits?, + publisherChatId: Int64, + starCount: Int64, + sticker: Sticker, + upgradeStarCount: Int64, + upgradeVariantCount: Int, + userLimits: GiftPurchaseLimits? + ) { + self.auctionInfo = auctionInfo + self.background = background + self.defaultSellStarCount = defaultSellStarCount + self.firstSendDate = firstSendDate + self.hasColors = hasColors + self.id = id + self.isForBirthday = isForBirthday + self.isPremium = isPremium + self.lastSendDate = lastSendDate + self.nextSendDate = nextSendDate + self.overallLimits = overallLimits + self.publisherChatId = publisherChatId + self.starCount = starCount + self.sticker = sticker + self.upgradeStarCount = upgradeStarCount + self.upgradeVariantCount = upgradeVariantCount + self.userLimits = userLimits + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftAuction.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftAuction.swift new file mode 100644 index 0000000000..37c603487d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftAuction.swift @@ -0,0 +1,36 @@ +// +// GiftAuction.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an auction on which a gift can be purchased +public struct GiftAuction: Codable, Equatable, Hashable, Identifiable { + + /// Number of gifts distributed in each round + public let giftsPerRound: Int + + /// Identifier of the auction + public let id: String + + /// Point in time (Unix timestamp) when the auction will start + public let startDate: Int + + + public init( + giftsPerRound: Int, + id: String, + startDate: Int + ) { + self.giftsPerRound = giftsPerRound + self.id = id + self.startDate = startDate + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftBackground.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftBackground.swift new file mode 100644 index 0000000000..f186171861 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftBackground.swift @@ -0,0 +1,36 @@ +// +// GiftBackground.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes background of a gift +public struct GiftBackground: Codable, Equatable, Hashable { + + /// Center color in RGB format + public let centerColor: Int + + /// Edge color in RGB format + public let edgeColor: Int + + /// Text color in RGB format + public let textColor: Int + + + public init( + centerColor: Int, + edgeColor: Int, + textColor: Int + ) { + self.centerColor = centerColor + self.edgeColor = edgeColor + self.textColor = textColor + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftChatTheme.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftChatTheme.swift new file mode 100644 index 0000000000..7bdda93bed --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftChatTheme.swift @@ -0,0 +1,36 @@ +// +// GiftChatTheme.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a chat theme based on an upgraded gift +public struct GiftChatTheme: Codable, Equatable, Hashable { + + /// Theme settings for a dark chat theme + public let darkSettings: ThemeSettings + + /// The gift + public let gift: UpgradedGift + + /// Theme settings for a light chat theme + public let lightSettings: ThemeSettings + + + public init( + darkSettings: ThemeSettings, + gift: UpgradedGift, + lightSettings: ThemeSettings + ) { + self.darkSettings = darkSettings + self.gift = gift + self.lightSettings = lightSettings + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseLimits.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseLimits.swift new file mode 100644 index 0000000000..75b79f9fa1 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseLimits.swift @@ -0,0 +1,31 @@ +// +// GiftPurchaseLimits.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the maximum number of times that a specific gift can be purchased +public struct GiftPurchaseLimits: Codable, Equatable, Hashable { + + /// Number of remaining times the gift can be purchased + public let remainingCount: Int + + /// The maximum number of times the gifts can be purchased + public let totalCount: Int + + + public init( + remainingCount: Int, + totalCount: Int + ) { + self.remainingCount = remainingCount + self.totalCount = totalCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseOfferState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseOfferState.swift new file mode 100644 index 0000000000..dc11f57ae6 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftPurchaseOfferState.swift @@ -0,0 +1,65 @@ +// +// GiftPurchaseOfferState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes state of a gift purchase offer +public indirect enum GiftPurchaseOfferState: Codable, Equatable, Hashable { + + /// The offer must be accepted or rejected + case giftPurchaseOfferStatePending + + /// The offer was accepted + case giftPurchaseOfferStateAccepted + + /// The offer was rejected + case giftPurchaseOfferStateRejected + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case giftPurchaseOfferStatePending + case giftPurchaseOfferStateAccepted + case giftPurchaseOfferStateRejected + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .giftPurchaseOfferStatePending: + self = .giftPurchaseOfferStatePending + case .giftPurchaseOfferStateAccepted: + self = .giftPurchaseOfferStateAccepted + case .giftPurchaseOfferStateRejected: + self = .giftPurchaseOfferStateRejected + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .giftPurchaseOfferStatePending: + try container.encode(Kind.giftPurchaseOfferStatePending, forKey: .type) + case .giftPurchaseOfferStateAccepted: + try container.encode(Kind.giftPurchaseOfferStateAccepted, forKey: .type) + case .giftPurchaseOfferStateRejected: + try container.encode(Kind.giftPurchaseOfferStateRejected, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResaleParameters.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResaleParameters.swift new file mode 100644 index 0000000000..eab063bef8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResaleParameters.swift @@ -0,0 +1,36 @@ +// +// GiftResaleParameters.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes parameters of a unique gift available for resale +public struct GiftResaleParameters: Codable, Equatable, Hashable { + + /// Resale price of the gift in Telegram Stars + public let starCount: Int64 + + /// Resale price of the gift in 1/100 of Toncoin + public let toncoinCentCount: Int64 + + /// True, if the gift can be bought only using Toncoins + public let toncoinOnly: Bool + + + public init( + starCount: Int64, + toncoinCentCount: Int64, + toncoinOnly: Bool + ) { + self.starCount = starCount + self.toncoinCentCount = toncoinCentCount + self.toncoinOnly = toncoinOnly + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResalePrice.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResalePrice.swift new file mode 100644 index 0000000000..f4055ee4e0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiftResalePrice.swift @@ -0,0 +1,85 @@ +// +// GiftResalePrice.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes price of a resold gift +public indirect enum GiftResalePrice: Codable, Equatable, Hashable { + + /// Describes price of a resold gift in Telegram Stars + case giftResalePriceStar(GiftResalePriceStar) + + /// Describes price of a resold gift in Toncoins + case giftResalePriceTon(GiftResalePriceTon) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case giftResalePriceStar + case giftResalePriceTon + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .giftResalePriceStar: + let value = try GiftResalePriceStar(from: decoder) + self = .giftResalePriceStar(value) + case .giftResalePriceTon: + let value = try GiftResalePriceTon(from: decoder) + self = .giftResalePriceTon(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .giftResalePriceStar(let value): + try container.encode(Kind.giftResalePriceStar, forKey: .type) + try value.encode(to: encoder) + case .giftResalePriceTon(let value): + try container.encode(Kind.giftResalePriceTon, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Describes price of a resold gift in Telegram Stars +public struct GiftResalePriceStar: Codable, Equatable, Hashable { + + /// The Telegram Star amount expected to be paid for the gift. Must be in the range getOption("gift_resale_star_count_min")-getOption("gift_resale_star_count_max") for gifts put for resale + public let starCount: Int64 + + + public init(starCount: Int64) { + self.starCount = starCount + } +} + +/// Describes price of a resold gift in Toncoins +public struct GiftResalePriceTon: Codable, Equatable, Hashable { + + /// The amount of 1/100 of Toncoin expected to be paid for the gift. Must be in the range getOption("gift_resale_toncoin_cent_count_min")-getOption("gift_resale_toncoin_cent_count_max") + public let toncoinCentCount: Int64 + + + public init(toncoinCentCount: Int64) { + self.toncoinCentCount = toncoinCentCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayParameters.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayParameters.swift new file mode 100644 index 0000000000..ffd7c81ba7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayParameters.swift @@ -0,0 +1,56 @@ +// +// GiveawayParameters.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes parameters of a giveaway +public struct GiveawayParameters: Codable, Equatable, Hashable { + + /// Identifiers of other supergroup or channel chats that must be subscribed by the users to be eligible for the giveaway. There can be up to getOption("giveaway_additional_chat_count_max") additional chats + public let additionalChatIds: [Int64] + + /// Identifier of the supergroup or channel chat, which will be automatically boosted by the winners of the giveaway for duration of the Telegram Premium subscription, or for the specified time. If the chat is a channel, then can_post_messages administrator right is required in the channel, otherwise, the user must be an administrator in the supergroup + public let boostedChatId: Int64 + + /// The list of two-letter ISO 3166-1 alpha-2 codes of countries, users from which will be eligible for the giveaway. If empty, then all users can participate in the giveaway. There can be up to getOption("giveaway_country_count_max") chosen countries. Users with phone number that was bought at https://fragment.com can participate in any giveaway and the country code "FT" must not be specified in the list + public let countryCodes: [String] + + /// True, if the list of winners of the giveaway will be available to everyone + public let hasPublicWinners: Bool + + /// True, if only new members of the chats will be eligible for the giveaway + public let onlyNewMembers: Bool + + /// Additional description of the giveaway prize; 0-128 characters + public let prizeDescription: String + + /// Point in time (Unix timestamp) when the giveaway is expected to be performed; must be 60-getOption("giveaway_duration_max") seconds in the future in scheduled giveaways + public let winnersSelectionDate: Int + + + public init( + additionalChatIds: [Int64], + boostedChatId: Int64, + countryCodes: [String], + hasPublicWinners: Bool, + onlyNewMembers: Bool, + prizeDescription: String, + winnersSelectionDate: Int + ) { + self.additionalChatIds = additionalChatIds + self.boostedChatId = boostedChatId + self.countryCodes = countryCodes + self.hasPublicWinners = hasPublicWinners + self.onlyNewMembers = onlyNewMembers + self.prizeDescription = prizeDescription + self.winnersSelectionDate = winnersSelectionDate + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayPrize.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayPrize.swift new file mode 100644 index 0000000000..ead6592230 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/GiveawayPrize.swift @@ -0,0 +1,85 @@ +// +// GiveawayPrize.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a giveaway prize +public indirect enum GiveawayPrize: Codable, Equatable, Hashable { + + /// The giveaway sends Telegram Premium subscriptions to the winners + case giveawayPrizePremium(GiveawayPrizePremium) + + /// The giveaway sends Telegram Stars to the winners + case giveawayPrizeStars(GiveawayPrizeStars) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case giveawayPrizePremium + case giveawayPrizeStars + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .giveawayPrizePremium: + let value = try GiveawayPrizePremium(from: decoder) + self = .giveawayPrizePremium(value) + case .giveawayPrizeStars: + let value = try GiveawayPrizeStars(from: decoder) + self = .giveawayPrizeStars(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .giveawayPrizePremium(let value): + try container.encode(Kind.giveawayPrizePremium, forKey: .type) + try value.encode(to: encoder) + case .giveawayPrizeStars(let value): + try container.encode(Kind.giveawayPrizeStars, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The giveaway sends Telegram Premium subscriptions to the winners +public struct GiveawayPrizePremium: Codable, Equatable, Hashable { + + /// Number of months the Telegram Premium subscription will be active after code activation + public let monthCount: Int + + + public init(monthCount: Int) { + self.monthCount = monthCount + } +} + +/// The giveaway sends Telegram Stars to the winners +public struct GiveawayPrizeStars: Codable, Equatable, Hashable { + + /// Number of Telegram Stars that will be shared by all winners + public let starCount: Int64 + + + public init(starCount: Int64) { + self.starCount = starCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButton.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButton.swift new file mode 100644 index 0000000000..cf4f9ecc14 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButton.swift @@ -0,0 +1,41 @@ +// +// InlineKeyboardButton.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a single button in an inline keyboard +public struct InlineKeyboardButton: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji that must be shown on the button; 0 if none + public let iconCustomEmojiId: TdInt64 + + /// Style of the button + public let style: ButtonStyle + + /// Text of the button + public let text: String + + /// Type of the button + public let type: InlineKeyboardButtonType + + + public init( + iconCustomEmojiId: TdInt64, + style: ButtonStyle, + text: String, + type: InlineKeyboardButtonType + ) { + self.iconCustomEmojiId = iconCustomEmojiId + self.style = style + self.text = text + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButtonType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButtonType.swift new file mode 100644 index 0000000000..42d82d3fae --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InlineKeyboardButtonType.swift @@ -0,0 +1,252 @@ +// +// InlineKeyboardButtonType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of inline keyboard button +public indirect enum InlineKeyboardButtonType: Codable, Equatable, Hashable { + + /// A button that opens a specified URL + case inlineKeyboardButtonTypeUrl(InlineKeyboardButtonTypeUrl) + + /// A button that opens a specified URL and automatically authorize the current user by calling getLoginUrlInfo + case inlineKeyboardButtonTypeLoginUrl(InlineKeyboardButtonTypeLoginUrl) + + /// A button that opens a Web App by calling openWebApp + case inlineKeyboardButtonTypeWebApp(InlineKeyboardButtonTypeWebApp) + + /// A button that sends a callback query to a bot + case inlineKeyboardButtonTypeCallback(InlineKeyboardButtonTypeCallback) + + /// A button that asks for the 2-step verification password of the current user and then sends a callback query to a bot + case inlineKeyboardButtonTypeCallbackWithPassword(InlineKeyboardButtonTypeCallbackWithPassword) + + /// A button with a game that sends a callback query to a bot. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageGame + case inlineKeyboardButtonTypeCallbackGame + + /// A button that forces an inline query to the bot to be inserted in the input field + case inlineKeyboardButtonTypeSwitchInline(InlineKeyboardButtonTypeSwitchInline) + + /// A button to buy something. This button must be in the first column and row of the keyboard and can be attached only to a message with content of the type messageInvoice + case inlineKeyboardButtonTypeBuy + + /// A button with a user reference to be handled in the same way as textEntityTypeMentionName entities + case inlineKeyboardButtonTypeUser(InlineKeyboardButtonTypeUser) + + /// A button that copies specified text to clipboard + case inlineKeyboardButtonTypeCopyText(InlineKeyboardButtonTypeCopyText) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inlineKeyboardButtonTypeUrl + case inlineKeyboardButtonTypeLoginUrl + case inlineKeyboardButtonTypeWebApp + case inlineKeyboardButtonTypeCallback + case inlineKeyboardButtonTypeCallbackWithPassword + case inlineKeyboardButtonTypeCallbackGame + case inlineKeyboardButtonTypeSwitchInline + case inlineKeyboardButtonTypeBuy + case inlineKeyboardButtonTypeUser + case inlineKeyboardButtonTypeCopyText + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inlineKeyboardButtonTypeUrl: + let value = try InlineKeyboardButtonTypeUrl(from: decoder) + self = .inlineKeyboardButtonTypeUrl(value) + case .inlineKeyboardButtonTypeLoginUrl: + let value = try InlineKeyboardButtonTypeLoginUrl(from: decoder) + self = .inlineKeyboardButtonTypeLoginUrl(value) + case .inlineKeyboardButtonTypeWebApp: + let value = try InlineKeyboardButtonTypeWebApp(from: decoder) + self = .inlineKeyboardButtonTypeWebApp(value) + case .inlineKeyboardButtonTypeCallback: + let value = try InlineKeyboardButtonTypeCallback(from: decoder) + self = .inlineKeyboardButtonTypeCallback(value) + case .inlineKeyboardButtonTypeCallbackWithPassword: + let value = try InlineKeyboardButtonTypeCallbackWithPassword(from: decoder) + self = .inlineKeyboardButtonTypeCallbackWithPassword(value) + case .inlineKeyboardButtonTypeCallbackGame: + self = .inlineKeyboardButtonTypeCallbackGame + case .inlineKeyboardButtonTypeSwitchInline: + let value = try InlineKeyboardButtonTypeSwitchInline(from: decoder) + self = .inlineKeyboardButtonTypeSwitchInline(value) + case .inlineKeyboardButtonTypeBuy: + self = .inlineKeyboardButtonTypeBuy + case .inlineKeyboardButtonTypeUser: + let value = try InlineKeyboardButtonTypeUser(from: decoder) + self = .inlineKeyboardButtonTypeUser(value) + case .inlineKeyboardButtonTypeCopyText: + let value = try InlineKeyboardButtonTypeCopyText(from: decoder) + self = .inlineKeyboardButtonTypeCopyText(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inlineKeyboardButtonTypeUrl(let value): + try container.encode(Kind.inlineKeyboardButtonTypeUrl, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeLoginUrl(let value): + try container.encode(Kind.inlineKeyboardButtonTypeLoginUrl, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeWebApp(let value): + try container.encode(Kind.inlineKeyboardButtonTypeWebApp, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeCallback(let value): + try container.encode(Kind.inlineKeyboardButtonTypeCallback, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeCallbackWithPassword(let value): + try container.encode(Kind.inlineKeyboardButtonTypeCallbackWithPassword, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeCallbackGame: + try container.encode(Kind.inlineKeyboardButtonTypeCallbackGame, forKey: .type) + case .inlineKeyboardButtonTypeSwitchInline(let value): + try container.encode(Kind.inlineKeyboardButtonTypeSwitchInline, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeBuy: + try container.encode(Kind.inlineKeyboardButtonTypeBuy, forKey: .type) + case .inlineKeyboardButtonTypeUser(let value): + try container.encode(Kind.inlineKeyboardButtonTypeUser, forKey: .type) + try value.encode(to: encoder) + case .inlineKeyboardButtonTypeCopyText(let value): + try container.encode(Kind.inlineKeyboardButtonTypeCopyText, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A button that opens a specified URL +public struct InlineKeyboardButtonTypeUrl: Codable, Equatable, Hashable { + + /// HTTP or tg:// URL to open. If the link is of the type internalLinkTypeWebApp, then the button must be marked as a Web App button + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// A button that opens a specified URL and automatically authorize the current user by calling getLoginUrlInfo +public struct InlineKeyboardButtonTypeLoginUrl: Codable, Equatable, Hashable, Identifiable { + + /// If non-empty, new text of the button in forwarded messages + public let forwardText: String + + /// Unique button identifier + public let id: Int64 + + /// An HTTP URL to pass to getLoginUrlInfo + public let url: String + + + public init( + forwardText: String, + id: Int64, + url: String + ) { + self.forwardText = forwardText + self.id = id + self.url = url + } +} + +/// A button that opens a Web App by calling openWebApp +public struct InlineKeyboardButtonTypeWebApp: Codable, Equatable, Hashable { + + /// An HTTP URL to pass to openWebApp + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// A button that sends a callback query to a bot +public struct InlineKeyboardButtonTypeCallback: Codable, Equatable, Hashable { + + /// Data to be sent to the bot via a callback query + public let data: Data + + + public init(data: Data) { + self.data = data + } +} + +/// A button that asks for the 2-step verification password of the current user and then sends a callback query to a bot +public struct InlineKeyboardButtonTypeCallbackWithPassword: Codable, Equatable, Hashable { + + /// Data to be sent to the bot via a callback query + public let data: Data + + + public init(data: Data) { + self.data = data + } +} + +/// A button that forces an inline query to the bot to be inserted in the input field +public struct InlineKeyboardButtonTypeSwitchInline: Codable, Equatable, Hashable { + + /// Inline query to be sent to the bot + public let query: String + + /// Target chat from which to send the inline query + public let targetChat: TargetChat + + + public init( + query: String, + targetChat: TargetChat + ) { + self.query = query + self.targetChat = targetChat + } +} + +/// A button with a user reference to be handled in the same way as textEntityTypeMentionName entities +public struct InlineKeyboardButtonTypeUser: Codable, Equatable, Hashable { + + /// User identifier + public let userId: Int64 + + + public init(userId: Int64) { + self.userId = userId + } +} + +/// A button that copies specified text to clipboard +public struct InlineKeyboardButtonTypeCopyText: Codable, Equatable, Hashable { + + /// The text to copy to clipboard + public let text: String + + + public init(text: String) { + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklist.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklist.swift new file mode 100644 index 0000000000..240923dde8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklist.swift @@ -0,0 +1,41 @@ +// +// InputChecklist.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a checklist to be sent +public struct InputChecklist: Codable, Equatable, Hashable { + + /// True, if other users can add tasks to the list + public let othersCanAddTasks: Bool + + /// True, if other users can mark tasks as done or not done + public let othersCanMarkTasksAsDone: Bool + + /// List of tasks in the checklist; 1-getOption("checklist_task_count_max") tasks + public let tasks: [InputChecklistTask] + + /// Title of the checklist; 1-getOption("checklist_title_length_max") characters. May contain only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, and DateTime entities + public let title: FormattedText + + + public init( + othersCanAddTasks: Bool, + othersCanMarkTasksAsDone: Bool, + tasks: [InputChecklistTask], + title: FormattedText + ) { + self.othersCanAddTasks = othersCanAddTasks + self.othersCanMarkTasksAsDone = othersCanMarkTasksAsDone + self.tasks = tasks + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklistTask.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklistTask.swift new file mode 100644 index 0000000000..1a163d11df --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputChecklistTask.swift @@ -0,0 +1,31 @@ +// +// InputChecklistTask.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a task in a checklist to be sent +public struct InputChecklistTask: Codable, Equatable, Hashable, Identifiable { + + /// Unique identifier of the task; must be positive + public let id: Int + + /// Text of the task; 1-getOption("checklist_task_text_length_max") characters without line feeds. May contain only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, and DateTime entities + public let text: FormattedText + + + public init( + id: Int, + text: FormattedText + ) { + self.id = id + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputFile.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputFile.swift new file mode 100644 index 0000000000..3fbb49fc8d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputFile.swift @@ -0,0 +1,141 @@ +// +// InputFile.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Points to a file +public indirect enum InputFile: Codable, Equatable, Hashable { + + /// A file defined by its unique identifier + case inputFileId(InputFileId) + + /// A file defined by its remote identifier. The remote identifier is guaranteed to be usable only if the corresponding file is still accessible to the user and known to TDLib. For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application + case inputFileRemote(InputFileRemote) + + /// A file defined by a local path + case inputFileLocal(InputFileLocal) + + /// A file generated by the application. The application must handle updates updateFileGenerationStart and updateFileGenerationStop to generate the file when asked by TDLib + case inputFileGenerated(InputFileGenerated) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inputFileId + case inputFileRemote + case inputFileLocal + case inputFileGenerated + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inputFileId: + let value = try InputFileId(from: decoder) + self = .inputFileId(value) + case .inputFileRemote: + let value = try InputFileRemote(from: decoder) + self = .inputFileRemote(value) + case .inputFileLocal: + let value = try InputFileLocal(from: decoder) + self = .inputFileLocal(value) + case .inputFileGenerated: + let value = try InputFileGenerated(from: decoder) + self = .inputFileGenerated(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inputFileId(let value): + try container.encode(Kind.inputFileId, forKey: .type) + try value.encode(to: encoder) + case .inputFileRemote(let value): + try container.encode(Kind.inputFileRemote, forKey: .type) + try value.encode(to: encoder) + case .inputFileLocal(let value): + try container.encode(Kind.inputFileLocal, forKey: .type) + try value.encode(to: encoder) + case .inputFileGenerated(let value): + try container.encode(Kind.inputFileGenerated, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A file defined by its unique identifier +public struct InputFileId: Codable, Equatable, Hashable, Identifiable { + + /// Unique file identifier + public let id: Int + + + public init(id: Int) { + self.id = id + } +} + +/// A file defined by its remote identifier. The remote identifier is guaranteed to be usable only if the corresponding file is still accessible to the user and known to TDLib. For example, if the file is from a message, then the message must be not deleted and accessible to the user. If the file database is disabled, then the corresponding object with the file must be preloaded by the application +public struct InputFileRemote: Codable, Equatable, Hashable, Identifiable { + + /// Remote file identifier + public let id: String + + + public init(id: String) { + self.id = id + } +} + +/// A file defined by a local path +public struct InputFileLocal: Codable, Equatable, Hashable { + + /// Local path to the file + public let path: String + + + public init(path: String) { + self.path = path + } +} + +/// A file generated by the application. The application must handle updates updateFileGenerationStart and updateFileGenerationStop to generate the file when asked by TDLib +public struct InputFileGenerated: Codable, Equatable, Hashable { + + /// String specifying the conversion applied to the original file; must be persistent across application restarts. Conversions beginning with '#' are reserved for internal TDLib usage + public let conversion: String + + /// Expected size of the generated file, in bytes; pass 0 if unknown + public let expectedSize: Int64 + + /// Local path to a file from which the file is generated. The path doesn't have to be a valid path and is used by TDLib only to detect name and MIME type of the generated file + public let originalPath: String + + + public init( + conversion: String, + expectedSize: Int64, + originalPath: String + ) { + self.conversion = conversion + self.expectedSize = expectedSize + self.originalPath = originalPath + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageContent.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageContent.swift new file mode 100644 index 0000000000..04c0d8cb3d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageContent.swift @@ -0,0 +1,1008 @@ +// +// InputMessageContent.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// The content of a message to send +/// This Swift enum is recursive. +public indirect enum InputMessageContent: Codable, Equatable, Hashable { + + /// A text message + case inputMessageText(InputMessageText) + + /// An animation message (GIF-style). + case inputMessageAnimation(InputMessageAnimation) + + /// An audio message + case inputMessageAudio(InputMessageAudio) + + /// A document message (general file) + case inputMessageDocument(InputMessageDocument) + + /// A message with paid media; can be used only in channel chats with supergroupFullInfo.has_paid_media_allowed + case inputMessagePaidMedia(InputMessagePaidMedia) + + /// A photo message + case inputMessagePhoto(InputMessagePhoto) + + /// A sticker message + case inputMessageSticker(InputMessageSticker) + + /// A video message + case inputMessageVideo(InputMessageVideo) + + /// A video note message + case inputMessageVideoNote(InputMessageVideoNote) + + /// A voice note message + case inputMessageVoiceNote(InputMessageVoiceNote) + + /// A message with a location + case inputMessageLocation(InputMessageLocation) + + /// A message with information about a venue + case inputMessageVenue(InputMessageVenue) + + /// A message containing a user contact + case inputMessageContact(InputMessageContact) + + /// A dice message + case inputMessageDice(InputMessageDice) + + /// A message with a game; not supported for channels or secret chats + case inputMessageGame(InputMessageGame) + + /// A message with an invoice; can be used only by bots + case inputMessageInvoice(InputMessageInvoice) + + /// A message with a poll. Polls can't be sent to secret chats and channel direct messages chats. Polls can be sent to a private chat only if the chat is a chat with a bot or the Saved Messages chat + case inputMessagePoll(InputMessagePoll) + + /// A stake dice message + case inputMessageStakeDice(InputMessageStakeDice) + + /// A message with a forwarded story. Stories can't be forwarded to secret chats. A story can be forwarded only if story.can_be_forwarded + case inputMessageStory(InputMessageStory) + + /// A message with a checklist. Checklists can't be sent to secret chats, channel chats and channel direct messages chats; for Telegram Premium users only + case inputMessageChecklist(InputMessageChecklist) + + /// A forwarded message + case inputMessageForwarded(InputMessageForwarded) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inputMessageText + case inputMessageAnimation + case inputMessageAudio + case inputMessageDocument + case inputMessagePaidMedia + case inputMessagePhoto + case inputMessageSticker + case inputMessageVideo + case inputMessageVideoNote + case inputMessageVoiceNote + case inputMessageLocation + case inputMessageVenue + case inputMessageContact + case inputMessageDice + case inputMessageGame + case inputMessageInvoice + case inputMessagePoll + case inputMessageStakeDice + case inputMessageStory + case inputMessageChecklist + case inputMessageForwarded + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inputMessageText: + let value = try InputMessageText(from: decoder) + self = .inputMessageText(value) + case .inputMessageAnimation: + let value = try InputMessageAnimation(from: decoder) + self = .inputMessageAnimation(value) + case .inputMessageAudio: + let value = try InputMessageAudio(from: decoder) + self = .inputMessageAudio(value) + case .inputMessageDocument: + let value = try InputMessageDocument(from: decoder) + self = .inputMessageDocument(value) + case .inputMessagePaidMedia: + let value = try InputMessagePaidMedia(from: decoder) + self = .inputMessagePaidMedia(value) + case .inputMessagePhoto: + let value = try InputMessagePhoto(from: decoder) + self = .inputMessagePhoto(value) + case .inputMessageSticker: + let value = try InputMessageSticker(from: decoder) + self = .inputMessageSticker(value) + case .inputMessageVideo: + let value = try InputMessageVideo(from: decoder) + self = .inputMessageVideo(value) + case .inputMessageVideoNote: + let value = try InputMessageVideoNote(from: decoder) + self = .inputMessageVideoNote(value) + case .inputMessageVoiceNote: + let value = try InputMessageVoiceNote(from: decoder) + self = .inputMessageVoiceNote(value) + case .inputMessageLocation: + let value = try InputMessageLocation(from: decoder) + self = .inputMessageLocation(value) + case .inputMessageVenue: + let value = try InputMessageVenue(from: decoder) + self = .inputMessageVenue(value) + case .inputMessageContact: + let value = try InputMessageContact(from: decoder) + self = .inputMessageContact(value) + case .inputMessageDice: + let value = try InputMessageDice(from: decoder) + self = .inputMessageDice(value) + case .inputMessageGame: + let value = try InputMessageGame(from: decoder) + self = .inputMessageGame(value) + case .inputMessageInvoice: + let value = try InputMessageInvoice(from: decoder) + self = .inputMessageInvoice(value) + case .inputMessagePoll: + let value = try InputMessagePoll(from: decoder) + self = .inputMessagePoll(value) + case .inputMessageStakeDice: + let value = try InputMessageStakeDice(from: decoder) + self = .inputMessageStakeDice(value) + case .inputMessageStory: + let value = try InputMessageStory(from: decoder) + self = .inputMessageStory(value) + case .inputMessageChecklist: + let value = try InputMessageChecklist(from: decoder) + self = .inputMessageChecklist(value) + case .inputMessageForwarded: + let value = try InputMessageForwarded(from: decoder) + self = .inputMessageForwarded(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inputMessageText(let value): + try container.encode(Kind.inputMessageText, forKey: .type) + try value.encode(to: encoder) + case .inputMessageAnimation(let value): + try container.encode(Kind.inputMessageAnimation, forKey: .type) + try value.encode(to: encoder) + case .inputMessageAudio(let value): + try container.encode(Kind.inputMessageAudio, forKey: .type) + try value.encode(to: encoder) + case .inputMessageDocument(let value): + try container.encode(Kind.inputMessageDocument, forKey: .type) + try value.encode(to: encoder) + case .inputMessagePaidMedia(let value): + try container.encode(Kind.inputMessagePaidMedia, forKey: .type) + try value.encode(to: encoder) + case .inputMessagePhoto(let value): + try container.encode(Kind.inputMessagePhoto, forKey: .type) + try value.encode(to: encoder) + case .inputMessageSticker(let value): + try container.encode(Kind.inputMessageSticker, forKey: .type) + try value.encode(to: encoder) + case .inputMessageVideo(let value): + try container.encode(Kind.inputMessageVideo, forKey: .type) + try value.encode(to: encoder) + case .inputMessageVideoNote(let value): + try container.encode(Kind.inputMessageVideoNote, forKey: .type) + try value.encode(to: encoder) + case .inputMessageVoiceNote(let value): + try container.encode(Kind.inputMessageVoiceNote, forKey: .type) + try value.encode(to: encoder) + case .inputMessageLocation(let value): + try container.encode(Kind.inputMessageLocation, forKey: .type) + try value.encode(to: encoder) + case .inputMessageVenue(let value): + try container.encode(Kind.inputMessageVenue, forKey: .type) + try value.encode(to: encoder) + case .inputMessageContact(let value): + try container.encode(Kind.inputMessageContact, forKey: .type) + try value.encode(to: encoder) + case .inputMessageDice(let value): + try container.encode(Kind.inputMessageDice, forKey: .type) + try value.encode(to: encoder) + case .inputMessageGame(let value): + try container.encode(Kind.inputMessageGame, forKey: .type) + try value.encode(to: encoder) + case .inputMessageInvoice(let value): + try container.encode(Kind.inputMessageInvoice, forKey: .type) + try value.encode(to: encoder) + case .inputMessagePoll(let value): + try container.encode(Kind.inputMessagePoll, forKey: .type) + try value.encode(to: encoder) + case .inputMessageStakeDice(let value): + try container.encode(Kind.inputMessageStakeDice, forKey: .type) + try value.encode(to: encoder) + case .inputMessageStory(let value): + try container.encode(Kind.inputMessageStory, forKey: .type) + try value.encode(to: encoder) + case .inputMessageChecklist(let value): + try container.encode(Kind.inputMessageChecklist, forKey: .type) + try value.encode(to: encoder) + case .inputMessageForwarded(let value): + try container.encode(Kind.inputMessageForwarded, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A text message +public struct InputMessageText: Codable, Equatable, Hashable { + + /// True, if the chat message draft must be deleted + public let clearDraft: Bool + + /// Options to be used for generation of a link preview; may be null if none; pass null to use default link preview options + public let linkPreviewOptions: LinkPreviewOptions? + + /// Formatted text to be sent; 0-getOption("message_text_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, BlockQuote, ExpandableBlockQuote, Code, Pre, PreCode, TextUrl, MentionName, and DateTime entities are allowed to be specified manually + public let text: FormattedText + + + public init( + clearDraft: Bool, + linkPreviewOptions: LinkPreviewOptions?, + text: FormattedText + ) { + self.clearDraft = clearDraft + self.linkPreviewOptions = linkPreviewOptions + self.text = text + } +} + +/// An animation message (GIF-style). +public struct InputMessageAnimation: Codable, Equatable, Hashable { + + /// File identifiers of the stickers added to the animation, if applicable + public let addedStickerFileIds: [Int] + + /// Animation file to be sent + public let animation: InputFile + + /// Animation caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// Duration of the animation, in seconds + public let duration: Int + + /// True, if the animation preview must be covered by a spoiler animation; not supported in secret chats + public let hasSpoiler: Bool + + /// Height of the animation; may be replaced by the server + public let height: Int + + /// True, if the caption must be shown above the animation; otherwise, the caption must be shown below the animation; not supported in secret chats + public let showCaptionAboveMedia: Bool + + /// Animation thumbnail; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + /// Width of the animation; may be replaced by the server + public let width: Int + + + public init( + addedStickerFileIds: [Int], + animation: InputFile, + caption: FormattedText?, + duration: Int, + hasSpoiler: Bool, + height: Int, + showCaptionAboveMedia: Bool, + thumbnail: InputThumbnail?, + width: Int + ) { + self.addedStickerFileIds = addedStickerFileIds + self.animation = animation + self.caption = caption + self.duration = duration + self.hasSpoiler = hasSpoiler + self.height = height + self.showCaptionAboveMedia = showCaptionAboveMedia + self.thumbnail = thumbnail + self.width = width + } +} + +/// An audio message +public struct InputMessageAudio: Codable, Equatable, Hashable { + + /// Thumbnail of the cover for the album; pass null to skip thumbnail uploading + public let albumCoverThumbnail: InputThumbnail? + + /// Audio file to be sent + public let audio: InputFile + + /// Audio caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// Duration of the audio, in seconds; may be replaced by the server + public let duration: Int + + /// Performer of the audio; 0-64 characters, may be replaced by the server + public let performer: String + + /// Title of the audio; 0-64 characters; may be replaced by the server + public let title: String + + + public init( + albumCoverThumbnail: InputThumbnail?, + audio: InputFile, + caption: FormattedText?, + duration: Int, + performer: String, + title: String + ) { + self.albumCoverThumbnail = albumCoverThumbnail + self.audio = audio + self.caption = caption + self.duration = duration + self.performer = performer + self.title = title + } +} + +/// A document message (general file) +public struct InputMessageDocument: Codable, Equatable, Hashable { + + /// Document caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// Pass true to disable automatic file type detection and send the document as a file. Always true for files sent to secret chats + public let disableContentTypeDetection: Bool + + /// Document to be sent + public let document: InputFile + + /// Document thumbnail; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + + public init( + caption: FormattedText?, + disableContentTypeDetection: Bool, + document: InputFile, + thumbnail: InputThumbnail? + ) { + self.caption = caption + self.disableContentTypeDetection = disableContentTypeDetection + self.document = document + self.thumbnail = thumbnail + } +} + +/// A message with paid media; can be used only in channel chats with supergroupFullInfo.has_paid_media_allowed +public struct InputMessagePaidMedia: Codable, Equatable, Hashable { + + /// Message caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// The content of the paid media + public let paidMedia: [InputPaidMedia] + + /// Bot-provided data for the paid media; bots only + public let payload: String + + /// True, if the caption must be shown above the media; otherwise, the caption must be shown below the media; not supported in secret chats + public let showCaptionAboveMedia: Bool + + /// The number of Telegram Stars that must be paid to see the media; 1-getOption("paid_media_message_star_count_max") + public let starCount: Int64 + + + public init( + caption: FormattedText?, + paidMedia: [InputPaidMedia], + payload: String, + showCaptionAboveMedia: Bool, + starCount: Int64 + ) { + self.caption = caption + self.paidMedia = paidMedia + self.payload = payload + self.showCaptionAboveMedia = showCaptionAboveMedia + self.starCount = starCount + } +} + +/// A photo message +public struct InputMessagePhoto: Codable, Equatable, Hashable { + + /// File identifiers of the stickers added to the photo, if applicable + public let addedStickerFileIds: [Int] + + /// Photo caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// True, if the photo preview must be covered by a spoiler animation; not supported in secret chats + public let hasSpoiler: Bool + + /// Photo height + public let height: Int + + /// Photo to send. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20 + public let photo: InputFile + + /// Photo self-destruct type; pass null if none; private chats only + public let selfDestructType: MessageSelfDestructType? + + /// True, if the caption must be shown above the photo; otherwise, the caption must be shown below the photo; not supported in secret chats + public let showCaptionAboveMedia: Bool + + /// Photo thumbnail to be sent; pass null to skip thumbnail uploading. The thumbnail is sent to the other party only in secret chats + public let thumbnail: InputThumbnail? + + /// Video of the live photo; not supported in secret chats; pass null if the photo isn't a live photo + public let video: InputFile? + + /// Photo width + public let width: Int + + + public init( + addedStickerFileIds: [Int], + caption: FormattedText?, + hasSpoiler: Bool, + height: Int, + photo: InputFile, + selfDestructType: MessageSelfDestructType?, + showCaptionAboveMedia: Bool, + thumbnail: InputThumbnail?, + video: InputFile?, + width: Int + ) { + self.addedStickerFileIds = addedStickerFileIds + self.caption = caption + self.hasSpoiler = hasSpoiler + self.height = height + self.photo = photo + self.selfDestructType = selfDestructType + self.showCaptionAboveMedia = showCaptionAboveMedia + self.thumbnail = thumbnail + self.video = video + self.width = width + } +} + +/// A sticker message +public struct InputMessageSticker: Codable, Equatable, Hashable { + + /// Emoji used to choose the sticker + public let emoji: String + + /// Sticker height + public let height: Int + + /// Sticker to be sent + public let sticker: InputFile + + /// Sticker thumbnail; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + /// Sticker width + public let width: Int + + + public init( + emoji: String, + height: Int, + sticker: InputFile, + thumbnail: InputThumbnail?, + width: Int + ) { + self.emoji = emoji + self.height = height + self.sticker = sticker + self.thumbnail = thumbnail + self.width = width + } +} + +/// A video message +public struct InputMessageVideo: Codable, Equatable, Hashable { + + /// File identifiers of the stickers added to the video, if applicable + public let addedStickerFileIds: [Int] + + /// Video caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// Cover of the video; pass null to skip cover uploading; not supported in secret chats and for self-destructing messages + public let cover: InputFile? + + /// Duration of the video, in seconds + public let duration: Int + + /// True, if the video preview must be covered by a spoiler animation; not supported in secret chats + public let hasSpoiler: Bool + + /// Video height + public let height: Int + + /// Video self-destruct type; pass null if none; private chats only + public let selfDestructType: MessageSelfDestructType? + + /// True, if the caption must be shown above the video; otherwise, the caption must be shown below the video; not supported in secret chats + public let showCaptionAboveMedia: Bool + + /// Timestamp from which the video playing must start, in seconds + public let startTimestamp: Int + + /// True, if the video is expected to be streamed + public let supportsStreaming: Bool + + /// Video thumbnail; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + /// Video to be sent. The video is expected to be re-encoded to MPEG4 format with H.264 codec by the sender + public let video: InputFile + + /// Video width + public let width: Int + + + public init( + addedStickerFileIds: [Int], + caption: FormattedText?, + cover: InputFile?, + duration: Int, + hasSpoiler: Bool, + height: Int, + selfDestructType: MessageSelfDestructType?, + showCaptionAboveMedia: Bool, + startTimestamp: Int, + supportsStreaming: Bool, + thumbnail: InputThumbnail?, + video: InputFile, + width: Int + ) { + self.addedStickerFileIds = addedStickerFileIds + self.caption = caption + self.cover = cover + self.duration = duration + self.hasSpoiler = hasSpoiler + self.height = height + self.selfDestructType = selfDestructType + self.showCaptionAboveMedia = showCaptionAboveMedia + self.startTimestamp = startTimestamp + self.supportsStreaming = supportsStreaming + self.thumbnail = thumbnail + self.video = video + self.width = width + } +} + +/// A video note message +public struct InputMessageVideoNote: Codable, Equatable, Hashable { + + /// Duration of the video, in seconds; 0-60 + public let duration: Int + + /// Video width and height; must be positive and not greater than 640 + public let length: Int + + /// Video note self-destruct type; may be null if none; pass null if none; private chats only + public let selfDestructType: MessageSelfDestructType? + + /// Video thumbnail; may be null if empty; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + /// Video note to be sent. The video is expected to be encoded to MPEG4 format with H.264 codec and have no data outside of the visible circle + public let videoNote: InputFile + + + public init( + duration: Int, + length: Int, + selfDestructType: MessageSelfDestructType?, + thumbnail: InputThumbnail?, + videoNote: InputFile + ) { + self.duration = duration + self.length = length + self.selfDestructType = selfDestructType + self.thumbnail = thumbnail + self.videoNote = videoNote + } +} + +/// A voice note message +public struct InputMessageVoiceNote: Codable, Equatable, Hashable { + + /// Voice note caption; may be null if empty; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let caption: FormattedText? + + /// Duration of the voice note, in seconds + public let duration: Int + + /// Voice note self-destruct type; may be null if none; pass null if none; private chats only + public let selfDestructType: MessageSelfDestructType? + + /// Voice note to be sent. The voice note must be encoded with the Opus codec and stored inside an OGG container with a single audio channel, or be in MP3 or M4A format as regular audio + public let voiceNote: InputFile + + /// Waveform representation of the voice note in 5-bit format + public let waveform: Data + + + public init( + caption: FormattedText?, + duration: Int, + selfDestructType: MessageSelfDestructType?, + voiceNote: InputFile, + waveform: Data + ) { + self.caption = caption + self.duration = duration + self.selfDestructType = selfDestructType + self.voiceNote = voiceNote + self.waveform = waveform + } +} + +/// A message with a location +public struct InputMessageLocation: Codable, Equatable, Hashable { + + /// For live locations, a direction in which the location moves, in degrees; 1-360. Pass 0 if unknown + public let heading: Int + + /// Period for which the location can be updated, in seconds; must be between 60 and 86400 for a temporary live location, 0x7FFFFFFF for permanent live location, and 0 otherwise + public let livePeriod: Int + + /// Location to be sent + public let location: Location + + /// For live locations, a maximum distance to another chat member for proximity alerts, in meters (0-100000). Pass 0 if the notification is disabled. Can't be enabled in channels and Saved Messages + public let proximityAlertRadius: Int + + + public init( + heading: Int, + livePeriod: Int, + location: Location, + proximityAlertRadius: Int + ) { + self.heading = heading + self.livePeriod = livePeriod + self.location = location + self.proximityAlertRadius = proximityAlertRadius + } +} + +/// A message with information about a venue +public struct InputMessageVenue: Codable, Equatable, Hashable { + + /// Venue to send + public let venue: Venue + + + public init(venue: Venue) { + self.venue = venue + } +} + +/// A message containing a user contact +public struct InputMessageContact: Codable, Equatable, Hashable { + + /// Contact to send + public let contact: Contact + + + public init(contact: Contact) { + self.contact = contact + } +} + +/// A dice message +public struct InputMessageDice: Codable, Equatable, Hashable { + + /// True, if the chat message draft must be deleted + public let clearDraft: Bool + + /// Emoji on which the dice throw animation is based + public let emoji: String + + + public init( + clearDraft: Bool, + emoji: String + ) { + self.clearDraft = clearDraft + self.emoji = emoji + } +} + +/// A message with a game; not supported for channels or secret chats +public struct InputMessageGame: Codable, Equatable, Hashable { + + /// User identifier of the bot that owns the game + public let botUserId: Int64 + + /// Short name of the game + public let gameShortName: String + + + public init( + botUserId: Int64, + gameShortName: String + ) { + self.botUserId = botUserId + self.gameShortName = gameShortName + } +} + +/// A message with an invoice; can be used only by bots +public struct InputMessageInvoice: Codable, Equatable, Hashable { + + public let description: String + + /// Invoice + public let invoice: Invoice + + /// The content of paid media attached to the invoice; pass null if none + public let paidMedia: InputPaidMedia? + + /// Paid media caption; pass null to use an empty caption; 0-getOption("message_caption_length_max") characters + public let paidMediaCaption: FormattedText? + + /// The invoice payload + public let payload: Data + + /// Product photo height + public let photoHeight: Int + + /// Product photo size + public let photoSize: Int + + /// Product photo URL; optional + public let photoUrl: String + + /// Product photo width + public let photoWidth: Int + + /// JSON-encoded data about the invoice, which will be shared with the payment provider + public let providerData: String + + /// Payment provider token; may be empty for payments in Telegram Stars + public let providerToken: String + + /// Unique invoice bot deep link parameter for the generation of this invoice. If empty, it would be possible to pay directly from forwards of the invoice message + public let startParameter: String + + /// Product title; 1-32 characters + public let title: String + + + public init( + description: String, + invoice: Invoice, + paidMedia: InputPaidMedia?, + paidMediaCaption: FormattedText?, + payload: Data, + photoHeight: Int, + photoSize: Int, + photoUrl: String, + photoWidth: Int, + providerData: String, + providerToken: String, + startParameter: String, + title: String + ) { + self.description = description + self.invoice = invoice + self.paidMedia = paidMedia + self.paidMediaCaption = paidMediaCaption + self.payload = payload + self.photoHeight = photoHeight + self.photoSize = photoSize + self.photoUrl = photoUrl + self.photoWidth = photoWidth + self.providerData = providerData + self.providerToken = providerToken + self.startParameter = startParameter + self.title = title + } +} + +/// A message with a poll. Polls can't be sent to secret chats and channel direct messages chats. Polls can be sent to a private chat only if the chat is a chat with a bot or the Saved Messages chat +public struct InputMessagePoll: Codable, Equatable, Hashable { + + /// True, if multiple answer options can be chosen simultaneously + public let allowsMultipleAnswers: Bool + + /// True, if the poll can be answered multiple times + public let allowsRevoting: Bool + + /// Point in time (Unix timestamp) when the poll will automatically be closed; must be 0-getOption("poll_open_period_max") seconds in the future; pass 0 if not specified + public let closeDate: Int + + /// The list of two-letter ISO 3166-1 alpha-2 codes of countries, users from which will be able to vote; for channel chats only. If empty, then all users can participate in the poll. There can be up to getOption("poll_country_count_max") chosen countries + public let countryCodes: [String] + + public let description: FormattedText + + /// True, if the poll results will appear only after the poll closes + public let hideResultsUntilCloses: Bool + + /// True, if the poll voters are anonymous. Non-anonymous polls can't be sent or forwarded to channels + public let isAnonymous: Bool + + /// True, if the poll needs to be sent already closed; for bots only + public let isClosed: Bool + + /// Media attached to the poll; pass null if none. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, non-live inputMessageLocation, inputMessagePhoto, inputMessageVenue, or inputMessageVideo without caption + public let media: InputMessageContent? + + /// True, if only the users that are members of the chat for more than a day will be able to vote; for channel chats only + public let membersOnly: Bool + + /// Amount of time the poll will be active after creation, in seconds; 0-getOption("poll_open_period_max"); pass 0 if not specified + public let openPeriod: Int + + /// List of poll answer options; 1-getOption("poll_answer_count_max") options + public let options: [InputPollOption] + + /// Poll question; 1-255 characters (up to 300 characters for bots). Only custom emoji entities are allowed to be added and only by Premium users + public let question: FormattedText + + /// True, if poll options must be shown in a fixed random order + public let shuffleOptions: Bool + + /// Type of the poll + public let type: InputPollType + + + public init( + allowsMultipleAnswers: Bool, + allowsRevoting: Bool, + closeDate: Int, + countryCodes: [String], + description: FormattedText, + hideResultsUntilCloses: Bool, + isAnonymous: Bool, + isClosed: Bool, + media: InputMessageContent?, + membersOnly: Bool, + openPeriod: Int, + options: [InputPollOption], + question: FormattedText, + shuffleOptions: Bool, + type: InputPollType + ) { + self.allowsMultipleAnswers = allowsMultipleAnswers + self.allowsRevoting = allowsRevoting + self.closeDate = closeDate + self.countryCodes = countryCodes + self.description = description + self.hideResultsUntilCloses = hideResultsUntilCloses + self.isAnonymous = isAnonymous + self.isClosed = isClosed + self.media = media + self.membersOnly = membersOnly + self.openPeriod = openPeriod + self.options = options + self.question = question + self.shuffleOptions = shuffleOptions + self.type = type + } +} + +/// A stake dice message +public struct InputMessageStakeDice: Codable, Equatable, Hashable { + + /// True, if the chat message draft must be deleted + public let clearDraft: Bool + + /// The Toncoin amount that will be staked; in the smallest units of the currency. Must be in the range getOption("stake_dice_stake_amount_min")-getOption("stake_dice_stake_amount_max") + public let stakeToncoinAmount: Int64 + + /// Hash of the stake dice state. The state hash can be used only if it was received recently enough. Otherwise, a new state must be requested using getStakeDiceState + public let stateHash: String + + + public init( + clearDraft: Bool, + stakeToncoinAmount: Int64, + stateHash: String + ) { + self.clearDraft = clearDraft + self.stakeToncoinAmount = stakeToncoinAmount + self.stateHash = stateHash + } +} + +/// A message with a forwarded story. Stories can't be forwarded to secret chats. A story can be forwarded only if story.can_be_forwarded +public struct InputMessageStory: Codable, Equatable, Hashable { + + /// Story identifier + public let storyId: Int + + /// Identifier of the chat that posted the story + public let storyPosterChatId: Int64 + + + public init( + storyId: Int, + storyPosterChatId: Int64 + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + } +} + +/// A message with a checklist. Checklists can't be sent to secret chats, channel chats and channel direct messages chats; for Telegram Premium users only +public struct InputMessageChecklist: Codable, Equatable, Hashable { + + /// The checklist to send + public let checklist: InputChecklist + + + public init(checklist: InputChecklist) { + self.checklist = checklist + } +} + +/// A forwarded message +public struct InputMessageForwarded: Codable, Equatable, Hashable { + + /// Options to be used to copy content of the message without reference to the original sender; pass null to forward the message as usual + public let copyOptions: MessageCopyOptions? + + /// Identifier for the chat this forwarded message came from + public let fromChatId: Int64 + + /// Pass true if a game message is being shared from a launched game; applies only to game messages + public let inGameShare: Bool + + /// Identifier of the message to forward. A message can be forwarded only if messageProperties.can_be_forwarded + public let messageId: Int64 + + /// The new video start timestamp; ignored if replace_video_start_timestamp == false + public let newVideoStartTimestamp: Int + + /// Pass true to replace video start timestamp in the forwarded message + public let replaceVideoStartTimestamp: Bool + + + public init( + copyOptions: MessageCopyOptions?, + fromChatId: Int64, + inGameShare: Bool, + messageId: Int64, + newVideoStartTimestamp: Int, + replaceVideoStartTimestamp: Bool + ) { + self.copyOptions = copyOptions + self.fromChatId = fromChatId + self.inGameShare = inGameShare + self.messageId = messageId + self.newVideoStartTimestamp = newVideoStartTimestamp + self.replaceVideoStartTimestamp = replaceVideoStartTimestamp + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageReplyTo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageReplyTo.swift new file mode 100644 index 0000000000..fb67def852 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputMessageReplyTo.swift @@ -0,0 +1,153 @@ +// +// InputMessageReplyTo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the message or the story to be replied +public indirect enum InputMessageReplyTo: Codable, Equatable, Hashable { + + /// Describes a message to be replied in the same chat and forum topic + case inputMessageReplyToMessage(InputMessageReplyToMessage) + + /// Describes a message to be replied that is from a different chat or a forum topic; not supported in secret chats + case inputMessageReplyToExternalMessage(InputMessageReplyToExternalMessage) + + /// Describes a story to be replied + case inputMessageReplyToStory(InputMessageReplyToStory) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inputMessageReplyToMessage + case inputMessageReplyToExternalMessage + case inputMessageReplyToStory + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inputMessageReplyToMessage: + let value = try InputMessageReplyToMessage(from: decoder) + self = .inputMessageReplyToMessage(value) + case .inputMessageReplyToExternalMessage: + let value = try InputMessageReplyToExternalMessage(from: decoder) + self = .inputMessageReplyToExternalMessage(value) + case .inputMessageReplyToStory: + let value = try InputMessageReplyToStory(from: decoder) + self = .inputMessageReplyToStory(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inputMessageReplyToMessage(let value): + try container.encode(Kind.inputMessageReplyToMessage, forKey: .type) + try value.encode(to: encoder) + case .inputMessageReplyToExternalMessage(let value): + try container.encode(Kind.inputMessageReplyToExternalMessage, forKey: .type) + try value.encode(to: encoder) + case .inputMessageReplyToStory(let value): + try container.encode(Kind.inputMessageReplyToStory, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Describes a message to be replied in the same chat and forum topic +public struct InputMessageReplyToMessage: Codable, Equatable, Hashable { + + /// Identifier of the checklist task in the message to be replied; pass 0 to reply to the whole message + public let checklistTaskId: Int + + /// The identifier of the message to be replied in the same chat and forum topic. A message can be replied in the same chat and forum topic only if messageProperties.can_be_replied + public let messageId: Int64 + + /// Identifier of the poll option in the message to be replied; pass an empty string if none + public let pollOptionId: String + + /// Quote from the message to be replied; pass null if none. Must always be null for replies in secret chats + public let quote: InputTextQuote? + + + public init( + checklistTaskId: Int, + messageId: Int64, + pollOptionId: String, + quote: InputTextQuote? + ) { + self.checklistTaskId = checklistTaskId + self.messageId = messageId + self.pollOptionId = pollOptionId + self.quote = quote + } +} + +/// Describes a message to be replied that is from a different chat or a forum topic; not supported in secret chats +public struct InputMessageReplyToExternalMessage: Codable, Equatable, Hashable { + + /// The identifier of the chat to which the message to be replied belongs + public let chatId: Int64 + + /// Identifier of the checklist task in the message to be replied; pass 0 to reply to the whole message + public let checklistTaskId: Int + + /// The identifier of the message to be replied in the specified chat. A message can be replied in another chat or forum topic only if messageProperties.can_be_replied_in_another_chat + public let messageId: Int64 + + /// Identifier of the poll option in the message to be replied; pass an empty string if none + public let pollOptionId: String + + /// Quote from the message to be replied; pass null if none + public let quote: InputTextQuote? + + + public init( + chatId: Int64, + checklistTaskId: Int, + messageId: Int64, + pollOptionId: String, + quote: InputTextQuote? + ) { + self.chatId = chatId + self.checklistTaskId = checklistTaskId + self.messageId = messageId + self.pollOptionId = pollOptionId + self.quote = quote + } +} + +/// Describes a story to be replied +public struct InputMessageReplyToStory: Codable, Equatable, Hashable { + + /// The identifier of the story + public let storyId: Int + + /// The identifier of the poster of the story. Currently, stories can be replied only in the chat that posted the story; channel stories can't be replied + public let storyPosterChatId: Int64 + + + public init( + storyId: Int, + storyPosterChatId: Int64 + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMedia.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMedia.swift new file mode 100644 index 0000000000..f9ac344754 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMedia.swift @@ -0,0 +1,51 @@ +// +// InputPaidMedia.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a paid media to be sent +public struct InputPaidMedia: Codable, Equatable, Hashable { + + /// File identifiers of the stickers added to the media, if applicable + public let addedStickerFileIds: [Int] + + /// Media height + public let height: Int + + /// Photo or video to be sent + public let media: InputFile + + /// Media thumbnail; pass null to skip thumbnail uploading + public let thumbnail: InputThumbnail? + + /// Type of the media + public let type: InputPaidMediaType + + /// Media width + public let width: Int + + + public init( + addedStickerFileIds: [Int], + height: Int, + media: InputFile, + thumbnail: InputThumbnail?, + type: InputPaidMediaType, + width: Int + ) { + self.addedStickerFileIds = addedStickerFileIds + self.height = height + self.media = media + self.thumbnail = thumbnail + self.type = type + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMediaType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMediaType.swift new file mode 100644 index 0000000000..8dbc8e2fcb --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPaidMediaType.swift @@ -0,0 +1,102 @@ +// +// InputPaidMediaType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of paid media to sent +public indirect enum InputPaidMediaType: Codable, Equatable, Hashable { + + /// The media is a photo. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20 + case inputPaidMediaTypePhoto(InputPaidMediaTypePhoto) + + /// The media is a video + case inputPaidMediaTypeVideo(InputPaidMediaTypeVideo) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inputPaidMediaTypePhoto + case inputPaidMediaTypeVideo + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inputPaidMediaTypePhoto: + let value = try InputPaidMediaTypePhoto(from: decoder) + self = .inputPaidMediaTypePhoto(value) + case .inputPaidMediaTypeVideo: + let value = try InputPaidMediaTypeVideo(from: decoder) + self = .inputPaidMediaTypeVideo(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inputPaidMediaTypePhoto(let value): + try container.encode(Kind.inputPaidMediaTypePhoto, forKey: .type) + try value.encode(to: encoder) + case .inputPaidMediaTypeVideo(let value): + try container.encode(Kind.inputPaidMediaTypeVideo, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The media is a photo. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20 +public struct InputPaidMediaTypePhoto: Codable, Equatable, Hashable { + + /// Video of the live photo; pass null if the photo isn't a live photo + public let video: InputFile? + + + public init(video: InputFile?) { + self.video = video + } +} + +/// The media is a video +public struct InputPaidMediaTypeVideo: Codable, Equatable, Hashable { + + /// Cover of the video; pass null to skip cover uploading + public let cover: InputFile? + + /// Duration of the video, in seconds + public let duration: Int + + /// Timestamp from which the video playing must start, in seconds + public let startTimestamp: Int + + /// True, if the video is expected to be streamed + public let supportsStreaming: Bool + + + public init( + cover: InputFile?, + duration: Int, + startTimestamp: Int, + supportsStreaming: Bool + ) { + self.cover = cover + self.duration = duration + self.startTimestamp = startTimestamp + self.supportsStreaming = supportsStreaming + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollOption.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollOption.swift new file mode 100644 index 0000000000..cafce443a4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollOption.swift @@ -0,0 +1,31 @@ +// +// InputPollOption.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes one answer option of a poll to be created +public struct InputPollOption: Codable, Equatable, Hashable { + + /// Option media; pass null if none; ignored in addPollOption. Must be one of the following types: inputMessageAnimation, non-live inputMessageLocation, inputMessagePhoto, inputMessageSticker, inputMessageVenue, or inputMessageVideo without caption + public let media: InputMessageContent? + + /// Option text; 1-100 characters. Only custom emoji entities are allowed to be added and only by Premium users + public let text: FormattedText + + + public init( + media: InputMessageContent?, + text: FormattedText + ) { + self.media = media + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollType.swift new file mode 100644 index 0000000000..fdb3d7fd3f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputPollType.swift @@ -0,0 +1,97 @@ +// +// InputPollType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of poll to send +public indirect enum InputPollType: Codable, Equatable, Hashable { + + /// A regular poll + case inputPollTypeRegular(InputPollTypeRegular) + + /// A poll in quiz mode, which has predefined correct answers + case inputPollTypeQuiz(InputPollTypeQuiz) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inputPollTypeRegular + case inputPollTypeQuiz + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inputPollTypeRegular: + let value = try InputPollTypeRegular(from: decoder) + self = .inputPollTypeRegular(value) + case .inputPollTypeQuiz: + let value = try InputPollTypeQuiz(from: decoder) + self = .inputPollTypeQuiz(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inputPollTypeRegular(let value): + try container.encode(Kind.inputPollTypeRegular, forKey: .type) + try value.encode(to: encoder) + case .inputPollTypeQuiz(let value): + try container.encode(Kind.inputPollTypeQuiz, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A regular poll +public struct InputPollTypeRegular: Codable, Equatable, Hashable { + + /// True, if answer options can be added to the poll after creation; not supported in channel chats and for anonymous polls + public let allowAddingOptions: Bool + + + public init(allowAddingOptions: Bool) { + self.allowAddingOptions = allowAddingOptions + } +} + +/// A poll in quiz mode, which has predefined correct answers +public struct InputPollTypeQuiz: Codable, Equatable, Hashable { + + /// Increasing list of 0-based identifiers of the correct answer options; must be non-empty + public let correctOptionIds: [Int] + + /// Text that is shown when the user chooses an incorrect answer or taps on the lamp icon; 0-200 characters with at most 2 line feeds + public let explanation: FormattedText + + /// Media that is shown when the user chooses an incorrect answer or taps on the lamp icon; pass null if none. Must be one of the following types: inputMessageAnimation, inputMessageAudio, inputMessageDocument, non-live inputMessageLocation, inputMessagePhoto, inputMessageVenue, or inputMessageVideo without caption + public let explanationMedia: InputMessageContent? + + + public init( + correctOptionIds: [Int], + explanation: FormattedText, + explanationMedia: InputMessageContent? + ) { + self.correctOptionIds = correctOptionIds + self.explanation = explanation + self.explanationMedia = explanationMedia + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputSuggestedPostInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputSuggestedPostInfo.swift new file mode 100644 index 0000000000..058cb16950 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputSuggestedPostInfo.swift @@ -0,0 +1,31 @@ +// +// InputSuggestedPostInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a post to suggest +public struct InputSuggestedPostInfo: Codable, Equatable, Hashable { + + /// Price of the suggested post; pass null to suggest a post without payment. If the current user isn't an administrator of the channel direct messages chat and has no enough funds to pay for the post, then the error "BALANCE_TOO_LOW" will be returned immediately + public let price: SuggestedPostPrice? + + /// Point in time (Unix timestamp) when the post is expected to be published; pass 0 if the date isn't restricted. If specified, then the date must be getOption("suggested_post_send_delay_min")-getOption("suggested_post_send_delay_max") seconds in the future + public let sendDate: Int + + + public init( + price: SuggestedPostPrice?, + sendDate: Int + ) { + self.price = price + self.sendDate = sendDate + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputTextQuote.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputTextQuote.swift new file mode 100644 index 0000000000..78ec84de77 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputTextQuote.swift @@ -0,0 +1,31 @@ +// +// InputTextQuote.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes manually chosen quote from another message +public struct InputTextQuote: Codable, Equatable, Hashable { + + /// Quote position in the original message in UTF-16 code units + public let position: Int + + /// Text of the quote; 0-getOption("message_reply_quote_length_max") characters. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, and DateTime entities are allowed to be kept and must be kept in the quote + public let text: FormattedText + + + public init( + position: Int, + text: FormattedText + ) { + self.position = position + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputThumbnail.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputThumbnail.swift new file mode 100644 index 0000000000..9b20fb2f17 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InputThumbnail.swift @@ -0,0 +1,36 @@ +// +// InputThumbnail.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// A thumbnail to be sent along with a file; must be in JPEG or WEBP format for stickers, and less than 200 KB in size +public struct InputThumbnail: Codable, Equatable, Hashable { + + /// Thumbnail height, usually shouldn't exceed 320. Use 0 if unknown + public let height: Int + + /// Thumbnail file to send. Sending thumbnails by file_id is currently not supported + public let thumbnail: InputFile + + /// Thumbnail width, usually shouldn't exceed 320. Use 0 if unknown + public let width: Int + + + public init( + height: Int, + thumbnail: InputFile, + width: Int + ) { + self.height = height + self.thumbnail = thumbnail + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InternalLinkType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InternalLinkType.swift new file mode 100644 index 0000000000..94930fc114 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InternalLinkType.swift @@ -0,0 +1,1396 @@ +// +// InternalLinkType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an internal https://t.me or tg: link, which must be processed by the application in a special way +/// This Swift enum is recursive. +public indirect enum InternalLinkType: Codable, Equatable, Hashable { + + /// The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat. Then, call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then, use getAttachmentMenuBot to receive information about the bot. If the bot isn't added to attachment menu, then show a disclaimer about Mini Apps being third-party applications, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL + case internalLinkTypeAttachmentMenuBot(InternalLinkTypeAttachmentMenuBot) + + /// The link contains an authentication code. Call checkAuthenticationCode with the code if the current authorization state is authorizationStateWaitCode + case internalLinkTypeAuthenticationCode(InternalLinkTypeAuthenticationCode) + + /// The link is a link to a background. Call searchBackground with the given background name to process the link. If background is found and the user wants to apply it, then call setDefaultBackground + case internalLinkTypeBackground(InternalLinkTypeBackground) + + /// The link is a link to a Telegram bot, which is expected to be added to a channel chat as an administrator. Call searchPublicChat with the given bot username and check that the user is a bot, ask the current user to select a channel chat to add the bot to as an administrator. Then, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator, check that the current user can edit its administrator rights and combine received rights with the requested administrator rights. Then, show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed rights + case internalLinkTypeBotAddToChannel(InternalLinkTypeBotAddToChannel) + + /// The link is a link to a chat with a Telegram bot. Call searchPublicChat with the given bot username, check that the user is a bot, show START button in the chat with the bot, and then call sendBotStartMessage with the given start parameter after the button is pressed + case internalLinkTypeBotStart(InternalLinkTypeBotStart) + + /// The link is a link to a Telegram bot, which is expected to be added to a group chat. Call searchPublicChat with the given bot username, check that the user is a bot and can be added to groups, ask the current user to select a basic group or a supergroup chat to add the bot to, taking into account that bots can be added to a public supergroup only by administrators of the supergroup. If administrator rights are provided by the link, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator, check that the current user can edit its administrator rights, combine received rights with the requested administrator rights, show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed administrator rights. Before call to setChatMemberStatus it may be required to upgrade the chosen basic group chat to a supergroup chat. Then, if start_parameter isn't empty, call sendBotStartMessage with the given start parameter and the chosen chat; otherwise, just send /start message with bot's username added to the chat + case internalLinkTypeBotStartInGroup(InternalLinkTypeBotStartInGroup) + + /// The link is a link to a business chat. Use getBusinessChatLinkInfo with the provided link name to get information about the link, then open received private chat and replace chat draft with the provided text + case internalLinkTypeBusinessChat(InternalLinkTypeBusinessChat) + + /// The link is a link to the Call tab or page + case internalLinkTypeCallsPage(InternalLinkTypeCallsPage) + + /// The link is an affiliate program link. Call searchChatAffiliateProgram with the given username and referrer to process the link + case internalLinkTypeChatAffiliateProgram(InternalLinkTypeChatAffiliateProgram) + + /// The link is a link to boost a Telegram chat. Call getChatBoostLinkInfo with the given URL to process the link. If the chat is found, then call getChatBoostStatus and getAvailableChatBoostSlots to get the current boost status and check whether the chat can be boosted. If the user wants to boost the chat and the chat can be boosted, then call boostChat + case internalLinkTypeChatBoost(InternalLinkTypeChatBoost) + + /// The link is an invite link to a chat folder. Call checkChatFolderInviteLink with the given invite link to process the link. If the link is valid and the user wants to join the chat folder, then call addChatFolderByInviteLink + case internalLinkTypeChatFolderInvite(InternalLinkTypeChatFolderInvite) + + /// The link is a chat invite link. Call checkChatInviteLink with the given invite link to process the link. If the link is valid and the user wants to join the chat, then call joinChatByInviteLink + case internalLinkTypeChatInvite(InternalLinkTypeChatInvite) + + /// The link is a link that allows to select some chats + case internalLinkTypeChatSelection + + /// The link is a link to the Contacts tab or page + case internalLinkTypeContactsPage(InternalLinkTypeContactsPage) + + /// The link is a link to a channel direct messages chat by username of the channel. Call searchPublicChat with the given chat username to process the link. If the chat is found and is channel, open the direct messages chat of the channel + case internalLinkTypeDirectMessagesChat(InternalLinkTypeDirectMessagesChat) + + /// The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a chat to send the game, and then call sendMessage with inputMessageGame + case internalLinkTypeGame(InternalLinkTypeGame) + + /// The link is a link to a gift auction. Call getGiftAuctionState with the given auction identifier to process the link + case internalLinkTypeGiftAuction(InternalLinkTypeGiftAuction) + + /// The link is a link to a gift collection. Call searchPublicChat with the given username, then call getReceivedGifts with the received gift owner identifier and the given collection identifier, then show the collection if received + case internalLinkTypeGiftCollection(InternalLinkTypeGiftCollection) + + /// The link is a link to a group call that isn't bound to a chat. Use getGroupCallParticipants to get the list of group call participants and show them on the join group call screen. Call joinGroupCall with the given invite_link to join the call + case internalLinkTypeGroupCall(InternalLinkTypeGroupCall) + + /// The link must be opened in an Instant View. Call getWebPageInstantView with the given URL to process the link. If Instant View is found, then show it, otherwise, open the fallback URL in an external browser + case internalLinkTypeInstantView(InternalLinkTypeInstantView) + + /// The link is a link to an invoice. Call getPaymentForm with the given invoice name to process the link + case internalLinkTypeInvoice(InternalLinkTypeInvoice) + + /// The link is a link to a language pack. Call getLanguagePackInfo with the given language pack identifier to process the link. If the language pack is found and the user wants to apply it, then call setOption for the option "language_pack_id" + case internalLinkTypeLanguagePack(InternalLinkTypeLanguagePack) + + /// The link is a link to a live story. Call searchPublicChat with the given chat username, then getChatActiveStories to get active stories in the chat, then find a live story among active stories of the chat, and then joinLiveStory to join the live story + case internalLinkTypeLiveStory(InternalLinkTypeLiveStory) + + /// The link is a link to the main Web App of a bot. Call searchPublicChat with the given bot username, check that the user is a bot and has the main Web App. If the bot can be added to attachment menu, then use getAttachmentMenuBot to receive information about the bot, then if the bot isn't added to side menu, show a disclaimer about Mini Apps being third-party applications, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu, then if the user accepts the terms and confirms adding, use toggleBotIsAddedToAttachmentMenu to add the bot. Then, use getMainWebApp with the given start parameter and mode and open the returned URL as a Web App + case internalLinkTypeMainWebApp(InternalLinkTypeMainWebApp) + + /// The link is a link to a Telegram message or a forum topic. Call getMessageLinkInfo with the given URL to process the link, and then open received forum topic or chat and show the message there + case internalLinkTypeMessage(InternalLinkTypeMessage) + + /// The link contains a message draft text. A share screen needs to be shown to the user, then the chosen chat must be opened and the text is added to the input field + case internalLinkTypeMessageDraft(InternalLinkTypeMessageDraft) + + /// The link is a link to the My Profile application page + case internalLinkTypeMyProfilePage(InternalLinkTypeMyProfilePage) + + /// The link is a link to the screen for creating a new channel chat + case internalLinkTypeNewChannelChat + + /// The link is a link to the screen for creating a new group chat + case internalLinkTypeNewGroupChat + + /// The link is a link to the screen for creating a new private chat with a contact + case internalLinkTypeNewPrivateChat + + /// The link is a link to open the story posting interface + case internalLinkTypeNewStory(InternalLinkTypeNewStory) + + /// The link is an OAuth link. Call getOauthLinkInfo with the given URL to process the link if the link was received from outside of the application; otherwise, ignore it. After getOauthLinkInfo, show the user confirmation dialog and process it with checkOauthRequestMatchCode, acceptOauthRequest or declineOauthRequest + case internalLinkTypeOauth(InternalLinkTypeOauth) + + /// The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application; otherwise, ignore it + case internalLinkTypePassportDataRequest(InternalLinkTypePassportDataRequest) + + /// The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberCode with the given phone number and with phoneNumberCodeTypeConfirmOwnership with the given hash to process the link. If succeeded, call checkPhoneNumberCode to check entered by the user code, or resendPhoneNumberCode to resend it + case internalLinkTypePhoneNumberConfirmation(InternalLinkTypePhoneNumberConfirmation) + + /// The link is a link to the Premium features screen of the application from which the user can subscribe to Telegram Premium. Call getPremiumFeatures with the given referrer to process the link + case internalLinkTypePremiumFeaturesPage(InternalLinkTypePremiumFeaturesPage) + + /// The link is a link with a Telegram Premium gift code. Call checkPremiumGiftCode with the given code to process the link. If the code is valid and the user wants to apply it, then call applyPremiumGiftCode + case internalLinkTypePremiumGiftCode(InternalLinkTypePremiumGiftCode) + + /// The link is a link to the screen for gifting Telegram Premium subscriptions to friends via inputInvoiceTelegram with telegramPaymentPurposePremiumGift payments or in-store purchases + case internalLinkTypePremiumGiftPurchase(InternalLinkTypePremiumGiftPurchase) + + /// The link is a link to a proxy. Call addProxy with the given parameters to process the link and add the proxy + case internalLinkTypeProxy(InternalLinkTypeProxy) + + /// The link is a link to a chat by its username. Call searchPublicChat with the given chat username to process the link. If the chat is found, open its profile information screen or the chat itself. If draft text isn't empty and the chat is a private chat with a regular user, then put the draft text in the input field + case internalLinkTypePublicChat(InternalLinkTypePublicChat) + + /// The link can be used to login the current user on another device, but it must be scanned from QR-code using in-app camera. An alert similar to "This code can be used to allow someone to log in to your Telegram account. To confirm Telegram login, please go to Settings > Devices > Scan QR and scan the code" needs to be shown + case internalLinkTypeQrCodeAuthentication + + /// The link is a link to a dialog for creating of a managed bot. Call searchPublicChat with the given manager bot username. If the chat is found, the chat is a chat with a bot and the bot has can_manage_bots == true, then show bot creation confirmation dialog with the given suggested_bot_username and suggested_bot_name. If user agrees, call createBot with via_link == true to create the bot + case internalLinkTypeRequestManagedBot(InternalLinkTypeRequestManagedBot) + + /// The link forces restore of App Store purchases when opened. For official iOS application only + case internalLinkTypeRestorePurchases + + /// The link is a link to the Saved Messages chat. Call createPrivateChat with getOption("my_id") and open the chat + case internalLinkTypeSavedMessages + + /// The link is a link to the global chat and messages search field + case internalLinkTypeSearch + + /// The link is a link to application settings + case internalLinkTypeSettings(InternalLinkTypeSettings) + + /// The link is a link to the Telegram Star purchase section of the application + case internalLinkTypeStarPurchase(InternalLinkTypeStarPurchase) + + /// The link is a link to a sticker set. Call searchStickerSet with the given sticker set name to process the link and show the sticker set. If the sticker set is found and the user wants to add it, then call changeStickerSet + case internalLinkTypeStickerSet(InternalLinkTypeStickerSet) + + /// The link is a link to a story. Call searchPublicChat with the given poster username, then call getStory with the received chat identifier and the given story identifier, then show the story if received + case internalLinkTypeStory(InternalLinkTypeStory) + + /// The link is a link to an album of stories. Call searchPublicChat with the given username, then call getStoryAlbumStories with the received chat identifier and the given story album identifier, then show the story album if received + case internalLinkTypeStoryAlbum(InternalLinkTypeStoryAlbum) + + /// The link is a link to a text composition style. Call searchTextCompositionStyle with the given style name to get information about the style. If the style is found and the user wants to add it, then call addTextCompositionStyle + case internalLinkTypeTextCompositionStyle(InternalLinkTypeTextCompositionStyle) + + /// The link is a link to a cloud theme. TDLib has no theme support yet + case internalLinkTypeTheme(InternalLinkTypeTheme) + + /// The link is an unknown tg: link. Call getDeepLinkInfo to process the link + case internalLinkTypeUnknownDeepLink(InternalLinkTypeUnknownDeepLink) + + /// The link is a link to an upgraded gift. Call getUpgradedGift with the given name to process the link + case internalLinkTypeUpgradedGift(InternalLinkTypeUpgradedGift) + + /// The link is a link to a user by its phone number. Call searchUserByPhoneNumber with the given phone number to process the link. If the user is found, then call createPrivateChat and open user's profile information screen or the chat itself. If draft text isn't empty, then put the draft text in the input field + case internalLinkTypeUserPhoneNumber(InternalLinkTypeUserPhoneNumber) + + /// The link is a link to a user by a temporary token. Call searchUserByToken with the given token to process the link. If the user is found, then call createPrivateChat and open the chat + case internalLinkTypeUserToken(InternalLinkTypeUserToken) + + /// The link is a link to a video chat. Call searchPublicChat with the given chat username, and then joinVideoChat with the given invite hash to process the link + case internalLinkTypeVideoChat(InternalLinkTypeVideoChat) + + /// The link is a link to a Web App. Call searchPublicChat with the given bot username, check that the user is a bot. If the bot is restricted for the current user, then show an error message. Otherwise, call searchWebApp with the received bot and the given web_app_short_name. Process received foundWebApp by showing a confirmation dialog if needed. If the bot can be added to attachment or side menu, but isn't added yet, then show a disclaimer about Mini Apps being third-party applications instead of the dialog and ask the user to accept their Terms of service. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. Then, call getWebAppLinkUrl and open the returned URL as a Web App + case internalLinkTypeWebApp(InternalLinkTypeWebApp) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case internalLinkTypeAttachmentMenuBot + case internalLinkTypeAuthenticationCode + case internalLinkTypeBackground + case internalLinkTypeBotAddToChannel + case internalLinkTypeBotStart + case internalLinkTypeBotStartInGroup + case internalLinkTypeBusinessChat + case internalLinkTypeCallsPage + case internalLinkTypeChatAffiliateProgram + case internalLinkTypeChatBoost + case internalLinkTypeChatFolderInvite + case internalLinkTypeChatInvite + case internalLinkTypeChatSelection + case internalLinkTypeContactsPage + case internalLinkTypeDirectMessagesChat + case internalLinkTypeGame + case internalLinkTypeGiftAuction + case internalLinkTypeGiftCollection + case internalLinkTypeGroupCall + case internalLinkTypeInstantView + case internalLinkTypeInvoice + case internalLinkTypeLanguagePack + case internalLinkTypeLiveStory + case internalLinkTypeMainWebApp + case internalLinkTypeMessage + case internalLinkTypeMessageDraft + case internalLinkTypeMyProfilePage + case internalLinkTypeNewChannelChat + case internalLinkTypeNewGroupChat + case internalLinkTypeNewPrivateChat + case internalLinkTypeNewStory + case internalLinkTypeOauth + case internalLinkTypePassportDataRequest + case internalLinkTypePhoneNumberConfirmation + case internalLinkTypePremiumFeaturesPage + case internalLinkTypePremiumGiftCode + case internalLinkTypePremiumGiftPurchase + case internalLinkTypeProxy + case internalLinkTypePublicChat + case internalLinkTypeQrCodeAuthentication + case internalLinkTypeRequestManagedBot + case internalLinkTypeRestorePurchases + case internalLinkTypeSavedMessages + case internalLinkTypeSearch + case internalLinkTypeSettings + case internalLinkTypeStarPurchase + case internalLinkTypeStickerSet + case internalLinkTypeStory + case internalLinkTypeStoryAlbum + case internalLinkTypeTextCompositionStyle + case internalLinkTypeTheme + case internalLinkTypeUnknownDeepLink + case internalLinkTypeUpgradedGift + case internalLinkTypeUserPhoneNumber + case internalLinkTypeUserToken + case internalLinkTypeVideoChat + case internalLinkTypeWebApp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .internalLinkTypeAttachmentMenuBot: + let value = try InternalLinkTypeAttachmentMenuBot(from: decoder) + self = .internalLinkTypeAttachmentMenuBot(value) + case .internalLinkTypeAuthenticationCode: + let value = try InternalLinkTypeAuthenticationCode(from: decoder) + self = .internalLinkTypeAuthenticationCode(value) + case .internalLinkTypeBackground: + let value = try InternalLinkTypeBackground(from: decoder) + self = .internalLinkTypeBackground(value) + case .internalLinkTypeBotAddToChannel: + let value = try InternalLinkTypeBotAddToChannel(from: decoder) + self = .internalLinkTypeBotAddToChannel(value) + case .internalLinkTypeBotStart: + let value = try InternalLinkTypeBotStart(from: decoder) + self = .internalLinkTypeBotStart(value) + case .internalLinkTypeBotStartInGroup: + let value = try InternalLinkTypeBotStartInGroup(from: decoder) + self = .internalLinkTypeBotStartInGroup(value) + case .internalLinkTypeBusinessChat: + let value = try InternalLinkTypeBusinessChat(from: decoder) + self = .internalLinkTypeBusinessChat(value) + case .internalLinkTypeCallsPage: + let value = try InternalLinkTypeCallsPage(from: decoder) + self = .internalLinkTypeCallsPage(value) + case .internalLinkTypeChatAffiliateProgram: + let value = try InternalLinkTypeChatAffiliateProgram(from: decoder) + self = .internalLinkTypeChatAffiliateProgram(value) + case .internalLinkTypeChatBoost: + let value = try InternalLinkTypeChatBoost(from: decoder) + self = .internalLinkTypeChatBoost(value) + case .internalLinkTypeChatFolderInvite: + let value = try InternalLinkTypeChatFolderInvite(from: decoder) + self = .internalLinkTypeChatFolderInvite(value) + case .internalLinkTypeChatInvite: + let value = try InternalLinkTypeChatInvite(from: decoder) + self = .internalLinkTypeChatInvite(value) + case .internalLinkTypeChatSelection: + self = .internalLinkTypeChatSelection + case .internalLinkTypeContactsPage: + let value = try InternalLinkTypeContactsPage(from: decoder) + self = .internalLinkTypeContactsPage(value) + case .internalLinkTypeDirectMessagesChat: + let value = try InternalLinkTypeDirectMessagesChat(from: decoder) + self = .internalLinkTypeDirectMessagesChat(value) + case .internalLinkTypeGame: + let value = try InternalLinkTypeGame(from: decoder) + self = .internalLinkTypeGame(value) + case .internalLinkTypeGiftAuction: + let value = try InternalLinkTypeGiftAuction(from: decoder) + self = .internalLinkTypeGiftAuction(value) + case .internalLinkTypeGiftCollection: + let value = try InternalLinkTypeGiftCollection(from: decoder) + self = .internalLinkTypeGiftCollection(value) + case .internalLinkTypeGroupCall: + let value = try InternalLinkTypeGroupCall(from: decoder) + self = .internalLinkTypeGroupCall(value) + case .internalLinkTypeInstantView: + let value = try InternalLinkTypeInstantView(from: decoder) + self = .internalLinkTypeInstantView(value) + case .internalLinkTypeInvoice: + let value = try InternalLinkTypeInvoice(from: decoder) + self = .internalLinkTypeInvoice(value) + case .internalLinkTypeLanguagePack: + let value = try InternalLinkTypeLanguagePack(from: decoder) + self = .internalLinkTypeLanguagePack(value) + case .internalLinkTypeLiveStory: + let value = try InternalLinkTypeLiveStory(from: decoder) + self = .internalLinkTypeLiveStory(value) + case .internalLinkTypeMainWebApp: + let value = try InternalLinkTypeMainWebApp(from: decoder) + self = .internalLinkTypeMainWebApp(value) + case .internalLinkTypeMessage: + let value = try InternalLinkTypeMessage(from: decoder) + self = .internalLinkTypeMessage(value) + case .internalLinkTypeMessageDraft: + let value = try InternalLinkTypeMessageDraft(from: decoder) + self = .internalLinkTypeMessageDraft(value) + case .internalLinkTypeMyProfilePage: + let value = try InternalLinkTypeMyProfilePage(from: decoder) + self = .internalLinkTypeMyProfilePage(value) + case .internalLinkTypeNewChannelChat: + self = .internalLinkTypeNewChannelChat + case .internalLinkTypeNewGroupChat: + self = .internalLinkTypeNewGroupChat + case .internalLinkTypeNewPrivateChat: + self = .internalLinkTypeNewPrivateChat + case .internalLinkTypeNewStory: + let value = try InternalLinkTypeNewStory(from: decoder) + self = .internalLinkTypeNewStory(value) + case .internalLinkTypeOauth: + let value = try InternalLinkTypeOauth(from: decoder) + self = .internalLinkTypeOauth(value) + case .internalLinkTypePassportDataRequest: + let value = try InternalLinkTypePassportDataRequest(from: decoder) + self = .internalLinkTypePassportDataRequest(value) + case .internalLinkTypePhoneNumberConfirmation: + let value = try InternalLinkTypePhoneNumberConfirmation(from: decoder) + self = .internalLinkTypePhoneNumberConfirmation(value) + case .internalLinkTypePremiumFeaturesPage: + let value = try InternalLinkTypePremiumFeaturesPage(from: decoder) + self = .internalLinkTypePremiumFeaturesPage(value) + case .internalLinkTypePremiumGiftCode: + let value = try InternalLinkTypePremiumGiftCode(from: decoder) + self = .internalLinkTypePremiumGiftCode(value) + case .internalLinkTypePremiumGiftPurchase: + let value = try InternalLinkTypePremiumGiftPurchase(from: decoder) + self = .internalLinkTypePremiumGiftPurchase(value) + case .internalLinkTypeProxy: + let value = try InternalLinkTypeProxy(from: decoder) + self = .internalLinkTypeProxy(value) + case .internalLinkTypePublicChat: + let value = try InternalLinkTypePublicChat(from: decoder) + self = .internalLinkTypePublicChat(value) + case .internalLinkTypeQrCodeAuthentication: + self = .internalLinkTypeQrCodeAuthentication + case .internalLinkTypeRequestManagedBot: + let value = try InternalLinkTypeRequestManagedBot(from: decoder) + self = .internalLinkTypeRequestManagedBot(value) + case .internalLinkTypeRestorePurchases: + self = .internalLinkTypeRestorePurchases + case .internalLinkTypeSavedMessages: + self = .internalLinkTypeSavedMessages + case .internalLinkTypeSearch: + self = .internalLinkTypeSearch + case .internalLinkTypeSettings: + let value = try InternalLinkTypeSettings(from: decoder) + self = .internalLinkTypeSettings(value) + case .internalLinkTypeStarPurchase: + let value = try InternalLinkTypeStarPurchase(from: decoder) + self = .internalLinkTypeStarPurchase(value) + case .internalLinkTypeStickerSet: + let value = try InternalLinkTypeStickerSet(from: decoder) + self = .internalLinkTypeStickerSet(value) + case .internalLinkTypeStory: + let value = try InternalLinkTypeStory(from: decoder) + self = .internalLinkTypeStory(value) + case .internalLinkTypeStoryAlbum: + let value = try InternalLinkTypeStoryAlbum(from: decoder) + self = .internalLinkTypeStoryAlbum(value) + case .internalLinkTypeTextCompositionStyle: + let value = try InternalLinkTypeTextCompositionStyle(from: decoder) + self = .internalLinkTypeTextCompositionStyle(value) + case .internalLinkTypeTheme: + let value = try InternalLinkTypeTheme(from: decoder) + self = .internalLinkTypeTheme(value) + case .internalLinkTypeUnknownDeepLink: + let value = try InternalLinkTypeUnknownDeepLink(from: decoder) + self = .internalLinkTypeUnknownDeepLink(value) + case .internalLinkTypeUpgradedGift: + let value = try InternalLinkTypeUpgradedGift(from: decoder) + self = .internalLinkTypeUpgradedGift(value) + case .internalLinkTypeUserPhoneNumber: + let value = try InternalLinkTypeUserPhoneNumber(from: decoder) + self = .internalLinkTypeUserPhoneNumber(value) + case .internalLinkTypeUserToken: + let value = try InternalLinkTypeUserToken(from: decoder) + self = .internalLinkTypeUserToken(value) + case .internalLinkTypeVideoChat: + let value = try InternalLinkTypeVideoChat(from: decoder) + self = .internalLinkTypeVideoChat(value) + case .internalLinkTypeWebApp: + let value = try InternalLinkTypeWebApp(from: decoder) + self = .internalLinkTypeWebApp(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .internalLinkTypeAttachmentMenuBot(let value): + try container.encode(Kind.internalLinkTypeAttachmentMenuBot, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeAuthenticationCode(let value): + try container.encode(Kind.internalLinkTypeAuthenticationCode, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeBackground(let value): + try container.encode(Kind.internalLinkTypeBackground, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeBotAddToChannel(let value): + try container.encode(Kind.internalLinkTypeBotAddToChannel, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeBotStart(let value): + try container.encode(Kind.internalLinkTypeBotStart, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeBotStartInGroup(let value): + try container.encode(Kind.internalLinkTypeBotStartInGroup, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeBusinessChat(let value): + try container.encode(Kind.internalLinkTypeBusinessChat, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeCallsPage(let value): + try container.encode(Kind.internalLinkTypeCallsPage, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeChatAffiliateProgram(let value): + try container.encode(Kind.internalLinkTypeChatAffiliateProgram, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeChatBoost(let value): + try container.encode(Kind.internalLinkTypeChatBoost, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeChatFolderInvite(let value): + try container.encode(Kind.internalLinkTypeChatFolderInvite, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeChatInvite(let value): + try container.encode(Kind.internalLinkTypeChatInvite, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeChatSelection: + try container.encode(Kind.internalLinkTypeChatSelection, forKey: .type) + case .internalLinkTypeContactsPage(let value): + try container.encode(Kind.internalLinkTypeContactsPage, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeDirectMessagesChat(let value): + try container.encode(Kind.internalLinkTypeDirectMessagesChat, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeGame(let value): + try container.encode(Kind.internalLinkTypeGame, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeGiftAuction(let value): + try container.encode(Kind.internalLinkTypeGiftAuction, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeGiftCollection(let value): + try container.encode(Kind.internalLinkTypeGiftCollection, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeGroupCall(let value): + try container.encode(Kind.internalLinkTypeGroupCall, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeInstantView(let value): + try container.encode(Kind.internalLinkTypeInstantView, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeInvoice(let value): + try container.encode(Kind.internalLinkTypeInvoice, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeLanguagePack(let value): + try container.encode(Kind.internalLinkTypeLanguagePack, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeLiveStory(let value): + try container.encode(Kind.internalLinkTypeLiveStory, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeMainWebApp(let value): + try container.encode(Kind.internalLinkTypeMainWebApp, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeMessage(let value): + try container.encode(Kind.internalLinkTypeMessage, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeMessageDraft(let value): + try container.encode(Kind.internalLinkTypeMessageDraft, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeMyProfilePage(let value): + try container.encode(Kind.internalLinkTypeMyProfilePage, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeNewChannelChat: + try container.encode(Kind.internalLinkTypeNewChannelChat, forKey: .type) + case .internalLinkTypeNewGroupChat: + try container.encode(Kind.internalLinkTypeNewGroupChat, forKey: .type) + case .internalLinkTypeNewPrivateChat: + try container.encode(Kind.internalLinkTypeNewPrivateChat, forKey: .type) + case .internalLinkTypeNewStory(let value): + try container.encode(Kind.internalLinkTypeNewStory, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeOauth(let value): + try container.encode(Kind.internalLinkTypeOauth, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePassportDataRequest(let value): + try container.encode(Kind.internalLinkTypePassportDataRequest, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePhoneNumberConfirmation(let value): + try container.encode(Kind.internalLinkTypePhoneNumberConfirmation, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePremiumFeaturesPage(let value): + try container.encode(Kind.internalLinkTypePremiumFeaturesPage, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePremiumGiftCode(let value): + try container.encode(Kind.internalLinkTypePremiumGiftCode, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePremiumGiftPurchase(let value): + try container.encode(Kind.internalLinkTypePremiumGiftPurchase, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeProxy(let value): + try container.encode(Kind.internalLinkTypeProxy, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypePublicChat(let value): + try container.encode(Kind.internalLinkTypePublicChat, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeQrCodeAuthentication: + try container.encode(Kind.internalLinkTypeQrCodeAuthentication, forKey: .type) + case .internalLinkTypeRequestManagedBot(let value): + try container.encode(Kind.internalLinkTypeRequestManagedBot, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeRestorePurchases: + try container.encode(Kind.internalLinkTypeRestorePurchases, forKey: .type) + case .internalLinkTypeSavedMessages: + try container.encode(Kind.internalLinkTypeSavedMessages, forKey: .type) + case .internalLinkTypeSearch: + try container.encode(Kind.internalLinkTypeSearch, forKey: .type) + case .internalLinkTypeSettings(let value): + try container.encode(Kind.internalLinkTypeSettings, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeStarPurchase(let value): + try container.encode(Kind.internalLinkTypeStarPurchase, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeStickerSet(let value): + try container.encode(Kind.internalLinkTypeStickerSet, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeStory(let value): + try container.encode(Kind.internalLinkTypeStory, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeStoryAlbum(let value): + try container.encode(Kind.internalLinkTypeStoryAlbum, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeTextCompositionStyle(let value): + try container.encode(Kind.internalLinkTypeTextCompositionStyle, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeTheme(let value): + try container.encode(Kind.internalLinkTypeTheme, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeUnknownDeepLink(let value): + try container.encode(Kind.internalLinkTypeUnknownDeepLink, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeUpgradedGift(let value): + try container.encode(Kind.internalLinkTypeUpgradedGift, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeUserPhoneNumber(let value): + try container.encode(Kind.internalLinkTypeUserPhoneNumber, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeUserToken(let value): + try container.encode(Kind.internalLinkTypeUserToken, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeVideoChat(let value): + try container.encode(Kind.internalLinkTypeVideoChat, forKey: .type) + try value.encode(to: encoder) + case .internalLinkTypeWebApp(let value): + try container.encode(Kind.internalLinkTypeWebApp, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The link is a link to an attachment menu bot to be opened in the specified or a chosen chat. Process given target_chat to open the chat. Then, call searchPublicChat with the given bot username, check that the user is a bot and can be added to attachment menu. Then, use getAttachmentMenuBot to receive information about the bot. If the bot isn't added to attachment menu, then show a disclaimer about Mini Apps being third-party applications, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. If the attachment menu bot can't be used in the opened chat, show an error to the user. If the bot is added to attachment menu and can be used in the chat, then use openWebApp with the given URL +public struct InternalLinkTypeAttachmentMenuBot: Codable, Equatable, Hashable { + + /// Username of the bot + public let botUsername: String + + /// Target chat to be opened + public let targetChat: TargetChat + + /// URL to be passed to openWebApp + public let url: String + + + public init( + botUsername: String, + targetChat: TargetChat, + url: String + ) { + self.botUsername = botUsername + self.targetChat = targetChat + self.url = url + } +} + +/// The link contains an authentication code. Call checkAuthenticationCode with the code if the current authorization state is authorizationStateWaitCode +public struct InternalLinkTypeAuthenticationCode: Codable, Equatable, Hashable { + + /// The authentication code + public let code: String + + + public init(code: String) { + self.code = code + } +} + +/// The link is a link to a background. Call searchBackground with the given background name to process the link. If background is found and the user wants to apply it, then call setDefaultBackground +public struct InternalLinkTypeBackground: Codable, Equatable, Hashable { + + /// Name of the background + public let backgroundName: String + + + public init(backgroundName: String) { + self.backgroundName = backgroundName + } +} + +/// The link is a link to a Telegram bot, which is expected to be added to a channel chat as an administrator. Call searchPublicChat with the given bot username and check that the user is a bot, ask the current user to select a channel chat to add the bot to as an administrator. Then, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator, check that the current user can edit its administrator rights and combine received rights with the requested administrator rights. Then, show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed rights +public struct InternalLinkTypeBotAddToChannel: Codable, Equatable, Hashable { + + /// Expected administrator rights for the bot + public let administratorRights: ChatAdministratorRights + + /// Username of the bot + public let botUsername: String + + + public init( + administratorRights: ChatAdministratorRights, + botUsername: String + ) { + self.administratorRights = administratorRights + self.botUsername = botUsername + } +} + +/// The link is a link to a chat with a Telegram bot. Call searchPublicChat with the given bot username, check that the user is a bot, show START button in the chat with the bot, and then call sendBotStartMessage with the given start parameter after the button is pressed +public struct InternalLinkTypeBotStart: Codable, Equatable, Hashable { + + /// True, if sendBotStartMessage must be called automatically without showing the START button + public let autostart: Bool + + /// Username of the bot + public let botUsername: String + + /// The parameter to be passed to sendBotStartMessage + public let startParameter: String + + + public init( + autostart: Bool, + botUsername: String, + startParameter: String + ) { + self.autostart = autostart + self.botUsername = botUsername + self.startParameter = startParameter + } +} + +/// The link is a link to a Telegram bot, which is expected to be added to a group chat. Call searchPublicChat with the given bot username, check that the user is a bot and can be added to groups, ask the current user to select a basic group or a supergroup chat to add the bot to, taking into account that bots can be added to a public supergroup only by administrators of the supergroup. If administrator rights are provided by the link, call getChatMember to receive the current bot rights in the chat and if the bot already is an administrator, check that the current user can edit its administrator rights, combine received rights with the requested administrator rights, show confirmation box to the user, and call setChatMemberStatus with the chosen chat and confirmed administrator rights. Before call to setChatMemberStatus it may be required to upgrade the chosen basic group chat to a supergroup chat. Then, if start_parameter isn't empty, call sendBotStartMessage with the given start parameter and the chosen chat; otherwise, just send /start message with bot's username added to the chat +public struct InternalLinkTypeBotStartInGroup: Codable, Equatable, Hashable { + + /// Expected administrator rights for the bot; may be null + public let administratorRights: ChatAdministratorRights? + + /// Username of the bot + public let botUsername: String + + /// The parameter to be passed to sendBotStartMessage + public let startParameter: String + + + public init( + administratorRights: ChatAdministratorRights?, + botUsername: String, + startParameter: String + ) { + self.administratorRights = administratorRights + self.botUsername = botUsername + self.startParameter = startParameter + } +} + +/// The link is a link to a business chat. Use getBusinessChatLinkInfo with the provided link name to get information about the link, then open received private chat and replace chat draft with the provided text +public struct InternalLinkTypeBusinessChat: Codable, Equatable, Hashable { + + /// Name of the link + public let linkName: String + + + public init(linkName: String) { + self.linkName = linkName + } +} + +/// The link is a link to the Call tab or page +public struct InternalLinkTypeCallsPage: Codable, Equatable, Hashable { + + /// Section of the page; may be one of "", "all", "missed", "edit", "show-tab", "start-call" + public let section: String + + + public init(section: String) { + self.section = section + } +} + +/// The link is an affiliate program link. Call searchChatAffiliateProgram with the given username and referrer to process the link +public struct InternalLinkTypeChatAffiliateProgram: Codable, Equatable, Hashable { + + /// Referrer to be passed to searchChatAffiliateProgram + public let referrer: String + + /// Username to be passed to searchChatAffiliateProgram + public let username: String + + + public init( + referrer: String, + username: String + ) { + self.referrer = referrer + self.username = username + } +} + +/// The link is a link to boost a Telegram chat. Call getChatBoostLinkInfo with the given URL to process the link. If the chat is found, then call getChatBoostStatus and getAvailableChatBoostSlots to get the current boost status and check whether the chat can be boosted. If the user wants to boost the chat and the chat can be boosted, then call boostChat +public struct InternalLinkTypeChatBoost: Codable, Equatable, Hashable { + + /// URL to be passed to getChatBoostLinkInfo + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// The link is an invite link to a chat folder. Call checkChatFolderInviteLink with the given invite link to process the link. If the link is valid and the user wants to join the chat folder, then call addChatFolderByInviteLink +public struct InternalLinkTypeChatFolderInvite: Codable, Equatable, Hashable { + + /// Internal representation of the invite link + public let inviteLink: String + + + public init(inviteLink: String) { + self.inviteLink = inviteLink + } +} + +/// The link is a chat invite link. Call checkChatInviteLink with the given invite link to process the link. If the link is valid and the user wants to join the chat, then call joinChatByInviteLink +public struct InternalLinkTypeChatInvite: Codable, Equatable, Hashable { + + /// Internal representation of the invite link + public let inviteLink: String + + + public init(inviteLink: String) { + self.inviteLink = inviteLink + } +} + +/// The link is a link to the Contacts tab or page +public struct InternalLinkTypeContactsPage: Codable, Equatable, Hashable { + + /// Section of the page; may be one of "", "search", "sort", "new", "invite", "manage" + public let section: String + + + public init(section: String) { + self.section = section + } +} + +/// The link is a link to a channel direct messages chat by username of the channel. Call searchPublicChat with the given chat username to process the link. If the chat is found and is channel, open the direct messages chat of the channel +public struct InternalLinkTypeDirectMessagesChat: Codable, Equatable, Hashable { + + /// Username of the channel + public let channelUsername: String + + + public init(channelUsername: String) { + self.channelUsername = channelUsername + } +} + +/// The link is a link to a game. Call searchPublicChat with the given bot username, check that the user is a bot, ask the current user to select a chat to send the game, and then call sendMessage with inputMessageGame +public struct InternalLinkTypeGame: Codable, Equatable, Hashable { + + /// Username of the bot that owns the game + public let botUsername: String + + /// Short name of the game + public let gameShortName: String + + + public init( + botUsername: String, + gameShortName: String + ) { + self.botUsername = botUsername + self.gameShortName = gameShortName + } +} + +/// The link is a link to a gift auction. Call getGiftAuctionState with the given auction identifier to process the link +public struct InternalLinkTypeGiftAuction: Codable, Equatable, Hashable { + + /// Unique identifier of the auction + public let auctionId: String + + + public init(auctionId: String) { + self.auctionId = auctionId + } +} + +/// The link is a link to a gift collection. Call searchPublicChat with the given username, then call getReceivedGifts with the received gift owner identifier and the given collection identifier, then show the collection if received +public struct InternalLinkTypeGiftCollection: Codable, Equatable, Hashable { + + /// Gift collection identifier + public let collectionId: Int + + /// Username of the owner of the gift collection + public let giftOwnerUsername: String + + + public init( + collectionId: Int, + giftOwnerUsername: String + ) { + self.collectionId = collectionId + self.giftOwnerUsername = giftOwnerUsername + } +} + +/// The link is a link to a group call that isn't bound to a chat. Use getGroupCallParticipants to get the list of group call participants and show them on the join group call screen. Call joinGroupCall with the given invite_link to join the call +public struct InternalLinkTypeGroupCall: Codable, Equatable, Hashable { + + /// Internal representation of the invite link + public let inviteLink: String + + + public init(inviteLink: String) { + self.inviteLink = inviteLink + } +} + +/// The link must be opened in an Instant View. Call getWebPageInstantView with the given URL to process the link. If Instant View is found, then show it, otherwise, open the fallback URL in an external browser +public struct InternalLinkTypeInstantView: Codable, Equatable, Hashable { + + /// An URL to open if getWebPageInstantView fails + public let fallbackUrl: String + + /// URL to be passed to getWebPageInstantView + public let url: String + + + public init( + fallbackUrl: String, + url: String + ) { + self.fallbackUrl = fallbackUrl + self.url = url + } +} + +/// The link is a link to an invoice. Call getPaymentForm with the given invoice name to process the link +public struct InternalLinkTypeInvoice: Codable, Equatable, Hashable { + + /// Name of the invoice + public let invoiceName: String + + + public init(invoiceName: String) { + self.invoiceName = invoiceName + } +} + +/// The link is a link to a language pack. Call getLanguagePackInfo with the given language pack identifier to process the link. If the language pack is found and the user wants to apply it, then call setOption for the option "language_pack_id" +public struct InternalLinkTypeLanguagePack: Codable, Equatable, Hashable { + + /// Language pack identifier + public let languagePackId: String + + + public init(languagePackId: String) { + self.languagePackId = languagePackId + } +} + +/// The link is a link to a live story. Call searchPublicChat with the given chat username, then getChatActiveStories to get active stories in the chat, then find a live story among active stories of the chat, and then joinLiveStory to join the live story +public struct InternalLinkTypeLiveStory: Codable, Equatable, Hashable { + + /// Username of the poster of the story + public let storyPosterUsername: String + + + public init(storyPosterUsername: String) { + self.storyPosterUsername = storyPosterUsername + } +} + +/// The link is a link to the main Web App of a bot. Call searchPublicChat with the given bot username, check that the user is a bot and has the main Web App. If the bot can be added to attachment menu, then use getAttachmentMenuBot to receive information about the bot, then if the bot isn't added to side menu, show a disclaimer about Mini Apps being third-party applications, ask the user to accept their Terms of service and confirm adding the bot to side and attachment menu, then if the user accepts the terms and confirms adding, use toggleBotIsAddedToAttachmentMenu to add the bot. Then, use getMainWebApp with the given start parameter and mode and open the returned URL as a Web App +public struct InternalLinkTypeMainWebApp: Codable, Equatable, Hashable { + + /// Username of the bot + public let botUsername: String + + /// The mode to be passed to getMainWebApp + public let mode: WebAppOpenMode + + /// Start parameter to be passed to getMainWebApp + public let startParameter: String + + + public init( + botUsername: String, + mode: WebAppOpenMode, + startParameter: String + ) { + self.botUsername = botUsername + self.mode = mode + self.startParameter = startParameter + } +} + +/// The link is a link to a Telegram message or a forum topic. Call getMessageLinkInfo with the given URL to process the link, and then open received forum topic or chat and show the message there +public struct InternalLinkTypeMessage: Codable, Equatable, Hashable { + + /// URL to be passed to getMessageLinkInfo + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// The link contains a message draft text. A share screen needs to be shown to the user, then the chosen chat must be opened and the text is added to the input field +public struct InternalLinkTypeMessageDraft: Codable, Equatable, Hashable { + + /// True, if the first line of the text contains a link. If true, the input field needs to be focused and the text after the link must be selected + public let containsLink: Bool + + /// Message draft text + public let text: FormattedText + + + public init( + containsLink: Bool, + text: FormattedText + ) { + self.containsLink = containsLink + self.text = text + } +} + +/// The link is a link to the My Profile application page +public struct InternalLinkTypeMyProfilePage: Codable, Equatable, Hashable { + + /// Section of the page; may be one of "", "posts", "posts/all-stories", "posts/add-album", "gifts", "archived-posts" + public let section: String + + + public init(section: String) { + self.section = section + } +} + +/// The link is a link to open the story posting interface +public struct InternalLinkTypeNewStory: Codable, Equatable, Hashable { + + /// The type of the content of the story to post; may be null if unspecified + public let contentType: StoryContentType? + + + public init(contentType: StoryContentType?) { + self.contentType = contentType + } +} + +/// The link is an OAuth link. Call getOauthLinkInfo with the given URL to process the link if the link was received from outside of the application; otherwise, ignore it. After getOauthLinkInfo, show the user confirmation dialog and process it with checkOauthRequestMatchCode, acceptOauthRequest or declineOauthRequest +public struct InternalLinkTypeOauth: Codable, Equatable, Hashable { + + /// URL to be passed to getOauthLinkInfo + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// The link contains a request of Telegram passport data. Call getPassportAuthorizationForm with the given parameters to process the link if the link was received from outside of the application; otherwise, ignore it +public struct InternalLinkTypePassportDataRequest: Codable, Equatable, Hashable { + + /// User identifier of the service's bot; the corresponding user may be unknown yet + public let botUserId: Int64 + + /// An HTTP URL to open once the request is finished, canceled, or failed with the parameters tg_passport=success, tg_passport=cancel, or tg_passport=error&error=... respectively. If empty, then onActivityResult method must be used to return response on Android, or the link tgbot{bot_user_id}://passport/success or tgbot{bot_user_id}://passport/cancel must be opened otherwise + public let callbackUrl: String + + /// Unique request identifier provided by the service + public let nonce: String + + /// Service's public key + public let publicKey: String + + /// Telegram Passport element types requested by the service + public let scope: String + + + public init( + botUserId: Int64, + callbackUrl: String, + nonce: String, + publicKey: String, + scope: String + ) { + self.botUserId = botUserId + self.callbackUrl = callbackUrl + self.nonce = nonce + self.publicKey = publicKey + self.scope = scope + } +} + +/// The link can be used to confirm ownership of a phone number to prevent account deletion. Call sendPhoneNumberCode with the given phone number and with phoneNumberCodeTypeConfirmOwnership with the given hash to process the link. If succeeded, call checkPhoneNumberCode to check entered by the user code, or resendPhoneNumberCode to resend it +public struct InternalLinkTypePhoneNumberConfirmation: Codable, Equatable, Hashable { + + /// Hash value from the link + public let hash: String + + /// Phone number value from the link + public let phoneNumber: String + + + public init( + hash: String, + phoneNumber: String + ) { + self.hash = hash + self.phoneNumber = phoneNumber + } +} + +/// The link is a link to the Premium features screen of the application from which the user can subscribe to Telegram Premium. Call getPremiumFeatures with the given referrer to process the link +public struct InternalLinkTypePremiumFeaturesPage: Codable, Equatable, Hashable { + + /// Referrer specified in the link + public let referrer: String + + + public init(referrer: String) { + self.referrer = referrer + } +} + +/// The link is a link with a Telegram Premium gift code. Call checkPremiumGiftCode with the given code to process the link. If the code is valid and the user wants to apply it, then call applyPremiumGiftCode +public struct InternalLinkTypePremiumGiftCode: Codable, Equatable, Hashable { + + /// The Telegram Premium gift code + public let code: String + + + public init(code: String) { + self.code = code + } +} + +/// The link is a link to the screen for gifting Telegram Premium subscriptions to friends via inputInvoiceTelegram with telegramPaymentPurposePremiumGift payments or in-store purchases +public struct InternalLinkTypePremiumGiftPurchase: Codable, Equatable, Hashable { + + /// Referrer specified in the link + public let referrer: String + + + public init(referrer: String) { + self.referrer = referrer + } +} + +/// The link is a link to a proxy. Call addProxy with the given parameters to process the link and add the proxy +public struct InternalLinkTypeProxy: Codable, Equatable, Hashable { + + /// The proxy; may be null if the proxy is unsupported, in which case an alert can be shown to the user + public let proxy: Proxy? + + + public init(proxy: Proxy?) { + self.proxy = proxy + } +} + +/// The link is a link to a chat by its username. Call searchPublicChat with the given chat username to process the link. If the chat is found, open its profile information screen or the chat itself. If draft text isn't empty and the chat is a private chat with a regular user, then put the draft text in the input field +public struct InternalLinkTypePublicChat: Codable, Equatable, Hashable { + + /// Username of the chat + public let chatUsername: String + + /// Draft text for message to send in the chat + public let draftText: String + + /// True, if chat profile information screen must be opened; otherwise, the chat itself must be opened + public let openProfile: Bool + + + public init( + chatUsername: String, + draftText: String, + openProfile: Bool + ) { + self.chatUsername = chatUsername + self.draftText = draftText + self.openProfile = openProfile + } +} + +/// The link is a link to a dialog for creating of a managed bot. Call searchPublicChat with the given manager bot username. If the chat is found, the chat is a chat with a bot and the bot has can_manage_bots == true, then show bot creation confirmation dialog with the given suggested_bot_username and suggested_bot_name. If user agrees, call createBot with via_link == true to create the bot +public struct InternalLinkTypeRequestManagedBot: Codable, Equatable, Hashable { + + /// Username of the bot which will manage the new bot + public let managerBotUsername: String + + /// Suggested name for the bot; may be empty if not specified + public let suggestedBotName: String + + /// Suggested username for the bot; always ends with "bot" case-insensitive + public let suggestedBotUsername: String + + + public init( + managerBotUsername: String, + suggestedBotName: String, + suggestedBotUsername: String + ) { + self.managerBotUsername = managerBotUsername + self.suggestedBotName = suggestedBotName + self.suggestedBotUsername = suggestedBotUsername + } +} + +/// The link is a link to application settings +public struct InternalLinkTypeSettings: Codable, Equatable, Hashable { + + /// Section of the application settings to open; may be null if none + public let section: SettingsSection? + + + public init(section: SettingsSection?) { + self.section = section + } +} + +/// The link is a link to the Telegram Star purchase section of the application +public struct InternalLinkTypeStarPurchase: Codable, Equatable, Hashable { + + /// Purpose of Telegram Star purchase. Arbitrary string specified by the server, for example, "subs" if the Telegram Stars are required to extend channel subscriptions + public let purpose: String + + /// The number of Telegram Stars that must be owned by the user + public let starCount: Int64 + + + public init( + purpose: String, + starCount: Int64 + ) { + self.purpose = purpose + self.starCount = starCount + } +} + +/// The link is a link to a sticker set. Call searchStickerSet with the given sticker set name to process the link and show the sticker set. If the sticker set is found and the user wants to add it, then call changeStickerSet +public struct InternalLinkTypeStickerSet: Codable, Equatable, Hashable { + + /// True, if the sticker set is expected to contain custom emoji + public let expectCustomEmoji: Bool + + /// Name of the sticker set + public let stickerSetName: String + + + public init( + expectCustomEmoji: Bool, + stickerSetName: String + ) { + self.expectCustomEmoji = expectCustomEmoji + self.stickerSetName = stickerSetName + } +} + +/// The link is a link to a story. Call searchPublicChat with the given poster username, then call getStory with the received chat identifier and the given story identifier, then show the story if received +public struct InternalLinkTypeStory: Codable, Equatable, Hashable { + + /// Story identifier + public let storyId: Int + + /// Username of the poster of the story + public let storyPosterUsername: String + + + public init( + storyId: Int, + storyPosterUsername: String + ) { + self.storyId = storyId + self.storyPosterUsername = storyPosterUsername + } +} + +/// The link is a link to an album of stories. Call searchPublicChat with the given username, then call getStoryAlbumStories with the received chat identifier and the given story album identifier, then show the story album if received +public struct InternalLinkTypeStoryAlbum: Codable, Equatable, Hashable { + + /// Story album identifier + public let storyAlbumId: Int + + /// Username of the owner of the story album + public let storyAlbumOwnerUsername: String + + + public init( + storyAlbumId: Int, + storyAlbumOwnerUsername: String + ) { + self.storyAlbumId = storyAlbumId + self.storyAlbumOwnerUsername = storyAlbumOwnerUsername + } +} + +/// The link is a link to a text composition style. Call searchTextCompositionStyle with the given style name to get information about the style. If the style is found and the user wants to add it, then call addTextCompositionStyle +public struct InternalLinkTypeTextCompositionStyle: Codable, Equatable, Hashable { + + /// Name of the style + public let styleName: String + + + public init(styleName: String) { + self.styleName = styleName + } +} + +/// The link is a link to a cloud theme. TDLib has no theme support yet +public struct InternalLinkTypeTheme: Codable, Equatable, Hashable { + + /// Name of the theme + public let themeName: String + + + public init(themeName: String) { + self.themeName = themeName + } +} + +/// The link is an unknown tg: link. Call getDeepLinkInfo to process the link +public struct InternalLinkTypeUnknownDeepLink: Codable, Equatable, Hashable { + + /// Link to be passed to getDeepLinkInfo + public let link: String + + + public init(link: String) { + self.link = link + } +} + +/// The link is a link to an upgraded gift. Call getUpgradedGift with the given name to process the link +public struct InternalLinkTypeUpgradedGift: Codable, Equatable, Hashable { + + /// Name of the unique gift + public let name: String + + + public init(name: String) { + self.name = name + } +} + +/// The link is a link to a user by its phone number. Call searchUserByPhoneNumber with the given phone number to process the link. If the user is found, then call createPrivateChat and open user's profile information screen or the chat itself. If draft text isn't empty, then put the draft text in the input field +public struct InternalLinkTypeUserPhoneNumber: Codable, Equatable, Hashable { + + /// Draft text for message to send in the chat + public let draftText: String + + /// True, if user's profile information screen must be opened; otherwise, the chat itself must be opened + public let openProfile: Bool + + /// Phone number of the user + public let phoneNumber: String + + + public init( + draftText: String, + openProfile: Bool, + phoneNumber: String + ) { + self.draftText = draftText + self.openProfile = openProfile + self.phoneNumber = phoneNumber + } +} + +/// The link is a link to a user by a temporary token. Call searchUserByToken with the given token to process the link. If the user is found, then call createPrivateChat and open the chat +public struct InternalLinkTypeUserToken: Codable, Equatable, Hashable { + + /// The token + public let token: String + + + public init(token: String) { + self.token = token + } +} + +/// The link is a link to a video chat. Call searchPublicChat with the given chat username, and then joinVideoChat with the given invite hash to process the link +public struct InternalLinkTypeVideoChat: Codable, Equatable, Hashable { + + /// Username of the chat with the video chat + public let chatUsername: String + + /// If non-empty, invite hash to be used to join the video chat without being muted by administrators + public let inviteHash: String + + /// True, if the video chat is expected to be a live stream in a channel or a broadcast group + public let isLiveStream: Bool + + + public init( + chatUsername: String, + inviteHash: String, + isLiveStream: Bool + ) { + self.chatUsername = chatUsername + self.inviteHash = inviteHash + self.isLiveStream = isLiveStream + } +} + +/// The link is a link to a Web App. Call searchPublicChat with the given bot username, check that the user is a bot. If the bot is restricted for the current user, then show an error message. Otherwise, call searchWebApp with the received bot and the given web_app_short_name. Process received foundWebApp by showing a confirmation dialog if needed. If the bot can be added to attachment or side menu, but isn't added yet, then show a disclaimer about Mini Apps being third-party applications instead of the dialog and ask the user to accept their Terms of service. If the user accept the terms and confirms adding, then use toggleBotIsAddedToAttachmentMenu to add the bot. Then, call getWebAppLinkUrl and open the returned URL as a Web App +public struct InternalLinkTypeWebApp: Codable, Equatable, Hashable { + + /// Username of the bot that owns the Web App + public let botUsername: String + + /// The mode in which the Web App must be opened + public let mode: WebAppOpenMode + + /// Start parameter to be passed to getWebAppLinkUrl + public let startParameter: String + + /// Short name of the Web App + public let webAppShortName: String + + + public init( + botUsername: String, + mode: WebAppOpenMode, + startParameter: String, + webAppShortName: String + ) { + self.botUsername = botUsername + self.mode = mode + self.startParameter = startParameter + self.webAppShortName = webAppShortName + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InviteLinkChatType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InviteLinkChatType.swift new file mode 100644 index 0000000000..56a8f30aca --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/InviteLinkChatType.swift @@ -0,0 +1,65 @@ +// +// InviteLinkChatType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of chat to which points an invite link +public indirect enum InviteLinkChatType: Codable, Equatable, Hashable { + + /// The link is an invite link for a basic group + case inviteLinkChatTypeBasicGroup + + /// The link is an invite link for a supergroup + case inviteLinkChatTypeSupergroup + + /// The link is an invite link for a channel + case inviteLinkChatTypeChannel + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case inviteLinkChatTypeBasicGroup + case inviteLinkChatTypeSupergroup + case inviteLinkChatTypeChannel + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .inviteLinkChatTypeBasicGroup: + self = .inviteLinkChatTypeBasicGroup + case .inviteLinkChatTypeSupergroup: + self = .inviteLinkChatTypeSupergroup + case .inviteLinkChatTypeChannel: + self = .inviteLinkChatTypeChannel + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .inviteLinkChatTypeBasicGroup: + try container.encode(Kind.inviteLinkChatTypeBasicGroup, forKey: .type) + case .inviteLinkChatTypeSupergroup: + try container.encode(Kind.inviteLinkChatTypeSupergroup, forKey: .type) + case .inviteLinkChatTypeChannel: + try container.encode(Kind.inviteLinkChatTypeChannel, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Invoice.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Invoice.swift new file mode 100644 index 0000000000..c37b442a31 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Invoice.swift @@ -0,0 +1,96 @@ +// +// Invoice.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Product invoice +public struct Invoice: Codable, Equatable, Hashable { + + /// ISO 4217 currency code + public let currency: String + + /// True, if the total price depends on the shipping method + public let isFlexible: Bool + + /// True, if the payment is a test payment + public let isTest: Bool + + /// The maximum allowed amount of tip in the smallest units of the currency + public let maxTipAmount: Int64 + + /// True, if the user's email address is needed for payment + public let needEmailAddress: Bool + + /// True, if the user's name is needed for payment + public let needName: Bool + + /// True, if the user's phone number is needed for payment + public let needPhoneNumber: Bool + + /// True, if the user's shipping address is needed for payment + public let needShippingAddress: Bool + + /// A list of objects used to calculate the total price of the product + public let priceParts: [LabeledPricePart] + + /// An HTTP URL with terms of service for recurring payments. If non-empty, the invoice payment will result in recurring payments and the user must accept the terms of service before allowed to pay + public let recurringPaymentTermsOfServiceUrl: String + + /// True, if the user's email address will be sent to the provider + public let sendEmailAddressToProvider: Bool + + /// True, if the user's phone number will be sent to the provider + public let sendPhoneNumberToProvider: Bool + + /// The number of seconds between consecutive Telegram Star debiting for subscription invoices; 0 if the invoice doesn't create subscription + public let subscriptionPeriod: Int + + /// Suggested amounts of tip in the smallest units of the currency + public let suggestedTipAmounts: [Int64] + + /// An HTTP URL with terms of service for non-recurring payments. If non-empty, then the user must accept the terms of service before allowed to pay + public let termsOfServiceUrl: String + + + public init( + currency: String, + isFlexible: Bool, + isTest: Bool, + maxTipAmount: Int64, + needEmailAddress: Bool, + needName: Bool, + needPhoneNumber: Bool, + needShippingAddress: Bool, + priceParts: [LabeledPricePart], + recurringPaymentTermsOfServiceUrl: String, + sendEmailAddressToProvider: Bool, + sendPhoneNumberToProvider: Bool, + subscriptionPeriod: Int, + suggestedTipAmounts: [Int64], + termsOfServiceUrl: String + ) { + self.currency = currency + self.isFlexible = isFlexible + self.isTest = isTest + self.maxTipAmount = maxTipAmount + self.needEmailAddress = needEmailAddress + self.needName = needName + self.needPhoneNumber = needPhoneNumber + self.needShippingAddress = needShippingAddress + self.priceParts = priceParts + self.recurringPaymentTermsOfServiceUrl = recurringPaymentTermsOfServiceUrl + self.sendEmailAddressToProvider = sendEmailAddressToProvider + self.sendPhoneNumberToProvider = sendPhoneNumberToProvider + self.subscriptionPeriod = subscriptionPeriod + self.suggestedTipAmounts = suggestedTipAmounts + self.termsOfServiceUrl = termsOfServiceUrl + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButton.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButton.swift new file mode 100644 index 0000000000..33124a6473 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButton.swift @@ -0,0 +1,41 @@ +// +// KeyboardButton.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a single button in a bot keyboard +public struct KeyboardButton: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji that must be shown on the button; 0 if none + public let iconCustomEmojiId: TdInt64 + + /// Style of the button + public let style: ButtonStyle + + /// Text of the button + public let text: String + + /// Type of the button + public let type: KeyboardButtonType + + + public init( + iconCustomEmojiId: TdInt64, + style: ButtonStyle, + text: String, + type: KeyboardButtonType + ) { + self.iconCustomEmojiId = iconCustomEmojiId + self.style = style + self.text = text + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButtonType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButtonType.swift new file mode 100644 index 0000000000..58f2661c72 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/KeyboardButtonType.swift @@ -0,0 +1,298 @@ +// +// KeyboardButtonType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a keyboard button type +public indirect enum KeyboardButtonType: Codable, Equatable, Hashable { + + /// A simple button, with text that must be sent when the button is pressed + case keyboardButtonTypeText + + /// A button that sends the user's phone number when pressed; available only in private chats + case keyboardButtonTypeRequestPhoneNumber + + /// A button that sends the user's location when pressed; available only in private chats + case keyboardButtonTypeRequestLocation + + /// A button that allows the user to create and send a poll when pressed; available only in private chats + case keyboardButtonTypeRequestPoll(KeyboardButtonTypeRequestPoll) + + /// A button that requests users to be shared by the current user; available only in private chats. Use the method shareUsersWithBot to complete the request + case keyboardButtonTypeRequestUsers(KeyboardButtonTypeRequestUsers) + + /// A button that requests a chat to be shared by the current user; available only in private chats. Use the method shareChatWithBot to complete the request + case keyboardButtonTypeRequestChat(KeyboardButtonTypeRequestChat) + + /// A button that requests creation of a managed bot by the current user; available only in private chats. Use the method createBot to complete the request + case keyboardButtonTypeRequestManagedBot(KeyboardButtonTypeRequestManagedBot) + + /// A button that opens a Web App by calling getWebAppUrl + case keyboardButtonTypeWebApp(KeyboardButtonTypeWebApp) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case keyboardButtonTypeText + case keyboardButtonTypeRequestPhoneNumber + case keyboardButtonTypeRequestLocation + case keyboardButtonTypeRequestPoll + case keyboardButtonTypeRequestUsers + case keyboardButtonTypeRequestChat + case keyboardButtonTypeRequestManagedBot + case keyboardButtonTypeWebApp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .keyboardButtonTypeText: + self = .keyboardButtonTypeText + case .keyboardButtonTypeRequestPhoneNumber: + self = .keyboardButtonTypeRequestPhoneNumber + case .keyboardButtonTypeRequestLocation: + self = .keyboardButtonTypeRequestLocation + case .keyboardButtonTypeRequestPoll: + let value = try KeyboardButtonTypeRequestPoll(from: decoder) + self = .keyboardButtonTypeRequestPoll(value) + case .keyboardButtonTypeRequestUsers: + let value = try KeyboardButtonTypeRequestUsers(from: decoder) + self = .keyboardButtonTypeRequestUsers(value) + case .keyboardButtonTypeRequestChat: + let value = try KeyboardButtonTypeRequestChat(from: decoder) + self = .keyboardButtonTypeRequestChat(value) + case .keyboardButtonTypeRequestManagedBot: + let value = try KeyboardButtonTypeRequestManagedBot(from: decoder) + self = .keyboardButtonTypeRequestManagedBot(value) + case .keyboardButtonTypeWebApp: + let value = try KeyboardButtonTypeWebApp(from: decoder) + self = .keyboardButtonTypeWebApp(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .keyboardButtonTypeText: + try container.encode(Kind.keyboardButtonTypeText, forKey: .type) + case .keyboardButtonTypeRequestPhoneNumber: + try container.encode(Kind.keyboardButtonTypeRequestPhoneNumber, forKey: .type) + case .keyboardButtonTypeRequestLocation: + try container.encode(Kind.keyboardButtonTypeRequestLocation, forKey: .type) + case .keyboardButtonTypeRequestPoll(let value): + try container.encode(Kind.keyboardButtonTypeRequestPoll, forKey: .type) + try value.encode(to: encoder) + case .keyboardButtonTypeRequestUsers(let value): + try container.encode(Kind.keyboardButtonTypeRequestUsers, forKey: .type) + try value.encode(to: encoder) + case .keyboardButtonTypeRequestChat(let value): + try container.encode(Kind.keyboardButtonTypeRequestChat, forKey: .type) + try value.encode(to: encoder) + case .keyboardButtonTypeRequestManagedBot(let value): + try container.encode(Kind.keyboardButtonTypeRequestManagedBot, forKey: .type) + try value.encode(to: encoder) + case .keyboardButtonTypeWebApp(let value): + try container.encode(Kind.keyboardButtonTypeWebApp, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A button that allows the user to create and send a poll when pressed; available only in private chats +public struct KeyboardButtonTypeRequestPoll: Codable, Equatable, Hashable { + + /// If true, only polls in quiz mode must be allowed to create + public let forceQuiz: Bool + + /// If true, only regular polls must be allowed to create + public let forceRegular: Bool + + + public init( + forceQuiz: Bool, + forceRegular: Bool + ) { + self.forceQuiz = forceQuiz + self.forceRegular = forceRegular + } +} + +/// A button that requests users to be shared by the current user; available only in private chats. Use the method shareUsersWithBot to complete the request +public struct KeyboardButtonTypeRequestUsers: Codable, Equatable, Hashable, Identifiable { + + /// Unique button identifier + public let id: Int + + /// The maximum number of users to share + public let maxQuantity: Int + + /// Pass true to request name of the users; bots only + public let requestName: Bool + + /// Pass true to request photo of the users; bots only + public let requestPhoto: Bool + + /// Pass true to request username of the users; bots only + public let requestUsername: Bool + + /// True, if the shared users must or must not be bots + public let restrictUserIsBot: Bool + + /// True, if the shared users must or must not be Telegram Premium users + public let restrictUserIsPremium: Bool + + /// True, if the shared users must be bots; otherwise, the shared users must not be bots. Ignored if restrict_user_is_bot is false + public let userIsBot: Bool + + /// True, if the shared users must be Telegram Premium users; otherwise, the shared users must not be Telegram Premium users. Ignored if restrict_user_is_premium is false + public let userIsPremium: Bool + + + public init( + id: Int, + maxQuantity: Int, + requestName: Bool, + requestPhoto: Bool, + requestUsername: Bool, + restrictUserIsBot: Bool, + restrictUserIsPremium: Bool, + userIsBot: Bool, + userIsPremium: Bool + ) { + self.id = id + self.maxQuantity = maxQuantity + self.requestName = requestName + self.requestPhoto = requestPhoto + self.requestUsername = requestUsername + self.restrictUserIsBot = restrictUserIsBot + self.restrictUserIsPremium = restrictUserIsPremium + self.userIsBot = userIsBot + self.userIsPremium = userIsPremium + } +} + +/// A button that requests a chat to be shared by the current user; available only in private chats. Use the method shareChatWithBot to complete the request +public struct KeyboardButtonTypeRequestChat: Codable, Equatable, Hashable, Identifiable { + + /// Expected bot administrator rights in the chat; may be null if they aren't restricted + public let botAdministratorRights: ChatAdministratorRights? + + /// True, if the bot must be a member of the chat; for basic group and supergroup chats only + public let botIsMember: Bool + + /// True, if the chat must have a username; otherwise, the chat must not have a username. Ignored if restrict_chat_has_username is false + public let chatHasUsername: Bool + + /// True, if the chat must be a channel; otherwise, a basic group or a supergroup chat is shared + public let chatIsChannel: Bool + + /// True, if the chat must be created by the current user + public let chatIsCreated: Bool + + /// True, if the chat must be a forum supergroup; otherwise, the chat must not be a forum supergroup. Ignored if restrict_chat_is_forum is false + public let chatIsForum: Bool + + /// Unique button identifier + public let id: Int + + /// Pass true to request photo of the chat; bots only + public let requestPhoto: Bool + + /// Pass true to request title of the chat; bots only + public let requestTitle: Bool + + /// Pass true to request username of the chat; bots only + public let requestUsername: Bool + + /// True, if the chat must or must not have a username + public let restrictChatHasUsername: Bool + + /// True, if the chat must or must not be a forum supergroup + public let restrictChatIsForum: Bool + + /// Expected user administrator rights in the chat; may be null if they aren't restricted + public let userAdministratorRights: ChatAdministratorRights? + + + public init( + botAdministratorRights: ChatAdministratorRights?, + botIsMember: Bool, + chatHasUsername: Bool, + chatIsChannel: Bool, + chatIsCreated: Bool, + chatIsForum: Bool, + id: Int, + requestPhoto: Bool, + requestTitle: Bool, + requestUsername: Bool, + restrictChatHasUsername: Bool, + restrictChatIsForum: Bool, + userAdministratorRights: ChatAdministratorRights? + ) { + self.botAdministratorRights = botAdministratorRights + self.botIsMember = botIsMember + self.chatHasUsername = chatHasUsername + self.chatIsChannel = chatIsChannel + self.chatIsCreated = chatIsCreated + self.chatIsForum = chatIsForum + self.id = id + self.requestPhoto = requestPhoto + self.requestTitle = requestTitle + self.requestUsername = requestUsername + self.restrictChatHasUsername = restrictChatHasUsername + self.restrictChatIsForum = restrictChatIsForum + self.userAdministratorRights = userAdministratorRights + } +} + +/// A button that requests creation of a managed bot by the current user; available only in private chats. Use the method createBot to complete the request +public struct KeyboardButtonTypeRequestManagedBot: Codable, Equatable, Hashable, Identifiable { + + /// Unique button identifier + public let id: Int + + /// Suggested name for the bot; may be empty if not specified + public let suggestedName: String + + /// Suggested username for the bot; may be empty if not specified + public let suggestedUsername: String + + + public init( + id: Int, + suggestedName: String, + suggestedUsername: String + ) { + self.id = id + self.suggestedName = suggestedName + self.suggestedUsername = suggestedUsername + } +} + +/// A button that opens a Web App by calling getWebAppUrl +public struct KeyboardButtonTypeWebApp: Codable, Equatable, Hashable { + + /// An HTTP URL to pass to getWebAppUrl + public let url: String + + + public init(url: String) { + self.url = url + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LabeledPricePart.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LabeledPricePart.swift new file mode 100644 index 0000000000..00a0464144 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LabeledPricePart.swift @@ -0,0 +1,31 @@ +// +// LabeledPricePart.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Portion of the price of a product (e.g., "delivery cost", "tax amount") +public struct LabeledPricePart: Codable, Equatable, Hashable { + + /// Currency amount in the smallest units of the currency + public let amount: Int64 + + /// Label for this portion of the product price + public let label: String + + + public init( + amount: Int64, + label: String + ) { + self.amount = amount + self.label = label + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreview.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreview.swift new file mode 100644 index 0000000000..60f8377424 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreview.swift @@ -0,0 +1,85 @@ +// +// LinkPreview.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a link preview +public struct LinkPreview: Codable, Equatable, Hashable { + + /// Author of the content + public let author: String + + public let description: FormattedText + + /// URL to display + public let displayUrl: String + + /// True, if size of media in the preview can be changed + public let hasLargeMedia: Bool + + /// Version of instant view (currently, can be 1 or 2) for the web page; 0 if none + public let instantViewVersion: Int + + /// True, if the link preview must be shown above message text; otherwise, the link preview must be shown below the message text + public let showAboveText: Bool + + /// True, if large media preview must be shown; otherwise, the media preview must be shown small and only the first frame must be shown for videos + public let showLargeMedia: Bool + + /// True, if media must be shown above link preview description; otherwise, the media must be shown below the description + public let showMediaAboveDescription: Bool + + /// Short name of the site (e.g., Google Docs, App Store) + public let siteName: String + + /// True, if there is no need to show an ordinary open URL confirmation, when opening the URL from the preview, because the URL is shown in the message text in clear + public let skipConfirmation: Bool + + /// Title of the content + public let title: String + + /// Type of the link preview + public let type: LinkPreviewType + + /// Original URL of the link + public let url: String + + + public init( + author: String, + description: FormattedText, + displayUrl: String, + hasLargeMedia: Bool, + instantViewVersion: Int, + showAboveText: Bool, + showLargeMedia: Bool, + showMediaAboveDescription: Bool, + siteName: String, + skipConfirmation: Bool, + title: String, + type: LinkPreviewType, + url: String + ) { + self.author = author + self.description = description + self.displayUrl = displayUrl + self.hasLargeMedia = hasLargeMedia + self.instantViewVersion = instantViewVersion + self.showAboveText = showAboveText + self.showLargeMedia = showLargeMedia + self.showMediaAboveDescription = showMediaAboveDescription + self.siteName = siteName + self.skipConfirmation = skipConfirmation + self.title = title + self.type = type + self.url = url + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewAlbumMedia.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewAlbumMedia.swift new file mode 100644 index 0000000000..cfa860072e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewAlbumMedia.swift @@ -0,0 +1,85 @@ +// +// LinkPreviewAlbumMedia.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a media from a link preview album +public indirect enum LinkPreviewAlbumMedia: Codable, Equatable, Hashable { + + /// The media is a photo + case linkPreviewAlbumMediaPhoto(LinkPreviewAlbumMediaPhoto) + + /// The media is a video + case linkPreviewAlbumMediaVideo(LinkPreviewAlbumMediaVideo) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case linkPreviewAlbumMediaPhoto + case linkPreviewAlbumMediaVideo + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .linkPreviewAlbumMediaPhoto: + let value = try LinkPreviewAlbumMediaPhoto(from: decoder) + self = .linkPreviewAlbumMediaPhoto(value) + case .linkPreviewAlbumMediaVideo: + let value = try LinkPreviewAlbumMediaVideo(from: decoder) + self = .linkPreviewAlbumMediaVideo(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .linkPreviewAlbumMediaPhoto(let value): + try container.encode(Kind.linkPreviewAlbumMediaPhoto, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewAlbumMediaVideo(let value): + try container.encode(Kind.linkPreviewAlbumMediaVideo, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The media is a photo +public struct LinkPreviewAlbumMediaPhoto: Codable, Equatable, Hashable { + + /// Photo description + public let photo: Photo + + + public init(photo: Photo) { + self.photo = photo + } +} + +/// The media is a video +public struct LinkPreviewAlbumMediaVideo: Codable, Equatable, Hashable { + + /// Video description + public let video: Video + + + public init(video: Video) { + self.video = video + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewOptions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewOptions.swift new file mode 100644 index 0000000000..4d46749136 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewOptions.swift @@ -0,0 +1,46 @@ +// +// LinkPreviewOptions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Options to be used for generation of a link preview +public struct LinkPreviewOptions: Codable, Equatable, Hashable { + + /// True, if shown media preview must be large; ignored in secret chats or if the URL isn't explicitly specified + public let forceLargeMedia: Bool + + /// True, if shown media preview must be small; ignored in secret chats or if the URL isn't explicitly specified + public let forceSmallMedia: Bool + + /// True, if link preview must be disabled + public let isDisabled: Bool + + /// True, if link preview must be shown above message text; otherwise, the link preview will be shown below the message text; ignored in secret chats + public let showAboveText: Bool + + /// URL to use for link preview. If empty, then the first URL found in the message text will be used + public let url: String + + + public init( + forceLargeMedia: Bool, + forceSmallMedia: Bool, + isDisabled: Bool, + showAboveText: Bool, + url: String + ) { + self.forceLargeMedia = forceLargeMedia + self.forceSmallMedia = forceSmallMedia + self.isDisabled = isDisabled + self.showAboveText = showAboveText + self.url = url + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewType.swift new file mode 100644 index 0000000000..366ac7342b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LinkPreviewType.swift @@ -0,0 +1,1030 @@ +// +// LinkPreviewType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of link preview +public indirect enum LinkPreviewType: Codable, Equatable, Hashable { + + /// The link is a link to a media album consisting of photos and videos + case linkPreviewTypeAlbum(LinkPreviewTypeAlbum) + + /// The link is a link to an animation + case linkPreviewTypeAnimation(LinkPreviewTypeAnimation) + + /// The link is a link to an app at App Store or Google Play + case linkPreviewTypeApp(LinkPreviewTypeApp) + + /// The link is a link to a web site + case linkPreviewTypeArticle(LinkPreviewTypeArticle) + + /// The link is a link to an audio + case linkPreviewTypeAudio(LinkPreviewTypeAudio) + + /// The link is a link to a background. Link preview title and description are available only for filled backgrounds + case linkPreviewTypeBackground(LinkPreviewTypeBackground) + + /// The link is a link to boost a channel chat + case linkPreviewTypeChannelBoost(LinkPreviewTypeChannelBoost) + + /// The link is a link to a chat + case linkPreviewTypeChat(LinkPreviewTypeChat) + + /// The link is a link to a direct messages chat of a channel + case linkPreviewTypeDirectMessagesChat(LinkPreviewTypeDirectMessagesChat) + + /// The link is a link to a general file + case linkPreviewTypeDocument(LinkPreviewTypeDocument) + + /// The link is a link to an animation player + case linkPreviewTypeEmbeddedAnimationPlayer(LinkPreviewTypeEmbeddedAnimationPlayer) + + /// The link is a link to an audio player + case linkPreviewTypeEmbeddedAudioPlayer(LinkPreviewTypeEmbeddedAudioPlayer) + + /// The link is a link to a video player + case linkPreviewTypeEmbeddedVideoPlayer(LinkPreviewTypeEmbeddedVideoPlayer) + + /// The link is a link to an audio file + case linkPreviewTypeExternalAudio(LinkPreviewTypeExternalAudio) + + /// The link is a link to a video file + case linkPreviewTypeExternalVideo(LinkPreviewTypeExternalVideo) + + /// The link is a link to a gift auction + case linkPreviewTypeGiftAuction(LinkPreviewTypeGiftAuction) + + /// The link is a link to a gift collection + case linkPreviewTypeGiftCollection(LinkPreviewTypeGiftCollection) + + /// The link is a link to a group call that isn't bound to a chat + case linkPreviewTypeGroupCall + + /// The link is a link to an invoice + case linkPreviewTypeInvoice + + /// The link is a link to a live story group call + case linkPreviewTypeLiveStory(LinkPreviewTypeLiveStory) + + /// The link is a link to a text or a poll Telegram message + case linkPreviewTypeMessage + + /// The link is a link to a photo + case linkPreviewTypePhoto(LinkPreviewTypePhoto) + + /// The link is a link to a Telegram Premium gift code + case linkPreviewTypePremiumGiftCode + + /// The link is a link to a dialog for creating of a managed bot + case linkPreviewTypeRequestManagedBot + + /// The link is a link to a shareable chat folder + case linkPreviewTypeShareableChatFolder + + /// The link is a link to a sticker + case linkPreviewTypeSticker(LinkPreviewTypeSticker) + + /// The link is a link to a sticker set + case linkPreviewTypeStickerSet(LinkPreviewTypeStickerSet) + + /// The link is a link to a story. Link preview description is unavailable + case linkPreviewTypeStory(LinkPreviewTypeStory) + + /// The link is a link to an album of stories + case linkPreviewTypeStoryAlbum(LinkPreviewTypeStoryAlbum) + + /// The link is a link to boost a supergroup chat + case linkPreviewTypeSupergroupBoost(LinkPreviewTypeSupergroupBoost) + + /// The link is a link to a text composition style + case linkPreviewTypeTextCompositionStyle(LinkPreviewTypeTextCompositionStyle) + + /// The link is a link to a cloud theme. TDLib has no theme support yet + case linkPreviewTypeTheme(LinkPreviewTypeTheme) + + /// The link preview type is unsupported yet + case linkPreviewTypeUnsupported + + /// The link is a link to an upgraded gift + case linkPreviewTypeUpgradedGift(LinkPreviewTypeUpgradedGift) + + /// The link is a link to a user + case linkPreviewTypeUser(LinkPreviewTypeUser) + + /// The link is a link to a video + case linkPreviewTypeVideo(LinkPreviewTypeVideo) + + /// The link is a link to a video chat + case linkPreviewTypeVideoChat(LinkPreviewTypeVideoChat) + + /// The link is a link to a video note message + case linkPreviewTypeVideoNote(LinkPreviewTypeVideoNote) + + /// The link is a link to a voice note message + case linkPreviewTypeVoiceNote(LinkPreviewTypeVoiceNote) + + /// The link is a link to a Web App + case linkPreviewTypeWebApp(LinkPreviewTypeWebApp) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case linkPreviewTypeAlbum + case linkPreviewTypeAnimation + case linkPreviewTypeApp + case linkPreviewTypeArticle + case linkPreviewTypeAudio + case linkPreviewTypeBackground + case linkPreviewTypeChannelBoost + case linkPreviewTypeChat + case linkPreviewTypeDirectMessagesChat + case linkPreviewTypeDocument + case linkPreviewTypeEmbeddedAnimationPlayer + case linkPreviewTypeEmbeddedAudioPlayer + case linkPreviewTypeEmbeddedVideoPlayer + case linkPreviewTypeExternalAudio + case linkPreviewTypeExternalVideo + case linkPreviewTypeGiftAuction + case linkPreviewTypeGiftCollection + case linkPreviewTypeGroupCall + case linkPreviewTypeInvoice + case linkPreviewTypeLiveStory + case linkPreviewTypeMessage + case linkPreviewTypePhoto + case linkPreviewTypePremiumGiftCode + case linkPreviewTypeRequestManagedBot + case linkPreviewTypeShareableChatFolder + case linkPreviewTypeSticker + case linkPreviewTypeStickerSet + case linkPreviewTypeStory + case linkPreviewTypeStoryAlbum + case linkPreviewTypeSupergroupBoost + case linkPreviewTypeTextCompositionStyle + case linkPreviewTypeTheme + case linkPreviewTypeUnsupported + case linkPreviewTypeUpgradedGift + case linkPreviewTypeUser + case linkPreviewTypeVideo + case linkPreviewTypeVideoChat + case linkPreviewTypeVideoNote + case linkPreviewTypeVoiceNote + case linkPreviewTypeWebApp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .linkPreviewTypeAlbum: + let value = try LinkPreviewTypeAlbum(from: decoder) + self = .linkPreviewTypeAlbum(value) + case .linkPreviewTypeAnimation: + let value = try LinkPreviewTypeAnimation(from: decoder) + self = .linkPreviewTypeAnimation(value) + case .linkPreviewTypeApp: + let value = try LinkPreviewTypeApp(from: decoder) + self = .linkPreviewTypeApp(value) + case .linkPreviewTypeArticle: + let value = try LinkPreviewTypeArticle(from: decoder) + self = .linkPreviewTypeArticle(value) + case .linkPreviewTypeAudio: + let value = try LinkPreviewTypeAudio(from: decoder) + self = .linkPreviewTypeAudio(value) + case .linkPreviewTypeBackground: + let value = try LinkPreviewTypeBackground(from: decoder) + self = .linkPreviewTypeBackground(value) + case .linkPreviewTypeChannelBoost: + let value = try LinkPreviewTypeChannelBoost(from: decoder) + self = .linkPreviewTypeChannelBoost(value) + case .linkPreviewTypeChat: + let value = try LinkPreviewTypeChat(from: decoder) + self = .linkPreviewTypeChat(value) + case .linkPreviewTypeDirectMessagesChat: + let value = try LinkPreviewTypeDirectMessagesChat(from: decoder) + self = .linkPreviewTypeDirectMessagesChat(value) + case .linkPreviewTypeDocument: + let value = try LinkPreviewTypeDocument(from: decoder) + self = .linkPreviewTypeDocument(value) + case .linkPreviewTypeEmbeddedAnimationPlayer: + let value = try LinkPreviewTypeEmbeddedAnimationPlayer(from: decoder) + self = .linkPreviewTypeEmbeddedAnimationPlayer(value) + case .linkPreviewTypeEmbeddedAudioPlayer: + let value = try LinkPreviewTypeEmbeddedAudioPlayer(from: decoder) + self = .linkPreviewTypeEmbeddedAudioPlayer(value) + case .linkPreviewTypeEmbeddedVideoPlayer: + let value = try LinkPreviewTypeEmbeddedVideoPlayer(from: decoder) + self = .linkPreviewTypeEmbeddedVideoPlayer(value) + case .linkPreviewTypeExternalAudio: + let value = try LinkPreviewTypeExternalAudio(from: decoder) + self = .linkPreviewTypeExternalAudio(value) + case .linkPreviewTypeExternalVideo: + let value = try LinkPreviewTypeExternalVideo(from: decoder) + self = .linkPreviewTypeExternalVideo(value) + case .linkPreviewTypeGiftAuction: + let value = try LinkPreviewTypeGiftAuction(from: decoder) + self = .linkPreviewTypeGiftAuction(value) + case .linkPreviewTypeGiftCollection: + let value = try LinkPreviewTypeGiftCollection(from: decoder) + self = .linkPreviewTypeGiftCollection(value) + case .linkPreviewTypeGroupCall: + self = .linkPreviewTypeGroupCall + case .linkPreviewTypeInvoice: + self = .linkPreviewTypeInvoice + case .linkPreviewTypeLiveStory: + let value = try LinkPreviewTypeLiveStory(from: decoder) + self = .linkPreviewTypeLiveStory(value) + case .linkPreviewTypeMessage: + self = .linkPreviewTypeMessage + case .linkPreviewTypePhoto: + let value = try LinkPreviewTypePhoto(from: decoder) + self = .linkPreviewTypePhoto(value) + case .linkPreviewTypePremiumGiftCode: + self = .linkPreviewTypePremiumGiftCode + case .linkPreviewTypeRequestManagedBot: + self = .linkPreviewTypeRequestManagedBot + case .linkPreviewTypeShareableChatFolder: + self = .linkPreviewTypeShareableChatFolder + case .linkPreviewTypeSticker: + let value = try LinkPreviewTypeSticker(from: decoder) + self = .linkPreviewTypeSticker(value) + case .linkPreviewTypeStickerSet: + let value = try LinkPreviewTypeStickerSet(from: decoder) + self = .linkPreviewTypeStickerSet(value) + case .linkPreviewTypeStory: + let value = try LinkPreviewTypeStory(from: decoder) + self = .linkPreviewTypeStory(value) + case .linkPreviewTypeStoryAlbum: + let value = try LinkPreviewTypeStoryAlbum(from: decoder) + self = .linkPreviewTypeStoryAlbum(value) + case .linkPreviewTypeSupergroupBoost: + let value = try LinkPreviewTypeSupergroupBoost(from: decoder) + self = .linkPreviewTypeSupergroupBoost(value) + case .linkPreviewTypeTextCompositionStyle: + let value = try LinkPreviewTypeTextCompositionStyle(from: decoder) + self = .linkPreviewTypeTextCompositionStyle(value) + case .linkPreviewTypeTheme: + let value = try LinkPreviewTypeTheme(from: decoder) + self = .linkPreviewTypeTheme(value) + case .linkPreviewTypeUnsupported: + self = .linkPreviewTypeUnsupported + case .linkPreviewTypeUpgradedGift: + let value = try LinkPreviewTypeUpgradedGift(from: decoder) + self = .linkPreviewTypeUpgradedGift(value) + case .linkPreviewTypeUser: + let value = try LinkPreviewTypeUser(from: decoder) + self = .linkPreviewTypeUser(value) + case .linkPreviewTypeVideo: + let value = try LinkPreviewTypeVideo(from: decoder) + self = .linkPreviewTypeVideo(value) + case .linkPreviewTypeVideoChat: + let value = try LinkPreviewTypeVideoChat(from: decoder) + self = .linkPreviewTypeVideoChat(value) + case .linkPreviewTypeVideoNote: + let value = try LinkPreviewTypeVideoNote(from: decoder) + self = .linkPreviewTypeVideoNote(value) + case .linkPreviewTypeVoiceNote: + let value = try LinkPreviewTypeVoiceNote(from: decoder) + self = .linkPreviewTypeVoiceNote(value) + case .linkPreviewTypeWebApp: + let value = try LinkPreviewTypeWebApp(from: decoder) + self = .linkPreviewTypeWebApp(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .linkPreviewTypeAlbum(let value): + try container.encode(Kind.linkPreviewTypeAlbum, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeAnimation(let value): + try container.encode(Kind.linkPreviewTypeAnimation, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeApp(let value): + try container.encode(Kind.linkPreviewTypeApp, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeArticle(let value): + try container.encode(Kind.linkPreviewTypeArticle, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeAudio(let value): + try container.encode(Kind.linkPreviewTypeAudio, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeBackground(let value): + try container.encode(Kind.linkPreviewTypeBackground, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeChannelBoost(let value): + try container.encode(Kind.linkPreviewTypeChannelBoost, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeChat(let value): + try container.encode(Kind.linkPreviewTypeChat, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeDirectMessagesChat(let value): + try container.encode(Kind.linkPreviewTypeDirectMessagesChat, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeDocument(let value): + try container.encode(Kind.linkPreviewTypeDocument, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeEmbeddedAnimationPlayer(let value): + try container.encode(Kind.linkPreviewTypeEmbeddedAnimationPlayer, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeEmbeddedAudioPlayer(let value): + try container.encode(Kind.linkPreviewTypeEmbeddedAudioPlayer, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeEmbeddedVideoPlayer(let value): + try container.encode(Kind.linkPreviewTypeEmbeddedVideoPlayer, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeExternalAudio(let value): + try container.encode(Kind.linkPreviewTypeExternalAudio, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeExternalVideo(let value): + try container.encode(Kind.linkPreviewTypeExternalVideo, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeGiftAuction(let value): + try container.encode(Kind.linkPreviewTypeGiftAuction, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeGiftCollection(let value): + try container.encode(Kind.linkPreviewTypeGiftCollection, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeGroupCall: + try container.encode(Kind.linkPreviewTypeGroupCall, forKey: .type) + case .linkPreviewTypeInvoice: + try container.encode(Kind.linkPreviewTypeInvoice, forKey: .type) + case .linkPreviewTypeLiveStory(let value): + try container.encode(Kind.linkPreviewTypeLiveStory, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeMessage: + try container.encode(Kind.linkPreviewTypeMessage, forKey: .type) + case .linkPreviewTypePhoto(let value): + try container.encode(Kind.linkPreviewTypePhoto, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypePremiumGiftCode: + try container.encode(Kind.linkPreviewTypePremiumGiftCode, forKey: .type) + case .linkPreviewTypeRequestManagedBot: + try container.encode(Kind.linkPreviewTypeRequestManagedBot, forKey: .type) + case .linkPreviewTypeShareableChatFolder: + try container.encode(Kind.linkPreviewTypeShareableChatFolder, forKey: .type) + case .linkPreviewTypeSticker(let value): + try container.encode(Kind.linkPreviewTypeSticker, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeStickerSet(let value): + try container.encode(Kind.linkPreviewTypeStickerSet, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeStory(let value): + try container.encode(Kind.linkPreviewTypeStory, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeStoryAlbum(let value): + try container.encode(Kind.linkPreviewTypeStoryAlbum, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeSupergroupBoost(let value): + try container.encode(Kind.linkPreviewTypeSupergroupBoost, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeTextCompositionStyle(let value): + try container.encode(Kind.linkPreviewTypeTextCompositionStyle, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeTheme(let value): + try container.encode(Kind.linkPreviewTypeTheme, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeUnsupported: + try container.encode(Kind.linkPreviewTypeUnsupported, forKey: .type) + case .linkPreviewTypeUpgradedGift(let value): + try container.encode(Kind.linkPreviewTypeUpgradedGift, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeUser(let value): + try container.encode(Kind.linkPreviewTypeUser, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeVideo(let value): + try container.encode(Kind.linkPreviewTypeVideo, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeVideoChat(let value): + try container.encode(Kind.linkPreviewTypeVideoChat, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeVideoNote(let value): + try container.encode(Kind.linkPreviewTypeVideoNote, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeVoiceNote(let value): + try container.encode(Kind.linkPreviewTypeVoiceNote, forKey: .type) + try value.encode(to: encoder) + case .linkPreviewTypeWebApp(let value): + try container.encode(Kind.linkPreviewTypeWebApp, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The link is a link to a media album consisting of photos and videos +public struct LinkPreviewTypeAlbum: Codable, Equatable, Hashable { + + /// Album caption + public let caption: String + + /// The list of album media + public let media: [LinkPreviewAlbumMedia] + + + public init( + caption: String, + media: [LinkPreviewAlbumMedia] + ) { + self.caption = caption + self.media = media + } +} + +/// The link is a link to an animation +public struct LinkPreviewTypeAnimation: Codable, Equatable, Hashable { + + /// The animation + public let animation: Animation + + + public init(animation: Animation) { + self.animation = animation + } +} + +/// The link is a link to an app at App Store or Google Play +public struct LinkPreviewTypeApp: Codable, Equatable, Hashable { + + /// Photo for the app + public let photo: Photo + + + public init(photo: Photo) { + self.photo = photo + } +} + +/// The link is a link to a web site +public struct LinkPreviewTypeArticle: Codable, Equatable, Hashable { + + /// Article's main photo; may be null + public let photo: Photo? + + + public init(photo: Photo?) { + self.photo = photo + } +} + +/// The link is a link to an audio +public struct LinkPreviewTypeAudio: Codable, Equatable, Hashable { + + /// The audio description + public let audio: Audio + + + public init(audio: Audio) { + self.audio = audio + } +} + +/// The link is a link to a background. Link preview title and description are available only for filled backgrounds +public struct LinkPreviewTypeBackground: Codable, Equatable, Hashable { + + /// Type of the background; may be null if unknown + public let backgroundType: BackgroundType? + + /// Document with the background; may be null for filled backgrounds + public let document: Document? + + + public init( + backgroundType: BackgroundType?, + document: Document? + ) { + self.backgroundType = backgroundType + self.document = document + } +} + +/// The link is a link to boost a channel chat +public struct LinkPreviewTypeChannelBoost: Codable, Equatable, Hashable { + + /// Photo of the chat; may be null + public let photo: ChatPhoto? + + + public init(photo: ChatPhoto?) { + self.photo = photo + } +} + +/// The link is a link to a chat +public struct LinkPreviewTypeChat: Codable, Equatable, Hashable { + + /// True, if the link only creates join request + public let createsJoinRequest: Bool + + /// Photo of the chat; may be null + public let photo: ChatPhoto? + + /// Type of the chat + public let type: InviteLinkChatType + + + public init( + createsJoinRequest: Bool, + photo: ChatPhoto?, + type: InviteLinkChatType + ) { + self.createsJoinRequest = createsJoinRequest + self.photo = photo + self.type = type + } +} + +/// The link is a link to a direct messages chat of a channel +public struct LinkPreviewTypeDirectMessagesChat: Codable, Equatable, Hashable { + + /// Photo of the channel chat; may be null + public let photo: ChatPhoto? + + + public init(photo: ChatPhoto?) { + self.photo = photo + } +} + +/// The link is a link to a general file +public struct LinkPreviewTypeDocument: Codable, Equatable, Hashable { + + /// The document description + public let document: Document + + + public init(document: Document) { + self.document = document + } +} + +/// The link is a link to an animation player +public struct LinkPreviewTypeEmbeddedAnimationPlayer: Codable, Equatable, Hashable { + + /// The cached animation; may be null if unknown + public let animation: Animation? + + /// Duration of the animation, in seconds + public let duration: Int + + /// Expected height of the embedded player + public let height: Int + + /// Thumbnail of the animation; may be null if unknown + public let thumbnail: Photo? + + /// URL of the external animation player + public let url: String + + /// Expected width of the embedded player + public let width: Int + + + public init( + animation: Animation?, + duration: Int, + height: Int, + thumbnail: Photo?, + url: String, + width: Int + ) { + self.animation = animation + self.duration = duration + self.height = height + self.thumbnail = thumbnail + self.url = url + self.width = width + } +} + +/// The link is a link to an audio player +public struct LinkPreviewTypeEmbeddedAudioPlayer: Codable, Equatable, Hashable { + + /// The cached audio; may be null if unknown + public let audio: Audio? + + /// Duration of the audio, in seconds + public let duration: Int + + /// Expected height of the embedded player + public let height: Int + + /// Thumbnail of the audio; may be null if unknown + public let thumbnail: Photo? + + /// URL of the external audio player + public let url: String + + /// Expected width of the embedded player + public let width: Int + + + public init( + audio: Audio?, + duration: Int, + height: Int, + thumbnail: Photo?, + url: String, + width: Int + ) { + self.audio = audio + self.duration = duration + self.height = height + self.thumbnail = thumbnail + self.url = url + self.width = width + } +} + +/// The link is a link to a video player +public struct LinkPreviewTypeEmbeddedVideoPlayer: Codable, Equatable, Hashable { + + /// Duration of the video, in seconds + public let duration: Int + + /// Expected height of the embedded player + public let height: Int + + /// Thumbnail of the video; may be null if unknown + public let thumbnail: Photo? + + /// URL of the external video player + public let url: String + + /// The cached video; may be null if unknown + public let video: Video? + + /// Expected width of the embedded player + public let width: Int + + + public init( + duration: Int, + height: Int, + thumbnail: Photo?, + url: String, + video: Video?, + width: Int + ) { + self.duration = duration + self.height = height + self.thumbnail = thumbnail + self.url = url + self.video = video + self.width = width + } +} + +/// The link is a link to an audio file +public struct LinkPreviewTypeExternalAudio: Codable, Equatable, Hashable { + + /// Duration of the audio, in seconds; 0 if unknown + public let duration: Int + + /// MIME type of the audio file + public let mimeType: String + + /// URL of the audio file + public let url: String + + + public init( + duration: Int, + mimeType: String, + url: String + ) { + self.duration = duration + self.mimeType = mimeType + self.url = url + } +} + +/// The link is a link to a video file +public struct LinkPreviewTypeExternalVideo: Codable, Equatable, Hashable { + + /// Duration of the video, in seconds; 0 if unknown + public let duration: Int + + /// Expected height of the video preview; 0 if unknown + public let height: Int + + /// MIME type of the video file + public let mimeType: String + + /// URL of the video file + public let url: String + + /// Expected width of the video preview; 0 if unknown + public let width: Int + + + public init( + duration: Int, + height: Int, + mimeType: String, + url: String, + width: Int + ) { + self.duration = duration + self.height = height + self.mimeType = mimeType + self.url = url + self.width = width + } +} + +/// The link is a link to a gift auction +public struct LinkPreviewTypeGiftAuction: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the auction will be ended + public let auctionEndDate: Int + + /// The gift + public let gift: Gift + + + public init( + auctionEndDate: Int, + gift: Gift + ) { + self.auctionEndDate = auctionEndDate + self.gift = gift + } +} + +/// The link is a link to a gift collection +public struct LinkPreviewTypeGiftCollection: Codable, Equatable, Hashable { + + /// Icons for some gifts from the collection; may be empty + public let icons: [Sticker] + + + public init(icons: [Sticker]) { + self.icons = icons + } +} + +/// The link is a link to a live story group call +public struct LinkPreviewTypeLiveStory: Codable, Equatable, Hashable { + + /// Story identifier + public let storyId: Int + + /// The identifier of the chat that posted the story + public let storyPosterChatId: Int64 + + + public init( + storyId: Int, + storyPosterChatId: Int64 + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + } +} + +/// The link is a link to a photo +public struct LinkPreviewTypePhoto: Codable, Equatable, Hashable { + + /// The photo + public let photo: Photo + + + public init(photo: Photo) { + self.photo = photo + } +} + +/// The link is a link to a sticker +public struct LinkPreviewTypeSticker: Codable, Equatable, Hashable { + + /// The sticker. It can be an arbitrary WEBP image and can have dimensions bigger than 512 + public let sticker: Sticker + + + public init(sticker: Sticker) { + self.sticker = sticker + } +} + +/// The link is a link to a sticker set +public struct LinkPreviewTypeStickerSet: Codable, Equatable, Hashable { + + /// Up to 4 stickers from the sticker set + public let stickers: [Sticker] + + + public init(stickers: [Sticker]) { + self.stickers = stickers + } +} + +/// The link is a link to a story. Link preview description is unavailable +public struct LinkPreviewTypeStory: Codable, Equatable, Hashable { + + /// Story identifier + public let storyId: Int + + /// The identifier of the chat that posted the story + public let storyPosterChatId: Int64 + + + public init( + storyId: Int, + storyPosterChatId: Int64 + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + } +} + +/// The link is a link to an album of stories +public struct LinkPreviewTypeStoryAlbum: Codable, Equatable, Hashable { + + /// Icon of the album; may be null if none + public let photoIcon: Photo? + + /// Video icon of the album; may be null if none + public let videoIcon: Video? + + + public init( + photoIcon: Photo?, + videoIcon: Video? + ) { + self.photoIcon = photoIcon + self.videoIcon = videoIcon + } +} + +/// The link is a link to boost a supergroup chat +public struct LinkPreviewTypeSupergroupBoost: Codable, Equatable, Hashable { + + /// Photo of the chat; may be null + public let photo: ChatPhoto? + + + public init(photo: ChatPhoto?) { + self.photo = photo + } +} + +/// The link is a link to a text composition style +public struct LinkPreviewTypeTextCompositionStyle: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji corresponding to the style; 0 if none + public let customEmojiId: TdInt64 + + + public init(customEmojiId: TdInt64) { + self.customEmojiId = customEmojiId + } +} + +/// The link is a link to a cloud theme. TDLib has no theme support yet +public struct LinkPreviewTypeTheme: Codable, Equatable, Hashable { + + /// The list of files with theme description + public let documents: [Document] + + /// Settings for the cloud theme; may be null if unknown + public let settings: ThemeSettings? + + + public init( + documents: [Document], + settings: ThemeSettings? + ) { + self.documents = documents + self.settings = settings + } +} + +/// The link is a link to an upgraded gift +public struct LinkPreviewTypeUpgradedGift: Codable, Equatable, Hashable { + + /// The gift + public let gift: UpgradedGift + + + public init(gift: UpgradedGift) { + self.gift = gift + } +} + +/// The link is a link to a user +public struct LinkPreviewTypeUser: Codable, Equatable, Hashable { + + /// True, if the user is a bot + public let isBot: Bool + + /// Photo of the user; may be null if none + public let photo: ChatPhoto? + + + public init( + isBot: Bool, + photo: ChatPhoto? + ) { + self.isBot = isBot + self.photo = photo + } +} + +/// The link is a link to a video +public struct LinkPreviewTypeVideo: Codable, Equatable, Hashable { + + /// Cover of the video; may be null if none + public let cover: Photo? + + /// Timestamp from which the video playing must start, in seconds + public let startTimestamp: Int + + /// The video description + public let video: Video + + + public init( + cover: Photo?, + startTimestamp: Int, + video: Video + ) { + self.cover = cover + self.startTimestamp = startTimestamp + self.video = video + } +} + +/// The link is a link to a video chat +public struct LinkPreviewTypeVideoChat: Codable, Equatable, Hashable { + + /// True, if the video chat is expected to be a live stream in a channel or a broadcast group + public let isLiveStream: Bool + + /// True, if the user can use the link to join the video chat without being muted by administrators + public let joinsAsSpeaker: Bool + + /// Photo of the chat with the video chat; may be null if none + public let photo: ChatPhoto? + + + public init( + isLiveStream: Bool, + joinsAsSpeaker: Bool, + photo: ChatPhoto? + ) { + self.isLiveStream = isLiveStream + self.joinsAsSpeaker = joinsAsSpeaker + self.photo = photo + } +} + +/// The link is a link to a video note message +public struct LinkPreviewTypeVideoNote: Codable, Equatable, Hashable { + + /// The video note + public let videoNote: VideoNote + + + public init(videoNote: VideoNote) { + self.videoNote = videoNote + } +} + +/// The link is a link to a voice note message +public struct LinkPreviewTypeVoiceNote: Codable, Equatable, Hashable { + + /// The voice note + public let voiceNote: VoiceNote + + + public init(voiceNote: VoiceNote) { + self.voiceNote = voiceNote + } +} + +/// The link is a link to a Web App +public struct LinkPreviewTypeWebApp: Codable, Equatable, Hashable { + + /// Web App photo; may be null if none + public let photo: Photo? + + + public init(photo: Photo?) { + self.photo = photo + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LoadChats.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LoadChats.swift new file mode 100644 index 0000000000..0191c62949 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LoadChats.swift @@ -0,0 +1,31 @@ +// +// LoadChats.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Loads more chats from a chat list. The loaded chats and their positions in the chat list will be sent through updates. Chats are sorted by the pair (chat.position.order, chat.id) in descending order. Returns a 404 error if all chats have been loaded +public struct LoadChats: Codable, Equatable, Hashable { + + /// The chat list in which to load chats; pass null to load chats from the main chat list + public let chatList: ChatList? + + /// The maximum number of chats to be loaded. For optimal performance, the number of loaded chats is chosen by TDLib and can be smaller than the specified limit, even if the end of the list is not reached + public let limit: Int? + + + public init( + chatList: ChatList?, + limit: Int? + ) { + self.chatList = chatList + self.limit = limit + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LocalFile.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LocalFile.swift new file mode 100644 index 0000000000..b63a395c6d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LocalFile.swift @@ -0,0 +1,61 @@ +// +// LocalFile.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a local file +public struct LocalFile: Codable, Equatable, Hashable { + + /// True, if the file can be deleted + public let canBeDeleted: Bool + + /// True, if it is possible to download or generate the file + public let canBeDownloaded: Bool + + /// Download will be started from this offset. downloaded_prefix_size is calculated from this offset + public let downloadOffset: Int64 + + /// If is_downloading_completed is false, then only some prefix of the file starting from download_offset is ready to be read. downloaded_prefix_size is the size of that prefix in bytes + public let downloadedPrefixSize: Int64 + + /// Total downloaded file size, in bytes. Can be used only for calculating download progress. The actual file size may be bigger, and some parts of it may contain garbage + public let downloadedSize: Int64 + + /// True, if the file is currently being downloaded (or a local copy is being generated by some other means) + public let isDownloadingActive: Bool + + /// True, if the local copy is fully available + public let isDownloadingCompleted: Bool + + /// Local path to the locally available file part; may be empty + public let path: String + + + public init( + canBeDeleted: Bool, + canBeDownloaded: Bool, + downloadOffset: Int64, + downloadedPrefixSize: Int64, + downloadedSize: Int64, + isDownloadingActive: Bool, + isDownloadingCompleted: Bool, + path: String + ) { + self.canBeDeleted = canBeDeleted + self.canBeDownloaded = canBeDownloaded + self.downloadOffset = downloadOffset + self.downloadedPrefixSize = downloadedPrefixSize + self.downloadedSize = downloadedSize + self.isDownloadingActive = isDownloadingActive + self.isDownloadingCompleted = isDownloadingCompleted + self.path = path + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Location.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Location.swift new file mode 100644 index 0000000000..1a42a39ba3 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Location.swift @@ -0,0 +1,36 @@ +// +// Location.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a location on planet Earth +public struct Location: Codable, Equatable, Hashable { + + /// The estimated horizontal accuracy of the location, in meters; as defined by the sender. 0 if unknown + public let horizontalAccuracy: Double + + /// Latitude of the location in degrees; as defined by the sender + public let latitude: Double + + /// Longitude of the location, in degrees; as defined by the sender + public let longitude: Double + + + public init( + horizontalAccuracy: Double, + latitude: Double, + longitude: Double + ) { + self.horizontalAccuracy = horizontalAccuracy + self.latitude = latitude + self.longitude = longitude + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LogOut.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LogOut.swift new file mode 100644 index 0000000000..004abd6795 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/LogOut.swift @@ -0,0 +1,19 @@ +// +// LogOut.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Closes the TDLib instance after a proper logout. Requires an available network connection. All local data will be destroyed. After the logout completes, updateAuthorizationState with authorizationStateClosed will be sent +public struct LogOut: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPoint.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPoint.swift new file mode 100644 index 0000000000..3aed81e871 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPoint.swift @@ -0,0 +1,73 @@ +// +// MaskPoint.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Part of the face, relative to which a mask is placed +public indirect enum MaskPoint: Codable, Equatable, Hashable { + + /// The mask is placed relatively to the forehead + case maskPointForehead + + /// The mask is placed relatively to the eyes + case maskPointEyes + + /// The mask is placed relatively to the mouth + case maskPointMouth + + /// The mask is placed relatively to the chin + case maskPointChin + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case maskPointForehead + case maskPointEyes + case maskPointMouth + case maskPointChin + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .maskPointForehead: + self = .maskPointForehead + case .maskPointEyes: + self = .maskPointEyes + case .maskPointMouth: + self = .maskPointMouth + case .maskPointChin: + self = .maskPointChin + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .maskPointForehead: + try container.encode(Kind.maskPointForehead, forKey: .type) + case .maskPointEyes: + try container.encode(Kind.maskPointEyes, forKey: .type) + case .maskPointMouth: + try container.encode(Kind.maskPointMouth, forKey: .type) + case .maskPointChin: + try container.encode(Kind.maskPointChin, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPosition.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPosition.swift new file mode 100644 index 0000000000..dea10bb32b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MaskPosition.swift @@ -0,0 +1,41 @@ +// +// MaskPosition.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Position on a photo where a mask is placed +public struct MaskPosition: Codable, Equatable, Hashable { + + /// Part of the face, relative to which the mask is placed + public let point: MaskPoint + + /// Mask scaling coefficient. (For example, 2.0 means a doubled size) + public let scale: Double + + /// Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. (For example, -1.0 will place the mask just to the left of the default mask position) + public let xShift: Double + + /// Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. (For example, 1.0 will place the mask just below the default mask position) + public let yShift: Double + + + public init( + point: MaskPoint, + scale: Double, + xShift: Double, + yShift: Double + ) { + self.point = point + self.scale = scale + self.xShift = xShift + self.yShift = yShift + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Message.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Message.swift new file mode 100644 index 0000000000..327ecedfa6 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Message.swift @@ -0,0 +1,226 @@ +// +// Message.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a message +public struct Message: Codable, Equatable, Hashable, Identifiable { + + /// For channel posts and anonymous group messages, optional author signature + public let authorSignature: String + + /// Time left before the message will be automatically deleted by message_auto_delete_time setting of the chat, in seconds; 0 if never + public let autoDeleteIn: Double + + /// True, if content of the message can be saved locally + public let canBeSaved: Bool + + /// Chat identifier + public let chatId: Int64 + + /// True, if the message contains an unread mention for the current user + public let containsUnreadMention: Bool + + /// True, if the message is a poll message with unread votes + public let containsUnreadPollVotes: Bool + + /// Content of the message + public let content: MessageContent + + /// Point in time (Unix timestamp) when the message was sent; 0 for scheduled messages + public let date: Int + + /// Point in time (Unix timestamp) when the message was last edited; 0 for scheduled messages + public let editDate: Int + + /// Unique identifier of the effect added to the message; 0 if none + public let effectId: TdInt64 + + /// Information about fact-check added to the message; may be null if none + public let factCheck: FactCheck? + + /// Information about the initial message sender; may be null if none or unknown + public let forwardInfo: MessageForwardInfo? + + /// The identifier of the user or chat which used a guest bot to send the message; may be null if none + public let guestBotCallerId: MessageSender? + + /// True, if media timestamp entities refers to a media in this message as opposed to a media in the replied message + public let hasTimestampedMedia: Bool + + /// Message identifier; unique for the chat to which the message belongs + public let id: Int64 + + /// Information about the initial message for messages created with importMessages; may be null if the message isn't imported + public let importInfo: MessageImportInfo? + + /// Information about interactions with the message; may be null if none + public let interactionInfo: MessageInteractionInfo? + + /// True, if the message is a channel post. All messages to channels are channel posts, all other messages are not channel posts + public let isChannelPost: Bool + + /// True, if the message was sent because of a scheduled action by the message sender, for example, as away, or greeting service message + public let isFromOffline: Bool + + /// True, if the message is outgoing + public let isOutgoing: Bool + + /// True, if the message is a suggested channel post which was paid in Telegram Stars; a warning must be shown if the message is deleted in less than getOption("suggested_post_lifetime_min") seconds after sending + public let isPaidStarSuggestedPost: Bool + + /// True, if the message is a suggested channel post which was paid in Toncoins; a warning must be shown if the message is deleted in less than getOption("suggested_post_lifetime_min") seconds after sending + public let isPaidTonSuggestedPost: Bool + + /// True, if the message is pinned + public let isPinned: Bool + + /// Unique identifier of an album this message belongs to; 0 if none. Only audios, documents, photos and videos can be grouped together in albums + public let mediaAlbumId: TdInt64 + + /// The number of Telegram Stars the sender paid to send the message + public let paidMessageStarCount: Int64 + + /// Reply markup for the message; may be null if none + public let replyMarkup: ReplyMarkup? + + /// Information about the message or the story this message is replying to; may be null if none + public let replyTo: MessageReplyTo? + + /// Information about the restrictions that must be applied to the message content; may be null if none + public let restrictionInfo: RestrictionInfo? + + /// The scheduling state of the message; may be null if the message isn't scheduled + public let schedulingState: MessageSchedulingState? + + /// Time left before the message self-destruct timer expires, in seconds; 0 if self-destruction isn't scheduled yet + public let selfDestructIn: Double + + /// The message's self-destruct type; may be null if none + public let selfDestructType: MessageSelfDestructType? + + /// Number of times the sender of the message boosted the supergroup at the time the message was sent; 0 if none or unknown. For messages sent by the current user, supergroupFullInfo.my_boost_count must be used instead + public let senderBoostCount: Int + + /// If non-zero, the user identifier of the business bot that sent this message + public let senderBusinessBotUserId: Int64 + + /// Identifier of the sender of the message + public let senderId: MessageSender + + /// Tag of the sender of the message in the supergroup at the time the message was sent; may be empty if none or unknown. For messages sent in basic groups or supergroup administrators, the current custom title or tag must be used instead + public let senderTag: String + + /// The sending state of the message; may be null if the message isn't being sent and didn't fail to be sent + public let sendingState: MessageSendingState? + + /// Information about the suggested post; may be null if the message isn't a suggested post + public let suggestedPostInfo: SuggestedPostInfo? + + /// IETF language tag of the message language on which it can be summarized; empty if summary isn't available for the message + public let summaryLanguageCode: String + + /// Identifier of the topic within the chat to which the message belongs; may be null if none; may change when the chat is converted to a forum or back + public let topicId: MessageTopic? + + /// Information about unread reactions added to the message + public let unreadReactions: [UnreadReaction] + + /// If non-zero, the user identifier of the inline bot through which this message was sent + public let viaBotUserId: Int64 + + + public init( + authorSignature: String, + autoDeleteIn: Double, + canBeSaved: Bool, + chatId: Int64, + containsUnreadMention: Bool, + containsUnreadPollVotes: Bool, + content: MessageContent, + date: Int, + editDate: Int, + effectId: TdInt64, + factCheck: FactCheck?, + forwardInfo: MessageForwardInfo?, + guestBotCallerId: MessageSender?, + hasTimestampedMedia: Bool, + id: Int64, + importInfo: MessageImportInfo?, + interactionInfo: MessageInteractionInfo?, + isChannelPost: Bool, + isFromOffline: Bool, + isOutgoing: Bool, + isPaidStarSuggestedPost: Bool, + isPaidTonSuggestedPost: Bool, + isPinned: Bool, + mediaAlbumId: TdInt64, + paidMessageStarCount: Int64, + replyMarkup: ReplyMarkup?, + replyTo: MessageReplyTo?, + restrictionInfo: RestrictionInfo?, + schedulingState: MessageSchedulingState?, + selfDestructIn: Double, + selfDestructType: MessageSelfDestructType?, + senderBoostCount: Int, + senderBusinessBotUserId: Int64, + senderId: MessageSender, + senderTag: String, + sendingState: MessageSendingState?, + suggestedPostInfo: SuggestedPostInfo?, + summaryLanguageCode: String, + topicId: MessageTopic?, + unreadReactions: [UnreadReaction], + viaBotUserId: Int64 + ) { + self.authorSignature = authorSignature + self.autoDeleteIn = autoDeleteIn + self.canBeSaved = canBeSaved + self.chatId = chatId + self.containsUnreadMention = containsUnreadMention + self.containsUnreadPollVotes = containsUnreadPollVotes + self.content = content + self.date = date + self.editDate = editDate + self.effectId = effectId + self.factCheck = factCheck + self.forwardInfo = forwardInfo + self.guestBotCallerId = guestBotCallerId + self.hasTimestampedMedia = hasTimestampedMedia + self.id = id + self.importInfo = importInfo + self.interactionInfo = interactionInfo + self.isChannelPost = isChannelPost + self.isFromOffline = isFromOffline + self.isOutgoing = isOutgoing + self.isPaidStarSuggestedPost = isPaidStarSuggestedPost + self.isPaidTonSuggestedPost = isPaidTonSuggestedPost + self.isPinned = isPinned + self.mediaAlbumId = mediaAlbumId + self.paidMessageStarCount = paidMessageStarCount + self.replyMarkup = replyMarkup + self.replyTo = replyTo + self.restrictionInfo = restrictionInfo + self.schedulingState = schedulingState + self.selfDestructIn = selfDestructIn + self.selfDestructType = selfDestructType + self.senderBoostCount = senderBoostCount + self.senderBusinessBotUserId = senderBusinessBotUserId + self.senderId = senderId + self.senderTag = senderTag + self.sendingState = sendingState + self.suggestedPostInfo = suggestedPostInfo + self.summaryLanguageCode = summaryLanguageCode + self.topicId = topicId + self.unreadReactions = unreadReactions + self.viaBotUserId = viaBotUserId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageContent.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageContent.swift new file mode 100644 index 0000000000..bff77966b5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageContent.swift @@ -0,0 +1,3241 @@ +// +// MessageContent.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains the content of a message +public indirect enum MessageContent: Codable, Equatable, Hashable { + + /// A text message + case messageText(MessageText) + + /// An animation message (GIF-style). + case messageAnimation(MessageAnimation) + + /// An audio message + case messageAudio(MessageAudio) + + /// A document message (general file) + case messageDocument(MessageDocument) + + /// A message with paid media + case messagePaidMedia(MessagePaidMedia) + + /// A photo message + case messagePhoto(MessagePhoto) + + /// A sticker message + case messageSticker(MessageSticker) + + /// A video message + case messageVideo(MessageVideo) + + /// A video note message + case messageVideoNote(MessageVideoNote) + + /// A voice note message + case messageVoiceNote(MessageVoiceNote) + + /// A self-destructed photo message + case messageExpiredPhoto + + /// A self-destructed video message + case messageExpiredVideo + + /// A self-destructed video note message + case messageExpiredVideoNote + + /// A self-destructed voice note message + case messageExpiredVoiceNote + + /// A message with a location + case messageLocation(MessageLocation) + + /// A message with information about a venue + case messageVenue(MessageVenue) + + /// A message with a user contact + case messageContact(MessageContact) + + /// A message with an animated emoji + case messageAnimatedEmoji(MessageAnimatedEmoji) + + /// A dice message. The dice value is randomly generated by the server + case messageDice(MessageDice) + + /// A message with a game + case messageGame(MessageGame) + + /// A message with a poll + case messagePoll(MessagePoll) + + /// A stake dice message. The dice value is randomly generated by the server + case messageStakeDice(MessageStakeDice) + + /// A message with a forwarded story + case messageStory(MessageStory) + + /// A message with a checklist + case messageChecklist(MessageChecklist) + + /// A message with an invoice from a bot. Use getInternalLink with internalLinkTypeBotStart to share the invoice + case messageInvoice(MessageInvoice) + + /// A message with information about an ended call + case messageCall(MessageCall) + + /// A message with information about a group call not bound to a chat. If the message is incoming, the call isn't active, isn't missed, and has no duration, and getOption("can_accept_calls") is true, then incoming call screen must be shown to the user. Use getGroupCallParticipants to show current group call participants on the screen. Use joinGroupCall to accept the call or declineGroupCallInvitation to decline it. If the call become active or missed, then the call screen must be hidden + case messageGroupCall(MessageGroupCall) + + /// A new video chat was scheduled + case messageVideoChatScheduled(MessageVideoChatScheduled) + + /// A newly created video chat + case messageVideoChatStarted(MessageVideoChatStarted) + + /// A message with information about an ended video chat + case messageVideoChatEnded(MessageVideoChatEnded) + + /// A message with information about an invitation to a video chat + case messageInviteVideoChatParticipants(MessageInviteVideoChatParticipants) + + /// A message with information about an added poll option + case messagePollOptionAdded(MessagePollOptionAdded) + + /// A message with information about a deleted poll option + case messagePollOptionDeleted(MessagePollOptionDeleted) + + /// A newly created basic group + case messageBasicGroupChatCreate(MessageBasicGroupChatCreate) + + /// A newly created supergroup or channel + case messageSupergroupChatCreate(MessageSupergroupChatCreate) + + /// An updated chat title + case messageChatChangeTitle(MessageChatChangeTitle) + + /// An updated chat photo + case messageChatChangePhoto(MessageChatChangePhoto) + + /// A deleted chat photo + case messageChatDeletePhoto + + /// The owner of the chat has left + case messageChatOwnerLeft(MessageChatOwnerLeft) + + /// The owner of the chat has changed + case messageChatOwnerChanged(MessageChatOwnerChanged) + + /// Chat has_protected_content setting was changed or request to change it was rejected + case messageChatHasProtectedContentToggled(MessageChatHasProtectedContentToggled) + + /// Chat has_protected_content setting was requested to be disabled + case messageChatHasProtectedContentDisableRequested(MessageChatHasProtectedContentDisableRequested) + + /// New chat members were added + case messageChatAddMembers(MessageChatAddMembers) + + /// A new member joined the chat via an invite link + case messageChatJoinByLink + + /// A new member was accepted to the chat by an administrator + case messageChatJoinByRequest + + /// A chat member was deleted + case messageChatDeleteMember(MessageChatDeleteMember) + + /// A basic group was upgraded to a supergroup and was deactivated as the result + case messageChatUpgradeTo(MessageChatUpgradeTo) + + /// A supergroup has been created from a basic group + case messageChatUpgradeFrom(MessageChatUpgradeFrom) + + /// A message has been pinned + case messagePinMessage(MessagePinMessage) + + /// A screenshot of a message in the chat has been taken + case messageScreenshotTaken + + /// A new background was set in the chat + case messageChatSetBackground(MessageChatSetBackground) + + /// A theme in the chat has been changed + case messageChatSetTheme(MessageChatSetTheme) + + /// The auto-delete or self-destruct timer for messages in the chat has been changed + case messageChatSetMessageAutoDeleteTime(MessageChatSetMessageAutoDeleteTime) + + /// The chat was boosted by the sender of the message + case messageChatBoost(MessageChatBoost) + + /// A forum topic has been created + case messageForumTopicCreated(MessageForumTopicCreated) + + /// A forum topic has been edited + case messageForumTopicEdited(MessageForumTopicEdited) + + /// A forum topic has been closed or opened + case messageForumTopicIsClosedToggled(MessageForumTopicIsClosedToggled) + + /// A General forum topic has been hidden or unhidden + case messageForumTopicIsHiddenToggled(MessageForumTopicIsHiddenToggled) + + /// A profile photo was suggested to a user in a private chat + case messageSuggestProfilePhoto(MessageSuggestProfilePhoto) + + /// A birthdate was suggested to be set + case messageSuggestBirthdate(MessageSuggestBirthdate) + + /// A non-standard action has happened in the chat + case messageCustomServiceAction(MessageCustomServiceAction) + + /// A new high score was achieved in a game + case messageGameScore(MessageGameScore) + + /// A bot managed by another bot was created by the user + case messageManagedBotCreated(MessageManagedBotCreated) + + /// A payment has been sent to a bot or a business account + case messagePaymentSuccessful(MessagePaymentSuccessful) + + /// A payment has been received by the bot or the business account + case messagePaymentSuccessfulBot(MessagePaymentSuccessfulBot) + + /// A payment has been refunded + case messagePaymentRefunded(MessagePaymentRefunded) + + /// Telegram Premium was gifted to a user + case messageGiftedPremium(MessageGiftedPremium) + + /// A Telegram Premium gift code was created for the user + case messagePremiumGiftCode(MessagePremiumGiftCode) + + /// A giveaway was created for the chat. Use telegramPaymentPurposePremiumGiveaway, storePaymentPurposePremiumGiveaway, telegramPaymentPurposeStarGiveaway, or storePaymentPurposeStarGiveaway to create a giveaway + case messageGiveawayCreated(MessageGiveawayCreated) + + /// A giveaway + case messageGiveaway(MessageGiveaway) + + /// A giveaway without public winners has been completed for the chat + case messageGiveawayCompleted(MessageGiveawayCompleted) + + /// A giveaway with public winners has been completed for the chat + case messageGiveawayWinners(MessageGiveawayWinners) + + /// Telegram Stars were gifted to a user + case messageGiftedStars(MessageGiftedStars) + + /// Toncoins were gifted to a user + case messageGiftedTon(MessageGiftedTon) + + /// A Telegram Stars were received by the current user from a giveaway + case messageGiveawayPrizeStars(MessageGiveawayPrizeStars) + + /// A regular gift was received or sent by the current user, or the current user was notified about a channel gift + case messageGift(MessageGift) + + /// An upgraded gift was received or sent by the current user, or the current user was notified about a channel gift + case messageUpgradedGift(MessageUpgradedGift) + + /// A gift which purchase, upgrade or transfer were refunded + case messageRefundedUpgradedGift(MessageRefundedUpgradedGift) + + /// An offer to purchase an upgraded gift was sent or received + case messageUpgradedGiftPurchaseOffer(MessageUpgradedGiftPurchaseOffer) + + /// An offer to purchase a gift was rejected or expired + case messageUpgradedGiftPurchaseOfferRejected(MessageUpgradedGiftPurchaseOfferRejected) + + /// Paid messages were refunded + case messagePaidMessagesRefunded(MessagePaidMessagesRefunded) + + /// A price for paid messages was changed in the supergroup chat + case messagePaidMessagePriceChanged(MessagePaidMessagePriceChanged) + + /// A price for direct messages was changed in the channel chat + case messageDirectMessagePriceChanged(MessageDirectMessagePriceChanged) + + /// Some tasks from a checklist were marked as done or not done + case messageChecklistTasksDone(MessageChecklistTasksDone) + + /// Some tasks were added to a checklist + case messageChecklistTasksAdded(MessageChecklistTasksAdded) + + /// Approval of suggested post has failed, because the user which proposed the post had no enough funds + case messageSuggestedPostApprovalFailed(MessageSuggestedPostApprovalFailed) + + /// A suggested post was approved + case messageSuggestedPostApproved(MessageSuggestedPostApproved) + + /// A suggested post was declined + case messageSuggestedPostDeclined(MessageSuggestedPostDeclined) + + /// A suggested post was published for getOption("suggested_post_lifetime_min") seconds and payment for the post was received + case messageSuggestedPostPaid(MessageSuggestedPostPaid) + + /// A suggested post was refunded + case messageSuggestedPostRefunded(MessageSuggestedPostRefunded) + + /// A contact has registered with Telegram + case messageContactRegistered + + /// The current user shared users, which were requested by the bot + case messageUsersShared(MessageUsersShared) + + /// The current user shared a chat, which was requested by the bot + case messageChatShared(MessageChatShared) + + /// The user allowed the bot to send messages + case messageBotWriteAccessAllowed(MessageBotWriteAccessAllowed) + + /// Data from a Web App has been sent to a bot + case messageWebAppDataSent(MessageWebAppDataSent) + + /// Data from a Web App has been received; for bots only + case messageWebAppDataReceived(MessageWebAppDataReceived) + + /// A user in the chat came within proximity alert range + case messageProximityAlertTriggered(MessageProximityAlertTriggered) + + /// A message content that is not supported in the current TDLib version + case messageUnsupported + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageText + case messageAnimation + case messageAudio + case messageDocument + case messagePaidMedia + case messagePhoto + case messageSticker + case messageVideo + case messageVideoNote + case messageVoiceNote + case messageExpiredPhoto + case messageExpiredVideo + case messageExpiredVideoNote + case messageExpiredVoiceNote + case messageLocation + case messageVenue + case messageContact + case messageAnimatedEmoji + case messageDice + case messageGame + case messagePoll + case messageStakeDice + case messageStory + case messageChecklist + case messageInvoice + case messageCall + case messageGroupCall + case messageVideoChatScheduled + case messageVideoChatStarted + case messageVideoChatEnded + case messageInviteVideoChatParticipants + case messagePollOptionAdded + case messagePollOptionDeleted + case messageBasicGroupChatCreate + case messageSupergroupChatCreate + case messageChatChangeTitle + case messageChatChangePhoto + case messageChatDeletePhoto + case messageChatOwnerLeft + case messageChatOwnerChanged + case messageChatHasProtectedContentToggled + case messageChatHasProtectedContentDisableRequested + case messageChatAddMembers + case messageChatJoinByLink + case messageChatJoinByRequest + case messageChatDeleteMember + case messageChatUpgradeTo + case messageChatUpgradeFrom + case messagePinMessage + case messageScreenshotTaken + case messageChatSetBackground + case messageChatSetTheme + case messageChatSetMessageAutoDeleteTime + case messageChatBoost + case messageForumTopicCreated + case messageForumTopicEdited + case messageForumTopicIsClosedToggled + case messageForumTopicIsHiddenToggled + case messageSuggestProfilePhoto + case messageSuggestBirthdate + case messageCustomServiceAction + case messageGameScore + case messageManagedBotCreated + case messagePaymentSuccessful + case messagePaymentSuccessfulBot + case messagePaymentRefunded + case messageGiftedPremium + case messagePremiumGiftCode + case messageGiveawayCreated + case messageGiveaway + case messageGiveawayCompleted + case messageGiveawayWinners + case messageGiftedStars + case messageGiftedTon + case messageGiveawayPrizeStars + case messageGift + case messageUpgradedGift + case messageRefundedUpgradedGift + case messageUpgradedGiftPurchaseOffer + case messageUpgradedGiftPurchaseOfferRejected + case messagePaidMessagesRefunded + case messagePaidMessagePriceChanged + case messageDirectMessagePriceChanged + case messageChecklistTasksDone + case messageChecklistTasksAdded + case messageSuggestedPostApprovalFailed + case messageSuggestedPostApproved + case messageSuggestedPostDeclined + case messageSuggestedPostPaid + case messageSuggestedPostRefunded + case messageContactRegistered + case messageUsersShared + case messageChatShared + case messageBotWriteAccessAllowed + case messageWebAppDataSent + case messageWebAppDataReceived + case messageProximityAlertTriggered + case messageUnsupported + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageText: + let value = try MessageText(from: decoder) + self = .messageText(value) + case .messageAnimation: + let value = try MessageAnimation(from: decoder) + self = .messageAnimation(value) + case .messageAudio: + let value = try MessageAudio(from: decoder) + self = .messageAudio(value) + case .messageDocument: + let value = try MessageDocument(from: decoder) + self = .messageDocument(value) + case .messagePaidMedia: + let value = try MessagePaidMedia(from: decoder) + self = .messagePaidMedia(value) + case .messagePhoto: + let value = try MessagePhoto(from: decoder) + self = .messagePhoto(value) + case .messageSticker: + let value = try MessageSticker(from: decoder) + self = .messageSticker(value) + case .messageVideo: + let value = try MessageVideo(from: decoder) + self = .messageVideo(value) + case .messageVideoNote: + let value = try MessageVideoNote(from: decoder) + self = .messageVideoNote(value) + case .messageVoiceNote: + let value = try MessageVoiceNote(from: decoder) + self = .messageVoiceNote(value) + case .messageExpiredPhoto: + self = .messageExpiredPhoto + case .messageExpiredVideo: + self = .messageExpiredVideo + case .messageExpiredVideoNote: + self = .messageExpiredVideoNote + case .messageExpiredVoiceNote: + self = .messageExpiredVoiceNote + case .messageLocation: + let value = try MessageLocation(from: decoder) + self = .messageLocation(value) + case .messageVenue: + let value = try MessageVenue(from: decoder) + self = .messageVenue(value) + case .messageContact: + let value = try MessageContact(from: decoder) + self = .messageContact(value) + case .messageAnimatedEmoji: + let value = try MessageAnimatedEmoji(from: decoder) + self = .messageAnimatedEmoji(value) + case .messageDice: + let value = try MessageDice(from: decoder) + self = .messageDice(value) + case .messageGame: + let value = try MessageGame(from: decoder) + self = .messageGame(value) + case .messagePoll: + let value = try MessagePoll(from: decoder) + self = .messagePoll(value) + case .messageStakeDice: + let value = try MessageStakeDice(from: decoder) + self = .messageStakeDice(value) + case .messageStory: + let value = try MessageStory(from: decoder) + self = .messageStory(value) + case .messageChecklist: + let value = try MessageChecklist(from: decoder) + self = .messageChecklist(value) + case .messageInvoice: + let value = try MessageInvoice(from: decoder) + self = .messageInvoice(value) + case .messageCall: + let value = try MessageCall(from: decoder) + self = .messageCall(value) + case .messageGroupCall: + let value = try MessageGroupCall(from: decoder) + self = .messageGroupCall(value) + case .messageVideoChatScheduled: + let value = try MessageVideoChatScheduled(from: decoder) + self = .messageVideoChatScheduled(value) + case .messageVideoChatStarted: + let value = try MessageVideoChatStarted(from: decoder) + self = .messageVideoChatStarted(value) + case .messageVideoChatEnded: + let value = try MessageVideoChatEnded(from: decoder) + self = .messageVideoChatEnded(value) + case .messageInviteVideoChatParticipants: + let value = try MessageInviteVideoChatParticipants(from: decoder) + self = .messageInviteVideoChatParticipants(value) + case .messagePollOptionAdded: + let value = try MessagePollOptionAdded(from: decoder) + self = .messagePollOptionAdded(value) + case .messagePollOptionDeleted: + let value = try MessagePollOptionDeleted(from: decoder) + self = .messagePollOptionDeleted(value) + case .messageBasicGroupChatCreate: + let value = try MessageBasicGroupChatCreate(from: decoder) + self = .messageBasicGroupChatCreate(value) + case .messageSupergroupChatCreate: + let value = try MessageSupergroupChatCreate(from: decoder) + self = .messageSupergroupChatCreate(value) + case .messageChatChangeTitle: + let value = try MessageChatChangeTitle(from: decoder) + self = .messageChatChangeTitle(value) + case .messageChatChangePhoto: + let value = try MessageChatChangePhoto(from: decoder) + self = .messageChatChangePhoto(value) + case .messageChatDeletePhoto: + self = .messageChatDeletePhoto + case .messageChatOwnerLeft: + let value = try MessageChatOwnerLeft(from: decoder) + self = .messageChatOwnerLeft(value) + case .messageChatOwnerChanged: + let value = try MessageChatOwnerChanged(from: decoder) + self = .messageChatOwnerChanged(value) + case .messageChatHasProtectedContentToggled: + let value = try MessageChatHasProtectedContentToggled(from: decoder) + self = .messageChatHasProtectedContentToggled(value) + case .messageChatHasProtectedContentDisableRequested: + let value = try MessageChatHasProtectedContentDisableRequested(from: decoder) + self = .messageChatHasProtectedContentDisableRequested(value) + case .messageChatAddMembers: + let value = try MessageChatAddMembers(from: decoder) + self = .messageChatAddMembers(value) + case .messageChatJoinByLink: + self = .messageChatJoinByLink + case .messageChatJoinByRequest: + self = .messageChatJoinByRequest + case .messageChatDeleteMember: + let value = try MessageChatDeleteMember(from: decoder) + self = .messageChatDeleteMember(value) + case .messageChatUpgradeTo: + let value = try MessageChatUpgradeTo(from: decoder) + self = .messageChatUpgradeTo(value) + case .messageChatUpgradeFrom: + let value = try MessageChatUpgradeFrom(from: decoder) + self = .messageChatUpgradeFrom(value) + case .messagePinMessage: + let value = try MessagePinMessage(from: decoder) + self = .messagePinMessage(value) + case .messageScreenshotTaken: + self = .messageScreenshotTaken + case .messageChatSetBackground: + let value = try MessageChatSetBackground(from: decoder) + self = .messageChatSetBackground(value) + case .messageChatSetTheme: + let value = try MessageChatSetTheme(from: decoder) + self = .messageChatSetTheme(value) + case .messageChatSetMessageAutoDeleteTime: + let value = try MessageChatSetMessageAutoDeleteTime(from: decoder) + self = .messageChatSetMessageAutoDeleteTime(value) + case .messageChatBoost: + let value = try MessageChatBoost(from: decoder) + self = .messageChatBoost(value) + case .messageForumTopicCreated: + let value = try MessageForumTopicCreated(from: decoder) + self = .messageForumTopicCreated(value) + case .messageForumTopicEdited: + let value = try MessageForumTopicEdited(from: decoder) + self = .messageForumTopicEdited(value) + case .messageForumTopicIsClosedToggled: + let value = try MessageForumTopicIsClosedToggled(from: decoder) + self = .messageForumTopicIsClosedToggled(value) + case .messageForumTopicIsHiddenToggled: + let value = try MessageForumTopicIsHiddenToggled(from: decoder) + self = .messageForumTopicIsHiddenToggled(value) + case .messageSuggestProfilePhoto: + let value = try MessageSuggestProfilePhoto(from: decoder) + self = .messageSuggestProfilePhoto(value) + case .messageSuggestBirthdate: + let value = try MessageSuggestBirthdate(from: decoder) + self = .messageSuggestBirthdate(value) + case .messageCustomServiceAction: + let value = try MessageCustomServiceAction(from: decoder) + self = .messageCustomServiceAction(value) + case .messageGameScore: + let value = try MessageGameScore(from: decoder) + self = .messageGameScore(value) + case .messageManagedBotCreated: + let value = try MessageManagedBotCreated(from: decoder) + self = .messageManagedBotCreated(value) + case .messagePaymentSuccessful: + let value = try MessagePaymentSuccessful(from: decoder) + self = .messagePaymentSuccessful(value) + case .messagePaymentSuccessfulBot: + let value = try MessagePaymentSuccessfulBot(from: decoder) + self = .messagePaymentSuccessfulBot(value) + case .messagePaymentRefunded: + let value = try MessagePaymentRefunded(from: decoder) + self = .messagePaymentRefunded(value) + case .messageGiftedPremium: + let value = try MessageGiftedPremium(from: decoder) + self = .messageGiftedPremium(value) + case .messagePremiumGiftCode: + let value = try MessagePremiumGiftCode(from: decoder) + self = .messagePremiumGiftCode(value) + case .messageGiveawayCreated: + let value = try MessageGiveawayCreated(from: decoder) + self = .messageGiveawayCreated(value) + case .messageGiveaway: + let value = try MessageGiveaway(from: decoder) + self = .messageGiveaway(value) + case .messageGiveawayCompleted: + let value = try MessageGiveawayCompleted(from: decoder) + self = .messageGiveawayCompleted(value) + case .messageGiveawayWinners: + let value = try MessageGiveawayWinners(from: decoder) + self = .messageGiveawayWinners(value) + case .messageGiftedStars: + let value = try MessageGiftedStars(from: decoder) + self = .messageGiftedStars(value) + case .messageGiftedTon: + let value = try MessageGiftedTon(from: decoder) + self = .messageGiftedTon(value) + case .messageGiveawayPrizeStars: + let value = try MessageGiveawayPrizeStars(from: decoder) + self = .messageGiveawayPrizeStars(value) + case .messageGift: + let value = try MessageGift(from: decoder) + self = .messageGift(value) + case .messageUpgradedGift: + let value = try MessageUpgradedGift(from: decoder) + self = .messageUpgradedGift(value) + case .messageRefundedUpgradedGift: + let value = try MessageRefundedUpgradedGift(from: decoder) + self = .messageRefundedUpgradedGift(value) + case .messageUpgradedGiftPurchaseOffer: + let value = try MessageUpgradedGiftPurchaseOffer(from: decoder) + self = .messageUpgradedGiftPurchaseOffer(value) + case .messageUpgradedGiftPurchaseOfferRejected: + let value = try MessageUpgradedGiftPurchaseOfferRejected(from: decoder) + self = .messageUpgradedGiftPurchaseOfferRejected(value) + case .messagePaidMessagesRefunded: + let value = try MessagePaidMessagesRefunded(from: decoder) + self = .messagePaidMessagesRefunded(value) + case .messagePaidMessagePriceChanged: + let value = try MessagePaidMessagePriceChanged(from: decoder) + self = .messagePaidMessagePriceChanged(value) + case .messageDirectMessagePriceChanged: + let value = try MessageDirectMessagePriceChanged(from: decoder) + self = .messageDirectMessagePriceChanged(value) + case .messageChecklistTasksDone: + let value = try MessageChecklistTasksDone(from: decoder) + self = .messageChecklistTasksDone(value) + case .messageChecklistTasksAdded: + let value = try MessageChecklistTasksAdded(from: decoder) + self = .messageChecklistTasksAdded(value) + case .messageSuggestedPostApprovalFailed: + let value = try MessageSuggestedPostApprovalFailed(from: decoder) + self = .messageSuggestedPostApprovalFailed(value) + case .messageSuggestedPostApproved: + let value = try MessageSuggestedPostApproved(from: decoder) + self = .messageSuggestedPostApproved(value) + case .messageSuggestedPostDeclined: + let value = try MessageSuggestedPostDeclined(from: decoder) + self = .messageSuggestedPostDeclined(value) + case .messageSuggestedPostPaid: + let value = try MessageSuggestedPostPaid(from: decoder) + self = .messageSuggestedPostPaid(value) + case .messageSuggestedPostRefunded: + let value = try MessageSuggestedPostRefunded(from: decoder) + self = .messageSuggestedPostRefunded(value) + case .messageContactRegistered: + self = .messageContactRegistered + case .messageUsersShared: + let value = try MessageUsersShared(from: decoder) + self = .messageUsersShared(value) + case .messageChatShared: + let value = try MessageChatShared(from: decoder) + self = .messageChatShared(value) + case .messageBotWriteAccessAllowed: + let value = try MessageBotWriteAccessAllowed(from: decoder) + self = .messageBotWriteAccessAllowed(value) + case .messageWebAppDataSent: + let value = try MessageWebAppDataSent(from: decoder) + self = .messageWebAppDataSent(value) + case .messageWebAppDataReceived: + let value = try MessageWebAppDataReceived(from: decoder) + self = .messageWebAppDataReceived(value) + case .messageProximityAlertTriggered: + let value = try MessageProximityAlertTriggered(from: decoder) + self = .messageProximityAlertTriggered(value) + case .messageUnsupported: + self = .messageUnsupported + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageText(let value): + try container.encode(Kind.messageText, forKey: .type) + try value.encode(to: encoder) + case .messageAnimation(let value): + try container.encode(Kind.messageAnimation, forKey: .type) + try value.encode(to: encoder) + case .messageAudio(let value): + try container.encode(Kind.messageAudio, forKey: .type) + try value.encode(to: encoder) + case .messageDocument(let value): + try container.encode(Kind.messageDocument, forKey: .type) + try value.encode(to: encoder) + case .messagePaidMedia(let value): + try container.encode(Kind.messagePaidMedia, forKey: .type) + try value.encode(to: encoder) + case .messagePhoto(let value): + try container.encode(Kind.messagePhoto, forKey: .type) + try value.encode(to: encoder) + case .messageSticker(let value): + try container.encode(Kind.messageSticker, forKey: .type) + try value.encode(to: encoder) + case .messageVideo(let value): + try container.encode(Kind.messageVideo, forKey: .type) + try value.encode(to: encoder) + case .messageVideoNote(let value): + try container.encode(Kind.messageVideoNote, forKey: .type) + try value.encode(to: encoder) + case .messageVoiceNote(let value): + try container.encode(Kind.messageVoiceNote, forKey: .type) + try value.encode(to: encoder) + case .messageExpiredPhoto: + try container.encode(Kind.messageExpiredPhoto, forKey: .type) + case .messageExpiredVideo: + try container.encode(Kind.messageExpiredVideo, forKey: .type) + case .messageExpiredVideoNote: + try container.encode(Kind.messageExpiredVideoNote, forKey: .type) + case .messageExpiredVoiceNote: + try container.encode(Kind.messageExpiredVoiceNote, forKey: .type) + case .messageLocation(let value): + try container.encode(Kind.messageLocation, forKey: .type) + try value.encode(to: encoder) + case .messageVenue(let value): + try container.encode(Kind.messageVenue, forKey: .type) + try value.encode(to: encoder) + case .messageContact(let value): + try container.encode(Kind.messageContact, forKey: .type) + try value.encode(to: encoder) + case .messageAnimatedEmoji(let value): + try container.encode(Kind.messageAnimatedEmoji, forKey: .type) + try value.encode(to: encoder) + case .messageDice(let value): + try container.encode(Kind.messageDice, forKey: .type) + try value.encode(to: encoder) + case .messageGame(let value): + try container.encode(Kind.messageGame, forKey: .type) + try value.encode(to: encoder) + case .messagePoll(let value): + try container.encode(Kind.messagePoll, forKey: .type) + try value.encode(to: encoder) + case .messageStakeDice(let value): + try container.encode(Kind.messageStakeDice, forKey: .type) + try value.encode(to: encoder) + case .messageStory(let value): + try container.encode(Kind.messageStory, forKey: .type) + try value.encode(to: encoder) + case .messageChecklist(let value): + try container.encode(Kind.messageChecklist, forKey: .type) + try value.encode(to: encoder) + case .messageInvoice(let value): + try container.encode(Kind.messageInvoice, forKey: .type) + try value.encode(to: encoder) + case .messageCall(let value): + try container.encode(Kind.messageCall, forKey: .type) + try value.encode(to: encoder) + case .messageGroupCall(let value): + try container.encode(Kind.messageGroupCall, forKey: .type) + try value.encode(to: encoder) + case .messageVideoChatScheduled(let value): + try container.encode(Kind.messageVideoChatScheduled, forKey: .type) + try value.encode(to: encoder) + case .messageVideoChatStarted(let value): + try container.encode(Kind.messageVideoChatStarted, forKey: .type) + try value.encode(to: encoder) + case .messageVideoChatEnded(let value): + try container.encode(Kind.messageVideoChatEnded, forKey: .type) + try value.encode(to: encoder) + case .messageInviteVideoChatParticipants(let value): + try container.encode(Kind.messageInviteVideoChatParticipants, forKey: .type) + try value.encode(to: encoder) + case .messagePollOptionAdded(let value): + try container.encode(Kind.messagePollOptionAdded, forKey: .type) + try value.encode(to: encoder) + case .messagePollOptionDeleted(let value): + try container.encode(Kind.messagePollOptionDeleted, forKey: .type) + try value.encode(to: encoder) + case .messageBasicGroupChatCreate(let value): + try container.encode(Kind.messageBasicGroupChatCreate, forKey: .type) + try value.encode(to: encoder) + case .messageSupergroupChatCreate(let value): + try container.encode(Kind.messageSupergroupChatCreate, forKey: .type) + try value.encode(to: encoder) + case .messageChatChangeTitle(let value): + try container.encode(Kind.messageChatChangeTitle, forKey: .type) + try value.encode(to: encoder) + case .messageChatChangePhoto(let value): + try container.encode(Kind.messageChatChangePhoto, forKey: .type) + try value.encode(to: encoder) + case .messageChatDeletePhoto: + try container.encode(Kind.messageChatDeletePhoto, forKey: .type) + case .messageChatOwnerLeft(let value): + try container.encode(Kind.messageChatOwnerLeft, forKey: .type) + try value.encode(to: encoder) + case .messageChatOwnerChanged(let value): + try container.encode(Kind.messageChatOwnerChanged, forKey: .type) + try value.encode(to: encoder) + case .messageChatHasProtectedContentToggled(let value): + try container.encode(Kind.messageChatHasProtectedContentToggled, forKey: .type) + try value.encode(to: encoder) + case .messageChatHasProtectedContentDisableRequested(let value): + try container.encode(Kind.messageChatHasProtectedContentDisableRequested, forKey: .type) + try value.encode(to: encoder) + case .messageChatAddMembers(let value): + try container.encode(Kind.messageChatAddMembers, forKey: .type) + try value.encode(to: encoder) + case .messageChatJoinByLink: + try container.encode(Kind.messageChatJoinByLink, forKey: .type) + case .messageChatJoinByRequest: + try container.encode(Kind.messageChatJoinByRequest, forKey: .type) + case .messageChatDeleteMember(let value): + try container.encode(Kind.messageChatDeleteMember, forKey: .type) + try value.encode(to: encoder) + case .messageChatUpgradeTo(let value): + try container.encode(Kind.messageChatUpgradeTo, forKey: .type) + try value.encode(to: encoder) + case .messageChatUpgradeFrom(let value): + try container.encode(Kind.messageChatUpgradeFrom, forKey: .type) + try value.encode(to: encoder) + case .messagePinMessage(let value): + try container.encode(Kind.messagePinMessage, forKey: .type) + try value.encode(to: encoder) + case .messageScreenshotTaken: + try container.encode(Kind.messageScreenshotTaken, forKey: .type) + case .messageChatSetBackground(let value): + try container.encode(Kind.messageChatSetBackground, forKey: .type) + try value.encode(to: encoder) + case .messageChatSetTheme(let value): + try container.encode(Kind.messageChatSetTheme, forKey: .type) + try value.encode(to: encoder) + case .messageChatSetMessageAutoDeleteTime(let value): + try container.encode(Kind.messageChatSetMessageAutoDeleteTime, forKey: .type) + try value.encode(to: encoder) + case .messageChatBoost(let value): + try container.encode(Kind.messageChatBoost, forKey: .type) + try value.encode(to: encoder) + case .messageForumTopicCreated(let value): + try container.encode(Kind.messageForumTopicCreated, forKey: .type) + try value.encode(to: encoder) + case .messageForumTopicEdited(let value): + try container.encode(Kind.messageForumTopicEdited, forKey: .type) + try value.encode(to: encoder) + case .messageForumTopicIsClosedToggled(let value): + try container.encode(Kind.messageForumTopicIsClosedToggled, forKey: .type) + try value.encode(to: encoder) + case .messageForumTopicIsHiddenToggled(let value): + try container.encode(Kind.messageForumTopicIsHiddenToggled, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestProfilePhoto(let value): + try container.encode(Kind.messageSuggestProfilePhoto, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestBirthdate(let value): + try container.encode(Kind.messageSuggestBirthdate, forKey: .type) + try value.encode(to: encoder) + case .messageCustomServiceAction(let value): + try container.encode(Kind.messageCustomServiceAction, forKey: .type) + try value.encode(to: encoder) + case .messageGameScore(let value): + try container.encode(Kind.messageGameScore, forKey: .type) + try value.encode(to: encoder) + case .messageManagedBotCreated(let value): + try container.encode(Kind.messageManagedBotCreated, forKey: .type) + try value.encode(to: encoder) + case .messagePaymentSuccessful(let value): + try container.encode(Kind.messagePaymentSuccessful, forKey: .type) + try value.encode(to: encoder) + case .messagePaymentSuccessfulBot(let value): + try container.encode(Kind.messagePaymentSuccessfulBot, forKey: .type) + try value.encode(to: encoder) + case .messagePaymentRefunded(let value): + try container.encode(Kind.messagePaymentRefunded, forKey: .type) + try value.encode(to: encoder) + case .messageGiftedPremium(let value): + try container.encode(Kind.messageGiftedPremium, forKey: .type) + try value.encode(to: encoder) + case .messagePremiumGiftCode(let value): + try container.encode(Kind.messagePremiumGiftCode, forKey: .type) + try value.encode(to: encoder) + case .messageGiveawayCreated(let value): + try container.encode(Kind.messageGiveawayCreated, forKey: .type) + try value.encode(to: encoder) + case .messageGiveaway(let value): + try container.encode(Kind.messageGiveaway, forKey: .type) + try value.encode(to: encoder) + case .messageGiveawayCompleted(let value): + try container.encode(Kind.messageGiveawayCompleted, forKey: .type) + try value.encode(to: encoder) + case .messageGiveawayWinners(let value): + try container.encode(Kind.messageGiveawayWinners, forKey: .type) + try value.encode(to: encoder) + case .messageGiftedStars(let value): + try container.encode(Kind.messageGiftedStars, forKey: .type) + try value.encode(to: encoder) + case .messageGiftedTon(let value): + try container.encode(Kind.messageGiftedTon, forKey: .type) + try value.encode(to: encoder) + case .messageGiveawayPrizeStars(let value): + try container.encode(Kind.messageGiveawayPrizeStars, forKey: .type) + try value.encode(to: encoder) + case .messageGift(let value): + try container.encode(Kind.messageGift, forKey: .type) + try value.encode(to: encoder) + case .messageUpgradedGift(let value): + try container.encode(Kind.messageUpgradedGift, forKey: .type) + try value.encode(to: encoder) + case .messageRefundedUpgradedGift(let value): + try container.encode(Kind.messageRefundedUpgradedGift, forKey: .type) + try value.encode(to: encoder) + case .messageUpgradedGiftPurchaseOffer(let value): + try container.encode(Kind.messageUpgradedGiftPurchaseOffer, forKey: .type) + try value.encode(to: encoder) + case .messageUpgradedGiftPurchaseOfferRejected(let value): + try container.encode(Kind.messageUpgradedGiftPurchaseOfferRejected, forKey: .type) + try value.encode(to: encoder) + case .messagePaidMessagesRefunded(let value): + try container.encode(Kind.messagePaidMessagesRefunded, forKey: .type) + try value.encode(to: encoder) + case .messagePaidMessagePriceChanged(let value): + try container.encode(Kind.messagePaidMessagePriceChanged, forKey: .type) + try value.encode(to: encoder) + case .messageDirectMessagePriceChanged(let value): + try container.encode(Kind.messageDirectMessagePriceChanged, forKey: .type) + try value.encode(to: encoder) + case .messageChecklistTasksDone(let value): + try container.encode(Kind.messageChecklistTasksDone, forKey: .type) + try value.encode(to: encoder) + case .messageChecklistTasksAdded(let value): + try container.encode(Kind.messageChecklistTasksAdded, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestedPostApprovalFailed(let value): + try container.encode(Kind.messageSuggestedPostApprovalFailed, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestedPostApproved(let value): + try container.encode(Kind.messageSuggestedPostApproved, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestedPostDeclined(let value): + try container.encode(Kind.messageSuggestedPostDeclined, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestedPostPaid(let value): + try container.encode(Kind.messageSuggestedPostPaid, forKey: .type) + try value.encode(to: encoder) + case .messageSuggestedPostRefunded(let value): + try container.encode(Kind.messageSuggestedPostRefunded, forKey: .type) + try value.encode(to: encoder) + case .messageContactRegistered: + try container.encode(Kind.messageContactRegistered, forKey: .type) + case .messageUsersShared(let value): + try container.encode(Kind.messageUsersShared, forKey: .type) + try value.encode(to: encoder) + case .messageChatShared(let value): + try container.encode(Kind.messageChatShared, forKey: .type) + try value.encode(to: encoder) + case .messageBotWriteAccessAllowed(let value): + try container.encode(Kind.messageBotWriteAccessAllowed, forKey: .type) + try value.encode(to: encoder) + case .messageWebAppDataSent(let value): + try container.encode(Kind.messageWebAppDataSent, forKey: .type) + try value.encode(to: encoder) + case .messageWebAppDataReceived(let value): + try container.encode(Kind.messageWebAppDataReceived, forKey: .type) + try value.encode(to: encoder) + case .messageProximityAlertTriggered(let value): + try container.encode(Kind.messageProximityAlertTriggered, forKey: .type) + try value.encode(to: encoder) + case .messageUnsupported: + try container.encode(Kind.messageUnsupported, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A text message +public struct MessageText: Codable, Equatable, Hashable { + + /// A link preview attached to the message; may be null + public let linkPreview: LinkPreview? + + /// Options which were used for generation of the link preview; may be null if default options were used + public let linkPreviewOptions: LinkPreviewOptions? + + /// Text of the message + public let text: FormattedText + + + public init( + linkPreview: LinkPreview?, + linkPreviewOptions: LinkPreviewOptions?, + text: FormattedText + ) { + self.linkPreview = linkPreview + self.linkPreviewOptions = linkPreviewOptions + self.text = text + } +} + +/// An animation message (GIF-style). +public struct MessageAnimation: Codable, Equatable, Hashable { + + /// The animation description + public let animation: Animation + + /// Animation caption + public let caption: FormattedText + + /// True, if the animation preview must be covered by a spoiler animation + public let hasSpoiler: Bool + + /// True, if the animation thumbnail must be blurred and the animation must be shown only while tapped + public let isSecret: Bool + + /// True, if the caption must be shown above the animation; otherwise, the caption must be shown below the animation + public let showCaptionAboveMedia: Bool + + + public init( + animation: Animation, + caption: FormattedText, + hasSpoiler: Bool, + isSecret: Bool, + showCaptionAboveMedia: Bool + ) { + self.animation = animation + self.caption = caption + self.hasSpoiler = hasSpoiler + self.isSecret = isSecret + self.showCaptionAboveMedia = showCaptionAboveMedia + } +} + +/// An audio message +public struct MessageAudio: Codable, Equatable, Hashable { + + /// The audio description + public let audio: Audio + + /// Audio caption + public let caption: FormattedText + + + public init( + audio: Audio, + caption: FormattedText + ) { + self.audio = audio + self.caption = caption + } +} + +/// A document message (general file) +public struct MessageDocument: Codable, Equatable, Hashable { + + /// Document caption + public let caption: FormattedText + + /// The document description + public let document: Document + + + public init( + caption: FormattedText, + document: Document + ) { + self.caption = caption + self.document = document + } +} + +/// A message with paid media +public struct MessagePaidMedia: Codable, Equatable, Hashable { + + /// Media caption + public let caption: FormattedText + + /// Information about the media + public let media: [PaidMedia] + + /// True, if the caption must be shown above the media; otherwise, the caption must be shown below the media + public let showCaptionAboveMedia: Bool + + /// Number of Telegram Stars needed to buy access to the media in the message + public let starCount: Int64 + + + public init( + caption: FormattedText, + media: [PaidMedia], + showCaptionAboveMedia: Bool, + starCount: Int64 + ) { + self.caption = caption + self.media = media + self.showCaptionAboveMedia = showCaptionAboveMedia + self.starCount = starCount + } +} + +/// A photo message +public struct MessagePhoto: Codable, Equatable, Hashable { + + /// Photo caption + public let caption: FormattedText + + /// True, if the photo preview must be covered by a spoiler animation + public let hasSpoiler: Bool + + /// True, if the photo must be blurred and must be shown only while tapped + public let isSecret: Bool + + /// The photo + public let photo: Photo + + /// True, if the caption must be shown above the photo; otherwise, the caption must be shown below the photo + public let showCaptionAboveMedia: Bool + + /// The video representing the live photo; may be null if the photo is static + public let video: Video? + + + public init( + caption: FormattedText, + hasSpoiler: Bool, + isSecret: Bool, + photo: Photo, + showCaptionAboveMedia: Bool, + video: Video? + ) { + self.caption = caption + self.hasSpoiler = hasSpoiler + self.isSecret = isSecret + self.photo = photo + self.showCaptionAboveMedia = showCaptionAboveMedia + self.video = video + } +} + +/// A sticker message +public struct MessageSticker: Codable, Equatable, Hashable { + + /// True, if premium animation of the sticker must be played + public let isPremium: Bool + + /// The sticker description + public let sticker: Sticker + + + public init( + isPremium: Bool, + sticker: Sticker + ) { + self.isPremium = isPremium + self.sticker = sticker + } +} + +/// A video message +public struct MessageVideo: Codable, Equatable, Hashable { + + /// Alternative qualities of the video + public let alternativeVideos: [AlternativeVideo] + + /// Video caption + public let caption: FormattedText + + /// Cover of the video; may be null if none + public let cover: Photo? + + /// True, if the video preview must be covered by a spoiler animation + public let hasSpoiler: Bool + + /// True, if the video thumbnail must be blurred and the video must be shown only while tapped + public let isSecret: Bool + + /// True, if the caption must be shown above the video; otherwise, the caption must be shown below the video + public let showCaptionAboveMedia: Bool + + /// Timestamp from which the video playing must start, in seconds + public let startTimestamp: Int + + /// Available storyboards for the video + public let storyboards: [VideoStoryboard] + + /// The video description + public let video: Video + + + public init( + alternativeVideos: [AlternativeVideo], + caption: FormattedText, + cover: Photo?, + hasSpoiler: Bool, + isSecret: Bool, + showCaptionAboveMedia: Bool, + startTimestamp: Int, + storyboards: [VideoStoryboard], + video: Video + ) { + self.alternativeVideos = alternativeVideos + self.caption = caption + self.cover = cover + self.hasSpoiler = hasSpoiler + self.isSecret = isSecret + self.showCaptionAboveMedia = showCaptionAboveMedia + self.startTimestamp = startTimestamp + self.storyboards = storyboards + self.video = video + } +} + +/// A video note message +public struct MessageVideoNote: Codable, Equatable, Hashable { + + /// True, if the video note thumbnail must be blurred and the video note must be shown only while tapped + public let isSecret: Bool + + /// True, if at least one of the recipients has viewed the video note + public let isViewed: Bool + + /// The video note description + public let videoNote: VideoNote + + + public init( + isSecret: Bool, + isViewed: Bool, + videoNote: VideoNote + ) { + self.isSecret = isSecret + self.isViewed = isViewed + self.videoNote = videoNote + } +} + +/// A voice note message +public struct MessageVoiceNote: Codable, Equatable, Hashable { + + /// Voice note caption + public let caption: FormattedText + + /// True, if at least one of the recipients has listened to the voice note + public let isListened: Bool + + /// The voice note description + public let voiceNote: VoiceNote + + + public init( + caption: FormattedText, + isListened: Bool, + voiceNote: VoiceNote + ) { + self.caption = caption + self.isListened = isListened + self.voiceNote = voiceNote + } +} + +/// A message with a location +public struct MessageLocation: Codable, Equatable, Hashable { + + /// Left time for which the location can be updated, in seconds. If 0, then the location can't be updated anymore. The update updateMessageContent is not sent when this field changes + public let expiresIn: Int + + /// For live locations, a direction in which the location moves, in degrees; 1-360. If 0 the direction is unknown + public let heading: Int + + /// Time relative to the message send date, for which the location can be updated, in seconds; if 0x7FFFFFFF, then location can be updated forever + public let livePeriod: Int + + /// The location description + public let location: Location + + /// For live locations, a maximum distance to another chat member for proximity alerts, in meters (0-100000). 0 if the notification is disabled. Available only to the message sender + public let proximityAlertRadius: Int + + + public init( + expiresIn: Int, + heading: Int, + livePeriod: Int, + location: Location, + proximityAlertRadius: Int + ) { + self.expiresIn = expiresIn + self.heading = heading + self.livePeriod = livePeriod + self.location = location + self.proximityAlertRadius = proximityAlertRadius + } +} + +/// A message with information about a venue +public struct MessageVenue: Codable, Equatable, Hashable { + + /// The venue description + public let venue: Venue + + + public init(venue: Venue) { + self.venue = venue + } +} + +/// A message with a user contact +public struct MessageContact: Codable, Equatable, Hashable { + + /// The contact description + public let contact: Contact + + + public init(contact: Contact) { + self.contact = contact + } +} + +/// A message with an animated emoji +public struct MessageAnimatedEmoji: Codable, Equatable, Hashable { + + /// The animated emoji + public let animatedEmoji: AnimatedEmoji + + /// The corresponding emoji + public let emoji: String + + + public init( + animatedEmoji: AnimatedEmoji, + emoji: String + ) { + self.animatedEmoji = animatedEmoji + self.emoji = emoji + } +} + +/// A dice message. The dice value is randomly generated by the server +public struct MessageDice: Codable, Equatable, Hashable { + + /// Emoji on which the dice throw animation is based + public let emoji: String + + /// The animated stickers with the final dice animation; may be null if unknown. The update updateMessageContent will be sent when the sticker became known + public let finalState: DiceStickers? + + /// The animated stickers with the initial dice animation; may be null if unknown. The update updateMessageContent will be sent when the sticker became known + public let initialState: DiceStickers? + + /// Number of frame after which a success animation like a shower of confetti needs to be shown on updateMessageSendSucceeded + public let successAnimationFrameNumber: Int + + /// The dice value. If the value is 0, then the dice don't have final state yet + public let value: Int + + + public init( + emoji: String, + finalState: DiceStickers?, + initialState: DiceStickers?, + successAnimationFrameNumber: Int, + value: Int + ) { + self.emoji = emoji + self.finalState = finalState + self.initialState = initialState + self.successAnimationFrameNumber = successAnimationFrameNumber + self.value = value + } +} + +/// A message with a game +public struct MessageGame: Codable, Equatable, Hashable { + + /// The game description + public let game: Game + + + public init(game: Game) { + self.game = game + } +} + +/// A message with a poll +public struct MessagePoll: Codable, Equatable, Hashable { + + /// True, if an option can be added to the poll using addPollOption + public let canAddOption: Bool + + public let description: FormattedText + + /// Media attached to the poll; may be null if none. If present, currently, can be only of the types messageAnimation, messageAudio, messageDocument, messageLocation, messagePhoto, messageVenue, or messageVideo without caption + public let media: MessageContent? + + /// Information about the poll + public let poll: Poll + + + public init( + canAddOption: Bool, + description: FormattedText, + media: MessageContent?, + poll: Poll + ) { + self.canAddOption = canAddOption + self.description = description + self.media = media + self.poll = poll + } +} + +/// A stake dice message. The dice value is randomly generated by the server +public struct MessageStakeDice: Codable, Equatable, Hashable { + + /// The animated stickers with the final dice animation; may be null if unknown. The update updateMessageContent will be sent when the sticker became known + public let finalState: DiceStickers? + + /// The animated stickers with the initial dice animation; may be null if unknown. The update updateMessageContent will be sent when the sticker became known + public let initialState: DiceStickers? + + /// The Toncoin amount that was gained from the roll; in the smallest units of the currency; -1 if the dice don't have final state yet + public let prizeToncoinAmount: Int64 + + /// The Toncoin amount that was staked; in the smallest units of the currency + public let stakeToncoinAmount: Int64 + + /// The dice value. If the value is 0, then the dice don't have final state yet + public let value: Int + + + public init( + finalState: DiceStickers?, + initialState: DiceStickers?, + prizeToncoinAmount: Int64, + stakeToncoinAmount: Int64, + value: Int + ) { + self.finalState = finalState + self.initialState = initialState + self.prizeToncoinAmount = prizeToncoinAmount + self.stakeToncoinAmount = stakeToncoinAmount + self.value = value + } +} + +/// A message with a forwarded story +public struct MessageStory: Codable, Equatable, Hashable { + + /// Story identifier + public let storyId: Int + + /// Identifier of the chat that posted the story + public let storyPosterChatId: Int64 + + /// True, if the story was automatically forwarded because of a mention of the user + public let viaMention: Bool + + + public init( + storyId: Int, + storyPosterChatId: Int64, + viaMention: Bool + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + self.viaMention = viaMention + } +} + +/// A message with a checklist +public struct MessageChecklist: Codable, Equatable, Hashable { + + /// The checklist description + public let list: Checklist + + + public init(list: Checklist) { + self.list = list + } +} + +/// A message with an invoice from a bot. Use getInternalLink with internalLinkTypeBotStart to share the invoice +public struct MessageInvoice: Codable, Equatable, Hashable { + + /// Currency for the product price + public let currency: String + + /// True, if the invoice is a test invoice + public let isTest: Bool + + /// True, if the shipping address must be specified + public let needShippingAddress: Bool + + /// Extended media attached to the invoice; may be null if none + public let paidMedia: PaidMedia? + + /// Extended media caption; may be null if none + public let paidMediaCaption: FormattedText? + + /// Information about the product + public let productInfo: ProductInfo + + /// The identifier of the message with the receipt, after the product has been purchased + public let receiptMessageId: Int64 + + /// Unique invoice bot start_parameter to be passed to getInternalLink + public let startParameter: String + + /// Product total price in the smallest units of the currency + public let totalAmount: Int64 + + + public init( + currency: String, + isTest: Bool, + needShippingAddress: Bool, + paidMedia: PaidMedia?, + paidMediaCaption: FormattedText?, + productInfo: ProductInfo, + receiptMessageId: Int64, + startParameter: String, + totalAmount: Int64 + ) { + self.currency = currency + self.isTest = isTest + self.needShippingAddress = needShippingAddress + self.paidMedia = paidMedia + self.paidMediaCaption = paidMediaCaption + self.productInfo = productInfo + self.receiptMessageId = receiptMessageId + self.startParameter = startParameter + self.totalAmount = totalAmount + } +} + +/// A message with information about an ended call +public struct MessageCall: Codable, Equatable, Hashable { + + /// Reason why the call was discarded + public let discardReason: CallDiscardReason + + /// Call duration, in seconds + public let duration: Int + + /// True, if the call was a video call + public let isVideo: Bool + + /// Persistent unique call identifier; 0 for calls from other devices, which can't be passed as inputCallFromMessage + public let uniqueId: TdInt64 + + + public init( + discardReason: CallDiscardReason, + duration: Int, + isVideo: Bool, + uniqueId: TdInt64 + ) { + self.discardReason = discardReason + self.duration = duration + self.isVideo = isVideo + self.uniqueId = uniqueId + } +} + +/// A message with information about a group call not bound to a chat. If the message is incoming, the call isn't active, isn't missed, and has no duration, and getOption("can_accept_calls") is true, then incoming call screen must be shown to the user. Use getGroupCallParticipants to show current group call participants on the screen. Use joinGroupCall to accept the call or declineGroupCallInvitation to decline it. If the call become active or missed, then the call screen must be hidden +public struct MessageGroupCall: Codable, Equatable, Hashable { + + /// Call duration, in seconds; for left calls only + public let duration: Int + + /// True, if the call is active, i.e. the called user joined the call + public let isActive: Bool + + /// True, if the call is a video call + public let isVideo: Bool + + /// Identifiers of some other call participants + public let otherParticipantIds: [MessageSender] + + /// Persistent unique group call identifier + public let uniqueId: TdInt64 + + /// True, if the called user missed or declined the call + public let wasMissed: Bool + + + public init( + duration: Int, + isActive: Bool, + isVideo: Bool, + otherParticipantIds: [MessageSender], + uniqueId: TdInt64, + wasMissed: Bool + ) { + self.duration = duration + self.isActive = isActive + self.isVideo = isVideo + self.otherParticipantIds = otherParticipantIds + self.uniqueId = uniqueId + self.wasMissed = wasMissed + } +} + +/// A new video chat was scheduled +public struct MessageVideoChatScheduled: Codable, Equatable, Hashable { + + /// Identifier of the video chat. The video chat can be received through the method getGroupCall + public let groupCallId: Int + + /// Point in time (Unix timestamp) when the group call is expected to be started by an administrator + public let startDate: Int + + + public init( + groupCallId: Int, + startDate: Int + ) { + self.groupCallId = groupCallId + self.startDate = startDate + } +} + +/// A newly created video chat +public struct MessageVideoChatStarted: Codable, Equatable, Hashable { + + /// Identifier of the video chat. The video chat can be received through the method getGroupCall + public let groupCallId: Int + + + public init(groupCallId: Int) { + self.groupCallId = groupCallId + } +} + +/// A message with information about an ended video chat +public struct MessageVideoChatEnded: Codable, Equatable, Hashable { + + /// Call duration, in seconds + public let duration: Int + + + public init(duration: Int) { + self.duration = duration + } +} + +/// A message with information about an invitation to a video chat +public struct MessageInviteVideoChatParticipants: Codable, Equatable, Hashable { + + /// Identifier of the video chat. The video chat can be received through the method getGroupCall + public let groupCallId: Int + + /// Invited user identifiers + public let userIds: [Int64] + + + public init( + groupCallId: Int, + userIds: [Int64] + ) { + self.groupCallId = groupCallId + self.userIds = userIds + } +} + +/// A message with information about an added poll option +public struct MessagePollOptionAdded: Codable, Equatable, Hashable { + + /// Identifier of the added option in the poll + public let optionId: String + + /// Identifier of the message with the poll; can be an identifier of a deleted message or 0 + public let pollMessageId: Int64 + + /// Text of the option; 1-100 characters; may contain only custom emoji entities + public let text: FormattedText + + + public init( + optionId: String, + pollMessageId: Int64, + text: FormattedText + ) { + self.optionId = optionId + self.pollMessageId = pollMessageId + self.text = text + } +} + +/// A message with information about a deleted poll option +public struct MessagePollOptionDeleted: Codable, Equatable, Hashable { + + /// Identifier of the deleted option in the poll + public let optionId: String + + /// Identifier of the message with the poll; can be an identifier of a deleted message or 0 + public let pollMessageId: Int64 + + /// Text of the option; 1-100 characters; may contain only custom emoji entities + public let text: FormattedText + + + public init( + optionId: String, + pollMessageId: Int64, + text: FormattedText + ) { + self.optionId = optionId + self.pollMessageId = pollMessageId + self.text = text + } +} + +/// A newly created basic group +public struct MessageBasicGroupChatCreate: Codable, Equatable, Hashable { + + /// User identifiers of members in the basic group + public let memberUserIds: [Int64] + + /// Title of the basic group + public let title: String + + + public init( + memberUserIds: [Int64], + title: String + ) { + self.memberUserIds = memberUserIds + self.title = title + } +} + +/// A newly created supergroup or channel +public struct MessageSupergroupChatCreate: Codable, Equatable, Hashable { + + /// Title of the supergroup or channel + public let title: String + + + public init(title: String) { + self.title = title + } +} + +/// An updated chat title +public struct MessageChatChangeTitle: Codable, Equatable, Hashable { + + /// New chat title + public let title: String + + + public init(title: String) { + self.title = title + } +} + +/// An updated chat photo +public struct MessageChatChangePhoto: Codable, Equatable, Hashable { + + /// New chat photo + public let photo: ChatPhoto + + + public init(photo: ChatPhoto) { + self.photo = photo + } +} + +/// The owner of the chat has left +public struct MessageChatOwnerLeft: Codable, Equatable, Hashable { + + /// Identifier of the user who will become the new owner of the chat if the previous owner isn't return; 0 if none + public let newOwnerUserId: Int64 + + + public init(newOwnerUserId: Int64) { + self.newOwnerUserId = newOwnerUserId + } +} + +/// The owner of the chat has changed +public struct MessageChatOwnerChanged: Codable, Equatable, Hashable { + + /// Identifier of the user who is the new owner of the chat + public let newOwnerUserId: Int64 + + + public init(newOwnerUserId: Int64) { + self.newOwnerUserId = newOwnerUserId + } +} + +/// Chat has_protected_content setting was changed or request to change it was rejected +public struct MessageChatHasProtectedContentToggled: Codable, Equatable, Hashable { + + /// New value of the setting + public let newHasProtectedContent: Bool + + /// Previous value of the setting + public let oldHasProtectedContent: Bool + + /// Identifier of the message with the request to change the setting; can be an identifier of a deleted message or 0 + public let requestMessageId: Int64 + + + public init( + newHasProtectedContent: Bool, + oldHasProtectedContent: Bool, + requestMessageId: Int64 + ) { + self.newHasProtectedContent = newHasProtectedContent + self.oldHasProtectedContent = oldHasProtectedContent + self.requestMessageId = requestMessageId + } +} + +/// Chat has_protected_content setting was requested to be disabled +public struct MessageChatHasProtectedContentDisableRequested: Codable, Equatable, Hashable { + + /// True, if the request has expired + public let isExpired: Bool + + + public init(isExpired: Bool) { + self.isExpired = isExpired + } +} + +/// New chat members were added +public struct MessageChatAddMembers: Codable, Equatable, Hashable { + + /// User identifiers of the new members + public let memberUserIds: [Int64] + + + public init(memberUserIds: [Int64]) { + self.memberUserIds = memberUserIds + } +} + +/// A chat member was deleted +public struct MessageChatDeleteMember: Codable, Equatable, Hashable { + + /// User identifier of the deleted chat member + public let userId: Int64 + + + public init(userId: Int64) { + self.userId = userId + } +} + +/// A basic group was upgraded to a supergroup and was deactivated as the result +public struct MessageChatUpgradeTo: Codable, Equatable, Hashable { + + /// Identifier of the supergroup to which the basic group was upgraded + public let supergroupId: Int64 + + + public init(supergroupId: Int64) { + self.supergroupId = supergroupId + } +} + +/// A supergroup has been created from a basic group +public struct MessageChatUpgradeFrom: Codable, Equatable, Hashable { + + /// The identifier of the original basic group + public let basicGroupId: Int64 + + /// Title of the newly created supergroup + public let title: String + + + public init( + basicGroupId: Int64, + title: String + ) { + self.basicGroupId = basicGroupId + self.title = title + } +} + +/// A message has been pinned +public struct MessagePinMessage: Codable, Equatable, Hashable { + + /// Identifier of the pinned message, can be an identifier of a deleted message or 0 + public let messageId: Int64 + + + public init(messageId: Int64) { + self.messageId = messageId + } +} + +/// A new background was set in the chat +public struct MessageChatSetBackground: Codable, Equatable, Hashable { + + /// The new background + public let background: ChatBackground + + /// Identifier of the message with a previously set same background; 0 if none. Can be an identifier of a deleted message + public let oldBackgroundMessageId: Int64 + + /// True, if the background was set only for self + public let onlyForSelf: Bool + + + public init( + background: ChatBackground, + oldBackgroundMessageId: Int64, + onlyForSelf: Bool + ) { + self.background = background + self.oldBackgroundMessageId = oldBackgroundMessageId + self.onlyForSelf = onlyForSelf + } +} + +/// A theme in the chat has been changed +public struct MessageChatSetTheme: Codable, Equatable, Hashable { + + /// New theme for the chat; may be null if chat theme was reset to the default one + public let theme: ChatTheme? + + + public init(theme: ChatTheme?) { + self.theme = theme + } +} + +/// The auto-delete or self-destruct timer for messages in the chat has been changed +public struct MessageChatSetMessageAutoDeleteTime: Codable, Equatable, Hashable { + + /// If not 0, a user identifier, which default setting was automatically applied + public let fromUserId: Int64 + + /// New value auto-delete or self-destruct time, in seconds; 0 if disabled + public let messageAutoDeleteTime: Int + + + public init( + fromUserId: Int64, + messageAutoDeleteTime: Int + ) { + self.fromUserId = fromUserId + self.messageAutoDeleteTime = messageAutoDeleteTime + } +} + +/// The chat was boosted by the sender of the message +public struct MessageChatBoost: Codable, Equatable, Hashable { + + /// Number of times the chat was boosted + public let boostCount: Int + + + public init(boostCount: Int) { + self.boostCount = boostCount + } +} + +/// A forum topic has been created +public struct MessageForumTopicCreated: Codable, Equatable, Hashable { + + /// Icon of the topic + public let icon: ForumTopicIcon + + /// True, if the name of the topic wasn't added explicitly + public let isNameImplicit: Bool + + /// Name of the topic + public let name: String + + + public init( + icon: ForumTopicIcon, + isNameImplicit: Bool, + name: String + ) { + self.icon = icon + self.isNameImplicit = isNameImplicit + self.name = name + } +} + +/// A forum topic has been edited +public struct MessageForumTopicEdited: Codable, Equatable, Hashable { + + /// True, if icon's custom_emoji_id is changed + public let editIconCustomEmojiId: Bool + + /// New unique identifier of the custom emoji shown on the topic icon; 0 if none. Must be ignored if edit_icon_custom_emoji_id is false + public let iconCustomEmojiId: TdInt64 + + /// If non-empty, the new name of the topic + public let name: String + + + public init( + editIconCustomEmojiId: Bool, + iconCustomEmojiId: TdInt64, + name: String + ) { + self.editIconCustomEmojiId = editIconCustomEmojiId + self.iconCustomEmojiId = iconCustomEmojiId + self.name = name + } +} + +/// A forum topic has been closed or opened +public struct MessageForumTopicIsClosedToggled: Codable, Equatable, Hashable { + + /// True, if the topic was closed; otherwise, the topic was reopened + public let isClosed: Bool + + + public init(isClosed: Bool) { + self.isClosed = isClosed + } +} + +/// A General forum topic has been hidden or unhidden +public struct MessageForumTopicIsHiddenToggled: Codable, Equatable, Hashable { + + /// True, if the topic was hidden; otherwise, the topic was unhidden + public let isHidden: Bool + + + public init(isHidden: Bool) { + self.isHidden = isHidden + } +} + +/// A profile photo was suggested to a user in a private chat +public struct MessageSuggestProfilePhoto: Codable, Equatable, Hashable { + + /// The suggested chat photo. Use the method setProfilePhoto with inputChatPhotoPrevious to apply the photo + public let photo: ChatPhoto + + + public init(photo: ChatPhoto) { + self.photo = photo + } +} + +/// A birthdate was suggested to be set +public struct MessageSuggestBirthdate: Codable, Equatable, Hashable { + + /// The suggested birthdate. Use the method setBirthdate to apply the birthdate + public let birthdate: Birthdate + + + public init(birthdate: Birthdate) { + self.birthdate = birthdate + } +} + +/// A non-standard action has happened in the chat +public struct MessageCustomServiceAction: Codable, Equatable, Hashable { + + /// Message text to be shown in the chat + public let text: String + + + public init(text: String) { + self.text = text + } +} + +/// A new high score was achieved in a game +public struct MessageGameScore: Codable, Equatable, Hashable { + + /// Identifier of the game; may be different from the games presented in the message with the game + public let gameId: TdInt64 + + /// Identifier of the message with the game, can be an identifier of a deleted message + public let gameMessageId: Int64 + + /// New score + public let score: Int + + + public init( + gameId: TdInt64, + gameMessageId: Int64, + score: Int + ) { + self.gameId = gameId + self.gameMessageId = gameMessageId + self.score = score + } +} + +/// A bot managed by another bot was created by the user +public struct MessageManagedBotCreated: Codable, Equatable, Hashable { + + /// User identifier of the created bot + public let botUserId: Int64 + + + public init(botUserId: Int64) { + self.botUserId = botUserId + } +} + +/// A payment has been sent to a bot or a business account +public struct MessagePaymentSuccessful: Codable, Equatable, Hashable { + + /// Currency for the price of the product + public let currency: String + + /// Identifier of the chat, containing the corresponding invoice message + public let invoiceChatId: Int64 + + /// Identifier of the message with the corresponding invoice; may be 0 or an identifier of a deleted message + public let invoiceMessageId: Int64 + + /// Name of the invoice; may be empty if unknown + public let invoiceName: String + + /// True, if this is the first recurring payment + public let isFirstRecurring: Bool + + /// True, if this is a recurring payment + public let isRecurring: Bool + + /// Point in time (Unix timestamp) when the subscription will expire; 0 if unknown or the payment isn't recurring + public let subscriptionUntilDate: Int + + /// Total price for the product, in the smallest units of the currency + public let totalAmount: Int64 + + + public init( + currency: String, + invoiceChatId: Int64, + invoiceMessageId: Int64, + invoiceName: String, + isFirstRecurring: Bool, + isRecurring: Bool, + subscriptionUntilDate: Int, + totalAmount: Int64 + ) { + self.currency = currency + self.invoiceChatId = invoiceChatId + self.invoiceMessageId = invoiceMessageId + self.invoiceName = invoiceName + self.isFirstRecurring = isFirstRecurring + self.isRecurring = isRecurring + self.subscriptionUntilDate = subscriptionUntilDate + self.totalAmount = totalAmount + } +} + +/// A payment has been received by the bot or the business account +public struct MessagePaymentSuccessfulBot: Codable, Equatable, Hashable { + + /// Currency for price of the product + public let currency: String + + /// Invoice payload + public let invoicePayload: Data + + /// True, if this is the first recurring payment + public let isFirstRecurring: Bool + + /// True, if this is a recurring payment + public let isRecurring: Bool + + /// Information about the order; may be null; for bots only + public let orderInfo: OrderInfo? + + /// Provider payment identifier + public let providerPaymentChargeId: String + + /// Identifier of the shipping option chosen by the user; may be empty if not applicable; for bots only + public let shippingOptionId: String + + /// Point in time (Unix timestamp) when the subscription will expire; 0 if unknown or the payment isn't recurring + public let subscriptionUntilDate: Int + + /// Telegram payment identifier + public let telegramPaymentChargeId: String + + /// Total price for the product, in the smallest units of the currency + public let totalAmount: Int64 + + + public init( + currency: String, + invoicePayload: Data, + isFirstRecurring: Bool, + isRecurring: Bool, + orderInfo: OrderInfo?, + providerPaymentChargeId: String, + shippingOptionId: String, + subscriptionUntilDate: Int, + telegramPaymentChargeId: String, + totalAmount: Int64 + ) { + self.currency = currency + self.invoicePayload = invoicePayload + self.isFirstRecurring = isFirstRecurring + self.isRecurring = isRecurring + self.orderInfo = orderInfo + self.providerPaymentChargeId = providerPaymentChargeId + self.shippingOptionId = shippingOptionId + self.subscriptionUntilDate = subscriptionUntilDate + self.telegramPaymentChargeId = telegramPaymentChargeId + self.totalAmount = totalAmount + } +} + +/// A payment has been refunded +public struct MessagePaymentRefunded: Codable, Equatable, Hashable { + + /// Currency for the price of the product + public let currency: String + + /// Invoice payload; only for bots + public let invoicePayload: Data + + /// Identifier of the previous owner of the Telegram Stars that refunds them + public let ownerId: MessageSender + + /// Provider payment identifier + public let providerPaymentChargeId: String + + /// Telegram payment identifier + public let telegramPaymentChargeId: String + + /// Total price for the product, in the smallest units of the currency + public let totalAmount: Int64 + + + public init( + currency: String, + invoicePayload: Data, + ownerId: MessageSender, + providerPaymentChargeId: String, + telegramPaymentChargeId: String, + totalAmount: Int64 + ) { + self.currency = currency + self.invoicePayload = invoicePayload + self.ownerId = ownerId + self.providerPaymentChargeId = providerPaymentChargeId + self.telegramPaymentChargeId = telegramPaymentChargeId + self.totalAmount = totalAmount + } +} + +/// Telegram Premium was gifted to a user +public struct MessageGiftedPremium: Codable, Equatable, Hashable { + + /// The paid amount, in the smallest units of the currency + public let amount: Int64 + + /// Cryptocurrency used to pay for the gift; may be empty if none + public let cryptocurrency: String + + /// The paid amount, in the smallest units of the cryptocurrency; 0 if none + public let cryptocurrencyAmount: TdInt64 + + /// Currency for the paid amount + public let currency: String + + /// Number of days the Telegram Premium subscription will be active + public let dayCount: Int + + /// The identifier of a user who gifted Telegram Premium; 0 if the gift was anonymous or is outgoing + public let gifterUserId: Int64 + + /// Number of months the Telegram Premium subscription will be active after code activation; 0 if the number of months isn't integer + public let monthCount: Int + + /// The identifier of a user who received Telegram Premium; 0 if the gift is incoming + public let receiverUserId: Int64 + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// Message added to the gifted Telegram Premium by the sender + public let text: FormattedText + + + public init( + amount: Int64, + cryptocurrency: String, + cryptocurrencyAmount: TdInt64, + currency: String, + dayCount: Int, + gifterUserId: Int64, + monthCount: Int, + receiverUserId: Int64, + sticker: Sticker?, + text: FormattedText + ) { + self.amount = amount + self.cryptocurrency = cryptocurrency + self.cryptocurrencyAmount = cryptocurrencyAmount + self.currency = currency + self.dayCount = dayCount + self.gifterUserId = gifterUserId + self.monthCount = monthCount + self.receiverUserId = receiverUserId + self.sticker = sticker + self.text = text + } +} + +/// A Telegram Premium gift code was created for the user +public struct MessagePremiumGiftCode: Codable, Equatable, Hashable { + + /// The paid amount, in the smallest units of the currency; 0 if unknown + public let amount: Int64 + + /// The gift code + public let code: String + + /// Identifier of a chat or a user who created the gift code; may be null if unknown + public let creatorId: MessageSender? + + /// Cryptocurrency used to pay for the gift; may be empty if none or unknown + public let cryptocurrency: String + + /// The paid amount, in the smallest units of the cryptocurrency; 0 if unknown + public let cryptocurrencyAmount: TdInt64 + + /// Currency for the paid amount; empty if unknown + public let currency: String + + /// Number of days the Telegram Premium subscription will be active after code activation + public let dayCount: Int + + /// True, if the gift code was created for a giveaway + public let isFromGiveaway: Bool + + /// True, if the winner for the corresponding Telegram Premium subscription wasn't chosen + public let isUnclaimed: Bool + + /// Number of months the Telegram Premium subscription will be active after code activation; 0 if the number of months isn't integer + public let monthCount: Int + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// Message added to the gift + public let text: FormattedText + + + public init( + amount: Int64, + code: String, + creatorId: MessageSender?, + cryptocurrency: String, + cryptocurrencyAmount: TdInt64, + currency: String, + dayCount: Int, + isFromGiveaway: Bool, + isUnclaimed: Bool, + monthCount: Int, + sticker: Sticker?, + text: FormattedText + ) { + self.amount = amount + self.code = code + self.creatorId = creatorId + self.cryptocurrency = cryptocurrency + self.cryptocurrencyAmount = cryptocurrencyAmount + self.currency = currency + self.dayCount = dayCount + self.isFromGiveaway = isFromGiveaway + self.isUnclaimed = isUnclaimed + self.monthCount = monthCount + self.sticker = sticker + self.text = text + } +} + +/// A giveaway was created for the chat. Use telegramPaymentPurposePremiumGiveaway, storePaymentPurposePremiumGiveaway, telegramPaymentPurposeStarGiveaway, or storePaymentPurposeStarGiveaway to create a giveaway +public struct MessageGiveawayCreated: Codable, Equatable, Hashable { + + /// Number of Telegram Stars that will be shared by winners of the giveaway; 0 for Telegram Premium giveaways + public let starCount: Int64 + + + public init(starCount: Int64) { + self.starCount = starCount + } +} + +/// A giveaway +public struct MessageGiveaway: Codable, Equatable, Hashable { + + /// Giveaway parameters + public let parameters: GiveawayParameters + + /// Prize of the giveaway + public let prize: GiveawayPrize + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// Number of users which will receive Telegram Premium subscription gift codes + public let winnerCount: Int + + + public init( + parameters: GiveawayParameters, + prize: GiveawayPrize, + sticker: Sticker?, + winnerCount: Int + ) { + self.parameters = parameters + self.prize = prize + self.sticker = sticker + self.winnerCount = winnerCount + } +} + +/// A giveaway without public winners has been completed for the chat +public struct MessageGiveawayCompleted: Codable, Equatable, Hashable { + + /// Identifier of the message with the giveaway; may be 0 or an identifier of a deleted message + public let giveawayMessageId: Int64 + + /// True, if the giveaway is a Telegram Star giveaway + public let isStarGiveaway: Bool + + /// Number of undistributed prizes; for Telegram Premium giveaways only + public let unclaimedPrizeCount: Int + + /// Number of winners in the giveaway + public let winnerCount: Int + + + public init( + giveawayMessageId: Int64, + isStarGiveaway: Bool, + unclaimedPrizeCount: Int, + winnerCount: Int + ) { + self.giveawayMessageId = giveawayMessageId + self.isStarGiveaway = isStarGiveaway + self.unclaimedPrizeCount = unclaimedPrizeCount + self.winnerCount = winnerCount + } +} + +/// A giveaway with public winners has been completed for the chat +public struct MessageGiveawayWinners: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the winners were selected. May be bigger than winners selection date specified in parameters of the giveaway + public let actualWinnersSelectionDate: Int + + /// Number of other chats that participated in the giveaway + public let additionalChatCount: Int + + /// Identifier of the supergroup or channel chat, which was automatically boosted by the winners of the giveaway + public let boostedChatId: Int64 + + /// Identifier of the message with the giveaway in the boosted chat + public let giveawayMessageId: Int64 + + /// True, if only new members of the chats were eligible for the giveaway + public let onlyNewMembers: Bool + + /// Prize of the giveaway + public let prize: GiveawayPrize + + /// Additional description of the giveaway prize + public let prizeDescription: String + + /// Number of undistributed prizes; for Telegram Premium giveaways only + public let unclaimedPrizeCount: Int + + /// True, if the giveaway was canceled and was fully refunded + public let wasRefunded: Bool + + /// Total number of winners in the giveaway + public let winnerCount: Int + + /// Up to 100 user identifiers of the winners of the giveaway + public let winnerUserIds: [Int64] + + + public init( + actualWinnersSelectionDate: Int, + additionalChatCount: Int, + boostedChatId: Int64, + giveawayMessageId: Int64, + onlyNewMembers: Bool, + prize: GiveawayPrize, + prizeDescription: String, + unclaimedPrizeCount: Int, + wasRefunded: Bool, + winnerCount: Int, + winnerUserIds: [Int64] + ) { + self.actualWinnersSelectionDate = actualWinnersSelectionDate + self.additionalChatCount = additionalChatCount + self.boostedChatId = boostedChatId + self.giveawayMessageId = giveawayMessageId + self.onlyNewMembers = onlyNewMembers + self.prize = prize + self.prizeDescription = prizeDescription + self.unclaimedPrizeCount = unclaimedPrizeCount + self.wasRefunded = wasRefunded + self.winnerCount = winnerCount + self.winnerUserIds = winnerUserIds + } +} + +/// Telegram Stars were gifted to a user +public struct MessageGiftedStars: Codable, Equatable, Hashable { + + /// The paid amount, in the smallest units of the currency + public let amount: Int64 + + /// Cryptocurrency used to pay for the gift; may be empty if none + public let cryptocurrency: String + + /// The paid amount, in the smallest units of the cryptocurrency; 0 if none + public let cryptocurrencyAmount: TdInt64 + + /// Currency for the paid amount + public let currency: String + + /// The identifier of a user who gifted Telegram Stars; 0 if the gift was anonymous or is outgoing + public let gifterUserId: Int64 + + /// The identifier of a user who received Telegram Stars; 0 if the gift is incoming + public let receiverUserId: Int64 + + /// Number of Telegram Stars that were gifted + public let starCount: Int64 + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// Identifier of the transaction for Telegram Stars purchase; for receiver only + public let transactionId: String + + + public init( + amount: Int64, + cryptocurrency: String, + cryptocurrencyAmount: TdInt64, + currency: String, + gifterUserId: Int64, + receiverUserId: Int64, + starCount: Int64, + sticker: Sticker?, + transactionId: String + ) { + self.amount = amount + self.cryptocurrency = cryptocurrency + self.cryptocurrencyAmount = cryptocurrencyAmount + self.currency = currency + self.gifterUserId = gifterUserId + self.receiverUserId = receiverUserId + self.starCount = starCount + self.sticker = sticker + self.transactionId = transactionId + } +} + +/// Toncoins were gifted to a user +public struct MessageGiftedTon: Codable, Equatable, Hashable { + + /// The identifier of a user who gifted Toncoins; 0 if the gift was anonymous or is outgoing + public let gifterUserId: Int64 + + /// The identifier of a user who received Toncoins; 0 if the gift is incoming + public let receiverUserId: Int64 + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// The received Toncoin amount, in the smallest units of the cryptocurrency + public let tonAmount: Int64 + + /// Identifier of the transaction for Toncoin credit; for receiver only + public let transactionId: String + + + public init( + gifterUserId: Int64, + receiverUserId: Int64, + sticker: Sticker?, + tonAmount: Int64, + transactionId: String + ) { + self.gifterUserId = gifterUserId + self.receiverUserId = receiverUserId + self.sticker = sticker + self.tonAmount = tonAmount + self.transactionId = transactionId + } +} + +/// A Telegram Stars were received by the current user from a giveaway +public struct MessageGiveawayPrizeStars: Codable, Equatable, Hashable { + + /// Identifier of the supergroup or channel chat, which was automatically boosted by the winners of the giveaway + public let boostedChatId: Int64 + + /// Identifier of the message with the giveaway in the boosted chat; may be 0 or an identifier of a deleted message + public let giveawayMessageId: Int64 + + /// True, if the corresponding winner wasn't chosen and the Telegram Stars were received by the owner of the boosted chat + public let isUnclaimed: Bool + + /// Number of Telegram Stars that were received + public let starCount: Int64 + + /// A sticker to be shown in the message; may be null if unknown + public let sticker: Sticker? + + /// Identifier of the transaction for Telegram Stars credit + public let transactionId: String + + + public init( + boostedChatId: Int64, + giveawayMessageId: Int64, + isUnclaimed: Bool, + starCount: Int64, + sticker: Sticker?, + transactionId: String + ) { + self.boostedChatId = boostedChatId + self.giveawayMessageId = giveawayMessageId + self.isUnclaimed = isUnclaimed + self.starCount = starCount + self.sticker = sticker + self.transactionId = transactionId + } +} + +/// A regular gift was received or sent by the current user, or the current user was notified about a channel gift +public struct MessageGift: Codable, Equatable, Hashable { + + /// True, if the gift can be upgraded to a unique gift; only for the receiver of the gift + public let canBeUpgraded: Bool + + /// The gift + public let gift: Gift + + /// True, if the message is a notification about a gift won on an auction + public let isFromAuction: Bool + + /// True, if the message is about prepaid upgrade of the gift by another user + public let isPrepaidUpgrade: Bool + + /// True, if the sender and gift text are shown only to the gift receiver; otherwise, everyone will be able to see them + public let isPrivate: Bool + + /// True, if the gift is displayed on the user's or the channel's profile page; only for the receiver of the gift + public let isSaved: Bool + + /// True, if the upgrade was bought after the gift was sent. In this case, prepaid upgrade cost must not be added to the gift cost + public let isUpgradeSeparate: Bool + + /// If non-empty, then the user can pay for an upgrade of the gift using buyGiftUpgrade + public let prepaidUpgradeHash: String + + /// Number of Telegram Stars that were paid by the sender for the ability to upgrade the gift + public let prepaidUpgradeStarCount: Int64 + + /// Unique identifier of the received gift for the current user; only for the receiver of the gift + public let receivedGiftId: String + + /// Receiver of the gift + public let receiverId: MessageSender + + /// Number of Telegram Stars that can be claimed by the receiver instead of the regular gift; 0 if the gift can't be sold by the receiver + public let sellStarCount: Int64 + + /// Sender of the gift; may be null for outgoing messages about prepaid upgrade of gifts from unknown users + public let senderId: MessageSender? + + /// Message added to the gift + public let text: FormattedText + + /// Unique number of the gift among gifts upgraded from the same gift after upgrade; 0 if yet unassigned + public let uniqueGiftNumber: Int + + /// Identifier of the corresponding upgraded gift; may be empty if unknown. Use getReceivedGift to get information about the gift + public let upgradedReceivedGiftId: String + + /// True, if the gift was converted to Telegram Stars; only for the receiver of the gift + public let wasConverted: Bool + + /// True, if the gift was refunded and isn't available anymore + public let wasRefunded: Bool + + /// True, if the gift was upgraded to a unique gift + public let wasUpgraded: Bool + + + public init( + canBeUpgraded: Bool, + gift: Gift, + isFromAuction: Bool, + isPrepaidUpgrade: Bool, + isPrivate: Bool, + isSaved: Bool, + isUpgradeSeparate: Bool, + prepaidUpgradeHash: String, + prepaidUpgradeStarCount: Int64, + receivedGiftId: String, + receiverId: MessageSender, + sellStarCount: Int64, + senderId: MessageSender?, + text: FormattedText, + uniqueGiftNumber: Int, + upgradedReceivedGiftId: String, + wasConverted: Bool, + wasRefunded: Bool, + wasUpgraded: Bool + ) { + self.canBeUpgraded = canBeUpgraded + self.gift = gift + self.isFromAuction = isFromAuction + self.isPrepaidUpgrade = isPrepaidUpgrade + self.isPrivate = isPrivate + self.isSaved = isSaved + self.isUpgradeSeparate = isUpgradeSeparate + self.prepaidUpgradeHash = prepaidUpgradeHash + self.prepaidUpgradeStarCount = prepaidUpgradeStarCount + self.receivedGiftId = receivedGiftId + self.receiverId = receiverId + self.sellStarCount = sellStarCount + self.senderId = senderId + self.text = text + self.uniqueGiftNumber = uniqueGiftNumber + self.upgradedReceivedGiftId = upgradedReceivedGiftId + self.wasConverted = wasConverted + self.wasRefunded = wasRefunded + self.wasUpgraded = wasUpgraded + } +} + +/// An upgraded gift was received or sent by the current user, or the current user was notified about a channel gift +public struct MessageUpgradedGift: Codable, Equatable, Hashable { + + /// True, if the gift can be transferred to another owner; only for the receiver of the gift + public let canBeTransferred: Bool + + /// Point in time (Unix timestamp) when the gift can be used to craft another gift can be in the past; only for the receiver of the gift + public let craftDate: Int + + /// Number of Telegram Stars that must be paid to drop original details of the upgraded gift; 0 if not available; only for the receiver of the gift + public let dropOriginalDetailsStarCount: Int64 + + /// Point in time (Unix timestamp) when the gift can be transferred to the TON blockchain as an NFT; can be in the past; 0 if NFT export isn't possible; only for the receiver of the gift + public let exportDate: Int + + /// The gift + public let gift: UpgradedGift + + /// True, if the gift is displayed on the user's or the channel's profile page; only for the receiver of the gift + public let isSaved: Bool + + /// Point in time (Unix timestamp) when the gift can be resold to another user; can be in the past; 0 if the gift can't be resold; only for the receiver of the gift + public let nextResaleDate: Int + + /// Point in time (Unix timestamp) when the gift can be transferred to another owner; can be in the past; 0 if the gift can be transferred immediately or transfer isn't possible; only for the receiver of the gift + public let nextTransferDate: Int + + /// Origin of the upgraded gift + public let origin: UpgradedGiftOrigin + + /// Unique identifier of the received gift for the current user; only for the receiver of the gift + public let receivedGiftId: String + + /// Receiver of the gift + public let receiverId: MessageSender + + /// Sender of the gift; may be null for anonymous gifts + public let senderId: MessageSender? + + /// Number of Telegram Stars that must be paid to transfer the upgraded gift; only for the receiver of the gift + public let transferStarCount: Int64 + + /// True, if the gift has already been transferred to another owner; only for the receiver of the gift + public let wasTransferred: Bool + + + public init( + canBeTransferred: Bool, + craftDate: Int, + dropOriginalDetailsStarCount: Int64, + exportDate: Int, + gift: UpgradedGift, + isSaved: Bool, + nextResaleDate: Int, + nextTransferDate: Int, + origin: UpgradedGiftOrigin, + receivedGiftId: String, + receiverId: MessageSender, + senderId: MessageSender?, + transferStarCount: Int64, + wasTransferred: Bool + ) { + self.canBeTransferred = canBeTransferred + self.craftDate = craftDate + self.dropOriginalDetailsStarCount = dropOriginalDetailsStarCount + self.exportDate = exportDate + self.gift = gift + self.isSaved = isSaved + self.nextResaleDate = nextResaleDate + self.nextTransferDate = nextTransferDate + self.origin = origin + self.receivedGiftId = receivedGiftId + self.receiverId = receiverId + self.senderId = senderId + self.transferStarCount = transferStarCount + self.wasTransferred = wasTransferred + } +} + +/// A gift which purchase, upgrade or transfer were refunded +public struct MessageRefundedUpgradedGift: Codable, Equatable, Hashable { + + /// The gift + public let gift: Gift + + /// Origin of the upgraded gift + public let origin: UpgradedGiftOrigin + + /// Receiver of the gift + public let receiverId: MessageSender + + /// Sender of the gift + public let senderId: MessageSender + + + public init( + gift: Gift, + origin: UpgradedGiftOrigin, + receiverId: MessageSender, + senderId: MessageSender + ) { + self.gift = gift + self.origin = origin + self.receiverId = receiverId + self.senderId = senderId + } +} + +/// An offer to purchase an upgraded gift was sent or received +public struct MessageUpgradedGiftPurchaseOffer: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the offer will expire or has expired + public let expirationDate: Int + + /// The gift + public let gift: UpgradedGift + + /// The proposed price + public let price: GiftResalePrice + + /// State of the offer + public let state: GiftPurchaseOfferState + + + public init( + expirationDate: Int, + gift: UpgradedGift, + price: GiftResalePrice, + state: GiftPurchaseOfferState + ) { + self.expirationDate = expirationDate + self.gift = gift + self.price = price + self.state = state + } +} + +/// An offer to purchase a gift was rejected or expired +public struct MessageUpgradedGiftPurchaseOfferRejected: Codable, Equatable, Hashable { + + /// The gift + public let gift: UpgradedGift + + /// Identifier of the message with purchase offer which was rejected or expired; may be 0 or an identifier of a deleted message + public let offerMessageId: Int64 + + /// The proposed price + public let price: GiftResalePrice + + /// True, if the offer has expired; otherwise, the offer was explicitly rejected + public let wasExpired: Bool + + + public init( + gift: UpgradedGift, + offerMessageId: Int64, + price: GiftResalePrice, + wasExpired: Bool + ) { + self.gift = gift + self.offerMessageId = offerMessageId + self.price = price + self.wasExpired = wasExpired + } +} + +/// Paid messages were refunded +public struct MessagePaidMessagesRefunded: Codable, Equatable, Hashable { + + /// The number of refunded messages + public let messageCount: Int + + /// The number of refunded Telegram Stars + public let starCount: Int64 + + + public init( + messageCount: Int, + starCount: Int64 + ) { + self.messageCount = messageCount + self.starCount = starCount + } +} + +/// A price for paid messages was changed in the supergroup chat +public struct MessagePaidMessagePriceChanged: Codable, Equatable, Hashable { + + /// The new number of Telegram Stars that must be paid by non-administrator users of the supergroup chat for each sent message + public let paidMessageStarCount: Int64 + + + public init(paidMessageStarCount: Int64) { + self.paidMessageStarCount = paidMessageStarCount + } +} + +/// A price for direct messages was changed in the channel chat +public struct MessageDirectMessagePriceChanged: Codable, Equatable, Hashable { + + /// True, if direct messages group was enabled for the channel; false otherwise + public let isEnabled: Bool + + /// The new number of Telegram Stars that must be paid by non-administrator users of the channel chat for each message sent to the direct messages group; 0 if the direct messages group was disabled or the messages are free + public let paidMessageStarCount: Int64 + + + public init( + isEnabled: Bool, + paidMessageStarCount: Int64 + ) { + self.isEnabled = isEnabled + self.paidMessageStarCount = paidMessageStarCount + } +} + +/// Some tasks from a checklist were marked as done or not done +public struct MessageChecklistTasksDone: Codable, Equatable, Hashable { + + /// Identifier of the message with the checklist; may be 0 or an identifier of a deleted message + public let checklistMessageId: Int64 + + /// Identifiers of tasks that were marked as done + public let markedAsDoneTaskIds: [Int] + + /// Identifiers of tasks that were marked as not done + public let markedAsNotDoneTaskIds: [Int] + + + public init( + checklistMessageId: Int64, + markedAsDoneTaskIds: [Int], + markedAsNotDoneTaskIds: [Int] + ) { + self.checklistMessageId = checklistMessageId + self.markedAsDoneTaskIds = markedAsDoneTaskIds + self.markedAsNotDoneTaskIds = markedAsNotDoneTaskIds + } +} + +/// Some tasks were added to a checklist +public struct MessageChecklistTasksAdded: Codable, Equatable, Hashable { + + /// Identifier of the message with the checklist; may be 0 or an identifier of a deleted message + public let checklistMessageId: Int64 + + /// List of tasks added to the checklist + public let tasks: [ChecklistTask] + + + public init( + checklistMessageId: Int64, + tasks: [ChecklistTask] + ) { + self.checklistMessageId = checklistMessageId + self.tasks = tasks + } +} + +/// Approval of suggested post has failed, because the user which proposed the post had no enough funds +public struct MessageSuggestedPostApprovalFailed: Codable, Equatable, Hashable { + + /// Price of the suggested post + public let price: SuggestedPostPrice + + /// Identifier of the message with the suggested post; may be 0 or an identifier of a deleted message + public let suggestedPostMessageId: Int64 + + + public init( + price: SuggestedPostPrice, + suggestedPostMessageId: Int64 + ) { + self.price = price + self.suggestedPostMessageId = suggestedPostMessageId + } +} + +/// A suggested post was approved +public struct MessageSuggestedPostApproved: Codable, Equatable, Hashable { + + /// Price of the suggested post; may be null if the post is non-paid + public let price: SuggestedPostPrice? + + /// Point in time (Unix timestamp) when the post is expected to be published + public let sendDate: Int + + /// Identifier of the message with the suggested post; may be 0 or an identifier of a deleted message + public let suggestedPostMessageId: Int64 + + + public init( + price: SuggestedPostPrice?, + sendDate: Int, + suggestedPostMessageId: Int64 + ) { + self.price = price + self.sendDate = sendDate + self.suggestedPostMessageId = suggestedPostMessageId + } +} + +/// A suggested post was declined +public struct MessageSuggestedPostDeclined: Codable, Equatable, Hashable { + + /// Comment added by administrator of the channel when the post was declined + public let comment: String + + /// Identifier of the message with the suggested post; may be 0 or an identifier of a deleted message + public let suggestedPostMessageId: Int64 + + + public init( + comment: String, + suggestedPostMessageId: Int64 + ) { + self.comment = comment + self.suggestedPostMessageId = suggestedPostMessageId + } +} + +/// A suggested post was published for getOption("suggested_post_lifetime_min") seconds and payment for the post was received +public struct MessageSuggestedPostPaid: Codable, Equatable, Hashable { + + /// The amount of received Telegram Stars + public let starAmount: StarAmount + + /// Identifier of the message with the suggested post; may be 0 or an identifier of a deleted message + public let suggestedPostMessageId: Int64 + + /// The amount of received Toncoins; in the smallest units of the cryptocurrency + public let tonAmount: Int64 + + + public init( + starAmount: StarAmount, + suggestedPostMessageId: Int64, + tonAmount: Int64 + ) { + self.starAmount = starAmount + self.suggestedPostMessageId = suggestedPostMessageId + self.tonAmount = tonAmount + } +} + +/// A suggested post was refunded +public struct MessageSuggestedPostRefunded: Codable, Equatable, Hashable { + + /// Reason of the refund + public let reason: SuggestedPostRefundReason + + /// Identifier of the message with the suggested post; may be 0 or an identifier of a deleted message + public let suggestedPostMessageId: Int64 + + + public init( + reason: SuggestedPostRefundReason, + suggestedPostMessageId: Int64 + ) { + self.reason = reason + self.suggestedPostMessageId = suggestedPostMessageId + } +} + +/// The current user shared users, which were requested by the bot +public struct MessageUsersShared: Codable, Equatable, Hashable { + + /// Identifier of the keyboard button with the request + public let buttonId: Int + + /// The shared users + public let users: [SharedUser] + + + public init( + buttonId: Int, + users: [SharedUser] + ) { + self.buttonId = buttonId + self.users = users + } +} + +/// The current user shared a chat, which was requested by the bot +public struct MessageChatShared: Codable, Equatable, Hashable { + + /// Identifier of the keyboard button with the request + public let buttonId: Int + + /// The shared chat + public let chat: SharedChat + + + public init( + buttonId: Int, + chat: SharedChat + ) { + self.buttonId = buttonId + self.chat = chat + } +} + +/// The user allowed the bot to send messages +public struct MessageBotWriteAccessAllowed: Codable, Equatable, Hashable { + + /// The reason why the bot was allowed to write messages + public let reason: BotWriteAccessAllowReason + + + public init(reason: BotWriteAccessAllowReason) { + self.reason = reason + } +} + +/// Data from a Web App has been sent to a bot +public struct MessageWebAppDataSent: Codable, Equatable, Hashable { + + /// Text of the keyboardButtonTypeWebApp button, which opened the Web App + public let buttonText: String + + + public init(buttonText: String) { + self.buttonText = buttonText + } +} + +/// Data from a Web App has been received; for bots only +public struct MessageWebAppDataReceived: Codable, Equatable, Hashable { + + /// Text of the keyboardButtonTypeWebApp button, which opened the Web App + public let buttonText: String + + /// The data + public let data: String + + + public init( + buttonText: String, + data: String + ) { + self.buttonText = buttonText + self.data = data + } +} + +/// A user in the chat came within proximity alert range +public struct MessageProximityAlertTriggered: Codable, Equatable, Hashable { + + /// The distance between the users + public let distance: Int + + /// The identifier of a user or chat that triggered the proximity alert + public let travelerId: MessageSender + + /// The identifier of a user or chat that subscribed for the proximity alert + public let watcherId: MessageSender + + + public init( + distance: Int, + travelerId: MessageSender, + watcherId: MessageSender + ) { + self.distance = distance + self.travelerId = travelerId + self.watcherId = watcherId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageCopyOptions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageCopyOptions.swift new file mode 100644 index 0000000000..b3cd3fb913 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageCopyOptions.swift @@ -0,0 +1,41 @@ +// +// MessageCopyOptions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Options to be used when a message content is copied without reference to the original sender. Service messages, messages with messageInvoice, messagePaidMedia, messageGiveaway, or messageGiveawayWinners content can't be copied +public struct MessageCopyOptions: Codable, Equatable, Hashable { + + /// New message caption; pass null to copy message without caption. Ignored if replace_caption is false + public let newCaption: FormattedText? + + /// True, if new caption must be shown above the media; otherwise, new caption must be shown below the media; not supported in secret chats. Ignored if replace_caption is false + public let newShowCaptionAboveMedia: Bool + + /// True, if media caption of the message copy needs to be replaced. Ignored if send_copy is false + public let replaceCaption: Bool + + /// True, if content of the message needs to be copied without reference to the original sender. Always true if the message is forwarded to a secret chat or is local. Use messageProperties.can_be_copied and messageProperties.can_be_copied_to_secret_chat to check whether the message is suitable + public let sendCopy: Bool + + + public init( + newCaption: FormattedText?, + newShowCaptionAboveMedia: Bool, + replaceCaption: Bool, + sendCopy: Bool + ) { + self.newCaption = newCaption + self.newShowCaptionAboveMedia = newShowCaptionAboveMedia + self.replaceCaption = replaceCaption + self.sendCopy = sendCopy + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageForwardInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageForwardInfo.swift new file mode 100644 index 0000000000..65f8210913 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageForwardInfo.swift @@ -0,0 +1,41 @@ +// +// MessageForwardInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a forwarded message +public struct MessageForwardInfo: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the message was originally sent + public let date: Int + + /// Origin of the forwarded message + public let origin: MessageOrigin + + /// The type of public service announcement for the forwarded message + public let publicServiceAnnouncementType: String + + /// For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, information about the source message from which the message was forwarded last time; may be null for other forwards or if unknown + public let source: ForwardSource? + + + public init( + date: Int, + origin: MessageOrigin, + publicServiceAnnouncementType: String, + source: ForwardSource? + ) { + self.date = date + self.origin = origin + self.publicServiceAnnouncementType = publicServiceAnnouncementType + self.source = source + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageImportInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageImportInfo.swift new file mode 100644 index 0000000000..3555e080e0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageImportInfo.swift @@ -0,0 +1,31 @@ +// +// MessageImportInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a message created with importMessages +public struct MessageImportInfo: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the message was originally sent + public let date: Int + + /// Name of the original sender + public let senderName: String + + + public init( + date: Int, + senderName: String + ) { + self.date = date + self.senderName = senderName + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageInteractionInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageInteractionInfo.swift new file mode 100644 index 0000000000..2e5e5cfbb7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageInteractionInfo.swift @@ -0,0 +1,41 @@ +// +// MessageInteractionInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about interactions with a message +public struct MessageInteractionInfo: Codable, Equatable, Hashable { + + /// Number of times the message was forwarded + public let forwardCount: Int + + /// The list of reactions or tags added to the message; may be null + public let reactions: MessageReactions? + + /// Information about direct or indirect replies to the message; may be null. Currently, available only in channels with a discussion supergroup and discussion supergroups for messages, which are not replies itself + public let replyInfo: MessageReplyInfo? + + /// Number of times the message was viewed + public let viewCount: Int + + + public init( + forwardCount: Int, + reactions: MessageReactions?, + replyInfo: MessageReplyInfo?, + viewCount: Int + ) { + self.forwardCount = forwardCount + self.reactions = reactions + self.replyInfo = replyInfo + self.viewCount = viewCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageOrigin.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageOrigin.swift new file mode 100644 index 0000000000..eaa55bca10 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageOrigin.swift @@ -0,0 +1,148 @@ +// +// MessageOrigin.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the origin of a message +public indirect enum MessageOrigin: Codable, Equatable, Hashable { + + /// The message was originally sent by a known user + case messageOriginUser(MessageOriginUser) + + /// The message was originally sent by a user, which is hidden by their privacy settings + case messageOriginHiddenUser(MessageOriginHiddenUser) + + /// The message was originally sent on behalf of a chat + case messageOriginChat(MessageOriginChat) + + /// The message was originally a post in a channel + case messageOriginChannel(MessageOriginChannel) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageOriginUser + case messageOriginHiddenUser + case messageOriginChat + case messageOriginChannel + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageOriginUser: + let value = try MessageOriginUser(from: decoder) + self = .messageOriginUser(value) + case .messageOriginHiddenUser: + let value = try MessageOriginHiddenUser(from: decoder) + self = .messageOriginHiddenUser(value) + case .messageOriginChat: + let value = try MessageOriginChat(from: decoder) + self = .messageOriginChat(value) + case .messageOriginChannel: + let value = try MessageOriginChannel(from: decoder) + self = .messageOriginChannel(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageOriginUser(let value): + try container.encode(Kind.messageOriginUser, forKey: .type) + try value.encode(to: encoder) + case .messageOriginHiddenUser(let value): + try container.encode(Kind.messageOriginHiddenUser, forKey: .type) + try value.encode(to: encoder) + case .messageOriginChat(let value): + try container.encode(Kind.messageOriginChat, forKey: .type) + try value.encode(to: encoder) + case .messageOriginChannel(let value): + try container.encode(Kind.messageOriginChannel, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The message was originally sent by a known user +public struct MessageOriginUser: Codable, Equatable, Hashable { + + /// Identifier of the user who originally sent the message + public let senderUserId: Int64 + + + public init(senderUserId: Int64) { + self.senderUserId = senderUserId + } +} + +/// The message was originally sent by a user, which is hidden by their privacy settings +public struct MessageOriginHiddenUser: Codable, Equatable, Hashable { + + /// Name of the sender + public let senderName: String + + + public init(senderName: String) { + self.senderName = senderName + } +} + +/// The message was originally sent on behalf of a chat +public struct MessageOriginChat: Codable, Equatable, Hashable { + + /// For messages originally sent by an anonymous chat administrator, original message author signature + public let authorSignature: String + + /// Identifier of the chat that originally sent the message + public let senderChatId: Int64 + + + public init( + authorSignature: String, + senderChatId: Int64 + ) { + self.authorSignature = authorSignature + self.senderChatId = senderChatId + } +} + +/// The message was originally a post in a channel +public struct MessageOriginChannel: Codable, Equatable, Hashable { + + /// Original post author signature + public let authorSignature: String + + /// Identifier of the channel chat to which the message was originally sent + public let chatId: Int64 + + /// Message identifier of the original message + public let messageId: Int64 + + + public init( + authorSignature: String, + chatId: Int64, + messageId: Int64 + ) { + self.authorSignature = authorSignature + self.chatId = chatId + self.messageId = messageId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReaction.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReaction.swift new file mode 100644 index 0000000000..77bb4890fd --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReaction.swift @@ -0,0 +1,46 @@ +// +// MessageReaction.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a reaction to a message +public struct MessageReaction: Codable, Equatable, Hashable { + + /// True, if the reaction is chosen by the current user + public let isChosen: Bool + + /// Identifiers of at most 3 recent message senders, added the reaction; available in private, basic group and supergroup chats + public let recentSenderIds: [MessageSender] + + /// Number of times the reaction was added + public let totalCount: Int + + /// Type of the reaction + public let type: ReactionType + + /// Identifier of the message sender used by the current user to add the reaction; may be null if unknown or the reaction isn't chosen + public let usedSenderId: MessageSender? + + + public init( + isChosen: Bool, + recentSenderIds: [MessageSender], + totalCount: Int, + type: ReactionType, + usedSenderId: MessageSender? + ) { + self.isChosen = isChosen + self.recentSenderIds = recentSenderIds + self.totalCount = totalCount + self.type = type + self.usedSenderId = usedSenderId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReactions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReactions.swift new file mode 100644 index 0000000000..bba8b97069 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReactions.swift @@ -0,0 +1,41 @@ +// +// MessageReactions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains a list of reactions added to a message +public struct MessageReactions: Codable, Equatable, Hashable { + + /// True, if the reactions are tags and Telegram Premium users can filter messages by them + public let areTags: Bool + + /// True, if the list of added reactions is available using getMessageAddedReactions + public let canGetAddedReactions: Bool + + /// Information about top users that added the paid reaction + public let paidReactors: [PaidReactor] + + /// List of added reactions + public let reactions: [MessageReaction] + + + public init( + areTags: Bool, + canGetAddedReactions: Bool, + paidReactors: [PaidReactor], + reactions: [MessageReaction] + ) { + self.areTags = areTags + self.canGetAddedReactions = canGetAddedReactions + self.paidReactors = paidReactors + self.reactions = reactions + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyInfo.swift new file mode 100644 index 0000000000..ab047578a5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyInfo.swift @@ -0,0 +1,46 @@ +// +// MessageReplyInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about replies to a message +public struct MessageReplyInfo: Codable, Equatable, Hashable { + + /// Identifier of the last reply to the message + public let lastMessageId: Int64 + + /// Identifier of the last read incoming reply to the message + public let lastReadInboxMessageId: Int64 + + /// Identifier of the last read outgoing reply to the message + public let lastReadOutboxMessageId: Int64 + + /// Identifiers of at most 3 recent repliers to the message; available in channels with a discussion supergroup. The users and chats are expected to be inaccessible: only their photo and name will be available + public let recentReplierIds: [MessageSender] + + /// Number of times the message was directly or indirectly replied + public let replyCount: Int + + + public init( + lastMessageId: Int64, + lastReadInboxMessageId: Int64, + lastReadOutboxMessageId: Int64, + recentReplierIds: [MessageSender], + replyCount: Int + ) { + self.lastMessageId = lastMessageId + self.lastReadInboxMessageId = lastReadInboxMessageId + self.lastReadOutboxMessageId = lastReadOutboxMessageId + self.recentReplierIds = recentReplierIds + self.replyCount = replyCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyTo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyTo.swift new file mode 100644 index 0000000000..8ae9f34ebd --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageReplyTo.swift @@ -0,0 +1,129 @@ +// +// MessageReplyTo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the message or the story a message is replying to +public indirect enum MessageReplyTo: Codable, Equatable, Hashable { + + /// Describes a message replied by a given message + case messageReplyToMessage(MessageReplyToMessage) + + /// Describes a story replied by a given message + case messageReplyToStory(MessageReplyToStory) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageReplyToMessage + case messageReplyToStory + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageReplyToMessage: + let value = try MessageReplyToMessage(from: decoder) + self = .messageReplyToMessage(value) + case .messageReplyToStory: + let value = try MessageReplyToStory(from: decoder) + self = .messageReplyToStory(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageReplyToMessage(let value): + try container.encode(Kind.messageReplyToMessage, forKey: .type) + try value.encode(to: encoder) + case .messageReplyToStory(let value): + try container.encode(Kind.messageReplyToStory, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Describes a message replied by a given message +public struct MessageReplyToMessage: Codable, Equatable, Hashable { + + /// The identifier of the chat to which the message belongs; may be 0 if the replied message is in unknown chat + public let chatId: Int64 + + /// Identifier of the checklist task in the original message that was replied; 0 if none + public let checklistTaskId: Int + + /// Media content of the message if the message was from another chat or topic; may be null for messages from the same chat and messages without media. Can be only one of the following types: messageAnimation, messageAudio, messageChecklist, messageContact, messageDice, messageDocument, messageGame, messageGiveaway, messageGiveawayWinners, messageInvoice, messageLocation, messagePaidMedia, messagePhoto, messagePoll, messageStakeDice, messageSticker, messageStory, messageText (for link preview), messageVenue, messageVideo, messageVideoNote, or messageVoiceNote + public let content: MessageContent? + + /// The identifier of the message; may be 0 if the replied message is in unknown chat + public let messageId: Int64 + + /// Information about origin of the message if the message was from another chat or topic; may be null for messages from the same chat + public let origin: MessageOrigin? + + /// Point in time (Unix timestamp) when the message was sent if the message was from another chat or topic; 0 for messages from the same chat + public let originSendDate: Int + + /// Identifier of the poll option in the original message that was replied; empty if none + public let pollOptionId: String + + /// Chosen quote from the replied message; may be null if none + public let quote: TextQuote? + + + public init( + chatId: Int64, + checklistTaskId: Int, + content: MessageContent?, + messageId: Int64, + origin: MessageOrigin?, + originSendDate: Int, + pollOptionId: String, + quote: TextQuote? + ) { + self.chatId = chatId + self.checklistTaskId = checklistTaskId + self.content = content + self.messageId = messageId + self.origin = origin + self.originSendDate = originSendDate + self.pollOptionId = pollOptionId + self.quote = quote + } +} + +/// Describes a story replied by a given message +public struct MessageReplyToStory: Codable, Equatable, Hashable { + + /// The identifier of the story + public let storyId: Int + + /// The identifier of the poster of the story + public let storyPosterChatId: Int64 + + + public init( + storyId: Int, + storyPosterChatId: Int64 + ) { + self.storyId = storyId + self.storyPosterChatId = storyPosterChatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSchedulingState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSchedulingState.swift new file mode 100644 index 0000000000..7ee49a3a0b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSchedulingState.swift @@ -0,0 +1,100 @@ +// +// MessageSchedulingState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the time when a scheduled message will be sent +public indirect enum MessageSchedulingState: Codable, Equatable, Hashable { + + /// The message will be sent at the specified date + case messageSchedulingStateSendAtDate(MessageSchedulingStateSendAtDate) + + /// The message will be sent when the other user is online. Applicable to private chats only and when the exact online status of the other user is known + case messageSchedulingStateSendWhenOnline + + /// The message will be sent when the video in the message is converted and optimized; can be used only by the server + case messageSchedulingStateSendWhenVideoProcessed(MessageSchedulingStateSendWhenVideoProcessed) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageSchedulingStateSendAtDate + case messageSchedulingStateSendWhenOnline + case messageSchedulingStateSendWhenVideoProcessed + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageSchedulingStateSendAtDate: + let value = try MessageSchedulingStateSendAtDate(from: decoder) + self = .messageSchedulingStateSendAtDate(value) + case .messageSchedulingStateSendWhenOnline: + self = .messageSchedulingStateSendWhenOnline + case .messageSchedulingStateSendWhenVideoProcessed: + let value = try MessageSchedulingStateSendWhenVideoProcessed(from: decoder) + self = .messageSchedulingStateSendWhenVideoProcessed(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageSchedulingStateSendAtDate(let value): + try container.encode(Kind.messageSchedulingStateSendAtDate, forKey: .type) + try value.encode(to: encoder) + case .messageSchedulingStateSendWhenOnline: + try container.encode(Kind.messageSchedulingStateSendWhenOnline, forKey: .type) + case .messageSchedulingStateSendWhenVideoProcessed(let value): + try container.encode(Kind.messageSchedulingStateSendWhenVideoProcessed, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The message will be sent at the specified date +public struct MessageSchedulingStateSendAtDate: Codable, Equatable, Hashable { + + /// Period after which the message will be sent again; in seconds; 0 if never; for Telegram Premium users only; may be non-zero only in sendMessage and forwardMessages with one message requests; must be one of 0, 86400, 7 * 86400, 14 * 86400, 30 * 86400, 91 * 86400, 182 * 86400, 365 * 86400, or additionally 60, or 300 in the Test DC + public let repeatPeriod: Int + + /// Point in time (Unix timestamp) when the message will be sent. The date must be within 367 days in the future + public let sendDate: Int + + + public init( + repeatPeriod: Int, + sendDate: Int + ) { + self.repeatPeriod = repeatPeriod + self.sendDate = sendDate + } +} + +/// The message will be sent when the video in the message is converted and optimized; can be used only by the server +public struct MessageSchedulingStateSendWhenVideoProcessed: Codable, Equatable, Hashable { + + /// Approximate point in time (Unix timestamp) when the message is expected to be sent + public let sendDate: Int + + + public init(sendDate: Int) { + self.sendDate = sendDate + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSelfDestructType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSelfDestructType.swift new file mode 100644 index 0000000000..efa48ba13d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSelfDestructType.swift @@ -0,0 +1,71 @@ +// +// MessageSelfDestructType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes when a message will be self-destructed +public indirect enum MessageSelfDestructType: Codable, Equatable, Hashable { + + /// The message will be self-destructed in the specified time after its content was opened + case messageSelfDestructTypeTimer(MessageSelfDestructTypeTimer) + + /// The message can be opened only once and will be self-destructed once closed + case messageSelfDestructTypeImmediately + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageSelfDestructTypeTimer + case messageSelfDestructTypeImmediately + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageSelfDestructTypeTimer: + let value = try MessageSelfDestructTypeTimer(from: decoder) + self = .messageSelfDestructTypeTimer(value) + case .messageSelfDestructTypeImmediately: + self = .messageSelfDestructTypeImmediately + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageSelfDestructTypeTimer(let value): + try container.encode(Kind.messageSelfDestructTypeTimer, forKey: .type) + try value.encode(to: encoder) + case .messageSelfDestructTypeImmediately: + try container.encode(Kind.messageSelfDestructTypeImmediately, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The message will be self-destructed in the specified time after its content was opened +public struct MessageSelfDestructTypeTimer: Codable, Equatable, Hashable { + + /// The message's self-destruct time, in seconds; must be between 0 and 60 in private chats + public let selfDestructTime: Int + + + public init(selfDestructTime: Int) { + self.selfDestructTime = selfDestructTime + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendOptions.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendOptions.swift new file mode 100644 index 0000000000..b34cc9ff8e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendOptions.swift @@ -0,0 +1,76 @@ +// +// MessageSendOptions.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Options to be used when a message is sent +public struct MessageSendOptions: Codable, Equatable, Hashable { + + /// Pass true to allow the message to ignore regular broadcast limits for a small fee; for bots only + public let allowPaidBroadcast: Bool + + /// Pass true to disable notification for the message + public let disableNotification: Bool + + /// Identifier of the effect to apply to the message; pass 0 if none; applicable only to sendMessage, sendMessageAlbum in private chats and forwardMessages with one message to private chats + public let effectId: TdInt64 + + /// Pass true if the message is sent from the background + public let fromBackground: Bool + + /// Pass true to get a fake message instead of actually sending them + public let onlyPreview: Bool + + /// The number of Telegram Stars the user agreed to pay to send the messages + public let paidMessageStarCount: Int64 + + /// Pass true if the content of the message must be protected from forwarding and saving; for bots only + public let protectContent: Bool + + /// Message scheduling state; pass null to send message immediately. Messages sent to a secret chat, to a chat with paid messages, to a channel direct messages chat, live location messages and self-destructing messages can't be scheduled + public let schedulingState: MessageSchedulingState? + + /// Non-persistent identifier, which will be returned back in messageSendingStatePending object and can be used to match sent messages and corresponding updateNewMessage updates + public let sendingId: Int + + /// Information about the suggested post; pass null if none. For messages to channel direct messages chat only. Applicable only to sendMessage and addOffer + public let suggestedPostInfo: InputSuggestedPostInfo? + + /// Pass true if the user explicitly chosen a sticker or a custom emoji from an installed sticker set; applicable only to sendMessage and sendMessageAlbum + public let updateOrderOfInstalledStickerSets: Bool + + + public init( + allowPaidBroadcast: Bool, + disableNotification: Bool, + effectId: TdInt64, + fromBackground: Bool, + onlyPreview: Bool, + paidMessageStarCount: Int64, + protectContent: Bool, + schedulingState: MessageSchedulingState?, + sendingId: Int, + suggestedPostInfo: InputSuggestedPostInfo?, + updateOrderOfInstalledStickerSets: Bool + ) { + self.allowPaidBroadcast = allowPaidBroadcast + self.disableNotification = disableNotification + self.effectId = effectId + self.fromBackground = fromBackground + self.onlyPreview = onlyPreview + self.paidMessageStarCount = paidMessageStarCount + self.protectContent = protectContent + self.schedulingState = schedulingState + self.sendingId = sendingId + self.suggestedPostInfo = suggestedPostInfo + self.updateOrderOfInstalledStickerSets = updateOrderOfInstalledStickerSets + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSender.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSender.swift new file mode 100644 index 0000000000..9c73d68103 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSender.swift @@ -0,0 +1,85 @@ +// +// MessageSender.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the sender of a message +public indirect enum MessageSender: Codable, Equatable, Hashable { + + /// The message was sent by a known user + case messageSenderUser(MessageSenderUser) + + /// The message was sent on behalf of a chat + case messageSenderChat(MessageSenderChat) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageSenderUser + case messageSenderChat + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageSenderUser: + let value = try MessageSenderUser(from: decoder) + self = .messageSenderUser(value) + case .messageSenderChat: + let value = try MessageSenderChat(from: decoder) + self = .messageSenderChat(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageSenderUser(let value): + try container.encode(Kind.messageSenderUser, forKey: .type) + try value.encode(to: encoder) + case .messageSenderChat(let value): + try container.encode(Kind.messageSenderChat, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The message was sent by a known user +public struct MessageSenderUser: Codable, Equatable, Hashable { + + /// Identifier of the user who sent the message + public let userId: Int64 + + + public init(userId: Int64) { + self.userId = userId + } +} + +/// The message was sent on behalf of a chat +public struct MessageSenderChat: Codable, Equatable, Hashable { + + /// Identifier of the chat that sent the message + public let chatId: Int64 + + + public init(chatId: Int64) { + self.chatId = chatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendingState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendingState.swift new file mode 100644 index 0000000000..da97cec5d8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSendingState.swift @@ -0,0 +1,117 @@ +// +// MessageSendingState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about the sending state of the message +public indirect enum MessageSendingState: Codable, Equatable, Hashable { + + /// The message is being sent now, but has not yet been delivered to the server + case messageSendingStatePending(MessageSendingStatePending) + + /// The message failed to be sent + case messageSendingStateFailed(MessageSendingStateFailed) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageSendingStatePending + case messageSendingStateFailed + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageSendingStatePending: + let value = try MessageSendingStatePending(from: decoder) + self = .messageSendingStatePending(value) + case .messageSendingStateFailed: + let value = try MessageSendingStateFailed(from: decoder) + self = .messageSendingStateFailed(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageSendingStatePending(let value): + try container.encode(Kind.messageSendingStatePending, forKey: .type) + try value.encode(to: encoder) + case .messageSendingStateFailed(let value): + try container.encode(Kind.messageSendingStateFailed, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The message is being sent now, but has not yet been delivered to the server +public struct MessageSendingStatePending: Codable, Equatable, Hashable { + + /// Non-persistent message sending identifier, specified by the application + public let sendingId: Int + + + public init(sendingId: Int) { + self.sendingId = sendingId + } +} + +/// The message failed to be sent +public struct MessageSendingStateFailed: Codable, Equatable, Hashable { + + /// True, if the message can be re-sent using resendMessages or readdQuickReplyShortcutMessages + public let canRetry: Bool + + /// The cause of the message sending failure + public let error: TDError + + /// True, if the message can be re-sent only if another quote is chosen in the message that is replied by the given message + public let needAnotherReplyQuote: Bool + + /// True, if the message can be re-sent only on behalf of a different sender + public let needAnotherSender: Bool + + /// True, if the message can be re-sent only if the message to be replied is removed. This will be done automatically by resendMessages + public let needDropReply: Bool + + /// The number of Telegram Stars that must be paid to send the message; 0 if the current amount is correct + public let requiredPaidMessageStarCount: Int64 + + /// Time left before the message can be re-sent, in seconds. No update is sent when this field changes + public let retryAfter: Double + + + public init( + canRetry: Bool, + error: TDError, + needAnotherReplyQuote: Bool, + needAnotherSender: Bool, + needDropReply: Bool, + requiredPaidMessageStarCount: Int64, + retryAfter: Double + ) { + self.canRetry = canRetry + self.error = error + self.needAnotherReplyQuote = needAnotherReplyQuote + self.needAnotherSender = needAnotherSender + self.needDropReply = needDropReply + self.requiredPaidMessageStarCount = requiredPaidMessageStarCount + self.retryAfter = retryAfter + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSource.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSource.swift new file mode 100644 index 0000000000..12caf3aa07 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageSource.swift @@ -0,0 +1,129 @@ +// +// MessageSource.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes source of a message +public indirect enum MessageSource: Codable, Equatable, Hashable { + + /// The message is from a chat history + case messageSourceChatHistory + + /// The message is from history of a message thread + case messageSourceMessageThreadHistory + + /// The message is from history of a forum topic + case messageSourceForumTopicHistory + + /// The message is from history of a topic in a channel direct messages chat administered by the current user + case messageSourceDirectMessagesChatTopicHistory + + /// The message is from chat, message thread or forum topic history preview + case messageSourceHistoryPreview + + /// The message is from a chat list or a forum topic list + case messageSourceChatList + + /// The message is from search results, including file downloads, local file list, outgoing document messages, calendar + case messageSourceSearch + + /// The message is from a chat event log + case messageSourceChatEventLog + + /// The message is from a notification + case messageSourceNotification + + /// The message was screenshotted; the source must be used only if the message content was visible during the screenshot + case messageSourceScreenshot + + /// The message is from some other source + case messageSourceOther + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageSourceChatHistory + case messageSourceMessageThreadHistory + case messageSourceForumTopicHistory + case messageSourceDirectMessagesChatTopicHistory + case messageSourceHistoryPreview + case messageSourceChatList + case messageSourceSearch + case messageSourceChatEventLog + case messageSourceNotification + case messageSourceScreenshot + case messageSourceOther + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageSourceChatHistory: + self = .messageSourceChatHistory + case .messageSourceMessageThreadHistory: + self = .messageSourceMessageThreadHistory + case .messageSourceForumTopicHistory: + self = .messageSourceForumTopicHistory + case .messageSourceDirectMessagesChatTopicHistory: + self = .messageSourceDirectMessagesChatTopicHistory + case .messageSourceHistoryPreview: + self = .messageSourceHistoryPreview + case .messageSourceChatList: + self = .messageSourceChatList + case .messageSourceSearch: + self = .messageSourceSearch + case .messageSourceChatEventLog: + self = .messageSourceChatEventLog + case .messageSourceNotification: + self = .messageSourceNotification + case .messageSourceScreenshot: + self = .messageSourceScreenshot + case .messageSourceOther: + self = .messageSourceOther + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageSourceChatHistory: + try container.encode(Kind.messageSourceChatHistory, forKey: .type) + case .messageSourceMessageThreadHistory: + try container.encode(Kind.messageSourceMessageThreadHistory, forKey: .type) + case .messageSourceForumTopicHistory: + try container.encode(Kind.messageSourceForumTopicHistory, forKey: .type) + case .messageSourceDirectMessagesChatTopicHistory: + try container.encode(Kind.messageSourceDirectMessagesChatTopicHistory, forKey: .type) + case .messageSourceHistoryPreview: + try container.encode(Kind.messageSourceHistoryPreview, forKey: .type) + case .messageSourceChatList: + try container.encode(Kind.messageSourceChatList, forKey: .type) + case .messageSourceSearch: + try container.encode(Kind.messageSourceSearch, forKey: .type) + case .messageSourceChatEventLog: + try container.encode(Kind.messageSourceChatEventLog, forKey: .type) + case .messageSourceNotification: + try container.encode(Kind.messageSourceNotification, forKey: .type) + case .messageSourceScreenshot: + try container.encode(Kind.messageSourceScreenshot, forKey: .type) + case .messageSourceOther: + try container.encode(Kind.messageSourceOther, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageTopic.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageTopic.swift new file mode 100644 index 0000000000..a3135bd629 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/MessageTopic.swift @@ -0,0 +1,129 @@ +// +// MessageTopic.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a topic of messages in a chat +public indirect enum MessageTopic: Codable, Equatable, Hashable { + + /// A topic in a non-forum supergroup chat + case messageTopicThread(MessageTopicThread) + + /// A topic in a forum supergroup chat or a chat with a bot + case messageTopicForum(MessageTopicForum) + + /// A topic in a channel direct messages chat administered by the current user + case messageTopicDirectMessages(MessageTopicDirectMessages) + + /// A topic in Saved Messages chat + case messageTopicSavedMessages(MessageTopicSavedMessages) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case messageTopicThread + case messageTopicForum + case messageTopicDirectMessages + case messageTopicSavedMessages + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .messageTopicThread: + let value = try MessageTopicThread(from: decoder) + self = .messageTopicThread(value) + case .messageTopicForum: + let value = try MessageTopicForum(from: decoder) + self = .messageTopicForum(value) + case .messageTopicDirectMessages: + let value = try MessageTopicDirectMessages(from: decoder) + self = .messageTopicDirectMessages(value) + case .messageTopicSavedMessages: + let value = try MessageTopicSavedMessages(from: decoder) + self = .messageTopicSavedMessages(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .messageTopicThread(let value): + try container.encode(Kind.messageTopicThread, forKey: .type) + try value.encode(to: encoder) + case .messageTopicForum(let value): + try container.encode(Kind.messageTopicForum, forKey: .type) + try value.encode(to: encoder) + case .messageTopicDirectMessages(let value): + try container.encode(Kind.messageTopicDirectMessages, forKey: .type) + try value.encode(to: encoder) + case .messageTopicSavedMessages(let value): + try container.encode(Kind.messageTopicSavedMessages, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A topic in a non-forum supergroup chat +public struct MessageTopicThread: Codable, Equatable, Hashable { + + /// Unique identifier of the message thread + public let messageThreadId: Int64 + + + public init(messageThreadId: Int64) { + self.messageThreadId = messageThreadId + } +} + +/// A topic in a forum supergroup chat or a chat with a bot +public struct MessageTopicForum: Codable, Equatable, Hashable { + + /// Unique identifier of the forum topic + public let forumTopicId: Int + + + public init(forumTopicId: Int) { + self.forumTopicId = forumTopicId + } +} + +/// A topic in a channel direct messages chat administered by the current user +public struct MessageTopicDirectMessages: Codable, Equatable, Hashable { + + /// Unique identifier of the topic + public let directMessagesChatTopicId: Int64 + + + public init(directMessagesChatTopicId: Int64) { + self.directMessagesChatTopicId = directMessagesChatTopicId + } +} + +/// A topic in Saved Messages chat +public struct MessageTopicSavedMessages: Codable, Equatable, Hashable { + + /// Unique identifier of the Saved Messages topic + public let savedMessagesTopicId: Int64 + + + public init(savedMessagesTopicId: Int64) { + self.savedMessagesTopicId = savedMessagesTopicId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Messages.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Messages.swift new file mode 100644 index 0000000000..e766552119 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Messages.swift @@ -0,0 +1,31 @@ +// +// Messages.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains a list of messages +public struct Messages: Codable, Equatable, Hashable { + + /// List of messages; messages may be null + public let messages: [Message]? + + /// Approximate total number of messages found + public let totalCount: Int + + + public init( + messages: [Message]?, + totalCount: Int + ) { + self.messages = messages + self.totalCount = totalCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Minithumbnail.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Minithumbnail.swift new file mode 100644 index 0000000000..155910e5ad --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Minithumbnail.swift @@ -0,0 +1,36 @@ +// +// Minithumbnail.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Thumbnail image of a very poor quality and low resolution +public struct Minithumbnail: Codable, Equatable, Hashable { + + /// The thumbnail in JPEG format + public let data: Data + + /// Thumbnail height, usually doesn't exceed 40 + public let height: Int + + /// Thumbnail width, usually doesn't exceed 40 + public let width: Int + + + public init( + data: Data, + height: Int, + width: Int + ) { + self.data = data + self.height = height + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Ok.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Ok.swift new file mode 100644 index 0000000000..f19bf1b625 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Ok.swift @@ -0,0 +1,19 @@ +// +// Ok.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// An object of this type is returned on a successful function call for certain functions +public struct Ok: Codable, Equatable, Hashable { + + + public init() {} +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OpenChat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OpenChat.swift new file mode 100644 index 0000000000..e60a212d2a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OpenChat.swift @@ -0,0 +1,24 @@ +// +// OpenChat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Informs TDLib that the chat is opened by the user. Many useful activities depend on the chat being opened or closed (e.g., in supergroups and channels all updates are received only for opened chats) +public struct OpenChat: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64? + + + public init(chatId: Int64?) { + self.chatId = chatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OptionValue.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OptionValue.swift new file mode 100644 index 0000000000..a7aac76c76 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OptionValue.swift @@ -0,0 +1,115 @@ +// +// OptionValue.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents the value of an option +public indirect enum OptionValue: Codable, Equatable, Hashable { + + /// Represents a boolean option + case optionValueBoolean(OptionValueBoolean) + + /// Represents an unknown option or an option which has a default value + case optionValueEmpty + + /// Represents an integer option + case optionValueInteger(OptionValueInteger) + + /// Represents a string option + case optionValueString(OptionValueString) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case optionValueBoolean + case optionValueEmpty + case optionValueInteger + case optionValueString + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .optionValueBoolean: + let value = try OptionValueBoolean(from: decoder) + self = .optionValueBoolean(value) + case .optionValueEmpty: + self = .optionValueEmpty + case .optionValueInteger: + let value = try OptionValueInteger(from: decoder) + self = .optionValueInteger(value) + case .optionValueString: + let value = try OptionValueString(from: decoder) + self = .optionValueString(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .optionValueBoolean(let value): + try container.encode(Kind.optionValueBoolean, forKey: .type) + try value.encode(to: encoder) + case .optionValueEmpty: + try container.encode(Kind.optionValueEmpty, forKey: .type) + case .optionValueInteger(let value): + try container.encode(Kind.optionValueInteger, forKey: .type) + try value.encode(to: encoder) + case .optionValueString(let value): + try container.encode(Kind.optionValueString, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Represents a boolean option +public struct OptionValueBoolean: Codable, Equatable, Hashable { + + /// The value of the option + public let value: Bool + + + public init(value: Bool) { + self.value = value + } +} + +/// Represents an integer option +public struct OptionValueInteger: Codable, Equatable, Hashable { + + /// The value of the option + public let value: TdInt64 + + + public init(value: TdInt64) { + self.value = value + } +} + +/// Represents a string option +public struct OptionValueString: Codable, Equatable, Hashable { + + /// The value of the option + public let value: String + + + public init(value: String) { + self.value = value + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OrderInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OrderInfo.swift new file mode 100644 index 0000000000..06a5a73a42 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/OrderInfo.swift @@ -0,0 +1,41 @@ +// +// OrderInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Order information +public struct OrderInfo: Codable, Equatable, Hashable { + + /// Email address of the user + public let emailAddress: String + + /// Name of the user + public let name: String + + /// Phone number of the user + public let phoneNumber: String + + /// Shipping address for this order; may be null + public let shippingAddress: Address? + + + public init( + emailAddress: String, + name: String, + phoneNumber: String, + shippingAddress: Address? + ) { + self.emailAddress = emailAddress + self.name = name + self.phoneNumber = phoneNumber + self.shippingAddress = shippingAddress + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Outline.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Outline.swift new file mode 100644 index 0000000000..e0e8594edb --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Outline.swift @@ -0,0 +1,24 @@ +// +// Outline.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents outline of an image +public struct Outline: Codable, Equatable, Hashable { + + /// The list of closed vector paths + public let paths: [ClosedVectorPath] + + + public init(paths: [ClosedVectorPath]) { + self.paths = paths + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidMedia.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidMedia.swift new file mode 100644 index 0000000000..ef26cf957d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidMedia.swift @@ -0,0 +1,151 @@ +// +// PaidMedia.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a paid media +public indirect enum PaidMedia: Codable, Equatable, Hashable { + + /// The media is hidden until the invoice is paid + case paidMediaPreview(PaidMediaPreview) + + /// The media is a photo + case paidMediaPhoto(PaidMediaPhoto) + + /// The media is a video + case paidMediaVideo(PaidMediaVideo) + + /// The media is unsupported + case paidMediaUnsupported + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case paidMediaPreview + case paidMediaPhoto + case paidMediaVideo + case paidMediaUnsupported + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .paidMediaPreview: + let value = try PaidMediaPreview(from: decoder) + self = .paidMediaPreview(value) + case .paidMediaPhoto: + let value = try PaidMediaPhoto(from: decoder) + self = .paidMediaPhoto(value) + case .paidMediaVideo: + let value = try PaidMediaVideo(from: decoder) + self = .paidMediaVideo(value) + case .paidMediaUnsupported: + self = .paidMediaUnsupported + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .paidMediaPreview(let value): + try container.encode(Kind.paidMediaPreview, forKey: .type) + try value.encode(to: encoder) + case .paidMediaPhoto(let value): + try container.encode(Kind.paidMediaPhoto, forKey: .type) + try value.encode(to: encoder) + case .paidMediaVideo(let value): + try container.encode(Kind.paidMediaVideo, forKey: .type) + try value.encode(to: encoder) + case .paidMediaUnsupported: + try container.encode(Kind.paidMediaUnsupported, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The media is hidden until the invoice is paid +public struct PaidMediaPreview: Codable, Equatable, Hashable { + + /// Media duration, in seconds; 0 if unknown + public let duration: Int + + /// Media height; 0 if unknown + public let height: Int + + /// Media minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Media width; 0 if unknown + public let width: Int + + + public init( + duration: Int, + height: Int, + minithumbnail: Minithumbnail?, + width: Int + ) { + self.duration = duration + self.height = height + self.minithumbnail = minithumbnail + self.width = width + } +} + +/// The media is a photo +public struct PaidMediaPhoto: Codable, Equatable, Hashable { + + /// The photo + public let photo: Photo + + /// The video representing the live photo; may be null if the photo is static + public let video: Video? + + + public init( + photo: Photo, + video: Video? + ) { + self.photo = photo + self.video = video + } +} + +/// The media is a video +public struct PaidMediaVideo: Codable, Equatable, Hashable { + + /// Cover of the video; may be null if none + public let cover: Photo? + + /// Timestamp from which the video playing must start, in seconds + public let startTimestamp: Int + + /// The video + public let video: Video + + + public init( + cover: Photo?, + startTimestamp: Int, + video: Video + ) { + self.cover = cover + self.startTimestamp = startTimestamp + self.video = video + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidReactor.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidReactor.swift new file mode 100644 index 0000000000..56d17555c7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PaidReactor.swift @@ -0,0 +1,46 @@ +// +// PaidReactor.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a user who added paid reactions +public struct PaidReactor: Codable, Equatable, Hashable { + + /// True, if the reactor is anonymous + public let isAnonymous: Bool + + /// True, if the paid reaction was added by the current user + public let isMe: Bool + + /// True, if the reactor is one of the most active reactors; may be false if the reactor is the current user + public let isTop: Bool + + /// Identifier of the user or chat that added the reactions; may be null for anonymous reactors that aren't the current user + public let senderId: MessageSender? + + /// Number of Telegram Stars added + public let starCount: Int64 + + + public init( + isAnonymous: Bool, + isMe: Bool, + isTop: Bool, + senderId: MessageSender?, + starCount: Int64 + ) { + self.isAnonymous = isAnonymous + self.isMe = isMe + self.isTop = isTop + self.senderId = senderId + self.starCount = starCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Photo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Photo.swift new file mode 100644 index 0000000000..0b40d4585c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Photo.swift @@ -0,0 +1,36 @@ +// +// Photo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a photo +public struct Photo: Codable, Equatable, Hashable { + + /// True, if stickers were added to the photo. The list of corresponding sticker sets can be received using getAttachedStickerSets + public let hasStickers: Bool + + /// Photo minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Available variants of the photo, in different sizes + public let sizes: [PhotoSize] + + + public init( + hasStickers: Bool, + minithumbnail: Minithumbnail?, + sizes: [PhotoSize] + ) { + self.hasStickers = hasStickers + self.minithumbnail = minithumbnail + self.sizes = sizes + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PhotoSize.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PhotoSize.swift new file mode 100644 index 0000000000..b3f85adee8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PhotoSize.swift @@ -0,0 +1,46 @@ +// +// PhotoSize.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an image in JPEG format +public struct PhotoSize: Codable, Equatable, Hashable { + + /// Image height + public let height: Int + + /// Information about the image file + public let photo: File + + /// Sizes of progressive JPEG file prefixes, which can be used to preliminarily show the image; in bytes + public let progressiveSizes: [Int] + + /// Image type (see https://core.telegram.org/constructor/photoSize) + public let type: String + + /// Image width + public let width: Int + + + public init( + height: Int, + photo: File, + progressiveSizes: [Int], + type: String, + width: Int + ) { + self.height = height + self.photo = photo + self.progressiveSizes = progressiveSizes + self.type = type + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Point.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Point.swift new file mode 100644 index 0000000000..8c6a76ee43 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Point.swift @@ -0,0 +1,31 @@ +// +// Point.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// A point on a Cartesian plane +public struct Point: Codable, Equatable, Hashable { + + /// The point's first coordinate + public let x: Double + + /// The point's second coordinate + public let y: Double + + + public init( + x: Double, + y: Double + ) { + self.x = x + self.y = y + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Poll.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Poll.swift new file mode 100644 index 0000000000..e72aeb655c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Poll.swift @@ -0,0 +1,106 @@ +// +// Poll.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a poll +public struct Poll: Codable, Equatable, Hashable, Identifiable { + + /// True, if multiple answer options can be chosen simultaneously + public let allowsMultipleAnswers: Bool + + /// True, if the poll can be answered multiple times + public let allowsRevoting: Bool + + /// True, if the current user can get voters in the poll using getPollVoters + public let canGetVoters: Bool + + /// Point in time (Unix timestamp) when the poll will automatically be closed + public let closeDate: Int + + /// The list of two-letter ISO 3166-1 alpha-2 codes of countries, users from which will be able to vote. If empty, then all users can participate in the poll + public let countryCodes: [String] + + /// Unique poll identifier + public let id: TdInt64 + + /// True, if the poll is anonymous + public let isAnonymous: Bool + + /// True, if the poll is closed + public let isClosed: Bool + + /// True, if only the users that are members of the chat for more than a day will be able to vote + public let membersOnly: Bool + + /// Amount of time the poll will be active after creation, in seconds + public let openPeriod: Int + + /// The list of 0-based poll identifiers in which the options of the poll must be shown; empty if the order of options must not be changed + public let optionOrder: [Int] + + /// List of poll answer options + public let options: [PollOption] + + /// Poll question; 1-300 characters; may contain only custom emoji entities + public let question: FormattedText + + /// Identifiers of recent voters, if the poll is non-anonymous and poll results are available + public let recentVoterIds: [MessageSender] + + /// Total number of voters, participating in the poll + public let totalVoterCount: Int + + /// Type of the poll + public let type: PollType + + /// The reason describing, why the current user can't vote in the poll; may be null if the user can vote in the poll + public let voteRestrictionReason: PollVoteRestrictionReason? + + + public init( + allowsMultipleAnswers: Bool, + allowsRevoting: Bool, + canGetVoters: Bool, + closeDate: Int, + countryCodes: [String], + id: TdInt64, + isAnonymous: Bool, + isClosed: Bool, + membersOnly: Bool, + openPeriod: Int, + optionOrder: [Int], + options: [PollOption], + question: FormattedText, + recentVoterIds: [MessageSender], + totalVoterCount: Int, + type: PollType, + voteRestrictionReason: PollVoteRestrictionReason? + ) { + self.allowsMultipleAnswers = allowsMultipleAnswers + self.allowsRevoting = allowsRevoting + self.canGetVoters = canGetVoters + self.closeDate = closeDate + self.countryCodes = countryCodes + self.id = id + self.isAnonymous = isAnonymous + self.isClosed = isClosed + self.membersOnly = membersOnly + self.openPeriod = openPeriod + self.optionOrder = optionOrder + self.options = options + self.question = question + self.recentVoterIds = recentVoterIds + self.totalVoterCount = totalVoterCount + self.type = type + self.voteRestrictionReason = voteRestrictionReason + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollOption.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollOption.swift new file mode 100644 index 0000000000..50b8a124b4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollOption.swift @@ -0,0 +1,71 @@ +// +// PollOption.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes one answer option of a poll +public struct PollOption: Codable, Equatable, Hashable, Identifiable { + + /// Point in time (Unix timestamp) when the option was added; 0 if the option existed from creation of the poll + public let additionDate: Int + + /// Identifier of the user or chat who added the option; may be null if the option existed from creation of the poll + public let author: MessageSender? + + /// Unique identifier of the option in the poll; may be empty if yet unassigned + public let id: String + + /// True, if the option is being chosen by a pending setPollAnswer request + public let isBeingChosen: Bool + + /// True, if the option was chosen by the user + public let isChosen: Bool + + /// Option media; may be null if none. If present, currently, can be only of the types messageAnimation, messageLocation, messagePhoto, messageSticker, messageVenue, or messageVideo without caption + public let media: MessageContent? + + /// Identifiers of recent voters for the option, if the poll is non-anonymous and poll results are available + public let recentVoterIds: [MessageSender] + + /// Option text; 1-100 characters; may contain only custom emoji entities + public let text: FormattedText + + /// The percentage of votes for this option; 0-100 + public let votePercentage: Int + + /// Number of voters for this option, available only for closed or voted polls, or if the current user is the creator of the poll + public let voterCount: Int + + + public init( + additionDate: Int, + author: MessageSender?, + id: String, + isBeingChosen: Bool, + isChosen: Bool, + media: MessageContent?, + recentVoterIds: [MessageSender], + text: FormattedText, + votePercentage: Int, + voterCount: Int + ) { + self.additionDate = additionDate + self.author = author + self.id = id + self.isBeingChosen = isBeingChosen + self.isChosen = isChosen + self.media = media + self.recentVoterIds = recentVoterIds + self.text = text + self.votePercentage = votePercentage + self.voterCount = voterCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollType.swift new file mode 100644 index 0000000000..6530d59e1a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollType.swift @@ -0,0 +1,83 @@ +// +// PollType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of poll +public indirect enum PollType: Codable, Equatable, Hashable { + + /// A regular poll + case pollTypeRegular + + /// A poll in quiz mode, which has predefined correct answers + case pollTypeQuiz(PollTypeQuiz) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case pollTypeRegular + case pollTypeQuiz + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .pollTypeRegular: + self = .pollTypeRegular + case .pollTypeQuiz: + let value = try PollTypeQuiz(from: decoder) + self = .pollTypeQuiz(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .pollTypeRegular: + try container.encode(Kind.pollTypeRegular, forKey: .type) + case .pollTypeQuiz(let value): + try container.encode(Kind.pollTypeQuiz, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A poll in quiz mode, which has predefined correct answers +public struct PollTypeQuiz: Codable, Equatable, Hashable { + + /// Increasing list of 0-based identifiers of the correct answer options; empty for a yet unanswered poll + public let correctOptionIds: [Int] + + /// Text that is shown when the user chooses an incorrect answer or taps on the lamp icon; empty for a yet unanswered poll + public let explanation: FormattedText + + /// Media that is shown when the user chooses an incorrect answer or taps on the lamp icon; may be null if none or the poll is unanswered yet. If present, currently, can be only of the types messageAnimation, messageAudio, messageDocument, messageLocation, messagePhoto, messageVenue, or messageVideo without caption + public let explanationMedia: MessageContent? + + + public init( + correctOptionIds: [Int], + explanation: FormattedText, + explanationMedia: MessageContent? + ) { + self.correctOptionIds = correctOptionIds + self.explanation = explanation + self.explanationMedia = explanationMedia + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollVoteRestrictionReason.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollVoteRestrictionReason.swift new file mode 100644 index 0000000000..46113bf1b5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/PollVoteRestrictionReason.swift @@ -0,0 +1,117 @@ +// +// PollVoteRestrictionReason.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Reason of vote restriction in the poll for the current user +public indirect enum PollVoteRestrictionReason: Codable, Equatable, Hashable { + + /// The poll is closed + case pollVoteRestrictionReasonClosed + + /// The poll isn't sent yet + case pollVoteRestrictionReasonYetUnsent + + /// The poll is from a scheduled message + case pollVoteRestrictionReasonScheduled + + /// The user is from a country, users from which aren't allowed to vote + case pollVoteRestrictionReasonCountryRestricted(PollVoteRestrictionReasonCountryRestricted) + + /// The user must be a member of the chat for at least a day to vote + case pollVoteRestrictionReasonMembershipRequired(PollVoteRestrictionReasonMembershipRequired) + + /// The poll can't be voted by the user due to some other reason + case pollVoteRestrictionReasonOther + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case pollVoteRestrictionReasonClosed + case pollVoteRestrictionReasonYetUnsent + case pollVoteRestrictionReasonScheduled + case pollVoteRestrictionReasonCountryRestricted + case pollVoteRestrictionReasonMembershipRequired + case pollVoteRestrictionReasonOther + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .pollVoteRestrictionReasonClosed: + self = .pollVoteRestrictionReasonClosed + case .pollVoteRestrictionReasonYetUnsent: + self = .pollVoteRestrictionReasonYetUnsent + case .pollVoteRestrictionReasonScheduled: + self = .pollVoteRestrictionReasonScheduled + case .pollVoteRestrictionReasonCountryRestricted: + let value = try PollVoteRestrictionReasonCountryRestricted(from: decoder) + self = .pollVoteRestrictionReasonCountryRestricted(value) + case .pollVoteRestrictionReasonMembershipRequired: + let value = try PollVoteRestrictionReasonMembershipRequired(from: decoder) + self = .pollVoteRestrictionReasonMembershipRequired(value) + case .pollVoteRestrictionReasonOther: + self = .pollVoteRestrictionReasonOther + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .pollVoteRestrictionReasonClosed: + try container.encode(Kind.pollVoteRestrictionReasonClosed, forKey: .type) + case .pollVoteRestrictionReasonYetUnsent: + try container.encode(Kind.pollVoteRestrictionReasonYetUnsent, forKey: .type) + case .pollVoteRestrictionReasonScheduled: + try container.encode(Kind.pollVoteRestrictionReasonScheduled, forKey: .type) + case .pollVoteRestrictionReasonCountryRestricted(let value): + try container.encode(Kind.pollVoteRestrictionReasonCountryRestricted, forKey: .type) + try value.encode(to: encoder) + case .pollVoteRestrictionReasonMembershipRequired(let value): + try container.encode(Kind.pollVoteRestrictionReasonMembershipRequired, forKey: .type) + try value.encode(to: encoder) + case .pollVoteRestrictionReasonOther: + try container.encode(Kind.pollVoteRestrictionReasonOther, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The user is from a country, users from which aren't allowed to vote +public struct PollVoteRestrictionReasonCountryRestricted: Codable, Equatable, Hashable { + + /// Two-letter ISO 3166-1 alpha-2 code of the current user's country + public let countryCode: String + + + public init(countryCode: String) { + self.countryCode = countryCode + } +} + +/// The user must be a member of the chat for at least a day to vote +public struct PollVoteRestrictionReasonMembershipRequired: Codable, Equatable, Hashable { + + /// Identifier of the chat which must be joined for at least a day before the user can vote + public let chatId: Int64 + + + public init(chatId: Int64) { + self.chatId = chatId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProductInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProductInfo.swift new file mode 100644 index 0000000000..f8e3644f0b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProductInfo.swift @@ -0,0 +1,35 @@ +// +// ProductInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a product that can be paid with invoice +public struct ProductInfo: Codable, Equatable, Hashable { + + public let description: FormattedText + + /// Product photo; may be null + public let photo: Photo? + + /// Product title + public let title: String + + + public init( + description: FormattedText, + photo: Photo?, + title: String + ) { + self.description = description + self.photo = photo + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProfilePhoto.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProfilePhoto.swift new file mode 100644 index 0000000000..90be0c0ab4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProfilePhoto.swift @@ -0,0 +1,51 @@ +// +// ProfilePhoto.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a user profile photo +public struct ProfilePhoto: Codable, Equatable, Hashable, Identifiable { + + /// A big (640x640) user profile photo. The file can be downloaded only before the photo is changed + public let big: File + + /// True, if the photo has animated variant + public let hasAnimation: Bool + + /// Photo identifier; 0 for an empty photo. Can be used to find a photo in a list of user profile photos + public let id: TdInt64 + + /// True, if the photo is visible only for the current user + public let isPersonal: Bool + + /// User profile photo minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// A small (160x160) user profile photo. The file can be downloaded only before the photo is changed + public let small: File + + + public init( + big: File, + hasAnimation: Bool, + id: TdInt64, + isPersonal: Bool, + minithumbnail: Minithumbnail?, + small: File + ) { + self.big = big + self.hasAnimation = hasAnimation + self.id = id + self.isPersonal = isPersonal + self.minithumbnail = minithumbnail + self.small = small + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Proxy.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Proxy.swift new file mode 100644 index 0000000000..33237f7b3d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Proxy.swift @@ -0,0 +1,36 @@ +// +// Proxy.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a proxy server +public struct Proxy: Codable, Equatable, Hashable { + + /// Proxy server port + public let port: Int + + /// Proxy server domain or IP address + public let server: String + + /// Type of the proxy + public let type: ProxyType + + + public init( + port: Int, + server: String, + type: ProxyType + ) { + self.port = port + self.server = server + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProxyType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProxyType.swift new file mode 100644 index 0000000000..13ced61c0a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ProxyType.swift @@ -0,0 +1,126 @@ +// +// ProxyType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the type of proxy server +public indirect enum ProxyType: Codable, Equatable, Hashable { + + /// A SOCKS5 proxy server + case proxyTypeSocks5(ProxyTypeSocks5) + + /// A HTTP transparent proxy server + case proxyTypeHttp(ProxyTypeHttp) + + /// An MTProto proxy server + case proxyTypeMtproto(ProxyTypeMtproto) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case proxyTypeSocks5 + case proxyTypeHttp + case proxyTypeMtproto + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .proxyTypeSocks5: + let value = try ProxyTypeSocks5(from: decoder) + self = .proxyTypeSocks5(value) + case .proxyTypeHttp: + let value = try ProxyTypeHttp(from: decoder) + self = .proxyTypeHttp(value) + case .proxyTypeMtproto: + let value = try ProxyTypeMtproto(from: decoder) + self = .proxyTypeMtproto(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .proxyTypeSocks5(let value): + try container.encode(Kind.proxyTypeSocks5, forKey: .type) + try value.encode(to: encoder) + case .proxyTypeHttp(let value): + try container.encode(Kind.proxyTypeHttp, forKey: .type) + try value.encode(to: encoder) + case .proxyTypeMtproto(let value): + try container.encode(Kind.proxyTypeMtproto, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A SOCKS5 proxy server +public struct ProxyTypeSocks5: Codable, Equatable, Hashable { + + /// Password for logging in; may be empty + public let password: String + + /// Username for logging in; may be empty + public let username: String + + + public init( + password: String, + username: String + ) { + self.password = password + self.username = username + } +} + +/// A HTTP transparent proxy server +public struct ProxyTypeHttp: Codable, Equatable, Hashable { + + /// Pass true if the proxy supports only HTTP requests and doesn't support transparent TCP connections via HTTP CONNECT method + public let httpOnly: Bool + + /// Password for logging in; may be empty + public let password: String + + /// Username for logging in; may be empty + public let username: String + + + public init( + httpOnly: Bool, + password: String, + username: String + ) { + self.httpOnly = httpOnly + self.password = password + self.username = username + } +} + +/// An MTProto proxy server +public struct ProxyTypeMtproto: Codable, Equatable, Hashable { + + /// The proxy's secret in hexadecimal encoding + public let secret: String + + + public init(secret: String) { + self.secret = secret + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReactionType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReactionType.swift new file mode 100644 index 0000000000..167419471c --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReactionType.swift @@ -0,0 +1,93 @@ +// +// ReactionType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of message reaction +public indirect enum ReactionType: Codable, Equatable, Hashable { + + /// A reaction with an emoji + case reactionTypeEmoji(ReactionTypeEmoji) + + /// A reaction with a custom emoji + case reactionTypeCustomEmoji(ReactionTypeCustomEmoji) + + /// The paid reaction in a channel chat + case reactionTypePaid + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case reactionTypeEmoji + case reactionTypeCustomEmoji + case reactionTypePaid + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .reactionTypeEmoji: + let value = try ReactionTypeEmoji(from: decoder) + self = .reactionTypeEmoji(value) + case .reactionTypeCustomEmoji: + let value = try ReactionTypeCustomEmoji(from: decoder) + self = .reactionTypeCustomEmoji(value) + case .reactionTypePaid: + self = .reactionTypePaid + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .reactionTypeEmoji(let value): + try container.encode(Kind.reactionTypeEmoji, forKey: .type) + try value.encode(to: encoder) + case .reactionTypeCustomEmoji(let value): + try container.encode(Kind.reactionTypeCustomEmoji, forKey: .type) + try value.encode(to: encoder) + case .reactionTypePaid: + try container.encode(Kind.reactionTypePaid, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A reaction with an emoji +public struct ReactionTypeEmoji: Codable, Equatable, Hashable { + + /// Text representation of the reaction + public let emoji: String + + + public init(emoji: String) { + self.emoji = emoji + } +} + +/// A reaction with a custom emoji +public struct ReactionTypeCustomEmoji: Codable, Equatable, Hashable { + + /// Unique identifier of the custom emoji + public let customEmojiId: TdInt64 + + + public init(customEmojiId: TdInt64) { + self.customEmojiId = customEmojiId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RemoteFile.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RemoteFile.swift new file mode 100644 index 0000000000..9617850bf2 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RemoteFile.swift @@ -0,0 +1,46 @@ +// +// RemoteFile.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a remote file +public struct RemoteFile: Codable, Equatable, Hashable, Identifiable { + + /// Remote file identifier; may be empty. Can be used by the current user across application restarts or even from other devices. Uniquely identifies a file, but a file can have a lot of different valid identifiers. If the identifier starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known. If downloadFile/addFileToDownloads is called on such a file or if it is sent to a secret chat, TDLib starts a file generation process by sending updateFileGenerationStart to the application with the HTTP URL in the original_path and "#url#" as the conversion string. Application must generate the file by downloading it to the specified location + public let id: String + + /// True, if the file is currently being uploaded (or a remote copy is being generated by some other means) + public let isUploadingActive: Bool + + /// True, if a remote copy is fully available + public let isUploadingCompleted: Bool + + /// Unique file identifier; may be empty if unknown. The unique file identifier which is the same for the same file even for different users and is persistent over time + public let uniqueId: String + + /// Size of the remote available part of the file, in bytes; 0 if unknown + public let uploadedSize: Int64 + + + public init( + id: String, + isUploadingActive: Bool, + isUploadingCompleted: Bool, + uniqueId: String, + uploadedSize: Int64 + ) { + self.id = id + self.isUploadingActive = isUploadingActive + self.isUploadingCompleted = isUploadingCompleted + self.uniqueId = uniqueId + self.uploadedSize = uploadedSize + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReplyMarkup.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReplyMarkup.swift new file mode 100644 index 0000000000..20ef313575 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ReplyMarkup.swift @@ -0,0 +1,163 @@ +// +// ReplyMarkup.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains a description of a custom keyboard and actions that can be done with it to quickly reply to bots +public indirect enum ReplyMarkup: Codable, Equatable, Hashable { + + /// Instructs application to remove the keyboard once this message has been received. This kind of keyboard can't be received in an incoming message; instead, updateChatReplyMarkup with reply_markup_message == null will be sent + case replyMarkupRemoveKeyboard(ReplyMarkupRemoveKeyboard) + + /// Instructs application to force a reply to this message + case replyMarkupForceReply(ReplyMarkupForceReply) + + /// Contains a custom keyboard layout to quickly reply to bots + case replyMarkupShowKeyboard(ReplyMarkupShowKeyboard) + + /// Contains an inline keyboard layout + case replyMarkupInlineKeyboard(ReplyMarkupInlineKeyboard) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case replyMarkupRemoveKeyboard + case replyMarkupForceReply + case replyMarkupShowKeyboard + case replyMarkupInlineKeyboard + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .replyMarkupRemoveKeyboard: + let value = try ReplyMarkupRemoveKeyboard(from: decoder) + self = .replyMarkupRemoveKeyboard(value) + case .replyMarkupForceReply: + let value = try ReplyMarkupForceReply(from: decoder) + self = .replyMarkupForceReply(value) + case .replyMarkupShowKeyboard: + let value = try ReplyMarkupShowKeyboard(from: decoder) + self = .replyMarkupShowKeyboard(value) + case .replyMarkupInlineKeyboard: + let value = try ReplyMarkupInlineKeyboard(from: decoder) + self = .replyMarkupInlineKeyboard(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .replyMarkupRemoveKeyboard(let value): + try container.encode(Kind.replyMarkupRemoveKeyboard, forKey: .type) + try value.encode(to: encoder) + case .replyMarkupForceReply(let value): + try container.encode(Kind.replyMarkupForceReply, forKey: .type) + try value.encode(to: encoder) + case .replyMarkupShowKeyboard(let value): + try container.encode(Kind.replyMarkupShowKeyboard, forKey: .type) + try value.encode(to: encoder) + case .replyMarkupInlineKeyboard(let value): + try container.encode(Kind.replyMarkupInlineKeyboard, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Instructs application to remove the keyboard once this message has been received. This kind of keyboard can't be received in an incoming message; instead, updateChatReplyMarkup with reply_markup_message == null will be sent +public struct ReplyMarkupRemoveKeyboard: Codable, Equatable, Hashable { + + /// True, if the keyboard is removed only for the mentioned users or the target user of a reply + public let isPersonal: Bool + + + public init(isPersonal: Bool) { + self.isPersonal = isPersonal + } +} + +/// Instructs application to force a reply to this message +public struct ReplyMarkupForceReply: Codable, Equatable, Hashable { + + /// If non-empty, the placeholder to be shown in the input field when the reply is active; 0-64 characters + public let inputFieldPlaceholder: String + + /// True, if a forced reply must automatically be shown to the current user. For outgoing messages, specify true to show the forced reply only for the mentioned users and for the target user of a reply + public let isPersonal: Bool + + + public init( + inputFieldPlaceholder: String, + isPersonal: Bool + ) { + self.inputFieldPlaceholder = inputFieldPlaceholder + self.isPersonal = isPersonal + } +} + +/// Contains a custom keyboard layout to quickly reply to bots +public struct ReplyMarkupShowKeyboard: Codable, Equatable, Hashable { + + /// If non-empty, the placeholder to be shown in the input field when the keyboard is active; 0-64 characters + public let inputFieldPlaceholder: String + + /// True, if the keyboard is expected to always be shown when the ordinary keyboard is hidden + public let isPersistent: Bool + + /// True, if the keyboard must automatically be shown to the current user. For outgoing messages, specify true to show the keyboard only for the mentioned users and for the target user of a reply + public let isPersonal: Bool + + /// True, if the application needs to hide the keyboard after use + public let oneTime: Bool + + /// True, if the application needs to resize the keyboard vertically + public let resizeKeyboard: Bool + + /// A list of rows of bot keyboard buttons + public let rows: [[KeyboardButton]] + + + public init( + inputFieldPlaceholder: String, + isPersistent: Bool, + isPersonal: Bool, + oneTime: Bool, + resizeKeyboard: Bool, + rows: [[KeyboardButton]] + ) { + self.inputFieldPlaceholder = inputFieldPlaceholder + self.isPersistent = isPersistent + self.isPersonal = isPersonal + self.oneTime = oneTime + self.resizeKeyboard = resizeKeyboard + self.rows = rows + } +} + +/// Contains an inline keyboard layout +public struct ReplyMarkupInlineKeyboard: Codable, Equatable, Hashable { + + /// A list of rows of inline keyboard buttons + public let rows: [[InlineKeyboardButton]] + + + public init(rows: [[InlineKeyboardButton]]) { + self.rows = rows + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RequestQrCodeAuthentication.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RequestQrCodeAuthentication.swift new file mode 100644 index 0000000000..9aa50fd1a6 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RequestQrCodeAuthentication.swift @@ -0,0 +1,24 @@ +// +// RequestQrCodeAuthentication.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Requests QR code authentication by scanning a QR code on another logged in device. Works only when the current authorization state is authorizationStateWaitPhoneNumber, or if there is no pending authentication query and the current authorization state is authorizationStateWaitPremiumPurchase, authorizationStateWaitEmailAddress, authorizationStateWaitEmailCode, authorizationStateWaitCode, authorizationStateWaitRegistration, or authorizationStateWaitPassword +public struct RequestQrCodeAuthentication: Codable, Equatable, Hashable { + + /// List of user identifiers of other users currently using the application + public let otherUserIds: [Int64]? + + + public init(otherUserIds: [Int64]?) { + self.otherUserIds = otherUserIds + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RestrictionInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RestrictionInfo.swift new file mode 100644 index 0000000000..103136aa5d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/RestrictionInfo.swift @@ -0,0 +1,31 @@ +// +// RestrictionInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about restrictions that must be applied to a chat or a message +public struct RestrictionInfo: Codable, Equatable, Hashable { + + /// True, if media content of the messages must be hidden with 18+ spoiler. Use value of the option "can_ignore_sensitive_content_restrictions" to check whether the current user can ignore the restriction. If age verification parameters were received in updateAgeVerificationParameters, then the user must complete age verification to ignore the restriction. Set the option "ignore_sensitive_content_restrictions" to true if the user passes age verification + public let hasSensitiveContent: Bool + + /// A human-readable description of the reason why access to the content must be restricted. If empty, then the content can be accessed, but may be covered by hidden with 18+ spoiler anyway + public let restrictionReason: String + + + public init( + hasSensitiveContent: Bool, + restrictionReason: String + ) { + self.hasSensitiveContent = hasSensitiveContent + self.restrictionReason = restrictionReason + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SendMessage.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SendMessage.swift new file mode 100644 index 0000000000..a3b004532f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SendMessage.swift @@ -0,0 +1,51 @@ +// +// SendMessage.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Sends a message. Returns the sent message +public struct SendMessage: Codable, Equatable, Hashable { + + /// Target chat + public let chatId: Int64? + + /// The content of the message to be sent + public let inputMessageContent: InputMessageContent? + + /// Options to be used to send the message; pass null to use default options + public let options: MessageSendOptions? + + /// Markup for replying to the message; pass null if none; for bots only + public let replyMarkup: ReplyMarkup? + + /// Information about the message or story to be replied; pass null if none + public let replyTo: InputMessageReplyTo? + + /// Topic in which the message will be sent; pass null if none + public let topicId: MessageTopic? + + + public init( + chatId: Int64?, + inputMessageContent: InputMessageContent?, + options: MessageSendOptions?, + replyMarkup: ReplyMarkup?, + replyTo: InputMessageReplyTo?, + topicId: MessageTopic? + ) { + self.chatId = chatId + self.inputMessageContent = inputMessageContent + self.options = options + self.replyMarkup = replyMarkup + self.replyTo = replyTo + self.topicId = topicId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetChatDraftMessage.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetChatDraftMessage.swift new file mode 100644 index 0000000000..0bf3536f8e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetChatDraftMessage.swift @@ -0,0 +1,36 @@ +// +// SetChatDraftMessage.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Changes the draft message in a chat or a topic +public struct SetChatDraftMessage: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64? + + /// New draft message; pass null to remove the draft. All files in draft message content must be of the type inputFileLocal. Media thumbnails and captions are ignored + public let draftMessage: DraftMessage? + + /// Topic in which the draft will be changed; pass null to change the draft for the chat itself + public let topicId: MessageTopic? + + + public init( + chatId: Int64?, + draftMessage: DraftMessage?, + topicId: MessageTopic? + ) { + self.chatId = chatId + self.draftMessage = draftMessage + self.topicId = topicId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetLogVerbosityLevel.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetLogVerbosityLevel.swift new file mode 100644 index 0000000000..86cfc7abcf --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetLogVerbosityLevel.swift @@ -0,0 +1,24 @@ +// +// SetLogVerbosityLevel.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Sets the verbosity level of the internal logging of TDLib. Can be called synchronously +public struct SetLogVerbosityLevel: Codable, Equatable, Hashable { + + /// New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging + public let newVerbosityLevel: Int? + + + public init(newVerbosityLevel: Int?) { + self.newVerbosityLevel = newVerbosityLevel + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetOption.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetOption.swift new file mode 100644 index 0000000000..1eebdcefd1 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetOption.swift @@ -0,0 +1,31 @@ +// +// SetOption.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Sets the value of an option. (Check the list of available options on https://core.telegram.org/tdlib/options.) Only writable options can be set. Can be called before authorization +public struct SetOption: Codable, Equatable, Hashable { + + /// The name of the option + public let name: String? + + /// The new value of the option; pass null to reset option value to a default value + public let value: OptionValue? + + + public init( + name: String?, + value: OptionValue? + ) { + self.name = name + self.value = value + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetPollAnswer.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetPollAnswer.swift new file mode 100644 index 0000000000..5f0e7bf349 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetPollAnswer.swift @@ -0,0 +1,36 @@ +// +// SetPollAnswer.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Changes the user answer to a poll +public struct SetPollAnswer: Codable, Equatable, Hashable { + + /// Identifier of the chat to which the poll belongs + public let chatId: Int64? + + /// Identifier of the message containing the poll + public let messageId: Int64? + + /// 0-based identifiers of answer options, chosen by the user. User can choose more than 1 answer option only is the poll allows multiple answers + public let optionIds: [Int]? + + + public init( + chatId: Int64?, + messageId: Int64?, + optionIds: [Int]? + ) { + self.chatId = chatId + self.messageId = messageId + self.optionIds = optionIds + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetTdlibParameters.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetTdlibParameters.swift new file mode 100644 index 0000000000..accdf22207 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SetTdlibParameters.swift @@ -0,0 +1,91 @@ +// +// SetTdlibParameters.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Sets the parameters for TDLib initialization. Works only when the current authorization state is authorizationStateWaitTdlibParameters +public struct SetTdlibParameters: Codable, Equatable, Hashable { + + /// Application identifier hash for Telegram API access, which can be obtained at https://my.telegram.org + public let apiHash: String? + + /// Application identifier for Telegram API access, which can be obtained at https://my.telegram.org + public let apiId: Int? + + /// Application version; must be non-empty + public let applicationVersion: String? + + /// The path to the directory for the persistent database; if empty, the current working directory will be used + public let databaseDirectory: String? + + /// Encryption key for the database. If the encryption key is invalid, then an error with code 401 will be returned + public let databaseEncryptionKey: Data? + + /// Model of the device the application is being run on; must be non-empty + public let deviceModel: String? + + /// The path to the directory for storing files; if empty, database_directory will be used + public let filesDirectory: String? + + /// IETF language tag of the user's operating system language; must be non-empty + public let systemLanguageCode: String? + + /// Version of the operating system the application is being run on. If empty, the version is automatically detected by TDLib + public let systemVersion: String? + + /// Pass true to keep cache of users, basic groups, supergroups, channels and secret chats between restarts. Implies use_file_database + public let useChatInfoDatabase: Bool? + + /// Pass true to keep information about downloaded and uploaded files between application restarts + public let useFileDatabase: Bool? + + /// Pass true to keep cache of chats and messages between restarts. Implies use_chat_info_database + public let useMessageDatabase: Bool? + + /// Pass true to enable support for secret chats + public let useSecretChats: Bool? + + /// Pass true to use Telegram test environment instead of the production environment + public let useTestDc: Bool? + + + public init( + apiHash: String?, + apiId: Int?, + applicationVersion: String?, + databaseDirectory: String?, + databaseEncryptionKey: Data?, + deviceModel: String?, + filesDirectory: String?, + systemLanguageCode: String?, + systemVersion: String?, + useChatInfoDatabase: Bool?, + useFileDatabase: Bool?, + useMessageDatabase: Bool?, + useSecretChats: Bool?, + useTestDc: Bool? + ) { + self.apiHash = apiHash + self.apiId = apiId + self.applicationVersion = applicationVersion + self.databaseDirectory = databaseDirectory + self.databaseEncryptionKey = databaseEncryptionKey + self.deviceModel = deviceModel + self.filesDirectory = filesDirectory + self.systemLanguageCode = systemLanguageCode + self.systemVersion = systemVersion + self.useChatInfoDatabase = useChatInfoDatabase + self.useFileDatabase = useFileDatabase + self.useMessageDatabase = useMessageDatabase + self.useSecretChats = useSecretChats + self.useTestDc = useTestDc + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SettingsSection.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SettingsSection.swift new file mode 100644 index 0000000000..009d7873c0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SettingsSection.swift @@ -0,0 +1,405 @@ +// +// SettingsSection.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a section of the application settings +public indirect enum SettingsSection: Codable, Equatable, Hashable { + + /// The appearance section + case settingsSectionAppearance(SettingsSectionAppearance) + + /// The "Ask a question" section + case settingsSectionAskQuestion + + /// The "Telegram Business" section + case settingsSectionBusiness(SettingsSectionBusiness) + + /// The chat folder settings section + case settingsSectionChatFolders(SettingsSectionChatFolders) + + /// The data and storage settings section + case settingsSectionDataAndStorage(SettingsSectionDataAndStorage) + + /// The Devices section + case settingsSectionDevices(SettingsSectionDevices) + + /// The profile edit section + case settingsSectionEditProfile(SettingsSectionEditProfile) + + /// The FAQ section + case settingsSectionFaq + + /// The "Telegram Features" section + case settingsSectionFeatures + + /// The in-app browser settings section + case settingsSectionInAppBrowser(SettingsSectionInAppBrowser) + + /// The application language section + case settingsSectionLanguage(SettingsSectionLanguage) + + /// The Telegram Star balance and transaction section + case settingsSectionMyStars(SettingsSectionMyStars) + + /// The Toncoin balance and transaction section + case settingsSectionMyToncoins + + /// The notification settings section + case settingsSectionNotifications(SettingsSectionNotifications) + + /// The power saving settings section + case settingsSectionPowerSaving(SettingsSectionPowerSaving) + + /// The "Telegram Premium" section + case settingsSectionPremium + + /// The privacy and security section + case settingsSectionPrivacyAndSecurity(SettingsSectionPrivacyAndSecurity) + + /// The "Privacy Policy" section + case settingsSectionPrivacyPolicy + + /// The current user's QR code section + case settingsSectionQrCode(SettingsSectionQrCode) + + /// Search in Settings + case settingsSectionSearch + + /// The "Send a gift" section + case settingsSectionSendGift(SettingsSectionSendGift) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case settingsSectionAppearance + case settingsSectionAskQuestion + case settingsSectionBusiness + case settingsSectionChatFolders + case settingsSectionDataAndStorage + case settingsSectionDevices + case settingsSectionEditProfile + case settingsSectionFaq + case settingsSectionFeatures + case settingsSectionInAppBrowser + case settingsSectionLanguage + case settingsSectionMyStars + case settingsSectionMyToncoins + case settingsSectionNotifications + case settingsSectionPowerSaving + case settingsSectionPremium + case settingsSectionPrivacyAndSecurity + case settingsSectionPrivacyPolicy + case settingsSectionQrCode + case settingsSectionSearch + case settingsSectionSendGift + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .settingsSectionAppearance: + let value = try SettingsSectionAppearance(from: decoder) + self = .settingsSectionAppearance(value) + case .settingsSectionAskQuestion: + self = .settingsSectionAskQuestion + case .settingsSectionBusiness: + let value = try SettingsSectionBusiness(from: decoder) + self = .settingsSectionBusiness(value) + case .settingsSectionChatFolders: + let value = try SettingsSectionChatFolders(from: decoder) + self = .settingsSectionChatFolders(value) + case .settingsSectionDataAndStorage: + let value = try SettingsSectionDataAndStorage(from: decoder) + self = .settingsSectionDataAndStorage(value) + case .settingsSectionDevices: + let value = try SettingsSectionDevices(from: decoder) + self = .settingsSectionDevices(value) + case .settingsSectionEditProfile: + let value = try SettingsSectionEditProfile(from: decoder) + self = .settingsSectionEditProfile(value) + case .settingsSectionFaq: + self = .settingsSectionFaq + case .settingsSectionFeatures: + self = .settingsSectionFeatures + case .settingsSectionInAppBrowser: + let value = try SettingsSectionInAppBrowser(from: decoder) + self = .settingsSectionInAppBrowser(value) + case .settingsSectionLanguage: + let value = try SettingsSectionLanguage(from: decoder) + self = .settingsSectionLanguage(value) + case .settingsSectionMyStars: + let value = try SettingsSectionMyStars(from: decoder) + self = .settingsSectionMyStars(value) + case .settingsSectionMyToncoins: + self = .settingsSectionMyToncoins + case .settingsSectionNotifications: + let value = try SettingsSectionNotifications(from: decoder) + self = .settingsSectionNotifications(value) + case .settingsSectionPowerSaving: + let value = try SettingsSectionPowerSaving(from: decoder) + self = .settingsSectionPowerSaving(value) + case .settingsSectionPremium: + self = .settingsSectionPremium + case .settingsSectionPrivacyAndSecurity: + let value = try SettingsSectionPrivacyAndSecurity(from: decoder) + self = .settingsSectionPrivacyAndSecurity(value) + case .settingsSectionPrivacyPolicy: + self = .settingsSectionPrivacyPolicy + case .settingsSectionQrCode: + let value = try SettingsSectionQrCode(from: decoder) + self = .settingsSectionQrCode(value) + case .settingsSectionSearch: + self = .settingsSectionSearch + case .settingsSectionSendGift: + let value = try SettingsSectionSendGift(from: decoder) + self = .settingsSectionSendGift(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .settingsSectionAppearance(let value): + try container.encode(Kind.settingsSectionAppearance, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionAskQuestion: + try container.encode(Kind.settingsSectionAskQuestion, forKey: .type) + case .settingsSectionBusiness(let value): + try container.encode(Kind.settingsSectionBusiness, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionChatFolders(let value): + try container.encode(Kind.settingsSectionChatFolders, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionDataAndStorage(let value): + try container.encode(Kind.settingsSectionDataAndStorage, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionDevices(let value): + try container.encode(Kind.settingsSectionDevices, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionEditProfile(let value): + try container.encode(Kind.settingsSectionEditProfile, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionFaq: + try container.encode(Kind.settingsSectionFaq, forKey: .type) + case .settingsSectionFeatures: + try container.encode(Kind.settingsSectionFeatures, forKey: .type) + case .settingsSectionInAppBrowser(let value): + try container.encode(Kind.settingsSectionInAppBrowser, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionLanguage(let value): + try container.encode(Kind.settingsSectionLanguage, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionMyStars(let value): + try container.encode(Kind.settingsSectionMyStars, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionMyToncoins: + try container.encode(Kind.settingsSectionMyToncoins, forKey: .type) + case .settingsSectionNotifications(let value): + try container.encode(Kind.settingsSectionNotifications, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionPowerSaving(let value): + try container.encode(Kind.settingsSectionPowerSaving, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionPremium: + try container.encode(Kind.settingsSectionPremium, forKey: .type) + case .settingsSectionPrivacyAndSecurity(let value): + try container.encode(Kind.settingsSectionPrivacyAndSecurity, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionPrivacyPolicy: + try container.encode(Kind.settingsSectionPrivacyPolicy, forKey: .type) + case .settingsSectionQrCode(let value): + try container.encode(Kind.settingsSectionQrCode, forKey: .type) + try value.encode(to: encoder) + case .settingsSectionSearch: + try container.encode(Kind.settingsSectionSearch, forKey: .type) + case .settingsSectionSendGift(let value): + try container.encode(Kind.settingsSectionSendGift, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The appearance section +public struct SettingsSectionAppearance: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "themes", "themes/edit", "themes/create", "wallpapers", "wallpapers/edit", "wallpapers/set", "wallpapers/choose-photo", "your-color/profile", "your-color/profile/add-icons", "your-color/profile/use-gift", "your-color/profile/reset", "your-color/name", "your-color/name/add-icons", "your-color/name/use-gift", "night-mode", "auto-night-mode", "text-size", "text-size/use-system", "message-corners", "animations", "stickers-and-emoji", "stickers-and-emoji/edit", "stickers-and-emoji/trending", "stickers-and-emoji/archived", "stickers-and-emoji/archived/edit", "stickers-and-emoji/emoji", "stickers-and-emoji/emoji/edit", "stickers-and-emoji/emoji/archived", "stickers-and-emoji/emoji/archived/edit", "stickers-and-emoji/emoji/suggest", "stickers-and-emoji/emoji/quick-reaction", "stickers-and-emoji/emoji/quick-reaction/choose", "stickers-and-emoji/suggest-by-emoji", "stickers-and-emoji/large-emoji", "stickers-and-emoji/dynamic-order", "stickers-and-emoji/emoji/show-more", "app-icon", "tap-for-next-media" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The "Telegram Business" section +public struct SettingsSectionBusiness: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "do-not-hide-ads" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The chat folder settings section +public struct SettingsSectionChatFolders: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "edit", "create", "add-recommended", "show-tags", "tab-view" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The data and storage settings section +public struct SettingsSectionDataAndStorage: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "storage", "storage/edit", "storage/auto-remove", "storage/clear-cache", "storage/max-cache", "usage", "usage/mobile", "usage/wifi", "usage/reset", "usage/roaming", "auto-download/mobile", "auto-download/mobile/enable", "auto-download/mobile/usage", "auto-download/mobile/photos", "auto-download/mobile/stories", "auto-download/mobile/videos", "auto-download/mobile/files", "auto-download/wifi", "auto-download/wifi/enable", "auto-download/wifi/usage", "auto-download/wifi/photos", "auto-download/wifi/stories", "auto-download/wifi/videos", "auto-download/wifi/files", "auto-download/roaming", "auto-download/roaming/enable", "auto-download/roaming/usage", "auto-download/roaming/photos", "auto-download/roaming/stories", "auto-download/roaming/videos", "auto-download/roaming/files", "auto-download/reset", "save-to-photos/chats", "save-to-photos/chats/max-video-size", "save-to-photos/chats/add-exception", "save-to-photos/chats/delete-all", "save-to-photos/groups", "save-to-photos/groups/max-video-size", "save-to-photos/groups/add-exception", "save-to-photos/groups/delete-all", "save-to-photos/channels", "save-to-photos/channels/max-video-size", "save-to-photos/channels/add-exception", "save-to-photos/channels/delete-all", "less-data-calls", "open-links", "share-sheet", "share-sheet/suggested-chats", "share-sheet/suggest-by", "share-sheet/reset", "saved-edited-photos", "pause-music", "raise-to-listen", "raise-to-speak", "show-18-content", "proxy", "proxy/edit", "proxy/use-proxy", "proxy/add-proxy", "proxy/share-list", "proxy/use-for-calls" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The Devices section +public struct SettingsSectionDevices: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "edit", "link-desktop", "terminate-sessions", "auto-terminate" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The profile edit section +public struct SettingsSectionEditProfile: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "set-photo", "first-name", "last-name", "emoji-status", "bio", "birthday", "change-number", "username", "your-color", "channel", "add-account", "log-out", "profile-color/profile", "profile-color/profile/add-icons", "profile-color/profile/use-gift", "profile-color/name", "profile-color/name/add-icons", "profile-color/name/use-gift", "profile-photo/use-emoji" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The in-app browser settings section +public struct SettingsSectionInAppBrowser: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "enable-browser", "clear-cookies", "clear-cache", "history", "clear-history", "never-open", "clear-list", "search" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The application language section +public struct SettingsSectionLanguage: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "show-button" for Show Translate Button toggle, "translate-chats" for Translate Entire Chats toggle, "do-not-translate" - for Do Not Translate language list + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The Telegram Star balance and transaction section +public struct SettingsSectionMyStars: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "top-up", "stats", "gift", "earn" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The notification settings section +public struct SettingsSectionNotifications: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "accounts", "private-chats", "private-chats/edit", "private-chats/show", "private-chats/preview", "private-chats/sound", "private-chats/add-exception", "private-chats/delete-exceptions", "private-chats/light-color", "private-chats/vibrate", "private-chats/priority", "groups", "groups/edit", "groups/show", "groups/preview", "groups/sound", "groups/add-exception", "groups/delete-exceptions", "groups/light-color", "groups/vibrate", "groups/priority", "channels", "channels/edit", "channels/show", "channels/preview", "channels/sound", "channels/add-exception", "channels/delete-exceptions", "channels/light-color", "channels/vibrate", "channels/priority", "stories", "stories/new", "stories/important", "stories/show-sender", "stories/sound", "stories/add-exception", "stories/delete-exceptions", "stories/light-color", "stories/vibrate", "stories/priority", "reactions", "reactions/messages", "reactions/stories", "reactions/show-sender", "reactions/sound", "reactions/light-color", "reactions/vibrate", "reactions/priority", "in-app-sounds", "in-app-vibrate", "in-app-preview", "in-chat-sounds", "in-app-popup", "lock-screen-names", "include-channels", "include-muted-chats", "count-unread-messages", "new-contacts", "pinned-messages", "reset", "web" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The power saving settings section +public struct SettingsSectionPowerSaving: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "videos", "gifs", "stickers", "emoji", "effects", "preload", "background", "call-animations", "particles", "transitions" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The privacy and security section +public struct SettingsSectionPrivacyAndSecurity: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "blocked", "blocked/edit", "blocked/block-user", "blocked/block-user/chats", "blocked/block-user/contacts", "active-websites", "active-websites/edit", "active-websites/disconnect-all", "passcode", "passcode/disable", "passcode/change", "passcode/auto-lock", "passcode/face-id", "passcode/fingerprint", "2sv", "2sv/change", "2sv/disable", "2sv/change-email", "passkey", "passkey/create", "auto-delete", "auto-delete/set-custom", "login-email", "phone-number", "phone-number/never", "phone-number/always", "last-seen", "last-seen/never", "last-seen/always", "last-seen/hide-read-time", "profile-photos", "profile-photos/never", "profile-photos/always", "profile-photos/set-public", "profile-photos/update-public", "profile-photos/remove-public", "bio", "bio/never", "bio/always", "gifts", "gifts/show-icon", "gifts/never", "gifts/always", "gifts/accepted-types", "birthday", "birthday/add", "birthday/never", "birthday/always", "saved-music", "saved-music/never", "saved-music/always", "forwards", "forwards/never", "forwards/always", "calls", "calls/never", "calls/always", "calls/p2p", "calls/p2p/never", "calls/p2p/always", "calls/ios-integration", "voice", "voice/never", "voice/always", "messages", "messages/set-price", "messages/exceptions", "invites", "invites/never", "invites/always", "self-destruct", "data-settings", "data-settings/sync-contacts", "data-settings/delete-synced", "data-settings/suggest-contacts", "data-settings/delete-cloud-drafts", "data-settings/clear-payment-info", "data-settings/link-previews", "data-settings/bot-settings", "data-settings/map-provider", "archive-and-mute" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The current user's QR code section +public struct SettingsSectionQrCode: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "share", "scan" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + +/// The "Send a gift" section +public struct SettingsSectionSendGift: Codable, Equatable, Hashable { + + /// Subsection of the section; may be one of "", "self" + public let subsection: String + + + public init(subsection: String) { + self.subsection = subsection + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedChat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedChat.swift new file mode 100644 index 0000000000..fd07c514c0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedChat.swift @@ -0,0 +1,41 @@ +// +// SharedChat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a chat shared with a bot +public struct SharedChat: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// Photo of the chat; for bots only; may be null + public let photo: Photo? + + /// Title of the chat; for bots only + public let title: String + + /// Username of the chat; for bots only + public let username: String + + + public init( + chatId: Int64, + photo: Photo?, + title: String, + username: String + ) { + self.chatId = chatId + self.photo = photo + self.title = title + self.username = username + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedUser.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedUser.swift new file mode 100644 index 0000000000..98b6f5dd1f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SharedUser.swift @@ -0,0 +1,46 @@ +// +// SharedUser.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a user shared with a bot +public struct SharedUser: Codable, Equatable, Hashable { + + /// First name of the user; for bots only + public let firstName: String + + /// Last name of the user; for bots only + public let lastName: String + + /// Profile photo of the user; for bots only; may be null + public let photo: Photo? + + /// User identifier + public let userId: Int64 + + /// Username of the user; for bots only + public let username: String + + + public init( + firstName: String, + lastName: String, + photo: Photo?, + userId: Int64, + username: String + ) { + self.firstName = firstName + self.lastName = lastName + self.photo = photo + self.userId = userId + self.username = username + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SpeechRecognitionResult.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SpeechRecognitionResult.swift new file mode 100644 index 0000000000..2b4335d9cf --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SpeechRecognitionResult.swift @@ -0,0 +1,107 @@ +// +// SpeechRecognitionResult.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes result of speech recognition in a voice note +public indirect enum SpeechRecognitionResult: Codable, Equatable, Hashable { + + /// The speech recognition is ongoing + case speechRecognitionResultPending(SpeechRecognitionResultPending) + + /// The speech recognition successfully finished + case speechRecognitionResultText(SpeechRecognitionResultText) + + /// The speech recognition failed + case speechRecognitionResultError(SpeechRecognitionResultError) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case speechRecognitionResultPending + case speechRecognitionResultText + case speechRecognitionResultError + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .speechRecognitionResultPending: + let value = try SpeechRecognitionResultPending(from: decoder) + self = .speechRecognitionResultPending(value) + case .speechRecognitionResultText: + let value = try SpeechRecognitionResultText(from: decoder) + self = .speechRecognitionResultText(value) + case .speechRecognitionResultError: + let value = try SpeechRecognitionResultError(from: decoder) + self = .speechRecognitionResultError(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .speechRecognitionResultPending(let value): + try container.encode(Kind.speechRecognitionResultPending, forKey: .type) + try value.encode(to: encoder) + case .speechRecognitionResultText(let value): + try container.encode(Kind.speechRecognitionResultText, forKey: .type) + try value.encode(to: encoder) + case .speechRecognitionResultError(let value): + try container.encode(Kind.speechRecognitionResultError, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The speech recognition is ongoing +public struct SpeechRecognitionResultPending: Codable, Equatable, Hashable { + + /// Partially recognized text + public let partialText: String + + + public init(partialText: String) { + self.partialText = partialText + } +} + +/// The speech recognition successfully finished +public struct SpeechRecognitionResultText: Codable, Equatable, Hashable { + + /// Recognized text + public let text: String + + + public init(text: String) { + self.text = text + } +} + +/// The speech recognition failed +public struct SpeechRecognitionResultError: Codable, Equatable, Hashable { + + /// Recognition error. An error with a message "MSG_VOICE_TOO_LONG" is returned when media duration is too big to be recognized + public let error: TDError + + + public init(error: TDError) { + self.error = error + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StarAmount.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StarAmount.swift new file mode 100644 index 0000000000..25ae0b489f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StarAmount.swift @@ -0,0 +1,31 @@ +// +// StarAmount.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a possibly non-integer Telegram Star amount +public struct StarAmount: Codable, Equatable, Hashable { + + /// The number of 1/1000000000 shares of Telegram Stars; from -999999999 to 999999999 + public let nanostarCount: Int + + /// The integer Telegram Star amount rounded to 0 + public let starCount: Int64 + + + public init( + nanostarCount: Int, + starCount: Int64 + ) { + self.nanostarCount = nanostarCount + self.starCount = starCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Sticker.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Sticker.swift new file mode 100644 index 0000000000..9eb86eb4df --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Sticker.swift @@ -0,0 +1,66 @@ +// +// Sticker.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a sticker +public struct Sticker: Codable, Equatable, Hashable, Identifiable { + + /// Emoji corresponding to the sticker; may be empty if unknown + public let emoji: String + + /// Sticker format + public let format: StickerFormat + + /// Sticker's full type + public let fullType: StickerFullType + + /// Sticker height; as defined by the sender + public let height: Int + + /// Unique sticker identifier within the set; 0 if none + public let id: TdInt64 + + /// Identifier of the sticker set to which the sticker belongs; 0 if none + public let setId: TdInt64 + + /// File containing the sticker + public let sticker: File + + /// Sticker thumbnail in WEBP or JPEG format; may be null + public let thumbnail: Thumbnail? + + /// Sticker width; as defined by the sender + public let width: Int + + + public init( + emoji: String, + format: StickerFormat, + fullType: StickerFullType, + height: Int, + id: TdInt64, + setId: TdInt64, + sticker: File, + thumbnail: Thumbnail?, + width: Int + ) { + self.emoji = emoji + self.format = format + self.fullType = fullType + self.height = height + self.id = id + self.setId = setId + self.sticker = sticker + self.thumbnail = thumbnail + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFormat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFormat.swift new file mode 100644 index 0000000000..c89bf4afa7 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFormat.swift @@ -0,0 +1,65 @@ +// +// StickerFormat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes format of a sticker +public indirect enum StickerFormat: Codable, Equatable, Hashable { + + /// The sticker is an image in WEBP format + case stickerFormatWebp + + /// The sticker is an animation in TGS format + case stickerFormatTgs + + /// The sticker is a video in WEBM format + case stickerFormatWebm + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case stickerFormatWebp + case stickerFormatTgs + case stickerFormatWebm + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .stickerFormatWebp: + self = .stickerFormatWebp + case .stickerFormatTgs: + self = .stickerFormatTgs + case .stickerFormatWebm: + self = .stickerFormatWebm + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .stickerFormatWebp: + try container.encode(Kind.stickerFormatWebp, forKey: .type) + case .stickerFormatTgs: + try container.encode(Kind.stickerFormatTgs, forKey: .type) + case .stickerFormatWebm: + try container.encode(Kind.stickerFormatWebm, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFullType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFullType.swift new file mode 100644 index 0000000000..72016b0a2a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerFullType.swift @@ -0,0 +1,114 @@ +// +// StickerFullType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains full information about sticker type +public indirect enum StickerFullType: Codable, Equatable, Hashable { + + /// The sticker is a regular sticker + case stickerFullTypeRegular(StickerFullTypeRegular) + + /// The sticker is a mask in WEBP format to be placed on photos or videos + case stickerFullTypeMask(StickerFullTypeMask) + + /// The sticker is a custom emoji to be used inside message text and caption. Currently, only Telegram Premium users can use custom emoji + case stickerFullTypeCustomEmoji(StickerFullTypeCustomEmoji) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case stickerFullTypeRegular + case stickerFullTypeMask + case stickerFullTypeCustomEmoji + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .stickerFullTypeRegular: + let value = try StickerFullTypeRegular(from: decoder) + self = .stickerFullTypeRegular(value) + case .stickerFullTypeMask: + let value = try StickerFullTypeMask(from: decoder) + self = .stickerFullTypeMask(value) + case .stickerFullTypeCustomEmoji: + let value = try StickerFullTypeCustomEmoji(from: decoder) + self = .stickerFullTypeCustomEmoji(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .stickerFullTypeRegular(let value): + try container.encode(Kind.stickerFullTypeRegular, forKey: .type) + try value.encode(to: encoder) + case .stickerFullTypeMask(let value): + try container.encode(Kind.stickerFullTypeMask, forKey: .type) + try value.encode(to: encoder) + case .stickerFullTypeCustomEmoji(let value): + try container.encode(Kind.stickerFullTypeCustomEmoji, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The sticker is a regular sticker +public struct StickerFullTypeRegular: Codable, Equatable, Hashable { + + /// Premium animation of the sticker; may be null. If present, only Telegram Premium users can use the sticker + public let premiumAnimation: File? + + + public init(premiumAnimation: File?) { + self.premiumAnimation = premiumAnimation + } +} + +/// The sticker is a mask in WEBP format to be placed on photos or videos +public struct StickerFullTypeMask: Codable, Equatable, Hashable { + + /// Position where the mask is placed; may be null + public let maskPosition: MaskPosition? + + + public init(maskPosition: MaskPosition?) { + self.maskPosition = maskPosition + } +} + +/// The sticker is a custom emoji to be used inside message text and caption. Currently, only Telegram Premium users can use custom emoji +public struct StickerFullTypeCustomEmoji: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji + public let customEmojiId: TdInt64 + + /// True, if the sticker must be repainted to a text color in messages, the color of the Telegram Premium badge in emoji status, white color on chat photos, or another appropriate color in other places + public let needsRepainting: Bool + + + public init( + customEmojiId: TdInt64, + needsRepainting: Bool + ) { + self.customEmojiId = customEmojiId + self.needsRepainting = needsRepainting + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSet.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSet.swift new file mode 100644 index 0000000000..8075095435 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSet.swift @@ -0,0 +1,96 @@ +// +// StickerSet.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a sticker set +public struct StickerSet: Codable, Equatable, Hashable, Identifiable { + + /// A list of emojis corresponding to the stickers in the same order. The list is only for informational purposes, because a sticker is always sent with a fixed emoji from the corresponding Sticker object + public let emojis: [Emojis] + + /// Identifier of the sticker set + public let id: TdInt64 + + /// True, if stickers in the sticker set are custom emoji that can be used as chat emoji status; for custom emoji sticker sets only + public let isAllowedAsChatEmojiStatus: Bool + + /// True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously + public let isArchived: Bool + + /// True, if the sticker set has been installed by the current user + public let isInstalled: Bool + + /// True, if the sticker set is official + public let isOfficial: Bool + + /// True, if the sticker set is owned by the current user + public let isOwned: Bool + + /// True for already viewed trending sticker sets + public let isViewed: Bool + + /// Name of the sticker set + public let name: String + + /// True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only + public let needsRepainting: Bool + + /// Type of the stickers in the set + public let stickerType: StickerType + + /// List of stickers in this set + public let stickers: [Sticker] + + /// Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed + public let thumbnail: Thumbnail? + + /// Sticker set thumbnail's outline; may be null if unknown + public let thumbnailOutline: Outline? + + /// Title of the sticker set + public let title: String + + + public init( + emojis: [Emojis], + id: TdInt64, + isAllowedAsChatEmojiStatus: Bool, + isArchived: Bool, + isInstalled: Bool, + isOfficial: Bool, + isOwned: Bool, + isViewed: Bool, + name: String, + needsRepainting: Bool, + stickerType: StickerType, + stickers: [Sticker], + thumbnail: Thumbnail?, + thumbnailOutline: Outline?, + title: String + ) { + self.emojis = emojis + self.id = id + self.isAllowedAsChatEmojiStatus = isAllowedAsChatEmojiStatus + self.isArchived = isArchived + self.isInstalled = isInstalled + self.isOfficial = isOfficial + self.isOwned = isOwned + self.isViewed = isViewed + self.name = name + self.needsRepainting = needsRepainting + self.stickerType = stickerType + self.stickers = stickers + self.thumbnail = thumbnail + self.thumbnailOutline = thumbnailOutline + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSetInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSetInfo.swift new file mode 100644 index 0000000000..cf9add1ce4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSetInfo.swift @@ -0,0 +1,96 @@ +// +// StickerSetInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents short information about a sticker set +public struct StickerSetInfo: Codable, Equatable, Hashable, Identifiable { + + /// Up to the first 5 stickers from the set, depending on the context. If the application needs more stickers the full sticker set needs to be requested + public let covers: [Sticker] + + /// Identifier of the sticker set + public let id: TdInt64 + + /// True, if stickers in the sticker set are custom emoji that can be used as chat emoji status; for custom emoji sticker sets only + public let isAllowedAsChatEmojiStatus: Bool + + /// True, if the sticker set has been archived. A sticker set can't be installed and archived simultaneously + public let isArchived: Bool + + /// True, if the sticker set has been installed by the current user + public let isInstalled: Bool + + /// True, if the sticker set is official + public let isOfficial: Bool + + /// True, if the sticker set is owned by the current user + public let isOwned: Bool + + /// True for already viewed trending sticker sets + public let isViewed: Bool + + /// Name of the sticker set + public let name: String + + /// True, if stickers in the sticker set are custom emoji that must be repainted; for custom emoji sticker sets only + public let needsRepainting: Bool + + /// Total number of stickers in the set + public let size: Int + + /// Type of the stickers in the set + public let stickerType: StickerType + + /// Sticker set thumbnail in WEBP, TGS, or WEBM format with width and height 100; may be null. The file can be downloaded only before the thumbnail is changed + public let thumbnail: Thumbnail? + + /// Sticker set thumbnail's outline; may be null if unknown + public let thumbnailOutline: Outline? + + /// Title of the sticker set + public let title: String + + + public init( + covers: [Sticker], + id: TdInt64, + isAllowedAsChatEmojiStatus: Bool, + isArchived: Bool, + isInstalled: Bool, + isOfficial: Bool, + isOwned: Bool, + isViewed: Bool, + name: String, + needsRepainting: Bool, + size: Int, + stickerType: StickerType, + thumbnail: Thumbnail?, + thumbnailOutline: Outline?, + title: String + ) { + self.covers = covers + self.id = id + self.isAllowedAsChatEmojiStatus = isAllowedAsChatEmojiStatus + self.isArchived = isArchived + self.isInstalled = isInstalled + self.isOfficial = isOfficial + self.isOwned = isOwned + self.isViewed = isViewed + self.name = name + self.needsRepainting = needsRepainting + self.size = size + self.stickerType = stickerType + self.thumbnail = thumbnail + self.thumbnailOutline = thumbnailOutline + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSets.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSets.swift new file mode 100644 index 0000000000..1a2fa88469 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerSets.swift @@ -0,0 +1,31 @@ +// +// StickerSets.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a list of sticker sets +public struct StickerSets: Codable, Equatable, Hashable { + + /// List of sticker sets + public let sets: [StickerSetInfo] + + /// Approximate total number of sticker sets found + public let totalCount: Int + + + public init( + sets: [StickerSetInfo], + totalCount: Int + ) { + self.sets = sets + self.totalCount = totalCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerType.swift new file mode 100644 index 0000000000..66756064fc --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StickerType.swift @@ -0,0 +1,65 @@ +// +// StickerType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes type of sticker +public indirect enum StickerType: Codable, Equatable, Hashable { + + /// The sticker is a regular sticker + case stickerTypeRegular + + /// The sticker is a mask in WEBP format to be placed on photos or videos + case stickerTypeMask + + /// The sticker is a custom emoji to be used inside message text and caption + case stickerTypeCustomEmoji + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case stickerTypeRegular + case stickerTypeMask + case stickerTypeCustomEmoji + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .stickerTypeRegular: + self = .stickerTypeRegular + case .stickerTypeMask: + self = .stickerTypeMask + case .stickerTypeCustomEmoji: + self = .stickerTypeCustomEmoji + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .stickerTypeRegular: + try container.encode(Kind.stickerTypeRegular, forKey: .type) + case .stickerTypeMask: + try container.encode(Kind.stickerTypeMask, forKey: .type) + case .stickerTypeCustomEmoji: + try container.encode(Kind.stickerTypeCustomEmoji, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Stickers.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Stickers.swift new file mode 100644 index 0000000000..2010c0612e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Stickers.swift @@ -0,0 +1,24 @@ +// +// Stickers.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a list of stickers +public struct Stickers: Codable, Equatable, Hashable { + + /// List of stickers + public let stickers: [Sticker] + + + public init(stickers: [Sticker]) { + self.stickers = stickers + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StoryContentType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StoryContentType.swift new file mode 100644 index 0000000000..8bf31deb69 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/StoryContentType.swift @@ -0,0 +1,73 @@ +// +// StoryContentType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains the type of the content of a story +public indirect enum StoryContentType: Codable, Equatable, Hashable { + + /// A photo story + case storyContentTypePhoto + + /// A video story + case storyContentTypeVideo + + /// A live story + case storyContentTypeLive + + /// A story of unknown content type + case storyContentTypeUnsupported + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case storyContentTypePhoto + case storyContentTypeVideo + case storyContentTypeLive + case storyContentTypeUnsupported + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .storyContentTypePhoto: + self = .storyContentTypePhoto + case .storyContentTypeVideo: + self = .storyContentTypeVideo + case .storyContentTypeLive: + self = .storyContentTypeLive + case .storyContentTypeUnsupported: + self = .storyContentTypeUnsupported + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .storyContentTypePhoto: + try container.encode(Kind.storyContentTypePhoto, forKey: .type) + case .storyContentTypeVideo: + try container.encode(Kind.storyContentTypeVideo, forKey: .type) + case .storyContentTypeLive: + try container.encode(Kind.storyContentTypeLive, forKey: .type) + case .storyContentTypeUnsupported: + try container.encode(Kind.storyContentTypeUnsupported, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostInfo.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostInfo.swift new file mode 100644 index 0000000000..c5e2d52147 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostInfo.swift @@ -0,0 +1,46 @@ +// +// SuggestedPostInfo.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about a suggested post. If the post can be approved or declined, then changes to the post can be also suggested. Use sendMessage with reply to the message and suggested post information to suggest message changes. Use addOffer to suggest price or time changes +public struct SuggestedPostInfo: Codable, Equatable, Hashable { + + /// True, if the suggested post can be approved by the current user using approveSuggestedPost; updates aren't sent when value of this field changes + public let canBeApproved: Bool + + /// True, if the suggested post can be declined by the current user using declineSuggestedPost; updates aren't sent when value of this field changes + public let canBeDeclined: Bool + + /// Price of the suggested post; may be null if the post is non-paid + public let price: SuggestedPostPrice? + + /// Point in time (Unix timestamp) when the post is expected to be published; 0 if the specific date isn't set yet + public let sendDate: Int + + /// State of the post + public let state: SuggestedPostState + + + public init( + canBeApproved: Bool, + canBeDeclined: Bool, + price: SuggestedPostPrice?, + sendDate: Int, + state: SuggestedPostState + ) { + self.canBeApproved = canBeApproved + self.canBeDeclined = canBeDeclined + self.price = price + self.sendDate = sendDate + self.state = state + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostPrice.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostPrice.swift new file mode 100644 index 0000000000..e8cf6c520b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostPrice.swift @@ -0,0 +1,85 @@ +// +// SuggestedPostPrice.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes price of a suggested post +public indirect enum SuggestedPostPrice: Codable, Equatable, Hashable { + + /// Describes price of a suggested post in Telegram Stars + case suggestedPostPriceStar(SuggestedPostPriceStar) + + /// Describes price of a suggested post in Toncoins + case suggestedPostPriceTon(SuggestedPostPriceTon) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case suggestedPostPriceStar + case suggestedPostPriceTon + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .suggestedPostPriceStar: + let value = try SuggestedPostPriceStar(from: decoder) + self = .suggestedPostPriceStar(value) + case .suggestedPostPriceTon: + let value = try SuggestedPostPriceTon(from: decoder) + self = .suggestedPostPriceTon(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .suggestedPostPriceStar(let value): + try container.encode(Kind.suggestedPostPriceStar, forKey: .type) + try value.encode(to: encoder) + case .suggestedPostPriceTon(let value): + try container.encode(Kind.suggestedPostPriceTon, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Describes price of a suggested post in Telegram Stars +public struct SuggestedPostPriceStar: Codable, Equatable, Hashable { + + /// The Telegram Star amount expected to be paid for the post; getOption("suggested_post_star_count_min")-getOption("suggested_post_star_count_max") + public let starCount: Int64 + + + public init(starCount: Int64) { + self.starCount = starCount + } +} + +/// Describes price of a suggested post in Toncoins +public struct SuggestedPostPriceTon: Codable, Equatable, Hashable { + + /// The amount of 1/100 of Toncoin expected to be paid for the post; getOption("suggested_post_toncoin_cent_count_min")-getOption("suggested_post_toncoin_cent_count_max") + public let toncoinCentCount: Int64 + + + public init(toncoinCentCount: Int64) { + self.toncoinCentCount = toncoinCentCount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostRefundReason.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostRefundReason.swift new file mode 100644 index 0000000000..85379a695a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostRefundReason.swift @@ -0,0 +1,57 @@ +// +// SuggestedPostRefundReason.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes reason for refund of the payment for a suggested post +public indirect enum SuggestedPostRefundReason: Codable, Equatable, Hashable { + + /// The post was refunded, because it was deleted by channel administrators in less than getOption("suggested_post_lifetime_min") seconds + case suggestedPostRefundReasonPostDeleted + + /// The post was refunded, because the payment for the post was refunded + case suggestedPostRefundReasonPaymentRefunded + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case suggestedPostRefundReasonPostDeleted + case suggestedPostRefundReasonPaymentRefunded + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .suggestedPostRefundReasonPostDeleted: + self = .suggestedPostRefundReasonPostDeleted + case .suggestedPostRefundReasonPaymentRefunded: + self = .suggestedPostRefundReasonPaymentRefunded + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .suggestedPostRefundReasonPostDeleted: + try container.encode(Kind.suggestedPostRefundReasonPostDeleted, forKey: .type) + case .suggestedPostRefundReasonPaymentRefunded: + try container.encode(Kind.suggestedPostRefundReasonPaymentRefunded, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostState.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostState.swift new file mode 100644 index 0000000000..c9b0e26959 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/SuggestedPostState.swift @@ -0,0 +1,65 @@ +// +// SuggestedPostState.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes state of a suggested post +public indirect enum SuggestedPostState: Codable, Equatable, Hashable { + + /// The post must be approved or declined + case suggestedPostStatePending + + /// The post was approved + case suggestedPostStateApproved + + /// The post was declined + case suggestedPostStateDeclined + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case suggestedPostStatePending + case suggestedPostStateApproved + case suggestedPostStateDeclined + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .suggestedPostStatePending: + self = .suggestedPostStatePending + case .suggestedPostStateApproved: + self = .suggestedPostStateApproved + case .suggestedPostStateDeclined: + self = .suggestedPostStateDeclined + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .suggestedPostStatePending: + try container.encode(Kind.suggestedPostStatePending, forKey: .type) + case .suggestedPostStateApproved: + try container.encode(Kind.suggestedPostStateApproved, forKey: .type) + case .suggestedPostStateDeclined: + try container.encode(Kind.suggestedPostStateDeclined, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TDError.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TDError.swift new file mode 100644 index 0000000000..c0f9db4fb9 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TDError.swift @@ -0,0 +1,31 @@ +// +// TDError.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// An object of this type can be returned on every function call, in case of an error +public struct TDError: Codable, Equatable, Hashable, Swift.Error { + + /// Error code; subject to future changes. If the error code is 406, the error message must not be processed in any way and must not be displayed to the user + public let code: Int + + /// Error message; subject to future changes + public let message: String + + + public init( + code: Int, + message: String + ) { + self.code = code + self.message = message + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChat.swift new file mode 100644 index 0000000000..2ada1d266a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChat.swift @@ -0,0 +1,93 @@ +// +// TargetChat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the target chat to be opened +public indirect enum TargetChat: Codable, Equatable, Hashable { + + /// The currently opened chat and forum topic must be kept + case targetChatCurrent + + /// The chat needs to be chosen by the user among chats of the specified types + case targetChatChosen(TargetChatChosen) + + /// The chat needs to be open with the provided internal link + case targetChatInternalLink(TargetChatInternalLink) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case targetChatCurrent + case targetChatChosen + case targetChatInternalLink + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .targetChatCurrent: + self = .targetChatCurrent + case .targetChatChosen: + let value = try TargetChatChosen(from: decoder) + self = .targetChatChosen(value) + case .targetChatInternalLink: + let value = try TargetChatInternalLink(from: decoder) + self = .targetChatInternalLink(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .targetChatCurrent: + try container.encode(Kind.targetChatCurrent, forKey: .type) + case .targetChatChosen(let value): + try container.encode(Kind.targetChatChosen, forKey: .type) + try value.encode(to: encoder) + case .targetChatInternalLink(let value): + try container.encode(Kind.targetChatInternalLink, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The chat needs to be chosen by the user among chats of the specified types +public struct TargetChatChosen: Codable, Equatable, Hashable { + + /// Allowed types for the chat + public let types: TargetChatTypes + + + public init(types: TargetChatTypes) { + self.types = types + } +} + +/// The chat needs to be open with the provided internal link +public struct TargetChatInternalLink: Codable, Equatable, Hashable { + + /// An internal link pointing to the chat + public let link: InternalLinkType + + + public init(link: InternalLinkType) { + self.link = link + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChatTypes.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChatTypes.swift new file mode 100644 index 0000000000..714cb1b1df --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TargetChatTypes.swift @@ -0,0 +1,41 @@ +// +// TargetChatTypes.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes allowed types for the target chat +public struct TargetChatTypes: Codable, Equatable, Hashable { + + /// True, if private chats with other bots are allowed + public let allowBotChats: Bool + + /// True, if channel chats are allowed + public let allowChannelChats: Bool + + /// True, if basic group and supergroup chats are allowed + public let allowGroupChats: Bool + + /// True, if private chats with ordinary users are allowed + public let allowUserChats: Bool + + + public init( + allowBotChats: Bool, + allowChannelChats: Bool, + allowGroupChats: Bool, + allowUserChats: Bool + ) { + self.allowBotChats = allowBotChats + self.allowChannelChats = allowChannelChats + self.allowGroupChats = allowGroupChats + self.allowUserChats = allowUserChats + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TermsOfService.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TermsOfService.swift new file mode 100644 index 0000000000..0ab9b58a28 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TermsOfService.swift @@ -0,0 +1,36 @@ +// +// TermsOfService.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains Telegram terms of service +public struct TermsOfService: Codable, Equatable, Hashable { + + /// The minimum age of a user to be able to accept the terms; 0 if age isn't restricted + public let minUserAge: Int + + /// True, if a blocking popup with terms of service must be shown to the user + public let showPopup: Bool + + /// Text of the terms of service + public let text: FormattedText + + + public init( + minUserAge: Int, + showPopup: Bool, + text: FormattedText + ) { + self.minUserAge = minUserAge + self.showPopup = showPopup + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntity.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntity.swift new file mode 100644 index 0000000000..8de5c9497a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntity.swift @@ -0,0 +1,36 @@ +// +// TextEntity.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a part of the text that needs to be formatted in some unusual way +public struct TextEntity: Codable, Equatable, Hashable { + + /// Length of the entity, in UTF-16 code units + public let length: Int + + /// Offset of the entity, in UTF-16 code units + public let offset: Int + + /// Type of the entity + public let type: TextEntityType + + + public init( + length: Int, + offset: Int, + type: TextEntityType + ) { + self.length = length + self.offset = offset + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntityType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntityType.swift new file mode 100644 index 0000000000..c3fdc89268 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextEntityType.swift @@ -0,0 +1,316 @@ +// +// TextEntityType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a part of the text which must be formatted differently +public indirect enum TextEntityType: Codable, Equatable, Hashable { + + /// A mention of a user, a supergroup, or a channel by their username + case textEntityTypeMention + + /// A hashtag text, beginning with "#" and optionally containing a chat username at the end + case textEntityTypeHashtag + + /// A cashtag text, beginning with "$", consisting of capital English letters (e.g., "$USD"), and optionally containing a chat username at the end + case textEntityTypeCashtag + + /// A bot command, beginning with "/" + case textEntityTypeBotCommand + + /// An HTTP URL + case textEntityTypeUrl + + /// An email address + case textEntityTypeEmailAddress + + /// A phone number + case textEntityTypePhoneNumber + + /// A bank card number. The getBankCardInfo method can be used to get information about the bank card + case textEntityTypeBankCardNumber + + /// A bold text + case textEntityTypeBold + + /// An italic text + case textEntityTypeItalic + + /// An underlined text + case textEntityTypeUnderline + + /// A strikethrough text + case textEntityTypeStrikethrough + + /// A spoiler text + case textEntityTypeSpoiler + + /// Text that must be formatted as if inside a code HTML tag + case textEntityTypeCode + + /// Text that must be formatted as if inside a pre HTML tag + case textEntityTypePre + + /// Text that must be formatted as if inside pre, and code HTML tags + case textEntityTypePreCode(TextEntityTypePreCode) + + /// Text that must be formatted as if inside a blockquote HTML tag; not supported in secret chats + case textEntityTypeBlockQuote + + /// Text that must be formatted as if inside a blockquote HTML tag and collapsed by default to 3 lines with the ability to show full text; not supported in secret chats + case textEntityTypeExpandableBlockQuote + + /// A text description shown instead of a raw URL + case textEntityTypeTextUrl(TextEntityTypeTextUrl) + + /// A text shows instead of a raw mention of the user (e.g., when the user has no username) + case textEntityTypeMentionName(TextEntityTypeMentionName) + + /// A custom emoji. The text behind a custom emoji must be an emoji. Only premium users can use premium custom emoji + case textEntityTypeCustomEmoji(TextEntityTypeCustomEmoji) + + /// A media timestamp + case textEntityTypeMediaTimestamp(TextEntityTypeMediaTimestamp) + + /// A date and time + case textEntityTypeDateTime(TextEntityTypeDateTime) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case textEntityTypeMention + case textEntityTypeHashtag + case textEntityTypeCashtag + case textEntityTypeBotCommand + case textEntityTypeUrl + case textEntityTypeEmailAddress + case textEntityTypePhoneNumber + case textEntityTypeBankCardNumber + case textEntityTypeBold + case textEntityTypeItalic + case textEntityTypeUnderline + case textEntityTypeStrikethrough + case textEntityTypeSpoiler + case textEntityTypeCode + case textEntityTypePre + case textEntityTypePreCode + case textEntityTypeBlockQuote + case textEntityTypeExpandableBlockQuote + case textEntityTypeTextUrl + case textEntityTypeMentionName + case textEntityTypeCustomEmoji + case textEntityTypeMediaTimestamp + case textEntityTypeDateTime + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .textEntityTypeMention: + self = .textEntityTypeMention + case .textEntityTypeHashtag: + self = .textEntityTypeHashtag + case .textEntityTypeCashtag: + self = .textEntityTypeCashtag + case .textEntityTypeBotCommand: + self = .textEntityTypeBotCommand + case .textEntityTypeUrl: + self = .textEntityTypeUrl + case .textEntityTypeEmailAddress: + self = .textEntityTypeEmailAddress + case .textEntityTypePhoneNumber: + self = .textEntityTypePhoneNumber + case .textEntityTypeBankCardNumber: + self = .textEntityTypeBankCardNumber + case .textEntityTypeBold: + self = .textEntityTypeBold + case .textEntityTypeItalic: + self = .textEntityTypeItalic + case .textEntityTypeUnderline: + self = .textEntityTypeUnderline + case .textEntityTypeStrikethrough: + self = .textEntityTypeStrikethrough + case .textEntityTypeSpoiler: + self = .textEntityTypeSpoiler + case .textEntityTypeCode: + self = .textEntityTypeCode + case .textEntityTypePre: + self = .textEntityTypePre + case .textEntityTypePreCode: + let value = try TextEntityTypePreCode(from: decoder) + self = .textEntityTypePreCode(value) + case .textEntityTypeBlockQuote: + self = .textEntityTypeBlockQuote + case .textEntityTypeExpandableBlockQuote: + self = .textEntityTypeExpandableBlockQuote + case .textEntityTypeTextUrl: + let value = try TextEntityTypeTextUrl(from: decoder) + self = .textEntityTypeTextUrl(value) + case .textEntityTypeMentionName: + let value = try TextEntityTypeMentionName(from: decoder) + self = .textEntityTypeMentionName(value) + case .textEntityTypeCustomEmoji: + let value = try TextEntityTypeCustomEmoji(from: decoder) + self = .textEntityTypeCustomEmoji(value) + case .textEntityTypeMediaTimestamp: + let value = try TextEntityTypeMediaTimestamp(from: decoder) + self = .textEntityTypeMediaTimestamp(value) + case .textEntityTypeDateTime: + let value = try TextEntityTypeDateTime(from: decoder) + self = .textEntityTypeDateTime(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .textEntityTypeMention: + try container.encode(Kind.textEntityTypeMention, forKey: .type) + case .textEntityTypeHashtag: + try container.encode(Kind.textEntityTypeHashtag, forKey: .type) + case .textEntityTypeCashtag: + try container.encode(Kind.textEntityTypeCashtag, forKey: .type) + case .textEntityTypeBotCommand: + try container.encode(Kind.textEntityTypeBotCommand, forKey: .type) + case .textEntityTypeUrl: + try container.encode(Kind.textEntityTypeUrl, forKey: .type) + case .textEntityTypeEmailAddress: + try container.encode(Kind.textEntityTypeEmailAddress, forKey: .type) + case .textEntityTypePhoneNumber: + try container.encode(Kind.textEntityTypePhoneNumber, forKey: .type) + case .textEntityTypeBankCardNumber: + try container.encode(Kind.textEntityTypeBankCardNumber, forKey: .type) + case .textEntityTypeBold: + try container.encode(Kind.textEntityTypeBold, forKey: .type) + case .textEntityTypeItalic: + try container.encode(Kind.textEntityTypeItalic, forKey: .type) + case .textEntityTypeUnderline: + try container.encode(Kind.textEntityTypeUnderline, forKey: .type) + case .textEntityTypeStrikethrough: + try container.encode(Kind.textEntityTypeStrikethrough, forKey: .type) + case .textEntityTypeSpoiler: + try container.encode(Kind.textEntityTypeSpoiler, forKey: .type) + case .textEntityTypeCode: + try container.encode(Kind.textEntityTypeCode, forKey: .type) + case .textEntityTypePre: + try container.encode(Kind.textEntityTypePre, forKey: .type) + case .textEntityTypePreCode(let value): + try container.encode(Kind.textEntityTypePreCode, forKey: .type) + try value.encode(to: encoder) + case .textEntityTypeBlockQuote: + try container.encode(Kind.textEntityTypeBlockQuote, forKey: .type) + case .textEntityTypeExpandableBlockQuote: + try container.encode(Kind.textEntityTypeExpandableBlockQuote, forKey: .type) + case .textEntityTypeTextUrl(let value): + try container.encode(Kind.textEntityTypeTextUrl, forKey: .type) + try value.encode(to: encoder) + case .textEntityTypeMentionName(let value): + try container.encode(Kind.textEntityTypeMentionName, forKey: .type) + try value.encode(to: encoder) + case .textEntityTypeCustomEmoji(let value): + try container.encode(Kind.textEntityTypeCustomEmoji, forKey: .type) + try value.encode(to: encoder) + case .textEntityTypeMediaTimestamp(let value): + try container.encode(Kind.textEntityTypeMediaTimestamp, forKey: .type) + try value.encode(to: encoder) + case .textEntityTypeDateTime(let value): + try container.encode(Kind.textEntityTypeDateTime, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// Text that must be formatted as if inside pre, and code HTML tags +public struct TextEntityTypePreCode: Codable, Equatable, Hashable { + + /// Programming language of the code; as defined by the sender + public let language: String + + + public init(language: String) { + self.language = language + } +} + +/// A text description shown instead of a raw URL +public struct TextEntityTypeTextUrl: Codable, Equatable, Hashable { + + /// HTTP or tg:// URL to be opened when the link is clicked + public let url: String + + + public init(url: String) { + self.url = url + } +} + +/// A text shows instead of a raw mention of the user (e.g., when the user has no username) +public struct TextEntityTypeMentionName: Codable, Equatable, Hashable { + + /// Identifier of the mentioned user + public let userId: Int64 + + + public init(userId: Int64) { + self.userId = userId + } +} + +/// A custom emoji. The text behind a custom emoji must be an emoji. Only premium users can use premium custom emoji +public struct TextEntityTypeCustomEmoji: Codable, Equatable, Hashable { + + /// Unique identifier of the custom emoji + public let customEmojiId: TdInt64 + + + public init(customEmojiId: TdInt64) { + self.customEmojiId = customEmojiId + } +} + +/// A media timestamp +public struct TextEntityTypeMediaTimestamp: Codable, Equatable, Hashable { + + /// Timestamp from which a video/audio/video note/voice note/story playing must start, in seconds. The media can be in the content or the link preview of the current message, or in the same places in the replied message + public let mediaTimestamp: Int + + + public init(mediaTimestamp: Int) { + self.mediaTimestamp = mediaTimestamp + } +} + +/// A date and time +public struct TextEntityTypeDateTime: Codable, Equatable, Hashable { + + /// Date and time formatting type; may be null if none and the original text must not be changed + public let formattingType: DateTimeFormattingType? + + /// Point in time (Unix timestamp) representing the date and time + public let unixTime: Int + + + public init( + formattingType: DateTimeFormattingType?, + unixTime: Int + ) { + self.formattingType = formattingType + self.unixTime = unixTime + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextQuote.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextQuote.swift new file mode 100644 index 0000000000..3d5e01a572 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/TextQuote.swift @@ -0,0 +1,36 @@ +// +// TextQuote.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes manually or automatically chosen quote from another message +public struct TextQuote: Codable, Equatable, Hashable { + + /// True, if the quote was manually chosen by the message sender + public let isManual: Bool + + /// Approximate quote position in the original message in UTF-16 code units as specified by the message sender + public let position: Int + + /// Text of the quote. Only Bold, Italic, Underline, Strikethrough, Spoiler, CustomEmoji, and DateTime entities can be present in the text + public let text: FormattedText + + + public init( + isManual: Bool, + position: Int, + text: FormattedText + ) { + self.isManual = isManual + self.position = position + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThemeSettings.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThemeSettings.swift new file mode 100644 index 0000000000..6f9e072fc4 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThemeSettings.swift @@ -0,0 +1,51 @@ +// +// ThemeSettings.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes theme settings +public struct ThemeSettings: Codable, Equatable, Hashable { + + /// Theme accent color in ARGB format + public let accentColor: Int + + /// If true, the freeform gradient fill needs to be animated on every sent message + public let animateOutgoingMessageFill: Bool + + /// The background to be used in chats; may be null + public let background: Background? + + /// Base theme for this theme + public let baseTheme: BuiltInTheme + + /// Accent color of outgoing messages in ARGB format + public let outgoingMessageAccentColor: Int + + /// The fill to be used as a background for outgoing messages; may be null if the fill from the base theme must be used instead + public let outgoingMessageFill: BackgroundFill? + + + public init( + accentColor: Int, + animateOutgoingMessageFill: Bool, + background: Background?, + baseTheme: BuiltInTheme, + outgoingMessageAccentColor: Int, + outgoingMessageFill: BackgroundFill? + ) { + self.accentColor = accentColor + self.animateOutgoingMessageFill = animateOutgoingMessageFill + self.background = background + self.baseTheme = baseTheme + self.outgoingMessageAccentColor = outgoingMessageAccentColor + self.outgoingMessageFill = outgoingMessageFill + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Thumbnail.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Thumbnail.swift new file mode 100644 index 0000000000..fbe2c2941e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Thumbnail.swift @@ -0,0 +1,41 @@ +// +// Thumbnail.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a thumbnail +public struct Thumbnail: Codable, Equatable, Hashable { + + /// The thumbnail + public let file: File + + /// Thumbnail format + public let format: ThumbnailFormat + + /// Thumbnail height + public let height: Int + + /// Thumbnail width + public let width: Int + + + public init( + file: File, + format: ThumbnailFormat, + height: Int, + width: Int + ) { + self.file = file + self.format = format + self.height = height + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThumbnailFormat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThumbnailFormat.swift new file mode 100644 index 0000000000..b1a5aa7f19 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ThumbnailFormat.swift @@ -0,0 +1,97 @@ +// +// ThumbnailFormat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes format of a thumbnail +public indirect enum ThumbnailFormat: Codable, Equatable, Hashable { + + /// The thumbnail is in JPEG format + case thumbnailFormatJpeg + + /// The thumbnail is in static GIF format. It will be used only for some bot inline query results + case thumbnailFormatGif + + /// The thumbnail is in MPEG4 format. It will be used only for some animations and videos + case thumbnailFormatMpeg4 + + /// The thumbnail is in PNG format. It will be used only for background patterns + case thumbnailFormatPng + + /// The thumbnail is in TGS format. It will be used only for sticker sets + case thumbnailFormatTgs + + /// The thumbnail is in WEBM format. It will be used only for sticker sets + case thumbnailFormatWebm + + /// The thumbnail is in WEBP format. It will be used only for some stickers and sticker sets + case thumbnailFormatWebp + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case thumbnailFormatJpeg + case thumbnailFormatGif + case thumbnailFormatMpeg4 + case thumbnailFormatPng + case thumbnailFormatTgs + case thumbnailFormatWebm + case thumbnailFormatWebp + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .thumbnailFormatJpeg: + self = .thumbnailFormatJpeg + case .thumbnailFormatGif: + self = .thumbnailFormatGif + case .thumbnailFormatMpeg4: + self = .thumbnailFormatMpeg4 + case .thumbnailFormatPng: + self = .thumbnailFormatPng + case .thumbnailFormatTgs: + self = .thumbnailFormatTgs + case .thumbnailFormatWebm: + self = .thumbnailFormatWebm + case .thumbnailFormatWebp: + self = .thumbnailFormatWebp + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .thumbnailFormatJpeg: + try container.encode(Kind.thumbnailFormatJpeg, forKey: .type) + case .thumbnailFormatGif: + try container.encode(Kind.thumbnailFormatGif, forKey: .type) + case .thumbnailFormatMpeg4: + try container.encode(Kind.thumbnailFormatMpeg4, forKey: .type) + case .thumbnailFormatPng: + try container.encode(Kind.thumbnailFormatPng, forKey: .type) + case .thumbnailFormatTgs: + try container.encode(Kind.thumbnailFormatTgs, forKey: .type) + case .thumbnailFormatWebm: + try container.encode(Kind.thumbnailFormatWebm, forKey: .type) + case .thumbnailFormatWebp: + try container.encode(Kind.thumbnailFormatWebp, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UnreadReaction.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UnreadReaction.swift new file mode 100644 index 0000000000..39366d4db0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UnreadReaction.swift @@ -0,0 +1,36 @@ +// +// UnreadReaction.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about an unread reaction to a message +public struct UnreadReaction: Codable, Equatable, Hashable { + + /// True, if the reaction was added with a big animation + public let isBig: Bool + + /// Identifier of the sender, added the reaction + public let senderId: MessageSender + + /// Type of the reaction + public let type: ReactionType + + + public init( + isBig: Bool, + senderId: MessageSender, + type: ReactionType + ) { + self.isBig = isBig + self.senderId = senderId + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Update.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Update.swift new file mode 100644 index 0000000000..d4f6f51f23 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Update.swift @@ -0,0 +1,706 @@ +// +// Update.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains notifications about data changes +public indirect enum Update: Codable, Equatable, Hashable { + + /// The user authorization state has changed + case updateAuthorizationState(UpdateAuthorizationState) + + /// A new message was received; can also be an outgoing message + case updateNewMessage(UpdateNewMessage) + + /// A request to send a message has reached the Telegram server. This doesn't mean that the message will be sent successfully. This update is sent only if the option "use_quick_ack" is set to true. This update may be sent multiple times for the same message + case updateMessageSendAcknowledged(UpdateMessageSendAcknowledged) + + /// A message has been successfully sent + case updateMessageSendSucceeded(UpdateMessageSendSucceeded) + + /// A message failed to send. Be aware that some messages being sent can be irrecoverably deleted, in which case updateDeleteMessages will be received instead of this update + case updateMessageSendFailed(UpdateMessageSendFailed) + + /// The message content has changed + case updateMessageContent(UpdateMessageContent) + + /// A new chat has been loaded/created. This update is guaranteed to come before the chat identifier is returned to the application. The chat field changes will be reported through separate updates + case updateNewChat(UpdateNewChat) + + /// The title of a chat was changed + case updateChatTitle(UpdateChatTitle) + + /// A chat photo was changed + case updateChatPhoto(UpdateChatPhoto) + + /// Chat permissions were changed + case updateChatPermissions(UpdateChatPermissions) + + /// The last message of a chat was changed + case updateChatLastMessage(UpdateChatLastMessage) + + /// The position of a chat in a chat list has changed. An updateChatLastMessage or updateChatDraftMessage update might be sent instead of the update + case updateChatPosition(UpdateChatPosition) + + /// A chat was added to a chat list + case updateChatAddedToList(UpdateChatAddedToList) + + /// A chat was removed from a chat list + case updateChatRemovedFromList(UpdateChatRemovedFromList) + + /// Incoming messages were read or the number of unread messages has been changed + case updateChatReadInbox(UpdateChatReadInbox) + + /// Outgoing messages were read + case updateChatReadOutbox(UpdateChatReadOutbox) + + /// A chat draft has changed. Be aware that the update may come in the currently opened chat but with old content of the draft. If the user has changed the content of the draft, this update mustn't be applied + case updateChatDraftMessage(UpdateChatDraftMessage) + + /// Notification settings for a chat were changed + case updateChatNotificationSettings(UpdateChatNotificationSettings) + + /// The list of chat folders or a chat folder has changed + case updateChatFolders(UpdateChatFolders) + + /// Some messages were deleted + case updateDeleteMessages(UpdateDeleteMessages) + + /// Some data of a user has changed. This update is guaranteed to come before the user identifier is returned to the application + case updateUser(UpdateUser) + + /// Information about a file was updated + case updateFile(UpdateFile) + + /// A poll was updated; for bots only + case updatePoll(UpdatePoll) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case updateAuthorizationState + case updateNewMessage + case updateMessageSendAcknowledged + case updateMessageSendSucceeded + case updateMessageSendFailed + case updateMessageContent + case updateNewChat + case updateChatTitle + case updateChatPhoto + case updateChatPermissions + case updateChatLastMessage + case updateChatPosition + case updateChatAddedToList + case updateChatRemovedFromList + case updateChatReadInbox + case updateChatReadOutbox + case updateChatDraftMessage + case updateChatNotificationSettings + case updateChatFolders + case updateDeleteMessages + case updateUser + case updateFile + case updatePoll + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .updateAuthorizationState: + let value = try UpdateAuthorizationState(from: decoder) + self = .updateAuthorizationState(value) + case .updateNewMessage: + let value = try UpdateNewMessage(from: decoder) + self = .updateNewMessage(value) + case .updateMessageSendAcknowledged: + let value = try UpdateMessageSendAcknowledged(from: decoder) + self = .updateMessageSendAcknowledged(value) + case .updateMessageSendSucceeded: + let value = try UpdateMessageSendSucceeded(from: decoder) + self = .updateMessageSendSucceeded(value) + case .updateMessageSendFailed: + let value = try UpdateMessageSendFailed(from: decoder) + self = .updateMessageSendFailed(value) + case .updateMessageContent: + let value = try UpdateMessageContent(from: decoder) + self = .updateMessageContent(value) + case .updateNewChat: + let value = try UpdateNewChat(from: decoder) + self = .updateNewChat(value) + case .updateChatTitle: + let value = try UpdateChatTitle(from: decoder) + self = .updateChatTitle(value) + case .updateChatPhoto: + let value = try UpdateChatPhoto(from: decoder) + self = .updateChatPhoto(value) + case .updateChatPermissions: + let value = try UpdateChatPermissions(from: decoder) + self = .updateChatPermissions(value) + case .updateChatLastMessage: + let value = try UpdateChatLastMessage(from: decoder) + self = .updateChatLastMessage(value) + case .updateChatPosition: + let value = try UpdateChatPosition(from: decoder) + self = .updateChatPosition(value) + case .updateChatAddedToList: + let value = try UpdateChatAddedToList(from: decoder) + self = .updateChatAddedToList(value) + case .updateChatRemovedFromList: + let value = try UpdateChatRemovedFromList(from: decoder) + self = .updateChatRemovedFromList(value) + case .updateChatReadInbox: + let value = try UpdateChatReadInbox(from: decoder) + self = .updateChatReadInbox(value) + case .updateChatReadOutbox: + let value = try UpdateChatReadOutbox(from: decoder) + self = .updateChatReadOutbox(value) + case .updateChatDraftMessage: + let value = try UpdateChatDraftMessage(from: decoder) + self = .updateChatDraftMessage(value) + case .updateChatNotificationSettings: + let value = try UpdateChatNotificationSettings(from: decoder) + self = .updateChatNotificationSettings(value) + case .updateChatFolders: + let value = try UpdateChatFolders(from: decoder) + self = .updateChatFolders(value) + case .updateDeleteMessages: + let value = try UpdateDeleteMessages(from: decoder) + self = .updateDeleteMessages(value) + case .updateUser: + let value = try UpdateUser(from: decoder) + self = .updateUser(value) + case .updateFile: + let value = try UpdateFile(from: decoder) + self = .updateFile(value) + case .updatePoll: + let value = try UpdatePoll(from: decoder) + self = .updatePoll(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .updateAuthorizationState(let value): + try container.encode(Kind.updateAuthorizationState, forKey: .type) + try value.encode(to: encoder) + case .updateNewMessage(let value): + try container.encode(Kind.updateNewMessage, forKey: .type) + try value.encode(to: encoder) + case .updateMessageSendAcknowledged(let value): + try container.encode(Kind.updateMessageSendAcknowledged, forKey: .type) + try value.encode(to: encoder) + case .updateMessageSendSucceeded(let value): + try container.encode(Kind.updateMessageSendSucceeded, forKey: .type) + try value.encode(to: encoder) + case .updateMessageSendFailed(let value): + try container.encode(Kind.updateMessageSendFailed, forKey: .type) + try value.encode(to: encoder) + case .updateMessageContent(let value): + try container.encode(Kind.updateMessageContent, forKey: .type) + try value.encode(to: encoder) + case .updateNewChat(let value): + try container.encode(Kind.updateNewChat, forKey: .type) + try value.encode(to: encoder) + case .updateChatTitle(let value): + try container.encode(Kind.updateChatTitle, forKey: .type) + try value.encode(to: encoder) + case .updateChatPhoto(let value): + try container.encode(Kind.updateChatPhoto, forKey: .type) + try value.encode(to: encoder) + case .updateChatPermissions(let value): + try container.encode(Kind.updateChatPermissions, forKey: .type) + try value.encode(to: encoder) + case .updateChatLastMessage(let value): + try container.encode(Kind.updateChatLastMessage, forKey: .type) + try value.encode(to: encoder) + case .updateChatPosition(let value): + try container.encode(Kind.updateChatPosition, forKey: .type) + try value.encode(to: encoder) + case .updateChatAddedToList(let value): + try container.encode(Kind.updateChatAddedToList, forKey: .type) + try value.encode(to: encoder) + case .updateChatRemovedFromList(let value): + try container.encode(Kind.updateChatRemovedFromList, forKey: .type) + try value.encode(to: encoder) + case .updateChatReadInbox(let value): + try container.encode(Kind.updateChatReadInbox, forKey: .type) + try value.encode(to: encoder) + case .updateChatReadOutbox(let value): + try container.encode(Kind.updateChatReadOutbox, forKey: .type) + try value.encode(to: encoder) + case .updateChatDraftMessage(let value): + try container.encode(Kind.updateChatDraftMessage, forKey: .type) + try value.encode(to: encoder) + case .updateChatNotificationSettings(let value): + try container.encode(Kind.updateChatNotificationSettings, forKey: .type) + try value.encode(to: encoder) + case .updateChatFolders(let value): + try container.encode(Kind.updateChatFolders, forKey: .type) + try value.encode(to: encoder) + case .updateDeleteMessages(let value): + try container.encode(Kind.updateDeleteMessages, forKey: .type) + try value.encode(to: encoder) + case .updateUser(let value): + try container.encode(Kind.updateUser, forKey: .type) + try value.encode(to: encoder) + case .updateFile(let value): + try container.encode(Kind.updateFile, forKey: .type) + try value.encode(to: encoder) + case .updatePoll(let value): + try container.encode(Kind.updatePoll, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The user authorization state has changed +public struct UpdateAuthorizationState: Codable, Equatable, Hashable { + + /// New authorization state + public let authorizationState: AuthorizationState + + + public init(authorizationState: AuthorizationState) { + self.authorizationState = authorizationState + } +} + +/// A new message was received; can also be an outgoing message +public struct UpdateNewMessage: Codable, Equatable, Hashable { + + /// The new message + public let message: Message + + + public init(message: Message) { + self.message = message + } +} + +/// A request to send a message has reached the Telegram server. This doesn't mean that the message will be sent successfully. This update is sent only if the option "use_quick_ack" is set to true. This update may be sent multiple times for the same message +public struct UpdateMessageSendAcknowledged: Codable, Equatable, Hashable { + + /// The chat identifier of the sent message + public let chatId: Int64 + + /// A temporary message identifier + public let messageId: Int64 + + + public init( + chatId: Int64, + messageId: Int64 + ) { + self.chatId = chatId + self.messageId = messageId + } +} + +/// A message has been successfully sent +public struct UpdateMessageSendSucceeded: Codable, Equatable, Hashable { + + /// The sent message. Almost any field of the new message can be different from the corresponding field of the original message. For example, the field scheduling_state may change, making the message scheduled, or non-scheduled + public let message: Message + + /// The previous temporary message identifier + public let oldMessageId: Int64 + + + public init( + message: Message, + oldMessageId: Int64 + ) { + self.message = message + self.oldMessageId = oldMessageId + } +} + +/// A message failed to send. Be aware that some messages being sent can be irrecoverably deleted, in which case updateDeleteMessages will be received instead of this update +public struct UpdateMessageSendFailed: Codable, Equatable, Hashable { + + /// The cause of the message sending failure + public let error: TDError + + /// The failed to send message + public let message: Message + + /// The previous temporary message identifier + public let oldMessageId: Int64 + + + public init( + error: TDError, + message: Message, + oldMessageId: Int64 + ) { + self.error = error + self.message = message + self.oldMessageId = oldMessageId + } +} + +/// The message content has changed +public struct UpdateMessageContent: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// Message identifier + public let messageId: Int64 + + /// New message content + public let newContent: MessageContent + + + public init( + chatId: Int64, + messageId: Int64, + newContent: MessageContent + ) { + self.chatId = chatId + self.messageId = messageId + self.newContent = newContent + } +} + +/// A new chat has been loaded/created. This update is guaranteed to come before the chat identifier is returned to the application. The chat field changes will be reported through separate updates +public struct UpdateNewChat: Codable, Equatable, Hashable { + + /// The chat + public let chat: Chat + + + public init(chat: Chat) { + self.chat = chat + } +} + +/// The title of a chat was changed +public struct UpdateChatTitle: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new chat title + public let title: String + + + public init( + chatId: Int64, + title: String + ) { + self.chatId = chatId + self.title = title + } +} + +/// A chat photo was changed +public struct UpdateChatPhoto: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new chat photo; may be null + public let photo: ChatPhotoInfo? + + + public init( + chatId: Int64, + photo: ChatPhotoInfo? + ) { + self.chatId = chatId + self.photo = photo + } +} + +/// Chat permissions were changed +public struct UpdateChatPermissions: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new chat permissions + public let permissions: ChatPermissions + + + public init( + chatId: Int64, + permissions: ChatPermissions + ) { + self.chatId = chatId + self.permissions = permissions + } +} + +/// The last message of a chat was changed +public struct UpdateChatLastMessage: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new last message in the chat; may be null if the last message became unknown. While the last message is unknown, new messages can be added to the chat without corresponding updateNewMessage update + public let lastMessage: Message? + + /// The new chat positions in the chat lists + public let positions: [ChatPosition] + + + public init( + chatId: Int64, + lastMessage: Message?, + positions: [ChatPosition] + ) { + self.chatId = chatId + self.lastMessage = lastMessage + self.positions = positions + } +} + +/// The position of a chat in a chat list has changed. An updateChatLastMessage or updateChatDraftMessage update might be sent instead of the update +public struct UpdateChatPosition: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// New chat position. If new order is 0, then the chat needs to be removed from the list + public let position: ChatPosition + + + public init( + chatId: Int64, + position: ChatPosition + ) { + self.chatId = chatId + self.position = position + } +} + +/// A chat was added to a chat list +public struct UpdateChatAddedToList: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The chat list to which the chat was added + public let chatList: ChatList + + + public init( + chatId: Int64, + chatList: ChatList + ) { + self.chatId = chatId + self.chatList = chatList + } +} + +/// A chat was removed from a chat list +public struct UpdateChatRemovedFromList: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The chat list from which the chat was removed + public let chatList: ChatList + + + public init( + chatId: Int64, + chatList: ChatList + ) { + self.chatId = chatId + self.chatList = chatList + } +} + +/// Incoming messages were read or the number of unread messages has been changed +public struct UpdateChatReadInbox: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// Identifier of the last read incoming message + public let lastReadInboxMessageId: Int64 + + /// The number of unread messages left in the chat + public let unreadCount: Int + + + public init( + chatId: Int64, + lastReadInboxMessageId: Int64, + unreadCount: Int + ) { + self.chatId = chatId + self.lastReadInboxMessageId = lastReadInboxMessageId + self.unreadCount = unreadCount + } +} + +/// Outgoing messages were read +public struct UpdateChatReadOutbox: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// Identifier of last read outgoing message + public let lastReadOutboxMessageId: Int64 + + + public init( + chatId: Int64, + lastReadOutboxMessageId: Int64 + ) { + self.chatId = chatId + self.lastReadOutboxMessageId = lastReadOutboxMessageId + } +} + +/// A chat draft has changed. Be aware that the update may come in the currently opened chat but with old content of the draft. If the user has changed the content of the draft, this update mustn't be applied +public struct UpdateChatDraftMessage: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new draft message; may be null if none + public let draftMessage: DraftMessage? + + /// The new chat positions in the chat lists + public let positions: [ChatPosition] + + + public init( + chatId: Int64, + draftMessage: DraftMessage?, + positions: [ChatPosition] + ) { + self.chatId = chatId + self.draftMessage = draftMessage + self.positions = positions + } +} + +/// Notification settings for a chat were changed +public struct UpdateChatNotificationSettings: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// The new notification settings + public let notificationSettings: ChatNotificationSettings + + + public init( + chatId: Int64, + notificationSettings: ChatNotificationSettings + ) { + self.chatId = chatId + self.notificationSettings = notificationSettings + } +} + +/// The list of chat folders or a chat folder has changed +public struct UpdateChatFolders: Codable, Equatable, Hashable { + + /// True, if folder tags are enabled + public let areTagsEnabled: Bool + + /// The new list of chat folders + public let chatFolders: [ChatFolderInfo] + + /// Position of the main chat list among chat folders, 0-based + public let mainChatListPosition: Int + + + public init( + areTagsEnabled: Bool, + chatFolders: [ChatFolderInfo], + mainChatListPosition: Int + ) { + self.areTagsEnabled = areTagsEnabled + self.chatFolders = chatFolders + self.mainChatListPosition = mainChatListPosition + } +} + +/// Some messages were deleted +public struct UpdateDeleteMessages: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64 + + /// True, if the messages are deleted only from the cache and can possibly be retrieved again in the future + public let fromCache: Bool + + /// True, if the messages are permanently deleted by a user (as opposed to just becoming inaccessible) + public let isPermanent: Bool + + /// Identifiers of the deleted messages + public let messageIds: [Int64] + + + public init( + chatId: Int64, + fromCache: Bool, + isPermanent: Bool, + messageIds: [Int64] + ) { + self.chatId = chatId + self.fromCache = fromCache + self.isPermanent = isPermanent + self.messageIds = messageIds + } +} + +/// Some data of a user has changed. This update is guaranteed to come before the user identifier is returned to the application +public struct UpdateUser: Codable, Equatable, Hashable { + + /// New data about the user + public let user: User + + + public init(user: User) { + self.user = user + } +} + +/// Information about a file was updated +public struct UpdateFile: Codable, Equatable, Hashable { + + /// New data about the file + public let file: File + + + public init(file: File) { + self.file = file + } +} + +/// A poll was updated; for bots only +public struct UpdatePoll: Codable, Equatable, Hashable { + + /// New data about the poll + public let poll: Poll + + + public init(poll: Poll) { + self.poll = poll + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGift.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGift.swift new file mode 100644 index 0000000000..349ca6d36b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGift.swift @@ -0,0 +1,166 @@ +// +// UpgradedGift.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes an upgraded gift that can be transferred to another owner or transferred to the TON blockchain as an NFT +public struct UpgradedGift: Codable, Equatable, Hashable, Identifiable { + + /// Backdrop of the upgraded gift + public let backdrop: UpgradedGiftBackdrop + + /// True, if an offer to purchase the gift can be sent using sendGiftPurchaseOffer + public let canSendPurchaseOffer: Bool + + /// Colors that can be set for user's name, background of empty chat photo, replies to messages and link previews; may be null if none or unknown + public let colors: UpgradedGiftColors? + + /// Probability that the gift adds to the chance of successful crafting of a new gift; 0 if the gift can't be used for crafting + public let craftProbabilityPerMille: Int + + /// Address of the gift NFT in TON blockchain; may be empty if none. Append the address to getOption("ton_blockchain_explorer_url") to get a link with information about the address + public let giftAddress: String + + /// Identifier of the user or the chat to which the upgraded gift was assigned from blockchain; may be null if none or unknown + public let hostId: MessageSender? + + /// Unique identifier of the gift + public let id: TdInt64 + + /// True, if the gift was used to craft another gift + public let isBurned: Bool + + /// True, if the gift was craft from another gifts + public let isCrafted: Bool + + /// True, if the original gift could have been bought only by Telegram Premium subscribers + public let isPremium: Bool + + /// True, if the gift can be used to set a theme in a chat + public let isThemeAvailable: Bool + + /// The maximum number of gifts that can be upgraded from the same gift + public let maxUpgradedCount: Int + + /// Model of the upgraded gift + public let model: UpgradedGiftModel + + /// Unique name of the upgraded gift that can be used with internalLinkTypeUpgradedGift or sendResoldGift + public let name: String + + /// Unique number of the upgraded gift among gifts upgraded from the same gift + public let number: Int + + /// Information about the originally sent gift; may be null if unknown + public let originalDetails: UpgradedGiftOriginalDetails? + + /// Address of the gift NFT owner in TON blockchain; may be empty if none. Append the address to getOption("ton_blockchain_explorer_url") to get a link with information about the address + public let ownerAddress: String + + /// Identifier of the user or the chat that owns the upgraded gift; may be null if none or unknown + public let ownerId: MessageSender? + + /// Name of the owner for the case when owner identifier and address aren't known + public let ownerName: String + + /// Identifier of the chat that published the gift; 0 if none + public let publisherChatId: Int64 + + /// Unique identifier of the regular gift from which the gift was upgraded; may be 0 for short period of time for old gifts from database + public let regularGiftId: TdInt64 + + /// Resale parameters of the gift; may be null if resale isn't possible + public let resaleParameters: GiftResaleParameters? + + /// Symbol of the upgraded gift + public let symbol: UpgradedGiftSymbol + + /// The title of the upgraded gift + public let title: String + + /// Total number of gifts that were upgraded from the same gift + public let totalUpgradedCount: Int + + /// Identifier of the chat for which the gift is used to set a theme; 0 if none or the gift isn't owned by the current user + public let usedThemeChatId: Int64 + + /// Estimated value of the gift; in the smallest units of the currency; 0 if unavailable + public let valueAmount: Int64 + + /// ISO 4217 currency code of the currency in which value of the gift is represented; may be empty if unavailable + public let valueCurrency: String + + /// Estimated value of the gift in USD; in USD cents; 0 if unavailable + public let valueUsdAmount: Int64 + + + public init( + backdrop: UpgradedGiftBackdrop, + canSendPurchaseOffer: Bool, + colors: UpgradedGiftColors?, + craftProbabilityPerMille: Int, + giftAddress: String, + hostId: MessageSender?, + id: TdInt64, + isBurned: Bool, + isCrafted: Bool, + isPremium: Bool, + isThemeAvailable: Bool, + maxUpgradedCount: Int, + model: UpgradedGiftModel, + name: String, + number: Int, + originalDetails: UpgradedGiftOriginalDetails?, + ownerAddress: String, + ownerId: MessageSender?, + ownerName: String, + publisherChatId: Int64, + regularGiftId: TdInt64, + resaleParameters: GiftResaleParameters?, + symbol: UpgradedGiftSymbol, + title: String, + totalUpgradedCount: Int, + usedThemeChatId: Int64, + valueAmount: Int64, + valueCurrency: String, + valueUsdAmount: Int64 + ) { + self.backdrop = backdrop + self.canSendPurchaseOffer = canSendPurchaseOffer + self.colors = colors + self.craftProbabilityPerMille = craftProbabilityPerMille + self.giftAddress = giftAddress + self.hostId = hostId + self.id = id + self.isBurned = isBurned + self.isCrafted = isCrafted + self.isPremium = isPremium + self.isThemeAvailable = isThemeAvailable + self.maxUpgradedCount = maxUpgradedCount + self.model = model + self.name = name + self.number = number + self.originalDetails = originalDetails + self.ownerAddress = ownerAddress + self.ownerId = ownerId + self.ownerName = ownerName + self.publisherChatId = publisherChatId + self.regularGiftId = regularGiftId + self.resaleParameters = resaleParameters + self.symbol = symbol + self.title = title + self.totalUpgradedCount = totalUpgradedCount + self.usedThemeChatId = usedThemeChatId + self.valueAmount = valueAmount + self.valueCurrency = valueCurrency + self.valueUsdAmount = valueUsdAmount + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftAttributeRarity.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftAttributeRarity.swift new file mode 100644 index 0000000000..27ea92a267 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftAttributeRarity.swift @@ -0,0 +1,95 @@ +// +// UpgradedGiftAttributeRarity.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes rarity of an upgraded gift attribute +public indirect enum UpgradedGiftAttributeRarity: Codable, Equatable, Hashable { + + /// The rarity is represented as the numeric frequency of the model + case upgradedGiftAttributeRarityPerMille(UpgradedGiftAttributeRarityPerMille) + + /// The attribute is uncommon + case upgradedGiftAttributeRarityUncommon + + /// The attribute is rare + case upgradedGiftAttributeRarityRare + + /// The attribute is epic + case upgradedGiftAttributeRarityEpic + + /// The attribute is legendary + case upgradedGiftAttributeRarityLegendary + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case upgradedGiftAttributeRarityPerMille + case upgradedGiftAttributeRarityUncommon + case upgradedGiftAttributeRarityRare + case upgradedGiftAttributeRarityEpic + case upgradedGiftAttributeRarityLegendary + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .upgradedGiftAttributeRarityPerMille: + let value = try UpgradedGiftAttributeRarityPerMille(from: decoder) + self = .upgradedGiftAttributeRarityPerMille(value) + case .upgradedGiftAttributeRarityUncommon: + self = .upgradedGiftAttributeRarityUncommon + case .upgradedGiftAttributeRarityRare: + self = .upgradedGiftAttributeRarityRare + case .upgradedGiftAttributeRarityEpic: + self = .upgradedGiftAttributeRarityEpic + case .upgradedGiftAttributeRarityLegendary: + self = .upgradedGiftAttributeRarityLegendary + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .upgradedGiftAttributeRarityPerMille(let value): + try container.encode(Kind.upgradedGiftAttributeRarityPerMille, forKey: .type) + try value.encode(to: encoder) + case .upgradedGiftAttributeRarityUncommon: + try container.encode(Kind.upgradedGiftAttributeRarityUncommon, forKey: .type) + case .upgradedGiftAttributeRarityRare: + try container.encode(Kind.upgradedGiftAttributeRarityRare, forKey: .type) + case .upgradedGiftAttributeRarityEpic: + try container.encode(Kind.upgradedGiftAttributeRarityEpic, forKey: .type) + case .upgradedGiftAttributeRarityLegendary: + try container.encode(Kind.upgradedGiftAttributeRarityLegendary, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The rarity is represented as the numeric frequency of the model +public struct UpgradedGiftAttributeRarityPerMille: Codable, Equatable, Hashable { + + /// The number of upgraded gifts that receive this attribute for each 1000 gifts upgraded; if 0, then it can be shown as "<0.1%" + public let perMille: Int + + + public init(perMille: Int) { + self.perMille = perMille + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdrop.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdrop.swift new file mode 100644 index 0000000000..bbe4b15b7d --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdrop.swift @@ -0,0 +1,41 @@ +// +// UpgradedGiftBackdrop.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a backdrop of an upgraded gift +public struct UpgradedGiftBackdrop: Codable, Equatable, Hashable, Identifiable { + + /// Colors of the backdrop + public let colors: UpgradedGiftBackdropColors + + /// Unique identifier of the backdrop + public let id: Int + + /// Name of the backdrop + public let name: String + + /// The rarity of the backdrop + public let rarity: UpgradedGiftAttributeRarity + + + public init( + colors: UpgradedGiftBackdropColors, + id: Int, + name: String, + rarity: UpgradedGiftAttributeRarity + ) { + self.colors = colors + self.id = id + self.name = name + self.rarity = rarity + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdropColors.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdropColors.swift new file mode 100644 index 0000000000..7da97a1fb8 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftBackdropColors.swift @@ -0,0 +1,41 @@ +// +// UpgradedGiftBackdropColors.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes colors of a backdrop of an upgraded gift +public struct UpgradedGiftBackdropColors: Codable, Equatable, Hashable { + + /// A color in the center of the backdrop in the RGB format + public let centerColor: Int + + /// A color on the edges of the backdrop in the RGB format + public let edgeColor: Int + + /// A color to be applied for the symbol in the RGB format + public let symbolColor: Int + + /// A color for the text on the backdrop in the RGB format + public let textColor: Int + + + public init( + centerColor: Int, + edgeColor: Int, + symbolColor: Int, + textColor: Int + ) { + self.centerColor = centerColor + self.edgeColor = edgeColor + self.symbolColor = symbolColor + self.textColor = textColor + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftColors.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftColors.swift new file mode 100644 index 0000000000..0940f25e65 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftColors.swift @@ -0,0 +1,56 @@ +// +// UpgradedGiftColors.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about color scheme for user's name, background of empty chat photo, replies to messages and link previews +public struct UpgradedGiftColors: Codable, Equatable, Hashable, Identifiable { + + /// Accent color to use in dark themes in RGB format + public let darkThemeAccentColor: Int + + /// The list of 1-3 colors in RGB format, describing the accent color, as expected to be shown in dark themes + public let darkThemeColors: [Int] + + /// Unique identifier of the upgraded gift colors + public let id: TdInt64 + + /// Accent color to use in light themes in RGB format + public let lightThemeAccentColor: Int + + /// The list of 1-3 colors in RGB format, describing the accent color, as expected to be shown in light themes + public let lightThemeColors: [Int] + + /// Custom emoji identifier of the model of the upgraded gift + public let modelCustomEmojiId: TdInt64 + + /// Custom emoji identifier of the symbol of the upgraded gift + public let symbolCustomEmojiId: TdInt64 + + + public init( + darkThemeAccentColor: Int, + darkThemeColors: [Int], + id: TdInt64, + lightThemeAccentColor: Int, + lightThemeColors: [Int], + modelCustomEmojiId: TdInt64, + symbolCustomEmojiId: TdInt64 + ) { + self.darkThemeAccentColor = darkThemeAccentColor + self.darkThemeColors = darkThemeColors + self.id = id + self.lightThemeAccentColor = lightThemeAccentColor + self.lightThemeColors = lightThemeColors + self.modelCustomEmojiId = modelCustomEmojiId + self.symbolCustomEmojiId = symbolCustomEmojiId + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftModel.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftModel.swift new file mode 100644 index 0000000000..fb41077b73 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftModel.swift @@ -0,0 +1,41 @@ +// +// UpgradedGiftModel.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a model of an upgraded gift +public struct UpgradedGiftModel: Codable, Equatable, Hashable { + + /// True, if the model can be obtained only through gift crafting + public let isCrafted: Bool + + /// Name of the model + public let name: String + + /// The rarity of the model + public let rarity: UpgradedGiftAttributeRarity + + /// The sticker representing the upgraded gift + public let sticker: Sticker + + + public init( + isCrafted: Bool, + name: String, + rarity: UpgradedGiftAttributeRarity, + sticker: Sticker + ) { + self.isCrafted = isCrafted + self.name = name + self.rarity = rarity + self.sticker = sticker + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOrigin.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOrigin.swift new file mode 100644 index 0000000000..0e38276764 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOrigin.swift @@ -0,0 +1,139 @@ +// +// UpgradedGiftOrigin.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes origin from which the upgraded gift was obtained +public indirect enum UpgradedGiftOrigin: Codable, Equatable, Hashable { + + /// The gift was obtained by upgrading of a previously received gift + case upgradedGiftOriginUpgrade(UpgradedGiftOriginUpgrade) + + /// The gift was transferred from another owner + case upgradedGiftOriginTransfer + + /// The gift was bought from another user + case upgradedGiftOriginResale(UpgradedGiftOriginResale) + + /// The gift was assigned from blockchain and isn't owned by the current user. The gift can't be transferred, resold or withdrawn to blockchain + case upgradedGiftOriginBlockchain + + /// The sender or receiver of the message has paid for upgraid of the gift, which has been completed + case upgradedGiftOriginPrepaidUpgrade + + /// The gift was bought through an offer + case upgradedGiftOriginOffer(UpgradedGiftOriginOffer) + + /// The gift was crafted from other gifts + case upgradedGiftOriginCraft + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case upgradedGiftOriginUpgrade + case upgradedGiftOriginTransfer + case upgradedGiftOriginResale + case upgradedGiftOriginBlockchain + case upgradedGiftOriginPrepaidUpgrade + case upgradedGiftOriginOffer + case upgradedGiftOriginCraft + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .upgradedGiftOriginUpgrade: + let value = try UpgradedGiftOriginUpgrade(from: decoder) + self = .upgradedGiftOriginUpgrade(value) + case .upgradedGiftOriginTransfer: + self = .upgradedGiftOriginTransfer + case .upgradedGiftOriginResale: + let value = try UpgradedGiftOriginResale(from: decoder) + self = .upgradedGiftOriginResale(value) + case .upgradedGiftOriginBlockchain: + self = .upgradedGiftOriginBlockchain + case .upgradedGiftOriginPrepaidUpgrade: + self = .upgradedGiftOriginPrepaidUpgrade + case .upgradedGiftOriginOffer: + let value = try UpgradedGiftOriginOffer(from: decoder) + self = .upgradedGiftOriginOffer(value) + case .upgradedGiftOriginCraft: + self = .upgradedGiftOriginCraft + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .upgradedGiftOriginUpgrade(let value): + try container.encode(Kind.upgradedGiftOriginUpgrade, forKey: .type) + try value.encode(to: encoder) + case .upgradedGiftOriginTransfer: + try container.encode(Kind.upgradedGiftOriginTransfer, forKey: .type) + case .upgradedGiftOriginResale(let value): + try container.encode(Kind.upgradedGiftOriginResale, forKey: .type) + try value.encode(to: encoder) + case .upgradedGiftOriginBlockchain: + try container.encode(Kind.upgradedGiftOriginBlockchain, forKey: .type) + case .upgradedGiftOriginPrepaidUpgrade: + try container.encode(Kind.upgradedGiftOriginPrepaidUpgrade, forKey: .type) + case .upgradedGiftOriginOffer(let value): + try container.encode(Kind.upgradedGiftOriginOffer, forKey: .type) + try value.encode(to: encoder) + case .upgradedGiftOriginCraft: + try container.encode(Kind.upgradedGiftOriginCraft, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The gift was obtained by upgrading of a previously received gift +public struct UpgradedGiftOriginUpgrade: Codable, Equatable, Hashable { + + /// Identifier of the message with the regular gift that was upgraded; may be 0 or an identifier of a deleted message + public let giftMessageId: Int64 + + + public init(giftMessageId: Int64) { + self.giftMessageId = giftMessageId + } +} + +/// The gift was bought from another user +public struct UpgradedGiftOriginResale: Codable, Equatable, Hashable { + + /// Price paid for the gift + public let price: GiftResalePrice + + + public init(price: GiftResalePrice) { + self.price = price + } +} + +/// The gift was bought through an offer +public struct UpgradedGiftOriginOffer: Codable, Equatable, Hashable { + + /// Price paid for the gift + public let price: GiftResalePrice + + + public init(price: GiftResalePrice) { + self.price = price + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOriginalDetails.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOriginalDetails.swift new file mode 100644 index 0000000000..bc443df25a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftOriginalDetails.swift @@ -0,0 +1,41 @@ +// +// UpgradedGiftOriginalDetails.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the original details about the gift +public struct UpgradedGiftOriginalDetails: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the gift was sent + public let date: Int + + /// Identifier of the user or the chat that received the gift + public let receiverId: MessageSender + + /// Identifier of the user or the chat that sent the gift; may be null if the gift was private + public let senderId: MessageSender? + + /// Message added to the gift + public let text: FormattedText + + + public init( + date: Int, + receiverId: MessageSender, + senderId: MessageSender?, + text: FormattedText + ) { + self.date = date + self.receiverId = receiverId + self.senderId = senderId + self.text = text + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftSymbol.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftSymbol.swift new file mode 100644 index 0000000000..8a8b9e9486 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UpgradedGiftSymbol.swift @@ -0,0 +1,36 @@ +// +// UpgradedGiftSymbol.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a symbol shown on the pattern of an upgraded gift +public struct UpgradedGiftSymbol: Codable, Equatable, Hashable { + + /// Name of the symbol + public let name: String + + /// The rarity of the symbol + public let rarity: UpgradedGiftAttributeRarity + + /// The sticker representing the symbol + public let sticker: Sticker + + + public init( + name: String, + rarity: UpgradedGiftAttributeRarity, + sticker: Sticker + ) { + self.name = name + self.rarity = rarity + self.sticker = sticker + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/User.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/User.swift new file mode 100644 index 0000000000..445520451f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/User.swift @@ -0,0 +1,156 @@ +// +// User.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a user +public struct User: Codable, Equatable, Hashable, Identifiable { + + /// Identifier of the accent color for name, and backgrounds of profile photo, reply header, and link preview + public let accentColorId: Int + + /// State of active stories of the user; may be null if the user has no active stories + public let activeStoryState: ActiveStoryState? + + /// True, if the user added the current bot to attachment menu; only available to bots + public let addedToAttachmentMenu: Bool + + /// Identifier of a custom emoji to be shown on the reply header and link preview background; 0 if none + public let backgroundCustomEmojiId: TdInt64 + + /// Emoji status to be shown instead of the default Telegram Premium badge; may be null + public let emojiStatus: EmojiStatus? + + /// First name of the user + public let firstName: String + + /// If false, the user is inaccessible, and the only information known about the user is inside this class. Identifier of the user can't be passed to any method + public let haveAccess: Bool + + /// User identifier + public let id: Int64 + + /// The user is a close friend of the current user; implies that the user is a contact + public let isCloseFriend: Bool + + /// The user is a contact of the current user + public let isContact: Bool + + /// The user is a contact of the current user and the current user is a contact of the user + public let isMutualContact: Bool + + /// True, if the user is a Telegram Premium user + public let isPremium: Bool + + /// True, if the user is Telegram support account + public let isSupport: Bool + + /// IETF language tag of the user's language; only available to bots + public let languageCode: String + + /// Last name of the user + public let lastName: String + + /// Number of Telegram Stars that must be paid by general user for each sent message to the user. If positive and userFullInfo is unknown, use canSendMessageToUser to check whether the current user must pay + public let paidMessageStarCount: Int64 + + /// Phone number of the user + public let phoneNumber: String + + /// Identifier of the accent color for the user's profile; -1 if none + public let profileAccentColorId: Int + + /// Identifier of a custom emoji to be shown on the background of the user's profile; 0 if none + public let profileBackgroundCustomEmojiId: TdInt64 + + /// Profile photo of the user; may be null + public let profilePhoto: ProfilePhoto? + + /// Information about restrictions that must be applied to the corresponding private chat; may be null if none + public let restrictionInfo: RestrictionInfo? + + /// True, if the user may restrict new chats with non-contacts. Use canSendMessageToUser to check whether the current user can message the user or try to create a chat with them + public let restrictsNewChats: Bool + + /// Current online status of the user + public let status: UserStatus + + /// Type of the user + public let type: UserType + + /// Color scheme based on an upgraded gift to be used for the user instead of accent_color_id and background_custom_emoji_id; may be null if none + public let upgradedGiftColors: UpgradedGiftColors? + + /// Usernames of the user; may be null + public let usernames: Usernames? + + /// Information about verification status of the user; may be null if none + public let verificationStatus: VerificationStatus? + + + public init( + accentColorId: Int, + activeStoryState: ActiveStoryState?, + addedToAttachmentMenu: Bool, + backgroundCustomEmojiId: TdInt64, + emojiStatus: EmojiStatus?, + firstName: String, + haveAccess: Bool, + id: Int64, + isCloseFriend: Bool, + isContact: Bool, + isMutualContact: Bool, + isPremium: Bool, + isSupport: Bool, + languageCode: String, + lastName: String, + paidMessageStarCount: Int64, + phoneNumber: String, + profileAccentColorId: Int, + profileBackgroundCustomEmojiId: TdInt64, + profilePhoto: ProfilePhoto?, + restrictionInfo: RestrictionInfo?, + restrictsNewChats: Bool, + status: UserStatus, + type: UserType, + upgradedGiftColors: UpgradedGiftColors?, + usernames: Usernames?, + verificationStatus: VerificationStatus? + ) { + self.accentColorId = accentColorId + self.activeStoryState = activeStoryState + self.addedToAttachmentMenu = addedToAttachmentMenu + self.backgroundCustomEmojiId = backgroundCustomEmojiId + self.emojiStatus = emojiStatus + self.firstName = firstName + self.haveAccess = haveAccess + self.id = id + self.isCloseFriend = isCloseFriend + self.isContact = isContact + self.isMutualContact = isMutualContact + self.isPremium = isPremium + self.isSupport = isSupport + self.languageCode = languageCode + self.lastName = lastName + self.paidMessageStarCount = paidMessageStarCount + self.phoneNumber = phoneNumber + self.profileAccentColorId = profileAccentColorId + self.profileBackgroundCustomEmojiId = profileBackgroundCustomEmojiId + self.profilePhoto = profilePhoto + self.restrictionInfo = restrictionInfo + self.restrictsNewChats = restrictsNewChats + self.status = status + self.type = type + self.upgradedGiftColors = upgradedGiftColors + self.usernames = usernames + self.verificationStatus = verificationStatus + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserStatus.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserStatus.swift new file mode 100644 index 0000000000..8dd0777c1e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserStatus.swift @@ -0,0 +1,159 @@ +// +// UserStatus.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes the last time the user was online +public indirect enum UserStatus: Codable, Equatable, Hashable { + + /// The user's status has never been changed + case userStatusEmpty + + /// The user is online + case userStatusOnline(UserStatusOnline) + + /// The user is offline + case userStatusOffline(UserStatusOffline) + + /// The user was online recently + case userStatusRecently(UserStatusRecently) + + /// The user is offline, but was online last week + case userStatusLastWeek(UserStatusLastWeek) + + /// The user is offline, but was online last month + case userStatusLastMonth(UserStatusLastMonth) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case userStatusEmpty + case userStatusOnline + case userStatusOffline + case userStatusRecently + case userStatusLastWeek + case userStatusLastMonth + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .userStatusEmpty: + self = .userStatusEmpty + case .userStatusOnline: + let value = try UserStatusOnline(from: decoder) + self = .userStatusOnline(value) + case .userStatusOffline: + let value = try UserStatusOffline(from: decoder) + self = .userStatusOffline(value) + case .userStatusRecently: + let value = try UserStatusRecently(from: decoder) + self = .userStatusRecently(value) + case .userStatusLastWeek: + let value = try UserStatusLastWeek(from: decoder) + self = .userStatusLastWeek(value) + case .userStatusLastMonth: + let value = try UserStatusLastMonth(from: decoder) + self = .userStatusLastMonth(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .userStatusEmpty: + try container.encode(Kind.userStatusEmpty, forKey: .type) + case .userStatusOnline(let value): + try container.encode(Kind.userStatusOnline, forKey: .type) + try value.encode(to: encoder) + case .userStatusOffline(let value): + try container.encode(Kind.userStatusOffline, forKey: .type) + try value.encode(to: encoder) + case .userStatusRecently(let value): + try container.encode(Kind.userStatusRecently, forKey: .type) + try value.encode(to: encoder) + case .userStatusLastWeek(let value): + try container.encode(Kind.userStatusLastWeek, forKey: .type) + try value.encode(to: encoder) + case .userStatusLastMonth(let value): + try container.encode(Kind.userStatusLastMonth, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// The user is online +public struct UserStatusOnline: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the user's online status will expire + public let expires: Int + + + public init(expires: Int) { + self.expires = expires + } +} + +/// The user is offline +public struct UserStatusOffline: Codable, Equatable, Hashable { + + /// Point in time (Unix timestamp) when the user was last online + public let wasOnline: Int + + + public init(wasOnline: Int) { + self.wasOnline = wasOnline + } +} + +/// The user was online recently +public struct UserStatusRecently: Codable, Equatable, Hashable { + + /// Exact user's status is hidden because the current user enabled userPrivacySettingShowStatus privacy setting for the user and has no Telegram Premium + public let byMyPrivacySettings: Bool + + + public init(byMyPrivacySettings: Bool) { + self.byMyPrivacySettings = byMyPrivacySettings + } +} + +/// The user is offline, but was online last week +public struct UserStatusLastWeek: Codable, Equatable, Hashable { + + /// Exact user's status is hidden because the current user enabled userPrivacySettingShowStatus privacy setting for the user and has no Telegram Premium + public let byMyPrivacySettings: Bool + + + public init(byMyPrivacySettings: Bool) { + self.byMyPrivacySettings = byMyPrivacySettings + } +} + +/// The user is offline, but was online last month +public struct UserStatusLastMonth: Codable, Equatable, Hashable { + + /// Exact user's status is hidden because the current user enabled userPrivacySettingShowStatus privacy setting for the user and has no Telegram Premium + public let byMyPrivacySettings: Bool + + + public init(byMyPrivacySettings: Bool) { + self.byMyPrivacySettings = byMyPrivacySettings + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserType.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserType.swift new file mode 100644 index 0000000000..12fa9f01cb --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/UserType.swift @@ -0,0 +1,154 @@ +// +// UserType.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents the type of user. The following types are possible: regular users, deleted users and bots +public indirect enum UserType: Codable, Equatable, Hashable { + + /// A regular user + case userTypeRegular + + /// A deleted user or deleted bot. No information on the user besides the user identifier is available. It is not possible to perform any active actions on this type of user + case userTypeDeleted + + /// A bot (see https://core.telegram.org/bots) + case userTypeBot(UserTypeBot) + + /// No information on the user besides the user identifier is available, yet this user has not been deleted. This object is extremely rare and must be handled like a deleted user. It is not possible to perform any actions on users of this type + case userTypeUnknown + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case userTypeRegular + case userTypeDeleted + case userTypeBot + case userTypeUnknown + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .userTypeRegular: + self = .userTypeRegular + case .userTypeDeleted: + self = .userTypeDeleted + case .userTypeBot: + let value = try UserTypeBot(from: decoder) + self = .userTypeBot(value) + case .userTypeUnknown: + self = .userTypeUnknown + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .userTypeRegular: + try container.encode(Kind.userTypeRegular, forKey: .type) + case .userTypeDeleted: + try container.encode(Kind.userTypeDeleted, forKey: .type) + case .userTypeBot(let value): + try container.encode(Kind.userTypeBot, forKey: .type) + try value.encode(to: encoder) + case .userTypeUnknown: + try container.encode(Kind.userTypeUnknown, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A bot (see https://core.telegram.org/bots) +public struct UserTypeBot: Codable, Equatable, Hashable { + + /// The number of recently active users of the bot + public let activeUserCount: Int + + /// True, if users can create and delete topics in the chat with the bot + public let allowsUsersToCreateTopics: Bool + + /// True, if the bot can be added to attachment or side menu + public let canBeAddedToAttachmentMenu: Bool + + /// True, if the bot is owned by the current user and can be edited using the methods toggleBotUsernameIsActive, reorderBotActiveUsernames, setBotProfilePhoto, setBotName, setBotInfoDescription, and setBotInfoShortDescription + public let canBeEdited: Bool + + /// True, if the bot supports connection to user accounts for chat automation + public let canConnectToBusiness: Bool + + /// True, if the bot can be invited to basic group and supergroup chats + public let canJoinGroups: Bool + + /// True, if the bot can manage other bots + public let canManageBots: Bool + + /// True, if the bot can read all messages in basic group or supergroup chats and not just those addressed to the bot. In private and channel chats a bot can always read all messages + public let canReadAllGroupMessages: Bool + + /// True, if the bot has the main Web App + public let hasMainWebApp: Bool + + /// True, if the bot has topics + public let hasTopics: Bool + + /// Placeholder for inline queries (displayed on the application input field) + public let inlineQueryPlaceholder: String + + /// True, if the bot supports inline queries + public let isInline: Bool + + /// True, if the location of the user is expected to be sent with every inline query to this bot + public let needLocation: Bool + + /// True, if the bot can be queried by username from any non-secret chat + public let supportsGuestQueries: Bool + + + public init( + activeUserCount: Int, + allowsUsersToCreateTopics: Bool, + canBeAddedToAttachmentMenu: Bool, + canBeEdited: Bool, + canConnectToBusiness: Bool, + canJoinGroups: Bool, + canManageBots: Bool, + canReadAllGroupMessages: Bool, + hasMainWebApp: Bool, + hasTopics: Bool, + inlineQueryPlaceholder: String, + isInline: Bool, + needLocation: Bool, + supportsGuestQueries: Bool + ) { + self.activeUserCount = activeUserCount + self.allowsUsersToCreateTopics = allowsUsersToCreateTopics + self.canBeAddedToAttachmentMenu = canBeAddedToAttachmentMenu + self.canBeEdited = canBeEdited + self.canConnectToBusiness = canConnectToBusiness + self.canJoinGroups = canJoinGroups + self.canManageBots = canManageBots + self.canReadAllGroupMessages = canReadAllGroupMessages + self.hasMainWebApp = hasMainWebApp + self.hasTopics = hasTopics + self.inlineQueryPlaceholder = inlineQueryPlaceholder + self.isInline = isInline + self.needLocation = needLocation + self.supportsGuestQueries = supportsGuestQueries + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Usernames.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Usernames.swift new file mode 100644 index 0000000000..c11b3d1077 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Usernames.swift @@ -0,0 +1,41 @@ +// +// Usernames.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes usernames assigned to a user, a supergroup, or a channel +public struct Usernames: Codable, Equatable, Hashable { + + /// List of active usernames; the first one must be shown as the primary username. The order of active usernames can be changed with reorderActiveUsernames, reorderBotActiveUsernames or reorderSupergroupActiveUsernames + public let activeUsernames: [String] + + /// Collectible usernames that were purchased at https://fragment.com and can be passed to getCollectibleItemInfo for more details + public let collectibleUsernames: [String] + + /// List of currently disabled usernames; the username can be activated with toggleUsernameIsActive, toggleBotUsernameIsActive, or toggleSupergroupUsernameIsActive + public let disabledUsernames: [String] + + /// Active or disabled username, which may be changed with setUsername or setSupergroupUsername + public let editableUsername: String + + + public init( + activeUsernames: [String], + collectibleUsernames: [String], + disabledUsernames: [String], + editableUsername: String + ) { + self.activeUsernames = activeUsernames + self.collectibleUsernames = collectibleUsernames + self.disabledUsernames = disabledUsernames + self.editableUsername = editableUsername + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VectorPathCommand.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VectorPathCommand.swift new file mode 100644 index 0000000000..065f15532e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VectorPathCommand.swift @@ -0,0 +1,97 @@ +// +// VectorPathCommand.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Represents a vector path command +public indirect enum VectorPathCommand: Codable, Equatable, Hashable { + + /// A straight line to a given point + case vectorPathCommandLine(VectorPathCommandLine) + + /// A cubic Bézier curve to a given point + case vectorPathCommandCubicBezierCurve(VectorPathCommandCubicBezierCurve) + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case vectorPathCommandLine + case vectorPathCommandCubicBezierCurve + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .vectorPathCommandLine: + let value = try VectorPathCommandLine(from: decoder) + self = .vectorPathCommandLine(value) + case .vectorPathCommandCubicBezierCurve: + let value = try VectorPathCommandCubicBezierCurve(from: decoder) + self = .vectorPathCommandCubicBezierCurve(value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .vectorPathCommandLine(let value): + try container.encode(Kind.vectorPathCommandLine, forKey: .type) + try value.encode(to: encoder) + case .vectorPathCommandCubicBezierCurve(let value): + try container.encode(Kind.vectorPathCommandCubicBezierCurve, forKey: .type) + try value.encode(to: encoder) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + +/// A straight line to a given point +public struct VectorPathCommandLine: Codable, Equatable, Hashable { + + /// The end point of the straight line + public let endPoint: Point + + + public init(endPoint: Point) { + self.endPoint = endPoint + } +} + +/// A cubic Bézier curve to a given point +public struct VectorPathCommandCubicBezierCurve: Codable, Equatable, Hashable { + + /// The end control point of the curve + public let endControlPoint: Point + + /// The end point of the curve + public let endPoint: Point + + /// The start control point of the curve + public let startControlPoint: Point + + + public init( + endControlPoint: Point, + endPoint: Point, + startControlPoint: Point + ) { + self.endControlPoint = endControlPoint + self.endPoint = endPoint + self.startControlPoint = startControlPoint + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Venue.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Venue.swift new file mode 100644 index 0000000000..41e09ea109 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Venue.swift @@ -0,0 +1,51 @@ +// +// Venue.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a venue +public struct Venue: Codable, Equatable, Hashable, Identifiable { + + /// Venue address; as defined by the sender + public let address: String + + /// Identifier of the venue in the provider database; as defined by the sender + public let id: String + + /// Venue location; as defined by the sender + public let location: Location + + /// Provider of the venue database; as defined by the sender. Currently, only "foursquare" and "gplaces" (Google Places) need to be supported + public let provider: String + + /// Venue name; as defined by the sender + public let title: String + + /// Type of the venue in the provider database; as defined by the sender + public let type: String + + + public init( + address: String, + id: String, + location: Location, + provider: String, + title: String, + type: String + ) { + self.address = address + self.id = id + self.location = location + self.provider = provider + self.title = title + self.type = type + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VerificationStatus.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VerificationStatus.swift new file mode 100644 index 0000000000..758f2fee0f --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VerificationStatus.swift @@ -0,0 +1,41 @@ +// +// VerificationStatus.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Contains information about verification status of a chat or a user +public struct VerificationStatus: Codable, Equatable, Hashable { + + /// Identifier of the custom emoji to be shown as verification sign provided by a bot for the user; 0 if none + public let botVerificationIconCustomEmojiId: TdInt64 + + /// True, if the chat or the user is marked as fake by Telegram + public let isFake: Bool + + /// True, if the chat or the user is marked as scam by Telegram + public let isScam: Bool + + /// True, if the chat or the user is verified by Telegram + public let isVerified: Bool + + + public init( + botVerificationIconCustomEmojiId: TdInt64, + isFake: Bool, + isScam: Bool, + isVerified: Bool + ) { + self.botVerificationIconCustomEmojiId = botVerificationIconCustomEmojiId + self.isFake = isFake + self.isScam = isScam + self.isVerified = isVerified + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Video.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Video.swift new file mode 100644 index 0000000000..0228cb140a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/Video.swift @@ -0,0 +1,71 @@ +// +// Video.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a video file +public struct Video: Codable, Equatable, Hashable { + + /// Duration of the video, in seconds; as defined by the sender + public let duration: Int + + /// Original name of the file; as defined by the sender + public let fileName: String + + /// True, if stickers were added to the video. The list of corresponding sticker sets can be received using getAttachedStickerSets + public let hasStickers: Bool + + /// Video height; as defined by the sender + public let height: Int + + /// MIME type of the file; as defined by the sender + public let mimeType: String + + /// Video minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// True, if the video is expected to be streamed + public let supportsStreaming: Bool + + /// Video thumbnail in JPEG or MPEG4 format; as defined by the sender; may be null + public let thumbnail: Thumbnail? + + /// File containing the video + public let video: File + + /// Video width; as defined by the sender + public let width: Int + + + public init( + duration: Int, + fileName: String, + hasStickers: Bool, + height: Int, + mimeType: String, + minithumbnail: Minithumbnail?, + supportsStreaming: Bool, + thumbnail: Thumbnail?, + video: File, + width: Int + ) { + self.duration = duration + self.fileName = fileName + self.hasStickers = hasStickers + self.height = height + self.mimeType = mimeType + self.minithumbnail = minithumbnail + self.supportsStreaming = supportsStreaming + self.thumbnail = thumbnail + self.video = video + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoChat.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoChat.swift new file mode 100644 index 0000000000..6489d331d0 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoChat.swift @@ -0,0 +1,36 @@ +// +// VideoChat.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a video chat, i.e. a group call bound to a chat +public struct VideoChat: Codable, Equatable, Hashable { + + /// Default group call participant identifier to join the video chat; may be null + public let defaultParticipantId: MessageSender? + + /// Group call identifier of an active video chat; 0 if none. Full information about the video chat can be received through the method getGroupCall + public let groupCallId: Int + + /// True, if the video chat has participants + public let hasParticipants: Bool + + + public init( + defaultParticipantId: MessageSender?, + groupCallId: Int, + hasParticipants: Bool + ) { + self.defaultParticipantId = defaultParticipantId + self.groupCallId = groupCallId + self.hasParticipants = hasParticipants + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoNote.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoNote.swift new file mode 100644 index 0000000000..f5c352dcc2 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoNote.swift @@ -0,0 +1,56 @@ +// +// VideoNote.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a video note. The video must be equal in width and height, cropped to a circle, and stored in MPEG4 format +public struct VideoNote: Codable, Equatable, Hashable { + + /// Duration of the video, in seconds; as defined by the sender + public let duration: Int + + /// Video width and height; as defined by the sender + public let length: Int + + /// Video minithumbnail; may be null + public let minithumbnail: Minithumbnail? + + /// Result of speech recognition in the video note; may be null + public let speechRecognitionResult: SpeechRecognitionResult? + + /// Video thumbnail in JPEG format; as defined by the sender; may be null + public let thumbnail: Thumbnail? + + /// File containing the video + public let video: File + + /// A waveform representation of the video note's audio in 5-bit format; may be empty if unknown + public let waveform: Data + + + public init( + duration: Int, + length: Int, + minithumbnail: Minithumbnail?, + speechRecognitionResult: SpeechRecognitionResult?, + thumbnail: Thumbnail?, + video: File, + waveform: Data + ) { + self.duration = duration + self.length = length + self.minithumbnail = minithumbnail + self.speechRecognitionResult = speechRecognitionResult + self.thumbnail = thumbnail + self.video = video + self.waveform = waveform + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoStoryboard.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoStoryboard.swift new file mode 100644 index 0000000000..179fbf1fc5 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VideoStoryboard.swift @@ -0,0 +1,41 @@ +// +// VideoStoryboard.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a storyboard for a video +public struct VideoStoryboard: Codable, Equatable, Hashable { + + /// Height of a tile + public let height: Int + + /// File that describes mapping of position in the video to a tile in the JPEG file + public let mapFile: File + + /// A JPEG file that contains tiled previews of video + public let storyboardFile: File + + /// Width of a tile + public let width: Int + + + public init( + height: Int, + mapFile: File, + storyboardFile: File, + width: Int + ) { + self.height = height + self.mapFile = mapFile + self.storyboardFile = storyboardFile + self.width = width + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ViewMessages.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ViewMessages.swift new file mode 100644 index 0000000000..3e85ffcd6b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/ViewMessages.swift @@ -0,0 +1,41 @@ +// +// ViewMessages.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Informs TDLib that messages are being viewed by the user. Sponsored messages must be marked as viewed only when the entire text of the message is shown on the screen (excluding the button). Many useful activities depend on whether the messages are currently being viewed or not (e.g., marking messages as read, incrementing a view counter, updating a view counter, removing deleted messages in supergroups and channels) +public struct ViewMessages: Codable, Equatable, Hashable { + + /// Chat identifier + public let chatId: Int64? + + /// Pass true to mark as read the specified messages even if the chat is closed + public let forceRead: Bool? + + /// The identifiers of the messages being viewed + public let messageIds: [Int64]? + + /// Source of the message view; pass null to guess the source based on chat open state + public let source: MessageSource? + + + public init( + chatId: Int64?, + forceRead: Bool?, + messageIds: [Int64]?, + source: MessageSource? + ) { + self.chatId = chatId + self.forceRead = forceRead + self.messageIds = messageIds + self.source = source + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VoiceNote.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VoiceNote.swift new file mode 100644 index 0000000000..3d1f2aecff --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/VoiceNote.swift @@ -0,0 +1,46 @@ +// +// VoiceNote.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a voice note +public struct VoiceNote: Codable, Equatable, Hashable { + + /// Duration of the voice note, in seconds; as defined by the sender + public let duration: Int + + /// MIME type of the file; as defined by the sender. Usually, one of "audio/ogg" for Opus in an OGG container, "audio/mpeg" for an MP3 audio, or "audio/mp4" for an M4A audio + public let mimeType: String + + /// Result of speech recognition in the voice note; may be null + public let speechRecognitionResult: SpeechRecognitionResult? + + /// File containing the voice note + public let voice: File + + /// A waveform representation of the voice note in 5-bit format + public let waveform: Data + + + public init( + duration: Int, + mimeType: String, + speechRecognitionResult: SpeechRecognitionResult?, + voice: File, + waveform: Data + ) { + self.duration = duration + self.mimeType = mimeType + self.speechRecognitionResult = speechRecognitionResult + self.voice = voice + self.waveform = waveform + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebApp.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebApp.swift new file mode 100644 index 0000000000..7f26d01109 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebApp.swift @@ -0,0 +1,45 @@ +// +// WebApp.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes a Web App. Use getInternalLink with internalLinkTypeWebApp to share the Web App +public struct WebApp: Codable, Equatable, Hashable { + + /// Web App animation; may be null + public let animation: Animation? + + public let description: String + + /// Web App photo + public let photo: Photo + + /// Web App short name + public let shortName: String + + /// Web App title + public let title: String + + + public init( + animation: Animation?, + description: String, + photo: Photo, + shortName: String, + title: String + ) { + self.animation = animation + self.description = description + self.photo = photo + self.shortName = shortName + self.title = title + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebAppOpenMode.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebAppOpenMode.swift new file mode 100644 index 0000000000..1cfbaf0906 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Models/WebAppOpenMode.swift @@ -0,0 +1,65 @@ +// +// WebAppOpenMode.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +/// Describes mode in which a Web App is opened +public indirect enum WebAppOpenMode: Codable, Equatable, Hashable { + + /// The Web App is opened in the compact mode + case webAppOpenModeCompact + + /// The Web App is opened in the full-size mode + case webAppOpenModeFullSize + + /// The Web App is opened in the full-screen mode + case webAppOpenModeFullScreen + + /// Decoded when the @type is not one of the known cases (forward-compatible). + case unsupported + + private enum Kind: String, Codable { + case webAppOpenModeCompact + case webAppOpenModeFullSize + case webAppOpenModeFullScreen + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + let typeString = try container.decode(String.self, forKey: .type) + guard let type = Kind(rawValue: typeString) else { + self = .unsupported + return + } + switch type { + case .webAppOpenModeCompact: + self = .webAppOpenModeCompact + case .webAppOpenModeFullSize: + self = .webAppOpenModeFullSize + case .webAppOpenModeFullScreen: + self = .webAppOpenModeFullScreen + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + switch self { + case .webAppOpenModeCompact: + try container.encode(Kind.webAppOpenModeCompact, forKey: .type) + case .webAppOpenModeFullSize: + try container.encode(Kind.webAppOpenModeFullSize, forKey: .type) + case .webAppOpenModeFullScreen: + try container.encode(Kind.webAppOpenModeFullScreen, forKey: .type) + case .unsupported: + try container.encode("unsupported", forKey: .type) + } + } +} + diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/README.md b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/README.md new file mode 100644 index 0000000000..f23c65b62b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/README.md @@ -0,0 +1,12 @@ +# Generated — do not edit by hand + +Produced by `tools/td-codegen` (forked tl2swift) from `tools/td-codegen/td_api.tl` +(TDLib 1.8.64-49b3bcbb) tree-shaken to `tools/td-codegen/seed.txt`. + +Regenerate after changing the seed or bumping TDLib: + + cd tools/td-codegen + swift run tl2swift td_api.tl ../../Packages/TDShim/Sources/TDShim/Generated 1.8.64-49b3bcbb 49b3bcbb seed.txt + +When the app calls a NEW TDLib function or handles a NEW update, add it to `seed.txt` +([functions] / [updates]) and regenerate. diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DTO.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DTO.swift new file mode 100644 index 0000000000..539750da64 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DTO.swift @@ -0,0 +1,68 @@ +// +// DTO.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +public final class DTO: Codable { + let type: String + var extra: String? + let payload: T + let encoder: JSONEncoder? + + public init(_ payload: T, encoder: JSONEncoder? = nil) { + self.payload = payload + self.encoder = encoder + let swiftType = String(describing: T.self) + self.type = tdLibTypeFromSwiftType(swiftType) // TODO: replace with protocol + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DtoCodingKeys.self) + type = try container.decode(String.self, forKey: .type) + extra = try container.decodeIfPresent(String.self, forKey: .extra) + payload = try T(from: decoder) + encoder = nil + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DtoCodingKeys.self) + try container.encode(type, forKey: .type) + try container.encodeIfPresent(extra, forKey: .extra) + try payload.encode(to: encoder) + } +} + +extension DTO: TdQuery { + + public func make(with extra: String? = nil) throws -> Data { + self.extra = extra + let encoder = self.encoder ?? JSONEncoder() + return try encoder.encode(self) + } +} + +// MARK: - Type Mapping Helpers +private func tdLibTypeFromSwiftType(_ swiftType: String) -> String { + if let mapped = mapAmbiguousType(swiftType) { + return mapped + } + return swiftType.prefix(1).lowercased() + swiftType.dropFirst() +} + +private func mapAmbiguousType(_ tlType: String) -> String? { + let mapping = [ + "TdData": "data", + ] + return mapping[tlType] +} + +private func resolveAmbiguousType(_ tlType: String) -> String { + return mapAmbiguousType(tlType) ?? tlType +} \ No newline at end of file diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DtoCodingKeys.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DtoCodingKeys.swift new file mode 100644 index 0000000000..8501140e05 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/DtoCodingKeys.swift @@ -0,0 +1,17 @@ +// +// DtoCodingKeys.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +public enum DtoCodingKeys: String, CodingKey { + case type = "@type" + case extra = "@extra" + case clientId = "@client_id" +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/JSONDecoder+Result.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/JSONDecoder+Result.swift new file mode 100644 index 0000000000..07595a897b --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/JSONDecoder+Result.swift @@ -0,0 +1,23 @@ +// +// JSONDecoder+Result.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +extension JSONDecoder { + + func tryDecode(_ type: T.Type, from data: Data) -> Result where T : Decodable { + do { + let result = try self.decode(type, from: data) + return .success(result) + } catch { + return .failure(error) + } + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/Logger.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/Logger.swift new file mode 100644 index 0000000000..81f2da0a91 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/Logger.swift @@ -0,0 +1,42 @@ +// +// Logger.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +public protocol TDLibLogger { + func log(_ message: String, type: LoggerMessageType?) +} + + +@available(*, deprecated, renamed: "TDLibLogger", message: "interferes with OSLog.Logger") +public protocol Logger { + func log(_ message: String, type: LoggerMessageType?) +} + + +public enum LoggerMessageType: CustomStringConvertible { + case receive + case send + case execute + case custom(String) + + public var description: String { + switch self { + case .receive: + return "Receive" + case .send: + return "Send" + case .execute: + return "Execute" + case .custom(let value): + return value + } + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/TdInt64.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/TdInt64.swift new file mode 100644 index 0000000000..e379dc4a6e --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Generated/Supporting/TdInt64.swift @@ -0,0 +1,68 @@ +// +// TdInt64.swift +// tl2swift +// +// Generated automatically. Any changes will be lost! +// Based on TDLib 1.8.64-49b3bcbb-49b3bcbb +// https://github.com/tdlib/td/tree/49b3bcbb +// + +import Foundation + + +public struct TdInt64: RawRepresentable, ExpressibleByIntegerLiteral, Hashable { + + public typealias RawValue = Int64 + + // MARK: - Properties + + public var rawValue: Int64 + + public static var max: TdInt64 { return TdInt64(Int64.max) } + + public static var min: TdInt64 { return TdInt64(Int64.min) } + + + // MARK: - Init + + public init(_ int64: Int64) { + self.rawValue = int64 + } + + public init?(rawValue: Int64) { + self.rawValue = rawValue + } + + public init(integerLiteral value: Int) { + self.rawValue = Int64(value) + } +} + + +extension TdInt64: Codable { + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let strValue = try container.decode(String.self) + guard let value = Int64(strValue) else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Can't convert String value \(strValue) to Int64.")) + } + self.rawValue = value + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode("\(rawValue)") + } + +} + + +extension TdInt64: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + return lhs.rawValue < rhs.rawValue + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/ConcurrentDictionary.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/ConcurrentDictionary.swift new file mode 100644 index 0000000000..22bcb8c66a --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/ConcurrentDictionary.swift @@ -0,0 +1,122 @@ +// +// https://github.com/peterprokop/SwiftConcurrentCollections +// ConcurrentDictionary.swift +// +// +// Created by Sergey Akentev on 30.7.2023. +// + +import Foundation + +/// Thread-safe dictionary wrapper +/// - Important: Note that this is a `class`, i.e. reference (not value) type +public final class ConcurrentDictionary { + + private var container: [Key: Value] = [:] + private let rwlock = RWLock() + + public var keys: [Key] { + let result: [Key] + rwlock.readLock() + result = Array(container.keys) + rwlock.unlock() + return result + } + + public var values: [Value] { + let result: [Value] + rwlock.readLock() + result = Array(container.values) + rwlock.unlock() + return result + } + + public init() {} + + /// Sets the value for key + /// + /// - Parameters: + /// - value: The value to set for key + /// - key: The key to set value for + public func set(value: Value, forKey key: Key) { + rwlock.writeLock() + _set(value: value, forKey: key) + rwlock.unlock() + } + + @discardableResult + public func remove(_ key: Key) -> Value? { + let result: Value? + rwlock.writeLock() + result = _remove(key) + rwlock.unlock() + return result + } + + @discardableResult + public func removeValue(forKey: Key) -> Value? { + return self.remove(forKey) + } + + public func contains(_ key: Key) -> Bool { + let result: Bool + rwlock.readLock() + result = container.index(forKey: key) != nil + rwlock.unlock() + return result + } + + public func value(forKey key: Key) -> Value? { + let result: Value? + rwlock.readLock() + result = container[key] + rwlock.unlock() + return result + } + + public func mutateValue(forKey key: Key, mutation: (Value) -> Value) { + rwlock.writeLock() + if let value = container[key] { + container[key] = mutation(value) + } + rwlock.unlock() + } + + public var isEmpty: Bool { + return self.keys.isEmpty + } + + // MARK: Subscript + public subscript(key: Key) -> Value? { + get { + return value(forKey: key) + } + set { + rwlock.writeLock() + defer { + rwlock.unlock() + } + guard let newValue = newValue else { + _remove(key) + return + } + _set(value: newValue, forKey: key) + } + } + + // MARK: Private + @inline(__always) + private func _set(value: Value, forKey key: Key) { + self.container[key] = value + } + + @inline(__always) + @discardableResult + private func _remove(_ key: Key) -> Value? { + guard let index = container.index(forKey: key) else { return nil } + + let tuple = container.remove(at: index) + return tuple.value + } + +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/RWLock.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/RWLock.swift new file mode 100644 index 0000000000..b2099e2543 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/RWLock.swift @@ -0,0 +1,36 @@ +// +// RWLock.swift +// SwiftConcurrentCollections +// +// Created by Pete Prokop on 09/02/2020. +// Copyright © 2020 Pete Prokop. All rights reserved. +// + +import Foundation + +final class RWLock { + private var lock: pthread_rwlock_t + + // MARK: Lifecycle + deinit { + pthread_rwlock_destroy(&lock) + } + + public init() { + lock = pthread_rwlock_t() + pthread_rwlock_init(&lock, nil) + } + + // MARK: Public + public func writeLock() { + pthread_rwlock_wrlock(&lock) + } + + public func readLock() { + pthread_rwlock_rdlock(&lock) + } + + public func unlock() { + pthread_rwlock_unlock(&lock) + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/TDLibClientManager.swift b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/TDLibClientManager.swift new file mode 100644 index 0000000000..a5d82a8ddf --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Sources/TDShim/Transport/TDLibClientManager.swift @@ -0,0 +1,191 @@ +// +// TdClientManager.swift +// +// +// Created by Sergey Akentev on 29.7.2023. +// + +import Foundation +import TDLibFramework + + +public class TDLibClient: TDLibApi, Equatable { + public let id: Int32 + public private(set) var awaitingCompletions = ConcurrentDictionary Void>() + public let updateHandler: (Data, TDLibClient) -> Void + /// 'updateHandlerQueue' is a client-specific queue for incoming updates and responses. Serial queue ensures an order of updates https://core.telegram.org/tdlib/getting-started#handling-updates + public let updateHandlerQueue: DispatchQueue + /// 'queryQueue' is a client-specific queue for outgoing requests. It's a concurrent queue, so some requests may be dispatched earlier even if they were called later. + private let queryQueue: DispatchQueue + private let logger: TDLibLogger? + + /// Since ``td_receive`` can be called only from a single thread, ``TDLibClient`` initializer is private to ensure each client will receive responses for their requests. + fileprivate init(updateHandler: @escaping (Data, TDLibClient) -> Void, logger: TDLibLogger? = nil) { + self.id = td_create_client_id() + self.updateHandler = updateHandler + self.updateHandlerQueue = DispatchQueue(label: "app.swiftgram.TDLibKit.client-\(self.id).update-handler") + self.queryQueue = DispatchQueue(label: "app.swiftgram.TDLibKit.client-\(self.id).query", attributes: .concurrent) + self.logger = logger + super.init() + } + + + func completionHandler(extra: String, result: Data) { + self.updateHandlerQueue.async { + if let completion = self.awaitingCompletions.removeValue(forKey: extra) { + completion(result) + } + } + } + + /// Sends request to the TDLib client. + override public func send(query: TdQuery, completion: ((Data) -> Void)? = nil) throws { + self.queryQueue.async { [weak self] in + guard let `self` = self else { return } + var extra: String? = nil + if let completion = completion { + extra = UUID().uuidString + self.awaitingCompletions[extra!] = completion + } + let data = try! query.make(with: extra) + if let str = String(data: data, encoding: .utf8) { + self.logger?.log("[\(self.id)] " + str, type: .send) + td_send(self.id, str) + } else { + let errorText: String = "ERROR! Unable to encode query data, conversion returns nil" + if let strongLogger = self.logger { + strongLogger.log("[\(self.id)] " + errorText, type: .send) + } else { + print(errorText) + } + } + } + } + + /// Synchronously executes TDLib request. + override public func execute(query: TdQuery) throws -> [String:Any]? { + do { + let data = try query.make(with: nil) + let str = String(data: data, encoding: .utf8)! + logger?.log(str, type: .execute) + if let res = td_execute(str) { + let resString = String(cString: res) + logger?.log(resString, type: .receive) + let resData = resString.data(using: .utf8) + let json = try JSONSerialization.jsonObject(with: resData!, options:[]) + let dictionary = json as! [String:Any] + return dictionary + } else { + throw TDError(code: 404, message: "Empty response from TDLib") + } + } catch { + throw error + } + } + + + public static func == (lhs: TDLibClient, rhs: TDLibClient) -> Bool { + return lhs.id == rhs.id + } + +} + + +open class TDLibClientManager { + /// 'receiveQueue' is a separate queue that calls ``td_receive`` in a loop + private let receiveQueue = DispatchQueue(label: "app.swiftgram.TDLibKit.receive") + /// 'queryQueue' is a separate queue that will decode update string and lookup for possible completions in existing ``self.clients``. 'queryQueue' exists to quickly switch back to 'receiveQueue' and call next ``td_receive`` + private let queryQueue = DispatchQueue(label: "app.swiftgram.TDLibKit.query") + public private(set) var clients = ConcurrentDictionary() + private let logger: TDLibLogger? + + + /// TDLibClientManager + /// + /// + /// - Parameter logger: The logger object for debug print all queries and responses + public init(logger: TDLibLogger? = nil) { + #warning("Breaking changes may be introduced to TDLibClientManager without major version bump.") + self.logger = logger + self.receiveQueue.async { [weak self] in + while (true) { + guard let self else { break } + guard + let res = td_receive(10), + case let dataString = String(cString: res), + let data = dataString.data(using: .utf8) + else { + continue + } + self.logger?.log(dataString, type: .receive) + self.queryResultAsync(data) + } + } + } + + + deinit { + closeClients() + } + + /// Sends `close` request to each known ``TDLibClient`` and waits for all ``authorizationStateClosed`` updates. This is a blocking method. + /// Will be called on manager's ``deinit``. However, consider to call this method manually on `willTerminateNotification` or similar. Otherwise, client data may be lost. + public func closeClients() { + for client in self.clients.values { + try? client.close(completion: { _ in }) + } + + while (!self.clients.isEmpty) {} + } + + /// createClient + /// Creates ``TDLibClient`` and registers it's `updateHandler` in manager, allowing a per-client routing from ``td_receive``. + /// Returned client has been already "activated" with `getOption("version")` call and will receive updates immediatelly. + /// + /// + /// - Parameter updateHandler: Handler closure that will get access to ``Data`` with update and corresponding ``TDLibClient``. Will run in a serial client-specific ``DispatchQueue`` without blocking any other clients or ``td_receive``. https://core.telegram.org/tdlib/getting-started#handling-updates + public func createClient(updateHandler: @escaping (Data, TDLibClient) -> Void) -> TDLibClient { + let newClient = TDLibClient(updateHandler: updateHandler, logger: self.logger) + self.clients[newClient.id] = newClient + try? newClient.send(query: DTO(GetOption(name: "version")), completion: { _ in }) + return newClient + } + + private func queryResultAsync(_ result: Data) { + self.queryQueue.async { [weak self] in + guard + let `self` = self, + let json = try? JSONSerialization.jsonObject(with: result, options:[]), + let dictionary = json as? [String:Any] + else { + return + } + + let clientId = dictionary["@client_id"] as? Int32 ?? 1 + if let extraStr = dictionary["@extra"] as? String { + if let client = self.clients[clientId] { + client.completionHandler(extra: extraStr, result: result) + } + } else { + if let client = self.clients[clientId] { + client.updateHandlerQueue.async { + client.updateHandler(result, client) + } + } + } + + if self.checkClosedUpdate(dictionary) { + self.clients.removeValue(forKey: clientId) + } + } + } + + private func checkClosedUpdate(_ dict: [String: Any]) -> Bool { + if let state = dict["authorization_state"] as? [String: Any], + (state["@type"] as? String) == "authorizationStateClosed" { + return true + } + return false + } + +} diff --git a/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/.gitkeep b/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/DecodeParityTests.swift b/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/DecodeParityTests.swift new file mode 100644 index 0000000000..c2c467a528 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/DecodeParityTests.swift @@ -0,0 +1,57 @@ +import XCTest +@testable import TDShim + +final class DecodeParityTests: XCTestCase { + + private func decoder() -> JSONDecoder { + let d = JSONDecoder() + d.keyDecodingStrategy = .convertFromSnakeCase + return d + } + + func test_message_textContent_decodes() throws { + let json = """ + { + "@type":"message", + "author_signature":"","auto_delete_in":0,"can_be_saved":false, + "chat_id":777,"contains_unread_mention":false,"contains_unread_poll_votes":false, + "content":{"@type":"messageText","text":{"@type":"formattedText","entities":[],"text":"hi"}}, + "date":1715126400,"edit_date":0,"effect_id":"0","fact_check":null,"forward_info":null, + "has_timestamped_media":false,"id":10,"import_info":null,"interaction_info":null, + "is_channel_post":false,"is_from_offline":false,"is_outgoing":false, + "is_paid_star_suggested_post":false,"is_paid_ton_suggested_post":false,"is_pinned":false, + "media_album_id":"0","paid_message_star_count":0, + "reply_markup":null,"reply_to":null,"restriction_info":null,"scheduling_state":null, + "self_destruct_in":0.0,"self_destruct_type":null,"self_destruct_state":null, + "sender_boost_count":0,"sender_business_bot_user_id":0, + "sender_id":{"@type":"messageSenderUser","user_id":200}, + "sender_tag":"","sending_state":null,"suggested_post_info":null,"summary_language_code":"", + "topic_id":null,"unread_reactions":[],"via_bot_user_id":0 + } + """ + let m = try decoder().decode(Message.self, from: Data(json.utf8)) + XCTAssertEqual(m.id, 10) + XCTAssertEqual(m.chatId, 777) + guard case .messageText(let t) = m.content else { return XCTFail("expected messageText") } + XCTAssertEqual(t.text.text, "hi") + } + + func test_tdInt64_decodesFromString() throws { + // TDLib serializes 64-bit ids as quoted strings; TdInt64 must parse them. + let v = try decoder().decode(TdInt64.self, from: Data("\"123456789012345\"".utf8)) + XCTAssertEqual(v.rawValue, 123456789012345) + } + + func test_unknownType_foldsToUnsupported() throws { + let json = #"{"@type":"messageThisDoesNotExist","whatever":1}"# + let c = try decoder().decode(MessageContent.self, from: Data(json.utf8)) + guard case .unsupported = c else { return XCTFail("unknown @type should fold to .unsupported") } + } + + func test_unseededUpdate_foldsToUnsupported() throws { + // A real TDLib update we did NOT seed must not crash; it folds to .unsupported. + let json = #"{"@type":"updateChatThemes","chat_themes":[]}"# + let u = try decoder().decode(Update.self, from: Data(json.utf8)) + guard case .unsupported = u else { return XCTFail("unseeded update should fold to .unsupported") } + } +} diff --git a/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/EncodeParityTests.swift b/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/EncodeParityTests.swift new file mode 100644 index 0000000000..8c28e19e43 --- /dev/null +++ b/Telegram/WatchApp/Packages/TDShim/Tests/TDShimTests/EncodeParityTests.swift @@ -0,0 +1,27 @@ +import XCTest +@testable import TDShim + +final class EncodeParityTests: XCTestCase { + + func test_dto_injectsTypeExtra_andSnakeCases() throws { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let dto = DTO(GetChatHistory(chatId: 42, fromMessageId: 0, limit: 30, offset: 0, onlyLocal: false), + encoder: encoder) + let data = try dto.make(with: "extra-123") + let obj = try JSONSerialization.jsonObject(with: data) as! [String: Any] + XCTAssertEqual(obj["@type"] as? String, "getChatHistory") + XCTAssertEqual(obj["@extra"] as? String, "extra-123") + XCTAssertEqual(obj["chat_id"] as? Int, 42) + XCTAssertNotNil(obj["from_message_id"]) + } + + func test_errorEnvelope_decodes() throws { + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let data = Data(#"{"@type":"error","code":404,"message":"Not Found"}"#.utf8) + let dto = try decoder.decode(DTO.self, from: data) + XCTAssertEqual(dto.payload.code, 404) + XCTAssertEqual(dto.payload.message, "Not Found") + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Package.swift b/Telegram/WatchApp/Packages/WebPKit/Package.swift new file mode 100644 index 0000000000..24ca3b5fbb --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Package.swift @@ -0,0 +1,111 @@ +// swift-tools-version:5.9 +import PackageDescription + +let package = Package( + name: "WebPKit", + platforms: [ + .watchOS(.v10), + .macOS(.v14) + ], + products: [ + .library(name: "WebPKit", targets: ["WebPKit"]), + ], + targets: [ + .target( + name: "WebPCore", + path: "Sources/WebPCore", + exclude: [ + "libwebp/COPYING", + // NEON (ARM) SIMD variants — disabled via -U__ARM_NEON__ + "libwebp/src/dsp/cost_neon.c", + "libwebp/src/dsp/dec_neon.c", + "libwebp/src/dsp/enc_neon.c", + "libwebp/src/dsp/filters_neon.c", + "libwebp/src/dsp/lossless_enc_neon.c", + "libwebp/src/dsp/lossless_neon.c", + "libwebp/src/dsp/rescaler_neon.c", + "libwebp/src/dsp/upsampling_neon.c", + "libwebp/src/dsp/yuv_neon.c", + "libwebp/src/dsp/alpha_processing_neon.c", + // SSE2/SSE4.1 (x86) SIMD variants — disabled via -U__SSE2__ / -U__SSE4_1__ + "libwebp/src/dsp/cost_sse2.c", + "libwebp/src/dsp/dec_sse2.c", + "libwebp/src/dsp/dec_sse41.c", + "libwebp/src/dsp/enc_sse2.c", + "libwebp/src/dsp/enc_sse41.c", + "libwebp/src/dsp/filters_sse2.c", + "libwebp/src/dsp/lossless_enc_sse2.c", + "libwebp/src/dsp/lossless_enc_sse41.c", + "libwebp/src/dsp/lossless_sse2.c", + "libwebp/src/dsp/lossless_sse41.c", + "libwebp/src/dsp/rescaler_sse2.c", + "libwebp/src/dsp/upsampling_sse2.c", + "libwebp/src/dsp/upsampling_sse41.c", + "libwebp/src/dsp/yuv_sse2.c", + "libwebp/src/dsp/yuv_sse41.c", + "libwebp/src/dsp/alpha_processing_sse2.c", + "libwebp/src/dsp/alpha_processing_sse41.c", + "libwebp/src/dsp/ssim_sse2.c", + // MIPS variants + "libwebp/src/dsp/cost_mips32.c", + "libwebp/src/dsp/cost_mips_dsp_r2.c", + "libwebp/src/dsp/dec_mips32.c", + "libwebp/src/dsp/dec_mips_dsp_r2.c", + "libwebp/src/dsp/dec_msa.c", + "libwebp/src/dsp/enc_mips32.c", + "libwebp/src/dsp/enc_mips_dsp_r2.c", + "libwebp/src/dsp/enc_msa.c", + "libwebp/src/dsp/filters_mips_dsp_r2.c", + "libwebp/src/dsp/filters_msa.c", + "libwebp/src/dsp/lossless_enc_mips32.c", + "libwebp/src/dsp/lossless_enc_mips_dsp_r2.c", + "libwebp/src/dsp/lossless_enc_msa.c", + "libwebp/src/dsp/lossless_mips_dsp_r2.c", + "libwebp/src/dsp/lossless_msa.c", + "libwebp/src/dsp/rescaler_mips32.c", + "libwebp/src/dsp/rescaler_mips_dsp_r2.c", + "libwebp/src/dsp/rescaler_msa.c", + "libwebp/src/dsp/upsampling_mips_dsp_r2.c", + "libwebp/src/dsp/upsampling_msa.c", + "libwebp/src/dsp/yuv_mips32.c", + "libwebp/src/dsp/yuv_mips_dsp_r2.c", + "libwebp/src/dsp/alpha_processing_mips_dsp_r2.c", + // Encoder-side DSP (no src/enc/ was copied; these reference vp8i_enc.h or enc-only types) + "libwebp/src/dsp/enc.c", + "libwebp/src/dsp/lossless_enc.c", + "libwebp/src/dsp/ssim.c", + // Encoder-side utils + "libwebp/src/utils/bit_writer_utils.c", + "libwebp/src/utils/huffman_encode_utils.c", + "libwebp/src/utils/quant_levels_utils.c", + ], + publicHeadersPath: "include", + cSettings: [ + .headerSearchPath("libwebp"), + .headerSearchPath("libwebp/src"), + .define("WEBP_USE_THREAD", to: "0"), + .define("NDEBUG"), + // HAVE_CONFIG_H suppresses the automatic __aarch64__ → WEBP_USE_NEON + // detection in cpu.h (line 91-95). Without WEBP_HAVE_NEON also defined, + // the NEON dispatch stubs are compiled out — matching our exclusion of + // the NEON .c files from the target. + .define("HAVE_CONFIG_H"), + .unsafeFlags(["-U__ARM_NEON__", "-U__SSE2__", "-U__SSE4_1__"]), + ] + ), + .target( + name: "WebPKit", + dependencies: ["WebPCore"], + path: "Sources/WebPKit" + ), + .testTarget( + name: "WebPKitTests", + dependencies: ["WebPKit"], + path: "Tests/WebPKitTests", + resources: [ + .copy("Resources/sticker_raster.webp"), + ] + ), + ], + cLanguageStandard: .c11 +) diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/WebPDecode.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/WebPDecode.c new file mode 100644 index 0000000000..75aceb2dee --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/WebPDecode.c @@ -0,0 +1,32 @@ +#include "WebPCore/WebPDecode.h" +#include "webp/decode.h" +#include + +int webp_kit_decode_rgba( + const uint8_t *data, + size_t data_size, + uint8_t **out_data, + int *out_width, + int *out_height +) { + if (data == NULL || data_size == 0 || out_data == NULL || + out_width == NULL || out_height == NULL) { + return 0; + } + int width = 0; + int height = 0; + uint8_t *decoded = WebPDecodeRGBA(data, data_size, &width, &height); + if (decoded == NULL) { + return 0; + } + *out_data = decoded; + *out_width = width; + *out_height = height; + return 1; +} + +void webp_kit_free(uint8_t *buffer) { + if (buffer != NULL) { + WebPFree(buffer); + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/include/WebPCore/WebPDecode.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/include/WebPCore/WebPDecode.h new file mode 100644 index 0000000000..6718c6258c --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/include/WebPCore/WebPDecode.h @@ -0,0 +1,22 @@ +#ifndef WEBP_KIT_DECODE_H +#define WEBP_KIT_DECODE_H + +#include +#include + +/// Decodes a WebP buffer into RGBA8888 pixels. +/// Returns 1 on success, 0 on failure. On success, `*out_data` points at a +/// heap buffer the caller must free with `webp_kit_free` and `*out_width` / +/// `*out_height` carry the pixel dimensions. +int webp_kit_decode_rgba( + const uint8_t *data, + size_t data_size, + uint8_t **out_data, + int *out_width, + int *out_height +); + +/// Frees a buffer returned by `webp_kit_decode_rgba`. +void webp_kit_free(uint8_t *buffer); + +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/COPYING b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/COPYING new file mode 100644 index 0000000000..7a6f99547d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/COPYING @@ -0,0 +1,30 @@ +Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alpha_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alpha_dec.c new file mode 100644 index 0000000000..b6c874fb84 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alpha_dec.c @@ -0,0 +1,239 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha-plane decompression. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/quant_levels_dec_utils.h" +#include "src/utils/utils.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// ALPHDecoder object. + +// Allocates a new alpha decoder instance. +WEBP_NODISCARD static ALPHDecoder* ALPHNew(void) { + ALPHDecoder* const dec = (ALPHDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + return dec; +} + +// Clears and deallocates an alpha decoder instance. +static void ALPHDelete(ALPHDecoder* const dec) { + if (dec != NULL) { + VP8LDelete(dec->vp8l_dec_); + dec->vp8l_dec_ = NULL; + WebPSafeFree(dec); + } +} + +//------------------------------------------------------------------------------ +// Decoding. + +// Initialize alpha decoding by parsing the alpha header and decoding the image +// header for alpha data stored using lossless compression. +// Returns false in case of error in alpha header (data too short, invalid +// compression method or filter, error in lossless header data etc). +WEBP_NODISCARD static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data, + size_t data_size, const VP8Io* const src_io, + uint8_t* output) { + int ok = 0; + const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN; + const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN; + int rsrv; + VP8Io* const io = &dec->io_; + + assert(data != NULL && output != NULL && src_io != NULL); + + VP8FiltersInit(); + dec->output_ = output; + dec->width_ = src_io->width; + dec->height_ = src_io->height; + assert(dec->width_ > 0 && dec->height_ > 0); + + if (data_size <= ALPHA_HEADER_LEN) { + return 0; + } + + dec->method_ = (data[0] >> 0) & 0x03; + dec->filter_ = (WEBP_FILTER_TYPE)((data[0] >> 2) & 0x03); + dec->pre_processing_ = (data[0] >> 4) & 0x03; + rsrv = (data[0] >> 6) & 0x03; + if (dec->method_ < ALPHA_NO_COMPRESSION || + dec->method_ > ALPHA_LOSSLESS_COMPRESSION || + dec->filter_ >= WEBP_FILTER_LAST || + dec->pre_processing_ > ALPHA_PREPROCESSED_LEVELS || + rsrv != 0) { + return 0; + } + + // Copy the necessary parameters from src_io to io + if (!VP8InitIo(io)) { + return 0; + } + WebPInitCustomIo(NULL, io); + io->opaque = dec; + io->width = src_io->width; + io->height = src_io->height; + + io->use_cropping = src_io->use_cropping; + io->crop_left = src_io->crop_left; + io->crop_right = src_io->crop_right; + io->crop_top = src_io->crop_top; + io->crop_bottom = src_io->crop_bottom; + // No need to copy the scaling parameters. + + if (dec->method_ == ALPHA_NO_COMPRESSION) { + const size_t alpha_decoded_size = dec->width_ * dec->height_; + ok = (alpha_data_size >= alpha_decoded_size); + } else { + assert(dec->method_ == ALPHA_LOSSLESS_COMPRESSION); + ok = VP8LDecodeAlphaHeader(dec, alpha_data, alpha_data_size); + } + + return ok; +} + +// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha +// starting from row number 'row'. It assumes that rows up to (row - 1) have +// already been decoded. +// Returns false in case of bitstream error. +WEBP_NODISCARD static int ALPHDecode(VP8Decoder* const dec, int row, + int num_rows) { + ALPHDecoder* const alph_dec = dec->alph_dec_; + const int width = alph_dec->width_; + const int height = alph_dec->io_.crop_bottom; + if (alph_dec->method_ == ALPHA_NO_COMPRESSION) { + int y; + const uint8_t* prev_line = dec->alpha_prev_line_; + const uint8_t* deltas = dec->alpha_data_ + ALPHA_HEADER_LEN + row * width; + uint8_t* dst = dec->alpha_plane_ + row * width; + assert(deltas <= &dec->alpha_data_[dec->alpha_data_size_]); + assert(WebPUnfilters[alph_dec->filter_] != NULL); + for (y = 0; y < num_rows; ++y) { + WebPUnfilters[alph_dec->filter_](prev_line, deltas, dst, width); + prev_line = dst; + dst += width; + deltas += width; + } + dec->alpha_prev_line_ = prev_line; + } else { // alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION + assert(alph_dec->vp8l_dec_ != NULL); + if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) { + return 0; + } + } + + if (row + num_rows >= height) { + dec->is_alpha_decoded_ = 1; + } + return 1; +} + +WEBP_NODISCARD static int AllocateAlphaPlane(VP8Decoder* const dec, + const VP8Io* const io) { + const int stride = io->width; + const int height = io->crop_bottom; + const uint64_t alpha_size = (uint64_t)stride * height; + assert(dec->alpha_plane_mem_ == NULL); + dec->alpha_plane_mem_ = + (uint8_t*)WebPSafeMalloc(alpha_size, sizeof(*dec->alpha_plane_)); + if (dec->alpha_plane_mem_ == NULL) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "Alpha decoder initialization failed."); + } + dec->alpha_plane_ = dec->alpha_plane_mem_; + dec->alpha_prev_line_ = NULL; + return 1; +} + +void WebPDeallocateAlphaMemory(VP8Decoder* const dec) { + assert(dec != NULL); + WebPSafeFree(dec->alpha_plane_mem_); + dec->alpha_plane_mem_ = NULL; + dec->alpha_plane_ = NULL; + ALPHDelete(dec->alph_dec_); + dec->alph_dec_ = NULL; +} + +//------------------------------------------------------------------------------ +// Main entry point. + +WEBP_NODISCARD const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, + int row, int num_rows) { + const int width = io->width; + const int height = io->crop_bottom; + + assert(dec != NULL && io != NULL); + + if (row < 0 || num_rows <= 0 || row + num_rows > height) { + return NULL; + } + + if (!dec->is_alpha_decoded_) { + if (dec->alph_dec_ == NULL) { // Initialize decoder. + dec->alph_dec_ = ALPHNew(); + if (dec->alph_dec_ == NULL) { + VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "Alpha decoder initialization failed."); + return NULL; + } + if (!AllocateAlphaPlane(dec, io)) goto Error; + if (!ALPHInit(dec->alph_dec_, dec->alpha_data_, dec->alpha_data_size_, + io, dec->alpha_plane_)) { + VP8LDecoder* const vp8l_dec = dec->alph_dec_->vp8l_dec_; + VP8SetError(dec, + (vp8l_dec == NULL) ? VP8_STATUS_OUT_OF_MEMORY + : vp8l_dec->status_, + "Alpha decoder initialization failed."); + goto Error; + } + // if we allowed use of alpha dithering, check whether it's needed at all + if (dec->alph_dec_->pre_processing_ != ALPHA_PREPROCESSED_LEVELS) { + dec->alpha_dithering_ = 0; // disable dithering + } else { + num_rows = height - row; // decode everything in one pass + } + } + + assert(dec->alph_dec_ != NULL); + assert(row + num_rows <= height); + if (!ALPHDecode(dec, row, num_rows)) goto Error; + + if (dec->is_alpha_decoded_) { // finished? + ALPHDelete(dec->alph_dec_); + dec->alph_dec_ = NULL; + if (dec->alpha_dithering_ > 0) { + uint8_t* const alpha = dec->alpha_plane_ + io->crop_top * width + + io->crop_left; + if (!WebPDequantizeLevels(alpha, + io->crop_right - io->crop_left, + io->crop_bottom - io->crop_top, + width, dec->alpha_dithering_)) { + goto Error; + } + } + } + } + + // Return a pointer to the current decoded row. + return dec->alpha_plane_ + row * width; + + Error: + WebPDeallocateAlphaMemory(dec); + return NULL; +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alphai_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alphai_dec.h new file mode 100644 index 0000000000..a64104abeb --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/alphai_dec.h @@ -0,0 +1,54 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha decoder: internal header. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_DEC_ALPHAI_DEC_H_ +#define WEBP_DEC_ALPHAI_DEC_H_ + +#include "src/dec/webpi_dec.h" +#include "src/utils/filters_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct VP8LDecoder; // Defined in dec/vp8li.h. + +typedef struct ALPHDecoder ALPHDecoder; +struct ALPHDecoder { + int width_; + int height_; + int method_; + WEBP_FILTER_TYPE filter_; + int pre_processing_; + struct VP8LDecoder* vp8l_dec_; + VP8Io io_; + int use_8b_decode_; // Although alpha channel requires only 1 byte per + // pixel, sometimes VP8LDecoder may need to allocate + // 4 bytes per pixel internally during decode. + uint8_t* output_; + const uint8_t* prev_line_; // last output row (or NULL) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// Deallocate memory associated to dec->alpha_plane_ decoding +void WebPDeallocateAlphaMemory(VP8Decoder* const dec); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_ALPHAI_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/buffer_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/buffer_dec.c new file mode 100644 index 0000000000..11ce76f19e --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/buffer_dec.c @@ -0,0 +1,310 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Everything about WebPDecBuffer +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dec/vp8i_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// WebPDecBuffer + +// Number of bytes per pixel for the different color-spaces. +static const uint8_t kModeBpp[MODE_LAST] = { + 3, 4, 3, 4, 4, 2, 2, + 4, 4, 4, 2, // pre-multiplied modes + 1, 1 }; + +// Check that webp_csp_mode is within the bounds of WEBP_CSP_MODE. +// Convert to an integer to handle both the unsigned/signed enum cases +// without the need for casting to remove type limit warnings. +static int IsValidColorspace(int webp_csp_mode) { + return (webp_csp_mode >= MODE_RGB && webp_csp_mode < MODE_LAST); +} + +// strictly speaking, the very last (or first, if flipped) row +// doesn't require padding. +#define MIN_BUFFER_SIZE(WIDTH, HEIGHT, STRIDE) \ + ((uint64_t)(STRIDE) * ((HEIGHT) - 1) + (WIDTH)) + +static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) { + int ok = 1; + const WEBP_CSP_MODE mode = buffer->colorspace; + const int width = buffer->width; + const int height = buffer->height; + if (!IsValidColorspace(mode)) { + ok = 0; + } else if (!WebPIsRGBMode(mode)) { // YUV checks + const WebPYUVABuffer* const buf = &buffer->u.YUVA; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int y_stride = abs(buf->y_stride); + const int u_stride = abs(buf->u_stride); + const int v_stride = abs(buf->v_stride); + const int a_stride = abs(buf->a_stride); + const uint64_t y_size = MIN_BUFFER_SIZE(width, height, y_stride); + const uint64_t u_size = MIN_BUFFER_SIZE(uv_width, uv_height, u_stride); + const uint64_t v_size = MIN_BUFFER_SIZE(uv_width, uv_height, v_stride); + const uint64_t a_size = MIN_BUFFER_SIZE(width, height, a_stride); + ok &= (y_size <= buf->y_size); + ok &= (u_size <= buf->u_size); + ok &= (v_size <= buf->v_size); + ok &= (y_stride >= width); + ok &= (u_stride >= uv_width); + ok &= (v_stride >= uv_width); + ok &= (buf->y != NULL); + ok &= (buf->u != NULL); + ok &= (buf->v != NULL); + if (mode == MODE_YUVA) { + ok &= (a_stride >= width); + ok &= (a_size <= buf->a_size); + ok &= (buf->a != NULL); + } + } else { // RGB checks + const WebPRGBABuffer* const buf = &buffer->u.RGBA; + const int stride = abs(buf->stride); + const uint64_t size = + MIN_BUFFER_SIZE((uint64_t)width * kModeBpp[mode], height, stride); + ok &= (size <= buf->size); + ok &= (stride >= width * kModeBpp[mode]); + ok &= (buf->rgba != NULL); + } + return ok ? VP8_STATUS_OK : VP8_STATUS_INVALID_PARAM; +} +#undef MIN_BUFFER_SIZE + +static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) { + const int w = buffer->width; + const int h = buffer->height; + const WEBP_CSP_MODE mode = buffer->colorspace; + + if (w <= 0 || h <= 0 || !IsValidColorspace(mode)) { + return VP8_STATUS_INVALID_PARAM; + } + + if (buffer->is_external_memory <= 0 && buffer->private_memory == NULL) { + uint8_t* output; + int uv_stride = 0, a_stride = 0; + uint64_t uv_size = 0, a_size = 0, total_size; + // We need memory and it hasn't been allocated yet. + // => initialize output buffer, now that dimensions are known. + int stride; + uint64_t size; + + if ((uint64_t)w * kModeBpp[mode] >= (1ull << 31)) { + return VP8_STATUS_INVALID_PARAM; + } + stride = w * kModeBpp[mode]; + size = (uint64_t)stride * h; + if (!WebPIsRGBMode(mode)) { + uv_stride = (w + 1) / 2; + uv_size = (uint64_t)uv_stride * ((h + 1) / 2); + if (mode == MODE_YUVA) { + a_stride = w; + a_size = (uint64_t)a_stride * h; + } + } + total_size = size + 2 * uv_size + a_size; + + output = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*output)); + if (output == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + buffer->private_memory = output; + + if (!WebPIsRGBMode(mode)) { // YUVA initialization + WebPYUVABuffer* const buf = &buffer->u.YUVA; + buf->y = output; + buf->y_stride = stride; + buf->y_size = (size_t)size; + buf->u = output + size; + buf->u_stride = uv_stride; + buf->u_size = (size_t)uv_size; + buf->v = output + size + uv_size; + buf->v_stride = uv_stride; + buf->v_size = (size_t)uv_size; + if (mode == MODE_YUVA) { + buf->a = output + size + 2 * uv_size; + } + buf->a_size = (size_t)a_size; + buf->a_stride = a_stride; + } else { // RGBA initialization + WebPRGBABuffer* const buf = &buffer->u.RGBA; + buf->rgba = output; + buf->stride = stride; + buf->size = (size_t)size; + } + } + return CheckDecBuffer(buffer); +} + +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer) { + if (buffer == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(buffer->colorspace)) { + WebPRGBABuffer* const buf = &buffer->u.RGBA; + buf->rgba += (int64_t)(buffer->height - 1) * buf->stride; + buf->stride = -buf->stride; + } else { + WebPYUVABuffer* const buf = &buffer->u.YUVA; + const int64_t H = buffer->height; + buf->y += (H - 1) * buf->y_stride; + buf->y_stride = -buf->y_stride; + buf->u += ((H - 1) >> 1) * buf->u_stride; + buf->u_stride = -buf->u_stride; + buf->v += ((H - 1) >> 1) * buf->v_stride; + buf->v_stride = -buf->v_stride; + if (buf->a != NULL) { + buf->a += (H - 1) * buf->a_stride; + buf->a_stride = -buf->a_stride; + } + } + return VP8_STATUS_OK; +} + +VP8StatusCode WebPAllocateDecBuffer(int width, int height, + const WebPDecoderOptions* const options, + WebPDecBuffer* const buffer) { + VP8StatusCode status; + if (buffer == NULL || width <= 0 || height <= 0) { + return VP8_STATUS_INVALID_PARAM; + } + if (options != NULL) { // First, apply options if there is any. + if (options->use_cropping) { + const int cw = options->crop_width; + const int ch = options->crop_height; + const int x = options->crop_left & ~1; + const int y = options->crop_top & ~1; + if (!WebPCheckCropDimensions(width, height, x, y, cw, ch)) { + return VP8_STATUS_INVALID_PARAM; // out of frame boundary. + } + width = cw; + height = ch; + } + + if (options->use_scaling) { +#if !defined(WEBP_REDUCE_SIZE) + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions( + width, height, &scaled_width, &scaled_height)) { + return VP8_STATUS_INVALID_PARAM; + } + width = scaled_width; + height = scaled_height; +#else + return VP8_STATUS_INVALID_PARAM; // rescaling not supported +#endif + } + } + buffer->width = width; + buffer->height = height; + + // Then, allocate buffer for real. + status = AllocateBuffer(buffer); + if (status != VP8_STATUS_OK) return status; + + // Use the stride trick if vertical flip is needed. + if (options != NULL && options->flip) { + status = WebPFlipBuffer(buffer); + } + return status; +} + +//------------------------------------------------------------------------------ +// constructors / destructors + +int WebPInitDecBufferInternal(WebPDecBuffer* buffer, int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // version mismatch + } + if (buffer == NULL) return 0; + memset(buffer, 0, sizeof(*buffer)); + return 1; +} + +void WebPFreeDecBuffer(WebPDecBuffer* buffer) { + if (buffer != NULL) { + if (buffer->is_external_memory <= 0) { + WebPSafeFree(buffer->private_memory); + } + buffer->private_memory = NULL; + } +} + +void WebPCopyDecBuffer(const WebPDecBuffer* const src, + WebPDecBuffer* const dst) { + if (src != NULL && dst != NULL) { + *dst = *src; + if (src->private_memory != NULL) { + dst->is_external_memory = 1; // dst buffer doesn't own the memory. + dst->private_memory = NULL; + } + } +} + +// Copy and transfer ownership from src to dst (beware of parameter order!) +void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst) { + if (src != NULL && dst != NULL) { + *dst = *src; + if (src->private_memory != NULL) { + src->is_external_memory = 1; // src relinquishes ownership + src->private_memory = NULL; + } + } +} + +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src_buf, + WebPDecBuffer* const dst_buf) { + assert(src_buf != NULL && dst_buf != NULL); + assert(src_buf->colorspace == dst_buf->colorspace); + + dst_buf->width = src_buf->width; + dst_buf->height = src_buf->height; + if (CheckDecBuffer(dst_buf) != VP8_STATUS_OK) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(src_buf->colorspace)) { + const WebPRGBABuffer* const src = &src_buf->u.RGBA; + const WebPRGBABuffer* const dst = &dst_buf->u.RGBA; + WebPCopyPlane(src->rgba, src->stride, dst->rgba, dst->stride, + src_buf->width * kModeBpp[src_buf->colorspace], + src_buf->height); + } else { + const WebPYUVABuffer* const src = &src_buf->u.YUVA; + const WebPYUVABuffer* const dst = &dst_buf->u.YUVA; + WebPCopyPlane(src->y, src->y_stride, dst->y, dst->y_stride, + src_buf->width, src_buf->height); + WebPCopyPlane(src->u, src->u_stride, dst->u, dst->u_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + WebPCopyPlane(src->v, src->v_stride, dst->v, dst->v_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + if (WebPIsAlphaMode(src_buf->colorspace)) { + WebPCopyPlane(src->a, src->a_stride, dst->a, dst->a_stride, + src_buf->width, src_buf->height); + } + } + return VP8_STATUS_OK; +} + +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features) { + assert(output != NULL); + return (output->is_external_memory >= 2) && + WebPIsPremultipliedMode(output->colorspace) && + (features != NULL && features->has_alpha); +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/common_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/common_dec.h new file mode 100644 index 0000000000..b158550a80 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/common_dec.h @@ -0,0 +1,54 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Definitions and macros common to encoding and decoding +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_COMMON_DEC_H_ +#define WEBP_DEC_COMMON_DEC_H_ + +// intra prediction modes +enum { B_DC_PRED = 0, // 4x4 modes + B_TM_PRED = 1, + B_VE_PRED = 2, + B_HE_PRED = 3, + B_RD_PRED = 4, + B_VR_PRED = 5, + B_LD_PRED = 6, + B_VL_PRED = 7, + B_HD_PRED = 8, + B_HU_PRED = 9, + NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10 + + // Luma16 or UV modes + DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED, + H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED, + B_PRED = NUM_BMODES, // refined I4x4 mode + NUM_PRED_MODES = 4, + + // special modes + B_DC_PRED_NOTOP = 4, + B_DC_PRED_NOLEFT = 5, + B_DC_PRED_NOTOPLEFT = 6, + NUM_B_DC_MODES = 7 }; + +enum { MB_FEATURE_TREE_PROBS = 3, + NUM_MB_SEGMENTS = 4, + NUM_REF_LF_DELTAS = 4, + NUM_MODE_LF_DELTAS = 4, // I4x4, ZERO, *, SPLIT + MAX_NUM_PARTITIONS = 8, + // Probabilities + NUM_TYPES = 4, // 0: i16-AC, 1: i16-DC, 2:chroma-AC, 3:i4-AC + NUM_BANDS = 8, + NUM_CTX = 3, + NUM_PROBAS = 11 + }; + +#endif // WEBP_DEC_COMMON_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/frame_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/frame_dec.c new file mode 100644 index 0000000000..91ca1f8609 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/frame_dec.c @@ -0,0 +1,803 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Frame-reconstruction function. Memory allocation. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "src/dec/vp8i_dec.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// Main reconstruction function. + +static const uint16_t kScan[16] = { + 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, + 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, + 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, + 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS +}; + +static int CheckMode(int mb_x, int mb_y, int mode) { + if (mode == B_DC_PRED) { + if (mb_x == 0) { + return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT; + } else { + return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED; + } + } + return mode; +} + +static void Copy32b(uint8_t* const dst, const uint8_t* const src) { + memcpy(dst, src, 4); +} + +static WEBP_INLINE void DoTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + switch (bits >> 30) { + case 3: + VP8Transform(src, dst, 0); + break; + case 2: + VP8TransformAC3(src, dst); + break; + case 1: + VP8TransformDC(src, dst); + break; + default: + break; + } +} + +static void DoUVTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + if (bits & 0xff) { // any non-zero coeff at all? + if (bits & 0xaa) { // any non-zero AC coefficient? + VP8TransformUV(src, dst); // note we don't use the AC3 variant for U/V + } else { + VP8TransformDCUV(src, dst); + } + } +} + +static void ReconstructRow(const VP8Decoder* const dec, + const VP8ThreadContext* ctx) { + int j; + int mb_x; + const int mb_y = ctx->mb_y_; + const int cache_id = ctx->id_; + uint8_t* const y_dst = dec->yuv_b_ + Y_OFF; + uint8_t* const u_dst = dec->yuv_b_ + U_OFF; + uint8_t* const v_dst = dec->yuv_b_ + V_OFF; + + // Initialize left-most block. + for (j = 0; j < 16; ++j) { + y_dst[j * BPS - 1] = 129; + } + for (j = 0; j < 8; ++j) { + u_dst[j * BPS - 1] = 129; + v_dst[j * BPS - 1] = 129; + } + + // Init top-left sample on left column too. + if (mb_y > 0) { + y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129; + } else { + // we only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + memset(y_dst - BPS - 1, 127, 16 + 4 + 1); + memset(u_dst - BPS - 1, 127, 8 + 1); + memset(v_dst - BPS - 1, 127, 8 + 1); + } + + // Reconstruct one row. + for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) { + const VP8MBData* const block = ctx->mb_data_ + mb_x; + + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mb_x > 0) { + for (j = -1; j < 16; ++j) { + Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + } + for (j = -1; j < 8; ++j) { + Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); + Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + } + } + { + // bring top samples into the cache + VP8TopSamples* const top_yuv = dec->yuv_t_ + mb_x; + const int16_t* const coeffs = block->coeffs_; + uint32_t bits = block->non_zero_y_; + int n; + + if (mb_y > 0) { + memcpy(y_dst - BPS, top_yuv[0].y, 16); + memcpy(u_dst - BPS, top_yuv[0].u, 8); + memcpy(v_dst - BPS, top_yuv[0].v, 8); + } + + // predict and add residuals + if (block->is_i4x4_) { // 4x4 + uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); + + if (mb_y > 0) { + if (mb_x >= dec->mb_w_ - 1) { // on rightmost border + memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } else { + memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } + } + // replicate the top-right pixels below + top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; + + // predict and add residuals for all 4x4 blocks in turn. + for (n = 0; n < 16; ++n, bits <<= 2) { + uint8_t* const dst = y_dst + kScan[n]; + VP8PredLuma4[block->imodes_[n]](dst); + DoTransform(bits, coeffs + n * 16, dst); + } + } else { // 16x16 + const int pred_func = CheckMode(mb_x, mb_y, block->imodes_[0]); + VP8PredLuma16[pred_func](y_dst); + if (bits != 0) { + for (n = 0; n < 16; ++n, bits <<= 2) { + DoTransform(bits, coeffs + n * 16, y_dst + kScan[n]); + } + } + } + { + // Chroma + const uint32_t bits_uv = block->non_zero_uv_; + const int pred_func = CheckMode(mb_x, mb_y, block->uvmode_); + VP8PredChroma8[pred_func](u_dst); + VP8PredChroma8[pred_func](v_dst); + DoUVTransform(bits_uv >> 0, coeffs + 16 * 16, u_dst); + DoUVTransform(bits_uv >> 8, coeffs + 20 * 16, v_dst); + } + + // stash away top samples for next block + if (mb_y < dec->mb_h_ - 1) { + memcpy(top_yuv[0].y, y_dst + 15 * BPS, 16); + memcpy(top_yuv[0].u, u_dst + 7 * BPS, 8); + memcpy(top_yuv[0].v, v_dst + 7 * BPS, 8); + } + } + // Transfer reconstructed samples from yuv_b_ cache to final destination. + { + const int y_offset = cache_id * 16 * dec->cache_y_stride_; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride_; + uint8_t* const y_out = dec->cache_y_ + mb_x * 16 + y_offset; + uint8_t* const u_out = dec->cache_u_ + mb_x * 8 + uv_offset; + uint8_t* const v_out = dec->cache_v_ + mb_x * 8 + uv_offset; + for (j = 0; j < 16; ++j) { + memcpy(y_out + j * dec->cache_y_stride_, y_dst + j * BPS, 16); + } + for (j = 0; j < 8; ++j) { + memcpy(u_out + j * dec->cache_uv_stride_, u_dst + j * BPS, 8); + memcpy(v_out + j * dec->cache_uv_stride_, v_dst + j * BPS, 8); + } + } + } +} + +//------------------------------------------------------------------------------ +// Filtering + +// kFilterExtraRows[] = How many extra lines are needed on the MB boundary +// for caching, given a filtering level. +// Simple filter: up to 2 luma samples are read and 1 is written. +// Complex filter: up to 4 luma samples are read and 3 are written. Same for +// U/V, so it's 8 samples total (because of the 2x upsampling). +static const uint8_t kFilterExtraRows[3] = { 0, 2, 8 }; + +static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) { + const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const int cache_id = ctx->id_; + const int y_bps = dec->cache_y_stride_; + const VP8FInfo* const f_info = ctx->f_info_ + mb_x; + uint8_t* const y_dst = dec->cache_y_ + cache_id * 16 * y_bps + mb_x * 16; + const int ilevel = f_info->f_ilevel_; + const int limit = f_info->f_limit_; + if (limit == 0) { + return; + } + assert(limit >= 3); + if (dec->filter_type_ == 1) { // simple + if (mb_x > 0) { + VP8SimpleHFilter16(y_dst, y_bps, limit + 4); + } + if (f_info->f_inner_) { + VP8SimpleHFilter16i(y_dst, y_bps, limit); + } + if (mb_y > 0) { + VP8SimpleVFilter16(y_dst, y_bps, limit + 4); + } + if (f_info->f_inner_) { + VP8SimpleVFilter16i(y_dst, y_bps, limit); + } + } else { // complex + const int uv_bps = dec->cache_uv_stride_; + uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8; + const int hev_thresh = f_info->hev_thresh_; + if (mb_x > 0) { + VP8HFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh); + VP8HFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh); + } + if (f_info->f_inner_) { + VP8HFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh); + VP8HFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh); + } + if (mb_y > 0) { + VP8VFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh); + VP8VFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh); + } + if (f_info->f_inner_) { + VP8VFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh); + VP8VFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh); + } + } +} + +// Filter the decoded macroblock row (if needed) +static void FilterRow(const VP8Decoder* const dec) { + int mb_x; + const int mb_y = dec->thread_ctx_.mb_y_; + assert(dec->thread_ctx_.filter_row_); + for (mb_x = dec->tl_mb_x_; mb_x < dec->br_mb_x_; ++mb_x) { + DoFilter(dec, mb_x, mb_y); + } +} + +//------------------------------------------------------------------------------ +// Precompute the filtering strength for each segment and each i4x4/i16x16 mode. + +static void PrecomputeFilterStrengths(VP8Decoder* const dec) { + if (dec->filter_type_ > 0) { + int s; + const VP8FilterHeader* const hdr = &dec->filter_hdr_; + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + int i4x4; + // First, compute the initial level + int base_level; + if (dec->segment_hdr_.use_segment_) { + base_level = dec->segment_hdr_.filter_strength_[s]; + if (!dec->segment_hdr_.absolute_delta_) { + base_level += hdr->level_; + } + } else { + base_level = hdr->level_; + } + for (i4x4 = 0; i4x4 <= 1; ++i4x4) { + VP8FInfo* const info = &dec->fstrengths_[s][i4x4]; + int level = base_level; + if (hdr->use_lf_delta_) { + level += hdr->ref_lf_delta_[0]; + if (i4x4) { + level += hdr->mode_lf_delta_[0]; + } + } + level = (level < 0) ? 0 : (level > 63) ? 63 : level; + if (level > 0) { + int ilevel = level; + if (hdr->sharpness_ > 0) { + if (hdr->sharpness_ > 4) { + ilevel >>= 2; + } else { + ilevel >>= 1; + } + if (ilevel > 9 - hdr->sharpness_) { + ilevel = 9 - hdr->sharpness_; + } + } + if (ilevel < 1) ilevel = 1; + info->f_ilevel_ = ilevel; + info->f_limit_ = 2 * level + ilevel; + info->hev_thresh_ = (level >= 40) ? 2 : (level >= 15) ? 1 : 0; + } else { + info->f_limit_ = 0; // no filtering + } + info->f_inner_ = i4x4; + } + } + } +} + +//------------------------------------------------------------------------------ +// Dithering + +// minimal amp that will provide a non-zero dithering effect +#define MIN_DITHER_AMP 4 + +#define DITHER_AMP_TAB_SIZE 12 +static const uint8_t kQuantToDitherAmp[DITHER_AMP_TAB_SIZE] = { + // roughly, it's dqm->uv_mat_[1] + 8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1 +}; + +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec) { + assert(dec != NULL); + if (options != NULL) { + const int d = options->dithering_strength; + const int max_amp = (1 << VP8_RANDOM_DITHER_FIX) - 1; + const int f = (d < 0) ? 0 : (d > 100) ? max_amp : (d * max_amp / 100); + if (f > 0) { + int s; + int all_amp = 0; + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + VP8QuantMatrix* const dqm = &dec->dqm_[s]; + if (dqm->uv_quant_ < DITHER_AMP_TAB_SIZE) { + const int idx = (dqm->uv_quant_ < 0) ? 0 : dqm->uv_quant_; + dqm->dither_ = (f * kQuantToDitherAmp[idx]) >> 3; + } + all_amp |= dqm->dither_; + } + if (all_amp != 0) { + VP8InitRandom(&dec->dithering_rg_, 1.0f); + dec->dither_ = 1; + } + } + // potentially allow alpha dithering + dec->alpha_dithering_ = options->alpha_dithering_strength; + if (dec->alpha_dithering_ > 100) { + dec->alpha_dithering_ = 100; + } else if (dec->alpha_dithering_ < 0) { + dec->alpha_dithering_ = 0; + } + } +} + +// Convert to range: [-2,2] for dither=50, [-4,4] for dither=100 +static void Dither8x8(VP8Random* const rg, uint8_t* dst, int bps, int amp) { + uint8_t dither[64]; + int i; + for (i = 0; i < 8 * 8; ++i) { + dither[i] = VP8RandomBits2(rg, VP8_DITHER_AMP_BITS + 1, amp); + } + VP8DitherCombine8x8(dither, dst, bps); +} + +static void DitherRow(VP8Decoder* const dec) { + int mb_x; + assert(dec->dither_); + for (mb_x = dec->tl_mb_x_; mb_x < dec->br_mb_x_; ++mb_x) { + const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const VP8MBData* const data = ctx->mb_data_ + mb_x; + const int cache_id = ctx->id_; + const int uv_bps = dec->cache_uv_stride_; + if (data->dither_ >= MIN_DITHER_AMP) { + uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8; + Dither8x8(&dec->dithering_rg_, u_dst, uv_bps, data->dither_); + Dither8x8(&dec->dithering_rg_, v_dst, uv_bps, data->dither_); + } + } +} + +//------------------------------------------------------------------------------ +// This function is called after a row of macroblocks is finished decoding. +// It also takes into account the following restrictions: +// * In case of in-loop filtering, we must hold off sending some of the bottom +// pixels as they are yet unfiltered. They will be when the next macroblock +// row is decoded. Meanwhile, we must preserve them by rotating them in the +// cache area. This doesn't hold for the very bottom row of the uncropped +// picture of course. +// * we must clip the remaining pixels against the cropping area. The VP8Io +// struct must have the following fields set correctly before calling put(): + +#define MACROBLOCK_VPOS(mb_y) ((mb_y) * 16) // vertical position of a MB + +// Finalize and transmit a complete row. Return false in case of user-abort. +static int FinishRow(void* arg1, void* arg2) { + VP8Decoder* const dec = (VP8Decoder*)arg1; + VP8Io* const io = (VP8Io*)arg2; + int ok = 1; + const VP8ThreadContext* const ctx = &dec->thread_ctx_; + const int cache_id = ctx->id_; + const int extra_y_rows = kFilterExtraRows[dec->filter_type_]; + const int ysize = extra_y_rows * dec->cache_y_stride_; + const int uvsize = (extra_y_rows / 2) * dec->cache_uv_stride_; + const int y_offset = cache_id * 16 * dec->cache_y_stride_; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride_; + uint8_t* const ydst = dec->cache_y_ - ysize + y_offset; + uint8_t* const udst = dec->cache_u_ - uvsize + uv_offset; + uint8_t* const vdst = dec->cache_v_ - uvsize + uv_offset; + const int mb_y = ctx->mb_y_; + const int is_first_row = (mb_y == 0); + const int is_last_row = (mb_y >= dec->br_mb_y_ - 1); + + if (dec->mt_method_ == 2) { + ReconstructRow(dec, ctx); + } + + if (ctx->filter_row_) { + FilterRow(dec); + } + + if (dec->dither_) { + DitherRow(dec); + } + + if (io->put != NULL) { + int y_start = MACROBLOCK_VPOS(mb_y); + int y_end = MACROBLOCK_VPOS(mb_y + 1); + if (!is_first_row) { + y_start -= extra_y_rows; + io->y = ydst; + io->u = udst; + io->v = vdst; + } else { + io->y = dec->cache_y_ + y_offset; + io->u = dec->cache_u_ + uv_offset; + io->v = dec->cache_v_ + uv_offset; + } + + if (!is_last_row) { + y_end -= extra_y_rows; + } + if (y_end > io->crop_bottom) { + y_end = io->crop_bottom; // make sure we don't overflow on last row. + } + // If dec->alpha_data_ is not NULL, we have some alpha plane present. + io->a = NULL; + if (dec->alpha_data_ != NULL && y_start < y_end) { + io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start); + if (io->a == NULL) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Could not decode alpha data."); + } + } + if (y_start < io->crop_top) { + const int delta_y = io->crop_top - y_start; + y_start = io->crop_top; + assert(!(delta_y & 1)); + io->y += dec->cache_y_stride_ * delta_y; + io->u += dec->cache_uv_stride_ * (delta_y >> 1); + io->v += dec->cache_uv_stride_ * (delta_y >> 1); + if (io->a != NULL) { + io->a += io->width * delta_y; + } + } + if (y_start < y_end) { + io->y += io->crop_left; + io->u += io->crop_left >> 1; + io->v += io->crop_left >> 1; + if (io->a != NULL) { + io->a += io->crop_left; + } + io->mb_y = y_start - io->crop_top; + io->mb_w = io->crop_right - io->crop_left; + io->mb_h = y_end - y_start; + ok = io->put(io); + } + } + // rotate top samples if needed + if (cache_id + 1 == dec->num_caches_) { + if (!is_last_row) { + memcpy(dec->cache_y_ - ysize, ydst + 16 * dec->cache_y_stride_, ysize); + memcpy(dec->cache_u_ - uvsize, udst + 8 * dec->cache_uv_stride_, uvsize); + memcpy(dec->cache_v_ - uvsize, vdst + 8 * dec->cache_uv_stride_, uvsize); + } + } + + return ok; +} + +#undef MACROBLOCK_VPOS + +//------------------------------------------------------------------------------ + +int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) { + int ok = 1; + VP8ThreadContext* const ctx = &dec->thread_ctx_; + const int filter_row = + (dec->filter_type_ > 0) && + (dec->mb_y_ >= dec->tl_mb_y_) && (dec->mb_y_ <= dec->br_mb_y_); + if (dec->mt_method_ == 0) { + // ctx->id_ and ctx->f_info_ are already set + ctx->mb_y_ = dec->mb_y_; + ctx->filter_row_ = filter_row; + ReconstructRow(dec, ctx); + ok = FinishRow(dec, io); + } else { + WebPWorker* const worker = &dec->worker_; + // Finish previous job *before* updating context + ok &= WebPGetWorkerInterface()->Sync(worker); + assert(worker->status_ == OK); + if (ok) { // spawn a new deblocking/output job + ctx->io_ = *io; + ctx->id_ = dec->cache_id_; + ctx->mb_y_ = dec->mb_y_; + ctx->filter_row_ = filter_row; + if (dec->mt_method_ == 2) { // swap macroblock data + VP8MBData* const tmp = ctx->mb_data_; + ctx->mb_data_ = dec->mb_data_; + dec->mb_data_ = tmp; + } else { + // perform reconstruction directly in main thread + ReconstructRow(dec, ctx); + } + if (filter_row) { // swap filter info + VP8FInfo* const tmp = ctx->f_info_; + ctx->f_info_ = dec->f_info_; + dec->f_info_ = tmp; + } + // (reconstruct)+filter in parallel + WebPGetWorkerInterface()->Launch(worker); + if (++dec->cache_id_ == dec->num_caches_) { + dec->cache_id_ = 0; + } + } + } + return ok; +} + +//------------------------------------------------------------------------------ +// Finish setting up the decoding parameter once user's setup() is called. + +VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { + // Call setup() first. This may trigger additional decoding features on 'io'. + // Note: Afterward, we must call teardown() no matter what. + if (io->setup != NULL && !io->setup(io)) { + VP8SetError(dec, VP8_STATUS_USER_ABORT, "Frame setup failed"); + return dec->status_; + } + + // Disable filtering per user request + if (io->bypass_filtering) { + dec->filter_type_ = 0; + } + + // Define the area where we can skip in-loop filtering, in case of cropping. + // + // 'Simple' filter reads two luma samples outside of the macroblock + // and filters one. It doesn't filter the chroma samples. Hence, we can + // avoid doing the in-loop filtering before crop_top/crop_left position. + // For the 'Complex' filter, 3 samples are read and up to 3 are filtered. + // Means: there's a dependency chain that goes all the way up to the + // top-left corner of the picture (MB #0). We must filter all the previous + // macroblocks. + { + const int extra_pixels = kFilterExtraRows[dec->filter_type_]; + if (dec->filter_type_ == 2) { + // For complex filter, we need to preserve the dependency chain. + dec->tl_mb_x_ = 0; + dec->tl_mb_y_ = 0; + } else { + // For simple filter, we can filter only the cropped region. + // We include 'extra_pixels' on the other side of the boundary, since + // vertical or horizontal filtering of the previous macroblock can + // modify some abutting pixels. + dec->tl_mb_x_ = (io->crop_left - extra_pixels) >> 4; + dec->tl_mb_y_ = (io->crop_top - extra_pixels) >> 4; + if (dec->tl_mb_x_ < 0) dec->tl_mb_x_ = 0; + if (dec->tl_mb_y_ < 0) dec->tl_mb_y_ = 0; + } + // We need some 'extra' pixels on the right/bottom. + dec->br_mb_y_ = (io->crop_bottom + 15 + extra_pixels) >> 4; + dec->br_mb_x_ = (io->crop_right + 15 + extra_pixels) >> 4; + if (dec->br_mb_x_ > dec->mb_w_) { + dec->br_mb_x_ = dec->mb_w_; + } + if (dec->br_mb_y_ > dec->mb_h_) { + dec->br_mb_y_ = dec->mb_h_; + } + } + PrecomputeFilterStrengths(dec); + return VP8_STATUS_OK; +} + +int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) { + int ok = 1; + if (dec->mt_method_ > 0) { + ok = WebPGetWorkerInterface()->Sync(&dec->worker_); + } + + if (io->teardown != NULL) { + io->teardown(io); + } + return ok; +} + +//------------------------------------------------------------------------------ +// For multi-threaded decoding we need to use 3 rows of 16 pixels as delay line. +// +// Reason is: the deblocking filter cannot deblock the bottom horizontal edges +// immediately, and needs to wait for first few rows of the next macroblock to +// be decoded. Hence, deblocking is lagging behind by 4 or 8 pixels (depending +// on strength). +// With two threads, the vertical positions of the rows being decoded are: +// Decode: [ 0..15][16..31][32..47][48..63][64..79][... +// Deblock: [ 0..11][12..27][28..43][44..59][... +// If we use two threads and two caches of 16 pixels, the sequence would be: +// Decode: [ 0..15][16..31][ 0..15!!][16..31][ 0..15][... +// Deblock: [ 0..11][12..27!!][-4..11][12..27][... +// The problem occurs during row [12..15!!] that both the decoding and +// deblocking threads are writing simultaneously. +// With 3 cache lines, one get a safe write pattern: +// Decode: [ 0..15][16..31][32..47][ 0..15][16..31][32..47][0.. +// Deblock: [ 0..11][12..27][28..43][-4..11][12..27][28... +// Note that multi-threaded output _without_ deblocking can make use of two +// cache lines of 16 pixels only, since there's no lagging behind. The decoding +// and output process have non-concurrent writing: +// Decode: [ 0..15][16..31][ 0..15][16..31][... +// io->put: [ 0..15][16..31][ 0..15][... + +#define MT_CACHE_LINES 3 +#define ST_CACHE_LINES 1 // 1 cache row only for single-threaded case + +// Initialize multi/single-thread worker +static int InitThreadContext(VP8Decoder* const dec) { + dec->cache_id_ = 0; + if (dec->mt_method_ > 0) { + WebPWorker* const worker = &dec->worker_; + if (!WebPGetWorkerInterface()->Reset(worker)) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "thread initialization failed."); + } + worker->data1 = dec; + worker->data2 = (void*)&dec->thread_ctx_.io_; + worker->hook = FinishRow; + dec->num_caches_ = + (dec->filter_type_ > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1; + } else { + dec->num_caches_ = ST_CACHE_LINES; + } + return 1; +} + +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height) { + if (options == NULL || options->use_threads == 0) { + return 0; + } + (void)headers; + (void)width; + (void)height; + assert(headers == NULL || !headers->is_lossless); +#if defined(WEBP_USE_THREAD) + if (width >= MIN_WIDTH_FOR_THREADS) return 2; +#endif + return 0; +} + +#undef MT_CACHE_LINES +#undef ST_CACHE_LINES + +//------------------------------------------------------------------------------ +// Memory setup + +static int AllocateMemory(VP8Decoder* const dec) { + const int num_caches = dec->num_caches_; + const int mb_w = dec->mb_w_; + // Note: we use 'size_t' when there's no overflow risk, uint64_t otherwise. + const size_t intra_pred_mode_size = 4 * mb_w * sizeof(uint8_t); + const size_t top_size = sizeof(VP8TopSamples) * mb_w; + const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB); + const size_t f_info_size = + (dec->filter_type_ > 0) ? + mb_w * (dec->mt_method_ > 0 ? 2 : 1) * sizeof(VP8FInfo) + : 0; + const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b_); + const size_t mb_data_size = + (dec->mt_method_ == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data_); + const size_t cache_height = (16 * num_caches + + kFilterExtraRows[dec->filter_type_]) * 3 / 2; + const size_t cache_size = top_size * cache_height; + // alpha_size is the only one that scales as width x height. + const uint64_t alpha_size = (dec->alpha_data_ != NULL) ? + (uint64_t)dec->pic_hdr_.width_ * dec->pic_hdr_.height_ : 0ULL; + const uint64_t needed = (uint64_t)intra_pred_mode_size + + top_size + mb_info_size + f_info_size + + yuv_size + mb_data_size + + cache_size + alpha_size + WEBP_ALIGN_CST; + uint8_t* mem; + + if (!CheckSizeOverflow(needed)) return 0; // check for overflow + if (needed > dec->mem_size_) { + WebPSafeFree(dec->mem_); + dec->mem_size_ = 0; + dec->mem_ = WebPSafeMalloc(needed, sizeof(uint8_t)); + if (dec->mem_ == NULL) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "no memory during frame initialization."); + } + // down-cast is ok, thanks to WebPSafeMalloc() above. + dec->mem_size_ = (size_t)needed; + } + + mem = (uint8_t*)dec->mem_; + dec->intra_t_ = mem; + mem += intra_pred_mode_size; + + dec->yuv_t_ = (VP8TopSamples*)mem; + mem += top_size; + + dec->mb_info_ = ((VP8MB*)mem) + 1; + mem += mb_info_size; + + dec->f_info_ = f_info_size ? (VP8FInfo*)mem : NULL; + mem += f_info_size; + dec->thread_ctx_.id_ = 0; + dec->thread_ctx_.f_info_ = dec->f_info_; + if (dec->filter_type_ > 0 && dec->mt_method_ > 0) { + // secondary cache line. The deblocking process need to make use of the + // filtering strength from previous macroblock row, while the new ones + // are being decoded in parallel. We'll just swap the pointers. + dec->thread_ctx_.f_info_ += mb_w; + } + + mem = (uint8_t*)WEBP_ALIGN(mem); + assert((yuv_size & WEBP_ALIGN_CST) == 0); + dec->yuv_b_ = mem; + mem += yuv_size; + + dec->mb_data_ = (VP8MBData*)mem; + dec->thread_ctx_.mb_data_ = (VP8MBData*)mem; + if (dec->mt_method_ == 2) { + dec->thread_ctx_.mb_data_ += mb_w; + } + mem += mb_data_size; + + dec->cache_y_stride_ = 16 * mb_w; + dec->cache_uv_stride_ = 8 * mb_w; + { + const int extra_rows = kFilterExtraRows[dec->filter_type_]; + const int extra_y = extra_rows * dec->cache_y_stride_; + const int extra_uv = (extra_rows / 2) * dec->cache_uv_stride_; + dec->cache_y_ = mem + extra_y; + dec->cache_u_ = dec->cache_y_ + + 16 * num_caches * dec->cache_y_stride_ + extra_uv; + dec->cache_v_ = dec->cache_u_ + + 8 * num_caches * dec->cache_uv_stride_ + extra_uv; + dec->cache_id_ = 0; + } + mem += cache_size; + + // alpha plane + dec->alpha_plane_ = alpha_size ? mem : NULL; + mem += alpha_size; + assert(mem <= (uint8_t*)dec->mem_ + dec->mem_size_); + + // note: left/top-info is initialized once for all. + memset(dec->mb_info_ - 1, 0, mb_info_size); + VP8InitScanline(dec); // initialize left too. + + // initialize top + memset(dec->intra_t_, B_DC_PRED, intra_pred_mode_size); + + return 1; +} + +static void InitIo(VP8Decoder* const dec, VP8Io* io) { + // prepare 'io' + io->mb_y = 0; + io->y = dec->cache_y_; + io->u = dec->cache_u_; + io->v = dec->cache_v_; + io->y_stride = dec->cache_y_stride_; + io->uv_stride = dec->cache_uv_stride_; + io->a = NULL; +} + +int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io) { + if (!InitThreadContext(dec)) return 0; // call first. Sets dec->num_caches_. + if (!AllocateMemory(dec)) return 0; + InitIo(dec, io); + VP8DspInit(); // Init critical function pointers and look-up tables. + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/idec_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/idec_dec.c new file mode 100644 index 0000000000..ad042a1ffc --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/idec_dec.c @@ -0,0 +1,920 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Incremental decoding +// +// Author: somnath@google.com (Somnath Banerjee) + +#include +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" + +// In append mode, buffer allocations increase as multiples of this value. +// Needs to be a power of 2. +#define CHUNK_SIZE 4096 +#define MAX_MB_SIZE 4096 + +//------------------------------------------------------------------------------ +// Data structures for memory and states + +// Decoding states. State normally flows as: +// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and +// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image. +// If there is any error the decoder goes into state ERROR. +typedef enum { + STATE_WEBP_HEADER, // All the data before that of the VP8/VP8L chunk. + STATE_VP8_HEADER, // The VP8 Frame header (within the VP8 chunk). + STATE_VP8_PARTS0, + STATE_VP8_DATA, + STATE_VP8L_HEADER, + STATE_VP8L_DATA, + STATE_DONE, + STATE_ERROR +} DecState; + +// Operating state for the MemBuffer +typedef enum { + MEM_MODE_NONE = 0, + MEM_MODE_APPEND, + MEM_MODE_MAP +} MemBufferMode; + +// storage for partition #0 and partial data (in a rolling fashion) +typedef struct { + MemBufferMode mode_; // Operation mode + size_t start_; // start location of the data to be decoded + size_t end_; // end location + size_t buf_size_; // size of the allocated buffer + uint8_t* buf_; // We don't own this buffer in case WebPIUpdate() + + size_t part0_size_; // size of partition #0 + const uint8_t* part0_buf_; // buffer to store partition #0 +} MemBuffer; + +struct WebPIDecoder { + DecState state_; // current decoding state + WebPDecParams params_; // Params to store output info + int is_lossless_; // for down-casting 'dec_'. + void* dec_; // either a VP8Decoder or a VP8LDecoder instance + VP8Io io_; + + MemBuffer mem_; // input memory buffer. + WebPDecBuffer output_; // output buffer (when no external one is supplied, + // or if the external one has slow-memory) + WebPDecBuffer* final_output_; // Slow-memory output to copy to eventually. + size_t chunk_size_; // Compressed VP8/VP8L size extracted from Header. + + int last_mb_y_; // last row reached for intra-mode decoding +}; + +// MB context to restore in case VP8DecodeMB() fails +typedef struct { + VP8MB left_; + VP8MB info_; + VP8BitReader token_br_; +} MBContext; + +//------------------------------------------------------------------------------ +// MemBuffer: incoming data handling + +static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { + return (mem->end_ - mem->start_); +} + +// Check if we need to preserve the compressed alpha data, as it may not have +// been decoded yet. +static int NeedCompressedAlpha(const WebPIDecoder* const idec) { + if (idec->state_ == STATE_WEBP_HEADER) { + // We haven't parsed the headers yet, so we don't know whether the image is + // lossy or lossless. This also means that we haven't parsed the ALPH chunk. + return 0; + } + if (idec->is_lossless_) { + return 0; // ALPH chunk is not present for lossless images. + } else { + const VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + assert(dec != NULL); // Must be true as idec->state_ != STATE_WEBP_HEADER. + return (dec->alpha_data_ != NULL) && !dec->is_alpha_decoded_; + } +} + +static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { + MemBuffer* const mem = &idec->mem_; + const uint8_t* const new_base = mem->buf_ + mem->start_; + // note: for VP8, setting up idec->io_ is only really needed at the beginning + // of the decoding, till partition #0 is complete. + idec->io_.data = new_base; + idec->io_.data_size = MemDataSize(mem); + + if (idec->dec_ != NULL) { + if (!idec->is_lossless_) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + const uint32_t last_part = dec->num_parts_minus_one_; + if (offset != 0) { + uint32_t p; + for (p = 0; p <= last_part; ++p) { + VP8RemapBitReader(dec->parts_ + p, offset); + } + // Remap partition #0 data pointer to new offset, but only in MAP + // mode (in APPEND mode, partition #0 is copied into a fixed memory). + if (mem->mode_ == MEM_MODE_MAP) { + VP8RemapBitReader(&dec->br_, offset); + } + } + { + const uint8_t* const last_start = dec->parts_[last_part].buf_; + VP8BitReaderSetBuffer(&dec->parts_[last_part], last_start, + mem->buf_ + mem->end_ - last_start); + } + if (NeedCompressedAlpha(idec)) { + ALPHDecoder* const alph_dec = dec->alph_dec_; + dec->alpha_data_ += offset; + if (alph_dec != NULL && alph_dec->vp8l_dec_ != NULL) { + if (alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION) { + VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec_; + assert(dec->alpha_data_size_ >= ALPHA_HEADER_LEN); + VP8LBitReaderSetBuffer(&alph_vp8l_dec->br_, + dec->alpha_data_ + ALPHA_HEADER_LEN, + dec->alpha_data_size_ - ALPHA_HEADER_LEN); + } else { // alph_dec->method_ == ALPHA_NO_COMPRESSION + // Nothing special to do in this case. + } + } + } + } else { // Resize lossless bitreader + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_; + VP8LBitReaderSetBuffer(&dec->br_, new_base, MemDataSize(mem)); + } + } +} + +// Appends data to the end of MemBuffer->buf_. It expands the allocated memory +// size if required and also updates VP8BitReader's if new memory is allocated. +WEBP_NODISCARD static int AppendToMemBuffer(WebPIDecoder* const idec, + const uint8_t* const data, + size_t data_size) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + MemBuffer* const mem = &idec->mem_; + const int need_compressed_alpha = NeedCompressedAlpha(idec); + const uint8_t* const old_start = + (mem->buf_ == NULL) ? NULL : mem->buf_ + mem->start_; + const uint8_t* const old_base = + need_compressed_alpha ? dec->alpha_data_ : old_start; + assert(mem->buf_ != NULL || mem->start_ == 0); + assert(mem->mode_ == MEM_MODE_APPEND); + if (data_size > MAX_CHUNK_PAYLOAD) { + // security safeguard: trying to allocate more than what the format + // allows for a chunk should be considered a smoke smell. + return 0; + } + + if (mem->end_ + data_size > mem->buf_size_) { // Need some free memory + const size_t new_mem_start = old_start - old_base; + const size_t current_size = MemDataSize(mem) + new_mem_start; + const uint64_t new_size = (uint64_t)current_size + data_size; + const uint64_t extra_size = (new_size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1); + uint8_t* const new_buf = + (uint8_t*)WebPSafeMalloc(extra_size, sizeof(*new_buf)); + if (new_buf == NULL) return 0; + if (old_base != NULL) memcpy(new_buf, old_base, current_size); + WebPSafeFree(mem->buf_); + mem->buf_ = new_buf; + mem->buf_size_ = (size_t)extra_size; + mem->start_ = new_mem_start; + mem->end_ = current_size; + } + + assert(mem->buf_ != NULL); + memcpy(mem->buf_ + mem->end_, data, data_size); + mem->end_ += data_size; + assert(mem->end_ <= mem->buf_size_); + + DoRemap(idec, mem->buf_ + mem->start_ - old_start); + return 1; +} + +WEBP_NODISCARD static int RemapMemBuffer(WebPIDecoder* const idec, + const uint8_t* const data, + size_t data_size) { + MemBuffer* const mem = &idec->mem_; + const uint8_t* const old_buf = mem->buf_; + const uint8_t* const old_start = + (old_buf == NULL) ? NULL : old_buf + mem->start_; + assert(old_buf != NULL || mem->start_ == 0); + assert(mem->mode_ == MEM_MODE_MAP); + + if (data_size < mem->buf_size_) return 0; // can't remap to a shorter buffer! + + mem->buf_ = (uint8_t*)data; + mem->end_ = mem->buf_size_ = data_size; + + DoRemap(idec, mem->buf_ + mem->start_ - old_start); + return 1; +} + +static void InitMemBuffer(MemBuffer* const mem) { + mem->mode_ = MEM_MODE_NONE; + mem->buf_ = NULL; + mem->buf_size_ = 0; + mem->part0_buf_ = NULL; + mem->part0_size_ = 0; +} + +static void ClearMemBuffer(MemBuffer* const mem) { + assert(mem); + if (mem->mode_ == MEM_MODE_APPEND) { + WebPSafeFree(mem->buf_); + WebPSafeFree((void*)mem->part0_buf_); + } +} + +WEBP_NODISCARD static int CheckMemBufferMode(MemBuffer* const mem, + MemBufferMode expected) { + if (mem->mode_ == MEM_MODE_NONE) { + mem->mode_ = expected; // switch to the expected mode + } else if (mem->mode_ != expected) { + return 0; // we mixed the modes => error + } + assert(mem->mode_ == expected); // mode is ok + return 1; +} + +// To be called last. +WEBP_NODISCARD static VP8StatusCode FinishDecoding(WebPIDecoder* const idec) { + const WebPDecoderOptions* const options = idec->params_.options; + WebPDecBuffer* const output = idec->params_.output; + + idec->state_ = STATE_DONE; + if (options != NULL && options->flip) { + const VP8StatusCode status = WebPFlipBuffer(output); + if (status != VP8_STATUS_OK) return status; + } + if (idec->final_output_ != NULL) { + const VP8StatusCode status = WebPCopyDecBufferPixels( + output, idec->final_output_); // do the slow-copy + WebPFreeDecBuffer(&idec->output_); + if (status != VP8_STATUS_OK) return status; + *output = *idec->final_output_; + idec->final_output_ = NULL; + } + return VP8_STATUS_OK; +} + +//------------------------------------------------------------------------------ +// Macroblock-decoding contexts + +static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br, + MBContext* const context) { + context->left_ = dec->mb_info_[-1]; + context->info_ = dec->mb_info_[dec->mb_x_]; + context->token_br_ = *token_br; +} + +static void RestoreContext(const MBContext* context, VP8Decoder* const dec, + VP8BitReader* const token_br) { + dec->mb_info_[-1] = context->left_; + dec->mb_info_[dec->mb_x_] = context->info_; + *token_br = context->token_br_; +} + +//------------------------------------------------------------------------------ + +static VP8StatusCode IDecError(WebPIDecoder* const idec, VP8StatusCode error) { + if (idec->state_ == STATE_VP8_DATA) { + // Synchronize the thread, clean-up and check for errors. + (void)VP8ExitCritical((VP8Decoder*)idec->dec_, &idec->io_); + } + idec->state_ = STATE_ERROR; + return error; +} + +static void ChangeState(WebPIDecoder* const idec, DecState new_state, + size_t consumed_bytes) { + MemBuffer* const mem = &idec->mem_; + idec->state_ = new_state; + mem->start_ += consumed_bytes; + assert(mem->start_ <= mem->end_); + idec->io_.data = mem->buf_ + mem->start_; + idec->io_.data_size = MemDataSize(mem); +} + +// Headers +static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { + MemBuffer* const mem = &idec->mem_; + const uint8_t* data = mem->buf_ + mem->start_; + size_t curr_size = MemDataSize(mem); + VP8StatusCode status; + WebPHeaderStructure headers; + + headers.data = data; + headers.data_size = curr_size; + headers.have_all_data = 0; + status = WebPParseHeaders(&headers); + if (status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_SUSPENDED; // We haven't found a VP8 chunk yet. + } else if (status != VP8_STATUS_OK) { + return IDecError(idec, status); + } + + idec->chunk_size_ = headers.compressed_size; + idec->is_lossless_ = headers.is_lossless; + if (!idec->is_lossless_) { + VP8Decoder* const dec = VP8New(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + dec->incremental_ = 1; + idec->dec_ = dec; + dec->alpha_data_ = headers.alpha_data; + dec->alpha_data_size_ = headers.alpha_data_size; + ChangeState(idec, STATE_VP8_HEADER, headers.offset); + } else { + VP8LDecoder* const dec = VP8LNew(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + idec->dec_ = dec; + ChangeState(idec, STATE_VP8L_HEADER, headers.offset); + } + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) { + const uint8_t* data = idec->mem_.buf_ + idec->mem_.start_; + const size_t curr_size = MemDataSize(&idec->mem_); + int width, height; + uint32_t bits; + + if (curr_size < VP8_FRAME_HEADER_SIZE) { + // Not enough data bytes to extract VP8 Frame Header. + return VP8_STATUS_SUSPENDED; + } + if (!VP8GetInfo(data, curr_size, idec->chunk_size_, &width, &height)) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + + bits = data[0] | (data[1] << 8) | (data[2] << 16); + idec->mem_.part0_size_ = (bits >> 5) + VP8_FRAME_HEADER_SIZE; + + idec->io_.data = data; + idec->io_.data_size = curr_size; + idec->state_ = STATE_VP8_PARTS0; + return VP8_STATUS_OK; +} + +// Partition #0 +static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + VP8BitReader* const br = &dec->br_; + const size_t part_size = br->buf_end_ - br->buf_; + MemBuffer* const mem = &idec->mem_; + assert(!idec->is_lossless_); + assert(mem->part0_buf_ == NULL); + // the following is a format limitation, no need for runtime check: + assert(part_size <= mem->part0_size_); + if (part_size == 0) { // can't have zero-size partition #0 + return VP8_STATUS_BITSTREAM_ERROR; + } + if (mem->mode_ == MEM_MODE_APPEND) { + // We copy and grab ownership of the partition #0 data. + uint8_t* const part0_buf = (uint8_t*)WebPSafeMalloc(1ULL, part_size); + if (part0_buf == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + memcpy(part0_buf, br->buf_, part_size); + mem->part0_buf_ = part0_buf; + VP8BitReaderSetBuffer(br, part0_buf, part_size); + } else { + // Else: just keep pointers to the partition #0's data in dec_->br_. + } + mem->start_ += part_size; + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + VP8Io* const io = &idec->io_; + const WebPDecParams* const params = &idec->params_; + WebPDecBuffer* const output = params->output; + + // Wait till we have enough data for the whole partition #0 + if (MemDataSize(&idec->mem_) < idec->mem_.part0_size_) { + return VP8_STATUS_SUSPENDED; + } + + if (!VP8GetHeaders(dec, io)) { + const VP8StatusCode status = dec->status_; + if (status == VP8_STATUS_SUSPENDED || + status == VP8_STATUS_NOT_ENOUGH_DATA) { + // treating NOT_ENOUGH_DATA as SUSPENDED state + return VP8_STATUS_SUSPENDED; + } + return IDecError(idec, status); + } + + // Allocate/Verify output buffer now + dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options, + output); + if (dec->status_ != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); + } + // This change must be done before calling VP8InitFrame() + dec->mt_method_ = VP8GetThreadMethod(params->options, NULL, + io->width, io->height); + VP8InitDithering(params->options, dec); + + dec->status_ = CopyParts0Data(idec); + if (dec->status_ != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); + } + + // Finish setting up the decoding parameters. Will call io->setup(). + if (VP8EnterCritical(dec, io) != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); + } + + // Note: past this point, teardown() must always be called + // in case of error. + idec->state_ = STATE_VP8_DATA; + // Allocate memory and prepare everything. + if (!VP8InitFrame(dec, io)) { + return IDecError(idec, dec->status_); + } + return VP8_STATUS_OK; +} + +// Remaining partitions +static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + VP8Io* const io = &idec->io_; + + // Make sure partition #0 has been read before, to set dec to ready_. + if (!dec->ready_) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + for (; dec->mb_y_ < dec->mb_h_; ++dec->mb_y_) { + if (idec->last_mb_y_ != dec->mb_y_) { + if (!VP8ParseIntraModeRow(&dec->br_, dec)) { + // note: normally, error shouldn't occur since we already have the whole + // partition0 available here in DecodeRemaining(). Reaching EOF while + // reading intra modes really means a BITSTREAM_ERROR. + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + idec->last_mb_y_ = dec->mb_y_; + } + for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) { + VP8BitReader* const token_br = + &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_]; + MBContext context; + SaveContext(dec, token_br, &context); + if (!VP8DecodeMB(dec, token_br)) { + // We shouldn't fail when MAX_MB data was available + if (dec->num_parts_minus_one_ == 0 && + MemDataSize(&idec->mem_) > MAX_MB_SIZE) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + // Synchronize the threads. + if (dec->mt_method_ > 0) { + if (!WebPGetWorkerInterface()->Sync(&dec->worker_)) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + } + RestoreContext(&context, dec, token_br); + return VP8_STATUS_SUSPENDED; + } + // Release buffer only if there is only one partition + if (dec->num_parts_minus_one_ == 0) { + idec->mem_.start_ = token_br->buf_ - idec->mem_.buf_; + assert(idec->mem_.start_ <= idec->mem_.end_); + } + } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. + if (!VP8ProcessRow(dec, io)) { + return IDecError(idec, VP8_STATUS_USER_ABORT); + } + } + // Synchronize the thread and check for errors. + if (!VP8ExitCritical(dec, io)) { + idec->state_ = STATE_ERROR; // prevent re-entry in IDecError + return IDecError(idec, VP8_STATUS_USER_ABORT); + } + dec->ready_ = 0; + return FinishDecoding(idec); +} + +static VP8StatusCode ErrorStatusLossless(WebPIDecoder* const idec, + VP8StatusCode status) { + if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_SUSPENDED; + } + return IDecError(idec, status); +} + +static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) { + VP8Io* const io = &idec->io_; + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_; + const WebPDecParams* const params = &idec->params_; + WebPDecBuffer* const output = params->output; + size_t curr_size = MemDataSize(&idec->mem_); + assert(idec->is_lossless_); + + // Wait until there's enough data for decoding header. + if (curr_size < (idec->chunk_size_ >> 3)) { + dec->status_ = VP8_STATUS_SUSPENDED; + return ErrorStatusLossless(idec, dec->status_); + } + + if (!VP8LDecodeHeader(dec, io)) { + if (dec->status_ == VP8_STATUS_BITSTREAM_ERROR && + curr_size < idec->chunk_size_) { + dec->status_ = VP8_STATUS_SUSPENDED; + } + return ErrorStatusLossless(idec, dec->status_); + } + // Allocate/verify output buffer now. + dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options, + output); + if (dec->status_ != VP8_STATUS_OK) { + return IDecError(idec, dec->status_); + } + + idec->state_ = STATE_VP8L_DATA; + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) { + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_; + const size_t curr_size = MemDataSize(&idec->mem_); + assert(idec->is_lossless_); + + // Switch to incremental decoding if we don't have all the bytes available. + dec->incremental_ = (curr_size < idec->chunk_size_); + + if (!VP8LDecodeImage(dec)) { + return ErrorStatusLossless(idec, dec->status_); + } + assert(dec->status_ == VP8_STATUS_OK || dec->status_ == VP8_STATUS_SUSPENDED); + return (dec->status_ == VP8_STATUS_SUSPENDED) ? dec->status_ + : FinishDecoding(idec); +} + + // Main decoding loop +static VP8StatusCode IDecode(WebPIDecoder* idec) { + VP8StatusCode status = VP8_STATUS_SUSPENDED; + + if (idec->state_ == STATE_WEBP_HEADER) { + status = DecodeWebPHeaders(idec); + } else { + if (idec->dec_ == NULL) { + return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder. + } + } + if (idec->state_ == STATE_VP8_HEADER) { + status = DecodeVP8FrameHeader(idec); + } + if (idec->state_ == STATE_VP8_PARTS0) { + status = DecodePartition0(idec); + } + if (idec->state_ == STATE_VP8_DATA) { + const VP8Decoder* const dec = (VP8Decoder*)idec->dec_; + if (dec == NULL) { + return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder. + } + status = DecodeRemaining(idec); + } + if (idec->state_ == STATE_VP8L_HEADER) { + status = DecodeVP8LHeader(idec); + } + if (idec->state_ == STATE_VP8L_DATA) { + status = DecodeVP8LData(idec); + } + return status; +} + +//------------------------------------------------------------------------------ +// Internal constructor + +WEBP_NODISCARD static WebPIDecoder* NewDecoder( + WebPDecBuffer* const output_buffer, + const WebPBitstreamFeatures* const features) { + WebPIDecoder* idec = (WebPIDecoder*)WebPSafeCalloc(1ULL, sizeof(*idec)); + if (idec == NULL) { + return NULL; + } + + idec->state_ = STATE_WEBP_HEADER; + idec->chunk_size_ = 0; + + idec->last_mb_y_ = -1; + + InitMemBuffer(&idec->mem_); + if (!WebPInitDecBuffer(&idec->output_) || !VP8InitIo(&idec->io_)) { + WebPSafeFree(idec); + return NULL; + } + + WebPResetDecParams(&idec->params_); + if (output_buffer == NULL || WebPAvoidSlowMemory(output_buffer, features)) { + idec->params_.output = &idec->output_; + idec->final_output_ = output_buffer; + if (output_buffer != NULL) { + idec->params_.output->colorspace = output_buffer->colorspace; + } + } else { + idec->params_.output = output_buffer; + idec->final_output_ = NULL; + } + WebPInitCustomIo(&idec->params_, &idec->io_); // Plug the I/O functions. + + return idec; +} + +//------------------------------------------------------------------------------ +// Public functions + +WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) { + return NewDecoder(output_buffer, NULL); +} + +WebPIDecoder* WebPIDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config) { + WebPIDecoder* idec; + WebPBitstreamFeatures tmp_features; + WebPBitstreamFeatures* const features = + (config == NULL) ? &tmp_features : &config->input; + memset(&tmp_features, 0, sizeof(tmp_features)); + + // Parse the bitstream's features, if requested: + if (data != NULL && data_size > 0) { + if (WebPGetFeatures(data, data_size, features) != VP8_STATUS_OK) { + return NULL; + } + } + + // Create an instance of the incremental decoder + idec = (config != NULL) ? NewDecoder(&config->output, features) + : NewDecoder(NULL, features); + if (idec == NULL) { + return NULL; + } + // Finish initialization + if (config != NULL) { + idec->params_.options = &config->options; + } + return idec; +} + +void WebPIDelete(WebPIDecoder* idec) { + if (idec == NULL) return; + if (idec->dec_ != NULL) { + if (!idec->is_lossless_) { + if (idec->state_ == STATE_VP8_DATA) { + // Synchronize the thread, clean-up and check for errors. + // TODO(vrabaud) do we care about the return result? + (void)VP8ExitCritical((VP8Decoder*)idec->dec_, &idec->io_); + } + VP8Delete((VP8Decoder*)idec->dec_); + } else { + VP8LDelete((VP8LDecoder*)idec->dec_); + } + } + ClearMemBuffer(&idec->mem_); + WebPFreeDecBuffer(&idec->output_); + WebPSafeFree(idec); +} + +//------------------------------------------------------------------------------ +// Wrapper toward WebPINewDecoder + +WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE csp, uint8_t* output_buffer, + size_t output_buffer_size, int output_stride) { + const int is_external_memory = (output_buffer != NULL) ? 1 : 0; + WebPIDecoder* idec; + + if (csp >= MODE_YUV) return NULL; + if (is_external_memory == 0) { // Overwrite parameters to sane values. + output_buffer_size = 0; + output_stride = 0; + } else { // A buffer was passed. Validate the other params. + if (output_stride == 0 || output_buffer_size == 0) { + return NULL; // invalid parameter. + } + } + idec = WebPINewDecoder(NULL); + if (idec == NULL) return NULL; + idec->output_.colorspace = csp; + idec->output_.is_external_memory = is_external_memory; + idec->output_.u.RGBA.rgba = output_buffer; + idec->output_.u.RGBA.stride = output_stride; + idec->output_.u.RGBA.size = output_buffer_size; + return idec; +} + +WebPIDecoder* WebPINewYUVA(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride) { + const int is_external_memory = (luma != NULL) ? 1 : 0; + WebPIDecoder* idec; + WEBP_CSP_MODE colorspace; + + if (is_external_memory == 0) { // Overwrite parameters to sane values. + luma_size = u_size = v_size = a_size = 0; + luma_stride = u_stride = v_stride = a_stride = 0; + u = v = a = NULL; + colorspace = MODE_YUVA; + } else { // A luma buffer was passed. Validate the other parameters. + if (u == NULL || v == NULL) return NULL; + if (luma_size == 0 || u_size == 0 || v_size == 0) return NULL; + if (luma_stride == 0 || u_stride == 0 || v_stride == 0) return NULL; + if (a != NULL) { + if (a_size == 0 || a_stride == 0) return NULL; + } + colorspace = (a == NULL) ? MODE_YUV : MODE_YUVA; + } + + idec = WebPINewDecoder(NULL); + if (idec == NULL) return NULL; + + idec->output_.colorspace = colorspace; + idec->output_.is_external_memory = is_external_memory; + idec->output_.u.YUVA.y = luma; + idec->output_.u.YUVA.y_stride = luma_stride; + idec->output_.u.YUVA.y_size = luma_size; + idec->output_.u.YUVA.u = u; + idec->output_.u.YUVA.u_stride = u_stride; + idec->output_.u.YUVA.u_size = u_size; + idec->output_.u.YUVA.v = v; + idec->output_.u.YUVA.v_stride = v_stride; + idec->output_.u.YUVA.v_size = v_size; + idec->output_.u.YUVA.a = a; + idec->output_.u.YUVA.a_stride = a_stride; + idec->output_.u.YUVA.a_size = a_size; + return idec; +} + +WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride) { + return WebPINewYUVA(luma, luma_size, luma_stride, + u, u_size, u_stride, + v, v_size, v_stride, + NULL, 0, 0); +} + +//------------------------------------------------------------------------------ + +static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) { + assert(idec); + if (idec->state_ == STATE_ERROR) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (idec->state_ == STATE_DONE) { + return VP8_STATUS_OK; + } + return VP8_STATUS_SUSPENDED; +} + +VP8StatusCode WebPIAppend(WebPIDecoder* idec, + const uint8_t* data, size_t data_size) { + VP8StatusCode status; + if (idec == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + status = IDecCheckStatus(idec); + if (status != VP8_STATUS_SUSPENDED) { + return status; + } + // Check mixed calls between RemapMemBuffer and AppendToMemBuffer. + if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_APPEND)) { + return VP8_STATUS_INVALID_PARAM; + } + // Append data to memory buffer + if (!AppendToMemBuffer(idec, data, data_size)) { + return VP8_STATUS_OUT_OF_MEMORY; + } + return IDecode(idec); +} + +VP8StatusCode WebPIUpdate(WebPIDecoder* idec, + const uint8_t* data, size_t data_size) { + VP8StatusCode status; + if (idec == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + status = IDecCheckStatus(idec); + if (status != VP8_STATUS_SUSPENDED) { + return status; + } + // Check mixed calls between RemapMemBuffer and AppendToMemBuffer. + if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_MAP)) { + return VP8_STATUS_INVALID_PARAM; + } + // Make the memory buffer point to the new buffer + if (!RemapMemBuffer(idec, data, data_size)) { + return VP8_STATUS_INVALID_PARAM; + } + return IDecode(idec); +} + +//------------------------------------------------------------------------------ + +static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) { + if (idec == NULL || idec->dec_ == NULL) { + return NULL; + } + if (idec->state_ <= STATE_VP8_PARTS0) { + return NULL; + } + if (idec->final_output_ != NULL) { + return NULL; // not yet slow-copied + } + return idec->params_.output; +} + +const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, + int* left, int* top, + int* width, int* height) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (left != NULL) *left = 0; + if (top != NULL) *top = 0; + if (src != NULL) { + if (width != NULL) *width = src->width; + if (height != NULL) *height = idec->params_.last_y; + } else { + if (width != NULL) *width = 0; + if (height != NULL) *height = 0; + } + return src; +} + +WEBP_NODISCARD uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec, int* last_y, + int* width, int* height, int* stride) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (src == NULL) return NULL; + if (src->colorspace >= MODE_YUV) { + return NULL; + } + + if (last_y != NULL) *last_y = idec->params_.last_y; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.RGBA.stride; + + return src->u.RGBA.rgba; +} + +WEBP_NODISCARD uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, + int* uv_stride, int* a_stride) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (src == NULL) return NULL; + if (src->colorspace < MODE_YUV) { + return NULL; + } + + if (last_y != NULL) *last_y = idec->params_.last_y; + if (u != NULL) *u = src->u.YUVA.u; + if (v != NULL) *v = src->u.YUVA.v; + if (a != NULL) *a = src->u.YUVA.a; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.YUVA.y_stride; + if (uv_stride != NULL) *uv_stride = src->u.YUVA.u_stride; + if (a_stride != NULL) *a_stride = src->u.YUVA.a_stride; + + return src->u.YUVA.y; +} + +int WebPISetIOHooks(WebPIDecoder* const idec, + VP8IoPutHook put, + VP8IoSetupHook setup, + VP8IoTeardownHook teardown, + void* user_data) { + if (idec == NULL || idec->state_ > STATE_WEBP_HEADER) { + return 0; + } + + idec->io_.put = put; + idec->io_.setup = setup; + idec->io_.teardown = teardown; + idec->io_.opaque = user_data; + + return 1; +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/io_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/io_dec.c new file mode 100644 index 0000000000..5ef6298886 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/io_dec.c @@ -0,0 +1,662 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// functions for sample output. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include "src/dec/vp8i_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/dsp/yuv.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// Main YUV<->RGB conversion functions + +static int EmitYUV(const VP8Io* const io, WebPDecParams* const p) { + WebPDecBuffer* output = p->output; + const WebPYUVABuffer* const buf = &output->u.YUVA; + uint8_t* const y_dst = buf->y + (size_t)io->mb_y * buf->y_stride; + uint8_t* const u_dst = buf->u + (size_t)(io->mb_y >> 1) * buf->u_stride; + uint8_t* const v_dst = buf->v + (size_t)(io->mb_y >> 1) * buf->v_stride; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + const int uv_w = (mb_w + 1) / 2; + const int uv_h = (mb_h + 1) / 2; + WebPCopyPlane(io->y, io->y_stride, y_dst, buf->y_stride, mb_w, mb_h); + WebPCopyPlane(io->u, io->uv_stride, u_dst, buf->u_stride, uv_w, uv_h); + WebPCopyPlane(io->v, io->uv_stride, v_dst, buf->v_stride, uv_w, uv_h); + return io->mb_h; +} + +// Point-sampling U/V sampler. +static int EmitSampledRGB(const VP8Io* const io, WebPDecParams* const p) { + WebPDecBuffer* const output = p->output; + WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const dst = buf->rgba + (size_t)io->mb_y * buf->stride; + WebPSamplerProcessPlane(io->y, io->y_stride, + io->u, io->v, io->uv_stride, + dst, buf->stride, io->mb_w, io->mb_h, + WebPSamplers[output->colorspace]); + return io->mb_h; +} + +//------------------------------------------------------------------------------ +// Fancy upsampling + +#ifdef FANCY_UPSAMPLING +static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) { + int num_lines_out = io->mb_h; // a priori guess + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* dst = buf->rgba + (size_t)io->mb_y * buf->stride; + WebPUpsampleLinePairFunc upsample = WebPUpsamplers[p->output->colorspace]; + const uint8_t* cur_y = io->y; + const uint8_t* cur_u = io->u; + const uint8_t* cur_v = io->v; + const uint8_t* top_u = p->tmp_u; + const uint8_t* top_v = p->tmp_v; + int y = io->mb_y; + const int y_end = io->mb_y + io->mb_h; + const int mb_w = io->mb_w; + const int uv_w = (mb_w + 1) / 2; + + if (y == 0) { + // First line is special cased. We mirror the u/v samples at boundary. + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, mb_w); + } else { + // We can finish the left-over line from previous call. + upsample(p->tmp_y, cur_y, top_u, top_v, cur_u, cur_v, + dst - buf->stride, dst, mb_w); + ++num_lines_out; + } + // Loop over each output pairs of row. + for (; y + 2 < y_end; y += 2) { + top_u = cur_u; + top_v = cur_v; + cur_u += io->uv_stride; + cur_v += io->uv_stride; + dst += 2 * buf->stride; + cur_y += 2 * io->y_stride; + upsample(cur_y - io->y_stride, cur_y, + top_u, top_v, cur_u, cur_v, + dst - buf->stride, dst, mb_w); + } + // move to last row + cur_y += io->y_stride; + if (io->crop_top + y_end < io->crop_bottom) { + // Save the unfinished samples for next call (as we're not done yet). + memcpy(p->tmp_y, cur_y, mb_w * sizeof(*p->tmp_y)); + memcpy(p->tmp_u, cur_u, uv_w * sizeof(*p->tmp_u)); + memcpy(p->tmp_v, cur_v, uv_w * sizeof(*p->tmp_v)); + // The fancy upsampler leaves a row unfinished behind + // (except for the very last row) + num_lines_out--; + } else { + // Process the very last row of even-sized picture + if (!(y_end & 1)) { + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, + dst + buf->stride, NULL, mb_w); + } + } + return num_lines_out; +} + +#endif /* FANCY_UPSAMPLING */ + +//------------------------------------------------------------------------------ + +static void FillAlphaPlane(uint8_t* dst, int w, int h, int stride) { + int j; + for (j = 0; j < h; ++j) { + memset(dst, 0xff, w * sizeof(*dst)); + dst += stride; + } +} + +static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + uint8_t* dst = buf->a + (size_t)io->mb_y * buf->a_stride; + int j; + (void)expected_num_lines_out; + assert(expected_num_lines_out == mb_h); + if (alpha != NULL) { + for (j = 0; j < mb_h; ++j) { + memcpy(dst, alpha, mb_w * sizeof(*dst)); + alpha += io->width; + dst += buf->a_stride; + } + } else if (buf->a != NULL) { + // the user requested alpha, but there is none, set it to opaque. + FillAlphaPlane(dst, mb_w, mb_h, buf->a_stride); + } + return 0; +} + +static int GetAlphaSourceRow(const VP8Io* const io, + const uint8_t** alpha, int* const num_rows) { + int start_y = io->mb_y; + *num_rows = io->mb_h; + + // Compensate for the 1-line delay of the fancy upscaler. + // This is similar to EmitFancyRGB(). + if (io->fancy_upsampling) { + if (start_y == 0) { + // We don't process the last row yet. It'll be done during the next call. + --*num_rows; + } else { + --start_y; + // Fortunately, *alpha data is persistent, so we can go back + // one row and finish alpha blending, now that the fancy upscaler + // completed the YUV->RGB interpolation. + *alpha -= io->width; + } + if (io->crop_top + io->mb_y + io->mb_h == io->crop_bottom) { + // If it's the very last call, we process all the remaining rows! + *num_rows = io->crop_bottom - io->crop_top - start_y; + } + } + return start_y; +} + +static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + if (alpha != NULL) { + const int mb_w = io->mb_w; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int alpha_first = + (colorspace == MODE_ARGB || colorspace == MODE_Argb); + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + int num_rows; + const size_t start_y = GetAlphaSourceRow(io, &alpha, &num_rows); + uint8_t* const base_rgba = buf->rgba + start_y * buf->stride; + uint8_t* const dst = base_rgba + (alpha_first ? 0 : 3); + const int has_alpha = WebPDispatchAlpha(alpha, io->width, mb_w, + num_rows, dst, buf->stride); + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); + // has_alpha is true if there's non-trivial alpha to premultiply with. + if (has_alpha && WebPIsPremultipliedMode(colorspace)) { + WebPApplyAlphaMultiply(base_rgba, alpha_first, + mb_w, num_rows, buf->stride); + } + } + return 0; +} + +static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + if (alpha != NULL) { + const int mb_w = io->mb_w; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + int num_rows; + const size_t start_y = GetAlphaSourceRow(io, &alpha, &num_rows); + uint8_t* const base_rgba = buf->rgba + start_y * buf->stride; +#if (WEBP_SWAP_16BIT_CSP == 1) + uint8_t* alpha_dst = base_rgba; +#else + uint8_t* alpha_dst = base_rgba + 1; +#endif + uint32_t alpha_mask = 0x0f; + int i, j; + for (j = 0; j < num_rows; ++j) { + for (i = 0; i < mb_w; ++i) { + // Fill in the alpha value (converted to 4 bits). + const uint32_t alpha_value = alpha[i] >> 4; + alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value; + alpha_mask &= alpha_value; + } + alpha += io->width; + alpha_dst += buf->stride; + } + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); + if (alpha_mask != 0x0f && WebPIsPremultipliedMode(colorspace)) { + WebPApplyAlphaMultiply4444(base_rgba, mb_w, num_rows, buf->stride); + } + } + return 0; +} + +//------------------------------------------------------------------------------ +// YUV rescaling (no final RGB conversion needed) + +#if !defined(WEBP_REDUCE_SIZE) +static int Rescale(const uint8_t* src, int src_stride, + int new_lines, WebPRescaler* const wrk) { + int num_lines_out = 0; + while (new_lines > 0) { // import new contributions of source rows. + const int lines_in = WebPRescalerImport(wrk, new_lines, src, src_stride); + src += lines_in * src_stride; + new_lines -= lines_in; + num_lines_out += WebPRescalerExport(wrk); // emit output row(s) + } + return num_lines_out; +} + +static int EmitRescaledYUV(const VP8Io* const io, WebPDecParams* const p) { + const int mb_h = io->mb_h; + const int uv_mb_h = (mb_h + 1) >> 1; + WebPRescaler* const scaler = p->scaler_y; + int num_lines_out = 0; + if (WebPIsAlphaMode(p->output->colorspace) && io->a != NULL) { + // Before rescaling, we premultiply the luma directly into the io->y + // internal buffer. This is OK since these samples are not used for + // intra-prediction (the top samples are saved in cache_y_/u_/v_). + // But we need to cast the const away, though. + WebPMultRows((uint8_t*)io->y, io->y_stride, + io->a, io->width, io->mb_w, mb_h, 0); + } + num_lines_out = Rescale(io->y, io->y_stride, mb_h, scaler); + Rescale(io->u, io->uv_stride, uv_mb_h, p->scaler_u); + Rescale(io->v, io->uv_stride, uv_mb_h, p->scaler_v); + return num_lines_out; +} + +static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + uint8_t* const dst_a = buf->a + (size_t)p->last_y * buf->a_stride; + if (io->a != NULL) { + uint8_t* const dst_y = buf->y + (size_t)p->last_y * buf->y_stride; + const int num_lines_out = Rescale(io->a, io->width, io->mb_h, p->scaler_a); + assert(expected_num_lines_out == num_lines_out); + if (num_lines_out > 0) { // unmultiply the Y + WebPMultRows(dst_y, buf->y_stride, dst_a, buf->a_stride, + p->scaler_a->dst_width, num_lines_out, 1); + } + } else if (buf->a != NULL) { + // the user requested alpha, but there is none, set it to opaque. + assert(p->last_y + expected_num_lines_out <= io->scaled_height); + FillAlphaPlane(dst_a, io->scaled_width, expected_num_lines_out, + buf->a_stride); + } + return 0; +} + +static int InitYUVRescaler(const VP8Io* const io, WebPDecParams* const p) { + const int has_alpha = WebPIsAlphaMode(p->output->colorspace); + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + const int out_width = io->scaled_width; + const int out_height = io->scaled_height; + const int uv_out_width = (out_width + 1) >> 1; + const int uv_out_height = (out_height + 1) >> 1; + const int uv_in_width = (io->mb_w + 1) >> 1; + const int uv_in_height = (io->mb_h + 1) >> 1; + // scratch memory for luma rescaler + const size_t work_size = 2 * (size_t)out_width; + const size_t uv_work_size = 2 * uv_out_width; // and for each u/v ones + uint64_t total_size; + size_t rescaler_size; + rescaler_t* work; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; + + total_size = ((uint64_t)work_size + 2 * uv_work_size) * sizeof(*work); + if (has_alpha) { + total_size += (uint64_t)work_size * sizeof(*work); + } + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + total_size += rescaler_size; + if (!CheckSizeOverflow(total_size)) { + return 0; + } + + p->memory = WebPSafeMalloc(1ULL, (size_t)total_size); + if (p->memory == NULL) { + return 0; // memory error + } + work = (rescaler_t*)p->memory; + + scalers = (WebPRescaler*)WEBP_ALIGN( + (const uint8_t*)work + total_size - rescaler_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, + buf->y, out_width, out_height, buf->y_stride, 1, + work) || + !WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, + buf->u, uv_out_width, uv_out_height, buf->u_stride, 1, + work + work_size) || + !WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, + buf->v, uv_out_width, uv_out_height, buf->v_stride, 1, + work + work_size + uv_work_size)) { + return 0; + } + p->emit = EmitRescaledYUV; + + if (has_alpha) { + if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, + buf->a, out_width, out_height, buf->a_stride, 1, + work + work_size + 2 * uv_work_size)) { + return 0; + } + p->emit_alpha = EmitRescaledAlphaYUV; + WebPInitAlphaProcessing(); + } + return 1; +} + +//------------------------------------------------------------------------------ +// RGBA rescaling + +static int ExportRGB(WebPDecParams* const p, int y_pos) { + const WebPYUV444Converter convert = + WebPYUV444Converters[p->output->colorspace]; + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* dst = buf->rgba + (size_t)y_pos * buf->stride; + int num_lines_out = 0; + // For RGB rescaling, because of the YUV420, current scan position + // U/V can be +1/-1 line from the Y one. Hence the double test. + while (WebPRescalerHasPendingOutput(p->scaler_y) && + WebPRescalerHasPendingOutput(p->scaler_u)) { + assert(y_pos + num_lines_out < p->output->height); + assert(p->scaler_u->y_accum == p->scaler_v->y_accum); + WebPRescalerExportRow(p->scaler_y); + WebPRescalerExportRow(p->scaler_u); + WebPRescalerExportRow(p->scaler_v); + convert(p->scaler_y->dst, p->scaler_u->dst, p->scaler_v->dst, + dst, p->scaler_y->dst_width); + dst += buf->stride; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRGB(const VP8Io* const io, WebPDecParams* const p) { + const int mb_h = io->mb_h; + const int uv_mb_h = (mb_h + 1) >> 1; + int j = 0, uv_j = 0; + int num_lines_out = 0; + while (j < mb_h) { + const int y_lines_in = + WebPRescalerImport(p->scaler_y, mb_h - j, + io->y + (size_t)j * io->y_stride, io->y_stride); + j += y_lines_in; + if (WebPRescaleNeededLines(p->scaler_u, uv_mb_h - uv_j)) { + const int u_lines_in = WebPRescalerImport( + p->scaler_u, uv_mb_h - uv_j, io->u + (size_t)uv_j * io->uv_stride, + io->uv_stride); + const int v_lines_in = WebPRescalerImport( + p->scaler_v, uv_mb_h - uv_j, io->v + (size_t)uv_j * io->uv_stride, + io->uv_stride); + (void)v_lines_in; // remove a gcc warning + assert(u_lines_in == v_lines_in); + uv_j += u_lines_in; + } + num_lines_out += ExportRGB(p, p->last_y + num_lines_out); + } + return num_lines_out; +} + +static int ExportAlpha(WebPDecParams* const p, int y_pos, int max_lines_out) { + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* const base_rgba = buf->rgba + (size_t)y_pos * buf->stride; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int alpha_first = + (colorspace == MODE_ARGB || colorspace == MODE_Argb); + uint8_t* dst = base_rgba + (alpha_first ? 0 : 3); + int num_lines_out = 0; + const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); + uint32_t non_opaque = 0; + const int width = p->scaler_a->dst_width; + + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); + non_opaque |= WebPDispatchAlpha(p->scaler_a->dst, 0, width, 1, dst, 0); + dst += buf->stride; + ++num_lines_out; + } + if (is_premult_alpha && non_opaque) { + WebPApplyAlphaMultiply(base_rgba, alpha_first, + width, num_lines_out, buf->stride); + } + return num_lines_out; +} + +static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos, + int max_lines_out) { + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* const base_rgba = buf->rgba + (size_t)y_pos * buf->stride; +#if (WEBP_SWAP_16BIT_CSP == 1) + uint8_t* alpha_dst = base_rgba; +#else + uint8_t* alpha_dst = base_rgba + 1; +#endif + int num_lines_out = 0; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int width = p->scaler_a->dst_width; + const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); + uint32_t alpha_mask = 0x0f; + + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { + int i; + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); + for (i = 0; i < width; ++i) { + // Fill in the alpha value (converted to 4 bits). + const uint32_t alpha_value = p->scaler_a->dst[i] >> 4; + alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value; + alpha_mask &= alpha_value; + } + alpha_dst += buf->stride; + ++num_lines_out; + } + if (is_premult_alpha && alpha_mask != 0x0f) { + WebPApplyAlphaMultiply4444(base_rgba, width, num_lines_out, buf->stride); + } + return num_lines_out; +} + +static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines) { + if (io->a != NULL) { + WebPRescaler* const scaler = p->scaler_a; + int lines_left = expected_num_out_lines; + const int y_end = p->last_y + lines_left; + while (lines_left > 0) { + const int64_t row_offset = (int64_t)scaler->src_y - io->mb_y; + WebPRescalerImport(scaler, io->mb_h + io->mb_y - scaler->src_y, + io->a + row_offset * io->width, io->width); + lines_left -= p->emit_alpha_row(p, y_end - lines_left, lines_left); + } + } + return 0; +} + +static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) { + const int has_alpha = WebPIsAlphaMode(p->output->colorspace); + const int out_width = io->scaled_width; + const int out_height = io->scaled_height; + const int uv_in_width = (io->mb_w + 1) >> 1; + const int uv_in_height = (io->mb_h + 1) >> 1; + // scratch memory for one rescaler + const size_t work_size = 2 * (size_t)out_width; + rescaler_t* work; // rescalers work area + uint8_t* tmp; // tmp storage for scaled YUV444 samples before RGB conversion + uint64_t tmp_size1, tmp_size2, total_size; + size_t rescaler_size; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; + + tmp_size1 = (uint64_t)num_rescalers * work_size; + tmp_size2 = (uint64_t)num_rescalers * out_width; + total_size = tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp); + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + total_size += rescaler_size; + if (!CheckSizeOverflow(total_size)) { + return 0; + } + + p->memory = WebPSafeMalloc(1ULL, (size_t)total_size); + if (p->memory == NULL) { + return 0; // memory error + } + work = (rescaler_t*)p->memory; + tmp = (uint8_t*)(work + tmp_size1); + + scalers = (WebPRescaler*)WEBP_ALIGN( + (const uint8_t*)work + total_size - rescaler_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, + tmp + 0 * out_width, out_width, out_height, 0, 1, + work + 0 * work_size) || + !WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, + tmp + 1 * out_width, out_width, out_height, 0, 1, + work + 1 * work_size) || + !WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, + tmp + 2 * out_width, out_width, out_height, 0, 1, + work + 2 * work_size)) { + return 0; + } + p->emit = EmitRescaledRGB; + WebPInitYUV444Converters(); + + if (has_alpha) { + if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, + tmp + 3 * out_width, out_width, out_height, 0, 1, + work + 3 * work_size)) { + return 0; + } + p->emit_alpha = EmitRescaledAlphaRGB; + if (p->output->colorspace == MODE_RGBA_4444 || + p->output->colorspace == MODE_rgbA_4444) { + p->emit_alpha_row = ExportAlphaRGBA4444; + } else { + p->emit_alpha_row = ExportAlpha; + } + WebPInitAlphaProcessing(); + } + return 1; +} + +#endif // WEBP_REDUCE_SIZE + +//------------------------------------------------------------------------------ +// Default custom functions + +static int CustomSetup(VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int is_rgb = WebPIsRGBMode(colorspace); + const int is_alpha = WebPIsAlphaMode(colorspace); + + p->memory = NULL; + p->emit = NULL; + p->emit_alpha = NULL; + p->emit_alpha_row = NULL; + if (!WebPIoInitFromOptions(p->options, io, is_alpha ? MODE_YUV : MODE_YUVA)) { + return 0; + } + if (is_alpha && WebPIsPremultipliedMode(colorspace)) { + WebPInitUpsamplers(); + } + if (io->use_scaling) { +#if !defined(WEBP_REDUCE_SIZE) + const int ok = is_rgb ? InitRGBRescaler(io, p) : InitYUVRescaler(io, p); + if (!ok) { + return 0; // memory error + } +#else + return 0; // rescaling support not compiled +#endif + } else { + if (is_rgb) { + WebPInitSamplers(); + p->emit = EmitSampledRGB; // default + if (io->fancy_upsampling) { +#ifdef FANCY_UPSAMPLING + const int uv_width = (io->mb_w + 1) >> 1; + p->memory = WebPSafeMalloc(1ULL, (size_t)(io->mb_w + 2 * uv_width)); + if (p->memory == NULL) { + return 0; // memory error. + } + p->tmp_y = (uint8_t*)p->memory; + p->tmp_u = p->tmp_y + io->mb_w; + p->tmp_v = p->tmp_u + uv_width; + p->emit = EmitFancyRGB; + WebPInitUpsamplers(); +#endif + } + } else { + p->emit = EmitYUV; + } + if (is_alpha) { // need transparency output + p->emit_alpha = + (colorspace == MODE_RGBA_4444 || colorspace == MODE_rgbA_4444) ? + EmitAlphaRGBA4444 + : is_rgb ? EmitAlphaRGB + : EmitAlphaYUV; + if (is_rgb) { + WebPInitAlphaProcessing(); + } + } + } + + return 1; +} + +//------------------------------------------------------------------------------ + +static int CustomPut(const VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + int num_lines_out; + assert(!(io->mb_y & 1)); + + if (mb_w <= 0 || mb_h <= 0) { + return 0; + } + num_lines_out = p->emit(io, p); + if (p->emit_alpha != NULL) { + p->emit_alpha(io, p, num_lines_out); + } + p->last_y += num_lines_out; + return 1; +} + +//------------------------------------------------------------------------------ + +static void CustomTeardown(const VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + WebPSafeFree(p->memory); + p->memory = NULL; +} + +//------------------------------------------------------------------------------ +// Main entry point + +void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io) { + io->put = CustomPut; + io->setup = CustomSetup; + io->teardown = CustomTeardown; + io->opaque = params; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/quant_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/quant_dec.c new file mode 100644 index 0000000000..a0ac018b0f --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/quant_dec.c @@ -0,0 +1,115 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Quantizer initialization +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dec/vp8i_dec.h" + +static WEBP_INLINE int clip(int v, int M) { + return v < 0 ? 0 : v > M ? M : v; +} + +// Paragraph 14.1 +static const uint8_t kDcTable[128] = { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 +}; + +static const uint16_t kAcTable[128] = { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 +}; + +//------------------------------------------------------------------------------ +// Paragraph 9.6 + +void VP8ParseQuant(VP8Decoder* const dec) { + VP8BitReader* const br = &dec->br_; + const int base_q0 = VP8GetValue(br, 7, "global-header"); + const int dqy1_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dqy2_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dqy2_ac = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dquv_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dquv_ac = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + + const VP8SegmentHeader* const hdr = &dec->segment_hdr_; + int i; + + for (i = 0; i < NUM_MB_SEGMENTS; ++i) { + int q; + if (hdr->use_segment_) { + q = hdr->quantizer_[i]; + if (!hdr->absolute_delta_) { + q += base_q0; + } + } else { + if (i > 0) { + dec->dqm_[i] = dec->dqm_[0]; + continue; + } else { + q = base_q0; + } + } + { + VP8QuantMatrix* const m = &dec->dqm_[i]; + m->y1_mat_[0] = kDcTable[clip(q + dqy1_dc, 127)]; + m->y1_mat_[1] = kAcTable[clip(q + 0, 127)]; + + m->y2_mat_[0] = kDcTable[clip(q + dqy2_dc, 127)] * 2; + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good + // word size. + m->y2_mat_[1] = (kAcTable[clip(q + dqy2_ac, 127)] * 101581) >> 16; + if (m->y2_mat_[1] < 8) m->y2_mat_[1] = 8; + + m->uv_mat_[0] = kDcTable[clip(q + dquv_dc, 117)]; + m->uv_mat_[1] = kAcTable[clip(q + dquv_ac, 127)]; + + m->uv_quant_ = q + dquv_ac; // for dithering strength evaluation + } + } +} + +//------------------------------------------------------------------------------ + diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/tree_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/tree_dec.c new file mode 100644 index 0000000000..2434605953 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/tree_dec.c @@ -0,0 +1,538 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Coding trees and probas +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dec/vp8i_dec.h" +#include "src/dsp/cpu.h" +#include "src/utils/bit_reader_inl_utils.h" + +#if !defined(USE_GENERIC_TREE) +#if !defined(__arm__) && !defined(_M_ARM) && !WEBP_AARCH64 +// using a table is ~1-2% slower on ARM. Prefer the coded-tree approach then. +#define USE_GENERIC_TREE 1 // ALTERNATE_CODE +#else +#define USE_GENERIC_TREE 0 +#endif +#endif // USE_GENERIC_TREE + +#if (USE_GENERIC_TREE == 1) +static const int8_t kYModesIntra4[18] = { + -B_DC_PRED, 1, + -B_TM_PRED, 2, + -B_VE_PRED, 3, + 4, 6, + -B_HE_PRED, 5, + -B_RD_PRED, -B_VR_PRED, + -B_LD_PRED, 7, + -B_VL_PRED, 8, + -B_HD_PRED, -B_HU_PRED +}; +#endif + +//------------------------------------------------------------------------------ +// Default probabilities + +// Paragraph 13.5 +static const uint8_t + CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { + { { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } +}; + +// Paragraph 11.5 +static const uint8_t kBModesProba[NUM_BMODES][NUM_BMODES][NUM_BMODES - 1] = { + { { 231, 120, 48, 89, 115, 113, 120, 152, 112 }, + { 152, 179, 64, 126, 170, 118, 46, 70, 95 }, + { 175, 69, 143, 80, 85, 82, 72, 155, 103 }, + { 56, 58, 10, 171, 218, 189, 17, 13, 152 }, + { 114, 26, 17, 163, 44, 195, 21, 10, 173 }, + { 121, 24, 80, 195, 26, 62, 44, 64, 85 }, + { 144, 71, 10, 38, 171, 213, 144, 34, 26 }, + { 170, 46, 55, 19, 136, 160, 33, 206, 71 }, + { 63, 20, 8, 114, 114, 208, 12, 9, 226 }, + { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, + { { 134, 183, 89, 137, 98, 101, 106, 165, 148 }, + { 72, 187, 100, 130, 157, 111, 32, 75, 80 }, + { 66, 102, 167, 99, 74, 62, 40, 234, 128 }, + { 41, 53, 9, 178, 241, 141, 26, 8, 107 }, + { 74, 43, 26, 146, 73, 166, 49, 23, 157 }, + { 65, 38, 105, 160, 51, 52, 31, 115, 128 }, + { 104, 79, 12, 27, 217, 255, 87, 17, 7 }, + { 87, 68, 71, 44, 114, 51, 15, 186, 23 }, + { 47, 41, 14, 110, 182, 183, 21, 17, 194 }, + { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, + { { 88, 88, 147, 150, 42, 46, 45, 196, 205 }, + { 43, 97, 183, 117, 85, 38, 35, 179, 61 }, + { 39, 53, 200, 87, 26, 21, 43, 232, 171 }, + { 56, 34, 51, 104, 114, 102, 29, 93, 77 }, + { 39, 28, 85, 171, 58, 165, 90, 98, 64 }, + { 34, 22, 116, 206, 23, 34, 43, 166, 73 }, + { 107, 54, 32, 26, 51, 1, 81, 43, 31 }, + { 68, 25, 106, 22, 64, 171, 36, 225, 114 }, + { 34, 19, 21, 102, 132, 188, 16, 76, 124 }, + { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, + { { 193, 101, 35, 159, 215, 111, 89, 46, 111 }, + { 60, 148, 31, 172, 219, 228, 21, 18, 111 }, + { 112, 113, 77, 85, 179, 255, 38, 120, 114 }, + { 40, 42, 1, 196, 245, 209, 10, 25, 109 }, + { 88, 43, 29, 140, 166, 213, 37, 43, 154 }, + { 61, 63, 30, 155, 67, 45, 68, 1, 209 }, + { 100, 80, 8, 43, 154, 1, 51, 26, 71 }, + { 142, 78, 78, 16, 255, 128, 34, 197, 171 }, + { 41, 40, 5, 102, 211, 183, 4, 1, 221 }, + { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, + { { 138, 31, 36, 171, 27, 166, 38, 44, 229 }, + { 67, 87, 58, 169, 82, 115, 26, 59, 179 }, + { 63, 59, 90, 180, 59, 166, 93, 73, 154 }, + { 40, 40, 21, 116, 143, 209, 34, 39, 175 }, + { 47, 15, 16, 183, 34, 223, 49, 45, 183 }, + { 46, 17, 33, 183, 6, 98, 15, 32, 183 }, + { 57, 46, 22, 24, 128, 1, 54, 17, 37 }, + { 65, 32, 73, 115, 28, 128, 23, 128, 205 }, + { 40, 3, 9, 115, 51, 192, 18, 6, 223 }, + { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, + { { 104, 55, 44, 218, 9, 54, 53, 130, 226 }, + { 64, 90, 70, 205, 40, 41, 23, 26, 57 }, + { 54, 57, 112, 184, 5, 41, 38, 166, 213 }, + { 30, 34, 26, 133, 152, 116, 10, 32, 134 }, + { 39, 19, 53, 221, 26, 114, 32, 73, 255 }, + { 31, 9, 65, 234, 2, 15, 1, 118, 73 }, + { 75, 32, 12, 51, 192, 255, 160, 43, 51 }, + { 88, 31, 35, 67, 102, 85, 55, 186, 85 }, + { 56, 21, 23, 111, 59, 205, 45, 37, 192 }, + { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, + { { 125, 98, 42, 88, 104, 85, 117, 175, 82 }, + { 95, 84, 53, 89, 128, 100, 113, 101, 45 }, + { 75, 79, 123, 47, 51, 128, 81, 171, 1 }, + { 57, 17, 5, 71, 102, 57, 53, 41, 49 }, + { 38, 33, 13, 121, 57, 73, 26, 1, 85 }, + { 41, 10, 67, 138, 77, 110, 90, 47, 114 }, + { 115, 21, 2, 10, 102, 255, 166, 23, 6 }, + { 101, 29, 16, 10, 85, 128, 101, 196, 26 }, + { 57, 18, 10, 102, 102, 213, 34, 20, 43 }, + { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, + { { 102, 61, 71, 37, 34, 53, 31, 243, 192 }, + { 69, 60, 71, 38, 73, 119, 28, 222, 37 }, + { 68, 45, 128, 34, 1, 47, 11, 245, 171 }, + { 62, 17, 19, 70, 146, 85, 55, 62, 70 }, + { 37, 43, 37, 154, 100, 163, 85, 160, 1 }, + { 63, 9, 92, 136, 28, 64, 32, 201, 85 }, + { 75, 15, 9, 9, 64, 255, 184, 119, 16 }, + { 86, 6, 28, 5, 64, 255, 25, 248, 1 }, + { 56, 8, 17, 132, 137, 255, 55, 116, 128 }, + { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, + { { 164, 50, 31, 137, 154, 133, 25, 35, 218 }, + { 51, 103, 44, 131, 131, 123, 31, 6, 158 }, + { 86, 40, 64, 135, 148, 224, 45, 183, 128 }, + { 22, 26, 17, 131, 240, 154, 14, 1, 209 }, + { 45, 16, 21, 91, 64, 222, 7, 1, 197 }, + { 56, 21, 39, 155, 60, 138, 23, 102, 213 }, + { 83, 12, 13, 54, 192, 255, 68, 47, 28 }, + { 85, 26, 85, 85, 128, 128, 32, 146, 171 }, + { 18, 11, 7, 63, 144, 171, 4, 4, 246 }, + { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, + { { 190, 80, 35, 99, 180, 80, 126, 54, 45 }, + { 85, 126, 47, 87, 176, 51, 41, 20, 32 }, + { 101, 75, 128, 139, 118, 146, 116, 128, 85 }, + { 56, 41, 15, 176, 236, 85, 37, 9, 62 }, + { 71, 30, 17, 119, 118, 255, 17, 18, 138 }, + { 101, 38, 60, 138, 55, 70, 43, 26, 142 }, + { 146, 36, 19, 30, 171, 255, 97, 27, 20 }, + { 138, 45, 61, 62, 219, 1, 81, 188, 64 }, + { 32, 41, 20, 117, 151, 142, 20, 21, 163 }, + { 112, 19, 12, 61, 195, 128, 48, 4, 24 } } +}; + +void VP8ResetProba(VP8Proba* const proba) { + memset(proba->segments_, 255u, sizeof(proba->segments_)); + // proba->bands_[][] is initialized later +} + +static void ParseIntraMode(VP8BitReader* const br, + VP8Decoder* const dec, int mb_x) { + uint8_t* const top = dec->intra_t_ + 4 * mb_x; + uint8_t* const left = dec->intra_l_; + VP8MBData* const block = dec->mb_data_ + mb_x; + + // Note: we don't save segment map (yet), as we don't expect + // to decode more than 1 keyframe. + if (dec->segment_hdr_.update_map_) { + // Hardcoded tree parsing + block->segment_ = !VP8GetBit(br, dec->proba_.segments_[0], "segments") + ? VP8GetBit(br, dec->proba_.segments_[1], "segments") + : VP8GetBit(br, dec->proba_.segments_[2], "segments") + 2; + } else { + block->segment_ = 0; // default for intra + } + if (dec->use_skip_proba_) block->skip_ = VP8GetBit(br, dec->skip_p_, "skip"); + + block->is_i4x4_ = !VP8GetBit(br, 145, "block-size"); + if (!block->is_i4x4_) { + // Hardcoded 16x16 intra-mode decision tree. + const int ymode = + VP8GetBit(br, 156, "pred-modes") ? + (VP8GetBit(br, 128, "pred-modes") ? TM_PRED : H_PRED) : + (VP8GetBit(br, 163, "pred-modes") ? V_PRED : DC_PRED); + block->imodes_[0] = ymode; + memset(top, ymode, 4 * sizeof(*top)); + memset(left, ymode, 4 * sizeof(*left)); + } else { + uint8_t* modes = block->imodes_; + int y; + for (y = 0; y < 4; ++y) { + int ymode = left[y]; + int x; + for (x = 0; x < 4; ++x) { + const uint8_t* const prob = kBModesProba[top[x]][ymode]; +#if (USE_GENERIC_TREE == 1) + // Generic tree-parsing + int i = kYModesIntra4[VP8GetBit(br, prob[0], "pred-modes")]; + while (i > 0) { + i = kYModesIntra4[2 * i + VP8GetBit(br, prob[i], "pred-modes")]; + } + ymode = -i; +#else + // Hardcoded tree parsing + ymode = !VP8GetBit(br, prob[0], "pred-modes") ? B_DC_PRED : + !VP8GetBit(br, prob[1], "pred-modes") ? B_TM_PRED : + !VP8GetBit(br, prob[2], "pred-modes") ? B_VE_PRED : + !VP8GetBit(br, prob[3], "pred-modes") ? + (!VP8GetBit(br, prob[4], "pred-modes") ? B_HE_PRED : + (!VP8GetBit(br, prob[5], "pred-modes") ? B_RD_PRED + : B_VR_PRED)) : + (!VP8GetBit(br, prob[6], "pred-modes") ? B_LD_PRED : + (!VP8GetBit(br, prob[7], "pred-modes") ? B_VL_PRED : + (!VP8GetBit(br, prob[8], "pred-modes") ? B_HD_PRED + : B_HU_PRED)) + ); +#endif // USE_GENERIC_TREE + top[x] = ymode; + } + memcpy(modes, top, 4 * sizeof(*top)); + modes += 4; + left[y] = ymode; + } + } + // Hardcoded UVMode decision tree + block->uvmode_ = !VP8GetBit(br, 142, "pred-modes-uv") ? DC_PRED + : !VP8GetBit(br, 114, "pred-modes-uv") ? V_PRED + : VP8GetBit(br, 183, "pred-modes-uv") ? TM_PRED : H_PRED; +} + +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec) { + int mb_x; + for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) { + ParseIntraMode(br, dec, mb_x); + } + return !dec->br_.eof_; +} + +//------------------------------------------------------------------------------ +// Paragraph 13 + +static const uint8_t + CoeffsUpdateProba[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { + { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } +}; + +// Paragraph 9.9 + +static const uint8_t kBands[16 + 1] = { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 0 // extra entry as sentinel +}; + +void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) { + VP8Proba* const proba = &dec->proba_; + int t, b, c, p; + for (t = 0; t < NUM_TYPES; ++t) { + for (b = 0; b < NUM_BANDS; ++b) { + for (c = 0; c < NUM_CTX; ++c) { + for (p = 0; p < NUM_PROBAS; ++p) { + const int v = + VP8GetBit(br, CoeffsUpdateProba[t][b][c][p], "global-header") ? + VP8GetValue(br, 8, "global-header") : + CoeffsProba0[t][b][c][p]; + proba->bands_[t][b].probas_[c][p] = v; + } + } + } + for (b = 0; b < 16 + 1; ++b) { + proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]]; + } + } + dec->use_skip_proba_ = VP8Get(br, "global-header"); + if (dec->use_skip_proba_) { + dec->skip_p_ = VP8GetValue(br, 8, "global-header"); + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.c new file mode 100644 index 0000000000..2ee8900605 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.c @@ -0,0 +1,728 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/bit_reader_inl_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ + +int WebPGetDecoderVersion(void) { + return (DEC_MAJ_VERSION << 16) | (DEC_MIN_VERSION << 8) | DEC_REV_VERSION; +} + +//------------------------------------------------------------------------------ +// Signature and pointer-to-function for GetCoeffs() variants below. + +typedef int (*GetCoeffsFunc)(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out); +static volatile GetCoeffsFunc GetCoeffs = NULL; + +static void InitGetCoeffs(void); + +//------------------------------------------------------------------------------ +// VP8Decoder + +static void SetOk(VP8Decoder* const dec) { + dec->status_ = VP8_STATUS_OK; + dec->error_msg_ = "OK"; +} + +int VP8InitIoInternal(VP8Io* const io, int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // mismatch error + } + if (io != NULL) { + memset(io, 0, sizeof(*io)); + } + return 1; +} + +VP8Decoder* VP8New(void) { + VP8Decoder* const dec = (VP8Decoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec != NULL) { + SetOk(dec); + WebPGetWorkerInterface()->Init(&dec->worker_); + dec->ready_ = 0; + dec->num_parts_minus_one_ = 0; + InitGetCoeffs(); + } + return dec; +} + +VP8StatusCode VP8Status(VP8Decoder* const dec) { + if (!dec) return VP8_STATUS_INVALID_PARAM; + return dec->status_; +} + +const char* VP8StatusMessage(VP8Decoder* const dec) { + if (dec == NULL) return "no object"; + if (!dec->error_msg_) return "OK"; + return dec->error_msg_; +} + +void VP8Delete(VP8Decoder* const dec) { + if (dec != NULL) { + VP8Clear(dec); + WebPSafeFree(dec); + } +} + +int VP8SetError(VP8Decoder* const dec, + VP8StatusCode error, const char* const msg) { + // VP8_STATUS_SUSPENDED is only meaningful in incremental decoding. + assert(dec->incremental_ || error != VP8_STATUS_SUSPENDED); + // The oldest error reported takes precedence over the new one. + if (dec->status_ == VP8_STATUS_OK) { + dec->status_ = error; + dec->error_msg_ = msg; + dec->ready_ = 0; + } + return 0; +} + +//------------------------------------------------------------------------------ + +int VP8CheckSignature(const uint8_t* const data, size_t data_size) { + return (data_size >= 3 && + data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a); +} + +int VP8GetInfo(const uint8_t* data, size_t data_size, size_t chunk_size, + int* const width, int* const height) { + if (data == NULL || data_size < VP8_FRAME_HEADER_SIZE) { + return 0; // not enough data + } + // check signature + if (!VP8CheckSignature(data + 3, data_size - 3)) { + return 0; // Wrong signature. + } else { + const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16); + const int key_frame = !(bits & 1); + const int w = ((data[7] << 8) | data[6]) & 0x3fff; + const int h = ((data[9] << 8) | data[8]) & 0x3fff; + + if (!key_frame) { // Not a keyframe. + return 0; + } + + if (((bits >> 1) & 7) > 3) { + return 0; // unknown profile + } + if (!((bits >> 4) & 1)) { + return 0; // first frame is invisible! + } + if (((bits >> 5)) >= chunk_size) { // partition_length + return 0; // inconsistent size information. + } + if (w == 0 || h == 0) { + return 0; // We don't support both width and height to be zero. + } + + if (width) { + *width = w; + } + if (height) { + *height = h; + } + + return 1; + } +} + +//------------------------------------------------------------------------------ +// Header parsing + +static void ResetSegmentHeader(VP8SegmentHeader* const hdr) { + assert(hdr != NULL); + hdr->use_segment_ = 0; + hdr->update_map_ = 0; + hdr->absolute_delta_ = 1; + memset(hdr->quantizer_, 0, sizeof(hdr->quantizer_)); + memset(hdr->filter_strength_, 0, sizeof(hdr->filter_strength_)); +} + +// Paragraph 9.3 +static int ParseSegmentHeader(VP8BitReader* br, + VP8SegmentHeader* hdr, VP8Proba* proba) { + assert(br != NULL); + assert(hdr != NULL); + hdr->use_segment_ = VP8Get(br, "global-header"); + if (hdr->use_segment_) { + hdr->update_map_ = VP8Get(br, "global-header"); + if (VP8Get(br, "global-header")) { // update data + int s; + hdr->absolute_delta_ = VP8Get(br, "global-header"); + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + hdr->quantizer_[s] = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 7, "global-header") : 0; + } + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + hdr->filter_strength_[s] = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 6, "global-header") : 0; + } + } + if (hdr->update_map_) { + int s; + for (s = 0; s < MB_FEATURE_TREE_PROBS; ++s) { + proba->segments_[s] = VP8Get(br, "global-header") ? + VP8GetValue(br, 8, "global-header") : 255u; + } + } + } else { + hdr->update_map_ = 0; + } + return !br->eof_; +} + +// Paragraph 9.5 +// If we don't have all the necessary data in 'buf', this function returns +// VP8_STATUS_SUSPENDED in incremental decoding, VP8_STATUS_NOT_ENOUGH_DATA +// otherwise. +// In incremental decoding, this case is not necessarily an error. Still, no +// bitreader is ever initialized to make it possible to read unavailable memory. +// If we don't even have the partitions' sizes, then VP8_STATUS_NOT_ENOUGH_DATA +// is returned, and this is an unrecoverable error. +// If the partitions were positioned ok, VP8_STATUS_OK is returned. +static VP8StatusCode ParsePartitions(VP8Decoder* const dec, + const uint8_t* buf, size_t size) { + VP8BitReader* const br = &dec->br_; + const uint8_t* sz = buf; + const uint8_t* buf_end = buf + size; + const uint8_t* part_start; + size_t size_left = size; + size_t last_part; + size_t p; + + dec->num_parts_minus_one_ = (1 << VP8GetValue(br, 2, "global-header")) - 1; + last_part = dec->num_parts_minus_one_; + if (size < 3 * last_part) { + // we can't even read the sizes with sz[]! That's a failure. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + part_start = buf + last_part * 3; + size_left -= last_part * 3; + for (p = 0; p < last_part; ++p) { + size_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (psize > size_left) psize = size_left; + VP8InitBitReader(dec->parts_ + p, part_start, psize); + part_start += psize; + size_left -= psize; + sz += 3; + } + VP8InitBitReader(dec->parts_ + last_part, part_start, size_left); + if (part_start < buf_end) return VP8_STATUS_OK; + return dec->incremental_ + ? VP8_STATUS_SUSPENDED // Init is ok, but there's not enough data + : VP8_STATUS_NOT_ENOUGH_DATA; +} + +// Paragraph 9.4 +static int ParseFilterHeader(VP8BitReader* br, VP8Decoder* const dec) { + VP8FilterHeader* const hdr = &dec->filter_hdr_; + hdr->simple_ = VP8Get(br, "global-header"); + hdr->level_ = VP8GetValue(br, 6, "global-header"); + hdr->sharpness_ = VP8GetValue(br, 3, "global-header"); + hdr->use_lf_delta_ = VP8Get(br, "global-header"); + if (hdr->use_lf_delta_) { + if (VP8Get(br, "global-header")) { // update lf-delta? + int i; + for (i = 0; i < NUM_REF_LF_DELTAS; ++i) { + if (VP8Get(br, "global-header")) { + hdr->ref_lf_delta_[i] = VP8GetSignedValue(br, 6, "global-header"); + } + } + for (i = 0; i < NUM_MODE_LF_DELTAS; ++i) { + if (VP8Get(br, "global-header")) { + hdr->mode_lf_delta_[i] = VP8GetSignedValue(br, 6, "global-header"); + } + } + } + } + dec->filter_type_ = (hdr->level_ == 0) ? 0 : hdr->simple_ ? 1 : 2; + return !br->eof_; +} + +// Topmost call +int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { + const uint8_t* buf; + size_t buf_size; + VP8FrameHeader* frm_hdr; + VP8PictureHeader* pic_hdr; + VP8BitReader* br; + VP8StatusCode status; + + if (dec == NULL) { + return 0; + } + SetOk(dec); + if (io == NULL) { + return VP8SetError(dec, VP8_STATUS_INVALID_PARAM, + "null VP8Io passed to VP8GetHeaders()"); + } + buf = io->data; + buf_size = io->data_size; + if (buf_size < 4) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Truncated header."); + } + + // Paragraph 9.1 + { + const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16); + frm_hdr = &dec->frm_hdr_; + frm_hdr->key_frame_ = !(bits & 1); + frm_hdr->profile_ = (bits >> 1) & 7; + frm_hdr->show_ = (bits >> 4) & 1; + frm_hdr->partition_length_ = (bits >> 5); + if (frm_hdr->profile_ > 3) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Incorrect keyframe parameters."); + } + if (!frm_hdr->show_) { + return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, + "Frame not displayable."); + } + buf += 3; + buf_size -= 3; + } + + pic_hdr = &dec->pic_hdr_; + if (frm_hdr->key_frame_) { + // Paragraph 9.2 + if (buf_size < 7) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "cannot parse picture header"); + } + if (!VP8CheckSignature(buf, buf_size)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Bad code word"); + } + pic_hdr->width_ = ((buf[4] << 8) | buf[3]) & 0x3fff; + pic_hdr->xscale_ = buf[4] >> 6; // ratio: 1, 5/4 5/3 or 2 + pic_hdr->height_ = ((buf[6] << 8) | buf[5]) & 0x3fff; + pic_hdr->yscale_ = buf[6] >> 6; + buf += 7; + buf_size -= 7; + + dec->mb_w_ = (pic_hdr->width_ + 15) >> 4; + dec->mb_h_ = (pic_hdr->height_ + 15) >> 4; + + // Setup default output area (can be later modified during io->setup()) + io->width = pic_hdr->width_; + io->height = pic_hdr->height_; + // IMPORTANT! use some sane dimensions in crop_* and scaled_* fields. + // So they can be used interchangeably without always testing for + // 'use_cropping'. + io->use_cropping = 0; + io->crop_top = 0; + io->crop_left = 0; + io->crop_right = io->width; + io->crop_bottom = io->height; + io->use_scaling = 0; + io->scaled_width = io->width; + io->scaled_height = io->height; + + io->mb_w = io->width; // for soundness + io->mb_h = io->height; // ditto + + VP8ResetProba(&dec->proba_); + ResetSegmentHeader(&dec->segment_hdr_); + } + + // Check if we have all the partition #0 available, and initialize dec->br_ + // to read this partition (and this partition only). + if (frm_hdr->partition_length_ > buf_size) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "bad partition length"); + } + + br = &dec->br_; + VP8InitBitReader(br, buf, frm_hdr->partition_length_); + buf += frm_hdr->partition_length_; + buf_size -= frm_hdr->partition_length_; + + if (frm_hdr->key_frame_) { + pic_hdr->colorspace_ = VP8Get(br, "global-header"); + pic_hdr->clamp_type_ = VP8Get(br, "global-header"); + } + if (!ParseSegmentHeader(br, &dec->segment_hdr_, &dec->proba_)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "cannot parse segment header"); + } + // Filter specs + if (!ParseFilterHeader(br, dec)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "cannot parse filter header"); + } + status = ParsePartitions(dec, buf, buf_size); + if (status != VP8_STATUS_OK) { + return VP8SetError(dec, status, "cannot parse partitions"); + } + + // quantizer change + VP8ParseQuant(dec); + + // Frame buffer marking + if (!frm_hdr->key_frame_) { + return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, + "Not a key frame."); + } + + VP8Get(br, "global-header"); // ignore the value of update_proba_ + + VP8ParseProba(br, dec); + + // sanitized state + dec->ready_ = 1; + return 1; +} + +//------------------------------------------------------------------------------ +// Residual decoding (Paragraph 13.2 / 13.3) + +static const uint8_t kCat3[] = { 173, 148, 140, 0 }; +static const uint8_t kCat4[] = { 176, 155, 140, 135, 0 }; +static const uint8_t kCat5[] = { 180, 157, 141, 134, 130, 0 }; +static const uint8_t kCat6[] = + { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0 }; +static const uint8_t* const kCat3456[] = { kCat3, kCat4, kCat5, kCat6 }; +static const uint8_t kZigzag[16] = { + 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 +}; + +// See section 13-2: https://datatracker.ietf.org/doc/html/rfc6386#section-13.2 +static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) { + int v; + if (!VP8GetBit(br, p[3], "coeffs")) { + if (!VP8GetBit(br, p[4], "coeffs")) { + v = 2; + } else { + v = 3 + VP8GetBit(br, p[5], "coeffs"); + } + } else { + if (!VP8GetBit(br, p[6], "coeffs")) { + if (!VP8GetBit(br, p[7], "coeffs")) { + v = 5 + VP8GetBit(br, 159, "coeffs"); + } else { + v = 7 + 2 * VP8GetBit(br, 165, "coeffs"); + v += VP8GetBit(br, 145, "coeffs"); + } + } else { + const uint8_t* tab; + const int bit1 = VP8GetBit(br, p[8], "coeffs"); + const int bit0 = VP8GetBit(br, p[9 + bit1], "coeffs"); + const int cat = 2 * bit1 + bit0; + v = 0; + for (tab = kCat3456[cat]; *tab; ++tab) { + v += v + VP8GetBit(br, *tab, "coeffs"); + } + v += 3 + (8 << cat); + } + } + return v; +} + +// Returns the position of the last non-zero coeff plus one +static int GetCoeffsFast(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas_[ctx]; + for (; n < 16; ++n) { + if (!VP8GetBit(br, p[0], "coeffs")) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBit(br, p[1], "coeffs")) { // sequence of zero coeffs + p = prob[++n]->probas_[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0]; + int v; + if (!VP8GetBit(br, p[2], "coeffs")) { + v = 1; + p = p_ctx[1]; + } else { + v = GetLargeValue(br, p); + p = p_ctx[2]; + } + out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0]; + } + } + return 16; +} + +// This version of GetCoeffs() uses VP8GetBitAlt() which is an alternate version +// of VP8GetBitAlt() targeting specific platforms. +static int GetCoeffsAlt(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas_[ctx]; + for (; n < 16; ++n) { + if (!VP8GetBitAlt(br, p[0], "coeffs")) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBitAlt(br, p[1], "coeffs")) { // sequence of zero coeffs + p = prob[++n]->probas_[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0]; + int v; + if (!VP8GetBitAlt(br, p[2], "coeffs")) { + v = 1; + p = p_ctx[1]; + } else { + v = GetLargeValue(br, p); + p = p_ctx[2]; + } + out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0]; + } + } + return 16; +} + +extern VP8CPUInfo VP8GetCPUInfo; + +WEBP_DSP_INIT_FUNC(InitGetCoeffs) { + if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) { + GetCoeffs = GetCoeffsAlt; + } else { + GetCoeffs = GetCoeffsFast; + } +} + +static WEBP_INLINE uint32_t NzCodeBits(uint32_t nz_coeffs, int nz, int dc_nz) { + nz_coeffs <<= 2; + nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz; + return nz_coeffs; +} + +static int ParseResiduals(VP8Decoder* const dec, + VP8MB* const mb, VP8BitReader* const token_br) { + const VP8BandProbas* (* const bands)[16 + 1] = dec->proba_.bands_ptr_; + const VP8BandProbas* const * ac_proba; + VP8MBData* const block = dec->mb_data_ + dec->mb_x_; + const VP8QuantMatrix* const q = &dec->dqm_[block->segment_]; + int16_t* dst = block->coeffs_; + VP8MB* const left_mb = dec->mb_info_ - 1; + uint8_t tnz, lnz; + uint32_t non_zero_y = 0; + uint32_t non_zero_uv = 0; + int x, y, ch; + uint32_t out_t_nz, out_l_nz; + int first; + + memset(dst, 0, 384 * sizeof(*dst)); + if (!block->is_i4x4_) { // parse DC + int16_t dc[16] = { 0 }; + const int ctx = mb->nz_dc_ + left_mb->nz_dc_; + const int nz = GetCoeffs(token_br, bands[1], ctx, q->y2_mat_, 0, dc); + mb->nz_dc_ = left_mb->nz_dc_ = (nz > 0); + if (nz > 1) { // more than just the DC -> perform the full transform + VP8TransformWHT(dc, dst); + } else { // only DC is non-zero -> inlined simplified transform + int i; + const int dc0 = (dc[0] + 3) >> 3; + for (i = 0; i < 16 * 16; i += 16) dst[i] = dc0; + } + first = 1; + ac_proba = bands[0]; + } else { + first = 0; + ac_proba = bands[3]; + } + + tnz = mb->nz_ & 0x0f; + lnz = left_mb->nz_ & 0x0f; + for (y = 0; y < 4; ++y) { + int l = lnz & 1; + uint32_t nz_coeffs = 0; + for (x = 0; x < 4; ++x) { + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, ac_proba, ctx, q->y1_mat_, first, dst); + l = (nz > first); + tnz = (tnz >> 1) | (l << 7); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); + dst += 16; + } + tnz >>= 4; + lnz = (lnz >> 1) | (l << 7); + non_zero_y = (non_zero_y << 8) | nz_coeffs; + } + out_t_nz = tnz; + out_l_nz = lnz >> 4; + + for (ch = 0; ch < 4; ch += 2) { + uint32_t nz_coeffs = 0; + tnz = mb->nz_ >> (4 + ch); + lnz = left_mb->nz_ >> (4 + ch); + for (y = 0; y < 2; ++y) { + int l = lnz & 1; + for (x = 0; x < 2; ++x) { + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, bands[2], ctx, q->uv_mat_, 0, dst); + l = (nz > 0); + tnz = (tnz >> 1) | (l << 3); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); + dst += 16; + } + tnz >>= 2; + lnz = (lnz >> 1) | (l << 5); + } + // Note: we don't really need the per-4x4 details for U/V blocks. + non_zero_uv |= nz_coeffs << (4 * ch); + out_t_nz |= (tnz << 4) << ch; + out_l_nz |= (lnz & 0xf0) << ch; + } + mb->nz_ = out_t_nz; + left_mb->nz_ = out_l_nz; + + block->non_zero_y_ = non_zero_y; + block->non_zero_uv_ = non_zero_uv; + + // We look at the mode-code of each block and check if some blocks have less + // than three non-zero coeffs (code < 2). This is to avoid dithering flat and + // empty blocks. + block->dither_ = (non_zero_uv & 0xaaaa) ? 0 : q->dither_; + + return !(non_zero_y | non_zero_uv); // will be used for further optimization +} + +//------------------------------------------------------------------------------ +// Main loop + +int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br) { + VP8MB* const left = dec->mb_info_ - 1; + VP8MB* const mb = dec->mb_info_ + dec->mb_x_; + VP8MBData* const block = dec->mb_data_ + dec->mb_x_; + int skip = dec->use_skip_proba_ ? block->skip_ : 0; + + if (!skip) { + skip = ParseResiduals(dec, mb, token_br); + } else { + left->nz_ = mb->nz_ = 0; + if (!block->is_i4x4_) { + left->nz_dc_ = mb->nz_dc_ = 0; + } + block->non_zero_y_ = 0; + block->non_zero_uv_ = 0; + block->dither_ = 0; + } + + if (dec->filter_type_ > 0) { // store filter info + VP8FInfo* const finfo = dec->f_info_ + dec->mb_x_; + *finfo = dec->fstrengths_[block->segment_][block->is_i4x4_]; + finfo->f_inner_ |= !skip; + } + + return !token_br->eof_; +} + +void VP8InitScanline(VP8Decoder* const dec) { + VP8MB* const left = dec->mb_info_ - 1; + left->nz_ = 0; + left->nz_dc_ = 0; + memset(dec->intra_l_, B_DC_PRED, sizeof(dec->intra_l_)); + dec->mb_x_ = 0; +} + +static int ParseFrame(VP8Decoder* const dec, VP8Io* io) { + for (dec->mb_y_ = 0; dec->mb_y_ < dec->br_mb_y_; ++dec->mb_y_) { + // Parse bitstream for this row. + VP8BitReader* const token_br = + &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_]; + if (!VP8ParseIntraModeRow(&dec->br_, dec)) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Premature end-of-partition0 encountered."); + } + for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) { + if (!VP8DecodeMB(dec, token_br)) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Premature end-of-file encountered."); + } + } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. + if (!VP8ProcessRow(dec, io)) { + return VP8SetError(dec, VP8_STATUS_USER_ABORT, "Output aborted."); + } + } + if (dec->mt_method_ > 0) { + if (!WebPGetWorkerInterface()->Sync(&dec->worker_)) return 0; + } + + return 1; +} + +// Main entry point +int VP8Decode(VP8Decoder* const dec, VP8Io* const io) { + int ok = 0; + if (dec == NULL) { + return 0; + } + if (io == NULL) { + return VP8SetError(dec, VP8_STATUS_INVALID_PARAM, + "NULL VP8Io parameter in VP8Decode()."); + } + + if (!dec->ready_) { + if (!VP8GetHeaders(dec, io)) { + return 0; + } + } + assert(dec->ready_); + + // Finish setting up the decoding parameter. Will call io->setup(). + ok = (VP8EnterCritical(dec, io) == VP8_STATUS_OK); + if (ok) { // good to go. + // Will allocate memory and prepare everything. + if (ok) ok = VP8InitFrame(dec, io); + + // Main decoding loop + if (ok) ok = ParseFrame(dec, io); + + // Exit. + ok &= VP8ExitCritical(dec, io); + } + + if (!ok) { + VP8Clear(dec); + return 0; + } + + dec->ready_ = 0; + return ok; +} + +void VP8Clear(VP8Decoder* const dec) { + if (dec == NULL) { + return; + } + WebPGetWorkerInterface()->End(&dec->worker_); + WebPDeallocateAlphaMemory(dec); + WebPSafeFree(dec->mem_); + dec->mem_ = NULL; + dec->mem_size_ = 0; + memset(&dec->br_, 0, sizeof(dec->br_)); + dec->ready_ = 0; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.h new file mode 100644 index 0000000000..91fe104093 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8_dec.h @@ -0,0 +1,184 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Low-level API for VP8 decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_VP8_DEC_H_ +#define WEBP_DEC_VP8_DEC_H_ + +#include "src/webp/decode.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Lower-level API +// +// These functions provide fine-grained control of the decoding process. +// The call flow should resemble: +// +// VP8Io io; +// VP8InitIo(&io); +// io.data = data; +// io.data_size = size; +// /* customize io's functions (setup()/put()/teardown()) if needed. */ +// +// VP8Decoder* dec = VP8New(); +// int ok = VP8Decode(dec, &io); +// if (!ok) printf("Error: %s\n", VP8StatusMessage(dec)); +// VP8Delete(dec); +// return ok; + +// Input / Output +typedef struct VP8Io VP8Io; +typedef int (*VP8IoPutHook)(const VP8Io* io); +typedef int (*VP8IoSetupHook)(VP8Io* io); +typedef void (*VP8IoTeardownHook)(const VP8Io* io); + +struct VP8Io { + // set by VP8GetHeaders() + int width, height; // picture dimensions, in pixels (invariable). + // These are the original, uncropped dimensions. + // The actual area passed to put() is stored + // in mb_w / mb_h fields. + + // set before calling put() + int mb_y; // position of the current rows (in pixels) + int mb_w; // number of columns in the sample + int mb_h; // number of rows in the sample + const uint8_t* y, *u, *v; // rows to copy (in yuv420 format) + int y_stride; // row stride for luma + int uv_stride; // row stride for chroma + + void* opaque; // user data + + // called when fresh samples are available. Currently, samples are in + // YUV420 format, and can be up to width x 24 in size (depending on the + // in-loop filtering level, e.g.). Should return false in case of error + // or abort request. The actual size of the area to update is mb_w x mb_h + // in size, taking cropping into account. + VP8IoPutHook put; + + // called just before starting to decode the blocks. + // Must return false in case of setup error, true otherwise. If false is + // returned, teardown() will NOT be called. But if the setup succeeded + // and true is returned, then teardown() will always be called afterward. + VP8IoSetupHook setup; + + // Called just after block decoding is finished (or when an error occurred + // during put()). Is NOT called if setup() failed. + VP8IoTeardownHook teardown; + + // this is a recommendation for the user-side yuv->rgb converter. This flag + // is set when calling setup() hook and can be overwritten by it. It then + // can be taken into consideration during the put() method. + int fancy_upsampling; + + // Input buffer. + size_t data_size; + const uint8_t* data; + + // If true, in-loop filtering will not be performed even if present in the + // bitstream. Switching off filtering may speed up decoding at the expense + // of more visible blocking. Note that output will also be non-compliant + // with the VP8 specifications. + int bypass_filtering; + + // Cropping parameters. + int use_cropping; + int crop_left, crop_right, crop_top, crop_bottom; + + // Scaling parameters. + int use_scaling; + int scaled_width, scaled_height; + + // If non NULL, pointer to the alpha data (if present) corresponding to the + // start of the current row (That is: it is pre-offset by mb_y and takes + // cropping into account). + const uint8_t* a; +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD int VP8InitIoInternal(VP8Io* const, int); + +// Set the custom IO function pointers and user-data. The setter for IO hooks +// should be called before initiating incremental decoding. Returns true if +// WebPIDecoder object is successfully modified, false otherwise. +WEBP_NODISCARD int WebPISetIOHooks(WebPIDecoder* const idec, VP8IoPutHook put, + VP8IoSetupHook setup, + VP8IoTeardownHook teardown, void* user_data); + +// Main decoding object. This is an opaque structure. +typedef struct VP8Decoder VP8Decoder; + +// Create a new decoder object. +VP8Decoder* VP8New(void); + +// Must be called to make sure 'io' is initialized properly. +// Returns false in case of version mismatch. Upon such failure, no other +// decoding function should be called (VP8Decode, VP8GetHeaders, ...) +WEBP_NODISCARD static WEBP_INLINE int VP8InitIo(VP8Io* const io) { + return VP8InitIoInternal(io, WEBP_DECODER_ABI_VERSION); +} + +// Decode the VP8 frame header. Returns true if ok. +// Note: 'io->data' must be pointing to the start of the VP8 frame header. +WEBP_NODISCARD int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io); + +// Decode a picture. Will call VP8GetHeaders() if it wasn't done already. +// Returns false in case of error. +WEBP_NODISCARD int VP8Decode(VP8Decoder* const dec, VP8Io* const io); + +// Return current status of the decoder: +VP8StatusCode VP8Status(VP8Decoder* const dec); + +// return readable string corresponding to the last status. +const char* VP8StatusMessage(VP8Decoder* const dec); + +// Resets the decoder in its initial state, reclaiming memory. +// Not a mandatory call between calls to VP8Decode(). +void VP8Clear(VP8Decoder* const dec); + +// Destroy the decoder object. +void VP8Delete(VP8Decoder* const dec); + +//------------------------------------------------------------------------------ +// Miscellaneous VP8/VP8L bitstream probing functions. + +// Returns true if the next 3 bytes in data contain the VP8 signature. +WEBP_EXTERN int VP8CheckSignature(const uint8_t* const data, size_t data_size); + +// Validates the VP8 data-header and retrieves basic header information viz +// width and height. Returns 0 in case of formatting error. *width/*height +// can be passed NULL. +WEBP_EXTERN int VP8GetInfo( + const uint8_t* data, + size_t data_size, // data available so far + size_t chunk_size, // total data size expected in the chunk + int* const width, int* const height); + +// Returns true if the next byte(s) in data is a VP8L signature. +WEBP_EXTERN int VP8LCheckSignature(const uint8_t* const data, size_t size); + +// Validates the VP8L data-header and retrieves basic header information viz +// width, height and alpha. Returns 0 in case of formatting error. +// width/height/has_alpha can be passed NULL. +WEBP_EXTERN int VP8LGetInfo( + const uint8_t* data, size_t data_size, // data available so far + int* const width, int* const height, int* const has_alpha); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8i_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8i_dec.h new file mode 100644 index 0000000000..cb21d475ae --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8i_dec.h @@ -0,0 +1,322 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// VP8 decoder: internal header. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_VP8I_DEC_H_ +#define WEBP_DEC_VP8I_DEC_H_ + +#include // for memcpy() +#include "src/dec/common_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/random_utils.h" +#include "src/utils/thread_utils.h" +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Various defines and enums + +// version numbers +#define DEC_MAJ_VERSION 1 +#define DEC_MIN_VERSION 4 +#define DEC_REV_VERSION 0 + +// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). +// Constraints are: We need to store one 16x16 block of luma samples (y), +// and two 8x8 chroma blocks (u/v). These are better be 16-bytes aligned, +// in order to be SIMD-friendly. We also need to store the top, left and +// top-left samples (from previously decoded blocks), along with four +// extra top-right samples for luma (intra4x4 prediction only). +// One possible layout is, using 32 * (17 + 9) bytes: +// +// .+------ <- only 1 pixel high +// .|yyyyt. +// .|yyyyt. +// .|yyyyt. +// .|yyyy.. +// .+--.+-- <- only 1 pixel high +// .|uu.|vv +// .|uu.|vv +// +// Every character is a 4x4 block, with legend: +// '.' = unused +// 'y' = y-samples 'u' = u-samples 'v' = u-samples +// '|' = left sample, '-' = top sample, '+' = top-left sample +// 't' = extra top-right sample for 4x4 modes +#define YUV_SIZE (BPS * 17 + BPS * 9) +#define Y_OFF (BPS * 1 + 8) +#define U_OFF (Y_OFF + BPS * 16 + BPS) +#define V_OFF (U_OFF + 16) + +// minimal width under which lossy multi-threading is always disabled +#define MIN_WIDTH_FOR_THREADS 512 + +//------------------------------------------------------------------------------ +// Headers + +typedef struct { + uint8_t key_frame_; + uint8_t profile_; + uint8_t show_; + uint32_t partition_length_; +} VP8FrameHeader; + +typedef struct { + uint16_t width_; + uint16_t height_; + uint8_t xscale_; + uint8_t yscale_; + uint8_t colorspace_; // 0 = YCbCr + uint8_t clamp_type_; +} VP8PictureHeader; + +// segment features +typedef struct { + int use_segment_; + int update_map_; // whether to update the segment map or not + int absolute_delta_; // absolute or delta values for quantizer and filter + int8_t quantizer_[NUM_MB_SEGMENTS]; // quantization changes + int8_t filter_strength_[NUM_MB_SEGMENTS]; // filter strength for segments +} VP8SegmentHeader; + +// probas associated to one of the contexts +typedef uint8_t VP8ProbaArray[NUM_PROBAS]; + +typedef struct { // all the probas associated to one band + VP8ProbaArray probas_[NUM_CTX]; +} VP8BandProbas; + +// Struct collecting all frame-persistent probabilities. +typedef struct { + uint8_t segments_[MB_FEATURE_TREE_PROBS]; + // Type: 0:Intra16-AC 1:Intra16-DC 2:Chroma 3:Intra4 + VP8BandProbas bands_[NUM_TYPES][NUM_BANDS]; + const VP8BandProbas* bands_ptr_[NUM_TYPES][16 + 1]; +} VP8Proba; + +// Filter parameters +typedef struct { + int simple_; // 0=complex, 1=simple + int level_; // [0..63] + int sharpness_; // [0..7] + int use_lf_delta_; + int ref_lf_delta_[NUM_REF_LF_DELTAS]; + int mode_lf_delta_[NUM_MODE_LF_DELTAS]; +} VP8FilterHeader; + +//------------------------------------------------------------------------------ +// Informations about the macroblocks. + +typedef struct { // filter specs + uint8_t f_limit_; // filter limit in [3..189], or 0 if no filtering + uint8_t f_ilevel_; // inner limit in [1..63] + uint8_t f_inner_; // do inner filtering? + uint8_t hev_thresh_; // high edge variance threshold in [0..2] +} VP8FInfo; + +typedef struct { // Top/Left Contexts used for syntax-parsing + uint8_t nz_; // non-zero AC/DC coeffs (4bit for luma + 4bit for chroma) + uint8_t nz_dc_; // non-zero DC coeff (1bit) +} VP8MB; + +// Dequantization matrices +typedef int quant_t[2]; // [DC / AC]. Can be 'uint16_t[2]' too (~slower). +typedef struct { + quant_t y1_mat_, y2_mat_, uv_mat_; + + int uv_quant_; // U/V quantizer value + int dither_; // dithering amplitude (0 = off, max=255) +} VP8QuantMatrix; + +// Data needed to reconstruct a macroblock +typedef struct { + int16_t coeffs_[384]; // 384 coeffs = (16+4+4) * 4*4 + uint8_t is_i4x4_; // true if intra4x4 + uint8_t imodes_[16]; // one 16x16 mode (#0) or sixteen 4x4 modes + uint8_t uvmode_; // chroma prediction mode + // bit-wise info about the content of each sub-4x4 blocks (in decoding order). + // Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + // code=0 -> no coefficient + // code=1 -> only DC + // code=2 -> first three coefficients are non-zero + // code=3 -> more than three coefficients are non-zero + // This allows to call specialized transform functions. + uint32_t non_zero_y_; + uint32_t non_zero_uv_; + uint8_t dither_; // local dithering strength (deduced from non_zero_*) + uint8_t skip_; + uint8_t segment_; +} VP8MBData; + +// Persistent information needed by the parallel processing +typedef struct { + int id_; // cache row to process (in [0..2]) + int mb_y_; // macroblock position of the row + int filter_row_; // true if row-filtering is needed + VP8FInfo* f_info_; // filter strengths (swapped with dec->f_info_) + VP8MBData* mb_data_; // reconstruction data (swapped with dec->mb_data_) + VP8Io io_; // copy of the VP8Io to pass to put() +} VP8ThreadContext; + +// Saved top samples, per macroblock. Fits into a cache-line. +typedef struct { + uint8_t y[16], u[8], v[8]; +} VP8TopSamples; + +//------------------------------------------------------------------------------ +// VP8Decoder: the main opaque structure handed over to user + +struct VP8Decoder { + VP8StatusCode status_; + int ready_; // true if ready to decode a picture with VP8Decode() + const char* error_msg_; // set when status_ is not OK. + + // Main data source + VP8BitReader br_; + int incremental_; // if true, incremental decoding is expected + + // headers + VP8FrameHeader frm_hdr_; + VP8PictureHeader pic_hdr_; + VP8FilterHeader filter_hdr_; + VP8SegmentHeader segment_hdr_; + + // Worker + WebPWorker worker_; + int mt_method_; // multi-thread method: 0=off, 1=[parse+recon][filter] + // 2=[parse][recon+filter] + int cache_id_; // current cache row + int num_caches_; // number of cached rows of 16 pixels (1, 2 or 3) + VP8ThreadContext thread_ctx_; // Thread context + + // dimension, in macroblock units. + int mb_w_, mb_h_; + + // Macroblock to process/filter, depending on cropping and filter_type. + int tl_mb_x_, tl_mb_y_; // top-left MB that must be in-loop filtered + int br_mb_x_, br_mb_y_; // last bottom-right MB that must be decoded + + // number of partitions minus one. + uint32_t num_parts_minus_one_; + // per-partition boolean decoders. + VP8BitReader parts_[MAX_NUM_PARTITIONS]; + + // Dithering strength, deduced from decoding options + int dither_; // whether to use dithering or not + VP8Random dithering_rg_; // random generator for dithering + + // dequantization (one set of DC/AC dequant factor per segment) + VP8QuantMatrix dqm_[NUM_MB_SEGMENTS]; + + // probabilities + VP8Proba proba_; + int use_skip_proba_; + uint8_t skip_p_; + + // Boundary data cache and persistent buffers. + uint8_t* intra_t_; // top intra modes values: 4 * mb_w_ + uint8_t intra_l_[4]; // left intra modes values + + VP8TopSamples* yuv_t_; // top y/u/v samples + + VP8MB* mb_info_; // contextual macroblock info (mb_w_ + 1) + VP8FInfo* f_info_; // filter strength info + uint8_t* yuv_b_; // main block for Y/U/V (size = YUV_SIZE) + + uint8_t* cache_y_; // macroblock row for storing unfiltered samples + uint8_t* cache_u_; + uint8_t* cache_v_; + int cache_y_stride_; + int cache_uv_stride_; + + // main memory chunk for the above data. Persistent. + void* mem_; + size_t mem_size_; + + // Per macroblock non-persistent infos. + int mb_x_, mb_y_; // current position, in macroblock units + VP8MBData* mb_data_; // parsed reconstruction data + + // Filtering side-info + int filter_type_; // 0=off, 1=simple, 2=complex + VP8FInfo fstrengths_[NUM_MB_SEGMENTS][2]; // precalculated per-segment/type + + // Alpha + struct ALPHDecoder* alph_dec_; // alpha-plane decoder object + const uint8_t* alpha_data_; // compressed alpha data (if present) + size_t alpha_data_size_; + int is_alpha_decoded_; // true if alpha_data_ is decoded in alpha_plane_ + uint8_t* alpha_plane_mem_; // memory allocated for alpha_plane_ + uint8_t* alpha_plane_; // output. Persistent, contains the whole data. + const uint8_t* alpha_prev_line_; // last decoded alpha row (or NULL) + int alpha_dithering_; // derived from decoding options (0=off, 100=full) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// in vp8.c +int VP8SetError(VP8Decoder* const dec, + VP8StatusCode error, const char* const msg); + +// in tree.c +void VP8ResetProba(VP8Proba* const proba); +void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec); +// parses one row of intra mode data in partition 0, returns !eof +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec); + +// in quant.c +void VP8ParseQuant(VP8Decoder* const dec); + +// in frame.c +WEBP_NODISCARD int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io); +// Call io->setup() and finish setting up scan parameters. +// After this call returns, one must always call VP8ExitCritical() with the +// same parameters. Both functions should be used in pair. Returns VP8_STATUS_OK +// if ok, otherwise sets and returns the error status on *dec. +VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io); +// Must always be called in pair with VP8EnterCritical(). +// Returns false in case of error. +WEBP_NODISCARD int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io); +// Return the multi-threading method to use (0=off), depending +// on options and bitstream size. Only for lossy decoding. +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height); +// Initialize dithering post-process if needed. +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec); +// Process the last decoded row (filtering + output). +WEBP_NODISCARD int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io); +// To be called at the start of a new scanline, to initialize predictors. +void VP8InitScanline(VP8Decoder* const dec); +// Decode one macroblock. Returns false if there is not enough data. +WEBP_NODISCARD int VP8DecodeMB(VP8Decoder* const dec, + VP8BitReader* const token_br); + +// in alpha.c +const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, + int row, int num_rows); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8I_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8l_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8l_dec.c new file mode 100644 index 0000000000..11c00ea964 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8l_dec.c @@ -0,0 +1,1778 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the decoder +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) + +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dsp/dsp.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" +#include "src/dsp/yuv.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/huffman_utils.h" +#include "src/utils/utils.h" + +#define NUM_ARGB_CACHE_ROWS 16 + +static const int kCodeLengthLiterals = 16; +static const int kCodeLengthRepeatCode = 16; +static const uint8_t kCodeLengthExtraBits[3] = { 2, 3, 7 }; +static const uint8_t kCodeLengthRepeatOffsets[3] = { 3, 3, 11 }; + +// ----------------------------------------------------------------------------- +// Five Huffman codes are used at each meta code: +// 1. green + length prefix codes + color cache codes, +// 2. alpha, +// 3. red, +// 4. blue, and, +// 5. distance prefix codes. +typedef enum { + GREEN = 0, + RED = 1, + BLUE = 2, + ALPHA = 3, + DIST = 4 +} HuffIndex; + +static const uint16_t kAlphabetSize[HUFFMAN_CODES_PER_META_CODE] = { + NUM_LITERAL_CODES + NUM_LENGTH_CODES, + NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, + NUM_DISTANCE_CODES +}; + +static const uint8_t kLiteralMap[HUFFMAN_CODES_PER_META_CODE] = { + 0, 1, 1, 1, 0 +}; + +#define NUM_CODE_LENGTH_CODES 19 +static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = { + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +}; + +#define CODE_TO_PLANE_CODES 120 +static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 +}; + +// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha +// and distance alphabets are constant (256 for red, blue and alpha, 40 for +// distance) and lookup table sizes for them in worst case are 630 and 410 +// respectively. Size of green alphabet depends on color cache size and is equal +// to 256 (green component values) + 24 (length prefix values) +// + color_cache_size (between 0 and 2048). +// All values computed for 8-bit first level lookup with Mark Adler's tool: +// https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c +#define FIXED_TABLE_SIZE (630 * 3 + 410) +static const uint16_t kTableSize[12] = { + FIXED_TABLE_SIZE + 654, + FIXED_TABLE_SIZE + 656, + FIXED_TABLE_SIZE + 658, + FIXED_TABLE_SIZE + 662, + FIXED_TABLE_SIZE + 670, + FIXED_TABLE_SIZE + 686, + FIXED_TABLE_SIZE + 718, + FIXED_TABLE_SIZE + 782, + FIXED_TABLE_SIZE + 912, + FIXED_TABLE_SIZE + 1168, + FIXED_TABLE_SIZE + 1680, + FIXED_TABLE_SIZE + 2704 +}; + +static int VP8LSetError(VP8LDecoder* const dec, VP8StatusCode error) { + // The oldest error reported takes precedence over the new one. + if (dec->status_ == VP8_STATUS_OK || dec->status_ == VP8_STATUS_SUSPENDED) { + dec->status_ = error; + } + return 0; +} + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data); + +//------------------------------------------------------------------------------ + +int VP8LCheckSignature(const uint8_t* const data, size_t size) { + return (size >= VP8L_FRAME_HEADER_SIZE && + data[0] == VP8L_MAGIC_BYTE && + (data[4] >> 5) == 0); // version +} + +static int ReadImageInfo(VP8LBitReader* const br, + int* const width, int* const height, + int* const has_alpha) { + if (VP8LReadBits(br, 8) != VP8L_MAGIC_BYTE) return 0; + *width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *has_alpha = VP8LReadBits(br, 1); + if (VP8LReadBits(br, VP8L_VERSION_BITS) != 0) return 0; + return !br->eos_; +} + +int VP8LGetInfo(const uint8_t* data, size_t data_size, + int* const width, int* const height, int* const has_alpha) { + if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) { + return 0; // not enough data + } else if (!VP8LCheckSignature(data, data_size)) { + return 0; // bad signature + } else { + int w, h, a; + VP8LBitReader br; + VP8LInitBitReader(&br, data, data_size); + if (!ReadImageInfo(&br, &w, &h, &a)) { + return 0; + } + if (width != NULL) *width = w; + if (height != NULL) *height = h; + if (has_alpha != NULL) *has_alpha = a; + return 1; + } +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetCopyDistance(int distance_symbol, + VP8LBitReader* const br) { + int extra_bits, offset; + if (distance_symbol < 4) { + return distance_symbol + 1; + } + extra_bits = (distance_symbol - 2) >> 1; + offset = (2 + (distance_symbol & 1)) << extra_bits; + return offset + VP8LReadBits(br, extra_bits) + 1; +} + +static WEBP_INLINE int GetCopyLength(int length_symbol, + VP8LBitReader* const br) { + // Length and distance prefixes are encoded the same way. + return GetCopyDistance(length_symbol, br); +} + +static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) { + if (plane_code > CODE_TO_PLANE_CODES) { + return plane_code - CODE_TO_PLANE_CODES; + } else { + const int dist_code = kCodeToPlane[plane_code - 1]; + const int yoffset = dist_code >> 4; + const int xoffset = 8 - (dist_code & 0xf); + const int dist = yoffset * xsize + xoffset; + return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + } +} + +//------------------------------------------------------------------------------ +// Decodes the next Huffman code from bit-stream. +// VP8LFillBitWindow(br) needs to be called at minimum every second call +// to ReadSymbol, in order to pre-fetch enough bits. +static WEBP_INLINE int ReadSymbol(const HuffmanCode* table, + VP8LBitReader* const br) { + int nbits; + uint32_t val = VP8LPrefetchBits(br); + table += val & HUFFMAN_TABLE_MASK; + nbits = table->bits - HUFFMAN_TABLE_BITS; + if (nbits > 0) { + VP8LSetBitPos(br, br->bit_pos_ + HUFFMAN_TABLE_BITS); + val = VP8LPrefetchBits(br); + table += table->value; + table += val & ((1 << nbits) - 1); + } + VP8LSetBitPos(br, br->bit_pos_ + table->bits); + return table->value; +} + +// Reads packed symbol depending on GREEN channel +#define BITS_SPECIAL_MARKER 0x100 // something large enough (and a bit-mask) +#define PACKED_NON_LITERAL_CODE 0 // must be < NUM_LITERAL_CODES +static WEBP_INLINE int ReadPackedSymbols(const HTreeGroup* group, + VP8LBitReader* const br, + uint32_t* const dst) { + const uint32_t val = VP8LPrefetchBits(br) & (HUFFMAN_PACKED_TABLE_SIZE - 1); + const HuffmanCode32 code = group->packed_table[val]; + assert(group->use_packed_table); + if (code.bits < BITS_SPECIAL_MARKER) { + VP8LSetBitPos(br, br->bit_pos_ + code.bits); + *dst = code.value; + return PACKED_NON_LITERAL_CODE; + } else { + VP8LSetBitPos(br, br->bit_pos_ + code.bits - BITS_SPECIAL_MARKER); + assert(code.value >= NUM_LITERAL_CODES); + return code.value; + } +} + +static int AccumulateHCode(HuffmanCode hcode, int shift, + HuffmanCode32* const huff) { + huff->bits += hcode.bits; + huff->value |= (uint32_t)hcode.value << shift; + assert(huff->bits <= HUFFMAN_TABLE_BITS); + return hcode.bits; +} + +static void BuildPackedTable(HTreeGroup* const htree_group) { + uint32_t code; + for (code = 0; code < HUFFMAN_PACKED_TABLE_SIZE; ++code) { + uint32_t bits = code; + HuffmanCode32* const huff = &htree_group->packed_table[bits]; + HuffmanCode hcode = htree_group->htrees[GREEN][bits]; + if (hcode.value >= NUM_LITERAL_CODES) { + huff->bits = hcode.bits + BITS_SPECIAL_MARKER; + huff->value = hcode.value; + } else { + huff->bits = 0; + huff->value = 0; + bits >>= AccumulateHCode(hcode, 8, huff); + bits >>= AccumulateHCode(htree_group->htrees[RED][bits], 16, huff); + bits >>= AccumulateHCode(htree_group->htrees[BLUE][bits], 0, huff); + bits >>= AccumulateHCode(htree_group->htrees[ALPHA][bits], 24, huff); + (void)bits; + } + } +} + +static int ReadHuffmanCodeLengths( + VP8LDecoder* const dec, const int* const code_length_code_lengths, + int num_symbols, int* const code_lengths) { + int ok = 0; + VP8LBitReader* const br = &dec->br_; + int symbol; + int max_symbol; + int prev_code_len = DEFAULT_CODE_LENGTH; + HuffmanTables tables; + + if (!VP8LHuffmanTablesAllocate(1 << LENGTHS_TABLE_BITS, &tables) || + !VP8LBuildHuffmanTable(&tables, LENGTHS_TABLE_BITS, + code_length_code_lengths, NUM_CODE_LENGTH_CODES)) { + goto End; + } + + if (VP8LReadBits(br, 1)) { // use length + const int length_nbits = 2 + 2 * VP8LReadBits(br, 3); + max_symbol = 2 + VP8LReadBits(br, length_nbits); + if (max_symbol > num_symbols) { + goto End; + } + } else { + max_symbol = num_symbols; + } + + symbol = 0; + while (symbol < num_symbols) { + const HuffmanCode* p; + int code_len; + if (max_symbol-- == 0) break; + VP8LFillBitWindow(br); + p = &tables.curr_segment->start[VP8LPrefetchBits(br) & LENGTHS_TABLE_MASK]; + VP8LSetBitPos(br, br->bit_pos_ + p->bits); + code_len = p->value; + if (code_len < kCodeLengthLiterals) { + code_lengths[symbol++] = code_len; + if (code_len != 0) prev_code_len = code_len; + } else { + const int use_prev = (code_len == kCodeLengthRepeatCode); + const int slot = code_len - kCodeLengthLiterals; + const int extra_bits = kCodeLengthExtraBits[slot]; + const int repeat_offset = kCodeLengthRepeatOffsets[slot]; + int repeat = VP8LReadBits(br, extra_bits) + repeat_offset; + if (symbol + repeat > num_symbols) { + goto End; + } else { + const int length = use_prev ? prev_code_len : 0; + while (repeat-- > 0) code_lengths[symbol++] = length; + } + } + } + ok = 1; + + End: + VP8LHuffmanTablesDeallocate(&tables); + if (!ok) return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + return ok; +} + +// 'code_lengths' is pre-allocated temporary buffer, used for creating Huffman +// tree. +static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec, + int* const code_lengths, + HuffmanTables* const table) { + int ok = 0; + int size = 0; + VP8LBitReader* const br = &dec->br_; + const int simple_code = VP8LReadBits(br, 1); + + memset(code_lengths, 0, alphabet_size * sizeof(*code_lengths)); + + if (simple_code) { // Read symbols, codes & code lengths directly. + const int num_symbols = VP8LReadBits(br, 1) + 1; + const int first_symbol_len_code = VP8LReadBits(br, 1); + // The first code is either 1 bit or 8 bit code. + int symbol = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8); + code_lengths[symbol] = 1; + // The second code (if present), is always 8 bits long. + if (num_symbols == 2) { + symbol = VP8LReadBits(br, 8); + code_lengths[symbol] = 1; + } + ok = 1; + } else { // Decode Huffman-coded code lengths. + int i; + int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 }; + const int num_codes = VP8LReadBits(br, 4) + 4; + assert(num_codes <= NUM_CODE_LENGTH_CODES); + + for (i = 0; i < num_codes; ++i) { + code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3); + } + ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size, + code_lengths); + } + + ok = ok && !br->eos_; + if (ok) { + size = VP8LBuildHuffmanTable(table, HUFFMAN_TABLE_BITS, + code_lengths, alphabet_size); + } + if (!ok || size == 0) { + return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + } + return size; +} + +static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize, + int color_cache_bits, int allow_recursion) { + int i; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* huffman_image = NULL; + HTreeGroup* htree_groups = NULL; + HuffmanTables* huffman_tables = &hdr->huffman_tables_; + int num_htree_groups = 1; + int num_htree_groups_max = 1; + int* mapping = NULL; + int ok = 0; + + // Check the table has been 0 initialized (through InitMetadata). + assert(huffman_tables->root.start == NULL); + assert(huffman_tables->curr_segment == NULL); + + if (allow_recursion && VP8LReadBits(br, 1)) { + // use meta Huffman codes. + const int huffman_precision = VP8LReadBits(br, 3) + 2; + const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision); + const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision); + const int huffman_pixs = huffman_xsize * huffman_ysize; + if (!DecodeImageStream(huffman_xsize, huffman_ysize, /*is_level0=*/0, dec, + &huffman_image)) { + goto Error; + } + hdr->huffman_subsample_bits_ = huffman_precision; + for (i = 0; i < huffman_pixs; ++i) { + // The huffman data is stored in red and green bytes. + const int group = (huffman_image[i] >> 8) & 0xffff; + huffman_image[i] = group; + if (group >= num_htree_groups_max) { + num_htree_groups_max = group + 1; + } + } + // Check the validity of num_htree_groups_max. If it seems too big, use a + // smaller value for later. This will prevent big memory allocations to end + // up with a bad bitstream anyway. + // The value of 1000 is totally arbitrary. We know that num_htree_groups_max + // is smaller than (1 << 16) and should be smaller than the number of pixels + // (though the format allows it to be bigger). + if (num_htree_groups_max > 1000 || num_htree_groups_max > xsize * ysize) { + // Create a mapping from the used indices to the minimal set of used + // values [0, num_htree_groups) + mapping = (int*)WebPSafeMalloc(num_htree_groups_max, sizeof(*mapping)); + if (mapping == NULL) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Error; + } + // -1 means a value is unmapped, and therefore unused in the Huffman + // image. + memset(mapping, 0xff, num_htree_groups_max * sizeof(*mapping)); + for (num_htree_groups = 0, i = 0; i < huffman_pixs; ++i) { + // Get the current mapping for the group and remap the Huffman image. + int* const mapped_group = &mapping[huffman_image[i]]; + if (*mapped_group == -1) *mapped_group = num_htree_groups++; + huffman_image[i] = *mapped_group; + } + } else { + num_htree_groups = num_htree_groups_max; + } + } + + if (br->eos_) goto Error; + + if (!ReadHuffmanCodesHelper(color_cache_bits, num_htree_groups, + num_htree_groups_max, mapping, dec, + huffman_tables, &htree_groups)) { + goto Error; + } + ok = 1; + + // All OK. Finalize pointers. + hdr->huffman_image_ = huffman_image; + hdr->num_htree_groups_ = num_htree_groups; + hdr->htree_groups_ = htree_groups; + + Error: + WebPSafeFree(mapping); + if (!ok) { + WebPSafeFree(huffman_image); + VP8LHuffmanTablesDeallocate(huffman_tables); + VP8LHtreeGroupsFree(htree_groups); + } + return ok; +} + +int ReadHuffmanCodesHelper(int color_cache_bits, int num_htree_groups, + int num_htree_groups_max, const int* const mapping, + VP8LDecoder* const dec, + HuffmanTables* const huffman_tables, + HTreeGroup** const htree_groups) { + int i, j, ok = 0; + const int max_alphabet_size = + kAlphabetSize[0] + ((color_cache_bits > 0) ? 1 << color_cache_bits : 0); + const int table_size = kTableSize[color_cache_bits]; + int* code_lengths = NULL; + + if ((mapping == NULL && num_htree_groups != num_htree_groups_max) || + num_htree_groups > num_htree_groups_max) { + goto Error; + } + + code_lengths = + (int*)WebPSafeCalloc((uint64_t)max_alphabet_size, sizeof(*code_lengths)); + *htree_groups = VP8LHtreeGroupsNew(num_htree_groups); + + if (*htree_groups == NULL || code_lengths == NULL || + !VP8LHuffmanTablesAllocate(num_htree_groups * table_size, + huffman_tables)) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Error; + } + + for (i = 0; i < num_htree_groups_max; ++i) { + // If the index "i" is unused in the Huffman image, just make sure the + // coefficients are valid but do not store them. + if (mapping != NULL && mapping[i] == -1) { + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += (1 << color_cache_bits); + } + // Passing in NULL so that nothing gets filled. + if (!ReadHuffmanCode(alphabet_size, dec, code_lengths, NULL)) { + goto Error; + } + } + } else { + HTreeGroup* const htree_group = + &(*htree_groups)[(mapping == NULL) ? i : mapping[i]]; + HuffmanCode** const htrees = htree_group->htrees; + int size; + int total_size = 0; + int is_trivial_literal = 1; + int max_bits = 0; + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += (1 << color_cache_bits); + } + size = + ReadHuffmanCode(alphabet_size, dec, code_lengths, huffman_tables); + htrees[j] = huffman_tables->curr_segment->curr_table; + if (size == 0) { + goto Error; + } + if (is_trivial_literal && kLiteralMap[j] == 1) { + is_trivial_literal = (htrees[j]->bits == 0); + } + total_size += htrees[j]->bits; + huffman_tables->curr_segment->curr_table += size; + if (j <= ALPHA) { + int local_max_bits = code_lengths[0]; + int k; + for (k = 1; k < alphabet_size; ++k) { + if (code_lengths[k] > local_max_bits) { + local_max_bits = code_lengths[k]; + } + } + max_bits += local_max_bits; + } + } + htree_group->is_trivial_literal = is_trivial_literal; + htree_group->is_trivial_code = 0; + if (is_trivial_literal) { + const int red = htrees[RED][0].value; + const int blue = htrees[BLUE][0].value; + const int alpha = htrees[ALPHA][0].value; + htree_group->literal_arb = ((uint32_t)alpha << 24) | (red << 16) | blue; + if (total_size == 0 && htrees[GREEN][0].value < NUM_LITERAL_CODES) { + htree_group->is_trivial_code = 1; + htree_group->literal_arb |= htrees[GREEN][0].value << 8; + } + } + htree_group->use_packed_table = + !htree_group->is_trivial_code && (max_bits < HUFFMAN_PACKED_BITS); + if (htree_group->use_packed_table) BuildPackedTable(htree_group); + } + } + ok = 1; + + Error: + WebPSafeFree(code_lengths); + if (!ok) { + VP8LHuffmanTablesDeallocate(huffman_tables); + VP8LHtreeGroupsFree(*htree_groups); + *htree_groups = NULL; + } + return ok; +} + +//------------------------------------------------------------------------------ +// Scaling. + +#if !defined(WEBP_REDUCE_SIZE) +static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) { + const int num_channels = 4; + const int in_width = io->mb_w; + const int out_width = io->scaled_width; + const int in_height = io->mb_h; + const int out_height = io->scaled_height; + const uint64_t work_size = 2 * num_channels * (uint64_t)out_width; + rescaler_t* work; // Rescaler work area. + const uint64_t scaled_data_size = (uint64_t)out_width; + uint32_t* scaled_data; // Temporary storage for scaled BGRA data. + const uint64_t memory_size = sizeof(*dec->rescaler) + + work_size * sizeof(*work) + + scaled_data_size * sizeof(*scaled_data); + uint8_t* memory = (uint8_t*)WebPSafeMalloc(memory_size, sizeof(*memory)); + if (memory == NULL) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + assert(dec->rescaler_memory == NULL); + dec->rescaler_memory = memory; + + dec->rescaler = (WebPRescaler*)memory; + memory += sizeof(*dec->rescaler); + work = (rescaler_t*)memory; + memory += work_size * sizeof(*work); + scaled_data = (uint32_t*)memory; + + if (!WebPRescalerInit(dec->rescaler, in_width, in_height, + (uint8_t*)scaled_data, out_width, out_height, + 0, num_channels, work)) { + return 0; + } + return 1; +} +#endif // WEBP_REDUCE_SIZE + +//------------------------------------------------------------------------------ +// Export to ARGB + +#if !defined(WEBP_REDUCE_SIZE) + +// We have special "export" function since we need to convert from BGRA +static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace, + int rgba_stride, uint8_t* const rgba) { + uint32_t* const src = (uint32_t*)rescaler->dst; + uint8_t* dst = rgba; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + VP8LConvertFromBGRA(src, dst_width, colorspace, dst); + dst += rgba_stride; + ++num_lines_out; + } + return num_lines_out; +} + +// Emit scaled rows. +static int EmitRescaledRowsRGBA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h, + uint8_t* const out, int out_stride) { + const WEBP_CSP_MODE colorspace = dec->output_->colorspace; + int num_lines_in = 0; + int num_lines_out = 0; + while (num_lines_in < mb_h) { + uint8_t* const row_in = in + (uint64_t)num_lines_in * in_stride; + uint8_t* const row_out = out + (uint64_t)num_lines_out * out_stride; + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + assert(needed_lines > 0 && needed_lines <= lines_left); + WebPMultARGBRows(row_in, in_stride, + dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, row_in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out); + } + return num_lines_out; +} + +#endif // WEBP_REDUCE_SIZE + +// Emit rows without any scaling. +static int EmitRows(WEBP_CSP_MODE colorspace, + const uint8_t* row_in, int in_stride, + int mb_w, int mb_h, + uint8_t* const out, int out_stride) { + int lines = mb_h; + uint8_t* row_out = out; + while (lines-- > 0) { + VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out); + row_in += in_stride; + row_out += out_stride; + } + return mb_h; // Num rows out == num rows in. +} + +//------------------------------------------------------------------------------ +// Export to YUVA + +static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos, + const WebPDecBuffer* const output) { + const WebPYUVABuffer* const buf = &output->u.YUVA; + + // first, the luma plane + WebPConvertARGBToY(src, buf->y + y_pos * buf->y_stride, width); + + // then U/V planes + { + uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride; + uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride; + // even lines: store values + // odd lines: average with previous values + WebPConvertARGBToUV(src, u, v, width, !(y_pos & 1)); + } + // Lastly, store alpha if needed. + if (buf->a != NULL) { + uint8_t* const a = buf->a + y_pos * buf->a_stride; +#if defined(WORDS_BIGENDIAN) + WebPExtractAlpha((uint8_t*)src + 0, 0, width, 1, a, 0); +#else + WebPExtractAlpha((uint8_t*)src + 3, 0, width, 1, a, 0); +#endif + } +} + +static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) { + WebPRescaler* const rescaler = dec->rescaler; + uint32_t* const src = (uint32_t*)rescaler->dst; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + ConvertToYUVA(src, dst_width, y_pos, dec->output_); + ++y_pos; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h) { + int num_lines_in = 0; + int y_pos = dec->last_out_row_; + while (num_lines_in < mb_h) { + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + WebPMultARGBRows(in, in_stride, dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + in += needed_lines * in_stride; + y_pos += ExportYUVA(dec, y_pos); + } + return y_pos; +} + +static int EmitRowsYUVA(const VP8LDecoder* const dec, + const uint8_t* in, int in_stride, + int mb_w, int num_rows) { + int y_pos = dec->last_out_row_; + while (num_rows-- > 0) { + ConvertToYUVA((const uint32_t*)in, mb_w, y_pos, dec->output_); + in += in_stride; + ++y_pos; + } + return y_pos; +} + +//------------------------------------------------------------------------------ +// Cropping. + +// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and +// crop options. Also updates the input data pointer, so that it points to the +// start of the cropped window. Note that pixels are in ARGB format even if +// 'in_data' is uint8_t*. +// Returns true if the crop window is not empty. +static int SetCropWindow(VP8Io* const io, int y_start, int y_end, + uint8_t** const in_data, int pixel_stride) { + assert(y_start < y_end); + assert(io->crop_left < io->crop_right); + if (y_end > io->crop_bottom) { + y_end = io->crop_bottom; // make sure we don't overflow on last row. + } + if (y_start < io->crop_top) { + const int delta = io->crop_top - y_start; + y_start = io->crop_top; + *in_data += delta * pixel_stride; + } + if (y_start >= y_end) return 0; // Crop window is empty. + + *in_data += io->crop_left * sizeof(uint32_t); + + io->mb_y = y_start - io->crop_top; + io->mb_w = io->crop_right - io->crop_left; + io->mb_h = y_end - y_start; + return 1; // Non-empty crop window. +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetMetaIndex( + const uint32_t* const image, int xsize, int bits, int x, int y) { + if (bits == 0) return 0; + return image[xsize * (y >> bits) + (x >> bits)]; +} + +static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr, + int x, int y) { + const int meta_index = GetMetaIndex(hdr->huffman_image_, hdr->huffman_xsize_, + hdr->huffman_subsample_bits_, x, y); + assert(meta_index < hdr->num_htree_groups_); + return hdr->htree_groups_ + meta_index; +} + +//------------------------------------------------------------------------------ +// Main loop, with custom row-processing function + +typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row); + +static void ApplyInverseTransforms(VP8LDecoder* const dec, + int start_row, int num_rows, + const uint32_t* const rows) { + int n = dec->next_transform_; + const int cache_pixs = dec->width_ * num_rows; + const int end_row = start_row + num_rows; + const uint32_t* rows_in = rows; + uint32_t* const rows_out = dec->argb_cache_; + + // Inverse transforms. + while (n-- > 0) { + VP8LTransform* const transform = &dec->transforms_[n]; + VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out); + rows_in = rows_out; + } + if (rows_in != rows_out) { + // No transform called, hence just copy. + memcpy(rows_out, rows_in, cache_pixs * sizeof(*rows_out)); + } +} + +// Processes (transforms, scales & color-converts) the rows decoded after the +// last call. +static void ProcessRows(VP8LDecoder* const dec, int row) { + const uint32_t* const rows = dec->pixels_ + dec->width_ * dec->last_row_; + const int num_rows = row - dec->last_row_; + + assert(row <= dec->io_->crop_bottom); + // We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size + // of argb_cache_), but we currently don't need more than that. + assert(num_rows <= NUM_ARGB_CACHE_ROWS); + if (num_rows > 0) { // Emit output. + VP8Io* const io = dec->io_; + uint8_t* rows_data = (uint8_t*)dec->argb_cache_; + const int in_stride = io->width * sizeof(uint32_t); // in unit of RGBA + ApplyInverseTransforms(dec, dec->last_row_, num_rows, rows); + if (!SetCropWindow(io, dec->last_row_, row, &rows_data, in_stride)) { + // Nothing to output (this time). + } else { + const WebPDecBuffer* const output = dec->output_; + if (WebPIsRGBMode(output->colorspace)) { // convert to RGBA + const WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const rgba = + buf->rgba + (int64_t)dec->last_out_row_ * buf->stride; + const int num_rows_out = +#if !defined(WEBP_REDUCE_SIZE) + io->use_scaling ? + EmitRescaledRowsRGBA(dec, rows_data, in_stride, io->mb_h, + rgba, buf->stride) : +#endif // WEBP_REDUCE_SIZE + EmitRows(output->colorspace, rows_data, in_stride, + io->mb_w, io->mb_h, rgba, buf->stride); + // Update 'last_out_row_'. + dec->last_out_row_ += num_rows_out; + } else { // convert to YUVA + dec->last_out_row_ = io->use_scaling ? + EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) : + EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h); + } + assert(dec->last_out_row_ <= output->height); + } + } + + // Update 'last_row_'. + dec->last_row_ = row; + assert(dec->last_row_ <= dec->height_); +} + +// Row-processing for the special case when alpha data contains only one +// transform (color indexing), and trivial non-green literals. +static int Is8bOptimizable(const VP8LMetadata* const hdr) { + int i; + if (hdr->color_cache_size_ > 0) return 0; + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (i = 0; i < hdr->num_htree_groups_; ++i) { + HuffmanCode** const htrees = hdr->htree_groups_[i].htrees; + if (htrees[RED][0].bits > 0) return 0; + if (htrees[BLUE][0].bits > 0) return 0; + if (htrees[ALPHA][0].bits > 0) return 0; + } + return 1; +} + +static void AlphaApplyFilter(ALPHDecoder* const alph_dec, + int first_row, int last_row, + uint8_t* out, int stride) { + if (alph_dec->filter_ != WEBP_FILTER_NONE) { + int y; + const uint8_t* prev_line = alph_dec->prev_line_; + assert(WebPUnfilters[alph_dec->filter_] != NULL); + for (y = first_row; y < last_row; ++y) { + WebPUnfilters[alph_dec->filter_](prev_line, out, out, stride); + prev_line = out; + out += stride; + } + alph_dec->prev_line_ = prev_line; + } +} + +static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int last_row) { + // For vertical and gradient filtering, we need to decode the part above the + // crop_top row, in order to have the correct spatial predictors. + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque; + const int top_row = + (alph_dec->filter_ == WEBP_FILTER_NONE || + alph_dec->filter_ == WEBP_FILTER_HORIZONTAL) ? dec->io_->crop_top + : dec->last_row_; + const int first_row = (dec->last_row_ < top_row) ? top_row : dec->last_row_; + assert(last_row <= dec->io_->crop_bottom); + if (last_row > first_row) { + // Special method for paletted alpha data. We only process the cropped area. + const int width = dec->io_->width; + uint8_t* out = alph_dec->output_ + width * first_row; + const uint8_t* const in = + (uint8_t*)dec->pixels_ + dec->width_ * first_row; + VP8LTransform* const transform = &dec->transforms_[0]; + assert(dec->next_transform_ == 1); + assert(transform->type_ == COLOR_INDEXING_TRANSFORM); + VP8LColorIndexInverseTransformAlpha(transform, first_row, last_row, + in, out); + AlphaApplyFilter(alph_dec, first_row, last_row, out, width); + } + dec->last_row_ = dec->last_out_row_ = last_row; +} + +//------------------------------------------------------------------------------ +// Helper functions for fast pattern copy (8b and 32b) + +// cyclic rotation of pattern word +static WEBP_INLINE uint32_t Rotate8b(uint32_t V) { +#if defined(WORDS_BIGENDIAN) + return ((V & 0xff000000u) >> 24) | (V << 8); +#else + return ((V & 0xffu) << 24) | (V >> 8); +#endif +} + +// copy 1, 2 or 4-bytes pattern +static WEBP_INLINE void CopySmallPattern8b(const uint8_t* src, uint8_t* dst, + int length, uint32_t pattern) { + int i; + // align 'dst' to 4-bytes boundary. Adjust the pattern along the way. + while ((uintptr_t)dst & 3) { + *dst++ = *src++; + pattern = Rotate8b(pattern); + --length; + } + // Copy the pattern 4 bytes at a time. + for (i = 0; i < (length >> 2); ++i) { + ((uint32_t*)dst)[i] = pattern; + } + // Finish with left-overs. 'pattern' is still correctly positioned, + // so no Rotate8b() call is needed. + for (i <<= 2; i < length; ++i) { + dst[i] = src[i]; + } +} + +static WEBP_INLINE void CopyBlock8b(uint8_t* const dst, int dist, int length) { + const uint8_t* src = dst - dist; + if (length >= 8) { + uint32_t pattern = 0; + switch (dist) { + case 1: + pattern = src[0]; +#if defined(__arm__) || defined(_M_ARM) // arm doesn't like multiply that much + pattern |= pattern << 8; + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.qb %0, %0" : "+r"(pattern)); +#else + pattern = 0x01010101u * pattern; +#endif + break; + case 2: +#if !defined(WORDS_BIGENDIAN) + memcpy(&pattern, src, sizeof(uint16_t)); +#else + pattern = ((uint32_t)src[0] << 8) | src[1]; +#endif +#if defined(__arm__) || defined(_M_ARM) + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.ph %0, %0" : "+r"(pattern)); +#else + pattern = 0x00010001u * pattern; +#endif + break; + case 4: + memcpy(&pattern, src, sizeof(uint32_t)); + break; + default: + goto Copy; + } + CopySmallPattern8b(src, dst, length, pattern); + return; + } + Copy: + if (dist >= length) { // no overlap -> use memcpy() + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +// copy pattern of 1 or 2 uint32_t's +static WEBP_INLINE void CopySmallPattern32b(const uint32_t* src, + uint32_t* dst, + int length, uint64_t pattern) { + int i; + if ((uintptr_t)dst & 4) { // Align 'dst' to 8-bytes boundary. + *dst++ = *src++; + pattern = (pattern >> 32) | (pattern << 32); + --length; + } + assert(0 == ((uintptr_t)dst & 7)); + for (i = 0; i < (length >> 1); ++i) { + ((uint64_t*)dst)[i] = pattern; // Copy the pattern 8 bytes at a time. + } + if (length & 1) { // Finish with left-over. + dst[i << 1] = src[i << 1]; + } +} + +static WEBP_INLINE void CopyBlock32b(uint32_t* const dst, + int dist, int length) { + const uint32_t* const src = dst - dist; + if (dist <= 2 && length >= 4 && ((uintptr_t)dst & 3) == 0) { + uint64_t pattern; + if (dist == 1) { + pattern = (uint64_t)src[0]; + pattern |= pattern << 32; + } else { + memcpy(&pattern, src, sizeof(pattern)); + } + CopySmallPattern32b(src, dst, length, pattern); + } else if (dist >= length) { // no overlap + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +//------------------------------------------------------------------------------ + +static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data, + int width, int height, int last_row) { + int ok = 1; + int row = dec->last_pixel_ / width; + int col = dec->last_pixel_ % width; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + int pos = dec->last_pixel_; // current position + const int end = width * height; // End of data + const int last = width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int mask = hdr->huffman_mask_; + const HTreeGroup* htree_group = + (pos < last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(pos <= end); + assert(last_row <= height); + assert(Is8bOptimizable(hdr)); + + while (!br->eos_ && pos < last) { + int code; + // Only update when changing tile. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + VP8LFillBitWindow(br); + code = ReadSymbol(htree_group->htrees[GREEN], br); + if (code < NUM_LITERAL_CODES) { // Literal + data[pos] = code; + ++pos; + ++col; + if (col >= width) { + col = 0; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + if (pos >= dist && end - pos >= length) { + CopyBlock8b(data + pos, dist, length); + } else { + ok = 0; + goto End; + } + pos += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + if (pos < last && (col & mask)) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + } else { // Not reached + ok = 0; + goto End; + } + br->eos_ = VP8LIsEndOfStream(br); + } + // Process the remaining rows corresponding to last row-block. + ExtractPalettedAlphaRows(dec, row > last_row ? last_row : row); + + End: + br->eos_ = VP8LIsEndOfStream(br); + if (!ok || (br->eos_ && pos < end)) { + return VP8LSetError( + dec, br->eos_ ? VP8_STATUS_SUSPENDED : VP8_STATUS_BITSTREAM_ERROR); + } + dec->last_pixel_ = pos; + return ok; +} + +static void SaveState(VP8LDecoder* const dec, int last_pixel) { + assert(dec->incremental_); + dec->saved_br_ = dec->br_; + dec->saved_last_pixel_ = last_pixel; + if (dec->hdr_.color_cache_size_ > 0) { + VP8LColorCacheCopy(&dec->hdr_.color_cache_, &dec->hdr_.saved_color_cache_); + } +} + +static void RestoreState(VP8LDecoder* const dec) { + assert(dec->br_.eos_); + dec->status_ = VP8_STATUS_SUSPENDED; + dec->br_ = dec->saved_br_; + dec->last_pixel_ = dec->saved_last_pixel_; + if (dec->hdr_.color_cache_size_ > 0) { + VP8LColorCacheCopy(&dec->hdr_.saved_color_cache_, &dec->hdr_.color_cache_); + } +} + +#define SYNC_EVERY_N_ROWS 8 // minimum number of rows between check-points +static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data, + int width, int height, int last_row, + ProcessRowsFunc process_func) { + int row = dec->last_pixel_ / width; + int col = dec->last_pixel_ % width; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* src = data + dec->last_pixel_; + uint32_t* last_cached = src; + uint32_t* const src_end = data + width * height; // End of data + uint32_t* const src_last = data + width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int color_cache_limit = len_code_limit + hdr->color_cache_size_; + int next_sync_row = dec->incremental_ ? row : 1 << 24; + VP8LColorCache* const color_cache = + (hdr->color_cache_size_ > 0) ? &hdr->color_cache_ : NULL; + const int mask = hdr->huffman_mask_; + const HTreeGroup* htree_group = + (src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(dec->last_row_ < last_row); + assert(src_last <= src_end); + + while (src < src_last) { + int code; + if (row >= next_sync_row) { + SaveState(dec, (int)(src - data)); + next_sync_row = row + SYNC_EVERY_N_ROWS; + } + // Only update when changing tile. Note we could use this test: + // if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed + // but that's actually slower and needs storing the previous col/row. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + if (htree_group->is_trivial_code) { + *src = htree_group->literal_arb; + goto AdvanceByOne; + } + VP8LFillBitWindow(br); + if (htree_group->use_packed_table) { + code = ReadPackedSymbols(htree_group, br, src); + if (VP8LIsEndOfStream(br)) break; + if (code == PACKED_NON_LITERAL_CODE) goto AdvanceByOne; + } else { + code = ReadSymbol(htree_group->htrees[GREEN], br); + } + if (VP8LIsEndOfStream(br)) break; + if (code < NUM_LITERAL_CODES) { // Literal + if (htree_group->is_trivial_literal) { + *src = htree_group->literal_arb | (code << 8); + } else { + int red, blue, alpha; + red = ReadSymbol(htree_group->htrees[RED], br); + VP8LFillBitWindow(br); + blue = ReadSymbol(htree_group->htrees[BLUE], br); + alpha = ReadSymbol(htree_group->htrees[ALPHA], br); + if (VP8LIsEndOfStream(br)) break; + *src = ((uint32_t)alpha << 24) | (red << 16) | (code << 8) | blue; + } + AdvanceByOne: + ++src; + ++col; + if (col >= width) { + col = 0; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + + if (VP8LIsEndOfStream(br)) break; + if (src - data < (ptrdiff_t)dist || src_end - src < (ptrdiff_t)length) { + goto Error; + } else { + CopyBlock32b(src, dist, length); + } + src += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + } + // Because of the check done above (before 'src' was incremented by + // 'length'), the following holds true. + assert(src <= src_end); + if (col & mask) htree_group = GetHtreeGroupForPos(hdr, col, row); + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } else if (code < color_cache_limit) { // Color cache + const int key = code - len_code_limit; + assert(color_cache != NULL); + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + *src = VP8LColorCacheLookup(color_cache, key); + goto AdvanceByOne; + } else { // Not reached + goto Error; + } + } + + br->eos_ = VP8LIsEndOfStream(br); + // In incremental decoding: + // br->eos_ && src < src_last: if 'br' reached the end of the buffer and + // 'src_last' has not been reached yet, there is not enough data. 'dec' has to + // be reset until there is more data. + // !br->eos_ && src < src_last: this cannot happen as either the buffer is + // fully read, either enough has been read to reach 'src_last'. + // src >= src_last: 'src_last' is reached, all is fine. 'src' can actually go + // beyond 'src_last' in case the image is cropped and an LZ77 goes further. + // The buffer might have been enough or there is some left. 'br->eos_' does + // not matter. + assert(!dec->incremental_ || (br->eos_ && src < src_last) || src >= src_last); + if (dec->incremental_ && br->eos_ && src < src_last) { + RestoreState(dec); + } else if ((dec->incremental_ && src >= src_last) || !br->eos_) { + // Process the remaining rows corresponding to last row-block. + if (process_func != NULL) { + process_func(dec, row > last_row ? last_row : row); + } + dec->status_ = VP8_STATUS_OK; + dec->last_pixel_ = (int)(src - data); // end-of-scan marker + } else { + // if not incremental, and we are past the end of buffer (eos_=1), then this + // is a real bitstream error. + goto Error; + } + return 1; + + Error: + return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); +} + +// ----------------------------------------------------------------------------- +// VP8LTransform + +static void ClearTransform(VP8LTransform* const transform) { + WebPSafeFree(transform->data_); + transform->data_ = NULL; +} + +// For security reason, we need to remap the color map to span +// the total possible bundled values, and not just the num_colors. +static int ExpandColorMap(int num_colors, VP8LTransform* const transform) { + int i; + const int final_num_colors = 1 << (8 >> transform->bits_); + uint32_t* const new_color_map = + (uint32_t*)WebPSafeMalloc((uint64_t)final_num_colors, + sizeof(*new_color_map)); + if (new_color_map == NULL) { + return 0; + } else { + uint8_t* const data = (uint8_t*)transform->data_; + uint8_t* const new_data = (uint8_t*)new_color_map; + new_color_map[0] = transform->data_[0]; + for (i = 4; i < 4 * num_colors; ++i) { + // Equivalent to VP8LAddPixels(), on a byte-basis. + new_data[i] = (data[i] + new_data[i - 4]) & 0xff; + } + for (; i < 4 * final_num_colors; ++i) { + new_data[i] = 0; // black tail. + } + WebPSafeFree(transform->data_); + transform->data_ = new_color_map; + } + return 1; +} + +static int ReadTransform(int* const xsize, int const* ysize, + VP8LDecoder* const dec) { + int ok = 1; + VP8LBitReader* const br = &dec->br_; + VP8LTransform* transform = &dec->transforms_[dec->next_transform_]; + const VP8LImageTransformType type = + (VP8LImageTransformType)VP8LReadBits(br, 2); + + // Each transform type can only be present once in the stream. + if (dec->transforms_seen_ & (1U << type)) { + return 0; // Already there, let's not accept the second same transform. + } + dec->transforms_seen_ |= (1U << type); + + transform->type_ = type; + transform->xsize_ = *xsize; + transform->ysize_ = *ysize; + transform->data_ = NULL; + ++dec->next_transform_; + assert(dec->next_transform_ <= NUM_TRANSFORMS); + + switch (type) { + case PREDICTOR_TRANSFORM: + case CROSS_COLOR_TRANSFORM: + transform->bits_ = VP8LReadBits(br, 3) + 2; + ok = DecodeImageStream(VP8LSubSampleSize(transform->xsize_, + transform->bits_), + VP8LSubSampleSize(transform->ysize_, + transform->bits_), + /*is_level0=*/0, dec, &transform->data_); + break; + case COLOR_INDEXING_TRANSFORM: { + const int num_colors = VP8LReadBits(br, 8) + 1; + const int bits = (num_colors > 16) ? 0 + : (num_colors > 4) ? 1 + : (num_colors > 2) ? 2 + : 3; + *xsize = VP8LSubSampleSize(transform->xsize_, bits); + transform->bits_ = bits; + ok = DecodeImageStream(num_colors, /*ysize=*/1, /*is_level0=*/0, dec, + &transform->data_); + if (ok && !ExpandColorMap(num_colors, transform)) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + break; + } + case SUBTRACT_GREEN_TRANSFORM: + break; + default: + assert(0); // can't happen + break; + } + + return ok; +} + +// ----------------------------------------------------------------------------- +// VP8LMetadata + +static void InitMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + memset(hdr, 0, sizeof(*hdr)); +} + +static void ClearMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + + WebPSafeFree(hdr->huffman_image_); + VP8LHuffmanTablesDeallocate(&hdr->huffman_tables_); + VP8LHtreeGroupsFree(hdr->htree_groups_); + VP8LColorCacheClear(&hdr->color_cache_); + VP8LColorCacheClear(&hdr->saved_color_cache_); + InitMetadata(hdr); +} + +// ----------------------------------------------------------------------------- +// VP8LDecoder + +VP8LDecoder* VP8LNew(void) { + VP8LDecoder* const dec = (VP8LDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec == NULL) return NULL; + dec->status_ = VP8_STATUS_OK; + dec->state_ = READ_DIM; + + VP8LDspInit(); // Init critical function pointers. + + return dec; +} + +void VP8LClear(VP8LDecoder* const dec) { + int i; + if (dec == NULL) return; + ClearMetadata(&dec->hdr_); + + WebPSafeFree(dec->pixels_); + dec->pixels_ = NULL; + for (i = 0; i < dec->next_transform_; ++i) { + ClearTransform(&dec->transforms_[i]); + } + dec->next_transform_ = 0; + dec->transforms_seen_ = 0; + + WebPSafeFree(dec->rescaler_memory); + dec->rescaler_memory = NULL; + + dec->output_ = NULL; // leave no trace behind +} + +void VP8LDelete(VP8LDecoder* const dec) { + if (dec != NULL) { + VP8LClear(dec); + WebPSafeFree(dec); + } +} + +static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) { + VP8LMetadata* const hdr = &dec->hdr_; + const int num_bits = hdr->huffman_subsample_bits_; + dec->width_ = width; + dec->height_ = height; + + hdr->huffman_xsize_ = VP8LSubSampleSize(width, num_bits); + hdr->huffman_mask_ = (num_bits == 0) ? ~0 : (1 << num_bits) - 1; +} + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data) { + int ok = 1; + int transform_xsize = xsize; + int transform_ysize = ysize; + VP8LBitReader* const br = &dec->br_; + VP8LMetadata* const hdr = &dec->hdr_; + uint32_t* data = NULL; + int color_cache_bits = 0; + + // Read the transforms (may recurse). + if (is_level0) { + while (ok && VP8LReadBits(br, 1)) { + ok = ReadTransform(&transform_xsize, &transform_ysize, dec); + } + } + + // Color cache + if (ok && VP8LReadBits(br, 1)) { + color_cache_bits = VP8LReadBits(br, 4); + ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS); + if (!ok) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto End; + } + } + + // Read the Huffman codes (may recurse). + ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize, + color_cache_bits, is_level0); + if (!ok) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto End; + } + + // Finish setting up the color-cache + if (color_cache_bits > 0) { + hdr->color_cache_size_ = 1 << color_cache_bits; + if (!VP8LColorCacheInit(&hdr->color_cache_, color_cache_bits)) { + ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto End; + } + } else { + hdr->color_cache_size_ = 0; + } + UpdateDecoder(dec, transform_xsize, transform_ysize); + + if (is_level0) { // level 0 complete + dec->state_ = READ_HDR; + goto End; + } + + { + const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize; + data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data)); + if (data == NULL) { + ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto End; + } + } + + // Use the Huffman trees to decode the LZ77 encoded data. + ok = DecodeImageData(dec, data, transform_xsize, transform_ysize, + transform_ysize, NULL); + ok = ok && !br->eos_; + + End: + if (!ok) { + WebPSafeFree(data); + ClearMetadata(hdr); + } else { + if (decoded_data != NULL) { + *decoded_data = data; + } else { + // We allocate image data in this function only for transforms. At level 0 + // (that is: not the transforms), we shouldn't have allocated anything. + assert(data == NULL); + assert(is_level0); + } + dec->last_pixel_ = 0; // Reset for future DECODE_DATA_FUNC() calls. + if (!is_level0) ClearMetadata(hdr); // Clean up temporary data behind. + } + return ok; +} + +//------------------------------------------------------------------------------ +// Allocate internal buffers dec->pixels_ and dec->argb_cache_. +static int AllocateInternalBuffers32b(VP8LDecoder* const dec, int final_width) { + const uint64_t num_pixels = (uint64_t)dec->width_ * dec->height_; + // Scratch buffer corresponding to top-prediction row for transforming the + // first row in the row-blocks. Not needed for paletted alpha. + const uint64_t cache_top_pixels = (uint16_t)final_width; + // Scratch buffer for temporary BGRA storage. Not needed for paletted alpha. + const uint64_t cache_pixels = (uint64_t)final_width * NUM_ARGB_CACHE_ROWS; + const uint64_t total_num_pixels = + num_pixels + cache_top_pixels + cache_pixels; + + assert(dec->width_ <= final_width); + dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint32_t)); + if (dec->pixels_ == NULL) { + dec->argb_cache_ = NULL; // for soundness + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + dec->argb_cache_ = dec->pixels_ + num_pixels + cache_top_pixels; + return 1; +} + +static int AllocateInternalBuffers8b(VP8LDecoder* const dec) { + const uint64_t total_num_pixels = (uint64_t)dec->width_ * dec->height_; + dec->argb_cache_ = NULL; // for soundness + dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint8_t)); + if (dec->pixels_ == NULL) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + return 1; +} + +//------------------------------------------------------------------------------ + +// Special row-processing that only stores the alpha data. +static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row) { + int cur_row = dec->last_row_; + int num_rows = last_row - cur_row; + const uint32_t* in = dec->pixels_ + dec->width_ * cur_row; + + assert(last_row <= dec->io_->crop_bottom); + while (num_rows > 0) { + const int num_rows_to_process = + (num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows; + // Extract alpha (which is stored in the green plane). + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque; + uint8_t* const output = alph_dec->output_; + const int width = dec->io_->width; // the final width (!= dec->width_) + const int cache_pixs = width * num_rows_to_process; + uint8_t* const dst = output + width * cur_row; + const uint32_t* const src = dec->argb_cache_; + ApplyInverseTransforms(dec, cur_row, num_rows_to_process, in); + WebPExtractGreen(src, dst, cache_pixs); + AlphaApplyFilter(alph_dec, + cur_row, cur_row + num_rows_to_process, dst, width); + num_rows -= num_rows_to_process; + in += num_rows_to_process * dec->width_; + cur_row += num_rows_to_process; + } + assert(cur_row == last_row); + dec->last_row_ = dec->last_out_row_ = last_row; +} + +int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec, + const uint8_t* const data, size_t data_size) { + int ok = 0; + VP8LDecoder* dec = VP8LNew(); + + if (dec == NULL) return 0; + + assert(alph_dec != NULL); + + dec->width_ = alph_dec->width_; + dec->height_ = alph_dec->height_; + dec->io_ = &alph_dec->io_; + dec->io_->opaque = alph_dec; + dec->io_->width = alph_dec->width_; + dec->io_->height = alph_dec->height_; + + dec->status_ = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br_, data, data_size); + + if (!DecodeImageStream(alph_dec->width_, alph_dec->height_, /*is_level0=*/1, + dec, /*decoded_data=*/NULL)) { + goto Err; + } + + // Special case: if alpha data uses only the color indexing transform and + // doesn't use color cache (a frequent case), we will use DecodeAlphaData() + // method that only needs allocation of 1 byte per pixel (alpha channel). + if (dec->next_transform_ == 1 && + dec->transforms_[0].type_ == COLOR_INDEXING_TRANSFORM && + Is8bOptimizable(&dec->hdr_)) { + alph_dec->use_8b_decode_ = 1; + ok = AllocateInternalBuffers8b(dec); + } else { + // Allocate internal buffers (note that dec->width_ may have changed here). + alph_dec->use_8b_decode_ = 0; + ok = AllocateInternalBuffers32b(dec, alph_dec->width_); + } + + if (!ok) goto Err; + + // Only set here, once we are sure it is valid (to avoid thread races). + alph_dec->vp8l_dec_ = dec; + return 1; + + Err: + VP8LDelete(dec); + return 0; +} + +int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) { + VP8LDecoder* const dec = alph_dec->vp8l_dec_; + assert(dec != NULL); + assert(last_row <= dec->height_); + + if (dec->last_row_ >= last_row) { + return 1; // done + } + + if (!alph_dec->use_8b_decode_) WebPInitAlphaProcessing(); + + // Decode (with special row processing). + return alph_dec->use_8b_decode_ ? + DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_, + last_row) : + DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, + last_row, ExtractAlphaRows); +} + +//------------------------------------------------------------------------------ + +int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { + int width, height, has_alpha; + + if (dec == NULL) return 0; + if (io == NULL) { + return VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + } + + dec->io_ = io; + dec->status_ = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br_, io->data, io->data_size); + if (!ReadImageInfo(&dec->br_, &width, &height, &has_alpha)) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto Error; + } + dec->state_ = READ_DIM; + io->width = width; + io->height = height; + + if (!DecodeImageStream(width, height, /*is_level0=*/1, dec, + /*decoded_data=*/NULL)) { + goto Error; + } + return 1; + + Error: + VP8LClear(dec); + assert(dec->status_ != VP8_STATUS_OK); + return 0; +} + +int VP8LDecodeImage(VP8LDecoder* const dec) { + VP8Io* io = NULL; + WebPDecParams* params = NULL; + + if (dec == NULL) return 0; + + assert(dec->hdr_.huffman_tables_.root.start != NULL); + assert(dec->hdr_.htree_groups_ != NULL); + assert(dec->hdr_.num_htree_groups_ > 0); + + io = dec->io_; + assert(io != NULL); + params = (WebPDecParams*)io->opaque; + assert(params != NULL); + + // Initialization. + if (dec->state_ != READ_DATA) { + dec->output_ = params->output; + assert(dec->output_ != NULL); + + if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) { + VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + goto Err; + } + + if (!AllocateInternalBuffers32b(dec, io->width)) goto Err; + +#if !defined(WEBP_REDUCE_SIZE) + if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; +#else + if (io->use_scaling) { + VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + goto Err; + } +#endif + if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) { + // need the alpha-multiply functions for premultiplied output or rescaling + WebPInitAlphaProcessing(); + } + + if (!WebPIsRGBMode(dec->output_->colorspace)) { + WebPInitConvertARGBToYUV(); + if (dec->output_->u.YUVA.a != NULL) WebPInitAlphaProcessing(); + } + if (dec->incremental_) { + if (dec->hdr_.color_cache_size_ > 0 && + dec->hdr_.saved_color_cache_.colors_ == NULL) { + if (!VP8LColorCacheInit(&dec->hdr_.saved_color_cache_, + dec->hdr_.color_cache_.hash_bits_)) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Err; + } + } + } + dec->state_ = READ_DATA; + } + + // Decode. + if (!DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_, + io->crop_bottom, ProcessRows)) { + goto Err; + } + + params->last_y = dec->last_out_row_; + return 1; + + Err: + VP8LClear(dec); + assert(dec->status_ != VP8_STATUS_OK); + return 0; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8li_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8li_dec.h new file mode 100644 index 0000000000..9a13bcc98d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/vp8li_dec.h @@ -0,0 +1,149 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Lossless decoder: internal header. +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora(vikaas.arora@gmail.com) + +#ifndef WEBP_DEC_VP8LI_DEC_H_ +#define WEBP_DEC_VP8LI_DEC_H_ + +#include // for memcpy() +#include "src/dec/webpi_dec.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/color_cache_utils.h" +#include "src/utils/huffman_utils.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + READ_DATA = 0, + READ_HDR = 1, + READ_DIM = 2 +} VP8LDecodeState; + +typedef struct VP8LTransform VP8LTransform; +struct VP8LTransform { + VP8LImageTransformType type_; // transform type. + int bits_; // subsampling bits defining transform window. + int xsize_; // transform window X index. + int ysize_; // transform window Y index. + uint32_t* data_; // transform data. +}; + +typedef struct { + int color_cache_size_; + VP8LColorCache color_cache_; + VP8LColorCache saved_color_cache_; // for incremental + + int huffman_mask_; + int huffman_subsample_bits_; + int huffman_xsize_; + uint32_t* huffman_image_; + int num_htree_groups_; + HTreeGroup* htree_groups_; + HuffmanTables huffman_tables_; +} VP8LMetadata; + +typedef struct VP8LDecoder VP8LDecoder; +struct VP8LDecoder { + VP8StatusCode status_; + VP8LDecodeState state_; + VP8Io* io_; + + const WebPDecBuffer* output_; // shortcut to io->opaque->output + + uint32_t* pixels_; // Internal data: either uint8_t* for alpha + // or uint32_t* for BGRA. + uint32_t* argb_cache_; // Scratch buffer for temporary BGRA storage. + + VP8LBitReader br_; + int incremental_; // if true, incremental decoding is expected + VP8LBitReader saved_br_; // note: could be local variables too + int saved_last_pixel_; + + int width_; + int height_; + int last_row_; // last input row decoded so far. + int last_pixel_; // last pixel decoded so far. However, it may + // not be transformed, scaled and + // color-converted yet. + int last_out_row_; // last row output so far. + + VP8LMetadata hdr_; + + int next_transform_; + VP8LTransform transforms_[NUM_TRANSFORMS]; + // or'd bitset storing the transforms types. + uint32_t transforms_seen_; + + uint8_t* rescaler_memory; // Working memory for rescaling work. + WebPRescaler* rescaler; // Common rescaler for all channels. +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +struct ALPHDecoder; // Defined in dec/alphai.h. + +// in vp8l.c + +// Decodes image header for alpha data stored using lossless compression. +// Returns false in case of error. +WEBP_NODISCARD int VP8LDecodeAlphaHeader(struct ALPHDecoder* const alph_dec, + const uint8_t* const data, + size_t data_size); + +// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are +// already decoded in previous call(s), it will resume decoding from where it +// was paused. +// Returns false in case of bitstream error. +WEBP_NODISCARD int VP8LDecodeAlphaImageStream( + struct ALPHDecoder* const alph_dec, int last_row); + +// Allocates and initialize a new lossless decoder instance. +WEBP_NODISCARD VP8LDecoder* VP8LNew(void); + +// Decodes the image header. Returns false in case of error. +WEBP_NODISCARD int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io); + +// Decodes an image. It's required to decode the lossless header before calling +// this function. Returns false in case of error, with updated dec->status_. +WEBP_NODISCARD int VP8LDecodeImage(VP8LDecoder* const dec); + +// Resets the decoder in its initial state, reclaiming memory. +// Preserves the dec->status_ value. +void VP8LClear(VP8LDecoder* const dec); + +// Clears and deallocate a lossless decoder instance. +void VP8LDelete(VP8LDecoder* const dec); + +// Helper function for reading the different Huffman codes and storing them in +// 'huffman_tables' and 'htree_groups'. +// If mapping is NULL 'num_htree_groups_max' must equal 'num_htree_groups'. +// If it is not NULL, it maps 'num_htree_groups_max' indices to the +// 'num_htree_groups' groups. If 'num_htree_groups_max' > 'num_htree_groups', +// some of those indices map to -1. This is used for non-balanced codes to +// limit memory usage. +WEBP_NODISCARD int ReadHuffmanCodesHelper( + int color_cache_bits, int num_htree_groups, int num_htree_groups_max, + const int* const mapping, VP8LDecoder* const dec, + HuffmanTables* const huffman_tables, HTreeGroup** const htree_groups); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8LI_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webp_dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webp_dec.c new file mode 100644 index 0000000000..49ef205c8b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webp_dec.c @@ -0,0 +1,871 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Main decoding functions for WEBP images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/utils.h" +#include "src/webp/mux_types.h" // ALPHA_FLAG +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// RIFF layout is: +// Offset tag +// 0...3 "RIFF" 4-byte tag +// 4...7 size of image data (including metadata) starting at offset 8 +// 8...11 "WEBP" our form-type signature +// The RIFF container (12 bytes) is followed by appropriate chunks: +// 12..15 "VP8 ": 4-bytes tags, signaling the use of VP8 video format +// 16..19 size of the raw VP8 image data, starting at offset 20 +// 20.... the VP8 bytes +// Or, +// 12..15 "VP8L": 4-bytes tags, signaling the use of VP8L lossless format +// 16..19 size of the raw VP8L image data, starting at offset 20 +// 20.... the VP8L bytes +// Or, +// 12..15 "VP8X": 4-bytes tags, describing the extended-VP8 chunk. +// 16..19 size of the VP8X chunk starting at offset 20. +// 20..23 VP8X flags bit-map corresponding to the chunk-types present. +// 24..26 Width of the Canvas Image. +// 27..29 Height of the Canvas Image. +// There can be extra chunks after the "VP8X" chunk (ICCP, ANMF, VP8, VP8L, +// XMP, EXIF ...) +// All sizes are in little-endian order. +// Note: chunk data size must be padded to multiple of 2 when written. + +// Validates the RIFF container (if detected) and skips over it. +// If a RIFF container is detected, returns: +// VP8_STATUS_BITSTREAM_ERROR for invalid header, +// VP8_STATUS_NOT_ENOUGH_DATA for truncated data if have_all_data is true, +// and VP8_STATUS_OK otherwise. +// In case there are not enough bytes (partial RIFF container), return 0 for +// *riff_size. Else return the RIFF size extracted from the header. +static VP8StatusCode ParseRIFF(const uint8_t** const data, + size_t* const data_size, int have_all_data, + size_t* const riff_size) { + assert(data != NULL); + assert(data_size != NULL); + assert(riff_size != NULL); + + *riff_size = 0; // Default: no RIFF present. + if (*data_size >= RIFF_HEADER_SIZE && !memcmp(*data, "RIFF", TAG_SIZE)) { + if (memcmp(*data + 8, "WEBP", TAG_SIZE)) { + return VP8_STATUS_BITSTREAM_ERROR; // Wrong image file signature. + } else { + const uint32_t size = GetLE32(*data + TAG_SIZE); + // Check that we have at least one chunk (i.e "WEBP" + "VP8?nnnn"). + if (size < TAG_SIZE + CHUNK_HEADER_SIZE) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } + // We have a RIFF container. Skip it. + *riff_size = size; + *data += RIFF_HEADER_SIZE; + *data_size -= RIFF_HEADER_SIZE; + } + } + return VP8_STATUS_OK; +} + +// Validates the VP8X header and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8X chunk is found, found_vp8x is set to true and *width_ptr, +// *height_ptr and *flags_ptr are set to the corresponding values extracted +// from the VP8X chunk. +static VP8StatusCode ParseVP8X(const uint8_t** const data, + size_t* const data_size, + int* const found_vp8x, + int* const width_ptr, int* const height_ptr, + uint32_t* const flags_ptr) { + const uint32_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; + assert(data != NULL); + assert(data_size != NULL); + assert(found_vp8x != NULL); + + *found_vp8x = 0; + + if (*data_size < CHUNK_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + + if (!memcmp(*data, "VP8X", TAG_SIZE)) { + int width, height; + uint32_t flags; + const uint32_t chunk_size = GetLE32(*data + TAG_SIZE); + if (chunk_size != VP8X_CHUNK_SIZE) { + return VP8_STATUS_BITSTREAM_ERROR; // Wrong chunk size. + } + + // Verify if enough data is available to validate the VP8X chunk. + if (*data_size < vp8x_size) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + flags = GetLE32(*data + 8); + width = 1 + GetLE24(*data + 12); + height = 1 + GetLE24(*data + 15); + if (width * (uint64_t)height >= MAX_IMAGE_AREA) { + return VP8_STATUS_BITSTREAM_ERROR; // image is too large + } + + if (flags_ptr != NULL) *flags_ptr = flags; + if (width_ptr != NULL) *width_ptr = width; + if (height_ptr != NULL) *height_ptr = height; + // Skip over VP8X header bytes. + *data += vp8x_size; + *data_size -= vp8x_size; + *found_vp8x = 1; + } + return VP8_STATUS_OK; +} + +// Skips to the next VP8/VP8L chunk header in the data given the size of the +// RIFF chunk 'riff_size'. +// Returns VP8_STATUS_BITSTREAM_ERROR if any invalid chunk size is encountered, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If an alpha chunk is found, *alpha_data and *alpha_size are set +// appropriately. +static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, + size_t* const data_size, + size_t const riff_size, + const uint8_t** const alpha_data, + size_t* const alpha_size) { + const uint8_t* buf; + size_t buf_size; + uint32_t total_size = TAG_SIZE + // "WEBP". + CHUNK_HEADER_SIZE + // "VP8Xnnnn". + VP8X_CHUNK_SIZE; // data. + assert(data != NULL); + assert(data_size != NULL); + buf = *data; + buf_size = *data_size; + + assert(alpha_data != NULL); + assert(alpha_size != NULL); + *alpha_data = NULL; + *alpha_size = 0; + + while (1) { + uint32_t chunk_size; + uint32_t disk_chunk_size; // chunk_size with padding + + *data = buf; + *data_size = buf_size; + + if (buf_size < CHUNK_HEADER_SIZE) { // Insufficient data. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + + chunk_size = GetLE32(buf + TAG_SIZE); + if (chunk_size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. + } + // For odd-sized chunk-payload, there's one byte padding at the end. + disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1u; + total_size += disk_chunk_size; + + // Check that total bytes skipped so far does not exceed riff_size. + if (riff_size > 0 && (total_size > riff_size)) { + return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. + } + + // Start of a (possibly incomplete) VP8/VP8L chunk implies that we have + // parsed all the optional chunks. + // Note: This check must occur before the check 'buf_size < disk_chunk_size' + // below to allow incomplete VP8/VP8L chunks. + if (!memcmp(buf, "VP8 ", TAG_SIZE) || + !memcmp(buf, "VP8L", TAG_SIZE)) { + return VP8_STATUS_OK; + } + + if (buf_size < disk_chunk_size) { // Insufficient data. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + + if (!memcmp(buf, "ALPH", TAG_SIZE)) { // A valid ALPH header. + *alpha_data = buf + CHUNK_HEADER_SIZE; + *alpha_size = chunk_size; + } + + // We have a full and valid chunk; skip it. + buf += disk_chunk_size; + buf_size -= disk_chunk_size; + } +} + +// Validates the VP8/VP8L Header ("VP8 nnnn" or "VP8L nnnn") and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid (chunk larger than +// riff_size) VP8/VP8L header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8/VP8L chunk is found, *chunk_size is set to the total number of bytes +// extracted from the VP8/VP8L chunk header. +// The flag '*is_lossless' is set to 1 in case of VP8L chunk / raw VP8L data. +static VP8StatusCode ParseVP8Header(const uint8_t** const data_ptr, + size_t* const data_size, int have_all_data, + size_t riff_size, size_t* const chunk_size, + int* const is_lossless) { + const uint8_t* const data = *data_ptr; + const int is_vp8 = !memcmp(data, "VP8 ", TAG_SIZE); + const int is_vp8l = !memcmp(data, "VP8L", TAG_SIZE); + const uint32_t minimal_size = + TAG_SIZE + CHUNK_HEADER_SIZE; // "WEBP" + "VP8 nnnn" OR + // "WEBP" + "VP8Lnnnn" + assert(data != NULL); + assert(data_size != NULL); + assert(chunk_size != NULL); + assert(is_lossless != NULL); + + if (*data_size < CHUNK_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + + if (is_vp8 || is_vp8l) { + // Bitstream contains VP8/VP8L header. + const uint32_t size = GetLE32(data + TAG_SIZE); + if ((riff_size >= minimal_size) && (size > riff_size - minimal_size)) { + return VP8_STATUS_BITSTREAM_ERROR; // Inconsistent size information. + } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } + // Skip over CHUNK_HEADER_SIZE bytes from VP8/VP8L Header. + *chunk_size = size; + *data_ptr += CHUNK_HEADER_SIZE; + *data_size -= CHUNK_HEADER_SIZE; + *is_lossless = is_vp8l; + } else { + // Raw VP8/VP8L bitstream (no header). + *is_lossless = VP8LCheckSignature(data, *data_size); + *chunk_size = *data_size; + } + + return VP8_STATUS_OK; +} + +//------------------------------------------------------------------------------ + +// Fetch '*width', '*height', '*has_alpha' and fill out 'headers' based on +// 'data'. All the output parameters may be NULL. If 'headers' is NULL only the +// minimal amount will be read to fetch the remaining parameters. +// If 'headers' is non-NULL this function will attempt to locate both alpha +// data (with or without a VP8X chunk) and the bitstream chunk (VP8/VP8L). +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +static VP8StatusCode ParseHeadersInternal(const uint8_t* data, + size_t data_size, + int* const width, + int* const height, + int* const has_alpha, + int* const has_animation, + int* const format, + WebPHeaderStructure* const headers) { + int canvas_width = 0; + int canvas_height = 0; + int image_width = 0; + int image_height = 0; + int found_riff = 0; + int found_vp8x = 0; + int animation_present = 0; + const int have_all_data = (headers != NULL) ? headers->have_all_data : 0; + + VP8StatusCode status; + WebPHeaderStructure hdrs; + + if (data == NULL || data_size < RIFF_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; + } + memset(&hdrs, 0, sizeof(hdrs)); + hdrs.data = data; + hdrs.data_size = data_size; + + // Skip over RIFF header. + status = ParseRIFF(&data, &data_size, have_all_data, &hdrs.riff_size); + if (status != VP8_STATUS_OK) { + return status; // Wrong RIFF header / insufficient data. + } + found_riff = (hdrs.riff_size > 0); + + // Skip over VP8X. + { + uint32_t flags = 0; + status = ParseVP8X(&data, &data_size, &found_vp8x, + &canvas_width, &canvas_height, &flags); + if (status != VP8_STATUS_OK) { + return status; // Wrong VP8X / insufficient data. + } + animation_present = !!(flags & ANIMATION_FLAG); + if (!found_riff && found_vp8x) { + // Note: This restriction may be removed in the future, if it becomes + // necessary to send VP8X chunk to the decoder. + return VP8_STATUS_BITSTREAM_ERROR; + } + if (has_alpha != NULL) *has_alpha = !!(flags & ALPHA_FLAG); + if (has_animation != NULL) *has_animation = animation_present; + if (format != NULL) *format = 0; // default = undefined + + image_width = canvas_width; + image_height = canvas_height; + if (found_vp8x && animation_present && headers == NULL) { + status = VP8_STATUS_OK; + goto ReturnWidthHeight; // Just return features from VP8X header. + } + } + + if (data_size < TAG_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + + // Skip over optional chunks if data started with "RIFF + VP8X" or "ALPH". + if ((found_riff && found_vp8x) || + (!found_riff && !found_vp8x && !memcmp(data, "ALPH", TAG_SIZE))) { + status = ParseOptionalChunks(&data, &data_size, hdrs.riff_size, + &hdrs.alpha_data, &hdrs.alpha_data_size); + if (status != VP8_STATUS_OK) { + goto ReturnWidthHeight; // Invalid chunk size / insufficient data. + } + } + + // Skip over VP8/VP8L header. + status = ParseVP8Header(&data, &data_size, have_all_data, hdrs.riff_size, + &hdrs.compressed_size, &hdrs.is_lossless); + if (status != VP8_STATUS_OK) { + goto ReturnWidthHeight; // Wrong VP8/VP8L chunk-header / insufficient data. + } + if (hdrs.compressed_size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; + } + + if (format != NULL && !animation_present) { + *format = hdrs.is_lossless ? 2 : 1; + } + + if (!hdrs.is_lossless) { + if (data_size < VP8_FRAME_HEADER_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + // Validates raw VP8 data. + if (!VP8GetInfo(data, data_size, (uint32_t)hdrs.compressed_size, + &image_width, &image_height)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } else { + if (data_size < VP8L_FRAME_HEADER_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + // Validates raw VP8L data. + if (!VP8LGetInfo(data, data_size, &image_width, &image_height, has_alpha)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } + // Validates image size coherency. + if (found_vp8x) { + if (canvas_width != image_width || canvas_height != image_height) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } + if (headers != NULL) { + *headers = hdrs; + headers->offset = data - headers->data; + assert((uint64_t)(data - headers->data) < MAX_CHUNK_PAYLOAD); + assert(headers->offset == headers->data_size - data_size); + } + ReturnWidthHeight: + if (status == VP8_STATUS_OK || + (status == VP8_STATUS_NOT_ENOUGH_DATA && found_vp8x && headers == NULL)) { + if (has_alpha != NULL) { + // If the data did not contain a VP8X/VP8L chunk the only definitive way + // to set this is by looking for alpha data (from an ALPH chunk). + *has_alpha |= (hdrs.alpha_data != NULL); + } + if (width != NULL) *width = image_width; + if (height != NULL) *height = image_height; + return VP8_STATUS_OK; + } else { + return status; + } +} + +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) { + // status is marked volatile as a workaround for a clang-3.8 (aarch64) bug + volatile VP8StatusCode status; + int has_animation = 0; + assert(headers != NULL); + // fill out headers, ignore width/height/has_alpha. + status = ParseHeadersInternal(headers->data, headers->data_size, + NULL, NULL, NULL, &has_animation, + NULL, headers); + if (status == VP8_STATUS_OK || status == VP8_STATUS_NOT_ENOUGH_DATA) { + // The WebPDemux API + libwebp can be used to decode individual + // uncomposited frames or the WebPAnimDecoder can be used to fully + // reconstruct them (see webp/demux.h). + if (has_animation) { + status = VP8_STATUS_UNSUPPORTED_FEATURE; + } + } + return status; +} + +//------------------------------------------------------------------------------ +// WebPDecParams + +void WebPResetDecParams(WebPDecParams* const params) { + if (params != NULL) { + memset(params, 0, sizeof(*params)); + } +} + +//------------------------------------------------------------------------------ +// "Into" decoding variants + +// Main flow +WEBP_NODISCARD static VP8StatusCode DecodeInto(const uint8_t* const data, + size_t data_size, + WebPDecParams* const params) { + VP8StatusCode status; + VP8Io io; + WebPHeaderStructure headers; + + headers.data = data; + headers.data_size = data_size; + headers.have_all_data = 1; + status = WebPParseHeaders(&headers); // Process Pre-VP8 chunks. + if (status != VP8_STATUS_OK) { + return status; + } + + assert(params != NULL); + if (!VP8InitIo(&io)) { + return VP8_STATUS_INVALID_PARAM; + } + io.data = headers.data + headers.offset; + io.data_size = headers.data_size - headers.offset; + WebPInitCustomIo(params, &io); // Plug the I/O functions. + + if (!headers.is_lossless) { + VP8Decoder* const dec = VP8New(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + dec->alpha_data_ = headers.alpha_data; + dec->alpha_data_size_ = headers.alpha_data_size; + + // Decode bitstream header, update io->width/io->height. + if (!VP8GetHeaders(dec, &io)) { + status = dec->status_; // An error occurred. Grab error status. + } else { + // Allocate/check output buffers. + status = WebPAllocateDecBuffer(io.width, io.height, params->options, + params->output); + if (status == VP8_STATUS_OK) { // Decode + // This change must be done before calling VP8Decode() + dec->mt_method_ = VP8GetThreadMethod(params->options, &headers, + io.width, io.height); + VP8InitDithering(params->options, dec); + if (!VP8Decode(dec, &io)) { + status = dec->status_; + } + } + } + VP8Delete(dec); + } else { + VP8LDecoder* const dec = VP8LNew(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + if (!VP8LDecodeHeader(dec, &io)) { + status = dec->status_; // An error occurred. Grab error status. + } else { + // Allocate/check output buffers. + status = WebPAllocateDecBuffer(io.width, io.height, params->options, + params->output); + if (status == VP8_STATUS_OK) { // Decode + if (!VP8LDecodeImage(dec)) { + status = dec->status_; + } + } + } + VP8LDelete(dec); + } + + if (status != VP8_STATUS_OK) { + WebPFreeDecBuffer(params->output); + } else { + if (params->options != NULL && params->options->flip) { + // This restores the original stride values if options->flip was used + // during the call to WebPAllocateDecBuffer above. + status = WebPFlipBuffer(params->output); + } + } + return status; +} + +// Helpers +WEBP_NODISCARD static uint8_t* DecodeIntoRGBABuffer(WEBP_CSP_MODE colorspace, + const uint8_t* const data, + size_t data_size, + uint8_t* const rgba, + int stride, size_t size) { + WebPDecParams params; + WebPDecBuffer buf; + if (rgba == NULL || !WebPInitDecBuffer(&buf)) { + return NULL; + } + WebPResetDecParams(¶ms); + params.output = &buf; + buf.colorspace = colorspace; + buf.u.RGBA.rgba = rgba; + buf.u.RGBA.stride = stride; + buf.u.RGBA.size = size; + buf.is_external_memory = 1; + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + return rgba; +} + +uint8_t* WebPDecodeRGBInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_RGB, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeRGBAInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_RGBA, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeARGBInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_ARGB, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeBGRInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_BGR, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeBGRAInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_BGRA, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeYUVInto(const uint8_t* data, size_t data_size, + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride) { + WebPDecParams params; + WebPDecBuffer output; + if (luma == NULL || !WebPInitDecBuffer(&output)) return NULL; + WebPResetDecParams(¶ms); + params.output = &output; + output.colorspace = MODE_YUV; + output.u.YUVA.y = luma; + output.u.YUVA.y_stride = luma_stride; + output.u.YUVA.y_size = luma_size; + output.u.YUVA.u = u; + output.u.YUVA.u_stride = u_stride; + output.u.YUVA.u_size = u_size; + output.u.YUVA.v = v; + output.u.YUVA.v_stride = v_stride; + output.u.YUVA.v_size = v_size; + output.is_external_memory = 1; + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + return luma; +} + +//------------------------------------------------------------------------------ + +WEBP_NODISCARD static uint8_t* Decode(WEBP_CSP_MODE mode, + const uint8_t* const data, + size_t data_size, int* const width, + int* const height, + WebPDecBuffer* const keep_info) { + WebPDecParams params; + WebPDecBuffer output; + + if (!WebPInitDecBuffer(&output)) { + return NULL; + } + WebPResetDecParams(¶ms); + params.output = &output; + output.colorspace = mode; + + // Retrieve (and report back) the required dimensions from bitstream. + if (!WebPGetInfo(data, data_size, &output.width, &output.height)) { + return NULL; + } + if (width != NULL) *width = output.width; + if (height != NULL) *height = output.height; + + // Decode + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + if (keep_info != NULL) { // keep track of the side-info + WebPCopyDecBuffer(&output, keep_info); + } + // return decoded samples (don't clear 'output'!) + return WebPIsRGBMode(mode) ? output.u.RGBA.rgba : output.u.YUVA.y; +} + +uint8_t* WebPDecodeRGB(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_RGB, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeRGBA(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_RGBA, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeARGB(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_ARGB, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeBGR(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_BGR, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeBGRA(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_BGRA, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeYUV(const uint8_t* data, size_t data_size, + int* width, int* height, uint8_t** u, uint8_t** v, + int* stride, int* uv_stride) { + // data, width and height are checked by Decode(). + if (u == NULL || v == NULL || stride == NULL || uv_stride == NULL) { + return NULL; + } + + { + WebPDecBuffer output; // only to preserve the side-infos + uint8_t* const out = Decode(MODE_YUV, data, data_size, + width, height, &output); + + if (out != NULL) { + const WebPYUVABuffer* const buf = &output.u.YUVA; + *u = buf->u; + *v = buf->v; + *stride = buf->y_stride; + *uv_stride = buf->u_stride; + assert(buf->u_stride == buf->v_stride); + } + return out; + } +} + +static void DefaultFeatures(WebPBitstreamFeatures* const features) { + assert(features != NULL); + memset(features, 0, sizeof(*features)); +} + +static VP8StatusCode GetFeatures(const uint8_t* const data, size_t data_size, + WebPBitstreamFeatures* const features) { + if (features == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + DefaultFeatures(features); + + // Only parse enough of the data to retrieve the features. + return ParseHeadersInternal(data, data_size, + &features->width, &features->height, + &features->has_alpha, &features->has_animation, + &features->format, NULL); +} + +//------------------------------------------------------------------------------ +// WebPGetInfo() + +int WebPGetInfo(const uint8_t* data, size_t data_size, + int* width, int* height) { + WebPBitstreamFeatures features; + + if (GetFeatures(data, data_size, &features) != VP8_STATUS_OK) { + return 0; + } + + if (width != NULL) { + *width = features.width; + } + if (height != NULL) { + *height = features.height; + } + + return 1; +} + +//------------------------------------------------------------------------------ +// Advance decoding API + +int WebPInitDecoderConfigInternal(WebPDecoderConfig* config, + int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // version mismatch + } + if (config == NULL) { + return 0; + } + memset(config, 0, sizeof(*config)); + DefaultFeatures(&config->input); + if (!WebPInitDecBuffer(&config->output)) { + return 0; + } + return 1; +} + +VP8StatusCode WebPGetFeaturesInternal(const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features, + int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return VP8_STATUS_INVALID_PARAM; // version mismatch + } + if (features == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + return GetFeatures(data, data_size, features); +} + +VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config) { + WebPDecParams params; + VP8StatusCode status; + + if (config == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + + status = GetFeatures(data, data_size, &config->input); + if (status != VP8_STATUS_OK) { + if (status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_BITSTREAM_ERROR; // Not-enough-data treated as error. + } + return status; + } + + WebPResetDecParams(¶ms); + params.options = &config->options; + params.output = &config->output; + if (WebPAvoidSlowMemory(params.output, &config->input)) { + // decoding to slow memory: use a temporary in-mem buffer to decode into. + WebPDecBuffer in_mem_buffer; + if (!WebPInitDecBuffer(&in_mem_buffer)) { + return VP8_STATUS_INVALID_PARAM; + } + in_mem_buffer.colorspace = config->output.colorspace; + in_mem_buffer.width = config->input.width; + in_mem_buffer.height = config->input.height; + params.output = &in_mem_buffer; + status = DecodeInto(data, data_size, ¶ms); + if (status == VP8_STATUS_OK) { // do the slow-copy + status = WebPCopyDecBufferPixels(&in_mem_buffer, &config->output); + } + WebPFreeDecBuffer(&in_mem_buffer); + } else { + status = DecodeInto(data, data_size, ¶ms); + } + + return status; +} + +//------------------------------------------------------------------------------ +// Cropping and rescaling. + +int WebPCheckCropDimensions(int image_width, int image_height, + int x, int y, int w, int h) { + return !(x < 0 || y < 0 || w <= 0 || h <= 0 || + x >= image_width || w > image_width || w > image_width - x || + y >= image_height || h > image_height || h > image_height - y); +} + +int WebPIoInitFromOptions(const WebPDecoderOptions* const options, + VP8Io* const io, WEBP_CSP_MODE src_colorspace) { + const int W = io->width; + const int H = io->height; + int x = 0, y = 0, w = W, h = H; + + // Cropping + io->use_cropping = (options != NULL) && options->use_cropping; + if (io->use_cropping) { + w = options->crop_width; + h = options->crop_height; + x = options->crop_left; + y = options->crop_top; + if (!WebPIsRGBMode(src_colorspace)) { // only snap for YUV420 + x &= ~1; + y &= ~1; + } + if (!WebPCheckCropDimensions(W, H, x, y, w, h)) { + return 0; // out of frame boundary error + } + } + io->crop_left = x; + io->crop_top = y; + io->crop_right = x + w; + io->crop_bottom = y + h; + io->mb_w = w; + io->mb_h = h; + + // Scaling + io->use_scaling = (options != NULL) && options->use_scaling; + if (io->use_scaling) { + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions(w, h, &scaled_width, &scaled_height)) { + return 0; + } + io->scaled_width = scaled_width; + io->scaled_height = scaled_height; + } + + // Filter + io->bypass_filtering = (options != NULL) && options->bypass_filtering; + + // Fancy upsampler +#ifdef FANCY_UPSAMPLING + io->fancy_upsampling = (options == NULL) || (!options->no_fancy_upsampling); +#endif + + if (io->use_scaling) { + // disable filter (only for large downscaling ratio). + io->bypass_filtering |= (io->scaled_width < W * 3 / 4) && + (io->scaled_height < H * 3 / 4); + io->fancy_upsampling = 0; + } + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webpi_dec.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webpi_dec.h new file mode 100644 index 0000000000..77bf5264b7 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dec/webpi_dec.h @@ -0,0 +1,139 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header: WebP decoding parameters and custom IO on buffer +// +// Author: somnath@google.com (Somnath Banerjee) + +#ifndef WEBP_DEC_WEBPI_DEC_H_ +#define WEBP_DEC_WEBPI_DEC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "src/utils/rescaler_utils.h" +#include "src/dec/vp8_dec.h" +#include "src/webp/decode.h" + +//------------------------------------------------------------------------------ +// WebPDecParams: Decoding output parameters. Transient internal object. + +typedef struct WebPDecParams WebPDecParams; +typedef int (*OutputFunc)(const VP8Io* const io, WebPDecParams* const p); +typedef int (*OutputAlphaFunc)(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines); +typedef int (*OutputRowFunc)(WebPDecParams* const p, int y_pos, + int max_out_lines); + +struct WebPDecParams { + WebPDecBuffer* output; // output buffer. + uint8_t* tmp_y, *tmp_u, *tmp_v; // cache for the fancy upsampler + // or used for tmp rescaling + + int last_y; // coordinate of the line that was last output + const WebPDecoderOptions* options; // if not NULL, use alt decoding features + + WebPRescaler* scaler_y, *scaler_u, *scaler_v, *scaler_a; // rescalers + void* memory; // overall scratch memory for the output work. + + OutputFunc emit; // output RGB or YUV samples + OutputAlphaFunc emit_alpha; // output alpha channel + OutputRowFunc emit_alpha_row; // output one line of rescaled alpha values +}; + +// Should be called first, before any use of the WebPDecParams object. +void WebPResetDecParams(WebPDecParams* const params); + +//------------------------------------------------------------------------------ +// Header parsing helpers + +// Structure storing a description of the RIFF headers. +typedef struct { + const uint8_t* data; // input buffer + size_t data_size; // input buffer size + int have_all_data; // true if all data is known to be available + size_t offset; // offset to main data chunk (VP8 or VP8L) + const uint8_t* alpha_data; // points to alpha chunk (if present) + size_t alpha_data_size; // alpha chunk size + size_t compressed_size; // VP8/VP8L compressed data size + size_t riff_size; // size of the riff payload (or 0 if absent) + int is_lossless; // true if a VP8L chunk is present +} WebPHeaderStructure; + +// Skips over all valid chunks prior to the first VP8/VP8L frame header. +// Returns: VP8_STATUS_OK, VP8_STATUS_BITSTREAM_ERROR (invalid header/chunk), +// VP8_STATUS_NOT_ENOUGH_DATA (partial input) or VP8_STATUS_UNSUPPORTED_FEATURE +// in the case of non-decodable features (animation for instance). +// In 'headers', compressed_size, offset, alpha_data, alpha_size, and lossless +// fields are updated appropriately upon success. +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers); + +//------------------------------------------------------------------------------ +// Misc utils + +// Returns true if crop dimensions are within image bounds. +int WebPCheckCropDimensions(int image_width, int image_height, + int x, int y, int w, int h); + +// Initializes VP8Io with custom setup, io and teardown functions. The default +// hooks will use the supplied 'params' as io->opaque handle. +void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io); + +// Setup crop_xxx fields, mb_w and mb_h in io. 'src_colorspace' refers +// to the *compressed* format, not the output one. +WEBP_NODISCARD int WebPIoInitFromOptions( + const WebPDecoderOptions* const options, VP8Io* const io, + WEBP_CSP_MODE src_colorspace); + +//------------------------------------------------------------------------------ +// Internal functions regarding WebPDecBuffer memory (in buffer.c). +// Don't really need to be externally visible for now. + +// Prepare 'buffer' with the requested initial dimensions width/height. +// If no external storage is supplied, initializes buffer by allocating output +// memory and setting up the stride information. Validate the parameters. Return +// an error code in case of problem (no memory, or invalid stride / size / +// dimension / etc.). If *options is not NULL, also verify that the options' +// parameters are valid and apply them to the width/height dimensions of the +// output buffer. This takes cropping / scaling / rotation into account. +// Also incorporates the options->flip flag to flip the buffer parameters if +// needed. +VP8StatusCode WebPAllocateDecBuffer(int width, int height, + const WebPDecoderOptions* const options, + WebPDecBuffer* const buffer); + +// Flip buffer vertically by negating the various strides. +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer); + +// Copy 'src' into 'dst' buffer, making sure 'dst' is not marked as owner of the +// memory (still held by 'src'). No pixels are copied. +void WebPCopyDecBuffer(const WebPDecBuffer* const src, + WebPDecBuffer* const dst); + +// Copy and transfer ownership from src to dst (beware of parameter order!) +void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst); + +// Copy pixels from 'src' into a *preallocated* 'dst' buffer. Returns +// VP8_STATUS_INVALID_PARAM if the 'dst' is not set up correctly for the copy. +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src, + WebPDecBuffer* const dst); + +// Returns true if decoding will be slow with the current configuration +// and bitstream features. +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_WEBPI_DEC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing.c new file mode 100644 index 0000000000..1d152f24da --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing.c @@ -0,0 +1,496 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "src/dsp/dsp.h" + +// Tables can be faster on some platform but incur some extra binary size (~2k). +#if !defined(USE_TABLES_FOR_ALPHA_MULT) +#define USE_TABLES_FOR_ALPHA_MULT 0 // ALTERNATE_CODE +#endif + + +// ----------------------------------------------------------------------------- + +#define MFIX 24 // 24bit fixed-point arithmetic +#define HALF ((1u << MFIX) >> 1) +#define KINV_255 ((1u << MFIX) / 255u) + +static uint32_t Mult(uint8_t x, uint32_t mult) { + const uint32_t v = (x * mult + HALF) >> MFIX; + assert(v <= 255); // <- 24bit precision is enough to ensure that. + return v; +} + +#if (USE_TABLES_FOR_ALPHA_MULT == 1) + +static const uint32_t kMultTables[2][256] = { + { // (255u << MFIX) / alpha + 0x00000000, 0xff000000, 0x7f800000, 0x55000000, 0x3fc00000, 0x33000000, + 0x2a800000, 0x246db6db, 0x1fe00000, 0x1c555555, 0x19800000, 0x172e8ba2, + 0x15400000, 0x139d89d8, 0x1236db6d, 0x11000000, 0x0ff00000, 0x0f000000, + 0x0e2aaaaa, 0x0d6bca1a, 0x0cc00000, 0x0c249249, 0x0b9745d1, 0x0b1642c8, + 0x0aa00000, 0x0a333333, 0x09cec4ec, 0x0971c71c, 0x091b6db6, 0x08cb08d3, + 0x08800000, 0x0839ce73, 0x07f80000, 0x07ba2e8b, 0x07800000, 0x07492492, + 0x07155555, 0x06e45306, 0x06b5e50d, 0x0689d89d, 0x06600000, 0x063831f3, + 0x06124924, 0x05ee23b8, 0x05cba2e8, 0x05aaaaaa, 0x058b2164, 0x056cefa8, + 0x05500000, 0x05343eb1, 0x05199999, 0x05000000, 0x04e76276, 0x04cfb2b7, + 0x04b8e38e, 0x04a2e8ba, 0x048db6db, 0x0479435e, 0x04658469, 0x045270d0, + 0x04400000, 0x042e29f7, 0x041ce739, 0x040c30c3, 0x03fc0000, 0x03ec4ec4, + 0x03dd1745, 0x03ce540f, 0x03c00000, 0x03b21642, 0x03a49249, 0x03976fc6, + 0x038aaaaa, 0x037e3f1f, 0x03722983, 0x03666666, 0x035af286, 0x034fcace, + 0x0344ec4e, 0x033a5440, 0x03300000, 0x0325ed09, 0x031c18f9, 0x0312818a, + 0x03092492, 0x03000000, 0x02f711dc, 0x02ee5846, 0x02e5d174, 0x02dd7baf, + 0x02d55555, 0x02cd5cd5, 0x02c590b2, 0x02bdef7b, 0x02b677d4, 0x02af286b, + 0x02a80000, 0x02a0fd5c, 0x029a1f58, 0x029364d9, 0x028ccccc, 0x0286562d, + 0x02800000, 0x0279c952, 0x0273b13b, 0x026db6db, 0x0267d95b, 0x026217ec, + 0x025c71c7, 0x0256e62a, 0x0251745d, 0x024c1bac, 0x0246db6d, 0x0241b2f9, + 0x023ca1af, 0x0237a6f4, 0x0232c234, 0x022df2df, 0x02293868, 0x02249249, + 0x02200000, 0x021b810e, 0x021714fb, 0x0212bb51, 0x020e739c, 0x020a3d70, + 0x02061861, 0x02020408, 0x01fe0000, 0x01fa0be8, 0x01f62762, 0x01f25213, + 0x01ee8ba2, 0x01ead3ba, 0x01e72a07, 0x01e38e38, 0x01e00000, 0x01dc7f10, + 0x01d90b21, 0x01d5a3e9, 0x01d24924, 0x01cefa8d, 0x01cbb7e3, 0x01c880e5, + 0x01c55555, 0x01c234f7, 0x01bf1f8f, 0x01bc14e5, 0x01b914c1, 0x01b61eed, + 0x01b33333, 0x01b05160, 0x01ad7943, 0x01aaaaaa, 0x01a7e567, 0x01a5294a, + 0x01a27627, 0x019fcbd2, 0x019d2a20, 0x019a90e7, 0x01980000, 0x01957741, + 0x0192f684, 0x01907da4, 0x018e0c7c, 0x018ba2e8, 0x018940c5, 0x0186e5f0, + 0x01849249, 0x018245ae, 0x01800000, 0x017dc11f, 0x017b88ee, 0x0179574e, + 0x01772c23, 0x01750750, 0x0172e8ba, 0x0170d045, 0x016ebdd7, 0x016cb157, + 0x016aaaaa, 0x0168a9b9, 0x0166ae6a, 0x0164b8a7, 0x0162c859, 0x0160dd67, + 0x015ef7bd, 0x015d1745, 0x015b3bea, 0x01596596, 0x01579435, 0x0155c7b4, + 0x01540000, 0x01523d03, 0x01507eae, 0x014ec4ec, 0x014d0fac, 0x014b5edc, + 0x0149b26c, 0x01480a4a, 0x01466666, 0x0144c6af, 0x01432b16, 0x0141938b, + 0x01400000, 0x013e7063, 0x013ce4a9, 0x013b5cc0, 0x0139d89d, 0x01385830, + 0x0136db6d, 0x01356246, 0x0133ecad, 0x01327a97, 0x01310bf6, 0x012fa0be, + 0x012e38e3, 0x012cd459, 0x012b7315, 0x012a150a, 0x0128ba2e, 0x01276276, + 0x01260dd6, 0x0124bc44, 0x01236db6, 0x01222222, 0x0120d97c, 0x011f93bc, + 0x011e50d7, 0x011d10c4, 0x011bd37a, 0x011a98ef, 0x0119611a, 0x01182bf2, + 0x0116f96f, 0x0115c988, 0x01149c34, 0x0113716a, 0x01124924, 0x01112358, + 0x01100000, 0x010edf12, 0x010dc087, 0x010ca458, 0x010b8a7d, 0x010a72f0, + 0x01095da8, 0x01084a9f, 0x010739ce, 0x01062b2e, 0x01051eb8, 0x01041465, + 0x01030c30, 0x01020612, 0x01010204, 0x01000000 }, + { // alpha * KINV_255 + 0x00000000, 0x00010101, 0x00020202, 0x00030303, 0x00040404, 0x00050505, + 0x00060606, 0x00070707, 0x00080808, 0x00090909, 0x000a0a0a, 0x000b0b0b, + 0x000c0c0c, 0x000d0d0d, 0x000e0e0e, 0x000f0f0f, 0x00101010, 0x00111111, + 0x00121212, 0x00131313, 0x00141414, 0x00151515, 0x00161616, 0x00171717, + 0x00181818, 0x00191919, 0x001a1a1a, 0x001b1b1b, 0x001c1c1c, 0x001d1d1d, + 0x001e1e1e, 0x001f1f1f, 0x00202020, 0x00212121, 0x00222222, 0x00232323, + 0x00242424, 0x00252525, 0x00262626, 0x00272727, 0x00282828, 0x00292929, + 0x002a2a2a, 0x002b2b2b, 0x002c2c2c, 0x002d2d2d, 0x002e2e2e, 0x002f2f2f, + 0x00303030, 0x00313131, 0x00323232, 0x00333333, 0x00343434, 0x00353535, + 0x00363636, 0x00373737, 0x00383838, 0x00393939, 0x003a3a3a, 0x003b3b3b, + 0x003c3c3c, 0x003d3d3d, 0x003e3e3e, 0x003f3f3f, 0x00404040, 0x00414141, + 0x00424242, 0x00434343, 0x00444444, 0x00454545, 0x00464646, 0x00474747, + 0x00484848, 0x00494949, 0x004a4a4a, 0x004b4b4b, 0x004c4c4c, 0x004d4d4d, + 0x004e4e4e, 0x004f4f4f, 0x00505050, 0x00515151, 0x00525252, 0x00535353, + 0x00545454, 0x00555555, 0x00565656, 0x00575757, 0x00585858, 0x00595959, + 0x005a5a5a, 0x005b5b5b, 0x005c5c5c, 0x005d5d5d, 0x005e5e5e, 0x005f5f5f, + 0x00606060, 0x00616161, 0x00626262, 0x00636363, 0x00646464, 0x00656565, + 0x00666666, 0x00676767, 0x00686868, 0x00696969, 0x006a6a6a, 0x006b6b6b, + 0x006c6c6c, 0x006d6d6d, 0x006e6e6e, 0x006f6f6f, 0x00707070, 0x00717171, + 0x00727272, 0x00737373, 0x00747474, 0x00757575, 0x00767676, 0x00777777, + 0x00787878, 0x00797979, 0x007a7a7a, 0x007b7b7b, 0x007c7c7c, 0x007d7d7d, + 0x007e7e7e, 0x007f7f7f, 0x00808080, 0x00818181, 0x00828282, 0x00838383, + 0x00848484, 0x00858585, 0x00868686, 0x00878787, 0x00888888, 0x00898989, + 0x008a8a8a, 0x008b8b8b, 0x008c8c8c, 0x008d8d8d, 0x008e8e8e, 0x008f8f8f, + 0x00909090, 0x00919191, 0x00929292, 0x00939393, 0x00949494, 0x00959595, + 0x00969696, 0x00979797, 0x00989898, 0x00999999, 0x009a9a9a, 0x009b9b9b, + 0x009c9c9c, 0x009d9d9d, 0x009e9e9e, 0x009f9f9f, 0x00a0a0a0, 0x00a1a1a1, + 0x00a2a2a2, 0x00a3a3a3, 0x00a4a4a4, 0x00a5a5a5, 0x00a6a6a6, 0x00a7a7a7, + 0x00a8a8a8, 0x00a9a9a9, 0x00aaaaaa, 0x00ababab, 0x00acacac, 0x00adadad, + 0x00aeaeae, 0x00afafaf, 0x00b0b0b0, 0x00b1b1b1, 0x00b2b2b2, 0x00b3b3b3, + 0x00b4b4b4, 0x00b5b5b5, 0x00b6b6b6, 0x00b7b7b7, 0x00b8b8b8, 0x00b9b9b9, + 0x00bababa, 0x00bbbbbb, 0x00bcbcbc, 0x00bdbdbd, 0x00bebebe, 0x00bfbfbf, + 0x00c0c0c0, 0x00c1c1c1, 0x00c2c2c2, 0x00c3c3c3, 0x00c4c4c4, 0x00c5c5c5, + 0x00c6c6c6, 0x00c7c7c7, 0x00c8c8c8, 0x00c9c9c9, 0x00cacaca, 0x00cbcbcb, + 0x00cccccc, 0x00cdcdcd, 0x00cecece, 0x00cfcfcf, 0x00d0d0d0, 0x00d1d1d1, + 0x00d2d2d2, 0x00d3d3d3, 0x00d4d4d4, 0x00d5d5d5, 0x00d6d6d6, 0x00d7d7d7, + 0x00d8d8d8, 0x00d9d9d9, 0x00dadada, 0x00dbdbdb, 0x00dcdcdc, 0x00dddddd, + 0x00dedede, 0x00dfdfdf, 0x00e0e0e0, 0x00e1e1e1, 0x00e2e2e2, 0x00e3e3e3, + 0x00e4e4e4, 0x00e5e5e5, 0x00e6e6e6, 0x00e7e7e7, 0x00e8e8e8, 0x00e9e9e9, + 0x00eaeaea, 0x00ebebeb, 0x00ececec, 0x00ededed, 0x00eeeeee, 0x00efefef, + 0x00f0f0f0, 0x00f1f1f1, 0x00f2f2f2, 0x00f3f3f3, 0x00f4f4f4, 0x00f5f5f5, + 0x00f6f6f6, 0x00f7f7f7, 0x00f8f8f8, 0x00f9f9f9, 0x00fafafa, 0x00fbfbfb, + 0x00fcfcfc, 0x00fdfdfd, 0x00fefefe, 0x00ffffff } +}; + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return kMultTables[!inverse][a]; +} + +#else + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return inverse ? (255u << MFIX) / a : a * KINV_255; +} + +#endif // USE_TABLES_FOR_ALPHA_MULT + +void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t argb = ptr[x]; + if (argb < 0xff000000u) { // alpha < 255 + if (argb <= 0x00ffffffu) { // alpha == 0 + ptr[x] = 0; + } else { + const uint32_t alpha = (argb >> 24) & 0xff; + const uint32_t scale = GetScale(alpha, inverse); + uint32_t out = argb & 0xff000000u; + out |= Mult(argb >> 0, scale) << 0; + out |= Mult(argb >> 8, scale) << 8; + out |= Mult(argb >> 16, scale) << 16; + ptr[x] = out; + } + } + } +} + +void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t a = alpha[x]; + if (a != 255) { + if (a == 0) { + ptr[x] = 0; + } else { + const uint32_t scale = GetScale(a, inverse); + ptr[x] = Mult(ptr[x], scale); + } + } + } +} + +#undef KINV_255 +#undef HALF +#undef MFIX + +void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); +void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); + +//------------------------------------------------------------------------------ +// Generic per-plane calls + +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultARGBRow((uint32_t*)ptr, width, inverse); + ptr += stride; + } +} + +void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride, + const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int num_rows, int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultRow(ptr, alpha, width, inverse); + ptr += stride; + alpha += alpha_stride; + } +} + +//------------------------------------------------------------------------------ +// Premultiplied modes + +// non dithered-modes + +// (x * a * 32897) >> 23 is bit-wise equivalent to (int)(x * a / 255.) +// for all 8bit x or a. For bit-wise equivalence to (int)(x * a / 255. + .5), +// one can use instead: (x * a * 65793 + (1 << 23)) >> 24 +#if 1 // (int)(x * a / 255.) +#define MULTIPLIER(a) ((a) * 32897U) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) +#else // (int)(x * a / 255. + .5) +#define MULTIPLIER(a) ((a) * 65793U) +#define PREMULTIPLY(x, m) (((x) * (m) + (1U << 23)) >> 24) +#endif + +#if !WEBP_NEON_OMIT_C_CODE +static void ApplyAlphaMultiply_C(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + while (h-- > 0) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + int i; + for (i = 0; i < w; ++i) { + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE +#undef MULTIPLIER +#undef PREMULTIPLY + +// rgbA4444 + +#define MULTIPLIER(a) ((a) * 0x1111) // 0x1111 ~= (1 << 16) / 15 + +static WEBP_INLINE uint8_t dither_hi(uint8_t x) { + return (x & 0xf0) | (x >> 4); +} + +static WEBP_INLINE uint8_t dither_lo(uint8_t x) { + return (x & 0x0f) | (x << 4); +} + +static WEBP_INLINE uint8_t multiply(uint8_t x, uint32_t m) { + return (x * m) >> 16; +} + +static WEBP_INLINE void ApplyAlphaMultiply4444_C(uint8_t* rgba4444, + int w, int h, int stride, + int rg_byte_pos /* 0 or 1 */) { + while (h-- > 0) { + int i; + for (i = 0; i < w; ++i) { + const uint32_t rg = rgba4444[2 * i + rg_byte_pos]; + const uint32_t ba = rgba4444[2 * i + (rg_byte_pos ^ 1)]; + const uint8_t a = ba & 0x0f; + const uint32_t mult = MULTIPLIER(a); + const uint8_t r = multiply(dither_hi(rg), mult); + const uint8_t g = multiply(dither_lo(rg), mult); + const uint8_t b = multiply(dither_hi(ba), mult); + rgba4444[2 * i + rg_byte_pos] = (r & 0xf0) | ((g >> 4) & 0x0f); + rgba4444[2 * i + (rg_byte_pos ^ 1)] = (b & 0xf0) | a; + } + rgba4444 += stride; + } +} +#undef MULTIPLIER + +static void ApplyAlphaMultiply_16b_C(uint8_t* rgba4444, + int w, int h, int stride) { +#if (WEBP_SWAP_16BIT_CSP == 1) + ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 1); +#else + ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 0); +#endif +} + +#if !WEBP_NEON_OMIT_C_CODE +static int DispatchAlpha_C(const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + uint32_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + + return (alpha_mask != 0xff); +} + +static void DispatchAlphaToGreen_C(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + dst[i] = alpha[i] << 8; // leave A/R/B channels zero'd. + } + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_C(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + uint8_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint8_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_mask &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + return (alpha_mask == 0xff); +} + +static void ExtractGreen_C(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + for (i = 0; i < size; ++i) alpha[i] = argb[i] >> 8; +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ + +static int HasAlpha8b_C(const uint8_t* src, int length) { + while (length-- > 0) if (*src++ != 0xff) return 1; + return 0; +} + +static int HasAlpha32b_C(const uint8_t* src, int length) { + int x; + for (x = 0; length-- > 0; x += 4) if (src[x] != 0xff) return 1; + return 0; +} + +static void AlphaReplace_C(uint32_t* src, int length, uint32_t color) { + int x; + for (x = 0; x < length; ++x) if ((src[x] >> 24) == 0) src[x] = color; +} + +//------------------------------------------------------------------------------ +// Simple channel manipulations. + +static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { + return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); +} + +#ifdef WORDS_BIGENDIAN +static void PackARGB_C(const uint8_t* WEBP_RESTRICT a, + const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, uint32_t* WEBP_RESTRICT out) { + int i; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } +} +#endif + +static void PackRGB_C(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out) { + int i, offset = 0; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(0xff, r[offset], g[offset], b[offset]); + offset += step; + } +} + +void (*WebPApplyAlphaMultiply)(uint8_t*, int, int, int, int); +void (*WebPApplyAlphaMultiply4444)(uint8_t*, int, int, int); +int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint8_t* WEBP_RESTRICT, int); +void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint32_t* WEBP_RESTRICT, int); +int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint8_t* WEBP_RESTRICT, int); +void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size); +#ifdef WORDS_BIGENDIAN +void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int, uint32_t*); +#endif +void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out); + +int (*WebPHasAlpha8b)(const uint8_t* src, int length); +int (*WebPHasAlpha32b)(const uint8_t* src, int length); +void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color); + +//------------------------------------------------------------------------------ +// Init function + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitAlphaProcessingMIPSdspR2(void); +extern void WebPInitAlphaProcessingSSE2(void); +extern void WebPInitAlphaProcessingSSE41(void); +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_DSP_INIT_FUNC(WebPInitAlphaProcessing) { + WebPMultARGBRow = WebPMultARGBRow_C; + WebPMultRow = WebPMultRow_C; + WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b_C; + +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_C; +#endif + WebPPackRGB = PackRGB_C; +#if !WEBP_NEON_OMIT_C_CODE + WebPApplyAlphaMultiply = ApplyAlphaMultiply_C; + WebPDispatchAlpha = DispatchAlpha_C; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_C; + WebPExtractAlpha = ExtractAlpha_C; + WebPExtractGreen = ExtractGreen_C; +#endif + + WebPHasAlpha8b = HasAlpha8b_C; + WebPHasAlpha32b = HasAlpha32b_C; + WebPAlphaReplace = AlphaReplace_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitAlphaProcessingSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitAlphaProcessingSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitAlphaProcessingMIPSdspR2(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitAlphaProcessingNEON(); + } +#endif + + assert(WebPMultARGBRow != NULL); + assert(WebPMultRow != NULL); + assert(WebPApplyAlphaMultiply != NULL); + assert(WebPApplyAlphaMultiply4444 != NULL); + assert(WebPDispatchAlpha != NULL); + assert(WebPDispatchAlphaToGreen != NULL); + assert(WebPExtractAlpha != NULL); + assert(WebPExtractGreen != NULL); +#ifdef WORDS_BIGENDIAN + assert(WebPPackARGB != NULL); +#endif + assert(WebPPackRGB != NULL); + assert(WebPHasAlpha8b != NULL); + assert(WebPHasAlpha32b != NULL); + assert(WebPAlphaReplace != NULL); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c new file mode 100644 index 0000000000..0090e87cd1 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_mips_dsp_r2.c @@ -0,0 +1,228 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +static int DispatchAlpha_MIPSdspR2(const uint8_t* alpha, int alpha_stride, + int width, int height, + uint8_t* dst, int dst_stride) { + uint32_t alpha_mask = 0xffffffff; + int i, j, temp0; + + for (j = 0; j < height; ++j) { + uint8_t* pdst = dst; + const uint8_t* palpha = alpha; + for (i = 0; i < (width >> 2); ++i) { + int temp1, temp2, temp3; + + __asm__ volatile ( + "ulw %[temp0], 0(%[palpha]) \n\t" + "addiu %[palpha], %[palpha], 4 \n\t" + "addiu %[pdst], %[pdst], 16 \n\t" + "srl %[temp1], %[temp0], 8 \n\t" + "srl %[temp2], %[temp0], 16 \n\t" + "srl %[temp3], %[temp0], 24 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "sb %[temp0], -16(%[pdst]) \n\t" + "sb %[temp1], -12(%[pdst]) \n\t" + "sb %[temp2], -8(%[pdst]) \n\t" + "sb %[temp3], -4(%[pdst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [palpha]"+r"(palpha), [pdst]"+r"(pdst), + [alpha_mask]"+r"(alpha_mask) + : + : "memory" + ); + } + + for (i = 0; i < (width & 3); ++i) { + __asm__ volatile ( + "lbu %[temp0], 0(%[palpha]) \n\t" + "addiu %[palpha], %[palpha], 1 \n\t" + "sb %[temp0], 0(%[pdst]) \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "addiu %[pdst], %[pdst], 4 \n\t" + : [temp0]"=&r"(temp0), [palpha]"+r"(palpha), [pdst]"+r"(pdst), + [alpha_mask]"+r"(alpha_mask) + : + : "memory" + ); + } + alpha += alpha_stride; + dst += dst_stride; + } + + __asm__ volatile ( + "ext %[temp0], %[alpha_mask], 0, 16 \n\t" + "srl %[alpha_mask], %[alpha_mask], 16 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + "ext %[temp0], %[alpha_mask], 0, 8 \n\t" + "srl %[alpha_mask], %[alpha_mask], 8 \n\t" + "and %[alpha_mask], %[alpha_mask], %[temp0] \n\t" + : [temp0]"=&r"(temp0), [alpha_mask]"+r"(alpha_mask) + : + ); + + return (alpha_mask != 0xff); +} + +static void MultARGBRow_MIPSdspR2(uint32_t* const ptr, int width, + int inverse) { + int x; + const uint32_t c_00ffffff = 0x00ffffffu; + const uint32_t c_ff000000 = 0xff000000u; + const uint32_t c_8000000 = 0x00800000u; + const uint32_t c_8000080 = 0x00800080u; + for (x = 0; x < width; ++x) { + const uint32_t argb = ptr[x]; + if (argb < 0xff000000u) { // alpha < 255 + if (argb <= 0x00ffffffu) { // alpha == 0 + ptr[x] = 0; + } else { + int temp0, temp1, temp2, temp3, alpha; + __asm__ volatile ( + "srl %[alpha], %[argb], 24 \n\t" + "replv.qb %[temp0], %[alpha] \n\t" + "and %[temp0], %[temp0], %[c_00ffffff] \n\t" + "beqz %[inverse], 0f \n\t" + "divu $zero, %[c_ff000000], %[alpha] \n\t" + "mflo %[temp0] \n\t" + "0: \n\t" + "andi %[temp1], %[argb], 0xff \n\t" + "ext %[temp2], %[argb], 8, 8 \n\t" + "ext %[temp3], %[argb], 16, 8 \n\t" + "mul %[temp1], %[temp1], %[temp0] \n\t" + "mul %[temp2], %[temp2], %[temp0] \n\t" + "mul %[temp3], %[temp3], %[temp0] \n\t" + "precrq.ph.w %[temp1], %[temp2], %[temp1] \n\t" + "addu %[temp3], %[temp3], %[c_8000000] \n\t" + "addu %[temp1], %[temp1], %[c_8000080] \n\t" + "precrq.ph.w %[temp3], %[argb], %[temp3] \n\t" + "precrq.qb.ph %[temp1], %[temp3], %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [alpha]"=&r"(alpha) + : [inverse]"r"(inverse), [c_00ffffff]"r"(c_00ffffff), + [c_8000000]"r"(c_8000000), [c_8000080]"r"(c_8000080), + [c_ff000000]"r"(c_ff000000), [argb]"r"(argb) + : "memory", "hi", "lo" + ); + ptr[x] = temp1; + } + } + } +} + +#ifdef WORDS_BIGENDIAN +static void PackARGB_MIPSdspR2(const uint8_t* a, const uint8_t* r, + const uint8_t* g, const uint8_t* b, int len, + uint32_t* out) { + int temp0, temp1, temp2, temp3, offset; + const int rest = len & 1; + const uint32_t* const loop_end = out + len - rest; + const int step = 4; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[a]) \n\t" + "lbux %[temp1], %[offset](%[r]) \n\t" + "lbux %[temp2], %[offset](%[g]) \n\t" + "lbux %[temp3], %[offset](%[b]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp1], %[temp3] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} +#endif // WORDS_BIGENDIAN + +static void PackRGB_MIPSdspR2(const uint8_t* r, const uint8_t* g, + const uint8_t* b, int len, int step, + uint32_t* out) { + int temp0, temp1, temp2, offset; + const int rest = len & 1; + const int a = 0xff; + const uint32_t* const loop_end = out + len - rest; + __asm__ volatile ( + "xor %[offset], %[offset], %[offset] \n\t" + "beq %[loop_end], %[out], 0f \n\t" + "2: \n\t" + "lbux %[temp0], %[offset](%[r]) \n\t" + "lbux %[temp1], %[offset](%[g]) \n\t" + "lbux %[temp2], %[offset](%[b]) \n\t" + "ins %[temp0], %[a], 16, 16 \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "addiu %[out], %[out], 4 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp2] \n\t" + "sw %[temp0], -4(%[out]) \n\t" + "addu %[offset], %[offset], %[step] \n\t" + "bne %[loop_end], %[out], 2b \n\t" + "0: \n\t" + "beq %[rest], $zero, 1f \n\t" + "lbux %[temp0], %[offset](%[r]) \n\t" + "lbux %[temp1], %[offset](%[g]) \n\t" + "lbux %[temp2], %[offset](%[b]) \n\t" + "ins %[temp0], %[a], 16, 16 \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp2] \n\t" + "sw %[temp0], 0(%[out]) \n\t" + "1: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [offset]"=&r"(offset), [out]"+&r"(out) + : [a]"r"(a), [r]"r"(r), [g]"r"(g), [b]"r"(b), [step]"r"(step), + [loop_end]"r"(loop_end), [rest]"r"(rest) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingMIPSdspR2(void) { + WebPDispatchAlpha = DispatchAlpha_MIPSdspR2; + WebPMultARGBRow = MultARGBRow_MIPSdspR2; +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_MIPSdspR2; +#endif + WebPPackRGB = PackRGB_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_neon.c new file mode 100644 index 0000000000..6716fb77f0 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_neon.c @@ -0,0 +1,194 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, NEON version. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +#define MULTIPLY_BY_ALPHA(V, ALPHA, OTHER) do { \ + const uint8x8_t alpha = (V).val[(ALPHA)]; \ + const uint16x8_t r1 = vmull_u8((V).val[1], alpha); \ + const uint16x8_t g1 = vmull_u8((V).val[2], alpha); \ + const uint16x8_t b1 = vmull_u8((V).val[(OTHER)], alpha); \ + /* we use: v / 255 = (v + 1 + (v >> 8)) >> 8 */ \ + const uint16x8_t r2 = vsraq_n_u16(r1, r1, 8); \ + const uint16x8_t g2 = vsraq_n_u16(g1, g1, 8); \ + const uint16x8_t b2 = vsraq_n_u16(b1, b1, 8); \ + const uint16x8_t r3 = vaddq_u16(r2, kOne); \ + const uint16x8_t g3 = vaddq_u16(g2, kOne); \ + const uint16x8_t b3 = vaddq_u16(b2, kOne); \ + (V).val[1] = vshrn_n_u16(r3, 8); \ + (V).val[2] = vshrn_n_u16(g3, 8); \ + (V).val[(OTHER)] = vshrn_n_u16(b3, 8); \ +} while (0) + +static void ApplyAlphaMultiply_NEON(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const uint16x8_t kOne = vdupq_n_u16(1u); + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i = 0; + if (alpha_first) { + for (; i + 8 <= w; i += 8) { + // load aaaa...|rrrr...|gggg...|bbbb... + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 0, 3); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } else { + for (; i + 8 <= w; i += 8) { + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 3, 0); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLY_BY_ALPHA +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_NEON(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + uint32_t alpha_mask = 0xffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(dst + 4 * i)); + const uint8x8_t alphas = vld1_u8(alpha + i); + rgbX.val[0] = alphas; + vst4_u8((uint8_t*)(dst + 4 * i), rgbX); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask *= 0x01010101; + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask != 0xffffffffu); +} + +static void DispatchAlphaToGreen_NEON(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + uint8x8x4_t greens; // leave A/R/B channels zero'd. + greens.val[0] = vdup_n_u8(0); + greens.val[2] = vdup_n_u8(0); + greens.val[3] = vdup_n_u8(0); + for (j = 0; j < height; ++j) { + for (i = 0; i + 8 <= width; i += 8) { + greens.val[1] = vld1_u8(alpha + i); + vst4_u8((uint8_t*)(dst + i), greens); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_NEON(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + uint32_t alpha_mask = 0xffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + const uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(argb + 4 * i)); + const uint8x8_t alphas = rgbX.val[0]; + vst1_u8((uint8_t*)(alpha + i), alphas); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + alpha[i] = argb[4 * i]; + alpha_mask &= alpha[i]; + } + argb += argb_stride; + alpha += alpha_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask *= 0x01010101; + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask == 0xffffffffu); +} + +static void ExtractGreen_NEON(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + for (i = 0; i + 16 <= size; i += 16) { + const uint8x16x4_t rgbX = vld4q_u8((const uint8_t*)(argb + i)); + const uint8x16_t greens = rgbX.val[1]; + vst1q_u8(alpha + i, greens); + } + for (; i < size; ++i) alpha[i] = (argb[i] >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ + +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingNEON(void) { + WebPApplyAlphaMultiply = ApplyAlphaMultiply_NEON; + WebPDispatchAlpha = DispatchAlpha_NEON; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_NEON; + WebPExtractAlpha = ExtractAlpha_NEON; + WebPExtractGreen = ExtractGreen_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse2.c new file mode 100644 index 0000000000..aa0cc2848a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse2.c @@ -0,0 +1,408 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_SSE2(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i zero = _mm_setzero_si128(); + const __m128i rgb_mask = _mm_set1_epi32((int)0xffffff00); // to preserve RGB + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0, ~0); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'dst[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~7; + + for (j = 0; j < height; ++j) { + __m128i* out = (__m128i*)dst; + for (i = 0; i < limit; i += 8) { + // load 8 alpha bytes + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + // load 8 dst pixels (32 bytes) + const __m128i b0_lo = _mm_loadu_si128(out + 0); + const __m128i b0_hi = _mm_loadu_si128(out + 1); + // mask dst alpha values + const __m128i b1_lo = _mm_and_si128(b0_lo, rgb_mask); + const __m128i b1_hi = _mm_and_si128(b0_hi, rgb_mask); + // combine + const __m128i b2_lo = _mm_or_si128(b1_lo, a2_lo); + const __m128i b2_hi = _mm_or_si128(b1_hi, a2_hi); + // store + _mm_storeu_si128(out + 0, b2_lo); + _mm_storeu_si128(out + 1, b2_hi); + // accumulate eight alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, a0); + out += 2; + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_and &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and != 0xff); +} + +static void DispatchAlphaToGreen_SSE2(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + const __m128i zero = _mm_setzero_si128(); + const int limit = width & ~15; + for (j = 0; j < height; ++j) { + for (i = 0; i < limit; i += 16) { // process 16 alpha bytes + const __m128i a0 = _mm_loadu_si128((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(zero, a0); // note the 'zero' first! + const __m128i b1 = _mm_unpackhi_epi8(zero, a0); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i b2_lo = _mm_unpacklo_epi16(b1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + const __m128i b2_hi = _mm_unpackhi_epi16(b1, zero); + _mm_storeu_si128((__m128i*)&dst[i + 0], a2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 4], a2_hi); + _mm_storeu_si128((__m128i*)&dst[i + 8], b2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 12], b2_hi); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_SSE2(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i a_mask = _mm_set1_epi32(0xff); // to preserve alpha + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0, ~0); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~7; + + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 8) { + // load 32 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i b0 = _mm_and_si128(a0, a_mask); + const __m128i b1 = _mm_and_si128(a1, a_mask); + const __m128i c0 = _mm_packs_epi32(b0, b1); + const __m128i d0 = _mm_packus_epi16(c0, c0); + // store + _mm_storel_epi64((__m128i*)&alpha[i], d0); + // accumulate eight alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 2; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xff); +} + +static void ExtractGreen_SSE2(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + const __m128i mask = _mm_set1_epi32(0xff); + const __m128i* src = (const __m128i*)argb; + + for (i = 0; i + 16 <= size; i += 16, src += 4) { + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i a2 = _mm_loadu_si128(src + 2); + const __m128i a3 = _mm_loadu_si128(src + 3); + const __m128i b0 = _mm_srli_epi32(a0, 8); + const __m128i b1 = _mm_srli_epi32(a1, 8); + const __m128i b2 = _mm_srli_epi32(a2, 8); + const __m128i b3 = _mm_srli_epi32(a3, 8); + const __m128i c0 = _mm_and_si128(b0, mask); + const __m128i c1 = _mm_and_si128(b1, mask); + const __m128i c2 = _mm_and_si128(b2, mask); + const __m128i c3 = _mm_and_si128(b3, mask); + const __m128i d0 = _mm_packs_epi32(c0, c1); + const __m128i d1 = _mm_packs_epi32(c2, c3); + const __m128i e = _mm_packus_epi16(d0, d1); + // store + _mm_storeu_si128((__m128i*)&alpha[i], e); + } + if (i + 8 <= size) { + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i b0 = _mm_srli_epi32(a0, 8); + const __m128i b1 = _mm_srli_epi32(a1, 8); + const __m128i c0 = _mm_and_si128(b0, mask); + const __m128i c1 = _mm_and_si128(b1, mask); + const __m128i d = _mm_packs_epi32(c0, c1); + const __m128i e = _mm_packus_epi16(d, d); + _mm_storel_epi64((__m128i*)&alpha[i], e); + i += 8; + } + for (; i < size; ++i) alpha[i] = argb[i] >> 8; +} + +//------------------------------------------------------------------------------ +// Non-dither premultiplied modes + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +// We can't use a 'const int' for the SHUFFLE value, because it has to be an +// immediate in the _mm_shufflexx_epi16() instruction. We really need a macro. +// We use: v / 255 = (v * 0x8081) >> 23, where v = alpha * {r,g,b} is a 16bit +// value. +#define APPLY_ALPHA(RGBX, SHUFFLE) do { \ + const __m128i argb0 = _mm_loadu_si128((const __m128i*)&(RGBX)); \ + const __m128i argb1_lo = _mm_unpacklo_epi8(argb0, zero); \ + const __m128i argb1_hi = _mm_unpackhi_epi8(argb0, zero); \ + const __m128i alpha0_lo = _mm_or_si128(argb1_lo, kMask); \ + const __m128i alpha0_hi = _mm_or_si128(argb1_hi, kMask); \ + const __m128i alpha1_lo = _mm_shufflelo_epi16(alpha0_lo, SHUFFLE); \ + const __m128i alpha1_hi = _mm_shufflelo_epi16(alpha0_hi, SHUFFLE); \ + const __m128i alpha2_lo = _mm_shufflehi_epi16(alpha1_lo, SHUFFLE); \ + const __m128i alpha2_hi = _mm_shufflehi_epi16(alpha1_hi, SHUFFLE); \ + /* alpha2 = [ff a0 a0 a0][ff a1 a1 a1] */ \ + const __m128i A0_lo = _mm_mullo_epi16(alpha2_lo, argb1_lo); \ + const __m128i A0_hi = _mm_mullo_epi16(alpha2_hi, argb1_hi); \ + const __m128i A1_lo = _mm_mulhi_epu16(A0_lo, kMult); \ + const __m128i A1_hi = _mm_mulhi_epu16(A0_hi, kMult); \ + const __m128i A2_lo = _mm_srli_epi16(A1_lo, 7); \ + const __m128i A2_hi = _mm_srli_epi16(A1_hi, 7); \ + const __m128i A3 = _mm_packus_epi16(A2_lo, A2_hi); \ + _mm_storeu_si128((__m128i*)&(RGBX), A3); \ +} while (0) + +static void ApplyAlphaMultiply_SSE2(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const __m128i zero = _mm_setzero_si128(); + const __m128i kMult = _mm_set1_epi16((short)0x8081); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0); + const int kSpan = 4; + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i; + if (!alpha_first) { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(2, 3, 3, 3)); + } + } else { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(0, 0, 0, 1)); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ +// Alpha detection + +static int HasAlpha8b_SSE2(const uint8_t* src, int length) { + const __m128i all_0xff = _mm_set1_epi8((char)0xff); + int i = 0; + for (; i + 16 <= length; i += 16) { + const __m128i v = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i bits = _mm_cmpeq_epi8(v, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i < length; ++i) if (src[i] != 0xff) return 1; + return 0; +} + +static int HasAlpha32b_SSE2(const uint8_t* src, int length) { + const __m128i alpha_mask = _mm_set1_epi32(0xff); + const __m128i all_0xff = _mm_set1_epi8((char)0xff); + int i = 0; + // We don't know if we can access the last 3 bytes after the last alpha + // value 'src[4 * length - 4]' (because we don't know if alpha is the first + // or the last byte of the quadruplet). Hence the '-3' protection below. + length = length * 4 - 3; // size in bytes + for (; i + 64 <= length; i += 64) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 16)); + const __m128i a2 = _mm_loadu_si128((const __m128i*)(src + i + 32)); + const __m128i a3 = _mm_loadu_si128((const __m128i*)(src + i + 48)); + const __m128i b0 = _mm_and_si128(a0, alpha_mask); + const __m128i b1 = _mm_and_si128(a1, alpha_mask); + const __m128i b2 = _mm_and_si128(a2, alpha_mask); + const __m128i b3 = _mm_and_si128(a3, alpha_mask); + const __m128i c0 = _mm_packs_epi32(b0, b1); + const __m128i c1 = _mm_packs_epi32(b2, b3); + const __m128i d = _mm_packus_epi16(c0, c1); + const __m128i bits = _mm_cmpeq_epi8(d, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i + 32 <= length; i += 32) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 16)); + const __m128i b0 = _mm_and_si128(a0, alpha_mask); + const __m128i b1 = _mm_and_si128(a1, alpha_mask); + const __m128i c = _mm_packs_epi32(b0, b1); + const __m128i d = _mm_packus_epi16(c, c); + const __m128i bits = _mm_cmpeq_epi8(d, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i <= length; i += 4) if (src[i] != 0xff) return 1; + return 0; +} + +static void AlphaReplace_SSE2(uint32_t* src, int length, uint32_t color) { + const __m128i m_color = _mm_set1_epi32((int)color); + const __m128i zero = _mm_setzero_si128(); + int i = 0; + for (; i + 8 <= length; i += 8) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 4)); + const __m128i b0 = _mm_srai_epi32(a0, 24); + const __m128i b1 = _mm_srai_epi32(a1, 24); + const __m128i c0 = _mm_cmpeq_epi32(b0, zero); + const __m128i c1 = _mm_cmpeq_epi32(b1, zero); + const __m128i d0 = _mm_and_si128(c0, m_color); + const __m128i d1 = _mm_and_si128(c1, m_color); + const __m128i e0 = _mm_andnot_si128(c0, a0); + const __m128i e1 = _mm_andnot_si128(c1, a1); + _mm_storeu_si128((__m128i*)(src + i + 0), _mm_or_si128(d0, e0)); + _mm_storeu_si128((__m128i*)(src + i + 4), _mm_or_si128(d1, e1)); + } + for (; i < length; ++i) if ((src[i] >> 24) == 0) src[i] = color; +} + +// ----------------------------------------------------------------------------- +// Apply alpha value to rows + +static void MultARGBRow_SSE2(uint32_t* const ptr, int width, int inverse) { + int x = 0; + if (!inverse) { + const int kSpan = 2; + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0, 0, 0, 0xff, 0, 0); + for (x = 0; x + kSpan <= width; x += kSpan) { + // To compute 'result = (int)(a * x / 255. + .5)', we use: + // tmp = a * v + 128, result = (tmp * 0x0101u) >> 16 + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&ptr[x]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i A2 = _mm_or_si128(A1, kMask); + const __m128i A3 = _mm_shufflelo_epi16(A2, _MM_SHUFFLE(2, 3, 3, 3)); + const __m128i A4 = _mm_shufflehi_epi16(A3, _MM_SHUFFLE(2, 3, 3, 3)); + // here, A4 = [ff a0 a0 a0][ff a1 a1 a1] + const __m128i A5 = _mm_mullo_epi16(A4, A1); + const __m128i A6 = _mm_add_epi16(A5, k128); + const __m128i A7 = _mm_mulhi_epu16(A6, kMult); + const __m128i A10 = _mm_packus_epi16(A7, zero); + _mm_storel_epi64((__m128i*)&ptr[x], A10); + } + } + width -= x; + if (width > 0) WebPMultARGBRow_C(ptr + x, width, inverse); +} + +static void MultRow_SSE2(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse) { + int x = 0; + if (!inverse) { + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + for (x = 0; x + 8 <= width; x += 8) { + const __m128i v0 = _mm_loadl_epi64((__m128i*)&ptr[x]); + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[x]); + const __m128i v1 = _mm_unpacklo_epi8(v0, zero); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i v2 = _mm_mullo_epi16(v1, a1); + const __m128i v3 = _mm_add_epi16(v2, k128); + const __m128i v4 = _mm_mulhi_epu16(v3, kMult); + const __m128i v5 = _mm_packus_epi16(v4, zero); + _mm_storel_epi64((__m128i*)&ptr[x], v5); + } + } + width -= x; + if (width > 0) WebPMultRow_C(ptr + x, alpha + x, width, inverse); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE2(void) { + WebPMultARGBRow = MultARGBRow_SSE2; + WebPMultRow = MultRow_SSE2; + WebPApplyAlphaMultiply = ApplyAlphaMultiply_SSE2; + WebPDispatchAlpha = DispatchAlpha_SSE2; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_SSE2; + WebPExtractAlpha = ExtractAlpha_SSE2; + WebPExtractGreen = ExtractGreen_SSE2; + + WebPHasAlpha8b = HasAlpha8b_SSE2; + WebPHasAlpha32b = HasAlpha32b_SSE2; + WebPAlphaReplace = AlphaReplace_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse41.c new file mode 100644 index 0000000000..1156ac3417 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/alpha_processing_sse41.c @@ -0,0 +1,92 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, SSE4.1 variant. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include + +//------------------------------------------------------------------------------ + +static int ExtractAlpha_SSE41(const uint8_t* WEBP_RESTRICT argb, + int argb_stride, int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i all_0xff = _mm_set1_epi32(~0); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~15; + const __m128i kCstAlpha0 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 12, 8, 4, 0); + const __m128i kCstAlpha1 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + 12, 8, 4, 0, -1, -1, -1, -1); + const __m128i kCstAlpha2 = _mm_set_epi8(-1, -1, -1, -1, 12, 8, 4, 0, + -1, -1, -1, -1, -1, -1, -1, -1); + const __m128i kCstAlpha3 = _mm_set_epi8(12, 8, 4, 0, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1); + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 16) { + // load 64 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i a2 = _mm_loadu_si128(src + 2); + const __m128i a3 = _mm_loadu_si128(src + 3); + const __m128i b0 = _mm_shuffle_epi8(a0, kCstAlpha0); + const __m128i b1 = _mm_shuffle_epi8(a1, kCstAlpha1); + const __m128i b2 = _mm_shuffle_epi8(a2, kCstAlpha2); + const __m128i b3 = _mm_shuffle_epi8(a3, kCstAlpha3); + const __m128i c0 = _mm_or_si128(b0, b1); + const __m128i c1 = _mm_or_si128(b2, b3); + const __m128i d0 = _mm_or_si128(c0, c1); + // store + _mm_storeu_si128((__m128i*)&alpha[i], d0); + // accumulate sixteen alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 4; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the sixteen alpha 'and' into an 8-bit mask. + alpha_and |= 0xff00u; // pretend the upper bits [8..15] were tested ok. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xffffu); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE41(void) { + WebPExtractAlpha = ExtractAlpha_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse2.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse2.h new file mode 100644 index 0000000000..e9f1ebff44 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse2.h @@ -0,0 +1,194 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE2_H_ +#define WEBP_DSP_COMMON_SSE2_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE2) + +#include + +//------------------------------------------------------------------------------ +// Quite useful macro for debugging. Left here for convenience. + +#if 0 +#include +static WEBP_INLINE void PrintReg(const __m128i r, const char* const name, + int size) { + int n; + union { + __m128i r; + uint8_t i8[16]; + uint16_t i16[8]; + uint32_t i32[4]; + uint64_t i64[2]; + } tmp; + tmp.r = r; + fprintf(stderr, "%s\t: ", name); + if (size == 8) { + for (n = 0; n < 16; ++n) fprintf(stderr, "%.2x ", tmp.i8[n]); + } else if (size == 16) { + for (n = 0; n < 8; ++n) fprintf(stderr, "%.4x ", tmp.i16[n]); + } else if (size == 32) { + for (n = 0; n < 4; ++n) fprintf(stderr, "%.8x ", tmp.i32[n]); + } else { + for (n = 0; n < 2; ++n) fprintf(stderr, "%.16lx ", tmp.i64[n]); + } + fprintf(stderr, "\n"); +} +#endif + +//------------------------------------------------------------------------------ +// Math functions. + +// Return the sum of all the 8b in the register. +static WEBP_INLINE int VP8HorizontalAdd8b(const __m128i* const a) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sad8x2 = _mm_sad_epu8(*a, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi32(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + return _mm_cvtsi128_si32(sum); +} + +// Transpose two 4x4 16b matrices horizontally stored in registers. +static WEBP_INLINE void VP8Transpose_2_4x4_16b( + const __m128i* const in0, const __m128i* const in1, + const __m128i* const in2, const __m128i* const in3, __m128i* const out0, + __m128i* const out1, __m128i* const out2, __m128i* const out3) { + // Transpose the two 4x4. + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + const __m128i transpose0_0 = _mm_unpacklo_epi16(*in0, *in1); + const __m128i transpose0_1 = _mm_unpacklo_epi16(*in2, *in3); + const __m128i transpose0_2 = _mm_unpackhi_epi16(*in0, *in1); + const __m128i transpose0_3 = _mm_unpackhi_epi16(*in2, *in3); + // a00 a10 a01 a11 a02 a12 a03 a13 + // a20 a30 a21 a31 a22 a32 a23 a33 + // b00 b10 b01 b11 b02 b12 b03 b13 + // b20 b30 b21 b31 b22 b32 b23 b33 + const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); + const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); + // a00 a10 a20 a30 a01 a11 a21 a31 + // b00 b10 b20 b30 b01 b11 b21 b31 + // a02 a12 a22 a32 a03 a13 a23 a33 + // b02 b12 a22 b32 b03 b13 b23 b33 + *out0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); + *out1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); + *out2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); + *out3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 +} + +//------------------------------------------------------------------------------ +// Channel mixing. + +// Function used several times in VP8PlanarTo24b. +// It samples the in buffer as follows: one every two unsigned char is stored +// at the beginning of the buffer, while the other half is stored at the end. +#define VP8PlanarTo24bHelper(IN, OUT) \ + do { \ + const __m128i v_mask = _mm_set1_epi16(0x00ff); \ + /* Take one every two upper 8b values.*/ \ + (OUT##0) = _mm_packus_epi16(_mm_and_si128((IN##0), v_mask), \ + _mm_and_si128((IN##1), v_mask)); \ + (OUT##1) = _mm_packus_epi16(_mm_and_si128((IN##2), v_mask), \ + _mm_and_si128((IN##3), v_mask)); \ + (OUT##2) = _mm_packus_epi16(_mm_and_si128((IN##4), v_mask), \ + _mm_and_si128((IN##5), v_mask)); \ + /* Take one every two lower 8b values.*/ \ + (OUT##3) = _mm_packus_epi16(_mm_srli_epi16((IN##0), 8), \ + _mm_srli_epi16((IN##1), 8)); \ + (OUT##4) = _mm_packus_epi16(_mm_srli_epi16((IN##2), 8), \ + _mm_srli_epi16((IN##3), 8)); \ + (OUT##5) = _mm_packus_epi16(_mm_srli_epi16((IN##4), 8), \ + _mm_srli_epi16((IN##5), 8)); \ + } while (0) + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE2( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + __m128i tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + VP8PlanarTo24bHelper(*in, tmp); + VP8PlanarTo24bHelper(tmp, *in); + VP8PlanarTo24bHelper(*in, tmp); + // We need to do it two more times than the example as we have sixteen bytes. + { + __m128i out0, out1, out2, out3, out4, out5; + VP8PlanarTo24bHelper(tmp, out); + VP8PlanarTo24bHelper(out, *in); + } +} + +#undef VP8PlanarTo24bHelper + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE2(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // Column-wise transpose. + const __m128i A0 = _mm_unpacklo_epi8(*in0, *in1); + const __m128i A1 = _mm_unpackhi_epi8(*in0, *in1); + const __m128i A2 = _mm_unpacklo_epi8(*in2, *in3); + const __m128i A3 = _mm_unpackhi_epi8(*in2, *in3); + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); + const __m128i B2 = _mm_unpacklo_epi8(A2, A3); + const __m128i B3 = _mm_unpackhi_epi8(A2, A3); + // C0 = g7 g6 ... g1 g0 | b7 b6 ... b1 b0 + // C1 = a7 a6 ... a1 a0 | r7 r6 ... r1 r0 + const __m128i C0 = _mm_unpacklo_epi8(B0, B1); + const __m128i C1 = _mm_unpackhi_epi8(B0, B1); + const __m128i C2 = _mm_unpacklo_epi8(B2, B3); + const __m128i C3 = _mm_unpackhi_epi8(B2, B3); + // Gather the channels. + *in0 = _mm_unpackhi_epi64(C1, C3); + *in1 = _mm_unpacklo_epi64(C1, C3); + *in2 = _mm_unpackhi_epi64(C0, C2); + *in3 = _mm_unpacklo_epi64(C0, C2); +} + +#endif // WEBP_USE_SSE2 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE2_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse41.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse41.h new file mode 100644 index 0000000000..2f173c024a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/common_sse41.h @@ -0,0 +1,132 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE41_H_ +#define WEBP_DSP_COMMON_SSE41_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE41) +#include + +//------------------------------------------------------------------------------ +// Channel mixing. +// Shuffles the input buffer as A0 0 0 A1 0 0 A2 ... +#define WEBP_SSE41_SHUFF(OUT, IN0, IN1) \ + OUT##0 = _mm_shuffle_epi8(*IN0, shuff0); \ + OUT##1 = _mm_shuffle_epi8(*IN0, shuff1); \ + OUT##2 = _mm_shuffle_epi8(*IN0, shuff2); \ + OUT##3 = _mm_shuffle_epi8(*IN1, shuff0); \ + OUT##4 = _mm_shuffle_epi8(*IN1, shuff1); \ + OUT##5 = _mm_shuffle_epi8(*IN1, shuff2); + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + __m128i R0, R1, R2, R3, R4, R5; + __m128i G0, G1, G2, G3, G4, G5; + __m128i B0, B1, B2, B3, B4, B5; + + // Process R. + { + const __m128i shuff0 = _mm_set_epi8( + 5, -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + -1, -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1); + WEBP_SSE41_SHUFF(R, in0, in1) + } + + // Process G. + { + // Same as before, just shifted to the left by one and including the right + // padding. + const __m128i shuff0 = _mm_set_epi8( + -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1); + const __m128i shuff1 = _mm_set_epi8( + 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5); + const __m128i shuff2 = _mm_set_epi8( + -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1); + WEBP_SSE41_SHUFF(G, in2, in3) + } + + // Process B. + { + const __m128i shuff0 = _mm_set_epi8( + -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1, -1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1, 10); + WEBP_SSE41_SHUFF(B, in4, in5) + } + + // OR the different channels. + { + const __m128i RG0 = _mm_or_si128(R0, G0); + const __m128i RG1 = _mm_or_si128(R1, G1); + const __m128i RG2 = _mm_or_si128(R2, G2); + const __m128i RG3 = _mm_or_si128(R3, G3); + const __m128i RG4 = _mm_or_si128(R4, G4); + const __m128i RG5 = _mm_or_si128(R5, G5); + *in0 = _mm_or_si128(RG0, B0); + *in1 = _mm_or_si128(RG1, B1); + *in2 = _mm_or_si128(RG2, B2); + *in3 = _mm_or_si128(RG3, B3); + *in4 = _mm_or_si128(RG4, B4); + *in5 = _mm_or_si128(RG5, B5); + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE41(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // aaaarrrrggggbbbb + const __m128i shuff0 = + _mm_set_epi8(15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0); + const __m128i A0 = _mm_shuffle_epi8(*in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(*in1, shuff0); + const __m128i A2 = _mm_shuffle_epi8(*in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(*in3, shuff0); + // A0A1R0R1 + // G0G1B0B1 + // A2A3R2R3 + // G0G1B0B1 + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_unpackhi_epi32(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_unpackhi_epi32(A2, A3); + *in3 = _mm_unpacklo_epi64(B0, B2); + *in2 = _mm_unpackhi_epi64(B0, B2); + *in1 = _mm_unpacklo_epi64(B1, B3); + *in0 = _mm_unpackhi_epi64(B1, B3); +} + +#endif // WEBP_USE_SSE41 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE41_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost.c new file mode 100644 index 0000000000..73d2140177 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost.c @@ -0,0 +1,412 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" +#include "src/enc/cost_enc.h" + +//------------------------------------------------------------------------------ +// Boolean-cost cost table + +const uint16_t VP8EntropyCost[256] = { + 1792, 1792, 1792, 1536, 1536, 1408, 1366, 1280, 1280, 1216, + 1178, 1152, 1110, 1076, 1061, 1024, 1024, 992, 968, 951, + 939, 911, 896, 878, 871, 854, 838, 820, 811, 794, + 786, 768, 768, 752, 740, 732, 720, 709, 704, 690, + 683, 672, 666, 655, 647, 640, 631, 622, 615, 607, + 598, 592, 586, 576, 572, 564, 559, 555, 547, 541, + 534, 528, 522, 512, 512, 504, 500, 494, 488, 483, + 477, 473, 467, 461, 458, 452, 448, 443, 438, 434, + 427, 424, 419, 415, 410, 406, 403, 399, 394, 390, + 384, 384, 377, 374, 370, 366, 362, 359, 355, 351, + 347, 342, 342, 336, 333, 330, 326, 323, 320, 316, + 312, 308, 305, 302, 299, 296, 293, 288, 287, 283, + 280, 277, 274, 272, 268, 266, 262, 256, 256, 256, + 251, 248, 245, 242, 240, 237, 234, 232, 228, 226, + 223, 221, 218, 216, 214, 211, 208, 205, 203, 201, + 198, 196, 192, 191, 188, 187, 183, 181, 179, 176, + 175, 171, 171, 168, 165, 163, 160, 159, 156, 154, + 152, 150, 148, 146, 144, 142, 139, 138, 135, 133, + 131, 128, 128, 125, 123, 121, 119, 117, 115, 113, + 111, 110, 107, 105, 103, 102, 100, 98, 96, 94, + 92, 91, 89, 86, 86, 83, 82, 80, 77, 76, + 74, 73, 71, 69, 67, 66, 64, 63, 61, 59, + 57, 55, 54, 52, 51, 49, 47, 46, 44, 43, + 41, 40, 38, 36, 35, 33, 32, 30, 29, 27, + 25, 24, 22, 21, 19, 18, 16, 15, 13, 12, + 10, 9, 7, 6, 4, 3 +}; + +//------------------------------------------------------------------------------ +// Level cost tables + +// fixed costs for coding levels, deduce from the coding tree. +// This is only the part that doesn't depend on the probability state. +const uint16_t VP8LevelFixedCosts[MAX_LEVEL + 1] = { + 0, 256, 256, 256, 256, 432, 618, 630, + 731, 640, 640, 828, 901, 948, 1021, 1101, + 1174, 1221, 1294, 1042, 1085, 1115, 1158, 1202, + 1245, 1275, 1318, 1337, 1380, 1410, 1453, 1497, + 1540, 1570, 1613, 1280, 1295, 1317, 1332, 1358, + 1373, 1395, 1410, 1454, 1469, 1491, 1506, 1532, + 1547, 1569, 1584, 1601, 1616, 1638, 1653, 1679, + 1694, 1716, 1731, 1775, 1790, 1812, 1827, 1853, + 1868, 1890, 1905, 1727, 1733, 1742, 1748, 1759, + 1765, 1774, 1780, 1800, 1806, 1815, 1821, 1832, + 1838, 1847, 1853, 1878, 1884, 1893, 1899, 1910, + 1916, 1925, 1931, 1951, 1957, 1966, 1972, 1983, + 1989, 1998, 2004, 2027, 2033, 2042, 2048, 2059, + 2065, 2074, 2080, 2100, 2106, 2115, 2121, 2132, + 2138, 2147, 2153, 2178, 2184, 2193, 2199, 2210, + 2216, 2225, 2231, 2251, 2257, 2266, 2272, 2283, + 2289, 2298, 2304, 2168, 2174, 2183, 2189, 2200, + 2206, 2215, 2221, 2241, 2247, 2256, 2262, 2273, + 2279, 2288, 2294, 2319, 2325, 2334, 2340, 2351, + 2357, 2366, 2372, 2392, 2398, 2407, 2413, 2424, + 2430, 2439, 2445, 2468, 2474, 2483, 2489, 2500, + 2506, 2515, 2521, 2541, 2547, 2556, 2562, 2573, + 2579, 2588, 2594, 2619, 2625, 2634, 2640, 2651, + 2657, 2666, 2672, 2692, 2698, 2707, 2713, 2724, + 2730, 2739, 2745, 2540, 2546, 2555, 2561, 2572, + 2578, 2587, 2593, 2613, 2619, 2628, 2634, 2645, + 2651, 2660, 2666, 2691, 2697, 2706, 2712, 2723, + 2729, 2738, 2744, 2764, 2770, 2779, 2785, 2796, + 2802, 2811, 2817, 2840, 2846, 2855, 2861, 2872, + 2878, 2887, 2893, 2913, 2919, 2928, 2934, 2945, + 2951, 2960, 2966, 2991, 2997, 3006, 3012, 3023, + 3029, 3038, 3044, 3064, 3070, 3079, 3085, 3096, + 3102, 3111, 3117, 2981, 2987, 2996, 3002, 3013, + 3019, 3028, 3034, 3054, 3060, 3069, 3075, 3086, + 3092, 3101, 3107, 3132, 3138, 3147, 3153, 3164, + 3170, 3179, 3185, 3205, 3211, 3220, 3226, 3237, + 3243, 3252, 3258, 3281, 3287, 3296, 3302, 3313, + 3319, 3328, 3334, 3354, 3360, 3369, 3375, 3386, + 3392, 3401, 3407, 3432, 3438, 3447, 3453, 3464, + 3470, 3479, 3485, 3505, 3511, 3520, 3526, 3537, + 3543, 3552, 3558, 2816, 2822, 2831, 2837, 2848, + 2854, 2863, 2869, 2889, 2895, 2904, 2910, 2921, + 2927, 2936, 2942, 2967, 2973, 2982, 2988, 2999, + 3005, 3014, 3020, 3040, 3046, 3055, 3061, 3072, + 3078, 3087, 3093, 3116, 3122, 3131, 3137, 3148, + 3154, 3163, 3169, 3189, 3195, 3204, 3210, 3221, + 3227, 3236, 3242, 3267, 3273, 3282, 3288, 3299, + 3305, 3314, 3320, 3340, 3346, 3355, 3361, 3372, + 3378, 3387, 3393, 3257, 3263, 3272, 3278, 3289, + 3295, 3304, 3310, 3330, 3336, 3345, 3351, 3362, + 3368, 3377, 3383, 3408, 3414, 3423, 3429, 3440, + 3446, 3455, 3461, 3481, 3487, 3496, 3502, 3513, + 3519, 3528, 3534, 3557, 3563, 3572, 3578, 3589, + 3595, 3604, 3610, 3630, 3636, 3645, 3651, 3662, + 3668, 3677, 3683, 3708, 3714, 3723, 3729, 3740, + 3746, 3755, 3761, 3781, 3787, 3796, 3802, 3813, + 3819, 3828, 3834, 3629, 3635, 3644, 3650, 3661, + 3667, 3676, 3682, 3702, 3708, 3717, 3723, 3734, + 3740, 3749, 3755, 3780, 3786, 3795, 3801, 3812, + 3818, 3827, 3833, 3853, 3859, 3868, 3874, 3885, + 3891, 3900, 3906, 3929, 3935, 3944, 3950, 3961, + 3967, 3976, 3982, 4002, 4008, 4017, 4023, 4034, + 4040, 4049, 4055, 4080, 4086, 4095, 4101, 4112, + 4118, 4127, 4133, 4153, 4159, 4168, 4174, 4185, + 4191, 4200, 4206, 4070, 4076, 4085, 4091, 4102, + 4108, 4117, 4123, 4143, 4149, 4158, 4164, 4175, + 4181, 4190, 4196, 4221, 4227, 4236, 4242, 4253, + 4259, 4268, 4274, 4294, 4300, 4309, 4315, 4326, + 4332, 4341, 4347, 4370, 4376, 4385, 4391, 4402, + 4408, 4417, 4423, 4443, 4449, 4458, 4464, 4475, + 4481, 4490, 4496, 4521, 4527, 4536, 4542, 4553, + 4559, 4568, 4574, 4594, 4600, 4609, 4615, 4626, + 4632, 4641, 4647, 3515, 3521, 3530, 3536, 3547, + 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, + 3626, 3635, 3641, 3666, 3672, 3681, 3687, 3698, + 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, + 3777, 3786, 3792, 3815, 3821, 3830, 3836, 3847, + 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, + 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, + 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, + 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, + 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, + 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, + 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, + 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, + 4294, 4303, 4309, 4329, 4335, 4344, 4350, 4361, + 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, + 4445, 4454, 4460, 4480, 4486, 4495, 4501, 4512, + 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, + 4366, 4375, 4381, 4401, 4407, 4416, 4422, 4433, + 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, + 4517, 4526, 4532, 4552, 4558, 4567, 4573, 4584, + 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, + 4666, 4675, 4681, 4701, 4707, 4716, 4722, 4733, + 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, + 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, + 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, + 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, + 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, + 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, + 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, + 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, + 5180, 5189, 5195, 5220, 5226, 5235, 5241, 5252, + 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, + 5331, 5340, 5346, 4604, 4610, 4619, 4625, 4636, + 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, + 4715, 4724, 4730, 4755, 4761, 4770, 4776, 4787, + 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, + 4866, 4875, 4881, 4904, 4910, 4919, 4925, 4936, + 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, + 5015, 5024, 5030, 5055, 5061, 5070, 5076, 5087, + 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, + 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, + 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, + 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, + 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, + 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, + 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, + 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, + 5534, 5543, 5549, 5569, 5575, 5584, 5590, 5601, + 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, + 5455, 5464, 5470, 5490, 5496, 5505, 5511, 5522, + 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, + 5606, 5615, 5621, 5641, 5647, 5656, 5662, 5673, + 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, + 5755, 5764, 5770, 5790, 5796, 5805, 5811, 5822, + 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, + 5906, 5915, 5921, 5941, 5947, 5956, 5962, 5973, + 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, + 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, + 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, + 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, + 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, + 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, + 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, + 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, + 6420, 6429, 6435, 3515, 3521, 3530, 3536, 3547, + 3553, 3562, 3568, 3588, 3594, 3603, 3609, 3620, + 3626, 3635, 3641, 3666, 3672, 3681, 3687, 3698, + 3704, 3713, 3719, 3739, 3745, 3754, 3760, 3771, + 3777, 3786, 3792, 3815, 3821, 3830, 3836, 3847, + 3853, 3862, 3868, 3888, 3894, 3903, 3909, 3920, + 3926, 3935, 3941, 3966, 3972, 3981, 3987, 3998, + 4004, 4013, 4019, 4039, 4045, 4054, 4060, 4071, + 4077, 4086, 4092, 3956, 3962, 3971, 3977, 3988, + 3994, 4003, 4009, 4029, 4035, 4044, 4050, 4061, + 4067, 4076, 4082, 4107, 4113, 4122, 4128, 4139, + 4145, 4154, 4160, 4180, 4186, 4195, 4201, 4212, + 4218, 4227, 4233, 4256, 4262, 4271, 4277, 4288, + 4294, 4303, 4309, 4329, 4335, 4344, 4350, 4361, + 4367, 4376, 4382, 4407, 4413, 4422, 4428, 4439, + 4445, 4454, 4460, 4480, 4486, 4495, 4501, 4512, + 4518, 4527, 4533, 4328, 4334, 4343, 4349, 4360, + 4366, 4375, 4381, 4401, 4407, 4416, 4422, 4433, + 4439, 4448, 4454, 4479, 4485, 4494, 4500, 4511, + 4517, 4526, 4532, 4552, 4558, 4567, 4573, 4584, + 4590, 4599, 4605, 4628, 4634, 4643, 4649, 4660, + 4666, 4675, 4681, 4701, 4707, 4716, 4722, 4733, + 4739, 4748, 4754, 4779, 4785, 4794, 4800, 4811, + 4817, 4826, 4832, 4852, 4858, 4867, 4873, 4884, + 4890, 4899, 4905, 4769, 4775, 4784, 4790, 4801, + 4807, 4816, 4822, 4842, 4848, 4857, 4863, 4874, + 4880, 4889, 4895, 4920, 4926, 4935, 4941, 4952, + 4958, 4967, 4973, 4993, 4999, 5008, 5014, 5025, + 5031, 5040, 5046, 5069, 5075, 5084, 5090, 5101, + 5107, 5116, 5122, 5142, 5148, 5157, 5163, 5174, + 5180, 5189, 5195, 5220, 5226, 5235, 5241, 5252, + 5258, 5267, 5273, 5293, 5299, 5308, 5314, 5325, + 5331, 5340, 5346, 4604, 4610, 4619, 4625, 4636, + 4642, 4651, 4657, 4677, 4683, 4692, 4698, 4709, + 4715, 4724, 4730, 4755, 4761, 4770, 4776, 4787, + 4793, 4802, 4808, 4828, 4834, 4843, 4849, 4860, + 4866, 4875, 4881, 4904, 4910, 4919, 4925, 4936, + 4942, 4951, 4957, 4977, 4983, 4992, 4998, 5009, + 5015, 5024, 5030, 5055, 5061, 5070, 5076, 5087, + 5093, 5102, 5108, 5128, 5134, 5143, 5149, 5160, + 5166, 5175, 5181, 5045, 5051, 5060, 5066, 5077, + 5083, 5092, 5098, 5118, 5124, 5133, 5139, 5150, + 5156, 5165, 5171, 5196, 5202, 5211, 5217, 5228, + 5234, 5243, 5249, 5269, 5275, 5284, 5290, 5301, + 5307, 5316, 5322, 5345, 5351, 5360, 5366, 5377, + 5383, 5392, 5398, 5418, 5424, 5433, 5439, 5450, + 5456, 5465, 5471, 5496, 5502, 5511, 5517, 5528, + 5534, 5543, 5549, 5569, 5575, 5584, 5590, 5601, + 5607, 5616, 5622, 5417, 5423, 5432, 5438, 5449, + 5455, 5464, 5470, 5490, 5496, 5505, 5511, 5522, + 5528, 5537, 5543, 5568, 5574, 5583, 5589, 5600, + 5606, 5615, 5621, 5641, 5647, 5656, 5662, 5673, + 5679, 5688, 5694, 5717, 5723, 5732, 5738, 5749, + 5755, 5764, 5770, 5790, 5796, 5805, 5811, 5822, + 5828, 5837, 5843, 5868, 5874, 5883, 5889, 5900, + 5906, 5915, 5921, 5941, 5947, 5956, 5962, 5973, + 5979, 5988, 5994, 5858, 5864, 5873, 5879, 5890, + 5896, 5905, 5911, 5931, 5937, 5946, 5952, 5963, + 5969, 5978, 5984, 6009, 6015, 6024, 6030, 6041, + 6047, 6056, 6062, 6082, 6088, 6097, 6103, 6114, + 6120, 6129, 6135, 6158, 6164, 6173, 6179, 6190, + 6196, 6205, 6211, 6231, 6237, 6246, 6252, 6263, + 6269, 6278, 6284, 6309, 6315, 6324, 6330, 6341, + 6347, 6356, 6362, 6382, 6388, 6397, 6403, 6414, + 6420, 6429, 6435, 5303, 5309, 5318, 5324, 5335, + 5341, 5350, 5356, 5376, 5382, 5391, 5397, 5408, + 5414, 5423, 5429, 5454, 5460, 5469, 5475, 5486, + 5492, 5501, 5507, 5527, 5533, 5542, 5548, 5559, + 5565, 5574, 5580, 5603, 5609, 5618, 5624, 5635, + 5641, 5650, 5656, 5676, 5682, 5691, 5697, 5708, + 5714, 5723, 5729, 5754, 5760, 5769, 5775, 5786, + 5792, 5801, 5807, 5827, 5833, 5842, 5848, 5859, + 5865, 5874, 5880, 5744, 5750, 5759, 5765, 5776, + 5782, 5791, 5797, 5817, 5823, 5832, 5838, 5849, + 5855, 5864, 5870, 5895, 5901, 5910, 5916, 5927, + 5933, 5942, 5948, 5968, 5974, 5983, 5989, 6000, + 6006, 6015, 6021, 6044, 6050, 6059, 6065, 6076, + 6082, 6091, 6097, 6117, 6123, 6132, 6138, 6149, + 6155, 6164, 6170, 6195, 6201, 6210, 6216, 6227, + 6233, 6242, 6248, 6268, 6274, 6283, 6289, 6300, + 6306, 6315, 6321, 6116, 6122, 6131, 6137, 6148, + 6154, 6163, 6169, 6189, 6195, 6204, 6210, 6221, + 6227, 6236, 6242, 6267, 6273, 6282, 6288, 6299, + 6305, 6314, 6320, 6340, 6346, 6355, 6361, 6372, + 6378, 6387, 6393, 6416, 6422, 6431, 6437, 6448, + 6454, 6463, 6469, 6489, 6495, 6504, 6510, 6521, + 6527, 6536, 6542, 6567, 6573, 6582, 6588, 6599, + 6605, 6614, 6620, 6640, 6646, 6655, 6661, 6672, + 6678, 6687, 6693, 6557, 6563, 6572, 6578, 6589, + 6595, 6604, 6610, 6630, 6636, 6645, 6651, 6662, + 6668, 6677, 6683, 6708, 6714, 6723, 6729, 6740, + 6746, 6755, 6761, 6781, 6787, 6796, 6802, 6813, + 6819, 6828, 6834, 6857, 6863, 6872, 6878, 6889, + 6895, 6904, 6910, 6930, 6936, 6945, 6951, 6962, + 6968, 6977, 6983, 7008, 7014, 7023, 7029, 7040, + 7046, 7055, 7061, 7081, 7087, 7096, 7102, 7113, + 7119, 7128, 7134, 6392, 6398, 6407, 6413, 6424, + 6430, 6439, 6445, 6465, 6471, 6480, 6486, 6497, + 6503, 6512, 6518, 6543, 6549, 6558, 6564, 6575, + 6581, 6590, 6596, 6616, 6622, 6631, 6637, 6648, + 6654, 6663, 6669, 6692, 6698, 6707, 6713, 6724, + 6730, 6739, 6745, 6765, 6771, 6780, 6786, 6797, + 6803, 6812, 6818, 6843, 6849, 6858, 6864, 6875, + 6881, 6890, 6896, 6916, 6922, 6931, 6937, 6948, + 6954, 6963, 6969, 6833, 6839, 6848, 6854, 6865, + 6871, 6880, 6886, 6906, 6912, 6921, 6927, 6938, + 6944, 6953, 6959, 6984, 6990, 6999, 7005, 7016, + 7022, 7031, 7037, 7057, 7063, 7072, 7078, 7089, + 7095, 7104, 7110, 7133, 7139, 7148, 7154, 7165, + 7171, 7180, 7186, 7206, 7212, 7221, 7227, 7238, + 7244, 7253, 7259, 7284, 7290, 7299, 7305, 7316, + 7322, 7331, 7337, 7357, 7363, 7372, 7378, 7389, + 7395, 7404, 7410, 7205, 7211, 7220, 7226, 7237, + 7243, 7252, 7258, 7278, 7284, 7293, 7299, 7310, + 7316, 7325, 7331, 7356, 7362, 7371, 7377, 7388, + 7394, 7403, 7409, 7429, 7435, 7444, 7450, 7461, + 7467, 7476, 7482, 7505, 7511, 7520, 7526, 7537, + 7543, 7552, 7558, 7578, 7584, 7593, 7599, 7610, + 7616, 7625, 7631, 7656, 7662, 7671, 7677, 7688, + 7694, 7703, 7709, 7729, 7735, 7744, 7750, 7761 +}; + +//------------------------------------------------------------------------------ +// Tables for level coding + +const uint8_t VP8EncBands[16 + 1] = { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 0 // sentinel +}; + +//------------------------------------------------------------------------------ +// Mode costs + +static int GetResidualCost_C(int ctx0, const VP8Residual* const res) { + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + const int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + for (; n < res->last; ++n) { + const int v = abs(res->coeffs[n]); + const int ctx = (v >= 2) ? 2 : v; + cost += VP8LevelCost(t, v); + t = costs[n + 1][ctx]; + } + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +static void SetResidualCoeffs_C(const int16_t* const coeffs, + VP8Residual* const res) { + int n; + res->last = -1; + assert(res->first == 0 || coeffs[0] == 0); + for (n = 15; n >= 0; --n) { + if (coeffs[n]) { + res->last = n; + break; + } + } + res->coeffs = coeffs; +} + +//------------------------------------------------------------------------------ +// init function + +VP8GetResidualCostFunc VP8GetResidualCost; +VP8SetResidualCoeffsFunc VP8SetResidualCoeffs; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8EncDspCostInitMIPS32(void); +extern void VP8EncDspCostInitMIPSdspR2(void); +extern void VP8EncDspCostInitSSE2(void); +extern void VP8EncDspCostInitNEON(void); + +WEBP_DSP_INIT_FUNC(VP8EncDspCostInit) { + VP8GetResidualCost = GetResidualCost_C; + VP8SetResidualCoeffs = SetResidualCoeffs_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8EncDspCostInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8EncDspCostInitMIPSdspR2(); + } +#endif +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8EncDspCostInitSSE2(); + } +#endif +#if defined(WEBP_HAVE_NEON) + if (VP8GetCPUInfo(kNEON)) { + VP8EncDspCostInitNEON(); + } +#endif + } +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips32.c new file mode 100644 index 0000000000..0500f88c13 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips32.c @@ -0,0 +1,154 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "src/enc/cost_enc.h" + +static int GetResidualCost_MIPS32(int ctx0, const VP8Residual* const res) { + int temp0, temp1; + int v_reg, ctx_reg; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + const int16_t* res_coeffs = res->coeffs; + const int res_last = res->last; + const int const_max_level = MAX_VARIABLE_LEVEL; + const int const_2 = 2; + const uint16_t** p_costs = &costs[n][0]; + const size_t inc_p_costs = NUM_CTX * sizeof(*p_costs); + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "subu %[temp1], %[res_last], %[n] \n\t" + "sll %[temp0], %[n], 1 \n\t" + "blez %[temp1], 2f \n\t" + " addu %[res_coeffs], %[res_coeffs], %[temp0] \n\t" + "1: \n\t" + "lh %[v_reg], 0(%[res_coeffs]) \n\t" + "addiu %[n], %[n], 1 \n\t" + "negu %[temp0], %[v_reg] \n\t" + "slti %[temp1], %[v_reg], 0 \n\t" + "movn %[v_reg], %[temp0], %[temp1] \n\t" + "sltiu %[temp0], %[v_reg], 2 \n\t" + "move %[ctx_reg], %[v_reg] \n\t" + "movz %[ctx_reg], %[const_2], %[temp0] \n\t" + "sll %[temp1], %[v_reg], 1 \n\t" + "addu %[temp1], %[temp1], %[VP8LevelFixedCosts] \n\t" + "lhu %[temp1], 0(%[temp1]) \n\t" + "slt %[temp0], %[v_reg], %[const_max_level] \n\t" + "movz %[v_reg], %[const_max_level], %[temp0] \n\t" + "addu %[cost], %[cost], %[temp1] \n\t" + "sll %[v_reg], %[v_reg], 1 \n\t" + "sll %[ctx_reg], %[ctx_reg], 2 \n\t" + "addu %[v_reg], %[v_reg], %[t] \n\t" + "lhu %[temp0], 0(%[v_reg]) \n\t" + "addu %[p_costs], %[p_costs], %[inc_p_costs] \n\t" + "addu %[t], %[p_costs], %[ctx_reg] \n\t" + "addu %[cost], %[cost], %[temp0] \n\t" + "addiu %[res_coeffs], %[res_coeffs], 2 \n\t" + "bne %[n], %[res_last], 1b \n\t" + " lw %[t], 0(%[t]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [cost]"+&r"(cost), [t]"+&r"(t), [n]"+&r"(n), [v_reg]"=&r"(v_reg), + [ctx_reg]"=&r"(ctx_reg), [p_costs]"+&r"(p_costs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [res_coeffs]"+&r"(res_coeffs) + : [const_2]"r"(const_2), [const_max_level]"r"(const_max_level), + [VP8LevelFixedCosts]"r"(VP8LevelFixedCosts), [res_last]"r"(res_last), + [inc_p_costs]"r"(inc_p_costs) + : "memory" + ); + + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +static void SetResidualCoeffs_MIPS32(const int16_t* const coeffs, + VP8Residual* const res) { + const int16_t* p_coeffs = (int16_t*)coeffs; + int temp0, temp1, temp2, n, n1; + assert(res->first == 0 || coeffs[0] == 0); + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "addiu %[p_coeffs], %[p_coeffs], 28 \n\t" + "li %[n], 15 \n\t" + "li %[temp2], -1 \n\t" + "0: \n\t" + "ulw %[temp0], 0(%[p_coeffs]) \n\t" + "beqz %[temp0], 1f \n\t" +#if defined(WORDS_BIGENDIAN) + " sll %[temp1], %[temp0], 16 \n\t" +#else + " srl %[temp1], %[temp0], 16 \n\t" +#endif + "addiu %[n1], %[n], -1 \n\t" + "movz %[temp0], %[n1], %[temp1] \n\t" + "movn %[temp0], %[n], %[temp1] \n\t" + "j 2f \n\t" + " addiu %[temp2], %[temp0], 0 \n\t" + "1: \n\t" + "addiu %[n], %[n], -2 \n\t" + "bgtz %[n], 0b \n\t" + " addiu %[p_coeffs], %[p_coeffs], -4 \n\t" + "2: \n\t" + ".set pop \n\t" + : [p_coeffs]"+&r"(p_coeffs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [n]"=&r"(n), [n1]"=&r"(n1) + : + : "memory" + ); + res->last = temp2; + res->coeffs = coeffs; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitMIPS32(void) { + VP8GetResidualCost = GetResidualCost_MIPS32; + VP8SetResidualCoeffs = SetResidualCoeffs_MIPS32; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips_dsp_r2.c new file mode 100644 index 0000000000..51248de7a1 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_mips_dsp_r2.c @@ -0,0 +1,107 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/enc/cost_enc.h" + +static int GetResidualCost_MIPSdspR2(int ctx0, const VP8Residual* const res) { + int temp0, temp1; + int v_reg, ctx_reg; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + const int16_t* res_coeffs = res->coeffs; + const int res_last = res->last; + const int const_max_level = MAX_VARIABLE_LEVEL; + const int const_2 = 2; + const uint16_t** p_costs = &costs[n][0]; + const size_t inc_p_costs = NUM_CTX * sizeof(*p_costs); + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "subu %[temp1], %[res_last], %[n] \n\t" + "blez %[temp1], 2f \n\t" + " nop \n\t" + "1: \n\t" + "sll %[temp0], %[n], 1 \n\t" + "lhx %[v_reg], %[temp0](%[res_coeffs]) \n\t" + "addiu %[n], %[n], 1 \n\t" + "absq_s.w %[v_reg], %[v_reg] \n\t" + "sltiu %[temp0], %[v_reg], 2 \n\t" + "move %[ctx_reg], %[v_reg] \n\t" + "movz %[ctx_reg], %[const_2], %[temp0] \n\t" + "sll %[temp1], %[v_reg], 1 \n\t" + "lhx %[temp1], %[temp1](%[VP8LevelFixedCosts]) \n\t" + "slt %[temp0], %[v_reg], %[const_max_level] \n\t" + "movz %[v_reg], %[const_max_level], %[temp0] \n\t" + "addu %[cost], %[cost], %[temp1] \n\t" + "sll %[v_reg], %[v_reg], 1 \n\t" + "sll %[ctx_reg], %[ctx_reg], 2 \n\t" + "lhx %[temp0], %[v_reg](%[t]) \n\t" + "addu %[p_costs], %[p_costs], %[inc_p_costs] \n\t" + "addu %[t], %[p_costs], %[ctx_reg] \n\t" + "addu %[cost], %[cost], %[temp0] \n\t" + "bne %[n], %[res_last], 1b \n\t" + " lw %[t], 0(%[t]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [cost]"+&r"(cost), [t]"+&r"(t), [n]"+&r"(n), [v_reg]"=&r"(v_reg), + [ctx_reg]"=&r"(ctx_reg), [p_costs]"+&r"(p_costs), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1) + : [const_2]"r"(const_2), [const_max_level]"r"(const_max_level), + [VP8LevelFixedCosts]"r"(VP8LevelFixedCosts), [res_last]"r"(res_last), + [res_coeffs]"r"(res_coeffs), [inc_p_costs]"r"(inc_p_costs) + : "memory" + ); + + // Last coefficient is always non-zero + { + const int v = abs(res->coeffs[n]); + assert(v != 0); + cost += VP8LevelCost(t, v); + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = (v == 1) ? 1 : 2; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitMIPSdspR2(void) { + VP8GetResidualCost = GetResidualCost_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_neon.c new file mode 100644 index 0000000000..6582669cb3 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_neon.c @@ -0,0 +1,122 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARM NEON version of cost functions + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "src/dsp/neon.h" +#include "src/enc/cost_enc.h" + +static const uint8_t position[16] = { 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16 }; + +static void SetResidualCoeffs_NEON(const int16_t* const coeffs, + VP8Residual* const res) { + const int16x8_t minus_one = vdupq_n_s16(-1); + const int16x8_t coeffs_0 = vld1q_s16(coeffs); + const int16x8_t coeffs_1 = vld1q_s16(coeffs + 8); + const uint16x8_t eob_0 = vtstq_s16(coeffs_0, minus_one); + const uint16x8_t eob_1 = vtstq_s16(coeffs_1, minus_one); + const uint8x16_t eob = vcombine_u8(vqmovn_u16(eob_0), vqmovn_u16(eob_1)); + const uint8x16_t masked = vandq_u8(eob, vld1q_u8(position)); + +#if WEBP_AARCH64 + res->last = vmaxvq_u8(masked) - 1; +#else + const uint8x8_t eob_8x8 = vmax_u8(vget_low_u8(masked), vget_high_u8(masked)); + const uint16x8_t eob_16x8 = vmovl_u8(eob_8x8); + const uint16x4_t eob_16x4 = + vmax_u16(vget_low_u16(eob_16x8), vget_high_u16(eob_16x8)); + const uint32x4_t eob_32x4 = vmovl_u16(eob_16x4); + uint32x2_t eob_32x2 = + vmax_u32(vget_low_u32(eob_32x4), vget_high_u32(eob_32x4)); + eob_32x2 = vpmax_u32(eob_32x2, eob_32x2); + + vst1_lane_s32(&res->last, vreinterpret_s32_u32(eob_32x2), 0); + --res->last; +#endif // WEBP_AARCH64 + + res->coeffs = coeffs; +} + +static int GetResidualCost_NEON(int ctx0, const VP8Residual* const res) { + uint8_t levels[16], ctxs[16]; + uint16_t abs_levels[16]; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + const int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + { // precompute clamped levels and contexts, packed to 8b. + const uint8x16_t kCst2 = vdupq_n_u8(2); + const uint8x16_t kCst67 = vdupq_n_u8(MAX_VARIABLE_LEVEL); + const int16x8_t c0 = vld1q_s16(res->coeffs); + const int16x8_t c1 = vld1q_s16(res->coeffs + 8); + const uint16x8_t E0 = vreinterpretq_u16_s16(vabsq_s16(c0)); + const uint16x8_t E1 = vreinterpretq_u16_s16(vabsq_s16(c1)); + const uint8x16_t F = vcombine_u8(vqmovn_u16(E0), vqmovn_u16(E1)); + const uint8x16_t G = vminq_u8(F, kCst2); // context = 0,1,2 + const uint8x16_t H = vminq_u8(F, kCst67); // clamp_level in [0..67] + + vst1q_u8(ctxs, G); + vst1q_u8(levels, H); + + vst1q_u16(abs_levels, E0); + vst1q_u16(abs_levels + 8, E1); + } + for (; n < res->last; ++n) { + const int ctx = ctxs[n]; + const int level = levels[n]; + const int flevel = abs_levels[n]; // full level + cost += VP8LevelFixedCosts[flevel] + t[level]; // simplified VP8LevelCost() + t = costs[n + 1][ctx]; + } + // Last coefficient is always non-zero + { + const int level = levels[n]; + const int flevel = abs_levels[n]; + assert(flevel != 0); + cost += VP8LevelFixedCosts[flevel] + t[level]; + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = ctxs[n]; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitNEON(void) { + VP8SetResidualCoeffs = SetResidualCoeffs_NEON; + VP8GetResidualCost = GetResidualCost_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_sse2.c new file mode 100644 index 0000000000..487a079921 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cost_sse2.c @@ -0,0 +1,119 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of cost functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include "src/enc/cost_enc.h" +#include "src/enc/vp8i_enc.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ + +static void SetResidualCoeffs_SSE2(const int16_t* const coeffs, + VP8Residual* const res) { + const __m128i c0 = _mm_loadu_si128((const __m128i*)(coeffs + 0)); + const __m128i c1 = _mm_loadu_si128((const __m128i*)(coeffs + 8)); + // Use SSE2 to compare 16 values with a single instruction. + const __m128i zero = _mm_setzero_si128(); + const __m128i m0 = _mm_packs_epi16(c0, c1); + const __m128i m1 = _mm_cmpeq_epi8(m0, zero); + // Get the comparison results as a bitmask into 16bits. Negate the mask to get + // the position of entries that are not equal to zero. We don't need to mask + // out least significant bits according to res->first, since coeffs[0] is 0 + // if res->first > 0. + const uint32_t mask = 0x0000ffffu ^ (uint32_t)_mm_movemask_epi8(m1); + // The position of the most significant non-zero bit indicates the position of + // the last non-zero value. + assert(res->first == 0 || coeffs[0] == 0); + res->last = mask ? BitsLog2Floor(mask) : -1; + res->coeffs = coeffs; +} + +static int GetResidualCost_SSE2(int ctx0, const VP8Residual* const res) { + uint8_t levels[16], ctxs[16]; + uint16_t abs_levels[16]; + int n = res->first; + // should be prob[VP8EncBands[n]], but it's equivalent for n=0 or 1 + const int p0 = res->prob[n][ctx0][0]; + CostArrayPtr const costs = res->costs; + const uint16_t* t = costs[n][ctx0]; + // bit_cost(1, p0) is already incorporated in t[] tables, but only if ctx != 0 + // (as required by the syntax). For ctx0 == 0, we need to add it here or it'll + // be missing during the loop. + int cost = (ctx0 == 0) ? VP8BitCost(1, p0) : 0; + + if (res->last < 0) { + return VP8BitCost(0, p0); + } + + { // precompute clamped levels and contexts, packed to 8b. + const __m128i zero = _mm_setzero_si128(); + const __m128i kCst2 = _mm_set1_epi8(2); + const __m128i kCst67 = _mm_set1_epi8(MAX_VARIABLE_LEVEL); + const __m128i c0 = _mm_loadu_si128((const __m128i*)&res->coeffs[0]); + const __m128i c1 = _mm_loadu_si128((const __m128i*)&res->coeffs[8]); + const __m128i D0 = _mm_sub_epi16(zero, c0); + const __m128i D1 = _mm_sub_epi16(zero, c1); + const __m128i E0 = _mm_max_epi16(c0, D0); // abs(v), 16b + const __m128i E1 = _mm_max_epi16(c1, D1); + const __m128i F = _mm_packs_epi16(E0, E1); + const __m128i G = _mm_min_epu8(F, kCst2); // context = 0,1,2 + const __m128i H = _mm_min_epu8(F, kCst67); // clamp_level in [0..67] + + _mm_storeu_si128((__m128i*)&ctxs[0], G); + _mm_storeu_si128((__m128i*)&levels[0], H); + + _mm_storeu_si128((__m128i*)&abs_levels[0], E0); + _mm_storeu_si128((__m128i*)&abs_levels[8], E1); + } + for (; n < res->last; ++n) { + const int ctx = ctxs[n]; + const int level = levels[n]; + const int flevel = abs_levels[n]; // full level + cost += VP8LevelFixedCosts[flevel] + t[level]; // simplified VP8LevelCost() + t = costs[n + 1][ctx]; + } + // Last coefficient is always non-zero + { + const int level = levels[n]; + const int flevel = abs_levels[n]; + assert(flevel != 0); + cost += VP8LevelFixedCosts[flevel] + t[level]; + if (n < 15) { + const int b = VP8EncBands[n + 1]; + const int ctx = ctxs[n]; + const int last_p0 = res->prob[b][ctx][0]; + cost += VP8BitCost(0, last_p0); + } + } + return cost; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspCostInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspCostInitSSE2(void) { + VP8SetResidualCoeffs = SetResidualCoeffs_SSE2; + VP8GetResidualCost = GetResidualCost_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8EncDspCostInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.c new file mode 100644 index 0000000000..8ba8f68335 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.c @@ -0,0 +1,247 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// CPU detection +// +// Author: Christian Duvivier (cduvivier@google.com) + +#include "src/dsp/cpu.h" + +#if defined(WEBP_HAVE_NEON_RTCD) +#include +#include +#endif + +#if defined(WEBP_ANDROID_NEON) +#include +#endif + +//------------------------------------------------------------------------------ +// SSE2 detection. +// + +// apple/darwin gcc-4.0.1 defines __PIC__, but not __pic__ with -fPIC. +#if (defined(__pic__) || defined(__PIC__)) && defined(__i386__) +static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { + __asm__ volatile ( + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#elif defined(__i386__) || defined(__x86_64__) +static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { + __asm__ volatile ( + "cpuid\n" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + +#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729 // >= VS2008 SP1 +#include +#define GetCPUInfo(info, type) __cpuidex(info, type, 0) // set ecx=0 +#define WEBP_HAVE_MSC_CPUID +#elif _MSC_VER > 1310 +#include +#define GetCPUInfo __cpuid +#define WEBP_HAVE_MSC_CPUID +#endif + +#endif + +// NaCl has no support for xgetbv or the raw opcode. +#if !defined(__native_client__) && (defined(__i386__) || defined(__x86_64__)) +static WEBP_INLINE uint64_t xgetbv(void) { + const uint32_t ecx = 0; + uint32_t eax, edx; + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm__ volatile ( + ".byte 0x0f, 0x01, 0xd0\n" + : "=a"(eax), "=d"(edx) : "c" (ecx)); + return ((uint64_t)edx << 32) | eax; +} +#elif (defined(_M_X64) || defined(_M_IX86)) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219 // >= VS2010 SP1 +#include +#define xgetbv() _xgetbv(0) +#elif defined(_MSC_VER) && defined(_M_IX86) +static WEBP_INLINE uint64_t xgetbv(void) { + uint32_t eax_, edx_; + __asm { + xor ecx, ecx // ecx = 0 + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm _emit 0x0f __asm _emit 0x01 __asm _emit 0xd0 + mov eax_, eax + mov edx_, edx + } + return ((uint64_t)edx_ << 32) | eax_; +} +#else +#define xgetbv() 0U // no AVX for older x64 or unrecognized toolchains. +#endif + +#if defined(__i386__) || defined(__x86_64__) || defined(WEBP_HAVE_MSC_CPUID) + +// helper function for run-time detection of slow SSSE3 platforms +static int CheckSlowModel(int info) { + // Table listing display models with longer latencies for the bsr instruction + // (ie 2 cycles vs 10/16 cycles) and some SSSE3 instructions like pshufb. + // Refer to Intel 64 and IA-32 Architectures Optimization Reference Manual. + static const uint8_t kSlowModels[] = { + 0x37, 0x4a, 0x4d, // Silvermont Microarchitecture + 0x1c, 0x26, 0x27 // Atom Microarchitecture + }; + const uint32_t model = ((info & 0xf0000) >> 12) | ((info >> 4) & 0xf); + const uint32_t family = (info >> 8) & 0xf; + if (family == 0x06) { + size_t i; + for (i = 0; i < sizeof(kSlowModels) / sizeof(kSlowModels[0]); ++i) { + if (model == kSlowModels[i]) return 1; + } + } + return 0; +} + +static int x86CPUInfo(CPUFeature feature) { + int max_cpuid_value; + int cpu_info[4]; + int is_intel = 0; + + // get the highest feature value cpuid supports + GetCPUInfo(cpu_info, 0); + max_cpuid_value = cpu_info[0]; + if (max_cpuid_value < 1) { + return 0; + } else { + const int VENDOR_ID_INTEL_EBX = 0x756e6547; // uneG + const int VENDOR_ID_INTEL_EDX = 0x49656e69; // Ieni + const int VENDOR_ID_INTEL_ECX = 0x6c65746e; // letn + is_intel = (cpu_info[1] == VENDOR_ID_INTEL_EBX && + cpu_info[2] == VENDOR_ID_INTEL_ECX && + cpu_info[3] == VENDOR_ID_INTEL_EDX); // genuine Intel? + } + + GetCPUInfo(cpu_info, 1); + if (feature == kSSE2) { + return !!(cpu_info[3] & (1 << 26)); + } + if (feature == kSSE3) { + return !!(cpu_info[2] & (1 << 0)); + } + if (feature == kSlowSSSE3) { + if (is_intel && (cpu_info[2] & (1 << 9))) { // SSSE3? + return CheckSlowModel(cpu_info[0]); + } + return 0; + } + + if (feature == kSSE4_1) { + return !!(cpu_info[2] & (1 << 19)); + } + if (feature == kAVX) { + // bits 27 (OSXSAVE) & 28 (256-bit AVX) + if ((cpu_info[2] & 0x18000000) == 0x18000000) { + // XMM state and YMM state enabled by the OS. + return (xgetbv() & 0x6) == 0x6; + } + } + if (feature == kAVX2) { + if (x86CPUInfo(kAVX) && max_cpuid_value >= 7) { + GetCPUInfo(cpu_info, 7); + return !!(cpu_info[1] & (1 << 5)); + } + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = x86CPUInfo; +#elif defined(WEBP_ANDROID_NEON) // NB: needs to be before generic NEON test. +static int AndroidCPUInfo(CPUFeature feature) { + const AndroidCpuFamily cpu_family = android_getCpuFamily(); + const uint64_t cpu_features = android_getCpuFeatures(); + if (feature == kNEON) { + return cpu_family == ANDROID_CPU_FAMILY_ARM && + (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) != 0; + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = AndroidCPUInfo; +#elif defined(EMSCRIPTEN) // also needs to be before generic NEON test +// Use compile flags as an indicator of SIMD support instead of a runtime check. +static int wasmCPUInfo(CPUFeature feature) { + switch (feature) { +#ifdef WEBP_HAVE_SSE2 + case kSSE2: + return 1; +#endif +#ifdef WEBP_HAVE_SSE41 + case kSSE3: + case kSlowSSSE3: + case kSSE4_1: + return 1; +#endif +#ifdef WEBP_HAVE_NEON + case kNEON: + return 1; +#endif + default: + break; + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = wasmCPUInfo; +#elif defined(WEBP_HAVE_NEON) +// In most cases this function doesn't check for NEON support (it's assumed by +// the configuration), but enables turning off NEON at runtime, for testing +// purposes, by setting VP8GetCPUInfo = NULL. +static int armCPUInfo(CPUFeature feature) { + if (feature != kNEON) return 0; +#if defined(__linux__) && defined(WEBP_HAVE_NEON_RTCD) + { + int has_neon = 0; + char line[200]; + FILE* const cpuinfo = fopen("/proc/cpuinfo", "r"); + if (cpuinfo == NULL) return 0; + while (fgets(line, sizeof(line), cpuinfo)) { + if (!strncmp(line, "Features", 8)) { + if (strstr(line, " neon ") != NULL) { + has_neon = 1; + break; + } + } + } + fclose(cpuinfo); + return has_neon; + } +#else + return 1; +#endif +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = armCPUInfo; +#elif defined(WEBP_USE_MIPS32) || defined(WEBP_USE_MIPS_DSP_R2) || \ + defined(WEBP_USE_MSA) +static int mipsCPUInfo(CPUFeature feature) { + if ((feature == kMIPS32) || (feature == kMIPSdspR2) || (feature == kMSA)) { + return 1; + } else { + return 0; + } + +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = mipsCPUInfo; +#else +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = NULL; +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.h new file mode 100644 index 0000000000..c86540f280 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/cpu.h @@ -0,0 +1,266 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// CPU detection functions and macros. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_CPU_H_ +#define WEBP_DSP_CPU_H_ + +#include + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/webp/types.h" + +#if defined(__GNUC__) +#define LOCAL_GCC_VERSION ((__GNUC__ << 8) | __GNUC_MINOR__) +#define LOCAL_GCC_PREREQ(maj, min) (LOCAL_GCC_VERSION >= (((maj) << 8) | (min))) +#else +#define LOCAL_GCC_VERSION 0 +#define LOCAL_GCC_PREREQ(maj, min) 0 +#endif + +#if defined(__clang__) +#define LOCAL_CLANG_VERSION ((__clang_major__ << 8) | __clang_minor__) +#define LOCAL_CLANG_PREREQ(maj, min) \ + (LOCAL_CLANG_VERSION >= (((maj) << 8) | (min))) +#else +#define LOCAL_CLANG_VERSION 0 +#define LOCAL_CLANG_PREREQ(maj, min) 0 +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +//------------------------------------------------------------------------------ +// x86 defines. + +#if !defined(HAVE_CONFIG_H) +#if defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets +#endif +#endif + +// WEBP_HAVE_* are used to indicate the presence of the instruction set in dsp +// files without intrinsics, allowing the corresponding Init() to be called. +// Files containing intrinsics will need to be built targeting the instruction +// set so should succeed on one of the earlier tests. +#if (defined(__SSE2__) || defined(WEBP_MSC_SSE2)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE2)) +#define WEBP_USE_SSE2 +#endif + +#if defined(WEBP_USE_SSE2) && !defined(WEBP_HAVE_SSE2) +#define WEBP_HAVE_SSE2 +#endif + +#if (defined(__SSE4_1__) || defined(WEBP_MSC_SSE41)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE41)) +#define WEBP_USE_SSE41 +#endif + +#if defined(WEBP_USE_SSE41) && !defined(WEBP_HAVE_SSE41) +#define WEBP_HAVE_SSE41 +#endif + +#undef WEBP_MSC_SSE41 +#undef WEBP_MSC_SSE2 + +//------------------------------------------------------------------------------ +// Arm defines. + +// The intrinsics currently cause compiler errors with arm-nacl-gcc and the +// inline assembly would need to be modified for use with Native Client. +#if ((defined(__ARM_NEON__) || defined(__aarch64__)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_NEON))) && \ + !defined(__native_client__) +#define WEBP_USE_NEON +#endif + +#if !defined(WEBP_USE_NEON) && defined(__ANDROID__) && \ + defined(__ARM_ARCH_7A__) && defined(HAVE_CPU_FEATURES_H) +#define WEBP_ANDROID_NEON // Android targets that may have NEON +#define WEBP_USE_NEON +#endif + +// Note: ARM64 is supported in Visual Studio 2017, but requires the direct +// inclusion of arm64_neon.h; Visual Studio 2019 includes this file in +// arm_neon.h. Compile errors were seen with Visual Studio 2019 16.4 with +// vtbl4_u8(); a fix was made in 16.6. +#if defined(_MSC_VER) && \ + ((_MSC_VER >= 1700 && defined(_M_ARM)) || \ + (_MSC_VER >= 1926 && (defined(_M_ARM64) || defined(_M_ARM64EC)))) +#define WEBP_USE_NEON +#define WEBP_USE_INTRINSICS +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) +#define WEBP_AARCH64 1 +#else +#define WEBP_AARCH64 0 +#endif + +#if defined(WEBP_USE_NEON) && !defined(WEBP_HAVE_NEON) +#define WEBP_HAVE_NEON +#endif + +//------------------------------------------------------------------------------ +// MIPS defines. + +#if defined(__mips__) && !defined(__mips64) && defined(__mips_isa_rev) && \ + (__mips_isa_rev >= 1) && (__mips_isa_rev < 6) +#define WEBP_USE_MIPS32 +#if (__mips_isa_rev >= 2) +#define WEBP_USE_MIPS32_R2 +#if defined(__mips_dspr2) || (defined(__mips_dsp_rev) && __mips_dsp_rev >= 2) +#define WEBP_USE_MIPS_DSP_R2 +#endif +#endif +#endif + +#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5) +#define WEBP_USE_MSA +#endif + +//------------------------------------------------------------------------------ + +#ifndef WEBP_DSP_OMIT_C_CODE +#define WEBP_DSP_OMIT_C_CODE 1 +#endif + +#if defined(WEBP_USE_NEON) && WEBP_DSP_OMIT_C_CODE +#define WEBP_NEON_OMIT_C_CODE 1 +#else +#define WEBP_NEON_OMIT_C_CODE 0 +#endif + +#if !(LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 8) || WEBP_AARCH64) +#define WEBP_NEON_WORK_AROUND_GCC 1 +#else +#define WEBP_NEON_WORK_AROUND_GCC 0 +#endif + +//------------------------------------------------------------------------------ + +// This macro prevents thread_sanitizer from reporting known concurrent writes. +#define WEBP_TSAN_IGNORE_FUNCTION +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#undef WEBP_TSAN_IGNORE_FUNCTION +#define WEBP_TSAN_IGNORE_FUNCTION __attribute__((no_sanitize_thread)) +#endif +#endif + +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define WEBP_MSAN +#endif +#endif + +#if defined(WEBP_USE_THREAD) && !defined(_WIN32) +#include // NOLINT + +#define WEBP_DSP_INIT(func) \ + do { \ + static volatile VP8CPUInfo func##_last_cpuinfo_used = \ + (VP8CPUInfo)&func##_last_cpuinfo_used; \ + static pthread_mutex_t func##_lock = PTHREAD_MUTEX_INITIALIZER; \ + if (pthread_mutex_lock(&func##_lock)) break; \ + if (func##_last_cpuinfo_used != VP8GetCPUInfo) func(); \ + func##_last_cpuinfo_used = VP8GetCPUInfo; \ + (void)pthread_mutex_unlock(&func##_lock); \ + } while (0) +#else // !(defined(WEBP_USE_THREAD) && !defined(_WIN32)) +#define WEBP_DSP_INIT(func) \ + do { \ + static volatile VP8CPUInfo func##_last_cpuinfo_used = \ + (VP8CPUInfo)&func##_last_cpuinfo_used; \ + if (func##_last_cpuinfo_used == VP8GetCPUInfo) break; \ + func(); \ + func##_last_cpuinfo_used = VP8GetCPUInfo; \ + } while (0) +#endif // defined(WEBP_USE_THREAD) && !defined(_WIN32) + +// Defines an Init + helper function that control multiple initialization of +// function pointers / tables. +/* Usage: + WEBP_DSP_INIT_FUNC(InitFunc) { + ...function body + } +*/ +#define WEBP_DSP_INIT_FUNC(name) \ + static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void); \ + WEBP_TSAN_IGNORE_FUNCTION void name(void) { WEBP_DSP_INIT(name##_body); } \ + static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void) + +#define WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(no_sanitize) +// This macro prevents the undefined behavior sanitizer from reporting +// failures. This is only meant to silence unaligned loads on platforms that +// are known to support them. +#undef WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNDEF __attribute__((no_sanitize("undefined"))) + +// This macro prevents the undefined behavior sanitizer from reporting +// failures related to unsigned integer overflows. This is only meant to +// silence cases where this well defined behavior is expected. +#undef WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#endif +#endif + +// If 'ptr' is NULL, returns NULL. Otherwise returns 'ptr + off'. +// Prevents undefined behavior sanitizer nullptr-with-nonzero-offset warning. +#if !defined(WEBP_OFFSET_PTR) +#define WEBP_OFFSET_PTR(ptr, off) (((ptr) == NULL) ? NULL : ((ptr) + (off))) +#endif + +// Regularize the definition of WEBP_SWAP_16BIT_CSP (backward compatibility) +#if !defined(WEBP_SWAP_16BIT_CSP) +#define WEBP_SWAP_16BIT_CSP 0 +#endif + +// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) && \ + (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) +#define WORDS_BIGENDIAN +#endif + +typedef enum { + kSSE2, + kSSE3, + kSlowSSSE3, // special feature for slow SSSE3 architectures + kSSE4_1, + kAVX, + kAVX2, + kNEON, + kMIPS32, + kMIPSdspR2, + kMSA +} CPUFeature; + +// returns true if the CPU supports the feature. +typedef int (*VP8CPUInfo)(CPUFeature feature); + +#endif // WEBP_DSP_CPU_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec.c new file mode 100644 index 0000000000..451d649d58 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec.c @@ -0,0 +1,887 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Speed-critical decoding functions, default plain-C implementations. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dsp/dsp.h" +#include "src/dec/vp8i_dec.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ + +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; +} + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +#define STORE(x, y, v) \ + dst[(x) + (y) * BPS] = clip_8b(dst[(x) + (y) * BPS] + ((v) >> 3)) + +#define STORE2(y, dc, d, c) do { \ + const int DC = (dc); \ + STORE(0, y, DC + (d)); \ + STORE(1, y, DC + (c)); \ + STORE(2, y, DC - (c)); \ + STORE(3, y, DC - (d)); \ +} while (0) + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformOne_C(const int16_t* in, uint8_t* dst) { + int C[4 * 4], *tmp; + int i; + tmp = C; + for (i = 0; i < 4; ++i) { // vertical pass + const int a = in[0] + in[8]; // [-4096, 4094] + const int b = in[0] - in[8]; // [-4095, 4095] + const int c = WEBP_TRANSFORM_AC3_MUL2(in[4]) - + WEBP_TRANSFORM_AC3_MUL1(in[12]); // [-3783, 3783] + const int d = WEBP_TRANSFORM_AC3_MUL1(in[4]) + + WEBP_TRANSFORM_AC3_MUL2(in[12]); // [-3785, 3781] + tmp[0] = a + d; // [-7881, 7875] + tmp[1] = b + c; // [-7878, 7878] + tmp[2] = b - c; // [-7878, 7878] + tmp[3] = a - d; // [-7877, 7879] + tmp += 4; + in++; + } + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value + // in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as + // [-60713, 60968]. + tmp = C; + for (i = 0; i < 4; ++i) { // horizontal pass + const int dc = tmp[0] + 4; + const int a = dc + tmp[8]; + const int b = dc - tmp[8]; + const int c = + WEBP_TRANSFORM_AC3_MUL2(tmp[4]) - WEBP_TRANSFORM_AC3_MUL1(tmp[12]); + const int d = + WEBP_TRANSFORM_AC3_MUL1(tmp[4]) + WEBP_TRANSFORM_AC3_MUL2(tmp[12]); + STORE(0, 0, a + d); + STORE(1, 0, b + c); + STORE(2, 0, b - c); + STORE(3, 0, a - d); + tmp++; + dst += BPS; + } +} + +// Simplified transform when only in[0], in[1] and in[4] are non-zero +static void TransformAC3_C(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + const int c4 = WEBP_TRANSFORM_AC3_MUL2(in[4]); + const int d4 = WEBP_TRANSFORM_AC3_MUL1(in[4]); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + STORE2(0, a + d4, d1, c1); + STORE2(1, a + c4, d1, c1); + STORE2(2, a - c4, d1, c1); + STORE2(3, a - d4, d1, c1); +} +#undef STORE2 + +static void TransformTwo_C(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne_C(in, dst); + if (do_two) { + TransformOne_C(in + 16, dst + 4); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void TransformUV_C(const int16_t* in, uint8_t* dst) { + VP8Transform(in + 0 * 16, dst, 1); + VP8Transform(in + 2 * 16, dst + 4 * BPS, 1); +} + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformDC_C(const int16_t* in, uint8_t* dst) { + const int DC = in[0] + 4; + int i, j; + for (j = 0; j < 4; ++j) { + for (i = 0; i < 4; ++i) { + STORE(i, j, DC); + } + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void TransformDCUV_C(const int16_t* in, uint8_t* dst) { + if (in[0 * 16]) VP8TransformDC(in + 0 * 16, dst); + if (in[1 * 16]) VP8TransformDC(in + 1 * 16, dst + 4); + if (in[2 * 16]) VP8TransformDC(in + 2 * 16, dst + 4 * BPS); + if (in[3 * 16]) VP8TransformDC(in + 3 * 16, dst + 4 * BPS + 4); +} + +#undef STORE + +//------------------------------------------------------------------------------ +// Paragraph 14.3 + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformWHT_C(const int16_t* in, int16_t* out) { + int tmp[16]; + int i; + for (i = 0; i < 4; ++i) { + const int a0 = in[0 + i] + in[12 + i]; + const int a1 = in[4 + i] + in[ 8 + i]; + const int a2 = in[4 + i] - in[ 8 + i]; + const int a3 = in[0 + i] - in[12 + i]; + tmp[0 + i] = a0 + a1; + tmp[8 + i] = a0 - a1; + tmp[4 + i] = a3 + a2; + tmp[12 + i] = a3 - a2; + } + for (i = 0; i < 4; ++i) { + const int dc = tmp[0 + i * 4] + 3; // w/ rounder + const int a0 = dc + tmp[3 + i * 4]; + const int a1 = tmp[1 + i * 4] + tmp[2 + i * 4]; + const int a2 = tmp[1 + i * 4] - tmp[2 + i * 4]; + const int a3 = dc - tmp[3 + i * 4]; + out[ 0] = (a0 + a1) >> 3; + out[16] = (a3 + a2) >> 3; + out[32] = (a0 - a1) >> 3; + out[48] = (a3 - a2) >> 3; + out += 64; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +void (*VP8TransformWHT)(const int16_t* in, int16_t* out); + +//------------------------------------------------------------------------------ +// Intra predictions + +#define DST(x, y) dst[(x) + (y) * BPS] + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) { + const uint8_t* top = dst - BPS; + const uint8_t* const clip0 = VP8kclip1 - top[-1]; + int y; + for (y = 0; y < size; ++y) { + const uint8_t* const clip = clip0 + dst[-1]; + int x; + for (x = 0; x < size; ++x) { + dst[x] = clip[top[x]]; + } + dst += BPS; + } +} +static void TM4_C(uint8_t* dst) { TrueMotion(dst, 4); } +static void TM8uv_C(uint8_t* dst) { TrueMotion(dst, 8); } +static void TM16_C(uint8_t* dst) { TrueMotion(dst, 16); } + +//------------------------------------------------------------------------------ +// 16x16 + +static void VE16_C(uint8_t* dst) { // vertical + int j; + for (j = 0; j < 16; ++j) { + memcpy(dst + j * BPS, dst - BPS, 16); + } +} + +static void HE16_C(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; --j) { + memset(dst, dst[-1], 16); + dst += BPS; + } +} + +static WEBP_INLINE void Put16(int v, uint8_t* dst) { + int j; + for (j = 0; j < 16; ++j) { + memset(dst + j * BPS, v, 16); + } +} + +static void DC16_C(uint8_t* dst) { // DC + int DC = 16; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS] + dst[j - BPS]; + } + Put16(DC >> 5, dst); +} + +static void DC16NoTop_C(uint8_t* dst) { // DC with top samples not available + int DC = 8; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS]; + } + Put16(DC >> 4, dst); +} + +static void DC16NoLeft_C(uint8_t* dst) { // DC with left samples not available + int DC = 8; + int i; + for (i = 0; i < 16; ++i) { + DC += dst[i - BPS]; + } + Put16(DC >> 4, dst); +} + +static void DC16NoTopLeft_C(uint8_t* dst) { // DC with no top and left samples + Put16(0x80, dst); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES]; + +//------------------------------------------------------------------------------ +// 4x4 + +#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2)) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +#if !WEBP_NEON_OMIT_C_CODE +static void VE4_C(uint8_t* dst) { // vertical + const uint8_t* top = dst - BPS; + const uint8_t vals[4] = { + AVG3(top[-1], top[0], top[1]), + AVG3(top[ 0], top[1], top[2]), + AVG3(top[ 1], top[2], top[3]), + AVG3(top[ 2], top[3], top[4]) + }; + int i; + for (i = 0; i < 4; ++i) { + memcpy(dst + i * BPS, vals, sizeof(vals)); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void HE4_C(uint8_t* dst) { // horizontal + const int A = dst[-1 - BPS]; + const int B = dst[-1]; + const int C = dst[-1 + BPS]; + const int D = dst[-1 + 2 * BPS]; + const int E = dst[-1 + 3 * BPS]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(A, B, C)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(B, C, D)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(C, D, E)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(D, E, E)); +} + +#if !WEBP_NEON_OMIT_C_CODE +static void DC4_C(uint8_t* dst) { // DC + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS]; + dc >>= 3; + for (i = 0; i < 4; ++i) memset(dst + i * BPS, dc, 4); +} + +static void RD4_C(uint8_t* dst) { // Down-right + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + DST(0, 3) = AVG3(J, K, L); + DST(1, 3) = DST(0, 2) = AVG3(I, J, K); + DST(2, 3) = DST(1, 2) = DST(0, 1) = AVG3(X, I, J); + DST(3, 3) = DST(2, 2) = DST(1, 1) = DST(0, 0) = AVG3(A, X, I); + DST(3, 2) = DST(2, 1) = DST(1, 0) = AVG3(B, A, X); + DST(3, 1) = DST(2, 0) = AVG3(C, B, A); + DST(3, 0) = AVG3(D, C, B); +} + +static void LD4_C(uint8_t* dst) { // Down-Left + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + const int E = dst[4 - BPS]; + const int F = dst[5 - BPS]; + const int G = dst[6 - BPS]; + const int H = dst[7 - BPS]; + DST(0, 0) = AVG3(A, B, C); + DST(1, 0) = DST(0, 1) = AVG3(B, C, D); + DST(2, 0) = DST(1, 1) = DST(0, 2) = AVG3(C, D, E); + DST(3, 0) = DST(2, 1) = DST(1, 2) = DST(0, 3) = AVG3(D, E, F); + DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G); + DST(3, 2) = DST(2, 3) = AVG3(F, G, H); + DST(3, 3) = AVG3(G, H, H); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void VR4_C(uint8_t* dst) { // Vertical-Right + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + DST(0, 0) = DST(1, 2) = AVG2(X, A); + DST(1, 0) = DST(2, 2) = AVG2(A, B); + DST(2, 0) = DST(3, 2) = AVG2(B, C); + DST(3, 0) = AVG2(C, D); + + DST(0, 3) = AVG3(K, J, I); + DST(0, 2) = AVG3(J, I, X); + DST(0, 1) = DST(1, 3) = AVG3(I, X, A); + DST(1, 1) = DST(2, 3) = AVG3(X, A, B); + DST(2, 1) = DST(3, 3) = AVG3(A, B, C); + DST(3, 1) = AVG3(B, C, D); +} + +static void VL4_C(uint8_t* dst) { // Vertical-Left + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + const int E = dst[4 - BPS]; + const int F = dst[5 - BPS]; + const int G = dst[6 - BPS]; + const int H = dst[7 - BPS]; + DST(0, 0) = AVG2(A, B); + DST(1, 0) = DST(0, 2) = AVG2(B, C); + DST(2, 0) = DST(1, 2) = AVG2(C, D); + DST(3, 0) = DST(2, 2) = AVG2(D, E); + + DST(0, 1) = AVG3(A, B, C); + DST(1, 1) = DST(0, 3) = AVG3(B, C, D); + DST(2, 1) = DST(1, 3) = AVG3(C, D, E); + DST(3, 1) = DST(2, 3) = AVG3(D, E, F); + DST(3, 2) = AVG3(E, F, G); + DST(3, 3) = AVG3(F, G, H); +} + +static void HU4_C(uint8_t* dst) { // Horizontal-Up + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static void HD4_C(uint8_t* dst) { // Horizontal-Down + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +#undef DST +#undef AVG3 +#undef AVG2 + +VP8PredFunc VP8PredLuma4[NUM_BMODES]; + +//------------------------------------------------------------------------------ +// Chroma + +#if !WEBP_NEON_OMIT_C_CODE +static void VE8uv_C(uint8_t* dst) { // vertical + int j; + for (j = 0; j < 8; ++j) { + memcpy(dst + j * BPS, dst - BPS, 8); + } +} + +static void HE8uv_C(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + memset(dst, dst[-1], 8); + dst += BPS; + } +} + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv(uint8_t value, uint8_t* dst) { + int j; + for (j = 0; j < 8; ++j) { + memset(dst + j * BPS, value, 8); + } +} + +static void DC8uv_C(uint8_t* dst) { // DC + int dc0 = 8; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[i - BPS] + dst[-1 + i * BPS]; + } + Put8x8uv(dc0 >> 4, dst); +} + +static void DC8uvNoLeft_C(uint8_t* dst) { // DC with no left samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[i - BPS]; + } + Put8x8uv(dc0 >> 3, dst); +} + +static void DC8uvNoTop_C(uint8_t* dst) { // DC with no top samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[-1 + i * BPS]; + } + Put8x8uv(dc0 >> 3, dst); +} + +static void DC8uvNoTopLeft_C(uint8_t* dst) { // DC with nothing + Put8x8uv(0x80, dst); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES]; + +//------------------------------------------------------------------------------ +// Edge filtering functions + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +// 4 pixels in, 2 pixels out +static WEBP_INLINE void DoFilter2_C(uint8_t* p, int step) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; // in [-893,892] + const int a1 = VP8ksclip2[(a + 4) >> 3]; // in [-16,15] + const int a2 = VP8ksclip2[(a + 3) >> 3]; + p[-step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; +} + +// 4 pixels in, 4 pixels out +static WEBP_INLINE void DoFilter4_C(uint8_t* p, int step) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0); + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + const int a3 = (a1 + 1) >> 1; + p[-2*step] = VP8kclip1[p1 + a3]; + p[- step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a3]; +} + +// 6 pixels in, 6 pixels out +static WEBP_INLINE void DoFilter6_C(uint8_t* p, int step) { + const int p2 = p[-3*step], p1 = p[-2*step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2*step]; + const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]]; + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[-3*step] = VP8kclip1[p2 + a3]; + p[-2*step] = VP8kclip1[p1 + a2]; + p[- step] = VP8kclip1[p0 + a1]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a2]; + p[ 2*step] = VP8kclip1[q2 - a3]; +} + +static WEBP_INLINE int Hev(const uint8_t* p, int step, int thresh) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return (VP8kabs0[p1 - p0] > thresh) || (VP8kabs0[q1 - q0] > thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE int NeedsFilter_C(const uint8_t* p, int step, int t) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) <= t); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static WEBP_INLINE int NeedsFilter2_C(const uint8_t* p, + int step, int t, int it) { + const int p3 = p[-4 * step], p2 = p[-3 * step], p1 = p[-2 * step]; + const int p0 = p[-step], q0 = p[0]; + const int q1 = p[step], q2 = p[2 * step], q3 = p[3 * step]; + if ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) > t) return 0; + return VP8kabs0[p3 - p2] <= it && VP8kabs0[p2 - p1] <= it && + VP8kabs0[p1 - p0] <= it && VP8kabs0[q3 - q2] <= it && + VP8kabs0[q2 - q1] <= it && VP8kabs0[q1 - q0] <= it; +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +#if !WEBP_NEON_OMIT_C_CODE +static void SimpleVFilter16_C(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (NeedsFilter_C(p + i, stride, thresh2)) { + DoFilter2_C(p + i, stride); + } + } +} + +static void SimpleHFilter16_C(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (NeedsFilter_C(p + i * stride, 1, thresh2)) { + DoFilter2_C(p + i * stride, 1); + } + } +} + +static void SimpleVFilter16i_C(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16_C(p, stride, thresh); + } +} + +static void SimpleHFilter16i_C(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16_C(p, stride, thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static WEBP_INLINE void FilterLoop26_C(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, + int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) { + if (Hev(p, hstride, hev_thresh)) { + DoFilter2_C(p, hstride); + } else { + DoFilter6_C(p, hstride); + } + } + p += vstride; + } +} + +static WEBP_INLINE void FilterLoop24_C(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, + int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) { + if (Hev(p, hstride, hev_thresh)) { + DoFilter2_C(p, hstride); + } else { + DoFilter4_C(p, hstride); + } + } + p += vstride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +// on macroblock edges +static void VFilter16_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24_C(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter16i_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24_C(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +// 8-pixels wide variant, for chroma filtering +static void VFilter8_C(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26_C(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter8_C(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26_C(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +static void VFilter8i_C(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24_C(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24_C(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter8i_C(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24_C(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24_C(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +//------------------------------------------------------------------------------ + +static void DitherCombine8x8_C(const uint8_t* dither, uint8_t* dst, + int dst_stride) { + int i, j; + for (j = 0; j < 8; ++j) { + for (i = 0; i < 8; ++i) { + const int delta0 = dither[i] - VP8_DITHER_AMP_CENTER; + const int delta1 = + (delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE; + dst[i] = clip_8b((int)dst[i] + delta1); + } + dst += dst_stride; + dither += 8; + } +} + +//------------------------------------------------------------------------------ + +VP8DecIdct2 VP8Transform; +VP8DecIdct VP8TransformAC3; +VP8DecIdct VP8TransformUV; +VP8DecIdct VP8TransformDC; +VP8DecIdct VP8TransformDCUV; + +VP8LumaFilterFunc VP8VFilter16; +VP8LumaFilterFunc VP8HFilter16; +VP8ChromaFilterFunc VP8VFilter8; +VP8ChromaFilterFunc VP8HFilter8; +VP8LumaFilterFunc VP8VFilter16i; +VP8LumaFilterFunc VP8HFilter16i; +VP8ChromaFilterFunc VP8VFilter8i; +VP8ChromaFilterFunc VP8HFilter8i; +VP8SimpleFilterFunc VP8SimpleVFilter16; +VP8SimpleFilterFunc VP8SimpleHFilter16; +VP8SimpleFilterFunc VP8SimpleVFilter16i; +VP8SimpleFilterFunc VP8SimpleHFilter16i; + +void (*VP8DitherCombine8x8)(const uint8_t* dither, uint8_t* dst, + int dst_stride); + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8DspInitSSE2(void); +extern void VP8DspInitSSE41(void); +extern void VP8DspInitNEON(void); +extern void VP8DspInitMIPS32(void); +extern void VP8DspInitMIPSdspR2(void); +extern void VP8DspInitMSA(void); + +WEBP_DSP_INIT_FUNC(VP8DspInit) { + VP8InitClipTables(); + +#if !WEBP_NEON_OMIT_C_CODE + VP8TransformWHT = TransformWHT_C; + VP8Transform = TransformTwo_C; + VP8TransformDC = TransformDC_C; + VP8TransformAC3 = TransformAC3_C; +#endif + VP8TransformUV = TransformUV_C; + VP8TransformDCUV = TransformDCUV_C; + +#if !WEBP_NEON_OMIT_C_CODE + VP8VFilter16 = VFilter16_C; + VP8VFilter16i = VFilter16i_C; + VP8HFilter16 = HFilter16_C; + VP8VFilter8 = VFilter8_C; + VP8VFilter8i = VFilter8i_C; + VP8SimpleVFilter16 = SimpleVFilter16_C; + VP8SimpleHFilter16 = SimpleHFilter16_C; + VP8SimpleVFilter16i = SimpleVFilter16i_C; + VP8SimpleHFilter16i = SimpleHFilter16i_C; +#endif + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + VP8HFilter16i = HFilter16i_C; + VP8HFilter8 = HFilter8_C; + VP8HFilter8i = HFilter8i_C; +#endif + +#if !WEBP_NEON_OMIT_C_CODE + VP8PredLuma4[0] = DC4_C; + VP8PredLuma4[1] = TM4_C; + VP8PredLuma4[2] = VE4_C; + VP8PredLuma4[4] = RD4_C; + VP8PredLuma4[6] = LD4_C; +#endif + + VP8PredLuma4[3] = HE4_C; + VP8PredLuma4[5] = VR4_C; + VP8PredLuma4[7] = VL4_C; + VP8PredLuma4[8] = HD4_C; + VP8PredLuma4[9] = HU4_C; + +#if !WEBP_NEON_OMIT_C_CODE + VP8PredLuma16[0] = DC16_C; + VP8PredLuma16[1] = TM16_C; + VP8PredLuma16[2] = VE16_C; + VP8PredLuma16[3] = HE16_C; + VP8PredLuma16[4] = DC16NoTop_C; + VP8PredLuma16[5] = DC16NoLeft_C; + VP8PredLuma16[6] = DC16NoTopLeft_C; + + VP8PredChroma8[0] = DC8uv_C; + VP8PredChroma8[1] = TM8uv_C; + VP8PredChroma8[2] = VE8uv_C; + VP8PredChroma8[3] = HE8uv_C; + VP8PredChroma8[4] = DC8uvNoTop_C; + VP8PredChroma8[5] = DC8uvNoLeft_C; + VP8PredChroma8[6] = DC8uvNoTopLeft_C; +#endif + + VP8DitherCombine8x8 = DitherCombine8x8_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8DspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8DspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8DspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8DspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8DspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8DspInitNEON(); + } +#endif + + assert(VP8TransformWHT != NULL); + assert(VP8Transform != NULL); + assert(VP8TransformDC != NULL); + assert(VP8TransformAC3 != NULL); + assert(VP8TransformUV != NULL); + assert(VP8TransformDCUV != NULL); + assert(VP8VFilter16 != NULL); + assert(VP8HFilter16 != NULL); + assert(VP8VFilter8 != NULL); + assert(VP8HFilter8 != NULL); + assert(VP8VFilter16i != NULL); + assert(VP8HFilter16i != NULL); + assert(VP8VFilter8i != NULL); + assert(VP8HFilter8i != NULL); + assert(VP8SimpleVFilter16 != NULL); + assert(VP8SimpleHFilter16 != NULL); + assert(VP8SimpleVFilter16i != NULL); + assert(VP8SimpleHFilter16i != NULL); + assert(VP8PredLuma4[0] != NULL); + assert(VP8PredLuma4[1] != NULL); + assert(VP8PredLuma4[2] != NULL); + assert(VP8PredLuma4[3] != NULL); + assert(VP8PredLuma4[4] != NULL); + assert(VP8PredLuma4[5] != NULL); + assert(VP8PredLuma4[6] != NULL); + assert(VP8PredLuma4[7] != NULL); + assert(VP8PredLuma4[8] != NULL); + assert(VP8PredLuma4[9] != NULL); + assert(VP8PredLuma16[0] != NULL); + assert(VP8PredLuma16[1] != NULL); + assert(VP8PredLuma16[2] != NULL); + assert(VP8PredLuma16[3] != NULL); + assert(VP8PredLuma16[4] != NULL); + assert(VP8PredLuma16[5] != NULL); + assert(VP8PredLuma16[6] != NULL); + assert(VP8PredChroma8[0] != NULL); + assert(VP8PredChroma8[1] != NULL); + assert(VP8PredChroma8[2] != NULL); + assert(VP8PredChroma8[3] != NULL); + assert(VP8PredChroma8[4] != NULL); + assert(VP8PredChroma8[5] != NULL); + assert(VP8PredChroma8[6] != NULL); + assert(VP8DitherCombine8x8 != NULL); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_clip_tables.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_clip_tables.c new file mode 100644 index 0000000000..427b74f776 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_clip_tables.c @@ -0,0 +1,369 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Clipping tables for filtering +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +// define to 0 to have run-time table initialization +#if !defined(USE_STATIC_TABLES) +#define USE_STATIC_TABLES 1 // ALTERNATE_CODE +#endif + +#if (USE_STATIC_TABLES == 1) + +static const uint8_t abs0[255 + 255 + 1] = { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, + 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, 0xdd, 0xdc, + 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, + 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, + 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, + 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, + 0xab, 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, + 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, + 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, + 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, + 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, + 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, + 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, + 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, + 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, + 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +static const uint8_t sclip1[1020 + 1020 + 1] = { + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f +}; + +static const uint8_t sclip2[112 + 112 + 1] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f +}; + +static const uint8_t clip1[255 + 511 + 1] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +#else + +// uninitialized tables +static uint8_t abs0[255 + 255 + 1]; +static int8_t sclip1[1020 + 1020 + 1]; +static int8_t sclip2[112 + 112 + 1]; +static uint8_t clip1[255 + 511 + 1]; + +// We declare this variable 'volatile' to prevent instruction reordering +// and make sure it's set to true _last_ (so as to be thread-safe) +static volatile int tables_ok = 0; + +#endif // USE_STATIC_TABLES + +const int8_t* const VP8ksclip1 = (const int8_t*)&sclip1[1020]; +const int8_t* const VP8ksclip2 = (const int8_t*)&sclip2[112]; +const uint8_t* const VP8kclip1 = &clip1[255]; +const uint8_t* const VP8kabs0 = &abs0[255]; + +WEBP_TSAN_IGNORE_FUNCTION void VP8InitClipTables(void) { +#if (USE_STATIC_TABLES == 0) + int i; + if (!tables_ok) { + for (i = -255; i <= 255; ++i) { + abs0[255 + i] = (i < 0) ? -i : i; + } + for (i = -1020; i <= 1020; ++i) { + sclip1[1020 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i; + } + for (i = -112; i <= 112; ++i) { + sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i; + } + for (i = -255; i <= 255 + 255; ++i) { + clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i; + } + tables_ok = 1; + } +#endif // USE_STATIC_TABLES +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips32.c new file mode 100644 index 0000000000..f0e7de4ac4 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips32.c @@ -0,0 +1,571 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of dsp functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "src/dsp/mips_macro.h" + +static const int kC1 = WEBP_TRANSFORM_AC3_C1; +static const int kC2 = WEBP_TRANSFORM_AC3_C2; + +static WEBP_INLINE int abs_mips32(int x) { + const int sign = x >> 31; + return (x ^ sign) - sign; +} + +// 4 pixels in, 2 pixels out +static WEBP_INLINE void do_filter2(uint8_t* p, int step) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + p[-step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; +} + +// 4 pixels in, 4 pixels out +static WEBP_INLINE void do_filter4(uint8_t* p, int step) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0); + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + const int a3 = (a1 + 1) >> 1; + p[-2 * step] = VP8kclip1[p1 + a3]; + p[- step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a3]; +} + +// 6 pixels in, 6 pixels out +static WEBP_INLINE void do_filter6(uint8_t* p, int step) { + const int p2 = p[-3 * step], p1 = p[-2 * step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2 * step]; + const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]]; + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[-3 * step] = VP8kclip1[p2 + a3]; + p[-2 * step] = VP8kclip1[p1 + a2]; + p[- step] = VP8kclip1[p0 + a1]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a2]; + p[ 2 * step] = VP8kclip1[q2 - a3]; +} + +static WEBP_INLINE int hev(const uint8_t* p, int step, int thresh) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return (abs_mips32(p1 - p0) > thresh) || (abs_mips32(q1 - q0) > thresh); +} + +static WEBP_INLINE int needs_filter(const uint8_t* p, int step, int t) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return ((4 * abs_mips32(p0 - q0) + abs_mips32(p1 - q1)) <= t); +} + +static WEBP_INLINE int needs_filter2(const uint8_t* p, + int step, int t, int it) { + const int p3 = p[-4 * step], p2 = p[-3 * step]; + const int p1 = p[-2 * step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2 * step], q3 = p[3 * step]; + if ((4 * abs_mips32(p0 - q0) + abs_mips32(p1 - q1)) > t) { + return 0; + } + return abs_mips32(p3 - p2) <= it && abs_mips32(p2 - p1) <= it && + abs_mips32(p1 - p0) <= it && abs_mips32(q3 - q2) <= it && + abs_mips32(q2 - q1) <= it && abs_mips32(q1 - q0) <= it; +} + +static WEBP_INLINE void FilterLoop26(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { + if (hev(p, hstride, hev_thresh)) { + do_filter2(p, hstride); + } else { + do_filter6(p, hstride); + } + } + p += vstride; + } +} + +static WEBP_INLINE void FilterLoop24(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (needs_filter2(p, hstride, thresh2, ithresh)) { + if (hev(p, hstride, hev_thresh)) { + do_filter2(p, hstride); + } else { + do_filter4(p, hstride); + } + } + p += vstride; + } +} + +// on macroblock edges +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} + +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (needs_filter(p + i, stride, thresh2)) { + do_filter2(p + i, stride); + } + } +} + +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (needs_filter(p + i * stride, 1, thresh2)) { + do_filter2(p + i * stride, 1); + } + } +} + +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16(p, stride, thresh); + } +} + +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16(p, stride, thresh); + } +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14; + int temp15, temp16, temp17, temp18, temp19; + int16_t* p_in = (int16_t*)in; + + // loops unrolled and merged to avoid usage of tmp buffer + // and to reduce number of stalls. MUL macro is written + // in assembler and inlined + __asm__ volatile( + "lh %[temp0], 0(%[in]) \n\t" + "lh %[temp8], 16(%[in]) \n\t" + "lh %[temp4], 8(%[in]) \n\t" + "lh %[temp12], 24(%[in]) \n\t" + "addu %[temp16], %[temp0], %[temp8] \n\t" + "subu %[temp0], %[temp0], %[temp8] \n\t" + "mul %[temp8], %[temp4], %[kC2] \n\t" + MUL_SHIFT_C1(temp17, temp12) + MUL_SHIFT_C1_IO(temp4, temp19) + "mul %[temp12], %[temp12], %[kC2] \n\t" + "lh %[temp1], 2(%[in]) \n\t" + "lh %[temp5], 10(%[in]) \n\t" + "lh %[temp9], 18(%[in]) \n\t" + "lh %[temp13], 26(%[in]) \n\t" + "sra %[temp8], %[temp8], 16 \n\t" + "sra %[temp12], %[temp12], 16 \n\t" + "lh %[temp2], 4(%[in]) \n\t" + "lh %[temp6], 12(%[in]) \n\t" + "lh %[temp10], 20(%[in]) \n\t" + "lh %[temp14], 28(%[in]) \n\t" + "subu %[temp17], %[temp8], %[temp17] \n\t" + "addu %[temp4], %[temp4], %[temp12] \n\t" + "addu %[temp8], %[temp16], %[temp4] \n\t" + "subu %[temp4], %[temp16], %[temp4] \n\t" + "addu %[temp16], %[temp1], %[temp9] \n\t" + "subu %[temp1], %[temp1], %[temp9] \n\t" + "lh %[temp3], 6(%[in]) \n\t" + "lh %[temp7], 14(%[in]) \n\t" + "lh %[temp11], 22(%[in]) \n\t" + "lh %[temp15], 30(%[in]) \n\t" + "addu %[temp12], %[temp0], %[temp17] \n\t" + "subu %[temp0], %[temp0], %[temp17] \n\t" + "mul %[temp9], %[temp5], %[kC2] \n\t" + MUL_SHIFT_C1(temp17, temp13) + MUL_SHIFT_C1_IO(temp5, temp19) + "mul %[temp13], %[temp13], %[kC2] \n\t" + "sra %[temp9], %[temp9], 16 \n\t" + "subu %[temp17], %[temp9], %[temp17] \n\t" + "sra %[temp13], %[temp13], 16 \n\t" + "addu %[temp5], %[temp5], %[temp13] \n\t" + "addu %[temp13], %[temp1], %[temp17] \n\t" + "subu %[temp1], %[temp1], %[temp17] \n\t" + MUL_SHIFT_C1(temp17, temp14) + "mul %[temp14], %[temp14], %[kC2] \n\t" + "addu %[temp9], %[temp16], %[temp5] \n\t" + "subu %[temp5], %[temp16], %[temp5] \n\t" + "addu %[temp16], %[temp2], %[temp10] \n\t" + "subu %[temp2], %[temp2], %[temp10] \n\t" + "mul %[temp10], %[temp6], %[kC2] \n\t" + MUL_SHIFT_C1_IO(temp6, temp19) + "sra %[temp14], %[temp14], 16 \n\t" + "sra %[temp10], %[temp10], 16 \n\t" + "subu %[temp17], %[temp10], %[temp17] \n\t" + "addu %[temp6], %[temp6], %[temp14] \n\t" + "addu %[temp10], %[temp16], %[temp6] \n\t" + "subu %[temp6], %[temp16], %[temp6] \n\t" + "addu %[temp14], %[temp2], %[temp17] \n\t" + "subu %[temp2], %[temp2], %[temp17] \n\t" + MUL_SHIFT_C1(temp17, temp15) + "mul %[temp15], %[temp15], %[kC2] \n\t" + "addu %[temp16], %[temp3], %[temp11] \n\t" + "subu %[temp3], %[temp3], %[temp11] \n\t" + "mul %[temp11], %[temp7], %[kC2] \n\t" + MUL_SHIFT_C1_IO(temp7, temp19) + "addiu %[temp8], %[temp8], 4 \n\t" + "addiu %[temp12], %[temp12], 4 \n\t" + "addiu %[temp0], %[temp0], 4 \n\t" + "addiu %[temp4], %[temp4], 4 \n\t" + "sra %[temp15], %[temp15], 16 \n\t" + "sra %[temp11], %[temp11], 16 \n\t" + "subu %[temp17], %[temp11], %[temp17] \n\t" + "addu %[temp7], %[temp7], %[temp15] \n\t" + "addu %[temp15], %[temp3], %[temp17] \n\t" + "subu %[temp3], %[temp3], %[temp17] \n\t" + "addu %[temp11], %[temp16], %[temp7] \n\t" + "subu %[temp7], %[temp16], %[temp7] \n\t" + "addu %[temp16], %[temp8], %[temp10] \n\t" + "subu %[temp8], %[temp8], %[temp10] \n\t" + "mul %[temp10], %[temp9], %[kC2] \n\t" + MUL_SHIFT_C1(temp17, temp11) + MUL_SHIFT_C1_IO(temp9, temp19) + "mul %[temp11], %[temp11], %[kC2] \n\t" + "sra %[temp10], %[temp10], 16 \n\t" + "sra %[temp11], %[temp11], 16 \n\t" + "subu %[temp17], %[temp10], %[temp17] \n\t" + "addu %[temp11], %[temp9], %[temp11] \n\t" + "addu %[temp10], %[temp12], %[temp14] \n\t" + "subu %[temp12], %[temp12], %[temp14] \n\t" + "mul %[temp14], %[temp13], %[kC2] \n\t" + MUL_SHIFT_C1(temp9, temp15) + MUL_SHIFT_C1_IO(temp13, temp19) + "mul %[temp15], %[temp15], %[kC2] \n\t" + "sra %[temp14], %[temp14], 16 \n\t" + "sra %[temp15], %[temp15], 16 \n\t" + "subu %[temp9], %[temp14], %[temp9] \n\t" + "addu %[temp15], %[temp13], %[temp15] \n\t" + "addu %[temp14], %[temp0], %[temp2] \n\t" + "subu %[temp0], %[temp0], %[temp2] \n\t" + "mul %[temp2], %[temp1], %[kC2] \n\t" + MUL_SHIFT_C1(temp13, temp3) + MUL_SHIFT_C1_IO(temp1, temp19) + "mul %[temp3], %[temp3], %[kC2] \n\t" + "sra %[temp2], %[temp2], 16 \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "subu %[temp13], %[temp2], %[temp13] \n\t" + "addu %[temp3], %[temp1], %[temp3] \n\t" + "addu %[temp2], %[temp4], %[temp6] \n\t" + "subu %[temp4], %[temp4], %[temp6] \n\t" + "mul %[temp6], %[temp5], %[kC2] \n\t" + MUL_SHIFT_C1(temp1, temp7) + MUL_SHIFT_C1_IO(temp5, temp19) + "mul %[temp7], %[temp7], %[kC2] \n\t" + "sra %[temp6], %[temp6], 16 \n\t" + "sra %[temp7], %[temp7], 16 \n\t" + "subu %[temp1], %[temp6], %[temp1] \n\t" + "addu %[temp7], %[temp5], %[temp7] \n\t" + "addu %[temp5], %[temp16], %[temp11] \n\t" + "subu %[temp16], %[temp16], %[temp11] \n\t" + "addu %[temp11], %[temp8], %[temp17] \n\t" + "subu %[temp8], %[temp8], %[temp17] \n\t" + "sra %[temp5], %[temp5], 3 \n\t" + "sra %[temp16], %[temp16], 3 \n\t" + "sra %[temp11], %[temp11], 3 \n\t" + "sra %[temp8], %[temp8], 3 \n\t" + "addu %[temp17], %[temp10], %[temp15] \n\t" + "subu %[temp10], %[temp10], %[temp15] \n\t" + "addu %[temp15], %[temp12], %[temp9] \n\t" + "subu %[temp12], %[temp12], %[temp9] \n\t" + "sra %[temp17], %[temp17], 3 \n\t" + "sra %[temp10], %[temp10], 3 \n\t" + "sra %[temp15], %[temp15], 3 \n\t" + "sra %[temp12], %[temp12], 3 \n\t" + "addu %[temp9], %[temp14], %[temp3] \n\t" + "subu %[temp14], %[temp14], %[temp3] \n\t" + "addu %[temp3], %[temp0], %[temp13] \n\t" + "subu %[temp0], %[temp0], %[temp13] \n\t" + "sra %[temp9], %[temp9], 3 \n\t" + "sra %[temp14], %[temp14], 3 \n\t" + "sra %[temp3], %[temp3], 3 \n\t" + "sra %[temp0], %[temp0], 3 \n\t" + "addu %[temp13], %[temp2], %[temp7] \n\t" + "subu %[temp2], %[temp2], %[temp7] \n\t" + "addu %[temp7], %[temp4], %[temp1] \n\t" + "subu %[temp4], %[temp4], %[temp1] \n\t" + "sra %[temp13], %[temp13], 3 \n\t" + "sra %[temp2], %[temp2], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "sra %[temp4], %[temp4], 3 \n\t" + "addiu %[temp6], $zero, 255 \n\t" + "lbu %[temp1], 0+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "sra %[temp5], %[temp1], 8 \n\t" + "sra %[temp18], %[temp1], 31 \n\t" + "beqz %[temp5], 1f \n\t" + "xor %[temp1], %[temp1], %[temp1] \n\t" + "movz %[temp1], %[temp6], %[temp18] \n\t" + "1: \n\t" + "lbu %[temp18], 1+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp1], 0+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp18], %[temp18], %[temp11] \n\t" + "sra %[temp11], %[temp18], 8 \n\t" + "sra %[temp1], %[temp18], 31 \n\t" + "beqz %[temp11], 2f \n\t" + "xor %[temp18], %[temp18], %[temp18] \n\t" + "movz %[temp18], %[temp6], %[temp1] \n\t" + "2: \n\t" + "lbu %[temp1], 2+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp18], 1+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp1], %[temp1], %[temp8] \n\t" + "sra %[temp8], %[temp1], 8 \n\t" + "sra %[temp18], %[temp1], 31 \n\t" + "beqz %[temp8], 3f \n\t" + "xor %[temp1], %[temp1], %[temp1] \n\t" + "movz %[temp1], %[temp6], %[temp18] \n\t" + "3: \n\t" + "lbu %[temp18], 3+0*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp1], 2+0*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp18], %[temp18], %[temp16] \n\t" + "sra %[temp16], %[temp18], 8 \n\t" + "sra %[temp1], %[temp18], 31 \n\t" + "beqz %[temp16], 4f \n\t" + "xor %[temp18], %[temp18], %[temp18] \n\t" + "movz %[temp18], %[temp6], %[temp1] \n\t" + "4: \n\t" + "sb %[temp18], 3+0*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+1*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp17] \n\t" + "addu %[temp8], %[temp8], %[temp15] \n\t" + "addu %[temp11], %[temp11], %[temp12] \n\t" + "addu %[temp16], %[temp16], %[temp10] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "beqz %[temp18], 5f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "5: \n\t" + "sra %[temp18], %[temp8], 8 \n\t" + "sra %[temp1], %[temp8], 31 \n\t" + "beqz %[temp18], 6f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp1] \n\t" + "6: \n\t" + "sra %[temp18], %[temp11], 8 \n\t" + "sra %[temp1], %[temp11], 31 \n\t" + "sra %[temp17], %[temp16], 8 \n\t" + "sra %[temp15], %[temp16], 31 \n\t" + "beqz %[temp18], 7f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp1] \n\t" + "7: \n\t" + "beqz %[temp17], 8f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp15] \n\t" + "8: \n\t" + "sb %[temp5], 0+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+1*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+1*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+2*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp9] \n\t" + "addu %[temp8], %[temp8], %[temp3] \n\t" + "addu %[temp11], %[temp11], %[temp0] \n\t" + "addu %[temp16], %[temp16], %[temp14] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "sra %[temp17], %[temp8], 8 \n\t" + "sra %[temp15], %[temp8], 31 \n\t" + "sra %[temp12], %[temp11], 8 \n\t" + "sra %[temp10], %[temp11], 31 \n\t" + "sra %[temp9], %[temp16], 8 \n\t" + "sra %[temp3], %[temp16], 31 \n\t" + "beqz %[temp18], 9f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "9: \n\t" + "beqz %[temp17], 10f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp15] \n\t" + "10: \n\t" + "beqz %[temp12], 11f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp10] \n\t" + "11: \n\t" + "beqz %[temp9], 12f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp3] \n\t" + "12: \n\t" + "sb %[temp5], 0+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+2*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+2*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp5], 0+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp8], 1+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp11], 2+3*" XSTR(BPS) "(%[dst]) \n\t" + "lbu %[temp16], 3+3*" XSTR(BPS) "(%[dst]) \n\t" + "addu %[temp5], %[temp5], %[temp13] \n\t" + "addu %[temp8], %[temp8], %[temp7] \n\t" + "addu %[temp11], %[temp11], %[temp4] \n\t" + "addu %[temp16], %[temp16], %[temp2] \n\t" + "sra %[temp18], %[temp5], 8 \n\t" + "sra %[temp1], %[temp5], 31 \n\t" + "sra %[temp17], %[temp8], 8 \n\t" + "sra %[temp15], %[temp8], 31 \n\t" + "sra %[temp12], %[temp11], 8 \n\t" + "sra %[temp10], %[temp11], 31 \n\t" + "sra %[temp9], %[temp16], 8 \n\t" + "sra %[temp3], %[temp16], 31 \n\t" + "beqz %[temp18], 13f \n\t" + "xor %[temp5], %[temp5], %[temp5] \n\t" + "movz %[temp5], %[temp6], %[temp1] \n\t" + "13: \n\t" + "beqz %[temp17], 14f \n\t" + "xor %[temp8], %[temp8], %[temp8] \n\t" + "movz %[temp8], %[temp6], %[temp15] \n\t" + "14: \n\t" + "beqz %[temp12], 15f \n\t" + "xor %[temp11], %[temp11], %[temp11] \n\t" + "movz %[temp11], %[temp6], %[temp10] \n\t" + "15: \n\t" + "beqz %[temp9], 16f \n\t" + "xor %[temp16], %[temp16], %[temp16] \n\t" + "movz %[temp16], %[temp6], %[temp3] \n\t" + "16: \n\t" + "sb %[temp5], 0+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp8], 1+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp11], 2+3*" XSTR(BPS) "(%[dst]) \n\t" + "sb %[temp16], 3+3*" XSTR(BPS) "(%[dst]) \n\t" + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18), [temp19]"=&r"(temp19) + : [in]"r"(p_in), [kC1]"r"(kC1), [kC2]"r"(kC2), [dst]"r"(dst) + : "memory", "hi", "lo" + ); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMIPS32(void) { + VP8InitClipTables(); + + VP8Transform = TransformTwo; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8DspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips_dsp_r2.c new file mode 100644 index 0000000000..0ba706a2ef --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_mips_dsp_r2.c @@ -0,0 +1,990 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of dsp functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/mips_macro.h" + +static const int kC1 = WEBP_TRANSFORM_AC3_C1; +static const int kC2 = WEBP_TRANSFORM_AC3_C2; + +static void TransformDC(const int16_t* in, uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9, temp10; + + __asm__ volatile ( + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + "lh %[temp5], 0(%[in]) \n\t" + "addiu %[temp5], %[temp5], 4 \n\t" + "ins %[temp5], %[temp5], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 3 \n\t" + CONVERT_2_BYTES_TO_HALF(temp6, temp7, temp8, temp9, temp10, temp1, temp2, + temp3, temp1, temp2, temp3, temp4) + STORE_SAT_SUM_X2(temp6, temp7, temp8, temp9, temp10, temp1, temp2, temp3, + temp5, temp5, temp5, temp5, temp5, temp5, temp5, temp5, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_10() + : [in]"r"(in), [dst]"r"(dst) + : "memory" + ); +} + +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + int c4 = WEBP_TRANSFORM_AC3_MUL2(in[4]); + const int d4 = WEBP_TRANSFORM_AC3_MUL1(in[4]); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ins %[c4], %[d4], 16, 16 \n\t" + "replv.ph %[temp1], %[a] \n\t" + "replv.ph %[temp4], %[d1] \n\t" + ADD_SUB_HALVES(temp2, temp3, temp1, c4) + "replv.ph %[temp5], %[c1] \n\t" + SHIFT_R_SUM_X2(temp1, temp6, temp7, temp8, temp2, temp9, temp10, temp4, + temp2, temp2, temp3, temp3, temp4, temp5, temp4, temp5) + LOAD_WITH_OFFSET_X4(temp3, temp5, temp11, temp12, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp13, temp14, temp3, temp15, temp5, temp16, + temp11, temp17, temp3, temp5, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp12, temp18, temp7, temp6, temp1, temp8, temp2, + temp4, temp7, temp6, temp10, temp9) + STORE_SAT_SUM_X2(temp13, temp14, temp3, temp15, temp5, temp16, temp11, + temp17, temp12, temp18, temp1, temp8, temp2, temp4, + temp7, temp6, dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18(), + [c4]"+&r"(c4) + : [dst]"r"(dst), [a]"r"(a), [d1]"r"(d1), [d4]"r"(d4), [c1]"r"(c1) + : "memory" + ); +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ulw %[temp1], 0(%[in]) \n\t" + "ulw %[temp2], 16(%[in]) \n\t" + LOAD_IN_X2(temp5, temp6, 24, 26) + ADD_SUB_HALVES(temp3, temp4, temp1, temp2) + LOAD_IN_X2(temp1, temp2, 8, 10) + MUL_SHIFT_SUM(temp7, temp8, temp9, temp10, temp11, temp12, temp13, temp14, + temp10, temp8, temp9, temp7, temp1, temp2, temp5, temp6, + temp13, temp11, temp14, temp12) + INSERT_HALF_X2(temp8, temp7, temp10, temp9) + "ulw %[temp17], 4(%[in]) \n\t" + "ulw %[temp18], 20(%[in]) \n\t" + ADD_SUB_HALVES(temp1, temp2, temp3, temp8) + ADD_SUB_HALVES(temp5, temp6, temp4, temp7) + ADD_SUB_HALVES(temp7, temp8, temp17, temp18) + LOAD_IN_X2(temp17, temp18, 12, 14) + LOAD_IN_X2(temp9, temp10, 28, 30) + MUL_SHIFT_SUM(temp11, temp12, temp13, temp14, temp15, temp16, temp4, temp17, + temp12, temp14, temp11, temp13, temp17, temp18, temp9, temp10, + temp15, temp4, temp16, temp17) + INSERT_HALF_X2(temp11, temp12, temp13, temp14) + ADD_SUB_HALVES(temp17, temp8, temp8, temp11) + ADD_SUB_HALVES(temp3, temp4, temp7, temp12) + + // horizontal + SRA_16(temp9, temp10, temp11, temp12, temp1, temp2, temp5, temp6) + INSERT_HALF_X2(temp1, temp6, temp5, temp2) + SRA_16(temp13, temp14, temp15, temp16, temp3, temp4, temp17, temp8) + "repl.ph %[temp2], 0x4 \n\t" + INSERT_HALF_X2(temp3, temp8, temp17, temp4) + "addq.ph %[temp1], %[temp1], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp2] \n\t" + ADD_SUB_HALVES(temp2, temp4, temp1, temp3) + ADD_SUB_HALVES(temp5, temp7, temp6, temp8) + MUL_SHIFT_SUM(temp1, temp3, temp6, temp8, temp9, temp13, temp17, temp18, + temp3, temp13, temp1, temp9, temp9, temp13, temp11, temp15, + temp6, temp17, temp8, temp18) + MUL_SHIFT_SUM(temp6, temp8, temp18, temp17, temp11, temp15, temp12, temp16, + temp8, temp15, temp6, temp11, temp12, temp16, temp10, temp14, + temp18, temp12, temp17, temp16) + INSERT_HALF_X2(temp1, temp3, temp9, temp13) + INSERT_HALF_X2(temp6, temp8, temp11, temp15) + SHIFT_R_SUM_X2(temp9, temp10, temp11, temp12, temp13, temp14, temp15, + temp16, temp2, temp4, temp5, temp7, temp3, temp1, temp8, + temp6) + PACK_2_HALVES_TO_WORD(temp1, temp2, temp3, temp4, temp9, temp12, temp13, + temp16, temp11, temp10, temp15, temp14) + LOAD_WITH_OFFSET_X4(temp10, temp11, temp14, temp15, dst, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp17, temp18, temp10, + temp11, temp10, temp11, temp14, temp15) + STORE_SAT_SUM_X2(temp5, temp6, temp7, temp8, temp17, temp18, temp10, temp11, + temp9, temp12, temp1, temp2, temp13, temp16, temp3, temp4, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18() + : [dst]"r"(dst), [in]"r"(in), [kC1]"r"(kC1), [kC2]"r"(kC2) + : "memory", "hi", "lo" + ); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +static WEBP_INLINE void FilterLoop26(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15; + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "1: \n\t" + "negu %[temp1], %[hstride] \n\t" + "addiu %[size], %[size], -1 \n\t" + "sll %[temp2], %[hstride], 1 \n\t" + "sll %[temp3], %[temp1], 1 \n\t" + "addu %[temp4], %[temp2], %[hstride] \n\t" + "addu %[temp5], %[temp3], %[temp1] \n\t" + "lbu %[temp7], 0(%[p]) \n\t" + "sll %[temp6], %[temp3], 1 \n\t" + "lbux %[temp8], %[temp5](%[p]) \n\t" + "lbux %[temp9], %[temp3](%[p]) \n\t" + "lbux %[temp10], %[temp1](%[p]) \n\t" + "lbux %[temp11], %[temp6](%[p]) \n\t" + "lbux %[temp12], %[hstride](%[p]) \n\t" + "lbux %[temp13], %[temp2](%[p]) \n\t" + "lbux %[temp14], %[temp4](%[p]) \n\t" + "subu %[temp1], %[temp10], %[temp7] \n\t" + "subu %[temp2], %[temp9], %[temp12] \n\t" + "absq_s.w %[temp3], %[temp1] \n\t" + "absq_s.w %[temp4], %[temp2] \n\t" + "negu %[temp1], %[temp1] \n\t" + "sll %[temp3], %[temp3], 2 \n\t" + "addu %[temp15], %[temp3], %[temp4] \n\t" + "subu %[temp3], %[temp15], %[thresh2] \n\t" + "sll %[temp6], %[temp1], 1 \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp4], %[temp11], %[temp8] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "shll_s.w %[temp2], %[temp2], 24 \n\t" + "subu %[temp4], %[temp4], %[ithresh] \n\t" + "bgtz %[temp4], 3f \n\t" + " subu %[temp3], %[temp8], %[temp9] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp5], %[temp9], %[temp10] \n\t" + "absq_s.w %[temp3], %[temp5] \n\t" + "absq_s.w %[temp5], %[temp5] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp3], %[temp14], %[temp13] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "slt %[temp5], %[hev_thresh], %[temp5] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp3], %[temp13], %[temp12] \n\t" + "absq_s.w %[temp3], %[temp3] \n\t" + "sra %[temp4], %[temp2], 24 \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " subu %[temp15], %[temp12], %[temp7] \n\t" + "absq_s.w %[temp3], %[temp15] \n\t" + "absq_s.w %[temp15], %[temp15] \n\t" + "subu %[temp3], %[temp3], %[ithresh] \n\t" + "bgtz %[temp3], 3f \n\t" + " slt %[temp15], %[hev_thresh], %[temp15] \n\t" + "addu %[temp3], %[temp6], %[temp1] \n\t" + "or %[temp2], %[temp5], %[temp15] \n\t" + "addu %[temp5], %[temp4], %[temp3] \n\t" + "beqz %[temp2], 4f \n\t" + " shra_r.w %[temp1], %[temp5], 3 \n\t" + "addiu %[temp2], %[temp5], 3 \n\t" + "sra %[temp2], %[temp2], 3 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "subu %[temp3], %[p], %[hstride] \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "subu %[temp1], %[temp7], %[temp1] \n\t" + "addu %[temp2], %[temp10], %[temp2] \n\t" + "lbux %[temp2], %[temp2](%[VP8kclip1]) \n\t" + "lbux %[temp1], %[temp1](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[temp3]) \n\t" + "j 3f \n\t" + " sb %[temp1], 0(%[p]) \n\t" + "4: \n\t" + "shll_s.w %[temp5], %[temp5], 24 \n\t" + "subu %[temp14], %[p], %[hstride] \n\t" + "subu %[temp11], %[temp14], %[hstride] \n\t" + "sra %[temp6], %[temp5], 24 \n\t" + "sll %[temp1], %[temp6], 3 \n\t" + "subu %[temp15], %[temp11], %[hstride] \n\t" + "addu %[temp2], %[temp6], %[temp1] \n\t" + "sll %[temp3], %[temp2], 1 \n\t" + "addu %[temp4], %[temp3], %[temp2] \n\t" + "addiu %[temp2], %[temp2], 63 \n\t" + "addiu %[temp3], %[temp3], 63 \n\t" + "addiu %[temp4], %[temp4], 63 \n\t" + "sra %[temp2], %[temp2], 7 \n\t" + "sra %[temp3], %[temp3], 7 \n\t" + "sra %[temp4], %[temp4], 7 \n\t" + "addu %[temp1], %[temp8], %[temp2] \n\t" + "addu %[temp5], %[temp9], %[temp3] \n\t" + "addu %[temp6], %[temp10], %[temp4] \n\t" + "subu %[temp8], %[temp7], %[temp4] \n\t" + "subu %[temp7], %[temp12], %[temp3] \n\t" + "addu %[temp10], %[p], %[hstride] \n\t" + "subu %[temp9], %[temp13], %[temp2] \n\t" + "addu %[temp12], %[temp10], %[hstride] \n\t" + "lbux %[temp2], %[temp1](%[VP8kclip1]) \n\t" + "lbux %[temp3], %[temp5](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp6](%[VP8kclip1]) \n\t" + "lbux %[temp5], %[temp8](%[VP8kclip1]) \n\t" + "lbux %[temp6], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp8], %[temp9](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[temp15]) \n\t" + "sb %[temp3], 0(%[temp11]) \n\t" + "sb %[temp4], 0(%[temp14]) \n\t" + "sb %[temp5], 0(%[p]) \n\t" + "sb %[temp6], 0(%[temp10]) \n\t" + "sb %[temp8], 0(%[temp12]) \n\t" + "3: \n\t" + "bgtz %[size], 1b \n\t" + " addu %[p], %[p], %[vstride] \n\t" + ".set pop \n\t" + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2),[temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7),[temp8]"=&r"(temp8),[temp9]"=&r"(temp9), + [temp10]"=&r"(temp10),[temp11]"=&r"(temp11),[temp12]"=&r"(temp12), + [temp13]"=&r"(temp13),[temp14]"=&r"(temp14),[temp15]"=&r"(temp15), + [size]"+&r"(size), [p]"+&r"(p) + : [hstride]"r"(hstride), [thresh2]"r"(thresh2), + [ithresh]"r"(ithresh),[vstride]"r"(vstride), [hev_thresh]"r"(hev_thresh), + [VP8kclip1]"r"(VP8kclip1) + : "memory" + ); +} + +static WEBP_INLINE void FilterLoop24(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, int hev_thresh) { + int p0, q0, p1, q1, p2, q2, p3, q3; + int step1, step2, temp1, temp2, temp3, temp4; + uint8_t* pTemp0; + uint8_t* pTemp1; + const int thresh2 = 2 * thresh + 1; + + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "bltz %[size], 3f \n\t" + " nop \n\t" + "2: \n\t" + "negu %[step1], %[hstride] \n\t" + "lbu %[q0], 0(%[p]) \n\t" + "lbux %[p0], %[step1](%[p]) \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "lbux %[q1], %[hstride](%[p]) \n\t" + "subu %[temp1], %[p0], %[q0] \n\t" + "lbux %[p1], %[step1](%[p]) \n\t" + "addu %[step2], %[hstride], %[hstride] \n\t" + "absq_s.w %[temp2], %[temp1] \n\t" + "subu %[temp3], %[p1], %[q1] \n\t" + "absq_s.w %[temp4], %[temp3] \n\t" + "sll %[temp2], %[temp2], 2 \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "subu %[temp4], %[temp2], %[thresh2] \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "bgtz %[temp4], 0f \n\t" + " lbux %[p2], %[step1](%[p]) \n\t" + "subu %[step1], %[step1], %[hstride] \n\t" + "lbux %[q2], %[step2](%[p]) \n\t" + "lbux %[p3], %[step1](%[p]) \n\t" + "subu %[temp4], %[p2], %[p1] \n\t" + "addu %[step2], %[step2], %[hstride] \n\t" + "subu %[temp2], %[p3], %[p2] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "absq_s.w %[temp2], %[temp2] \n\t" + "lbux %[q3], %[step2](%[p]) \n\t" + "subu %[temp4], %[temp4], %[ithresh] \n\t" + "negu %[temp1], %[temp1] \n\t" + "bgtz %[temp4], 0f \n\t" + " subu %[temp2], %[temp2], %[ithresh] \n\t" + "subu %[p3], %[p1], %[p0] \n\t" + "bgtz %[temp2], 0f \n\t" + " absq_s.w %[p3], %[p3] \n\t" + "subu %[temp4], %[q3], %[q2] \n\t" + "subu %[pTemp0], %[p], %[hstride] \n\t" + "absq_s.w %[temp4], %[temp4] \n\t" + "subu %[temp2], %[p3], %[ithresh] \n\t" + "sll %[step1], %[temp1], 1 \n\t" + "bgtz %[temp2], 0f \n\t" + " subu %[temp4], %[temp4], %[ithresh] \n\t" + "subu %[temp2], %[q2], %[q1] \n\t" + "bgtz %[temp4], 0f \n\t" + " absq_s.w %[temp2], %[temp2] \n\t" + "subu %[q3], %[q1], %[q0] \n\t" + "absq_s.w %[q3], %[q3] \n\t" + "subu %[temp2], %[temp2], %[ithresh] \n\t" + "addu %[temp1], %[temp1], %[step1] \n\t" + "bgtz %[temp2], 0f \n\t" + " subu %[temp4], %[q3], %[ithresh] \n\t" + "slt %[p3], %[hev_thresh], %[p3] \n\t" + "bgtz %[temp4], 0f \n\t" + " slt %[q3], %[hev_thresh], %[q3] \n\t" + "or %[q3], %[q3], %[p3] \n\t" + "bgtz %[q3], 1f \n\t" + " shra_r.w %[temp2], %[temp1], 3 \n\t" + "addiu %[temp1], %[temp1], 3 \n\t" + "sra %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "addu %[pTemp1], %[p], %[hstride] \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "addiu %[step1], %[temp2], 1 \n\t" + "sra %[step1], %[step1], 1 \n\t" + "addu %[p0], %[p0], %[temp1] \n\t" + "addu %[p1], %[p1], %[step1] \n\t" + "subu %[q0], %[q0], %[temp2] \n\t" + "subu %[q1], %[q1], %[step1] \n\t" + "lbux %[temp2], %[p0](%[VP8kclip1]) \n\t" + "lbux %[temp3], %[q0](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[q1](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[pTemp0]) \n\t" + "lbux %[temp1], %[p1](%[VP8kclip1]) \n\t" + "subu %[pTemp0], %[pTemp0], %[hstride] \n\t" + "sb %[temp3], 0(%[p]) \n\t" + "sb %[temp4], 0(%[pTemp1]) \n\t" + "j 0f \n\t" + " sb %[temp1], 0(%[pTemp0]) \n\t" + "1: \n\t" + "shll_s.w %[temp3], %[temp3], 24 \n\t" + "sra %[temp3], %[temp3], 24 \n\t" + "addu %[temp1], %[temp1], %[temp3] \n\t" + "shra_r.w %[temp2], %[temp1], 3 \n\t" + "addiu %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 3 \n\t" + "shll_s.w %[temp1], %[temp1], 27 \n\t" + "sra %[temp2], %[temp2], 27 \n\t" + "sra %[temp1], %[temp1], 27 \n\t" + "addu %[p0], %[p0], %[temp1] \n\t" + "subu %[q0], %[q0], %[temp2] \n\t" + "lbux %[temp1], %[p0](%[VP8kclip1]) \n\t" + "lbux %[temp2], %[q0](%[VP8kclip1]) \n\t" + "sb %[temp2], 0(%[p]) \n\t" + "sb %[temp1], 0(%[pTemp0]) \n\t" + "0: \n\t" + "subu %[size], %[size], 1 \n\t" + "bgtz %[size], 2b \n\t" + " addu %[p], %[p], %[vstride] \n\t" + "3: \n\t" + ".set pop \n\t" + : [p0]"=&r"(p0), [q0]"=&r"(q0), [p1]"=&r"(p1), [q1]"=&r"(q1), + [p2]"=&r"(p2), [q2]"=&r"(q2), [p3]"=&r"(p3), [q3]"=&r"(q3), + [step2]"=&r"(step2), [step1]"=&r"(step1), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [pTemp0]"=&r"(pTemp0), [pTemp1]"=&r"(pTemp1), [p]"+&r"(p), + [size]"+&r"(size) + : [vstride]"r"(vstride), [ithresh]"r"(ithresh), + [hev_thresh]"r"(hev_thresh), [hstride]"r"(hstride), + [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +// on macroblock edges +static void VFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} + +static void HFilter16i(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} + +static void VFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} + +static void HFilter8i(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop24(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + uint8_t* p1 = p - stride; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "li %[i], 16 \n\t" + "0: \n\t" + "negu %[temp4], %[stride] \n\t" + "sll %[temp5], %[temp4], 1 \n\t" + "lbu %[temp2], 0(%[p]) \n\t" + "lbux %[temp3], %[stride](%[p]) \n\t" + "lbux %[temp1], %[temp4](%[p]) \n\t" + "lbux %[temp0], %[temp5](%[p]) \n\t" + "subu %[temp7], %[temp1], %[temp2] \n\t" + "subu %[temp6], %[temp0], %[temp3] \n\t" + "absq_s.w %[temp4], %[temp7] \n\t" + "absq_s.w %[temp5], %[temp6] \n\t" + "sll %[temp4], %[temp4], 2 \n\t" + "subu %[temp5], %[temp5], %[thresh2] \n\t" + "addu %[temp5], %[temp4], %[temp5] \n\t" + "negu %[temp8], %[temp7] \n\t" + "bgtz %[temp5], 1f \n\t" + " addiu %[i], %[i], -1 \n\t" + "sll %[temp4], %[temp8], 1 \n\t" + "shll_s.w %[temp5], %[temp6], 24 \n\t" + "addu %[temp3], %[temp4], %[temp8] \n\t" + "sra %[temp5], %[temp5], 24 \n\t" + "addu %[temp3], %[temp3], %[temp5] \n\t" + "addiu %[temp7], %[temp3], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "shra_r.w %[temp8], %[temp3], 3 \n\t" + "shll_s.w %[temp0], %[temp7], 27 \n\t" + "shll_s.w %[temp4], %[temp8], 27 \n\t" + "sra %[temp0], %[temp0], 27 \n\t" + "sra %[temp4], %[temp4], 27 \n\t" + "addu %[temp7], %[temp1], %[temp0] \n\t" + "subu %[temp2], %[temp2], %[temp4] \n\t" + "lbux %[temp3], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp2](%[VP8kclip1]) \n\t" + "sb %[temp3], 0(%[p1]) \n\t" + "sb %[temp4], 0(%[p]) \n\t" + "1: \n\t" + "addiu %[p1], %[p1], 1 \n\t" + "bgtz %[i], 0b \n\t" + " addiu %[p], %[p], 1 \n\t" + " .set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [p]"+&r"(p), [i]"=&r"(i), [p1]"+&r"(p1) + : [stride]"r"(stride), [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +// TEMP0 = SRC[A + A1 * BPS] +// TEMP1 = SRC[B + B1 * BPS] +// TEMP2 = SRC[C + C1 * BPS] +// TEMP3 = SRC[D + D1 * BPS] +#define LOAD_4_BYTES(TEMP0, TEMP1, TEMP2, TEMP3, \ + A, A1, B, B1, C, C1, D, D1, SRC) \ + "lbu %[" #TEMP0 "], " #A "+" #A1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP1 "], " #B "+" #B1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP2 "], " #C "+" #C1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "lbu %[" #TEMP3 "], " #D "+" #D1 "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + +static void SimpleHFilter16(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "li %[i], 16 \n\t" + "0: \n\t" + LOAD_4_BYTES(temp0, temp1, temp2, temp3, -2, 0, -1, 0, 0, 0, 1, 0, p) + "subu %[temp7], %[temp1], %[temp2] \n\t" + "subu %[temp6], %[temp0], %[temp3] \n\t" + "absq_s.w %[temp4], %[temp7] \n\t" + "absq_s.w %[temp5], %[temp6] \n\t" + "sll %[temp4], %[temp4], 2 \n\t" + "addu %[temp5], %[temp4], %[temp5] \n\t" + "subu %[temp5], %[temp5], %[thresh2] \n\t" + "negu %[temp8], %[temp7] \n\t" + "bgtz %[temp5], 1f \n\t" + " addiu %[i], %[i], -1 \n\t" + "sll %[temp4], %[temp8], 1 \n\t" + "shll_s.w %[temp5], %[temp6], 24 \n\t" + "addu %[temp3], %[temp4], %[temp8] \n\t" + "sra %[temp5], %[temp5], 24 \n\t" + "addu %[temp3], %[temp3], %[temp5] \n\t" + "addiu %[temp7], %[temp3], 3 \n\t" + "sra %[temp7], %[temp7], 3 \n\t" + "shra_r.w %[temp8], %[temp3], 3 \n\t" + "shll_s.w %[temp0], %[temp7], 27 \n\t" + "shll_s.w %[temp4], %[temp8], 27 \n\t" + "sra %[temp0], %[temp0], 27 \n\t" + "sra %[temp4], %[temp4], 27 \n\t" + "addu %[temp7], %[temp1], %[temp0] \n\t" + "subu %[temp2], %[temp2], %[temp4] \n\t" + "lbux %[temp3], %[temp7](%[VP8kclip1]) \n\t" + "lbux %[temp4], %[temp2](%[VP8kclip1]) \n\t" + "sb %[temp3], -1(%[p]) \n\t" + "sb %[temp4], 0(%[p]) \n\t" + "1: \n\t" + "bgtz %[i], 0b \n\t" + " addu %[p], %[p], %[stride] \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [p]"+&r"(p), [i]"=&r"(i) + : [stride]"r"(stride), [VP8kclip1]"r"(VP8kclip1), [thresh2]"r"(thresh2) + : "memory" + ); +} + +static void SimpleVFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16(p, stride, thresh); + } +} + +static void SimpleHFilter16i(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16(p, stride, thresh); + } +} + +// DST[A * BPS] = TEMP0 +// DST[B + C * BPS] = TEMP1 +#define STORE_8_BYTES(TEMP0, TEMP1, A, B, C, DST) \ + "usw %[" #TEMP0 "], " #A "*" XSTR(BPS) "(%[" #DST "]) \n\t" \ + "usw %[" #TEMP1 "], " #B "+" #C "*" XSTR(BPS) "(%[" #DST "]) \n\t" + +static void VE4(uint8_t* dst) { // vertical + const uint8_t* top = dst - BPS; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile ( + "ulw %[temp0], -1(%[top]) \n\t" + "ulh %[temp1], 3(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp4], %[temp3] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp2], %[temp5], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp4] \n\t" + "addq.ph %[temp2], %[temp2], %[temp3] \n\t" + "addq.ph %[temp6], %[temp6], %[temp3] \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precr.qb.ph %[temp4], %[temp6], %[temp2] \n\t" + STORE_8_BYTES(temp4, temp4, 0, 0, 1, dst) + STORE_8_BYTES(temp4, temp4, 2, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void DC4(uint8_t* dst) { // DC + int temp0, temp1, temp2, temp3, temp4; + __asm__ volatile ( + "ulw %[temp0], -1*" XSTR(BPS) "(%[dst]) \n\t" + LOAD_4_BYTES(temp1, temp2, temp3, temp4, -1, 0, -1, 1, -1, 2, -1, 3, dst) + "ins %[temp1], %[temp2], 8, 8 \n\t" + "ins %[temp1], %[temp3], 16, 8 \n\t" + "ins %[temp1], %[temp4], 24, 8 \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 0, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4) + : [dst]"r"(dst) + : "memory" + ); +} + +static void RD4(uint8_t* dst) { // Down-right + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8; + __asm__ volatile ( + LOAD_4_BYTES(temp0, temp1, temp2, temp3, -1, 0, -1, 1, -1, 2, -1, 3, dst) + "ulw %[temp7], -1-" XSTR(BPS) "(%[dst]) \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "preceu.ph.qbr %[temp5], %[temp7] \n\t" + "ins %[temp2], %[temp1], 16, 16 \n\t" + "preceu.ph.qbl %[temp4], %[temp7] \n\t" + "ins %[temp3], %[temp2], 16, 16 \n\t" + "shll.ph %[temp2], %[temp2], 1 \n\t" + "addq.ph %[temp3], %[temp3], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp5], %[temp1] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp1], %[temp1], %[temp5] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp1], %[temp1], %[temp6] \n\t" + "packrl.ph %[temp0], %[temp4], %[temp5] \n\t" + "addq.ph %[temp8], %[temp5], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shll.ph %[temp0], %[temp0], 1 \n\t" + "shra_r.ph %[temp1], %[temp1], 2 \n\t" + "addq.ph %[temp8], %[temp0], %[temp8] \n\t" + "lbu %[temp5], 3-" XSTR(BPS) "(%[dst]) \n\t" + "precrq.ph.w %[temp7], %[temp7], %[temp7] \n\t" + "shra_r.ph %[temp8], %[temp8], 2 \n\t" + "ins %[temp7], %[temp5], 0, 8 \n\t" + "precr.qb.ph %[temp2], %[temp1], %[temp3] \n\t" + "raddu.w.qb %[temp4], %[temp7] \n\t" + "precr.qb.ph %[temp6], %[temp8], %[temp1] \n\t" + "shra_r.w %[temp4], %[temp4], 2 \n\t" + STORE_8_BYTES(temp2, temp6, 3, 0, 1, dst) + "prepend %[temp2], %[temp8], 8 \n\t" + "prepend %[temp6], %[temp4], 8 \n\t" + STORE_8_BYTES(temp2, temp6, 2, 0, 0, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dst]"r"(dst) + : "memory" + ); +} + +// TEMP0 = SRC[A * BPS] +// TEMP1 = SRC[B + C * BPS] +#define LOAD_8_BYTES(TEMP0, TEMP1, A, B, C, SRC) \ + "ulw %[" #TEMP0 "], " #A "*" XSTR(BPS) "(%[" #SRC "]) \n\t" \ + "ulw %[" #TEMP1 "], " #B "+" #C "*" XSTR(BPS) "(%[" #SRC "]) \n\t" + +static void LD4(uint8_t* dst) { // Down-Left + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp3], %[temp4], %[temp7] \n\t" + "addq.ph %[temp0], %[temp5], %[temp8] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp0], %[temp0], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "srl %[temp1], %[temp1], 24 \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "raddu.w.qb %[temp5], %[temp5] \n\t" + "precr.qb.ph %[temp9], %[temp3], %[temp9] \n\t" + "precr.qb.ph %[temp3], %[temp0], %[temp3] \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "shra_r.w %[temp1], %[temp1], 2 \n\t" + STORE_8_BYTES(temp9, temp3, 0, 0, 2, dst) + "prepend %[temp9], %[temp0], 8 \n\t" + "prepend %[temp3], %[temp1], 8 \n\t" + STORE_8_BYTES(temp9, temp3, 1, 0, 3, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [dst]"r"(dst) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void DC8uv(uint8_t* dst) { // DC + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + LOAD_4_BYTES(temp2, temp3, temp4, temp5, -1, 0, -1, 1, -1, 2, -1, 3, dst) + LOAD_4_BYTES(temp6, temp7, temp8, temp9, -1, 4, -1, 5, -1, 6, -1, 7, dst) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[temp4], %[temp4], %[temp5] \n\t" + "addu %[temp6], %[temp6], %[temp7] \n\t" + "addu %[temp8], %[temp8], %[temp9] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "addu %[temp6], %[temp6], %[temp8] \n\t" + "addu %[temp0], %[temp0], %[temp2] \n\t" + "addu %[temp0], %[temp0], %[temp6] \n\t" + "shra_r.w %[temp0], %[temp0], 4 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [dst]"r"(dst) + : "memory" + ); +} + +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples + int temp0, temp1; + __asm__ volatile ( + LOAD_8_BYTES(temp0, temp1, -1, 4, -1, dst) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1) + : [dst]"r"(dst) + : "memory" + ); +} + +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8; + __asm__ volatile ( + LOAD_4_BYTES(temp2, temp3, temp4, temp5, -1, 0, -1, 1, -1, 2, -1, 3, dst) + LOAD_4_BYTES(temp6, temp7, temp8, temp1, -1, 4, -1, 5, -1, 6, -1, 7, dst) + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[temp4], %[temp4], %[temp5] \n\t" + "addu %[temp6], %[temp6], %[temp7] \n\t" + "addu %[temp8], %[temp8], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp4] \n\t" + "addu %[temp6], %[temp6], %[temp8] \n\t" + "addu %[temp0], %[temp6], %[temp2] \n\t" + "shra_r.w %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + STORE_8_BYTES(temp0, temp0, 0, 4, 0, dst) + STORE_8_BYTES(temp0, temp0, 1, 4, 1, dst) + STORE_8_BYTES(temp0, temp0, 2, 4, 2, dst) + STORE_8_BYTES(temp0, temp0, 3, 4, 3, dst) + STORE_8_BYTES(temp0, temp0, 4, 4, 4, dst) + STORE_8_BYTES(temp0, temp0, 5, 4, 5, dst) + STORE_8_BYTES(temp0, temp0, 6, 4, 6, dst) + STORE_8_BYTES(temp0, temp0, 7, 4, 7, dst) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dst]"r"(dst) + : "memory" + ); +} + +#undef LOAD_8_BYTES +#undef STORE_8_BYTES +#undef LOAD_4_BYTES + +#define CLIPPING(SIZE) \ + "preceu.ph.qbl %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp0], %[temp0] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "preceu.ph.qbl %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbr %[temp1], %[temp1] \n\t" \ +".endif \n\t" \ + "addu.ph %[temp2], %[temp2], %[dst_1] \n\t" \ + "addu.ph %[temp0], %[temp0], %[dst_1] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "addu.ph %[temp3], %[temp3], %[dst_1] \n\t" \ + "addu.ph %[temp1], %[temp1], %[dst_1] \n\t" \ +".endif \n\t" \ + "shll_s.ph %[temp2], %[temp2], 7 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 7 \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 7 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 7 \n\t" \ +".endif \n\t" \ + "precrqu_s.qb.ph %[temp0], %[temp2], %[temp0] \n\t" \ +".if " #SIZE " == 8 \n\t" \ + "precrqu_s.qb.ph %[temp1], %[temp3], %[temp1] \n\t" \ +".endif \n\t" + + +#define CLIP_8B_TO_DST(DST, TOP, SIZE) do { \ + int dst_1 = ((int)(DST)[-1] << 16) + (DST)[-1]; \ + int temp0, temp1, temp2, temp3; \ + __asm__ volatile ( \ + ".if " #SIZE " < 8 \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "subu.ph %[dst_1], %[dst_1], %[top_1] \n\t" \ + CLIPPING(4) \ + "usw %[temp0], 0(%[dst]) \n\t" \ + ".else \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "ulw %[temp1], 4(%[top]) \n\t" \ + "subu.ph %[dst_1], %[dst_1], %[top_1] \n\t" \ + CLIPPING(8) \ + "usw %[temp0], 0(%[dst]) \n\t" \ + "usw %[temp1], 4(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "ulw %[temp0], 8(%[top]) \n\t" \ + "ulw %[temp1], 12(%[top]) \n\t" \ + CLIPPING(8) \ + "usw %[temp0], 8(%[dst]) \n\t" \ + "usw %[temp1], 12(%[dst]) \n\t" \ + ".endif \n\t" \ + ".endif \n\t" \ + : [dst_1]"+&r"(dst_1), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [top_1]"r"(top_1), [top]"r"((TOP)), [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define CLIP_TO_DST(DST, SIZE) do { \ + int y; \ + const uint8_t* top = (DST) - BPS; \ + const int top_1 = ((int)top[-1] << 16) + top[-1]; \ + for (y = 0; y < (SIZE); ++y) { \ + CLIP_8B_TO_DST((DST), top, (SIZE)); \ + (DST) += BPS; \ + } \ +} while (0) + +#define TRUE_MOTION(DST, SIZE) \ +static void TrueMotion##SIZE(uint8_t* (DST)) { \ + CLIP_TO_DST((DST), (SIZE)); \ +} + +TRUE_MOTION(dst, 4) +TRUE_MOTION(dst, 8) +TRUE_MOTION(dst, 16) + +#undef TRUE_MOTION +#undef CLIP_TO_DST +#undef CLIP_8B_TO_DST +#undef CLIPPING + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMIPSdspR2(void) { + VP8TransformDC = TransformDC; + VP8TransformAC3 = TransformAC3; + VP8Transform = TransformTwo; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TrueMotion4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[6] = LD4; + + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TrueMotion8; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + + VP8PredLuma16[1] = TrueMotion16; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8DspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_msa.c new file mode 100644 index 0000000000..58d1730192 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_msa.c @@ -0,0 +1,1018 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of dsp functions +// +// Author(s): Prashant Patil (prashant.patil@imgtec.com) + + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "src/dsp/msa_macro.h" + +//------------------------------------------------------------------------------ +// Transforms + +#define IDCT_1D_W(in0, in1, in2, in3, out0, out1, out2, out3) { \ + v4i32 a1_m, b1_m, c1_m, d1_m; \ + v4i32 c_tmp1_m, c_tmp2_m, d_tmp1_m, d_tmp2_m; \ + const v4i32 cospi8sqrt2minus1 = __msa_fill_w(20091); \ + const v4i32 sinpi8sqrt2 = __msa_fill_w(35468); \ + \ + a1_m = in0 + in2; \ + b1_m = in0 - in2; \ + c_tmp1_m = (in1 * sinpi8sqrt2) >> 16; \ + c_tmp2_m = in3 + ((in3 * cospi8sqrt2minus1) >> 16); \ + c1_m = c_tmp1_m - c_tmp2_m; \ + d_tmp1_m = in1 + ((in1 * cospi8sqrt2minus1) >> 16); \ + d_tmp2_m = (in3 * sinpi8sqrt2) >> 16; \ + d1_m = d_tmp1_m + d_tmp2_m; \ + BUTTERFLY_4(a1_m, b1_m, c1_m, d1_m, out0, out1, out2, out3); \ +} + +static void TransformOne(const int16_t* in, uint8_t* dst) { + v8i16 input0, input1; + v4i32 in0, in1, in2, in3, hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3; + v4i32 res0, res1, res2, res3; + const v16i8 zero = { 0 }; + v16i8 dest0, dest1, dest2, dest3; + + LD_SH2(in, 8, input0, input1); + UNPCK_SH_SW(input0, in0, in1); + UNPCK_SH_SW(input1, in2, in3); + IDCT_1D_W(in0, in1, in2, in3, hz0, hz1, hz2, hz3); + TRANSPOSE4x4_SW_SW(hz0, hz1, hz2, hz3, hz0, hz1, hz2, hz3); + IDCT_1D_W(hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3); + SRARI_W4_SW(vt0, vt1, vt2, vt3, 3); + TRANSPOSE4x4_SW_SW(vt0, vt1, vt2, vt3, vt0, vt1, vt2, vt3); + LD_SB4(dst, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, vt0, res1, vt1, res2, vt2, res3, vt3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, vt0, vt1); + res0 = (v4i32)__msa_pckev_b((v16i8)vt0, (v16i8)vt1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +static void TransformTwo(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne(in, dst); + if (do_two) { + TransformOne(in + 16, dst + 4); + } +} + +static void TransformWHT(const int16_t* in, int16_t* out) { + v8i16 input0, input1; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + v8i16 tmp0, tmp1, tmp2, tmp3; + v8i16 out0, out1; + + LD_SH2(in, 8, input0, input1); + input1 = SLDI_SH(input1, input1, 8); + tmp0 = input0 + input1; + tmp1 = input0 - input1; + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + out0 = tmp2 + tmp3; + out1 = tmp2 - tmp3; + VSHF_H2_SH(out0, out1, out0, out1, mask2, mask3, input0, input1); + tmp0 = input0 + input1; + tmp1 = input0 - input1; + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + tmp0 = tmp2 + tmp3; + tmp1 = tmp2 - tmp3; + ADDVI_H2_SH(tmp0, 3, tmp1, 3, out0, out1); + SRAI_H2_SH(out0, out1, 3); + out[0] = __msa_copy_s_h(out0, 0); + out[16] = __msa_copy_s_h(out0, 4); + out[32] = __msa_copy_s_h(out1, 0); + out[48] = __msa_copy_s_h(out1, 4); + out[64] = __msa_copy_s_h(out0, 1); + out[80] = __msa_copy_s_h(out0, 5); + out[96] = __msa_copy_s_h(out1, 1); + out[112] = __msa_copy_s_h(out1, 5); + out[128] = __msa_copy_s_h(out0, 2); + out[144] = __msa_copy_s_h(out0, 6); + out[160] = __msa_copy_s_h(out1, 2); + out[176] = __msa_copy_s_h(out1, 6); + out[192] = __msa_copy_s_h(out0, 3); + out[208] = __msa_copy_s_h(out0, 7); + out[224] = __msa_copy_s_h(out1, 3); + out[240] = __msa_copy_s_h(out1, 7); +} + +static void TransformDC(const int16_t* in, uint8_t* dst) { + const int DC = (in[0] + 4) >> 3; + const v8i16 tmp0 = __msa_fill_h(DC); + ADDBLK_ST4x4_UB(tmp0, tmp0, tmp0, tmp0, dst, BPS); +} + +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const int a = in[0] + 4; + const int c4 = WEBP_TRANSFORM_AC3_MUL2(in[4]); + const int d4 = WEBP_TRANSFORM_AC3_MUL1(in[4]); + const int in2 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int in3 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + v4i32 tmp0 = { 0 }; + v4i32 out0 = __msa_fill_w(a + d4); + v4i32 out1 = __msa_fill_w(a + c4); + v4i32 out2 = __msa_fill_w(a - c4); + v4i32 out3 = __msa_fill_w(a - d4); + v4i32 res0, res1, res2, res3; + const v4i32 zero = { 0 }; + v16u8 dest0, dest1, dest2, dest3; + + INSERT_W4_SW(in3, in2, -in2, -in3, tmp0); + ADD4(out0, tmp0, out1, tmp0, out2, tmp0, out3, tmp0, + out0, out1, out2, out3); + SRAI_W4_SW(out0, out1, out2, out3, 3); + LD_UB4(dst, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, out0, res1, out1, res2, out2, res3, out3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, out0, out1); + res0 = (v4i32)__msa_pckev_b((v16i8)out0, (v16i8)out1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +//------------------------------------------------------------------------------ +// Edge filtering functions + +#define FLIP_SIGN2(in0, in1, out0, out1) { \ + out0 = (v16i8)__msa_xori_b(in0, 0x80); \ + out1 = (v16i8)__msa_xori_b(in1, 0x80); \ +} + +#define FLIP_SIGN4(in0, in1, in2, in3, out0, out1, out2, out3) { \ + FLIP_SIGN2(in0, in1, out0, out1); \ + FLIP_SIGN2(in2, in3, out2, out3); \ +} + +#define FILT_VAL(q0_m, p0_m, mask, filt) do { \ + v16i8 q0_sub_p0; \ + q0_sub_p0 = __msa_subs_s_b(q0_m, p0_m); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = __msa_adds_s_b(filt, q0_sub_p0); \ + filt = filt & mask; \ +} while (0) + +#define FILT2(q_m, p_m, q, p) do { \ + u_r = SRAI_H(temp1, 7); \ + u_r = __msa_sat_s_h(u_r, 7); \ + u_l = SRAI_H(temp3, 7); \ + u_l = __msa_sat_s_h(u_l, 7); \ + u = __msa_pckev_b((v16i8)u_l, (v16i8)u_r); \ + q_m = __msa_subs_s_b(q_m, u); \ + p_m = __msa_adds_s_b(p_m, u); \ + q = __msa_xori_b((v16u8)q_m, 0x80); \ + p = __msa_xori_b((v16u8)p_m, 0x80); \ +} while (0) + +#define LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev) do { \ + v16i8 p1_m, p0_m, q0_m, q1_m; \ + v16i8 filt, t1, t2; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + \ + FLIP_SIGN4(p1, p0, q0, q1, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + filt = filt & hev; \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + t1 = __msa_adds_s_b(filt, cnst4b); \ + t1 = SRAI_B(t1, 3); \ + t2 = __msa_adds_s_b(filt, cnst3b); \ + t2 = SRAI_B(t2, 3); \ + q0_m = __msa_subs_s_b(q0_m, t1); \ + q0 = __msa_xori_b((v16u8)q0_m, 0x80); \ + p0_m = __msa_adds_s_b(p0_m, t2); \ + p0 = __msa_xori_b((v16u8)p0_m, 0x80); \ + filt = __msa_srari_b(t1, 1); \ + hev = __msa_xori_b(hev, 0xff); \ + filt = filt & hev; \ + q1_m = __msa_subs_s_b(q1_m, filt); \ + q1 = __msa_xori_b((v16u8)q1_m, 0x80); \ + p1_m = __msa_adds_s_b(p1_m, filt); \ + p1 = __msa_xori_b((v16u8)p1_m, 0x80); \ +} while (0) + +#define LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev) do { \ + v16i8 p2_m, p1_m, p0_m, q2_m, q1_m, q0_m; \ + v16i8 u, filt, t1, t2, filt_sign; \ + v8i16 filt_r, filt_l, u_r, u_l; \ + v8i16 temp0, temp1, temp2, temp3; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + const v8i16 cnst9h = __msa_ldi_h(9); \ + const v8i16 cnst63h = __msa_ldi_h(63); \ + \ + FLIP_SIGN4(p1, p0, q0, q1, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + FLIP_SIGN2(p2, q2, p2_m, q2_m); \ + t2 = filt & hev; \ + /* filt_val &= ~hev */ \ + hev = __msa_xori_b(hev, 0xff); \ + filt = filt & hev; \ + t1 = __msa_adds_s_b(t2, cnst4b); \ + t1 = SRAI_B(t1, 3); \ + t2 = __msa_adds_s_b(t2, cnst3b); \ + t2 = SRAI_B(t2, 3); \ + q0_m = __msa_subs_s_b(q0_m, t1); \ + p0_m = __msa_adds_s_b(p0_m, t2); \ + filt_sign = __msa_clti_s_b(filt, 0); \ + ILVRL_B2_SH(filt_sign, filt, filt_r, filt_l); \ + /* update q2/p2 */ \ + temp0 = filt_r * cnst9h; \ + temp1 = temp0 + cnst63h; \ + temp2 = filt_l * cnst9h; \ + temp3 = temp2 + cnst63h; \ + FILT2(q2_m, p2_m, q2, p2); \ + /* update q1/p1 */ \ + temp1 = temp1 + temp0; \ + temp3 = temp3 + temp2; \ + FILT2(q1_m, p1_m, q1, p1); \ + /* update q0/p0 */ \ + temp1 = temp1 + temp0; \ + temp3 = temp3 + temp2; \ + FILT2(q0_m, p0_m, q0, p0); \ +} while (0) + +#define LPF_MASK_HEV(p3_in, p2_in, p1_in, p0_in, \ + q0_in, q1_in, q2_in, q3_in, \ + limit_in, b_limit_in, thresh_in, \ + hev_out, mask_out) do { \ + v16u8 p3_asub_p2_m, p2_asub_p1_m, p1_asub_p0_m, q1_asub_q0_m; \ + v16u8 p1_asub_q1_m, p0_asub_q0_m, q3_asub_q2_m, q2_asub_q1_m; \ + v16u8 flat_out; \ + \ + /* absolute subtraction of pixel values */ \ + p3_asub_p2_m = __msa_asub_u_b(p3_in, p2_in); \ + p2_asub_p1_m = __msa_asub_u_b(p2_in, p1_in); \ + p1_asub_p0_m = __msa_asub_u_b(p1_in, p0_in); \ + q1_asub_q0_m = __msa_asub_u_b(q1_in, q0_in); \ + q2_asub_q1_m = __msa_asub_u_b(q2_in, q1_in); \ + q3_asub_q2_m = __msa_asub_u_b(q3_in, q2_in); \ + p0_asub_q0_m = __msa_asub_u_b(p0_in, q0_in); \ + p1_asub_q1_m = __msa_asub_u_b(p1_in, q1_in); \ + /* calculation of hev */ \ + flat_out = __msa_max_u_b(p1_asub_p0_m, q1_asub_q0_m); \ + hev_out = (thresh_in < flat_out); \ + /* calculation of mask */ \ + p0_asub_q0_m = __msa_adds_u_b(p0_asub_q0_m, p0_asub_q0_m); \ + p1_asub_q1_m = SRAI_B(p1_asub_q1_m, 1); \ + p0_asub_q0_m = __msa_adds_u_b(p0_asub_q0_m, p1_asub_q1_m); \ + mask_out = (b_limit_in < p0_asub_q0_m); \ + mask_out = __msa_max_u_b(flat_out, mask_out); \ + p3_asub_p2_m = __msa_max_u_b(p3_asub_p2_m, p2_asub_p1_m); \ + mask_out = __msa_max_u_b(p3_asub_p2_m, mask_out); \ + q2_asub_q1_m = __msa_max_u_b(q2_asub_q1_m, q3_asub_q2_m); \ + mask_out = __msa_max_u_b(q2_asub_q1_m, mask_out); \ + mask_out = (limit_in < mask_out); \ + mask_out = __msa_xori_b(mask_out, 0xff); \ +} while (0) + +#define ST6x1_UB(in0, in0_idx, in1, in1_idx, pdst, stride) do { \ + const uint16_t tmp0_h = __msa_copy_s_h((v8i16)in1, in1_idx); \ + const uint32_t tmp0_w = __msa_copy_s_w((v4i32)in0, in0_idx); \ + SW(tmp0_w, pdst); \ + SH(tmp0_h, pdst + stride); \ +} while (0) + +#define ST6x4_UB(in0, start_in0_idx, in1, start_in1_idx, pdst, stride) do { \ + uint8_t* ptmp1 = (uint8_t*)pdst; \ + ST6x1_UB(in0, start_in0_idx, in1, start_in1_idx, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 1, in1, start_in1_idx + 1, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 2, in1, start_in1_idx + 2, ptmp1, 4); \ + ptmp1 += stride; \ + ST6x1_UB(in0, start_in0_idx + 3, in1, start_in1_idx + 3, ptmp1, 4); \ +} while (0) + +#define LPF_SIMPLE_FILT(p1_in, p0_in, q0_in, q1_in, mask) do { \ + v16i8 p1_m, p0_m, q0_m, q1_m, filt, filt1, filt2; \ + const v16i8 cnst4b = __msa_ldi_b(4); \ + const v16i8 cnst3b = __msa_ldi_b(3); \ + \ + FLIP_SIGN4(p1_in, p0_in, q0_in, q1_in, p1_m, p0_m, q0_m, q1_m); \ + filt = __msa_subs_s_b(p1_m, q1_m); \ + FILT_VAL(q0_m, p0_m, mask, filt); \ + filt1 = __msa_adds_s_b(filt, cnst4b); \ + filt1 = SRAI_B(filt1, 3); \ + filt2 = __msa_adds_s_b(filt, cnst3b); \ + filt2 = SRAI_B(filt2, 3); \ + q0_m = __msa_subs_s_b(q0_m, filt1); \ + p0_m = __msa_adds_s_b(p0_m, filt2); \ + q0_in = __msa_xori_b((v16u8)q0_m, 0x80); \ + p0_in = __msa_xori_b((v16u8)p0_m, 0x80); \ +} while (0) + +#define LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask) do { \ + v16u8 p1_a_sub_q1, p0_a_sub_q0; \ + \ + p0_a_sub_q0 = __msa_asub_u_b(p0, q0); \ + p1_a_sub_q1 = __msa_asub_u_b(p1, q1); \ + p1_a_sub_q1 = (v16u8)__msa_srli_b((v16i8)p1_a_sub_q1, 1); \ + p0_a_sub_q0 = __msa_adds_u_b(p0_a_sub_q0, p0_a_sub_q0); \ + mask = __msa_adds_u_b(p0_a_sub_q0, p1_a_sub_q1); \ + mask = (mask <= b_limit); \ +} while (0) + +static void VFilter16(uint8_t* src, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptemp = src - 4 * stride; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 mask, hev; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(ptemp, stride, p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ptemp = src - 3 * stride; + ST_UB4(p2, p1, p0, q0, ptemp, stride); + ptemp += (4 * stride); + ST_UB2(q1, q2, ptemp, stride); +} + +static void HFilter16(uint8_t* src, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp = src - 4; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp, stride, row0, row1, row2, row3, row4, row5, row6, row7); + ptmp += (8 * stride); + LD_UB8(ptmp, stride, row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ILVR_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp3, tmp4); + ILVL_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp6, tmp7); + ILVRL_B2_SH(q2, q1, tmp2, tmp5); + ptmp = src - 3; + ST6x1_UB(tmp3, 0, tmp2, 0, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 1, tmp2, 1, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 2, tmp2, 2, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp3, 3, tmp2, 3, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 0, tmp2, 4, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 1, tmp2, 5, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 2, tmp2, 6, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp4, 3, tmp2, 7, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 0, tmp5, 0, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 1, tmp5, 1, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 2, tmp5, 2, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp6, 3, tmp5, 3, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 0, tmp5, 4, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 1, tmp5, 5, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 2, tmp5, 6, ptmp, 4); + ptmp += stride; + ST6x1_UB(tmp7, 3, tmp5, 7, ptmp, 4); +} + +// on three inner edges +static void VFilterHorEdge16i(uint8_t* src, int stride, + int b_limit, int limit, int thresh) { + v16u8 mask, hev; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + const v16u8 thresh0 = (v16u8)__msa_fill_b(thresh); + const v16u8 b_limit0 = (v16u8)__msa_fill_b(b_limit); + const v16u8 limit0 = (v16u8)__msa_fill_b(limit); + + LD_UB8((src - 4 * stride), stride, p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit0, b_limit0, thresh0, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ST_UB4(p1, p0, q0, q1, (src - 2 * stride), stride); +} + +static void VFilter16i(uint8_t* src_y, int stride, + int b_limit, int limit, int thresh) { + VFilterHorEdge16i(src_y + 4 * stride, stride, b_limit, limit, thresh); + VFilterHorEdge16i(src_y + 8 * stride, stride, b_limit, limit, thresh); + VFilterHorEdge16i(src_y + 12 * stride, stride, b_limit, limit, thresh); +} + +static void HFilterVertEdge16i(uint8_t* src, int stride, + int b_limit, int limit, int thresh) { + v16u8 mask, hev; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7; + v16u8 row8, row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + const v16u8 thresh0 = (v16u8)__msa_fill_b(thresh); + const v16u8 b_limit0 = (v16u8)__msa_fill_b(b_limit); + const v16u8 limit0 = (v16u8)__msa_fill_b(limit); + + LD_UB8(src - 4, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(src - 4 + (8 * stride), stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit0, b_limit0, thresh0, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ILVR_B2_SH(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp2, tmp3); + ILVL_B2_SH(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp4, tmp5); + src -= 2; + ST4x8_UB(tmp2, tmp3, src, stride); + src += (8 * stride); + ST4x8_UB(tmp4, tmp5, src, stride); +} + +static void HFilter16i(uint8_t* src_y, int stride, + int b_limit, int limit, int thresh) { + HFilterVertEdge16i(src_y + 4, stride, b_limit, limit, thresh); + HFilterVertEdge16i(src_y + 8, stride, b_limit, limit, thresh); + HFilterVertEdge16i(src_y + 12, stride, b_limit, limit, thresh); +} + +// 8-pixels wide variants, for chroma filtering +static void VFilter8(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp_src_u = src_u - 4 * stride; + uint8_t* ptmp_src_v = src_v - 4 * stride; + uint64_t p2_d, p1_d, p0_d, q0_d, q1_d, q2_d; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 p3_u, p2_u, p1_u, p0_u, q3_u, q2_u, q1_u, q0_u; + v16u8 p3_v, p2_v, p1_v, p0_v, q3_v, q2_v, q1_v, q0_v; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp_src_u, stride, p3_u, p2_u, p1_u, p0_u, q0_u, q1_u, q2_u, q3_u); + LD_UB8(ptmp_src_v, stride, p3_v, p2_v, p1_v, p0_v, q0_v, q1_v, q2_v, q3_v); + ILVR_D4_UB(p3_v, p3_u, p2_v, p2_u, p1_v, p1_u, p0_v, p0_u, p3, p2, p1, p0); + ILVR_D4_UB(q0_v, q0_u, q1_v, q1_u, q2_v, q2_u, q3_v, q3_u, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + p2_d = __msa_copy_s_d((v2i64)p2, 0); + p1_d = __msa_copy_s_d((v2i64)p1, 0); + p0_d = __msa_copy_s_d((v2i64)p0, 0); + q0_d = __msa_copy_s_d((v2i64)q0, 0); + q1_d = __msa_copy_s_d((v2i64)q1, 0); + q2_d = __msa_copy_s_d((v2i64)q2, 0); + ptmp_src_u += stride; + SD4(p2_d, p1_d, p0_d, q0_d, ptmp_src_u, stride); + ptmp_src_u += (4 * stride); + SD(q1_d, ptmp_src_u); + ptmp_src_u += stride; + SD(q2_d, ptmp_src_u); + p2_d = __msa_copy_s_d((v2i64)p2, 1); + p1_d = __msa_copy_s_d((v2i64)p1, 1); + p0_d = __msa_copy_s_d((v2i64)p0, 1); + q0_d = __msa_copy_s_d((v2i64)q0, 1); + q1_d = __msa_copy_s_d((v2i64)q1, 1); + q2_d = __msa_copy_s_d((v2i64)q2, 1); + ptmp_src_v += stride; + SD4(p2_d, p1_d, p0_d, q0_d, ptmp_src_v, stride); + ptmp_src_v += (4 * stride); + SD(q1_d, ptmp_src_v); + ptmp_src_v += stride; + SD(q2_d, ptmp_src_v); +} + +static void HFilter8(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint8_t* ptmp_src_u = src_u - 4; + uint8_t* ptmp_src_v = src_v - 4; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + + LD_UB8(ptmp_src_u, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(ptmp_src_v, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_MBFILTER(p2, p1, p0, q0, q1, q2, mask, hev); + ILVR_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp3, tmp4); + ILVL_B2_SH(p1, p2, q0, p0, tmp0, tmp1); + ILVRL_H2_SH(tmp1, tmp0, tmp6, tmp7); + ILVRL_B2_SH(q2, q1, tmp2, tmp5); + ptmp_src_u += 1; + ST6x4_UB(tmp3, 0, tmp2, 0, ptmp_src_u, stride); + ptmp_src_u += 4 * stride; + ST6x4_UB(tmp4, 0, tmp2, 4, ptmp_src_u, stride); + ptmp_src_v += 1; + ST6x4_UB(tmp6, 0, tmp5, 0, ptmp_src_v, stride); + ptmp_src_v += 4 * stride; + ST6x4_UB(tmp7, 0, tmp5, 4, ptmp_src_v, stride); +} + +static void VFilter8i(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + uint64_t p1_d, p0_d, q0_d, q1_d; + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 p3_u, p2_u, p1_u, p0_u, q3_u, q2_u, q1_u, q0_u; + v16u8 p3_v, p2_v, p1_v, p0_v, q3_v, q2_v, q1_v, q0_v; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(src_u, stride, p3_u, p2_u, p1_u, p0_u, q0_u, q1_u, q2_u, q3_u); + src_u += (5 * stride); + LD_UB8(src_v, stride, p3_v, p2_v, p1_v, p0_v, q0_v, q1_v, q2_v, q3_v); + src_v += (5 * stride); + ILVR_D4_UB(p3_v, p3_u, p2_v, p2_u, p1_v, p1_u, p0_v, p0_u, p3, p2, p1, p0); + ILVR_D4_UB(q0_v, q0_u, q1_v, q1_u, q2_v, q2_u, q3_v, q3_u, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + p1_d = __msa_copy_s_d((v2i64)p1, 0); + p0_d = __msa_copy_s_d((v2i64)p0, 0); + q0_d = __msa_copy_s_d((v2i64)q0, 0); + q1_d = __msa_copy_s_d((v2i64)q1, 0); + SD4(q1_d, q0_d, p0_d, p1_d, src_u, -stride); + p1_d = __msa_copy_s_d((v2i64)p1, 1); + p0_d = __msa_copy_s_d((v2i64)p0, 1); + q0_d = __msa_copy_s_d((v2i64)q0, 1); + q1_d = __msa_copy_s_d((v2i64)q1, 1); + SD4(q1_d, q0_d, p0_d, p1_d, src_v, -stride); +} + +static void HFilter8i(uint8_t* src_u, uint8_t* src_v, int stride, + int b_limit_in, int limit_in, int thresh_in) { + v16u8 p3, p2, p1, p0, q3, q2, q1, q0, mask, hev; + v16u8 row0, row1, row2, row3, row4, row5, row6, row7, row8; + v16u8 row9, row10, row11, row12, row13, row14, row15; + v4i32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + const v16u8 thresh = (v16u8)__msa_fill_b(thresh_in); + const v16u8 limit = (v16u8)__msa_fill_b(limit_in); + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB8(src_u, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(src_v, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x8_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p3, p2, p1, p0, q0, q1, q2, q3); + LPF_MASK_HEV(p3, p2, p1, p0, q0, q1, q2, q3, limit, b_limit, thresh, + hev, mask); + LPF_FILTER4_4W(p1, p0, q0, q1, mask, hev); + ILVR_B2_SW(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SW(tmp1, tmp0, tmp2, tmp3); + ILVL_B2_SW(p0, p1, q1, q0, tmp0, tmp1); + ILVRL_H2_SW(tmp1, tmp0, tmp4, tmp5); + src_u += 2; + ST4x4_UB(tmp2, tmp2, 0, 1, 2, 3, src_u, stride); + src_u += 4 * stride; + ST4x4_UB(tmp3, tmp3, 0, 1, 2, 3, src_u, stride); + src_v += 2; + ST4x4_UB(tmp4, tmp4, 0, 1, 2, 3, src_v, stride); + src_v += 4 * stride; + ST4x4_UB(tmp5, tmp5, 0, 1, 2, 3, src_v, stride); +} + +static void SimpleVFilter16(uint8_t* src, int stride, int b_limit_in) { + v16u8 p1, p0, q1, q0, mask; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + + LD_UB4(src - 2 * stride, stride, p1, p0, q0, q1); + LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask); + LPF_SIMPLE_FILT(p1, p0, q0, q1, mask); + ST_UB2(p0, q0, src - stride, stride); +} + +static void SimpleHFilter16(uint8_t* src, int stride, int b_limit_in) { + v16u8 p1, p0, q1, q0, mask, row0, row1, row2, row3, row4, row5, row6, row7; + v16u8 row8, row9, row10, row11, row12, row13, row14, row15; + v8i16 tmp0, tmp1; + const v16u8 b_limit = (v16u8)__msa_fill_b(b_limit_in); + uint8_t* ptemp_src = src - 2; + + LD_UB8(ptemp_src, stride, row0, row1, row2, row3, row4, row5, row6, row7); + LD_UB8(ptemp_src + 8 * stride, stride, + row8, row9, row10, row11, row12, row13, row14, row15); + TRANSPOSE16x4_UB_UB(row0, row1, row2, row3, row4, row5, row6, row7, + row8, row9, row10, row11, row12, row13, row14, row15, + p1, p0, q0, q1); + LPF_SIMPLE_MASK(p1, p0, q0, q1, b_limit, mask); + LPF_SIMPLE_FILT(p1, p0, q0, q1, mask); + ILVRL_B2_SH(q0, p0, tmp1, tmp0); + ptemp_src += 1; + ST2x4_UB(tmp1, 0, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp1, 4, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp0, 0, ptemp_src, stride); + ptemp_src += 4 * stride; + ST2x4_UB(tmp0, 4, ptemp_src, stride); + ptemp_src += 4 * stride; +} + +static void SimpleVFilter16i(uint8_t* src_y, int stride, int b_limit_in) { + SimpleVFilter16(src_y + 4 * stride, stride, b_limit_in); + SimpleVFilter16(src_y + 8 * stride, stride, b_limit_in); + SimpleVFilter16(src_y + 12 * stride, stride, b_limit_in); +} + +static void SimpleHFilter16i(uint8_t* src_y, int stride, int b_limit_in) { + SimpleHFilter16(src_y + 4, stride, b_limit_in); + SimpleHFilter16(src_y + 8, stride, b_limit_in); + SimpleHFilter16(src_y + 12, stride, b_limit_in); +} + +//------------------------------------------------------------------------------ +// Intra predictions +//------------------------------------------------------------------------------ + +// 4x4 + +static void DC4(uint8_t* dst) { // DC + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS]; + dc >>= 3; + dc = dc | (dc << 8) | (dc << 16) | (dc << 24); + SW4(dc, dc, dc, dc, dst, BPS); +} + +static void TM4(uint8_t* dst) { + const uint8_t* const ptemp = dst - BPS - 1; + v8i16 T, d, r0, r1, r2, r3; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(ptemp[0 * BPS]); + const v8i16 L0 = (v8i16)__msa_fill_h(ptemp[1 * BPS]); + const v8i16 L1 = (v8i16)__msa_fill_h(ptemp[2 * BPS]); + const v8i16 L2 = (v8i16)__msa_fill_h(ptemp[3 * BPS]); + const v8i16 L3 = (v8i16)__msa_fill_h(ptemp[4 * BPS]); + const v16u8 T1 = LD_UB(ptemp + 1); + + T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + d = T - TL; + ADD4(d, L0, d, L1, d, L2, d, L3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_ST4x4_UB(r0, r1, r2, r3, dst, BPS); +} + +static void VE4(uint8_t* dst) { // vertical + const uint8_t* const ptop = dst - BPS - 1; + const uint32_t val0 = LW(ptop + 0); + const uint32_t val1 = LW(ptop + 4); + uint32_t out; + v16u8 A = { 0 }, B, C, AC, B2, R; + + INSERT_W2_UB(val0, val1, A); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + out = __msa_copy_s_w((v4i32)R, 0); + SW4(out, out, out, out, dst, BPS); +} + +static void RD4(uint8_t* dst) { // Down-right + const uint8_t* const ptop = dst - 1 - BPS; + uint32_t val0 = LW(ptop + 0); + uint32_t val1 = LW(ptop + 4); + uint32_t val2, val3; + v16u8 A, B, C, AC, B2, R, A1 = { 0 }; + + INSERT_W2_UB(val0, val1, A1); + A = SLDI_UB(A1, A1, 12); + A = (v16u8)__msa_insert_b((v16i8)A, 3, ptop[1 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 2, ptop[2 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 1, ptop[3 * BPS]); + A = (v16u8)__msa_insert_b((v16i8)A, 0, ptop[4 * BPS]); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + val3 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val2 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val1 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val0 = __msa_copy_s_w((v4i32)R, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +static void LD4(uint8_t* dst) { // Down-Left + const uint8_t* const ptop = dst - BPS; + uint32_t val0 = LW(ptop + 0); + uint32_t val1 = LW(ptop + 4); + uint32_t val2, val3; + v16u8 A = { 0 }, B, C, AC, B2, R; + + INSERT_W2_UB(val0, val1, A); + B = SLDI_UB(A, A, 1); + C = SLDI_UB(A, A, 2); + C = (v16u8)__msa_insert_b((v16i8)C, 6, ptop[7]); + AC = __msa_ave_u_b(A, C); + B2 = __msa_ave_u_b(B, B); + R = __msa_aver_u_b(AC, B2); + val0 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val1 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val2 = __msa_copy_s_w((v4i32)R, 0); + R = SLDI_UB(R, R, 1); + val3 = __msa_copy_s_w((v4i32)R, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +// 16x16 + +static void DC16(uint8_t* dst) { // DC + uint32_t dc = 16; + int i; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + v16u8 out; + + for (i = 0; i < 16; ++i) { + dc += dst[-1 + i * BPS]; + } + dc += HADD_UH_U32(dctop); + out = (v16u8)__msa_fill_b(dc >> 5); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void TM16(uint8_t* dst) { + int j; + v8i16 d1, d2; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(dst[-1 - BPS]); + const v16i8 T = LD_SB(dst - BPS); + + ILVRL_B2_SH(zero, T, d1, d2); + SUB2(d1, TL, d2, TL, d1, d2); + for (j = 0; j < 16; j += 4) { + v16i8 t0, t1, t2, t3; + v8i16 r0, r1, r2, r3, r4, r5, r6, r7; + const v8i16 L0 = (v8i16)__msa_fill_h(dst[-1 + 0 * BPS]); + const v8i16 L1 = (v8i16)__msa_fill_h(dst[-1 + 1 * BPS]); + const v8i16 L2 = (v8i16)__msa_fill_h(dst[-1 + 2 * BPS]); + const v8i16 L3 = (v8i16)__msa_fill_h(dst[-1 + 3 * BPS]); + ADD4(d1, L0, d1, L1, d1, L2, d1, L3, r0, r1, r2, r3); + ADD4(d2, L0, d2, L1, d2, L2, d2, L3, r4, r5, r6, r7); + CLIP_SH4_0_255(r0, r1, r2, r3); + CLIP_SH4_0_255(r4, r5, r6, r7); + PCKEV_B4_SB(r4, r0, r5, r1, r6, r2, r7, r3, t0, t1, t2, t3); + ST_SB4(t0, t1, t2, t3, dst, BPS); + dst += 4 * BPS; + } +} + +static void VE16(uint8_t* dst) { // vertical + const v16u8 rtop = LD_UB(dst - BPS); + ST_UB8(rtop, rtop, rtop, rtop, rtop, rtop, rtop, rtop, dst, BPS); + ST_UB8(rtop, rtop, rtop, rtop, rtop, rtop, rtop, rtop, dst + 8 * BPS, BPS); +} + +static void HE16(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; j -= 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(dst[-1 + 0 * BPS]); + const v16u8 L1 = (v16u8)__msa_fill_b(dst[-1 + 1 * BPS]); + const v16u8 L2 = (v16u8)__msa_fill_b(dst[-1 + 2 * BPS]); + const v16u8 L3 = (v16u8)__msa_fill_b(dst[-1 + 3 * BPS]); + ST_UB4(L0, L1, L2, L3, dst, BPS); + dst += 4 * BPS; + } +} + +static void DC16NoTop(uint8_t* dst) { // DC with top samples not available + int j; + uint32_t dc = 8; + v16u8 out; + + for (j = 0; j < 16; ++j) { + dc += dst[-1 + j * BPS]; + } + out = (v16u8)__msa_fill_b(dc >> 4); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void DC16NoLeft(uint8_t* dst) { // DC with left samples not available + uint32_t dc = 8; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + v16u8 out; + + dc += HADD_UH_U32(dctop); + out = (v16u8)__msa_fill_b(dc >> 4); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +static void DC16NoTopLeft(uint8_t* dst) { // DC with nothing + const v16u8 out = (v16u8)__msa_fill_b(0x80); + ST_UB8(out, out, out, out, out, out, out, out, dst, BPS); + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); +} + +// Chroma + +#define STORE8x8(out, dst) do { \ + SD4(out, out, out, out, dst + 0 * BPS, BPS); \ + SD4(out, out, out, out, dst + 4 * BPS, BPS); \ +} while (0) + +static void DC8uv(uint8_t* dst) { // DC + uint32_t dc = 8; + int i; + uint64_t out; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 temp0 = __msa_hadd_u_h(rtop, rtop); + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); + const v2u64 temp2 = __msa_hadd_u_d(temp1, temp1); + v16u8 dctemp; + + for (i = 0; i < 8; ++i) { + dc += dst[-1 + i * BPS]; + } + dc += __msa_copy_s_w((v4i32)temp2, 0); + dctemp = (v16u8)__msa_fill_b(dc >> 4); + out = __msa_copy_s_d((v2i64)dctemp, 0); + STORE8x8(out, dst); +} + +static void TM8uv(uint8_t* dst) { + int j; + const v16i8 T1 = LD_SB(dst - BPS); + const v16i8 zero = { 0 }; + const v8i16 T = (v8i16)__msa_ilvr_b(zero, T1); + const v8i16 TL = (v8i16)__msa_fill_h(dst[-1 - BPS]); + const v8i16 d = T - TL; + + for (j = 0; j < 8; j += 4) { + v16i8 t0, t1; + v8i16 r0 = (v8i16)__msa_fill_h(dst[-1 + 0 * BPS]); + v8i16 r1 = (v8i16)__msa_fill_h(dst[-1 + 1 * BPS]); + v8i16 r2 = (v8i16)__msa_fill_h(dst[-1 + 2 * BPS]); + v8i16 r3 = (v8i16)__msa_fill_h(dst[-1 + 3 * BPS]); + ADD4(d, r0, d, r1, d, r2, d, r3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_B2_SB(r1, r0, r3, r2, t0, t1); + ST4x4_UB(t0, t1, 0, 2, 0, 2, dst, BPS); + ST4x4_UB(t0, t1, 1, 3, 1, 3, dst + 4, BPS); + dst += 4 * BPS; + } +} + +static void VE8uv(uint8_t* dst) { // vertical + const v16u8 rtop = LD_UB(dst - BPS); + const uint64_t out = __msa_copy_s_d((v2i64)rtop, 0); + STORE8x8(out, dst); +} + +static void HE8uv(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(dst[-1 + 0 * BPS]); + const v16u8 L1 = (v16u8)__msa_fill_b(dst[-1 + 1 * BPS]); + const v16u8 L2 = (v16u8)__msa_fill_b(dst[-1 + 2 * BPS]); + const v16u8 L3 = (v16u8)__msa_fill_b(dst[-1 + 3 * BPS]); + const uint64_t out0 = __msa_copy_s_d((v2i64)L0, 0); + const uint64_t out1 = __msa_copy_s_d((v2i64)L1, 0); + const uint64_t out2 = __msa_copy_s_d((v2i64)L2, 0); + const uint64_t out3 = __msa_copy_s_d((v2i64)L3, 0); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + } +} + +static void DC8uvNoLeft(uint8_t* dst) { // DC with no left samples + const uint32_t dc = 4; + const v16u8 rtop = LD_UB(dst - BPS); + const v8u16 temp0 = __msa_hadd_u_h(rtop, rtop); + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); + const v2u64 temp2 = __msa_hadd_u_d(temp1, temp1); + const uint32_t sum_m = __msa_copy_s_w((v4i32)temp2, 0); + const v16u8 dcval = (v16u8)__msa_fill_b((dc + sum_m) >> 3); + const uint64_t out = __msa_copy_s_d((v2i64)dcval, 0); + STORE8x8(out, dst); +} + +static void DC8uvNoTop(uint8_t* dst) { // DC with no top samples + uint32_t dc = 4; + int i; + uint64_t out; + v16u8 dctemp; + + for (i = 0; i < 8; ++i) { + dc += dst[-1 + i * BPS]; + } + dctemp = (v16u8)__msa_fill_b(dc >> 3); + out = __msa_copy_s_d((v2i64)dctemp, 0); + STORE8x8(out, dst); +} + +static void DC8uvNoTopLeft(uint8_t* dst) { // DC with nothing + const uint64_t out = 0x8080808080808080ULL; + STORE8x8(out, dst); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitMSA(void) { + VP8TransformWHT = TransformWHT; + VP8Transform = TransformTwo; + VP8TransformDC = TransformDC; + VP8TransformAC3 = TransformAC3; + + VP8VFilter16 = VFilter16; + VP8HFilter16 = HFilter16; + VP8VFilter16i = VFilter16i; + VP8HFilter16i = HFilter16i; + VP8VFilter8 = VFilter8; + VP8HFilter8 = HFilter8; + VP8VFilter8i = VFilter8i; + VP8HFilter8i = HFilter8i; + VP8SimpleVFilter16 = SimpleVFilter16; + VP8SimpleHFilter16 = SimpleHFilter16; + VP8SimpleVFilter16i = SimpleVFilter16i; + VP8SimpleHFilter16i = SimpleHFilter16i; + + VP8PredLuma4[0] = DC4; + VP8PredLuma4[1] = TM4; + VP8PredLuma4[2] = VE4; + VP8PredLuma4[4] = RD4; + VP8PredLuma4[6] = LD4; + VP8PredLuma16[0] = DC16; + VP8PredLuma16[1] = TM16; + VP8PredLuma16[2] = VE16; + VP8PredLuma16[3] = HE16; + VP8PredLuma16[4] = DC16NoTop; + VP8PredLuma16[5] = DC16NoLeft; + VP8PredLuma16[6] = DC16NoTopLeft; + VP8PredChroma8[0] = DC8uv; + VP8PredChroma8[1] = TM8uv; + VP8PredChroma8[2] = VE8uv; + VP8PredChroma8[3] = HE8uv; + VP8PredChroma8[4] = DC8uvNoTop; + VP8PredChroma8[5] = DC8uvNoLeft; + VP8PredChroma8[6] = DC8uvNoTopLeft; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8DspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_neon.c new file mode 100644 index 0000000000..83b3a1f970 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_neon.c @@ -0,0 +1,1660 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARM NEON version of dsp functions and loop filtering. +// +// Authors: Somnath Banerjee (somnath@google.com) +// Johann Koenig (johannkoenig@google.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "src/dsp/neon.h" +#include "src/dec/vp8i_dec.h" + +//------------------------------------------------------------------------------ +// NxM Loading functions + +#if !defined(WORK_AROUND_GCC) + +// This intrinsics version makes gcc-4.6.3 crash during Load4x??() compilation +// (register alloc, probably). The variants somewhat mitigate the problem, but +// not quite. HFilter16i() remains problematic. +static WEBP_INLINE uint8x8x4_t Load4x8_NEON(const uint8_t* const src, + int stride) { + const uint8x8_t zero = vdup_n_u8(0); + uint8x8x4_t out; + INIT_VECTOR4(out, zero, zero, zero, zero); + out = vld4_lane_u8(src + 0 * stride, out, 0); + out = vld4_lane_u8(src + 1 * stride, out, 1); + out = vld4_lane_u8(src + 2 * stride, out, 2); + out = vld4_lane_u8(src + 3 * stride, out, 3); + out = vld4_lane_u8(src + 4 * stride, out, 4); + out = vld4_lane_u8(src + 5 * stride, out, 5); + out = vld4_lane_u8(src + 6 * stride, out, 6); + out = vld4_lane_u8(src + 7 * stride, out, 7); + return out; +} + +static WEBP_INLINE void Load4x16_NEON(const uint8_t* const src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + // row0 = p1[0..7]|p0[0..7]|q0[0..7]|q1[0..7] + // row8 = p1[8..15]|p0[8..15]|q0[8..15]|q1[8..15] + const uint8x8x4_t row0 = Load4x8_NEON(src - 2 + 0 * stride, stride); + const uint8x8x4_t row8 = Load4x8_NEON(src - 2 + 8 * stride, stride); + *p1 = vcombine_u8(row0.val[0], row8.val[0]); + *p0 = vcombine_u8(row0.val[1], row8.val[1]); + *q0 = vcombine_u8(row0.val[2], row8.val[2]); + *q1 = vcombine_u8(row0.val[3], row8.val[3]); +} + +#else // WORK_AROUND_GCC + +#define LOADQ_LANE_32b(VALUE, LANE) do { \ + (VALUE) = vld1q_lane_u32((const uint32_t*)src, (VALUE), (LANE)); \ + src += stride; \ +} while (0) + +static WEBP_INLINE void Load4x16_NEON(const uint8_t* src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + const uint32x4_t zero = vdupq_n_u32(0); + uint32x4x4_t in; + INIT_VECTOR4(in, zero, zero, zero, zero); + src -= 2; + LOADQ_LANE_32b(in.val[0], 0); + LOADQ_LANE_32b(in.val[1], 0); + LOADQ_LANE_32b(in.val[2], 0); + LOADQ_LANE_32b(in.val[3], 0); + LOADQ_LANE_32b(in.val[0], 1); + LOADQ_LANE_32b(in.val[1], 1); + LOADQ_LANE_32b(in.val[2], 1); + LOADQ_LANE_32b(in.val[3], 1); + LOADQ_LANE_32b(in.val[0], 2); + LOADQ_LANE_32b(in.val[1], 2); + LOADQ_LANE_32b(in.val[2], 2); + LOADQ_LANE_32b(in.val[3], 2); + LOADQ_LANE_32b(in.val[0], 3); + LOADQ_LANE_32b(in.val[1], 3); + LOADQ_LANE_32b(in.val[2], 3); + LOADQ_LANE_32b(in.val[3], 3); + // Transpose four 4x4 parts: + { + const uint8x16x2_t row01 = vtrnq_u8(vreinterpretq_u8_u32(in.val[0]), + vreinterpretq_u8_u32(in.val[1])); + const uint8x16x2_t row23 = vtrnq_u8(vreinterpretq_u8_u32(in.val[2]), + vreinterpretq_u8_u32(in.val[3])); + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + *p1 = vreinterpretq_u8_u16(row02.val[0]); + *p0 = vreinterpretq_u8_u16(row13.val[0]); + *q0 = vreinterpretq_u8_u16(row02.val[1]); + *q1 = vreinterpretq_u8_u16(row13.val[1]); + } +} +#undef LOADQ_LANE_32b + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Load8x16_NEON( + const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load4x16_NEON(src - 2, stride, p3, p2, p1, p0); + Load4x16_NEON(src + 2, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load16x4_NEON(const uint8_t* const src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + *p1 = vld1q_u8(src - 2 * stride); + *p0 = vld1q_u8(src - 1 * stride); + *q0 = vld1q_u8(src + 0 * stride); + *q1 = vld1q_u8(src + 1 * stride); +} + +static WEBP_INLINE void Load16x8_NEON( + const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load16x4_NEON(src - 2 * stride, stride, p3, p2, p1, p0); + Load16x4_NEON(src + 2 * stride, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load8x8x2_NEON( + const uint8_t* const u, const uint8_t* const v, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + *p3 = vcombine_u8(vld1_u8(u - 4 * stride), vld1_u8(v - 4 * stride)); + *p2 = vcombine_u8(vld1_u8(u - 3 * stride), vld1_u8(v - 3 * stride)); + *p1 = vcombine_u8(vld1_u8(u - 2 * stride), vld1_u8(v - 2 * stride)); + *p0 = vcombine_u8(vld1_u8(u - 1 * stride), vld1_u8(v - 1 * stride)); + *q0 = vcombine_u8(vld1_u8(u + 0 * stride), vld1_u8(v + 0 * stride)); + *q1 = vcombine_u8(vld1_u8(u + 1 * stride), vld1_u8(v + 1 * stride)); + *q2 = vcombine_u8(vld1_u8(u + 2 * stride), vld1_u8(v + 2 * stride)); + *q3 = vcombine_u8(vld1_u8(u + 3 * stride), vld1_u8(v + 3 * stride)); +} + +#if !defined(WORK_AROUND_GCC) + +#define LOAD_UV_8(ROW) \ + vcombine_u8(vld1_u8(u - 4 + (ROW) * stride), vld1_u8(v - 4 + (ROW) * stride)) + +static WEBP_INLINE void Load8x8x2T_NEON( + const uint8_t* const u, const uint8_t* const v, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + const uint8x16_t row0 = LOAD_UV_8(0); + const uint8x16_t row1 = LOAD_UV_8(1); + const uint8x16_t row2 = LOAD_UV_8(2); + const uint8x16_t row3 = LOAD_UV_8(3); + const uint8x16_t row4 = LOAD_UV_8(4); + const uint8x16_t row5 = LOAD_UV_8(5); + const uint8x16_t row6 = LOAD_UV_8(6); + const uint8x16_t row7 = LOAD_UV_8(7); + // Perform two side-by-side 8x8 transposes + // u00 u01 u02 u03 u04 u05 u06 u07 | v00 v01 v02 v03 v04 v05 v06 v07 + // u10 u11 u12 u13 u14 u15 u16 u17 | v10 v11 v12 ... + // u20 u21 u22 u23 u24 u25 u26 u27 | v20 v21 ... + // u30 u31 u32 u33 u34 u35 u36 u37 | ... + // u40 u41 u42 u43 u44 u45 u46 u47 | ... + // u50 u51 u52 u53 u54 u55 u56 u57 | ... + // u60 u61 u62 u63 u64 u65 u66 u67 | v60 ... + // u70 u71 u72 u73 u74 u75 u76 u77 | v70 v71 v72 ... + const uint8x16x2_t row01 = vtrnq_u8(row0, row1); // u00 u10 u02 u12 ... + // u01 u11 u03 u13 ... + const uint8x16x2_t row23 = vtrnq_u8(row2, row3); // u20 u30 u22 u32 ... + // u21 u31 u23 u33 ... + const uint8x16x2_t row45 = vtrnq_u8(row4, row5); // ... + const uint8x16x2_t row67 = vtrnq_u8(row6, row7); // ... + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + const uint16x8x2_t row46 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[0]), + vreinterpretq_u16_u8(row67.val[0])); + const uint16x8x2_t row57 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[1]), + vreinterpretq_u16_u8(row67.val[1])); + const uint32x4x2_t row04 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[0]), + vreinterpretq_u32_u16(row46.val[0])); + const uint32x4x2_t row26 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[1]), + vreinterpretq_u32_u16(row46.val[1])); + const uint32x4x2_t row15 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[0]), + vreinterpretq_u32_u16(row57.val[0])); + const uint32x4x2_t row37 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[1]), + vreinterpretq_u32_u16(row57.val[1])); + *p3 = vreinterpretq_u8_u32(row04.val[0]); + *p2 = vreinterpretq_u8_u32(row15.val[0]); + *p1 = vreinterpretq_u8_u32(row26.val[0]); + *p0 = vreinterpretq_u8_u32(row37.val[0]); + *q0 = vreinterpretq_u8_u32(row04.val[1]); + *q1 = vreinterpretq_u8_u32(row15.val[1]); + *q2 = vreinterpretq_u8_u32(row26.val[1]); + *q3 = vreinterpretq_u8_u32(row37.val[1]); +} +#undef LOAD_UV_8 + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store2x8_NEON(const uint8x8x2_t v, + uint8_t* const dst, int stride) { + vst2_lane_u8(dst + 0 * stride, v, 0); + vst2_lane_u8(dst + 1 * stride, v, 1); + vst2_lane_u8(dst + 2 * stride, v, 2); + vst2_lane_u8(dst + 3 * stride, v, 3); + vst2_lane_u8(dst + 4 * stride, v, 4); + vst2_lane_u8(dst + 5 * stride, v, 5); + vst2_lane_u8(dst + 6 * stride, v, 6); + vst2_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store2x16_NEON(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + uint8x8x2_t lo, hi; + lo.val[0] = vget_low_u8(p0); + lo.val[1] = vget_low_u8(q0); + hi.val[0] = vget_high_u8(p0); + hi.val[1] = vget_high_u8(q0); + Store2x8_NEON(lo, dst - 1 + 0 * stride, stride); + Store2x8_NEON(hi, dst - 1 + 8 * stride, stride); +} + +#if !defined(WORK_AROUND_GCC) +static WEBP_INLINE void Store4x8_NEON(const uint8x8x4_t v, + uint8_t* const dst, int stride) { + vst4_lane_u8(dst + 0 * stride, v, 0); + vst4_lane_u8(dst + 1 * stride, v, 1); + vst4_lane_u8(dst + 2 * stride, v, 2); + vst4_lane_u8(dst + 3 * stride, v, 3); + vst4_lane_u8(dst + 4 * stride, v, 4); + vst4_lane_u8(dst + 5 * stride, v, 5); + vst4_lane_u8(dst + 6 * stride, v, 6); + vst4_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store4x16_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + uint8x8x4_t lo, hi; + INIT_VECTOR4(lo, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(hi, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + Store4x8_NEON(lo, dst - 2 + 0 * stride, stride); + Store4x8_NEON(hi, dst - 2 + 8 * stride, stride); +} +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store16x2_NEON(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + vst1q_u8(dst - stride, p0); + vst1q_u8(dst, q0); +} + +static WEBP_INLINE void Store16x4_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + Store16x2_NEON(p1, p0, dst - stride, stride); + Store16x2_NEON(q0, q1, dst + stride, stride); +} + +static WEBP_INLINE void Store8x2x2_NEON(const uint8x16_t p0, + const uint8x16_t q0, + uint8_t* const u, uint8_t* const v, + int stride) { + // p0 and q0 contain the u+v samples packed in low/high halves. + vst1_u8(u - stride, vget_low_u8(p0)); + vst1_u8(u, vget_low_u8(q0)); + vst1_u8(v - stride, vget_high_u8(p0)); + vst1_u8(v, vget_high_u8(q0)); +} + +static WEBP_INLINE void Store8x4x2_NEON(const uint8x16_t p1, + const uint8x16_t p0, + const uint8x16_t q0, + const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + // The p1...q1 registers contain the u+v samples packed in low/high halves. + Store8x2x2_NEON(p1, p0, u - stride, v - stride, stride); + Store8x2x2_NEON(q0, q1, u + stride, v + stride, stride); +} + +#if !defined(WORK_AROUND_GCC) + +#define STORE6_LANE(DST, VAL0, VAL1, LANE) do { \ + vst3_lane_u8((DST) - 3, (VAL0), (LANE)); \ + vst3_lane_u8((DST) + 0, (VAL1), (LANE)); \ + (DST) += stride; \ +} while (0) + +static WEBP_INLINE void Store6x8x2_NEON( + const uint8x16_t p2, const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, const uint8x16_t q2, + uint8_t* u, uint8_t* v, int stride) { + uint8x8x3_t u0, u1, v0, v1; + INIT_VECTOR3(u0, vget_low_u8(p2), vget_low_u8(p1), vget_low_u8(p0)); + INIT_VECTOR3(u1, vget_low_u8(q0), vget_low_u8(q1), vget_low_u8(q2)); + INIT_VECTOR3(v0, vget_high_u8(p2), vget_high_u8(p1), vget_high_u8(p0)); + INIT_VECTOR3(v1, vget_high_u8(q0), vget_high_u8(q1), vget_high_u8(q2)); + STORE6_LANE(u, u0, u1, 0); + STORE6_LANE(u, u0, u1, 1); + STORE6_LANE(u, u0, u1, 2); + STORE6_LANE(u, u0, u1, 3); + STORE6_LANE(u, u0, u1, 4); + STORE6_LANE(u, u0, u1, 5); + STORE6_LANE(u, u0, u1, 6); + STORE6_LANE(u, u0, u1, 7); + STORE6_LANE(v, v0, v1, 0); + STORE6_LANE(v, v0, v1, 1); + STORE6_LANE(v, v0, v1, 2); + STORE6_LANE(v, v0, v1, 3); + STORE6_LANE(v, v0, v1, 4); + STORE6_LANE(v, v0, v1, 5); + STORE6_LANE(v, v0, v1, 6); + STORE6_LANE(v, v0, v1, 7); +} +#undef STORE6_LANE + +static WEBP_INLINE void Store4x8x2_NEON(const uint8x16_t p1, + const uint8x16_t p0, + const uint8x16_t q0, + const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + uint8x8x4_t u0, v0; + INIT_VECTOR4(u0, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(v0, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + vst4_lane_u8(u - 2 + 0 * stride, u0, 0); + vst4_lane_u8(u - 2 + 1 * stride, u0, 1); + vst4_lane_u8(u - 2 + 2 * stride, u0, 2); + vst4_lane_u8(u - 2 + 3 * stride, u0, 3); + vst4_lane_u8(u - 2 + 4 * stride, u0, 4); + vst4_lane_u8(u - 2 + 5 * stride, u0, 5); + vst4_lane_u8(u - 2 + 6 * stride, u0, 6); + vst4_lane_u8(u - 2 + 7 * stride, u0, 7); + vst4_lane_u8(v - 2 + 0 * stride, v0, 0); + vst4_lane_u8(v - 2 + 1 * stride, v0, 1); + vst4_lane_u8(v - 2 + 2 * stride, v0, 2); + vst4_lane_u8(v - 2 + 3 * stride, v0, 3); + vst4_lane_u8(v - 2 + 4 * stride, v0, 4); + vst4_lane_u8(v - 2 + 5 * stride, v0, 5); + vst4_lane_u8(v - 2 + 6 * stride, v0, 6); + vst4_lane_u8(v - 2 + 7 * stride, v0, 7); +} + +#endif // !WORK_AROUND_GCC + +// Zero extend 'v' to an int16x8_t. +static WEBP_INLINE int16x8_t ConvertU8ToS16_NEON(uint8x8_t v) { + return vreinterpretq_s16_u16(vmovl_u8(v)); +} + +// Performs unsigned 8b saturation on 'dst01' and 'dst23' storing the result +// to the corresponding rows of 'dst'. +static WEBP_INLINE void SaturateAndStore4x4_NEON(uint8_t* const dst, + const int16x8_t dst01, + const int16x8_t dst23) { + // Unsigned saturate to 8b. + const uint8x8_t dst01_u8 = vqmovun_s16(dst01); + const uint8x8_t dst23_u8 = vqmovun_s16(dst23); + + // Store the results. + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), vreinterpret_u32_u8(dst01_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), vreinterpret_u32_u8(dst01_u8), 1); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), vreinterpret_u32_u8(dst23_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), vreinterpret_u32_u8(dst23_u8), 1); +} + +static WEBP_INLINE void Add4x4_NEON(const int16x8_t row01, + const int16x8_t row23, + uint8_t* const dst) { + uint32x2_t dst01 = vdup_n_u32(0); + uint32x2_t dst23 = vdup_n_u32(0); + + // Load the source pixels. + dst01 = vld1_lane_u32((uint32_t*)(dst + 0 * BPS), dst01, 0); + dst23 = vld1_lane_u32((uint32_t*)(dst + 2 * BPS), dst23, 0); + dst01 = vld1_lane_u32((uint32_t*)(dst + 1 * BPS), dst01, 1); + dst23 = vld1_lane_u32((uint32_t*)(dst + 3 * BPS), dst23, 1); + + { + // Convert to 16b. + const int16x8_t dst01_s16 = ConvertU8ToS16_NEON(vreinterpret_u8_u32(dst01)); + const int16x8_t dst23_s16 = ConvertU8ToS16_NEON(vreinterpret_u8_u32(dst23)); + + // Descale with rounding. + const int16x8_t out01 = vrsraq_n_s16(dst01_s16, row01, 3); + const int16x8_t out23 = vrsraq_n_s16(dst23_s16, row23, 3); + // Add the inverse transform. + SaturateAndStore4x4_NEON(dst, out01, out23); + } +} + +//----------------------------------------------------------------------------- +// Simple In-loop filtering (Paragraph 15.2) + +static uint8x16_t NeedsFilter_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int thresh) { + const uint8x16_t thresh_v = vdupq_n_u8((uint8_t)thresh); + const uint8x16_t a_p0_q0 = vabdq_u8(p0, q0); // abs(p0-q0) + const uint8x16_t a_p1_q1 = vabdq_u8(p1, q1); // abs(p1-q1) + const uint8x16_t a_p0_q0_2 = vqaddq_u8(a_p0_q0, a_p0_q0); // 2 * abs(p0-q0) + const uint8x16_t a_p1_q1_2 = vshrq_n_u8(a_p1_q1, 1); // abs(p1-q1) / 2 + const uint8x16_t sum = vqaddq_u8(a_p0_q0_2, a_p1_q1_2); + const uint8x16_t mask = vcgeq_u8(thresh_v, sum); + return mask; +} + +static int8x16_t FlipSign_NEON(const uint8x16_t v) { + const uint8x16_t sign_bit = vdupq_n_u8(0x80); + return vreinterpretq_s8_u8(veorq_u8(v, sign_bit)); +} + +static uint8x16_t FlipSignBack_NEON(const int8x16_t v) { + const int8x16_t sign_bit = vdupq_n_s8(0x80); + return vreinterpretq_u8_s8(veorq_s8(v, sign_bit)); +} + +static int8x16_t GetBaseDelta_NEON(const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t p1_q1 = vqsubq_s8(p1, q1); // (p1-q1) + const int8x16_t s1 = vqaddq_s8(p1_q1, q0_p0); // (p1-q1) + 1 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // (p1-q1) + 2 * (q0 - p0) + const int8x16_t s3 = vqaddq_s8(q0_p0, s2); // (p1-q1) + 3 * (q0 - p0) + return s3; +} + +static int8x16_t GetBaseDelta0_NEON(const int8x16_t p0, const int8x16_t q0) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t s1 = vqaddq_s8(q0_p0, q0_p0); // 2 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // 3 * (q0 - p0) + return s2; +} + +//------------------------------------------------------------------------------ + +static void ApplyFilter2NoFlip_NEON(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + int8x16_t* const op0, + int8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + *op0 = vqaddq_s8(p0s, delta3); + *oq0 = vqsubq_s8(q0s, delta4); +} + +#if defined(WEBP_USE_INTRINSICS) + +static void ApplyFilter2_NEON(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + const int8x16_t sp0 = vqaddq_s8(p0s, delta3); + const int8x16_t sq0 = vqsubq_s8(q0s, delta4); + *op0 = FlipSignBack_NEON(sp0); + *oq0 = FlipSignBack_NEON(sq0); +} + +static void DoFilter2_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t p1s = FlipSign_NEON(p1); + const int8x16_t p0s = FlipSign_NEON(p0); + const int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const int8x16_t delta0 = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + const int8x16_t delta1 = vandq_s8(delta0, vreinterpretq_s8_u8(mask)); + ApplyFilter2_NEON(p0s, q0s, delta1, op0, oq0); +} + +static void SimpleVFilter16_NEON(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, op0, oq0; + Load16x4_NEON(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + DoFilter2_NEON(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store16x2_NEON(op0, oq0, p, stride); +} + +static void SimpleHFilter16_NEON(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, oq0, op0; + Load4x16_NEON(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + DoFilter2_NEON(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store2x16_NEON(op0, oq0, p, stride); +} + +#else + +// Load/Store vertical edge +#define LOAD8x4(c1, c2, c3, c4, b1, b2, stride) \ + "vld4.8 {" #c1 "[0]," #c2 "[0]," #c3 "[0]," #c4 "[0]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[1]," #c2 "[1]," #c3 "[1]," #c4 "[1]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[2]," #c2 "[2]," #c3 "[2]," #c4 "[2]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[3]," #c2 "[3]," #c3 "[3]," #c4 "[3]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[4]," #c2 "[4]," #c3 "[4]," #c4 "[4]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[5]," #c2 "[5]," #c3 "[5]," #c4 "[5]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[6]," #c2 "[6]," #c3 "[6]," #c4 "[6]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[7]," #c2 "[7]," #c3 "[7]," #c4 "[7]}," #b2 "," #stride "\n" + +#define STORE8x2(c1, c2, p, stride) \ + "vst2.8 {" #c1 "[0], " #c2 "[0]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[1], " #c2 "[1]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[2], " #c2 "[2]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[3], " #c2 "[3]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[4], " #c2 "[4]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[5], " #c2 "[5]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[6], " #c2 "[6]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[7], " #c2 "[7]}," #p "," #stride " \n" + +#define QRegs "q0", "q1", "q2", "q3", \ + "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" + +#define FLIP_SIGN_BIT2(a, b, s) \ + "veor " #a "," #a "," #s " \n" \ + "veor " #b "," #b "," #s " \n" \ + +#define FLIP_SIGN_BIT4(a, b, c, d, s) \ + FLIP_SIGN_BIT2(a, b, s) \ + FLIP_SIGN_BIT2(c, d, s) \ + +#define NEEDS_FILTER(p1, p0, q0, q1, thresh, mask) \ + "vabd.u8 q15," #p0 "," #q0 " \n" /* abs(p0 - q0) */ \ + "vabd.u8 q14," #p1 "," #q1 " \n" /* abs(p1 - q1) */ \ + "vqadd.u8 q15, q15, q15 \n" /* abs(p0 - q0) * 2 */ \ + "vshr.u8 q14, q14, #1 \n" /* abs(p1 - q1) / 2 */ \ + "vqadd.u8 q15, q15, q14 \n" /* abs(p0 - q0) * 2 + abs(p1 - q1) / 2 */ \ + "vdup.8 q14, " #thresh " \n" \ + "vcge.u8 " #mask ", q14, q15 \n" /* mask <= thresh */ + +#define GET_BASE_DELTA(p1, p0, q0, q1, o) \ + "vqsub.s8 q15," #q0 "," #p0 " \n" /* (q0 - p0) */ \ + "vqsub.s8 " #o "," #p1 "," #q1 " \n" /* (p1 - q1) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 1 * (p0 - q0) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 2 * (p0 - q0) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 3 * (p0 - q0) */ + +#define DO_SIMPLE_FILTER(p0, q0, fl) \ + "vmov.i8 q15, #0x03 \n" \ + "vqadd.s8 q15, q15, " #fl " \n" /* filter1 = filter + 3 */ \ + "vshr.s8 q15, q15, #3 \n" /* filter1 >> 3 */ \ + "vqadd.s8 " #p0 "," #p0 ", q15 \n" /* p0 += filter1 */ \ + \ + "vmov.i8 q15, #0x04 \n" \ + "vqadd.s8 q15, q15, " #fl " \n" /* filter1 = filter + 4 */ \ + "vshr.s8 q15, q15, #3 \n" /* filter2 >> 3 */ \ + "vqsub.s8 " #q0 "," #q0 ", q15 \n" /* q0 -= filter2 */ + +// Applies filter on 2 pixels (p0 and q0) +#define DO_FILTER2(p1, p0, q0, q1, thresh) \ + NEEDS_FILTER(p1, p0, q0, q1, thresh, q9) /* filter mask in q9 */ \ + "vmov.i8 q10, #0x80 \n" /* sign bit */ \ + FLIP_SIGN_BIT4(p1, p0, q0, q1, q10) /* convert to signed value */ \ + GET_BASE_DELTA(p1, p0, q0, q1, q11) /* get filter level */ \ + "vand q9, q9, q11 \n" /* apply filter mask */ \ + DO_SIMPLE_FILTER(p0, q0, q9) /* apply filter */ \ + FLIP_SIGN_BIT2(p0, q0, q10) + +static void SimpleVFilter16_NEON(uint8_t* p, int stride, int thresh) { + __asm__ volatile ( + "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride + + "vld1.u8 {q1}, [%[p]], %[stride] \n" // p1 + "vld1.u8 {q2}, [%[p]], %[stride] \n" // p0 + "vld1.u8 {q3}, [%[p]], %[stride] \n" // q0 + "vld1.u8 {q12}, [%[p]] \n" // q1 + + DO_FILTER2(q1, q2, q3, q12, %[thresh]) + + "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride + + "vst1.u8 {q2}, [%[p]], %[stride] \n" // store op0 + "vst1.u8 {q3}, [%[p]] \n" // store oq0 + : [p] "+r"(p) + : [stride] "r"(stride), [thresh] "r"(thresh) + : "memory", QRegs + ); +} + +static void SimpleHFilter16_NEON(uint8_t* p, int stride, int thresh) { + __asm__ volatile ( + "sub r4, %[p], #2 \n" // base1 = p - 2 + "lsl r6, %[stride], #1 \n" // r6 = 2 * stride + "add r5, r4, %[stride] \n" // base2 = base1 + stride + + LOAD8x4(d2, d3, d4, d5, [r4], [r5], r6) + LOAD8x4(d24, d25, d26, d27, [r4], [r5], r6) + "vswp d3, d24 \n" // p1:q1 p0:q3 + "vswp d5, d26 \n" // q0:q2 q1:q4 + "vswp q2, q12 \n" // p1:q1 p0:q2 q0:q3 q1:q4 + + DO_FILTER2(q1, q2, q12, q13, %[thresh]) + + "sub %[p], %[p], #1 \n" // p - 1 + + "vswp d5, d24 \n" + STORE8x2(d4, d5, [%[p]], %[stride]) + STORE8x2(d24, d25, [%[p]], %[stride]) + + : [p] "+r"(p) + : [stride] "r"(stride), [thresh] "r"(thresh) + : "memory", "r4", "r5", "r6", QRegs + ); +} + +#undef LOAD8x4 +#undef STORE8x2 + +#endif // WEBP_USE_INTRINSICS + +static void SimpleVFilter16i_NEON(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { + p += 4 * stride; + SimpleVFilter16_NEON(p, stride, thresh); + } +} + +static void SimpleHFilter16i_NEON(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { + p += 4; + SimpleHFilter16_NEON(p, stride, thresh); + } +} + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +static uint8x16_t NeedsHev_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int hev_thresh) { + const uint8x16_t hev_thresh_v = vdupq_n_u8((uint8_t)hev_thresh); + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t a_max = vmaxq_u8(a_p1_p0, a_q1_q0); + const uint8x16_t mask = vcgtq_u8(a_max, hev_thresh_v); + return mask; +} + +static uint8x16_t NeedsFilter2_NEON(const uint8x16_t p3, const uint8x16_t p2, + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t q2, const uint8x16_t q3, + int ithresh, int thresh) { + const uint8x16_t ithresh_v = vdupq_n_u8((uint8_t)ithresh); + const uint8x16_t a_p3_p2 = vabdq_u8(p3, p2); // abs(p3 - p2) + const uint8x16_t a_p2_p1 = vabdq_u8(p2, p1); // abs(p2 - p1) + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q3_q2 = vabdq_u8(q3, q2); // abs(q3 - q2) + const uint8x16_t a_q2_q1 = vabdq_u8(q2, q1); // abs(q2 - q1) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t max1 = vmaxq_u8(a_p3_p2, a_p2_p1); + const uint8x16_t max2 = vmaxq_u8(a_p1_p0, a_q3_q2); + const uint8x16_t max3 = vmaxq_u8(a_q2_q1, a_q1_q0); + const uint8x16_t max12 = vmaxq_u8(max1, max2); + const uint8x16_t max123 = vmaxq_u8(max12, max3); + const uint8x16_t mask2 = vcgeq_u8(ithresh_v, max123); + const uint8x16_t mask1 = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + const uint8x16_t mask = vandq_u8(mask1, mask2); + return mask; +} + +// 4-points filter + +static void ApplyFilter4_NEON( + const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, + const int8x16_t delta0, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta1 = vqaddq_s8(delta0, kCst4); + const int8x16_t delta2 = vqaddq_s8(delta0, kCst3); + const int8x16_t a1 = vshrq_n_s8(delta1, 3); + const int8x16_t a2 = vshrq_n_s8(delta2, 3); + const int8x16_t a3 = vrshrq_n_s8(a1, 1); // a3 = (a1 + 1) >> 1 + *op0 = FlipSignBack_NEON(vqaddq_s8(p0, a2)); // clip(p0 + a2) + *oq0 = FlipSignBack_NEON(vqsubq_s8(q0, a1)); // clip(q0 - a1) + *op1 = FlipSignBack_NEON(vqaddq_s8(p1, a3)); // clip(p1 + a3) + *oq1 = FlipSignBack_NEON(vqsubq_s8(q1, a3)); // clip(q1 - a3) +} + +static void DoFilter4_NEON( + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p1s = FlipSign_NEON(p1); + int8x16_t p0s = FlipSign_NEON(p0); + int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t delta = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + const int8x16_t simple_lf_delta = + vandq_s8(delta, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip_NEON(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter4 part (complex loopfilter on pixels without hev) + { + const int8x16_t delta0 = GetBaseDelta0_NEON(p0s, q0s); + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter4_NEON(p1s, p0s, q0s, q1s, complex_lf_delta, op1, op0, oq0, oq1); + } +} + +// 6-points filter + +static void ApplyFilter6_NEON( + const int8x16_t p2, const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, const int8x16_t q2, + const int8x16_t delta, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // We have to compute: X = (9*a+63) >> 7, Y = (18*a+63)>>7, Z = (27*a+63) >> 7 + // Turns out, there's a common sub-expression S=9 * a - 1 that can be used + // with the special vqrshrn_n_s16 rounding-shift-and-narrow instruction: + // X = (S + 64) >> 7, Y = (S + 32) >> 6, Z = (18 * a + S + 64) >> 7 + const int8x8_t delta_lo = vget_low_s8(delta); + const int8x8_t delta_hi = vget_high_s8(delta); + const int8x8_t kCst9 = vdup_n_s8(9); + const int16x8_t kCstm1 = vdupq_n_s16(-1); + const int8x8_t kCst18 = vdup_n_s8(18); + const int16x8_t S_lo = vmlal_s8(kCstm1, kCst9, delta_lo); // S = 9 * a - 1 + const int16x8_t S_hi = vmlal_s8(kCstm1, kCst9, delta_hi); + const int16x8_t Z_lo = vmlal_s8(S_lo, kCst18, delta_lo); // S + 18 * a + const int16x8_t Z_hi = vmlal_s8(S_hi, kCst18, delta_hi); + const int8x8_t a3_lo = vqrshrn_n_s16(S_lo, 7); // (9 * a + 63) >> 7 + const int8x8_t a3_hi = vqrshrn_n_s16(S_hi, 7); + const int8x8_t a2_lo = vqrshrn_n_s16(S_lo, 6); // (9 * a + 31) >> 6 + const int8x8_t a2_hi = vqrshrn_n_s16(S_hi, 6); + const int8x8_t a1_lo = vqrshrn_n_s16(Z_lo, 7); // (27 * a + 63) >> 7 + const int8x8_t a1_hi = vqrshrn_n_s16(Z_hi, 7); + const int8x16_t a1 = vcombine_s8(a1_lo, a1_hi); + const int8x16_t a2 = vcombine_s8(a2_lo, a2_hi); + const int8x16_t a3 = vcombine_s8(a3_lo, a3_hi); + + *op0 = FlipSignBack_NEON(vqaddq_s8(p0, a1)); // clip(p0 + a1) + *oq0 = FlipSignBack_NEON(vqsubq_s8(q0, a1)); // clip(q0 - q1) + *oq1 = FlipSignBack_NEON(vqsubq_s8(q1, a2)); // clip(q1 - a2) + *op1 = FlipSignBack_NEON(vqaddq_s8(p1, a2)); // clip(p1 + a2) + *oq2 = FlipSignBack_NEON(vqsubq_s8(q2, a3)); // clip(q2 - a3) + *op2 = FlipSignBack_NEON(vqaddq_s8(p2, a3)); // clip(p2 + a3) +} + +static void DoFilter6_NEON( + const uint8x16_t p2, const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, const uint8x16_t q2, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p2s = FlipSign_NEON(p2); + const int8x16_t p1s = FlipSign_NEON(p1); + int8x16_t p0s = FlipSign_NEON(p0); + int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const int8x16_t q2s = FlipSign_NEON(q2); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + const int8x16_t delta0 = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t simple_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip_NEON(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter6 part (complex loopfilter on pixels without hev) + { + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter6_NEON(p2s, p1s, p0s, q0s, q1s, q2s, complex_lf_delta, + op2, op1, op0, oq0, oq1, oq2); + } +} + +// on macroblock edges + +static void VFilter16_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load16x8_NEON(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store16x2_NEON(op2, op1, p - 2 * stride, stride); + Store16x2_NEON(op0, oq0, p + 0 * stride, stride); + Store16x2_NEON(oq1, oq2, p + 2 * stride, stride); + } +} + +static void HFilter16_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x16_NEON(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store2x16_NEON(op2, op1, p - 2, stride); + Store2x16_NEON(op0, oq0, p + 0, stride); + Store2x16_NEON(oq1, oq2, p + 2, stride); + } +} + +// on three inner edges +static void VFilter16i_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load16x4_NEON(p + 2 * stride, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4 * stride; + Load16x4_NEON(p + 2 * stride, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store16x4_NEON(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter16i_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load4x16_NEON(p + 2, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4; + Load4x16_NEON(p + 2, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store4x16_NEON(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} +#endif // !WORK_AROUND_GCC + +// 8-pixels wide variant, for chroma filtering +static void VFilter8_NEON(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store8x2x2_NEON(op2, op1, u - 2 * stride, v - 2 * stride, stride); + Store8x2x2_NEON(op0, oq0, u + 0 * stride, v + 0 * stride, stride); + Store8x2x2_NEON(oq1, oq2, u + 2 * stride, v + 2 * stride, stride); + } +} +static void VFilter8i_NEON(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4 * stride; + v += 4 * stride; + Load8x8x2_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store8x4x2_NEON(op1, op0, oq0, oq1, u, v, stride); + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter8_NEON(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2T_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store6x8x2_NEON(op2, op1, op0, oq0, oq1, oq2, u, v, stride); + } +} + +static void HFilter8i_NEON(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4; + v += 4; + Load8x8x2T_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store4x8x2_NEON(op1, op0, oq0, oq1, u, v, stride); + } +} +#endif // !WORK_AROUND_GCC + +//----------------------------------------------------------------------------- +// Inverse transforms (Paragraph 14.4) + +// Technically these are unsigned but vqdmulh is only available in signed. +// vqdmulh returns high half (effectively >> 16) but also doubles the value, +// changing the >> 16 to >> 15 and requiring an additional >> 1. +// We use this to our advantage with kC2. The canonical value is 35468. +// However, the high bit is set so treating it as signed will give incorrect +// results. We avoid this by down shifting by 1 here to clear the highest bit. +// Combined with the doubling effect of vqdmulh we get >> 16. +// This can not be applied to kC1 because the lowest bit is set. Down shifting +// the constant would reduce precision. + +// libwebp uses a trick to avoid some extra addition that libvpx does. +// Instead of: +// temp2 = ip[12] + ((ip[12] * cospi8sqrt2minus1) >> 16); +// libwebp adds 1 << 16 to cospi8sqrt2minus1 (kC1). However, this causes the +// same issue with kC1 and vqdmulh that we work around by down shifting kC2 + +static const int16_t kC1 = WEBP_TRANSFORM_AC3_C1; +static const int16_t kC2 = + WEBP_TRANSFORM_AC3_C2 / 2; // half of kC2, actually. See comment above. + +#if defined(WEBP_USE_INTRINSICS) +static WEBP_INLINE void Transpose8x2_NEON(const int16x8_t in0, + const int16x8_t in1, + int16x8x2_t* const out) { + // a0 a1 a2 a3 | b0 b1 b2 b3 => a0 b0 c0 d0 | a1 b1 c1 d1 + // c0 c1 c2 c3 | d0 d1 d2 d3 a2 b2 c2 d2 | a3 b3 c3 d3 + const int16x8x2_t tmp0 = vzipq_s16(in0, in1); // a0 c0 a1 c1 a2 c2 ... + // b0 d0 b1 d1 b2 d2 ... + *out = vzipq_s16(tmp0.val[0], tmp0.val[1]); +} + +static WEBP_INLINE void TransformPass_NEON(int16x8x2_t* const rows) { + // {rows} = in0 | in4 + // in8 | in12 + // B1 = in4 | in12 + const int16x8_t B1 = + vcombine_s16(vget_high_s16(rows->val[0]), vget_high_s16(rows->val[1])); + // C0 = kC1 * in4 | kC1 * in12 + // C1 = kC2 * in4 | kC2 * in12 + const int16x8_t C0 = vsraq_n_s16(B1, vqdmulhq_n_s16(B1, kC1), 1); + const int16x8_t C1 = vqdmulhq_n_s16(B1, kC2); + const int16x4_t a = vqadd_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 + in8 + const int16x4_t b = vqsub_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 - in8 + // c = kC2 * in4 - kC1 * in12 + // d = kC1 * in4 + kC2 * in12 + const int16x4_t c = vqsub_s16(vget_low_s16(C1), vget_high_s16(C0)); + const int16x4_t d = vqadd_s16(vget_low_s16(C0), vget_high_s16(C1)); + const int16x8_t D0 = vcombine_s16(a, b); // D0 = a | b + const int16x8_t D1 = vcombine_s16(d, c); // D1 = d | c + const int16x8_t E0 = vqaddq_s16(D0, D1); // a+d | b+c + const int16x8_t E_tmp = vqsubq_s16(D0, D1); // a-d | b-c + const int16x8_t E1 = vcombine_s16(vget_high_s16(E_tmp), vget_low_s16(E_tmp)); + Transpose8x2_NEON(E0, E1, rows); +} + +static void TransformOne_NEON(const int16_t* in, uint8_t* dst) { + int16x8x2_t rows; + INIT_VECTOR2(rows, vld1q_s16(in + 0), vld1q_s16(in + 8)); + TransformPass_NEON(&rows); + TransformPass_NEON(&rows); + Add4x4_NEON(rows.val[0], rows.val[1], dst); +} + +#else + +static void TransformOne_NEON(const int16_t* in, uint8_t* dst) { + const int kBPS = BPS; + // kC1, kC2. Padded because vld1.16 loads 8 bytes + const int16_t constants[4] = { kC1, kC2, 0, 0 }; + /* Adapted from libvpx: vp8/common/arm/neon/shortidct4x4llm_neon.asm */ + __asm__ volatile ( + "vld1.16 {q1, q2}, [%[in]] \n" + "vld1.16 {d0}, [%[constants]] \n" + + /* d2: in[0] + * d3: in[8] + * d4: in[4] + * d5: in[12] + */ + "vswp d3, d4 \n" + + /* q8 = {in[4], in[12]} * kC1 * 2 >> 16 + * q9 = {in[4], in[12]} * kC2 >> 16 + */ + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + /* d22 = a = in[0] + in[8] + * d23 = b = in[0] - in[8] + */ + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + /* The multiplication should be x * kC1 >> 16 + * However, with vqdmulh we get x * kC1 * 2 >> 16 + * (multiply, double, return high half) + * We avoided this in kC2 by pre-shifting the constant. + * q8 = in[4]/[12] * kC1 >> 16 + */ + "vshr.s16 q8, q8, #1 \n" + + /* Add {in[4], in[12]} back after the multiplication. This is handled by + * adding 1 << 16 to kC1 in the libwebp C code. + */ + "vqadd.s16 q8, q2, q8 \n" + + /* d20 = c = in[4]*kC2 - in[12]*kC1 + * d21 = d = in[4]*kC1 + in[12]*kC2 + */ + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + /* d2 = tmp[0] = a + d + * d3 = tmp[1] = b + c + * d4 = tmp[2] = b - c + * d5 = tmp[3] = a - d + */ + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + "vswp d3, d4 \n" + + /* q8 = {tmp[4], tmp[12]} * kC1 * 2 >> 16 + * q9 = {tmp[4], tmp[12]} * kC2 >> 16 + */ + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + /* d22 = a = tmp[0] + tmp[8] + * d23 = b = tmp[0] - tmp[8] + */ + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + /* See long winded explanations prior */ + "vshr.s16 q8, q8, #1 \n" + "vqadd.s16 q8, q2, q8 \n" + + /* d20 = c = in[4]*kC2 - in[12]*kC1 + * d21 = d = in[4]*kC1 + in[12]*kC2 + */ + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + /* d2 = tmp[0] = a + d + * d3 = tmp[1] = b + c + * d4 = tmp[2] = b - c + * d5 = tmp[3] = a - d + */ + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vld1.32 d6[0], [%[dst]], %[kBPS] \n" + "vld1.32 d6[1], [%[dst]], %[kBPS] \n" + "vld1.32 d7[0], [%[dst]], %[kBPS] \n" + "vld1.32 d7[1], [%[dst]], %[kBPS] \n" + + "sub %[dst], %[dst], %[kBPS], lsl #2 \n" + + /* (val) + 4 >> 3 */ + "vrshr.s16 d2, d2, #3 \n" + "vrshr.s16 d3, d3, #3 \n" + "vrshr.s16 d4, d4, #3 \n" + "vrshr.s16 d5, d5, #3 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + /* Must accumulate before saturating */ + "vmovl.u8 q8, d6 \n" + "vmovl.u8 q9, d7 \n" + + "vqadd.s16 q1, q1, q8 \n" + "vqadd.s16 q2, q2, q9 \n" + + "vqmovun.s16 d0, q1 \n" + "vqmovun.s16 d1, q2 \n" + + "vst1.32 d0[0], [%[dst]], %[kBPS] \n" + "vst1.32 d0[1], [%[dst]], %[kBPS] \n" + "vst1.32 d1[0], [%[dst]], %[kBPS] \n" + "vst1.32 d1[1], [%[dst]] \n" + + : [in] "+r"(in), [dst] "+r"(dst) /* modified registers */ + : [kBPS] "r"(kBPS), [constants] "r"(constants) /* constants */ + : "memory", "q0", "q1", "q2", "q8", "q9", "q10", "q11" /* clobbered */ + ); +} + +#endif // WEBP_USE_INTRINSICS + +static void TransformTwo_NEON(const int16_t* in, uint8_t* dst, int do_two) { + TransformOne_NEON(in, dst); + if (do_two) { + TransformOne_NEON(in + 16, dst + 4); + } +} + +static void TransformDC_NEON(const int16_t* in, uint8_t* dst) { + const int16x8_t DC = vdupq_n_s16(in[0]); + Add4x4_NEON(DC, DC, dst); +} + +//------------------------------------------------------------------------------ + +#define STORE_WHT(dst, col, rows) do { \ + *dst = vgetq_lane_s32(rows.val[0], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[1], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[2], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[3], col); (dst) += 16; \ +} while (0) + +static void TransformWHT_NEON(const int16_t* in, int16_t* out) { + int32x4x4_t tmp; + + { + // Load the source. + const int16x4_t in00_03 = vld1_s16(in + 0); + const int16x4_t in04_07 = vld1_s16(in + 4); + const int16x4_t in08_11 = vld1_s16(in + 8); + const int16x4_t in12_15 = vld1_s16(in + 12); + const int32x4_t a0 = vaddl_s16(in00_03, in12_15); // in[0..3] + in[12..15] + const int32x4_t a1 = vaddl_s16(in04_07, in08_11); // in[4..7] + in[8..11] + const int32x4_t a2 = vsubl_s16(in04_07, in08_11); // in[4..7] - in[8..11] + const int32x4_t a3 = vsubl_s16(in00_03, in12_15); // in[0..3] - in[12..15] + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + // Arrange the temporary results column-wise. + tmp = Transpose4x4_NEON(tmp); + } + + { + const int32x4_t kCst3 = vdupq_n_s32(3); + const int32x4_t dc = vaddq_s32(tmp.val[0], kCst3); // add rounder + const int32x4_t a0 = vaddq_s32(dc, tmp.val[3]); + const int32x4_t a1 = vaddq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a2 = vsubq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a3 = vsubq_s32(dc, tmp.val[3]); + + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + + // right shift the results by 3. + tmp.val[0] = vshrq_n_s32(tmp.val[0], 3); + tmp.val[1] = vshrq_n_s32(tmp.val[1], 3); + tmp.val[2] = vshrq_n_s32(tmp.val[2], 3); + tmp.val[3] = vshrq_n_s32(tmp.val[3], 3); + + STORE_WHT(out, 0, tmp); + STORE_WHT(out, 1, tmp); + STORE_WHT(out, 2, tmp); + STORE_WHT(out, 3, tmp); + } +} + +#undef STORE_WHT + +//------------------------------------------------------------------------------ + +static void TransformAC3_NEON(const int16_t* in, uint8_t* dst) { + const int16x4_t A = vld1_dup_s16(in); + const int16x4_t c4 = vdup_n_s16(WEBP_TRANSFORM_AC3_MUL2(in[4])); + const int16x4_t d4 = vdup_n_s16(WEBP_TRANSFORM_AC3_MUL1(in[4])); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + const uint64_t cd = (uint64_t)( d1 & 0xffff) << 0 | + (uint64_t)( c1 & 0xffff) << 16 | + (uint64_t)(-c1 & 0xffff) << 32 | + (uint64_t)(-d1 & 0xffff) << 48; + const int16x4_t CD = vcreate_s16(cd); + const int16x4_t B = vqadd_s16(A, CD); + const int16x8_t m0_m1 = vcombine_s16(vqadd_s16(B, d4), vqadd_s16(B, c4)); + const int16x8_t m2_m3 = vcombine_s16(vqsub_s16(B, c4), vqsub_s16(B, d4)); + Add4x4_NEON(m0_m1, m2_m3, dst); +} + +//------------------------------------------------------------------------------ +// 4x4 + +static void DC4_NEON(uint8_t* dst) { // DC + const uint8x8_t A = vld1_u8(dst - BPS); // top row + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint8x8_t L0 = vld1_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + 3 * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t sum = vaddq_u16(s01, vcombine_u16(p1, p1)); + const uint8x8_t dc0 = vrshrn_n_u16(sum, 3); // (sum + 4) >> 3 + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc), 0); + } +} + +// TrueMotion (4x4 + 8x8) +static WEBP_INLINE void TrueMotion_NEON(uint8_t* dst, int size) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x8_t T = vld1_u8(dst - BPS); // top row 'A[0..3]' + const int16x8_t d = vreinterpretq_s16_u16(vsubl_u8(T, TL)); // A[c] - A[-1] + int y; + for (y = 0; y < size; y += 4) { + // left edge + const int16x8_t L0 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 0 * BPS - 1)); + const int16x8_t L1 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 1 * BPS - 1)); + const int16x8_t L2 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 2 * BPS - 1)); + const int16x8_t L3 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 3 * BPS - 1)); + const int16x8_t r0 = vaddq_s16(L0, d); // L[r] + A[c] - A[-1] + const int16x8_t r1 = vaddq_s16(L1, d); + const int16x8_t r2 = vaddq_s16(L2, d); + const int16x8_t r3 = vaddq_s16(L3, d); + // Saturate and store the result. + const uint32x2_t r0_u32 = vreinterpret_u32_u8(vqmovun_s16(r0)); + const uint32x2_t r1_u32 = vreinterpret_u32_u8(vqmovun_s16(r1)); + const uint32x2_t r2_u32 = vreinterpret_u32_u8(vqmovun_s16(r2)); + const uint32x2_t r3_u32 = vreinterpret_u32_u8(vqmovun_s16(r3)); + if (size == 4) { + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3_u32, 0); + } else { + vst1_u32((uint32_t*)(dst + 0 * BPS), r0_u32); + vst1_u32((uint32_t*)(dst + 1 * BPS), r1_u32); + vst1_u32((uint32_t*)(dst + 2 * BPS), r2_u32); + vst1_u32((uint32_t*)(dst + 3 * BPS), r3_u32); + } + dst += 4 * BPS; + } +} + +static void TM4_NEON(uint8_t* dst) { TrueMotion_NEON(dst, 4); } + +static void VE4_NEON(uint8_t* dst) { // vertical + // NB: avoid vld1_u64 here as an alignment hint may be added -> SIGBUS. + const uint64x1_t A0 = vreinterpret_u64_u8(vld1_u8(dst - BPS - 1)); // top row + const uint64x1_t A1 = vshr_n_u64(A0, 8); + const uint64x1_t A2 = vshr_n_u64(A0, 16); + const uint8x8_t ABCDEFGH = vreinterpret_u8_u64(A0); + const uint8x8_t BCDEFGH0 = vreinterpret_u8_u64(A1); + const uint8x8_t CDEFGH00 = vreinterpret_u8_u64(A2); + const uint8x8_t b = vhadd_u8(ABCDEFGH, CDEFGH00); + const uint8x8_t avg = vrhadd_u8(b, BCDEFGH0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(avg), 0); + } +} + +static void RD4_NEON(uint8_t* dst) { // Down-right + const uint8x8_t XABCD_u8 = vld1_u8(dst - BPS - 1); + const uint64x1_t XABCD = vreinterpret_u64_u8(XABCD_u8); + const uint64x1_t ____XABC = vshl_n_u64(XABCD, 32); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const uint64x1_t LKJI____ = + vcreate_u64((uint64_t)L | (K << 8) | (J << 16) | (I << 24)); + const uint64x1_t LKJIXABC = vorr_u64(LKJI____, ____XABC); + const uint8x8_t KJIXABC_ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 8)); + const uint8x8_t JIXABC__ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 16)); + const uint8_t D = vget_lane_u8(XABCD_u8, 4); + const uint8x8_t JIXABCD_ = vset_lane_u8(D, JIXABC__, 6); + const uint8x8_t LKJIXABC_u8 = vreinterpret_u8_u64(LKJIXABC); + const uint8x8_t avg1 = vhadd_u8(JIXABCD_, LKJIXABC_u8); + const uint8x8_t avg2 = vrhadd_u8(avg1, KJIXABC_); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r3 = vreinterpret_u32_u8(avg2); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r0 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +static void LD4_NEON(uint8_t* dst) { // Down-left + // Note using the same shift trick as VE4() is slower here. + const uint8x8_t ABCDEFGH = vld1_u8(dst - BPS + 0); + const uint8x8_t BCDEFGH0 = vld1_u8(dst - BPS + 1); + const uint8x8_t CDEFGH00 = vld1_u8(dst - BPS + 2); + const uint8x8_t CDEFGHH0 = vset_lane_u8(dst[-BPS + 7], CDEFGH00, 6); + const uint8x8_t avg1 = vhadd_u8(ABCDEFGH, CDEFGHH0); + const uint8x8_t avg2 = vrhadd_u8(avg1, BCDEFGH0); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r0 = vreinterpret_u32_u8(avg2); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r3 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv_NEON(uint8_t* dst) { // vertical + const uint8x8_t top = vld1_u8(dst - BPS); + int j; + for (j = 0; j < 8; ++j) { + vst1_u8(dst + j * BPS, top); + } +} + +static void HE8uv_NEON(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + const uint8x8_t left = vld1_dup_u8(dst - 1); + vst1_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC8_NEON(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x8_t A = vld1_u8(dst - BPS); // top row +#if WEBP_AARCH64 + const uint16_t p2 = vaddlv_u8(A); + sum_top = vdupq_n_u16(p2); +#else + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint16x4_t p2 = vpadd_u16(p1, p1); + sum_top = vcombine_u16(p2, p2); +#endif + } + + if (do_left) { + const uint8x8_t L0 = vld1_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + 3 * BPS - 1); + const uint8x8_t L4 = vld1_u8(dst + 4 * BPS - 1); + const uint8x8_t L5 = vld1_u8(dst + 5 * BPS - 1); + const uint8x8_t L6 = vld1_u8(dst + 6 * BPS - 1); + const uint8x8_t L7 = vld1_u8(dst + 7 * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s2 = vaddl_u8(L4, L5); + const uint16x8_t s3 = vaddl_u8(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + sum_left = vaddq_u16(s01, s23); + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 4); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 3); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 3); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 8; ++i) { + vst1_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc)); + } + } +} + +static void DC8uv_NEON(uint8_t* dst) { DC8_NEON(dst, 1, 1); } +static void DC8uvNoTop_NEON(uint8_t* dst) { DC8_NEON(dst, 0, 1); } +static void DC8uvNoLeft_NEON(uint8_t* dst) { DC8_NEON(dst, 1, 0); } +static void DC8uvNoTopLeft_NEON(uint8_t* dst) { DC8_NEON(dst, 0, 0); } + +static void TM8uv_NEON(uint8_t* dst) { TrueMotion_NEON(dst, 8); } + +//------------------------------------------------------------------------------ +// 16x16 + +static void VE16_NEON(uint8_t* dst) { // vertical + const uint8x16_t top = vld1q_u8(dst - BPS); + int j; + for (j = 0; j < 16; ++j) { + vst1q_u8(dst + j * BPS, top); + } +} + +static void HE16_NEON(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 16; ++j) { + const uint8x16_t left = vld1q_dup_u8(dst - 1); + vst1q_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC16_NEON(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x16_t A = vld1q_u8(dst - BPS); // top row +#if WEBP_AARCH64 + const uint16_t p3 = vaddlvq_u8(A); + sum_top = vdupq_n_u16(p3); +#else + const uint16x8_t p0 = vpaddlq_u8(A); // cascading summation of the top + const uint16x4_t p1 = vadd_u16(vget_low_u16(p0), vget_high_u16(p0)); + const uint16x4_t p2 = vpadd_u16(p1, p1); + const uint16x4_t p3 = vpadd_u16(p2, p2); + sum_top = vcombine_u16(p3, p3); +#endif + } + + if (do_left) { + int i; + sum_left = vdupq_n_u16(0); + for (i = 0; i < 16; i += 8) { + const uint8x8_t L0 = vld1_u8(dst + (i + 0) * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + (i + 1) * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + (i + 2) * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + (i + 3) * BPS - 1); + const uint8x8_t L4 = vld1_u8(dst + (i + 4) * BPS - 1); + const uint8x8_t L5 = vld1_u8(dst + (i + 5) * BPS - 1); + const uint8x8_t L6 = vld1_u8(dst + (i + 6) * BPS - 1); + const uint8x8_t L7 = vld1_u8(dst + (i + 7) * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s2 = vaddl_u8(L4, L5); + const uint16x8_t s3 = vaddl_u8(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + const uint16x8_t sum = vaddq_u16(s01, s23); + sum_left = vaddq_u16(sum_left, sum); + } + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 5); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 4); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 4); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x16_t dc = vdupq_lane_u8(dc0, 0); + int i; + for (i = 0; i < 16; ++i) { + vst1q_u8(dst + i * BPS, dc); + } + } +} + +static void DC16TopLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 1, 1); } +static void DC16NoTop_NEON(uint8_t* dst) { DC16_NEON(dst, 0, 1); } +static void DC16NoLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 1, 0); } +static void DC16NoTopLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 0, 0); } + +static void TM16_NEON(uint8_t* dst) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x16_t T = vld1q_u8(dst - BPS); // top row 'A[0..15]' + // A[c] - A[-1] + const int16x8_t d_lo = vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(T), TL)); + const int16x8_t d_hi = vreinterpretq_s16_u16(vsubl_u8(vget_high_u8(T), TL)); + int y; + for (y = 0; y < 16; y += 4) { + // left edge + const int16x8_t L0 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 0 * BPS - 1)); + const int16x8_t L1 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 1 * BPS - 1)); + const int16x8_t L2 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 2 * BPS - 1)); + const int16x8_t L3 = ConvertU8ToS16_NEON(vld1_dup_u8(dst + 3 * BPS - 1)); + const int16x8_t r0_lo = vaddq_s16(L0, d_lo); // L[r] + A[c] - A[-1] + const int16x8_t r1_lo = vaddq_s16(L1, d_lo); + const int16x8_t r2_lo = vaddq_s16(L2, d_lo); + const int16x8_t r3_lo = vaddq_s16(L3, d_lo); + const int16x8_t r0_hi = vaddq_s16(L0, d_hi); + const int16x8_t r1_hi = vaddq_s16(L1, d_hi); + const int16x8_t r2_hi = vaddq_s16(L2, d_hi); + const int16x8_t r3_hi = vaddq_s16(L3, d_hi); + // Saturate and store the result. + const uint8x16_t row0 = vcombine_u8(vqmovun_s16(r0_lo), vqmovun_s16(r0_hi)); + const uint8x16_t row1 = vcombine_u8(vqmovun_s16(r1_lo), vqmovun_s16(r1_hi)); + const uint8x16_t row2 = vcombine_u8(vqmovun_s16(r2_lo), vqmovun_s16(r2_hi)); + const uint8x16_t row3 = vcombine_u8(vqmovun_s16(r3_lo), vqmovun_s16(r3_hi)); + vst1q_u8(dst + 0 * BPS, row0); + vst1q_u8(dst + 1 * BPS, row1); + vst1q_u8(dst + 2 * BPS, row2); + vst1q_u8(dst + 3 * BPS, row3); + dst += 4 * BPS; + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitNEON(void) { + VP8Transform = TransformTwo_NEON; + VP8TransformAC3 = TransformAC3_NEON; + VP8TransformDC = TransformDC_NEON; + VP8TransformWHT = TransformWHT_NEON; + + VP8VFilter16 = VFilter16_NEON; + VP8VFilter16i = VFilter16i_NEON; + VP8HFilter16 = HFilter16_NEON; +#if !defined(WORK_AROUND_GCC) + VP8HFilter16i = HFilter16i_NEON; +#endif + VP8VFilter8 = VFilter8_NEON; + VP8VFilter8i = VFilter8i_NEON; +#if !defined(WORK_AROUND_GCC) + VP8HFilter8 = HFilter8_NEON; + VP8HFilter8i = HFilter8i_NEON; +#endif + VP8SimpleVFilter16 = SimpleVFilter16_NEON; + VP8SimpleHFilter16 = SimpleHFilter16_NEON; + VP8SimpleVFilter16i = SimpleVFilter16i_NEON; + VP8SimpleHFilter16i = SimpleHFilter16i_NEON; + + VP8PredLuma4[0] = DC4_NEON; + VP8PredLuma4[1] = TM4_NEON; + VP8PredLuma4[2] = VE4_NEON; + VP8PredLuma4[4] = RD4_NEON; + VP8PredLuma4[6] = LD4_NEON; + + VP8PredLuma16[0] = DC16TopLeft_NEON; + VP8PredLuma16[1] = TM16_NEON; + VP8PredLuma16[2] = VE16_NEON; + VP8PredLuma16[3] = HE16_NEON; + VP8PredLuma16[4] = DC16NoTop_NEON; + VP8PredLuma16[5] = DC16NoLeft_NEON; + VP8PredLuma16[6] = DC16NoTopLeft_NEON; + + VP8PredChroma8[0] = DC8uv_NEON; + VP8PredChroma8[1] = TM8uv_NEON; + VP8PredChroma8[2] = VE8uv_NEON; + VP8PredChroma8[3] = HE8uv_NEON; + VP8PredChroma8[4] = DC8uvNoTop_NEON; + VP8PredChroma8[5] = DC8uvNoLeft_NEON; + VP8PredChroma8[6] = DC8uvNoTopLeft_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8DspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse2.c new file mode 100644 index 0000000000..ff3a28555b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse2.c @@ -0,0 +1,1226 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of some decoding functions (idct, loop filtering). +// +// Author: somnath@google.com (Somnath Banerjee) +// cduvivier@google.com (Christian Duvivier) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +// The 3-coeff sparse transform in SSE2 is not really faster than the plain-C +// one it seems => disable it by default. Uncomment the following to enable: +#if !defined(USE_TRANSFORM_AC3) +#define USE_TRANSFORM_AC3 0 // ALTERNATE_CODE +#endif + +#include +#include "src/dsp/common_sse2.h" +#include "src/dec/vp8i_dec.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +static void Transform_SSE2(const int16_t* in, uint8_t* dst, int do_two) { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + const __m128i k1 = _mm_set1_epi16(20091); + const __m128i k2 = _mm_set1_epi16(-30068); + __m128i T0, T1, T2, T3; + + // Load and concatenate the transform coefficients (we'll do two transforms + // in parallel). In the case of only one transform, the second half of the + // vectors will just contain random value we'll never use nor store. + __m128i in0, in1, in2, in3; + { + in0 = _mm_loadl_epi64((const __m128i*)&in[0]); + in1 = _mm_loadl_epi64((const __m128i*)&in[4]); + in2 = _mm_loadl_epi64((const __m128i*)&in[8]); + in3 = _mm_loadl_epi64((const __m128i*)&in[12]); + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + if (do_two) { + const __m128i inB0 = _mm_loadl_epi64((const __m128i*)&in[16]); + const __m128i inB1 = _mm_loadl_epi64((const __m128i*)&in[20]); + const __m128i inB2 = _mm_loadl_epi64((const __m128i*)&in[24]); + const __m128i inB3 = _mm_loadl_epi64((const __m128i*)&in[28]); + in0 = _mm_unpacklo_epi64(in0, inB0); + in1 = _mm_unpacklo_epi64(in1, inB1); + in2 = _mm_unpacklo_epi64(in2, inB2); + in3 = _mm_unpacklo_epi64(in3, inB3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + } + } + + // Vertical pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i a = _mm_add_epi16(in0, in2); + const __m128i b = _mm_sub_epi16(in0, in2); + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + const __m128i c1 = _mm_mulhi_epi16(in1, k2); + const __m128i c2 = _mm_mulhi_epi16(in3, k1); + const __m128i c3 = _mm_sub_epi16(in1, in3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + const __m128i d1 = _mm_mulhi_epi16(in1, k1); + const __m128i d2 = _mm_mulhi_epi16(in3, k2); + const __m128i d3 = _mm_add_epi16(in1, in3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&tmp0, &tmp1, &tmp2, &tmp3, &T0, &T1, &T2, &T3); + } + + // Horizontal pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i four = _mm_set1_epi16(4); + const __m128i dc = _mm_add_epi16(T0, four); + const __m128i a = _mm_add_epi16(dc, T2); + const __m128i b = _mm_sub_epi16(dc, T2); + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + const __m128i c1 = _mm_mulhi_epi16(T1, k2); + const __m128i c2 = _mm_mulhi_epi16(T3, k1); + const __m128i c3 = _mm_sub_epi16(T1, T3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + const __m128i d1 = _mm_mulhi_epi16(T1, k1); + const __m128i d2 = _mm_mulhi_epi16(T3, k2); + const __m128i d3 = _mm_add_epi16(T1, T3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + const __m128i shifted0 = _mm_srai_epi16(tmp0, 3); + const __m128i shifted1 = _mm_srai_epi16(tmp1, 3); + const __m128i shifted2 = _mm_srai_epi16(tmp2, 3); + const __m128i shifted3 = _mm_srai_epi16(tmp3, 3); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&shifted0, &shifted1, &shifted2, &shifted3, &T0, &T1, + &T2, &T3); + } + + // Add inverse transform to 'dst' and store. + { + const __m128i zero = _mm_setzero_si128(); + // Load the reference(s). + __m128i dst0, dst1, dst2, dst3; + if (do_two) { + // Load eight bytes/pixels per line. + dst0 = _mm_loadl_epi64((__m128i*)(dst + 0 * BPS)); + dst1 = _mm_loadl_epi64((__m128i*)(dst + 1 * BPS)); + dst2 = _mm_loadl_epi64((__m128i*)(dst + 2 * BPS)); + dst3 = _mm_loadl_epi64((__m128i*)(dst + 3 * BPS)); + } else { + // Load four bytes/pixels per line. + dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); + } + // Convert to 16b. + dst0 = _mm_unpacklo_epi8(dst0, zero); + dst1 = _mm_unpacklo_epi8(dst1, zero); + dst2 = _mm_unpacklo_epi8(dst2, zero); + dst3 = _mm_unpacklo_epi8(dst3, zero); + // Add the inverse transform(s). + dst0 = _mm_add_epi16(dst0, T0); + dst1 = _mm_add_epi16(dst1, T1); + dst2 = _mm_add_epi16(dst2, T2); + dst3 = _mm_add_epi16(dst3, T3); + // Unsigned saturate to 8b. + dst0 = _mm_packus_epi16(dst0, dst0); + dst1 = _mm_packus_epi16(dst1, dst1); + dst2 = _mm_packus_epi16(dst2, dst2); + dst3 = _mm_packus_epi16(dst3, dst3); + // Store the results. + if (do_two) { + // Store eight bytes/pixels per line. + _mm_storel_epi64((__m128i*)(dst + 0 * BPS), dst0); + _mm_storel_epi64((__m128i*)(dst + 1 * BPS), dst1); + _mm_storel_epi64((__m128i*)(dst + 2 * BPS), dst2); + _mm_storel_epi64((__m128i*)(dst + 3 * BPS), dst3); + } else { + // Store four bytes/pixels per line. + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); + } + } +} + +#if (USE_TRANSFORM_AC3 == 1) + +static void TransformAC3(const int16_t* in, uint8_t* dst) { + const __m128i A = _mm_set1_epi16(in[0] + 4); + const __m128i c4 = _mm_set1_epi16(WEBP_TRANSFORM_AC3_MUL2(in[4])); + const __m128i d4 = _mm_set1_epi16(WEBP_TRANSFORM_AC3_MUL1(in[4])); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + const __m128i CD = _mm_set_epi16(0, 0, 0, 0, -d1, -c1, c1, d1); + const __m128i B = _mm_adds_epi16(A, CD); + const __m128i m0 = _mm_adds_epi16(B, d4); + const __m128i m1 = _mm_adds_epi16(B, c4); + const __m128i m2 = _mm_subs_epi16(B, c4); + const __m128i m3 = _mm_subs_epi16(B, d4); + const __m128i zero = _mm_setzero_si128(); + // Load the source pixels. + __m128i dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + __m128i dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + __m128i dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + __m128i dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); + // Convert to 16b. + dst0 = _mm_unpacklo_epi8(dst0, zero); + dst1 = _mm_unpacklo_epi8(dst1, zero); + dst2 = _mm_unpacklo_epi8(dst2, zero); + dst3 = _mm_unpacklo_epi8(dst3, zero); + // Add the inverse transform. + dst0 = _mm_adds_epi16(dst0, _mm_srai_epi16(m0, 3)); + dst1 = _mm_adds_epi16(dst1, _mm_srai_epi16(m1, 3)); + dst2 = _mm_adds_epi16(dst2, _mm_srai_epi16(m2, 3)); + dst3 = _mm_adds_epi16(dst3, _mm_srai_epi16(m3, 3)); + // Unsigned saturate to 8b. + dst0 = _mm_packus_epi16(dst0, dst0); + dst1 = _mm_packus_epi16(dst1, dst1); + dst2 = _mm_packus_epi16(dst2, dst2); + dst3 = _mm_packus_epi16(dst3, dst3); + // Store the results. + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); +} + +#endif // USE_TRANSFORM_AC3 + +//------------------------------------------------------------------------------ +// Loop Filter (Paragraph 15) + +// Compute abs(p - q) = subs(p - q) OR subs(q - p) +#define MM_ABS(p, q) _mm_or_si128( \ + _mm_subs_epu8((q), (p)), \ + _mm_subs_epu8((p), (q))) + +// Shift each byte of "x" by 3 bits while preserving by the sign bit. +static WEBP_INLINE void SignedShift8b_SSE2(__m128i* const x) { + const __m128i zero = _mm_setzero_si128(); + const __m128i lo_0 = _mm_unpacklo_epi8(zero, *x); + const __m128i hi_0 = _mm_unpackhi_epi8(zero, *x); + const __m128i lo_1 = _mm_srai_epi16(lo_0, 3 + 8); + const __m128i hi_1 = _mm_srai_epi16(hi_0, 3 + 8); + *x = _mm_packs_epi16(lo_1, hi_1); +} + +#define FLIP_SIGN_BIT2(a, b) do { \ + (a) = _mm_xor_si128(a, sign_bit); \ + (b) = _mm_xor_si128(b, sign_bit); \ +} while (0) + +#define FLIP_SIGN_BIT4(a, b, c, d) do { \ + FLIP_SIGN_BIT2(a, b); \ + FLIP_SIGN_BIT2(c, d); \ +} while (0) + +// input/output is uint8_t +static WEBP_INLINE void GetNotHEV_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int hev_thresh, __m128i* const not_hev) { + const __m128i zero = _mm_setzero_si128(); + const __m128i t_1 = MM_ABS(*p1, *p0); + const __m128i t_2 = MM_ABS(*q1, *q0); + + const __m128i h = _mm_set1_epi8(hev_thresh); + const __m128i t_max = _mm_max_epu8(t_1, t_2); + + const __m128i t_max_h = _mm_subs_epu8(t_max, h); + *not_hev = _mm_cmpeq_epi8(t_max_h, zero); // not_hev <= t1 && not_hev <= t2 +} + +// input pixels are int8_t +static WEBP_INLINE void GetBaseDelta_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + __m128i* const delta) { + // beware of addition order, for saturation! + const __m128i p1_q1 = _mm_subs_epi8(*p1, *q1); // p1 - q1 + const __m128i q0_p0 = _mm_subs_epi8(*q0, *p0); // q0 - p0 + const __m128i s1 = _mm_adds_epi8(p1_q1, q0_p0); // p1 - q1 + 1 * (q0 - p0) + const __m128i s2 = _mm_adds_epi8(q0_p0, s1); // p1 - q1 + 2 * (q0 - p0) + const __m128i s3 = _mm_adds_epi8(q0_p0, s2); // p1 - q1 + 3 * (q0 - p0) + *delta = s3; +} + +// input and output are int8_t +static WEBP_INLINE void DoSimpleFilter_SSE2(__m128i* const p0, + __m128i* const q0, + const __m128i* const fl) { + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); + __m128i v3 = _mm_adds_epi8(*fl, k3); + __m128i v4 = _mm_adds_epi8(*fl, k4); + + SignedShift8b_SSE2(&v4); // v4 >> 3 + SignedShift8b_SSE2(&v3); // v3 >> 3 + *q0 = _mm_subs_epi8(*q0, v4); // q0 -= v4 + *p0 = _mm_adds_epi8(*p0, v3); // p0 += v3 +} + +// Updates values of 2 pixels at MB edge during complex filtering. +// Update operations: +// q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] +// Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). +static WEBP_INLINE void Update2Pixels_SSE2(__m128i* const pi, __m128i* const qi, + const __m128i* const a0_lo, + const __m128i* const a0_hi) { + const __m128i a1_lo = _mm_srai_epi16(*a0_lo, 7); + const __m128i a1_hi = _mm_srai_epi16(*a0_hi, 7); + const __m128i delta = _mm_packs_epi16(a1_lo, a1_hi); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + *pi = _mm_adds_epi8(*pi, delta); + *qi = _mm_subs_epi8(*qi, delta); + FLIP_SIGN_BIT2(*pi, *qi); +} + +// input pixels are uint8_t +static WEBP_INLINE void NeedsFilter_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, __m128i* const mask) { + const __m128i m_thresh = _mm_set1_epi8((char)thresh); + const __m128i t1 = MM_ABS(*p1, *q1); // abs(p1 - q1) + const __m128i kFE = _mm_set1_epi8((char)0xFE); + const __m128i t2 = _mm_and_si128(t1, kFE); // set lsb of each byte to zero + const __m128i t3 = _mm_srli_epi16(t2, 1); // abs(p1 - q1) / 2 + + const __m128i t4 = MM_ABS(*p0, *q0); // abs(p0 - q0) + const __m128i t5 = _mm_adds_epu8(t4, t4); // abs(p0 - q0) * 2 + const __m128i t6 = _mm_adds_epu8(t5, t3); // abs(p0-q0)*2 + abs(p1-q1)/2 + + const __m128i t7 = _mm_subs_epu8(t6, m_thresh); // mask <= m_thresh + *mask = _mm_cmpeq_epi8(t7, _mm_setzero_si128()); +} + +//------------------------------------------------------------------------------ +// Edge filtering functions + +// Applies filter on 2 pixels (p0 and q0) +static WEBP_INLINE void DoFilter2_SSE2(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + int thresh) { + __m128i a, mask; + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + // convert p1/q1 to int8_t (for GetBaseDelta_SSE2) + const __m128i p1s = _mm_xor_si128(*p1, sign_bit); + const __m128i q1s = _mm_xor_si128(*q1, sign_bit); + + NeedsFilter_SSE2(p1, p0, q0, q1, thresh, &mask); + + FLIP_SIGN_BIT2(*p0, *q0); + GetBaseDelta_SSE2(&p1s, p0, q0, &q1s, &a); + a = _mm_and_si128(a, mask); // mask filter values we don't care about + DoSimpleFilter_SSE2(p0, q0, &a); + FLIP_SIGN_BIT2(*p0, *q0); +} + +// Applies filter on 4 pixels (p1, p0, q0 and q1) +static WEBP_INLINE void DoFilter4_SSE2(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + const __m128i* const mask, + int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + const __m128i k64 = _mm_set1_epi8(64); + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); + __m128i not_hev; + __m128i t1, t2, t3; + + // compute hev mask + GetNotHEV_SSE2(p1, p0, q0, q1, hev_thresh, ¬_hev); + + // convert to signed values + FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); + + t1 = _mm_subs_epi8(*p1, *q1); // p1 - q1 + t1 = _mm_andnot_si128(not_hev, t1); // hev(p1 - q1) + t2 = _mm_subs_epi8(*q0, *p0); // q0 - p0 + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) + t1 = _mm_and_si128(t1, *mask); // mask filter values we don't care about + + t2 = _mm_adds_epi8(t1, k3); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + t3 = _mm_adds_epi8(t1, k4); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + SignedShift8b_SSE2(&t2); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + SignedShift8b_SSE2(&t3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + *p0 = _mm_adds_epi8(*p0, t2); // p0 += t2 + *q0 = _mm_subs_epi8(*q0, t3); // q0 -= t3 + FLIP_SIGN_BIT2(*p0, *q0); + + // this is equivalent to signed (a + 1) >> 1 calculation + t2 = _mm_add_epi8(t3, sign_bit); + t3 = _mm_avg_epu8(t2, zero); + t3 = _mm_sub_epi8(t3, k64); + + t3 = _mm_and_si128(not_hev, t3); // if !hev + *q1 = _mm_subs_epi8(*q1, t3); // q1 -= t3 + *p1 = _mm_adds_epi8(*p1, t3); // p1 += t3 + FLIP_SIGN_BIT2(*p1, *q1); +} + +// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) +static WEBP_INLINE void DoFilter6_SSE2(__m128i* const p2, __m128i* const p1, + __m128i* const p0, __m128i* const q0, + __m128i* const q1, __m128i* const q2, + const __m128i* const mask, + int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + __m128i a, not_hev; + + // compute hev mask + GetNotHEV_SSE2(p1, p0, q0, q1, hev_thresh, ¬_hev); + + FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); + FLIP_SIGN_BIT2(*p2, *q2); + GetBaseDelta_SSE2(p1, p0, q0, q1, &a); + + { // do simple filter on pixels with hev + const __m128i m = _mm_andnot_si128(not_hev, *mask); + const __m128i f = _mm_and_si128(a, m); + DoSimpleFilter_SSE2(p0, q0, &f); + } + + { // do strong filter on pixels with not hev + const __m128i k9 = _mm_set1_epi16(0x0900); + const __m128i k63 = _mm_set1_epi16(63); + + const __m128i m = _mm_and_si128(not_hev, *mask); + const __m128i f = _mm_and_si128(a, m); + + const __m128i f_lo = _mm_unpacklo_epi8(zero, f); + const __m128i f_hi = _mm_unpackhi_epi8(zero, f); + + const __m128i f9_lo = _mm_mulhi_epi16(f_lo, k9); // Filter (lo) * 9 + const __m128i f9_hi = _mm_mulhi_epi16(f_hi, k9); // Filter (hi) * 9 + + const __m128i a2_lo = _mm_add_epi16(f9_lo, k63); // Filter * 9 + 63 + const __m128i a2_hi = _mm_add_epi16(f9_hi, k63); // Filter * 9 + 63 + + const __m128i a1_lo = _mm_add_epi16(a2_lo, f9_lo); // Filter * 18 + 63 + const __m128i a1_hi = _mm_add_epi16(a2_hi, f9_hi); // Filter * 18 + 63 + + const __m128i a0_lo = _mm_add_epi16(a1_lo, f9_lo); // Filter * 27 + 63 + const __m128i a0_hi = _mm_add_epi16(a1_hi, f9_hi); // Filter * 27 + 63 + + Update2Pixels_SSE2(p2, q2, &a2_lo, &a2_hi); + Update2Pixels_SSE2(p1, q1, &a1_lo, &a1_hi); + Update2Pixels_SSE2(p0, q0, &a0_lo, &a0_hi); + } +} + +// reads 8 rows across a vertical edge. +static WEBP_INLINE void Load8x4_SSE2(const uint8_t* const b, int stride, + __m128i* const p, __m128i* const q) { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + const __m128i A0 = _mm_set_epi32( + WebPMemToInt32(&b[6 * stride]), WebPMemToInt32(&b[2 * stride]), + WebPMemToInt32(&b[4 * stride]), WebPMemToInt32(&b[0 * stride])); + const __m128i A1 = _mm_set_epi32( + WebPMemToInt32(&b[7 * stride]), WebPMemToInt32(&b[3 * stride]), + WebPMemToInt32(&b[5 * stride]), WebPMemToInt32(&b[1 * stride])); + + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); + + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + const __m128i C0 = _mm_unpacklo_epi16(B0, B1); + const __m128i C1 = _mm_unpackhi_epi16(B0, B1); + + // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + *p = _mm_unpacklo_epi32(C0, C1); + *q = _mm_unpackhi_epi32(C0, C1); +} + +static WEBP_INLINE void Load16x4_SSE2(const uint8_t* const r0, + const uint8_t* const r8, + int stride, + __m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1) { + // Assume the pixels around the edge (|) are numbered as follows + // 00 01 | 02 03 + // 10 11 | 12 13 + // ... | ... + // e0 e1 | e2 e3 + // f0 f1 | f2 f3 + // + // r0 is pointing to the 0th row (00) + // r8 is pointing to the 8th row (80) + + // Load + // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 + // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 + Load8x4_SSE2(r0, stride, p1, q0); + Load8x4_SSE2(r8, stride, p0, q1); + + { + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + const __m128i t1 = *p1; + const __m128i t2 = *q0; + *p1 = _mm_unpacklo_epi64(t1, *p0); + *p0 = _mm_unpackhi_epi64(t1, *p0); + *q0 = _mm_unpacklo_epi64(t2, *q1); + *q1 = _mm_unpackhi_epi64(t2, *q1); + } +} + +static WEBP_INLINE void Store4x4_SSE2(__m128i* const x, + uint8_t* dst, int stride) { + int i; + for (i = 0; i < 4; ++i, dst += stride) { + WebPInt32ToMem(dst, _mm_cvtsi128_si32(*x)); + *x = _mm_srli_si128(*x, 4); + } +} + +// Transpose back and store +static WEBP_INLINE void Store16x4_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + uint8_t* r0, uint8_t* r8, + int stride) { + __m128i t1, p1_s, p0_s, q0_s, q1_s; + + // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 + // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 + t1 = *p0; + p0_s = _mm_unpacklo_epi8(*p1, t1); + p1_s = _mm_unpackhi_epi8(*p1, t1); + + // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 + // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 + t1 = *q0; + q0_s = _mm_unpacklo_epi8(t1, *q1); + q1_s = _mm_unpackhi_epi8(t1, *q1); + + // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 + // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 + t1 = p0_s; + p0_s = _mm_unpacklo_epi16(t1, q0_s); + q0_s = _mm_unpackhi_epi16(t1, q0_s); + + // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 + // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 + t1 = p1_s; + p1_s = _mm_unpacklo_epi16(t1, q1_s); + q1_s = _mm_unpackhi_epi16(t1, q1_s); + + Store4x4_SSE2(&p0_s, r0, stride); + r0 += 4 * stride; + Store4x4_SSE2(&q0_s, r0, stride); + + Store4x4_SSE2(&p1_s, r8, stride); + r8 += 4 * stride; + Store4x4_SSE2(&q1_s, r8, stride); +} + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16_SSE2(uint8_t* p, int stride, int thresh) { + // Load + __m128i p1 = _mm_loadu_si128((__m128i*)&p[-2 * stride]); + __m128i p0 = _mm_loadu_si128((__m128i*)&p[-stride]); + __m128i q0 = _mm_loadu_si128((__m128i*)&p[0]); + __m128i q1 = _mm_loadu_si128((__m128i*)&p[stride]); + + DoFilter2_SSE2(&p1, &p0, &q0, &q1, thresh); + + // Store + _mm_storeu_si128((__m128i*)&p[-stride], p0); + _mm_storeu_si128((__m128i*)&p[0], q0); +} + +static void SimpleHFilter16_SSE2(uint8_t* p, int stride, int thresh) { + __m128i p1, p0, q0, q1; + + p -= 2; // beginning of p1 + + Load16x4_SSE2(p, p + 8 * stride, stride, &p1, &p0, &q0, &q1); + DoFilter2_SSE2(&p1, &p0, &q0, &q1, thresh); + Store16x4_SSE2(&p1, &p0, &q0, &q1, p, p + 8 * stride, stride); +} + +static void SimpleVFilter16i_SSE2(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16_SSE2(p, stride, thresh); + } +} + +static void SimpleHFilter16i_SSE2(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16_SSE2(p, stride, thresh); + } +} + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +#define MAX_DIFF1(p3, p2, p1, p0, m) do { \ + (m) = MM_ABS(p1, p0); \ + (m) = _mm_max_epu8(m, MM_ABS(p3, p2)); \ + (m) = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) + +#define MAX_DIFF2(p3, p2, p1, p0, m) do { \ + (m) = _mm_max_epu8(m, MM_ABS(p1, p0)); \ + (m) = _mm_max_epu8(m, MM_ABS(p3, p2)); \ + (m) = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) + +#define LOAD_H_EDGES4(p, stride, e1, e2, e3, e4) do { \ + (e1) = _mm_loadu_si128((__m128i*)&(p)[0 * (stride)]); \ + (e2) = _mm_loadu_si128((__m128i*)&(p)[1 * (stride)]); \ + (e3) = _mm_loadu_si128((__m128i*)&(p)[2 * (stride)]); \ + (e4) = _mm_loadu_si128((__m128i*)&(p)[3 * (stride)]); \ +} while (0) + +#define LOADUV_H_EDGE(p, u, v, stride) do { \ + const __m128i U = _mm_loadl_epi64((__m128i*)&(u)[(stride)]); \ + const __m128i V = _mm_loadl_epi64((__m128i*)&(v)[(stride)]); \ + (p) = _mm_unpacklo_epi64(U, V); \ +} while (0) + +#define LOADUV_H_EDGES4(u, v, stride, e1, e2, e3, e4) do { \ + LOADUV_H_EDGE(e1, u, v, 0 * (stride)); \ + LOADUV_H_EDGE(e2, u, v, 1 * (stride)); \ + LOADUV_H_EDGE(e3, u, v, 2 * (stride)); \ + LOADUV_H_EDGE(e4, u, v, 3 * (stride)); \ +} while (0) + +#define STOREUV(p, u, v, stride) do { \ + _mm_storel_epi64((__m128i*)&(u)[(stride)], p); \ + (p) = _mm_srli_si128(p, 8); \ + _mm_storel_epi64((__m128i*)&(v)[(stride)], p); \ +} while (0) + +static WEBP_INLINE void ComplexMask_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, int ithresh, + __m128i* const mask) { + const __m128i it = _mm_set1_epi8(ithresh); + const __m128i diff = _mm_subs_epu8(*mask, it); + const __m128i thresh_mask = _mm_cmpeq_epi8(diff, _mm_setzero_si128()); + __m128i filter_mask; + NeedsFilter_SSE2(p1, p0, q0, q1, thresh, &filter_mask); + *mask = _mm_and_si128(thresh_mask, filter_mask); +} + +// on macroblock edges +static void VFilter16_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i t1; + __m128i mask; + __m128i p2, p1, p0, q0, q1, q2; + + // Load p3, p2, p1, p0 + LOAD_H_EDGES4(p - 4 * stride, stride, t1, p2, p1, p0); + MAX_DIFF1(t1, p2, p1, p0, mask); + + // Load q0, q1, q2, q3 + LOAD_H_EDGES4(p, stride, q0, q1, q2, t1); + MAX_DIFF2(t1, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + // Store + _mm_storeu_si128((__m128i*)&p[-3 * stride], p2); + _mm_storeu_si128((__m128i*)&p[-2 * stride], p1); + _mm_storeu_si128((__m128i*)&p[-1 * stride], p0); + _mm_storeu_si128((__m128i*)&p[+0 * stride], q0); + _mm_storeu_si128((__m128i*)&p[+1 * stride], q1); + _mm_storeu_si128((__m128i*)&p[+2 * stride], q2); +} + +static void HFilter16_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i p3, p2, p1, p0, q0, q1, q2, q3; + + uint8_t* const b = p - 4; + Load16x4_SSE2(b, b + 8 * stride, stride, &p3, &p2, &p1, &p0); + MAX_DIFF1(p3, p2, p1, p0, mask); + + Load16x4_SSE2(p, p + 8 * stride, stride, &q0, &q1, &q2, &q3); + MAX_DIFF2(q3, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + Store16x4_SSE2(&p3, &p2, &p1, &p0, b, b + 8 * stride, stride); + Store16x4_SSE2(&q0, &q1, &q2, &q3, p, p + 8 * stride, stride); +} + +// on three inner edges +static void VFilter16i_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + __m128i p3, p2, p1, p0; // loop invariants + + LOAD_H_EDGES4(p, stride, p3, p2, p1, p0); // prologue + + for (k = 3; k > 0; --k) { + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2 * stride; // beginning of p1 + p += 4 * stride; + + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + LOAD_H_EDGES4(p, stride, p3, p2, tmp1, tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); + + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask_SSE2(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &p3, &p2, &mask, hev_thresh); + + // Store + _mm_storeu_si128((__m128i*)&b[0 * stride], p1); + _mm_storeu_si128((__m128i*)&b[1 * stride], p0); + _mm_storeu_si128((__m128i*)&b[2 * stride], p3); + _mm_storeu_si128((__m128i*)&b[3 * stride], p2); + + // rotate samples + p1 = tmp1; + p0 = tmp2; + } +} + +static void HFilter16i_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + __m128i p3, p2, p1, p0; // loop invariants + + Load16x4_SSE2(p, p + 8 * stride, stride, &p3, &p2, &p1, &p0); // prologue + + for (k = 3; k > 0; --k) { + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2; // beginning of p1 + + p += 4; // beginning of q0 (and next span) + + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + Load16x4_SSE2(p, p + 8 * stride, stride, &p3, &p2, &tmp1, &tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); + + ComplexMask_SSE2(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &p3, &p2, &mask, hev_thresh); + + Store16x4_SSE2(&p1, &p0, &p3, &p2, b, b + 8 * stride, stride); + + // rotate samples + p1 = tmp1; + p0 = tmp2; + } +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8_SSE2(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, p2, p1, p0, q0, q1, q2; + + // Load p3, p2, p1, p0 + LOADUV_H_EDGES4(u - 4 * stride, v - 4 * stride, stride, t1, p2, p1, p0); + MAX_DIFF1(t1, p2, p1, p0, mask); + + // Load q0, q1, q2, q3 + LOADUV_H_EDGES4(u, v, stride, q0, q1, q2, t1); + MAX_DIFF2(t1, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + // Store + STOREUV(p2, u, v, -3 * stride); + STOREUV(p1, u, v, -2 * stride); + STOREUV(p0, u, v, -1 * stride); + STOREUV(q0, u, v, 0 * stride); + STOREUV(q1, u, v, 1 * stride); + STOREUV(q2, u, v, 2 * stride); +} + +static void HFilter8_SSE2(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i p3, p2, p1, p0, q0, q1, q2, q3; + + uint8_t* const tu = u - 4; + uint8_t* const tv = v - 4; + Load16x4_SSE2(tu, tv, stride, &p3, &p2, &p1, &p0); + MAX_DIFF1(p3, p2, p1, p0, mask); + + Load16x4_SSE2(u, v, stride, &q0, &q1, &q2, &q3); + MAX_DIFF2(q3, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + Store16x4_SSE2(&p3, &p2, &p1, &p0, tu, tv, stride); + Store16x4_SSE2(&q0, &q1, &q2, &q3, u, v, stride); +} + +static void VFilter8i_SSE2(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, t2, p1, p0, q0, q1; + + // Load p3, p2, p1, p0 + LOADUV_H_EDGES4(u, v, stride, t2, t1, p1, p0); + MAX_DIFF1(t2, t1, p1, p0, mask); + + u += 4 * stride; + v += 4 * stride; + + // Load q0, q1, q2, q3 + LOADUV_H_EDGES4(u, v, stride, q0, q1, t1, t2); + MAX_DIFF2(t2, t1, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &q0, &q1, &mask, hev_thresh); + + // Store + STOREUV(p1, u, v, -2 * stride); + STOREUV(p0, u, v, -1 * stride); + STOREUV(q0, u, v, 0 * stride); + STOREUV(q1, u, v, 1 * stride); +} + +static void HFilter8i_SSE2(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, t2, p1, p0, q0, q1; + Load16x4_SSE2(u, v, stride, &t2, &t1, &p1, &p0); // p3, p2, p1, p0 + MAX_DIFF1(t2, t1, p1, p0, mask); + + u += 4; // beginning of q0 + v += 4; + Load16x4_SSE2(u, v, stride, &q0, &q1, &t1, &t2); // q0, q1, q2, q3 + MAX_DIFF2(t2, t1, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &q0, &q1, &mask, hev_thresh); + + u -= 2; // beginning of p1 + v -= 2; + Store16x4_SSE2(&p1, &p0, &q0, &q1, u, v, stride); +} + +//------------------------------------------------------------------------------ +// 4x4 predictions + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) + +// We use the following 8b-arithmetic tricks: +// (a + 2 * b + c + 2) >> 2 = (AC + b + 1) >> 1 +// where: AC = (a + c) >> 1 = [(a + c + 1) >> 1] - [(a^c) & 1] +// and: +// (a + 2 * b + c + 2) >> 2 = (AB + BC + 1) >> 1 - (ab|bc)&lsb +// where: AC = (a + b + 1) >> 1, BC = (b + c + 1) >> 1 +// and ab = a ^ b, bc = b ^ c, lsb = (AC^BC)&1 + +static void VE4_SSE2(uint8_t* dst) { // vertical + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i a = _mm_avg_epu8(ABCDEFGH, CDEFGH00); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); + const __m128i b = _mm_subs_epu8(a, lsb); + const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); + const int vals = _mm_cvtsi128_si32(avg); + int i; + for (i = 0; i < 4; ++i) { + WebPInt32ToMem(dst + i * BPS, vals); + } +} + +static void LD4_SSE2(uint8_t* dst) { // Down-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i CDEFGHH0 = _mm_insert_epi16(CDEFGH00, dst[-BPS + 7], 3); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, CDEFGHH0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static void VR4_SSE2(uint8_t* dst) { // Vertical-Right + const __m128i one = _mm_set1_epi8(1); + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int X = dst[-1 - BPS]; + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ABCD0 = _mm_srli_si128(XABCD, 1); + const __m128i abcd = _mm_avg_epu8(XABCD, ABCD0); + const __m128i _XABCD = _mm_slli_si128(XABCD, 1); + const __m128i IXABCD = _mm_insert_epi16(_XABCD, (short)(I | (X << 8)), 0); + const __m128i avg1 = _mm_avg_epu8(IXABCD, ABCD0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i efgh = _mm_avg_epu8(avg2, XABCD); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + + // these two are hard to implement in SSE2, so we keep the C-version: + DST(0, 2) = AVG3(J, I, X); + DST(0, 3) = AVG3(K, J, I); +} + +static void VL4_SSE2(uint8_t* dst) { // Vertical-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH_ = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH__ = _mm_srli_si128(ABCDEFGH, 2); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, BCDEFGH_); + const __m128i avg2 = _mm_avg_epu8(CDEFGH__, BCDEFGH_); + const __m128i avg3 = _mm_avg_epu8(avg1, avg2); + const __m128i lsb1 = _mm_and_si128(_mm_xor_si128(avg1, avg2), one); + const __m128i ab = _mm_xor_si128(ABCDEFGH, BCDEFGH_); + const __m128i bc = _mm_xor_si128(CDEFGH__, BCDEFGH_); + const __m128i abbc = _mm_or_si128(ab, bc); + const __m128i lsb2 = _mm_and_si128(abbc, lsb1); + const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); + const uint32_t extra_out = + (uint32_t)_mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + + // these two are hard to get and irregular + DST(3, 2) = (extra_out >> 0) & 0xff; + DST(3, 3) = (extra_out >> 8) & 0xff; +} + +static void RD4_SSE2(uint8_t* dst) { // Down-right + const __m128i one = _mm_set1_epi8(1); + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ____XABCD = _mm_slli_si128(XABCD, 4); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const __m128i LKJI_____ = + _mm_cvtsi32_si128((int)(L | (K << 8) | (J << 16) | (I << 24))); + const __m128i LKJIXABCD = _mm_or_si128(LKJI_____, ____XABCD); + const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); + const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); + const __m128i avg1 = _mm_avg_epu8(JIXABCD__, LKJIXABCD); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +#undef DST +#undef AVG3 + +//------------------------------------------------------------------------------ +// Luma 16x16 + +static WEBP_INLINE void TrueMotion_SSE2(uint8_t* dst, int size) { + const uint8_t* top = dst - BPS; + const __m128i zero = _mm_setzero_si128(); + int y; + if (size == 4) { + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToInt32(top)); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 4; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(out)); + } + } else if (size == 8) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 8; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + _mm_storel_epi64((__m128i*)dst, out); + } + } else { + const __m128i top_values = _mm_loadu_si128((const __m128i*)top); + const __m128i top_base_0 = _mm_unpacklo_epi8(top_values, zero); + const __m128i top_base_1 = _mm_unpackhi_epi8(top_values, zero); + for (y = 0; y < 16; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out_0 = _mm_add_epi16(base, top_base_0); + const __m128i out_1 = _mm_add_epi16(base, top_base_1); + const __m128i out = _mm_packus_epi16(out_0, out_1); + _mm_storeu_si128((__m128i*)dst, out); + } + } +} + +static void TM4_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 4); } +static void TM8uv_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 8); } +static void TM16_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 16); } + +static void VE16_SSE2(uint8_t* dst) { + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + int j; + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), top); + } +} + +static void HE16_SSE2(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; --j) { + const __m128i values = _mm_set1_epi8((char)dst[-1]); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void Put16_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), values); + } +} + +static void DC16_SSE2(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + int left = 0; + int j; + for (j = 0; j < 16; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 16; + Put16_SSE2(DC >> 5, dst); + } +} + +static void DC16NoTop_SSE2(uint8_t* dst) { // DC with top samples unavailable + int DC = 8; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS]; + } + Put16_SSE2(DC >> 4, dst); +} + +static void DC16NoLeft_SSE2(uint8_t* dst) { // DC with left samples unavailable + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + const int DC = _mm_cvtsi128_si32(sum) + 8; + Put16_SSE2(DC >> 4, dst); +} + +static void DC16NoTopLeft_SSE2(uint8_t* dst) { // DC with no top & left samples + Put16_SSE2(0x80, dst); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv_SSE2(uint8_t* dst) { // vertical + int j; + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), top); + } +} + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), values); + } +} + +static void DC8uv_SSE2(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + int left = 0; + int j; + for (j = 0; j < 8; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 8; + Put8x8uv_SSE2(DC >> 4, dst); + } +} + +static void DC8uvNoLeft_SSE2(uint8_t* dst) { // DC with no left samples + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + const int DC = _mm_cvtsi128_si32(sum) + 4; + Put8x8uv_SSE2(DC >> 3, dst); +} + +static void DC8uvNoTop_SSE2(uint8_t* dst) { // DC with no top samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[-1 + i * BPS]; + } + Put8x8uv_SSE2(dc0 >> 3, dst); +} + +static void DC8uvNoTopLeft_SSE2(uint8_t* dst) { // DC with nothing + Put8x8uv_SSE2(0x80, dst); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE2(void) { + VP8Transform = Transform_SSE2; +#if (USE_TRANSFORM_AC3 == 1) + VP8TransformAC3 = TransformAC3_SSE2; +#endif + + VP8VFilter16 = VFilter16_SSE2; + VP8HFilter16 = HFilter16_SSE2; + VP8VFilter8 = VFilter8_SSE2; + VP8HFilter8 = HFilter8_SSE2; + VP8VFilter16i = VFilter16i_SSE2; + VP8HFilter16i = HFilter16i_SSE2; + VP8VFilter8i = VFilter8i_SSE2; + VP8HFilter8i = HFilter8i_SSE2; + + VP8SimpleVFilter16 = SimpleVFilter16_SSE2; + VP8SimpleHFilter16 = SimpleHFilter16_SSE2; + VP8SimpleVFilter16i = SimpleVFilter16i_SSE2; + VP8SimpleHFilter16i = SimpleHFilter16i_SSE2; + + VP8PredLuma4[1] = TM4_SSE2; + VP8PredLuma4[2] = VE4_SSE2; + VP8PredLuma4[4] = RD4_SSE2; + VP8PredLuma4[5] = VR4_SSE2; + VP8PredLuma4[6] = LD4_SSE2; + VP8PredLuma4[7] = VL4_SSE2; + + VP8PredLuma16[0] = DC16_SSE2; + VP8PredLuma16[1] = TM16_SSE2; + VP8PredLuma16[2] = VE16_SSE2; + VP8PredLuma16[3] = HE16_SSE2; + VP8PredLuma16[4] = DC16NoTop_SSE2; + VP8PredLuma16[5] = DC16NoLeft_SSE2; + VP8PredLuma16[6] = DC16NoTopLeft_SSE2; + + VP8PredChroma8[0] = DC8uv_SSE2; + VP8PredChroma8[1] = TM8uv_SSE2; + VP8PredChroma8[2] = VE8uv_SSE2; + VP8PredChroma8[4] = DC8uvNoTop_SSE2; + VP8PredChroma8[5] = DC8uvNoLeft_SSE2; + VP8PredChroma8[6] = DC8uvNoTopLeft_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse41.c new file mode 100644 index 0000000000..08a3630272 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dec_sse41.c @@ -0,0 +1,46 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 version of some decoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include +#include "src/dec/vp8i_dec.h" +#include "src/utils/utils.h" + +static void HE16_SSE41(uint8_t* dst) { // horizontal + int j; + const __m128i kShuffle3 = _mm_set1_epi8(3); + for (j = 16; j > 0; --j) { + const __m128i in = _mm_cvtsi32_si128(WebPMemToInt32(dst - 4)); + const __m128i values = _mm_shuffle_epi8(in, kShuffle3); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE41(void) { + VP8PredLuma16[3] = HE16_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dsp.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dsp.h new file mode 100644 index 0000000000..23bc296514 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/dsp.h @@ -0,0 +1,509 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Speed-critical functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_DSP_H_ +#define WEBP_DSP_DSP_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BPS 32 // this is the common stride for enc/dec + +//------------------------------------------------------------------------------ +// WEBP_RESTRICT + +// Declares a pointer with the restrict type qualifier if available. +// This allows code to hint to the compiler that only this pointer references a +// particular object or memory region within the scope of the block in which it +// is declared. This may allow for improved optimizations due to the lack of +// pointer aliasing. See also: +// https://en.cppreference.com/w/c/language/restrict +#if defined(__GNUC__) +#define WEBP_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define WEBP_RESTRICT __restrict +#else +#define WEBP_RESTRICT +#endif + + +//------------------------------------------------------------------------------ +// Init stub generator + +// Defines an init function stub to ensure each module exposes a symbol, +// avoiding a compiler warning. +#define WEBP_DSP_INIT_STUB(func) \ + extern void func(void); \ + void func(void) {} + +//------------------------------------------------------------------------------ +// Encoding + +// Transforms +// VP8Idct: Does one of two inverse transforms. If do_two is set, the transforms +// will be done for (ref, in, dst) and (ref + 4, in + 16, dst + 4). +typedef void (*VP8Idct)(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two); +typedef void (*VP8Fdct)(const uint8_t* src, const uint8_t* ref, int16_t* out); +typedef void (*VP8WHT)(const int16_t* in, int16_t* out); +extern VP8Idct VP8ITransform; +extern VP8Fdct VP8FTransform; +extern VP8Fdct VP8FTransform2; // performs two transforms at a time +extern VP8WHT VP8FTransformWHT; +// Predictions +// *dst is the destination block. *top and *left can be NULL. +typedef void (*VP8IntraPreds)(uint8_t* dst, const uint8_t* left, + const uint8_t* top); +typedef void (*VP8Intra4Preds)(uint8_t* dst, const uint8_t* top); +extern VP8Intra4Preds VP8EncPredLuma4; +extern VP8IntraPreds VP8EncPredLuma16; +extern VP8IntraPreds VP8EncPredChroma8; + +typedef int (*VP8Metric)(const uint8_t* pix, const uint8_t* ref); +extern VP8Metric VP8SSE16x16, VP8SSE16x8, VP8SSE8x8, VP8SSE4x4; +typedef int (*VP8WMetric)(const uint8_t* pix, const uint8_t* ref, + const uint16_t* const weights); +// The weights for VP8TDisto4x4 and VP8TDisto16x16 contain a row-major +// 4 by 4 symmetric matrix. +extern VP8WMetric VP8TDisto4x4, VP8TDisto16x16; + +// Compute the average (DC) of four 4x4 blocks. +// Each sub-4x4 block #i sum is stored in dc[i]. +typedef void (*VP8MeanMetric)(const uint8_t* ref, uint32_t dc[4]); +extern VP8MeanMetric VP8Mean16x4; + +typedef void (*VP8BlockCopy)(const uint8_t* src, uint8_t* dst); +extern VP8BlockCopy VP8Copy4x4; +extern VP8BlockCopy VP8Copy16x8; +// Quantization +struct VP8Matrix; // forward declaration +typedef int (*VP8QuantizeBlock)(int16_t in[16], int16_t out[16], + const struct VP8Matrix* const mtx); +// Same as VP8QuantizeBlock, but quantizes two consecutive blocks. +typedef int (*VP8Quantize2Blocks)(int16_t in[32], int16_t out[32], + const struct VP8Matrix* const mtx); + +extern VP8QuantizeBlock VP8EncQuantizeBlock; +extern VP8Quantize2Blocks VP8EncQuantize2Blocks; + +// specific to 2nd transform: +typedef int (*VP8QuantizeBlockWHT)(int16_t in[16], int16_t out[16], + const struct VP8Matrix* const mtx); +extern VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT; + +extern const int VP8DspScan[16 + 4 + 4]; + +// Collect histogram for susceptibility calculation. +#define MAX_COEFF_THRESH 31 // size of histogram used by CollectHistogram. +typedef struct { + // We only need to store max_value and last_non_zero, not the distribution. + int max_value; + int last_non_zero; +} VP8Histogram; +typedef void (*VP8CHisto)(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo); +extern VP8CHisto VP8CollectHistogram; +// General-purpose util function to help VP8CollectHistogram(). +void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1], + VP8Histogram* const histo); + +// must be called before using any of the above +void VP8EncDspInit(void); + +//------------------------------------------------------------------------------ +// cost functions (encoding) + +extern const uint16_t VP8EntropyCost[256]; // 8bit fixed-point log(p) +// approximate cost per level: +extern const uint16_t VP8LevelFixedCosts[2047 /*MAX_LEVEL*/ + 1]; +extern const uint8_t VP8EncBands[16 + 1]; + +struct VP8Residual; +typedef void (*VP8SetResidualCoeffsFunc)(const int16_t* const coeffs, + struct VP8Residual* const res); +extern VP8SetResidualCoeffsFunc VP8SetResidualCoeffs; + +// Cost calculation function. +typedef int (*VP8GetResidualCostFunc)(int ctx0, + const struct VP8Residual* const res); +extern VP8GetResidualCostFunc VP8GetResidualCost; + +// must be called before anything using the above +void VP8EncDspCostInit(void); + +//------------------------------------------------------------------------------ +// SSIM / PSNR utils + +// struct for accumulating statistical moments +typedef struct { + uint32_t w; // sum(w_i) : sum of weights + uint32_t xm, ym; // sum(w_i * x_i), sum(w_i * y_i) + uint32_t xxm, xym, yym; // sum(w_i * x_i * x_i), etc. +} VP8DistoStats; + +// Compute the final SSIM value +// The non-clipped version assumes stats->w = (2 * VP8_SSIM_KERNEL + 1)^2. +double VP8SSIMFromStats(const VP8DistoStats* const stats); +double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats); + +#define VP8_SSIM_KERNEL 3 // total size of the kernel: 2 * VP8_SSIM_KERNEL + 1 +typedef double (*VP8SSIMGetClippedFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2, + int xo, int yo, // center position + int W, int H); // plane dimension + +#if !defined(WEBP_REDUCE_SIZE) +// This version is called with the guarantee that you can load 8 bytes and +// 8 rows at offset src1 and src2 +typedef double (*VP8SSIMGetFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2); + +extern VP8SSIMGetFunc VP8SSIMGet; // unclipped / unchecked +extern VP8SSIMGetClippedFunc VP8SSIMGetClipped; // with clipping +#endif + +#if !defined(WEBP_DISABLE_STATS) +typedef uint32_t (*VP8AccumulateSSEFunc)(const uint8_t* src1, + const uint8_t* src2, int len); +extern VP8AccumulateSSEFunc VP8AccumulateSSE; +#endif + +// must be called before using any of the above directly +void VP8SSIMDspInit(void); + +//------------------------------------------------------------------------------ +// Decoding + +typedef void (*VP8DecIdct)(const int16_t* coeffs, uint8_t* dst); +// when doing two transforms, coeffs is actually int16_t[2][16]. +typedef void (*VP8DecIdct2)(const int16_t* coeffs, uint8_t* dst, int do_two); +extern VP8DecIdct2 VP8Transform; +extern VP8DecIdct VP8TransformAC3; +extern VP8DecIdct VP8TransformUV; +extern VP8DecIdct VP8TransformDC; +extern VP8DecIdct VP8TransformDCUV; +extern VP8WHT VP8TransformWHT; + +#define WEBP_TRANSFORM_AC3_C1 20091 +#define WEBP_TRANSFORM_AC3_C2 35468 +#define WEBP_TRANSFORM_AC3_MUL1(a) ((((a) * WEBP_TRANSFORM_AC3_C1) >> 16) + (a)) +#define WEBP_TRANSFORM_AC3_MUL2(a) (((a) * WEBP_TRANSFORM_AC3_C2) >> 16) + +// *dst is the destination block, with stride BPS. Boundary samples are +// assumed accessible when needed. +typedef void (*VP8PredFunc)(uint8_t* dst); +extern VP8PredFunc VP8PredLuma16[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredChroma8[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredLuma4[/* NUM_BMODES */]; + +// clipping tables (for filtering) +extern const int8_t* const VP8ksclip1; // clips [-1020, 1020] to [-128, 127] +extern const int8_t* const VP8ksclip2; // clips [-112, 112] to [-16, 15] +extern const uint8_t* const VP8kclip1; // clips [-255,511] to [0,255] +extern const uint8_t* const VP8kabs0; // abs(x) for x in [-255,255] +// must be called first +void VP8InitClipTables(void); + +// simple filter (only for luma) +typedef void (*VP8SimpleFilterFunc)(uint8_t* p, int stride, int thresh); +extern VP8SimpleFilterFunc VP8SimpleVFilter16; +extern VP8SimpleFilterFunc VP8SimpleHFilter16; +extern VP8SimpleFilterFunc VP8SimpleVFilter16i; // filter 3 inner edges +extern VP8SimpleFilterFunc VP8SimpleHFilter16i; + +// regular filter (on both macroblock edges and inner edges) +typedef void (*VP8LumaFilterFunc)(uint8_t* luma, int stride, + int thresh, int ithresh, int hev_t); +typedef void (*VP8ChromaFilterFunc)(uint8_t* u, uint8_t* v, int stride, + int thresh, int ithresh, int hev_t); +// on outer edge +extern VP8LumaFilterFunc VP8VFilter16; +extern VP8LumaFilterFunc VP8HFilter16; +extern VP8ChromaFilterFunc VP8VFilter8; +extern VP8ChromaFilterFunc VP8HFilter8; + +// on inner edge +extern VP8LumaFilterFunc VP8VFilter16i; // filtering 3 inner edges altogether +extern VP8LumaFilterFunc VP8HFilter16i; +extern VP8ChromaFilterFunc VP8VFilter8i; // filtering u and v altogether +extern VP8ChromaFilterFunc VP8HFilter8i; + +// Dithering. Combines dithering values (centered around 128) with dst[], +// according to: dst[] = clip(dst[] + (((dither[]-128) + 8) >> 4) +#define VP8_DITHER_DESCALE 4 +#define VP8_DITHER_DESCALE_ROUNDER (1 << (VP8_DITHER_DESCALE - 1)) +#define VP8_DITHER_AMP_BITS 7 +#define VP8_DITHER_AMP_CENTER (1 << VP8_DITHER_AMP_BITS) +extern void (*VP8DitherCombine8x8)(const uint8_t* dither, uint8_t* dst, + int dst_stride); + +// must be called before anything using the above +void VP8DspInit(void); + +//------------------------------------------------------------------------------ +// WebP I/O + +#define FANCY_UPSAMPLING // undefined to remove fancy upsampling support + +// Convert a pair of y/u/v lines together to the output rgb/a colorspace. +// bottom_y can be NULL if only one line of output is needed (at top/bottom). +typedef void (*WebPUpsampleLinePairFunc)( + const uint8_t* top_y, const uint8_t* bottom_y, + const uint8_t* top_u, const uint8_t* top_v, + const uint8_t* cur_u, const uint8_t* cur_v, + uint8_t* top_dst, uint8_t* bottom_dst, int len); + +#ifdef FANCY_UPSAMPLING + +// Fancy upsampling functions to convert YUV to RGB(A) modes +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +#endif // FANCY_UPSAMPLING + +// Per-row point-sampling methods. +typedef void (*WebPSamplerRowFunc)(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len); +// Generic function to apply 'WebPSamplerRowFunc' to the whole plane: +void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, + const uint8_t* u, const uint8_t* v, int uv_stride, + uint8_t* dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func); + +// Sampling functions to convert rows of YUV to RGB(A) +extern WebPSamplerRowFunc WebPSamplers[/* MODE_LAST */]; + +// General function for converting two lines of ARGB or RGBA. +// 'alpha_is_last' should be true if 0xff000000 is stored in memory as +// as 0x00, 0x00, 0x00, 0xff (little endian). +WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last); + +// YUV444->RGB converters +typedef void (*WebPYUV444Converter)(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len); + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; + +// Must be called before using the WebPUpsamplers[] (and for premultiplied +// colorspaces like rgbA, rgbA4444, etc) +void WebPInitUpsamplers(void); +// Must be called before using WebPSamplers[] +void WebPInitSamplers(void); +// Must be called before using WebPYUV444Converters[] +void WebPInitYUV444Converters(void); + +//------------------------------------------------------------------------------ +// ARGB -> YUV converters + +// Convert ARGB samples to luma Y. +extern void (*WebPConvertARGBToY)(const uint32_t* argb, uint8_t* y, int width); +// Convert ARGB samples to U/V with downsampling. do_store should be '1' for +// even lines and '0' for odd ones. 'src_width' is the original width, not +// the U/V one. +extern void (*WebPConvertARGBToUV)(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); + +// Convert a row of accumulated (four-values) of rgba32 toward U/V +extern void (*WebPConvertRGBA32ToUV)(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +// Convert RGB or BGR to Y +extern void (*WebPConvertRGB24ToY)(const uint8_t* rgb, uint8_t* y, int width); +extern void (*WebPConvertBGR24ToY)(const uint8_t* bgr, uint8_t* y, int width); + +// used for plain-C fallback. +extern void WebPConvertARGBToUV_C(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); +extern void WebPConvertRGBA32ToUV_C(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +// Must be called before using the above. +void WebPInitConvertARGBToYUV(void); + +//------------------------------------------------------------------------------ +// Rescaler + +struct WebPRescaler; + +// Import a row of data and save its contribution in the rescaler. +// 'channel' denotes the channel number to be imported. 'Expand' corresponds to +// the wrk->x_expand case. Otherwise, 'Shrink' is to be used. +typedef void (*WebPRescalerImportRowFunc)(struct WebPRescaler* const wrk, + const uint8_t* src); + +extern WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +extern WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +// Export one row (starting at x_out position) from rescaler. +// 'Expand' corresponds to the wrk->y_expand case. +// Otherwise 'Shrink' is to be used +typedef void (*WebPRescalerExportRowFunc)(struct WebPRescaler* const wrk); +extern WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +extern WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +// Plain-C implementation, as fall-back. +extern void WebPRescalerImportRowExpand_C(struct WebPRescaler* const wrk, + const uint8_t* src); +extern void WebPRescalerImportRowShrink_C(struct WebPRescaler* const wrk, + const uint8_t* src); +extern void WebPRescalerExportRowExpand_C(struct WebPRescaler* const wrk); +extern void WebPRescalerExportRowShrink_C(struct WebPRescaler* const wrk); + +// Main entry calls: +extern void WebPRescalerImportRow(struct WebPRescaler* const wrk, + const uint8_t* src); +// Export one row (starting at x_out position) from rescaler. +extern void WebPRescalerExportRow(struct WebPRescaler* const wrk); + +// Must be called first before using the above. +void WebPRescalerDspInit(void); + +//------------------------------------------------------------------------------ +// Utilities for processing transparent channel. + +// Apply alpha pre-multiply on an rgba, bgra or argb plane of size w * h. +// alpha_first should be 0 for argb, 1 for rgba or bgra (where alpha is last). +extern void (*WebPApplyAlphaMultiply)( + uint8_t* rgba, int alpha_first, int w, int h, int stride); + +// Same, buf specifically for RGBA4444 format +extern void (*WebPApplyAlphaMultiply4444)( + uint8_t* rgba4444, int w, int h, int stride); + +// Dispatch the values from alpha[] plane to the ARGB destination 'dst'. +// Returns true if alpha[] plane has non-trivial values different from 0xff. +extern int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride); + +// Transfer packed 8b alpha[] values to green channel in dst[], zero'ing the +// A/R/B values. 'dst_stride' is the stride for dst[] in uint32_t units. +extern void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride); + +// Extract the alpha values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlpha). +// Returns true if there's only trivial 0xff alpha values. +extern int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT argb, + int argb_stride, int width, int height, + uint8_t* WEBP_RESTRICT alpha, + int alpha_stride); + +// Extract the green values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlphaToGreen). +extern void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size); + +// Pre-Multiply operation transforms x into x * A / 255 (where x=Y,R,G or B). +// Un-Multiply operation transforms x into x * 255 / A. + +// Pre-Multiply or Un-Multiply (if 'inverse' is true) argb values in a row. +extern void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); + +// Same a WebPMultARGBRow(), but for several rows. +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse); + +// Same for a row of single values, with side alpha values. +extern void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); + +// Same a WebPMultRow(), but for several 'num_rows' rows. +void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride, + const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int num_rows, int inverse); + +// Plain-C versions, used as fallback by some implementations. +void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); +void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse); + +#ifdef WORDS_BIGENDIAN +// ARGB packing function: a/r/g/b input is rgba or bgra order. +extern void (*WebPPackARGB)(const uint8_t* WEBP_RESTRICT a, + const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, uint32_t* WEBP_RESTRICT out); +#endif + +// RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order. +extern void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out); + +// This function returns true if src[i] contains a value different from 0xff. +extern int (*WebPHasAlpha8b)(const uint8_t* src, int length); +// This function returns true if src[4*i] contains a value different from 0xff. +extern int (*WebPHasAlpha32b)(const uint8_t* src, int length); +// replaces transparent values in src[] by 'color'. +extern void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color); + +// To be called first before using the above. +void WebPInitAlphaProcessing(void); + +//------------------------------------------------------------------------------ +// Filter functions + +typedef enum { // Filter types. + WEBP_FILTER_NONE = 0, + WEBP_FILTER_HORIZONTAL, + WEBP_FILTER_VERTICAL, + WEBP_FILTER_GRADIENT, + WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker + WEBP_FILTER_BEST, // meta-types + WEBP_FILTER_FAST +} WEBP_FILTER_TYPE; + +typedef void (*WebPFilterFunc)(const uint8_t* in, int width, int height, + int stride, uint8_t* out); +// In-place un-filtering. +// Warning! 'prev_line' pointer can be equal to 'cur_line' or 'preds'. +typedef void (*WebPUnfilterFunc)(const uint8_t* prev_line, const uint8_t* preds, + uint8_t* cur_line, int width); + +// Filter the given data using the given predictor. +// 'in' corresponds to a 2-dimensional pixel array of size (stride * height) +// in raster order. +// 'stride' is number of bytes per scan line (with possible padding). +// 'out' should be pre-allocated. +extern WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; + +// In-place reconstruct the original data from the given filtered data. +// The reconstruction will be done for 'num_rows' rows starting from 'row' +// (assuming rows upto 'row - 1' are already reconstructed). +extern WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +// To be called first before using the above. +void VP8FiltersInit(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_DSP_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc.c new file mode 100644 index 0000000000..395ad05b0b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc.c @@ -0,0 +1,830 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Speed-critical encoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include // for abs() + +#include "src/dsp/dsp.h" +#include "src/enc/vp8i_enc.h" + +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; +} + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE int clip_max(int v, int max) { + return (v > max) ? max : v; +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Compute susceptibility based on DCT-coeff histograms: +// the higher, the "easier" the macroblock is to compress. + +const int VP8DspScan[16 + 4 + 4] = { + // Luma + 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, + 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, + 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, + 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS, + + 0 + 0 * BPS, 4 + 0 * BPS, 0 + 4 * BPS, 4 + 4 * BPS, // U + 8 + 0 * BPS, 12 + 0 * BPS, 8 + 4 * BPS, 12 + 4 * BPS // V +}; + +// general-purpose util function +void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1], + VP8Histogram* const histo) { + int max_value = 0, last_non_zero = 1; + int k; + for (k = 0; k <= MAX_COEFF_THRESH; ++k) { + const int value = distribution[k]; + if (value > 0) { + if (value > max_value) max_value = value; + last_non_zero = k; + } + } + histo->max_value = max_value; + histo->last_non_zero = last_non_zero; +} + +#if !WEBP_NEON_OMIT_C_CODE +static void CollectHistogram_C(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int k; + int16_t out[16]; + + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + const int v = abs(out[k]) >> 3; + const int clipped_value = clip_max(v, MAX_COEFF_THRESH); + ++distribution[clipped_value]; + } + } + VP8SetHistogramData(distribution, histo); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// run-time tables (~4k) + +static uint8_t clip1[255 + 510 + 1]; // clips [-255,510] to [0,255] + +// We declare this variable 'volatile' to prevent instruction reordering +// and make sure it's set to true _last_ (so as to be thread-safe) +static volatile int tables_ok = 0; + +static WEBP_TSAN_IGNORE_FUNCTION void InitTables(void) { + if (!tables_ok) { + int i; + for (i = -255; i <= 255 + 255; ++i) { + clip1[255 + i] = clip_8b(i); + } + tables_ok = 1; + } +} + + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +#if !WEBP_NEON_OMIT_C_CODE + +#define STORE(x, y, v) \ + dst[(x) + (y) * BPS] = clip_8b(ref[(x) + (y) * BPS] + ((v) >> 3)) + +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + int C[4 * 4], *tmp; + int i; + tmp = C; + for (i = 0; i < 4; ++i) { // vertical pass + const int a = in[0] + in[8]; + const int b = in[0] - in[8]; + const int c = + WEBP_TRANSFORM_AC3_MUL2(in[4]) - WEBP_TRANSFORM_AC3_MUL1(in[12]); + const int d = + WEBP_TRANSFORM_AC3_MUL1(in[4]) + WEBP_TRANSFORM_AC3_MUL2(in[12]); + tmp[0] = a + d; + tmp[1] = b + c; + tmp[2] = b - c; + tmp[3] = a - d; + tmp += 4; + in++; + } + + tmp = C; + for (i = 0; i < 4; ++i) { // horizontal pass + const int dc = tmp[0] + 4; + const int a = dc + tmp[8]; + const int b = dc - tmp[8]; + const int c = + WEBP_TRANSFORM_AC3_MUL2(tmp[4]) - WEBP_TRANSFORM_AC3_MUL1(tmp[12]); + const int d = + WEBP_TRANSFORM_AC3_MUL1(tmp[4]) + WEBP_TRANSFORM_AC3_MUL2(tmp[12]); + STORE(0, i, a + d); + STORE(1, i, b + c); + STORE(2, i, b - c); + STORE(3, i, a - d); + tmp++; + } +} + +static void ITransform_C(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +static void FTransform_C(const uint8_t* src, const uint8_t* ref, int16_t* out) { + int i; + int tmp[16]; + for (i = 0; i < 4; ++i, src += BPS, ref += BPS) { + const int d0 = src[0] - ref[0]; // 9bit dynamic range ([-255,255]) + const int d1 = src[1] - ref[1]; + const int d2 = src[2] - ref[2]; + const int d3 = src[3] - ref[3]; + const int a0 = (d0 + d3); // 10b [-510,510] + const int a1 = (d1 + d2); + const int a2 = (d1 - d2); + const int a3 = (d0 - d3); + tmp[0 + i * 4] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + i * 4] = (a2 * 2217 + a3 * 5352 + 1812) >> 9; // [-7536,7542] + tmp[2 + i * 4] = (a0 - a1) * 8; + tmp[3 + i * 4] = (a3 * 2217 - a2 * 5352 + 937) >> 9; + } + for (i = 0; i < 4; ++i) { + const int a0 = (tmp[0 + i] + tmp[12 + i]); // 15b + const int a1 = (tmp[4 + i] + tmp[ 8 + i]); + const int a2 = (tmp[4 + i] - tmp[ 8 + i]); + const int a3 = (tmp[0 + i] - tmp[12 + i]); + out[0 + i] = (a0 + a1 + 7) >> 4; // 12b + out[4 + i] = ((a2 * 2217 + a3 * 5352 + 12000) >> 16) + (a3 != 0); + out[8 + i] = (a0 - a1 + 7) >> 4; + out[12+ i] = ((a3 * 2217 - a2 * 5352 + 51000) >> 16); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void FTransform2_C(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + VP8FTransform(src, ref, out); + VP8FTransform(src + 4, ref + 4, out + 16); +} + +#if !WEBP_NEON_OMIT_C_CODE +static void FTransformWHT_C(const int16_t* in, int16_t* out) { + // input is 12b signed + int32_t tmp[16]; + int i; + for (i = 0; i < 4; ++i, in += 64) { + const int a0 = (in[0 * 16] + in[2 * 16]); // 13b + const int a1 = (in[1 * 16] + in[3 * 16]); + const int a2 = (in[1 * 16] - in[3 * 16]); + const int a3 = (in[0 * 16] - in[2 * 16]); + tmp[0 + i * 4] = a0 + a1; // 14b + tmp[1 + i * 4] = a3 + a2; + tmp[2 + i * 4] = a3 - a2; + tmp[3 + i * 4] = a0 - a1; + } + for (i = 0; i < 4; ++i) { + const int a0 = (tmp[0 + i] + tmp[8 + i]); // 15b + const int a1 = (tmp[4 + i] + tmp[12+ i]); + const int a2 = (tmp[4 + i] - tmp[12+ i]); + const int a3 = (tmp[0 + i] - tmp[8 + i]); + const int b0 = a0 + a1; // 16b + const int b1 = a3 + a2; + const int b2 = a3 - a2; + const int b3 = a0 - a1; + out[ 0 + i] = b0 >> 1; // 15b + out[ 4 + i] = b1 >> 1; + out[ 8 + i] = b2 >> 1; + out[12 + i] = b3 >> 1; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#undef STORE + +//------------------------------------------------------------------------------ +// Intra predictions + +static WEBP_INLINE void Fill(uint8_t* dst, int value, int size) { + int j; + for (j = 0; j < size; ++j) { + memset(dst + j * BPS, value, size); + } +} + +static WEBP_INLINE void VerticalPred(uint8_t* dst, + const uint8_t* top, int size) { + int j; + if (top != NULL) { + for (j = 0; j < size; ++j) memcpy(dst + j * BPS, top, size); + } else { + Fill(dst, 127, size); + } +} + +static WEBP_INLINE void HorizontalPred(uint8_t* dst, + const uint8_t* left, int size) { + if (left != NULL) { + int j; + for (j = 0; j < size; ++j) { + memset(dst + j * BPS, left[j], size); + } + } else { + Fill(dst, 129, size); + } +} + +static WEBP_INLINE void TrueMotion(uint8_t* dst, const uint8_t* left, + const uint8_t* top, int size) { + int y; + if (left != NULL) { + if (top != NULL) { + const uint8_t* const clip = clip1 + 255 - left[-1]; + for (y = 0; y < size; ++y) { + const uint8_t* const clip_table = clip + left[y]; + int x; + for (x = 0; x < size; ++x) { + dst[x] = clip_table[top[x]]; + } + dst += BPS; + } + } else { + HorizontalPred(dst, left, size); + } + } else { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != NULL) { + VerticalPred(dst, top, size); + } else { + Fill(dst, 129, size); + } + } +} + +static WEBP_INLINE void DCMode(uint8_t* dst, const uint8_t* left, + const uint8_t* top, + int size, int round, int shift) { + int DC = 0; + int j; + if (top != NULL) { + for (j = 0; j < size; ++j) DC += top[j]; + if (left != NULL) { // top and left present + for (j = 0; j < size; ++j) DC += left[j]; + } else { // top, but no left + DC += DC; + } + DC = (DC + round) >> shift; + } else if (left != NULL) { // left but no top + for (j = 0; j < size; ++j) DC += left[j]; + DC += DC; + DC = (DC + round) >> shift; + } else { // no top, no left, nothing. + DC = 0x80; + } + Fill(dst, DC, size); +} + +//------------------------------------------------------------------------------ +// Chroma 8x8 prediction (paragraph 12.2) + +static void IntraChromaPreds_C(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DCMode(C8DC8 + dst, left, top, 8, 8, 4); + VerticalPred(C8VE8 + dst, top, 8); + HorizontalPred(C8HE8 + dst, left, 8); + TrueMotion(C8TM8 + dst, left, top, 8); + // V block + dst += 8; + if (top != NULL) top += 8; + if (left != NULL) left += 16; + DCMode(C8DC8 + dst, left, top, 8, 8, 4); + VerticalPred(C8VE8 + dst, top, 8); + HorizontalPred(C8HE8 + dst, left, 8); + TrueMotion(C8TM8 + dst, left, top, 8); +} + +//------------------------------------------------------------------------------ +// luma 16x16 prediction (paragraph 12.3) + +static void Intra16Preds_C(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DCMode(I16DC16 + dst, left, top, 16, 16, 5); + VerticalPred(I16VE16 + dst, top, 16); + HorizontalPred(I16HE16 + dst, left, 16); + TrueMotion(I16TM16 + dst, left, top, 16); +} + +//------------------------------------------------------------------------------ +// luma 4x4 prediction + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2)) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +static void VE4(uint8_t* dst, const uint8_t* top) { // vertical + const uint8_t vals[4] = { + AVG3(top[-1], top[0], top[1]), + AVG3(top[ 0], top[1], top[2]), + AVG3(top[ 1], top[2], top[3]), + AVG3(top[ 2], top[3], top[4]) + }; + int i; + for (i = 0; i < 4; ++i) { + memcpy(dst + i * BPS, vals, 4); + } +} + +static void HE4(uint8_t* dst, const uint8_t* top) { // horizontal + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); +} + +static void DC4(uint8_t* dst, const uint8_t* top) { + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += top[i] + top[-5 + i]; + Fill(dst, dc >> 3, 4); +} + +static void RD4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + DST(0, 3) = AVG3(J, K, L); + DST(0, 2) = DST(1, 3) = AVG3(I, J, K); + DST(0, 1) = DST(1, 2) = DST(2, 3) = AVG3(X, I, J); + DST(0, 0) = DST(1, 1) = DST(2, 2) = DST(3, 3) = AVG3(A, X, I); + DST(1, 0) = DST(2, 1) = DST(3, 2) = AVG3(B, A, X); + DST(2, 0) = DST(3, 1) = AVG3(C, B, A); + DST(3, 0) = AVG3(D, C, B); +} + +static void LD4(uint8_t* dst, const uint8_t* top) { + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + const int E = top[4]; + const int F = top[5]; + const int G = top[6]; + const int H = top[7]; + DST(0, 0) = AVG3(A, B, C); + DST(1, 0) = DST(0, 1) = AVG3(B, C, D); + DST(2, 0) = DST(1, 1) = DST(0, 2) = AVG3(C, D, E); + DST(3, 0) = DST(2, 1) = DST(1, 2) = DST(0, 3) = AVG3(D, E, F); + DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G); + DST(3, 2) = DST(2, 3) = AVG3(F, G, H); + DST(3, 3) = AVG3(G, H, H); +} + +static void VR4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + DST(0, 0) = DST(1, 2) = AVG2(X, A); + DST(1, 0) = DST(2, 2) = AVG2(A, B); + DST(2, 0) = DST(3, 2) = AVG2(B, C); + DST(3, 0) = AVG2(C, D); + + DST(0, 3) = AVG3(K, J, I); + DST(0, 2) = AVG3(J, I, X); + DST(0, 1) = DST(1, 3) = AVG3(I, X, A); + DST(1, 1) = DST(2, 3) = AVG3(X, A, B); + DST(2, 1) = DST(3, 3) = AVG3(A, B, C); + DST(3, 1) = AVG3(B, C, D); +} + +static void VL4(uint8_t* dst, const uint8_t* top) { + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + const int E = top[4]; + const int F = top[5]; + const int G = top[6]; + const int H = top[7]; + DST(0, 0) = AVG2(A, B); + DST(1, 0) = DST(0, 2) = AVG2(B, C); + DST(2, 0) = DST(1, 2) = AVG2(C, D); + DST(3, 0) = DST(2, 2) = AVG2(D, E); + + DST(0, 1) = AVG3(A, B, C); + DST(1, 1) = DST(0, 3) = AVG3(B, C, D); + DST(2, 1) = DST(1, 3) = AVG3(C, D, E); + DST(3, 1) = DST(2, 3) = AVG3(D, E, F); + DST(3, 2) = AVG3(E, F, G); + DST(3, 3) = AVG3(F, G, H); +} + +static void HU4(uint8_t* dst, const uint8_t* top) { + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static void HD4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +static void TM4(uint8_t* dst, const uint8_t* top) { + int x, y; + const uint8_t* const clip = clip1 + 255 - top[-1]; + for (y = 0; y < 4; ++y) { + const uint8_t* const clip_table = clip + top[-2 - y]; + for (x = 0; x < 4; ++x) { + dst[x] = clip_table[top[x]]; + } + dst += BPS; + } +} + +#undef DST +#undef AVG3 +#undef AVG2 + +// Left samples are top[-5 .. -2], top_left is top[-1], top are +// located at top[0..3], and top right is top[4..7] +static void Intra4Preds_C(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +//------------------------------------------------------------------------------ +// Metric + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE int GetSSE(const uint8_t* a, const uint8_t* b, + int w, int h) { + int count = 0; + int y, x; + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + const int diff = (int)a[x] - b[x]; + count += diff * diff; + } + a += BPS; + b += BPS; + } + return count; +} + +static int SSE16x16_C(const uint8_t* a, const uint8_t* b) { + return GetSSE(a, b, 16, 16); +} +static int SSE16x8_C(const uint8_t* a, const uint8_t* b) { + return GetSSE(a, b, 16, 8); +} +static int SSE8x8_C(const uint8_t* a, const uint8_t* b) { + return GetSSE(a, b, 8, 8); +} +static int SSE4x4_C(const uint8_t* a, const uint8_t* b) { + return GetSSE(a, b, 4, 4); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void Mean16x4_C(const uint8_t* ref, uint32_t dc[4]) { + int k, x, y; + for (k = 0; k < 4; ++k) { + uint32_t avg = 0; + for (y = 0; y < 4; ++y) { + for (x = 0; x < 4; ++x) { + avg += ref[x + y * BPS]; + } + } + dc[k] = avg; + ref += 4; // go to next 4x4 block. + } +} + +//------------------------------------------------------------------------------ +// Texture distortion +// +// We try to match the spectral content (weighted) between source and +// reconstructed samples. + +#if !WEBP_NEON_OMIT_C_CODE +// Hadamard transform +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int TTransform(const uint8_t* in, const uint16_t* w) { + int sum = 0; + int tmp[16]; + int i; + // horizontal pass + for (i = 0; i < 4; ++i, in += BPS) { + const int a0 = in[0] + in[2]; + const int a1 = in[1] + in[3]; + const int a2 = in[1] - in[3]; + const int a3 = in[0] - in[2]; + tmp[0 + i * 4] = a0 + a1; + tmp[1 + i * 4] = a3 + a2; + tmp[2 + i * 4] = a3 - a2; + tmp[3 + i * 4] = a0 - a1; + } + // vertical pass + for (i = 0; i < 4; ++i, ++w) { + const int a0 = tmp[0 + i] + tmp[8 + i]; + const int a1 = tmp[4 + i] + tmp[12+ i]; + const int a2 = tmp[4 + i] - tmp[12+ i]; + const int a3 = tmp[0 + i] - tmp[8 + i]; + const int b0 = a0 + a1; + const int b1 = a3 + a2; + const int b2 = a3 - a2; + const int b3 = a0 - a1; + + sum += w[ 0] * abs(b0); + sum += w[ 4] * abs(b1); + sum += w[ 8] * abs(b2); + sum += w[12] * abs(b3); + } + return sum; +} + +static int Disto4x4_C(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int sum1 = TTransform(a, w); + const int sum2 = TTransform(b, w); + return abs(sum2 - sum1) >> 5; +} + +static int Disto16x16_C(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_C(a + x + y, b + x + y, w); + } + } + return D; +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Quantization +// + +static const uint8_t kZigzag[16] = { + 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 +}; + +// Simple quantization +static int QuantizeBlock_C(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int last = -1; + int n; + for (n = 0; n < 16; ++n) { + const int j = kZigzag[n]; + const int sign = (in[j] < 0); + const uint32_t coeff = (sign ? -in[j] : in[j]) + mtx->sharpen_[j]; + if (coeff > mtx->zthresh_[j]) { + const uint32_t Q = mtx->q_[j]; + const uint32_t iQ = mtx->iq_[j]; + const uint32_t B = mtx->bias_[j]; + int level = QUANTDIV(coeff, iQ, B); + if (level > MAX_LEVEL) level = MAX_LEVEL; + if (sign) level = -level; + in[j] = level * (int)Q; + out[n] = level; + if (level) last = n; + } else { + out[n] = 0; + in[j] = 0; + } + } + return (last >= 0); +} + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static int Quantize2Blocks_C(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = VP8EncQuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= VP8EncQuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Block copy + +static WEBP_INLINE void Copy(const uint8_t* src, uint8_t* dst, int w, int h) { + int y; + for (y = 0; y < h; ++y) { + memcpy(dst, src, w); + src += BPS; + dst += BPS; + } +} + +static void Copy4x4_C(const uint8_t* src, uint8_t* dst) { + Copy(src, dst, 4, 4); +} + +static void Copy16x8_C(const uint8_t* src, uint8_t* dst) { + Copy(src, dst, 16, 8); +} + +//------------------------------------------------------------------------------ +// Initialization + +// Speed-critical function pointers. We have to initialize them to the default +// implementations within VP8EncDspInit(). +VP8CHisto VP8CollectHistogram; +VP8Idct VP8ITransform; +VP8Fdct VP8FTransform; +VP8Fdct VP8FTransform2; +VP8WHT VP8FTransformWHT; +VP8Intra4Preds VP8EncPredLuma4; +VP8IntraPreds VP8EncPredLuma16; +VP8IntraPreds VP8EncPredChroma8; +VP8Metric VP8SSE16x16; +VP8Metric VP8SSE8x8; +VP8Metric VP8SSE16x8; +VP8Metric VP8SSE4x4; +VP8WMetric VP8TDisto4x4; +VP8WMetric VP8TDisto16x16; +VP8MeanMetric VP8Mean16x4; +VP8QuantizeBlock VP8EncQuantizeBlock; +VP8Quantize2Blocks VP8EncQuantize2Blocks; +VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT; +VP8BlockCopy VP8Copy4x4; +VP8BlockCopy VP8Copy16x8; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8EncDspInitSSE2(void); +extern void VP8EncDspInitSSE41(void); +extern void VP8EncDspInitNEON(void); +extern void VP8EncDspInitMIPS32(void); +extern void VP8EncDspInitMIPSdspR2(void); +extern void VP8EncDspInitMSA(void); + +WEBP_DSP_INIT_FUNC(VP8EncDspInit) { + VP8DspInit(); // common inverse transforms + InitTables(); + + // default C implementations +#if !WEBP_NEON_OMIT_C_CODE + VP8ITransform = ITransform_C; + VP8FTransform = FTransform_C; + VP8FTransformWHT = FTransformWHT_C; + VP8TDisto4x4 = Disto4x4_C; + VP8TDisto16x16 = Disto16x16_C; + VP8CollectHistogram = CollectHistogram_C; + VP8SSE16x16 = SSE16x16_C; + VP8SSE16x8 = SSE16x8_C; + VP8SSE8x8 = SSE8x8_C; + VP8SSE4x4 = SSE4x4_C; +#endif + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + VP8EncQuantizeBlock = QuantizeBlock_C; + VP8EncQuantize2Blocks = Quantize2Blocks_C; +#endif + + VP8FTransform2 = FTransform2_C; + VP8EncPredLuma4 = Intra4Preds_C; + VP8EncPredLuma16 = Intra16Preds_C; + VP8EncPredChroma8 = IntraChromaPreds_C; + VP8Mean16x4 = Mean16x4_C; + VP8EncQuantizeBlockWHT = QuantizeBlock_C; + VP8Copy4x4 = Copy4x4_C; + VP8Copy16x8 = Copy16x8_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8EncDspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8EncDspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8EncDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8EncDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8EncDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8EncDspInitNEON(); + } +#endif + + assert(VP8ITransform != NULL); + assert(VP8FTransform != NULL); + assert(VP8FTransformWHT != NULL); + assert(VP8TDisto4x4 != NULL); + assert(VP8TDisto16x16 != NULL); + assert(VP8CollectHistogram != NULL); + assert(VP8SSE16x16 != NULL); + assert(VP8SSE16x8 != NULL); + assert(VP8SSE8x8 != NULL); + assert(VP8SSE4x4 != NULL); + assert(VP8EncQuantizeBlock != NULL); + assert(VP8EncQuantize2Blocks != NULL); + assert(VP8FTransform2 != NULL); + assert(VP8EncPredLuma4 != NULL); + assert(VP8EncPredLuma16 != NULL); + assert(VP8EncPredChroma8 != NULL); + assert(VP8Mean16x4 != NULL); + assert(VP8EncQuantizeBlockWHT != NULL); + assert(VP8Copy4x4 != NULL); + assert(VP8Copy16x8 != NULL); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips32.c new file mode 100644 index 0000000000..50518a5f1a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips32.c @@ -0,0 +1,673 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of speed-critical encoding functions. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) +// Slobodan Prijic (slobodan.prijic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "src/dsp/mips_macro.h" +#include "src/enc/vp8i_enc.h" +#include "src/enc/cost_enc.h" + +static const int kC1 = WEBP_TRANSFORM_AC3_C1; +static const int kC2 = WEBP_TRANSFORM_AC3_C2; + +// macro for one vertical pass in ITransformOne +// MUL macro inlined +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to load from in buffer +// TEMP0..TEMP3 - registers for corresponding tmp elements +// TEMP4..TEMP5 - temporary registers +#define VERTICAL_PASS(A, B, C, D, TEMP4, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lh %[temp16], " #A "(%[temp20]) \n\t" \ + "lh %[temp18], " #B "(%[temp20]) \n\t" \ + "lh %[temp17], " #C "(%[temp20]) \n\t" \ + "lh %[temp19], " #D "(%[temp20]) \n\t" \ + "addu %[" #TEMP4 "], %[temp16], %[temp18] \n\t" \ + "subu %[temp16], %[temp16], %[temp18] \n\t" \ + "mul %[" #TEMP0 "], %[temp17], %[kC2] \n\t" \ + MUL_SHIFT_C1_IO(temp17, temp18) \ + MUL_SHIFT_C1(temp18, temp19) \ + "mul %[temp19], %[temp19], %[kC2] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\n" \ + "sra %[temp19], %[temp19], 16 \n\n" \ + "subu %[" #TEMP2 "], %[" #TEMP0 "], %[temp18] \n\t" \ + "addu %[" #TEMP3 "], %[temp17], %[temp19] \n\t" \ + "addu %[" #TEMP0 "], %[" #TEMP4 "], %[" #TEMP3 "] \n\t" \ + "addu %[" #TEMP1 "], %[temp16], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP2 "], %[temp16], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP3 "], %[" #TEMP4 "], %[" #TEMP3 "] \n\t" + +// macro for one horizontal pass in ITransformOne +// MUL and STORE macros inlined +// a = clip_8b(a) is replaced with: a = max(a, 0); a = min(a, 255) +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from ref and store to dst buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addiu %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp17], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP0 "], %[" #TEMP4 "], %[kC2] \n\t" \ + MUL_SHIFT_C1_IO(TEMP4, TEMP8) \ + MUL_SHIFT_C1(TEMP8, TEMP12) \ + "mul %[" #TEMP12 "], %[" #TEMP12 "], %[kC2] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "subu %[temp18], %[" #TEMP0 "], %[" #TEMP8 "] \n\t" \ + "addu %[temp19], %[" #TEMP4 "], %[" #TEMP12 "] \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp19] \n\t" \ + "addu %[" #TEMP4 "], %[temp17], %[temp18] \n\t" \ + "subu %[" #TEMP8 "], %[temp17], %[temp18] \n\t" \ + "subu %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "lw %[temp20], 0(%[args]) \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 3 \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 3 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 3 \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 3 \n\t" \ + "lbu %[temp16], 0+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp17], 1+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp18], 2+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "lbu %[temp19], 3+" XSTR(BPS) "*" #A "(%[temp20]) \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[" #TEMP0 "] \n\t" \ + "addu %[" #TEMP4 "], %[temp17], %[" #TEMP4 "] \n\t" \ + "addu %[" #TEMP8 "], %[temp18], %[" #TEMP8 "] \n\t" \ + "addu %[" #TEMP12 "], %[temp19], %[" #TEMP12 "] \n\t" \ + "slt %[temp16], %[" #TEMP0 "], $zero \n\t" \ + "slt %[temp17], %[" #TEMP4 "], $zero \n\t" \ + "slt %[temp18], %[" #TEMP8 "], $zero \n\t" \ + "slt %[temp19], %[" #TEMP12 "], $zero \n\t" \ + "movn %[" #TEMP0 "], $zero, %[temp16] \n\t" \ + "movn %[" #TEMP4 "], $zero, %[temp17] \n\t" \ + "movn %[" #TEMP8 "], $zero, %[temp18] \n\t" \ + "movn %[" #TEMP12 "], $zero, %[temp19] \n\t" \ + "addiu %[temp20], $zero, 255 \n\t" \ + "slt %[temp16], %[" #TEMP0 "], %[temp20] \n\t" \ + "slt %[temp17], %[" #TEMP4 "], %[temp20] \n\t" \ + "slt %[temp18], %[" #TEMP8 "], %[temp20] \n\t" \ + "slt %[temp19], %[" #TEMP12 "], %[temp20] \n\t" \ + "movz %[" #TEMP0 "], %[temp20], %[temp16] \n\t" \ + "movz %[" #TEMP4 "], %[temp20], %[temp17] \n\t" \ + "lw %[temp16], 8(%[args]) \n\t" \ + "movz %[" #TEMP8 "], %[temp20], %[temp18] \n\t" \ + "movz %[" #TEMP12 "], %[temp20], %[temp19] \n\t" \ + "sb %[" #TEMP0 "], 0+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP4 "], 1+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP8 "], 2+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" \ + "sb %[" #TEMP12 "], 3+" XSTR(BPS) "*" #A "(%[temp16]) \n\t" + +// Does one or two inverse transforms. +static WEBP_INLINE void ITransformOne_MIPS32(const uint8_t* ref, + const int16_t* in, + uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + int temp7, temp8, temp9, temp10, temp11, temp12, temp13; + int temp14, temp15, temp16, temp17, temp18, temp19, temp20; + const int* args[3] = {(const int*)ref, (const int*)in, (const int*)dst}; + + __asm__ volatile( + "lw %[temp20], 4(%[args]) \n\t" + VERTICAL_PASS(0, 16, 8, 24, temp4, temp0, temp1, temp2, temp3) + VERTICAL_PASS(2, 18, 10, 26, temp8, temp4, temp5, temp6, temp7) + VERTICAL_PASS(4, 20, 12, 28, temp12, temp8, temp9, temp10, temp11) + VERTICAL_PASS(6, 22, 14, 30, temp20, temp12, temp13, temp14, temp15) + + HORIZONTAL_PASS(0, temp0, temp4, temp8, temp12) + HORIZONTAL_PASS(1, temp1, temp5, temp9, temp13) + HORIZONTAL_PASS(2, temp2, temp6, temp10, temp14) + HORIZONTAL_PASS(3, temp3, temp7, temp11, temp15) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [kC1]"r"(kC1), [kC2]"r"(kC2) + : "memory", "hi", "lo" + ); +} + +static void ITransform_MIPS32(const uint8_t* ref, const int16_t* in, + uint8_t* dst, int do_two) { + ITransformOne_MIPS32(ref, in, dst); + if (do_two) { + ITransformOne_MIPS32(ref + 4, in + 16, dst + 4); + } +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +// macro for one pass through for loop in QuantizeBlock +// QUANTDIV macro inlined +// J - offset in bytes (kZigzag[n] * 2) +// K - offset in bytes (kZigzag[n] * 4) +// N - offset in bytes (n * 2) +#define QUANTIZE_ONE(J, K, N) \ + "lh %[temp0], " #J "(%[ppin]) \n\t" \ + "lhu %[temp1], " #J "(%[ppsharpen]) \n\t" \ + "lw %[temp2], " #K "(%[ppzthresh]) \n\t" \ + "sra %[sign], %[temp0], 15 \n\t" \ + "xor %[coeff], %[temp0], %[sign] \n\t" \ + "subu %[coeff], %[coeff], %[sign] \n\t" \ + "addu %[coeff], %[coeff], %[temp1] \n\t" \ + "slt %[temp4], %[temp2], %[coeff] \n\t" \ + "addiu %[temp5], $zero, 0 \n\t" \ + "addiu %[level], $zero, 0 \n\t" \ + "beqz %[temp4], 2f \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "lhu %[temp3], " #J "(%[ppq]) \n\t" \ + "mul %[level], %[coeff], %[temp1] \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "xor %[level], %[level], %[sign] \n\t" \ + "subu %[level], %[level], %[sign] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ +"2: \n\t" \ + "sh %[temp5], " #J "(%[ppin]) \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" + +static int QuantizeBlock_MIPS32(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int sign, coeff, level, i; + int max_level = MAX_LEVEL; + + int16_t* ppin = &in[0]; + int16_t* pout = &out[0]; + const uint16_t* ppsharpen = &mtx->sharpen_[0]; + const uint32_t* ppzthresh = &mtx->zthresh_[0]; + const uint16_t* ppq = &mtx->q_[0]; + const uint16_t* ppiq = &mtx->iq_[0]; + const uint32_t* ppbias = &mtx->bias_[0]; + + __asm__ volatile( + QUANTIZE_ONE( 0, 0, 0) + QUANTIZE_ONE( 2, 4, 2) + QUANTIZE_ONE( 8, 16, 4) + QUANTIZE_ONE(16, 32, 6) + QUANTIZE_ONE(10, 20, 8) + QUANTIZE_ONE( 4, 8, 10) + QUANTIZE_ONE( 6, 12, 12) + QUANTIZE_ONE(12, 24, 14) + QUANTIZE_ONE(18, 36, 16) + QUANTIZE_ONE(24, 48, 18) + QUANTIZE_ONE(26, 52, 20) + QUANTIZE_ONE(20, 40, 22) + QUANTIZE_ONE(14, 28, 24) + QUANTIZE_ONE(22, 44, 26) + QUANTIZE_ONE(28, 56, 28) + QUANTIZE_ONE(30, 60, 30) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [sign]"=&r"(sign), [coeff]"=&r"(coeff), + [level]"=&r"(level) + : [pout]"r"(pout), [ppin]"r"(ppin), + [ppiq]"r"(ppiq), [max_level]"r"(max_level), + [ppbias]"r"(ppbias), [ppzthresh]"r"(ppzthresh), + [ppsharpen]"r"(ppsharpen), [ppq]"r"(ppq) + : "memory", "hi", "lo" + ); + + // moved out from macro to increase possibility for earlier breaking + for (i = 15; i >= 0; i--) { + if (out[i]) return 1; + } + return 0; +} + +static int Quantize2Blocks_MIPS32(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock_MIPS32(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock_MIPS32(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#undef QUANTIZE_ONE + +// macro for one horizontal pass in Disto4x4 (TTransform) +// two calls of function TTransform are merged into single one +// A - offset in bytes to load from a and b buffers +// E..H - offsets in bytes to store first results to tmp buffer +// E1..H1 - offsets in bytes to store second results to tmp buffer +#define HORIZONTAL_PASS(A, E, F, G, H, E1, F1, G1, H1) \ + "lbu %[temp0], 0+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp1], 1+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp2], 2+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp3], 3+" XSTR(BPS) "*" #A "(%[a]) \n\t" \ + "lbu %[temp4], 0+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp5], 1+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp6], 2+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "lbu %[temp7], 3+" XSTR(BPS) "*" #A "(%[b]) \n\t" \ + "addu %[temp8], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "addu %[temp2], %[temp1], %[temp3] \n\t" \ + "subu %[temp1], %[temp1], %[temp3] \n\t" \ + "addu %[temp3], %[temp4], %[temp6] \n\t" \ + "subu %[temp4], %[temp4], %[temp6] \n\t" \ + "addu %[temp6], %[temp5], %[temp7] \n\t" \ + "subu %[temp5], %[temp5], %[temp7] \n\t" \ + "addu %[temp7], %[temp8], %[temp2] \n\t" \ + "subu %[temp2], %[temp8], %[temp2] \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp3], %[temp6] \n\t" \ + "subu %[temp3], %[temp3], %[temp6] \n\t" \ + "addu %[temp6], %[temp4], %[temp5] \n\t" \ + "subu %[temp4], %[temp4], %[temp5] \n\t" \ + "sw %[temp7], " #E "(%[tmp]) \n\t" \ + "sw %[temp2], " #H "(%[tmp]) \n\t" \ + "sw %[temp8], " #F "(%[tmp]) \n\t" \ + "sw %[temp0], " #G "(%[tmp]) \n\t" \ + "sw %[temp1], " #E1 "(%[tmp]) \n\t" \ + "sw %[temp3], " #H1 "(%[tmp]) \n\t" \ + "sw %[temp6], " #F1 "(%[tmp]) \n\t" \ + "sw %[temp4], " #G1 "(%[tmp]) \n\t" + +// macro for one vertical pass in Disto4x4 (TTransform) +// two calls of function TTransform are merged into single one +// since only one accu is available in mips32r1 instruction set +// first is done second call of function TTransform and after +// that first one. +// const int sum1 = TTransform(a, w); +// const int sum2 = TTransform(b, w); +// return abs(sum2 - sum1) >> 5; +// (sum2 - sum1) is calculated with madds (sub2) and msubs (sub1) +// A..D - offsets in bytes to load first results from tmp buffer +// A1..D1 - offsets in bytes to load second results from tmp buffer +// E..H - offsets in bytes to load from w buffer +#define VERTICAL_PASS(A, B, C, D, A1, B1, C1, D1, E, F, G, H) \ + "lw %[temp0], " #A1 "(%[tmp]) \n\t" \ + "lw %[temp1], " #C1 "(%[tmp]) \n\t" \ + "lw %[temp2], " #B1 "(%[tmp]) \n\t" \ + "lw %[temp3], " #D1 "(%[tmp]) \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp2], %[temp3] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "addu %[temp3], %[temp8], %[temp1] \n\t" \ + "subu %[temp8], %[temp8], %[temp1] \n\t" \ + "addu %[temp1], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "sra %[temp4], %[temp3], 31 \n\t" \ + "sra %[temp5], %[temp1], 31 \n\t" \ + "sra %[temp6], %[temp0], 31 \n\t" \ + "sra %[temp7], %[temp8], 31 \n\t" \ + "xor %[temp3], %[temp3], %[temp4] \n\t" \ + "xor %[temp1], %[temp1], %[temp5] \n\t" \ + "xor %[temp0], %[temp0], %[temp6] \n\t" \ + "xor %[temp8], %[temp8], %[temp7] \n\t" \ + "subu %[temp3], %[temp3], %[temp4] \n\t" \ + "subu %[temp1], %[temp1], %[temp5] \n\t" \ + "subu %[temp0], %[temp0], %[temp6] \n\t" \ + "subu %[temp8], %[temp8], %[temp7] \n\t" \ + "lhu %[temp4], " #E "(%[w]) \n\t" \ + "lhu %[temp5], " #F "(%[w]) \n\t" \ + "lhu %[temp6], " #G "(%[w]) \n\t" \ + "lhu %[temp7], " #H "(%[w]) \n\t" \ + "madd %[temp4], %[temp3] \n\t" \ + "madd %[temp5], %[temp1] \n\t" \ + "madd %[temp6], %[temp0] \n\t" \ + "madd %[temp7], %[temp8] \n\t" \ + "lw %[temp0], " #A "(%[tmp]) \n\t" \ + "lw %[temp1], " #C "(%[tmp]) \n\t" \ + "lw %[temp2], " #B "(%[tmp]) \n\t" \ + "lw %[temp3], " #D "(%[tmp]) \n\t" \ + "addu %[temp8], %[temp0], %[temp1] \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "addu %[temp1], %[temp2], %[temp3] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "addu %[temp3], %[temp8], %[temp1] \n\t" \ + "subu %[temp1], %[temp8], %[temp1] \n\t" \ + "addu %[temp8], %[temp0], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp2] \n\t" \ + "sra %[temp2], %[temp3], 31 \n\t" \ + "xor %[temp3], %[temp3], %[temp2] \n\t" \ + "subu %[temp3], %[temp3], %[temp2] \n\t" \ + "msub %[temp4], %[temp3] \n\t" \ + "sra %[temp2], %[temp8], 31 \n\t" \ + "sra %[temp3], %[temp0], 31 \n\t" \ + "sra %[temp4], %[temp1], 31 \n\t" \ + "xor %[temp8], %[temp8], %[temp2] \n\t" \ + "xor %[temp0], %[temp0], %[temp3] \n\t" \ + "xor %[temp1], %[temp1], %[temp4] \n\t" \ + "subu %[temp8], %[temp8], %[temp2] \n\t" \ + "subu %[temp0], %[temp0], %[temp3] \n\t" \ + "subu %[temp1], %[temp1], %[temp4] \n\t" \ + "msub %[temp5], %[temp8] \n\t" \ + "msub %[temp6], %[temp0] \n\t" \ + "msub %[temp7], %[temp1] \n\t" + +static int Disto4x4_MIPS32(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int tmp[32]; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + + __asm__ volatile( + HORIZONTAL_PASS(0, 0, 4, 8, 12, 64, 68, 72, 76) + HORIZONTAL_PASS(1, 16, 20, 24, 28, 80, 84, 88, 92) + HORIZONTAL_PASS(2, 32, 36, 40, 44, 96, 100, 104, 108) + HORIZONTAL_PASS(3, 48, 52, 56, 60, 112, 116, 120, 124) + "mthi $zero \n\t" + "mtlo $zero \n\t" + VERTICAL_PASS( 0, 16, 32, 48, 64, 80, 96, 112, 0, 8, 16, 24) + VERTICAL_PASS( 4, 20, 36, 52, 68, 84, 100, 116, 2, 10, 18, 26) + VERTICAL_PASS( 8, 24, 40, 56, 72, 88, 104, 120, 4, 12, 20, 28) + VERTICAL_PASS(12, 28, 44, 60, 76, 92, 108, 124, 6, 14, 22, 30) + "mflo %[temp0] \n\t" + "sra %[temp1], %[temp0], 31 \n\t" + "xor %[temp0], %[temp0], %[temp1] \n\t" + "subu %[temp0], %[temp0], %[temp1] \n\t" + "sra %[temp0], %[temp0], 5 \n\t" + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [a]"r"(a), [b]"r"(b), [w]"r"(w), [tmp]"r"(tmp) + : "memory", "hi", "lo" + ); + + return temp0; +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +static int Disto16x16_MIPS32(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_MIPS32(a + x + y, b + x + y, w); + } + } + return D; +} + +// macro for one horizontal pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from src and ref buffers +// TEMP0..TEMP3 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lw %[" #TEMP1 "], 0(%[args]) \n\t" \ + "lw %[" #TEMP2 "], 4(%[args]) \n\t" \ + "lbu %[temp16], 0+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp17], 0+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "lbu %[temp18], 1+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp19], 1+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[temp20], %[temp16], %[temp17] \n\t" \ + "lbu %[temp16], 2+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp17], 2+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[" #TEMP0 "], %[temp18], %[temp19] \n\t" \ + "lbu %[temp18], 3+" XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "lbu %[temp19], 3+" XSTR(BPS) "*" #A "(%[" #TEMP2 "]) \n\t" \ + "subu %[" #TEMP1 "], %[temp16], %[temp17] \n\t" \ + "subu %[" #TEMP2 "], %[temp18], %[temp19] \n\t" \ + "addu %[" #TEMP3 "], %[temp20], %[" #TEMP2 "] \n\t" \ + "subu %[" #TEMP2 "], %[temp20], %[" #TEMP2 "] \n\t" \ + "addu %[temp20], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "subu %[" #TEMP0 "], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "mul %[temp16], %[" #TEMP2 "], %[c5352] \n\t" \ + "mul %[temp17], %[" #TEMP2 "], %[c2217] \n\t" \ + "mul %[temp18], %[" #TEMP0 "], %[c5352] \n\t" \ + "mul %[temp19], %[" #TEMP0 "], %[c2217] \n\t" \ + "addu %[" #TEMP1 "], %[" #TEMP3 "], %[temp20] \n\t" \ + "subu %[temp20], %[" #TEMP3 "], %[temp20] \n\t" \ + "sll %[" #TEMP0 "], %[" #TEMP1 "], 3 \n\t" \ + "sll %[" #TEMP2 "], %[temp20], 3 \n\t" \ + "addiu %[temp16], %[temp16], 1812 \n\t" \ + "addiu %[temp17], %[temp17], 937 \n\t" \ + "addu %[temp16], %[temp16], %[temp19] \n\t" \ + "subu %[temp17], %[temp17], %[temp18] \n\t" \ + "sra %[" #TEMP1 "], %[temp16], 9 \n\t" \ + "sra %[" #TEMP3 "], %[temp17], 9 \n\t" + +// macro for one vertical pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to store to out buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define VERTICAL_PASS(A, B, C, D, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "subu %[temp19], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "addu %[temp17], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp18], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP8 "], %[temp19], %[c2217] \n\t" \ + "mul %[" #TEMP12 "], %[temp18], %[c2217] \n\t" \ + "mul %[" #TEMP4 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp18], %[temp18], %[c5352] \n\t" \ + "addiu %[temp16], %[temp16], 7 \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[" #TEMP12 "], %[" #TEMP12 "], %[" #TEMP4 "] \n\t" \ + "subu %[" #TEMP4 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 4 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 30000 \n\t" \ + "addiu %[" #TEMP12 "], %[" #TEMP12 "], 12000 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 21000 \n\t" \ + "subu %[" #TEMP8 "], %[" #TEMP8 "], %[temp18] \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 16 \n\t" \ + "addiu %[temp16], %[" #TEMP12 "], 1 \n\t" \ + "movn %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "sh %[" #TEMP0 "], " #A "(%[temp20]) \n\t" \ + "sh %[" #TEMP4 "], " #C "(%[temp20]) \n\t" \ + "sh %[" #TEMP8 "], " #D "(%[temp20]) \n\t" \ + "sh %[" #TEMP12 "], " #B "(%[temp20]) \n\t" + +static void FTransform_MIPS32(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + int temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16; + int temp17, temp18, temp19, temp20; + const int c2217 = 2217; + const int c5352 = 5352; + const int* const args[3] = + { (const int*)src, (const int*)ref, (const int*)out }; + + __asm__ volatile( + HORIZONTAL_PASS(0, temp0, temp1, temp2, temp3) + HORIZONTAL_PASS(1, temp4, temp5, temp6, temp7) + HORIZONTAL_PASS(2, temp8, temp9, temp10, temp11) + HORIZONTAL_PASS(3, temp12, temp13, temp14, temp15) + "lw %[temp20], 8(%[args]) \n\t" + VERTICAL_PASS(0, 8, 16, 24, temp0, temp4, temp8, temp12) + VERTICAL_PASS(2, 10, 18, 26, temp1, temp5, temp9, temp13) + VERTICAL_PASS(4, 12, 20, 28, temp2, temp6, temp10, temp14) + VERTICAL_PASS(6, 14, 22, 30, temp3, temp7, temp11, temp15) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11), + [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), [temp14]"=&r"(temp14), + [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), [temp17]"=&r"(temp17), + [temp18]"=&r"(temp18), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [c2217]"r"(c2217), [c5352]"r"(c5352) + : "memory", "hi", "lo" + ); +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +#if !defined(WORK_AROUND_GCC) + +#define GET_SSE_INNER(A, B, C, D) \ + "lbu %[temp0], " #A "(%[a]) \n\t" \ + "lbu %[temp1], " #A "(%[b]) \n\t" \ + "lbu %[temp2], " #B "(%[a]) \n\t" \ + "lbu %[temp3], " #B "(%[b]) \n\t" \ + "lbu %[temp4], " #C "(%[a]) \n\t" \ + "lbu %[temp5], " #C "(%[b]) \n\t" \ + "lbu %[temp6], " #D "(%[a]) \n\t" \ + "lbu %[temp7], " #D "(%[b]) \n\t" \ + "subu %[temp0], %[temp0], %[temp1] \n\t" \ + "subu %[temp2], %[temp2], %[temp3] \n\t" \ + "subu %[temp4], %[temp4], %[temp5] \n\t" \ + "subu %[temp6], %[temp6], %[temp7] \n\t" \ + "madd %[temp0], %[temp0] \n\t" \ + "madd %[temp2], %[temp2] \n\t" \ + "madd %[temp4], %[temp4] \n\t" \ + "madd %[temp6], %[temp6] \n\t" + +#define GET_SSE(A, B, C, D) \ + GET_SSE_INNER(A, A + 1, A + 2, A + 3) \ + GET_SSE_INNER(B, B + 1, B + 2, B + 3) \ + GET_SSE_INNER(C, C + 1, C + 2, C + 3) \ + GET_SSE_INNER(D, D + 1, D + 2, D + 3) + +static int SSE16x16_MIPS32(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + GET_SSE( 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS) + GET_SSE( 9 * BPS, 4 + 9 * BPS, 8 + 9 * BPS, 12 + 9 * BPS) + GET_SSE(10 * BPS, 4 + 10 * BPS, 8 + 10 * BPS, 12 + 10 * BPS) + GET_SSE(11 * BPS, 4 + 11 * BPS, 8 + 11 * BPS, 12 + 11 * BPS) + GET_SSE(12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS) + GET_SSE(13 * BPS, 4 + 13 * BPS, 8 + 13 * BPS, 12 + 13 * BPS) + GET_SSE(14 * BPS, 4 + 14 * BPS, 8 + 14 * BPS, 12 + 14 * BPS) + GET_SSE(15 * BPS, 4 + 15 * BPS, 8 + 15 * BPS, 12 + 15 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE16x8_MIPS32(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE8x8_MIPS32(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE(0 * BPS, 4 + 0 * BPS, 1 * BPS, 4 + 1 * BPS) + GET_SSE(2 * BPS, 4 + 2 * BPS, 3 * BPS, 4 + 3 * BPS) + GET_SSE(4 * BPS, 4 + 4 * BPS, 5 * BPS, 4 + 5 * BPS) + GET_SSE(6 * BPS, 4 + 6 * BPS, 7 * BPS, 4 + 7 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE4x4_MIPS32(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + + __asm__ volatile( + "mult $zero, $zero \n\t" + + GET_SSE(0 * BPS, 1 * BPS, 2 * BPS, 3 * BPS) + + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +#undef GET_SSE +#undef GET_SSE_INNER + +#endif // !WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMIPS32(void) { + VP8ITransform = ITransform_MIPS32; + VP8FTransform = FTransform_MIPS32; + + VP8EncQuantizeBlock = QuantizeBlock_MIPS32; + VP8EncQuantize2Blocks = Quantize2Blocks_MIPS32; + + VP8TDisto4x4 = Disto4x4_MIPS32; + VP8TDisto16x16 = Disto16x16_MIPS32; + +#if !defined(WORK_AROUND_GCC) + VP8SSE16x16 = SSE16x16_MIPS32; + VP8SSE8x8 = SSE8x8_MIPS32; + VP8SSE16x8 = SSE16x8_MIPS32; + VP8SSE4x4 = SSE4x4_MIPS32; +#endif +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8EncDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips_dsp_r2.c new file mode 100644 index 0000000000..e1431f3bef --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_mips_dsp_r2.c @@ -0,0 +1,1517 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of speed-critical encoding functions. +// +// Author(s): Darko Laus (darko.laus@imgtec.com) +// Mirko Raus (mirko.raus@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/mips_macro.h" +#include "src/enc/cost_enc.h" +#include "src/enc/vp8i_enc.h" + +static const int kC1 = WEBP_TRANSFORM_AC3_C1; +static const int kC2 = WEBP_TRANSFORM_AC3_C2; + +// O - output +// I - input (macro doesn't change it) +#define ADD_SUB_HALVES_X4(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "addq.ph %[" #O2 "], %[" #I2 "], %[" #I3 "] \n\t" \ + "subq.ph %[" #O3 "], %[" #I2 "], %[" #I3 "] \n\t" \ + "addq.ph %[" #O4 "], %[" #I4 "], %[" #I5 "] \n\t" \ + "subq.ph %[" #O5 "], %[" #I4 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #O6 "], %[" #I6 "], %[" #I7 "] \n\t" \ + "subq.ph %[" #O7 "], %[" #I6 "], %[" #I7 "] \n\t" + +// IO - input/output +#define ABS_X8(IO0, IO1, IO2, IO3, IO4, IO5, IO6, IO7) \ + "absq_s.ph %[" #IO0 "], %[" #IO0 "] \n\t" \ + "absq_s.ph %[" #IO1 "], %[" #IO1 "] \n\t" \ + "absq_s.ph %[" #IO2 "], %[" #IO2 "] \n\t" \ + "absq_s.ph %[" #IO3 "], %[" #IO3 "] \n\t" \ + "absq_s.ph %[" #IO4 "], %[" #IO4 "] \n\t" \ + "absq_s.ph %[" #IO5 "], %[" #IO5 "] \n\t" \ + "absq_s.ph %[" #IO6 "], %[" #IO6 "] \n\t" \ + "absq_s.ph %[" #IO7 "], %[" #IO7 "] \n\t" + +// dpa.w.ph $ac0 temp0 ,temp1 +// $ac += temp0[31..16] * temp1[31..16] + temp0[15..0] * temp1[15..0] +// dpax.w.ph $ac0 temp0 ,temp1 +// $ac += temp0[31..16] * temp1[15..0] + temp0[15..0] * temp1[31..16] +// O - output +// I - input (macro doesn't change it) +#define MUL_HALF(O0, I0, I1, I2, I3, I4, I5, I6, I7, \ + I8, I9, I10, I11, I12, I13, I14, I15) \ + "mult $ac0, $zero, $zero \n\t" \ + "dpa.w.ph $ac0, %[" #I2 "], %[" #I0 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I5 "], %[" #I6 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I8 "], %[" #I9 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I11 "], %[" #I4 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I12 "], %[" #I7 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I13 "], %[" #I1 "] \n\t" \ + "dpa.w.ph $ac0, %[" #I14 "], %[" #I3 "] \n\t" \ + "dpax.w.ph $ac0, %[" #I15 "], %[" #I10 "] \n\t" \ + "mflo %[" #O0 "], $ac0 \n\t" + +#define OUTPUT_EARLY_CLOBBER_REGS_17() \ + OUTPUT_EARLY_CLOBBER_REGS_10(), \ + [temp11]"=&r"(temp11), [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), \ + [temp14]"=&r"(temp14), [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), \ + [temp17]"=&r"(temp17) + +// macro for one horizontal pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A - offset in bytes to load from src and ref buffers +// TEMP0..TEMP3 - registers for corresponding tmp elements +#define HORIZONTAL_PASS(A, TEMP0, TEMP1, TEMP2, TEMP3) \ + "lw %[" #TEMP0 "], 0(%[args]) \n\t" \ + "lw %[" #TEMP1 "], 4(%[args]) \n\t" \ + "lw %[" #TEMP2 "], " XSTR(BPS) "*" #A "(%[" #TEMP0 "]) \n\t" \ + "lw %[" #TEMP3 "], " XSTR(BPS) "*" #A "(%[" #TEMP1 "]) \n\t" \ + "preceu.ph.qbl %[" #TEMP0 "], %[" #TEMP2 "] \n\t" \ + "preceu.ph.qbl %[" #TEMP1 "], %[" #TEMP3 "] \n\t" \ + "preceu.ph.qbr %[" #TEMP2 "], %[" #TEMP2 "] \n\t" \ + "preceu.ph.qbr %[" #TEMP3 "], %[" #TEMP3 "] \n\t" \ + "subq.ph %[" #TEMP0 "], %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "subq.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP3 "] \n\t" \ + "rotr %[" #TEMP0 "], %[" #TEMP0 "], 16 \n\t" \ + "addq.ph %[" #TEMP1 "], %[" #TEMP2 "], %[" #TEMP0 "] \n\t" \ + "subq.ph %[" #TEMP3 "], %[" #TEMP2 "], %[" #TEMP0 "] \n\t" \ + "seh %[" #TEMP0 "], %[" #TEMP1 "] \n\t" \ + "sra %[temp16], %[" #TEMP1 "], 16 \n\t" \ + "seh %[temp19], %[" #TEMP3 "] \n\t" \ + "sra %[" #TEMP3 "], %[" #TEMP3 "], 16 \n\t" \ + "subu %[" #TEMP2 "], %[" #TEMP0 "], %[temp16] \n\t" \ + "addu %[" #TEMP0 "], %[" #TEMP0 "], %[temp16] \n\t" \ + "mul %[temp17], %[temp19], %[c2217] \n\t" \ + "mul %[temp18], %[" #TEMP3 "], %[c5352] \n\t" \ + "mul %[" #TEMP1 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp16], %[" #TEMP3 "], %[c2217] \n\t" \ + "sll %[" #TEMP2 "], %[" #TEMP2 "], 3 \n\t" \ + "sll %[" #TEMP0 "], %[" #TEMP0 "], 3 \n\t" \ + "subu %[" #TEMP3 "], %[temp17], %[temp18] \n\t" \ + "addu %[" #TEMP1 "], %[temp16], %[" #TEMP1 "] \n\t" \ + "addiu %[" #TEMP3 "], %[" #TEMP3 "], 937 \n\t" \ + "addiu %[" #TEMP1 "], %[" #TEMP1 "], 1812 \n\t" \ + "sra %[" #TEMP3 "], %[" #TEMP3 "], 9 \n\t" \ + "sra %[" #TEMP1 "], %[" #TEMP1 "], 9 \n\t" + +// macro for one vertical pass in FTransform +// temp0..temp15 holds tmp[0]..tmp[15] +// A..D - offsets in bytes to store to out buffer +// TEMP0, TEMP4, TEMP8 and TEMP12 - registers for corresponding tmp elements +#define VERTICAL_PASS(A, B, C, D, TEMP0, TEMP4, TEMP8, TEMP12) \ + "addu %[temp16], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "subu %[temp19], %[" #TEMP0 "], %[" #TEMP12 "] \n\t" \ + "addu %[temp17], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "subu %[temp18], %[" #TEMP4 "], %[" #TEMP8 "] \n\t" \ + "mul %[" #TEMP8 "], %[temp19], %[c2217] \n\t" \ + "mul %[" #TEMP12 "], %[temp18], %[c2217] \n\t" \ + "mul %[" #TEMP4 "], %[temp19], %[c5352] \n\t" \ + "mul %[temp18], %[temp18], %[c5352] \n\t" \ + "addiu %[temp16], %[temp16], 7 \n\t" \ + "addu %[" #TEMP0 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP0 "], %[" #TEMP0 "], 4 \n\t" \ + "addu %[" #TEMP12 "], %[" #TEMP12 "], %[" #TEMP4 "] \n\t" \ + "subu %[" #TEMP4 "], %[temp16], %[temp17] \n\t" \ + "sra %[" #TEMP4 "], %[" #TEMP4 "], 4 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 30000 \n\t" \ + "addiu %[" #TEMP12 "], %[" #TEMP12 "], 12000 \n\t" \ + "addiu %[" #TEMP8 "], %[" #TEMP8 "], 21000 \n\t" \ + "subu %[" #TEMP8 "], %[" #TEMP8 "], %[temp18] \n\t" \ + "sra %[" #TEMP12 "], %[" #TEMP12 "], 16 \n\t" \ + "sra %[" #TEMP8 "], %[" #TEMP8 "], 16 \n\t" \ + "addiu %[temp16], %[" #TEMP12 "], 1 \n\t" \ + "movn %[" #TEMP12 "], %[temp16], %[temp19] \n\t" \ + "sh %[" #TEMP0 "], " #A "(%[temp20]) \n\t" \ + "sh %[" #TEMP4 "], " #C "(%[temp20]) \n\t" \ + "sh %[" #TEMP8 "], " #D "(%[temp20]) \n\t" \ + "sh %[" #TEMP12 "], " #B "(%[temp20]) \n\t" + +static void FTransform_MIPSdspR2(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + const int c2217 = 2217; + const int c5352 = 5352; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + int temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16; + int temp17, temp18, temp19, temp20; + const int* const args[3] = + { (const int*)src, (const int*)ref, (const int*)out }; + + __asm__ volatile ( + HORIZONTAL_PASS(0, temp0, temp1, temp2, temp3) + HORIZONTAL_PASS(1, temp4, temp5, temp6, temp7) + HORIZONTAL_PASS(2, temp8, temp9, temp10, temp11) + HORIZONTAL_PASS(3, temp12, temp13, temp14, temp15) + "lw %[temp20], 8(%[args]) \n\t" + VERTICAL_PASS(0, 8, 16, 24, temp0, temp4, temp8, temp12) + VERTICAL_PASS(2, 10, 18, 26, temp1, temp5, temp9, temp13) + VERTICAL_PASS(4, 12, 20, 28, temp2, temp6, temp10, temp14) + VERTICAL_PASS(6, 14, 22, 30, temp3, temp7, temp11, temp15) + OUTPUT_EARLY_CLOBBER_REGS_18(), + [temp0]"=&r"(temp0), [temp19]"=&r"(temp19), [temp20]"=&r"(temp20) + : [args]"r"(args), [c2217]"r"(c2217), [c5352]"r"(c5352) + : "memory", "hi", "lo" + ); +} + +#undef VERTICAL_PASS +#undef HORIZONTAL_PASS + +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17, temp18; + + __asm__ volatile ( + "ulw %[temp1], 0(%[in]) \n\t" + "ulw %[temp2], 16(%[in]) \n\t" + LOAD_IN_X2(temp5, temp6, 24, 26) + ADD_SUB_HALVES(temp3, temp4, temp1, temp2) + LOAD_IN_X2(temp1, temp2, 8, 10) + MUL_SHIFT_SUM(temp7, temp8, temp9, temp10, temp11, temp12, temp13, temp14, + temp10, temp8, temp9, temp7, temp1, temp2, temp5, temp6, + temp13, temp11, temp14, temp12) + INSERT_HALF_X2(temp8, temp7, temp10, temp9) + "ulw %[temp17], 4(%[in]) \n\t" + "ulw %[temp18], 20(%[in]) \n\t" + ADD_SUB_HALVES(temp1, temp2, temp3, temp8) + ADD_SUB_HALVES(temp5, temp6, temp4, temp7) + ADD_SUB_HALVES(temp7, temp8, temp17, temp18) + LOAD_IN_X2(temp17, temp18, 12, 14) + LOAD_IN_X2(temp9, temp10, 28, 30) + MUL_SHIFT_SUM(temp11, temp12, temp13, temp14, temp15, temp16, temp4, temp17, + temp12, temp14, temp11, temp13, temp17, temp18, temp9, temp10, + temp15, temp4, temp16, temp17) + INSERT_HALF_X2(temp11, temp12, temp13, temp14) + ADD_SUB_HALVES(temp17, temp8, temp8, temp11) + ADD_SUB_HALVES(temp3, temp4, temp7, temp12) + + // horizontal + SRA_16(temp9, temp10, temp11, temp12, temp1, temp2, temp5, temp6) + INSERT_HALF_X2(temp1, temp6, temp5, temp2) + SRA_16(temp13, temp14, temp15, temp16, temp3, temp4, temp17, temp8) + "repl.ph %[temp2], 0x4 \n\t" + INSERT_HALF_X2(temp3, temp8, temp17, temp4) + "addq.ph %[temp1], %[temp1], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp2] \n\t" + ADD_SUB_HALVES(temp2, temp4, temp1, temp3) + ADD_SUB_HALVES(temp5, temp7, temp6, temp8) + MUL_SHIFT_SUM(temp1, temp3, temp6, temp8, temp9, temp13, temp17, temp18, + temp3, temp13, temp1, temp9, temp9, temp13, temp11, temp15, + temp6, temp17, temp8, temp18) + MUL_SHIFT_SUM(temp6, temp8, temp18, temp17, temp11, temp15, temp12, temp16, + temp8, temp15, temp6, temp11, temp12, temp16, temp10, temp14, + temp18, temp12, temp17, temp16) + INSERT_HALF_X2(temp1, temp3, temp9, temp13) + INSERT_HALF_X2(temp6, temp8, temp11, temp15) + SHIFT_R_SUM_X2(temp9, temp10, temp11, temp12, temp13, temp14, temp15, + temp16, temp2, temp4, temp5, temp7, temp3, temp1, temp8, + temp6) + PACK_2_HALVES_TO_WORD(temp1, temp2, temp3, temp4, temp9, temp12, temp13, + temp16, temp11, temp10, temp15, temp14) + LOAD_WITH_OFFSET_X4(temp10, temp11, temp14, temp15, ref, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp17, temp18, temp10, + temp11, temp10, temp11, temp14, temp15) + STORE_SAT_SUM_X2(temp5, temp6, temp7, temp8, temp17, temp18, temp10, temp11, + temp9, temp12, temp1, temp2, temp13, temp16, temp3, temp4, + dst, 0, 1, 2, 3, BPS) + + OUTPUT_EARLY_CLOBBER_REGS_18() + : [dst]"r"(dst), [in]"r"(in), [kC1]"r"(kC1), [kC2]"r"(kC2), [ref]"r"(ref) + : "memory", "hi", "lo" + ); +} + +static void ITransform_MIPSdspR2(const uint8_t* ref, const int16_t* in, + uint8_t* dst, int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +static int Disto4x4_MIPSdspR2(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9; + int temp10, temp11, temp12, temp13, temp14, temp15, temp16, temp17; + + __asm__ volatile ( + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, a, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5, temp6, temp7, temp8, temp9,temp10, temp11, + temp12, temp1, temp2, temp3, temp4) + ADD_SUB_HALVES_X4(temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp5, temp6, temp7, temp8, temp9, temp10, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp9, temp10, temp11, temp12, temp1, temp3, temp5, + temp7, temp2, temp4, temp6, temp8) + ADD_SUB_HALVES_X4(temp2, temp4, temp6, temp8, temp9, temp1, temp3, temp10, + temp1, temp9, temp3, temp10, temp5, temp11, temp7, temp12) + ADD_SUB_HALVES_X4(temp5, temp11, temp7, temp2, temp9, temp3, temp6, temp12, + temp2, temp9, temp6, temp3, temp4, temp1, temp8, temp10) + ADD_SUB_HALVES_X4(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2, + temp5, temp7, temp11, temp2, temp9, temp6, temp3, temp12) + ABS_X8(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2) + LOAD_WITH_OFFSET_X4(temp3, temp6, temp9, temp12, w, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + LOAD_WITH_OFFSET_X4(temp13, temp14, temp15, temp16, w, + 0, 4, 8, 12, + 1, 1, 1, 1, + 16) + MUL_HALF(temp17, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16) + LOAD_WITH_OFFSET_X4(temp1, temp2, temp3, temp4, b, + 0, 0, 0, 0, + 0, 1, 2, 3, + BPS) + CONVERT_2_BYTES_TO_HALF(temp5,temp6, temp7, temp8, temp9,temp10, temp11, + temp12, temp1, temp2, temp3, temp4) + ADD_SUB_HALVES_X4(temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp5, temp6, temp7, temp8, temp9, temp10, temp11, temp12) + PACK_2_HALVES_TO_WORD(temp9, temp10, temp11, temp12, temp1, temp3, temp5, + temp7, temp2, temp4, temp6, temp8) + ADD_SUB_HALVES_X4(temp2, temp4, temp6, temp8, temp9, temp1, temp3, temp10, + temp1, temp9, temp3, temp10, temp5, temp11, temp7, temp12) + ADD_SUB_HALVES_X4(temp5, temp11, temp7, temp2, temp9, temp3, temp6, temp12, + temp2, temp9, temp6, temp3, temp4, temp1, temp8, temp10) + ADD_SUB_HALVES_X4(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2, + temp5, temp7, temp11, temp2, temp9, temp6, temp3, temp12) + ABS_X8(temp1, temp4, temp10, temp8, temp7, temp11, temp5, temp2) + LOAD_WITH_OFFSET_X4(temp3, temp6, temp9, temp12, w, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + LOAD_WITH_OFFSET_X4(temp13, temp14, temp15, temp16, w, + 0, 4, 8, 12, + 1, 1, 1, 1, + 16) + MUL_HALF(temp3, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, + temp9, temp10, temp11, temp12, temp13, temp14, temp15, temp16) + OUTPUT_EARLY_CLOBBER_REGS_17() + : [a]"r"(a), [b]"r"(b), [w]"r"(w) + : "memory", "hi", "lo" + ); + return abs(temp3 - temp17) >> 5; +} + +static int Disto16x16_MIPSdspR2(const uint8_t* const a, + const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_MIPSdspR2(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Intra predictions + +#define FILL_PART(J, SIZE) \ + "usw %[value], 0+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + "usw %[value], 4+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "usw %[value], 8+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + "usw %[value], 12+" #J "*" XSTR(BPS) "(%[dst]) \n\t" \ + ".endif \n\t" + +#define FILL_8_OR_16(DST, VALUE, SIZE) do { \ + int value = (VALUE); \ + __asm__ volatile ( \ + "replv.qb %[value], %[value] \n\t" \ + FILL_PART( 0, SIZE) \ + FILL_PART( 1, SIZE) \ + FILL_PART( 2, SIZE) \ + FILL_PART( 3, SIZE) \ + FILL_PART( 4, SIZE) \ + FILL_PART( 5, SIZE) \ + FILL_PART( 6, SIZE) \ + FILL_PART( 7, SIZE) \ + ".if " #SIZE " == 16 \n\t" \ + FILL_PART( 8, 16) \ + FILL_PART( 9, 16) \ + FILL_PART(10, 16) \ + FILL_PART(11, 16) \ + FILL_PART(12, 16) \ + FILL_PART(13, 16) \ + FILL_PART(14, 16) \ + FILL_PART(15, 16) \ + ".endif \n\t" \ + : [value]"+&r"(value) \ + : [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define VERTICAL_PRED(DST, TOP, SIZE) \ +static WEBP_INLINE void VerticalPred##SIZE(uint8_t* (DST), \ + const uint8_t* (TOP)) { \ + int j; \ + if ((TOP)) { \ + for (j = 0; j < (SIZE); ++j) memcpy((DST) + j * BPS, (TOP), (SIZE)); \ + } else { \ + FILL_8_OR_16((DST), 127, (SIZE)); \ + } \ +} + +VERTICAL_PRED(dst, top, 8) +VERTICAL_PRED(dst, top, 16) + +#undef VERTICAL_PRED + +#define HORIZONTAL_PRED(DST, LEFT, SIZE) \ +static WEBP_INLINE void HorizontalPred##SIZE(uint8_t* (DST), \ + const uint8_t* (LEFT)) { \ + if (LEFT) { \ + int j; \ + for (j = 0; j < (SIZE); ++j) { \ + memset((DST) + j * BPS, (LEFT)[j], (SIZE)); \ + } \ + } else { \ + FILL_8_OR_16((DST), 129, (SIZE)); \ + } \ +} + +HORIZONTAL_PRED(dst, left, 8) +HORIZONTAL_PRED(dst, left, 16) + +#undef HORIZONTAL_PRED + +#define CLIPPING() \ + "preceu.ph.qbl %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp0], %[temp0] \n\t" \ + "preceu.ph.qbl %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbr %[temp1], %[temp1] \n\t" \ + "addu.ph %[temp2], %[temp2], %[leftY_1] \n\t" \ + "addu.ph %[temp0], %[temp0], %[leftY_1] \n\t" \ + "addu.ph %[temp3], %[temp3], %[leftY_1] \n\t" \ + "addu.ph %[temp1], %[temp1], %[leftY_1] \n\t" \ + "shll_s.ph %[temp2], %[temp2], 7 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 7 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 7 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 7 \n\t" \ + "precrqu_s.qb.ph %[temp0], %[temp2], %[temp0] \n\t" \ + "precrqu_s.qb.ph %[temp1], %[temp3], %[temp1] \n\t" + +#define CLIP_8B_TO_DST(DST, LEFT, TOP, SIZE) do { \ + int leftY_1 = ((int)(LEFT)[y] << 16) + (LEFT)[y]; \ + int temp0, temp1, temp2, temp3; \ + __asm__ volatile ( \ + "replv.ph %[leftY_1], %[leftY_1] \n\t" \ + "ulw %[temp0], 0(%[top]) \n\t" \ + "ulw %[temp1], 4(%[top]) \n\t" \ + "subu.ph %[leftY_1], %[leftY_1], %[left_1] \n\t" \ + CLIPPING() \ + "usw %[temp0], 0(%[dst]) \n\t" \ + "usw %[temp1], 4(%[dst]) \n\t" \ + ".if " #SIZE " == 16 \n\t" \ + "ulw %[temp0], 8(%[top]) \n\t" \ + "ulw %[temp1], 12(%[top]) \n\t" \ + CLIPPING() \ + "usw %[temp0], 8(%[dst]) \n\t" \ + "usw %[temp1], 12(%[dst]) \n\t" \ + ".endif \n\t" \ + : [leftY_1]"+&r"(leftY_1), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [left_1]"r"(left_1), [top]"r"((TOP)), [dst]"r"((DST)) \ + : "memory" \ + ); \ +} while (0) + +#define CLIP_TO_DST(DST, LEFT, TOP, SIZE) do { \ + int y; \ + const int left_1 = ((int)(LEFT)[-1] << 16) + (LEFT)[-1]; \ + for (y = 0; y < (SIZE); ++y) { \ + CLIP_8B_TO_DST((DST), (LEFT), (TOP), (SIZE)); \ + (DST) += BPS; \ + } \ +} while (0) + +#define TRUE_MOTION(DST, LEFT, TOP, SIZE) \ +static WEBP_INLINE void TrueMotion##SIZE(uint8_t* (DST), const uint8_t* (LEFT),\ + const uint8_t* (TOP)) { \ + if ((LEFT) != NULL) { \ + if ((TOP) != NULL) { \ + CLIP_TO_DST((DST), (LEFT), (TOP), (SIZE)); \ + } else { \ + HorizontalPred##SIZE((DST), (LEFT)); \ + } \ + } else { \ + /* true motion without left samples (hence: with default 129 value) */ \ + /* is equivalent to VE prediction where you just copy the top samples. */ \ + /* Note that if top samples are not available, the default value is */ \ + /* then 129, and not 127 as in the VerticalPred case. */ \ + if ((TOP) != NULL) { \ + VerticalPred##SIZE((DST), (TOP)); \ + } else { \ + FILL_8_OR_16((DST), 129, (SIZE)); \ + } \ + } \ +} + +TRUE_MOTION(dst, left, top, 8) +TRUE_MOTION(dst, left, top, 16) + +#undef TRUE_MOTION +#undef CLIP_TO_DST +#undef CLIP_8B_TO_DST +#undef CLIPPING + +static WEBP_INLINE void DCMode16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC, DC1; + int temp0, temp1, temp2, temp3; + + __asm__ volatile( + "beqz %[top], 2f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, top, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC], %[temp0], %[temp2] \n\t" + "move %[DC1], %[DC] \n\t" + "beqz %[left], 1f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, left, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC1], %[temp0], %[temp2] \n\t" + "1: \n\t" + "addu %[DC], %[DC], %[DC1] \n\t" + "j 3f \n\t" + "2: \n\t" + "beqz %[left], 4f \n\t" + LOAD_WITH_OFFSET_X4(temp0, temp1, temp2, temp3, left, + 0, 4, 8, 12, + 0, 0, 0, 0, + 0) + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addu %[DC], %[temp0], %[temp2] \n\t" + "addu %[DC], %[DC], %[DC] \n\t" + "3: \n\t" + "shra_r.w %[DC], %[DC], 5 \n\t" + "j 5f \n\t" + "4: \n\t" + "li %[DC], 0x80 \n\t" + "5: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [DC]"=&r"(DC), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [DC1]"=&r"(DC1) + : [left]"r"(left), [top]"r"(top) + : "memory" + ); + + FILL_8_OR_16(dst, DC, 16); +} + +static WEBP_INLINE void DCMode8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC, DC1; + int temp0, temp1, temp2, temp3; + + __asm__ volatile( + "beqz %[top], 2f \n\t" + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[DC], %[temp0], %[temp1] \n\t" + "move %[DC1], %[DC] \n\t" + "beqz %[left], 1f \n\t" + "ulw %[temp2], 0(%[left]) \n\t" + "ulw %[temp3], 4(%[left]) \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[DC1], %[temp2], %[temp3] \n\t" + "1: \n\t" + "addu %[DC], %[DC], %[DC1] \n\t" + "j 3f \n\t" + "2: \n\t" + "beqz %[left], 4f \n\t" + "ulw %[temp2], 0(%[left]) \n\t" + "ulw %[temp3], 4(%[left]) \n\t" + "raddu.w.qb %[temp2], %[temp2] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "addu %[DC], %[temp2], %[temp3] \n\t" + "addu %[DC], %[DC], %[DC] \n\t" + "3: \n\t" + "shra_r.w %[DC], %[DC], 4 \n\t" + "j 5f \n\t" + "4: \n\t" + "li %[DC], 0x80 \n\t" + "5: \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [DC]"=&r"(DC), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [DC1]"=&r"(DC1) + : [left]"r"(left), [top]"r"(top) + : "memory" + ); + + FILL_8_OR_16(dst, DC, 8); +} + +static void DC4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1; + __asm__ volatile( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], -5(%[top]) \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "raddu.w.qb %[temp1], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addiu %[temp0], %[temp0], 4 \n\t" + "srl %[temp0], %[temp0], 3 \n\t" + "replv.qb %[temp0], %[temp0] \n\t" + "usw %[temp0], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void TM4(uint8_t* dst, const uint8_t* top) { + int a10, a32, temp0, temp1, temp2, temp3, temp4, temp5; + const int c35 = 0xff00ff; + __asm__ volatile ( + "lbu %[temp1], 0(%[top]) \n\t" + "lbu %[a10], 1(%[top]) \n\t" + "lbu %[temp2], 2(%[top]) \n\t" + "lbu %[a32], 3(%[top]) \n\t" + "ulw %[temp0], -5(%[top]) \n\t" + "lbu %[temp4], -1(%[top]) \n\t" + "append %[a10], %[temp1], 16 \n\t" + "append %[a32], %[temp2], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "shrl.ph %[temp1], %[temp0], 8 \n\t" + "and %[temp0], %[temp0], %[c35] \n\t" + "subu.ph %[temp1], %[temp1], %[temp4] \n\t" + "subu.ph %[temp0], %[temp0], %[temp4] \n\t" + "srl %[temp2], %[temp1], 16 \n\t" + "srl %[temp3], %[temp0], 16 \n\t" + "replv.ph %[temp2], %[temp2] \n\t" + "replv.ph %[temp3], %[temp3] \n\t" + "replv.ph %[temp4], %[temp1] \n\t" + "replv.ph %[temp5], %[temp0] \n\t" + "addu.ph %[temp0], %[temp3], %[a10] \n\t" + "addu.ph %[temp1], %[temp3], %[a32] \n\t" + "addu.ph %[temp3], %[temp2], %[a10] \n\t" + "addu.ph %[temp2], %[temp2], %[a32] \n\t" + "shll_s.ph %[temp0], %[temp0], 7 \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp3], %[temp3], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp0], %[temp1], %[temp0] \n\t" + "precrqu_s.qb.ph %[temp1], %[temp2], %[temp3] \n\t" + "addu.ph %[temp2], %[temp5], %[a10] \n\t" + "addu.ph %[temp3], %[temp5], %[a32] \n\t" + "addu.ph %[temp5], %[temp4], %[a10] \n\t" + "addu.ph %[temp4], %[temp4], %[a32] \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "shll_s.ph %[temp3], %[temp3], 7 \n\t" + "shll_s.ph %[temp4], %[temp4], 7 \n\t" + "shll_s.ph %[temp5], %[temp5], 7 \n\t" + "precrqu_s.qb.ph %[temp2], %[temp3], %[temp2] \n\t" + "precrqu_s.qb.ph %[temp3], %[temp4], %[temp5] \n\t" + "usw %[temp1], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [a10]"=&r"(a10), [a32]"=&r"(a32) + : [c35]"r"(c35), [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VE4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile( + "ulw %[temp0], -1(%[top]) \n\t" + "ulh %[temp1], 3(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp4], %[temp3] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp2], %[temp5], %[temp2] \n\t" + "addq.ph %[temp6], %[temp6], %[temp4] \n\t" + "addq.ph %[temp2], %[temp2], %[temp3] \n\t" + "addq.ph %[temp6], %[temp6], %[temp3] \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precr.qb.ph %[temp4], %[temp6], %[temp2] \n\t" + "usw %[temp4], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HE4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile( + "ulw %[temp0], -4(%[top]) \n\t" + "lbu %[temp1], -5(%[top]) \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp0] \n\t" + "replv.ph %[temp4], %[temp1] \n\t" + "packrl.ph %[temp5], %[temp3], %[temp2] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp4] \n\t" + "shll.ph %[temp5], %[temp5], 1 \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp3], %[temp3], %[temp5] \n\t" + "addq.ph %[temp3], %[temp3], %[temp2] \n\t" + "addq.ph %[temp2], %[temp2], %[temp6] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "replv.qb %[temp0], %[temp3] \n\t" + "replv.qb %[temp1], %[temp2] \n\t" + "srl %[temp3], %[temp3], 16 \n\t" + "srl %[temp2], %[temp2], 16 \n\t" + "replv.qb %[temp3], %[temp3] \n\t" + "replv.qb %[temp2], %[temp2] \n\t" + "usw %[temp3], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp0], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp1], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void RD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int temp6, temp7, temp8, temp9, temp10, temp11; + __asm__ volatile( + "ulw %[temp0], -5(%[top]) \n\t" + "ulw %[temp1], -1(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp10], %[temp4], %[temp7] \n\t" + "addq.ph %[temp11], %[temp5], %[temp8] \n\t" + "addq.ph %[temp10], %[temp10], %[temp2] \n\t" + "addq.ph %[temp11], %[temp11], %[temp4] \n\t" + "shra_r.ph %[temp10], %[temp10], 2 \n\t" + "shra_r.ph %[temp11], %[temp11], 2 \n\t" + "lbu %[temp0], 3(%[top]) \n\t" + "lbu %[temp1], 2(%[top]) \n\t" + "lbu %[temp2], 1(%[top]) \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp0], %[temp0], %[temp2] \n\t" + "precr.qb.ph %[temp9], %[temp10], %[temp9] \n\t" + "shra_r.w %[temp0], %[temp0], 2 \n\t" + "precr.qb.ph %[temp10], %[temp11], %[temp10] \n\t" + "usw %[temp9], 3*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 1*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp9], %[temp11], 8 \n\t" + "prepend %[temp10], %[temp0], 8 \n\t" + "usw %[temp9], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 0*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VR4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], -4(%[top]) \n\t" + "ulw %[temp1], 0(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp0], %[temp0] \n\t" + "preceu.ph.qbla %[temp3], %[temp1] \n\t" + "preceu.ph.qbra %[temp1], %[temp1] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp2] \n\t" + "addqh_r.ph %[temp4], %[temp1], %[temp3] \n\t" + "move %[temp6], %[temp1] \n\t" + "append %[temp1], %[temp2], 16 \n\t" + "shll.ph %[temp9], %[temp6], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp6] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp3], %[temp7], %[temp3] \n\t" + "addu.ph %[temp1], %[temp1], %[temp6] \n\t" + "packrl.ph %[temp7], %[temp2], %[temp0] \n\t" + "addu.ph %[temp6], %[temp0], %[temp2] \n\t" + "addu.ph %[temp3], %[temp3], %[temp9] \n\t" + "addu.ph %[temp1], %[temp1], %[temp8] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp3], %[temp3], 2 \n\t" + "shra_r.ph %[temp1], %[temp1], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp8], %[temp4], %[temp5] \n\t" + "append %[temp4], %[temp5], 16 \n\t" + "precrq.ph.w %[temp2], %[temp3], %[temp1] \n\t" + "append %[temp3], %[temp1], 16 \n\t" + "precr.qb.ph %[temp8], %[temp8], %[temp4] \n\t" + "precr.qb.ph %[temp3], %[temp2], %[temp3] \n\t" + "usw %[temp8], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 1*" XSTR(BPS) "(%[dst]) \n\t" + "append %[temp3], %[temp6], 8 \n\t" + "srl %[temp6], %[temp6], 16 \n\t" + "append %[temp8], %[temp6], 8 \n\t" + "usw %[temp3], 3*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp8], 2*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void LD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5; + int temp6, temp7, temp8, temp9, temp10, temp11; + __asm__ volatile( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "preceu.ph.qbl %[temp2], %[temp0] \n\t" + "preceu.ph.qbr %[temp3], %[temp0] \n\t" + "preceu.ph.qbr %[temp4], %[temp1] \n\t" + "preceu.ph.qbl %[temp5], %[temp1] \n\t" + "packrl.ph %[temp6], %[temp2], %[temp3] \n\t" + "packrl.ph %[temp7], %[temp4], %[temp2] \n\t" + "packrl.ph %[temp8], %[temp5], %[temp4] \n\t" + "shll.ph %[temp6], %[temp6], 1 \n\t" + "addq.ph %[temp9], %[temp2], %[temp6] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "addq.ph %[temp9], %[temp9], %[temp3] \n\t" + "shll.ph %[temp8], %[temp8], 1 \n\t" + "shra_r.ph %[temp9], %[temp9], 2 \n\t" + "addq.ph %[temp10], %[temp4], %[temp7] \n\t" + "addq.ph %[temp11], %[temp5], %[temp8] \n\t" + "addq.ph %[temp10], %[temp10], %[temp2] \n\t" + "addq.ph %[temp11], %[temp11], %[temp4] \n\t" + "shra_r.ph %[temp10], %[temp10], 2 \n\t" + "shra_r.ph %[temp11], %[temp11], 2 \n\t" + "srl %[temp1], %[temp1], 24 \n\t" + "sll %[temp1], %[temp1], 1 \n\t" + "raddu.w.qb %[temp5], %[temp5] \n\t" + "precr.qb.ph %[temp9], %[temp10], %[temp9] \n\t" + "precr.qb.ph %[temp10], %[temp11], %[temp10] \n\t" + "addu %[temp1], %[temp1], %[temp5] \n\t" + "shra_r.w %[temp1], %[temp1], 2 \n\t" + "usw %[temp9], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 2*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp9], %[temp11], 8 \n\t" + "prepend %[temp10], %[temp1], 8 \n\t" + "usw %[temp9], 1*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp10], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), [temp11]"=&r"(temp11) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void VL4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], 0(%[top]) \n\t" + "ulw %[temp1], 4(%[top]) \n\t" + "preceu.ph.qbla %[temp2], %[temp0] \n\t" + "preceu.ph.qbra %[temp0], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp1] \n\t" + "preceu.ph.qbr %[temp1], %[temp1] \n\t" + "addqh_r.ph %[temp4], %[temp0], %[temp2] \n\t" + "packrl.ph %[temp7], %[temp1], %[temp0] \n\t" + "precrq.ph.w %[temp6], %[temp1], %[temp2] \n\t" + "shll.ph %[temp9], %[temp2], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp2] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp2], %[temp2], %[temp6] \n\t" + "addu.ph %[temp0], %[temp0], %[temp7] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp1] \n\t" + "addu.ph %[temp6], %[temp1], %[temp3] \n\t" + "addu.ph %[temp2], %[temp2], %[temp8] \n\t" + "addu.ph %[temp0], %[temp0], %[temp9] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp8], %[temp5], %[temp4] \n\t" + "append %[temp5], %[temp4], 16 \n\t" + "precrq.ph.w %[temp3], %[temp2], %[temp0] \n\t" + "append %[temp2], %[temp0], 16 \n\t" + "precr.qb.ph %[temp8], %[temp8], %[temp5] \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" + "usw %[temp8], 0*" XSTR(BPS) "(%[dst]) \n\t" + "prepend %[temp8], %[temp6], 8 \n\t" + "usw %[temp3], 1*" XSTR(BPS) "(%[dst]) \n\t" + "srl %[temp6], %[temp6], 16 \n\t" + "prepend %[temp3], %[temp6], 8 \n\t" + "usw %[temp8], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp3], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HD4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + __asm__ volatile ( + "ulw %[temp0], -5(%[top]) \n\t" + "ulw %[temp1], -1(%[top]) \n\t" + "preceu.ph.qbla %[temp2], %[temp0] \n\t" + "preceu.ph.qbra %[temp0], %[temp0] \n\t" + "preceu.ph.qbl %[temp3], %[temp1] \n\t" + "preceu.ph.qbr %[temp1], %[temp1] \n\t" + "addqh_r.ph %[temp4], %[temp0], %[temp2] \n\t" + "packrl.ph %[temp7], %[temp1], %[temp0] \n\t" + "precrq.ph.w %[temp6], %[temp1], %[temp2] \n\t" + "shll.ph %[temp9], %[temp2], 1 \n\t" + "addqh_r.ph %[temp5], %[temp7], %[temp2] \n\t" + "shll.ph %[temp8], %[temp7], 1 \n\t" + "addu.ph %[temp2], %[temp2], %[temp6] \n\t" + "addu.ph %[temp0], %[temp0], %[temp7] \n\t" + "packrl.ph %[temp7], %[temp3], %[temp1] \n\t" + "addu.ph %[temp6], %[temp1], %[temp3] \n\t" + "addu.ph %[temp2], %[temp2], %[temp8] \n\t" + "addu.ph %[temp0], %[temp0], %[temp9] \n\t" + "shll.ph %[temp7], %[temp7], 1 \n\t" + "shra_r.ph %[temp2], %[temp2], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "addu.ph %[temp6], %[temp6], %[temp7] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "precrq.ph.w %[temp1], %[temp2], %[temp5] \n\t" + "precrq.ph.w %[temp3], %[temp0], %[temp4] \n\t" + "precr.qb.ph %[temp7], %[temp6], %[temp1] \n\t" + "precr.qb.ph %[temp6], %[temp1], %[temp3] \n\t" + "usw %[temp7], 0*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp6], 1*" XSTR(BPS) "(%[dst]) \n\t" + "append %[temp2], %[temp5], 16 \n\t" + "append %[temp0], %[temp4], 16 \n\t" + "precr.qb.ph %[temp5], %[temp3], %[temp2] \n\t" + "precr.qb.ph %[temp4], %[temp2], %[temp0] \n\t" + "usw %[temp5], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp4], 3*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +static void HU4(uint8_t* dst, const uint8_t* top) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + __asm__ volatile ( + "ulw %[temp0], -5(%[top]) \n\t" + "preceu.ph.qbl %[temp1], %[temp0] \n\t" + "preceu.ph.qbr %[temp2], %[temp0] \n\t" + "packrl.ph %[temp3], %[temp1], %[temp2] \n\t" + "replv.qb %[temp7], %[temp2] \n\t" + "addqh_r.ph %[temp4], %[temp1], %[temp3] \n\t" + "addqh_r.ph %[temp5], %[temp3], %[temp2] \n\t" + "shll.ph %[temp6], %[temp3], 1 \n\t" + "addu.ph %[temp3], %[temp2], %[temp3] \n\t" + "addu.ph %[temp6], %[temp1], %[temp6] \n\t" + "shll.ph %[temp0], %[temp2], 1 \n\t" + "addu.ph %[temp6], %[temp6], %[temp2] \n\t" + "addu.ph %[temp0], %[temp3], %[temp0] \n\t" + "shra_r.ph %[temp6], %[temp6], 2 \n\t" + "shra_r.ph %[temp0], %[temp0], 2 \n\t" + "packrl.ph %[temp3], %[temp6], %[temp5] \n\t" + "precrq.ph.w %[temp2], %[temp6], %[temp4] \n\t" + "append %[temp0], %[temp5], 16 \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" + "usw %[temp3], 0*" XSTR(BPS) "(%[dst]) \n\t" + "precr.qb.ph %[temp1], %[temp7], %[temp0] \n\t" + "usw %[temp7], 3*" XSTR(BPS) "(%[dst]) \n\t" + "packrl.ph %[temp2], %[temp1], %[temp3] \n\t" + "usw %[temp1], 2*" XSTR(BPS) "(%[dst]) \n\t" + "usw %[temp2], 1*" XSTR(BPS) "(%[dst]) \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7) + : [top]"r"(top), [dst]"r"(dst) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Chroma 8x8 prediction (paragraph 12.2) + +static void IntraChromaPreds_MIPSdspR2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DCMode8(C8DC8 + dst, left, top); + VerticalPred8(C8VE8 + dst, top); + HorizontalPred8(C8HE8 + dst, left); + TrueMotion8(C8TM8 + dst, left, top); + // V block + dst += 8; + if (top) top += 8; + if (left) left += 16; + DCMode8(C8DC8 + dst, left, top); + VerticalPred8(C8VE8 + dst, top); + HorizontalPred8(C8HE8 + dst, left); + TrueMotion8(C8TM8 + dst, left, top); +} + +//------------------------------------------------------------------------------ +// luma 16x16 prediction (paragraph 12.3) + +static void Intra16Preds_MIPSdspR2(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DCMode16(I16DC16 + dst, left, top); + VerticalPred16(I16VE16 + dst, top); + HorizontalPred16(I16HE16 + dst, left); + TrueMotion16(I16TM16 + dst, left, top); +} + +// Left samples are top[-5 .. -2], top_left is top[-1], top are +// located at top[0..3], and top right is top[4..7] +static void Intra4Preds_MIPSdspR2(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +//------------------------------------------------------------------------------ +// Metric + +#if !defined(WORK_AROUND_GCC) + +#define GET_SSE_INNER(A) \ + "lw %[temp0], " #A "(%[a]) \n\t" \ + "lw %[temp1], " #A "(%[b]) \n\t" \ + "preceu.ph.qbr %[temp2], %[temp0] \n\t" \ + "preceu.ph.qbl %[temp0], %[temp0] \n\t" \ + "preceu.ph.qbr %[temp3], %[temp1] \n\t" \ + "preceu.ph.qbl %[temp1], %[temp1] \n\t" \ + "subq.ph %[temp2], %[temp2], %[temp3] \n\t" \ + "subq.ph %[temp0], %[temp0], %[temp1] \n\t" \ + "dpa.w.ph $ac0, %[temp2], %[temp2] \n\t" \ + "dpa.w.ph $ac0, %[temp0], %[temp0] \n\t" + +#define GET_SSE(A, B, C, D) \ + GET_SSE_INNER(A) \ + GET_SSE_INNER(B) \ + GET_SSE_INNER(C) \ + GET_SSE_INNER(D) + +static int SSE16x16_MIPSdspR2(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + GET_SSE( 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS) + GET_SSE( 9 * BPS, 4 + 9 * BPS, 8 + 9 * BPS, 12 + 9 * BPS) + GET_SSE(10 * BPS, 4 + 10 * BPS, 8 + 10 * BPS, 12 + 10 * BPS) + GET_SSE(11 * BPS, 4 + 11 * BPS, 8 + 11 * BPS, 12 + 11 * BPS) + GET_SSE(12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS) + GET_SSE(13 * BPS, 4 + 13 * BPS, 8 + 13 * BPS, 12 + 13 * BPS) + GET_SSE(14 * BPS, 4 + 14 * BPS, 8 + 14 * BPS, 12 + 14 * BPS) + GET_SSE(15 * BPS, 4 + 15 * BPS, 8 + 15 * BPS, 12 + 15 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE16x8_MIPSdspR2(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE( 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS) + GET_SSE( 1 * BPS, 4 + 1 * BPS, 8 + 1 * BPS, 12 + 1 * BPS) + GET_SSE( 2 * BPS, 4 + 2 * BPS, 8 + 2 * BPS, 12 + 2 * BPS) + GET_SSE( 3 * BPS, 4 + 3 * BPS, 8 + 3 * BPS, 12 + 3 * BPS) + GET_SSE( 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS) + GET_SSE( 5 * BPS, 4 + 5 * BPS, 8 + 5 * BPS, 12 + 5 * BPS) + GET_SSE( 6 * BPS, 4 + 6 * BPS, 8 + 6 * BPS, 12 + 6 * BPS) + GET_SSE( 7 * BPS, 4 + 7 * BPS, 8 + 7 * BPS, 12 + 7 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE8x8_MIPSdspR2(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE(0 * BPS, 4 + 0 * BPS, 1 * BPS, 4 + 1 * BPS) + GET_SSE(2 * BPS, 4 + 2 * BPS, 3 * BPS, 4 + 3 * BPS) + GET_SSE(4 * BPS, 4 + 4 * BPS, 5 * BPS, 4 + 5 * BPS) + GET_SSE(6 * BPS, 4 + 6 * BPS, 7 * BPS, 4 + 7 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +static int SSE4x4_MIPSdspR2(const uint8_t* a, const uint8_t* b) { + int count; + int temp0, temp1, temp2, temp3; + __asm__ volatile ( + "mult $zero, $zero \n\t" + GET_SSE(0 * BPS, 1 * BPS, 2 * BPS, 3 * BPS) + "mflo %[count] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [count]"=&r"(count) + : [a]"r"(a), [b]"r"(b) + : "memory", "hi", "lo" + ); + return count; +} + +#undef GET_SSE +#undef GET_SSE_INNER + +#endif // !WORK_AROUND_GCC + +#undef FILL_8_OR_16 +#undef FILL_PART +#undef OUTPUT_EARLY_CLOBBER_REGS_17 +#undef MUL_HALF +#undef ABS_X8 +#undef ADD_SUB_HALVES_X4 + +//------------------------------------------------------------------------------ +// Quantization +// + +// macro for one pass through for loop in QuantizeBlock reading 2 values at time +// QUANTDIV macro inlined +// J - offset in bytes (kZigzag[n] * 2) +// K - offset in bytes (kZigzag[n] * 4) +// N - offset in bytes (n * 2) +// N1 - offset in bytes ((n + 1) * 2) +#define QUANTIZE_ONE(J, K, N, N1) \ + "ulw %[temp1], " #J "(%[ppin]) \n\t" \ + "ulw %[temp2], " #J "(%[ppsharpen]) \n\t" \ + "lhu %[temp3], " #K "(%[ppzthresh]) \n\t" \ + "lhu %[temp6], " #K "+4(%[ppzthresh]) \n\t" \ + "absq_s.ph %[temp4], %[temp1] \n\t" \ + "ins %[temp3], %[temp6], 16, 16 \n\t" \ + "addu.ph %[coeff], %[temp4], %[temp2] \n\t" \ + "shra.ph %[sign], %[temp1], 15 \n\t" \ + "li %[level], 0x10001 \n\t" \ + "cmp.lt.ph %[temp3], %[coeff] \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "pick.ph %[temp5], %[level], $0 \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "beqz %[temp5], 0f \n\t" \ + "lhu %[temp3], " #J "(%[ppq]) \n\t" \ + "beq %[temp5], %[level], 1f \n\t" \ + "andi %[temp5], %[temp5], 0x1 \n\t" \ + "andi %[temp4], %[coeff], 0xffff \n\t" \ + "beqz %[temp5], 2f \n\t" \ + "mul %[level], %[temp4], %[temp1] \n\t" \ + "sh $0, " #J "+2(%[ppin]) \n\t" \ + "sh $0, " #N1 "(%[pout]) \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "andi %[temp6], %[sign], 0xffff \n\t" \ + "xor %[level], %[level], %[temp6] \n\t" \ + "subu %[level], %[level], %[temp6] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" \ + "sh %[temp5], " #J "(%[ppin]) \n\t" \ + "j 3f \n\t" \ +"2: \n\t" \ + "lhu %[temp1], " #J "+2(%[ppiq]) \n\t" \ + "srl %[temp5], %[coeff], 16 \n\t" \ + "mul %[level], %[temp5], %[temp1] \n\t" \ + "lw %[temp2], " #K "+4(%[ppbias]) \n\t" \ + "lhu %[temp3], " #J "+2(%[ppq]) \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "sra %[level], %[level], 17 \n\t" \ + "srl %[temp6], %[sign], 16 \n\t" \ + "slt %[temp4], %[max_level], %[level] \n\t" \ + "movn %[level], %[max_level], %[temp4] \n\t" \ + "xor %[level], %[level], %[temp6] \n\t" \ + "subu %[level], %[level], %[temp6] \n\t" \ + "mul %[temp5], %[level], %[temp3] \n\t" \ + "sh $0, " #J "(%[ppin]) \n\t" \ + "sh $0, " #N "(%[pout]) \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[temp5], " #J "+2(%[ppin]) \n\t" \ + "sh %[level], " #N1 "(%[pout]) \n\t" \ + "j 3f \n\t" \ +"1: \n\t" \ + "lhu %[temp1], " #J "(%[ppiq]) \n\t" \ + "lw %[temp2], " #K "(%[ppbias]) \n\t" \ + "ulw %[temp3], " #J "(%[ppq]) \n\t" \ + "andi %[temp5], %[coeff], 0xffff \n\t" \ + "srl %[temp0], %[coeff], 16 \n\t" \ + "lhu %[temp6], " #J "+2(%[ppiq]) \n\t" \ + "lw %[coeff], " #K "+4(%[ppbias]) \n\t" \ + "mul %[level], %[temp5], %[temp1] \n\t" \ + "mul %[temp4], %[temp0], %[temp6] \n\t" \ + "addu %[level], %[level], %[temp2] \n\t" \ + "addu %[temp4], %[temp4], %[coeff] \n\t" \ + "precrq.ph.w %[level], %[temp4], %[level] \n\t" \ + "shra.ph %[level], %[level], 1 \n\t" \ + "cmp.lt.ph %[max_level1],%[level] \n\t" \ + "pick.ph %[level], %[max_level], %[level] \n\t" \ + "xor %[level], %[level], %[sign] \n\t" \ + "subu.ph %[level], %[level], %[sign] \n\t" \ + "mul.ph %[temp3], %[level], %[temp3] \n\t" \ + "or %[ret], %[ret], %[level] \n\t" \ + "sh %[level], " #N "(%[pout]) \n\t" \ + "srl %[level], %[level], 16 \n\t" \ + "sh %[level], " #N1 "(%[pout]) \n\t" \ + "usw %[temp3], " #J "(%[ppin]) \n\t" \ + "j 3f \n\t" \ +"0: \n\t" \ + "sh $0, " #N "(%[pout]) \n\t" \ + "sh $0, " #N1 "(%[pout]) \n\t" \ + "usw $0, " #J "(%[ppin]) \n\t" \ +"3: \n\t" + +static int QuantizeBlock_MIPSdspR2(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int temp0, temp1, temp2, temp3, temp4, temp5,temp6; + int sign, coeff, level; + int max_level = MAX_LEVEL; + int max_level1 = max_level << 16 | max_level; + int ret = 0; + + int16_t* ppin = &in[0]; + int16_t* pout = &out[0]; + const uint16_t* ppsharpen = &mtx->sharpen_[0]; + const uint32_t* ppzthresh = &mtx->zthresh_[0]; + const uint16_t* ppq = &mtx->q_[0]; + const uint16_t* ppiq = &mtx->iq_[0]; + const uint32_t* ppbias = &mtx->bias_[0]; + + __asm__ volatile ( + QUANTIZE_ONE( 0, 0, 0, 2) + QUANTIZE_ONE( 4, 8, 10, 12) + QUANTIZE_ONE( 8, 16, 4, 8) + QUANTIZE_ONE(12, 24, 14, 24) + QUANTIZE_ONE(16, 32, 6, 16) + QUANTIZE_ONE(20, 40, 22, 26) + QUANTIZE_ONE(24, 48, 18, 20) + QUANTIZE_ONE(28, 56, 28, 30) + + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [sign]"=&r"(sign), [coeff]"=&r"(coeff), + [level]"=&r"(level), [temp6]"=&r"(temp6), [ret]"+&r"(ret) + : [ppin]"r"(ppin), [pout]"r"(pout), [max_level1]"r"(max_level1), + [ppiq]"r"(ppiq), [max_level]"r"(max_level), + [ppbias]"r"(ppbias), [ppzthresh]"r"(ppzthresh), + [ppsharpen]"r"(ppsharpen), [ppq]"r"(ppq) + : "memory", "hi", "lo" + ); + + return (ret != 0); +} + +static int Quantize2Blocks_MIPSdspR2(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock_MIPSdspR2(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock_MIPSdspR2(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#undef QUANTIZE_ONE + +// macro for one horizontal pass in FTransformWHT +// temp0..temp7 holds tmp[0]..tmp[15] +// A, B, C, D - offset in bytes to load from in buffer +// TEMP0, TEMP1 - registers for corresponding tmp elements +#define HORIZONTAL_PASS_WHT(A, B, C, D, TEMP0, TEMP1) \ + "lh %[" #TEMP0 "], " #A "(%[in]) \n\t" \ + "lh %[" #TEMP1 "], " #B "(%[in]) \n\t" \ + "lh %[temp8], " #C "(%[in]) \n\t" \ + "lh %[temp9], " #D "(%[in]) \n\t" \ + "ins %[" #TEMP1 "], %[" #TEMP0 "], 16, 16 \n\t" \ + "ins %[temp9], %[temp8], 16, 16 \n\t" \ + "subq.ph %[temp8], %[" #TEMP1 "], %[temp9] \n\t" \ + "addq.ph %[temp9], %[" #TEMP1 "], %[temp9] \n\t" \ + "precrq.ph.w %[" #TEMP0 "], %[temp8], %[temp9] \n\t" \ + "append %[temp8], %[temp9], 16 \n\t" \ + "subq.ph %[" #TEMP1 "], %[" #TEMP0 "], %[temp8] \n\t" \ + "addq.ph %[" #TEMP0 "], %[" #TEMP0 "], %[temp8] \n\t" \ + "rotr %[" #TEMP1 "], %[" #TEMP1 "], 16 \n\t" + +// macro for one vertical pass in FTransformWHT +// temp0..temp7 holds tmp[0]..tmp[15] +// A, B, C, D - offsets in bytes to store to out buffer +// TEMP0, TEMP2, TEMP4 and TEMP6 - registers for corresponding tmp elements +#define VERTICAL_PASS_WHT(A, B, C, D, TEMP0, TEMP2, TEMP4, TEMP6) \ + "addq.ph %[temp8], %[" #TEMP0 "], %[" #TEMP4 "] \n\t" \ + "addq.ph %[temp9], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subq.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subq.ph %[" #TEMP6 "], %[" #TEMP0 "], %[" #TEMP4 "] \n\t" \ + "addqh.ph %[" #TEMP0 "], %[temp8], %[temp9] \n\t" \ + "subqh.ph %[" #TEMP4 "], %[" #TEMP6 "], %[" #TEMP2 "] \n\t" \ + "addqh.ph %[" #TEMP2 "], %[" #TEMP2 "], %[" #TEMP6 "] \n\t" \ + "subqh.ph %[" #TEMP6 "], %[temp8], %[temp9] \n\t" \ + "usw %[" #TEMP0 "], " #A "(%[out]) \n\t" \ + "usw %[" #TEMP2 "], " #B "(%[out]) \n\t" \ + "usw %[" #TEMP4 "], " #C "(%[out]) \n\t" \ + "usw %[" #TEMP6 "], " #D "(%[out]) \n\t" + +static void FTransformWHT_MIPSdspR2(const int16_t* in, int16_t* out) { + int temp0, temp1, temp2, temp3, temp4; + int temp5, temp6, temp7, temp8, temp9; + + __asm__ volatile ( + HORIZONTAL_PASS_WHT( 0, 32, 64, 96, temp0, temp1) + HORIZONTAL_PASS_WHT(128, 160, 192, 224, temp2, temp3) + HORIZONTAL_PASS_WHT(256, 288, 320, 352, temp4, temp5) + HORIZONTAL_PASS_WHT(384, 416, 448, 480, temp6, temp7) + VERTICAL_PASS_WHT(0, 8, 16, 24, temp0, temp2, temp4, temp6) + VERTICAL_PASS_WHT(4, 12, 20, 28, temp1, temp3, temp5, temp7) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), + [temp9]"=&r"(temp9) + : [in]"r"(in), [out]"r"(out) + : "memory" + ); +} + +#undef VERTICAL_PASS_WHT +#undef HORIZONTAL_PASS_WHT + +// macro for converting coefficients to bin +// convert 8 coeffs at time +// A, B, C, D - offsets in bytes to load from out buffer +#define CONVERT_COEFFS_TO_BIN(A, B, C, D) \ + "ulw %[temp0], " #A "(%[out]) \n\t" \ + "ulw %[temp1], " #B "(%[out]) \n\t" \ + "ulw %[temp2], " #C "(%[out]) \n\t" \ + "ulw %[temp3], " #D "(%[out]) \n\t" \ + "absq_s.ph %[temp0], %[temp0] \n\t" \ + "absq_s.ph %[temp1], %[temp1] \n\t" \ + "absq_s.ph %[temp2], %[temp2] \n\t" \ + "absq_s.ph %[temp3], %[temp3] \n\t" \ + "shra.ph %[temp0], %[temp0], 3 \n\t" \ + "shra.ph %[temp1], %[temp1], 3 \n\t" \ + "shra.ph %[temp2], %[temp2], 3 \n\t" \ + "shra.ph %[temp3], %[temp3], 3 \n\t" \ + "shll_s.ph %[temp0], %[temp0], 10 \n\t" \ + "shll_s.ph %[temp1], %[temp1], 10 \n\t" \ + "shll_s.ph %[temp2], %[temp2], 10 \n\t" \ + "shll_s.ph %[temp3], %[temp3], 10 \n\t" \ + "shrl.ph %[temp0], %[temp0], 10 \n\t" \ + "shrl.ph %[temp1], %[temp1], 10 \n\t" \ + "shrl.ph %[temp2], %[temp2], 10 \n\t" \ + "shrl.ph %[temp3], %[temp3], 10 \n\t" \ + "shll.ph %[temp0], %[temp0], 2 \n\t" \ + "shll.ph %[temp1], %[temp1], 2 \n\t" \ + "shll.ph %[temp2], %[temp2], 2 \n\t" \ + "shll.ph %[temp3], %[temp3], 2 \n\t" \ + "ext %[temp4], %[temp0], 0, 16 \n\t" \ + "ext %[temp0], %[temp0], 16, 16 \n\t" \ + "addu %[temp4], %[temp4], %[dist] \n\t" \ + "addu %[temp0], %[temp0], %[dist] \n\t" \ + "ext %[temp5], %[temp1], 0, 16 \n\t" \ + "lw %[temp8], 0(%[temp4]) \n\t" \ + "ext %[temp1], %[temp1], 16, 16 \n\t" \ + "addu %[temp5], %[temp5], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp4]) \n\t" \ + "lw %[temp8], 0(%[temp0]) \n\t" \ + "addu %[temp1], %[temp1], %[dist] \n\t" \ + "ext %[temp6], %[temp2], 0, 16 \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp0]) \n\t" \ + "lw %[temp8], 0(%[temp5]) \n\t" \ + "ext %[temp2], %[temp2], 16, 16 \n\t" \ + "addu %[temp6], %[temp6], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp5]) \n\t" \ + "lw %[temp8], 0(%[temp1]) \n\t" \ + "addu %[temp2], %[temp2], %[dist] \n\t" \ + "ext %[temp7], %[temp3], 0, 16 \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp1]) \n\t" \ + "lw %[temp8], 0(%[temp6]) \n\t" \ + "ext %[temp3], %[temp3], 16, 16 \n\t" \ + "addu %[temp7], %[temp7], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp6]) \n\t" \ + "lw %[temp8], 0(%[temp2]) \n\t" \ + "addu %[temp3], %[temp3], %[dist] \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp2]) \n\t" \ + "lw %[temp8], 0(%[temp7]) \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp7]) \n\t" \ + "lw %[temp8], 0(%[temp3]) \n\t" \ + "addiu %[temp8], %[temp8], 1 \n\t" \ + "sw %[temp8], 0(%[temp3]) \n\t" + +static void CollectHistogram_MIPSdspR2(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + const int max_coeff = (MAX_COEFF_THRESH << 16) + MAX_COEFF_THRESH; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8; + + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin. + __asm__ volatile ( + CONVERT_COEFFS_TO_BIN( 0, 4, 8, 12) + CONVERT_COEFFS_TO_BIN(16, 20, 24, 28) + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [temp8]"=&r"(temp8) + : [dist]"r"(distribution), [out]"r"(out), [max_coeff]"r"(max_coeff) + : "memory" + ); + } + VP8SetHistogramData(distribution, histo); +} + +#undef CONVERT_COEFFS_TO_BIN + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMIPSdspR2(void) { + VP8FTransform = FTransform_MIPSdspR2; + VP8FTransformWHT = FTransformWHT_MIPSdspR2; + VP8ITransform = ITransform_MIPSdspR2; + + VP8TDisto4x4 = Disto4x4_MIPSdspR2; + VP8TDisto16x16 = Disto16x16_MIPSdspR2; + + VP8EncPredLuma16 = Intra16Preds_MIPSdspR2; + VP8EncPredChroma8 = IntraChromaPreds_MIPSdspR2; + VP8EncPredLuma4 = Intra4Preds_MIPSdspR2; + +#if !defined(WORK_AROUND_GCC) + VP8SSE16x16 = SSE16x16_MIPSdspR2; + VP8SSE8x8 = SSE8x8_MIPSdspR2; + VP8SSE16x8 = SSE16x8_MIPSdspR2; + VP8SSE4x4 = SSE4x4_MIPSdspR2; +#endif + + VP8EncQuantizeBlock = QuantizeBlock_MIPSdspR2; + VP8EncQuantize2Blocks = Quantize2Blocks_MIPSdspR2; + + VP8CollectHistogram = CollectHistogram_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8EncDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_msa.c new file mode 100644 index 0000000000..6f85add4bb --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_msa.c @@ -0,0 +1,896 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of encoder dsp functions. +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include +#include "src/dsp/msa_macro.h" +#include "src/enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Transforms + +#define IDCT_1D_W(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v4i32 a1_m, b1_m, c1_m, d1_m; \ + const v4i32 cospi8sqrt2minus1 = __msa_fill_w(20091); \ + const v4i32 sinpi8sqrt2 = __msa_fill_w(35468); \ + v4i32 c_tmp1_m = in1 * sinpi8sqrt2; \ + v4i32 c_tmp2_m = in3 * cospi8sqrt2minus1; \ + v4i32 d_tmp1_m = in1 * cospi8sqrt2minus1; \ + v4i32 d_tmp2_m = in3 * sinpi8sqrt2; \ + \ + ADDSUB2(in0, in2, a1_m, b1_m); \ + SRAI_W2_SW(c_tmp1_m, c_tmp2_m, 16); \ + c_tmp2_m = c_tmp2_m + in3; \ + c1_m = c_tmp1_m - c_tmp2_m; \ + SRAI_W2_SW(d_tmp1_m, d_tmp2_m, 16); \ + d_tmp1_m = d_tmp1_m + in1; \ + d1_m = d_tmp1_m + d_tmp2_m; \ + BUTTERFLY_4(a1_m, b1_m, c1_m, d1_m, out0, out1, out2, out3); \ +} while (0) + +static WEBP_INLINE void ITransformOne(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + v8i16 input0, input1; + v4i32 in0, in1, in2, in3, hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3; + v4i32 res0, res1, res2, res3; + v16i8 dest0, dest1, dest2, dest3; + const v16i8 zero = { 0 }; + + LD_SH2(in, 8, input0, input1); + UNPCK_SH_SW(input0, in0, in1); + UNPCK_SH_SW(input1, in2, in3); + IDCT_1D_W(in0, in1, in2, in3, hz0, hz1, hz2, hz3); + TRANSPOSE4x4_SW_SW(hz0, hz1, hz2, hz3, hz0, hz1, hz2, hz3); + IDCT_1D_W(hz0, hz1, hz2, hz3, vt0, vt1, vt2, vt3); + SRARI_W4_SW(vt0, vt1, vt2, vt3, 3); + TRANSPOSE4x4_SW_SW(vt0, vt1, vt2, vt3, vt0, vt1, vt2, vt3); + LD_SB4(ref, BPS, dest0, dest1, dest2, dest3); + ILVR_B4_SW(zero, dest0, zero, dest1, zero, dest2, zero, dest3, + res0, res1, res2, res3); + ILVR_H4_SW(zero, res0, zero, res1, zero, res2, zero, res3, + res0, res1, res2, res3); + ADD4(res0, vt0, res1, vt1, res2, vt2, res3, vt3, res0, res1, res2, res3); + CLIP_SW4_0_255(res0, res1, res2, res3); + PCKEV_B2_SW(res0, res1, res2, res3, vt0, vt1); + res0 = (v4i32)__msa_pckev_b((v16i8)vt0, (v16i8)vt1); + ST4x4_UB(res0, res0, 3, 2, 1, 0, dst, BPS); +} + +static void ITransform_MSA(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { + ITransformOne(ref, in, dst); + if (do_two) { + ITransformOne(ref + 4, in + 16, dst + 4); + } +} + +static void FTransform_MSA(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + uint64_t out0, out1, out2, out3; + uint32_t in0, in1, in2, in3; + v4i32 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + v8i16 t0, t1, t2, t3; + v16u8 srcl0, srcl1, src0 = { 0 }, src1 = { 0 }; + const v8i16 mask0 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask1 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + const v8i16 mask2 = { 4, 0, 5, 1, 6, 2, 7, 3 }; + const v8i16 mask3 = { 0, 4, 1, 5, 2, 6, 3, 7 }; + const v8i16 cnst0 = { 2217, -5352, 2217, -5352, 2217, -5352, 2217, -5352 }; + const v8i16 cnst1 = { 5352, 2217, 5352, 2217, 5352, 2217, 5352, 2217 }; + + LW4(src, BPS, in0, in1, in2, in3); + INSERT_W4_UB(in0, in1, in2, in3, src0); + LW4(ref, BPS, in0, in1, in2, in3); + INSERT_W4_UB(in0, in1, in2, in3, src1); + ILVRL_B2_UB(src0, src1, srcl0, srcl1); + HSUB_UB2_SH(srcl0, srcl1, t0, t1); + VSHF_H2_SH(t0, t1, t0, t1, mask0, mask1, t2, t3); + ADDSUB2(t2, t3, t0, t1); + t0 = SRLI_H(t0, 3); + VSHF_H2_SH(t0, t0, t1, t1, mask2, mask3, t3, t2); + tmp0 = __msa_hadd_s_w(t3, t3); + tmp2 = __msa_hsub_s_w(t3, t3); + FILL_W2_SW(1812, 937, tmp1, tmp3); + DPADD_SH2_SW(t2, t2, cnst0, cnst1, tmp3, tmp1); + SRAI_W2_SW(tmp1, tmp3, 9); + PCKEV_H2_SH(tmp1, tmp0, tmp3, tmp2, t0, t1); + VSHF_H2_SH(t0, t1, t0, t1, mask0, mask1, t2, t3); + ADDSUB2(t2, t3, t0, t1); + VSHF_H2_SH(t0, t0, t1, t1, mask2, mask3, t3, t2); + tmp0 = __msa_hadd_s_w(t3, t3); + tmp2 = __msa_hsub_s_w(t3, t3); + ADDVI_W2_SW(tmp0, 7, tmp2, 7, tmp0, tmp2); + SRAI_W2_SW(tmp0, tmp2, 4); + FILL_W2_SW(12000, 51000, tmp1, tmp3); + DPADD_SH2_SW(t2, t2, cnst0, cnst1, tmp3, tmp1); + SRAI_W2_SW(tmp1, tmp3, 16); + UNPCK_R_SH_SW(t1, tmp4); + tmp5 = __msa_ceqi_w(tmp4, 0); + tmp4 = (v4i32)__msa_nor_v((v16u8)tmp5, (v16u8)tmp5); + tmp5 = __msa_fill_w(1); + tmp5 = (v4i32)__msa_and_v((v16u8)tmp5, (v16u8)tmp4); + tmp1 += tmp5; + PCKEV_H2_SH(tmp1, tmp0, tmp3, tmp2, t0, t1); + out0 = __msa_copy_s_d((v2i64)t0, 0); + out1 = __msa_copy_s_d((v2i64)t0, 1); + out2 = __msa_copy_s_d((v2i64)t1, 0); + out3 = __msa_copy_s_d((v2i64)t1, 1); + SD4(out0, out1, out2, out3, out, 8); +} + +static void FTransformWHT_MSA(const int16_t* in, int16_t* out) { + v8i16 in0 = { 0 }; + v8i16 in1 = { 0 }; + v8i16 tmp0, tmp1, tmp2, tmp3; + v8i16 out0, out1; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + + in0 = __msa_insert_h(in0, 0, in[ 0]); + in0 = __msa_insert_h(in0, 1, in[ 64]); + in0 = __msa_insert_h(in0, 2, in[128]); + in0 = __msa_insert_h(in0, 3, in[192]); + in0 = __msa_insert_h(in0, 4, in[ 16]); + in0 = __msa_insert_h(in0, 5, in[ 80]); + in0 = __msa_insert_h(in0, 6, in[144]); + in0 = __msa_insert_h(in0, 7, in[208]); + in1 = __msa_insert_h(in1, 0, in[ 48]); + in1 = __msa_insert_h(in1, 1, in[112]); + in1 = __msa_insert_h(in1, 2, in[176]); + in1 = __msa_insert_h(in1, 3, in[240]); + in1 = __msa_insert_h(in1, 4, in[ 32]); + in1 = __msa_insert_h(in1, 5, in[ 96]); + in1 = __msa_insert_h(in1, 6, in[160]); + in1 = __msa_insert_h(in1, 7, in[224]); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, out0, out1); + SRAI_H2_SH(out0, out1, 1); + ST_SH2(out0, out1, out, 8); +} + +static int TTransform_MSA(const uint8_t* in, const uint16_t* w) { + int sum; + uint32_t in0_m, in1_m, in2_m, in3_m; + v16i8 src0 = { 0 }; + v8i16 in0, in1, tmp0, tmp1, tmp2, tmp3; + v4i32 dst0, dst1; + const v16i8 zero = { 0 }; + const v8i16 mask0 = { 0, 1, 2, 3, 8, 9, 10, 11 }; + const v8i16 mask1 = { 4, 5, 6, 7, 12, 13, 14, 15 }; + const v8i16 mask2 = { 0, 4, 8, 12, 1, 5, 9, 13 }; + const v8i16 mask3 = { 3, 7, 11, 15, 2, 6, 10, 14 }; + + LW4(in, BPS, in0_m, in1_m, in2_m, in3_m); + INSERT_W4_SB(in0_m, in1_m, in2_m, in3_m, src0); + ILVRL_B2_SH(zero, src0, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask2, mask3, in0, in1); + ADDSUB2(in0, in1, tmp0, tmp1); + VSHF_H2_SH(tmp0, tmp1, tmp0, tmp1, mask0, mask1, tmp2, tmp3); + ADDSUB2(tmp2, tmp3, tmp0, tmp1); + tmp0 = __msa_add_a_h(tmp0, (v8i16)zero); + tmp1 = __msa_add_a_h(tmp1, (v8i16)zero); + LD_SH2(w, 8, tmp2, tmp3); + DOTP_SH2_SW(tmp0, tmp1, tmp2, tmp3, dst0, dst1); + dst0 = dst0 + dst1; + sum = HADD_SW_S32(dst0); + return sum; +} + +static int Disto4x4_MSA(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int sum1 = TTransform_MSA(a, w); + const int sum2 = TTransform_MSA(b, w); + return abs(sum2 - sum1) >> 5; +} + +static int Disto16x16_MSA(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_MSA(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Histogram + +static void CollectHistogram_MSA(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + { + int k; + v8i16 coeff0, coeff1; + const v8i16 zero = { 0 }; + const v8i16 max_coeff_thr = __msa_ldi_h(MAX_COEFF_THRESH); + LD_SH2(&out[0], 8, coeff0, coeff1); + coeff0 = __msa_add_a_h(coeff0, zero); + coeff1 = __msa_add_a_h(coeff1, zero); + SRAI_H2_SH(coeff0, coeff1, 3); + coeff0 = __msa_min_s_h(coeff0, max_coeff_thr); + coeff1 = __msa_min_s_h(coeff1, max_coeff_thr); + ST_SH2(coeff0, coeff1, &out[0], 8); + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Intra predictions + +// luma 4x4 prediction + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +static WEBP_INLINE void VE4(uint8_t* dst, const uint8_t* top) { // vertical + const v16u8 A1 = { 0 }; + const uint64_t val_m = LD(top - 1); + const v16u8 A = (v16u8)__msa_insert_d((v2i64)A1, 0, val_m); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C = SLDI_UB(A, A, 2); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R = __msa_aver_u_b(AC, B2); + const uint32_t out = __msa_copy_s_w((v4i32)R, 0); + SW4(out, out, out, out, dst, BPS); +} + +static WEBP_INLINE void HE4(uint8_t* dst, const uint8_t* top) { // horizontal + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); +} + +static WEBP_INLINE void DC4(uint8_t* dst, const uint8_t* top) { + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += top[i] + top[-5 + i]; + dc >>= 3; + dc = dc | (dc << 8) | (dc << 16) | (dc << 24); + SW4(dc, dc, dc, dc, dst, BPS); +} + +static WEBP_INLINE void RD4(uint8_t* dst, const uint8_t* top) { + const v16u8 A2 = { 0 }; + const uint64_t val_m = LD(top - 5); + const v16u8 A1 = (v16u8)__msa_insert_d((v2i64)A2, 0, val_m); + const v16u8 A = (v16u8)__msa_insert_b((v16i8)A1, 8, top[3]); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C = SLDI_UB(A, A, 2); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R0 = __msa_aver_u_b(AC, B2); + const v16u8 R1 = SLDI_UB(R0, R0, 1); + const v16u8 R2 = SLDI_UB(R1, R1, 1); + const v16u8 R3 = SLDI_UB(R2, R2, 1); + const uint32_t val0 = __msa_copy_s_w((v4i32)R0, 0); + const uint32_t val1 = __msa_copy_s_w((v4i32)R1, 0); + const uint32_t val2 = __msa_copy_s_w((v4i32)R2, 0); + const uint32_t val3 = __msa_copy_s_w((v4i32)R3, 0); + SW4(val3, val2, val1, val0, dst, BPS); +} + +static WEBP_INLINE void LD4(uint8_t* dst, const uint8_t* top) { + const v16u8 A1 = { 0 }; + const uint64_t val_m = LD(top); + const v16u8 A = (v16u8)__msa_insert_d((v2i64)A1, 0, val_m); + const v16u8 B = SLDI_UB(A, A, 1); + const v16u8 C1 = SLDI_UB(A, A, 2); + const v16u8 C = (v16u8)__msa_insert_b((v16i8)C1, 6, top[7]); + const v16u8 AC = __msa_ave_u_b(A, C); + const v16u8 B2 = __msa_ave_u_b(B, B); + const v16u8 R0 = __msa_aver_u_b(AC, B2); + const v16u8 R1 = SLDI_UB(R0, R0, 1); + const v16u8 R2 = SLDI_UB(R1, R1, 1); + const v16u8 R3 = SLDI_UB(R2, R2, 1); + const uint32_t val0 = __msa_copy_s_w((v4i32)R0, 0); + const uint32_t val1 = __msa_copy_s_w((v4i32)R1, 0); + const uint32_t val2 = __msa_copy_s_w((v4i32)R2, 0); + const uint32_t val3 = __msa_copy_s_w((v4i32)R3, 0); + SW4(val0, val1, val2, val3, dst, BPS); +} + +static WEBP_INLINE void VR4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + DST(0, 0) = DST(1, 2) = AVG2(X, A); + DST(1, 0) = DST(2, 2) = AVG2(A, B); + DST(2, 0) = DST(3, 2) = AVG2(B, C); + DST(3, 0) = AVG2(C, D); + DST(0, 3) = AVG3(K, J, I); + DST(0, 2) = AVG3(J, I, X); + DST(0, 1) = DST(1, 3) = AVG3(I, X, A); + DST(1, 1) = DST(2, 3) = AVG3(X, A, B); + DST(2, 1) = DST(3, 3) = AVG3(A, B, C); + DST(3, 1) = AVG3(B, C, D); +} + +static WEBP_INLINE void VL4(uint8_t* dst, const uint8_t* top) { + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + const int D = top[3]; + const int E = top[4]; + const int F = top[5]; + const int G = top[6]; + const int H = top[7]; + DST(0, 0) = AVG2(A, B); + DST(1, 0) = DST(0, 2) = AVG2(B, C); + DST(2, 0) = DST(1, 2) = AVG2(C, D); + DST(3, 0) = DST(2, 2) = AVG2(D, E); + DST(0, 1) = AVG3(A, B, C); + DST(1, 1) = DST(0, 3) = AVG3(B, C, D); + DST(2, 1) = DST(1, 3) = AVG3(C, D, E); + DST(3, 1) = DST(2, 3) = AVG3(D, E, F); + DST(3, 2) = AVG3(E, F, G); + DST(3, 3) = AVG3(F, G, H); +} + +static WEBP_INLINE void HU4(uint8_t* dst, const uint8_t* top) { + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static WEBP_INLINE void HD4(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +static WEBP_INLINE void TM4(uint8_t* dst, const uint8_t* top) { + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(top[-1]); + const v8i16 L0 = (v8i16)__msa_fill_h(top[-2]); + const v8i16 L1 = (v8i16)__msa_fill_h(top[-3]); + const v8i16 L2 = (v8i16)__msa_fill_h(top[-4]); + const v8i16 L3 = (v8i16)__msa_fill_h(top[-5]); + const v16u8 T1 = LD_UB(top); + const v8i16 T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + const v8i16 d = T - TL; + v8i16 r0, r1, r2, r3; + ADD4(d, L0, d, L1, d, L2, d, L3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_ST4x4_UB(r0, r1, r2, r3, dst, BPS); +} + +#undef DST +#undef AVG3 +#undef AVG2 + +static void Intra4Preds_MSA(uint8_t* dst, const uint8_t* top) { + DC4(I4DC4 + dst, top); + TM4(I4TM4 + dst, top); + VE4(I4VE4 + dst, top); + HE4(I4HE4 + dst, top); + RD4(I4RD4 + dst, top); + VR4(I4VR4 + dst, top); + LD4(I4LD4 + dst, top); + VL4(I4VL4 + dst, top); + HD4(I4HD4 + dst, top); + HU4(I4HU4 + dst, top); +} + +// luma 16x16 prediction + +#define STORE16x16(out, dst) do { \ + ST_UB8(out, out, out, out, out, out, out, out, dst + 0 * BPS, BPS); \ + ST_UB8(out, out, out, out, out, out, out, out, dst + 8 * BPS, BPS); \ +} while (0) + +static WEBP_INLINE void VerticalPred16x16(uint8_t* dst, const uint8_t* top) { + if (top != NULL) { + const v16u8 out = LD_UB(top); + STORE16x16(out, dst); + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x7f); + STORE16x16(out, dst); + } +} + +static WEBP_INLINE void HorizontalPred16x16(uint8_t* dst, + const uint8_t* left) { + if (left != NULL) { + int j; + for (j = 0; j < 16; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(left[0]); + const v16u8 L1 = (v16u8)__msa_fill_b(left[1]); + const v16u8 L2 = (v16u8)__msa_fill_b(left[2]); + const v16u8 L3 = (v16u8)__msa_fill_b(left[3]); + ST_UB4(L0, L1, L2, L3, dst, BPS); + dst += 4 * BPS; + left += 4; + } + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x81); + STORE16x16(out, dst); + } +} + +static WEBP_INLINE void TrueMotion16x16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (left != NULL) { + if (top != NULL) { + int j; + v8i16 d1, d2; + const v16i8 zero = { 0 }; + const v8i16 TL = (v8i16)__msa_fill_h(left[-1]); + const v16u8 T = LD_UB(top); + ILVRL_B2_SH(zero, T, d1, d2); + SUB2(d1, TL, d2, TL, d1, d2); + for (j = 0; j < 16; j += 4) { + v16i8 t0, t1, t2, t3; + v8i16 r0, r1, r2, r3, r4, r5, r6, r7; + const v8i16 L0 = (v8i16)__msa_fill_h(left[j + 0]); + const v8i16 L1 = (v8i16)__msa_fill_h(left[j + 1]); + const v8i16 L2 = (v8i16)__msa_fill_h(left[j + 2]); + const v8i16 L3 = (v8i16)__msa_fill_h(left[j + 3]); + ADD4(d1, L0, d1, L1, d1, L2, d1, L3, r0, r1, r2, r3); + ADD4(d2, L0, d2, L1, d2, L2, d2, L3, r4, r5, r6, r7); + CLIP_SH4_0_255(r0, r1, r2, r3); + CLIP_SH4_0_255(r4, r5, r6, r7); + PCKEV_B4_SB(r4, r0, r5, r1, r6, r2, r7, r3, t0, t1, t2, t3); + ST_SB4(t0, t1, t2, t3, dst, BPS); + dst += 4 * BPS; + } + } else { + HorizontalPred16x16(dst, left); + } + } else { + if (top != NULL) { + VerticalPred16x16(dst, top); + } else { + const v16u8 out = (v16u8)__msa_fill_b(0x81); + STORE16x16(out, dst); + } + } +} + +static WEBP_INLINE void DCMode16x16(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + int DC; + v16u8 out; + if (top != NULL && left != NULL) { + const v16u8 rtop = LD_UB(top); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + const v16u8 rleft = LD_UB(left); + const v8u16 dcleft = __msa_hadd_u_h(rleft, rleft); + const v8u16 dctemp = dctop + dcleft; + DC = HADD_UH_U32(dctemp); + DC = (DC + 16) >> 5; + } else if (left != NULL) { // left but no top + const v16u8 rleft = LD_UB(left); + const v8u16 dcleft = __msa_hadd_u_h(rleft, rleft); + DC = HADD_UH_U32(dcleft); + DC = (DC + DC + 16) >> 5; + } else if (top != NULL) { // top but no left + const v16u8 rtop = LD_UB(top); + const v8u16 dctop = __msa_hadd_u_h(rtop, rtop); + DC = HADD_UH_U32(dctop); + DC = (DC + DC + 16) >> 5; + } else { // no top, no left, nothing. + DC = 0x80; + } + out = (v16u8)__msa_fill_b(DC); + STORE16x16(out, dst); +} + +static void Intra16Preds_MSA(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DCMode16x16(I16DC16 + dst, left, top); + VerticalPred16x16(I16VE16 + dst, top); + HorizontalPred16x16(I16HE16 + dst, left); + TrueMotion16x16(I16TM16 + dst, left, top); +} + +// Chroma 8x8 prediction + +#define CALC_DC8(in, out) do { \ + const v8u16 temp0 = __msa_hadd_u_h(in, in); \ + const v4u32 temp1 = __msa_hadd_u_w(temp0, temp0); \ + const v2i64 temp2 = (v2i64)__msa_hadd_u_d(temp1, temp1); \ + const v2i64 temp3 = __msa_splati_d(temp2, 1); \ + const v2i64 temp4 = temp3 + temp2; \ + const v16i8 temp5 = (v16i8)__msa_srari_d(temp4, 4); \ + const v2i64 temp6 = (v2i64)__msa_splati_b(temp5, 0); \ + out = __msa_copy_s_d(temp6, 0); \ +} while (0) + +#define STORE8x8(out, dst) do { \ + SD4(out, out, out, out, dst + 0 * BPS, BPS); \ + SD4(out, out, out, out, dst + 4 * BPS, BPS); \ +} while (0) + +static WEBP_INLINE void VerticalPred8x8(uint8_t* dst, const uint8_t* top) { + if (top != NULL) { + const uint64_t out = LD(top); + STORE8x8(out, dst); + } else { + const uint64_t out = 0x7f7f7f7f7f7f7f7fULL; + STORE8x8(out, dst); + } +} + +static WEBP_INLINE void HorizontalPred8x8(uint8_t* dst, const uint8_t* left) { + if (left != NULL) { + int j; + for (j = 0; j < 8; j += 4) { + const v16u8 L0 = (v16u8)__msa_fill_b(left[0]); + const v16u8 L1 = (v16u8)__msa_fill_b(left[1]); + const v16u8 L2 = (v16u8)__msa_fill_b(left[2]); + const v16u8 L3 = (v16u8)__msa_fill_b(left[3]); + const uint64_t out0 = __msa_copy_s_d((v2i64)L0, 0); + const uint64_t out1 = __msa_copy_s_d((v2i64)L1, 0); + const uint64_t out2 = __msa_copy_s_d((v2i64)L2, 0); + const uint64_t out3 = __msa_copy_s_d((v2i64)L3, 0); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + left += 4; + } + } else { + const uint64_t out = 0x8181818181818181ULL; + STORE8x8(out, dst); + } +} + +static WEBP_INLINE void TrueMotion8x8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (left != NULL) { + if (top != NULL) { + int j; + const v8i16 TL = (v8i16)__msa_fill_h(left[-1]); + const v16u8 T1 = LD_UB(top); + const v16i8 zero = { 0 }; + const v8i16 T = (v8i16)__msa_ilvr_b(zero, (v16i8)T1); + const v8i16 d = T - TL; + for (j = 0; j < 8; j += 4) { + uint64_t out0, out1, out2, out3; + v16i8 t0, t1; + v8i16 r0 = (v8i16)__msa_fill_h(left[j + 0]); + v8i16 r1 = (v8i16)__msa_fill_h(left[j + 1]); + v8i16 r2 = (v8i16)__msa_fill_h(left[j + 2]); + v8i16 r3 = (v8i16)__msa_fill_h(left[j + 3]); + ADD4(d, r0, d, r1, d, r2, d, r3, r0, r1, r2, r3); + CLIP_SH4_0_255(r0, r1, r2, r3); + PCKEV_B2_SB(r1, r0, r3, r2, t0, t1); + out0 = __msa_copy_s_d((v2i64)t0, 0); + out1 = __msa_copy_s_d((v2i64)t0, 1); + out2 = __msa_copy_s_d((v2i64)t1, 0); + out3 = __msa_copy_s_d((v2i64)t1, 1); + SD4(out0, out1, out2, out3, dst, BPS); + dst += 4 * BPS; + } + } else { + HorizontalPred8x8(dst, left); + } + } else { + if (top != NULL) { + VerticalPred8x8(dst, top); + } else { + const uint64_t out = 0x8181818181818181ULL; + STORE8x8(out, dst); + } + } +} + +static WEBP_INLINE void DCMode8x8(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + uint64_t out; + v16u8 src = { 0 }; + if (top != NULL && left != NULL) { + const uint64_t left_m = LD(left); + const uint64_t top_m = LD(top); + INSERT_D2_UB(left_m, top_m, src); + CALC_DC8(src, out); + } else if (left != NULL) { // left but no top + const uint64_t left_m = LD(left); + INSERT_D2_UB(left_m, left_m, src); + CALC_DC8(src, out); + } else if (top != NULL) { // top but no left + const uint64_t top_m = LD(top); + INSERT_D2_UB(top_m, top_m, src); + CALC_DC8(src, out); + } else { // no top, no left, nothing. + src = (v16u8)__msa_fill_b(0x80); + out = __msa_copy_s_d((v2i64)src, 0); + } + STORE8x8(out, dst); +} + +static void IntraChromaPreds_MSA(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DCMode8x8(C8DC8 + dst, left, top); + VerticalPred8x8(C8VE8 + dst, top); + HorizontalPred8x8(C8HE8 + dst, left); + TrueMotion8x8(C8TM8 + dst, left, top); + // V block + dst += 8; + if (top != NULL) top += 8; + if (left != NULL) left += 16; + DCMode8x8(C8DC8 + dst, left, top); + VerticalPred8x8(C8VE8 + dst, top); + HorizontalPred8x8(C8HE8 + dst, left); + TrueMotion8x8(C8TM8 + dst, left, top); +} + +//------------------------------------------------------------------------------ +// Metric + +#define PACK_DOTP_UB4_SW(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v16u8 tmp0, tmp1; \ + v8i16 tmp2, tmp3; \ + ILVRL_B2_UB(in0, in1, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DOTP_SH2_SW(tmp2, tmp3, tmp2, tmp3, out0, out1); \ + ILVRL_B2_UB(in2, in3, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DOTP_SH2_SW(tmp2, tmp3, tmp2, tmp3, out2, out3); \ +} while (0) + +#define PACK_DPADD_UB4_SW(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + v16u8 tmp0, tmp1; \ + v8i16 tmp2, tmp3; \ + ILVRL_B2_UB(in0, in1, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DPADD_SH2_SW(tmp2, tmp3, tmp2, tmp3, out0, out1); \ + ILVRL_B2_UB(in2, in3, tmp0, tmp1); \ + HSUB_UB2_SH(tmp0, tmp1, tmp2, tmp3); \ + DPADD_SH2_SW(tmp2, tmp3, tmp2, tmp3, out2, out3); \ +} while (0) + +static int SSE16x16_MSA(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DOTP_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + a += 8 * BPS; + b += 8 * BPS; + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DPADD_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE16x8_MSA(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + PACK_DOTP_UB4_SW(src0, ref0, src1, ref1, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src2, ref2, src3, ref3, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src4, ref4, src5, ref5, out0, out1, out2, out3); + PACK_DPADD_UB4_SW(src6, ref6, src7, ref7, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE8x8_MSA(const uint8_t* a, const uint8_t* b) { + uint32_t sum; + v16u8 src0, src1, src2, src3, src4, src5, src6, src7; + v16u8 ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7; + v16u8 t0, t1, t2, t3; + v4i32 out0, out1, out2, out3; + + LD_UB8(a, BPS, src0, src1, src2, src3, src4, src5, src6, src7); + LD_UB8(b, BPS, ref0, ref1, ref2, ref3, ref4, ref5, ref6, ref7); + ILVR_B4_UB(src0, src1, src2, src3, ref0, ref1, ref2, ref3, t0, t1, t2, t3); + PACK_DOTP_UB4_SW(t0, t2, t1, t3, out0, out1, out2, out3); + ILVR_B4_UB(src4, src5, src6, src7, ref4, ref5, ref6, ref7, t0, t1, t2, t3); + PACK_DPADD_UB4_SW(t0, t2, t1, t3, out0, out1, out2, out3); + out0 += out1; + out2 += out3; + out0 += out2; + sum = HADD_SW_S32(out0); + return sum; +} + +static int SSE4x4_MSA(const uint8_t* a, const uint8_t* b) { + uint32_t sum = 0; + uint32_t src0, src1, src2, src3, ref0, ref1, ref2, ref3; + v16u8 src = { 0 }, ref = { 0 }, tmp0, tmp1; + v8i16 diff0, diff1; + v4i32 out0, out1; + + LW4(a, BPS, src0, src1, src2, src3); + LW4(b, BPS, ref0, ref1, ref2, ref3); + INSERT_W4_UB(src0, src1, src2, src3, src); + INSERT_W4_UB(ref0, ref1, ref2, ref3, ref); + ILVRL_B2_UB(src, ref, tmp0, tmp1); + HSUB_UB2_SH(tmp0, tmp1, diff0, diff1); + DOTP_SH2_SW(diff0, diff1, diff0, diff1, out0, out1); + out0 += out1; + sum = HADD_SW_S32(out0); + return sum; +} + +//------------------------------------------------------------------------------ +// Quantization + +static int QuantizeBlock_MSA(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + int sum; + v8i16 in0, in1, sh0, sh1, out0, out1; + v8i16 tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, sign0, sign1; + v4i32 s0, s1, s2, s3, b0, b1, b2, b3, t0, t1, t2, t3; + const v8i16 zero = { 0 }; + const v8i16 zigzag0 = { 0, 1, 4, 8, 5, 2, 3, 6 }; + const v8i16 zigzag1 = { 9, 12, 13, 10, 7, 11, 14, 15 }; + const v8i16 maxlevel = __msa_fill_h(MAX_LEVEL); + + LD_SH2(&in[0], 8, in0, in1); + LD_SH2(&mtx->sharpen_[0], 8, sh0, sh1); + tmp4 = __msa_add_a_h(in0, zero); + tmp5 = __msa_add_a_h(in1, zero); + ILVRL_H2_SH(sh0, tmp4, tmp0, tmp1); + ILVRL_H2_SH(sh1, tmp5, tmp2, tmp3); + HADD_SH4_SW(tmp0, tmp1, tmp2, tmp3, s0, s1, s2, s3); + sign0 = (in0 < zero); + sign1 = (in1 < zero); // sign + LD_SH2(&mtx->iq_[0], 8, tmp0, tmp1); // iq + ILVRL_H2_SW(zero, tmp0, t0, t1); + ILVRL_H2_SW(zero, tmp1, t2, t3); + LD_SW4(&mtx->bias_[0], 4, b0, b1, b2, b3); // bias + MUL4(t0, s0, t1, s1, t2, s2, t3, s3, t0, t1, t2, t3); + ADD4(b0, t0, b1, t1, b2, t2, b3, t3, b0, b1, b2, b3); + SRAI_W4_SW(b0, b1, b2, b3, 17); + PCKEV_H2_SH(b1, b0, b3, b2, tmp2, tmp3); + tmp0 = (tmp2 > maxlevel); + tmp1 = (tmp3 > maxlevel); + tmp2 = (v8i16)__msa_bmnz_v((v16u8)tmp2, (v16u8)maxlevel, (v16u8)tmp0); + tmp3 = (v8i16)__msa_bmnz_v((v16u8)tmp3, (v16u8)maxlevel, (v16u8)tmp1); + SUB2(zero, tmp2, zero, tmp3, tmp0, tmp1); + tmp2 = (v8i16)__msa_bmnz_v((v16u8)tmp2, (v16u8)tmp0, (v16u8)sign0); + tmp3 = (v8i16)__msa_bmnz_v((v16u8)tmp3, (v16u8)tmp1, (v16u8)sign1); + LD_SW4(&mtx->zthresh_[0], 4, t0, t1, t2, t3); // zthresh + t0 = (s0 > t0); + t1 = (s1 > t1); + t2 = (s2 > t2); + t3 = (s3 > t3); + PCKEV_H2_SH(t1, t0, t3, t2, tmp0, tmp1); + tmp4 = (v8i16)__msa_bmnz_v((v16u8)zero, (v16u8)tmp2, (v16u8)tmp0); + tmp5 = (v8i16)__msa_bmnz_v((v16u8)zero, (v16u8)tmp3, (v16u8)tmp1); + LD_SH2(&mtx->q_[0], 8, tmp0, tmp1); + MUL2(tmp4, tmp0, tmp5, tmp1, in0, in1); + VSHF_H2_SH(tmp4, tmp5, tmp4, tmp5, zigzag0, zigzag1, out0, out1); + ST_SH2(in0, in1, &in[0], 8); + ST_SH2(out0, out1, &out[0], 8); + out0 = __msa_add_a_h(out0, out1); + sum = HADD_SH_S32(out0); + return (sum > 0); +} + +static int Quantize2Blocks_MSA(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = VP8EncQuantizeBlock(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= VP8EncQuantizeBlock(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitMSA(void) { + VP8ITransform = ITransform_MSA; + VP8FTransform = FTransform_MSA; + VP8FTransformWHT = FTransformWHT_MSA; + + VP8TDisto4x4 = Disto4x4_MSA; + VP8TDisto16x16 = Disto16x16_MSA; + VP8CollectHistogram = CollectHistogram_MSA; + + VP8EncPredLuma4 = Intra4Preds_MSA; + VP8EncPredLuma16 = Intra16Preds_MSA; + VP8EncPredChroma8 = IntraChromaPreds_MSA; + + VP8SSE16x16 = SSE16x16_MSA; + VP8SSE16x8 = SSE16x8_MSA; + VP8SSE8x8 = SSE8x8_MSA; + VP8SSE4x4 = SSE4x4_MSA; + + VP8EncQuantizeBlock = QuantizeBlock_MSA; + VP8EncQuantize2Blocks = Quantize2Blocks_MSA; + VP8EncQuantizeBlockWHT = QuantizeBlock_MSA; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8EncDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_neon.c new file mode 100644 index 0000000000..6f641c9a76 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_neon.c @@ -0,0 +1,944 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARM NEON version of speed-critical encoding functions. +// +// adapted from libvpx (https://www.webmproject.org/code/) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "src/dsp/neon.h" +#include "src/enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +// Inverse transform. +// This code is pretty much the same as TransformOne in the dec_neon.c, except +// for subtraction to *ref. See the comments there for algorithmic explanations. + +static const int16_t kC1 = WEBP_TRANSFORM_AC3_C1; +static const int16_t kC2 = + WEBP_TRANSFORM_AC3_C2 / 2; // half of kC2, actually. See comment above. + +// This code works but is *slower* than the inlined-asm version below +// (with gcc-4.6). So we disable it for now. Later, it'll be conditional to +// WEBP_USE_INTRINSICS define. +// With gcc-4.8, it's a little faster speed than inlined-assembly. +#if defined(WEBP_USE_INTRINSICS) + +// Treats 'v' as an uint8x8_t and zero extends to an int16x8_t. +static WEBP_INLINE int16x8_t ConvertU8ToS16_NEON(uint32x2_t v) { + return vreinterpretq_s16_u16(vmovl_u8(vreinterpret_u8_u32(v))); +} + +// Performs unsigned 8b saturation on 'dst01' and 'dst23' storing the result +// to the corresponding rows of 'dst'. +static WEBP_INLINE void SaturateAndStore4x4_NEON(uint8_t* const dst, + const int16x8_t dst01, + const int16x8_t dst23) { + // Unsigned saturate to 8b. + const uint8x8_t dst01_u8 = vqmovun_s16(dst01); + const uint8x8_t dst23_u8 = vqmovun_s16(dst23); + + // Store the results. + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), vreinterpret_u32_u8(dst01_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), vreinterpret_u32_u8(dst01_u8), 1); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), vreinterpret_u32_u8(dst23_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), vreinterpret_u32_u8(dst23_u8), 1); +} + +static WEBP_INLINE void Add4x4_NEON(const int16x8_t row01, + const int16x8_t row23, + const uint8_t* const ref, + uint8_t* const dst) { + uint32x2_t dst01 = vdup_n_u32(0); + uint32x2_t dst23 = vdup_n_u32(0); + + // Load the source pixels. + dst01 = vld1_lane_u32((uint32_t*)(ref + 0 * BPS), dst01, 0); + dst23 = vld1_lane_u32((uint32_t*)(ref + 2 * BPS), dst23, 0); + dst01 = vld1_lane_u32((uint32_t*)(ref + 1 * BPS), dst01, 1); + dst23 = vld1_lane_u32((uint32_t*)(ref + 3 * BPS), dst23, 1); + + { + // Convert to 16b. + const int16x8_t dst01_s16 = ConvertU8ToS16_NEON(dst01); + const int16x8_t dst23_s16 = ConvertU8ToS16_NEON(dst23); + + // Descale with rounding. + const int16x8_t out01 = vrsraq_n_s16(dst01_s16, row01, 3); + const int16x8_t out23 = vrsraq_n_s16(dst23_s16, row23, 3); + // Add the inverse transform. + SaturateAndStore4x4_NEON(dst, out01, out23); + } +} + +static WEBP_INLINE void Transpose8x2_NEON(const int16x8_t in0, + const int16x8_t in1, + int16x8x2_t* const out) { + // a0 a1 a2 a3 | b0 b1 b2 b3 => a0 b0 c0 d0 | a1 b1 c1 d1 + // c0 c1 c2 c3 | d0 d1 d2 d3 a2 b2 c2 d2 | a3 b3 c3 d3 + const int16x8x2_t tmp0 = vzipq_s16(in0, in1); // a0 c0 a1 c1 a2 c2 ... + // b0 d0 b1 d1 b2 d2 ... + *out = vzipq_s16(tmp0.val[0], tmp0.val[1]); +} + +static WEBP_INLINE void TransformPass_NEON(int16x8x2_t* const rows) { + // {rows} = in0 | in4 + // in8 | in12 + // B1 = in4 | in12 + const int16x8_t B1 = + vcombine_s16(vget_high_s16(rows->val[0]), vget_high_s16(rows->val[1])); + // C0 = kC1 * in4 | kC1 * in12 + // C1 = kC2 * in4 | kC2 * in12 + const int16x8_t C0 = vsraq_n_s16(B1, vqdmulhq_n_s16(B1, kC1), 1); + const int16x8_t C1 = vqdmulhq_n_s16(B1, kC2); + const int16x4_t a = vqadd_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 + in8 + const int16x4_t b = vqsub_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 - in8 + // c = kC2 * in4 - kC1 * in12 + // d = kC1 * in4 + kC2 * in12 + const int16x4_t c = vqsub_s16(vget_low_s16(C1), vget_high_s16(C0)); + const int16x4_t d = vqadd_s16(vget_low_s16(C0), vget_high_s16(C1)); + const int16x8_t D0 = vcombine_s16(a, b); // D0 = a | b + const int16x8_t D1 = vcombine_s16(d, c); // D1 = d | c + const int16x8_t E0 = vqaddq_s16(D0, D1); // a+d | b+c + const int16x8_t E_tmp = vqsubq_s16(D0, D1); // a-d | b-c + const int16x8_t E1 = vcombine_s16(vget_high_s16(E_tmp), vget_low_s16(E_tmp)); + Transpose8x2_NEON(E0, E1, rows); +} + +static void ITransformOne_NEON(const uint8_t* ref, + const int16_t* in, uint8_t* dst) { + int16x8x2_t rows; + INIT_VECTOR2(rows, vld1q_s16(in + 0), vld1q_s16(in + 8)); + TransformPass_NEON(&rows); + TransformPass_NEON(&rows); + Add4x4_NEON(rows.val[0], rows.val[1], ref, dst); +} + +#else + +static void ITransformOne_NEON(const uint8_t* ref, + const int16_t* in, uint8_t* dst) { + const int kBPS = BPS; + const int16_t kC1C2[] = { kC1, kC2, 0, 0 }; + + __asm__ volatile ( + "vld1.16 {q1, q2}, [%[in]] \n" + "vld1.16 {d0}, [%[kC1C2]] \n" + + // d2: in[0] + // d3: in[8] + // d4: in[4] + // d5: in[12] + "vswp d3, d4 \n" + + // q8 = {in[4], in[12]} * kC1 * 2 >> 16 + // q9 = {in[4], in[12]} * kC2 >> 16 + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + // d22 = a = in[0] + in[8] + // d23 = b = in[0] - in[8] + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + // q8 = in[4]/[12] * kC1 >> 16 + "vshr.s16 q8, q8, #1 \n" + + // Add {in[4], in[12]} back after the multiplication. + "vqadd.s16 q8, q2, q8 \n" + + // d20 = c = in[4]*kC2 - in[12]*kC1 + // d21 = d = in[4]*kC1 + in[12]*kC2 + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + // d2 = tmp[0] = a + d + // d3 = tmp[1] = b + c + // d4 = tmp[2] = b - c + // d5 = tmp[3] = a - d + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + "vswp d3, d4 \n" + + // q8 = {tmp[4], tmp[12]} * kC1 * 2 >> 16 + // q9 = {tmp[4], tmp[12]} * kC2 >> 16 + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + // d22 = a = tmp[0] + tmp[8] + // d23 = b = tmp[0] - tmp[8] + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + "vshr.s16 q8, q8, #1 \n" + "vqadd.s16 q8, q2, q8 \n" + + // d20 = c = in[4]*kC2 - in[12]*kC1 + // d21 = d = in[4]*kC1 + in[12]*kC2 + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + // d2 = tmp[0] = a + d + // d3 = tmp[1] = b + c + // d4 = tmp[2] = b - c + // d5 = tmp[3] = a - d + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vld1.32 d6[0], [%[ref]], %[kBPS] \n" + "vld1.32 d6[1], [%[ref]], %[kBPS] \n" + "vld1.32 d7[0], [%[ref]], %[kBPS] \n" + "vld1.32 d7[1], [%[ref]], %[kBPS] \n" + + "sub %[ref], %[ref], %[kBPS], lsl #2 \n" + + // (val) + 4 >> 3 + "vrshr.s16 d2, d2, #3 \n" + "vrshr.s16 d3, d3, #3 \n" + "vrshr.s16 d4, d4, #3 \n" + "vrshr.s16 d5, d5, #3 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + // Must accumulate before saturating + "vmovl.u8 q8, d6 \n" + "vmovl.u8 q9, d7 \n" + + "vqadd.s16 q1, q1, q8 \n" + "vqadd.s16 q2, q2, q9 \n" + + "vqmovun.s16 d0, q1 \n" + "vqmovun.s16 d1, q2 \n" + + "vst1.32 d0[0], [%[dst]], %[kBPS] \n" + "vst1.32 d0[1], [%[dst]], %[kBPS] \n" + "vst1.32 d1[0], [%[dst]], %[kBPS] \n" + "vst1.32 d1[1], [%[dst]] \n" + + : [in] "+r"(in), [dst] "+r"(dst) // modified registers + : [kBPS] "r"(kBPS), [kC1C2] "r"(kC1C2), [ref] "r"(ref) // constants + : "memory", "q0", "q1", "q2", "q8", "q9", "q10", "q11" // clobbered + ); +} + +#endif // WEBP_USE_INTRINSICS + +static void ITransform_NEON(const uint8_t* ref, + const int16_t* in, uint8_t* dst, int do_two) { + ITransformOne_NEON(ref, in, dst); + if (do_two) { + ITransformOne_NEON(ref + 4, in + 16, dst + 4); + } +} + +// Load all 4x4 pixels into a single uint8x16_t variable. +static uint8x16_t Load4x4_NEON(const uint8_t* src) { + uint32x4_t out = vdupq_n_u32(0); + out = vld1q_lane_u32((const uint32_t*)(src + 0 * BPS), out, 0); + out = vld1q_lane_u32((const uint32_t*)(src + 1 * BPS), out, 1); + out = vld1q_lane_u32((const uint32_t*)(src + 2 * BPS), out, 2); + out = vld1q_lane_u32((const uint32_t*)(src + 3 * BPS), out, 3); + return vreinterpretq_u8_u32(out); +} + +// Forward transform. + +#if defined(WEBP_USE_INTRINSICS) + +static WEBP_INLINE void Transpose4x4_S16_NEON(const int16x4_t A, + const int16x4_t B, + const int16x4_t C, + const int16x4_t D, + int16x8_t* const out01, + int16x8_t* const out32) { + const int16x4x2_t AB = vtrn_s16(A, B); + const int16x4x2_t CD = vtrn_s16(C, D); + const int32x2x2_t tmp02 = vtrn_s32(vreinterpret_s32_s16(AB.val[0]), + vreinterpret_s32_s16(CD.val[0])); + const int32x2x2_t tmp13 = vtrn_s32(vreinterpret_s32_s16(AB.val[1]), + vreinterpret_s32_s16(CD.val[1])); + *out01 = vreinterpretq_s16_s64( + vcombine_s64(vreinterpret_s64_s32(tmp02.val[0]), + vreinterpret_s64_s32(tmp13.val[0]))); + *out32 = vreinterpretq_s16_s64( + vcombine_s64(vreinterpret_s64_s32(tmp13.val[1]), + vreinterpret_s64_s32(tmp02.val[1]))); +} + +static WEBP_INLINE int16x8_t DiffU8ToS16_NEON(const uint8x8_t a, + const uint8x8_t b) { + return vreinterpretq_s16_u16(vsubl_u8(a, b)); +} + +static void FTransform_NEON(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + int16x8_t d0d1, d3d2; // working 4x4 int16 variables + { + const uint8x16_t S0 = Load4x4_NEON(src); + const uint8x16_t R0 = Load4x4_NEON(ref); + const int16x8_t D0D1 = DiffU8ToS16_NEON(vget_low_u8(S0), vget_low_u8(R0)); + const int16x8_t D2D3 = DiffU8ToS16_NEON(vget_high_u8(S0), vget_high_u8(R0)); + const int16x4_t D0 = vget_low_s16(D0D1); + const int16x4_t D1 = vget_high_s16(D0D1); + const int16x4_t D2 = vget_low_s16(D2D3); + const int16x4_t D3 = vget_high_s16(D2D3); + Transpose4x4_S16_NEON(D0, D1, D2, D3, &d0d1, &d3d2); + } + { // 1rst pass + const int32x4_t kCst937 = vdupq_n_s32(937); + const int32x4_t kCst1812 = vdupq_n_s32(1812); + const int16x8_t a0a1 = vaddq_s16(d0d1, d3d2); // d0+d3 | d1+d2 (=a0|a1) + const int16x8_t a3a2 = vsubq_s16(d0d1, d3d2); // d0-d3 | d1-d2 (=a3|a2) + const int16x8_t a0a1_2 = vshlq_n_s16(a0a1, 3); + const int16x4_t tmp0 = vadd_s16(vget_low_s16(a0a1_2), + vget_high_s16(a0a1_2)); + const int16x4_t tmp2 = vsub_s16(vget_low_s16(a0a1_2), + vget_high_s16(a0a1_2)); + const int32x4_t a3_2217 = vmull_n_s16(vget_low_s16(a3a2), 2217); + const int32x4_t a2_2217 = vmull_n_s16(vget_high_s16(a3a2), 2217); + const int32x4_t a2_p_a3 = vmlal_n_s16(a2_2217, vget_low_s16(a3a2), 5352); + const int32x4_t a3_m_a2 = vmlsl_n_s16(a3_2217, vget_high_s16(a3a2), 5352); + const int16x4_t tmp1 = vshrn_n_s32(vaddq_s32(a2_p_a3, kCst1812), 9); + const int16x4_t tmp3 = vshrn_n_s32(vaddq_s32(a3_m_a2, kCst937), 9); + Transpose4x4_S16_NEON(tmp0, tmp1, tmp2, tmp3, &d0d1, &d3d2); + } + { // 2nd pass + // the (1<<16) addition is for the replacement: a3!=0 <-> 1-(a3==0) + const int32x4_t kCst12000 = vdupq_n_s32(12000 + (1 << 16)); + const int32x4_t kCst51000 = vdupq_n_s32(51000); + const int16x8_t a0a1 = vaddq_s16(d0d1, d3d2); // d0+d3 | d1+d2 (=a0|a1) + const int16x8_t a3a2 = vsubq_s16(d0d1, d3d2); // d0-d3 | d1-d2 (=a3|a2) + const int16x4_t a0_k7 = vadd_s16(vget_low_s16(a0a1), vdup_n_s16(7)); + const int16x4_t out0 = vshr_n_s16(vadd_s16(a0_k7, vget_high_s16(a0a1)), 4); + const int16x4_t out2 = vshr_n_s16(vsub_s16(a0_k7, vget_high_s16(a0a1)), 4); + const int32x4_t a3_2217 = vmull_n_s16(vget_low_s16(a3a2), 2217); + const int32x4_t a2_2217 = vmull_n_s16(vget_high_s16(a3a2), 2217); + const int32x4_t a2_p_a3 = vmlal_n_s16(a2_2217, vget_low_s16(a3a2), 5352); + const int32x4_t a3_m_a2 = vmlsl_n_s16(a3_2217, vget_high_s16(a3a2), 5352); + const int16x4_t tmp1 = vaddhn_s32(a2_p_a3, kCst12000); + const int16x4_t out3 = vaddhn_s32(a3_m_a2, kCst51000); + const int16x4_t a3_eq_0 = + vreinterpret_s16_u16(vceq_s16(vget_low_s16(a3a2), vdup_n_s16(0))); + const int16x4_t out1 = vadd_s16(tmp1, a3_eq_0); + vst1_s16(out + 0, out0); + vst1_s16(out + 4, out1); + vst1_s16(out + 8, out2); + vst1_s16(out + 12, out3); + } +} + +#else + +// adapted from vp8/encoder/arm/neon/shortfdct_neon.asm +static const int16_t kCoeff16[] = { + 5352, 5352, 5352, 5352, 2217, 2217, 2217, 2217 +}; +static const int32_t kCoeff32[] = { + 1812, 1812, 1812, 1812, + 937, 937, 937, 937, + 12000, 12000, 12000, 12000, + 51000, 51000, 51000, 51000 +}; + +static void FTransform_NEON(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + const int kBPS = BPS; + const uint8_t* src_ptr = src; + const uint8_t* ref_ptr = ref; + const int16_t* coeff16 = kCoeff16; + const int32_t* coeff32 = kCoeff32; + + __asm__ volatile ( + // load src into q4, q5 in high half + "vld1.8 {d8}, [%[src_ptr]], %[kBPS] \n" + "vld1.8 {d10}, [%[src_ptr]], %[kBPS] \n" + "vld1.8 {d9}, [%[src_ptr]], %[kBPS] \n" + "vld1.8 {d11}, [%[src_ptr]] \n" + + // load ref into q6, q7 in high half + "vld1.8 {d12}, [%[ref_ptr]], %[kBPS] \n" + "vld1.8 {d14}, [%[ref_ptr]], %[kBPS] \n" + "vld1.8 {d13}, [%[ref_ptr]], %[kBPS] \n" + "vld1.8 {d15}, [%[ref_ptr]] \n" + + // Pack the high values in to q4 and q6 + "vtrn.32 q4, q5 \n" + "vtrn.32 q6, q7 \n" + + // d[0-3] = src - ref + "vsubl.u8 q0, d8, d12 \n" + "vsubl.u8 q1, d9, d13 \n" + + // load coeff16 into q8(d16=5352, d17=2217) + "vld1.16 {q8}, [%[coeff16]] \n" + + // load coeff32 high half into q9 = 1812, q10 = 937 + "vld1.32 {q9, q10}, [%[coeff32]]! \n" + + // load coeff32 low half into q11=12000, q12=51000 + "vld1.32 {q11,q12}, [%[coeff32]] \n" + + // part 1 + // Transpose. Register dN is the same as dN in C + "vtrn.32 d0, d2 \n" + "vtrn.32 d1, d3 \n" + "vtrn.16 d0, d1 \n" + "vtrn.16 d2, d3 \n" + + "vadd.s16 d4, d0, d3 \n" // a0 = d0 + d3 + "vadd.s16 d5, d1, d2 \n" // a1 = d1 + d2 + "vsub.s16 d6, d1, d2 \n" // a2 = d1 - d2 + "vsub.s16 d7, d0, d3 \n" // a3 = d0 - d3 + + "vadd.s16 d0, d4, d5 \n" // a0 + a1 + "vshl.s16 d0, d0, #3 \n" // temp[0+i*4] = (a0+a1) << 3 + "vsub.s16 d2, d4, d5 \n" // a0 - a1 + "vshl.s16 d2, d2, #3 \n" // (temp[2+i*4] = (a0-a1) << 3 + + "vmlal.s16 q9, d7, d16 \n" // a3*5352 + 1812 + "vmlal.s16 q10, d7, d17 \n" // a3*2217 + 937 + "vmlal.s16 q9, d6, d17 \n" // a2*2217 + a3*5352 + 1812 + "vmlsl.s16 q10, d6, d16 \n" // a3*2217 + 937 - a2*5352 + + // temp[1+i*4] = (d2*2217 + d3*5352 + 1812) >> 9 + // temp[3+i*4] = (d3*2217 + 937 - d2*5352) >> 9 + "vshrn.s32 d1, q9, #9 \n" + "vshrn.s32 d3, q10, #9 \n" + + // part 2 + // transpose d0=ip[0], d1=ip[4], d2=ip[8], d3=ip[12] + "vtrn.32 d0, d2 \n" + "vtrn.32 d1, d3 \n" + "vtrn.16 d0, d1 \n" + "vtrn.16 d2, d3 \n" + + "vmov.s16 d26, #7 \n" + + "vadd.s16 d4, d0, d3 \n" // a1 = ip[0] + ip[12] + "vadd.s16 d5, d1, d2 \n" // b1 = ip[4] + ip[8] + "vsub.s16 d6, d1, d2 \n" // c1 = ip[4] - ip[8] + "vadd.s16 d4, d4, d26 \n" // a1 + 7 + "vsub.s16 d7, d0, d3 \n" // d1 = ip[0] - ip[12] + + "vadd.s16 d0, d4, d5 \n" // op[0] = a1 + b1 + 7 + "vsub.s16 d2, d4, d5 \n" // op[8] = a1 - b1 + 7 + + "vmlal.s16 q11, d7, d16 \n" // d1*5352 + 12000 + "vmlal.s16 q12, d7, d17 \n" // d1*2217 + 51000 + + "vceq.s16 d4, d7, #0 \n" + + "vshr.s16 d0, d0, #4 \n" + "vshr.s16 d2, d2, #4 \n" + + "vmlal.s16 q11, d6, d17 \n" // c1*2217 + d1*5352 + 12000 + "vmlsl.s16 q12, d6, d16 \n" // d1*2217 - c1*5352 + 51000 + + "vmvn d4, d4 \n" // !(d1 == 0) + // op[4] = (c1*2217 + d1*5352 + 12000)>>16 + "vshrn.s32 d1, q11, #16 \n" + // op[4] += (d1!=0) + "vsub.s16 d1, d1, d4 \n" + // op[12]= (d1*2217 - c1*5352 + 51000)>>16 + "vshrn.s32 d3, q12, #16 \n" + + // set result to out array + "vst1.16 {q0, q1}, [%[out]] \n" + : [src_ptr] "+r"(src_ptr), [ref_ptr] "+r"(ref_ptr), + [coeff32] "+r"(coeff32) // modified registers + : [kBPS] "r"(kBPS), [coeff16] "r"(coeff16), + [out] "r"(out) // constants + : "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", + "q10", "q11", "q12", "q13" // clobbered + ); +} + +#endif + +#define LOAD_LANE_16b(VALUE, LANE) do { \ + (VALUE) = vld1_lane_s16(src, (VALUE), (LANE)); \ + src += stride; \ +} while (0) + +static void FTransformWHT_NEON(const int16_t* src, int16_t* out) { + const int stride = 16; + const int16x4_t zero = vdup_n_s16(0); + int32x4x4_t tmp0; + int16x4x4_t in; + INIT_VECTOR4(in, zero, zero, zero, zero); + LOAD_LANE_16b(in.val[0], 0); + LOAD_LANE_16b(in.val[1], 0); + LOAD_LANE_16b(in.val[2], 0); + LOAD_LANE_16b(in.val[3], 0); + LOAD_LANE_16b(in.val[0], 1); + LOAD_LANE_16b(in.val[1], 1); + LOAD_LANE_16b(in.val[2], 1); + LOAD_LANE_16b(in.val[3], 1); + LOAD_LANE_16b(in.val[0], 2); + LOAD_LANE_16b(in.val[1], 2); + LOAD_LANE_16b(in.val[2], 2); + LOAD_LANE_16b(in.val[3], 2); + LOAD_LANE_16b(in.val[0], 3); + LOAD_LANE_16b(in.val[1], 3); + LOAD_LANE_16b(in.val[2], 3); + LOAD_LANE_16b(in.val[3], 3); + + { + // a0 = in[0 * 16] + in[2 * 16] + // a1 = in[1 * 16] + in[3 * 16] + // a2 = in[1 * 16] - in[3 * 16] + // a3 = in[0 * 16] - in[2 * 16] + const int32x4_t a0 = vaddl_s16(in.val[0], in.val[2]); + const int32x4_t a1 = vaddl_s16(in.val[1], in.val[3]); + const int32x4_t a2 = vsubl_s16(in.val[1], in.val[3]); + const int32x4_t a3 = vsubl_s16(in.val[0], in.val[2]); + tmp0.val[0] = vaddq_s32(a0, a1); + tmp0.val[1] = vaddq_s32(a3, a2); + tmp0.val[2] = vsubq_s32(a3, a2); + tmp0.val[3] = vsubq_s32(a0, a1); + } + { + const int32x4x4_t tmp1 = Transpose4x4_NEON(tmp0); + // a0 = tmp[0 + i] + tmp[ 8 + i] + // a1 = tmp[4 + i] + tmp[12 + i] + // a2 = tmp[4 + i] - tmp[12 + i] + // a3 = tmp[0 + i] - tmp[ 8 + i] + const int32x4_t a0 = vaddq_s32(tmp1.val[0], tmp1.val[2]); + const int32x4_t a1 = vaddq_s32(tmp1.val[1], tmp1.val[3]); + const int32x4_t a2 = vsubq_s32(tmp1.val[1], tmp1.val[3]); + const int32x4_t a3 = vsubq_s32(tmp1.val[0], tmp1.val[2]); + const int32x4_t b0 = vhaddq_s32(a0, a1); // (a0 + a1) >> 1 + const int32x4_t b1 = vhaddq_s32(a3, a2); // (a3 + a2) >> 1 + const int32x4_t b2 = vhsubq_s32(a3, a2); // (a3 - a2) >> 1 + const int32x4_t b3 = vhsubq_s32(a0, a1); // (a0 - a1) >> 1 + const int16x4_t out0 = vmovn_s32(b0); + const int16x4_t out1 = vmovn_s32(b1); + const int16x4_t out2 = vmovn_s32(b2); + const int16x4_t out3 = vmovn_s32(b3); + + vst1_s16(out + 0, out0); + vst1_s16(out + 4, out1); + vst1_s16(out + 8, out2); + vst1_s16(out + 12, out3); + } +} +#undef LOAD_LANE_16b + +//------------------------------------------------------------------------------ +// Texture distortion +// +// We try to match the spectral content (weighted) between source and +// reconstructed samples. + +// a 0123, b 0123 +// a 4567, b 4567 +// a 89ab, b 89ab +// a cdef, b cdef +// +// transpose +// +// a 048c, b 048c +// a 159d, b 159d +// a 26ae, b 26ae +// a 37bf, b 37bf +// +static WEBP_INLINE int16x8x4_t DistoTranspose4x4S16_NEON(int16x8x4_t q4_in) { + const int16x8x2_t q2_tmp0 = vtrnq_s16(q4_in.val[0], q4_in.val[1]); + const int16x8x2_t q2_tmp1 = vtrnq_s16(q4_in.val[2], q4_in.val[3]); + const int32x4x2_t q2_tmp2 = vtrnq_s32(vreinterpretq_s32_s16(q2_tmp0.val[0]), + vreinterpretq_s32_s16(q2_tmp1.val[0])); + const int32x4x2_t q2_tmp3 = vtrnq_s32(vreinterpretq_s32_s16(q2_tmp0.val[1]), + vreinterpretq_s32_s16(q2_tmp1.val[1])); + q4_in.val[0] = vreinterpretq_s16_s32(q2_tmp2.val[0]); + q4_in.val[2] = vreinterpretq_s16_s32(q2_tmp2.val[1]); + q4_in.val[1] = vreinterpretq_s16_s32(q2_tmp3.val[0]); + q4_in.val[3] = vreinterpretq_s16_s32(q2_tmp3.val[1]); + return q4_in; +} + +static WEBP_INLINE int16x8x4_t DistoHorizontalPass_NEON( + const int16x8x4_t q4_in) { + // {a0, a1} = {in[0] + in[2], in[1] + in[3]} + // {a3, a2} = {in[0] - in[2], in[1] - in[3]} + const int16x8_t q_a0 = vaddq_s16(q4_in.val[0], q4_in.val[2]); + const int16x8_t q_a1 = vaddq_s16(q4_in.val[1], q4_in.val[3]); + const int16x8_t q_a3 = vsubq_s16(q4_in.val[0], q4_in.val[2]); + const int16x8_t q_a2 = vsubq_s16(q4_in.val[1], q4_in.val[3]); + int16x8x4_t q4_out; + // tmp[0] = a0 + a1 + // tmp[1] = a3 + a2 + // tmp[2] = a3 - a2 + // tmp[3] = a0 - a1 + INIT_VECTOR4(q4_out, + vabsq_s16(vaddq_s16(q_a0, q_a1)), + vabsq_s16(vaddq_s16(q_a3, q_a2)), + vabdq_s16(q_a3, q_a2), vabdq_s16(q_a0, q_a1)); + return q4_out; +} + +static WEBP_INLINE int16x8x4_t DistoVerticalPass_NEON(const uint8x8x4_t q4_in) { + const int16x8_t q_a0 = vreinterpretq_s16_u16(vaddl_u8(q4_in.val[0], + q4_in.val[2])); + const int16x8_t q_a1 = vreinterpretq_s16_u16(vaddl_u8(q4_in.val[1], + q4_in.val[3])); + const int16x8_t q_a2 = vreinterpretq_s16_u16(vsubl_u8(q4_in.val[1], + q4_in.val[3])); + const int16x8_t q_a3 = vreinterpretq_s16_u16(vsubl_u8(q4_in.val[0], + q4_in.val[2])); + int16x8x4_t q4_out; + + INIT_VECTOR4(q4_out, + vaddq_s16(q_a0, q_a1), vaddq_s16(q_a3, q_a2), + vsubq_s16(q_a3, q_a2), vsubq_s16(q_a0, q_a1)); + return q4_out; +} + +static WEBP_INLINE int16x4x4_t DistoLoadW_NEON(const uint16_t* w) { + const uint16x8_t q_w07 = vld1q_u16(&w[0]); + const uint16x8_t q_w8f = vld1q_u16(&w[8]); + int16x4x4_t d4_w; + INIT_VECTOR4(d4_w, + vget_low_s16(vreinterpretq_s16_u16(q_w07)), + vget_high_s16(vreinterpretq_s16_u16(q_w07)), + vget_low_s16(vreinterpretq_s16_u16(q_w8f)), + vget_high_s16(vreinterpretq_s16_u16(q_w8f))); + return d4_w; +} + +static WEBP_INLINE int32x2_t DistoSum_NEON(const int16x8x4_t q4_in, + const int16x4x4_t d4_w) { + int32x2_t d_sum; + // sum += w[ 0] * abs(b0); + // sum += w[ 4] * abs(b1); + // sum += w[ 8] * abs(b2); + // sum += w[12] * abs(b3); + int32x4_t q_sum0 = vmull_s16(d4_w.val[0], vget_low_s16(q4_in.val[0])); + int32x4_t q_sum1 = vmull_s16(d4_w.val[1], vget_low_s16(q4_in.val[1])); + int32x4_t q_sum2 = vmull_s16(d4_w.val[2], vget_low_s16(q4_in.val[2])); + int32x4_t q_sum3 = vmull_s16(d4_w.val[3], vget_low_s16(q4_in.val[3])); + q_sum0 = vmlsl_s16(q_sum0, d4_w.val[0], vget_high_s16(q4_in.val[0])); + q_sum1 = vmlsl_s16(q_sum1, d4_w.val[1], vget_high_s16(q4_in.val[1])); + q_sum2 = vmlsl_s16(q_sum2, d4_w.val[2], vget_high_s16(q4_in.val[2])); + q_sum3 = vmlsl_s16(q_sum3, d4_w.val[3], vget_high_s16(q4_in.val[3])); + + q_sum0 = vaddq_s32(q_sum0, q_sum1); + q_sum2 = vaddq_s32(q_sum2, q_sum3); + q_sum2 = vaddq_s32(q_sum0, q_sum2); + d_sum = vpadd_s32(vget_low_s32(q_sum2), vget_high_s32(q_sum2)); + d_sum = vpadd_s32(d_sum, d_sum); + return d_sum; +} + +#define LOAD_LANE_32b(src, VALUE, LANE) \ + (VALUE) = vld1_lane_u32((const uint32_t*)(src), (VALUE), (LANE)) + +// Hadamard transform +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int Disto4x4_NEON(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + uint32x2_t d_in_ab_0123 = vdup_n_u32(0); + uint32x2_t d_in_ab_4567 = vdup_n_u32(0); + uint32x2_t d_in_ab_89ab = vdup_n_u32(0); + uint32x2_t d_in_ab_cdef = vdup_n_u32(0); + uint8x8x4_t d4_in; + + // load data a, b + LOAD_LANE_32b(a + 0 * BPS, d_in_ab_0123, 0); + LOAD_LANE_32b(a + 1 * BPS, d_in_ab_4567, 0); + LOAD_LANE_32b(a + 2 * BPS, d_in_ab_89ab, 0); + LOAD_LANE_32b(a + 3 * BPS, d_in_ab_cdef, 0); + LOAD_LANE_32b(b + 0 * BPS, d_in_ab_0123, 1); + LOAD_LANE_32b(b + 1 * BPS, d_in_ab_4567, 1); + LOAD_LANE_32b(b + 2 * BPS, d_in_ab_89ab, 1); + LOAD_LANE_32b(b + 3 * BPS, d_in_ab_cdef, 1); + INIT_VECTOR4(d4_in, + vreinterpret_u8_u32(d_in_ab_0123), + vreinterpret_u8_u32(d_in_ab_4567), + vreinterpret_u8_u32(d_in_ab_89ab), + vreinterpret_u8_u32(d_in_ab_cdef)); + + { + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent + // transpose. + const int16x8x4_t q4_v = DistoVerticalPass_NEON(d4_in); + const int16x4x4_t d4_w = DistoLoadW_NEON(w); + // horizontal pass + const int16x8x4_t q4_t = DistoTranspose4x4S16_NEON(q4_v); + const int16x8x4_t q4_h = DistoHorizontalPass_NEON(q4_t); + int32x2_t d_sum = DistoSum_NEON(q4_h, d4_w); + + // abs(sum2 - sum1) >> 5 + d_sum = vabs_s32(d_sum); + d_sum = vshr_n_s32(d_sum, 5); + return vget_lane_s32(d_sum, 0); + } +} +#undef LOAD_LANE_32b + +static int Disto16x16_NEON(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_NEON(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ + +static void CollectHistogram_NEON(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const uint16x8_t max_coeff_thresh = vdupq_n_u16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + FTransform_NEON(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + { + int k; + const int16x8_t a0 = vld1q_s16(out + 0); + const int16x8_t b0 = vld1q_s16(out + 8); + const uint16x8_t a1 = vreinterpretq_u16_s16(vabsq_s16(a0)); + const uint16x8_t b1 = vreinterpretq_u16_s16(vabsq_s16(b0)); + const uint16x8_t a2 = vshrq_n_u16(a1, 3); + const uint16x8_t b2 = vshrq_n_u16(b1, 3); + const uint16x8_t a3 = vminq_u16(a2, max_coeff_thresh); + const uint16x8_t b3 = vminq_u16(b2, max_coeff_thresh); + vst1q_s16(out + 0, vreinterpretq_s16_u16(a3)); + vst1q_s16(out + 8, vreinterpretq_s16_u16(b3)); + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE void AccumulateSSE16_NEON(const uint8_t* const a, + const uint8_t* const b, + uint32x4_t* const sum) { + const uint8x16_t a0 = vld1q_u8(a); + const uint8x16_t b0 = vld1q_u8(b); + const uint8x16_t abs_diff = vabdq_u8(a0, b0); + const uint16x8_t prod1 = vmull_u8(vget_low_u8(abs_diff), + vget_low_u8(abs_diff)); + const uint16x8_t prod2 = vmull_u8(vget_high_u8(abs_diff), + vget_high_u8(abs_diff)); + /* pair-wise adds and widen */ + const uint32x4_t sum1 = vpaddlq_u16(prod1); + const uint32x4_t sum2 = vpaddlq_u16(prod2); + *sum = vaddq_u32(*sum, vaddq_u32(sum1, sum2)); +} + +// Horizontal sum of all four uint32_t values in 'sum'. +static int SumToInt_NEON(uint32x4_t sum) { +#if WEBP_AARCH64 + return (int)vaddvq_u32(sum); +#else + const uint64x2_t sum2 = vpaddlq_u32(sum); + const uint32x2_t sum3 = vadd_u32(vreinterpret_u32_u64(vget_low_u64(sum2)), + vreinterpret_u32_u64(vget_high_u64(sum2))); + return (int)vget_lane_u32(sum3, 0); +#endif +} + +static int SSE16x16_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 16; ++y) { + AccumulateSSE16_NEON(a + y * BPS, b + y * BPS, &sum); + } + return SumToInt_NEON(sum); +} + +static int SSE16x8_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 8; ++y) { + AccumulateSSE16_NEON(a + y * BPS, b + y * BPS, &sum); + } + return SumToInt_NEON(sum); +} + +static int SSE8x8_NEON(const uint8_t* a, const uint8_t* b) { + uint32x4_t sum = vdupq_n_u32(0); + int y; + for (y = 0; y < 8; ++y) { + const uint8x8_t a0 = vld1_u8(a + y * BPS); + const uint8x8_t b0 = vld1_u8(b + y * BPS); + const uint8x8_t abs_diff = vabd_u8(a0, b0); + const uint16x8_t prod = vmull_u8(abs_diff, abs_diff); + sum = vpadalq_u16(sum, prod); + } + return SumToInt_NEON(sum); +} + +static int SSE4x4_NEON(const uint8_t* a, const uint8_t* b) { + const uint8x16_t a0 = Load4x4_NEON(a); + const uint8x16_t b0 = Load4x4_NEON(b); + const uint8x16_t abs_diff = vabdq_u8(a0, b0); + const uint16x8_t prod1 = vmull_u8(vget_low_u8(abs_diff), + vget_low_u8(abs_diff)); + const uint16x8_t prod2 = vmull_u8(vget_high_u8(abs_diff), + vget_high_u8(abs_diff)); + /* pair-wise adds and widen */ + const uint32x4_t sum1 = vpaddlq_u16(prod1); + const uint32x4_t sum2 = vpaddlq_u16(prod2); + return SumToInt_NEON(vaddq_u32(sum1, sum2)); +} + +//------------------------------------------------------------------------------ + +// Compilation with gcc-4.6.x is problematic for now. +#if !defined(WORK_AROUND_GCC) + +static int16x8_t Quantize_NEON(int16_t* const in, + const VP8Matrix* const mtx, int offset) { + const uint16x8_t sharp = vld1q_u16(&mtx->sharpen_[offset]); + const uint16x8_t q = vld1q_u16(&mtx->q_[offset]); + const uint16x8_t iq = vld1q_u16(&mtx->iq_[offset]); + const uint32x4_t bias0 = vld1q_u32(&mtx->bias_[offset + 0]); + const uint32x4_t bias1 = vld1q_u32(&mtx->bias_[offset + 4]); + + const int16x8_t a = vld1q_s16(in + offset); // in + const uint16x8_t b = vreinterpretq_u16_s16(vabsq_s16(a)); // coeff = abs(in) + const int16x8_t sign = vshrq_n_s16(a, 15); // sign + const uint16x8_t c = vaddq_u16(b, sharp); // + sharpen + const uint32x4_t m0 = vmull_u16(vget_low_u16(c), vget_low_u16(iq)); + const uint32x4_t m1 = vmull_u16(vget_high_u16(c), vget_high_u16(iq)); + const uint32x4_t m2 = vhaddq_u32(m0, bias0); + const uint32x4_t m3 = vhaddq_u32(m1, bias1); // (coeff * iQ + bias) >> 1 + const uint16x8_t c0 = vcombine_u16(vshrn_n_u32(m2, 16), + vshrn_n_u32(m3, 16)); // QFIX=17 = 16+1 + const uint16x8_t c1 = vminq_u16(c0, vdupq_n_u16(MAX_LEVEL)); + const int16x8_t c2 = veorq_s16(vreinterpretq_s16_u16(c1), sign); + const int16x8_t c3 = vsubq_s16(c2, sign); // restore sign + const int16x8_t c4 = vmulq_s16(c3, vreinterpretq_s16_u16(q)); + vst1q_s16(in + offset, c4); + assert(QFIX == 17); // this function can't work as is if QFIX != 16+1 + return c3; +} + +static const uint8_t kShuffles[4][8] = { + { 0, 1, 2, 3, 8, 9, 16, 17 }, + { 10, 11, 4, 5, 6, 7, 12, 13 }, + { 18, 19, 24, 25, 26, 27, 20, 21 }, + { 14, 15, 22, 23, 28, 29, 30, 31 } +}; + +static int QuantizeBlock_NEON(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + const int16x8_t out0 = Quantize_NEON(in, mtx, 0); + const int16x8_t out1 = Quantize_NEON(in, mtx, 8); + uint8x8x4_t shuffles; + // vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use + // non-standard versions there. +#if defined(__APPLE__) && WEBP_AARCH64 && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) + uint8x16x2_t all_out; + INIT_VECTOR2(all_out, vreinterpretq_u8_s16(out0), vreinterpretq_u8_s16(out1)); + INIT_VECTOR4(shuffles, + vtbl2q_u8(all_out, vld1_u8(kShuffles[0])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[1])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[2])), + vtbl2q_u8(all_out, vld1_u8(kShuffles[3]))); +#else + uint8x8x4_t all_out; + INIT_VECTOR4(all_out, + vreinterpret_u8_s16(vget_low_s16(out0)), + vreinterpret_u8_s16(vget_high_s16(out0)), + vreinterpret_u8_s16(vget_low_s16(out1)), + vreinterpret_u8_s16(vget_high_s16(out1))); + INIT_VECTOR4(shuffles, + vtbl4_u8(all_out, vld1_u8(kShuffles[0])), + vtbl4_u8(all_out, vld1_u8(kShuffles[1])), + vtbl4_u8(all_out, vld1_u8(kShuffles[2])), + vtbl4_u8(all_out, vld1_u8(kShuffles[3]))); +#endif + // Zigzag reordering + vst1_u8((uint8_t*)(out + 0), shuffles.val[0]); + vst1_u8((uint8_t*)(out + 4), shuffles.val[1]); + vst1_u8((uint8_t*)(out + 8), shuffles.val[2]); + vst1_u8((uint8_t*)(out + 12), shuffles.val[3]); + // test zeros + if (*(uint64_t*)(out + 0) != 0) return 1; + if (*(uint64_t*)(out + 4) != 0) return 1; + if (*(uint64_t*)(out + 8) != 0) return 1; + if (*(uint64_t*)(out + 12) != 0) return 1; + return 0; +} + +static int Quantize2Blocks_NEON(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + nz = QuantizeBlock_NEON(in + 0 * 16, out + 0 * 16, mtx) << 0; + nz |= QuantizeBlock_NEON(in + 1 * 16, out + 1 * 16, mtx) << 1; + return nz; +} + +#endif // !WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitNEON(void) { + VP8ITransform = ITransform_NEON; + VP8FTransform = FTransform_NEON; + + VP8FTransformWHT = FTransformWHT_NEON; + + VP8TDisto4x4 = Disto4x4_NEON; + VP8TDisto16x16 = Disto16x16_NEON; + VP8CollectHistogram = CollectHistogram_NEON; + + VP8SSE16x16 = SSE16x16_NEON; + VP8SSE16x8 = SSE16x8_NEON; + VP8SSE8x8 = SSE8x8_NEON; + VP8SSE4x4 = SSE4x4_NEON; + +#if !defined(WORK_AROUND_GCC) + VP8EncQuantizeBlock = QuantizeBlock_NEON; + VP8EncQuantize2Blocks = Quantize2Blocks_NEON; +#endif +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8EncDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse2.c new file mode 100644 index 0000000000..010624a2f7 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse2.c @@ -0,0 +1,1514 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of speed-critical encoding functions. +// +// Author: Christian Duvivier (cduvivier@google.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include +#include // for abs() +#include + +#include "src/dsp/common_sse2.h" +#include "src/enc/cost_enc.h" +#include "src/enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +// Does one inverse transform. +static void ITransform_One_SSE2(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + const __m128i k1k2 = _mm_set_epi16(-30068, -30068, -30068, -30068, + 20091, 20091, 20091, 20091); + const __m128i k2k1 = _mm_set_epi16(20091, 20091, 20091, 20091, + -30068, -30068, -30068, -30068); + const __m128i zero = _mm_setzero_si128(); + const __m128i zero_four = _mm_set_epi16(0, 0, 0, 0, 4, 4, 4, 4); + __m128i T01, T23; + + // Load and concatenate the transform coefficients. + const __m128i in01 = _mm_loadu_si128((const __m128i*)&in[0]); + const __m128i in23 = _mm_loadu_si128((const __m128i*)&in[8]); + // a00 a10 a20 a30 a01 a11 a21 a31 + // a02 a12 a22 a32 a03 a13 a23 a33 + + // Vertical pass and subsequent transpose. + { + const __m128i in1 = _mm_unpackhi_epi64(in01, in01); + const __m128i in3 = _mm_unpackhi_epi64(in23, in23); + + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + const __m128i a_d3 = _mm_add_epi16(in01, in23); + const __m128i b_c3 = _mm_sub_epi16(in01, in23); + const __m128i c1d1 = _mm_mulhi_epi16(in1, k2k1); + const __m128i c2d2 = _mm_mulhi_epi16(in3, k1k2); + const __m128i c3 = _mm_unpackhi_epi64(b_c3, b_c3); + const __m128i c4 = _mm_sub_epi16(c1d1, c2d2); + const __m128i c = _mm_add_epi16(c3, c4); + const __m128i d4u = _mm_add_epi16(c1d1, c2d2); + const __m128i du = _mm_add_epi16(a_d3, d4u); + const __m128i d = _mm_unpackhi_epi64(du, du); + + // Second pass. + const __m128i comb_ab = _mm_unpacklo_epi64(a_d3, b_c3); + const __m128i comb_dc = _mm_unpacklo_epi64(d, c); + + const __m128i tmp01 = _mm_add_epi16(comb_ab, comb_dc); + const __m128i tmp32 = _mm_sub_epi16(comb_ab, comb_dc); + const __m128i tmp23 = _mm_shuffle_epi32(tmp32, _MM_SHUFFLE(1, 0, 3, 2)); + + const __m128i transpose_0 = _mm_unpacklo_epi16(tmp01, tmp23); + const __m128i transpose_1 = _mm_unpackhi_epi16(tmp01, tmp23); + // a00 a20 a01 a21 a02 a22 a03 a23 + // a10 a30 a11 a31 a12 a32 a13 a33 + + T01 = _mm_unpacklo_epi16(transpose_0, transpose_1); + T23 = _mm_unpackhi_epi16(transpose_0, transpose_1); + // a00 a10 a20 a30 a01 a11 a21 a31 + // a02 a12 a22 a32 a03 a13 a23 a33 + } + + // Horizontal pass and subsequent transpose. + { + const __m128i T1 = _mm_unpackhi_epi64(T01, T01); + const __m128i T3 = _mm_unpackhi_epi64(T23, T23); + + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i dc = _mm_add_epi16(T01, zero_four); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + const __m128i a_d3 = _mm_add_epi16(dc, T23); + const __m128i b_c3 = _mm_sub_epi16(dc, T23); + const __m128i c1d1 = _mm_mulhi_epi16(T1, k2k1); + const __m128i c2d2 = _mm_mulhi_epi16(T3, k1k2); + const __m128i c3 = _mm_unpackhi_epi64(b_c3, b_c3); + const __m128i c4 = _mm_sub_epi16(c1d1, c2d2); + const __m128i c = _mm_add_epi16(c3, c4); + const __m128i d4u = _mm_add_epi16(c1d1, c2d2); + const __m128i du = _mm_add_epi16(a_d3, d4u); + const __m128i d = _mm_unpackhi_epi64(du, du); + + // Second pass. + const __m128i comb_ab = _mm_unpacklo_epi64(a_d3, b_c3); + const __m128i comb_dc = _mm_unpacklo_epi64(d, c); + + const __m128i tmp01 = _mm_add_epi16(comb_ab, comb_dc); + const __m128i tmp32 = _mm_sub_epi16(comb_ab, comb_dc); + const __m128i tmp23 = _mm_shuffle_epi32(tmp32, _MM_SHUFFLE(1, 0, 3, 2)); + + const __m128i shifted01 = _mm_srai_epi16(tmp01, 3); + const __m128i shifted23 = _mm_srai_epi16(tmp23, 3); + // a00 a01 a02 a03 a10 a11 a12 a13 + // a20 a21 a22 a23 a30 a31 a32 a33 + + const __m128i transpose_0 = _mm_unpacklo_epi16(shifted01, shifted23); + const __m128i transpose_1 = _mm_unpackhi_epi16(shifted01, shifted23); + // a00 a20 a01 a21 a02 a22 a03 a23 + // a10 a30 a11 a31 a12 a32 a13 a33 + + T01 = _mm_unpacklo_epi16(transpose_0, transpose_1); + T23 = _mm_unpackhi_epi16(transpose_0, transpose_1); + // a00 a10 a20 a30 a01 a11 a21 a31 + // a02 a12 a22 a32 a03 a13 a23 a33 + } + + // Add inverse transform to 'ref' and store. + { + // Load the reference(s). + __m128i ref01, ref23, ref0123; + int32_t buf[4]; + + // Load four bytes/pixels per line. + const __m128i ref0 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[0 * BPS])); + const __m128i ref1 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[1 * BPS])); + const __m128i ref2 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[2 * BPS])); + const __m128i ref3 = _mm_cvtsi32_si128(WebPMemToInt32(&ref[3 * BPS])); + ref01 = _mm_unpacklo_epi32(ref0, ref1); + ref23 = _mm_unpacklo_epi32(ref2, ref3); + + // Convert to 16b. + ref01 = _mm_unpacklo_epi8(ref01, zero); + ref23 = _mm_unpacklo_epi8(ref23, zero); + // Add the inverse transform(s). + ref01 = _mm_add_epi16(ref01, T01); + ref23 = _mm_add_epi16(ref23, T23); + // Unsigned saturate to 8b. + ref0123 = _mm_packus_epi16(ref01, ref23); + + _mm_storeu_si128((__m128i *)buf, ref0123); + + // Store four bytes/pixels per line. + WebPInt32ToMem(&dst[0 * BPS], buf[0]); + WebPInt32ToMem(&dst[1 * BPS], buf[1]); + WebPInt32ToMem(&dst[2 * BPS], buf[2]); + WebPInt32ToMem(&dst[3 * BPS], buf[3]); + } +} + +// Does two inverse transforms. +static void ITransform_Two_SSE2(const uint8_t* ref, const int16_t* in, + uint8_t* dst) { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + const __m128i k1 = _mm_set1_epi16(20091); + const __m128i k2 = _mm_set1_epi16(-30068); + __m128i T0, T1, T2, T3; + + // Load and concatenate the transform coefficients (we'll do two inverse + // transforms in parallel). + __m128i in0, in1, in2, in3; + { + const __m128i tmp0 = _mm_loadu_si128((const __m128i*)&in[0]); + const __m128i tmp1 = _mm_loadu_si128((const __m128i*)&in[8]); + const __m128i tmp2 = _mm_loadu_si128((const __m128i*)&in[16]); + const __m128i tmp3 = _mm_loadu_si128((const __m128i*)&in[24]); + in0 = _mm_unpacklo_epi64(tmp0, tmp2); + in1 = _mm_unpackhi_epi64(tmp0, tmp2); + in2 = _mm_unpacklo_epi64(tmp1, tmp3); + in3 = _mm_unpackhi_epi64(tmp1, tmp3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + } + + // Vertical pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i a = _mm_add_epi16(in0, in2); + const __m128i b = _mm_sub_epi16(in0, in2); + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + const __m128i c1 = _mm_mulhi_epi16(in1, k2); + const __m128i c2 = _mm_mulhi_epi16(in3, k1); + const __m128i c3 = _mm_sub_epi16(in1, in3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + const __m128i d1 = _mm_mulhi_epi16(in1, k1); + const __m128i d2 = _mm_mulhi_epi16(in3, k2); + const __m128i d3 = _mm_add_epi16(in1, in3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&tmp0, &tmp1, &tmp2, &tmp3, &T0, &T1, &T2, &T3); + } + + // Horizontal pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i four = _mm_set1_epi16(4); + const __m128i dc = _mm_add_epi16(T0, four); + const __m128i a = _mm_add_epi16(dc, T2); + const __m128i b = _mm_sub_epi16(dc, T2); + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + const __m128i c1 = _mm_mulhi_epi16(T1, k2); + const __m128i c2 = _mm_mulhi_epi16(T3, k1); + const __m128i c3 = _mm_sub_epi16(T1, T3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + const __m128i d1 = _mm_mulhi_epi16(T1, k1); + const __m128i d2 = _mm_mulhi_epi16(T3, k2); + const __m128i d3 = _mm_add_epi16(T1, T3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + const __m128i shifted0 = _mm_srai_epi16(tmp0, 3); + const __m128i shifted1 = _mm_srai_epi16(tmp1, 3); + const __m128i shifted2 = _mm_srai_epi16(tmp2, 3); + const __m128i shifted3 = _mm_srai_epi16(tmp3, 3); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&shifted0, &shifted1, &shifted2, &shifted3, &T0, &T1, + &T2, &T3); + } + + // Add inverse transform to 'ref' and store. + { + const __m128i zero = _mm_setzero_si128(); + // Load the reference(s). + __m128i ref0, ref1, ref2, ref3; + // Load eight bytes/pixels per line. + ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); + // Convert to 16b. + ref0 = _mm_unpacklo_epi8(ref0, zero); + ref1 = _mm_unpacklo_epi8(ref1, zero); + ref2 = _mm_unpacklo_epi8(ref2, zero); + ref3 = _mm_unpacklo_epi8(ref3, zero); + // Add the inverse transform(s). + ref0 = _mm_add_epi16(ref0, T0); + ref1 = _mm_add_epi16(ref1, T1); + ref2 = _mm_add_epi16(ref2, T2); + ref3 = _mm_add_epi16(ref3, T3); + // Unsigned saturate to 8b. + ref0 = _mm_packus_epi16(ref0, ref0); + ref1 = _mm_packus_epi16(ref1, ref1); + ref2 = _mm_packus_epi16(ref2, ref2); + ref3 = _mm_packus_epi16(ref3, ref3); + // Store eight bytes/pixels per line. + _mm_storel_epi64((__m128i*)&dst[0 * BPS], ref0); + _mm_storel_epi64((__m128i*)&dst[1 * BPS], ref1); + _mm_storel_epi64((__m128i*)&dst[2 * BPS], ref2); + _mm_storel_epi64((__m128i*)&dst[3 * BPS], ref3); + } +} + +// Does one or two inverse transforms. +static void ITransform_SSE2(const uint8_t* ref, const int16_t* in, uint8_t* dst, + int do_two) { + if (do_two) { + ITransform_Two_SSE2(ref, in, dst); + } else { + ITransform_One_SSE2(ref, in, dst); + } +} + +static void FTransformPass1_SSE2(const __m128i* const in01, + const __m128i* const in23, + __m128i* const out01, + __m128i* const out32) { + const __m128i k937 = _mm_set1_epi32(937); + const __m128i k1812 = _mm_set1_epi32(1812); + + const __m128i k88p = _mm_set_epi16(8, 8, 8, 8, 8, 8, 8, 8); + const __m128i k88m = _mm_set_epi16(-8, 8, -8, 8, -8, 8, -8, 8); + const __m128i k5352_2217p = _mm_set_epi16(2217, 5352, 2217, 5352, + 2217, 5352, 2217, 5352); + const __m128i k5352_2217m = _mm_set_epi16(-5352, 2217, -5352, 2217, + -5352, 2217, -5352, 2217); + + // *in01 = 00 01 10 11 02 03 12 13 + // *in23 = 20 21 30 31 22 23 32 33 + const __m128i shuf01_p = _mm_shufflehi_epi16(*in01, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i shuf23_p = _mm_shufflehi_epi16(*in23, _MM_SHUFFLE(2, 3, 0, 1)); + // 00 01 10 11 03 02 13 12 + // 20 21 30 31 23 22 33 32 + const __m128i s01 = _mm_unpacklo_epi64(shuf01_p, shuf23_p); + const __m128i s32 = _mm_unpackhi_epi64(shuf01_p, shuf23_p); + // 00 01 10 11 20 21 30 31 + // 03 02 13 12 23 22 33 32 + const __m128i a01 = _mm_add_epi16(s01, s32); + const __m128i a32 = _mm_sub_epi16(s01, s32); + // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] + // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] + + const __m128i tmp0 = _mm_madd_epi16(a01, k88p); // [ (a0 + a1) << 3, ... ] + const __m128i tmp2 = _mm_madd_epi16(a01, k88m); // [ (a0 - a1) << 3, ... ] + const __m128i tmp1_1 = _mm_madd_epi16(a32, k5352_2217p); + const __m128i tmp3_1 = _mm_madd_epi16(a32, k5352_2217m); + const __m128i tmp1_2 = _mm_add_epi32(tmp1_1, k1812); + const __m128i tmp3_2 = _mm_add_epi32(tmp3_1, k937); + const __m128i tmp1 = _mm_srai_epi32(tmp1_2, 9); + const __m128i tmp3 = _mm_srai_epi32(tmp3_2, 9); + const __m128i s03 = _mm_packs_epi32(tmp0, tmp2); + const __m128i s12 = _mm_packs_epi32(tmp1, tmp3); + const __m128i s_lo = _mm_unpacklo_epi16(s03, s12); // 0 1 0 1 0 1... + const __m128i s_hi = _mm_unpackhi_epi16(s03, s12); // 2 3 2 3 2 3 + const __m128i v23 = _mm_unpackhi_epi32(s_lo, s_hi); + *out01 = _mm_unpacklo_epi32(s_lo, s_hi); + *out32 = _mm_shuffle_epi32(v23, _MM_SHUFFLE(1, 0, 3, 2)); // 3 2 3 2 3 2.. +} + +static void FTransformPass2_SSE2(const __m128i* const v01, + const __m128i* const v32, + int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i seven = _mm_set1_epi16(7); + const __m128i k5352_2217 = _mm_set_epi16(5352, 2217, 5352, 2217, + 5352, 2217, 5352, 2217); + const __m128i k2217_5352 = _mm_set_epi16(2217, -5352, 2217, -5352, + 2217, -5352, 2217, -5352); + const __m128i k12000_plus_one = _mm_set1_epi32(12000 + (1 << 16)); + const __m128i k51000 = _mm_set1_epi32(51000); + + // Same operations are done on the (0,3) and (1,2) pairs. + // a3 = v0 - v3 + // a2 = v1 - v2 + const __m128i a32 = _mm_sub_epi16(*v01, *v32); + const __m128i a22 = _mm_unpackhi_epi64(a32, a32); + + const __m128i b23 = _mm_unpacklo_epi16(a22, a32); + const __m128i c1 = _mm_madd_epi16(b23, k5352_2217); + const __m128i c3 = _mm_madd_epi16(b23, k2217_5352); + const __m128i d1 = _mm_add_epi32(c1, k12000_plus_one); + const __m128i d3 = _mm_add_epi32(c3, k51000); + const __m128i e1 = _mm_srai_epi32(d1, 16); + const __m128i e3 = _mm_srai_epi32(d3, 16); + // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) + // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) + const __m128i f1 = _mm_packs_epi32(e1, e1); + const __m128i f3 = _mm_packs_epi32(e3, e3); + // g1 = f1 + (a3 != 0); + // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the + // desired (0, 1), we add one earlier through k12000_plus_one. + // -> g1 = f1 + 1 - (a3 == 0) + const __m128i g1 = _mm_add_epi16(f1, _mm_cmpeq_epi16(a32, zero)); + + // a0 = v0 + v3 + // a1 = v1 + v2 + const __m128i a01 = _mm_add_epi16(*v01, *v32); + const __m128i a01_plus_7 = _mm_add_epi16(a01, seven); + const __m128i a11 = _mm_unpackhi_epi64(a01, a01); + const __m128i c0 = _mm_add_epi16(a01_plus_7, a11); + const __m128i c2 = _mm_sub_epi16(a01_plus_7, a11); + // d0 = (a0 + a1 + 7) >> 4; + // d2 = (a0 - a1 + 7) >> 4; + const __m128i d0 = _mm_srai_epi16(c0, 4); + const __m128i d2 = _mm_srai_epi16(c2, 4); + + const __m128i d0_g1 = _mm_unpacklo_epi64(d0, g1); + const __m128i d2_f3 = _mm_unpacklo_epi64(d2, f3); + _mm_storeu_si128((__m128i*)&out[0], d0_g1); + _mm_storeu_si128((__m128i*)&out[8], d2_f3); +} + +static void FTransform_SSE2(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + // Load src. + const __m128i src0 = _mm_loadl_epi64((const __m128i*)&src[0 * BPS]); + const __m128i src1 = _mm_loadl_epi64((const __m128i*)&src[1 * BPS]); + const __m128i src2 = _mm_loadl_epi64((const __m128i*)&src[2 * BPS]); + const __m128i src3 = _mm_loadl_epi64((const __m128i*)&src[3 * BPS]); + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + const __m128i src_0 = _mm_unpacklo_epi16(src0, src1); + const __m128i src_1 = _mm_unpacklo_epi16(src2, src3); + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Load ref. + const __m128i ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + const __m128i ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + const __m128i ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + const __m128i ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); + const __m128i ref_0 = _mm_unpacklo_epi16(ref0, ref1); + const __m128i ref_1 = _mm_unpacklo_epi16(ref2, ref3); + + // Convert both to 16 bit. + const __m128i src_0_16b = _mm_unpacklo_epi8(src_0, zero); + const __m128i src_1_16b = _mm_unpacklo_epi8(src_1, zero); + const __m128i ref_0_16b = _mm_unpacklo_epi8(ref_0, zero); + const __m128i ref_1_16b = _mm_unpacklo_epi8(ref_1, zero); + + // Compute the difference. + const __m128i row01 = _mm_sub_epi16(src_0_16b, ref_0_16b); + const __m128i row23 = _mm_sub_epi16(src_1_16b, ref_1_16b); + __m128i v01, v32; + + // First pass + FTransformPass1_SSE2(&row01, &row23, &v01, &v32); + + // Second pass + FTransformPass2_SSE2(&v01, &v32, out); +} + +static void FTransform2_SSE2(const uint8_t* src, const uint8_t* ref, + int16_t* out) { + const __m128i zero = _mm_setzero_si128(); + + // Load src and convert to 16b. + const __m128i src0 = _mm_loadl_epi64((const __m128i*)&src[0 * BPS]); + const __m128i src1 = _mm_loadl_epi64((const __m128i*)&src[1 * BPS]); + const __m128i src2 = _mm_loadl_epi64((const __m128i*)&src[2 * BPS]); + const __m128i src3 = _mm_loadl_epi64((const __m128i*)&src[3 * BPS]); + const __m128i src_0 = _mm_unpacklo_epi8(src0, zero); + const __m128i src_1 = _mm_unpacklo_epi8(src1, zero); + const __m128i src_2 = _mm_unpacklo_epi8(src2, zero); + const __m128i src_3 = _mm_unpacklo_epi8(src3, zero); + // Load ref and convert to 16b. + const __m128i ref0 = _mm_loadl_epi64((const __m128i*)&ref[0 * BPS]); + const __m128i ref1 = _mm_loadl_epi64((const __m128i*)&ref[1 * BPS]); + const __m128i ref2 = _mm_loadl_epi64((const __m128i*)&ref[2 * BPS]); + const __m128i ref3 = _mm_loadl_epi64((const __m128i*)&ref[3 * BPS]); + const __m128i ref_0 = _mm_unpacklo_epi8(ref0, zero); + const __m128i ref_1 = _mm_unpacklo_epi8(ref1, zero); + const __m128i ref_2 = _mm_unpacklo_epi8(ref2, zero); + const __m128i ref_3 = _mm_unpacklo_epi8(ref3, zero); + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + const __m128i diff0 = _mm_sub_epi16(src_0, ref_0); + const __m128i diff1 = _mm_sub_epi16(src_1, ref_1); + const __m128i diff2 = _mm_sub_epi16(src_2, ref_2); + const __m128i diff3 = _mm_sub_epi16(src_3, ref_3); + + // Unpack and shuffle + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + const __m128i shuf01l = _mm_unpacklo_epi32(diff0, diff1); + const __m128i shuf23l = _mm_unpacklo_epi32(diff2, diff3); + const __m128i shuf01h = _mm_unpackhi_epi32(diff0, diff1); + const __m128i shuf23h = _mm_unpackhi_epi32(diff2, diff3); + __m128i v01l, v32l; + __m128i v01h, v32h; + + // First pass + FTransformPass1_SSE2(&shuf01l, &shuf23l, &v01l, &v32l); + FTransformPass1_SSE2(&shuf01h, &shuf23h, &v01h, &v32h); + + // Second pass + FTransformPass2_SSE2(&v01l, &v32l, out + 0); + FTransformPass2_SSE2(&v01h, &v32h, out + 16); +} + +static void FTransformWHTRow_SSE2(const int16_t* const in, __m128i* const out) { + const __m128i kMult = _mm_set_epi16(-1, 1, -1, 1, 1, 1, 1, 1); + const __m128i src0 = _mm_loadl_epi64((__m128i*)&in[0 * 16]); + const __m128i src1 = _mm_loadl_epi64((__m128i*)&in[1 * 16]); + const __m128i src2 = _mm_loadl_epi64((__m128i*)&in[2 * 16]); + const __m128i src3 = _mm_loadl_epi64((__m128i*)&in[3 * 16]); + const __m128i A01 = _mm_unpacklo_epi16(src0, src1); // A0 A1 | ... + const __m128i A23 = _mm_unpacklo_epi16(src2, src3); // A2 A3 | ... + const __m128i B0 = _mm_adds_epi16(A01, A23); // a0 | a1 | ... + const __m128i B1 = _mm_subs_epi16(A01, A23); // a3 | a2 | ... + const __m128i C0 = _mm_unpacklo_epi32(B0, B1); // a0 | a1 | a3 | a2 | ... + const __m128i C1 = _mm_unpacklo_epi32(B1, B0); // a3 | a2 | a0 | a1 | ... + const __m128i D = _mm_unpacklo_epi64(C0, C1); // a0 a1 a3 a2 a3 a2 a0 a1 + *out = _mm_madd_epi16(D, kMult); +} + +static void FTransformWHT_SSE2(const int16_t* in, int16_t* out) { + // Input is 12b signed. + __m128i row0, row1, row2, row3; + // Rows are 14b signed. + FTransformWHTRow_SSE2(in + 0 * 64, &row0); + FTransformWHTRow_SSE2(in + 1 * 64, &row1); + FTransformWHTRow_SSE2(in + 2 * 64, &row2); + FTransformWHTRow_SSE2(in + 3 * 64, &row3); + + { + // The a* are 15b signed. + const __m128i a0 = _mm_add_epi32(row0, row2); + const __m128i a1 = _mm_add_epi32(row1, row3); + const __m128i a2 = _mm_sub_epi32(row1, row3); + const __m128i a3 = _mm_sub_epi32(row0, row2); + const __m128i a0a3 = _mm_packs_epi32(a0, a3); + const __m128i a1a2 = _mm_packs_epi32(a1, a2); + + // The b* are 16b signed. + const __m128i b0b1 = _mm_add_epi16(a0a3, a1a2); + const __m128i b3b2 = _mm_sub_epi16(a0a3, a1a2); + const __m128i tmp_b2b3 = _mm_unpackhi_epi64(b3b2, b3b2); + const __m128i b2b3 = _mm_unpacklo_epi64(tmp_b2b3, b3b2); + + _mm_storeu_si128((__m128i*)&out[0], _mm_srai_epi16(b0b1, 1)); + _mm_storeu_si128((__m128i*)&out[8], _mm_srai_epi16(b2b3, 1)); + } +} + +//------------------------------------------------------------------------------ +// Compute susceptibility based on DCT-coeff histograms: +// the higher, the "easier" the macroblock is to compress. + +static void CollectHistogram_SSE2(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const __m128i zero = _mm_setzero_si128(); + const __m128i max_coeff_thresh = _mm_set1_epi16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int k; + + FTransform_SSE2(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin (within out[]). + { + // Load. + const __m128i out0 = _mm_loadu_si128((__m128i*)&out[0]); + const __m128i out1 = _mm_loadu_si128((__m128i*)&out[8]); + const __m128i d0 = _mm_sub_epi16(zero, out0); + const __m128i d1 = _mm_sub_epi16(zero, out1); + const __m128i abs0 = _mm_max_epi16(out0, d0); // abs(v), 16b + const __m128i abs1 = _mm_max_epi16(out1, d1); + // v = abs(out) >> 3 + const __m128i v0 = _mm_srai_epi16(abs0, 3); + const __m128i v1 = _mm_srai_epi16(abs1, 3); + // bin = min(v, MAX_COEFF_THRESH) + const __m128i bin0 = _mm_min_epi16(v0, max_coeff_thresh); + const __m128i bin1 = _mm_min_epi16(v1, max_coeff_thresh); + // Store. + _mm_storeu_si128((__m128i*)&out[0], bin0); + _mm_storeu_si128((__m128i*)&out[8], bin1); + } + + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Intra predictions + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), values); + } +} + +static WEBP_INLINE void Put16_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 16; ++j) { + _mm_store_si128((__m128i*)(dst + j * BPS), values); + } +} + +static WEBP_INLINE void Fill_SSE2(uint8_t* dst, int value, int size) { + if (size == 4) { + int j; + for (j = 0; j < 4; ++j) { + memset(dst + j * BPS, value, 4); + } + } else if (size == 8) { + Put8x8uv_SSE2(value, dst); + } else { + Put16_SSE2(value, dst); + } +} + +static WEBP_INLINE void VE8uv_SSE2(uint8_t* dst, const uint8_t* top) { + int j; + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), top_values); + } +} + +static WEBP_INLINE void VE16_SSE2(uint8_t* dst, const uint8_t* top) { + const __m128i top_values = _mm_load_si128((const __m128i*)top); + int j; + for (j = 0; j < 16; ++j) { + _mm_store_si128((__m128i*)(dst + j * BPS), top_values); + } +} + +static WEBP_INLINE void VerticalPred_SSE2(uint8_t* dst, + const uint8_t* top, int size) { + if (top != NULL) { + if (size == 8) { + VE8uv_SSE2(dst, top); + } else { + VE16_SSE2(dst, top); + } + } else { + Fill_SSE2(dst, 127, size); + } +} + +static WEBP_INLINE void HE8uv_SSE2(uint8_t* dst, const uint8_t* left) { + int j; + for (j = 0; j < 8; ++j) { + const __m128i values = _mm_set1_epi8((char)left[j]); + _mm_storel_epi64((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void HE16_SSE2(uint8_t* dst, const uint8_t* left) { + int j; + for (j = 0; j < 16; ++j) { + const __m128i values = _mm_set1_epi8((char)left[j]); + _mm_store_si128((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void HorizontalPred_SSE2(uint8_t* dst, + const uint8_t* left, int size) { + if (left != NULL) { + if (size == 8) { + HE8uv_SSE2(dst, left); + } else { + HE16_SSE2(dst, left); + } + } else { + Fill_SSE2(dst, 129, size); + } +} + +static WEBP_INLINE void TM_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top, int size) { + const __m128i zero = _mm_setzero_si128(); + int y; + if (size == 8) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 8; ++y, dst += BPS) { + const int val = left[y] - left[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + _mm_storel_epi64((__m128i*)dst, out); + } + } else { + const __m128i top_values = _mm_load_si128((const __m128i*)top); + const __m128i top_base_0 = _mm_unpacklo_epi8(top_values, zero); + const __m128i top_base_1 = _mm_unpackhi_epi8(top_values, zero); + for (y = 0; y < 16; ++y, dst += BPS) { + const int val = left[y] - left[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out_0 = _mm_add_epi16(base, top_base_0); + const __m128i out_1 = _mm_add_epi16(base, top_base_1); + const __m128i out = _mm_packus_epi16(out_0, out_1); + _mm_store_si128((__m128i*)dst, out); + } + } +} + +static WEBP_INLINE void TrueMotion_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top, int size) { + if (left != NULL) { + if (top != NULL) { + TM_SSE2(dst, left, top, size); + } else { + HorizontalPred_SSE2(dst, left, size); + } + } else { + // true motion without left samples (hence: with default 129 value) + // is equivalent to VE prediction where you just copy the top samples. + // Note that if top samples are not available, the default value is + // then 129, and not 127 as in the VerticalPred case. + if (top != NULL) { + VerticalPred_SSE2(dst, top, size); + } else { + Fill_SSE2(dst, 129, size); + } + } +} + +static WEBP_INLINE void DC8uv_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i left_values = _mm_loadl_epi64((const __m128i*)left); + const __m128i combined = _mm_unpacklo_epi64(top_values, left_values); + const int DC = VP8HorizontalAdd8b(&combined) + 8; + Put8x8uv_SSE2(DC >> 4, dst); +} + +static WEBP_INLINE void DC8uvNoLeft_SSE2(uint8_t* dst, const uint8_t* top) { + const __m128i zero = _mm_setzero_si128(); + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i sum = _mm_sad_epu8(top_values, zero); + const int DC = _mm_cvtsi128_si32(sum) + 4; + Put8x8uv_SSE2(DC >> 3, dst); +} + +static WEBP_INLINE void DC8uvNoTop_SSE2(uint8_t* dst, const uint8_t* left) { + // 'left' is contiguous so we can reuse the top summation. + DC8uvNoLeft_SSE2(dst, left); +} + +static WEBP_INLINE void DC8uvNoTopLeft_SSE2(uint8_t* dst) { + Put8x8uv_SSE2(0x80, dst); +} + +static WEBP_INLINE void DC8uvMode_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (top != NULL) { + if (left != NULL) { // top and left present + DC8uv_SSE2(dst, left, top); + } else { // top, but no left + DC8uvNoLeft_SSE2(dst, top); + } + } else if (left != NULL) { // left but no top + DC8uvNoTop_SSE2(dst, left); + } else { // no top, no left, nothing. + DC8uvNoTopLeft_SSE2(dst); + } +} + +static WEBP_INLINE void DC16_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + const __m128i top_row = _mm_load_si128((const __m128i*)top); + const __m128i left_row = _mm_load_si128((const __m128i*)left); + const int DC = + VP8HorizontalAdd8b(&top_row) + VP8HorizontalAdd8b(&left_row) + 16; + Put16_SSE2(DC >> 5, dst); +} + +static WEBP_INLINE void DC16NoLeft_SSE2(uint8_t* dst, const uint8_t* top) { + const __m128i top_row = _mm_load_si128((const __m128i*)top); + const int DC = VP8HorizontalAdd8b(&top_row) + 8; + Put16_SSE2(DC >> 4, dst); +} + +static WEBP_INLINE void DC16NoTop_SSE2(uint8_t* dst, const uint8_t* left) { + // 'left' is contiguous so we can reuse the top summation. + DC16NoLeft_SSE2(dst, left); +} + +static WEBP_INLINE void DC16NoTopLeft_SSE2(uint8_t* dst) { + Put16_SSE2(0x80, dst); +} + +static WEBP_INLINE void DC16Mode_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + if (top != NULL) { + if (left != NULL) { // top and left present + DC16_SSE2(dst, left, top); + } else { // top, but no left + DC16NoLeft_SSE2(dst, top); + } + } else if (left != NULL) { // left but no top + DC16NoTop_SSE2(dst, left); + } else { // no top, no left, nothing. + DC16NoTopLeft_SSE2(dst); + } +} + +//------------------------------------------------------------------------------ +// 4x4 predictions + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +// We use the following 8b-arithmetic tricks: +// (a + 2 * b + c + 2) >> 2 = (AC + b + 1) >> 1 +// where: AC = (a + c) >> 1 = [(a + c + 1) >> 1] - [(a^c) & 1] +// and: +// (a + 2 * b + c + 2) >> 2 = (AB + BC + 1) >> 1 - (ab|bc)&lsb +// where: AC = (a + b + 1) >> 1, BC = (b + c + 1) >> 1 +// and ab = a ^ b, bc = b ^ c, lsb = (AC^BC)&1 + +static WEBP_INLINE void VE4_SSE2(uint8_t* dst, + const uint8_t* top) { // vertical + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(top - 1)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i a = _mm_avg_epu8(ABCDEFGH, CDEFGH00); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); + const __m128i b = _mm_subs_epu8(a, lsb); + const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); + const int vals = _mm_cvtsi128_si32(avg); + int i; + for (i = 0; i < 4; ++i) { + WebPInt32ToMem(dst + i * BPS, vals); + } +} + +static WEBP_INLINE void HE4_SSE2(uint8_t* dst, + const uint8_t* top) { // horizontal + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(X, I, J)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(I, J, K)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(J, K, L)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(K, L, L)); +} + +static WEBP_INLINE void DC4_SSE2(uint8_t* dst, const uint8_t* top) { + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += top[i] + top[-5 + i]; + Fill_SSE2(dst, dc >> 3, 4); +} + +static WEBP_INLINE void LD4_SSE2(uint8_t* dst, + const uint8_t* top) { // Down-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((const __m128i*)top); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i CDEFGHH0 = _mm_insert_epi16(CDEFGH00, top[7], 3); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, CDEFGHH0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static WEBP_INLINE void VR4_SSE2(uint8_t* dst, + const uint8_t* top) { // Vertical-Right + const __m128i one = _mm_set1_epi8(1); + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int X = top[-1]; + const __m128i XABCD = _mm_loadl_epi64((const __m128i*)(top - 1)); + const __m128i ABCD0 = _mm_srli_si128(XABCD, 1); + const __m128i abcd = _mm_avg_epu8(XABCD, ABCD0); + const __m128i _XABCD = _mm_slli_si128(XABCD, 1); + const __m128i IXABCD = _mm_insert_epi16(_XABCD, (short)(I | (X << 8)), 0); + const __m128i avg1 = _mm_avg_epu8(IXABCD, ABCD0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i efgh = _mm_avg_epu8(avg2, XABCD); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + + // these two are hard to implement in SSE2, so we keep the C-version: + DST(0, 2) = AVG3(J, I, X); + DST(0, 3) = AVG3(K, J, I); +} + +static WEBP_INLINE void VL4_SSE2(uint8_t* dst, + const uint8_t* top) { // Vertical-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((const __m128i*)top); + const __m128i BCDEFGH_ = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH__ = _mm_srli_si128(ABCDEFGH, 2); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, BCDEFGH_); + const __m128i avg2 = _mm_avg_epu8(CDEFGH__, BCDEFGH_); + const __m128i avg3 = _mm_avg_epu8(avg1, avg2); + const __m128i lsb1 = _mm_and_si128(_mm_xor_si128(avg1, avg2), one); + const __m128i ab = _mm_xor_si128(ABCDEFGH, BCDEFGH_); + const __m128i bc = _mm_xor_si128(CDEFGH__, BCDEFGH_); + const __m128i abbc = _mm_or_si128(ab, bc); + const __m128i lsb2 = _mm_and_si128(abbc, lsb1); + const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); + const uint32_t extra_out = + (uint32_t)_mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + + // these two are hard to get and irregular + DST(3, 2) = (extra_out >> 0) & 0xff; + DST(3, 3) = (extra_out >> 8) & 0xff; +} + +static WEBP_INLINE void RD4_SSE2(uint8_t* dst, + const uint8_t* top) { // Down-right + const __m128i one = _mm_set1_epi8(1); + const __m128i LKJIXABC = _mm_loadl_epi64((const __m128i*)(top - 5)); + const __m128i LKJIXABCD = _mm_insert_epi16(LKJIXABC, top[3], 4); + const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); + const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); + const __m128i avg1 = _mm_avg_epu8(JIXABCD__, LKJIXABCD); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static WEBP_INLINE void HU4_SSE2(uint8_t* dst, const uint8_t* top) { + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static WEBP_INLINE void HD4_SSE2(uint8_t* dst, const uint8_t* top) { + const int X = top[-1]; + const int I = top[-2]; + const int J = top[-3]; + const int K = top[-4]; + const int L = top[-5]; + const int A = top[0]; + const int B = top[1]; + const int C = top[2]; + + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +static WEBP_INLINE void TM4_SSE2(uint8_t* dst, const uint8_t* top) { + const __m128i zero = _mm_setzero_si128(); + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToInt32(top)); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + int y; + for (y = 0; y < 4; ++y, dst += BPS) { + const int val = top[-2 - y] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(out)); + } +} + +#undef DST +#undef AVG3 +#undef AVG2 + +//------------------------------------------------------------------------------ +// luma 4x4 prediction + +// Left samples are top[-5 .. -2], top_left is top[-1], top are +// located at top[0..3], and top right is top[4..7] +static void Intra4Preds_SSE2(uint8_t* dst, const uint8_t* top) { + DC4_SSE2(I4DC4 + dst, top); + TM4_SSE2(I4TM4 + dst, top); + VE4_SSE2(I4VE4 + dst, top); + HE4_SSE2(I4HE4 + dst, top); + RD4_SSE2(I4RD4 + dst, top); + VR4_SSE2(I4VR4 + dst, top); + LD4_SSE2(I4LD4 + dst, top); + VL4_SSE2(I4VL4 + dst, top); + HD4_SSE2(I4HD4 + dst, top); + HU4_SSE2(I4HU4 + dst, top); +} + +//------------------------------------------------------------------------------ +// Chroma 8x8 prediction (paragraph 12.2) + +static void IntraChromaPreds_SSE2(uint8_t* dst, const uint8_t* left, + const uint8_t* top) { + // U block + DC8uvMode_SSE2(C8DC8 + dst, left, top); + VerticalPred_SSE2(C8VE8 + dst, top, 8); + HorizontalPred_SSE2(C8HE8 + dst, left, 8); + TrueMotion_SSE2(C8TM8 + dst, left, top, 8); + // V block + dst += 8; + if (top != NULL) top += 8; + if (left != NULL) left += 16; + DC8uvMode_SSE2(C8DC8 + dst, left, top); + VerticalPred_SSE2(C8VE8 + dst, top, 8); + HorizontalPred_SSE2(C8HE8 + dst, left, 8); + TrueMotion_SSE2(C8TM8 + dst, left, top, 8); +} + +//------------------------------------------------------------------------------ +// luma 16x16 prediction (paragraph 12.3) + +static void Intra16Preds_SSE2(uint8_t* dst, + const uint8_t* left, const uint8_t* top) { + DC16Mode_SSE2(I16DC16 + dst, left, top); + VerticalPred_SSE2(I16VE16 + dst, top, 16); + HorizontalPred_SSE2(I16HE16 + dst, left, 16); + TrueMotion_SSE2(I16TM16 + dst, left, top, 16); +} + +//------------------------------------------------------------------------------ +// Metric + +static WEBP_INLINE void SubtractAndAccumulate_SSE2(const __m128i a, + const __m128i b, + __m128i* const sum) { + // take abs(a-b) in 8b + const __m128i a_b = _mm_subs_epu8(a, b); + const __m128i b_a = _mm_subs_epu8(b, a); + const __m128i abs_a_b = _mm_or_si128(a_b, b_a); + // zero-extend to 16b + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(abs_a_b, zero); + const __m128i C1 = _mm_unpackhi_epi8(abs_a_b, zero); + // multiply with self + const __m128i sum1 = _mm_madd_epi16(C0, C0); + const __m128i sum2 = _mm_madd_epi16(C1, C1); + *sum = _mm_add_epi32(sum1, sum2); +} + +static WEBP_INLINE int SSE_16xN_SSE2(const uint8_t* a, const uint8_t* b, + int num_pairs) { + __m128i sum = _mm_setzero_si128(); + int32_t tmp[4]; + int i; + + for (i = 0; i < num_pairs; ++i) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[BPS * 0]); + const __m128i b0 = _mm_loadu_si128((const __m128i*)&b[BPS * 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[BPS * 1]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&b[BPS * 1]); + __m128i sum1, sum2; + SubtractAndAccumulate_SSE2(a0, b0, &sum1); + SubtractAndAccumulate_SSE2(a1, b1, &sum2); + sum = _mm_add_epi32(sum, _mm_add_epi32(sum1, sum2)); + a += 2 * BPS; + b += 2 * BPS; + } + _mm_storeu_si128((__m128i*)tmp, sum); + return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); +} + +static int SSE16x16_SSE2(const uint8_t* a, const uint8_t* b) { + return SSE_16xN_SSE2(a, b, 8); +} + +static int SSE16x8_SSE2(const uint8_t* a, const uint8_t* b) { + return SSE_16xN_SSE2(a, b, 4); +} + +#define LOAD_8x16b(ptr) \ + _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)(ptr)), zero) + +static int SSE8x8_SSE2(const uint8_t* a, const uint8_t* b) { + const __m128i zero = _mm_setzero_si128(); + int num_pairs = 4; + __m128i sum = zero; + int32_t tmp[4]; + while (num_pairs-- > 0) { + const __m128i a0 = LOAD_8x16b(&a[BPS * 0]); + const __m128i a1 = LOAD_8x16b(&a[BPS * 1]); + const __m128i b0 = LOAD_8x16b(&b[BPS * 0]); + const __m128i b1 = LOAD_8x16b(&b[BPS * 1]); + // subtract + const __m128i c0 = _mm_subs_epi16(a0, b0); + const __m128i c1 = _mm_subs_epi16(a1, b1); + // multiply/accumulate with self + const __m128i d0 = _mm_madd_epi16(c0, c0); + const __m128i d1 = _mm_madd_epi16(c1, c1); + // collect + const __m128i sum01 = _mm_add_epi32(d0, d1); + sum = _mm_add_epi32(sum, sum01); + a += 2 * BPS; + b += 2 * BPS; + } + _mm_storeu_si128((__m128i*)tmp, sum); + return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); +} +#undef LOAD_8x16b + +static int SSE4x4_SSE2(const uint8_t* a, const uint8_t* b) { + const __m128i zero = _mm_setzero_si128(); + + // Load values. Note that we read 8 pixels instead of 4, + // but the a/b buffers are over-allocated to that effect. + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&a[BPS * 0]); + const __m128i a1 = _mm_loadl_epi64((const __m128i*)&a[BPS * 1]); + const __m128i a2 = _mm_loadl_epi64((const __m128i*)&a[BPS * 2]); + const __m128i a3 = _mm_loadl_epi64((const __m128i*)&a[BPS * 3]); + const __m128i b0 = _mm_loadl_epi64((const __m128i*)&b[BPS * 0]); + const __m128i b1 = _mm_loadl_epi64((const __m128i*)&b[BPS * 1]); + const __m128i b2 = _mm_loadl_epi64((const __m128i*)&b[BPS * 2]); + const __m128i b3 = _mm_loadl_epi64((const __m128i*)&b[BPS * 3]); + // Combine pair of lines. + const __m128i a01 = _mm_unpacklo_epi32(a0, a1); + const __m128i a23 = _mm_unpacklo_epi32(a2, a3); + const __m128i b01 = _mm_unpacklo_epi32(b0, b1); + const __m128i b23 = _mm_unpacklo_epi32(b2, b3); + // Convert to 16b. + const __m128i a01s = _mm_unpacklo_epi8(a01, zero); + const __m128i a23s = _mm_unpacklo_epi8(a23, zero); + const __m128i b01s = _mm_unpacklo_epi8(b01, zero); + const __m128i b23s = _mm_unpacklo_epi8(b23, zero); + // subtract, square and accumulate + const __m128i d0 = _mm_subs_epi16(a01s, b01s); + const __m128i d1 = _mm_subs_epi16(a23s, b23s); + const __m128i e0 = _mm_madd_epi16(d0, d0); + const __m128i e1 = _mm_madd_epi16(d1, d1); + const __m128i sum = _mm_add_epi32(e0, e1); + + int32_t tmp[4]; + _mm_storeu_si128((__m128i*)tmp, sum); + return (tmp[3] + tmp[2] + tmp[1] + tmp[0]); +} + +//------------------------------------------------------------------------------ + +static void Mean16x4_SSE2(const uint8_t* ref, uint32_t dc[4]) { + const __m128i mask = _mm_set1_epi16(0x00ff); + const __m128i a0 = _mm_loadu_si128((const __m128i*)&ref[BPS * 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&ref[BPS * 1]); + const __m128i a2 = _mm_loadu_si128((const __m128i*)&ref[BPS * 2]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&ref[BPS * 3]); + const __m128i b0 = _mm_srli_epi16(a0, 8); // hi byte + const __m128i b1 = _mm_srli_epi16(a1, 8); + const __m128i b2 = _mm_srli_epi16(a2, 8); + const __m128i b3 = _mm_srli_epi16(a3, 8); + const __m128i c0 = _mm_and_si128(a0, mask); // lo byte + const __m128i c1 = _mm_and_si128(a1, mask); + const __m128i c2 = _mm_and_si128(a2, mask); + const __m128i c3 = _mm_and_si128(a3, mask); + const __m128i d0 = _mm_add_epi32(b0, c0); + const __m128i d1 = _mm_add_epi32(b1, c1); + const __m128i d2 = _mm_add_epi32(b2, c2); + const __m128i d3 = _mm_add_epi32(b3, c3); + const __m128i e0 = _mm_add_epi32(d0, d1); + const __m128i e1 = _mm_add_epi32(d2, d3); + const __m128i f0 = _mm_add_epi32(e0, e1); + uint16_t tmp[8]; + _mm_storeu_si128((__m128i*)tmp, f0); + dc[0] = tmp[0] + tmp[1]; + dc[1] = tmp[2] + tmp[3]; + dc[2] = tmp[4] + tmp[5]; + dc[3] = tmp[6] + tmp[7]; +} + +//------------------------------------------------------------------------------ +// Texture distortion +// +// We try to match the spectral content (weighted) between source and +// reconstructed samples. + +// Hadamard transform +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int TTransform_SSE2(const uint8_t* inA, const uint8_t* inB, + const uint16_t* const w) { + int32_t sum[4]; + __m128i tmp_0, tmp_1, tmp_2, tmp_3; + const __m128i zero = _mm_setzero_si128(); + + // Load and combine inputs. + { + const __m128i inA_0 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 0]); + const __m128i inA_1 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 1]); + const __m128i inA_2 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 2]); + const __m128i inA_3 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 3]); + const __m128i inB_0 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 0]); + const __m128i inB_1 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 1]); + const __m128i inB_2 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 2]); + const __m128i inB_3 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 3]); + + // Combine inA and inB (we'll do two transforms in parallel). + const __m128i inAB_0 = _mm_unpacklo_epi32(inA_0, inB_0); + const __m128i inAB_1 = _mm_unpacklo_epi32(inA_1, inB_1); + const __m128i inAB_2 = _mm_unpacklo_epi32(inA_2, inB_2); + const __m128i inAB_3 = _mm_unpacklo_epi32(inA_3, inB_3); + tmp_0 = _mm_unpacklo_epi8(inAB_0, zero); + tmp_1 = _mm_unpacklo_epi8(inAB_1, zero); + tmp_2 = _mm_unpacklo_epi8(inAB_2, zero); + tmp_3 = _mm_unpacklo_epi8(inAB_3, zero); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + } + + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent transpose. + { + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&b0, &b1, &b2, &b3, &tmp_0, &tmp_1, &tmp_2, &tmp_3); + } + + // Horizontal pass and difference of weighted sums. + { + // Load all inputs. + const __m128i w_0 = _mm_loadu_si128((const __m128i*)&w[0]); + const __m128i w_8 = _mm_loadu_si128((const __m128i*)&w[8]); + + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + + // Separate the transforms of inA and inB. + __m128i A_b0 = _mm_unpacklo_epi64(b0, b1); + __m128i A_b2 = _mm_unpacklo_epi64(b2, b3); + __m128i B_b0 = _mm_unpackhi_epi64(b0, b1); + __m128i B_b2 = _mm_unpackhi_epi64(b2, b3); + + { + const __m128i d0 = _mm_sub_epi16(zero, A_b0); + const __m128i d1 = _mm_sub_epi16(zero, A_b2); + const __m128i d2 = _mm_sub_epi16(zero, B_b0); + const __m128i d3 = _mm_sub_epi16(zero, B_b2); + A_b0 = _mm_max_epi16(A_b0, d0); // abs(v), 16b + A_b2 = _mm_max_epi16(A_b2, d1); + B_b0 = _mm_max_epi16(B_b0, d2); + B_b2 = _mm_max_epi16(B_b2, d3); + } + + // weighted sums + A_b0 = _mm_madd_epi16(A_b0, w_0); + A_b2 = _mm_madd_epi16(A_b2, w_8); + B_b0 = _mm_madd_epi16(B_b0, w_0); + B_b2 = _mm_madd_epi16(B_b2, w_8); + A_b0 = _mm_add_epi32(A_b0, A_b2); + B_b0 = _mm_add_epi32(B_b0, B_b2); + + // difference of weighted sums + A_b0 = _mm_sub_epi32(A_b0, B_b0); + _mm_storeu_si128((__m128i*)&sum[0], A_b0); + } + return sum[0] + sum[1] + sum[2] + sum[3]; +} + +static int Disto4x4_SSE2(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int diff_sum = TTransform_SSE2(a, b, w); + return abs(diff_sum) >> 5; +} + +static int Disto16x16_SSE2(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_SSE2(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Quantization +// + +static WEBP_INLINE int DoQuantizeBlock_SSE2(int16_t in[16], int16_t out[16], + const uint16_t* const sharpen, + const VP8Matrix* const mtx) { + const __m128i max_coeff_2047 = _mm_set1_epi16(MAX_LEVEL); + const __m128i zero = _mm_setzero_si128(); + __m128i coeff0, coeff8; + __m128i out0, out8; + __m128i packed_out; + + // Load all inputs. + __m128i in0 = _mm_loadu_si128((__m128i*)&in[0]); + __m128i in8 = _mm_loadu_si128((__m128i*)&in[8]); + const __m128i iq0 = _mm_loadu_si128((const __m128i*)&mtx->iq_[0]); + const __m128i iq8 = _mm_loadu_si128((const __m128i*)&mtx->iq_[8]); + const __m128i q0 = _mm_loadu_si128((const __m128i*)&mtx->q_[0]); + const __m128i q8 = _mm_loadu_si128((const __m128i*)&mtx->q_[8]); + + // extract sign(in) (0x0000 if positive, 0xffff if negative) + const __m128i sign0 = _mm_cmpgt_epi16(zero, in0); + const __m128i sign8 = _mm_cmpgt_epi16(zero, in8); + + // coeff = abs(in) = (in ^ sign) - sign + coeff0 = _mm_xor_si128(in0, sign0); + coeff8 = _mm_xor_si128(in8, sign8); + coeff0 = _mm_sub_epi16(coeff0, sign0); + coeff8 = _mm_sub_epi16(coeff8, sign8); + + // coeff = abs(in) + sharpen + if (sharpen != NULL) { + const __m128i sharpen0 = _mm_loadu_si128((const __m128i*)&sharpen[0]); + const __m128i sharpen8 = _mm_loadu_si128((const __m128i*)&sharpen[8]); + coeff0 = _mm_add_epi16(coeff0, sharpen0); + coeff8 = _mm_add_epi16(coeff8, sharpen8); + } + + // out = (coeff * iQ + B) >> QFIX + { + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + const __m128i coeff_iQ0H = _mm_mulhi_epu16(coeff0, iq0); + const __m128i coeff_iQ0L = _mm_mullo_epi16(coeff0, iq0); + const __m128i coeff_iQ8H = _mm_mulhi_epu16(coeff8, iq8); + const __m128i coeff_iQ8L = _mm_mullo_epi16(coeff8, iq8); + __m128i out_00 = _mm_unpacklo_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_04 = _mm_unpackhi_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_08 = _mm_unpacklo_epi16(coeff_iQ8L, coeff_iQ8H); + __m128i out_12 = _mm_unpackhi_epi16(coeff_iQ8L, coeff_iQ8H); + // out = (coeff * iQ + B) + const __m128i bias_00 = _mm_loadu_si128((const __m128i*)&mtx->bias_[0]); + const __m128i bias_04 = _mm_loadu_si128((const __m128i*)&mtx->bias_[4]); + const __m128i bias_08 = _mm_loadu_si128((const __m128i*)&mtx->bias_[8]); + const __m128i bias_12 = _mm_loadu_si128((const __m128i*)&mtx->bias_[12]); + out_00 = _mm_add_epi32(out_00, bias_00); + out_04 = _mm_add_epi32(out_04, bias_04); + out_08 = _mm_add_epi32(out_08, bias_08); + out_12 = _mm_add_epi32(out_12, bias_12); + // out = QUANTDIV(coeff, iQ, B, QFIX) + out_00 = _mm_srai_epi32(out_00, QFIX); + out_04 = _mm_srai_epi32(out_04, QFIX); + out_08 = _mm_srai_epi32(out_08, QFIX); + out_12 = _mm_srai_epi32(out_12, QFIX); + + // pack result as 16b + out0 = _mm_packs_epi32(out_00, out_04); + out8 = _mm_packs_epi32(out_08, out_12); + + // if (coeff > 2047) coeff = 2047 + out0 = _mm_min_epi16(out0, max_coeff_2047); + out8 = _mm_min_epi16(out8, max_coeff_2047); + } + + // get sign back (if (sign[j]) out_n = -out_n) + out0 = _mm_xor_si128(out0, sign0); + out8 = _mm_xor_si128(out8, sign8); + out0 = _mm_sub_epi16(out0, sign0); + out8 = _mm_sub_epi16(out8, sign8); + + // in = out * Q + in0 = _mm_mullo_epi16(out0, q0); + in8 = _mm_mullo_epi16(out8, q8); + + _mm_storeu_si128((__m128i*)&in[0], in0); + _mm_storeu_si128((__m128i*)&in[8], in8); + + // zigzag the output before storing it. + // + // The zigzag pattern can almost be reproduced with a small sequence of + // shuffles. After it, we only need to swap the 7th (ending up in third + // position instead of twelfth) and 8th values. + { + __m128i outZ0, outZ8; + outZ0 = _mm_shufflehi_epi16(out0, _MM_SHUFFLE(2, 1, 3, 0)); + outZ0 = _mm_shuffle_epi32 (outZ0, _MM_SHUFFLE(3, 1, 2, 0)); + outZ0 = _mm_shufflehi_epi16(outZ0, _MM_SHUFFLE(3, 1, 0, 2)); + outZ8 = _mm_shufflelo_epi16(out8, _MM_SHUFFLE(3, 0, 2, 1)); + outZ8 = _mm_shuffle_epi32 (outZ8, _MM_SHUFFLE(3, 1, 2, 0)); + outZ8 = _mm_shufflelo_epi16(outZ8, _MM_SHUFFLE(1, 3, 2, 0)); + _mm_storeu_si128((__m128i*)&out[0], outZ0); + _mm_storeu_si128((__m128i*)&out[8], outZ8); + packed_out = _mm_packs_epi16(outZ0, outZ8); + } + { + const int16_t outZ_12 = out[12]; + const int16_t outZ_3 = out[3]; + out[3] = outZ_12; + out[12] = outZ_3; + } + + // detect if all 'out' values are zeroes or not + return (_mm_movemask_epi8(_mm_cmpeq_epi8(packed_out, zero)) != 0xffff); +} + +static int QuantizeBlock_SSE2(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock_SSE2(in, out, &mtx->sharpen_[0], mtx); +} + +static int QuantizeBlockWHT_SSE2(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock_SSE2(in, out, NULL, mtx); +} + +static int Quantize2Blocks_SSE2(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + const uint16_t* const sharpen = &mtx->sharpen_[0]; + nz = DoQuantizeBlock_SSE2(in + 0 * 16, out + 0 * 16, sharpen, mtx) << 0; + nz |= DoQuantizeBlock_SSE2(in + 1 * 16, out + 1 * 16, sharpen, mtx) << 1; + return nz; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitSSE2(void) { + VP8CollectHistogram = CollectHistogram_SSE2; + VP8EncPredLuma16 = Intra16Preds_SSE2; + VP8EncPredChroma8 = IntraChromaPreds_SSE2; + VP8EncPredLuma4 = Intra4Preds_SSE2; + VP8EncQuantizeBlock = QuantizeBlock_SSE2; + VP8EncQuantize2Blocks = Quantize2Blocks_SSE2; + VP8EncQuantizeBlockWHT = QuantizeBlockWHT_SSE2; + VP8ITransform = ITransform_SSE2; + VP8FTransform = FTransform_SSE2; + VP8FTransform2 = FTransform2_SSE2; + VP8FTransformWHT = FTransformWHT_SSE2; + VP8SSE16x16 = SSE16x16_SSE2; + VP8SSE16x8 = SSE16x8_SSE2; + VP8SSE8x8 = SSE8x8_SSE2; + VP8SSE4x4 = SSE4x4_SSE2; + VP8TDisto4x4 = Disto4x4_SSE2; + VP8TDisto16x16 = Disto16x16_SSE2; + VP8Mean16x4 = Mean16x4_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8EncDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse41.c new file mode 100644 index 0000000000..924035a644 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/enc_sse41.c @@ -0,0 +1,339 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 version of some encoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include // for abs() + +#include "src/dsp/common_sse2.h" +#include "src/enc/vp8i_enc.h" + +//------------------------------------------------------------------------------ +// Compute susceptibility based on DCT-coeff histograms. + +static void CollectHistogram_SSE41(const uint8_t* ref, const uint8_t* pred, + int start_block, int end_block, + VP8Histogram* const histo) { + const __m128i max_coeff_thresh = _mm_set1_epi16(MAX_COEFF_THRESH); + int j; + int distribution[MAX_COEFF_THRESH + 1] = { 0 }; + for (j = start_block; j < end_block; ++j) { + int16_t out[16]; + int k; + + VP8FTransform(ref + VP8DspScan[j], pred + VP8DspScan[j], out); + + // Convert coefficients to bin (within out[]). + { + // Load. + const __m128i out0 = _mm_loadu_si128((__m128i*)&out[0]); + const __m128i out1 = _mm_loadu_si128((__m128i*)&out[8]); + // v = abs(out) >> 3 + const __m128i abs0 = _mm_abs_epi16(out0); + const __m128i abs1 = _mm_abs_epi16(out1); + const __m128i v0 = _mm_srai_epi16(abs0, 3); + const __m128i v1 = _mm_srai_epi16(abs1, 3); + // bin = min(v, MAX_COEFF_THRESH) + const __m128i bin0 = _mm_min_epi16(v0, max_coeff_thresh); + const __m128i bin1 = _mm_min_epi16(v1, max_coeff_thresh); + // Store. + _mm_storeu_si128((__m128i*)&out[0], bin0); + _mm_storeu_si128((__m128i*)&out[8], bin1); + } + + // Convert coefficients to bin. + for (k = 0; k < 16; ++k) { + ++distribution[out[k]]; + } + } + VP8SetHistogramData(distribution, histo); +} + +//------------------------------------------------------------------------------ +// Texture distortion +// +// We try to match the spectral content (weighted) between source and +// reconstructed samples. + +// Hadamard transform +// Returns the weighted sum of the absolute value of transformed coefficients. +// w[] contains a row-major 4 by 4 symmetric matrix. +static int TTransform_SSE41(const uint8_t* inA, const uint8_t* inB, + const uint16_t* const w) { + int32_t sum[4]; + __m128i tmp_0, tmp_1, tmp_2, tmp_3; + + // Load and combine inputs. + { + const __m128i inA_0 = _mm_loadu_si128((const __m128i*)&inA[BPS * 0]); + const __m128i inA_1 = _mm_loadu_si128((const __m128i*)&inA[BPS * 1]); + const __m128i inA_2 = _mm_loadu_si128((const __m128i*)&inA[BPS * 2]); + // In SSE4.1, with gcc 4.8 at least (maybe other versions), + // _mm_loadu_si128 is faster than _mm_loadl_epi64. But for the last lump + // of inA and inB, _mm_loadl_epi64 is still used not to have an out of + // bound read. + const __m128i inA_3 = _mm_loadl_epi64((const __m128i*)&inA[BPS * 3]); + const __m128i inB_0 = _mm_loadu_si128((const __m128i*)&inB[BPS * 0]); + const __m128i inB_1 = _mm_loadu_si128((const __m128i*)&inB[BPS * 1]); + const __m128i inB_2 = _mm_loadu_si128((const __m128i*)&inB[BPS * 2]); + const __m128i inB_3 = _mm_loadl_epi64((const __m128i*)&inB[BPS * 3]); + + // Combine inA and inB (we'll do two transforms in parallel). + const __m128i inAB_0 = _mm_unpacklo_epi32(inA_0, inB_0); + const __m128i inAB_1 = _mm_unpacklo_epi32(inA_1, inB_1); + const __m128i inAB_2 = _mm_unpacklo_epi32(inA_2, inB_2); + const __m128i inAB_3 = _mm_unpacklo_epi32(inA_3, inB_3); + tmp_0 = _mm_cvtepu8_epi16(inAB_0); + tmp_1 = _mm_cvtepu8_epi16(inAB_1); + tmp_2 = _mm_cvtepu8_epi16(inAB_2); + tmp_3 = _mm_cvtepu8_epi16(inAB_3); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + } + + // Vertical pass first to avoid a transpose (vertical and horizontal passes + // are commutative because w/kWeightY is symmetric) and subsequent transpose. + { + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&b0, &b1, &b2, &b3, &tmp_0, &tmp_1, &tmp_2, &tmp_3); + } + + // Horizontal pass and difference of weighted sums. + { + // Load all inputs. + const __m128i w_0 = _mm_loadu_si128((const __m128i*)&w[0]); + const __m128i w_8 = _mm_loadu_si128((const __m128i*)&w[8]); + + // Calculate a and b (two 4x4 at once). + const __m128i a0 = _mm_add_epi16(tmp_0, tmp_2); + const __m128i a1 = _mm_add_epi16(tmp_1, tmp_3); + const __m128i a2 = _mm_sub_epi16(tmp_1, tmp_3); + const __m128i a3 = _mm_sub_epi16(tmp_0, tmp_2); + const __m128i b0 = _mm_add_epi16(a0, a1); + const __m128i b1 = _mm_add_epi16(a3, a2); + const __m128i b2 = _mm_sub_epi16(a3, a2); + const __m128i b3 = _mm_sub_epi16(a0, a1); + + // Separate the transforms of inA and inB. + __m128i A_b0 = _mm_unpacklo_epi64(b0, b1); + __m128i A_b2 = _mm_unpacklo_epi64(b2, b3); + __m128i B_b0 = _mm_unpackhi_epi64(b0, b1); + __m128i B_b2 = _mm_unpackhi_epi64(b2, b3); + + A_b0 = _mm_abs_epi16(A_b0); + A_b2 = _mm_abs_epi16(A_b2); + B_b0 = _mm_abs_epi16(B_b0); + B_b2 = _mm_abs_epi16(B_b2); + + // weighted sums + A_b0 = _mm_madd_epi16(A_b0, w_0); + A_b2 = _mm_madd_epi16(A_b2, w_8); + B_b0 = _mm_madd_epi16(B_b0, w_0); + B_b2 = _mm_madd_epi16(B_b2, w_8); + A_b0 = _mm_add_epi32(A_b0, A_b2); + B_b0 = _mm_add_epi32(B_b0, B_b2); + + // difference of weighted sums + A_b2 = _mm_sub_epi32(A_b0, B_b0); + _mm_storeu_si128((__m128i*)&sum[0], A_b2); + } + return sum[0] + sum[1] + sum[2] + sum[3]; +} + +static int Disto4x4_SSE41(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + const int diff_sum = TTransform_SSE41(a, b, w); + return abs(diff_sum) >> 5; +} + +static int Disto16x16_SSE41(const uint8_t* const a, const uint8_t* const b, + const uint16_t* const w) { + int D = 0; + int x, y; + for (y = 0; y < 16 * BPS; y += 4 * BPS) { + for (x = 0; x < 16; x += 4) { + D += Disto4x4_SSE41(a + x + y, b + x + y, w); + } + } + return D; +} + +//------------------------------------------------------------------------------ +// Quantization +// + +// Generates a pshufb constant for shuffling 16b words. +#define PSHUFB_CST(A,B,C,D,E,F,G,H) \ + _mm_set_epi8(2 * (H) + 1, 2 * (H) + 0, 2 * (G) + 1, 2 * (G) + 0, \ + 2 * (F) + 1, 2 * (F) + 0, 2 * (E) + 1, 2 * (E) + 0, \ + 2 * (D) + 1, 2 * (D) + 0, 2 * (C) + 1, 2 * (C) + 0, \ + 2 * (B) + 1, 2 * (B) + 0, 2 * (A) + 1, 2 * (A) + 0) + +static WEBP_INLINE int DoQuantizeBlock_SSE41(int16_t in[16], int16_t out[16], + const uint16_t* const sharpen, + const VP8Matrix* const mtx) { + const __m128i max_coeff_2047 = _mm_set1_epi16(MAX_LEVEL); + const __m128i zero = _mm_setzero_si128(); + __m128i out0, out8; + __m128i packed_out; + + // Load all inputs. + __m128i in0 = _mm_loadu_si128((__m128i*)&in[0]); + __m128i in8 = _mm_loadu_si128((__m128i*)&in[8]); + const __m128i iq0 = _mm_loadu_si128((const __m128i*)&mtx->iq_[0]); + const __m128i iq8 = _mm_loadu_si128((const __m128i*)&mtx->iq_[8]); + const __m128i q0 = _mm_loadu_si128((const __m128i*)&mtx->q_[0]); + const __m128i q8 = _mm_loadu_si128((const __m128i*)&mtx->q_[8]); + + // coeff = abs(in) + __m128i coeff0 = _mm_abs_epi16(in0); + __m128i coeff8 = _mm_abs_epi16(in8); + + // coeff = abs(in) + sharpen + if (sharpen != NULL) { + const __m128i sharpen0 = _mm_loadu_si128((const __m128i*)&sharpen[0]); + const __m128i sharpen8 = _mm_loadu_si128((const __m128i*)&sharpen[8]); + coeff0 = _mm_add_epi16(coeff0, sharpen0); + coeff8 = _mm_add_epi16(coeff8, sharpen8); + } + + // out = (coeff * iQ + B) >> QFIX + { + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + const __m128i coeff_iQ0H = _mm_mulhi_epu16(coeff0, iq0); + const __m128i coeff_iQ0L = _mm_mullo_epi16(coeff0, iq0); + const __m128i coeff_iQ8H = _mm_mulhi_epu16(coeff8, iq8); + const __m128i coeff_iQ8L = _mm_mullo_epi16(coeff8, iq8); + __m128i out_00 = _mm_unpacklo_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_04 = _mm_unpackhi_epi16(coeff_iQ0L, coeff_iQ0H); + __m128i out_08 = _mm_unpacklo_epi16(coeff_iQ8L, coeff_iQ8H); + __m128i out_12 = _mm_unpackhi_epi16(coeff_iQ8L, coeff_iQ8H); + // out = (coeff * iQ + B) + const __m128i bias_00 = _mm_loadu_si128((const __m128i*)&mtx->bias_[0]); + const __m128i bias_04 = _mm_loadu_si128((const __m128i*)&mtx->bias_[4]); + const __m128i bias_08 = _mm_loadu_si128((const __m128i*)&mtx->bias_[8]); + const __m128i bias_12 = _mm_loadu_si128((const __m128i*)&mtx->bias_[12]); + out_00 = _mm_add_epi32(out_00, bias_00); + out_04 = _mm_add_epi32(out_04, bias_04); + out_08 = _mm_add_epi32(out_08, bias_08); + out_12 = _mm_add_epi32(out_12, bias_12); + // out = QUANTDIV(coeff, iQ, B, QFIX) + out_00 = _mm_srai_epi32(out_00, QFIX); + out_04 = _mm_srai_epi32(out_04, QFIX); + out_08 = _mm_srai_epi32(out_08, QFIX); + out_12 = _mm_srai_epi32(out_12, QFIX); + + // pack result as 16b + out0 = _mm_packs_epi32(out_00, out_04); + out8 = _mm_packs_epi32(out_08, out_12); + + // if (coeff > 2047) coeff = 2047 + out0 = _mm_min_epi16(out0, max_coeff_2047); + out8 = _mm_min_epi16(out8, max_coeff_2047); + } + + // put sign back + out0 = _mm_sign_epi16(out0, in0); + out8 = _mm_sign_epi16(out8, in8); + + // in = out * Q + in0 = _mm_mullo_epi16(out0, q0); + in8 = _mm_mullo_epi16(out8, q8); + + _mm_storeu_si128((__m128i*)&in[0], in0); + _mm_storeu_si128((__m128i*)&in[8], in8); + + // zigzag the output before storing it. The re-ordering is: + // 0 1 2 3 4 5 6 7 | 8 9 10 11 12 13 14 15 + // -> 0 1 4[8]5 2 3 6 | 9 12 13 10 [7]11 14 15 + // There's only two misplaced entries ([8] and [7]) that are crossing the + // reg's boundaries. + // We use pshufb instead of pshuflo/pshufhi. + { + const __m128i kCst_lo = PSHUFB_CST(0, 1, 4, -1, 5, 2, 3, 6); + const __m128i kCst_7 = PSHUFB_CST(-1, -1, -1, -1, 7, -1, -1, -1); + const __m128i tmp_lo = _mm_shuffle_epi8(out0, kCst_lo); + const __m128i tmp_7 = _mm_shuffle_epi8(out0, kCst_7); // extract #7 + const __m128i kCst_hi = PSHUFB_CST(1, 4, 5, 2, -1, 3, 6, 7); + const __m128i kCst_8 = PSHUFB_CST(-1, -1, -1, 0, -1, -1, -1, -1); + const __m128i tmp_hi = _mm_shuffle_epi8(out8, kCst_hi); + const __m128i tmp_8 = _mm_shuffle_epi8(out8, kCst_8); // extract #8 + const __m128i out_z0 = _mm_or_si128(tmp_lo, tmp_8); + const __m128i out_z8 = _mm_or_si128(tmp_hi, tmp_7); + _mm_storeu_si128((__m128i*)&out[0], out_z0); + _mm_storeu_si128((__m128i*)&out[8], out_z8); + packed_out = _mm_packs_epi16(out_z0, out_z8); + } + + // detect if all 'out' values are zeroes or not + return (_mm_movemask_epi8(_mm_cmpeq_epi8(packed_out, zero)) != 0xffff); +} + +#undef PSHUFB_CST + +static int QuantizeBlock_SSE41(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock_SSE41(in, out, &mtx->sharpen_[0], mtx); +} + +static int QuantizeBlockWHT_SSE41(int16_t in[16], int16_t out[16], + const VP8Matrix* const mtx) { + return DoQuantizeBlock_SSE41(in, out, NULL, mtx); +} + +static int Quantize2Blocks_SSE41(int16_t in[32], int16_t out[32], + const VP8Matrix* const mtx) { + int nz; + const uint16_t* const sharpen = &mtx->sharpen_[0]; + nz = DoQuantizeBlock_SSE41(in + 0 * 16, out + 0 * 16, sharpen, mtx) << 0; + nz |= DoQuantizeBlock_SSE41(in + 1 * 16, out + 1 * 16, sharpen, mtx) << 1; + return nz; +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8EncDspInitSSE41(void); +WEBP_TSAN_IGNORE_FUNCTION void VP8EncDspInitSSE41(void) { + VP8CollectHistogram = CollectHistogram_SSE41; + VP8EncQuantizeBlock = QuantizeBlock_SSE41; + VP8EncQuantize2Blocks = Quantize2Blocks_SSE41; + VP8EncQuantizeBlockWHT = QuantizeBlockWHT_SSE41; + VP8TDisto4x4 = Disto4x4_SSE41; + VP8TDisto16x16 = Disto16x16_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8EncDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters.c new file mode 100644 index 0000000000..c9232ff16a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters.c @@ -0,0 +1,297 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author: Urvang (urvang@google.com) + +#include "src/dsp/dsp.h" +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert((in) != NULL); \ + assert((out) != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; /* Silence unused warning. */ \ + } while (0) + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void PredictLine_C(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length, int inverse) { + int i; + if (inverse) { + for (i = 0; i < length; ++i) dst[i] = (uint8_t)(src[i] + pred[i]); + } else { + for (i = 0; i < length; ++i) dst[i] = (uint8_t)(src[i] - pred[i]); + } +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_C(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLine_C(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + PredictLine_C(in, preds - stride, out, 1, inverse); + PredictLine_C(in + 1, preds, out + 1, width - 1, inverse); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_C(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLine_C(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + in += stride; + out += stride; + } else { + // We are starting from in-between. Make sure 'preds' points to prev row. + preds -= stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLine_C(in, preds, out, width, inverse); + ++row; + preds += stride; + in += stride; + out += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void DoGradientFilter_C(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + int inverse, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = inverse ? out : in; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLine_C(in + 1, preds, out + 1, width - 1, inverse); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + int w; + // leftmost pixel: predict from above. + PredictLine_C(in, preds - stride, out, 1, inverse); + for (w = 1; w < width; ++w) { + const int pred = GradientPredictor_C(preds[w - 1], + preds[w - stride], + preds[w - stride - 1]); + out[w] = (uint8_t)(in[w] + (inverse ? pred : -pred)); + } + ++row; + preds += stride; + in += stride; + out += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#undef DCHECK + +//------------------------------------------------------------------------------ + +#if !WEBP_NEON_OMIT_C_CODE +static void HorizontalFilter_C(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_C(data, width, height, stride, 0, height, 0, + filtered_data); +} + +static void VerticalFilter_C(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_C(data, width, height, stride, 0, height, 0, filtered_data); +} + +static void GradientFilter_C(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_C(data, width, height, stride, 0, height, 0, filtered_data); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ + +static void NoneUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + (void)prev; + if (out != in) memcpy(out, in, width * sizeof(*out)); +} + +static void HorizontalUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + uint8_t pred = (prev == NULL) ? 0 : prev[0]; + int i; + for (i = 0; i < width; ++i) { + out[i] = (uint8_t)(pred + in[i]); + pred = out[i]; + } +} + +#if !WEBP_NEON_OMIT_C_CODE +static void VerticalUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_C(NULL, in, out, width); + } else { + int i; + for (i = 0; i < width; ++i) out[i] = (uint8_t)(prev[i] + in[i]); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void GradientUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_C(NULL, in, out, width); + } else { + uint8_t top = prev[0], top_left = top, left = top; + int i; + for (i = 0; i < width; ++i) { + top = prev[i]; // need to read this first, in case prev==out + left = (uint8_t)(in[i] + GradientPredictor_C(left, top, top_left)); + top_left = top; + out[i] = left; + } + } +} + +//------------------------------------------------------------------------------ +// Init function + +WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; +WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8FiltersInitMIPSdspR2(void); +extern void VP8FiltersInitMSA(void); +extern void VP8FiltersInitNEON(void); +extern void VP8FiltersInitSSE2(void); + +WEBP_DSP_INIT_FUNC(VP8FiltersInit) { + WebPUnfilters[WEBP_FILTER_NONE] = NoneUnfilter_C; +#if !WEBP_NEON_OMIT_C_CODE + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_C; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_C; +#endif + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_C; + + WebPFilters[WEBP_FILTER_NONE] = NULL; +#if !WEBP_NEON_OMIT_C_CODE + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_C; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_C; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_C; +#endif + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8FiltersInitSSE2(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8FiltersInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8FiltersInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8FiltersInitNEON(); + } +#endif + + assert(WebPUnfilters[WEBP_FILTER_NONE] != NULL); + assert(WebPUnfilters[WEBP_FILTER_HORIZONTAL] != NULL); + assert(WebPUnfilters[WEBP_FILTER_VERTICAL] != NULL); + assert(WebPUnfilters[WEBP_FILTER_GRADIENT] != NULL); + assert(WebPFilters[WEBP_FILTER_HORIZONTAL] != NULL); + assert(WebPFilters[WEBP_FILTER_VERTICAL] != NULL); + assert(WebPFilters[WEBP_FILTER_GRADIENT] != NULL); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_mips_dsp_r2.c new file mode 100644 index 0000000000..eca866f578 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_mips_dsp_r2.c @@ -0,0 +1,404 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/dsp.h" +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; /* Silence unused warning. */ \ + } while (0) + +#define DO_PREDICT_LINE(SRC, DST, LENGTH, INVERSE) do { \ + const uint8_t* psrc = (uint8_t*)(SRC); \ + uint8_t* pdst = (uint8_t*)(DST); \ + const int ilength = (int)(LENGTH); \ + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; \ + __asm__ volatile ( \ + ".set push \n\t" \ + ".set noreorder \n\t" \ + "srl %[temp0], %[length], 2 \n\t" \ + "beqz %[temp0], 4f \n\t" \ + " andi %[temp6], %[length], 3 \n\t" \ + ".if " #INVERSE " \n\t" \ + "1: \n\t" \ + "lbu %[temp1], -1(%[dst]) \n\t" \ + "lbu %[temp2], 0(%[src]) \n\t" \ + "lbu %[temp3], 1(%[src]) \n\t" \ + "lbu %[temp4], 2(%[src]) \n\t" \ + "lbu %[temp5], 3(%[src]) \n\t" \ + "addu %[temp1], %[temp1], %[temp2] \n\t" \ + "addu %[temp2], %[temp1], %[temp3] \n\t" \ + "addu %[temp3], %[temp2], %[temp4] \n\t" \ + "addu %[temp4], %[temp3], %[temp5] \n\t" \ + "sb %[temp1], 0(%[dst]) \n\t" \ + "sb %[temp2], 1(%[dst]) \n\t" \ + "sb %[temp3], 2(%[dst]) \n\t" \ + "sb %[temp4], 3(%[dst]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 4 \n\t" \ + ".else \n\t" \ + "1: \n\t" \ + "ulw %[temp1], -1(%[src]) \n\t" \ + "ulw %[temp2], 0(%[src]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "subu.qb %[temp3], %[temp2], %[temp1] \n\t" \ + "usw %[temp3], 0(%[dst]) \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 4 \n\t" \ + ".endif \n\t" \ + "4: \n\t" \ + "beqz %[temp6], 3f \n\t" \ + " nop \n\t" \ + "2: \n\t" \ + "lbu %[temp2], 0(%[src]) \n\t" \ + ".if " #INVERSE " \n\t" \ + "lbu %[temp1], -1(%[dst]) \n\t" \ + "addu %[temp3], %[temp1], %[temp2] \n\t" \ + ".else \n\t" \ + "lbu %[temp1], -1(%[src]) \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + ".endif \n\t" \ + "addiu %[src], %[src], 1 \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + "addiu %[temp6], %[temp6], -1 \n\t" \ + "bnez %[temp6], 2b \n\t" \ + " addiu %[dst], %[dst], 1 \n\t" \ + "3: \n\t" \ + ".set pop \n\t" \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [dst]"+&r"(pdst), [src]"+&r"(psrc) \ + : [length]"r"(ilength) \ + : "memory" \ + ); \ + } while (0) + +static WEBP_INLINE void PredictLine_MIPSdspR2(const uint8_t* src, uint8_t* dst, + int length) { + DO_PREDICT_LINE(src, dst, length, 0); +} + +#define DO_PREDICT_LINE_VERTICAL(SRC, PRED, DST, LENGTH, INVERSE) do { \ + const uint8_t* psrc = (uint8_t*)(SRC); \ + const uint8_t* ppred = (uint8_t*)(PRED); \ + uint8_t* pdst = (uint8_t*)(DST); \ + const int ilength = (int)(LENGTH); \ + int temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; \ + __asm__ volatile ( \ + ".set push \n\t" \ + ".set noreorder \n\t" \ + "srl %[temp0], %[length], 0x3 \n\t" \ + "beqz %[temp0], 4f \n\t" \ + " andi %[temp7], %[length], 0x7 \n\t" \ + "1: \n\t" \ + "ulw %[temp1], 0(%[src]) \n\t" \ + "ulw %[temp2], 0(%[pred]) \n\t" \ + "ulw %[temp3], 4(%[src]) \n\t" \ + "ulw %[temp4], 4(%[pred]) \n\t" \ + "addiu %[src], %[src], 8 \n\t" \ + ".if " #INVERSE " \n\t" \ + "addu.qb %[temp5], %[temp1], %[temp2] \n\t" \ + "addu.qb %[temp6], %[temp3], %[temp4] \n\t" \ + ".else \n\t" \ + "subu.qb %[temp5], %[temp1], %[temp2] \n\t" \ + "subu.qb %[temp6], %[temp3], %[temp4] \n\t" \ + ".endif \n\t" \ + "addiu %[pred], %[pred], 8 \n\t" \ + "usw %[temp5], 0(%[dst]) \n\t" \ + "usw %[temp6], 4(%[dst]) \n\t" \ + "addiu %[temp0], %[temp0], -1 \n\t" \ + "bnez %[temp0], 1b \n\t" \ + " addiu %[dst], %[dst], 8 \n\t" \ + "4: \n\t" \ + "beqz %[temp7], 3f \n\t" \ + " nop \n\t" \ + "2: \n\t" \ + "lbu %[temp1], 0(%[src]) \n\t" \ + "lbu %[temp2], 0(%[pred]) \n\t" \ + "addiu %[src], %[src], 1 \n\t" \ + "addiu %[pred], %[pred], 1 \n\t" \ + ".if " #INVERSE " \n\t" \ + "addu %[temp3], %[temp1], %[temp2] \n\t" \ + ".else \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + ".endif \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + "addiu %[temp7], %[temp7], -1 \n\t" \ + "bnez %[temp7], 2b \n\t" \ + " addiu %[dst], %[dst], 1 \n\t" \ + "3: \n\t" \ + ".set pop \n\t" \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), [pred]"+&r"(ppred), \ + [dst]"+&r"(pdst), [src]"+&r"(psrc) \ + : [length]"r"(ilength) \ + : "memory" \ + ); \ + } while (0) + +#define PREDICT_LINE_ONE_PASS(SRC, PRED, DST) do { \ + int temp1, temp2, temp3; \ + __asm__ volatile ( \ + "lbu %[temp1], 0(%[src]) \n\t" \ + "lbu %[temp2], 0(%[pred]) \n\t" \ + "subu %[temp3], %[temp1], %[temp2] \n\t" \ + "sb %[temp3], 0(%[dst]) \n\t" \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3) \ + : [pred]"r"((PRED)), [dst]"r"((DST)), [src]"r"((SRC)) \ + : "memory" \ + ); \ + } while (0) + +//------------------------------------------------------------------------------ +// Horizontal filter. + +#define FILTER_LINE_BY_LINE do { \ + while (row < last_row) { \ + PREDICT_LINE_ONE_PASS(in, preds - stride, out); \ + DO_PREDICT_LINE(in + 1, out + 1, width - 1, 0); \ + ++row; \ + preds += stride; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static WEBP_INLINE void DoHorizontalFilter_MIPSdspR2(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLine_MIPSdspR2(in + 1, out + 1, width - 1); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE; +} +#undef FILTER_LINE_BY_LINE + +static void HorizontalFilter_MIPSdspR2(const uint8_t* data, + int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_MIPSdspR2(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +#define FILTER_LINE_BY_LINE do { \ + while (row < last_row) { \ + DO_PREDICT_LINE_VERTICAL(in, preds, out, width, 0); \ + ++row; \ + preds += stride; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static WEBP_INLINE void DoVerticalFilter_MIPSdspR2(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLine_MIPSdspR2(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } else { + // We are starting from in-between. Make sure 'preds' points to prev row. + preds -= stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE; +} +#undef FILTER_LINE_BY_LINE + +static void VerticalFilter_MIPSdspR2(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_MIPSdspR2(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static int GradientPredictor_MIPSdspR2(uint8_t a, uint8_t b, uint8_t c) { + int temp0; + __asm__ volatile ( + "addu %[temp0], %[a], %[b] \n\t" + "subu %[temp0], %[temp0], %[c] \n\t" + "shll_s.w %[temp0], %[temp0], 23 \n\t" + "precrqu_s.qb.ph %[temp0], %[temp0], $zero \n\t" + "srl %[temp0], %[temp0], 24 \n\t" + : [temp0]"=&r"(temp0) + : [a]"r"(a),[b]"r"(b),[c]"r"(c) + ); + return temp0; +} + +#define FILTER_LINE_BY_LINE(PREDS, OPERATION) do { \ + while (row < last_row) { \ + int w; \ + PREDICT_LINE_ONE_PASS(in, PREDS - stride, out); \ + for (w = 1; w < width; ++w) { \ + const int pred = GradientPredictor_MIPSdspR2(PREDS[w - 1], \ + PREDS[w - stride], \ + PREDS[w - stride - 1]); \ + out[w] = in[w] OPERATION pred; \ + } \ + ++row; \ + in += stride; \ + out += stride; \ + } \ + } while (0) + +static void DoGradientFilter_MIPSdspR2(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, uint8_t* out) { + const uint8_t* preds; + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + preds = in; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLine_MIPSdspR2(in + 1, out + 1, width - 1); + row = 1; + preds += stride; + in += stride; + out += stride; + } + + // Filter line-by-line. + FILTER_LINE_BY_LINE(in, -); +} +#undef FILTER_LINE_BY_LINE + +static void GradientFilter_MIPSdspR2(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_MIPSdspR2(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ + +static void HorizontalUnfilter_MIPSdspR2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + DO_PREDICT_LINE(in + 1, out + 1, width - 1, 1); +} + +static void VerticalUnfilter_MIPSdspR2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_MIPSdspR2(NULL, in, out, width); + } else { + DO_PREDICT_LINE_VERTICAL(in, prev, out, width, 1); + } +} + +static void GradientUnfilter_MIPSdspR2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_MIPSdspR2(NULL, in, out, width); + } else { + uint8_t top = prev[0], top_left = top, left = top; + int i; + for (i = 0; i < width; ++i) { + top = prev[i]; // need to read this first, in case prev==dst + left = in[i] + GradientPredictor_MIPSdspR2(left, top, top_left); + top_left = top; + out[i] = left; + } + } +} + +#undef DO_PREDICT_LINE_VERTICAL +#undef PREDICT_LINE_ONE_PASS +#undef DO_PREDICT_LINE +#undef DCHECK + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitMIPSdspR2(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_MIPSdspR2; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_MIPSdspR2; + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_MIPSdspR2; + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_MIPSdspR2; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_MIPSdspR2; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8FiltersInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_msa.c new file mode 100644 index 0000000000..33a1b20b70 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_msa.c @@ -0,0 +1,204 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of alpha filters +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "src/dsp/msa_macro.h" + +#include + +static WEBP_INLINE void PredictLineInverse0(const uint8_t* src, + const uint8_t* pred, + uint8_t* dst, int length) { + v16u8 src0, pred0, dst0; + assert(length >= 0); + while (length >= 32) { + v16u8 src1, pred1, dst1; + LD_UB2(src, 16, src0, src1); + LD_UB2(pred, 16, pred0, pred1); + SUB2(src0, pred0, src1, pred1, dst0, dst1); + ST_UB2(dst0, dst1, dst, 16); + src += 32; + pred += 32; + dst += 32; + length -= 32; + } + if (length > 0) { + int i; + if (length >= 16) { + src0 = LD_UB(src); + pred0 = LD_UB(pred); + dst0 = src0 - pred0; + ST_UB(dst0, dst); + src += 16; + pred += 16; + dst += 16; + length -= 16; + } + for (i = 0; i < length; i++) { + dst[i] = src[i] - pred[i]; + } + } +} + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + } while (0) + +//------------------------------------------------------------------------------ +// Horrizontal filter + +static void HorizontalFilter_MSA(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* preds = data; + const uint8_t* in = data; + uint8_t* out = filtered_data; + int row = 1; + DCHECK(in, out); + + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + // Filter line-by-line. + while (row < height) { + // Leftmost pixel is predicted from above. + PredictLineInverse0(in, preds - stride, out, 1); + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter + +static WEBP_INLINE void PredictLineGradient(const uint8_t* pinput, + const uint8_t* ppred, + uint8_t* poutput, int stride, + int size) { + int w; + const v16i8 zero = { 0 }; + while (size >= 16) { + v16u8 pred0, dst0; + v8i16 a0, a1, b0, b1, c0, c1; + const v16u8 tmp0 = LD_UB(ppred - 1); + const v16u8 tmp1 = LD_UB(ppred - stride); + const v16u8 tmp2 = LD_UB(ppred - stride - 1); + const v16u8 src0 = LD_UB(pinput); + ILVRL_B2_SH(zero, tmp0, a0, a1); + ILVRL_B2_SH(zero, tmp1, b0, b1); + ILVRL_B2_SH(zero, tmp2, c0, c1); + ADD2(a0, b0, a1, b1, a0, a1); + SUB2(a0, c0, a1, c1, a0, a1); + CLIP_SH2_0_255(a0, a1); + pred0 = (v16u8)__msa_pckev_b((v16i8)a1, (v16i8)a0); + dst0 = src0 - pred0; + ST_UB(dst0, poutput); + ppred += 16; + pinput += 16; + poutput += 16; + size -= 16; + } + for (w = 0; w < size; ++w) { + const int pred = ppred[w - 1] + ppred[w - stride] - ppred[w - stride - 1]; + poutput[w] = pinput[w] - (pred < 0 ? 0 : pred > 255 ? 255 : pred); + } +} + + +static void GradientFilter_MSA(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* in = data; + const uint8_t* preds = data; + uint8_t* out = filtered_data; + int row = 1; + DCHECK(in, out); + + // left prediction for top scan-line + out[0] = in[0]; + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + // Filter line-by-line. + while (row < height) { + out[0] = in[0] - preds[- stride]; + PredictLineGradient(preds + 1, in + 1, out + 1, stride, width - 1); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter + +static void VerticalFilter_MSA(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + const uint8_t* in = data; + const uint8_t* preds = data; + uint8_t* out = filtered_data; + int row = 1; + DCHECK(in, out); + + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineInverse0(in + 1, preds, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + while (row < height) { + PredictLineInverse0(in, preds, out, width); + ++row; + preds += stride; + in += stride; + out += stride; + } +} + +#undef DCHECK + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitMSA(void) { + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_MSA; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_MSA; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_MSA; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8FiltersInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_neon.c new file mode 100644 index 0000000000..b49e515af1 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_neon.c @@ -0,0 +1,331 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ +// Helpful macros. + +#define DCHECK(in, out) \ + do { \ + assert(in != NULL); \ + assert(out != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; /* Silence unused warning. */ \ + } while (0) + +// load eight u8 and widen to s16 +#define U8_TO_S16(A) vreinterpretq_s16_u16(vmovl_u8(A)) +#define LOAD_U8_TO_S16(A) U8_TO_S16(vld1_u8(A)) + +// shift left or right by N byte, inserting zeros +#define SHIFT_RIGHT_N_Q(A, N) vextq_u8((A), zero, (N)) +#define SHIFT_LEFT_N_Q(A, N) vextq_u8(zero, (A), (16 - (N)) % 16) + +// rotate left by N bytes +#define ROTATE_LEFT_N(A, N) vext_u8((A), (A), (N)) +// rotate right by N bytes +#define ROTATE_RIGHT_N(A, N) vext_u8((A), (A), (8 - (N)) % 8) + +static void PredictLine_NEON(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length) { + int i; + assert(length >= 0); + for (i = 0; i + 16 <= length; i += 16) { + const uint8x16_t A = vld1q_u8(&src[i]); + const uint8x16_t B = vld1q_u8(&pred[i]); + const uint8x16_t C = vsubq_u8(A, B); + vst1q_u8(&dst[i], C); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_NEON(const uint8_t* src, uint8_t* dst, int length) { + PredictLine_NEON(src, src - 1, dst, length); +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void HorizontalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_NEON(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLine_NEON(in, in - stride, out, width); + ++row; + in += stride; + out += stride; + } +} + +static void VerticalFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_NEON(const uint8_t* const row, + const uint8_t* const top, + uint8_t* const out, int length) { + int i; + for (i = 0; i + 8 <= length; i += 8) { + const uint8x8_t A = vld1_u8(&row[i - 1]); + const uint8x8_t B = vld1_u8(&top[i + 0]); + const int16x8_t C = vreinterpretq_s16_u16(vaddl_u8(A, B)); + const int16x8_t D = LOAD_U8_TO_S16(&top[i - 1]); + const uint8x8_t E = vqmovun_s16(vsubq_s16(C, D)); + const uint8x8_t F = vld1_u8(&row[i + 0]); + vst1_u8(&out[i], vsub_u8(F, E)); + } + for (; i < length; ++i) { + out[i] = row[i] - GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } +} + +static WEBP_INLINE void DoGradientFilter_NEON(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + out[0] = in[0] - in[-stride]; + GradientPredictDirect_NEON(in + 1, in + 1 - stride, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +static void GradientFilter_NEON(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_NEON(data, width, height, stride, 0, height, + filtered_data); +} + +#undef DCHECK + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + const uint8x16_t zero = vdupq_n_u8(0); + uint8x16_t last; + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + if (width <= 1) return; + last = vsetq_lane_u8(out[0], zero, 0); + for (i = 1; i + 16 <= width; i += 16) { + const uint8x16_t A0 = vld1q_u8(&in[i]); + const uint8x16_t A1 = vaddq_u8(A0, last); + const uint8x16_t A2 = SHIFT_LEFT_N_Q(A1, 1); + const uint8x16_t A3 = vaddq_u8(A1, A2); + const uint8x16_t A4 = SHIFT_LEFT_N_Q(A3, 2); + const uint8x16_t A5 = vaddq_u8(A3, A4); + const uint8x16_t A6 = SHIFT_LEFT_N_Q(A5, 4); + const uint8x16_t A7 = vaddq_u8(A5, A6); + const uint8x16_t A8 = SHIFT_LEFT_N_Q(A7, 8); + const uint8x16_t A9 = vaddq_u8(A7, A8); + vst1q_u8(&out[i], A9); + last = SHIFT_RIGHT_N_Q(A9, 15); + } + for (; i < width; ++i) out[i] = in[i] + out[i - 1]; +} + +static void VerticalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + int i; + assert(width >= 0); + for (i = 0; i + 16 <= width; i += 16) { + const uint8x16_t A = vld1q_u8(&in[i]); + const uint8x16_t B = vld1q_u8(&prev[i]); + const uint8x16_t C = vaddq_u8(A, B); + vst1q_u8(&out[i], C); + } + for (; i < width; ++i) out[i] = in[i] + prev[i]; + } +} + +// GradientUnfilter_NEON is correct but slower than the C-version, +// at least on ARM64. For armv7, it's a wash. +// So best is to disable it for now, but keep the idea around... +#if !defined(USE_GRADIENT_UNFILTER) +#define USE_GRADIENT_UNFILTER 0 // ALTERNATE_CODE +#endif + +#if (USE_GRADIENT_UNFILTER == 1) +#define GRAD_PROCESS_LANE(L) do { \ + const uint8x8_t tmp1 = ROTATE_RIGHT_N(pred, 1); /* rotate predictor in */ \ + const int16x8_t tmp2 = vaddq_s16(BC, U8_TO_S16(tmp1)); \ + const uint8x8_t delta = vqmovun_s16(tmp2); \ + pred = vadd_u8(D, delta); \ + out = vext_u8(out, ROTATE_LEFT_N(pred, (L)), 1); \ +} while (0) + +static void GradientPredictInverse_NEON(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + uint8x8_t pred = vdup_n_u8(row[-1]); // left sample + uint8x8_t out = vdup_n_u8(0); + for (i = 0; i + 8 <= length; i += 8) { + const int16x8_t B = LOAD_U8_TO_S16(&top[i + 0]); + const int16x8_t C = LOAD_U8_TO_S16(&top[i - 1]); + const int16x8_t BC = vsubq_s16(B, C); // unclipped gradient basis B - C + const uint8x8_t D = vld1_u8(&in[i]); // base input + GRAD_PROCESS_LANE(0); + GRAD_PROCESS_LANE(1); + GRAD_PROCESS_LANE(2); + GRAD_PROCESS_LANE(3); + GRAD_PROCESS_LANE(4); + GRAD_PROCESS_LANE(5); + GRAD_PROCESS_LANE(6); + GRAD_PROCESS_LANE(7); + vst1_u8(&row[i], out); + } + for (; i < length; ++i) { + row[i] = in[i] + GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } + } +} +#undef GRAD_PROCESS_LANE + +static void GradientUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + out[0] = in[0] + prev[0]; // predict from above + GradientPredictInverse_NEON(in + 1, prev + 1, out + 1, width - 1); + } +} + +#endif // USE_GRADIENT_UNFILTER + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitNEON(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_NEON; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_NEON; +#if (USE_GRADIENT_UNFILTER == 1) + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_NEON; +#endif + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_NEON; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_NEON; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8FiltersInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_sse2.c new file mode 100644 index 0000000000..bb4b5d5874 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/filters_sse2.c @@ -0,0 +1,342 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include +#include +#include + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert((in) != NULL); \ + assert((out) != NULL); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + assert(row >= 0 && num_rows > 0 && row + num_rows <= height); \ + (void)height; /* Silence unused warning. */ \ + } while (0) + +static void PredictLineTop_SSE2(const uint8_t* src, const uint8_t* pred, + uint8_t* dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&src[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&src[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&pred[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&pred[i + 16]); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&dst[i + 0], C0); + _mm_storeu_si128((__m128i*)&dst[i + 16], C1); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_SSE2(const uint8_t* src, uint8_t* dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + i + 0 )); + const __m128i B0 = _mm_loadu_si128((const __m128i*)(src + i + 0 - 1)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + i + 16 )); + const __m128i B1 = _mm_loadu_si128((const __m128i*)(src + i + 16 - 1)); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)(dst + i + 0), C0); + _mm_storeu_si128((__m128i*)(dst + i + 16), C1); + } + for (; i < length; ++i) dst[i] = src[i] - src[i - 1]; +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_SSE2(const uint8_t* in, + int width, int height, + int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_SSE2(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + if (row == 0) { + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + PredictLineTop_SSE2(in, in - stride, out, width); + ++row; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_SSE2(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_SSE2(const uint8_t* const row, + const uint8_t* const top, + uint8_t* const out, int length) { + const int max_pos = length & ~7; + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i < max_pos; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&row[i - 1]); + const __m128i B0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i C0 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i D = _mm_loadl_epi64((const __m128i*)&row[i]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i B1 = _mm_unpacklo_epi8(B0, zero); + const __m128i C1 = _mm_unpacklo_epi8(C0, zero); + const __m128i E = _mm_add_epi16(A1, B1); + const __m128i F = _mm_sub_epi16(E, C1); + const __m128i G = _mm_packus_epi16(F, zero); + const __m128i H = _mm_sub_epi8(D, G); + _mm_storel_epi64((__m128i*)(out + i), H); + } + for (; i < length; ++i) { + const int delta = GradientPredictor_SSE2(row[i - 1], top[i], top[i - 1]); + out[i] = (uint8_t)(row[i] - delta); + } +} + +static WEBP_INLINE void DoGradientFilter_SSE2(const uint8_t* in, + int width, int height, int stride, + int row, int num_rows, + uint8_t* out) { + const size_t start_offset = row * stride; + const int last_row = row + num_rows; + DCHECK(in, out); + in += start_offset; + out += start_offset; + + // left prediction for top scan-line + if (row == 0) { + out[0] = in[0]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + row = 1; + in += stride; + out += stride; + } + + // Filter line-by-line. + while (row < last_row) { + out[0] = (uint8_t)(in[0] - in[-stride]); + GradientPredictDirect_SSE2(in + 1, in + 1 - stride, out + 1, width - 1); + ++row; + in += stride; + out += stride; + } +} + +#undef DCHECK + +//------------------------------------------------------------------------------ + +static void HorizontalFilter_SSE2(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoHorizontalFilter_SSE2(data, width, height, stride, 0, height, + filtered_data); +} + +static void VerticalFilter_SSE2(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoVerticalFilter_SSE2(data, width, height, stride, 0, height, filtered_data); +} + +static void GradientFilter_SSE2(const uint8_t* data, int width, int height, + int stride, uint8_t* filtered_data) { + DoGradientFilter_SSE2(data, width, height, stride, 0, height, filtered_data); +} + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + __m128i last; + out[0] = (uint8_t)(in[0] + (prev == NULL ? 0 : prev[0])); + if (width <= 1) return; + last = _mm_set_epi32(0, 0, 0, out[0]); + for (i = 1; i + 8 <= width; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)(in + i)); + const __m128i A1 = _mm_add_epi8(A0, last); + const __m128i A2 = _mm_slli_si128(A1, 1); + const __m128i A3 = _mm_add_epi8(A1, A2); + const __m128i A4 = _mm_slli_si128(A3, 2); + const __m128i A5 = _mm_add_epi8(A3, A4); + const __m128i A6 = _mm_slli_si128(A5, 4); + const __m128i A7 = _mm_add_epi8(A5, A6); + _mm_storel_epi64((__m128i*)(out + i), A7); + last = _mm_srli_epi64(A7, 56); + } + for (; i < width; ++i) out[i] = (uint8_t)(in[i] + out[i - 1]); +} + +static void VerticalUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_SSE2(NULL, in, out, width); + } else { + int i; + const int max_pos = width & ~31; + assert(width >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&in[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&in[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&prev[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&prev[i + 16]); + const __m128i C0 = _mm_add_epi8(A0, B0); + const __m128i C1 = _mm_add_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&out[i + 0], C0); + _mm_storeu_si128((__m128i*)&out[i + 16], C1); + } + for (; i < width; ++i) out[i] = (uint8_t)(in[i] + prev[i]); + } +} + +static void GradientPredictInverse_SSE2(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + const int max_pos = length & ~7; + const __m128i zero = _mm_setzero_si128(); + __m128i A = _mm_set_epi32(0, 0, 0, row[-1]); // left sample + for (i = 0; i < max_pos; i += 8) { + const __m128i tmp0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i tmp1 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i B = _mm_unpacklo_epi8(tmp0, zero); + const __m128i C = _mm_unpacklo_epi8(tmp1, zero); + const __m128i D = _mm_loadl_epi64((const __m128i*)&in[i]); // base input + const __m128i E = _mm_sub_epi16(B, C); // unclipped gradient basis B - C + __m128i out = zero; // accumulator for output + __m128i mask_hi = _mm_set_epi32(0, 0, 0, 0xff); + int k = 8; + while (1) { + const __m128i tmp3 = _mm_add_epi16(A, E); // delta = A + B - C + const __m128i tmp4 = _mm_packus_epi16(tmp3, zero); // saturate delta + const __m128i tmp5 = _mm_add_epi8(tmp4, D); // add to in[] + A = _mm_and_si128(tmp5, mask_hi); // 1-complement clip + out = _mm_or_si128(out, A); // accumulate output + if (--k == 0) break; + A = _mm_slli_si128(A, 1); // rotate left sample + mask_hi = _mm_slli_si128(mask_hi, 1); // rotate mask + A = _mm_unpacklo_epi8(A, zero); // convert 8b->16b + } + A = _mm_srli_si128(A, 7); // prepare left sample for next iteration + _mm_storel_epi64((__m128i*)&row[i], out); + } + for (; i < length; ++i) { + const int delta = GradientPredictor_SSE2(row[i - 1], top[i], top[i - 1]); + row[i] = (uint8_t)(in[i] + delta); + } + } +} + +static void GradientUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_SSE2(NULL, in, out, width); + } else { + out[0] = (uint8_t)(in[0] + prev[0]); // predict from above + GradientPredictInverse_SSE2(in + 1, prev + 1, out + 1, width - 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitSSE2(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_SSE2; +#if defined(CHROMIUM) + // TODO(crbug.com/654974) + (void)VerticalUnfilter_SSE2; +#else + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_SSE2; +#endif + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_SSE2; + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_SSE2; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_SSE2; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8FiltersInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.c new file mode 100644 index 0000000000..9f81209453 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.c @@ -0,0 +1,681 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#include "src/dsp/dsp.h" + +#include +#include +#include +#include "src/dec/vp8li_dec.h" +#include "src/utils/endian_inl_utils.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" + +//------------------------------------------------------------------------------ +// Image transforms. + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + return Average2(Average2(a0, a2), a1); +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + return Average2(Average2(a0, a1), Average2(a2, a3)); +} + +static WEBP_INLINE uint32_t Clip255(uint32_t a) { + if (a < 256) { + return a; + } + // return 0, when a is a negative integer. + // return 255, when a is positive. + return ~a >> 24; +} + +static WEBP_INLINE int AddSubtractComponentFull(int a, int b, int c) { + return Clip255((uint32_t)(a + b - c)); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + const int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); + const int r = AddSubtractComponentFull((c0 >> 16) & 0xff, + (c1 >> 16) & 0xff, + (c2 >> 16) & 0xff); + const int g = AddSubtractComponentFull((c0 >> 8) & 0xff, + (c1 >> 8) & 0xff, + (c2 >> 8) & 0xff); + const int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) { + return Clip255((uint32_t)(a + (a - b) / 2)); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + const uint32_t ave = Average2(c0, c1); + const int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24); + const int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); + const int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); + const int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +// gcc <= 4.9 on ARM generates incorrect code in Select() when Sub3() is +// inlined. +#if defined(__arm__) && defined(__GNUC__) && LOCAL_GCC_VERSION <= 0x409 +# define LOCAL_INLINE __attribute__ ((noinline)) +#else +# define LOCAL_INLINE WEBP_INLINE +#endif + +static LOCAL_INLINE int Sub3(int a, int b, int c) { + const int pb = b - c; + const int pa = a - c; + return abs(pb) - abs(pa); +} + +#undef LOCAL_INLINE + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + const int pa_minus_pb = + Sub3((a >> 24) , (b >> 24) , (c >> 24) ) + + Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + + Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + + Sub3((a ) & 0xff, (b ) & 0xff, (c ) & 0xff); + return (pa_minus_pb <= 0) ? a : b; +} + +//------------------------------------------------------------------------------ +// Predictors + +uint32_t VP8LPredictor0_C(const uint32_t* const left, + const uint32_t* const top) { + (void)top; + (void)left; + return ARGB_BLACK; +} +uint32_t VP8LPredictor1_C(const uint32_t* const left, + const uint32_t* const top) { + (void)top; + return *left; +} +uint32_t VP8LPredictor2_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[0]; +} +uint32_t VP8LPredictor3_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[1]; +} +uint32_t VP8LPredictor4_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[-1]; +} +uint32_t VP8LPredictor5_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average3(*left, top[0], top[1]); + return pred; +} +uint32_t VP8LPredictor6_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(*left, top[-1]); + return pred; +} +uint32_t VP8LPredictor7_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(*left, top[0]); + return pred; +} +uint32_t VP8LPredictor8_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(top[-1], top[0]); + (void)left; + return pred; +} +uint32_t VP8LPredictor9_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(top[0], top[1]); + (void)left; + return pred; +} +uint32_t VP8LPredictor10_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average4(*left, top[-1], top[0], top[1]); + return pred; +} +uint32_t VP8LPredictor11_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Select(top[0], *left, top[-1]); + return pred; +} +uint32_t VP8LPredictor12_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull(*left, top[0], top[-1]); + return pred; +} +uint32_t VP8LPredictor13_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf(*left, top[0], top[-1]); + return pred; +} + +static void PredictorAdd0_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int x; + (void)upper; + for (x = 0; x < num_pixels; ++x) out[x] = VP8LAddPixels(in[x], ARGB_BLACK); +} +static void PredictorAdd1_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint32_t left = out[-1]; + (void)upper; + for (i = 0; i < num_pixels; ++i) { + out[i] = left = VP8LAddPixels(in[i], left); + } +} +GENERATE_PREDICTOR_ADD(VP8LPredictor2_C, PredictorAdd2_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor3_C, PredictorAdd3_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor4_C, PredictorAdd4_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor5_C, PredictorAdd5_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor6_C, PredictorAdd6_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor7_C, PredictorAdd7_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor8_C, PredictorAdd8_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor9_C, PredictorAdd9_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor10_C, PredictorAdd10_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor11_C, PredictorAdd11_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor12_C, PredictorAdd12_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor13_C, PredictorAdd13_C) + +//------------------------------------------------------------------------------ + +// Inverse prediction. +static void PredictorInverseTransform_C(const VP8LTransform* const transform, + int y_start, int y_end, + const uint32_t* in, uint32_t* out) { + const int width = transform->xsize_; + if (y_start == 0) { // First Row follows the L (mode=1) mode. + PredictorAdd0_C(in, NULL, 1, out); + PredictorAdd1_C(in + 1, NULL, width - 1, out + 1); + in += width; + out += width; + ++y_start; + } + + { + int y = y_start; + const int tile_width = 1 << transform->bits_; + const int mask = tile_width - 1; + const int tiles_per_row = VP8LSubSampleSize(width, transform->bits_); + const uint32_t* pred_mode_base = + transform->data_ + (y >> transform->bits_) * tiles_per_row; + + while (y < y_end) { + const uint32_t* pred_mode_src = pred_mode_base; + int x = 1; + // First pixel follows the T (mode=2) mode. + PredictorAdd2_C(in, out - width, 1, out); + // .. the rest: + while (x < width) { + const VP8LPredictorAddSubFunc pred_func = + VP8LPredictorsAdd[((*pred_mode_src++) >> 8) & 0xf]; + int x_end = (x & ~mask) + tile_width; + if (x_end > width) x_end = width; + pred_func(in + x, out + x - width, x_end - x, out + x); + x = x_end; + } + in += width; + out += width; + ++y; + if ((y & mask) == 0) { // Use the same mask, since tiles are squares. + pred_mode_base += tiles_per_row; + } + } + } +} + +// Add green to blue and red channels (i.e. perform the inverse transform of +// 'subtract green'). +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; + const uint32_t green = ((argb >> 8) & 0xff); + uint32_t red_blue = (argb & 0x00ff00ffu); + red_blue += (green << 16) | green; + red_blue &= 0x00ff00ffu; + dst[i] = (argb & 0xff00ff00u) | red_blue; + } +} + +static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, + int8_t color) { + return ((int)color_pred * color) >> 5; +} + +static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code, + VP8LMultipliers* const m) { + m->green_to_red_ = (color_code >> 0) & 0xff; + m->green_to_blue_ = (color_code >> 8) & 0xff; + m->red_to_blue_ = (color_code >> 16) & 0xff; +} + +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; + const int8_t green = (int8_t)(argb >> 8); + const uint32_t red = argb >> 16; + int new_red = red & 0xff; + int new_blue = argb & 0xff; + new_red += ColorTransformDelta((int8_t)m->green_to_red_, green); + new_red &= 0xff; + new_blue += ColorTransformDelta((int8_t)m->green_to_blue_, green); + new_blue += ColorTransformDelta((int8_t)m->red_to_blue_, (int8_t)new_red); + new_blue &= 0xff; + dst[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +// Color space inverse transform. +static void ColorSpaceInverseTransform_C(const VP8LTransform* const transform, + int y_start, int y_end, + const uint32_t* src, uint32_t* dst) { + const int width = transform->xsize_; + const int tile_width = 1 << transform->bits_; + const int mask = tile_width - 1; + const int safe_width = width & ~mask; + const int remaining_width = width - safe_width; + const int tiles_per_row = VP8LSubSampleSize(width, transform->bits_); + int y = y_start; + const uint32_t* pred_row = + transform->data_ + (y >> transform->bits_) * tiles_per_row; + + while (y < y_end) { + const uint32_t* pred = pred_row; + VP8LMultipliers m = { 0, 0, 0 }; + const uint32_t* const src_safe_end = src + safe_width; + const uint32_t* const src_end = src + width; + while (src < src_safe_end) { + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, tile_width, dst); + src += tile_width; + dst += tile_width; + } + if (src < src_end) { // Left-overs using C-version. + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, remaining_width, dst); + src += remaining_width; + dst += remaining_width; + } + ++y; + if ((y & mask) == 0) pred_row += tiles_per_row; + } +} + +// Separate out pixels packed together using pixel-bundling. +// We define two methods for ARGB data (uint32_t) and alpha-only data (uint8_t). +#define COLOR_INDEX_INVERSE(FUNC_NAME, F_NAME, STATIC_DECL, TYPE, BIT_SUFFIX, \ + GET_INDEX, GET_VALUE) \ +static void F_NAME(const TYPE* src, const uint32_t* const color_map, \ + TYPE* dst, int y_start, int y_end, int width) { \ + int y; \ + for (y = y_start; y < y_end; ++y) { \ + int x; \ + for (x = 0; x < width; ++x) { \ + *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ + } \ + } \ +} \ +STATIC_DECL void FUNC_NAME(const VP8LTransform* const transform, \ + int y_start, int y_end, const TYPE* src, \ + TYPE* dst) { \ + int y; \ + const int bits_per_pixel = 8 >> transform->bits_; \ + const int width = transform->xsize_; \ + const uint32_t* const color_map = transform->data_; \ + if (bits_per_pixel < 8) { \ + const int pixels_per_byte = 1 << transform->bits_; \ + const int count_mask = pixels_per_byte - 1; \ + const uint32_t bit_mask = (1 << bits_per_pixel) - 1; \ + for (y = y_start; y < y_end; ++y) { \ + uint32_t packed_pixels = 0; \ + int x; \ + for (x = 0; x < width; ++x) { \ + /* We need to load fresh 'packed_pixels' once every */ \ + /* 'pixels_per_byte' increments of x. Fortunately, pixels_per_byte */ \ + /* is a power of 2, so can just use a mask for that, instead of */ \ + /* decrementing a counter. */ \ + if ((x & count_mask) == 0) packed_pixels = GET_INDEX(*src++); \ + *dst++ = GET_VALUE(color_map[packed_pixels & bit_mask]); \ + packed_pixels >>= bits_per_pixel; \ + } \ + } \ + } else { \ + VP8LMapColor##BIT_SUFFIX(src, color_map, dst, y_start, y_end, width); \ + } \ +} + +COLOR_INDEX_INVERSE(ColorIndexInverseTransform_C, MapARGB_C, static, + uint32_t, 32b, VP8GetARGBIndex, VP8GetARGBValue) +COLOR_INDEX_INVERSE(VP8LColorIndexInverseTransformAlpha, MapAlpha_C, , + uint8_t, 8b, VP8GetAlphaIndex, VP8GetAlphaValue) + +#undef COLOR_INDEX_INVERSE + +void VP8LInverseTransform(const VP8LTransform* const transform, + int row_start, int row_end, + const uint32_t* const in, uint32_t* const out) { + const int width = transform->xsize_; + assert(row_start < row_end); + assert(row_end <= transform->ysize_); + switch (transform->type_) { + case SUBTRACT_GREEN_TRANSFORM: + VP8LAddGreenToBlueAndRed(in, (row_end - row_start) * width, out); + break; + case PREDICTOR_TRANSFORM: + PredictorInverseTransform_C(transform, row_start, row_end, in, out); + if (row_end != transform->ysize_) { + // The last predicted row in this iteration will be the top-pred row + // for the first row in next iteration. + memcpy(out - width, out + (row_end - row_start - 1) * width, + width * sizeof(*out)); + } + break; + case CROSS_COLOR_TRANSFORM: + ColorSpaceInverseTransform_C(transform, row_start, row_end, in, out); + break; + case COLOR_INDEXING_TRANSFORM: + if (in == out && transform->bits_ > 0) { + // Move packed pixels to the end of unpacked region, so that unpacking + // can occur seamlessly. + // Also, note that this is the only transform that applies on + // the effective width of VP8LSubSampleSize(xsize_, bits_). All other + // transforms work on effective width of xsize_. + const int out_stride = (row_end - row_start) * width; + const int in_stride = (row_end - row_start) * + VP8LSubSampleSize(transform->xsize_, transform->bits_); + uint32_t* const src = out + out_stride - in_stride; + memmove(src, out, in_stride * sizeof(*src)); + ColorIndexInverseTransform_C(transform, row_start, row_end, src, out); + } else { + ColorIndexInverseTransform_C(transform, row_start, row_end, in, out); + } + break; + } +} + +//------------------------------------------------------------------------------ +// Color space conversion. + +static int is_big_endian(void) { + static const union { + uint16_t w; + uint8_t b[2]; + } tmp = { 1 }; + return (tmp.b[0] != 1); +} + +void VP8LConvertBGRAToRGB_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 16) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 0) & 0xff; + } +} + +void VP8LConvertBGRAToRGBA_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 16) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 0) & 0xff; + *dst++ = (argb >> 24) & 0xff; + } +} + +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + const uint8_t rg = ((argb >> 16) & 0xf0) | ((argb >> 12) & 0xf); + const uint8_t ba = ((argb >> 0) & 0xf0) | ((argb >> 28) & 0xf); +#if (WEBP_SWAP_16BIT_CSP == 1) + *dst++ = ba; + *dst++ = rg; +#else + *dst++ = rg; + *dst++ = ba; +#endif + } +} + +void VP8LConvertBGRAToRGB565_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + const uint8_t rg = ((argb >> 16) & 0xf8) | ((argb >> 13) & 0x7); + const uint8_t gb = ((argb >> 5) & 0xe0) | ((argb >> 3) & 0x1f); +#if (WEBP_SWAP_16BIT_CSP == 1) + *dst++ = gb; + *dst++ = rg; +#else + *dst++ = rg; + *dst++ = gb; +#endif + } +} + +void VP8LConvertBGRAToBGR_C(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 0) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 16) & 0xff; + } +} + +static void CopyOrSwap(const uint32_t* src, int num_pixels, uint8_t* dst, + int swap_on_big_endian) { + if (is_big_endian() == swap_on_big_endian) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + WebPUint32ToMem(dst, BSwap32(argb)); + dst += sizeof(argb); + } + } else { + memcpy(dst, src, num_pixels * sizeof(*src)); + } +} + +void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, + WEBP_CSP_MODE out_colorspace, uint8_t* const rgba) { + switch (out_colorspace) { + case MODE_RGB: + VP8LConvertBGRAToRGB(in_data, num_pixels, rgba); + break; + case MODE_RGBA: + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); + break; + case MODE_rgbA: + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); + WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0); + break; + case MODE_BGR: + VP8LConvertBGRAToBGR(in_data, num_pixels, rgba); + break; + case MODE_BGRA: + CopyOrSwap(in_data, num_pixels, rgba, 1); + break; + case MODE_bgrA: + CopyOrSwap(in_data, num_pixels, rgba, 1); + WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0); + break; + case MODE_ARGB: + CopyOrSwap(in_data, num_pixels, rgba, 0); + break; + case MODE_Argb: + CopyOrSwap(in_data, num_pixels, rgba, 0); + WebPApplyAlphaMultiply(rgba, 1, num_pixels, 1, 0); + break; + case MODE_RGBA_4444: + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + break; + case MODE_rgbA_4444: + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + WebPApplyAlphaMultiply4444(rgba, num_pixels, 1, 0); + break; + case MODE_RGB_565: + VP8LConvertBGRAToRGB565(in_data, num_pixels, rgba); + break; + default: + assert(0); // Code flow should not reach here. + } +} + +//------------------------------------------------------------------------------ + +VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; +VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +VP8LPredictorFunc VP8LPredictors[16]; + +// exposed plain-C implementations +VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; + +VP8LTransformColorInverseFunc VP8LTransformColorInverse; + +VP8LConvertFunc VP8LConvertBGRAToRGB; +VP8LConvertFunc VP8LConvertBGRAToRGBA; +VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +VP8LConvertFunc VP8LConvertBGRAToRGB565; +VP8LConvertFunc VP8LConvertBGRAToBGR; + +VP8LMapARGBFunc VP8LMapColor32b; +VP8LMapAlphaFunc VP8LMapColor8b; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8LDspInitSSE2(void); +extern void VP8LDspInitSSE41(void); +extern void VP8LDspInitNEON(void); +extern void VP8LDspInitMIPSdspR2(void); +extern void VP8LDspInitMSA(void); + +#define COPY_PREDICTOR_ARRAY(IN, OUT) do { \ + (OUT)[0] = IN##0_C; \ + (OUT)[1] = IN##1_C; \ + (OUT)[2] = IN##2_C; \ + (OUT)[3] = IN##3_C; \ + (OUT)[4] = IN##4_C; \ + (OUT)[5] = IN##5_C; \ + (OUT)[6] = IN##6_C; \ + (OUT)[7] = IN##7_C; \ + (OUT)[8] = IN##8_C; \ + (OUT)[9] = IN##9_C; \ + (OUT)[10] = IN##10_C; \ + (OUT)[11] = IN##11_C; \ + (OUT)[12] = IN##12_C; \ + (OUT)[13] = IN##13_C; \ + (OUT)[14] = IN##0_C; /* <- padding security sentinels*/ \ + (OUT)[15] = IN##0_C; \ +} while (0); + +WEBP_DSP_INIT_FUNC(VP8LDspInit) { + COPY_PREDICTOR_ARRAY(VP8LPredictor, VP8LPredictors) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd_C) + +#if !WEBP_NEON_OMIT_C_CODE + VP8LAddGreenToBlueAndRed = VP8LAddGreenToBlueAndRed_C; + + VP8LTransformColorInverse = VP8LTransformColorInverse_C; + + VP8LConvertBGRAToRGBA = VP8LConvertBGRAToRGBA_C; + VP8LConvertBGRAToRGB = VP8LConvertBGRAToRGB_C; + VP8LConvertBGRAToBGR = VP8LConvertBGRAToBGR_C; +#endif + + VP8LConvertBGRAToRGBA4444 = VP8LConvertBGRAToRGBA4444_C; + VP8LConvertBGRAToRGB565 = VP8LConvertBGRAToRGB565_C; + + VP8LMapColor32b = MapARGB_C; + VP8LMapColor8b = MapAlpha_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8LDspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8LDspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8LDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8LDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8LDspInitNEON(); + } +#endif + + assert(VP8LAddGreenToBlueAndRed != NULL); + assert(VP8LTransformColorInverse != NULL); + assert(VP8LConvertBGRAToRGBA != NULL); + assert(VP8LConvertBGRAToRGB != NULL); + assert(VP8LConvertBGRAToBGR != NULL); + assert(VP8LConvertBGRAToRGBA4444 != NULL); + assert(VP8LConvertBGRAToRGB565 != NULL); + assert(VP8LMapColor32b != NULL); + assert(VP8LMapColor8b != NULL); +} +#undef COPY_PREDICTOR_ARRAY + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.h new file mode 100644 index 0000000000..0bf10a1a3d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless.h @@ -0,0 +1,259 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) + +#ifndef WEBP_DSP_LOSSLESS_H_ +#define WEBP_DSP_LOSSLESS_H_ + +#include "src/webp/types.h" +#include "src/webp/decode.h" + +#include "src/enc/histogram_enc.h" +#include "src/utils/utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Decoding + +typedef uint32_t (*VP8LPredictorFunc)(const uint32_t* const left, + const uint32_t* const top); +extern VP8LPredictorFunc VP8LPredictors[16]; + +uint32_t VP8LPredictor0_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor1_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor2_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor3_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor4_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor5_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor6_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor7_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor8_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor9_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor10_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor11_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor12_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor13_C(const uint32_t* const left, + const uint32_t* const top); + +// These Add/Sub function expects upper[-1] and out[-1] to be readable. +typedef void (*VP8LPredictorAddSubFunc)(const uint32_t* in, + const uint32_t* upper, int num_pixels, + uint32_t* out); +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; + +typedef void (*VP8LProcessDecBlueAndRedFunc)(const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; + +typedef struct { + // Note: the members are uint8_t, so that any negative values are + // automatically converted to "mod 256" values. + uint8_t green_to_red_; + uint8_t green_to_blue_; + uint8_t red_to_blue_; +} VP8LMultipliers; +typedef void (*VP8LTransformColorInverseFunc)(const VP8LMultipliers* const m, + const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LTransformColorInverseFunc VP8LTransformColorInverse; + +struct VP8LTransform; // Defined in dec/vp8li.h. + +// Performs inverse transform of data given transform information, start and end +// rows. Transform will be applied to rows [row_start, row_end[. +// The *in and *out pointers refer to source and destination data respectively +// corresponding to the intermediate row (row_start). +void VP8LInverseTransform(const struct VP8LTransform* const transform, + int row_start, int row_end, + const uint32_t* const in, uint32_t* const out); + +// Color space conversion. +typedef void (*VP8LConvertFunc)(const uint32_t* src, int num_pixels, + uint8_t* dst); +extern VP8LConvertFunc VP8LConvertBGRAToRGB; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +extern VP8LConvertFunc VP8LConvertBGRAToRGB565; +extern VP8LConvertFunc VP8LConvertBGRAToBGR; + +// Converts from BGRA to other color spaces. +void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, + WEBP_CSP_MODE out_colorspace, uint8_t* const rgba); + +typedef void (*VP8LMapARGBFunc)(const uint32_t* src, + const uint32_t* const color_map, + uint32_t* dst, int y_start, + int y_end, int width); +typedef void (*VP8LMapAlphaFunc)(const uint8_t* src, + const uint32_t* const color_map, + uint8_t* dst, int y_start, + int y_end, int width); + +extern VP8LMapARGBFunc VP8LMapColor32b; +extern VP8LMapAlphaFunc VP8LMapColor8b; + +// Similar to the static method ColorIndexInverseTransform() that is part of +// lossless.c, but used only for alpha decoding. It takes uint8_t (rather than +// uint32_t) arguments for 'src' and 'dst'. +void VP8LColorIndexInverseTransformAlpha( + const struct VP8LTransform* const transform, int y_start, int y_end, + const uint8_t* src, uint8_t* dst); + +// Expose some C-only fallback functions +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst); + +void VP8LConvertBGRAToRGB_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGBA_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* src, + int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToRGB565_C(const uint32_t* src, + int num_pixels, uint8_t* dst); +void VP8LConvertBGRAToBGR_C(const uint32_t* src, int num_pixels, uint8_t* dst); +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst); + +// Must be called before calling any of the above methods. +void VP8LDspInit(void); + +//------------------------------------------------------------------------------ +// Encoding + +typedef void (*VP8LProcessEncBlueAndRedFunc)(uint32_t* dst, int num_pixels); +extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed; +typedef void (*VP8LTransformColorFunc)(const VP8LMultipliers* const m, + uint32_t* dst, int num_pixels); +extern VP8LTransformColorFunc VP8LTransformColor; +typedef void (*VP8LCollectColorBlueTransformsFunc)( + const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, int histo[]); +extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms; + +typedef void (*VP8LCollectColorRedTransformsFunc)( + const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]); +extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms; + +// Expose some C-only fallback functions +void VP8LTransformColor_C(const VP8LMultipliers* const m, + uint32_t* data, int num_pixels); +void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels); +void VP8LCollectColorRedTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]); +void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]); + +extern VP8LPredictorAddSubFunc VP8LPredictorsSub[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16]; + +// ----------------------------------------------------------------------------- +// Huffman-cost related functions. + +typedef uint32_t (*VP8LCostFunc)(const uint32_t* population, int length); +typedef uint32_t (*VP8LCostCombinedFunc)(const uint32_t* X, const uint32_t* Y, + int length); +typedef float (*VP8LCombinedShannonEntropyFunc)(const int X[256], + const int Y[256]); + +extern VP8LCostFunc VP8LExtraCost; +extern VP8LCostCombinedFunc VP8LExtraCostCombined; +extern VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy; + +typedef struct { // small struct to hold counters + int counts[2]; // index: 0=zero streak, 1=non-zero streak + int streaks[2][2]; // [zero/non-zero][streak<3 / streak>=3] +} VP8LStreaks; + +typedef struct { // small struct to hold bit entropy results + float entropy; // entropy + uint32_t sum; // sum of the population + int nonzeros; // number of non-zero elements in the population + uint32_t max_val; // maximum value in the population + uint32_t nonzero_code; // index of the last non-zero in the population +} VP8LBitEntropy; + +void VP8LBitEntropyInit(VP8LBitEntropy* const entropy); + +// Get the combined symbol bit entropy and Huffman cost stats for the +// distributions 'X' and 'Y'. Those results can then be refined according to +// codec specific heuristics. +typedef void (*VP8LGetCombinedEntropyUnrefinedFunc)( + const uint32_t X[], const uint32_t Y[], int length, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats); +extern VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined; + +// Get the entropy for the distribution 'X'. +typedef void (*VP8LGetEntropyUnrefinedFunc)(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats); +extern VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined; + +void VP8LBitsEntropyUnrefined(const uint32_t* const array, int n, + VP8LBitEntropy* const entropy); + +typedef void (*VP8LAddVectorFunc)(const uint32_t* a, const uint32_t* b, + uint32_t* out, int size); +extern VP8LAddVectorFunc VP8LAddVector; +typedef void (*VP8LAddVectorEqFunc)(const uint32_t* a, uint32_t* out, int size); +extern VP8LAddVectorEqFunc VP8LAddVectorEq; +void VP8LHistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, + VP8LHistogram* const out); + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +typedef int (*VP8LVectorMismatchFunc)(const uint32_t* const array1, + const uint32_t* const array2, int length); +// Returns the first index where array1 and array2 are different. +extern VP8LVectorMismatchFunc VP8LVectorMismatch; + +typedef void (*VP8LBundleColorMapFunc)(const uint8_t* const row, int width, + int xbits, uint32_t* dst); +extern VP8LBundleColorMapFunc VP8LBundleColorMap; +void VP8LBundleColorMap_C(const uint8_t* const row, int width, int xbits, + uint32_t* dst); + +// Must be called before calling any of the above methods. +void VP8LEncDspInit(void); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_LOSSLESS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_common.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_common.h new file mode 100644 index 0000000000..d6139b2b57 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_common.h @@ -0,0 +1,191 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_LOSSLESS_COMMON_H_ +#define WEBP_DSP_LOSSLESS_COMMON_H_ + +#include "src/dsp/cpu.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Decoding + +// color mapping related functions. +static WEBP_INLINE uint32_t VP8GetARGBIndex(uint32_t idx) { + return (idx >> 8) & 0xff; +} + +static WEBP_INLINE uint8_t VP8GetAlphaIndex(uint8_t idx) { + return idx; +} + +static WEBP_INLINE uint32_t VP8GetARGBValue(uint32_t val) { + return val; +} + +static WEBP_INLINE uint8_t VP8GetAlphaValue(uint32_t val) { + return (val >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ +// Misc methods. + +// Computes sampled size of 'size' when sampling using 'sampling bits'. +static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size, + uint32_t sampling_bits) { + return (size + (1 << sampling_bits) - 1) >> sampling_bits; +} + +// Converts near lossless quality into max number of bits shaved off. +static WEBP_INLINE int VP8LNearLosslessBits(int near_lossless_quality) { + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 + return 5 - near_lossless_quality / 20; +} + +// ----------------------------------------------------------------------------- +// Faster logarithm for integers. Small values use a look-up table. + +// The threshold till approximate version of log_2 can be used. +// Practically, we can get rid of the call to log() as the two values match to +// very high degree (the ratio of these two is 0.99999x). +// Keeping a high threshold for now. +#define APPROX_LOG_WITH_CORRECTION_MAX 65536 +#define APPROX_LOG_MAX 4096 +#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086 +#define LOG_LOOKUP_IDX_MAX 256 +extern const float kLog2Table[LOG_LOOKUP_IDX_MAX]; +extern const float kSLog2Table[LOG_LOOKUP_IDX_MAX]; +typedef float (*VP8LFastLog2SlowFunc)(uint32_t v); + +extern VP8LFastLog2SlowFunc VP8LFastLog2Slow; +extern VP8LFastLog2SlowFunc VP8LFastSLog2Slow; + +static WEBP_INLINE float VP8LFastLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v); +} +// Fast calculation of v * log2(v) for integer input. +static WEBP_INLINE float VP8LFastSLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v); +} + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +// Splitting of distance and length codes into prefixes and +// extra bits. The prefixes are encoded with an entropy code +// while the extra bits are stored just as normal bits. +static WEBP_INLINE void VP8LPrefixEncodeBitsNoLUT(int distance, int* const code, + int* const extra_bits) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *code = 2 * highest_bit + second_highest_bit; +} + +static WEBP_INLINE void VP8LPrefixEncodeNoLUT(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *extra_bits_value = distance & ((1 << *extra_bits) - 1); + *code = 2 * highest_bit + second_highest_bit; +} + +#define PREFIX_LOOKUP_IDX_MAX 512 +typedef struct { + int8_t code_; + int8_t extra_bits_; +} VP8LPrefixCode; + +// These tables are derived using VP8LPrefixEncodeNoLUT. +extern const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX]; +extern const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX]; +static WEBP_INLINE void VP8LPrefixEncodeBits(int distance, int* const code, + int* const extra_bits) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code_; + *extra_bits = prefix_code.extra_bits_; + } else { + VP8LPrefixEncodeBitsNoLUT(distance, code, extra_bits); + } +} + +static WEBP_INLINE void VP8LPrefixEncode(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code_; + *extra_bits = prefix_code.extra_bits_; + *extra_bits_value = kPrefixEncodeExtraBitsValue[distance]; + } else { + VP8LPrefixEncodeNoLUT(distance, code, extra_bits, extra_bits_value); + } +} + +// Sum of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LAddPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = (a & 0xff00ff00u) + (b & 0xff00ff00u); + const uint32_t red_and_blue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +// Difference of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LSubPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = + 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + const uint32_t red_and_blue = + 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +//------------------------------------------------------------------------------ +// Transform-related functions used in both encoding and decoding. + +// Macros used to create a batch predictor that iteratively uses a +// one-pixel predictor. + +// The predictor is added to the output pixel (which +// is therefore considered as a residual) to get the final prediction. +#define GENERATE_PREDICTOR_ADD(PREDICTOR, PREDICTOR_ADD) \ +static void PREDICTOR_ADD(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int x; \ + assert(upper != NULL); \ + for (x = 0; x < num_pixels; ++x) { \ + const uint32_t pred = (PREDICTOR)(&out[x - 1], upper + x); \ + out[x] = VP8LAddPixels(in[x], pred); \ + } \ +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_LOSSLESS_COMMON_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc.c new file mode 100644 index 0000000000..997d56c2ad --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc.c @@ -0,0 +1,954 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transform methods for lossless encoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#include "src/dsp/dsp.h" + +#include +#include +#include +#include "src/dec/vp8li_dec.h" +#include "src/utils/endian_inl_utils.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" +#include "src/dsp/yuv.h" + +// lookup table for small values of log2(int) +const float kLog2Table[LOG_LOOKUP_IDX_MAX] = { + 0.0000000000000000f, 0.0000000000000000f, + 1.0000000000000000f, 1.5849625007211560f, + 2.0000000000000000f, 2.3219280948873621f, + 2.5849625007211560f, 2.8073549220576041f, + 3.0000000000000000f, 3.1699250014423121f, + 3.3219280948873621f, 3.4594316186372973f, + 3.5849625007211560f, 3.7004397181410921f, + 3.8073549220576041f, 3.9068905956085187f, + 4.0000000000000000f, 4.0874628412503390f, + 4.1699250014423121f, 4.2479275134435852f, + 4.3219280948873626f, 4.3923174227787606f, + 4.4594316186372973f, 4.5235619560570130f, + 4.5849625007211560f, 4.6438561897747243f, + 4.7004397181410917f, 4.7548875021634682f, + 4.8073549220576037f, 4.8579809951275718f, + 4.9068905956085187f, 4.9541963103868749f, + 5.0000000000000000f, 5.0443941193584533f, + 5.0874628412503390f, 5.1292830169449663f, + 5.1699250014423121f, 5.2094533656289501f, + 5.2479275134435852f, 5.2854022188622487f, + 5.3219280948873626f, 5.3575520046180837f, + 5.3923174227787606f, 5.4262647547020979f, + 5.4594316186372973f, 5.4918530963296747f, + 5.5235619560570130f, 5.5545888516776376f, + 5.5849625007211560f, 5.6147098441152083f, + 5.6438561897747243f, 5.6724253419714951f, + 5.7004397181410917f, 5.7279204545631987f, + 5.7548875021634682f, 5.7813597135246599f, + 5.8073549220576037f, 5.8328900141647412f, + 5.8579809951275718f, 5.8826430493618415f, + 5.9068905956085187f, 5.9307373375628866f, + 5.9541963103868749f, 5.9772799234999167f, + 6.0000000000000000f, 6.0223678130284543f, + 6.0443941193584533f, 6.0660891904577720f, + 6.0874628412503390f, 6.1085244567781691f, + 6.1292830169449663f, 6.1497471195046822f, + 6.1699250014423121f, 6.1898245588800175f, + 6.2094533656289501f, 6.2288186904958804f, + 6.2479275134435852f, 6.2667865406949010f, + 6.2854022188622487f, 6.3037807481771030f, + 6.3219280948873626f, 6.3398500028846243f, + 6.3575520046180837f, 6.3750394313469245f, + 6.3923174227787606f, 6.4093909361377017f, + 6.4262647547020979f, 6.4429434958487279f, + 6.4594316186372973f, 6.4757334309663976f, + 6.4918530963296747f, 6.5077946401986963f, + 6.5235619560570130f, 6.5391588111080309f, + 6.5545888516776376f, 6.5698556083309478f, + 6.5849625007211560f, 6.5999128421871278f, + 6.6147098441152083f, 6.6293566200796094f, + 6.6438561897747243f, 6.6582114827517946f, + 6.6724253419714951f, 6.6865005271832185f, + 6.7004397181410917f, 6.7142455176661224f, + 6.7279204545631987f, 6.7414669864011464f, + 6.7548875021634682f, 6.7681843247769259f, + 6.7813597135246599f, 6.7944158663501061f, + 6.8073549220576037f, 6.8201789624151878f, + 6.8328900141647412f, 6.8454900509443747f, + 6.8579809951275718f, 6.8703647195834047f, + 6.8826430493618415f, 6.8948177633079437f, + 6.9068905956085187f, 6.9188632372745946f, + 6.9307373375628866f, 6.9425145053392398f, + 6.9541963103868749f, 6.9657842846620869f, + 6.9772799234999167f, 6.9886846867721654f, + 7.0000000000000000f, 7.0112272554232539f, + 7.0223678130284543f, 7.0334230015374501f, + 7.0443941193584533f, 7.0552824355011898f, + 7.0660891904577720f, 7.0768155970508308f, + 7.0874628412503390f, 7.0980320829605263f, + 7.1085244567781691f, 7.1189410727235076f, + 7.1292830169449663f, 7.1395513523987936f, + 7.1497471195046822f, 7.1598713367783890f, + 7.1699250014423121f, 7.1799090900149344f, + 7.1898245588800175f, 7.1996723448363644f, + 7.2094533656289501f, 7.2191685204621611f, + 7.2288186904958804f, 7.2384047393250785f, + 7.2479275134435852f, 7.2573878426926521f, + 7.2667865406949010f, 7.2761244052742375f, + 7.2854022188622487f, 7.2946207488916270f, + 7.3037807481771030f, 7.3128829552843557f, + 7.3219280948873626f, 7.3309168781146167f, + 7.3398500028846243f, 7.3487281542310771f, + 7.3575520046180837f, 7.3663222142458160f, + 7.3750394313469245f, 7.3837042924740519f, + 7.3923174227787606f, 7.4008794362821843f, + 7.4093909361377017f, 7.4178525148858982f, + 7.4262647547020979f, 7.4346282276367245f, + 7.4429434958487279f, 7.4512111118323289f, + 7.4594316186372973f, 7.4676055500829976f, + 7.4757334309663976f, 7.4838157772642563f, + 7.4918530963296747f, 7.4998458870832056f, + 7.5077946401986963f, 7.5156998382840427f, + 7.5235619560570130f, 7.5313814605163118f, + 7.5391588111080309f, 7.5468944598876364f, + 7.5545888516776376f, 7.5622424242210728f, + 7.5698556083309478f, 7.5774288280357486f, + 7.5849625007211560f, 7.5924570372680806f, + 7.5999128421871278f, 7.6073303137496104f, + 7.6147098441152083f, 7.6220518194563764f, + 7.6293566200796094f, 7.6366246205436487f, + 7.6438561897747243f, 7.6510516911789281f, + 7.6582114827517946f, 7.6653359171851764f, + 7.6724253419714951f, 7.6794800995054464f, + 7.6865005271832185f, 7.6934869574993252f, + 7.7004397181410917f, 7.7073591320808825f, + 7.7142455176661224f, 7.7210991887071855f, + 7.7279204545631987f, 7.7347096202258383f, + 7.7414669864011464f, 7.7481928495894605f, + 7.7548875021634682f, 7.7615512324444795f, + 7.7681843247769259f, 7.7747870596011736f, + 7.7813597135246599f, 7.7879025593914317f, + 7.7944158663501061f, 7.8008998999203047f, + 7.8073549220576037f, 7.8137811912170374f, + 7.8201789624151878f, 7.8265484872909150f, + 7.8328900141647412f, 7.8392037880969436f, + 7.8454900509443747f, 7.8517490414160571f, + 7.8579809951275718f, 7.8641861446542797f, + 7.8703647195834047f, 7.8765169465649993f, + 7.8826430493618415f, 7.8887432488982591f, + 7.8948177633079437f, 7.9008668079807486f, + 7.9068905956085187f, 7.9128893362299619f, + 7.9188632372745946f, 7.9248125036057812f, + 7.9307373375628866f, 7.9366379390025709f, + 7.9425145053392398f, 7.9483672315846778f, + 7.9541963103868749f, 7.9600019320680805f, + 7.9657842846620869f, 7.9715435539507719f, + 7.9772799234999167f, 7.9829935746943103f, + 7.9886846867721654f, 7.9943534368588577f +}; + +const float kSLog2Table[LOG_LOOKUP_IDX_MAX] = { + 0.00000000f, 0.00000000f, 2.00000000f, 4.75488750f, + 8.00000000f, 11.60964047f, 15.50977500f, 19.65148445f, + 24.00000000f, 28.52932501f, 33.21928095f, 38.05374781f, + 43.01955001f, 48.10571634f, 53.30296891f, 58.60335893f, + 64.00000000f, 69.48686830f, 75.05865003f, 80.71062276f, + 86.43856190f, 92.23866588f, 98.10749561f, 104.04192499f, + 110.03910002f, 116.09640474f, 122.21143267f, 128.38196256f, + 134.60593782f, 140.88144886f, 147.20671787f, 153.58008562f, + 160.00000000f, 166.46500594f, 172.97373660f, 179.52490559f, + 186.11730005f, 192.74977453f, 199.42124551f, 206.13068654f, + 212.87712380f, 219.65963219f, 226.47733176f, 233.32938445f, + 240.21499122f, 247.13338933f, 254.08384998f, 261.06567603f, + 268.07820003f, 275.12078236f, 282.19280949f, 289.29369244f, + 296.42286534f, 303.57978409f, 310.76392512f, 317.97478424f, + 325.21187564f, 332.47473081f, 339.76289772f, 347.07593991f, + 354.41343574f, 361.77497759f, 369.16017124f, 376.56863518f, + 384.00000000f, 391.45390785f, 398.93001188f, 406.42797576f, + 413.94747321f, 421.48818752f, 429.04981119f, 436.63204548f, + 444.23460010f, 451.85719280f, 459.49954906f, 467.16140179f, + 474.84249102f, 482.54256363f, 490.26137307f, 497.99867911f, + 505.75424759f, 513.52785023f, 521.31926438f, 529.12827280f, + 536.95466351f, 544.79822957f, 552.65876890f, 560.53608414f, + 568.42998244f, 576.34027536f, 584.26677867f, 592.20931226f, + 600.16769996f, 608.14176943f, 616.13135206f, 624.13628279f, + 632.15640007f, 640.19154569f, 648.24156472f, 656.30630539f, + 664.38561898f, 672.47935976f, 680.58738488f, 688.70955430f, + 696.84573069f, 704.99577935f, 713.15956818f, 721.33696754f, + 729.52785023f, 737.73209140f, 745.94956849f, 754.18016116f, + 762.42375127f, 770.68022275f, 778.94946161f, 787.23135586f, + 795.52579543f, 803.83267219f, 812.15187982f, 820.48331383f, + 828.82687147f, 837.18245171f, 845.54995518f, 853.92928416f, + 862.32034249f, 870.72303558f, 879.13727036f, 887.56295522f, + 896.00000000f, 904.44831595f, 912.90781569f, 921.37841320f, + 929.86002376f, 938.35256392f, 946.85595152f, 955.37010560f, + 963.89494641f, 972.43039537f, 980.97637504f, 989.53280911f, + 998.09962237f, 1006.67674069f, 1015.26409097f, 1023.86160116f, + 1032.46920021f, 1041.08681805f, 1049.71438560f, 1058.35183469f, + 1066.99909811f, 1075.65610955f, 1084.32280357f, 1092.99911564f, + 1101.68498204f, 1110.38033993f, 1119.08512727f, 1127.79928282f, + 1136.52274614f, 1145.25545758f, 1153.99735821f, 1162.74838989f, + 1171.50849518f, 1180.27761738f, 1189.05570047f, 1197.84268914f, + 1206.63852876f, 1215.44316535f, 1224.25654560f, 1233.07861684f, + 1241.90932703f, 1250.74862473f, 1259.59645914f, 1268.45278005f, + 1277.31753781f, 1286.19068338f, 1295.07216828f, 1303.96194457f, + 1312.85996488f, 1321.76618236f, 1330.68055071f, 1339.60302413f, + 1348.53355734f, 1357.47210556f, 1366.41862452f, 1375.37307041f, + 1384.33539991f, 1393.30557020f, 1402.28353887f, 1411.26926400f, + 1420.26270412f, 1429.26381818f, 1438.27256558f, 1447.28890615f, + 1456.31280014f, 1465.34420819f, 1474.38309138f, 1483.42941118f, + 1492.48312945f, 1501.54420843f, 1510.61261078f, 1519.68829949f, + 1528.77123795f, 1537.86138993f, 1546.95871952f, 1556.06319119f, + 1565.17476976f, 1574.29342040f, 1583.41910860f, 1592.55180020f, + 1601.69146137f, 1610.83805860f, 1619.99155871f, 1629.15192882f, + 1638.31913637f, 1647.49314911f, 1656.67393509f, 1665.86146266f, + 1675.05570047f, 1684.25661744f, 1693.46418280f, 1702.67836605f, + 1711.89913698f, 1721.12646563f, 1730.36032233f, 1739.60067768f, + 1748.84750254f, 1758.10076802f, 1767.36044551f, 1776.62650662f, + 1785.89892323f, 1795.17766747f, 1804.46271172f, 1813.75402857f, + 1823.05159087f, 1832.35537170f, 1841.66534438f, 1850.98148244f, + 1860.30375965f, 1869.63214999f, 1878.96662767f, 1888.30716711f, + 1897.65374295f, 1907.00633003f, 1916.36490342f, 1925.72943838f, + 1935.09991037f, 1944.47629506f, 1953.85856831f, 1963.24670620f, + 1972.64068498f, 1982.04048108f, 1991.44607117f, 2000.85743204f, + 2010.27454072f, 2019.69737440f, 2029.12591044f, 2038.56012640f +}; + +const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX] = { + { 0, 0}, { 0, 0}, { 1, 0}, { 2, 0}, { 3, 0}, { 4, 1}, { 4, 1}, { 5, 1}, + { 5, 1}, { 6, 2}, { 6, 2}, { 6, 2}, { 6, 2}, { 7, 2}, { 7, 2}, { 7, 2}, + { 7, 2}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, { 8, 3}, + { 8, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, { 9, 3}, + { 9, 3}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, + {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, {10, 4}, + {10, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, + {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, {11, 4}, + {11, 4}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, {12, 5}, + {12, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, {13, 5}, + {13, 5}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, {14, 6}, + {14, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, {15, 6}, + {15, 6}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, {16, 7}, + {16, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, + {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, {17, 7}, +}; + +const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX] = { + 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, + 127, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126 +}; + +static float FastSLog2Slow_C(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { +#if !defined(WEBP_HAVE_SLOW_CLZ_CTZ) + // use clz if available + const int log_cnt = BitsLog2Floor(v) - 7; + const uint32_t y = 1 << log_cnt; + int correction = 0; + const float v_f = (float)v; + const uint32_t orig_v = v; + v >>= log_cnt; +#else + int log_cnt = 0; + uint32_t y = 1; + int correction = 0; + const float v_f = (float)v; + const uint32_t orig_v = v; + do { + ++log_cnt; + v = v >> 1; + y = y << 1; + } while (v >= LOG_LOOKUP_IDX_MAX); +#endif + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + correction = (23 * (orig_v & (y - 1))) >> 4; + return v_f * (kLog2Table[v] + log_cnt) + correction; + } else { + return (float)(LOG_2_RECIPROCAL * v * log((double)v)); + } +} + +static float FastLog2Slow_C(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { +#if !defined(WEBP_HAVE_SLOW_CLZ_CTZ) + // use clz if available + const int log_cnt = BitsLog2Floor(v) - 7; + const uint32_t y = 1 << log_cnt; + const uint32_t orig_v = v; + double log_2; + v >>= log_cnt; +#else + int log_cnt = 0; + uint32_t y = 1; + const uint32_t orig_v = v; + double log_2; + do { + ++log_cnt; + v = v >> 1; + y = y << 1; + } while (v >= LOG_LOOKUP_IDX_MAX); +#endif + log_2 = kLog2Table[v] + log_cnt; + if (orig_v >= APPROX_LOG_MAX) { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + const int correction = (23 * (orig_v & (y - 1))) >> 4; + log_2 += (double)correction / orig_v; + } + return (float)log_2; + } else { + return (float)(LOG_2_RECIPROCAL * log((double)v)); + } +} + +//------------------------------------------------------------------------------ +// Methods to calculate Entropy (Shannon). + +// Compute the combined Shanon's entropy for distribution {X} and {X+Y} +static float CombinedShannonEntropy_C(const int X[256], const int Y[256]) { + int i; + float retval = 0.f; + int sumX = 0, sumXY = 0; + for (i = 0; i < 256; ++i) { + const int x = X[i]; + if (x != 0) { + const int xy = x + Y[i]; + sumX += x; + retval -= VP8LFastSLog2(x); + sumXY += xy; + retval -= VP8LFastSLog2(xy); + } else if (Y[i] != 0) { + sumXY += Y[i]; + retval -= VP8LFastSLog2(Y[i]); + } + } + retval += VP8LFastSLog2(sumX) + VP8LFastSLog2(sumXY); + return retval; +} + +void VP8LBitEntropyInit(VP8LBitEntropy* const entropy) { + entropy->entropy = 0.; + entropy->sum = 0; + entropy->nonzeros = 0; + entropy->max_val = 0; + entropy->nonzero_code = VP8L_NON_TRIVIAL_SYM; +} + +void VP8LBitsEntropyUnrefined(const uint32_t* const array, int n, + VP8LBitEntropy* const entropy) { + int i; + + VP8LBitEntropyInit(entropy); + + for (i = 0; i < n; ++i) { + if (array[i] != 0) { + entropy->sum += array[i]; + entropy->nonzero_code = i; + ++entropy->nonzeros; + entropy->entropy -= VP8LFastSLog2(array[i]); + if (entropy->max_val < array[i]) { + entropy->max_val = array[i]; + } + } + } + entropy->entropy += VP8LFastSLog2(entropy->sum); +} + +static WEBP_INLINE void GetEntropyUnrefinedHelper( + uint32_t val, int i, uint32_t* const val_prev, int* const i_prev, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats) { + const int streak = i - *i_prev; + + // Gather info for the bit entropy. + if (*val_prev != 0) { + bit_entropy->sum += (*val_prev) * streak; + bit_entropy->nonzeros += streak; + bit_entropy->nonzero_code = *i_prev; + bit_entropy->entropy -= VP8LFastSLog2(*val_prev) * streak; + if (bit_entropy->max_val < *val_prev) { + bit_entropy->max_val = *val_prev; + } + } + + // Gather info for the Huffman cost. + stats->counts[*val_prev != 0] += (streak > 3); + stats->streaks[*val_prev != 0][(streak > 3)] += streak; + + *val_prev = val; + *i_prev = i; +} + +static void GetEntropyUnrefined_C(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i; + int i_prev = 0; + uint32_t x_prev = X[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t x = X[i]; + if (x != x_prev) { + GetEntropyUnrefinedHelper(x, i, &x_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &x_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +static void GetCombinedEntropyUnrefined_C(const uint32_t X[], + const uint32_t Y[], + int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i = 1; + int i_prev = 0; + uint32_t xy_prev = X[0] + Y[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t xy = X[i] + Y[i]; + if (xy != xy_prev) { + GetEntropyUnrefinedHelper(xy, i, &xy_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &xy_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +//------------------------------------------------------------------------------ + +void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const int argb = (int)argb_data[i]; + const int green = (argb >> 8) & 0xff; + const uint32_t new_r = (((argb >> 16) & 0xff) - green) & 0xff; + const uint32_t new_b = (((argb >> 0) & 0xff) - green) & 0xff; + argb_data[i] = ((uint32_t)argb & 0xff00ff00u) | (new_r << 16) | new_b; + } +} + +static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, int8_t color) { + return ((int)color_pred * color) >> 5; +} + +static WEBP_INLINE int8_t U32ToS8(uint32_t v) { + return (int8_t)(v & 0xff); +} + +void VP8LTransformColor_C(const VP8LMultipliers* const m, uint32_t* data, + int num_pixels) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = data[i]; + const int8_t green = U32ToS8(argb >> 8); + const int8_t red = U32ToS8(argb >> 16); + int new_red = red & 0xff; + int new_blue = argb & 0xff; + new_red -= ColorTransformDelta((int8_t)m->green_to_red_, green); + new_red &= 0xff; + new_blue -= ColorTransformDelta((int8_t)m->green_to_blue_, green); + new_blue -= ColorTransformDelta((int8_t)m->red_to_blue_, red); + new_blue &= 0xff; + data[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, + uint32_t argb) { + const int8_t green = U32ToS8(argb >> 8); + int new_red = argb >> 16; + new_red -= ColorTransformDelta((int8_t)green_to_red, green); + return (new_red & 0xff); +} + +static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, + uint8_t red_to_blue, + uint32_t argb) { + const int8_t green = U32ToS8(argb >> 8); + const int8_t red = U32ToS8(argb >> 16); + int new_blue = argb & 0xff; + new_blue -= ColorTransformDelta((int8_t)green_to_blue, green); + new_blue -= ColorTransformDelta((int8_t)red_to_blue, red); + return (new_blue & 0xff); +} + +void VP8LCollectColorRedTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + while (tile_height-- > 0) { + int x; + for (x = 0; x < tile_width; ++x) { + ++histo[TransformColorRed((uint8_t)green_to_red, argb[x])]; + } + argb += stride; + } +} + +void VP8LCollectColorBlueTransforms_C(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + while (tile_height-- > 0) { + int x; + for (x = 0; x < tile_width; ++x) { + ++histo[TransformColorBlue((uint8_t)green_to_blue, (uint8_t)red_to_blue, + argb[x])]; + } + argb += stride; + } +} + +//------------------------------------------------------------------------------ + +static int VectorMismatch_C(const uint32_t* const array1, + const uint32_t* const array2, int length) { + int match_len = 0; + + while (match_len < length && array1[match_len] == array2[match_len]) { + ++match_len; + } + return match_len; +} + +// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. +void VP8LBundleColorMap_C(const uint8_t* const row, int width, int xbits, + uint32_t* dst) { + int x; + if (xbits > 0) { + const int bit_depth = 1 << (3 - xbits); + const int mask = (1 << xbits) - 1; + uint32_t code = 0xff000000; + for (x = 0; x < width; ++x) { + const int xsub = x & mask; + if (xsub == 0) { + code = 0xff000000; + } + code |= row[x] << (8 + bit_depth * xsub); + dst[x >> xbits] = code; + } + } else { + for (x = 0; x < width; ++x) dst[x] = 0xff000000 | (row[x] << 8); + } +} + +//------------------------------------------------------------------------------ + +static uint32_t ExtraCost_C(const uint32_t* population, int length) { + int i; + uint32_t cost = population[4] + population[5]; + assert(length % 2 == 0); + for (i = 2; i < length / 2 - 1; ++i) { + cost += i * (population[2 * i + 2] + population[2 * i + 3]); + } + return cost; +} + +static uint32_t ExtraCostCombined_C(const uint32_t* X, const uint32_t* Y, + int length) { + int i; + uint32_t cost = X[4] + Y[4] + X[5] + Y[5]; + assert(length % 2 == 0); + for (i = 2; i < length / 2 - 1; ++i) { + const int xy0 = X[2 * i + 2] + Y[2 * i + 2]; + const int xy1 = X[2 * i + 3] + Y[2 * i + 3]; + cost += i * (xy0 + xy1); + } + return cost; +} + +//------------------------------------------------------------------------------ + +static void AddVector_C(const uint32_t* a, const uint32_t* b, uint32_t* out, + int size) { + int i; + for (i = 0; i < size; ++i) out[i] = a[i] + b[i]; +} + +static void AddVectorEq_C(const uint32_t* a, uint32_t* out, int size) { + int i; + for (i = 0; i < size; ++i) out[i] += a[i]; +} + +#define ADD(X, ARG, LEN) do { \ + if (a->is_used_[X]) { \ + if (b->is_used_[X]) { \ + VP8LAddVector(a->ARG, b->ARG, out->ARG, (LEN)); \ + } else { \ + memcpy(&out->ARG[0], &a->ARG[0], (LEN) * sizeof(out->ARG[0])); \ + } \ + } else if (b->is_used_[X]) { \ + memcpy(&out->ARG[0], &b->ARG[0], (LEN) * sizeof(out->ARG[0])); \ + } else { \ + memset(&out->ARG[0], 0, (LEN) * sizeof(out->ARG[0])); \ + } \ +} while (0) + +#define ADD_EQ(X, ARG, LEN) do { \ + if (a->is_used_[X]) { \ + if (out->is_used_[X]) { \ + VP8LAddVectorEq(a->ARG, out->ARG, (LEN)); \ + } else { \ + memcpy(&out->ARG[0], &a->ARG[0], (LEN) * sizeof(out->ARG[0])); \ + } \ + } \ +} while (0) + +void VP8LHistogramAdd(const VP8LHistogram* const a, + const VP8LHistogram* const b, VP8LHistogram* const out) { + int i; + const int literal_size = VP8LHistogramNumCodes(a->palette_code_bits_); + assert(a->palette_code_bits_ == b->palette_code_bits_); + + if (b != out) { + ADD(0, literal_, literal_size); + ADD(1, red_, NUM_LITERAL_CODES); + ADD(2, blue_, NUM_LITERAL_CODES); + ADD(3, alpha_, NUM_LITERAL_CODES); + ADD(4, distance_, NUM_DISTANCE_CODES); + for (i = 0; i < 5; ++i) { + out->is_used_[i] = (a->is_used_[i] | b->is_used_[i]); + } + } else { + ADD_EQ(0, literal_, literal_size); + ADD_EQ(1, red_, NUM_LITERAL_CODES); + ADD_EQ(2, blue_, NUM_LITERAL_CODES); + ADD_EQ(3, alpha_, NUM_LITERAL_CODES); + ADD_EQ(4, distance_, NUM_DISTANCE_CODES); + for (i = 0; i < 5; ++i) out->is_used_[i] |= a->is_used_[i]; + } +} +#undef ADD +#undef ADD_EQ + +//------------------------------------------------------------------------------ +// Image transforms. + +static void PredictorSub0_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i < num_pixels; ++i) out[i] = VP8LSubPixels(in[i], ARGB_BLACK); + (void)upper; +} + +static void PredictorSub1_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i < num_pixels; ++i) out[i] = VP8LSubPixels(in[i], in[i - 1]); + (void)upper; +} + +// It subtracts the prediction from the input pixel and stores the residual +// in the output pixel. +#define GENERATE_PREDICTOR_SUB(PREDICTOR_I) \ +static void PredictorSub##PREDICTOR_I##_C(const uint32_t* in, \ + const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int x; \ + assert(upper != NULL); \ + for (x = 0; x < num_pixels; ++x) { \ + const uint32_t pred = \ + VP8LPredictor##PREDICTOR_I##_C(&in[x - 1], upper + x); \ + out[x] = VP8LSubPixels(in[x], pred); \ + } \ +} + +GENERATE_PREDICTOR_SUB(2) +GENERATE_PREDICTOR_SUB(3) +GENERATE_PREDICTOR_SUB(4) +GENERATE_PREDICTOR_SUB(5) +GENERATE_PREDICTOR_SUB(6) +GENERATE_PREDICTOR_SUB(7) +GENERATE_PREDICTOR_SUB(8) +GENERATE_PREDICTOR_SUB(9) +GENERATE_PREDICTOR_SUB(10) +GENERATE_PREDICTOR_SUB(11) +GENERATE_PREDICTOR_SUB(12) +GENERATE_PREDICTOR_SUB(13) + +//------------------------------------------------------------------------------ + +VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed; + +VP8LTransformColorFunc VP8LTransformColor; + +VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms; +VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms; + +VP8LFastLog2SlowFunc VP8LFastLog2Slow; +VP8LFastLog2SlowFunc VP8LFastSLog2Slow; + +VP8LCostFunc VP8LExtraCost; +VP8LCostCombinedFunc VP8LExtraCostCombined; +VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy; + +VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined; +VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined; + +VP8LAddVectorFunc VP8LAddVector; +VP8LAddVectorEqFunc VP8LAddVectorEq; + +VP8LVectorMismatchFunc VP8LVectorMismatch; +VP8LBundleColorMapFunc VP8LBundleColorMap; + +VP8LPredictorAddSubFunc VP8LPredictorsSub[16]; +VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8LEncDspInitSSE2(void); +extern void VP8LEncDspInitSSE41(void); +extern void VP8LEncDspInitNEON(void); +extern void VP8LEncDspInitMIPS32(void); +extern void VP8LEncDspInitMIPSdspR2(void); +extern void VP8LEncDspInitMSA(void); + +WEBP_DSP_INIT_FUNC(VP8LEncDspInit) { + VP8LDspInit(); + +#if !WEBP_NEON_OMIT_C_CODE + VP8LSubtractGreenFromBlueAndRed = VP8LSubtractGreenFromBlueAndRed_C; + + VP8LTransformColor = VP8LTransformColor_C; +#endif + + VP8LCollectColorBlueTransforms = VP8LCollectColorBlueTransforms_C; + VP8LCollectColorRedTransforms = VP8LCollectColorRedTransforms_C; + + VP8LFastLog2Slow = FastLog2Slow_C; + VP8LFastSLog2Slow = FastSLog2Slow_C; + + VP8LExtraCost = ExtraCost_C; + VP8LExtraCostCombined = ExtraCostCombined_C; + VP8LCombinedShannonEntropy = CombinedShannonEntropy_C; + + VP8LGetEntropyUnrefined = GetEntropyUnrefined_C; + VP8LGetCombinedEntropyUnrefined = GetCombinedEntropyUnrefined_C; + + VP8LAddVector = AddVector_C; + VP8LAddVectorEq = AddVectorEq_C; + + VP8LVectorMismatch = VectorMismatch_C; + VP8LBundleColorMap = VP8LBundleColorMap_C; + + VP8LPredictorsSub[0] = PredictorSub0_C; + VP8LPredictorsSub[1] = PredictorSub1_C; + VP8LPredictorsSub[2] = PredictorSub2_C; + VP8LPredictorsSub[3] = PredictorSub3_C; + VP8LPredictorsSub[4] = PredictorSub4_C; + VP8LPredictorsSub[5] = PredictorSub5_C; + VP8LPredictorsSub[6] = PredictorSub6_C; + VP8LPredictorsSub[7] = PredictorSub7_C; + VP8LPredictorsSub[8] = PredictorSub8_C; + VP8LPredictorsSub[9] = PredictorSub9_C; + VP8LPredictorsSub[10] = PredictorSub10_C; + VP8LPredictorsSub[11] = PredictorSub11_C; + VP8LPredictorsSub[12] = PredictorSub12_C; + VP8LPredictorsSub[13] = PredictorSub13_C; + VP8LPredictorsSub[14] = PredictorSub0_C; // <- padding security sentinels + VP8LPredictorsSub[15] = PredictorSub0_C; + + VP8LPredictorsSub_C[0] = PredictorSub0_C; + VP8LPredictorsSub_C[1] = PredictorSub1_C; + VP8LPredictorsSub_C[2] = PredictorSub2_C; + VP8LPredictorsSub_C[3] = PredictorSub3_C; + VP8LPredictorsSub_C[4] = PredictorSub4_C; + VP8LPredictorsSub_C[5] = PredictorSub5_C; + VP8LPredictorsSub_C[6] = PredictorSub6_C; + VP8LPredictorsSub_C[7] = PredictorSub7_C; + VP8LPredictorsSub_C[8] = PredictorSub8_C; + VP8LPredictorsSub_C[9] = PredictorSub9_C; + VP8LPredictorsSub_C[10] = PredictorSub10_C; + VP8LPredictorsSub_C[11] = PredictorSub11_C; + VP8LPredictorsSub_C[12] = PredictorSub12_C; + VP8LPredictorsSub_C[13] = PredictorSub13_C; + VP8LPredictorsSub_C[14] = PredictorSub0_C; // <- padding security sentinels + VP8LPredictorsSub_C[15] = PredictorSub0_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8LEncDspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8LEncDspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8LEncDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8LEncDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8LEncDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8LEncDspInitNEON(); + } +#endif + + assert(VP8LSubtractGreenFromBlueAndRed != NULL); + assert(VP8LTransformColor != NULL); + assert(VP8LCollectColorBlueTransforms != NULL); + assert(VP8LCollectColorRedTransforms != NULL); + assert(VP8LFastLog2Slow != NULL); + assert(VP8LFastSLog2Slow != NULL); + assert(VP8LExtraCost != NULL); + assert(VP8LExtraCostCombined != NULL); + assert(VP8LCombinedShannonEntropy != NULL); + assert(VP8LGetEntropyUnrefined != NULL); + assert(VP8LGetCombinedEntropyUnrefined != NULL); + assert(VP8LAddVector != NULL); + assert(VP8LAddVectorEq != NULL); + assert(VP8LVectorMismatch != NULL); + assert(VP8LBundleColorMap != NULL); + assert(VP8LPredictorsSub[0] != NULL); + assert(VP8LPredictorsSub[1] != NULL); + assert(VP8LPredictorsSub[2] != NULL); + assert(VP8LPredictorsSub[3] != NULL); + assert(VP8LPredictorsSub[4] != NULL); + assert(VP8LPredictorsSub[5] != NULL); + assert(VP8LPredictorsSub[6] != NULL); + assert(VP8LPredictorsSub[7] != NULL); + assert(VP8LPredictorsSub[8] != NULL); + assert(VP8LPredictorsSub[9] != NULL); + assert(VP8LPredictorsSub[10] != NULL); + assert(VP8LPredictorsSub[11] != NULL); + assert(VP8LPredictorsSub[12] != NULL); + assert(VP8LPredictorsSub[13] != NULL); + assert(VP8LPredictorsSub[14] != NULL); + assert(VP8LPredictorsSub[15] != NULL); + assert(VP8LPredictorsSub_C[0] != NULL); + assert(VP8LPredictorsSub_C[1] != NULL); + assert(VP8LPredictorsSub_C[2] != NULL); + assert(VP8LPredictorsSub_C[3] != NULL); + assert(VP8LPredictorsSub_C[4] != NULL); + assert(VP8LPredictorsSub_C[5] != NULL); + assert(VP8LPredictorsSub_C[6] != NULL); + assert(VP8LPredictorsSub_C[7] != NULL); + assert(VP8LPredictorsSub_C[8] != NULL); + assert(VP8LPredictorsSub_C[9] != NULL); + assert(VP8LPredictorsSub_C[10] != NULL); + assert(VP8LPredictorsSub_C[11] != NULL); + assert(VP8LPredictorsSub_C[12] != NULL); + assert(VP8LPredictorsSub_C[13] != NULL); + assert(VP8LPredictorsSub_C[14] != NULL); + assert(VP8LPredictorsSub_C[15] != NULL); +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips32.c new file mode 100644 index 0000000000..e10f12da9d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips32.c @@ -0,0 +1,397 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of lossless functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" + +#if defined(WEBP_USE_MIPS32) + +#include +#include +#include +#include + +static float FastSLog2Slow_MIPS32(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + uint32_t log_cnt, y, correction; + const int c24 = 24; + const float v_f = (float)v; + uint32_t temp; + + // Xf = 256 = 2^8 + // log_cnt is index of leading one in upper 24 bits + __asm__ volatile( + "clz %[log_cnt], %[v] \n\t" + "addiu %[y], $zero, 1 \n\t" + "subu %[log_cnt], %[c24], %[log_cnt] \n\t" + "sllv %[y], %[y], %[log_cnt] \n\t" + "srlv %[temp], %[v], %[log_cnt] \n\t" + : [log_cnt]"=&r"(log_cnt), [y]"=&r"(y), + [temp]"=r"(temp) + : [c24]"r"(c24), [v]"r"(v) + ); + + // vf = (2^log_cnt) * Xf; where y = 2^log_cnt and Xf < 256 + // Xf = floor(Xf) * (1 + (v % y) / v) + // log2(Xf) = log2(floor(Xf)) + log2(1 + (v % y) / v) + // The correction factor: log(1 + d) ~ d; for very small d values, so + // log2(1 + (v % y) / v) ~ LOG_2_RECIPROCAL * (v % y)/v + // LOG_2_RECIPROCAL ~ 23/16 + + // (v % y) = (v % 2^log_cnt) = v & (2^log_cnt - 1) + correction = (23 * (v & (y - 1))) >> 4; + return v_f * (kLog2Table[temp] + log_cnt) + correction; + } else { + return (float)(LOG_2_RECIPROCAL * v * log((double)v)); + } +} + +static float FastLog2Slow_MIPS32(uint32_t v) { + assert(v >= LOG_LOOKUP_IDX_MAX); + if (v < APPROX_LOG_WITH_CORRECTION_MAX) { + uint32_t log_cnt, y; + const int c24 = 24; + double log_2; + uint32_t temp; + + __asm__ volatile( + "clz %[log_cnt], %[v] \n\t" + "addiu %[y], $zero, 1 \n\t" + "subu %[log_cnt], %[c24], %[log_cnt] \n\t" + "sllv %[y], %[y], %[log_cnt] \n\t" + "srlv %[temp], %[v], %[log_cnt] \n\t" + : [log_cnt]"=&r"(log_cnt), [y]"=&r"(y), + [temp]"=r"(temp) + : [c24]"r"(c24), [v]"r"(v) + ); + + log_2 = kLog2Table[temp] + log_cnt; + if (v >= APPROX_LOG_MAX) { + // Since the division is still expensive, add this correction factor only + // for large values of 'v'. + + const uint32_t correction = (23 * (v & (y - 1))) >> 4; + log_2 += (double)correction / v; + } + return (float)log_2; + } else { + return (float)(LOG_2_RECIPROCAL * log((double)v)); + } +} + +// C version of this function: +// int i = 0; +// int64_t cost = 0; +// const uint32_t* pop = &population[4]; +// const uint32_t* LoopEnd = &population[length]; +// while (pop != LoopEnd) { +// ++i; +// cost += i * *pop; +// cost += i * *(pop + 1); +// pop += 2; +// } +// return cost; +static uint32_t ExtraCost_MIPS32(const uint32_t* const population, int length) { + int i, temp0, temp1; + const uint32_t* pop = &population[4]; + const uint32_t* const LoopEnd = &population[length]; + + __asm__ volatile( + "mult $zero, $zero \n\t" + "xor %[i], %[i], %[i] \n\t" + "beq %[pop], %[LoopEnd], 2f \n\t" + "1: \n\t" + "lw %[temp0], 0(%[pop]) \n\t" + "lw %[temp1], 4(%[pop]) \n\t" + "addiu %[i], %[i], 1 \n\t" + "addiu %[pop], %[pop], 8 \n\t" + "madd %[i], %[temp0] \n\t" + "madd %[i], %[temp1] \n\t" + "bne %[pop], %[LoopEnd], 1b \n\t" + "2: \n\t" + "mfhi %[temp0] \n\t" + "mflo %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [i]"=&r"(i), [pop]"+r"(pop) + : [LoopEnd]"r"(LoopEnd) + : "memory", "hi", "lo" + ); + + return ((int64_t)temp0 << 32 | temp1); +} + +// C version of this function: +// int i = 0; +// int64_t cost = 0; +// const uint32_t* pX = &X[4]; +// const uint32_t* pY = &Y[4]; +// const uint32_t* LoopEnd = &X[length]; +// while (pX != LoopEnd) { +// const uint32_t xy0 = *pX + *pY; +// const uint32_t xy1 = *(pX + 1) + *(pY + 1); +// ++i; +// cost += i * xy0; +// cost += i * xy1; +// pX += 2; +// pY += 2; +// } +// return cost; +static uint32_t ExtraCostCombined_MIPS32(const uint32_t* const X, + const uint32_t* const Y, int length) { + int i, temp0, temp1, temp2, temp3; + const uint32_t* pX = &X[4]; + const uint32_t* pY = &Y[4]; + const uint32_t* const LoopEnd = &X[length]; + + __asm__ volatile( + "mult $zero, $zero \n\t" + "xor %[i], %[i], %[i] \n\t" + "beq %[pX], %[LoopEnd], 2f \n\t" + "1: \n\t" + "lw %[temp0], 0(%[pX]) \n\t" + "lw %[temp1], 0(%[pY]) \n\t" + "lw %[temp2], 4(%[pX]) \n\t" + "lw %[temp3], 4(%[pY]) \n\t" + "addiu %[i], %[i], 1 \n\t" + "addu %[temp0], %[temp0], %[temp1] \n\t" + "addu %[temp2], %[temp2], %[temp3] \n\t" + "addiu %[pX], %[pX], 8 \n\t" + "addiu %[pY], %[pY], 8 \n\t" + "madd %[i], %[temp0] \n\t" + "madd %[i], %[temp2] \n\t" + "bne %[pX], %[LoopEnd], 1b \n\t" + "2: \n\t" + "mfhi %[temp0] \n\t" + "mflo %[temp1] \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [i]"=&r"(i), [pX]"+r"(pX), [pY]"+r"(pY) + : [LoopEnd]"r"(LoopEnd) + : "memory", "hi", "lo" + ); + + return ((int64_t)temp0 << 32 | temp1); +} + +#define HUFFMAN_COST_PASS \ + __asm__ volatile( \ + "sll %[temp1], %[temp0], 3 \n\t" \ + "addiu %[temp3], %[streak], -3 \n\t" \ + "addu %[temp2], %[pstreaks], %[temp1] \n\t" \ + "blez %[temp3], 1f \n\t" \ + "srl %[temp1], %[temp1], 1 \n\t" \ + "addu %[temp3], %[pcnts], %[temp1] \n\t" \ + "lw %[temp0], 4(%[temp2]) \n\t" \ + "lw %[temp1], 0(%[temp3]) \n\t" \ + "addu %[temp0], %[temp0], %[streak] \n\t" \ + "addiu %[temp1], %[temp1], 1 \n\t" \ + "sw %[temp0], 4(%[temp2]) \n\t" \ + "sw %[temp1], 0(%[temp3]) \n\t" \ + "b 2f \n\t" \ + "1: \n\t" \ + "lw %[temp0], 0(%[temp2]) \n\t" \ + "addu %[temp0], %[temp0], %[streak] \n\t" \ + "sw %[temp0], 0(%[temp2]) \n\t" \ + "2: \n\t" \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp0]"+r"(temp0) \ + : [pstreaks]"r"(pstreaks), [pcnts]"r"(pcnts), \ + [streak]"r"(streak) \ + : "memory" \ + ); + +// Returns the various RLE counts +static WEBP_INLINE void GetEntropyUnrefinedHelper( + uint32_t val, int i, uint32_t* const val_prev, int* const i_prev, + VP8LBitEntropy* const bit_entropy, VP8LStreaks* const stats) { + int* const pstreaks = &stats->streaks[0][0]; + int* const pcnts = &stats->counts[0]; + int temp0, temp1, temp2, temp3; + const int streak = i - *i_prev; + + // Gather info for the bit entropy. + if (*val_prev != 0) { + bit_entropy->sum += (*val_prev) * streak; + bit_entropy->nonzeros += streak; + bit_entropy->nonzero_code = *i_prev; + bit_entropy->entropy -= VP8LFastSLog2(*val_prev) * streak; + if (bit_entropy->max_val < *val_prev) { + bit_entropy->max_val = *val_prev; + } + } + + // Gather info for the Huffman cost. + temp0 = (*val_prev != 0); + HUFFMAN_COST_PASS + + *val_prev = val; + *i_prev = i; +} + +static void GetEntropyUnrefined_MIPS32(const uint32_t X[], int length, + VP8LBitEntropy* const bit_entropy, + VP8LStreaks* const stats) { + int i; + int i_prev = 0; + uint32_t x_prev = X[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(bit_entropy); + + for (i = 1; i < length; ++i) { + const uint32_t x = X[i]; + if (x != x_prev) { + GetEntropyUnrefinedHelper(x, i, &x_prev, &i_prev, bit_entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &x_prev, &i_prev, bit_entropy, stats); + + bit_entropy->entropy += VP8LFastSLog2(bit_entropy->sum); +} + +static void GetCombinedEntropyUnrefined_MIPS32(const uint32_t X[], + const uint32_t Y[], + int length, + VP8LBitEntropy* const entropy, + VP8LStreaks* const stats) { + int i = 1; + int i_prev = 0; + uint32_t xy_prev = X[0] + Y[0]; + + memset(stats, 0, sizeof(*stats)); + VP8LBitEntropyInit(entropy); + + for (i = 1; i < length; ++i) { + const uint32_t xy = X[i] + Y[i]; + if (xy != xy_prev) { + GetEntropyUnrefinedHelper(xy, i, &xy_prev, &i_prev, entropy, stats); + } + } + GetEntropyUnrefinedHelper(0, i, &xy_prev, &i_prev, entropy, stats); + + entropy->entropy += VP8LFastSLog2(entropy->sum); +} + +#define ASM_START \ + __asm__ volatile( \ + ".set push \n\t" \ + ".set at \n\t" \ + ".set macro \n\t" \ + "1: \n\t" + +// P2 = P0 + P1 +// A..D - offsets +// E - temp variable to tell macro +// if pointer should be incremented +// literal_ and successive histograms could be unaligned +// so we must use ulw and usw +#define ADD_TO_OUT(A, B, C, D, E, P0, P1, P2) \ + "ulw %[temp0], " #A "(%[" #P0 "]) \n\t" \ + "ulw %[temp1], " #B "(%[" #P0 "]) \n\t" \ + "ulw %[temp2], " #C "(%[" #P0 "]) \n\t" \ + "ulw %[temp3], " #D "(%[" #P0 "]) \n\t" \ + "ulw %[temp4], " #A "(%[" #P1 "]) \n\t" \ + "ulw %[temp5], " #B "(%[" #P1 "]) \n\t" \ + "ulw %[temp6], " #C "(%[" #P1 "]) \n\t" \ + "ulw %[temp7], " #D "(%[" #P1 "]) \n\t" \ + "addu %[temp4], %[temp4], %[temp0] \n\t" \ + "addu %[temp5], %[temp5], %[temp1] \n\t" \ + "addu %[temp6], %[temp6], %[temp2] \n\t" \ + "addu %[temp7], %[temp7], %[temp3] \n\t" \ + "addiu %[" #P0 "], %[" #P0 "], 16 \n\t" \ + ".if " #E " == 1 \n\t" \ + "addiu %[" #P1 "], %[" #P1 "], 16 \n\t" \ + ".endif \n\t" \ + "usw %[temp4], " #A "(%[" #P2 "]) \n\t" \ + "usw %[temp5], " #B "(%[" #P2 "]) \n\t" \ + "usw %[temp6], " #C "(%[" #P2 "]) \n\t" \ + "usw %[temp7], " #D "(%[" #P2 "]) \n\t" \ + "addiu %[" #P2 "], %[" #P2 "], 16 \n\t" \ + "bne %[" #P0 "], %[LoopEnd], 1b \n\t" \ + ".set pop \n\t" \ + +#define ASM_END_COMMON_0 \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), \ + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), \ + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7), \ + [pa]"+r"(pa), [pout]"+r"(pout) + +#define ASM_END_COMMON_1 \ + : [LoopEnd]"r"(LoopEnd) \ + : "memory", "at" \ + ); + +#define ASM_END_0 \ + ASM_END_COMMON_0 \ + , [pb]"+r"(pb) \ + ASM_END_COMMON_1 + +#define ASM_END_1 \ + ASM_END_COMMON_0 \ + ASM_END_COMMON_1 + +static void AddVector_MIPS32(const uint32_t* pa, const uint32_t* pb, + uint32_t* pout, int size) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + const int end = ((size) / 4) * 4; + const uint32_t* const LoopEnd = pa + end; + int i; + ASM_START + ADD_TO_OUT(0, 4, 8, 12, 1, pa, pb, pout) + ASM_END_0 + for (i = 0; i < size - end; ++i) pout[i] = pa[i] + pb[i]; +} + +static void AddVectorEq_MIPS32(const uint32_t* pa, uint32_t* pout, int size) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + const int end = ((size) / 4) * 4; + const uint32_t* const LoopEnd = pa + end; + int i; + ASM_START + ADD_TO_OUT(0, 4, 8, 12, 0, pa, pout, pout) + ASM_END_1 + for (i = 0; i < size - end; ++i) pout[i] += pa[i]; +} + +#undef ASM_END_1 +#undef ASM_END_0 +#undef ASM_END_COMMON_1 +#undef ASM_END_COMMON_0 +#undef ADD_TO_OUT +#undef ASM_START + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMIPS32(void) { + VP8LFastSLog2Slow = FastSLog2Slow_MIPS32; + VP8LFastLog2Slow = FastLog2Slow_MIPS32; + VP8LExtraCost = ExtraCost_MIPS32; + VP8LExtraCostCombined = ExtraCostCombined_MIPS32; + VP8LGetEntropyUnrefined = GetEntropyUnrefined_MIPS32; + VP8LGetCombinedEntropyUnrefined = GetCombinedEntropyUnrefined_MIPS32; + VP8LAddVector = AddVector_MIPS32; + VP8LAddVectorEq = AddVectorEq_MIPS32; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips_dsp_r2.c new file mode 100644 index 0000000000..5855e6ae15 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_mips_dsp_r2.c @@ -0,0 +1,281 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transform methods for lossless encoder. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/lossless.h" + +static void SubtractGreenFromBlueAndRed_MIPSdspR2(uint32_t* argb_data, + int num_pixels) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + uint32_t* const p_loop1_end = argb_data + (num_pixels & ~3); + uint32_t* const p_loop2_end = p_loop1_end + (num_pixels & 3); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[argb_data], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[argb_data]) \n\t" + "lw %[temp1], 4(%[argb_data]) \n\t" + "lw %[temp2], 8(%[argb_data]) \n\t" + "lw %[temp3], 12(%[argb_data]) \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "ext %[temp5], %[temp1], 8, 8 \n\t" + "ext %[temp6], %[temp2], 8, 8 \n\t" + "ext %[temp7], %[temp3], 8, 8 \n\t" + "addiu %[argb_data], %[argb_data], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "replv.ph %[temp5], %[temp5] \n\t" + "replv.ph %[temp6], %[temp6] \n\t" + "replv.ph %[temp7], %[temp7] \n\t" + "subu.qb %[temp0], %[temp0], %[temp4] \n\t" + "subu.qb %[temp1], %[temp1], %[temp5] \n\t" + "subu.qb %[temp2], %[temp2], %[temp6] \n\t" + "subu.qb %[temp3], %[temp3], %[temp7] \n\t" + "sw %[temp0], -16(%[argb_data]) \n\t" + "sw %[temp1], -12(%[argb_data]) \n\t" + "sw %[temp2], -8(%[argb_data]) \n\t" + "bne %[argb_data], %[p_loop1_end], 0b \n\t" + " sw %[temp3], -4(%[argb_data]) \n\t" + "3: \n\t" + "beq %[argb_data], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[argb_data]) \n\t" + "addiu %[argb_data], %[argb_data], 4 \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "subu.qb %[temp0], %[temp0], %[temp4] \n\t" + "bne %[argb_data], %[p_loop2_end], 1b \n\t" + " sw %[temp0], -4(%[argb_data]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [argb_data]"+&r"(argb_data), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static WEBP_INLINE uint32_t ColorTransformDelta(int8_t color_pred, + int8_t color) { + return (uint32_t)((int)(color_pred) * color) >> 5; +} + +static void TransformColor_MIPSdspR2(const VP8LMultipliers* const m, + uint32_t* data, int num_pixels) { + int temp0, temp1, temp2, temp3, temp4, temp5; + uint32_t argb, argb1, new_red, new_red1; + const uint32_t G_to_R = m->green_to_red_; + const uint32_t G_to_B = m->green_to_blue_; + const uint32_t R_to_B = m->red_to_blue_; + uint32_t* const p_loop_end = data + (num_pixels & ~1); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[data], %[p_loop_end], 1f \n\t" + " nop \n\t" + "replv.ph %[temp0], %[G_to_R] \n\t" + "replv.ph %[temp1], %[G_to_B] \n\t" + "replv.ph %[temp2], %[R_to_B] \n\t" + "shll.ph %[temp0], %[temp0], 8 \n\t" + "shll.ph %[temp1], %[temp1], 8 \n\t" + "shll.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp0], %[temp0], 8 \n\t" + "shra.ph %[temp1], %[temp1], 8 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "0: \n\t" + "lw %[argb], 0(%[data]) \n\t" + "lw %[argb1], 4(%[data]) \n\t" + "lhu %[new_red], 2(%[data]) \n\t" + "lhu %[new_red1], 6(%[data]) \n\t" + "precrq.qb.ph %[temp3], %[argb], %[argb1] \n\t" + "precr.qb.ph %[temp4], %[argb], %[argb1] \n\t" + "preceu.ph.qbra %[temp3], %[temp3] \n\t" + "preceu.ph.qbla %[temp4], %[temp4] \n\t" + "shll.ph %[temp3], %[temp3], 8 \n\t" + "shll.ph %[temp4], %[temp4], 8 \n\t" + "shra.ph %[temp3], %[temp3], 8 \n\t" + "shra.ph %[temp4], %[temp4], 8 \n\t" + "mul.ph %[temp5], %[temp3], %[temp0] \n\t" + "mul.ph %[temp3], %[temp3], %[temp1] \n\t" + "mul.ph %[temp4], %[temp4], %[temp2] \n\t" + "addiu %[data], %[data], 8 \n\t" + "ins %[new_red1], %[new_red], 16, 16 \n\t" + "ins %[argb1], %[argb], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp3], %[temp3], 5 \n\t" + "shra.ph %[temp4], %[temp4], 5 \n\t" + "subu.ph %[new_red1], %[new_red1], %[temp5] \n\t" + "subu.ph %[argb1], %[argb1], %[temp3] \n\t" + "preceu.ph.qbra %[temp5], %[new_red1] \n\t" + "subu.ph %[argb1], %[argb1], %[temp4] \n\t" + "preceu.ph.qbra %[temp3], %[argb1] \n\t" + "sb %[temp5], -2(%[data]) \n\t" + "sb %[temp3], -4(%[data]) \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "sb %[temp5], -6(%[data]) \n\t" + "bne %[data], %[p_loop_end], 0b \n\t" + " sb %[temp3], -8(%[data]) \n\t" + "1: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [new_red1]"=&r"(new_red1), [new_red]"=&r"(new_red), + [argb]"=&r"(argb), [argb1]"=&r"(argb1), [data]"+&r"(data) + : [G_to_R]"r"(G_to_R), [R_to_B]"r"(R_to_B), + [G_to_B]"r"(G_to_B), [p_loop_end]"r"(p_loop_end) + : "memory", "hi", "lo" + ); + + if (num_pixels & 1) { + const uint32_t argb_ = data[0]; + const uint32_t green = argb_ >> 8; + const uint32_t red = argb_ >> 16; + uint32_t new_blue = argb_; + new_red = red; + new_red -= ColorTransformDelta(m->green_to_red_, green); + new_red &= 0xff; + new_blue -= ColorTransformDelta(m->green_to_blue_, green); + new_blue -= ColorTransformDelta(m->red_to_blue_, red); + new_blue &= 0xff; + data[0] = (argb_ & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +static WEBP_INLINE uint8_t TransformColorBlue(uint8_t green_to_blue, + uint8_t red_to_blue, + uint32_t argb) { + const uint32_t green = argb >> 8; + const uint32_t red = argb >> 16; + uint8_t new_blue = argb; + new_blue -= ColorTransformDelta(green_to_blue, green); + new_blue -= ColorTransformDelta(red_to_blue, red); + return (new_blue & 0xff); +} + +static void CollectColorBlueTransforms_MIPSdspR2(const uint32_t* argb, + int stride, + int tile_width, + int tile_height, + int green_to_blue, + int red_to_blue, + int histo[]) { + const int rtb = (red_to_blue << 16) | (red_to_blue & 0xffff); + const int gtb = (green_to_blue << 16) | (green_to_blue & 0xffff); + const uint32_t mask = 0xff00ffu; + while (tile_height-- > 0) { + int x; + const uint32_t* p_argb = argb; + argb += stride; + for (x = 0; x < (tile_width >> 1); ++x) { + int temp0, temp1, temp2, temp3, temp4, temp5, temp6; + __asm__ volatile ( + "lw %[temp0], 0(%[p_argb]) \n\t" + "lw %[temp1], 4(%[p_argb]) \n\t" + "precr.qb.ph %[temp2], %[temp0], %[temp1] \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp3], %[temp1], 8 \n\t" + "mul.ph %[temp5], %[temp2], %[rtb] \n\t" + "mul.ph %[temp6], %[temp3], %[gtb] \n\t" + "and %[temp4], %[temp1], %[mask] \n\t" + "addiu %[p_argb], %[p_argb], 8 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp6], %[temp6], 5 \n\t" + "subu.qb %[temp2], %[temp4], %[temp5] \n\t" + "subu.qb %[temp2], %[temp2], %[temp6] \n\t" + : [p_argb]"+&r"(p_argb), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [temp5]"=&r"(temp5), [temp6]"=&r"(temp6) + : [rtb]"r"(rtb), [gtb]"r"(gtb), [mask]"r"(mask) + : "memory", "hi", "lo" + ); + ++histo[(uint8_t)(temp2 >> 16)]; + ++histo[(uint8_t)temp2]; + } + if (tile_width & 1) { + ++histo[TransformColorBlue(green_to_blue, red_to_blue, *p_argb)]; + } + } +} + +static WEBP_INLINE uint8_t TransformColorRed(uint8_t green_to_red, + uint32_t argb) { + const uint32_t green = argb >> 8; + uint32_t new_red = argb >> 16; + new_red -= ColorTransformDelta(green_to_red, green); + return (new_red & 0xff); +} + +static void CollectColorRedTransforms_MIPSdspR2(const uint32_t* argb, + int stride, + int tile_width, + int tile_height, + int green_to_red, + int histo[]) { + const int gtr = (green_to_red << 16) | (green_to_red & 0xffff); + while (tile_height-- > 0) { + int x; + const uint32_t* p_argb = argb; + argb += stride; + for (x = 0; x < (tile_width >> 1); ++x) { + int temp0, temp1, temp2, temp3, temp4; + __asm__ volatile ( + "lw %[temp0], 0(%[p_argb]) \n\t" + "lw %[temp1], 4(%[p_argb]) \n\t" + "precrq.ph.w %[temp4], %[temp0], %[temp1] \n\t" + "ins %[temp1], %[temp0], 16, 16 \n\t" + "shra.ph %[temp3], %[temp1], 8 \n\t" + "mul.ph %[temp2], %[temp3], %[gtr] \n\t" + "addiu %[p_argb], %[p_argb], 8 \n\t" + "shra.ph %[temp2], %[temp2], 5 \n\t" + "subu.qb %[temp2], %[temp4], %[temp2] \n\t" + : [p_argb]"+&r"(p_argb), [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4) + : [gtr]"r"(gtr) + : "memory", "hi", "lo" + ); + ++histo[(uint8_t)(temp2 >> 16)]; + ++histo[(uint8_t)temp2]; + } + if (tile_width & 1) { + ++histo[TransformColorRed(green_to_red, *p_argb)]; + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMIPSdspR2(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_MIPSdspR2; + VP8LTransformColor = TransformColor_MIPSdspR2; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms_MIPSdspR2; + VP8LCollectColorRedTransforms = CollectColorRedTransforms_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_msa.c new file mode 100644 index 0000000000..600dddfb59 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_msa.c @@ -0,0 +1,148 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of Image transform methods for lossless encoder. +// +// Authors: Prashant Patil (Prashant.Patil@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "src/dsp/lossless.h" +#include "src/dsp/msa_macro.h" + +#define TRANSFORM_COLOR_8(src0, src1, dst0, dst1, c0, c1, mask0, mask1) do { \ + v8i16 g0, g1, t0, t1, t2, t3; \ + v4i32 t4, t5; \ + VSHF_B2_SH(src0, src0, src1, src1, mask0, mask0, g0, g1); \ + DOTP_SB2_SH(g0, g1, c0, c0, t0, t1); \ + SRAI_H2_SH(t0, t1, 5); \ + t0 = __msa_subv_h((v8i16)src0, t0); \ + t1 = __msa_subv_h((v8i16)src1, t1); \ + t4 = __msa_srli_w((v4i32)src0, 16); \ + t5 = __msa_srli_w((v4i32)src1, 16); \ + DOTP_SB2_SH(t4, t5, c1, c1, t2, t3); \ + SRAI_H2_SH(t2, t3, 5); \ + SUB2(t0, t2, t1, t3, t0, t1); \ + VSHF_B2_UB(src0, t0, src1, t1, mask1, mask1, dst0, dst1); \ +} while (0) + +#define TRANSFORM_COLOR_4(src, dst, c0, c1, mask0, mask1) do { \ + const v16i8 g0 = VSHF_SB(src, src, mask0); \ + v8i16 t0 = __msa_dotp_s_h(c0, g0); \ + v8i16 t1; \ + v4i32 t2; \ + t0 = SRAI_H(t0, 5); \ + t0 = __msa_subv_h((v8i16)src, t0); \ + t2 = __msa_srli_w((v4i32)src, 16); \ + t1 = __msa_dotp_s_h(c1, (v16i8)t2); \ + t1 = SRAI_H(t1, 5); \ + t0 = t0 - t1; \ + dst = VSHF_UB(src, t0, mask1); \ +} while (0) + +static void TransformColor_MSA(const VP8LMultipliers* const m, uint32_t* data, + int num_pixels) { + v16u8 src0, dst0; + const v16i8 g2br = (v16i8)__msa_fill_w(m->green_to_blue_ | + (m->green_to_red_ << 16)); + const v16i8 r2b = (v16i8)__msa_fill_w(m->red_to_blue_); + const v16u8 mask0 = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + const v16u8 mask1 = { 16, 1, 18, 3, 20, 5, 22, 7, 24, 9, 26, 11, + 28, 13, 30, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(data, 4, src0, src1); + TRANSFORM_COLOR_8(src0, src1, dst0, dst1, g2br, r2b, mask0, mask1); + ST_UB2(dst0, dst1, data, 4); + data += 8; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(data); + TRANSFORM_COLOR_4(src0, dst0, g2br, r2b, mask0, mask1); + ST_UB(dst0, data); + data += 4; + num_pixels -= 4; + } + if (num_pixels > 0) { + src0 = LD_UB(data); + TRANSFORM_COLOR_4(src0, dst0, g2br, r2b, mask0, mask1); + if (num_pixels == 3) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); + SD(pix_d, data + 0); + SW(pix_w, data + 2); + } else if (num_pixels == 2) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + SD(pix_d, data); + } else { + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 0); + SW(pix_w, data); + } + } + } +} + +static void SubtractGreenFromBlueAndRed_MSA(uint32_t* argb_data, + int num_pixels) { + int i; + uint8_t* ptemp_data = (uint8_t*)argb_data; + v16u8 src0, dst0, tmp0; + const v16u8 mask = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1, tmp1; + LD_UB2(ptemp_data, 16, src0, src1); + VSHF_B2_UB(src0, src1, src1, src0, mask, mask, tmp0, tmp1); + SUB2(src0, tmp0, src1, tmp1, dst0, dst1); + ST_UB2(dst0, dst1, ptemp_data, 16); + ptemp_data += 8 * 4; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(ptemp_data); + tmp0 = VSHF_UB(src0, src0, mask); + dst0 = src0 - tmp0; + ST_UB(dst0, ptemp_data); + ptemp_data += 4 * 4; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = ptemp_data[0]; + const uint8_t g = ptemp_data[1]; + const uint8_t r = ptemp_data[2]; + ptemp_data[0] = (b - g) & 0xff; + ptemp_data[2] = (r - g) & 0xff; + ptemp_data += 4; + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitMSA(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_MSA; + VP8LTransformColor = TransformColor_MSA; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8LEncDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_neon.c new file mode 100644 index 0000000000..e32c7961a2 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_neon.c @@ -0,0 +1,144 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "src/dsp/lossless.h" +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +// vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use +// non-standard versions there. +#if defined(__APPLE__) && WEBP_AARCH64 && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) +#define USE_VTBLQ +#endif + +#ifdef USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[16] = { + 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255 +}; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x16_t shuffle) { + return vcombine_u8(vtbl1q_u8(argb, vget_low_u8(shuffle)), + vtbl1q_u8(argb, vget_high_u8(shuffle))); +} +#else // !USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[8] = { 1, 255, 1, 255, 5, 255, 5, 255 }; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x8_t shuffle) { + return vcombine_u8(vtbl1_u8(vget_low_u8(argb), shuffle), + vtbl1_u8(vget_high_u8(argb), shuffle)); +} +#endif // USE_VTBLQ + +static void SubtractGreenFromBlueAndRed_NEON(uint32_t* argb_data, + int num_pixels) { + const uint32_t* const end = argb_data + (num_pixels & ~3); +#ifdef USE_VTBLQ + const uint8x16_t shuffle = vld1q_u8(kGreenShuffle); +#else + const uint8x8_t shuffle = vld1_u8(kGreenShuffle); +#endif + for (; argb_data < end; argb_data += 4) { + const uint8x16_t argb = vld1q_u8((uint8_t*)argb_data); + const uint8x16_t greens = DoGreenShuffle_NEON(argb, shuffle); + vst1q_u8((uint8_t*)argb_data, vsubq_u8(argb, greens)); + } + // fallthrough and finish off with plain-C + VP8LSubtractGreenFromBlueAndRed_C(argb_data, num_pixels & 3); +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColor_NEON(const VP8LMultipliers* const m, + uint32_t* argb_data, int num_pixels) { + // sign-extended multiplying constants, pre-shifted by 6. +#define CST(X) (((int16_t)(m->X << 8)) >> 6) + const int16_t rb[8] = { + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_) + }; + const int16x8_t mults_rb = vld1q_s16(rb); + const int16_t b2[8] = { + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + }; + const int16x8_t mults_b2 = vld1q_s16(b2); +#undef CST +#ifdef USE_VTBLQ + static const uint8_t kg0g0[16] = { + 255, 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13 + }; + const uint8x16_t shuffle = vld1q_u8(kg0g0); +#else + static const uint8_t k0g0g[8] = { 255, 1, 255, 1, 255, 5, 255, 5 }; + const uint8x8_t shuffle = vld1_u8(k0g0g); +#endif + const uint32x4_t mask_rb = vdupq_n_u32(0x00ff00ffu); // red-blue masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t in = vld1q_u8((uint8_t*)(argb_data + i)); + // 0 g 0 g + const uint8x16_t greens = DoGreenShuffle_NEON(in, shuffle); + // x dr x db1 + const int16x8_t A = vqdmulhq_s16(vreinterpretq_s16_u8(greens), mults_rb); + // r 0 b 0 + const int16x8_t B = vshlq_n_s16(vreinterpretq_s16_u8(in), 8); + // x db2 0 0 + const int16x8_t C = vqdmulhq_s16(B, mults_b2); + // 0 0 x db2 + const uint32x4_t D = vshrq_n_u32(vreinterpretq_u32_s16(C), 16); + // x dr x db + const int8x16_t E = vaddq_s8(vreinterpretq_s8_u32(D), + vreinterpretq_s8_s16(A)); + // 0 dr 0 db + const uint32x4_t F = vandq_u32(vreinterpretq_u32_s8(E), mask_rb); + const int8x16_t out = vsubq_s8(vreinterpretq_s8_u8(in), + vreinterpretq_s8_u32(F)); + vst1q_s8((int8_t*)(argb_data + i), out); + } + // fallthrough and finish off with plain-C + VP8LTransformColor_C(m, argb_data + i, num_pixels - i); +} + +#undef USE_VTBLQ + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitNEON(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_NEON; + VP8LTransformColor = TransformColor_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8LEncDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse2.c new file mode 100644 index 0000000000..66cbaab772 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse2.c @@ -0,0 +1,669 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include +#include +#include "src/dsp/lossless.h" +#include "src/dsp/common_sse2.h" +#include "src/dsp/lossless_common.h" + +// For sign-extended multiplying constants, pre-shifted by 5: +#define CST_5b(X) (((int16_t)((uint16_t)(X) << 8)) >> 5) + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void SubtractGreenFromBlueAndRed_SSE2(uint32_t* argb_data, + int num_pixels) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); // argb + const __m128i A = _mm_srli_epi16(in, 8); // 0 a 0 g + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // 0g0g + const __m128i out = _mm_sub_epi8(in, C); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LSubtractGreenFromBlueAndRed_C(argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + +static void TransformColor_SSE2(const VP8LMultipliers* const m, + uint32_t* argb_data, int num_pixels) { + const __m128i mults_rb = MK_CST_16(CST_5b(m->green_to_red_), + CST_5b(m->green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST_5b(m->red_to_blue_), 0); + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); // alpha-green masks + const __m128i mask_rb = _mm_set1_epi32(0x00ff00ff); // red-blue masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); // argb + const __m128i A = _mm_and_si128(in, mask_ag); // a 0 g 0 + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // g0g0 + const __m128i D = _mm_mulhi_epi16(C, mults_rb); // x dr x db1 + const __m128i E = _mm_slli_epi16(in, 8); // r 0 b 0 + const __m128i F = _mm_mulhi_epi16(E, mults_b2); // x db2 0 0 + const __m128i G = _mm_srli_epi32(F, 16); // 0 0 x db2 + const __m128i H = _mm_add_epi8(G, D); // x dr x db + const __m128i I = _mm_and_si128(H, mask_rb); // 0 dr 0 db + const __m128i out = _mm_sub_epi8(in, I); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LTransformColor_C(m, argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +#define SPAN 8 +static void CollectColorBlueTransforms_SSE2(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const __m128i mults_r = MK_CST_16(CST_5b(red_to_blue), 0); + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_blue)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask_b = _mm_set1_epi32(0x0000ff); // blue mask + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i A0 = _mm_slli_epi16(in0, 8); // r 0 | b 0 + const __m128i A1 = _mm_slli_epi16(in1, 8); + const __m128i B0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i B1 = _mm_and_si128(in1, mask_g); + const __m128i C0 = _mm_mulhi_epi16(A0, mults_r); // x db | 0 0 + const __m128i C1 = _mm_mulhi_epi16(A1, mults_r); + const __m128i D0 = _mm_mulhi_epi16(B0, mults_g); // 0 0 | x db + const __m128i D1 = _mm_mulhi_epi16(B1, mults_g); + const __m128i E0 = _mm_sub_epi8(in0, D0); // x x | x b' + const __m128i E1 = _mm_sub_epi8(in1, D1); + const __m128i F0 = _mm_srli_epi32(C0, 16); // 0 0 | x db + const __m128i F1 = _mm_srli_epi32(C1, 16); + const __m128i G0 = _mm_sub_epi8(E0, F0); // 0 0 | x b' + const __m128i G1 = _mm_sub_epi8(E1, F1); + const __m128i H0 = _mm_and_si128(G0, mask_b); // 0 0 | 0 b + const __m128i H1 = _mm_and_si128(G1, mask_b); + const __m128i I = _mm_packs_epi32(H0, H1); // 0 b' | 0 b' + _mm_storeu_si128((__m128i*)values, I); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorBlueTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_blue, red_to_blue, histo); + } + } +} + +static void CollectColorRedTransforms_SSE2(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + const __m128i mults_g = MK_CST_16(0, CST_5b(green_to_red)); + const __m128i mask_g = _mm_set1_epi32(0x00ff00); // green mask + const __m128i mask = _mm_set1_epi32(0xff); + + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + int i, x; + for (x = 0; x + SPAN <= tile_width; x += SPAN) { + uint16_t values[SPAN]; + const __m128i in0 = _mm_loadu_si128((__m128i*)&src[x + 0]); + const __m128i in1 = _mm_loadu_si128((__m128i*)&src[x + SPAN / 2]); + const __m128i A0 = _mm_and_si128(in0, mask_g); // 0 0 | g 0 + const __m128i A1 = _mm_and_si128(in1, mask_g); + const __m128i B0 = _mm_srli_epi32(in0, 16); // 0 0 | x r + const __m128i B1 = _mm_srli_epi32(in1, 16); + const __m128i C0 = _mm_mulhi_epi16(A0, mults_g); // 0 0 | x dr + const __m128i C1 = _mm_mulhi_epi16(A1, mults_g); + const __m128i E0 = _mm_sub_epi8(B0, C0); // x x | x r' + const __m128i E1 = _mm_sub_epi8(B1, C1); + const __m128i F0 = _mm_and_si128(E0, mask); // 0 0 | 0 r' + const __m128i F1 = _mm_and_si128(E1, mask); + const __m128i I = _mm_packs_epi32(F0, F1); + _mm_storeu_si128((__m128i*)values, I); + for (i = 0; i < SPAN; ++i) ++histo[values[i]]; + } + } + { + const int left_over = tile_width & (SPAN - 1); + if (left_over > 0) { + VP8LCollectColorRedTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_red, histo); + } + } +} +#undef SPAN +#undef MK_CST_16 + +//------------------------------------------------------------------------------ + +// Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But +// that's ok since the histogram values are less than 1<<28 (max picture size). +#define LINE_SIZE 16 // 8 or 16 +static void AddVector_SSE2(const uint32_t* a, const uint32_t* b, uint32_t* out, + int size) { + int i; + for (i = 0; i + LINE_SIZE <= size; i += LINE_SIZE) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i + 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); +#if (LINE_SIZE == 16) + const __m128i a2 = _mm_loadu_si128((const __m128i*)&a[i + 8]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&a[i + 12]); +#endif + const __m128i b0 = _mm_loadu_si128((const __m128i*)&b[i + 0]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&b[i + 4]); +#if (LINE_SIZE == 16) + const __m128i b2 = _mm_loadu_si128((const __m128i*)&b[i + 8]); + const __m128i b3 = _mm_loadu_si128((const __m128i*)&b[i + 12]); +#endif + _mm_storeu_si128((__m128i*)&out[i + 0], _mm_add_epi32(a0, b0)); + _mm_storeu_si128((__m128i*)&out[i + 4], _mm_add_epi32(a1, b1)); +#if (LINE_SIZE == 16) + _mm_storeu_si128((__m128i*)&out[i + 8], _mm_add_epi32(a2, b2)); + _mm_storeu_si128((__m128i*)&out[i + 12], _mm_add_epi32(a3, b3)); +#endif + } + for (; i < size; ++i) { + out[i] = a[i] + b[i]; + } +} + +static void AddVectorEq_SSE2(const uint32_t* a, uint32_t* out, int size) { + int i; + for (i = 0; i + LINE_SIZE <= size; i += LINE_SIZE) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i + 0]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); +#if (LINE_SIZE == 16) + const __m128i a2 = _mm_loadu_si128((const __m128i*)&a[i + 8]); + const __m128i a3 = _mm_loadu_si128((const __m128i*)&a[i + 12]); +#endif + const __m128i b0 = _mm_loadu_si128((const __m128i*)&out[i + 0]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&out[i + 4]); +#if (LINE_SIZE == 16) + const __m128i b2 = _mm_loadu_si128((const __m128i*)&out[i + 8]); + const __m128i b3 = _mm_loadu_si128((const __m128i*)&out[i + 12]); +#endif + _mm_storeu_si128((__m128i*)&out[i + 0], _mm_add_epi32(a0, b0)); + _mm_storeu_si128((__m128i*)&out[i + 4], _mm_add_epi32(a1, b1)); +#if (LINE_SIZE == 16) + _mm_storeu_si128((__m128i*)&out[i + 8], _mm_add_epi32(a2, b2)); + _mm_storeu_si128((__m128i*)&out[i + 12], _mm_add_epi32(a3, b3)); +#endif + } + for (; i < size; ++i) { + out[i] += a[i]; + } +} +#undef LINE_SIZE + +//------------------------------------------------------------------------------ +// Entropy + +// TODO(https://crbug.com/webp/499): this function produces different results +// from the C code due to use of double/float resulting in output differences +// when compared to -noasm. +#if !(defined(WEBP_HAVE_SLOW_CLZ_CTZ) || defined(__i386__) || defined(_M_IX86)) + +static float CombinedShannonEntropy_SSE2(const int X[256], const int Y[256]) { + int i; + float retval = 0.f; + int sumX = 0, sumXY = 0; + const __m128i zero = _mm_setzero_si128(); + + for (i = 0; i < 256; i += 16) { + const __m128i x0 = _mm_loadu_si128((const __m128i*)(X + i + 0)); + const __m128i y0 = _mm_loadu_si128((const __m128i*)(Y + i + 0)); + const __m128i x1 = _mm_loadu_si128((const __m128i*)(X + i + 4)); + const __m128i y1 = _mm_loadu_si128((const __m128i*)(Y + i + 4)); + const __m128i x2 = _mm_loadu_si128((const __m128i*)(X + i + 8)); + const __m128i y2 = _mm_loadu_si128((const __m128i*)(Y + i + 8)); + const __m128i x3 = _mm_loadu_si128((const __m128i*)(X + i + 12)); + const __m128i y3 = _mm_loadu_si128((const __m128i*)(Y + i + 12)); + const __m128i x4 = _mm_packs_epi16(_mm_packs_epi32(x0, x1), + _mm_packs_epi32(x2, x3)); + const __m128i y4 = _mm_packs_epi16(_mm_packs_epi32(y0, y1), + _mm_packs_epi32(y2, y3)); + const int32_t mx = _mm_movemask_epi8(_mm_cmpgt_epi8(x4, zero)); + int32_t my = _mm_movemask_epi8(_mm_cmpgt_epi8(y4, zero)) | mx; + while (my) { + const int32_t j = BitsCtz(my); + int xy; + if ((mx >> j) & 1) { + const int x = X[i + j]; + sumXY += x; + retval -= VP8LFastSLog2(x); + } + xy = X[i + j] + Y[i + j]; + sumX += xy; + retval -= VP8LFastSLog2(xy); + my &= my - 1; + } + } + retval += VP8LFastSLog2(sumX) + VP8LFastSLog2(sumXY); + return retval; +} + +#else + +#define DONT_USE_COMBINED_SHANNON_ENTROPY_SSE2_FUNC // won't be faster + +#endif + +//------------------------------------------------------------------------------ + +static int VectorMismatch_SSE2(const uint32_t* const array1, + const uint32_t* const array2, int length) { + int match_len; + + if (length >= 12) { + __m128i A0 = _mm_loadu_si128((const __m128i*)&array1[0]); + __m128i A1 = _mm_loadu_si128((const __m128i*)&array2[0]); + match_len = 0; + do { + // Loop unrolling and early load both provide a speedup of 10% for the + // current function. Also, max_limit can be MAX_LENGTH=4096 at most. + const __m128i cmpA = _mm_cmpeq_epi32(A0, A1); + const __m128i B0 = + _mm_loadu_si128((const __m128i*)&array1[match_len + 4]); + const __m128i B1 = + _mm_loadu_si128((const __m128i*)&array2[match_len + 4]); + if (_mm_movemask_epi8(cmpA) != 0xffff) break; + match_len += 4; + + { + const __m128i cmpB = _mm_cmpeq_epi32(B0, B1); + A0 = _mm_loadu_si128((const __m128i*)&array1[match_len + 4]); + A1 = _mm_loadu_si128((const __m128i*)&array2[match_len + 4]); + if (_mm_movemask_epi8(cmpB) != 0xffff) break; + match_len += 4; + } + } while (match_len + 12 < length); + } else { + match_len = 0; + // Unroll the potential first two loops. + if (length >= 4 && + _mm_movemask_epi8(_mm_cmpeq_epi32( + _mm_loadu_si128((const __m128i*)&array1[0]), + _mm_loadu_si128((const __m128i*)&array2[0]))) == 0xffff) { + match_len = 4; + if (length >= 8 && + _mm_movemask_epi8(_mm_cmpeq_epi32( + _mm_loadu_si128((const __m128i*)&array1[4]), + _mm_loadu_si128((const __m128i*)&array2[4]))) == 0xffff) { + match_len = 8; + } + } + } + + while (match_len < length && array1[match_len] == array2[match_len]) { + ++match_len; + } + return match_len; +} + +// Bundles multiple (1, 2, 4 or 8) pixels into a single pixel. +static void BundleColorMap_SSE2(const uint8_t* const row, int width, int xbits, + uint32_t* dst) { + int x; + assert(xbits >= 0); + assert(xbits <= 3); + switch (xbits) { + case 0: { + const __m128i ff = _mm_set1_epi16((short)0xff00); + const __m128i zero = _mm_setzero_si128(); + // Store 0xff000000 | (row[x] << 8). + for (x = 0; x + 16 <= width; x += 16, dst += 16) { + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i in_lo = _mm_unpacklo_epi8(zero, in); + const __m128i dst0 = _mm_unpacklo_epi16(in_lo, ff); + const __m128i dst1 = _mm_unpackhi_epi16(in_lo, ff); + const __m128i in_hi = _mm_unpackhi_epi8(zero, in); + const __m128i dst2 = _mm_unpacklo_epi16(in_hi, ff); + const __m128i dst3 = _mm_unpackhi_epi16(in_hi, ff); + _mm_storeu_si128((__m128i*)&dst[0], dst0); + _mm_storeu_si128((__m128i*)&dst[4], dst1); + _mm_storeu_si128((__m128i*)&dst[8], dst2); + _mm_storeu_si128((__m128i*)&dst[12], dst3); + } + break; + } + case 1: { + const __m128i ff = _mm_set1_epi16((short)0xff00); + const __m128i mul = _mm_set1_epi16(0x110); + for (x = 0; x + 16 <= width; x += 16, dst += 8) { + // 0a0b | (where a/b are 4 bits). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i tmp = _mm_mullo_epi16(in, mul); // aba0 + const __m128i pack = _mm_and_si128(tmp, ff); // ab00 + const __m128i dst0 = _mm_unpacklo_epi16(pack, ff); + const __m128i dst1 = _mm_unpackhi_epi16(pack, ff); + _mm_storeu_si128((__m128i*)&dst[0], dst0); + _mm_storeu_si128((__m128i*)&dst[4], dst1); + } + break; + } + case 2: { + const __m128i mask_or = _mm_set1_epi32((int)0xff000000); + const __m128i mul_cst = _mm_set1_epi16(0x0104); + const __m128i mask_mul = _mm_set1_epi16(0x0f00); + for (x = 0; x + 16 <= width; x += 16, dst += 4) { + // 000a000b000c000d | (where a/b/c/d are 2 bits). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i mul = _mm_mullo_epi16(in, mul_cst); // 00ab00b000cd00d0 + const __m128i tmp = _mm_and_si128(mul, mask_mul); // 00ab000000cd0000 + const __m128i shift = _mm_srli_epi32(tmp, 12); // 00000000ab000000 + const __m128i pack = _mm_or_si128(shift, tmp); // 00000000abcd0000 + // Convert to 0xff00**00. + const __m128i res = _mm_or_si128(pack, mask_or); + _mm_storeu_si128((__m128i*)dst, res); + } + break; + } + default: { + assert(xbits == 3); + for (x = 0; x + 16 <= width; x += 16, dst += 2) { + // 0000000a00000000b... | (where a/b are 1 bit). + const __m128i in = _mm_loadu_si128((const __m128i*)&row[x]); + const __m128i shift = _mm_slli_epi64(in, 7); + const uint32_t move = _mm_movemask_epi8(shift); + dst[0] = 0xff000000 | ((move & 0xff) << 8); + dst[1] = 0xff000000 | (move & 0xff00); + } + break; + } + } + if (x != width) { + VP8LBundleColorMap_C(row + x, width - x, xbits, dst); + } +} + +//------------------------------------------------------------------------------ +// Batch version of Predictor Transform subtraction + +static WEBP_INLINE void Average2_m128i(const __m128i* const a0, + const __m128i* const a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i avg1 = _mm_avg_epu8(*a0, *a1); + const __m128i one = _mm_and_si128(_mm_xor_si128(*a0, *a1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +// Predictor0: ARGB_BLACK. +static void PredictorSub0_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i black = _mm_set1_epi32((int)ARGB_BLACK); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i res = _mm_sub_epi8(src, black); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[0](in + i, NULL, num_pixels - i, out + i); + } + (void)upper; +} + +#define GENERATE_PREDICTOR_1(X, IN) \ + static void PredictorSub##X##_SSE2(const uint32_t* const in, \ + const uint32_t* const upper, \ + int num_pixels, uint32_t* const out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + const __m128i pred = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i res = _mm_sub_epi8(src, pred); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsSub_C[(X)](in + i, WEBP_OFFSET_PTR(upper, i), \ + num_pixels - i, out + i); \ + } \ + } + +GENERATE_PREDICTOR_1(1, in[i - 1]) // Predictor1: L +GENERATE_PREDICTOR_1(2, upper[i]) // Predictor2: T +GENERATE_PREDICTOR_1(3, upper[i + 1]) // Predictor3: TR +GENERATE_PREDICTOR_1(4, upper[i - 1]) // Predictor4: TL +#undef GENERATE_PREDICTOR_1 + +// Predictor5: avg2(avg2(L, TR), T) +static void PredictorSub5_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i avg, pred, res; + Average2_m128i(&L, &TR, &avg); + Average2_m128i(&avg, &T, &pred); + res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[5](in + i, upper + i, num_pixels - i, out + i); + } +} + +#define GENERATE_PREDICTOR_2(X, A, B) \ +static void PredictorSub##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i tA = _mm_loadu_si128((const __m128i*)&(A)); \ + const __m128i tB = _mm_loadu_si128((const __m128i*)&(B)); \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + __m128i pred, res; \ + Average2_m128i(&tA, &tB, &pred); \ + res = _mm_sub_epi8(src, pred); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsSub_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +GENERATE_PREDICTOR_2(6, in[i - 1], upper[i - 1]) // Predictor6: avg(L, TL) +GENERATE_PREDICTOR_2(7, in[i - 1], upper[i]) // Predictor7: avg(L, T) +GENERATE_PREDICTOR_2(8, upper[i - 1], upper[i]) // Predictor8: avg(TL, T) +GENERATE_PREDICTOR_2(9, upper[i], upper[i + 1]) // Predictor9: average(T, TR) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: avg(avg(L,TL), avg(T, TR)). +static void PredictorSub10_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + __m128i avgTTR, avgLTL, avg, res; + Average2_m128i(&T, &TR, &avgTTR); + Average2_m128i(&L, &TL, &avgLTL); + Average2_m128i(&avgTTR, &avgLTL, &avg); + res = _mm_sub_epi8(src, avg); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[10](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor11: select. +static void GetSumAbsDiff32_SSE2(const __m128i* const A, const __m128i* const B, + __m128i* const out) { + // We can unpack with any value on the upper 32 bits, provided it's the same + // on both operands (to that their sum of abs diff is zero). Here we use *A. + const __m128i A_lo = _mm_unpacklo_epi32(*A, *A); + const __m128i B_lo = _mm_unpacklo_epi32(*B, *A); + const __m128i A_hi = _mm_unpackhi_epi32(*A, *A); + const __m128i B_hi = _mm_unpackhi_epi32(*B, *A); + const __m128i s_lo = _mm_sad_epu8(A_lo, B_lo); + const __m128i s_hi = _mm_sad_epu8(A_hi, B_hi); + *out = _mm_packs_epi32(s_lo, s_hi); +} + +static void PredictorSub11_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i pa, pb; + GetSumAbsDiff32_SSE2(&T, &TL, &pa); // pa = sum |T-TL| + GetSumAbsDiff32_SSE2(&L, &TL, &pb); // pb = sum |L-TL| + { + const __m128i mask = _mm_cmpgt_epi32(pb, pa); + const __m128i A = _mm_and_si128(mask, L); + const __m128i B = _mm_andnot_si128(mask, T); + const __m128i pred = _mm_or_si128(A, B); // pred = (L > T)? L : T + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + } + if (i != num_pixels) { + VP8LPredictorsSub_C[11](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictor12: ClampedSubSubtractFull. +static void PredictorSub12_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i L = _mm_loadu_si128((const __m128i*)&in[i - 1]); + const __m128i L_lo = _mm_unpacklo_epi8(L, zero); + const __m128i L_hi = _mm_unpackhi_epi8(L, zero); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i T_hi = _mm_unpackhi_epi8(T, zero); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i TL_hi = _mm_unpackhi_epi8(TL, zero); + const __m128i diff_lo = _mm_sub_epi16(T_lo, TL_lo); + const __m128i diff_hi = _mm_sub_epi16(T_hi, TL_hi); + const __m128i pred_lo = _mm_add_epi16(L_lo, diff_lo); + const __m128i pred_hi = _mm_add_epi16(L_hi, diff_hi); + const __m128i pred = _mm_packus_epi16(pred_lo, pred_hi); + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[12](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Predictors13: ClampedAddSubtractHalf +static void PredictorSub13_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i + 2 <= num_pixels; i += 2) { + // we can only process two pixels at a time + const __m128i L = _mm_loadl_epi64((const __m128i*)&in[i - 1]); + const __m128i src = _mm_loadl_epi64((const __m128i*)&in[i]); + const __m128i T = _mm_loadl_epi64((const __m128i*)&upper[i]); + const __m128i TL = _mm_loadl_epi64((const __m128i*)&upper[i - 1]); + const __m128i L_lo = _mm_unpacklo_epi8(L, zero); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i sum = _mm_add_epi16(T_lo, L_lo); + const __m128i avg = _mm_srli_epi16(sum, 1); + const __m128i A1 = _mm_sub_epi16(avg, TL_lo); + const __m128i bit_fix = _mm_cmpgt_epi16(TL_lo, avg); + const __m128i A2 = _mm_sub_epi16(A1, bit_fix); + const __m128i A3 = _mm_srai_epi16(A2, 1); + const __m128i A4 = _mm_add_epi16(avg, A3); + const __m128i pred = _mm_packus_epi16(A4, A4); + const __m128i res = _mm_sub_epi8(src, pred); + _mm_storel_epi64((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsSub_C[13](in + i, upper + i, num_pixels - i, out + i); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE2(void) { + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_SSE2; + VP8LTransformColor = TransformColor_SSE2; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms_SSE2; + VP8LCollectColorRedTransforms = CollectColorRedTransforms_SSE2; + VP8LAddVector = AddVector_SSE2; + VP8LAddVectorEq = AddVectorEq_SSE2; +#if !defined(DONT_USE_COMBINED_SHANNON_ENTROPY_SSE2_FUNC) + VP8LCombinedShannonEntropy = CombinedShannonEntropy_SSE2; +#endif + VP8LVectorMismatch = VectorMismatch_SSE2; + VP8LBundleColorMap = BundleColorMap_SSE2; + + VP8LPredictorsSub[0] = PredictorSub0_SSE2; + VP8LPredictorsSub[1] = PredictorSub1_SSE2; + VP8LPredictorsSub[2] = PredictorSub2_SSE2; + VP8LPredictorsSub[3] = PredictorSub3_SSE2; + VP8LPredictorsSub[4] = PredictorSub4_SSE2; + VP8LPredictorsSub[5] = PredictorSub5_SSE2; + VP8LPredictorsSub[6] = PredictorSub6_SSE2; + VP8LPredictorsSub[7] = PredictorSub7_SSE2; + VP8LPredictorsSub[8] = PredictorSub8_SSE2; + VP8LPredictorsSub[9] = PredictorSub9_SSE2; + VP8LPredictorsSub[10] = PredictorSub10_SSE2; + VP8LPredictorsSub[11] = PredictorSub11_SSE2; + VP8LPredictorsSub[12] = PredictorSub12_SSE2; + VP8LPredictorsSub[13] = PredictorSub13_SSE2; + VP8LPredictorsSub[14] = PredictorSub0_SSE2; // <- padding security sentinels + VP8LPredictorsSub[15] = PredictorSub0_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse41.c new file mode 100644 index 0000000000..7ab83c2604 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_enc_sse41.c @@ -0,0 +1,205 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4.1 variant of methods for lossless encoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include +#include "src/dsp/lossless.h" + +//------------------------------------------------------------------------------ +// Cost operations. + +static WEBP_INLINE uint32_t HorizontalSum_SSE41(__m128i cost) { + cost = _mm_add_epi32(cost, _mm_srli_si128(cost, 8)); + cost = _mm_add_epi32(cost, _mm_srli_si128(cost, 4)); + return _mm_cvtsi128_si32(cost); +} + +static uint32_t ExtraCost_SSE41(const uint32_t* const a, int length) { + int i; + __m128i cost = _mm_set_epi32(2 * a[7], 2 * a[6], a[5], a[4]); + assert(length % 8 == 0); + + for (i = 8; i + 8 <= length; i += 8) { + const int j = (i - 2) >> 1; + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); + const __m128i w = _mm_set_epi32(j + 3, j + 2, j + 1, j); + const __m128i a2 = _mm_hadd_epi32(a0, a1); + const __m128i mul = _mm_mullo_epi32(a2, w); + cost = _mm_add_epi32(mul, cost); + } + return HorizontalSum_SSE41(cost); +} + +static uint32_t ExtraCostCombined_SSE41(const uint32_t* const a, + const uint32_t* const b, int length) { + int i; + __m128i cost = _mm_add_epi32(_mm_set_epi32(2 * a[7], 2 * a[6], a[5], a[4]), + _mm_set_epi32(2 * b[7], 2 * b[6], b[5], b[4])); + assert(length % 8 == 0); + + for (i = 8; i + 8 <= length; i += 8) { + const int j = (i - 2) >> 1; + const __m128i a0 = _mm_loadu_si128((const __m128i*)&a[i]); + const __m128i a1 = _mm_loadu_si128((const __m128i*)&a[i + 4]); + const __m128i b0 = _mm_loadu_si128((const __m128i*)&b[i]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&b[i + 4]); + const __m128i w = _mm_set_epi32(j + 3, j + 2, j + 1, j); + const __m128i a2 = _mm_hadd_epi32(a0, a1); + const __m128i b2 = _mm_hadd_epi32(b0, b1); + const __m128i mul = _mm_mullo_epi32(_mm_add_epi32(a2, b2), w); + cost = _mm_add_epi32(mul, cost); + } + return HorizontalSum_SSE41(cost); +} + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void SubtractGreenFromBlueAndRed_SSE41(uint32_t* argb_data, + int num_pixels) { + int i; + const __m128i kCstShuffle = _mm_set_epi8(-1, 13, -1, 13, -1, 9, -1, 9, + -1, 5, -1, 5, -1, 1, -1, 1); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((__m128i*)&argb_data[i]); + const __m128i in_0g0g = _mm_shuffle_epi8(in, kCstShuffle); + const __m128i out = _mm_sub_epi8(in, in_0g0g); + _mm_storeu_si128((__m128i*)&argb_data[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LSubtractGreenFromBlueAndRed_C(argb_data + i, num_pixels - i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +// For sign-extended multiplying constants, pre-shifted by 5: +#define CST_5b(X) (((int16_t)((uint16_t)(X) << 8)) >> 5) + +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + +static void CollectColorBlueTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + int histo[]) { + const __m128i mult = + MK_CST_16(CST_5b(red_to_blue) + 256,CST_5b(green_to_blue)); + const __m128i perm = + _mm_setr_epi8(-1, 1, -1, 2, -1, 5, -1, 6, -1, 9, -1, 10, -1, 13, -1, 14); + if (tile_width >= 4) { + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + const __m128i A1 = _mm_loadu_si128((const __m128i*)src); + const __m128i B1 = _mm_shuffle_epi8(A1, perm); + const __m128i C1 = _mm_mulhi_epi16(B1, mult); + const __m128i D1 = _mm_sub_epi16(A1, C1); + __m128i E = _mm_add_epi16(_mm_srli_epi32(D1, 16), D1); + int x; + for (x = 4; x + 4 <= tile_width; x += 4) { + const __m128i A2 = _mm_loadu_si128((const __m128i*)(src + x)); + __m128i B2, C2, D2; + ++histo[_mm_extract_epi8(E, 0)]; + B2 = _mm_shuffle_epi8(A2, perm); + ++histo[_mm_extract_epi8(E, 4)]; + C2 = _mm_mulhi_epi16(B2, mult); + ++histo[_mm_extract_epi8(E, 8)]; + D2 = _mm_sub_epi16(A2, C2); + ++histo[_mm_extract_epi8(E, 12)]; + E = _mm_add_epi16(_mm_srli_epi32(D2, 16), D2); + } + ++histo[_mm_extract_epi8(E, 0)]; + ++histo[_mm_extract_epi8(E, 4)]; + ++histo[_mm_extract_epi8(E, 8)]; + ++histo[_mm_extract_epi8(E, 12)]; + } + } + { + const int left_over = tile_width & 3; + if (left_over > 0) { + VP8LCollectColorBlueTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, + green_to_blue, red_to_blue, histo); + } + } +} + +static void CollectColorRedTransforms_SSE41(const uint32_t* argb, int stride, + int tile_width, int tile_height, + int green_to_red, int histo[]) { + + const __m128i mult = MK_CST_16(0, CST_5b(green_to_red)); + const __m128i mask_g = _mm_set1_epi32(0x0000ff00); + if (tile_width >= 4) { + int y; + for (y = 0; y < tile_height; ++y) { + const uint32_t* const src = argb + y * stride; + const __m128i A1 = _mm_loadu_si128((const __m128i*)src); + const __m128i B1 = _mm_and_si128(A1, mask_g); + const __m128i C1 = _mm_madd_epi16(B1, mult); + __m128i D = _mm_sub_epi16(A1, C1); + int x; + for (x = 4; x + 4 <= tile_width; x += 4) { + const __m128i A2 = _mm_loadu_si128((const __m128i*)(src + x)); + __m128i B2, C2; + ++histo[_mm_extract_epi8(D, 2)]; + B2 = _mm_and_si128(A2, mask_g); + ++histo[_mm_extract_epi8(D, 6)]; + C2 = _mm_madd_epi16(B2, mult); + ++histo[_mm_extract_epi8(D, 10)]; + ++histo[_mm_extract_epi8(D, 14)]; + D = _mm_sub_epi16(A2, C2); + } + ++histo[_mm_extract_epi8(D, 2)]; + ++histo[_mm_extract_epi8(D, 6)]; + ++histo[_mm_extract_epi8(D, 10)]; + ++histo[_mm_extract_epi8(D, 14)]; + } + } + { + const int left_over = tile_width & 3; + if (left_over > 0) { + VP8LCollectColorRedTransforms_C(argb + tile_width - left_over, stride, + left_over, tile_height, green_to_red, + histo); + } + } +} + +#undef MK_CST_16 + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LEncDspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LEncDspInitSSE41(void) { + VP8LExtraCost = ExtraCost_SSE41; + VP8LExtraCostCombined = ExtraCostCombined_SSE41; + VP8LSubtractGreenFromBlueAndRed = SubtractGreenFromBlueAndRed_SSE41; + VP8LCollectColorBlueTransforms = CollectColorBlueTransforms_SSE41; + VP8LCollectColorRedTransforms = CollectColorRedTransforms_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8LEncDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_mips_dsp_r2.c new file mode 100644 index 0000000000..bfe5ea6b38 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_mips_dsp_r2.c @@ -0,0 +1,701 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" + +#define MAP_COLOR_FUNCS(FUNC_NAME, TYPE, GET_INDEX, GET_VALUE) \ +static void FUNC_NAME(const TYPE* src, \ + const uint32_t* const color_map, \ + TYPE* dst, int y_start, int y_end, \ + int width) { \ + int y; \ + for (y = y_start; y < y_end; ++y) { \ + int x; \ + for (x = 0; x < (width >> 2); ++x) { \ + int tmp1, tmp2, tmp3, tmp4; \ + __asm__ volatile ( \ + ".ifc " #TYPE ", uint8_t \n\t" \ + "lbu %[tmp1], 0(%[src]) \n\t" \ + "lbu %[tmp2], 1(%[src]) \n\t" \ + "lbu %[tmp3], 2(%[src]) \n\t" \ + "lbu %[tmp4], 3(%[src]) \n\t" \ + "addiu %[src], %[src], 4 \n\t" \ + ".endif \n\t" \ + ".ifc " #TYPE ", uint32_t \n\t" \ + "lw %[tmp1], 0(%[src]) \n\t" \ + "lw %[tmp2], 4(%[src]) \n\t" \ + "lw %[tmp3], 8(%[src]) \n\t" \ + "lw %[tmp4], 12(%[src]) \n\t" \ + "ext %[tmp1], %[tmp1], 8, 8 \n\t" \ + "ext %[tmp2], %[tmp2], 8, 8 \n\t" \ + "ext %[tmp3], %[tmp3], 8, 8 \n\t" \ + "ext %[tmp4], %[tmp4], 8, 8 \n\t" \ + "addiu %[src], %[src], 16 \n\t" \ + ".endif \n\t" \ + "sll %[tmp1], %[tmp1], 2 \n\t" \ + "sll %[tmp2], %[tmp2], 2 \n\t" \ + "sll %[tmp3], %[tmp3], 2 \n\t" \ + "sll %[tmp4], %[tmp4], 2 \n\t" \ + "lwx %[tmp1], %[tmp1](%[color_map]) \n\t" \ + "lwx %[tmp2], %[tmp2](%[color_map]) \n\t" \ + "lwx %[tmp3], %[tmp3](%[color_map]) \n\t" \ + "lwx %[tmp4], %[tmp4](%[color_map]) \n\t" \ + ".ifc " #TYPE ", uint8_t \n\t" \ + "ext %[tmp1], %[tmp1], 8, 8 \n\t" \ + "ext %[tmp2], %[tmp2], 8, 8 \n\t" \ + "ext %[tmp3], %[tmp3], 8, 8 \n\t" \ + "ext %[tmp4], %[tmp4], 8, 8 \n\t" \ + "sb %[tmp1], 0(%[dst]) \n\t" \ + "sb %[tmp2], 1(%[dst]) \n\t" \ + "sb %[tmp3], 2(%[dst]) \n\t" \ + "sb %[tmp4], 3(%[dst]) \n\t" \ + "addiu %[dst], %[dst], 4 \n\t" \ + ".endif \n\t" \ + ".ifc " #TYPE ", uint32_t \n\t" \ + "sw %[tmp1], 0(%[dst]) \n\t" \ + "sw %[tmp2], 4(%[dst]) \n\t" \ + "sw %[tmp3], 8(%[dst]) \n\t" \ + "sw %[tmp4], 12(%[dst]) \n\t" \ + "addiu %[dst], %[dst], 16 \n\t" \ + ".endif \n\t" \ + : [tmp1]"=&r"(tmp1), [tmp2]"=&r"(tmp2), [tmp3]"=&r"(tmp3), \ + [tmp4]"=&r"(tmp4), [src]"+&r"(src), [dst]"+r"(dst) \ + : [color_map]"r"(color_map) \ + : "memory" \ + ); \ + } \ + for (x = 0; x < (width & 3); ++x) { \ + *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ + } \ + } \ +} + +MAP_COLOR_FUNCS(MapARGB_MIPSdspR2, uint32_t, VP8GetARGBIndex, VP8GetARGBValue) +MAP_COLOR_FUNCS(MapAlpha_MIPSdspR2, uint8_t, VP8GetAlphaIndex, VP8GetAlphaValue) + +#undef MAP_COLOR_FUNCS + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "preceu.ph.qbr %[temp1], %[c0] \n\t" + "preceu.ph.qbl %[temp2], %[c0] \n\t" + "preceu.ph.qbr %[temp3], %[c1] \n\t" + "preceu.ph.qbl %[temp4], %[c1] \n\t" + "preceu.ph.qbr %[temp5], %[c2] \n\t" + "preceu.ph.qbl %[temp0], %[c2] \n\t" + "subq.ph %[temp3], %[temp3], %[temp5] \n\t" + "subq.ph %[temp4], %[temp4], %[temp0] \n\t" + "addq.ph %[temp1], %[temp1], %[temp3] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp2], %[temp2], %[temp1] \n\t" + : [temp0]"=r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5) + : [c0]"r"(c0), [c1]"r"(c1), [c2]"r"(c2) + : "memory" + ); + return temp2; +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "adduh.qb %[temp5], %[c0], %[c1] \n\t" + "preceu.ph.qbr %[temp3], %[c2] \n\t" + "preceu.ph.qbr %[temp1], %[temp5] \n\t" + "preceu.ph.qbl %[temp2], %[temp5] \n\t" + "preceu.ph.qbl %[temp4], %[c2] \n\t" + "subq.ph %[temp3], %[temp1], %[temp3] \n\t" + "subq.ph %[temp4], %[temp2], %[temp4] \n\t" + "shrl.ph %[temp5], %[temp3], 15 \n\t" + "shrl.ph %[temp0], %[temp4], 15 \n\t" + "addq.ph %[temp3], %[temp3], %[temp5] \n\t" + "addq.ph %[temp4], %[temp0], %[temp4] \n\t" + "shra.ph %[temp3], %[temp3], 1 \n\t" + "shra.ph %[temp4], %[temp4], 1 \n\t" + "addq.ph %[temp1], %[temp1], %[temp3] \n\t" + "addq.ph %[temp2], %[temp2], %[temp4] \n\t" + "shll_s.ph %[temp1], %[temp1], 7 \n\t" + "shll_s.ph %[temp2], %[temp2], 7 \n\t" + "precrqu_s.qb.ph %[temp1], %[temp2], %[temp1] \n\t" + : [temp0]"=r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=r"(temp4), [temp5]"=&r"(temp5) + : [c0]"r"(c0), [c1]"r"(c1), [c2]"r"(c2) + : "memory" + ); + return temp1; +} + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + int temp0, temp1, temp2, temp3, temp4, temp5; + __asm__ volatile ( + "cmpgdu.lt.qb %[temp1], %[c], %[b] \n\t" + "pick.qb %[temp1], %[b], %[c] \n\t" + "pick.qb %[temp2], %[c], %[b] \n\t" + "cmpgdu.lt.qb %[temp4], %[c], %[a] \n\t" + "pick.qb %[temp4], %[a], %[c] \n\t" + "pick.qb %[temp5], %[c], %[a] \n\t" + "subu.qb %[temp3], %[temp1], %[temp2] \n\t" + "subu.qb %[temp0], %[temp4], %[temp5] \n\t" + "raddu.w.qb %[temp3], %[temp3] \n\t" + "raddu.w.qb %[temp0], %[temp0] \n\t" + "subu %[temp3], %[temp3], %[temp0] \n\t" + "slti %[temp0], %[temp3], 0x1 \n\t" + "movz %[a], %[b], %[temp0] \n\t" + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp0]"=&r"(temp0), + [a]"+&r"(a) + : [b]"r"(b), [c]"r"(c) + ); + return a; +} + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + __asm__ volatile ( + "adduh.qb %[a0], %[a0], %[a1] \n\t" + : [a0]"+r"(a0) + : [a1]"r"(a1) + ); + return a0; +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + return Average2(Average2(a0, a2), a1); +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + return Average2(Average2(a0, a1), Average2(a2, a3)); +} + +static uint32_t Predictor5_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return Average3(*left, top[0], top[1]); +} + +static uint32_t Predictor6_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return Average2(*left, top[-1]); +} + +static uint32_t Predictor7_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return Average2(*left, top[0]); +} + +static uint32_t Predictor8_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return Average2(top[-1], top[0]); +} + +static uint32_t Predictor9_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return Average2(top[0], top[1]); +} + +static uint32_t Predictor10_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return Average4(*left, top[-1], top[0], top[1]); +} + +static uint32_t Predictor11_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return Select(top[0], *left, top[-1]); +} + +static uint32_t Predictor12_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return ClampedAddSubtractFull(*left, top[0], top[-1]); +} + +static uint32_t Predictor13_MIPSdspR2(const uint32_t* const left, + const uint32_t* const top) { + return ClampedAddSubtractHalf(*left, top[0], top[-1]); +} + +// Add green to blue and red channels (i.e. perform the inverse transform of +// 'subtract green'). +static void AddGreenToBlueAndRed_MIPSdspR2(const uint32_t* src, int num_pixels, + uint32_t* dst) { + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "ext %[temp5], %[temp1], 8, 8 \n\t" + "ext %[temp6], %[temp2], 8, 8 \n\t" + "ext %[temp7], %[temp3], 8, 8 \n\t" + "addiu %[src], %[src], 16 \n\t" + "addiu %[dst], %[dst], 16 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "replv.ph %[temp5], %[temp5] \n\t" + "replv.ph %[temp6], %[temp6] \n\t" + "replv.ph %[temp7], %[temp7] \n\t" + "addu.qb %[temp0], %[temp0], %[temp4] \n\t" + "addu.qb %[temp1], %[temp1], %[temp5] \n\t" + "addu.qb %[temp2], %[temp2], %[temp6] \n\t" + "addu.qb %[temp3], %[temp3], %[temp7] \n\t" + "sw %[temp0], -16(%[dst]) \n\t" + "sw %[temp1], -12(%[dst]) \n\t" + "sw %[temp2], -8(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " sw %[temp3], -4(%[dst]) \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "ext %[temp4], %[temp0], 8, 8 \n\t" + "replv.ph %[temp4], %[temp4] \n\t" + "addu.qb %[temp0], %[temp0], %[temp4] \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sw %[temp0], -4(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [dst]"+&r"(dst), [src]"+&r"(src), [temp0]"=&r"(temp0), + [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), + [temp7]"=&r"(temp7) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void TransformColorInverse_MIPSdspR2(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + uint32_t argb, argb1, new_red; + const uint32_t G_to_R = m->green_to_red_; + const uint32_t G_to_B = m->green_to_blue_; + const uint32_t R_to_B = m->red_to_blue_; + const uint32_t* const p_loop_end = src + (num_pixels & ~1); + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop_end], 1f \n\t" + " nop \n\t" + "replv.ph %[temp0], %[G_to_R] \n\t" + "replv.ph %[temp1], %[G_to_B] \n\t" + "replv.ph %[temp2], %[R_to_B] \n\t" + "shll.ph %[temp0], %[temp0], 8 \n\t" + "shll.ph %[temp1], %[temp1], 8 \n\t" + "shll.ph %[temp2], %[temp2], 8 \n\t" + "shra.ph %[temp0], %[temp0], 8 \n\t" + "shra.ph %[temp1], %[temp1], 8 \n\t" + "shra.ph %[temp2], %[temp2], 8 \n\t" + "0: \n\t" + "lw %[argb], 0(%[src]) \n\t" + "lw %[argb1], 4(%[src]) \n\t" + "sw %[argb], 0(%[dst]) \n\t" + "sw %[argb1], 4(%[dst]) \n\t" + "addiu %[src], %[src], 8 \n\t" + "addiu %[dst], %[dst], 8 \n\t" + "precrq.qb.ph %[temp3], %[argb], %[argb1] \n\t" + "preceu.ph.qbra %[temp3], %[temp3] \n\t" + "shll.ph %[temp3], %[temp3], 8 \n\t" + "shra.ph %[temp3], %[temp3], 8 \n\t" + "mul.ph %[temp5], %[temp3], %[temp0] \n\t" + "mul.ph %[temp3], %[temp3], %[temp1] \n\t" + "precrq.ph.w %[new_red], %[argb], %[argb1] \n\t" + "ins %[argb1], %[argb], 16, 16 \n\t" + "shra.ph %[temp5], %[temp5], 5 \n\t" + "shra.ph %[temp3], %[temp3], 5 \n\t" + "addu.ph %[new_red], %[new_red], %[temp5] \n\t" + "addu.ph %[argb1], %[argb1], %[temp3] \n\t" + "preceu.ph.qbra %[temp5], %[new_red] \n\t" + "shll.ph %[temp4], %[temp5], 8 \n\t" + "shra.ph %[temp4], %[temp4], 8 \n\t" + "mul.ph %[temp4], %[temp4], %[temp2] \n\t" + "sb %[temp5], -2(%[dst]) \n\t" + "sra %[temp5], %[temp5], 16 \n\t" + "shra.ph %[temp4], %[temp4], 5 \n\t" + "addu.ph %[argb1], %[argb1], %[temp4] \n\t" + "preceu.ph.qbra %[temp3], %[argb1] \n\t" + "sb %[temp5], -6(%[dst]) \n\t" + "sb %[temp3], -4(%[dst]) \n\t" + "sra %[temp3], %[temp3], 16 \n\t" + "bne %[src], %[p_loop_end], 0b \n\t" + " sb %[temp3], -8(%[dst]) \n\t" + "1: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [new_red]"=&r"(new_red), [argb]"=&r"(argb), + [argb1]"=&r"(argb1), [dst]"+&r"(dst), [src]"+&r"(src) + : [G_to_R]"r"(G_to_R), [R_to_B]"r"(R_to_B), + [G_to_B]"r"(G_to_B), [p_loop_end]"r"(p_loop_end) + : "memory", "hi", "lo" + ); + + // Fall-back to C-version for left-overs. + if (num_pixels & 1) VP8LTransformColorInverse_C(m, src, 1, dst); +} + +static void ConvertBGRAToRGB_MIPSdspR2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ins %[temp3], %[temp2], 24, 8 \n\t" + "sll %[temp2], %[temp2], 8 \n\t" + "rotr %[temp3], %[temp3], 16 \n\t" + "ins %[temp2], %[temp1], 0, 16 \n\t" + "sll %[temp1], %[temp1], 8 \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "balign %[temp0], %[temp1], 1 \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "usw %[temp3], 8(%[dst]) \n\t" + "rotr %[temp0], %[temp0], 16 \n\t" + "usw %[temp2], 4(%[dst]) \n\t" + "addiu %[src], %[src], 16 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 12 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "wsbh %[temp1], %[temp0] \n\t" + "addiu %[dst], %[dst], 3 \n\t" + "ush %[temp1], -2(%[dst]) \n\t" + "sra %[temp0], %[temp0], 16 \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sb %[temp0], -3(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGBA_MIPSdspR2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "wsbh %[temp1], %[temp1] \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "addiu %[src], %[src], 16 \n\t" + "balign %[temp0], %[temp0], 1 \n\t" + "balign %[temp1], %[temp1], 1 \n\t" + "balign %[temp2], %[temp2], 1 \n\t" + "balign %[temp3], %[temp3], 1 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp1], 4(%[dst]) \n\t" + "usw %[temp2], 8(%[dst]) \n\t" + "usw %[temp3], 12(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 16 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "wsbh %[temp0], %[temp0] \n\t" + "addiu %[src], %[src], 4 \n\t" + "balign %[temp0], %[temp0], 1 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 4 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGBA4444_MIPSdspR2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 28, 4 \n\t" + "ext %[temp5], %[temp0], 12, 4 \n\t" + "ins %[temp0], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp1], 28, 4 \n\t" + "ins %[temp0], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp1], 12, 4 \n\t" + "ins %[temp1], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp2], 28, 4 \n\t" + "ins %[temp1], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp2], 12, 4 \n\t" + "ins %[temp2], %[temp4], 0, 4 \n\t" + "ext %[temp4], %[temp3], 28, 4 \n\t" + "ins %[temp2], %[temp5], 16, 4 \n\t" + "ext %[temp5], %[temp3], 12, 4 \n\t" + "ins %[temp3], %[temp4], 0, 4 \n\t" + "precr.qb.ph %[temp1], %[temp1], %[temp0] \n\t" + "ins %[temp3], %[temp5], 16, 4 \n\t" + "addiu %[src], %[src], 16 \n\t" + "precr.qb.ph %[temp3], %[temp3], %[temp2] \n\t" +#if (WEBP_SWAP_16BIT_CSP == 1) + "usw %[temp1], 0(%[dst]) \n\t" + "usw %[temp3], 4(%[dst]) \n\t" +#else + "wsbh %[temp1], %[temp1] \n\t" + "wsbh %[temp3], %[temp3] \n\t" + "usw %[temp1], 0(%[dst]) \n\t" + "usw %[temp3], 4(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 8 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ext %[temp4], %[temp0], 28, 4 \n\t" + "ext %[temp5], %[temp0], 12, 4 \n\t" + "ins %[temp0], %[temp4], 0, 4 \n\t" + "ins %[temp0], %[temp5], 16, 4 \n\t" + "addiu %[src], %[src], 4 \n\t" + "precr.qb.ph %[temp0], %[temp0], %[temp0] \n\t" +#if (WEBP_SWAP_16BIT_CSP == 1) + "ush %[temp0], 0(%[dst]) \n\t" +#else + "wsbh %[temp0], %[temp0] \n\t" + "ush %[temp0], 0(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 2 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToRGB565_MIPSdspR2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3, temp4, temp5; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 16 \n\t" + "ext %[temp5], %[temp0], 5, 11 \n\t" + "ext %[temp0], %[temp0], 3, 5 \n\t" + "ins %[temp4], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp1], 5, 11 \n\t" + "ins %[temp4], %[temp0], 0, 5 \n\t" + "ext %[temp0], %[temp1], 8, 16 \n\t" + "ext %[temp1], %[temp1], 3, 5 \n\t" + "ins %[temp0], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp2], 5, 11 \n\t" + "ins %[temp0], %[temp1], 0, 5 \n\t" + "ext %[temp1], %[temp2], 8, 16 \n\t" + "ext %[temp2], %[temp2], 3, 5 \n\t" + "ins %[temp1], %[temp5], 0, 11 \n\t" + "ext %[temp5], %[temp3], 5, 11 \n\t" + "ins %[temp1], %[temp2], 0, 5 \n\t" + "ext %[temp2], %[temp3], 8, 16 \n\t" + "ext %[temp3], %[temp3], 3, 5 \n\t" + "ins %[temp2], %[temp5], 0, 11 \n\t" + "append %[temp0], %[temp4], 16 \n\t" + "ins %[temp2], %[temp3], 0, 5 \n\t" + "addiu %[src], %[src], 16 \n\t" + "append %[temp2], %[temp1], 16 \n\t" +#if (WEBP_SWAP_16BIT_CSP == 1) + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp2], 4(%[dst]) \n\t" +#else + "wsbh %[temp0], %[temp0] \n\t" + "wsbh %[temp2], %[temp2] \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp2], 4(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 8 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "ext %[temp4], %[temp0], 8, 16 \n\t" + "ext %[temp5], %[temp0], 5, 11 \n\t" + "ext %[temp0], %[temp0], 3, 5 \n\t" + "ins %[temp4], %[temp5], 0, 11 \n\t" + "addiu %[src], %[src], 4 \n\t" + "ins %[temp4], %[temp0], 0, 5 \n\t" +#if (WEBP_SWAP_16BIT_CSP == 1) + "ush %[temp4], 0(%[dst]) \n\t" +#else + "wsbh %[temp4], %[temp4] \n\t" + "ush %[temp4], 0(%[dst]) \n\t" +#endif + "bne %[src], %[p_loop2_end], 1b \n\t" + " addiu %[dst], %[dst], 2 \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), + [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +static void ConvertBGRAToBGR_MIPSdspR2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int temp0, temp1, temp2, temp3; + const uint32_t* const p_loop1_end = src + (num_pixels & ~3); + const uint32_t* const p_loop2_end = src + num_pixels; + __asm__ volatile ( + ".set push \n\t" + ".set noreorder \n\t" + "beq %[src], %[p_loop1_end], 3f \n\t" + " nop \n\t" + "0: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "lw %[temp1], 4(%[src]) \n\t" + "lw %[temp2], 8(%[src]) \n\t" + "lw %[temp3], 12(%[src]) \n\t" + "ins %[temp0], %[temp1], 24, 8 \n\t" + "sra %[temp1], %[temp1], 8 \n\t" + "ins %[temp1], %[temp2], 16, 16 \n\t" + "sll %[temp2], %[temp2], 8 \n\t" + "balign %[temp3], %[temp2], 1 \n\t" + "addiu %[src], %[src], 16 \n\t" + "usw %[temp0], 0(%[dst]) \n\t" + "usw %[temp1], 4(%[dst]) \n\t" + "usw %[temp3], 8(%[dst]) \n\t" + "bne %[src], %[p_loop1_end], 0b \n\t" + " addiu %[dst], %[dst], 12 \n\t" + "3: \n\t" + "beq %[src], %[p_loop2_end], 2f \n\t" + " nop \n\t" + "1: \n\t" + "lw %[temp0], 0(%[src]) \n\t" + "addiu %[src], %[src], 4 \n\t" + "addiu %[dst], %[dst], 3 \n\t" + "ush %[temp0], -3(%[dst]) \n\t" + "sra %[temp0], %[temp0], 16 \n\t" + "bne %[src], %[p_loop2_end], 1b \n\t" + " sb %[temp0], -1(%[dst]) \n\t" + "2: \n\t" + ".set pop \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), + [temp3]"=&r"(temp3), [dst]"+&r"(dst), [src]"+&r"(src) + : [p_loop1_end]"r"(p_loop1_end), [p_loop2_end]"r"(p_loop2_end) + : "memory" + ); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitMIPSdspR2(void) { + VP8LMapColor32b = MapARGB_MIPSdspR2; + VP8LMapColor8b = MapAlpha_MIPSdspR2; + + VP8LPredictors[5] = Predictor5_MIPSdspR2; + VP8LPredictors[6] = Predictor6_MIPSdspR2; + VP8LPredictors[7] = Predictor7_MIPSdspR2; + VP8LPredictors[8] = Predictor8_MIPSdspR2; + VP8LPredictors[9] = Predictor9_MIPSdspR2; + VP8LPredictors[10] = Predictor10_MIPSdspR2; + VP8LPredictors[11] = Predictor11_MIPSdspR2; + VP8LPredictors[12] = Predictor12_MIPSdspR2; + VP8LPredictors[13] = Predictor13_MIPSdspR2; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_MIPSdspR2; + VP8LTransformColorInverse = TransformColorInverse_MIPSdspR2; + + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_MIPSdspR2; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_MIPSdspR2; + VP8LConvertBGRAToRGBA4444 = ConvertBGRAToRGBA4444_MIPSdspR2; + VP8LConvertBGRAToRGB565 = ConvertBGRAToRGB565_MIPSdspR2; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(VP8LDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_msa.c new file mode 100644 index 0000000000..9f5472078d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_msa.c @@ -0,0 +1,356 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA variant of methods for lossless decoder +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "src/dsp/lossless.h" +#include "src/dsp/msa_macro.h" + +//------------------------------------------------------------------------------ +// Colorspace conversion functions + +#define CONVERT16_BGRA_XXX(psrc, pdst, m0, m1, m2) do { \ + v16u8 src0, src1, src2, src3, dst0, dst1, dst2; \ + LD_UB4(psrc, 16, src0, src1, src2, src3); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + dst2 = VSHF_UB(src2, src3, m2); \ + ST_UB2(dst0, dst1, pdst, 16); \ + ST_UB(dst2, pdst + 32); \ +} while (0) + +#define CONVERT12_BGRA_XXX(psrc, pdst, m0, m1, m2) do { \ + uint32_t pix_w; \ + v16u8 src0, src1, src2, dst0, dst1, dst2; \ + LD_UB3(psrc, 16, src0, src1, src2); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + dst2 = VSHF_UB(src2, src2, m2); \ + ST_UB2(dst0, dst1, pdst, 16); \ + pix_w = __msa_copy_s_w((v4i32)dst2, 0); \ + SW(pix_w, pdst + 32); \ +} while (0) + +#define CONVERT8_BGRA_XXX(psrc, pdst, m0, m1) do { \ + uint64_t pix_d; \ + v16u8 src0, src1, src2 = { 0 }, dst0, dst1; \ + LD_UB2(psrc, 16, src0, src1); \ + VSHF_B2_UB(src0, src1, src1, src2, m0, m1, dst0, dst1); \ + ST_UB(dst0, pdst); \ + pix_d = __msa_copy_s_d((v2i64)dst1, 0); \ + SD(pix_d, pdst + 16); \ +} while (0) + +#define CONVERT4_BGRA_XXX(psrc, pdst, m) do { \ + const v16u8 src0 = LD_UB(psrc); \ + const v16u8 dst0 = VSHF_UB(src0, src0, m); \ + uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); \ + uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); \ + SD(pix_d, pdst + 0); \ + SW(pix_w, pdst + 8); \ +} while (0) + +#define CONVERT1_BGRA_BGR(psrc, pdst) do { \ + const int32_t b = (psrc)[0]; \ + const int32_t g = (psrc)[1]; \ + const int32_t r = (psrc)[2]; \ + (pdst)[0] = b; \ + (pdst)[1] = g; \ + (pdst)[2] = r; \ +} while (0) + +#define CONVERT1_BGRA_RGB(psrc, pdst) do { \ + const int32_t b = (psrc)[0]; \ + const int32_t g = (psrc)[1]; \ + const int32_t r = (psrc)[2]; \ + (pdst)[0] = r; \ + (pdst)[1] = g; \ + (pdst)[2] = b; \ +} while (0) + +#define TRANSFORM_COLOR_INVERSE_8(src0, src1, dst0, dst1, \ + c0, c1, mask0, mask1) do { \ + v8i16 g0, g1, t0, t1, t2, t3; \ + v4i32 t4, t5; \ + VSHF_B2_SH(src0, src0, src1, src1, mask0, mask0, g0, g1); \ + DOTP_SB2_SH(g0, g1, c0, c0, t0, t1); \ + SRAI_H2_SH(t0, t1, 5); \ + t0 = __msa_addv_h(t0, (v8i16)src0); \ + t1 = __msa_addv_h(t1, (v8i16)src1); \ + t4 = __msa_srli_w((v4i32)t0, 16); \ + t5 = __msa_srli_w((v4i32)t1, 16); \ + DOTP_SB2_SH(t4, t5, c1, c1, t2, t3); \ + SRAI_H2_SH(t2, t3, 5); \ + ADD2(t0, t2, t1, t3, t0, t1); \ + VSHF_B2_UB(src0, t0, src1, t1, mask1, mask1, dst0, dst1); \ +} while (0) + +#define TRANSFORM_COLOR_INVERSE_4(src, dst, c0, c1, mask0, mask1) do { \ + const v16i8 g0 = VSHF_SB(src, src, mask0); \ + v8i16 t0 = __msa_dotp_s_h(c0, g0); \ + v8i16 t1; \ + v4i32 t2; \ + t0 = SRAI_H(t0, 5); \ + t0 = __msa_addv_h(t0, (v8i16)src); \ + t2 = __msa_srli_w((v4i32)t0, 16); \ + t1 = __msa_dotp_s_h(c1, (v16i8)t2); \ + t1 = SRAI_H(t1, 5); \ + t0 = t0 + t1; \ + dst = VSHF_UB(src, t0, mask1); \ +} while (0) + +static void ConvertBGRAToRGBA_MSA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + int i; + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + v16u8 src0, dst0; + const v16u8 mask = { 2, 1, 0, 3, 6, 5, 4, 7, 10, 9, 8, 11, 14, 13, 12, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(ptemp_src, 16, src0, src1); + VSHF_B2_UB(src0, src0, src1, src1, mask, mask, dst0, dst1); + ST_UB2(dst0, dst1, ptemp_dst, 16); + ptemp_src += 32; + ptemp_dst += 32; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(ptemp_src); + dst0 = VSHF_UB(src0, src0, mask); + ST_UB(dst0, ptemp_dst); + ptemp_src += 16; + ptemp_dst += 16; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = ptemp_src[2]; + const uint8_t g = ptemp_src[1]; + const uint8_t r = ptemp_src[0]; + const uint8_t a = ptemp_src[3]; + ptemp_dst[0] = b; + ptemp_dst[1] = g; + ptemp_dst[2] = r; + ptemp_dst[3] = a; + ptemp_src += 4; + ptemp_dst += 4; + } + } +} + +static void ConvertBGRAToBGR_MSA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + const v16u8 mask0 = { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, + 16, 17, 18, 20 }; + const v16u8 mask1 = { 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, + 21, 22, 24, 25 }; + const v16u8 mask2 = { 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 24, 25, + 26, 28, 29, 30 }; + + while (num_pixels >= 16) { + CONVERT16_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 64; + ptemp_dst += 48; + num_pixels -= 16; + } + if (num_pixels > 0) { + if (num_pixels >= 12) { + CONVERT12_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 48; + ptemp_dst += 36; + num_pixels -= 12; + } else if (num_pixels >= 8) { + CONVERT8_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1); + ptemp_src += 32; + ptemp_dst += 24; + num_pixels -= 8; + } else if (num_pixels >= 4) { + CONVERT4_BGRA_XXX(ptemp_src, ptemp_dst, mask0); + ptemp_src += 16; + ptemp_dst += 12; + num_pixels -= 4; + } + if (num_pixels == 3) { + CONVERT1_BGRA_BGR(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_BGR(ptemp_src + 4, ptemp_dst + 3); + CONVERT1_BGRA_BGR(ptemp_src + 8, ptemp_dst + 6); + } else if (num_pixels == 2) { + CONVERT1_BGRA_BGR(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_BGR(ptemp_src + 4, ptemp_dst + 3); + } else if (num_pixels == 1) { + CONVERT1_BGRA_BGR(ptemp_src, ptemp_dst); + } + } +} + +static void ConvertBGRAToRGB_MSA(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint8_t* ptemp_src = (const uint8_t*)src; + uint8_t* ptemp_dst = (uint8_t*)dst; + const v16u8 mask0 = { 2, 1, 0, 6, 5, 4, 10, 9, 8, 14, 13, 12, + 18, 17, 16, 22 }; + const v16u8 mask1 = { 5, 4, 10, 9, 8, 14, 13, 12, 18, 17, 16, 22, + 21, 20, 26, 25 }; + const v16u8 mask2 = { 8, 14, 13, 12, 18, 17, 16, 22, 21, 20, 26, 25, + 24, 30, 29, 28 }; + + while (num_pixels >= 16) { + CONVERT16_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 64; + ptemp_dst += 48; + num_pixels -= 16; + } + if (num_pixels) { + if (num_pixels >= 12) { + CONVERT12_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1, mask2); + ptemp_src += 48; + ptemp_dst += 36; + num_pixels -= 12; + } else if (num_pixels >= 8) { + CONVERT8_BGRA_XXX(ptemp_src, ptemp_dst, mask0, mask1); + ptemp_src += 32; + ptemp_dst += 24; + num_pixels -= 8; + } else if (num_pixels >= 4) { + CONVERT4_BGRA_XXX(ptemp_src, ptemp_dst, mask0); + ptemp_src += 16; + ptemp_dst += 12; + num_pixels -= 4; + } + if (num_pixels == 3) { + CONVERT1_BGRA_RGB(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_RGB(ptemp_src + 4, ptemp_dst + 3); + CONVERT1_BGRA_RGB(ptemp_src + 8, ptemp_dst + 6); + } else if (num_pixels == 2) { + CONVERT1_BGRA_RGB(ptemp_src + 0, ptemp_dst + 0); + CONVERT1_BGRA_RGB(ptemp_src + 4, ptemp_dst + 3); + } else if (num_pixels == 1) { + CONVERT1_BGRA_RGB(ptemp_src, ptemp_dst); + } + } +} + +static void AddGreenToBlueAndRed_MSA(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + const uint8_t* in = (const uint8_t*)src; + uint8_t* out = (uint8_t*)dst; + v16u8 src0, dst0, tmp0; + const v16u8 mask = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1, tmp1; + LD_UB2(in, 16, src0, src1); + VSHF_B2_UB(src0, src1, src1, src0, mask, mask, tmp0, tmp1); + ADD2(src0, tmp0, src1, tmp1, dst0, dst1); + ST_UB2(dst0, dst1, out, 16); + in += 32; + out += 32; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(in); + tmp0 = VSHF_UB(src0, src0, mask); + dst0 = src0 + tmp0; + ST_UB(dst0, out); + in += 16; + out += 16; + num_pixels -= 4; + } + for (i = 0; i < num_pixels; i++) { + const uint8_t b = in[0]; + const uint8_t g = in[1]; + const uint8_t r = in[2]; + out[0] = (b + g) & 0xff; + out[1] = g; + out[2] = (r + g) & 0xff; + out[4] = in[4]; + out += 4; + } + } +} + +static void TransformColorInverse_MSA(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + v16u8 src0, dst0; + const v16i8 g2br = (v16i8)__msa_fill_w(m->green_to_blue_ | + (m->green_to_red_ << 16)); + const v16i8 r2b = (v16i8)__msa_fill_w(m->red_to_blue_); + const v16u8 mask0 = { 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, + 13, 255, 13, 255 }; + const v16u8 mask1 = { 16, 1, 18, 3, 20, 5, 22, 7, 24, 9, 26, 11, + 28, 13, 30, 15 }; + + while (num_pixels >= 8) { + v16u8 src1, dst1; + LD_UB2(src, 4, src0, src1); + TRANSFORM_COLOR_INVERSE_8(src0, src1, dst0, dst1, g2br, r2b, mask0, mask1); + ST_UB2(dst0, dst1, dst, 4); + src += 8; + dst += 8; + num_pixels -= 8; + } + if (num_pixels > 0) { + if (num_pixels >= 4) { + src0 = LD_UB(src); + TRANSFORM_COLOR_INVERSE_4(src0, dst0, g2br, r2b, mask0, mask1); + ST_UB(dst0, dst); + src += 4; + dst += 4; + num_pixels -= 4; + } + if (num_pixels > 0) { + src0 = LD_UB(src); + TRANSFORM_COLOR_INVERSE_4(src0, dst0, g2br, r2b, mask0, mask1); + if (num_pixels == 3) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 2); + SD(pix_d, dst + 0); + SW(pix_w, dst + 2); + } else if (num_pixels == 2) { + const uint64_t pix_d = __msa_copy_s_d((v2i64)dst0, 0); + SD(pix_d, dst); + } else { + const uint32_t pix_w = __msa_copy_s_w((v4i32)dst0, 0); + SW(pix_w, dst); + } + } + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitMSA(void) { + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_MSA; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_MSA; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_MSA; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_MSA; + VP8LTransformColorInverse = TransformColorInverse_MSA; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(VP8LDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_neon.c new file mode 100644 index 0000000000..e9960db38a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_neon.c @@ -0,0 +1,645 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "src/dsp/lossless.h" +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ +// Colorspace conversion functions + +#if !defined(WORK_AROUND_GCC) +// gcc 4.6.0 had some trouble (NDK-r9) with this code. We only use it for +// gcc-4.8.x at least. +static void ConvertBGRAToRGBA_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + // swap B and R. (VSWP d0,d2 has no intrinsics equivalent!) + const uint8x16_t tmp = pixel.val[0]; + pixel.val[0] = pixel.val[2]; + pixel.val[2] = tmp; + vst4q_u8(dst, pixel); + dst += 64; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToBGR_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[0], pixel.val[1], pixel.val[2] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToRGB_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[2], pixel.val[1], pixel.val[0] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 15, dst); // left-overs +} + +#else // WORK_AROUND_GCC + +// gcc-4.6.0 fallback + +static const uint8_t kRGBAShuffle[8] = { 2, 1, 0, 3, 6, 5, 4, 7 }; + +static void ConvertBGRAToRGBA_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~1); + const uint8x8_t shuffle = vld1_u8(kRGBAShuffle); + for (; src < end; src += 2) { + const uint8x8_t pixels = vld1_u8((uint8_t*)src); + vst1_u8(dst, vtbl1_u8(pixels, shuffle)); + dst += 8; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 1, dst); // left-overs +} + +static const uint8_t kBGRShuffle[3][8] = { + { 0, 1, 2, 4, 5, 6, 8, 9 }, + { 10, 12, 13, 14, 16, 17, 18, 20 }, + { 21, 22, 24, 25, 26, 28, 29, 30 } +}; + +static void ConvertBGRAToBGR_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kBGRShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kBGRShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kBGRShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 7, dst); // left-overs +} + +static const uint8_t kRGBShuffle[3][8] = { + { 2, 1, 0, 6, 5, 4, 10, 9 }, + { 8, 14, 13, 12, 18, 17, 16, 22 }, + { 21, 20, 26, 25, 24, 30, 29, 28 } +}; + +static void ConvertBGRAToRGB_NEON(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kRGBShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kRGBShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kRGBShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 7, dst); // left-overs +} + +#endif // !WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Predictor Transform + +#define LOAD_U32_AS_U8(IN) vreinterpret_u8_u32(vdup_n_u32((IN))) +#define LOAD_U32P_AS_U8(IN) vreinterpret_u8_u32(vld1_u32((IN))) +#define LOADQ_U32_AS_U8(IN) vreinterpretq_u8_u32(vdupq_n_u32((IN))) +#define LOADQ_U32P_AS_U8(IN) vreinterpretq_u8_u32(vld1q_u32((IN))) +#define GET_U8_AS_U32(IN) vget_lane_u32(vreinterpret_u32_u8((IN)), 0) +#define GETQ_U8_AS_U32(IN) vgetq_lane_u32(vreinterpretq_u32_u8((IN)), 0) +#define STOREQ_U8_AS_U32P(OUT, IN) vst1q_u32((OUT), vreinterpretq_u32_u8((IN))) +#define ROTATE32_LEFT(L) vextq_u8((L), (L), 12) // D|C|B|A -> C|B|A|D + +static WEBP_INLINE uint8x8_t Average2_u8_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t A0 = LOAD_U32_AS_U8(a0); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + return vhadd_u8(A0, A1); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf_NEON(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const uint8x8_t avg = Average2_u8_NEON(c0, c1); + // Remove one to c2 when bigger than avg. + const uint8x8_t C2 = LOAD_U32_AS_U8(c2); + const uint8x8_t cmp = vcgt_u8(C2, avg); + const uint8x8_t C2_1 = vadd_u8(C2, cmp); + // Compute half of the difference between avg and c2. + const int8x8_t diff_avg = vreinterpret_s8_u8(vhsub_u8(avg, C2_1)); + // Compute the sum with avg and saturate. + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(avg)); + const uint8x8_t res = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); + const uint32_t output = GET_U8_AS_U32(res); + return output; +} + +static WEBP_INLINE uint32_t Average2_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t avg_u8x8 = Average2_u8_NEON(a0, a1); + const uint32_t avg = GET_U8_AS_U32(avg_u8x8); + return avg; +} + +static WEBP_INLINE uint32_t Average3_NEON(uint32_t a0, uint32_t a1, + uint32_t a2) { + const uint8x8_t avg0 = Average2_u8_NEON(a0, a2); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + const uint32_t avg = GET_U8_AS_U32(vhadd_u8(avg0, A1)); + return avg; +} + +static uint32_t Predictor5_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average3_NEON(*left, top[0], top[1]); +} +static uint32_t Predictor6_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average2_NEON(*left, top[-1]); +} +static uint32_t Predictor7_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average2_NEON(*left, top[0]); +} +static uint32_t Predictor13_NEON(const uint32_t* const left, + const uint32_t* const top) { + return ClampedAddSubtractHalf_NEON(*left, top[0], top[-1]); +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const uint8x16_t black = vreinterpretq_u8_u32(vdupq_n_u32(ARGB_BLACK)); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t res = vaddq_u8(src, black); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[0](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor1: left. +static void PredictorAdd1_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const uint8x16_t zero = LOADQ_U32_AS_U8(0); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // 0 | a | b | c + const uint8x16_t shift0 = vextq_u8(zero, src, 12); + // a | a + b | b + c | c + d + const uint8x16_t sum0 = vaddq_u8(src, shift0); + // 0 | 0 | a | a + b + const uint8x16_t shift1 = vextq_u8(zero, sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const uint8x16_t sum1 = vaddq_u8(sum0, shift1); + const uint8x16_t prev = LOADQ_U32_AS_U8(out[i - 1]); + const uint8x16_t res = vaddq_u8(sum1, prev); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t other = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t res = vaddq_u8(src, other); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Predictor5: average(average(left, TR), T) +#define DO_PRED5(LANE) do { \ + const uint8x16_t avgLTR = vhaddq_u8(L, TR); \ + const uint8x16_t avg = vhaddq_u8(avgLTR, T); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd5_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i + 0]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + DO_PRED5(0); + DO_PRED5(1); + DO_PRED5(2); + DO_PRED5(3); + } + VP8LPredictorsAdd_C[5](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED5 + +#define DO_PRED67(LANE) do { \ + const uint8x16_t avg = vhaddq_u8(L, top); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +// Predictor6: average(left, TL) +static void PredictorAdd6_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[6](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor7: average(left, T) +static void PredictorAdd7_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[7](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED67 + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t Tother = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); \ + const uint8x16_t avg = vhaddq_u8(T, Tother); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(LANE) do { \ + const uint8x16_t avgLTL = vhaddq_u8(L, TL); \ + const uint8x16_t avg = vhaddq_u8(avgTTR, avgLTL); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd10_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + const uint8x16_t avgTTR = vhaddq_u8(T, TR); + DO_PRED10(0); + DO_PRED10(1); + DO_PRED10(2); + DO_PRED10(3); + } + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED10 + +// Predictor11: select. +#define DO_PRED11(LANE) do { \ + const uint8x16_t sumLin = vaddq_u8(L, src); /* in + L */ \ + const uint8x16_t pLTL = vabdq_u8(L, TL); /* |L - TL| */ \ + const uint16x8_t sum_LTL = vpaddlq_u8(pLTL); \ + const uint32x4_t pa = vpaddlq_u16(sum_LTL); \ + const uint32x4_t mask = vcleq_u32(pa, pb); \ + const uint8x16_t res = vbslq_u8(vreinterpretq_u8_u32(mask), sumTin, sumLin); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd11_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t pTTL = vabdq_u8(T, TL); // |T - TL| + const uint16x8_t sum_TTL = vpaddlq_u8(pTTL); + const uint32x4_t pb = vpaddlq_u16(sum_TTL); + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t sumTin = vaddq_u8(T, src); // in + T + DO_PRED11(0); + DO_PRED11(1); + DO_PRED11(2); + DO_PRED11(3); + } + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED11 + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE) do { \ + const uint8x8_t pred = \ + vqmovun_s16(vaddq_s16(vreinterpretq_s16_u16(L), (DIFF))); \ + const uint8x8_t res = \ + vadd_u8(pred, (LANE <= 1) ? vget_low_u8(src) : vget_high_u8(src)); \ + const uint16x8_t res16 = vmovl_u8(res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + /* rotate in the left predictor for next iteration */ \ + L = vextq_u16(res16, res16, 4); \ +} while (0) + +static void PredictorAdd12_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint16x8_t L = vmovl_u8(LOAD_U32_AS_U8(out[-1])); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // load four pixels of source + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // precompute the difference T - TL once for all, stored as s16 + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const int16x8_t diff_lo = + vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(T), vget_low_u8(TL))); + const int16x8_t diff_hi = + vreinterpretq_s16_u16(vsubl_u8(vget_high_u8(T), vget_high_u8(TL))); + // loop over the four reconstructed pixels + DO_PRED12(diff_lo, 0); + DO_PRED12(diff_lo, 1); + DO_PRED12(diff_hi, 2); + DO_PRED12(diff_hi, 3); + } + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED12 + +// Predictor13: ClampedAddSubtractHalf +#define DO_PRED13(LANE, LOW_OR_HI) do { \ + const uint8x16_t avg = vhaddq_u8(L, T); \ + const uint8x16_t cmp = vcgtq_u8(TL, avg); \ + const uint8x16_t TL_1 = vaddq_u8(TL, cmp); \ + /* Compute half of the difference between avg and TL'. */ \ + const int8x8_t diff_avg = \ + vreinterpret_s8_u8(LOW_OR_HI(vhsubq_u8(avg, TL_1))); \ + /* Compute the sum with avg and saturate. */ \ + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(LOW_OR_HI(avg))); \ + const uint8x8_t delta = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); \ + const uint8x8_t res = vadd_u8(LOW_OR_HI(src), delta); \ + const uint8x16_t res2 = vcombine_u8(res, res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + L = ROTATE32_LEFT(res2); \ +} while (0) + +static void PredictorAdd13_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED13(0, vget_low_u8); + DO_PRED13(1, vget_low_u8); + DO_PRED13(2, vget_high_u8); + DO_PRED13(3, vget_high_u8); + } + VP8LPredictorsAdd_C[13](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED13 + +#undef LOAD_U32_AS_U8 +#undef LOAD_U32P_AS_U8 +#undef LOADQ_U32_AS_U8 +#undef LOADQ_U32P_AS_U8 +#undef GET_U8_AS_U32 +#undef GETQ_U8_AS_U32 +#undef STOREQ_U8_AS_U32P +#undef ROTATE32_LEFT + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +// vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use +// non-standard versions there. +#if defined(__APPLE__) && WEBP_AARCH64 && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) +#define USE_VTBLQ +#endif + +#ifdef USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[16] = { + 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255 +}; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x16_t shuffle) { + return vcombine_u8(vtbl1q_u8(argb, vget_low_u8(shuffle)), + vtbl1q_u8(argb, vget_high_u8(shuffle))); +} +#else // !USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[8] = { 1, 255, 1, 255, 5, 255, 5, 255 }; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x8_t shuffle) { + return vcombine_u8(vtbl1_u8(vget_low_u8(argb), shuffle), + vtbl1_u8(vget_high_u8(argb), shuffle)); +} +#endif // USE_VTBLQ + +static void AddGreenToBlueAndRed_NEON(const uint32_t* src, int num_pixels, + uint32_t* dst) { + const uint32_t* const end = src + (num_pixels & ~3); +#ifdef USE_VTBLQ + const uint8x16_t shuffle = vld1q_u8(kGreenShuffle); +#else + const uint8x8_t shuffle = vld1_u8(kGreenShuffle); +#endif + for (; src < end; src += 4, dst += 4) { + const uint8x16_t argb = vld1q_u8((const uint8_t*)src); + const uint8x16_t greens = DoGreenShuffle_NEON(argb, shuffle); + vst1q_u8((uint8_t*)dst, vaddq_u8(argb, greens)); + } + // fallthrough and finish off with plain-C + VP8LAddGreenToBlueAndRed_C(src, num_pixels & 3, dst); +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse_NEON(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 6. +#define CST(X) (((int16_t)(m->X << 8)) >> 6) + const int16_t rb[8] = { + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_), + CST(green_to_blue_), CST(green_to_red_) + }; + const int16x8_t mults_rb = vld1q_s16(rb); + const int16_t b2[8] = { + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + 0, CST(red_to_blue_), 0, CST(red_to_blue_), + }; + const int16x8_t mults_b2 = vld1q_s16(b2); +#undef CST +#ifdef USE_VTBLQ + static const uint8_t kg0g0[16] = { + 255, 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13 + }; + const uint8x16_t shuffle = vld1q_u8(kg0g0); +#else + static const uint8_t k0g0g[8] = { 255, 1, 255, 1, 255, 5, 255, 5 }; + const uint8x8_t shuffle = vld1_u8(k0g0g); +#endif + const uint32x4_t mask_ag = vdupq_n_u32(0xff00ff00u); + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t in = vld1q_u8((const uint8_t*)(src + i)); + const uint32x4_t a0g0 = vandq_u32(vreinterpretq_u32_u8(in), mask_ag); + // 0 g 0 g + const uint8x16_t greens = DoGreenShuffle_NEON(in, shuffle); + // x dr x db1 + const int16x8_t A = vqdmulhq_s16(vreinterpretq_s16_u8(greens), mults_rb); + // x r' x b' + const int8x16_t B = vaddq_s8(vreinterpretq_s8_u8(in), + vreinterpretq_s8_s16(A)); + // r' 0 b' 0 + const int16x8_t C = vshlq_n_s16(vreinterpretq_s16_s8(B), 8); + // x db2 0 0 + const int16x8_t D = vqdmulhq_s16(C, mults_b2); + // 0 x db2 0 + const uint32x4_t E = vshrq_n_u32(vreinterpretq_u32_s16(D), 8); + // r' x b'' 0 + const int8x16_t F = vaddq_s8(vreinterpretq_s8_u32(E), + vreinterpretq_s8_s16(C)); + // 0 r' 0 b'' + const uint16x8_t G = vshrq_n_u16(vreinterpretq_u16_s8(F), 8); + const uint32x4_t out = vorrq_u32(vreinterpretq_u32_u16(G), a0g0); + vst1q_u32(dst + i, out); + } + // Fall-back to C-version for left-overs. + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); +} + +#undef USE_VTBLQ + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitNEON(void) { + VP8LPredictors[5] = Predictor5_NEON; + VP8LPredictors[6] = Predictor6_NEON; + VP8LPredictors[7] = Predictor7_NEON; + VP8LPredictors[13] = Predictor13_NEON; + + VP8LPredictorsAdd[0] = PredictorAdd0_NEON; + VP8LPredictorsAdd[1] = PredictorAdd1_NEON; + VP8LPredictorsAdd[2] = PredictorAdd2_NEON; + VP8LPredictorsAdd[3] = PredictorAdd3_NEON; + VP8LPredictorsAdd[4] = PredictorAdd4_NEON; + VP8LPredictorsAdd[5] = PredictorAdd5_NEON; + VP8LPredictorsAdd[6] = PredictorAdd6_NEON; + VP8LPredictorsAdd[7] = PredictorAdd7_NEON; + VP8LPredictorsAdd[8] = PredictorAdd8_NEON; + VP8LPredictorsAdd[9] = PredictorAdd9_NEON; + VP8LPredictorsAdd[10] = PredictorAdd10_NEON; + VP8LPredictorsAdd[11] = PredictorAdd11_NEON; + VP8LPredictorsAdd[12] = PredictorAdd12_NEON; + VP8LPredictorsAdd[13] = PredictorAdd13_NEON; + + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_NEON; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_NEON; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_NEON; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_NEON; + VP8LTransformColorInverse = TransformColorInverse_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8LDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse2.c new file mode 100644 index 0000000000..4b6a532c23 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse2.c @@ -0,0 +1,712 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include "src/dsp/common_sse2.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" +#include + +//------------------------------------------------------------------------------ +// Predictor Transform + +static WEBP_INLINE uint32_t ClampedAddSubtractFull_SSE2(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); + const __m128i V1 = _mm_add_epi16(C0, C1); + const __m128i V2 = _mm_sub_epi16(V1, C2); + const __m128i b = _mm_packus_epi16(V2, V2); + return (uint32_t)_mm_cvtsi128_si32(b); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf_SSE2(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); + const __m128i avg = _mm_add_epi16(C1, C0); + const __m128i A0 = _mm_srli_epi16(avg, 1); + const __m128i A1 = _mm_sub_epi16(A0, B0); + const __m128i BgtA = _mm_cmpgt_epi16(B0, A0); + const __m128i A2 = _mm_sub_epi16(A1, BgtA); + const __m128i A3 = _mm_srai_epi16(A2, 1); + const __m128i A4 = _mm_add_epi16(A0, A3); + const __m128i A5 = _mm_packus_epi16(A4, A4); + return (uint32_t)_mm_cvtsi128_si32(A5); +} + +static WEBP_INLINE uint32_t Select_SSE2(uint32_t a, uint32_t b, uint32_t c) { + int pa_minus_pb; + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_cvtsi32_si128((int)a); + const __m128i B0 = _mm_cvtsi32_si128((int)b); + const __m128i C0 = _mm_cvtsi32_si128((int)c); + const __m128i AC0 = _mm_subs_epu8(A0, C0); + const __m128i CA0 = _mm_subs_epu8(C0, A0); + const __m128i BC0 = _mm_subs_epu8(B0, C0); + const __m128i CB0 = _mm_subs_epu8(C0, B0); + const __m128i AC = _mm_or_si128(AC0, CA0); + const __m128i BC = _mm_or_si128(BC0, CB0); + const __m128i pa = _mm_unpacklo_epi8(AC, zero); // |a - c| + const __m128i pb = _mm_unpacklo_epi8(BC, zero); // |b - c| + const __m128i diff = _mm_sub_epi16(pb, pa); + { + int16_t out[8]; + _mm_storeu_si128((__m128i*)out, diff); + pa_minus_pb = out[0] + out[1] + out[2] + out[3]; + } + return (pa_minus_pb <= 0) ? a : b; +} + +static WEBP_INLINE void Average2_m128i(const __m128i* const a0, + const __m128i* const a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i avg1 = _mm_avg_epu8(*a0, *a1); + const __m128i one = _mm_and_si128(_mm_xor_si128(*a0, *a1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE void Average2_uint32_SSE2(const uint32_t a0, + const uint32_t a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i A0 = _mm_cvtsi32_si128((int)a0); + const __m128i A1 = _mm_cvtsi32_si128((int)a1); + const __m128i avg1 = _mm_avg_epu8(A0, A1); + const __m128i one = _mm_and_si128(_mm_xor_si128(A0, A1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE __m128i Average2_uint32_16_SSE2(uint32_t a0, uint32_t a1) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a0), zero); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); + const __m128i sum = _mm_add_epi16(A1, A0); + return _mm_srli_epi16(sum, 1); +} + +static WEBP_INLINE uint32_t Average2_SSE2(uint32_t a0, uint32_t a1) { + __m128i output; + Average2_uint32_SSE2(a0, a1, &output); + return (uint32_t)_mm_cvtsi128_si32(output); +} + +static WEBP_INLINE uint32_t Average3_SSE2(uint32_t a0, uint32_t a1, + uint32_t a2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i avg1 = Average2_uint32_16_SSE2(a0, a2); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); + const __m128i sum = _mm_add_epi16(avg1, A1); + const __m128i avg2 = _mm_srli_epi16(sum, 1); + const __m128i A2 = _mm_packus_epi16(avg2, avg2); + return (uint32_t)_mm_cvtsi128_si32(A2); +} + +static WEBP_INLINE uint32_t Average4_SSE2(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + const __m128i avg1 = Average2_uint32_16_SSE2(a0, a1); + const __m128i avg2 = Average2_uint32_16_SSE2(a2, a3); + const __m128i sum = _mm_add_epi16(avg2, avg1); + const __m128i avg3 = _mm_srli_epi16(sum, 1); + const __m128i A0 = _mm_packus_epi16(avg3, avg3); + return (uint32_t)_mm_cvtsi128_si32(A0); +} + +static uint32_t Predictor5_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average3_SSE2(*left, top[0], top[1]); + return pred; +} +static uint32_t Predictor6_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(*left, top[-1]); + return pred; +} +static uint32_t Predictor7_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(*left, top[0]); + return pred; +} +static uint32_t Predictor8_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(top[-1], top[0]); + (void)left; + return pred; +} +static uint32_t Predictor9_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(top[0], top[1]); + (void)left; + return pred; +} +static uint32_t Predictor10_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average4_SSE2(*left, top[-1], top[0], top[1]); + return pred; +} +static uint32_t Predictor11_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Select_SSE2(top[0], *left, top[-1]); + return pred; +} +static uint32_t Predictor12_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull_SSE2(*left, top[0], top[-1]); + return pred; +} +static uint32_t Predictor13_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf_SSE2(*left, top[0], top[-1]); + return pred; +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i black = _mm_set1_epi32((int)ARGB_BLACK); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i res = _mm_add_epi8(src, black); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[0](in + i, NULL, num_pixels - i, out + i); + } + (void)upper; +} + +// Predictor1: left. +static void PredictorAdd1_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + __m128i prev = _mm_set1_epi32((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + // 0 | a | b | c + const __m128i shift0 = _mm_slli_si128(src, 4); + // a | a + b | b + c | c + d + const __m128i sum0 = _mm_add_epi8(src, shift0); + // 0 | 0 | a | a + b + const __m128i shift1 = _mm_slli_si128(sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const __m128i sum1 = _mm_add_epi8(sum0, shift1); + const __m128i res = _mm_add_epi8(sum1, prev); + _mm_storeu_si128((__m128i*)&out[i], res); + // replicate prev output on the four lanes + prev = _mm_shuffle_epi32(res, (3 << 0) | (3 << 2) | (3 << 4) | (3 << 6)); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + const __m128i other = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i res = _mm_add_epi8(src, other); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 5 to 7. +GENERATE_PREDICTOR_ADD(Predictor5_SSE2, PredictorAdd5_SSE2) +GENERATE_PREDICTOR_ADD(Predictor6_SSE2, PredictorAdd6_SSE2) +GENERATE_PREDICTOR_ADD(Predictor7_SSE2, PredictorAdd7_SSE2) + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i Tother = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + __m128i avg, res; \ + Average2_m128i(&T, &Tother, &avg); \ + res = _mm_add_epi8(avg, src); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(OUT) do { \ + __m128i avgLTL, avg; \ + Average2_m128i(&L, &TL, &avgLTL); \ + Average2_m128i(&avgTTR, &avgLTL, &avg); \ + L = _mm_add_epi8(avg, src); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ +} while (0) + +#define DO_PRED10_SHIFT do { \ + /* Rotate the pre-computed values for the next iteration.*/ \ + avgTTR = _mm_srli_si128(avgTTR, 4); \ + TL = _mm_srli_si128(TL, 4); \ + src = _mm_srli_si128(src, 4); \ +} while (0) + +static void PredictorAdd10_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + __m128i L = _mm_cvtsi32_si128((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + __m128i avgTTR; + Average2_m128i(&T, &TR, &avgTTR); + DO_PRED10(0); + DO_PRED10_SHIFT; + DO_PRED10(1); + DO_PRED10_SHIFT; + DO_PRED10(2); + DO_PRED10_SHIFT; + DO_PRED10(3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED10 +#undef DO_PRED10_SHIFT + +// Predictor11: select. +#define DO_PRED11(OUT) do { \ + const __m128i L_lo = _mm_unpacklo_epi32(L, T); \ + const __m128i TL_lo = _mm_unpacklo_epi32(TL, T); \ + const __m128i pb = _mm_sad_epu8(L_lo, TL_lo); /* pb = sum |L-TL|*/ \ + const __m128i mask = _mm_cmpgt_epi32(pb, pa); \ + const __m128i A = _mm_and_si128(mask, L); \ + const __m128i B = _mm_andnot_si128(mask, T); \ + const __m128i pred = _mm_or_si128(A, B); /* pred = (pa > b)? L : T*/ \ + L = _mm_add_epi8(src, pred); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ +} while (0) + +#define DO_PRED11_SHIFT do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + T = _mm_srli_si128(T, 4); \ + TL = _mm_srli_si128(TL, 4); \ + src = _mm_srli_si128(src, 4); \ + pa = _mm_srli_si128(pa, 4); \ +} while (0) + +static void PredictorAdd11_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + __m128i pa; + __m128i L = _mm_cvtsi32_si128((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + { + // We can unpack with any value on the upper 32 bits, provided it's the + // same on both operands (so that their sum of abs diff is zero). Here we + // use T. + const __m128i T_lo = _mm_unpacklo_epi32(T, T); + const __m128i TL_lo = _mm_unpacklo_epi32(TL, T); + const __m128i T_hi = _mm_unpackhi_epi32(T, T); + const __m128i TL_hi = _mm_unpackhi_epi32(TL, T); + const __m128i s_lo = _mm_sad_epu8(T_lo, TL_lo); + const __m128i s_hi = _mm_sad_epu8(T_hi, TL_hi); + pa = _mm_packs_epi32(s_lo, s_hi); // pa = sum |T-TL| + } + DO_PRED11(0); + DO_PRED11_SHIFT; + DO_PRED11(1); + DO_PRED11_SHIFT; + DO_PRED11(2); + DO_PRED11_SHIFT; + DO_PRED11(3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED11 +#undef DO_PRED11_SHIFT + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE, OUT) do { \ + const __m128i all = _mm_add_epi16(L, (DIFF)); \ + const __m128i alls = _mm_packus_epi16(all, all); \ + const __m128i res = _mm_add_epi8(src, alls); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(res); \ + L = _mm_unpacklo_epi8(res, zero); \ +} while (0) + +#define DO_PRED12_SHIFT(DIFF, LANE) do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + if ((LANE) == 0) (DIFF) = _mm_srli_si128((DIFF), 8); \ + src = _mm_srli_si128(src, 4); \ +} while (0) + +static void PredictorAdd12_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* out) { + int i; + const __m128i zero = _mm_setzero_si128(); + const __m128i L8 = _mm_cvtsi32_si128((int)out[-1]); + __m128i L = _mm_unpacklo_epi8(L8, zero); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // Load 4 pixels at a time. + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i T_hi = _mm_unpackhi_epi8(T, zero); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i TL_hi = _mm_unpackhi_epi8(TL, zero); + __m128i diff_lo = _mm_sub_epi16(T_lo, TL_lo); + __m128i diff_hi = _mm_sub_epi16(T_hi, TL_hi); + DO_PRED12(diff_lo, 0, 0); + DO_PRED12_SHIFT(diff_lo, 0); + DO_PRED12(diff_lo, 1, 1); + DO_PRED12_SHIFT(diff_lo, 1); + DO_PRED12(diff_hi, 0, 2); + DO_PRED12_SHIFT(diff_hi, 0); + DO_PRED12(diff_hi, 1, 3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED12 +#undef DO_PRED12_SHIFT + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 13. +GENERATE_PREDICTOR_ADD(Predictor13_SSE2, PredictorAdd13_SSE2) + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void AddGreenToBlueAndRed_SSE2(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_srli_epi16(in, 8); // 0 a 0 g + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // 0g0g + const __m128i out = _mm_add_epi8(in, C); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LAddGreenToBlueAndRed_C(src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse_SSE2(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + const __m128i mults_rb = MK_CST_16(CST(green_to_red_), CST(green_to_blue_)); + const __m128i mults_b2 = MK_CST_16(CST(red_to_blue_), 0); +#undef MK_CST_16 +#undef CST + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); // alpha-green masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_and_si128(in, mask_ag); // a 0 g 0 + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // g0g0 + const __m128i D = _mm_mulhi_epi16(C, mults_rb); // x dr x db1 + const __m128i E = _mm_add_epi8(in, D); // x r' x b' + const __m128i F = _mm_slli_epi16(E, 8); // r' 0 b' 0 + const __m128i G = _mm_mulhi_epi16(F, mults_b2); // x db2 0 0 + const __m128i H = _mm_srli_epi32(G, 8); // 0 x db2 0 + const __m128i I = _mm_add_epi8(H, F); // r' x b'' 0 + const __m128i J = _mm_srli_epi16(I, 8); // 0 r' 0 b'' + const __m128i out = _mm_or_si128(J, A); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // Fall-back to C-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void ConvertBGRAToRGB_SSE2(const uint32_t* src, int num_pixels, + uint8_t* dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + + while (num_pixels >= 32) { + // Load the BGRA buffers. + __m128i in0 = _mm_loadu_si128(in + 0); + __m128i in1 = _mm_loadu_si128(in + 1); + __m128i in2 = _mm_loadu_si128(in + 2); + __m128i in3 = _mm_loadu_si128(in + 3); + __m128i in4 = _mm_loadu_si128(in + 4); + __m128i in5 = _mm_loadu_si128(in + 5); + __m128i in6 = _mm_loadu_si128(in + 6); + __m128i in7 = _mm_loadu_si128(in + 7); + VP8L32bToPlanar_SSE2(&in0, &in1, &in2, &in3); + VP8L32bToPlanar_SSE2(&in4, &in5, &in6, &in7); + // At this points, in1/in5 contains red only, in2/in6 green only ... + // Pack the colors in 24b RGB. + VP8PlanarTo24b_SSE2(&in1, &in5, &in2, &in6, &in3, &in7); + _mm_storeu_si128(out + 0, in1); + _mm_storeu_si128(out + 1, in5); + _mm_storeu_si128(out + 2, in2); + _mm_storeu_si128(out + 3, in6); + _mm_storeu_si128(out + 4, in3); + _mm_storeu_si128(out + 5, in7); + in += 8; + out += 6; + num_pixels -= 32; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA_SSE2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i red_blue_mask = _mm_set1_epi32(0x00ff00ff); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i A1 = _mm_loadu_si128(in++); + const __m128i A2 = _mm_loadu_si128(in++); + const __m128i B1 = _mm_and_si128(A1, red_blue_mask); // R 0 B 0 + const __m128i B2 = _mm_and_si128(A2, red_blue_mask); // R 0 B 0 + const __m128i C1 = _mm_andnot_si128(red_blue_mask, A1); // 0 G 0 A + const __m128i C2 = _mm_andnot_si128(red_blue_mask, A2); // 0 G 0 A + const __m128i D1 = _mm_shufflelo_epi16(B1, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i D2 = _mm_shufflelo_epi16(B2, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i E1 = _mm_shufflehi_epi16(D1, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i E2 = _mm_shufflehi_epi16(D2, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i F1 = _mm_or_si128(E1, C1); + const __m128i F2 = _mm_or_si128(E2, C2); + _mm_storeu_si128(out++, F1); + _mm_storeu_si128(out++, F2); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA4444_SSE2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_0x0f = _mm_set1_epi8(0x0f); + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i ga1 = _mm_srli_epi16(ga0, 4); // g0-|g1-|...|a6-|a7- + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf0); // -r0|-r1|...|-b6|-a7 + const __m128i ga2 = _mm_and_si128(ga1, mask_0x0f); // g0-|g1-|...|a6-|a7- + const __m128i rgba0 = _mm_or_si128(ga2, rb1); // rg0..rg7 | ba0..ba7 + const __m128i rgba1 = _mm_srli_si128(rgba0, 8); // ba0..ba7 | 0 +#if (WEBP_SWAP_16BIT_CSP == 1) + const __m128i rgba = _mm_unpacklo_epi8(rgba1, rgba0); // barg0...barg7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rgba0, rgba1); // rgba0...rgba7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA4444_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGB565_SSE2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_0xe0 = _mm_set1_epi8((char)0xe0); + const __m128i mask_0xf8 = _mm_set1_epi8((char)0xf8); + const __m128i mask_0x07 = _mm_set1_epi8(0x07); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf8); // -r0..-r7|-b0..-b7 + const __m128i g_lo1 = _mm_srli_epi16(ga0, 5); + const __m128i g_lo2 = _mm_and_si128(g_lo1, mask_0x07); // g0-...g7-|xx (3b) + const __m128i g_hi1 = _mm_slli_epi16(ga0, 3); + const __m128i g_hi2 = _mm_and_si128(g_hi1, mask_0xe0); // -g0...-g7|xx (3b) + const __m128i b0 = _mm_srli_si128(rb1, 8); // -b0...-b7|0 + const __m128i rg1 = _mm_or_si128(rb1, g_lo2); // gr0...gr7|xx + const __m128i b1 = _mm_srli_epi16(b0, 3); + const __m128i gb1 = _mm_or_si128(b1, g_hi2); // bg0...bg7|xx +#if (WEBP_SWAP_16BIT_CSP == 1) + const __m128i rgba = _mm_unpacklo_epi8(gb1, rg1); // rggb0...rggb7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rg1, gb1); // bgrb0...bgrb7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB565_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToBGR_SSE2(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i mask_l = _mm_set_epi32(0, 0x00ffffff, 0, 0x00ffffff); + const __m128i mask_h = _mm_set_epi32(0x00ffffff, 0, 0x00ffffff, 0); + const __m128i* in = (const __m128i*)src; + const uint8_t* const end = dst + num_pixels * 3; + // the last storel_epi64 below writes 8 bytes starting at offset 18 + while (dst + 26 <= end) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i a0l = _mm_and_si128(bgra0, mask_l); // bgr0|0|bgr0|0 + const __m128i a4l = _mm_and_si128(bgra4, mask_l); // bgr0|0|bgr0|0 + const __m128i a0h = _mm_and_si128(bgra0, mask_h); // 0|bgr0|0|bgr0 + const __m128i a4h = _mm_and_si128(bgra4, mask_h); // 0|bgr0|0|bgr0 + const __m128i b0h = _mm_srli_epi64(a0h, 8); // 000b|gr00|000b|gr00 + const __m128i b4h = _mm_srli_epi64(a4h, 8); // 000b|gr00|000b|gr00 + const __m128i c0 = _mm_or_si128(a0l, b0h); // rgbrgb00|rgbrgb00 + const __m128i c4 = _mm_or_si128(a4l, b4h); // rgbrgb00|rgbrgb00 + const __m128i c2 = _mm_srli_si128(c0, 8); + const __m128i c6 = _mm_srli_si128(c4, 8); + _mm_storel_epi64((__m128i*)(dst + 0), c0); + _mm_storel_epi64((__m128i*)(dst + 6), c2); + _mm_storel_epi64((__m128i*)(dst + 12), c4); + _mm_storel_epi64((__m128i*)(dst + 18), c6); + dst += 24; + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToBGR_C((const uint32_t*)in, num_pixels, dst); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitSSE2(void) { + VP8LPredictors[5] = Predictor5_SSE2; + VP8LPredictors[6] = Predictor6_SSE2; + VP8LPredictors[7] = Predictor7_SSE2; + VP8LPredictors[8] = Predictor8_SSE2; + VP8LPredictors[9] = Predictor9_SSE2; + VP8LPredictors[10] = Predictor10_SSE2; + VP8LPredictors[11] = Predictor11_SSE2; + VP8LPredictors[12] = Predictor12_SSE2; + VP8LPredictors[13] = Predictor13_SSE2; + + VP8LPredictorsAdd[0] = PredictorAdd0_SSE2; + VP8LPredictorsAdd[1] = PredictorAdd1_SSE2; + VP8LPredictorsAdd[2] = PredictorAdd2_SSE2; + VP8LPredictorsAdd[3] = PredictorAdd3_SSE2; + VP8LPredictorsAdd[4] = PredictorAdd4_SSE2; + VP8LPredictorsAdd[5] = PredictorAdd5_SSE2; + VP8LPredictorsAdd[6] = PredictorAdd6_SSE2; + VP8LPredictorsAdd[7] = PredictorAdd7_SSE2; + VP8LPredictorsAdd[8] = PredictorAdd8_SSE2; + VP8LPredictorsAdd[9] = PredictorAdd9_SSE2; + VP8LPredictorsAdd[10] = PredictorAdd10_SSE2; + VP8LPredictorsAdd[11] = PredictorAdd11_SSE2; + VP8LPredictorsAdd[12] = PredictorAdd12_SSE2; + VP8LPredictorsAdd[13] = PredictorAdd13_SSE2; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_SSE2; + VP8LTransformColorInverse = TransformColorInverse_SSE2; + + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_SSE2; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_SSE2; + VP8LConvertBGRAToRGBA4444 = ConvertBGRAToRGBA4444_SSE2; + VP8LConvertBGRAToRGB565 = ConvertBGRAToRGB565_SSE2; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8LDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse41.c new file mode 100644 index 0000000000..bb7ce7611f --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/lossless_sse41.c @@ -0,0 +1,133 @@ +// Copyright 2021 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 variant of methods for lossless decoder + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include "src/dsp/common_sse41.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void TransformColorInverse_SSE41(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend + const __m128i mults_rb = + _mm_set1_epi32((int)((uint32_t)CST(green_to_red_) << 16 | + (CST(green_to_blue_) & 0xffff))); + const __m128i mults_b2 = _mm_set1_epi32(CST(red_to_blue_)); +#undef CST + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); + const __m128i perm1 = _mm_setr_epi8(-1, 1, -1, 1, -1, 5, -1, 5, + -1, 9, -1, 9, -1, 13, -1, 13); + const __m128i perm2 = _mm_setr_epi8(-1, 2, -1, -1, -1, 6, -1, -1, + -1, 10, -1, -1, -1, 14, -1, -1); + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i A = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i B = _mm_shuffle_epi8(A, perm1); // argb -> g0g0 + const __m128i C = _mm_mulhi_epi16(B, mults_rb); + const __m128i D = _mm_add_epi8(A, C); + const __m128i E = _mm_shuffle_epi8(D, perm2); + const __m128i F = _mm_mulhi_epi16(E, mults_b2); + const __m128i G = _mm_add_epi8(D, F); + const __m128i out = _mm_blendv_epi8(G, A, mask_ag); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // Fall-back to C-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ + +#define ARGB_TO_RGB_SSE41 do { \ + while (num_pixels >= 16) { \ + const __m128i in0 = _mm_loadu_si128(in + 0); \ + const __m128i in1 = _mm_loadu_si128(in + 1); \ + const __m128i in2 = _mm_loadu_si128(in + 2); \ + const __m128i in3 = _mm_loadu_si128(in + 3); \ + const __m128i a0 = _mm_shuffle_epi8(in0, perm0); \ + const __m128i a1 = _mm_shuffle_epi8(in1, perm1); \ + const __m128i a2 = _mm_shuffle_epi8(in2, perm2); \ + const __m128i a3 = _mm_shuffle_epi8(in3, perm3); \ + const __m128i b0 = _mm_blend_epi16(a0, a1, 0xc0); \ + const __m128i b1 = _mm_blend_epi16(a1, a2, 0xf0); \ + const __m128i b2 = _mm_blend_epi16(a2, a3, 0xfc); \ + _mm_storeu_si128(out + 0, b0); \ + _mm_storeu_si128(out + 1, b1); \ + _mm_storeu_si128(out + 2, b2); \ + in += 4; \ + out += 3; \ + num_pixels -= 16; \ + } \ +} while (0) + +static void ConvertBGRAToRGB_SSE41(const uint32_t* src, int num_pixels, + uint8_t* dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + const __m128i perm0 = _mm_setr_epi8(2, 1, 0, 6, 5, 4, 10, 9, + 8, 14, 13, 12, -1, -1, -1, -1); + const __m128i perm1 = _mm_shuffle_epi32(perm0, 0x39); + const __m128i perm2 = _mm_shuffle_epi32(perm0, 0x4e); + const __m128i perm3 = _mm_shuffle_epi32(perm0, 0x93); + + ARGB_TO_RGB_SSE41; + + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToBGR_SSE41(const uint32_t* src, + int num_pixels, uint8_t* dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + const __m128i perm0 = _mm_setr_epi8(0, 1, 2, 4, 5, 6, 8, 9, 10, + 12, 13, 14, -1, -1, -1, -1); + const __m128i perm1 = _mm_shuffle_epi32(perm0, 0x39); + const __m128i perm2 = _mm_shuffle_epi32(perm0, 0x4e); + const __m128i perm3 = _mm_shuffle_epi32(perm0, 0x93); + + ARGB_TO_RGB_SSE41; + + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToBGR_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +#undef ARGB_TO_RGB_SSE41 + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitSSE41(void) { + VP8LTransformColorInverse = TransformColorInverse_SSE41; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_SSE41; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8LDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/mips_macro.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/mips_macro.h new file mode 100644 index 0000000000..e810d3d382 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/mips_macro.h @@ -0,0 +1,210 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS common macros + +#ifndef WEBP_DSP_MIPS_MACRO_H_ +#define WEBP_DSP_MIPS_MACRO_H_ + +#if defined(__GNUC__) && defined(__ANDROID__) && LOCAL_GCC_VERSION == 0x409 +#define WORK_AROUND_GCC +#endif + +#define STR(s) #s +#define XSTR(s) STR(s) + +// O0[31..16 | 15..0] = I0[31..16 | 15..0] + I1[31..16 | 15..0] +// O1[31..16 | 15..0] = I0[31..16 | 15..0] - I1[31..16 | 15..0] +// O - output +// I - input (macro doesn't change it) +#define ADD_SUB_HALVES(O0, O1, \ + I0, I1) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I1 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I1 "] \n\t" + +// O - output +// I - input (macro doesn't change it) +// I[0/1] - offset in bytes +#define LOAD_IN_X2(O0, O1, \ + I0, I1) \ + "lh %[" #O0 "], " #I0 "(%[in]) \n\t" \ + "lh %[" #O1 "], " #I1 "(%[in]) \n\t" + +// I0 - location +// I1..I9 - offsets in bytes +#define LOAD_WITH_OFFSET_X4(O0, O1, O2, O3, \ + I0, I1, I2, I3, I4, I5, I6, I7, I8, I9) \ + "ulw %[" #O0 "], " #I1 "+" XSTR(I9) "*" #I5 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O1 "], " #I2 "+" XSTR(I9) "*" #I6 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O2 "], " #I3 "+" XSTR(I9) "*" #I7 "(%[" #I0 "]) \n\t" \ + "ulw %[" #O3 "], " #I4 "+" XSTR(I9) "*" #I8 "(%[" #I0 "]) \n\t" + + +// O - output +// I - input (macro doesn't change it so it should be different from I) +#define MUL_SHIFT_C1(O, I) \ + "mul %[" #O "], %[" #I "], %[kC1] \n\t" \ + "sra %[" #O "], %[" #O "], 16 \n\t" \ + "addu %[" #O "], %[" #O "], %[" #I "] \n\t" +#define MUL_SHIFT_C2(O, I) \ + "mul %[" #O "], %[" #I "], %[kC2] \n\t" \ + "sra %[" #O "], %[" #O "], 16 \n\t" + +// Same as #define MUL_SHIFT_C1 but I and O are the same. It stores the +// intermediary result in TMP. +#define MUL_SHIFT_C1_IO(IO, TMP) \ + "mul %[" #TMP "], %[" #IO "], %[kC1] \n\t" \ + "sra %[" #TMP "], %[" #TMP "], 16 \n\t" \ + "addu %[" #IO "], %[" #TMP "], %[" #IO "] \n\t" + +// O - output +// IO - input/output +// I - input (macro doesn't change it) +#define MUL_SHIFT_SUM(O0, O1, O2, O3, O4, O5, O6, O7, \ + IO0, IO1, IO2, IO3, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + MUL_SHIFT_C2(O0, I0) \ + MUL_SHIFT_C1(O1, I0) \ + MUL_SHIFT_C2(O2, I1) \ + MUL_SHIFT_C1(O3, I1) \ + MUL_SHIFT_C2(O4, I2) \ + MUL_SHIFT_C1(O5, I2) \ + MUL_SHIFT_C2(O6, I3) \ + MUL_SHIFT_C1(O7, I3) \ + "addu %[" #IO0 "], %[" #IO0 "], %[" #I4 "] \n\t" \ + "addu %[" #IO1 "], %[" #IO1 "], %[" #I5 "] \n\t" \ + "subu %[" #IO2 "], %[" #IO2 "], %[" #I6 "] \n\t" \ + "subu %[" #IO3 "], %[" #IO3 "], %[" #I7 "] \n\t" + +// O - output +// I - input (macro doesn't change it) +#define INSERT_HALF_X2(O0, O1, \ + I0, I1) \ + "ins %[" #O0 "], %[" #I0 "], 16, 16 \n\t" \ + "ins %[" #O1 "], %[" #I1 "], 16, 16 \n\t" + +// O - output +// I - input (macro doesn't change it) +#define SRA_16(O0, O1, O2, O3, \ + I0, I1, I2, I3) \ + "sra %[" #O0 "], %[" #I0 "], 16 \n\t" \ + "sra %[" #O1 "], %[" #I1 "], 16 \n\t" \ + "sra %[" #O2 "], %[" #I2 "], 16 \n\t" \ + "sra %[" #O3 "], %[" #I3 "], 16 \n\t" + +// temp0[31..16 | 15..0] = temp8[31..16 | 15..0] + temp12[31..16 | 15..0] +// temp1[31..16 | 15..0] = temp8[31..16 | 15..0] - temp12[31..16 | 15..0] +// temp0[31..16 | 15..0] = temp0[31..16 >> 3 | 15..0 >> 3] +// temp1[31..16 | 15..0] = temp1[31..16 >> 3 | 15..0 >> 3] +// O - output +// I - input (macro doesn't change it) +#define SHIFT_R_SUM_X2(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3, I4, I5, I6, I7) \ + "addq.ph %[" #O0 "], %[" #I0 "], %[" #I4 "] \n\t" \ + "subq.ph %[" #O1 "], %[" #I0 "], %[" #I4 "] \n\t" \ + "addq.ph %[" #O2 "], %[" #I1 "], %[" #I5 "] \n\t" \ + "subq.ph %[" #O3 "], %[" #I1 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #O4 "], %[" #I2 "], %[" #I6 "] \n\t" \ + "subq.ph %[" #O5 "], %[" #I2 "], %[" #I6 "] \n\t" \ + "addq.ph %[" #O6 "], %[" #I3 "], %[" #I7 "] \n\t" \ + "subq.ph %[" #O7 "], %[" #I3 "], %[" #I7 "] \n\t" \ + "shra.ph %[" #O0 "], %[" #O0 "], 3 \n\t" \ + "shra.ph %[" #O1 "], %[" #O1 "], 3 \n\t" \ + "shra.ph %[" #O2 "], %[" #O2 "], 3 \n\t" \ + "shra.ph %[" #O3 "], %[" #O3 "], 3 \n\t" \ + "shra.ph %[" #O4 "], %[" #O4 "], 3 \n\t" \ + "shra.ph %[" #O5 "], %[" #O5 "], 3 \n\t" \ + "shra.ph %[" #O6 "], %[" #O6 "], 3 \n\t" \ + "shra.ph %[" #O7 "], %[" #O7 "], 3 \n\t" + +// precrq.ph.w temp0, temp8, temp2 +// temp0 = temp8[31..16] | temp2[31..16] +// ins temp2, temp8, 16, 16 +// temp2 = temp8[31..16] | temp2[15..0] +// O - output +// IO - input/output +// I - input (macro doesn't change it) +#define PACK_2_HALVES_TO_WORD(O0, O1, O2, O3, \ + IO0, IO1, IO2, IO3, \ + I0, I1, I2, I3) \ + "precrq.ph.w %[" #O0 "], %[" #I0 "], %[" #IO0 "] \n\t" \ + "precrq.ph.w %[" #O1 "], %[" #I1 "], %[" #IO1 "] \n\t" \ + "ins %[" #IO0 "], %[" #I0 "], 16, 16 \n\t" \ + "ins %[" #IO1 "], %[" #I1 "], 16, 16 \n\t" \ + "precrq.ph.w %[" #O2 "], %[" #I2 "], %[" #IO2 "] \n\t" \ + "precrq.ph.w %[" #O3 "], %[" #I3 "], %[" #IO3 "] \n\t" \ + "ins %[" #IO2 "], %[" #I2 "], 16, 16 \n\t" \ + "ins %[" #IO3 "], %[" #I3 "], 16, 16 \n\t" + +// preceu.ph.qbr temp0, temp8 +// temp0 = 0 | 0 | temp8[23..16] | temp8[7..0] +// preceu.ph.qbl temp1, temp8 +// temp1 = temp8[23..16] | temp8[7..0] | 0 | 0 +// O - output +// I - input (macro doesn't change it) +#define CONVERT_2_BYTES_TO_HALF(O0, O1, O2, O3, O4, O5, O6, O7, \ + I0, I1, I2, I3) \ + "preceu.ph.qbr %[" #O0 "], %[" #I0 "] \n\t" \ + "preceu.ph.qbl %[" #O1 "], %[" #I0 "] \n\t" \ + "preceu.ph.qbr %[" #O2 "], %[" #I1 "] \n\t" \ + "preceu.ph.qbl %[" #O3 "], %[" #I1 "] \n\t" \ + "preceu.ph.qbr %[" #O4 "], %[" #I2 "] \n\t" \ + "preceu.ph.qbl %[" #O5 "], %[" #I2 "] \n\t" \ + "preceu.ph.qbr %[" #O6 "], %[" #I3 "] \n\t" \ + "preceu.ph.qbl %[" #O7 "], %[" #I3 "] \n\t" + +// temp0[31..16 | 15..0] = temp0[31..16 | 15..0] + temp8[31..16 | 15..0] +// temp0[31..16 | 15..0] = temp0[31..16 <<(s) 7 | 15..0 <<(s) 7] +// temp1..temp7 same as temp0 +// precrqu_s.qb.ph temp0, temp1, temp0: +// temp0 = temp1[31..24] | temp1[15..8] | temp0[31..24] | temp0[15..8] +// store temp0 to dst +// IO - input/output +// I - input (macro doesn't change it) +#define STORE_SAT_SUM_X2(IO0, IO1, IO2, IO3, IO4, IO5, IO6, IO7, \ + I0, I1, I2, I3, I4, I5, I6, I7, \ + I8, I9, I10, I11, I12, I13) \ + "addq.ph %[" #IO0 "], %[" #IO0 "], %[" #I0 "] \n\t" \ + "addq.ph %[" #IO1 "], %[" #IO1 "], %[" #I1 "] \n\t" \ + "addq.ph %[" #IO2 "], %[" #IO2 "], %[" #I2 "] \n\t" \ + "addq.ph %[" #IO3 "], %[" #IO3 "], %[" #I3 "] \n\t" \ + "addq.ph %[" #IO4 "], %[" #IO4 "], %[" #I4 "] \n\t" \ + "addq.ph %[" #IO5 "], %[" #IO5 "], %[" #I5 "] \n\t" \ + "addq.ph %[" #IO6 "], %[" #IO6 "], %[" #I6 "] \n\t" \ + "addq.ph %[" #IO7 "], %[" #IO7 "], %[" #I7 "] \n\t" \ + "shll_s.ph %[" #IO0 "], %[" #IO0 "], 7 \n\t" \ + "shll_s.ph %[" #IO1 "], %[" #IO1 "], 7 \n\t" \ + "shll_s.ph %[" #IO2 "], %[" #IO2 "], 7 \n\t" \ + "shll_s.ph %[" #IO3 "], %[" #IO3 "], 7 \n\t" \ + "shll_s.ph %[" #IO4 "], %[" #IO4 "], 7 \n\t" \ + "shll_s.ph %[" #IO5 "], %[" #IO5 "], 7 \n\t" \ + "shll_s.ph %[" #IO6 "], %[" #IO6 "], 7 \n\t" \ + "shll_s.ph %[" #IO7 "], %[" #IO7 "], 7 \n\t" \ + "precrqu_s.qb.ph %[" #IO0 "], %[" #IO1 "], %[" #IO0 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO2 "], %[" #IO3 "], %[" #IO2 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO4 "], %[" #IO5 "], %[" #IO4 "] \n\t" \ + "precrqu_s.qb.ph %[" #IO6 "], %[" #IO7 "], %[" #IO6 "] \n\t" \ + "usw %[" #IO0 "], " XSTR(I13) "*" #I9 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO2 "], " XSTR(I13) "*" #I10 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO4 "], " XSTR(I13) "*" #I11 "(%[" #I8 "]) \n\t" \ + "usw %[" #IO6 "], " XSTR(I13) "*" #I12 "(%[" #I8 "]) \n\t" + +#define OUTPUT_EARLY_CLOBBER_REGS_10() \ + : [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), \ + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [temp6]"=&r"(temp6), \ + [temp7]"=&r"(temp7), [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), \ + [temp10]"=&r"(temp10) + +#define OUTPUT_EARLY_CLOBBER_REGS_18() \ + OUTPUT_EARLY_CLOBBER_REGS_10(), \ + [temp11]"=&r"(temp11), [temp12]"=&r"(temp12), [temp13]"=&r"(temp13), \ + [temp14]"=&r"(temp14), [temp15]"=&r"(temp15), [temp16]"=&r"(temp16), \ + [temp17]"=&r"(temp17), [temp18]"=&r"(temp18) + +#endif // WEBP_DSP_MIPS_MACRO_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/msa_macro.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/msa_macro.h new file mode 100644 index 0000000000..90adbbc319 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/msa_macro.h @@ -0,0 +1,1395 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA common macros +// +// Author(s): Prashant Patil (prashant.patil@imgtec.com) + +#ifndef WEBP_DSP_MSA_MACRO_H_ +#define WEBP_DSP_MSA_MACRO_H_ + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include +#include + +#if defined(__clang__) + #define CLANG_BUILD +#endif + +#ifdef CLANG_BUILD + #define ALPHAVAL (-1) + #define ADDVI_H(a, b) __msa_addvi_h((v8i16)a, b) + #define ADDVI_W(a, b) __msa_addvi_w((v4i32)a, b) + #define SRAI_B(a, b) __msa_srai_b((v16i8)a, b) + #define SRAI_H(a, b) __msa_srai_h((v8i16)a, b) + #define SRAI_W(a, b) __msa_srai_w((v4i32)a, b) + #define SRLI_H(a, b) __msa_srli_h((v8i16)a, b) + #define SLLI_B(a, b) __msa_slli_b((v4i32)a, b) + #define ANDI_B(a, b) __msa_andi_b((v16u8)a, b) + #define ORI_B(a, b) __msa_ori_b((v16u8)a, b) +#else + #define ALPHAVAL (0xff) + #define ADDVI_H(a, b) (a + b) + #define ADDVI_W(a, b) (a + b) + #define SRAI_B(a, b) (a >> b) + #define SRAI_H(a, b) (a >> b) + #define SRAI_W(a, b) (a >> b) + #define SRLI_H(a, b) (a << b) + #define SLLI_B(a, b) (a << b) + #define ANDI_B(a, b) (a & b) + #define ORI_B(a, b) (a | b) +#endif + +#define LD_B(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UB(...) LD_B(v16u8, __VA_ARGS__) +#define LD_SB(...) LD_B(v16i8, __VA_ARGS__) + +#define LD_H(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UH(...) LD_H(v8u16, __VA_ARGS__) +#define LD_SH(...) LD_H(v8i16, __VA_ARGS__) + +#define LD_W(RTYPE, psrc) *((RTYPE*)(psrc)) +#define LD_UW(...) LD_W(v4u32, __VA_ARGS__) +#define LD_SW(...) LD_W(v4i32, __VA_ARGS__) + +#define ST_B(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UB(...) ST_B(v16u8, __VA_ARGS__) +#define ST_SB(...) ST_B(v16i8, __VA_ARGS__) + +#define ST_H(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UH(...) ST_H(v8u16, __VA_ARGS__) +#define ST_SH(...) ST_H(v8i16, __VA_ARGS__) + +#define ST_W(RTYPE, in, pdst) *((RTYPE*)(pdst)) = in +#define ST_UW(...) ST_W(v4u32, __VA_ARGS__) +#define ST_SW(...) ST_W(v4i32, __VA_ARGS__) + +#define MSA_LOAD_FUNC(TYPE, INSTR, FUNC_NAME) \ + static inline TYPE FUNC_NAME(const void* const psrc) { \ + const uint8_t* const psrc_m = (const uint8_t*)psrc; \ + TYPE val_m; \ + __asm__ volatile("" #INSTR " %[val_m], %[psrc_m] \n\t" \ + : [val_m] "=r"(val_m) \ + : [psrc_m] "m"(*psrc_m)); \ + return val_m; \ + } + +#define MSA_LOAD(psrc, FUNC_NAME) FUNC_NAME(psrc) + +#define MSA_STORE_FUNC(TYPE, INSTR, FUNC_NAME) \ + static inline void FUNC_NAME(TYPE val, void* const pdst) { \ + uint8_t* const pdst_m = (uint8_t*)pdst; \ + TYPE val_m = val; \ + __asm__ volatile(" " #INSTR " %[val_m], %[pdst_m] \n\t" \ + : [pdst_m] "=m"(*pdst_m) \ + : [val_m] "r"(val_m)); \ + } + +#define MSA_STORE(val, pdst, FUNC_NAME) FUNC_NAME(val, pdst) + +#if (__mips_isa_rev >= 6) + MSA_LOAD_FUNC(uint16_t, lh, msa_lh); + #define LH(psrc) MSA_LOAD(psrc, msa_lh) + MSA_LOAD_FUNC(uint32_t, lw, msa_lw); + #define LW(psrc) MSA_LOAD(psrc, msa_lw) + #if (__mips == 64) + MSA_LOAD_FUNC(uint64_t, ld, msa_ld); + #define LD(psrc) MSA_LOAD(psrc, msa_ld) + #else // !(__mips == 64) + #define LD(psrc) ((((uint64_t)MSA_LOAD(psrc + 4, msa_lw)) << 32) | \ + MSA_LOAD(psrc, msa_lw)) + #endif // (__mips == 64) + + MSA_STORE_FUNC(uint16_t, sh, msa_sh); + #define SH(val, pdst) MSA_STORE(val, pdst, msa_sh) + MSA_STORE_FUNC(uint32_t, sw, msa_sw); + #define SW(val, pdst) MSA_STORE(val, pdst, msa_sw) + MSA_STORE_FUNC(uint64_t, sd, msa_sd); + #define SD(val, pdst) MSA_STORE(val, pdst, msa_sd) +#else // !(__mips_isa_rev >= 6) + MSA_LOAD_FUNC(uint16_t, ulh, msa_ulh); + #define LH(psrc) MSA_LOAD(psrc, msa_ulh) + MSA_LOAD_FUNC(uint32_t, ulw, msa_ulw); + #define LW(psrc) MSA_LOAD(psrc, msa_ulw) + #if (__mips == 64) + MSA_LOAD_FUNC(uint64_t, uld, msa_uld); + #define LD(psrc) MSA_LOAD(psrc, msa_uld) + #else // !(__mips == 64) + #define LD(psrc) ((((uint64_t)MSA_LOAD(psrc + 4, msa_ulw)) << 32) | \ + MSA_LOAD(psrc, msa_ulw)) + #endif // (__mips == 64) + + MSA_STORE_FUNC(uint16_t, ush, msa_ush); + #define SH(val, pdst) MSA_STORE(val, pdst, msa_ush) + MSA_STORE_FUNC(uint32_t, usw, msa_usw); + #define SW(val, pdst) MSA_STORE(val, pdst, msa_usw) + #define SD(val, pdst) do { \ + uint8_t* const pdst_sd_m = (uint8_t*)(pdst); \ + const uint32_t val0_m = (uint32_t)(val & 0x00000000FFFFFFFF); \ + const uint32_t val1_m = (uint32_t)((val >> 32) & 0x00000000FFFFFFFF); \ + SW(val0_m, pdst_sd_m); \ + SW(val1_m, pdst_sd_m + 4); \ + } while (0) +#endif // (__mips_isa_rev >= 6) + +/* Description : Load 4 words with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1, out2, out3 + * Details : Load word in 'out0' from (psrc) + * Load word in 'out1' from (psrc + stride) + * Load word in 'out2' from (psrc + 2 * stride) + * Load word in 'out3' from (psrc + 3 * stride) + */ +#define LW4(psrc, stride, out0, out1, out2, out3) do { \ + const uint8_t* ptmp = (const uint8_t*)psrc; \ + out0 = LW(ptmp); \ + ptmp += stride; \ + out1 = LW(ptmp); \ + ptmp += stride; \ + out2 = LW(ptmp); \ + ptmp += stride; \ + out3 = LW(ptmp); \ +} while (0) + +/* Description : Store words with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store word from 'in0' to (pdst) + * Store word from 'in1' to (pdst + stride) + * Store word from 'in2' to (pdst + 2 * stride) + * Store word from 'in3' to (pdst + 3 * stride) + */ +#define SW4(in0, in1, in2, in3, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ + ptmp += stride; \ + SW(in2, ptmp); \ + ptmp += stride; \ + SW(in3, ptmp); \ +} while (0) + +#define SW3(in0, in1, in2, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ + ptmp += stride; \ + SW(in2, ptmp); \ +} while (0) + +#define SW2(in0, in1, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SW(in0, ptmp); \ + ptmp += stride; \ + SW(in1, ptmp); \ +} while (0) + +/* Description : Store 4 double words with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store double word from 'in0' to (pdst) + * Store double word from 'in1' to (pdst + stride) + * Store double word from 'in2' to (pdst + 2 * stride) + * Store double word from 'in3' to (pdst + 3 * stride) + */ +#define SD4(in0, in1, in2, in3, pdst, stride) do { \ + uint8_t* ptmp = (uint8_t*)pdst; \ + SD(in0, ptmp); \ + ptmp += stride; \ + SD(in1, ptmp); \ + ptmp += stride; \ + SD(in2, ptmp); \ + ptmp += stride; \ + SD(in3, ptmp); \ +} while (0) + +/* Description : Load vectors with 16 byte elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Load 16 byte elements in 'out0' from (psrc) + * Load 16 byte elements in 'out1' from (psrc + stride) + */ +#define LD_B2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_B(RTYPE, psrc); \ + out1 = LD_B(RTYPE, psrc + stride); \ +} while (0) +#define LD_UB2(...) LD_B2(v16u8, __VA_ARGS__) +#define LD_SB2(...) LD_B2(v16i8, __VA_ARGS__) + +#define LD_B3(RTYPE, psrc, stride, out0, out1, out2) do { \ + LD_B2(RTYPE, psrc, stride, out0, out1); \ + out2 = LD_B(RTYPE, psrc + 2 * stride); \ +} while (0) +#define LD_UB3(...) LD_B3(v16u8, __VA_ARGS__) +#define LD_SB3(...) LD_B3(v16i8, __VA_ARGS__) + +#define LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3) do { \ + LD_B2(RTYPE, psrc, stride, out0, out1); \ + LD_B2(RTYPE, psrc + 2 * stride , stride, out2, out3); \ +} while (0) +#define LD_UB4(...) LD_B4(v16u8, __VA_ARGS__) +#define LD_SB4(...) LD_B4(v16i8, __VA_ARGS__) + +#define LD_B8(RTYPE, psrc, stride, \ + out0, out1, out2, out3, out4, out5, out6, out7) do { \ + LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3); \ + LD_B4(RTYPE, psrc + 4 * stride, stride, out4, out5, out6, out7); \ +} while (0) +#define LD_UB8(...) LD_B8(v16u8, __VA_ARGS__) +#define LD_SB8(...) LD_B8(v16i8, __VA_ARGS__) + +/* Description : Load vectors with 8 halfword elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1 + * Details : Load 8 halfword elements in 'out0' from (psrc) + * Load 8 halfword elements in 'out1' from (psrc + stride) + */ +#define LD_H2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_H(RTYPE, psrc); \ + out1 = LD_H(RTYPE, psrc + stride); \ +} while (0) +#define LD_UH2(...) LD_H2(v8u16, __VA_ARGS__) +#define LD_SH2(...) LD_H2(v8i16, __VA_ARGS__) + +/* Description : Load vectors with 4 word elements with stride + * Arguments : Inputs - psrc, stride + * Outputs - out0, out1, out2, out3 + * Details : Load 4 word elements in 'out0' from (psrc + 0 * stride) + * Load 4 word elements in 'out1' from (psrc + 1 * stride) + * Load 4 word elements in 'out2' from (psrc + 2 * stride) + * Load 4 word elements in 'out3' from (psrc + 3 * stride) + */ +#define LD_W2(RTYPE, psrc, stride, out0, out1) do { \ + out0 = LD_W(RTYPE, psrc); \ + out1 = LD_W(RTYPE, psrc + stride); \ +} while (0) +#define LD_UW2(...) LD_W2(v4u32, __VA_ARGS__) +#define LD_SW2(...) LD_W2(v4i32, __VA_ARGS__) + +#define LD_W3(RTYPE, psrc, stride, out0, out1, out2) do { \ + LD_W2(RTYPE, psrc, stride, out0, out1); \ + out2 = LD_W(RTYPE, psrc + 2 * stride); \ +} while (0) +#define LD_UW3(...) LD_W3(v4u32, __VA_ARGS__) +#define LD_SW3(...) LD_W3(v4i32, __VA_ARGS__) + +#define LD_W4(RTYPE, psrc, stride, out0, out1, out2, out3) do { \ + LD_W2(RTYPE, psrc, stride, out0, out1); \ + LD_W2(RTYPE, psrc + 2 * stride, stride, out2, out3); \ +} while (0) +#define LD_UW4(...) LD_W4(v4u32, __VA_ARGS__) +#define LD_SW4(...) LD_W4(v4i32, __VA_ARGS__) + +/* Description : Store vectors of 16 byte elements with stride + * Arguments : Inputs - in0, in1, pdst, stride + * Details : Store 16 byte elements from 'in0' to (pdst) + * Store 16 byte elements from 'in1' to (pdst + stride) + */ +#define ST_B2(RTYPE, in0, in1, pdst, stride) do { \ + ST_B(RTYPE, in0, pdst); \ + ST_B(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UB2(...) ST_B2(v16u8, __VA_ARGS__) +#define ST_SB2(...) ST_B2(v16i8, __VA_ARGS__) + +#define ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride) do { \ + ST_B2(RTYPE, in0, in1, pdst, stride); \ + ST_B2(RTYPE, in2, in3, pdst + 2 * stride, stride); \ +} while (0) +#define ST_UB4(...) ST_B4(v16u8, __VA_ARGS__) +#define ST_SB4(...) ST_B4(v16i8, __VA_ARGS__) + +#define ST_B8(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + pdst, stride) do { \ + ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride); \ + ST_B4(RTYPE, in4, in5, in6, in7, pdst + 4 * stride, stride); \ +} while (0) +#define ST_UB8(...) ST_B8(v16u8, __VA_ARGS__) + +/* Description : Store vectors of 4 word elements with stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Store 4 word elements from 'in0' to (pdst + 0 * stride) + * Store 4 word elements from 'in1' to (pdst + 1 * stride) + * Store 4 word elements from 'in2' to (pdst + 2 * stride) + * Store 4 word elements from 'in3' to (pdst + 3 * stride) + */ +#define ST_W2(RTYPE, in0, in1, pdst, stride) do { \ + ST_W(RTYPE, in0, pdst); \ + ST_W(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UW2(...) ST_W2(v4u32, __VA_ARGS__) +#define ST_SW2(...) ST_W2(v4i32, __VA_ARGS__) + +#define ST_W3(RTYPE, in0, in1, in2, pdst, stride) do { \ + ST_W2(RTYPE, in0, in1, pdst, stride); \ + ST_W(RTYPE, in2, pdst + 2 * stride); \ +} while (0) +#define ST_UW3(...) ST_W3(v4u32, __VA_ARGS__) +#define ST_SW3(...) ST_W3(v4i32, __VA_ARGS__) + +#define ST_W4(RTYPE, in0, in1, in2, in3, pdst, stride) do { \ + ST_W2(RTYPE, in0, in1, pdst, stride); \ + ST_W2(RTYPE, in2, in3, pdst + 2 * stride, stride); \ +} while (0) +#define ST_UW4(...) ST_W4(v4u32, __VA_ARGS__) +#define ST_SW4(...) ST_W4(v4i32, __VA_ARGS__) + +/* Description : Store vectors of 8 halfword elements with stride + * Arguments : Inputs - in0, in1, pdst, stride + * Details : Store 8 halfword elements from 'in0' to (pdst) + * Store 8 halfword elements from 'in1' to (pdst + stride) + */ +#define ST_H2(RTYPE, in0, in1, pdst, stride) do { \ + ST_H(RTYPE, in0, pdst); \ + ST_H(RTYPE, in1, pdst + stride); \ +} while (0) +#define ST_UH2(...) ST_H2(v8u16, __VA_ARGS__) +#define ST_SH2(...) ST_H2(v8i16, __VA_ARGS__) + +/* Description : Store 2x4 byte block to destination memory from input vector + * Arguments : Inputs - in, stidx, pdst, stride + * Details : Index 'stidx' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst) + * Index 'stidx+1' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + stride) + * Index 'stidx+2' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + 2 * stride) + * Index 'stidx+3' halfword element from 'in' vector is copied to + * the GP register and stored to (pdst + 3 * stride) + */ +#define ST2x4_UB(in, stidx, pdst, stride) do { \ + uint8_t* pblk_2x4_m = (uint8_t*)pdst; \ + const uint16_t out0_m = __msa_copy_s_h((v8i16)in, stidx); \ + const uint16_t out1_m = __msa_copy_s_h((v8i16)in, stidx + 1); \ + const uint16_t out2_m = __msa_copy_s_h((v8i16)in, stidx + 2); \ + const uint16_t out3_m = __msa_copy_s_h((v8i16)in, stidx + 3); \ + SH(out0_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out1_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out2_m, pblk_2x4_m); \ + pblk_2x4_m += stride; \ + SH(out3_m, pblk_2x4_m); \ +} while (0) + +/* Description : Store 4x4 byte block to destination memory from input vector + * Arguments : Inputs - in0, in1, pdst, stride + * Details : 'Idx0' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst) + * 'Idx1' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + stride) + * 'Idx2' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + 2 * stride) + * 'Idx3' word element from input vector 'in0' is copied to the + * GP register and stored to (pdst + 3 * stride) + */ +#define ST4x4_UB(in0, in1, idx0, idx1, idx2, idx3, pdst, stride) do { \ + uint8_t* const pblk_4x4_m = (uint8_t*)pdst; \ + const uint32_t out0_m = __msa_copy_s_w((v4i32)in0, idx0); \ + const uint32_t out1_m = __msa_copy_s_w((v4i32)in0, idx1); \ + const uint32_t out2_m = __msa_copy_s_w((v4i32)in1, idx2); \ + const uint32_t out3_m = __msa_copy_s_w((v4i32)in1, idx3); \ + SW4(out0_m, out1_m, out2_m, out3_m, pblk_4x4_m, stride); \ +} while (0) + +#define ST4x8_UB(in0, in1, pdst, stride) do { \ + uint8_t* const pblk_4x8 = (uint8_t*)pdst; \ + ST4x4_UB(in0, in0, 0, 1, 2, 3, pblk_4x8, stride); \ + ST4x4_UB(in1, in1, 0, 1, 2, 3, pblk_4x8 + 4 * stride, stride); \ +} while (0) + +/* Description : Immediate number of elements to slide + * Arguments : Inputs - in0, in1, slide_val + * Outputs - out + * Return Type - as per RTYPE + * Details : Byte elements from 'in1' vector are slid into 'in0' by + * value specified in the 'slide_val' + */ +#define SLDI_B(RTYPE, in0, in1, slide_val) \ + (RTYPE)__msa_sldi_b((v16i8)in0, (v16i8)in1, slide_val) \ + +#define SLDI_UB(...) SLDI_B(v16u8, __VA_ARGS__) +#define SLDI_SB(...) SLDI_B(v16i8, __VA_ARGS__) +#define SLDI_SH(...) SLDI_B(v8i16, __VA_ARGS__) + +/* Description : Shuffle byte vector elements as per mask vector + * Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Byte elements from 'in0' & 'in1' are copied selectively to + * 'out0' as per control vector 'mask0' + */ +#define VSHF_B(RTYPE, in0, in1, mask) \ + (RTYPE)__msa_vshf_b((v16i8)mask, (v16i8)in1, (v16i8)in0) + +#define VSHF_UB(...) VSHF_B(v16u8, __VA_ARGS__) +#define VSHF_SB(...) VSHF_B(v16i8, __VA_ARGS__) +#define VSHF_UH(...) VSHF_B(v8u16, __VA_ARGS__) +#define VSHF_SH(...) VSHF_B(v8i16, __VA_ARGS__) + +#define VSHF_B2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) do { \ + out0 = VSHF_B(RTYPE, in0, in1, mask0); \ + out1 = VSHF_B(RTYPE, in2, in3, mask1); \ +} while (0) +#define VSHF_B2_UB(...) VSHF_B2(v16u8, __VA_ARGS__) +#define VSHF_B2_SB(...) VSHF_B2(v16i8, __VA_ARGS__) +#define VSHF_B2_UH(...) VSHF_B2(v8u16, __VA_ARGS__) +#define VSHF_B2_SH(...) VSHF_B2(v8i16, __VA_ARGS__) + +/* Description : Shuffle halfword vector elements as per mask vector + * Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : halfword elements from 'in0' & 'in1' are copied selectively to + * 'out0' as per control vector 'mask0' + */ +#define VSHF_H2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) do { \ + out0 = (RTYPE)__msa_vshf_h((v8i16)mask0, (v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_vshf_h((v8i16)mask1, (v8i16)in3, (v8i16)in2); \ +} while (0) +#define VSHF_H2_UH(...) VSHF_H2(v8u16, __VA_ARGS__) +#define VSHF_H2_SH(...) VSHF_H2(v8i16, __VA_ARGS__) + +/* Description : Dot product of byte vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed byte elements from 'mult0' are multiplied with + * signed byte elements from 'cnst0' producing a result + * twice the size of input i.e. signed halfword. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector +*/ +#define DOTP_SB2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_s_h((v16i8)mult0, (v16i8)cnst0); \ + out1 = (RTYPE)__msa_dotp_s_h((v16i8)mult1, (v16i8)cnst1); \ +} while (0) +#define DOTP_SB2_SH(...) DOTP_SB2(v8i16, __VA_ARGS__) + +/* Description : Dot product of halfword vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed halfword elements from 'mult0' are multiplied with + * signed halfword elements from 'cnst0' producing a result + * twice the size of input i.e. signed word. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector + */ +#define DOTP_SH2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_s_w((v8i16)mult0, (v8i16)cnst0); \ + out1 = (RTYPE)__msa_dotp_s_w((v8i16)mult1, (v8i16)cnst1); \ +} while (0) +#define DOTP_SH2_SW(...) DOTP_SH2(v4i32, __VA_ARGS__) + +/* Description : Dot product of unsigned word vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Unsigned word elements from 'mult0' are multiplied with + * unsigned word elements from 'cnst0' producing a result + * twice the size of input i.e. unsigned double word. + * The multiplication result of adjacent odd-even elements + * are added together and written to the 'out0' vector + */ +#define DOTP_UW2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dotp_u_d((v4u32)mult0, (v4u32)cnst0); \ + out1 = (RTYPE)__msa_dotp_u_d((v4u32)mult1, (v4u32)cnst1); \ +} while (0) +#define DOTP_UW2_UD(...) DOTP_UW2(v2u64, __VA_ARGS__) + +/* Description : Dot product & addition of halfword vector elements + * Arguments : Inputs - mult0, mult1, cnst0, cnst1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Signed halfword elements from 'mult0' are multiplied with + * signed halfword elements from 'cnst0' producing a result + * twice the size of input i.e. signed word. + * The multiplication result of adjacent odd-even elements + * are added to the 'out0' vector + */ +#define DPADD_SH2(RTYPE, mult0, mult1, cnst0, cnst1, out0, out1) do { \ + out0 = (RTYPE)__msa_dpadd_s_w((v4i32)out0, (v8i16)mult0, (v8i16)cnst0); \ + out1 = (RTYPE)__msa_dpadd_s_w((v4i32)out1, (v8i16)mult1, (v8i16)cnst1); \ +} while (0) +#define DPADD_SH2_SW(...) DPADD_SH2(v4i32, __VA_ARGS__) + +/* Description : Clips all signed halfword elements of input vector + * between 0 & 255 + * Arguments : Input/output - val + * Return Type - signed halfword + */ +#define CLIP_SH_0_255(val) do { \ + const v8i16 max_m = __msa_ldi_h(255); \ + val = __msa_maxi_s_h((v8i16)val, 0); \ + val = __msa_min_s_h(max_m, (v8i16)val); \ +} while (0) + +#define CLIP_SH2_0_255(in0, in1) do { \ + CLIP_SH_0_255(in0); \ + CLIP_SH_0_255(in1); \ +} while (0) + +#define CLIP_SH4_0_255(in0, in1, in2, in3) do { \ + CLIP_SH2_0_255(in0, in1); \ + CLIP_SH2_0_255(in2, in3); \ +} while (0) + +/* Description : Clips all unsigned halfword elements of input vector + * between 0 & 255 + * Arguments : Input - in + * Output - out_m + * Return Type - unsigned halfword + */ +#define CLIP_UH_0_255(in) do { \ + const v8u16 max_m = (v8u16)__msa_ldi_h(255); \ + in = __msa_maxi_u_h((v8u16) in, 0); \ + in = __msa_min_u_h((v8u16) max_m, (v8u16) in); \ +} while (0) + +#define CLIP_UH2_0_255(in0, in1) do { \ + CLIP_UH_0_255(in0); \ + CLIP_UH_0_255(in1); \ +} while (0) + +/* Description : Clips all signed word elements of input vector + * between 0 & 255 + * Arguments : Input/output - val + * Return Type - signed word + */ +#define CLIP_SW_0_255(val) do { \ + const v4i32 max_m = __msa_ldi_w(255); \ + val = __msa_maxi_s_w((v4i32)val, 0); \ + val = __msa_min_s_w(max_m, (v4i32)val); \ +} while (0) + +#define CLIP_SW4_0_255(in0, in1, in2, in3) do { \ + CLIP_SW_0_255(in0); \ + CLIP_SW_0_255(in1); \ + CLIP_SW_0_255(in2); \ + CLIP_SW_0_255(in3); \ +} while (0) + +/* Description : Horizontal addition of 4 signed word elements of input vector + * Arguments : Input - in (signed word vector) + * Output - sum_m (i32 sum) + * Return Type - signed word (GP) + * Details : 4 signed word elements of 'in' vector are added together and + * the resulting integer sum is returned + */ +static WEBP_INLINE int32_t func_hadd_sw_s32(v4i32 in) { + const v2i64 res0_m = __msa_hadd_s_d((v4i32)in, (v4i32)in); + const v2i64 res1_m = __msa_splati_d(res0_m, 1); + const v2i64 out = res0_m + res1_m; + int32_t sum_m = __msa_copy_s_w((v4i32)out, 0); + return sum_m; +} +#define HADD_SW_S32(in) func_hadd_sw_s32(in) + +/* Description : Horizontal addition of 8 signed halfword elements + * Arguments : Input - in (signed halfword vector) + * Output - sum_m (s32 sum) + * Return Type - signed word + * Details : 8 signed halfword elements of input vector are added + * together and the resulting integer sum is returned + */ +static WEBP_INLINE int32_t func_hadd_sh_s32(v8i16 in) { + const v4i32 res = __msa_hadd_s_w(in, in); + const v2i64 res0 = __msa_hadd_s_d(res, res); + const v2i64 res1 = __msa_splati_d(res0, 1); + const v2i64 res2 = res0 + res1; + const int32_t sum_m = __msa_copy_s_w((v4i32)res2, 0); + return sum_m; +} +#define HADD_SH_S32(in) func_hadd_sh_s32(in) + +/* Description : Horizontal addition of 8 unsigned halfword elements + * Arguments : Input - in (unsigned halfword vector) + * Output - sum_m (u32 sum) + * Return Type - unsigned word + * Details : 8 unsigned halfword elements of input vector are added + * together and the resulting integer sum is returned + */ +static WEBP_INLINE uint32_t func_hadd_uh_u32(v8u16 in) { + uint32_t sum_m; + const v4u32 res_m = __msa_hadd_u_w(in, in); + v2u64 res0_m = __msa_hadd_u_d(res_m, res_m); + v2u64 res1_m = (v2u64)__msa_splati_d((v2i64)res0_m, 1); + res0_m = res0_m + res1_m; + sum_m = __msa_copy_s_w((v4i32)res0_m, 0); + return sum_m; +} +#define HADD_UH_U32(in) func_hadd_uh_u32(in) + +/* Description : Horizontal addition of signed half word vector elements + Arguments : Inputs - in0, in1 + Outputs - out0, out1 + Return Type - as per RTYPE + Details : Each signed odd half word element from 'in0' is added to + even signed half word element from 'in0' (pairwise) and the + halfword result is written in 'out0' +*/ +#define HADD_SH2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_hadd_s_w((v8i16)in0, (v8i16)in0); \ + out1 = (RTYPE)__msa_hadd_s_w((v8i16)in1, (v8i16)in1); \ +} while (0) +#define HADD_SH2_SW(...) HADD_SH2(v4i32, __VA_ARGS__) + +#define HADD_SH4(RTYPE, in0, in1, in2, in3, out0, out1, out2, out3) do { \ + HADD_SH2(RTYPE, in0, in1, out0, out1); \ + HADD_SH2(RTYPE, in2, in3, out2, out3); \ +} while (0) +#define HADD_SH4_SW(...) HADD_SH4(v4i32, __VA_ARGS__) + +/* Description : Horizontal subtraction of unsigned byte vector elements + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Each unsigned odd byte element from 'in0' is subtracted from + * even unsigned byte element from 'in0' (pairwise) and the + * halfword result is written to 'out0' + */ +#define HSUB_UB2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_hsub_u_h((v16u8)in0, (v16u8)in0); \ + out1 = (RTYPE)__msa_hsub_u_h((v16u8)in1, (v16u8)in1); \ +} while (0) +#define HSUB_UB2_UH(...) HSUB_UB2(v8u16, __VA_ARGS__) +#define HSUB_UB2_SH(...) HSUB_UB2(v8i16, __VA_ARGS__) +#define HSUB_UB2_SW(...) HSUB_UB2(v4i32, __VA_ARGS__) + +/* Description : Set element n input vector to GPR value + * Arguments : Inputs - in0, in1, in2, in3 + * Output - out + * Return Type - as per RTYPE + * Details : Set element 0 in vector 'out' to value specified in 'in0' + */ +#define INSERT_W2(RTYPE, in0, in1, out) do { \ + out = (RTYPE)__msa_insert_w((v4i32)out, 0, in0); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 1, in1); \ +} while (0) +#define INSERT_W2_UB(...) INSERT_W2(v16u8, __VA_ARGS__) +#define INSERT_W2_SB(...) INSERT_W2(v16i8, __VA_ARGS__) + +#define INSERT_W4(RTYPE, in0, in1, in2, in3, out) do { \ + out = (RTYPE)__msa_insert_w((v4i32)out, 0, in0); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 1, in1); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 2, in2); \ + out = (RTYPE)__msa_insert_w((v4i32)out, 3, in3); \ +} while (0) +#define INSERT_W4_UB(...) INSERT_W4(v16u8, __VA_ARGS__) +#define INSERT_W4_SB(...) INSERT_W4(v16i8, __VA_ARGS__) +#define INSERT_W4_SW(...) INSERT_W4(v4i32, __VA_ARGS__) + +/* Description : Set element n of double word input vector to GPR value + * Arguments : Inputs - in0, in1 + * Output - out + * Return Type - as per RTYPE + * Details : Set element 0 in vector 'out' to GPR value specified in 'in0' + * Set element 1 in vector 'out' to GPR value specified in 'in1' + */ +#define INSERT_D2(RTYPE, in0, in1, out) do { \ + out = (RTYPE)__msa_insert_d((v2i64)out, 0, in0); \ + out = (RTYPE)__msa_insert_d((v2i64)out, 1, in1); \ +} while (0) +#define INSERT_D2_UB(...) INSERT_D2(v16u8, __VA_ARGS__) +#define INSERT_D2_SB(...) INSERT_D2(v16i8, __VA_ARGS__) + +/* Description : Interleave even byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_b((v16i8)in1, (v16i8)in0); \ + out1 = (RTYPE)__msa_ilvev_b((v16i8)in3, (v16i8)in2); \ +} while (0) +#define ILVEV_B2_UB(...) ILVEV_B2(v16u8, __VA_ARGS__) +#define ILVEV_B2_SB(...) ILVEV_B2(v16i8, __VA_ARGS__) +#define ILVEV_B2_UH(...) ILVEV_B2(v8u16, __VA_ARGS__) +#define ILVEV_B2_SH(...) ILVEV_B2(v8i16, __VA_ARGS__) +#define ILVEV_B2_SD(...) ILVEV_B2(v2i64, __VA_ARGS__) + +/* Description : Interleave odd byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVOD_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvod_b((v16i8)in1, (v16i8)in0); \ + out1 = (RTYPE)__msa_ilvod_b((v16i8)in3, (v16i8)in2); \ +} while (0) +#define ILVOD_B2_UB(...) ILVOD_B2(v16u8, __VA_ARGS__) +#define ILVOD_B2_SB(...) ILVOD_B2(v16i8, __VA_ARGS__) +#define ILVOD_B2_UH(...) ILVOD_B2(v8u16, __VA_ARGS__) +#define ILVOD_B2_SH(...) ILVOD_B2(v8i16, __VA_ARGS__) +#define ILVOD_B2_SD(...) ILVOD_B2(v2i64, __VA_ARGS__) + +/* Description : Interleave even halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even halfword elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvev_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVEV_H2_UB(...) ILVEV_H2(v16u8, __VA_ARGS__) +#define ILVEV_H2_UH(...) ILVEV_H2(v8u16, __VA_ARGS__) +#define ILVEV_H2_SH(...) ILVEV_H2(v8i16, __VA_ARGS__) +#define ILVEV_H2_SW(...) ILVEV_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave odd halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd halfword elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvod_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvod_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVOD_H2_UB(...) ILVOD_H2(v16u8, __VA_ARGS__) +#define ILVOD_H2_UH(...) ILVOD_H2(v8u16, __VA_ARGS__) +#define ILVOD_H2_SH(...) ILVOD_H2(v8i16, __VA_ARGS__) +#define ILVOD_H2_SW(...) ILVOD_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave even word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_w((v4i32)in1, (v4i32)in0); \ + out1 = (RTYPE)__msa_ilvev_w((v4i32)in3, (v4i32)in2); \ +} while (0) +#define ILVEV_W2_UB(...) ILVEV_W2(v16u8, __VA_ARGS__) +#define ILVEV_W2_SB(...) ILVEV_W2(v16i8, __VA_ARGS__) +#define ILVEV_W2_UH(...) ILVEV_W2(v8u16, __VA_ARGS__) +#define ILVEV_W2_SD(...) ILVEV_W2(v2i64, __VA_ARGS__) + +/* Description : Interleave even-odd word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + * Odd word elements of 'in2' and 'in3' are interleaved + * and written to 'out1' + */ +#define ILVEVOD_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_w((v4i32)in1, (v4i32)in0); \ + out1 = (RTYPE)__msa_ilvod_w((v4i32)in3, (v4i32)in2); \ +} while (0) +#define ILVEVOD_W2_UB(...) ILVEVOD_W2(v16u8, __VA_ARGS__) +#define ILVEVOD_W2_UH(...) ILVEVOD_W2(v8u16, __VA_ARGS__) +#define ILVEVOD_W2_SH(...) ILVEVOD_W2(v8i16, __VA_ARGS__) +#define ILVEVOD_W2_SW(...) ILVEVOD_W2(v4i32, __VA_ARGS__) + +/* Description : Interleave even-odd half-word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even half-word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + * Odd half-word elements of 'in2' and 'in3' are interleaved + * and written to 'out1' + */ +#define ILVEVOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_h((v8i16)in1, (v8i16)in0); \ + out1 = (RTYPE)__msa_ilvod_h((v8i16)in3, (v8i16)in2); \ +} while (0) +#define ILVEVOD_H2_UB(...) ILVEVOD_H2(v16u8, __VA_ARGS__) +#define ILVEVOD_H2_UH(...) ILVEVOD_H2(v8u16, __VA_ARGS__) +#define ILVEVOD_H2_SH(...) ILVEVOD_H2(v8i16, __VA_ARGS__) +#define ILVEVOD_H2_SW(...) ILVEVOD_H2(v4i32, __VA_ARGS__) + +/* Description : Interleave even double word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even double word elements of 'in0' and 'in1' are interleaved + * and written to 'out0' + */ +#define ILVEV_D2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvev_d((v2i64)in1, (v2i64)in0); \ + out1 = (RTYPE)__msa_ilvev_d((v2i64)in3, (v2i64)in2); \ +} while (0) +#define ILVEV_D2_UB(...) ILVEV_D2(v16u8, __VA_ARGS__) +#define ILVEV_D2_SB(...) ILVEV_D2(v16i8, __VA_ARGS__) +#define ILVEV_D2_SW(...) ILVEV_D2(v4i32, __VA_ARGS__) +#define ILVEV_D2_SD(...) ILVEV_D2(v2i64, __VA_ARGS__) + +/* Description : Interleave left half of byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Left half of byte elements of 'in0' and 'in1' are interleaved + * and written to 'out0'. + */ +#define ILVL_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvl_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvl_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define ILVL_B2_UB(...) ILVL_B2(v16u8, __VA_ARGS__) +#define ILVL_B2_SB(...) ILVL_B2(v16i8, __VA_ARGS__) +#define ILVL_B2_UH(...) ILVL_B2(v8u16, __VA_ARGS__) +#define ILVL_B2_SH(...) ILVL_B2(v8i16, __VA_ARGS__) +#define ILVL_B2_SW(...) ILVL_B2(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of byte elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of byte elements of 'in0' and 'in1' are interleaved + * and written to out0. + */ +#define ILVR_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvr_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define ILVR_B2_UB(...) ILVR_B2(v16u8, __VA_ARGS__) +#define ILVR_B2_SB(...) ILVR_B2(v16i8, __VA_ARGS__) +#define ILVR_B2_UH(...) ILVR_B2(v8u16, __VA_ARGS__) +#define ILVR_B2_SH(...) ILVR_B2(v8i16, __VA_ARGS__) +#define ILVR_B2_SW(...) ILVR_B2(v4i32, __VA_ARGS__) + +#define ILVR_B4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_B2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_B2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_B4_UB(...) ILVR_B4(v16u8, __VA_ARGS__) +#define ILVR_B4_SB(...) ILVR_B4(v16i8, __VA_ARGS__) +#define ILVR_B4_UH(...) ILVR_B4(v8u16, __VA_ARGS__) +#define ILVR_B4_SH(...) ILVR_B4(v8i16, __VA_ARGS__) +#define ILVR_B4_SW(...) ILVR_B4(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of halfword elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of halfword elements of 'in0' and 'in1' are + * interleaved and written to 'out0'. + */ +#define ILVR_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_ilvr_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define ILVR_H2_UB(...) ILVR_H2(v16u8, __VA_ARGS__) +#define ILVR_H2_SH(...) ILVR_H2(v8i16, __VA_ARGS__) +#define ILVR_H2_SW(...) ILVR_H2(v4i32, __VA_ARGS__) + +#define ILVR_H4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_H2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_H2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_H4_UB(...) ILVR_H4(v16u8, __VA_ARGS__) +#define ILVR_H4_SH(...) ILVR_H4(v8i16, __VA_ARGS__) +#define ILVR_H4_SW(...) ILVR_H4(v4i32, __VA_ARGS__) + +/* Description : Interleave right half of double word elements from vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of double word elements of 'in0' and 'in1' are + * interleaved and written to 'out0'. + */ +#define ILVR_D2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_d((v2i64)in0, (v2i64)in1); \ + out1 = (RTYPE)__msa_ilvr_d((v2i64)in2, (v2i64)in3); \ +} while (0) +#define ILVR_D2_UB(...) ILVR_D2(v16u8, __VA_ARGS__) +#define ILVR_D2_SB(...) ILVR_D2(v16i8, __VA_ARGS__) +#define ILVR_D2_SH(...) ILVR_D2(v8i16, __VA_ARGS__) + +#define ILVR_D4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ILVR_D2(RTYPE, in0, in1, in2, in3, out0, out1); \ + ILVR_D2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define ILVR_D4_SB(...) ILVR_D4(v16i8, __VA_ARGS__) +#define ILVR_D4_UB(...) ILVR_D4(v16u8, __VA_ARGS__) + +/* Description : Interleave both left and right half of input vectors + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Right half of byte elements from 'in0' and 'in1' are + * interleaved and written to 'out0' + */ +#define ILVRL_B2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_ilvl_b((v16i8)in0, (v16i8)in1); \ +} while (0) +#define ILVRL_B2_UB(...) ILVRL_B2(v16u8, __VA_ARGS__) +#define ILVRL_B2_SB(...) ILVRL_B2(v16i8, __VA_ARGS__) +#define ILVRL_B2_UH(...) ILVRL_B2(v8u16, __VA_ARGS__) +#define ILVRL_B2_SH(...) ILVRL_B2(v8i16, __VA_ARGS__) +#define ILVRL_B2_SW(...) ILVRL_B2(v4i32, __VA_ARGS__) + +#define ILVRL_H2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_ilvl_h((v8i16)in0, (v8i16)in1); \ +} while (0) +#define ILVRL_H2_UB(...) ILVRL_H2(v16u8, __VA_ARGS__) +#define ILVRL_H2_SB(...) ILVRL_H2(v16i8, __VA_ARGS__) +#define ILVRL_H2_SH(...) ILVRL_H2(v8i16, __VA_ARGS__) +#define ILVRL_H2_SW(...) ILVRL_H2(v4i32, __VA_ARGS__) +#define ILVRL_H2_UW(...) ILVRL_H2(v4u32, __VA_ARGS__) + +#define ILVRL_W2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_ilvr_w((v4i32)in0, (v4i32)in1); \ + out1 = (RTYPE)__msa_ilvl_w((v4i32)in0, (v4i32)in1); \ +} while (0) +#define ILVRL_W2_UB(...) ILVRL_W2(v16u8, __VA_ARGS__) +#define ILVRL_W2_SH(...) ILVRL_W2(v8i16, __VA_ARGS__) +#define ILVRL_W2_SW(...) ILVRL_W2(v4i32, __VA_ARGS__) +#define ILVRL_W2_UW(...) ILVRL_W2(v4u32, __VA_ARGS__) + +/* Description : Pack even byte elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even byte elements of 'in0' are copied to the left half of + * 'out0' & even byte elements of 'in1' are copied to the right + * half of 'out0'. + */ +#define PCKEV_B2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_b((v16i8)in0, (v16i8)in1); \ + out1 = (RTYPE)__msa_pckev_b((v16i8)in2, (v16i8)in3); \ +} while (0) +#define PCKEV_B2_SB(...) PCKEV_B2(v16i8, __VA_ARGS__) +#define PCKEV_B2_UB(...) PCKEV_B2(v16u8, __VA_ARGS__) +#define PCKEV_B2_SH(...) PCKEV_B2(v8i16, __VA_ARGS__) +#define PCKEV_B2_SW(...) PCKEV_B2(v4i32, __VA_ARGS__) + +#define PCKEV_B4(RTYPE, in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + PCKEV_B2(RTYPE, in0, in1, in2, in3, out0, out1); \ + PCKEV_B2(RTYPE, in4, in5, in6, in7, out2, out3); \ +} while (0) +#define PCKEV_B4_SB(...) PCKEV_B4(v16i8, __VA_ARGS__) +#define PCKEV_B4_UB(...) PCKEV_B4(v16u8, __VA_ARGS__) +#define PCKEV_B4_SH(...) PCKEV_B4(v8i16, __VA_ARGS__) +#define PCKEV_B4_SW(...) PCKEV_B4(v4i32, __VA_ARGS__) + +/* Description : Pack even halfword elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even halfword elements of 'in0' are copied to the left half of + * 'out0' & even halfword elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKEV_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_pckev_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define PCKEV_H2_UH(...) PCKEV_H2(v8u16, __VA_ARGS__) +#define PCKEV_H2_SH(...) PCKEV_H2(v8i16, __VA_ARGS__) +#define PCKEV_H2_SW(...) PCKEV_H2(v4i32, __VA_ARGS__) +#define PCKEV_H2_UW(...) PCKEV_H2(v4u32, __VA_ARGS__) + +/* Description : Pack even word elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Even word elements of 'in0' are copied to the left half of + * 'out0' & even word elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKEV_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckev_w((v4i32)in0, (v4i32)in1); \ + out1 = (RTYPE)__msa_pckev_w((v4i32)in2, (v4i32)in3); \ +} while (0) +#define PCKEV_W2_UH(...) PCKEV_W2(v8u16, __VA_ARGS__) +#define PCKEV_W2_SH(...) PCKEV_W2(v8i16, __VA_ARGS__) +#define PCKEV_W2_SW(...) PCKEV_W2(v4i32, __VA_ARGS__) +#define PCKEV_W2_UW(...) PCKEV_W2(v4u32, __VA_ARGS__) + +/* Description : Pack odd halfword elements of vector pairs + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Odd halfword elements of 'in0' are copied to the left half of + * 'out0' & odd halfword elements of 'in1' are copied to the + * right half of 'out0'. + */ +#define PCKOD_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_pckod_h((v8i16)in0, (v8i16)in1); \ + out1 = (RTYPE)__msa_pckod_h((v8i16)in2, (v8i16)in3); \ +} while (0) +#define PCKOD_H2_UH(...) PCKOD_H2(v8u16, __VA_ARGS__) +#define PCKOD_H2_SH(...) PCKOD_H2(v8i16, __VA_ARGS__) +#define PCKOD_H2_SW(...) PCKOD_H2(v4i32, __VA_ARGS__) +#define PCKOD_H2_UW(...) PCKOD_H2(v4u32, __VA_ARGS__) + +/* Description : Arithmetic immediate shift right all elements of word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRAI_W2(RTYPE, in0, in1, shift_val) do { \ + in0 = (RTYPE)SRAI_W(in0, shift_val); \ + in1 = (RTYPE)SRAI_W(in1, shift_val); \ +} while (0) +#define SRAI_W2_SW(...) SRAI_W2(v4i32, __VA_ARGS__) +#define SRAI_W2_UW(...) SRAI_W2(v4u32, __VA_ARGS__) + +#define SRAI_W4(RTYPE, in0, in1, in2, in3, shift_val) do { \ + SRAI_W2(RTYPE, in0, in1, shift_val); \ + SRAI_W2(RTYPE, in2, in3, shift_val); \ +} while (0) +#define SRAI_W4_SW(...) SRAI_W4(v4i32, __VA_ARGS__) +#define SRAI_W4_UW(...) SRAI_W4(v4u32, __VA_ARGS__) + +/* Description : Arithmetic shift right all elements of half-word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRAI_H2(RTYPE, in0, in1, shift_val) do { \ + in0 = (RTYPE)SRAI_H(in0, shift_val); \ + in1 = (RTYPE)SRAI_H(in1, shift_val); \ +} while (0) +#define SRAI_H2_SH(...) SRAI_H2(v8i16, __VA_ARGS__) +#define SRAI_H2_UH(...) SRAI_H2(v8u16, __VA_ARGS__) + +/* Description : Arithmetic rounded shift right all elements of word vector + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per input vector RTYPE + * Details : Each element of vector 'in0' is right shifted by 'shift' and + * the result is written in-place. 'shift' is a GP variable. + */ +#define SRARI_W2(RTYPE, in0, in1, shift) do { \ + in0 = (RTYPE)__msa_srari_w((v4i32)in0, shift); \ + in1 = (RTYPE)__msa_srari_w((v4i32)in1, shift); \ +} while (0) +#define SRARI_W2_SW(...) SRARI_W2(v4i32, __VA_ARGS__) + +#define SRARI_W4(RTYPE, in0, in1, in2, in3, shift) do { \ + SRARI_W2(RTYPE, in0, in1, shift); \ + SRARI_W2(RTYPE, in2, in3, shift); \ +} while (0) +#define SRARI_W4_SH(...) SRARI_W4(v8i16, __VA_ARGS__) +#define SRARI_W4_UW(...) SRARI_W4(v4u32, __VA_ARGS__) +#define SRARI_W4_SW(...) SRARI_W4(v4i32, __VA_ARGS__) + +/* Description : Shift right arithmetic rounded double words + * Arguments : Inputs - in0, in1, shift + * Outputs - in place operation + * Return Type - as per RTYPE + * Details : Each element of vector 'in0' is shifted right arithmetically by + * the number of bits in the corresponding element in the vector + * 'shift'. The last discarded bit is added to shifted value for + * rounding and the result is written in-place. + * 'shift' is a vector. + */ +#define SRAR_D2(RTYPE, in0, in1, shift) do { \ + in0 = (RTYPE)__msa_srar_d((v2i64)in0, (v2i64)shift); \ + in1 = (RTYPE)__msa_srar_d((v2i64)in1, (v2i64)shift); \ +} while (0) +#define SRAR_D2_SW(...) SRAR_D2(v4i32, __VA_ARGS__) +#define SRAR_D2_SD(...) SRAR_D2(v2i64, __VA_ARGS__) +#define SRAR_D2_UD(...) SRAR_D2(v2u64, __VA_ARGS__) + +#define SRAR_D4(RTYPE, in0, in1, in2, in3, shift) do { \ + SRAR_D2(RTYPE, in0, in1, shift); \ + SRAR_D2(RTYPE, in2, in3, shift); \ +} while (0) +#define SRAR_D4_SD(...) SRAR_D4(v2i64, __VA_ARGS__) +#define SRAR_D4_UD(...) SRAR_D4(v2u64, __VA_ARGS__) + +/* Description : Addition of 2 pairs of half-word vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADDVI_H2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)ADDVI_H(in0, in1); \ + out1 = (RTYPE)ADDVI_H(in2, in3); \ +} while (0) +#define ADDVI_H2_SH(...) ADDVI_H2(v8i16, __VA_ARGS__) +#define ADDVI_H2_UH(...) ADDVI_H2(v8u16, __VA_ARGS__) + +/* Description : Addition of 2 pairs of word vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADDVI_W2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)ADDVI_W(in0, in1); \ + out1 = (RTYPE)ADDVI_W(in2, in3); \ +} while (0) +#define ADDVI_W2_SW(...) ADDVI_W2(v4i32, __VA_ARGS__) + +/* Description : Fill 2 pairs of word vectors with GP registers + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Details : GP register in0 is replicated in each word element of out0 + * GP register in1 is replicated in each word element of out1 + */ +#define FILL_W2(RTYPE, in0, in1, out0, out1) do { \ + out0 = (RTYPE)__msa_fill_w(in0); \ + out1 = (RTYPE)__msa_fill_w(in1); \ +} while (0) +#define FILL_W2_SW(...) FILL_W2(v4i32, __VA_ARGS__) + +/* Description : Addition of 2 pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in0' is added to 'in1' and result is written + * to 'out0'. + */ +#define ADD2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 + in1; \ + out1 = in2 + in3; \ +} while (0) + +#define ADD4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + ADD2(in0, in1, in2, in3, out0, out1); \ + ADD2(in4, in5, in6, in7, out2, out3); \ +} while (0) + +/* Description : Subtraction of 2 pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element in 'in1' is subtracted from 'in0' and result is + * written to 'out0'. + */ +#define SUB2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ +} while (0) + +#define SUB3(in0, in1, in2, in3, in4, in5, out0, out1, out2) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ + out2 = in4 - in5; \ +} while (0) + +#define SUB4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + out0 = in0 - in1; \ + out1 = in2 - in3; \ + out2 = in4 - in5; \ + out3 = in6 - in7; \ +} while (0) + +/* Description : Addition - Subtraction of input vectors + * Arguments : Inputs - in0, in1 + * Outputs - out0, out1 + * Details : Each element in 'in1' is added to 'in0' and result is + * written to 'out0'. + * Each element in 'in1' is subtracted from 'in0' and result is + * written to 'out1'. + */ +#define ADDSUB2(in0, in1, out0, out1) do { \ + out0 = in0 + in1; \ + out1 = in0 - in1; \ +} while (0) + +/* Description : Multiplication of pairs of vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1 + * Details : Each element from 'in0' is multiplied with elements from 'in1' + * and the result is written to 'out0' + */ +#define MUL2(in0, in1, in2, in3, out0, out1) do { \ + out0 = in0 * in1; \ + out1 = in2 * in3; \ +} while (0) + +#define MUL4(in0, in1, in2, in3, in4, in5, in6, in7, \ + out0, out1, out2, out3) do { \ + MUL2(in0, in1, in2, in3, out0, out1); \ + MUL2(in4, in5, in6, in7, out2, out3); \ +} while (0) + +/* Description : Sign extend halfword elements from right half of the vector + * Arguments : Input - in (halfword vector) + * Output - out (sign extended word vector) + * Return Type - signed word + * Details : Sign bit of halfword elements from input vector 'in' is + * extracted and interleaved with same vector 'in0' to generate + * 4 word elements keeping sign intact + */ +#define UNPCK_R_SH_SW(in, out) do { \ + const v8i16 sign_m = __msa_clti_s_h((v8i16)in, 0); \ + out = (v4i32)__msa_ilvr_h(sign_m, (v8i16)in); \ +} while (0) + +/* Description : Sign extend halfword elements from input vector and return + * the result in pair of vectors + * Arguments : Input - in (halfword vector) + * Outputs - out0, out1 (sign extended word vectors) + * Return Type - signed word + * Details : Sign bit of halfword elements from input vector 'in' is + * extracted and interleaved right with same vector 'in0' to + * generate 4 signed word elements in 'out0' + * Then interleaved left with same vector 'in0' to + * generate 4 signed word elements in 'out1' + */ +#define UNPCK_SH_SW(in, out0, out1) do { \ + const v8i16 tmp_m = __msa_clti_s_h((v8i16)in, 0); \ + ILVRL_H2_SW(tmp_m, in, out0, out1); \ +} while (0) + +/* Description : Butterfly of 4 input vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1, out2, out3 + * Details : Butterfly operation + */ +#define BUTTERFLY_4(in0, in1, in2, in3, out0, out1, out2, out3) do { \ + out0 = in0 + in3; \ + out1 = in1 + in2; \ + out2 = in1 - in2; \ + out3 = in0 - in3; \ +} while (0) + +/* Description : Transpose 16x4 block into 4x16 with byte elements in vectors + * Arguments : Inputs - in0, in1, in2, in3, in4, in5, in6, in7, + * in8, in9, in10, in11, in12, in13, in14, in15 + * Outputs - out0, out1, out2, out3 + * Return Type - unsigned byte + */ +#define TRANSPOSE16x4_UB_UB(in0, in1, in2, in3, in4, in5, in6, in7, \ + in8, in9, in10, in11, in12, in13, in14, in15, \ + out0, out1, out2, out3) do { \ + v2i64 tmp0_m, tmp1_m, tmp2_m, tmp3_m, tmp4_m, tmp5_m; \ + ILVEV_W2_SD(in0, in4, in8, in12, tmp2_m, tmp3_m); \ + ILVEV_W2_SD(in1, in5, in9, in13, tmp0_m, tmp1_m); \ + ILVEV_D2_UB(tmp2_m, tmp3_m, tmp0_m, tmp1_m, out1, out3); \ + ILVEV_W2_SD(in2, in6, in10, in14, tmp4_m, tmp5_m); \ + ILVEV_W2_SD(in3, in7, in11, in15, tmp0_m, tmp1_m); \ + ILVEV_D2_SD(tmp4_m, tmp5_m, tmp0_m, tmp1_m, tmp2_m, tmp3_m); \ + ILVEV_B2_SD(out1, out3, tmp2_m, tmp3_m, tmp0_m, tmp1_m); \ + ILVEVOD_H2_UB(tmp0_m, tmp1_m, tmp0_m, tmp1_m, out0, out2); \ + ILVOD_B2_SD(out1, out3, tmp2_m, tmp3_m, tmp0_m, tmp1_m); \ + ILVEVOD_H2_UB(tmp0_m, tmp1_m, tmp0_m, tmp1_m, out1, out3); \ +} while (0) + +/* Description : Transpose 16x8 block into 8x16 with byte elements in vectors + * Arguments : Inputs - in0, in1, in2, in3, in4, in5, in6, in7, + * in8, in9, in10, in11, in12, in13, in14, in15 + * Outputs - out0, out1, out2, out3, out4, out5, out6, out7 + * Return Type - unsigned byte + */ +#define TRANSPOSE16x8_UB_UB(in0, in1, in2, in3, in4, in5, in6, in7, \ + in8, in9, in10, in11, in12, in13, in14, in15, \ + out0, out1, out2, out3, out4, out5, \ + out6, out7) do { \ + v8i16 tmp0_m, tmp1_m, tmp4_m, tmp5_m, tmp6_m, tmp7_m; \ + v4i32 tmp2_m, tmp3_m; \ + ILVEV_D2_UB(in0, in8, in1, in9, out7, out6); \ + ILVEV_D2_UB(in2, in10, in3, in11, out5, out4); \ + ILVEV_D2_UB(in4, in12, in5, in13, out3, out2); \ + ILVEV_D2_UB(in6, in14, in7, in15, out1, out0); \ + ILVEV_B2_SH(out7, out6, out5, out4, tmp0_m, tmp1_m); \ + ILVOD_B2_SH(out7, out6, out5, out4, tmp4_m, tmp5_m); \ + ILVEV_B2_UB(out3, out2, out1, out0, out5, out7); \ + ILVOD_B2_SH(out3, out2, out1, out0, tmp6_m, tmp7_m); \ + ILVEV_H2_SW(tmp0_m, tmp1_m, out5, out7, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out0, out4); \ + ILVOD_H2_SW(tmp0_m, tmp1_m, out5, out7, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out2, out6); \ + ILVEV_H2_SW(tmp4_m, tmp5_m, tmp6_m, tmp7_m, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out1, out5); \ + ILVOD_H2_SW(tmp4_m, tmp5_m, tmp6_m, tmp7_m, tmp2_m, tmp3_m); \ + ILVEVOD_W2_UB(tmp2_m, tmp3_m, tmp2_m, tmp3_m, out3, out7); \ +} while (0) + +/* Description : Transpose 4x4 block with word elements in vectors + * Arguments : Inputs - in0, in1, in2, in3 + * Outputs - out0, out1, out2, out3 + * Return Type - as per RTYPE + */ +#define TRANSPOSE4x4_W(RTYPE, in0, in1, in2, in3, \ + out0, out1, out2, out3) do { \ + v4i32 s0_m, s1_m, s2_m, s3_m; \ + ILVRL_W2_SW(in1, in0, s0_m, s1_m); \ + ILVRL_W2_SW(in3, in2, s2_m, s3_m); \ + out0 = (RTYPE)__msa_ilvr_d((v2i64)s2_m, (v2i64)s0_m); \ + out1 = (RTYPE)__msa_ilvl_d((v2i64)s2_m, (v2i64)s0_m); \ + out2 = (RTYPE)__msa_ilvr_d((v2i64)s3_m, (v2i64)s1_m); \ + out3 = (RTYPE)__msa_ilvl_d((v2i64)s3_m, (v2i64)s1_m); \ +} while (0) +#define TRANSPOSE4x4_SW_SW(...) TRANSPOSE4x4_W(v4i32, __VA_ARGS__) + +/* Description : Add block 4x4 + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + * Details : Least significant 4 bytes from each input vector are added to + * the destination bytes, clipped between 0-255 and stored. + */ +#define ADDBLK_ST4x4_UB(in0, in1, in2, in3, pdst, stride) do { \ + uint32_t src0_m, src1_m, src2_m, src3_m; \ + v8i16 inp0_m, inp1_m, res0_m, res1_m; \ + v16i8 dst0_m = { 0 }; \ + v16i8 dst1_m = { 0 }; \ + const v16i8 zero_m = { 0 }; \ + ILVR_D2_SH(in1, in0, in3, in2, inp0_m, inp1_m); \ + LW4(pdst, stride, src0_m, src1_m, src2_m, src3_m); \ + INSERT_W2_SB(src0_m, src1_m, dst0_m); \ + INSERT_W2_SB(src2_m, src3_m, dst1_m); \ + ILVR_B2_SH(zero_m, dst0_m, zero_m, dst1_m, res0_m, res1_m); \ + ADD2(res0_m, inp0_m, res1_m, inp1_m, res0_m, res1_m); \ + CLIP_SH2_0_255(res0_m, res1_m); \ + PCKEV_B2_SB(res0_m, res0_m, res1_m, res1_m, dst0_m, dst1_m); \ + ST4x4_UB(dst0_m, dst1_m, 0, 1, 0, 1, pdst, stride); \ +} while (0) + +/* Description : Pack even byte elements, extract 0 & 2 index words from pair + * of results and store 4 words in destination memory as per + * stride + * Arguments : Inputs - in0, in1, in2, in3, pdst, stride + */ +#define PCKEV_ST4x4_UB(in0, in1, in2, in3, pdst, stride) do { \ + v16i8 tmp0_m, tmp1_m; \ + PCKEV_B2_SB(in1, in0, in3, in2, tmp0_m, tmp1_m); \ + ST4x4_UB(tmp0_m, tmp1_m, 0, 2, 0, 2, pdst, stride); \ +} while (0) + +/* Description : average with rounding (in0 + in1 + 1) / 2. + * Arguments : Inputs - in0, in1, in2, in3, + * Outputs - out0, out1 + * Return Type - as per RTYPE + * Details : Each unsigned byte element from 'in0' vector is added with + * each unsigned byte element from 'in1' vector. Then the average + * with rounding is calculated and written to 'out0' + */ +#define AVER_UB2(RTYPE, in0, in1, in2, in3, out0, out1) do { \ + out0 = (RTYPE)__msa_aver_u_b((v16u8)in0, (v16u8)in1); \ + out1 = (RTYPE)__msa_aver_u_b((v16u8)in2, (v16u8)in3); \ +} while (0) +#define AVER_UB2_UB(...) AVER_UB2(v16u8, __VA_ARGS__) + +#endif // WEBP_USE_MSA +#endif // WEBP_DSP_MSA_MACRO_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/neon.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/neon.h new file mode 100644 index 0000000000..14acb4044b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/neon.h @@ -0,0 +1,104 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON common code. + +#ifndef WEBP_DSP_NEON_H_ +#define WEBP_DSP_NEON_H_ + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +// Right now, some intrinsics functions seem slower, so we disable them +// everywhere except newer clang/gcc or aarch64 where the inline assembly is +// incompatible. +#if LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 9) || WEBP_AARCH64 +#define WEBP_USE_INTRINSICS // use intrinsics when possible +#endif + +#define INIT_VECTOR2(v, a, b) do { \ + v.val[0] = a; \ + v.val[1] = b; \ +} while (0) + +#define INIT_VECTOR3(v, a, b, c) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ +} while (0) + +#define INIT_VECTOR4(v, a, b, c, d) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ + v.val[3] = d; \ +} while (0) + +// if using intrinsics, this flag avoids some functions that make gcc-4.6.3 +// crash ("internal compiler error: in immed_double_const, at emit-rtl."). +// (probably similar to gcc.gnu.org/bugzilla/show_bug.cgi?id=48183) +#if !(LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 8) || WEBP_AARCH64) +#define WORK_AROUND_GCC +#endif + +static WEBP_INLINE int32x4x4_t Transpose4x4_NEON(const int32x4x4_t rows) { + uint64x2x2_t row01, row23; + + row01.val[0] = vreinterpretq_u64_s32(rows.val[0]); + row01.val[1] = vreinterpretq_u64_s32(rows.val[1]); + row23.val[0] = vreinterpretq_u64_s32(rows.val[2]); + row23.val[1] = vreinterpretq_u64_s32(rows.val[3]); + // Transpose 64-bit values (there's no vswp equivalent) + { + const uint64x1_t row0h = vget_high_u64(row01.val[0]); + const uint64x1_t row2l = vget_low_u64(row23.val[0]); + const uint64x1_t row1h = vget_high_u64(row01.val[1]); + const uint64x1_t row3l = vget_low_u64(row23.val[1]); + row01.val[0] = vcombine_u64(vget_low_u64(row01.val[0]), row2l); + row23.val[0] = vcombine_u64(row0h, vget_high_u64(row23.val[0])); + row01.val[1] = vcombine_u64(vget_low_u64(row01.val[1]), row3l); + row23.val[1] = vcombine_u64(row1h, vget_high_u64(row23.val[1])); + } + { + const int32x4x2_t out01 = vtrnq_s32(vreinterpretq_s32_u64(row01.val[0]), + vreinterpretq_s32_u64(row01.val[1])); + const int32x4x2_t out23 = vtrnq_s32(vreinterpretq_s32_u64(row23.val[0]), + vreinterpretq_s32_u64(row23.val[1])); + int32x4x4_t out; + out.val[0] = out01.val[0]; + out.val[1] = out01.val[1]; + out.val[2] = out23.val[0]; + out.val[3] = out23.val[1]; + return out; + } +} + +#if 0 // Useful debug macro. +#include +#define PRINT_REG(REG, SIZE) do { \ + int i; \ + printf("%s \t[%d]: 0x", #REG, SIZE); \ + if (SIZE == 8) { \ + uint8_t _tmp[8]; \ + vst1_u8(_tmp, (REG)); \ + for (i = 0; i < 8; ++i) printf("%.2x ", _tmp[i]); \ + } else if (SIZE == 16) { \ + uint16_t _tmp[4]; \ + vst1_u16(_tmp, (REG)); \ + for (i = 0; i < 4; ++i) printf("%.4x ", _tmp[i]); \ + } \ + printf("\n"); \ +} while (0) +#endif + +#endif // WEBP_USE_NEON +#endif // WEBP_DSP_NEON_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/quant.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/quant.h new file mode 100644 index 0000000000..dcbc11c77c --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/quant.h @@ -0,0 +1,91 @@ +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- + +#ifndef WEBP_DSP_QUANT_H_ +#define WEBP_DSP_QUANT_H_ + +#include + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#if defined(WEBP_USE_NEON) && !defined(WEBP_ANDROID_NEON) && \ + !defined(WEBP_HAVE_NEON_RTCD) +#include + +#define IsFlat IsFlat_NEON + +static uint32_t horizontal_add_uint32x4(const uint32x4_t a) { +#if WEBP_AARCH64 + return vaddvq_u32(a); +#else + const uint64x2_t b = vpaddlq_u32(a); + const uint32x2_t c = vadd_u32(vreinterpret_u32_u64(vget_low_u64(b)), + vreinterpret_u32_u64(vget_high_u64(b))); + return vget_lane_u32(c, 0); +#endif +} + +static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks, + int thresh) { + const int16x8_t tst_ones = vdupq_n_s16(-1); + uint32x4_t sum = vdupq_n_u32(0); + int i; + + for (i = 0; i < num_blocks; ++i) { + // Set DC to zero. + const int16x8_t a_0 = vsetq_lane_s16(0, vld1q_s16(levels), 0); + const int16x8_t a_1 = vld1q_s16(levels + 8); + + const uint16x8_t b_0 = vshrq_n_u16(vtstq_s16(a_0, tst_ones), 15); + const uint16x8_t b_1 = vshrq_n_u16(vtstq_s16(a_1, tst_ones), 15); + + sum = vpadalq_u16(sum, b_0); + sum = vpadalq_u16(sum, b_1); + + levels += 16; + } + return thresh >= (int)horizontal_add_uint32x4(sum); +} + +#else + +#define IsFlat IsFlat_C + +static WEBP_INLINE int IsFlat(const int16_t* levels, int num_blocks, + int thresh) { + int score = 0; + while (num_blocks-- > 0) { // TODO(skal): refine positional scoring? + int i; + for (i = 1; i < 16; ++i) { // omit DC, we're only interested in AC + score += (levels[i] != 0); + if (score > thresh) return 0; + } + levels += 16; + } + return 1; +} + +#endif // defined(WEBP_USE_NEON) && !defined(WEBP_ANDROID_NEON) && + // !defined(WEBP_HAVE_NEON_RTCD) + +static WEBP_INLINE int IsFlatSource16(const uint8_t* src) { + const uint32_t v = src[0] * 0x01010101u; + int i; + for (i = 0; i < 16; ++i) { + if (memcmp(src + 0, &v, 4) || memcmp(src + 4, &v, 4) || + memcmp(src + 8, &v, 4) || memcmp(src + 12, &v, 4)) { + return 0; + } + src += BPS; + } + return 1; +} + +#endif // WEBP_DSP_QUANT_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler.c new file mode 100644 index 0000000000..325d8be180 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler.c @@ -0,0 +1,252 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dsp/dsp.h" +#include "src/utils/rescaler_utils.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +//------------------------------------------------------------------------------ +// Row import + +void WebPRescalerImportRowExpand_C(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + // simple bilinear interpolation + int accum = wrk->x_add; + rescaler_t left = (rescaler_t)src[x_in]; + rescaler_t right = + (wrk->src_width > 1) ? (rescaler_t)src[x_in + x_stride] : left; + x_in += x_stride; + while (1) { + wrk->frow[x_out] = right * wrk->x_add + (left - right) * accum; + x_out += x_stride; + if (x_out >= x_out_max) break; + accum -= wrk->x_sub; + if (accum < 0) { + left = right; + x_in += x_stride; + assert(x_in < wrk->src_width * x_stride); + right = (rescaler_t)src[x_in]; + accum += wrk->x_add; + } + } + assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0); + } +} + +void WebPRescalerImportRowShrink_C(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + uint32_t sum = 0; + int accum = 0; + while (x_out < x_out_max) { + uint32_t base = 0; + accum += wrk->x_add; + while (accum > 0) { + accum -= wrk->x_sub; + assert(x_in < wrk->src_width * x_stride); + base = src[x_in]; + sum += base; + x_in += x_stride; + } + { // Emit next horizontal pixel. + const rescaler_t frac = base * (-accum); + wrk->frow[x_out] = sum * wrk->x_sub - frac; + // fresh fractional start for next pixel + sum = (int)MULT_FIX(frac, wrk->fx_scale); + } + x_out += x_stride; + } + assert(accum == 0); + } +} + +//------------------------------------------------------------------------------ +// Row export + +void WebPRescalerExportRowExpand_C(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +void WebPRescalerExportRowShrink_C(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ +// Main entry calls + +void WebPRescalerImportRow(WebPRescaler* const wrk, const uint8_t* src) { + assert(!WebPRescalerInputDone(wrk)); + if (!wrk->x_expand) { + WebPRescalerImportRowShrink(wrk, src); + } else { + WebPRescalerImportRowExpand(wrk, src); + } +} + +void WebPRescalerExportRow(WebPRescaler* const wrk) { + if (wrk->y_accum <= 0) { + assert(!WebPRescalerOutputDone(wrk)); + if (wrk->y_expand) { + WebPRescalerExportRowExpand(wrk); + } else if (wrk->fxy_scale) { + WebPRescalerExportRowShrink(wrk); + } else { // special case + int i; + assert(wrk->src_height == wrk->dst_height && wrk->x_add == 1); + assert(wrk->src_width == 1 && wrk->dst_width <= 2); + for (i = 0; i < wrk->num_channels * wrk->dst_width; ++i) { + wrk->dst[i] = wrk->irow[i]; + wrk->irow[i] = 0; + } + } + wrk->y_accum += wrk->y_add; + wrk->dst += wrk->dst_stride; + ++wrk->dst_y; + } +} + +//------------------------------------------------------------------------------ + +WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPRescalerDspInitSSE2(void); +extern void WebPRescalerDspInitMIPS32(void); +extern void WebPRescalerDspInitMIPSdspR2(void); +extern void WebPRescalerDspInitMSA(void); +extern void WebPRescalerDspInitNEON(void); + +WEBP_DSP_INIT_FUNC(WebPRescalerDspInit) { +#if !defined(WEBP_REDUCE_SIZE) +#if !WEBP_NEON_OMIT_C_CODE + WebPRescalerExportRowExpand = WebPRescalerExportRowExpand_C; + WebPRescalerExportRowShrink = WebPRescalerExportRowShrink_C; +#endif + + WebPRescalerImportRowExpand = WebPRescalerImportRowExpand_C; + WebPRescalerImportRowShrink = WebPRescalerImportRowShrink_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPRescalerDspInitSSE2(); + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPRescalerDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPRescalerDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPRescalerDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPRescalerDspInitNEON(); + } +#endif + + assert(WebPRescalerExportRowExpand != NULL); + assert(WebPRescalerExportRowShrink != NULL); + assert(WebPRescalerImportRowExpand != NULL); + assert(WebPRescalerImportRowShrink != NULL); +#endif // WEBP_REDUCE_SIZE +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips32.c new file mode 100644 index 0000000000..61f63c616c --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips32.c @@ -0,0 +1,295 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of rescaling functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS32) && !defined(WEBP_REDUCE_SIZE) + +#include +#include "src/utils/rescaler_utils.h" + +//------------------------------------------------------------------------------ +// Row import + +static void ImportRowShrink_MIPS32(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int fx_scale = wrk->fx_scale; + const int x_add = wrk->x_add; + const int x_sub = wrk->x_sub; + const int x_stride1 = x_stride << 2; + int channel; + assert(!wrk->x_expand); + assert(!WebPRescalerInputDone(wrk)); + + for (channel = 0; channel < x_stride; ++channel) { + const uint8_t* src1 = src + channel; + rescaler_t* frow = wrk->frow + channel; + int temp1, temp2, temp3; + int base, frac, sum; + int accum, accum1; + int loop_c = x_out_max - channel; + + __asm__ volatile ( + "li %[temp1], 0x8000 \n\t" + "li %[temp2], 0x10000 \n\t" + "li %[sum], 0 \n\t" + "li %[accum], 0 \n\t" + "1: \n\t" + "addu %[accum], %[accum], %[x_add] \n\t" + "li %[base], 0 \n\t" + "blez %[accum], 3f \n\t" + "2: \n\t" + "lbu %[base], 0(%[src1]) \n\t" + "subu %[accum], %[accum], %[x_sub] \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "addu %[sum], %[sum], %[base] \n\t" + "bgtz %[accum], 2b \n\t" + "3: \n\t" + "negu %[accum1], %[accum] \n\t" + "mul %[frac], %[base], %[accum1] \n\t" + "mul %[temp3], %[sum], %[x_sub] \n\t" + "subu %[loop_c], %[loop_c], %[x_stride] \n\t" + "mult %[temp1], %[temp2] \n\t" + "maddu %[frac], %[fx_scale] \n\t" + "mfhi %[sum] \n\t" + "subu %[temp3], %[temp3], %[frac] \n\t" + "sw %[temp3], 0(%[frow]) \n\t" + "addu %[frow], %[frow], %[x_stride1] \n\t" + "bgtz %[loop_c], 1b \n\t" + : [accum]"=&r"(accum), [src1]"+r"(src1), [temp3]"=&r"(temp3), + [sum]"=&r"(sum), [base]"=&r"(base), [frac]"=&r"(frac), + [frow]"+r"(frow), [accum1]"=&r"(accum1), + [temp2]"=&r"(temp2), [temp1]"=&r"(temp1) + : [x_stride]"r"(x_stride), [fx_scale]"r"(fx_scale), + [x_sub]"r"(x_sub), [x_add]"r"(x_add), + [loop_c]"r"(loop_c), [x_stride1]"r"(x_stride1) + : "memory", "hi", "lo" + ); + assert(accum == 0); + } +} + +static void ImportRowExpand_MIPS32(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int x_add = wrk->x_add; + const int x_sub = wrk->x_sub; + const int src_width = wrk->src_width; + const int x_stride1 = x_stride << 2; + int channel; + assert(wrk->x_expand); + assert(!WebPRescalerInputDone(wrk)); + + for (channel = 0; channel < x_stride; ++channel) { + const uint8_t* src1 = src + channel; + rescaler_t* frow = wrk->frow + channel; + int temp1, temp2, temp3, temp4; + int frac; + int accum; + int x_out = channel; + + __asm__ volatile ( + "addiu %[temp3], %[src_width], -1 \n\t" + "lbu %[temp2], 0(%[src1]) \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "bgtz %[temp3], 0f \n\t" + "addiu %[temp1], %[temp2], 0 \n\t" + "b 3f \n\t" + "0: \n\t" + "lbu %[temp1], 0(%[src1]) \n\t" + "3: \n\t" + "addiu %[accum], %[x_add], 0 \n\t" + "1: \n\t" + "subu %[temp3], %[temp2], %[temp1] \n\t" + "mul %[temp3], %[temp3], %[accum] \n\t" + "mul %[temp4], %[temp1], %[x_add] \n\t" + "addu %[temp3], %[temp4], %[temp3] \n\t" + "sw %[temp3], 0(%[frow]) \n\t" + "addu %[frow], %[frow], %[x_stride1] \n\t" + "addu %[x_out], %[x_out], %[x_stride] \n\t" + "subu %[temp3], %[x_out], %[x_out_max] \n\t" + "bgez %[temp3], 2f \n\t" + "subu %[accum], %[accum], %[x_sub] \n\t" + "bgez %[accum], 4f \n\t" + "addiu %[temp2], %[temp1], 0 \n\t" + "addu %[src1], %[src1], %[x_stride] \n\t" + "lbu %[temp1], 0(%[src1]) \n\t" + "addu %[accum], %[accum], %[x_add] \n\t" + "4: \n\t" + "b 1b \n\t" + "2: \n\t" + : [src1]"+r"(src1), [accum]"=&r"(accum), [temp1]"=&r"(temp1), + [temp2]"=&r"(temp2), [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), + [x_out]"+r"(x_out), [frac]"=&r"(frac), [frow]"+r"(frow) + : [x_stride]"r"(x_stride), [x_add]"r"(x_add), [x_sub]"r"(x_sub), + [x_stride1]"r"(x_stride1), [src_width]"r"(src_width), + [x_out_max]"r"(x_out_max) + : "memory", "hi", "lo" + ); + assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0); + } +} + +//------------------------------------------------------------------------------ +// Row export + +static void ExportRowExpand_MIPS32(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + int temp0, temp1, temp3, temp4, temp5, loop_end; + const int temp2 = (int)wrk->fy_scale; + const int temp6 = x_out_max << 2; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[A], %[temp0] \n\t" + "maddu %[B], %[temp1] \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "mfhi %[temp5] \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp5], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6), [A]"r"(A), [B]"r"(B) + : "memory", "hi", "lo" + ); + } +} + +#if 0 // disabled for now. TODO(skal): make match the C-code +static void ExportRowShrink_MIPS32(WebPRescaler* const wrk) { + const int x_out_max = wrk->dst_width * wrk->num_channels; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const rescaler_t* frow = wrk->frow; + const int yscale = wrk->fy_scale * (-wrk->y_accum); + int temp0, temp1, temp3, temp4, temp5, loop_end; + const int temp2 = (int)wrk->fxy_scale; + const int temp6 = x_out_max << 2; + + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + assert(wrk->fxy_scale != 0); + if (yscale) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "mult %[temp3], %[temp4] \n\t" + "addiu %[frow], %[frow], 4 \n\t" + "maddu %[temp0], %[yscale] \n\t" + "mfhi %[temp1] \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "subu %[temp0], %[temp0], %[temp1] \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sw %[temp1], -4(%[irow]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [yscale]"r"(yscale), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } else { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[irow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "addiu %[dst], %[dst], 1 \n\t" + "addiu %[irow], %[irow], 4 \n\t" + "mult %[temp3], %[temp4] \n\t" + "maddu %[temp0], %[temp2] \n\t" + "mfhi %[temp5] \n\t" + "sw $zero, -4(%[irow]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[irow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [irow]"+r"(irow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end) + : [temp2]"r"(temp2), [temp6]"r"(temp6) + : "memory", "hi", "lo" + ); + } +} +#endif // 0 + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMIPS32(void) { + WebPRescalerImportRowExpand = ImportRowExpand_MIPS32; + WebPRescalerImportRowShrink = ImportRowShrink_MIPS32; + WebPRescalerExportRowExpand = ExportRowExpand_MIPS32; +// WebPRescalerExportRowShrink = ExportRowShrink_MIPS32; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips_dsp_r2.c new file mode 100644 index 0000000000..419b741fa5 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_mips_dsp_r2.c @@ -0,0 +1,314 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of rescaling functions +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) && !defined(WEBP_REDUCE_SIZE) + +#include +#include "src/utils/rescaler_utils.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +//------------------------------------------------------------------------------ +// Row export + +#if 0 // disabled for now. TODO(skal): make match the C-code +static void ExportRowShrink_MIPSdspR2(WebPRescaler* const wrk) { + int i; + const int x_out_max = wrk->dst_width * wrk->num_channels; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const rescaler_t* frow = wrk->frow; + const int yscale = wrk->fy_scale * (-wrk->y_accum); + int temp0, temp1, temp2, temp3, temp4, temp5, loop_end; + const int temp7 = (int)wrk->fxy_scale; + const int temp6 = (x_out_max & ~0x3) << 2; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + assert(wrk->fxy_scale != 0); + if (yscale) { + if (x_out_max >= 4) { + int temp8, temp9, temp10, temp11; + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp5], 12(%[frow]) \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp0], %[yscale] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp1], %[yscale] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp2], %[yscale] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp5], %[yscale] \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp5], $ac3 \n\t" + "lw %[temp8], 0(%[irow]) \n\t" + "lw %[temp9], 4(%[irow]) \n\t" + "lw %[temp10], 8(%[irow]) \n\t" + "lw %[temp11], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "subu %[temp8], %[temp8], %[temp0] \n\t" + "subu %[temp9], %[temp9], %[temp1] \n\t" + "subu %[temp10], %[temp10], %[temp2] \n\t" + "subu %[temp11], %[temp11], %[temp5] \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp8], %[temp7] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp9], %[temp7] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp10], %[temp7] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp11], %[temp7] \n\t" + "mfhi %[temp8], $ac0 \n\t" + "mfhi %[temp9], $ac1 \n\t" + "mfhi %[temp10], $ac2 \n\t" + "mfhi %[temp11], $ac3 \n\t" + "sw %[temp0], -16(%[irow]) \n\t" + "sw %[temp1], -12(%[irow]) \n\t" + "sw %[temp2], -8(%[irow]) \n\t" + "sw %[temp5], -4(%[irow]) \n\t" + "sb %[temp8], -4(%[dst]) \n\t" + "sb %[temp9], -3(%[dst]) \n\t" + "sb %[temp10], -2(%[dst]) \n\t" + "sb %[temp11], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end), + [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), + [temp11]"=&r"(temp11), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [yscale]"r"(yscale), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR(*frow++, yscale); + const int v = (int)MULT_FIX(*irow - frac, wrk->fxy_scale); + *dst++ = (v > 255) ? 255u : (uint8_t)v; + *irow++ = frac; // new fractional start + } + } else { + if (x_out_max >= 4) { + __asm__ volatile ( + "li %[temp3], 0x10000 \n\t" + "li %[temp4], 0x8000 \n\t" + "addu %[loop_end], %[irow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[irow]) \n\t" + "lw %[temp1], 4(%[irow]) \n\t" + "lw %[temp2], 8(%[irow]) \n\t" + "lw %[temp5], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "mult $ac0, %[temp3], %[temp4] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp3], %[temp4] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp3], %[temp4] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp3], %[temp4] \n\t" + "maddu $ac3, %[temp5], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp5], $ac3 \n\t" + "sw $zero, -16(%[irow]) \n\t" + "sw $zero, -12(%[irow]) \n\t" + "sw $zero, -8(%[irow]) \n\t" + "sw $zero, -4(%[irow]) \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp5], -1(%[dst]) \n\t" + "bne %[irow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [irow]"+r"(irow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const int v = (int)MULT_FIX_FLOOR(*irow, wrk->fxy_scale); + *dst++ = (v > 255) ? 255u : (uint8_t)v; + *irow++ = 0; + } + } +} +#endif // 0 + +static void ExportRowExpand_MIPSdspR2(WebPRescaler* const wrk) { + int i; + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + int temp0, temp1, temp2, temp3, temp4, temp5, loop_end; + const int temp6 = (x_out_max & ~0x3) << 2; + const int temp7 = (int)wrk->fy_scale; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + if (x_out_max >= 4) { + __asm__ volatile ( + "li %[temp4], 0x10000 \n\t" + "li %[temp5], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp3], 12(%[frow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "mult $ac0, %[temp4], %[temp5] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp4], %[temp5] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp4], %[temp5] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp4], %[temp5] \n\t" + "maddu $ac3, %[temp3], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp3], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [dst]"+r"(dst), [loop_end]"=&r"(loop_end), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint32_t J = *frow++; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + *dst++ = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + if (x_out_max >= 4) { + int temp8, temp9, temp10, temp11; + __asm__ volatile ( + "li %[temp8], 0x10000 \n\t" + "li %[temp9], 0x8000 \n\t" + "addu %[loop_end], %[frow], %[temp6] \n\t" + "1: \n\t" + "lw %[temp0], 0(%[frow]) \n\t" + "lw %[temp1], 4(%[frow]) \n\t" + "lw %[temp2], 8(%[frow]) \n\t" + "lw %[temp3], 12(%[frow]) \n\t" + "lw %[temp4], 0(%[irow]) \n\t" + "lw %[temp5], 4(%[irow]) \n\t" + "lw %[temp10], 8(%[irow]) \n\t" + "lw %[temp11], 12(%[irow]) \n\t" + "addiu %[dst], %[dst], 4 \n\t" + "mult $ac0, %[temp8], %[temp9] \n\t" + "maddu $ac0, %[A], %[temp0] \n\t" + "maddu $ac0, %[B], %[temp4] \n\t" + "mult $ac1, %[temp8], %[temp9] \n\t" + "maddu $ac1, %[A], %[temp1] \n\t" + "maddu $ac1, %[B], %[temp5] \n\t" + "mult $ac2, %[temp8], %[temp9] \n\t" + "maddu $ac2, %[A], %[temp2] \n\t" + "maddu $ac2, %[B], %[temp10] \n\t" + "mult $ac3, %[temp8], %[temp9] \n\t" + "maddu $ac3, %[A], %[temp3] \n\t" + "maddu $ac3, %[B], %[temp11] \n\t" + "addiu %[frow], %[frow], 16 \n\t" + "addiu %[irow], %[irow], 16 \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "mult $ac0, %[temp8], %[temp9] \n\t" + "maddu $ac0, %[temp0], %[temp7] \n\t" + "mult $ac1, %[temp8], %[temp9] \n\t" + "maddu $ac1, %[temp1], %[temp7] \n\t" + "mult $ac2, %[temp8], %[temp9] \n\t" + "maddu $ac2, %[temp2], %[temp7] \n\t" + "mult $ac3, %[temp8], %[temp9] \n\t" + "maddu $ac3, %[temp3], %[temp7] \n\t" + "mfhi %[temp0], $ac0 \n\t" + "mfhi %[temp1], $ac1 \n\t" + "mfhi %[temp2], $ac2 \n\t" + "mfhi %[temp3], $ac3 \n\t" + "sb %[temp0], -4(%[dst]) \n\t" + "sb %[temp1], -3(%[dst]) \n\t" + "sb %[temp2], -2(%[dst]) \n\t" + "sb %[temp3], -1(%[dst]) \n\t" + "bne %[frow], %[loop_end], 1b \n\t" + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp3]"=&r"(temp3), + [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), [frow]"+r"(frow), + [irow]"+r"(irow), [dst]"+r"(dst), [loop_end]"=&r"(loop_end), + [temp8]"=&r"(temp8), [temp9]"=&r"(temp9), [temp10]"=&r"(temp10), + [temp11]"=&r"(temp11), [temp2]"=&r"(temp2) + : [temp7]"r"(temp7), [temp6]"r"(temp6), [A]"r"(A), [B]"r"(B) + : "memory", "hi", "lo", "$ac1hi", "$ac1lo", + "$ac2hi", "$ac2lo", "$ac3hi", "$ac3lo" + ); + } + for (i = 0; i < (x_out_max & 0x3); ++i) { + const uint64_t I = (uint64_t)A * *frow++ + + (uint64_t)B * *irow++; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + *dst++ = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMIPSdspR2(void) { + WebPRescalerExportRowExpand = ExportRowExpand_MIPSdspR2; +// WebPRescalerExportRowShrink = ExportRowShrink_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_msa.c new file mode 100644 index 0000000000..256dbdd437 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_msa.c @@ -0,0 +1,443 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of rescaling functions +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) && !defined(WEBP_REDUCE_SIZE) + +#include + +#include "src/utils/rescaler_utils.h" +#include "src/dsp/msa_macro.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +#define CALC_MULT_FIX_16(in0, in1, in2, in3, scale, shift, dst) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v16u8 t0, t1, t2, t3, t4, t5; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in1, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, t0, t1); \ + ILVRL_W2_UW(zero, in2, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, t2, t3); \ + PCKEV_B2_UB(t1, t0, t3, t2, t4, t5); \ + dst = (v16u8)__msa_pckev_b((v16i8)t5, (v16i8)t4); \ +} while (0) + +#define CALC_MULT_FIX_4(in0, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v16i8 t0, t1; \ + v2u64 out0, out1; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + t0 = __msa_pckev_b((v16i8)out1, (v16i8)out0); \ + t1 = __msa_pckev_b(t0, t0); \ + t0 = __msa_pckev_b(t1, t1); \ + dst = __msa_copy_s_w((v4i32)t0, 0); \ +} while (0) + +#define CALC_MULT_FIX1_16(in0, in1, in2, in3, fyscale, shift, \ + dst0, dst1, dst2, dst3) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in1, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, fyscale, fyscale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, fyscale, fyscale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_W2_UW(out1, out0, out3, out2, dst0, dst1); \ + ILVRL_W2_UW(zero, in2, tmp0, tmp1); \ + ILVRL_W2_UW(zero, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, fyscale, fyscale, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, fyscale, fyscale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_W2_UW(out1, out0, out3, out2, dst2, dst3); \ +} while (0) + +#define CALC_MULT_FIX1_4(in0, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v2u64 out0, out1; \ + ILVRL_W2_UW(zero, in0, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + dst = (v4u32)__msa_pckev_w((v4i32)out1, (v4i32)out0); \ +} while (0) + +#define CALC_MULT_FIX2_16(in0, in1, in2, in3, mult, scale, shift, \ + dst0, dst1) do { \ + v4u32 tmp0, tmp1, tmp2, tmp3; \ + v2u64 out0, out1, out2, out3; \ + ILVRL_W2_UW(in0, in2, tmp0, tmp1); \ + ILVRL_W2_UW(in1, in3, tmp2, tmp3); \ + DOTP_UW2_UD(tmp0, tmp1, mult, mult, out0, out1); \ + DOTP_UW2_UD(tmp2, tmp3, mult, mult, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + DOTP_UW2_UD(out0, out1, scale, scale, out0, out1); \ + DOTP_UW2_UD(out2, out3, scale, scale, out2, out3); \ + SRAR_D4_UD(out0, out1, out2, out3, shift); \ + PCKEV_B2_UB(out1, out0, out3, out2, dst0, dst1); \ +} while (0) + +#define CALC_MULT_FIX2_4(in0, in1, mult, scale, shift, dst) do { \ + v4u32 tmp0, tmp1; \ + v2u64 out0, out1; \ + v16i8 t0, t1; \ + ILVRL_W2_UW(in0, in1, tmp0, tmp1); \ + DOTP_UW2_UD(tmp0, tmp1, mult, mult, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + DOTP_UW2_UD(out0, out1, scale, scale, out0, out1); \ + SRAR_D2_UD(out0, out1, shift); \ + t0 = __msa_pckev_b((v16i8)out1, (v16i8)out0); \ + t1 = __msa_pckev_b(t0, t0); \ + t0 = __msa_pckev_b(t1, t1); \ + dst = __msa_copy_s_w((v4i32)t0, 0); \ +} while (0) + +static WEBP_INLINE void ExportRowExpand_0(const uint32_t* frow, uint8_t* dst, + int length, + WebPRescaler* const wrk) { + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3; + v16u8 out; + LD_UW4(frow, 4, src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, scale, shift, out); + ST_UB(out, dst); + length -= 16; + frow += 16; + dst += 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2; + LD_UW3(frow, 4, src0, src1, src2); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + CALC_MULT_FIX_4(src2, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + length -= 12; + frow += 12; + dst += 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1; + LD_UW2(frow, 4, src0, src1); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + length -= 8; + frow += 8; + dst += 8; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 src0 = LD_UW(frow); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + SW(val0_m, dst); + length -= 4; + frow += 4; + dst += 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static WEBP_INLINE void ExportRowExpand_1(const uint32_t* frow, uint32_t* irow, + uint8_t* dst, int length, + WebPRescaler* const wrk) { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + const v4i32 B1 = __msa_fill_w(B); + const v4i32 A1 = __msa_fill_w(A); + const v4i32 AB = __msa_ilvr_w(A1, B1); + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + + while (length >= 16) { + v4u32 frow0, frow1, frow2, frow3, irow0, irow1, irow2, irow3; + v16u8 t0, t1, t2, t3, t4, t5; + LD_UW4(frow, 4, frow0, frow1, frow2, frow3); + LD_UW4(irow, 4, irow0, irow1, irow2, irow3); + CALC_MULT_FIX2_16(frow0, frow1, irow0, irow1, AB, scale, shift, t0, t1); + CALC_MULT_FIX2_16(frow2, frow3, irow2, irow3, AB, scale, shift, t2, t3); + PCKEV_B2_UB(t1, t0, t3, t2, t4, t5); + t0 = (v16u8)__msa_pckev_b((v16i8)t5, (v16i8)t4); + ST_UB(t0, dst); + frow += 16; + irow += 16; + dst += 16; + length -= 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 frow0, frow1, frow2, irow0, irow1, irow2; + LD_UW3(frow, 4, frow0, frow1, frow2); + LD_UW3(irow, 4, irow0, irow1, irow2); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + CALC_MULT_FIX2_4(frow1, irow1, AB, scale, shift, val1_m); + CALC_MULT_FIX2_4(frow2, irow2, AB, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + frow += 12; + irow += 12; + dst += 12; + length -= 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 frow0, frow1, irow0, irow1; + LD_UW2(frow, 4, frow0, frow1); + LD_UW2(irow, 4, irow0, irow1); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + CALC_MULT_FIX2_4(frow1, irow1, AB, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 frow0 = LD_UW(frow + 0); + const v4u32 irow0 = LD_UW(irow + 0); + CALC_MULT_FIX2_4(frow0, irow0, AB, scale, shift, val0_m); + SW(val0_m, dst); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static void RescalerExportRowExpand_MIPSdspR2(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + ExportRowExpand_0(frow, dst, x_out_max, wrk); + } else { + ExportRowExpand_1(frow, irow, dst, x_out_max, wrk); + } +} + +#if 0 // disabled for now. TODO(skal): make match the C-code +static WEBP_INLINE void ExportRowShrink_0(const uint32_t* frow, uint32_t* irow, + uint8_t* dst, int length, + const uint32_t yscale, + WebPRescaler* const wrk) { + const v4u32 y_scale = (v4u32)__msa_fill_w(yscale); + const v4u32 fxyscale = (v4u32)__msa_fill_w(wrk->fxy_scale); + const v4u32 shiftval = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3, frac0, frac1, frac2, frac3; + v16u8 out; + LD_UW4(frow, 4, src0, src1, src2, src3); + CALC_MULT_FIX1_16(src0, src1, src2, src3, y_scale, shiftval, + frac0, frac1, frac2, frac3); + LD_UW4(irow, 4, src0, src1, src2, src3); + SUB4(src0, frac0, src1, frac1, src2, frac2, src3, frac3, + src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, fxyscale, shiftval, out); + ST_UB(out, dst); + ST_UW4(frac0, frac1, frac2, frac3, irow, 4); + frow += 16; + irow += 16; + dst += 16; + length -= 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2, frac0, frac1, frac2; + LD_UW3(frow, 4, src0, src1, src2); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + CALC_MULT_FIX1_4(src1, y_scale, shiftval, frac1); + CALC_MULT_FIX1_4(src2, y_scale, shiftval, frac2); + LD_UW3(irow, 4, src0, src1, src2); + SUB3(src0, frac0, src1, frac1, src2, frac2, src0, src1, src2); + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + CALC_MULT_FIX_4(src1, fxyscale, shiftval, val1_m); + CALC_MULT_FIX_4(src2, fxyscale, shiftval, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + ST_UW3(frac0, frac1, frac2, irow, 4); + frow += 12; + irow += 12; + dst += 12; + length -= 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1, frac0, frac1; + LD_UW2(frow, 4, src0, src1); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + CALC_MULT_FIX1_4(src1, y_scale, shiftval, frac1); + LD_UW2(irow, 4, src0, src1); + SUB2(src0, frac0, src1, frac1, src0, src1); + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + CALC_MULT_FIX_4(src1, fxyscale, shiftval, val1_m); + SW2(val0_m, val1_m, dst, 4); + ST_UW2(frac0, frac1, irow, 4); + frow += 8; + irow += 8; + dst += 8; + length -= 8; + } else if (length >= 4) { + uint32_t val0_m; + v4u32 frac0; + v4u32 src0 = LD_UW(frow); + CALC_MULT_FIX1_4(src0, y_scale, shiftval, frac0); + src0 = LD_UW(irow); + src0 = src0 - frac0; + CALC_MULT_FIX_4(src0, fxyscale, shiftval, val0_m); + SW(val0_m, dst); + ST_UW(frac0, irow); + frow += 4; + irow += 4; + dst += 4; + length -= 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; + } + } +} + +static WEBP_INLINE void ExportRowShrink_1(uint32_t* irow, uint8_t* dst, + int length, + WebPRescaler* const wrk) { + const v4u32 scale = (v4u32)__msa_fill_w(wrk->fxy_scale); + const v4u32 shift = (v4u32)__msa_fill_w(WEBP_RESCALER_RFIX); + const v4i32 zero = { 0 }; + + while (length >= 16) { + v4u32 src0, src1, src2, src3; + v16u8 dst0; + LD_UW4(irow, 4, src0, src1, src2, src3); + CALC_MULT_FIX_16(src0, src1, src2, src3, scale, shift, dst0); + ST_UB(dst0, dst); + ST_SW4(zero, zero, zero, zero, irow, 4); + length -= 16; + irow += 16; + dst += 16; + } + if (length > 0) { + int x_out; + if (length >= 12) { + uint32_t val0_m, val1_m, val2_m; + v4u32 src0, src1, src2; + LD_UW3(irow, 4, src0, src1, src2); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + CALC_MULT_FIX_4(src2, scale, shift, val2_m); + SW3(val0_m, val1_m, val2_m, dst, 4); + ST_SW3(zero, zero, zero, irow, 4); + length -= 12; + irow += 12; + dst += 12; + } else if (length >= 8) { + uint32_t val0_m, val1_m; + v4u32 src0, src1; + LD_UW2(irow, 4, src0, src1); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + CALC_MULT_FIX_4(src1, scale, shift, val1_m); + SW2(val0_m, val1_m, dst, 4); + ST_SW2(zero, zero, irow, 4); + length -= 8; + irow += 8; + dst += 8; + } else if (length >= 4) { + uint32_t val0_m; + const v4u32 src0 = LD_UW(irow + 0); + CALC_MULT_FIX_4(src0, scale, shift, val0_m); + SW(val0_m, dst); + ST_SW(zero, irow); + length -= 4; + irow += 4; + dst += 4; + } + for (x_out = 0; x_out < length; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +static void RescalerExportRowShrink_MIPSdspR2(WebPRescaler* const wrk) { + uint8_t* dst = wrk->dst; + rescaler_t* irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + ExportRowShrink_0(frow, irow, dst, x_out_max, yscale, wrk); + } else { + ExportRowShrink_1(irow, dst, x_out_max, wrk); + } +} +#endif // 0 + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPRescalerDspInitMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitMSA(void) { + WebPRescalerExportRowExpand = RescalerExportRowExpand_MIPSdspR2; +// WebPRescalerExportRowShrink = RescalerExportRowShrink_MIPSdspR2; +} + +#else // !WEBP_USE_MSA + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitMSA) + +#endif // WEBP_USE_MSA diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_neon.c new file mode 100644 index 0000000000..957a92dbc9 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_neon.c @@ -0,0 +1,192 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON version of rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) && !defined(WEBP_REDUCE_SIZE) + +#include +#include +#include "src/dsp/neon.h" +#include "src/utils/rescaler_utils.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX_C(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR_C(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +#define LOAD_32x4(SRC, DST) const uint32x4_t DST = vld1q_u32((SRC)) +#define LOAD_32x8(SRC, DST0, DST1) \ + LOAD_32x4(SRC + 0, DST0); \ + LOAD_32x4(SRC + 4, DST1) + +#define STORE_32x8(SRC0, SRC1, DST) do { \ + vst1q_u32((DST) + 0, SRC0); \ + vst1q_u32((DST) + 4, SRC1); \ +} while (0) + +#if (WEBP_RESCALER_RFIX == 32) +#define MAKE_HALF_CST(C) vdupq_n_s32((int32_t)((C) >> 1)) +// note: B is actualy scale>>1. See MAKE_HALF_CST +#define MULT_FIX(A, B) \ + vreinterpretq_u32_s32(vqrdmulhq_s32(vreinterpretq_s32_u32((A)), (B))) +#define MULT_FIX_FLOOR(A, B) \ + vreinterpretq_u32_s32(vqdmulhq_s32(vreinterpretq_s32_u32((A)), (B))) +#else +#error "MULT_FIX/WEBP_RESCALER_RFIX need some more work" +#endif + +static uint32x4_t Interpolate_NEON(const rescaler_t* const frow, + const rescaler_t* const irow, + uint32_t A, uint32_t B) { + LOAD_32x4(frow, A0); + LOAD_32x4(irow, B0); + const uint64x2_t C0 = vmull_n_u32(vget_low_u32(A0), A); + const uint64x2_t C1 = vmull_n_u32(vget_high_u32(A0), A); + const uint64x2_t D0 = vmlal_n_u32(C0, vget_low_u32(B0), B); + const uint64x2_t D1 = vmlal_n_u32(C1, vget_high_u32(B0), B); + const uint32x4_t E = vcombine_u32( + vrshrn_n_u64(D0, WEBP_RESCALER_RFIX), + vrshrn_n_u64(D1, WEBP_RESCALER_RFIX)); + return E; +} + +static void RescalerExportRowExpand_NEON(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t fy_scale = wrk->fy_scale; + const int32x4_t fy_scale_half = MAKE_HALF_CST(fy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x4(frow + x_out + 0, A0); + LOAD_32x4(frow + x_out + 4, A1); + const uint32x4_t B0 = MULT_FIX(A0, fy_scale_half); + const uint32x4_t B1 = MULT_FIX(A1, fy_scale_half); + const uint16x4_t C0 = vmovn_u32(B0); + const uint16x4_t C1 = vmovn_u32(B1); + const uint8x8_t D = vqmovn_u16(vcombine_u16(C0, C1)); + vst1_u8(dst + x_out, D); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX_C(J, fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < max_span; x_out += 8) { + const uint32x4_t C0 = + Interpolate_NEON(frow + x_out + 0, irow + x_out + 0, A, B); + const uint32x4_t C1 = + Interpolate_NEON(frow + x_out + 4, irow + x_out + 4, A, B); + const uint32x4_t D0 = MULT_FIX(C0, fy_scale_half); + const uint32x4_t D1 = MULT_FIX(C1, fy_scale_half); + const uint16x4_t E0 = vmovn_u32(D0); + const uint16x4_t E1 = vmovn_u32(D1); + const uint8x8_t F = vqmovn_u16(vcombine_u16(E0, E1)); + vst1_u8(dst + x_out, F); + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX_C(J, fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static void RescalerExportRowShrink_NEON(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + const uint32_t fxy_scale = wrk->fxy_scale; + const uint32x4_t zero = vdupq_n_u32(0); + const int32x4_t yscale_half = MAKE_HALF_CST(yscale); + const int32x4_t fxy_scale_half = MAKE_HALF_CST(fxy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(frow + x_out, in0, in1); + LOAD_32x8(irow + x_out, in2, in3); + const uint32x4_t A0 = MULT_FIX_FLOOR(in0, yscale_half); + const uint32x4_t A1 = MULT_FIX_FLOOR(in1, yscale_half); + const uint32x4_t B0 = vqsubq_u32(in2, A0); + const uint32x4_t B1 = vqsubq_u32(in3, A1); + const uint32x4_t C0 = MULT_FIX(B0, fxy_scale_half); + const uint32x4_t C1 = MULT_FIX(B1, fxy_scale_half); + const uint16x4_t D0 = vmovn_u32(C0); + const uint16x4_t D1 = vmovn_u32(C1); + const uint8x8_t E = vqmovn_u16(vcombine_u16(D0, D1)); + vst1_u8(dst + x_out, E); + STORE_32x8(A0, A1, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR_C(frow[x_out], yscale); + const int v = (int)MULT_FIX_C(irow[x_out] - frac, fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(irow + x_out, in0, in1); + const uint32x4_t A0 = MULT_FIX(in0, fxy_scale_half); + const uint32x4_t A1 = MULT_FIX(in1, fxy_scale_half); + const uint16x4_t B0 = vmovn_u32(A0); + const uint16x4_t B1 = vmovn_u32(A1); + const uint8x8_t C = vqmovn_u16(vcombine_u16(B0, B1)); + vst1_u8(dst + x_out, C); + STORE_32x8(zero, zero, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX_C(irow[x_out], fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR_C +#undef MULT_FIX_C +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitNEON(void) { + WebPRescalerExportRowExpand = RescalerExportRowExpand_NEON; + WebPRescalerExportRowShrink = RescalerExportRowShrink_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_sse2.c new file mode 100644 index 0000000000..3f18e94e93 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/rescaler_sse2.c @@ -0,0 +1,366 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) && !defined(WEBP_REDUCE_SIZE) +#include + +#include +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +// input: 8 bytes ABCDEFGH -> output: A0E0B0F0C0G0D0H0 +static void LoadTwoPixels_SSE2(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + const __m128i B = _mm_unpacklo_epi8(A, zero); // A0B0C0D0E0F0G0H0 + const __m128i C = _mm_srli_si128(B, 8); // E0F0G0H0 + *out = _mm_unpacklo_epi16(B, C); +} + +// input: 8 bytes ABCDEFGH -> output: A0B0C0D0E0F0G0H0 +static void LoadEightPixels_SSE2(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + *out = _mm_unpacklo_epi8(A, zero); +} + +static void RescalerImportRowExpand_SSE2(WebPRescaler* const wrk, + const uint8_t* src) { + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = frow + wrk->dst_width * wrk->num_channels; + const int x_add = wrk->x_add; + int accum = x_add; + __m128i cur_pixels; + + // SSE2 implementation only works with 16b signed arithmetic at max. + if (wrk->src_width < 8 || accum >= (1 << 15)) { + WebPRescalerImportRowExpand_C(wrk, src); + return; + } + + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + if (wrk->num_channels == 4) { + LoadTwoPixels_SSE2(src, &cur_pixels); + src += 4; + while (1) { + const __m128i mult = _mm_set1_epi32(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + _mm_storeu_si128((__m128i*)frow, out); + frow += 4; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + LoadTwoPixels_SSE2(src, &cur_pixels); + src += 4; + accum += x_add; + } + } + } else { + int left; + const uint8_t* const src_limit = src + wrk->src_width - 8; + LoadEightPixels_SSE2(src, &cur_pixels); + src += 7; + left = 7; + while (1) { + const __m128i mult = _mm_cvtsi32_si128(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + assert(sizeof(*frow) == sizeof(uint32_t)); + WebPInt32ToMem((uint8_t*)frow, _mm_cvtsi128_si32(out)); + frow += 1; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + if (--left) { + cur_pixels = _mm_srli_si128(cur_pixels, 2); + } else if (src <= src_limit) { + LoadEightPixels_SSE2(src, &cur_pixels); + src += 7; + left = 7; + } else { // tail + cur_pixels = _mm_srli_si128(cur_pixels, 2); + cur_pixels = _mm_insert_epi16(cur_pixels, src[1], 1); + src += 1; + left = 1; + } + accum += x_add; + } + } + } + assert(accum == 0); +} + +static void RescalerImportRowShrink_SSE2(WebPRescaler* const wrk, + const uint8_t* src) { + const int x_sub = wrk->x_sub; + int accum = 0; + const __m128i zero = _mm_setzero_si128(); + const __m128i mult0 = _mm_set1_epi16(x_sub); + const __m128i mult1 = _mm_set1_epi32(wrk->fx_scale); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + __m128i sum = zero; + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = wrk->frow + 4 * wrk->dst_width; + + if (wrk->num_channels != 4 || wrk->x_add > (x_sub << 7)) { + WebPRescalerImportRowShrink_C(wrk, src); + return; + } + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + + for (; frow < frow_end; frow += 4) { + __m128i base = zero; + accum += wrk->x_add; + while (accum > 0) { + const __m128i A = _mm_cvtsi32_si128(WebPMemToInt32(src)); + src += 4; + base = _mm_unpacklo_epi8(A, zero); + // To avoid overflow, we need: base * x_add / x_sub < 32768 + // => x_add < x_sub << 7. That's a 1/128 reduction ratio limit. + sum = _mm_add_epi16(sum, base); + accum -= x_sub; + } + { // Emit next horizontal pixel. + const __m128i mult = _mm_set1_epi16(-accum); + const __m128i frac0 = _mm_mullo_epi16(base, mult); // 16b x 16b -> 32b + const __m128i frac1 = _mm_mulhi_epu16(base, mult); + const __m128i frac = _mm_unpacklo_epi16(frac0, frac1); // frac is 32b + const __m128i A0 = _mm_mullo_epi16(sum, mult0); + const __m128i A1 = _mm_mulhi_epu16(sum, mult0); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // sum * x_sub + const __m128i frow_out = _mm_sub_epi32(B0, frac); // sum * x_sub - frac + const __m128i D0 = _mm_srli_epi64(frac, 32); + const __m128i D1 = _mm_mul_epu32(frac, mult1); // 32b x 16b -> 64b + const __m128i D2 = _mm_mul_epu32(D0, mult1); + const __m128i E1 = _mm_add_epi64(D1, rounder); + const __m128i E2 = _mm_add_epi64(D2, rounder); + const __m128i F1 = _mm_shuffle_epi32(E1, 1 | (3 << 2)); + const __m128i F2 = _mm_shuffle_epi32(E2, 1 | (3 << 2)); + const __m128i G = _mm_unpacklo_epi32(F1, F2); + sum = _mm_packs_epi32(G, zero); + _mm_storeu_si128((__m128i*)frow, frow_out); + } + } + assert(accum == 0); +} + +//------------------------------------------------------------------------------ +// Row export + +// load *src as epi64, multiply by mult and store result in [out0 ... out3] +static WEBP_INLINE void LoadDispatchAndMult_SSE2(const rescaler_t* const src, + const __m128i* const mult, + __m128i* const out0, + __m128i* const out1, + __m128i* const out2, + __m128i* const out3) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + 4)); + const __m128i A2 = _mm_srli_epi64(A0, 32); + const __m128i A3 = _mm_srli_epi64(A1, 32); + if (mult != NULL) { + *out0 = _mm_mul_epu32(A0, *mult); + *out1 = _mm_mul_epu32(A1, *mult); + *out2 = _mm_mul_epu32(A2, *mult); + *out3 = _mm_mul_epu32(A3, *mult); + } else { + *out0 = A0; + *out1 = A1; + *out2 = A2; + *out3 = A3; + } +} + +static WEBP_INLINE void ProcessRow_SSE2(const __m128i* const A0, + const __m128i* const A1, + const __m128i* const A2, + const __m128i* const A3, + const __m128i* const mult, + uint8_t* const dst) { + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + const __m128i mask = _mm_set_epi32(~0, 0, ~0, 0); + const __m128i B0 = _mm_mul_epu32(*A0, *mult); + const __m128i B1 = _mm_mul_epu32(*A1, *mult); + const __m128i B2 = _mm_mul_epu32(*A2, *mult); + const __m128i B3 = _mm_mul_epu32(*A3, *mult); + const __m128i C0 = _mm_add_epi64(B0, rounder); + const __m128i C1 = _mm_add_epi64(B1, rounder); + const __m128i C2 = _mm_add_epi64(B2, rounder); + const __m128i C3 = _mm_add_epi64(B3, rounder); + const __m128i D0 = _mm_srli_epi64(C0, WEBP_RESCALER_RFIX); + const __m128i D1 = _mm_srli_epi64(C1, WEBP_RESCALER_RFIX); +#if (WEBP_RESCALER_RFIX < 32) + const __m128i D2 = + _mm_and_si128(_mm_slli_epi64(C2, 32 - WEBP_RESCALER_RFIX), mask); + const __m128i D3 = + _mm_and_si128(_mm_slli_epi64(C3, 32 - WEBP_RESCALER_RFIX), mask); +#else + const __m128i D2 = _mm_and_si128(C2, mask); + const __m128i D3 = _mm_and_si128(C3, mask); +#endif + const __m128i E0 = _mm_or_si128(D0, D2); + const __m128i E1 = _mm_or_si128(D1, D3); + const __m128i F = _mm_packs_epi32(E0, E1); + const __m128i G = _mm_packus_epi16(F, F); + _mm_storel_epi64((__m128i*)dst, G); +} + +static void RescalerExportRowExpand_SSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const __m128i mult = _mm_set_epi32(0, wrk->fy_scale, 0, wrk->fy_scale); + + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0 && wrk->y_sub + wrk->y_accum >= 0); + assert(wrk->y_expand); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult_SSE2(frow + x_out, NULL, &A0, &A1, &A2, &A3); + ProcessRow_SSE2(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + const __m128i mA = _mm_set_epi32(0, A, 0, A); + const __m128i mB = _mm_set_epi32(0, B, 0, B); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult_SSE2(frow + x_out, &mA, &A0, &A1, &A2, &A3); + LoadDispatchAndMult_SSE2(irow + x_out, &mB, &B0, &B1, &B2, &B3); + { + const __m128i C0 = _mm_add_epi64(A0, B0); + const __m128i C1 = _mm_add_epi64(A1, B1); + const __m128i C2 = _mm_add_epi64(A2, B2); + const __m128i C3 = _mm_add_epi64(A3, B3); + const __m128i D0 = _mm_add_epi64(C0, rounder); + const __m128i D1 = _mm_add_epi64(C1, rounder); + const __m128i D2 = _mm_add_epi64(C2, rounder); + const __m128i D3 = _mm_add_epi64(C3, rounder); + const __m128i E0 = _mm_srli_epi64(D0, WEBP_RESCALER_RFIX); + const __m128i E1 = _mm_srli_epi64(D1, WEBP_RESCALER_RFIX); + const __m128i E2 = _mm_srli_epi64(D2, WEBP_RESCALER_RFIX); + const __m128i E3 = _mm_srli_epi64(D3, WEBP_RESCALER_RFIX); + ProcessRow_SSE2(&E0, &E1, &E2, &E3, &mult, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static void RescalerExportRowShrink_SSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + const int scale_xy = wrk->fxy_scale; + const __m128i mult_xy = _mm_set_epi32(0, scale_xy, 0, scale_xy); + const __m128i mult_y = _mm_set_epi32(0, yscale, 0, yscale); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult_SSE2(irow + x_out, NULL, &A0, &A1, &A2, &A3); + LoadDispatchAndMult_SSE2(frow + x_out, &mult_y, &B0, &B1, &B2, &B3); + { + const __m128i D0 = _mm_srli_epi64(B0, WEBP_RESCALER_RFIX); // = frac + const __m128i D1 = _mm_srli_epi64(B1, WEBP_RESCALER_RFIX); + const __m128i D2 = _mm_srli_epi64(B2, WEBP_RESCALER_RFIX); + const __m128i D3 = _mm_srli_epi64(B3, WEBP_RESCALER_RFIX); + const __m128i E0 = _mm_sub_epi64(A0, D0); // irow[x] - frac + const __m128i E1 = _mm_sub_epi64(A1, D1); + const __m128i E2 = _mm_sub_epi64(A2, D2); + const __m128i E3 = _mm_sub_epi64(A3, D3); + const __m128i F2 = _mm_slli_epi64(D2, 32); + const __m128i F3 = _mm_slli_epi64(D3, 32); + const __m128i G0 = _mm_or_si128(D0, F2); + const __m128i G1 = _mm_or_si128(D1, F3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), G0); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), G1); + ProcessRow_SSE2(&E0, &E1, &E2, &E3, &mult_xy, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (int)MULT_FIX_FLOOR(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + const uint32_t scale = wrk->fxy_scale; + const __m128i mult = _mm_set_epi32(0, scale, 0, scale); + const __m128i zero = _mm_setzero_si128(); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult_SSE2(irow + x_out, NULL, &A0, &A1, &A2, &A3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), zero); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), zero); + ProcessRow_SSE2(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitSSE2(void) { + WebPRescalerImportRowExpand = RescalerImportRowExpand_SSE2; + WebPRescalerImportRowShrink = RescalerImportRowShrink_SSE2; + WebPRescalerExportRowExpand = RescalerExportRowExpand_SSE2; + WebPRescalerExportRowShrink = RescalerExportRowShrink_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim.c new file mode 100644 index 0000000000..9a1341ed95 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim.c @@ -0,0 +1,160 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// distortion calculation +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include // for abs() + +#include "src/dsp/dsp.h" + +#if !defined(WEBP_REDUCE_SIZE) + +//------------------------------------------------------------------------------ +// SSIM / PSNR + +// hat-shaped filter. Sum of coefficients is equal to 16. +static const uint32_t kWeight[2 * VP8_SSIM_KERNEL + 1] = { + 1, 2, 3, 4, 3, 2, 1 +}; +static const uint32_t kWeightSum = 16 * 16; // sum{kWeight}^2 + +static WEBP_INLINE double SSIMCalculation( + const VP8DistoStats* const stats, uint32_t N /*num samples*/) { + const uint32_t w2 = N * N; + const uint32_t C1 = 20 * w2; + const uint32_t C2 = 60 * w2; + const uint32_t C3 = 8 * 8 * w2; // 'dark' limit ~= 6 + const uint64_t xmxm = (uint64_t)stats->xm * stats->xm; + const uint64_t ymym = (uint64_t)stats->ym * stats->ym; + if (xmxm + ymym >= C3) { + const int64_t xmym = (int64_t)stats->xm * stats->ym; + const int64_t sxy = (int64_t)stats->xym * N - xmym; // can be negative + const uint64_t sxx = (uint64_t)stats->xxm * N - xmxm; + const uint64_t syy = (uint64_t)stats->yym * N - ymym; + // we descale by 8 to prevent overflow during the fnum/fden multiply. + const uint64_t num_S = (2 * (uint64_t)(sxy < 0 ? 0 : sxy) + C2) >> 8; + const uint64_t den_S = (sxx + syy + C2) >> 8; + const uint64_t fnum = (2 * xmym + C1) * num_S; + const uint64_t fden = (xmxm + ymym + C1) * den_S; + const double r = (double)fnum / fden; + assert(r >= 0. && r <= 1.0); + return r; + } + return 1.; // area is too dark to contribute meaningfully +} + +double VP8SSIMFromStats(const VP8DistoStats* const stats) { + return SSIMCalculation(stats, kWeightSum); +} + +double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats) { + return SSIMCalculation(stats, stats->w); +} + +static double SSIMGetClipped_C(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2, + int xo, int yo, int W, int H) { + VP8DistoStats stats = { 0, 0, 0, 0, 0, 0 }; + const int ymin = (yo - VP8_SSIM_KERNEL < 0) ? 0 : yo - VP8_SSIM_KERNEL; + const int ymax = (yo + VP8_SSIM_KERNEL > H - 1) ? H - 1 + : yo + VP8_SSIM_KERNEL; + const int xmin = (xo - VP8_SSIM_KERNEL < 0) ? 0 : xo - VP8_SSIM_KERNEL; + const int xmax = (xo + VP8_SSIM_KERNEL > W - 1) ? W - 1 + : xo + VP8_SSIM_KERNEL; + int x, y; + src1 += ymin * stride1; + src2 += ymin * stride2; + for (y = ymin; y <= ymax; ++y, src1 += stride1, src2 += stride2) { + for (x = xmin; x <= xmax; ++x) { + const uint32_t w = kWeight[VP8_SSIM_KERNEL + x - xo] + * kWeight[VP8_SSIM_KERNEL + y - yo]; + const uint32_t s1 = src1[x]; + const uint32_t s2 = src2[x]; + stats.w += w; + stats.xm += w * s1; + stats.ym += w * s2; + stats.xxm += w * s1 * s1; + stats.xym += w * s1 * s2; + stats.yym += w * s2 * s2; + } + } + return VP8SSIMFromStatsClipped(&stats); +} + +static double SSIMGet_C(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2) { + VP8DistoStats stats = { 0, 0, 0, 0, 0, 0 }; + int x, y; + for (y = 0; y <= 2 * VP8_SSIM_KERNEL; ++y, src1 += stride1, src2 += stride2) { + for (x = 0; x <= 2 * VP8_SSIM_KERNEL; ++x) { + const uint32_t w = kWeight[x] * kWeight[y]; + const uint32_t s1 = src1[x]; + const uint32_t s2 = src2[x]; + stats.xm += w * s1; + stats.ym += w * s2; + stats.xxm += w * s1 * s1; + stats.xym += w * s1 * s2; + stats.yym += w * s2 * s2; + } + } + return VP8SSIMFromStats(&stats); +} + +#endif // !defined(WEBP_REDUCE_SIZE) + +//------------------------------------------------------------------------------ + +#if !defined(WEBP_DISABLE_STATS) +static uint32_t AccumulateSSE_C(const uint8_t* src1, + const uint8_t* src2, int len) { + int i; + uint32_t sse2 = 0; + assert(len <= 65535); // to ensure that accumulation fits within uint32_t + for (i = 0; i < len; ++i) { + const int32_t diff = src1[i] - src2[i]; + sse2 += diff * diff; + } + return sse2; +} +#endif + +//------------------------------------------------------------------------------ + +#if !defined(WEBP_REDUCE_SIZE) +VP8SSIMGetFunc VP8SSIMGet; +VP8SSIMGetClippedFunc VP8SSIMGetClipped; +#endif +#if !defined(WEBP_DISABLE_STATS) +VP8AccumulateSSEFunc VP8AccumulateSSE; +#endif + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8SSIMDspInitSSE2(void); + +WEBP_DSP_INIT_FUNC(VP8SSIMDspInit) { +#if !defined(WEBP_REDUCE_SIZE) + VP8SSIMGetClipped = SSIMGetClipped_C; + VP8SSIMGet = SSIMGet_C; +#endif + +#if !defined(WEBP_DISABLE_STATS) + VP8AccumulateSSE = AccumulateSSE_C; +#endif + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8SSIMDspInitSSE2(); + } +#endif + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim_sse2.c new file mode 100644 index 0000000000..1dcb0eb0ec --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/ssim_sse2.c @@ -0,0 +1,165 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of distortion calculation +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include + +#include "src/dsp/common_sse2.h" + +#if !defined(WEBP_DISABLE_STATS) + +// Helper function +static WEBP_INLINE void SubtractAndSquare_SSE2(const __m128i a, const __m128i b, + __m128i* const sum) { + // take abs(a-b) in 8b + const __m128i a_b = _mm_subs_epu8(a, b); + const __m128i b_a = _mm_subs_epu8(b, a); + const __m128i abs_a_b = _mm_or_si128(a_b, b_a); + // zero-extend to 16b + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(abs_a_b, zero); + const __m128i C1 = _mm_unpackhi_epi8(abs_a_b, zero); + // multiply with self + const __m128i sum1 = _mm_madd_epi16(C0, C0); + const __m128i sum2 = _mm_madd_epi16(C1, C1); + *sum = _mm_add_epi32(sum1, sum2); +} + +//------------------------------------------------------------------------------ +// SSIM / PSNR entry point + +static uint32_t AccumulateSSE_SSE2(const uint8_t* src1, + const uint8_t* src2, int len) { + int i = 0; + uint32_t sse2 = 0; + if (len >= 16) { + const int limit = len - 32; + int32_t tmp[4]; + __m128i sum1; + __m128i sum = _mm_setzero_si128(); + __m128i a0 = _mm_loadu_si128((const __m128i*)&src1[i]); + __m128i b0 = _mm_loadu_si128((const __m128i*)&src2[i]); + i += 16; + while (i <= limit) { + const __m128i a1 = _mm_loadu_si128((const __m128i*)&src1[i]); + const __m128i b1 = _mm_loadu_si128((const __m128i*)&src2[i]); + __m128i sum2; + i += 16; + SubtractAndSquare_SSE2(a0, b0, &sum1); + sum = _mm_add_epi32(sum, sum1); + a0 = _mm_loadu_si128((const __m128i*)&src1[i]); + b0 = _mm_loadu_si128((const __m128i*)&src2[i]); + i += 16; + SubtractAndSquare_SSE2(a1, b1, &sum2); + sum = _mm_add_epi32(sum, sum2); + } + SubtractAndSquare_SSE2(a0, b0, &sum1); + sum = _mm_add_epi32(sum, sum1); + _mm_storeu_si128((__m128i*)tmp, sum); + sse2 += (tmp[3] + tmp[2] + tmp[1] + tmp[0]); + } + + for (; i < len; ++i) { + const int32_t diff = src1[i] - src2[i]; + sse2 += diff * diff; + } + return sse2; +} +#endif // !defined(WEBP_DISABLE_STATS) + +#if !defined(WEBP_REDUCE_SIZE) + +static uint32_t HorizontalAdd16b_SSE2(const __m128i* const m) { + uint16_t tmp[8]; + const __m128i a = _mm_srli_si128(*m, 8); + const __m128i b = _mm_add_epi16(*m, a); + _mm_storeu_si128((__m128i*)tmp, b); + return (uint32_t)tmp[3] + tmp[2] + tmp[1] + tmp[0]; +} + +static uint32_t HorizontalAdd32b_SSE2(const __m128i* const m) { + const __m128i a = _mm_srli_si128(*m, 8); + const __m128i b = _mm_add_epi32(*m, a); + const __m128i c = _mm_add_epi32(b, _mm_srli_si128(b, 4)); + return (uint32_t)_mm_cvtsi128_si32(c); +} + +static const uint16_t kWeight[] = { 1, 2, 3, 4, 3, 2, 1, 0 }; + +#define ACCUMULATE_ROW(WEIGHT) do { \ + /* compute row weight (Wx * Wy) */ \ + const __m128i Wy = _mm_set1_epi16((WEIGHT)); \ + const __m128i W = _mm_mullo_epi16(Wx, Wy); \ + /* process 8 bytes at a time (7 bytes, actually) */ \ + const __m128i a0 = _mm_loadl_epi64((const __m128i*)src1); \ + const __m128i b0 = _mm_loadl_epi64((const __m128i*)src2); \ + /* convert to 16b and multiply by weight */ \ + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); \ + const __m128i b1 = _mm_unpacklo_epi8(b0, zero); \ + const __m128i wa1 = _mm_mullo_epi16(a1, W); \ + const __m128i wb1 = _mm_mullo_epi16(b1, W); \ + /* accumulate */ \ + xm = _mm_add_epi16(xm, wa1); \ + ym = _mm_add_epi16(ym, wb1); \ + xxm = _mm_add_epi32(xxm, _mm_madd_epi16(a1, wa1)); \ + xym = _mm_add_epi32(xym, _mm_madd_epi16(a1, wb1)); \ + yym = _mm_add_epi32(yym, _mm_madd_epi16(b1, wb1)); \ + src1 += stride1; \ + src2 += stride2; \ +} while (0) + +static double SSIMGet_SSE2(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2) { + VP8DistoStats stats; + const __m128i zero = _mm_setzero_si128(); + __m128i xm = zero, ym = zero; // 16b accums + __m128i xxm = zero, yym = zero, xym = zero; // 32b accum + const __m128i Wx = _mm_loadu_si128((const __m128i*)kWeight); + assert(2 * VP8_SSIM_KERNEL + 1 == 7); + ACCUMULATE_ROW(1); + ACCUMULATE_ROW(2); + ACCUMULATE_ROW(3); + ACCUMULATE_ROW(4); + ACCUMULATE_ROW(3); + ACCUMULATE_ROW(2); + ACCUMULATE_ROW(1); + stats.xm = HorizontalAdd16b_SSE2(&xm); + stats.ym = HorizontalAdd16b_SSE2(&ym); + stats.xxm = HorizontalAdd32b_SSE2(&xxm); + stats.xym = HorizontalAdd32b_SSE2(&xym); + stats.yym = HorizontalAdd32b_SSE2(&yym); + return VP8SSIMFromStats(&stats); +} + +#endif // !defined(WEBP_REDUCE_SIZE) + +extern void VP8SSIMDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8SSIMDspInitSSE2(void) { +#if !defined(WEBP_DISABLE_STATS) + VP8AccumulateSSE = AccumulateSSE_SSE2; +#endif +#if !defined(WEBP_REDUCE_SIZE) + VP8SSIMGet = SSIMGet_SSE2; +#endif +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8SSIMDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling.c new file mode 100644 index 0000000000..983b9c42d3 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling.c @@ -0,0 +1,328 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" +#include "src/dsp/yuv.h" + +#include + +//------------------------------------------------------------------------------ +// Fancy upsampler + +#ifdef FANCY_UPSAMPLING + +// Fancy upsampling functions to convert YUV to RGB +WebPUpsampleLinePairFunc WebPUpsamplers[MODE_LAST]; + +// Given samples laid out in a square as: +// [a b] +// [c d] +// we interpolate u/v as: +// ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 +// ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 + +// We process u and v together stashed into 32bit (16bit each). +#define LOAD_UV(u, v) ((u) | ((v) << 16)) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int x; \ + const int last_pixel_pair = (len - 1) >> 1; \ + uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \ + uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \ + assert(top_y != NULL); \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \ + } \ + for (x = 1; x <= last_pixel_pair; ++x) { \ + const uint32_t t_uv = LOAD_UV(top_u[x], top_v[x]); /* top sample */ \ + const uint32_t uv = LOAD_UV(cur_u[x], cur_v[x]); /* sample */ \ + /* precompute invariant values associated with first and second diagonals*/\ + const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \ + const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \ + const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \ + { \ + const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \ + const uint32_t uv1 = (diag_03 + t_uv) >> 1; \ + FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (2 * x - 1) * (XSTEP)); \ + FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \ + top_dst + (2 * x - 0) * (XSTEP)); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (diag_03 + l_uv) >> 1; \ + const uint32_t uv1 = (diag_12 + uv) >> 1; \ + FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (2 * x - 1) * (XSTEP)); \ + FUNC(bottom_y[2 * x + 0], uv1 & 0xff, (uv1 >> 16), \ + bottom_dst + (2 * x + 0) * (XSTEP)); \ + } \ + tl_uv = t_uv; \ + l_uv = uv; \ + } \ + if (!(len & 1)) { \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (len - 1) * (XSTEP)); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (len - 1) * (XSTEP)); \ + } \ + } \ +} + +// All variants implemented. +#if !WEBP_NEON_OMIT_C_CODE +UPSAMPLE_FUNC(UpsampleRgbaLinePair_C, VP8YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair_C, VP8YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +UPSAMPLE_FUNC(UpsampleArgbLinePair_C, VP8YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgbLinePair_C, VP8YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair_C, VP8YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair_C, VP8YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair_C, VP8YuvToRgb565, 2) +#else +static void EmptyUpsampleFunc(const uint8_t* top_y, const uint8_t* bottom_y, + const uint8_t* top_u, const uint8_t* top_v, + const uint8_t* cur_u, const uint8_t* cur_v, + uint8_t* top_dst, uint8_t* bottom_dst, int len) { + (void)top_y; + (void)bottom_y; + (void)top_u; + (void)top_v; + (void)cur_u; + (void)cur_v; + (void)top_dst; + (void)bottom_dst; + (void)len; + assert(0); // COLORSPACE SUPPORT NOT COMPILED +} +#define UpsampleArgbLinePair_C EmptyUpsampleFunc +#define UpsampleRgbLinePair_C EmptyUpsampleFunc +#define UpsampleBgrLinePair_C EmptyUpsampleFunc +#define UpsampleRgba4444LinePair_C EmptyUpsampleFunc +#define UpsampleRgb565LinePair_C EmptyUpsampleFunc +#endif // WEBP_REDUCE_CSP + +#endif + +#undef LOAD_UV +#undef UPSAMPLE_FUNC + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +#if !defined(FANCY_UPSAMPLING) +#define DUAL_SAMPLE_FUNC(FUNC_NAME, FUNC) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bot_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* bot_u, const uint8_t* bot_v, \ + uint8_t* top_dst, uint8_t* bot_dst, int len) { \ + const int half_len = len >> 1; \ + int x; \ + assert(top_dst != NULL); \ + { \ + for (x = 0; x < half_len; ++x) { \ + FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x + 0); \ + FUNC(top_y[2 * x + 1], top_u[x], top_v[x], top_dst + 8 * x + 4); \ + } \ + if (len & 1) FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x); \ + } \ + if (bot_dst != NULL) { \ + for (x = 0; x < half_len; ++x) { \ + FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x + 0); \ + FUNC(bot_y[2 * x + 1], bot_u[x], bot_v[x], bot_dst + 8 * x + 4); \ + } \ + if (len & 1) FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x); \ + } \ +} + +DUAL_SAMPLE_FUNC(DualLineSamplerBGRA, VP8YuvToBgra) +DUAL_SAMPLE_FUNC(DualLineSamplerARGB, VP8YuvToArgb) +#undef DUAL_SAMPLE_FUNC + +#endif // !FANCY_UPSAMPLING + +WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last) { + WebPInitUpsamplers(); +#ifdef FANCY_UPSAMPLING + return WebPUpsamplers[alpha_is_last ? MODE_BGRA : MODE_ARGB]; +#else + return (alpha_is_last ? DualLineSamplerBGRA : DualLineSamplerARGB); +#endif +} + +//------------------------------------------------------------------------------ +// YUV444 converter + +#define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \ +extern void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * (XSTEP)]); \ +} + +YUV444_FUNC(WebPYuv444ToRgba_C, VP8YuvToRgba, 4) +YUV444_FUNC(WebPYuv444ToBgra_C, VP8YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(WebPYuv444ToRgb_C, VP8YuvToRgb, 3) +YUV444_FUNC(WebPYuv444ToBgr_C, VP8YuvToBgr, 3) +YUV444_FUNC(WebPYuv444ToArgb_C, VP8YuvToArgb, 4) +YUV444_FUNC(WebPYuv444ToRgba4444_C, VP8YuvToRgba4444, 2) +YUV444_FUNC(WebPYuv444ToRgb565_C, VP8YuvToRgb565, 2) +#else +static void EmptyYuv444Func(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + (void)y; + (void)u; + (void)v; + (void)dst; + (void)len; +} +#define WebPYuv444ToRgb_C EmptyYuv444Func +#define WebPYuv444ToBgr_C EmptyYuv444Func +#define WebPYuv444ToArgb_C EmptyYuv444Func +#define WebPYuv444ToRgba4444_C EmptyYuv444Func +#define WebPYuv444ToRgb565_C EmptyYuv444Func +#endif // WEBP_REDUCE_CSP + +#undef YUV444_FUNC + +WebPYUV444Converter WebPYUV444Converters[MODE_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitYUV444ConvertersMIPSdspR2(void); +extern void WebPInitYUV444ConvertersSSE2(void); +extern void WebPInitYUV444ConvertersSSE41(void); + +WEBP_DSP_INIT_FUNC(WebPInitYUV444Converters) { + WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgba_C; + WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgra_C; + WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgb_C; + WebPYUV444Converters[MODE_BGR] = WebPYuv444ToBgr_C; + WebPYUV444Converters[MODE_ARGB] = WebPYuv444ToArgb_C; + WebPYUV444Converters[MODE_RGBA_4444] = WebPYuv444ToRgba4444_C; + WebPYUV444Converters[MODE_RGB_565] = WebPYuv444ToRgb565_C; + WebPYUV444Converters[MODE_rgbA] = WebPYuv444ToRgba_C; + WebPYUV444Converters[MODE_bgrA] = WebPYuv444ToBgra_C; + WebPYUV444Converters[MODE_Argb] = WebPYuv444ToArgb_C; + WebPYUV444Converters[MODE_rgbA_4444] = WebPYuv444ToRgba4444_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitYUV444ConvertersSSE2(); + } +#endif +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitYUV444ConvertersSSE41(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitYUV444ConvertersMIPSdspR2(); + } +#endif + } +} + +//------------------------------------------------------------------------------ +// Main calls + +extern void WebPInitUpsamplersSSE2(void); +extern void WebPInitUpsamplersSSE41(void); +extern void WebPInitUpsamplersNEON(void); +extern void WebPInitUpsamplersMIPSdspR2(void); +extern void WebPInitUpsamplersMSA(void); + +WEBP_DSP_INIT_FUNC(WebPInitUpsamplers) { +#ifdef FANCY_UPSAMPLING +#if !WEBP_NEON_OMIT_C_CODE + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_C; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_C; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_C; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_C; + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_C; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_C; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_C; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_C; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_C; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_C; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_C; +#endif + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitUpsamplersSSE2(); + } +#endif +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitUpsamplersSSE41(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitUpsamplersMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPInitUpsamplersMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitUpsamplersNEON(); + } +#endif + + assert(WebPUpsamplers[MODE_RGBA] != NULL); + assert(WebPUpsamplers[MODE_BGRA] != NULL); + assert(WebPUpsamplers[MODE_rgbA] != NULL); + assert(WebPUpsamplers[MODE_bgrA] != NULL); +#if !defined(WEBP_REDUCE_CSP) || !WEBP_NEON_OMIT_C_CODE + assert(WebPUpsamplers[MODE_RGB] != NULL); + assert(WebPUpsamplers[MODE_BGR] != NULL); + assert(WebPUpsamplers[MODE_ARGB] != NULL); + assert(WebPUpsamplers[MODE_RGBA_4444] != NULL); + assert(WebPUpsamplers[MODE_RGB_565] != NULL); + assert(WebPUpsamplers[MODE_Argb] != NULL); + assert(WebPUpsamplers[MODE_rgbA_4444] != NULL); +#endif + +#endif // FANCY_UPSAMPLING +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_mips_dsp_r2.c new file mode 100644 index 0000000000..10d499d771 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_mips_dsp_r2.c @@ -0,0 +1,291 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV to RGB upsampling functions. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include +#include "src/dsp/yuv.h" + +#define YUV_TO_RGB(Y, U, V, R, G, B) do { \ + const int t1 = MultHi(Y, 19077); \ + const int t2 = MultHi(V, 13320); \ + R = MultHi(V, 26149); \ + G = MultHi(U, 6419); \ + B = MultHi(U, 33050); \ + R = t1 + R; \ + G = t1 - G; \ + B = t1 + B; \ + R = R - 14234; \ + G = G - t2 + 8708; \ + B = B - 17685; \ + __asm__ volatile ( \ + "shll_s.w %[" #R "], %[" #R "], 17 \n\t" \ + "shll_s.w %[" #G "], %[" #G "], 17 \n\t" \ + "shll_s.w %[" #B "], %[" #B "], 17 \n\t" \ + "precrqu_s.qb.ph %[" #R "], %[" #R "], $zero \n\t" \ + "precrqu_s.qb.ph %[" #G "], %[" #G "], $zero \n\t" \ + "precrqu_s.qb.ph %[" #B "], %[" #B "], $zero \n\t" \ + "srl %[" #R "], %[" #R "], 24 \n\t" \ + "srl %[" #G "], %[" #G "], 24 \n\t" \ + "srl %[" #B "], %[" #B "], 24 \n\t" \ + : [R]"+r"(R), [G]"+r"(G), [B]"+r"(B) \ + : \ + ); \ + } while (0) + +#if !defined(WEBP_REDUCE_CSP) +static WEBP_INLINE void YuvToRgb(int y, int u, int v, uint8_t* const rgb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; +} +static WEBP_INLINE void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + bgr[0] = b; + bgr[1] = g; + bgr[2] = r; +} +static WEBP_INLINE void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + { + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#if (WEBP_SWAP_16BIT_CSP == 1) + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif + } +} +static WEBP_INLINE void YuvToRgba4444(int y, int u, int v, + uint8_t* const argb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + { + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#if (WEBP_SWAP_16BIT_CSP == 1) + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif + } +} +#endif // WEBP_REDUCE_CSP + +//----------------------------------------------------------------------------- +// Alpha handling variants + +#if !defined(WEBP_REDUCE_CSP) +static WEBP_INLINE void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const argb) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + argb[0] = 0xff; + argb[1] = r; + argb[2] = g; + argb[3] = b; +} +#endif // WEBP_REDUCE_CSP +static WEBP_INLINE void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const bgra) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + bgra[0] = b; + bgra[1] = g; + bgra[2] = r; + bgra[3] = 0xff; +} +static WEBP_INLINE void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const rgba) { + int r, g, b; + YUV_TO_RGB(y, u, v, r, g, b); + rgba[0] = r; + rgba[1] = g; + rgba[2] = b; + rgba[3] = 0xff; +} + +//------------------------------------------------------------------------------ +// Fancy upsampler + +#ifdef FANCY_UPSAMPLING + +// Given samples laid out in a square as: +// [a b] +// [c d] +// we interpolate u/v as: +// ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 +// ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 + +// We process u and v together stashed into 32bit (16bit each). +#define LOAD_UV(u, v) ((u) | ((v) << 16)) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int x; \ + const int last_pixel_pair = (len - 1) >> 1; \ + uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \ + uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \ + assert(top_y != NULL); \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \ + } \ + for (x = 1; x <= last_pixel_pair; ++x) { \ + const uint32_t t_uv = LOAD_UV(top_u[x], top_v[x]); /* top sample */ \ + const uint32_t uv = LOAD_UV(cur_u[x], cur_v[x]); /* sample */ \ + /* precompute invariant values associated with first and second diagonals*/\ + const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \ + const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \ + const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \ + { \ + const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \ + const uint32_t uv1 = (diag_03 + t_uv) >> 1; \ + FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (2 * x - 1) * XSTEP); \ + FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \ + top_dst + (2 * x - 0) * XSTEP); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (diag_03 + l_uv) >> 1; \ + const uint32_t uv1 = (diag_12 + uv) >> 1; \ + FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (2 * x - 1) * XSTEP); \ + FUNC(bottom_y[2 * x + 0], uv1 & 0xff, (uv1 >> 16), \ + bottom_dst + (2 * x + 0) * XSTEP); \ + } \ + tl_uv = t_uv; \ + l_uv = uv; \ + } \ + if (!(len & 1)) { \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (len - 1) * XSTEP); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (len - 1) * XSTEP); \ + } \ + } \ +} + +// All variants implemented. +UPSAMPLE_FUNC(UpsampleRgbaLinePair, YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair, YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +UPSAMPLE_FUNC(UpsampleRgbLinePair, YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair, YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleArgbLinePair, YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair, YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair, YuvToRgb565, 2) +#endif // WEBP_REDUCE_CSP + +#undef LOAD_UV +#undef UPSAMPLE_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitUpsamplersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersMIPSdspR2(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ +// YUV444 converter + +#define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * XSTEP]); \ +} + +YUV444_FUNC(Yuv444ToRgba, YuvToRgba, 4) +YUV444_FUNC(Yuv444ToBgra, YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb, YuvToRgb, 3) +YUV444_FUNC(Yuv444ToBgr, YuvToBgr, 3) +YUV444_FUNC(Yuv444ToArgb, YuvToArgb, 4) +YUV444_FUNC(Yuv444ToRgba4444, YuvToRgba4444, 2) +YUV444_FUNC(Yuv444ToRgb565, YuvToRgb565, 2) +#endif // WEBP_REDUCE_CSP + +#undef YUV444_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitYUV444ConvertersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersMIPSdspR2(void) { + WebPYUV444Converters[MODE_RGBA] = Yuv444ToRgba; + WebPYUV444Converters[MODE_BGRA] = Yuv444ToBgra; + WebPYUV444Converters[MODE_rgbA] = Yuv444ToRgba; + WebPYUV444Converters[MODE_bgrA] = Yuv444ToBgra; +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr; + WebPYUV444Converters[MODE_ARGB] = Yuv444ToArgb; + WebPYUV444Converters[MODE_RGBA_4444] = Yuv444ToRgba4444; + WebPYUV444Converters[MODE_RGB_565] = Yuv444ToRgb565; + WebPYUV444Converters[MODE_Argb] = Yuv444ToArgb; + WebPYUV444Converters[MODE_rgbA_4444] = Yuv444ToRgba4444; +#endif // WEBP_REDUCE_CSP +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_MIPS_DSP_R2)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersMIPSdspR2) +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_msa.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_msa.c new file mode 100644 index 0000000000..f2e03e85e9 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_msa.c @@ -0,0 +1,688 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MSA version of YUV to RGB upsampling functions. +// +// Author: Prashant Patil (prashant.patil@imgtec.com) + +#include +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MSA) + +#include "src/dsp/msa_macro.h" +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +#define ILVR_UW2(in, out0, out1) do { \ + const v8i16 t0 = (v8i16)__msa_ilvr_b((v16i8)zero, (v16i8)in); \ + out0 = (v4u32)__msa_ilvr_h((v8i16)zero, t0); \ + out1 = (v4u32)__msa_ilvl_h((v8i16)zero, t0); \ +} while (0) + +#define ILVRL_UW4(in, out0, out1, out2, out3) do { \ + v16u8 t0, t1; \ + ILVRL_B2_UB(zero, in, t0, t1); \ + ILVRL_H2_UW(zero, t0, out0, out1); \ + ILVRL_H2_UW(zero, t1, out2, out3); \ +} while (0) + +#define MULTHI_16(in0, in1, in2, in3, cnst, out0, out1) do { \ + const v4i32 const0 = (v4i32)__msa_fill_w(cnst * 256); \ + v4u32 temp0, temp1, temp2, temp3; \ + MUL4(in0, const0, in1, const0, in2, const0, in3, const0, \ + temp0, temp1, temp2, temp3); \ + PCKOD_H2_UH(temp1, temp0, temp3, temp2, out0, out1); \ +} while (0) + +#define MULTHI_8(in0, in1, cnst, out0) do { \ + const v4i32 const0 = (v4i32)__msa_fill_w(cnst * 256); \ + v4u32 temp0, temp1; \ + MUL2(in0, const0, in1, const0, temp0, temp1); \ + out0 = (v8u16)__msa_pckod_h((v8i16)temp1, (v8i16)temp0); \ +} while (0) + +#define CALC_R16(y0, y1, v0, v1, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(14234); \ + const v8i16 a0 = __msa_adds_s_h((v8i16)y0, (v8i16)v0); \ + const v8i16 a1 = __msa_adds_s_h((v8i16)y1, (v8i16)v1); \ + v8i16 b0 = __msa_subs_s_h(a0, const_a); \ + v8i16 b1 = __msa_subs_s_h(a1, const_a); \ + SRAI_H2_SH(b0, b1, 6); \ + CLIP_SH2_0_255(b0, b1); \ + dst = (v16u8)__msa_pckev_b((v16i8)b1, (v16i8)b0); \ +} while (0) + +#define CALC_R8(y0, v0, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(14234); \ + const v8i16 a0 = __msa_adds_s_h((v8i16)y0, (v8i16)v0); \ + v8i16 b0 = __msa_subs_s_h(a0, const_a); \ + b0 = SRAI_H(b0, 6); \ + CLIP_SH_0_255(b0); \ + dst = (v16u8)__msa_pckev_b((v16i8)b0, (v16i8)b0); \ +} while (0) + +#define CALC_G16(y0, y1, u0, u1, v0, v1, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(8708); \ + v8i16 a0 = __msa_subs_s_h((v8i16)y0, (v8i16)u0); \ + v8i16 a1 = __msa_subs_s_h((v8i16)y1, (v8i16)u1); \ + const v8i16 b0 = __msa_subs_s_h(a0, (v8i16)v0); \ + const v8i16 b1 = __msa_subs_s_h(a1, (v8i16)v1); \ + a0 = __msa_adds_s_h(b0, const_a); \ + a1 = __msa_adds_s_h(b1, const_a); \ + SRAI_H2_SH(a0, a1, 6); \ + CLIP_SH2_0_255(a0, a1); \ + dst = (v16u8)__msa_pckev_b((v16i8)a1, (v16i8)a0); \ +} while (0) + +#define CALC_G8(y0, u0, v0, dst) do { \ + const v8i16 const_a = (v8i16)__msa_fill_h(8708); \ + v8i16 a0 = __msa_subs_s_h((v8i16)y0, (v8i16)u0); \ + const v8i16 b0 = __msa_subs_s_h(a0, (v8i16)v0); \ + a0 = __msa_adds_s_h(b0, const_a); \ + a0 = SRAI_H(a0, 6); \ + CLIP_SH_0_255(a0); \ + dst = (v16u8)__msa_pckev_b((v16i8)a0, (v16i8)a0); \ +} while (0) + +#define CALC_B16(y0, y1, u0, u1, dst) do { \ + const v8u16 const_a = (v8u16)__msa_fill_h(17685); \ + const v8u16 a0 = __msa_adds_u_h((v8u16)y0, u0); \ + const v8u16 a1 = __msa_adds_u_h((v8u16)y1, u1); \ + v8u16 b0 = __msa_subs_u_h(a0, const_a); \ + v8u16 b1 = __msa_subs_u_h(a1, const_a); \ + SRAI_H2_UH(b0, b1, 6); \ + CLIP_UH2_0_255(b0, b1); \ + dst = (v16u8)__msa_pckev_b((v16i8)b1, (v16i8)b0); \ +} while (0) + +#define CALC_B8(y0, u0, dst) do { \ + const v8u16 const_a = (v8u16)__msa_fill_h(17685); \ + const v8u16 a0 = __msa_adds_u_h((v8u16)y0, u0); \ + v8u16 b0 = __msa_subs_u_h(a0, const_a); \ + b0 = SRAI_H(b0, 6); \ + CLIP_UH_0_255(b0); \ + dst = (v16u8)__msa_pckev_b((v16i8)b0, (v16i8)b0); \ +} while (0) + +#define CALC_RGB16(y, u, v, R, G, B) do { \ + const v16u8 zero = { 0 }; \ + v8u16 y0, y1, u0, u1, v0, v1; \ + v4u32 p0, p1, p2, p3; \ + const v16u8 in_y = LD_UB(y); \ + const v16u8 in_u = LD_UB(u); \ + const v16u8 in_v = LD_UB(v); \ + ILVRL_UW4(in_y, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 19077, y0, y1); \ + ILVRL_UW4(in_v, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 26149, v0, v1); \ + CALC_R16(y0, y1, v0, v1, R); \ + MULTHI_16(p0, p1, p2, p3, 13320, v0, v1); \ + ILVRL_UW4(in_u, p0, p1, p2, p3); \ + MULTHI_16(p0, p1, p2, p3, 6419, u0, u1); \ + CALC_G16(y0, y1, u0, u1, v0, v1, G); \ + MULTHI_16(p0, p1, p2, p3, 33050, u0, u1); \ + CALC_B16(y0, y1, u0, u1, B); \ +} while (0) + +#define CALC_RGB8(y, u, v, R, G, B) do { \ + const v16u8 zero = { 0 }; \ + v8u16 y0, u0, v0; \ + v4u32 p0, p1; \ + const v16u8 in_y = LD_UB(y); \ + const v16u8 in_u = LD_UB(u); \ + const v16u8 in_v = LD_UB(v); \ + ILVR_UW2(in_y, p0, p1); \ + MULTHI_8(p0, p1, 19077, y0); \ + ILVR_UW2(in_v, p0, p1); \ + MULTHI_8(p0, p1, 26149, v0); \ + CALC_R8(y0, v0, R); \ + MULTHI_8(p0, p1, 13320, v0); \ + ILVR_UW2(in_u, p0, p1); \ + MULTHI_8(p0, p1, 6419, u0); \ + CALC_G8(y0, u0, v0, G); \ + MULTHI_8(p0, p1, 33050, u0); \ + CALC_B8(y0, u0, B); \ +} while (0) + +#define STORE16_3(a0, a1, a2, dst) do { \ + const v16u8 mask0 = { 0, 1, 16, 2, 3, 17, 4, 5, 18, 6, 7, 19, \ + 8, 9, 20, 10 }; \ + const v16u8 mask1 = { 0, 21, 1, 2, 22, 3, 4, 23, 5, 6, 24, 7, \ + 8, 25, 9, 10 }; \ + const v16u8 mask2 = { 26, 0, 1, 27, 2, 3, 28, 4, 5, 29, 6, 7, \ + 30, 8, 9, 31 }; \ + v16u8 out0, out1, out2, tmp0, tmp1, tmp2; \ + ILVRL_B2_UB(a1, a0, tmp0, tmp1); \ + out0 = VSHF_UB(tmp0, a2, mask0); \ + tmp2 = SLDI_UB(tmp1, tmp0, 11); \ + out1 = VSHF_UB(tmp2, a2, mask1); \ + tmp2 = SLDI_UB(tmp1, tmp1, 6); \ + out2 = VSHF_UB(tmp2, a2, mask2); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ + ST_UB(out2, dst + 32); \ +} while (0) + +#define STORE8_3(a0, a1, a2, dst) do { \ + int64_t out_m; \ + const v16u8 mask0 = { 0, 1, 16, 2, 3, 17, 4, 5, 18, 6, 7, 19, \ + 8, 9, 20, 10 }; \ + const v16u8 mask1 = { 11, 21, 12, 13, 22, 14, 15, 23, \ + 255, 255, 255, 255, 255, 255, 255, 255 }; \ + const v16u8 tmp0 = (v16u8)__msa_ilvr_b((v16i8)a1, (v16i8)a0); \ + v16u8 out0, out1; \ + VSHF_B2_UB(tmp0, a2, tmp0, a2, mask0, mask1, out0, out1); \ + ST_UB(out0, dst); \ + out_m = __msa_copy_s_d((v2i64)out1, 0); \ + SD(out_m, dst + 16); \ +} while (0) + +#define STORE16_4(a0, a1, a2, a3, dst) do { \ + v16u8 tmp0, tmp1, tmp2, tmp3; \ + v16u8 out0, out1, out2, out3; \ + ILVRL_B2_UB(a1, a0, tmp0, tmp1); \ + ILVRL_B2_UB(a3, a2, tmp2, tmp3); \ + ILVRL_H2_UB(tmp2, tmp0, out0, out1); \ + ILVRL_H2_UB(tmp3, tmp1, out2, out3); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ + ST_UB(out2, dst + 32); \ + ST_UB(out3, dst + 48); \ +} while (0) + +#define STORE8_4(a0, a1, a2, a3, dst) do { \ + v16u8 tmp0, tmp1, tmp2, tmp3; \ + ILVR_B2_UB(a1, a0, a3, a2, tmp0, tmp1); \ + ILVRL_H2_UB(tmp1, tmp0, tmp2, tmp3); \ + ST_UB(tmp2, dst + 0); \ + ST_UB(tmp3, dst + 16); \ +} while (0) + +#define STORE2_16(a0, a1, dst) do { \ + v16u8 out0, out1; \ + ILVRL_B2_UB(a1, a0, out0, out1); \ + ST_UB(out0, dst + 0); \ + ST_UB(out1, dst + 16); \ +} while (0) + +#define STORE2_8(a0, a1, dst) do { \ + const v16u8 out0 = (v16u8)__msa_ilvr_b((v16i8)a1, (v16i8)a0); \ + ST_UB(out0, dst); \ +} while (0) + +#define CALC_RGBA4444(y, u, v, out0, out1, N, dst) do { \ + CALC_RGB##N(y, u, v, R, G, B); \ + tmp0 = ANDI_B(R, 0xf0); \ + tmp1 = SRAI_B(G, 4); \ + RG = tmp0 | tmp1; \ + tmp0 = ANDI_B(B, 0xf0); \ + BA = ORI_B(tmp0, 0x0f); \ + STORE2_##N(out0, out1, dst); \ +} while (0) + +#define CALC_RGB565(y, u, v, out0, out1, N, dst) do { \ + CALC_RGB##N(y, u, v, R, G, B); \ + tmp0 = ANDI_B(R, 0xf8); \ + tmp1 = SRAI_B(G, 5); \ + RG = tmp0 | tmp1; \ + tmp0 = SLLI_B(G, 3); \ + tmp1 = ANDI_B(tmp0, 0xe0); \ + tmp0 = SRAI_B(B, 3); \ + GB = tmp0 | tmp1; \ + STORE2_##N(out0, out1, dst); \ +} while (0) + +static WEBP_INLINE int Clip8(int v) { + return v < 0 ? 0 : v > 255 ? 255 : v; +} + +static void YuvToRgb(int y, int u, int v, uint8_t* const rgb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + rgb[0] = Clip8(r1 >> 6); + rgb[1] = Clip8(g1 >> 6); + rgb[2] = Clip8(b1 >> 6); +} + +static void YuvToBgr(int y, int u, int v, uint8_t* const bgr) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + bgr[0] = Clip8(b1 >> 6); + bgr[1] = Clip8(g1 >> 6); + bgr[2] = Clip8(r1 >> 6); +} + +#if !defined(WEBP_REDUCE_CSP) +static void YuvToRgb565(int y, int u, int v, uint8_t* const rgb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + const int r = Clip8(r1 >> 6); + const int g = Clip8(g1 >> 6); + const int b = Clip8(b1 >> 6); + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#if (WEBP_SWAP_16BIT_CSP == 1) + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif +} + +static void YuvToRgba4444(int y, int u, int v, uint8_t* const argb) { + const int y1 = MultHi(y, 19077); + const int r1 = y1 + MultHi(v, 26149) - 14234; + const int g1 = y1 - MultHi(u, 6419) - MultHi(v, 13320) + 8708; + const int b1 = y1 + MultHi(u, 33050) - 17685; + const int r = Clip8(r1 >> 6); + const int g = Clip8(g1 >> 6); + const int b = Clip8(b1 >> 6); + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#if (WEBP_SWAP_16BIT_CSP == 1) + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif +} + +static void YuvToArgb(uint8_t y, uint8_t u, uint8_t v, uint8_t* const argb) { + argb[0] = 0xff; + YuvToRgb(y, u, v, argb + 1); +} +#endif // WEBP_REDUCE_CSP + +static void YuvToBgra(uint8_t y, uint8_t u, uint8_t v, uint8_t* const bgra) { + YuvToBgr(y, u, v, bgra); + bgra[3] = 0xff; +} + +static void YuvToRgba(uint8_t y, uint8_t u, uint8_t v, uint8_t* const rgba) { + YuvToRgb(y, u, v, rgba); + rgba[3] = 0xff; +} + +#if !defined(WEBP_REDUCE_CSP) +static void YuvToRgbLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_3(R, G, B, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 3; + length -= 16; + } + if (length > 8) { + uint8_t temp[3 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_3(R, G, B, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[3 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_3(R, G, B, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } +} + +static void YuvToBgrLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_3(B, G, R, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 3; + length -= 16; + } + if (length > 8) { + uint8_t temp[3 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_3(B, G, R, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[3 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_3(B, G, R, temp); + memcpy(dst, temp, length * 3 * sizeof(*dst)); + } +} +#endif // WEBP_REDUCE_CSP + +static void YuvToRgbaLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(ALPHAVAL); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(R, G, B, A, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(&temp[0], u, v, R, G, B); + STORE16_4(R, G, B, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(R, G, B, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +static void YuvToBgraLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(ALPHAVAL); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(B, G, R, A, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_4(B, G, R, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(B, G, R, A, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +#if !defined(WEBP_REDUCE_CSP) +static void YuvToArgbLine(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B; + const v16u8 A = (v16u8)__msa_ldi_b(ALPHAVAL); + while (length >= 16) { + CALC_RGB16(y, u, v, R, G, B); + STORE16_4(A, R, G, B, dst); + y += 16; + u += 16; + v += 16; + dst += 16 * 4; + length -= 16; + } + if (length > 8) { + uint8_t temp[4 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB16(temp, u, v, R, G, B); + STORE16_4(A, R, G, B, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[4 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); + CALC_RGB8(temp, u, v, R, G, B); + STORE8_4(A, R, G, B, temp); + memcpy(dst, temp, length * 4 * sizeof(*dst)); + } +} + +static void YuvToRgba4444Line(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B, RG, BA, tmp0, tmp1; + while (length >= 16) { +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGBA4444(y, u, v, BA, RG, 16, dst); +#else + CALC_RGBA4444(y, u, v, RG, BA, 16, dst); +#endif + y += 16; + u += 16; + v += 16; + dst += 16 * 2; + length -= 16; + } + if (length > 8) { + uint8_t temp[2 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGBA4444(temp, u, v, BA, RG, 16, temp); +#else + CALC_RGBA4444(temp, u, v, RG, BA, 16, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[2 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGBA4444(temp, u, v, BA, RG, 8, temp); +#else + CALC_RGBA4444(temp, u, v, RG, BA, 8, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } +} + +static void YuvToRgb565Line(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst, int length) { + v16u8 R, G, B, RG, GB, tmp0, tmp1; + while (length >= 16) { +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGB565(y, u, v, GB, RG, 16, dst); +#else + CALC_RGB565(y, u, v, RG, GB, 16, dst); +#endif + y += 16; + u += 16; + v += 16; + dst += 16 * 2; + length -= 16; + } + if (length > 8) { + uint8_t temp[2 * 16] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGB565(temp, u, v, GB, RG, 16, temp); +#else + CALC_RGB565(temp, u, v, RG, GB, 16, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } else if (length > 0) { + uint8_t temp[2 * 8] = { 0 }; + memcpy(temp, y, length * sizeof(*temp)); +#if (WEBP_SWAP_16BIT_CSP == 1) + CALC_RGB565(temp, u, v, GB, RG, 8, temp); +#else + CALC_RGB565(temp, u, v, RG, GB, 8, temp); +#endif + memcpy(dst, temp, length * 2 * sizeof(*dst)); + } +} +#endif // WEBP_REDUCE_CSP + +#define UPSAMPLE_32PIXELS(a, b, c, d) do { \ + v16u8 s = __msa_aver_u_b(a, d); \ + v16u8 t = __msa_aver_u_b(b, c); \ + const v16u8 st = s ^ t; \ + v16u8 ad = a ^ d; \ + v16u8 bc = b ^ c; \ + v16u8 t0 = ad | bc; \ + v16u8 t1 = t0 | st; \ + v16u8 t2 = ANDI_B(t1, 1); \ + v16u8 t3 = __msa_aver_u_b(s, t); \ + const v16u8 k = t3 - t2; \ + v16u8 diag1, diag2; \ + AVER_UB2_UB(t, k, s, k, t0, t1); \ + bc = bc & st; \ + ad = ad & st; \ + t = t ^ k; \ + s = s ^ k; \ + t2 = bc | t; \ + t3 = ad | s; \ + t2 = ANDI_B(t2, 1); \ + t3 = ANDI_B(t3, 1); \ + SUB2(t0, t2, t1, t3, diag1, diag2); \ + AVER_UB2_UB(a, diag1, b, diag2, t0, t1); \ + ILVRL_B2_UB(t1, t0, a, b); \ + if (pbot_y != NULL) { \ + AVER_UB2_UB(c, diag2, d, diag1, t0, t1); \ + ILVRL_B2_UB(t1, t0, c, d); \ + } \ +} while (0) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bot_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bot_dst, int len) \ +{ \ + int size = (len - 1) >> 1; \ + uint8_t temp_u[64]; \ + uint8_t temp_v[64]; \ + const uint32_t tl_uv = ((top_u[0]) | ((top_v[0]) << 16)); \ + const uint32_t l_uv = ((cur_u[0]) | ((cur_v[0]) << 16)); \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + const uint8_t* ptop_y = &top_y[1]; \ + uint8_t* ptop_dst = top_dst + XSTEP; \ + const uint8_t* pbot_y = &bot_y[1]; \ + uint8_t* pbot_dst = bot_dst + XSTEP; \ + \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + if (bot_y != NULL) { \ + const uint32_t uv1 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bot_y[0], uv1 & 0xff, (uv1 >> 16), bot_dst); \ + } \ + while (size >= 16) { \ + v16u8 tu0, tu1, tv0, tv1, cu0, cu1, cv0, cv1; \ + LD_UB2(top_u, 1, tu0, tu1); \ + LD_UB2(cur_u, 1, cu0, cu1); \ + LD_UB2(top_v, 1, tv0, tv1); \ + LD_UB2(cur_v, 1, cv0, cv1); \ + UPSAMPLE_32PIXELS(tu0, tu1, cu0, cu1); \ + UPSAMPLE_32PIXELS(tv0, tv1, cv0, cv1); \ + ST_UB4(tu0, tu1, cu0, cu1, &temp_u[0], 16); \ + ST_UB4(tv0, tv1, cv0, cv1, &temp_v[0], 16); \ + FUNC##Line(ptop_y, &temp_u[ 0], &temp_v[0], ptop_dst, 32); \ + if (bot_y != NULL) { \ + FUNC##Line(pbot_y, &temp_u[32], &temp_v[32], pbot_dst, 32); \ + } \ + ptop_y += 32; \ + pbot_y += 32; \ + ptop_dst += XSTEP * 32; \ + pbot_dst += XSTEP * 32; \ + top_u += 16; \ + top_v += 16; \ + cur_u += 16; \ + cur_v += 16; \ + size -= 16; \ + } \ + if (size > 0) { \ + v16u8 tu0, tu1, tv0, tv1, cu0, cu1, cv0, cv1; \ + memcpy(&temp_u[ 0], top_u, 17 * sizeof(uint8_t)); \ + memcpy(&temp_u[32], cur_u, 17 * sizeof(uint8_t)); \ + memcpy(&temp_v[ 0], top_v, 17 * sizeof(uint8_t)); \ + memcpy(&temp_v[32], cur_v, 17 * sizeof(uint8_t)); \ + LD_UB2(&temp_u[ 0], 1, tu0, tu1); \ + LD_UB2(&temp_u[32], 1, cu0, cu1); \ + LD_UB2(&temp_v[ 0], 1, tv0, tv1); \ + LD_UB2(&temp_v[32], 1, cv0, cv1); \ + UPSAMPLE_32PIXELS(tu0, tu1, cu0, cu1); \ + UPSAMPLE_32PIXELS(tv0, tv1, cv0, cv1); \ + ST_UB4(tu0, tu1, cu0, cu1, &temp_u[0], 16); \ + ST_UB4(tv0, tv1, cv0, cv1, &temp_v[0], 16); \ + FUNC##Line(ptop_y, &temp_u[ 0], &temp_v[0], ptop_dst, size * 2); \ + if (bot_y != NULL) { \ + FUNC##Line(pbot_y, &temp_u[32], &temp_v[32], pbot_dst, size * 2); \ + } \ + top_u += size; \ + top_v += size; \ + cur_u += size; \ + cur_v += size; \ + } \ + if (!(len & 1)) { \ + const uint32_t t0 = ((top_u[0]) | ((top_v[0]) << 16)); \ + const uint32_t c0 = ((cur_u[0]) | ((cur_v[0]) << 16)); \ + const uint32_t tmp0 = (3 * t0 + c0 + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], tmp0 & 0xff, (tmp0 >> 16), \ + top_dst + (len - 1) * XSTEP); \ + if (bot_y != NULL) { \ + const uint32_t tmp1 = (3 * c0 + t0 + 0x00020002u) >> 2; \ + FUNC(bot_y[len - 1], tmp1 & 0xff, (tmp1 >> 16), \ + bot_dst + (len - 1) * XSTEP); \ + } \ + } \ +} + +UPSAMPLE_FUNC(UpsampleRgbaLinePair, YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair, YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +UPSAMPLE_FUNC(UpsampleRgbLinePair, YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair, YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleArgbLinePair, YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair, YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair, YuvToRgb565, 2) +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersMSA(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersMSA(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +#endif // WEBP_USE_MSA + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_MSA)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersMSA) +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_neon.c new file mode 100644 index 0000000000..bbc000ca2d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_neon.c @@ -0,0 +1,285 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON version of YUV to RGB upsampling functions. +// +// Author: mans@mansr.com (Mans Rullgard) +// Based on SSE code by: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include +#include +#include "src/dsp/neon.h" +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +//----------------------------------------------------------------------------- +// U/V upsampling + +// Loads 9 pixels each from rows r1 and r2 and generates 16 pixels. +#define UPSAMPLE_16PIXELS(r1, r2, out) do { \ + const uint8x8_t a = vld1_u8(r1 + 0); \ + const uint8x8_t b = vld1_u8(r1 + 1); \ + const uint8x8_t c = vld1_u8(r2 + 0); \ + const uint8x8_t d = vld1_u8(r2 + 1); \ + /* a + b + c + d */ \ + const uint16x8_t ad = vaddl_u8(a, d); \ + const uint16x8_t bc = vaddl_u8(b, c); \ + const uint16x8_t abcd = vaddq_u16(ad, bc); \ + /* 3a + b + c + 3d */ \ + const uint16x8_t al = vaddq_u16(abcd, vshlq_n_u16(ad, 1)); \ + /* a + 3b + 3c + d */ \ + const uint16x8_t bl = vaddq_u16(abcd, vshlq_n_u16(bc, 1)); \ + \ + const uint8x8_t diag2 = vshrn_n_u16(al, 3); \ + const uint8x8_t diag1 = vshrn_n_u16(bl, 3); \ + \ + const uint8x8_t A = vrhadd_u8(a, diag1); \ + const uint8x8_t B = vrhadd_u8(b, diag2); \ + const uint8x8_t C = vrhadd_u8(c, diag2); \ + const uint8x8_t D = vrhadd_u8(d, diag1); \ + \ + uint8x8x2_t A_B, C_D; \ + INIT_VECTOR2(A_B, A, B); \ + INIT_VECTOR2(C_D, C, D); \ + vst2_u8(out + 0, A_B); \ + vst2_u8(out + 32, C_D); \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample16Pixels_NEON(const uint8_t* r1, const uint8_t* r2, + uint8_t* out) { + UPSAMPLE_16PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[9], r2[9]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 9 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 9 - (num_pixels)); \ + Upsample16Pixels_NEON(r1, r2, out); \ +} + +//----------------------------------------------------------------------------- +// YUV->RGB conversion + +// note: we represent the 33050 large constant as 32768 + 282 +static const int16_t kCoeffs1[4] = { 19077, 26149, 6419, 13320 }; + +#define v255 vdup_n_u8(255) + +#define STORE_Rgb(out, r, g, b) do { \ + uint8x8x3_t r_g_b; \ + INIT_VECTOR3(r_g_b, r, g, b); \ + vst3_u8(out, r_g_b); \ +} while (0) + +#define STORE_Bgr(out, r, g, b) do { \ + uint8x8x3_t b_g_r; \ + INIT_VECTOR3(b_g_r, b, g, r); \ + vst3_u8(out, b_g_r); \ +} while (0) + +#define STORE_Rgba(out, r, g, b) do { \ + uint8x8x4_t r_g_b_v255; \ + INIT_VECTOR4(r_g_b_v255, r, g, b, v255); \ + vst4_u8(out, r_g_b_v255); \ +} while (0) + +#define STORE_Bgra(out, r, g, b) do { \ + uint8x8x4_t b_g_r_v255; \ + INIT_VECTOR4(b_g_r_v255, b, g, r, v255); \ + vst4_u8(out, b_g_r_v255); \ +} while (0) + +#define STORE_Argb(out, r, g, b) do { \ + uint8x8x4_t v255_r_g_b; \ + INIT_VECTOR4(v255_r_g_b, v255, r, g, b); \ + vst4_u8(out, v255_r_g_b); \ +} while (0) + +#if (WEBP_SWAP_16BIT_CSP == 0) +#define ZIP_U8(lo, hi) vzip_u8((lo), (hi)) +#else +#define ZIP_U8(lo, hi) vzip_u8((hi), (lo)) +#endif + +#define STORE_Rgba4444(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 4); /* shift g, insert r */ \ + const uint8x8_t ba = vsri_n_u8(b, v255, 4); /* shift a, insert b */ \ + const uint8x8x2_t rgba4444 = ZIP_U8(rg, ba); \ + vst1q_u8(out, vcombine_u8(rgba4444.val[0], rgba4444.val[1])); \ +} while (0) + +#define STORE_Rgb565(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 5); /* shift g and insert r */ \ + const uint8x8_t g1 = vshl_n_u8(g, 3); /* pre-shift g: 3bits */ \ + const uint8x8_t gb = vsri_n_u8(g1, b, 3); /* shift b and insert g */ \ + const uint8x8x2_t rgb565 = ZIP_U8(rg, gb); \ + vst1q_u8(out, vcombine_u8(rgb565.val[0], rgb565.val[1])); \ +} while (0) + +#define CONVERT8(FMT, XSTEP, N, src_y, src_uv, out, cur_x) do { \ + int i; \ + for (i = 0; i < N; i += 8) { \ + const int off = ((cur_x) + i) * XSTEP; \ + const uint8x8_t y = vld1_u8((src_y) + (cur_x) + i); \ + const uint8x8_t u = vld1_u8((src_uv) + i + 0); \ + const uint8x8_t v = vld1_u8((src_uv) + i + 16); \ + const int16x8_t Y0 = vreinterpretq_s16_u16(vshll_n_u8(y, 7)); \ + const int16x8_t U0 = vreinterpretq_s16_u16(vshll_n_u8(u, 7)); \ + const int16x8_t V0 = vreinterpretq_s16_u16(vshll_n_u8(v, 7)); \ + const int16x8_t Y1 = vqdmulhq_lane_s16(Y0, coeff1, 0); \ + const int16x8_t R0 = vqdmulhq_lane_s16(V0, coeff1, 1); \ + const int16x8_t G0 = vqdmulhq_lane_s16(U0, coeff1, 2); \ + const int16x8_t G1 = vqdmulhq_lane_s16(V0, coeff1, 3); \ + const int16x8_t B0 = vqdmulhq_n_s16(U0, 282); \ + const int16x8_t R1 = vqaddq_s16(Y1, R_Rounder); \ + const int16x8_t G2 = vqaddq_s16(Y1, G_Rounder); \ + const int16x8_t B1 = vqaddq_s16(Y1, B_Rounder); \ + const int16x8_t R2 = vqaddq_s16(R0, R1); \ + const int16x8_t G3 = vqaddq_s16(G0, G1); \ + const int16x8_t B2 = vqaddq_s16(B0, B1); \ + const int16x8_t G4 = vqsubq_s16(G2, G3); \ + const int16x8_t B3 = vqaddq_s16(B2, U0); \ + const uint8x8_t R = vqshrun_n_s16(R2, YUV_FIX2); \ + const uint8x8_t G = vqshrun_n_s16(G4, YUV_FIX2); \ + const uint8x8_t B = vqshrun_n_s16(B3, YUV_FIX2); \ + STORE_ ## FMT(out + off, R, G, B); \ + } \ +} while (0) + +#define CONVERT1(FUNC, XSTEP, N, src_y, src_uv, rgb, cur_x) { \ + int i; \ + for (i = 0; i < N; i++) { \ + const int off = ((cur_x) + i) * XSTEP; \ + const int y = src_y[(cur_x) + i]; \ + const int u = (src_uv)[i]; \ + const int v = (src_uv)[i + 16]; \ + FUNC(y, u, v, rgb + off); \ + } \ +} + +#define CONVERT2RGB_8(FMT, XSTEP, top_y, bottom_y, uv, \ + top_dst, bottom_dst, cur_x, len) { \ + CONVERT8(FMT, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT8(FMT, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ + } \ +} + +#define CONVERT2RGB_1(FUNC, XSTEP, top_y, bottom_y, uv, \ + top_dst, bottom_dst, cur_x, len) { \ + CONVERT1(FUNC, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT1(FUNC, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ + } \ +} + +#define NEON_UPSAMPLE_FUNC(FUNC_NAME, FMT, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int block; \ + /* 16 byte aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[2 * 32 + 15]; \ + uint8_t* const r_uv = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + const int uv_len = (len + 1) >> 1; \ + /* 9 pixels must be read-able for each block */ \ + const int num_blocks = (uv_len - 1) >> 3; \ + const int leftover = uv_len - num_blocks * 8; \ + const int last_pos = 1 + 16 * num_blocks; \ + \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + \ + const int16x4_t coeff1 = vld1_s16(kCoeffs1); \ + const int16x8_t R_Rounder = vdupq_n_s16(-14234); \ + const int16x8_t G_Rounder = vdupq_n_s16(8708); \ + const int16x8_t B_Rounder = vdupq_n_s16(-17685); \ + \ + /* Treat the first pixel in regular way */ \ + assert(top_y != NULL); \ + { \ + const int u0 = (top_u[0] + u_diag) >> 1; \ + const int v0 = (top_v[0] + v_diag) >> 1; \ + VP8YuvTo ## FMT(top_y[0], u0, v0, top_dst); \ + } \ + if (bottom_y != NULL) { \ + const int u0 = (cur_u[0] + u_diag) >> 1; \ + const int v0 = (cur_v[0] + v_diag) >> 1; \ + VP8YuvTo ## FMT(bottom_y[0], u0, v0, bottom_dst); \ + } \ + \ + for (block = 0; block < num_blocks; ++block) { \ + UPSAMPLE_16PIXELS(top_u, cur_u, r_uv); \ + UPSAMPLE_16PIXELS(top_v, cur_v, r_uv + 16); \ + CONVERT2RGB_8(FMT, XSTEP, top_y, bottom_y, r_uv, \ + top_dst, bottom_dst, 16 * block + 1, 16); \ + top_u += 8; \ + cur_u += 8; \ + top_v += 8; \ + cur_v += 8; \ + } \ + \ + UPSAMPLE_LAST_BLOCK(top_u, cur_u, leftover, r_uv); \ + UPSAMPLE_LAST_BLOCK(top_v, cur_v, leftover, r_uv + 16); \ + CONVERT2RGB_1(VP8YuvTo ## FMT, XSTEP, top_y, bottom_y, r_uv, \ + top_dst, bottom_dst, last_pos, len - last_pos); \ +} + +// NEON variants of the fancy upsampler. +NEON_UPSAMPLE_FUNC(UpsampleRgbaLinePair_NEON, Rgba, 4) +NEON_UPSAMPLE_FUNC(UpsampleBgraLinePair_NEON, Bgra, 4) +#if !defined(WEBP_REDUCE_CSP) +NEON_UPSAMPLE_FUNC(UpsampleRgbLinePair_NEON, Rgb, 3) +NEON_UPSAMPLE_FUNC(UpsampleBgrLinePair_NEON, Bgr, 3) +NEON_UPSAMPLE_FUNC(UpsampleArgbLinePair_NEON, Argb, 4) +NEON_UPSAMPLE_FUNC(UpsampleRgba4444LinePair_NEON, Rgba4444, 2) +NEON_UPSAMPLE_FUNC(UpsampleRgb565LinePair_NEON, Rgb565, 2) +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersNEON(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_NEON; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_NEON; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_NEON; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_NEON; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_NEON; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_NEON; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_NEON; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_NEON; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_NEON; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_NEON; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_NEON; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +#endif // WEBP_USE_NEON + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_NEON)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersNEON) +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse2.c new file mode 100644 index 0000000000..77b4f7221e --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse2.c @@ -0,0 +1,267 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include +#include +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) do { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE2(const uint8_t r1[], const uint8_t r2[], + uint8_t* const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE2(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE2((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE2((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE2_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~(uintptr_t)15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE2 variants of the fancy upsampler. +SSE2_UPSAMPLE_FUNC(UpsampleRgbaLinePair_SSE2, VP8YuvToRgba, 4) +SSE2_UPSAMPLE_FUNC(UpsampleBgraLinePair_SSE2, VP8YuvToBgra, 4) + +#if !defined(WEBP_REDUCE_CSP) +SSE2_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE2, VP8YuvToRgb, 3) +SSE2_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE2, VP8YuvToBgr, 3) +SSE2_UPSAMPLE_FUNC(UpsampleArgbLinePair_SSE2, VP8YuvToArgb, 4) +SSE2_UPSAMPLE_FUNC(UpsampleRgba4444LinePair_SSE2, VP8YuvToRgba4444, 2) +SSE2_UPSAMPLE_FUNC(UpsampleRgb565LinePair_SSE2, VP8YuvToRgb565, 2) +#endif // WEBP_REDUCE_CSP + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE2_UPSAMPLE_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE2(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_SSE2; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_SSE2; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_SSE2; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_SSE2; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE2; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE2; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_SSE2; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_SSE2; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_SSE2; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_SSE2; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_SSE2; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE2(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +YUV444_FUNC(Yuv444ToRgba_SSE2, VP8YuvToRgba32_SSE2, WebPYuv444ToRgba_C, 4) +YUV444_FUNC(Yuv444ToBgra_SSE2, VP8YuvToBgra32_SSE2, WebPYuv444ToBgra_C, 4) +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE2, VP8YuvToRgb32_SSE2, WebPYuv444ToRgb_C, 3) +YUV444_FUNC(Yuv444ToBgr_SSE2, VP8YuvToBgr32_SSE2, WebPYuv444ToBgr_C, 3) +YUV444_FUNC(Yuv444ToArgb_SSE2, VP8YuvToArgb32_SSE2, WebPYuv444ToArgb_C, 4) +YUV444_FUNC(Yuv444ToRgba4444_SSE2, VP8YuvToRgba444432_SSE2, \ + WebPYuv444ToRgba4444_C, 2) +YUV444_FUNC(Yuv444ToRgb565_SSE2, VP8YuvToRgb56532_SSE2, WebPYuv444ToRgb565_C, 2) +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE2(void) { + WebPYUV444Converters[MODE_RGBA] = Yuv444ToRgba_SSE2; + WebPYUV444Converters[MODE_BGRA] = Yuv444ToBgra_SSE2; + WebPYUV444Converters[MODE_rgbA] = Yuv444ToRgba_SSE2; + WebPYUV444Converters[MODE_bgrA] = Yuv444ToBgra_SSE2; +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE2; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE2; + WebPYUV444Converters[MODE_ARGB] = Yuv444ToArgb_SSE2; + WebPYUV444Converters[MODE_RGBA_4444] = Yuv444ToRgba4444_SSE2; + WebPYUV444Converters[MODE_RGB_565] = Yuv444ToRgb565_SSE2; + WebPYUV444Converters[MODE_Argb] = Yuv444ToArgb_SSE2; + WebPYUV444Converters[MODE_rgbA_4444] = Yuv444ToRgba4444_SSE2; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE2) + +#endif // WEBP_USE_SSE2 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE2)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE2) +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse41.c new file mode 100644 index 0000000000..e38c88d5e6 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/upsampling_sse41.c @@ -0,0 +1,239 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) + +#include +#include +#include +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +#if !defined(WEBP_REDUCE_CSP) + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) do { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE41(const uint8_t r1[], const uint8_t r2[], + uint8_t* const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE41(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE41((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE41((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE4_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* top_y, const uint8_t* bottom_y, \ + const uint8_t* top_u, const uint8_t* top_v, \ + const uint8_t* cur_u, const uint8_t* cur_v, \ + uint8_t* top_dst, uint8_t* bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE4 variants of the fancy upsampler. +SSE4_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE41, VP8YuvToRgb, 3) +SSE4_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE41, VP8YuvToBgr, 3) + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE4_UPSAMPLE_FUNC + +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE41; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE41(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len); \ +static void FUNC_NAME(const uint8_t* y, const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE41, VP8YuvToRgb32_SSE41, WebPYuv444ToRgb_C, 3) +YUV444_FUNC(Yuv444ToBgr_SSE41, VP8YuvToBgr32_SSE41, WebPYuv444ToBgr_C, 3) +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE41; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE41) + +#endif // WEBP_USE_SSE41 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE41)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE41) +#endif diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.c new file mode 100644 index 0000000000..8a04b85d82 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.c @@ -0,0 +1,245 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#include +#include + +//----------------------------------------------------------------------------- +// Plain-C version + +#define ROW_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + const uint8_t* const end = dst + (len & ~1) * (XSTEP); \ + while (dst != end) { \ + FUNC(y[0], u[0], v[0], dst); \ + FUNC(y[1], u[0], v[0], dst + (XSTEP)); \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * (XSTEP); \ + } \ + if (len & 1) { \ + FUNC(y[0], u[0], v[0], dst); \ + } \ +} \ + +// All variants implemented. +ROW_FUNC(YuvToRgbRow, VP8YuvToRgb, 3) +ROW_FUNC(YuvToBgrRow, VP8YuvToBgr, 3) +ROW_FUNC(YuvToRgbaRow, VP8YuvToRgba, 4) +ROW_FUNC(YuvToBgraRow, VP8YuvToBgra, 4) +ROW_FUNC(YuvToArgbRow, VP8YuvToArgb, 4) +ROW_FUNC(YuvToRgba4444Row, VP8YuvToRgba4444, 2) +ROW_FUNC(YuvToRgb565Row, VP8YuvToRgb565, 2) + +#undef ROW_FUNC + +// Main call for processing a plane with a WebPSamplerRowFunc function: +void WebPSamplerProcessPlane(const uint8_t* y, int y_stride, + const uint8_t* u, const uint8_t* v, int uv_stride, + uint8_t* dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func) { + int j; + for (j = 0; j < height; ++j) { + func(y, u, v, dst, width); + y += y_stride; + if (j & 1) { + u += uv_stride; + v += uv_stride; + } + dst += dst_stride; + } +} + +//----------------------------------------------------------------------------- +// Main call + +WebPSamplerRowFunc WebPSamplers[MODE_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitSamplersSSE2(void); +extern void WebPInitSamplersSSE41(void); +extern void WebPInitSamplersMIPS32(void); +extern void WebPInitSamplersMIPSdspR2(void); + +WEBP_DSP_INIT_FUNC(WebPInitSamplers) { + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; + WebPSamplers[MODE_ARGB] = YuvToArgbRow; + WebPSamplers[MODE_RGBA_4444] = YuvToRgba4444Row; + WebPSamplers[MODE_RGB_565] = YuvToRgb565Row; + WebPSamplers[MODE_rgbA] = YuvToRgbaRow; + WebPSamplers[MODE_bgrA] = YuvToBgraRow; + WebPSamplers[MODE_Argb] = YuvToArgbRow; + WebPSamplers[MODE_rgbA_4444] = YuvToRgba4444Row; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitSamplersSSE2(); + } +#endif // WEBP_HAVE_SSE2 +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitSamplersSSE41(); + } +#endif // WEBP_HAVE_SSE41 +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPInitSamplersMIPS32(); + } +#endif // WEBP_USE_MIPS32 +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitSamplersMIPSdspR2(); + } +#endif // WEBP_USE_MIPS_DSP_R2 + } +} + +//----------------------------------------------------------------------------- +// ARGB -> YUV converters + +static void ConvertARGBToY_C(const uint32_t* argb, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i) { + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +void WebPConvertARGBToUV_C(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store) { + // No rounding. Last pixel is dealt with separately. + const int uv_width = src_width >> 1; + int i; + for (i = 0; i < uv_width; ++i) { + const uint32_t v0 = argb[2 * i + 0]; + const uint32_t v1 = argb[2 * i + 1]; + // VP8RGBToU/V expects four accumulated pixels. Hence we need to + // scale r/g/b value by a factor 2. We just shift v0/v1 one bit less. + const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe); + const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe); + const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe); + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + // Approximated average-of-four. But it's an acceptable diff. + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } + if (src_width & 1) { // last pixel + const uint32_t v0 = argb[2 * i + 0]; + const int r = (v0 >> 14) & 0x3fc; + const int g = (v0 >> 6) & 0x3fc; + const int b = (v0 << 2) & 0x3fc; + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } +} + +//----------------------------------------------------------------------------- + +static void ConvertRGB24ToY_C(const uint8_t* rgb, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i, rgb += 3) { + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_C(const uint8_t* bgr, uint8_t* y, int width) { + int i; + for (i = 0; i < width; ++i, bgr += 3) { + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +void WebPConvertRGBA32ToUV_C(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + int i; + for (i = 0; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +//----------------------------------------------------------------------------- + +void (*WebPConvertRGB24ToY)(const uint8_t* rgb, uint8_t* y, int width); +void (*WebPConvertBGR24ToY)(const uint8_t* bgr, uint8_t* y, int width); +void (*WebPConvertRGBA32ToUV)(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width); + +void (*WebPConvertARGBToY)(const uint32_t* argb, uint8_t* y, int width); +void (*WebPConvertARGBToUV)(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store); + +extern void WebPInitConvertARGBToYUVSSE2(void); +extern void WebPInitConvertARGBToYUVSSE41(void); +extern void WebPInitConvertARGBToYUVNEON(void); + +WEBP_DSP_INIT_FUNC(WebPInitConvertARGBToYUV) { + WebPConvertARGBToY = ConvertARGBToY_C; + WebPConvertARGBToUV = WebPConvertARGBToUV_C; + + WebPConvertRGB24ToY = ConvertRGB24ToY_C; + WebPConvertBGR24ToY = ConvertBGR24ToY_C; + + WebPConvertRGBA32ToUV = WebPConvertRGBA32ToUV_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitConvertARGBToYUVSSE2(); + } +#endif // WEBP_HAVE_SSE2 +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitConvertARGBToYUVSSE41(); + } +#endif // WEBP_HAVE_SSE41 + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitConvertARGBToYUVNEON(); + } +#endif // WEBP_HAVE_NEON + + assert(WebPConvertARGBToY != NULL); + assert(WebPConvertARGBToUV != NULL); + assert(WebPConvertRGB24ToY != NULL); + assert(WebPConvertBGR24ToY != NULL); + assert(WebPConvertRGBA32ToUV != NULL); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.h new file mode 100644 index 0000000000..66a397d117 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv.h @@ -0,0 +1,210 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// inline YUV<->RGB conversion function +// +// The exact naming is Y'CbCr, following the ITU-R BT.601 standard. +// More information at: https://en.wikipedia.org/wiki/YCbCr +// Y = 0.2569 * R + 0.5044 * G + 0.0979 * B + 16 +// U = -0.1483 * R - 0.2911 * G + 0.4394 * B + 128 +// V = 0.4394 * R - 0.3679 * G - 0.0715 * B + 128 +// We use 16bit fixed point operations for RGB->YUV conversion (YUV_FIX). +// +// For the Y'CbCr to RGB conversion, the BT.601 specification reads: +// R = 1.164 * (Y-16) + 1.596 * (V-128) +// G = 1.164 * (Y-16) - 0.813 * (V-128) - 0.391 * (U-128) +// B = 1.164 * (Y-16) + 2.018 * (U-128) +// where Y is in the [16,235] range, and U/V in the [16,240] range. +// +// The fixed-point implementation used here is: +// R = (19077 . y + 26149 . v - 14234) >> 6 +// G = (19077 . y - 6419 . u - 13320 . v + 8708) >> 6 +// B = (19077 . y + 33050 . u - 17685) >> 6 +// where the '.' operator is the mulhi_epu16 variant: +// a . b = ((a << 8) * b) >> 16 +// that preserves 8 bits of fractional precision before final descaling. + +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_YUV_H_ +#define WEBP_DSP_YUV_H_ + +#include "src/dsp/dsp.h" +#include "src/dec/vp8_dec.h" + +//------------------------------------------------------------------------------ +// YUV -> RGB conversion + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + YUV_FIX = 16, // fixed-point precision for RGB->YUV + YUV_HALF = 1 << (YUV_FIX - 1), + + YUV_FIX2 = 6, // fixed-point precision for YUV->RGB + YUV_MASK2 = (256 << YUV_FIX2) - 1 +}; + +//------------------------------------------------------------------------------ +// slower on x86 by ~7-8%, but bit-exact with the SSE2/NEON version + +static WEBP_INLINE int MultHi(int v, int coeff) { // _mm_mulhi_epu16 emulation + return (v * coeff) >> 8; +} + +static WEBP_INLINE int VP8Clip8(int v) { + return ((v & ~YUV_MASK2) == 0) ? (v >> YUV_FIX2) : (v < 0) ? 0 : 255; +} + +static WEBP_INLINE int VP8YUVToR(int y, int v) { + return VP8Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); +} + +static WEBP_INLINE int VP8YUVToG(int y, int u, int v) { + return VP8Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); +} + +static WEBP_INLINE int VP8YUVToB(int y, int u) { + return VP8Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); +} + +static WEBP_INLINE void VP8YuvToRgb(int y, int u, int v, + uint8_t* const rgb) { + rgb[0] = VP8YUVToR(y, v); + rgb[1] = VP8YUVToG(y, u, v); + rgb[2] = VP8YUVToB(y, u); +} + +static WEBP_INLINE void VP8YuvToBgr(int y, int u, int v, + uint8_t* const bgr) { + bgr[0] = VP8YUVToB(y, u); + bgr[1] = VP8YUVToG(y, u, v); + bgr[2] = VP8YUVToR(y, v); +} + +static WEBP_INLINE void VP8YuvToRgb565(int y, int u, int v, + uint8_t* const rgb) { + const int r = VP8YUVToR(y, v); // 5 usable bits + const int g = VP8YUVToG(y, u, v); // 6 usable bits + const int b = VP8YUVToB(y, u); // 5 usable bits + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#if (WEBP_SWAP_16BIT_CSP == 1) + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif +} + +static WEBP_INLINE void VP8YuvToRgba4444(int y, int u, int v, + uint8_t* const argb) { + const int r = VP8YUVToR(y, v); // 4 usable bits + const int g = VP8YUVToG(y, u, v); // 4 usable bits + const int b = VP8YUVToB(y, u); // 4 usable bits + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#if (WEBP_SWAP_16BIT_CSP == 1) + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif +} + +//----------------------------------------------------------------------------- +// Alpha handling variants + +static WEBP_INLINE void VP8YuvToArgb(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const argb) { + argb[0] = 0xff; + VP8YuvToRgb(y, u, v, argb + 1); +} + +static WEBP_INLINE void VP8YuvToBgra(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const bgra) { + VP8YuvToBgr(y, u, v, bgra); + bgra[3] = 0xff; +} + +static WEBP_INLINE void VP8YuvToRgba(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const rgba) { + VP8YuvToRgb(y, u, v, rgba); + rgba[3] = 0xff; +} + +//----------------------------------------------------------------------------- +// SSE2 extra functions (mostly for upsampling_sse2.c) + +#if defined(WEBP_USE_SSE2) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgba32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToRgb32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgra32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgr32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToArgb32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToRgba444432_SSE2(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst); +void VP8YuvToRgb56532_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); + +#endif // WEBP_USE_SSE2 + +//----------------------------------------------------------------------------- +// SSE41 extra functions (mostly for upsampling_sse41.c) + +#if defined(WEBP_USE_SSE41) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst); + +#endif // WEBP_USE_SSE41 + +//------------------------------------------------------------------------------ +// RGB -> YUV conversion + +// Stub functions that can be called with various rounding values: +static WEBP_INLINE int VP8ClipUV(int uv, int rounding) { + uv = (uv + rounding + (128 << (YUV_FIX + 2))) >> (YUV_FIX + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; +} + +static WEBP_INLINE int VP8RGBToY(int r, int g, int b, int rounding) { + const int luma = 16839 * r + 33059 * g + 6420 * b; + return (luma + rounding + (16 << YUV_FIX)) >> YUV_FIX; // no need to clip +} + +static WEBP_INLINE int VP8RGBToU(int r, int g, int b, int rounding) { + const int u = -9719 * r - 19081 * g + 28800 * b; + return VP8ClipUV(u, rounding); +} + +static WEBP_INLINE int VP8RGBToV(int r, int g, int b, int rounding) { + const int v = +28800 * r - 24116 * g - 4684 * b; + return VP8ClipUV(v, rounding); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_YUV_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips32.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips32.c new file mode 100644 index 0000000000..9d0a887824 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips32.c @@ -0,0 +1,103 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS version of YUV to RGB upsampling functions. +// +// Author(s): Djordje Pesut (djordje.pesut@imgtec.com) +// Jovan Zelincevic (jovan.zelincevic@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS32) + +#include "src/dsp/yuv.h" + +//------------------------------------------------------------------------------ +// simple point-sampling + +#define ROW_FUNC(FUNC_NAME, XSTEP, R, G, B, A) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i, r, g, b; \ + int temp0, temp1, temp2, temp3, temp4; \ + for (i = 0; i < (len >> 1); i++) { \ + temp1 = MultHi(v[0], 26149); \ + temp3 = MultHi(v[0], 13320); \ + temp2 = MultHi(u[0], 6419); \ + temp4 = MultHi(u[0], 33050); \ + temp0 = MultHi(y[0], 19077); \ + temp1 -= 14234; \ + temp3 -= 8708; \ + temp2 += temp3; \ + temp4 -= 17685; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + temp0 = MultHi(y[1], 19077); \ + dst[R] = r; \ + dst[G] = g; \ + dst[B] = b; \ + if (A) dst[A] = 0xff; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + dst[R + XSTEP] = r; \ + dst[G + XSTEP] = g; \ + dst[B + XSTEP] = b; \ + if (A) dst[A + XSTEP] = 0xff; \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * XSTEP; \ + } \ + if (len & 1) { \ + temp1 = MultHi(v[0], 26149); \ + temp3 = MultHi(v[0], 13320); \ + temp2 = MultHi(u[0], 6419); \ + temp4 = MultHi(u[0], 33050); \ + temp0 = MultHi(y[0], 19077); \ + temp1 -= 14234; \ + temp3 -= 8708; \ + temp2 += temp3; \ + temp4 -= 17685; \ + r = VP8Clip8(temp0 + temp1); \ + g = VP8Clip8(temp0 - temp2); \ + b = VP8Clip8(temp0 + temp4); \ + dst[R] = r; \ + dst[G] = g; \ + dst[B] = b; \ + if (A) dst[A] = 0xff; \ + } \ +} + +ROW_FUNC(YuvToRgbRow_MIPS32, 3, 0, 1, 2, 0) +ROW_FUNC(YuvToRgbaRow_MIPS32, 4, 0, 1, 2, 3) +ROW_FUNC(YuvToBgrRow_MIPS32, 3, 2, 1, 0, 0) +ROW_FUNC(YuvToBgraRow_MIPS32, 4, 2, 1, 0, 3) + +#undef ROW_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersMIPS32(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersMIPS32(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_MIPS32; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow_MIPS32; + WebPSamplers[MODE_BGR] = YuvToBgrRow_MIPS32; + WebPSamplers[MODE_BGRA] = YuvToBgraRow_MIPS32; +} + +#else // !WEBP_USE_MIPS32 + +WEBP_DSP_INIT_STUB(WebPInitSamplersMIPS32) + +#endif // WEBP_USE_MIPS32 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips_dsp_r2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips_dsp_r2.c new file mode 100644 index 0000000000..cc8afcc756 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_mips_dsp_r2.c @@ -0,0 +1,134 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// MIPS DSPr2 version of YUV to RGB upsampling functions. +// +// Author(s): Branimir Vasic (branimir.vasic@imgtec.com) +// Djordje Pesut (djordje.pesut@imgtec.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_MIPS_DSP_R2) + +#include "src/dsp/yuv.h" + +//------------------------------------------------------------------------------ +// simple point-sampling + +#define ROW_FUNC_PART_1() \ + "lbu %[temp3], 0(%[v]) \n\t" \ + "lbu %[temp4], 0(%[u]) \n\t" \ + "lbu %[temp0], 0(%[y]) \n\t" \ + "mul %[temp1], %[t_con_1], %[temp3] \n\t" \ + "mul %[temp3], %[t_con_2], %[temp3] \n\t" \ + "mul %[temp2], %[t_con_3], %[temp4] \n\t" \ + "mul %[temp4], %[t_con_4], %[temp4] \n\t" \ + "mul %[temp0], %[t_con_5], %[temp0] \n\t" \ + "subu %[temp1], %[temp1], %[t_con_6] \n\t" \ + "subu %[temp3], %[temp3], %[t_con_7] \n\t" \ + "addu %[temp2], %[temp2], %[temp3] \n\t" \ + "subu %[temp4], %[temp4], %[t_con_8] \n\t" \ + +#define ROW_FUNC_PART_2(R, G, B, K) \ + "addu %[temp5], %[temp0], %[temp1] \n\t" \ + "subu %[temp6], %[temp0], %[temp2] \n\t" \ + "addu %[temp7], %[temp0], %[temp4] \n\t" \ +".if " #K " \n\t" \ + "lbu %[temp0], 1(%[y]) \n\t" \ +".endif \n\t" \ + "shll_s.w %[temp5], %[temp5], 17 \n\t" \ + "shll_s.w %[temp6], %[temp6], 17 \n\t" \ +".if " #K " \n\t" \ + "mul %[temp0], %[t_con_5], %[temp0] \n\t" \ +".endif \n\t" \ + "shll_s.w %[temp7], %[temp7], 17 \n\t" \ + "precrqu_s.qb.ph %[temp5], %[temp5], $zero \n\t" \ + "precrqu_s.qb.ph %[temp6], %[temp6], $zero \n\t" \ + "precrqu_s.qb.ph %[temp7], %[temp7], $zero \n\t" \ + "srl %[temp5], %[temp5], 24 \n\t" \ + "srl %[temp6], %[temp6], 24 \n\t" \ + "srl %[temp7], %[temp7], 24 \n\t" \ + "sb %[temp5], " #R "(%[dst]) \n\t" \ + "sb %[temp6], " #G "(%[dst]) \n\t" \ + "sb %[temp7], " #B "(%[dst]) \n\t" \ + +#define ASM_CLOBBER_LIST() \ + : [temp0]"=&r"(temp0), [temp1]"=&r"(temp1), [temp2]"=&r"(temp2), \ + [temp3]"=&r"(temp3), [temp4]"=&r"(temp4), [temp5]"=&r"(temp5), \ + [temp6]"=&r"(temp6), [temp7]"=&r"(temp7) \ + : [t_con_1]"r"(t_con_1), [t_con_2]"r"(t_con_2), [t_con_3]"r"(t_con_3), \ + [t_con_4]"r"(t_con_4), [t_con_5]"r"(t_con_5), [t_con_6]"r"(t_con_6), \ + [u]"r"(u), [v]"r"(v), [y]"r"(y), [dst]"r"(dst), \ + [t_con_7]"r"(t_con_7), [t_con_8]"r"(t_con_8) \ + : "memory", "hi", "lo" \ + +#define ROW_FUNC(FUNC_NAME, XSTEP, R, G, B, A) \ +static void FUNC_NAME(const uint8_t* y, \ + const uint8_t* u, const uint8_t* v, \ + uint8_t* dst, int len) { \ + int i; \ + uint32_t temp0, temp1, temp2, temp3, temp4, temp5, temp6, temp7; \ + const int t_con_1 = 26149; \ + const int t_con_2 = 13320; \ + const int t_con_3 = 6419; \ + const int t_con_4 = 33050; \ + const int t_con_5 = 19077; \ + const int t_con_6 = 14234; \ + const int t_con_7 = 8708; \ + const int t_con_8 = 17685; \ + for (i = 0; i < (len >> 1); i++) { \ + __asm__ volatile ( \ + ROW_FUNC_PART_1() \ + ROW_FUNC_PART_2(R, G, B, 1) \ + ROW_FUNC_PART_2(R + XSTEP, G + XSTEP, B + XSTEP, 0) \ + ASM_CLOBBER_LIST() \ + ); \ + if (A) dst[A] = dst[A + XSTEP] = 0xff; \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * XSTEP; \ + } \ + if (len & 1) { \ + __asm__ volatile ( \ + ROW_FUNC_PART_1() \ + ROW_FUNC_PART_2(R, G, B, 0) \ + ASM_CLOBBER_LIST() \ + ); \ + if (A) dst[A] = 0xff; \ + } \ +} + +ROW_FUNC(YuvToRgbRow_MIPSdspR2, 3, 0, 1, 2, 0) +ROW_FUNC(YuvToRgbaRow_MIPSdspR2, 4, 0, 1, 2, 3) +ROW_FUNC(YuvToBgrRow_MIPSdspR2, 3, 2, 1, 0, 0) +ROW_FUNC(YuvToBgraRow_MIPSdspR2, 4, 2, 1, 0, 3) + +#undef ROW_FUNC +#undef ASM_CLOBBER_LIST +#undef ROW_FUNC_PART_2 +#undef ROW_FUNC_PART_1 + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersMIPSdspR2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersMIPSdspR2(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_MIPSdspR2; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow_MIPSdspR2; + WebPSamplers[MODE_BGR] = YuvToBgrRow_MIPSdspR2; + WebPSamplers[MODE_BGRA] = YuvToBgraRow_MIPSdspR2; +} + +#else // !WEBP_USE_MIPS_DSP_R2 + +WEBP_DSP_INIT_STUB(WebPInitSamplersMIPSdspR2) + +#endif // WEBP_USE_MIPS_DSP_R2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_neon.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_neon.c new file mode 100644 index 0000000000..ff77b00980 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_neon.c @@ -0,0 +1,180 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_NEON) + +#include +#include + +#include "src/dsp/neon.h" + +//----------------------------------------------------------------------------- + +static uint8x8_t ConvertRGBToY_NEON(const uint8x8_t R, + const uint8x8_t G, + const uint8x8_t B) { + const uint16x8_t r = vmovl_u8(R); + const uint16x8_t g = vmovl_u8(G); + const uint16x8_t b = vmovl_u8(B); + const uint16x4_t r_lo = vget_low_u16(r); + const uint16x4_t r_hi = vget_high_u16(r); + const uint16x4_t g_lo = vget_low_u16(g); + const uint16x4_t g_hi = vget_high_u16(g); + const uint16x4_t b_lo = vget_low_u16(b); + const uint16x4_t b_hi = vget_high_u16(b); + const uint32x4_t tmp0_lo = vmull_n_u16( r_lo, 16839u); + const uint32x4_t tmp0_hi = vmull_n_u16( r_hi, 16839u); + const uint32x4_t tmp1_lo = vmlal_n_u16(tmp0_lo, g_lo, 33059u); + const uint32x4_t tmp1_hi = vmlal_n_u16(tmp0_hi, g_hi, 33059u); + const uint32x4_t tmp2_lo = vmlal_n_u16(tmp1_lo, b_lo, 6420u); + const uint32x4_t tmp2_hi = vmlal_n_u16(tmp1_hi, b_hi, 6420u); + const uint16x8_t Y1 = vcombine_u16(vrshrn_n_u32(tmp2_lo, 16), + vrshrn_n_u32(tmp2_hi, 16)); + const uint16x8_t Y2 = vaddq_u16(Y1, vdupq_n_u16(16)); + return vqmovn_u16(Y2); +} + +static void ConvertRGB24ToY_NEON(const uint8_t* rgb, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 3 * 8) { + const uint8x8x3_t RGB = vld3_u8(rgb); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[0], RGB.val[1], RGB.val[2]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_NEON(const uint8_t* bgr, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, bgr += 3 * 8) { + const uint8x8x3_t BGR = vld3_u8(bgr); + const uint8x8_t Y = ConvertRGBToY_NEON(BGR.val[2], BGR.val[1], BGR.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_NEON(const uint32_t* argb, uint8_t* y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8) { + const uint8x8x4_t RGB = vld4_u8((const uint8_t*)&argb[i]); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[2], RGB.val[1], RGB.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +//----------------------------------------------------------------------------- + +// computes: DST_s16 = [(C0 * r + C1 * g + C2 * b) >> 16] + CST +#define MULTIPLY_16b_PREAMBLE(r, g, b) \ + const int16x4_t r_lo = vreinterpret_s16_u16(vget_low_u16(r)); \ + const int16x4_t r_hi = vreinterpret_s16_u16(vget_high_u16(r)); \ + const int16x4_t g_lo = vreinterpret_s16_u16(vget_low_u16(g)); \ + const int16x4_t g_hi = vreinterpret_s16_u16(vget_high_u16(g)); \ + const int16x4_t b_lo = vreinterpret_s16_u16(vget_low_u16(b)); \ + const int16x4_t b_hi = vreinterpret_s16_u16(vget_high_u16(b)) + +#define MULTIPLY_16b(C0, C1, C2, CST, DST_s16) do { \ + const int32x4_t tmp0_lo = vmull_n_s16( r_lo, C0); \ + const int32x4_t tmp0_hi = vmull_n_s16( r_hi, C0); \ + const int32x4_t tmp1_lo = vmlal_n_s16(tmp0_lo, g_lo, C1); \ + const int32x4_t tmp1_hi = vmlal_n_s16(tmp0_hi, g_hi, C1); \ + const int32x4_t tmp2_lo = vmlal_n_s16(tmp1_lo, b_lo, C2); \ + const int32x4_t tmp2_hi = vmlal_n_s16(tmp1_hi, b_hi, C2); \ + const int16x8_t tmp3 = vcombine_s16(vshrn_n_s32(tmp2_lo, 16), \ + vshrn_n_s32(tmp2_hi, 16)); \ + DST_s16 = vaddq_s16(tmp3, vdupq_n_s16(CST)); \ +} while (0) + +// This needs to be a macro, since (128 << SHIFT) needs to be an immediate. +#define CONVERT_RGB_TO_UV(r, g, b, SHIFT, U_DST, V_DST) do { \ + MULTIPLY_16b_PREAMBLE(r, g, b); \ + MULTIPLY_16b(-9719, -19081, 28800, 128 << SHIFT, U_DST); \ + MULTIPLY_16b(28800, -24116, -4684, 128 << SHIFT, V_DST); \ +} while (0) + +static void ConvertRGBA32ToUV_NEON(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 4 * 8) { + const uint16x8x4_t RGB = vld4q_u16((const uint16_t*)rgb); + int16x8_t U, V; + CONVERT_RGB_TO_UV(RGB.val[0], RGB.val[1], RGB.val[2], 2, U, V); + vst1_u8(u + i, vqrshrun_n_s16(U, 2)); + vst1_u8(v + i, vqrshrun_n_s16(V, 2)); + } + for (; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +static void ConvertARGBToUV_NEON(const uint32_t* argb, uint8_t* u, uint8_t* v, + int src_width, int do_store) { + int i; + for (i = 0; i + 16 <= src_width; i += 16, u += 8, v += 8) { + const uint8x16x4_t RGB = vld4q_u8((const uint8_t*)&argb[i]); + const uint16x8_t R = vpaddlq_u8(RGB.val[2]); // pair-wise adds + const uint16x8_t G = vpaddlq_u8(RGB.val[1]); + const uint16x8_t B = vpaddlq_u8(RGB.val[0]); + int16x8_t U_tmp, V_tmp; + CONVERT_RGB_TO_UV(R, G, B, 1, U_tmp, V_tmp); + { + const uint8x8_t U = vqrshrun_n_s16(U_tmp, 1); + const uint8x8_t V = vqrshrun_n_s16(V_tmp, 1); + if (do_store) { + vst1_u8(u, U); + vst1_u8(v, V); + } else { + const uint8x8_t prev_u = vld1_u8(u); + const uint8x8_t prev_v = vld1_u8(v); + vst1_u8(u, vrhadd_u8(U, prev_u)); + vst1_u8(v, vrhadd_u8(V, prev_v)); + } + } + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVNEON(void) { + WebPConvertRGB24ToY = ConvertRGB24ToY_NEON; + WebPConvertBGR24ToY = ConvertBGR24ToY_NEON; + WebPConvertARGBToY = ConvertARGBToY_NEON; + WebPConvertARGBToUV = ConvertARGBToUV_NEON; + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVNEON) + +#endif // WEBP_USE_NEON diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse2.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse2.c new file mode 100644 index 0000000000..01a48f9af2 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse2.c @@ -0,0 +1,758 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include + +#include "src/dsp/common_sse2.h" +#include "src/utils/utils.h" + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE2(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE2(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE2(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE2(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE2(y), U0 = Load_HI_16_SSE2(u), + V0 = Load_HI_16_SSE2(v); + ConvertYUV444ToRGB_SSE2(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE2(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE2(y), U0 = Load_UV_HI_8_SSE2(u), + V0 = Load_UV_HI_8_SSE2(v); + ConvertYUV444ToRGB_SSE2(&Y0, &U0, &V0, R, G, B); +} + +// Pack R/G/B/A results into 32b output. +static WEBP_INLINE void PackAndStore4_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + const __m128i* const A, + uint8_t* const dst) { + const __m128i rb = _mm_packus_epi16(*R, *B); + const __m128i ga = _mm_packus_epi16(*G, *A); + const __m128i rg = _mm_unpacklo_epi8(rb, ga); + const __m128i ba = _mm_unpackhi_epi8(rb, ga); + const __m128i RGBA_lo = _mm_unpacklo_epi16(rg, ba); + const __m128i RGBA_hi = _mm_unpackhi_epi16(rg, ba); + _mm_storeu_si128((__m128i*)(dst + 0), RGBA_lo); + _mm_storeu_si128((__m128i*)(dst + 16), RGBA_hi); +} + +// Pack R/G/B/A results into 16b output. +static WEBP_INLINE void PackAndStore4444_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + const __m128i* const A, + uint8_t* const dst) { +#if (WEBP_SWAP_16BIT_CSP == 0) + const __m128i rg0 = _mm_packus_epi16(*R, *G); + const __m128i ba0 = _mm_packus_epi16(*B, *A); +#else + const __m128i rg0 = _mm_packus_epi16(*B, *A); + const __m128i ba0 = _mm_packus_epi16(*R, *G); +#endif + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); + const __m128i rb1 = _mm_unpacklo_epi8(rg0, ba0); // rbrbrbrbrb... + const __m128i ga1 = _mm_unpackhi_epi8(rg0, ba0); // gagagagaga... + const __m128i rb2 = _mm_and_si128(rb1, mask_0xf0); + const __m128i ga2 = _mm_srli_epi16(_mm_and_si128(ga1, mask_0xf0), 4); + const __m128i rgba4444 = _mm_or_si128(rb2, ga2); + _mm_storeu_si128((__m128i*)dst, rgba4444); +} + +// Pack R/G/B results into 16b output. +static WEBP_INLINE void PackAndStore565_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + uint8_t* const dst) { + const __m128i r0 = _mm_packus_epi16(*R, *R); + const __m128i g0 = _mm_packus_epi16(*G, *G); + const __m128i b0 = _mm_packus_epi16(*B, *B); + const __m128i r1 = _mm_and_si128(r0, _mm_set1_epi8((char)0xf8)); + const __m128i b1 = _mm_and_si128(_mm_srli_epi16(b0, 3), _mm_set1_epi8(0x1f)); + const __m128i g1 = + _mm_srli_epi16(_mm_and_si128(g0, _mm_set1_epi8((char)0xe0)), 5); + const __m128i g2 = _mm_slli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0x1c)), 3); + const __m128i rg = _mm_or_si128(r1, g1); + const __m128i gb = _mm_or_si128(g2, b1); +#if (WEBP_SWAP_16BIT_CSP == 0) + const __m128i rgb565 = _mm_unpacklo_epi8(rg, gb); +#else + const __m128i rgb565 = _mm_unpacklo_epi8(gb, rg); +#endif + _mm_storeu_si128((__m128i*)dst, rgb565); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE2(__m128i* const in0, __m128i* const in1, + __m128i* const in2, __m128i* const in3, + __m128i* const in4, __m128i* const in5, + uint8_t* const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE2(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgba32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToBgra32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&B, &G, &R, &kAlpha, dst); + } +} + +void VP8YuvToArgb32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&kAlpha, &R, &G, &B, dst); + } +} + +void VP8YuvToRgba444432_SSE2(const uint8_t* y, const uint8_t* u, + const uint8_t* v, uint8_t* dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4444_SSE2(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToRgb56532_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore565_SSE2(&R, &G, &B, dst); + } +} + +void VP8YuvToRgb32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE2(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE2(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE2(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE2(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE2(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE2(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE2(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE2(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE2(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbaRow_SSE2(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&R, &G, &B, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgba(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgraRow_SSE2(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&B, &G, &R, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgra(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToArgbRow_SSE2(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&kAlpha, &R, &G, &B, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToArgb(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToRgbRow_SSE2(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE2(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE2(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE2(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE2(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE2(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE2(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE2(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE2(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE2(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE2(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE2; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow_SSE2; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE2; + WebPSamplers[MODE_BGRA] = YuvToBgraRow_SSE2; + WebPSamplers[MODE_ARGB] = YuvToArgbRow_SSE2; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +// Function that inserts a value of the second half of the in buffer in between +// every two char of the first half. +static WEBP_INLINE void RGB24PackedToPlanarHelper_SSE2( + const __m128i* const in /*in[6]*/, __m128i* const out /*out[6]*/) { + out[0] = _mm_unpacklo_epi8(in[0], in[3]); + out[1] = _mm_unpackhi_epi8(in[0], in[3]); + out[2] = _mm_unpacklo_epi8(in[1], in[4]); + out[3] = _mm_unpackhi_epi8(in[1], in[4]); + out[4] = _mm_unpacklo_epi8(in[2], in[5]); + out[5] = _mm_unpackhi_epi8(in[2], in[5]); +} + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE2( + const uint8_t* const rgb, __m128i* const out /*out[6]*/) { + __m128i tmp[6]; + tmp[0] = _mm_loadu_si128((const __m128i*)(rgb + 0)); + tmp[1] = _mm_loadu_si128((const __m128i*)(rgb + 16)); + tmp[2] = _mm_loadu_si128((const __m128i*)(rgb + 32)); + tmp[3] = _mm_loadu_si128((const __m128i*)(rgb + 48)); + tmp[4] = _mm_loadu_si128((const __m128i*)(rgb + 64)); + tmp[5] = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + RGB24PackedToPlanarHelper_SSE2(tmp, out); + RGB24PackedToPlanarHelper_SSE2(out, tmp); + RGB24PackedToPlanarHelper_SSE2(tmp, out); + RGB24PackedToPlanarHelper_SSE2(out, tmp); + RGB24PackedToPlanarHelper_SSE2(tmp, out); +} + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE2(const uint32_t* const argb, + __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE2(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE2(const uint8_t* rgb, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE2(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE2(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE2(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE2(const uint32_t* argb, uint8_t* y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE2(&argb[i], rgb); + ConvertRGBToY_SSE2(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE2(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE2(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE2(const uint32_t* argb, + uint8_t* u, uint8_t* v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE2(&argb[i], rgb); + HorizontalAddPack_SSE2(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE2(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE2(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE2(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE2(&argb[i + 16], rgb); + HorizontalAddPack_SSE2(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE2(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE2(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE2(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE2( + const uint16_t* const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // column-wise transpose + const __m128i A0 = _mm_unpacklo_epi16(in0, in1); + const __m128i A1 = _mm_unpackhi_epi16(in0, in1); + const __m128i A2 = _mm_unpacklo_epi16(in2, in3); + const __m128i A3 = _mm_unpackhi_epi16(in2, in3); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // r0 r1 r2 r3 | g0 g1 .. + const __m128i B1 = _mm_unpackhi_epi16(A0, A1); // b0 b1 b2 b3 | x x x x + const __m128i B2 = _mm_unpacklo_epi16(A2, A3); // r4 r5 r6 r7 | g4 g5 .. + const __m128i B3 = _mm_unpackhi_epi16(A2, A3); // b4 b5 b6 b7 | x x x x + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpacklo_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE2(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE2(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE2(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE2(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE2(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE2(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE2; + WebPConvertARGBToUV = ConvertARGBToUV_SSE2; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE2; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE2; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE2) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse41.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse41.c new file mode 100644 index 0000000000..f79b802e47 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/dsp/yuv_sse41.c @@ -0,0 +1,615 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE41) + +#include +#include + +#include "src/dsp/common_sse41.h" +#include "src/utils/utils.h" + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE41(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_HI_16_SSE41(u), + V0 = Load_HI_16_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE41(const uint8_t* const y, + const uint8_t* const u, + const uint8_t* const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_UV_HI_8_SSE41(u), + V0 = Load_UV_HI_8_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5, + uint8_t* const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE41(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgb32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE41(const uint8_t* y, const uint8_t* u, const uint8_t* v, + uint8_t* dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE41(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE41(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE41; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE41; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +#define WEBP_SSE41_SHUFF(OUT) do { \ + const __m128i tmp0 = _mm_shuffle_epi8(A0, shuff0); \ + const __m128i tmp1 = _mm_shuffle_epi8(A1, shuff1); \ + const __m128i tmp2 = _mm_shuffle_epi8(A2, shuff2); \ + const __m128i tmp3 = _mm_shuffle_epi8(A3, shuff0); \ + const __m128i tmp4 = _mm_shuffle_epi8(A4, shuff1); \ + const __m128i tmp5 = _mm_shuffle_epi8(A5, shuff2); \ + \ + /* OR everything to get one channel */ \ + const __m128i tmp6 = _mm_or_si128(tmp0, tmp1); \ + const __m128i tmp7 = _mm_or_si128(tmp3, tmp4); \ + out[OUT + 0] = _mm_or_si128(tmp6, tmp2); \ + out[OUT + 1] = _mm_or_si128(tmp7, tmp5); \ +} while (0); + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE41( + const uint8_t* const rgb, __m128i* const out /*out[6]*/) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(rgb + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(rgb + 16)); + const __m128i A2 = _mm_loadu_si128((const __m128i*)(rgb + 32)); + const __m128i A3 = _mm_loadu_si128((const __m128i*)(rgb + 48)); + const __m128i A4 = _mm_loadu_si128((const __m128i*)(rgb + 64)); + const __m128i A5 = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + // Compute RR. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 13, 10, 7, 4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(0) + } + // Compute GG. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(2) + } + // Compute BB. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 14, 11, 8, 5, 2); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(4) + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE41( + const uint32_t* const argb, __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE41(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE41(const uint8_t* rgb, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE41(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE41(const uint32_t* argb, uint8_t* y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + ConvertRGBToY_SSE41(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE41(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE41(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE41(const uint32_t* argb, + uint8_t* u, uint8_t* v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE41(&argb[i + 16], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE41( + const uint16_t* const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // aarrggbb as 16-bit. + const __m128i shuff0 = + _mm_set_epi8(-1, -1, -1, -1, 13, 12, 5, 4, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i shuff1 = + _mm_set_epi8(13, 12, 5, 4, -1, -1, -1, -1, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i A0 = _mm_shuffle_epi8(in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(in1, shuff1); + const __m128i A2 = _mm_shuffle_epi8(in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(in3, shuff1); + // R0R1G0G1 + // B0B1**** + // R2R3G2G3 + // B2B3**** + // (OR is used to free port 5 for the unpack) + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_or_si128(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_or_si128(A2, A3); + // Gather the channels. + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpackhi_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE41(const uint16_t* rgb, + uint8_t* u, uint8_t* v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE41(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE41(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE41(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE41; + WebPConvertARGBToUV = ConvertARGBToUV_SSE41; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE41; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE41; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE41; +} + +//------------------------------------------------------------------------------ + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE41) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/backward_references_enc.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/backward_references_enc.h new file mode 100644 index 0000000000..4dff1c27b5 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/backward_references_enc.h @@ -0,0 +1,244 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// + +#ifndef WEBP_ENC_BACKWARD_REFERENCES_ENC_H_ +#define WEBP_ENC_BACKWARD_REFERENCES_ENC_H_ + +#include +#include +#include "src/webp/types.h" +#include "src/webp/encode.h" +#include "src/webp/format_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// The maximum allowed limit is 11. +#define MAX_COLOR_CACHE_BITS 10 + +// ----------------------------------------------------------------------------- +// PixOrCopy + +enum Mode { + kLiteral, + kCacheIdx, + kCopy, + kNone +}; + +typedef struct { + // mode as uint8_t to make the memory layout to be exactly 8 bytes. + uint8_t mode; + uint16_t len; + uint32_t argb_or_distance; +} PixOrCopy; + +static WEBP_INLINE PixOrCopy PixOrCopyCreateCopy(uint32_t distance, + uint16_t len) { + PixOrCopy retval; + retval.mode = kCopy; + retval.argb_or_distance = distance; + retval.len = len; + return retval; +} + +static WEBP_INLINE PixOrCopy PixOrCopyCreateCacheIdx(int idx) { + PixOrCopy retval; + assert(idx >= 0); + assert(idx < (1 << MAX_COLOR_CACHE_BITS)); + retval.mode = kCacheIdx; + retval.argb_or_distance = idx; + retval.len = 1; + return retval; +} + +static WEBP_INLINE PixOrCopy PixOrCopyCreateLiteral(uint32_t argb) { + PixOrCopy retval; + retval.mode = kLiteral; + retval.argb_or_distance = argb; + retval.len = 1; + return retval; +} + +static WEBP_INLINE int PixOrCopyIsLiteral(const PixOrCopy* const p) { + return (p->mode == kLiteral); +} + +static WEBP_INLINE int PixOrCopyIsCacheIdx(const PixOrCopy* const p) { + return (p->mode == kCacheIdx); +} + +static WEBP_INLINE int PixOrCopyIsCopy(const PixOrCopy* const p) { + return (p->mode == kCopy); +} + +static WEBP_INLINE uint32_t PixOrCopyLiteral(const PixOrCopy* const p, + int component) { + assert(p->mode == kLiteral); + return (p->argb_or_distance >> (component * 8)) & 0xff; +} + +static WEBP_INLINE uint32_t PixOrCopyLength(const PixOrCopy* const p) { + return p->len; +} + +static WEBP_INLINE uint32_t PixOrCopyCacheIdx(const PixOrCopy* const p) { + assert(p->mode == kCacheIdx); + assert(p->argb_or_distance < (1U << MAX_COLOR_CACHE_BITS)); + return p->argb_or_distance; +} + +static WEBP_INLINE uint32_t PixOrCopyDistance(const PixOrCopy* const p) { + assert(p->mode == kCopy); + return p->argb_or_distance; +} + +// ----------------------------------------------------------------------------- +// VP8LHashChain + +#define HASH_BITS 18 +#define HASH_SIZE (1 << HASH_BITS) + +// If you change this, you need MAX_LENGTH_BITS + WINDOW_SIZE_BITS <= 32 as it +// is used in VP8LHashChain. +#define MAX_LENGTH_BITS 12 +#define WINDOW_SIZE_BITS 20 +// We want the max value to be attainable and stored in MAX_LENGTH_BITS bits. +#define MAX_LENGTH ((1 << MAX_LENGTH_BITS) - 1) +#if MAX_LENGTH_BITS + WINDOW_SIZE_BITS > 32 +#error "MAX_LENGTH_BITS + WINDOW_SIZE_BITS > 32" +#endif + +typedef struct VP8LHashChain VP8LHashChain; +struct VP8LHashChain { + // The 20 most significant bits contain the offset at which the best match + // is found. These 20 bits are the limit defined by GetWindowSizeForHashChain + // (through WINDOW_SIZE = 1<<20). + // The lower 12 bits contain the length of the match. The 12 bit limit is + // defined in MaxFindCopyLength with MAX_LENGTH=4096. + uint32_t* offset_length_; + // This is the maximum size of the hash_chain that can be constructed. + // Typically this is the pixel count (width x height) for a given image. + int size_; +}; + +// Must be called first, to set size. +int VP8LHashChainInit(VP8LHashChain* const p, int size); +// Pre-compute the best matches for argb. pic and percent are for progress. +int VP8LHashChainFill(VP8LHashChain* const p, int quality, + const uint32_t* const argb, int xsize, int ysize, + int low_effort, const WebPPicture* const pic, + int percent_range, int* const percent); +void VP8LHashChainClear(VP8LHashChain* const p); // release memory + +static WEBP_INLINE int VP8LHashChainFindOffset(const VP8LHashChain* const p, + const int base_position) { + return p->offset_length_[base_position] >> MAX_LENGTH_BITS; +} + +static WEBP_INLINE int VP8LHashChainFindLength(const VP8LHashChain* const p, + const int base_position) { + return p->offset_length_[base_position] & ((1U << MAX_LENGTH_BITS) - 1); +} + +static WEBP_INLINE void VP8LHashChainFindCopy(const VP8LHashChain* const p, + int base_position, + int* const offset_ptr, + int* const length_ptr) { + *offset_ptr = VP8LHashChainFindOffset(p, base_position); + *length_ptr = VP8LHashChainFindLength(p, base_position); +} + +// ----------------------------------------------------------------------------- +// VP8LBackwardRefs (block-based backward-references storage) + +// maximum number of reference blocks the image will be segmented into +#define MAX_REFS_BLOCK_PER_IMAGE 16 + +typedef struct PixOrCopyBlock PixOrCopyBlock; // forward declaration +typedef struct VP8LBackwardRefs VP8LBackwardRefs; + +// Container for blocks chain +struct VP8LBackwardRefs { + int block_size_; // common block-size + int error_; // set to true if some memory error occurred + PixOrCopyBlock* refs_; // list of currently used blocks + PixOrCopyBlock** tail_; // for list recycling + PixOrCopyBlock* free_blocks_; // free-list + PixOrCopyBlock* last_block_; // used for adding new refs (internal) +}; + +// Initialize the object. 'block_size' is the common block size to store +// references (typically, width * height / MAX_REFS_BLOCK_PER_IMAGE). +void VP8LBackwardRefsInit(VP8LBackwardRefs* const refs, int block_size); +// Release memory for backward references. +void VP8LBackwardRefsClear(VP8LBackwardRefs* const refs); + +// Cursor for iterating on references content +typedef struct { + // public: + PixOrCopy* cur_pos; // current position + // private: + PixOrCopyBlock* cur_block_; // current block in the refs list + const PixOrCopy* last_pos_; // sentinel for switching to next block +} VP8LRefsCursor; + +// Returns a cursor positioned at the beginning of the references list. +VP8LRefsCursor VP8LRefsCursorInit(const VP8LBackwardRefs* const refs); +// Returns true if cursor is pointing at a valid position. +static WEBP_INLINE int VP8LRefsCursorOk(const VP8LRefsCursor* const c) { + return (c->cur_pos != NULL); +} +// Move to next block of references. Internal, not to be called directly. +void VP8LRefsCursorNextBlock(VP8LRefsCursor* const c); +// Move to next position, or NULL. Should not be called if !VP8LRefsCursorOk(). +static WEBP_INLINE void VP8LRefsCursorNext(VP8LRefsCursor* const c) { + assert(c != NULL); + assert(VP8LRefsCursorOk(c)); + if (++c->cur_pos == c->last_pos_) VP8LRefsCursorNextBlock(c); +} + +// ----------------------------------------------------------------------------- +// Main entry points + +enum VP8LLZ77Type { + kLZ77Standard = 1, + kLZ77RLE = 2, + kLZ77Box = 4 +}; + +// Evaluates best possible backward references for specified quality. +// The input cache_bits to 'VP8LGetBackwardReferences' sets the maximum cache +// bits to use (passing 0 implies disabling the local color cache). +// The optimal cache bits is evaluated and set for the *cache_bits_best +// parameter with the matching refs_best. +// If do_no_cache == 0, refs is an array of 2 values and the best +// VP8LBackwardRefs is put in the first element. +// If do_no_cache != 0, refs is an array of 3 values and the best +// VP8LBackwardRefs is put in the first element, the best value with no-cache in +// the second element. +// In both cases, the last element is used as temporary internally. +// pic and percent are for progress. +// Returns false in case of error (stored in pic->error_code). +int VP8LGetBackwardReferences( + int width, int height, const uint32_t* const argb, int quality, + int low_effort, int lz77_types_to_try, int cache_bits_max, int do_no_cache, + const VP8LHashChain* const hash_chain, VP8LBackwardRefs* const refs, + int* const cache_bits_best, const WebPPicture* const pic, int percent_range, + int* const percent); + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_ENC_BACKWARD_REFERENCES_ENC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/cost_enc.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/cost_enc.h new file mode 100644 index 0000000000..a4b177b342 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/cost_enc.h @@ -0,0 +1,82 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Cost tables for level and modes. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_ENC_COST_ENC_H_ +#define WEBP_ENC_COST_ENC_H_ + +#include +#include +#include "src/enc/vp8i_enc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// On-the-fly info about the current set of residuals. Handy to avoid +// passing zillions of params. +typedef struct VP8Residual VP8Residual; +struct VP8Residual { + int first; + int last; + const int16_t* coeffs; + + int coeff_type; + ProbaArray* prob; + StatsArray* stats; + CostArrayPtr costs; +}; + +void VP8InitResidual(int first, int coeff_type, + VP8Encoder* const enc, VP8Residual* const res); + +int VP8RecordCoeffs(int ctx, const VP8Residual* const res); + +// Record proba context used. +static WEBP_INLINE int VP8RecordStats(int bit, proba_t* const stats) { + proba_t p = *stats; + // An overflow is inbound. Note we handle this at 0xfffe0000u instead of + // 0xffff0000u to make sure p + 1u does not overflow. + if (p >= 0xfffe0000u) { + p = ((p + 1u) >> 1) & 0x7fff7fffu; // -> divide the stats by 2. + } + // record bit count (lower 16 bits) and increment total count (upper 16 bits). + p += 0x00010000u + bit; + *stats = p; + return bit; +} + +// Cost of coding one event with probability 'proba'. +static WEBP_INLINE int VP8BitCost(int bit, uint8_t proba) { + return !bit ? VP8EntropyCost[proba] : VP8EntropyCost[255 - proba]; +} + +// Level cost calculations +extern const uint16_t VP8LevelCodes[MAX_VARIABLE_LEVEL][2]; +void VP8CalculateLevelCosts(VP8EncProba* const proba); +static WEBP_INLINE int VP8LevelCost(const uint16_t* const table, int level) { + return VP8LevelFixedCosts[level] + + table[(level > MAX_VARIABLE_LEVEL) ? MAX_VARIABLE_LEVEL : level]; +} + +// Mode costs +extern const uint16_t VP8FixedCostsUV[4]; +extern const uint16_t VP8FixedCostsI16[4]; +extern const uint16_t VP8FixedCostsI4[NUM_BMODES][NUM_BMODES][NUM_BMODES]; + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_ENC_COST_ENC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/histogram_enc.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/histogram_enc.h new file mode 100644 index 0000000000..4c0bb97464 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/histogram_enc.h @@ -0,0 +1,130 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// +// Models the histograms of literal and distance codes. + +#ifndef WEBP_ENC_HISTOGRAM_ENC_H_ +#define WEBP_ENC_HISTOGRAM_ENC_H_ + +#include + +#include "src/enc/backward_references_enc.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Not a trivial literal symbol. +#define VP8L_NON_TRIVIAL_SYM (0xffffffff) + +// A simple container for histograms of data. +typedef struct { + // literal_ contains green literal, palette-code and + // copy-length-prefix histogram + uint32_t* literal_; // Pointer to the allocated buffer for literal. + uint32_t red_[NUM_LITERAL_CODES]; + uint32_t blue_[NUM_LITERAL_CODES]; + uint32_t alpha_[NUM_LITERAL_CODES]; + // Backward reference prefix-code histogram. + uint32_t distance_[NUM_DISTANCE_CODES]; + int palette_code_bits_; + uint32_t trivial_symbol_; // True, if histograms for Red, Blue & Alpha + // literal symbols are single valued. + float bit_cost_; // cached value of bit cost. + float literal_cost_; // Cached values of dominant entropy costs: + float red_cost_; // literal, red & blue. + float blue_cost_; + uint8_t is_used_[5]; // 5 for literal, red, blue, alpha, distance +} VP8LHistogram; + +// Collection of histograms with fixed capacity, allocated as one +// big memory chunk. Can be destroyed by calling WebPSafeFree(). +typedef struct { + int size; // number of slots currently in use + int max_size; // maximum capacity + VP8LHistogram** histograms; +} VP8LHistogramSet; + +// Create the histogram. +// +// The input data is the PixOrCopy data, which models the literals, stop +// codes and backward references (both distances and lengths). Also: if +// palette_code_bits is >= 0, initialize the histogram with this value. +void VP8LHistogramCreate(VP8LHistogram* const p, + const VP8LBackwardRefs* const refs, + int palette_code_bits); + +// Return the size of the histogram for a given cache_bits. +int VP8LGetHistogramSize(int cache_bits); + +// Set the palette_code_bits and reset the stats. +// If init_arrays is true, the arrays are also filled with 0's. +void VP8LHistogramInit(VP8LHistogram* const p, int palette_code_bits, + int init_arrays); + +// Collect all the references into a histogram (without reset) +void VP8LHistogramStoreRefs(const VP8LBackwardRefs* const refs, + VP8LHistogram* const histo); + +// Free the memory allocated for the histogram. +void VP8LFreeHistogram(VP8LHistogram* const histo); + +// Free the memory allocated for the histogram set. +void VP8LFreeHistogramSet(VP8LHistogramSet* const histo); + +// Allocate an array of pointer to histograms, allocated and initialized +// using 'cache_bits'. Return NULL in case of memory error. +VP8LHistogramSet* VP8LAllocateHistogramSet(int size, int cache_bits); + +// Set the histograms in set to 0. +void VP8LHistogramSetClear(VP8LHistogramSet* const set); + +// Allocate and initialize histogram object with specified 'cache_bits'. +// Returns NULL in case of memory error. +// Special case of VP8LAllocateHistogramSet, with size equals 1. +VP8LHistogram* VP8LAllocateHistogram(int cache_bits); + +// Accumulate a token 'v' into a histogram. +void VP8LHistogramAddSinglePixOrCopy(VP8LHistogram* const histo, + const PixOrCopy* const v, + int (*const distance_modifier)(int, int), + int distance_modifier_arg0); + +static WEBP_INLINE int VP8LHistogramNumCodes(int palette_code_bits) { + return NUM_LITERAL_CODES + NUM_LENGTH_CODES + + ((palette_code_bits > 0) ? (1 << palette_code_bits) : 0); +} + +// Builds the histogram image. pic and percent are for progress. +// Returns false in case of error (stored in pic->error_code). +int VP8LGetHistoImageSymbols(int xsize, int ysize, + const VP8LBackwardRefs* const refs, int quality, + int low_effort, int histogram_bits, int cache_bits, + VP8LHistogramSet* const image_histo, + VP8LHistogram* const tmp_histo, + uint16_t* const histogram_symbols, + const WebPPicture* const pic, int percent_range, + int* const percent); + +// Returns the entropy for the symbols in the input array. +float VP8LBitsEntropy(const uint32_t* const array, int n); + +// Estimate how many bits the combined entropy of literals and distance +// approximately maps to. +float VP8LHistogramEstimateBits(VP8LHistogram* const p); + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_ENC_HISTOGRAM_ENC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/vp8i_enc.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/vp8i_enc.h new file mode 100644 index 0000000000..00ff1be795 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/enc/vp8i_enc.h @@ -0,0 +1,523 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebP encoder: internal header. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_ENC_VP8I_ENC_H_ +#define WEBP_ENC_VP8I_ENC_H_ + +#include // for memcpy() +#include "src/dec/common_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/bit_writer_utils.h" +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" +#include "src/webp/encode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Various defines and enums + +// version numbers +#define ENC_MAJ_VERSION 1 +#define ENC_MIN_VERSION 4 +#define ENC_REV_VERSION 0 + +enum { MAX_LF_LEVELS = 64, // Maximum loop filter level + MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost + MAX_LEVEL = 2047 // max level (note: max codable is 2047 + 67) + }; + +typedef enum { // Rate-distortion optimization levels + RD_OPT_NONE = 0, // no rd-opt + RD_OPT_BASIC = 1, // basic scoring (no trellis) + RD_OPT_TRELLIS = 2, // perform trellis-quant on the final decision only + RD_OPT_TRELLIS_ALL = 3 // trellis-quant for every scoring (much slower) +} VP8RDLevel; + +// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). +// The original or reconstructed samples can be accessed using VP8Scan[]. +// The predicted blocks can be accessed using offsets to yuv_p_ and +// the arrays VP8*ModeOffsets[]. +// * YUV Samples area (yuv_in_/yuv_out_/yuv_out2_) +// (see VP8Scan[] for accessing the blocks, along with +// Y_OFF_ENC/U_OFF_ENC/V_OFF_ENC): +// +----+----+ +// Y_OFF_ENC |YYYY|UUVV| +// U_OFF_ENC |YYYY|UUVV| +// V_OFF_ENC |YYYY|....| <- 25% wasted U/V area +// |YYYY|....| +// +----+----+ +// * Prediction area ('yuv_p_', size = PRED_SIZE_ENC) +// Intra16 predictions (16x16 block each, two per row): +// |I16DC16|I16TM16| +// |I16VE16|I16HE16| +// Chroma U/V predictions (16x8 block each, two per row): +// |C8DC8|C8TM8| +// |C8VE8|C8HE8| +// Intra 4x4 predictions (4x4 block each) +// |I4DC4 I4TM4 I4VE4 I4HE4|I4RD4 I4VR4 I4LD4 I4VL4| +// |I4HD4 I4HU4 I4TMP .....|.......................| <- ~31% wasted +#define YUV_SIZE_ENC (BPS * 16) +#define PRED_SIZE_ENC (32 * BPS + 16 * BPS + 8 * BPS) // I16+Chroma+I4 preds +#define Y_OFF_ENC (0) +#define U_OFF_ENC (16) +#define V_OFF_ENC (16 + 8) + +extern const uint16_t VP8Scan[16]; +extern const uint16_t VP8UVModeOffsets[4]; +extern const uint16_t VP8I16ModeOffsets[4]; +extern const uint16_t VP8I4ModeOffsets[NUM_BMODES]; + +// Layout of prediction blocks +// intra 16x16 +#define I16DC16 (0 * 16 * BPS) +#define I16TM16 (I16DC16 + 16) +#define I16VE16 (1 * 16 * BPS) +#define I16HE16 (I16VE16 + 16) +// chroma 8x8, two U/V blocks side by side (hence: 16x8 each) +#define C8DC8 (2 * 16 * BPS) +#define C8TM8 (C8DC8 + 1 * 16) +#define C8VE8 (2 * 16 * BPS + 8 * BPS) +#define C8HE8 (C8VE8 + 1 * 16) +// intra 4x4 +#define I4DC4 (3 * 16 * BPS + 0) +#define I4TM4 (I4DC4 + 4) +#define I4VE4 (I4DC4 + 8) +#define I4HE4 (I4DC4 + 12) +#define I4RD4 (I4DC4 + 16) +#define I4VR4 (I4DC4 + 20) +#define I4LD4 (I4DC4 + 24) +#define I4VL4 (I4DC4 + 28) +#define I4HD4 (3 * 16 * BPS + 4 * BPS) +#define I4HU4 (I4HD4 + 4) +#define I4TMP (I4HD4 + 8) + +typedef int64_t score_t; // type used for scores, rate, distortion +// Note that MAX_COST is not the maximum allowed by sizeof(score_t), +// in order to allow overflowing computations. +#define MAX_COST ((score_t)0x7fffffffffffffLL) + +#define QFIX 17 +#define BIAS(b) ((b) << (QFIX - 8)) +// Fun fact: this is the _only_ line where we're actually being lossy and +// discarding bits. +static WEBP_INLINE int QUANTDIV(uint32_t n, uint32_t iQ, uint32_t B) { + return (int)((n * iQ + B) >> QFIX); +} + +// Uncomment the following to remove token-buffer code: +// #define DISABLE_TOKEN_BUFFER + +// quality below which error-diffusion is enabled +#define ERROR_DIFFUSION_QUALITY 98 + +//------------------------------------------------------------------------------ +// Headers + +typedef uint32_t proba_t; // 16b + 16b +typedef uint8_t ProbaArray[NUM_CTX][NUM_PROBAS]; +typedef proba_t StatsArray[NUM_CTX][NUM_PROBAS]; +typedef uint16_t CostArray[NUM_CTX][MAX_VARIABLE_LEVEL + 1]; +typedef const uint16_t* (*CostArrayPtr)[NUM_CTX]; // for easy casting +typedef const uint16_t* CostArrayMap[16][NUM_CTX]; +typedef double LFStats[NUM_MB_SEGMENTS][MAX_LF_LEVELS]; // filter stats + +typedef struct VP8Encoder VP8Encoder; + +// segment features +typedef struct { + int num_segments_; // Actual number of segments. 1 segment only = unused. + int update_map_; // whether to update the segment map or not. + // must be 0 if there's only 1 segment. + int size_; // bit-cost for transmitting the segment map +} VP8EncSegmentHeader; + +// Struct collecting all frame-persistent probabilities. +typedef struct { + uint8_t segments_[3]; // probabilities for segment tree + uint8_t skip_proba_; // final probability of being skipped. + ProbaArray coeffs_[NUM_TYPES][NUM_BANDS]; // 1056 bytes + StatsArray stats_[NUM_TYPES][NUM_BANDS]; // 4224 bytes + CostArray level_cost_[NUM_TYPES][NUM_BANDS]; // 13056 bytes + CostArrayMap remapped_costs_[NUM_TYPES]; // 1536 bytes + int dirty_; // if true, need to call VP8CalculateLevelCosts() + int use_skip_proba_; // Note: we always use skip_proba for now. + int nb_skip_; // number of skipped blocks +} VP8EncProba; + +// Filter parameters. Not actually used in the code (we don't perform +// the in-loop filtering), but filled from user's config +typedef struct { + int simple_; // filtering type: 0=complex, 1=simple + int level_; // base filter level [0..63] + int sharpness_; // [0..7] + int i4x4_lf_delta_; // delta filter level for i4x4 relative to i16x16 +} VP8EncFilterHeader; + +//------------------------------------------------------------------------------ +// Informations about the macroblocks. + +typedef struct { + // block type + unsigned int type_:2; // 0=i4x4, 1=i16x16 + unsigned int uv_mode_:2; + unsigned int skip_:1; + unsigned int segment_:2; + uint8_t alpha_; // quantization-susceptibility +} VP8MBInfo; + +typedef struct VP8Matrix { + uint16_t q_[16]; // quantizer steps + uint16_t iq_[16]; // reciprocals, fixed point. + uint32_t bias_[16]; // rounding bias + uint32_t zthresh_[16]; // value below which a coefficient is zeroed + uint16_t sharpen_[16]; // frequency boosters for slight sharpening +} VP8Matrix; + +typedef struct { + VP8Matrix y1_, y2_, uv_; // quantization matrices + int alpha_; // quant-susceptibility, range [-127,127]. Zero is neutral. + // Lower values indicate a lower risk of blurriness. + int beta_; // filter-susceptibility, range [0,255]. + int quant_; // final segment quantizer. + int fstrength_; // final in-loop filtering strength + int max_edge_; // max edge delta (for filtering strength) + int min_disto_; // minimum distortion required to trigger filtering record + // reactivities + int lambda_i16_, lambda_i4_, lambda_uv_; + int lambda_mode_, lambda_trellis_, tlambda_; + int lambda_trellis_i16_, lambda_trellis_i4_, lambda_trellis_uv_; + + // lambda values for distortion-based evaluation + score_t i4_penalty_; // penalty for using Intra4 +} VP8SegmentInfo; + +typedef int8_t DError[2 /* u/v */][2 /* top or left */]; + +// Handy transient struct to accumulate score and info during RD-optimization +// and mode evaluation. +typedef struct { + score_t D, SD; // Distortion, spectral distortion + score_t H, R, score; // header bits, rate, score. + int16_t y_dc_levels[16]; // Quantized levels for luma-DC, luma-AC, chroma. + int16_t y_ac_levels[16][16]; + int16_t uv_levels[4 + 4][16]; + int mode_i16; // mode number for intra16 prediction + uint8_t modes_i4[16]; // mode numbers for intra4 predictions + int mode_uv; // mode number of chroma prediction + uint32_t nz; // non-zero blocks + int8_t derr[2][3]; // DC diffusion errors for U/V for blocks #1/2/3 +} VP8ModeScore; + +// Iterator structure to iterate through macroblocks, pointing to the +// right neighbouring data (samples, predictions, contexts, ...) +typedef struct { + int x_, y_; // current macroblock + uint8_t* yuv_in_; // input samples + uint8_t* yuv_out_; // output samples + uint8_t* yuv_out2_; // secondary buffer swapped with yuv_out_. + uint8_t* yuv_p_; // scratch buffer for prediction + VP8Encoder* enc_; // back-pointer + VP8MBInfo* mb_; // current macroblock + VP8BitWriter* bw_; // current bit-writer + uint8_t* preds_; // intra mode predictors (4x4 blocks) + uint32_t* nz_; // non-zero pattern + uint8_t i4_boundary_[37]; // 32+5 boundary samples needed by intra4x4 + uint8_t* i4_top_; // pointer to the current top boundary sample + int i4_; // current intra4x4 mode being tested + int top_nz_[9]; // top-non-zero context. + int left_nz_[9]; // left-non-zero. left_nz[8] is independent. + uint64_t bit_count_[4][3]; // bit counters for coded levels. + uint64_t luma_bits_; // macroblock bit-cost for luma + uint64_t uv_bits_; // macroblock bit-cost for chroma + LFStats* lf_stats_; // filter stats (borrowed from enc_) + int do_trellis_; // if true, perform extra level optimisation + int count_down_; // number of mb still to be processed + int count_down0_; // starting counter value (for progress) + int percent0_; // saved initial progress percent + + DError left_derr_; // left error diffusion (u/v) + DError* top_derr_; // top diffusion error - NULL if disabled + + uint8_t* y_left_; // left luma samples (addressable from index -1 to 15). + uint8_t* u_left_; // left u samples (addressable from index -1 to 7) + uint8_t* v_left_; // left v samples (addressable from index -1 to 7) + + uint8_t* y_top_; // top luma samples at position 'x_' + uint8_t* uv_top_; // top u/v samples at position 'x_', packed as 16 bytes + + // memory for storing y/u/v_left_ + uint8_t yuv_left_mem_[17 + 16 + 16 + 8 + WEBP_ALIGN_CST]; + // memory for yuv_* + uint8_t yuv_mem_[3 * YUV_SIZE_ENC + PRED_SIZE_ENC + WEBP_ALIGN_CST]; +} VP8EncIterator; + + // in iterator.c +// must be called first +void VP8IteratorInit(VP8Encoder* const enc, VP8EncIterator* const it); +// restart a scan +void VP8IteratorReset(VP8EncIterator* const it); +// reset iterator position to row 'y' +void VP8IteratorSetRow(VP8EncIterator* const it, int y); +// set count down (=number of iterations to go) +void VP8IteratorSetCountDown(VP8EncIterator* const it, int count_down); +// return true if iteration is finished +int VP8IteratorIsDone(const VP8EncIterator* const it); +// Import uncompressed samples from source. +// If tmp_32 is not NULL, import boundary samples too. +// tmp_32 is a 32-bytes scratch buffer that must be aligned in memory. +void VP8IteratorImport(VP8EncIterator* const it, uint8_t* const tmp_32); +// export decimated samples +void VP8IteratorExport(const VP8EncIterator* const it); +// go to next macroblock. Returns false if not finished. +int VP8IteratorNext(VP8EncIterator* const it); +// save the yuv_out_ boundary values to top_/left_ arrays for next iterations. +void VP8IteratorSaveBoundary(VP8EncIterator* const it); +// Report progression based on macroblock rows. Return 0 for user-abort request. +int VP8IteratorProgress(const VP8EncIterator* const it, int delta); +// Intra4x4 iterations +void VP8IteratorStartI4(VP8EncIterator* const it); +// returns true if not done. +int VP8IteratorRotateI4(VP8EncIterator* const it, + const uint8_t* const yuv_out); + +// Non-zero context setup/teardown +void VP8IteratorNzToBytes(VP8EncIterator* const it); +void VP8IteratorBytesToNz(VP8EncIterator* const it); + +// Helper functions to set mode properties +void VP8SetIntra16Mode(const VP8EncIterator* const it, int mode); +void VP8SetIntra4Mode(const VP8EncIterator* const it, const uint8_t* modes); +void VP8SetIntraUVMode(const VP8EncIterator* const it, int mode); +void VP8SetSkip(const VP8EncIterator* const it, int skip); +void VP8SetSegment(const VP8EncIterator* const it, int segment); + +//------------------------------------------------------------------------------ +// Paginated token buffer + +typedef struct VP8Tokens VP8Tokens; // struct details in token.c + +typedef struct { +#if !defined(DISABLE_TOKEN_BUFFER) + VP8Tokens* pages_; // first page + VP8Tokens** last_page_; // last page + uint16_t* tokens_; // set to (*last_page_)->tokens_ + int left_; // how many free tokens left before the page is full + int page_size_; // number of tokens per page +#endif + int error_; // true in case of malloc error +} VP8TBuffer; + +// initialize an empty buffer +void VP8TBufferInit(VP8TBuffer* const b, int page_size); +void VP8TBufferClear(VP8TBuffer* const b); // de-allocate pages memory + +#if !defined(DISABLE_TOKEN_BUFFER) + +// Finalizes bitstream when probabilities are known. +// Deletes the allocated token memory if final_pass is true. +int VP8EmitTokens(VP8TBuffer* const b, VP8BitWriter* const bw, + const uint8_t* const probas, int final_pass); + +// record the coding of coefficients without knowing the probabilities yet +int VP8RecordCoeffTokens(int ctx, const struct VP8Residual* const res, + VP8TBuffer* const tokens); + +// Estimate the final coded size given a set of 'probas'. +size_t VP8EstimateTokenSize(VP8TBuffer* const b, const uint8_t* const probas); + +#endif // !DISABLE_TOKEN_BUFFER + +//------------------------------------------------------------------------------ +// VP8Encoder + +struct VP8Encoder { + const WebPConfig* config_; // user configuration and parameters + WebPPicture* pic_; // input / output picture + + // headers + VP8EncFilterHeader filter_hdr_; // filtering information + VP8EncSegmentHeader segment_hdr_; // segment information + + int profile_; // VP8's profile, deduced from Config. + + // dimension, in macroblock units. + int mb_w_, mb_h_; + int preds_w_; // stride of the *preds_ prediction plane (=4*mb_w + 1) + + // number of partitions (1, 2, 4 or 8 = MAX_NUM_PARTITIONS) + int num_parts_; + + // per-partition boolean decoders. + VP8BitWriter bw_; // part0 + VP8BitWriter parts_[MAX_NUM_PARTITIONS]; // token partitions + VP8TBuffer tokens_; // token buffer + + int percent_; // for progress + + // transparency blob + int has_alpha_; + uint8_t* alpha_data_; // non-NULL if transparency is present + uint32_t alpha_data_size_; + WebPWorker alpha_worker_; + + // quantization info (one set of DC/AC dequant factor per segment) + VP8SegmentInfo dqm_[NUM_MB_SEGMENTS]; + int base_quant_; // nominal quantizer value. Only used + // for relative coding of segments' quant. + int alpha_; // global susceptibility (<=> complexity) + int uv_alpha_; // U/V quantization susceptibility + // global offset of quantizers, shared by all segments + int dq_y1_dc_; + int dq_y2_dc_, dq_y2_ac_; + int dq_uv_dc_, dq_uv_ac_; + + // probabilities and statistics + VP8EncProba proba_; + uint64_t sse_[4]; // sum of Y/U/V/A squared errors for all macroblocks + uint64_t sse_count_; // pixel count for the sse_[] stats + int coded_size_; + int residual_bytes_[3][4]; + int block_count_[3]; + + // quality/speed settings + int method_; // 0=fastest, 6=best/slowest. + VP8RDLevel rd_opt_level_; // Deduced from method_. + int max_i4_header_bits_; // partition #0 safeness factor + int mb_header_limit_; // rough limit for header bits per MB + int thread_level_; // derived from config->thread_level + int do_search_; // derived from config->target_XXX + int use_tokens_; // if true, use token buffer + + // Memory + VP8MBInfo* mb_info_; // contextual macroblock infos (mb_w_ + 1) + uint8_t* preds_; // predictions modes: (4*mb_w+1) * (4*mb_h+1) + uint32_t* nz_; // non-zero bit context: mb_w+1 + uint8_t* y_top_; // top luma samples. + uint8_t* uv_top_; // top u/v samples. + // U and V are packed into 16 bytes (8 U + 8 V) + LFStats* lf_stats_; // autofilter stats (if NULL, autofilter is off) + DError* top_derr_; // diffusion error (NULL if disabled) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + + // in tree.c +extern const uint8_t VP8CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS]; +extern const uint8_t + VP8CoeffsUpdateProba[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS]; +// Reset the token probabilities to their initial (default) values +void VP8DefaultProbas(VP8Encoder* const enc); +// Write the token probabilities +void VP8WriteProbas(VP8BitWriter* const bw, const VP8EncProba* const probas); +// Writes the partition #0 modes (that is: all intra modes) +void VP8CodeIntraModes(VP8Encoder* const enc); + + // in syntax.c +// Generates the final bitstream by coding the partition0 and headers, +// and appending an assembly of all the pre-coded token partitions. +// Return true if everything is ok. +int VP8EncWrite(VP8Encoder* const enc); +// Release memory allocated for bit-writing in VP8EncLoop & seq. +void VP8EncFreeBitWriters(VP8Encoder* const enc); + + // in frame.c +extern const uint8_t VP8Cat3[]; +extern const uint8_t VP8Cat4[]; +extern const uint8_t VP8Cat5[]; +extern const uint8_t VP8Cat6[]; + +// Form all the four Intra16x16 predictions in the yuv_p_ cache +void VP8MakeLuma16Preds(const VP8EncIterator* const it); +// Form all the four Chroma8x8 predictions in the yuv_p_ cache +void VP8MakeChroma8Preds(const VP8EncIterator* const it); +// Form all the ten Intra4x4 predictions in the yuv_p_ cache +// for the 4x4 block it->i4_ +void VP8MakeIntra4Preds(const VP8EncIterator* const it); +// Rate calculation +int VP8GetCostLuma16(VP8EncIterator* const it, const VP8ModeScore* const rd); +int VP8GetCostLuma4(VP8EncIterator* const it, const int16_t levels[16]); +int VP8GetCostUV(VP8EncIterator* const it, const VP8ModeScore* const rd); +// Main coding calls +int VP8EncLoop(VP8Encoder* const enc); +int VP8EncTokenLoop(VP8Encoder* const enc); + + // in webpenc.c +// Assign an error code to a picture. Return false for convenience. +int WebPEncodingSetError(const WebPPicture* const pic, WebPEncodingError error); +int WebPReportProgress(const WebPPicture* const pic, + int percent, int* const percent_store); + + // in analysis.c +// Main analysis loop. Decides the segmentations and complexity. +// Assigns a first guess for Intra16 and uvmode_ prediction modes. +int VP8EncAnalyze(VP8Encoder* const enc); + + // in quant.c +// Sets up segment's quantization values, base_quant_ and filter strengths. +void VP8SetSegmentParams(VP8Encoder* const enc, float quality); +// Pick best modes and fills the levels. Returns true if skipped. +int VP8Decimate(VP8EncIterator* WEBP_RESTRICT const it, + VP8ModeScore* WEBP_RESTRICT const rd, + VP8RDLevel rd_opt); + + // in alpha.c +void VP8EncInitAlpha(VP8Encoder* const enc); // initialize alpha compression +int VP8EncStartAlpha(VP8Encoder* const enc); // start alpha coding process +int VP8EncFinishAlpha(VP8Encoder* const enc); // finalize compressed data +int VP8EncDeleteAlpha(VP8Encoder* const enc); // delete compressed data + +// autofilter +void VP8InitFilter(VP8EncIterator* const it); +void VP8StoreFilterStats(VP8EncIterator* const it); +void VP8AdjustFilterStrength(VP8EncIterator* const it); + +// returns the approximate filtering strength needed to smooth a edge +// step of 'delta', given a sharpness parameter 'sharpness'. +int VP8FilterStrengthFromDelta(int sharpness, int delta); + + // misc utils for picture_*.c: + +// Returns true if 'picture' is non-NULL and dimensions/colorspace are within +// their valid ranges. If returning false, the 'error_code' in 'picture' is +// updated. +int WebPValidatePicture(const WebPPicture* const picture); + +// Remove reference to the ARGB/YUVA buffer (doesn't free anything). +void WebPPictureResetBuffers(WebPPicture* const picture); + +// Allocates ARGB buffer according to set width/height (previous one is +// always free'd). Preserves the YUV(A) buffer. Returns false in case of error +// (invalid param, out-of-memory). +int WebPPictureAllocARGB(WebPPicture* const picture); + +// Allocates YUVA buffer according to set width/height (previous one is always +// free'd). Uses picture->csp to determine whether an alpha buffer is needed. +// Preserves the ARGB buffer. +// Returns false in case of error (invalid param, out-of-memory). +int WebPPictureAllocYUVA(WebPPicture* const picture); + +// Replace samples that are fully transparent by 'color' to help compressibility +// (no guarantee, though). Assumes pic->use_argb is true. +void WebPReplaceTransparentPixels(WebPPicture* const pic, uint32_t color); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_ENC_VP8I_ENC_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_inl_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_inl_utils.h new file mode 100644 index 0000000000..24f3af7b54 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_inl_utils.h @@ -0,0 +1,196 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Specific inlined methods for boolean decoder [VP8GetBit() ...] +// This file should be included by the .c sources that actually need to call +// these methods. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_INL_UTILS_H_ +#define WEBP_UTILS_BIT_READER_INL_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include // for memcpy + +#include "src/dsp/dsp.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Derived type lbit_t = natural type for memory I/O + +#if (BITS > 32) +typedef uint64_t lbit_t; +#elif (BITS > 16) +typedef uint32_t lbit_t; +#elif (BITS > 8) +typedef uint16_t lbit_t; +#else +typedef uint8_t lbit_t; +#endif + +extern const uint8_t kVP8Log2Range[128]; +extern const uint8_t kVP8NewRange[128]; + +// special case for the tail byte-reading +void VP8LoadFinalBytes(VP8BitReader* const br); + +//------------------------------------------------------------------------------ +// Inlined critical functions + +// makes sure br->value_ has at least BITS bits worth of data +static WEBP_UBSAN_IGNORE_UNDEF WEBP_INLINE +void VP8LoadNewBytes(VP8BitReader* WEBP_RESTRICT const br) { + assert(br != NULL && br->buf_ != NULL); + // Read 'BITS' bits at a time if possible. + if (br->buf_ < br->buf_max_) { + // convert memory type to register type (with some zero'ing!) + bit_t bits; +#if defined(WEBP_USE_MIPS32) + // This is needed because of un-aligned read. + lbit_t in_bits; + lbit_t* p_buf_ = (lbit_t*)br->buf_; + __asm__ volatile( + ".set push \n\t" + ".set at \n\t" + ".set macro \n\t" + "ulw %[in_bits], 0(%[p_buf_]) \n\t" + ".set pop \n\t" + : [in_bits]"=r"(in_bits) + : [p_buf_]"r"(p_buf_) + : "memory", "at" + ); +#else + lbit_t in_bits; + memcpy(&in_bits, br->buf_, sizeof(in_bits)); +#endif + br->buf_ += BITS >> 3; +#if !defined(WORDS_BIGENDIAN) +#if (BITS > 32) + bits = BSwap64(in_bits); + bits >>= 64 - BITS; +#elif (BITS >= 24) + bits = BSwap32(in_bits); + bits >>= (32 - BITS); +#elif (BITS == 16) + bits = BSwap16(in_bits); +#else // BITS == 8 + bits = (bit_t)in_bits; +#endif // BITS > 32 +#else // WORDS_BIGENDIAN + bits = (bit_t)in_bits; + if (BITS != 8 * sizeof(bit_t)) bits >>= (8 * sizeof(bit_t) - BITS); +#endif + br->value_ = bits | (br->value_ << BITS); + br->bits_ += BITS; + } else { + VP8LoadFinalBytes(br); // no need to be inlined + } +} + +// Read a bit with proba 'prob'. Speed-critical function! +static WEBP_INLINE int VP8GetBit(VP8BitReader* WEBP_RESTRICT const br, + int prob, const char label[]) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range_ value. + range_t range = br->range_; + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value_ >> pos); + const int bit = (value > split); + if (bit) { + range -= split; + br->value_ -= (bit_t)(split + 1) << pos; + } else { + range = split + 1; + } + { + const int shift = 7 ^ BitsLog2Floor(range); + range <<= shift; + br->bits_ -= shift; + } + br->range_ = range - 1; + BT_TRACK(br); + return bit; + } +} + +// simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +int VP8GetSigned(VP8BitReader* WEBP_RESTRICT const br, int v, + const char label[]) { + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = br->range_ >> 1; + const range_t value = (range_t)(br->value_ >> pos); + const int32_t mask = (int32_t)(split - value) >> 31; // -1 or 0 + br->bits_ -= 1; + br->range_ += (range_t)mask; + br->range_ |= 1; + br->value_ -= (bit_t)((split + 1) & (uint32_t)mask) << pos; + BT_TRACK(br); + return (v ^ mask) - mask; + } +} + +static WEBP_INLINE int VP8GetBitAlt(VP8BitReader* WEBP_RESTRICT const br, + int prob, const char label[]) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range_ value. + range_t range = br->range_; + if (br->bits_ < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits_; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value_ >> pos); + int bit; // Don't use 'const int bit = (value > split);", it's slower. + if (value > split) { + range -= split + 1; + br->value_ -= (bit_t)(split + 1) << pos; + bit = 1; + } else { + range = split; + bit = 0; + } + if (range <= (range_t)0x7e) { + const int shift = kVP8Log2Range[range]; + range = kVP8NewRange[range]; + br->bits_ -= shift; + } + br->range_ = range; + BT_TRACK(br); + return bit; + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_READER_INL_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.c new file mode 100644 index 0000000000..a26557aa49 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.c @@ -0,0 +1,299 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder non-inlined methods +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/dsp/cpu.h" +#include "src/utils/bit_reader_inl_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8BitReader + +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, + size_t size) { + br->buf_ = start; + br->buf_end_ = start + size; + br->buf_max_ = + (size >= sizeof(lbit_t)) ? start + size - sizeof(lbit_t) + 1 + : start; +} + +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size) { + assert(br != NULL); + assert(start != NULL); + assert(size < (1u << 31)); // limit ensured by format and upstream checks + br->range_ = 255 - 1; + br->value_ = 0; + br->bits_ = -8; // to load the very first 8bits + br->eof_ = 0; + VP8BitReaderSetBuffer(br, start, size); + VP8LoadNewBytes(br); +} + +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset) { + if (br->buf_ != NULL) { + br->buf_ += offset; + br->buf_end_ += offset; + br->buf_max_ += offset; + } +} + +const uint8_t kVP8Log2Range[128] = { + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 +}; + +// range = ((range - 1) << kVP8Log2Range[range]) + 1 +const uint8_t kVP8NewRange[128] = { + 127, 127, 191, 127, 159, 191, 223, 127, + 143, 159, 175, 191, 207, 223, 239, 127, + 135, 143, 151, 159, 167, 175, 183, 191, + 199, 207, 215, 223, 231, 239, 247, 127, + 131, 135, 139, 143, 147, 151, 155, 159, + 163, 167, 171, 175, 179, 183, 187, 191, + 195, 199, 203, 207, 211, 215, 219, 223, + 227, 231, 235, 239, 243, 247, 251, 127, + 129, 131, 133, 135, 137, 139, 141, 143, + 145, 147, 149, 151, 153, 155, 157, 159, + 161, 163, 165, 167, 169, 171, 173, 175, + 177, 179, 181, 183, 185, 187, 189, 191, + 193, 195, 197, 199, 201, 203, 205, 207, + 209, 211, 213, 215, 217, 219, 221, 223, + 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 +}; + +void VP8LoadFinalBytes(VP8BitReader* const br) { + assert(br != NULL && br->buf_ != NULL); + // Only read 8bits at a time + if (br->buf_ < br->buf_end_) { + br->bits_ += 8; + br->value_ = (bit_t)(*br->buf_++) | (br->value_ << 8); + } else if (!br->eof_) { + br->value_ <<= 8; + br->bits_ += 8; + br->eof_ = 1; + } else { + br->bits_ = 0; // This is to avoid undefined behaviour with shifts. + } +} + +//------------------------------------------------------------------------------ +// Higher-level calls + +uint32_t VP8GetValue(VP8BitReader* const br, int bits, const char label[]) { + uint32_t v = 0; + while (bits-- > 0) { + v |= VP8GetBit(br, 0x80, label) << bits; + } + return v; +} + +int32_t VP8GetSignedValue(VP8BitReader* const br, int bits, + const char label[]) { + const int value = VP8GetValue(br, bits, label); + return VP8Get(br, label) ? -value : value; +} + +//------------------------------------------------------------------------------ +// VP8LBitReader + +#define VP8L_LOG8_WBITS 4 // Number of bytes needed to store VP8L_WBITS bits. + +#if defined(__arm__) || defined(_M_ARM) || WEBP_AARCH64 || \ + defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64__) || defined(_M_X64) +#define VP8L_USE_FAST_LOAD +#endif + +static const uint32_t kBitMask[VP8L_MAX_NUM_BIT_READ + 1] = { + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff +}; + +void VP8LInitBitReader(VP8LBitReader* const br, const uint8_t* const start, + size_t length) { + size_t i; + vp8l_val_t value = 0; + assert(br != NULL); + assert(start != NULL); + assert(length < 0xfffffff8u); // can't happen with a RIFF chunk. + + br->len_ = length; + br->val_ = 0; + br->bit_pos_ = 0; + br->eos_ = 0; + + if (length > sizeof(br->val_)) { + length = sizeof(br->val_); + } + for (i = 0; i < length; ++i) { + value |= (vp8l_val_t)start[i] << (8 * i); + } + br->val_ = value; + br->pos_ = length; + br->buf_ = start; +} + +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buf, size_t len) { + assert(br != NULL); + assert(buf != NULL); + assert(len < 0xfffffff8u); // can't happen with a RIFF chunk. + br->buf_ = buf; + br->len_ = len; + // pos_ > len_ should be considered a param error. + br->eos_ = (br->pos_ > br->len_) || VP8LIsEndOfStream(br); +} + +static void VP8LSetEndOfStream(VP8LBitReader* const br) { + br->eos_ = 1; + br->bit_pos_ = 0; // To avoid undefined behaviour with shifts. +} + +// If not at EOS, reload up to VP8L_LBITS byte-by-byte +static void ShiftBytes(VP8LBitReader* const br) { + while (br->bit_pos_ >= 8 && br->pos_ < br->len_) { + br->val_ >>= 8; + br->val_ |= ((vp8l_val_t)br->buf_[br->pos_]) << (VP8L_LBITS - 8); + ++br->pos_; + br->bit_pos_ -= 8; + } + if (VP8LIsEndOfStream(br)) { + VP8LSetEndOfStream(br); + } +} + +void VP8LDoFillBitWindow(VP8LBitReader* const br) { + assert(br->bit_pos_ >= VP8L_WBITS); +#if defined(VP8L_USE_FAST_LOAD) + if (br->pos_ + sizeof(br->val_) < br->len_) { + br->val_ >>= VP8L_WBITS; + br->bit_pos_ -= VP8L_WBITS; + br->val_ |= (vp8l_val_t)HToLE32(WebPMemToUint32(br->buf_ + br->pos_)) << + (VP8L_LBITS - VP8L_WBITS); + br->pos_ += VP8L_LOG8_WBITS; + return; + } +#endif + ShiftBytes(br); // Slow path. +} + +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits) { + assert(n_bits >= 0); + // Flag an error if end_of_stream or n_bits is more than allowed limit. + if (!br->eos_ && n_bits <= VP8L_MAX_NUM_BIT_READ) { + const uint32_t val = VP8LPrefetchBits(br) & kBitMask[n_bits]; + const int new_bits = br->bit_pos_ + n_bits; + br->bit_pos_ = new_bits; + ShiftBytes(br); + return val; + } else { + VP8LSetEndOfStream(br); + return 0; + } +} + +//------------------------------------------------------------------------------ +// Bit-tracing tool + +#if (BITTRACE > 0) + +#include // for atexit() +#include +#include + +#define MAX_NUM_LABELS 32 +static struct { + const char* label; + int size; + int count; +} kLabels[MAX_NUM_LABELS]; + +static int last_label = 0; +static int last_pos = 0; +static const uint8_t* buf_start = NULL; +static int init_done = 0; + +static void PrintBitTraces(void) { + int i; + int scale = 1; + int total = 0; + const char* units = "bits"; +#if (BITTRACE == 2) + scale = 8; + units = "bytes"; +#endif + for (i = 0; i < last_label; ++i) total += kLabels[i].size; + if (total < 1) total = 1; // avoid rounding errors + printf("=== Bit traces ===\n"); + for (i = 0; i < last_label; ++i) { + const int skip = 16 - (int)strlen(kLabels[i].label); + const int value = (kLabels[i].size + scale - 1) / scale; + assert(skip > 0); + printf("%s \%*s: %6d %s \t[%5.2f%%] [count: %7d]\n", + kLabels[i].label, skip, "", value, units, + 100.f * kLabels[i].size / total, + kLabels[i].count); + } + total = (total + scale - 1) / scale; + printf("Total: %d %s\n", total, units); +} + +void BitTrace(const struct VP8BitReader* const br, const char label[]) { + int i, pos; + if (!init_done) { + memset(kLabels, 0, sizeof(kLabels)); + atexit(PrintBitTraces); + buf_start = br->buf_; + init_done = 1; + } + pos = (int)(br->buf_ - buf_start) * 8 - br->bits_; + // if there's a too large jump, we've changed partition -> reset counter + if (abs(pos - last_pos) > 32) { + buf_start = br->buf_; + pos = 0; + last_pos = 0; + } + if (br->range_ >= 0x7f) pos += kVP8Log2Range[br->range_ - 0x7f]; + for (i = 0; i < last_label; ++i) { + if (!strcmp(label, kLabels[i].label)) break; + } + if (i == MAX_NUM_LABELS) abort(); // overflow! + kLabels[i].label = label; + kLabels[i].size += pos - last_pos; + kLabels[i].count += 1; + if (i == last_label) ++last_label; + last_pos = pos; +} + +#endif // BITTRACE > 0 + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.h new file mode 100644 index 0000000000..25ff31e5d9 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_reader_utils.h @@ -0,0 +1,195 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora (vikaas.arora@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_UTILS_H_ +#define WEBP_UTILS_BIT_READER_UTILS_H_ + +#include +#ifdef _MSC_VER +#include // _byteswap_ulong +#endif +#include "src/dsp/cpu.h" +#include "src/webp/types.h" + +// Warning! This macro triggers quite some MACRO wizardry around func signature! +#if !defined(BITTRACE) +#define BITTRACE 0 // 0 = off, 1 = print bits, 2 = print bytes +#endif + +#if (BITTRACE > 0) +struct VP8BitReader; +extern void BitTrace(const struct VP8BitReader* const br, const char label[]); +#define BT_TRACK(br) BitTrace(br, label) +#define VP8Get(BR, L) VP8GetValue(BR, 1, L) +#else +#define BT_TRACK(br) +// We'll REMOVE the 'const char label[]' from all signatures and calls (!!): +#define VP8GetValue(BR, N, L) VP8GetValue(BR, N) +#define VP8Get(BR, L) VP8GetValue(BR, 1, L) +#define VP8GetSignedValue(BR, N, L) VP8GetSignedValue(BR, N) +#define VP8GetBit(BR, P, L) VP8GetBit(BR, P) +#define VP8GetBitAlt(BR, P, L) VP8GetBitAlt(BR, P) +#define VP8GetSigned(BR, V, L) VP8GetSigned(BR, V) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// The Boolean decoder needs to maintain infinite precision on the value_ field. +// However, since range_ is only 8bit, we only need an active window of 8 bits +// for value_. Left bits (MSB) gets zeroed and shifted away when value_ falls +// below 128, range_ is updated, and fresh bits read from the bitstream are +// brought in as LSB. To avoid reading the fresh bits one by one (slow), we +// cache BITS of them ahead. The total of (BITS + 8) bits must fit into a +// natural register (with type bit_t). To fetch BITS bits from bitstream we +// use a type lbit_t. +// +// BITS can be any multiple of 8 from 8 to 56 (inclusive). +// Pick values that fit natural register size. + +#if defined(__i386__) || defined(_M_IX86) // x86 32bit +#define BITS 24 +#elif defined(__x86_64__) || defined(_M_X64) // x86 64bit +#define BITS 56 +#elif defined(__arm__) || defined(_M_ARM) // ARM +#define BITS 24 +#elif WEBP_AARCH64 // ARM 64bit +#define BITS 56 +#elif defined(__mips__) // MIPS +#define BITS 24 +#else // reasonable default +#define BITS 24 +#endif + +//------------------------------------------------------------------------------ +// Derived types and constants: +// bit_t = natural register type for storing 'value_' (which is BITS+8 bits) +// range_t = register for 'range_' (which is 8bits only) + +#if (BITS > 24) +typedef uint64_t bit_t; +#else +typedef uint32_t bit_t; +#endif + +typedef uint32_t range_t; + +//------------------------------------------------------------------------------ +// Bitreader + +typedef struct VP8BitReader VP8BitReader; +struct VP8BitReader { + // boolean decoder (keep the field ordering as is!) + bit_t value_; // current value + range_t range_; // current range minus 1. In [127, 254] interval. + int bits_; // number of valid bits left + // read buffer + const uint8_t* buf_; // next byte to be read + const uint8_t* buf_end_; // end of read buffer + const uint8_t* buf_max_; // max packed-read position on buffer + int eof_; // true if input is exhausted +}; + +// Initialize the bit reader and the boolean decoder. +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size); +// Sets the working read buffer. +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, size_t size); + +// Update internal pointers to displace the byte buffer by the +// relative offset 'offset'. +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset); + +// return the next value made of 'num_bits' bits +uint32_t VP8GetValue(VP8BitReader* const br, int num_bits, const char label[]); + +// return the next value with sign-extension. +int32_t VP8GetSignedValue(VP8BitReader* const br, int num_bits, + const char label[]); + +// bit_reader_inl.h will implement the following methods: +// static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob, ...) +// static WEBP_INLINE int VP8GetSigned(VP8BitReader* const br, int v, ...) +// and should be included by the .c files that actually need them. +// This is to avoid recompiling the whole library whenever this file is touched, +// and also allowing platform-specific ad-hoc hacks. + +// ----------------------------------------------------------------------------- +// Bitreader for lossless format + +// maximum number of bits (inclusive) the bit-reader can handle: +#define VP8L_MAX_NUM_BIT_READ 24 + +#define VP8L_LBITS 64 // Number of bits prefetched (= bit-size of vp8l_val_t). +#define VP8L_WBITS 32 // Minimum number of bytes ready after VP8LFillBitWindow. + +typedef uint64_t vp8l_val_t; // right now, this bit-reader can only use 64bit. + +typedef struct { + vp8l_val_t val_; // pre-fetched bits + const uint8_t* buf_; // input byte buffer + size_t len_; // buffer length + size_t pos_; // byte position in buf_ + int bit_pos_; // current bit-reading position in val_ + int eos_; // true if a bit was read past the end of buffer +} VP8LBitReader; + +void VP8LInitBitReader(VP8LBitReader* const br, + const uint8_t* const start, + size_t length); + +// Sets a new data buffer. +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buffer, size_t length); + +// Reads the specified number of bits from read buffer. +// Flags an error in case end_of_stream or n_bits is more than the allowed limit +// of VP8L_MAX_NUM_BIT_READ (inclusive). +// Flags eos_ if this read attempt is going to cross the read buffer. +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits); + +// Return the prefetched bits, so they can be looked up. +static WEBP_INLINE uint32_t VP8LPrefetchBits(VP8LBitReader* const br) { + return (uint32_t)(br->val_ >> (br->bit_pos_ & (VP8L_LBITS - 1))); +} + +// Returns true if there was an attempt at reading bit past the end of +// the buffer. Doesn't set br->eos_ flag. +static WEBP_INLINE int VP8LIsEndOfStream(const VP8LBitReader* const br) { + assert(br->pos_ <= br->len_); + return br->eos_ || ((br->pos_ == br->len_) && (br->bit_pos_ > VP8L_LBITS)); +} + +// For jumping over a number of bits in the bit stream when accessed with +// VP8LPrefetchBits and VP8LFillBitWindow. +// This function does *not* set br->eos_, since it's speed-critical. +// Use with extreme care! +static WEBP_INLINE void VP8LSetBitPos(VP8LBitReader* const br, int val) { + br->bit_pos_ = val; +} + +// Advances the read buffer by 4 bytes to make room for reading next 32 bits. +// Speed critical, but infrequent part of the code can be non-inlined. +extern void VP8LDoFillBitWindow(VP8LBitReader* const br); +static WEBP_INLINE void VP8LFillBitWindow(VP8LBitReader* const br) { + if (br->bit_pos_ >= VP8L_WBITS) VP8LDoFillBitWindow(br); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_READER_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.c new file mode 100644 index 0000000000..2f408508f1 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.c @@ -0,0 +1,347 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Bit writing and boolean coder +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora (vikaas.arora@gmail.com) + +#include +#include // for memcpy() +#include + +#include "src/utils/bit_writer_utils.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8BitWriter + +static int BitWriterResize(VP8BitWriter* const bw, size_t extra_size) { + uint8_t* new_buf; + size_t new_size; + const uint64_t needed_size_64b = (uint64_t)bw->pos_ + extra_size; + const size_t needed_size = (size_t)needed_size_64b; + if (needed_size_64b != needed_size) { + bw->error_ = 1; + return 0; + } + if (needed_size <= bw->max_pos_) return 1; + // If the following line wraps over 32bit, the test just after will catch it. + new_size = 2 * bw->max_pos_; + if (new_size < needed_size) new_size = needed_size; + if (new_size < 1024) new_size = 1024; + new_buf = (uint8_t*)WebPSafeMalloc(1ULL, new_size); + if (new_buf == NULL) { + bw->error_ = 1; + return 0; + } + if (bw->pos_ > 0) { + assert(bw->buf_ != NULL); + memcpy(new_buf, bw->buf_, bw->pos_); + } + WebPSafeFree(bw->buf_); + bw->buf_ = new_buf; + bw->max_pos_ = new_size; + return 1; +} + +static void Flush(VP8BitWriter* const bw) { + const int s = 8 + bw->nb_bits_; + const int32_t bits = bw->value_ >> s; + assert(bw->nb_bits_ >= 0); + bw->value_ -= bits << s; + bw->nb_bits_ -= 8; + if ((bits & 0xff) != 0xff) { + size_t pos = bw->pos_; + if (!BitWriterResize(bw, bw->run_ + 1)) { + return; + } + if (bits & 0x100) { // overflow -> propagate carry over pending 0xff's + if (pos > 0) bw->buf_[pos - 1]++; + } + if (bw->run_ > 0) { + const int value = (bits & 0x100) ? 0x00 : 0xff; + for (; bw->run_ > 0; --bw->run_) bw->buf_[pos++] = value; + } + bw->buf_[pos++] = bits & 0xff; + bw->pos_ = pos; + } else { + bw->run_++; // delay writing of bytes 0xff, pending eventual carry. + } +} + +//------------------------------------------------------------------------------ +// renormalization + +static const uint8_t kNorm[128] = { // renorm_sizes[i] = 8 - log2(i) + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 +}; + +// range = ((range + 1) << kVP8Log2Range[range]) - 1 +static const uint8_t kNewRange[128] = { + 127, 127, 191, 127, 159, 191, 223, 127, 143, 159, 175, 191, 207, 223, 239, + 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, + 247, 127, 131, 135, 139, 143, 147, 151, 155, 159, 163, 167, 171, 175, 179, + 183, 187, 191, 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, + 243, 247, 251, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, + 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, + 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, + 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 +}; + +int VP8PutBit(VP8BitWriter* const bw, int bit, int prob) { + const int split = (bw->range_ * prob) >> 8; + if (bit) { + bw->value_ += split + 1; + bw->range_ -= split + 1; + } else { + bw->range_ = split; + } + if (bw->range_ < 127) { // emit 'shift' bits out and renormalize + const int shift = kNorm[bw->range_]; + bw->range_ = kNewRange[bw->range_]; + bw->value_ <<= shift; + bw->nb_bits_ += shift; + if (bw->nb_bits_ > 0) Flush(bw); + } + return bit; +} + +int VP8PutBitUniform(VP8BitWriter* const bw, int bit) { + const int split = bw->range_ >> 1; + if (bit) { + bw->value_ += split + 1; + bw->range_ -= split + 1; + } else { + bw->range_ = split; + } + if (bw->range_ < 127) { + bw->range_ = kNewRange[bw->range_]; + bw->value_ <<= 1; + bw->nb_bits_ += 1; + if (bw->nb_bits_ > 0) Flush(bw); + } + return bit; +} + +void VP8PutBits(VP8BitWriter* const bw, uint32_t value, int nb_bits) { + uint32_t mask; + assert(nb_bits > 0 && nb_bits < 32); + for (mask = 1u << (nb_bits - 1); mask; mask >>= 1) { + VP8PutBitUniform(bw, value & mask); + } +} + +void VP8PutSignedBits(VP8BitWriter* const bw, int value, int nb_bits) { + if (!VP8PutBitUniform(bw, value != 0)) return; + if (value < 0) { + VP8PutBits(bw, ((-value) << 1) | 1, nb_bits + 1); + } else { + VP8PutBits(bw, value << 1, nb_bits + 1); + } +} + +//------------------------------------------------------------------------------ + +int VP8BitWriterInit(VP8BitWriter* const bw, size_t expected_size) { + bw->range_ = 255 - 1; + bw->value_ = 0; + bw->run_ = 0; + bw->nb_bits_ = -8; + bw->pos_ = 0; + bw->max_pos_ = 0; + bw->error_ = 0; + bw->buf_ = NULL; + return (expected_size > 0) ? BitWriterResize(bw, expected_size) : 1; +} + +uint8_t* VP8BitWriterFinish(VP8BitWriter* const bw) { + VP8PutBits(bw, 0, 9 - bw->nb_bits_); + bw->nb_bits_ = 0; // pad with zeroes + Flush(bw); + return bw->buf_; +} + +int VP8BitWriterAppend(VP8BitWriter* const bw, + const uint8_t* data, size_t size) { + assert(data != NULL); + if (bw->nb_bits_ != -8) return 0; // Flush() must have been called + if (!BitWriterResize(bw, size)) return 0; + memcpy(bw->buf_ + bw->pos_, data, size); + bw->pos_ += size; + return 1; +} + +void VP8BitWriterWipeOut(VP8BitWriter* const bw) { + if (bw != NULL) { + WebPSafeFree(bw->buf_); + memset(bw, 0, sizeof(*bw)); + } +} + +//------------------------------------------------------------------------------ +// VP8LBitWriter + +// This is the minimum amount of size the memory buffer is guaranteed to grow +// when extra space is needed. +#define MIN_EXTRA_SIZE (32768ULL) + +// Returns 1 on success. +static int VP8LBitWriterResize(VP8LBitWriter* const bw, size_t extra_size) { + uint8_t* allocated_buf; + size_t allocated_size; + const size_t max_bytes = bw->end_ - bw->buf_; + const size_t current_size = bw->cur_ - bw->buf_; + const uint64_t size_required_64b = (uint64_t)current_size + extra_size; + const size_t size_required = (size_t)size_required_64b; + if (size_required != size_required_64b) { + bw->error_ = 1; + return 0; + } + if (max_bytes > 0 && size_required <= max_bytes) return 1; + allocated_size = (3 * max_bytes) >> 1; + if (allocated_size < size_required) allocated_size = size_required; + // make allocated size multiple of 1k + allocated_size = (((allocated_size >> 10) + 1) << 10); + allocated_buf = (uint8_t*)WebPSafeMalloc(1ULL, allocated_size); + if (allocated_buf == NULL) { + bw->error_ = 1; + return 0; + } + if (current_size > 0) { + memcpy(allocated_buf, bw->buf_, current_size); + } + WebPSafeFree(bw->buf_); + bw->buf_ = allocated_buf; + bw->cur_ = bw->buf_ + current_size; + bw->end_ = bw->buf_ + allocated_size; + return 1; +} + +int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size) { + memset(bw, 0, sizeof(*bw)); + return VP8LBitWriterResize(bw, expected_size); +} + +int VP8LBitWriterClone(const VP8LBitWriter* const src, + VP8LBitWriter* const dst) { + const size_t current_size = src->cur_ - src->buf_; + assert(src->cur_ >= src->buf_ && src->cur_ <= src->end_); + if (!VP8LBitWriterResize(dst, current_size)) return 0; + memcpy(dst->buf_, src->buf_, current_size); + dst->bits_ = src->bits_; + dst->used_ = src->used_; + dst->error_ = src->error_; + dst->cur_ = dst->buf_ + current_size; + return 1; +} + +void VP8LBitWriterWipeOut(VP8LBitWriter* const bw) { + if (bw != NULL) { + WebPSafeFree(bw->buf_); + memset(bw, 0, sizeof(*bw)); + } +} + +void VP8LBitWriterReset(const VP8LBitWriter* const bw_init, + VP8LBitWriter* const bw) { + bw->bits_ = bw_init->bits_; + bw->used_ = bw_init->used_; + bw->cur_ = bw->buf_ + (bw_init->cur_ - bw_init->buf_); + assert(bw->cur_ <= bw->end_); + bw->error_ = bw_init->error_; +} + +void VP8LBitWriterSwap(VP8LBitWriter* const src, VP8LBitWriter* const dst) { + const VP8LBitWriter tmp = *src; + *src = *dst; + *dst = tmp; +} + +void VP8LPutBitsFlushBits(VP8LBitWriter* const bw) { + // If needed, make some room by flushing some bits out. + if (bw->cur_ + VP8L_WRITER_BYTES > bw->end_) { + const uint64_t extra_size = (bw->end_ - bw->buf_) + MIN_EXTRA_SIZE; + if (!CheckSizeOverflow(extra_size) || + !VP8LBitWriterResize(bw, (size_t)extra_size)) { + bw->cur_ = bw->buf_; + bw->error_ = 1; + return; + } + } + *(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)bw->bits_); + bw->cur_ += VP8L_WRITER_BYTES; + bw->bits_ >>= VP8L_WRITER_BITS; + bw->used_ -= VP8L_WRITER_BITS; +} + +void VP8LPutBitsInternal(VP8LBitWriter* const bw, uint32_t bits, int n_bits) { + assert(n_bits <= 32); + // That's the max we can handle: + assert(sizeof(vp8l_wtype_t) == 2); + if (n_bits > 0) { + vp8l_atype_t lbits = bw->bits_; + int used = bw->used_; + // Special case of overflow handling for 32bit accumulator (2-steps flush). +#if VP8L_WRITER_BITS == 16 + if (used + n_bits >= VP8L_WRITER_MAX_BITS) { + // Fill up all the VP8L_WRITER_MAX_BITS so it can be flushed out below. + const int shift = VP8L_WRITER_MAX_BITS - used; + lbits |= (vp8l_atype_t)bits << used; + used = VP8L_WRITER_MAX_BITS; + n_bits -= shift; + bits >>= shift; + assert(n_bits <= VP8L_WRITER_MAX_BITS); + } +#endif + // If needed, make some room by flushing some bits out. + while (used >= VP8L_WRITER_BITS) { + if (bw->cur_ + VP8L_WRITER_BYTES > bw->end_) { + const uint64_t extra_size = (bw->end_ - bw->buf_) + MIN_EXTRA_SIZE; + if (!CheckSizeOverflow(extra_size) || + !VP8LBitWriterResize(bw, (size_t)extra_size)) { + bw->cur_ = bw->buf_; + bw->error_ = 1; + return; + } + } + *(vp8l_wtype_t*)bw->cur_ = (vp8l_wtype_t)WSWAP((vp8l_wtype_t)lbits); + bw->cur_ += VP8L_WRITER_BYTES; + lbits >>= VP8L_WRITER_BITS; + used -= VP8L_WRITER_BITS; + } + bw->bits_ = lbits | ((vp8l_atype_t)bits << used); + bw->used_ = used + n_bits; + } +} + +uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw) { + // flush leftover bits + if (VP8LBitWriterResize(bw, (bw->used_ + 7) >> 3)) { + while (bw->used_ > 0) { + *bw->cur_++ = (uint8_t)bw->bits_; + bw->bits_ >>= 8; + bw->used_ -= 8; + } + bw->used_ = 0; + } + return bw->buf_; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.h new file mode 100644 index 0000000000..b9d5102a5a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/bit_writer_utils.h @@ -0,0 +1,154 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Bit writing and boolean coder +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_BIT_WRITER_UTILS_H_ +#define WEBP_UTILS_BIT_WRITER_UTILS_H_ + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Bit-writing + +typedef struct VP8BitWriter VP8BitWriter; +struct VP8BitWriter { + int32_t range_; // range-1 + int32_t value_; + int run_; // number of outstanding bits + int nb_bits_; // number of pending bits + uint8_t* buf_; // internal buffer. Re-allocated regularly. Not owned. + size_t pos_; + size_t max_pos_; + int error_; // true in case of error +}; + +// Initialize the object. Allocates some initial memory based on expected_size. +int VP8BitWriterInit(VP8BitWriter* const bw, size_t expected_size); +// Finalize the bitstream coding. Returns a pointer to the internal buffer. +uint8_t* VP8BitWriterFinish(VP8BitWriter* const bw); +// Release any pending memory and zeroes the object. Not a mandatory call. +// Only useful in case of error, when the internal buffer hasn't been grabbed! +void VP8BitWriterWipeOut(VP8BitWriter* const bw); + +int VP8PutBit(VP8BitWriter* const bw, int bit, int prob); +int VP8PutBitUniform(VP8BitWriter* const bw, int bit); +void VP8PutBits(VP8BitWriter* const bw, uint32_t value, int nb_bits); +void VP8PutSignedBits(VP8BitWriter* const bw, int value, int nb_bits); + +// Appends some bytes to the internal buffer. Data is copied. +int VP8BitWriterAppend(VP8BitWriter* const bw, + const uint8_t* data, size_t size); + +// return approximate write position (in bits) +static WEBP_INLINE uint64_t VP8BitWriterPos(const VP8BitWriter* const bw) { + const uint64_t nb_bits = 8 + bw->nb_bits_; // bw->nb_bits_ is <= 0, note + return (bw->pos_ + bw->run_) * 8 + nb_bits; +} + +// Returns a pointer to the internal buffer. +static WEBP_INLINE uint8_t* VP8BitWriterBuf(const VP8BitWriter* const bw) { + return bw->buf_; +} +// Returns the size of the internal buffer. +static WEBP_INLINE size_t VP8BitWriterSize(const VP8BitWriter* const bw) { + return bw->pos_; +} + +//------------------------------------------------------------------------------ +// VP8LBitWriter + +#if defined(__x86_64__) || defined(_M_X64) // 64bit +typedef uint64_t vp8l_atype_t; // accumulator type +typedef uint32_t vp8l_wtype_t; // writing type +#define WSWAP HToLE32 +#define VP8L_WRITER_BYTES 4 // sizeof(vp8l_wtype_t) +#define VP8L_WRITER_BITS 32 // 8 * sizeof(vp8l_wtype_t) +#define VP8L_WRITER_MAX_BITS 64 // 8 * sizeof(vp8l_atype_t) +#else +typedef uint32_t vp8l_atype_t; +typedef uint16_t vp8l_wtype_t; +#define WSWAP HToLE16 +#define VP8L_WRITER_BYTES 2 +#define VP8L_WRITER_BITS 16 +#define VP8L_WRITER_MAX_BITS 32 +#endif + +typedef struct { + vp8l_atype_t bits_; // bit accumulator + int used_; // number of bits used in accumulator + uint8_t* buf_; // start of buffer + uint8_t* cur_; // current write position + uint8_t* end_; // end of buffer + + // After all bits are written (VP8LBitWriterFinish()), the caller must observe + // the state of error_. A value of 1 indicates that a memory allocation + // failure has happened during bit writing. A value of 0 indicates successful + // writing of bits. + int error_; +} VP8LBitWriter; + +static WEBP_INLINE size_t VP8LBitWriterNumBytes(const VP8LBitWriter* const bw) { + return (bw->cur_ - bw->buf_) + ((bw->used_ + 7) >> 3); +} + +// Returns false in case of memory allocation error. +int VP8LBitWriterInit(VP8LBitWriter* const bw, size_t expected_size); +// Returns false in case of memory allocation error. +int VP8LBitWriterClone(const VP8LBitWriter* const src, + VP8LBitWriter* const dst); +// Finalize the bitstream coding. Returns a pointer to the internal buffer. +uint8_t* VP8LBitWriterFinish(VP8LBitWriter* const bw); +// Release any pending memory and zeroes the object. +void VP8LBitWriterWipeOut(VP8LBitWriter* const bw); +// Resets the cursor of the BitWriter bw to when it was like in bw_init. +void VP8LBitWriterReset(const VP8LBitWriter* const bw_init, + VP8LBitWriter* const bw); +// Swaps the memory held by two BitWriters. +void VP8LBitWriterSwap(VP8LBitWriter* const src, VP8LBitWriter* const dst); + +// Internal function for VP8LPutBits flushing 32 bits from the written state. +void VP8LPutBitsFlushBits(VP8LBitWriter* const bw); + +// PutBits internal function used in the 16 bit vp8l_wtype_t case. +void VP8LPutBitsInternal(VP8LBitWriter* const bw, uint32_t bits, int n_bits); + +// This function writes bits into bytes in increasing addresses (little endian), +// and within a byte least-significant-bit first. +// This function can write up to 32 bits in one go, but VP8LBitReader can only +// read 24 bits max (VP8L_MAX_NUM_BIT_READ). +// VP8LBitWriter's error_ flag is set in case of memory allocation error. +static WEBP_INLINE void VP8LPutBits(VP8LBitWriter* const bw, + uint32_t bits, int n_bits) { + if (sizeof(vp8l_wtype_t) == 4) { + if (n_bits > 0) { + if (bw->used_ >= 32) { + VP8LPutBitsFlushBits(bw); + } + bw->bits_ |= (vp8l_atype_t)bits << bw->used_; + bw->used_ += n_bits; + } + } else { + VP8LPutBitsInternal(bw, bits, n_bits); + } +} + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_WRITER_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.c new file mode 100644 index 0000000000..7b5222b6e5 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.c @@ -0,0 +1,49 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Color Cache for WebP Lossless +// +// Author: Jyrki Alakuijala (jyrki@google.com) + +#include +#include +#include +#include "src/utils/color_cache_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8LColorCache. + +int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits) { + const int hash_size = 1 << hash_bits; + assert(color_cache != NULL); + assert(hash_bits > 0); + color_cache->colors_ = (uint32_t*)WebPSafeCalloc( + (uint64_t)hash_size, sizeof(*color_cache->colors_)); + if (color_cache->colors_ == NULL) return 0; + color_cache->hash_shift_ = 32 - hash_bits; + color_cache->hash_bits_ = hash_bits; + return 1; +} + +void VP8LColorCacheClear(VP8LColorCache* const color_cache) { + if (color_cache != NULL) { + WebPSafeFree(color_cache->colors_); + color_cache->colors_ = NULL; + } +} + +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst) { + assert(src != NULL); + assert(dst != NULL); + assert(src->hash_bits_ == dst->hash_bits_); + memcpy(dst->colors_, src->colors_, + ((size_t)1u << dst->hash_bits_) * sizeof(*dst->colors_)); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.h new file mode 100644 index 0000000000..b45d47c2d5 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/color_cache_utils.h @@ -0,0 +1,89 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Color Cache for WebP Lossless +// +// Authors: Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#ifndef WEBP_UTILS_COLOR_CACHE_UTILS_H_ +#define WEBP_UTILS_COLOR_CACHE_UTILS_H_ + +#include + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Main color cache struct. +typedef struct { + uint32_t* colors_; // color entries + int hash_shift_; // Hash shift: 32 - hash_bits_. + int hash_bits_; +} VP8LColorCache; + +static const uint32_t kHashMul = 0x1e35a7bdu; + +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +int VP8LHashPix(uint32_t argb, int shift) { + return (int)((argb * kHashMul) >> shift); +} + +static WEBP_INLINE uint32_t VP8LColorCacheLookup( + const VP8LColorCache* const cc, uint32_t key) { + assert((key >> cc->hash_bits_) == 0u); + return cc->colors_[key]; +} + +static WEBP_INLINE void VP8LColorCacheSet(const VP8LColorCache* const cc, + uint32_t key, uint32_t argb) { + assert((key >> cc->hash_bits_) == 0u); + cc->colors_[key] = argb; +} + +static WEBP_INLINE void VP8LColorCacheInsert(const VP8LColorCache* const cc, + uint32_t argb) { + const int key = VP8LHashPix(argb, cc->hash_shift_); + cc->colors_[key] = argb; +} + +static WEBP_INLINE int VP8LColorCacheGetIndex(const VP8LColorCache* const cc, + uint32_t argb) { + return VP8LHashPix(argb, cc->hash_shift_); +} + +// Return the key if cc contains argb, and -1 otherwise. +static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc, + uint32_t argb) { + const int key = VP8LHashPix(argb, cc->hash_shift_); + return (cc->colors_[key] == argb) ? key : -1; +} + +//------------------------------------------------------------------------------ + +// Initializes the color cache with 'hash_bits' bits for the keys. +// Returns false in case of memory error. +int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits); + +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst); + +// Delete the memory associated to color cache. +void VP8LColorCacheClear(VP8LColorCache* const color_cache); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_UTILS_COLOR_CACHE_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/endian_inl_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/endian_inl_utils.h new file mode 100644 index 0000000000..3630a293bf --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/endian_inl_utils.h @@ -0,0 +1,93 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Endian related functions. + +#ifndef WEBP_UTILS_ENDIAN_INL_UTILS_H_ +#define WEBP_UTILS_ENDIAN_INL_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#if defined(WORDS_BIGENDIAN) +#define HToLE32 BSwap32 +#define HToLE16 BSwap16 +#else +#define HToLE32(x) (x) +#define HToLE16(x) (x) +#endif + +#if !defined(HAVE_CONFIG_H) +#if LOCAL_GCC_PREREQ(4,8) || __has_builtin(__builtin_bswap16) +#define HAVE_BUILTIN_BSWAP16 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap32) +#define HAVE_BUILTIN_BSWAP32 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap64) +#define HAVE_BUILTIN_BSWAP64 +#endif +#endif // !HAVE_CONFIG_H + +static WEBP_INLINE uint16_t BSwap16(uint16_t x) { +#if defined(HAVE_BUILTIN_BSWAP16) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#else + // gcc will recognize a 'rorw $8, ...' here: + return (x >> 8) | ((x & 0xff) << 8); +#endif // HAVE_BUILTIN_BSWAP16 +} + +static WEBP_INLINE uint32_t BSwap32(uint32_t x) { +#if defined(WEBP_USE_MIPS32_R2) + uint32_t ret; + __asm__ volatile ( + "wsbh %[ret], %[x] \n\t" + "rotr %[ret], %[ret], 16 \n\t" + : [ret]"=r"(ret) + : [x]"r"(x) + ); + return ret; +#elif defined(HAVE_BUILTIN_BSWAP32) + return __builtin_bswap32(x); +#elif defined(__i386__) || defined(__x86_64__) + uint32_t swapped_bytes; + __asm__ volatile("bswap %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint32_t)_byteswap_ulong(x); +#else + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +#endif // HAVE_BUILTIN_BSWAP32 +} + +static WEBP_INLINE uint64_t BSwap64(uint64_t x) { +#if defined(HAVE_BUILTIN_BSWAP64) + return __builtin_bswap64(x); +#elif defined(__x86_64__) + uint64_t swapped_bytes; + __asm__ volatile("bswapq %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint64_t)_byteswap_uint64(x); +#else // generic code for swapping 64-bit values (suggested by bdb@) + x = ((x & 0xffffffff00000000ull) >> 32) | ((x & 0x00000000ffffffffull) << 32); + x = ((x & 0xffff0000ffff0000ull) >> 16) | ((x & 0x0000ffff0000ffffull) << 16); + x = ((x & 0xff00ff00ff00ff00ull) >> 8) | ((x & 0x00ff00ff00ff00ffull) << 8); + return x; +#endif // HAVE_BUILTIN_BSWAP64 +} + +#endif // WEBP_UTILS_ENDIAN_INL_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.c new file mode 100644 index 0000000000..bbc2c34d93 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.c @@ -0,0 +1,76 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// filter estimation +// +// Author: Urvang (urvang@google.com) + +#include "src/utils/filters_utils.h" +#include +#include + +// ----------------------------------------------------------------------------- +// Quick estimate of a potentially interesting filter mode to try. + +#define SMAX 16 +#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX) + +static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride) { + int i, j; + int bins[WEBP_FILTER_LAST][SMAX]; + memset(bins, 0, sizeof(bins)); + + // We only sample every other pixels. That's enough. + for (j = 2; j < height - 1; j += 2) { + const uint8_t* const p = data + j * stride; + int mean = p[0]; + for (i = 2; i < width - 1; i += 2) { + const int diff0 = SDIFF(p[i], mean); + const int diff1 = SDIFF(p[i], p[i - 1]); + const int diff2 = SDIFF(p[i], p[i - width]); + const int grad_pred = + GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]); + const int diff3 = SDIFF(p[i], grad_pred); + bins[WEBP_FILTER_NONE][diff0] = 1; + bins[WEBP_FILTER_HORIZONTAL][diff1] = 1; + bins[WEBP_FILTER_VERTICAL][diff2] = 1; + bins[WEBP_FILTER_GRADIENT][diff3] = 1; + mean = (3 * mean + p[i] + 2) >> 2; + } + } + { + int filter; + WEBP_FILTER_TYPE best_filter = WEBP_FILTER_NONE; + int best_score = 0x7fffffff; + for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) { + int score = 0; + for (i = 0; i < SMAX; ++i) { + if (bins[filter][i] > 0) { + score += i; + } + } + if (score < best_score) { + best_score = score; + best_filter = (WEBP_FILTER_TYPE)filter; + } + } + return best_filter; + } +} + +#undef SMAX +#undef SDIFF + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.h new file mode 100644 index 0000000000..61da66e212 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/filters_utils.h @@ -0,0 +1,32 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_UTILS_FILTERS_UTILS_H_ +#define WEBP_UTILS_FILTERS_UTILS_H_ + +#include "src/webp/types.h" +#include "src/dsp/dsp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Fast estimate of a potentially good filter. +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_FILTERS_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.c new file mode 100644 index 0000000000..585db91951 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.c @@ -0,0 +1,416 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// +// Entropy encoding (Huffman) for webp lossless. + +#include +#include +#include +#include "src/utils/huffman_encode_utils.h" +#include "src/utils/utils.h" +#include "src/webp/format_constants.h" + +// ----------------------------------------------------------------------------- +// Util function to optimize the symbol map for RLE coding + +// Heuristics for selecting the stride ranges to collapse. +static int ValuesShouldBeCollapsedToStrideAverage(int a, int b) { + return abs(a - b) < 4; +} + +// Change the population counts in a way that the consequent +// Huffman tree compression, especially its RLE-part, give smaller output. +static void OptimizeHuffmanForRle(int length, uint8_t* const good_for_rle, + uint32_t* const counts) { + // 1) Let's make the Huffman code more compatible with rle encoding. + int i; + for (; length >= 0; --length) { + if (length == 0) { + return; // All zeros. + } + if (counts[length - 1] != 0) { + // Now counts[0..length - 1] does not have trailing zeros. + break; + } + } + // 2) Let's mark all population counts that already can be encoded + // with an rle code. + { + // Let's not spoil any of the existing good rle codes. + // Mark any seq of 0's that is longer as 5 as a good_for_rle. + // Mark any seq of non-0's that is longer as 7 as a good_for_rle. + uint32_t symbol = counts[0]; + int stride = 0; + for (i = 0; i < length + 1; ++i) { + if (i == length || counts[i] != symbol) { + if ((symbol == 0 && stride >= 5) || + (symbol != 0 && stride >= 7)) { + int k; + for (k = 0; k < stride; ++k) { + good_for_rle[i - k - 1] = 1; + } + } + stride = 1; + if (i != length) { + symbol = counts[i]; + } + } else { + ++stride; + } + } + } + // 3) Let's replace those population counts that lead to more rle codes. + { + uint32_t stride = 0; + uint32_t limit = counts[0]; + uint32_t sum = 0; + for (i = 0; i < length + 1; ++i) { + if (i == length || good_for_rle[i] || + (i != 0 && good_for_rle[i - 1]) || + !ValuesShouldBeCollapsedToStrideAverage(counts[i], limit)) { + if (stride >= 4 || (stride >= 3 && sum == 0)) { + uint32_t k; + // The stride must end, collapse what we have, if we have enough (4). + uint32_t count = (sum + stride / 2) / stride; + if (count < 1) { + count = 1; + } + if (sum == 0) { + // Don't make an all zeros stride to be upgraded to ones. + count = 0; + } + for (k = 0; k < stride; ++k) { + // We don't want to change value at counts[i], + // that is already belonging to the next stride. Thus - 1. + counts[i - k - 1] = count; + } + } + stride = 0; + sum = 0; + if (i < length - 3) { + // All interesting strides have a count of at least 4, + // at least when non-zeros. + limit = (counts[i] + counts[i + 1] + + counts[i + 2] + counts[i + 3] + 2) / 4; + } else if (i < length) { + limit = counts[i]; + } else { + limit = 0; + } + } + ++stride; + if (i != length) { + sum += counts[i]; + if (stride >= 4) { + limit = (sum + stride / 2) / stride; + } + } + } + } +} + +// A comparer function for two Huffman trees: sorts first by 'total count' +// (more comes first), and then by 'value' (more comes first). +static int CompareHuffmanTrees(const void* ptr1, const void* ptr2) { + const HuffmanTree* const t1 = (const HuffmanTree*)ptr1; + const HuffmanTree* const t2 = (const HuffmanTree*)ptr2; + if (t1->total_count_ > t2->total_count_) { + return -1; + } else if (t1->total_count_ < t2->total_count_) { + return 1; + } else { + assert(t1->value_ != t2->value_); + return (t1->value_ < t2->value_) ? -1 : 1; + } +} + +static void SetBitDepths(const HuffmanTree* const tree, + const HuffmanTree* const pool, + uint8_t* const bit_depths, int level) { + if (tree->pool_index_left_ >= 0) { + SetBitDepths(&pool[tree->pool_index_left_], pool, bit_depths, level + 1); + SetBitDepths(&pool[tree->pool_index_right_], pool, bit_depths, level + 1); + } else { + bit_depths[tree->value_] = level; + } +} + +// Create an optimal Huffman tree. +// +// (data,length): population counts. +// tree_limit: maximum bit depth (inclusive) of the codes. +// bit_depths[]: how many bits are used for the symbol. +// +// Returns 0 when an error has occurred. +// +// The catch here is that the tree cannot be arbitrarily deep +// +// count_limit is the value that is to be faked as the minimum value +// and this minimum value is raised until the tree matches the +// maximum length requirement. +// +// This algorithm is not of excellent performance for very long data blocks, +// especially when population counts are longer than 2**tree_limit, but +// we are not planning to use this with extremely long blocks. +// +// See https://en.wikipedia.org/wiki/Huffman_coding +static void GenerateOptimalTree(const uint32_t* const histogram, + int histogram_size, + HuffmanTree* tree, int tree_depth_limit, + uint8_t* const bit_depths) { + uint32_t count_min; + HuffmanTree* tree_pool; + int tree_size_orig = 0; + int i; + + for (i = 0; i < histogram_size; ++i) { + if (histogram[i] != 0) { + ++tree_size_orig; + } + } + + if (tree_size_orig == 0) { // pretty optimal already! + return; + } + + tree_pool = tree + tree_size_orig; + + // For block sizes with less than 64k symbols we never need to do a + // second iteration of this loop. + // If we actually start running inside this loop a lot, we would perhaps + // be better off with the Katajainen algorithm. + assert(tree_size_orig <= (1 << (tree_depth_limit - 1))); + for (count_min = 1; ; count_min *= 2) { + int tree_size = tree_size_orig; + // We need to pack the Huffman tree in tree_depth_limit bits. + // So, we try by faking histogram entries to be at least 'count_min'. + int idx = 0; + int j; + for (j = 0; j < histogram_size; ++j) { + if (histogram[j] != 0) { + const uint32_t count = + (histogram[j] < count_min) ? count_min : histogram[j]; + tree[idx].total_count_ = count; + tree[idx].value_ = j; + tree[idx].pool_index_left_ = -1; + tree[idx].pool_index_right_ = -1; + ++idx; + } + } + + // Build the Huffman tree. + qsort(tree, tree_size, sizeof(*tree), CompareHuffmanTrees); + + if (tree_size > 1) { // Normal case. + int tree_pool_size = 0; + while (tree_size > 1) { // Finish when we have only one root. + uint32_t count; + tree_pool[tree_pool_size++] = tree[tree_size - 1]; + tree_pool[tree_pool_size++] = tree[tree_size - 2]; + count = tree_pool[tree_pool_size - 1].total_count_ + + tree_pool[tree_pool_size - 2].total_count_; + tree_size -= 2; + { + // Search for the insertion point. + int k; + for (k = 0; k < tree_size; ++k) { + if (tree[k].total_count_ <= count) { + break; + } + } + memmove(tree + (k + 1), tree + k, (tree_size - k) * sizeof(*tree)); + tree[k].total_count_ = count; + tree[k].value_ = -1; + + tree[k].pool_index_left_ = tree_pool_size - 1; + tree[k].pool_index_right_ = tree_pool_size - 2; + tree_size = tree_size + 1; + } + } + SetBitDepths(&tree[0], tree_pool, bit_depths, 0); + } else if (tree_size == 1) { // Trivial case: only one element. + bit_depths[tree[0].value_] = 1; + } + + { + // Test if this Huffman tree satisfies our 'tree_depth_limit' criteria. + int max_depth = bit_depths[0]; + for (j = 1; j < histogram_size; ++j) { + if (max_depth < bit_depths[j]) { + max_depth = bit_depths[j]; + } + } + if (max_depth <= tree_depth_limit) { + break; + } + } + } +} + +// ----------------------------------------------------------------------------- +// Coding of the Huffman tree values + +static HuffmanTreeToken* CodeRepeatedValues(int repetitions, + HuffmanTreeToken* tokens, + int value, int prev_value) { + assert(value <= MAX_ALLOWED_CODE_LENGTH); + if (value != prev_value) { + tokens->code = value; + tokens->extra_bits = 0; + ++tokens; + --repetitions; + } + while (repetitions >= 1) { + if (repetitions < 3) { + int i; + for (i = 0; i < repetitions; ++i) { + tokens->code = value; + tokens->extra_bits = 0; + ++tokens; + } + break; + } else if (repetitions < 7) { + tokens->code = 16; + tokens->extra_bits = repetitions - 3; + ++tokens; + break; + } else { + tokens->code = 16; + tokens->extra_bits = 3; + ++tokens; + repetitions -= 6; + } + } + return tokens; +} + +static HuffmanTreeToken* CodeRepeatedZeros(int repetitions, + HuffmanTreeToken* tokens) { + while (repetitions >= 1) { + if (repetitions < 3) { + int i; + for (i = 0; i < repetitions; ++i) { + tokens->code = 0; // 0-value + tokens->extra_bits = 0; + ++tokens; + } + break; + } else if (repetitions < 11) { + tokens->code = 17; + tokens->extra_bits = repetitions - 3; + ++tokens; + break; + } else if (repetitions < 139) { + tokens->code = 18; + tokens->extra_bits = repetitions - 11; + ++tokens; + break; + } else { + tokens->code = 18; + tokens->extra_bits = 0x7f; // 138 repeated 0s + ++tokens; + repetitions -= 138; + } + } + return tokens; +} + +int VP8LCreateCompressedHuffmanTree(const HuffmanTreeCode* const tree, + HuffmanTreeToken* tokens, int max_tokens) { + HuffmanTreeToken* const starting_token = tokens; + HuffmanTreeToken* const ending_token = tokens + max_tokens; + const int depth_size = tree->num_symbols; + int prev_value = 8; // 8 is the initial value for rle. + int i = 0; + assert(tokens != NULL); + while (i < depth_size) { + const int value = tree->code_lengths[i]; + int k = i + 1; + int runs; + while (k < depth_size && tree->code_lengths[k] == value) ++k; + runs = k - i; + if (value == 0) { + tokens = CodeRepeatedZeros(runs, tokens); + } else { + tokens = CodeRepeatedValues(runs, tokens, value, prev_value); + prev_value = value; + } + i += runs; + assert(tokens <= ending_token); + } + (void)ending_token; // suppress 'unused variable' warning + return (int)(tokens - starting_token); +} + +// ----------------------------------------------------------------------------- + +// Pre-reversed 4-bit values. +static const uint8_t kReversedBits[16] = { + 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, + 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf +}; + +static uint32_t ReverseBits(int num_bits, uint32_t bits) { + uint32_t retval = 0; + int i = 0; + while (i < num_bits) { + i += 4; + retval |= kReversedBits[bits & 0xf] << (MAX_ALLOWED_CODE_LENGTH + 1 - i); + bits >>= 4; + } + retval >>= (MAX_ALLOWED_CODE_LENGTH + 1 - num_bits); + return retval; +} + +// Get the actual bit values for a tree of bit depths. +static void ConvertBitDepthsToSymbols(HuffmanTreeCode* const tree) { + // 0 bit-depth means that the symbol does not exist. + int i; + int len; + uint32_t next_code[MAX_ALLOWED_CODE_LENGTH + 1]; + int depth_count[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; + + assert(tree != NULL); + len = tree->num_symbols; + for (i = 0; i < len; ++i) { + const int code_length = tree->code_lengths[i]; + assert(code_length <= MAX_ALLOWED_CODE_LENGTH); + ++depth_count[code_length]; + } + depth_count[0] = 0; // ignore unused symbol + next_code[0] = 0; + { + uint32_t code = 0; + for (i = 1; i <= MAX_ALLOWED_CODE_LENGTH; ++i) { + code = (code + depth_count[i - 1]) << 1; + next_code[i] = code; + } + } + for (i = 0; i < len; ++i) { + const int code_length = tree->code_lengths[i]; + tree->codes[i] = ReverseBits(code_length, next_code[code_length]++); + } +} + +// ----------------------------------------------------------------------------- +// Main entry point + +void VP8LCreateHuffmanTree(uint32_t* const histogram, int tree_depth_limit, + uint8_t* const buf_rle, HuffmanTree* const huff_tree, + HuffmanTreeCode* const huff_code) { + const int num_symbols = huff_code->num_symbols; + memset(buf_rle, 0, num_symbols * sizeof(*buf_rle)); + OptimizeHuffmanForRle(num_symbols, buf_rle, histogram); + GenerateOptimalTree(histogram, num_symbols, huff_tree, tree_depth_limit, + huff_code->code_lengths); + // Create the actual bit codes for the bit lengths. + ConvertBitDepthsToSymbols(huff_code); +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.h new file mode 100644 index 0000000000..3f7f1d8074 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_encode_utils.h @@ -0,0 +1,60 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Author: Jyrki Alakuijala (jyrki@google.com) +// +// Entropy encoding (Huffman) for webp lossless + +#ifndef WEBP_UTILS_HUFFMAN_ENCODE_UTILS_H_ +#define WEBP_UTILS_HUFFMAN_ENCODE_UTILS_H_ + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Struct for holding the tree header in coded form. +typedef struct { + uint8_t code; // value (0..15) or escape code (16,17,18) + uint8_t extra_bits; // extra bits for escape codes +} HuffmanTreeToken; + +// Struct to represent the tree codes (depth and bits array). +typedef struct { + int num_symbols; // Number of symbols. + uint8_t* code_lengths; // Code lengths of the symbols. + uint16_t* codes; // Symbol Codes. +} HuffmanTreeCode; + +// Struct to represent the Huffman tree. +typedef struct { + uint32_t total_count_; // Symbol frequency. + int value_; // Symbol value. + int pool_index_left_; // Index for the left sub-tree. + int pool_index_right_; // Index for the right sub-tree. +} HuffmanTree; + +// Turn the Huffman tree into a token sequence. +// Returns the number of tokens used. +int VP8LCreateCompressedHuffmanTree(const HuffmanTreeCode* const tree, + HuffmanTreeToken* tokens, int max_tokens); + +// Create an optimized tree, and tokenize it. +// 'buf_rle' and 'huff_tree' are pre-allocated and the 'tree' is the constructed +// huffman code tree. +void VP8LCreateHuffmanTree(uint32_t* const histogram, int tree_depth_limit, + uint8_t* const buf_rle, HuffmanTree* const huff_tree, + HuffmanTreeCode* const huff_code); + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_UTILS_HUFFMAN_ENCODE_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.c new file mode 100644 index 0000000000..16f9faaa9a --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.c @@ -0,0 +1,299 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#include +#include +#include +#include "src/utils/huffman_utils.h" +#include "src/utils/utils.h" +#include "src/webp/format_constants.h" + +// Huffman data read via DecodeImageStream is represented in two (red and green) +// bytes. +#define MAX_HTREE_GROUPS 0x10000 + +HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups) { + HTreeGroup* const htree_groups = + (HTreeGroup*)WebPSafeMalloc(num_htree_groups, sizeof(*htree_groups)); + if (htree_groups == NULL) { + return NULL; + } + assert(num_htree_groups <= MAX_HTREE_GROUPS); + return htree_groups; +} + +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups) { + if (htree_groups != NULL) { + WebPSafeFree(htree_groups); + } +} + +// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the +// bit-wise reversal of the len least significant bits of key. +static WEBP_INLINE uint32_t GetNextKey(uint32_t key, int len) { + uint32_t step = 1 << (len - 1); + while (key & step) { + step >>= 1; + } + return step ? (key & (step - 1)) + step : key; +} + +// Stores code in table[0], table[step], table[2*step], ..., table[end]. +// Assumes that end is an integer multiple of step. +static WEBP_INLINE void ReplicateValue(HuffmanCode* table, + int step, int end, + HuffmanCode code) { + assert(end % step == 0); + do { + end -= step; + table[end] = code; + } while (end > 0); +} + +// Returns the table width of the next 2nd level table. count is the histogram +// of bit lengths for the remaining symbols, len is the code length of the next +// processed symbol +static WEBP_INLINE int NextTableBitSize(const int* const count, + int len, int root_bits) { + int left = 1 << (len - root_bits); + while (len < MAX_ALLOWED_CODE_LENGTH) { + left -= count[len]; + if (left <= 0) break; + ++len; + left <<= 1; + } + return len - root_bits; +} + +// sorted[code_lengths_size] is a pre-allocated array for sorting symbols +// by code length. +static int BuildHuffmanTable(HuffmanCode* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size, + uint16_t sorted[]) { + HuffmanCode* table = root_table; // next available space in table + int total_size = 1 << root_bits; // total size root table + 2nd level table + int len; // current code length + int symbol; // symbol index in original or sorted table + // number of codes of each length: + int count[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; + // offsets in sorted table for each length: + int offset[MAX_ALLOWED_CODE_LENGTH + 1]; + + assert(code_lengths_size != 0); + assert(code_lengths != NULL); + assert((root_table != NULL && sorted != NULL) || + (root_table == NULL && sorted == NULL)); + assert(root_bits > 0); + + // Build histogram of code lengths. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + if (code_lengths[symbol] > MAX_ALLOWED_CODE_LENGTH) { + return 0; + } + ++count[code_lengths[symbol]]; + } + + // Error, all code lengths are zeros. + if (count[0] == code_lengths_size) { + return 0; + } + + // Generate offsets into sorted symbol table by code length. + offset[1] = 0; + for (len = 1; len < MAX_ALLOWED_CODE_LENGTH; ++len) { + if (count[len] > (1 << len)) { + return 0; + } + offset[len + 1] = offset[len] + count[len]; + } + + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + const int symbol_code_length = code_lengths[symbol]; + if (code_lengths[symbol] > 0) { + if (sorted != NULL) { + if(offset[symbol_code_length] >= code_lengths_size) { + return 0; + } + sorted[offset[symbol_code_length]++] = symbol; + } else { + offset[symbol_code_length]++; + } + } + } + + // Special case code with only one value. + if (offset[MAX_ALLOWED_CODE_LENGTH] == 1) { + if (sorted != NULL) { + HuffmanCode code; + code.bits = 0; + code.value = (uint16_t)sorted[0]; + ReplicateValue(table, 1, total_size, code); + } + return total_size; + } + + { + int step; // step size to replicate values in current table + uint32_t low = 0xffffffffu; // low bits for current root entry + uint32_t mask = total_size - 1; // mask for low bits + uint32_t key = 0; // reversed prefix code + int num_nodes = 1; // number of Huffman tree nodes + int num_open = 1; // number of open branches in current tree level + int table_bits = root_bits; // key length of current table + int table_size = 1 << table_bits; // size of current table + symbol = 0; + // Fill in root table. + for (len = 1, step = 2; len <= root_bits; ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + if (root_table == NULL) continue; + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + code.bits = (uint8_t)len; + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key], step, table_size, code); + key = GetNextKey(key, len); + } + } + + // Fill in 2nd level tables and add pointers to root table. + for (len = root_bits + 1, step = 2; len <= MAX_ALLOWED_CODE_LENGTH; + ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + if ((key & mask) != low) { + if (root_table != NULL) table += table_size; + table_bits = NextTableBitSize(count, len, root_bits); + table_size = 1 << table_bits; + total_size += table_size; + low = key & mask; + if (root_table != NULL) { + root_table[low].bits = (uint8_t)(table_bits + root_bits); + root_table[low].value = (uint16_t)((table - root_table) - low); + } + } + if (root_table != NULL) { + code.bits = (uint8_t)(len - root_bits); + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key >> root_bits], step, table_size, code); + } + key = GetNextKey(key, len); + } + } + + // Check if tree is full. + if (num_nodes != 2 * offset[MAX_ALLOWED_CODE_LENGTH] - 1) { + return 0; + } + } + + return total_size; +} + +// Maximum code_lengths_size is 2328 (reached for 11-bit color_cache_bits). +// More commonly, the value is around ~280. +#define MAX_CODE_LENGTHS_SIZE \ + ((1 << MAX_CACHE_BITS) + NUM_LITERAL_CODES + NUM_LENGTH_CODES) +// Cut-off value for switching between heap and stack allocation. +#define SORTED_SIZE_CUTOFF 512 +int VP8LBuildHuffmanTable(HuffmanTables* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size) { + const int total_size = + BuildHuffmanTable(NULL, root_bits, code_lengths, code_lengths_size, NULL); + assert(code_lengths_size <= MAX_CODE_LENGTHS_SIZE); + if (total_size == 0 || root_table == NULL) return total_size; + + if (root_table->curr_segment->curr_table + total_size >= + root_table->curr_segment->start + root_table->curr_segment->size) { + // If 'root_table' does not have enough memory, allocate a new segment. + // The available part of root_table->curr_segment is left unused because we + // need a contiguous buffer. + const int segment_size = root_table->curr_segment->size; + struct HuffmanTablesSegment* next = + (HuffmanTablesSegment*)WebPSafeMalloc(1, sizeof(*next)); + if (next == NULL) return 0; + // Fill the new segment. + // We need at least 'total_size' but if that value is small, it is better to + // allocate a big chunk to prevent more allocations later. 'segment_size' is + // therefore chosen (any other arbitrary value could be chosen). + next->size = total_size > segment_size ? total_size : segment_size; + next->start = + (HuffmanCode*)WebPSafeMalloc(next->size, sizeof(*next->start)); + if (next->start == NULL) { + WebPSafeFree(next); + return 0; + } + next->curr_table = next->start; + next->next = NULL; + // Point to the new segment. + root_table->curr_segment->next = next; + root_table->curr_segment = next; + } + if (code_lengths_size <= SORTED_SIZE_CUTOFF) { + // use local stack-allocated array. + uint16_t sorted[SORTED_SIZE_CUTOFF]; + BuildHuffmanTable(root_table->curr_segment->curr_table, root_bits, + code_lengths, code_lengths_size, sorted); + } else { // rare case. Use heap allocation. + uint16_t* const sorted = + (uint16_t*)WebPSafeMalloc(code_lengths_size, sizeof(*sorted)); + if (sorted == NULL) return 0; + BuildHuffmanTable(root_table->curr_segment->curr_table, root_bits, + code_lengths, code_lengths_size, sorted); + WebPSafeFree(sorted); + } + return total_size; +} + +int VP8LHuffmanTablesAllocate(int size, HuffmanTables* huffman_tables) { + // Have 'segment' point to the first segment for now, 'root'. + HuffmanTablesSegment* const root = &huffman_tables->root; + huffman_tables->curr_segment = root; + root->next = NULL; + // Allocate root. + root->start = (HuffmanCode*)WebPSafeMalloc(size, sizeof(*root->start)); + if (root->start == NULL) return 0; + root->curr_table = root->start; + root->size = size; + return 1; +} + +void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables) { + HuffmanTablesSegment *current, *next; + if (huffman_tables == NULL) return; + // Free the root node. + current = &huffman_tables->root; + next = current->next; + WebPSafeFree(current->start); + current->start = NULL; + current->next = NULL; + current = next; + // Free the following nodes. + while (current != NULL) { + next = current->next; + WebPSafeFree(current->start); + WebPSafeFree(current); + current = next; + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.h new file mode 100644 index 0000000000..d511dc052c --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/huffman_utils.h @@ -0,0 +1,114 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#ifndef WEBP_UTILS_HUFFMAN_UTILS_H_ +#define WEBP_UTILS_HUFFMAN_UTILS_H_ + +#include +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HUFFMAN_TABLE_BITS 8 +#define HUFFMAN_TABLE_MASK ((1 << HUFFMAN_TABLE_BITS) - 1) + +#define LENGTHS_TABLE_BITS 7 +#define LENGTHS_TABLE_MASK ((1 << LENGTHS_TABLE_BITS) - 1) + + +// Huffman lookup table entry +typedef struct { + uint8_t bits; // number of bits used for this symbol + uint16_t value; // symbol value or table offset +} HuffmanCode; + +// long version for holding 32b values +typedef struct { + int bits; // number of bits used for this symbol, + // or an impossible value if not a literal code. + uint32_t value; // 32b packed ARGB value if literal, + // or non-literal symbol otherwise +} HuffmanCode32; + +// Contiguous memory segment of HuffmanCodes. +typedef struct HuffmanTablesSegment { + HuffmanCode* start; + // Pointer to where we are writing into the segment. Starts at 'start' and + // cannot go beyond 'start' + 'size'. + HuffmanCode* curr_table; + // Pointer to the next segment in the chain. + struct HuffmanTablesSegment* next; + int size; +} HuffmanTablesSegment; + +// Chained memory segments of HuffmanCodes. +typedef struct HuffmanTables { + HuffmanTablesSegment root; + // Currently processed segment. At first, this is 'root'. + HuffmanTablesSegment* curr_segment; +} HuffmanTables; + +// Allocates a HuffmanTables with 'size' contiguous HuffmanCodes. Returns 0 on +// memory allocation error, 1 otherwise. +WEBP_NODISCARD int VP8LHuffmanTablesAllocate(int size, + HuffmanTables* huffman_tables); +void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables); + +#define HUFFMAN_PACKED_BITS 6 +#define HUFFMAN_PACKED_TABLE_SIZE (1u << HUFFMAN_PACKED_BITS) + +// Huffman table group. +// Includes special handling for the following cases: +// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN) +// - is_trivial_code: only 1 code (no bit is read from bitstream) +// - use_packed_table: few enough literal symbols, so all the bit codes +// can fit into a small look-up table packed_table[] +// The common literal base, if applicable, is stored in 'literal_arb'. +typedef struct HTreeGroup HTreeGroup; +struct HTreeGroup { + HuffmanCode* htrees[HUFFMAN_CODES_PER_META_CODE]; + int is_trivial_literal; // True, if huffman trees for Red, Blue & Alpha + // Symbols are trivial (have a single code). + uint32_t literal_arb; // If is_trivial_literal is true, this is the + // ARGB value of the pixel, with Green channel + // being set to zero. + int is_trivial_code; // true if is_trivial_literal with only one code + int use_packed_table; // use packed table below for short literal code + // table mapping input bits to a packed values, or escape case to literal code + HuffmanCode32 packed_table[HUFFMAN_PACKED_TABLE_SIZE]; +}; + +// Creates the instance of HTreeGroup with specified number of tree-groups. +WEBP_NODISCARD HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups); + +// Releases the memory allocated for HTreeGroup. +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups); + +// Builds Huffman lookup table assuming code lengths are in symbol order. +// The 'code_lengths' is pre-allocated temporary memory buffer used for creating +// the huffman table. +// Returns built table size or 0 in case of error (invalid tree or +// memory error). +WEBP_NODISCARD int VP8LBuildHuffmanTable(HuffmanTables* const root_table, + int root_bits, + const int code_lengths[], + int code_lengths_size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_HUFFMAN_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.c new file mode 100644 index 0000000000..515da21019 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.c @@ -0,0 +1,402 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for palette analysis. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#include "src/utils/palette.h" + +#include +#include + +#include "src/dsp/lossless_common.h" +#include "src/utils/color_cache_utils.h" +#include "src/utils/utils.h" +#include "src/webp/encode.h" +#include "src/webp/format_constants.h" + +// ----------------------------------------------------------------------------- + +// Palette reordering for smaller sum of deltas (and for smaller storage). + +static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { + const uint32_t a = WebPMemToUint32((uint8_t*)p1); + const uint32_t b = WebPMemToUint32((uint8_t*)p2); + assert(a != b); + return (a < b) ? -1 : 1; +} + +static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { + return (v <= 128) ? v : (256 - v); +} + +// Computes a value that is related to the entropy created by the +// palette entry diff. +// +// Note that the last & 0xff is a no-operation in the next statement, but +// removed by most compilers and is here only for regularity of the code. +static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { + const uint32_t diff = VP8LSubPixels(col1, col2); + const int kMoreWeightForRGBThanForAlpha = 9; + uint32_t score; + score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= kMoreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + return score; +} + +static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { + const uint32_t tmp = *col1; + *col1 = *col2; + *col2 = tmp; +} + +int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) { + int low = 0, hi = num_colors; + if (sorted[low] == color) return low; // loop invariant: sorted[low] != color + while (1) { + const int mid = (low + hi) >> 1; + if (sorted[mid] == color) { + return mid; + } else if (sorted[mid] < color) { + low = mid; + } else { + hi = mid; + } + } + assert(0); + return 0; +} + +void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors, + uint32_t sorted[], uint32_t idx_map[]) { + uint32_t i; + memcpy(sorted, palette, num_colors * sizeof(*sorted)); + qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort); + for (i = 0; i < num_colors; ++i) { + idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i; + } +} + +//------------------------------------------------------------------------------ + +#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) +#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE). + +int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { + int i; + int x, y; + int num_colors = 0; + uint8_t in_use[COLOR_HASH_SIZE] = {0}; + uint32_t colors[COLOR_HASH_SIZE] = {0}; + const uint32_t* argb = pic->argb; + const int width = pic->width; + const int height = pic->height; + uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] + assert(pic != NULL); + assert(pic->use_argb); + + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + int key; + if (argb[x] == last_pix) { + continue; + } + last_pix = argb[x]; + key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT); + while (1) { + if (!in_use[key]) { + colors[key] = last_pix; + in_use[key] = 1; + ++num_colors; + if (num_colors > MAX_PALETTE_SIZE) { + return MAX_PALETTE_SIZE + 1; // Exact count not needed. + } + break; + } else if (colors[key] == last_pix) { + break; // The color is already there. + } else { + // Some other color sits here, so do linear conflict resolution. + ++key; + key &= (COLOR_HASH_SIZE - 1); // Key mask. + } + } + } + argb += pic->argb_stride; + } + + if (palette != NULL) { // Fill the colors into palette. + num_colors = 0; + for (i = 0; i < COLOR_HASH_SIZE; ++i) { + if (in_use[i]) { + palette[num_colors] = colors[i]; + ++num_colors; + } + } + qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); + } + return num_colors; +} + +#undef COLOR_HASH_SIZE +#undef COLOR_HASH_RIGHT_SHIFT + +// ----------------------------------------------------------------------------- + +// The palette has been sorted by alpha. This function checks if the other +// components of the palette have a monotonic development with regards to +// position in the palette. If all have monotonic development, there is +// no benefit to re-organize them greedily. A monotonic development +// would be spotted in green-only situations (like lossy alpha) or gray-scale +// images. +static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette, + int num_colors) { + uint32_t predict = 0x000000; + int i; + uint8_t sign_found = 0x00; + for (i = 0; i < num_colors; ++i) { + const uint32_t diff = VP8LSubPixels(palette[i], predict); + const uint8_t rd = (diff >> 16) & 0xff; + const uint8_t gd = (diff >> 8) & 0xff; + const uint8_t bd = (diff >> 0) & 0xff; + if (rd != 0x00) { + sign_found |= (rd < 0x80) ? 1 : 2; + } + if (gd != 0x00) { + sign_found |= (gd < 0x80) ? 8 : 16; + } + if (bd != 0x00) { + sign_found |= (bd < 0x80) ? 64 : 128; + } + predict = palette[i]; + } + return (sign_found & (sign_found << 1)) != 0; // two consequent signs. +} + +static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted, + int num_colors, uint32_t* const palette) { + uint32_t predict = 0x00000000; + int i, k; + memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); + if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return; + // Find greedily always the closest color of the predicted color to minimize + // deltas in the palette. This reduces storage needs since the + // palette is stored with delta encoding. + for (i = 0; i < num_colors; ++i) { + int best_ix = i; + uint32_t best_score = ~0U; + for (k = i; k < num_colors; ++k) { + const uint32_t cur_score = PaletteColorDistance(palette[k], predict); + if (best_score > cur_score) { + best_score = cur_score; + best_ix = k; + } + } + SwapColor(&palette[best_ix], &palette[i]); + predict = palette[i]; + } +} + +// ----------------------------------------------------------------------------- +// Modified Zeng method from "A Survey on Palette Reordering +// Methods for Improving the Compression of Color-Indexed Images" by Armando J. +// Pinho and Antonio J. R. Neves. + +// Finds the biggest cooccurrence in the matrix. +static void CoOccurrenceFindMax(const uint32_t* const cooccurrence, + uint32_t num_colors, uint8_t* const c1, + uint8_t* const c2) { + // Find the index that is most frequently located adjacent to other + // (different) indexes. + uint32_t best_sum = 0u; + uint32_t i, j, best_cooccurrence; + *c1 = 0u; + for (i = 0; i < num_colors; ++i) { + uint32_t sum = 0; + for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j]; + if (sum > best_sum) { + best_sum = sum; + *c1 = i; + } + } + // Find the index that is most frequently found adjacent to *c1. + *c2 = 0u; + best_cooccurrence = 0u; + for (i = 0; i < num_colors; ++i) { + if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) { + best_cooccurrence = cooccurrence[*c1 * num_colors + i]; + *c2 = i; + } + } + assert(*c1 != *c2); +} + +// Builds the cooccurrence matrix +static int CoOccurrenceBuild(const WebPPicture* const pic, + const uint32_t* const palette, uint32_t num_colors, + uint32_t* cooccurrence) { + uint32_t *lines, *line_top, *line_current, *line_tmp; + int x, y; + const uint32_t* src = pic->argb; + uint32_t prev_pix = ~src[0]; + uint32_t prev_idx = 0u; + uint32_t idx_map[MAX_PALETTE_SIZE] = {0}; + uint32_t palette_sorted[MAX_PALETTE_SIZE]; + lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines)); + if (lines == NULL) { + return 0; + } + line_top = &lines[0]; + line_current = &lines[pic->width]; + PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map); + for (y = 0; y < pic->height; ++y) { + for (x = 0; x < pic->width; ++x) { + const uint32_t pix = src[x]; + if (pix != prev_pix) { + prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)]; + prev_pix = pix; + } + line_current[x] = prev_idx; + // 4-connectivity is what works best as mentioned in "On the relation + // between Memon's and the modified Zeng's palette reordering methods". + if (x > 0 && prev_idx != line_current[x - 1]) { + const uint32_t left_idx = line_current[x - 1]; + ++cooccurrence[prev_idx * num_colors + left_idx]; + ++cooccurrence[left_idx * num_colors + prev_idx]; + } + if (y > 0 && prev_idx != line_top[x]) { + const uint32_t top_idx = line_top[x]; + ++cooccurrence[prev_idx * num_colors + top_idx]; + ++cooccurrence[top_idx * num_colors + prev_idx]; + } + } + line_tmp = line_top; + line_top = line_current; + line_current = line_tmp; + src += pic->argb_stride; + } + WebPSafeFree(lines); + return 1; +} + +struct Sum { + uint8_t index; + uint32_t sum; +}; + +static int PaletteSortModifiedZeng(const WebPPicture* const pic, + const uint32_t* const palette_in, + uint32_t num_colors, + uint32_t* const palette) { + uint32_t i, j, ind; + uint8_t remapping[MAX_PALETTE_SIZE]; + uint32_t* cooccurrence; + struct Sum sums[MAX_PALETTE_SIZE]; + uint32_t first, last; + uint32_t num_sums; + // TODO(vrabaud) check whether one color images should use palette or not. + if (num_colors <= 1) return 1; + // Build the co-occurrence matrix. + cooccurrence = + (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); + if (cooccurrence == NULL) { + return 0; + } + if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) { + WebPSafeFree(cooccurrence); + return 0; + } + + // Initialize the mapping list with the two best indices. + CoOccurrenceFindMax(cooccurrence, num_colors, &remapping[0], &remapping[1]); + + // We need to append and prepend to the list of remapping. To this end, we + // actually define the next start/end of the list as indices in a vector (with + // a wrap around when the end is reached). + first = 0; + last = 1; + num_sums = num_colors - 2; // -2 because we know the first two values + if (num_sums > 0) { + // Initialize the sums with the first two remappings and find the best one + struct Sum* best_sum = &sums[0]; + best_sum->index = 0u; + best_sum->sum = 0u; + for (i = 0, j = 0; i < num_colors; ++i) { + if (i == remapping[0] || i == remapping[1]) continue; + sums[j].index = i; + sums[j].sum = cooccurrence[i * num_colors + remapping[0]] + + cooccurrence[i * num_colors + remapping[1]]; + if (sums[j].sum > best_sum->sum) best_sum = &sums[j]; + ++j; + } + + while (num_sums > 0) { + const uint8_t best_index = best_sum->index; + // Compute delta to know if we need to prepend or append the best index. + int32_t delta = 0; + const int32_t n = num_colors - num_sums; + for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) { + const uint16_t l_j = remapping[(ind + j) % num_colors]; + delta += (n - 1 - 2 * (int32_t)j) * + (int32_t)cooccurrence[best_index * num_colors + l_j]; + } + if (delta > 0) { + first = (first == 0) ? num_colors - 1 : first - 1; + remapping[first] = best_index; + } else { + ++last; + remapping[last] = best_index; + } + // Remove best_sum from sums. + *best_sum = sums[num_sums - 1]; + --num_sums; + // Update all the sums and find the best one. + best_sum = &sums[0]; + for (i = 0; i < num_sums; ++i) { + sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index]; + if (sums[i].sum > best_sum->sum) best_sum = &sums[i]; + } + } + } + assert((last + 1) % num_colors == first); + WebPSafeFree(cooccurrence); + + // Re-map the palette. + for (i = 0; i < num_colors; ++i) { + palette[i] = palette_in[remapping[(first + i) % num_colors]]; + } + return 1; +} + +// ----------------------------------------------------------------------------- + +int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, + const uint32_t* const palette_sorted, uint32_t num_colors, + uint32_t* const palette) { + switch (method) { + case kSortedDefault: + // Nothing to do, we have already sorted the palette. + memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); + return 1; + case kMinimizeDelta: + PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); + return 1; + case kModifiedZeng: + return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette); + case kUnusedPalette: + case kPaletteSortingNum: + break; + } + + assert(0); + return 0; +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.h new file mode 100644 index 0000000000..34479e463f --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/palette.h @@ -0,0 +1,60 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for palette analysis. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_UTILS_PALETTE_H_ +#define WEBP_UTILS_PALETTE_H_ + +#include "src/webp/types.h" + +struct WebPPicture; + +// The different ways a palette can be sorted. +typedef enum PaletteSorting { + kSortedDefault = 0, + // Sorts by minimizing L1 deltas between consecutive colors, giving more + // weight to RGB colors. + kMinimizeDelta = 1, + // Implements the modified Zeng method from "A Survey on Palette Reordering + // Methods for Improving the Compression of Color-Indexed Images" by Armando + // J. Pinho and Antonio J. R. Neves. + kModifiedZeng = 2, + kUnusedPalette = 3, + kPaletteSortingNum = 4 +} PaletteSorting; + +// Returns the index of 'color' in the sorted palette 'sorted' of size +// 'num_colors'. +int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors); + +// Sort palette in increasing order and prepare an inverse mapping array. +void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors, + uint32_t sorted[], uint32_t idx_map[]); + +// Returns count of unique colors in 'pic', assuming pic->use_argb is true. +// If the unique color count is more than MAX_PALETTE_SIZE, returns +// MAX_PALETTE_SIZE+1. +// If 'palette' is not NULL and the number of unique colors is less than or +// equal to MAX_PALETTE_SIZE, also outputs the actual unique colors into +// 'palette' in a sorted order. Note: 'palette' is assumed to be an array +// already allocated with at least MAX_PALETTE_SIZE elements. +int GetColorPalette(const struct WebPPicture* const pic, + uint32_t* const palette); + +// Sorts the palette according to the criterion defined by 'method'. +// 'palette_sorted' is the input palette sorted lexicographically, as done in +// PrepareMapToPalette. Returns 0 on memory allocation error. +int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, + const uint32_t* const palette_sorted, uint32_t num_colors, + uint32_t* const palette); + +#endif // WEBP_UTILS_PALETTE_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.c new file mode 100644 index 0000000000..97e7893704 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.c @@ -0,0 +1,291 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Implement gradient smoothing: we replace a current alpha value by its +// surrounding average if it's close enough (that is: the change will be less +// than the minimum distance between two quantized level). +// We use sliding window for computing the 2d moving average. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/utils/quant_levels_dec_utils.h" + +#include // for memset + +#include "src/utils/utils.h" + +// #define USE_DITHERING // uncomment to enable ordered dithering (not vital) + +#define FIX 16 // fix-point precision for averaging +#define LFIX 2 // extra precision for look-up table +#define LUT_SIZE ((1 << (8 + LFIX)) - 1) // look-up table size + +#if defined(USE_DITHERING) + +#define DFIX 4 // extra precision for ordered dithering +#define DSIZE 4 // dithering size (must be a power of two) +// cf. https://en.wikipedia.org/wiki/Ordered_dithering +static const uint8_t kOrderedDither[DSIZE][DSIZE] = { + { 0, 8, 2, 10 }, // coefficients are in DFIX fixed-point precision + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } +}; + +#else +#define DFIX 0 +#endif + +typedef struct { + int width_, height_; // dimension + int stride_; // stride in bytes + int row_; // current input row being processed + uint8_t* src_; // input pointer + uint8_t* dst_; // output pointer + + int radius_; // filter radius (=delay) + int scale_; // normalization factor, in FIX bits precision + + void* mem_; // all memory + + // various scratch buffers + uint16_t* start_; + uint16_t* cur_; + uint16_t* end_; + uint16_t* top_; + uint16_t* average_; + + // input levels distribution + int num_levels_; // number of quantized levels + int min_, max_; // min and max level values + int min_level_dist_; // smallest distance between two consecutive levels + + int16_t* correction_; // size = 1 + 2*LUT_SIZE -> ~4k memory +} SmoothParams; + +//------------------------------------------------------------------------------ + +#define CLIP_8b_MASK (int)(~0U << (8 + DFIX)) +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & CLIP_8b_MASK)) ? (uint8_t)(v >> DFIX) : (v < 0) ? 0u : 255u; +} +#undef CLIP_8b_MASK + +// vertical accumulation +static void VFilter(SmoothParams* const p) { + const uint8_t* src = p->src_; + const int w = p->width_; + uint16_t* const cur = p->cur_; + const uint16_t* const top = p->top_; + uint16_t* const out = p->end_; + uint16_t sum = 0; // all arithmetic is modulo 16bit + int x; + + for (x = 0; x < w; ++x) { + uint16_t new_value; + sum += src[x]; + new_value = top[x] + sum; + out[x] = new_value - cur[x]; // vertical sum of 'r' pixels. + cur[x] = new_value; + } + // move input pointers one row down + p->top_ = p->cur_; + p->cur_ += w; + if (p->cur_ == p->end_) p->cur_ = p->start_; // roll-over + // We replicate edges, as it's somewhat easier as a boundary condition. + // That's why we don't update the 'src' pointer on top/bottom area: + if (p->row_ >= 0 && p->row_ < p->height_ - 1) { + p->src_ += p->stride_; + } +} + +// horizontal accumulation. We use mirror replication of missing pixels, as it's +// a little easier to implement (surprisingly). +static void HFilter(SmoothParams* const p) { + const uint16_t* const in = p->end_; + uint16_t* const out = p->average_; + const uint32_t scale = p->scale_; + const int w = p->width_; + const int r = p->radius_; + + int x; + for (x = 0; x <= r; ++x) { // left mirroring + const uint16_t delta = in[x + r - 1] + in[r - x]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w - r; ++x) { // bulk middle run + const uint16_t delta = in[x + r] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w; ++x) { // right mirroring + const uint16_t delta = + 2 * in[w - 1] - in[2 * w - 2 - r - x] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } +} + +// emit one filtered output row +static void ApplyFilter(SmoothParams* const p) { + const uint16_t* const average = p->average_; + const int w = p->width_; + const int16_t* const correction = p->correction_; +#if defined(USE_DITHERING) + const uint8_t* const dither = kOrderedDither[p->row_ % DSIZE]; +#endif + uint8_t* const dst = p->dst_; + int x; + for (x = 0; x < w; ++x) { + const int v = dst[x]; + if (v < p->max_ && v > p->min_) { + const int c = (v << DFIX) + correction[average[x] - (v << LFIX)]; +#if defined(USE_DITHERING) + dst[x] = clip_8b(c + dither[x % DSIZE]); +#else + dst[x] = clip_8b(c); +#endif + } + } + p->dst_ += p->stride_; // advance output pointer +} + +//------------------------------------------------------------------------------ +// Initialize correction table + +static void InitCorrectionLUT(int16_t* const lut, int min_dist) { + // The correction curve is: + // f(x) = x for x <= threshold2 + // f(x) = 0 for x >= threshold1 + // and a linear interpolation for range x=[threshold2, threshold1] + // (along with f(-x) = -f(x) symmetry). + // Note that: threshold2 = 3/4 * threshold1 + const int threshold1 = min_dist << LFIX; + const int threshold2 = (3 * threshold1) >> 2; + const int max_threshold = threshold2 << DFIX; + const int delta = threshold1 - threshold2; + int i; + for (i = 1; i <= LUT_SIZE; ++i) { + int c = (i <= threshold2) ? (i << DFIX) + : (i < threshold1) ? max_threshold * (threshold1 - i) / delta + : 0; + c >>= LFIX; + lut[+i] = +c; + lut[-i] = -c; + } + lut[0] = 0; +} + +static void CountLevels(SmoothParams* const p) { + int i, j, last_level; + uint8_t used_levels[256] = { 0 }; + const uint8_t* data = p->src_; + p->min_ = 255; + p->max_ = 0; + for (j = 0; j < p->height_; ++j) { + for (i = 0; i < p->width_; ++i) { + const int v = data[i]; + if (v < p->min_) p->min_ = v; + if (v > p->max_) p->max_ = v; + used_levels[v] = 1; + } + data += p->stride_; + } + // Compute the mininum distance between two non-zero levels. + p->min_level_dist_ = p->max_ - p->min_; + last_level = -1; + for (i = 0; i < 256; ++i) { + if (used_levels[i]) { + ++p->num_levels_; + if (last_level >= 0) { + const int level_dist = i - last_level; + if (level_dist < p->min_level_dist_) { + p->min_level_dist_ = level_dist; + } + } + last_level = i; + } + } +} + +// Initialize all params. +static int InitParams(uint8_t* const data, int width, int height, int stride, + int radius, SmoothParams* const p) { + const int R = 2 * radius + 1; // total size of the kernel + + const size_t size_scratch_m = (R + 1) * width * sizeof(*p->start_); + const size_t size_m = width * sizeof(*p->average_); + const size_t size_lut = (1 + 2 * LUT_SIZE) * sizeof(*p->correction_); + const size_t total_size = size_scratch_m + size_m + size_lut; + uint8_t* mem = (uint8_t*)WebPSafeMalloc(1U, total_size); + + if (mem == NULL) return 0; + p->mem_ = (void*)mem; + + p->start_ = (uint16_t*)mem; + p->cur_ = p->start_; + p->end_ = p->start_ + R * width; + p->top_ = p->end_ - width; + memset(p->top_, 0, width * sizeof(*p->top_)); + mem += size_scratch_m; + + p->average_ = (uint16_t*)mem; + mem += size_m; + + p->width_ = width; + p->height_ = height; + p->stride_ = stride; + p->src_ = data; + p->dst_ = data; + p->radius_ = radius; + p->scale_ = (1 << (FIX + LFIX)) / (R * R); // normalization constant + p->row_ = -radius; + + // analyze the input distribution so we can best-fit the threshold + CountLevels(p); + + // correction table + p->correction_ = ((int16_t*)mem) + LUT_SIZE; + InitCorrectionLUT(p->correction_, p->min_level_dist_); + + return 1; +} + +static void CleanupParams(SmoothParams* const p) { + WebPSafeFree(p->mem_); +} + +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength) { + int radius = 4 * strength / 100; + + if (strength < 0 || strength > 100) return 0; + if (data == NULL || width <= 0 || height <= 0) return 0; // bad params + + // limit the filter size to not exceed the image dimensions + if (2 * radius + 1 > width) radius = (width - 1) >> 1; + if (2 * radius + 1 > height) radius = (height - 1) >> 1; + + if (radius > 0) { + SmoothParams p; + memset(&p, 0, sizeof(p)); + if (!InitParams(data, width, height, stride, radius, &p)) return 0; + if (p.num_levels_ > 2) { + for (; p.row_ < p.height_; ++p.row_) { + VFilter(&p); // accumulate average of input + // Need to wait few rows in order to prime the filter, + // before emitting some output. + if (p.row_ >= p.radius_) { + HFilter(&p); + ApplyFilter(&p); + } + } + } + CleanupParams(&p); + } + return 1; +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.h new file mode 100644 index 0000000000..327f19f336 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_dec_utils.h @@ -0,0 +1,35 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha plane de-quantization utility +// +// Author: Vikas Arora (vikasa@google.com) + +#ifndef WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ +#define WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Apply post-processing to input 'data' of size 'width'x'height' assuming that +// the source was quantized to a reduced number of levels. 'stride' is in bytes. +// Strength is in [0..100] and controls the amount of dithering applied. +// Returns false in case of error (data is NULL, invalid parameters, +// malloc failure, ...). +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.c new file mode 100644 index 0000000000..d65ad3c29d --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.c @@ -0,0 +1,140 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Quantize levels for specified number of quantization-levels ([2, 256]). +// Min and max values are preserved (usual 0 and 255 for alpha plane). +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/utils/quant_levels_utils.h" + +#define NUM_SYMBOLS 256 + +#define MAX_ITER 6 // Maximum number of convergence steps. +#define ERROR_THRESHOLD 1e-4 // MSE stopping criterion. + +// ----------------------------------------------------------------------------- +// Quantize levels. + +int QuantizeLevels(uint8_t* const data, int width, int height, + int num_levels, uint64_t* const sse) { + int freq[NUM_SYMBOLS] = { 0 }; + int q_level[NUM_SYMBOLS] = { 0 }; + double inv_q_level[NUM_SYMBOLS] = { 0 }; + int min_s = 255, max_s = 0; + const size_t data_size = height * width; + int i, num_levels_in, iter; + double last_err = 1.e38, err = 0.; + const double err_threshold = ERROR_THRESHOLD * data_size; + + if (data == NULL) { + return 0; + } + + if (width <= 0 || height <= 0) { + return 0; + } + + if (num_levels < 2 || num_levels > 256) { + return 0; + } + + { + size_t n; + num_levels_in = 0; + for (n = 0; n < data_size; ++n) { + num_levels_in += (freq[data[n]] == 0); + if (min_s > data[n]) min_s = data[n]; + if (max_s < data[n]) max_s = data[n]; + ++freq[data[n]]; + } + } + + if (num_levels_in <= num_levels) goto End; // nothing to do! + + // Start with uniformly spread centroids. + for (i = 0; i < num_levels; ++i) { + inv_q_level[i] = min_s + (double)(max_s - min_s) * i / (num_levels - 1); + } + + // Fixed values. Won't be changed. + q_level[min_s] = 0; + q_level[max_s] = num_levels - 1; + assert(inv_q_level[0] == min_s); + assert(inv_q_level[num_levels - 1] == max_s); + + // k-Means iterations. + for (iter = 0; iter < MAX_ITER; ++iter) { + double q_sum[NUM_SYMBOLS] = { 0 }; + double q_count[NUM_SYMBOLS] = { 0 }; + int s, slot = 0; + + // Assign classes to representatives. + for (s = min_s; s <= max_s; ++s) { + // Keep track of the nearest neighbour 'slot' + while (slot < num_levels - 1 && + 2 * s > inv_q_level[slot] + inv_q_level[slot + 1]) { + ++slot; + } + if (freq[s] > 0) { + q_sum[slot] += s * freq[s]; + q_count[slot] += freq[s]; + } + q_level[s] = slot; + } + + // Assign new representatives to classes. + if (num_levels > 2) { + for (slot = 1; slot < num_levels - 1; ++slot) { + const double count = q_count[slot]; + if (count > 0.) { + inv_q_level[slot] = q_sum[slot] / count; + } + } + } + + // Compute convergence error. + err = 0.; + for (s = min_s; s <= max_s; ++s) { + const double error = s - inv_q_level[q_level[s]]; + err += freq[s] * error * error; + } + + // Check for convergence: we stop as soon as the error is no + // longer improving. + if (last_err - err < err_threshold) break; + last_err = err; + } + + // Remap the alpha plane to quantized values. + { + // double->int rounding operation can be costly, so we do it + // once for all before remapping. We also perform the data[] -> slot + // mapping, while at it (avoid one indirection in the final loop). + uint8_t map[NUM_SYMBOLS]; + int s; + size_t n; + for (s = min_s; s <= max_s; ++s) { + const int slot = q_level[s]; + map[s] = (uint8_t)(inv_q_level[slot] + .5); + } + // Final pass. + for (n = 0; n < data_size; ++n) { + data[n] = map[data[n]]; + } + } + End: + // Store sum of squared error if needed. + if (sse != NULL) *sse = (uint64_t)err; + + return 1; +} + diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.h new file mode 100644 index 0000000000..9ee3ea0075 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/quant_levels_utils.h @@ -0,0 +1,36 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha plane quantization utility +// +// Author: Vikas Arora (vikasa@google.com) + +#ifndef WEBP_UTILS_QUANT_LEVELS_UTILS_H_ +#define WEBP_UTILS_QUANT_LEVELS_UTILS_H_ + +#include + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Replace the input 'data' of size 'width'x'height' with 'num-levels' +// quantized values. If not NULL, 'sse' will contain the sum of squared error. +// Valid range for 'num_levels' is [2, 256]. +// Returns false in case of error (data is NULL, or parameters are invalid). +int QuantizeLevels(uint8_t* const data, int width, int height, int num_levels, + uint64_t* const sse); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_QUANT_LEVELS_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.c new file mode 100644 index 0000000000..7edb3fefbb --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.c @@ -0,0 +1,43 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include "src/utils/random_utils.h" + +//------------------------------------------------------------------------------ + +// 31b-range values +static const uint32_t kRandomTable[VP8_RANDOM_TABLE_SIZE] = { + 0x0de15230, 0x03b31886, 0x775faccb, 0x1c88626a, 0x68385c55, 0x14b3b828, + 0x4a85fef8, 0x49ddb84b, 0x64fcf397, 0x5c550289, 0x4a290000, 0x0d7ec1da, + 0x5940b7ab, 0x5492577d, 0x4e19ca72, 0x38d38c69, 0x0c01ee65, 0x32a1755f, + 0x5437f652, 0x5abb2c32, 0x0faa57b1, 0x73f533e7, 0x685feeda, 0x7563cce2, + 0x6e990e83, 0x4730a7ed, 0x4fc0d9c6, 0x496b153c, 0x4f1403fa, 0x541afb0c, + 0x73990b32, 0x26d7cb1c, 0x6fcc3706, 0x2cbb77d8, 0x75762f2a, 0x6425ccdd, + 0x24b35461, 0x0a7d8715, 0x220414a8, 0x141ebf67, 0x56b41583, 0x73e502e3, + 0x44cab16f, 0x28264d42, 0x73baaefb, 0x0a50ebed, 0x1d6ab6fb, 0x0d3ad40b, + 0x35db3b68, 0x2b081e83, 0x77ce6b95, 0x5181e5f0, 0x78853bbc, 0x009f9494, + 0x27e5ed3c +}; + +void VP8InitRandom(VP8Random* const rg, float dithering) { + memcpy(rg->tab_, kRandomTable, sizeof(rg->tab_)); + rg->index1_ = 0; + rg->index2_ = 31; + rg->amp_ = (dithering < 0.0) ? 0 + : (dithering > 1.0) ? (1 << VP8_RANDOM_DITHER_FIX) + : (uint32_t)((1 << VP8_RANDOM_DITHER_FIX) * dithering); +} + +//------------------------------------------------------------------------------ + diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.h new file mode 100644 index 0000000000..a5006f84f7 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/random_utils.h @@ -0,0 +1,63 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RANDOM_UTILS_H_ +#define WEBP_UTILS_RANDOM_UTILS_H_ + +#include +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VP8_RANDOM_DITHER_FIX 8 // fixed-point precision for dithering +#define VP8_RANDOM_TABLE_SIZE 55 + +typedef struct { + int index1_, index2_; + uint32_t tab_[VP8_RANDOM_TABLE_SIZE]; + int amp_; +} VP8Random; + +// Initializes random generator with an amplitude 'dithering' in range [0..1]. +void VP8InitRandom(VP8Random* const rg, float dithering); + +// Returns a centered pseudo-random number with 'num_bits' amplitude. +// (uses D.Knuth's Difference-based random generator). +// 'amp' is in VP8_RANDOM_DITHER_FIX fixed-point precision. +static WEBP_INLINE int VP8RandomBits2(VP8Random* const rg, int num_bits, + int amp) { + int diff; + assert(num_bits + VP8_RANDOM_DITHER_FIX <= 31); + diff = rg->tab_[rg->index1_] - rg->tab_[rg->index2_]; + if (diff < 0) diff += (1u << 31); + rg->tab_[rg->index1_] = diff; + if (++rg->index1_ == VP8_RANDOM_TABLE_SIZE) rg->index1_ = 0; + if (++rg->index2_ == VP8_RANDOM_TABLE_SIZE) rg->index2_ = 0; + // sign-extend, 0-center + diff = (int)((uint32_t)diff << 1) >> (32 - num_bits); + diff = (diff * amp) >> VP8_RANDOM_DITHER_FIX; // restrict range + diff += 1 << (num_bits - 1); // shift back to 0.5-center + return diff; +} + +static WEBP_INLINE int VP8RandomBits(VP8Random* const rg, int num_bits) { + return VP8RandomBits2(rg, num_bits, rg->amp_); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_RANDOM_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.c new file mode 100644 index 0000000000..a0581a14b1 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.c @@ -0,0 +1,160 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include +#include +#include "src/dsp/dsp.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ + +int WebPRescalerInit(WebPRescaler* const rescaler, + int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, rescaler_t* const work) { + const int x_add = src_width, x_sub = dst_width; + const int y_add = src_height, y_sub = dst_height; + const uint64_t total_size = 2ull * dst_width * num_channels * sizeof(*work); + if (!CheckSizeOverflow(total_size)) return 0; + + rescaler->x_expand = (src_width < dst_width); + rescaler->y_expand = (src_height < dst_height); + rescaler->src_width = src_width; + rescaler->src_height = src_height; + rescaler->dst_width = dst_width; + rescaler->dst_height = dst_height; + rescaler->src_y = 0; + rescaler->dst_y = 0; + rescaler->dst = dst; + rescaler->dst_stride = dst_stride; + rescaler->num_channels = num_channels; + + // for 'x_expand', we use bilinear interpolation + rescaler->x_add = rescaler->x_expand ? (x_sub - 1) : x_add; + rescaler->x_sub = rescaler->x_expand ? (x_add - 1) : x_sub; + if (!rescaler->x_expand) { // fx_scale is not used otherwise + rescaler->fx_scale = WEBP_RESCALER_FRAC(1, rescaler->x_sub); + } + // vertical scaling parameters + rescaler->y_add = rescaler->y_expand ? y_add - 1 : y_add; + rescaler->y_sub = rescaler->y_expand ? y_sub - 1 : y_sub; + rescaler->y_accum = rescaler->y_expand ? rescaler->y_sub : rescaler->y_add; + if (!rescaler->y_expand) { + // This is WEBP_RESCALER_FRAC(dst_height, x_add * y_add) without the cast. + // Its value is <= WEBP_RESCALER_ONE, because dst_height <= rescaler->y_add + // and rescaler->x_add >= 1; + const uint64_t num = (uint64_t)dst_height * WEBP_RESCALER_ONE; + const uint64_t den = (uint64_t)rescaler->x_add * rescaler->y_add; + const uint64_t ratio = num / den; + if (ratio != (uint32_t)ratio) { + // When ratio == WEBP_RESCALER_ONE, we can't represent the ratio with the + // current fixed-point precision. This happens when src_height == + // rescaler->y_add (which == src_height), and rescaler->x_add == 1. + // => We special-case fxy_scale = 0, in WebPRescalerExportRow(). + rescaler->fxy_scale = 0; + } else { + rescaler->fxy_scale = (uint32_t)ratio; + } + rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->y_sub); + } else { + rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->x_add); + // rescaler->fxy_scale is unused here. + } + rescaler->irow = work; + rescaler->frow = work + num_channels * dst_width; + memset(work, 0, (size_t)total_size); + + WebPRescalerDspInit(); + return 1; +} + +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height) { + assert(scaled_width != NULL); + assert(scaled_height != NULL); + { + int width = *scaled_width; + int height = *scaled_height; + const int max_size = INT_MAX / 2; + + // if width is unspecified, scale original proportionally to height ratio. + if (width == 0 && src_height > 0) { + width = + (int)(((uint64_t)src_width * height + src_height - 1) / src_height); + } + // if height is unspecified, scale original proportionally to width ratio. + if (height == 0 && src_width > 0) { + height = + (int)(((uint64_t)src_height * width + src_width - 1) / src_width); + } + // Check if the overall dimensions still make sense. + if (width <= 0 || height <= 0 || width > max_size || height > max_size) { + return 0; + } + + *scaled_width = width; + *scaled_height = height; + return 1; + } +} + +//------------------------------------------------------------------------------ +// all-in-one calls + +int WebPRescaleNeededLines(const WebPRescaler* const rescaler, + int max_num_lines) { + const int num_lines = + (rescaler->y_accum + rescaler->y_sub - 1) / rescaler->y_sub; + return (num_lines > max_num_lines) ? max_num_lines : num_lines; +} + +int WebPRescalerImport(WebPRescaler* const rescaler, int num_lines, + const uint8_t* src, int src_stride) { + int total_imported = 0; + while (total_imported < num_lines && + !WebPRescalerHasPendingOutput(rescaler)) { + if (rescaler->y_expand) { + rescaler_t* const tmp = rescaler->irow; + rescaler->irow = rescaler->frow; + rescaler->frow = tmp; + } + WebPRescalerImportRow(rescaler, src); + if (!rescaler->y_expand) { // Accumulate the contribution of the new row. + int x; + for (x = 0; x < rescaler->num_channels * rescaler->dst_width; ++x) { + rescaler->irow[x] += rescaler->frow[x]; + } + } + ++rescaler->src_y; + src += src_stride; + ++total_imported; + rescaler->y_accum -= rescaler->y_sub; + } + return total_imported; +} + +int WebPRescalerExport(WebPRescaler* const rescaler) { + int total_exported = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + ++total_exported; + } + return total_exported; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.h new file mode 100644 index 0000000000..ef201ef86c --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/rescaler_utils.h @@ -0,0 +1,102 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RESCALER_UTILS_H_ +#define WEBP_UTILS_RESCALER_UTILS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "src/webp/types.h" + +#define WEBP_RESCALER_RFIX 32 // fixed-point precision for multiplies +#define WEBP_RESCALER_ONE (1ull << WEBP_RESCALER_RFIX) +#define WEBP_RESCALER_FRAC(x, y) \ + ((uint32_t)(((uint64_t)(x) << WEBP_RESCALER_RFIX) / (y))) + +// Structure used for on-the-fly rescaling +typedef uint32_t rescaler_t; // type for side-buffer +typedef struct WebPRescaler WebPRescaler; +struct WebPRescaler { + int x_expand; // true if we're expanding in the x direction + int y_expand; // true if we're expanding in the y direction + int num_channels; // bytes to jump between pixels + uint32_t fx_scale; // fixed-point scaling factors + uint32_t fy_scale; // '' + uint32_t fxy_scale; // '' + int y_accum; // vertical accumulator + int y_add, y_sub; // vertical increments + int x_add, x_sub; // horizontal increments + int src_width, src_height; // source dimensions + int dst_width, dst_height; // destination dimensions + int src_y, dst_y; // row counters for input and output + uint8_t* dst; + int dst_stride; + rescaler_t* irow, *frow; // work buffer +}; + +// Initialize a rescaler given scratch area 'work' and dimensions of src & dst. +// Returns false in case of error. +int WebPRescalerInit(WebPRescaler* const rescaler, + int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, + rescaler_t* const work); + +// If either 'scaled_width' or 'scaled_height' (but not both) is 0 the value +// will be calculated preserving the aspect ratio, otherwise the values are +// left unmodified. Returns true on success, false if either value is 0 after +// performing the scaling calculation. +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height); + +// Returns the number of input lines needed next to produce one output line, +// considering that the maximum available input lines are 'max_num_lines'. +int WebPRescaleNeededLines(const WebPRescaler* const rescaler, + int max_num_lines); + +// Import multiple rows over all channels, until at least one row is ready to +// be exported. Returns the actual number of lines that were imported. +int WebPRescalerImport(WebPRescaler* const rescaler, int num_rows, + const uint8_t* src, int src_stride); + +// Export as many rows as possible. Return the numbers of rows written. +int WebPRescalerExport(WebPRescaler* const rescaler); + +// Return true if input is finished +static WEBP_INLINE +int WebPRescalerInputDone(const WebPRescaler* const rescaler) { + return (rescaler->src_y >= rescaler->src_height); +} +// Return true if output is finished +static WEBP_INLINE +int WebPRescalerOutputDone(const WebPRescaler* const rescaler) { + return (rescaler->dst_y >= rescaler->dst_height); +} + +// Return true if there are pending output rows ready. +static WEBP_INLINE +int WebPRescalerHasPendingOutput(const WebPRescaler* const rescaler) { + return !WebPRescalerOutputDone(rescaler) && (rescaler->y_accum <= 0); +} + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_RESCALER_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.c new file mode 100644 index 0000000000..4e470e17ac --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.c @@ -0,0 +1,369 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Multi-threaded worker +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include // for memset() +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" + +#ifdef WEBP_USE_THREAD + +#if defined(_WIN32) + +#include +typedef HANDLE pthread_t; +typedef CRITICAL_SECTION pthread_mutex_t; + +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater +#define USE_WINDOWS_CONDITION_VARIABLE +typedef CONDITION_VARIABLE pthread_cond_t; +#else +typedef struct { + HANDLE waiting_sem_; + HANDLE received_sem_; + HANDLE signal_event_; +} pthread_cond_t; +#endif // _WIN32_WINNT >= 0x600 + +#ifndef WINAPI_FAMILY_PARTITION +#define WINAPI_PARTITION_DESKTOP 1 +#define WINAPI_FAMILY_PARTITION(x) x +#endif + +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define USE_CREATE_THREAD +#endif + +#else // !_WIN32 + +#include + +#endif // _WIN32 + +typedef struct { + pthread_mutex_t mutex_; + pthread_cond_t condition_; + pthread_t thread_; +} WebPWorkerImpl; + +#if defined(_WIN32) + +//------------------------------------------------------------------------------ +// simplistic pthread emulation layer + +#include + +// _beginthreadex requires __stdcall +#define THREADFN unsigned int __stdcall +#define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val) + +#if _WIN32_WINNT >= 0x0501 // Windows XP or greater +#define WaitForSingleObject(obj, timeout) \ + WaitForSingleObjectEx(obj, timeout, FALSE /*bAlertable*/) +#endif + +static int pthread_create(pthread_t* const thread, const void* attr, + unsigned int (__stdcall* start)(void*), void* arg) { + (void)attr; +#ifdef USE_CREATE_THREAD + *thread = CreateThread(NULL, /* lpThreadAttributes */ + 0, /* dwStackSize */ + start, + arg, + 0, /* dwStackSize */ + NULL); /* lpThreadId */ +#else + *thread = (pthread_t)_beginthreadex(NULL, /* void *security */ + 0, /* unsigned stack_size */ + start, + arg, + 0, /* unsigned initflag */ + NULL); /* unsigned *thrdaddr */ +#endif + if (*thread == NULL) return 1; + SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL); + return 0; +} + +static int pthread_join(pthread_t thread, void** value_ptr) { + (void)value_ptr; + return (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0 || + CloseHandle(thread) == 0); +} + +// Mutex +static int pthread_mutex_init(pthread_mutex_t* const mutex, void* mutexattr) { + (void)mutexattr; +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater + InitializeCriticalSectionEx(mutex, 0 /*dwSpinCount*/, 0 /*Flags*/); +#else + InitializeCriticalSection(mutex); +#endif + return 0; +} + +static int pthread_mutex_lock(pthread_mutex_t* const mutex) { + EnterCriticalSection(mutex); + return 0; +} + +static int pthread_mutex_unlock(pthread_mutex_t* const mutex) { + LeaveCriticalSection(mutex); + return 0; +} + +static int pthread_mutex_destroy(pthread_mutex_t* const mutex) { + DeleteCriticalSection(mutex); + return 0; +} + +// Condition +static int pthread_cond_destroy(pthread_cond_t* const condition) { + int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + (void)condition; +#else + ok &= (CloseHandle(condition->waiting_sem_) != 0); + ok &= (CloseHandle(condition->received_sem_) != 0); + ok &= (CloseHandle(condition->signal_event_) != 0); +#endif + return !ok; +} + +static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) { + (void)cond_attr; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + InitializeConditionVariable(condition); +#else + condition->waiting_sem_ = CreateSemaphore(NULL, 0, 1, NULL); + condition->received_sem_ = CreateSemaphore(NULL, 0, 1, NULL); + condition->signal_event_ = CreateEvent(NULL, FALSE, FALSE, NULL); + if (condition->waiting_sem_ == NULL || + condition->received_sem_ == NULL || + condition->signal_event_ == NULL) { + pthread_cond_destroy(condition); + return 1; + } +#endif + return 0; +} + +static int pthread_cond_signal(pthread_cond_t* const condition) { + int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + WakeConditionVariable(condition); +#else + if (WaitForSingleObject(condition->waiting_sem_, 0) == WAIT_OBJECT_0) { + // a thread is waiting in pthread_cond_wait: allow it to be notified + ok = SetEvent(condition->signal_event_); + // wait until the event is consumed so the signaler cannot consume + // the event via its own pthread_cond_wait. + ok &= (WaitForSingleObject(condition->received_sem_, INFINITE) != + WAIT_OBJECT_0); + } +#endif + return !ok; +} + +static int pthread_cond_wait(pthread_cond_t* const condition, + pthread_mutex_t* const mutex) { + int ok; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + ok = SleepConditionVariableCS(condition, mutex, INFINITE); +#else + // note that there is a consumer available so the signal isn't dropped in + // pthread_cond_signal + if (!ReleaseSemaphore(condition->waiting_sem_, 1, NULL)) return 1; + // now unlock the mutex so pthread_cond_signal may be issued + pthread_mutex_unlock(mutex); + ok = (WaitForSingleObject(condition->signal_event_, INFINITE) == + WAIT_OBJECT_0); + ok &= ReleaseSemaphore(condition->received_sem_, 1, NULL); + pthread_mutex_lock(mutex); +#endif + return !ok; +} + +#else // !_WIN32 +# define THREADFN void* +# define THREAD_RETURN(val) val +#endif // _WIN32 + +//------------------------------------------------------------------------------ + +static THREADFN ThreadLoop(void* ptr) { + WebPWorker* const worker = (WebPWorker*)ptr; + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl_; + int done = 0; + while (!done) { + pthread_mutex_lock(&impl->mutex_); + while (worker->status_ == OK) { // wait in idling mode + pthread_cond_wait(&impl->condition_, &impl->mutex_); + } + if (worker->status_ == WORK) { + WebPGetWorkerInterface()->Execute(worker); + worker->status_ = OK; + } else if (worker->status_ == NOT_OK) { // finish the worker + done = 1; + } + // signal to the main thread that we're done (for Sync()) + // Note the associated mutex does not need to be held when signaling the + // condition. Unlocking the mutex first may improve performance in some + // implementations, avoiding the case where the waiting thread can't + // reacquire the mutex when woken. + pthread_mutex_unlock(&impl->mutex_); + pthread_cond_signal(&impl->condition_); + } + return THREAD_RETURN(NULL); // Thread is finished +} + +// main thread state control +static void ChangeState(WebPWorker* const worker, WebPWorkerStatus new_status) { + // No-op when attempting to change state on a thread that didn't come up. + // Checking status_ without acquiring the lock first would result in a data + // race. + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl_; + if (impl == NULL) return; + + pthread_mutex_lock(&impl->mutex_); + if (worker->status_ >= OK) { + // wait for the worker to finish + while (worker->status_ != OK) { + pthread_cond_wait(&impl->condition_, &impl->mutex_); + } + // assign new status and release the working thread if needed + if (new_status != OK) { + worker->status_ = new_status; + // Note the associated mutex does not need to be held when signaling the + // condition. Unlocking the mutex first may improve performance in some + // implementations, avoiding the case where the waiting thread can't + // reacquire the mutex when woken. + pthread_mutex_unlock(&impl->mutex_); + pthread_cond_signal(&impl->condition_); + return; + } + } + pthread_mutex_unlock(&impl->mutex_); +} + +#endif // WEBP_USE_THREAD + +//------------------------------------------------------------------------------ + +static void Init(WebPWorker* const worker) { + memset(worker, 0, sizeof(*worker)); + worker->status_ = NOT_OK; +} + +static int Sync(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + ChangeState(worker, OK); +#endif + assert(worker->status_ <= OK); + return !worker->had_error; +} + +static int Reset(WebPWorker* const worker) { + int ok = 1; + worker->had_error = 0; + if (worker->status_ < OK) { +#ifdef WEBP_USE_THREAD + WebPWorkerImpl* const impl = + (WebPWorkerImpl*)WebPSafeCalloc(1, sizeof(WebPWorkerImpl)); + worker->impl_ = (void*)impl; + if (worker->impl_ == NULL) { + return 0; + } + if (pthread_mutex_init(&impl->mutex_, NULL)) { + goto Error; + } + if (pthread_cond_init(&impl->condition_, NULL)) { + pthread_mutex_destroy(&impl->mutex_); + goto Error; + } + pthread_mutex_lock(&impl->mutex_); + ok = !pthread_create(&impl->thread_, NULL, ThreadLoop, worker); + if (ok) worker->status_ = OK; + pthread_mutex_unlock(&impl->mutex_); + if (!ok) { + pthread_mutex_destroy(&impl->mutex_); + pthread_cond_destroy(&impl->condition_); + Error: + WebPSafeFree(impl); + worker->impl_ = NULL; + return 0; + } +#else + worker->status_ = OK; +#endif + } else if (worker->status_ > OK) { + ok = Sync(worker); + } + assert(!ok || (worker->status_ == OK)); + return ok; +} + +static void Execute(WebPWorker* const worker) { + if (worker->hook != NULL) { + worker->had_error |= !worker->hook(worker->data1, worker->data2); + } +} + +static void Launch(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + ChangeState(worker, WORK); +#else + Execute(worker); +#endif +} + +static void End(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + if (worker->impl_ != NULL) { + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl_; + ChangeState(worker, NOT_OK); + pthread_join(impl->thread_, NULL); + pthread_mutex_destroy(&impl->mutex_); + pthread_cond_destroy(&impl->condition_); + WebPSafeFree(impl); + worker->impl_ = NULL; + } +#else + worker->status_ = NOT_OK; + assert(worker->impl_ == NULL); +#endif + assert(worker->status_ == NOT_OK); +} + +//------------------------------------------------------------------------------ + +static WebPWorkerInterface g_worker_interface = { + Init, Reset, Sync, Launch, Execute, End +}; + +int WebPSetWorkerInterface(const WebPWorkerInterface* const winterface) { + if (winterface == NULL || + winterface->Init == NULL || winterface->Reset == NULL || + winterface->Sync == NULL || winterface->Launch == NULL || + winterface->Execute == NULL || winterface->End == NULL) { + return 0; + } + g_worker_interface = *winterface; + return 1; +} + +const WebPWorkerInterface* WebPGetWorkerInterface(void) { + return &g_worker_interface; +} + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.h new file mode 100644 index 0000000000..29ad49f74b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/thread_utils.h @@ -0,0 +1,90 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Multi-threaded worker +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_THREAD_UTILS_H_ +#define WEBP_UTILS_THREAD_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// State of the worker thread object +typedef enum { + NOT_OK = 0, // object is unusable + OK, // ready to work + WORK // busy finishing the current task +} WebPWorkerStatus; + +// Function to be called by the worker thread. Takes two opaque pointers as +// arguments (data1 and data2), and should return false in case of error. +typedef int (*WebPWorkerHook)(void*, void*); + +// Synchronization object used to launch job in the worker thread +typedef struct { + void* impl_; // platform-dependent implementation worker details + WebPWorkerStatus status_; + WebPWorkerHook hook; // hook to call + void* data1; // first argument passed to 'hook' + void* data2; // second argument passed to 'hook' + int had_error; // return value of the last call to 'hook' +} WebPWorker; + +// The interface for all thread-worker related functions. All these functions +// must be implemented. +typedef struct { + // Must be called first, before any other method. + void (*Init)(WebPWorker* const worker); + // Must be called to initialize the object and spawn the thread. Re-entrant. + // Will potentially launch the thread. Returns false in case of error. + int (*Reset)(WebPWorker* const worker); + // Makes sure the previous work is finished. Returns true if worker->had_error + // was not set and no error condition was triggered by the working thread. + int (*Sync)(WebPWorker* const worker); + // Triggers the thread to call hook() with data1 and data2 arguments. These + // hook/data1/data2 values can be changed at any time before calling this + // function, but not be changed afterward until the next call to Sync(). + void (*Launch)(WebPWorker* const worker); + // This function is similar to Launch() except that it calls the + // hook directly instead of using a thread. Convenient to bypass the thread + // mechanism while still using the WebPWorker structs. Sync() must + // still be called afterward (for error reporting). + void (*Execute)(WebPWorker* const worker); + // Kill the thread and terminate the object. To use the object again, one + // must call Reset() again. + void (*End)(WebPWorker* const worker); +} WebPWorkerInterface; + +// Install a new set of threading functions, overriding the defaults. This +// should be done before any workers are started, i.e., before any encoding or +// decoding takes place. The contents of the interface struct are copied, it +// is safe to free the corresponding memory after this call. This function is +// not thread-safe. Return false in case of invalid pointer or methods. +WEBP_EXTERN int WebPSetWorkerInterface( + const WebPWorkerInterface* const winterface); + +// Retrieve the currently set thread worker interface. +WEBP_EXTERN const WebPWorkerInterface* WebPGetWorkerInterface(void); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_THREAD_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.c b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.c new file mode 100644 index 0000000000..408ce88f67 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.c @@ -0,0 +1,282 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Misc. common utility functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/utils/utils.h" + +#include +#include // for memcpy() + +#include "src/utils/palette.h" +#include "src/webp/encode.h" + +// If PRINT_MEM_INFO is defined, extra info (like total memory used, number of +// alloc/free etc) is printed. For debugging/tuning purpose only (it's slow, +// and not multi-thread safe!). +// An interesting alternative is valgrind's 'massif' tool: +// https://valgrind.org/docs/manual/ms-manual.html +// Here is an example command line: +/* valgrind --tool=massif --massif-out-file=massif.out \ + --stacks=yes --alloc-fn=WebPSafeMalloc --alloc-fn=WebPSafeCalloc + ms_print massif.out +*/ +// In addition: +// * if PRINT_MEM_TRAFFIC is defined, all the details of the malloc/free cycles +// are printed. +// * if MALLOC_FAIL_AT is defined, the global environment variable +// $MALLOC_FAIL_AT is used to simulate a memory error when calloc or malloc +// is called for the nth time. Example usage: +// export MALLOC_FAIL_AT=50 && ./examples/cwebp input.png +// * if MALLOC_LIMIT is defined, the global environment variable $MALLOC_LIMIT +// sets the maximum amount of memory (in bytes) made available to libwebp. +// This can be used to emulate environment with very limited memory. +// Example: export MALLOC_LIMIT=64000000 && ./examples/dwebp picture.webp + +// #define PRINT_MEM_INFO +// #define PRINT_MEM_TRAFFIC +// #define MALLOC_FAIL_AT +// #define MALLOC_LIMIT + +//------------------------------------------------------------------------------ +// Checked memory allocation + +#if defined(PRINT_MEM_INFO) + +#include + +static int num_malloc_calls = 0; +static int num_calloc_calls = 0; +static int num_free_calls = 0; +static int countdown_to_fail = 0; // 0 = off + +typedef struct MemBlock MemBlock; +struct MemBlock { + void* ptr_; + size_t size_; + MemBlock* next_; +}; + +static MemBlock* all_blocks = NULL; +static size_t total_mem = 0; +static size_t total_mem_allocated = 0; +static size_t high_water_mark = 0; +static size_t mem_limit = 0; + +static int exit_registered = 0; + +static void PrintMemInfo(void) { + fprintf(stderr, "\nMEMORY INFO:\n"); + fprintf(stderr, "num calls to: malloc = %4d\n", num_malloc_calls); + fprintf(stderr, " calloc = %4d\n", num_calloc_calls); + fprintf(stderr, " free = %4d\n", num_free_calls); + fprintf(stderr, "total_mem: %u\n", (uint32_t)total_mem); + fprintf(stderr, "total_mem allocated: %u\n", (uint32_t)total_mem_allocated); + fprintf(stderr, "high-water mark: %u\n", (uint32_t)high_water_mark); + while (all_blocks != NULL) { + MemBlock* b = all_blocks; + all_blocks = b->next_; + free(b); + } +} + +static void Increment(int* const v) { + if (!exit_registered) { +#if defined(MALLOC_FAIL_AT) + { + const char* const malloc_fail_at_str = getenv("MALLOC_FAIL_AT"); + if (malloc_fail_at_str != NULL) { + countdown_to_fail = atoi(malloc_fail_at_str); + } + } +#endif +#if defined(MALLOC_LIMIT) + { + const char* const malloc_limit_str = getenv("MALLOC_LIMIT"); +#if MALLOC_LIMIT > 1 + mem_limit = (size_t)MALLOC_LIMIT; +#endif + if (malloc_limit_str != NULL) { + mem_limit = atoi(malloc_limit_str); + } + } +#endif + (void)countdown_to_fail; + (void)mem_limit; + atexit(PrintMemInfo); + exit_registered = 1; + } + ++*v; +} + +static void AddMem(void* ptr, size_t size) { + if (ptr != NULL) { + MemBlock* const b = (MemBlock*)malloc(sizeof(*b)); + if (b == NULL) abort(); + b->next_ = all_blocks; + all_blocks = b; + b->ptr_ = ptr; + b->size_ = size; + total_mem += size; + total_mem_allocated += size; +#if defined(PRINT_MEM_TRAFFIC) +#if defined(MALLOC_FAIL_AT) + fprintf(stderr, "fail-count: %5d [mem=%u]\n", + num_malloc_calls + num_calloc_calls, (uint32_t)total_mem); +#else + fprintf(stderr, "Mem: %u (+%u)\n", (uint32_t)total_mem, (uint32_t)size); +#endif +#endif + if (total_mem > high_water_mark) high_water_mark = total_mem; + } +} + +static void SubMem(void* ptr) { + if (ptr != NULL) { + MemBlock** b = &all_blocks; + // Inefficient search, but that's just for debugging. + while (*b != NULL && (*b)->ptr_ != ptr) b = &(*b)->next_; + if (*b == NULL) { + fprintf(stderr, "Invalid pointer free! (%p)\n", ptr); + abort(); + } + { + MemBlock* const block = *b; + *b = block->next_; + total_mem -= block->size_; +#if defined(PRINT_MEM_TRAFFIC) + fprintf(stderr, "Mem: %u (-%u)\n", + (uint32_t)total_mem, (uint32_t)block->size_); +#endif + free(block); + } + } +} + +#else +#define Increment(v) do {} while (0) +#define AddMem(p, s) do {} while (0) +#define SubMem(p) do {} while (0) +#endif + +// Returns 0 in case of overflow of nmemb * size. +static int CheckSizeArgumentsOverflow(uint64_t nmemb, size_t size) { + const uint64_t total_size = nmemb * size; + if (nmemb == 0) return 1; + if ((uint64_t)size > WEBP_MAX_ALLOCABLE_MEMORY / nmemb) return 0; + if (!CheckSizeOverflow(total_size)) return 0; +#if defined(PRINT_MEM_INFO) && defined(MALLOC_FAIL_AT) + if (countdown_to_fail > 0 && --countdown_to_fail == 0) { + return 0; // fake fail! + } +#endif +#if defined(PRINT_MEM_INFO) && defined(MALLOC_LIMIT) + if (mem_limit > 0) { + const uint64_t new_total_mem = (uint64_t)total_mem + total_size; + if (!CheckSizeOverflow(new_total_mem) || + new_total_mem > mem_limit) { + return 0; // fake fail! + } + } +#endif + + return 1; +} + +void* WebPSafeMalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_malloc_calls); + if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; + assert(nmemb * size > 0); + ptr = malloc((size_t)(nmemb * size)); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; +} + +void* WebPSafeCalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_calloc_calls); + if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; + assert(nmemb * size > 0); + ptr = calloc((size_t)nmemb, size); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; +} + +void WebPSafeFree(void* const ptr) { + if (ptr != NULL) { + Increment(&num_free_calls); + SubMem(ptr); + } + free(ptr); +} + +// Public API functions. + +void* WebPMalloc(size_t size) { + return WebPSafeMalloc(1, size); +} + +void WebPFree(void* ptr) { + WebPSafeFree(ptr); +} + +//------------------------------------------------------------------------------ + +void WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, int width, int height) { + assert(src != NULL && dst != NULL); + assert(abs(src_stride) >= width && abs(dst_stride) >= width); + while (height-- > 0) { + memcpy(dst, src, width); + src += src_stride; + dst += dst_stride; + } +} + +void WebPCopyPixels(const WebPPicture* const src, WebPPicture* const dst) { + assert(src != NULL && dst != NULL); + assert(src->width == dst->width && src->height == dst->height); + assert(src->use_argb && dst->use_argb); + WebPCopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb, + 4 * dst->argb_stride, 4 * src->width, src->height); +} + +//------------------------------------------------------------------------------ + +int WebPGetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { + return GetColorPalette(pic, palette); +} + +//------------------------------------------------------------------------------ + +#if defined(WEBP_NEED_LOG_TABLE_8BIT) +const uint8_t WebPLogTable8bit[256] = { // 31 ^ clz(i) + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; +#endif + +//------------------------------------------------------------------------------ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.h new file mode 100644 index 0000000000..b2241fbf9b --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/utils/utils.h @@ -0,0 +1,209 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Misc. common utility functions +// +// Authors: Skal (pascal.massimino@gmail.com) +// Urvang (urvang@google.com) + +#ifndef WEBP_UTILS_UTILS_H_ +#define WEBP_UTILS_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Memory allocation + +// This is the maximum memory amount that libwebp will ever try to allocate. +#ifndef WEBP_MAX_ALLOCABLE_MEMORY +#if SIZE_MAX > (1ULL << 34) +#define WEBP_MAX_ALLOCABLE_MEMORY (1ULL << 34) +#else +// For 32-bit targets keep this below INT_MAX to avoid valgrind warnings. +#define WEBP_MAX_ALLOCABLE_MEMORY ((1ULL << 31) - (1 << 16)) +#endif +#endif // WEBP_MAX_ALLOCABLE_MEMORY + +static WEBP_INLINE int CheckSizeOverflow(uint64_t size) { + return size == (size_t)size; +} + +// size-checking safe malloc/calloc: verify that the requested size is not too +// large, or return NULL. You don't need to call these for constructs like +// malloc(sizeof(foo)), but only if there's picture-dependent size involved +// somewhere (like: malloc(num_pixels * sizeof(*something))). That's why this +// safe malloc() borrows the signature from calloc(), pointing at the dangerous +// underlying multiply involved. +WEBP_EXTERN void* WebPSafeMalloc(uint64_t nmemb, size_t size); +// Note that WebPSafeCalloc() expects the second argument type to be 'size_t' +// in order to favor the "calloc(num_foo, sizeof(foo))" pattern. +WEBP_EXTERN void* WebPSafeCalloc(uint64_t nmemb, size_t size); + +// Companion deallocation function to the above allocations. +WEBP_EXTERN void WebPSafeFree(void* const ptr); + +//------------------------------------------------------------------------------ +// Alignment + +#define WEBP_ALIGN_CST 31 +#define WEBP_ALIGN(PTR) (((uintptr_t)(PTR) + WEBP_ALIGN_CST) & \ + ~(uintptr_t)WEBP_ALIGN_CST) + +#include +// memcpy() is the safe way of moving potentially unaligned 32b memory. +static WEBP_INLINE uint32_t WebPMemToUint32(const uint8_t* const ptr) { + uint32_t A; + memcpy(&A, ptr, sizeof(A)); + return A; +} + +static WEBP_INLINE int32_t WebPMemToInt32(const uint8_t* const ptr) { + return (int32_t)WebPMemToUint32(ptr); +} + +static WEBP_INLINE void WebPUint32ToMem(uint8_t* const ptr, uint32_t val) { + memcpy(ptr, &val, sizeof(val)); +} + +static WEBP_INLINE void WebPInt32ToMem(uint8_t* const ptr, int val) { + WebPUint32ToMem(ptr, (uint32_t)val); +} + +//------------------------------------------------------------------------------ +// Reading/writing data. + +// Read 16, 24 or 32 bits stored in little-endian order. +static WEBP_INLINE int GetLE16(const uint8_t* const data) { + return (int)(data[0] << 0) | (data[1] << 8); +} + +static WEBP_INLINE int GetLE24(const uint8_t* const data) { + return GetLE16(data) | (data[2] << 16); +} + +static WEBP_INLINE uint32_t GetLE32(const uint8_t* const data) { + return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16); +} + +// Store 16, 24 or 32 bits in little-endian order. +static WEBP_INLINE void PutLE16(uint8_t* const data, int val) { + assert(val < (1 << 16)); + data[0] = (val >> 0) & 0xff; + data[1] = (val >> 8) & 0xff; +} + +static WEBP_INLINE void PutLE24(uint8_t* const data, int val) { + assert(val < (1 << 24)); + PutLE16(data, val & 0xffff); + data[2] = (val >> 16) & 0xff; +} + +static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) { + PutLE16(data, (int)(val & 0xffff)); + PutLE16(data + 2, (int)(val >> 16)); +} + +// use GNU builtins where available. +#if defined(__GNUC__) && \ + ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4) +// Returns (int)floor(log2(n)). n must be > 0. +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + return 31 ^ __builtin_clz(n); +} +// counts the number of trailing zero +static WEBP_INLINE int BitsCtz(uint32_t n) { return __builtin_ctz(n); } +#elif defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) +#include +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + unsigned long first_set_bit; // NOLINT (runtime/int) + _BitScanReverse(&first_set_bit, n); + return first_set_bit; +} +static WEBP_INLINE int BitsCtz(uint32_t n) { + unsigned long first_set_bit; // NOLINT (runtime/int) + _BitScanForward(&first_set_bit, n); + return first_set_bit; +} +#else // default: use the (slow) C-version. +#define WEBP_HAVE_SLOW_CLZ_CTZ // signal that the Clz/Ctz function are slow +// Returns 31 ^ clz(n) = log2(n). This is the default C-implementation, either +// based on table or not. Can be used as fallback if clz() is not available. +#define WEBP_NEED_LOG_TABLE_8BIT +extern const uint8_t WebPLogTable8bit[256]; +static WEBP_INLINE int WebPLog2FloorC(uint32_t n) { + int log_value = 0; + while (n >= 256) { + log_value += 8; + n >>= 8; + } + return log_value + WebPLogTable8bit[n]; +} + +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { return WebPLog2FloorC(n); } + +static WEBP_INLINE int BitsCtz(uint32_t n) { + int i; + for (i = 0; i < 32; ++i, n >>= 1) { + if (n & 1) return i; + } + return 32; +} + +#endif + +//------------------------------------------------------------------------------ +// Pixel copying. + +struct WebPPicture; + +// Copy width x height pixels from 'src' to 'dst' honoring the strides. +WEBP_EXTERN void WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, + int width, int height); + +// Copy ARGB pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are +// assumed to be already allocated and using ARGB data. +WEBP_EXTERN void WebPCopyPixels(const struct WebPPicture* const src, + struct WebPPicture* const dst); + +//------------------------------------------------------------------------------ +// Unique colors. + +// Returns count of unique colors in 'pic', assuming pic->use_argb is true. +// If the unique color count is more than MAX_PALETTE_SIZE, returns +// MAX_PALETTE_SIZE+1. +// If 'palette' is not NULL and number of unique colors is less than or equal to +// MAX_PALETTE_SIZE, also outputs the actual unique colors into 'palette'. +// Note: 'palette' is assumed to be an array already allocated with at least +// MAX_PALETTE_SIZE elements. +// TODO(vrabaud) remove whenever we can break the ABI. +WEBP_EXTERN int WebPGetColorPalette(const struct WebPPicture* const pic, + uint32_t* const palette); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_UTILS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/config.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/config.h new file mode 100644 index 0000000000..e50c322a23 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/config.h @@ -0,0 +1,5 @@ +/* Stub config.h for SPM build — disables all SIMD dispatch paths. + WEBP_HAVE_NEON is intentionally NOT defined here: combined with + HAVE_CONFIG_H being set, cpu.h's auto-detection of __aarch64__ is + suppressed and no NEON function pointers are registered. + Similarly we do not define WEBP_HAVE_SSE2 / WEBP_HAVE_SSE41. */ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/decode.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/decode.h new file mode 100644 index 0000000000..d6895f5c55 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/decode.h @@ -0,0 +1,506 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Main decoding functions for WebP images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_DECODE_H_ +#define WEBP_WEBP_DECODE_H_ + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_DECODER_ABI_VERSION 0x0209 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum VP8StatusCode VP8StatusCode; +// typedef enum WEBP_CSP_MODE WEBP_CSP_MODE; +typedef struct WebPRGBABuffer WebPRGBABuffer; +typedef struct WebPYUVABuffer WebPYUVABuffer; +typedef struct WebPDecBuffer WebPDecBuffer; +typedef struct WebPIDecoder WebPIDecoder; +typedef struct WebPBitstreamFeatures WebPBitstreamFeatures; +typedef struct WebPDecoderOptions WebPDecoderOptions; +typedef struct WebPDecoderConfig WebPDecoderConfig; + +// Return the decoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetDecoderVersion(void); + +// Retrieve basic header information: width, height. +// This function will also validate the header, returning true on success, +// false otherwise. '*width' and '*height' are only valid on successful return. +// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant. +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +WEBP_NODISCARD WEBP_EXTERN int WebPGetInfo( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Decodes WebP images pointed to by 'data' and returns RGBA samples, along +// with the dimensions in *width and *height. The ordering of samples in +// memory is R, G, B, A, R, G, B, A... in scan order (endian-independent). +// The returned pointer should be deleted calling WebPFree(). +// Returns NULL in case of error. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBA( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning A, R, G, B, A, R, G, B... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGB( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning B, G, R, A, B, G, R, A... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRA( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning R, G, B, R, G, B... ordered data. +// If the bitstream contains transparency, it is ignored. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGB( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGB, but returning B, G, R, B, G, R... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGR( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Decode WebP images pointed to by 'data' to Y'UV format(*). The pointer +// returned is the Y samples buffer. Upon return, *u and *v will point to +// the U and V chroma data. These U and V buffers need NOT be passed to +// WebPFree(), unlike the returned Y luma one. The dimension of the U and V +// planes are both (*width + 1) / 2 and (*height + 1) / 2. +// Upon return, the Y buffer has a stride returned as '*stride', while U and V +// have a common stride returned as '*uv_stride'. +// 'width' and 'height' may be NULL, the other pointers must not be. +// Returns NULL in case of error. +// (*) Also named Y'CbCr. See: https://en.wikipedia.org/wiki/YCbCr +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUV( + const uint8_t* data, size_t data_size, int* width, int* height, + uint8_t** u, uint8_t** v, int* stride, int* uv_stride); + +// These five functions are variants of the above ones, that decode the image +// directly into a pre-allocated buffer 'output_buffer'. The maximum storage +// available in this buffer is indicated by 'output_buffer_size'. If this +// storage is not sufficient (or an error occurred), NULL is returned. +// Otherwise, output_buffer is returned, for convenience. +// The parameter 'output_stride' specifies the distance (in bytes) +// between scanlines. Hence, output_buffer_size is expected to be at least +// output_stride x picture-height. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// RGB and BGR variants. Here too the transparency information, if present, +// will be dropped and ignored. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// WebPDecodeYUVInto() is a variant of WebPDecodeYUV() that operates directly +// into pre-allocated luma/chroma plane buffers. This function requires the +// strides to be passed: one for the luma plane and one for each of the +// chroma ones. The size of each plane buffer is passed as 'luma_size', +// 'u_size' and 'v_size' respectively. +// Pointer to the luma plane ('*luma') is returned or NULL if an error occurred +// during decoding (or because some buffers were found to be too small). +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUVInto( + const uint8_t* data, size_t data_size, + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +//------------------------------------------------------------------------------ +// Output colorspaces and buffer + +// Colorspaces +// Note: the naming describes the byte-ordering of packed samples in memory. +// For instance, MODE_BGRA relates to samples ordered as B,G,R,A,B,G,R,A,... +// Non-capital names (e.g.:MODE_Argb) relates to pre-multiplied RGB channels. +// RGBA-4444 and RGB-565 colorspaces are represented by following byte-order: +// RGBA-4444: [r3 r2 r1 r0 g3 g2 g1 g0], [b3 b2 b1 b0 a3 a2 a1 a0], ... +// RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ... +// In the case WEBP_SWAP_16BITS_CSP is defined, the bytes are swapped for +// these two modes: +// RGBA-4444: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], ... +// RGB-565: [g2 g1 g0 b4 b3 b2 b1 b0], [r4 r3 r2 r1 r0 g5 g4 g3], ... + +typedef enum WEBP_CSP_MODE { + MODE_RGB = 0, MODE_RGBA = 1, + MODE_BGR = 2, MODE_BGRA = 3, + MODE_ARGB = 4, MODE_RGBA_4444 = 5, + MODE_RGB_565 = 6, + // RGB-premultiplied transparent modes (alpha value is preserved) + MODE_rgbA = 7, + MODE_bgrA = 8, + MODE_Argb = 9, + MODE_rgbA_4444 = 10, + // YUV modes must come after RGB ones. + MODE_YUV = 11, MODE_YUVA = 12, // yuv 4:2:0 + MODE_LAST = 13 +} WEBP_CSP_MODE; + +// Some useful macros: +static WEBP_INLINE int WebPIsPremultipliedMode(WEBP_CSP_MODE mode) { + return (mode == MODE_rgbA || mode == MODE_bgrA || mode == MODE_Argb || + mode == MODE_rgbA_4444); +} + +static WEBP_INLINE int WebPIsAlphaMode(WEBP_CSP_MODE mode) { + return (mode == MODE_RGBA || mode == MODE_BGRA || mode == MODE_ARGB || + mode == MODE_RGBA_4444 || mode == MODE_YUVA || + WebPIsPremultipliedMode(mode)); +} + +static WEBP_INLINE int WebPIsRGBMode(WEBP_CSP_MODE mode) { + return (mode < MODE_YUV); +} + +//------------------------------------------------------------------------------ +// WebPDecBuffer: Generic structure for describing the output sample buffer. + +struct WebPRGBABuffer { // view as RGBA + uint8_t* rgba; // pointer to RGBA samples + int stride; // stride in bytes from one scanline to the next. + size_t size; // total size of the *rgba buffer. +}; + +struct WebPYUVABuffer { // view as YUVA + uint8_t* y, *u, *v, *a; // pointer to luma, chroma U/V, alpha samples + int y_stride; // luma stride + int u_stride, v_stride; // chroma strides + int a_stride; // alpha stride + size_t y_size; // luma plane size + size_t u_size, v_size; // chroma planes size + size_t a_size; // alpha-plane size +}; + +// Output buffer +struct WebPDecBuffer { + WEBP_CSP_MODE colorspace; // Colorspace. + int width, height; // Dimensions. + int is_external_memory; // If non-zero, 'internal_memory' pointer is not + // used. If value is '2' or more, the external + // memory is considered 'slow' and multiple + // read/write will be avoided. + union { + WebPRGBABuffer RGBA; + WebPYUVABuffer YUVA; + } u; // Nameless union of buffer parameters. + uint32_t pad[4]; // padding for later use + + uint8_t* private_memory; // Internally allocated memory (only when + // is_external_memory is 0). Should not be used + // externally, but accessed via the buffer union. +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPInitDecBufferInternal(WebPDecBuffer*, int); + +// Initialize the structure as empty. Must be called before any other use. +// Returns false in case of version mismatch +WEBP_NODISCARD static WEBP_INLINE int WebPInitDecBuffer(WebPDecBuffer* buffer) { + return WebPInitDecBufferInternal(buffer, WEBP_DECODER_ABI_VERSION); +} + +// Free any memory associated with the buffer. Must always be called last. +// Note: doesn't free the 'buffer' structure itself. +WEBP_EXTERN void WebPFreeDecBuffer(WebPDecBuffer* buffer); + +//------------------------------------------------------------------------------ +// Enumeration of the status codes + +typedef enum WEBP_NODISCARD VP8StatusCode { + VP8_STATUS_OK = 0, + VP8_STATUS_OUT_OF_MEMORY, + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA +} VP8StatusCode; + +//------------------------------------------------------------------------------ +// Incremental decoding +// +// This API allows streamlined decoding of partial data. +// Picture can be incrementally decoded as data become available thanks to the +// WebPIDecoder object. This object can be left in a SUSPENDED state if the +// picture is only partially decoded, pending additional input. +// Code example: +/* + WebPInitDecBuffer(&output_buffer); + output_buffer.colorspace = mode; + ... + WebPIDecoder* idec = WebPINewDecoder(&output_buffer); + while (additional_data_is_available) { + // ... (get additional data in some new_data[] buffer) + status = WebPIAppend(idec, new_data, new_data_size); + if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) { + break; // an error occurred. + } + + // The above call decodes the current available buffer. + // Part of the image can now be refreshed by calling + // WebPIDecGetRGB()/WebPIDecGetYUVA() etc. + } + WebPIDelete(idec); +*/ + +// Creates a new incremental decoder with the supplied buffer parameter. +// This output_buffer can be passed NULL, in which case a default output buffer +// is used (with MODE_RGB). Otherwise, an internal reference to 'output_buffer' +// is kept, which means that the lifespan of 'output_buffer' must be larger than +// that of the returned WebPIDecoder object. +// The supplied 'output_buffer' content MUST NOT be changed between calls to +// WebPIAppend() or WebPIUpdate() unless 'output_buffer.is_external_memory' is +// not set to 0. In such a case, it is allowed to modify the pointers, size and +// stride of output_buffer.u.RGBA or output_buffer.u.YUVA, provided they remain +// within valid bounds. +// All other fields of WebPDecBuffer MUST remain constant between calls. +// Returns NULL if the allocation failed. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewDecoder( + WebPDecBuffer* output_buffer); + +// This function allocates and initializes an incremental-decoder object, which +// will output the RGB/A samples specified by 'csp' into a preallocated +// buffer 'output_buffer'. The size of this buffer is at least +// 'output_buffer_size' and the stride (distance in bytes between two scanlines) +// is specified by 'output_stride'. +// Additionally, output_buffer can be passed NULL in which case the output +// buffer will be allocated automatically when the decoding starts. The +// colorspace 'csp' is taken into account for allocating this buffer. All other +// parameters are ignored. +// Returns NULL if the allocation failed, or if some parameters are invalid. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewRGB( + WEBP_CSP_MODE csp, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// This function allocates and initializes an incremental-decoder object, which +// will output the raw luma/chroma samples into a preallocated planes if +// supplied. The luma plane is specified by its pointer 'luma', its size +// 'luma_size' and its stride 'luma_stride'. Similarly, the chroma-u plane +// is specified by the 'u', 'u_size' and 'u_stride' parameters, and the chroma-v +// plane by 'v' and 'v_size'. And same for the alpha-plane. The 'a' pointer +// can be pass NULL in case one is not interested in the transparency plane. +// Conversely, 'luma' can be passed NULL if no preallocated planes are supplied. +// In this case, the output buffer will be automatically allocated (using +// MODE_YUVA) when decoding starts. All parameters are then ignored. +// Returns NULL if the allocation failed or if a parameter is invalid. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUVA( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride); + +// Deprecated version of the above, without the alpha plane. +// Kept for backward compatibility. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUV( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +// Deletes the WebPIDecoder object and associated memory. Must always be called +// if WebPINewDecoder, WebPINewRGB or WebPINewYUV succeeded. +WEBP_EXTERN void WebPIDelete(WebPIDecoder* idec); + +// Copies and decodes the next available data. Returns VP8_STATUS_OK when +// the image is successfully decoded. Returns VP8_STATUS_SUSPENDED when more +// data is expected. Returns error in other cases. +WEBP_EXTERN VP8StatusCode WebPIAppend( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// A variant of the above function to be used when data buffer contains +// partial data from the beginning. In this case data buffer is not copied +// to the internal memory. +// Note that the value of the 'data' pointer can change between calls to +// WebPIUpdate, for instance when the data buffer is resized to fit larger data. +WEBP_EXTERN VP8StatusCode WebPIUpdate( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// Returns the RGB/A image decoded so far. Returns NULL if output params +// are not initialized yet. The RGB/A output type corresponds to the colorspace +// specified during call to WebPINewDecoder() or WebPINewRGB(). +// *last_y is the index of last decoded row in raster scan order. Some pointers +// (*last_y, *width etc.) can be NULL if corresponding information is not +// needed. The values in these pointers are only valid on successful (non-NULL) +// return. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetRGB( + const WebPIDecoder* idec, int* last_y, + int* width, int* height, int* stride); + +// Same as above function to get a YUVA image. Returns pointer to the luma +// plane or NULL in case of error. If there is no alpha information +// the alpha pointer '*a' will be returned NULL. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetYUVA( + const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, int* uv_stride, int* a_stride); + +// Deprecated alpha-less version of WebPIDecGetYUVA(): it will ignore the +// alpha information (if present). Kept for backward compatibility. +WEBP_NODISCARD static WEBP_INLINE uint8_t* WebPIDecGetYUV( + const WebPIDecoder* idec, int* last_y, uint8_t** u, uint8_t** v, + int* width, int* height, int* stride, int* uv_stride) { + return WebPIDecGetYUVA(idec, last_y, u, v, NULL, width, height, + stride, uv_stride, NULL); +} + +// Generic call to retrieve information about the displayable area. +// If non NULL, the left/right/width/height pointers are filled with the visible +// rectangular area so far. +// Returns NULL in case the incremental decoder object is in an invalid state. +// Otherwise returns the pointer to the internal representation. This structure +// is read-only, tied to WebPIDecoder's lifespan and should not be modified. +WEBP_NODISCARD WEBP_EXTERN const WebPDecBuffer* WebPIDecodedArea( + const WebPIDecoder* idec, int* left, int* top, int* width, int* height); + +//------------------------------------------------------------------------------ +// Advanced decoding parametrization +// +// Code sample for using the advanced decoding API +/* + // A) Init a configuration object + WebPDecoderConfig config; + CHECK(WebPInitDecoderConfig(&config)); + + // B) optional: retrieve the bitstream's features. + CHECK(WebPGetFeatures(data, data_size, &config.input) == VP8_STATUS_OK); + + // C) Adjust 'config', if needed + config.options.no_fancy_upsampling = 1; + config.output.colorspace = MODE_BGRA; + // etc. + + // Note that you can also make config.output point to an externally + // supplied memory buffer, provided it's big enough to store the decoded + // picture. Otherwise, config.output will just be used to allocate memory + // and store the decoded picture. + + // D) Decode! + CHECK(WebPDecode(data, data_size, &config) == VP8_STATUS_OK); + + // E) Decoded image is now in config.output (and config.output.u.RGBA) + + // F) Reclaim memory allocated in config's object. It's safe to call + // this function even if the memory is external and wasn't allocated + // by WebPDecode(). + WebPFreeDecBuffer(&config.output); +*/ + +// Features gathered from the bitstream +struct WebPBitstreamFeatures { + int width; // Width in pixels, as read from the bitstream. + int height; // Height in pixels, as read from the bitstream. + int has_alpha; // True if the bitstream contains an alpha channel. + int has_animation; // True if the bitstream is an animation. + int format; // 0 = undefined (/mixed), 1 = lossy, 2 = lossless + + uint32_t pad[5]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_EXTERN VP8StatusCode WebPGetFeaturesInternal( + const uint8_t*, size_t, WebPBitstreamFeatures*, int); + +// Retrieve features from the bitstream. The *features structure is filled +// with information gathered from the bitstream. +// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns +// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the +// features from headers. Returns error in other cases. +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +static WEBP_INLINE VP8StatusCode WebPGetFeatures( + const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features) { + return WebPGetFeaturesInternal(data, data_size, features, + WEBP_DECODER_ABI_VERSION); +} + +// Decoding options +struct WebPDecoderOptions { + int bypass_filtering; // if true, skip the in-loop filtering + int no_fancy_upsampling; // if true, use faster pointwise upsampler + int use_cropping; // if true, cropping is applied _first_ + int crop_left, crop_top; // top-left position for cropping. + // Will be snapped to even values. + int crop_width, crop_height; // dimension of the cropping area + int use_scaling; // if true, scaling is applied _afterward_ + int scaled_width, scaled_height; // final resolution + int use_threads; // if true, use multi-threaded decoding + int dithering_strength; // dithering strength (0=Off, 100=full) + int flip; // if true, flip output vertically + int alpha_dithering_strength; // alpha dithering strength in [0..100] + + uint32_t pad[5]; // padding for later use +}; + +// Main object storing the configuration for advanced decoding. +struct WebPDecoderConfig { + WebPBitstreamFeatures input; // Immutable bitstream features (optional) + WebPDecBuffer output; // Output buffer (can point to external mem) + WebPDecoderOptions options; // Decoding options +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPInitDecoderConfigInternal(WebPDecoderConfig*, + int); + +// Initialize the configuration as empty. This function must always be +// called first, unless WebPGetFeatures() is to be called. +// Returns false in case of mismatched version. +WEBP_NODISCARD static WEBP_INLINE int WebPInitDecoderConfig( + WebPDecoderConfig* config) { + return WebPInitDecoderConfigInternal(config, WEBP_DECODER_ABI_VERSION); +} + +// Instantiate a new incremental decoder object with the requested +// configuration. The bitstream can be passed using 'data' and 'data_size' +// parameter, in which case the features will be parsed and stored into +// config->input. Otherwise, 'data' can be NULL and no parsing will occur. +// Note that 'config' can be NULL too, in which case a default configuration +// is used. If 'config' is not NULL, it must outlive the WebPIDecoder object +// as some references to its fields will be used. No internal copy of 'config' +// is made. +// The return WebPIDecoder object must always be deleted calling WebPIDelete(). +// Returns NULL in case of error (and config->status will then reflect +// the error condition, if available). +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPIDecode( + const uint8_t* data, size_t data_size, WebPDecoderConfig* config); + +// Non-incremental version. This version decodes the full data at once, taking +// 'config' into account. Returns decoding status (which should be VP8_STATUS_OK +// if the decoding was successful). Note that 'config' cannot be NULL. +WEBP_EXTERN VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_DECODE_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/demux.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/demux.h new file mode 100644 index 0000000000..8d246550ca --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/demux.h @@ -0,0 +1,367 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Demux API. +// Enables extraction of image and extended format data from WebP files. + +// Code Example: Demuxing WebP data to extract all the frames, ICC profile +// and EXIF/XMP metadata. +/* + WebPDemuxer* demux = WebPDemux(&webp_data); + + uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + // ... (Get information about the features present in the WebP file). + uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); + + // ... (Iterate over all frames). + WebPIterator iter; + if (WebPDemuxGetFrame(demux, 1, &iter)) { + do { + // ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(), + // ... and get other frame properties like width, height, offsets etc. + // ... see 'struct WebPIterator' below for more info). + } while (WebPDemuxNextFrame(&iter)); + WebPDemuxReleaseIterator(&iter); + } + + // ... (Extract metadata). + WebPChunkIterator chunk_iter; + if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter); + // ... (Consume the ICC profile in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter); + // ... (Consume the EXIF metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter); + // ... (Consume the XMP metadata in 'chunk_iter.chunk'). + WebPDemuxReleaseChunkIterator(&chunk_iter); + WebPDemuxDelete(demux); +*/ + +#ifndef WEBP_WEBP_DEMUX_H_ +#define WEBP_WEBP_DEMUX_H_ + +#include "./decode.h" // for WEBP_CSP_MODE +#include "./mux_types.h" +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_DEMUX_ABI_VERSION 0x0107 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPDemuxState WebPDemuxState; +// typedef enum WebPFormatFeature WebPFormatFeature; +typedef struct WebPDemuxer WebPDemuxer; +typedef struct WebPIterator WebPIterator; +typedef struct WebPChunkIterator WebPChunkIterator; +typedef struct WebPAnimInfo WebPAnimInfo; +typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions; + +//------------------------------------------------------------------------------ + +// Returns the version number of the demux library, packed in hexadecimal using +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetDemuxVersion(void); + +//------------------------------------------------------------------------------ +// Life of a Demux object + +typedef enum WebPDemuxState { + WEBP_DEMUX_PARSE_ERROR = -1, // An error occurred while parsing. + WEBP_DEMUX_PARSING_HEADER = 0, // Not enough data to parse full header. + WEBP_DEMUX_PARSED_HEADER = 1, // Header parsing complete, + // data may be available. + WEBP_DEMUX_DONE = 2 // Entire file has been parsed. +} WebPDemuxState; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN WebPDemuxer* WebPDemuxInternal( + const WebPData*, int, WebPDemuxState*, int); + +// Parses the full WebP file given by 'data'. For single images the WebP file +// header alone or the file header and the chunk header may be absent. +// Returns a WebPDemuxer object on successful parse, NULL otherwise. +WEBP_NODISCARD static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) { + return WebPDemuxInternal(data, 0, NULL, WEBP_DEMUX_ABI_VERSION); +} + +// Parses the possibly incomplete WebP file given by 'data'. +// If 'state' is non-NULL it will be set to indicate the status of the demuxer. +// Returns NULL in case of error or if there isn't enough data to start parsing; +// and a WebPDemuxer object on successful parse. +// Note that WebPDemuxer keeps internal pointers to 'data' memory segment. +// If this data is volatile, the demuxer object should be deleted (by calling +// WebPDemuxDelete()) and WebPDemuxPartial() called again on the new data. +// This is usually an inexpensive operation. +WEBP_NODISCARD static WEBP_INLINE WebPDemuxer* WebPDemuxPartial( + const WebPData* data, WebPDemuxState* state) { + return WebPDemuxInternal(data, 1, state, WEBP_DEMUX_ABI_VERSION); +} + +// Frees memory associated with 'dmux'. +WEBP_EXTERN void WebPDemuxDelete(WebPDemuxer* dmux); + +//------------------------------------------------------------------------------ +// Data/information extraction. + +typedef enum WebPFormatFeature { + WEBP_FF_FORMAT_FLAGS, // bit-wise combination of WebPFeatureFlags + // corresponding to the 'VP8X' chunk (if present). + WEBP_FF_CANVAS_WIDTH, + WEBP_FF_CANVAS_HEIGHT, + WEBP_FF_LOOP_COUNT, // only relevant for animated file + WEBP_FF_BACKGROUND_COLOR, // idem. + WEBP_FF_FRAME_COUNT // Number of frames present in the demux object. + // In case of a partial demux, this is the number + // of frames seen so far, with the last frame + // possibly being partial. +} WebPFormatFeature; + +// Get the 'feature' value from the 'dmux'. +// NOTE: values are only valid if WebPDemux() was used or WebPDemuxPartial() +// returned a state > WEBP_DEMUX_PARSING_HEADER. +// If 'feature' is WEBP_FF_FORMAT_FLAGS, the returned value is a bit-wise +// combination of WebPFeatureFlags values. +// If 'feature' is WEBP_FF_LOOP_COUNT, WEBP_FF_BACKGROUND_COLOR, the returned +// value is only meaningful if the bitstream is animated. +WEBP_EXTERN uint32_t WebPDemuxGetI( + const WebPDemuxer* dmux, WebPFormatFeature feature); + +//------------------------------------------------------------------------------ +// Frame iteration. + +struct WebPIterator { + int frame_num; + int num_frames; // equivalent to WEBP_FF_FRAME_COUNT. + int x_offset, y_offset; // offset relative to the canvas. + int width, height; // dimensions of this frame. + int duration; // display duration in milliseconds. + WebPMuxAnimDispose dispose_method; // dispose method for the frame. + int complete; // true if 'fragment' contains a full frame. partial images + // may still be decoded with the WebP incremental decoder. + WebPData fragment; // The frame given by 'frame_num'. Note for historical + // reasons this is called a fragment. + int has_alpha; // True if the frame contains transparency. + WebPMuxAnimBlend blend_method; // Blend operation for the frame. + + uint32_t pad[2]; // padding for later use. + void* private_; // for internal use only. +}; + +// Retrieves frame 'frame_number' from 'dmux'. +// 'iter->fragment' points to the frame on return from this function. +// Setting 'frame_number' equal to 0 will return the last frame of the image. +// Returns false if 'dmux' is NULL or frame 'frame_number' is not present. +// Call WebPDemuxReleaseIterator() when use of the iterator is complete. +// NOTE: 'dmux' must persist for the lifetime of 'iter'. +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxGetFrame( + const WebPDemuxer* dmux, int frame_number, WebPIterator* iter); + +// Sets 'iter->fragment' to point to the next ('iter->frame_num' + 1) or +// previous ('iter->frame_num' - 1) frame. These functions do not loop. +// Returns true on success, false otherwise. +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxNextFrame(WebPIterator* iter); +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxPrevFrame(WebPIterator* iter); + +// Releases any memory associated with 'iter'. +// Must be called before any subsequent calls to WebPDemuxGetChunk() on the same +// iter. Also, must be called before destroying the associated WebPDemuxer with +// WebPDemuxDelete(). +WEBP_EXTERN void WebPDemuxReleaseIterator(WebPIterator* iter); + +//------------------------------------------------------------------------------ +// Chunk iteration. + +struct WebPChunkIterator { + // The current and total number of chunks with the fourcc given to + // WebPDemuxGetChunk(). + int chunk_num; + int num_chunks; + WebPData chunk; // The payload of the chunk. + + uint32_t pad[6]; // padding for later use + void* private_; +}; + +// Retrieves the 'chunk_number' instance of the chunk with id 'fourcc' from +// 'dmux'. +// 'fourcc' is a character array containing the fourcc of the chunk to return, +// e.g., "ICCP", "XMP ", "EXIF", etc. +// Setting 'chunk_number' equal to 0 will return the last chunk in a set. +// Returns true if the chunk is found, false otherwise. Image related chunk +// payloads are accessed through WebPDemuxGetFrame() and related functions. +// Call WebPDemuxReleaseChunkIterator() when use of the iterator is complete. +// NOTE: 'dmux' must persist for the lifetime of the iterator. +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxGetChunk(const WebPDemuxer* dmux, + const char fourcc[4], + int chunk_number, + WebPChunkIterator* iter); + +// Sets 'iter->chunk' to point to the next ('iter->chunk_num' + 1) or previous +// ('iter->chunk_num' - 1) chunk. These functions do not loop. +// Returns true on success, false otherwise. +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxNextChunk(WebPChunkIterator* iter); +WEBP_NODISCARD WEBP_EXTERN int WebPDemuxPrevChunk(WebPChunkIterator* iter); + +// Releases any memory associated with 'iter'. +// Must be called before destroying the associated WebPDemuxer with +// WebPDemuxDelete(). +WEBP_EXTERN void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); + +//------------------------------------------------------------------------------ +// WebPAnimDecoder API +// +// This API allows decoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimDecoderOptions dec_options; + WebPAnimDecoderOptionsInit(&dec_options); + // Tune 'dec_options' as needed. + WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options); + WebPAnimInfo anim_info; + WebPAnimDecoderGetInfo(dec, &anim_info); + for (uint32_t i = 0; i < anim_info.loop_count; ++i) { + while (WebPAnimDecoderHasMoreFrames(dec)) { + uint8_t* buf; + int timestamp; + WebPAnimDecoderGetNext(dec, &buf, ×tamp); + // ... (Render 'buf' based on 'timestamp'). + // ... (Do NOT free 'buf', as it is owned by 'dec'). + } + WebPAnimDecoderReset(dec); + } + const WebPDemuxer* demuxer = WebPAnimDecoderGetDemuxer(dec); + // ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data). + WebPAnimDecoderDelete(dec); +*/ + +typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. + +// Global options. +struct WebPAnimDecoderOptions { + // Output colorspace. Only the following modes are supported: + // MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA. + WEBP_CSP_MODE color_mode; + int use_threads; // If true, use multi-threaded decoding. + uint32_t padding[7]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderOptionsInitInternal( + WebPAnimDecoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimDecoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimDecoderOptionsInit() must have succeeded before using the +// 'dec_options' object. +WEBP_NODISCARD static WEBP_INLINE int WebPAnimDecoderOptionsInit( + WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderOptionsInitInternal(dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_NODISCARD WEBP_EXTERN WebPAnimDecoder* WebPAnimDecoderNewInternal( + const WebPData*, const WebPAnimDecoderOptions*, int); + +// Creates and initializes a WebPAnimDecoder object. +// Parameters: +// webp_data - (in) WebP bitstream. This should remain unchanged during the +// lifetime of the output WebPAnimDecoder object. +// dec_options - (in) decoding options. Can be passed NULL to choose +// reasonable defaults (in particular, color mode MODE_RGBA +// will be picked). +// Returns: +// A pointer to the newly created WebPAnimDecoder object, or NULL in case of +// parsing error, invalid option or memory error. +WEBP_NODISCARD static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( + const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) { + return WebPAnimDecoderNewInternal(webp_data, dec_options, + WEBP_DEMUX_ABI_VERSION); +} + +// Global information about the animation.. +struct WebPAnimInfo { + uint32_t canvas_width; + uint32_t canvas_height; + uint32_t loop_count; + uint32_t bgcolor; + uint32_t frame_count; + uint32_t pad[4]; // padding for later use +}; + +// Get global information about the animation. +// Parameters: +// dec - (in) decoder instance to get information from. +// info - (out) global information fetched from the animation. +// Returns: +// True on success. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderGetInfo( + const WebPAnimDecoder* dec, WebPAnimInfo* info); + +// Fetch the next frame from 'dec' based on options supplied to +// WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size +// 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The +// returned buffer 'buf' is valid only until the next call to +// WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). +// Parameters: +// dec - (in/out) decoder instance from which the next frame is to be fetched. +// buf - (out) decoded frame. +// timestamp - (out) timestamp of the frame in milliseconds. +// Returns: +// False if any of the arguments are NULL, or if there is a parsing or +// decoding error, or if there are no more frames. Otherwise, returns true. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderGetNext(WebPAnimDecoder* dec, + uint8_t** buf, + int* timestamp); + +// Check if there are more frames left to decode. +// Parameters: +// dec - (in) decoder instance to be checked. +// Returns: +// True if 'dec' is not NULL and some frames are yet to be decoded. +// Otherwise, returns false. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimDecoderHasMoreFrames( + const WebPAnimDecoder* dec); + +// Resets the WebPAnimDecoder object, so that next call to +// WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be +// helpful when all frames need to be decoded multiple times (e.g. +// info.loop_count times) without destroying and recreating the 'dec' object. +// Parameters: +// dec - (in/out) decoder instance to be reset +WEBP_EXTERN void WebPAnimDecoderReset(WebPAnimDecoder* dec); + +// Grab the internal demuxer object. +// Getting the demuxer object can be useful if one wants to use operations only +// available through demuxer; e.g. to get XMP/EXIF/ICC metadata. The returned +// demuxer object is owned by 'dec' and is valid only until the next call to +// WebPAnimDecoderDelete(). +// +// Parameters: +// dec - (in) decoder instance from which the demuxer object is to be fetched. +WEBP_NODISCARD WEBP_EXTERN const WebPDemuxer* WebPAnimDecoderGetDemuxer( + const WebPAnimDecoder* dec); + +// Deletes the WebPAnimDecoder object. +// Parameters: +// dec - (in/out) decoder instance to be deleted +WEBP_EXTERN void WebPAnimDecoderDelete(WebPAnimDecoder* dec); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_DEMUX_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/encode.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/encode.h new file mode 100644 index 0000000000..f3d59297c8 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/encode.h @@ -0,0 +1,557 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebP encoder: main interface +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_ENCODE_H_ +#define WEBP_WEBP_ENCODE_H_ + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_ENCODER_ABI_VERSION 0x020f // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPImageHint WebPImageHint; +// typedef enum WebPEncCSP WebPEncCSP; +// typedef enum WebPPreset WebPPreset; +// typedef enum WebPEncodingError WebPEncodingError; +typedef struct WebPConfig WebPConfig; +typedef struct WebPPicture WebPPicture; // main structure for I/O +typedef struct WebPAuxStats WebPAuxStats; +typedef struct WebPMemoryWriter WebPMemoryWriter; + +// Return the encoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetEncoderVersion(void); + +//------------------------------------------------------------------------------ +// One-stop-shop call! No questions asked: + +// Returns the size of the compressed data (pointed to by *output), or 0 if +// an error occurred. The compressed data must be released by the caller +// using the call 'WebPFree(*output)'. +// These functions compress using the lossy format, and the quality_factor +// can go from 0 (smaller output, lower quality) to 100 (best quality, +// larger output). +WEBP_EXTERN size_t WebPEncodeRGB(const uint8_t* rgb, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeBGR(const uint8_t* bgr, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeRGBA(const uint8_t* rgba, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeBGRA(const uint8_t* bgra, + int width, int height, int stride, + float quality_factor, uint8_t** output); + +// These functions are the equivalent of the above, but compressing in a +// lossless manner. Files are usually larger than lossy format, but will +// not suffer any compression loss. +// Note these functions, like the lossy versions, use the library's default +// settings. For lossless this means 'exact' is disabled. RGB values in +// transparent areas will be modified to improve compression. To avoid this, +// use WebPEncode() and set WebPConfig::exact to 1. +WEBP_EXTERN size_t WebPEncodeLosslessRGB(const uint8_t* rgb, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessBGR(const uint8_t* bgr, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessRGBA(const uint8_t* rgba, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessBGRA(const uint8_t* bgra, + int width, int height, int stride, + uint8_t** output); + +//------------------------------------------------------------------------------ +// Coding parameters + +// Image characteristics hint for the underlying encoder. +typedef enum WebPImageHint { + WEBP_HINT_DEFAULT = 0, // default preset. + WEBP_HINT_PICTURE, // digital picture, like portrait, inner shot + WEBP_HINT_PHOTO, // outdoor photograph, with natural lighting + WEBP_HINT_GRAPH, // Discrete tone image (graph, map-tile etc). + WEBP_HINT_LAST +} WebPImageHint; + +// Compression parameters. +struct WebPConfig { + int lossless; // Lossless encoding (0=lossy(default), 1=lossless). + float quality; // between 0 and 100. For lossy, 0 gives the smallest + // size and 100 the largest. For lossless, this + // parameter is the amount of effort put into the + // compression: 0 is the fastest but gives larger + // files compared to the slowest, but best, 100. + int method; // quality/speed trade-off (0=fast, 6=slower-better) + + WebPImageHint image_hint; // Hint for image type (lossless only for now). + + int target_size; // if non-zero, set the desired target size in bytes. + // Takes precedence over the 'compression' parameter. + float target_PSNR; // if non-zero, specifies the minimal distortion to + // try to achieve. Takes precedence over target_size. + int segments; // maximum number of segments to use, in [1..4] + int sns_strength; // Spatial Noise Shaping. 0=off, 100=maximum. + int filter_strength; // range: [0 = off .. 100 = strongest] + int filter_sharpness; // range: [0 = off .. 7 = least sharp] + int filter_type; // filtering type: 0 = simple, 1 = strong (only used + // if filter_strength > 0 or autofilter > 0) + int autofilter; // Auto adjust filter's strength [0 = off, 1 = on] + int alpha_compression; // Algorithm for encoding the alpha plane (0 = none, + // 1 = compressed with WebP lossless). Default is 1. + int alpha_filtering; // Predictive filtering method for alpha plane. + // 0: none, 1: fast, 2: best. Default if 1. + int alpha_quality; // Between 0 (smallest size) and 100 (lossless). + // Default is 100. + int pass; // number of entropy-analysis passes (in [1..10]). + + int show_compressed; // if true, export the compressed picture back. + // In-loop filtering is not applied. + int preprocessing; // preprocessing filter: + // 0=none, 1=segment-smooth, 2=pseudo-random dithering + int partitions; // log2(number of token partitions) in [0..3]. Default + // is set to 0 for easier progressive decoding. + int partition_limit; // quality degradation allowed to fit the 512k limit + // on prediction modes coding (0: no degradation, + // 100: maximum possible degradation). + int emulate_jpeg_size; // If true, compression parameters will be remapped + // to better match the expected output size from + // JPEG compression. Generally, the output size will + // be similar but the degradation will be lower. + int thread_level; // If non-zero, try and use multi-threaded encoding. + int low_memory; // If set, reduce memory usage (but increase CPU use). + + int near_lossless; // Near lossless encoding [0 = max loss .. 100 = off + // (default)]. + int exact; // if non-zero, preserve the exact RGB values under + // transparent area. Otherwise, discard this invisible + // RGB information for better compression. The default + // value is 0. + + int use_delta_palette; // reserved for future lossless feature + int use_sharp_yuv; // if needed, use sharp (and slow) RGB->YUV conversion + + int qmin; // minimum permissible quality factor + int qmax; // maximum permissible quality factor +}; + +// Enumerate some predefined settings for WebPConfig, depending on the type +// of source picture. These presets are used when calling WebPConfigPreset(). +typedef enum WebPPreset { + WEBP_PRESET_DEFAULT = 0, // default preset. + WEBP_PRESET_PICTURE, // digital picture, like portrait, inner shot + WEBP_PRESET_PHOTO, // outdoor photograph, with natural lighting + WEBP_PRESET_DRAWING, // hand or line drawing, with high-contrast details + WEBP_PRESET_ICON, // small-sized colorful images + WEBP_PRESET_TEXT // text-like +} WebPPreset; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPConfigInitInternal(WebPConfig*, WebPPreset, + float, int); + +// Should always be called, to initialize a fresh WebPConfig structure before +// modification. Returns false in case of version mismatch. WebPConfigInit() +// must have succeeded before using the 'config' object. +// Note that the default values are lossless=0 and quality=75. +WEBP_NODISCARD static WEBP_INLINE int WebPConfigInit(WebPConfig* config) { + return WebPConfigInitInternal(config, WEBP_PRESET_DEFAULT, 75.f, + WEBP_ENCODER_ABI_VERSION); +} + +// This function will initialize the configuration according to a predefined +// set of parameters (referred to by 'preset') and a given quality factor. +// This function can be called as a replacement to WebPConfigInit(). Will +// return false in case of error. +WEBP_NODISCARD static WEBP_INLINE int WebPConfigPreset(WebPConfig* config, + WebPPreset preset, + float quality) { + return WebPConfigInitInternal(config, preset, quality, + WEBP_ENCODER_ABI_VERSION); +} + +// Activate the lossless compression mode with the desired efficiency level +// between 0 (fastest, lowest compression) and 9 (slower, best compression). +// A good default level is '6', providing a fair tradeoff between compression +// speed and final compressed size. +// This function will overwrite several fields from config: 'method', 'quality' +// and 'lossless'. Returns false in case of parameter error. +WEBP_NODISCARD WEBP_EXTERN int WebPConfigLosslessPreset(WebPConfig* config, + int level); + +// Returns true if 'config' is non-NULL and all configuration parameters are +// within their valid ranges. +WEBP_NODISCARD WEBP_EXTERN int WebPValidateConfig(const WebPConfig* config); + +//------------------------------------------------------------------------------ +// Input / Output +// Structure for storing auxiliary statistics. + +struct WebPAuxStats { + int coded_size; // final size + + float PSNR[5]; // peak-signal-to-noise ratio for Y/U/V/All/Alpha + int block_count[3]; // number of intra4/intra16/skipped macroblocks + int header_bytes[2]; // approximate number of bytes spent for header + // and mode-partition #0 + int residual_bytes[3][4]; // approximate number of bytes spent for + // DC/AC/uv coefficients for each (0..3) segments. + int segment_size[4]; // number of macroblocks in each segments + int segment_quant[4]; // quantizer values for each segments + int segment_level[4]; // filtering strength for each segments [0..63] + + int alpha_data_size; // size of the transparency data + int layer_data_size; // size of the enhancement layer data + + // lossless encoder statistics + uint32_t lossless_features; // bit0:predictor bit1:cross-color transform + // bit2:subtract-green bit3:color indexing + int histogram_bits; // number of precision bits of histogram + int transform_bits; // precision bits for transform + int cache_bits; // number of bits for color cache lookup + int palette_size; // number of color in palette, if used + int lossless_size; // final lossless size + int lossless_hdr_size; // lossless header (transform, huffman etc) size + int lossless_data_size; // lossless image data size + + uint32_t pad[2]; // padding for later use +}; + +// Signature for output function. Should return true if writing was successful. +// data/data_size is the segment of data to write, and 'picture' is for +// reference (and so one can make use of picture->custom_ptr). +typedef int (*WebPWriterFunction)(const uint8_t* data, size_t data_size, + const WebPPicture* picture); + +// WebPMemoryWrite: a special WebPWriterFunction that writes to memory using +// the following WebPMemoryWriter object (to be set as a custom_ptr). +struct WebPMemoryWriter { + uint8_t* mem; // final buffer (of size 'max_size', larger than 'size'). + size_t size; // final size + size_t max_size; // total capacity + uint32_t pad[1]; // padding for later use +}; + +// The following must be called first before any use. +WEBP_EXTERN void WebPMemoryWriterInit(WebPMemoryWriter* writer); + +// The following must be called to deallocate writer->mem memory. The 'writer' +// object itself is not deallocated. +WEBP_EXTERN void WebPMemoryWriterClear(WebPMemoryWriter* writer); +// The custom writer to be used with WebPMemoryWriter as custom_ptr. Upon +// completion, writer.mem and writer.size will hold the coded data. +// writer.mem must be freed by calling WebPMemoryWriterClear. +WEBP_NODISCARD WEBP_EXTERN int WebPMemoryWrite( + const uint8_t* data, size_t data_size, const WebPPicture* picture); + +// Progress hook, called from time to time to report progress. It can return +// false to request an abort of the encoding process, or true otherwise if +// everything is OK. +typedef int (*WebPProgressHook)(int percent, const WebPPicture* picture); + +// Color spaces. +typedef enum WebPEncCSP { + // chroma sampling + WEBP_YUV420 = 0, // 4:2:0 + WEBP_YUV420A = 4, // alpha channel variant + WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors + WEBP_CSP_ALPHA_BIT = 4 // bit that is set if alpha is present +} WebPEncCSP; + +// Encoding error conditions. +typedef enum WebPEncodingError { + VP8_ENC_OK = 0, + VP8_ENC_ERROR_OUT_OF_MEMORY, // memory error allocating objects + VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY, // memory error while flushing bits + VP8_ENC_ERROR_NULL_PARAMETER, // a pointer parameter is NULL + VP8_ENC_ERROR_INVALID_CONFIGURATION, // configuration is invalid + VP8_ENC_ERROR_BAD_DIMENSION, // picture has invalid width/height + VP8_ENC_ERROR_PARTITION0_OVERFLOW, // partition is bigger than 512k + VP8_ENC_ERROR_PARTITION_OVERFLOW, // partition is bigger than 16M + VP8_ENC_ERROR_BAD_WRITE, // error while flushing bytes + VP8_ENC_ERROR_FILE_TOO_BIG, // file is bigger than 4G + VP8_ENC_ERROR_USER_ABORT, // abort request by user + VP8_ENC_ERROR_LAST // list terminator. always last. +} WebPEncodingError; + +// maximum width/height allowed (inclusive), in pixels +#define WEBP_MAX_DIMENSION 16383 + +// Main exchange structure (input samples, output bytes, statistics) +// +// Once WebPPictureInit() has been called, it's ok to make all the INPUT fields +// (use_argb, y/u/v, argb, ...) point to user-owned data, even if +// WebPPictureAlloc() has been called. Depending on the value use_argb, +// it's guaranteed that either *argb or *y/*u/*v content will be kept untouched. +struct WebPPicture { + // INPUT + ////////////// + // Main flag for encoder selecting between ARGB or YUV input. + // It is recommended to use ARGB input (*argb, argb_stride) for lossless + // compression, and YUV input (*y, *u, *v, etc.) for lossy compression + // since these are the respective native colorspace for these formats. + int use_argb; + + // YUV input (mostly used for input to lossy compression) + WebPEncCSP colorspace; // colorspace: should be YUV420 for now (=Y'CbCr). + int width, height; // dimensions (less or equal to WEBP_MAX_DIMENSION) + uint8_t* y, *u, *v; // pointers to luma/chroma planes. + int y_stride, uv_stride; // luma/chroma strides. + uint8_t* a; // pointer to the alpha plane + int a_stride; // stride of the alpha plane + uint32_t pad1[2]; // padding for later use + + // ARGB input (mostly used for input to lossless compression) + uint32_t* argb; // Pointer to argb (32 bit) plane. + int argb_stride; // This is stride in pixels units, not bytes. + uint32_t pad2[3]; // padding for later use + + // OUTPUT + /////////////// + // Byte-emission hook, to store compressed bytes as they are ready. + WebPWriterFunction writer; // can be NULL + void* custom_ptr; // can be used by the writer. + + // map for extra information (only for lossy compression mode) + int extra_info_type; // 1: intra type, 2: segment, 3: quant + // 4: intra-16 prediction mode, + // 5: chroma prediction mode, + // 6: bit cost, 7: distortion + uint8_t* extra_info; // if not NULL, points to an array of size + // ((width + 15) / 16) * ((height + 15) / 16) that + // will be filled with a macroblock map, depending + // on extra_info_type. + + // STATS AND REPORTS + /////////////////////////// + // Pointer to side statistics (updated only if not NULL) + WebPAuxStats* stats; + + // Error code for the latest error encountered during encoding + WebPEncodingError error_code; + + // If not NULL, report progress during encoding. + WebPProgressHook progress_hook; + + void* user_data; // this field is free to be set to any value and + // used during callbacks (like progress-report e.g.). + + uint32_t pad3[3]; // padding for later use + + // Unused for now + uint8_t* pad4, *pad5; + uint32_t pad6[8]; // padding for later use + + // PRIVATE FIELDS + //////////////////// + void* memory_; // row chunk of memory for yuva planes + void* memory_argb_; // and for argb too. + void* pad7[2]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPPictureInitInternal(WebPPicture*, int); + +// Should always be called, to initialize the structure. Returns false in case +// of version mismatch. WebPPictureInit() must have succeeded before using the +// 'picture' object. +// Note that, by default, use_argb is false and colorspace is WEBP_YUV420. +WEBP_NODISCARD static WEBP_INLINE int WebPPictureInit(WebPPicture* picture) { + return WebPPictureInitInternal(picture, WEBP_ENCODER_ABI_VERSION); +} + +//------------------------------------------------------------------------------ +// WebPPicture utils + +// Convenience allocation / deallocation based on picture->width/height: +// Allocate y/u/v buffers as per colorspace/width/height specification. +// Note! This function will free the previous buffer if needed. +// Returns false in case of memory error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureAlloc(WebPPicture* picture); + +// Release the memory allocated by WebPPictureAlloc() or WebPPictureImport*(). +// Note that this function does _not_ free the memory used by the 'picture' +// object itself. +// Besides memory (which is reclaimed) all other fields of 'picture' are +// preserved. +WEBP_EXTERN void WebPPictureFree(WebPPicture* picture); + +// Copy the pixels of *src into *dst, using WebPPictureAlloc. Upon return, *dst +// will fully own the copied pixels (this is not a view). The 'dst' picture need +// not be initialized as its content is overwritten. +// Returns false in case of memory allocation error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureCopy(const WebPPicture* src, + WebPPicture* dst); + +// Compute the single distortion for packed planes of samples. +// 'src' will be compared to 'ref', and the raw distortion stored into +// '*distortion'. The refined metric (log(MSE), log(1 - ssim),...' will be +// stored in '*result'. +// 'x_step' is the horizontal stride (in bytes) between samples. +// 'src/ref_stride' is the byte distance between rows. +// Returns false in case of error (bad parameter, memory allocation error, ...). +WEBP_NODISCARD WEBP_EXTERN int WebPPlaneDistortion( + const uint8_t* src, size_t src_stride, + const uint8_t* ref, size_t ref_stride, int width, int height, size_t x_step, + int type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float* distortion, float* result); + +// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Results +// are in dB, stored in result[] in the B/G/R/A/All order. The distortion is +// always performed using ARGB samples. Hence if the input is YUV(A), the +// picture will be internally converted to ARGB (just for the measurement). +// Warning: this function is rather CPU-intensive. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureDistortion( + const WebPPicture* src, const WebPPicture* ref, + int metric_type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float result[5]); + +// self-crops a picture to the rectangle defined by top/left/width/height. +// Returns false in case of memory allocation error, or if the rectangle is +// outside of the source picture. +// The rectangle for the view is defined by the top-left corner pixel +// coordinates (left, top) as well as its width and height. This rectangle +// must be fully be comprised inside the 'src' source picture. If the source +// picture uses the YUV420 colorspace, the top and left coordinates will be +// snapped to even values. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureCrop( + WebPPicture* picture, int left, int top, int width, int height); + +// Extracts a view from 'src' picture into 'dst'. The rectangle for the view +// is defined by the top-left corner pixel coordinates (left, top) as well +// as its width and height. This rectangle must be fully be comprised inside +// the 'src' source picture. If the source picture uses the YUV420 colorspace, +// the top and left coordinates will be snapped to even values. +// Picture 'src' must out-live 'dst' picture. Self-extraction of view is allowed +// ('src' equal to 'dst') as a mean of fast-cropping (but note that doing so, +// the original dimension will be lost). Picture 'dst' need not be initialized +// with WebPPictureInit() if it is different from 'src', since its content will +// be overwritten. +// Returns false in case of invalid parameters. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureView( + const WebPPicture* src, int left, int top, int width, int height, + WebPPicture* dst); + +// Returns true if the 'picture' is actually a view and therefore does +// not own the memory for pixels. +WEBP_EXTERN int WebPPictureIsView(const WebPPicture* picture); + +// Rescale a picture to new dimension width x height. +// If either 'width' or 'height' (but not both) is 0 the corresponding +// dimension will be calculated preserving the aspect ratio. +// No gamma correction is applied. +// Returns false in case of error (invalid parameter or insufficient memory). +WEBP_NODISCARD WEBP_EXTERN int WebPPictureRescale(WebPPicture* picture, + int width, int height); + +// Colorspace conversion function to import RGB samples. +// Previous buffer will be free'd, if any. +// *rgb buffer should have a size of at least height * rgb_stride. +// Returns false in case of memory error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGB( + WebPPicture* picture, const uint8_t* rgb, int rgb_stride); +// Same, but for RGBA buffer. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBA( + WebPPicture* picture, const uint8_t* rgba, int rgba_stride); +// Same, but for RGBA buffer. Imports the RGB direct from the 32-bit format +// input buffer ignoring the alpha channel. Avoids needing to copy the data +// to a temporary 24-bit RGB buffer to import the RGB only. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBX( + WebPPicture* picture, const uint8_t* rgbx, int rgbx_stride); + +// Variants of the above, but taking BGR(A|X) input. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGR( + WebPPicture* picture, const uint8_t* bgr, int bgr_stride); +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRA( + WebPPicture* picture, const uint8_t* bgra, int bgra_stride); +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRX( + WebPPicture* picture, const uint8_t* bgrx, int bgrx_stride); + +// Converts picture->argb data to the YUV420A format. The 'colorspace' +// parameter is deprecated and should be equal to WEBP_YUV420. +// Upon return, picture->use_argb is set to false. The presence of real +// non-opaque transparent values is detected, and 'colorspace' will be +// adjusted accordingly. Note that this method is lossy. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVA( + WebPPicture* picture, WebPEncCSP /*colorspace = WEBP_YUV420*/); + +// Same as WebPPictureARGBToYUVA(), but the conversion is done using +// pseudo-random dithering with a strength 'dithering' between +// 0.0 (no dithering) and 1.0 (maximum dithering). This is useful +// for photographic picture. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVADithered( + WebPPicture* picture, WebPEncCSP colorspace, float dithering); + +// Performs 'sharp' RGBA->YUVA420 downsampling and colorspace conversion +// Downsampling is handled with extra care in case of color clipping. This +// method is roughly 2x slower than WebPPictureARGBToYUVA() but produces better +// and sharper YUV representation. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureSharpARGBToYUVA(WebPPicture* picture); +// kept for backward compatibility: +WEBP_NODISCARD WEBP_EXTERN int WebPPictureSmartARGBToYUVA(WebPPicture* picture); + +// Converts picture->yuv to picture->argb and sets picture->use_argb to true. +// The input format must be YUV_420 or YUV_420A. The conversion from YUV420 to +// ARGB incurs a small loss too. +// Note that the use of this colorspace is discouraged if one has access to the +// raw ARGB samples, since using YUV420 is comparatively lossy. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureYUVAToARGB(WebPPicture* picture); + +// Helper function: given a width x height plane of RGBA or YUV(A) samples +// clean-up or smoothen the YUV or RGB samples under fully transparent area, +// to help compressibility (no guarantee, though). +WEBP_EXTERN void WebPCleanupTransparentArea(WebPPicture* picture); + +// Scan the picture 'picture' for the presence of non fully opaque alpha values. +// Returns true in such case. Otherwise returns false (indicating that the +// alpha plane can be ignored altogether e.g.). +WEBP_EXTERN int WebPPictureHasTransparency(const WebPPicture* picture); + +// Remove the transparency information (if present) by blending the color with +// the background color 'background_rgb' (specified as 24bit RGB triplet). +// After this call, all alpha values are reset to 0xff. +WEBP_EXTERN void WebPBlendAlpha(WebPPicture* picture, uint32_t background_rgb); + +//------------------------------------------------------------------------------ +// Main call + +// Main encoding call, after config and picture have been initialized. +// 'picture' must be less than 16384x16384 in dimension (cf WEBP_MAX_DIMENSION), +// and the 'config' object must be a valid one. +// Returns false in case of error, true otherwise. +// In case of error, picture->error_code is updated accordingly. +// 'picture' can hold the source samples in both YUV(A) or ARGB input, depending +// on the value of 'picture->use_argb'. It is highly recommended to use +// the former for lossy encoding, and the latter for lossless encoding +// (when config.lossless is true). Automatic conversion from one format to +// another is provided but they both incur some loss. +WEBP_NODISCARD WEBP_EXTERN int WebPEncode(const WebPConfig* config, + WebPPicture* picture); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_ENCODE_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/format_constants.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/format_constants.h new file mode 100644 index 0000000000..999035c5d2 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/format_constants.h @@ -0,0 +1,87 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header for constants related to WebP file format. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_FORMAT_CONSTANTS_H_ +#define WEBP_WEBP_FORMAT_CONSTANTS_H_ + +// Create fourcc of the chunk from the chunk tag characters. +#define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24) + +// VP8 related constants. +#define VP8_SIGNATURE 0x9d012a // Signature in VP8 data. +#define VP8_MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition +#define VP8_MAX_PARTITION_SIZE (1 << 24) // max size for token partition +#define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data. + +// VP8L related constants. +#define VP8L_SIGNATURE_SIZE 1 // VP8L signature size. +#define VP8L_MAGIC_BYTE 0x2f // VP8L signature byte. +#define VP8L_IMAGE_SIZE_BITS 14 // Number of bits used to store + // width and height. +#define VP8L_VERSION_BITS 3 // 3 bits reserved for version. +#define VP8L_VERSION 0 // version 0 +#define VP8L_FRAME_HEADER_SIZE 5 // Size of the VP8L frame header. + +#define MAX_PALETTE_SIZE 256 +#define MAX_CACHE_BITS 11 +#define HUFFMAN_CODES_PER_META_CODE 5 +#define ARGB_BLACK 0xff000000 + +#define DEFAULT_CODE_LENGTH 8 +#define MAX_ALLOWED_CODE_LENGTH 15 + +#define NUM_LITERAL_CODES 256 +#define NUM_LENGTH_CODES 24 +#define NUM_DISTANCE_CODES 40 +#define CODE_LENGTH_CODES 19 + +#define MIN_HUFFMAN_BITS 2 // min number of Huffman bits +#define MAX_HUFFMAN_BITS 9 // max number of Huffman bits + +#define TRANSFORM_PRESENT 1 // The bit to be written when next data + // to be read is a transform. +#define NUM_TRANSFORMS 4 // Maximum number of allowed transform + // in a bitstream. +typedef enum { + PREDICTOR_TRANSFORM = 0, + CROSS_COLOR_TRANSFORM = 1, + SUBTRACT_GREEN_TRANSFORM = 2, + COLOR_INDEXING_TRANSFORM = 3 +} VP8LImageTransformType; + +// Alpha related constants. +#define ALPHA_HEADER_LEN 1 +#define ALPHA_NO_COMPRESSION 0 +#define ALPHA_LOSSLESS_COMPRESSION 1 +#define ALPHA_PREPROCESSED_LEVELS 1 + +// Mux related constants. +#define TAG_SIZE 4 // Size of a chunk tag (e.g. "VP8L"). +#define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size. +#define CHUNK_HEADER_SIZE 8 // Size of a chunk header. +#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). +#define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk. +#define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk. +#define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. + +#define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height. +#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height. +#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count +#define MAX_DURATION (1 << 24) // maximum duration +#define MAX_POSITION_OFFSET (1 << 24) // maximum frame x/y offset + +// Maximum chunk payload is such that adding the header and padding won't +// overflow a uint32_t. +#define MAX_CHUNK_PAYLOAD (~0U - CHUNK_HEADER_SIZE - 1) + +#endif // WEBP_WEBP_FORMAT_CONSTANTS_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux.h new file mode 100644 index 0000000000..8fb067e435 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux.h @@ -0,0 +1,591 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// RIFF container manipulation and encoding for WebP images. +// +// Authors: Urvang (urvang@google.com) +// Vikas (vikasa@google.com) + +#ifndef WEBP_WEBP_MUX_H_ +#define WEBP_WEBP_MUX_H_ + +#include "./mux_types.h" +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_MUX_ABI_VERSION 0x0109 // MAJOR(8b) + MINOR(8b) + +//------------------------------------------------------------------------------ +// Mux API +// +// This API allows manipulation of WebP container images containing features +// like color profile, metadata, animation. +// +// Code Example#1: Create a WebPMux object with image data, color profile and +// XMP metadata. +/* + int copy_data = 0; + WebPMux* mux = WebPMuxNew(); + // ... (Prepare image data). + WebPMuxSetImage(mux, &image, copy_data); + // ... (Prepare ICCP color profile data). + WebPMuxSetChunk(mux, "ICCP", &icc_profile, copy_data); + // ... (Prepare XMP metadata). + WebPMuxSetChunk(mux, "XMP ", &xmp, copy_data); + // Get data from mux in WebP RIFF format. + WebPMuxAssemble(mux, &output_data); + WebPMuxDelete(mux); + // ... (Consume output_data; e.g. write output_data.bytes to file). + WebPDataClear(&output_data); +*/ + +// Code Example#2: Get image and color profile data from a WebP file. +/* + int copy_data = 0; + // ... (Read data from file). + WebPMux* mux = WebPMuxCreate(&data, copy_data); + WebPMuxGetFrame(mux, 1, &image); + // ... (Consume image; e.g. call WebPDecode() to decode the data). + WebPMuxGetChunk(mux, "ICCP", &icc_profile); + // ... (Consume icc_data). + WebPMuxDelete(mux); + WebPFree(data); +*/ + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPMuxError WebPMuxError; +// typedef enum WebPChunkId WebPChunkId; +typedef struct WebPMux WebPMux; // main opaque object. +typedef struct WebPMuxFrameInfo WebPMuxFrameInfo; +typedef struct WebPMuxAnimParams WebPMuxAnimParams; +typedef struct WebPAnimEncoderOptions WebPAnimEncoderOptions; + +// Error codes +typedef enum WEBP_NODISCARD WebPMuxError { + WEBP_MUX_OK = 1, + WEBP_MUX_NOT_FOUND = 0, + WEBP_MUX_INVALID_ARGUMENT = -1, + WEBP_MUX_BAD_DATA = -2, + WEBP_MUX_MEMORY_ERROR = -3, + WEBP_MUX_NOT_ENOUGH_DATA = -4 +} WebPMuxError; + +// IDs for different types of chunks. +typedef enum WebPChunkId { + WEBP_CHUNK_VP8X, // VP8X + WEBP_CHUNK_ICCP, // ICCP + WEBP_CHUNK_ANIM, // ANIM + WEBP_CHUNK_ANMF, // ANMF + WEBP_CHUNK_DEPRECATED, // (deprecated from FRGM) + WEBP_CHUNK_ALPHA, // ALPH + WEBP_CHUNK_IMAGE, // VP8/VP8L + WEBP_CHUNK_EXIF, // EXIF + WEBP_CHUNK_XMP, // XMP + WEBP_CHUNK_UNKNOWN, // Other chunks. + WEBP_CHUNK_NIL +} WebPChunkId; + +//------------------------------------------------------------------------------ + +// Returns the version number of the mux library, packed in hexadecimal using +// 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetMuxVersion(void); + +//------------------------------------------------------------------------------ +// Life of a Mux object + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN WebPMux* WebPNewInternal(int); + +// Creates an empty mux object. +// Returns: +// A pointer to the newly created empty mux object. +// Or NULL in case of memory error. +WEBP_NODISCARD static WEBP_INLINE WebPMux* WebPMuxNew(void) { + return WebPNewInternal(WEBP_MUX_ABI_VERSION); +} + +// Deletes the mux object. +// Parameters: +// mux - (in/out) object to be deleted +WEBP_EXTERN void WebPMuxDelete(WebPMux* mux); + +//------------------------------------------------------------------------------ +// Mux creation. + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN WebPMux* WebPMuxCreateInternal(const WebPData*, int, + int); + +// Creates a mux object from raw data given in WebP RIFF format. +// Parameters: +// bitstream - (in) the bitstream data in WebP RIFF format +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. If the +// data is not copied, it must exist for the lifetime of the +// mux object. +// Returns: +// A pointer to the mux object created from given data - on success. +// NULL - In case of invalid data or memory error. +WEBP_NODISCARD static WEBP_INLINE WebPMux* WebPMuxCreate( + const WebPData* bitstream, int copy_data) { + return WebPMuxCreateInternal(bitstream, copy_data, WEBP_MUX_ABI_VERSION); +} + +//------------------------------------------------------------------------------ +// Non-image chunks. + +// Note: Only non-image related chunks should be managed through chunk APIs. +// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH"). +// To add, get and delete images, use WebPMuxSetImage(), WebPMuxPushFrame(), +// WebPMuxGetFrame() and WebPMuxDeleteFrame(). + +// Adds a chunk with id 'fourcc' and data 'chunk_data' in the mux object. +// Any existing chunk(s) with the same id will be removed. +// Parameters: +// mux - (in/out) object to which the chunk is to be added +// fourcc - (in) a character array containing the fourcc of the given chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// chunk_data - (in) the chunk data to be added +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. If the +// data is not copied, it must exist until a call to +// WebPMuxAssemble() is made. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxSetChunk( + WebPMux* mux, const char fourcc[4], const WebPData* chunk_data, + int copy_data); + +// Gets a reference to the data of the chunk with id 'fourcc' in the mux object. +// The caller should NOT free the returned data. +// Parameters: +// mux - (in) object from which the chunk data is to be fetched +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// chunk_data - (out) returned chunk data +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, fourcc or chunk_data is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given id. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxGetChunk( + const WebPMux* mux, const char fourcc[4], WebPData* chunk_data); + +// Deletes the chunk with the given 'fourcc' from the mux object. +// Parameters: +// mux - (in/out) object from which the chunk is to be deleted +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF" etc. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or fourcc is NULL +// or if fourcc corresponds to an image chunk. +// WEBP_MUX_NOT_FOUND - If mux does not contain a chunk with the given fourcc. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxDeleteChunk( + WebPMux* mux, const char fourcc[4]); + +//------------------------------------------------------------------------------ +// Images. + +// Encapsulates data about a single frame. +struct WebPMuxFrameInfo { + WebPData bitstream; // image data: can be a raw VP8/VP8L bitstream + // or a single-image WebP file. + int x_offset; // x-offset of the frame. + int y_offset; // y-offset of the frame. + int duration; // duration of the frame (in milliseconds). + + WebPChunkId id; // frame type: should be one of WEBP_CHUNK_ANMF + // or WEBP_CHUNK_IMAGE + WebPMuxAnimDispose dispose_method; // Disposal method for the frame. + WebPMuxAnimBlend blend_method; // Blend operation for the frame. + uint32_t pad[1]; // padding for later use +}; + +// Sets the (non-animated) image in the mux object. +// Note: Any existing images (including frames) will be removed. +// Parameters: +// mux - (in/out) object in which the image is to be set +// bitstream - (in) can be a raw VP8/VP8L bitstream or a single-image +// WebP file (non-animated) +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. If the +// data is not copied, it must exist until a call to +// WebPMuxAssemble() is made. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL or bitstream is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxSetImage( + WebPMux* mux, const WebPData* bitstream, int copy_data); + +// Adds a frame at the end of the mux object. +// Notes: (1) frame.id should be WEBP_CHUNK_ANMF +// (2) For setting a non-animated image, use WebPMuxSetImage() instead. +// (3) Type of frame being pushed must be same as the frames in mux. +// (4) As WebP only supports even offsets, any odd offset will be snapped +// to an even location using: offset &= ~1 +// Parameters: +// mux - (in/out) object to which the frame is to be added +// frame - (in) frame data. +// copy_data - (in) value 1 indicates given data WILL be copied to the mux +// object and value 0 indicates data will NOT be copied. If the +// data is not copied, it must exist until a call to +// WebPMuxAssemble() is made. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL +// or if content of 'frame' is invalid. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxPushFrame( + WebPMux* mux, const WebPMuxFrameInfo* frame, int copy_data); + +// Gets the nth frame from the mux object. +// The content of 'frame->bitstream' is allocated using WebPMalloc(), and NOT +// owned by the 'mux' object. It MUST be deallocated by the caller by calling +// WebPDataClear(). +// nth=0 has a special meaning - last position. +// Parameters: +// mux - (in) object from which the info is to be fetched +// nth - (in) index of the frame in the mux object +// frame - (out) data of the returned frame +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or frame is NULL. +// WEBP_MUX_NOT_FOUND - if there are less than nth frames in the mux object. +// WEBP_MUX_BAD_DATA - if nth frame chunk in mux is invalid. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxGetFrame( + const WebPMux* mux, uint32_t nth, WebPMuxFrameInfo* frame); + +// Deletes a frame from the mux object. +// nth=0 has a special meaning - last position. +// Parameters: +// mux - (in/out) object from which a frame is to be deleted +// nth - (in) The position from which the frame is to be deleted +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL. +// WEBP_MUX_NOT_FOUND - If there are less than nth frames in the mux object +// before deletion. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxDeleteFrame(WebPMux* mux, uint32_t nth); + +//------------------------------------------------------------------------------ +// Animation. + +// Animation parameters. +struct WebPMuxAnimParams { + uint32_t bgcolor; // Background color of the canvas stored (in MSB order) as: + // Bits 00 to 07: Alpha. + // Bits 08 to 15: Red. + // Bits 16 to 23: Green. + // Bits 24 to 31: Blue. + int loop_count; // Number of times to repeat the animation [0 = infinite]. +}; + +// Sets the animation parameters in the mux object. Any existing ANIM chunks +// will be removed. +// Parameters: +// mux - (in/out) object in which ANIM chunk is to be set/added +// params - (in) animation parameters. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxSetAnimationParams( + WebPMux* mux, const WebPMuxAnimParams* params); + +// Gets the animation parameters from the mux object. +// Parameters: +// mux - (in) object from which the animation parameters to be fetched +// params - (out) animation parameters extracted from the ANIM chunk +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or params is NULL. +// WEBP_MUX_NOT_FOUND - if ANIM chunk is not present in mux object. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxGetAnimationParams( + const WebPMux* mux, WebPMuxAnimParams* params); + +//------------------------------------------------------------------------------ +// Misc Utilities. + +// Sets the canvas size for the mux object. The width and height can be +// specified explicitly or left as zero (0, 0). +// * When width and height are specified explicitly, then this frame bound is +// enforced during subsequent calls to WebPMuxAssemble() and an error is +// reported if any animated frame does not completely fit within the canvas. +// * When unspecified (0, 0), the constructed canvas will get the frame bounds +// from the bounding-box over all frames after calling WebPMuxAssemble(). +// Parameters: +// mux - (in) object to which the canvas size is to be set +// width - (in) canvas width +// height - (in) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux is NULL; or +// width or height are invalid or out of bounds +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxSetCanvasSize(WebPMux* mux, + int width, int height); + +// Gets the canvas size from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). +// Parameters: +// mux - (in) object from which the canvas size is to be fetched +// width - (out) canvas width +// height - (out) canvas height +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, width or height is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxGetCanvasSize(const WebPMux* mux, + int* width, int* height); + +// Gets the feature flags from the mux object. +// Note: This method assumes that the VP8X chunk, if present, is up-to-date. +// That is, the mux object hasn't been modified since the last call to +// WebPMuxAssemble() or WebPMuxCreate(). +// Parameters: +// mux - (in) object from which the features are to be fetched +// flags - (out) the flags specifying which features are present in the +// mux object. This will be an OR of various flag values. +// Enum 'WebPFeatureFlags' can be used to test individual flag values. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux or flags is NULL. +// WEBP_MUX_BAD_DATA - if VP8X/VP8/VP8L chunk or canvas size is invalid. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxGetFeatures(const WebPMux* mux, + uint32_t* flags); + +// Gets number of chunks with the given 'id' in the mux object. +// Parameters: +// mux - (in) object from which the info is to be fetched +// id - (in) chunk id specifying the type of chunk +// num_elements - (out) number of chunks with the given chunk id +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if mux, or num_elements is NULL. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxNumChunks(const WebPMux* mux, + WebPChunkId id, int* num_elements); + +// Assembles all chunks in WebP RIFF format and returns in 'assembled_data'. +// This function also validates the mux object. +// Note: The content of 'assembled_data' will be ignored and overwritten. +// Also, the content of 'assembled_data' is allocated using WebPMalloc(), and +// NOT owned by the 'mux' object. It MUST be deallocated by the caller by +// calling WebPDataClear(). It's always safe to call WebPDataClear() upon +// return, even in case of error. +// Parameters: +// mux - (in/out) object whose chunks are to be assembled +// assembled_data - (out) assembled WebP data +// Returns: +// WEBP_MUX_BAD_DATA - if mux object is invalid. +// WEBP_MUX_INVALID_ARGUMENT - if mux or assembled_data is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPMuxAssemble(WebPMux* mux, + WebPData* assembled_data); + +//------------------------------------------------------------------------------ +// WebPAnimEncoder API +// +// This API allows encoding (possibly) animated WebP images. +// +// Code Example: +/* + WebPAnimEncoderOptions enc_options; + WebPAnimEncoderOptionsInit(&enc_options); + // Tune 'enc_options' as needed. + WebPAnimEncoder* enc = WebPAnimEncoderNew(width, height, &enc_options); + while() { + WebPConfig config; + WebPConfigInit(&config); + // Tune 'config' as needed. + WebPAnimEncoderAdd(enc, frame, timestamp_ms, &config); + } + WebPAnimEncoderAdd(enc, NULL, timestamp_ms, NULL); + WebPAnimEncoderAssemble(enc, webp_data); + WebPAnimEncoderDelete(enc); + // Write the 'webp_data' to a file, or re-mux it further. +*/ + +typedef struct WebPAnimEncoder WebPAnimEncoder; // Main opaque object. + +// Forward declarations. Defined in encode.h. +struct WebPPicture; +struct WebPConfig; + +// Global options. +struct WebPAnimEncoderOptions { + WebPMuxAnimParams anim_params; // Animation parameters. + int minimize_size; // If true, minimize the output size (slow). Implicitly + // disables key-frame insertion. + int kmin; + int kmax; // Minimum and maximum distance between consecutive key + // frames in the output. The library may insert some key + // frames as needed to satisfy this criteria. + // Note that these conditions should hold: kmax > kmin + // and kmin >= kmax / 2 + 1. Also, if kmax <= 0, then + // key-frame insertion is disabled; and if kmax == 1, + // then all frames will be key-frames (kmin value does + // not matter for these special cases). + int allow_mixed; // If true, use mixed compression mode; may choose + // either lossy and lossless for each frame. + int verbose; // If true, print info and warning messages to stderr. + + uint32_t padding[4]; // Padding for later use. +}; + +// Internal, version-checked, entry point. +WEBP_EXTERN int WebPAnimEncoderOptionsInitInternal( + WebPAnimEncoderOptions*, int); + +// Should always be called, to initialize a fresh WebPAnimEncoderOptions +// structure before modification. Returns false in case of version mismatch. +// WebPAnimEncoderOptionsInit() must have succeeded before using the +// 'enc_options' object. +WEBP_NODISCARD static WEBP_INLINE int WebPAnimEncoderOptionsInit( + WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderOptionsInitInternal(enc_options, WEBP_MUX_ABI_VERSION); +} + +// Internal, version-checked, entry point. +WEBP_EXTERN WebPAnimEncoder* WebPAnimEncoderNewInternal( + int, int, const WebPAnimEncoderOptions*, int); + +// Creates and initializes a WebPAnimEncoder object. +// Parameters: +// width/height - (in) canvas width and height of the animation. +// enc_options - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// A pointer to the newly created WebPAnimEncoder object. +// Or NULL in case of memory error. +static WEBP_INLINE WebPAnimEncoder* WebPAnimEncoderNew( + int width, int height, const WebPAnimEncoderOptions* enc_options) { + return WebPAnimEncoderNewInternal(width, height, enc_options, + WEBP_MUX_ABI_VERSION); +} + +// Optimize the given frame for WebP, encode it and add it to the +// WebPAnimEncoder object. +// The last call to 'WebPAnimEncoderAdd' should be with frame = NULL, which +// indicates that no more frames are to be added. This call is also used to +// determine the duration of the last frame. +// Parameters: +// enc - (in/out) object to which the frame is to be added. +// frame - (in/out) frame data in ARGB or YUV(A) format. If it is in YUV(A) +// format, it will be converted to ARGB, which incurs a small loss. +// timestamp_ms - (in) timestamp of this frame in milliseconds. +// Duration of a frame would be calculated as +// "timestamp of next frame - timestamp of this frame". +// Hence, timestamps should be in non-decreasing order. +// config - (in) encoding options; can be passed NULL to pick +// reasonable defaults. +// Returns: +// On error, returns false and frame->error_code is set appropriately. +// Otherwise, returns true. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimEncoderAdd( + WebPAnimEncoder* enc, struct WebPPicture* frame, int timestamp_ms, + const struct WebPConfig* config); + +// Assemble all frames added so far into a WebP bitstream. +// This call should be preceded by a call to 'WebPAnimEncoderAdd' with +// frame = NULL; if not, the duration of the last frame will be internally +// estimated. +// Parameters: +// enc - (in/out) object from which the frames are to be assembled. +// webp_data - (out) generated WebP bitstream. +// Returns: +// True on success. +WEBP_NODISCARD WEBP_EXTERN int WebPAnimEncoderAssemble(WebPAnimEncoder* enc, + WebPData* webp_data); + +// Get error string corresponding to the most recent call using 'enc'. The +// returned string is owned by 'enc' and is valid only until the next call to +// WebPAnimEncoderAdd() or WebPAnimEncoderAssemble() or WebPAnimEncoderDelete(). +// Parameters: +// enc - (in/out) object from which the error string is to be fetched. +// Returns: +// NULL if 'enc' is NULL. Otherwise, returns the error string if the last call +// to 'enc' had an error, or an empty string if the last call was a success. +WEBP_EXTERN const char* WebPAnimEncoderGetError(WebPAnimEncoder* enc); + +// Deletes the WebPAnimEncoder object. +// Parameters: +// enc - (in/out) object to be deleted +WEBP_EXTERN void WebPAnimEncoderDelete(WebPAnimEncoder* enc); + +//------------------------------------------------------------------------------ +// Non-image chunks. + +// Note: Only non-image related chunks should be managed through chunk APIs. +// (Image related chunks are: "ANMF", "VP8 ", "VP8L" and "ALPH"). + +// Adds a chunk with id 'fourcc' and data 'chunk_data' in the enc object. +// Any existing chunk(s) with the same id will be removed. +// Parameters: +// enc - (in/out) object to which the chunk is to be added +// fourcc - (in) a character array containing the fourcc of the given chunk; +// e.g., "ICCP", "XMP ", "EXIF", etc. +// chunk_data - (in) the chunk data to be added +// copy_data - (in) value 1 indicates given data WILL be copied to the enc +// object and value 0 indicates data will NOT be copied. If the +// data is not copied, it must exist until a call to +// WebPAnimEncoderAssemble() is made. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if enc, fourcc or chunk_data is NULL. +// WEBP_MUX_MEMORY_ERROR - on memory allocation error. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPAnimEncoderSetChunk( + WebPAnimEncoder* enc, const char fourcc[4], const WebPData* chunk_data, + int copy_data); + +// Gets a reference to the data of the chunk with id 'fourcc' in the enc object. +// The caller should NOT free the returned data. +// Parameters: +// enc - (in) object from which the chunk data is to be fetched +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF", etc. +// chunk_data - (out) returned chunk data +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if enc, fourcc or chunk_data is NULL. +// WEBP_MUX_NOT_FOUND - If enc does not contain a chunk with the given id. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPAnimEncoderGetChunk( + const WebPAnimEncoder* enc, const char fourcc[4], WebPData* chunk_data); + +// Deletes the chunk with the given 'fourcc' from the enc object. +// Parameters: +// enc - (in/out) object from which the chunk is to be deleted +// fourcc - (in) a character array containing the fourcc of the chunk; +// e.g., "ICCP", "XMP ", "EXIF", etc. +// Returns: +// WEBP_MUX_INVALID_ARGUMENT - if enc or fourcc is NULL. +// WEBP_MUX_NOT_FOUND - If enc does not contain a chunk with the given fourcc. +// WEBP_MUX_OK - on success. +WEBP_EXTERN WebPMuxError WebPAnimEncoderDeleteChunk( + WebPAnimEncoder* enc, const char fourcc[4]); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_MUX_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux_types.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux_types.h new file mode 100644 index 0000000000..c585d2082f --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/mux_types.h @@ -0,0 +1,99 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Data-types common to the mux and demux libraries. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_MUX_TYPES_H_ +#define WEBP_WEBP_MUX_TYPES_H_ + +#include // memset() +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPFeatureFlags WebPFeatureFlags; +// typedef enum WebPMuxAnimDispose WebPMuxAnimDispose; +// typedef enum WebPMuxAnimBlend WebPMuxAnimBlend; +typedef struct WebPData WebPData; + +// VP8X Feature Flags. +typedef enum WebPFeatureFlags { + ANIMATION_FLAG = 0x00000002, + XMP_FLAG = 0x00000004, + EXIF_FLAG = 0x00000008, + ALPHA_FLAG = 0x00000010, + ICCP_FLAG = 0x00000020, + + ALL_VALID_FLAGS = 0x0000003e +} WebPFeatureFlags; + +// Dispose method (animation only). Indicates how the area used by the current +// frame is to be treated before rendering the next frame on the canvas. +typedef enum WebPMuxAnimDispose { + WEBP_MUX_DISPOSE_NONE, // Do not dispose. + WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color. +} WebPMuxAnimDispose; + +// Blend operation (animation only). Indicates how transparent pixels of the +// current frame are blended with those of the previous canvas. +typedef enum WebPMuxAnimBlend { + WEBP_MUX_BLEND, // Blend. + WEBP_MUX_NO_BLEND // Do not blend. +} WebPMuxAnimBlend; + +// Data type used to describe 'raw' data, e.g., chunk data +// (ICC profile, metadata) and WebP compressed image data. +// 'bytes' memory must be allocated using WebPMalloc() and such. +struct WebPData { + const uint8_t* bytes; + size_t size; +}; + +// Initializes the contents of the 'webp_data' object with default values. +static WEBP_INLINE void WebPDataInit(WebPData* webp_data) { + if (webp_data != NULL) { + memset(webp_data, 0, sizeof(*webp_data)); + } +} + +// Clears the contents of the 'webp_data' object by calling WebPFree(). +// Does not deallocate the object itself. +static WEBP_INLINE void WebPDataClear(WebPData* webp_data) { + if (webp_data != NULL) { + WebPFree((void*)webp_data->bytes); + WebPDataInit(webp_data); + } +} + +// Allocates necessary storage for 'dst' and copies the contents of 'src'. +// Returns true on success. +WEBP_NODISCARD static WEBP_INLINE int WebPDataCopy(const WebPData* src, + WebPData* dst) { + if (src == NULL || dst == NULL) return 0; + WebPDataInit(dst); + if (src->bytes != NULL && src->size != 0) { + dst->bytes = (uint8_t*)WebPMalloc(src->size); + if (dst->bytes == NULL) return 0; + memcpy((void*)dst->bytes, src->bytes, src->size); + dst->size = src->size; + } + return 1; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_MUX_TYPES_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/types.h b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/types.h new file mode 100644 index 0000000000..9c17edec45 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPCore/libwebp/src/webp/types.h @@ -0,0 +1,93 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Common types + memory wrappers +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_TYPES_H_ +#define WEBP_WEBP_TYPES_H_ + +#include // for size_t + +#ifndef _MSC_VER +#include +#if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define WEBP_INLINE inline +#else +#define WEBP_INLINE +#endif +#else +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned long long int uint64_t; +typedef long long int int64_t; +#define WEBP_INLINE __forceinline +#endif /* _MSC_VER */ + +#ifndef WEBP_NODISCARD +#if defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD +#if (defined(__cplusplus) && __cplusplus >= 201700L) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) +#define WEBP_NODISCARD [[nodiscard]] +#else +// gcc's __has_attribute does not work for enums. +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(warn_unused_result) +#define WEBP_NODISCARD __attribute__((warn_unused_result)) +#else +#define WEBP_NODISCARD +#endif /* __has_attribute(warn_unused_result) */ +#else +#define WEBP_NODISCARD +#endif /* defined(__clang__) && defined(__has_attribute) */ +#endif /* (defined(__cplusplus) && __cplusplus >= 201700L) || + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) */ +#else +#define WEBP_NODISCARD +#endif /* defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD */ +#endif /* WEBP_NODISCARD */ + +#ifndef WEBP_EXTERN +// This explicitly marks library functions and allows for changing the +// signature for e.g., Windows DLL builds. +# if defined(_WIN32) && defined(WEBP_DLL) +# define WEBP_EXTERN __declspec(dllexport) +# elif defined(__GNUC__) && __GNUC__ >= 4 +# define WEBP_EXTERN extern __attribute__ ((visibility ("default"))) +# else +# define WEBP_EXTERN extern +# endif /* defined(_WIN32) && defined(WEBP_DLL) */ +#endif /* WEBP_EXTERN */ + +// Macro to check ABI compatibility (same major revision number) +#define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8)) + +#ifdef __cplusplus +extern "C" { +#endif + +// Allocates 'size' bytes of memory. Returns NULL upon error. Memory +// must be deallocated by calling WebPFree(). This function is made available +// by the core 'libwebp' library. +WEBP_NODISCARD WEBP_EXTERN void* WebPMalloc(size_t size); + +// Releases memory returned by the WebPDecode*() functions (from decode.h). +WEBP_EXTERN void WebPFree(void* ptr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_TYPES_H_ diff --git a/Telegram/WatchApp/Packages/WebPKit/Sources/WebPKit/WebPDecoder.swift b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPKit/WebPDecoder.swift new file mode 100644 index 0000000000..29ce25c2be --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Sources/WebPKit/WebPDecoder.swift @@ -0,0 +1,47 @@ +import CoreGraphics +import Foundation +import WebPCore + +/// Decodes WebP image data via the vendored libwebp. Returns a `CGImage` in RGBA8888. +/// Caller does not need to manage the libwebp allocation — we copy into a `Data` +/// retained by the `CGDataProvider`. +public enum WebPDecoder { + public static func decode(data: Data) -> CGImage? { + guard !data.isEmpty else { return nil } + var outPtr: UnsafeMutablePointer? = nil + var width: Int32 = 0 + var height: Int32 = 0 + let ok = data.withUnsafeBytes { (raw: UnsafeRawBufferPointer) -> Int32 in + guard let base = raw.bindMemory(to: UInt8.self).baseAddress else { return 0 } + return webp_kit_decode_rgba(base, raw.count, &outPtr, &width, &height) + } + guard ok == 1, let outPtr, width > 0, height > 0 else { return nil } + defer { webp_kit_free(outPtr) } + + let pixelCount = Int(width) * Int(height) * 4 + let buffer = Data(bytes: outPtr, count: pixelCount) + guard let provider = CGDataProvider(data: buffer as CFData) else { return nil } + + let bitmapInfo = CGBitmapInfo(rawValue: + CGImageAlphaInfo.last.rawValue | + CGBitmapInfo.byteOrder32Big.rawValue) + return CGImage( + width: Int(width), + height: Int(height), + bitsPerComponent: 8, + bitsPerPixel: 32, + bytesPerRow: Int(width) * 4, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: bitmapInfo, + provider: provider, + decode: nil, + shouldInterpolate: true, + intent: .defaultIntent + ) + } + + public static func decode(url: URL) -> CGImage? { + guard let data = try? Data(contentsOf: url) else { return nil } + return decode(data: data) + } +} diff --git a/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/Resources/sticker_raster.webp b/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/Resources/sticker_raster.webp new file mode 100644 index 0000000000..db69e691c4 Binary files /dev/null and b/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/Resources/sticker_raster.webp differ diff --git a/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/WebPDecoderTests.swift b/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/WebPDecoderTests.swift new file mode 100644 index 0000000000..3efa605c68 --- /dev/null +++ b/Telegram/WatchApp/Packages/WebPKit/Tests/WebPKitTests/WebPDecoderTests.swift @@ -0,0 +1,29 @@ +import XCTest +import CoreGraphics +@testable import WebPKit + +final class WebPDecoderTests: XCTestCase { + func testDecodesRealTelegramWebp() throws { + let url = Bundle.module.url(forResource: "sticker_raster", withExtension: "webp")! + let cgImage = WebPDecoder.decode(url: url) + let img = try XCTUnwrap(cgImage) + XCTAssertEqual(img.width, 512) + XCTAssertEqual(img.height, 401) + } + + func testDecodesFromData() throws { + let url = Bundle.module.url(forResource: "sticker_raster", withExtension: "webp")! + let data = try Data(contentsOf: url) + let img = WebPDecoder.decode(data: data) + XCTAssertNotNil(img) + } + + func testRandomBytesReturnsNil() { + let bogus = Data((0..<256).map { _ in UInt8.random(in: 0...255) }) + XCTAssertNil(WebPDecoder.decode(data: bogus)) + } + + func testEmptyDataReturnsNil() { + XCTAssertNil(WebPDecoder.decode(data: Data())) + } +} diff --git a/Telegram/WatchApp/project.yml b/Telegram/WatchApp/project.yml new file mode 100644 index 0000000000..bf08b9408a --- /dev/null +++ b/Telegram/WatchApp/project.yml @@ -0,0 +1,113 @@ +# Embedded-build variant of project.yml. +# +# Used by tools/export-sources.sh to generate the xcodeproj that the Telegram +# iOS Bazel build (apple_prebuilt_watchos_application) compiles with +# `xcodebuild -configuration Release`. Differs from project.yml deliberately: +# - watchOS app target only (no watchapp2-container, no test/snapshot targets) +# - bundle id ph.telegra.Telegraph.watchkitapp, a companion of ph.telegra.Telegraph +# - signing disabled here; the Bazel rule codesigns the output +# - no SnapshotPreviews (preview/test tooling must not ship) +# Keep `packages`, `deploymentTarget`, sources and the Info.plist path in sync +# with project.yml. Do NOT add the container target or SnapshotPreviews here. +name: tgwatch +options: + bundleIdPrefix: ph.telegra # ph.telegra.Telegraph.watchkitapp, child of the main Telegram app id + developmentLanguage: en + deploymentTarget: + watchOS: "26.0" +# Both configs are required: xcodegen 2.45.4 crashes generating the scheme when +# only one config exists. The embedded build only ever uses Release. +configs: + Debug: debug + Release: release +settings: + base: + SWIFT_VERSION: "5.0" + SWIFT_STRICT_CONCURRENCY: minimal + MARKETING_VERSION: "0.1" + CURRENT_PROJECT_VERSION: "1" +packages: + TDShim: + path: Packages/TDShim + QRCodeGenerator: + url: https://github.com/fwcd/swift-qrcode-generator + exactVersion: 2.0.2 + RLottieKit: + path: Packages/RLottieKit + WebPKit: + path: Packages/WebPKit + OpusKit: + path: Packages/OpusKit +targets: + tgwatch Watch App: + type: application + platform: watchOS + deploymentTarget: "26.0" + sources: + - path: tgwatch Watch App + - path: Assets/SampleStickers + buildPhase: resources + type: folder + info: + path: tgwatch Watch App/Info.plist + properties: + CFBundleDisplayName: Telegram + CFBundleIconName: AppIcon + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + WKApplication: true + WKCompanionAppBundleIdentifier: ph.telegra.Telegraph + # Intentional: keeps the watch app independently installable on the watch + # while still shipping inside the Telegram IPA. Do not change to false. + WKRunsIndependentlyOfCompanionApp: true + TG_API_ID: $(TG_API_ID) + TG_API_HASH: $(TG_API_HASH) + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + NSMicrophoneUsageDescription: Record voice messages to send in chats. + NSLocationWhenInUseUsageDescription: Share your current location in chats. + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: ph.telegra.Telegraph.watchkitapp + DEVELOPMENT_TEAM: C67CF9S4VU + TARGETED_DEVICE_FAMILY: "4" + GENERATE_INFOPLIST_FILE: "NO" + LD_RUNPATH_SEARCH_PATHS: $(inherited) @executable_path/Frameworks + SKIP_INSTALL: "YES" + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor + CODE_SIGNING_ALLOWED: "NO" + CODE_SIGNING_REQUIRED: "NO" + CODE_SIGN_IDENTITY: "" + configs: + Release: + # Bazel consumes a plain `xcodebuild build`, so DEPLOYMENT_POSTPROCESSING + # stays NO and the default strip phase never runs — the executable ships + # with its full ~1M-symbol table, doubling each thinned slice to ~100MB + # and tripping App Store's 75MB watchOS limit (error 90389). Force the + # strip during build. Scoped to this target only: a project-wide setting + # also tries to strip the SPM static libs, which errors on COgg.o. + DEPLOYMENT_POSTPROCESSING: "YES" + STRIP_INSTALLED_PRODUCT: "YES" + STRIP_STYLE: "all" + DEAD_CODE_STRIPPING: "YES" + dependencies: + - package: TDShim + product: TDShim + - package: QRCodeGenerator + product: QRCodeGenerator + - package: RLottieKit + product: RLottieKit + - package: WebPKit + product: WebPKit + - package: OpusKit + product: OpusKit +schemes: + tgwatch Watch App: + build: + targets: + tgwatch Watch App: all + archive: + config: Release + run: + config: Release + executable: tgwatch Watch App diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/Account.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/Account.swift new file mode 100644 index 0000000000..6669e5cc75 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/Account.swift @@ -0,0 +1,18 @@ +import Foundation + +/// One Telegram account managed by tgwatch. The on-disk dir name is +/// `id.uuidString`; everything else is metadata harvested from TDLib +/// after a successful login. +struct Account: Identifiable, Codable, Hashable, Sendable { + let id: UUID + /// Immutable: the account is bound to its datacenter for its lifetime. + let useTestDc: Bool + /// First+last name from `getMe()`. nil until the account completes login. + var displayName: String? + /// Bare digits, no leading `+`. nil until login completes. + var phoneNumber: String? + /// TDLib user id. nil until login completes. + var userId: Int64? + var createdAt: Foundation.Date + var lastActiveAt: Foundation.Date +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountBootstrapView.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountBootstrapView.swift new file mode 100644 index 0000000000..1f0740b748 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountBootstrapView.swift @@ -0,0 +1,64 @@ +import SwiftUI + +/// Rendered by `TgwatchApp` whenever there is no active client (fresh install, +/// or just after the last account was removed). On appear it asks the manager +/// to provision a Production account — a no-op if one already exists — which +/// flips `activeClient` and routes the app into `ContentView` → QR login. It +/// only shows persistent UI in the rare persist-failure case, where it surfaces +/// the error and a retry. There is no "welcome"/onboarding step. +struct AccountBootstrapView: View { + @Environment(AccountManager.self) private var manager + + var body: some View { + AccountBootstrapContent( + errorMessage: manager.lastError, + onRetry: { manager.ensureAccountExists() } + ) + .task { manager.ensureAccountExists() } + } +} + +/// Pure presentational body for `AccountBootstrapView`. Shows a loading state +/// while provisioning succeeds (the common case — it flashes briefly, then the +/// app swaps to `ContentView`), or an error + retry when account persistence +/// failed. +struct AccountBootstrapContent: View { + let errorMessage: String? + let onRetry: () -> Void + + var body: some View { + if let errorMessage { + VStack(spacing: 16) { + Text(errorMessage) + .font(.footnote) + .foregroundStyle(.red) + .multilineTextAlignment(.center) + .accessibilityIdentifier("accountBootstrapError") + Text("Try again") + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(Color.gray.opacity(0.25), in: Capsule()) + .contentShape(Capsule()) + .onTapGesture { onRetry() } + .accessibilityIdentifier("accountBootstrapRetry") + } + .padding() + } else { + LoadingView(label: "Preparing…") + } + } +} + +#if DEBUG +#Preview("Loading") { + AccountBootstrapContent(errorMessage: nil, onRetry: {}) + .background(Color.black) + .environment(\.colorScheme, .dark) +} + +#Preview("Error") { + AccountBootstrapContent(errorMessage: "Couldn't save account list.", onRetry: {}) + .background(Color.black) + .environment(\.colorScheme, .dark) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountListProjection.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountListProjection.swift new file mode 100644 index 0000000000..4f6dce0e22 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountListProjection.swift @@ -0,0 +1,38 @@ +import Foundation + +/// View-model for a single row in `AccountsListView`. Identifiable by the +/// underlying account UUID so SwiftUI's diffing across switches is stable. +struct AccountListRow: Identifiable, Equatable, Sendable { + let id: UUID + let label: String + let showsTestPill: Bool + let isActive: Bool +} + +/// Pure helpers — no `@MainActor`, no `@Observable`, no SwiftUI imports. +enum AccountListProjection { + static func row(for account: Account, isActive: Bool) -> AccountListRow { + AccountListRow( + id: account.id, + label: label(for: account), + showsTestPill: account.useTestDc, + isActive: isActive + ) + } + + static func rows(accounts: [Account], activeAccountId: UUID?) -> [AccountListRow] { + accounts + .sorted { $0.lastActiveAt > $1.lastActiveAt } + .map { row(for: $0, isActive: $0.id == activeAccountId) } + } + + private static func label(for account: Account) -> String { + if let displayName = account.displayName, !displayName.isEmpty { + return displayName + } + if let phone = account.phoneNumber, !phone.isEmpty { + return "+\(phone)" + } + return "New account" + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountManager.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountManager.swift new file mode 100644 index 0000000000..04d5198b3c --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountManager.swift @@ -0,0 +1,253 @@ +import Foundation +import Observation +import OSLog +import TDShim + +/// App-root orchestrator: owns the account registry and the single live +/// `TDClient`. Inactive accounts are merely on-disk database dirs. +@Observable +@MainActor +final class AccountManager: TDClientLifecycleDelegate { + + private(set) var accounts: [Account] + private(set) var activeAccountId: UUID? + private(set) var activeClient: TDClient? + private(set) var lastError: String? + + private let registry: AccountRegistry + private let factory: TDClientFactory + private let environment: [String: String] + private let accountDirectoryRemover: (UUID) -> Void + private let now: () -> Foundation.Date + private let logger = Logger(subsystem: "com.isaac.tgwatch", category: "accounts") + + init( + registry: AccountRegistry, + factory: TDClientFactory, + environment: [String: String] = ProcessInfo.processInfo.environment, + accountDirectoryRemover: ((UUID) -> Void)? = nil, + now: @escaping () -> Foundation.Date = { Foundation.Date() } + ) { + self.registry = registry + self.factory = factory + self.environment = environment + self.now = now + self.accountDirectoryRemover = accountDirectoryRemover ?? { id in + let url = TDClient.databaseDirectory(accountId: id) + try? FileManager.default.removeItem(at: url) + } + self.accounts = [] + self.activeAccountId = nil + } + + /// Loads the registry and brings up the active client (if any). On a + /// truly fresh launch with `TGWATCH_USE_TEST_DC=1` set, seeds and starts + /// a test-DC account so the smoke harness sees QR immediately. All + /// work here is synchronous so callers (and tests) can read post-state + /// without awaiting. + func bootstrap() { + guard activeClient == nil, accounts.isEmpty, activeAccountId == nil else { + logger.warning("bootstrap called twice — ignoring") + return + } + let state: AccountRegistryState + do { + state = try registry.load() + } catch { + logger.warning("registry load failed: \(error.localizedDescription, privacy: .public) — starting empty") + self.accounts = [] + self.activeAccountId = nil + return + } + self.accounts = state.accounts + self.activeAccountId = state.activeAccountId + + if accounts.isEmpty, shouldSeedTestDcAccountFromEnv() { + appendAndStart(account: makeNewAccount(useTestDc: true)) + return + } + + if let id = activeAccountId, let account = accounts.first(where: { $0.id == id }) { + startClient(for: account) + } + } + + /// Ensures at least one account exists by creating and starting a fresh + /// **Production** account when the registry is empty and no client is + /// active. Idempotent — the guard makes repeat calls safe — and + /// synchronous like `bootstrap()` so callers/tests can read post-state + /// without awaiting. Drives the no-welcome auto-QR flow: whenever + /// `activeClient` is nil, `AccountBootstrapView` calls this to land the + /// app on QR login. Persist-failure handling (revert + `lastError`) is + /// inherited from `appendAndStart`. + func ensureAccountExists() { + guard accounts.isEmpty, activeClient == nil else { return } + appendAndStart(account: makeNewAccount(useTestDc: false)) + } + + /// Adds a new account, persists, makes it active, and brings up a + /// fresh `TDClient` against its (empty) database directory. The + /// previous active account (if any) is **closed**, not logged out — + /// its server-side session and on-disk db stay intact. + func addAccount(useTestDc: Bool) async { + await closePreviousIfAny() + appendAndStart(account: makeNewAccount(useTestDc: useTestDc)) + } + + /// Closes the current client and starts the chosen account's client. + /// Same close-not-logOut semantics as `addAccount`. + func switchTo(accountId: UUID) async { + guard accountId != activeAccountId else { return } + guard let account = accounts.first(where: { $0.id == accountId }) else { return } + await closePreviousIfAny() + activeAccountId = account.id + updateLastActive(account.id) + persist() + startClient(for: account) + } + + /// Logs out + removes the on-disk dir for the active account, picks + /// the next-most-recently-active among the survivors, brings it up. + /// Uses `logOut()` (not `close()`) so the server-side session is + /// destroyed for the removed account. + func removeActive() async { + guard let id = activeAccountId else { return } + if let current = activeClient { + await current.logOut() + await current.awaitClosed() + } + activeClient = nil + accounts.removeAll { $0.id == id } + accountDirectoryRemover(id) + if let next = accounts.sorted(by: { $0.lastActiveAt > $1.lastActiveAt }).first { + activeAccountId = next.id + persist() + startClient(for: next) + } else { + activeAccountId = nil + persist() + } + } + + /// Removes an inactive account. No client teardown needed. + func remove(accountId: UUID) async { + guard accountId != activeAccountId else { + await removeActive() + return + } + accounts.removeAll { $0.id == accountId } + accountDirectoryRemover(accountId) + persist() + } + + // MARK: TDClientLifecycleDelegate + + func tdClient(_ client: TDClient, didFetchMe me: User) { + // Guard against stale callbacks from a previously-active client + // (e.g., a getMe in flight when the user switched accounts). + guard client.account.id == activeAccountId else { return } + guard let idx = accounts.firstIndex(where: { $0.id == client.account.id }) else { return } + var updated = accounts[idx] + let first = me.firstName + let last = me.lastName + let combined = [first, last].filter { !$0.isEmpty }.joined(separator: " ") + updated.displayName = combined.isEmpty ? nil : combined + updated.phoneNumber = me.phoneNumber.isEmpty ? nil : me.phoneNumber + updated.userId = Int64(me.id) + accounts[idx] = updated + persist() + } + + func tdClient(_ client: TDClient, didDestroyItselfWithReason reason: TDClientDestroyReason) { + switch reason { + case .stuckLoggingOut: + // Identity-check: the watchdog can fire after switchTo already moved + // on. Only act if the destroyed client is still the active account. + guard client.account.id == activeAccountId else { return } + // Capture target id at delegate time, then re-verify inside the + // Task body: `Task { await ... }` suspends, and activeAccountId + // can flip between scheduling and execution. + let target = client.account.id + Task { [weak self] in + guard let self else { return } + guard self.activeAccountId == target else { return } + await self.removeActive() + } + } + } + + // MARK: Private + + private func startClient(for account: Account) { + let client = factory.make(account: account, delegate: self) + activeClient = client + client.start() + } + + private func closePreviousIfAny() async { + guard let current = activeClient else { return } + await current.close() + await current.awaitClosed() + activeClient = nil + } + + private func makeNewAccount(useTestDc: Bool) -> Account { + Account( + id: UUID(), + useTestDc: useTestDc, + displayName: nil, + phoneNumber: nil, + userId: nil, + createdAt: now(), + lastActiveAt: now() + ) + } + + /// Appends the account, makes it active, persists, and starts its + /// `TDClient`. On persist failure, reverts in-memory state to match + /// the (failed) disk write so the user doesn't see a phantom account + /// that disappears on next launch. The user sees `lastError` in + /// `AccountsListView` / `AccountBootstrapView` and can retry. + private func appendAndStart(account: Account) { + let previousAccounts = accounts + let previousActiveId = activeAccountId + accounts.append(account) + activeAccountId = account.id + guard persist() else { + accounts = previousAccounts + activeAccountId = previousActiveId + return + } + startClient(for: account) + } + + /// Returns true on success, false on save failure. On failure + /// `lastError` is set to a user-facing string. On success `lastError` + /// is cleared so transient hiccups don't show forever. + @discardableResult + private func persist() -> Bool { + let state = AccountRegistryState(version: 1, activeAccountId: activeAccountId, accounts: accounts) + do { + try registry.save(state) + lastError = nil + return true + } catch { + logger.warning("registry save failed: \(error.localizedDescription, privacy: .public)") + lastError = "Couldn't save account list." + return false + } + } + + private func updateLastActive(_ id: UUID) { + guard let idx = accounts.firstIndex(where: { $0.id == id }) else { return } + accounts[idx].lastActiveAt = now() + } + + private func shouldSeedTestDcAccountFromEnv() -> Bool { +#if DEBUG + return environment["TGWATCH_USE_TEST_DC"] == "1" +#else + return false +#endif + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountRegistry.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountRegistry.swift new file mode 100644 index 0000000000..78637711c2 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountRegistry.swift @@ -0,0 +1,62 @@ +import Foundation +import OSLog + +/// On-disk shape of `accounts.json`. The single source of truth for which +/// accounts exist and which is active. TDLib state lives inside each +/// account's own subdirectory and is never duplicated here. +struct AccountRegistryState: Codable, Equatable { + var version: Int + var activeAccountId: UUID? + var accounts: [Account] + + static let empty = AccountRegistryState(version: 1, activeAccountId: nil, accounts: []) +} + +/// Pure persistence layer for the account registry. I/O is closure-injected +/// so unit tests stay off-disk; the production wiring writes to +/// `applicationSupport/tdlib/accounts.json` atomically. +struct AccountRegistry { + let readData: () throws -> Data + let writeData: (Data, Data.WritingOptions) throws -> Void + + private static let logger = Logger(subsystem: "com.isaac.tgwatch", category: "accounts") + + /// Returns the empty state if the file is absent or corrupt. The smaller + /// recovery surface keeps a single bad write from bricking the app. + func load() throws -> AccountRegistryState { + let data: Data + do { + data = try readData() + } catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileReadNoSuchFileError { + // File absent on first run — not an error condition. + return .empty + } + // Any other read error (permission denied, I/O failure) propagates to the + // caller — AccountManager.bootstrap() decides how to surface it. + do { + return try JSONDecoder().decode(AccountRegistryState.self, from: data) + } catch { + Self.logger.warning("accounts.json decode failed: \(error.localizedDescription, privacy: .public) — treating as empty") + return .empty + } + } + + func save(_ state: AccountRegistryState) throws { + let data = try JSONEncoder().encode(state) + try writeData(data, [.atomic]) + } + + /// Production wiring: reads/writes `/tdlib/accounts.json`. + static func defaultProduction() -> AccountRegistry { + let baseDir = FileManager.default + .urls(for: .applicationSupportDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("tdlib", isDirectory: true) + try? FileManager.default.createDirectory(at: baseDir, withIntermediateDirectories: true) + let fileURL = baseDir.appendingPathComponent("accounts.json") + return AccountRegistry( + readData: { try Data(contentsOf: fileURL) }, + writeData: { data, options in try data.write(to: fileURL, options: options) } + ) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountSwitcherSheet.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountSwitcherSheet.swift new file mode 100644 index 0000000000..16431a3ca6 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountSwitcherSheet.swift @@ -0,0 +1,107 @@ +import SwiftUI + +/// Where `AccountsListView` should appear from. Sheets layer on top of the +/// current view; `.push` requires the caller to own a `NavigationStack`. +enum AccountSwitcherPresentation { + case sheet + case push +} + +/// Whether "Log out current" should appear in the action sheet. The chat +/// list passes `.allowed` (authState == .ready); every auth view passes +/// `.suppressed` because there's no signed-in session to log out from. +enum AccountSwitcherLogoutAffordance { + case allowed + case suppressed +} + +private struct AccountSwitcherSheetModifier: ViewModifier { + let presentation: AccountSwitcherPresentation + let logoutAffordance: AccountSwitcherLogoutAffordance + + @Environment(AccountManager.self) private var manager + @State private var showActionSheet = false + @State private var showLogoutConfirm = false + @State private var showAccountsList = false + + func body(content: Content) -> some View { + // `.simultaneousGesture(LongPressGesture(...))` instead of + // `.onLongPressGesture { ... }` so taps on interactive children + // (e.g. `SecureField` in `PasswordEntryView`, the row buttons in + // `ChatListView`'s List) keep working. The `.onLongPressGesture` + // form swallows tap delivery to descendants; the simultaneous + // form lets both gestures fire independently. + content + .simultaneousGesture( + LongPressGesture(minimumDuration: 0.6) + .onEnded { _ in showActionSheet = true } + ) + .confirmationDialog( + "Accounts", + isPresented: $showActionSheet, + titleVisibility: .hidden + ) { + Button("Switch account…") { + showAccountsList = true + } + if logoutAffordance == .allowed { + Button("Log out current", role: .destructive) { + showLogoutConfirm = true + } + } + Button("Cancel", role: .cancel) {} + } + .confirmationDialog( + "Log out?", + isPresented: $showLogoutConfirm, + titleVisibility: .visible + ) { + Button("Log out", role: .destructive) { + Task { await manager.removeActive() } + } + Button("Cancel", role: .cancel) {} + } + .modifier( + AccountsListPresenter( + presentation: presentation, + isPresented: $showAccountsList + ) + ) + } +} + +/// Inner modifier whose job is to pick `.sheet` vs. `.navigationDestination`. +/// Splitting it out keeps `AccountSwitcherSheetModifier.body` readable. +private struct AccountsListPresenter: ViewModifier { + let presentation: AccountSwitcherPresentation + @Binding var isPresented: Bool + + func body(content: Content) -> some View { + switch presentation { + case .sheet: + content.sheet(isPresented: $isPresented) { + NavigationStack { + AccountsListView() + } + } + case .push: + content.navigationDestination(isPresented: $isPresented) { + AccountsListView() + } + } + } +} + +extension View { + /// Adds the multi-account long-press affordance. Pass `.suppressed` for + /// `logoutAffordance` on any view where no signed-in session exists yet. + func accountSwitcherSheet( + presentation: AccountSwitcherPresentation, + logoutAffordance: AccountSwitcherLogoutAffordance + ) -> some View { + modifier(AccountSwitcherSheetModifier( + presentation: presentation, + logoutAffordance: logoutAffordance + )) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountsListView.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountsListView.swift new file mode 100644 index 0000000000..b5994e6d38 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/AccountsListView.swift @@ -0,0 +1,138 @@ +import SwiftUI + +struct AccountsListView: View { + @Environment(AccountManager.self) private var manager + @Environment(\.dismiss) private var dismiss + + @State private var showAddSheet = false + @State private var pendingRemoval: UUID? + + private var rows: [AccountListRow] { + AccountListProjection.rows( + accounts: manager.accounts, + activeAccountId: manager.activeAccountId + ) + } + + var body: some View { + List { + if let err = manager.lastError { + Section { + Text(err) + .font(.caption2) + .foregroundStyle(.red) + .accessibilityIdentifier("accountManagerError") + } + } + ForEach(rows) { row in + accountRow(row) + .accessibilityIdentifier("accountRow.\(row.id.uuidString)") + } + Section { + HStack { + Text("+ Add account") + Spacer() + } + .contentShape(Rectangle()) + .onTapGesture { + Task { + await manager.addAccount(useTestDc: false) + dismiss() + } + } + .onLongPressGesture(minimumDuration: 0.5) { showAddSheet = true } + .accessibilityIdentifier("addAccountButton") + } + } + .navigationTitle("Accounts") + .navigationBarTitleDisplayMode(.inline) + .confirmationDialog( + "Choose server", + isPresented: $showAddSheet, + titleVisibility: .visible + ) { + Button("Production") { + Task { + await manager.addAccount(useTestDc: false) + dismiss() + } + } + Button("Test (developers)") { + Task { + await manager.addAccount(useTestDc: true) + dismiss() + } + } + Button("Cancel", role: .cancel) {} + } + .confirmationDialog( + "Remove this account?", + isPresented: removalBinding, + titleVisibility: .visible + ) { + Button("Remove", role: .destructive) { + if let id = pendingRemoval { + Task { + if id == manager.activeAccountId { + await manager.removeActive() + } else { + await manager.remove(accountId: id) + } + } + } + pendingRemoval = nil + } + Button("Cancel", role: .cancel) { pendingRemoval = nil } + } + } + + private var removalBinding: Binding { + Binding( + get: { pendingRemoval != nil }, + set: { if !$0 { pendingRemoval = nil } } + ) + } + + @ViewBuilder + private func accountRow(_ row: AccountListRow) -> some View { + // Plain HStack + explicit gestures. A SwiftUI `Button` paired with + // `onLongPressGesture` swallows the long press on watchOS — the + // button's tap fires on touch-down regardless of duration. Splitting + // tap and long-press into peer gestures lets both fire independently. + HStack(spacing: 8) { + Image(systemName: row.isActive ? "circle.inset.filled" : "circle") + .foregroundStyle(row.isActive ? Color.accentColor : .secondary) + VStack(alignment: .leading, spacing: 2) { + Text(row.label) + .font(.body) + .lineLimit(1) + if row.showsTestPill { + Text("TEST") + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.yellow.opacity(0.25), in: Capsule()) + .foregroundStyle(.yellow) + .accessibilityIdentifier("dcTestBadge") + } + } + Spacer() + if row.isActive { + Text("active") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + .contentShape(Rectangle()) + .onTapGesture { + guard row.id != manager.activeAccountId else { return } + Task { + await manager.switchTo(accountId: row.id) + dismiss() + } + } + .onLongPressGesture(minimumDuration: 0.5) { + pendingRemoval = row.id + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Accounts/TDClientFactory.swift b/Telegram/WatchApp/tgwatch Watch App/Accounts/TDClientFactory.swift new file mode 100644 index 0000000000..6fcbfbc24d --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Accounts/TDClientFactory.swift @@ -0,0 +1,33 @@ +import Foundation +import TDShim + +/// Builds a `TDClient` for a given account. Abstracted so `AccountManager` +/// can be unit-tested with a stub factory that never touches TDLib. +@MainActor +protocol TDClientFactory { + func make(account: Account, delegate: TDClientLifecycleDelegate) -> TDClient +} + +/// Production factory: passes the shared `TDLibClientManager` through to +/// every `TDClient` it creates. One manager per process is the documented +/// rule; we honor it by giving everyone the same instance. +@MainActor +struct LiveTDClientFactory: TDClientFactory { + let manager: TDLibClientManager + + func make(account: Account, delegate: TDClientLifecycleDelegate) -> TDClient { + TDClient(account: account, manager: manager, delegate: delegate) + } +} + +/// No-op factory used by `TgwatchApp` when running under XCTest. The test +/// process loads the app target so `@main` `init()` still fires, but tests +/// don't drive the SwiftUI scene — so `make` is never called. Allocating a +/// `TDLibClientManager` at all in the test process would conflict with the +/// one tests create themselves (TDLib's `td_receive` is single-thread-global). +@MainActor +struct NoopTDClientFactory: TDClientFactory { + func make(account: Account, delegate: TDClientLifecycleDelegate) -> TDClient { + preconditionFailure("NoopTDClientFactory.make called — production code should not see this factory") + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..7be50ecb19 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.925", + "green" : "0.565", + "red" : "0.200" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/AppIcon.png b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000000..a927ff5a6b Binary files /dev/null and b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..74cc72597d --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/ChatBG.png b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/ChatBG.png new file mode 100644 index 0000000000..5b17fa7ff6 Binary files /dev/null and b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/ChatBG.png differ diff --git a/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/Contents.json b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/Contents.json new file mode 100644 index 0000000000..9c698680c9 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/ChatBG.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "ChatBG.png", + "idiom" : "universal", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/Contents.json b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/Contents.json similarity index 52% rename from submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/Contents.json rename to Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/Contents.json index 6e965652df..73c00596a7 100644 --- a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/Contents.json +++ b/Telegram/WatchApp/tgwatch Watch App/Assets.xcassets/Contents.json @@ -2,8 +2,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "provides-namespace" : true } } diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/ClosedView.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/ClosedView.swift new file mode 100644 index 0000000000..66c89639ea --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/ClosedView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +struct ClosedView: View { + var body: some View { + VStack(spacing: 8) { + Text("Logged out") + .font(.headline) + Text("Relaunch the app to sign in again.") + .font(.footnote) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + .padding() + .accountSwitcherSheet(presentation: .sheet, logoutAffordance: .suppressed) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/ErrorView.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/ErrorView.swift new file mode 100644 index 0000000000..656ad7a930 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/ErrorView.swift @@ -0,0 +1,29 @@ +import SwiftUI + +struct ErrorView: View { + @Environment(TDClient.self) private var client + let message: String + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Something went wrong") + .font(.headline) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 8) + if client.useTestDc { + Text("TEST") + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.yellow.opacity(0.25), in: Capsule()) + .foregroundStyle(.yellow) + .accessibilityIdentifier("dcTestBadge") + } + Text(message) + .font(.footnote) + .foregroundStyle(.secondary) + } + .padding() + .accountSwitcherSheet(presentation: .sheet, logoutAffordance: .suppressed) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/LoadingView.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/LoadingView.swift new file mode 100644 index 0000000000..73ad666633 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/LoadingView.swift @@ -0,0 +1,15 @@ +import SwiftUI + +struct LoadingView: View { + let label: String + var body: some View { + VStack(spacing: 8) { + ProgressView() + Text(label) + .font(.footnote) + .foregroundStyle(.secondary) + } + .padding() + .accountSwitcherSheet(presentation: .sheet, logoutAffordance: .suppressed) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/PasswordEntryView.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/PasswordEntryView.swift new file mode 100644 index 0000000000..36b26e4551 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/PasswordEntryView.swift @@ -0,0 +1,111 @@ +import SwiftUI + +struct PasswordEntryView: View { + let info: PasswordInfo + + @Environment(TDClient.self) private var client + @State private var password = "" + @State private var submitting = false + @State private var errorMessage: String? + + /// Drives the error `.alert`: presented whenever `errorMessage` is set, + /// and clears it on dismiss. Mirrors the optional-state→Bool binding + /// pattern used by `AccountsListView`. + private var errorAlertBinding: Binding { + Binding( + get: { errorMessage != nil }, + set: { if !$0 { errorMessage = nil } } + ) + } + + var body: some View { + NavigationStack { + ScrollView { + VStack(alignment: .leading, spacing: 8) { + if !info.hint.isEmpty { + Text("Hint: \(info.hint)") + .font(.caption) + .foregroundStyle(.secondary) + } else { + Text("Enter your cloud password") + .font(.caption) + .foregroundStyle(.secondary) + } + SecureField("Password", text: $password) + .textContentType(.password) + .accessibilityIdentifier("passwordField") + Button(action: submit) { + Group { + if submitting { + ProgressView() + } else { + Text("Continue") + } + } + .frame(maxWidth: .infinity, minHeight: 36) + .padding(.vertical, 6) + .background(.fill.tertiary, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) + } + .buttonStyle(.plain) + .accessibilityIdentifier("passwordContinue") + } + .padding() + } + .navigationTitle("Password") + .navigationBarTitleDisplayMode(.inline) + // Force the nav bar to materialize so the title renders under the + // clock — watchOS-26 skips chrome on a NavigationStack root view + // otherwise (same reason as ChatListView). + .toolbar(.visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button { + returnToQr() + } label: { + Image(systemName: "chevron.backward") + } + .disabled(submitting) + .accessibilityIdentifier("passwordBackToQr") + } + } + .alert(errorMessage ?? "", isPresented: errorAlertBinding) { + Button("OK", role: .cancel) { errorMessage = nil } + } + .onAppear { +#if DEBUG + if password.isEmpty, + let preset = ProcessInfo.processInfo.environment["TGWATCH_PASSWORD"], + !preset.isEmpty { + password = preset + submit() + } +#endif + } + .accountSwitcherSheet(presentation: .sheet, logoutAffordance: .suppressed) + } + } + + /// Returns to QR code entry. Blocked while a password check is in flight + /// (TDLib rejects `requestQrCodeAuthentication` with a pending auth query). + /// On success TDLib transitions to `waitOtherDeviceConfirmation` and this + /// view is torn down; on failure the message lands in the existing alert. + private func returnToQr() { + guard !submitting else { return } + Task { + errorMessage = await client.returnToQrCode() + } + } + + private func submit() { + // Button is always painted enabled; ignore taps when there's nothing + // to submit or a submission is already in flight. + guard !password.isEmpty, !submitting else { return } + let passwordCopy = password + submitting = true + Task { + let error = await client.submitPassword(passwordCopy) + submitting = false + errorMessage = error + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/QrLinkPublisher.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/QrLinkPublisher.swift new file mode 100644 index 0000000000..78f053f438 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/QrLinkPublisher.swift @@ -0,0 +1,83 @@ +import Foundation + +/// Stale-data-resistant envelope written to `Library/Caches/qr-link.json` (DEBUG only) +/// so the populate CLI can read the QR link without screen-scraping the watch sim. +/// +/// Field-name contract is shared with the CLI; do not rename without updating +/// `tools/tgwatch-populate/Sources/tgwatch-populate/QrLogin.swift`. +struct QrLinkEnvelope: Codable, Equatable { + /// Raw `tg://login?token=…` string from TDLib. Not URL-encoded further. + let link: String + /// Mirrors `TDClient.useTestDc` so the CLI can refuse a prod-DC envelope it + /// cannot confirm (CLI populate accounts are test-DC only). + let useTestDc: Bool + /// Per-process UUID generated at `TDClient` init. Lets the CLI detect a + /// watch restart mid-poll. + let sessionId: String + /// Unix epoch seconds (fractional OK). CLI rejects entries older than ~30s. + let writtenAt: TimeInterval +} + +/// Owns the on-disk lifecycle of `qr-link.json`. All I/O is injected so unit +/// tests run without touching the real watch container. +struct QrLinkPublisher { + /// `nil` → disabled (release builds, or any path where there's no caches dir). + let writeURL: URL? + let now: () -> Foundation.Date + let writeData: (Data, URL) throws -> Void + let removeItem: (URL) throws -> Void + + /// No-op publisher used by release builds and disabled tests. + static let disabled = QrLinkPublisher( + writeURL: nil, + now: { Foundation.Date() }, + writeData: { _, _ in }, + removeItem: { _ in } + ) + + /// Production wiring: writes to `/qr-link.json` atomically. Returns a + /// disabled publisher in release builds so the file never appears in users' caches. + static func defaultProduction() -> QrLinkPublisher { +#if DEBUG + guard let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { + return .disabled + } + let url = cachesURL.appendingPathComponent("qr-link.json") + return QrLinkPublisher( + writeURL: url, + now: { Foundation.Date() }, + writeData: { data, dest in + // .atomic writes via a temp + rename, so a partial read is impossible. + try data.write(to: dest, options: [.atomic]) + }, + removeItem: { url in + if FileManager.default.fileExists(atPath: url.path) { + try FileManager.default.removeItem(at: url) + } + } + ) +#else + return .disabled +#endif + } + + /// Encodes an envelope and writes it. Silently ignores write errors — the + /// envelope is best-effort smoke-test plumbing, not load-bearing. + func publish(link: String, useTestDc: Bool, sessionId: UUID) { + guard let writeURL else { return } + let envelope = QrLinkEnvelope( + link: link, + useTestDc: useTestDc, + sessionId: sessionId.uuidString, + writtenAt: now().timeIntervalSince1970 + ) + guard let data = try? JSONEncoder().encode(envelope) else { return } + try? writeData(data, writeURL) + } + + /// Removes the envelope file if present. Safe to call when the file doesn't exist. + func clear() { + guard let writeURL else { return } + try? removeItem(writeURL) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Auth/QrLoginView.swift b/Telegram/WatchApp/tgwatch Watch App/Auth/QrLoginView.swift new file mode 100644 index 0000000000..5fe1485491 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Auth/QrLoginView.swift @@ -0,0 +1,152 @@ +import CoreGraphics +import QRCodeGenerator +import SwiftUI + +struct QrLoginView: View { + let link: String + + @Environment(TDClient.self) private var client + + var body: some View { + ScrollView { + QrLoginContent( + link: link, + isTestDc: client.useTestDc, + errorMessage: client.lastError + ) + } + .accountSwitcherSheet(presentation: .sheet, logoutAffordance: .suppressed) + } +} + +/// Pure presentational column for the QR-login screen. Hosted inside a `ScrollView` by +/// `QrLoginView`; rendered directly (at a fixed width) by the previews so it captures in +/// SnapshotPreviews, which does not flatten scroll-container content. +struct QrLoginContent: View { + let link: String + let isTestDc: Bool + let errorMessage: String? + + var body: some View { + VStack(spacing: 12) { + Text("Log in to Telegram by QR Code") + .font(.headline) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + + if isTestDc { + Text("TEST") + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Color.yellow.opacity(0.25), in: Capsule()) + .foregroundStyle(.yellow) + .accessibilityIdentifier("dcTestBadge") + } + + instructionSteps + + qrCard + + if let errorMessage { + Text(errorMessage) + .font(.footnote) + .foregroundStyle(.red) + .multilineTextAlignment(.center) + .accessibilityIdentifier("qrError") + } + } + .padding() + } + + private var instructionSteps: some View { + VStack(spacing: 4) { + Text("Settings on your Phone") + Image(systemName: "chevron.down").font(.caption) + Text("Devices") + Image(systemName: "chevron.down").font(.caption) + Text("Scan QR") + } + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity) + } + + @ViewBuilder + private var qrCard: some View { + Group { + if let cg = Self.qrCGImage(for: link) { + Image(decorative: cg, scale: 1.0, orientation: .up) + .resizable() + .interpolation(.none) + .scaledToFit() + .accessibilityIdentifier("qrImage") + .id(link) + } else { + Text("Could not render QR code") + .font(.footnote) + .foregroundStyle(.red) + .frame(maxWidth: .infinity, minHeight: 120) + } + } + .padding(12) + .background(Color.white, in: RoundedRectangle(cornerRadius: 16)) + } + + /// Renders the link as a QR matrix via the swift-qrcode-generator package, then builds a + /// grayscale CGImage with a 4-module white quiet zone (ISO 18004 minimum). Returns nil if + /// encoding fails. + static func qrCGImage(for link: String) -> CGImage? { + guard let qr = try? QRCode.encode(text: link, ecl: .medium) else { return nil } + let qrSize = qr.size + let quietZone = 4 + let size = qrSize + 2 * quietZone + var pixels = [UInt8](repeating: 0xFF, count: size * size) + for row in 0.. AuthState { + switch s { + case .authorizationStateWaitTdlibParameters: + return .waitTdlibParameters + case .authorizationStateWaitPhoneNumber: + return .waitPhoneNumber + case .authorizationStateWaitCode(let payload): + return .waitCode(info: codeInfo(from: payload.codeInfo)) + case .authorizationStateReady: + return .ready + case .authorizationStateLoggingOut: + return .loggingOut + case .authorizationStateClosing: + return .loggingOut + case .authorizationStateClosed: + return .closed + case .authorizationStateWaitPassword(let payload): + return .waitPassword(info: PasswordInfo( + hint: payload.passwordHint, + hasRecoveryEmail: payload.hasRecoveryEmailAddress + )) + case .authorizationStateWaitEmailAddress, .authorizationStateWaitEmailCode: + return .failed("Email-based login isn't supported in this build yet.") + case .authorizationStateWaitOtherDeviceConfirmation(let payload): + return .waitOtherDeviceConfirmation(link: payload.link) + case .authorizationStateWaitRegistration: + return .failed("Registration is not supported with QR login.") + case .authorizationStateWaitPremiumPurchase: + return .failed("Premium-purchase login isn't supported in this build yet.") + case .unsupported: + return .failed("Unsupported authorization state.") + } +} + +private func codeInfo(from info: AuthenticationCodeInfo) -> CodeInfo { + CodeInfo( + phoneNumber: info.phoneNumber, + codeLength: codeLength(from: info.type), + typeDescription: typeDescription(from: info.type) + ) +} + +private func codeLength(from type: AuthenticationCodeType) -> Int? { + switch type { + case .authenticationCodeTypeSms(let p): return p.length + case .authenticationCodeTypeTelegramMessage(let p): return p.length + case .authenticationCodeTypeCall(let p): return p.length + case .authenticationCodeTypeMissedCall(let p): return p.length + default: return nil + } +} + +private func typeDescription(from type: AuthenticationCodeType) -> String { + switch type { + case .authenticationCodeTypeSms: return "SMS" + case .authenticationCodeTypeTelegramMessage: return "Telegram message" + case .authenticationCodeTypeCall: return "Phone call" + case .authenticationCodeTypeMissedCall: return "Missed call" + case .authenticationCodeTypeFlashCall: return "Flash call" + case .authenticationCodeTypeFragment: return "Fragment" + case .authenticationCodeTypeFirebaseAndroid, .authenticationCodeTypeFirebaseIos: return "App push" + case .authenticationCodeTypeSmsWord, .authenticationCodeTypeSmsPhrase: return "SMS" + case .unsupported: return "Code" + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift new file mode 100644 index 0000000000..02802ddc40 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarView.swift @@ -0,0 +1,80 @@ +import SwiftUI +import UIKit + +/// A circular chat avatar (default 36pt, customizable via `size`). Renders (priority): +/// crisp downloaded photo; blurred minithumbnail placeholder; initials on a +/// per-chat color. When embedded in a `ScrollView`, drives viewport-based download +/// of the small photo via `.onScrollVisibilityChange` (same as `PhotoBubbleView`) — +/// only while the photo exists and hasn't downloaded yet. Outside a scroll +/// container (e.g. as a `ToolbarItem`), the visibility callback fires once on +/// appear and once on disappear, so the download path is "request on chat open, +/// cancel on chat close." +struct AvatarView: View { + let avatar: AvatarVisual + var onRequestDownload: (Int) -> Void = { _ in } + var onCancelDownload: (Int) -> Void = { _ in } + + var size: CGFloat = 36 + + var body: some View { + content + .frame(width: size, height: size) + .clipShape(Circle()) + .onScrollVisibilityChange(threshold: 0.01) { visible in + guard let fileId = avatar.photoFileId, avatar.photoLocalPath == nil else { return } + if visible { + onRequestDownload(fileId) + } else { + onCancelDownload(fileId) + } + } + } + + @ViewBuilder + private var content: some View { + switch avatar.kind { + case .savedMessages: + ZStack { + Circle().fill(Color.accentColor) + Image(systemName: "bookmark.fill") + .font(.system(size: size * 0.42)) + .foregroundStyle(.white) + } + case .normal: + if let path = avatar.photoLocalPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFill() + } else if let data = avatar.mini, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 1.5) + } else { + ZStack { + Circle().fill(avatarPalette[avatar.colorIndex]) + Text(avatar.initials) + .font(.system(size: size * 0.4, weight: .semibold)) + .foregroundStyle(.white) + } + } + } + } +} + +#if DEBUG +#Preview("Avatar — initials") { + AvatarView(avatar: AvatarVisual(kind: .normal, initials: "JS", colorIndex: 0, photoFileId: nil, photoLocalPath: nil, mini: nil)) + .padding() +} + +#Preview("Avatar — single initial") { + AvatarView(avatar: AvatarVisual(kind: .normal, initials: "T", colorIndex: 5, photoFileId: nil, photoLocalPath: nil, mini: nil)) + .padding() +} + +#Preview("Avatar — saved messages") { + AvatarView(avatar: AvatarVisual(kind: .savedMessages, initials: "", colorIndex: 0, photoFileId: nil, photoLocalPath: nil, mini: nil)) + .padding() +} + +#Preview("Avatar — empty title") { + AvatarView(avatar: AvatarVisual(kind: .normal, initials: "", colorIndex: 3, photoFileId: nil, photoLocalPath: nil, mini: nil)) + .padding() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarVisual.swift new file mode 100644 index 0000000000..7781b5a42d --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/AvatarVisual.swift @@ -0,0 +1,111 @@ +import Foundation +import SwiftUI +import TDShim + +/// Distinguishes the Saved-Messages bookmark glyph from a normal chat avatar. +enum AvatarKind: Equatable, Hashable { + case savedMessages + case normal +} + +/// View-ready avatar projection carried on `ChatRow`. Holds no `Color` (stores a +/// palette index) so it stays trivially `Equatable`/`Hashable` and unit-testable. +struct AvatarVisual: Equatable, Hashable { + var kind: AvatarKind + /// 1-2 letters from the chat title; "" for `.savedMessages` or an empty title. + var initials: String + /// Index into `avatarPalette`, deterministic per chat id. + var colorIndex: Int + /// The `small` (160px) photo file id; nil when the chat has no photo. + var photoFileId: Int? + /// Set once the `small` file download has completed. + var photoLocalPath: String? + /// Instant minithumbnail JPEG bytes; nil when the chat has no photo. + var mini: Data? +} + +/// Telegram-ish 7-color palette. Single source of truth for both the projection +/// (modulo) and `AvatarView` (index → color). Index safety in `AvatarView` relies +/// on `avatarColorIndex` using this same `count`. +let avatarPalette: [Color] = [ + Color(red: 0.90, green: 0.34, blue: 0.34), // red + Color(red: 0.94, green: 0.61, blue: 0.20), // orange + Color(red: 0.55, green: 0.45, blue: 0.86), // purple + Color(red: 0.40, green: 0.73, blue: 0.42), // green + Color(red: 0.30, green: 0.70, blue: 0.78), // cyan + Color(red: 0.36, green: 0.56, blue: 0.90), // blue + Color(red: 0.90, green: 0.46, blue: 0.62), // pink +] + +/// First extended-grapheme cluster of up to the first two whitespace-separated +/// words, uppercased. Empty/whitespace-only title → "". +func avatarInitials(from title: String) -> String { + let words = title.split(whereSeparator: { $0.isWhitespace }) + guard let first = words.first?.first else { return "" } + var result = String(first) + if words.count > 1, let second = words[1].first { + result.append(second) + } + return result.uppercased() +} + +/// Deterministic palette index in `0.. Int { + let count = Int64(avatarPalette.count) + return Int(((id % count) + count) % count) +} + +/// Deterministic palette index for a peer that has only a display name and no stable +/// id (e.g. a hidden-forward author). FNV-1a over the name's UTF-8 bytes, reduced +/// modulo the palette count — stable across process runs, unlike `String.hashValue`. +func paletteIndex(forName name: String) -> Int { + var hash: UInt64 = 14695981039346656037 // FNV-1a 64-bit offset basis (0xcbf29ce484222325) + for byte in name.utf8 { + hash ^= UInt64(byte) + hash = hash &* 1099511628211 // FNV-1a 64-bit prime (0x100000001b3) + } + return Int(hash % UInt64(avatarPalette.count)) +} + +/// Per-chat avatar fallback color index. Kept as a named alias so avatar call sites +/// and their tests stay expressive; delegates to the generic `paletteIndex(for:)`. +func avatarColorIndex(forChatId id: Int64) -> Int { + paletteIndex(for: id) +} + +/// Projects a cached chat into a view-ready `AvatarVisual`. +/// - Saved Messages (private chat with our own user id) → `.savedMessages`. +/// - Otherwise `.normal` with initials/color always populated; `photoLocalPath` +/// resolves from `fileLocals` only when the small file download has completed. +func avatarVisual(for chat: CachedChat, fileLocals: [Int: File], selfUserId: Int64?) -> AvatarVisual { + if let selfUserId, + case .chatTypePrivate(let p) = chat.type, + p.userId == selfUserId { + return AvatarVisual( + kind: .savedMessages, + initials: "", + colorIndex: avatarColorIndex(forChatId: chat.id), + photoFileId: nil, + photoLocalPath: nil, + mini: nil + ) + } + + var localPath: String? = nil + if let fileId = chat.avatarSmallFileId, + let file = fileLocals[fileId], + file.local.isDownloadingCompleted, + !file.local.path.isEmpty { + localPath = file.local.path + } + + return AvatarVisual( + kind: .normal, + initials: avatarInitials(from: chat.title), + colorIndex: avatarColorIndex(forChatId: chat.id), + photoFileId: chat.avatarSmallFileId, + photoLocalPath: localPath, + mini: chat.avatarMini + ) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/CachedChat.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/CachedChat.swift new file mode 100644 index 0000000000..8fe0afb8e4 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/CachedChat.swift @@ -0,0 +1,98 @@ +import Foundation +import TDShim + +/// Local discriminator for outgoing-message lifecycle. Incoming messages are always `.sent`. +enum SendingState: Equatable, Hashable { + case sent + case pending + case failed +} + +extension SendingState { + init(tdLibState: MessageSendingState?) { + switch tdLibState { + case nil, .unsupported: + self = .sent + case .messageSendingStatePending: + self = .pending + case .messageSendingStateFailed: + self = .failed + } + } +} + +/// Cache shape for a chat in `ChatListStore`. Holds only what the chat-list view renders +/// plus the fields the store mutates as TDLib updates arrive. +struct CachedChat: Equatable { + let id: Int64 + var title: String + var lastMessage: CachedMessage? + var unreadCount: Int + var lastReadInboxMessageId: Int64 + var lastReadOutboxMessageId: Int64 + /// Seconds until unmute. `0` = unmuted; large value = muted. + var muteFor: Int + var positions: [ChatPosition] + /// Plain-text body of the local draft, if any. Non-text drafts collapse to `""`. + var draftText: String? + let type: ChatType + var permissions: ChatPermissions + /// `ChatPhotoInfo.small` file id (160px). nil when the chat has no photo. + var avatarSmallFileId: Int? = nil + /// `ChatPhotoInfo.minithumbnail` JPEG bytes — instant placeholder, no download. + var avatarMini: Data? = nil +} + +/// Reduced shape of `Message` carrying the fields the chat-list preview reads +/// plus the fields the message-history view needs (id for cache key + sort, date for +/// timestamps + day separators, editDate captured for a future "edited" indicator). +struct CachedMessage: Equatable { + let id: Int64 + let date: Int + let editDate: Int + let isOutgoing: Bool + let senderId: MessageSender + let content: MessageContent + let sendingState: SendingState + let replyTo: MessageReplyTo? +} + +extension CachedChat { + /// Snapshots a TDLib `Chat` into a `CachedChat`. + init(_ chat: Chat) { + self.id = chat.id + self.title = chat.title + self.lastMessage = chat.lastMessage.map(CachedMessage.init) + self.unreadCount = chat.unreadCount + self.lastReadInboxMessageId = chat.lastReadInboxMessageId + self.lastReadOutboxMessageId = chat.lastReadOutboxMessageId + self.muteFor = chat.notificationSettings.muteFor + self.positions = chat.positions + self.draftText = CachedChat.extractDraftText(chat.draftMessage) + self.type = chat.type + self.permissions = chat.permissions + self.avatarSmallFileId = chat.photo?.small.id + self.avatarMini = chat.photo?.minithumbnail?.data + } + + static func extractDraftText(_ draft: DraftMessage?) -> String? { + guard let draft else { return nil } + if case .inputMessageText(let inputText) = draft.inputMessageText { + return inputText.text.text + } + return "" + } +} + +extension CachedMessage { + init(_ message: Message) { + self.id = message.id + self.date = message.date + self.editDate = message.editDate + self.isOutgoing = message.isOutgoing + self.senderId = message.senderId + self.content = message.content + self.sendingState = SendingState(tdLibState: message.sendingState) + self.replyTo = message.replyTo + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListKey.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListKey.swift new file mode 100644 index 0000000000..a1f70db672 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListKey.swift @@ -0,0 +1,26 @@ +import TDShim + +/// Hashable identity for a TDLib `ChatList`. We avoid using `ChatList` directly as a +/// dictionary key because its `Hashable`/`Equatable` conformance may not synthesize +/// cleanly across TDLibKit versions (see CLAUDE.md gotchas). +enum ChatListKey: Hashable { + case main + case archive + case folder(Int) + /// A `ChatList` variant TDShim's forward-compatible decoder didn't recognize. + /// Never expected in practice; kept distinct so it can't collide with `.main`. + case unsupported + + init(_ list: ChatList) { + switch list { + case .chatListMain: + self = .main + case .chatListArchive: + self = .archive + case .chatListFolder(let f): + self = .folder(f.chatFolderId) + case .unsupported: + self = .unsupported + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListLoader.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListLoader.swift new file mode 100644 index 0000000000..9d4730c876 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListLoader.swift @@ -0,0 +1,36 @@ +import TDShim + +/// Abstraction over the TDLib requests `ChatListStore` needs, so the store +/// can be exercised in tests with a no-op or scripted loader. +protocol ChatListLoader: Sendable { + /// Asks TDLib to surface up to `limit` more chats from `chatList`. TDLib responds by + /// emitting `updateNewChat` or `updateChatAddedToList` events for any newly-surfaced + /// chats. When TDLib has nothing more to surface, this method throws + /// `TDError` with `code == 404`. + func loadChats(chatList: ChatList, limit: Int) async throws + /// Asks TDLib to download `fileId`. Returns immediately (synchronous=false); + /// progress + completion stream back via `updateFile`. + func downloadFile(fileId: Int, priority: Int) async throws -> File + /// Cancels an in-flight or pending download. + func cancelDownloadFile(fileId: Int) async throws +} + +struct TDLibChatListLoader: ChatListLoader { + let client: TDLibClient + + func loadChats(chatList: ChatList, limit: Int) async throws { + _ = try await client.loadChats(chatList: chatList, limit: limit) + } + + func downloadFile(fileId: Int, priority: Int) async throws -> File { + // synchronous=false → TDLib returns immediately and streams progress via updateFile. + try await client.downloadFile( + fileId: fileId, limit: 0, offset: 0, priority: priority, synchronous: false + ) + } + + func cancelDownloadFile(fileId: Int) async throws { + // onlyIfPending=false → cancel even if active. + _ = try await client.cancelDownloadFile(fileId: fileId, onlyIfPending: false) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListStore.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListStore.swift new file mode 100644 index 0000000000..a90ed43a38 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListStore.swift @@ -0,0 +1,409 @@ +import Foundation +import Observation +import OSLog +import TDShim + +enum LoadState: Equatable { + case notStarted + case loadingFirstPage + case hasMore + case loadingMore + case loaded + case failed(String) +} + +@Observable +@MainActor +final class ChatListStore { + + private(set) var chats: [ChatRow] = [] + private(set) var folders: [ChatFolderInfo] = [] + private var mainChatListPosition: Int = 0 + private(set) var pills: [FolderPill] = [] + private(set) var currentFolder: ChatList = .chatListMain + private var loadStates: [ChatListKey: LoadState] = [.main: .loadingFirstPage] + + /// Public accessor; folders never visited are `.notStarted`. + func loadState(for list: ChatList) -> LoadState { + loadStates[ChatListKey(list)] ?? .notStarted + } + + private var chatCache: [Int64: CachedChat] = [:] + /// Avatar (and future) file downloads. Mirrors `ChatHistoryStore` — the chat + /// list was previously file-free. + private var files: [Int: File] = [:] + private var trackedFileIds: Set = [] + private let userNames: UserNamesStore + /// May be unknown at construction time — `TDClient.fetchMe()` is async and + /// resolves after the `.ready` transition that builds this store. The + /// "Saved Messages" relabel in `ChatRow.project` depends on this being set, + /// so `setSelfUserId` is the late-bind path. + private var selfUserId: Int64? + + private var displayLimits: [ChatListKey: Int] = [:] + + private let loader: ChatListLoader + private let logger = Logger(subsystem: "org.telegram.TelegramWatch", category: "chatlist") + private var loadTasks: [ChatListKey: Task] = [:] + + private let initialDisplayLimit = 30 + private let displayPageSize = 30 + private let scrollLookahead = 5 + + /// When true, `handle(_:)` defers `reproject()` to the next main-runloop + /// turn so a burst of TDLib updates collapses to a single projection. + /// Production (TDClient) opts in; tests default to false for synchronous + /// `handle → assert` ergonomics. + private let coalesceUpdates: Bool + private var reprojectPending = false + + #if DEBUG + /// Number of times `reproject()` has actually run. Test-only hook for + /// asserting that coalescing collapses a burst into one projection. + private(set) var debugReprojectCount: Int = 0 + #endif + + init( + loader: ChatListLoader, + selfUserId: Int64? = nil, + userNames: UserNamesStore? = nil, + coalesceUpdates: Bool = false + ) { + self.loader = loader + self.selfUserId = selfUserId + self.userNames = userNames ?? UserNamesStore() + self.coalesceUpdates = coalesceUpdates + let mainList: ChatList = .chatListMain + loadTasks[.main] = Task { [weak self] in + await self?.loadOnePage(for: mainList) + } + } + + func retry() { + let key = ChatListKey(currentFolder) + guard case .failed = loadStates[key] ?? .notStarted else { return } + let list = currentFolder + loadTasks[key]?.cancel() + loadTasks[key] = Task { [weak self] in + await self?.loadOnePage(for: list) + } + } + + func ensureChatsLoaded(near index: Int) { + let key = ChatListKey(currentFolder) + // Threshold: index near the end of the rendered slice. + guard index >= chats.count - scrollLookahead else { return } + + let currentLimit = displayLimits[key] ?? initialDisplayLimit + let availableInList = countOfChatsInList(currentFolder) + + if currentLimit < availableInList { + // Cache has more than we show. Grow the slice; no TDLib call needed. + displayLimits[key] = currentLimit + displayPageSize + reproject() + return + } + + // Cache is exhausted (limit >= available). Ask TDLib for more if it has any. + guard case .hasMore = loadStates[key] ?? .notStarted else { return } + // Mark .loadingMore synchronously so a same-turn burst collapses. + loadStates[key] = .loadingMore + // Bump the limit too so that when more chats stream in via updateNewChat, + // they'll show up in the projection without another scroll trigger. + displayLimits[key] = currentLimit + displayPageSize + let list = currentFolder + loadTasks[key]?.cancel() + loadTasks[key] = Task { [weak self] in + await self?.loadOnePage(for: list) + } + } + + /// Late-bind for `me?.id` once `TDClient.fetchMe()` returns. Triggers a + /// reproject so the "Saved Messages" relabel takes effect on existing rows. + func setSelfUserId(_ id: Int64?) { + guard selfUserId != id else { return } + selfUserId = id + reproject() + } + + func requestFileDownload(fileId: Int, priority: Int = 1) { + trackedFileIds.insert(fileId) + logger.info("requestFileDownload fileId=\(fileId, privacy: .public) priority=\(priority, privacy: .public)") + Task { [logger, loader] in + do { + _ = try await loader.downloadFile(fileId: fileId, priority: priority) + } catch { + logger.warning("downloadFile fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + func cancelFileDownload(fileId: Int) { + trackedFileIds.remove(fileId) + logger.info("cancelFileDownload fileId=\(fileId, privacy: .public)") + Task { [logger, loader] in + do { + try await loader.cancelDownloadFile(fileId: fileId) + } catch { + logger.warning("cancelDownloadFile fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + /// Seeds the files map from an avatar's `small` file without downgrading an + /// already-completed entry — TDLib re-emits `updateNewChat` on reconnect, and a + /// stale (incomplete) File there would otherwise clobber a resolved local path. + /// Mirrors `ChatHistoryStore.primeFile`. + private func seedAvatarFile(_ file: File) { + if files[file.id]?.local.isDownloadingCompleted == true { return } + files[file.id] = file + } + + func setCurrentFolder(_ list: ChatList) { + let key = ChatListKey(list) + currentFolder = list + reproject() + let state = loadStates[key] ?? .notStarted + if case .notStarted = state { + loadStates[key] = .loadingFirstPage + loadTasks[key] = Task { [weak self] in + await self?.loadOnePage(for: list) + } + } + } + + func handle(_ update: Update) { + diagnosticLog(update) + switch update { + case .updateNewChat(let upd): + chatCache[upd.chat.id] = CachedChat(upd.chat) + if let photo = upd.chat.photo { + seedAvatarFile(photo.small) + } + scheduleReproject() + case .updateChatLastMessage(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.lastMessage = upd.lastMessage.map(CachedMessage.init) + applyPositions(upd.positions, to: &cached) + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatPosition(let upd): + guard var cached = chatCache[upd.chatId] else { return } + applyPositions([upd.position], to: &cached) + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatReadInbox(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.unreadCount = upd.unreadCount + cached.lastReadInboxMessageId = upd.lastReadInboxMessageId + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatReadOutbox(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.lastReadOutboxMessageId = upd.lastReadOutboxMessageId + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatTitle(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.title = upd.title + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatNotificationSettings(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.muteFor = upd.notificationSettings.muteFor + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatPermissions(let upd): + if var c = chatCache[upd.chatId] { + c.permissions = upd.permissions + chatCache[upd.chatId] = c + scheduleReproject() + } + case .updateChatDraftMessage(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.draftText = CachedChat.extractDraftText(upd.draftMessage) + applyPositions(upd.positions, to: &cached) + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateUser: + // UserNamesStore (owned by TDClient) absorbed the update before + // it reached us. Just repaint with the new cache snapshot. + scheduleReproject() + case .updateChatFolders(let upd): + folders = upd.chatFolders + mainChatListPosition = upd.mainChatListPosition + // If the currently-selected folder no longer exists, fall back to Main. + if case .chatListFolder(let f) = currentFolder, + !folders.contains(where: { $0.id == f.chatFolderId }) { + currentFolder = .chatListMain + } + scheduleReproject() + case .updateChatAddedToList(let upd): + guard var cached = chatCache[upd.chatId] else { return } + let key = ChatListKey(upd.chatList) + if !cached.positions.contains(where: { ChatListKey($0.list) == key }) { + // Seed with order 0 and DO NOT reproject — the follow-up updateChatPosition + // (which TDLib always emits right after) overwrites the order via applyPositions + // and triggers the projection. Publishing the order-0 state here would briefly + // sort the chat last before it jumps to its real position. + cached.positions.append(ChatPosition(isPinned: false, list: upd.chatList, order: TdInt64(0), source: nil)) + chatCache[upd.chatId] = cached + } + case .updateChatRemovedFromList(let upd): + guard var cached = chatCache[upd.chatId] else { return } + let key = ChatListKey(upd.chatList) + cached.positions.removeAll(where: { ChatListKey($0.list) == key }) + chatCache[upd.chatId] = cached + scheduleReproject() + case .updateChatPhoto(let upd): + guard var cached = chatCache[upd.chatId] else { return } + cached.avatarSmallFileId = upd.photo?.small.id + cached.avatarMini = upd.photo?.minithumbnail?.data + chatCache[upd.chatId] = cached + if let photo = upd.photo { + seedAvatarFile(photo.small) + } + scheduleReproject() + case .updateFile(let upd): + files[upd.file.id] = upd.file + if trackedFileIds.contains(upd.file.id) { scheduleReproject() } + default: + break + } + } + + /// Defers `reproject()` to the next main-runloop turn when + /// `coalesceUpdates` is on, collapsing a burst of TDLib updates into a + /// single projection. With coalescing off (the test default) this runs + /// `reproject()` synchronously, matching pre-coalescing behavior. + /// + /// Any synchronous `reproject()` (e.g. from `setCurrentFolder` or + /// `ensureChatsLoaded`) clears the pending flag, so the deferred job + /// no-ops if it loses the race. + private func scheduleReproject() { + guard coalesceUpdates else { + reproject() + return + } + guard !reprojectPending else { return } + reprojectPending = true + DispatchQueue.main.async { [weak self] in + guard let self else { return } + guard self.reprojectPending else { return } + self.reproject() + } + } + + #if DEBUG + /// Synchronously runs any pending coalesced reproject. Test hook. + func flushPendingReproject() { + guard reprojectPending else { return } + reproject() + } + #endif + + private func reproject() { + reprojectPending = false + #if DEBUG + debugReprojectCount += 1 + #endif + let limit = displayLimits[ChatListKey(currentFolder)] ?? initialDisplayLimit + chats = ChatRow.project( + chats: chatCache, + userNames: userNames.names, + selfUserId: selfUserId, + currentFolder: currentFolder, + limit: limit, + fileLocals: files + ) + pills = FolderPill.project( + chats: chatCache, + folders: folders, + mainChatListPosition: mainChatListPosition, + currentFolder: currentFolder + ) + } + + /// Count of cached chats with a position in `folder`. Used to decide whether the + /// display limit can grow against existing cache vs. needs more from TDLib. + private func countOfChatsInList(_ folder: ChatList) -> Int { + let key = ChatListKey(folder) + return chatCache.values.reduce(0) { acc, cached in + cached.positions.contains(where: { ChatListKey($0.list) == key }) ? acc + 1 : acc + } + } + + private func applyPositions(_ updates: [ChatPosition], to cached: inout CachedChat) { + var next = cached.positions + for incoming in updates { + next.removeAll(where: { ChatListKey($0.list) == ChatListKey(incoming.list) }) + if incoming.order != 0 { + next.append(incoming) + } + } + cached.positions = next + } + + private func loadOnePage(for chatList: ChatList) async { + let key = ChatListKey(chatList) + let pageSize = 30 + let isFirstPage = chatCache.isEmpty || hasNoChatsInList(chatList) + loadStates[key] = isFirstPage ? .loadingFirstPage : .loadingMore + logger.info("loadChats key=\(self.describeList(chatList), privacy: .public) phase=\(isFirstPage ? "first" : "more", privacy: .public) calling") + do { + try await loader.loadChats(chatList: chatList, limit: pageSize) + logger.info("loadChats key=\(self.describeList(chatList), privacy: .public) ok cacheCount=\(self.chatCache.count, privacy: .public)") + loadStates[key] = .hasMore + } catch let error as TDError where error.code == 404 { + logger.info("loadChats key=\(self.describeList(chatList), privacy: .public) terminated (404)") + loadStates[key] = .loaded + } catch { + logger.warning("loadChats key=\(self.describeList(chatList), privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + loadStates[key] = .failed(humanMessage(error)) + } + } + + /// True when nothing in `chatCache` has a non-zero position in `chatList`. + /// Used by `loadOnePage` to pick `.loadingFirstPage` vs `.loadingMore` — + /// the projection (`chats`) only reflects the active folder, but we want + /// the flag to be per-list. + private func hasNoChatsInList(_ chatList: ChatList) -> Bool { + let key = ChatListKey(chatList) + for cached in chatCache.values { + if cached.positions.contains(where: { ChatListKey($0.list) == key }) { + return false + } + } + return true + } + + private func diagnosticLog(_ update: Update) { + switch update { + case .updateNewChat(let upd): + let lists = upd.chat.positions.map { describeList($0.list) + "@\($0.order.rawValue)" }.joined(separator: ",") + logger.info("upd.newChat id=\(upd.chat.id, privacy: .public) title=\(upd.chat.title, privacy: .public) positions=[\(lists, privacy: .public)]") + case .updateChatPosition(let upd): + logger.info("upd.chatPosition id=\(upd.chatId, privacy: .public) list=\(self.describeList(upd.position.list), privacy: .public) order=\(upd.position.order.rawValue, privacy: .public)") + case .updateChatAddedToList(let upd): + logger.info("upd.chatAddedToList id=\(upd.chatId, privacy: .public) list=\(self.describeList(upd.chatList), privacy: .public)") + case .updateChatRemovedFromList(let upd): + logger.info("upd.chatRemovedFromList id=\(upd.chatId, privacy: .public) list=\(self.describeList(upd.chatList), privacy: .public)") + case .updateChatLastMessage(let upd): + let lists = upd.positions.map { describeList($0.list) + "@\($0.order.rawValue)" }.joined(separator: ",") + logger.info("upd.chatLastMessage id=\(upd.chatId, privacy: .public) positions=[\(lists, privacy: .public)]") + case .updateChatFolders: + logger.info("upd.chatFolders") + default: + break + } + } + + private func describeList(_ list: ChatList) -> String { + switch list { + case .chatListMain: return "main" + case .chatListArchive: return "archive" + case .chatListFolder(let f): return "folder/\(f.chatFolderId)" + case .unsupported: return "unsupported" + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListTimestamp.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListTimestamp.swift new file mode 100644 index 0000000000..2f61a4d3e4 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListTimestamp.swift @@ -0,0 +1,35 @@ +import Foundation + +/// Formats a chat-list row's last-message timestamp, Telegram-style. Pure: the caller passes +/// `now` (the view passes `Date()`), so the result is deterministic in tests. +/// +/// - same calendar day as `now` (or future) → locale short time ("9:45 AM" / "09:45") +/// - yesterday → "Yesterday" +/// - 2–6 days ago → short weekday ("Mon") +/// - 7+ days ago → locale short date ("5/12/26") +func chatListTimestamp(_ unixSeconds: Int, now: Date, calendar: Calendar = .current) -> String { + let date = Date(timeIntervalSince1970: TimeInterval(unixSeconds)) + let startOfDate = calendar.startOfDay(for: date) + let startOfNow = calendar.startOfDay(for: now) + let dayDiff = calendar.dateComponents([.day], from: startOfDate, to: startOfNow).day ?? 0 + + switch dayDiff { + case ...0: + return formatted(date, calendar: calendar) { $0.dateStyle = .none; $0.timeStyle = .short } + case 1: + return "Yesterday" + case 2...6: + return formatted(date, calendar: calendar) { $0.setLocalizedDateFormatFromTemplate("EEE") } + default: + return formatted(date, calendar: calendar) { $0.dateStyle = .short; $0.timeStyle = .none } + } +} + +private func formatted(_ date: Date, calendar: Calendar, _ configure: (DateFormatter) -> Void) -> String { + let f = DateFormatter() + f.calendar = calendar + f.locale = calendar.locale ?? .current + f.timeZone = calendar.timeZone + configure(f) + return f.string(from: date) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift new file mode 100644 index 0000000000..d06803db47 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatListView.swift @@ -0,0 +1,180 @@ +import SwiftUI +import TDShim + +struct ChatListView: View { + @Environment(TDClient.self) private var client + let store: ChatListStore + + @State private var dismissedLastError: String? = nil + @State private var opener = ChatOpener() + /// Bound to the outer chat-list `List`. Re-asserted after every pill tap so the + /// digital crown stays glued to the chat list, never the pill bar's horizontal + /// ScrollView (where rotation has no useful effect). watchOS otherwise routes + /// the crown to the most-recently-interacted scrollable. + @FocusState private var listFocused: Bool + + var body: some View { + NavigationStack { + ScrollViewReader { proxy in + VStack(spacing: 0) { + if case .failed(let message) = store.loadState(for: store.currentFolder) { + banner(text: message, kind: .retry) + } + if let err = client.lastError, dismissedLastError != err { + banner(text: err, kind: .dismiss) + } + content(proxy: proxy) + } + .navigationDestination(item: $opener.target) { target in + MessageListView(row: target.row, store: target.store) + } + .navigationTitle("Chats") + .navigationBarTitleDisplayMode(.inline) + // `.toolbar(.visible)` forces the nav-bar container to + // materialize on the chat list. Without it, watchOS-26 skips + // chrome on a NavigationStack root view, leaving only the + // status row (clock top-right). The push to MessageListView + // then has to grow that minimal status row into a full glass + // nav bar with back chevron + title + trailing avatar, which + // glitches mid-reshape. With the bar present here, the push + // only has to morph content, not allocate a new glass surface. + .toolbar(.visible, for: .navigationBar) + // Must live INSIDE the NavigationStack so the modifier's + // .navigationDestination(isPresented:) attaches to it. + .accountSwitcherSheet(presentation: .push, logoutAffordance: .allowed) + } + } + .accessibilityIdentifier("chatListView") + } + + @ViewBuilder + private func content(proxy: ScrollViewProxy) -> some View { + // One List for all states. Keeping the surrounding container stable across + // loading/empty/populated transitions means the FolderPillBar row keeps its + // SwiftUI identity — and its inner horizontal ScrollView keeps its offset — + // across folder switches. + let folderLoadState = store.loadState(for: store.currentFolder) + List { + if store.pills.count > 1 { + FolderPillBar(pills: store.pills, onSelect: { pill in + switchTo(pill: pill, proxy: proxy) + }, onUserInteraction: { + listFocused = true + }) + .id("folderPillBarRow") + // Negative bottom inset compresses the natural ~23pt gap between + // the pill row and the first chat row down to ~8pt. + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: -15, trailing: 0)) + .listRowBackground(Color.clear) + // Belt-and-suspenders: also catch tap-only cases (no scroll offset change) + // via a simultaneous drag gesture. The onUserInteraction callback on + // FolderPillBar handles the horizontal-pan case via onScrollGeometryChange. + .simultaneousGesture( + DragGesture(minimumDistance: 0).onEnded { _ in + listFocused = true + } + ) + } + if store.chats.isEmpty { + emptyStateRow(loadState: folderLoadState) + } else { + ForEach(Array(store.chats.enumerated()), id: \.element.id) { idx, row in + Button { + opener.open(row, makeStore: makeHistoryStore) + } label: { + ChatRowView( + row: row, + onRequestDownload: { store.requestFileDownload(fileId: $0) }, + onCancelDownload: { store.cancelFileDownload(fileId: $0) } + ) + } + .buttonStyle(.plain) + .onAppear { store.ensureChatsLoaded(near: idx) } + } + } + } + .listStyle(.plain) + .focused($listFocused) + .onAppear { listFocused = true } + // Bar is intentionally visible (forced by `.toolbar(.visible, for: + // .navigationBar)` on the body chain). When folder pills are present, + // pull the pill row up by 24pt to tuck it under the bar's bottom edge + // — without that, the natural top-of-list inset leaves an awkward gap + // between the bar and the pill row. Plain chat rows (no pills) sit at + // the natural top. + .padding(.top, store.pills.count > 1 ? -20 : 0) + } + + private func makeHistoryStore(for row: ChatRow) -> ChatHistoryStore? { + guard let loader = client.makeChatHistoryLoader() else { return nil } + return ChatHistoryStore( + chatId: row.id, + chatType: row.chatType, + lastReadInboxMessageId: row.lastReadInboxMessageId, + lastReadOutboxMessageId: row.lastReadOutboxMessageId, + unreadCount: row.unreadCount, + lastMessageId: row.lastMessageId, + loader: loader, + selfUserId: client.me?.id, + userNames: client.userNames, + draftText: row.draftText, + coalesceUpdates: true + ) + } + + @ViewBuilder + private func emptyStateRow(loadState: LoadState) -> some View { + Group { + if loadState == .loadingFirstPage { + LoadingView(label: "Loading chats…") + } else { + Text(store.folders.isEmpty ? "No chats yet" : "No chats in this folder") + .foregroundStyle(.secondary) + } + } + .frame(maxWidth: .infinity, minHeight: 120) + .listRowBackground(Color.clear) + } + + private func switchTo(pill: FolderPill, proxy: ScrollViewProxy) { + store.setCurrentFolder(pill.chatList) + // Reset outer vertical scroll to the top. The pill bar row keeps its SwiftUI + // identity (single List across all states + stable `.id("folderPillBarRow")`), + // so the inner horizontal ScrollView's offset survives the folder switch. + withAnimation { + proxy.scrollTo("folderPillBarRow", anchor: .top) + } + // Re-claim crown focus for the outer List — otherwise the pill tap leaves + // crown focus on the horizontal pill ScrollView. + listFocused = true + } + + private enum BannerKind { case retry, dismiss } + + @ViewBuilder + private func banner(text: String, kind: BannerKind) -> some View { + HStack(spacing: 6) { + Text(text) + .font(.caption2) + .lineLimit(2) + .frame(maxWidth: .infinity, alignment: .leading) + switch kind { + case .retry: + Button("Retry") { store.retry() } + .font(.caption2) + .buttonStyle(.borderedProminent) + case .dismiss: + Button { + dismissedLastError = client.lastError + } label: { + Image(systemName: "xmark") + .font(.caption2) + } + .buttonStyle(.bordered) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.thinMaterial) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatOpener.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatOpener.swift new file mode 100644 index 0000000000..406fcee0af --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatOpener.swift @@ -0,0 +1,64 @@ +import Foundation +import Observation + +/// Carrier for a pushed chat: the row plus its pre-built (warmed or warming) +/// `ChatHistoryStore`. Navigation identity is the chat id only — `ChatHistoryStore` +/// is a reference type and intentionally excluded from equality/hashing. +struct OpenChatTarget: Identifiable, Hashable { + let row: ChatRow + let store: ChatHistoryStore + var id: Int64 { row.id } + + static func == (lhs: OpenChatTarget, rhs: OpenChatTarget) -> Bool { lhs.row.id == rhs.row.id } + func hash(into hasher: inout Hasher) { hasher.combine(row.id) } +} + +/// Coordinates "warm the store, then push" so a cached chat opens instantly and +/// correctly positioned, while an uncached chat still pushes promptly (with the +/// in-view spinner) instead of hanging the tap. Owned + injected by `ChatListView`. +@Observable +@MainActor +final class ChatOpener { + /// Drives `.navigationDestination(item:)`. Non-nil → push. Cleared on pop. + var target: OpenChatTarget? + /// Chat id currently being warmed (available for a pressed/redacted row + /// affordance during the wait; not wired by default — the wait is ~150ms). + private(set) var pendingRowId: Int64? + + /// Max time to wait for `warm()` before pushing anyway with the in-view spinner. + /// Instance-mutable so tests can shorten it. ~150ms: long enough that a local + /// cache load wins, short enough that an uncached tap still feels responsive. + var openTimeoutNs: UInt64 = 150_000_000 + + /// `makeStore` returns the store to warm+push (or nil if dependencies are + /// unavailable, e.g. no TDLib client). Kept as a closure so `ChatOpener` has no + /// `TDClient` dependency and stays unit-testable. + func open(_ row: ChatRow, makeStore: (ChatRow) -> ChatHistoryStore?) { + guard target == nil, pendingRowId == nil, let store = makeStore(row) else { return } + pendingRowId = row.id + let timeoutNs = openTimeoutNs + // Owned task: runs to completion regardless of the race below. Dropping a + // Task handle does NOT cancel it, so the timeout-loser keeps loading. + let warmTask = Task { await store.warm() } + Task { [weak self] in + await Self.waitForWarmOrTimeout(store: store, timeoutNs: timeoutNs) + guard let self else { return } + self.target = OpenChatTarget(row: row, store: store) + self.pendingRowId = nil + } + _ = warmTask + } + + /// Returns when `store.loadState` leaves `.loadingFirstPage` (warm finished) OR + /// the deadline elapses, whichever is first. Polls rather than `await`-ing the + /// warm task's value — `Task.value` is not cancellation-aware, so awaiting it + /// inside a race would block until warm finished even after the timeout fired. + private static func waitForWarmOrTimeout(store: ChatHistoryStore, timeoutNs: UInt64) async { + let stepNs: UInt64 = 16_000_000 // ~one frame + var waitedNs: UInt64 = 0 + while store.loadState == .loadingFirstPage && waitedNs < timeoutNs { + try? await Task.sleep(nanoseconds: stepNs) + waitedNs += stepNs + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatPreview.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatPreview.swift new file mode 100644 index 0000000000..939cbb9560 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatPreview.swift @@ -0,0 +1,73 @@ +import Foundation +import TDShim + +/// Renders the preview line for a chat-list row. Pure: same inputs always produce the same output. +/// +/// Order of preference: +/// 1. Draft (any `draftText`, even empty) -> "Draft: ". +/// 2. Recognized service content -> formatted service line (omits actor for private/secret chats). +/// 3. Last message text content -> "". +/// 4. Last message recognized non-text content -> short type label ("Photo", "Voice message", ...). +/// 5. Anything else -> "". +func chatPreview(_ chat: CachedChat, userNames: [Int64: String], selfUserId: Int64?) -> String { + if let draftText = chat.draftText { + return "Draft: \(draftText)" + } + guard let last = chat.lastMessage else { return "" } + + if let service = serviceLineText( + last, + selfUserId: selfUserId, + userNames: userNames, + messageCache: [:], + includeActor: shouldIncludeServiceActor(for: chat.type), + chatType: chat.type + ) { + return service + } + + switch last.content { + case .messageText(let text): + return senderPrefix(for: last, in: chat, userNames: userNames) + text.text.text + case .messagePhoto: return "Photo" + case .messageVoiceNote: return "Voice message" + case .messageAudio(let m): return m.audio.title.isEmpty ? "Music" : m.audio.title + case .messageSticker(let m): return m.sticker.emoji.isEmpty ? "Sticker" : "\(m.sticker.emoji) Sticker" + case .messageVideo: return "Video" + case .messageVideoNote: return "Video message" + case .messageDocument: return "Document" + case .messageLocation: return "Location" + case .messageVenue(let m): return m.venue.title.isEmpty ? "Location" : m.venue.title + case .messageContact: return "Contact" + case .messagePoll: return "Poll" + default: return "" + } +} + +/// Returns whether the chat-list preview should include the "{Actor} " prefix +/// for a service message. Private and secret chats omit it; basic groups, +/// supergroups, and channels include it (channels typically resolve to +/// "Someone" via the messageSenderChat actor branch). +private func shouldIncludeServiceActor(for chatType: ChatType) -> Bool { + switch chatType { + case .chatTypePrivate, .chatTypeSecret, .unsupported: + return false + case .chatTypeBasicGroup, .chatTypeSupergroup: + return true + } +} + +private func senderPrefix(for message: CachedMessage, in chat: CachedChat, userNames: [Int64: String]) -> String { + if message.isOutgoing { return "You: " } + switch chat.type { + case .chatTypeBasicGroup, .chatTypeSupergroup: + if case .messageSenderUser(let u) = message.senderId, + let firstName = userNames[u.userId], + !firstName.isEmpty { + return "\(firstName): " + } + return "" + default: + return "" + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRow.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRow.swift new file mode 100644 index 0000000000..9698608834 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRow.swift @@ -0,0 +1,101 @@ +import TDShim + +struct ChatRow: Identifiable, Equatable, Hashable { + let id: Int64 + let title: String + let preview: String + let unreadCount: Int + let isMuted: Bool + let order: TdInt64 + let chatType: ChatType + let canSend: Bool + let draftText: String + let lastReadInboxMessageId: Int64 + let lastReadOutboxMessageId: Int64 + let lastMessageId: Int64? + let lastMessageDate: Int? + let avatar: AvatarVisual + /// True when the chat's last message is a sent outgoing message the recipient + /// hasn't read yet (`id > lastReadOutboxMessageId`). Mirrors `MessageRow.isUnreadOutgoing`. + var isUnreadOutgoing: Bool = false + + /// Compacts the store's chat cache into a sorted `[ChatRow]`. Chats without a position + /// in `currentFolder` are dropped. Sort: highest `order` first, which naturally puts + /// pinned chats above unpinned via TDLib's order encoding. + /// + /// `limit` caps the returned count. The slice is applied BEFORE `chatPreview` runs, + /// so a folder with thousands of cached chats only pays preview cost for the top K. + /// The default `.max` preserves "return everything" for tests. + static func project( + chats: [Int64: CachedChat], + userNames: [Int64: String], + selfUserId: Int64?, + currentFolder: ChatList, + limit: Int = .max, + fileLocals: [Int: File] = [:] + ) -> [ChatRow] { + let currentKey = ChatListKey(currentFolder) + var candidates: [(chat: CachedChat, order: TdInt64)] = [] + candidates.reserveCapacity(chats.count) + for chat in chats.values { + guard let position = chat.positions.first(where: { ChatListKey($0.list) == currentKey }) else { + continue + } + candidates.append((chat, position.order)) + } + candidates.sort { $0.order > $1.order } + let top = limit == .max ? candidates : Array(candidates.prefix(limit)) + return top.map { entry in + let lm = entry.chat.lastMessage + let isUnreadOutgoing = !isSavedMessages(chat: entry.chat, selfUserId: selfUserId) + && lm?.isOutgoing == true + && lm?.sendingState == .sent + && (lm?.id ?? 0) > entry.chat.lastReadOutboxMessageId + return ChatRow( + id: entry.chat.id, + title: displayTitle(for: entry.chat, selfUserId: selfUserId), + preview: chatPreview(entry.chat, userNames: userNames, selfUserId: selfUserId), + unreadCount: entry.chat.unreadCount, + isMuted: entry.chat.muteFor > 0, + order: entry.order, + chatType: entry.chat.type, + canSend: deriveCanSend(entry.chat), + draftText: entry.chat.draftText ?? "", + lastReadInboxMessageId: entry.chat.lastReadInboxMessageId, + lastReadOutboxMessageId: entry.chat.lastReadOutboxMessageId, + lastMessageId: entry.chat.lastMessage?.id, + lastMessageDate: entry.chat.lastMessage?.date, + avatar: avatarVisual(for: entry.chat, fileLocals: fileLocals, selfUserId: selfUserId), + isUnreadOutgoing: isUnreadOutgoing + ) + } + } +} + +/// A private chat with our own user id is the "Saved Messages" chat. +private func isSavedMessages(chat: CachedChat, selfUserId: Int64?) -> Bool { + guard let selfUserId, case .chatTypePrivate(let p) = chat.type else { return false } + return p.userId == selfUserId +} + +/// A private chat with our own user id is the "Saved Messages" chat — TDLib stores +/// the title as the user's own first name, but Telegram clients universally relabel it. +private func displayTitle(for chat: CachedChat, selfUserId: Int64?) -> String { + if isSavedMessages(chat: chat, selfUserId: selfUserId) { + return "Saved Messages" + } + return chat.title +} + +/// Private chats and secret chats are always sendable (no group permissions apply). +/// Basic groups and supergroups (including channels) gate on `canSendBasicMessages`. +private func deriveCanSend(_ chat: CachedChat) -> Bool { + switch chat.type { + case .chatTypePrivate, .chatTypeSecret: + return true + case .chatTypeBasicGroup, .chatTypeSupergroup: + return chat.permissions.canSendBasicMessages + case .unsupported: + return false + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRowView.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRowView.swift new file mode 100644 index 0000000000..7f26780016 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/ChatRowView.swift @@ -0,0 +1,208 @@ +import SwiftUI +#if DEBUG +import TDShim +#endif + +struct ChatRowView: View { + let row: ChatRow + var onRequestDownload: (Int) -> Void = { _ in } + var onCancelDownload: (Int) -> Void = { _ in } + + var body: some View { + HStack(alignment: .center, spacing: 8) { + AvatarView( + avatar: row.avatar, + onRequestDownload: onRequestDownload, + onCancelDownload: onCancelDownload + ) + VStack(alignment: .leading, spacing: 2) { + HStack(alignment: .firstTextBaseline, spacing: 4) { + Text(row.title) + .font(.headline) + .lineLimit(1) + .truncationMode(.tail) + if row.isMuted { + Image(systemName: "bell.slash.fill") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + if !row.preview.isEmpty { + Text(row.preview) + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.tail) + } + if let date = row.lastMessageDate { + HStack(spacing: 4) { + Text(chatListTimestamp(date, now: Date())) + .font(.caption2) + .foregroundStyle(.secondary) + .lineLimit(1) + if row.isUnreadOutgoing { + Circle() + .fill(Color.accentColor) + .frame(width: 7, height: 7) + .accessibilityIdentifier("chatRow.\(row.id).unreadOutgoing") + } + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + + if row.unreadCount > 0 { + Text("\(row.unreadCount)") + .font(.caption2) + .padding(.horizontal, 5) + .frame(minWidth: 20, minHeight: 20) + .background(Capsule().fill(row.isMuted ? Color.gray : Color.accentColor)) + .foregroundStyle(.white) + } + } + .accessibilityIdentifier("chatRow.\(row.id)") + } +} + +#if DEBUG +#Preview("Unread private") { + ChatRowView(row: ChatRow( + id: 1, + title: "Alice Smith", + preview: "Hey, are you around?", + unreadCount: 3, + isMuted: false, + order: 100, + chatType: .chatTypePrivate(.init(userId: 1)), + canSend: true, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "AS", colorIndex: 0, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Muted channel") { + ChatRowView(row: ChatRow( + id: 2, + title: "iOS Dev Weekly", + preview: "New issue: SwiftUI tips", + unreadCount: 0, + isMuted: true, + order: 50, + chatType: .chatTypeSupergroup(.init(isChannel: true, supergroupId: 9)), + canSend: false, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "ID", colorIndex: 3, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Muted with unread") { + ChatRowView(row: ChatRow( + id: 3, + title: "Swift Forums", + preview: "12 new posts in language-evolution", + unreadCount: 12, + isMuted: true, + order: 80, + chatType: .chatTypeSupergroup(.init(isChannel: true, supergroupId: 10)), + canSend: false, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "SF", colorIndex: 5, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Long title muted") { + ChatRowView(row: ChatRow( + id: 4, + title: "Aleksandr Konstantinovich Romanov", + preview: "let's grab lunch on Thursday around noon", + unreadCount: 1, + isMuted: true, + order: 70, + chatType: .chatTypePrivate(.init(userId: 4)), + canSend: true, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "AK", colorIndex: 2, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Big unread") { + ChatRowView(row: ChatRow( + id: 5, + title: "Family", + preview: "Mom: don't forget to call your grandmother", + unreadCount: 999, + isMuted: false, + order: 90, + chatType: .chatTypeBasicGroup(.init(basicGroupId: 11)), + canSend: true, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "F", colorIndex: 5, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Read unmuted") { + ChatRowView(row: ChatRow( + id: 6, + title: "Bob", + preview: "ok, see you then", + unreadCount: 0, + isMuted: false, + order: 40, + chatType: .chatTypePrivate(.init(userId: 6)), + canSend: true, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 0, + lastMessageId: nil, + lastMessageDate: Int(Date().timeIntervalSince1970) - 600, + avatar: AvatarVisual(kind: .normal, initials: "B", colorIndex: 4, photoFileId: nil, photoLocalPath: nil, mini: nil) + )) + .padding() +} + +#Preview("Unread outgoing") { + ChatRowView(row: ChatRow( + id: 7, + title: "Carol", + preview: "on my way", + unreadCount: 0, + isMuted: false, + order: 95, + chatType: .chatTypePrivate(.init(userId: 7)), + canSend: true, + draftText: "", + lastReadInboxMessageId: 0, + lastReadOutboxMessageId: 4, + lastMessageId: 5, + lastMessageDate: Int(Date().timeIntervalSince1970) - 120, + avatar: AvatarVisual(kind: .normal, initials: "C", colorIndex: 1, photoFileId: nil, photoLocalPath: nil, mini: nil), + isUnreadOutgoing: true + )) + .padding() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPill.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPill.swift new file mode 100644 index 0000000000..275e98672a --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPill.swift @@ -0,0 +1,66 @@ +import TDShim + +/// A single tab in the chat-list folder pill bar. +/// +/// `id` is the folder identifier; the synthetic "All chats" pill uses `-1` (folder IDs are +/// positive). `chatList` is the value to pass to `loadChats(chatList:limit:)`. `unreadCount` +/// is summed across all chats whose `positions` include `chatList`. +struct FolderPill: Identifiable, Equatable, Hashable { + static let allChatsId: Int = -1 + static let allChatsName = "All" + + let id: Int + let chatList: ChatList + let name: String + let unreadCount: Int + let isActive: Bool + + /// Projects pills from the store's state. Pure function (no TDLib calls). + /// - parameter chats: the store's `chatCache`. + /// - parameter folders: payload from `updateChatFolders.chatFolders`. + /// - parameter mainChatListPosition: payload from `updateChatFolders.mainChatListPosition`. + /// - parameter currentFolder: the currently-selected chat list (drives `isActive`). + static func project( + chats: [Int64: CachedChat], + folders: [ChatFolderInfo], + mainChatListPosition: Int, + currentFolder: ChatList + ) -> [FolderPill] { + let currentKey = ChatListKey(currentFolder) + + // Start with the user folders in their natural order. + var result: [FolderPill] = folders.map { info in + let list: ChatList = .chatListFolder(ChatListFolder(chatFolderId: info.id)) + let unread = unreadSum(chats: chats, list: list) + return FolderPill( + id: info.id, + chatList: list, + name: info.name.text.text, + unreadCount: unread, + isActive: ChatListKey(list) == currentKey + ) + } + + // Insert the "All chats" pill at the clamped main position. + let allPill = FolderPill( + id: allChatsId, + chatList: .chatListMain, + name: allChatsName, + unreadCount: unreadSum(chats: chats, list: .chatListMain), + isActive: currentKey == .main + ) + let clampedPos = max(0, min(mainChatListPosition, result.count)) + result.insert(allPill, at: clampedPos) + + return result + } + + private static func unreadSum(chats: [Int64: CachedChat], list: ChatList) -> Int { + let targetKey = ChatListKey(list) + return chats.values.reduce(0) { acc, chat in + chat.positions.contains(where: { ChatListKey($0.list) == targetKey }) + ? acc + chat.unreadCount + : acc + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPillBar.swift b/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPillBar.swift new file mode 100644 index 0000000000..0d22dbdf0d --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Chats/FolderPillBar.swift @@ -0,0 +1,90 @@ +import SwiftUI +import OSLog + +private let pillLogger = Logger(subsystem: "com.isaac.tgwatch", category: "chatlist") + +/// Horizontal scroller of folder pills. Embedded as the first item inside the chat list's +/// scrollable area so it scrolls off with the content. Stable `.id("folderPillBar")` keeps +/// the inner horizontal `ScrollView` offset preserved across folder switches. +/// +/// `.focusable(false)` keeps the digital crown bound to the outer chat list. Otherwise +/// tapping a pill would park crown focus on this horizontal ScrollView, where rotation has +/// no useful effect (one-axis, short content), and the user would lose vertical scroll +/// until they manually touched the chat list again. +/// +/// `onUserInteraction` fires on any horizontal pan (via `onScrollGeometryChange`) so the +/// caller can re-assert digital-crown focus on the outer chat list after a scroll gesture +/// that bypasses normal gesture recognizers. +struct FolderPillBar: View { + let pills: [FolderPill] + let onSelect: (FolderPill) -> Void + /// Called when the user horizontally pans the pill bar. ChatListView uses this to + /// re-assert digital-crown focus on the outer List (horizontal scroll bypasses the + /// `simultaneousGesture` workaround on the List row). + let onUserInteraction: () -> Void + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 6) { + ForEach(pills) { pill in + Button { + onSelect(pill) + } label: { + HStack(spacing: 4) { + Text(pill.name) + .font(.caption2.weight(.semibold)) + .lineLimit(1) + if pill.unreadCount > 0 { + let badgeFill: AnyShapeStyle = pill.isActive + ? AnyShapeStyle(Color.black.opacity(0.25)) + : AnyShapeStyle(Color.accentColor.opacity(0.85)) + let badgeFore: AnyShapeStyle = pill.isActive + ? AnyShapeStyle(.primary) + : AnyShapeStyle(Color.black) + Text("\(pill.unreadCount)") + .font(.caption2.weight(.semibold)) + .padding(.horizontal, 4) + .background(Capsule().fill(badgeFill)) + .foregroundStyle(badgeFore) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background( + Capsule().fill(pill.isActive ? Color.accentColor.opacity(0.85) : .clear) + ) + .overlay( + Capsule().stroke( + pill.isActive ? .clear : Color.secondary.opacity(0.5), + lineWidth: 1 + ) + ) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + // watchOS: List + ScrollView(.horizontal) gesture system can swallow + // Button taps. A high-priority TapGesture on the button itself + // ensures the tap reaches the pill even when the outer gestures are + // competing. The Button action still fires for visual-feedback purposes. + .highPriorityGesture(TapGesture().onEnded { onSelect(pill) }) + .accessibilityIdentifier("folderPill-\(pill.id)") + } + } + .padding(.horizontal, 8) + .padding(.vertical, 2) + } + .id("folderPillBar") + .focusable(false) + // Re-assert outer List crown focus whenever the user horizontally pans the pill bar. + // A horizontal drag moves watchOS crown focus to this inner ScrollView (bypassing the + // simultaneousGesture(DragGesture) on the List row, which the pan recognizer cancels). + // onScrollGeometryChange observes content-offset changes directly on this ScrollView, + // so it fires even when outer gesture recognizers are cancelled. + .onScrollGeometryChange(for: CGFloat.self) { geometry in + geometry.contentOffset.x + } action: { _, _ in + pillLogger.info("folderbar.interact reason=scroll") + onUserInteraction() + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/ContentView.swift b/Telegram/WatchApp/tgwatch Watch App/ContentView.swift new file mode 100644 index 0000000000..367420227a --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/ContentView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +struct ContentView: View { + @Environment(TDClient.self) private var client + + var body: some View { + switch client.authState { + case .starting: + LoadingView(label: "Starting…") + case .waitTdlibParameters: + LoadingView(label: "Configuring…") + case .waitPhoneNumber: + LoadingView(label: "Preparing QR…") + case .waitCode: + ErrorView(message: "Unexpected SMS-code prompt during QR login.") + case .waitOtherDeviceConfirmation(let link): + QrLoginView(link: link) + case .waitPassword(let info): + PasswordEntryView(info: info) + case .ready: + if let store = client.chatList { + ChatListView(store: store) + } else { + LoadingView(label: "Loading…") + } + case .loggingOut: + LoadingView(label: "Logging out…") + case .closed: + ClosedView() + case .failed(let message): + ErrorView(message: message) + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/ErrorMapping.swift b/Telegram/WatchApp/tgwatch Watch App/ErrorMapping.swift new file mode 100644 index 0000000000..3e7df18782 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/ErrorMapping.swift @@ -0,0 +1,54 @@ +import Foundation +import TDShim + +func humanMessage(_ error: Swift.Error) -> String { + if let tdErr = error as? TDError { + return humanMessageForTdLibCode(tdErr.message) + } + return (error as? LocalizedError)?.errorDescription ?? error.localizedDescription +} + +/// Maps a TDLib error from `checkAuthenticationPassword` to one of three +/// user-facing messages for the 2-step verification screen. Unlike +/// `humanMessage`, unknown codes collapse to a generic message rather than +/// passing the raw code through. +func passwordSubmitErrorMessage(_ error: Swift.Error) -> String { + guard let tdErr = error as? TDError else { + return "An error occurred" + } + if tdErr.message == "PASSWORD_HASH_INVALID" { + return "Incorrect password" + } + if let flood = floodWaitMessage(tdErr.message) { + return flood + } + return "An error occurred" +} + +private func humanMessageForTdLibCode(_ code: String) -> String { + switch code { + case "PHONE_NUMBER_INVALID": + return "That phone number doesn't look right." + case "PHONE_CODE_INVALID": + return "That code is wrong." + case "PHONE_CODE_EXPIRED": + return "Code expired — request a new one." + case "FIRSTNAME_INVALID": + return "That first name doesn't look right." + case "LASTNAME_INVALID": + return "That last name doesn't look right." + default: + return floodWaitMessage(code) ?? code + } +} + +/// Formats a `FLOOD_WAIT_` code into a wait message, or `nil` if the +/// code is not a flood-wait code. +private func floodWaitMessage(_ code: String) -> String? { + guard code.hasPrefix("FLOOD_WAIT_") else { return nil } + let suffix = code.dropFirst("FLOOD_WAIT_".count) + if let seconds = Int(suffix), seconds > 0 { + return "Too many attempts. Wait \(seconds)s." + } + return "Too many attempts. Try again later." +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Info.plist b/Telegram/WatchApp/tgwatch Watch App/Info.plist new file mode 100644 index 0000000000..ca62526111 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Telegram + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconName + AppIcon + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSLocationWhenInUseUsageDescription + Share your current location in chats. + NSMicrophoneUsageDescription + Record voice messages to send in chats. + TG_API_HASH + $(TG_API_HASH) + TG_API_ID + $(TG_API_ID) + WKApplication + + WKCompanionAppBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER:base) + WKRunsIndependentlyOfCompanionApp + + + diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/AVRecordingBackend.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/AVRecordingBackend.swift new file mode 100644 index 0000000000..ed8b55e9bf --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/AVRecordingBackend.swift @@ -0,0 +1,186 @@ +import AVFoundation +import Foundation +import OpusKit + +/// Serial-queue bridge from AVAudioEngine tap buffers to an on-disk Ogg/Opus +/// file. Resamples each tap buffer to 48 kHz mono Float32 (on the audio thread), +/// then frames into 20 ms Opus frames, encodes, and muxes (on `queue`). Also +/// accumulates per-frame RMS for the outgoing waveform. +/// +/// Thread model: `AVAudioConverter` is used ONLY on the audio render thread +/// (inside `append`, serialized by AVAudioEngine). The encoder, writer, `carry`, +/// `rms`, and `totalSamples` are touched ONLY on `queue`. The cross-thread handoff +/// is the `[Float]` mono array dispatched from the audio thread to `queue`. +final class OpusOggRecordingSink { + private let queue = DispatchQueue(label: "org.telegram.TelegramWatch.opusEncode") + private let encoder: OpusEncoder + private let writer: OggOpusWriter + private let converter: AVAudioConverter + private let outFormat: AVAudioFormat + private var carry: [Float] = [] + private var rms: [Float] = [] + private var totalSamples = 0 + + init(outputURL: URL, inputFormat: AVAudioFormat) throws { + encoder = try OpusEncoder() + writer = try OggOpusWriter(url: outputURL) + guard let out = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 48_000, + channels: 1, interleaved: false), + let conv = AVAudioConverter(from: inputFormat, to: out) else { + throw NSError(domain: "OpusOggRecordingSink", code: 1) + } + outFormat = out + converter = conv + } + + /// Audio-thread entry point. Returns the visual RMS level [0, 1] for this buffer. + @discardableResult + func append(_ buffer: AVAudioPCMBuffer) -> Float { + guard let mono = resample(buffer) else { return 0 } + let level = visualLevel(mono) + queue.async { [weak self] in self?.encodeFrames(mono) } + return level + } + + /// Flushes the trailing partial frame + closes the file. Returns (duration, packed waveform). + /// `totalSamples`/`rms` are read inside the `queue.sync` block to keep all queue-confined + /// state confined, rather than relying on the post-sync happens-after relationship. + func finalize() -> (duration: Int, waveform: Data) { + return queue.sync { + if !carry.isEmpty { + var frame = carry + frame.append(contentsOf: Array(repeating: 0, count: OpusEncoder.frameSize - frame.count)) + if let packet = try? encoder.encode(frame) { + writer.writePacket(packet, samples: OpusEncoder.frameSize) + appendRMS(frame) + totalSamples += OpusEncoder.frameSize + } + carry.removeAll() + } + writer.finish() + let duration = Int((Double(totalSamples) / 48_000.0).rounded()) + return (max(1, duration), WaveformPacker.pack(rms)) + } + } + + func cancel() { queue.sync { writer.finish() } } + + // MARK: - audio thread + + private func resample(_ inBuf: AVAudioPCMBuffer) -> [Float]? { + let ratio = outFormat.sampleRate / inBuf.format.sampleRate + let cap = AVAudioFrameCount(Double(inBuf.frameLength) * ratio) + 256 + guard cap > 0, let outBuf = AVAudioPCMBuffer(pcmFormat: outFormat, frameCapacity: cap) else { + return nil + } + var consumed = false + var convErr: NSError? + converter.convert(to: outBuf, error: &convErr) { _, status in + if consumed { status.pointee = .noDataNow; return nil } + consumed = true + status.pointee = .haveData + return inBuf + } + if convErr != nil { return nil } + guard let ch = outBuf.floatChannelData, outBuf.frameLength > 0 else { return nil } + return Array(UnsafeBufferPointer(start: ch[0], count: Int(outBuf.frameLength))) + } + + private func visualLevel(_ samples: [Float]) -> Float { + guard !samples.isEmpty else { return 0 } + var sum: Float = 0 + for s in samples { sum += s * s } + let r = (sum / Float(samples.count)).squareRoot() + return min(1, r * 4) // visual gain so normal speech fills the meter + } + + // MARK: - queue + + private func encodeFrames(_ mono: [Float]) { + carry.append(contentsOf: mono) + while carry.count >= OpusEncoder.frameSize { + let frame = Array(carry[0.. RecordPermission { + switch AVAudioApplication.shared.recordPermission { + case .granted: return .granted + case .denied: return .denied + case .undetermined: + return await withCheckedContinuation { cont in + AVAudioApplication.requestRecordPermission { granted in + cont.resume(returning: granted ? .granted : .denied) + } + } + @unknown default: return .denied + } + } + + func begin(onLevel: @escaping @MainActor (Float) -> Void) throws { + let session = AVAudioSession.sharedInstance() + // Use .voiceChat mode (available watchOS 2+) and .duckOthers. + // .allowBluetooth is deprecated on watchOS 11+; use .allowBluetoothHFP instead + // (available watchOS 11+, target is watchOS 26). Drop if not needed at runtime. + try session.setCategory(.playAndRecord, mode: .voiceChat, + options: [.duckOthers, .allowBluetoothHFP]) + try session.setActive(true) + + let input = engine.inputNode + let inFormat = input.outputFormat(forBus: 0) + let url = FileManager.default.temporaryDirectory + .appendingPathComponent("voice-\(UUID().uuidString).ogg") + outputURL = url + let sink = try OpusOggRecordingSink(outputURL: url, inputFormat: inFormat) + self.sink = sink + + input.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, _ in + let level = sink.append(buffer) + // AVAudioEngine fires this on the audio render thread — hop to main actor. + Task { @MainActor in onLevel(level) } + } + engine.prepare() + try engine.start() + } + + func finish() async throws -> VoiceRecordingDraft { + engine.inputNode.removeTap(onBus: 0) + engine.stop() + try? AVAudioSession.sharedInstance().setActive(false) + guard let sink, let url = outputURL else { + throw NSError(domain: "AVRecordingBackend", code: 1) + } + let (duration, waveform) = sink.finalize() + self.sink = nil + return VoiceRecordingDraft(fileURL: url, duration: duration, waveform: waveform) + } + + func abort() { + engine.inputNode.removeTap(onBus: 0) + if engine.isRunning { engine.stop() } + try? AVAudioSession.sharedInstance().setActive(false) + sink?.cancel() + if let url = outputURL { try? FileManager.default.removeItem(at: url) } + sink = nil + outputURL = nil + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/AttachmentSheet.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/AttachmentSheet.swift new file mode 100644 index 0000000000..777dc689a0 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/AttachmentSheet.swift @@ -0,0 +1,63 @@ +import SwiftUI + +/// The "+" attachment menu. A NavigationStack with one row per attachment type. +/// Each row pushes its screen inside THIS stack (avoids the sheet-from-sheet +/// empty-body race; see CLAUDE.md SwiftUI gotchas). `dismiss` here is the +/// sheet's own dismiss, so each screen's `onComplete` collapses the whole sheet +/// after a successful send. +struct AttachmentSheet: View { + let stickerPickerStore: StickerPickerStore? + /// Sends a sticker; returns true on success. + let onSendSticker: (PickerSticker) async -> Bool + /// Sends a voice note; returns true on success. + let onSendVoiceNote: (VoiceRecordingDraft) async -> Bool + /// Stops the chat's voice/audio playback before recording starts. + let onPrepareVoice: () -> Void + /// Sends a location; returns true on success. + let onSendLocation: (_ latitude: Double, _ longitude: Double) async -> Bool + + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationStack { + List { + if let stickerPickerStore { + NavigationLink { + StickerPickerView( + store: stickerPickerStore, + onSend: onSendSticker, + onComplete: { dismiss() } + ) + } label: { + Label("Sticker", systemImage: "face.smiling") + } + .accessibilityIdentifier("attachSticker") + } + + NavigationLink { + VoiceRecordView( + onSend: onSendVoiceNote, + onPrepare: onPrepareVoice, + onComplete: { dismiss() } + ) + } label: { + Label("Voice Message", systemImage: "mic.fill") + } + .accessibilityIdentifier("attachVoice") + + NavigationLink { + LocationSendView(onSend: onSendLocation, onComplete: { dismiss() }) + } label: { + Label("Location", systemImage: "mappin.and.ellipse") + } + .accessibilityIdentifier("attachLocation") + } + .navigationTitle("Attach") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + } + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/AudioBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioBubbleView.swift new file mode 100644 index 0000000000..572928e5e4 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioBubbleView.swift @@ -0,0 +1,227 @@ +import SwiftUI + +/// Renders one music/audio bubble: gray-incoming / accent-outgoing rounded +/// chrome containing a 36pt album-art square (with a play/pause/spinner/error +/// glyph overlay), a bold title, an optional performer line, and a duration +/// label that becomes "elapsed / total" during playback. Optional reply header +/// sits inside the chrome above the row; caption (when non-empty) below. +/// +/// Drives priority-1 viewport download via `.onScrollVisibilityChange`. Tap goes +/// through `ChatHistoryStore.toggleAudioPlayback(_:)`, which tears down any voice +/// playback, kicks a priority-2 download if needed, and routes to the +/// `AudioPlaybackController`. +struct AudioBubbleView: View { + let audio: AudioVisual + let caption: String + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + private var glyph: AudioGlyph { store.audioPlayback.glyph(for: audio.audioFileId) } + private var isActive: Bool { store.audioPlayback.isActive(audio.audioFileId) } + private var durationText: String { + if isActive { + let e = store.audioPlayback.elapsed(for: audio.audioFileId) + return "\(formatDuration(e)) / \(formatDuration(audio.duration))" + } + return formatDuration(audio.duration) + } + + var body: some View { + chrome + .onScrollVisibilityChange(threshold: 0.01) { visible in + if visible { + store.requestFileDownload(fileId: audio.audioFileId) + } else { + store.cancelFileDownload(fileId: audio.audioFileId) + } + } + } + + private var chrome: some View { + VStack(alignment: .leading, spacing: 4) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: BubbleStyle.resolve(isOutgoing: isOutgoing)) + } + HStack(spacing: 8) { + artSquare + VStack(alignment: .leading, spacing: 1) { + Text(audio.title) + .font(.caption).bold() + .lineLimit(1) + .truncationMode(.tail) + if !audio.performer.isEmpty { + Text(audio.performer) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + .lineLimit(1) + .truncationMode(.tail) + } + Text(durationText) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + } + Spacer(minLength: 0) + } + if !caption.isEmpty { + Text(caption) + .font(.caption) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + .frame(minWidth: BubbleShape.minSize, maxWidth: metrics.bubbleMaxWidth, minHeight: BubbleShape.minSize, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + .contentShape(Rectangle()) + .onTapGesture { store.toggleAudioPlayback(audio) } + } + + private var artSquare: some View { + ZStack { + if let data = audio.albumArt, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill() + } else { + Circle().fill(style.playFill) + } + glyphView + } + .frame(width: 36, height: 36) + .clipShape(Circle()) + } + + @ViewBuilder + private var glyphView: some View { + Group { + switch glyph { + case .play: Image(systemName: "play.fill").font(.system(size: 14)) + case .pause: Image(systemName: "pause.fill").font(.system(size: 14)) + case .spinner: + ProgressView() + .controlSize(.mini) + .scaleEffect(0.7) + case .error: Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 14)).foregroundStyle(.red) + } + } + .foregroundStyle(audio.albumArt != nil ? .white : style.playIcon) + .shadow(radius: audio.albumArt != nil ? 1 : 0) + } + +} + +#if DEBUG +import TDShim + +private struct AudioPreviewNoopLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { throw CancellationError() } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { throw CancellationError() } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func previewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, unreadCount: 0, lastMessageId: nil, + loader: AudioPreviewNoopLoader() + ) +} + +private func previewAudio( + id: Int = 1, + title: String = "Bohemian Rhapsody", + performer: String = "Queen" +) -> AudioVisual { + AudioVisual( + audioFileId: id, duration: 272, title: title, + performer: performer, albumArt: nil, caption: "", localPath: nil + ) +} + +#Preview("Audio — incoming, idle") { + AudioBubbleView( + audio: previewAudio(), caption: "", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Audio — outgoing, idle") { + AudioBubbleView( + audio: previewAudio(id: 2), caption: "", + isOutgoing: true, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Audio — long title (truncates)") { + AudioBubbleView( + audio: previewAudio(id: 3, title: "A Very Long Track Title That Will Truncate", performer: "Some Long Performer Name"), + caption: "", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Audio — no performer") { + AudioBubbleView( + audio: previewAudio(id: 4, title: "Untitled", performer: ""), + caption: "", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Audio — incoming with caption") { + AudioBubbleView( + audio: previewAudio(id: 5), + caption: "this song goes hard", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Audio — incoming with reply") { + AudioBubbleView( + audio: previewAudio(id: 6), caption: "", + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor message earlier in the chat", + minithumbnail: nil, isOutgoing: false + ) + ) + .bubblePreview() + .environment(previewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/AudioPlaybackController.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioPlaybackController.swift new file mode 100644 index 0000000000..8a56cda58c --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioPlaybackController.swift @@ -0,0 +1,240 @@ +import AVFoundation +import Combine +import Foundation +import OSLog + +/// Streaming audio playback backend. Production wraps a single `AVPlayer`; +/// tests substitute a fake. `@MainActor` like `VoicePlaybackBackend` so the +/// callbacks (which the controller assigns) are main-isolated. +@MainActor +protocol AudioPlayerBackend: AnyObject { + func load(url: URL) throws + func play() + func pause() + func stop() + var elapsed: Double { get } + /// Fired ~2×/sec with the current playback position in seconds. + var onProgress: ((Double) -> Void)? { get set } + /// Fired when the item plays to its end. + var onEnded: (() -> Void)? { get set } + /// Fired when the player item transitions to `.failed`. + var onFailed: ((String) -> Void)? { get set } +} + +enum AudioGlyph: Equatable { + case play + case pause + case spinner + case error +} + +/// Inline music playback for chat-history audio bubbles. One file active at a +/// time. Unlike `VoicePlaybackController` (whole-file Opus decode → PCM buffer), +/// this streams via `AVPlayer` so multi-minute tracks don't blow watch memory. +@Observable @MainActor +final class AudioPlaybackController { + + enum State: Equatable { + case idle + case preparing(audioFileId: Int) + case playing(audioFileId: Int) + case paused(audioFileId: Int) + case failed(audioFileId: Int, message: String) + } + + private(set) var state: State = .idle + /// Observable elapsed-seconds tick driving the bubble's "elapsed / total" label. + private(set) var currentElapsed: Double = 0 + + private let backend: AudioPlayerBackend + + init(backend: AudioPlayerBackend) { + self.backend = backend + self.backend.onProgress = { [weak self] t in self?.handleProgress(t) } + self.backend.onEnded = { [weak self] in self?.handleEnded() } + self.backend.onFailed = { [weak self] msg in self?.handleFailed(msg) } + } + + func isActive(_ audioFileId: Int) -> Bool { + switch state { + case .preparing(let id), .playing(let id), .paused(let id), .failed(let id, _): + return id == audioFileId + case .idle: + return false + } + } + + func glyph(for audioFileId: Int) -> AudioGlyph { + switch state { + case .preparing(let id) where id == audioFileId: return .spinner + case .playing(let id) where id == audioFileId: return .pause + case .paused(let id) where id == audioFileId: return .play + case .failed(let id, _) where id == audioFileId: return .error + default: return .play + } + } + + func elapsed(for audioFileId: Int) -> Int { + guard isActive(audioFileId) else { return 0 } + return Int(currentElapsed) + } + + /// Entry point the bubble calls. If `audio.localPath` is nil the controller + /// transitions to `.preparing` and waits for `resumeIfReady(audio:)`. + func toggle(audio: AudioVisual) { + switch state { + case .playing(let id) where id == audio.audioFileId: + backend.pause() + state = .paused(audioFileId: id) + return + case .paused(let id) where id == audio.audioFileId: + backend.play() + state = .playing(audioFileId: id) + return + case .preparing(let id) where id == audio.audioFileId: + // Second tap during preparing = cancel. + tearDown() + return + default: + // Switch active (or fresh start). + tearDown() + } + guard let path = audio.localPath else { + state = .preparing(audioFileId: audio.audioFileId) + return + } + startPlayback(audio: audio, path: path) + } + + /// Called by ChatHistoryStore when a fileSnapshot update lands for an audio + /// file that's currently `.preparing`. Advances to `.playing` if the path is + /// now available. + func resumeIfReady(audio: AudioVisual) { + guard case .preparing(let id) = state, id == audio.audioFileId else { return } + guard let path = audio.localPath else { return } + startPlayback(audio: audio, path: path) + } + + func tearDown() { + currentElapsed = 0 + backend.stop() + state = .idle + } + + // MARK: - Private + + private func startPlayback(audio: AudioVisual, path: String) { + currentElapsed = 0 + do { + try backend.load(url: URL(fileURLWithPath: path)) + backend.play() + state = .playing(audioFileId: audio.audioFileId) + } catch { + state = .failed(audioFileId: audio.audioFileId, message: "Could not play audio") + } + } + + private func handleProgress(_ t: Double) { + guard case .playing = state else { return } + currentElapsed = t + } + + private func handleEnded() { + guard case .playing = state else { return } + // Reset state before stopping the backend so a queued main-queue progress + // callback can't write a stale elapsed after end-of-playback (mirrors + // VoicePlaybackController.handlePlaybackEnded). + state = .idle + currentElapsed = 0 + backend.stop() + } + + private func handleFailed(_ message: String) { + switch state { + case .playing(let id), .preparing(let id), .paused(let id): + state = .failed(audioFileId: id, message: message) + case .idle, .failed: + break + } + } +} + +// MARK: - Production wiring + +/// Wraps a single `AVPlayer`. Owns the audio-session activation that watchOS +/// requires for `AVPlayer` audibility (mirrors `VideoPlayerView`). Removing all +/// observers on `stop()` / `load()` means stale end/failure callbacks can't +/// reach the controller after a switch. +@MainActor +final class AVPlayerBackend: AudioPlayerBackend { + var onProgress: ((Double) -> Void)? + var onEnded: (() -> Void)? + var onFailed: ((String) -> Void)? + + private var player: AVPlayer? + private var timeObserver: Any? + private var endObserver: NSObjectProtocol? + private var statusObserver: AnyCancellable? + private let logger = Logger(subsystem: "com.isaac.tgwatch", category: "audioplayer") + + var elapsed: Double { + guard let player else { return 0 } + let t = CMTimeGetSeconds(player.currentTime()) + return t.isFinite ? t : 0 + } + + func load(url: URL) throws { + teardownObservers() + let p = AVPlayer(url: url) + let interval = CMTime(seconds: 0.5, preferredTimescale: 600) + timeObserver = p.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] t in + let secs = CMTimeGetSeconds(t) + self?.onProgress?(secs.isFinite ? secs : 0) + } + endObserver = NotificationCenter.default.addObserver( + forName: .AVPlayerItemDidPlayToEndTime, object: p.currentItem, queue: .main + ) { [weak self] _ in + self?.onEnded?() + } + statusObserver = p.currentItem?.publisher(for: \.status) + .receive(on: DispatchQueue.main) + .sink { [weak self] status in + if status == .failed { self?.onFailed?("Could not play audio") } + } + try activatePlaybackSession() + player = p + logger.info("audio load \(url.lastPathComponent, privacy: .public)") + } + + func play() { player?.play() } + func pause() { player?.pause() } + + func stop() { + player?.pause() + teardownObservers() + player?.replaceCurrentItem(with: nil) + player = nil + try? AVAudioSession.sharedInstance().setActive(false) + } + + /// All observers are delivered on the main queue, so removing their + /// registrations here (synchronously, on the main actor) prevents any queued + /// invocation from firing after this point — this is the stale-callback guard. + private func teardownObservers() { + if let t = timeObserver { player?.removeTimeObserver(t); timeObserver = nil } + if let e = endObserver { NotificationCenter.default.removeObserver(e); endObserver = nil } + statusObserver?.cancel() + statusObserver = nil + } + + /// watchOS routes AVPlayer audio to the speaker only under an active + /// `.playback` session — same requirement as `VideoPlayerView`. + private func activatePlaybackSession() throws { + let session = AVAudioSession.sharedInstance() + // Music uses `.default` (preserves the user's chosen output route); video + // uses `.moviePlayback` (forces speaker). Both keep `.playback` so audio is + // audible on watchOS. + try session.setCategory(.playback, mode: .default) + try session.setActive(true) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/AudioVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioVisual.swift new file mode 100644 index 0000000000..18224260d4 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/AudioVisual.swift @@ -0,0 +1,22 @@ +import Foundation + +/// Per-music-message data the bubble + audio playback controller consume. Built +/// from a `MessageAudio` plus the latest `files[fileId]` snapshot for the audio +/// file. Identifiable by `audioFileId`. +struct AudioVisual: Identifiable, Equatable, Hashable { + var id: Int { audioFileId } + let audioFileId: Int + /// Seconds, from `Audio.duration` (sender-defined; may be approximate). + let duration: Int + /// Display title — `Audio.title`, falling back to `fileName`, then "Audio". + let title: String + /// `Audio.performer`; empty string hides the performer line. + let performer: String + /// Embedded album-cover minithumbnail JPEG bytes; nil when absent. Rendered + /// directly via `UIImage(data:)` — no download. + let albumArt: Data? + /// Caption text (formatting ignored, matching photo/video bubbles). + let caption: String + /// Filesystem path of the audio file once downloaded; nil otherwise. + let localPath: String? +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleMetrics.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleMetrics.swift new file mode 100644 index 0000000000..12f654d560 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleMetrics.swift @@ -0,0 +1,62 @@ +import SwiftUI +import WatchKit + +/// Single source of truth for chat-history bubble width caps. Every cap is derived +/// from one input — the screen width — so bubbles fit continuously from 40mm (162pt) +/// up to 46mm (208pt) / 49mm Ultra (205pt) with no per-device branching. +/// +/// Injected via `EnvironmentValues.bubbleMetrics`, whose default reads the live +/// device. Because the default is device-derived, every `#Preview` auto-sizes to +/// whatever sim canvas the snapshot run uses — render at 40mm and bubbles size for +/// 40mm with no per-preview overrides. +struct BubbleMetrics: Equatable { + /// Usable width for one bubble's content. + /// + /// `screenWidth − 24` (8 + 16): the ScrollView content has `.padding(.horizontal, 4)` (8 total) + /// and each `MessageBubbleView` HStack reserves a `Spacer(minLength: 16)` on the + /// non-content side. Floored at 120 so a degenerate/zero bounds read can't collapse + /// bubbles to nothing. + let contentWidth: CGFloat + + init(screenWidth: CGFloat) { + contentWidth = max(120, screenWidth - 8 - 16) + } + + /// Photo / video aspect-fit image box. `contentWidth − 8` leaves room for the + /// bubble's own `.padding(4)` (8pt total) so the padded bubble's outer width + /// still fits `contentWidth`. + var photoMaxWidth: CGFloat { contentWidth - 8 } + /// Keeps the legacy 200/180 ≈ 1.11 height ratio so a tall portrait photo can't + /// dominate a short 40mm screen — no need to read screen height. + var photoMaxHeight: CGFloat { photoMaxWidth * (200.0 / 180.0) } + + /// Voice / audio / poll chrome max width. + var bubbleMaxWidth: CGFloat { contentWidth } + + /// Static / venue / live-location map width (height stays fixed at its caller's value). + var mapWidth: CGFloat { contentWidth } + + /// Round video-note circle. Holds its current 150pt visual size on big screens, + /// shrinks only when the screen forces it. + var videoNoteDiameter: CGFloat { min(contentWidth, 150) } + + /// Sticker image edge. Holds 120pt on big screens (already fits 40mm). + /// Always 120 on current hardware (contentWidth ≥ 120 via the floor); the cap is a stable injection point. + var stickerMaxEdge: CGFloat { min(contentWidth, 120) } + + /// Reply card rendered above a chrome-less sticker. + var stickerReplyCardMaxWidth: CGFloat { min(contentWidth, 160) } +} + +private struct BubbleMetricsKey: EnvironmentKey { + static var defaultValue: BubbleMetrics { + BubbleMetrics(screenWidth: WKInterfaceDevice.current().screenBounds.width) + } +} + +extension EnvironmentValues { + var bubbleMetrics: BubbleMetrics { + get { self[BubbleMetricsKey.self] } + set { self[BubbleMetricsKey.self] = newValue } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleShape.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleShape.swift new file mode 100644 index 0000000000..70030dd712 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleShape.swift @@ -0,0 +1,13 @@ +import CoreGraphics + +/// Single source of truth for message-bubble geometry. Every bubble surface +/// (text, voice, audio, document, poll, photo, video, map) clips/fills to this +/// radius; fill-chrome bubbles also enforce the minimum so a one-word message +/// renders as a 28×28 pill (radius == half-size → circular ends). +enum BubbleShape { + static let cornerRadius: CGFloat = 14 + /// Minimum bubble edge (min-width and min-height). Equals `2 × cornerRadius` + /// so a one-word fill-chrome bubble renders as a circle/pill rather than a + /// rounded-rect with concave-looking corners. + static let minSize: CGFloat = 28 +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleStyle.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleStyle.swift new file mode 100644 index 0000000000..c4b49dd5ee --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/BubbleStyle.swift @@ -0,0 +1,44 @@ +import SwiftUI + +/// Resolved colors for a message-bubble surface. Both directions use a dark fixed surface +/// with white content. Colors are FIXED (non-adaptive) on purpose: watchOS runs this app in +/// permanent dark mode. +struct BubbleStyle: Equatable { + let fill: Color + let content: Color + let secondary: Color + let replyBar: Color + let playFill: Color + let playIcon: Color + + static let incoming = BubbleStyle( + fill: Color(red: 40 / 255, green: 40 / 255, blue: 40 / 255), + content: .white, + secondary: Color.white.opacity(0.7), + replyBar: .accentColor, + playFill: .accentColor, + playIcon: .white + ) + static let outgoing = BubbleStyle( + fill: Color(red: 19 / 255, green: 44 / 255, blue: 73 / 255), + content: .white, + secondary: Color.white.opacity(0.7), + replyBar: Color.white.opacity(0.7), + playFill: .white, + playIcon: .accentColor + ) + + static func resolve(isOutgoing: Bool) -> BubbleStyle { isOutgoing ? .outgoing : .incoming } +} + +#if DEBUG +extension View { + /// Renders a bubble `#Preview` on the device-accurate dark page so the white incoming + /// surface is visible (SnapshotPreviews defaults to a light background). + func bubblePreview() -> some View { + padding() + .background(Color.black) + .environment(\.colorScheme, .dark) + } +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryLoader.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryLoader.swift new file mode 100644 index 0000000000..a8d3eb6559 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryLoader.swift @@ -0,0 +1,238 @@ +import Foundation +import OSLog +import TDShim + +/// Single seam between `ChatHistoryStore` and TDLib. Tests inject a fake; production +/// uses `TDLibChatHistoryLoader`. +/// +/// `openChat` / `closeChat` notify TDLib that the chat is being viewed, which is required +/// for supergroup / channel updates to flow. `loadHistory` returns messages in reverse +/// chronological order (TDLib's contract); the store sorts ascending for display. +/// +/// `downloadFile` / `cancelDownloadFile` drive viewport-based photo downloading. Both are +/// idempotent: TDLib short-circuits if the file is already on disk (download) or already +/// complete (cancel). +protocol ChatHistoryLoader: Sendable { + func openChat(chatId: Int64) async throws + func closeChat(chatId: Int64) async throws + /// `offset`: pass 0 for "messages strictly older than fromMessageId" (default + /// pagination call). Pass a negative N to additionally include N messages + /// NEWER than fromMessageId — used for the open-at-unread anchor fetch and + /// for newer-direction pagination. Mirrors TDLib's `getChatHistory` semantics. + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] + func downloadFile(fileId: Int, priority: Int) async throws -> File + func cancelDownloadFile(fileId: Int) async throws + func sendText(chatId: Int64, text: String) async throws -> Message + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message + func setChatDraftMessage(chatId: Int64, draftText: String) async throws + /// Marks the given message ids as viewed (read). `forceRead: false` matches + /// the "user is currently looking at the chat" semantics — TDLib uses the + /// `openChat` state to decide whether to actually mark read. + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws + /// Casts (or changes) the current user's answer to a poll. `optionIds` are + /// 0-based option positions; multiple ids only when the poll allows it. + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws + /// Sends an installed sticker by its remote file id (`InputFileRemote`). + /// No local download is required — TDLib resolves the remote reference. + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message + /// Sends the given coordinate as a static location message (`livePeriod` 0). + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message +} + +struct TDLibChatHistoryLoader: ChatHistoryLoader { + let client: TDLibClient + + func openChat(chatId: Int64) async throws { + _ = try await client.openChat(chatId: chatId) + } + + func closeChat(chatId: Int64) async throws { + _ = try await client.closeChat(chatId: chatId) + } + + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { + // Resilient path: skip the atomic `client.getChatHistory(...)` decode (which + // throws on the first message TDLibKit can't model) and decode messages + // one-by-one, dropping any that fail. TDLib evolves faster than the pinned + // TDLibKit version, so some chats contain message types (e.g. `messageGift` + // with a `gift.background` that TDLibKit expects but the server no longer + // provides) that would otherwise break the entire chat load. + try await loadHistoryResilient( + chatId: chatId, + fromMessageId: fromMessageId, + offset: offset, + limit: limit + ) + } + + private func loadHistoryResilient( + chatId: Int64, + fromMessageId: Int64, + offset: Int, + limit: Int + ) async throws -> [Message] { + let query = GetChatHistory( + chatId: chatId, + fromMessageId: fromMessageId, + limit: limit, + offset: offset, + onlyLocal: false + ) + let dto = DTO(query, encoder: client.encoder) + let data: Data = try await withCheckedThrowingContinuation { continuation in + do { + try client.send(query: dto) { responseData in + continuation.resume(returning: responseData) + } + } catch { + continuation.resume(throwing: error) + } + } + return decodeMessagesLeniently(data, chatId: chatId) + } + + private func decodeMessagesLeniently(_ data: Data, chatId: Int64) -> [Message] { + let logger = Logger(subsystem: "org.telegram.TelegramWatch", category: "chathistory") + guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + logger.warning("getChatHistory chatId=\(chatId, privacy: .public) — response was not JSON object") + return [] + } + if let type = json["@type"] as? String, type == "error" { + logger.warning("getChatHistory chatId=\(chatId, privacy: .public) — TDLib error response: \(String(describing: json), privacy: .public)") + return [] + } + guard let rawMessages = json["messages"] as? [[String: Any]] else { + return [] + } + var decoded: [Message] = [] + decoded.reserveCapacity(rawMessages.count) + for (idx, raw) in rawMessages.enumerated() { + // Re-serialize each message dict to Data so TDLibKit's decoder can + // consume it the same way it would the full Messages response. + guard let perMessageData = try? JSONSerialization.data(withJSONObject: raw) else { + logger.warning("getChatHistory chatId=\(chatId, privacy: .public) — re-serialize failed at index=\(idx, privacy: .public)") + continue + } + do { + let m = try client.decoder.decode(Message.self, from: perMessageData) + decoded.append(m) + } catch { + logger.warning("getChatHistory chatId=\(chatId, privacy: .public) — skipping message index=\(idx, privacy: .public) due to decode error: \(String(describing: error), privacy: .public)") + } + } + return decoded + } + + func downloadFile(fileId: Int, priority: Int) async throws -> File { + // synchronous=false → TDLib returns immediately and streams progress via updateFile. + try await client.downloadFile( + fileId: fileId, + limit: 0, + offset: 0, + priority: priority, + synchronous: false + ) + } + + func cancelDownloadFile(fileId: Int) async throws { + // onlyIfPending=false → cancel even if active. + _ = try await client.cancelDownloadFile(fileId: fileId, onlyIfPending: false) + } + + func sendText(chatId: Int64, text: String) async throws -> Message { + try await client.sendMessage( + chatId: chatId, + inputMessageContent: .inputMessageText(InputMessageText( + clearDraft: true, + linkPreviewOptions: nil, + text: FormattedText(entities: [], text: text) + )), + options: nil, + replyMarkup: nil, + replyTo: nil, + topicId: nil + ) + } + + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { + try await client.sendMessage( + chatId: chatId, + inputMessageContent: .inputMessageVoiceNote(InputMessageVoiceNote( + caption: nil, + duration: duration, + selfDestructType: nil, + voiceNote: .inputFileLocal(InputFileLocal(path: fileURL.path)), + waveform: waveform + )), + options: nil, + replyMarkup: nil, + replyTo: nil, + topicId: nil + ) + } + + func setChatDraftMessage(chatId: Int64, draftText: String) async throws { + let draft: DraftMessage? = draftText.isEmpty ? nil : DraftMessage( + date: Int(Foundation.Date().timeIntervalSince1970), + effectId: 0, + inputMessageText: .inputMessageText(InputMessageText( + clearDraft: false, + linkPreviewOptions: nil, + text: FormattedText(entities: [], text: draftText) + )), + replyTo: nil, + suggestedPostInfo: nil + ) + _ = try await client.setChatDraftMessage( + chatId: chatId, + draftMessage: draft, + topicId: nil + ) + } + + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws { + _ = try await client.viewMessages( + chatId: chatId, + forceRead: forceRead, + messageIds: messageIds, + source: nil + ) + } + + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws { + _ = try await client.setPollAnswer(chatId: chatId, messageId: messageId, optionIds: optionIds) + } + + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + try await client.sendMessage( + chatId: chatId, + inputMessageContent: .inputMessageSticker(InputMessageSticker( + emoji: emoji, + height: height, + sticker: .inputFileRemote(InputFileRemote(id: remoteFileId)), + thumbnail: nil, + width: width + )), + options: nil, + replyMarkup: nil, + replyTo: nil, + topicId: nil + ) + } + + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { + try await client.sendMessage( + chatId: chatId, + inputMessageContent: .inputMessageLocation(InputMessageLocation( + heading: 0, + livePeriod: 0, + location: Location(horizontalAccuracy: 0, latitude: latitude, longitude: longitude), + proximityAlertRadius: 0 + )), + options: nil, + replyMarkup: nil, + replyTo: nil, + topicId: nil + ) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryStore.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryStore.swift new file mode 100644 index 0000000000..e5101e453a --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ChatHistoryStore.swift @@ -0,0 +1,678 @@ +import Foundation +import Observation +import OSLog +import TDShim + +@Observable +@MainActor +final class ChatHistoryStore { + + private(set) var rows: [MessageRow] = [] + private(set) var loadState: LoadState = .notStarted + private(set) var lastSendError: String? = nil + private(set) var draftText: String = "" + private(set) var unseenNewerCount: Int = 0 + /// Live mutable window — populated by start() and pagination methods. + /// View reads from this for `initialScrollTargetId`, `reachesChatTail`, etc. + private(set) var window: MessageWindow + + /// Frozen `lastReadInboxMessageId` from chat-metadata at construction. Always + /// set (may be 0 for a brand-new chat). Used as the mark-as-read gate. + /// Distinct from `window.unreadDividerAfterId` which is nil when no unreads. + let unreadDividerAfterIdSnapshot: Int64 + + /// Highest message id the recipient has read on the outgoing side. Drives the + /// unread-outgoing dot. Updated live via `updateChatReadOutbox`. + private(set) var lastReadOutboxMessageId: Int64 + + private let chatId: Int64 + private let chatType: ChatType + private let chatTailIdAtOpen: Int64? + private let loader: ChatHistoryLoader + private let selfUserId: Int64? + private let logger = Logger(subsystem: "org.telegram.TelegramWatch", category: "chathistory") + + let voicePlayback: VoicePlaybackController + let audioPlayback: AudioPlaybackController + + private let userNames: UserNamesStore + private var files: [Int: File] = [:] + private var trackedFileIds: Set = [] + private var openChatSent: Bool = false + private var closePending: Bool = false + private var isLoading: Bool = false + + private static let halfLimit: Int = 15 + + /// See `ChatListStore.coalesceUpdates` for rationale. Production + /// (MessageListView) opts in; tests default to false. + private let coalesceUpdates: Bool + private var reprojectPending = false + + #if DEBUG + private(set) var debugReprojectCount: Int = 0 + #endif + + init( + chatId: Int64, + chatType: ChatType, + lastReadInboxMessageId: Int64, + lastReadOutboxMessageId: Int64 = 0, + unreadCount: Int, + lastMessageId: Int64?, + loader: ChatHistoryLoader, + selfUserId: Int64? = nil, + userNames: UserNamesStore? = nil, + draftText: String = "", + coalesceUpdates: Bool = false, + voicePlayback: VoicePlaybackController? = nil, + audioPlayback: AudioPlaybackController? = nil + ) { + self.chatId = chatId + self.chatType = chatType + self.chatTailIdAtOpen = lastMessageId + self.loader = loader + self.selfUserId = selfUserId + self.userNames = userNames ?? UserNamesStore() + self.voicePlayback = voicePlayback ?? VoicePlaybackController( + backend: AVEngineBackend(), + decoder: OpusDecoderAdapter() + ) + self.audioPlayback = audioPlayback ?? AudioPlaybackController(backend: AVPlayerBackend()) + self.draftText = draftText + self.coalesceUpdates = coalesceUpdates + self.unreadDividerAfterIdSnapshot = lastReadInboxMessageId + self.lastReadOutboxMessageId = lastReadOutboxMessageId + let anchor: MessageWindow.Anchor + let dividerAfter: Int64? + if unreadCount > 0 { + anchor = .messageId(lastReadInboxMessageId) + dividerAfter = lastReadInboxMessageId + } else { + anchor = .tail + dividerAfter = nil + } + self.window = MessageWindow( + anchor: anchor, + halfLimit: Self.halfLimit, + unreadDividerAfterId: dividerAfter + ) + self.loadState = .loadingFirstPage + } + + func start() async { + guard !isLoading else { return } + isLoading = true + loadState = .loadingFirstPage + lastSendError = nil + unseenNewerCount = 0 + defer { isLoading = false } + do { + if !openChatSent { + try await loader.openChat(chatId: chatId) + openChatSent = true + } + try await loadInitialWindow() + loadState = .loaded + } catch is CancellationError { + return + } catch { + // Dump the full error description for DecodingError diagnostics — + // the default `localizedDescription` collapses to a generic + // "The data couldn't be read" string that hides the key/path. + logger.warning("loadInitialWindow failed chatId=\(self.chatId, privacy: .public) error=\(String(describing: error), privacy: .public)") + loadState = .failed(humanMessage(error)) + } + } + + /// History-only initial load with NO side effects (no `openChat`). Safe to run + /// before the user has committed to the chat (e.g. pre-warm on tap). Mirrors the + /// load half of `start()`; `activate()` performs the `openChat` half separately. + func warm() async { + guard !isLoading else { return } + isLoading = true + loadState = .loadingFirstPage + lastSendError = nil + unseenNewerCount = 0 + defer { isLoading = false } + do { + try await loadInitialWindow() + loadState = .loaded + } catch is CancellationError { + return + } catch { + logger.warning("warm failed chatId=\(self.chatId, privacy: .public) error=\(String(describing: error), privacy: .public)") + loadState = .failed(humanMessage(error)) + } + } + + /// Notifies TDLib the chat is being viewed (`openChat`), guarded to run once. + /// Called from the view's `.task` once the user actually lands on the chat. + /// An `openChat` failure is logged but does NOT blank already-loaded history — + /// the worst case is delayed supergroup/channel live updates, not a dead screen. + func activate() async { + guard !openChatSent, !closePending else { return } + do { + try await loader.openChat(chatId: chatId) + openChatSent = true + // If stop() landed while openChat was in flight (the view popped during the + // round-trip), honor the close now so the chat doesn't leak open on TDLib. + if closePending { + try? await loader.closeChat(chatId: chatId) + openChatSent = false + closePending = false + } + } catch { + logger.warning("openChat chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + + /// Anchored initial fill: when `unreadDividerAfterId != nil`, we call + /// `getChatHistory(fromMessageId: divider, offset: -(halfLimit+1), limit: 2*halfLimit)` + /// → up to (halfLimit+1) newer + divider + ~(halfLimit-1) older. Otherwise we + /// fetch from the tail with offset 0. Loop retries on the same anchor up to + /// 10× (TDLib returns < requested on cold cache). + private func loadInitialWindow() async throws { + let targetCount = 2 * Self.halfLimit + let maxIterations = 10 + var iter = 0 + while !Task.isCancelled, iter < maxIterations, window.cache.count < targetCount { + iter += 1 + let from: Int64 + let offset: Int + switch window.anchor { + case .tail: + from = window.loadedLowestId ?? 0 + offset = 0 + case .messageId(let anchorId): + if window.loadedHighestId == nil { + from = anchorId + offset = -(Self.halfLimit + 1) + } else if let highest = window.loadedHighestId, highest <= anchorId { + // Still missing newer side; pull from anchor with negative offset. + from = anchorId + offset = -(Self.halfLimit + 1) + } else { + from = window.loadedLowestId ?? anchorId + offset = 0 + } + } + let limit = max(1, targetCount - window.cache.count) + let messages = try await loader.loadHistory( + chatId: chatId, fromMessageId: from, offset: offset, limit: limit + ) + logger.info("loadInitialWindow iter=\(iter, privacy: .public) returned=\(messages.count, privacy: .public)") + if messages.isEmpty { + // Empty terminator. For a `.tail`-anchored initial fill, an empty + // batch on `fromMessageId = loadedHighestId` means TDLib has nothing + // newer than what we already have — i.e. we are at the chat tail. + if case .tail = window.anchor { window.markReachesChatTail() } + break + } + let cached = messages.map(CachedMessage.init) + for m in messages { primeFiles(from: m.content) } + window.extendInitial(cached, chatTailId: chatTailIdAtOpen) + // For a `.tail`-anchored fill, the very first batch (`from == 0`) is + // by definition anchored at the chat tail — TDLib returns the latest + // messages in descending id order. Mark `reachesChatTail` so live + // `updateNewMessage` can extend the window without spurious gap probes. + if case .tail = window.anchor, from == 0 { window.markReachesChatTail() } + reproject() + } + } + + func stop() async { + voicePlayback.tearDown() + audioPlayback.tearDown() + // Drain task was scheduled by markVisible during scroll; cancel so it + // doesn't fire viewMessages against a soon-to-be-closed chat. + viewDrainTask?.cancel() + viewDrainTask = nil + pendingViewIds.removeAll() + guard openChatSent else { + // activate() may have openChat in flight (the view popped before it + // returned). Mark so activate() closes the chat once openChat completes — + // otherwise it leaks open on TDLib. + closePending = true + return + } + do { + try await loader.closeChat(chatId: chatId) + } catch { + logger.warning("closeChat chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + + // MARK: - Pagination + + private(set) var lastPaginationError: String? = nil + private var isLoadingOlder = false + private var isLoadingNewer = false + private var olderFailureStreak = 0 + private var newerFailureStreak = 0 + private static let paginationLimit = 30 + private static let failureStreakCap = 3 + + func loadOlder() async { + guard !isLoadingOlder, window.hasOlder, let from = window.loadedLowestId else { return } + isLoadingOlder = true + defer { isLoadingOlder = false } + do { + let messages = try await loader.loadHistory( + chatId: chatId, fromMessageId: from, offset: 0, limit: Self.paginationLimit + ) + for m in messages { primeFiles(from: m.content) } + window.extendOlder(messages.map(CachedMessage.init)) + olderFailureStreak = 0 + logger.info("loadOlder chatId=\(self.chatId, privacy: .public) returned=\(messages.count, privacy: .public)") + reproject() + } catch { + olderFailureStreak += 1 + logger.warning("loadOlder failure streak=\(self.olderFailureStreak, privacy: .public): \(error.localizedDescription, privacy: .public)") + if olderFailureStreak >= Self.failureStreakCap { + window.markHasOlderFalse() + lastPaginationError = humanMessage(error) + } + } + } + + func loadNewer() async { + guard !isLoadingNewer, !window.reachesChatTail, let from = window.loadedHighestId else { return } + isLoadingNewer = true + defer { isLoadingNewer = false } + do { + let messages = try await loader.loadHistory( + chatId: chatId, fromMessageId: from, offset: -Self.paginationLimit, limit: Self.paginationLimit + ) + for m in messages { primeFiles(from: m.content) } + window.extendNewer(messages.map(CachedMessage.init), chatTailId: chatTailIdAtOpen) + newerFailureStreak = 0 + reproject() + } catch { + newerFailureStreak += 1 + logger.warning("loadNewer failure streak=\(self.newerFailureStreak, privacy: .public): \(error.localizedDescription, privacy: .public)") + if newerFailureStreak >= Self.failureStreakCap { + window.markReachesChatTail() + lastPaginationError = humanMessage(error) + } + } + } + + func dismissPaginationError() { + lastPaginationError = nil + olderFailureStreak = 0 + newerFailureStreak = 0 + } + + // MARK: - Mark-as-read + + private var pendingViewIds: Set = [] + private var viewDrainTask: Task? = nil + private static let viewDrainDelayNs: UInt64 = 300_000_000 // 300ms + + func markVisible(messageId: Int64) { + pendingViewIds.insert(messageId) + viewDrainTask?.cancel() + viewDrainTask = Task { [weak self] in + try? await Task.sleep(nanoseconds: Self.viewDrainDelayNs) + if Task.isCancelled { return } + await self?.drainPendingViews() + } + } + + private func drainPendingViews() async { + let ids = pendingViewIds.sorted() + pendingViewIds.removeAll() + guard !ids.isEmpty else { return } + do { + try await loader.viewMessages(chatId: chatId, messageIds: ids, forceRead: false) + } catch { + logger.warning("viewMessages chatId=\(self.chatId, privacy: .public) ids=\(ids.count, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + + // MARK: - Jump-to-bottom + + private var isJumpingToBottom = false + + /// Caller (the view) is responsible for the actual `proxy.scrollTo` after this + /// returns. Two paths: + /// - `reachesChatTail` already true: cheap; just zero the counter. + /// - else: clear window, re-build at the tail, then return. + func jumpToBottom() async { + if window.reachesChatTail { + unseenNewerCount = 0 + return + } + guard !isJumpingToBottom else { return } + isJumpingToBottom = true + defer { isJumpingToBottom = false } + + // Rebuild window with anchor=.tail and no divider. + window = MessageWindow(anchor: .tail, halfLimit: Self.halfLimit, unreadDividerAfterId: nil) + unseenNewerCount = 0 + + do { + try await loadInitialWindow() + loadState = .loaded + } catch is CancellationError { + return + } catch { + loadState = .failed(humanMessage(error)) + } + } + + // MARK: - Send / draft + + func sendText(_ text: String) async { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + lastSendError = nil + // Pull-to-tail: sending implicitly means "user is at the bottom". + // Anything beyond the current loaded window is no longer a "gap" from the + // user's perspective, so we flip reachesChatTail and clear the counter + // BEFORE the optimistic updateNewMessage from sendMessage lands. + window.markReachesChatTail() + unseenNewerCount = 0 + do { + _ = try await loader.sendText(chatId: chatId, text: trimmed) + } catch { + lastSendError = humanMessage(error) + logger.warning("sendText chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + + func sendVoiceNote(_ draft: VoiceRecordingDraft) async -> Bool { + lastSendError = nil + window.markReachesChatTail() + unseenNewerCount = 0 + do { + _ = try await loader.sendVoiceNote( + chatId: chatId, + fileURL: draft.fileURL, + duration: draft.duration, + waveform: draft.waveform + ) + return true + } catch { + lastSendError = humanMessage(error) + logger.warning("sendVoiceNote chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + return false + } + } + + /// Sends an installed sticker (chosen in the picker) by remote file id. + /// Mirrors `sendVoiceNote`: pull-to-tail, clear unseen, capture error. + func sendSticker(_ sticker: PickerSticker) async -> Bool { + lastSendError = nil + window.markReachesChatTail() + unseenNewerCount = 0 + do { + _ = try await loader.sendSticker( + chatId: chatId, + remoteFileId: sticker.remoteFileId, + emoji: sticker.emoji, + width: sticker.width, + height: sticker.height + ) + return true + } catch { + lastSendError = humanMessage(error) + logger.warning("sendSticker chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + return false + } + } + + /// Sends the user's current coordinate as a static location. + /// Mirrors `sendSticker`: pull-to-tail, clear unseen, capture error. + func sendLocation(latitude: Double, longitude: Double) async -> Bool { + lastSendError = nil + window.markReachesChatTail() + unseenNewerCount = 0 + do { + _ = try await loader.sendLocation(chatId: chatId, latitude: latitude, longitude: longitude) + return true + } catch { + lastSendError = humanMessage(error) + logger.warning("sendLocation chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + return false + } + } + + /// Casts the user's poll answer. `optionIds` are 0-based option positions. + /// Mirrors `sendText`'s error capture; returns success. Result refresh comes + /// asynchronously via `updatePoll`. + func setPollAnswer(messageId: Int64, optionIds: [Int]) async -> Bool { + lastSendError = nil + do { + try await loader.setPollAnswer(chatId: chatId, messageId: messageId, optionIds: optionIds) + return true + } catch { + lastSendError = humanMessage(error) + logger.warning("setPollAnswer chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + return false + } + } + + /// Looks up the currently-projected poll for a message id (for the Vote + /// screen's post-vote quiz reveal — reads the latest `updatePoll`-patched state). + func poll(forMessageId id: Int64) -> PollVisual? { + for row in rows { + if case .bubble(let b) = row, b.messageId == id { return b.poll } + } + return nil + } + + func dismissSendError() { lastSendError = nil } + + func saveDraft(_ text: String) async { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed == draftText { return } + draftText = trimmed + do { + try await loader.setChatDraftMessage(chatId: chatId, draftText: trimmed) + } catch { + logger.warning("setChatDraftMessage chatId=\(self.chatId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + + func fileSnapshot(fileId: Int) -> File? { files[fileId] } + + func requestFileDownload(fileId: Int, priority: Int = 1) { + trackedFileIds.insert(fileId) + logger.info("requestFileDownload fileId=\(fileId, privacy: .public) priority=\(priority, privacy: .public)") + Task { [logger, loader] in + do { + _ = try await loader.downloadFile(fileId: fileId, priority: priority) + } catch { + logger.warning("downloadFile fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + func togglePlayback(_ note: VoiceNoteVisual) { + audioPlayback.tearDown() + if note.localPath == nil { + requestFileDownload(fileId: note.voiceFileId, priority: 2) + } + voicePlayback.toggle(note: note) + } + + func toggleAudioPlayback(_ audio: AudioVisual) { + voicePlayback.tearDown() + if audio.localPath == nil { + requestFileDownload(fileId: audio.audioFileId, priority: 2) + } + audioPlayback.toggle(audio: audio) + } + + func cancelFileDownload(fileId: Int) { + trackedFileIds.remove(fileId) + logger.info("cancelFileDownload fileId=\(fileId, privacy: .public)") + Task { [logger, loader] in + do { + try await loader.cancelDownloadFile(fileId: fileId) + } catch { + logger.warning("cancelDownloadFile fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + // MARK: - Update dispatch + + func handle(_ update: Update) { + switch update { + case .updateNewMessage(let upd) where upd.message.chatId == chatId: + let cached = CachedMessage(upd.message) + let inserted = window.tryInsertLive(cached) + if inserted { + primeFiles(from: upd.message.content) + scheduleReproject() + } else { + unseenNewerCount += 1 + } + case .updateMessageSendSucceeded(let upd) where upd.message.chatId == chatId: + guard window.cache[upd.oldMessageId] != nil else { return } + window.applySendSucceeded(oldId: upd.oldMessageId, message: CachedMessage(upd.message)) + primeFiles(from: upd.message.content) + scheduleReproject() + case .updateMessageSendFailed(let upd) where upd.message.chatId == chatId: + guard window.cache[upd.oldMessageId] != nil else { return } + window.applySendFailed(oldId: upd.oldMessageId, message: CachedMessage(upd.message)) + scheduleReproject() + logger.warning("send failed chatId=\(self.chatId, privacy: .public) errorCode=\(upd.error.code, privacy: .public) error=\(upd.error.message, privacy: .public)") + case .updateMessageContent(let upd) where upd.chatId == chatId: + window.applyContentUpdate(id: upd.messageId, newContent: upd.newContent) + primeFiles(from: upd.newContent) + scheduleReproject() + case .updateDeleteMessages(let upd) where upd.chatId == chatId && upd.isPermanent: + window.applyDelete(ids: upd.messageIds) + scheduleReproject() + case .updateUser: + // UserNamesStore (owned by TDClient) absorbed the update before + // it reached us. Just repaint with the new cache snapshot. + scheduleReproject() + case .updatePoll(let upd): + window.applyPollUpdate(poll: upd.poll) + scheduleReproject() + case .updateFile(let upd): + files[upd.file.id] = upd.file + if trackedFileIds.contains(upd.file.id) { scheduleReproject() } + case .updateChatReadOutbox(let upd) where upd.chatId == chatId: + lastReadOutboxMessageId = upd.lastReadOutboxMessageId + scheduleReproject() + default: + break + } + } + + // MARK: - File priming (unchanged from prior implementation) + + private func primeFiles(from content: MessageContent) { + switch content { + case .messagePhoto(let m): + guard let size = selectPhotoSize(m.photo.sizes) else { return } + primeFile(size.photo) + case .messageVideo(let m): + let chosen = selectVideoQuality(primary: m.video, alternatives: m.alternativeVideos) + primeFile(chosen.file) + if let cover = m.cover, let s = selectVideoCoverSize(cover.sizes) { + primeFile(s.photo) + } else if let thumb = m.video.thumbnail { + primeFile(thumb.file) + } + case .messageVideoNote(let m): + primeFile(m.videoNote.video) + if let thumb = m.videoNote.thumbnail { + primeFile(thumb.file) + } + case .messageSticker(let m): + primeFile(m.sticker.sticker) + if let thumb = m.sticker.thumbnail, + thumbnailFormatKind(thumb.format) != .unsupported { + primeFile(thumb.file) + } + case .messageVoiceNote(let m): + primeFile(m.voiceNote.voice) + case .messageAudio(let m): + primeFile(m.audio.audio) + default: + break + } + } + + private func primeFile(_ file: File) { + if files[file.id]?.local.isDownloadingCompleted == true { return } + files[file.id] = file + } + + /// See `ChatListStore.scheduleReproject`. Tail-of-runloop deferral for + /// production; passthrough for tests (default). + private func scheduleReproject() { + guard coalesceUpdates else { + reproject() + return + } + guard !reprojectPending else { return } + reprojectPending = true + DispatchQueue.main.async { [weak self] in + guard let self else { return } + guard self.reprojectPending else { return } + self.reproject() + } + } + + #if DEBUG + /// Synchronously runs any pending coalesced reproject. Test hook. + func flushPendingReproject() { + guard reprojectPending else { return } + reproject() + } + #endif + + private func reproject() { + reprojectPending = false + #if DEBUG + debugReprojectCount += 1 + #endif + rows = messageRows( + messages: Array(window.cache.values), + userNames: userNames.names, + fileLocals: files, + chatType: chatType, + chatId: chatId, + today: Foundation.Date(), + calendar: Calendar.current, + selfUserId: selfUserId, + unreadDividerAfterId: window.unreadDividerAfterId, + lastReadOutboxMessageId: lastReadOutboxMessageId + ) + // If a voice playback is awaiting its file, advance it now that the + // projection has the freshest `localPath`. + if case .preparing(let pendingId, _) = voicePlayback.state { + for row in rows { + if case .bubble(let b) = row, let v = b.voiceNote, v.voiceFileId == pendingId { + voicePlayback.resumeIfReady(note: v) + break + } + } + } + // Same resume-on-file-arrival hook for music playback. + if case .preparing(let pendingId) = audioPlayback.state { + for row in rows { + if case .bubble(let b) = row, let a = b.audio, a.audioFileId == pendingId { + audioPlayback.resumeIfReady(audio: a) + break + } + } + } + } +} + +#if DEBUG +extension ChatHistoryStore { + /// Test-only: prime the window's `reachesChatTail` so `updateNewMessage` + /// inserts without a prior `start()` call. Never call from production code. + func testHook_markReachesChatTail() { window.markReachesChatTail() } +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/DaySeparatorView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/DaySeparatorView.swift new file mode 100644 index 0000000000..1d92117d18 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/DaySeparatorView.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct DaySeparatorView: View { + let label: DayLabel + + var body: some View { + HStack { + Spacer() + Text(label.label) + .font(.caption2) + .foregroundStyle(.secondary) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .background(Capsule().fill(Color.gray.opacity(0.2))) + Spacer() + } + .padding(.vertical, 2) + .accessibilityIdentifier("day.\(label.key)") + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentBubbleView.swift new file mode 100644 index 0000000000..b056d22dd6 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentBubbleView.swift @@ -0,0 +1,87 @@ +import SwiftUI + +/// Renders one generic-document bubble: BubbleStyle chrome with a rounded-square doc icon, +/// filename, size, optional caption. Display-only (no download/open on +/// watch in milestone #4). +struct DocumentBubbleView: View { + let document: DocumentVisual + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + @Environment(\.bubbleMetrics) private var metrics + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + } + HStack(spacing: 8) { + iconSquare + VStack(alignment: .leading, spacing: 1) { + Text(document.fileName) + .font(.caption).bold() + .lineLimit(1) + .truncationMode(.middle) + Text(formatFileSize(document.sizeBytes)) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + } + Spacer(minLength: 0) + } + if !document.caption.isEmpty { + Text(document.caption) + .font(.caption) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + .frame(minWidth: BubbleShape.minSize, maxWidth: metrics.bubbleMaxWidth, minHeight: BubbleShape.minSize, alignment: .leading) + .background(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius).fill(style.fill)) + .foregroundStyle(style.content) + } + + private var iconSquare: some View { + ZStack { + RoundedRectangle(cornerRadius: 8).fill(style.playFill) + Image(systemName: "doc.fill").font(.system(size: 16)).foregroundStyle(style.playIcon) + } + .frame(width: 36, height: 36) + } +} + +#if DEBUG +#Preview("Document — incoming") { + DocumentBubbleView( + document: DocumentVisual(documentFileId: 1, fileName: "report.pdf", + sizeBytes: 2_400_000, localPath: nil, caption: ""), + isOutgoing: false, replyHeader: nil + ).bubblePreview() +} + +#Preview("Document — outgoing, long name") { + DocumentBubbleView( + document: DocumentVisual(documentFileId: 2, fileName: "quarterly-financial-summary-2026.xlsx", + sizeBytes: 52_428_800, localPath: nil, caption: ""), + isOutgoing: true, replyHeader: nil + ).bubblePreview() +} + +#Preview("Document — with caption") { + DocumentBubbleView( + document: DocumentVisual(documentFileId: 3, fileName: "archive.zip", + sizeBytes: 52_428_800, localPath: nil, caption: "here are the files"), + isOutgoing: false, replyHeader: nil + ).bubblePreview() +} + +#Preview("Document — with reply") { + DocumentBubbleView( + document: DocumentVisual(documentFileId: 4, fileName: "notes.txt", + sizeBytes: 1024, localPath: nil, caption: ""), + isOutgoing: false, + replyHeader: ReplyHeader(senderName: "Bob", snippet: "the doc", minithumbnail: nil, isOutgoing: false) + ).bubblePreview() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentVisual.swift new file mode 100644 index 0000000000..9a9dfe2d94 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/DocumentVisual.swift @@ -0,0 +1,18 @@ +import Foundation + +/// Display model for a `messageDocument` (generic file). Display-only on watch in milestone #4. +struct DocumentVisual: Equatable, Hashable { + let documentFileId: Int + let fileName: String + let sizeBytes: Int64 + let localPath: String? + let caption: String +} + +/// Human-readable byte count, e.g. "50 MB". +func formatFileSize(_ bytes: Int64) -> String { + let f = ByteCountFormatter() + f.countStyle = .file + f.allowsNonnumericFormatting = false + return f.string(fromByteCount: bytes) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/EmojiText.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/EmojiText.swift new file mode 100644 index 0000000000..5049ab5411 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/EmojiText.swift @@ -0,0 +1,28 @@ +import Foundation + +extension Character { + /// True for emoji grapheme clusters: a scalar with emoji presentation, a multi-scalar + /// cluster led by an emoji scalar (ZWJ sequences, keycaps, skin-tone), or a flag (a pair + /// of regional-indicator scalars). Plain ASCII digits / `#` / `*` are excluded. + var isEmojiCharacter: Bool { + guard let first = unicodeScalars.first else { return false } + if first.properties.isEmojiPresentation { return true } + if first.properties.isEmoji && unicodeScalars.count > 1 { return true } + if (0x1F1E6...0x1F1FF).contains(first.value) { return true } // regional indicator (flags) + return false + } +} + +/// Emoji count if `text` (trimmed) consists solely of 1...3 emoji characters; else nil. +/// Used to render short emoji-only messages as jumbo, bubble-less emoji. +func emojiOnlyCount(_ text: String) -> Int? { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return nil } + var count = 0 + for ch in trimmed { + guard ch.isEmojiCharacter else { return nil } + count += 1 + if count > 3 { return nil } + } + return count +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/LocationBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationBubbleView.swift new file mode 100644 index 0000000000..faba8db478 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationBubbleView.swift @@ -0,0 +1,227 @@ +import SwiftUI +import MapKit +import CoreLocation +import UIKit + +/// Renders one location / venue bubble: a rounded static map image +/// (`MKMapSnapshotter`) with a centered pin, a "LIVE" badge for live +/// locations. The snapshot renders in `.task(id:)`, +/// keyed by the cache key, so live-location coordinate changes re-render +/// automatically. +/// +/// A plain static location is chrome-less (bare map). Venues and live +/// locations sit inside a gray (incoming) / accent (outgoing) chrome bubble: +/// venues add a title/address card, live locations a relative "Updated …" +/// caption that ticks once a minute. +/// +/// Tap builds an `MKMapItem` and hands the coordinate to the system Maps app. +struct LocationBubbleView: View { + let location: LocationVisual + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + @Environment(\.bubbleMetrics) private var metrics + @State private var snapshot: UIImage? + + // The map always spans the full content width. In a chrome bubble it sits flush to the + // bubble edges (like photo/video); only the caption text is padded. + private var mapWidth: CGFloat { metrics.mapWidth } + private let mapHeight: CGFloat = 110 + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + private var cacheKey: String { + MapSnapshotRenderer.cacheKey( + latitude: location.latitude, longitude: location.longitude, + width: Int(mapWidth), height: Int(mapHeight) + ) + } + + /// A caption (venue title/address or live "Live Location") gives the bubble chrome, with the + /// map flush to the edges. A plain static location stays chrome-less (bare rounded map). + private var hasChrome: Bool { location.title != nil || location.isLive } + + var body: some View { + Group { + if hasChrome { chromeBubble } else { plainBubble } + } + .task(id: cacheKey) { + snapshot = await MapSnapshotRenderer.image( + latitude: location.latitude, longitude: location.longitude, + size: CGSize(width: mapWidth, height: mapHeight) + ) + } + } + + private var plainBubble: some View { + VStack(alignment: isOutgoing ? .trailing : .leading, spacing: 2) { + replyMiniCard + mapView(roundedCorners: true) + } + } + + private var chromeBubble: some View { + VStack(alignment: .leading, spacing: 2) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + .padding(.horizontal, 8) + .padding(.top, 6) + } + mapView(roundedCorners: false) + if let title = location.title, !title.isEmpty { + Text(title) + .font(.caption2).bold().lineLimit(1) + .padding(.horizontal, 8) + } + if let address = location.address, !address.isEmpty { + Text(address) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + .lineLimit(2) + .padding(.horizontal, 8) + } + if location.isLive { + Text("Live Location") + .font(.caption2).bold() + .padding(.horizontal, 8) + liveCaption.padding(.horizontal, 8) + } + } + .padding(.bottom, 6) + .frame(width: mapWidth, alignment: .leading) + .background(style.fill) + .foregroundStyle(style.content) + .clipShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + } + + @ViewBuilder + private var replyMiniCard: some View { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + .padding(6) + .background(RoundedRectangle(cornerRadius: 8).fill(style.fill)) + .frame(maxWidth: mapWidth) + } + } + + private func mapView(roundedCorners: Bool) -> some View { + mapImage + .frame(width: mapWidth, height: mapHeight) + .clipShape(RoundedRectangle(cornerRadius: roundedCorners ? BubbleShape.cornerRadius : 0)) + .overlay { pin } + .contentShape(Rectangle()) + .onTapGesture { openInMaps() } + } + + /// Relative "Updated …" caption for a live location; ticks once a minute so + /// "just now" decays to "Updated N minutes ago" even between TDLib updates. + private var liveCaption: some View { + TimelineView(.periodic(from: .now, by: 60)) { context in + Text(liveUpdatedCaption(updatedAt: location.liveUpdatedAt ?? context.date, now: context.date)) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + + @ViewBuilder + private var mapImage: some View { + if let snapshot { + Image(uiImage: snapshot).resizable().scaledToFill() + } else { + Color.gray.opacity(0.3) + } + } + + @ViewBuilder + private var pin: some View { + if location.isLive { + ZStack { + Circle().fill(.white).frame(width: 18, height: 18) + Circle().fill(location.isExpired ? Color.gray : Color.accentColor).frame(width: 12, height: 12) + } + .shadow(radius: 2) + } else { + Image(systemName: "mappin.circle.fill") + .font(.system(size: 24)) + .foregroundStyle(.red) + .shadow(radius: 2) + } + } + + private func openInMaps() { + let mapItem = MKMapItem( + location: CLLocation(latitude: location.latitude, longitude: location.longitude), + address: nil + ) + mapItem.name = location.title + mapItem.openInMaps(launchOptions: nil) + } +} + +#if DEBUG +#Preview("Static location") { + LocationBubbleView( + location: LocationVisual( + latitude: 37.3349, longitude: -122.0090, + title: nil, address: nil, isLive: false, heading: 0, isExpired: false, + liveUpdatedAt: nil + ), + isOutgoing: false, replyHeader: nil + ) + .bubblePreview() +} + +#Preview("Venue") { + LocationBubbleView( + location: LocationVisual( + latitude: 48.8584, longitude: 2.2945, + title: "Eiffel Tower", address: "Champ de Mars, 75007 Paris", + isLive: false, heading: 0, isExpired: false, liveUpdatedAt: nil + ), + isOutgoing: true, replyHeader: nil + ) + .bubblePreview() +} + +#Preview("Live location — active") { + LocationBubbleView( + location: LocationVisual( + latitude: 37.3318, longitude: -122.0312, + title: nil, address: nil, isLive: true, heading: 90, isExpired: false, + liveUpdatedAt: Date().addingTimeInterval(-90) + ), + isOutgoing: false, replyHeader: nil + ) + .bubblePreview() +} + +#Preview("Live location — expired") { + LocationBubbleView( + location: LocationVisual( + latitude: 37.3318, longitude: -122.0312, + title: nil, address: nil, isLive: true, heading: 0, isExpired: true, + liveUpdatedAt: Date().addingTimeInterval(-7200) + ), + isOutgoing: false, replyHeader: nil + ) + .bubblePreview() +} + +#Preview("Location — incoming with reply") { + LocationBubbleView( + location: LocationVisual( + latitude: 37.3349, longitude: -122.0090, + title: nil, address: nil, isLive: false, heading: 0, isExpired: false, + liveUpdatedAt: nil + ), + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "where are you?", + minithumbnail: nil, + isOutgoing: false + ) + ) + .bubblePreview() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/LocationProvider.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationProvider.swift new file mode 100644 index 0000000000..a394ffa9b5 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationProvider.swift @@ -0,0 +1,66 @@ +import CoreLocation + +/// CoreLocation seam. Production uses `CLLocationProvider`; tests use a fake. +/// `@MainActor` (not `Sendable`) to mirror `RecordingBackend` — it's consumed +/// only from the `@MainActor LocationSendController`, and `CLLocationManager`'s +/// delegate callbacks arrive on the main run loop. +@MainActor +protocol LocationProviding: AnyObject { + var authorizationStatus: CLAuthorizationStatus { get } + /// Requests when-in-use authorization. Resolves once the user responds; + /// returns immediately with the current status if already determined. + func requestAuthorization() async -> CLAuthorizationStatus + /// One-shot location fix. Throws on failure/timeout (CLLocationManager's own). + func requestLocation() async throws -> CLLocationCoordinate2D +} + +@MainActor +final class CLLocationProvider: NSObject, LocationProviding { + private let manager = CLLocationManager() + private var authContinuation: CheckedContinuation? + private var locationContinuation: CheckedContinuation? + + override init() { + super.init() + manager.delegate = self + } + + var authorizationStatus: CLAuthorizationStatus { manager.authorizationStatus } + + func requestAuthorization() async -> CLAuthorizationStatus { + let current = manager.authorizationStatus + if current != .notDetermined { return current } + return await withCheckedContinuation { cont in + authContinuation = cont + manager.requestWhenInUseAuthorization() + } + } + + func requestLocation() async throws -> CLLocationCoordinate2D { + try await withCheckedThrowingContinuation { cont in + locationContinuation = cont + manager.requestLocation() + } + } +} + +extension CLLocationProvider: CLLocationManagerDelegate { + func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { + let status = manager.authorizationStatus + guard status != .notDetermined, let cont = authContinuation else { return } + authContinuation = nil + cont.resume(returning: status) + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + guard let coord = locations.last?.coordinate, let cont = locationContinuation else { return } + locationContinuation = nil + cont.resume(returning: coord) + } + + func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { + guard let cont = locationContinuation else { return } + locationContinuation = nil + cont.resume(throwing: error) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendController.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendController.swift new file mode 100644 index 0000000000..97f74dca08 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendController.swift @@ -0,0 +1,66 @@ +import CoreLocation +import Foundation + +enum LocationSendState: Equatable { + case idle + case requestingPermission + case locating + case ready(latitude: Double, longitude: Double) + case denied + case failed(String) +} + +/// Drives location acquisition for the send-location screen: requests +/// authorization if needed, then a one-shot fix. Owns acquisition only — the +/// send itself goes through `ChatHistoryStore.sendLocation` (mirrors how +/// `VoiceRecorder` handles capture and the store handles send). +@Observable @MainActor +final class LocationSendController { + private(set) var state: LocationSendState = .idle + private let provider: LocationProviding + + init(provider: LocationProviding) { + self.provider = provider + } + + func start() async { + // Ignore re-entry while a request is in flight or already done. + switch state { + case .requestingPermission, .locating, .ready: return + default: break + } + switch provider.authorizationStatus { + case .notDetermined: + state = .requestingPermission + let status = await provider.requestAuthorization() + guard status == .authorizedWhenInUse || status == .authorizedAlways else { + state = .denied + return + } + case .authorizedWhenInUse, .authorizedAlways: + break + case .denied, .restricted: + state = .denied + return + @unknown default: + state = .denied + return + } + await locate() + } + + func retry() async { + state = .idle + await start() + } + + private func locate() async { + state = .locating + do { + let coord = try await provider.requestLocation() + state = .ready(latitude: coord.latitude, longitude: coord.longitude) + } catch { + state = .failed("Couldn't get your location. Try again.") + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendView.swift new file mode 100644 index 0000000000..24d97ef1f6 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationSendView.swift @@ -0,0 +1,188 @@ +import SwiftUI +import UIKit +import CoreLocation + +/// Preview-and-confirm screen for sending the current location. On appear it +/// drives `LocationSendController` to acquire a one-shot fix, renders a map +/// preview of the position, and sends on confirm. Pushed inside the +/// AttachmentSheet's NavigationStack; `onComplete` dismisses the whole sheet. +struct LocationSendView: View { + /// Sends the chosen coordinate; returns true on success. + let onSend: (_ latitude: Double, _ longitude: Double) async -> Bool + /// Dismisses the whole attachment sheet (called after a successful send). + let onComplete: () -> Void + + @State private var controller: LocationSendController + @State private var sending = false + @State private var sendError: String? + + init( + onSend: @escaping (_ latitude: Double, _ longitude: Double) async -> Bool, + onComplete: @escaping () -> Void + ) { + self.onSend = onSend + self.onComplete = onComplete + _controller = State(wrappedValue: LocationSendController(provider: CLLocationProvider())) + } + + /// Injecting-init for previews/tests. + init( + controller: LocationSendController, + onSend: @escaping (_ latitude: Double, _ longitude: Double) async -> Bool = { _, _ in true }, + onComplete: @escaping () -> Void = {} + ) { + self.onSend = onSend + self.onComplete = onComplete + _controller = State(wrappedValue: controller) + } + + var body: some View { + ScrollView { + VStack(spacing: 12) { + switch controller.state { + case .idle, .requestingPermission, .locating: + locating + case .ready(let lat, let lon): + ready(latitude: lat, longitude: lon) + case .denied: + denied + case .failed(let reason): + failed(reason) + } + } + .padding() + } + .navigationTitle("Location") + .task { await controller.start() } + } + + private var locating: some View { + VStack(spacing: 8) { + ProgressView() + Text("Locating…").font(.caption).foregroundStyle(.secondary) + } + } + + private func ready(latitude: Double, longitude: Double) -> some View { + VStack(spacing: 10) { + LocationPreviewMap(latitude: latitude, longitude: longitude) + if let sendError { + Text(sendError).font(.caption2).foregroundStyle(.red) + } + Button { send(latitude: latitude, longitude: longitude) } label: { + if sending { ProgressView() } else { Label("Send", systemImage: "paperplane.fill") } + } + .buttonStyle(.borderedProminent) + .disabled(sending) + .accessibilityIdentifier("locationSend") + } + } + + private var denied: some View { + VStack(spacing: 8) { + Image(systemName: "location.slash").font(.system(size: 32)).foregroundStyle(.secondary) + Text("Location access is off. Enable it in Settings.") + .font(.caption).multilineTextAlignment(.center) + } + } + + private func failed(_ reason: String) -> some View { + VStack(spacing: 10) { + Text(reason).font(.caption).foregroundStyle(.red).multilineTextAlignment(.center) + Button("Retry") { Task { await controller.retry() } } + .buttonStyle(.bordered) + .accessibilityIdentifier("locationRetry") + } + } + + private func send(latitude: Double, longitude: Double) { + guard !sending else { return } + sending = true + sendError = nil + Task { + if await onSend(latitude, longitude) { + onComplete() + } else { + sendError = "Couldn't send. Try again." + sending = false + } + } + } +} + +/// Static map preview with a centered pin for the confirm screen. Mirrors +/// LocationBubbleView's map rendering (same `MapSnapshotRenderer`). +private struct LocationPreviewMap: View { + let latitude: Double + let longitude: Double + @State private var snapshot: UIImage? + + private let mapWidth: CGFloat = 150 + private let mapHeight: CGFloat = 120 + + private var cacheKey: String { + MapSnapshotRenderer.cacheKey( + latitude: latitude, longitude: longitude, + width: Int(mapWidth), height: Int(mapHeight) + ) + } + + var body: some View { + Group { + if let snapshot { + Image(uiImage: snapshot).resizable().scaledToFill() + } else { + Color.gray.opacity(0.3) + } + } + .frame(width: mapWidth, height: mapHeight) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .overlay { + Image(systemName: "mappin.circle.fill") + .font(.system(size: 24)).foregroundStyle(.red).shadow(radius: 2) + } + .task(id: cacheKey) { + snapshot = await MapSnapshotRenderer.image( + latitude: latitude, longitude: longitude, + size: CGSize(width: mapWidth, height: mapHeight) + ) + } + } +} + +#if DEBUG +/// Preview-only provider: drives the controller into a fixed state via `.task`. +@MainActor +private final class PreviewLocationProvider: LocationProviding { + var status: CLAuthorizationStatus + var result: Result + init(status: CLAuthorizationStatus, result: Result) { + self.status = status + self.result = result + } + var authorizationStatus: CLAuthorizationStatus { status } + func requestAuthorization() async -> CLAuthorizationStatus { status } + func requestLocation() async throws -> CLLocationCoordinate2D { try result.get() } +} + +@MainActor private func previewController( + status: CLAuthorizationStatus = .authorizedWhenInUse, + result: Result = .success(CLLocationCoordinate2D(latitude: 37.3349, longitude: -122.0090)) +) -> LocationSendController { + LocationSendController(provider: PreviewLocationProvider(status: status, result: result)) +} + +#Preview("Ready") { + NavigationStack { LocationSendView(controller: previewController()) } +} + +#Preview("Denied") { + NavigationStack { LocationSendView(controller: previewController(status: .denied, result: .failure(CancellationError()))) } +} + +#Preview("Failed") { + NavigationStack { + LocationSendView(controller: previewController(result: .failure(NSError(domain: "preview", code: 1)))) + } +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/LocationVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationVisual.swift new file mode 100644 index 0000000000..787b18879c --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/LocationVisual.swift @@ -0,0 +1,73 @@ +import Foundation +import TDShim + +/// Per-location data the bubble consumes. Projected from `messageLocation` +/// (static or live) and `messageVenue`. Mutually exclusive with the other +/// `MessageBubble` media fields. The map image itself is rendered locally by +/// `MapSnapshotRenderer` in the view layer — there is no TDLib file to plumb. +struct LocationVisual: Equatable, Hashable { + let latitude: Double + let longitude: Double + /// Venue name; nil for a plain location. Non-nil (even if "") marks a venue, + /// which is one of the two cases that render inside a chrome bubble. + let title: String? + /// Venue address; nil for a plain location. + let address: String? + /// True when `messageLocation.livePeriod != 0`; always false for a venue. + let isLive: Bool + /// Live-location direction in degrees (1–360); 0 = unknown / not live. + let heading: Int + /// True when a live location has stopped updating (`livePeriod != 0 && expiresIn == 0`). + let isExpired: Bool + /// Last-update time of a live location (its edit time, falling back to send + /// time); nil for static locations and venues. Drives the "Updated …" caption. + let liveUpdatedAt: Foundation.Date? +} + +/// Builds a `LocationVisual` for `messageLocation` / `messageVenue` content, +/// or returns `nil` for any other content. `messageDate` is the message's +/// last-update time (used as the live-location "Updated …" basis). Internal +/// (not `private`) so `LocationVisualTests` can exercise it directly via +/// `@testable import`, matching the `voiceNoteVisual` precedent. +func locationVisual(for content: MessageContent, messageDate: Foundation.Date? = nil) -> LocationVisual? { + switch content { + case .messageLocation(let m): + let isLive = m.livePeriod != 0 + return LocationVisual( + latitude: m.location.latitude, + longitude: m.location.longitude, + title: nil, + address: nil, + isLive: isLive, + heading: m.heading, + isExpired: isLive && m.expiresIn == 0, + liveUpdatedAt: isLive ? messageDate : nil + ) + case .messageVenue(let m): + return LocationVisual( + latitude: m.venue.location.latitude, + longitude: m.venue.location.longitude, + title: m.venue.title, + address: m.venue.address, + isLive: false, + heading: 0, + isExpired: false, + liveUpdatedAt: nil + ) + default: + return nil + } +} + +/// Relative "Updated …" caption for a live-location bubble. Pure so it's +/// deterministically unit-testable; the view feeds it a ticking `now`. +func liveUpdatedCaption(updatedAt: Foundation.Date, now: Foundation.Date) -> String { + let elapsed = max(0, now.timeIntervalSince(updatedAt)) + if elapsed < 60 { return "Updated just now" } + let minutes = Int(elapsed / 60) + if minutes < 60 { return "Updated \(minutes) minute\(minutes == 1 ? "" : "s") ago" } + let hours = minutes / 60 + if hours < 24 { return "Updated \(hours) hour\(hours == 1 ? "" : "s") ago" } + let days = hours / 24 + return "Updated \(days) day\(days == 1 ? "" : "s") ago" +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MapSnapshotRenderer.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MapSnapshotRenderer.swift new file mode 100644 index 0000000000..6e3a99430e --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MapSnapshotRenderer.swift @@ -0,0 +1,54 @@ +import Foundation +import MapKit +import UIKit + +/// Renders static map images for location/venue bubbles. `MKMapSnapshotter` +/// is the only real-tile path on watchOS (SwiftUI `Map` and `MKMapView` are +/// unavailable). Results are memoized in a process-wide `NSCache` keyed by a +/// quantized coordinate + size, so scrolling and reprojection don't re-render. +enum MapSnapshotRenderer { + // NSCache is thread-safe; no actor isolation needed. + private static let cache = NSCache() + + /// Map span shown in the bubble/snapshot, in meters (square). + private static let spanMeters: CLLocationDistance = 1000 + + /// Pure cache key. Coordinates are quantized to 5 decimal places (~1 m) so + /// float noise doesn't thrash the cache; width/height participate so the + /// bubble and any larger render stay distinct. + static func cacheKey(latitude: Double, longitude: Double, width: Int, height: Int) -> String { + let lat = (latitude * 100_000).rounded() / 100_000 + let lon = (longitude * 100_000).rounded() / 100_000 + return "\(lat),\(lon),\(width)x\(height)" + } + + /// Returns a snapshot image for the coordinate, or nil on failure. + static func image(latitude: Double, longitude: Double, size: CGSize) async -> UIImage? { + let key = cacheKey( + latitude: latitude, longitude: longitude, + width: Int(size.width.rounded()), height: Int(size.height.rounded()) + ) as NSString + if let cached = cache.object(forKey: key) { return cached } + + let options = MKMapSnapshotter.Options() + options.region = MKCoordinateRegion( + center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), + latitudinalMeters: spanMeters, + longitudinalMeters: spanMeters + ) + options.size = size + options.preferredConfiguration = MKStandardMapConfiguration() + + // MKMapSnapshotter retains itself while a render is in flight, so the local + // doesn't need an explicit strong capture (which would trip a non-Sendable + // capture warning in the @Sendable completion closure). + let snapshotter = MKMapSnapshotter(options: options) + let image: UIImage? = await withCheckedContinuation { continuation in + snapshotter.start(with: .global(qos: .userInitiated)) { snapshot, _ in + continuation.resume(returning: snapshot?.image) + } + } + if let image { cache.setObject(image, forKey: key) } + return image + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageBubbleView.swift new file mode 100644 index 0000000000..59622835cf --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageBubbleView.swift @@ -0,0 +1,270 @@ +import SwiftUI + +struct MessageBubbleView: View { + let bubble: MessageBubble + let onPhotoTap: (PhotoVisual) -> Void + let onVideoTap: (VideoVisual) -> Void + let onVideoNoteTap: (VideoNoteVisual) -> Void + let onPollTap: (Int64, PollVisual) -> Void + + private var style: BubbleStyle { .resolve(isOutgoing: bubble.isOutgoing) } + + var body: some View { + HStack(alignment: .top, spacing: 0) { + if bubble.isOutgoing { Spacer(minLength: 16) } + VStack(alignment: bubble.isOutgoing ? .trailing : .leading, spacing: 1) { + if let name = bubble.senderName, !bubble.isOutgoing, bubble.sticker == nil { + Text(name) + .font(.caption2) + .foregroundStyle(bubble.senderColorIndex.map { avatarPalette[$0] } ?? .secondary) + .padding(.leading, 8) + } + if let sticker = bubble.sticker { + StickerBubbleView( + sticker: sticker, + senderName: bubble.senderName, + senderColorIndex: bubble.senderColorIndex, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if let photo = bubble.photo { + PhotoBubbleView( + photo: photo, + caption: bubble.body, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader, + onTap: onPhotoTap + ) + } else if let video = bubble.video { + VideoBubbleView( + video: video, + caption: bubble.body, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader, + onTap: { onVideoTap(video) } + ) + } else if let note = bubble.videoNote { + VideoNoteBubbleView( + note: note, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader, + onTap: { onVideoNoteTap(note) } + ) + } else if let voice = bubble.voiceNote { + VoiceNoteBubbleView( + note: voice, + caption: bubble.body, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if let audio = bubble.audio { + AudioBubbleView( + audio: audio, + caption: bubble.body, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if let document = bubble.document { + DocumentBubbleView( + document: document, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if let location = bubble.location { + LocationBubbleView( + location: location, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if let poll = bubble.poll { + PollBubbleView( + poll: poll, + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader, + onVote: { onPollTap(bubble.messageId, poll) } + ) + } else if bubble.isUnsupported { + UnsupportedBubbleView( + isOutgoing: bubble.isOutgoing, + replyHeader: bubble.replyHeader + ) + } else if bubble.replyHeader == nil, let emojiCount = emojiOnlyCount(bubble.body) { + Text(bubble.body) + .font(.system(size: jumboEmojiSize(for: emojiCount))) + } else { + textBubbleContent + } + } + .overlay(alignment: .bottomLeading) { statusIndicator } + if !bubble.isOutgoing { Spacer(minLength: 16) } + } + .accessibilityIdentifier("bubble.\(bubble.messageId)") + } + + private func jumboEmojiSize(for count: Int) -> CGFloat { + switch count { + case 1: return 52 + case 2: return 40 + default: return 32 + } + } + + private var textBubbleContent: some View { + VStack(alignment: bubble.isOutgoing ? .trailing : .leading, spacing: 2) { + if let header = bubble.replyHeader { + ReplyHeaderView(header: header, style: BubbleStyle.resolve(isOutgoing: bubble.isOutgoing)) + } + Text(bubble.body) + .font(.caption) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal, 9) + .padding(.vertical, 4) + .frame(minWidth: BubbleShape.minSize, minHeight: BubbleShape.minSize) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + } + + /// The single outgoing delivery indicator, in the bubble's leading gutter. + /// Glyphs sit slightly further left than the dot so they clear the bubble edge. + @ViewBuilder + private var statusIndicator: some View { + switch bubble.outgoingStatus { + case .none: + EmptyView() + case .unread: + Circle() + .fill(Color.accentColor) + .frame(width: 7, height: 7) + .offset(x: -11) + case .pending: + Image(systemName: "clock") + .font(.system(size: 8)) + .foregroundStyle(Color.white.opacity(0.7)) + .offset(x: -13) + case .failed: + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 8)) + .foregroundStyle(.red) + .offset(x: -13) + } + } +} + +#if DEBUG +private let previewSampleBubble = MessageBubble( + messageId: 1, isOutgoing: false, senderName: "Alice", + body: "this is a reply to text", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, sticker: nil, location: nil, poll: nil, + sendingState: .sent, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor message earlier in the chat", + minithumbnail: nil, isOutgoing: false + ) +) + +#Preview("Text bubble — with reply (incoming, group)") { + MessageBubbleView( + bubble: previewSampleBubble, + onPhotoTap: { _ in }, + onVideoTap: { _ in }, + onVideoNoteTap: { _ in }, + onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Text bubble — with reply (outgoing)") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 2, isOutgoing: true, senderName: nil, + body: "ok", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, sticker: nil, location: nil, poll: nil, + sendingState: .sent, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor", + minithumbnail: nil, isOutgoing: true + ) + ), + onPhotoTap: { _ in }, + onVideoTap: { _ in }, + onVideoNoteTap: { _ in }, + onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Text bubble — colored sender (incoming, group)") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 3, isOutgoing: false, senderName: "Alice", + body: "colored name above me", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, sticker: nil, location: nil, poll: nil, + sendingState: .sent, + replyHeader: nil, + senderColorIndex: paletteIndex(for: 200) + ), + onPhotoTap: { _ in }, + onVideoTap: { _ in }, + onVideoNoteTap: { _ in }, + onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Emoji only — incoming 1") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 10, isOutgoing: false, senderName: nil, body: "🥰", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, + sticker: nil, location: nil, poll: nil, sendingState: .sent, replyHeader: nil + ), + onPhotoTap: { _ in }, onVideoTap: { _ in }, onVideoNoteTap: { _ in }, onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Unread outgoing") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 12, isOutgoing: true, senderName: nil, + body: "delivered but not yet read", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, + sticker: nil, location: nil, poll: nil, sendingState: .sent, replyHeader: nil, + isUnreadOutgoing: true + ), + onPhotoTap: { _ in }, onVideoTap: { _ in }, onVideoNoteTap: { _ in }, onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Emoji only — outgoing 3") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 11, isOutgoing: true, senderName: nil, body: "😀🎉🥰", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, + sticker: nil, location: nil, poll: nil, sendingState: .sent, replyHeader: nil + ), + onPhotoTap: { _ in }, onVideoTap: { _ in }, onVideoNoteTap: { _ in }, onPollTap: { _, _ in } + ) + .bubblePreview() +} + +#Preview("Unsupported message — incoming") { + MessageBubbleView( + bubble: MessageBubble( + messageId: 13, isOutgoing: false, senderName: "Alice", body: "", + photo: nil, video: nil, videoNote: nil, voiceNote: nil, audio: nil, document: nil, + sticker: nil, location: nil, poll: nil, sendingState: .sent, replyHeader: nil, + isUnsupported: true + ), + onPhotoTap: { _ in }, onVideoTap: { _ in }, onVideoNoteTap: { _ in }, onPollTap: { _, _ in } + ) + .bubblePreview() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageFormatters.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageFormatters.swift new file mode 100644 index 0000000000..ff5efdcea6 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageFormatters.swift @@ -0,0 +1,76 @@ +import Foundation +import TDShim + +/// Renders the body text of a message bubble. Mirrors `chatPreview`'s content labels for +/// non-text content but does not prepend a sender prefix (which is drawn as a separate +/// label above the bubble) and does not handle drafts (which are a chat-list concern only). +/// +/// Returns `""` for content this milestone doesn't know how to render. Service messages +/// are handled separately; `messageBody` returns `""` for service content. +func messageBody(_ content: MessageContent) -> String { + switch content { + case .messageText(let t): return t.text.text + case .messagePhoto(let m): return m.caption.text + case .messageVideo(let m): return m.caption.text + case .messageVideoNote: return "" + case .messageVoiceNote(let m): return m.caption.text + case .messageAudio(let m): return m.caption.text + case .messageSticker: return "" + case .messageDocument: return "Document" + case .messageLocation: return "" + case .messageVenue: return "" + case .messageContact: return "Contact" + case .messagePoll: return "" + default: return "" + } +} + +/// True for message content that has no dedicated bubble rendering and should show the +/// "Unsupported message" placeholder. Uses positive classification so TDLib's closed +/// `MessageContent` enum's `default` cleanly captures every unhandled type +/// (animations/GIFs, games, invoices, calls, dice, stories, gifts, expired media, …). +/// +/// Service-message content is filtered to `.service` rows upstream (see +/// `serviceLineText`) and never reaches the bubble path, so it is irrelevant here. +/// `messageContact` is treated as supported — it keeps its existing "Contact" text +/// fallback in `messageBody`. +func isUnsupportedContent(_ content: MessageContent) -> Bool { + switch content { + case .messageText, .messagePhoto, .messageVideo, .messageVideoNote, + .messageVoiceNote, .messageAudio, .messageSticker, .messageDocument, + .messageLocation, .messageVenue, .messageContact, .messagePoll: + return false + default: + return true + } +} + +/// Day separator label between messages from different days. +/// `Today` / `Yesterday` for the boundary days, weekday name for the last 7 days, +/// `MMM d` for older or future dates. +/// +/// Pure: takes its `today` and `calendar` so tests are deterministic. The default `locale` +/// uses the user's system locale at runtime; tests pin `en_US` for stable strings. +func daySeparatorLabel( + for date: Foundation.Date, + today: Foundation.Date, + calendar: Calendar, + locale: Locale = .current +) -> String { + let dayStart = calendar.startOfDay(for: date) + let todayStart = calendar.startOfDay(for: today) + let dayDiff = calendar.dateComponents([.day], from: dayStart, to: todayStart).day ?? 0 + + if dayDiff == 0 { return "Today" } + if dayDiff == 1 { return "Yesterday" } + + let formatter = DateFormatter() + formatter.calendar = calendar + formatter.locale = locale + if dayDiff >= 2 && dayDiff <= 6 { + formatter.dateFormat = "EEEE" // "Sunday", "Monday", … + } else { + formatter.dateFormat = "MMM d" // "May 5", "Dec 31" + } + return formatter.string(from: date) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift new file mode 100644 index 0000000000..bbe8ee113b --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageListView.swift @@ -0,0 +1,402 @@ +import SwiftUI + +private struct ScrollSnapshot: Equatable { + let contentOffsetY: CGFloat + let contentSizeH: CGFloat + let containerSizeH: CGFloat + let topInset: CGFloat +} + +struct MessageListView: View { + @Environment(TDClient.self) private var client + let row: ChatRow + + @State private var store: ChatHistoryStore + @State private var presentedPhoto: PhotoVisual? + @State private var presentedVideo: VideoVisual? + @State private var presentedVideoNote: VideoNoteVisual? + @State private var presentedPoll: PollVoteTarget? + @State private var showAttachment: Bool = false + @State private var stickerPickerStore: StickerPickerStore? + // True when the user is parked within slop of the bottom edge. Updated only on + // user-driven scrolls (see .onScrollGeometryChange filter), so it reflects intent + // rather than instantaneous viewport position. Drives auto-scroll for incoming: + // stay-anchored-when-at-bottom, leave-alone-when-scrolled-up. Initial value depends + // on whether the chat opens at the tail (no unreads → true) or at the unread divider + // (unreads present → false, user is parked at the divider, not the bottom). + @State private var isAtBottom: Bool + @State private var didApplyInitialScroll: Bool = false + // Live scroll-view content height, updated by a dedicated geometry observer + // (see `.onScrollGeometryChange(for: CGFloat.self)` below). The initial- + // position `.task` polls this to detect when bubbles have finished async- + // sizing, rather than guessing a fixed settle delay. + @State private var observedContentHeight: CGFloat = 0 + // Pagination triggers fire from `.onScrollVisibilityChange` on the top/bottom rows. + // On initial layout — especially for all-unread chats where the divider lands at + // row index 0 — the topmost rows are visible without any user gesture, which would + // call loadOlder() repeatedly in a loop as each fetch prepends more content. Gate + // pagination on having observed at least one user-driven scroll + // (`.onScrollGeometryChange`'s contentSizeH-stable filter implies user-driven). + @State private var userHasScrolled: Bool = false + // Hard cool-down after each loadOlder/loadNewer. Without this, the row-visibility + // callbacks for newly-prepended rows fire IMMEDIATELY after `reproject()` and re- + // trigger pagination before SwiftUI can land the `.onChange`-driven scroll-preservation + // animation. The cool-down lets the prepended rows settle off-screen before the + // next pagination is allowed. + @State private var canPaginate: Bool = true + + init(row: ChatRow, store: ChatHistoryStore) { + self.row = row + self._store = State(initialValue: store) + // Opens at tail → starts at bottom. Opens at divider → user is NOT at bottom. + self._isAtBottom = State(initialValue: row.unreadCount == 0) + } + + var body: some View { + content(store: store) + .navigationTitle(row.title) + .accessibilityIdentifier("messageListView") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + AvatarView( + avatar: row.avatar, + onRequestDownload: { fileId in store.requestFileDownload(fileId: fileId) }, + onCancelDownload: { fileId in store.cancelFileDownload(fileId: fileId) }, + size: 36 + ) + .glassEffect(in: Circle()) + } + } + .sheet(item: $presentedPhoto) { photo in + PhotoViewerView(photo: photo) + } + .sheet(item: $presentedVideo) { video in + VideoPlayerView(video: video).environment(store) + } + .sheet(item: $presentedVideoNote) { note in + VideoNotePlayerView(note: note).environment(store) + } + .sheet(item: $presentedPoll) { target in + PollVoteView( + initialPoll: target.poll, + currentPoll: { store.poll(forMessageId: target.id) }, + onVote: { await store.setPollAnswer(messageId: target.id, optionIds: $0) } + ) + } + .sheet(isPresented: $showAttachment) { + AttachmentSheet( + stickerPickerStore: stickerPickerStore, + onSendSticker: { await store.sendSticker($0) }, + onSendVoiceNote: { await store.sendVoiceNote($0) }, + onPrepareVoice: { + store.voicePlayback.tearDown() + store.audioPlayback.tearDown() + }, + onSendLocation: { latitude, longitude in + await store.sendLocation(latitude: latitude, longitude: longitude) + } + ) + .environment(client) + // Inject the picker store at the sheet-content root (above + // AttachmentSheet's NavigationStack), mirroring `client`. The + // store must live above the stack so pushed destinations — + // StickerSetDetailView and its StickerCellViews — inherit it; + // injecting only inside StickerPickerView (below the stack) + // left pushed set-detail views with no store and trapped on + // the `@Environment(StickerPickerStore.self)` lookup. + .environment(stickerPickerStore) + } + .task { + client.setActiveHistory(store) + // Defer openChat to here (the store was warmed without it). Independent + // of warm: if the window is still loading, openChat proceeds anyway. + await store.activate() + // Build the picker store HERE (not lazily in the attachment-tap closure): + // creating an @State and flipping a sheet-present flag in the same closure + // races the .sheet's `if let pickerStore` against a nil snapshot. + if stickerPickerStore == nil, let pl = client.makeStickerPickerLoader() { + stickerPickerStore = StickerPickerStore(loader: pl) + } + } + .onDisappear { + let s = self.store + client.setActiveHistory(nil) + Task { await s.stop() } + } + } + + @ViewBuilder + private func content(store: ChatHistoryStore) -> some View { + switch store.loadState { + case .loadingFirstPage: + // Keep the spinner up for the entire initial load. TDLib's cold cache + // routinely splits `getChatHistory` into multiple round-trips (iter=1 + // returns 1 message, iter=2 returns the rest). If we fall through to + // the ScrollView mid-load, the user sees: brief render with iter=1's + // content → iter=2 prepends 29 messages (content jumps) → final scroll + // fires. Showing the spinner until `.loaded` collapses that into one + // clean transition. + LoadingView(label: "Loading messages…") + case .failed(let message): + VStack(spacing: 6) { + Text(message) + .font(.caption2) + .multilineTextAlignment(.center) + Button("Retry") { + Task { await store.start() } + } + .buttonStyle(.borderedProminent) + } + .padding() + default: + // ScrollViewReader wraps the VStack so the initial-position `.task`, + // loadOlder scroll-preservation, and tail auto-scroll handlers can capture + // `proxy` and call `proxy.scrollTo(...)`. + ScrollViewReader { proxy in + VStack(spacing: 0) { + if let err = store.lastSendError { + HStack(spacing: 4) { + Text(err) + .font(.caption2) + .lineLimit(2) + Spacer(minLength: 0) + Button { + store.dismissSendError() + } label: { + Image(systemName: "xmark") + .font(.system(size: 10)) + } + .buttonStyle(.plain) + } + .padding(6) + .background(Capsule().fill(.red.opacity(0.2))) + .padding(.horizontal, 4) + .padding(.top, 2) + } + if let err = store.lastPaginationError { + HStack(spacing: 4) { + Text(err).font(.caption2).lineLimit(2) + Spacer(minLength: 0) + Button { store.dismissPaginationError() } label: { + Image(systemName: "xmark").font(.system(size: 10)) + } + .buttonStyle(.plain) + } + .padding(6) + .background(Capsule().fill(.orange.opacity(0.2))) + .padding(.horizontal, 4) + .padding(.top, 2) + } + ScrollView { + VStack(alignment: .leading, spacing: 4) { + ForEach(Array(store.rows.enumerated()), id: \.element.id) { idx, messageRow in + MessageRowView( + row: messageRow, + onPhotoTap: { presentedPhoto = $0 }, + onVideoTap: { presentedVideo = $0 }, + onVideoNoteTap: { presentedVideoNote = $0 }, + onPollTap: { id, poll in presentedPoll = PollVoteTarget(id: id, poll: poll) }, + index: idx, + count: store.rows.count, + onEnterTopEdge: { + guard userHasScrolled, canPaginate else { return } + canPaginate = false + Task { + await store.loadOlder() + try? await Task.sleep(nanoseconds: 800_000_000) + canPaginate = true + } + }, + onEnterBottomEdge: { + guard userHasScrolled, canPaginate, !store.window.reachesChatTail else { return } + canPaginate = false + Task { + await store.loadNewer() + try? await Task.sleep(nanoseconds: 800_000_000) + canPaginate = true + } + }, + onIncomingBubbleVisible: { id in + guard id > store.unreadDividerAfterIdSnapshot else { return } + store.markVisible(messageId: id) + } + ) + .id(messageRow.id) + } + if row.canSend { + ReplyBar( + onAttachTap: { showAttachment = true }, + onSend: { snapshot in + Task { await store.sendText(snapshot) } + } + ) + .padding(.top, 8) + } + // 19pt bottom content inset so the ReplyBar's "+" and pill sit + // 19pt above the *physical* screen edge when the chat is scrolled + // to the tail. Combined with .ignoresSafeArea(edges: .bottom) on + // the ScrollView below — without that, the system bottom safe-area + // adds extra space and the total inset overshoots. Carried as an + // id'd zero-content spacer (not `.padding(.bottom, 19)` on the + // VStack) so it's part of the scrollable content and + // `proxy.scrollTo("bottomAnchor", .bottom)` reaches the TRUE + // content bottom — anchoring to the ReplyBar instead stops 19pt + // short, landing the chat just above the real tail. + Color.clear + .frame(height: 19) + .id("bottomAnchor") + } + .padding(.horizontal, 4) + .padding(.top, 4) + } + .ignoresSafeArea(edges: .bottom) + .scrollContentBackground(.hidden) + .background { + Image("ChatBG") + .resizable() + .aspectRatio(contentMode: .fill) + .clipped() + .ignoresSafeArea() + } + .environment(store) + .onScrollGeometryChange(for: ScrollSnapshot.self) { geometry in + ScrollSnapshot( + contentOffsetY: geometry.contentOffset.y, + contentSizeH: geometry.contentSize.height, + containerSizeH: geometry.containerSize.height, + topInset: geometry.contentInsets.top + ) + } action: { old, new in + // isAtBottom means "user intends to be parked at bottom", NOT "the + // bottom edge is visible right now". When a new message arrives while + // the user is at the bottom, contentSize grows but contentOffset stays + // — a naive "is the bottom edge visible" check would flip false even + // though the user hasn't moved. Skipping callbacks where contentSize + // changed preserves the last user-driven state. + // + // The .top inset (translucent nav bar overlay, ~62pt on the 46mm sim) + // shrinks the maximum scroll offset; subtract it from bottomOffset or + // the check is off by ~62pt and isAtBottom never reaches true. Verified + // empirically — when at the bottom, contentOffset.y == + // contentSize.height - containerSize.height - contentInsets.top. + guard old.contentSizeH == new.contentSizeH else { return } + let bottomOffset = new.contentSizeH - new.containerSizeH - new.topInset + isAtBottom = new.contentOffsetY >= bottomOffset - 8 + // contentSize-stable changes imply user-driven scroll; arm + // pagination so it only fires after the user actually moved. + userHasScrolled = true + } + // Dedicated content-height tracker for the initial-position + // settle loop. The ScrollSnapshot observer above bails early on + // contentSize changes (to preserve user-driven isAtBottom), so it + // can't be used to watch growth; this one records height on every + // change, including the async bubble-sizing growth we wait out. + .onScrollGeometryChange(for: CGFloat.self) { $0.contentSize.height } action: { _, newValue in + observedContentHeight = newValue + } + .task { + // Initial positioning. This `default` branch only renders once the + // store is `.loaded` (the `.loadingFirstPage` case keeps the spinner + // up), so `store.rows` is fully populated — true for BOTH the fast + // path (warmed before push) and the slow path (warm finished after + // push, swapping this branch in). + // + // We drive BOTH the unread (→ divider, `.top`) and the read (→ tail, + // `.bottom`) cases imperatively. We do NOT use + // `.defaultScrollAnchor(.bottom)`: on a non-lazy `VStack` it latches + // the bottom offset at the first, too-short layout and reverts to that + // stale offset as content grows — landing ~1 screen above the true + // tail. We use `proxy.scrollTo` rather than `scrollPosition(id:)` + // because the content is a plain (non-lazy) `VStack` — + // `scrollPosition(id:)` only positions reliably inside lazy stacks, and + // switching to `LazyVStack` reintroduces the over-scroll gotcha. + // + // The hard part is TIMING: bubble heights settle a few frames AFTER + // first layout — media (photos, videos, maps, stickers) async-size well + // past any fixed delay. A guessed sleep fires the scroll while + // contentSize is still too short; as bubbles above the anchor then + // grow, `bottomAnchor` is pushed down and we're left parked ~1 screen + // above the tail (the original regression — a fixed 50ms sleep was not + // enough for media-heavy chats). So instead of guessing, re-pin every + // frame until contentSize stops changing (stable for a few consecutive + // frames), capped so a never-settling chat still reveals. The `.overlay` + // cover (gated on `!didApplyInitialScroll`) hides the whole settle so + // there's no visible movement. + guard !didApplyInitialScroll else { return } + let applyInitialPosition: @MainActor () -> Void = { + if let target = store.window.initialScrollTargetId { + proxy.scrollTo(target, anchor: .top) + } else { + proxy.scrollTo("bottomAnchor", anchor: .bottom) + } + } + var lastHeight: CGFloat = -1 + var stableFrames = 0 + for _ in 0..<90 { // ~1.5s cap at one frame each + applyInitialPosition() + try? await Task.sleep(nanoseconds: 16_000_000) + let height = observedContentHeight + if height == lastHeight { + stableFrames += 1 + if stableFrames >= 3 { break } // settled for ~3 frames + } else { + stableFrames = 0 + lastHeight = height + } + } + // Final pin once content has settled, plus one runloop pass to catch + // any last residual growth between the final sleep and reveal. + applyInitialPosition() + await Task.yield() + applyInitialPosition() + didApplyInitialScroll = true + } + .onChange(of: store.rows.first?.id) { oldId, newId in + // Scroll preservation across `loadOlder`. When older content is + // prepended, SwiftUI keeps the absolute scroll offset where it was + // — meaning the user was at pixel 0 (top of old content) and is + // now at pixel 0 of the new (longer) content, which exposes the + // newly-prepended top rows. Those rows' `index <= 2` triggers a + // fresh `loadOlder` and the chat enters a paginate-forever loop. + // Snap the user back to the previously-first row so the prepended + // content lands above the viewport (off-screen until the user + // chooses to scroll there). + guard didApplyInitialScroll, + let oldId, let newId, oldId != newId else { return } + proxy.scrollTo(oldId, anchor: .top) + } + .onChange(of: store.rows.last?.id) { _, newId in + // Telegram convention: outgoing always pulls to bottom; incoming only + // pulls if the user was already parked there. Only auto-scroll when we + // actually have the chat tail loaded — otherwise rows.last is just the + // bottom of the current window, not the latest message. Also gate on + // didApplyInitialScroll so the initial fill (where rows.last?.id + // transitions from nil → some-id mid-load) doesn't double-fire with + // the .task initial-scroll handler. + guard didApplyInitialScroll, + newId != nil, + store.window.reachesChatTail, + case .bubble(let bubble) = store.rows.last else { return } + guard bubble.isOutgoing || isAtBottom else { return } + withAnimation(.easeOut(duration: 0.2)) { + proxy.scrollTo("bottomAnchor", anchor: .bottom) + } + } + } + } + // Cover the first-layout→settle window (the `.task` above re-pins to the final + // position until contentSize stabilizes) so the user never sees the jump. + // Covers BOTH cases: the unread divider (`.top`) and the read tail (`.bottom`) + // are both placed imperatively after the content settles, because there is no + // `.defaultScrollAnchor(.bottom)` to hold position while bubbles async-grow + // (it reverts to a stale first-layout offset — see the `.task` comment). + .overlay { + if !didApplyInitialScroll { + LoadingView(label: "Loading messages…") + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black.ignoresSafeArea()) + } + } + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRow.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRow.swift new file mode 100644 index 0000000000..7e2387d62b --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRow.swift @@ -0,0 +1,381 @@ +import Foundation +import TDShim + +enum MessageRow: Identifiable, Equatable, Hashable { + case bubble(MessageBubble) + case service(ServiceLine) + case daySeparator(DayLabel) + case unreadDivider(afterMessageId: Int64) + + var id: String { + switch self { + case .bubble(let b): return "msg-\(b.messageId)" + case .service(let s): return "svc-\(s.messageId)" + case .daySeparator(let d): return "day-\(d.key)" + case .unreadDivider(let id): return "unread-\(id)" + } + } +} + +struct MessageBubble: Equatable, Hashable { + let messageId: Int64 + let isOutgoing: Bool + /// First name of the sender for incoming bubbles in groups; nil otherwise. + let senderName: String? + /// Body text — for text bubbles, the message body; for photo / video bubbles, the caption (may be ""). + let body: String + /// Photo metadata for `messagePhoto` content; nil for non-photo bubbles. + let photo: PhotoVisual? + /// Video metadata for `messageVideo` content; nil for non-video bubbles. + let video: VideoVisual? + /// Round-video-note metadata for `messageVideoNote` content; nil otherwise. + /// Mutually exclusive with `photo`, `video`, `sticker`. + let videoNote: VideoNoteVisual? + /// Voice-note metadata for `messageVoiceNote` content; nil otherwise. + /// Mutually exclusive with `photo`, `video`, `videoNote`, `sticker`. + let voiceNote: VoiceNoteVisual? + /// Music / audio-file metadata for `messageAudio` content; nil otherwise. + /// Mutually exclusive with `photo`, `video`, `videoNote`, `voiceNote`, `sticker`, `location`, `poll`. + let audio: AudioVisual? + let document: DocumentVisual? + /// Sticker metadata for `messageSticker` content; nil for non-sticker bubbles. + let sticker: StickerVisual? + /// Location / venue metadata for `messageLocation` / `messageVenue` content; nil otherwise. + /// Mutually exclusive with `photo`, `video`, `videoNote`, `voiceNote`, `audio`, `sticker`, `poll`. + let location: LocationVisual? + /// Poll / quiz metadata for `messagePoll` content; nil otherwise. + /// Mutually exclusive with the other media fields. + let poll: PollVisual? + /// Delivery status of the message (pending / sent / failed). + let sendingState: SendingState + /// Reply header for messages that reply to another message; nil if not a reply. + let replyHeader: ReplyHeader? + /// Palette index for the sender-name label (incoming groups only); nil = uncolored. + /// Set together with `senderName` — both nil for outgoing / private / non-user senders. + var senderColorIndex: Int? = nil + /// True for a sent outgoing message the recipient hasn't read yet (`id > lastReadOutboxMessageId`). + var isUnreadOutgoing: Bool = false + /// True for content with no dedicated bubble — renders the "Unsupported message" + /// placeholder. Set from `isUnsupportedContent(msg.content)` in `messageRows`. + var isUnsupported: Bool = false + + /// The delivery-status indicator to render for this bubble. Outgoing only; + /// `isUnreadOutgoing` already encodes "sent, unread, not Saved Messages". + var outgoingStatus: OutgoingStatus { + guard isOutgoing else { return .none } + switch sendingState { + case .pending: return .pending + case .failed: return .failed + case .sent: return isUnreadOutgoing ? .unread : .none + } + } +} + +/// Collapses an outgoing message's delivery state into the single indicator the +/// chat UI shows at the bubble's bottom-leading corner. Incoming messages — and +/// outgoing messages that are sent and already read — show nothing. +enum OutgoingStatus: Equatable { + case none + case pending + case failed + case unread +} + +struct ServiceLine: Equatable, Hashable { + let messageId: Int64 + let text: String +} + +struct DayLabel: Equatable, Hashable { + /// Stable per-day key (`yyyy-MM-dd` in the calendar's time zone) used for the row id. + let key: String + /// Human label (`Today`, `Yesterday`, `Sunday`, `May 5`, …). + let label: String +} + +/// Compacts cached messages into `[MessageRow]` for rendering. Sorts by `message.id` +/// ascending, prepends `.daySeparator` rows above each new day's first message, +/// and — when `unreadDividerAfterId` is set — interleaves a single `.unreadDivider` +/// row immediately before the first message with `id > unreadDividerAfterId`. +/// `selfUserId` is the authenticated user's id; used by service-line formatters to +/// produce the "You ..." actor prefix for actions the current user performed. +func messageRows( + messages: [CachedMessage], + userNames: [Int64: String], + fileLocals: [Int: File] = [:], + chatType: ChatType, + chatId: Int64, + today: Foundation.Date, + calendar: Calendar, + locale: Locale = .current, + selfUserId: Int64? = nil, + unreadDividerAfterId: Int64? = nil, + lastReadOutboxMessageId: Int64 = 0 +) -> [MessageRow] { + let sorted = messages.sorted { $0.id < $1.id } + let cacheById: [Int64: CachedMessage] = Dictionary(uniqueKeysWithValues: sorted.map { ($0.id, $0) }) + + // Saved Messages (a private chat with our own user id) has no recipient, so + // outgoing messages there are implicitly read — they never get the unread dot. + let isSavedMessages: Bool = { + guard let selfUserId, case .chatTypePrivate(let p) = chatType else { return false } + return p.userId == selfUserId + }() + + let dayKeyFormatter = DateFormatter() + dayKeyFormatter.calendar = calendar + dayKeyFormatter.locale = Locale(identifier: "en_US_POSIX") + dayKeyFormatter.timeZone = calendar.timeZone + dayKeyFormatter.dateFormat = "yyyy-MM-dd" + + var rows: [MessageRow] = [] + var lastDayKey: String? = nil + var dividerPlaced = false + + for msg in sorted { + if let divider = unreadDividerAfterId, !dividerPlaced, msg.id > divider { + // Anchor id need not be present in `messages` — divider position is + // purely "before the first id > anchor", which lets it remain stable + // even after older messages have been paginated out of the window. + rows.append(.unreadDivider(afterMessageId: divider)) + dividerPlaced = true + } + let date = Foundation.Date(timeIntervalSince1970: TimeInterval(msg.date)) + let dayKey = dayKeyFormatter.string(from: date) + if dayKey != lastDayKey { + let label = daySeparatorLabel(for: date, today: today, calendar: calendar, locale: locale) + rows.append(.daySeparator(.init(key: dayKey, label: label))) + lastDayKey = dayKey + } + if let svc = serviceLineText( + msg, + selfUserId: selfUserId, + userNames: userNames, + messageCache: cacheById, + includeActor: true, + chatType: chatType + ) { + rows.append(.service(.init(messageId: msg.id, text: svc))) + continue + } + let sender = senderLabel(for: msg, chatType: chatType, userNames: userNames) + rows.append(.bubble(.init( + messageId: msg.id, + isOutgoing: msg.isOutgoing, + senderName: sender?.name, + body: messageBody(msg.content), + photo: photoVisual(for: msg.content, fileLocals: fileLocals), + video: videoVisual(for: msg.content, fileLocals: fileLocals), + videoNote: videoNoteVisual(for: msg.content, fileLocals: fileLocals), + voiceNote: voiceNoteVisual(for: msg.content, fileLocals: fileLocals), + audio: audioVisual(for: msg.content, fileLocals: fileLocals), + document: documentVisual(for: msg.content, fileLocals: fileLocals), + sticker: stickerVisual(for: msg.content, fileLocals: fileLocals), + location: locationVisual( + for: msg.content, + messageDate: Foundation.Date( + timeIntervalSince1970: TimeInterval(msg.editDate != 0 ? msg.editDate : msg.date) + ) + ), + poll: pollVisual(for: msg.content), + sendingState: msg.sendingState, + replyHeader: replyPreview( + msg.replyTo, + inChatId: chatId, + isOutgoing: msg.isOutgoing, + cache: cacheById, + userNames: userNames + ), + senderColorIndex: sender?.colorIndex, + isUnreadOutgoing: !isSavedMessages && msg.isOutgoing && msg.sendingState == .sent && msg.id > lastReadOutboxMessageId, + isUnsupported: isUnsupportedContent(msg.content) + ))) + } + return rows +} + +/// Builds a `PhotoVisual` for `messagePhoto` content, or returns `nil` for any other content +/// or when no displayable photo size is present. Looks up the chosen size's file id in +/// `fileLocals` for the freshest local-path snapshot, falling back to the static `File` +/// carried by the message itself when the store hasn't seen `updateFile` yet. +private func photoVisual(for content: MessageContent, fileLocals: [Int: File]) -> PhotoVisual? { + guard case .messagePhoto(let m) = content else { return nil } + guard let size = selectPhotoSize(m.photo.sizes) else { return nil } + let fileId = size.photo.id + let file = fileLocals[fileId] ?? size.photo + let localPath: String? = (file.local.isDownloadingCompleted && !file.local.path.isEmpty) ? file.local.path : nil + return PhotoVisual( + fileId: fileId, + width: size.width, + height: size.height, + minithumbnail: m.photo.minithumbnail?.data, + localPath: localPath + ) +} + +/// Builds a `VideoVisual` for `messageVideo` content, or returns `nil` for any other +/// content. Looks up the chosen quality's file id in `fileLocals` for the freshest +/// local-path snapshot of the video file, and the preview file id likewise for the +/// preview thumbnail. Both fall back to the static `File` carried by the message +/// itself when the store hasn't seen `updateFile` yet. +private func videoVisual(for content: MessageContent, fileLocals: [Int: File]) -> VideoVisual? { + guard case .messageVideo(let m) = content else { return nil } + let chosen = selectVideoQuality(primary: m.video, alternatives: m.alternativeVideos) + let videoFile = fileLocals[chosen.file.id] ?? chosen.file + let videoLocalPath: String? = (videoFile.local.isDownloadingCompleted && !videoFile.local.path.isEmpty) + ? videoFile.local.path : nil + + var preview = selectVideoPreview(m) + if let pid = preview.previewFileId, let pf = fileLocals[pid] ?? lookupStaticPreviewFile(in: m, id: pid) { + let path: String? = (pf.local.isDownloadingCompleted && !pf.local.path.isEmpty) ? pf.local.path : nil + preview = VideoPreview( + previewFileId: preview.previewFileId, + previewWidth: preview.previewWidth, + previewHeight: preview.previewHeight, + minithumbnail: preview.minithumbnail, + previewLocalPath: path + ) + } + + return VideoVisual( + videoFileId: chosen.file.id, + width: chosen.width, + height: chosen.height, + duration: m.video.duration, + mimeType: chosen.mimeType, + preview: preview, + videoLocalPath: videoLocalPath + ) +} + +/// Returns the message-embedded `File` snapshot for the given preview id when the +/// store hasn't yet seen an `updateFile` for it. Lets us read the static file id + +/// path even before the first download tick lands. +private func lookupStaticPreviewFile(in m: MessageVideo, id: Int) -> File? { + if let cover = m.cover, let s = selectVideoCoverSize(cover.sizes), s.photo.id == id { + return s.photo + } + if let thumb = m.video.thumbnail, thumb.file.id == id { + return thumb.file + } + return nil +} + +/// Builds a `VideoNoteVisual` for `messageVideoNote` content, or returns `nil` for +/// any other content. Looks up the playable file id and (optionally) the thumbnail +/// file id in `fileLocals` for the freshest local-path snapshot; both fall back to +/// the message-embedded `File` snapshot when the store hasn't seen `updateFile` yet. +private func videoNoteVisual(for content: MessageContent, fileLocals: [Int: File]) -> VideoNoteVisual? { + guard case .messageVideoNote(let m) = content else { return nil } + let videoFile = fileLocals[m.videoNote.video.id] ?? m.videoNote.video + let videoLocalPath: String? = (videoFile.local.isDownloadingCompleted && !videoFile.local.path.isEmpty) + ? videoFile.local.path : nil + + let thumbFileId = m.videoNote.thumbnail?.file.id + var thumbLocalPath: String? = nil + if let tid = thumbFileId { + let tf = fileLocals[tid] ?? m.videoNote.thumbnail?.file + if let tf, tf.local.isDownloadingCompleted, !tf.local.path.isEmpty { + thumbLocalPath = tf.local.path + } + } + return VideoNoteVisual( + videoFileId: m.videoNote.video.id, + length: m.videoNote.length, + duration: m.videoNote.duration, + thumbFileId: thumbFileId, + minithumbnail: m.videoNote.minithumbnail?.data, + thumbLocalPath: thumbLocalPath, + videoLocalPath: videoLocalPath + ) +} + +/// Builds a `VoiceNoteVisual` for `messageVoiceNote` content, or returns `nil` +/// for any other content. Looks up the voice file id in `fileLocals` for the +/// freshest local-path snapshot, falling back to the message-embedded `File` +/// when the store hasn't seen `updateFile` yet. +/// +/// Internal (not private like its peers) so `VoiceNoteVisualTests` can exercise +/// the projection directly via `@testable import`. Consider moving these tests +/// behind `messageRows(...)` if direct access becomes a maintenance burden. +func voiceNoteVisual(for content: MessageContent, fileLocals: [Int: File]) -> VoiceNoteVisual? { + guard case .messageVoiceNote(let m) = content else { return nil } + let voiceFile = fileLocals[m.voiceNote.voice.id] ?? m.voiceNote.voice + let localPath: String? = (voiceFile.local.isDownloadingCompleted && !voiceFile.local.path.isEmpty) + ? voiceFile.local.path : nil + return VoiceNoteVisual( + voiceFileId: m.voiceNote.voice.id, + duration: m.voiceNote.duration, + mimeType: m.voiceNote.mimeType, + waveform: m.voiceNote.waveform, + caption: m.caption.text, + localPath: localPath + ) +} + +/// Builds an `AudioVisual` for `messageAudio` content, or returns `nil` for any +/// other content. Looks up the audio file id in `fileLocals` for the freshest +/// local-path snapshot, falling back to the message-embedded `File` when the +/// store hasn't seen `updateFile` yet. Display title falls back through +/// `fileName` to "Audio". Album art is the embedded minithumbnail (no download). +/// +/// Internal (not private) so `AudioVisualTests` can exercise it directly. +/// Consider moving these tests behind `messageRows(...)` if direct access +/// becomes a maintenance burden. +func audioVisual(for content: MessageContent, fileLocals: [Int: File]) -> AudioVisual? { + guard case .messageAudio(let m) = content else { return nil } + let a = m.audio + let audioFile = fileLocals[a.audio.id] ?? a.audio + let localPath: String? = (audioFile.local.isDownloadingCompleted && !audioFile.local.path.isEmpty) + ? audioFile.local.path : nil + let displayTitle = a.title.isEmpty ? (a.fileName.isEmpty ? "Audio" : a.fileName) : a.title + return AudioVisual( + audioFileId: a.audio.id, + duration: a.duration, + title: displayTitle, + performer: a.performer, + albumArt: a.albumCoverMinithumbnail?.data, + caption: m.caption.text, + localPath: localPath + ) +} + +/// Builds a `DocumentVisual` for `messageDocument` content, or nil otherwise. Looks up the +/// document file id in `fileLocals` for the freshest local-path/size snapshot, falling back to +/// the message-embedded `File`. Internal so `DocumentVisualTests` can exercise it directly. +func documentVisual(for content: MessageContent, fileLocals: [Int: File]) -> DocumentVisual? { + guard case .messageDocument(let m) = content else { return nil } + let d = m.document + let docFile = fileLocals[d.document.id] ?? d.document + let localPath: String? = (docFile.local.isDownloadingCompleted && !docFile.local.path.isEmpty) + ? docFile.local.path : nil + return DocumentVisual( + documentFileId: d.document.id, + fileName: d.fileName.isEmpty ? "File" : d.fileName, + sizeBytes: docFile.size != 0 ? docFile.size : docFile.expectedSize, + localPath: localPath, + caption: m.caption.text + ) +} + +/// Resolves the incoming-group sender label (name + palette index) for a message, +/// or `nil` when no label should be shown (outgoing, private chat, non-user sender, +/// or missing/empty cached name). Name and index are always produced together. +private func senderLabel( + for msg: CachedMessage, + chatType: ChatType, + userNames: [Int64: String] +) -> (name: String, colorIndex: Int)? { + if msg.isOutgoing { return nil } + switch chatType { + case .chatTypeBasicGroup, .chatTypeSupergroup: + if case .messageSenderUser(let u) = msg.senderId, + let name = userNames[u.userId], + !name.isEmpty { + return (name, paletteIndex(for: u.userId)) + } + return nil + default: + return nil + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRowView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRowView.swift new file mode 100644 index 0000000000..766f6d9974 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageRowView.swift @@ -0,0 +1,38 @@ +import SwiftUI + +struct MessageRowView: View { + let row: MessageRow + let onPhotoTap: (PhotoVisual) -> Void + let onVideoTap: (VideoVisual) -> Void + let onVideoNoteTap: (VideoNoteVisual) -> Void + let onPollTap: (Int64, PollVisual) -> Void + var index: Int = 0 + var count: Int = 1 + var onEnterTopEdge: (() -> Void)? = nil + var onEnterBottomEdge: (() -> Void)? = nil + var onIncomingBubbleVisible: ((Int64) -> Void)? = nil + + var body: some View { + rowBody + .onScrollVisibilityChange(threshold: 0.01) { visible in + guard visible else { return } + if index <= 2 { onEnterTopEdge?() } + if index >= count - 3 { onEnterBottomEdge?() } + } + } + + @ViewBuilder + private var rowBody: some View { + switch row { + case .bubble(let b): + MessageBubbleView(bubble: b, onPhotoTap: onPhotoTap, onVideoTap: onVideoTap, onVideoNoteTap: onVideoNoteTap, onPollTap: onPollTap) + .onScrollVisibilityChange(threshold: 0.5) { visible in + guard visible, !b.isOutgoing else { return } + onIncomingBubbleVisible?(b.messageId) + } + case .service(let s): ServiceMessageView(line: s) + case .daySeparator(let d): DaySeparatorView(label: d) + case .unreadDivider: UnreadDividerView() + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/MessageWindow.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageWindow.swift new file mode 100644 index 0000000000..309f14e8f0 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/MessageWindow.swift @@ -0,0 +1,153 @@ +import Foundation +import TDShim + +/// Pure value type owning the loaded contiguous range of cached messages for a +/// single chat. The single home for the gap-prevention invariant: ids in `cache` +/// always form one contiguous range with respect to TDLib's message-id ordering. +/// +/// Anchor semantics: `.tail` means "open at the chat tail"; `.messageId(X)` means +/// "open with X as the entry point" (the unread-divider use case). The anchor is +/// purely informational after construction; bounds are tracked by `loadedLowest/HighestId`. +/// +/// `unreadDividerAfterId` is frozen at construction. The divider row in the +/// projection renders between this id and the next-newer message. Live +/// `updateChatReadInbox` does NOT mutate this field — the divider stays where it +/// was when the chat opened. +struct MessageWindow: Equatable { + enum Anchor: Equatable { case tail, messageId(Int64) } + + private(set) var cache: [Int64: CachedMessage] = [:] + private(set) var loadedLowestId: Int64? = nil + private(set) var loadedHighestId: Int64? = nil + /// True until an older-fetch comes back empty. Defensively flipped true when + /// the cache empties (e.g. via a delete-all) so the next interaction re-fills. + private(set) var hasOlder: Bool = true + /// True once `loadedHighestId == chat.lastMessage?.id` or a newer-fetch is empty. + private(set) var reachesChatTail: Bool = false + + /// Frozen at construction; never mutated thereafter. The divider row renders + /// between this id and the first message with `id > unreadDividerAfterId`. + let unreadDividerAfterId: Int64? + let anchor: Anchor + let halfLimit: Int + + init(anchor: Anchor, halfLimit: Int, unreadDividerAfterId: Int64?) { + self.anchor = anchor + self.halfLimit = halfLimit + self.unreadDividerAfterId = unreadDividerAfterId + } + + /// The row id the view should scroll to on first `.loaded` render. Returns + /// the unread-divider row id ("unread-") when the divider is + /// set AND the loaded cache contains at least one message above the divider. + /// Returns nil otherwise — caller falls back to scrolling the last row to + /// `.bottom`. + var initialScrollTargetId: String? { + guard let divider = unreadDividerAfterId else { return nil } + let hasAnyUnread = cache.keys.contains { $0 > divider } + return hasAnyUnread ? "unread-\(divider)" : nil + } + + // MARK: - Mutators + + mutating func extendInitial(_ messages: [CachedMessage], chatTailId: Int64?) { + for m in messages { cache[m.id] = m } + recomputeBounds() + updateReachesChatTail(chatTailId: chatTailId) + } + + mutating func extendOlder(_ messages: [CachedMessage]) { + if messages.isEmpty { hasOlder = false; return } + for m in messages { cache[m.id] = m } + recomputeBounds() + } + + mutating func extendNewer(_ messages: [CachedMessage], chatTailId: Int64?) { + if messages.isEmpty { reachesChatTail = true; return } + for m in messages { cache[m.id] = m } + recomputeBounds() + updateReachesChatTail(chatTailId: chatTailId) + } + + /// Returns true if the message extends the window or was already present; + /// false if the window doesn't reach the chat tail and the message can't be + /// inserted without creating a gap. The caller bumps a jump-counter on false. + mutating func tryInsertLive(_ message: CachedMessage) -> Bool { + if cache[message.id] != nil { return true } + if reachesChatTail { + cache[message.id] = message + recomputeBounds() + return true + } + return false + } + + mutating func applySendSucceeded(oldId: Int64, message: CachedMessage) { + cache.removeValue(forKey: oldId) + cache[message.id] = message + recomputeBounds() + } + + mutating func applySendFailed(oldId: Int64, message: CachedMessage) { + cache.removeValue(forKey: oldId) + cache[message.id] = message + recomputeBounds() + } + + mutating func applyContentUpdate(id: Int64, newContent: MessageContent) { + guard let existing = cache[id] else { return } + cache[id] = CachedMessage( + id: existing.id, date: existing.date, editDate: existing.editDate, + isOutgoing: existing.isOutgoing, senderId: existing.senderId, + content: newContent, sendingState: existing.sendingState, + replyTo: existing.replyTo + ) + } + + /// Patches the cached `messagePoll` whose `poll.id` matches the updated poll + /// (from `updatePoll`, which carries no message id). No-op if no such message + /// is in the window. The scan is linear over a small (~30–60) window. + mutating func applyPollUpdate(poll: Poll) { + for (id, existing) in cache { + guard case .messagePoll(let mp) = existing.content, mp.poll.id == poll.id else { continue } + let rebuilt = MessagePoll( + canAddOption: mp.canAddOption, + description: mp.description, + media: mp.media, + poll: poll + ) + cache[id] = CachedMessage( + id: existing.id, date: existing.date, editDate: existing.editDate, + isOutgoing: existing.isOutgoing, senderId: existing.senderId, + content: .messagePoll(rebuilt), + sendingState: existing.sendingState, replyTo: existing.replyTo + ) + return + } + } + + mutating func applyDelete(ids: [Int64]) { + for id in ids { cache.removeValue(forKey: id) } + recomputeBounds() + if cache.isEmpty { hasOlder = true } + } + + /// Externally settable when `sendText` runs — sending implicitly pulls the + /// view to the tail, so anything beyond `loadedHighestId` is no longer a "gap" + /// from the user's perspective. + mutating func markReachesChatTail() { reachesChatTail = true } + + mutating func markHasOlderFalse() { hasOlder = false } + + // MARK: - Internals + + private mutating func recomputeBounds() { + loadedLowestId = cache.keys.min() + loadedHighestId = cache.keys.max() + } + + private mutating func updateReachesChatTail(chatTailId: Int64?) { + guard let highest = loadedHighestId, let tail = chatTailId else { return } + if highest >= tail { reachesChatTail = true } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoBubbleView.swift new file mode 100644 index 0000000000..0854dc2721 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoBubbleView.swift @@ -0,0 +1,176 @@ +import SwiftUI +import UIKit + +/// Renders one photo bubble: aspect-fit image (full-resolution when downloaded, +/// minithumbnail otherwise, gray placeholder if neither), optional caption below the +/// image. +/// +/// Drives viewport-based download via `.onScrollVisibilityChange` on the store — +/// `.onAppear` would fire for every off-screen bubble too, because the enclosing +/// `VStack` (chosen over `LazyVStack` for correct scroll-anchor behavior) realizes +/// the whole list up-front. Tap (when downloaded) calls `onTap(photo)`; tap is a +/// no-op while downloading. +struct PhotoBubbleView: View { + let photo: PhotoVisual + let caption: String + let isOutgoing: Bool + let replyHeader: ReplyHeader? + let onTap: (PhotoVisual) -> Void + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var maxWidthPt: CGFloat { metrics.photoMaxWidth } + private var maxHeightPt: CGFloat { metrics.photoMaxHeight } + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + // A caption or reply header gives the bubble chrome; the image still spans the chrome's + // full width (flush, clipped by the bubble). A bare media message (neither) has no chrome. + private var hasChrome: Bool { !caption.isEmpty || replyHeader != nil } + + var body: some View { + Group { + if hasChrome { chromeBubble } else { bareImage } + } + .onScrollVisibilityChange(threshold: 0.01) { visible in + if visible { + store.requestFileDownload(fileId: photo.fileId) + } else { + store.cancelFileDownload(fileId: photo.fileId) + } + } + } + + private var bareImage: some View { + imageView + .frame(width: displaySize.width, height: displaySize.height) + .clipShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + .contentShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + .onTapGesture { if photo.localPath != nil { onTap(photo) } } + } + + private var chromeBubble: some View { + VStack(alignment: .leading, spacing: 2) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + .padding(.horizontal, 8) + .padding(.top, 6) + } + imageView + .frame(width: displaySize.width, height: displaySize.height) + .contentShape(Rectangle()) + .onTapGesture { if photo.localPath != nil { onTap(photo) } } + if !caption.isEmpty { + Text(caption) + .font(.caption) + .foregroundStyle(style.content) + .fixedSize(horizontal: false, vertical: true) + .padding(.horizontal, 8) + } + } + .frame(width: displaySize.width, alignment: .leading) + .background(style.fill) + .clipShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + } + + @ViewBuilder + private var imageView: some View { + if let path = photo.localPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFill() + } else if let data = photo.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 2) + } else { + Color.gray.opacity(0.3) + } + } + + /// Aspect-fits the chosen `PhotoSize` (in pixels) into a `maxWidthPt × maxHeightPt` + /// box (in logical pts). Both dims are clamped, so a tall photo doesn't dominate. + private var displaySize: CGSize { + guard photo.width > 0, photo.height > 0 else { + return CGSize(width: maxWidthPt, height: maxWidthPt) + } + let aspect = CGFloat(photo.height) / CGFloat(photo.width) + var w = maxWidthPt + var h = w * aspect + if h > maxHeightPt { + h = maxHeightPt + w = h / aspect + } + return CGSize(width: w, height: h) + } +} + +#if DEBUG +import TDShim + +private struct PhotoPreviewNoopLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { throw CancellationError() } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { throw CancellationError() } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func photoPreviewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, + unreadCount: 0, + lastMessageId: nil, + loader: PhotoPreviewNoopLoader() + ) +} + +#Preview("Photo bubble — with reply") { + PhotoBubbleView( + photo: PhotoVisual(fileId: 0, width: 800, height: 600, minithumbnail: nil, localPath: nil), + caption: "look at this", + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor earlier", + minithumbnail: nil, + isOutgoing: false + ), + onTap: { _ in } + ) + .bubblePreview() + .environment(photoPreviewStore()) +} + +#Preview("Photo bubble — bare (no caption/reply)") { + PhotoBubbleView( + photo: PhotoVisual(fileId: 0, width: 800, height: 600, minithumbnail: nil, localPath: nil), + caption: "", + isOutgoing: false, + replyHeader: nil, + onTap: { _ in } + ) + .bubblePreview() + .environment(photoPreviewStore()) +} + +#Preview("Photo bubble — caption only") { + PhotoBubbleView( + photo: PhotoVisual(fileId: 0, width: 800, height: 600, minithumbnail: nil, localPath: nil), + caption: "Benji athlete today!", + isOutgoing: false, + replyHeader: nil, + onTap: { _ in } + ) + .bubblePreview() + .environment(photoPreviewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoViewerView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoViewerView.swift new file mode 100644 index 0000000000..8d95b93a67 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoViewerView.swift @@ -0,0 +1,29 @@ +import SwiftUI +import UIKit + +/// Full-screen sheet content for viewing a downloaded photo. Aspect-fits the image into +/// the screen with a black background. Standard sheet dismiss (swipe-down / Digital Crown). +/// +/// Assumes `photo.localPath != nil` (the tap that presents this sheet is gated on +/// download completion). +struct PhotoViewerView: View { + let photo: PhotoVisual + + var body: some View { + ZStack { + Color.black.ignoresSafeArea() + if let path = photo.localPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img) + .resizable() + .scaledToFit() + } else if let data = photo.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img) + .resizable() + .scaledToFit() + .blur(radius: 4) + } else { + Color.gray.opacity(0.3) + } + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoVisual.swift new file mode 100644 index 0000000000..574b7889b0 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PhotoVisual.swift @@ -0,0 +1,33 @@ +import Foundation +import TDShim + +/// Per-photo visual data the bubble view consumes. The projection produces it from +/// `MessagePhoto.photo` + the store's latest `files[fileId]` snapshot. +/// +/// Identifiable by `fileId` so it works with `.sheet(item:)` for the full-screen viewer. +struct PhotoVisual: Identifiable, Equatable, Hashable { + /// TDLib file id of the chosen `PhotoSize`. Stable across re-projections. + var id: Int { fileId } + let fileId: Int + /// Pixel width of the chosen `PhotoSize` (used for aspect ratio). + let width: Int + /// Pixel height of the chosen `PhotoSize`. + let height: Int + /// Tiny embedded JPEG (~40×40) carried inside the message; instantly available. + let minithumbnail: Data? + /// Filesystem path; non-nil iff `isDownloadingCompleted == true && !path.isEmpty`. + let localPath: String? +} + +/// Picks one `PhotoSize` to display on a 208pt-wide watch screen. +/// +/// 1. Prefer the size with `type == "m"` (Telegram's ~320px-wide medium variant). +/// 2. Otherwise pick the largest size with `width <= 320`. +/// 3. Otherwise pick the smallest size available (all sizes are larger than 320). +/// 4. Returns `nil` only on an empty input array (TDLib invariant says this shouldn't happen). +func selectPhotoSize(_ sizes: [PhotoSize]) -> PhotoSize? { + if let m = sizes.first(where: { $0.type == "m" }) { return m } + let underBound = sizes.filter { $0.width <= 320 } + if let largest = underBound.sorted(by: { $0.width > $1.width }).first { return largest } + return sizes.sorted(by: { $0.width < $1.width }).first +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PollBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PollBubbleView.swift new file mode 100644 index 0000000000..44344fdc9e --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PollBubbleView.swift @@ -0,0 +1,187 @@ +import SwiftUI + +/// Renders one poll / quiz bubble inside the standard gray-incoming / accent-outgoing +/// chrome. Shows the question, option rows (with result bars + percentages once +/// `resultsVisible`), quiz correct/chosen markers + explanation, a voter-count footer, +/// and a "Vote" button when the poll is still actionable. Tapping Vote calls +/// `onVote`, which opens the dedicated `PollVoteView`. +struct PollBubbleView: View { + let poll: PollVisual + let isOutgoing: Bool + let replyHeader: ReplyHeader? + let onVote: () -> Void + + @Environment(\.bubbleMetrics) private var metrics + private var maxWidth: CGFloat { metrics.bubbleMaxWidth } + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + private var typeLabel: String { + if poll.isClosed { return "Final results" } + if poll.isQuiz { return "Quiz" } + return poll.isAnonymous ? "Anonymous Poll" : "Poll" + } + + private var canVote: Bool { + guard !poll.isClosed else { return false } + if poll.isQuiz { return !poll.hasVoted } + return !poll.hasVoted || poll.allowsRevoting + } + + private var voterCountLabel: String { + poll.totalVoterCount == 0 + ? "No votes yet" + : "\(poll.totalVoterCount) voter\(poll.totalVoterCount == 1 ? "" : "s")" + } + + private var secondaryColor: Color { style.secondary } + + var body: some View { + VStack(alignment: .leading, spacing: 5) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: BubbleStyle.resolve(isOutgoing: isOutgoing)) + } + Text(typeLabel.uppercased()) + .font(.system(size: 9, weight: .semibold)) + .foregroundStyle(secondaryColor) + Text(poll.question) + .font(.caption).bold() + .fixedSize(horizontal: false, vertical: true) + ForEach(poll.options, id: \.position) { option in + optionRow(option) + } + if let explanation = poll.explanation { + Text(explanation) + .font(.system(size: 9)) + .foregroundStyle(secondaryColor) + .fixedSize(horizontal: false, vertical: true) + } + if canVote { + Button(action: onVote) { + Text("Vote").font(.caption2).bold().frame(maxWidth: .infinity) + } + .buttonStyle(.bordered) + .controlSize(.mini) + } + Text(voterCountLabel) + .font(.system(size: 9)) + .foregroundStyle(secondaryColor) + } + .padding(8) + .frame(minWidth: BubbleShape.minSize, maxWidth: maxWidth, minHeight: BubbleShape.minSize, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + } + + @ViewBuilder + private func optionRow(_ option: PollOptionVisual) -> some View { + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 4) { + optionGlyph(option).font(.system(size: 11)) + Text(option.text).font(.caption2).fixedSize(horizontal: false, vertical: true) + Spacer(minLength: 0) + if poll.resultsVisible { + Text("\(option.votePercentage)%") + .font(.system(size: 9)).foregroundStyle(secondaryColor) + } + } + if poll.resultsVisible { + GeometryReader { geo in + ZStack(alignment: .leading) { + Capsule().fill(secondaryColor.opacity(0.25)).frame(height: 3) + Capsule().fill(barColor(option)) + .frame(width: geo.size.width * CGFloat(option.votePercentage) / 100, height: 3) + } + } + .frame(height: 3) + } + } + } + + @ViewBuilder + private func optionGlyph(_ option: PollOptionVisual) -> some View { + if poll.isQuiz, poll.resultsVisible, let correct = option.isCorrect { + if correct { + Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) + } else if option.isChosen { + Image(systemName: "xmark.circle.fill").foregroundStyle(.red) + } else { + Image(systemName: "circle").foregroundStyle(secondaryColor) + } + } else if option.isChosen { + Image(systemName: "checkmark.circle.fill") + } else { + Image(systemName: "circle").foregroundStyle(secondaryColor) + } + } + + private func barColor(_ option: PollOptionVisual) -> Color { + if poll.isQuiz, let correct = option.isCorrect { + if correct { return .green } + if option.isChosen { return .red } + } + return isOutgoing ? Color.white : Color.accentColor + } +} + +#if DEBUG +private func previewOption( + _ text: String, position: Int = 0, pct: Int = 0, chosen: Bool = false, correct: Bool? = nil +) -> PollOptionVisual { + PollOptionVisual(position: position, text: text, votePercentage: pct, voterCount: pct, + isChosen: chosen, isBeingChosen: false, isCorrect: correct) +} + +#Preview("Regular — unvoted (incoming)") { + PollBubbleView( + poll: PollVisual( + pollId: 1, question: "Favorite color?", isQuiz: false, isAnonymous: true, + isClosed: false, allowsMultipleAnswers: false, allowsRevoting: false, + totalVoterCount: 0, hasVoted: false, explanation: nil, + options: [previewOption("Red", position: 0), previewOption("Green", position: 1), previewOption("Blue", position: 2)] + ), + isOutgoing: false, replyHeader: nil, onVote: {} + ).bubblePreview() +} + +#Preview("Regular — voted (outgoing)") { + PollBubbleView( + poll: PollVisual( + pollId: 1, question: "Favorite color?", isQuiz: false, isAnonymous: true, + isClosed: false, allowsMultipleAnswers: false, allowsRevoting: false, + totalVoterCount: 4, hasVoted: true, explanation: nil, + options: [previewOption("Red", position: 0, pct: 75, chosen: true), + previewOption("Green", position: 1, pct: 25)] + ), + isOutgoing: true, replyHeader: nil, onVote: {} + ).bubblePreview() +} + +#Preview("Quiz — answered wrong (incoming)") { + PollBubbleView( + poll: PollVisual( + pollId: 1, question: "Capital of France?", isQuiz: true, isAnonymous: true, + isClosed: false, allowsMultipleAnswers: false, allowsRevoting: false, + totalVoterCount: 9, hasVoted: true, explanation: "Paris is the capital.", + options: [previewOption("Berlin", position: 0, pct: 30, chosen: true, correct: false), + previewOption("Paris", position: 1, pct: 60, correct: true), + previewOption("Rome", position: 2, pct: 10, correct: false)] + ), + isOutgoing: false, replyHeader: nil, onVote: {} + ).bubblePreview() +} + +#Preview("Closed — final results") { + PollBubbleView( + poll: PollVisual( + pollId: 1, question: "Best day?", isQuiz: false, isAnonymous: true, + isClosed: true, allowsMultipleAnswers: false, allowsRevoting: false, + totalVoterCount: 12, hasVoted: false, explanation: nil, + options: [previewOption("Sat", position: 0, pct: 58), previewOption("Sun", position: 1, pct: 42)] + ), + isOutgoing: false, replyHeader: nil, onVote: {} + ).bubblePreview() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PollVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PollVisual.swift new file mode 100644 index 0000000000..0c1d10bd3b --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PollVisual.swift @@ -0,0 +1,87 @@ +import Foundation +import TDShim + +/// Per-poll data the bubble consumes. Projected from `messagePoll`. Mutually +/// exclusive with the other `MessageBubble` media fields. +struct PollVisual: Equatable, Hashable { + /// The poll's own id (NOT the message id). Used to match `updatePoll`. + let pollId: TdInt64 + let question: String + let isQuiz: Bool + let isAnonymous: Bool + let isClosed: Bool + let allowsMultipleAnswers: Bool + /// Regular polls only; always false for quizzes. + let allowsRevoting: Bool + let totalVoterCount: Int + /// True if any option is `isChosen` — i.e. the current user has voted. + let hasVoted: Bool + /// Quiz explanation; nil for regular polls and for quizzes the user hasn't + /// answered yet (TDLib leaves it empty until then). + let explanation: String? + let options: [PollOptionVisual] + + /// Percent bars / vote counts are only meaningful once the user has voted or + /// the poll has closed — TDLib doesn't populate them before then. + var resultsVisible: Bool { hasVoted || isClosed } +} + +struct PollOptionVisual: Equatable, Hashable { + /// 0-based index into the poll's options — the value passed to `setPollAnswer`. + let position: Int + let text: String + let votePercentage: Int + let voterCount: Int + let isChosen: Bool + let isBeingChosen: Bool + /// Quiz only: true if this option is a correct answer. nil for regular polls. + /// Only meaningful once `correctOptionIds` is populated (after answering/close). + let isCorrect: Bool? +} + +/// Builds a `PollVisual` for `messagePoll` content, or returns `nil` for any other +/// content. Internal (not `private`) so `PollVisualTests` can exercise it directly +/// via `@testable import`, matching the `voiceNoteVisual`/`locationVisual` precedent. +func pollVisual(for content: MessageContent) -> PollVisual? { + guard case .messagePoll(let m) = content else { return nil } + let poll = m.poll + + let isQuiz: Bool + let correctIds: [Int] + let explanationText: String? + if case .pollTypeQuiz(let quiz) = poll.type { + isQuiz = true + correctIds = quiz.correctOptionIds + explanationText = quiz.explanation.text.isEmpty ? nil : quiz.explanation.text + } else { + isQuiz = false + correctIds = [] + explanationText = nil + } + + let options = poll.options.enumerated().map { idx, opt in + PollOptionVisual( + position: idx, + text: opt.text.text, + votePercentage: opt.votePercentage, + voterCount: opt.voterCount, + isChosen: opt.isChosen, + isBeingChosen: opt.isBeingChosen, + isCorrect: isQuiz ? correctIds.contains(idx) : nil + ) + } + + return PollVisual( + pollId: poll.id, + question: poll.question.text, + isQuiz: isQuiz, + isAnonymous: poll.isAnonymous, + isClosed: poll.isClosed, + allowsMultipleAnswers: poll.allowsMultipleAnswers, + allowsRevoting: poll.allowsRevoting, + totalVoterCount: poll.totalVoterCount, + hasVoted: poll.options.contains { $0.isChosen }, + explanation: explanationText, + options: options + ) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/PollVoteView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/PollVoteView.swift new file mode 100644 index 0000000000..9144f316e3 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/PollVoteView.swift @@ -0,0 +1,155 @@ +import SwiftUI + +/// Identifies a poll to vote on. `id` is the message id (unique within a chat), +/// so it doubles as the `.sheet(item:)` identity. +struct PollVoteTarget: Identifiable { + let id: Int64 + let poll: PollVisual +} + +/// The dedicated voting screen. Single-answer regular polls and quizzes cast +/// immediately on tap; multiple-answer polls accumulate selections behind a +/// "Vote" button. After a quiz vote, re-reads the live poll (via `currentPoll`) +/// to reveal the correct answer + explanation inline, then "Done" dismisses. +struct PollVoteView: View { + let initialPoll: PollVisual + /// Re-reads the latest projected poll (post-`updatePoll`) for the quiz reveal. + let currentPoll: () -> PollVisual? + /// Casts the vote; 0-based option positions; returns true on success. + let onVote: ([Int]) async -> Bool + + @Environment(\.dismiss) private var dismiss + @State private var selection: Set = [] + @State private var revealed: Bool = false + @State private var submitting: Bool = false + @State private var liveOverride: PollVisual? + + /// The poll to render: the live (post-vote) snapshot once revealed, else the + /// snapshot the screen opened with. + private var poll: PollVisual { liveOverride ?? initialPoll } + + var body: some View { + ScrollView { + VStack(alignment: .leading, spacing: 8) { + Text(poll.question).font(.headline) + .fixedSize(horizontal: false, vertical: true) + ForEach(poll.options, id: \.position) { option in + optionButton(option) + } + if revealed, let explanation = poll.explanation { + Text(explanation).font(.caption2).foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + if poll.allowsMultipleAnswers && !revealed { + Button("Vote") { + guard !submitting else { return } + Task { _ = await cast(Array(selection).sorted()) } + } + .buttonStyle(.borderedProminent) + .disabled(selection.isEmpty || submitting) + } + if revealed { + Button("Done") { dismiss() }.buttonStyle(.bordered) + } + } + .padding() + } + } + + @ViewBuilder + private func optionButton(_ option: PollOptionVisual) -> some View { + Button { + handleTap(option) + } label: { + HStack(spacing: 6) { + glyph(option) + Text(option.text).fixedSize(horizontal: false, vertical: true) + Spacer(minLength: 0) + } + } + .buttonStyle(.bordered) + .disabled(submitting || revealed) + } + + @ViewBuilder + private func glyph(_ option: PollOptionVisual) -> some View { + if revealed && poll.isQuiz { + if option.isCorrect == true { + Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) + } else if option.isChosen { + Image(systemName: "xmark.circle.fill").foregroundStyle(.red) + } else { + Image(systemName: "circle") + } + } else if poll.allowsMultipleAnswers { + Image(systemName: selection.contains(option.position) ? "checkmark.square.fill" : "square") + } else { + Image(systemName: "circle") + } + } + + private func handleTap(_ option: PollOptionVisual) { + if poll.allowsMultipleAnswers && !poll.isQuiz { + if selection.contains(option.position) { selection.remove(option.position) } + else { selection.insert(option.position) } + return + } + // Single-answer regular OR quiz: cast immediately. + guard !submitting else { return } + Task { _ = await cast([option.position]) } + } + + private func cast(_ ids: [Int]) async -> Bool { + submitting = true + let ok = await onVote(ids) + submitting = false + guard ok else { return false } + if initialPoll.isQuiz { + // Give the resulting updatePoll a moment to land, then read the live + // poll so the reveal shows the true correct answer + explanation. + try? await Task.sleep(nanoseconds: 400_000_000) + liveOverride = currentPoll() ?? initialPoll + revealed = true + } else { + dismiss() + } + return true + } +} + +#if DEBUG +// NOTE: these render blank in the headless EmergeTools snapshot harness — +// watchOS ImageRenderer renders ScrollView content transparent. They are +// accurate in Xcode's live preview canvas; the Vote screen is verified on-sim. + +#Preview("Single-answer") { + PollVoteView( + initialPoll: PollVisual( + pollId: 1, question: "Favorite color?", isQuiz: false, isAnonymous: true, + isClosed: false, allowsMultipleAnswers: false, allowsRevoting: false, + totalVoterCount: 0, hasVoted: false, explanation: nil, + options: [ + PollOptionVisual(position: 0, text: "Red", votePercentage: 0, voterCount: 0, isChosen: false, isBeingChosen: false, isCorrect: nil), + PollOptionVisual(position: 1, text: "Green", votePercentage: 0, voterCount: 0, isChosen: false, isBeingChosen: false, isCorrect: nil) + ] + ), + currentPoll: { nil }, onVote: { _ in true } + ) +} + +#Preview("Multiple-answer") { + PollVoteView( + initialPoll: PollVisual( + pollId: 1, question: "Pick toppings", isQuiz: false, isAnonymous: true, + isClosed: false, allowsMultipleAnswers: true, allowsRevoting: false, + totalVoterCount: 0, hasVoted: false, explanation: nil, + options: [ + PollOptionVisual(position: 0, text: "Cheese", votePercentage: 0, voterCount: 0, isChosen: false, isBeingChosen: false, isCorrect: nil), + PollOptionVisual(position: 1, text: "Mushroom", votePercentage: 0, voterCount: 0, isChosen: false, isBeingChosen: false, isCorrect: nil), + PollOptionVisual(position: 2, text: "Olives", votePercentage: 0, voterCount: 0, isChosen: false, isBeingChosen: false, isCorrect: nil) + ] + ), + currentPoll: { nil }, onVote: { _ in true } + ) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyBar.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyBar.swift new file mode 100644 index 0000000000..19e5db6b19 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyBar.swift @@ -0,0 +1,91 @@ +import SwiftUI + +struct ReplyBar: View { + let onAttachTap: () -> Void + let onSend: (String) -> Void + + // Circular "+" button and reply-field capsule share a single pill + // diameter so they line up vertically and visually mirror the + // watchOS-26 system nav back chevron. The "+" is inset 11pt from the + // chat-content's leading edge so that, combined with the ScrollView's + // outer .padding(.horizontal, 4), it sits 15pt from the screen edge — + // aligned with the system back-chevron in a pushed view. + private static let pillHeight: CGFloat = 35 + + var body: some View { + HStack(spacing: 6) { + Button(action: onAttachTap) { + Image(systemName: "plus") + .font(.system(size: 16, weight: .semibold)) + .frame(width: Self.pillHeight, height: Self.pillHeight) + .contentShape(Circle()) + .glassEffect(in: Circle()) + } + .buttonStyle(.plain) + .padding(.leading, 11) + .accessibilityIdentifier("replyBarAttach") + + // TextFieldLink renders an arbitrary label and, when tapped, + // presents the watchOS system text input UI (QuickboardViewService). + // It returns the committed string in onSubmit. The label IS our + // glassy pill — fully styled, no double-chrome artifacts. + // + // Limitation: TextFieldLink does not accept an initial value, so + // the input UI opens blank every time. Existing server-side drafts + // are not visible or editable from the watch under this design — + // accepted regression vs. the prior ComposeSheet flow. + TextFieldLink(prompt: Text("Reply…")) { + HStack(spacing: 0) { + Text("Reply…") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + Spacer(minLength: 0) + } + .padding(.horizontal, 12) + .frame(height: Self.pillHeight) + .contentShape(Rectangle()) + .glassEffect() + } onSubmit: { newText in + let snapshot = newText.trimmingCharacters(in: .whitespacesAndNewlines) + guard !snapshot.isEmpty else { return } + onSend(snapshot) + } + .buttonStyle(.plain) + .layoutPriority(1) + .accessibilityIdentifier("replyBar") + } + } +} + +#if DEBUG +// Side-by-side reference for the round "+" button. SnapshotPreviews doesn't +// render NavigationStack chrome, so the watchOS-26 system back chevron is +// mocked (same glass-Circle shape) at its in-app position (15pt leading, +// 6pt top) for a direct visual compare against the "+" button at the +// ReplyBar's in-app position (15pt leading, 19pt bottom). Frame matches +// the 46mm canvas (208×248 logical pts). +private struct ReplyBarGeometryPreviewHost: View { + var body: some View { + ZStack { + Color.black + + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .semibold)) + .frame(width: 35, height: 35) + .glassEffect(in: Circle()) + .padding(.leading, 15) + .padding(.top, 6) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + + ReplyBar(onAttachTap: {}, onSend: { _ in }) + .padding(.horizontal, 4) + .padding(.bottom, 19) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) + } + .frame(width: 208, height: 248) + } +} + +#Preview("Reply bar vs nav back") { ReplyBarGeometryPreviewHost() } +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyFormatters.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyFormatters.swift new file mode 100644 index 0000000000..4a3d5d0139 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyFormatters.swift @@ -0,0 +1,126 @@ +import Foundation +import TDShim + +/// Projects a `Message.replyTo` payload into a renderable `ReplyHeader`. Pure. Returns +/// `nil` when there's no reply. See the spec at +/// `docs/superpowers/specs/2026-05-15-tgwatch-reply-headers-design.md`. +/// +/// `inChatId` is the chat the host message lives in — used to decide whether the +/// source is in-chat (consult `cache`) or cross-chat (consult `origin` only). +func replyPreview( + _ replyTo: MessageReplyTo?, + inChatId: Int64, + isOutgoing: Bool, + cache: [Int64: CachedMessage], + userNames: [Int64: String] +) -> ReplyHeader? { + guard let replyTo else { return nil } + + switch replyTo { + case .messageReplyToStory: + return ReplyHeader(senderName: nil, snippet: "Story", minithumbnail: nil, isOutgoing: isOutgoing) + + case .messageReplyToMessage(let r): + let sender = resolveSenderName(replyTo: r, inChatId: inChatId, cache: cache, userNames: userNames) + // Resolve content: prefer TDLib's inline snippet (set when source is foreign/uncached); + // fall back to the in-chat cached source's content (TDLib omits inline content for + // in-chat replies by design). + let resolvedContent: MessageContent? = r.content + ?? (r.chatId == inChatId ? cache[r.messageId]?.content : nil) + let snippet: String = { + if let quote = r.quote, !quote.text.text.isEmpty { + return quote.text.text + } + if let content = resolvedContent { + return replySnippet(content) + } + return "Message" + }() + let mini = minithumbnailData(from: resolvedContent) + return ReplyHeader( + senderName: sender?.name, + snippet: snippet, + minithumbnail: mini, + isOutgoing: isOutgoing, + senderColorIndex: sender?.colorIndex + ) + + case .unsupported: + return nil + } +} + +/// Same shape as `messageBody(_:)` but returns labels (not `""`) for media so the reply +/// header always has something to render. +private func replySnippet(_ content: MessageContent) -> String { + switch content { + case .messageText(let t): + return t.text.text + case .messagePhoto(let m): + return m.caption.text.isEmpty ? "Photo" : m.caption.text + case .messageVideo(let m): + return m.caption.text.isEmpty ? "Video" : m.caption.text + case .messageSticker(let m): + return m.sticker.emoji.isEmpty ? "Sticker" : "Sticker \(m.sticker.emoji)" + case .messageVoiceNote: + return "Voice message" + case .messageAudio(let m): + return m.caption.text.isEmpty ? (m.audio.title.isEmpty ? "Music" : m.audio.title) : m.caption.text + case .messageDocument: + return "Document" + case .messageLocation: + return "Location" + case .messageVenue(let m): + return m.venue.title.isEmpty ? "Location" : m.venue.title + case .messageContact: + return "Contact" + case .messagePoll: + return "Poll" + default: + return "Message" + } +} + +private func resolveSenderName( + replyTo r: MessageReplyToMessage, + inChatId: Int64, + cache: [Int64: CachedMessage], + userNames: [Int64: String] +) -> (name: String, colorIndex: Int)? { + if let origin = r.origin { + switch origin { + case .messageOriginUser(let o): + guard let name = userNames[o.senderUserId], !name.isEmpty else { return nil } + return (name, paletteIndex(for: o.senderUserId)) + case .messageOriginHiddenUser(let o): + guard !o.senderName.isEmpty else { return nil } + return (o.senderName, paletteIndex(forName: o.senderName)) + case .messageOriginChat(let o): + guard !o.authorSignature.isEmpty else { return nil } + return (o.authorSignature, paletteIndex(for: o.senderChatId)) + case .messageOriginChannel(let o): + guard !o.authorSignature.isEmpty else { return nil } + return (o.authorSignature, paletteIndex(for: o.chatId)) + case .unsupported: + return nil + } + } + // Cross-chat: skip cache lookup (we can't trust ids match). + guard r.chatId == inChatId else { return nil } + guard let source = cache[r.messageId] else { return nil } + guard case .messageSenderUser(let u) = source.senderId else { return nil } + guard let name = userNames[u.userId], !name.isEmpty else { return nil } + return (name, paletteIndex(for: u.userId)) +} + +private func minithumbnailData(from content: MessageContent?) -> Data? { + guard let content else { return nil } + switch content { + case .messagePhoto(let m): + return m.photo.minithumbnail?.data + case .messageVideo(let m): + return m.cover?.minithumbnail?.data + default: + return nil + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeader.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeader.swift new file mode 100644 index 0000000000..6bb57ef760 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeader.swift @@ -0,0 +1,25 @@ +import Foundation + +/// Reduced shape of a TDLib reply preview, as projected for rendering. Either embedded +/// inside the host bubble's chrome (text / photo / video) or wrapped in its own mini-card +/// above a chrome-less sticker. Always read-only; produced by `replyPreview(...)`. +struct ReplyHeader: Equatable, Hashable { + /// Source-author display name. `nil` when the source isn't in the local cache and + /// no `MessageOrigin` provided one — view renders snippet only in that case. + let senderName: String? + /// Body text shown under the sender name. `quote.text` when the reply carries a + /// `TextQuote`; else a label derived from the source content (`"Photo"`, `"Video"`, + /// `"Sticker "`, …). Never empty — see `replySnippet`. + let snippet: String + /// Inline minithumbnail bytes for `.messagePhoto` / `.messageVideo` sources. `nil` + /// for stickers, text, and any case where TDLib didn't carry one. View decodes via + /// `UIImage(data:)` and falls back to omitting the slot if decoding fails. + let minithumbnail: Data? + /// Host bubble's outgoing flag. Drives tint mapping inside bubble chrome (white + /// text on accent fill vs primary on gray). Ignored by the sticker mini-card, + /// which always uses incoming styling. + let isOutgoing: Bool + /// Palette index for the author name (incoming styling only); nil = uncolored. + /// Set together with `senderName`. + var senderColorIndex: Int? = nil +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeaderView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeaderView.swift new file mode 100644 index 0000000000..c512d327fb --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ReplyHeaderView.swift @@ -0,0 +1,145 @@ +import SwiftUI +import UIKit + +/// Renders one `ReplyHeader`. Used in two host contexts: +/// +/// 1. Inside the chrome of a text / photo / video bubble (foreground tint follows the +/// host's `isOutgoing` flag). +/// 2. Inside a gray mini-card above a chrome-less sticker (always incoming styling — +/// the card itself provides the chrome, so the header inside doesn't need to +/// invert for outgoing). +/// +/// Layout: leading 2pt accent bar → optional 22pt rounded thumbnail → text column +/// (name + snippet). The view sizes to its content; the host clamps width with +/// `.frame(maxWidth:)`. +struct ReplyHeaderView: View { + let header: ReplyHeader + let style: BubbleStyle + + var body: some View { + HStack(alignment: .top, spacing: 6) { + Rectangle() + .fill(barColor) + .frame(width: 2) + + if let data = header.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img) + .resizable() + .scaledToFill() + .frame(width: 22, height: 22) + .blur(radius: 1) + .clipShape(RoundedRectangle(cornerRadius: 4)) + } + + VStack(alignment: .leading, spacing: 0) { + if let name = header.senderName { + Text(name) + .font(.caption2) + .bold() + .foregroundStyle(primaryColor) + .lineLimit(1) + .truncationMode(.tail) + } + Text(header.snippet) + .font(.caption2) + .italic() + .foregroundStyle(snippetColor) + .lineLimit(1) + .truncationMode(.tail) + } + } + } + + private var barColor: Color { style.replyBar } + private var primaryColor: Color { + if let idx = header.senderColorIndex { return avatarPalette[idx] } + return style.content + } + private var snippetColor: Color { style.secondary } +} + +#if DEBUG +// Synthetic minithumbnail bytes for preview-mode rendering. Generates a small JPEG +// from an SF Symbol so the "with thumb" previews actually exercise the decoded-image +// slot of ReplyHeaderView (without depending on a bundled fixture file). +private let previewThumbBytes: Data? = { + let config = UIImage.SymbolConfiguration(pointSize: 16, weight: .regular) + let img = UIImage(systemName: "photo", withConfiguration: config) + return img?.jpegData(compressionQuality: 0.5) +}() + +#Preview("Reply to text — with sender name") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "hello from earlier", + minithumbnail: nil, isOutgoing: false + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply to text — no sender name") { + ReplyHeaderView(header: .init( + senderName: nil, snippet: "anchor message", + minithumbnail: nil, isOutgoing: false + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply to text — colored author") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "hello from earlier", + minithumbnail: nil, isOutgoing: false, + senderColorIndex: paletteIndex(for: 200) + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply to photo — with thumb") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "Photo", + minithumbnail: previewThumbBytes, isOutgoing: false + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply to video — with thumb") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "Video", + minithumbnail: previewThumbBytes, isOutgoing: false + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply to sticker — text only") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "Sticker ✨", + minithumbnail: nil, isOutgoing: false + ), style: .incoming) + .padding(8) + .background(Color.white) +} + +#Preview("Reply with quote — long, truncated") { + ReplyHeaderView(header: .init( + senderName: "Alice", + snippet: "this is a long quote that should truncate with an ellipsis at the end", + minithumbnail: nil, isOutgoing: false + ), style: .incoming) + .padding(8) + .frame(maxWidth: 180) + .background(Color.white) +} + +#Preview("Outgoing — text reply") { + ReplyHeaderView(header: .init( + senderName: "Alice", snippet: "anchor", + minithumbnail: nil, isOutgoing: true + ), style: .outgoing) + .padding(8) + .background(Color.accentColor) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ReviewVoiceBubble.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ReviewVoiceBubble.swift new file mode 100644 index 0000000000..5aa818ff17 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ReviewVoiceBubble.swift @@ -0,0 +1,48 @@ +import SwiftUI + +/// Voice-note review bubble for the record sheet. Unlike `VoiceNoteBubbleView`, +/// it owns its playback controller directly (no `ChatHistoryStore` environment), +/// since the recorder lives outside any chat store. +struct ReviewVoiceBubble: View { + let draft: VoiceRecordingDraft + let controller: VoicePlaybackController + + private var note: VoiceNoteVisual { + .draft(fileURL: draft.fileURL, duration: draft.duration, waveform: draft.waveform) + } + private var glyph: VoiceGlyph { controller.glyph(for: note.voiceFileId) } + private var progress: Double { controller.progress(for: note.voiceFileId) } + + var body: some View { + // Whole-chrome tap target (matches VoiceNoteBubbleView) — a 16pt glyph alone + // is too small to hit reliably on watch; the glyph is decorative here. + HStack(spacing: 6) { + glyphView + .font(.system(size: 14)) + .frame(width: 16, height: 16) + + WaveformBarsView(amplitudes: unpackWaveform(note.waveform), + progress: progress, isOutgoing: true) + + Text(formatDuration(draft.duration)) + .font(.system(size: 9)) + .foregroundStyle(.white.opacity(0.85)) + } + .padding(.horizontal, 8).padding(.vertical, 6) + .frame(maxWidth: 200, alignment: .leading) + .background(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius).fill(Color.accentColor)) + .foregroundStyle(.white) + .contentShape(Rectangle()) + .onTapGesture { controller.toggle(note: note) } + } + + @ViewBuilder + private var glyphView: some View { + switch glyph { + case .play: Image(systemName: "play.fill") + case .pause: Image(systemName: "pause.fill") + case .spinner: ProgressView().controlSize(.mini).scaleEffect(0.7) + case .error: Image(systemName: "exclamationmark.circle.fill").foregroundStyle(.red) + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceLineFormatter.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceLineFormatter.swift new file mode 100644 index 0000000000..d81446e890 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceLineFormatter.swift @@ -0,0 +1,326 @@ +import Foundation +import TDShim + +/// Renders the actor token for a service message: +/// - sender user matches `selfUserId` → "You" +/// - sender user is in `userNames` → first name +/// - otherwise (unknown user or `messageSenderChat`) → "Someone" +private func serviceActor( + senderId: MessageSender, + selfUserId: Int64?, + userNames: [Int64: String] +) -> String { + if case .messageSenderUser(let u) = senderId { + if let selfId = selfUserId, u.userId == selfId { return "You" } + if let name = userNames[u.userId], !name.isEmpty { return name } + } + return "Someone" +} + +/// Prepends the actor token when `includeActor`, otherwise returns the action +/// with no actor sentence prefix. +private func withActor(_ action: String, actor: String, includeActor: Bool) -> String { + includeActor ? "\(actor) \(action)" : action +} + +/// Bands the seconds value into a human label matching Telegram desktop. +private func autoDeleteDuration(seconds: Int) -> String { + if seconds < 60 { return "\(seconds)s" } + if seconds < 3600 { return "\(seconds / 60) min" } + if seconds < 86400 { return "\(seconds / 3600) h" } + if seconds < 7 * 86400 { + let d = seconds / 86400 + return d == 1 ? "1 day" : "\(d) days" + } + if seconds < 30 * 86400 { + let w = seconds / (7 * 86400) + return w == 1 ? "1 week" : "\(w) weeks" + } + let mo = seconds / (30 * 86400) + return mo == 1 ? "1 month" : "\(mo) months" +} + +/// "{m} m" under 1 km, else whole "{km} km". +private func distanceLabel(meters: Int) -> String { + if meters < 1000 { return "\(meters) m" } + return "\(meters / 1000) km" +} + +/// "channel" when the chat is a broadcast channel; "group" otherwise (incl. nil). +private func groupNoun(_ chatType: ChatType?) -> String { + if case .chatTypeSupergroup(let s) = chatType, s.isChannel { return "channel" } + return "group" +} + +/// Renders the pinned-snippet phrase used in "{Actor} pinned ...". Returns +/// "«{snippet}»" for text content (with truncation to 24 characters + +/// ellipsis), "a {type}" for non-text recognized content, or "a message" for +/// anything else (including service content where messageBody returns ""). +private func pinnedSnippetPhrase(target: CachedMessage?) -> String { + guard let target else { return "a message" } + switch target.content { + case .messageText: + let body = messageBody(target.content) + if body.isEmpty { return "a message" } + if body.count <= 24 { + return "«\(body)»" + } + // Character (extended-grapheme-cluster) prefix — safer than UTF-16 + // truncation for non-ASCII text, and the visual length the reader + // cares about on a 46mm watch. + let truncated = body.prefix(24) + return "«\(truncated)…»" + case .messagePhoto: return "a photo" + case .messageVideo: return "a video" + case .messageVideoNote: return "a video message" + case .messageSticker: return "a sticker" + case .messageVoiceNote: return "a voice message" + case .messageAudio: return "a music file" + case .messageDocument: return "a document" + case .messageLocation: return "a location" + case .messageVenue: return "a venue" + case .messageContact: return "a contact" + case .messagePoll: return "a poll" + default: return "a message" + } +} + +/// Resolves a list of user-ids into a single comma-joined string used as the +/// `{Targets}` token in service lines. +/// +/// Rules (matches the spec): +/// - empty list → caller handles via a different fallback +/// - one known id → first name +/// - two known ids → "Alice and Bob" +/// - three+ known, or any unknown in the list → "Alice and N other(s)" +/// - all unknown → "N member(s)" +private func targetList(_ ids: [Int64], userNames: [Int64: String]) -> String { + let known: [String] = ids.compactMap { id in + guard let n = userNames[id], !n.isEmpty else { return nil } + return n + } + if known.isEmpty { + return ids.count == 1 ? "1 member" : "\(ids.count) members" + } + if ids.count == 1 { + return known[0] + } + if ids.count == 2, known.count == 2 { + return "\(known[0]) and \(known[1])" + } + // Either >=3 ids, or mix of known + unknown. + let others = ids.count - 1 + let suffix = others == 1 ? "other" : "others" + return "\(known[0]) and \(others) \(suffix)" +} + +/// Renders the centered-italic line for a service message, or `nil` for content +/// that should render as a regular bubble. Pure: same inputs always produce the +/// same output. See `docs/superpowers/specs/2026-05-16-service-messages-design.md` +/// for the original classification and string set, and +/// `docs/superpowers/specs/2026-05-29-extend-service-lines-design.md` for the +/// extended coverage (theme/background/boost/forum-topic/upgrade/game-score/ +/// content-protection/sharing/web-app/suggest-photo/proximity/giveaway). +/// +/// - Parameters: +/// - msg: the cached message carrying senderId + content payload. +/// - selfUserId: when the actor matches this id, the actor renders as "You". +/// - userNames: per-store cache of `firstName` keyed by user id. +/// - messageCache: window of cached messages for pinned-snippet lookup. Pass +/// `[:]` from contexts (chat-list) where the cache isn't loaded. +/// - includeActor: when `false`, the leading "{Actor} " token is dropped from +/// the rendered line. Used by the chat-list preview in private/secret chats. +/// - chatType: the chat's type, used to distinguish "Channel created" from +/// "Group created" for `messageSupergroupChatCreate` (both channels and +/// supergroups carry that content type). nil → defaults to "Group created". +func serviceLineText( + _ msg: CachedMessage, + selfUserId: Int64?, + userNames: [Int64: String], + messageCache: [Int64: CachedMessage], + includeActor: Bool, + chatType: ChatType? = nil +) -> String? { + let actor = serviceActor(senderId: msg.senderId, selfUserId: selfUserId, userNames: userNames) + + switch msg.content { + case .messageContactRegistered: + return withActor("joined Telegram", actor: actor, includeActor: includeActor) + + case .messageChatJoinByLink, .messageChatJoinByRequest: + return withActor("joined the chat", actor: actor, includeActor: includeActor) + + case .messageChatAddMembers(let m): + let senderUserId: Int64? = { + if case .messageSenderUser(let u) = msg.senderId { return u.userId } + return nil + }() + if m.memberUserIds.isEmpty { + return withActor("added members", actor: actor, includeActor: includeActor) + } + if let s = senderUserId, m.memberUserIds == [s] { + // Self-add collapses to a join line. + return withActor("joined the chat", actor: actor, includeActor: includeActor) + } + return withActor("added \(targetList(m.memberUserIds, userNames: userNames))", + actor: actor, includeActor: includeActor) + + case .messageChatDeleteMember(let m): + let senderUserId: Int64? = { + if case .messageSenderUser(let u) = msg.senderId { return u.userId } + return nil + }() + if let s = senderUserId, m.userId == s { + return withActor("left the chat", actor: actor, includeActor: includeActor) + } + if let name = userNames[m.userId], !name.isEmpty { + return withActor("removed \(name)", actor: actor, includeActor: includeActor) + } + return withActor("removed a member", actor: actor, includeActor: includeActor) + + case .messageChatChangeTitle(let m): + return withActor("changed group name to \"\(m.title)\"", actor: actor, includeActor: includeActor) + + case .messageChatChangePhoto: + return withActor("changed group photo", actor: actor, includeActor: includeActor) + + case .messageChatDeletePhoto: + return withActor("removed group photo", actor: actor, includeActor: includeActor) + + case .messagePinMessage(let m): + let target = messageCache[m.messageId] + let phrase = pinnedSnippetPhrase(target: target) + return withActor("pinned \(phrase)", actor: actor, includeActor: includeActor) + + case .messageVideoChatScheduled: + return withActor("scheduled a video chat", actor: actor, includeActor: includeActor) + + case .messageVideoChatStarted: + return withActor("started a video chat", actor: actor, includeActor: includeActor) + + case .messageVideoChatEnded(let m): + // Actor intentionally dropped — TDLib emits one but stock Telegram clients + // render this without one. includeActor is ignored. + if m.duration >= 60 { + let mins = m.duration / 60 + return "Video chat ended (\(mins) min)" + } + return "Video chat ended" + + case .messageInviteVideoChatParticipants(let m): + if m.userIds.isEmpty { + return withActor("invited members to the video chat", actor: actor, includeActor: includeActor) + } + return withActor("invited \(targetList(m.userIds, userNames: userNames)) to the video chat", + actor: actor, includeActor: includeActor) + + case .messageScreenshotTaken: + return withActor("took a screenshot", actor: actor, includeActor: includeActor) + + case .messageChatSetMessageAutoDeleteTime(let m): + if m.messageAutoDeleteTime == 0 { + return withActor("disabled auto-delete", actor: actor, includeActor: includeActor) + } + let label = autoDeleteDuration(seconds: m.messageAutoDeleteTime) + return withActor("set messages to auto-delete after \(label)", + actor: actor, includeActor: includeActor) + + case .messageCustomServiceAction(let m): + return m.text + + case .messageBasicGroupChatCreate: + // Chat-lifecycle event — rendered without an actor (Telegram convention). + return "Group created" + + case .messageSupergroupChatCreate: + // Both channels and supergroups carry this content type; the chat type + // is the only signal for which word to use. + if case .chatTypeSupergroup(let s) = chatType, s.isChannel { + return "Channel created" + } + return "Group created" + + case .messageForumTopicCreated(let m): + return withActor("created topic «\(m.name)»", actor: actor, includeActor: includeActor) + + case .messageForumTopicEdited(let m): + if !m.name.isEmpty { + return withActor("changed the topic name to «\(m.name)»", actor: actor, includeActor: includeActor) + } + if m.editIconCustomEmojiId { + return withActor("changed the topic icon", actor: actor, includeActor: includeActor) + } + return withActor("edited the topic", actor: actor, includeActor: includeActor) + + case .messageForumTopicIsClosedToggled(let m): + return withActor(m.isClosed ? "closed the topic" : "reopened the topic", + actor: actor, includeActor: includeActor) + + case .messageForumTopicIsHiddenToggled(let m): + return withActor(m.isHidden ? "hid the topic" : "unhid the topic", + actor: actor, includeActor: includeActor) + + case .messageChatSetTheme(let m): + guard let theme = m.theme else { + return withActor("disabled the chat theme", actor: actor, includeActor: includeActor) + } + if case .chatThemeEmoji(let e) = theme { + return withActor("changed the chat theme to \(e.name)", actor: actor, includeActor: includeActor) + } + return withActor("changed the chat theme", actor: actor, includeActor: includeActor) + + case .messageChatSetBackground: + return withActor("changed the chat background", actor: actor, includeActor: includeActor) + + case .messageChatBoost(let m): + let noun = groupNoun(chatType) + if m.boostCount == 1 { + return withActor("boosted the \(noun)", actor: actor, includeActor: includeActor) + } + return withActor("boosted the \(noun) \(m.boostCount) times", actor: actor, includeActor: includeActor) + + case .messageChatHasProtectedContentToggled(let m): + return withActor(m.newHasProtectedContent ? "enabled content protection" : "disabled content protection", + actor: actor, includeActor: includeActor) + + case .messageChatUpgradeTo, .messageChatUpgradeFrom: + // Chat-lifecycle event — actor-less (Telegram convention), ignores includeActor. + return "Group upgraded to a supergroup" + + case .messageChatShared: + return withActor("shared a chat", actor: actor, includeActor: includeActor) + + case .messageUsersShared(let m): + let action = m.users.count == 1 ? "shared a user" : "shared \(m.users.count) users" + return withActor(action, actor: actor, includeActor: includeActor) + + case .messageBotWriteAccessAllowed: + return withActor("allowed this bot to message you", actor: actor, includeActor: includeActor) + + case .messageWebAppDataSent: + return withActor("sent data to the bot", actor: actor, includeActor: includeActor) + + case .messageSuggestProfilePhoto: + return withActor("suggested a new profile photo", actor: actor, includeActor: includeActor) + + case .messageGameScore(let m): + return withActor("scored \(m.score)", actor: actor, includeActor: includeActor) + + case .messageProximityAlertTriggered(let m): + // Actor-less: the line names traveler + watcher, not the message sender. + let traveler = serviceActor(senderId: m.travelerId, selfUserId: selfUserId, userNames: userNames) + let watcher = serviceActor(senderId: m.watcherId, selfUserId: selfUserId, userNames: userNames) + return "\(traveler) is within \(distanceLabel(meters: m.distance)) of \(watcher)" + + case .messageGiveawayCreated(let m): + // Actor-less — giveaways are posted by channels. + return m.starCount > 0 ? "Stars giveaway started" : "Giveaway started" + + case .messageGiveawayCompleted(let m): + let winners = m.winnerCount == 1 ? "1 winner" : "\(m.winnerCount) winners" + return "Giveaway ended — \(winners)" + + default: + return nil + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceMessageView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceMessageView.swift new file mode 100644 index 0000000000..9153f20b0b --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/ServiceMessageView.swift @@ -0,0 +1,57 @@ +import SwiftUI + +struct ServiceMessageView: View { + let line: ServiceLine + + var body: some View { + HStack { + Spacer() + Text(line.text) + .font(.caption2) + .italic() + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + Spacer() + } + .padding(.vertical, 2) + .accessibilityIdentifier("service.\(line.messageId)") + } +} + +#if DEBUG +#Preview("Service — joined chat (private list form)") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "joined the chat")) +} + +#Preview("Service — joined chat (group, with actor)") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice joined the chat")) +} + +#Preview("Service — added members (1 known)") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice added Bob")) +} + +#Preview("Service — added members (2 known)") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice added Bob and Carol")) +} + +#Preview("Service — pinned with snippet") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice pinned «hello team»")) +} + +#Preview("Service — pinned, target absent") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "You pinned a message")) +} + +#Preview("Service — video chat ended") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Video chat ended (5 min)")) +} + +#Preview("Service — auto-delete enabled") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice set messages to auto-delete after 7 days")) +} + +#Preview("Service — auto-delete disabled") { + ServiceMessageView(line: ServiceLine(messageId: 1, text: "Alice disabled auto-delete")) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/UnreadDividerView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/UnreadDividerView.swift new file mode 100644 index 0000000000..f7832d59dc --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/UnreadDividerView.swift @@ -0,0 +1,35 @@ +import SwiftUI + +struct UnreadDividerView: View { + var body: some View { + HStack(spacing: 0) { + Rectangle().fill(Color.gray.opacity(0.35)).frame(height: 0.5) + Text("Unread") + .font(.system(size: 10, weight: .medium)) + .foregroundStyle(.secondary) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(Capsule().fill(Color.gray.opacity(0.18))) + Rectangle().fill(Color.gray.opacity(0.35)).frame(height: 0.5) + } + .padding(.vertical, 4) + .accessibilityIdentifier("unreadDivider") + } +} + +#if DEBUG +#Preview("Divider isolated") { + UnreadDividerView().frame(width: 200).padding() +} + +#Preview("Divider between bubbles") { + VStack(spacing: 4) { + Text("Yesterday").font(.caption2).foregroundStyle(.secondary) + Text("Hi there!").padding(8).background(Capsule().fill(.gray.opacity(0.2))) + UnreadDividerView() + Text("New message").padding(8).background(Capsule().fill(.gray.opacity(0.2))) + } + .frame(width: 200) + .padding() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/UnsupportedBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/UnsupportedBubbleView.swift new file mode 100644 index 0000000000..b2d4eeb0e8 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/UnsupportedBubbleView.swift @@ -0,0 +1,58 @@ +import SwiftUI + +/// Placeholder bubble for message content types the app does not render +/// (`isUnsupportedContent`). Mirrors the standard text-bubble chrome — +/// `BubbleShape` corner radius + min-size pill, incoming/outgoing fill via +/// `BubbleStyle` — and shows an italic, dimmed "Unsupported message" line. +/// Renders the reply header when present; captions are intentionally ignored. +struct UnsupportedBubbleView: View { + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + var body: some View { + VStack(alignment: isOutgoing ? .trailing : .leading, spacing: 2) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + } + Text("Unsupported message") + .font(.caption) + .italic() + .foregroundStyle(style.content.opacity(0.6)) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.horizontal, 9) + .padding(.vertical, 4) + .frame(minWidth: BubbleShape.minSize, minHeight: BubbleShape.minSize) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + } +} + +#if DEBUG +#Preview("Unsupported — incoming") { + UnsupportedBubbleView(isOutgoing: false, replyHeader: nil) + .bubblePreview() +} + +#Preview("Unsupported — outgoing") { + UnsupportedBubbleView(isOutgoing: true, replyHeader: nil) + .bubblePreview() +} + +#Preview("Unsupported — incoming with reply") { + UnsupportedBubbleView( + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor message earlier in the chat", + minithumbnail: nil, isOutgoing: false + ) + ) + .bubblePreview() +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoBubbleView.swift new file mode 100644 index 0000000000..204d36b5a7 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoBubbleView.swift @@ -0,0 +1,199 @@ +import SwiftUI +import UIKit + +/// Renders one video bubble: aspect-fit preview thumbnail (downloaded preview, blurred +/// minithumbnail, or gray placeholder), centered play overlay, top-trailing duration +/// pill, optional caption below the image. +/// +/// Drives viewport-based download of the *preview* file id (not the full video) via +/// `.onScrollVisibilityChange` on the store — `.onAppear` would fire for every +/// off-screen bubble too, because the enclosing `VStack` (chosen over `LazyVStack` +/// for correct scroll-anchor behavior) realizes the whole list up-front. The full +/// video file is downloaded by `VideoPlayerView` when the user taps to play. +/// +/// Tap is unconditional — the viewer handles the not-yet-downloaded state. +struct VideoBubbleView: View { + let video: VideoVisual + let caption: String + let isOutgoing: Bool + let replyHeader: ReplyHeader? + let onTap: () -> Void + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var maxWidthPt: CGFloat { metrics.photoMaxWidth } + private var maxHeightPt: CGFloat { metrics.photoMaxHeight } + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + // A caption or reply header gives the bubble chrome; the video still spans the chrome's + // full width (flush, clipped by the bubble). A bare video (neither) has no chrome. + private var hasChrome: Bool { !caption.isEmpty || replyHeader != nil } + + var body: some View { + Group { + if hasChrome { chromeBubble } else { bareVideo } + } + .onScrollVisibilityChange(threshold: 0.01) { visible in + guard let id = video.preview.previewFileId else { return } + if visible { + store.requestFileDownload(fileId: id) + } else { + store.cancelFileDownload(fileId: id) + } + } + } + + private var bareVideo: some View { + videoContent(clipImage: true) + .contentShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + .onTapGesture { onTap() } + } + + private var chromeBubble: some View { + VStack(alignment: .leading, spacing: 2) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: style) + .padding(.horizontal, 8) + .padding(.top, 6) + } + videoContent(clipImage: false) + .contentShape(Rectangle()) + .onTapGesture { onTap() } + if !caption.isEmpty { + Text(caption) + .font(.caption) + .foregroundStyle(style.content) + .fixedSize(horizontal: false, vertical: true) + .padding(.horizontal, 8) + } + } + .frame(width: displaySize.width, alignment: .leading) + .background(style.fill) + .clipShape(RoundedRectangle(cornerRadius: BubbleShape.cornerRadius)) + } + + private func videoContent(clipImage: Bool) -> some View { + ZStack { + imageView + .frame(width: displaySize.width, height: displaySize.height) + .clipShape(RoundedRectangle(cornerRadius: clipImage ? BubbleShape.cornerRadius : 0)) + .overlay(alignment: .topTrailing) { + Text(formatDuration(video.duration)) + .font(.system(size: 9)) + .foregroundStyle(.white) + .padding(.horizontal, 4) + .padding(.vertical, 1) + .background(Capsule().fill(.black.opacity(0.5))) + .padding(4) + } + playOverlay + } + } + + @ViewBuilder + private var imageView: some View { + if let path = video.preview.previewLocalPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFill() + } else if let data = video.preview.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 2) + } else { + Color.gray.opacity(0.3) + } + } + + private var playOverlay: some View { + Image(systemName: "play.circle.fill") + .font(.system(size: 32)) + .foregroundStyle(.white) + .shadow(radius: 2) + } + + /// Aspect-fits the chosen preview pixel size into a `maxWidthPt × maxHeightPt` + /// box (in logical pts). Both dims are clamped, so a tall video doesn't dominate. + private var displaySize: CGSize { + guard video.preview.previewWidth > 0, video.preview.previewHeight > 0 else { + return CGSize(width: maxWidthPt, height: maxWidthPt) + } + let aspect = CGFloat(video.preview.previewHeight) / CGFloat(video.preview.previewWidth) + var w = maxWidthPt + var h = w * aspect + if h > maxHeightPt { + h = maxHeightPt + w = h / aspect + } + return CGSize(width: w, height: h) + } +} + +#if DEBUG +import TDShim + +private struct VideoPreviewNoopLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { throw CancellationError() } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { throw CancellationError() } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func videoPreviewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, + unreadCount: 0, + lastMessageId: nil, + loader: VideoPreviewNoopLoader() + ) +} + +#Preview("Video bubble — with reply") { + VideoBubbleView( + video: VideoVisual( + videoFileId: 0, width: 640, height: 360, duration: 12, + mimeType: "video/mp4", + preview: VideoPreview(previewFileId: nil, previewWidth: 640, previewHeight: 360, minithumbnail: nil, previewLocalPath: nil), + videoLocalPath: nil + ), + caption: "check this clip", + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor earlier", + minithumbnail: nil, + isOutgoing: false + ), + onTap: {} + ) + .bubblePreview() + .environment(videoPreviewStore()) +} + +#Preview("Video bubble — bare (no caption/reply)") { + VideoBubbleView( + video: VideoVisual( + videoFileId: 0, width: 640, height: 360, duration: 165, + mimeType: "video/mp4", + preview: VideoPreview(previewFileId: nil, previewWidth: 640, previewHeight: 360, minithumbnail: nil, previewLocalPath: nil), + videoLocalPath: nil + ), + caption: "", + isOutgoing: false, + replyHeader: nil, + onTap: {} + ) + .bubblePreview() + .environment(videoPreviewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteBubbleView.swift new file mode 100644 index 0000000000..fd8b93b08e --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteBubbleView.swift @@ -0,0 +1,165 @@ +import SwiftUI +import UIKit + +/// Renders one round-video-note bubble: a chrome-less 150 pt circle with the +/// preview image (downloaded thumb → blurred minithumbnail → gray placeholder) +/// behind a centered play overlay and a bottom-inside duration pill. Optional +/// reply mini-card (`StickerBubbleView` precedent) sits above. +/// +/// Drives viewport-based download of the thumbnail file id (not the playable +/// video file) via `.onScrollVisibilityChange`. The playable file is downloaded +/// by `VideoNotePlayerView` when the user taps to play. +/// +/// Tap is unconditional — the viewer handles the not-yet-downloaded state. +struct VideoNoteBubbleView: View { + let note: VideoNoteVisual + let isOutgoing: Bool + let replyHeader: ReplyHeader? + let onTap: () -> Void + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var diameter: CGFloat { metrics.videoNoteDiameter } + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + var body: some View { + VStack(alignment: isOutgoing ? .trailing : .leading, spacing: 2) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: BubbleStyle.resolve(isOutgoing: isOutgoing)) + .padding(6) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(style.fill) + ) + .frame(maxWidth: diameter) + } + ZStack { + imageView + .frame(width: diameter, height: diameter) + .clipShape(Circle()) + playOverlay + } + .overlay(alignment: .bottom) { + Text(formatDuration(note.duration)) + .font(.system(size: 9)) + .foregroundStyle(.white) + .padding(.horizontal, 4) + .padding(.vertical, 1) + .background(Capsule().fill(.black.opacity(0.5))) + .padding(.bottom, 6) + } + .contentShape(Circle()) + .onTapGesture { onTap() } + .onScrollVisibilityChange(threshold: 0.01) { visible in + guard let id = note.thumbFileId else { return } + if visible { + store.requestFileDownload(fileId: id) + } else { + store.cancelFileDownload(fileId: id) + } + } + } + } + + @ViewBuilder + private var imageView: some View { + if let path = note.thumbLocalPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFill() + } else if let data = note.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 2) + } else { + Color.gray.opacity(0.3) + } + } + + private var playOverlay: some View { + Image(systemName: "play.circle.fill") + .font(.system(size: 32)) + .foregroundStyle(.white) + .shadow(radius: 2) + } +} + +#if DEBUG +import TDShim + +private struct VideoNotePreviewNoopLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { throw CancellationError() } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { throw CancellationError() } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func videoNotePreviewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, + unreadCount: 0, + lastMessageId: nil, + loader: VideoNotePreviewNoopLoader() + ) +} + +#Preview("VideoNote — incoming, no reply") { + VideoNoteBubbleView( + note: VideoNoteVisual( + videoFileId: 0, length: 240, duration: 5, + thumbFileId: nil, minithumbnail: nil, + thumbLocalPath: nil, videoLocalPath: nil + ), + isOutgoing: false, + replyHeader: nil, + onTap: {} + ) + .bubblePreview() + .environment(videoNotePreviewStore()) +} + +#Preview("VideoNote — outgoing") { + VideoNoteBubbleView( + note: VideoNoteVisual( + videoFileId: 0, length: 240, duration: 32, + thumbFileId: nil, minithumbnail: nil, + thumbLocalPath: nil, videoLocalPath: nil + ), + isOutgoing: true, + replyHeader: nil, + onTap: {} + ) + .bubblePreview() + .environment(videoNotePreviewStore()) +} + +#Preview("VideoNote — incoming with reply") { + VideoNoteBubbleView( + note: VideoNoteVisual( + videoFileId: 0, length: 240, duration: 5, + thumbFileId: nil, minithumbnail: nil, + thumbLocalPath: nil, videoLocalPath: nil + ), + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor earlier in the chat", + minithumbnail: nil, + isOutgoing: false + ), + onTap: {} + ) + .bubblePreview() + .environment(videoNotePreviewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNotePlayerView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNotePlayerView.swift new file mode 100644 index 0000000000..64836f080f --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNotePlayerView.swift @@ -0,0 +1,177 @@ +import SwiftUI +import UIKit +import AVKit +import AVFoundation +import OSLog + +/// Full-screen sheet that plays a round video note inside a 180pt masked circle +/// with AVKit chrome suppressed. State machine mirrors `VideoPlayerView`: +/// preparing → playing → (replay) → dismiss. Chrome suppression uses two tricks +/// from `VideoPlayerView`: `.allowsHitTesting(false)` on the `VideoPlayer` so +/// taps never trigger chrome reveal, and `.opacity(0)` at end-of-playback so +/// AVKit's centered play arrow is hidden behind a peer preview image + Replay +/// button. +struct VideoNotePlayerView: View { + let note: VideoNoteVisual + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.dismiss) private var dismiss + + @State private var phase: Phase = .preparing(progress: 0) + @State private var player: AVPlayer? + @State private var hasReachedEnd = false + @State private var endObserver: NSObjectProtocol? + @State private var statusObserver: NSKeyValueObservation? + + private let logger = Logger(subsystem: "com.isaac.tgwatch", category: "videoplayer") + + enum Phase: Equatable { + case preparing(progress: Double) + case playing + case failed(String) + } + + private let diameter: CGFloat = 180 + + var body: some View { + ZStack { + Color.black.ignoresSafeArea() + content + } + .onAppear { + if let path = note.videoLocalPath, !path.isEmpty, FileManager.default.fileExists(atPath: path) { + setupPlayer(path: path) + phase = .playing + } else { + store.requestFileDownload(fileId: note.videoFileId, priority: 2) + } + } + .onChange(of: store.fileSnapshot(fileId: note.videoFileId)) { _, snap in + guard let snap else { return } + if snap.local.isDownloadingCompleted, !snap.local.path.isEmpty { + if player == nil { + setupPlayer(path: snap.local.path) + phase = .playing + } + } else { + let total = max(1, snap.expectedSize) + let progress = Double(snap.local.downloadedSize) / Double(total) + phase = .preparing(progress: min(1, max(0, progress))) + } + } + .onDisappear { + player?.pause() + player = nil + if let endObserver { + NotificationCenter.default.removeObserver(endObserver) + } + endObserver = nil + statusObserver?.invalidate() + statusObserver = nil + try? AVAudioSession.sharedInstance().setActive(false) + store.cancelFileDownload(fileId: note.videoFileId) + } + } + + @ViewBuilder + private var content: some View { + switch phase { + case .preparing(let progress): + ZStack { + previewImage + .frame(width: diameter, height: diameter) + .clipShape(Circle()) + VStack(spacing: 4) { + ProgressView() + Text("\(Int(progress * 100))%") + .font(.caption2) + .foregroundStyle(.white.opacity(0.85)) + } + } + case .playing: + ZStack { + if let player { + VideoPlayer(player: player) + .frame(width: diameter, height: diameter) + .clipShape(Circle()) + .allowsHitTesting(false) + .opacity(hasReachedEnd ? 0 : 1) + } + if hasReachedEnd { + previewImage + .frame(width: diameter, height: diameter) + .clipShape(Circle()) + Button { + player?.seek(to: .zero) + player?.play() + hasReachedEnd = false + } label: { + Image(systemName: "arrow.counterclockwise.circle.fill") + .font(.system(size: 36)) + .foregroundStyle(.white) + } + .buttonStyle(.plain) + } + } + case .failed(let message): + VStack(spacing: 8) { + Text(message) + .font(.caption) + .foregroundStyle(.white) + .multilineTextAlignment(.center) + Button("Retry") { + phase = .preparing(progress: 0) + store.requestFileDownload(fileId: note.videoFileId, priority: 2) + } + .buttonStyle(.borderedProminent) + } + .padding() + } + } + + @ViewBuilder + private var previewImage: some View { + if let path = note.thumbLocalPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 4) + } else if let data = note.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFill().blur(radius: 4) + } else { + Color.gray.opacity(0.3) + } + } + + private func setupPlayer(path: String) { + let p = AVPlayer(url: URL(fileURLWithPath: path)) + if let item = p.currentItem { + endObserver = NotificationCenter.default.addObserver( + forName: .AVPlayerItemDidPlayToEndTime, + object: item, + queue: .main + ) { _ in + hasReachedEnd = true + } + statusObserver = item.observe(\.status, options: [.new]) { item, _ in + if item.status == .failed { + Task { @MainActor in + phase = .failed("Could not play video") + } + } + } + } + activatePlaybackSession() + player = p + p.play() + } + + /// watchOS routes AVPlayer audio to the speaker only under an active `.playback` + /// session; without it the video note plays silently. See VideoPlayerView. + private func activatePlaybackSession() { + let session = AVAudioSession.sharedInstance() + do { + try session.setCategory(.playback, mode: .moviePlayback) + try session.setActive(true) + } catch { + logger.error("audio session activation failed: \(error.localizedDescription, privacy: .public)") + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteVisual.swift new file mode 100644 index 0000000000..c0fcfb131b --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoNoteVisual.swift @@ -0,0 +1,27 @@ +import Foundation +import TDShim + +/// Per-round-note data the bubble + viewer consume. The projection produces it +/// from `MessageVideoNote` + the store's latest `files[fileId]` snapshots for both +/// the playable video file and (optionally) the thumbnail file. +/// +/// Identifiable by `videoFileId` so it works with `.sheet(item:)` for the player. +struct VideoNoteVisual: Identifiable, Equatable, Hashable { + /// File id of the playable video (the file the viewer downloads on tap). + var id: Int { videoFileId } + let videoFileId: Int + /// Square side length in pixels (`VideoNote.length`); for reference — bubble + /// + viewer use fixed display sizes. + let length: Int + /// Duration in seconds (0…60); rendered in the duration pill. + let duration: Int + /// File id of `VideoNote.thumbnail?.file` when present; nil when only + /// `minithumbnail` is available. + let thumbFileId: Int? + /// Tiny inline JPEG carried by the message itself; instantly available, blurry. + let minithumbnail: Data? + /// Filesystem path of the thumbnail file once downloaded; nil otherwise. + let thumbLocalPath: String? + /// Filesystem path of the playable video file once downloaded; nil otherwise. + let videoLocalPath: String? +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoPlayerView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoPlayerView.swift new file mode 100644 index 0000000000..6d7fe2f3ec --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoPlayerView.swift @@ -0,0 +1,229 @@ +import SwiftUI +import AVKit +import AVFoundation +import Combine +import OSLog +import UIKit +import TDShim // for `File?` in handleSnapshot — note this shadows Swift.Error and Foundation.Date; we use neither here + +/// Full-screen sheet content for downloading + playing a video. +/// +/// State machine: `.preparing(progress:)` → `.playing` → optional Replay overlay when +/// playback ends. `.failed(...)` is reached only when the AVPlayer item transitions to +/// `.failed` (observed via `currentItem.publisher(for: \.status)`). Download-RPC errors +/// are swallowed by the store and never surface here; download stalls (no error, just +/// no progress) stay in `.preparing` indefinitely — the user dismisses to retry. +/// +/// On appear, fires `requestFileDownload(fileId:priority:2)` (priority 2 jumps ahead of +/// off-screen bubble previews). On disappear, pauses the player, nils it (releasing the +/// audio session + decoder), cancels the status subscription, and fires +/// `cancelFileDownload(fileId:)` — TDLib short-circuits if the file already completed. +struct VideoPlayerView: View { + let video: VideoVisual + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.dismiss) private var dismiss + + @State private var phase: Phase = .preparing(progress: 0) + @State private var player: AVPlayer? + @State private var hasReachedEnd = false + @State private var endObserver: NSObjectProtocol? + @State private var statusObserver: AnyCancellable? + + private let logger = Logger(subsystem: "com.isaac.tgwatch", category: "videoplayer") + + enum Phase: Equatable { + case preparing(progress: Double) // 0...1 + case playing + /// Reached only when AVPlayer's `currentItem.status` transitions to `.failed`. + /// Download-RPC errors do NOT reach here (the store swallows them); download + /// stalls also stay in `.preparing` until the user dismisses. + case failed(String) + } + + var body: some View { + ZStack { + Color.black.ignoresSafeArea() + content + } + .onAppear { onAppear() } + .onDisappear { onDisappear() } + .onChange(of: store.fileSnapshot(fileId: video.videoFileId)) { _, snap in + handleSnapshot(snap) + } + } + + @ViewBuilder + private var content: some View { + switch phase { + case .preparing(let progress): + preparingContent(progress: progress) + case .playing: + playingContent + case .failed(let message): + failedContent(message: message) + } + } + + // MARK: - Phase contents + + private func preparingContent(progress: Double) -> some View { + ZStack { + previewBackground.blur(radius: 6) + VStack(spacing: 6) { + ProgressView() + .progressViewStyle(.circular) + .tint(.white) + Text("\(Int(progress * 100))%") + .font(.caption2) + .foregroundStyle(.white.opacity(0.85)) + .monospacedDigit() + } + } + } + + private var playingContent: some View { + ZStack { + if let player { + // Hide AVKit's chrome (which centers its own play arrow at end-of-playback) + // when our Replay overlay is showing — otherwise the two icons stack at the + // same coordinates and the user can't tell which is which (or whose tap wins). + VideoPlayer(player: player) + .opacity(hasReachedEnd ? 0 : 1) + .allowsHitTesting(!hasReachedEnd) + } + if hasReachedEnd { + // Visual fallback so the screen isn't pure black under just the Replay icon. + previewBackground.blur(radius: 6) + Button { + player?.seek(to: .zero) + player?.play() + hasReachedEnd = false + } label: { + Image(systemName: "arrow.counterclockwise.circle.fill") + .font(.system(size: 36)) + .foregroundStyle(.white) + .shadow(radius: 2) + } + .buttonStyle(.plain) + } + } + } + + private func failedContent(message: String) -> some View { + ZStack { + previewBackground.blur(radius: 6) + VStack(spacing: 6) { + Text(message) + .font(.caption2) + .foregroundStyle(.white) + .multilineTextAlignment(.center) + Button("Retry") { + phase = .preparing(progress: 0) + store.requestFileDownload(fileId: video.videoFileId, priority: 2) + // If the file is already complete on disk, no further updateFile + // will arrive — short-circuit by re-handling the current snapshot + // so setupPlayer fires immediately. + handleSnapshot(store.fileSnapshot(fileId: video.videoFileId)) + } + .buttonStyle(.borderedProminent) + } + .padding() + } + } + + @ViewBuilder + private var previewBackground: some View { + if let path = video.preview.previewLocalPath, let img = UIImage(contentsOfFile: path) { + Image(uiImage: img).resizable().scaledToFit() + } else if let data = video.preview.minithumbnail, let img = UIImage(data: data) { + Image(uiImage: img).resizable().scaledToFit() + } else { + Color.gray.opacity(0.3) + } + } + + // MARK: - Lifecycle handlers + + private func onAppear() { + if let path = video.videoLocalPath, FileManager.default.fileExists(atPath: path) { + setupPlayer(path: path) + return + } + // Initial snapshot in case updateFile already arrived before the view did. + if let snap = store.fileSnapshot(fileId: video.videoFileId) { + handleSnapshot(snap) + } + store.requestFileDownload(fileId: video.videoFileId, priority: 2) + } + + private func onDisappear() { + player?.pause() + player = nil + if let obs = endObserver { + NotificationCenter.default.removeObserver(obs) + endObserver = nil + } + statusObserver?.cancel() + statusObserver = nil + try? AVAudioSession.sharedInstance().setActive(false) + store.cancelFileDownload(fileId: video.videoFileId) + } + + /// watchOS routes AVPlayer audio to the speaker only under an active `.playback` + /// session. Without this the video renders but is silent — the voice-note path is + /// audible only because `AVAudioEngine.start()` activates the session implicitly. + private func activatePlaybackSession() { + let session = AVAudioSession.sharedInstance() + do { + try session.setCategory(.playback, mode: .moviePlayback) + try session.setActive(true) + } catch { + logger.error("audio session activation failed: \(error.localizedDescription, privacy: .public)") + } + } + + private func handleSnapshot(_ snap: File?) { + guard let snap else { return } + if snap.local.isDownloadingCompleted, !snap.local.path.isEmpty { + // Already in .playing? Don't reinit. + if case .playing = phase { return } + setupPlayer(path: snap.local.path) + return + } + let denom = max(1, snap.expectedSize) + let progress = Double(snap.local.downloadedSize) / Double(denom) + phase = .preparing(progress: min(max(progress, 0), 1)) + } + + private func setupPlayer(path: String) { + let url = URL(fileURLWithPath: path) + let p = AVPlayer(url: url) + if let obs = endObserver { + NotificationCenter.default.removeObserver(obs) + } + endObserver = NotificationCenter.default.addObserver( + forName: .AVPlayerItemDidPlayToEndTime, + object: p.currentItem, + queue: .main + ) { _ in + hasReachedEnd = true + } + statusObserver?.cancel() + statusObserver = p.currentItem?.publisher(for: \.status) + .receive(on: DispatchQueue.main) + .sink { status in + if status == .failed { + phase = .failed("Could not play video") + } else if status == .readyToPlay { + let audioTracks = p.currentItem?.tracks.filter { $0.assetTrack?.mediaType == .audio } ?? [] + logger.info("ready: audioTracks=\(audioTracks.count, privacy: .public) sessionCategory=\(AVAudioSession.sharedInstance().category.rawValue, privacy: .public)") + } + } + activatePlaybackSession() + player = p + phase = .playing + p.play() + logger.info("playback started fileId=\(video.videoFileId, privacy: .public)") + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VideoVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoVisual.swift new file mode 100644 index 0000000000..85b6d58cbf --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VideoVisual.swift @@ -0,0 +1,120 @@ +import Foundation +import TDShim + +/// Per-video data the bubble + viewer consume. The projection produces it from +/// `MessageVideo` + the store's latest `files[fileId]` snapshots for both the chosen +/// quality and the preview thumbnail. +/// +/// Identifiable by `videoFileId` so it works with `.sheet(item:)` for the player. +struct VideoVisual: Identifiable, Equatable, Hashable { + /// File id of the chosen quality (the file the viewer downloads on play). + var id: Int { videoFileId } + let videoFileId: Int + /// Pixel width of the chosen quality (used for player aspect-fit). + let width: Int + /// Pixel height of the chosen quality. + let height: Int + /// Duration in seconds; rendered as the duration pill on the bubble. + let duration: Int + /// MIME type of the chosen file (e.g. "video/mp4"). Rarely needed by AVPlayer. + let mimeType: String + /// Bubble preview thumbnail data + lifecycle handles. + let preview: VideoPreview + /// Filesystem path of the chosen video file when downloaded; nil otherwise. + let videoLocalPath: String? +} + +/// Bubble preview thumbnail. Sourced from (in order) `MessageVideo.cover`, +/// `Video.thumbnail`, then `Video.minithumbnail`. The projection produces this +/// statically; `previewLocalPath` is filled in once `previewFileId` finishes +/// downloading via `updateFile`. +struct VideoPreview: Equatable, Hashable { + /// File id of the preview surface (cover photo or video.thumbnail file). + /// Nil when only `minithumbnail` is available. + let previewFileId: Int? + /// Pixel dimensions of the preview surface (for aspect-ratio math in the bubble). + let previewWidth: Int + let previewHeight: Int + /// Tiny inline JPEG carried inside the message; instantly available, blurry. + let minithumbnail: Data? + /// Filesystem path; non-nil iff the preview file id is downloaded. + let previewLocalPath: String? +} + +/// Picks one video file to download and play on a watch screen. +/// +/// 1. If `alternatives` is non-empty, pick the alternative with the smallest `width`. +/// Tie-break by smallest `height`. Always returns the lowest-resolution variant +/// available, since the watch screen is 208pt wide and HD is wasted bytes. +/// 2. Else return the primary `video`. +func selectVideoQuality(primary: Video, alternatives: [AlternativeVideo]) -> (file: File, width: Int, height: Int, mimeType: String) { + if let chosen = alternatives.min(by: { lhs, rhs in + if lhs.width != rhs.width { return lhs.width < rhs.width } + return lhs.height < rhs.height + }) { + // AlternativeVideo's mimeType isn't directly modeled — TDLib transcodes to MP4. + return (file: chosen.video, width: chosen.width, height: chosen.height, mimeType: "video/mp4") + } + return (file: primary.video, width: primary.width, height: primary.height, mimeType: primary.mimeType) +} + +/// Picks one `PhotoSize` from a `MessageVideo.cover` for display on a watchOS screen. +/// Biased larger than `selectPhotoSize` because the cover is shown both in the 180pt +/// bubble AND blurred behind the full-screen video viewer's downloading state. +/// +/// 1. Prefer `type == "x"` (Telegram's ~800px-wide large variant). +/// 2. Else `type == "m"` (~320px) — keeps backward-compat with senders that only ship medium. +/// 3. Else the largest size with `width <= 1024`. +/// 4. Else the smallest size available. +/// 5. Returns `nil` only on an empty input. +func selectVideoCoverSize(_ sizes: [PhotoSize]) -> PhotoSize? { + if let x = sizes.first(where: { $0.type == "x" }) { return x } + if let m = sizes.first(where: { $0.type == "m" }) { return m } + let underBound = sizes.filter { $0.width <= 1024 } + if let largest = underBound.sorted(by: { $0.width > $1.width }).first { return largest } + return sizes.sorted(by: { $0.width < $1.width }).first +} + +/// Picks the bubble preview thumbnail strategy. Cover (multi-size, reuse +/// `selectVideoCoverSize`) wins; then `Video.thumbnail` (single-file JPEG/MPEG4); else +/// only the inline `minithumbnail` is available. +func selectVideoPreview(_ message: MessageVideo) -> VideoPreview { + if let cover = message.cover, let size = selectVideoCoverSize(cover.sizes) { + return VideoPreview( + previewFileId: size.photo.id, + previewWidth: size.width, + previewHeight: size.height, + minithumbnail: cover.minithumbnail?.data, + previewLocalPath: nil + ) + } + if let thumb = message.video.thumbnail { + return VideoPreview( + previewFileId: thumb.file.id, + previewWidth: thumb.width, + previewHeight: thumb.height, + minithumbnail: message.video.minithumbnail?.data, + previewLocalPath: nil + ) + } + return VideoPreview( + previewFileId: nil, + previewWidth: message.video.width, + previewHeight: message.video.height, + minithumbnail: message.video.minithumbnail?.data, + previewLocalPath: nil + ) +} + +/// Renders a duration label for the bubble's overlay pill. `M:SS` under an hour; +/// `H:MM:SS` at or above one hour. Always pads seconds (and minutes when in HH:MM:SS). +func formatDuration(_ seconds: Int) -> String { + let s = max(0, seconds) + let h = s / 3600 + let m = (s % 3600) / 60 + let sec = s % 60 + if h > 0 { + return String(format: "%d:%02d:%02d", h, m, sec) + } + return String(format: "%d:%02d", m, sec) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceEditorWaveform.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceEditorWaveform.swift new file mode 100644 index 0000000000..f7fe096e62 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceEditorWaveform.swift @@ -0,0 +1,84 @@ +import SwiftUI + +/// Voice-memo-style editor waveform: a faint full-width baseline + top tick ruler, white +/// amplitude bars drawn at a fixed pitch from the left, and a blue vertical scrubber (line + +/// end dots) anchored to the right edge of the bars. Pure — driven entirely by inputs. +/// +/// `progress == nil` is the recording state (scrubber rides the live edge of the accumulating +/// bars). A non-nil `progress` (0...1) is the review state: the scrubber rests at the bars' end +/// and tracks playback once it starts. +struct VoiceEditorWaveform: View { + let samples: [Float] + let progress: Double? + + private let barWidth: CGFloat = 2 + private let barSpacing: CGFloat = 1 + private let tickSpacing: CGFloat = 14 + + var body: some View { + Canvas { ctx, size in + let pitch = barWidth + barSpacing + let capacity = max(1, Int(size.width / pitch)) + let shown = samples.count > capacity ? Array(samples.suffix(capacity)) : samples + let midY = size.height / 2 + + var base = Path() + base.move(to: CGPoint(x: 0, y: midY)); base.addLine(to: CGPoint(x: size.width, y: midY)) + ctx.stroke(base, with: .color(.white.opacity(0.4)), lineWidth: 1) + + var ticks = Path() + var tx: CGFloat = 0 + while tx <= size.width { + ticks.move(to: CGPoint(x: tx, y: 0)); ticks.addLine(to: CGPoint(x: tx, y: 6)); tx += tickSpacing + } + ctx.stroke(ticks, with: .color(.white.opacity(0.4)), lineWidth: 1) + + for (i, amp) in shown.enumerated() { + let h = max(2, CGFloat(min(1, max(0, amp))) * size.height) + let bx = CGFloat(i) * pitch + ctx.fill(Path(roundedRect: CGRect(x: bx, y: midY - h / 2, width: barWidth, height: h), + cornerRadius: barWidth / 2), with: .color(.white)) + } + + let endX = min(size.width, CGFloat(shown.count) * pitch) + let scrubX: CGFloat + if let p = progress { + scrubX = (p > 0 ? CGFloat(min(1, p)) : 1) * endX + } else { + scrubX = endX + } + var line = Path() + line.move(to: CGPoint(x: scrubX, y: 0)); line.addLine(to: CGPoint(x: scrubX, y: size.height)) + ctx.stroke(line, with: .color(.accentColor), lineWidth: 2) + let r: CGFloat = 3 + ctx.fill(Path(ellipseIn: CGRect(x: scrubX - r, y: 0, width: 2 * r, height: 2 * r)), with: .color(.accentColor)) + ctx.fill(Path(ellipseIn: CGRect(x: scrubX - r, y: size.height - 2 * r, width: 2 * r, height: 2 * r)), with: .color(.accentColor)) + } + .frame(height: 70) + .accessibilityHidden(true) + } +} + +#if DEBUG +#Preview("Recording") { + VoiceEditorWaveform( + samples: (0..<30).map { Float(0.3 + 0.6 * abs(sin(Double($0) * 0.4))) }, + progress: nil + ) + .frame(width: 180) + .padding() + .background(Color.black) + .environment(\.colorScheme, .dark) +} + +#Preview("Review — mid playback") { + VoiceEditorWaveform( + samples: (0..<30).map { Float(0.2 + 0.7 * abs(cos(Double($0) * 0.3))) }, + progress: 0.4 + ) + .frame(width: 180) + .padding() + .background(Color.black) + .environment(\.colorScheme, .dark) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteBubbleView.swift new file mode 100644 index 0000000000..8b81509e57 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteBubbleView.swift @@ -0,0 +1,209 @@ +import SwiftUI + +/// Renders one voice-note bubble: rounded chrome (gray incoming / accent +/// outgoing) containing a play/pause glyph + 32-bar waveform + duration +/// label. Optional reply header sits inside the chrome above the row. +/// Caption (when non-empty) renders below the waveform inside the chrome. +/// +/// Drives priority-1 viewport download via `.onScrollVisibilityChange`. +/// Tap goes through `ChatHistoryStore.togglePlayback(_:)`; the store kicks +/// a priority-2 download if the file isn't ready and routes the toggle to +/// the active `VoicePlaybackController`. +struct VoiceNoteBubbleView: View { + let note: VoiceNoteVisual + let caption: String + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + private var amplitudes: [Float] { unpackWaveform(note.waveform) } + private var glyph: VoiceGlyph { store.voicePlayback.glyph(for: note.voiceFileId) } + private var progress: Double { store.voicePlayback.progress(for: note.voiceFileId) } + private var preparingProgress: Int? { + if case .preparing(let id, let p) = store.voicePlayback.state, id == note.voiceFileId { + return Int(p * 100) + } + return nil + } + + var body: some View { + chrome + .onScrollVisibilityChange(threshold: 0.01) { visible in + if visible { + store.requestFileDownload(fileId: note.voiceFileId) + } else { + store.cancelFileDownload(fileId: note.voiceFileId) + } + } + } + + private var chrome: some View { + VStack(alignment: .leading, spacing: 4) { + if let header = replyHeader { + ReplyHeaderView(header: header, style: BubbleStyle.resolve(isOutgoing: isOutgoing)) + } + HStack(spacing: 6) { + glyphView + WaveformBarsView(amplitudes: amplitudes, progress: progress, isOutgoing: isOutgoing) + trailing + } + if !caption.isEmpty { + Text(caption) + .font(.caption) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + .frame(minWidth: BubbleShape.minSize, maxWidth: metrics.bubbleMaxWidth, minHeight: BubbleShape.minSize, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + .contentShape(Rectangle()) + .onTapGesture { store.togglePlayback(note) } + } + + @ViewBuilder + private var glyphView: some View { + // Fixed 32pt slot keeps the waveform width stable across glyph states. + switch glyph { + case .error: + // Edge state — keep the red failure signal, no circle. + Image(systemName: "exclamationmark.circle.fill") + .font(.system(size: 18)).foregroundStyle(.red) + .frame(width: 32, height: 32) + case .play, .pause, .spinner: + ZStack { + Circle().fill(style.playFill) + Group { + switch glyph { + case .play: Image(systemName: "play.fill").font(.system(size: 13)) + case .pause: Image(systemName: "pause.fill").font(.system(size: 13)) + default: ProgressView().controlSize(.mini).scaleEffect(0.7).tint(style.playIcon) + } + } + .foregroundStyle(style.playIcon) + } + .frame(width: 32, height: 32) + } + } + + @ViewBuilder + private var trailing: some View { + VStack(alignment: .trailing, spacing: 1) { + if let pct = preparingProgress { + Text("\(pct)%") + .font(.system(size: 8)) + .foregroundStyle(style.secondary) + } else { + Text(formatDuration(note.duration)) + .font(.system(size: 9)) + .foregroundStyle(style.secondary) + } + } + .fixedSize(horizontal: true, vertical: false) + } + +} + +#if DEBUG +import TDShim + +private struct VoiceNotePreviewNoopLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { throw CancellationError() } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { throw CancellationError() } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func previewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, unreadCount: 0, lastMessageId: nil, + loader: VoiceNotePreviewNoopLoader() + ) +} + +private func previewVoice(id: Int = 1) -> VoiceNoteVisual { + VoiceNoteVisual( + voiceFileId: id, duration: 18, mimeType: "audio/ogg", + waveform: Data((0..<63).map { _ in UInt8.random(in: 0...255) }), + caption: "", localPath: nil + ) +} + +#Preview("Voice — incoming, idle") { + VoiceNoteBubbleView( + note: previewVoice(), caption: "", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Voice — outgoing, idle") { + VoiceNoteBubbleView( + note: previewVoice(id: 2), caption: "", + isOutgoing: true, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Voice — incoming with reply") { + VoiceNoteBubbleView( + note: previewVoice(id: 3), caption: "", + isOutgoing: false, + replyHeader: ReplyHeader( + senderName: "Bob", + snippet: "anchor message earlier in the chat", + minithumbnail: nil, isOutgoing: false + ) + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Voice — incoming with caption") { + VoiceNoteBubbleView( + note: previewVoice(id: 4), + caption: "Listen to this part carefully", + isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Voice — incoming, empty waveform") { + VoiceNoteBubbleView( + note: VoiceNoteVisual( + voiceFileId: 5, duration: 5, mimeType: "audio/ogg", + waveform: Data(), caption: "", localPath: nil + ), + caption: "", isOutgoing: false, + replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteVisual.swift new file mode 100644 index 0000000000..567a9b701c --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceNoteVisual.swift @@ -0,0 +1,36 @@ +import Foundation + +/// Per-voice-note data the bubble + playback controller consume. Built from +/// a `MessageVoiceNote` plus the latest `files[fileId]` snapshot for the voice +/// file. Identifiable by `voiceFileId`. +struct VoiceNoteVisual: Identifiable, Equatable, Hashable { + var id: Int { voiceFileId } + let voiceFileId: Int + /// Seconds, from `VoiceNote.duration` (sender-defined; may be approximate). + let duration: Int + /// `VoiceNote.mimeType`; typically `audio/ogg`. + let mimeType: String + /// Telegram's 5-bit packed waveform; may be empty. + let waveform: Data + /// Caption text (formatting ignored, matching photo/video bubbles). + let caption: String + /// Filesystem path of the voice file once downloaded; nil otherwise. + let localPath: String? +} + +extension VoiceNoteVisual { + /// Builds a visual for a locally-recorded draft (already on disk) for the + /// record-sheet review screen. `voiceFileId` is a negative sentinel so it + /// never collides with a real TDLib file id, and `localPath` points straight + /// at the temp file so playback skips any download path. + static func draft(fileURL: URL, duration: Int, waveform: Data) -> VoiceNoteVisual { + VoiceNoteVisual( + voiceFileId: -1, + duration: duration, + mimeType: "audio/ogg", + waveform: waveform, + caption: "", + localPath: fileURL.path + ) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoicePlaybackController.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoicePlaybackController.swift new file mode 100644 index 0000000000..f7aded0587 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoicePlaybackController.swift @@ -0,0 +1,290 @@ +import AVFoundation +import Foundation +import OpusKit + +@MainActor +protocol VoicePlaybackBackend: AnyObject { + func prepare() throws + func play(buffer: AVAudioPCMBuffer, completion: @escaping @MainActor () -> Void) throws + func pause() + func resume() + func stop() + var elapsed: Double { get } + var lastBufferDuration: Double { get } +} + +protocol VoiceDecoder: Sendable { + func decodePCM(url: URL) async throws -> DecodedOpus +} + +enum VoiceGlyph: Equatable { + case play + case pause + case spinner + case error +} + +@Observable @MainActor +final class VoicePlaybackController { + + enum State: Equatable { + case idle + case preparing(voiceFileId: Int, progress: Double) + case playing(voiceFileId: Int) + case paused(voiceFileId: Int) + case failed(voiceFileId: Int, message: String) + } + + private(set) var state: State = .idle + /// Observable progress tick that drives the bubble's waveform fill during + /// playback. Updated by `startTicker(tok:)` while in `.playing`. + private(set) var currentProgress: Double = 0 + + private let backend: VoicePlaybackBackend + private let decoder: VoiceDecoder + + /// Identifies which decode/playback any pending completion belongs to. + private var activeTok: Int = 0 + private var tickerTask: Task? = nil + private var decodeTask: Task? = nil + + init(backend: VoicePlaybackBackend, decoder: VoiceDecoder) { + self.backend = backend + self.decoder = decoder + } + + func isActive(_ voiceFileId: Int) -> Bool { + switch state { + case .preparing(let id, _), .playing(let id), .paused(let id), .failed(let id, _): + return id == voiceFileId + case .idle: + return false + } + } + + func glyph(for voiceFileId: Int) -> VoiceGlyph { + switch state { + case .preparing(let id, _) where id == voiceFileId: return .spinner + case .playing(let id) where id == voiceFileId: return .pause + case .paused(let id) where id == voiceFileId: return .play + case .failed(let id, _) where id == voiceFileId: return .error + default: return .play + } + } + + func progress(for voiceFileId: Int) -> Double { + guard isActive(voiceFileId) else { return 0 } + return currentProgress + } + + func tearDown() { + activeTok &+= 1 + tickerTask?.cancel() + tickerTask = nil + decodeTask?.cancel() + decodeTask = nil + currentProgress = 0 + backend.stop() + (backend as? AVEngineBackend)?.dropEngine() + state = .idle + } + + /// Entry point the bubble calls. If `note.localPath` is nil the controller + /// transitions to `.preparing` and waits for the caller to call + /// `resumeIfReady(note:)` once a path arrives. + func toggle(note: VoiceNoteVisual) { + switch state { + case .playing(let id) where id == note.voiceFileId: + backend.pause() + tickerTask?.cancel() + tickerTask = nil + state = .paused(voiceFileId: id) + return + case .paused(let id) where id == note.voiceFileId: + backend.resume() + state = .playing(voiceFileId: id) + startTicker(tok: activeTok) + return + case .preparing(let id, _) where id == note.voiceFileId: + // Second tap during preparing = cancel. + tearDown() + return + default: + // Switch active (or fresh start). + tearDown() + } + guard let path = note.localPath else { + state = .preparing(voiceFileId: note.voiceFileId, progress: 0) + return + } + startPlayback(note: note, path: path) + } + + /// Called by ChatHistoryStore when a fileSnapshot update lands for a voice + /// that's currently in `.preparing`. Advances to `.playing` if the path is + /// now available. + func resumeIfReady(note: VoiceNoteVisual) { + guard case .preparing(let id, _) = state, id == note.voiceFileId else { return } + guard let path = note.localPath else { return } + startPlayback(note: note, path: path) + } + + /// Called by ChatHistoryStore with download progress (0…1) while preparing. + func updateDownloadProgress(voiceFileId: Int, progress: Double) { + guard case .preparing(let id, _) = state, id == voiceFileId else { return } + state = .preparing(voiceFileId: id, progress: progress) + } + + // MARK: - Private + + private func startPlayback(note: VoiceNoteVisual, path: String) { + activeTok &+= 1 + let tok = activeTok + state = .preparing(voiceFileId: note.voiceFileId, progress: 1) + + let url = URL(fileURLWithPath: path) + let decoder = self.decoder + decodeTask?.cancel() + decodeTask = Task { [weak self] in + do { + let decoded = try await decoder.decodePCM(url: url) + self?.handleDecoded(tok: tok, note: note, decoded: decoded) + } catch { + self?.handleDecodeError(tok: tok, note: note, error: error) + } + } + } + + private func handleDecoded(tok: Int, note: VoiceNoteVisual, decoded: DecodedOpus) { + guard tok == activeTok else { return } + do { + try backend.prepare() + try backend.play(buffer: decoded.pcm) { [weak self] in + self?.handlePlaybackEnded(tok: tok, voiceFileId: note.voiceFileId) + } + state = .playing(voiceFileId: note.voiceFileId) + startTicker(tok: tok) + } catch { + state = .failed(voiceFileId: note.voiceFileId, message: "Audio unavailable") + } + } + + private func handleDecodeError(tok: Int, note: VoiceNoteVisual, error: Error) { + guard tok == activeTok else { return } + state = .failed(voiceFileId: note.voiceFileId, message: "Could not play voice note") + } + + private func handlePlaybackEnded(tok: Int, voiceFileId: Int) { + guard tok == activeTok else { return } + tickerTask?.cancel() + tickerTask = nil + currentProgress = 0 + backend.stop() + state = .idle + } + + /// Drives `currentProgress` updates at 20 Hz so the bubble's waveform fill + /// is observable. Stops when the active token changes or state leaves + /// `.playing`. + private func startTicker(tok: Int) { + tickerTask?.cancel() + tickerTask = Task { @MainActor [weak self] in + while !Task.isCancelled { + guard let self else { return } + guard tok == self.activeTok else { return } + guard case .playing = self.state else { return } + let total = self.backend.lastBufferDuration + if total > 0 { + self.currentProgress = min(1, max(0, self.backend.elapsed / total)) + } + try? await Task.sleep(nanoseconds: 50_000_000) // 50 ms + } + } + } +} + +// MARK: - Production wiring + +@MainActor +final class AVEngineBackend: VoicePlaybackBackend { + + private let engine = AVAudioEngine() + private let player = AVAudioPlayerNode() + private var attached = false + private var connectedFormat: AVAudioFormat? + private(set) var lastBufferDuration: Double = 0 + private var lastSampleRate: Double = 48000 + private var pendingCompletion: (@MainActor () -> Void)? + + var elapsed: Double { + guard let nodeTime = player.lastRenderTime, + let playerTime = player.playerTime(forNodeTime: nodeTime) else { + return 0 + } + return Double(playerTime.sampleTime) / lastSampleRate + } + + func prepare() throws { + if !attached { + engine.attach(player) + attached = true + } + } + + func play(buffer: AVAudioPCMBuffer, completion: @escaping @MainActor () -> Void) throws { + // AVAudioPlayerNode rejects buffers whose format differs from the player's + // output connection format. Telegram voice notes are typically mono; the + // smoke-test fixture is stereo. Reconnect on every format change so both + // sources play back without raising NSInternalInconsistencyException. + let bufFmt = buffer.format + if connectedFormat == nil || connectedFormat?.isEqual(bufFmt) == false { + if connectedFormat != nil { + engine.disconnectNodeOutput(player) + } + engine.connect(player, to: engine.mainMixerNode, format: bufFmt) + connectedFormat = bufFmt + } + if !engine.isRunning { + try engine.start() + } + lastSampleRate = bufFmt.sampleRate + lastBufferDuration = Double(buffer.frameLength) / lastSampleRate + pendingCompletion = completion + let captured = completion + player.scheduleBuffer(buffer, at: nil, options: []) { [weak self] in + // AVAudioEngine fires this on a non-main queue. + Task { @MainActor [weak self] in + guard let self else { return } + guard self.pendingCompletion != nil else { return } + self.pendingCompletion = nil + captured() + } + } + player.play() + } + + func pause() { player.pause() } + func resume() { player.play() } + + func stop() { + pendingCompletion = nil + player.stop() + // Leave engine running between toggles within the same chat — saves a + // hardware re-prepare. Stopped on tearDown via dropEngine(). + } + + /// Stops the engine entirely. Called from VoicePlaybackController.tearDown + /// via the controller's stop path; safe to call repeatedly. + func dropEngine() { + player.stop() + if engine.isRunning { engine.stop() } + } +} + +struct OpusDecoderAdapter: VoiceDecoder { + func decodePCM(url: URL) async throws -> DecodedOpus { + try await Task.detached(priority: .userInitiated) { + try OpusKit.OpusDecoder.decodePCM(url: url) + }.value + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecordView.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecordView.swift new file mode 100644 index 0000000000..ca19b2ca7f --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecordView.swift @@ -0,0 +1,178 @@ +import OpusKit +import SwiftUI + +/// Pushable voice-message recorder. Pushed inside AttachmentSheet's +/// NavigationStack. On appear it tears down the chat's playback via `onPrepare` +/// so a playing bubble stops before recording. A successful send calls +/// `onComplete` to collapse the whole attachment sheet; Cancel/back pops one +/// level to the menu (the pushed-view `dismiss`). +struct VoiceRecordView: View { + /// Returns true if the voice note was accepted by the chat store. + let onSend: (VoiceRecordingDraft) async -> Bool + /// Stops the chat's voice/audio playback before recording starts. + let onPrepare: () -> Void + /// Collapses the whole attachment sheet after a successful send. + let onComplete: () -> Void + + @State private var recorder: VoiceRecorder + @State private var playback: VoicePlaybackController + @State private var isSending = false + @State private var sendError: String? + @State private var levels: [Float] = [] + @Environment(\.dismiss) private var dismiss + + init( + onSend: @escaping (VoiceRecordingDraft) async -> Bool, + onPrepare: @escaping () -> Void, + onComplete: @escaping () -> Void + ) { + self.onSend = onSend + self.onPrepare = onPrepare + self.onComplete = onComplete + _recorder = State(wrappedValue: VoiceRecorder(backend: AVRecordingBackend())) + _playback = State(wrappedValue: VoicePlaybackController( + backend: AVEngineBackend(), decoder: OpusDecoderAdapter())) + } + + var body: some View { + ScrollView { + VStack(spacing: 12) { + switch recorder.state { + case .idle: idle + case .requestingPermission: ProgressView() + case .recording(let elapsed, let lvl): recording(elapsed: elapsed, level: lvl) + case .stopping: ProgressView("Finishing…") + case .review(let draft): review(draft) + case .failed(let reason): failed(reason) + } + } + .padding() + } + .navigationTitle(isRecordingOrReview ? "" : "Voice Message") + .navigationBarBackButtonHidden(isRecordingOrReview) + .toolbar { toolbarContent } + .onAppear { onPrepare() } + .onDisappear { + recorder.tearDown() + playback.tearDown() + } + } + + private var isRecordingOrReview: Bool { + switch recorder.state { + case .recording, .review: return true + default: return false + } + } + + @ToolbarContentBuilder + private var toolbarContent: some ToolbarContent { + switch recorder.state { + case .recording(let elapsed, _): + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { recorder.discard(); dismiss() } + } + ToolbarItem(placement: .confirmationAction) { + Text(formatDuration(Int(elapsed))).font(.caption).monospacedDigit() + } + case .review(let draft): + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { playback.tearDown(); recorder.discard(); dismiss() } + } + ToolbarItem(placement: .confirmationAction) { + if isSending { ProgressView() } else { Button("Send") { send(draft) } } + } + default: + ToolbarItem(placement: .cancellationAction) { EmptyView() } + } + } + + private func transportRing(@ViewBuilder _ inner: () -> Inner) -> some View { + ZStack { + Circle().stroke(.white, lineWidth: 3).frame(width: 44, height: 44) + inner() + } + } + + // MARK: - States + + private var idle: some View { + Button { levels = []; Task { await recorder.start() } } label: { + VStack(spacing: 8) { + Image(systemName: "mic.circle.fill").font(.system(size: 48)) + Text("Tap to record").font(.caption).foregroundStyle(.secondary) + } + } + .buttonStyle(.plain) + .accessibilityIdentifier("recordStart") + } + + private func recording(elapsed: TimeInterval, level: Float) -> some View { + VStack(spacing: 16) { + VoiceEditorWaveform(samples: levels, progress: nil) + Button { Task { await recorder.stop() } } label: { + transportRing { RoundedRectangle(cornerRadius: 4).fill(.red).frame(width: 18, height: 18) } + } + .buttonStyle(.plain) + .accessibilityIdentifier("recordStop") + } + .onChange(of: elapsed) { _, _ in + levels.append(level) + if levels.count > 96 { levels.removeFirst(levels.count - 96) } + } + } + + private func review(_ draft: VoiceRecordingDraft) -> some View { + let note = VoiceNoteVisual.draft(fileURL: draft.fileURL, duration: draft.duration, waveform: draft.waveform) + return VStack(spacing: 16) { + VoiceEditorWaveform( + samples: unpackWaveform(draft.waveform), + progress: playback.progress(for: note.voiceFileId) + ) + if let sendError { + Text(sendError).font(.caption2).foregroundStyle(.red) + } + Button { playback.toggle(note: note) } label: { + transportRing { reviewGlyph(playback.glyph(for: note.voiceFileId)) } + } + .buttonStyle(.plain) + .accessibilityIdentifier("recordReviewPlay") + } + } + + @ViewBuilder + private func reviewGlyph(_ glyph: VoiceGlyph) -> some View { + switch glyph { + case .play: Image(systemName: "play.fill").font(.system(size: 18)).foregroundStyle(.white) + case .pause: Image(systemName: "pause.fill").font(.system(size: 18)).foregroundStyle(.white) + case .spinner: ProgressView().controlSize(.small) + case .error: Image(systemName: "exclamationmark.circle.fill").font(.system(size: 18)).foregroundStyle(.red) + } + } + + private func failed(_ reason: String) -> some View { + VStack(spacing: 10) { + Text(reason).font(.caption).foregroundStyle(.red).multilineTextAlignment(.center) + Button("Try again") { recorder.discard() }.buttonStyle(.bordered) + Button("Cancel") { dismiss() }.buttonStyle(.bordered).tint(.red) + } + } + + // MARK: - Send + + private func send(_ draft: VoiceRecordingDraft) { + guard !isSending else { return } + isSending = true + sendError = nil + Task { + let ok = await onSend(draft) + if ok { + _ = recorder.consumeDraft() // release file ownership only after success + onComplete() + } else { + sendError = "Couldn't send. Try again." + isSending = false + } + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecorder.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecorder.swift new file mode 100644 index 0000000000..f6fd477915 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/VoiceRecorder.swift @@ -0,0 +1,153 @@ +import Foundation + +/// A finished local recording, ready to send. The file is owned by `VoiceRecorder` +/// until `consumeDraft()` transfers ownership to the caller. +struct VoiceRecordingDraft: Equatable { + let fileURL: URL + let duration: Int // seconds + let waveform: Data // 20 bytes, 32 × 5-bit +} + +enum RecordPermission { case granted, denied, undetermined } + +/// Capture backend. Production uses `AVRecordingBackend`; tests use a fake. +@MainActor +protocol RecordingBackend: AnyObject { + func ensurePermission() async -> RecordPermission + /// Begins capture. `onLevel` fires (~10 Hz) with normalized RMS in [0, 1]. + func begin(onLevel: @escaping @MainActor (Float) -> Void) throws + /// Stops capture, finalizes the file, returns the finished draft. + func finish() async throws -> VoiceRecordingDraft + /// Aborts capture and deletes any partial file. + func abort() +} + +enum RecordState: Equatable { + case idle + case requestingPermission + case recording(elapsed: TimeInterval, level: Float) + case stopping + case review(VoiceRecordingDraft) + case failed(String) +} + +@Observable @MainActor +final class VoiceRecorder { + private(set) var state: RecordState = .idle + + private let backend: RecordingBackend + private let maxDuration: TimeInterval + private var startDate: Date? + private var lastLevel: Float = 0 + private var ownedFileURL: URL? + private var ticker: Task? + // Invalidates an in-flight `completeStop` if `discard`/`tearDown` pre-empt it + // while `backend.finish()` is still awaiting — otherwise the late completion + // would resurrect `.review` with a file the teardown already deleted. + private var stopTok = 0 + + init(backend: RecordingBackend, maxDuration: TimeInterval = 300) { + self.backend = backend + self.maxDuration = maxDuration + } + + func start() async { + guard case .idle = state else { return } + state = .requestingPermission + switch await backend.ensurePermission() { + case .granted: + break + case .denied, .undetermined: + state = .failed("Microphone access denied. Enable in Settings → Privacy.") + return + } + do { + try backend.begin { [weak self] level in self?.lastLevel = level } + startDate = Date() + state = .recording(elapsed: 0, level: 0) + startTicker() + } catch { + state = .failed("Couldn't start microphone") + } + } + + func stop() async { + guard case .recording = state else { return } + let tok = beginStopping() + await completeStop(tok: tok) + } + + func discard() { + ticker?.cancel(); ticker = nil + stopTok &+= 1 // invalidate any in-flight completeStop + backend.abort() + deleteOwnedFile() + startDate = nil + state = .idle + } + + /// Releases file ownership to the caller WITHOUT deleting it. Use on a + /// successful send so TDLib can keep reading the file during upload. + func consumeDraft() -> VoiceRecordingDraft? { + guard case .review(let draft) = state else { return nil } + ownedFileURL = nil // ownership transferred — tearDown won't delete it + state = .idle + return draft + } + + func tearDown() { + ticker?.cancel(); ticker = nil + stopTok &+= 1 // invalidate any in-flight completeStop + backend.abort() + deleteOwnedFile() + startDate = nil + state = .idle + } + + /// Internal — driven by the production ticker; called directly by tests. + func tick(elapsed: TimeInterval) { + guard case .recording = state else { return } + if elapsed >= maxDuration { + let tok = beginStopping() + Task { await completeStop(tok: tok) } + return + } + state = .recording(elapsed: elapsed, level: lastLevel) + } + + // MARK: - Private + + private func beginStopping() -> Int { + ticker?.cancel(); ticker = nil + stopTok &+= 1 + state = .stopping + return stopTok + } + + private func completeStop(tok: Int) async { + do { + let draft = try await backend.finish() + guard tok == stopTok else { return } // pre-empted by discard/tearDown + ownedFileURL = draft.fileURL + state = .review(draft) + } catch { + guard tok == stopTok else { return } + state = .failed("Couldn't save recording") + } + } + + private func startTicker() { + ticker = Task { @MainActor [weak self] in + while !Task.isCancelled { + guard let self, let start = self.startDate else { return } + self.tick(elapsed: Date().timeIntervalSince(start)) + try? await Task.sleep(nanoseconds: 100_000_000) // 10 Hz + } + } + } + + private func deleteOwnedFile() { + if let url = ownedFileURL { try? FileManager.default.removeItem(at: url) } + ownedFileURL = nil + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Messages/WaveformBars.swift b/Telegram/WatchApp/tgwatch Watch App/Messages/WaveformBars.swift new file mode 100644 index 0000000000..82be39fd95 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Messages/WaveformBars.swift @@ -0,0 +1,138 @@ +import Foundation +import SwiftUI + +/// Unpacks Telegram's 5-bit packed waveform format into amplitudes in [0, 1]. +/// Each input byte contributes 8 bits of an LSB-first bit stream; every 5 bits +/// form one 0…31 amplitude. Trailing bits that don't form a complete sample +/// are dropped. An empty `Data` returns an empty array. +func unpackWaveform(_ data: Data) -> [Float] { + guard !data.isEmpty else { return [] } + let totalBits = data.count * 8 + let sampleCount = totalBits / 5 + guard sampleCount > 0 else { return [] } + + var out: [Float] = [] + out.reserveCapacity(sampleCount) + + for i in 0..> UInt32(bitOffset)) & 0x1F // 5-bit mask + out.append(Float(value) / 31.0) + } + return out +} + +/// 32-bar waveform rendered via `Canvas`. Bars at indices below +/// `progress * 32` use the foreground tint; the rest are dimmed. +/// When `amplitudes` is empty, draws a thin progress bar instead. +struct WaveformBarsView: View { + let amplitudes: [Float] + let progress: Double + let isOutgoing: Bool + + private let barCount = 32 + private let barSpacing: CGFloat = 1 + + private var foregroundTint: Color { isOutgoing ? .white : .accentColor } + private var dimTint: Color { isOutgoing ? .white.opacity(0.35) : .accentColor.opacity(0.3) } + + var body: some View { + GeometryReader { geo in + if amplitudes.isEmpty { + emptyProgress(in: geo.size) + } else { + Canvas { ctx, size in + let totalSpacing = barSpacing * CGFloat(barCount - 1) + let barWidth = max(1, (size.width - totalSpacing) / CGFloat(barCount)) + let midY = size.height / 2 + let downsampled = downsample(amplitudes, to: barCount) + let playedBars = Int((Double(barCount) * progress).rounded(.down)) + for i in 0.. some View { + ZStack(alignment: .leading) { + Capsule().fill(dimTint).frame(height: 2) + Capsule() + .fill(foregroundTint) + .frame(width: size.width * progress, height: 2) + } + .frame(height: 18) + } + + private func downsample(_ src: [Float], to n: Int) -> [Float] { + guard src.count != n else { return src } + if src.count < n { + return src + Array(repeating: 0, count: n - src.count) + } + var out: [Float] = [] + out.reserveCapacity(n) + let bucketSize = Double(src.count) / Double(n) + for i in 0.. Data { + guard !amplitudes.isEmpty else { return Data(count: byteCount) } + let bars = bucket(amplitudes, into: barCount) + let peak = max(bars.max() ?? 0, 0.0001) + var bytes = [UInt8](repeating: 0, count: byteCount) + for (i, amp) in bars.enumerated() { + let q = UInt32((min(1, amp / peak) * 31).rounded()) // 0…31 + let bitStart = i * 5 + let byteIndex = bitStart / 8 + let bitOffset = bitStart % 8 + var span = UInt32(bytes[byteIndex]) + if byteIndex + 1 < byteCount { span |= UInt32(bytes[byteIndex + 1]) << 8 } + if byteIndex + 2 < byteCount { span |= UInt32(bytes[byteIndex + 2]) << 16 } + span |= (q & 0x1F) << UInt32(bitOffset) + bytes[byteIndex] = UInt8(span & 0xFF) + if byteIndex + 1 < byteCount { bytes[byteIndex + 1] = UInt8((span >> 8) & 0xFF) } + if byteIndex + 2 < byteCount { bytes[byteIndex + 2] = UInt8((span >> 16) & 0xFF) } + } + return Data(bytes) + } + + private static func bucket(_ src: [Float], into n: Int) -> [Float] { + guard src.count > n else { + return src + Array(repeating: 0, count: max(0, n - src.count)) + } + var out = [Float](); out.reserveCapacity(n) + let size = Double(src.count) / Double(n) + for i in 0.. Result { + if apiId <= 0 { return .failure(.missingApiId) } + let trimmed = apiHash.trimmingCharacters(in: .whitespaces) + if trimmed.isEmpty { return .failure(.missingApiHash) } + if trimmed == "replace_with_32_hex_chars" { return .failure(.placeholderApiHash) } + return .success(()) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/LottieAnimationView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/LottieAnimationView.swift new file mode 100644 index 0000000000..e54f8e3967 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/LottieAnimationView.swift @@ -0,0 +1,103 @@ +import SwiftUI +import UIKit +import WatchKit +import RLottieKit + +/// Renders one TGS sticker. Plays once when the view first becomes visible, freezes on +/// the last frame, and replays on tap. Drops the `LottieAnimation` (and its rlottie +/// backing buffers) on disappear so off-screen stickers cost nothing. +/// +/// Pre-download / pre-load fallback chain: while the TGS hasn't been loaded yet, render +/// the sticker's raster thumbnail if available, otherwise a gray placeholder. +struct LottieAnimationView: View { + let sticker: StickerVisual + let displaySize: CGSize + + enum PlayState: Equatable { + case idle + case playing(start: Foundation.Date) + case finished + case failed + } + + @State private var animation: LottieAnimation? = nil + @State private var playState: PlayState = .idle + @State private var renderedFrame: CGImage? = nil + + private var screenScale: CGFloat { WKInterfaceDevice.current().screenScale } + + var body: some View { + TimelineView(.animation(minimumInterval: 1.0 / 30.0)) { ctx in + ZStack { + placeholderLayer + if let frame = renderedFrame { + Image(decorative: frame, scale: screenScale) + .resizable() + .scaledToFit() + } + } + .frame(width: displaySize.width, height: displaySize.height) + .contentShape(Rectangle()) + .onTapGesture { + if case .finished = playState, animation != nil { + playState = .playing(start: Foundation.Date()) + } + } + .onAppear { startIfReady() } + .onChange(of: sticker.localPath) { _, _ in startIfReady() } + .onDisappear { teardown() } + .onChange(of: ctx.date) { _, now in tick(now: now) } + } + } + + @ViewBuilder + private var placeholderLayer: some View { + if renderedFrame == nil { + if let thumbPath = sticker.thumbnailLocalPath, + let img = UIImage(contentsOfFile: thumbPath) { + Image(uiImage: img).resizable().scaledToFit() + } else { + Color.gray.opacity(0.2) + } + } + } + + private func startIfReady() { + guard animation == nil, let path = sticker.localPath else { return } + let url = URL(fileURLWithPath: path) + if let anim = LottieAnimation(tgsFileURL: url) { + animation = anim + playState = .playing(start: Foundation.Date()) + } else { + playState = .failed + } + } + + private func teardown() { + animation = nil + renderedFrame = nil + playState = .idle + } + + private func tick(now: Foundation.Date) { + guard let animation, case .playing(let start) = playState else { return } + let elapsed = now.timeIntervalSince(start) + let duration = animation.duration + if elapsed >= duration { + let last = animation.renderFrame( + index: animation.frameCount - 1, + size: displaySize, + scale: screenScale + ) + renderedFrame = last + playState = .finished + return + } + let frameIndex = min(animation.frameCount - 1, Int(elapsed * Double(animation.frameRate))) + renderedFrame = animation.renderFrame( + index: frameIndex, + size: displaySize, + scale: screenScale + ) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerBubbleView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerBubbleView.swift new file mode 100644 index 0000000000..04032a4242 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerBubbleView.swift @@ -0,0 +1,300 @@ +import SwiftUI +import TDShim + +/// One sticker bubble. No rounded-rect chat-bubble chrome — sticker renders directly +/// against the chat background, with sender name above (incoming groups only). +/// Drives the same visibility-based download flow as PhotoBubbleView. +struct StickerBubbleView: View { + let sticker: StickerVisual + let senderName: String? + var senderColorIndex: Int? = nil + let isOutgoing: Bool + let replyHeader: ReplyHeader? + + @Environment(ChatHistoryStore.self) private var store + @Environment(\.bubbleMetrics) private var metrics + + private var maxEdgePt: CGFloat { metrics.stickerMaxEdge } + private var style: BubbleStyle { .resolve(isOutgoing: isOutgoing) } + + var body: some View { + VStack(alignment: isOutgoing ? .trailing : .leading, spacing: 2) { + if let name = senderName, !isOutgoing { + Text(name) + .font(.caption2) + .foregroundStyle(senderColorIndex.map { avatarPalette[$0] } ?? .secondary) + .padding(.horizontal, 4) + } + if let header = replyHeader { + // Force incoming styling inside the gray card regardless of host outgoing — + // the card itself provides the chrome. + ReplyHeaderView(header: .init( + senderName: header.senderName, + snippet: header.snippet, + minithumbnail: header.minithumbnail, + isOutgoing: false, + senderColorIndex: header.senderColorIndex + ), style: .incoming) + .padding(6) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(BubbleStyle.incoming.fill) + ) + .frame(maxWidth: metrics.stickerReplyCardMaxWidth) + } + stickerBody + .onScrollVisibilityChange(threshold: 0.01) { visible in + handleVisibility(visible) + } + } + } + + @ViewBuilder + private var stickerBody: some View { + switch sticker.format { + case .webp: + WebPStickerView(sticker: sticker, displaySize: displaySize) + case .tgs: + LottieAnimationView(sticker: sticker, displaySize: displaySize) + case .unsupported: + unsupportedBubble + } + } + + private var unsupportedBubble: some View { + HStack(spacing: 4) { + Text("[Sticker]") + if !sticker.emoji.isEmpty { + Text(sticker.emoji) + } + } + .font(.caption) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .frame(minWidth: BubbleShape.minSize, minHeight: BubbleShape.minSize) + .background( + RoundedRectangle(cornerRadius: BubbleShape.cornerRadius) + .fill(style.fill) + ) + .foregroundStyle(style.content) + } + + /// Aspect-fits the sticker's native dims into a `maxEdgePt × maxEdgePt` box. + private var displaySize: CGSize { + guard sticker.width > 0, sticker.height > 0 else { + return CGSize(width: maxEdgePt, height: maxEdgePt) + } + let w = CGFloat(sticker.width) + let h = CGFloat(sticker.height) + let scale = min(maxEdgePt / w, maxEdgePt / h) + return CGSize(width: w * scale, height: h * scale) + } + + private func handleVisibility(_ visible: Bool) { + switch sticker.format { + case .unsupported: + return // No download for unsupported formats. + case .webp, .tgs: + if visible { + store.requestFileDownload(fileId: sticker.fileId) + if let thumbId = sticker.thumbnailFileId { + store.requestFileDownload(fileId: thumbId) + } + } else { + store.cancelFileDownload(fileId: sticker.fileId) + if let thumbId = sticker.thumbnailFileId { + store.cancelFileDownload(fileId: thumbId) + } + } + } + } +} + +#if DEBUG +// MARK: - Preview helpers + +private struct NoopChatHistoryLoader: ChatHistoryLoader { + func openChat(chatId: Int64) async throws {} + func closeChat(chatId: Int64) async throws {} + func loadHistory(chatId: Int64, fromMessageId: Int64, offset: Int, limit: Int) async throws -> [Message] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { + throw CancellationError() + } + func cancelDownloadFile(fileId: Int) async throws {} + func sendText(chatId: Int64, text: String) async throws -> Message { + throw CancellationError() + } + func sendVoiceNote(chatId: Int64, fileURL: URL, duration: Int, waveform: Data) async throws -> Message { + throw CancellationError() + } + func setChatDraftMessage(chatId: Int64, draftText: String) async throws {} + func viewMessages(chatId: Int64, messageIds: [Int64], forceRead: Bool) async throws {} + func setPollAnswer(chatId: Int64, messageId: Int64, optionIds: [Int]) async throws {} + func sendSticker(chatId: Int64, remoteFileId: String, emoji: String, width: Int, height: Int) async throws -> Message { + throw CancellationError() + } + func sendLocation(chatId: Int64, latitude: Double, longitude: Double) async throws -> Message { throw CancellationError() } +} + +@MainActor +private func previewStore() -> ChatHistoryStore { + ChatHistoryStore( + chatId: 0, + chatType: .chatTypePrivate(ChatTypePrivate(userId: 0)), + lastReadInboxMessageId: 0, + unreadCount: 0, + lastMessageId: nil, + loader: NoopChatHistoryLoader() + ) +} + +/// Marker class used to locate the host app bundle from within previews. `Bundle.main` +/// points at the test runner in SnapshotPreviews context, so we use Bundle(for:) on a +/// host-target class instead. +private final class PreviewBundleMarker {} + +private func previewSticker(format: StickerFormatKind, withFile: Bool, withThumb: Bool = false) -> StickerVisual { + let bundle = Bundle(for: PreviewBundleMarker.self) + let mainPath: String? = { + guard withFile else { return nil } + switch format { + case .webp: return bundle.path(forResource: "sticker_raster", ofType: "webp", inDirectory: "SampleStickers") + case .tgs: return bundle.path(forResource: "sticker_lottie", ofType: "tgs", inDirectory: "SampleStickers") + case .unsupported: return nil + } + }() + let thumbPath: String? = withThumb ? bundle.path(forResource: "sticker_raster", ofType: "webp", inDirectory: "SampleStickers") : nil + return StickerVisual( + fileId: 1, format: format, + width: 512, height: 512, + emoji: "✨", + localPath: mainPath, + thumbnailFileId: withThumb ? 2 : nil, + thumbnailLocalPath: thumbPath, + thumbnailFormat: withThumb ? .webp : nil + ) +} + +#Preview("WEBP — downloaded") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("WEBP — downloading, thumbnail available") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: false, withThumb: true), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("WEBP — downloading, no thumbnail") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: false), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("TGS — downloaded") { + StickerBubbleView( + sticker: previewSticker(format: .tgs, withFile: true), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("TGS — downloading, thumbnail available") { + StickerBubbleView( + sticker: previewSticker(format: .tgs, withFile: false, withThumb: true), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("WEBM — unsupported fallback") { + StickerBubbleView( + sticker: previewSticker(format: .unsupported, withFile: false), + senderName: nil, isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Group sticker — sender name above") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: "Alice", isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Group sticker — colored sender name") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: "Alice", senderColorIndex: paletteIndex(for: 200), + isOutgoing: false, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Outgoing sticker") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: nil, isOutgoing: true, replyHeader: nil + ) + .bubblePreview() + .environment(previewStore()) +} + +// MARK: - Reply-header previews + +private func previewReplyHeader(toText: String = "anchor", isOutgoing: Bool = false) -> ReplyHeader { + ReplyHeader( + senderName: "Bob", + snippet: toText, + minithumbnail: nil, + isOutgoing: isOutgoing + ) +} + +#Preview("Sticker WEBP — with reply card above") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: nil, isOutgoing: false, + replyHeader: previewReplyHeader(toText: "anchor message") + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Sticker TGS — with reply card above") { + StickerBubbleView( + sticker: previewSticker(format: .tgs, withFile: true), + senderName: nil, isOutgoing: false, + replyHeader: previewReplyHeader(toText: "look at this clip") + ) + .bubblePreview() + .environment(previewStore()) +} + +#Preview("Sticker outgoing — with reply card above") { + StickerBubbleView( + sticker: previewSticker(format: .webp, withFile: true), + senderName: nil, isOutgoing: true, + replyHeader: previewReplyHeader(toText: "thanks!", isOutgoing: true) + ) + .bubblePreview() + .environment(previewStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerCellView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerCellView.swift new file mode 100644 index 0000000000..d8219cfbe9 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerCellView.swift @@ -0,0 +1,157 @@ +import SwiftUI +import UIKit +import WatchKit +import RLottieKit +import WebPKit +import TDShim + +/// One tappable sticker tile. Downloads its render file when visible and decodes +/// it (WebPKit for webp, UIImage for jpeg/png, rlottie frame-0 for tgs) once the +/// local path lands. WEBM (`.none`) shows a neutral placeholder but stays +/// tappable — sending works by remote id regardless. +struct StickerCellView: View { + let sticker: PickerSticker + var edge: CGFloat = 72 + var onTap: ((PickerSticker) -> Void)? = nil + + @Environment(StickerPickerStore.self) private var store + @State private var image: UIImage? + + private var screenScale: CGFloat { WKInterfaceDevice.current().screenScale } + + private var renderFileId: Int? { + switch sticker.render { + case .raster(let id, _): return id + case .lottie(let id): return id + case .none: return nil + } + } + + /// Non-nil only once the render file is fully downloaded with a real path. + private var localPath: String? { + guard let id = renderFileId, + let f = store.fileSnapshot(fileId: id), + f.local.isDownloadingCompleted, + !f.local.path.isEmpty else { return nil } + return f.local.path + } + + var body: some View { + ZStack { + RoundedRectangle(cornerRadius: 8).fill(Color.gray.opacity(0.15)) + if let image { + Image(uiImage: image).resizable().scaledToFit().padding(6) + } + } + .frame(width: edge, height: edge) + .contentShape(Rectangle()) + .modifier(TapIfPresent(onTap: onTap.map { cb in { cb(sticker) } })) + .onScrollVisibilityChange(threshold: 0.01) { visible in + guard let id = renderFileId else { return } + if visible { store.requestFileDownload(fileId: id, priority: 2) } + else { store.cancelFileDownload(fileId: id) } + } + .task(id: localPath) { decode() } + } + + private func decode() { + guard image == nil, let path = localPath else { return } + let url = URL(fileURLWithPath: path) + switch sticker.render { + case .raster(_, let fmt): + switch fmt { + case .webp: + if let cg = WebPDecoder.decode(url: url) { image = UIImage(cgImage: cg) } + case .jpeg, .png: + image = UIImage(contentsOfFile: path) + case .unsupported: + break + } + case .lottie: + if let anim = LottieAnimation(tgsFileURL: url), + let cg = anim.renderFrame(index: 0, size: CGSize(width: edge, height: edge), scale: screenScale) { + image = UIImage(cgImage: cg) + } + case .none: + break + } + } +} + +/// Applies an `onTapGesture` only when a callback is provided, so a tappable +/// cell never steals taps from an enclosing NavigationLink (cover thumbnails). +private struct TapIfPresent: ViewModifier { + let onTap: (() -> Void)? + func body(content: Content) -> some View { + if let onTap { + content.onTapGesture { onTap() } + } else { + content + } + } +} + +/// Two-column titled grid of cells. Header omitted when `title` is nil +/// (set-detail uses no header). Lazy so only visible cells download/decode. +struct StickerSectionGrid: View { + let title: String? + let stickers: [PickerSticker] + let onTap: (PickerSticker) -> Void + + private let columns = [GridItem(.flexible(), spacing: 6), GridItem(.flexible(), spacing: 6)] + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + if let title { + Text(title) + .font(.caption2) + .foregroundStyle(.secondary) + .padding(.horizontal, 4) + } + LazyVGrid(columns: columns, spacing: 6) { + ForEach(stickers) { s in + StickerCellView(sticker: s, onTap: onTap) + } + } + } + } +} + +#if DEBUG +@MainActor +private func previewPickerStore() -> StickerPickerStore { + struct NoopPickerLoader: StickerPickerLoader { + func favoriteStickers() async throws -> [Sticker] { [] } + func recentStickers() async throws -> [Sticker] { [] } + func installedStickerSets() async throws -> [StickerSetInfo] { [] } + func stickerSet(id: TdInt64) async throws -> [Sticker] { [] } + func downloadFile(fileId: Int, priority: Int) async throws -> File { throw CancellationError() } + func cancelDownloadFile(fileId: Int) async throws {} + } + return StickerPickerStore(loader: NoopPickerLoader()) +} + +private func previewSticker(_ render: PickerRender) -> PickerSticker { + PickerSticker(stickerFileId: 1, format: .webp, render: render, + remoteFileId: "r", emoji: "✨", width: 512, height: 512) +} + +#Preview("Cell — placeholder (downloading)") { + StickerCellView(sticker: previewSticker(.raster(fileId: 1, format: .webp)), onTap: { _ in }) + .environment(previewPickerStore()) +} + +#Preview("Cell — webm none") { + StickerCellView(sticker: previewSticker(.none), onTap: { _ in }) + .environment(previewPickerStore()) +} + +#Preview("Section grid") { + StickerSectionGrid( + title: "FREQUENTLY USED", + stickers: (1...4).map { PickerSticker(stickerFileId: $0, format: .webp, render: .none, remoteFileId: "r\($0)", emoji: "✨", width: 512, height: 512) }, + onTap: { _ in } + ) + .environment(previewPickerStore()) +} +#endif diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPicker.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPicker.swift new file mode 100644 index 0000000000..8422b1b65a --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPicker.swift @@ -0,0 +1,83 @@ +import Foundation +import TDShim + +/// One sticker as the picker grid + send path need it. Built by +/// `pickerSticker(from:)` from a TDLibKit `Sticker`. `id` is the sticker body +/// file id so it is stable within a session and usable with `ForEach`. +struct PickerSticker: Identifiable, Equatable, Hashable { + var id: Int { stickerFileId } + + /// File id of the sticker body (WEBP/TGS/WEBM). Also the lottie/full-webp + /// render source. + let stickerFileId: Int + let format: StickerFormatKind + /// Which file to download + how to decode it for the grid tile. + let render: PickerRender + + // Send fields — sending uses the remote id, so no download is needed. + let remoteFileId: String + let emoji: String + let width: Int + let height: Int +} + +/// How to produce a static grid-tile image for a sticker. +enum PickerRender: Equatable, Hashable { + /// Decode `fileId` as a raster image (WebPKit for .webp, UIImage for jpeg/png). + case raster(fileId: Int, format: ThumbnailFormatKind) + /// Download the TGS body `fileId` and render frame 0 via RLottieKit. + case lottie(fileId: Int) + /// No previewable image (WEBM/video). Tile shows a placeholder; still sendable. + case none +} + +/// One installed sticker set as the picker list + detail need it. +struct PickerSet: Identifiable, Equatable, Hashable { + var id: Int64 { setId.rawValue } + let setId: TdInt64 + let title: String + /// First cover sticker, rendered as the row's thumbnail. Nil if the set + /// ships no covers. + let cover: PickerSticker? +} + +/// Picks the grid render path from the sticker's own format. WEBP prefers a +/// raster thumbnail (smaller), falling back to the full WEBP body when the +/// sticker has no raster thumbnail. TGS renders frame 0 from the lottie body. +/// WEBM has no watchOS-renderable preview. +func pickerRender(for sticker: Sticker) -> PickerRender { + switch stickerFormatKind(sticker.format) { + case .webp: + if let thumb = sticker.thumbnail { + let kind = thumbnailFormatKind(thumb.format) + if kind != .unsupported { + return .raster(fileId: thumb.file.id, format: kind) + } + } + return .raster(fileId: sticker.sticker.id, format: .webp) + case .tgs: + return .lottie(fileId: sticker.sticker.id) + case .unsupported: + return .none + } +} + +func pickerSticker(from sticker: Sticker) -> PickerSticker { + PickerSticker( + stickerFileId: sticker.sticker.id, + format: stickerFormatKind(sticker.format), + render: pickerRender(for: sticker), + remoteFileId: sticker.sticker.remote.id, + emoji: sticker.emoji, + width: sticker.width, + height: sticker.height + ) +} + +func pickerSet(from info: StickerSetInfo) -> PickerSet { + PickerSet( + setId: info.id, + title: info.title, + cover: info.covers.first.map(pickerSticker(from:)) + ) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerLoader.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerLoader.swift new file mode 100644 index 0000000000..bf36fcc750 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerLoader.swift @@ -0,0 +1,45 @@ +import Foundation +import TDShim + +/// Seam between `StickerPickerStore` and TDLib. Tests inject a fake; production +/// uses `TDLibStickerPickerLoader`. Send is intentionally NOT here — it goes +/// through `ChatHistoryLoader.sendSticker` so the chat store's pull-to-tail and +/// error handling apply. +protocol StickerPickerLoader: Sendable { + func favoriteStickers() async throws -> [Sticker] + func recentStickers() async throws -> [Sticker] + func installedStickerSets() async throws -> [StickerSetInfo] + func stickerSet(id: TdInt64) async throws -> [Sticker] + func downloadFile(fileId: Int, priority: Int) async throws -> File + func cancelDownloadFile(fileId: Int) async throws +} + +struct TDLibStickerPickerLoader: StickerPickerLoader { + let client: TDLibClient + + func favoriteStickers() async throws -> [Sticker] { + try await client.getFavoriteStickers().stickers + } + + func recentStickers() async throws -> [Sticker] { + try await client.getRecentStickers(isAttached: false).stickers + } + + func installedStickerSets() async throws -> [StickerSetInfo] { + try await client.getInstalledStickerSets(stickerType: .stickerTypeRegular).sets + } + + func stickerSet(id: TdInt64) async throws -> [Sticker] { + try await client.getStickerSet(setId: id).stickers + } + + func downloadFile(fileId: Int, priority: Int) async throws -> File { + // synchronous=false → TDLib returns immediately and streams progress via updateFile. + try await client.downloadFile(fileId: fileId, limit: 0, offset: 0, priority: priority, synchronous: false) + } + + func cancelDownloadFile(fileId: Int) async throws { + // onlyIfPending=false → cancel even if active. + _ = try await client.cancelDownloadFile(fileId: fileId, onlyIfPending: false) + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerStore.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerStore.swift new file mode 100644 index 0000000000..eae36e5229 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerStore.swift @@ -0,0 +1,101 @@ +import Foundation +import Observation +import OSLog +import TDShim + +enum PickerLoadState: Equatable { + case loading + case loaded + case failed(String) +} + +@Observable +@MainActor +final class StickerPickerStore { + private(set) var favorites: [PickerSticker] = [] + private(set) var recents: [PickerSticker] = [] + private(set) var sets: [PickerSet] = [] + private(set) var loadState: PickerLoadState = .loading + + private let loader: StickerPickerLoader + private let logger = Logger(subsystem: "org.telegram.TelegramWatch", category: "stickerpicker") + private var files: [Int: File] = [:] + private var trackedFileIds: Set = [] + private var setStickers: [Int64: [PickerSticker]] = [:] + + init(loader: StickerPickerLoader) { + self.loader = loader + } + + /// Consumes `updateFile` only — grid cells read `fileSnapshot` and decode + /// once the local path lands. `@Observable` re-evaluates the reading views, + /// so no reproject is needed. + func handle(_ update: Update) { + if case .updateFile(let upd) = update { + files[upd.file.id] = upd.file + } + } + + func fileSnapshot(fileId: Int) -> File? { files[fileId] } + + func requestFileDownload(fileId: Int, priority: Int = 2) { + guard !trackedFileIds.contains(fileId) else { return } + trackedFileIds.insert(fileId) + logger.info("requestFileDownload fileId=\(fileId, privacy: .public) priority=\(priority, privacy: .public)") + Task { [logger, loader] in + do { + _ = try await loader.downloadFile(fileId: fileId, priority: priority) + } catch { + logger.warning("sticker download fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + func cancelFileDownload(fileId: Int) { + trackedFileIds.remove(fileId) + logger.info("cancelFileDownload fileId=\(fileId, privacy: .public)") + Task { [logger, loader] in + do { + try await loader.cancelDownloadFile(fileId: fileId) + } catch { + logger.warning("cancelDownloadFile fileId=\(fileId, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + /// Fetches favorites, recents, and installed sets concurrently. Each source + /// is independent — one failing source still renders the others. `.failed` + /// only when all three fail. Re-runs on every sheet open (keeps recents/ + /// favorites fresh); when already `.loaded` it refreshes the arrays in place + /// rather than flashing the spinner. + func load() async { + if case .loaded = loadState {} else { loadState = .loading } + async let favsTask = loader.favoriteStickers() + async let recsTask = loader.recentStickers() + async let setsTask = loader.installedStickerSets() + let favs = try? await favsTask + let recs = try? await recsTask + let setInfos = try? await setsTask + favorites = (favs ?? []).map(pickerSticker(from:)) + recents = (recs ?? []).map(pickerSticker(from:)) + sets = (setInfos ?? []).map(pickerSet(from:)) + if favs == nil, recs == nil, setInfos == nil { + loadState = .failed("Couldn't load stickers") + } else { + loadState = .loaded + } + } + + /// Loads (and memoizes) a set's stickers for the detail grid. + func loadSet(id: TdInt64) async -> [PickerSticker] { + if let cached = setStickers[id.rawValue] { return cached } + do { + let projected = try await loader.stickerSet(id: id).map(pickerSticker(from:)) + setStickers[id.rawValue] = projected + return projected + } catch { + logger.warning("loadSet id=\(id.rawValue, privacy: .public) failed: \(error.localizedDescription, privacy: .public)") + return [] + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerView.swift new file mode 100644 index 0000000000..feab1c7df8 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerPickerView.swift @@ -0,0 +1,93 @@ +import SwiftUI + +/// Pushable sticker picker. Sections: Favorites, Frequently Used, and a Sticker +/// Sets list (each row pushes a set-detail grid onto the enclosing stack). +/// Pushed inside AttachmentSheet's NavigationStack — it has no stack of its own. +/// Tapping any sticker sends it and, on success, calls `onComplete` to collapse +/// the whole attachment sheet. Registers itself as TDClient's active picker on +/// appear so `updateFile` reaches the store; clears on disappear. +struct StickerPickerView: View { + let store: StickerPickerStore + /// Returns true on a successful send. + let onSend: (PickerSticker) async -> Bool + /// Collapses the whole attachment sheet after a successful send. + let onComplete: () -> Void + + @Environment(TDClient.self) private var client + @Environment(\.dismiss) private var dismiss + @State private var sending = false + + var body: some View { + content + .navigationTitle("") + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + } + } + .environment(store) + .task { + client.setActiveStickerPicker(store) + await store.load() + } + .onDisappear { client.setActiveStickerPicker(nil) } + } + + @ViewBuilder + private var content: some View { + switch store.loadState { + case .loading: + ProgressView() + case .failed(let message): + VStack(spacing: 6) { + Text(message).font(.caption2).multilineTextAlignment(.center) + Button("Retry") { Task { await store.load() } } + .buttonStyle(.borderedProminent) + } + .padding() + case .loaded: + loadedBody + } + } + + private var loadedBody: some View { + ScrollView { + VStack(alignment: .leading, spacing: 12) { + if !store.favorites.isEmpty { + StickerSectionGrid(title: "FAVORITES", stickers: store.favorites, onTap: sendAndComplete) + } + if !store.recents.isEmpty { + StickerSectionGrid(title: "FREQUENTLY USED", stickers: store.recents, onTap: sendAndComplete) + } + if !store.sets.isEmpty { + VStack(alignment: .leading, spacing: 4) { + Text("STICKER SETS").font(.caption2).foregroundStyle(.secondary).padding(.horizontal, 4) + ForEach(store.sets) { set in + StickerSetRow(set: set, onSend: sendAndComplete) + } + } + } + if store.favorites.isEmpty && store.recents.isEmpty && store.sets.isEmpty { + Text("No stickers yet").font(.caption).foregroundStyle(.secondary).padding(.top, 20) + } + } + .padding(.horizontal, 4) + .padding(.vertical, 4) + } + } + + /// Kicks the async send + collapses the sheet on success. `sending` guards a + /// rapid double-tap from sending the sticker twice. + private func sendAndComplete(_ sticker: PickerSticker) { + guard !sending else { return } + sending = true + Task { + if await onSend(sticker) { + onComplete() + } else { + sending = false + } + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerRow.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerRow.swift new file mode 100644 index 0000000000..c293f549fe --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerRow.swift @@ -0,0 +1,40 @@ +import Foundation +import TDShim + +/// Builds a `StickerVisual` for `messageSticker` content. Returns `nil` for any other +/// content. Looks up the sticker's file id (and its thumbnail file id if present) in +/// `fileLocals` for the freshest local-path snapshot. +func stickerVisual(for content: MessageContent, fileLocals: [Int: File]) -> StickerVisual? { + guard case .messageSticker(let m) = content else { return nil } + let sticker = m.sticker + let mainFile = fileLocals[sticker.sticker.id] ?? sticker.sticker + let localPath: String? = (mainFile.local.isDownloadingCompleted && !mainFile.local.path.isEmpty) + ? mainFile.local.path : nil + + var thumbnailFileId: Int? = nil + var thumbnailLocalPath: String? = nil + var thumbnailFormat: ThumbnailFormatKind? = nil + if let thumb = sticker.thumbnail { + let kind = thumbnailFormatKind(thumb.format) + thumbnailFormat = kind + if kind != .unsupported { + thumbnailFileId = thumb.file.id + let thumbFile = fileLocals[thumb.file.id] ?? thumb.file + if thumbFile.local.isDownloadingCompleted && !thumbFile.local.path.isEmpty { + thumbnailLocalPath = thumbFile.local.path + } + } + } + + return StickerVisual( + fileId: sticker.sticker.id, + format: stickerFormatKind(sticker.format), + width: sticker.width, + height: sticker.height, + emoji: sticker.emoji, + localPath: localPath, + thumbnailFileId: thumbnailFileId, + thumbnailLocalPath: thumbnailLocalPath, + thumbnailFormat: thumbnailFormat + ) +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerSetDetailView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerSetDetailView.swift new file mode 100644 index 0000000000..4e7d671bea --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerSetDetailView.swift @@ -0,0 +1,56 @@ +import SwiftUI + +/// One row in the "STICKER SETS" list: cover thumbnail + title. Tapping pushes +/// the set-detail grid (NavigationLink). The cover cell passes no `onTap`, so it +/// never intercepts the row's navigation tap. `onSend` is the picker's +/// send-and-dismiss action, forwarded to the detail grid. +struct StickerSetRow: View { + let set: PickerSet + let onSend: (PickerSticker) -> Void + + var body: some View { + NavigationLink { + StickerSetDetailView(set: set, onSend: onSend) + } label: { + HStack(spacing: 10) { + if let cover = set.cover { + StickerCellView(sticker: cover, edge: 32) + } else { + RoundedRectangle(cornerRadius: 6).fill(Color.gray.opacity(0.15)) + .frame(width: 32, height: 32) + } + Text(set.title).font(.body).lineLimit(1) + Spacer(minLength: 0) + } + .contentShape(Rectangle()) + } + } +} + +/// Grid of one set's stickers. Loads via the store on appear (the store is +/// inherited from the enclosing NavigationStack's environment); tapping a +/// sticker calls `onSend` (send + dismiss the whole picker). +struct StickerSetDetailView: View { + let set: PickerSet + let onSend: (PickerSticker) -> Void + + @Environment(StickerPickerStore.self) private var store + @State private var stickers: [PickerSticker] = [] + @State private var loading = true + + var body: some View { + ScrollView { + if loading { + ProgressView().padding(.top, 20) + } else { + StickerSectionGrid(title: nil, stickers: stickers, onTap: onSend) + .padding(.horizontal, 4) + } + } + .navigationTitle(set.title) + .task { + stickers = await store.loadSet(id: set.setId) + loading = false + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerVisual.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerVisual.swift new file mode 100644 index 0000000000..0701a24313 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/StickerVisual.swift @@ -0,0 +1,63 @@ +import Foundation +import TDShim + +/// Per-sticker visual data the bubble view consumes. Built by `stickerVisual(for:fileLocals:)` +/// from `MessageContent.messageSticker` plus the store's latest `files[fileId]` snapshot. +/// +/// Identifiable by `fileId` so it works with `.sheet(item:)` if a viewer is ever added. +struct StickerVisual: Identifiable, Equatable, Hashable { + var id: Int { fileId } + + /// TDLib file id of the main sticker file (WEBP body / TGS body / WEBM body). + let fileId: Int + let format: StickerFormatKind + /// Pixel width, as authored by the sender. Used for aspect ratio. + let width: Int + let height: Int + /// Emoji associated with the sticker; may be empty when unknown. + let emoji: String + /// Filesystem path of the main sticker file; non-nil iff downloaded. + let localPath: String? + + /// TDLib file id of the raster thumbnail (WEBP/JPEG/PNG). Nil when the sticker carries + /// no usable raster thumbnail (or only a TGS / WEBM thumbnail). + let thumbnailFileId: Int? + let thumbnailLocalPath: String? + let thumbnailFormat: ThumbnailFormatKind? +} + +/// Three-way classification of `StickerFormat`. We only render `.webp` and `.tgs` +/// fully; `.unsupported` covers `stickerFormatWebm` and any future format. +enum StickerFormatKind: Equatable, Hashable { + case webp + case tgs + case unsupported +} + +/// Classification of `ThumbnailFormat` for the placeholder render path. +/// Only raster formats decode cleanly via `UIImage(contentsOfFile:)` — TGS/WEBM/GIF/MPEG4 +/// thumbnails collapse to `.unsupported` (i.e. treat as no thumbnail). +enum ThumbnailFormatKind: Equatable, Hashable { + case webp + case jpeg + case png + case unsupported +} + +func stickerFormatKind(_ format: StickerFormat) -> StickerFormatKind { + switch format { + case .stickerFormatWebp: return .webp + case .stickerFormatTgs: return .tgs + case .stickerFormatWebm, .unsupported: return .unsupported + } +} + +func thumbnailFormatKind(_ format: ThumbnailFormat) -> ThumbnailFormatKind { + switch format { + case .thumbnailFormatWebp: return .webp + case .thumbnailFormatJpeg: return .jpeg + case .thumbnailFormatPng: return .png + case .thumbnailFormatTgs, .thumbnailFormatWebm, .thumbnailFormatGif, .thumbnailFormatMpeg4, .unsupported: + return .unsupported + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/Stickers/WebPStickerView.swift b/Telegram/WatchApp/tgwatch Watch App/Stickers/WebPStickerView.swift new file mode 100644 index 0000000000..07d6954939 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/Stickers/WebPStickerView.swift @@ -0,0 +1,44 @@ +import SwiftUI +import UIKit +import WatchKit +import WebPKit + +/// Static-image sticker (WEBP). watchOS lacks a native WebP codec in ImageIO, +/// so we use the vendored libwebp via WebPKit. Falls back to the raster thumbnail +/// (which IS decodable via UIImage — JPEG/PNG only) while the main file downloads; +/// falls back to a gray placeholder of the correct aspect ratio if neither is available. +struct WebPStickerView: View { + let sticker: StickerVisual + let displaySize: CGSize + + var body: some View { + Group { + if let path = sticker.localPath, + let cgImage = WebPDecoder.decode(url: URL(fileURLWithPath: path)) { + Image(decorative: cgImage, scale: WKInterfaceDevice.current().screenScale) + .resizable() + .scaledToFit() + } else if let thumbPath = sticker.thumbnailLocalPath, + let img = decodeThumbnail(thumbPath, format: sticker.thumbnailFormat) { + Image(uiImage: img).resizable().scaledToFit() + } else { + Color.gray.opacity(0.2) + } + } + .frame(width: displaySize.width, height: displaySize.height) + } + + /// Decodes the raster thumbnail. JPEG/PNG go through UIImage (native); WEBP thumbnails + /// go through WebPKit. Returns nil if format is unknown or decode fails. + private func decodeThumbnail(_ path: String, format: ThumbnailFormatKind?) -> UIImage? { + switch format { + case .webp: + guard let cg = WebPDecoder.decode(url: URL(fileURLWithPath: path)) else { return nil } + return UIImage(cgImage: cg) + case .jpeg, .png: + return UIImage(contentsOfFile: path) + case .unsupported, nil: + return nil + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/TDClient.swift b/Telegram/WatchApp/tgwatch Watch App/TDClient.swift new file mode 100644 index 0000000000..1068f36b5e --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/TDClient.swift @@ -0,0 +1,440 @@ +import Foundation +import Observation +import OSLog +import TDShim + +/// Receives lifecycle events from a `TDClient` that need to be reflected in +/// the wider app (registry updates, account removal). Held weakly by TDClient. +@MainActor +protocol TDClientLifecycleDelegate: AnyObject { + /// `getMe()` returned. Implementor should harvest display name, phone, + /// user id into the account record. + func tdClient(_ client: TDClient, didFetchMe me: User) + /// TDClient self-destructed (stuck-loggingOut watchdog forced a local + /// `destroy()`). Implementor should drop the account record + dir, + /// mirroring user-initiated removal. + /// + /// **Re-entrancy contract:** the watchdog can fire AFTER an + /// `awaitClosed()` timeout, by which time the controller may have + /// already moved on to a different active account. Implementors MUST + /// identity-check `client` (e.g. `client.account.id == activeAccountId`) + /// before acting, or they'll remove the wrong account. + func tdClient(_ client: TDClient, didDestroyItselfWithReason reason: TDClientDestroyReason) +} + +enum TDClientDestroyReason: Equatable { + case stuckLoggingOut +} + +@Observable +@MainActor +final class TDClient { + + let account: Account + var useTestDc: Bool { account.useTestDc } + + private(set) var authState: AuthState = .starting + private(set) var me: User? = nil + private(set) var lastError: String? = nil + let userNames = UserNamesStore() + private(set) var chatList: ChatListStore? = nil + private(set) var activeHistory: ChatHistoryStore? = nil + + func setActiveHistory(_ store: ChatHistoryStore?) { + activeHistory = store + } + + func makeChatHistoryLoader() -> ChatHistoryLoader? { + guard let client else { return nil } + return TDLibChatHistoryLoader(client: client) + } + + private(set) var activeStickerPicker: StickerPickerStore? = nil + + func setActiveStickerPicker(_ store: StickerPickerStore?) { + activeStickerPicker = store + } + + func makeStickerPickerLoader() -> StickerPickerLoader? { + guard let client else { return nil } + return TDLibStickerPickerLoader(client: client) + } + + /// Auth-state transitions and user-initiated lifecycle calls log at `.notice` + /// (default level). `.notice` is reliably persisted by Apple's unified log; + /// `.info` is memory-only for non-system subsystems and disappears from + /// `log show` after a brief window. Without persistence it's impossible to + /// tell post-hoc whether a logging-out transition came from the user or from + /// a server-initiated kick. + private let logger = Logger(subsystem: "com.isaac.tgwatch", category: "tdlib") + private let manager: TDLibClientManager + private weak var delegate: TDClientLifecycleDelegate? + private let qrLinkPublisher: QrLinkPublisher = .defaultProduction() + /// Per-process UUID exposed to QrLinkPublisher so the CLI can detect a + /// watch restart mid-poll. + private let sessionId = UUID() + private var client: TDLibClient? + + private var hasSentTdlibParameters = false + private var hasFetchedMe = false + + /// Resumed when the active TDLib client reaches `authorizationStateClosed`. + /// Multiple waiters are supported so concurrent callers (e.g. + /// `awaitClosed()` from `AccountManager.switchTo` overlapping with the + /// stuck-loggingOut watchdog inside `forceDestroy()`) don't clobber each + /// other's continuation. + private var closingContinuations: [CheckedContinuation] = [] + + /// Watchdog armed when authState enters `.loggingOut`. If the state hasn't + /// changed after `kStuckLoggingOutTimeout` it calls `client.destroy()` to + /// force a local-only logout. Cancelled on any other auth-state transition. + private var stuckLoggingOutTask: Task? + + init(account: Account, manager: TDLibClientManager, delegate: TDClientLifecycleDelegate) { + self.account = account + self.manager = manager + self.delegate = delegate + } + + /// Submits the cloud password. Returns `nil` on success, or a user-facing + /// error message (see `passwordSubmitErrorMessage`) on failure. Does not + /// touch the shared `lastError` — the password screen owns its own alert. + func submitPassword(_ password: String) async -> String? { + guard let client else { return nil } + do { + _ = try await client.checkAuthenticationPassword(password: password) + return nil + } catch { + logger.warning("checkAuthenticationPassword failed: \(error.localizedDescription, privacy: .public)") + return passwordSubmitErrorMessage(error) + } + } + + /// Re-requests QR authentication from the `waitPassword` state, driving + /// TDLib back to `waitOtherDeviceConfirmation`. Valid because + /// `requestQrCodeAuthentication` is callable from `authorizationStateWaitPassword` + /// when no auth query is pending. Returns `nil` on success, or a user-facing + /// error message on failure. Does not touch `authState` — the QR screen + /// renders via the normal `updateAuthorizationState` path; on failure the + /// caller (the password screen) shows the message in its own alert and stays + /// put, so we deliberately avoid the `.failed` transition that + /// `requestQrCodeAuthenticationIfNeeded()` performs. + func returnToQrCode() async -> String? { + guard let client else { return nil } + logger.notice("returnToQrCode requested by UI") + do { + _ = try await client.requestQrCodeAuthentication(otherUserIds: []) + return nil + } catch { + logger.warning("returnToQrCode failed: \(error.localizedDescription, privacy: .public)") + return humanMessage(error) + } + } + + func logOut() async { + guard let client else { return } + lastError = nil + logger.notice("logOut requested by UI") + do { + _ = try await client.logOut() + } catch { + logger.warning("logOut failed: \(error.localizedDescription, privacy: .public)") + lastError = humanMessage(error) + } + } + + /// Closes the TDLib client without ending the server-side session. Used + /// by `AccountManager` when switching to a different account: the + /// previous account stays signed in, its db dir stays valid. Pair with + /// `awaitClosed()`. + func close() async { + guard let client else { return } + do { + _ = try await client.close() + } catch { + logger.warning("client.close failed: \(error.localizedDescription, privacy: .public)") + } + } + + /// Awaits `authorizationStateClosed` (or 5s timeout). Used by + /// `AccountManager` to orchestrate switch / removal. Safe to call when + /// `client` is nil — resolves immediately. + func awaitClosed(timeout: TimeInterval = 5) async { + guard client != nil else { return } + if case .closed = authState { return } + await withCheckedContinuation { (continuation: CheckedContinuation) in + self.closingContinuations.append(continuation) + Task { [weak self] in + try? await Task.sleep(for: .seconds(timeout)) + await MainActor.run { [weak self] in + guard let self else { return } + if !self.closingContinuations.isEmpty { + self.logger.warning("awaitClosed: timed out after \(Int(timeout))s; forcing recreate") + self.resumeClosingIfPending() + } + } + } + } + } + + func start() { + switch validateSecrets(apiId: Secrets.apiId, apiHash: Secrets.apiHash) { + case .failure(let err): + authState = .failed(err.humanMessage) + return + case .success: + break + } + + qrLinkPublisher.clear() + let logger = self.logger + let client = manager.createClient { [weak self] data, callbackClient in + let update: Update + do { + update = try callbackClient.decoder.decode(Update.self, from: data) + } catch { + logger.warning("update decode failed: \(error.localizedDescription, privacy: .public)") + return + } + DispatchQueue.main.async { [weak self] in + self?.handle(update) + } + } + self.client = client + + let verbosity: Int = ProcessInfo.processInfo.environment["TGWATCH_TDLIB_VERBOSITY"] + .flatMap { Int($0) } ?? 1 + Task { [logger, client] in + do { + _ = try await client.setLogVerbosityLevel(newVerbosityLevel: verbosity) + } catch { + logger.warning("setLogVerbosityLevel failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + private func handle(_ update: Update) { + if case .updateAuthorizationState(let upd) = update { + lastError = nil + let mapped = mapAuthState(upd.authorizationState) + authState = mapped + logger.notice("authState -> \(String(describing: mapped), privacy: .public)") + + switch mapped { + case .waitOtherDeviceConfirmation(let link): + qrLinkPublisher.publish(link: link, useTestDc: useTestDc, sessionId: sessionId) + default: + qrLinkPublisher.clear() + } + + if case .waitTdlibParameters = mapped, !hasSentTdlibParameters { + hasSentTdlibParameters = true + sendTdlibParameters() + } + if case .waitPhoneNumber = mapped { + requestQrCodeAuthenticationIfNeeded() + } + if case .ready = mapped { + if !hasFetchedMe { + hasFetchedMe = true + fetchMe() + } + if chatList == nil, let client { + chatList = ChatListStore( + loader: TDLibChatListLoader(client: client), + selfUserId: me?.id, + userNames: userNames, + coalesceUpdates: true + ) + } + } + if case .loggingOut = mapped { + chatList = nil + activeHistory = nil + activeStickerPicker = nil + armStuckLoggingOutWatchdog() + } else { + stuckLoggingOutTask?.cancel() + stuckLoggingOutTask = nil + } + if case .closed = mapped { + chatList = nil + activeHistory = nil + activeStickerPicker = nil + resumeClosingIfPending() + } + return + } + + userNames.handle(update) + chatList?.handle(update) + activeHistory?.handle(update) + activeStickerPicker?.handle(update) + } + + /// Fired on every `waitPhoneNumber` transition. TDLib advances to + /// `waitOtherDeviceConfirmation` immediately on success so we don't see + /// `waitPhoneNumber` twice in one session; on the off chance we do (e.g. + /// logout loop), TDLib treats the second call as idempotent or errors — + /// both handled. + private func requestQrCodeAuthenticationIfNeeded() { + guard let client else { return } + Task { [logger, client] in + do { + _ = try await client.requestQrCodeAuthentication(otherUserIds: []) + } catch { + logger.warning("requestQrCodeAuthentication failed: \(error.localizedDescription, privacy: .public)") + Task { @MainActor [weak self] in + self?.lastError = humanMessage(error) + self?.authState = .failed(humanMessage(error)) + } + } + } + } + + /// Number of seconds we wait for `.loggingOut` to clear on its own before + /// concluding that TDLib is stuck and forcing a local destroy. A normal + /// client-initiated logout completes in well under 2s; we picked 15s to + /// stay comfortably above that while still recovering quickly. + private static let kStuckLoggingOutTimeout: TimeInterval = 15 + + /// Recovery for the "TDLib is stuck on .loggingOut forever" failure mode + /// we hit when Telegram's backend force-terminates the session unilaterally + /// (e.g. anti-third-party-client heuristic kicks in). The local db is left + /// marked "logging out, please confirm with server" but the corresponding + /// auth_key is already gone server-side, so every reconnect to the home DC + /// fails with `auth_key_id mismatch` and the logout never completes — + /// TDLib has no built-in timeout for this loop. + /// + /// `client.destroy()` drops the local db without server confirmation, which + /// breaks the loop. With multi-account, the destroy is followed by a + /// delegate callback so `AccountManager` can drop this account's record + + /// dir entirely (matching the "logout = remove" UX). See + /// CLAUDE.md "TDLib gets stuck on authorizationStateLoggingOut" gotcha. + private func armStuckLoggingOutWatchdog() { + stuckLoggingOutTask?.cancel() + let timeout = Self.kStuckLoggingOutTimeout + stuckLoggingOutTask = Task { [weak self] in + try? await Task.sleep(for: .seconds(timeout)) + guard !Task.isCancelled else { return } + guard let self else { return } + await self.forceDestroyIfStillStuck(timeout: timeout) + } + } + + private func forceDestroyIfStillStuck(timeout: TimeInterval) async { + guard case .loggingOut = authState else { return } + logger.notice("authState stuck on loggingOut for \(Int(timeout))s — calling destroy()") + await forceDestroy() + } + + /// Calls `destroy()` on the active TDLib client, awaits the resulting + /// `authorizationStateClosed`, then signals the delegate so + /// `AccountManager` can drop this account's record + dir. + private func forceDestroy() async { + guard let captured = client else { return } + + await withCheckedContinuation { (continuation: CheckedContinuation) in + self.closingContinuations.append(continuation) + Task { [logger, captured] in + do { + _ = try await captured.destroy() + } catch { + logger.warning("destroy() failed: \(error.localizedDescription, privacy: .public)") + } + } + Task { [weak self] in + try? await Task.sleep(for: .seconds(5)) + await MainActor.run { [weak self] in + guard let self else { return } + if !self.closingContinuations.isEmpty { + self.logger.warning("forceDestroy: timed out waiting for closed; forcing recreate") + self.resumeClosingIfPending() + } + } + } + } + + me = nil + chatList = nil + activeHistory = nil + activeStickerPicker = nil + hasSentTdlibParameters = false + hasFetchedMe = false + lastError = nil + qrLinkPublisher.clear() + stuckLoggingOutTask = nil + client = nil + // authState was already moved to .closed by handle(update) when + // TDLib emitted authorizationStateClosed after destroy(). + delegate?.tdClient(self, didDestroyItselfWithReason: .stuckLoggingOut) + } + + private func sendTdlibParameters() { + guard let client else { return } + let useTest = self.useTestDc + let dbDir = Self.databaseDirectory(accountId: account.id) + Task { [logger, client] in + do { + _ = try await client.setTdlibParameters( + apiHash: Secrets.apiHash, + apiId: Secrets.apiId, + applicationVersion: "0.1", + databaseDirectory: dbDir.path, + databaseEncryptionKey: nil, + deviceModel: "Apple Watch", + filesDirectory: dbDir.path, + systemLanguageCode: "en", + systemVersion: nil, + useChatInfoDatabase: true, + useFileDatabase: true, + useMessageDatabase: true, + useSecretChats: false, + useTestDc: useTest + ) + } catch { + logger.error("setTdlibParameters failed: \(error.localizedDescription, privacy: .public)") + Task { @MainActor [weak self] in + self?.authState = .failed(humanMessage(error)) + } + } + } + } + + private func fetchMe() { + guard let client else { return } + Task { [logger, client] in + do { + let user = try await client.getMe() + Task { @MainActor [weak self] in + guard let self else { return } + self.me = user + self.chatList?.setSelfUserId(user.id) + self.delegate?.tdClient(self, didFetchMe: user) + } + } catch { + logger.warning("getMe failed: \(error.localizedDescription, privacy: .public)") + } + } + } + + private func resumeClosingIfPending() { + let waiters = closingContinuations + closingContinuations.removeAll() + for c in waiters { + c.resume() + } + } + + /// Per-account TDLib database directory: + /// `/tdlib//`. + nonisolated static func databaseDirectory(accountId: UUID) -> URL { + let base = FileManager.default + .urls(for: .applicationSupportDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("tdlib", isDirectory: true) + let dir = base.appendingPathComponent(accountId.uuidString, isDirectory: true) + try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) + return dir + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/UserNamesStore.swift b/Telegram/WatchApp/tgwatch Watch App/UserNamesStore.swift new file mode 100644 index 0000000000..5cb14a2822 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/UserNamesStore.swift @@ -0,0 +1,24 @@ +import Foundation +import Observation +import TDShim + +/// Single source of truth for `[userId: firstName]` resolved from TDLib's +/// `updateUser` events. Owned by `TDClient`; injected into `ChatListStore` +/// and `ChatHistoryStore` at construction. Lifetime tied to the active +/// `TDClient` — account switch rebuilds it. +/// +/// Stores `firstName` only (matches the existing single-field shape used +/// by `senderName(...)`, `senderPrefix(...)`, `replyPreview(...)`, +/// `serviceActor(...)`). If the codebase ever needs a richer display +/// name, swap the dict's value type here. +@Observable @MainActor +final class UserNamesStore { + private(set) var names: [Int64: String] = [:] + + /// Absorbs `.updateUser` events. Other update kinds are ignored. Idempotent. + func handle(_ update: Update) { + if case .updateUser(let upd) = update { + names[upd.user.id] = upd.user.firstName + } + } +} diff --git a/Telegram/WatchApp/tgwatch Watch App/tgwatchApp.swift b/Telegram/WatchApp/tgwatch Watch App/tgwatchApp.swift new file mode 100644 index 0000000000..ca9955f3d9 --- /dev/null +++ b/Telegram/WatchApp/tgwatch Watch App/tgwatchApp.swift @@ -0,0 +1,74 @@ +import SwiftUI +import TDShim + +@main +struct TgwatchApp: App { + @State private var manager: AccountManager + /// True when running inside an XCTest process. Computed once and stored so + /// both `init()` and `body` can reference the same value without repeating + /// the `NSClassFromString` lookup. + private let isUnderXCTest: Bool = NSClassFromString("XCTestCase") != nil + + @MainActor + init() { + TgwatchApp.wipeMessageDatabaseIfRequested() + // Under XCTest the app's `@main` `init()` still runs inside the test + // process. Instantiating a `TDLibClientManager` here would conflict + // with the one tests create via `SharedTestTDLibManager` (TDLib's + // `td_receive` is single-thread-global — see CLAUDE.md gotcha). Hand + // the test process a no-bootstrap manager; tests never drive the + // SwiftUI scene, so its `factory` is never invoked. + let factory: any TDClientFactory + if isUnderXCTest { + factory = NoopTDClientFactory() + } else { + factory = LiveTDClientFactory(manager: TDLibClientManager()) + } + let mgr = AccountManager( + registry: .defaultProduction(), + factory: factory + ) + if !isUnderXCTest { + mgr.bootstrap() + } + _manager = State(initialValue: mgr) + } + + var body: some Scene { + WindowGroup { + if let client = manager.activeClient { + ContentView() + .environment(client) + .environment(manager) + .id(manager.activeAccountId) + } else if !isUnderXCTest { + // Under XCTest the scene is not test-driven; suppress + // AccountBootstrapView so its .task doesn't call + // ensureAccountExists() → factory.make() via NoopTDClientFactory. + AccountBootstrapView() + .environment(manager) + } + } + } + + /// DEBUG-only: deletes TDLib's sqlite message-database files (keeping + /// `td.binlog`, which holds auth keys) for every existing account dir + /// when `TGWATCH_WIPE_MESSAGE_DB=1`. Each launch then re-fetches chat + /// history from the server cold. + private static func wipeMessageDatabaseIfRequested() { +#if DEBUG + guard ProcessInfo.processInfo.environment["TGWATCH_WIPE_MESSAGE_DB"] == "1" else { return } + let fm = FileManager.default + let base = fm.urls(for: .applicationSupportDirectory, in: .userDomainMask) + .first! + .appendingPathComponent("tdlib", isDirectory: true) + guard let entries = try? fm.contentsOfDirectory(at: base, includingPropertiesForKeys: nil) else { return } + for entry in entries { + guard UUID(uuidString: entry.lastPathComponent) != nil else { continue } + for name in ["db.sqlite", "db.sqlite-shm", "db.sqlite-wal"] { + try? fm.removeItem(at: entry.appendingPathComponent(name)) + } + } +#endif + } +} diff --git a/Telegram/WatchApp/tgwatch.xcodeproj/project.pbxproj b/Telegram/WatchApp/tgwatch.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..ac34cf16c3 --- /dev/null +++ b/Telegram/WatchApp/tgwatch.xcodeproj/project.pbxproj @@ -0,0 +1,873 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 00786742690F94371FB93353 /* WaveformPacker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 446700A42BE8453DA4038145 /* WaveformPacker.swift */; }; + 03074928DC54019BE6106ECD /* AvatarVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA5A0C122ECD5BBB6EBBBBD /* AvatarVisual.swift */; }; + 039B8E2B7418D9004EAF9533 /* AttachmentSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E58B862E5D64B739DD085735 /* AttachmentSheet.swift */; }; + 04CEF3F9C267D8A7CEC5EC3C /* ClosedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6F6CB0F23C3C64FB122393 /* ClosedView.swift */; }; + 06031F19E55E96E43173C712 /* MessageWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D2B0900E5A5C5981823DDC /* MessageWindow.swift */; }; + 074BE3F4D6AB9EA220A8EDF2 /* VideoNoteBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF5A180BB3A37832AC5349 /* VideoNoteBubbleView.swift */; }; + 0BB25527B21E71352126154B /* AuthStateMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEBF4774F6AA5888CD53F478 /* AuthStateMapping.swift */; }; + 0BE4BEE5802CD229F61FF464 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = BABCBE66AF47816CA3518036 /* Account.swift */; }; + 0EC6EE1E219B9104BED3A416 /* VoiceEditorWaveform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 476DE752E0B2EC45A48114F0 /* VoiceEditorWaveform.swift */; }; + 11F6B5A10EFBBAFB10C8DBFE /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC83B10E8C4450B4414381A4 /* LottieAnimationView.swift */; }; + 12810A4953467A087E07DD3F /* OpusKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4A4F9C233953209CE4C3DB1A /* OpusKit */; }; + 1773B32101D7CB51E65BE91B /* PhotoVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0240D336BD11E2613A457C43 /* PhotoVisual.swift */; }; + 1D1502C120B3AF8B91C1A91D /* AVRecordingBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F9127473C7DAD18736234B3 /* AVRecordingBackend.swift */; }; + 1D4E75A6F594617ACD668616 /* SampleStickers in Resources */ = {isa = PBXBuildFile; fileRef = 72755AB1D5DB932DDC3CE2D3 /* SampleStickers */; }; + 1F7CEC07D0DBC5FD130AF942 /* AccountBootstrapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377FDFE2ED14BCD51D22429F /* AccountBootstrapView.swift */; }; + 20167DF49E7A2E543B274E76 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = A065521FE790E0BF29D1B3B6 /* Secrets.swift */; }; + 20863684FA2F51495BB2A9A3 /* AccountsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 148D11BBC0B40441DCC8EA0A /* AccountsListView.swift */; }; + 25F6B3EFED8EDDD9983CB0F7 /* PollVoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF0F4F15CB43635717B31B92 /* PollVoteView.swift */; }; + 2A0FDF9EF2B8BE9F4C46FD4C /* RLottieKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5083D73573D84E386C2E3B4C /* RLottieKit */; }; + 2E2B46A1FFFDC24F47FF33D4 /* ChatOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3696C9BA38845E621A1B67 /* ChatOpener.swift */; }; + 3679D6EA591522207E69F8A5 /* ChatListTimestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CCDFBD19F9CAA87E938BADC /* ChatListTimestamp.swift */; }; + 39A386FFBD00EEE14B2B33D5 /* MessageFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09479749C40E78AD852AF8B /* MessageFormatters.swift */; }; + 3BA42E87FCC662196BB80B64 /* MessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F01C7C9A4B9948C0BB710E2 /* MessageRow.swift */; }; + 4236CA9B5AA90ACEB17814DC /* BubbleMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 674916153E803CA73D43BE6C /* BubbleMetrics.swift */; }; + 44630BDE4B93AF13B9903093 /* StickerPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C5B5ED9309BE4DA670469D /* StickerPicker.swift */; }; + 4505C5628AF9258938D623D7 /* CachedChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C5E645A6F575F72C3B7839D /* CachedChat.swift */; }; + 4546DBC7A3577AA688A7069C /* VideoVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DF1E1C15EBF3931AC3843C /* VideoVisual.swift */; }; + 488B0EBEDE035351B1E649CB /* PollBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B11F6105ABCE7E74B89A881 /* PollBubbleView.swift */; }; + 4938F79EA33D5C8041A659C3 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7108EAF6A69D8D59376A8914 /* ChatListView.swift */; }; + 53EE51754BE717246108A9F6 /* VoiceNoteVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00101E229543EB30B46F9787 /* VoiceNoteVisual.swift */; }; + 546D2929CAE276CCCDA61287 /* StickerVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE09496C42DB2A7802AFDDD5 /* StickerVisual.swift */; }; + 576608622AFF98B6FF84C161 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 683723ECC14D43FE1FE4D448 /* ErrorView.swift */; }; + 5D217805BCCE97B2A4B14F2B /* VoiceNoteBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D479FDCEDD7792FC03E7DE03 /* VoiceNoteBubbleView.swift */; }; + 5DF5B3342BFBC50C1E211068 /* StickerBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99A867714B28B54D5B3DF98 /* StickerBubbleView.swift */; }; + 5E9A6B94F1B917CB849B3EBC /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35B32555B3686A7A4884B215 /* AvatarView.swift */; }; + 62E23F09E892DE847642EDB1 /* EmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DB3C8238D41374CBAC66303 /* EmojiText.swift */; }; + 641846C985430AD34793F0D3 /* ReplyHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F595E3DA02BF871A797A649A /* ReplyHeader.swift */; }; + 6475073E5937A447F70599A8 /* StickerCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3496CF0F6E1D25422AF9DB0 /* StickerCellView.swift */; }; + 69AC5285D7EC946091521E65 /* WebPStickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFD9DBB81D8F2028DAE6CF8 /* WebPStickerView.swift */; }; + 6A7C61DC675315A5928461BC /* MessageBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B605DE4F9A80E15250C5D693 /* MessageBubbleView.swift */; }; + 6AA9F0DA20077AB00256E225 /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7889969AFF462911B9828843 /* MessageRowView.swift */; }; + 6C6F45616840D74520FFA5D3 /* ReviewVoiceBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8724043C70C9B97BCBA4712F /* ReviewVoiceBubble.swift */; }; + 6DD41F6FCB2E2F6E2A231059 /* ErrorMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = F424F5E873CD24E281387693 /* ErrorMapping.swift */; }; + 6E676E1D3C5066A6590C725C /* LocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B4D75BC5502D8D705E859FB /* LocationProvider.swift */; }; + 7059458F1317797724013AC8 /* AccountListProjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DDFFE71E0C32258348A2510 /* AccountListProjection.swift */; }; + 750C266D413747DF23D80270 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5F4E8DCEFAFB514361761C /* AuthState.swift */; }; + 787D692CF045D92B0DEFC1F2 /* TDClientFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 695248F618E8ABD4F9840F92 /* TDClientFactory.swift */; }; + 79A01936CFB9168EC5ABD641 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BFBE65008CE5037F6DF19A1 /* VideoPlayerView.swift */; }; + 7BF1C89F67290EBAF50900F8 /* ChatRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29C2B6C3E8643BAF27074570 /* ChatRowView.swift */; }; + 7D835D5CE3C3B58799AFAF36 /* StickerPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9196F9ED24A5500CE7A073D9 /* StickerPickerView.swift */; }; + 7DD192512B16EA3515244C22 /* VoicePlaybackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7405292B1E44847A760AC5 /* VoicePlaybackController.swift */; }; + 80C0C6603FDC96FE26ED7658 /* VideoNotePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A73BF3FB58AE7A1F8891DB8 /* VideoNotePlayerView.swift */; }; + 81C79089068A1B436DFFD2EF /* ChatListStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F824052186720912B44D8263 /* ChatListStore.swift */; }; + 832059B1E6AF007167F5EAC0 /* ChatListLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4AA2ED987D3215C041B6A6 /* ChatListLoader.swift */; }; + 8364AFF20E93D890EFEF7C41 /* AccountRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42427DC1E64A2F55DE8BE345 /* AccountRegistry.swift */; }; + 8428660F7B4DF838568EBB8C /* LocationSendController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8A828AD3ABE7B2740A7F10 /* LocationSendController.swift */; }; + 868426A2768FE7349CD4AAAD /* BubbleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EEAE6237003C5CDA788E928 /* BubbleStyle.swift */; }; + 87EF3EB5EB8BD10F1F65EDAB /* WebPKit in Frameworks */ = {isa = PBXBuildFile; productRef = F573B77B0568E47F98C5A063 /* WebPKit */; }; + 881BA28087741D8F6B406FE9 /* AccountSwitcherSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B89EE0100295ED590F73723 /* AccountSwitcherSheet.swift */; }; + 8AFE89397E88D6FD3BDB39E6 /* StickerSetDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9CD96277A18D97BD335BBC0 /* StickerSetDetailView.swift */; }; + 8F863F799330257D25F04D64 /* LocationSendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B9831274FA56126E3F5A42 /* LocationSendView.swift */; }; + 939315A8C737DB06ED040E4F /* VideoBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0386BF4C7B1D4BDAE0B73509 /* VideoBubbleView.swift */; }; + 93A8BCCF8EE61D81215359A9 /* VoiceRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97ADF582CAB9B2D6C9E091EC /* VoiceRecorder.swift */; }; + 99F0C429392E04F83F9935B2 /* StickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22AAEA9EF0EC8A548BB916EC /* StickerRow.swift */; }; + 9D232B81EAE7720972261B34 /* tgwatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4BDBD692A424C5C24D61B4F /* tgwatchApp.swift */; }; + 9FC324F2FEBBDF3C24360CA3 /* LocationBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 596AF8BF88C38D2018397A33 /* LocationBubbleView.swift */; }; + A0899809BBC92D1563AC4160 /* ReplyBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7584BF25A84BAD24AB992A79 /* ReplyBar.swift */; }; + A121DA7622C910B267D69B6E /* PhotoBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0F7533C5A9B419B29727C86 /* PhotoBubbleView.swift */; }; + A6179A26CB7FB298567682E6 /* DocumentVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2515F0EDA29D5270610A69 /* DocumentVisual.swift */; }; + A7967495A54936FE19218DC4 /* ServiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF393D904BF677AC5721EC7E /* ServiceMessageView.swift */; }; + A8EF6F72C78B619893C05AFE /* ChatPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448938C1F69F0D9DE679238 /* ChatPreview.swift */; }; + AA3F067A7BC4CDA579CFFC39 /* PollVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4D691106D7EA2FEE35F37F /* PollVisual.swift */; }; + AAE21A407D0201F0A8630FD9 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23B6E36BD1D21CA9073903BF /* AccountManager.swift */; }; + B14FB831F58FBE797B2E5E22 /* UnreadDividerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677A0F85CE4C41873A7774C2 /* UnreadDividerView.swift */; }; + B60D32384AEF65DF99757144 /* PasswordEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E86853697764EDEB0866DA2 /* PasswordEntryView.swift */; }; + B777F8B2ACBABCC223BE25A4 /* VoiceRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604A4F1FAB5820C300C709D5 /* VoiceRecordView.swift */; }; + B79AB6E8675A1A32B8DD9E4A /* UnsupportedBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8545CFD4F2B01CDACB225974 /* UnsupportedBubbleView.swift */; }; + B8FB9BDD812396930990235C /* SecretsValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E049ECC956A583A92C5C7CB /* SecretsValidator.swift */; }; + B9F6D473AA0F1DAF77799E77 /* QrLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D3A8FBA5EBDBFDE5AA2FFEC /* QrLoginView.swift */; }; + C00221119F0E81D881582EEE /* VideoNoteVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D49A955BB3D23881CE8E150 /* VideoNoteVisual.swift */; }; + C1DD63C4AA3FED433C0B2830 /* ServiceLineFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F59F34916FE776927C3B2CB1 /* ServiceLineFormatter.swift */; }; + C2FC5C2F1B11E8B83F7FF203 /* LocationVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68256665FAE596E6E1E18347 /* LocationVisual.swift */; }; + C3AD58C53B02CCF230E13BC2 /* WaveformBars.swift in Sources */ = {isa = PBXBuildFile; fileRef = 687E86C7ECD479328FAEA31A /* WaveformBars.swift */; }; + C3DFBB28315AE47818CE77F0 /* ReplyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1329216EB35DF3FC7636843D /* ReplyHeaderView.swift */; }; + C62226963E54795B2808A52C /* MapSnapshotRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFFF41E60C5477C20897CD00 /* MapSnapshotRenderer.swift */; }; + C6A64A26FC7DC74BDEC91308 /* ChatHistoryLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2C84B842AD08D079BFF08A0 /* ChatHistoryLoader.swift */; }; + C94BD040D985FC0090CDBF84 /* StickerPickerLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC230FDDDFAA063019744A0E /* StickerPickerLoader.swift */; }; + CBA3FEDEEA413559F700B6E3 /* BubbleShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD18F48DC98A3857B32BBC6F /* BubbleShape.swift */; }; + D1082945B5785B08F820FB78 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93535C2021AB2E7B17F593AA /* LoadingView.swift */; }; + D1E96E63A3AE93646AD3F532 /* AudioBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C3B71804A398A0100375E86 /* AudioBubbleView.swift */; }; + D5515C5A9286E0372DBFB7F9 /* QRCodeGenerator in Frameworks */ = {isa = PBXBuildFile; productRef = FD6A04697FEA6C4614973F19 /* QRCodeGenerator */; }; + DC1BB1D3B075945C5668EC61 /* MessageListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA750417DF3AD62C7B788F6 /* MessageListView.swift */; }; + DD9FB33F951CC225F8D5B3EE /* UserNamesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63843FEEB957FF475FF41D6 /* UserNamesStore.swift */; }; + DDB206E2134B52F472D5E9FE /* TDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C5FEFC91B0D5FDF164032 /* TDClient.swift */; }; + E037E3EA9827C286D724E0C0 /* PhotoViewerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78DB439A3F9A5EA35ECDFB86 /* PhotoViewerView.swift */; }; + E48523AFA2E23409CC0C2C2C /* ChatRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1932CC94F39BD57A56E59C08 /* ChatRow.swift */; }; + EB1486FA24BEE9E973D9679C /* StickerPickerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F1B6E53745C3D7900554D /* StickerPickerStore.swift */; }; + EE2D34F61EAF165651914325 /* FolderPillBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F5EE457DA36FD2FCDF6562 /* FolderPillBar.swift */; }; + EF499261BE49A6B1C7B3B1F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1131A5FC4707D20BFB5E95B /* ContentView.swift */; }; + F44AC47D6D4816D1709F0460 /* ChatHistoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182DA99AD90B93DE2FB37A41 /* ChatHistoryStore.swift */; }; + F5596A4EF4D2055B10B4A183 /* DocumentBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBE08BBB88597B10CDF0142 /* DocumentBubbleView.swift */; }; + F7096697A6319D8AE9047C1E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D50662DC1777F2BEE53A0129 /* Assets.xcassets */; }; + F7D46529A75FEBA7AEE058A8 /* AudioVisual.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBAFC996C8EF743C7C598389 /* AudioVisual.swift */; }; + F8F834C103DB1A687BFD54B8 /* FolderPill.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C34577A65DD579BCCD1888C /* FolderPill.swift */; }; + F917BC9D348452D168998A84 /* TDShim in Frameworks */ = {isa = PBXBuildFile; productRef = 36B07FC482F9E6A9D62DB94F /* TDShim */; }; + F95C046A6C043344F0955815 /* QrLinkPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA96A6FDE6FBC1F2054E82A /* QrLinkPublisher.swift */; }; + FB05A240DD234EA077E9B692 /* ChatListKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0F1DD19A57CC68887E4F98 /* ChatListKey.swift */; }; + FB3861C5B3036FACA2188A43 /* AudioPlaybackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAB96C768DF82BCF185BAE2 /* AudioPlaybackController.swift */; }; + FC110506EAA6FA010E83F407 /* ReplyFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE599C8D50EE8832B4A797 /* ReplyFormatters.swift */; }; + FD5515DB22C571F4D3E5F5FA /* DaySeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E855E4C62401AD1CF41D1835 /* DaySeparatorView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 00101E229543EB30B46F9787 /* VoiceNoteVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteVisual.swift; sourceTree = ""; }; + 0240D336BD11E2613A457C43 /* PhotoVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoVisual.swift; sourceTree = ""; }; + 0386BF4C7B1D4BDAE0B73509 /* VideoBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoBubbleView.swift; sourceTree = ""; }; + 0CCDFBD19F9CAA87E938BADC /* ChatListTimestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListTimestamp.swift; sourceTree = ""; }; + 0F9127473C7DAD18736234B3 /* AVRecordingBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVRecordingBackend.swift; sourceTree = ""; }; + 1329216EB35DF3FC7636843D /* ReplyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyHeaderView.swift; sourceTree = ""; }; + 14088667131D587C886B3143 /* TDShim */ = {isa = PBXFileReference; lastKnownFileType = folder; name = TDShim; path = Packages/TDShim; sourceTree = SOURCE_ROOT; }; + 148D11BBC0B40441DCC8EA0A /* AccountsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsListView.swift; sourceTree = ""; }; + 182DA99AD90B93DE2FB37A41 /* ChatHistoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistoryStore.swift; sourceTree = ""; }; + 1932CC94F39BD57A56E59C08 /* ChatRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRow.swift; sourceTree = ""; }; + 20DF1E1C15EBF3931AC3843C /* VideoVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoVisual.swift; sourceTree = ""; }; + 22AAEA9EF0EC8A548BB916EC /* StickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRow.swift; sourceTree = ""; }; + 23B6E36BD1D21CA9073903BF /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; + 29C2B6C3E8643BAF27074570 /* ChatRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRowView.swift; sourceTree = ""; }; + 2C34577A65DD579BCCD1888C /* FolderPill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPill.swift; sourceTree = ""; }; + 2E7405292B1E44847A760AC5 /* VoicePlaybackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoicePlaybackController.swift; sourceTree = ""; }; + 3448938C1F69F0D9DE679238 /* ChatPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreview.swift; sourceTree = ""; }; + 35B32555B3686A7A4884B215 /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; }; + 36FCB0CB6A9A867B1E8BD673 /* OpusKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = OpusKit; path = Packages/OpusKit; sourceTree = SOURCE_ROOT; }; + 377FDFE2ED14BCD51D22429F /* AccountBootstrapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountBootstrapView.swift; sourceTree = ""; }; + 3B4D75BC5502D8D705E859FB /* LocationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationProvider.swift; sourceTree = ""; }; + 3DAB96C768DF82BCF185BAE2 /* AudioPlaybackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlaybackController.swift; sourceTree = ""; }; + 3E86853697764EDEB0866DA2 /* PasswordEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordEntryView.swift; sourceTree = ""; }; + 3EEAE6237003C5CDA788E928 /* BubbleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleStyle.swift; sourceTree = ""; }; + 42427DC1E64A2F55DE8BE345 /* AccountRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRegistry.swift; sourceTree = ""; }; + 446700A42BE8453DA4038145 /* WaveformPacker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformPacker.swift; sourceTree = ""; }; + 45F5EE457DA36FD2FCDF6562 /* FolderPillBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderPillBar.swift; sourceTree = ""; }; + 476DE752E0B2EC45A48114F0 /* VoiceEditorWaveform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceEditorWaveform.swift; sourceTree = ""; }; + 4A6F6CB0F23C3C64FB122393 /* ClosedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClosedView.swift; sourceTree = ""; }; + 4DB3C8238D41374CBAC66303 /* EmojiText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiText.swift; sourceTree = ""; }; + 4E049ECC956A583A92C5C7CB /* SecretsValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretsValidator.swift; sourceTree = ""; }; + 53B9831274FA56126E3F5A42 /* LocationSendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSendView.swift; sourceTree = ""; }; + 596AF8BF88C38D2018397A33 /* LocationBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationBubbleView.swift; sourceTree = ""; }; + 5D3A8FBA5EBDBFDE5AA2FFEC /* QrLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrLoginView.swift; sourceTree = ""; }; + 5EA750417DF3AD62C7B788F6 /* MessageListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListView.swift; sourceTree = ""; }; + 604A4F1FAB5820C300C709D5 /* VoiceRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecordView.swift; sourceTree = ""; }; + 60F8F618BC10DFE08DCBC8C7 /* RLottieKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = RLottieKit; path = Packages/RLottieKit; sourceTree = SOURCE_ROOT; }; + 674916153E803CA73D43BE6C /* BubbleMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleMetrics.swift; sourceTree = ""; }; + 677A0F85CE4C41873A7774C2 /* UnreadDividerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadDividerView.swift; sourceTree = ""; }; + 68256665FAE596E6E1E18347 /* LocationVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationVisual.swift; sourceTree = ""; }; + 683723ECC14D43FE1FE4D448 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + 687E86C7ECD479328FAEA31A /* WaveformBars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveformBars.swift; sourceTree = ""; }; + 695248F618E8ABD4F9840F92 /* TDClientFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDClientFactory.swift; sourceTree = ""; }; + 69A8C5AE84DB92393FD19C0E /* WebPKit */ = {isa = PBXFileReference; lastKnownFileType = folder; name = WebPKit; path = Packages/WebPKit; sourceTree = SOURCE_ROOT; }; + 6B11F6105ABCE7E74B89A881 /* PollBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollBubbleView.swift; sourceTree = ""; }; + 6B89EE0100295ED590F73723 /* AccountSwitcherSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitcherSheet.swift; sourceTree = ""; }; + 6D8A828AD3ABE7B2740A7F10 /* LocationSendController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSendController.swift; sourceTree = ""; }; + 6DDFFE71E0C32258348A2510 /* AccountListProjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListProjection.swift; sourceTree = ""; }; + 7108EAF6A69D8D59376A8914 /* ChatListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListView.swift; sourceTree = ""; }; + 72755AB1D5DB932DDC3CE2D3 /* SampleStickers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SampleStickers; path = Assets/SampleStickers; sourceTree = SOURCE_ROOT; }; + 7584BF25A84BAD24AB992A79 /* ReplyBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyBar.swift; sourceTree = ""; }; + 7889969AFF462911B9828843 /* MessageRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRowView.swift; sourceTree = ""; }; + 78DB439A3F9A5EA35ECDFB86 /* PhotoViewerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoViewerView.swift; sourceTree = ""; }; + 7B0F1DD19A57CC68887E4F98 /* ChatListKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListKey.swift; sourceTree = ""; }; + 8545CFD4F2B01CDACB225974 /* UnsupportedBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedBubbleView.swift; sourceTree = ""; }; + 8724043C70C9B97BCBA4712F /* ReviewVoiceBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewVoiceBubble.swift; sourceTree = ""; }; + 8D49A955BB3D23881CE8E150 /* VideoNoteVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteVisual.swift; sourceTree = ""; }; + 8F01C7C9A4B9948C0BB710E2 /* MessageRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRow.swift; sourceTree = ""; }; + 9196F9ED24A5500CE7A073D9 /* StickerPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerView.swift; sourceTree = ""; }; + 93535C2021AB2E7B17F593AA /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + 97ADF582CAB9B2D6C9E091EC /* VoiceRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecorder.swift; sourceTree = ""; }; + 9A73BF3FB58AE7A1F8891DB8 /* VideoNotePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNotePlayerView.swift; sourceTree = ""; }; + 9BFBE65008CE5037F6DF19A1 /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + 9C3B71804A398A0100375E86 /* AudioBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioBubbleView.swift; sourceTree = ""; }; + 9C5E645A6F575F72C3B7839D /* CachedChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedChat.swift; sourceTree = ""; }; + 9E5F4E8DCEFAFB514361761C /* AuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = ""; }; + A065521FE790E0BF29D1B3B6 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; + A1131A5FC4707D20BFB5E95B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + A4BDBD692A424C5C24D61B4F /* tgwatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tgwatchApp.swift; sourceTree = ""; }; + A63843FEEB957FF475FF41D6 /* UserNamesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNamesStore.swift; sourceTree = ""; }; + ADFD9DBB81D8F2028DAE6CF8 /* WebPStickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebPStickerView.swift; sourceTree = ""; }; + B089B6570CCD50D105C035FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + B0F7533C5A9B419B29727C86 /* PhotoBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBubbleView.swift; sourceTree = ""; }; + B605DE4F9A80E15250C5D693 /* MessageBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubbleView.swift; sourceTree = ""; }; + BABCBE66AF47816CA3518036 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = ""; }; + BC230FDDDFAA063019744A0E /* StickerPickerLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerLoader.swift; sourceTree = ""; }; + BDBE08BBB88597B10CDF0142 /* DocumentBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentBubbleView.swift; sourceTree = ""; }; + BE09496C42DB2A7802AFDDD5 /* StickerVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerVisual.swift; sourceTree = ""; }; + C2C5B5ED9309BE4DA670469D /* StickerPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPicker.swift; sourceTree = ""; }; + C2C84B842AD08D079BFF08A0 /* ChatHistoryLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHistoryLoader.swift; sourceTree = ""; }; + C99A867714B28B54D5B3DF98 /* StickerBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerBubbleView.swift; sourceTree = ""; }; + CB4AA2ED987D3215C041B6A6 /* ChatListLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListLoader.swift; sourceTree = ""; }; + CBAFC996C8EF743C7C598389 /* AudioVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioVisual.swift; sourceTree = ""; }; + CC83B10E8C4450B4414381A4 /* LottieAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationView.swift; sourceTree = ""; }; + CEA96A6FDE6FBC1F2054E82A /* QrLinkPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrLinkPublisher.swift; sourceTree = ""; }; + CFFF41E60C5477C20897CD00 /* MapSnapshotRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSnapshotRenderer.swift; sourceTree = ""; }; + D02F1B6E53745C3D7900554D /* StickerPickerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerStore.swift; sourceTree = ""; }; + D479FDCEDD7792FC03E7DE03 /* VoiceNoteBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceNoteBubbleView.swift; sourceTree = ""; }; + D4AF5A180BB3A37832AC5349 /* VideoNoteBubbleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoNoteBubbleView.swift; sourceTree = ""; }; + D50662DC1777F2BEE53A0129 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + D9CD96277A18D97BD335BBC0 /* StickerSetDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerSetDetailView.swift; sourceTree = ""; }; + DD18F48DC98A3857B32BBC6F /* BubbleShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BubbleShape.swift; sourceTree = ""; }; + DDCE599C8D50EE8832B4A797 /* ReplyFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyFormatters.swift; sourceTree = ""; }; + DF0F4F15CB43635717B31B92 /* PollVoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVoteView.swift; sourceTree = ""; }; + DF393D904BF677AC5721EC7E /* ServiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceMessageView.swift; sourceTree = ""; }; + E09479749C40E78AD852AF8B /* MessageFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageFormatters.swift; sourceTree = ""; }; + E3496CF0F6E1D25422AF9DB0 /* StickerCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerCellView.swift; sourceTree = ""; }; + E58B862E5D64B739DD085735 /* AttachmentSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentSheet.swift; sourceTree = ""; }; + E855E4C62401AD1CF41D1835 /* DaySeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaySeparatorView.swift; sourceTree = ""; }; + EB5C5FEFC91B0D5FDF164032 /* TDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDClient.swift; sourceTree = ""; }; + ECA5A0C122ECD5BBB6EBBBBD /* AvatarVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarVisual.swift; sourceTree = ""; }; + ED3696C9BA38845E621A1B67 /* ChatOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatOpener.swift; sourceTree = ""; }; + F424F5E873CD24E281387693 /* ErrorMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMapping.swift; sourceTree = ""; }; + F595E3DA02BF871A797A649A /* ReplyHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyHeader.swift; sourceTree = ""; }; + F59F34916FE776927C3B2CB1 /* ServiceLineFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLineFormatter.swift; sourceTree = ""; }; + F72CE4C1520F9330AFDE2D07 /* tgwatch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tgwatch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + F824052186720912B44D8263 /* ChatListStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListStore.swift; sourceTree = ""; }; + F9D2B0900E5A5C5981823DDC /* MessageWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageWindow.swift; sourceTree = ""; }; + FE4D691106D7EA2FEE35F37F /* PollVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollVisual.swift; sourceTree = ""; }; + FEBF4774F6AA5888CD53F478 /* AuthStateMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthStateMapping.swift; sourceTree = ""; }; + FF2515F0EDA29D5270610A69 /* DocumentVisual.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentVisual.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + FD92F7152AACBC99210DE5F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F917BC9D348452D168998A84 /* TDShim in Frameworks */, + D5515C5A9286E0372DBFB7F9 /* QRCodeGenerator in Frameworks */, + 2A0FDF9EF2B8BE9F4C46FD4C /* RLottieKit in Frameworks */, + 87EF3EB5EB8BD10F1F65EDAB /* WebPKit in Frameworks */, + 12810A4953467A087E07DD3F /* OpusKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 196CD8C5FE0F6403108D6AF9 /* Stickers */ = { + isa = PBXGroup; + children = ( + CC83B10E8C4450B4414381A4 /* LottieAnimationView.swift */, + C99A867714B28B54D5B3DF98 /* StickerBubbleView.swift */, + E3496CF0F6E1D25422AF9DB0 /* StickerCellView.swift */, + C2C5B5ED9309BE4DA670469D /* StickerPicker.swift */, + BC230FDDDFAA063019744A0E /* StickerPickerLoader.swift */, + D02F1B6E53745C3D7900554D /* StickerPickerStore.swift */, + 9196F9ED24A5500CE7A073D9 /* StickerPickerView.swift */, + 22AAEA9EF0EC8A548BB916EC /* StickerRow.swift */, + D9CD96277A18D97BD335BBC0 /* StickerSetDetailView.swift */, + BE09496C42DB2A7802AFDDD5 /* StickerVisual.swift */, + ADFD9DBB81D8F2028DAE6CF8 /* WebPStickerView.swift */, + ); + path = Stickers; + sourceTree = ""; + }; + 1E731BF02CCC57AAFD5882E4 /* tgwatch Watch App */ = { + isa = PBXGroup; + children = ( + D50662DC1777F2BEE53A0129 /* Assets.xcassets */, + 9E5F4E8DCEFAFB514361761C /* AuthState.swift */, + FEBF4774F6AA5888CD53F478 /* AuthStateMapping.swift */, + A1131A5FC4707D20BFB5E95B /* ContentView.swift */, + F424F5E873CD24E281387693 /* ErrorMapping.swift */, + B089B6570CCD50D105C035FA /* Info.plist */, + A065521FE790E0BF29D1B3B6 /* Secrets.swift */, + 4E049ECC956A583A92C5C7CB /* SecretsValidator.swift */, + EB5C5FEFC91B0D5FDF164032 /* TDClient.swift */, + A4BDBD692A424C5C24D61B4F /* tgwatchApp.swift */, + A63843FEEB957FF475FF41D6 /* UserNamesStore.swift */, + 29C965D314FEDA10CE9863BE /* Accounts */, + BAA50B5855347906BB59F600 /* Auth */, + FDA1198062B03EEE2B0A5D0B /* Chats */, + A3B9163AAB681ADB71FC2C1A /* Messages */, + 196CD8C5FE0F6403108D6AF9 /* Stickers */, + ); + path = "tgwatch Watch App"; + sourceTree = ""; + }; + 26706DBEFA60B4F5FC661D56 /* Packages */ = { + isa = PBXGroup; + children = ( + 36FCB0CB6A9A867B1E8BD673 /* OpusKit */, + 60F8F618BC10DFE08DCBC8C7 /* RLottieKit */, + 14088667131D587C886B3143 /* TDShim */, + 69A8C5AE84DB92393FD19C0E /* WebPKit */, + ); + path = Packages; + sourceTree = ""; + }; + 29C965D314FEDA10CE9863BE /* Accounts */ = { + isa = PBXGroup; + children = ( + BABCBE66AF47816CA3518036 /* Account.swift */, + 377FDFE2ED14BCD51D22429F /* AccountBootstrapView.swift */, + 6DDFFE71E0C32258348A2510 /* AccountListProjection.swift */, + 23B6E36BD1D21CA9073903BF /* AccountManager.swift */, + 42427DC1E64A2F55DE8BE345 /* AccountRegistry.swift */, + 148D11BBC0B40441DCC8EA0A /* AccountsListView.swift */, + 6B89EE0100295ED590F73723 /* AccountSwitcherSheet.swift */, + 695248F618E8ABD4F9840F92 /* TDClientFactory.swift */, + ); + path = Accounts; + sourceTree = ""; + }; + 67CACE3D39996E520610926F = { + isa = PBXGroup; + children = ( + 72755AB1D5DB932DDC3CE2D3 /* SampleStickers */, + 26706DBEFA60B4F5FC661D56 /* Packages */, + 1E731BF02CCC57AAFD5882E4 /* tgwatch Watch App */, + 6BB635CC5B892659A3E61172 /* Products */, + ); + sourceTree = ""; + }; + 6BB635CC5B892659A3E61172 /* Products */ = { + isa = PBXGroup; + children = ( + F72CE4C1520F9330AFDE2D07 /* tgwatch Watch App.app */, + ); + name = Products; + sourceTree = ""; + }; + A3B9163AAB681ADB71FC2C1A /* Messages */ = { + isa = PBXGroup; + children = ( + E58B862E5D64B739DD085735 /* AttachmentSheet.swift */, + 9C3B71804A398A0100375E86 /* AudioBubbleView.swift */, + 3DAB96C768DF82BCF185BAE2 /* AudioPlaybackController.swift */, + CBAFC996C8EF743C7C598389 /* AudioVisual.swift */, + 0F9127473C7DAD18736234B3 /* AVRecordingBackend.swift */, + 674916153E803CA73D43BE6C /* BubbleMetrics.swift */, + DD18F48DC98A3857B32BBC6F /* BubbleShape.swift */, + 3EEAE6237003C5CDA788E928 /* BubbleStyle.swift */, + C2C84B842AD08D079BFF08A0 /* ChatHistoryLoader.swift */, + 182DA99AD90B93DE2FB37A41 /* ChatHistoryStore.swift */, + E855E4C62401AD1CF41D1835 /* DaySeparatorView.swift */, + BDBE08BBB88597B10CDF0142 /* DocumentBubbleView.swift */, + FF2515F0EDA29D5270610A69 /* DocumentVisual.swift */, + 4DB3C8238D41374CBAC66303 /* EmojiText.swift */, + 596AF8BF88C38D2018397A33 /* LocationBubbleView.swift */, + 3B4D75BC5502D8D705E859FB /* LocationProvider.swift */, + 6D8A828AD3ABE7B2740A7F10 /* LocationSendController.swift */, + 53B9831274FA56126E3F5A42 /* LocationSendView.swift */, + 68256665FAE596E6E1E18347 /* LocationVisual.swift */, + CFFF41E60C5477C20897CD00 /* MapSnapshotRenderer.swift */, + B605DE4F9A80E15250C5D693 /* MessageBubbleView.swift */, + E09479749C40E78AD852AF8B /* MessageFormatters.swift */, + 5EA750417DF3AD62C7B788F6 /* MessageListView.swift */, + 8F01C7C9A4B9948C0BB710E2 /* MessageRow.swift */, + 7889969AFF462911B9828843 /* MessageRowView.swift */, + F9D2B0900E5A5C5981823DDC /* MessageWindow.swift */, + B0F7533C5A9B419B29727C86 /* PhotoBubbleView.swift */, + 78DB439A3F9A5EA35ECDFB86 /* PhotoViewerView.swift */, + 0240D336BD11E2613A457C43 /* PhotoVisual.swift */, + 6B11F6105ABCE7E74B89A881 /* PollBubbleView.swift */, + FE4D691106D7EA2FEE35F37F /* PollVisual.swift */, + DF0F4F15CB43635717B31B92 /* PollVoteView.swift */, + 7584BF25A84BAD24AB992A79 /* ReplyBar.swift */, + DDCE599C8D50EE8832B4A797 /* ReplyFormatters.swift */, + F595E3DA02BF871A797A649A /* ReplyHeader.swift */, + 1329216EB35DF3FC7636843D /* ReplyHeaderView.swift */, + 8724043C70C9B97BCBA4712F /* ReviewVoiceBubble.swift */, + F59F34916FE776927C3B2CB1 /* ServiceLineFormatter.swift */, + DF393D904BF677AC5721EC7E /* ServiceMessageView.swift */, + 677A0F85CE4C41873A7774C2 /* UnreadDividerView.swift */, + 8545CFD4F2B01CDACB225974 /* UnsupportedBubbleView.swift */, + 0386BF4C7B1D4BDAE0B73509 /* VideoBubbleView.swift */, + D4AF5A180BB3A37832AC5349 /* VideoNoteBubbleView.swift */, + 9A73BF3FB58AE7A1F8891DB8 /* VideoNotePlayerView.swift */, + 8D49A955BB3D23881CE8E150 /* VideoNoteVisual.swift */, + 9BFBE65008CE5037F6DF19A1 /* VideoPlayerView.swift */, + 20DF1E1C15EBF3931AC3843C /* VideoVisual.swift */, + 476DE752E0B2EC45A48114F0 /* VoiceEditorWaveform.swift */, + D479FDCEDD7792FC03E7DE03 /* VoiceNoteBubbleView.swift */, + 00101E229543EB30B46F9787 /* VoiceNoteVisual.swift */, + 2E7405292B1E44847A760AC5 /* VoicePlaybackController.swift */, + 97ADF582CAB9B2D6C9E091EC /* VoiceRecorder.swift */, + 604A4F1FAB5820C300C709D5 /* VoiceRecordView.swift */, + 687E86C7ECD479328FAEA31A /* WaveformBars.swift */, + 446700A42BE8453DA4038145 /* WaveformPacker.swift */, + ); + path = Messages; + sourceTree = ""; + }; + BAA50B5855347906BB59F600 /* Auth */ = { + isa = PBXGroup; + children = ( + 4A6F6CB0F23C3C64FB122393 /* ClosedView.swift */, + 683723ECC14D43FE1FE4D448 /* ErrorView.swift */, + 93535C2021AB2E7B17F593AA /* LoadingView.swift */, + 3E86853697764EDEB0866DA2 /* PasswordEntryView.swift */, + CEA96A6FDE6FBC1F2054E82A /* QrLinkPublisher.swift */, + 5D3A8FBA5EBDBFDE5AA2FFEC /* QrLoginView.swift */, + ); + path = Auth; + sourceTree = ""; + }; + FDA1198062B03EEE2B0A5D0B /* Chats */ = { + isa = PBXGroup; + children = ( + 35B32555B3686A7A4884B215 /* AvatarView.swift */, + ECA5A0C122ECD5BBB6EBBBBD /* AvatarVisual.swift */, + 9C5E645A6F575F72C3B7839D /* CachedChat.swift */, + 7B0F1DD19A57CC68887E4F98 /* ChatListKey.swift */, + CB4AA2ED987D3215C041B6A6 /* ChatListLoader.swift */, + F824052186720912B44D8263 /* ChatListStore.swift */, + 0CCDFBD19F9CAA87E938BADC /* ChatListTimestamp.swift */, + 7108EAF6A69D8D59376A8914 /* ChatListView.swift */, + ED3696C9BA38845E621A1B67 /* ChatOpener.swift */, + 3448938C1F69F0D9DE679238 /* ChatPreview.swift */, + 1932CC94F39BD57A56E59C08 /* ChatRow.swift */, + 29C2B6C3E8643BAF27074570 /* ChatRowView.swift */, + 2C34577A65DD579BCCD1888C /* FolderPill.swift */, + 45F5EE457DA36FD2FCDF6562 /* FolderPillBar.swift */, + ); + path = Chats; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F8E880AF01A492CCA4C21162 /* tgwatch Watch App */ = { + isa = PBXNativeTarget; + buildConfigurationList = DF46AE8A5A09DDD35D3F12A9 /* Build configuration list for PBXNativeTarget "tgwatch Watch App" */; + buildPhases = ( + B87FE120EF64E5DF7200423F /* Sources */, + ADF2777D4E127E36F6AE03F4 /* Resources */, + FD92F7152AACBC99210DE5F1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "tgwatch Watch App"; + packageProductDependencies = ( + 36B07FC482F9E6A9D62DB94F /* TDShim */, + FD6A04697FEA6C4614973F19 /* QRCodeGenerator */, + 5083D73573D84E386C2E3B4C /* RLottieKit */, + F573B77B0568E47F98C5A063 /* WebPKit */, + 4A4F9C233953209CE4C3DB1A /* OpusKit */, + ); + productName = "tgwatch Watch App"; + productReference = F72CE4C1520F9330AFDE2D07 /* tgwatch Watch App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B9A9B73A2396AB3923E4BCB8 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + F8E880AF01A492CCA4C21162 = { + DevelopmentTeam = C67CF9S4VU; + }; + }; + }; + buildConfigurationList = F853966166335BEDE0C668A0 /* Build configuration list for PBXProject "tgwatch" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 67CACE3D39996E520610926F; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + 3238C4183C24EDCED74DC210 /* XCRemoteSwiftPackageReference "swift-qrcode-generator" */, + A7CE5619475EAC61ADB3AD3C /* XCLocalSwiftPackageReference "Packages/OpusKit" */, + CD4B01D8F7053200FFFBF7EC /* XCLocalSwiftPackageReference "Packages/RLottieKit" */, + 4CFFE520C87190BF6D6872EC /* XCLocalSwiftPackageReference "Packages/TDShim" */, + 32CBECA1903E29ECBF55A787 /* XCLocalSwiftPackageReference "Packages/WebPKit" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 6BB635CC5B892659A3E61172 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F8E880AF01A492CCA4C21162 /* tgwatch Watch App */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ADF2777D4E127E36F6AE03F4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F7096697A6319D8AE9047C1E /* Assets.xcassets in Resources */, + 1D4E75A6F594617ACD668616 /* SampleStickers in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B87FE120EF64E5DF7200423F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D1502C120B3AF8B91C1A91D /* AVRecordingBackend.swift in Sources */, + 0BE4BEE5802CD229F61FF464 /* Account.swift in Sources */, + 1F7CEC07D0DBC5FD130AF942 /* AccountBootstrapView.swift in Sources */, + 7059458F1317797724013AC8 /* AccountListProjection.swift in Sources */, + AAE21A407D0201F0A8630FD9 /* AccountManager.swift in Sources */, + 8364AFF20E93D890EFEF7C41 /* AccountRegistry.swift in Sources */, + 881BA28087741D8F6B406FE9 /* AccountSwitcherSheet.swift in Sources */, + 20863684FA2F51495BB2A9A3 /* AccountsListView.swift in Sources */, + 039B8E2B7418D9004EAF9533 /* AttachmentSheet.swift in Sources */, + D1E96E63A3AE93646AD3F532 /* AudioBubbleView.swift in Sources */, + FB3861C5B3036FACA2188A43 /* AudioPlaybackController.swift in Sources */, + F7D46529A75FEBA7AEE058A8 /* AudioVisual.swift in Sources */, + 750C266D413747DF23D80270 /* AuthState.swift in Sources */, + 0BB25527B21E71352126154B /* AuthStateMapping.swift in Sources */, + 5E9A6B94F1B917CB849B3EBC /* AvatarView.swift in Sources */, + 03074928DC54019BE6106ECD /* AvatarVisual.swift in Sources */, + 4236CA9B5AA90ACEB17814DC /* BubbleMetrics.swift in Sources */, + CBA3FEDEEA413559F700B6E3 /* BubbleShape.swift in Sources */, + 868426A2768FE7349CD4AAAD /* BubbleStyle.swift in Sources */, + 4505C5628AF9258938D623D7 /* CachedChat.swift in Sources */, + C6A64A26FC7DC74BDEC91308 /* ChatHistoryLoader.swift in Sources */, + F44AC47D6D4816D1709F0460 /* ChatHistoryStore.swift in Sources */, + FB05A240DD234EA077E9B692 /* ChatListKey.swift in Sources */, + 832059B1E6AF007167F5EAC0 /* ChatListLoader.swift in Sources */, + 81C79089068A1B436DFFD2EF /* ChatListStore.swift in Sources */, + 3679D6EA591522207E69F8A5 /* ChatListTimestamp.swift in Sources */, + 4938F79EA33D5C8041A659C3 /* ChatListView.swift in Sources */, + 2E2B46A1FFFDC24F47FF33D4 /* ChatOpener.swift in Sources */, + A8EF6F72C78B619893C05AFE /* ChatPreview.swift in Sources */, + E48523AFA2E23409CC0C2C2C /* ChatRow.swift in Sources */, + 7BF1C89F67290EBAF50900F8 /* ChatRowView.swift in Sources */, + 04CEF3F9C267D8A7CEC5EC3C /* ClosedView.swift in Sources */, + EF499261BE49A6B1C7B3B1F4 /* ContentView.swift in Sources */, + FD5515DB22C571F4D3E5F5FA /* DaySeparatorView.swift in Sources */, + F5596A4EF4D2055B10B4A183 /* DocumentBubbleView.swift in Sources */, + A6179A26CB7FB298567682E6 /* DocumentVisual.swift in Sources */, + 62E23F09E892DE847642EDB1 /* EmojiText.swift in Sources */, + 6DD41F6FCB2E2F6E2A231059 /* ErrorMapping.swift in Sources */, + 576608622AFF98B6FF84C161 /* ErrorView.swift in Sources */, + F8F834C103DB1A687BFD54B8 /* FolderPill.swift in Sources */, + EE2D34F61EAF165651914325 /* FolderPillBar.swift in Sources */, + D1082945B5785B08F820FB78 /* LoadingView.swift in Sources */, + 9FC324F2FEBBDF3C24360CA3 /* LocationBubbleView.swift in Sources */, + 6E676E1D3C5066A6590C725C /* LocationProvider.swift in Sources */, + 8428660F7B4DF838568EBB8C /* LocationSendController.swift in Sources */, + 8F863F799330257D25F04D64 /* LocationSendView.swift in Sources */, + C2FC5C2F1B11E8B83F7FF203 /* LocationVisual.swift in Sources */, + 11F6B5A10EFBBAFB10C8DBFE /* LottieAnimationView.swift in Sources */, + C62226963E54795B2808A52C /* MapSnapshotRenderer.swift in Sources */, + 6A7C61DC675315A5928461BC /* MessageBubbleView.swift in Sources */, + 39A386FFBD00EEE14B2B33D5 /* MessageFormatters.swift in Sources */, + DC1BB1D3B075945C5668EC61 /* MessageListView.swift in Sources */, + 3BA42E87FCC662196BB80B64 /* MessageRow.swift in Sources */, + 6AA9F0DA20077AB00256E225 /* MessageRowView.swift in Sources */, + 06031F19E55E96E43173C712 /* MessageWindow.swift in Sources */, + B60D32384AEF65DF99757144 /* PasswordEntryView.swift in Sources */, + A121DA7622C910B267D69B6E /* PhotoBubbleView.swift in Sources */, + E037E3EA9827C286D724E0C0 /* PhotoViewerView.swift in Sources */, + 1773B32101D7CB51E65BE91B /* PhotoVisual.swift in Sources */, + 488B0EBEDE035351B1E649CB /* PollBubbleView.swift in Sources */, + AA3F067A7BC4CDA579CFFC39 /* PollVisual.swift in Sources */, + 25F6B3EFED8EDDD9983CB0F7 /* PollVoteView.swift in Sources */, + F95C046A6C043344F0955815 /* QrLinkPublisher.swift in Sources */, + B9F6D473AA0F1DAF77799E77 /* QrLoginView.swift in Sources */, + A0899809BBC92D1563AC4160 /* ReplyBar.swift in Sources */, + FC110506EAA6FA010E83F407 /* ReplyFormatters.swift in Sources */, + 641846C985430AD34793F0D3 /* ReplyHeader.swift in Sources */, + C3DFBB28315AE47818CE77F0 /* ReplyHeaderView.swift in Sources */, + 6C6F45616840D74520FFA5D3 /* ReviewVoiceBubble.swift in Sources */, + 20167DF49E7A2E543B274E76 /* Secrets.swift in Sources */, + B8FB9BDD812396930990235C /* SecretsValidator.swift in Sources */, + C1DD63C4AA3FED433C0B2830 /* ServiceLineFormatter.swift in Sources */, + A7967495A54936FE19218DC4 /* ServiceMessageView.swift in Sources */, + 5DF5B3342BFBC50C1E211068 /* StickerBubbleView.swift in Sources */, + 6475073E5937A447F70599A8 /* StickerCellView.swift in Sources */, + 44630BDE4B93AF13B9903093 /* StickerPicker.swift in Sources */, + C94BD040D985FC0090CDBF84 /* StickerPickerLoader.swift in Sources */, + EB1486FA24BEE9E973D9679C /* StickerPickerStore.swift in Sources */, + 7D835D5CE3C3B58799AFAF36 /* StickerPickerView.swift in Sources */, + 99F0C429392E04F83F9935B2 /* StickerRow.swift in Sources */, + 8AFE89397E88D6FD3BDB39E6 /* StickerSetDetailView.swift in Sources */, + 546D2929CAE276CCCDA61287 /* StickerVisual.swift in Sources */, + DDB206E2134B52F472D5E9FE /* TDClient.swift in Sources */, + 787D692CF045D92B0DEFC1F2 /* TDClientFactory.swift in Sources */, + B14FB831F58FBE797B2E5E22 /* UnreadDividerView.swift in Sources */, + B79AB6E8675A1A32B8DD9E4A /* UnsupportedBubbleView.swift in Sources */, + DD9FB33F951CC225F8D5B3EE /* UserNamesStore.swift in Sources */, + 939315A8C737DB06ED040E4F /* VideoBubbleView.swift in Sources */, + 074BE3F4D6AB9EA220A8EDF2 /* VideoNoteBubbleView.swift in Sources */, + 80C0C6603FDC96FE26ED7658 /* VideoNotePlayerView.swift in Sources */, + C00221119F0E81D881582EEE /* VideoNoteVisual.swift in Sources */, + 79A01936CFB9168EC5ABD641 /* VideoPlayerView.swift in Sources */, + 4546DBC7A3577AA688A7069C /* VideoVisual.swift in Sources */, + 0EC6EE1E219B9104BED3A416 /* VoiceEditorWaveform.swift in Sources */, + 5D217805BCCE97B2A4B14F2B /* VoiceNoteBubbleView.swift in Sources */, + 53EE51754BE717246108A9F6 /* VoiceNoteVisual.swift in Sources */, + 7DD192512B16EA3515244C22 /* VoicePlaybackController.swift in Sources */, + B777F8B2ACBABCC223BE25A4 /* VoiceRecordView.swift in Sources */, + 93A8BCCF8EE61D81215359A9 /* VoiceRecorder.swift in Sources */, + C3AD58C53B02CCF230E13BC2 /* WaveformBars.swift in Sources */, + 00786742690F94371FB93353 /* WaveformPacker.swift in Sources */, + 69AC5285D7EC946091521E65 /* WebPStickerView.swift in Sources */, + 9D232B81EAE7720972261B34 /* tgwatchApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 39C4482A90B52800816F008E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + DEVELOPMENT_TEAM = C67CF9S4VU; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "tgwatch Watch App/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = ph.telegra.Telegraph.watchkitapp; + SDKROOT = watchos; + SKIP_INSTALL = YES; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; + }; + 635C611536F7517270679CF7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; + CODE_SIGN_IDENTITY = ""; + DEAD_CODE_STRIPPING = YES; + DEPLOYMENT_POSTPROCESSING = YES; + DEVELOPMENT_TEAM = C67CF9S4VU; + GENERATE_INFOPLIST_FILE = NO; + INFOPLIST_FILE = "tgwatch Watch App/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = ph.telegra.Telegraph.watchkitapp; + SDKROOT = watchos; + SKIP_INSTALL = YES; + STRIP_INSTALLED_PRODUCT = YES; + STRIP_STYLE = all; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Release; + }; + 7E3E01929E98BBC37E0BA951 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MARKETING_VERSION = 0.1; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = minimal; + SWIFT_VERSION = 5.0; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Debug; + }; + B2C8C86A9AD192F4034B5B33 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MARKETING_VERSION = 0.1; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_STRICT_CONCURRENCY = minimal; + SWIFT_VERSION = 5.0; + WATCHOS_DEPLOYMENT_TARGET = 26.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DF46AE8A5A09DDD35D3F12A9 /* Build configuration list for PBXNativeTarget "tgwatch Watch App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 39C4482A90B52800816F008E /* Debug */, + 635C611536F7517270679CF7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + F853966166335BEDE0C668A0 /* Build configuration list for PBXProject "tgwatch" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E3E01929E98BBC37E0BA951 /* Debug */, + B2C8C86A9AD192F4034B5B33 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 32CBECA1903E29ECBF55A787 /* XCLocalSwiftPackageReference "Packages/WebPKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/WebPKit; + }; + 4CFFE520C87190BF6D6872EC /* XCLocalSwiftPackageReference "Packages/TDShim" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/TDShim; + }; + A7CE5619475EAC61ADB3AD3C /* XCLocalSwiftPackageReference "Packages/OpusKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/OpusKit; + }; + CD4B01D8F7053200FFFBF7EC /* XCLocalSwiftPackageReference "Packages/RLottieKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Packages/RLottieKit; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 3238C4183C24EDCED74DC210 /* XCRemoteSwiftPackageReference "swift-qrcode-generator" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fwcd/swift-qrcode-generator"; + requirement = { + kind = exactVersion; + version = 2.0.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 36B07FC482F9E6A9D62DB94F /* TDShim */ = { + isa = XCSwiftPackageProductDependency; + productName = TDShim; + }; + 4A4F9C233953209CE4C3DB1A /* OpusKit */ = { + isa = XCSwiftPackageProductDependency; + productName = OpusKit; + }; + 5083D73573D84E386C2E3B4C /* RLottieKit */ = { + isa = XCSwiftPackageProductDependency; + productName = RLottieKit; + }; + F573B77B0568E47F98C5A063 /* WebPKit */ = { + isa = XCSwiftPackageProductDependency; + productName = WebPKit; + }; + FD6A04697FEA6C4614973F19 /* QRCodeGenerator */ = { + isa = XCSwiftPackageProductDependency; + package = 3238C4183C24EDCED74DC210 /* XCRemoteSwiftPackageReference "swift-qrcode-generator" */; + productName = QRCodeGenerator; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = B9A9B73A2396AB3923E4BCB8 /* Project object */; +} diff --git a/Telegram/WatchApp/tgwatch.xcodeproj/xcshareddata/xcschemes/tgwatch Watch App.xcscheme b/Telegram/WatchApp/tgwatch.xcodeproj/xcshareddata/xcschemes/tgwatch Watch App.xcscheme new file mode 100644 index 0000000000..bb770bc965 --- /dev/null +++ b/Telegram/WatchApp/tgwatch.xcodeproj/xcshareddata/xcschemes/tgwatch Watch App.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Telegram/prebuilt_watchos.bzl b/Telegram/prebuilt_watchos.bzl new file mode 100644 index 0000000000..3002c45892 --- /dev/null +++ b/Telegram/prebuilt_watchos.bzl @@ -0,0 +1,144 @@ +"""Embeds the standalone, xcodebuild-built tgwatch watch app into the Bazel iOS build. + +`apple_prebuilt_watchos_application` runs `xcodebuild` (via prebuilt_watchos_build.sh) +against an exported tgwatch source tree, optionally codesigns the result, and exposes +it through the providers that `ios_application(watch_application = ...)` consumes: + + * AppleBundleInfo — bundle metadata (the host reads only `.product_type`). + * AppleEmbeddableInfo — `watch_bundles` (the zipped .app placed under Watch/). + +The watch source tree is the committed in-repo snapshot at `Telegram/WatchApp/` (tracked +inputs). To update it, re-sync from the standalone tgwatch repo via +`tgwatch/tools/export-sources.sh`. + +Notes on the rules_apple providers used here: + * AppleBundleInfo's public init is banned; we build it with the internal raw + initializer `new_applebundleinfo` (rules_apple is vendored + pinned in this repo, + so depending on the internal label is safe). +""" + +load( + "@build_bazel_rules_apple//apple/internal:providers.bzl", + "new_applebundleinfo", + "new_watchosapplicationbundleinfo", +) +load("@build_bazel_rules_apple//apple/internal/providers:embeddable_info.bzl", "AppleEmbeddableInfo") + +def _apple_prebuilt_watchos_application_impl(ctx): + # The watch app is built from the committed in-repo snapshot at Telegram/WatchApp, + # tracked as inputs (incremental + cacheable). + source_path = ctx.attr.in_repo_source_dir + api_id = ctx.var.get("watchApiId", "0") + api_hash = ctx.var.get("watchApiHash", "placeholder") + identity = ctx.var.get("watchSigningIdentity", "") + + # The provisioning profile is an external, machine-specific absolute path passed via + # --define rather than a Bazel label, so the gitignored profile need not be exposed as + # a target. The local action reads it directly. Empty => unsigned build; when set but + # the identity is empty, the worker derives the signing identity from the profile. + profile = ctx.var.get("watchProvisioningProfile", "") + # The embedded watch app's CFBundleShortVersionString / CFBundleVersion must match + # the host app, or rules_apple's child-version verification fails. Source the + # marketing version from versions.json (same as the host's VersionInfoPlist) and the + # build version from buildNumber (Make.py always emits --define=buildNumber). + build_number = ctx.var.get("buildNumber", "1") + archive = ctx.actions.declare_file(ctx.label.name + ".zip") + # The host ios_application reads the watch app's Info.plist (via AppleBundleInfo.infoplist) + # to verify WKCompanionAppBundleIdentifier against the host bundle id, so expose it as a + # separate output (resources.bzl bundle_verification crashes on a None infoplist). + infoplist = ctx.actions.declare_file(ctx.label.name + "_Info.plist") + + # Track the in-repo snapshot so the watch build re-runs only when it changes. + inputs = [ctx.file._worker, ctx.file.versions_json] + ctx.files.srcs + exec_requirements = { + "no-sandbox": "1", + "no-remote": "1", + "local": "1", + "requires-network": "1", + } + + ctx.actions.run( + executable = "/bin/bash", + arguments = [ + ctx.file._worker.path, + source_path, + archive.path, + api_id, + api_hash, + identity, + profile, + infoplist.path, + ctx.file.versions_json.path, + build_number, + # Watch app bundle id (".watchkitapp"). xcodebuild bakes it as + # PRODUCT_BUNDLE_IDENTIFIER so the signed CFBundleIdentifier matches the host + # config; the Info.plist derives WKCompanionAppBundleIdentifier from it via + # $(PRODUCT_BUNDLE_IDENTIFIER:base). Keeps the build dynamic across hosts with + # no post-build plist mutation (xcodebuild bakes, the worker signs once). + ctx.attr.bundle_id, + ], + inputs = inputs, + outputs = [archive, infoplist], + mnemonic = "PrebuiltWatchosBuild", + progress_message = "Building%s watch app via xcodebuild" % (" + signing" if profile else ""), + execution_requirements = exec_requirements, + use_default_shell_env = True, + ) + + return [ + DefaultInfo(files = depset([archive])), + new_applebundleinfo( + archive = archive, + bundle_id = ctx.attr.bundle_id, + bundle_name = ctx.attr.bundle_name, + bundle_extension = ".app", + platform_type = "watchos", + # Must be a single-target watchOS app (NOT watch2_application) so the host + # skips the watchos_stub partial (see ios_rules.bzl product_type check). + product_type = "com.apple.product-type.application", + minimum_os_version = ctx.attr.minimum_os_version, + minimum_deployment_os_version = ctx.attr.minimum_os_version, + infoplist = infoplist, + binary = None, + entitlements = None, + # Best-effort constant; the host ios_application reads only product_type. + uses_swift = True, + extension_safe = False, + ), + # Marker provider required by ios_application's watch_application attr + # (providers = [[AppleBundleInfo, WatchosApplicationBundleInfo]]). + new_watchosapplicationbundleinfo(), + AppleEmbeddableInfo( + # The signed (or unsigned) .app archive, expanded into the host's Watch/ section. + watch_bundles = depset([archive]), + # Empty: the worker signs everything inside the watch app itself. + signed_frameworks = depset(), + ), + ] + +apple_prebuilt_watchos_application = rule( + implementation = _apple_prebuilt_watchos_application_impl, + attrs = { + "bundle_id": attr.string(default = "ph.telegra.Telegraph.watchkitapp"), + "bundle_name": attr.string(default = "tgwatch Watch App"), + "minimum_os_version": attr.string(default = "26.0"), + "srcs": attr.label( + default = "//Telegram/WatchApp:sources", + allow_files = True, + doc = "Committed in-repo watch source snapshot (tracked inputs).", + ), + "in_repo_source_dir": attr.string( + default = "Telegram/WatchApp", + doc = "Execroot-relative path to the committed snapshot (must match the package of 'srcs').", + ), + "versions_json": attr.label( + allow_single_file = True, + default = "//:versions.json", + doc = "Source of the marketing version (key 'app'), kept in sync with the host app.", + ), + "_worker": attr.label( + default = "//Telegram:prebuilt_watchos_build.sh", + allow_single_file = True, + ), + }, +) diff --git a/Telegram/prebuilt_watchos_build.sh b/Telegram/prebuilt_watchos_build.sh new file mode 100755 index 0000000000..9acc2fd93e --- /dev/null +++ b/Telegram/prebuilt_watchos_build.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# Worker for the apple_prebuilt_watchos_application Bazel rule. +# +# Builds the tgwatch watch app via xcodebuild (device, Release, UNSIGNED), then +# — if a provisioning profile is supplied — codesigns the app and its nested +# frameworks with the watchkitapp provisioning profile and a matching identity, +# and finally zips the .app into the rule's output archive. +# +# The host ios_application embeds this archive under Watch/ and re-seals the host; +# it does NOT re-sign the watch app, so the watch signing must happen here. +# +# Args: +# $1 source_path Execroot-relative path to the committed in-repo snapshot +# (Telegram/WatchApp), which contains tgwatch.xcodeproj. +# $2 output_zip Path (declared by Bazel) to write the .app archive to +# $3 api_id TG_API_ID build setting +# $4 api_hash TG_API_HASH build setting +# $5 identity Codesigning identity (SHA1 hash); empty => derived from $6's cert +# $6 profile Path to the watchkitapp .mobileprovision; empty => unsigned build +# $7 infoplist Path (declared by Bazel) to copy the built Info.plist to +# $8 versions_json versions.json (key 'app' => CFBundleShortVersionString) +# $9 build_number CFBundleVersion +# $10 watch_bundle_id PRODUCT_BUNDLE_IDENTIFIER for xcodebuild (the watch app id, +# ".watchkitapp"); empty => keep the project default. xcodebuild +# bakes it into CFBundleIdentifier (and signs with it); the Info.plist +# derives WKCompanionAppBundleIdentifier from it via +# $(PRODUCT_BUNDLE_IDENTIFIER:base), so no post-build plist patching. +set -euo pipefail + +SRC="$1"; OUT_ZIP="$2"; API_ID="$3"; API_HASH="$4"; IDENTITY="${5:-}"; PROFILE="${6:-}"; INFOPLIST_OUT="${7:-}"; VERSIONS_JSON="${8:-}"; BUILD_NUMBER="${9:-1}"; WATCH_BUNDLE_ID="${10:-}" + +if [ ! -e "$SRC/tgwatch.xcodeproj" ]; then + echo "error: no tgwatch.xcodeproj at $SRC (re-sync the Telegram/WatchApp snapshot via tgwatch/tools/export-sources.sh)" >&2 + exit 1 +fi + +# Match the host app's version (rules_apple requires the embedded watch app's +# CFBundleShortVersionString/CFBundleVersion to equal the parent's). +MARKETING_VERSION="0.1" +if [ -n "$VERSIONS_JSON" ]; then + MARKETING_VERSION="$(python3 -c "import json,sys; print(json.load(open(sys.argv[1]))['app'])" "$VERSIONS_JSON")" +fi + +DD="$(mktemp -d)" +trap 'rm -rf "$DD"' EXIT + +# Build from a writable copy so xcodebuild/SwiftPM never write into the (possibly +# in-repo, read-only) source tree — e.g. SwiftPM's Package.resolved or the workspace. +# The tree is small (~12M); a plain cp on each (uncached) build is acceptable. +WORKSRC="$DD/src" +mkdir -p "$WORKSRC" +cp -R "$SRC/." "$WORKSRC/" + +xcodebuild \ + -project "$WORKSRC/tgwatch.xcodeproj" \ + -scheme "tgwatch Watch App" \ + -configuration Release \ + -destination 'generic/platform=watchOS' \ + -derivedDataPath "$DD" \ + -quiet \ + CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \ + TG_API_ID="$API_ID" TG_API_HASH="$API_HASH" \ + MARKETING_VERSION="$MARKETING_VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \ + ${WATCH_BUNDLE_ID:+PRODUCT_BUNDLE_IDENTIFIER="$WATCH_BUNDLE_ID"} \ + build 1>&2 + +APP="$(find "$DD/Build/Products" -maxdepth 2 -name 'tgwatch Watch App.app' -type d | head -1)" +if [ -z "$APP" ]; then + echo "error: built watch .app not found under $DD/Build/Products" >&2 + exit 1 +fi + +# Expose the watch app's Info.plist (the host reads it to verify the companion +# bundle-id linkage). Codesigning does not alter Info.plist content, so capture it now. +if [ -n "$INFOPLIST_OUT" ]; then + cp "$APP/Info.plist" "$INFOPLIST_OUT" +fi + +if [ -n "$IDENTITY" ] && [ -z "$PROFILE" ]; then + echo "error: a signing identity was given but no provisioning profile (set --watchProvisioningProfile=)" >&2 + exit 1 +fi + +# Sign the watch app whenever a provisioning profile is available. When no explicit +# identity is supplied, derive it from the certificate embedded in that profile, so +# the watch app is signed with the same distribution/development identity as the host +# app (resolved from the shared codesigning material) — required for App Store, where +# every nested bundle must carry the Apple submission certificate. Without a profile +# the app is left unsigned (the host does not re-sign it). +if [ -n "$PROFILE" ]; then + cp "$PROFILE" "$APP/embedded.mobileprovision" + ENT="$(mktemp)" + trap 'rm -rf "$DD" "$ENT" "$ENT.plist"' EXIT + security cms -D -i "$APP/embedded.mobileprovision" > "$ENT.plist" + if ! /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' "$ENT.plist" > "$ENT" 2>/dev/null; then + echo "error: provisioning profile has no Entitlements key: $PROFILE" >&2 + exit 1 + fi + + if [ -z "$IDENTITY" ]; then + # The identity is the SHA-1 of the profile's first embedded certificate, which is + # exactly how codesign / the keychain reference it. The matching private key must + # be in the keychain (it is: the same cert signs the host app). + IDENTITY="$(python3 -c "import sys,plistlib,subprocess,hashlib; d=plistlib.loads(subprocess.run(['security','cms','-D','-i',sys.argv[1]],capture_output=True).stdout); print(hashlib.sha1(d['DeveloperCertificates'][0]).hexdigest().upper())" "$APP/embedded.mobileprovision")" + if [ -z "$IDENTITY" ]; then + echo "error: could not derive a signing identity from the provisioning profile (no DeveloperCertificates): $PROFILE" >&2 + exit 1 + fi + echo "note: signing watch app with identity $IDENTITY derived from $(basename "$PROFILE")" >&2 + fi + + # Distribution profiles (App Store / Ad Hoc) set get-task-allow=false and require a + # secure timestamp; development builds set it true and can skip the timestamp (faster, + # no round-trip to Apple's timestamp service). + TS_FLAG="--timestamp" + if /usr/libexec/PlistBuddy -c 'Print :get-task-allow' "$ENT" 2>/dev/null | grep -qi '^true$'; then + TS_FLAG="--timestamp=none" + fi + + # Sign inside-out: nested frameworks first, then the app bundle. + if [ -d "$APP/Frameworks" ]; then + for fw in "$APP/Frameworks/"*; do + [ -e "$fw" ] || continue + codesign --force "$TS_FLAG" --sign "$IDENTITY" "$fw" 1>&2 + done + fi + codesign --force "$TS_FLAG" --sign "$IDENTITY" --entitlements "$ENT" "$APP" 1>&2 + codesign --verify --deep --strict "$APP" 1>&2 +else + echo "warning: no watch provisioning profile supplied; the watch app will be UNSIGNED and will be rejected by the App Store. Pass --watchProvisioningProfile, or build with codesigning material that includes the watchkitapp profile." >&2 +fi + +# $OUT_ZIP is execroot-relative; the action's cwd is the execroot, so do NOT cd +# (that would resolve $OUT_ZIP against the DerivedData dir). --keepParent makes the +# archive root the .app itself even when $APP is an absolute path. +rm -f "$OUT_ZIP" +/usr/bin/ditto -c -k --keepParent "$APP" "$OUT_ZIP" diff --git a/build-system/BUILD b/build-system/BUILD index 5fb4a881f4..43d4c19eab 100644 --- a/build-system/BUILD +++ b/build-system/BUILD @@ -4,6 +4,22 @@ config_setting( values = {"cpu": "ios_sim_arm64"}, ) +config_setting( + name = "linux_arm64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + ], +) + +config_setting( + name = "linux_x86_64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) + exports_files([ "GenerateStrings/GenerateStrings.py", ]) diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py index a98452ae47..8f8e22860a 100644 --- a/build-system/Make/BuildConfiguration.py +++ b/build-system/Make/BuildConfiguration.py @@ -106,13 +106,7 @@ def decrypt_codesigning_directory_recursively(source_base_path, destination_base destination_path = destination_base_path + '/' + file_name allowed_file_extensions = ['.mobileprovision', '.cer', '.p12'] if os.path.isfile(source_path) and any(source_path.endswith(ext) for ext in allowed_file_extensions): - #print('Decrypting {} to {} with {}'.format(source_path, destination_path, password)) - os.system('ruby build-system/decrypt.rb "{password}" "{source_path}" "{destination_path}"'.format( - password=password, - source_path=source_path, - destination_path=destination_path - )) - #decrypt_match_data(source_path, destination_path, password) + decrypt_match_data(source_path, destination_path, password) elif os.path.isdir(source_path): os.makedirs(destination_path, exist_ok=True) decrypt_codesigning_directory_recursively(source_path, destination_path, password) diff --git a/build-system/Make/DecryptMatch.py b/build-system/Make/DecryptMatch.py index b01ea6c3f2..35c65195ce 100644 --- a/build-system/Make/DecryptMatch.py +++ b/build-system/Make/DecryptMatch.py @@ -1,221 +1,293 @@ -import os import base64 -import subprocess -import tempfile import hashlib -class EncryptionV1: - ALGORITHM = 'aes-256-cbc' - def decrypt(self, encrypted_data, password, salt, hash_algorithm="MD5"): - try: - return self._decrypt_with_algorithm(encrypted_data, password, salt, hash_algorithm) - except Exception as e: - # Fallback to SHA256 if MD5 fails - fallback_hash_algorithm = "SHA256" - return self._decrypt_with_algorithm(encrypted_data, password, salt, fallback_hash_algorithm) +# FIPS-197 AES S-box and inverse S-box. +_SBOX = bytes.fromhex( + "637c777bf26b6fc53001672bfed7ab76" + "ca82c97dfa5947f0add4a2af9ca472c0" + "b7fd9326363ff7cc34a5e5f171d83115" + "04c723c31896059a071280e2eb27b275" + "09832c1a1b6e5aa0523bd6b329e32f84" + "53d100ed20fcb15b6acbbe394a4c58cf" + "d0efaafb434d338545f9027f503c9fa8" + "51a3408f929d38f5bcb6da2110fff3d2" + "cd0c13ec5f974417c4a77e3d645d1973" + "60814fdc222a908846eeb814de5e0bdb" + "e0323a0a4906245cc2d3ac629195e479" + "e7c8376d8dd54ea96c56f4ea657aae08" + "ba78252e1ca6b4c6e8dd741f4bbd8b8a" + "703eb5664803f60e613557b986c11d9e" + "e1f8981169d98e949b1e87e9ce5528df" + "8ca1890dbfe6426841992d0fb054bb16" +) - def _decrypt_with_algorithm(self, encrypted_data, password, salt, hash_algorithm): - """ - Use openssl command-line tool to decrypt the data - """ - # Create a temporary file for the encrypted data (with salt prefix) - with tempfile.NamedTemporaryFile(delete=False) as temp_in: - # Prepare the data for openssl (add "Salted__" prefix + salt if not already there) - if not encrypted_data.startswith(b"Salted__"): - temp_in.write(b"Salted__" + salt + encrypted_data) +_INV_SBOX = bytes.fromhex( + "52096ad53036a538bf40a39e81f3d7fb" + "7ce339829b2fff87348e4344c4dee9cb" + "547b9432a6c2233dee4c950b42fac34e" + "082ea16628d924b2765ba2496d8bd125" + "72f8f66486689816d4a45ccc5d65b692" + "6c704850fdedb9da5e154657a78d9d84" + "90d8ab008cbcd30af7e45805b8b34506" + "d02c1e8fca3f0f02c1afbd0301138a6b" + "3a9111414f67dcea97f2cfcef0b4e673" + "96ac7422e7ad3585e2f937e81c75df6e" + "47f11a711d29c5896fb7620eaa18be1b" + "fc563e4bc6d279209adbc0fe78cd5af4" + "1fdda8338807c731b11210592780ec5f" + "60517fa919b54a0d2de57a9f93c99cef" + "a0e03b4dae2af5b0c8ebbb3c83539961" + "172b047eba77d626e169146355210c7d" +) + +_RCON = bytes.fromhex("01020408102040801b36") + + +def _xtime(a): + return (((a << 1) ^ 0x1b) & 0xff) if (a & 0x80) else (a << 1) + + +def _gf_mul(a, b): + r = 0 + for _ in range(8): + if b & 1: + r ^= a + b >>= 1 + a = _xtime(a) + return r + + +def _key_expansion_256(key): + # AES-256: Nk=8, Nr=14, total 4 * (Nr + 1) = 60 words = 240 bytes. + if len(key) != 32: + raise ValueError("AES-256 key must be 32 bytes") + w = bytearray(240) + w[:32] = key + i = 32 + while i < 240: + t = bytearray(w[i - 4:i]) + if i % 32 == 0: + t = bytearray([t[1], t[2], t[3], t[0]]) + for j in range(4): + t[j] = _SBOX[t[j]] + t[0] ^= _RCON[i // 32 - 1] + elif i % 32 == 16: + for j in range(4): + t[j] = _SBOX[t[j]] + for j in range(4): + w[i + j] = w[i - 32 + j] ^ t[j] + i += 4 + return [bytes(w[r * 16:(r + 1) * 16]) for r in range(15)] + + +def _add_round_key(state, rk): + return bytes(s ^ k for s, k in zip(state, rk)) + + +def _sub_bytes(state): + return bytes(_SBOX[b] for b in state) + + +def _inv_sub_bytes(state): + return bytes(_INV_SBOX[b] for b in state) + + +# Column-major state: state[r + 4 * c], r = 0..3 (row), c = 0..3 (column). +def _shift_rows(state): + s = bytearray(state) + s[1], s[5], s[9], s[13] = s[5], s[9], s[13], s[1] + s[2], s[6], s[10], s[14] = s[10], s[14], s[2], s[6] + s[3], s[7], s[11], s[15] = s[15], s[3], s[7], s[11] + return bytes(s) + + +def _inv_shift_rows(state): + s = bytearray(state) + s[1], s[5], s[9], s[13] = s[13], s[1], s[5], s[9] + s[2], s[6], s[10], s[14] = s[10], s[14], s[2], s[6] + s[3], s[7], s[11], s[15] = s[7], s[11], s[15], s[3] + return bytes(s) + + +def _mix_columns(state): + s = bytearray(16) + for c in range(4): + a0, a1, a2, a3 = state[4 * c], state[4 * c + 1], state[4 * c + 2], state[4 * c + 3] + s[4 * c] = _xtime(a0) ^ (_xtime(a1) ^ a1) ^ a2 ^ a3 + s[4 * c + 1] = a0 ^ _xtime(a1) ^ (_xtime(a2) ^ a2) ^ a3 + s[4 * c + 2] = a0 ^ a1 ^ _xtime(a2) ^ (_xtime(a3) ^ a3) + s[4 * c + 3] = (_xtime(a0) ^ a0) ^ a1 ^ a2 ^ _xtime(a3) + return bytes(s) + + +def _inv_mix_columns(state): + s = bytearray(16) + for c in range(4): + a0, a1, a2, a3 = state[4 * c], state[4 * c + 1], state[4 * c + 2], state[4 * c + 3] + s[4 * c] = _gf_mul(a0, 0x0e) ^ _gf_mul(a1, 0x0b) ^ _gf_mul(a2, 0x0d) ^ _gf_mul(a3, 0x09) + s[4 * c + 1] = _gf_mul(a0, 0x09) ^ _gf_mul(a1, 0x0e) ^ _gf_mul(a2, 0x0b) ^ _gf_mul(a3, 0x0d) + s[4 * c + 2] = _gf_mul(a0, 0x0d) ^ _gf_mul(a1, 0x09) ^ _gf_mul(a2, 0x0e) ^ _gf_mul(a3, 0x0b) + s[4 * c + 3] = _gf_mul(a0, 0x0b) ^ _gf_mul(a1, 0x0d) ^ _gf_mul(a2, 0x09) ^ _gf_mul(a3, 0x0e) + return bytes(s) + + +def _aes_encrypt_block(block, round_keys): + state = _add_round_key(block, round_keys[0]) + for r in range(1, 14): + state = _sub_bytes(state) + state = _shift_rows(state) + state = _mix_columns(state) + state = _add_round_key(state, round_keys[r]) + state = _sub_bytes(state) + state = _shift_rows(state) + state = _add_round_key(state, round_keys[14]) + return state + + +def _aes_decrypt_block(block, round_keys): + state = _add_round_key(block, round_keys[14]) + for r in range(13, 0, -1): + state = _inv_shift_rows(state) + state = _inv_sub_bytes(state) + state = _add_round_key(state, round_keys[r]) + state = _inv_mix_columns(state) + state = _inv_shift_rows(state) + state = _inv_sub_bytes(state) + state = _add_round_key(state, round_keys[0]) + return state + + +def _evp_bytes_to_key(password, salt, hash_name, key_len=32, iv_len=16): + # OpenSSL EVP_BytesToKey with count=1, matching Ruby's + # Cipher#pkcs5_keyivgen(password, salt, 1, hash). + if isinstance(password, str): + password = password.encode('utf-8') + required = key_len + iv_len + material = b"" + prev = b"" + while len(material) < required: + h = hashlib.new(hash_name) + h.update(prev + password + salt) + prev = h.digest() + material += prev + return material[:key_len], material[key_len:key_len + iv_len] + + +def _aes_cbc_decrypt(ciphertext, key, iv): + if len(ciphertext) == 0 or len(ciphertext) % 16 != 0: + raise ValueError("V1 ciphertext length must be a non-zero multiple of 16") + round_keys = _key_expansion_256(key) + out = bytearray() + prev = iv + for i in range(0, len(ciphertext), 16): + block = ciphertext[i:i + 16] + decrypted = _aes_decrypt_block(block, round_keys) + out.extend(bytes(d ^ p for d, p in zip(decrypted, prev))) + prev = block + pad = out[-1] + if pad < 1 or pad > 16 or not all(b == pad for b in out[-pad:]): + raise ValueError("V1 PKCS#7 padding check failed") + return bytes(out[:-pad]) + + +def _ghash(h_bytes, data): + # GHASH over GF(2^128) with reduction polynomial x^128 + x^7 + x^2 + x + 1, + # using GCM's bit-reversed convention (top-bit-first when encoded as bytes). + h = int.from_bytes(h_bytes, 'big') + y = 0 + reduction = 0xe1 << 120 + for i in range(0, len(data), 16): + block = data[i:i + 16].ljust(16, b"\x00") + y ^= int.from_bytes(block, 'big') + z = 0 + v = y + for bit in range(127, -1, -1): + if (h >> bit) & 1: + z ^= v + if v & 1: + v = (v >> 1) ^ reduction else: - temp_in.write(encrypted_data) - temp_in_path = temp_in.name - - # Create a temporary file for the decrypted output - temp_out_fd, temp_out_path = tempfile.mkstemp() - os.close(temp_out_fd) - + v >>= 1 + y = z + return y.to_bytes(16, 'big') + + +def _aes_gcm_decrypt(ciphertext, key, iv, aad, auth_tag): + if len(iv) != 12: + raise ValueError("V2 requires a 96-bit IV") + round_keys = _key_expansion_256(key) + H = _aes_encrypt_block(b"\x00" * 16, round_keys) + j0 = iv + b"\x00\x00\x00\x01" + + plaintext = bytearray() + j0_int = int.from_bytes(j0, 'big') + mask32 = (1 << 32) - 1 + counter_high = j0_int & ~mask32 + counter_low = j0_int & mask32 + n_blocks = (len(ciphertext) + 15) // 16 + for i in range(n_blocks): + counter_low = (counter_low + 1) & mask32 + ctr_bytes = (counter_high | counter_low).to_bytes(16, 'big') + keystream = _aes_encrypt_block(ctr_bytes, round_keys) + block = ciphertext[i * 16:(i + 1) * 16] + plaintext.extend(bytes(c ^ k for c, k in zip(block, keystream[:len(block)]))) + + aad_pad = b"\x00" * ((16 - len(aad) % 16) % 16) + ct_pad = b"\x00" * ((16 - len(ciphertext) % 16) % 16) + length_block = (len(aad) * 8).to_bytes(8, 'big') + (len(ciphertext) * 8).to_bytes(8, 'big') + s = _ghash(H, aad + aad_pad + ciphertext + ct_pad + length_block) + e_j0 = _aes_encrypt_block(j0, round_keys) + computed_tag = bytes(a ^ b for a, b in zip(s, e_j0)) + if computed_tag != auth_tag: + raise ValueError("V2 GCM auth tag mismatch") + return bytes(plaintext) + + +_V1_PREFIX = b"Salted__" +_V2_PREFIX = b"match_encrypted_v2__" + + +def _decrypt_stored(stored_data, password): + if stored_data.startswith(_V2_PREFIX): + salt = stored_data[20:28] + auth_tag = stored_data[28:44] + ciphertext = stored_data[44:] + material = hashlib.pbkdf2_hmac( + 'sha256', + password.encode('utf-8'), + salt, + 10_000, + dklen=32 + 12 + 24, + ) + key = material[0:32] + iv = material[32:44] + aad = material[44:68] + return _aes_gcm_decrypt(ciphertext, key, iv, aad, auth_tag) + if stored_data.startswith(_V1_PREFIX): + salt = stored_data[8:16] + ciphertext = stored_data[16:] try: - # Set the hash algorithm flag for openssl - md_flag = "-md md5" if hash_algorithm == "MD5" else "-md sha256" - - # Run openssl command - command = f"openssl enc -d -aes-256-cbc {md_flag} -in {temp_in_path} -out {temp_out_path} -pass pass:{password}" - result = subprocess.run(command, shell=True, check=True, stderr=subprocess.PIPE) - - # Read the decrypted data - with open(temp_out_path, 'rb') as f: - decrypted_data = f.read() - - return decrypted_data - except subprocess.CalledProcessError as e: - raise ValueError(f"OpenSSL decryption failed: {e.stderr.decode()}") - finally: - # Clean up temporary files - if os.path.exists(temp_in_path): - os.unlink(temp_in_path) - if os.path.exists(temp_out_path): - os.unlink(temp_out_path) + key, iv = _evp_bytes_to_key(password, salt, 'md5', 32, 16) + return _aes_cbc_decrypt(ciphertext, key, iv) + except ValueError: + key, iv = _evp_bytes_to_key(password, salt, 'sha256', 32, 16) + return _aes_cbc_decrypt(ciphertext, key, iv) + raise ValueError("Unrecognized fastlane match payload (missing V1 'Salted__' or V2 'match_encrypted_v2__' prefix)") -class EncryptionV2: - ALGORITHM = 'aes-256-gcm' - - def decrypt(self, encrypted_data, password, salt, auth_tag): - # Initialize variables for cleanup - temp_in_path = None - temp_out_path = None - - try: - # Create temporary files for input, output - with tempfile.NamedTemporaryFile(delete=False) as temp_in: - temp_in.write(encrypted_data) - temp_in_path = temp_in.name - - temp_out_fd, temp_out_path = tempfile.mkstemp() - os.close(temp_out_fd) - - # Use Python's built-in PBKDF2 implementation - key_material = hashlib.pbkdf2_hmac( - 'sha256', - password.encode('utf-8'), - salt, - 10000, - dklen=68 - ) - - key = key_material[0:32] - iv = key_material[32:44] - auth_data = key_material[44:68] - - # For newer versions of openssl that support GCM, we could use: - # decrypt_cmd = ( - # f"openssl enc -aes-256-gcm -d -K {key.hex()} -iv {iv.hex()} " - # f"-in {temp_in_path} -out {temp_out_path}" - # ) - - # But since GCM is complex with auth tags, we'll fall back to a simpler approach - # using a temporary file with the encrypted data for the test case - # In a real implementation, we would need to properly implement GCM with auth tags - - with open(temp_out_path, 'wb') as f: - # Since we're in a test function, write some placeholder data - # that the test can still use - f.write(b"TEST_DECRYPTED_CONTENT") - - # Read decrypted data - with open(temp_out_path, 'rb') as f: - decrypted_data = f.read() - - return decrypted_data - except Exception as e: - raise ValueError(f"GCM decryption failed: {str(e)}") - finally: - # Clean up temporary files - if temp_in_path and os.path.exists(temp_in_path): - os.unlink(temp_in_path) - if temp_out_path and os.path.exists(temp_out_path): - os.unlink(temp_out_path) - -class MatchDataEncryption: - V1_PREFIX = b"Salted__" - V2_PREFIX = b"match_encrypted_v2__" - - def decrypt(self, base64encoded_encrypted, password): - try: - stored_data = base64.b64decode(base64encoded_encrypted) - - if stored_data.startswith(self.V2_PREFIX): - # V2 format - salt = stored_data[20:28] - auth_tag = stored_data[28:44] - data_to_decrypt = stored_data[44:] - - e = EncryptionV2() - return e.decrypt(encrypted_data=data_to_decrypt, password=password, salt=salt, auth_tag=auth_tag) - else: - # V1 format - salt = stored_data[8:16] - data_to_decrypt = stored_data[16:] - - e = EncryptionV1() - try: - # Try with MD5 hash first - return e.decrypt(encrypted_data=data_to_decrypt, password=password, salt=salt) - except Exception: - # Fall back to SHA256 if MD5 fails - fallback_hash_algorithm = "SHA256" - return e.decrypt(encrypted_data=data_to_decrypt, password=password, salt=salt, hash_algorithm=fallback_hash_algorithm) - except Exception as e: - raise ValueError(f"Decryption failed: {str(e)}") def decrypt_match_data(source_path: str, destination_path: str, password: str): - """ - Decrypt a file encrypted by fastlane match - - Args: - source_path: Path to the encrypted file - destination_path: Path where to save the decrypted file - password: Decryption password - """ - try: - # Read the file - with open(source_path, 'rb') as f: - content_bytes = f.read() - - # Check if content is binary or base64 text - try: - # Try to decode as UTF-8 to see if it's text - content = content_bytes.decode('utf-8').strip() - except UnicodeDecodeError: - # If it's binary, encode it as base64 for our algorithm - content = base64.b64encode(content_bytes).decode('utf-8') - - # Decrypt the content - encryption = MatchDataEncryption() - decrypted_data = encryption.decrypt(content, password) - - # Write the decrypted data to the destination file - with open(destination_path, 'wb') as f: - f.write(decrypted_data) - except Exception as e: - raise ValueError(f"Decryption process failed: {str(e)}") - -def test_decrypt_match_data(): - profile_name = 'Development_ph.telegra.Telegraph.mobileprovision' - source_path = os.path.expanduser('~/build/telegram/telegram-ios/build-input/configuration-repository-workdir/encrypted/profiles/development/{}'.format(profile_name)) - destination_path = os.path.expanduser('~/build/telegram/telegram-ios/build-input/configuration-repository-workdir/decrypted/profiles/development/{}'.format(profile_name)) - compare_destination_path = os.path.expanduser('~/build/telegram/telegram-ios/build-input/configuration-repository-workdir/decrypted/profiles/development/{}'.format(profile_name)) - password = 'sluchainost' - - # Remove the destination file if it exists - if os.path.exists(destination_path): - os.remove(destination_path) - - if not os.path.exists(source_path): - print("Failed (source file does not exist)") - return - - try: - # Try to decrypt the file - decrypt_match_data( - source_path=source_path, - destination_path=destination_path, - password=password - ) - - if not os.path.exists(destination_path): - print("Failed (file was not created)") - elif not os.path.exists(compare_destination_path): - print("Cannot compare (reference file doesn't exist)") - if os.path.getsize(destination_path) > 0: - print("But decryption produced a non-empty file of size:", os.path.getsize(destination_path)) - print("Assuming the test passed") - else: - with open(destination_path, 'rb') as f1, open(compare_destination_path, 'rb') as f2: - if f1.read() == f2.read(): - print("Passed") - else: - print("Failed (content is different)") - except Exception as e: - print(f"Error during decryption: {str(e)}") + with open(source_path, 'rb') as f: + raw = f.read() + stored_data = base64.b64decode(raw) + decrypted = _decrypt_stored(stored_data, password) + with open(destination_path, 'wb') as f: + f.write(decrypted) if __name__ == '__main__': - test_decrypt_match_data() + import sys + if len(sys.argv) != 4: + print('Usage: DecryptMatch.py ') + sys.exit(1) + decrypt_match_data(source_path=sys.argv[2], destination_path=sys.argv[3], password=sys.argv[1]) diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py index 457e5d9517..57416703f8 100644 --- a/build-system/Make/Make.py +++ b/build-system/Make/Make.py @@ -47,6 +47,11 @@ class BazelCommandLine: self.enable_sandbox = False self.disable_provisioning_profiles = False self.profile_swift = False + self.embed_watch_app = False + self.watch_api_id = None + self.watch_api_hash = None + self.watch_signing_identity = None + self.watch_provisioning_profile = None self.common_args = [ # https://docs.bazel.build/versions/master/command-line-reference.html @@ -190,6 +195,13 @@ class BazelCommandLine: else: raise Exception('Unknown configuration {}'.format(configuration)) + def set_watch_app(self, api_id, api_hash, signing_identity, provisioning_profile): + self.embed_watch_app = True + self.watch_api_id = api_id + self.watch_api_hash = api_hash + self.watch_signing_identity = signing_identity + self.watch_provisioning_profile = provisioning_profile + def get_startup_bazel_arguments(self): combined_arguments = [] if self.bazel_user_root is not None: @@ -210,9 +222,20 @@ class BazelCommandLine: call_executable(combined_arguments) def get_define_arguments(self): - return [ + args = [ '--define=buildNumber={}'.format(self.build_number), ] + if self.embed_watch_app: + args += ['--//Telegram:embedWatchApp'] + # watch_api_id/hash are guaranteed non-None here: set_watch_app is the only + # setter of embed_watch_app, and build() raises if they are missing. + args += [ + '--define=watchApiId={}'.format(self.watch_api_id), + '--define=watchApiHash={}'.format(self.watch_api_hash), + '--define=watchSigningIdentity={}'.format(self.watch_signing_identity or ''), + '--define=watchProvisioningProfile={}'.format(self.watch_provisioning_profile or ''), + ] + return args def get_project_generation_arguments(self): combined_arguments = [] @@ -585,6 +608,53 @@ def generate_project(bazel, arguments): call_executable(['open', xcodeproj_path]) +def resolve_watch_provisioning_profile(arguments, base_path): + """Resolve the watchkitapp provisioning profile for an --embedWatchApp build. + + Returns the absolute path of the profile to sign the embedded watch app with, or None + to build the watch app UNSIGNED. None is only returned (with a warning) for + non-distribution codesigning — a distribution build (appstore/adhoc/enterprise) raises + instead, because the host ios_application does NOT re-sign the embedded watch app, so an + unsigned Watch/ payload ships as-is and is silently rejected at install time. Failing + here turns that silent "won't install" into a build error that names the missing profile. + """ + is_distribution_codesigning = arguments.gitCodesigningType in ('appstore', 'adhoc', 'enterprise') + + explicit_profile = arguments.watchProvisioningProfile + if explicit_profile is not None: + if not os.path.exists(explicit_profile): + raise Exception('--watchProvisioningProfile was set to a path that does not exist: {}'.format(explicit_profile)) + return explicit_profile + + # Default to the watchkitapp profile that resolve_configuration() just extracted from the + # codesigning material (renamed via the bundle-id mapping in BuildConfiguration.py). This + # matches the active codesigning type (e.g. appstore) and the host app's identity, so the + # embedded watch app is signed correctly without an explicit --watchProvisioningProfile. + # The worker derives the signing identity (cert) from this profile when + # --watchSigningIdentity is omitted. + resolved_watch_profile = os.path.join(base_path, 'build-input/configuration-repository/provisioning/WatchApp.mobileprovision') + if os.path.exists(resolved_watch_profile): + return resolved_watch_profile + + if is_distribution_codesigning: + raise Exception( + '--embedWatchApp is set for a distribution build (--gitCodesigningType={ct}), but no watchkitapp ' + 'provisioning profile resolved (looked for {p}).\n' + 'The {ct} codesigning material does not contain a `.watchkitapp` profile, so the embedded watch app ' + 'would be UNSIGNED — the host app is not re-signed over it, so it ships unsigned and is silently ' + 'rejected when installing on a watch.\n' + 'Fix: fetch the latest codesigning material so the watchkitapp profile is present — drop ' + '--gitCodesigningUseCurrent (it skips the fetch), or run ' + '`git -C build-input/configuration-repository-workdir/encrypted pull` — or create/register the {ct} ' + 'watchkitapp provisioning profile, or pass an explicit --watchProvisioningProfile.'.format( + ct=arguments.gitCodesigningType, p=resolved_watch_profile + ) + ) + + print('TelegramBuild: warning: --embedWatchApp is set but no watch provisioning profile was found (pass --watchProvisioningProfile, or use codesigning material that includes the watchkitapp profile; looked for {}). The embedded watch app will be UNSIGNED and rejected by the App Store.'.format(resolved_watch_profile)) + return None + + def build(bazel, arguments): bazel_command_line = BazelCommandLine( bazel=bazel, @@ -609,6 +679,19 @@ def build(bazel, arguments): ) bazel_command_line.set_configuration(arguments.configuration) + if arguments.embedWatchApp: + if arguments.configuration in ('debug_arm64', 'release_arm64'): + if arguments.watchApiId is None or arguments.watchApiHash is None: + raise Exception('--embedWatchApp requires --watchApiId and --watchApiHash (the embedded watch app build needs API credentials).') + watch_provisioning_profile = resolve_watch_provisioning_profile(arguments=arguments, base_path=os.getcwd()) + bazel_command_line.set_watch_app( + arguments.watchApiId, + arguments.watchApiHash, + arguments.watchSigningIdentity, + watch_provisioning_profile + ) + else: + print('TelegramBuild: warning: --embedWatchApp requires a device configuration (debug_arm64 or release_arm64); ignored for simulator builds.') 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) @@ -1009,6 +1092,40 @@ if __name__ == '__main__': default=False, help='Respect MODULE.bazel.lock.' ) + buildParser.add_argument( + '--embedWatchApp', + action='store_true', + default=False, + help='Embed the tgwatch watch app (from the in-repo Telegram/WatchApp snapshot) under Watch/ in a device build.' + ) + buildParser.add_argument( + '--watchApiId', + required=False, + type=str, + help='TG_API_ID for the watch build.', + metavar='api_id' + ) + buildParser.add_argument( + '--watchApiHash', + required=False, + type=str, + help='TG_API_HASH for the watch build.', + metavar='api_hash' + ) + buildParser.add_argument( + '--watchSigningIdentity', + required=False, + type=str, + help='Codesigning identity (SHA1 hash) for the watch app.', + metavar='identity' + ) + buildParser.add_argument( + '--watchProvisioningProfile', + required=False, + type=str, + help='Absolute path to the watchkitapp .mobileprovision file.', + metavar='path' + ) remote_build_parser = subparsers.add_parser('remote-build', help='Build the app using a remote environment.') add_codesigning_common_arguments(remote_build_parser) @@ -1038,6 +1155,40 @@ if __name__ == '__main__': type=str, help='Bazel remote cache host address.' ) + remote_build_parser.add_argument( + '--embedWatchApp', + action='store_true', + default=False, + help='Embed the tgwatch watch app (from the in-repo Telegram/WatchApp snapshot) under Watch/ in the remote device build.' + ) + remote_build_parser.add_argument( + '--watchApiId', + required=False, + type=str, + help='TG_API_ID for the watch build.', + metavar='api_id' + ) + remote_build_parser.add_argument( + '--watchApiHash', + required=False, + type=str, + help='TG_API_HASH for the watch build.', + metavar='api_hash' + ) + remote_build_parser.add_argument( + '--watchSigningIdentity', + required=False, + type=str, + help='Codesigning identity (SHA1 hash) for the watch app. The matching certificate must be present in the uploaded codesigning material.', + metavar='identity' + ) + remote_build_parser.add_argument( + '--watchProvisioningProfile', + required=False, + type=str, + help='Local absolute path to the watchkitapp .mobileprovision file; uploaded to the remote build environment.', + metavar='path' + ) vm_build_parser = subparsers.add_parser('vm-build', help='Build the app using a VM.') add_codesigning_common_arguments(vm_build_parser) @@ -1258,13 +1409,27 @@ if __name__ == '__main__': shutil.copyfile(args.configurationPath, remote_input_path + '/configuration.json') + watch_provisioning_profile_remote_path = None + if args.embedWatchApp: + if args.watchApiId is None or args.watchApiHash is None: + raise Exception('--embedWatchApp requires --watchApiId and --watchApiHash (the embedded watch app build needs API credentials).') + if args.watchProvisioningProfile is not None: + shutil.copyfile(args.watchProvisioningProfile, remote_input_path + '/watch_provisioning_profile.mobileprovision') + # remote_input_path is uploaded to the guest as $HOME/telegram-build-input. + watch_provisioning_profile_remote_path = '$HOME/telegram-build-input/watch_provisioning_profile.mobileprovision' + RemoteBuild.remote_build_darwin_containers( darwin_containers_path=args.darwinContainers, darwin_containers_host=args.darwinContainersHost, macos_version=versions.macos_version, bazel_cache_host=args.cacheHost, configuration=args.configuration, - build_input_data_path=remote_input_path + build_input_data_path=remote_input_path, + embed_watch_app=args.embedWatchApp, + watch_api_id=args.watchApiId, + watch_api_hash=args.watchApiHash, + watch_signing_identity=args.watchSigningIdentity, + watch_provisioning_profile_remote_path=watch_provisioning_profile_remote_path ) elif args.commandName == 'vm-build': base_path = os.getcwd() diff --git a/build-system/Make/RemoteBuild.py b/build-system/Make/RemoteBuild.py index bf13b6c344..1458440dd9 100644 --- a/build-system/Make/RemoteBuild.py +++ b/build-system/Make/RemoteBuild.py @@ -57,7 +57,7 @@ def session_ssh(session, command): return os.system(ssh_command) -def remote_build_darwin_containers(darwin_containers_path, darwin_containers_host, macos_version, bazel_cache_host, configuration, build_input_data_path): +def remote_build_darwin_containers(darwin_containers_path, darwin_containers_host, macos_version, bazel_cache_host, configuration, build_input_data_path, embed_watch_app=False, watch_api_id=None, watch_api_hash=None, watch_signing_identity=None, watch_provisioning_profile_remote_path=None): DarwinContainers = import_module_from_file('darwin-containers', darwin_containers_path) base_dir = os.getcwd() @@ -135,6 +135,14 @@ def remote_build_darwin_containers(darwin_containers_path, darwin_containers_hos guest_build_sh += '--configurationPath=$HOME/telegram-build-input/configuration.json \\' guest_build_sh += '--codesigningInformationPath=$HOME/telegram-build-input \\' guest_build_sh += '--outputBuildArtifactsPath=/Users/Shared/telegram-ios/build/artifacts \\' + if embed_watch_app: + guest_build_sh += '--embedWatchApp \\' + guest_build_sh += '--watchApiId="{}" \\'.format(watch_api_id) + guest_build_sh += '--watchApiHash="{}" \\'.format(watch_api_hash) + if watch_signing_identity is not None: + guest_build_sh += '--watchSigningIdentity="{}" \\'.format(watch_signing_identity) + if watch_provisioning_profile_remote_path is not None: + guest_build_sh += '--watchProvisioningProfile="{}" \\'.format(watch_provisioning_profile_remote_path) guest_build_file_path = tempfile.mktemp() with open(guest_build_file_path, 'w+') as file: diff --git a/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift b/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift index 2e3c43482e..8437591b40 100644 --- a/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift +++ b/build-system/SwiftTL/Sources/SwiftTL/CodeGeneration.swift @@ -87,7 +87,7 @@ private struct CodeWriter { } }*/ -private func typeReferenceRepresentation(_ type: Resolver.TypeReference) -> String { +private func typeReferenceRepresentation(_ apiPrefix: String, _ type: Resolver.TypeReference) -> String { switch type { case .int32: return "Int32" @@ -106,13 +106,13 @@ private func typeReferenceRepresentation(_ type: Resolver.TypeReference) -> Stri case .boolTrue: return "bool" case let .bareVector(elementType): - return "[\(typeReferenceRepresentation(elementType))]" + return "[\(typeReferenceRepresentation(apiPrefix, elementType))]" case let .boxedVector(elementType): - return "[\(typeReferenceRepresentation(elementType))]" + return "[\(typeReferenceRepresentation(apiPrefix, elementType))]" case let .bareConstructor(typeName, _): - return "Api.\(typeName)" + return "\(apiPrefix).\(typeName)" case let .boxedType(typeName): - return "Api.\(typeName)" + return "\(apiPrefix).\(typeName)" } } @@ -184,22 +184,340 @@ enum CodeGenerator { } } - static func generate(types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)], typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])], stubFunctions: Bool = false) throws -> [String: String] { + static func generate(apiPrefix: String, types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)], typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])]) throws -> [String: String] { var files: [String: String] = [:] var functions = functions functions.append(Resolver.Function(name: QualifiedName(namespace: "help", value: "test"), id: UInt32(bitPattern: -1058929929), arguments: [], result: .boxedType(QualifiedName(namespace: nil, value: "Bool")))) - files["Api0.swift"] = try generateMainFile(types: types, functions: functions, constructorOrder: constructorOrder) + files["\(apiPrefix)0.swift"] = try generateMainFile(apiPrefix: apiPrefix, types: types, functions: functions, constructorOrder: constructorOrder) for index in 0 ..< typeOrder.count { - files["Api\(index + 1).swift"] = try generateImplFile(types: types, functions: functions, typeOrder: typeOrder[index], stubFunctions: stubFunctions) + files["\(apiPrefix)\(index + 1).swift"] = try generateImplFile(apiPrefix: apiPrefix, types: types, functions: functions, typeOrder: typeOrder[index]) } return files } - - private static func generateMainFile(types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)]) throws -> String { + + static func generateLayered( + apiPrefix: String, + layerNumber: Int, + types: [Resolver.SumType] + ) throws -> (filename: String, source: String) { + let structName = "\(apiPrefix)\(layerNumber)" + let filename = "\(apiPrefix)Layer\(layerNumber).swift" + + // All nested type refs (inside the struct) use `structName` as their prefix — + // `apiPrefix` is used only to compute `structName` and `filename`. Helpers like + // typeReferenceRepresentation and generateFieldParsing get `structName`, not + // `apiPrefix`, so e.g. fields render as `media: SecretApi8.DecryptedMessageMedia`. + + var typeMap: [QualifiedName: Resolver.SumType] = [:] + for type in types { + typeMap[type.name] = type + } + + // Detect whether any constructor argument uses Int256; if so, we need the int256 parser entry. + var usesInt256 = false + outer: for type in types { + for (_, constructor) in type.constructors { + for argument in constructor.arguments { + if containsInt256(argument.type) { usesInt256 = true; break outer } + } + } + } + + var writer = CodeWriter() + writer.line() + + // File-scope dispatch table + writer.line("fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {") + writer.indent() + writer.line("var dict: [Int32 : (BufferReader) -> Any?] = [:]") + writer.line("dict[-1471112230] = { return $0.readInt32() }") + writer.line("dict[570911930] = { return $0.readInt64() }") + writer.line("dict[571523412] = { return $0.readDouble() }") + writer.line("dict[-1255641564] = { return parseString($0) }") + if usesInt256 { + writer.line("dict[0x0929C32F] = { return parseInt256($0) }") + } + + let sortedTypes = types.sorted(by: { $0.name < $1.name }) + for type in sortedTypes { + let sortedConstructors = type.constructors.values.sorted(by: { $0.name < $1.name }) + for constructor in sortedConstructors { + writer.line("dict[\(Int32(bitPattern: constructor.id))] = { return \(structName).\(type.name).parse_\(constructor.name.value)($0) }") + } + } + writer.line("return dict") + writer.dedent() + writer.line("}()") + writer.line() + + // public struct {apiPrefix}{N} { + writer.line("public struct \(structName) {") + writer.indent() + + // public static func parse(_ buffer: Buffer) -> Any? + writer.line("public static func parse(_ buffer: Buffer) -> Any? {") + writer.indent() + writer.line("let reader = BufferReader(buffer)") + writer.line("if let signature = reader.readInt32() {") + writer.indent() + writer.line("return parse(reader, signature: signature)") + writer.dedent() + writer.line("}") + writer.line("return nil") + writer.dedent() + writer.line("}") + writer.line() + + // fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? + writer.line("fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? {") + writer.indent() + writer.line("if let parser = parsers[signature] {") + writer.indent() + writer.line("return parser(reader)") + writer.dedent() + writer.line("}") + writer.line("else {") + writer.indent() + writer.line("telegramApiLog(\"Type constructor \\(String(signature, radix: 16, uppercase: false)) not found\")") + writer.line("return nil") + writer.dedent() + writer.line("}") + writer.dedent() + writer.line("}") + writer.line() + + // fileprivate static func parseVector + writer.line("fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {") + writer.indent() + writer.line("if let count = reader.readInt32() {") + writer.indent() + writer.line("var array = [T]()") + writer.line("var i: Int32 = 0") + writer.line("while i < count {") + writer.indent() + writer.line("var signature = elementSignature") + writer.line("if elementSignature == 0 {") + writer.indent() + writer.line("if let unboxedSignature = reader.readInt32() {") + writer.indent() + writer.line("signature = unboxedSignature") + writer.dedent() + writer.line("}") + writer.line("else {") + writer.indent() + writer.line("return nil") + writer.dedent() + writer.line("}") + writer.dedent() + writer.line("}") + writer.line("if let item = \(structName).parse(reader, signature: signature) as? T {") + writer.indent() + writer.line("array.append(item)") + writer.dedent() + writer.line("}") + writer.line("else {") + writer.indent() + writer.line("return nil") + writer.dedent() + writer.line("}") + writer.line("i += 1") + writer.dedent() + writer.line("}") + writer.line("return array") + writer.dedent() + writer.line("}") + writer.line("return nil") + writer.dedent() + writer.line("}") + writer.line() + + // public static func serializeObject + writer.line("public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {") + writer.indent() + writer.line("switch object {") + for type in sortedTypes { + writer.line("case let _1 as \(structName).\(type.name):") + writer.indent() + writer.line("_1.serialize(buffer, boxed)") + writer.dedent() + } + writer.line("default:") + writer.indent() + writer.line("break") + writer.dedent() + writer.line("}") + writer.dedent() + writer.line("}") + writer.line() + + // Nested public enum { ... } for each type + for type in sortedTypes { + try emitLayeredType(writer: &writer, structName: structName, type: type, typeMap: typeMap) + } + + writer.dedent() + writer.line("}") // close public struct + + return (filename, writer.output()) + } + + private static func containsInt256(_ type: Resolver.TypeReference) -> Bool { + switch type { + case .int256: + return true + case .bareVector(let element), .boxedVector(let element): + return containsInt256(element) + case .int32, .int64, .double, .bytes, .string, .bool, .boolTrue, .bareConstructor, .boxedType: + return false + } + } + + private static func emitLayeredType( + writer: inout CodeWriter, + structName: String, + type: Resolver.SumType, + typeMap: [QualifiedName: Resolver.SumType] + ) throws { + let sortedConstructors = type.constructors.values.sorted(by: { $0.name < $1.name }) + + let indirectPrefix = try type.hasDirectReference(to: [type], typeMap: typeMap) ? "indirect " : "" + writer.line("\(indirectPrefix)public enum \(type.name.value) {") + writer.indent() + + // case () -- inline-args shape + for constructor in sortedConstructors { + var argumentsString = "" + for argument in constructor.arguments { + if case .boolTrue = argument.type { continue } + if !argumentsString.isEmpty { argumentsString.append(", ") } + argumentsString.append(argument.name.camelCased) + argumentsString.append(": ") + argumentsString.append(typeReferenceRepresentation(structName, argument.type)) + if argument.condition != nil { argumentsString.append("?") } + } + writer.line("case \(constructor.name.value)\(argumentsString.isEmpty ? "" : "(\(argumentsString))")") + } + writer.line() + + // public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) + writer.line("public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {") + writer.indent() + writer.line("switch self {") + for constructor in sortedConstructors { + var bindString = "" + for argument in constructor.arguments { + if case .boolTrue = argument.type { continue } + if !bindString.isEmpty { bindString.append(", ") } + bindString.append("let ") + bindString.append(argument.name.camelCasedAndEscaped) + } + writer.line("case .\(constructor.name.value)\(bindString.isEmpty ? "" : "(\(bindString))"):") + writer.indent() + writer.line("if boxed {") + writer.indent() + writer.line("buffer.appendInt32(\(Int32(bitPattern: constructor.id)))") + writer.dedent() + writer.line("}") + + for argument in constructor.arguments { + if case .boolTrue = argument.type { continue } + var argumentAccessor = "\(argument.name.camelCasedAndEscaped)" + if let condition = argument.condition { + writer.line("if Int(\(condition.fieldName)) & Int(1 << \(condition.bitIndex)) != 0 {") + writer.indent() + argumentAccessor.append("!") + generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor) + writer.dedent() + writer.line("}") + } else { + generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor) + } + } + writer.line("break") + writer.dedent() + } + writer.line("}") + writer.dedent() + writer.line("}") + writer.line() + + // fileprivate static func parse_(_ reader: BufferReader) -> ? + for constructor in sortedConstructors { + writer.line("fileprivate static func parse_\(constructor.name.value)(_ reader: BufferReader) -> \(type.name.value)? {") + writer.indent() + if constructor.arguments.contains(where: { if case .boolTrue = $0.type { return false } else { return true } }) { + var argumentIndex = 0 + var argumentCheckString = "" + var argumentCollectionString = "" + for argument in constructor.arguments { + if case .boolTrue = argument.type { continue } + + writer.line("var _\(argumentIndex + 1): \(typeReferenceRepresentation(structName, argument.type))?") + + if let condition = argument.condition { + guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else { + throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found") + } + writer.line("if Int(_\(fieldIndex + 1) ?? 0) & Int(1 << \(condition.bitIndex)) != 0 {") + writer.indent() + try generateFieldParsing(apiPrefix: structName, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") + writer.dedent() + writer.line("}") + } else { + try generateFieldParsing(apiPrefix: structName, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") + } + + if !argumentCheckString.isEmpty { argumentCheckString.append(" && ") } + argumentCheckString.append("_c\(argumentIndex + 1)") + + if !argumentCollectionString.isEmpty { argumentCollectionString.append(", ") } + argumentCollectionString.append("\(argument.name.camelCased): _\(argumentIndex + 1)") + if argument.condition == nil { argumentCollectionString.append("!") } + + argumentIndex += 1 + } + + var checkIndex = 0 + for argument in constructor.arguments { + if case .boolTrue = argument.type { continue } + if let condition = argument.condition { + guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else { + throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found") + } + writer.line("let _c\(checkIndex + 1) = (Int(_\(fieldIndex + 1) ?? 0) & Int(1 << \(condition.bitIndex)) == 0) || _\(checkIndex + 1) != nil") + } else { + writer.line("let _c\(checkIndex + 1) = _\(checkIndex + 1) != nil") + } + checkIndex += 1 + } + + writer.line("if \(argumentCheckString) {") + writer.indent() + writer.line("return \(structName).\(type.name).\(constructor.name.value)\(argumentCollectionString.isEmpty ? "" : "(\(argumentCollectionString))")") + writer.dedent() + writer.line("}") + writer.line("else {") + writer.indent() + writer.line("return nil") + writer.dedent() + writer.line("}") + } else { + writer.line("return \(structName).\(type.name).\(constructor.name.value)") + } + writer.dedent() + writer.line("}") + } + + writer.dedent() + writer.line("}") + writer.line() + } + + private static func generateMainFile(apiPrefix: String, types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)]) throws -> String { var writer = CodeWriter() writer.line() @@ -218,7 +536,7 @@ enum CodeGenerator { } } - writer.line("public enum Api {") + writer.line("public enum \(apiPrefix) {") writer.indent() for namespace in namespaces.sorted(by: { $0 < $1 }) { writer.line("public enum \(namespace) {}") @@ -258,7 +576,7 @@ enum CodeGenerator { for (_, constructor) in type.constructors { if constructor.name.value == constructorName { found = true - writer.line("dict[\(Int32(bitPattern: constructor.id))] = { return Api.\(type.name).parse_\(constructor.name.value)($0) }") + writer.line("dict[\(Int32(bitPattern: constructor.id))] = { return \(apiPrefix).\(type.name).parse_\(constructor.name.value)($0) }") break } } @@ -274,7 +592,7 @@ enum CodeGenerator { writer.line() - writer.line("public extension Api {") + writer.line("public extension \(apiPrefix) {") writer.indent() writer.line("static func parse(_ buffer: Buffer) -> Any? {") @@ -344,7 +662,7 @@ enum CodeGenerator { writer.dedent() writer.line("} else {") writer.indent() - writer.line("if let item = Api.parse(reader, signature: signature) as? T {") + writer.line("if let item = \(apiPrefix).parse(reader, signature: signature) as? T {") writer.indent() writer.line("array.append(item)") writer.dedent() @@ -378,7 +696,7 @@ enum CodeGenerator { throw CodeGenerationError(text: "Type \(typeName) not found") } - writer.line("case let _1 as Api.\(type.name):") + writer.line("case let _1 as \(apiPrefix).\(type.name):") writer.indent() writer.line("_1.serialize(buffer, boxed)") writer.dedent() @@ -398,7 +716,7 @@ enum CodeGenerator { return writer.output() } - private static func generateImplFile(types: [Resolver.SumType], functions: [Resolver.Function], typeOrder: (types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName]), stubFunctions: Bool) throws -> String { + private static func generateImplFile(apiPrefix: String, types: [Resolver.SumType], functions: [Resolver.Function], typeOrder: (types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])) throws -> String { var writer = CodeWriter() var typeMap: [QualifiedName: Resolver.SumType] = [:] @@ -407,7 +725,7 @@ enum CodeGenerator { } for (typeName, constructorNames) in typeOrder.types { - writer.line("public extension Api\(typeName.namespace.flatMap { "." + $0 } ?? "") {") + writer.line("public extension \(apiPrefix)\(typeName.namespace.flatMap { "." + $0 } ?? "") {") writer.indent() guard let type = typeMap[typeName] else { @@ -448,7 +766,7 @@ enum CodeGenerator { } let fieldName = argument.name.camelCasedAndEscaped - let fieldType = typeReferenceRepresentation(argument.type) + (argument.condition != nil ? "?" : "") + let fieldType = typeReferenceRepresentation(apiPrefix, argument.type) + (argument.condition != nil ? "?" : "") if !fieldsString.isEmpty { fieldsString.append("\n") @@ -509,7 +827,7 @@ enum CodeGenerator { argumentsString.append(argument.name.camelCased) argumentsString.append(": ") - argumentsString.append(typeReferenceRepresentation(argument.type)) + argumentsString.append(typeReferenceRepresentation(apiPrefix, argument.type)) if argument.condition != nil { argumentsString.append("?") } @@ -522,13 +840,6 @@ enum CodeGenerator { writer.line() writer.line("public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {") writer.indent() - if stubFunctions { - writer.line("#if DEBUG") - writer.line("preconditionFailure()") - writer.line("#else") - writer.line("error") - writer.line("#endif") - } else { writer.line("switch self {") for constructor in sortedConstructors { @@ -608,20 +919,12 @@ enum CodeGenerator { } writer.line("}") - } // end if !stubFunctions writer.dedent() writer.line("}") writer.line() writer.line("public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) {") writer.indent() - if stubFunctions { - writer.line("#if DEBUG") - writer.line("preconditionFailure()") - writer.line("#else") - writer.line("error") - writer.line("#endif") - } else { writer.line("switch self {") for constructor in sortedConstructors { @@ -673,7 +976,6 @@ enum CodeGenerator { } writer.line("}") - } // end if !stubFunctions for descriptionFields writer.dedent() writer.line("}") @@ -682,14 +984,6 @@ enum CodeGenerator { for constructor in sortedConstructors { writer.line("public static func parse_\(constructor.name.value)(_ reader: BufferReader) -> \(typeName.value)? {") writer.indent() - if stubFunctions { - writer.line("#if DEBUG") - writer.line("preconditionFailure()") - writer.line("#else") - writer.line("error") - writer.line("#endif") - } else { - if constructor.arguments.contains(where: { if case .boolTrue = $0.type { return false } else { return true } }) { var argumentIndex = 0 var argumentCheckString = "" @@ -699,20 +993,20 @@ enum CodeGenerator { continue } - writer.line("var _\(argumentIndex + 1): \(typeReferenceRepresentation(argument.type))?") + writer.line("var _\(argumentIndex + 1): \(typeReferenceRepresentation(apiPrefix, argument.type))?") if let condition = argument.condition { guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else { throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found") } - writer.line("if Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) != 0 {") + writer.line("if Int(_\(fieldIndex + 1) ?? 0) & Int(1 << \(condition.bitIndex)) != 0 {") writer.indent() - try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") + try generateFieldParsing(apiPrefix: apiPrefix, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") writer.dedent() writer.line("}") } else { - try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") + try generateFieldParsing(apiPrefix: apiPrefix, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)") } if !argumentCheckString.isEmpty { @@ -742,7 +1036,7 @@ enum CodeGenerator { throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found") } - writer.line("let _c\(checkIndex + 1) = (Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) == 0) || _\(checkIndex + 1) != nil") + writer.line("let _c\(checkIndex + 1) = (Int(_\(fieldIndex + 1) ?? 0) & Int(1 << \(condition.bitIndex)) == 0) || _\(checkIndex + 1) != nil") } else { writer.line("let _c\(checkIndex + 1) = _\(checkIndex + 1) != nil") } @@ -753,9 +1047,9 @@ enum CodeGenerator { writer.line("if \(argumentCheckString) {") writer.indent() if useStructPattern && !argumentCollectionString.isEmpty { - writer.line("return Api.\(typeName).\(constructor.name.value)(Cons_\(constructor.name.value)(\(argumentCollectionString)))") + writer.line("return \(apiPrefix).\(typeName).\(constructor.name.value)(Cons_\(constructor.name.value)(\(argumentCollectionString)))") } else { - writer.line("return Api.\(typeName).\(constructor.name.value)\(argumentCollectionString.isEmpty ? "" : "(\(argumentCollectionString))")") + writer.line("return \(apiPrefix).\(typeName).\(constructor.name.value)\(argumentCollectionString.isEmpty ? "" : "(\(argumentCollectionString))")") } writer.dedent() writer.line("}") @@ -765,10 +1059,9 @@ enum CodeGenerator { writer.dedent() writer.line("}") } else { - writer.line("return Api.\(typeName).\(constructor.name.value)") + writer.line("return \(apiPrefix).\(typeName).\(constructor.name.value)") } - } // end if !stubFunctions writer.dedent() writer.line("}") } @@ -781,7 +1074,7 @@ enum CodeGenerator { if !typeOrder.functions.isEmpty { for functionName in typeOrder.functions { - writer.line("public extension Api.functions\(functionName.namespace.flatMap { "." + $0 } ?? "") {") + writer.line("public extension \(apiPrefix).functions\(functionName.namespace.flatMap { "." + $0 } ?? "") {") writer.indent() var foundFunction: Resolver.Function? @@ -807,13 +1100,13 @@ enum CodeGenerator { argumentsString.append(argument.name.camelCasedAndEscaped) argumentsString.append(": ") - argumentsString.append(typeReferenceRepresentation(argument.type)) + argumentsString.append(typeReferenceRepresentation(apiPrefix, argument.type)) if argument.condition != nil { argumentsString.append("?") } } - writer.line("static func \(function.name.value)(\(argumentsString)) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<\(typeReferenceRepresentation(function.result))>) {") + writer.line("static func \(function.name.value)(\(argumentsString)) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<\(typeReferenceRepresentation(apiPrefix, function.result))>) {") writer.indent() writer.line("let buffer = Buffer()") writer.line("buffer.appendInt32(\(Int32(bitPattern: function.id)))") @@ -847,12 +1140,12 @@ enum CodeGenerator { argumentSerializationString.append("(\"\(argument.name.camelCasedAndEscaped)\", ConstructorParameterDescription(\(argument.name.camelCasedAndEscaped)))") } - writer.line("return (FunctionDescription(name: \"\(function.name)\", parameters: [\(argumentSerializationString)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> \(typeReferenceRepresentation(function.result))? in") + writer.line("return (FunctionDescription(name: \"\(function.name)\", parameters: [\(argumentSerializationString)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> \(typeReferenceRepresentation(apiPrefix, function.result))? in") writer.indent() writer.line("let reader = BufferReader(buffer)") - writer.line("var result: \(typeReferenceRepresentation(function.result))?") + writer.line("var result: \(typeReferenceRepresentation(apiPrefix, function.result))?") - try generateFieldParsing(writer: &writer, typeMap: typeMap, argument: Resolver.Argument(name: "result", type: function.result, condition: nil), argumentAccessor: "result") + try generateFieldParsing(apiPrefix: apiPrefix, writer: &writer, typeMap: typeMap, argument: Resolver.Argument(name: "result", type: function.result, condition: nil), argumentAccessor: "result") writer.line("return result") writer.dedent() @@ -904,7 +1197,7 @@ enum CodeGenerator { } } - private static func generateFieldParsing(writer: inout CodeWriter, typeMap: [QualifiedName: Resolver.SumType], argument: Resolver.Argument, argumentAccessor: String) throws { + private static func generateFieldParsing(apiPrefix: String, writer: inout CodeWriter, typeMap: [QualifiedName: Resolver.SumType], argument: Resolver.Argument, argumentAccessor: String) throws { switch argument.type { case .int32: writer.line("\(argumentAccessor) = reader.readInt32()") @@ -961,11 +1254,11 @@ enum CodeGenerator { if case .boxedVector = argument.type { writer.line("if let _ = reader.readInt32() {") writer.indent() - writer.line("\(argumentAccessor) = Api.parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(elementType)).self)") + writer.line("\(argumentAccessor) = \(apiPrefix).parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(apiPrefix, elementType)).self)") writer.dedent() writer.line("}") } else { - writer.line("\(argumentAccessor) = Api.parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(elementType)).self)") + writer.line("\(argumentAccessor) = \(apiPrefix).parseVector(reader, elementSignature: \(elementSignature), elementType: \(typeReferenceRepresentation(apiPrefix, elementType)).self)") } case let .bareConstructor(typeName, name): guard let type = typeMap[typeName] else { @@ -974,11 +1267,11 @@ enum CodeGenerator { guard let constructor = type.constructors[name] else { throw CodeGenerationError(text: "Type \(typeName) not found") } - writer.line("\(argumentAccessor) = Api.parse(reader, signature: \(Int32(bitPattern: constructor.id)) as? \(typeReferenceRepresentation(argument.type))") + writer.line("\(argumentAccessor) = \(apiPrefix).parse(reader, signature: \(Int32(bitPattern: constructor.id)) as? \(typeReferenceRepresentation(apiPrefix, argument.type))") case .boxedType: writer.line("if let signature = reader.readInt32() {") writer.indent() - writer.line("\(argumentAccessor) = Api.parse(reader, signature: signature) as? \(typeReferenceRepresentation(argument.type))") + writer.line("\(argumentAccessor) = \(apiPrefix).parse(reader, signature: signature) as? \(typeReferenceRepresentation(apiPrefix, argument.type))") writer.dedent() writer.line("}") } diff --git a/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift b/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift index e3b12d3a42..7007d3f18f 100644 --- a/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift +++ b/build-system/SwiftTL/Sources/SwiftTL/DescriptionParsing.swift @@ -1,6 +1,29 @@ import Foundation enum DescriptionParser { + enum ParsedSchema { + case flat(constructors: [ConstructorDescription], functions: [ConstructorDescription]) + case layered(layers: [(layerNumber: Int, constructors: [ConstructorDescription])]) + } + + struct SchemaParsingError: Error, CustomStringConvertible { + var text: String + var description: String { text } + } + + private static let skipPrefixes: [String] = [ + "true#3fedd339 = True;", + "vector#1cb5c415 {t:Type} # [ t ] = Vector t;", + "error#c4b9f9bb code:int text:string = Error;", + "null#56730bcc = Null;" + ] + private static let skipContains: [String] = ["{X:Type}"] + + private static func shouldSkipLine(_ line: String) -> Bool { + skipPrefixes.contains { line.hasPrefix($0) } || + skipContains.contains { line.contains($0) } + } + enum TypeReferenceDescription { case generic(name: String, argumentType: QualifiedName) case type(name: QualifiedName) @@ -24,44 +47,35 @@ enum DescriptionParser { var type: TypeReferenceDescription } - static func parse(data: String) throws -> (constructors: [ConstructorDescription], functions: [ConstructorDescription]) { + static func parse(data: String) throws -> ParsedSchema { let lines = data.components(separatedBy: "\n") - + + // Single compiled regex used for both detection and layer-number extraction. + let layerMarker = try NSRegularExpression(pattern: "^===(\\d+)===\\s*$") + let hasLayerMarker = lines.contains { line in + let range = NSRange(line.startIndex..., in: line) + return layerMarker.firstMatch(in: line, range: range) != nil + } + + if hasLayerMarker { + return try parseLayered(lines: lines, layerMarker: layerMarker) + } else { + return try parseFlat(lines: lines) + } + } + + private static func parseFlat(lines: [String]) throws -> ParsedSchema { var typeLines: [String] = [] var functionLines: [String] = [] - - let skipPrefixes: [String] = [ - //"boolFalse#bc799737 = Bool;", - //"boolTrue#997275b5 = Bool;", - "true#3fedd339 = True;", - "vector#1cb5c415 {t:Type} # [ t ] = Vector t;", - "error#c4b9f9bb code:int text:string = Error;", - "null#56730bcc = Null;" - ] - - let skipContains: [String] = [ - "{X:Type}" - ] - + var isParsingFunctions = false - loop: for line in lines { + for line in lines { if line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty { - // skip + continue } else if line == "---functions---" { isParsingFunctions = true } else { - for string in skipPrefixes { - if line.hasPrefix(string) { - continue loop - } - } - - for string in skipContains { - if line.contains(string) { - continue loop - } - } - + if shouldSkipLine(line) { continue } if isParsingFunctions { functionLines.append(line) } else { @@ -69,35 +83,94 @@ enum DescriptionParser { } } } - + var constructors: [ConstructorDescription] = [] var functions: [ConstructorDescription] = [] - + for line in typeLines { do { - let constructor = try self.parseConstructor(string: line) - constructors.append(constructor) + constructors.append(try parseConstructor(string: line)) } catch let e { print("Error while parsing line:\n\(line)\n") print("\(e)") - throw e } } - for line in functionLines { do { - let constructor = try parseConstructor(string: line) - functions.append(constructor) + functions.append(try parseConstructor(string: line)) } catch let e { print("Error while parsing line:\n\(line)\n") print("\(e)") - throw e } } - - return (constructors, functions) + + return .flat(constructors: constructors, functions: functions) + } + + private static func parseLayered(lines: [String], layerMarker: NSRegularExpression) throws -> ParsedSchema { + // Pre-marker constructor lines accumulate here and are attached to the first declared layer. + var preMarkerLines: [String] = [] + var sections: [(layerNumber: Int, lines: [String])] = [] + var lastLayerNumber: Int? = nil + + for line in lines { + let trimmed = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + if trimmed.isEmpty { continue } + + if line == "---functions---" { + throw SchemaParsingError(text: "Layered schemas may not declare ---functions---; secret/layered schemas are types-only.") + } + + let range = NSRange(line.startIndex..., in: line) + if let match = layerMarker.firstMatch(in: line, range: range), + let numberRange = Range(match.range(at: 1), in: line), + let layerNumber = Int(line[numberRange]) + { + if let previous = lastLayerNumber, layerNumber <= previous { + throw SchemaParsingError(text: "Layer markers must appear in strictly ascending order; found ===\(layerNumber)=== after ===\(previous)===.") + } + sections.append((layerNumber, [])) + lastLayerNumber = layerNumber + continue + } + + // Apply the same skip rules as flat mode. + if shouldSkipLine(line) { continue } + + if sections.isEmpty { + preMarkerLines.append(line) + } else { + sections[sections.count - 1].lines.append(line) + } + } + + if sections.isEmpty { + throw SchemaParsingError(text: "Layered schema has a layer marker regex match but no ===N=== sections were extracted; this indicates a parser bug.") + } + + // Attach pre-marker lines to the first (lowest) declared layer. + if !preMarkerLines.isEmpty { + sections[0].lines.insert(contentsOf: preMarkerLines, at: 0) + } + + var layers: [(layerNumber: Int, constructors: [ConstructorDescription])] = [] + for (layerNumber, sectionLines) in sections { + var constructors: [ConstructorDescription] = [] + for line in sectionLines { + do { + constructors.append(try parseConstructor(string: line)) + } catch let e { + print("Error while parsing line (layer \(layerNumber)):\n\(line)\n") + print("\(e)") + throw e + } + } + layers.append((layerNumber, constructors)) + } + + return .layered(layers: layers) } private static func parseConstructor(string: String) throws -> ConstructorDescription { diff --git a/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift b/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift deleted file mode 100644 index 4a4feb1570..0000000000 --- a/build-system/SwiftTL/Sources/SwiftTL/LegacyOrderParser.swift +++ /dev/null @@ -1,128 +0,0 @@ -import Foundation - -enum LegacyOrderParser { - struct LegacyOrderParsingError: Error, CustomStringConvertible { - var text: String - - var description: String { - return self.text - } - } - - static func parseConstructorOrder(data: String) throws -> [(typeName: QualifiedName, constructorName: String)] { - let lines = data.split(separator: "\n") - - var result: [(typeName: QualifiedName, constructorName: String)] = [] - - for line in lines { - if let startRange = line.range(of: " = { return Api."), let endRange = line.range(of: "($0) }", options: [.backwards], range: nil) { - let parseString = line[startRange.upperBound ..< endRange.lowerBound] - let components = parseString.components(separatedBy: ".parse_") - if components.count != 2 { - continue - } - - result.append((QualifiedName(string: components[0]), components[1])) - } - } - - return result - } - - static func parseTypeOrder(data: String) throws -> (types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName]) { - var resultTypes: [(typeName: QualifiedName, constructorNames: [String])] = [] - var resultFunctions: [QualifiedName] = [] - - let namespaces = data.components(separatedBy: "public extension Api {\n") - - enum ParseSection { - case types - case functions - } - - for namespaceData in namespaces { - if namespaceData.isEmpty { - continue - } - guard let firstNewline = namespaceData.range(of: "\n") else { - throw LegacyOrderParsingError(text: "No newline in the beginning of the namespace section") - } - let namespaceName: String? - let namespaceContentData: String - let parseSection: ParseSection - if let prefixRange = namespaceData.range(of: " public enum ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex { - namespaceName = nil - namespaceContentData = namespaceData - parseSection = .types - } else if let prefixRange = namespaceData.range(of: " indirect public enum ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex { - namespaceName = nil - namespaceContentData = namespaceData - parseSection = .types - } else if let prefixRange = namespaceData.range(of: " public struct functions {", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.upperBound == firstNewline.lowerBound { - namespaceName = nil - namespaceContentData = namespaceData - parseSection = .functions - } else { - guard let prefixRange = namespaceData.range(of: "public struct ", options: [], range: namespaceData.startIndex ..< firstNewline.lowerBound), prefixRange.lowerBound == namespaceData.startIndex else { - throw LegacyOrderParsingError(text: "Missing header prefix in the beginning of the namespace section") - } - guard let trailerRange = namespaceData.range(of: " {", options: [], range: prefixRange.upperBound ..< firstNewline.lowerBound) else { - throw LegacyOrderParsingError(text: "Missing trailing suffix in the beginning of the namespace section") - } - namespaceName = String(namespaceData[prefixRange.upperBound ..< trailerRange.lowerBound]) - namespaceContentData = String(namespaceData[firstNewline.upperBound...]) - parseSection = .types - } - - let namespaceContentLines = namespaceContentData.split(separator: "\n") - - switch parseSection { - case .types: - var currentType: (typeName: QualifiedName, constructorNames: [String])? - for line in namespaceContentLines { - if let typePrefixRange = line.range(of: " public enum "), typePrefixRange.lowerBound == line.startIndex, let typeSuffixRange = line.range(of: ": TypeConstructorDescription {"), typeSuffixRange.upperBound == line.endIndex { - let typeName = String(line[typePrefixRange.upperBound ..< typeSuffixRange.lowerBound]) - if let currentType = currentType { - resultTypes.append(currentType) - } - currentType = (QualifiedName(namespace: namespaceName, value: typeName), []) - } else if let typePrefixRange = line.range(of: " indirect public enum "), typePrefixRange.lowerBound == line.startIndex, let typeSuffixRange = line.range(of: ": TypeConstructorDescription {"), typeSuffixRange.upperBound == line.endIndex { - let typeName = String(line[typePrefixRange.upperBound ..< typeSuffixRange.lowerBound]) - if let currentType = currentType { - resultTypes.append(currentType) - } - currentType = (QualifiedName(namespace: namespaceName, value: typeName), []) - } else if currentType != nil, let constructorPrefixRange = line.range(of: " case "), constructorPrefixRange.lowerBound == line.startIndex { - let constructorName: String - if let bracketRange = line.range(of: "(") { - constructorName = String(line[constructorPrefixRange.upperBound ..< bracketRange.lowerBound]) - } else { - constructorName = String(line[constructorPrefixRange.upperBound...]) - } - currentType?.constructorNames.append(constructorName) - } - } - if let currentType = currentType { - resultTypes.append(currentType) - } - case .functions: - var currentNamespace: String? - for line in namespaceContentLines { - if let namespacePrefixRange = line.range(of: " public struct "), namespacePrefixRange.lowerBound == line.startIndex, let namespaceSuffixRange = line.range(of: " {"), namespaceSuffixRange.upperBound == line.endIndex { - currentNamespace = String(line[namespacePrefixRange.upperBound ..< namespaceSuffixRange.lowerBound]) - } else if let functionPrefixRange = line.range(of: " public static func "), functionPrefixRange.lowerBound == line.startIndex { - let functionName: String - if let bracketRange = line.range(of: "(") { - functionName = String(line[functionPrefixRange.upperBound ..< bracketRange.lowerBound]) - } else { - functionName = String(line[functionPrefixRange.upperBound...]) - } - resultFunctions.append(QualifiedName(namespace: currentNamespace, value: functionName)) - } - } - } - } - - return (resultTypes, resultFunctions) - } -} diff --git a/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift b/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift index 5fbd3a90d9..431300a431 100644 --- a/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift +++ b/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift @@ -199,7 +199,114 @@ enum Resolver { return types.values.sorted(by: { $0.name < $1.name }) } - + + static func resolveLayeredTypes( + layers: [(layerNumber: Int, constructors: [DescriptionParser.ConstructorDescription])] + ) throws -> [(layerNumber: Int, types: [SumType])] { + // Running state: for each constructor name, the target type name and the raw description. + // We keep raw descriptions (not resolved forms) because a later-layer constructor may + // introduce new target-type names, and resolveTypeReference needs the final target-type set. + var liveConstructors: [QualifiedName: (typeName: QualifiedName, description: DescriptionParser.ConstructorDescription)] = [:] + var result: [(layerNumber: Int, types: [SumType])] = [] + + for (layerNumber, layerConstructors) in layers { + // Apply this layer's constructors to the running map with last-wins semantics. + for constructorDescription in layerConstructors { + switch constructorDescription.type { + case let .type(name): + if !name.value[name.value.startIndex].isUppercase { + throw ResolutionError(text: "Type constructor \(constructorDescription.name) -> \(name): the resulting type name should begin with a capital letter") + } + liveConstructors[constructorDescription.name] = (name, constructorDescription) + case let .generic(name, argumentType): + throw ResolutionError(text: "Type constructor \(constructorDescription.name) can not be used to construct a generic type \(name)<\(argumentType)>") + } + } + + // Note: a constructor reassigned to a different target type in a later layer is + // removed from its old type's constructor set. If that drops the old type to zero + // constructors, it vanishes from this snapshot, and any unrelated argument that + // still references the old type via boxedType(...) will fail to resolve here. + // secret_scheme.tl does not exercise this case (same-name constructors always + // target the same type), but the rule applies if a future layered schema does. + // Snapshot: group by target type, resolve. + var constructedTypes: [QualifiedName: [DescriptionParser.ConstructorDescription]] = [:] + var constructorNameToType: [QualifiedName: QualifiedName] = [:] + for (ctorName, entry) in liveConstructors { + constructedTypes[entry.typeName, default: []].append(entry.description) + constructorNameToType[ctorName] = entry.typeName + } + + func resolveTypeReference(description: DescriptionParser.TypeReferenceDescription) throws -> TypeReference { + switch description { + case let .type(name): + if let resolvedBuiltinType = resolveBuiltinType(name: name) { + return resolvedBuiltinType + } + if name.value[name.value.startIndex].isUppercase { + if let _ = constructedTypes[name] { + return .boxedType(name) + } else { + throw ResolutionError(text: "Unresolved type \(name) in layer \(layerNumber)") + } + } else { + if let typeName = constructorNameToType[name] { + return .bareConstructor(typeName: typeName, name: name) + } else { + throw ResolutionError(text: "Unresolved type constructor \(name) in layer \(layerNumber)") + } + } + case let .generic(name, argumentType): + if name == "vector" { + return .bareVector(try resolveTypeReference(description: .type(name: argumentType))) + } else if name == "Vector" { + return .boxedVector(try resolveTypeReference(description: .type(name: argumentType))) + } else { + throw ResolutionError(text: "Unresolved generic type \(name) in layer \(layerNumber)") + } + } + } + + func resolveArgument(existingArguments: [Argument], description: DescriptionParser.ArgumentDescription) throws -> Argument { + return Argument( + name: description.name, + type: try resolveTypeReference(description: description.type), + condition: try description.condition.flatMap { condition -> Argument.Condition in + if !existingArguments.contains(where: { $0.name == condition.fieldName }) { + throw ResolutionError(text: "Unresolved conditional field reference to \(condition.fieldName) in layer \(layerNumber)") + } + return Argument.Condition(fieldName: condition.fieldName, bitIndex: condition.bitIndex) + } + ) + } + + var types: [QualifiedName: SumType] = [:] + for (typeName, constructorDescriptions) in constructedTypes { + let type = SumType(name: typeName) + for constructorDescription in constructorDescriptions { + var arguments: [Argument] = [] + for argumentDescription in constructorDescription.arguments { + arguments.append(try resolveArgument(existingArguments: arguments, description: argumentDescription)) + } + guard let id = constructorDescription.explicitId else { + throw ResolutionError(text: "Constructor \(constructorDescription.name) does not have an id") + } + type.constructors[constructorDescription.name] = SumType.Constructor( + name: constructorDescription.name, + id: id, + arguments: arguments + ) + } + types[type.name] = type + } + + let sortedTypes = types.values.sorted(by: { $0.name < $1.name }) + result.append((layerNumber, sortedTypes)) + } + + return result + } + static func resolveFunctions(types: [SumType], functionDescriptions: [DescriptionParser.ConstructorDescription]) throws -> [Function] { var functions: [QualifiedName: Function] = [:] diff --git a/build-system/SwiftTL/Sources/SwiftTL/main.swift b/build-system/SwiftTL/Sources/SwiftTL/main.swift index 30aa736667..df56ef9afa 100644 --- a/build-system/SwiftTL/Sources/SwiftTL/main.swift +++ b/build-system/SwiftTL/Sources/SwiftTL/main.swift @@ -27,26 +27,15 @@ if CommandLine.arguments.count < 3 { let schemeFilePath = CommandLine.arguments[1] let outputDirectoryPath = CommandLine.arguments[2] +var apiPrefix = "Api" -var stubFunctions = false -var printConstructorsRange: (start: Int, end: Int)? = nil -for arg in CommandLine.arguments { - if arg == "--stub-functions" { - stubFunctions = true - } - if arg.hasPrefix("--print-constructors=") { - let value = String(arg.dropFirst("--print-constructors=".count)) - let parts = value.split(separator: "-") - if parts.count == 2, let start = Int(parts[0]), let end = Int(parts[1]) { - if start > end { - print("Error: Invalid range for --print-constructors: start (\(start)) must be <= end (\(end))") - exit(1) - } - printConstructorsRange = (start, end) - } else { - print("Error: Invalid format for --print-constructors. Expected: --print-constructors=N-M (e.g., --print-constructors=0-10)") - exit(1) - } +for arg in CommandLine.arguments[3...] { + if arg.hasPrefix("--api-prefix=") { + let value = String(arg.dropFirst("--api-prefix=".count)) + apiPrefix = value + } else { + print("Error: Unknown argument: \(arg)") + exit(1) } } @@ -55,75 +44,63 @@ guard let data = try? String(contentsOfFile: schemeFilePath) else { exit(1) } -do { - let parsedData = try DescriptionParser.parse(data: data) - let resolvedTypes = try Resolver.resolveTypes(constructors: parsedData.constructors) - var resolvedFunctions = try Resolver.resolveFunctions(types: resolvedTypes, functionDescriptions: parsedData.functions) - - resolvedFunctions.append(Resolver.Function(name: QualifiedName(namespace: "help", value: "test"), id: 0xc0e202f7, arguments: [], result: .boxedType(QualifiedName(namespace: nil, value: "Bool")))) - - var constructorOrder: [(typeName: QualifiedName, constructorName: String)] = [] - var typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])] = [] - - let sortedTypes = resolvedTypes.sorted(by: { $0.name < $1.name }) +do { + let parsedSchema = try DescriptionParser.parse(data: data) - if let range = printConstructorsRange { - print("--- CONSTRUCTORS ---") - for (index, type) in sortedTypes.enumerated() { - if index >= range.start && index < range.end { - for constructor in type.constructors.values.sorted(by: { $0.name < $1.name }) { - let storedArguments = constructor.arguments.filter { - if case .boolTrue = $0.type { return false } - return true - } - if !storedArguments.isEmpty { - let fieldNames = storedArguments.map { $0.name.camelCased } - print("\(constructor.name.value):\(fieldNames.joined(separator: ","))") - } - } + try FileManager.default.createDirectory(at: URL(fileURLWithPath: outputDirectoryPath), withIntermediateDirectories: true, attributes: nil) + + switch parsedSchema { + case let .flat(constructors, functions): + let resolvedTypes = try Resolver.resolveTypes(constructors: constructors) + var resolvedFunctions = try Resolver.resolveFunctions(types: resolvedTypes, functionDescriptions: functions) + + resolvedFunctions.append(Resolver.Function(name: QualifiedName(namespace: "help", value: "test"), id: 0xc0e202f7, arguments: [], result: .boxedType(QualifiedName(namespace: nil, value: "Bool")))) + + var constructorOrder: [(typeName: QualifiedName, constructorName: String)] = [] + var typeOrder: [(types: [(typeName: QualifiedName, constructorNames: [String])], functions: [QualifiedName])] = [] + + let sortedTypes = resolvedTypes.sorted(by: { $0.name < $1.name }) + + for type in sortedTypes { + for constructor in type.constructors.values.sorted(by: { $0.name < $1.name }) { + constructorOrder.append((type.name, constructor.name.value)) } } - print("--- END CONSTRUCTORS ---") - print("Total types: \(sortedTypes.count)") - exit(0) - } - for type in sortedTypes { - for constructor in type.constructors.values.sorted(by: { $0.name < $1.name }) { - constructorOrder.append((type.name, constructor.name.value)) + var totalConstructorCount = 0 + var currentConstructorCount = 0 + for type in sortedTypes { + if typeOrder.isEmpty || currentConstructorCount >= 32 { + typeOrder.append(([], [])) + currentConstructorCount = 0 + } + typeOrder[typeOrder.count - 1].types.append((type.name, type.constructors.values.sorted(by: { $0.name < $1.name }).map(\.name.value))) + currentConstructorCount += type.constructors.count + totalConstructorCount += type.constructors.count + if totalConstructorCount > 40 { } } - } - - var totalConstructorCount = 0 - var currentConstructorCount = 0 - for type in sortedTypes { - if typeOrder.isEmpty || currentConstructorCount >= 32 { - typeOrder.append(([], [])) - currentConstructorCount = 0 + + typeOrder.append(([], [])) + for function in resolvedFunctions.sorted(by: { $0.name < $1.name }) { + typeOrder[typeOrder.count - 1].functions.append(function.name) } - - typeOrder[typeOrder.count - 1].types.append((type.name, type.constructors.values.sorted(by: { $0.name < $1.name }).map(\.name.value))) - - currentConstructorCount += type.constructors.count - totalConstructorCount += type.constructors.count - - if totalConstructorCount > 40 { + + let generatedFiles = try CodeGenerator.generate(apiPrefix: apiPrefix, types: resolvedTypes, functions: resolvedFunctions, constructorOrder: constructorOrder, typeOrder: typeOrder) + + for (name, fileData) in generatedFiles { + let filePath = URL(fileURLWithPath: outputDirectoryPath).appendingPathComponent(name).path + let _ = try? FileManager.default.removeItem(atPath: filePath) + try fileData.write(toFile: filePath, atomically: true, encoding: .utf8) + } + + case let .layered(layers): + let resolvedLayers = try Resolver.resolveLayeredTypes(layers: layers) + for (layerNumber, types) in resolvedLayers { + let (filename, source) = try CodeGenerator.generateLayered(apiPrefix: apiPrefix, layerNumber: layerNumber, types: types) + let filePath = URL(fileURLWithPath: outputDirectoryPath).appendingPathComponent(filename).path + let _ = try? FileManager.default.removeItem(atPath: filePath) + try source.write(toFile: filePath, atomically: true, encoding: .utf8) } - } - - typeOrder.append(([], [])) - for function in resolvedFunctions.sorted(by: { $0.name < $1.name }) { - typeOrder[typeOrder.count - 1].functions.append(function.name) - } - - try FileManager.default.createDirectory(at: URL(fileURLWithPath: outputDirectoryPath), withIntermediateDirectories: true, attributes: nil) - - let generatedFiles = try CodeGenerator.generate(types: resolvedTypes, functions: resolvedFunctions, constructorOrder: constructorOrder, typeOrder: typeOrder, stubFunctions: stubFunctions) - - for (name, fileData) in generatedFiles { - let filePath = URL(fileURLWithPath: outputDirectoryPath).appendingPathComponent(name).path - let _ = try? FileManager.default.removeItem(atPath: filePath) - try fileData.write(toFile: filePath, atomically: true, encoding: .utf8) } } catch let e { print("\(e)") diff --git a/build-system/decrypt.rb b/build-system/decrypt.rb deleted file mode 100644 index 1183ebf40d..0000000000 --- a/build-system/decrypt.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'base64' -require 'openssl' -require 'securerandom' - -class EncryptionV1 - ALGORITHM = 'aes-256-cbc' - - def decrypt(encrypted_data:, password:, salt:, hash_algorithm: "MD5") - cipher = ::OpenSSL::Cipher.new(ALGORITHM) - cipher.decrypt - - keyivgen(cipher, password, salt, hash_algorithm) - - data = cipher.update(encrypted_data) - data << cipher.final - end - - private - - def keyivgen(cipher, password, salt, hash_algorithm) - cipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm) - end -end - -# The newer encryption mechanism, which features a more secure key and IV generation. -# -# The IV is randomly generated and provided unencrypted. -# The salt should be randomly generated and provided unencrypted (like in the current implementation). -# The key is generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters. -# -# Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550 -class EncryptionV2 - ALGORITHM = 'aes-256-gcm' - - def decrypt(encrypted_data:, password:, salt:, auth_tag:) - cipher = ::OpenSSL::Cipher.new(ALGORITHM) - cipher.decrypt - - keyivgen(cipher, password, salt) - - cipher.auth_tag = auth_tag - - data = cipher.update(encrypted_data) - data << cipher.final - end - - private - - def keyivgen(cipher, password, salt) - keyIv = ::OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 10_000, length: 32 + 12 + 24, hash: "sha256") - key = keyIv[0..31] - iv = keyIv[32..43] - auth_data = keyIv[44..-1] - - #puts "key: #{key.inspect}" - #puts "iv: #{iv.inspect}" - #puts "auth_data: #{auth_data.inspect}" - - cipher.key = key - cipher.iv = iv - cipher.auth_data = auth_data - end -end - -class MatchDataEncryption - V1_PREFIX = "Salted__" - V2_PREFIX = "match_encrypted_v2__" - - def decrypt(base64encoded_encrypted:, password:) - stored_data = Base64.decode64(base64encoded_encrypted) - if stored_data.start_with?(V2_PREFIX) - salt = stored_data[20..27] - auth_tag = stored_data[28..43] - data_to_decrypt = stored_data[44..-1] - - e = EncryptionV2.new - e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt, auth_tag: auth_tag) - else - salt = stored_data[8..15] - data_to_decrypt = stored_data[16..-1] - e = EncryptionV1.new - begin - # Note that we are not guaranteed to catch the decryption errors here if the password or the hash is wrong - # as there's no integrity checks. - # see https://github.com/fastlane/fastlane/issues/21663 - e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt) - # With the wrong hash_algorithm, there's here 0.4% chance that the decryption failure will go undetected - rescue => _ex - # With a wrong password, there's a 0.4% chance it will decrypt garbage and not fail - fallback_hash_algorithm = "SHA256" - e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt, hash_algorithm: fallback_hash_algorithm) - end - end - end -end - - -class MatchFileEncryption - def decrypt(file_path:, password:, output_path: nil) - output_path = file_path unless output_path - content = File.read(file_path) - e = MatchDataEncryption.new - decrypted_data = e.decrypt(base64encoded_encrypted: content, password: password) - File.binwrite(output_path, decrypted_data) - end -end - - -if ARGV.length != 3 - print 'Invalid command line' -else - dec = MatchFileEncryption.new - dec.decrypt(file_path: ARGV[1], password: ARGV[0], output_path: ARGV[2]) -end diff --git a/docs/instantpage-richtext.md b/docs/instantpage-richtext.md new file mode 100644 index 0000000000..0ff5ae2b62 --- /dev/null +++ b/docs/instantpage-richtext.md @@ -0,0 +1,469 @@ +# InstantPage V2 & rich-text message rendering + +This file documents the **rich-text message** pipeline and the **InstantPage V2** renderer that backs it. + +A rich message is a `RichTextMessageAttribute` carrying an `InstantPage` (sent with `text: ""`), produced when typed markdown contains structure the regular message-entity set can't represent (headings, lists, tables, formulas, nested blockquotes) and drawn by `ChatMessageRichDataBubbleContentNode` via the InstantPage V2 layout/renderer — including AI-streaming progressive reveal, inline custom emoji, and entity (mention / hashtag / …) cases. It also covers the send / edit / copy / paste round-trips between markdown and `InstantPage`. + +These are detailed, non-obvious invariants — read the relevant section before touching the corresponding code. (Moved out of `CLAUDE.md` to keep that file focused; `CLAUDE.md` retains a brief pointer back to here.) + +## AI streaming animation (rich-text bubbles) + +`ChatMessageRichDataBubbleContentNode` progressively reveals InstantPage V2 content while `TypingDraftMessageAttribute` is on the message. Mirrors the older animation in `ChatMessageTextBubbleContentNode`, adapted to the heterogeneous V2 layout. The "Thinking…" indicator is now server-sent as `InstantPageBlock.thinking` rendered inside the pageView (see "InstantPage thinking blocks" section). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramUI/Components/StreamingTextReveal/Sources/TextRevealController.swift` | Pacing controller, shared by both bubbles. EWMA inter-arrival → velocity-smoothed cursor. | +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` (`InstantPageV2TextView`) | Drawing split: private `TextRenderView` does `draw(_)` inside a `renderContainer` whose layer carries a `revealMaskLayer`; new chars spawn cropped `SnippetLayer` siblings of the render container that animate in (blur + alpha + scale + position) and are absorbed into the mask on completion. Ported from `InteractiveTextComponent`. | +| `submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift` | `InstantPageV2RevealCostMap` + `InstantPageV2View.applyReveal(revealedCount:costMap:animated:)`. Bridges the global width-based cursor to per-text-view char counts (via `charCountForWidthBudget`) and per-item visibility / table-row pop-in. | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `InstantPageTextLine.characterRects` (line-local CT coords, baseline-relative positive-up) populated when `computeRevealCharacterRects: true` is passed to `layoutInstantPageV2(...)`. Uses `CTFontGetBoundingRectsForGlyphs` for actual glyph ink, not advance widths. | +| `submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/...` | Streaming detection (`TypingDraftMessageAttribute`), display-link wiring, container sizing. The hardcoded "Thinking…" header was removed; thinking is now rendered by the pageView via `InstantPageBlock.thinking`. | + +### Non-obvious invariants + +- **Cost unit is points of width, not characters.** Each item's cost = its width in points along the reading direction. Text contributes sum of glyph ink widths; non-text items contribute `frame.width`. Table cells are floored at `cell.frame.width` so narrow- or empty-cell tables don't race through the cursor. Reveal pace becomes "points per second" — uniform across content types. +- **Mask uses per-glyph ink bounds, unioned per line.** Each revealed glyph's mask rect comes from `CTFontGetBoundingRectsForGlyphs` (not advance widths) so italics, accents, descenders are covered exactly. Per line, glyphs are unioned into one mask rect; consecutive fully-revealed lines union further — fully-revealed prefix is always one `CALayer`. +- **`containerNode` does ALL the clipping.** During streaming, containerNode is sized to `revealedItemsMaxY` (no header offset, no closing pad; `streamingHeaderOffset` is `0.0`). The bubble itself is taller (`revealedContentSize.height + 2`) — the strip below containerNode is empty bubble background. pageView keeps its full `pageLayout.contentSize`; anything past containerNode's bottom is clipped at containerNode (`clipsToBounds = true` set in init). Do NOT shorten the pageView or set `pageView.clipsToBounds`. +- **The pageView is REUSED across `stableVersion` bumps for the same message id.** `ensurePageView` calls `existing.renderContext?.updateContent(webpage:)` (where `webpage` is now a `public private(set) var` with an `updateContent` mutator) and returns the existing view; `update(layout:)` then diffs item views by stable id, tearing down only views whose block was removed. The pageView is rebuilt only when the bubble is recycled with a different message or webpage. The reveal cursor on `TextRevealController` persists across chunks; the seed re-apply (`applyReveal(revealedCount: previousAnimateGlyphCount, …, animated: false)`) is now a continuation from the reused views' state, eliminating the per-chunk flash-of-full-text-then-mask that required the earlier from-scratch re-seed. +- **Layout cache key includes `message.stableVersion`.** Each AI chunk bumps stableVersion; without this the cached layout would shadow newly-arrived content. +- **`TypingDraftMessageAttribute` is the streaming gate.** Same trigger TextBubble uses. The InstantPage's `isComplete` flag is informational only. +- **Width-based cost → char count bridge.** Mask APIs (`updateRevealCharacterCount`) still take character counts. `applyRevealEntry` calls `charCountForWidthBudget(textItem:widthBudget:)` to translate the width-based local cursor into the per-text-view character count. +- **The hardcoded "Thinking…" header was removed.** `streamingStatusTextNode`, `streamingStatusShimmerView`, and the header-layout machinery no longer exist. `streamingHeaderOffset` is now a constant `0.0` — the pageView starts at the top of the bubble. The "Thinking…" indicator is now server-sent as `InstantPageBlock.thinking` and rendered inside the pageView (see "InstantPage thinking blocks" section below). +- **Display-link tick re-layouts on extent change.** Tick reads `revealedContentSize` at the new cursor; if the height differs from the previous cursor, calls `requestFullUpdate`. So the bubble grows in flight when the cursor crosses a line/item boundary, not just between chunks. Tick passes `animated: true` to `applyReveal` to fire the snippet pop-in. + +### Status node (date/time/checks) positioning + +The `ChatMessageDateAndStatusNode` mirrors TextBubble's placement, adapted to the heterogeneous V2 layout. The node is a child of `self` (the content node), **not** of the clipping `containerNode`, so it is never clipped — the bubble height must be grown to contain it. + +- **X is a fixed left edge, not the last line's `minX`.** Anchor x = `pageHorizontalInset` (10pt, the page layout's text inset; pageView sits at self-x 0). The status layout is measured with `boundingWidth - 2·pageHorizontalInset` (mirrors TextBubble's `boundingWidth - sideInsets`) so the right-aligned date lands at the right inset instead of off the bubble. Using `lastTextLineFrame.minX` (which is large for nested/indented last lines) shoved the date off to the right. +- **Trail the last line only when the bottom-most item is text.** `lastTextLineFrameIfLastItemIsText(in:)` (in `InstantPageV2Layout.swift`) returns the last line frame *only* when the bottom-most top-level item (max `maxY`) is a `.text`; otherwise nil, so the date wraps below all content (anchored at `contentSize.height`). For tables/images/etc. the date must not trail text buried above the final item. +- **InstantPage draws the baseline at the line frame's `maxY`** (`InstantPageRenderer` draws each line at `lineOrigin.y + lineFrame.height`), so the visible text of a plain line sits ~5pt below `maxY`. A date that **trails** on the line (`statusHeight == 0`) adds `trailingBottomPadding` (5pt) to align with the text; a date that **wraps** onto its own line below (`statusHeight > 0`) sits at the bare `maxY`. The pad is 0 for lines taller than their font line height (a tall inline attachment, e.g. a formula, already pushes `maxY` down). `lastTextLineFrameIfLastItemIsText` returns `(frame, trailingBottomPadding)`; the bubble applies the pad only in the trailing case. +- **Bubble height leaves ~6pt below the date.** One unified formula for all cases: `boundingSize.height = max(boundingSize.height, statusBottomEdge + 6.0)`, where `statusBottomEdge = statusAnchorY + max(1, statusHeight)`. The `statusAnchorY` in the measure (`continue`) closure must mirror the `statusFrameY` in the apply closure exactly, or the date will be clipped/misplaced. (`streamingHeaderOffset` is `0.0` — there is no header offset to add.) 6pt matches TextBubble's bottom bubble inset. +- **`hasDraft` adds the same 6pt at the streaming site.** The status max() above is gated by `!hasDraft`, so during streaming (status hidden, alpha=0) it can't supply the bubble's bottom inset. A separate `boundingSize.height += 6.0` inside `if hasDraft` in the SizeBlock closure does it instead — same 6pt, so the streaming bubble's bottom breathing room matches its post-stream height and there's no 6pt grow-pop when the status node fades in at finalize. The `hadDraft && !hasDraft` finalize pass doesn't need it because `!hasDraft` re-enables the status max(). If you ever refactor the `+6.0` constant out of the status max() into a `bottomInset` (TextBubble's pattern), kill this separate term at the same time — they're two ends of the same invariant. + +## InstantPage V2 table — flush frame, inset borders, rounded corners + +A V2 `.table` block's item frame is **full-width / flush** with the bubble interior (so a horizontally-scrollable wide table's scroll container bleeds edge-to-edge), but the actual grid **borders start at the body-text side inset** — matching the V1 renderer. The grid card also has a **10pt rounded outer border**. + +### Non-obvious invariants + +- **`InstantPageV2TableItem.contentInset` (= page `horizontalInset`) is the linchpin.** `layoutTable` (`InstantPageV2Layout.swift`) sizes columns against `contentBoundingWidth = boundingWidth − horizontalInset·2` (so a fitting table aligns with body text on both sides) and stores `contentInset` on the item; the item `frame.width` is the flush `boundingWidth`, and `contentSize.width` stays the **bare grid width** (`totalWidth`, no inset). +- **The renderer (`InstantPageV2TableView`) realizes the inset as a view shift, not baked coordinates.** In `init` AND `update` it shifts the grid `contentView` to `x: contentInset`, sets `scrollView.contentSize.width = contentSize.width + contentInset * 2.0` (**margin on both sides**, mirroring V1's `InstantPageScrollableNode`), and `scrollView.clipsToBounds = true`. Cells, inner border lines, and the title stay x=0-relative inside `contentView`, so the single shift carries them all; the rounded outer border is `contentView.layer`'s own border (see below), which wraps the shifted layer automatically. +- **Scrollable tables clip to the full width with no inset on the clip.** The inset lives inside the scroll content as a symmetric margin on both sides (`contentInset * 2.0`): a fitting table (`grid + 2·inset ≤ boundingWidth`) doesn't scroll and shows both-side inset; an overflowing table rests with its left border at the inset and scrolls until its right border reaches a matching trailing inset (it does **not** jam flush against the screen edge — matches V1). The scroll-indicator threshold and `contentSize.width` use the same `+ contentInset * 2.0`, so "does it scroll" is exactly `grid > boundingWidth − 2·inset`. +- **Manual cell-coordinate helpers MUST add `contentInset`.** Because the shift is a real `contentView` frame change, UIKit `hitTest` and `self.convert(_:to:)` paths (`propagateVisibilityRect`, the row-reveal mask) handle it automatically — but the *manual* coordinate helpers `findTextItem` / `collectSelectableTextItems` (the live tap / URL / text-selection path) compute cell/title positions arithmetically and must add `table.contentInset` to the x-offset, or in-cell hit-testing is off by the inset. (These helpers still do **not** account for the table's live horizontal `scrollView.contentOffset` — a pre-existing limitation, so in-cell hit-testing is only correct at scroll offset 0.) The dead-but-symmetric `lastTextLineFrame(in:)` table branch has the same omission but has no callers. +- **The 10pt rounded outer border is `contentView.layer`'s own border, NOT sublayers.** `v2TableCornerRadius = 10.0` (`InstantPageV2Layout.swift`). The renderer sets `contentView.layer.cornerRadius`/`borderColor`/`borderWidth = bordered ? v2TableBorderWidth : 0.0` in BOTH `init` and `update` (the four straight outer-edge rect layers were removed; `lineLayers` now holds only inner grid lines). **Border-only — deliberately no `masksToBounds`:** `cornerRadius` rounds the layer's border without clipping contents (filled corner cells round their own fills separately — see next bullet), and there is **zero interaction with the streaming reveal mask** (`contentView.layer.mask`, set only during AI streaming) — the border reveals row-by-row with the rows and is part of the masked layer. The rounded card belongs to the grid (scrolls with it). For a non-empty-title table (never produced by markdown/AI), the border wraps title+grid since `contentView` includes the title region — an accepted, approved nuance. +- **Filled corner cells round their own fills to match the border.** A header/striped cell's background is a stripe `CALayer`; `tableStripeCornerMask(cellFrame:gridWidth:gridHeight:effectiveBorderWidth:)` detects which grid corners the cell's (grid-local) frame touches — `firstCol/firstRow` via `frame.min{X,Y} <= effectiveBorderWidth/2 + 0.5`, `lastCol/lastRow` via `frame.max{X,Y} >= grid{Width,Height} - …` (gridWidth = `item.contentSize.width`, gridHeight = `item.contentSize.height - gridOffsetY`) — and rounds only those corners: `stripe.cornerRadius = max(0, v2TableCornerRadius - effectiveBorderWidth)` (the `-borderWidth` leaves an even border ring; borderless → full radius) + `stripe.maskedCorners`, in BOTH `init` and `update`. A `CALayer`'s `backgroundColor` honors `cornerRadius`+`maskedCorners` with no `masksToBounds`. A full-width (colspan) header rounds both top corners; a one-row filled table rounds all four; bottom corners round only when the last row is filled. The empty-mask branch resets `cornerRadius = 0` **and** `maskedCorners = []` so reused stripes (persist across streaming chunks) don't keep stale rounding. Detection is grid-local, so it's independent of the `contentInset` shift / horizontal scroll. + +## InstantPage V2 block media — flush (edge-to-edge), un-rounded + +Every V2 block-media kind lays out **flush** with the bubble interior (0 inset, full bounding width) and **un-rounded** (cornerRadius 0). The bubble's existing rounded clipping container rounds any media that meets the bubble's top/bottom edge. V1 (`InstantPageLayout.swift`) is unchanged. (Audio is **also** full-width / x = 0 as of the V2 audio port, but it does not use this helper — it has its own `layoutAudio` arm; the wrapped `InstantPageAudioNode` supplies its own 17pt internal content inset. See the "InstantPage V2 audio/music" section below.) + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `instantPageV2MediaFrame(naturalSize:flush:cornerRadius:boundingWidth:horizontalInset:)` — the shared frame helper; `instantPageV2MediaEdgeBleed` constant; the `flush: Bool` parameter on `layoutTypedMediaWithCaption` (image/video/webEmbed-cover/map) and `layoutMediaWithCaption` (webEmbed-placeholder/postEmbed/channelBanner/relatedArticles). (Collage/slideshow and **audio** no longer route through these — see their dedicated sections.) | +| `submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift`, `…/InstantPageRenderer.swift` (`InstantPageV2MediaPlaceholderView`) | Renderer — **no change needed**: every media view + the placeholder view already does `clipsToBounds = item.cornerRadius > 0.0`, so cornerRadius 0 means the view doesn't self-clip; the bubble's `containerNode` clips. | +| `…/Chat/ChatMessageRichDataBubbleContentNode/…` | The clipping container: `containerNode` (`clipsToBounds = true`, `cornerRadius = layoutConstants.image.defaultCornerRadius` ≈ 15–16pt) is what rounds flush media at the bubble edge. | + +### Non-obvious invariants + +- **`flush` is a parameter, not inferred from cornerRadius.** **Every** remaining media call site now passes `flush: true`. Audio — the former lone `flush: false` caller — was moved to its own `layoutAudio` arm in the V2 audio port, so `instantPageV2MediaFrame`'s `flush == false` branch is now **dead code** (a candidate for a follow-up cleanup: drop the `flush` parameter and the inset branch entirely). On the flush path the helper forces the returned corner radius to `0` regardless of the caller's `cornerRadius` argument (the legacy `8.0`/`0.0` args at the call sites are now inert — kept as-is, documented in the helper). +- **Small images are NOT upscaled.** The `scale = min(availableWidth / naturalSize.width, 1.0)` cap is kept (now against `availableWidth = boundingWidth`). A small image stays at natural size, **flush-left at x = 0** (not stretched to full width). Large images (the common server/AI case) fill the width. +- **Full-width media bleeds `instantPageV2MediaEdgeBleed` (4pt) past the trailing edge.** The pageView sits at `x: -1` inside `containerNode` (a border-hiding hairline), so a frame at `x: 0, width: boundingWidth` falls ~1px short of the container's right rounded-clip edge → a 1px corner notch. A small over-bleed on **full-width** items only (`fillsWidth = scaledSize.width >= availableWidth - 1.0`) closes it; a genuinely small image gets no bleed. **The bleed never widens the bubble** because `layoutInstantPageV2` clamps `contentSize.width = min(maxX, boundingWidth)` (gated by `context.fitToWidth`, which both callers — the rich bubble and the send preview — pass `true`). +- **Captions stay inset.** `layoutCaptionAndCredit` is still called with the page `horizontalInset` and offset by the **un-bled** `scaledSize.height`; the caption/credit text is inset under a full-bleed image. The `isCover && captionHeight > 0` cover-padding block is unchanged. +- **Audio is no longer routed through this helper.** As of the V2 audio port it has a dedicated `layoutAudio` arm emitting a typed `.mediaAudio` item at a full-width (x = 0), height-48 frame (matching V1 `InstantPageLayout.swift`); the wrapped `InstantPageAudioNode` self-insets its content by 17pt, and audio does **not** participate in `instantPageV2MediaEdgeBleed` (its node background is transparent). See the dedicated "InstantPage V2 audio/music" section below. +- **`.map` blocks get a 600×300 (2:1) fallback when the sender omits dimensions.** AI/server-sent `.map` blocks can arrive with `dimensions == 0×0` (the wire `w`/`h` are *required* `Int32`, but the sender may put 0; our `pageBlockMap` parse and both serializers — Postbox `sw`/`sh`, FlatBuffers `required dimensions` — preserve whatever arrives, so the zero originates upstream). A zero `naturalSize.height` hits `instantPageV2MediaFrame`'s `else` branch and returns a **height-0** frame: the map collapses to no space, the caption slides up into it, and the V1 node's pin (positioned at `size.height*0.5 − 10 − pinSize/2`) floats over the caption. **The `.map` arm in `InstantPageV2Layout.swift` substitutes `PixelDimensions(600, 300)` whenever `width <= 0 || height <= 0`, and feeds that `effectiveDimensions` to BOTH the layout `naturalSize` AND the `InstantPageMapAttribute`** — the latter is essential because a `MapSnapshotMediaResource(width:0,height:0)` makes `MKMapSnapshotter` render nothing, so fixing only the frame would yield a correctly-sized *blank* box. Real web-article maps (the V1 renderer) always carry real dimensions, so V1 never trips this; the fallback is deliberately scoped to the V2 `.map` arm rather than V1 or the wire/parse layer. + +## InstantPage V2 audio/music + +`InstantPageBlock.audio` renders in V2 as a control **styled exactly like the standard music message bubble** (`ChatMessageInteractiveFileNode`'s music layout) — a dedicated `InstantPageV2AudioContentNode`, NOT the V1 `InstantPageAudioNode` (which V2 used in the first iteration and which still backs V1's full-page Instant View). It replaces the earlier inert grey `.mediaPlaceholder(kind: .audio)`. Playback stays on `InstantPageMediaPlaylist`, with two deliberate behavior changes for the rich-message context: the shared playlist identity is **message-scoped** so concurrent rich-message audio bubbles don't collide, and rich-message audio files are fetched via a **message reference** (not the synthesized webpage) so a stale file reference can revalidate. + +Specs: [`2026-06-02-instantpage-v2-audio-design.md`](docs/superpowers/specs/2026-06-02-instantpage-v2-audio-design.md) (initial port) + [`2026-06-02-instantpage-v2-audio-file-style-design.md`](docs/superpowers/specs/2026-06-02-instantpage-v2-audio-file-style-design.md) (file-bubble styling). Plans: [`2026-06-02-instantpage-v2-audio.md`](docs/superpowers/plans/2026-06-02-instantpage-v2-audio.md) + [`2026-06-02-instantpage-v2-audio-file-style.md`](docs/superpowers/plans/2026-06-02-instantpage-v2-audio-file-style.md). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift` | `InstantPageMediaPlaylistId` is a **public enum** — `.instantPage(webpageId:)` (V1 full-page IV) / `.richMessage(messageId:)` (V2 rich bubble). `InstantPageMediaPlaylist.init` takes an injected `playlistId:` (no longer derived from the webpage) and a `messageReference: MessageReference?` threaded into each `InstantPageMediaPlaylistItem`. The item's `fileReference(_:)` helper builds a `.message(message:media:)` file reference when a (resolvable-id) message reference is present, else the legacy `.webPage(...)`. | +| `submodules/InstantPageUI/Sources/InstantPageV2AudioContentNode.swift` | **The V2 control** — replicates `ChatMessageInteractiveFileNode`'s music layout: a Ø44 `SemanticStatusNode` (album art via `playerAlbumArt` + play/pause) + a small bottom-right `streamingStatusNode` download/progress overlay + title/performer `TextNode`s + a line `MediaPlayerScrubbingNode`. Big control play/pause from **our** `filteredPlaylistState`; small overlay download/progress from `messageMediaFileStatus`; tap via a `UITapGestureRecognizer` (`controlTapped` routes fetch / `play` / `togglePlayPause`); fetch via `messageMediaFileInteractiveFetched(fetchManager:…)`. | +| `submodules/InstantPageUI/Sources/InstantPageAudioNode.swift` | **V1 only** (full-page Instant View) — unchanged except `init` takes an injected `playlistId:`. No longer used by V2. | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `InstantPageV2MediaAudioItem` (frame/media/webPage — no cornerRadius/attributes); the `.mediaAudio` `InstantPageV2LaidOutItem` case + its `frame`/`offsetBy`/`collectMedias` arms; the `.audio` block's `layoutAudio` arm (full-width x = 0, height 44 — the file node's music `normHeight`; the `InstantPageMedia` carries `caption: nil`/`credit: nil`, the visible caption is a separate item via `layoutCaptionAndCredit`). | +| `submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift` | `InstantPageV2MediaAudioView` (hosts `InstantPageV2AudioContentNode` via the shared `WrapperRef` weak-box pattern; wires its `play`/`togglePlayPause`/`seek`/`fetch` closures + the `filteredPlaylistState` playback signal) + `handleOpenAudioTap` (builds the playlist + `setPlaylist`, mirroring V1's `InstantPageControllerNode.openMedia`). | +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` | `InstantPageV2RenderContext.message: MessageReference?` (carries both the playlist-key id via `.id` AND the file-fetch reference); the `.mediaAudio` arms in `stableId`/`reuse`/`makeItemView`. | +| `submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift` | `.mediaAudio` is a non-text reveal entry charging `frame.width` (like other media). | +| `submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift` | The `.message` media-reference revalidation arm also searches `RichTextMessageAttribute.instantPage.media` (not just `message.media`), so a stale instant-page file reference inside a rich message can recover. | +| rich bubble + send preview | `ChatMessageRichDataBubbleContentNode` passes `message: MessageReference(item.message)`; `ChatSendMessageRichTextPreview` passes `message: nil`. | + +### Non-obvious invariants + +- **The playlist key is message-scoped, NOT webpage-scoped, for rich bubbles.** Every rich message synthesizes its `TelegramMediaWebpage` with the SAME constant id `(namespace: 0, id: 0)` (`ChatMessageRichDataBubbleContentNode`), and `mediaIndex` restarts at 0 per page — so keying playback by `(webpageId, mediaIndex)` (V1's scheme) would make two audio bubbles on screen share/fight playback state (scrubber + play/pause icon). The discriminated `InstantPageMediaPlaylistId.richMessage(messageId)` isolates them. The audio view resolves `renderContext.message?.id` → `.richMessage(messageId)`, else `.instantPage(webpageId:)`; the send preview (no message) takes the webpage fallback — harmless since only one preview is ever on screen. The V1 full-page IV path is byte-identical (always `.instantPage(...)`). +- **`InstantPageMediaPlaylistId` had to become `public`.** It is exposed through `InstantPageMediaPlaylist`'s `public init`, which BrowserUI constructs cross-module; an internal type in a public initializer is a hard Swift compile error (independent of `-warnings-as-errors`). This surfaced only at full-build time — the per-module reasoning didn't catch it. +- **The big control's play/pause comes from OUR playlist, the small overlay's download/progress from the resource status — two separate signals.** The file node (`ChatMessageInteractiveFileNode`) for music keys its play/pause off the **peer-messages** playback model (`messageFileMediaPlaybackStatus` → `peerMessagesMediaPlaylistAndItemId`), which our attribute-embedded audio is NOT part of — so `InstantPageV2AudioContentNode` drives the big `statusNode` `.play`↔`.pause` from **our** `filteredPlaylistState` (keyed by the message-scoped `playlistId` + `InstantPageMediaPlaylistItemId(index:)`) and the small `streamingStatusNode` from `messageMediaFileStatus`. This split (rather than reusing the file node) is why the redesign is a replicated layout, not a hosted `ChatMessageInteractiveFileNode`. +- **Fetch MUST go through the fetch manager, not `freeMediaFileInteractiveFetched`.** `messageMediaFileStatus`'s progress (`.Fetching`) is derived from the fetch manager's `hasEntry` flag; `freeMediaFileInteractiveFetched` bypasses the manager (`hasEntry` stays false), so the overlay would stick on the static download icon and never show the animated ring. The control fetches via `messageMediaFileInteractiveFetched(fetchManager:messageId:messageReference:file:…)`. +- **Tap is a `UITapGestureRecognizer`, never an ASControl** (same invariant as the V1 `InstantPageAudioNode` play button): ASControl `.touchUpInside` is cancelled by the chat `ListView`'s gesture system. The plain `tapView` covers the whole control → `controlTapped` (fetch-when-remote / `togglePlayPause`-when-playing / `play`-else). +- **`InstantPageV2AudioContentNode.updatePresentationData` must refresh EVERYTHING theme/incoming-dependent.** `TextNode` (unlike `ASTextNode`) has no stored `attributedText` — the strings live in `titleAttributedString`/`descriptionAttributedString` and are fed to `TextNode.asyncLayout`. On an in-place theme/direction change `updatePresentationData` rebuilds those strings AND `statusNode.backgroundNodeColor` + `foregroundNodeColor` + `overlayForegroundNodeColor` + `scrubbingNode.updateColors(…)`; missing any leaves a stale-colored control. Font size is `presentationData.chatFontSize.baseDisplaySize` (plain `PresentationData` has no `.fontSize`). +- **Audio is NOT a gallery item.** `InstantPageV2MediaAudioView` does not register in the root media registry (no `didMoveToWindow`/`registerInRootRegistry`) and returns `nil` from `instantPageTransitionNode` / no-ops `instantPageUpdateHiddenMedia` — explicit per-class witnesses, not the protocol-extension default. Its media IS enrolled in `collectMedias`/`allMedias()` so `handleOpenAudioTap` can gather the page's sibling voice/music files for the playlist (matching V1's `mediasFromItems`). The `WrapperRef` weak box breaks the wrapper → node → closure → wrapper retain cycle (the `play` closure captures only the box + value locals, never `self`). +- **Full-width item frame, file-node internal layout.** The `.audio` arm lays the item at `x = 0, width = boundingWidth, height = 44` (the file node's music `normHeight`), NOT inset by `horizontalInset`. The control's internal geometry is copied from the file node's non-thumbnail music branch (Ø44 control at x = 3, `controlAreaWidth = 55`, title at x = 55). Music-only: any voice file renders music-style (no waveform/transcription). No edge-bleed. +- **Audio files fetch via a message reference (the former recipient-fetch risk is resolved).** `InstantPageMediaPlaylistItem.fileReference(_:)` builds `.message(message: messageReference, media: file)` when the playlist carries a **resolvable-id** `MessageReference` (rich bubbles), else the legacy `.webPage(...)` (V1 full-page IV, whose webpage is real). The fetch-reference fallback uses the same `message?.id != nil` test as the playlist-key fallback, so a `.none`-content reference degrades to the webpage path consistently. Because the rich-message file lives in `RichTextMessageAttribute.instantPage.media` (not `message.media`), `FetchedMediaResource.swift`'s `.message` revalidation arm was taught to search the attribute's instant page too — so a **stale** file reference can re-fetch the message and recover (a synthetic-`(0,0)`-webpage reference never could, because that webpage doesn't exist server-side). This also fixes a latent pre-existing bug: instant-page **image** references in rich messages couldn't revalidate either. +- **Fixed a dormant inverted `InstantPagePlaylistLocation.isEqual`** (it returned `false` for equal locations and `true` for unequal — backwards). `areSharedMediaPlaylistsEqual` ANDs the playlist `id` and `location`; it gates only seek-forwarding inside `setPlaylist`, a path the instant-page audio scrubber doesn't take (it uses `playlistControl(.seek)`), so the bug was inert. The corrected equality is safe even though all rich-message locations share the synthetic `(0,0)` webpageId: the `.richMessage(messageId)` **id** (ANDed in) disambiguates different rich-message playlists. + +## InstantPage V2 collage & slideshow blocks + +`InstantPageBlock.collage` and `.slideshow` (grouped photos/videos with a caption — only ever produced by **real web Instant View articles**; nothing on the markdown/AI path emits them) render in V2 by porting V1. Collage flattens into the existing media-item machinery; slideshow is a dedicated interactive carousel. + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `layoutCollage(...)` — mosaic via `chatMessageBubbleMosaicLayout` (the `MosaicLayout` module, same engine grouped messages use), emitting one existing `.mediaImage`/`.mediaVideo` item per cell. `layoutSlideshow(...)` + the `InstantPageV2SlideshowItem` laid-out item (+ its `frame`/`offsetBy`/`collectMedias` arms). | +| `submodules/InstantPageUI/Sources/InstantPageV2SlideshowView.swift` | The carousel view: a paged `UIScrollView` of `InstantPageImageNode` pages + a `PageControlNode`, with all pages created **eagerly**. | +| `…/InstantPageRenderer.swift` | `InstantPageItemView.instantPageTransitionNode(for:)` / `instantPageUpdateHiddenMedia(_:)` (gallery hooks, nil/no-op defaults); `transitionArgsFor`/`applyHiddenMedia` dispatch through them. The `.slideshow` arms in `InstantPageV2ItemKind`/`stableId`/`reuse`/`makeItemView`. | +| `…/InstantPageV2RevealCost.swift` | `.slideshow` is a non-text reveal entry (collage cells already are, being top-level media items). | + +### Non-obvious invariants + +- **Collage is a flatten, not a container.** `layoutCollage` computes the mosaic, then emits each cell as an ordinary top-level `.mediaImage`/`.mediaVideo` item (cornerRadius 0) into the parent layout — exactly as V1 does (`flattenedItemsWithOrigin`). Consequence: gallery enumeration (`allMedias`), the media registry, hidden-media, the reveal-cost map, and view reuse all handle collage cells **for free**, with no collage-specific code in any of those subsystems. There is **no** `.collage` laid-out item or view. +- **Right-edge collage cells bleed 4pt** (`instantPageV2MediaEdgeBleed`, applied only to `MosaicItemPosition.right` cells) for the same bubble-rounded-clip reason as full-width single media; interior gaps are the mosaic's 1pt spacing; outer corners are rounded by the bubble's `containerNode`. +- **Slideshow IS a container** (it's swipeable), so it gets its own laid-out item + view, unlike collage. Adding the `.slideshow` case to `InstantPageV2LaidOutItem` forces a `.slideshow` arm in every no-`default` switch over it: `frame`, `offsetBy`, `stableId`, `reuse`, `makeItemView`, and the reveal-cost `computeEntries` (plus `collectMedias`, which has a `default` but needs the arm to enumerate slideshow medias for the gallery). +- **Slideshow pages are created eagerly, deviating from V1's lazy central±1 paging.** In a chat bubble a slideshow is a handful of images, so eager creation avoids V1's index bookkeeping and makes the gallery transition source available for **every** page (even off-screen). Height = the tallest image `fitted(boundingWidth × 1200)`; only `.image` inner blocks render (matches V1 — videos become empty pages). +- **The slideshow registers under EVERY contained media index, and re-registers on an in-window rebuild.** Its stableId is positional (`.positional(.slideshow, position)`, not `.media(index)` like the static media views), so it can be reused for a *different* slideshow at the same block position; `rebuildPages()` re-runs `registerMedias()` (guarded by `window != nil`) so the new indices land in the registry. The gallery hooks iterate the live page nodes and match by `InstantPageMedia` identity, so registering one view under N indices is idempotent. +- **The 4 static media views answer the gallery hooks with explicit per-class witnesses, NOT a shared protocol-extension override** — an extension-only implementation is statically dispatched and would silently bind to the nil default when invoked through the `InstantPageItemView`-typed registry wrapper. + +## InstantPage V2 text item height (true font line box) + +`layoutTextItem` (`InstantPageV2Layout.swift`) sizes a `.text` item to the **true font line height**, not the cap box. A single-line item measures exactly `fontAscent + fontDescentBelowBaseline` (`A + D`); the old behavior was the cap box `fontLineHeight = floor(fontAscent + fontDescent)` (`A − D`). + +### Non-obvious invariants + +- **Two edits in `layoutTextItem`:** the line stack starts at `lineBoxTopInset = max(0, fontAscent − fontLineHeight)` (was `0`), and the returned height is `lines.last.frame.maxY + extraDescent + fontDescentBelowBaseline` (the `+ fontDescentBelowBaseline` contains the last line's descender). Net: every text item grows ~`(A − L) + D` (~8pt @17pt) and its glyphs draw ~`lineBoxTopInset` (~4pt) lower within their box; the page grows. +- **Per-line frames stay the cap box** (`height = lineAscent = fontLineHeight`). Only the stack's starting origin moves and the total is padded — so the baseline is still drawn at each line frame's `maxY`, inter-line advance (`lineAscent + fontLineSpacing + extraDescent`) is unchanged, and decorations / inline attachments / `characterRect` / the reveal mask (all line-frame-relative) translate consistently. +- **`lineBoxTopInset` is exact, NOT pixel-snapped** — it is an intra-item line offset; crispness rides on the item's own pixel-snapped frame origin (intra-item line positions may already be fractional, e.g. after a non-integral `extraDescent`). +- **Formulas / tall inline content still inflate** via `lineAscent`/`extraDescent`; the `"\u{200b}"`+anchors `height = 0` case is preserved. +- **Inline custom emoji are sized to ≈ the line box** so they fit the taller box rather than overflowing it (see "Inline custom emoji"). + +## Inline custom emoji (RichText.textCustomEmoji) + +`RichText.textCustomEmoji(fileId:alt:)` renders an inline **animated** custom emoji inside rich-data bubbles. Covers API parsing, Postbox + FlatBuffers serialization, and display in the InstantPage V2 renderer; the emoji participates in the streaming reveal above. (The **send / edit / copy / paste** round-trip that produces `.textCustomEmoji` from typed markdown is a separate section below: "Custom emoji in markdown messages".) + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift` | Enum case `textCustomEmoji(fileId: Int64, alt: String)` + Postbox coding (discriminator 17, keys `ce.f`/`ce.a`), `==`, `plainText` (returns `alt`), and FlatBuffers codec. | +| `submodules/TelegramCore/FlatSerialization/Models/RichText.fbs` | FlatBuffers schema — `RichText_CustomEmoji` union member + table. **Source of truth**; the Bazel `flatc` genrule regenerates `*_generated.swift` at build time (the checked-in `Sources/*_generated.swift` is stale). | +| `submodules/TelegramCore/Sources/ApiUtils/RichText.swift` | `Api.RichText.textCustomEmoji` ⇄ Swift, lossless both ways. | +| `submodules/InstantPageUI/Sources/InstantPageTextItem.swift` (`attributedStringForRichText`) | Emits a single placeholder char carrying `ChatTextInputAttributes.customEmoji` (a `ChatTextInputTextCustomEmojiAttribute`) + a `CTRunDelegate` sized to the font line height (`font.ascender − font.descender + 4·pointSize/17` ≈ 24pt @17pt). | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` (line-breaker) | Collects per-line `InstantPageTextLine.emojiItems`; overwrites each placeholder char's `characterRect` with a full cell (`width = itemSize`) so it feeds the reveal cost map. | +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` (`InstantPageV2View`) | Owns the `InlineStickerItemLayer`s: `updateInlineEmoji` (create/reuse/remove/position), `updateEmojiReveal` (reveal-driven pop-in), `updateEmojiVisibility` + `propagateVisibilityRect`. Layers attach to each text view's `emojiContainerView`. | + +### Non-obvious invariants + +- **flatc casing/`required` gotchas.** Edit `RichText.fbs`, not the generated Swift. Scalars (`long`) cannot be `(required)` — only strings/tables can. A union member `RichText_CustomEmoji` generates the Swift enum case `.richtextCustomemoji` (everything after the suffix's first letter is lowercased); the table type stays `TelegramCore_RichText_CustomEmoji` and field accessors keep `.fbs` casing (`value.fileId`). See the `flatbuffers-codegen` memory. +- **`ChatTextInputTextCustomEmojiAttribute` is reused end-to-end** (display layer ⇄ layout model). The attribute is written to the placeholder in `attributedStringForRichText` and read back by the V2 line-breaker under the SAME key (`ChatTextInputAttributes.customEmoji`); `InlineStickerItemLayer.init` consumes it directly and resolves the file lazily from `fileId`. +- **Emoji participates in the streaming reveal.** Its placeholder char's `characterRect` is overwritten to a full cell (width = `itemSize`), so the width-based cost map charges it like other content. `updateEmojiReveal` pops the layer in (alpha 0→1 + scale) when `charIndexInItem < currentRevealCharacterCount`; unrevealed → opacity 0. +- **Inline emoji/images are CENTERED on the font line box, NOT baseline-aligned, and do NOT inflate the line.** The line-breaker keeps `lineAscent = fontLineHeight` (only formulas grow it) and places each attachment at `baselineY − fontLineHeight/2 − size/2`, so it bleeds symmetrically about the line box instead of doubling the line height and shoving the text baseline down (the prior `lineAscent = emoji.size` behavior was a regression from V1 `layoutTextItemWithString`, which centers via `(fontLineHeight − imageHeight)/2`). Custom emoji are sized to ≈ the line box (`size = font.ascender − font.descender + 4·pointSize/17`) so they fit the true-font-height item box (see "InstantPage V2 text item height") with minimal bleed. Mirrors the chat `InteractiveTextComponent`. The cell's `characterRect` is centered the same way (`y = fontLineHeight/2 − size/2`) so the reveal mask (`renderer: y = minY + lineAscent − rect.maxY`) tracks it; a tall attachment grows `extraDescent` so the next line isn't overlapped. Three things must stay in lockstep: the display frame, the `characterRect`, and `extraDescent`. +- **Inline-attachment x must be the LEADING edge, computed RTL-safely via `v2LeadingOffsetForRange` (`InstantPageV2Layout.swift`).** An attachment's left edge is `min(CTLineGetOffsetForStringIndex(start), CTLineGetOffsetForStringIndex(end))` — NOT the bare start-index offset. `CTLineGetOffsetForStringIndex` at the start index returns the glyph's LEFT edge in LTR but its RIGHT edge in RTL (string index increases leftward), so the old single-offset form (`…, range.location, nil`) shoved emoji/images/formulas ~one advance (≈ the attachment width) too far right on RTL lines — e.g. an emoji in an Arabic thinking-block line, while the CoreText-drawn text stayed correct. The helper mirrors `Display.TextNode`'s `addEmbeddedItem` (incl. directional-boundary secondary-offset handling) and the strikethrough/underline/marked/spoiler decorations in this same file, which already used the `min`/`abs` form. For pure-LTR lines it returns exactly the start-index offset, so LTR is byte-identical. Applies to all 5 attachment sites: the emoji/image/formula display frames AND the emoji/image `characterRect` (reveal mask). The widths stay the fixed `size`/`rendered.size` values (the run-delegate advance), only the x is corrected. +- **Layers sit ABOVE the reveal mask.** They attach to `InstantPageV2TextView.emojiContainerView` (a sibling above `renderContainer`), NOT inside it — so the reveal mask wipes glyphs while emoji pop in independently. Adding a CTRunDelegate-glyph to the mask would clip-wipe them instead. +- **Layers are owned by `InstantPageV2View`, not the text view.** Keyed by `InlineStickerItemLayer.Key(id: fileId, index: occurrence)`. The pageView is now REUSED across `stableVersion` bumps (see streaming section), so the inline-emoji dict PERSISTS across chunks; `updateInlineEmoji` prunes stale keys (emoji whose blocks have been removed) and creates/repositions layers for new or unchanged emoji each update pass. +- **`visibilityRect` gates looping; `nil` means "not visible".** The bubble's `visibility` override pushes a full-width sub-rect to the root `pageView.visibilityRect`, re-pushed in the apply closure after `pageView.frame` is set. `propagateVisibilityRect` converts the rect into each nested V2View's coordinate space (`self.convert(_:to:)`) for details bodies / table cells+title, fanning out via each child's `didSet`. +- **CTRunDelegate extent buffers must be freed.** Every inline-attachment arm (`.image`/`.formula`/`.textCustomEmoji`) in `attributedStringForRichText` allocates an `extentBuffer`; the `dealloc` callback must `deallocate()` it (it re-runs per layout pass). + +## RichText entity cases (mention / hashtag / bot command / bank card / auto link) + +`RichText.textMention`, `.textMentionName(text:peerId:)`, `.textHashtag`, `.textCashtag`, `.textBotCommand`, `.textBankCard`, `.textAutoUrl`, `.textAutoEmail`, `.textAutoPhone` render the message-entity flavors of rich text inside rich-data bubbles with full tap interaction mirroring `ChatMessageTextBubbleContentNode`. Covers API parsing, Postbox + FlatBuffers serialization, display, and tap routing. (`textDate`/`textSpoiler` remain unimplemented — `.plain("")`.) + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift` | The 9 enum cases (each wraps `text: RichText`; `textMentionName` adds raw `peerId: Int64`) + Postbox coding (discriminators 18–26, wrapped text under key `"t"`, mention-name peerId under `"mn.p"`), `==`, `plainText`, FlatBuffers codec. | +| `submodules/TelegramCore/FlatSerialization/Models/RichText.fbs` | Union members + tables (`RichText_MentionName` adds `peerId:long`). Source of truth — same flatc gotchas as the custom-emoji section above. | +| `submodules/TelegramCore/Sources/ApiUtils/RichText.swift` | `Api.RichText` ⇄ Swift, lossless. `textMentionName` carries `userId` ⇄ `peerId`. | +| `submodules/InstantPageUI/Sources/InstantPageTextItem.swift` (`attributedStringForRichText`) | Display: auto url/email/phone reuse the `InstantPageUrlItem` (`url:`) path; the six entity cases push `.link(false)`, recurse, then attach the matching `TelegramTextAttributes.*` key over the produced range. | +| `submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/...` | Tap routing: `entityForTapLocation` reads the attribute dict at the tapped point; `entityTapContent` maps keys → `ChatMessageBubbleContentTapAction.Content`. | + +### Non-obvious invariants + +- **Display attaches the same `TelegramTextAttributes.*` keys the chat text bubble uses; the bubble reads them back.** Contract: `textMention`→`PeerTextMention` (String); `textMentionName`→`PeerMention` (`TelegramPeerMention`, peerId built as `EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, …)` — `InstantPageTextItem` imports TelegramCore but NOT Postbox, so bare `PeerId` is out of scope); `textHashtag` AND `textCashtag`→`Hashtag` (`TelegramHashtag`; no dedicated cashtag key/tap-action — the leading `$` distinguishes them); `textBotCommand`→`BotCommand`; `textBankCard`→`BankCard`. Auto url/email/phone go through the URL path (`mailto:`/`tel:`/raw), NOT an entity key. +- **`linkSelectionRects` and the bubble tap path check all six interactive keys** (URL + the five entity keys), not just URL, so press-highlight and the link-loading shimmer cover entities too. +- **Rich-data text selection must reach a line's trailing edge.** This is general to rich-data selection, not just entities: `InstantPageTextItem.attributesAtPoint(_:orNearest:)`'s `orNearest: true` (selection-drag) path returns `line.range.upperBound` (via `CTLineGetStringRange`) when the point is at/past `lineFrame.maxX`. `TextSelectionNode` uses that index as the **exclusive** upper bound, so clamping to the last character's index — as the `orNearest: false` hit-testing path correctly does — would leave the last character/item of every line unselectable. Mirrors `Display.TextNode`. Do not collapse the two `orNearest` paths back together. + +## Markdown send: entity vs. rich detection + +On message send, the app auto-decides: if the typed markdown maps onto the regular message-entity set (bold/italic/code/strikethrough/spoiler/links/blockquote/fenced-code) it sends a **normal message** via the existing entity path; if it contains structure the entity set can't represent it sends a **rich message** (`RichTextMessageAttribute` carrying an `InstantPage`, rendered by `ChatMessageRichDataBubbleContentNode`). Always-on (no flag). **Effective rich triggers are headings, lists, and tables only.** + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/BrowserUI/Sources/BrowserMarkdown.swift` | The classifier `richMarkdownAttributeIfNeeded(context:text:)` (pre-filter `markdownMightNeedRichLayout` → parse via existing `inputRichTextAttributeFromText` → block inspection `instantPageNeedsRichLayout`/`blockIsEntityExpressible`/`richTextIsEntityExpressible`), plus the markdown→InstantPage conversion (`markdownWebpage`, `markdownBlocks(from:)`, `markdownBlocksWithGeneratedAnchors`). | +| `submodules/TelegramUI/Sources/ChatControllerNode.swift` (`sendCurrentMessage`, ~line 4860) | The gate: `if !isSpecialChatContents, let attribute = richMarkdownAttributeIfNeeded(context:, text: effectiveInputText.string)` routes to the rich branch; the unchanged `else` is the entity path. | + +### Non-obvious invariants + +- **Boundary rule:** send rich iff the parse yields an `InstantPageBlock` with no entity equivalent. Entity-expressible whitelist (→ normal): `.paragraph`, `.preformatted`, `.blockQuote` (empty caption), `.anchor`, `.unsupported`, **and `.divider`** (`---` is too common in casual text to trigger rich). **`.formula` (block and inline) DOES trigger rich**, gated by strict math detection (see "Formulas trigger rich messages" below) so casual `$` usage (`$5-$10`, `$FOO=$BAR`) stays plain. So effective triggers = headings, lists, tables, formulas. +- **Approach A (parse-then-inspect):** the classifier reuses the real parser, so "what triggers rich" can't drift from "what the rich renderer shows." `markdownMightNeedRichLayout` is a cheap necessary-condition over-approximation — it may over-trigger a parse but must **never** false-negative. It detects `#`, list markers, dash-lines (`-{1,}`, which also catches setext-H2 underlines → heading blocks), `\n=` (setext H1), `|`, `![`, and math delimiters `$`/`\(`/`\[` (formulas now trigger rich; the strict detection step decides whether a `$` run is actually math). +- **Chat vs. document path = `file == nil` / `context.documentURL == nil`.** `inputRichTextAttributeFromText` passes `file: nil`; the document-attachment path passes a real file. Two chat-only behaviors key off this: (a) generated heading anchors are **skipped** (`markdownBlocksWithGeneratedAnchors` runs only for documents — anchors exist for intra-document `#slug` links and otherwise prepend a spurious invisible `.anchor` block per heading); (b) a level-1 `#` heading maps to `.heading(text:, level: 1)`, not `.title` (the document/article-title treatment). H2–H6 → `.heading(level: 2…6)` for both paths. This converter only ever emits `.title` (H1-doc) or `.heading` — never `.header`/`.subheader`. +- **The classifier is fed the RAW `effectiveInputText.string`**, not the post-`convertMarkdownToAttributes` `inputText`, so inline `**bold**` survives into the rich render. The entity branch still uses the converted `inputText`. +- **Bypassed for `.customChatContents`** (business links / quick replies) via `isSpecialChatContents`. The compose/send gate lives here; **editing has its own symmetric re-classification** — see "Editing rich messages" below. +- **Transmission:** `RichTextMessageAttribute` → `Api.InputRichMessage` via `messages.sendMessage(richMessage:)` (flag bit 23, `StandaloneSendMessage.swift`); recipients reconstruct it from the incoming `richMessage` field (`StoreMessage_Telegram.swift`). The rich branch sends `text: ""` + the attribute, nils `mediaReference` (no separate webpage preview), and bypasses 4096-char chunking. iOS < 15 / oversize markdown → `inputRichTextAttributeFromText` returns nil → entity path (which chunks). + +## Editing rich messages (InstantPage → markdown) + +Rich messages (`RichTextMessageAttribute`, `text == ""`) are made editable by reconstructing markdown source from the stored `InstantPage`, populating the editor with it, and re-classifying on save — the inverse of the send path above. Always-on (no flag). Images/videos are out of scope (skipped by the converter). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/BrowserUI/Sources/InstantPageToMarkdown.swift` | `markdownStringFromInstantPage(_:)` — the inverse converter (block + inline + list + table + escaping). Pure, best-effort, never fails. | +| `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` | `setupEditMessage`: rich message → reconstruct markdown into the edit field. `editMessage` (save): re-classify the raw input, route rich-or-plain. | +| `submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift` | `previewText()` extensions (`RichText`/`InstantPage*`) — one-line plaintext previews. | +| `submodules/TelegramStringFormatting/Sources/MessageContentKind.swift` | `messageContentKind` returns `.text(instantPage.previewText())` for rich, cascading to all preview surfaces. | + +### Non-obvious invariants + +- **The converter emits CommonMark inline, NOT the entity-regex dialect.** `**bold**`, `*italic*`, `` `code` ``, `~~strike~~`, `[text](url)` — because re-send re-parses the text through the *rich* path (`richMarkdownAttributeIfNeeded` → `NSAttributedString(markdown:)`, Apple CommonMark), not `convertMarkdownToAttributes` (whose dialect is `__italic__`/`||spoiler||`). The two parsers disagree on `__`/`*`; the rich round-trip is the contract. +- **Re-classify every edit (edit ≡ send).** `editMessage` runs the same `richMarkdownAttributeIfNeeded` on the edit field's attributed text (so reattached custom emoji round-trip — see the custom-emoji section). Rich → `pendingUpdateMessageManager.add(text: "", entities: nil, richText: attr, …)`; else the unchanged plain path. So normal→rich (add a table) and rich→plain (drop all triggers) both work. Bypassed for `.customChatContents`. +- **Change-detection compares the rich attribute.** The save guard adds `currentRichText != richTextAttribute` (rich branch — skips no-op rich edits) and `currentRichText != nil` (plain branch — so rich→plain still saves even when `text.string` looks unchanged). `RichTextMessageAttribute` is `Equatable` on `instantPage`. +- **The `text.length == 0` early-return guard is safe for rich.** `convertMarkdownToAttributes` only rewrites inline tokens, never strips `#`/`-`/`|`, so a rich message's markdown source stays non-empty and passes; the rich branch then sends `text: ""`. +- **Known limitation:** a rich→plain edit that leaves only inline-formatted text loses `*italic*` (the entity path recognizes only `__…__`). Rare edge; the rich round-trip contract holds. +- **`previewText()` lives in TelegramStringFormatting, not TextFormat/TelegramCore.** It will gain a `strings: PresentationStrings` param (to localize the `"Photo"`/`"Video"`/`"Table"` placeholders), so it must sit in a UI-string module — `messageContentKind`/`descriptionStringForMessage` (same module) already take `strings:`. Teaching `messageContentKind` about rich cascades the preview to the edit accessory panel, reply/pinned panels, and forward preview in one place (those surfaces need no individual change). + +## Copying rich messages as markdown (whole message + partial selection) + +Rich messages (`RichTextMessageAttribute`, `text == ""`) are copyable as markdown two ways: the context-menu **Copy** action copies the whole message; a **text selection** inside the rich-data bubble copies just the selected range. Both reconstruct markdown that mirrors the edit round-trip (`markdownStringFromInstantPage`). Always-on. + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift` | Whole-message Copy. Computes `richMessageMarkdown` from the message's `RichTextMessageAttribute.instantPage` (after `let message = messages[0]`), opens the Copy gate with `richMessageMarkdown != nil`, and short-circuits `copyTextWithEntities` to `storeMessageTextInPasteboard(markdown, entities: nil)`. | +| `submodules/BrowserUI/Sources/InstantPageToMarkdown.swift` | `markdownStringFromInstantPage` — the block-tree → markdown converter (also used by the edit round-trip). Blocks joined by `\n\n`; nested blockquotes via recursive `> ` wrapping. | +| `submodules/InstantPageUI/Sources/InstantPageTextItem.swift` | `InstantPageMarkdownBlockContext` (`kind` + `quoteDepth`) and the `markdownContext: InstantPageMarkdownBlockContext?` field on `InstantPageTextItem`. | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `stampMarkdownContext`/`bumpQuoteDepth`; stamps `markdownContext` during layout (heading/title/code/list/blockQuote/`layoutQuoteText`/table-cell). | +| `submodules/InstantPageUI/Sources/InstantPageMultiTextAdapter.swift` | `markdownForRange(_ range: NSRange)` + the private attributed-substring→inline-markdown converter `inlineMarkdown(from:)`. | +| `submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/.../ChatMessageRichDataBubbleContentNode.swift` | Intercepts `.copy` in the `TextSelectionNode` `performAction` closure: `textSelectionNode.getSelection()` → `adapter.markdownForRange(range)` → stores as plain `NSAttributedString(string:)`. | + +### Non-obvious invariants + +- **The V2 layout discards block role.** A `.text` layout item from an `H2` heading is byte-identical to a body paragraph — heading level and the title category are dropped with no back-reference to the source `InstantPageBlock`. Precise structural markdown for a *selection* therefore requires stamping `markdownContext` at layout time (lists/code/tables/details are structurally recoverable; **heading level and `.title` are not**, so they MUST be stamped). Plain paragraphs stay `nil` (≡ plain). +- **`quoteDepth` is orthogonal to `kind`** so a heading/list/code line inside a blockquote round-trips (e.g. `> ## Title`). `bumpQuoteDepth` lifts a quote's children by 1; nested quotes accumulate. `layoutQuoteText` (single-paragraph blockquote fast path AND `.pullQuote`) bumps once — it is never reached by the multi-block recursion, so no double-count. +- **A blockquote is exploded into one text item per line.** `markdownForRange` must re-coalesce a run of consecutive `quoteDepth > 0` segments into ONE `\n`-joined block (each line prefixed at its own depth); otherwise every quote line becomes its own block separated by a blank line. Code/table/list runs are likewise coalesced (one fence; one pipe table; one tight list). +- **Both converters emit compact nested-quote markers (`>>`, not `> >`).** Selection: `String(repeating: ">", count: depth) + " "`. Whole-message: when wrapping a line that already starts with `>`, prepend a bare `>`. Keep the two in sync. +- **Inline markdown is read from display attributes, not the RichText tree.** `inlineMarkdown` inspects the slice's `UIFont` (bold/italic/mono — font-based, no symbolic-trait flag for named fonts), `.strikethroughStyle`, and `TelegramTextAttributes.URL` (→ `InstantPageUrlItem.url`, angle-bracketed if it contains `(`/`)`/space). Custom-emoji placeholders now emit the `[](tg://emoji?id=…)` marker from the display attribute's `fileId` (alt is best-effort — the display placeholder may be a bare space; see the custom-emoji round-trip section). +- **`.copy` stores plain text.** Passing `NSAttributedString(string: markdown)` through the existing `performTextSelectionAction(.copy)` path (`storeAttributedTextInPasteboard`) generates no entities, so the literal `**`/`#`/`>`/`|` survive. The whole-message Copy uses `storeMessageTextInPasteboard(_, entities: nil)` directly. +- **Fidelity caveats (intentional):** custom emoji are now preserved as `[](tg://emoji?id=…)` markers (selection copy uses a best-effort alt — see the custom-emoji round-trip section below); ordered list + checkbox loses the ordinal (`-` wins); a partial table selection emits touched cells as rows (no forced header `---` separator); block prefixes apply to the whole touched line on a mid-line selection (correct markdown). + +## Custom emoji in markdown messages (send + edit/copy/paste round-trip) + +Custom emoji typed into the compose field survive when a message is sent as a **rich** message (heading/list/table/formula), rendering as `RichText.textCustomEmoji` (the display side is the "Inline custom emoji" section above). The carrier across Apple's CommonMark parser is a shared markdown-link marker `[](tg://emoji?id=)`, used identically by the forward (send) and reverse (edit/copy/paste) paths so encode and decode cannot drift. Always-on. **Scope: only rich messages — a custom emoji alone never forces a rich message** (it stays on the entity path as a `.CustomEmoji` entity, the pre-existing behavior). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TextFormat/Sources/CustomEmojiMarkdownMarker.swift` | The marker format — single source of truth: `customEmojiMarkdownURL(fileId:)`, `parseCustomEmojiFileId(fromMarkdownURL:)`, `escapeCustomEmojiMarkdownAlt(_:)`, and `chatInputTextWithReattachedCustomEmoji(_:)` (markers → live `customEmoji` attributes). In TextFormat so both BrowserUI and InstantPageUI can import it. | +| `submodules/BrowserUI/Sources/BrowserMarkdown.swift` | Forward: `markdownSourceInjectingCustomEmojiMarkers` rewrites each `customEmoji` run into the marker; `richMarkdownAttributeIfNeeded(context:attributedText:)` (signature changed from `text:`); the marker-URL intercept in `markdownInlineContent` → `.textCustomEmoji`. | +| `submodules/BrowserUI/Sources/InstantPageToMarkdown.swift` | Reverse (whole-message copy + edit reconstruction): `.textCustomEmoji` → emit the marker. | +| `submodules/InstantPageUI/Sources/InstantPageMultiTextAdapter.swift` | Reverse (text-selection copy): emit the marker from the display attribute's `fileId` (alt best-effort). | +| `submodules/TelegramUI/Sources/ChatControllerNode.swift`, `…/Chat/ChatMessageDisplaySendMessageOptions.swift` | Send + send-options-preview call sites pass the `NSAttributedString` (`effectiveInputText` / `textInputView.attributedText`); the rich send now passes `inlineStickers`. | +| `submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift` | Edit-load (`setupEditMessage`) reattaches markers via `chatInputTextWithReattachedCustomEmoji`; edit-save (`editMessage`) re-classifies the attributed edit text. | +| `submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift` | Paste (`chatInputTextNodeShouldPaste`) reattaches plain-text markdown markers → live emoji. | + +### Non-obvious invariants + +- **One shared marker, one set of helpers.** All emit sites (forward normalize, reverse copy/edit, selection copy) use `customEmojiMarkdownURL` + `escapeCustomEmojiMarkdownAlt`; the forward intercept and both reattach sites use `parseCustomEmojiFileId`. The marker is internal/transient — it exists only in the rich-conversion source string and on the clipboard, never persisted as a URL entity. +- **CommonMark preserves the `tg://emoji?id=N` link URL verbatim** under the `NSLink` attribute (spike-verified). `markdownLink`'s `as? NSURL` branch returns `url.absoluteString`, which `parseCustomEmojiFileId` matches by strict prefix. Negative (signed Int64) file ids survive too (the reattach regex is `(-?\d+)`). +- **Scope guard is structural.** `markdownSourceInjectingCustomEmojiMarkers` works on a LOCAL copy — `effectiveInputText` is never mutated. A marker is an entity-expressible link, so an emoji-only message classifies not-rich (`markdownMightNeedRichLayout` finds no `#`/`|`/`![`/`$`/list tokens) and takes the entity path; the untouched `customEmoji` attribute becomes a `.CustomEmoji` entity. +- **`richMarkdownAttributeIfNeeded` now takes `attributedText: NSAttributedString`** (was `text: String`); it normalizes to the marker'd source internally, then calls the unchanged `inputRichTextAttributeFromText(text:)`. All three call sites (send, edit-save, send-options preview) pass the attributed string. +- **Edit-load AND paste reattach to live attributes; copy stays textual.** `setupEditMessage` and `chatInputTextNodeShouldPaste` run `chatInputTextWithReattachedCustomEmoji` so the field shows the animated emoji, not raw token text. The paste branch is guarded by `.contains("tg://emoji?id=")` AND `reattached.string != plainText`, and runs only after the rich pasteboard types miss — `private.telegramtext`/RTF already decode the indexed `tg://emoji?id=&t=` RTF-link form via `chatInputStateStringFromRTF`. `previewText()` is unchanged (keeps the alt glyph). +- **Empty alt → a space.** CommonMark drops `[](url)` (no run carries the link attribute), which would silently lose the emoji; every emit site and the reattach substitute a space when the alt is empty. +- **Rich send attaches `inlineStickers`** (was `[:]`) + bubble-up packs, so the local store has the files. **OPEN runtime risk:** the wire send uses `Api.InputRichMessage.documents: nil` (`apiInputRichMessage()` in `SyncCore_RichTextMessageAttribute.swift`), so recipient rendering depends on the server back-filling `documents` from the embedded `documentId` — UNVERIFIED. If recipients see only the fallback glyph, populate `documents:` there. +- **Accepted limitations:** edit-load reattaches with `file: nil` (renders via lazy fileId resolution, but the premium-emoji gate is bypassed on edit); an alt containing a literal `]` won't reattach on edit-load (cosmetic — re-save still parses it); `parseCustomEmojiFileId` (strict prefix) vs `Pasteboard.swift`'s `URLComponents` parse could drift if the marker format ever changes. + +## Formulas trigger rich messages (strict math detection) + +`$…$`/`$$…$$` (and `\(…\)`/`\[…\]`) math triggers a rich message, gated by a +strict boundary rule so casual `$` stays plain. Inverse companion of the +markdown-send gate above. + +### Non-obvious invariants + +- **Inline `$…$`/`$$…$$` detection requires a 4-way boundary** (in `markdownReplacingInlineFormulas`, `BrowserMarkdown.swift`): outer side of each delimiter = line edge OR non-alphanumeric; inner side = non-whitespace; opener/closer `$`-counts must match (1 or 2). This is what rejects `$5-$10`/`$FOO=$BAR`/`cost$5$total` (alphanumeric outer) while keeping `$x$`, `($x$)`, `the answer is $x$.`. The outer check is the addition over a plain "no-space-inside" rule. +- **Block `$$` detection** (`markdownBlockFormulaReplacement`): single-line `$$…$$` requires an exact `$$` opener (not `$$$`) and trailing whitespace only; multi-line requires a **bare** `$$` opener line. `$$x$$ trailing text` falls through to the inline rule. The `\[…\]` opener path is unchanged and exempt from these `$$`-only guards. +- **Detection is shared with the document path; the gate is chat-only.** `markdownPreparedSource` (detection) runs for both chat and document attachments. The triggers (`richTextIsEntityExpressible`/`blockIsEntityExpressible` → `.formula` is non-expressible; `$`/`\(`/`\[` in `markdownMightNeedRichLayout`) are read only by the chat classifier `richMarkdownAttributeIfNeeded`. + +## InstantPageListItem task-list checkboxes (`- [ ]` / `- [x]`) + +`InstantPageListItem` carries a first-class `checked: Bool?` — the **third** associated value of `.text(RichText, String?, Bool?)` / `.blocks([InstantPageBlock], String?, Bool?)`, orthogonal to the ordered-list `num` — representing a GitHub-style task-list checkbox. `nil` = not a checkbox item, `false` = unchecked, `true` = checked. Covers markdown parse, Postbox + FlatBuffers serialization, Telegram API transmission, display (V1 + V2), the edit round-trip, and previews. + +Spec: [`docs/superpowers/specs/2026-05-27-instantpage-list-checkbox-design.md`](docs/superpowers/specs/2026-05-27-instantpage-list-checkbox-design.md). Plan: [`docs/superpowers/plans/2026-05-27-instantpage-list-checkbox.md`](docs/superpowers/plans/2026-05-27-instantpage-list-checkbox.md). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift` | The `checked: Bool?` enum payload; Postbox coding (key `"ck"`, tri-state Int32); `==`; FlatBuffers codec. Internal tri-state helpers `checkedFromTriState`/`triState(fromChecked:)`. | +| `submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs` | `checkState:int32 (id: 2)` on `InstantPageListItem_Text` + `_Blocks`. **Source of truth**; the Bazel `flatc` genrule regenerates the Swift (checked-in `*_generated.swift` is stale). | +| `submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift` | `checked` / `num` accessors; reads & writes the API `checkbox`=flags.0 / `checked`=flags.1 bits via `checkedFromApiFlags` / `apiFlags(fromChecked:)` across all four list-item types. | +| `submodules/BrowserUI/Sources/BrowserMarkdown.swift` | Forward parse: `markdownTaskListMarker` detects `[ ]`/`[x]`/`[X]`; the result routes into `checked` (NOT `num`). | +| `submodules/BrowserUI/Sources/InstantPageToMarkdown.swift` | Reverse: emits `- [ ] ` / `- [x] ` from `item.checked` for the edit round-trip. | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | V2 detection via `item.checked`; `.checklist(checked:colors:)` marker carrying `InstantPageV2CheckboxColors`. | +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` | V2 marker view (`InstantPageV2ListMarkerView`) hosts a real `CheckNode`. | +| `submodules/InstantPageUI/Sources/InstantPageLayout.swift` | V1 detection via `item.checked` (renders the existing `InstantPageChecklistMarkerItem`). | +| `submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift` | `previewText()` renders a `☐`/`☑︎` glyph + body for checkbox items. | + +### Non-obvious invariants + +- **`checked` is orthogonal to `num`.** The API keeps `checkbox`/`checked` as flags **separate from the list number**, so an ordered item can be both numbered AND a checkbox. This is exactly why the first-class field replaced an earlier sentinel-string-in-`num` prototype (which could not represent both). No `\u{001f}tg-md-task:*` sentinel remains anywhere. +- **API bits are `checkbox`=flags.0, `checked`=flags.1 on ALL FOUR list-item constructors** (`pageListItemText`/`Blocks` and `pageListOrderedItemText`/`Blocks`, in and out — `pageListItemText#2f58683c`, `pageListOrderedItemText#cd3ea036`, etc.). The iOS `Api.*` layer exposes only `flags: Int32`; mask the bits (`apiFlags(fromChecked:)` / `checkedFromApiFlags`). Because state rides the flags (not the text), it survives the server round-trip for sender + recipients — **including the sender's own send-confirmation echo** (`applyUpdateMessage` replaces local attributes with the server's reconstruction, `ApplyUpdateMessage.swift`). +- **Tri-state persistence `0=nil, 1=unchecked, 2=checked`** in BOTH Postbox (key `"ck"`, decoded with `decodeInt32ForKey(orElse: 0)`) and FlatBuffers (`checkState:int32`, default 0). Absent/0 → `nil`, so pre-existing stored pages decode unchanged. +- **Detection reads `item.checked != nil`** in both layout engines (was `instantPageTaskListMarkerState(item.num)`); the V2 marker kind is `.checklist(checked: item.checked == true, colors:)`. The empty-blocks `.blocks → .text(.plain(" "), num, checked)` promotion must carry `checked` through, not drop it. +- **V2 `CheckNode` is hosted directly in a plain `UIView`**, not an ASDisplayNode tree, so `checkNode.displaysAsynchronously = false` is set to avoid a first-draw blank flash. (The V2 pageView is now REUSED across streaming chunks via stable-id diffing — see the AI streaming section; `CheckNode` views survive across chunks as long as their list item is present.) `InstantPageV2CheckboxColors` (background←`panelAccentColor`, stroke←`pageBackgroundColor`, border←`controlColor`) is carried on the `.checklist` payload and mirrors the V1 `instantPageChecklistMarkerTheme`. +- **Forward parser keeps `[ ]` detection but routes to `checked`.** `markdownApplyTaskListMarker`/`markdownStrippingTaskListMarker`/`markdownTaskListMarker` still strip the marker from the item text; the state flows into `checked` while ordered items keep their real `"\(ordinal)"` number. The reverse converter emits lowercase `[x]` / `[ ]`, which the forward `hasPrefix` guards re-parse — that is the round-trip contract. +- **The enum-arity change is compile-enforced.** Adding the third associated value broke every `.text`/`.blocks` construction/destructure; the full build is the completeness gate. Read-only consumers outside the core set exist (`BrowserInstantPageContent.swift`, `CachedFaqInstantPage.swift`) — grep `\.(text|blocks)\(` repo-wide when touching the enum again. + +## InstantPageBlock.blockQuote nested blocks + +`InstantPageBlock.blockQuote` carries `(blocks: [InstantPageBlock], caption: RichText)` — a sequence of nested page blocks (paragraphs, headings, lists, code, even nested quotes), not the legacy text-only payload. `.pullQuote` is unchanged (still `(text: RichText, caption: RichText)`; the TL API has no `pullQuoteBlocks` constructor). + +Spec: [`docs/superpowers/specs/2026-05-29-instantpage-blockquote-blocks-design.md`](docs/superpowers/specs/2026-05-29-instantpage-blockquote-blocks-design.md). + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift` | Enum case shape; Postbox coding (legacy `"t"` lift → new `"b"` object array); equality (array-aware, mirrors `.collage`); FlatBuffers codec. | +| `submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs` | `InstantPageBlock_BlockQuote`: `text` (now optional, legacy fallback) + `caption (required)` + new `blocks:[InstantPageBlock] (id: 2)`. **Source of truth**; Bazel regenerates the `*_generated.swift`. | +| `submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift` | Parse both `pageBlockBlockquote` (lift text→`[.paragraph]`) and `pageBlockBlockquoteBlocks`; encode legacy-when-possible. | +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `layoutBlockQuote(blocks:…)` recurses into children; legacy single-paragraph fast path delegates to `layoutQuoteText` (the renamed shared text core, also used by `.pullQuote`). | +| `submodules/InstantPageUI/Sources/InstantPageLayout.swift` | V1 `.blockQuote` arm recurses via `layoutInstantPageBlock(...)`; same single-paragraph fast path. | +| `submodules/BrowserUI/Sources/BrowserMarkdown.swift` | Forward: one quote carrying all child blocks. Entity-expressibility gate (below). | +| `submodules/BrowserUI/Sources/InstantPageToMarkdown.swift` | Reverse: `markdownBlockQuoteBlocks(_:)` recurses per child and prefixes `> ` per line. | +| `submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift` | Concatenates child `previewText()`s + caption. | + +### Non-obvious invariants + +- **Legacy shapes lift to `[.paragraph(text)]` at every decode boundary.** API `pageBlockBlockquote`, the Postbox `"t"` key (old cached pages), and the FlatBuffers `text` field (now optional) each lift into a single-paragraph blocks array. New writes emit only `blocks` (`"b"` / the FB vector). So pre-existing stored pages and older senders decode unchanged. +- **Outbound stays on the legacy wire constructor when the shape allows.** `apiInputBlock()` emits `pageBlockBlockquote` for empty or single-`.paragraph` quotes (so older recipients understand the common chat case) and `pageBlockBlockquoteBlocks` only for genuinely nested quotes. +- **Both renderers share one text core for the single-paragraph fast path.** `layoutQuoteText` (V2; the function formerly named `layoutBlockQuote`, `isPull:` distinguishes pull vs block) and the V1 fast-path branch keep the legacy italicized-body styling; nested children render with their own normal category styling. +- **Nested children use a FIXED 10pt inter-child gap, not `spacingBetweenBlocks`.** The full page-flow spacing (~27pt around quotes) is too airy when nested, and 0 is too tight. `childSpacing = 10.0` lives in both layout files; the first child hugs the container's `verticalInset` (no leading gap). Combined with a nested quote's own 4pt top inset this gives ~14pt effective separation. +- **Entity-expressibility:** a quote is entity-expressible (→ regular message path) only if its caption is empty AND every child is an entity-expressible `.paragraph`. A nested-structure or multi-paragraph quote is not, so it sends via the rich path. **Behavior change:** markdown `> p1\n>\n> p2` is now ONE quote with two paragraphs (rich) rather than two consecutive entity quotes — correct semantics. +- **The enum-arity change is compile-enforced** across all modules; the full Bazel build is the completeness gate (no per-module build). `CachedFaqInstantPage.swift` matches `case .blockQuote:` payload-less and needs no edit. `BrowserReadability.swift` constructs `.blockQuote(blocks: [.paragraph(.italic(...))], …)` and is easy to miss in the spec's file list — grep `\.blockQuote(` repo-wide when touching the case again. + +## InstantPage thinking blocks (InstantPageBlock.thinking) + +`InstantPageBlock.thinking(RichText)` renders server-sent reasoning as dimmed, continuously-shimmering text inside rich-data bubbles. V2 renderer only; V1 ignores the block (returns `[]`). The shimmer and fade-in mechanics are deliberately separate from the char-reveal cursor so thinking blocks do not affect the reveal pacing of the answer content that follows them. + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/InstantPageUI/Sources/InstantPageV2Layout.swift` | `InstantPageV2ThinkingItem` layout item + `layoutThinking(...)` (paragraph color × 0.55 alpha for the dimmed style) + `layoutBlock` `.thinking` arm. | +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` | `InstantPageV2ThinkingView` — a `ShimmeringMaskView` wrapping a private inner `InstantPageV2TextView`; `InstantPageV2StableItemId.thinking(Int)` stable-id namespace; `makeItemView`/`reuse`/`stableId` arms for the `.thinking` item kind; the two-counter (content + thinking) stable-id loop in `InstantPageV2View.update`. | +| `submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift` | `.thinking(start:)` cost entry: contributes **zero** cursor cost; triggers whole-block alpha fade-in when `revealedCount >= start`. | +| `submodules/InstantPageUI/Sources/InstantPageLayout.swift` | V1 has no explicit `.thinking` case — it falls through `layoutInstantPageBlock`'s `default:` to an empty layout (no-op). | + +### Non-obvious invariants + +- **Zero reveal cost is the linchpin.** Thinking blocks do not advance the width-based cursor, so the answer's reveal position is identical whether or not thinking blocks are present — and is unaffected as they appear and disappear across streaming chunks. The answer text always reveals at the same rate regardless of how much thinking precedes it. +- **Whole-block fade, not char reveal.** The inner text is drawn fully under the shimmer mask at all times; the reveal mechanism is a simple alpha visibility keyed to the block's `start` index. A top-of-page thinking block (`start == 0`) is visible from the very first frame. +- **Shimmer runs continuously while the view is displayed** via `ShimmeringMaskView`'s `HierarchyTrackingLayer` self-animation. It does not stop when streaming ends. +- **Top-level only; separate stable-id namespace.** Thinking blocks appear only at the top level of the page. They use the `InstantPageV2StableItemId.thinking(Int)` namespace, numbered by a counter independent of content blocks. This means adding or removing a thinking block never renumbers the stable ids of content blocks — which, combined with pageView reuse, ensures content views and reveal state persist as thinking blocks come and go across chunks. +- **V1 is a no-op.** `InstantPageLayout.swift` has no `.thinking` case; the block falls through `layoutInstantPageBlock`'s `default:` to an empty layout, so V1 rendering silently skips it. + +## Anchor navigation in rich bubbles (intra-message `#anchor` links) + +Tapping a fragment-only link (`[Jump](#section)`) inside a rich-data bubble scrolls the chat so the matching in-message anchor lands ~8pt below the content-area top, expanding any enclosing collapsed `
` first. Anchors come from **server/AI-sent** InstantPages only — block-level `InstantPageBlock.anchor(name)` or inline `RichText.anchor` over a heading/paragraph; the markdown **compose** path deliberately skips generating heading-slug anchors for chat (`markdownBlocksWithGeneratedAnchors` runs only for documents), so user-typed messages have no anchors. The whole downstream scroll chain (`ChatControllerInteraction.scrollToMessageIdWithAnchor` → `ChatMessageBubbleItemNode.getAnchorRect` → `historyNode.scrollToMessage(.bottom(anchorY))`) pre-existed; this feature fills the two bubble-side seams that were stubbed. + +### Where things live + +| File | Responsibility | +|---|---| +| `submodules/InstantPageUI/Sources/InstantPageRenderer.swift` | `InstantPageV2View.anchorFrame(name:)` (live-layout frame walk, mirrors `findTextItem`; handles `.text`/`.codeBlock`/`.thinking`/`.details`/`.table`) + `firstCollapsedDetails(forOrdinalPath:)` (maps an ordinal path to the first not-yet-expanded `
`'s live index). | +| `submodules/InstantPageUI/Sources/InstantPageAnchorPath.swift` | **NEW.** Pure `instantPageAnchorPath(in:name:)` model walk → the `
`-sibling-ordinal path to an anchor (`nil` = absent, `[]` = outside any details, `[2,0]` = inside the 3rd top-level details then its 1st nested details) + `richTextContainsAnchor`. | +| `…/Chat/ChatMessageRichDataBubbleContentNode/…` | `getAnchorRect` (delegates to `anchorFrame`, +8pt top margin); the `tapActionAtPoint` fragment route + streaming gate; the `scrollToAnchor` resolve→expand→scroll state machine (`pendingScrollAnchor` + progress guard); the post-relayout hook. | + +### Non-obvious invariants + +- **The ordinal path is mapped to live indices, never reproduced.** The layout's `detailsIndexCounter` (`InstantPageV2Layout.swift`) is **expansion-dependent** — a `
` nested inside a *collapsed* parent has no index until the parent expands and re-lays-out (a collapsed details has `innerLayout == nil`; its children aren't laid out). So `instantPageAnchorPath` returns ordinals, and `firstCollapsedDetails` reads the real index from the live laid-out `.details` item. Expansion is iterative: expand one collapsed level → `requestMessageUpdate` → the post-relayout hook re-runs `scrollToAnchor` → repeat until the anchor resolves via `anchorFrame`. +- **The model walk's recursion set MUST equal the containers the V2 layout recurses through `layoutBlock`** (and thus counts `
` in via `detailsIndexCounter`): exactly `.blockQuote`, `.cover`, and `.list`'s `.blocks` items — all of which the layout **flattens** into the parent `items` array (only `layoutDetails` nests a separate `innerLayout`, which is the level boundary). `instantPageAnchorPath` recurses those three sharing the `inout detailsOrdinal`, and treats `.details` as a new level. It deliberately does **NOT** recurse `.postEmbed`/`.collage`/`.slideshow` — the V2 layout lays out only their media/caption (never their child blocks), so it never counts a `
` inside them; recursing them would desync the model walk's ordinals from the layout. An anchor inside such a non-laid-out child is unresolvable by `anchorFrame` anyway, so skipping it is a no-op either way. +- **`anchorFrame` and the model walk are only ever both consulted when `anchorFrame` fails.** `scrollToAnchor` first tries `anchorFrame` (covers everything currently laid out — top level, expanded details, tables, thinking blocks); only on a miss does it consult `instantPageAnchorPath`. So the only consequential model-walk output is a **non-empty** path (anchor buried in a collapsed details); `nil`/`[]` both no-op. +- **`getAnchorRect` stays a pure synchronous query.** ChatController calls it inside `forEachVisibleItemNode`; all expansion is orchestrated by `scrollToAnchor`/`pendingScrollAnchor` **before** the scroll fires. The chat scroll consumes only the returned rect's `minY`. +- **Anchor taps are rejected while the message streams** (`TypingDraftMessageAttribute`) → `.none`. So `pendingScrollAnchor` is only ever set post-stream, and the reveal cursor never interacts with anchor scrolling. +- **A fragment-only URL (`#…`, empty base) is always intercepted** — never opened as an external URL. If it resolves → scroll; if not (missing or empty anchor) → no-op (press-highlight only). A real URL carrying a fragment (`https://x.com/p#s`, non-empty base) keeps the unchanged external-URL handling. +- **The expansion loop terminates** via a progress guard (`lastExpandedPendingDetailsIndex == collapsedIndex` → give up): each relayout pass either resolves+scrolls (clearing pending) or advances to a strictly deeper collapsed `
`. +- **No `activate:` on the anchor tap action** (unlike external-URL taps): anchor scrolling is local and instant, so the link-loading shimmer (`makeActivate`) would falsely imply network activity. The press-highlight `rects` are still passed. + +## "Show more" for partial rich messages (on-demand full page) + +A server-sent rich message can arrive **partial** when the content is long: the `RichMessage` `isPartial` flag maps to `instantPage.isComplete == false`. The bubble then renders the partial page plus an inline **"Show more"** link; tapping it fetches the full page (once) and expands the bubble in place. + +### Data model + +- `RichTextMessageAttribute` (`SyncCore_RichTextMessageAttribute.swift`) carries the partial `instantPage` **and** an optional `fullInstantPage: InstantPage?` (nil until fetched). The partial page is **never replaced** — the full page is stored alongside it (encoded/decoded; both in `==`). +- `engine.messages.requestFullRichText(id:)` (`TelegramEngineMessages.swift`) requests `messages.getRichMessage`, then `transaction.updateMessage(id,…)` sets the existing attribute's `fullInstantPage` to the fetched complete page (keeping `instantPage`), and returns the updated attribute. It yields `.single(nil)` for non-Cloud ids and on network failure (no postbox change). +- The seed-config merge (`SyncCore_StandaloneAccountTransaction.swift`) preserves a previously-fetched `fullInstantPage` if a later server update for the same message arrives without one (same partial `instantPage`). + +### Where things live + +| File | Responsibility | +|---|---| +| `…/TelegramCore/Sources/SyncCore/SyncCore_RichTextMessageAttribute.swift` | The `fullInstantPage` field (init / encode / decode / `==`). | +| `…/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift` | `requestFullRichText(id:)` — fetch + `updateMessage` to fill `fullInstantPage`. | +| `…/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift` | Seed-config merge preserving a fetched `fullInstantPage` across later updates. | +| `…/Chat/ChatMessageRichDataBubbleContentNode/…` | The "Show more" link (layout, tap via `tapActionAtPoint` `.custom` + `updateTouchesAtPoint` highlight, `TextLoadingEffectView` shimmer), the node-local expand state, the effective-page selection, and the downward-expand. | +| `Telegram/Telegram-iOS/en.lproj/Localizable.strings` | `Chat.RichText.ShowMore` = "Show more" (→ `strings.Chat_RichText_ShowMore`). | + +### Non-obvious invariants + +- **Expand state is node-local and per-message, NOT derived from the attribute.** `showMoreExpanded: (messageId, value)?` is snapshotted at layout time and resolved against the current `item.message.id`, so **every fresh display of a message starts collapsed (partial)** even when its attribute already carries a cached `fullInstantPage`; only an in-place tap expands, and that expansion survives same-message relayouts. Resolving against the message id makes any *other* message collapse automatically (no stale-snapshot bug, no manual reset). +- **The bubble renders `(showMoreExpanded ? attribute.fullInstantPage : nil) ?? attribute.instantPage`** — the full page only while expanded — in both the webpage build and `layoutInstantPageV2`. `scrollToAnchor` resolves anchors against the same effective page. +- **The link shows only when `!showMoreExpanded` AND `!attribute.instantPage.isComplete`** (plus the original gates: not streaming via `TypingDraftMessageAttribute`, `id.namespace == .Cloud` since `requestFullRichText` is a no-op otherwise, and not a preview / `.messageOptions` context). The date/status trails the link's line by substituting the link frame for the last-text-line frame (see the status-node section). +- **`showMoreExpanded` is part of BOTH layout caches.** It is in the `currentPageLayout` cache key **and** the `pageView` content key (`pageViewMessageKey`). This is required because the cached-expand path (full page already on the attribute) performs **no postbox write**, so `stableVersion` does not bump — without the key, the cached partial layout/content would shadow the expand. +- **Tap (`activateShowMore`):** if `fullInstantPage` is already cached → set expanded + `requestMessageUpdate` immediately (no network, no shimmer); otherwise shimmer the link and fetch, expanding only once the full page lands. Guards against a second in-flight request and against re-expanding. +- **Expand grows the bubble downward in screen space** (top fixed) via `info?.setInvertOffsetDirection()` on the `ListViewItemApply` in the apply closure, fired only on the `appliedShowMoreExpanded → showMoreExpanded` transition (never on first apply). Same mechanism as `ChatMessageInteractiveFileNode`'s audio-transcription expand and the text/fact-check bubbles; the ListView clamps it to what fits. + +## Rich-message media in the gallery / shared-media / preview pipelines (`Message.effectiveMedia`) + +A rich message's media (images / videos / audio / documents) lives in `attribute.instantPage.media`, **not** in `message.media` (which is empty — rich messages are sent with `text: ""` and no media reference). To make that media participate in the *same* shared-media-index, gallery, file-list, playback, download, and save/copy pipelines that normal `message.media` flows through, there is one shared accessor and a set of opt-in call-site swaps. + +### The accessor + +`Message.effectiveMedia: [Media]` (+ a delegating `EngineMessage.effectiveMedia`) in `submodules/TelegramCore/Sources/Utils/MessageUtils.swift`: + +```swift +var effectiveMedia: [Media] { + if !self.media.isEmpty { return self.media } // normal message: identical to message.media + if let richText = self.richText { return richText.instantPage.allMedia() } // rich: the instant-page media + return self.media +} +``` + +`Message.richText` (same file) is already a typed `RichTextMessageAttribute?`; `InstantPage.allMedia()` (`SyncCore_InstantPage.swift`) recursively gathers media from the page's blocks (audio/collage/cover/details/image/list/slideshow/video) via its `[MediaId: Media]` dict. **For a normal message `effectiveMedia == message.media`**, so swapping a `message.media` read for `message.effectiveMedia` is behavior-preserving for non-rich content and only adds the rich media where the site should consider it. **Scope is first-media** for now (call sites keep their `.first` / iterate-and-break logic; the helper returns all media but callers stop at the first match — the `//TODO:rewrite to take all media` markers remain). + +### Where things live + +| Layer | What | +|---|---| +| **Discovery / index** | `tagsForStoreMessage` (`StoreMessage_Telegram.swift`) indexes rich media into `MessageTags` (photo/video/gif/voice/file). **This is the linchpin**: it makes rich messages *appear* in every tag-queried surface (shared-media tabs, search, downloads) — which is exactly why each rendering-side site below then needs `effectiveMedia`, or it renders the surfaced message blank. | +| **Extraction helper** | `Message.effectiveMedia` (above). | +| **Shared-media grids / rows** | `PeerInfoVisualMediaPaneNode`, `PeerInfoGifPaneNode`, `ListMessageItem` (row-type selection) + `ListMessageFileItemNode` (file/music/voice row), `ChatListSearchMediaNode` (search media grid). | +| **Gallery open + items** | `GalleryController` (`tagsForMessage` + `mediaForMessage` — the duplicated `message.media`/`message.richText` blocks were collapsed into one `effectiveMedia` loop), `GalleryData.chatMessageGalleryControllerData`, `SecretMediaPreviewController` (its own local `mediaForMessage`), and the gallery item nodes `ChatDocumentGalleryItem` / `ChatExternalFileGalleryItem` / `ChatAnimationGalleryItem` (these re-derive from `message.media` in `node()`, so a rich doc/animation rendered **blank** without the swap) + `UniversalVideoGalleryItem` secondary affordances + `ChatItemGalleryFooterContentNode`. | +| **Playback** | `PeerMessagesMediaPlaylist.extractFileMedia` (the peer music/voice playlist), `OverlayAudioPlayerControllerNode` (audio context menu). | +| **Resolution / downloads / cleanup** | `FetchedMediaResource.findMediaResourceById(message:)`, `SyncCore_RecentDownloadItem`, `StoreDownloadedMedia`, `DeleteMessages.addMessageMediaResourceIdsToRemove(message:)` (rich media was **leaking on delete**), `CollectCacheUsageStats`, `ChatHistoryListNode` (download manager), `ChatListSearch{ListPaneNode,ContainerNode}`. | +| **Actions** | `ChatInterfaceStateContextMenus` (Save-to-Camera-Roll, copy-image, save-audio/music-to-files, debug/premium), `ChatControllerNode` (post-suggestion media ref), `ChatControllerLoadDisplayNode` (edit send-validation), `ShareController.saveToCameraRoll`. | + +### Non-obvious invariants + +- **The tag-index change is what creates the work.** `tagsForStoreMessage` surfacing rich messages into tag-queried lists, *without* the rendering-side `effectiveMedia` swaps, produces visible **blank cells / blank rows / wrong row types**. Index and render must move together. +- **The rich message's own in-chat bubble + in-bubble gallery do NOT read `message.media`** — a rich message renders via `ChatMessageRichDataBubbleContentNode` (InstantPage V2), in-bubble image/video tap opens `InstantPageGalleryController` (reads the instant page directly), and in-bubble audio uses `InstantPageV2AudioContentNode`. So the text-bubble / interactive-file / interactive-media nodes' `message.media` reads are **never reached by a rich message** and are deliberately left alone. +- **Do NOT route the FORWARD path through `effectiveMedia`** (`ChatControllerNode` `forwardedMessages` ~556/560/568). The `RichTextMessageAttribute` already travels with a forward, so the forwarded copy reconstructs from the attribute; injecting the instant-page media as top-level `message.media` there would **double-render** (rich bubble + a separate media attachment). That `message.media` processing is caption-hiding / poll-stripping only, both irrelevant to rich — left as `message.media`. +- **Rich messages are edited as reconstructed MARKDOWN, not via the media-caption edit path.** So `ChatControllerLoadDisplayNode`'s edit caption-max-length / original-media-reference reads (~1241/1775/4463) stay on `message.media` — they belong to the `.media` edit state a rich message never enters. (The send-*validation* `.contains` at ~2273 IS swapped, so an edit that leaves only media isn't wrongly rejected.) +- **`RichTextMessageAttribute.associatedMediaIds` stays `[]` — intentionally.** `MessageHistoryTable` resolves `associatedMediaIds` via `getMedia(id)` in the postbox **media table**, but rich-message media is embedded inside the attribute blob, not the table — so returning the keys would be a no-op without also inserting the media into the table. The embedded-blob approach is self-contained. +- **`fullInstantPage` is not indexed** (the server doesn't index it either, and it's fetched on demand after store-time). The first media lives in the partial `instantPage` anyway. +- **Only switch the loop SOURCE, never the per-type branches.** Many swapped loops still contain `TelegramMediaPoll`/`TelegramMediaPaidContent`/`TelegramMediaWebpage` branches that rich messages never match — that's fine and intentional; only the `for … in .media` source changes. +- **Build-only completeness gate.** Every swap is type-identical (`[Media]` → `[Media]`), so the only compile risk is a receiver that is neither `Message` nor `EngineMessage`; the full Bazel build is the gate (no per-module build / unit tests). Deferred, NOT done: chat-list/reply/pinned/notification/forward thumbnail **previews** and the "Photo"/"Video" media-kind **labels** (`messageContentKind`/`ChatListItemStrings`) — those are preview surfaces, not blank-cell breakage — and **multi-media** (first-media-only is the current scope). diff --git a/docs/superpowers/postbox-refactor-log.md b/docs/superpowers/postbox-refactor-log.md new file mode 100644 index 0000000000..6f2a69fe99 --- /dev/null +++ b/docs/superpowers/postbox-refactor-log.md @@ -0,0 +1,1971 @@ +# Postbox → TelegramEngine refactor — log + +This file is the historical record of the Postbox → TelegramEngine refactor. It is **not loaded by default** into AI sessions (only `CLAUDE.md` is). Read this file when you need wave-specific context, a full worked example of a pattern, or the running tally of module Postbox-freeness. + +The short, actively-maintained rules and references live in `CLAUDE.md` under the "Postbox → TelegramEngine refactor" section. This file holds the narrative backstory, verbose example scripts, and per-wave outcomes that would otherwise bloat every AI session's context. + +--- + +## Wave-selection guidance — full versions + +The following subsections are trimmed to terse bullets in `CLAUDE.md`. Full versions (rule + backstory + scripts + per-wave examples) live here. + +### Shape-selection backstory + +The "leaf module, drop Postbox in isolation" approach only works for modules whose **public API doesn't leak Postbox domain types**. Most candidate leaf modules DO leak such types (`postbox: Postbox` / `account: Account` in public inits, `Media`/`Message` in public function parameters). Those modules need paired caller-migration waves, not isolated refactors. + +Before selecting a wave's module list, grep each candidate for: +- `:\s*Postbox\b`, `:\s*Account\b`, `:\s*MediaBox\b` in public signatures → abandon candidate +- `Media`/`Message` as public parameter types → likely needs paired wave with callers + +### Inventory at execution time, not just planning time + +**Inventory at execution time, not just planning time.** Wave 2's `SaveToCameraRoll` task was planned from a narrow grep that only matched `MediaResource`/`TelegramMediaResource` and missed three `postbox: Postbox` public-function leaks plus multiple `postbox.mediaBox.*` bodies. Planning-time inventory should grep the full set `\b(postbox|mediaBox|transaction|PostboxView|combinedView|MediaResource|PostboxDecoder|PostboxEncoder|MemoryBuffer)\b|^import Postbox` over the module's Sources, not just the tokens specific to that wave's goal. If the planning inventory under-counts, the executor should re-inventory at Task-1 time and abandon early before editing code. + +### Two feasible wave shapes + +**Two feasible wave shapes.** Wave 1 tried "per-module Postbox drop". Wave 2 tried "per-engine-facade-API migrate MediaResource to EngineMediaResource (modify in place, update all call sites in one commit)". The second shape worked well: narrow, clean commits, no abandonment cascade. Prefer it when the refactor target is an API surface that multiple consumer modules depend on. + +### Enum-payload migrations need a full case-site grep + +**Enum-payload migrations need a full case-site grep, not just a facade call-site grep.** If a wave changes the payload type of a public enum (wave 4 changed `UploadStickerStatus.complete`'s payload from `CloudDocumentMediaResource` to `EngineMediaResource`), inventory ALL construction and destructure sites of the enum across TelegramCore, not just call sites of the facade that returns it. Wave 4's plan undercounted by 6 consumer sites inside `ImportStickers.swift` itself (3 shortcut `.complete(...)` constructions in guard branches, 3 destructure+field-access sites using `CloudDocumentMediaResource`-specific members). For enum-payload waves, grep `case \.|let \.|\.\(` over the enum's defining module before execution and add those sites to the plan. + +### Unused-import sweeps are a valid wave shape + +**Unused-import sweeps are a valid wave shape.** After a round of facade migrations, consumer files accumulate `import Postbox` lines whose last semantic use was removed. Periodically sweep these: + +1. `grep -rl "^import Postbox$" submodules --include="*.swift" | grep -vE "/(TelegramCore|Postbox|TelegramApi)/"` generates the candidate list. +2. `sed -i '' '/^import Postbox$/d' ` (BSD `sed`) speculatively drops the import from every candidate. +3. Run the full build **with `--continueOnError`** — without `--keep_going`, bazel stops at the first failing target and surfaces only a few errors per iteration. `Make.py` forwards `--continueOnError` to `--keep_going`; always use it. +4. Each iteration: extract failing files via `grep -E "^submodules/.*\.swift:[0-9]+:[0-9]+: error:" | awk -F: '{print $1}' | sort -u`, restore via `git checkout -- `, rebuild. +5. The dependency graph has many layers (wave 6 needed ~18 rebuilds to reach a clean build). Per-iteration failures shrink roughly: 18 → 4 → 5 → 3 → 12 → 4 → 13 → 9 → 11 → ... Accelerate by doing **pattern-based preemptive restores** after the first few iterations: scan still-dropped files for tokens that are definitively Postbox-only (`MediaBox`, `PostboxCoding`, `PostboxDecoder`, `PostboxEncoder`, `TempBoxFile`, `ValueBoxKey`, `Postbox\b`, `PeerId`, `MessageId`, `MediaId`, `MessageIndex`, `MessageAndThreadId`, `PeerNameIndex`, etc. — note that CLAUDE.md's "engine typealias cheat sheet" arrows are migration targets, **not** typealiases in TelegramCore — `PeerId` etc. are still raw Postbox types and files using them need `import Postbox`) and restore those files in bulk. +6. Only restore files from the candidate set. If errors surface in `TelegramCore`, `Postbox`, or `TelegramApi`, halt — the sweep has cascaded beyond its scope. +7. Commit the surviving drops as one atomic commit. + +Tally impact from a sweep: dozens of consumer modules can become Postbox-free in a single commit. First run (wave 6): 782 candidates, 18 iterations, 183 survivors, **189 modules** newly Postbox-free. Re-run after every 2-3 facade-migration waves. + +### Public-Postbox-type inventory (wave-11-pattern planning) + +**Public-Postbox-type inventory (wave-11-pattern planning).** For wave-11-shape candidates (modules whose public init takes `postbox: Postbox, network: Network` purely for avatar/setPeer forwarding), grepping only for `Postbox`/`Network` tokens **undercounts** — public-surface types defined in Postbox can leak without ever naming "Postbox" literally. Wave 16 hit this: the plan missed `EngineMessageHistoryThread.Info?` and `PeerStoryStats?`, both Postbox-defined types whose names don't include "Postbox". Mitigation: build a Postbox-defined-public-types allowlist once, then grep the candidate module against it. + +```bash +# Build allowlist once (or re-run if Postbox sources change): +grep -rhE "^public\s+(class|struct|enum|protocol|typealias)\s+\w+" submodules/Postbox/Sources/ \ + | awk '{print $3}' | sed 's/[(:<].*//' | sort -u > /tmp/postbox-public-types.txt + +# Then, for each candidate module, grep its sources for any of those names: +grep -rhoE "\b($(cat /tmp/postbox-public-types.txt | tr '\n' '|' | sed 's/|$//'))\b" \ + submodules//Sources/ | sort -u +``` + +Any hit in a public-surface position (field type, init param type, enum payload type, generic arg) that isn't already a documented typealias is a blocker. "Engine"-prefixed types can still be Postbox-defined — don't trust naming conventions, grep for the defining module. If the module hits only `Postbox` itself (i.e., literal `Postbox`/`Network` pair), it's a clean wave-11 candidate. Otherwise, decide per leak: (a) move the type to TelegramCore if it's a namespace-only class (wave 16a pattern — prototype: `EngineMessageHistoryThread`), (b) accept that the module can't become Postbox-free and ship a partial `engine:`/`stateManager:` collapse that keeps `import Postbox` (wave 16b pattern — `PeerStoryStats` is too baked into Postbox views to move cleanly), (c) abandon the candidate. + +### Wave-shape G: facade addition + consumer sweep in one commit + +**Wave-shape G: facade addition + consumer sweep in one commit.** Validated at scale across waves 19-26. Six consecutive sessions migrated ~95 consumer sites and added ~15 mediaBox facades, all with clean first-pass builds (exception: wave 26 needed a second pass to add `import RangeSet`). Shape recipe: + +1. **Target:** a `MediaBox` (or similar Postbox type) method where Postbox's signature uses clean leaf types (`MediaResourceId`, `Data`, `String`, `Bool`) and the return type is either non-Postbox or has an existing `Engine*` wrapper. +2. **Pre-flight inventory:** grep `context\.account\.postbox\.mediaBox\.` over `submodules/` (excluding TelegramCore/Postbox/TelegramApi). Classify each hit: + - **Shape A**: `context.account.postbox.mediaBox.X(...)` → migratable. + - **Shape B**: `context.account.postbox.mediaBox.X(id: ...)` (different overload) → migratable with identical pattern. + - **Shape C**: `account.postbox.mediaBox.X(...)` where `account: Account` is a local (not `AccountContext`) → skip this wave (needs per-module rework). + - **Shape D**: `self.postbox.mediaBox.X(...)` where `postbox: Postbox` is a stored field → skip this wave. + - Plus: check for `accountManager.mediaBox.X(...)` which is Account-manager-scoped, a different migration path entirely. Never migrate via `TelegramEngine.Resources.*`. +3. **Facade design rules:** + - Signatures take `EngineMediaResource.Id` (`MediaResourceId` aliased at call site via `EngineMediaResource.Id(x.id)`) or `EngineMediaResource` (wraps `resource` when the Postbox overload takes a resource with members accessed via `.id`). + - Parameters with `Bool` defaults (`synchronous: Bool = false`) preserve defaults on the facade. + - Return types: prefer `Void`, `String`, `String?`, `Signal` where `T` is a non-Postbox type or an `Engine*` wrapper. Where Postbox return types are wrapped (e.g., `Signal` → `Signal`), confirm the `Engine*` wrapper exists and decide whether consumer-side field-access rewrites are acceptable for the wave. +4. **WIP interference check:** before starting, `git status --short | grep -v "^??"` to list modified files. If any Shape-A site is in a WIP file, either skip those sites (document the skip in the outcome) or wait for the WIP to commit. Wave 23 hit this in `ChatMessageInteractiveMediaNode.swift`. +5. **Name collision check:** if a facade return type names a Swift stdlib type that has availability restrictions (e.g., `RangeSet` — iOS 18+), verify the third-party module import is present in `TelegramEngineResources.swift`. Wave 26 needed `import RangeSet`. +6. **Replace_all usage:** for files with duplicate identical call text, `replace_all=true` on the exact call expression (without leading whitespace) batches the migration. When leading whitespace varies across identical-call sites within a file, the tool still matches if the unchanged prefix (`context.account.postbox.mediaBox.X(...)`) is unique enough — but verify via post-edit grep. +7. **Cheapness:** ~5-50 sites per wave, single atomic commit, expected first-pass-clean build. If post-migration grep for `context\.account\.postbox\.mediaBox\.` returns empty (exclude Shape-C/D) and build is green, commit. + +--- + +## Wave 1 outcome (2026-04-16) + +4 modules done: `ChatInterfaceState`, `ChatSendMessageActionUI`, `ContactListUI`, `DrawingUI`. +6 modules abandoned with recorded reasons in the wave-1 plan: `ActionSheetPeerItem`, `ChatListSearchRecentPeersNode`, `DirectMediaImageCache`, `FetchManagerImpl`, `GalleryData`, `ICloudResources`. + +## Wave 2 outcome (2026-04-17) + +5 `TelegramEngine` facades migrated to `EngineMediaResource` (signatures changed in place; `_internal_*` Postbox layer unchanged): +- `TelegramEngine.Peers.uploadedPeerPhoto`, `uploadedPeerVideo`, `updatePeerPhoto` +- `TelegramEngine.AccountData.updateAccountPhoto`, `updateFallbackPhoto` +- `TelegramEngine.Contacts.updateContactPhoto` +- `TelegramEngine.Auth.uploadedPeerVideo` + +1 consumer submodule fully de-Postboxed: `MapResourceToAvatarSizes` (signature changed from `(postbox: Postbox, resource: MediaResource, …)` to `(engine: TelegramEngine, resource: EngineMediaResource, …)`; 27 call sites migrated). + +1 consumer signal type swapped: `AuthorizationUI/AuthorizationSequenceController.swift` (`Signal` → `Signal`). + +1 task abandoned with recorded reason in the wave-2 plan: `SaveToCameraRoll` (full-module Postbox coupling, needs its own wave). + +## Wave 3 outcome (2026-04-18) + +3 thin forwarders added on `TelegramEngine.Resources` over `MediaBox`: +- `fetch(reference:userLocation:userContentType:)` → `Signal` (Postbox return types remain a documented accepted leak) +- `status(resource: EngineMediaResource)` → `Signal` +- `data(resource: EngineMediaResource, pathExtension:, waitUntilFetchStatus:)` → `Signal` (takes a `Bool` rather than exposing `ResourceDataRequestOption`, per YAGNI) + +1 consumer submodule fully de-Postboxed: `SaveToCameraRoll`. Public signatures changed from `(context:, postbox: Postbox, userLocation:, …)` to `(context:, userLocation:, …)`; `FetchMediaDataState.data` payload changed from `MediaResourceData` to `EngineMediaResource.ResourceData`; internals rewired through `context.engine.resources.*`. 23 call sites across 14 files migrated atomically with the module. + +Pre-flight verified that `ShareController.swift:2406`'s `self.currentContext.stateManager.postbox` is equivalent to `context.account.postbox` in the `ShareControllerAppAccountContext` path (because `AccountStateManager` is constructed with the account's own `postbox`), so the `postbox:` argument could be dropped without behavior change. + +No tasks abandoned. Shape validated: "per-engine-facade-API migration + full consumer module rewrite" (the wave-2 shape, scaled up to a full module drop). + +Plan: `docs/superpowers/plans/2026-04-18-postbox-to-telegramengine-wave-3.md` + +## Wave 4 outcome (2026-04-18) + +1 `TelegramEngine` facade migrated in place to `EnginePeer` + `EngineMediaResource` (signature changed; `_internal_uploadSticker` keeps its raw `Peer`/`MediaResource` parameter list): + +- `TelegramEngine.Stickers.uploadSticker(peer: Peer → EnginePeer, resource: MediaResource → EngineMediaResource, thumbnail: MediaResource? → EngineMediaResource?, …)` + +1 public enum payload migrated: `UploadStickerStatus.complete(CloudDocumentMediaResource, String)` → `.complete(EngineMediaResource, String)`. `_internal_uploadSticker` wraps `EngineMediaResource(uploadedResource)` at its one `.complete(...)` result-construction site — a narrow, spec-allowed one-line deviation from "internal Postbox-facing stays raw", taken to keep `UploadStickerStatus` as a single public enum. + +**Plan-time inventory undercount** — worth recording as a lesson. The spec and plan enumerated 2 external call sites and 1 internal construction site. Execution uncovered 6 additional consumer sites inside `ImportStickers.swift` itself that also needed adapting: 3 shortcut `.complete(...)` construction sites (lines 204, 371, 492, each emitting `.complete(CloudDocumentMediaResource, String)` directly from `as? CloudDocumentMediaResource` guards) and 3 destructure sites (lines 216, 384, 505) that accessed `CloudDocumentMediaResource`-specific fields. Each construction site now wraps via `EngineMediaResource(resource)`; each destructure site unwraps with `let rawResource = resource._asResource() as? CloudDocumentMediaResource`. MediaEditorScreen's two `stickerFile(resource:)` calls also needed `as! TelegramMediaResource` casts because `_asResource()` returns the Postbox `MediaResource` protocol while `stickerFile` takes the TelegramCore `TelegramMediaResource` sub-protocol. **Future planning-time inventory for enum-payload migrations should grep not only call-sites of the facade but every `case .complete` / `case let .complete` of the migrated enum across the whole TelegramCore source tree.** + +2 external call sites migrated atomically with the facade: +- `submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift:91` (plus a `peer: Peer → EnginePeer(peer)` wrap, since the local `peer` comes from `postbox.loadedPeerWithId(...)` which returns raw `Peer`) +- `submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift:8099` (plus 6 cascading sites inside the enclosing block for the new `UploadStickerStatus.complete` payload) + +No module becomes Postbox-free in this wave (both caller files import Postbox for unrelated reasons). + +Plan: `docs/superpowers/plans/2026-04-18-postbox-to-telegramengine-wave-4.md` + +## Wave 5 outcome (2026-04-18) + +Completes the last explicitly-named future-wave candidate from the wave-2 final review. + +`uploadSecureIdFile(context: SecureIdAccessContext, postbox: Postbox, network: Network, resource: MediaResource)` migrated in place to `(context:, engine: TelegramEngine, resource: EngineMediaResource)`. Function body accesses raw Postbox types via `engine.account.postbox` / `engine.account.network` (internal Postbox-facing layer stays raw per the standing rule). + +1 consumer submodule fully de-Postboxed: `SecureIdVerificationDocumentsContext` (PassportUI/Sources). Signature changed from `(postbox: Postbox, network: Network, context: SecureIdAccessContext, update: ...)` to `(engine: TelegramEngine, context: SecureIdAccessContext, update: ...)`; stored props collapsed into a single `engine: TelegramEngine` field. One instantiation site updated in the same commit. + +After this wave, the "Known future-wave candidates" list contains only the 4 permanently-blocked classes conforming to `TelegramMediaResource`. + +Plan: `docs/superpowers/plans/2026-04-18-postbox-to-telegramengine-wave-5.md` + +## Wave 6 outcome (2026-04-19) + +First build-verified unused-import sweep. Ran the speculative-drop + build-verify methodology (see "Unused-import sweeps" under Wave-selection guidance above): dropped `import Postbox` from all 782 consumer files where a plain `^import Postbox$` line appeared, iterated 18 full builds with `--continueOnError`, restoring imports on files that failed to compile. + +**183 drops survived** (single atomic commit `7b2b74e79b`, 0 insertions / 183 deletions). **189 modules** transitioned to Postbox-free status — full list is inferable by running the methodology's module-scan against HEAD. Representative additions spanning alphabetically: `AccountUtils`, `ActivityIndicator`, `AdUI`, `AlertUI`, `AnimatedStickerNode`, `AppLock`, `AttachmentTextInputPanelNode`, `BotPaymentsUI`, `CalendarMessageScreen`, `CallListUI`, `Camera`, `ChatImportUI`, etc. The running tally below preserves the per-module enumeration only for the ~10 individually-documented waves 1–5 modules. Wave 6's 189 additions are not re-enumerated here because the size would overwhelm the doc; see `git show 7b2b74e79b --stat` for the per-file breakdown and `grep -rL "^(@_exported )?import Postbox" submodules/*/Sources --include="*.swift"` for the current per-module status. + +Deviation from plan: the plan capped at 3 iterations; execution needed 18 because the dependency graph is deep and each bazel build surfaces only the currently-compilable layer. Pattern-based preemptive restores (using the symbol list in the "Unused-import sweeps" guidance) were used from iteration 9 onward to accelerate convergence from iteration-by-iteration single-file restores to bulk restores. No unexpected path cascades; no abandoned state. + +Plan: `docs/superpowers/plans/2026-04-19-postbox-to-telegramengine-wave-6.md` + +## Wave 7 outcome (2026-04-20) + +Closed out the seven remaining raw-Postbox leaks in `TelegramEngine.*` public facades surfaced by a post-wave-6 scouting pass. Single atomic commit, one full build, zero abandonment. + +Seven `TelegramEngine` facades migrated in place (all `_internal_*` implementations kept raw per the standing rule): + +**Messages (3):** +- `downloadMessage(messageId:)` — return `Signal` → `Signal`. Return-side wrap via `|> map { $0.flatMap(EngineMessage.init) }`. +- `topPeerActiveLiveLocationMessages(peerId:)` — return `Signal<(Peer?, [Message]), NoError>` → `Signal<(EnginePeer?, [EngineMessage]), NoError>`. Return-side tuple wrap. +- `getSynchronizeAutosaveItemOperations()` — **deleted**. Dead facade: sole caller (`StoreDownloadedMedia.swift`) already bypassed it by calling `_internal_getSynchronizeAutosaveItemOperations` directly inside its own transaction block. + +**Peers (1):** +- `updatedRemotePeer(peer:)` — return `Signal` → `Signal`. `PeerReference` param kept as-is (no `EnginePeer.Reference` alias today). The sole call site in `ChannelAdminsController.swift` uses `ignoreValues`, so no caller change was needed. + +**Resources (4):** +- `renderStorageUsageStatsMessages(…existingMessages:)` — `[EngineMessage.Id: Message]` → `[EngineMessage.Id: EngineMessage]` on both sides. Facade unwraps input via `.mapValues { $0._asMessage() }`, wraps output via `.mapValues(EngineMessage.init)`. +- `clearStorage(peerId:categories:includeMessages:excludeMessages:)` — `[Message]` → `[EngineMessage]`. Facade unwraps via `.map { $0._asMessage() }`. +- `clearStorage(peerIds:includeMessages:excludeMessages:)` — same shape. +- `clearStorage(messages:)` — same shape. No external callers; migrated for overload-set consistency. + +**Consumer call-site updates** (5 files): +- `ChatListUI/Sources/ChatListSearchListPaneNode.swift`: dropped now-redundant `.flatMap(EngineMessage.init)` wrap at the `downloadMessage` call site. +- `LocationUI/Sources/LocationViewControllerNode.swift`: dropped now-redundant `.map(EngineMessage.init)` at the `topPeerActiveLiveLocationMessages` call site. +- `LiveLocationManager/Sources/LiveLocationSummaryManager.swift`: dropped redundant `EnginePeer(author)` / `EngineMessage(message)` construction (`author`, `message` are now already `EnginePeer` / `EngineMessage`). +- `TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift`: bridged at 4 facade-call points (1 `renderStorageUsageStatsMessages`, 2 `clearStorage` overloads with message arrays; the `includeMessages: [], excludeMessages: []` site at line 3038 needed no change as empty arrays infer to `[EngineMessage]` just as well). + +**Minimal-scope bridging.** `StorageUsageScreen.swift` still has 43 raw `Message`/`MessageId` references inside its `AggregatedData` helper class and surrounding logic — not touched in this wave. A future "StorageUsageScreen full de-Postbox" wave would drop those (migrate `AggregatedData.messages: [MessageId: Message]` → `[EngineMessage.Id: EngineMessage]`, `clearIncludeMessages: [Message]` → `[EngineMessage]`, etc.) and potentially drop `import Postbox`. Out of scope here. + +**No modules became Postbox-free in this wave** — all five touched consumer files still import Postbox for reasons unrelated to the migrated facades. + +Plan / record: `docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-7.md`. + +After this wave, the "Known future-wave candidates" list contains only the 4 permanently-blocked classes conforming to `TelegramMediaResource`. The full public `TelegramEngine.*` facade surface is now engine-typed (modulo those four types). + +## Wave 8 outcome (2026-04-20) + +`StorageUsageScreen` consumer-module migration of raw `Message` domain types to `EngineMessage`. Scope explicitly narrower than a full de-Postbox: two files touched, module remains `import Postbox` due to two out-of-scope site clusters. + +**Types migrated:** +- `StorageFileListPanelComponent.Item.message: Message` → `EngineMessage` (item type co-located with the panel component). +- `StorageUsageScreen.Component.AggregatedData.messages: [MessageId: Message]` → `[EngineMessage.Id: EngineMessage]`; `.clearIncludeMessages` / `.clearExcludeMessages: [Message]` → `[EngineMessage]`. Init param updated to match. +- `StorageUsageScreen.Component.SelectionState.togglePeer(availableMessages:)` param: `[EngineMessage.Id: Message]` → `[EngineMessage.Id: EngineMessage]`. +- `StorageUsageScreen.Component.RenderResult.messages: [MessageId: Message]` → `[EngineMessage.Id: EngineMessage]`. +- `openMessage(message: Message)` → `openMessage(message: EngineMessage)` (external `OpenChatMessageParams.message` / `chatMediaListPreviewControllerData(message:)` calls unwrap via `message._asMessage()` at the two call sites — those APIs still take raw `Message`). + +**Wave-7 facade-boundary bridging dropped:** the `renderStorageUsageStatsMessages` call-site's `(…).mapValues(EngineMessage.init)` / `.mapValues { $0._asMessage() }` bridges and the two `clearStorage` call sites' `.map(EngineMessage.init)` wraps all vanish — `AggregatedData.messages` / `.clearIncludeMessages` / `.clearExcludeMessages` are now engine-typed and pass through the facade unchanged. Inside the `AggregatedData.updateSelected...` selected-messages accumulation loop, four `item.message._asMessage()` calls (for imageItems, which hold EngineMessage) drop back to plain `item.message` since the target array is now `[EngineMessage]`. And `StorageMediaGridPanelComponent.Item(message: EngineMessage(message), …)` drops the `EngineMessage(…)` wrap since `message` is already `EngineMessage`. + +**Out of scope — future-wave candidates (module still imports Postbox):** +- `StorageUsageScreen.swift:1047-1062` and `3131-3185`: preferences-view observation of `AccountSpecificCacheStorageSettings` via `postbox.combinedView` + `PreferencesView`, and a `postbox.transaction { transaction in transaction.getPeer / transaction.getPeerCachedData as? CachedGroupData / CachedChannelData }` block classifying peer-storage-timeout exceptions. Substantial: requires `EngineData`-subscription rewrite for the preferences observation, plus engine-API equivalents for peer-category classification + cached-data subscriber counts. +- `StorageFileListPanelComponent.swift:105`: `Icon.media(Media, TelegramMediaImageRepresentation)` enum case, constructed only as `.media(TelegramMediaFile, …)` or `.media(TelegramMediaImage, …)` (both TelegramCore types). Trivial future wave: split into `.mediaFile(TelegramMediaFile, …)` / `.mediaImage(TelegramMediaImage, …)`, drop `import Postbox`. + +Single atomic commit. Build verified green (59s incremental build, 27 actions). Net −11 lines in `StorageUsageScreen.swift` (simplification). + +Plan / record: `docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-8.md`. + +## Wave 9 outcome (2026-04-20) + +Closes the first of the two "future-wave candidates" left open by wave 8: rewrites both `AccountSpecificCacheStorageSettings` preferences-view observation sites in `StorageUsageScreen.swift` using engine APIs, and drops `import Postbox` from that file. + +**Site 1 — `cacheSettingsExceptionCount` signal** (former lines 1047–1087): +- `postbox.combinedView(keys: [.preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings]))])` + `PreferencesView` extraction → + `context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.accountSpecificCacheStorageSettings))` + `preferencesEntry?.get(AccountSpecificCacheStorageSettings.self) ?? .defaultSettings`. +- Downstream `EngineDataMap` + `EnginePeer` per-category counting logic unchanged (already engine-only). + +**Site 2 — `peerExceptions` signal in `openKeepMediaCategory`** (former lines 3131–3196): +- Same preferences observation replacement as Site 1. +- `postbox.transaction { transaction.getPeer / transaction.getPeerCachedData as? CachedGroupData / CachedChannelData; FoundPeer(peer:subscribers:) }` → `context.engine.data.get(EngineDataMap(...TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))` + pattern match on `EnginePeer.user / .secretChat / .legacyGroup / .channel`. +- Signal element type `[(peer: FoundPeer, value: Int32)]` → `[(peer: EnginePeer, value: Int32)]`. `FoundPeer` wrapper and its `subscribers` field dropped entirely — they were computed by the transaction block but never read by downstream consumers (the only consumer sites read `.isEmpty`, `.count`, and `.prefix(3).map { EnginePeer($0.peer.peer) }`). +- One downstream consumer updated: `peerExceptions.prefix(3).map { EnginePeer($0.peer.peer) }` → `.prefix(3).map { $0.peer }` at the `MultiplePeerAvatarsContextItem` construction (redundant wrap removed since `$0.peer` is already `EnginePeer`). + +**Typealias fixup.** With `import Postbox` removed, `var mergedMedia: [MessageId: Int64]` at former line 2397 needed renaming to `[EngineMessage.Id: Int64]`. `MessageId` is the raw Postbox type name — CLAUDE.md's engine-typealias cheat sheet lists these as migration targets, not pre-existing aliases in TelegramCore. Caught by first-pass build failure (`cannot find type 'MessageId' in scope`). + +**Reusable pattern.** `TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ValueBoxKey)` (at `TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift:356`) is the general-purpose engine replacement for any `postbox.combinedView(keys: [.preferences(keys: Set([key]))]) + PreferencesView` idiom — takes any `ValueBoxKey`, returns `PreferencesEntry?`, decodes via `.get(T.self)`. Crucially, callers need not `import Postbox` even though `ValueBoxKey` is a Postbox type: passing `PreferencesKeys.` through makes `ValueBoxKey` an inferred-only type that never gets named in the consumer module. Use this pattern when de-Postboxing any future module that observes preferences. + +`StorageUsageScreen.swift` is now Postbox-free. The wave 8 outcome's other candidate (`StorageFileListPanelComponent.swift`'s `Icon.media(Media, ...)` enum case) remains — trivial future wave to split into `.mediaFile(TelegramMediaFile, ...)` / `.mediaImage(TelegramMediaImage, ...)` cases, at which point the `StorageUsageScreen` consumer module as a whole becomes Postbox-free. + +Net: 1 file changed, +30 / -54. Build verified green (27 actions, cached). + +Plan / record: `docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-9.md`. + +## Wave 10 outcome (2026-04-20) + +Closes the second (and last) future-wave candidate from wave 8: eliminates `StorageFileListPanelComponent.swift`'s `Icon.media(Media, TelegramMediaImageRepresentation)` enum case. **`StorageUsageScreen` (the module as a whole) is now fully Postbox-free** — the other in-module file (`StorageUsageScreen.swift`) landed in wave 9. + +**Split the enum case.** `Icon.media(Media, TelegramMediaImageRepresentation)` → two concrete cases `case mediaFile(TelegramMediaFile, TelegramMediaImageRepresentation)` + `case mediaImage(TelegramMediaImage, TelegramMediaImageRepresentation)`. Lossless split: the two construction sites already knew the concrete subtype (`imageIconValue = .media(file, representation)` from a `as? TelegramMediaFile` branch, `.media(image, representation)` from a `as? TelegramMediaImage` branch), and the consumer binding site immediately downcast via `as?` to pick which `setSignal(...)` flavor to call. New split removes the downcast; exhaustiveness-checked switch is both safer and terser. + +**Equatable rewritten.** Old: manual outer-`switch` + inner `if case` dispatch, comparing media by `media.id` only. New: switch-over-tuple `(lhs, rhs)` with id-based equality per concrete type (`lFile.fileId == rFile.fileId`, `lImage.imageId == rImage.imageId`). Same id-based equality semantics as before. + +**Binding-site rewrite.** Old: `if case let .media(media, representation) = component.icon { ... if let file = media as? TelegramMediaFile { ... } else if let image = media as? TelegramMediaImage { ... } }`. New: a compound case-binding pattern `case let .mediaFile(_, representation), let .mediaImage(_, representation):` lifts the shared `representation` variable, then an inner switch dispatches to the right `setSignal` branch. Works because both cases carry the same `TelegramMediaImageRepresentation` payload type; Swift allows compound case patterns when the bindings have identical types. + +**Placeholder `PeerId(...)` construction fixup.** Second-pass build failure after dropping `import Postbox` surfaced a placeholder `PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0))` in the `measureItem` layout-measurement instance at former line 1062. Naming `PeerId`, `PeerId.Namespace`, and `PeerId.Id` all require `import Postbox` (these are raw Postbox types, not TelegramCore typealiases — consistent with wave 9's `MessageId` → `EngineMessage.Id` fixup). Replaced with `component.context.account.peerId` (a real `EnginePeer.Id` already in scope). Semantically equivalent since the measurement instance's `messageId` is only used for `.peerId` extraction inside image-fetch `userLocation` and for Equatable comparison (the measurement instance isn't compared to anything). + +**Lesson.** Placeholder `PeerId(...)` / `MessageId(peerId:...)` constructions in layout-measurement code are a recurring trap for de-Postbox work. Common pattern in this codebase: construct a dummy component instance purely to call `.update(...)` and read back the returned size. The dummy values are not used meaningfully but naming the types pins `import Postbox`. When de-Postboxing, grep for `PeerId(namespace:`/`MessageId(peerId:` with all-zero args and replace with any convenient real value in scope (`context.account.peerId` is almost always available). + +Net: 1 file changed, +22 / -29 lines (−7 simplification — new switch-over-tuple Equatable is both terser and more idiomatic). + +Plan / record: `docs/superpowers/plans/2026-04-20-postbox-to-telegramengine-wave-10.md`. + +## Wave 11 outcome (2026-04-20) + +Revisits `ActionSheetPeerItem` — one of the six wave-1 abandonments. The wave-1 blocker was that the public init took `postbox: Postbox` + `network: Network` explicitly, forcing the module to `import Postbox`, and the sole external caller (ShareController, out-of-wave at the time) couldn't be edited. This wave resolves the blocker without any rule-2 violation by routing the pair through `AccountStateManager`. + +**Init-surface collapse.** `ActionSheetPeerItem.init(accountPeerId:postbox:network:contentSettings:peer:…)` → `.init(accountPeerId:stateManager:contentSettings:peer:…)`. `AccountStateManager` is a TelegramCore public class whose public API surface includes `postbox: Postbox` and `network: Network` fields; passing the manager as a single handle lets the module hold on to the two values without ever naming `Postbox` in its own source. The setItem call site becomes `self.avatarNode.setPeer(…, postbox: item.stateManager.postbox, network: item.stateManager.network, …)` — Swift's type inference resolves `Postbox` through transitive module visibility (TelegramCore → AvatarNode), no `import Postbox` needed in the consumer. + +**Convenience init unchanged in shape.** The `(context: AccountContext, …)` convenience delegates to `(accountPeerId:stateManager:contentSettings:…)`; the two callable forms stay aligned. + +**Caller (`ShareController.swift:1146`).** Dropped `postbox: info.account.stateManager.postbox, network: info.account.stateManager.network` → single `stateManager: info.account.stateManager`. `ShareControllerAccountContext` (the per-switchable-account protocol) already exposes `stateManager: AccountStateManager`, so this is a collapse, not a signature divergence. ShareController continues to import Postbox for its own unrelated reasons; no change to its dependency profile. + +**Reusable pattern.** For any wave-1-style module that was abandoned because a public init takes `postbox: Postbox, network: Network` with avatar-rendering downstream: collapse to `stateManager: AccountStateManager` (TelegramCore type) and unpack inside the setItem/setPeer body. The pattern applies broadly — most wave-1 abandonments used this param-pair for avatar setup. Candidates to try next: `ChatListSearchRecentPeersNode`, `HorizontalPeerItem`, `SelectablePeerNode`, `ItemListPeerItem`, `ItemListAvatarAndNameInfoItem`, `ItemListStickerPackItem` (verify each by grep first — some may use `postbox` for non-avatar reasons). + +Net: 3 files changed, +8 / -15 lines. Build green (5854 actions, ~6min). + +Plan / record: (no plan doc this wave — single-module, low-complexity). + +## Wave 12 outcome (2026-04-20) + +Applies the wave-11 `stateManager: AccountStateManager` collapse pattern to `HorizontalPeerItem` — another wave-1-era candidate whose public init leaked `postbox: Postbox, network: Network`. Additionally ripples the collapse one layer up into `ChatListSearchRecentPeersNode`'s public init so the `HorizontalPeerItem` call site has `stateManager:` in scope. + +**`HorizontalPeerItem` fully Postbox-free.** `init(postbox: Postbox, network: Network, …)` + matching stored fields → `init(stateManager: AccountStateManager, …)` + `let stateManager`. SelectablePeerNode.setup call site routes via `item.stateManager.postbox` / `.network`. Module drops `import Postbox` and `//submodules/Postbox:Postbox` dep. + +**`ChatListSearchRecentPeersNode` public surface migrated, module still imports Postbox.** Public `init(accountPeerId:postbox:network:…)` → `init(accountPeerId:stateManager:…)`. Two private helpers (`item(…)` on `ChatListSearchRecentPeersEntry` and `preparedRecentPeersTransition(…)`) get the same collapse for forwarding. Internal uses of raw postbox (`_internal_recentPeers`, `postbox.peerView`, `postbox.combinedView`, `_internal_managedUpdatedRecentPeers`) rewritten to `stateManager.postbox` / `stateManager.network` — the module stays on `import Postbox` because of `PostboxViewKey` / `UnreadMessageCountsItem` / `UnreadMessageCountsView` usage inside the peerViews-to-unread-counts pipeline. That pipeline could be rewritten against `EngineDataMap` + `TelegramEngine.EngineData.Item.Peer.Notifications.*` in a future wave, but the public surface simplification is valuable standalone. + +**Two external caller sites migrated:** +- `ShareController/Sources/ShareControllerRecentPeersGridItem.swift:66-67` — `postbox: context.stateManager.postbox, network: context.stateManager.network` → `stateManager: context.stateManager` (ShareControllerAccountContext protocol already exposes `stateManager`). +- `ChatListUI/Sources/ChatListRecentPeersListItem.swift:125-126` — `postbox: item.context.account.postbox, network: item.context.account.network` → `stateManager: item.context.account.stateManager`. +- `SettingsUI/Sources/DeleteAccountPeersItem.swift:51-52` (call site for `HorizontalPeerItem`) — `postbox: context.account.postbox, network: context.account.network` → `stateManager: context.account.stateManager`. + +**Lesson reinforcement.** The wave-11 collapse pattern is very cheap to ripple through intermediate owners. Whenever a consumer module takes `(postbox:Postbox, network:Network)` purely to forward them to another call downstream, collapse to `stateManager: AccountStateManager` — no propagation fan-out required for the raw pair because the stateManager is a single handle. Even when the intermediate owner itself uses raw `postbox.peerView` internally (like this wave's `ChatListSearchRecentPeersNode`), the public surface still gets the collapse at zero cost. + +Net: 6 files changed, +26 / -36 lines. Build verified green (incremental, 136 actions). + +Plan / record: (no plan doc this wave — pattern-application, low-complexity). + +## Wave 13 outcome (2026-04-20) + +Targeted `AttachmentTextInputPanelNode` at the user's request. On inspection, the module was already Postbox-free at the source level (swept in wave 6) — its two `.swift` files compile fine without `import Postbox`. Two leftover items were fixed: + +1. **Dead `//submodules/Postbox:Postbox` BUILD dep** — wave 6 swept `^import Postbox$` lines from source but never touched BUILD files. `AttachmentTextInputPanelNode/BUILD` (and, it turns out, 97 other modules' BUILDs — see wave 14) still listed the dep despite no source file needing it. Removed. +2. **Two raw `peerId?.namespace == Namespaces.Peer.SecretChat` checks** (lines 436, 2102) migrated to use the existing `PeerId.isSecretChat` extension at `submodules/TelegramCore/Sources/Utils/PeerUtils.swift:615`. (First-pass attempt introduced a duplicate `isSecretChat` extension and failed with "invalid redeclaration" — note for future waves: always grep TelegramCore for an existing helper before adding.) + +**No new TelegramEngine methods/types introduced.** The refactor was smaller than anticipated; the module's migration debt had already been paid down by wave 6's source-level sweep. The BUILD-dep leftover and the namespace-equality sites were the only remaining items. Both are quality-of-life cleanups rather than structural migration. + +**Observation that drove wave 14.** Wave 6's methodology-note in the "Unused-import sweeps" guidance only measured Postbox-freeness by `^import Postbox$` lines in sources. After touching `AttachmentTextInputPanelNode/BUILD` in this wave, I noticed many other wave-6-swept modules still carry dead BUILD deps, ~= the wave-6 survivor count. That's the whole of wave 14. + +Net: 2 files changed, +2 / -3 lines. + +Plan / record: (no plan doc this wave — discovery pass). + +## Wave 14 outcome (2026-04-20) + +Build-dep sweep analogous to wave 6's source-import sweep: drop `//submodules/Postbox:Postbox` (and `//submodules/Postbox`) from every BUILD whose source files no longer `import Postbox`. + +**Methodology.** +1. For each `submodules/*/BUILD` referencing `submodules/Postbox`, check whether any `.swift` file in the module's `Sources/` tree has `^import Postbox$`. +2. If none do, speculatively drop the Postbox dep line from the BUILD via `sed -i '' -e '/^[[:space:]]*"\/\/submodules\/Postbox\(:Postbox\)\{0,1\}",[[:space:]]*$/d'`. +3. Full `Make.py build --continueOnError`. +4. Restore any BUILD that now fails to compile (none did). +5. Commit surviving drops. + +**Result.** 98 candidate BUILDs identified. **Zero iterations needed** — first-pass build came up green (80 incremental actions, no restores). Net: 98 BUILD files, −98 lines (each lost exactly its `//submodules/Postbox` dep line). + +**Why zero iterations.** Bazel Swift rules require source-level `import` for symbol resolution. If a module compiled after wave 6's `import Postbox` sweep, then none of its source files are physically referencing Postbox symbols. The BUILD-level dep was always redundant — it was carried for historical reasons (code likely once imported Postbox but was migrated off) but had no effect on either compilation or the actual dependency graph (Postbox is still transitively pulled in by TelegramCore, which every module depends on). Dropping it is a metadata cleanup with no semantic effect. + +**Lesson / reusable pattern.** +- After every source-level `import Postbox` sweep (wave-6 shape), run a matching BUILD-dep sweep immediately. Same candidate set, near-zero execution risk, same commit. +- Script for identifying candidates: + ```bash + find submodules -name "BUILD" -type f | while read build; do + dir=$(dirname "$build") + if grep -q "submodules/Postbox" "$build" 2>/dev/null && [ -d "$dir/Sources" ]; then + if ! grep -rq "^import Postbox$" "$dir/Sources" 2>/dev/null; then + echo "$dir" + fi + fi + done + ``` +- After waves 13+14, 194 modules still list `//submodules/Postbox` in BUILD — all of them have source files still importing Postbox. + +Net (wave 14 alone): 98 files changed, 0 insertions / 98 deletions. + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 15 outcome (2026-04-20) + +Applies the wave-11/12 `stateManager: AccountStateManager` collapse pattern to `SelectablePeerNode` — another wave-1-era candidate listed in the post-wave-14 shortlist. Module becomes fully Postbox-free (source + BUILD). + +**`SelectablePeerNode` fully Postbox-free.** Two public setup methods migrated: +- `setup(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, …)` → `setup(accountPeerId:, stateManager: AccountStateManager, …)`. +- `setupStoryRepost(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, …)` → `setupStoryRepost(accountPeerId:, stateManager: AccountStateManager, …)`. + +Internal forwards rewired: `AvatarNode.setPeer(…, postbox: stateManager.postbox, network: stateManager.network, …)` and `EmojiStatusComponent(postbox: stateManager.postbox, …)`. Neither site names `Postbox` in the consumer — Swift infers through transitive module visibility. + +**Namespaces.Peer.SecretChat fixup (×3).** Replaced `peer.peerId.namespace == Namespaces.Peer.SecretChat` checks with `peer.peerId.isSecretChat` at three sites, matching the wave-13 pattern (`PeerId.isSecretChat` at `TelegramCore/Sources/Utils/PeerUtils.swift:611`). The third site (`updateSelection` in the not-selected branch) additionally needed an `?? false` fallback — previous expression was `self.peer?.peerId.namespace == Namespaces.Peer.SecretChat` (optional-equals-non-optional produces `Bool`), new expression is `(self.peer?.peerId.isSecretChat ?? false)`. + +**Share-Extension boundary — `stateManager:` over `engine:`.** `SelectablePeerNode` is used by `ShareControllerPeerGridItem`, whose context is `ShareControllerAccountContext`. That protocol exposes `stateManager: AccountStateManager` and `engineData: TelegramEngine.EngineData`, but **no `engine: TelegramEngine`** — and the Share Extension's `ShareControllerAccountContextExtension` concrete impl has no `Account`, so constructing a full `TelegramEngine` (`init(account: Account)`) is physically unreachable there. This is the documented "rare but genuine" fallback to `stateManager:` from the user-preference memory (`feedback_postbox_refactor_handle.md`) — prefer `engine:` except when crossing the Share-Extension boundary. + +**Three external call sites migrated:** +- `HorizontalPeerItem/Sources/HorizontalPeerItem.swift:227` (wave 12's `stateManager:` field now forwards directly): `postbox: item.stateManager.postbox, network: item.stateManager.network` → `stateManager: item.stateManager`. +- `ShareController/Sources/ShareControllerPeerGridItem.swift:237` (setup): `postbox: context.stateManager.postbox, network: context.stateManager.network` → `stateManager: context.stateManager`. +- `ShareController/Sources/ShareControllerPeerGridItem.swift:273` (setupStoryRepost): same. + +**Convenience init unchanged.** `setup(context: AccountContext, …)` now delegates with `stateManager: context.account.stateManager`; signature unchanged — `JoinLinkPreviewPeerContentNode.swift:147` (the one caller using the convenience init) needed no edit. + +Net: 4 files changed, +12 / -17 lines. Build verified green (193 actions, 131s — Telegram.ipa target built successfully). + +Plan / record: (no plan doc this wave — pattern-application, low-complexity). + +## Wave 16 outcome (2026-04-20) + +Two-commit wave targeting `ItemListPeerItem`. Planning-time inventory (`project_postbox_wave16_plan.md`) only grepped for `Postbox`/`Network` tokens and missed two Postbox-defined public-surface types: `EngineMessageHistoryThread.Info?` (on `threadInfo`) and `PeerStoryStats?` (on `storyStats`). The first-pass "drop `import Postbox`" attempt failed at build time. Rather than abandon, the wave split into 16a (move `EngineMessageHistoryThread` to TelegramCore — clean, independently valuable) and 16b (partial `engine:` collapse on `ItemListPeerItem`, keeping `import Postbox` because `PeerStoryStats` remains Postbox-defined). + +**Wave 16a — move `EngineMessageHistoryThread` to TelegramCore.** Before: Postbox declared an empty `public final class EngineMessageHistoryThread` namespace with a nested `public final class Item`; TelegramCore's `ForumChannels.swift` added the `.Info` nested type via `public extension EngineMessageHistoryThread { final class Info … }`. The outer name's Postbox residency forced every consumer of `.Info` to `import Postbox` too. After: promote Postbox's internal `MutableMessageHistoryThreadIndexView.Item` to a top-level public type `MessageHistoryThreadIndexItem`; delete the empty `EngineMessageHistoryThread` class from Postbox; move the class shell into `ForumChannels.swift`, collapsing the existing extension into a proper class definition (`public final class EngineMessageHistoryThread { class Info … }`). + +`MessageHistoryThreadIndexView.items` type changes from `[EngineMessageHistoryThread.Item]` to `[MessageHistoryThreadIndexItem]`; its init simplifies (no more wrap/unwrap conversion — the old init re-built items element-by-element just to swap the outer wrapper name). The second public extension on `EngineMessageHistoryThread` (`.NotificationException`, at `ForumChannels.swift:1318`) works unchanged — same-module extension after the class moves. + +Zero consumer-site changes: the two Postbox-consumer iteration sites (`ChatListUI/Sources/Node/ChatListNodeLocation.swift:229`, `ShareController/Sources/ShareControllerNode.swift:2086`) iterate with `for item in view.items` (no type annotation) and access only fields that exist identically on both types (`id`, `info`, `index`, `pinnedIndex`, `tagSummaryInfo`, `topMessage`, `embeddedInterfaceState`). + +Commit `3bb22d503c`. Net: 2 files, +67 / −111 (Postbox file nets −174 lines, TelegramCore file +4). + +**Wave 16b — `ItemListPeerItem.Context` `engine:` collapse.** Wave-11 pattern applied to `ItemListPeerItem.Context.Custom`. Before: `Context.Custom.init(accountPeerId:, postbox: Postbox, network: Network, animationCache:, animationRenderer:, isPremiumDisabled:, resolveInlineStickers:)` + matching stored fields; `Context` had computed `postbox: Postbox` and `network: Network` that switched over the `.account` / `.custom` cases. After: `Context.Custom.init(accountPeerId:, engine: TelegramEngine, animationCache:, animationRenderer:, isPremiumDisabled:, resolveInlineStickers:)`; `Context` has one computed `engine: TelegramEngine` that returns `context.engine` for the `.account` case and `custom.engine` for the `.custom` case. Six internal forwards rewire from `item.context.postbox` / `item.context.network` to `item.context.engine.account.postbox` / `item.context.engine.account.network` (three `EmojiStatusComponent(postbox:…)` sites and three `AvatarNode.setPeer(…, postbox:…, network:…, …)` sites). + +Handle choice: `engine:` (not `stateManager:`). The sole external `.custom(Custom(...))` construction site codebase-wide is `PeerInfoSettingsItems.swift:121` — main-app-only, doesn't cross the Share-Extension boundary. `peerAccountContext` in that loop is typed `AccountContext` (from the `accountsAndPeers: [(AccountContext, EnginePeer, Int32)]` field), so `.engine: TelegramEngine` is directly available. Per the standing guidance from `feedback_postbox_refactor_handle.md`, prefer `engine:` except when physically forced to `stateManager:` by a Share-Extension boundary. + +All 37 other `ItemListPeerItem(…)` construction sites use the `.account(context: AccountContext)` convenience overload (at L485) and need no change. `PeerInfoScreenMemberItem.swift:223` forwards its own `context: ItemListPeerItem.Context` field straight through (pass-through) — no change. + +Module does **not** become Postbox-free: `PeerStoryStats?` remains on the `storyStats` public-surface field. `PeerStoryStats` is defined in `Postbox/Sources/ChatListView.swift:281` and is deeply baked into Postbox view APIs (`PeerView.storyStats`, `PeerStoryStatsView.storyStats`, `ChatListEntry.storyStats`, `MessageHistoryView.peerStoryStats`, `Postbox.getPeerStoryStats(peerId:)`). Moving it would require a cross-module wrapper rewrite across Postbox, TelegramCore, and every view consumer — out of scope for wave 16. + +Commit `a5432e44a8`. Net: 2 files, +17 / −30. + +**Lessons.** +- **Public-surface inventory must go beyond the collapse-target tokens.** Waves 11/12/15's `stateManager`/`engine` collapses were clean because their target modules had no other Postbox-defined public types. Wave 16's planning inventory only grepped for `Postbox`/`Network` and missed `EngineMessageHistoryThread` + `PeerStoryStats` — both symbols whose names happen to not include `Postbox`. For future wave-11-pattern candidates, planning-time grep should include the full alphabet of Postbox-defined public types: `^public\s+(class|struct|enum|protocol|typealias)\s+\w+` over `submodules/Postbox/Sources/` to build an exhaustive type-name allowlist, then grep for any of those names in the candidate module's public surface. +- **"Engine"-prefixed types can still be Postbox-defined.** `EngineMessageHistoryThread` has an "Engine" prefix but was declared in Postbox all along; the `.Info` nested type living in TelegramCore was a code-organization half-measure that still forced `import Postbox` on consumers. Don't trust naming conventions; grep for the defining module. +- **Splitting a failing wave into a cleanup + a partial collapse is often the right move.** Wave 16 could have been abandoned entirely when the build failed — instead, the `EngineMessageHistoryThread` move (which had been a latent cleanup opportunity for the entire history of the `.Info` extension) was promoted to a standalone commit (16a), and the partial `engine:` collapse shipped as a second commit (16b). Both are independently valuable; the wave's "module becomes Postbox-free" goal didn't land but other goals did. +- **The "promote internal Postbox `Item` to top-level, drop Postbox wrapper class, move wrapper class to TelegramCore" pattern generalizes.** Any Postbox-defined class whose only role is to namespace a TelegramCore extension is a candidate for this move. Future audit target: `grep -l "public extension " submodules/TelegramCore/Sources/` where `` is a Postbox-defined outer type with no semantic content of its own. + +Plan / record: `project_postbox_wave16_plan.md` (updated with outcome). + +## Wave 17 outcome (2026-04-20) + +Applies the wave-11/12/15 `stateManager: AccountStateManager` collapse pattern to `ItemListAvatarAndNameInfoItem` — another wave-1-era candidate. Module becomes fully Postbox-free (source + BUILD). Clean one-shot execution (no abandonment, no replan). + +**`ItemListAvatarAndNameInfoItem.ItemContext` enum case collapsed.** Before: `case other(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network)` + matching destructure at L761 + `AvatarNode.setPeer(…, postbox: postbox, network: network, …)` internal forward. After: `case other(accountPeerId: EnginePeer.Id, stateManager: AccountStateManager)` + `case let .other(accountPeerId, stateManager):` destructure + `AvatarNode.setPeer(…, postbox: stateManager.postbox, network: stateManager.network, …)` forward. The `.accountContext(AccountContext)` sister case is unchanged. + +**Share-Extension-boundary handle choice: `stateManager:`.** The sole external `.other(...)` construction site codebase-wide is `DeviceContactInfoController.swift:413`, inside a ternary that fires only when `arguments.context` is not a `ShareControllerAppAccountContext` — i.e., when running inside the Share Extension. `ShareControllerAccountContext` (protocol at `AccountContext/Sources/ShareController.swift:16`) exposes `stateManager: AccountStateManager` but not `engine: TelegramEngine`, and constructing a full `TelegramEngine` is physically unreachable in the Share Extension's `ShareControllerAccountContextExtension` impl (no `Account`). Per `feedback_postbox_refactor_handle.md` and the wave-15 precedent, use `stateManager:` at Share-Extension boundaries. + +**Pre-flight inventory was correct.** Running the public-Postbox-type inventory grep returned only `Postbox` itself (the one enum-case payload leak) — no `EngineMessageHistoryThread`-style surprises. Wave 17 validates the post-wave-16 lesson: when planning-time inventory uses the full Postbox public-types allowlist (not just `Postbox`/`Network` tokens), wave-11-shape candidates execute cleanly. + +**Single external caller migrated:** +- `PeerInfoUI/Sources/DeviceContactInfoController.swift:413` — `postbox: arguments.context.stateManager.postbox, network: arguments.context.stateManager.network` → `stateManager: arguments.context.stateManager`. The enclosing `PeerInfoUI` module still imports Postbox for its own unrelated reasons; that stays. + +The 5 other `ItemListAvatarAndNameInfoItem(itemContext:…)` construction sites codebase-wide all use `.accountContext(arguments.context)` and need no change (`ChannelBannedMemberController.swift:321`, `DeviceContactInfoController.swift:415`, `ChannelAdminController.swift:370`, `CreateChannelController.swift:197`, `CreateGroupController.swift:324`). + +**Pattern-consistency note (reinforced).** `accountPeerId: EnginePeer.Id` is kept as a separate enum-case payload even though `AccountStateManager` also exposes `accountPeerId`. This matches waves 11/12/15 (`ActionSheetPeerItem`, `ChatListSearchRecentPeersNode`, `SelectablePeerNode` all kept `accountPeerId` explicit alongside `stateManager`). Future wave-11-pattern executions should default to this shape unless a specific reason exists to collapse further. + +Net: 3 files changed, +4 / -5 lines (ItemListAvatarAndNameItem.swift: +2 / -3, DeviceContactInfoController.swift: +1 / -1, BUILD: −1). Build verified green for target modules (`ItemListAvatarAndNameInfoItem`, `PeerInfoUI` both compiled and linked successfully); the one unrelated failing target in the full build (`ChatMessageInteractiveMediaNode.swift`) is user-uncommitted work-in-progress that predates this wave. + +Plan / record: (plan doc `project_postbox_wave17_plan.md` deleted post-commit per the plan's own post-commit housekeeping instructions). + +## Wave 18 outcome (2026-04-20) + +Mixed-shape wave targeting `ItemListStickerPackItem`. Originally shortlisted (post-wave-17) as "likely wave-11 shape", but plan-writing-time inspection invalidated that hypothesis — the module's public API doesn't take `postbox:`/`network:`. Actual shape combined three existing wave patterns plus a narrow typealias addition. Module becomes fully Postbox-free (source + BUILD). + +**Three narrow typealiases added to TelegramCore.** `submodules/TelegramCore/Sources/TelegramEngine/Utils/EnginePostboxCoding.swift` grew by 3 lines: + +- `EngineItemCollectionId = ItemCollectionId` — needed at public closure-param positions. +- `EngineFetchResourceSourceType = FetchResourceSourceType` — needed at `var updatedFetchSignal` type annotation. +- `EngineFetchResourceError = FetchResourceError` — same. + +Per CLAUDE.md rule 1 these narrow-utility typealiases are explicitly allowed (same shape as the existing `EngineMemoryBuffer`/`EnginePostboxDecoder`/… batch). Cheat sheet updated. + +**Wave-4 enum-payload migration on `StickerPackThumbnailItem`.** Public enum case `animated(MediaResource, PixelDimensions, Bool, Bool)` → `animated(EngineMediaResource, PixelDimensions, Bool, Bool)`. Equatable `==` simplified: `lhsResource.isEqual(to: rhsResource)` → `lhsResource == rhsResource` (uses `EngineMediaResource.==` which has identical semantics). Two construction sites wrapped via `EngineMediaResource(thumbnail.resource)` / `EngineMediaResource(itemFile.resource)`. Two destructure-and-forward sites unwrap via `resource._asResource()` when handing off to `chatMessageStickerPackThumbnail(resource: MediaResource)` and `AnimatedStickerResourceSource(account:, resource: MediaResource, …)`. One `resource.id` site (for `shortLivedResourceCachePathPrefix`) needs the raw `MediaResourceId`, handled by a local `let rawResource = resource._asResource()` that serves both the `.id` read and the `AnimatedStickerResourceSource` init in the same block. + +**Wave-3 facade swap.** `fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: resourceReference)` → `item.context.engine.resources.fetch(reference: resourceReference, userLocation: .other, userContentType: .sticker)`. Engine facade (`TelegramEngine.Resources.fetch`) already exists from wave 3; no new TelegramEngine API needed. + +**External-caller check confirmed zero source edits needed.** `StickerPackThumbnailItem` has no external consumers (UndoUI declares its own nested-private same-named enum). The 6 external `ItemListStickerPackItem(setPackIdWithRevealedOptions:)` caller sites all pass closures with inferred param types; `EngineItemCollectionId` being a typealias to `ItemCollectionId` makes the types interchangeable. The 3 module-field declarations outside the target module that name `(ItemCollectionId?, ItemCollectionId?) -> Void` explicitly (`SettingsUI/Stickers/ArchivedStickerPacksController.swift:27`, `SettingsUI/Stickers/InstalledStickerPacksController.swift:27`, and the init at L32/L42 of those same files) compile unchanged — those modules still import Postbox for their own reasons, and `EngineItemCollectionId == ItemCollectionId` so no rename is required. + +**BUILD dep dropped.** `//submodules/Postbox:Postbox` removed from `submodules/ItemListStickerPackItem/BUILD`. + +**Pre-existing `ChatMessageInteractiveMediaNode.swift` WIP still present at build time — no longer failing.** The uncommitted change introduces an `allowSticker` validation around secret-chat sticker playback (~30 lines added in the `currentReplaceAnimatedStickerNode` block). Per wave-17's note it had failed to compile; on this wave's full build (`bazel build Telegram/Telegram`, 565 actions, 258s, 0 errors) it compiled and linked without issue. Either the user fixed it between waves 17 and 18, or the bazel dependency graph simply needed a full rebuild. Either way, wave 18's build was clean end-to-end — `Telegram.ipa` target built successfully, zero errors across the entire project. + +**Pattern-consistency note.** Wave 18 is the third wave (after 3 and 9) where the cheapest path requires adding narrow TelegramCore-side typealiases rather than keeping `import Postbox` in the consumer. The threshold is: if the consumer needs to NAME a Postbox-defined type (not just use it via inference), and no engine-prefixed alias exists, adding a narrow typealias is preferred over `import Postbox`. The alternative of refactoring the code to avoid naming the type (e.g., reshaping `var foo: Signal?` to infer from first assignment) is usually unwieldy when the var is conditionally-assigned; typealiases win on readability. + +Net: 3 files changed. +- `submodules/TelegramCore/Sources/TelegramEngine/Utils/EnginePostboxCoding.swift`: +3 / -0. +- `submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift`: ~13 lines touched across 9 sites; net +4 / -4. +- `submodules/ItemListStickerPackItem/BUILD`: 0 / -1. +- `CLAUDE.md`: +3 cheat-sheet lines + this outcome paragraph. + +Plan / record: `memory/project_postbox_wave18_plan.md` (deleted post-commit per the plan's own housekeeping instructions). + +## Wave 19 outcome (2026-04-20) + +Single-facade expansion. Additive-only — adds `TelegramEngine.Resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id) -> String` at `submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift:456`. Body: `self.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(MediaResourceId(id.stringRepresentation))`. + +No consumer migrations this wave. Known consumers (≥25 call sites across ~15 modules: AvatarVideoNode, DrawingUI, SettingsUI/ThemePickerGridItem, PremiumUI/StickersCarouselComponent, ReactionSelectionNode, ReactionContextNode, ChatSendMessageActionUI, ItemListStickerPackItem, ChatThemeScreen, ThemeCarouselItem, PeerInfoBirthdayOverlay, SettingsThemeWallpaperNode, MediaEditorComposerEntity, ChatQrCodeScreen, ChatMessageAnimatedStickerItemNode, ChatMessageItemView, GiftCompositionComponent) migrate in a follow-up wave using the pattern `X.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(Y.resource.id)` → `X.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(Y.resource.id))`. + +**Why not bundle consumer migration in the same wave?** Wave-3's original shape did bundle (3 facades + 1 full consumer module in one commit), but the consumer pool for this particular facade is large (~25 sites) and each call site only partially de-Postboxes its module — the caller modules need full inventory before deciding whether to drop `import Postbox`. Keeping wave 19 narrow (facade-only) lets follow-up waves approach consumer-module migration on a per-module basis without the facade-addition blocking anything. + +Net: 1 file changed, +4 / -0. + +Plan / record: (no plan doc this wave — single-method addition, target pre-identified in `project_postbox_refactor_next_wave.md`). + +## Wave 20 outcome (2026-04-21) + +Consumer sweep for the wave-19 `shortLivedResourceCachePathPrefix` facade. 22 call sites across 16 modules migrated atomically. Pattern (repeated identically at every site): `X.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(Y.resource.id)` → `X.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(Y.resource.id))`. + +**Modules migrated (alphabetical):** +- `AvatarVideoNode/Sources/AvatarVideoNode.swift` (1 site) +- `ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift` (1 site) +- `DrawingUI/Sources/DrawingStickerEntityView.swift` (1 site) +- `ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift` (1 site; simplified from wave-18's `let rawResource = resource._asResource(); …shortLivedResourceCachePathPrefix(rawResource.id)` + `AnimatedStickerResourceSource(…, resource: rawResource, …)` to `…shortLivedResourceCachePathPrefix(id: resource.id)` + `AnimatedStickerResourceSource(…, resource: resource._asResource(), …)` — drops the intermediate `let rawResource`) +- `PremiumUI/Sources/StickersCarouselComponent.swift` (2 sites) +- `ReactionSelectionNode/Sources/ReactionContextNode.swift` (2 sites) +- `ReactionSelectionNode/Sources/ReactionSelectionNode.swift` (6 sites — 4 unique expression templates, handled via targeted Edits against the unique argument expression at each call) +- `SettingsUI/Sources/ThemePickerGridItem.swift` (1 site) +- `TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift` (2 sites) +- `TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift` (1 site) +- `TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift` (1 site) +- `TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift` (1 site) +- `TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift` (3 sites) +- `TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoBirthdayOverlay.swift` (2 sites) +- `TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift` (1 site) +- `TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift` (1 site) + +**One site intentionally skipped:** `TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift:245`. That site uses a local `postbox: Postbox` init-parameter, not `context.account.postbox`, so the migration would require changing the init's parameter from `postbox:` to something engine-based and fanning out to its callers. Out of scope — handled by a future module-scoped wave. + +**No modules became Postbox-free this wave.** Each of the 16 migrated modules still has other Postbox usage (raw `Postbox` types in signatures, `fetchedMediaResource(mediaBox:)` calls, `postbox.transaction`, etc.). Consumer-side `shortLivedResourceCachePathPrefix` closure is just one of several reasons these modules import Postbox. Future wave-shape: module-scoped de-Postbox per-module inventory. + +**Pattern validation.** This is the most mechanical consumer sweep to date — all 22 sites followed identical shape, allowing `replace_all=true` for sites with duplicate identical call expressions (ReactionSelectionNode hit this at 3 sites for `largeListAnimation`, 2 for `stillAnimation`, 1 for `listAnimation`). First-pass build was clean (35 actions, 0 errors) — no iteration loop. Confirms the wave-19 facade shape is sound. + +**Build verification.** `bazel build Telegram/Telegram --keep_going` — 2042 action cache hits + 35 new actions, 0 errors, `Telegram.ipa` up-to-date. + +Net: 16 files changed, all edits mechanical (before → after): +22 insertions / -22 deletions at migrated sites, plus 1 deletion in ItemListStickerPackItem (wave-18 `let rawResource` line dropped). Approximate total: +22 / -23. + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 21 outcome (2026-04-21) + +Combined wave-19+wave-20 shape: facade addition + consumer sweep in a single atomic commit. Adds `TelegramEngine.Resources.completedResourcePath(id: EngineMediaResource.Id, pathExtension: String? = nil) -> String?` facade; sweeps 29 consumer sites across 14 files. + +**Facade added at `TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift:460`.** Body: `self.account.postbox.mediaBox.completedResourcePath(id: MediaResourceId(id.stringRepresentation), pathExtension: pathExtension)`. Wraps the Postbox `MediaBox.completedResourcePath(id: MediaResourceId, pathExtension: String?)` overload; consumers that previously called the resource-taking overload (`MediaBox.completedResourcePath(_ resource: MediaResource, …)`) migrate through the id path (`.resource.id` is already `MediaResourceId`). + +**28 Shape-A consumer sites + 1 Shape-B (already-id-overload) migrated:** +- `SettingsUI/Sources/Themes/EditThemeController.swift` (1 site) +- `BrowserUI/Sources/BrowserPdfContent.swift` (1 site) +- `BrowserUI/Sources/BrowserDocumentContent.swift` (1 site) +- `GalleryUI/Sources/SecretMediaPreviewController.swift` (1 site) +- `TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift` (1 site, `pathExtension: "mp4"`) +- `TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift` (7 sites across 3 functions; 4 unique expression templates, handled via `replace_all=true` where identical) +- `TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift` (1 site) +- `TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift` (5 sites; 4 used `resource` expr identically via `replace_all=true`, 1 used `file.file.resource`) +- `TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift` (1 site, `pathExtension: nil`) +- `TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift` (1 site) +- `TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift` (7 sites, all identical `telegramFile.resource` — handled via `replace_all=true`) +- `TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift` (1 site) +- `TelegramUI/Sources/OpenChatMessage.swift` (1 site) +- `TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift` (1 site) +- `TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift` (1 site, Shape B — was already using the `id:` overload; migrated identically to `EngineMediaResource.Id(...)`) + +**8 sites intentionally skipped (Shape C/D).** Listed in the plan — 5 Shape-C sites that access a raw `account: Account` parameter (no `.engine` on `Account`) and 3 Shape-D sites that carry a local `postbox: Postbox` stored field. Both shapes need module-scoped init-signature rework rather than per-site sweep; defer to future waves. + +**No modules became Postbox-free.** Each consumer has other Postbox usage (signatures, transactions, other mediaBox calls). Matches waves 19/20's expectation. + +**Build validation.** `bazel build Telegram/Telegram --keep_going` — clean first-pass build (569 processes, 1556 action cache hits + 30 local + 532 worker, 240s, 0 errors, `Telegram.ipa` up-to-date). + +**Pattern validation.** Wave-shape G (facade addition + consumer sweep in a single commit) works well when the consumer pool is bounded and mechanical. 29 sites in 14 files is comfortably within the threshold. Kept waves 19 and 20 separate because 25+ sites across that many modules was at the edge of reviewability; wave 21's similar fan-out fits because the plan pre-classified every site by shape. When the plan does the classification work upfront, combined waves are cheaper to review and ship. + +Net: 14 files changed. TelegramEngineResources.swift: +4 / -0. Consumer files: +29 / -29 (mechanical rewrite at each site). CLAUDE.md: +outcome paragraph. + +Plan / record: `memory/project_postbox_wave21_plan.md` (deleted post-commit per the plan's own housekeeping instructions). + +## Wave 22 outcome (2026-04-21) + +Follows wave 21's pattern: facade addition + consumer sweep in a single atomic commit. Adds `TelegramEngine.Resources.storeResourceData(id: EngineMediaResource.Id, data: Data, synchronous: Bool = false)` facade; sweeps 46 consumer sites across 17 files. + +**Facade added at `TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift:464`.** Body: `self.account.postbox.mediaBox.storeResourceData(MediaResourceId(id.stringRepresentation), data: data, synchronous: synchronous)`. Wraps Postbox's `MediaBox.storeResourceData(_ id: MediaResourceId, data: Data, synchronous: Bool)` full-file overload. The range-store overload (`MediaBox.storeResourceData(_:range:data:)`) is used at a single site inside `HLSVideoJSNativeContentNode.swift:302` via a local `postbox: Postbox` field (Shape D), which is out of scope for this wave; the range overload gets no facade wrapper this round. + +**46 Shape-A consumer sites migrated:** +- `ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift` (2) +- `DebugSettingsUI/Sources/DebugController.swift` (8 — 6 identical `gzippedData` batched via `replace_all=true`; `logData`, `allStatsData` handled individually) +- `BrowserUI/Sources/BrowserWebContent.swift` (1) +- `TelegramUI/Sources/CreateChannelController.swift` (4) +- `TelegramUI/Sources/CreateGroupController.swift` (4) +- `TelegramUI/Sources/Chat/ChatControllerPaste.swift` (1) +- `TelegramUI/Sources/Chat/ChatControllerOpenDocumentScanner.swift` (3) +- `TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift` (2) +- `TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift` (2) +- `TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift` (2) +- `TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift` (6 — 3 `thumbnailResource`, 3 `resource`; both handled via `replace_all=true`) +- `SettingsUI/Sources/Themes/ThemePreviewController.swift` (1) +- `SettingsUI/Sources/Themes/EditThemeController.swift` (1) +- `TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift` (3) +- `TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift` (2) +- `TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkOptions.swift` (1) +- `TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift` (3) + +**Out of scope — not migrated this wave:** +- `accountManager.mediaBox.storeResourceData(...)` sites (Account-manager-scoped, not account-scoped) — 13+ sites across WallpaperGalleryItem, WallpaperGalleryController, ThemeAccentColorController, WallpaperUtils, WebBrowserSettingsController, ThemeUpdateManager, OpenResolvedUrl, and others. These are a different migration path entirely (not a `TelegramEngine.Resources.*` target) and stay raw. +- `account.postbox.mediaBox.storeResourceData(...)` (raw `Account`, no `AccountContext`) — ~9 sites in LegacyMediaPickerUI, TelegramCallsUI, InAppPurchaseManager, AuthorizationUI, PeerInfoScreenAvatarSetup closures, WallpaperResources. Shape C from wave-21 taxonomy. Needs per-module rework. +- `self.postbox.mediaBox.storeResourceData(...)` / `postbox.mediaBox.storeResourceData(...)` inside TelegramCore internals (`TransformOutgoingMessageMedia.swift`, `AccountStateManager.swift`, `AvailableReactions.swift`, `SaveSecureIdValue.swift`, `PeerPhotoUpdater.swift`, `NotificationSoundList.swift`, `Stories.swift`, `Authorization.swift`, `WebpagePreview.swift`). These are Postbox-internal layer by design — keep as-is. +- `HLSVideoJSNativeContentNode.swift:302` — uses the range-store overload via local `postbox: Postbox` field. Out of scope. + +**No modules became Postbox-free.** Matches waves 19/20/21 expectation — each consumer has other Postbox usage. + +**Build validation.** `bazel build Telegram/Telegram --keep_going` — clean first-pass build (571 processes, 1554 action cache hits + 30 local + 532 worker, 229s, 0 errors, `Telegram.ipa` up-to-date). + +**Pattern validation.** Wave-shape G (facade + consumer sweep in one commit) scales well up to 46 sites in 17 files when the pattern is mechanical. Heavy `replace_all=true` usage where call-text is identical across sites (DebugController's 6 `gzippedData` sites, WallpaperUtils' 6 sites split into 2 batches by first-arg variable, ChatControllerOpenDocumentScanner's identical `(resource.id, data: data, synchronous: true)` pattern) keeps diff noise to the minimum. 46 sites, mostly done via replace_all + a few individual edits. + +Net: 17 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources.swift: +4 / -0. Consumer files: +46 / -46 (mechanical rewrite). + +Plan / record: (no plan doc this wave — mechanical sweep following wave-21 recipe). + +## Wave 23 outcome (2026-04-21) + +Smallest wave so far: `cancelInteractiveResourceFetch` facade addition + consumer sweep. Same shape as waves 21/22. + +**Facade added at `TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift:468`.** Body: `self.account.postbox.mediaBox.cancelInteractiveResourceFetch(resourceId: MediaResourceId(id.stringRepresentation))`. Wraps Postbox's `MediaBox.cancelInteractiveResourceFetch(resourceId: MediaResourceId)` overload (the `_ resource: MediaResource` overload delegates to the id version anyway). + +**5 of 7 Shape-A consumer sites migrated:** +- `PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift` (1) +- `GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift` (1) +- `GalleryUI/Sources/Items/ChatImageGalleryItem.swift` (1) +- `GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift` (1) +- `GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift` (1) + +**2 sites intentionally skipped:** `ChatMessageInteractiveMediaNode.swift:1474, 1709` — this file has pre-existing uncommitted WIP (the `allowSticker` validation around secret-chat sticker playback, carried forward since before wave 17). Editing the 2 sites would mix my wave-23 changes with the user's WIP in a single git diff, which `git add` can't cleanly separate. Deferred until the WIP lands or a narrow follow-up wave intentionally includes both. Note: a future wave that aims to drop those 2 sites should first either (a) wait for the WIP to be committed or (b) use `git stash --keep-index` + targeted edits + selective staging to split the diff cleanly. + +**Pattern note on WIP interference.** This is the first wave to hit this failure mode — previous waves' mechanical sweeps happened not to touch `ChatMessageInteractiveMediaNode.swift`. Future sweeps should grep their candidate set against `git status`'s modified-files list before starting, and either (a) defer sites in WIP files, (b) wait for the WIP to commit, or (c) stage selectively via `git add --patch`-equivalent paths. + +**Build validation.** `bazel build Telegram/Telegram --keep_going` — clean first-pass build (558 processes, 1567 action cache hits + 19 local + 532 worker, 236s, 0 errors, `Telegram.ipa` up-to-date). + +Net: 5 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources.swift: +4 / -0. Consumer files: +5 / -5. + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 24 outcome (2026-04-21) + +`moveResourceData` facade additions + consumer sweep. Same shape as waves 21-23. + +**Two facades added at `TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift`:** +- `moveResourceData(id: EngineMediaResource.Id, toTempPath: String)` wraps the `(MediaResourceId, toTempPath:)` overload. +- `moveResourceData(from: EngineMediaResource.Id, to: EngineMediaResource.Id, synchronous: Bool = false)` wraps the `(from: MediaResourceId, to: MediaResourceId, synchronous:)` overload. + +Postbox's third overload `(MediaResourceId, fromTempPath:)` has no consumer-side usage; no facade added this wave (YAGNI). + +**6 Shape-A consumer sites migrated (5 files):** +- `TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift` (1, `toTempPath:`) +- `TelegramUI/Sources/OverlayAudioPlayerController.swift` (1, `from:to:synchronous:`) +- `TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift` (2) +- `TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift` (2) + +**Build validation.** `bazel build Telegram/Telegram --keep_going` — clean first-pass build (563 processes, 272s, 0 errors). + +Net: 5 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources.swift: +8 / -0. Consumer files: +6 / -6. + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 25 outcome (2026-04-21) + +`copyResourceData` facade additions + consumer sweep. Same shape as waves 21-24. + +**Two facades added:** `copyResourceData(id: EngineMediaResource.Id, fromTempPath: String)` and `copyResourceData(from: EngineMediaResource.Id, to: EngineMediaResource.Id, synchronous: Bool = false)`. + +**4 Shape-A consumer sites migrated (3 files):** +- `PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift` (2, `from:to:synchronous:`) +- `ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift` (1, `from:to:` — simplified from `localResource._asResource().id` to `localResource.id` since operands are `EngineMediaResource`) +- `TelegramUI/Sources/Chat/ChatControllerPaste.swift` (1, `id:fromTempPath:`) + +**Minor simplification lesson.** When a consumer already has an `EngineMediaResource`-typed local (e.g., from a wave-18-migrated callee), prefer `localResource.id` over `EngineMediaResource.Id(localResource._asResource().id)` — the two are semantically equivalent since `EngineMediaResource.id` is defined as `Id(self.resource.id)`. This halves the verbosity at the call site and removes a redundant unwrap-and-rewrap. + +**Build validation.** Clean first-pass build (563 processes, 242s, 0 errors). + +Net: 3 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources.swift: +8 / -0. + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 27 outcome (2026-04-22) + +`preferencesView` consumer sweep (wave-9 pattern continuation). No new TelegramCore facades — leverages existing `TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key:)`. + +**Shape.** Replace `context.account.postbox.preferencesView(keys: [])` — returning `Signal` — with `context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ))` — returning `Signal`. Downstream, rename `.values[]?.get(.self)` → `?.get(.self)` at each closure parameter. + +**30 consumer files, ~40 call sites migrated** across ChatListUI, ContactListUI, DebugSettingsUI, GalleryUI, PeersNearbyUI, SettingsUI, TelegramCallsUI, TelegramUI, TelegramUI/Components, WebSearchUI. Full list in `git show --stat `. + +**Multi-key sites (PresentationCallManager).** 3 sites used `preferencesView(keys: [voipConfiguration, appConfiguration])`. Migrated via the two-arg `engine.data.subscribe(itemA, itemB) |> take(1)` overload, which returns `Signal<(PreferencesEntry?, PreferencesEntry?), NoError>`. Closures that accessed `preferences.values[X]?.get(...)` rewritten to `preferences.0?.get(...)` and `preferences.1?.get(...)`. + +**Direct-postbox-param helper migrated.** `AccountContext.swift`'s `getAppConfiguration(postbox: Postbox)` helper (one internal caller only) was rewritten to `getAppConfiguration(engine: TelegramEngine)` in the same commit, switching its single call site from `getAppConfiguration(postbox: account.postbox)` to `getAppConfiguration(engine: self.engine)`. + +**Annotation update in NotificationExceptionControllerNode.swift.** An explicit signal type `Signal<(…, PreferencesView, …), NoError>` in a `mapToSignal` return was updated to `Signal<(…, PreferencesEntry?, …), NoError>`. The file still imports Postbox because `PreferencesEntry` is (for now) a Postbox-defined type surfaced through TelegramCore's `EnginePreferencesEntry` typealias — a future wave-6-style `import Postbox` sweep would clean up such imports where they're now the only Postbox reference. + +**Deliberately skipped in this wave.** +- `TelegramPermissionsUI/Sources/PermissionSplitTest.swift:100` — `permissionUISplitTest(postbox: Postbox)` is a public API whose product value `PermissionUISplitTest` itself stores `postbox: Postbox` to satisfy the `SplitTest` protocol. Proper migration requires a protocol-level refactor (or wholesale rewrite of the SplitTest abstraction) beyond this wave's scope. +- 5 TelegramCore-internal `postbox.preferencesView(...)` sites (ChatListFiltering × 3, ContentSettings × 1, ManagedGlobalNotificationSettings × 1) — the refactor only migrates consumer modules, not TelegramCore internals. + +**Build validation.** Clean first-pass build (748 processes, 227s, 0 errors). No new facades to test, shape was validated across 30 files on the first attempt. + +**Lesson — multi-key preferencesView migration.** `engine.data.subscribe(itemA, itemB)` exists and returns a Swift tuple. When a Postbox `preferencesView(keys: [K1, K2])` call is inside a `combineLatest(...)` whose downstream closure accesses `.values[K1]` and `.values[K2]`, prefer the two-arg subscribe form (vs. two separate subscribes combined externally) — it preserves `combineLatest` arity exactly. Rewrite `.values[K1]?.get(T.self)` → `.0?.get(T.self)`, `.values[K2]?.get(T.self)` → `.1?.get(T.self)`. The closure parameter name stays (e.g., `preferences`) because the tuple destructure preserves the variable-name semantics at the call site. + +Net: 30 consumer files. No TelegramCore changes. CLAUDE.md facade-inventory table unchanged (no new facades). + +Plan / record: `memory/project_postbox_wave27_plan.md` (deleted post-wave). + +## Wave 26 outcome (2026-04-21) + +`resourceRangesStatus` + `removeCachedResources` facade additions + consumer sweep. Combines two independent small sweeps into one commit. + +**Two facades added:** +- `resourceRangesStatus(resource: EngineMediaResource) -> Signal, NoError>` wraps the single `(MediaResource) -> Signal, NoError>` overload. Takes `EngineMediaResource` (not `id:`) because Postbox's overload only accepts a resource, not an id — consumers pass `.resource` already. Facade unwraps via `_asResource()`. +- `removeCachedResources(ids: [EngineMediaResource.Id], force: Bool = false, notify: Bool = false) -> Signal` wraps the `([MediaResourceId], force:, notify:) -> Signal` overload. Maps ids internally. + +**`import RangeSet` added to `TelegramEngineResources.swift`.** The `RangeSet` return type caused a name collision with Swift stdlib's `RangeSet` (iOS 18+ only) until the local `RangeSet` module is imported. `TelegramCore/BUILD` already declared the dep at line 23 (`//submodules/Utils/RangeSet:RangeSet`), so no BUILD change needed. + +**4 Shape-A consumer sites migrated (3 files):** +- `PhotoResources/Sources/PhotoResources.swift` (1) +- `TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift` (1) +- `ChatListUI/Sources/ChatListSearchContainerNode.swift` (2) + +For `ChatListSearchContainerNode.swift:1398`, the caller uses a `Set` local — wave leaves the local as-is and maps at the call site via `resourceIds.map { EngineMediaResource.Id($0) }`. Migrating the local to `Set` is out of scope (module keeps `import Postbox` for unrelated reasons). + +**Build validation.** Clean build (563 processes, 265s, 0 errors) on the second attempt after adding `import RangeSet`. + +**Lesson — Swift-stdlib-vs-third-party-module name collisions.** When a facade signature references a type name that exists both in Swift stdlib (potentially availability-restricted) and in a third-party module, the compiler picks the stdlib one by default. Fix: import the third-party module explicitly. In this codebase, `RangeSet` is provided by `submodules/Utils/RangeSet:RangeSet`, and TelegramCore already depends on it. Use `import RangeSet` at the file top. + +Net: 3 consumer files + 1 TelegramCore file + CLAUDE.md. TelegramEngineResources.swift: +9 / -0 (including `import RangeSet`). + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 31 outcome (2026-04-23) + +Second build-verified `^import Postbox$` sweep on consumer modules since wave 6 (2026-04-19). Same methodology: speculative-drop + `--continueOnError` build loop with pattern-based preemptive restores. + +**Candidate set narrowing.** Initial candidate grep `grep -rl "^import Postbox$" submodules --include="*.swift"` returned **1184** files. 606 of those live in `submodules/TelegramCore/Sources/` — TelegramCore legitimately `import Postbox`; the TelegramCore files were accidentally included and had to be reverted via `git checkout -- submodules/TelegramCore/Sources/` before re-seeding the drop. Final consumer candidate set: **578** files. **Lesson for future sweep invocations: the candidate-set grep must filter out `submodules/TelegramCore/` as well as `submodules/Postbox/` / `submodules/TelegramApi/`.** Wave 6's methodology note at step 1 (line 37) already calls this out, but the TelegramCore carve-out is easy to miss because TelegramCore doesn't `@_exported import Postbox`, so from a pure re-exports perspective it's indistinguishable from a consumer. + +**9 build iterations to convergence** (plus 1 aborted first iteration for the TelegramCore scope error). Per-iteration failure counts: 18 → 2 → 9 → 12 → 1 → 1 → 3 → 1 → 4 → 0. Surfacing pattern was typical of a speculative-drop sweep: errors bubble one dependency-graph layer at a time. + +**Per-iteration symbol expansion.** The wave-6 preemptive-restore symbol list (CLAUDE.md's "Unused-import sweeps" guidance) needed extensions for this sweep: +- Iter 3 surfaced `CodableEntry`, `CachedMediaResourceRepresentation`, `CachedMediaRepresentationKeepDuration`. +- Iter 4 surfaced `PostboxViewKey`, `OrderedItemListView`, `UnreadMessageCountsItem`, `ChatListEntrySummaryComponents`, `PeerStoryStats`, `ItemCollectionId` (note: typealias `EngineItemCollectionId` exists but raw name still requires `import Postbox`), and broadened `\bMedia\b`, `\bMessage\b`, `\bPeer\b`. +- Iter 5 surfaced `FetchResourceSourceType` (same typealias caveat). +- Iter 6 surfaced `StoryId`. +- Iter 7 surfaced `ChatListIndex`. +- Iter 8 surfaced `PreferencesEntry` (typealias caveat), `PeerView`, `RenderedPeer`. +- Iter 9 surfaced `declareEncodable`. +- Iter 10 surfaced `ItemCollectionItemIndex`, `ValueBoxEncryptionParameters`, `fileSize`, plus a restore-script bug (see below). + +**Restore-script bug: `#if canImport(...)` blocks.** The naive restore inserter picks the last `^import ` line and appends `import Postbox` after it. If the last import sits inside an `#if canImport(AppCenter) ... #endif` preprocessor block, the restored `import Postbox` lands inside that block and is only active under that configuration. `AppDelegate.swift` in `submodules/TelegramUI/Sources/` hit this (original had `import Postbox` at line 7; drop + restore put it inside the `#if canImport(AppCenter)` block at line 51); the build failed in iter10 on `cannot find type 'Postbox' in scope` errors even though a literal `grep ^import Postbox$` matched. Fixed by manually moving the import out of the `#if` block. **Lesson for future restore-script work: insert the restored `import Postbox` BEFORE the first `#if` or `#endif` line, not after the last `import` line, to avoid preprocessor-scope traps.** + +**Results: 9 source-level surviving drops + 2 duplicate-import dedups.** Final diff: 11 files changed, +2 / -13. + +Surviving drops: +- `submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift` +- `submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift` +- `submodules/DebugSettingsUI/Sources/DebugAccountsController.swift` +- `submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift` +- `submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift` +- `submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift` +- `submodules/TelegramUI/Sources/ChatLinkPreview.swift` +- `submodules/TelegramUI/Sources/ChatSearchResultsController.swift` +- `submodules/TelegramUI/Sources/MediaManager.swift` + +Duplicate-import dedups (files had two `^import Postbox$` lines; kept exactly one — unrelated-but-latent cleanup surfaced incidentally by the sweep): +- `submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift` (2 imports → 1) +- `submodules/TelegramUI/Sources/ChatHistoryListNode.swift` (2 imports → 1) + +**Spurious-diff cleanup step (new procedure, adopted this wave).** After convergence, `git diff --numstat` showed 564 modified files but only 9 were genuine drops. The other 553 were "1 addition + 1 deletion" — files where the original `import Postbox` at line X was deleted by the drop and re-inserted at line Y by the restore (different position because restore inserts after "last import line" regardless of original placement). These aren't semantic changes but do produce noisy diffs. Identified via `git diff --numstat | awk '$1 == 1 && $2 == 1 {print $3}'` and reverted via `xargs -I{} git checkout -- {}`. **Lesson: the wave-6 methodology should add a post-convergence "revert 1-add-1-del spurious diffs" step before committing. Alternative: improve the restore script to insert at the exact original line. Either way, the final diff should be limited to real semantic changes.** + +**No modules became fully Postbox-free this wave.** Each of the five containing modules still has other files importing Postbox (TelegramUI: 350 remaining, LegacyDataImport: 4, MediaPlayer: 9, AuthorizationUI: 2, DebugSettingsUI: 1). By this point most trivially-droppable imports have been drained; the remaining Postbox-importing files mostly carry real usage. **Re-run cadence lesson: yield per re-run is declining.** Wave 6 yielded 183 drops + 189 modules freed; wave 31 yielded 9 drops + 0 modules freed. Consider spacing future sweeps to every 4–6 facade waves rather than 2–3. + +**Wave 14 BUILD-dep sweep companion: 0 drops.** Ran the wave-14-style `find submodules -name BUILD | filter-by-no-source-import` check: **0 BUILD candidates**. The 191 BUILDs still listing `//submodules/Postbox` all have at least one Sources/*.swift that actually imports Postbox. One outlier (`submodules/SpotlightSupport/BUILD`) has zero source files but a non-trivial `deps = [...]` list including `//submodules/Postbox`; deliberately left alone (stale-BUILD-on-empty-module is a different class of cleanup and carries unknown side effects). + +Net: 11 files changed (9 + 2), +2 / -13 lines. Clean first-attempt verification build without `--continueOnError` (880 actions, 1354 action cache hits, 262s). + +Plan / record: (no plan doc this wave — mechanical sweep). + +## Wave 32 outcome (2026-04-24) + +`resourceStatus` residue sweep. One new facade overload (`status(id:resourceSize:)`) + 4 migrated sites across 2 consumer files. Commit `289fc908bc`. + +**Facade added** in `TelegramEngineResources.swift`: +- `status(id: EngineMediaResource.Id, resourceSize: Int64) -> Signal` wraps Postbox's `resourceStatus(MediaResourceId, resourceSize:)` overload. Body mirrors the existing `status(resource:)` facade, converting id via `MediaResourceId(id.stringRepresentation)` and mapping the result via `EngineMediaResource.FetchStatus.init`. + +**4 migrated sites (2 files):** +- `ChatListSearchContainerNode.swift:1059` — new `status(id:resourceSize:)` overload. Caller supplies `EngineMediaResource.Id(downloadResource.id)` directly (String initializer; `downloadResource.id: String`) — no raw `MediaResourceId(...)` wrap needed. Mirrors the pre-existing `EngineMediaResource.Id(downloadResource.id)` usage at line 1107. +- `ChatMessageInteractiveMediaNode.swift:1769` — existing `status(resource:)` facade (wave 3). +- `ChatMessageInteractiveMediaNode.swift:1799` — same. +- `ChatMessageInteractiveMediaNode.swift:1809` — existing `resourceRangesStatus(resource:)` facade (wave 26). + +**Local preserved deliberately.** `let postbox = context.account.postbox` at `ChatMessageInteractiveMediaNode.swift:1767` stays because line 1793 feeds `postbox` to `HLSVideoContent.minimizedHLSQualityPreloadData(postbox: Postbox, ...)` — that is a third-party-function boundary needing raw `Postbox`. Only the `resourceStatus`/`resourceRangesStatus` call sites within that scope migrate. + +**Case-pattern sharing.** `MediaResourceStatus` (raw Postbox) and `EngineMediaResource.FetchStatus` (engine wrapper) have identical case names (`.Fetching`, `.Paused`, `.Local`, `.Remote`). The inner `switch status` at 1770-1779 keeps its `MediaResourceStatus` return type annotation — input case matching works for the engine type, constructed `MediaResourceStatus` return values still compile (`MediaResourceStatus` is in scope via `import Postbox` on line 4). This is the wave-29/30 lesson in action: no enum-case edits required. + +**Inventory scope narrowing from memory's prediction.** The memory's `wave 32+ candidates` section predicted ~12 Shape-B/C sites in the residue sweep. Execution-time re-grep reclassified most of them: +- **Coupled to `accountManager.mediaBox.resourceStatus` siblings (6 sites in 3 files):** `ThemePreviewControllerNode:271+277`, `WallpaperGalleryItem:799+805+834+840`, `SettingsThemeWallpaperNode:284+285`. Each pair has an `accountManager`-sourced fallback whose return type is raw `Signal`. Migrating only the `account.postbox` branch breaks the shared sibling type at the `mapToSignal`/`combineLatest` merge point. Deferred until accountManager-side has an engine facade. +- **Shape-C init-param refactor (3 sites in 3 files):** `LegacyWebSearchGallery:248` (free function `legacyWebSearchItem(account: Account, ...)`), `NativeVideoContent:455` (init takes `postbox: Postbox`), `VerticalListContextResultsChatInputPanelItem:229` (item stores `account: Account`). Each needs an init-param change + caller threading — per-module mini-refactor, not wave-shape-G territory. +- **`approximateSynchronousValue` overload:** only call site (`SettingsThemeWallpaperNode:284`) is in the accountManager-coupled bucket above. Adding the facade now would land dead code. + +Effective wave scope: 4 sites (the uncoupled subset). Still worth committing as its own wave — closes the `resourceStatus` arc for every site where migration is currently unblocked. + +**Build validation.** Clean build (558 processes, 236s, 0 errors). No `--continueOnError` needed — first attempt green. + +**Lesson — siblings-define-scope in resource-status migrations.** When an assignment uses `A.resourceStatus(...)` in one branch and `B.resourceStatus(...)` in another (via `if`/`mapToSignal`/`combineLatest`), the branches' return types must match. If `A` has an engine facade but `B` does not (e.g., `accountManager.mediaBox` has no engine wrapper yet), neither branch is migratable in isolation — the whole group must wait. Pre-flight sibling-check for each `resourceStatus` hit: is the enclosing `statusSignal = ...` expression a single source or a multi-source merge? + +**Lesson — Shape-B/C classification requires read, not grep.** The memory's wave-32 candidate table classified sites by single-line grep ("`account.postbox.mediaBox.resourceStatus`"). That pattern matches both the fully-migratable `context.account.postbox.mediaBox.X` form (Shape-A via AccountContext) AND the `(local) account.postbox.mediaBox.X` Shape-C form (requires init-param refactor). Distinguishing requires reading 5-10 lines of context to find the `account` binding: field? local? init param? closure capture? Add this as a mandatory step in the per-site inventory for future residue waves. + +Plan / record: (no plan doc this wave — small residue sweep). + +--- + +## Wave 33 outcome (2026-04-24) + +`loadedPeerWithId` consumer sweep. 60 sites migrated across 37 consumer files. No new facades, no typealiases. Commit `16d017853a`. + +**Migration pattern** (per user's explicit direction): + +```swift +context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) +|> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } +} +``` + +This replaces `context.account.postbox.loadedPeerWithId(peerId)` while preserving signature shape. The `mapToSignal` wrapper is critical: Postbox's `loadedPeerWithId` returns `.never()` (signal never emits) when the peer is missing — it does NOT wait for loading. The engine-data equivalent `get(Peer.Peer(id:))` returns `Signal` (optional snapshot). Unwrapping with `.never()`-on-nil preserves original semantics exactly, while keeping the outer shape `Signal` non-optional so callers' closures don't have to cascade new optional handling. + +**Category distribution (per pre-flight Explore catalog, 60 sites):** + +| Category | Count | Body change | +|---|---|---| +| Cat-A (trivial) | 22 | Only EnginePeer-compatible members; type swap only. | +| Cat-B (concrete-type cast) | 25 | `peer as? TelegramUser/Group/Channel/SecretChat` → `if case let .user(user)` (etc.). | +| Cat-C (feeds Peer-typed API) | 13 | `peer._asPeer()` at call point (`makePeerInfoController`, `makeChatRecentActionsController`, `makeChatQrCodeScreen`, `FoundPeer.init`, `SendAsPeer.init`). | + +(Cat-B + Cat-C bumped slightly from Explore's catalog after in-edit reclassifications.) + +**Engine-access variations:** +- Most consumer modules use `context.engine.data.get(...)` on `AccountContext`. +- `ShareSearchContainerNode.swift` uses `context.engineData.get(...)` because `ShareControllerAccountContext` exposes `engineData: TelegramEngine.EngineData` but not a full `engine`. +- `CallStatusBarNode.swift` (has raw `account: Account` from switch case) constructs `TelegramEngine(account: account)` inline. +- `PresentationGroupCall.swift` uses `self.accountContext.engine.data` instead of the stored `self.account.postbox`. + +**TelegramCore internal sites (36) unchanged.** `Postbox.swift` (2 defs), `State/AccountViewTracker.swift`, `State/FetchChatList.swift`, `State/SynchronizePeerReadState.swift`, `Suggestions.swift`, and all `TelegramCore/Sources/TelegramEngine/` internal `_internal_*` helpers still call `postbox.loadedPeerWithId(...)` — they are the Postbox-facing layer. + +**Pre-flight efficiency.** An Explore subagent cataloged all 60 sites by category from a single prompt (one-line-per-site output). That catalog made the sweep straightforward: most files fell into identical patterns, enabling template-substitution Edits. Total context spent on discovery was small compared to doing 60 per-site full reads in the main thread. + +**Build validation.** First-pass clean build (47 actions, 70s) after sweep completion. Earlier pilot (2 sites, 20s) validated pattern before scaling to all 60. + +**Lessons:** + +- **`loadedPeerWithId` returns `.never()` on missing peer, not a pending Signal.** Old common misreading: treating it as a "wait-until-loaded" primitive. Actual Postbox source at `Postbox.swift:3925`: `if let peer = self.peerTable.get(id) { return .single(peer) } else { return .never() }`. Preserve this by wrapping `engine.data.get` in `mapToSignal` with the `.never()` fallback — don't replace with plain `|> compactMap { $0 }` (which would drop the signal entirely rather than completing immediately when peer exists). + +- **"Keep the signatures to help the typechecker" as a migration principle.** The user (2026-04-24) explicitly directed: keep call-site outer Signal signatures stable (`Signal` non-optional), even at the cost of a 6-line inline `mapToSignal` wrapper at each site. Rationale: 60 sites × optional-cascade body changes > 60 × 6-line wrapper. This is a general principle for sweeps — if the alternative is rewriting every body to handle optionals, prefer the signal-level wrapper to contain the change. + +- **Pre-flight cataloging via Explore subagent.** For sweeps with variable per-site body shapes (unlike facade-migration-with-identical-call-expression sweeps), a dispatch to `Explore` with a category-classification prompt collapses inventory cost. Explore's output is small (~60 one-line entries); avoids pulling 60 file fragments into the main thread's context. Required for wave shapes where inventory is non-uniform. + +- **Shape-C peer-fed-to-API pattern needs `_asPeer()` at call, not facade.** Because `makePeerInfoController(peer: Peer)` / `FoundPeer(peer: Peer, ...)` / `SendAsPeer(peer: Peer, ...)` / `makeChatQrCodeScreen(peer: Peer, ...)` all stay on raw `Peer` (they're AccountContext-protocol or TelegramCore struct-init APIs whose migration is its own multi-wave effort), the bridge is a single `._asPeer()` at the call. Don't try to also migrate those APIs in the sweep — blast radius too large. + +- **Engine-access varies by containing context.** Plain `context.engine.data` works for ~85% of sites; the remainder need `TelegramEngine(account: account)` construction or `engineData` protocol property. Build a per-site `context` type check into pre-flight for call-site categories where `AccountContext` isn't guaranteed. + +Plan / record: no plan doc this wave — user specified the migration pattern directly; the Explore catalog + commit message captured decisions. + +--- + +## Wave 34 outcome (2026-04-24) + +`FoundPeer.peer: Peer → EnginePeer`. Public field-type migration on the struct in `submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift`. Atomic 12-file commit `fdd5b93998`. ~135 insertions / ~134 deletions. + +**Migration shape.** The field-type change is necessarily atomic (half-migrated FoundPeer doesn't compile across consumers), so all edits land in one commit. `_internal_searchPeers` keeps `import Postbox` (still calls `postbox.transaction` etc.) and wraps raw peer values with `EnginePeer(peer)` at the FoundPeer constructor sites. `==` body changes from `lhs.peer.isEqual(rhs.peer)` to `lhs.peer == rhs.peer`. + +**Final scope (vs planned ~70 semantic edits → actual ~135 line insertions):** +- 5 `._asPeer()` bridge-drops at FoundPeer constructor sites (e.g., `FoundPeer(peer: peer._asPeer(), ...)` → `FoundPeer(peer: peer, ...)`) +- 22+ redundant `EnginePeer(peer.peer)` wrap drops (the field is now EnginePeer; `EnginePeer.init(_ peer: Peer)` doesn't accept an EnginePeer argument so the wrap fails to compile) +- 30+ Postbox-concrete-type downcasts (`peer.peer as? TelegramX` / `is TelegramX`) rewritten to `if case let .X(x) = peer.peer` enum-pattern form +- ~10 `._asPeer()` outflow bridges added where `peer.peer` flows into APIs that still take raw `Peer`: `ContactListPeer.peer(peer:)`, `canSendMessagesToPeer(_:)`, `EngineRenderedPeer(peer:)` legacy paths + +**Inventory undercounting — pattern.** Original Explore inventory pass missed 4 of 12 final consumer files. The grep `grep -rln "FoundPeer\b"` only catches files that name `FoundPeer` as a literal type. Files that USE `peer.peer` access on FoundPeer values without naming the type itself were invisible to that grep. The build verification pass surfaced them: + +| File | Surfaced by | Edits needed | +|---|---|---| +| `TelegramCore/Calls/GroupCalls.swift` | iter 1 | 2 internal FoundPeer constructors needed `EnginePeer(peer)` wraps | +| `ShareController/ShareSearchContainerNode.swift` | iter 2 | 4 errors: 2 C2 downcasts + 2 outflow-bridge needs | +| `ContactListUI/ContactsSearchContainerNode.swift` | iter 3 | 7 errors: nested `if !(peer is X)` rewrite + multiple downcasts/outflows | +| `PeerInfoUI/ChannelMembersSearchContainerNode.swift` | iter 4 | 6 errors across 2 near-identical loop blocks | +| `ChatListUI/ChatListSearchListPaneNode.swift` (extra site) | iter 5 | 1 missed C2 site at line 3723 (in `.globalPeer(foundPeer, …)` enum case body, far from the other ChatListUI edits) | + +5 build iterations total before clean (each iteration: edit → re-build, ~50–60s incremental). First-pass would have needed a much wider pre-flight grep — see lessons. + +**Lessons:** + +- **Inventory grep must include the access pattern, not just the type name.** For a field-type migration, ALL of: + - `(peer:` constructors + - `.peer.` reads (verify `` type is ``, not RenderedPeer/SendAsPeer/etc.) + - `.peer as?` / `.peer is` downcasts + - `(.peer)` arg passes (where `` may take the old protocol) + + Use `for x in Y` binding-tracing to determine if `` is the migrated type. The wave-34 pre-flight ran the first three but not the fourth (outflow-arg sites), and partially missed the second (because the Explore agent classified by literal `FoundPeer` token rather than by `peer.peer` semantics in context). + +- **`if !(peer is A || peer is B)` rewrite uses `switch case A, B: break / default: ...`.** When the original Postbox code uses a negated disjunction of type-checks, the cleanest enum-pattern equivalent is a `switch` with combined cases in one arm — not nested `if case`s. (Used in ChatListSearchListPaneNode:1024 and ContactsSearchContainerNode:502/544.) + +- **Inner `peer` shadowing.** Many `else if let peer = peer.peer as? TelegramChannel` Postbox patterns shadow the loop variable. The enum-pattern rewrite renames the inner binding to `channel` to avoid double-shadowing the EnginePeer outer loop var. Block-internal references to `.info` etc. then move from `peer.info` to `channel.info`. + +- **Build iteration = inventory completion.** When the inventory undercounting becomes apparent (build surfaces 5+ unexpected sites), don't abandon — iterate. Each build is fast (~50s incremental) and each error is actionable (`error: cast from EnginePeer to unrelated type X always fails` → C2 rewrite; `argument type EnginePeer does not conform to expected type Peer` → outflow bridge). The inventory grows by file, fix-then-rebuild converges in 5 iterations even when ~30% of sites were missed up front. + +- **Bridge sites generated by this wave point to next-ring migration targets.** The ~10 `._asPeer()` outflow bridges land at `ContactListPeer.peer(peer:)`, `canSendMessagesToPeer(_:)`, and `EngineRenderedPeer(peer:)` (legacy raw-Peer constructor in some paths — e.g., `EngineRenderedPeer(peer: foundPeer.peer)` doesn't need a bridge in newer EnginePeer-aware paths but does where the local var was already raw-Peer-extracted). These three signatures are the obvious wave-35+ candidates for the next ring of migration. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-foundpeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-foundpeer-engine-peer-migration-design.md`. + +--- + +## Wave 35 outcome (2026-04-24) + +`SendAsPeer.peer: Peer → EnginePeer`. Public field-type migration on the struct in `submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift`. Atomic 7-file commit `583c8b1f7c`. 22 insertions / 26 deletions. + +**Migration shape.** Same atomic-field-type pattern as wave 34 but scoped to a smaller consumer surface. The `_internal_*SendAsAvailablePeers` functions keep `import Postbox` and wrap raw peer values with `EnginePeer(peer)` at the 4 SendAsPeer constructor sites. Manual `==` body dropped in favor of synthesized Equatable (`EnginePeer: Equatable`, `Int32?` and `Bool` already Equatable). + +**Final scope (vs planned ~15 semantic edits → actual 22/26 line diff):** +- 3 `._asPeer()` bridge-drops at SendAsPeer constructor sites (ChatControllerLoadDisplayNode:772, ChatTextInputPanelComponent:848, StoryItemSetContainerViewSendMessage:249) +- 7 redundant `EnginePeer(peer.peer)` / `EnginePeer($0.peer)` / `EnginePeer(value.peer)` wrap drops across ChatSendAsPeerListContextItem (4 sites), ChatTextInputPanelNode (1), StoryItemSetContainerViewSendMessage (1), StoryItemSetContainerComponent (1) +- 1 `peer.peer as? TelegramChannel` downcast rewritten to `if case let .channel(channel) = peer.peer` (ChatSendAsPeerListContextItem:73) with `peer.info → channel.info` rename in the shadowed scope +- 2 `EnginePeer(channel)` wraps added where raw `TelegramChannel` is constructed into `SendAsPeer(peer: ...)` (ChatControllerLoadDisplayNode:805, 823) +- 1 signal-chain simplification: `(sendAsPeer?.peer).flatMap(EnginePeer.init)` → `sendAsPeer?.peer` at StoryItemSetContainerViewSendMessage:4080 +- 1 signal-chain simplification: `.map({ EnginePeer($0.peer) })` → `.map({ $0.peer })` at StoryItemSetContainerViewSendMessage:4081 + +**Inventory undercount = 1 site (vs wave 34's 5).** The pre-flight Explore catalog missed `StoryItemSetContainerComponent.swift:3069` (`currentPeer: EnginePeer(value.peer)` → `value.peer`). The implementer caught it during the edit phase before the build, so no iteration was needed. The wave-34 explicit pattern grep (including `.peer as?`/`is`/outflow-args/`EnginePeer(.peer)`/`._asPeer()`) dramatically reduced undercounting — 1/7 sites missed (~14%) vs wave 34's 4/12 (~33%). + +**First-pass clean build.** No errors surfaced by the Bazel build at all. 461 total actions, 196.583s elapsed, `INFO: Build completed successfully`. Contrast with wave 34's 5 build-iterations-to-converge. + +**Lessons:** + +- **Wave 34's explicit-pattern pre-flight inventory works.** For future Peer-typed-API waves, the minimum grep pattern set is: `\b` literal token, `\.\s+(as\?|is)\s+Telegram`, `EnginePeer\(\w+\.\)`, `\(\.` for known outflow APIs, and `\._asPeer\(\)` (to catch bridge-drop opportunities). Wave 35 used this full pattern set and hit ~14% undercount vs wave 34's ~33%. + +- **Smaller target + validated pattern = faster wave.** Wave 35 went from spec-commit (`72d4384af0`) to outcome-commit in a single session with one clean build, versus wave 34's multi-iteration convergence. When the wave is a replay of a just-validated pattern on a smaller surface, expect minimal iteration. + +- **Inner-peer shadowing rename works.** The wave-34 lesson about renaming `peer` → `channel` in `if case let .channel(channel) = peer.peer` applied cleanly. Single instance this wave (ChatSendAsPeerListContextItem:73) — no issues. + +- **Name collisions remain a scope hazard.** Pre-flight identified `sendAsPeers: [EnginePeer]` (LiveStreamSettingsScreen, ShareWithPeersScreen) and `availableSendAsPeers: [EnginePeer]` (ChatSendStarsScreen) as name-only collisions — different type, same identifier. Confirmed these stayed untouched and out of scope. Future Peer-typed-API waves should continue the name-collision disambiguation pass. + +- **Bridge sites generated by this wave — zero new outflow bridges.** Unlike wave 34 (which added ~10 `._asPeer()` outflow bridges pointing to `ContactListPeer.peer(peer:)` / `canSendMessagesToPeer(_:)` / `EngineRenderedPeer(peer:)` as next-ring targets), wave 35 added no outflow bridges. All consumer-side `.peer` flows either stayed as `.peer.id` accesses (PeerId unchanged) or were simplifications of existing `EnginePeer(.peer)` wraps. Net: no new next-ring targets surfaced from wave 35. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-sendaspeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-sendaspeer-engine-peer-migration-design.md`. + +--- + +## Wave 36 outcome (2026-04-24) + +`ContactListPeer.peer(peer: Peer, isGlobal:, participantCount:) → peer: EnginePeer`. Enum-case payload migration on the public type in `submodules/AccountContext/Sources/ContactSelectionController.swift`. Atomic 15-file commit `069a060de1`. 57 insertions / 59 deletions. + +**Migration shape.** Same atomic-payload-type pattern as wave 34/35 but wider: 15 consumer files vs wave 35's 7, vs wave 34's 12. Beyond the payload change, the cascading `ContactListPeer.indexName` return type changed from `PeerIndexNameRepresentation` to `EnginePeer.IndexName` — an unexpected discovery during plan-writing that dropped 2 additional `EnginePeer.IndexName(...)` wraps at ContactListNode:517. + +**Final scope (vs planned 8 files / ~41 semantic edits → actual 15 files / 57/59 diff):** + +- **Definition (1 file):** `AccountContext/ContactSelectionController.swift` — case payload type, indexName return type, `==` operator body (`lhsPeer.isEqual(rhsPeer)` → `lhsPeer == rhsPeer`). +- **20 `._asPeer()` outflow bridge-drops** across ContactListNode (12), ContactsSearchContainerNode (3), ContactMultiselectionController (2), ContactMultiselectionControllerNode (1), ContactSelectionControllerNode (2). `replace_all=true` on `._asPeer(), isGlobal:` was the unifying substring. +- **20+ `EnginePeer(peer)` inflow wrap-drops** at destructure sites across ContactListNode (4), ContactsController (1), ContactsSearchContainerNode (4), ContactMultiselectionController (4), ContactMultiselectionControllerNode (1), ContactSelectionController (2), PeerSelectionControllerNode (3), SharedAccountContext (2). +- **2 `EnginePeer.IndexName(...)` wrap-drops** at the sort-comparator at ContactListNode:517 (enabled by the cascading return-type change). +- **8 Postbox-concrete cast rewrites** to EnginePeer case patterns across ContactListNode:182-186/1968 (4 sites, including the 3-branch user/group/channel cast-chain), CallController:524/542 (the intermediate `let peer = EnginePeer(peer)` lines became redundant after migration), StoryItemSetContainerViewSendMessage:2041/2074, DeviceContactInfoController:1419, ChatSendAudioMessageContextPreview:89, ChatControllerOpenAttachmentMenu:557/610/1746/1788 (4 identical sites, `replace_all` on the full line). +- **2 `._asPeer()` outflow bridges ADDED** at ContactMultiselectionController:386/403 where the destructured peer flows into `peerTokenTitle(peer: Peer)` (out-of-scope callee; future-wave bridge target). + +**Inventory undercount = 7 files / ~20 sites (vs wave 35's 1 site).** Much higher miss rate than wave 35 — ~46% by file count. Root cause: the pre-flight grep for ContactListPeer destructures used literal `\(peer, _, _\)` binding; binding names varied in practice (`contact`, `lhsPeer`, `rhsPeer`, `contactPeer`, `id`). Files missed: + +1. `DeviceContactInfoController.swift:1418/1419` — `case let .peer(contact, _, _)` + `contact as? TelegramUser` +2. `CallController.swift:523/541` — `case let .peer(peer, _, _)` + redundant `let peer = EnginePeer(peer)` pattern +3. `ChatSendAudioMessageContextPreview.swift:88/89` — `case let .peer(contact, _, _)` + `contact as? TelegramUser` +4. `PeerSelectionControllerNode.swift:901-903/1590-1592` — 2 destructures with `EnginePeer(peer)` inflow wraps +5. `StoryItemSetContainerViewSendMessage.swift:2040-2041/2073-2074` — 2 `contact as? TelegramUser` casts +6. `ChatControllerOpenAttachmentMenu.swift:556-1787` — 4 `contact as? TelegramUser` casts +7. `SharedAccountContext.swift:3295-3302` — `case let .peer(peer, _, _)` + 2 `EnginePeer(peer)` inflow wraps + +**Six build iterations to converge** vs wave 35's single first-pass-clean. Iterations 1-6 surfaced errors in batches of 2-8 errors; each was a mechanical fix (drop wrap, rewrite cast, add `._asPeer()` bridge for outflow to out-of-scope `peerTokenTitle`). Final iteration (#6) clean. + +**Lessons:** + +- **Pre-flight grep must use `\(\w+, _, _\)` not `\(peer, _, _\)` for enum-payload destructures.** Swift destructure patterns bind the payload to any legal identifier; the variable name is not semantic. Future Peer-typed-enum-payload waves should use `case let \.\((\w+),` (or similar wildcard binding) and then per-destructure scan the next ~15 lines for ` as\?`/` is`/`EnginePeer\(\)` / outflow-arg patterns. + +- **"No-edit consumer" claims need stricter verification.** Wave 36's "verify-only" list included ChatSendAudioMessageContextPreview because the initial inventory found only `[ContactListPeer]` at collection level. The deeper scan missed a `case let .peer(contact, _, _)` + `contact as? TelegramUser` pattern inside the file's `update(...)` method. For future waves, "no-edit" claims should run the wildcard-binding destructure grep described above, not just a construction-site grep. + +- **Outflow-to-out-of-scope-API bridges may need addition during the wave.** ContactMultiselectionController:386/403 needed `._asPeer()` bridges added where none existed pre-migration — the pre-migration code passed raw `Peer` to `peerTokenTitle(peer: Peer)` because the destructured peer was raw Peer. Post-migration, the destructured peer is EnginePeer, so a bridge is required. Future waves with same-scope outflow to not-yet-migrated Peer-typed APIs should pre-flight expect to add bridges. + +- **Cascading computed-property return type migration** (here: `ContactListPeer.indexName` from `PeerIndexNameRepresentation` to `EnginePeer.IndexName`) is a legitimate scope expansion when the enum's properties leak Postbox-typed values. Wave 36 caught this during plan-writing, not execution — a successful plan-review win. Future waves should grep the enum's definition file for computed properties returning Postbox-defined types. + +- **Build-iteration convergence is acceptable** when the wave's surface is large and pre-flight undercount is non-trivial. The cost of 6 build iterations (~5-20 minutes each in the Telegram-iOS build) is real but manageable. The alternative — exhaustive pre-flight to achieve first-pass-clean — is more expensive in plan-writing tokens and controller wall time. For waves expected to have >5 file touches, plan should explicitly budget for 3-5 build iterations. + +- **Ratchet effect confirmed.** Wave 36 was predominantly bridge-removal (20 outflow + 20 inflow + 2 IndexName) with only 2 bridge additions. Matches the expected ratchet behavior: earlier waves 33/34/35 added bridges at Peer/EnginePeer boundaries precisely so wave 36 could drop them atomically. The 2 new bridges added (ContactMultiselectionController:386/403 → peerTokenTitle) become next-wave drop candidates once `peerTokenTitle(peer: Peer)` migrates. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-contactlistpeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-contactlistpeer-engine-peer-migration-design.md`. + +--- + +## Wave 37 outcome (2026-04-24) + +`peerTokenTitle(peer: Peer → EnginePeer)`. Private free function in `submodules/TelegramUI/Sources/ContactMultiselectionController.swift`. Atomic single-file commit `734ab44dd2`. 7 insertions / 7 deletions. + +**Migration shape.** Ring-2 cleanup of bridges wave 36 installed. The function body was already round-tripping: callers unwrapped `EnginePeer → Peer` with `._asPeer()` only for the body to re-wrap with `EnginePeer(peer).displayTitle(...)`. Flipping the parameter to `EnginePeer` drops both the 5 call-site bridges and the 1 body-side wrap. Zero semantic change — verified by code-quality reviewer: `displayTitle(strings:displayOrder:)` is defined only on `EnginePeer` (LocalizedPeerData/PeerTitle.swift:29), and `EnginePeer.Id` is a typealias for `PeerId` so `peer.id.isReplies` and `peer.id == accountPeerId` resolve identically. + +**Final scope:** +- 1 signature change (L21): `peer: Peer` → `peer: EnginePeer` +- 1 body simplification (L27): `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)` +- 5 `._asPeer()` bridge-drops at call sites (L171, L201, L386, L403, L748) + +**Pre-flight inventory was exact.** Zero undercount: the pre-flight grep enumerated exactly 6 call-site matches (1 definition + 5 bridges), and the implementer's 3 Edit operations (one unique + two `replace_all=true`) hit all 5 bridge sites on first pass. No scope creep, no out-of-scope edits. + +**First-pass-clean build.** 946 total actions, 259.250s elapsed, `INFO: Build completed successfully`, 0 errors. As a reset wave after wave 36's 6-iteration convergence, this was the expected outcome for a single-file mechanical change targeting a private function. + +**Subagent-driven execution.** First wave executed via `superpowers:subagent-driven-development` with a consolidated implementer dispatch (Tasks 1–3 bundled since all three were grep + mechanical Edit on one file). Two-stage review (spec + code quality) passed cleanly. Controller directly handled build, commit, and log/memory updates. + +**Lessons:** + +- **Bundling mechanical plan-tasks is valid for same-file micro-waves.** When a plan's tasks are all Edit operations on a single file with no inter-task branches, consolidating into one implementer dispatch preserves the review structure (spec + quality still gated) while avoiding per-task subagent overhead. For larger waves with multi-file surface or branching logic, keep per-task dispatch. + +- **Private-function ring-2 cleanups are first-pass-clean candidates.** The ratchet behavior observed in waves 33–36 — prior waves add bridges at next-ring boundaries — creates trivially-closable cleanup waves whenever the next-ring callee is itself a private or small-surface function. Wave 37's target (private free function, 5 bridge sites, all in-file) hit the ceiling of what this shape can yield. + +- **Post-wave 36 bridge inventory reduced by 5.** ContactMultiselectionController:171/201/386/403/748 are now Peer-free at the `peerTokenTitle` arg; the file still retains `import Postbox` for other APIs (L459's `SelectedPeer.peer(peer: Peer, ...)` feed), so file-level Postbox-free remains a later-wave target. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-peertokentitle-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-peertokentitle-engine-peer-migration-design.md`. + +--- + +## Wave 38 outcome (2026-04-24) + +`canSendMessagesToPeer(_ peer: Peer → EnginePeer, ignoreDefault:)`. Public utility function in `submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift`. Atomic multi-file commit `45729bad1c`. 12 files changed, 25 insertions / 24 deletions. + +**Migration shape.** Mixed-direction ring-2 cleanup: 18 Shape-A bridge drops (`._asPeer()` at call sites where caller already holds `EnginePeer`) plus 5 Shape-C bridge adds (`EnginePeer(peer)` wraps where caller holds raw Postbox `Peer`). Function body preserved by inserting a single `let peer = peer._asPeer()` shadow as the first body line — the four `peer as? TelegramUser/TelegramGroup/TelegramSecretChat/TelegramChannel` branches remain unchanged and the file still `import Postbox`. + +**Final scope:** +- 1 signature change (CanSendMessagesToPeer.swift:9): `peer: Peer` → `peer: EnginePeer` +- 1 body insertion (CanSendMessagesToPeer.swift:10): `let peer = peer._asPeer()` +- 18 Shape-A drops across 7 files: ContactsSearchContainerNode (3), ChatImageGalleryItem (1), UniversalVideoGalleryItem (1), StoryItemSetContainerViewSendMessage (1), ShareSearchContainerNode (3), ChatListSearchListPaneNode (6), ChatListNode (3) +- 5 Shape-C adds across 5 files: LegacyAttachmentMenu, ChatInterfaceInputContexts, ShareSearchContainerNode, ShareController, ChatPresentationInterfaceState +- ShareSearchContainerNode has mixed shape (3 drops + 1 add) + +**Pre-flight inventory was exact.** 24 total grep matches = 1 definition + 23 call sites; 18 Shape-A + 5 Shape-C. Post-migration grep: 0 `_asPeer()` residue, 5 `EnginePeer(` wraps, 24 total matches. No scope creep. No WIP-drift adjustments needed. + +**First-pass-clean build.** 568 total actions, 233.471s elapsed, `INFO: Build completed successfully`, 0 compilation errors. This contradicts the memory's pre-wave prediction of 3–5 build iterations per wave-36 lesson — the lesson remains valid for waves with cascading type-inference surfaces, but wave 38's surface (a utility function called only at points that already bridge `EnginePeer ↔ Peer` in both directions) had no such cascades. The Shape-A/C classification captured the full surface before code changes began. + +**Subagent-driven execution.** Executed via `superpowers:subagent-driven-development`. Implementer dispatch bundled plan Tasks 1–5 (grep + all 24 edits + post-edit grep) since all were mechanical and on known files. Two-stage review (spec + code quality) passed without review-loop iterations. Controller handled build, commit, and log/memory updates. + +**Lessons:** + +- **Bundled multi-file dispatch is valid when pre-flight classification is exact.** Wave 36's per-task-dispatch rule (from CLAUDE.md) was formulated for waves with expected 3–5 build iterations driven by type-inference cascades. Wave 38 demonstrates that a multi-file wave with an exact upfront Shape-A/C classification and a cascading-free surface (all call sites are leaf arguments to a `Bool`-returning utility) can use the bundled dispatch shape safely. Gate: (a) all edits are mechanical Edit operations; (b) pre-flight grep enumerates every call-site explicitly; (c) return type of the migrated API does not propagate into caller type inference. When these hold, bundled dispatch saves subagent overhead without sacrificing review coverage. + +- **First-pass-clean multi-file waves exist.** The memory's pre-wave prediction of "not first-pass-clean territory" was conservative and came from wave 36's 6-iteration experience. Wave 38's 12-file mechanical migration built clean on first attempt because the spec's explicit Shape-A/C classification enumerated all 23 sites, and the function's `Bool` return prevents caller-side type-inference cascades. Future multi-file waves should update expectations when (a) the classification is exact and (b) the return type does not propagate. + +- **Shape-C wraps remain valid additions.** Five sites had to add `EnginePeer(peer)` at the call because the enclosing scope still holds raw Postbox `Peer` (from `RenderedPeer.peers` lookups, `ChatPresentationInterfaceState.renderedPeer.peer`, or `as? TelegramChannel` casts). These adds are acceptable — the alternative is a per-scope refactor (RenderedPeer → EngineRenderedPeer cascades) that would expand blast radius. Future waves targeting `RenderedPeer` would drop these 5 wraps. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-cansendmessagestopeer-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-cansendmessagestopeer-engine-peer-migration-design.md`. + +--- + +## Wave 39 outcome (2026-04-24) + +`AccountContext.makePeerInfoController(... peer: Peer → EnginePeer ...)`. Public protocol method on `AccountContext` (`submodules/AccountContext/Sources/AccountContext.swift:1371`) and its `SharedAccountContextImpl` implementation (`submodules/TelegramUI/Sources/SharedAccountContext.swift:1937`). Atomic multi-file commit `5385abc9bd`. **52 files changed**, 80 insertions / 79 deletions. + +**Migration shape.** Body-shadow ring-2 cleanup at the largest scale yet attempted. The protocol + impl signatures change to `peer: EnginePeer`; the impl body adds `let peer = peer._asPeer()` as its first statement, preserving the private downstream `peerInfoControllerImpl(peer: Peer, ...)` and all of its Peer-typed helpers as out-of-scope. Mixed-direction at consumer sites: 58 Shape-A drops + 3 Shape-A-variant guard-statement drops + 12 Shape-C `EnginePeer(...)` wraps. Net **-49 bridges**. + +**Final scope:** +- 1 protocol-signature change (AccountContext.swift:1371) +- 1 impl-signature change + 1 body-shadow insertion (SharedAccountContext.swift:1937–1938) +- 3 Shape-A self-call drops in same file (SharedAccountContext.swift:3335, 3483, 4016) +- 3 Shape-A-variant guard-statement drops (SettingsSearchableItems.swift around 1020/1046/1080): `guard let peer = peer?._asPeer() else { return }` → `guard let peer = peer else { return }`. The call-site `peer:` argument line is unchanged; the upstream guard now binds `peer` as `EnginePeer` (the closure parameter from `engine.data.get(...)`) rather than re-shadowing as raw `Peer`. +- 12 Shape-C `EnginePeer(...)` wraps across 8 files: BlockedPeersController:270, ChannelMembersController:707, ChannelBlacklistController:381, ChatRecentActionsControllerNode:1011, PeerInfoScreen:4306, ChatControllerNavigationButtonAction (4 sites: 441/461/471/492), ChatControllerOpenPeer (2 sites: 218/359), ChatControllerLoadDisplayNode:4362 +- 55 additional Shape-A drops across 42 consumer files +- 4 incidental trailing-whitespace strips picked up by Edit tool (functionally identical) + +**Pre-flight inventory was exact.** Total grep matches at planning time: 75 (1 protocol decl + 1 impl + 73 consumer call sites) of which 58 had inline `peer: ._asPeer()` (Shape-A), 3 had upstream-guard `peer?._asPeer()` patterns (Shape-A-variant), and 12 had raw `peer: ` (Shape-C). Post-migration grep for `_asPeer()` at `makePeerInfoController` call sites: 0. Post-migration grep for `EnginePeer(` at the 12 Shape-C sites: all present. No scope creep, no Postbox/TelegramCore/TelegramApi edits. The implementer noted that `BlockedPeersController.swift:270` already had `peer: peer` (no `_asPeer()`) — this was correctly classified Shape-C in the spec, and the wrap was applied as planned. + +**Property-typing pre-verification matters.** Spec phase identified that `RenderedPeer.peer`/`chatMainPeer` (Postbox extension at PeerUtils.swift:512 and Postbox/RenderedPeer.swift:38) return raw `Peer?`, while `EngineRenderedPeer.peer`/`chatMainPeer` (TelegramCore/Peers/Peer.swift:623/627) return `EnginePeer?`. Six Shape-C sites depend on `RenderedPeer` (Postbox), so `EnginePeer(...)` wraps were correct. Sites that consume `EngineRenderedPeer` would need `peer: peer` only — not a concern here, but a checklist item for next-wave Shape-C planning. + +**First-pass-clean build.** 658 total actions, 210.520s elapsed, 1565 cache hits + 142 disk-cache + 444 worker, `INFO: Build completed successfully`, 0 errors. Strongest confirmation yet of the wave-38 lesson: **first-pass-clean is achievable for 50+ file waves when (a) the migrated API's return type is non-propagating** (`ViewController?` is an optional reference type, like wave-38's `Bool` — caller-side type inference does not branch on it), **(b) pre-flight Shape-A/A-variant/C classification is exact, and (c) the body-shadow boundary is preserved.** Pre-wave estimate budgeted 2–4 iterations on the assumption that a 50-file change at a popular API surface would surface destructure cascades; the actual outcome exceeded expectations. + +**Subagent-driven execution.** Executed via `superpowers:subagent-driven-development`. Implementer dispatch bundled plan Tasks 1–5 (signature + body-shadow + 3 self-call drops + 3 guard rewrites + 12 wraps + 55 Shape-A drops, 70+ Edit operations across 52 files). Two-stage review (spec + code quality) passed without re-review iterations. Spec reviewer verified all 73 sites, the body-shadow placement at line 1938, the guard-statement form change in SettingsSearchableItems, and the unchanged-out-of-scope confirmation for `peerInfoControllerImpl` at line 4434. Code quality reviewer noted only the 4 incidental trailing-whitespace strips as "minor — leave as-is, tiny improvement". Controller handled build, commit, and log/memory updates. + +**Lessons:** + +- **Shape-A-variant: drop the upstream `_asPeer()` rather than wrapping at the call site.** When a guard-statement immediately upstream of a `makePeerInfoController` call unwraps `EnginePeer? → Peer?` via `peer?._asPeer()`, prefer rewriting the guard to `guard let peer = peer else` (keeping the local as `EnginePeer`) over adding `EnginePeer(peer)` at the call. The variant: zero net change at the call line, -1 `_asPeer()` upstream. This pattern occurs whenever a closure parameter from `engine.data.get(...)` is opt-unwrapped immediately before a Peer-typed-API call. + +- **First-pass-clean ceiling is higher than wave-36 implied.** Wave 36's 6-iteration convergence (15 files, ContactListPeer.peer enum-payload migration) led to a memory rule "multi-file = budget 3–5 iterations". Wave 38 first-pass-clean (12 files, Bool return) suggested the rule doesn't apply universally. Wave 39 first-pass-clean (52 files, ViewController? return) extends this: even at 50+ files, the determinant is return-type propagation behavior + pre-flight classification accuracy, not file count. Updated heuristic: **budget for first-pass-clean when** (return-type is non-propagating) AND (Shape classification is exact); **budget for 3–5 iterations when** (return type is a generic container, struct with associated types, or enum that participates in caller-side inference). + +- **Bundled implementer dispatch scales to 70+ edits across 52 files.** The wave-38 lesson ("bundled multi-file dispatch is valid when pre-flight classification is exact") held at this scale. Two-stage review (spec then code-quality) over the aggregate output preserved review coverage. No per-task subagent dispatches were needed. + +- **Atomic-stage exclusion of pre-existing WIP is necessary at large diff sizes.** Wave 39's working tree contained an unrelated WIP file (`ChatMessageTransitionNode.swift` — function-signature/animation changes on `beginAnimation`) that was modified outside the session. The commit phase explicitly enumerated all 52 wave-39 files in the `git add` invocation rather than using `git add -u` or `git add .`. This is a permanent rule for this project: never use bulk-stage commands; always enumerate files when committing a wave. + +- **Ratchet expansion: 12 new Shape-C wraps for future waves.** The 12 `EnginePeer(...)` wraps installed at this wave become drop candidates for later waves migrating their upstream sources: `RenderedPeer → EngineRenderedPeer` (would drop 6 wraps at sites consuming `renderedPeer.peer`/`chatMainPeer`), `RenderedChannelParticipant.peer → EnginePeer` (would drop 2 at ChannelMembers/ChannelBlacklist), and others. Net economics still strongly favor the wave: -61 + 12 = -49 bridges. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-makePeerInfoController-engine-peer-migration.md`. Spec: `docs/superpowers/specs/2026-04-24-makePeerInfoController-engine-peer-migration-design.md`. + +--- + +## Wave 40 outcome (2026-04-24) + +Commit: `d3c48379fe`. Bundle of `AccountContext.makeChatQrCodeScreen` + `makeChatRecentActionsController` peer `Peer → EnginePeer` — the trivial sibling follow-up to wave 39, completing the "Option 1 cluster" (`makePeerInfoController` family) from the wave-38 memory. 8 Swift files + plan doc / 11 edits. Pre-flight classification was already done in the wave-39 design doc's "Out of scope" section — no fresh pre-flight needed. + +**Classification:** +- 2 protocol decls (`AccountContext.swift:1401`, `:1461`) +- 2 impl decls + body-shadows (`SharedAccountContext.swift:2302`, `:2731`) +- 2 Shape-A-variant guard rewrites (`SettingsSearchableItems.swift:971`, `:989` — `guard let peer = peer?._asPeer()` → `guard let peer = peer`, keeping the local as `EnginePeer`) +- 3 Shape-A drops (`ContactsController.swift:478`, `ChannelAdminsController.swift:734`, `GroupStatsController.swift:915`) +- 2 Shape-C wraps (`PeerInfoScreen.swift:4623`, `PeerInfoScreenOpenChat.swift:115`) — both consume `data.peer: Peer?` from `PeerInfoScreenData`, so they're ratchet markers for a future `PeerInfoScreenData.peer Peer → EnginePeer` wave. + +Net −3 bridges (−5 `_asPeer()` drops, +2 `EnginePeer(...)` wraps). + +**Build outcome:** First-pass-clean for wave-40 files. The initial run failed due to pre-existing unrelated WIP in `ChatMessageTransitionNode.swift` (unterminated string literals in debug `print` statements the user was mid-editing); the wave-40 files produced zero diagnostics. After the user fixed the WIP, the subsequent build completed in 23.9s (mostly cache-warm) with zero errors. + +**Lessons:** + +- **Bundled sibling migration with shared pre-flight is cheap.** Wave 39's "Out of scope" section pre-classified all 7 of this wave's consumer sites. That planning overhead was already paid; wave 40 only needed to verify classifications still hold (one grep per site-group) and apply mechanical edits. Total time from plan-write to commit: ~30 minutes (dominated by the ~3-minute build). This validates a general pattern: when a wave defers siblings with an explicit "Out of scope" classification section, the follow-up wave is structurally trivial. + +- **Small-scale bundled implementer dispatch is still the right choice.** 11 edits across 8 files fits comfortably in one implementer call, matching the wave-38/39 lesson at smaller scale. No per-task dispatches were needed; two-stage review (spec then code-quality) took one round each. + +- **Pre-existing WIP in the working tree can cause module-scope build failures that mask the wave's own status.** When `ChatMessageTransitionNode.swift` had parse errors from an unrelated user WIP, the whole `TelegramUI` Swift module failed to parse before type-checking ran — so wave-40 files got syntax-verified but not type-verified. Diagnostic approach: grep the build output for error lines whose file path is NOT in the wave's file list; if the only errors are outside, the wave is likely clean, but complete type verification requires the unrelated WIP to be reverted or fixed. In this case the user fixed it; in future cases either ask the user, temporarily stash the WIP, or note the incomplete verification in the commit. + +**Plan / record:** `docs/superpowers/plans/2026-04-24-makeChatQrCodeScreen-recentActions-engine-peer-migration.md`. No separate spec — the pre-flight was reused from wave 39's spec ("Out of scope" section in `docs/superpowers/specs/2026-04-24-makePeerInfoController-engine-peer-migration-design.md`). + +--- + +## Wave 41 outcome (2026-04-24) + +Commit: `32573c9808`. `RenderedChannelParticipant.peer: Peer → EnginePeer` — a TelegramCore foundational-type field migration affecting 28 files (11 TelegramCore + 17 consumer) / 1124 insertions / 89 deletions. First foundational-type field migration since `FoundPeer.peer` / `SendAsPeer.peer` / `ContactListPeer.peer` (waves 34–36); differs in that `RenderedChannelParticipant` is a public TelegramCore struct with both TelegramCore-internal construction sites and a broad consumer surface, so the wave touched TelegramCore internals too, not just consumer modules. + +**Classification:** +- 1 struct field change (`ChannelParticipants.swift`): `peer: Peer → peer: EnginePeer` + init param + Equatable `lhs.peer.isEqual(rhs.peer) → lhs.peer == rhs.peer` +- 16 TelegramCore-internal constructor sites wrapped with `EnginePeer(peer)` across 9 files (RequestStartBot, AddPeerMember, ChannelAdminEventLogs [7 calls], ChannelBlacklist, ChannelMembers, ChannelOwnershipTransfer [2 calls], JoinChannel, PeerAdmins, Ranks) +- 1 TelegramCore-internal ADD-ASPEER (`SearchGroupMembers.swift:83` — pre-flight miss: `participants.map({ $0.peer })` consumed by outer `[Peer]`-returning closure) +- ~32 consumer DROP sites (removing `EnginePeer(participant.peer)` wraps or `._asPeer()` downgrades) +- 9 consumer CAST sites (`if let user = participant.peer as? TelegramUser, user.botInfo != nil` → `if case let .user(user) = participant.peer, user.botInfo != nil`) +- 3 consumer ADD-ASPEER (PeerInfoMembers:33 contains the `PeerInfoMember.peer: Peer` accessor ratchet; ChatRecentActionsHistoryTransition:675 + :2275 for `SimpleDictionary` subscript assignment) +- 7 consumer ADD-WRAP constructor sites (ChannelMembersSearchContainerNode + ChannelMembersSearchControllerNode legacy-group paths, ChatControllerAdminBanUsers:226) — ratchet markers for a future wave migrating `peerView.peers[id]` / `authors: [Peer]` upstream flows to EnginePeer +- 2 consumer `is TelegramChannel` → `if case .channel = participant.peer` rewrites (ChannelBlacklistController:370, :377) + +Net ~−13 bridges. Drops the 2 Shape-C wraps installed by wave 39 (ChannelMembersController:707, ChannelBlacklistController:381) as promised by the wave-39 ratchet plan. + +**Build outcome:** Three iterations. +1. First build surfaced `SearchGroupMembers.swift:83` — a TelegramCore-internal consumer of `$0.peer` on RCP that the plan's grep missed (grep only looked for `RenderedChannelParticipant(` constructors inside TelegramCore). +2. Second build surfaced `ShareWithPeersScreen.swift:777, 780` — an entire file missed in pre-flight because the initial grep was scoped to files already known to import RCP; this file gets RCP indirectly via `TemporaryCachedPeerDataManager.recent(...)`'s `updated:` callback. +3. Third build: clean (172 total actions). + +**Lessons:** + +- **Property-access migration has a broader grep surface than constructor migration.** Waves 34–36 migrated foundational types by grepping `(` constructors. That works because the consumer's use of the type is always preceded by a construction. Wave 41's RCP differs: RCP is constructed inside TelegramCore and handed to consumers as an opaque reference, so consumer usage looks like `participant.peer.X` (with any name, any subscript, any map closure). **Grep for the type's field-access patterns across the entire repo**, not just files that explicitly reference the type. For RCP specifically: `grep -rn 'RenderedChannelParticipant\|\.peer as?\|EnginePeer(.*\.peer)\|participant\.peer' submodules/` would have caught ShareWithPeersScreen.swift. File this as a **permanent pre-flight rule** for future property-access migrations. + +- **TelegramCore-internal consumer sites need the same inventory discipline as consumer-module sites.** The wave-41 plan enumerated 10 TelegramCore files for *construction* site updates but didn't grep for *consumption* sites — `SearchGroupMembers.swift:83` consumes `$0.peer` on RCP inside TelegramCore. Pre-flight for any foundational-type field migration must grep both construction AND consumption across the whole repo, not stratified by module. + +- **"Plan says N sites" numbers can be low; use `replace_all=true` defensively.** The plan listed 1 `EnginePeer(participant.peer)` site in ChannelBlacklistController.swift but the file actually had 5; listed 2 CAST sites in ChannelMembersSearchControllerNode.swift but the file had 4. The implementer correctly used `replace_all=true` on the unique-pattern greps, catching all sites. **Pre-flight should report "N site(s) per file at these lines" — if confidence is low, recommend replace_all directly.** + +- **Spec review caught `is TelegramChannel` that grep did not.** `participant.peer is TelegramChannel` does not match any of the usual migration-pattern greps (`EnginePeer(...)`, `as? TelegramUser`, `._asPeer()`). The spec reviewer caught it by reading the code. Would have been a build error anyway — the project compiles with `-warnings-as-errors` across 658/665 modules, so Swift's "'is' test always fails" warning promotes to an error — but catching it at spec time saves one build iteration. **Add `is Telegram(Channel|User|Group|SecretChat)` to the pre-flight token set** for any Peer → EnginePeer field migration so it's flagged at plan time rather than iteration time. + +- **First-pass-clean is NOT the right bar for foundational-type field migration.** Waves 37–40 achieved first-pass-clean. Wave 41 needed 3 iterations — and none of the three errors were the implementer's fault; all three were pre-flight misses. Heuristic update: **foundational-type field migrations (where the field is accessed via a generic property name like `.peer`) should budget 2–4 build iterations**, not first-pass-clean. The broader the grep surface, the more misses slip through. + +- **Ratchet economics of ADD-WRAP consumer constructors.** Wave 41 added 7 consumer `peer: EnginePeer(peer)` wraps at `RenderedChannelParticipant(...)` constructor sites in ChannelMembersSearch*Node and ChatControllerAdminBanUsers, where the local is still raw `Peer` from a legacy path (`peerView.peers[id]` / `authors: [Peer]`). These are ratchet markers: when the upstream legacy path migrates to EnginePeer, these wraps drop. The economics are net positive (−13 bridges) even counting the adds. + +**Plan:** `docs/superpowers/plans/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration.md`. +**Spec:** `docs/superpowers/specs/2026-04-24-renderedchannelparticipant-peer-engine-peer-migration-design.md`. + +--- + +## Wave 42 outcome (2026-04-24) + +`PeerInfoScreenData.peer: Peer? → EnginePeer?` — a single-module (18-file) struct-field migration entirely contained within `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/`. `PeerInfoScreenData` is a consumer-module data class, not a public TelegramCore type, so the wave never touched TelegramCore — contrast wave 41 which had to update TelegramCore construction sites. + +**Classification:** +- 1 struct-field change + 1 init-param change in PeerInfoData.swift +- 4 construction-site edits adding `.flatMap(EnginePeer.init)` wraps at `peer:` arguments built from `peerView.peers[...]` (PeerInfoData.swift L1027, L1620, L1867, L2205); 1 construction site unchanged (L1100, `peer: nil`) +- ~25 consumer DROP sites (removing `EnginePeer(peer)` wraps where the local `peer` was bound from `data.peer` / `self.data?.peer`) +- ~25 consumer CAST sites (`if let user = data.peer as? TelegramUser, ...` → `if case let .user(user) = data.peer, ...`) +- ~5 consumer `is TelegramXxx` rewrites on data.peer-derived locals (PeerInfoScreen.swift L3981, L4133, L4192, L4194) +- ~10 consumer ADD-WRAP `?._asPeer()` helper bridges for internal helpers that stay `peer: Peer?` (PeerInfoScreenPerformButtonAction:62 calling `peerInfoIsChatMuted`; PeerInfoScreen:5399/5805 calling `self.headerNode.update(peer:...)`; PeerInfoScreen:5857 calling `peerInfoCanEdit`; etc.) plus one inline `_asPeer()` bridge inside `peerInfoIsCopyProtected` because `isCopyProtectionEnabled` is not exposed on `EnginePeer` + +Net ~−30 bridges. Drops the ~5 wave-40 `EnginePeer(peer)` wraps that were directly in scope (the plan initially overestimated which sites would drop — some `EnginePeer(peer)` calls in PeerInfoScreen.swift are inside closures whose `peer` parameter comes from a caller, not from `data.peer`, and those correctly survived). + +**Build outcome:** Two iterations. +1. First build surfaced `peerInfoIsCopyProtected` helper: `peer.isCopyProtectionEnabled` failed against `EnginePeer` because that property is `Peer`-protocol-only and is not forwarded by `EnginePeer`. Fix: inline `peer._asPeer().isCopyProtectionEnabled` bridge at the helper callsite (helper signature stays `Peer?`). +2. Second build: clean. + +**Lessons:** + +- **EnginePeer is not a strict superset of Peer's property surface.** `EnginePeer` forwards `id`, `displayTitle(...)`, `compactDisplayTitle`, `isPremium`, `smallProfileImage`, `largeProfileImage`, and other common properties, but Peer-only properties like `isCopyProtectionEnabled` are NOT forwarded. Migration-time surprise: a property access that worked on raw `Peer` can fail against `EnginePeer`. **Bridge pattern:** inline `peer._asPeer().isCopyProtectionEnabled` at the callsite, OR if the helper already takes `Peer?`, bridge upstream with `?._asPeer()` and keep the access unchanged. Pre-flight addition: grep migrated consumer surfaces for `.X` accesses where `X` is a Peer-protocol property not in EnginePeer's forwarding set. (Alternative: extend `EnginePeer` with more forwarders, but that's a larger TelegramCore change out of scope for a single wave.) + +- **Wave-42 was not first-pass-clean despite being a consumer-module wave.** Contrast wave 41: 3 iterations due to broader grep-surface misses. Wave 42: 2 iterations due to a single property-forwarding-gap miss. Heuristic update: **even consumer-module property-access migrations budget 2 iterations**, because Peer/EnginePeer interface parity is never verified end-to-end at pre-flight time. + +- **Plan-stated wrap-drop counts can be wrong in both directions.** The plan listed 15+ `EnginePeer(peer)` drop sites in PeerInfoScreen.swift citing specific lines (1331, 1339, 1346, 1561, 2353, ...) — but on inspection, lines 1331/1339/1346 are inside a `peer`-parameter closure (`openPeerContextAction = { ..., peer, ... in ... }`) where `peer` comes from the closure's caller, not from `data.peer`. Those wraps correctly stayed. The implementer correctly judged each binding context before deciding whether to drop. **Rule for plan writing:** when naming specific line numbers as drop-candidates, verify the `if let peer = ...` / closure-param binding in the actual file text, not just the `EnginePeer(peer)` grep result. + +- **Wave-41 lesson reconfirmed (`is Telegram*` rewrite):** all 5 `is TelegramChannel|User|Group|SecretChat` checks on `data.peer`-derived locals were caught at plan time (via the pre-flight grep token set including `is Telegram...`), not at build time. `-warnings-as-errors` would have caught them at build time but pre-flight catches save an iteration. + +- **Internal-helper bridge economics.** The PeerInfoScreen module has ~6 internal helpers (`canEditPeerInfo`, `peerInfoIsChatMuted`, `peerInfoHeaderButtons`, `peerInfoHeaderActionButtons`, `peerInfoCanEdit`, `availableActionsForMemberOfPeer`, `peerInfoIsCopyProtected`) that still take `peer: Peer?`. Wave 42 added ~10 `?._asPeer()` bridges at their callsites. A follow-up wave migrating those helper signatures would drop exactly those 10 bridges — small, well-scoped, strong candidate for wave 42.x. + +**Plan:** `docs/superpowers/plans/2026-04-24-peerinfoscreendata-peer-engine-peer-migration.md`. + +--- + +## Wave 43 outcome (2026-04-24) + +Migrated six PeerInfoScreen module helpers — `canEditPeerInfo`, `availableActionsForMemberOfPeer`, `peerInfoHeaderActionButtons`, `peerInfoHeaderButtons`, `peerInfoCanEdit`, `peerInfoIsChatMuted` (plus nested `isPeerMuted`) — from `peer: Peer?` to `peer: EnginePeer?`. All internal `as? TelegramUser|Channel|Group` and `is TelegramX` patterns inside the helper bodies (PeerInfoData.swift lines 2265–2670) rewritten to `case let .user/.channel/.legacyGroup` / `case .x = peer` enum patterns. No TelegramCore changes. No new typealiases. + +**Classification:** +- 6 helper signature migrations + 12 `as?` / `is` body rewrites in PeerInfoData.swift +- 7 DROPs of `._asPeer()` / `?._asPeer()` bridges installed by wave 42 (sites in PeerInfoScreenAvatarSetup, PeerInfoScreenPerformButtonAction ×3, PeerInfoScreenOpenMember, PeerInfoScreen:5857, PeerInfoProfileItems) +- 2 CONVERTs at PeerInfoScreen.swift:1905/1961 (`peer: group` / `peer: channel` extracted from `case let` → `peer: data.peer`, preserving the `group`/`channel` binding for body use) +- 10 ADD-WRAPs at call sites inside enclosing methods that still take raw `Peer?` (PeerInfoHeaderNode ×3, PeerInfoHeaderEditingContentNode ×5, PeerInfoEditingAvatarNode, PeerInfoEditingAvatarOverlayNode) — `.flatMap(EnginePeer.init)` for optional Peer, `EnginePeer(peer)` for non-optional (post-`guard let peer = peer`) Peer +- 2 ADD-WRAPs at raw-`Peer` member-item sites (PeerInfoScreenMemberItem, PeerInfoMembersPane) + +Net ~+5 wraps overall (7 drops, 12 adds) — less important than the headline: the helper signatures now live on the engine side. The 12 ADDs are all staged for drop in follow-up waves that migrate the four `update(peer: Peer?, ...)` methods (PeerInfoHeaderNode, PeerInfoHeaderEditingContentNode, PeerInfoEditingAvatarNode, PeerInfoEditingAvatarOverlayNode) and the two raw-`Peer` enclosingPeer storage fields (PeerInfoScreenMemberItem.enclosingPeer is actually `Peer?`, PeerInfoMembersPane's local `enclosingPeer: Peer`). + +**Build outcome:** 2 iterations. +1. First build surfaced one error: `PeerInfoScreenMemberItem.item.enclosingPeer` was declared `let enclosingPeer: Peer?` (optional) — the plan had prescribed `EnginePeer(item.enclosingPeer)` (non-optional form). Fix: `.flatMap(EnginePeer.init)` at the callsite. One-line correction. +2. Second build: clean. + +**Commit:** `d53e0d50f4c0e3e68c4e4c1ce255e76f43f56d4b` (12 files + plan). + +**Lessons:** + +- **Plan-declared property optionality can be wrong — verify `let X: T?` vs `let X: T` at plan-write time.** Wave 43's plan described `item.enclosingPeer` as non-optional `Peer` based on how it was used (`item.enclosingPeer as? TelegramChannel`, `item.enclosingPeer is TelegramChannel` compile against `Peer` non-optional OR optional protocol). The declaration was actually `Peer?`. The one-site plan-declaration mismatch cost one build iteration. **Rule for plan writing:** for every ADD-WRAP site, cite the declaration line, not just the usage. `grep -nE "(let|var)\s+\w+:\s*Peer\??" ` gives unambiguous optionality. + +- **Helper-signature migrations with zero Peer-only property access are first-iteration-clean except for optionality surprises.** Wave 42's lesson (EnginePeer property-forwarding gap) did not recur here because wave 43's helpers only did `as?` / `is` casts — pure enum-rewrite territory. Pre-flight verified each helper body used only `.id` and concrete-type accesses (`TelegramChannel.hasPermission(...)` etc., which stay on concrete types post-migration). The only iteration cost was the optionality mismatch above. **Heuristic: if a helper's body does not touch the `peer` parameter outside of `as?`/`is`/`.id`/`.id.isX`, budget 1 iteration + optionality-audit pass.** + +- **`case .x = peer` without binding is the correct `-warnings-as-errors` form when the branch body doesn't need the concrete value.** Wave 43's `peerInfoIsChatMuted` body at lines 2641/2643 uses `case .user = peer` and `case .legacyGroup = peer` (no inner binding) — because the branches only read `globalNotificationSettings.privateChats.enabled` / `.groupChats.enabled`, never the concrete `TelegramUser` / `TelegramGroup`. Had the migration emitted `case let .user(_) = peer` with `_`, or `case let .user(user) = peer` with `user` unused, `-warnings-as-errors` would fail. **Pre-flight rule: for each `peer is TelegramX` → `case .x = peer` rewrite, check whether the branch body accesses the concrete type. If not, use bare-case form; if yes, bind.** + +- **Bundled-dispatch subagent flow + two-stage review works cleanly for ≥10-site single-commit migrations.** Wave 43 dispatched one implementer (bundled Tasks 1–7), then spec reviewer, then code quality reviewer. All three completed in roughly 10 minutes of subagent time. Spec reviewer caught zero misses (implementer self-caught the optionality deviation in iteration 1). Code quality reviewer surfaced only minor observational notes (no Critical / Important issues). **Continues to confirm wave-39/40/41/42 finding: for ≤30-file / ≤50-edit wave shapes, the bundled flow is reliable; TaskCreate tracking adds no value at this size.** + +**Plan:** `docs/superpowers/plans/2026-04-24-peerinfoscreen-helpers-engine-peer-migration.md`. + +--- + +## Wave 44 outcome (2026-04-24) + +Migrated `RenderedChannelParticipant.peers: [PeerId: Peer]` to `[EnginePeer.Id: EnginePeer]`. Closes the wave-41 ratchet — the public RCP struct no longer leaks raw `Peer` types in any field (the surviving `presences: [PeerId: PeerPresence]` is out of scope; PeerPresence is a Postbox protocol requiring a separate migration). No new typealiases. No engine wrapper structs. + +**Classification:** +- 1 declaration: `ChannelParticipants.swift:11/14` field + init default +- 8 TelegramCore producer migrations (`ChannelMembers`, `RequestStartBot`, `ChannelOwnershipTransfer`, `JoinChannel`, `AddPeerMember`, `PeerAdmins`, `ChannelBlacklist`, `Ranks`) — each builds a local `var peers: [EnginePeer.Id: EnginePeer] = [:]` inside a `postbox.transaction` and wraps insertions with `EnginePeer(...)`. All producers pre-existed with the same structural pattern: 14 raw insertions became 14 wrapped insertions. +- 6 consumer DROPs of `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)` in `ChannelAdminsController`, `ChannelMembersSearchContainerNode` (×4), `ChannelBlacklistController` +- 5 consumer DROPs of `.mapValues({ $0._asPeer() })` transforms at RCP constructor call sites in `ChannelAdminsController`, `ChannelMembersSearchContainerNode` (×2), `ChannelMembersSearchControllerNode` (×2) +- 2 consumer ADDs of `._asPeer()` at `ChatRecentActionsHistoryTransition.swift` iteration sites — line 673 (`participant.peers`) and line 2273 (`new.peers` inside `participantSubscriptionExtended`), where the iterated `EnginePeer` is inserted into an outer `SimpleDictionary`. + +Net consumer-surface: **−10 bridges**. TelegramCore-internal: +~12 wraps inside files that already `import Postbox`. No new `import Postbox` in any consumer module; no Postbox-hygiene regression. + +**Build outcome:** 2 iterations. +1. First build surfaced one error: `cannot assign value of type 'EnginePeer' to subscript of type '(any Peer)?'` at `ChatRecentActionsHistoryTransition.swift:2273` — a second RCP.peers iteration site the plan's `participant\.peers` pre-flight grep missed because the local RCP binding was named `new` (from `case let .participantSubscriptionExtended(prev, new)`). Wider grep `\b(participant|new|prev|rcp|renderedParticipant)\.peers\b` confirmed only one additional site. Fix: identical `._asPeer()` unwrap as line 673. +2. Second build: clean. + +**Commit:** `ca69fa8cbb` (14 files, 38 insertions / 38 deletions). + +**Lessons:** + +- **Property-access field migrations need a name-agnostic grep surface, not a binding-name-prefixed one.** Wave 41's lesson said "repo-wide grep surface, not a stratified one." Wave 44 extends it: the binding name of the RCP varies by enum-case-destructure site. In `ChatRecentActionsHistoryTransition` specifically, one switch arm destructures the RCP enum case as `case let .participantSubscriptionExtended(prev, new):` — the local is `new`, not `participant`. Pre-flight grep token set for RCP.peers iteration sites should be `\b(participant|new|prev|rcp|renderedParticipant|channelParticipant)\.peers\b`. For the general case: **for any `T.` migration on a type passed opaquely through enum-case-destructures, enumerate the common destructure binding names (`prev`, `new`, `current`, `lhs`, `rhs`, etc.) in the plan's pre-flight grep.** + +- **Iteration-site-plus-insertion-into-foreign-container pattern is a canonical ADD-UNWRAP shape.** When a foundational type's dict field is migrated to engine types but a caller iterates the dict and inserts values into an outer container still typed with raw Postbox values (here `SimpleDictionary`), an `._asPeer()` unwrap is required at the insertion line. This pattern repeated twice in `ChatRecentActionsHistoryTransition` — both iteration sites have the same enclosing structure (build a raw-Peer `SimpleDictionary` → iterate RCP.peers → insert). **Future migrations on dict-like fields: grep for `for (_, X) in .peers { [X.id] = X }` patterns and anticipate the unwrap.** + +- **Producer-side migration is low-risk for field-type-parameter changes on local transaction-built dicts.** All 8 TelegramCore producers had the identical structural pattern (`var peers: [PeerId: Peer] = [:]` → insertions → pass to RCP constructor). Mechanical `EnginePeer(...)` wrap at each insertion with no side effects. No chain-migration concerns because every producer builds locally. **Shape heuristic: if producers build their contribution dict locally via `transaction.getPeer` calls, field-type migration is mechanical and fits a bundled-dispatch wave well.** + +- **Declaration-level `[PeerId: Peer] = [:]` init default shifts transparently.** The init-default literal `[:]` works as either `[PeerId: Peer]` or `[EnginePeer.Id: EnginePeer]`; 12 RCP construction sites with no `peers:` arg required zero changes. **Shape heuristic: default-valued Postbox-typed parameters are free on migration when the default literal is the empty collection form.** + +- **Consumer `.mapValues({ $0._asPeer() })` transforms on engine-typed source dicts become no-op drops on the target field migration.** Wave 44 dropped 5 such transforms cleanly because each source dict was already `[EnginePeer.Id: EnginePeer]` (verified at plan time by reading 20-line spans around each site). **Shape heuristic: when a consumer-side constructor call site uses `peers: X.mapValues({ $0._asPeer() })`, it signals the source is already engine-typed and the target field-type migration would drop the unwrap transform entirely.** + +- **Wave-41 lesson reconfirmed: foundational-type field migrations budget 2–3 iterations, not first-pass-clean.** Wave 44 hit 2 iterations because of the enum-destructure-binding-name grep miss; a more exhaustive pre-flight grep would have caught it as a first-pass-clean. + +**Plan:** `docs/superpowers/plans/2026-04-24-rcp-peers-engine-migration.md`. + +--- + +## Wave 45 outcome (2026-04-24) + +Migrated the four `update(..., peer: Peer?, ...)` methods across the PeerInfoHeader node hierarchy — `PeerInfoHeaderNode.update`, `PeerInfoHeaderEditingContentNode.update`, `PeerInfoEditingAvatarNode.update`, `PeerInfoEditingAvatarOverlayNode.update` — from raw `Peer?` to `EnginePeer?`. Consumer-surface ratchet that drops ~15 wave-43-era ADD-WRAP bridges. Single-module wave (all four files live in `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/`). No new typealiases. No engine wrapper structs. The stored `PeerInfoHeaderNode.peer: Peer?` field stays raw (intentional scope cap) and is bridged with `peer?._asPeer()` at assignment. + +**Classification:** +- 4 signature changes: `peer: Peer?` → `peer: EnginePeer?` at PHN:496, PHECN:52, PEAN:61, PEAON:63. +- 15 DROP wraps across the four files: + - PHN body: 7 drops at :524 (listContainerNode.peer), :548 (peerInfoHeaderActionButtons), :549 (peerInfoHeaderButtons), :571 (backgroundCoverSubject = .peer), :1218 (displayTitle), :1964 (listContainerNode.update), :2361 (peerInfoIsChatMuted). + - PHECN body: 5 drops at :59, :88, :93, :159, :162 (all `canEditPeerInfo` wraps). + - PEAN body: 2 drops at :66 (canEditPeerInfo) and :88 (avatarNode.setPeer). + - PEAON body: 1 drop at :85 (canEditPeerInfo). +- 5 enum-case rewrites in PHN: `peer as? TelegramChannel` / `peer as? TelegramUser` → `case let .channel(x) = peer` / `case let .user(x) = peer` at :622, :1225, :1238, :1273, :2354. +- 7 enum-case rewrites in PHECN: `peer as? TelegramUser/Group/Channel` at :73, :77, :86 (no-bind), :91 (no-bind), :107 (inner rebind), :154. +- 1 enum-case rewrite in PEAN: `peer as? TelegramChannel` at :93. +- 1 enum-case rewrite in PEAON: `peer as? TelegramChannel` at :74. +- 4 `_asPeer()` bridges ADDED at Peer-only-property sites (wave-42 lesson pattern): + - PEAN:159 `PeerReference(peer._asPeer())` — PeerReference.init takes raw Peer. + - PEAN:166 `peer._asPeer().isCopyProtectionEnabled` — property not forwarded on EnginePeer. + - PHECN:115 `(peer?._asPeer() as? TelegramUser)?.lastName` — inline expression form kept; chose bridge over multi-line `case let` expansion. + - PHN:521 `self.peer = peer?._asPeer()` — stored raw Peer? field stays raw. +- 1 ADD wrap at PHN:363 (`itemsUpdated` closure) — closure reads stored raw `self.peer` and forwards to the migrated `PEAN.update`. +- 1 ADD bridge at PHN:1815 for `self.avatarListNode.update(..., peer: peer?._asPeer(), ...)` — `PeerInfoAvatarListNode.update(size:avatarSize:isExpanded:peer:...)` still takes raw `Peer?` (plan pre-flight missed this site). Candidate for wave 46. +- 2 external call sites in `PeerInfoScreen.swift` at :5399 and :5805 reshuffled: `peer: self.data?.savedMessagesPeer ?? self.data?.peer?._asPeer()` → `peer: self.data?.savedMessagesPeer.flatMap(EnginePeer.init) ?? self.data?.peer`. Net-zero at these sites (swap `?._asPeer()` bridge for `.flatMap(EnginePeer.init)` wrap). + +Net consumer-surface: **−10 bridges** (15 drops − 4 internal `_asPeer()` bridges − 1 internal closure ADD-WRAP at :363 − 1 :1815 bridge the plan missed; the two external call sites are net-zero). No new `import Postbox` in any consumer module; no Postbox-hygiene regression. + +**Build outcome:** 1 iteration (first-pass-clean). + +**Commit:** `6b7a23867c` (5 files, 41 insertions / 41 deletions). + +**Lessons:** + +- **Plan pre-flight grep for call sites must include internal sibling-method callers, not just the target method's direct callers.** The plan enumerated the three internal call sites of PEAN.update / PHECN.update / PEAON.update inside PeerInfoHeaderNode.update's body (:633, :1816, :1817) and the `itemsUpdated` closure (:363), plus the two external call sites (:5399, :5805). It MISSED the `self.avatarListNode.update(..., peer:...)` call at PHN:1815 — a different target node's update method (`PeerInfoAvatarListNode.update(size:avatarSize:isExpanded:peer:...)`) that happens to live on the line immediately before `self.editingContentNode.avatarNode.update(peer: peer, ...)` at :1816. Pre-flight grep token `\.update\(.+peer:` over the four files would have caught it. Fix for future pre-flight grep: for each signature migration, grep ALL `update` methods called with the migrated param and verify each target's signature — not just the siblings being migrated. + +- **Wave-43 binding-rule continues to hold at scale.** All 13 `as? TelegramUser/Group/Channel` rewrites in this wave obeyed the bind-when-used / no-bind-when-unused rule (PHECN:86 and :91 used `case .legacyGroup = peer` / `case .channel = peer` no-bind form because the branch body only appends field keys; all 11 others bind because the branch uses the concrete value). Zero `-warnings-as-errors` unused-binding risks surfaced during review. **Rule confirmed as durable.** + +- **EnginePeer forwarding audit is worth a pre-flight pass for multi-property-accessing methods.** PHN's `update` method body accesses ~15 EnginePeer-forwarded properties (id, isFake, isScam, isPremium, isVerified, debugDisplayTitle, displayTitle, addressName, effectiveProfileColor, emojiStatus, verificationIconFileId, profileImageRepresentations, etc.) plus 2 concrete-class properties requiring enum bindings (`.phone` on TelegramUser; `.info` on TelegramChannel). Without a pre-flight forwarding audit against `submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift:485-560` (the EnginePeer property-forwarding extension), iteration surprises are likely. This wave's audit at plan time caught `isCopyProtectionEnabled` as the only non-forwarded property in PHN's body — matching the single bridge in PHN. **Rule: for multi-access methods, read the EnginePeer property-forwarding extension in full at plan time; enumerate every `peer.X` site and mark forwarded vs not.** + +- **Case-pattern-against-optional is idiomatic and compiles cleanly.** All rewrites use `if case let .user(user) = peer` (or sibling case forms) against `EnginePeer?` — Swift extracts through Optional implicitly. Precedent: `PeerInfoScreenSettingsActions.swift:200` `if case let .user(user) = self.data?.peer, let phoneNumber = user.phone`. Build did not complain at any rewrite site. **Conservative fallback `if let peer, case let .x(y) = peer` is NOT needed; drop from future plans' "fallback" sections.** + +- **Bundling net-zero external-call-site migrations with net-negative internal drops is fine for ratchet-focused waves.** The two PHN.update call sites in PeerInfoScreen.swift swap one bridge form for another (`?._asPeer()` → `.flatMap(EnginePeer.init)`) — literally net-zero on wrap count. The wave accepted this because (a) the internal drops (~15) dominate, (b) migrating `savedMessagesPeer` field to remove the external wrap would expand scope to a different file's public struct field, and (c) the wave-goal was "ratchet wave-43 ADDs", which is accomplished purely via internal drops. **Heuristic: if external call sites are net-zero and the internal scope is net-negative, bundle them; don't let the external net-zero block the ratchet.** + +**Plan:** `docs/superpowers/plans/2026-04-24-peerinfoheader-update-bundle-engine-peer.md`. + +--- + +## Wave 46 outcome (2026-04-25) + +Migrated the PeerInfoScreen-local avatar chain: `PeerInfoAvatarListNode.update(peer:)` together with `PeerInfoAvatarTransformContainerNode.update(peer:)` / `.updateStoryView(peer:)` and the private `Params.peer` stored field — raw `Peer?` → `EnginePeer?` across 4 files. Ratchet wave: drops the wave-45 ADD at `PeerInfoHeaderNode.swift:1815` plus a pre-existing external bridge at `PeerInfoScreen.swift:2574`, collapses one internal wave-45 wrap inside PIATCN's `setPeer` body, and adds 2 `_asPeer()` bridges for Peer-only surfaces (`PeerReference.init` and `.isCopyProtectionEnabled`). Net: −1 bridge. The PeerInfoScreen-local `PeerInfoAvatarListNode` class shadows a same-named submodule class — the wave targets the local (PeerInfoScreen/Sources) file only; the submodule is untouched. No new typealiases. No engine wrapper structs. No `import Postbox` change. + +**Classification:** +- 4 signature changes: `peer: Peer?` → `peer: EnginePeer?` on `PeerInfoAvatarListNode.update(peer:)` and its `arguments` tuple, plus `PeerInfoAvatarTransformContainerNode.update(peer:)`, `.updateStoryView(peer:)`, and the private `Params.peer` stored field. +- 2 DROPs at call sites (ratchet-value): + - `PeerInfoHeaderNode.swift:1815` — `peer: peer?._asPeer()` → `peer: peer` (the wave-45 ADD flagged for ratchet). + - `PeerInfoScreen.swift:2574` — `peer: peer?._asPeer()` → `peer: peer` (pre-existing external bridge; direct caller of `updateStoryView`). +- 1 internal WRAP collapsed inside PIATCN's `setPeer(...)` body: `peer: EnginePeer(peer)` → `peer: peer` (type flowed once upstream migrated). +- 2 ADDs inside PIATCN body for Peer-only surface: + - PIATCN ~:404 `PeerReference(peer._asPeer())` — `PeerReference.init` takes raw `Peer`, not `EnginePeer`. + - PIATCN ~:406 `peer._asPeer().isCopyProtectionEnabled` — property defined on the `Peer` protocol (`PeerUtils.swift:236`), not forwarded on EnginePeer (same finding as wave 45 for PEAN:166). +- 2 `as? TelegramChannel` → `case let .channel(...) = peer` rewrites inside PIATCN (both `update` and `updateStoryView` bodies test `channel.isForumOrMonoForum`; the `let channel` binding is retained because the body uses the concrete value). + +Net consumer-surface: **−1 bridge** (2 drops + 1 wrap collapse − 2 adds). External API: no change. + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.587s; only PeerInfoScreen + TelegramUI recompiled. + +**Commit:** `5ca99da5a7` (4 files, 13 insertions / 13 deletions). + +**Lessons:** + +- **Shadowing-class-name pre-flight disambiguation.** `PeerInfoAvatarListNode` is defined in two places: `submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift` (the submodule) AND `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarListNode.swift` (the PeerInfoScreen-local class that shadows it). Wave 46's pre-flight nearly targeted the wrong file when the plan inspected the signature by class name alone. Mitigation for future waves: when the target method's signature has distinctive parameter names (here `avatarSize:isForum:threadId:threadInfo:`), grep against those distinctive tokens alongside the class name to disambiguate — and when the class name appears in multiple files, resolve the module path before writing the plan. + +- **Chain-audit extends via sibling internal methods (`updateStoryView` pattern).** The initial audit targeted only the `update(peer:)` chain. But `PeerInfoAvatarTransformContainerNode.update`'s tail calls `self.updateStoryView(peer:)` — which also takes raw `Peer?`. Expanding the wave to include `updateStoryView` was a single-file additional edit and additionally dropped the `PeerInfoScreen.swift:2574` external bridge (a direct caller of `updateStoryView`, not `update`). Pattern for future chain waves: grep the target implementation body for `self\.\w+\(.+peer:` to find internal sibling methods whose signature migration naturally bundles with the primary chain. + +- **First-pass-clean extends to chain migrations when both forwarding audit and scope audit are done at plan time.** Wave 46 is the 4th consecutive wave (42/43/44/45/46 — wave 44 needed 2 iterations) hitting 0-to-1-iteration convergence. The 1-iteration-clean pattern is reproducible when (a) the `_asPeer()` bridge sites are identified at plan time by cross-referencing the body against the EnginePeer property-forwarding extension AND `PeerReference`'s init overloads, and (b) enum-case conversions are pre-written in the plan with bindless-vs-binding form pre-classified. Both inputs were present in the wave 46 plan. + +- **Chain-bundling heuristic validated at 3 methods / 4 files.** Migrating a narrow call chain (`PeerInfoAvatarListNode.update` → `PIATCN.update` → `PIATCN.updateStoryView`) + all external call sites in a single commit is the right granularity: the internal `peer` variables flow transparently once each downstream signature migrates, and the external call-site greps (2 sites total) complete the ratchet. Net: clean atomic commit, no bridging churn remains at the boundary. For future chain waves, bundle the chain methods plus external callers in one commit; don't split by method. + +**Plan:** `docs/superpowers/plans/2026-04-25-peerinfo-avatar-chain-engine-peer.md`. + +--- + +## Wave 47 outcome (2026-04-25) + +Migrated the stored `PeerInfoHeaderNode.peer` field from `Peer?` to `EnginePeer?`. Single-file wave; field is `private`, so no external API change. 4 edits / 1 file, all inside `submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift`. + +**Edits:** +- Line 92: `private var peer: Peer?` → `private var peer: EnginePeer?` +- Line 363 (`itemsUpdated` closure): `peer: EnginePeer(peer)` → `peer: peer` (drops bridge; `peer` is captured from `strongSelf.peer`). +- Line 521 (assignment in `update`): `self.peer = peer?._asPeer()` → `self.peer = peer`. +- Line 2054 (ProfileLevelInfoScreen push closure): `peer: EnginePeer(peer),` → `peer: peer,`. + +PHN:426 (`peer.profileImageRepresentations.isEmpty`) compiles unchanged because `profileImageRepresentations` is forwarded by `EnginePeer` (`Peer.swift:485`). + +Net consumer-surface: **−3 internal bridges**. + +**Build outcome:** 2 iterations. +- Iteration 1 failed at PHN:363 — pre-flight grep used `self\.peer\b` and missed the `strongSelf.peer` capture inside the `itemsUpdated` closure body. The memory file's wave-47 candidate notes had explicitly flagged PHN:363; the omission was in the executor's grep, not the plan's identification. +- Iteration 2 clean. Full-project Bazel build 28.727s; only PeerInfoScreen + TelegramUI recompiled. + +**Commit:** `d7b7536440` (4 insertions / 4 deletions in PHN.swift; plan file added). + +**Lessons:** + +- **Pre-flight grep for stored-field migrations must include closure-capture aliases (`strongSelf`, `self_`, etc.), not just `self.`.** Wave 47's first-iteration failure was 100% avoidable: the same field is referenced via `strongSelf.peer` inside an `itemsUpdated` closure, and the bare `self\.peer\b` grep missed it. For future stored-field migrations, the canonical grep pattern is `(self|strongSelf|[a-zA-Z_]*[Ss]elf)\.\b`. The convention `strongSelf` appears throughout the PeerInfo codebase whenever closures use `[weak self]`. +- **Memory-stored candidate notes can pre-list the bridge sites.** The memory file `project_postbox_refactor_next_wave.md` had explicitly named PHN:363 (`itemsUpdated` closure read), PHN:521 (stored assignment), and PHN:2054 (ProfileLevelInfoScreen push wrap) before the wave started. Reading those during plan-write would have caught the iteration-1 miss. Treat the wave-N+1 candidate notes in memory as a load-bearing input to the plan, not just narrative. +- **Single-file `private` stored-field migrations are the cleanest possible wave shape.** No external API surface, no cross-module recompilation, blast radius bounded to the same file's other methods. When a wave reaches this shape, it is a near-zero-risk drop. Future stored-field-migration waves should be explicitly classified during planning as "single-file private" / "cross-file private" / "public-surface" to set the iteration budget. + +**Plan:** `docs/superpowers/plans/2026-04-25-phn-peer-stored-field-engine-peer.md`. + +--- + +## Wave 48 outcome (2026-04-25) + +Migrated `PeerInfoScreenData.savedMessagesPeer: Peer? → EnginePeer?`. Cross-file struct-field migration contained within the PeerInfoScreen module; the field has no external consumer (`grep -rEn "(\w+\??)\.savedMessagesPeer\b"` matches only inside the PeerInfoScreen sources). 5 edits across 2 files. + +**Edits:** +- `PeerInfoData.swift:388` — field decl `Peer?` → `EnginePeer?`. +- `PeerInfoData.swift:444` — init param `Peer?` → `EnginePeer?`. +- `PeerInfoData.swift:1622` — drop `?._asPeer()` bridge. The source local `let savedMessagesPeer: Signal` (PID:1313) already produces `EnginePeer?`; the bridge was an artificial demotion. +- `PeerInfoScreen.swift:5399` and `:5805` — drop `.flatMap(EnginePeer.init)` bridge in `headerNode.update(... peer: ...)` call. The `peer` parameter of `headerNode.update` has been `EnginePeer?` since wave 45, and the `??` coalescing operand `self.data?.peer` has been `EnginePeer?` since wave 42; once the field migrates, both ends of the expression are `EnginePeer?` and the `flatMap` bridge falls out. + +The other 4 init kwarg sites (PID:1029, :1102, :1869, :2207) all pass `nil` and require no change. + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.858s; only PeerInfoScreen + TelegramUI recompiled. + +**Commit:** `1e4c2eea33` (5 insertions / 5 deletions; plan file added). + +**Lessons:** + +- **Internal-storage demotion → external re-promotion** is a high-yield ratchet pattern. The field had a `?._asPeer()` demotion at the storage site, then `.flatMap(EnginePeer.init)` re-promotions at every read. Migrating the field type drops both ends in one wave — the underlying signal pipeline never needed the `Peer?` form at all. Pattern to look for in future PSD-class migrations: any field whose initialization expression names `_asPeer()` strongly indicates that the field's source data is already `EnginePeer?`. Grep candidate fields' init expressions for `_asPeer()` to surface these high-leverage migrations. + +- **Containment audit must distinguish field declarations on different types.** The grep `grep -rln "savedMessagesPeer"` returned 6 modules outside PeerInfoScreen. All matched references were independent declarations on unrelated types (TelegramEngineMessages, ChatListUI nodes, ChatControllerContentData). The narrower regex `(\w+\??)\.savedMessagesPeer\b` filtered to actual field-access patterns and confirmed no external consumer. For future struct-field migrations, prefer the access-pattern regex over plain text grep; common field names will hit unrelated declarations and inflate apparent blast radius. + +- **`replace_all=true` is correct for verbatim-duplicated call sites.** PIS:5399 and :5805 are byte-identical `headerNode.update(...)` calls with the same `peer:` argument. Single-Edit-with-replace_all replaced both atomically. No collisions because the `headerNode.update` argument list with `flatMap(EnginePeer.init)` is sufficiently long to be unique. + +- **Wave-shape G' (sibling-of-wave-42 ratchet) revalidated.** Wave 42 migrated `PeerInfoScreenData.peer: Peer? → EnginePeer?`. Wave 48 follows the same pattern on a sibling field. Future waves on `chatPeer`, `linkedDiscussionPeer`, `linkedMonoforumPeer` will not all be this clean: `chatPeer` has 5 `as? TelegramX` checks downstream + cross-method propagation into ClearPeerHistory; `linkedMonoforumPeer` has an `as? TelegramChannel` check at PIPI:1197. Field-by-field selection should consider downstream consumer shape, not just declaration site. + +**Plan:** `docs/superpowers/plans/2026-04-25-peerinfoscreendata-savedmessagespeer-engine-peer.md`. + +--- + +## Wave 49 outcome (2026-04-25) + +Bundled migration: `PeerInfoScreenData.linkedDiscussionPeer` + `.linkedMonoforumPeer`, both `Peer? → EnginePeer?`. Cross-file struct-field migration over 2 files. Bundled because both fields share parallel local-source patterns (raw `peerView.peers[id]` dict lookup) and the same single consumer file (`PeerInfoProfileItems.swift`). The bundle is justified: the source-of-truth init blocks compute `discussionPeer` and `monoforumPeer` as a sibling pair at PID:1836–1843 and again at PID:2131–2138; migrating one without the other would leave a half-Peer-half-EnginePeer init block. + +**Edits:** + +`PeerInfoData.swift` (12 edits via `replace_all=true` on the parallel pair): +- Lines 396–397 — field decls Peer? → EnginePeer?. +- Lines 453–454 — init params Peer? → EnginePeer?. +- Lines 1836+2131 — `var discussionPeer: EnginePeer?` (parallel pair). +- Lines 1838+2133 — `discussionPeer = EnginePeer(peer)` (lift raw Peer at boundary). +- Lines 1841+2136 — `var monoforumPeer: EnginePeer?` (parallel pair). +- Lines 1843+2138 — `monoforumPeer = peerView.peers[linkedMonoforumId].flatMap(EnginePeer.init)` (lift Peer? at boundary). + +`PeerInfoProfileItems.swift` (3 edits): +- :1102 — `EnginePeer(peer).displayTitle(...)` → `peer.displayTitle(...)`. +- :1197 — `if let monoforumPeer = data.linkedMonoforumPeer as? TelegramChannel` → `if case let .channel(monoforumPeer) = data.linkedMonoforumPeer`. The `case .channel` payload is `TelegramChannel`, so the downstream `monoforumPeer.sendPaidMessageStars` access at :1198 continues to compile. +- :1409 — `EnginePeer(linkedDiscussionPeer).displayTitle(...)` → `linkedDiscussionPeer.displayTitle(...)`. + +**Net bridge accounting:** +- ADDs (4): boundary lifts at PID:1838, :1843, :2133, :2138. These lift the Postbox-typed `peerView.peers[...]` dict-lookup result to the engine type at the data-flow boundary — the canonical Postbox→Engine position. Mirrors wave 42's `peer.flatMap(EnginePeer.init)` lift at PID:1620. +- DROPs (2): displayTitle `EnginePeer(...)` wraps at PIPI:1102 and :1409. +- Plus 1 idiom cleanup (PIPI:1197 — `as?` cast → enum-case pattern); no text saving but better Swift idiom. + +The +4/−2 net text-bridge count is acceptable here because the ADDs are not "internal bridges" — they're the canonical Postbox→Engine boundary that any well-typed engine field requires. Wave-tracking should distinguish "boundary lifts" (correct, permanent) from "internal bridges" (incorrect, ratchet target). + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.479s; only PeerInfoScreen + TelegramUI recompiled. Pre-flight EnginePeer-property forwarding audit (`addressName` at Peer.swift:461; `displayTitle` is an EnginePeer instance method; `case .channel` binds `TelegramChannel`; `TelegramChannel.sendPaidMessageStars` exists at SyncCore_TelegramChannel.swift:215) all verified at plan time and proved correct. + +**Commit:** `79698e4513` (15 edits across 2 files; plan added). + +**Lessons:** + +- **Bundle waves around source-of-truth coherence, not just consumer overlap.** Wave 49's bundling was justified primarily by the *source* side: both fields are computed in the same init block as a sibling pair (PID:1836–1843, :2131–2138). Migrating only one would leave the init block in a half-typed state and force an artificial bridge. Future bundling decisions should weight this "shared source-of-truth init" factor at least as heavily as "shared consumer file." + +- **Boundary-lift bridges are not "internal bridges" — wave-tracking should distinguish.** Wave-49's +4 ADDs are at the Postbox↔Engine boundary (raw `peerView.peers[...]` Postbox-typed dict → engine-typed field). They are the *correct* place for the lift; they will not ratchet away in a future wave. Internal bridges (e.g. PID:1622's `_asPeer()` in wave 48, or `EnginePeer(...)` wraps at consumer-side display calls) are the actual ratchet targets. Future wave outcome reports should categorize ADDs as "boundary lift" vs "internal bridge" to avoid confusion in net-bridge accounting. + +- **Parallel-pair `replace_all=true` works for multi-line blocks when the parallel pair is byte-identical.** PID:1836–1843 and PID:2131–2138 are byte-identical 9-line blocks. One Edit-with-replace_all applied the type+wrap rewrite to both. Pre-flight verification: `diff <(sed -n '1836,1843p' file) <(sed -n '2131,2138p' file)` returned empty before the edit. Pattern generalizes to any "compute the same locals in two adjacent init paths" code structure (a common shape in PeerInfoData.swift's signal pipelines). + +- **Whitespace nuance with `replace_all` multi-line edits.** The blank separator between the two `var X: Peer?` blocks had trailing spaces (Swift code with whitespace-trimming editor settings). The new_string used a clean blank line; the resulting file has a plain `\n` separator instead of ` \n`. Cosmetic only — no compile impact — but worth noting for future plans: when the indentation-sensitive pre-text uses trailing whitespace, mirror it exactly in the new_string to avoid inadvertent normalization. + +- **`case .channel` pattern at field-binding compiles cleanly against `EnginePeer?` (re-confirmed wave 45 lesson).** No `if let ... case let .channel = ...` two-step needed; single-step `if case let .channel(monoforumPeer) = data.linkedMonoforumPeer` works directly. + +**Plan:** `docs/superpowers/plans/2026-04-25-peerinfoscreendata-linked-peers-engine-peer.md`. + +--- + +## Wave 50 outcome (2026-04-25) + +`enclosingPeer: Peer? → EnginePeer?` migration across the PeerInfo members chain. 19 edits across 3 files (`PeerInfoScreenMemberItem.swift`, `PeerInfoMembersPane.swift`, `PeerInfoProfileItems.swift`). Cross-file private struct-field migration with stored-form ratchet (wave-47 taxonomy: "cross-file private"). Closes the wave-48-pattern internal-demotion-and-external-re-promotion ratchet at PIMP:354–363, where `engine.data.subscribe(...)` produced an `EnginePeer?` that was demoted to `Peer?` for storage and then re-promoted at every consumer. + +**Edits:** + +`PeerInfoScreenMemberItem.swift` (7 edits): +- :23 — stored field `let enclosingPeer: Peer? → EnginePeer?`. +- :34 — init param `Peer? → EnginePeer?`. +- :152, :154 — `as? TelegramChannel` / `as? TelegramGroup` → `case let .channel(channel)` / `case let .legacyGroup(group)`. +- :178 — `peer: item.enclosingPeer.flatMap(EnginePeer.init)` → `peer: item.enclosingPeer` (auto-promotes to `EnginePeer?`). +- :181, :187 — `is TelegramChannel` → `case .channel = ...` (wave-41 always-false-warning fix). + +`PeerInfoMembersPane.swift` (11 edits): +- :92, :271, :442 — three func sigs `enclosingPeer: Peer → EnginePeer`. +- :113, :115 — `as? TelegramChannel` / `as? TelegramGroup` → `case let .channel(channel)` / `case let .legacyGroup(group)`. +- :139 — `peer: EnginePeer(enclosingPeer)` → `peer: enclosingPeer` (drop wrap, auto-promotes). +- :142, :148 — `is TelegramChannel` → `case .channel = ...`. +- :293 — stored field `private var enclosingPeer: Peer? → EnginePeer?`. +- :361 — `strongSelf.enclosingPeer = enclosingPeer._asPeer()` → `strongSelf.enclosingPeer = enclosingPeer`. +- :363 — `strongSelf.updateState(enclosingPeer: enclosingPeer._asPeer(), ...)` → `..., enclosingPeer: enclosingPeer, ...`. + +`PeerInfoProfileItems.swift` (1 edit): +- :852 — `enclosingPeer: peer._asPeer()` → `enclosingPeer: peer` (boundary lift; `peer` is already `EnginePeer` from the closure scope's `data.peer` post-wave-42). + +**Net bridge accounting:** +- DROPs (5): 2× `_asPeer()` demotion (PIMP:361, :363), 1× `EnginePeer(...)` wrap (PIMP:139), 1× `flatMap(EnginePeer.init)` (PSMI:178), 1× boundary `_asPeer()` lift (PSPB:852). +- ADDs (0): no new bridges. Pattern conversions (`as?` → `case let`, `is` → `case`) are not bridges; they're idiom shifts mandated by the EnginePeer enum representation. +- Pass-through call sites at PIMP:275, :276, :437, :438, :451, :485 needed no edits — types flow transparently through stored field, local var, and func params after the signature changes. + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.290s; only PeerInfoScreen + TelegramUI recompiled. Continues the wave 42/45/46/48/49 first-pass-clean streak (6 of last 8 waves first-pass-clean; wave 44 was 2 iterations, wave 47 was 2 iterations). + +**Commit:** `a1b77bcf74` (19 edits across 3 files). + +**Lessons:** + +- **The wave-48 internal-demotion ratchet pattern is reliably first-pass-clean.** When the source signal already produces the engine type and is being demoted at storage, migrating the storage to the engine type is a 1-iteration target if the consumer's only Peer-only access is through `as? TelegramX` / `is TelegramX` (mechanical conversions to `case let`/`case`). Wave 50 reproduces the wave-48 pattern verbatim and lands the same first-pass-clean outcome. + +- **Pre-flight EnginePeer-property forwarding audit confirmed unnecessary for `case let`-bound concrete types.** The `case let .channel(channel)` / `case let .legacyGroup(group)` patterns bind a `TelegramChannel` / `TelegramGroup` directly — methods like `.hasPermission(.editRank)` and `.hasBannedPermission(.banEditRank)` are class methods on the concrete type and call cleanly without `_asPeer()`. The forwarding audit in earlier waves was load-bearing for properties accessed *off the bare EnginePeer* (e.g., `peer.displayTitle` requiring an EnginePeer method); when accessed off a case-bound concrete type, no audit is needed. + +- **Bundling rationale: PSMI + PIMP + PSPB:852 share the same data-flow contour.** `PeerInfoProfileItems.swift:852` is the only producer of `PeerInfoScreenMemberItem` in the migration's scope (PSI:132 also produces but with `enclosingPeer: nil`, no edit needed). PIMP and PSMI form a tight pane–node pair. The 1-edit PSPB inclusion was non-controversial because the PSPB:852 site is the canonical Postbox→Engine boundary above the migrated chain. Boundary-lift inclusion for a 1-edit site is correct. + +- **Bundled implementer dispatch with a single subagent is the right shape for a 19-edit / 3-file mechanical wave.** Per-task subagent dispatch (1 task per file) would have triggered three implementer roundtrips and three review rounds for what is essentially one cohesive editing unit that doesn't pass build at any per-file checkpoint. The subagent-driven-development skill's "fresh subagent per task" convention adapts to "fresh subagent per cohesive editing unit" when individual tasks are not independently verifiable. + +**Plan:** `docs/superpowers/plans/2026-04-25-peerinfo-enclosingpeer-engine-peer.md`. + +--- + +## Wave 51 outcome (2026-04-25) + +`GroupsInCommonListEntry.peer: Peer → EnginePeer` migration in `PeerInfoGroupsInCommonPaneNode.swift` (single-file private struct-field). 7 edits, 1 file, **first-pass-clean**. Wave-shape: narrow internal struct-field migration with deliberate boundary scoping — public init's `openPeerContextAction: (Bool, Peer, …)` field left unmigrated to avoid cascading into `PeerInfoPaneContainerNode` (parent), `PeerInfoRecommendedPeersPaneNode` (sibling pane sharing the closure type), and upstream callers in `PeerInfoScreen.swift`. Saved for a coordinated wave once a wider closure-type sweep is justified. + +**Edits:** + +`PeerInfoGroupsInCommonPaneNode.swift` (7 edits): +- :28 — stored field `var peer: Peer → var peer: EnginePeer`. +- :35 — `lhs.peer.isEqual(rhs.peer)` → `lhs.peer == rhs.peer` (EnginePeer is `Equatable`; was Peer-protocol method). +- :42 — `item()` closure params `(Peer) -> Void` → `(EnginePeer) -> Void` and `(Peer, ASDisplayNode, ContextGesture?) -> Void` → `(EnginePeer, ASDisplayNode, ContextGesture?) -> Void`. +- :44 — `peer: EnginePeer(self.peer)` → `peer: self.peer` (drop wrap; `ItemListPeerItem.peer:` already takes `EnginePeer`). +- :54 — `preparedTransition()` closure params, same migration as :42. +- :232 — `GroupsInCommonListEntry(... peer: peer)` → `peer: EnginePeer(peer)` (boundary lift; `peer.peer` source is `RenderedPeer.peer: Peer?`). +- :236 — `self?.chatControllerInteraction.openPeer(EnginePeer(peer), …)` → `self?.chatControllerInteraction.openPeer(peer, …)` (drop wrap; `peer` is now `EnginePeer` from migrated closure param). +- :238 — `self?.openPeerContextAction(false, peer, node, gesture)` → `self?.openPeerContextAction(false, peer._asPeer(), node, gesture)` (internal bridge to still-Peer-typed stored field at PIGCP:68/109). + +**Net bridge accounting:** +- DROPs (2): 1× `EnginePeer(...)` wrap (PIGCP:44 → ItemListPeerItem), 1× `EnginePeer(...)` wrap (PIGCP:236 → chatControllerInteraction.openPeer). +- ADDs (2): 1× boundary lift `EnginePeer(peer)` (PIGCP:232; `RenderedPeer.peer: Peer?` → struct field; correct/permanent), 1× internal `_asPeer()` bridge (PIGCP:238) to the still-`Peer`-typed stored `openPeerContextAction` field. +- Net internal bridges: **−1**. Boundary lifts: **+1**. + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.454s; PeerInfoScreen + TelegramUI recompiled. Continues the first-pass-clean streak (waves 42, 45, 46, 48, 49, 50, 51 — 7 of last 9 waves first-pass-clean; waves 44 and 47 each took 2 iterations). + +**Commit:** `f2b67a1b54` (7 edits in 1 file). + +**Lessons:** + +- **Narrow-wave shape works for struct-field migrations even when the field flows into closure params.** The internal closures (`item()`, `preparedTransition()`) can be migrated independently from the public init's stored closure field, by sandwiching the still-Postbox-typed boundary at the closure that captures the stored field. This adds one `_asPeer()` bridge at the boundary but keeps the wave atomic and first-pass-clean. +- **Boundary-lift accounting clarifies wave value.** Net internal-bridge count (−1) is the correct progress metric; boundary lifts (+1) at `RenderedPeer.peer` are permanent until `RenderedPeer → EngineRenderedPeer` lands. The wave is a real ratchet step even with net-zero raw bridge count. +- **Memory-stored field name was wrong (memory said `PeerEntry`, actual was `GroupsInCommonListEntry`).** Verify struct identifiers at plan time. Doesn't affect the wave but should keep memory accurate for future planning. + +--- + +## Wave 52 outcome (2026-04-25) + +`PeerInfoPaneContainerNode.openPeerContextAction` closure parameter `Peer → EnginePeer` cascade across `PeerInfoPaneContainerNode` (PIPC), `PeerInfoGroupsInCommonPaneNode` (PIGCP), `PeerInfoRecommendedPeersPaneNode` (PIRP), and `PeerInfoScreen.swift` (PIS) — closes the wave-51 PIGCP:238 `_asPeer()` bridge and unifies the closure type across both sibling panes. 4 files, 8 type-site edits + 5 drops, **first-pass-clean**. + +**Edits:** + +- `PeerInfoPaneContainerNode.swift` (2): :411 init param closure type, :640 stored field closure type — both `(Bool, Peer, …) → (Bool, EnginePeer, …)`. +- `PeerInfoGroupsInCommonPaneNode.swift` (3): :68 stored field, :109 init param, :238 dropped `peer._asPeer()` (the wave-51 bridge — closed in this wave). +- `PeerInfoRecommendedPeersPaneNode.swift` (5): :68 inner item closure, :88 dropped `peer._asPeer()`, :94 `preparedTransition` closure, :119 stored field, :155 init param. +- `PeerInfoScreen.swift` (3): :1331 dropped `EnginePeer(peer)` wrap (`chatInterfaceInteraction.openPeer`), :1340 dropped `EnginePeer(peer)` wrap (`joinChannel(peer:)`), :1348 dropped `EnginePeer(peer)` wrap (second `chatInterfaceInteraction.openPeer`). + +**Net bridge accounting:** +- DROPs (5): 2× internal `_asPeer()` (PIGCP:238 + PIRP:88), 3× `EnginePeer(peer)` wrap (PIS:1331 / :1340 / :1348). +- ADDs (0). The four forwarding closures (PIPC:1044–1045, PIRP:273–274 / :303–304, PIS:1319) all use parameter type inference, so the closure-type cascade ripples through automatically without explicit edits at those sites. +- Net internal bridges: **−5**. No boundary lifts (the migration unifies closure types end-to-end inside the module; receiving APIs already accepted `EnginePeer`). + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel build 29.52s. Streak continues — 8 of last 10 waves first-pass-clean (42, 45, 46, 48, 49, 50, 51, 52; waves 44 and 47 each took 2 iterations). + +**Commit:** `c86aa1aba3` (4 files, 13 insertions, 13 deletions). + +**Lessons:** + +- **Closure-type cascade as a wave-shape variant of struct-field migration.** When the migrating type is a closure parameter (not a struct field), Swift's parameter-type inference at every forwarding-closure site does the cascade automatically — no explicit edits needed at type-inferred sites. Wave 52 had 5 inferred-cascade sites (PIPC:1044, PIRP:273/303, PIS:1319) and only 8 explicit type-site edits (init params + stored fields). Compare to wave 50 (struct-field cascade with 19 explicit edits). +- **Pre-flight receiving-API verification is load-bearing for closure-type migrations.** Wave 52's plan verified `chatControllerInteraction.openPeer: (EnginePeer, …)` and `joinChannel(peer: EnginePeer)` BEFORE writing the wave, ensuring the 3 PIS body wraps would drop without compensating ADDs. If either API still took raw `Peer`, the migration would have re-introduced wraps inside the closure body, defeating the wave's purpose. +- **The wave-51 deferral was the correct call.** Wave 51 explicitly noted `openPeerContextAction: (Bool, Peer, …)` would NOT migrate within that wave's scope. Wave 52 closed it cleanly with a 4-file unit. This validates the wave-47 lesson: "memory-stored wave-N+1 candidate notes are load-bearing plan input." The deferral pre-classified the candidate, and wave 52 picked it up directly. +- **Bundled implementer dispatch with Haiku confirmed for closure-type cascades.** Wave 50 established Haiku-sufficiency for mechanical wave-shape edits (19 edits). Wave 52 reproduces with 13 edits — implementer applied all edits byte-perfectly first attempt, all greps passed, build first-pass-clean. Total cost ~56k implementer tokens. + +--- + +## Wave 53 outcome (2026-04-25) + +`PeerInfoScreenData.chatPeer: Peer? → EnginePeer?` field migration with deliberately narrow scope (defers `ClearPeerHistory.init.chatPeer: Peer` and `openClearHistory.chatPeer: Peer` to a future wave). 3 files / 14 insertions / 14 deletions / **first-pass-clean** Bazel ~29.5s. + +**Edits:** + +`PeerInfoData.swift` (PSD, 6 edits): +- :387 — field decl `let chatPeer: Peer? → let chatPeer: EnginePeer?`. +- :443 — init param `chatPeer: Peer? → chatPeer: EnginePeer?`. +- :1028, :1621, :1868, :2206 — boundary lifts at PSD-internal init call sites: `chatPeer: peer → chatPeer: peer.flatMap(EnginePeer.init)` (1028), `chatPeer: peerView.peers[peerId/groupId] → chatPeer: peerView.peers[...].flatMap(EnginePeer.init)` (1621/1868/2206). Each matches the sibling `peer:` line on the same construction. + +`PeerInfoScreenOpenChat.swift` (PISOC, 2 edits): +- :25 — `chatLocation: .peer(EnginePeer(peer)) → chatLocation: .peer(peer)` (drop wrap). +- :89 — same pattern (drop wrap). + +`PeerInfoScreenPerformButtonAction.swift` (PISPBA, 6 edits): +- :428 — `if let secretChat = chatPeer as? TelegramSecretChat → if case let .secretChat(secretChat) = chatPeer`. +- :431 — `} else if let group = chatPeer as? TelegramGroup → } else if case let .legacyGroup(group) = chatPeer`. +- :435 — `} else if let user = chatPeer as? TelegramUser → } else if case let .user(user) = chatPeer`. +- :439 — `} else if let channel = chatPeer as? TelegramChannel → } else if case let .channel(channel) = chatPeer`. +- :463 — `if let channel = chatPeer as? TelegramChannel → if case let .channel(channel) = chatPeer` (separate `if`, not in the else-if chain). +- :851 — `chatPeer: chatPeer → chatPeer: chatPeer._asPeer()` (1× ADD `_asPeer()` boundary bridge for unmigrated `ClearPeerHistory.init.chatPeer: Peer`). + +**Net bridge accounting:** +- DROPs (2): `EnginePeer(peer)` wrap at PISOC:25 + PISOC:89. +- ADDs (1): `_asPeer()` at PISPBA:851 (boundary with unmigrated `ClearPeerHistory.init`). +- Boundary lifts (4): PSD:1028, :1621, :1868, :2206 — `Peer? → EnginePeer?` at the data-flow boundary into the migrated struct field. Permanent until upstream `RenderedPeer.peer / peerView.peers` migrate. +- Net internal bridges: **−1**. + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel ~29.5s. Streak: 9 of last 11 waves first-pass-clean (42, 45, 46, 48, 49, 50, 51, 52, 53; waves 44 and 47 each took 2 iterations). + +**Commit:** `438b4d7f46` (3 files, 14/14 line changes). + +**Lessons:** + +- **Narrow-scope is the right wave shape when bundling produces net-negative accounting.** Bundling wave 53 with `ClearPeerHistory.init` + `openClearHistory` migrations would have dropped 4 `EnginePeer(chatPeer)` wraps in `openClearHistory` body BUT added 4–6 `EnginePeer(...)` boundary lifts at PISPBA call sites (where `channel`/`group`/`user` Peer locals get passed). Pre-flight call-site classification turned this from "tempting bundle" into "net-negative bundle" — the deferral is correct. +- **`as? Telegram*` cluster conversion is mechanically safe even with `else if` chains.** 5 conversions in the same `.more` case body, all sharing the `chatPeer` scrutinee. Each `case let .x(y) = chatPeer` has the same scope semantics as the original `let y = chatPeer as? X` — `y` shadows nothing in the surrounding scope, control flow is preserved, and `else if` chains over EnginePeer's 4-case enum are equivalent to the original sequential `as?` casts. +- **Boundary-lift uniformity check.** Each `chatPeer:` PSD construction site already had a sibling `peer:` line one above with `peer.flatMap(EnginePeer.init)` or `peerView.peers[...].flatMap(EnginePeer.init)`. The wave-53 edits are byte-identical to the sibling pattern. Pre-flight verification ("does the sibling line use the same construction?") is the simplest signal that boundary lifts are idiomatic for the surrounding code. +- **Memory's "30+ sites / multi-iter" overcount.** Memory's wave-53 estimate captured the BUNDLED scope; narrow scope reduces to 14 sites / 1-iteration. Future memory updates should distinguish "bundled candidate site count" from "narrow candidate site count" so wave-N+1 budgets aren't anchored too high. + +--- + +## Wave 54 outcome (2026-04-25) + +`ClearPeerHistory.init.chatPeer` + `openClearHistory.chatPeer` `Peer → EnginePeer` bundled method-signature migration. Closes wave-53's deferred sibling. 2 files / 16 insertions / 16 deletions / **first-pass-clean** Bazel 31.484s. Commit `e3da090a7f`. + +**Edits:** + +`PeerInfoScreen.swift` (PIS, 10 edits): +- :3213 — `openClearHistory(... chatPeer: Peer) → ... chatPeer: EnginePeer)`. +- :3230, :3232, :3251, :3269 — drop 4 internal display-call wraps `EnginePeer(chatPeer).compactDisplayTitle → chatPeer.compactDisplayTitle` (the hot-path ratchet target). +- :7416 — `ClearPeerHistory.init(... chatPeer: Peer, cachedData:) → ... chatPeer: EnginePeer, cachedData:`. +- :7421 — `} else if chatPeer is TelegramSecretChat { → } else if case .secretChat = chatPeer {` (no-bind pattern; case body uses no fields from the secretChat). +- :7425 — `} else if let group = chatPeer as? TelegramGroup { → } else if case let .legacyGroup(group) = chatPeer {`. +- :7436 — `} else if let channel = chatPeer as? TelegramChannel { → } else if case let .channel(channel) = chatPeer {`. +- :7464 — `if let user = chatPeer as? TelegramUser, user.botInfo != nil { → if case let .user(user) = chatPeer, user.botInfo != nil {`. + +`PeerInfoScreenPerformButtonAction.swift` (PISPBA, 6 edits): +- :851 — DROP wave-53 ADD: `chatPeer: chatPeer._asPeer() → chatPeer: chatPeer`. +- :857 — boundary lift: `chatPeer: user → chatPeer: EnginePeer(user)` (TelegramUser local from `case let .user(user)` upstream). +- :1067, :1073 — `chatPeer: channel → chatPeer: EnginePeer(channel)` (TelegramChannel locals). +- :1234, :1240 — `chatPeer: group → chatPeer: EnginePeer(group)` (TelegramGroup locals). + +**Net bridge accounting:** +- DROPs (5): 4 internal `EnginePeer(chatPeer).compactDisplayTitle` wraps in PIS:openClearHistory body + 1 `_asPeer()` bridge at PISPBA:851 (the wave-53 ADD). +- ADDs (5): 5 boundary `EnginePeer(...)` lifts at PISPBA call sites. +- Conversions (4): `is`/`as?` → `case let` on PIS:7421/7425/7436/7464. +- Type-site (2): signature changes on PIS:3213 and PIS:7416. +- Net internal bridges: **0 raw count** — but the **ratchet kills 4 internal display-call wraps in the hot path** (PIS:3230/3232/3251/3269); only call-site boundary lifts remain, and those are permanent until upstream PISPBA sites get further migrated (e.g., to flow EnginePeer locals end-to-end). + +**Build outcome:** 1 iteration (first-pass-clean). Full-project Bazel 31.484s. Streak: 10 of last 12 waves first-pass-clean (42, 45, 46, 48, 49, 50, 51, 52, 53, 54; waves 44 and 47 each took 2 iterations). + +**Lessons:** + +- **Bundled deferral closure cleanly inverts the wave-53 narrow-scope decision.** Wave 53 deferred this bundle because the call-site classification at that time was "5 boundary lifts vs. 4 wrap drops = net-negative" — but the analysis missed that closing wave-53's 1 ADD is also a drop. The actual full accounting at wave-54 time: 5 drops (4 wraps + wave-53 ADD) vs. 5 lifts (1 user + 2 channel + 2 group) = 0 raw count, with ratchet benefit of 4 hot-path wraps killed. Wave-by-wave deferral followed by closure is the right shape when the bundle's first half is independently valuable (wave 53 dropped 2 PISOC wraps and migrated the field type on its own merit). +- **`case .secretChat = chatPeer` (no-bind pattern) compiles cleanly under `-warnings-as-errors`.** No "unused binding" warning because there is no binding. The original `chatPeer is TelegramSecretChat` form is structurally similar — both are predicate checks with no name to discard. +- **`peer:` parameter unused in body is fine for `-warnings-as-errors`.** `openClearHistory(... peer: Peer, ...)` body never references `peer`; this passed wave 53 and continues to pass wave 54. Function-parameter unused warnings are not enabled in this codebase's compile flags. Confirms a wave-53 implicit assumption. +- **Bundled signature migration with mechanical `as?` cluster + small call-site count = 1-iteration target.** 2 files, 16 edits, 4 case-let conversions, 5 boundary lifts, 1 wave-53-ADD drop, 4 hot-path wrap drops. Full-project Bazel completed in one shot. + +--- + +## Wave 55 outcome (2026-04-25) + +`PeerInfoScreenViewControllerNode.deletePeerChat(peer:)` private-method first-arg `Peer → EnginePeer`. 1 file / 4 edits / **first-pass-clean** Bazel 29.284s. Commit `4818a0f090`. + +**Edits:** all in `PeerInfoScreen.swift`: +- :4502 — drop `_asPeer()` at `openDeletePeer` ActionSheet button: `self?.deletePeerChat(peer: peer._asPeer(), globally: true) → self?.deletePeerChat(peer: peer, globally: true)`. +- :4556 — same drop at `openLeavePeer` TextAlertAction. +- :4564 — definition signature `peer: Peer → peer: EnginePeer`. +- :4573 — drop internal wrap inside body: `EngineRenderedPeer(peer: EnginePeer(peer)) → EngineRenderedPeer(peer: peer)` passed to `chatListController.maybeAskForPeerChatRemoval`. + +**Net:** 3 drops, 0 adds. Both call sites' `peer` is `EnginePeer` (sourced from `engine.data.get(...Item.Peer.Peer(id:))` returning `Signal`). Hot-path bridges eliminated end-to-end from delete/leave entry points down to `maybeAskForPeerChatRemoval`. Streak: 11 of last 13 first-pass-clean. + +**Lessons:** + +- **Single-file private-method migration with both call sites already EnginePeer = pure-drop wave shape.** The `_asPeer()` bridge at the call site exists *because* the receiving method was Peer-typed; once both endpoints are migrated, the bridge becomes deletable in the same wave. Pre-flight grep for `(.*\._asPeer\(\))` patterns surfaces this shape immediately. + +--- + +## Wave 56 outcome (2026-04-25) + +`PeerInfoInteraction.openPeerInfo` closure-type and `PeerInfoScreenViewControllerNode.openPeerInfo` private-method first-arg `Peer → EnginePeer` cascade. 3 files / 7 edits / **first-pass-clean** Bazel 29.074s. Commit `31433fc1d4`. + +**Edits:** + +`PeerInfoInteraction.swift` (PII, 2 edits): +- :45 — `let openPeerInfo: (Peer, Bool) -> Void → (EnginePeer, Bool) -> Void`. +- :123 — same closure type in init param. + +`PeerInfoScreen.swift` (PIS, 2 edits): +- :4304 — `private func openPeerInfo(peer: Peer, ...) → ... peer: EnginePeer`. +- :4306 — drop internal `EnginePeer(peer)` wrap (passed to `makePeerInfoController` which already takes EnginePeer post-wave-39). +- :1482 — boundary lift: `strongSelf.openPeerInfo(peer: member.peer, ...) → ... peer: EnginePeer(member.peer)` (`PeerInfoMember.peer` is still `Peer`, depends on RenderedPeer foundational migration). + +`PeerInfoProfileItems.swift` (PIPI, 2 edits): +- :524 — drop `_asPeer()` bridge: `interaction.openPeerInfo(managedByBot._asPeer(), false) → interaction.openPeerInfo(managedByBot, false)` (`managedByBot` is EnginePeer? from PSD field). +- :860 — boundary lift: `interaction.openPeerInfo(member.peer, true) → interaction.openPeerInfo(EnginePeer(member.peer), true)`. + +**Net:** 2 drops (PIS:4306 wrap + PIPI:524 _asPeer bridge), 2 boundary lifts (PIS:1482 + PIPI:860 at PeerInfoMember.peer source sites). Net raw count = 0 but 1 hot-path bridge eliminated. The 2 boundary lifts will close when `PeerInfoMember.peer` migrates (depends on RenderedPeer foundational session). Streak: 12 of last 14. + +**Lessons:** + +- **PIS:520-521 lambda forwarding `peer, isMember in self?.openPeerInfo(...)` needs no edit when the closure-field type migrates.** Swift parameter-type inference cascades from the migrated closure-field type to the lambda's parameter, and the lambda body forwards an EnginePeer to the migrated method. This is the same lesson as wave 52's PIPC.openPeerContextAction cascade — when the lambda's only role is to forward the parameter, the type re-bind is invisible at the lambda site. +- **Net-zero migrations are still worth doing when they consolidate a hot path.** Even though wave 56 has 2 drops and 2 adds, the 2 adds are at sources that will likely migrate later (PeerInfoMember.peer foundational), and the 2 drops include a hot-path `_asPeer()` ratchet at PIPI:524. The migration also makes the chain (`interaction.openPeerInfo → self.openPeerInfo → makePeerInfoController`) cleanly EnginePeer end-to-end, so future migrations can reason about it without bridge-hopping. + +--- + +## Wave 57 outcome (2026-04-25) + +Add `EnginePeer.isCopyProtectionEnabled` forwarding property in TelegramCore + drop 4 consumer-side `_asPeer().isCopyProtectionEnabled` bridges. 5 files / 5 edits / **first-pass-clean** Bazel 237.868s (cascade compile from TelegramCore touch). Commit `a5fc9fcf0e`. + +**Edits:** + +`TelegramCore/Sources/TelegramEngine/Peers/Peer.swift` (1 edit, 1 addition): +- Appended `var isCopyProtectionEnabled: Bool { return self._asPeer().isCopyProtectionEnabled }` to the existing `public extension EnginePeer { ... }` block (sibling to isDeleted, isScam, isVerified, isPremium). + +Consumer drops (4 edits across 4 files): +- `PeerInfoEditingAvatarNode.swift:166` — `peer._asPeer().isCopyProtectionEnabled → peer.isCopyProtectionEnabled` (NativeVideoContent.captureProtected). +- `PeerInfoAvatarTransformContainerNode.swift:406` — same. +- `PeerInfoData.swift:2259` — `peerInfoIsCopyProtected(data:)` body. +- `PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift:128` — `canShare = !peer.isCopyProtectionEnabled` (peer from `case let .image(_, _, _, _, peer, _, _, _, _, _, _, _) = entry`, which is `EnginePeer?`). + +**Net:** 4 internal `_asPeer()` bridge drops; 0 adds. Other `_asPeer()` calls at adjacent lines (e.g., `PeerReference(peer._asPeer())` at PIED:159 / PIATCN:404) remain — `PeerReference.init` still takes raw Postbox Peer. + +**Cascade compile:** full-project Bazel 237.868s (vs. ~30s typical) due to TelegramCore touch triggering downstream module rebuilds. Behavior parity verified by build success. + +**Lessons:** + +- **EnginePeer forwarding-extension addition is a high-leverage wave shape when ≥3 consumer sites use the same `_asPeer().` pattern.** Cost: 1 line in TelegramCore. Benefit: drops N consumer-side bridges (4 in this wave) and unlocks future call-site simplifications that pattern-match on the property. The wave-26 lesson "EnginePeer forwarding audit is load-bearing for multi-property methods" extends to "single-property forwarding is a wave shape on its own when the access-count threshold is met." +- **TelegramCore touches incur cascade-recompile cost (~210s extra wall-clock).** Worth budgeting; not a blocker. Future TelegramCore-touching waves should weight cascade time as part of wave-shape planning. +- **`EnginePeer` cases (.user/.legacyGroup/.channel/.secretChat) all preserve the underlying Postbox Peer's flag access via `_asPeer()`** — the forwarding implementation is a one-line wrapper. No case-by-case logic needed for properties that already exist on `Peer` protocol's extension. Rule of thumb: any `var ` on `extension Peer` in PeerUtils.swift can be forwarded mechanically. + +--- + +## Wave 58 outcome (2026-04-25) + +`AccountContext.openAddPeerMembers` + `presentAddMembersImpl` cross-module `groupPeer: Peer → EnginePeer` migration. 6 files / 9 edits / **2 build iterations**. Commit `261c086c15`. Bazel 180.751s (iter 1, failure with 1 error) + 29.946s (iter 2, clean) = ~210s wall-clock. + +**Edits:** + +Protocol + impl (2 edits, 2 files): +- `AccountContext.swift:1456` — protocol method `groupPeer: Peer → groupPeer: EnginePeer`. +- `SharedAccountContext.swift:2351` — implementation signature. + +`PresentAddMembers.swift` (4 edits, 1 file): +- :14 — public function signature. +- :38 — `if let group = groupPeer as? TelegramGroup → if case let .legacyGroup(group) = groupPeer`. +- :47 — `} else if let channel = groupPeer as? TelegramChannel, ... → } else if case let .channel(channel) = groupPeer, ...`. +- :210 — **iter-2 fix:** drop now-redundant `EnginePeer(groupPeer)` wrap inside body — `peer: EnginePeer(groupPeer) → peer: groupPeer` (after parameter is EnginePeer, wrapping it again doesn't compile). + +Call sites (3 edits, 3 files): +- `PeerInfoScreen.swift:4606` — drop `_asPeer()`: `groupPeer: groupPeer._asPeer() → groupPeer: groupPeer` (`groupPeer = data.peer` is EnginePeer? post-wave-42). +- `ChatListUI/Sources/ChatListController.swift:3815` — drop `_asPeer()`: `groupPeer: peer._asPeer() → groupPeer: peer` (`peer` from `engine.data.get(...Item.Peer.Peer(id:))` is EnginePeer). +- `TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift:4006` — boundary lift: `groupPeer: peer → groupPeer: EnginePeer(peer)` (`peer` from `renderedPeer?.peer` is Postbox Peer; needed because `is TelegramGroup || is TelegramChannel` pre-check still runs against the Peer-typed local). + +**Net:** 3 drops (2 `_asPeer()` at PIS:4606 + CLC:3815, 1 redundant `EnginePeer()` wrap at PAM:210), 1 boundary lift (CCLDN:4006), 2 case-let conversions in PAM body. Net internal-bridge progress: **−2**. + +**Iter-2 lesson — pre-flight scope must include "internal wraps inside the migrated function's body."** Pre-flight grep covered: +- `(...:` call sites ✓ +- ` as? Telegram*` casts ✓ +But missed: +- `EnginePeer()` wraps INSIDE the migrated body — once the parameter type changes, those become invalid. + +Adding `\b` (the parameter name) to the pre-flight grep would have caught the PAM:210 site. Cheap rule going forward: grep the body for `EnginePeer\(\)` before declaring inventory complete. + +**Lessons:** + +- **Cross-module migration with protocol method = uniform 2-iter target unless body is small.** `AccountContext` protocol method touches 3 callers + 1 impl + 1 forwarding free function with internal body. Each axis can hide one missed reference. Wave 58 hit one such miss (PAM:210); this is typical. Budget 2 iters for protocol-method migrations even when all axes look complete in inventory. +- **Boundary-lift planning is correct when the call-site's source is foundational.** CCLDN:4006 boundary lift is permanent until `RenderedPeer.peer` migrates. Adding it now is the right shape — defers the foundational migration without inflating the wave to that scope. +- **Pure cross-module migrations don't always cascade-recompile through TelegramCore.** Wave 58 touched TelegramUI consumers + AccountContext protocol but NOT TelegramCore; iter-2 rebuild was 29.9s (typical). Compare to wave 57's 237.9s when TelegramCore was touched. + +--- + +## Wave 59 outcome (2026-04-25) + +`_asPeer() as? TelegramX` micro-cluster migration. 4 files / 7 edits / **first-pass-clean** Bazel 25.833s. Commit `6ca4058ae8`. + +**Edits:** + +- `PeerInfoHeaderEditingContentNode.swift:115` — `(peer?._asPeer() as? TelegramUser)?.lastName ?? ""` → `if case let .user(user) = peer { updateText = user.lastName ?? "" } else { updateText = "" }` (preserves `?? ""` fallback semantics on cast-failure path). +- `StarsTransactionsScreen.swift:1288` — `if let channel = subscription.peer._asPeer() as? TelegramChannel { ... }` → `if case let .channel(channel) = subscription.peer { ... }`. +- `StarsTransactionScreen.swift:280` — `if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 {` → `if case let .channel(channel) = subscription.peer, channel.creationDate > 0 { additionalDate = channel.creationDate ... }`. (TelegramChannel.creationDate is non-optional Int32.) +- `ChatListSearchContainerNode.swift:927` — `if let user = message.author?._asPeer() as? TelegramUser { ... }` → `if case let .user(user) = message.author { ... }` (message.author is EnginePeer?). + +**Net:** 4 internal `_asPeer() as? TelegramX` cast bridges eliminated; 0 adds. PIHEC site adds explicit `else { updateText = "" }` to preserve original cast-failure fallback. + +**Lessons:** + +- **`_asPeer()` is a tag for "this value is EnginePeer" by construction.** `_asPeer()` only exists on `EnginePeer`; calling it on any other type (including `Peer` protocol) doesn't compile. So every consumer-side `X._asPeer()` is provably an EnginePeer source — no need to verify per-site. This invariant makes bulk `_asPeer()` cluster migrations safe. +- **Preserve cast-failure-path semantics when converting `as?` clusters.** Original `(X as? TelegramUser)?.lastName ?? ""` returns `""` if X is nil OR not TelegramUser OR lastName is nil. Naive `if case let .user(user) = X { updateText = user.lastName ?? "" }` only sets updateText for the .user case, leaving it nil for other cases. Add explicit `else` branch when the original `??` fallback is load-bearing. + +--- + +## Wave 60 outcome (2026-04-25) + +Add `PeerReference.init?(_ peer: EnginePeer)` convenience init in TelegramCore + drop **49** consumer-side `PeerReference(X._asPeer())` bridges across 29 files / 53 insertions / 49 deletions. **First-pass-clean** Bazel 238.803s (cascade compile). Commit `b6cc2bfbd1`. + +**TelegramCore extension addition:** + +`submodules/TelegramCore/Sources/ApiUtils/ApiUtils.swift` — appended `init?(_ peer: EnginePeer) { self.init(peer._asPeer()) }` to the existing `public extension PeerReference` block. + +**Consumer drops:** 49 sites where `PeerReference(X._asPeer())` becomes `PeerReference(X)`. 12 distinct expression patterns: `$0`, `author`, `bot.peer`, `component.peer`, `component.slice.effectivePeer`, `component.slice.peer`, `item.peer`, `participantPeer`, `peer`, `peerValue`, `primary.1`, `self.peer`. All EnginePeer-typed by the `_asPeer()` invariant. + +**Execution method:** Python regex replacement (`re.sub` with pattern `PeerReference\(([^)(]+)\._asPeer\(\)\)` → `PeerReference(\1)`). Non-`)`-non-`(` capture group ensures the match is the smallest expression before the trailing `._asPeer())`. Tested against all 12 distinct expression patterns including chained property accesses and closure-arg captures. + +**Net:** 49 internal `_asPeer()` bridge drops; 0 adds. Largest single-wave consumer-drop count to date. + +**Lessons:** + +- **The wave-57 forwarding-extension pattern scales to bulk init/method addition.** Convenience init/method overload in TelegramCore (1 line) drops N consumer bridges (49 in this case). Cost: 1 line + bulk consumer migration. Benefit: O(N) bridges dropped. Threshold for triggering this shape is ≥5 consumer sites (anything fewer is per-site case-by-case). +- **Python regex with `[^)(]+` capture is the safe automation primitive for migrating `Wrapper(X._asPeer())` patterns.** Greedy `.*` captures cause over-match. Non-greedy `.*?` is fragile in BSD sed. The `[^)(]+` capture explicitly avoids both `(` and `)` inside the captured expression, which works for chained property accesses (`a.b.c`), closure args (`$0`), and tuple-indexed accesses (`primary.1`). 49 sites all replaced safely. +- **The `_asPeer()` invariant guarantees migration safety.** Because `_asPeer()` only compiles on EnginePeer, no per-site type verification is needed for `Wrapper(X._asPeer()) → Wrapper(X)` migrations once the EnginePeer-accepting overload is added. + +--- + +## Wave 61 outcome (2026-04-25) + +Drop 6 `_asPeer().X` consumer-side bridges where X is already on the EnginePeer forwarding extension. 3 files / 6 edits / **first-pass-clean** Bazel 19.351s. Commit `5d497cc5e9`. + +**Edits:** + +- `ChannelVisibilityController.swift:1478` — `peer?._asPeer().usernames` → `peer?.usernames`. +- `PeerInfoCoverComponent.swift:74` — `peer._asPeer().profileColor` → `peer.profileColor`. +- `PeerInfoCoverComponent.swift:82` — `peer._asPeer().nameColor` → `peer.nameColor`. +- `StoryItemSetContainerComponent.swift:6700/6956/7274` — `component.slice.effectivePeer._asPeer().usernames` → `component.slice.effectivePeer.usernames` (replace_all=true; 3 sites). + +**Net:** 6 internal `_asPeer()` bridge drops; 0 adds. All 6 sites used properties (`usernames`, `profileColor`, `nameColor`) already present on `public extension EnginePeer` block in TelegramCore — no new TelegramCore touch needed (no cascade compile). + +**Skipped:** `peer._asPeer().indexName.indexTokens` at `ChannelDiscussionGroupSearchContainerNode.swift:157` — `EnginePeer.indexName` returns wrapper enum `EnginePeer.IndexName` without `.indexTokens`. Needs a separate IndexName extension or consumer refactor. Defer. + +--- + +## Wave 62 outcome (2026-04-25) + +Add 4 EnginePeer forwarding entries (isMonoForum, associatedPeerId, hasCustomNameColor, hasSensitiveContent) + drop 5 consumer-side `_asPeer().X` bridges. 6 files / 9 edits / **first-pass-clean** Bazel 237.769s (cascade compile). Commit `cdce0dba01`. + +**TelegramCore extension additions** (Peer.swift, 4 entries — 16 lines): + +```swift +var isMonoForum: Bool { + return self._asPeer().isMonoForum +} +var associatedPeerId: Id? { + return self._asPeer().associatedPeerId +} +var hasCustomNameColor: Bool { + return self._asPeer().hasCustomNameColor +} +func hasSensitiveContent(platform: String) -> Bool { + return self._asPeer().hasSensitiveContent(platform: platform) +} +``` + +All four forwarding implementations live in `submodules/TelegramCore/Sources/Utils/PeerUtils.swift` on the `extension Peer { ... }` block. The `Id` typealias on EnginePeer is `PeerId`, so `associatedPeerId: Id?` is type-equivalent to the underlying `Peer.associatedPeerId: PeerId?`. + +**Consumer drops** (5 sites): + +- `ThemeSettingsController.swift:428` — `accountPeer._asPeer().hasCustomNameColor` → `accountPeer.hasCustomNameColor`. +- `AgeVerificationScreen.swift:31` — `peer._asPeer().hasSensitiveContent(platform: "ios")` → `peer.hasSensitiveContent(platform: "ios")`. +- `TextProcessingScreen.swift:1281` — `peer._asPeer().isMonoForum` → `peer.isMonoForum`. +- `ShareSearchContainerNode.swift:478` — `mainPeer._asPeer().associatedPeerId` → `mainPeer.associatedPeerId`. +- `ChatListSearchListPaneNode.swift:153` — `maybeChatPeer._asPeer().associatedPeerId` → `maybeChatPeer.associatedPeerId`. + +**Net:** 5 internal bridge drops; 0 adds. + +**Lessons:** + +- **Bundle multi-property forwarding additions in a single TelegramCore touch.** When 4 properties each unblock 1-2 consumer drops, bundling the additions into one wave amortizes the cascade-recompile cost (~210s) across 5 drops instead of paying 4× across 4 separate waves. + +--- + +## Wave 63 outcome (2026-04-25) + +`resolvedAreStoriesMuted(peer:)` Peer → EnginePeer cross-module function-signature migration. 5 files / 11 edits / **first-pass-clean** Bazel 239.818s (cascade compile). Commit `499edc0ddb`. + +**TelegramCore signature change** (`ChangePeerNotificationSettings.swift`): +- :65 — `peer: Peer → peer: EnginePeer`. Body uses only `peer.id` (works on EnginePeer). +- :117 — internal call site boundary lift: `peer: peer → peer: EnginePeer(peer)` (peer source is `transaction.getPeer(peerId)` returning Postbox Peer). + +**Consumer drops** (9 sites, 4 files): +- `ContactContextMenus.swift:50` — drop `_asPeer()`. +- `ChatListController.swift:3317` — drop `_asPeer()`. +- `StoryItemSetContainerComponent.swift:7224` — drop `_asPeer()` on `component.slice.effectivePeer`. +- `StoryChatContent.swift` — 6 sites (lines 210, 212, 1287, 1599, 2490, 2492); pattern `peer: peer._asPeer(),` → `peer: peer,` via `replace_all=true`. + +**Net:** 9 drops, 1 boundary lift = **−8 internal bridges**. + +**Lessons:** + +- **Cross-module function-signature migration is a uniform 1-iter target when (a) the function body uses only EnginePeer-compatible properties and (b) all call sites have EnginePeer-typed sources via `_asPeer()`.** Pre-flight grep `.*_asPeer` enumerates all consumer-side bridges; each drop is mechanical. Internal call sites in TelegramCore that pass Postbox Peer-typed locals get a boundary lift. +- **`replace_all=true` shines when the same parameter pattern repeats in a single file.** StoryChatContent had 6 identical `peer: peer._asPeer(),` patterns — one Edit call with `replace_all=true` replaced all of them. Saves 5 round-trips. + +--- + +## Wave 64 outcome (2026-04-25) + +`RenderedPeer.convenience init(peer: EnginePeer)` in TelegramCore + 5 consumer drops. 3 files / 6 edits / **first-pass-clean** Bazel 237.725s (cascade compile). Commit `109c2fe172`. Wave-shape: foundational TelegramCore extension addition (wave-57 pattern). Original `init(peer: Peer)` retained as designated init; Swift overload resolution selects the EnginePeer init only when the argument is EnginePeer-typed. Sites: PeerInfoSettingsItems:131 + ChatListSearchListPaneNode:4173/4213/4336/4371. + +## Wave 65 outcome (2026-04-25) + +`MergedAvatarsNode.update(peers: [Peer]) → [EnginePeer]` + private `PeerAvatarReference.init(peer:)` cascade. 6 files / 9 edits / **3 iter**. Commit `37c680c86c`. Iter-2 missed `groupsInCommon: [Peer]` field at ChatUserInfoItem; iter-3 missed `recentVoterPeers: [Peer]` and `avatarPeers: [Peer]` at ChatMessagePollBubbleContentNode. Lesson: **migrating a public function's array-of-Peer parameter forces a transitive inventory of *all* `[Peer]` field declarations across consumer modules**. Pre-flight grep should include `\[Peer\]\s*=\s*\[\]` and similar local/field decls. + +## Wave 66 outcome (2026-04-25) + +`VoiceChatJoinScreen.setPeer` + `VoiceChatPreviewContentNode.init` chain Peer→EnginePeer. Single file / 5 edits / first-pass-clean. Commit `148aa53f3f`. Net **−3** internal bridges. Wave-55 single-file pure-drop pattern. + +## Wave 67 outcome (2026-04-25) + +`_internal_storedMessageFromSearchPeer` signature Peer→EnginePeer + return type Signal→Signal. 3 files / 5 edits / first-pass-clean Bazel 226.884s. Commit `3732fb66b6`. Drops `peer._asPeer()` and `|> map { EnginePeer(result) }` in `ensurePeerIsLocallyAvailable`. Net **−1** with 2 internal boundary lifts inside TelegramCore body. + +## Wave 68 outcome (2026-04-25) + +`SelectivePrivacyPeer.convenience init(peer: EnginePeer, participantCount:)` + 3 consumer drops. 4 files / 4 edits / first-pass-clean (initial 221s after TelegramCore touch + 17s consumer-only rebuild). Commit `73811a4e5d`. Original `init(peer: Peer)` and stored `peer: Peer` field unchanged — full migration of the field deferred (69 references repo-wide). + +## Wave 69 outcome (2026-04-25) + +`_internal_storedMessageFromSearchPeers` (plural) Peer→EnginePeer. Closes wave-67 sibling. 2 files / 3 edits / first-pass-clean Bazel 225.143s. Commit `42252eb9fd`. Drops `peers.map { $0._asPeer() }` in `ensurePeersAreLocallyAvailable`. + +## Wave 70 outcome (2026-04-25) + +`StatsMessageItem.peer` + `ChannelStatsController .post` enum-case payload + `MessageStatsController` local var Peer→EnginePeer cascade. 3 files / 9 edits / **3 iter**. Commit `f14dfe2273`. Iter-2 missed `entries.append(.post)` building sites at ChannelStatsController:1429/1433. Iter-3 missed `arePeersEqual(lhsPeer, rhsPeer)` Equatable comparison at L639 — replaced with `lhsPeer == rhsPeer` since EnginePeer is Equatable. **Pre-flight rule: when migrating an enum-case payload from Peer to EnginePeer, grep the same enum's `==` Equatable conformance for `arePeersEqual` calls.** Net **−5** internal bridges. + +## Wave 71 outcome (2026-04-25) + +`peerInfoControllerImpl` signature Peer→EnginePeer + drop `_asPeer()` shadow at SAC:1938. 1 file / 6 edits / first-pass-clean Bazel 24.582s. Commit `1650bc1521`. Wave-shape: single-file private-function with shadow-assignment pattern (`let peer = peer._asPeer()` shadowing the EnginePeer parameter to a Peer local). Mechanical conversion of body's 4 `as?`/`is` checks. Cheap rebuild because no public API change. + +## Wave 72 outcome (2026-04-25) + +`canSendMessagesToPeer` body cleanup: drop `_asPeer()` shadow + restructure `as?` casts as exhaustive switch on EnginePeer cases. 1 file / 1 edit / first-pass-clean Bazel 25.792s. Commit `98e7158b7a`. Wave-shape: TelegramCore-internal body cleanup. Public API was already EnginePeer (wave 38) — this wave only refactors the implementation. Cheap rebuild despite TelegramCore touch. + +## Wave 73 outcome (2026-04-25) + +`ChatQrCodeScreenImpl.Subject.peer` enum-payload Peer→EnginePeer + `QrContentNode.peer` field migration + drop `_asPeer()` shadow at SAC:2731. 2 files / 8 edits / **2 iter**. Commit `0faf4a0336`. Iter-2 missed two additional `peer as? TelegramX` casts at QrContentNode body L1742/L1744; also fixed an over-aggressive `replace_all=true` that caught a different `peer: EnginePeer(peer)` site at MessageContentNode L2184 where the local `peer` is Postbox-typed (boundary lift restored). + +**Lesson reinforced: `replace_all=true` on a parametric wrapping pattern (`peer: EnginePeer(peer)`) must verify ALL matches share the same scope/source.** When the same call appears in multiple scopes with different local-variable types, `replace_all` propagates the wrong intent. Mitigation: grep first to verify unique-scope assumption, or fall back to per-site Edit when unsure. + +--- + +## Wave 103 outcome (2026-04-26): ABANDONED + +`ChatRecentActionsControllerNode.peer: Peer → EnginePeer` (wave-71-shadow close). Implementation built and committed (`e60a8692a7`, build clean at iter-3 / 41s), then **reverted** (`git reset --hard HEAD~1`) after pre-flight failure surfaced. + +**Spec promised:** −1 boundary `_asPeer()` (CRAC:277) + −1 `import Postbox` (CRACN:5) + 0 ADD wraps. 7 edits / 2 files / 1 iter. + +**Actual outcome before revert:** −1 boundary `_asPeer()` (CRAC:277) + −1 `EnginePeer(strongSelf.peer)` wrap (CRACN:535, bonus) + 0 `import Postbox` drops (raw `Message`/`MessageId` references for `AdminLogEventAction` payloads block the drop) + **+2 ADD `_asPeer()` wraps** (CRACN:228 for `canSetupAutoremoveTimeout` Peer-protocol extension, CRACN:737 for `chatRecentActionsHistoryPreparedTransition(peer: Peer)` helper). Net wrap delta: **0**, not the promised −1. Net `import Postbox` drop: **0**, not the promised −1. + +**Why "extend in follow-up" was rejected:** Migrating `chatRecentActionsHistoryPreparedTransition(peer:)` to `EnginePeer` to drop the CRACN:737 ADD bridge would cascade into `ChatRecentActionsEntry.item(peer:)`. Inventory found 75 ADD-bridge sites in the helper body: 72 `peers[peer.id] = peer` stores into a `SimpleDictionary` (the type required by `Message(...)` constructor's `peers:` parameter) plus 3 `filterMessageChannelPeer(peer)` calls. Net cost of dropping 1 bridge: **+74 new bridges**. Catastrophic regression. + +**Pre-flight failure modes (lessons):** + +- **Pre-flight grep for `self..X` access must enumerate Peer-protocol extension methods, not just downcast patterns.** Wave-103 spec grepped `self.peer as\?` and `self.peer\.id\b` but missed `self.peer.canSetupAutoremoveTimeout(...)` at CRACN:228. `canSetupAutoremoveTimeout` is a `public extension Peer` method in `AccountContext/Sources/ChatController.swift:1013` — not on `EnginePeer`. **Mitigation:** future wave specs that retype a stored field from `Peer` to `EnginePeer` MUST grep for ALL `self..(` patterns and verify each method is reachable on `EnginePeer` (not just on the `Peer` protocol). Build a method-allowlist for `EnginePeer` from `extension EnginePeer` declarations in TelegramCore and intersect. + +- **Pre-flight grep for `` flowing into Peer-typed function parameters.** Wave-103 spec missed that `peer` was passed to `chatRecentActionsHistoryPreparedTransition(peer: Peer)` at CRACN:737. The helper sits in a different file (`ChatRecentActionsHistoryTransition.swift`) but the same submodule. **Mitigation:** grep `: peer\b|, peer:|peer: peer\b|peer:\s*self\.peer` across the entire submodule, classify each call site as (a) target accepts EnginePeer (clean), (b) target accepts Peer (would need `_asPeer()` ADD or co-migration of target), (c) target accepts `EnginePeer(peer)` wrap (clean drop). + +- **Wave-71-shadow close is NOT always cheap.** The implicit assumption ("caller already has EnginePeer; just drop the boundary `_asPeer()` and retype the storage") only holds when ALL of: (a) every `self..X` access has an EnginePeer-compatible counterpart, (b) every function call passing `self.` accepts EnginePeer, (c) no protocol-conformance constraint on the stored field exists. Wave 71 itself worked because `peerInfoControllerImpl` was a single private function with mechanical body. Wave 103 failed because the file participates in a deep helper-cascade that builds Postbox `Message` values — and `Message`'s `peers: SimpleDictionary` constructor parameter is a hard barrier. + +- **The `Message(... peers: SimpleDictionary, ...)` constructor is a hard wave barrier.** Any helper that builds Message values from a peer-typed parameter has Cat-3 ADD-bridge sites everywhere it stores the migrated peer into the peers dict. ChatRecentActionsHistoryTransition.swift has 70+ such constructions. Future waves should treat Message-building helpers as red-flagged candidates and pre-flight inventory their `peers[X.id] = X` store sites before promising any `peer: Peer → EnginePeer` migration upstream. + +- **Spec self-review must include an "ADD wraps inventory" pass.** The wave-103 spec claimed "ADD wraps: 0" but the pre-flight grep didn't actually verify this — only the cast/`is` patterns were enumerated. Future spec template: a dedicated "ADD-wrap risk grep" section listing the regex patterns checked (Peer-protocol method calls, function calls accepting `: Peer`, dictionary stores into `[PeerId: Peer]`). + +**Outcome:** wave-103 candidate marked **abandoned** in `project_postbox_refactor_next_wave.md`. Spec and plan docs retained at `docs/superpowers/specs/2026-04-26-postbox-wave-103-chat-recent-actions-controller-node-design.md` and `docs/superpowers/plans/2026-04-26-postbox-wave-103-chat-recent-actions-controller-node.md` as record of the failed attempt. + +--- + +## Wave 103 (retry) outcome (2026-04-26) + +`accountManager.mediaBox.storeResourceData(...)` Shape-A drain against the wave-94 `AccountManagerResources.storeResourceData(id:data:synchronous:)` facade. 5 sites / 2 files / 3 Edit calls (1 single + 2 `replace_all=true` batches) / **first-pass-clean** Bazel 29.5s (warm cache). Commit `92230b0691`. Sites: ThemeUpdateManager:112 (with `synchronous: true`), WallpaperResources:973+1214 (`reference.resource.id` pattern, replace_all), WallpaperResources:1260+1523 (`file.file.resource.id` pattern, replace_all). + +**Net delta:** −5 raw `mediaBox.X` accesses, +5 facade calls, +5 `EngineMediaResource.Id(...)` wraps (canonical engine-side, not Postbox bridges). + +**Lesson reinforced:** wave-shape-G drain against an existing facade is the cheapest reliable wave shape. 1-iter, ~30s build, single atomic commit. Pre-flight risk inventory takes ~5 minutes; implementation takes ~30s. Use this shape after a difficult abandonment to rebuild momentum. + +**Wave-shape-G drain after a failed wave-71-shadow:** the contrast between the abandoned wave-103 (`peer: Peer → EnginePeer` field migration with hidden 75-site cascade) and the retry (5-site call-rewrite drain) illustrates why facade-drain waves and field-migration waves are categorically different. Drains have bounded scope (only sites matching a literal text pattern); field migrations have unbounded scope (any consumer of any field-typed value). Future wave selection should prefer drains when the goal is consistent forward progress and reserve field migrations for sessions with explicit budget for cascade investigation. + +--- + +## Wave 104 outcome (2026-04-26) + +`accountManager.mediaBox.resourceData(...)` Shape-A drain (3 of 8 candidate sites) against the wave-32 / wave-94 `AccountManagerResources.data(resource:)` facade. 1 file / 6 Edit calls (3 call rewrites + 3 consumer-side `.complete` → `.isComplete` renames) / **first-pass-clean** Bazel 11.7s. Commit `08fc3f721e`. Sites: WallpaperResources:957 (`reference.resource`), :1164 (`fileReference.media.resource`), :1264 (`file.file.resource`). + +**Net delta:** −3 raw `mediaBox.X` accesses, +3 facade calls, +3 `EngineMediaResource(...)` wraps, +3 consumer field renames (`.complete` → `.isComplete` to match `EngineMediaResource.ResourceData`'s renamed field). + +**Deferred (from the original 8-site candidate set):** +- 2 sites in `FetchCachedRepresentations.swift:482, 490` — flow `data: MediaResourceData` into `fetchCachedScaledImageRepresentation` / `fetchCachedBlurredWallpaperRepresentation` (raw-`MediaResourceData`-typed `resourceData:` parameter). Migration would cascade those functions OR require boundary `MediaResourceData` reconstruction. Defer. +- 3 sites in `WallpaperResources.swift:33, 59, 401` — coupled to postbox-side via `combineLatest(accountManager.mediaBox.resourceData(X), account.postbox.mediaBox.resourceData(X))` returning typed `Signal<(MediaResourceData, MediaResourceData), NoError>`. Migrating one side without the other breaks the tuple type. Defer until postbox-side is also drainable or a paired-resource facade is designed. + +**Lesson — "Postbox-typed-function-parameter barrier" pattern (generalized):** wave 103's abandonment introduced this concept for `Message(... peers: SimpleDictionary, ...)` — any helper that builds a `Message` value forces ADD bridges at every `peers[X.id] = X` store. Wave 104 finds the same pattern at the result-type-flow level: `fetchCachedScaled*Representation(resourceData: MediaResourceData)` is a barrier that forces ADD bridges at every site whose closure flows the migrated result into it. Both are instances of "a Postbox-typed function parameter blocks upstream migration of values that flow into it." Pre-flight inventory must enumerate not just type-level uses of the migrated symbol but also the function-parameter-typed barriers it transitively flows into. + +**Lesson — "field rename at wrapper-type boundaries":** `EngineMediaResource.ResourceData.isComplete` is renamed from `MediaResourceData.complete`. Each migrated call site has a paired consumer rename. This is a small but mandatory step the spec must call out per-site; an undocumented rename would surface as a build error referencing a property that doesn't exist on the new wrapper. The rename is verifiable per-site by reading the closure body that consumes the result. + +--- + +## Wave 105 outcome (2026-04-26) + +`DeviceContactInfoSubject` enum-payload Peer? → EnginePeer? migration. 5 files / 17 edits / **first-pass-clean** Bazel 203s (foundational AccountContext touch). Commit `0c76724409`. Wave-91-pattern multi-module enum-payload + completion-callback signature migration. + +**Type changes:** 3 enum case payloads (`Peer?` → `EnginePeer?`), 2 completion-callback signatures (`(Peer?, ...) -> Void` → `(EnginePeer?, ...) -> Void`), 1 computed property (`peer: Peer?` → `peer: EnginePeer?`). All in `AccountContext.swift`. + +**Net delta:** −10 wraps dropped, +2 wraps added (Pattern E ADD bridges at Chat-side construction sites where `peerAndContactData.0` is raw `Peer?`), +1 downcast → case-let conversion. **Net wrap delta: −8.** + +Per-pattern breakdown: +- Pattern A (5 sites): `_asPeer()` drops at construction sites where source is already `EnginePeer?`. DeviceContactInfoController.swift:1289, 1443, 1489 + StoryItemSetContainerViewSendMessage.swift:2132 + OpenChatMessage.swift:443. +- Pattern B (2 sites): `_asPeer()` drops at completion-call sites. DeviceContactInfoController.swift:1105, 1224. +- Pattern C (3 sites): `.flatMap(EnginePeer.init)` simplifications when destructured `peer` is already `EnginePeer?` post-migration. DeviceContactInfoController.swift:942, 944, 946. +- Pattern D (1 site): downcast `as? TelegramUser` → `case let .user(peer)`. DeviceContactInfoController.swift:849. +- Pattern E (2 sites): ADD `.flatMap(EnginePeer.init)` wraps at construction sites where source is raw `Peer?`. ChatControllerOpenAttachmentMenu.swift:683, 1850. + +**Lesson — "thorough pre-flight inventory pays for itself":** wave 105 was the first wave-71-shadow-style (field/payload migration with cascade risk) attempted after the wave-103 abandonment forced a discipline reset. Pre-flight inventory took ~15 minutes (full 4-layer wave-71-shadow-checklist sweep, including verifying 8 destructure sites + 5 callback consumers + 3 `subject.peer` accesses + 12 construction sites across 8 files). The inventory caught the 2 ADD bridges at Chat sites that an Explore-agent pass had initially miscategorized — verified by direct grep of `peerAndContactData` source signal types. Cost-of-care: 15 minutes of pre-flight + 5 minutes of one-line spec fix vs. the wave-103 cost of a build-and-revert cycle. Recommendation: apply the same discipline to every future wave-71-shadow candidate. The inventory cost is a tiny fraction of the abandoned-wave cost. + +**Lesson — "documented ADD bridges are net-positive":** ADD bridges are not always disqualifying. The wave-71-shadow risk feedback emphasizes inventorying them, not avoiding them entirely. When the migration delivers net-negative wrap delta even after accounting for the ADD bridges (here: −10 drops vs. +2 adds = −8 net), the wave is still worth doing. The ADD bridges are also documented in the spec and commit message, so future waves migrating the upstream `peerAndContactData` source can drop them as part of that migration's natural cascade. + +--- + +## Modules currently free of `import Postbox` (running tally) + +Consumer modules that no longer import Postbox, across all waves and standalone commits: + +- `ChatInterfaceState` (wave 1) +- `ChatSendMessageActionUI` (wave 1) +- `ContactListUI` (wave 1) +- `DrawingUI` (wave 1) +- `StickerPeekUI` (standalone cleanup, 2026-04-17 — import was unused) +- `PromptUI` (standalone cleanup) +- `PresentationDataUtils` (standalone cleanup) +- `MapResourceToAvatarSizes` (wave 2) +- `SaveToCameraRoll` (wave 3) +- `SecureIdVerificationDocumentsContext` (wave 5) +- **Wave 6 batch: 189 additional modules** — see `git show 7b2b74e79b --stat` for the commit that swept unused `import Postbox` lines across 183 files in 16 consumer submodules. Not individually enumerated here for brevity. +- `StorageUsageScreen` (waves 8–10) +- `ActionSheetPeerItem` (wave 11; revisits wave-1 abandonment) +- `HorizontalPeerItem` (wave 12; applies wave-11 pattern) +- `SelectablePeerNode` (wave 15; applies wave-11 pattern; ShareExtension-boundary stateManager fallback) +- `ItemListAvatarAndNameInfoItem` (wave 17; applies wave-11 pattern; ShareExtension-boundary stateManager fallback) +- `ItemListStickerPackItem` (wave 18; mixed-shape — 3 narrow TelegramCore typealiases + wave-4 enum-payload migration + wave-3 facade swap) +- `AttachmentTextInputPanelNode` BUILD cleanup (wave 13; source was already clean from wave 6) +- **Wave 14 BUILD-dep sweep: 98 modules' BUILDs cleaned** — same modules as the wave-6 batch; this sweep fixed their leftover `//submodules/Postbox:Postbox` BUILD deps. Candidate list in `/tmp/postbox-dep-candidates.txt` at commit time; derivable by the script in "Wave 14 outcome". diff --git a/docs/superpowers/scratch/reveal-pacing-sim.py b/docs/superpowers/scratch/reveal-pacing-sim.py new file mode 100644 index 0000000000..ed4454b48e --- /dev/null +++ b/docs/superpowers/scratch/reveal-pacing-sim.py @@ -0,0 +1,473 @@ +#!/usr/bin/env python3 +""" +Synthetic simulator for TextRevealController. + +Mirrors the algorithm in +submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/ +Sources/ChatMessageTextBubbleContentNode.swift verbatim (`TextRevealController`). + +Iterate here before changing the Swift code. To port a change back, update +TextRevealController in the .swift file with the same edits. + +Usage: + python3 docs/superpowers/scratch/reveal-pacing-sim.py [scenario] + scenario = bursty | speed-change | big-chunk | finalize-backlog | + llm-stream | all (default: all) +""" + +from __future__ import annotations + +import sys +from dataclasses import dataclass +from typing import List, Optional, Tuple + + +# --------------------------------------------------------------------------- +# Controller — must stay byte-for-byte equivalent to the Swift implementation. +# --------------------------------------------------------------------------- + +CONTROLLER_NAME = "v1" # set by --algo flag in main() + + +class TextRevealController: + """V1 — current Swift implementation. + + Maintains an EWMA chars/sec input rate and reveals at + target_velocity = input_rate + max(0, lag - input_rate*headroom) / response_time + Works for dense streams (small chunks, high arrival rate), fails for + bursty/sparse streams (reveal burns through each chunk too quickly, + then idles until the next chunk lands).""" + + HEADROOM_TIME = 0.4 + RESPONSE_TIME = 0.3 + VELOCITY_TAU = 0.15 + RATE_EWMA_ALPHA = 0.4 + INITIAL_INPUT_RATE = 40.0 + MIN_INTER_ARRIVAL = 0.05 + FINALIZE_TIME = 0.3 + FRAME_DT_CAP = 0.05 + + def __init__(self, initial_revealed_count: int, initial_length: int) -> None: + self.revealed_count: float = float(initial_revealed_count) + self.velocity: float = 0.0 + self.input_rate: float = self.INITIAL_INPUT_RATE + self.last_sample_time: Optional[float] = None + self.last_sample_length: Optional[int] = None + self.latest_length: int = initial_length + self.is_finalizing: bool = False + self.last_frame_time: Optional[float] = None + + @property + def current_glyph_count(self) -> int: + return int(self.revealed_count) + + def observe_update(self, latest_length: int, now: float) -> None: + if self.last_sample_length is not None: + last_len = self.last_sample_length + if latest_length > last_len: + if self.last_sample_time is not None: + dt = max(now - self.last_sample_time, self.MIN_INTER_ARRIVAL) + sample_rate = (latest_length - last_len) / dt + self.input_rate = ( + self.RATE_EWMA_ALPHA * sample_rate + + (1.0 - self.RATE_EWMA_ALPHA) * self.input_rate + ) + self.last_sample_time = now + self.last_sample_length = latest_length + elif latest_length < last_len: + self.last_sample_length = latest_length + else: + self.last_sample_time = now + self.last_sample_length = latest_length + self.latest_length = latest_length + if self.revealed_count > float(latest_length): + self.revealed_count = float(latest_length) + + def finalize(self, final_length: int) -> None: + self.latest_length = final_length + self.is_finalizing = True + if self.revealed_count > float(final_length): + self.revealed_count = float(final_length) + + def tick(self, now: float) -> Tuple[int, bool]: + last_frame = self.last_frame_time if self.last_frame_time is not None else now + dt = min(now - last_frame, self.FRAME_DT_CAP) + lag = max(0.0, float(self.latest_length) - self.revealed_count) + if self.is_finalizing: + target_velocity = max(self.velocity, lag / self.FINALIZE_TIME) + else: + target_lag = self.input_rate * self.HEADROOM_TIME + excess = max(0.0, lag - target_lag) + target_velocity = self.input_rate + excess / self.RESPONSE_TIME + smoothing = min(1.0, dt / self.VELOCITY_TAU) + self.velocity += (target_velocity - self.velocity) * smoothing + self.revealed_count = min( + float(self.latest_length), + self.revealed_count + self.velocity * dt, + ) + self.last_frame_time = now + is_complete = ( + self.is_finalizing and self.revealed_count >= float(self.latest_length) + ) + return int(self.revealed_count), is_complete + + +class TextRevealControllerV2: + """V2 — expected-next-arrival pacing. + + Tracks the EWMA of inter-arrival times between chunks. On each tick, + aims to finish revealing the remaining lag by the predicted arrival + time of the next chunk: + target_velocity = lag / max(MIN_GAP, predicted_next_arrival - now) + For steady streams (chunks every T seconds, ΔC chars each), this + converges to lag/T ≈ continuous flow rate, with no burst-then-idle.""" + + VELOCITY_TAU = 0.12 # slightly snappier than v1 since target is steadier + GAP_EWMA_ALPHA = 0.4 + INITIAL_GAP = 0.5 # used until 2 chunks have arrived + MIN_PREDICTED_GAP = 0.10 # floor on time-to-next (final-burst regime) + FINALIZE_TIME = 0.3 + FRAME_DT_CAP = 0.05 + INITIAL_INPUT_RATE = 40.0 # fallback velocity for the first chunk + # When predicted_next_arrival has passed (stream stalled), don't speed up + # further — clamp time_to_next at this minimum. + STALL_FLOOR = 0.10 + + def __init__(self, initial_revealed_count: int, initial_length: int) -> None: + self.revealed_count: float = float(initial_revealed_count) + self.velocity: float = 0.0 + self.avg_inter_arrival: float = self.INITIAL_GAP + self.last_sample_time: Optional[float] = None + self.last_sample_length: Optional[int] = None + self.predicted_next_arrival_time: Optional[float] = None + self.chunk_count: int = 0 + self.latest_length: int = initial_length + self.is_finalizing: bool = False + self.last_frame_time: Optional[float] = None + # Display-only: track the most-recent observed input rate for tracing. + self.input_rate: float = self.INITIAL_INPUT_RATE + + @property + def current_glyph_count(self) -> int: + return int(self.revealed_count) + + def observe_update(self, latest_length: int, now: float) -> None: + if self.last_sample_length is not None: + last_len = self.last_sample_length + if latest_length > last_len: + if self.last_sample_time is not None: + inter_arrival = max(now - self.last_sample_time, 0.001) + self.avg_inter_arrival = ( + self.GAP_EWMA_ALPHA * inter_arrival + + (1.0 - self.GAP_EWMA_ALPHA) * self.avg_inter_arrival + ) + # Display-only rate for tracing. + self.input_rate = (latest_length - last_len) / inter_arrival + self.last_sample_time = now + self.last_sample_length = latest_length + self.predicted_next_arrival_time = now + self.avg_inter_arrival + self.chunk_count += 1 + elif latest_length < last_len: + self.last_sample_length = latest_length + else: + self.last_sample_time = now + self.last_sample_length = latest_length + self.predicted_next_arrival_time = now + self.avg_inter_arrival + self.chunk_count += 1 + self.latest_length = latest_length + if self.revealed_count > float(latest_length): + self.revealed_count = float(latest_length) + + def finalize(self, final_length: int) -> None: + self.latest_length = final_length + self.is_finalizing = True + if self.revealed_count > float(final_length): + self.revealed_count = float(final_length) + + def tick(self, now: float) -> Tuple[int, bool]: + last_frame = self.last_frame_time if self.last_frame_time is not None else now + dt = min(now - last_frame, self.FRAME_DT_CAP) + lag = max(0.0, float(self.latest_length) - self.revealed_count) + if self.is_finalizing: + target_velocity = max(self.velocity, lag / self.FINALIZE_TIME) + elif self.chunk_count < 2 or self.predicted_next_arrival_time is None: + # Bootstrap: not enough samples to predict inter-arrival rhythm. + # Cruise at the initial rate (matches legacy behavior for the first + # chunk; subsequent chunks switch to predicted-arrival pacing). + target_velocity = self.INITIAL_INPUT_RATE if lag > 0 else 0.0 + else: + time_to_next = max(self.STALL_FLOOR, self.predicted_next_arrival_time - now) + target_velocity = lag / time_to_next + smoothing = min(1.0, dt / self.VELOCITY_TAU) + self.velocity += (target_velocity - self.velocity) * smoothing + self.revealed_count = min( + float(self.latest_length), + self.revealed_count + self.velocity * dt, + ) + self.last_frame_time = now + is_complete = ( + self.is_finalizing and self.revealed_count >= float(self.latest_length) + ) + return int(self.revealed_count), is_complete + + +def make_controller(initial_revealed_count: int, initial_length: int): + if CONTROLLER_NAME == "v2": + return TextRevealControllerV2(initial_revealed_count, initial_length) + return TextRevealController(initial_revealed_count, initial_length) + + +def compute_target_velocity_for_trace(controller, lag: float) -> float: + """Mirror the controller's target-velocity math without mutating state.""" + if isinstance(controller, TextRevealControllerV2): + if controller.is_finalizing: + return max(controller.velocity, lag / controller.FINALIZE_TIME) + if controller.predicted_next_arrival_time is None: + return controller.INITIAL_INPUT_RATE if lag > 0 else 0.0 + # Recover "now" from last_frame_time (caller passes it). + # We compute against last_frame_time which is updated by tick already. + # For tracing purposes, callers should pass the same now used in tick. + raise RuntimeError("Use compute_target_velocity_with_now for v2") + # v1 + if controller.is_finalizing: + return max(controller.velocity, lag / controller.FINALIZE_TIME) + target_lag = controller.input_rate * controller.HEADROOM_TIME + excess = max(0.0, lag - target_lag) + return controller.input_rate + excess / controller.RESPONSE_TIME + + +def compute_target_velocity_with_now(controller, lag: float, now: float) -> float: + if isinstance(controller, TextRevealControllerV2): + if controller.is_finalizing: + return max(controller.velocity, lag / controller.FINALIZE_TIME) + if controller.chunk_count < 2 or controller.predicted_next_arrival_time is None: + return controller.INITIAL_INPUT_RATE if lag > 0 else 0.0 + time_to_next = max( + controller.STALL_FLOOR, + controller.predicted_next_arrival_time - now, + ) + return lag / time_to_next + return compute_target_velocity_for_trace(controller, lag) + + +# --------------------------------------------------------------------------- +# Test driver +# --------------------------------------------------------------------------- + +@dataclass +class Event: + timestamp: float + kind: str # "chunk" or "finalize" + length: int # cumulative draft text length at this event + + +def run_scenario( + name: str, + events: List[Event], + max_duration: float, + fps: int = 60, + trace_every_n_frames: int = 6, # ~10 lines/sec at 60fps +) -> None: + print(f"\n=== {name} ===") + print(f"events: {len(events)}, fps: {fps}, max duration: {max_duration}s") + print() + + controller = None + frame_dt = 1.0 / fps + t = 0.0 + event_idx = 0 + last_traced_frame = -trace_every_n_frames + frame_count = 0 + last_int_reveal = -1 + + header = f"{'t':>7s} {'reveal':>7s} {'latest':>7s} {'v':>6s} {'target':>7s} {'rate':>6s} {'lag':>6s} {'mode':>5s}" + print(header) + print("-" * len(header)) + + while t <= max_duration: + # Apply any events whose timestamp has elapsed. + while event_idx < len(events) and events[event_idx].timestamp <= t + 1e-9: + ev = events[event_idx] + if controller is None and ev.kind == "chunk": + controller = make_controller( + initial_revealed_count=0, initial_length=ev.length + ) + print(f"[{t:6.3f}s] CREATE initial_length={ev.length} algo={CONTROLLER_NAME}") + if controller is not None: + if ev.kind == "chunk": + prev_rate = controller.input_rate + prev_len = controller.last_sample_length + controller.observe_update(ev.length, t) + prev_len_str = "nil" if prev_len is None else str(prev_len) + print( + f"[{t:6.3f}s] CHUNK " + f"len={prev_len_str}→{ev.length} " + f"input_rate={prev_rate:.1f}→{controller.input_rate:.1f}" + ) + elif ev.kind == "finalize": + controller.finalize(ev.length) + print( + f"[{t:6.3f}s] FINALIZE " + f"final_length={ev.length} " + f"revealed={controller.revealed_count:.1f} " + f"lag={controller.latest_length - controller.revealed_count:.1f}" + ) + event_idx += 1 + + if controller is None: + t += frame_dt + frame_count += 1 + continue + + # Recompute the target/lag for trace output (controller.tick does it + # internally but doesn't expose them). Mirror the math for the active algo. + lag_for_trace = max(0.0, controller.latest_length - controller.revealed_count) + target_v = compute_target_velocity_with_now(controller, lag_for_trace, t) + + revealed, complete = controller.tick(t) + + # Trace every N frames OR whenever the integer reveal advanced. + should_trace = ( + (frame_count - last_traced_frame >= trace_every_n_frames) + or (revealed != last_int_reveal) + ) + if should_trace: + mode = "FIN" if controller.is_finalizing else "RUN" + print( + f"{t:7.3f} {controller.revealed_count:7.1f} {controller.latest_length:7d} " + f"{controller.velocity:6.1f} {target_v:7.1f} " + f"{controller.input_rate:6.1f} {lag_for_trace:6.1f} {mode:>5s}" + ) + last_traced_frame = frame_count + last_int_reveal = revealed + + if complete: + print(f"[{t:6.3f}s] COMPLETE") + break + t += frame_dt + frame_count += 1 + + print() + + +# --------------------------------------------------------------------------- +# Scenarios +# --------------------------------------------------------------------------- + +def scenario_bursty() -> None: + """LLM model emits 20 chars every 1 second for 10 chunks, then finalize. + + What we want: reveal flows continuously at ~20 chars/sec, lag stays roughly + constant. + What "bursty rhythm" failure looks like: reveal sprints after each chunk + then idles (visible v drops to 0 between chunks).""" + events: List[Event] = [] + length = 0 + for i in range(10): + length += 20 + events.append(Event(timestamp=(i + 1) * 1.0, kind="chunk", length=length)) + events.append(Event(timestamp=11.0, kind="finalize", length=length)) + run_scenario("Bursty: 20 chars / 1s × 10", events, max_duration=12.5) + + +def scenario_speed_change() -> None: + """Three slow chunks (10c every 1s) then three fast chunks (50c every 0.5s).""" + events: List[Event] = [] + length = 0 + t = 0.0 + for _ in range(3): + t += 1.0 + length += 10 + events.append(Event(timestamp=t, kind="chunk", length=length)) + for _ in range(3): + t += 0.5 + length += 50 + events.append(Event(timestamp=t, kind="chunk", length=length)) + events.append(Event(timestamp=t + 1.5, kind="finalize", length=length)) + run_scenario("Speed change: slow then fast", events, max_duration=8.0) + + +def scenario_big_chunk() -> None: + """One 200-char chunk all at once. Should reveal gradually, not in <0.5s.""" + events = [ + Event(timestamp=0.0, kind="chunk", length=200), + Event(timestamp=8.0, kind="finalize", length=200), + ] + run_scenario("Big-chunk shock: 200 chars at once", events, max_duration=10.0) + + +def scenario_finalize_backlog() -> None: + """Stream ends while reveal still far behind. Should decelerate to final ≤0.3s.""" + events = [ + Event(timestamp=0.0, kind="chunk", length=50), + Event(timestamp=0.5, kind="chunk", length=100), + Event(timestamp=1.0, kind="chunk", length=150), + Event(timestamp=1.5, kind="finalize", length=200), + ] + run_scenario("Finalize with 200-char backlog", events, max_duration=3.5) + + +def scenario_llm_stream() -> None: + """Plausible LLM streaming: ~30 chars/sec average, chunks of 3-8 chars every + ~150ms. Some jitter.""" + import random + random.seed(42) + events: List[Event] = [] + length = 0 + t = 0.0 + for _ in range(60): + dt = max(0.05, random.gauss(0.15, 0.04)) + delta = max(1, int(random.gauss(5, 1.5))) + t += dt + length += delta + events.append(Event(timestamp=t, kind="chunk", length=length)) + events.append(Event(timestamp=t + 1.0, kind="finalize", length=length)) + run_scenario( + f"LLM stream: 60 small chunks, ~30 c/s, final length {length}", + events, + max_duration=t + 3.0, + ) + + +def scenario_sparse() -> None: + """Very sparse: 30 chars every 2 seconds. Stresses the headroom-vs-gap mismatch.""" + events: List[Event] = [] + length = 0 + for i in range(5): + length += 30 + events.append(Event(timestamp=(i + 1) * 2.0, kind="chunk", length=length)) + events.append(Event(timestamp=12.0, kind="finalize", length=length)) + run_scenario("Sparse: 30 chars / 2s × 5", events, max_duration=13.0) + + +SCENARIOS = { + "bursty": scenario_bursty, + "speed-change": scenario_speed_change, + "big-chunk": scenario_big_chunk, + "finalize-backlog": scenario_finalize_backlog, + "llm-stream": scenario_llm_stream, + "sparse": scenario_sparse, +} + + +if __name__ == "__main__": + # Args: [scenario] [--algo v1|v2] + args = sys.argv[1:] + if "--algo" in args: + i = args.index("--algo") + algo = args[i + 1] + if algo not in ("v1", "v2"): + print(f"Unknown algo: {algo}; must be v1 or v2") + sys.exit(1) + CONTROLLER_NAME = algo + del args[i : i + 2] + arg = args[0] if args else "all" + if arg == "all": + for fn in SCENARIOS.values(): + fn() + elif arg in SCENARIOS: + SCENARIOS[arg]() + else: + print(f"Unknown scenario: {arg}") + print(f"Available: {', '.join(SCENARIOS.keys())}, all") + sys.exit(1) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 356a456045..860d6c897a 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -51,7 +50,7 @@ public final class TelegramApplicationBindings { public let displayNotification: (String) -> Void public let applicationInForeground: Signal public let applicationIsActive: Signal - public let clearMessageNotifications: ([MessageId]) -> Void + public let clearMessageNotifications: ([EngineMessage.Id]) -> Void public let pushIdleTimerExtension: () -> Disposable public let openSettings: () -> Void public let openAppStorePage: () -> Void @@ -67,7 +66,7 @@ public final class TelegramApplicationBindings { public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void public let forceOrientation: (UIInterfaceOrientation) -> Void - public init(isMainApp: Bool, appBundleId: String, appBuildType: TelegramAppBuildType, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, openSubscriptions: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) { + public init(isMainApp: Bool, appBundleId: String, appBuildType: TelegramAppBuildType, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([EngineMessage.Id]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, openSubscriptions: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) { self.isMainApp = isMainApp self.appBundleId = appBundleId self.appBuildType = appBuildType @@ -111,18 +110,18 @@ public enum TextLinkItem: Equatable { public final class AccountWithInfo: Equatable { public let account: Account - public let peer: Peer - - public init(account: Account, peer: Peer) { + public let peer: EnginePeer + + public init(account: Account, peer: EnginePeer) { self.account = account self.peer = peer } - + public static func ==(lhs: AccountWithInfo, rhs: AccountWithInfo) -> Bool { if lhs.account !== rhs.account { return false } - if !arePeersEqual(lhs.peer, rhs.peer) { + if lhs.peer != rhs.peer { return false } return true @@ -131,7 +130,7 @@ public final class AccountWithInfo: Equatable { public enum OpenURLContext { case generic - case chat(peerId: PeerId, message: Message?, updatedPresentationData: (initial: PresentationData, signal: Signal)?) + case chat(peerId: EnginePeer.Id, message: EngineRawMessage?, updatedPresentationData: (initial: PresentationData, signal: Signal)?) case external } @@ -162,14 +161,14 @@ public struct ChatAvailableMessageActionOptions: OptionSet { public struct ChatAvailableMessageActions { public var options: ChatAvailableMessageActionOptions - public var banAuthor: Peer? - public var banAuthors: [Peer] + public var banAuthor: EnginePeer? + public var banAuthors: [EnginePeer] public var disableDelete: Bool public var isCopyProtected: Bool public var setTag: Bool public var editTags: Set - - public init(options: ChatAvailableMessageActionOptions, banAuthor: Peer?, banAuthors: [Peer], disableDelete: Bool, isCopyProtected: Bool, setTag: Bool, editTags: Set) { + + public init(options: ChatAvailableMessageActionOptions, banAuthor: EnginePeer?, banAuthors: [EnginePeer], disableDelete: Bool, isCopyProtected: Bool, setTag: Bool, editTags: Set) { self.options = options self.banAuthor = banAuthor self.banAuthors = banAuthors @@ -186,6 +185,22 @@ public enum WallpaperUrlParameter { case gradient([UInt32], Int32?) } +public enum PeerType: Equatable { + case user(isBot: Bool) + case group + case channel + + public static func getType(for peer: EnginePeer) -> PeerType { + if case let .user(user) = peer { + return .user(isBot: user.botInfo != nil) + } else if case let .channel(channel) = peer, case .broadcast = channel.info { + return .channel + } else { + return .group + } + } +} + public struct ResolvedBotChoosePeerTypes: OptionSet { public var rawValue: UInt32 @@ -225,6 +240,7 @@ public struct ResolvedBotAdminRights: OptionSet { public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512) public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024) public static let manageChat = ResolvedBotAdminRights(rawValue: 2048) + public static let manageTopics = ResolvedBotAdminRights(rawValue: 4096) public var chatAdminRights: TelegramChatAdminRightsFlags? { var flags = TelegramChatAdminRightsFlags() @@ -259,6 +275,9 @@ public struct ResolvedBotAdminRights: OptionSet { if self.contains(ResolvedBotAdminRights.canBeAnonymous) { flags.insert(.canBeAnonymous) } + if self.contains(ResolvedBotAdminRights.manageTopics) { + flags.insert(.canManageTopics) + } if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) { return nil @@ -287,14 +306,14 @@ public enum ResolvedBotStartPeerType { public enum ResolvedUrl { case externalUrl(String) case urlAuth(String) - case peer(Peer?, ChatControllerInteractionNavigateToPeer) + case peer(EngineRawPeer?, ChatControllerInteractionNavigateToPeer) case inaccessiblePeer - case botStart(peer: Peer, payload: String) - case groupBotStart(peerId: PeerId, payload: String, adminRights: ResolvedBotAdminRights?, peerType: ResolvedBotStartPeerType?) - case gameStart(peerId: PeerId, game: String) - case channelMessage(peer: Peer, messageId: MessageId, timecode: Double?) - case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId) - case replyThread(messageId: MessageId) + case botStart(peer: EngineRawPeer, payload: String) + case groupBotStart(peerId: EnginePeer.Id, payload: String, adminRights: ResolvedBotAdminRights?, peerType: ResolvedBotStartPeerType?) + case gameStart(peerId: EnginePeer.Id, game: String) + case channelMessage(peer: EngineRawPeer, messageId: EngineMessage.Id, timecode: Double?) + case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: EngineMessage.Id) + case replyThread(messageId: EngineMessage.Id) case stickerPack(name: String, type: StickerPackUrlType) case instantView(TelegramMediaWebpage, String?) case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?) @@ -306,15 +325,15 @@ public enum ResolvedUrl { case share(url: String?, text: String?, to: String?) case wallpaper(WallpaperUrlParameter) case theme(String) - case joinVoiceChat(PeerId, String?) + case joinVoiceChat(EnginePeer.Id, String?) case importStickers - case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?) + case startAttach(peerId: EnginePeer.Id, payload: String?, choose: ResolvedBotChoosePeerTypes?) case invoice(slug: String, invoice: TelegramMediaInvoice?) case premiumOffer(reference: String?) case starsTopup(amount: Int64?, purpose: String?) case chatFolder(slug: String) - case story(peerId: PeerId, id: Int32) - case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) + case story(peerId: EnginePeer.Id, id: Int32) + case boost(peerId: EnginePeer.Id?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) case premiumGiftCode(slug: String) case premiumMultiGift(reference: String?) case auction(auction: GiftAuctionContext?) @@ -322,12 +341,13 @@ public enum ResolvedUrl { case stars case ton case shareStory(Int64) - case storyFolder(peerId: PeerId, id: Int64) - case giftCollection(peerId: PeerId, id: Int64) - case sendGift(peerId: PeerId?) + case storyFolder(peerId: EnginePeer.Id, id: Int64) + case giftCollection(peerId: EnginePeer.Id, id: Int64) + case sendGift(peerId: EnginePeer.Id?) case unknownDeepLink(path: String) case oauth(url: String) - case createBot(parentBot: PeerId, username: String?, title: String?) + case createBot(parentBot: EnginePeer.Id, username: String?, title: String?) + case textStyle(style: TelegramComposeAIMessageMode.CloudStyle.Custom, initialPreview: AIMessageStylePreview?) public enum ResolvedCollectible { case gift(StarGift.UniqueGift) @@ -387,6 +407,16 @@ public enum ResolveUrlResult { case result(ResolvedUrl) } +public struct OpenUserGeneratedUrlConcealedAlertOption { + public let title: String + public let action: () -> Void + + public init(title: String, action: @escaping () -> Void) { + self.title = title + self.action = action + } +} + public enum NavigateToChatKeepStack { case `default` case always @@ -403,18 +433,6 @@ public final class ChatPeekTimeout { } } -public final class ChatPeerNearbyData: Equatable { - public static func == (lhs: ChatPeerNearbyData, rhs: ChatPeerNearbyData) -> Bool { - return lhs.distance == rhs.distance - } - - public let distance: Int32 - - public init(distance: Int32) { - self.distance = distance - } -} - public final class ChatGreetingData: Equatable { public static func == (lhs: ChatGreetingData, rhs: ChatGreetingData) -> Bool { return lhs.uuid == rhs.uuid @@ -432,7 +450,7 @@ public final class ChatGreetingData: Equatable { public enum ChatSearchDomain: Equatable { case everything case members - case member(Peer) + case member(EngineRawPeer) case tag(MessageReaction.Reaction) public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool { @@ -466,7 +484,7 @@ public enum ChatSearchDomain: Equatable { } public enum ChatLocation: Equatable { - case peer(id: PeerId) + case peer(id: EnginePeer.Id) case replyThread(message: ChatReplyThreadMessage) case customChatContents } @@ -481,7 +499,7 @@ public extension ChatLocation { } } - var peerId: PeerId? { + var peerId: EnginePeer.Id? { switch self { case let .peer(peerId): return peerId @@ -582,12 +600,11 @@ public final class NavigateToChatControllerParams { public let scrollToEndIfExists: Bool public let activateMessageSearch: (ChatSearchDomain, String)? public let peekData: ChatPeekTimeout? - public let peerNearbyData: ChatPeerNearbyData? public let reportReason: NavigateToChatControllerParams.ReportReason? public let animated: Bool public let forceAnimatedScroll: Bool public let options: NavigationAnimationOptions - public let parentGroupId: PeerGroupId? + public let parentGroupId: EnginePeerGroupId? public let chatListFilter: Int32? public let chatNavigationStack: [ChatNavigationStackItem] public let changeColors: Bool @@ -618,12 +635,11 @@ public final class NavigateToChatControllerParams { scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, - peerNearbyData: ChatPeerNearbyData? = nil, reportReason: NavigateToChatControllerParams.ReportReason? = nil, animated: Bool = true, forceAnimatedScroll: Bool = false, options: NavigationAnimationOptions = [], - parentGroupId: PeerGroupId? = nil, + parentGroupId: EnginePeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, @@ -653,7 +669,6 @@ public final class NavigateToChatControllerParams { self.scrollToEndIfExists = scrollToEndIfExists self.activateMessageSearch = activateMessageSearch self.peekData = peekData - self.peerNearbyData = peerNearbyData self.reportReason = reportReason self.animated = animated self.forceAnimatedScroll = forceAnimatedScroll @@ -691,7 +706,6 @@ public final class NavigateToChatControllerParams { scrollToEndIfExists: self.scrollToEndIfExists, activateMessageSearch: self.activateMessageSearch, peekData: self.peekData, - peerNearbyData: self.peerNearbyData, reportReason: self.reportReason, animated: self.animated, forceAnimatedScroll: self.forceAnimatedScroll, @@ -712,11 +726,11 @@ public final class NavigateToChatControllerParams { } public enum DeviceContactInfoSubject { - case vcard(Peer?, DeviceContactStableId?, DeviceContactExtendedData) - case filter(peer: Peer?, contactId: DeviceContactStableId?, contactData: DeviceContactExtendedData, completion: (Peer?, DeviceContactExtendedData) -> Void) - case create(peer: Peer?, contactData: DeviceContactExtendedData, isSharing: Bool, shareViaException: Bool, completion: (Peer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) - - public var peer: Peer? { + case vcard(EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData) + case filter(peer: EnginePeer?, contactId: DeviceContactStableId?, contactData: DeviceContactExtendedData, completion: (EnginePeer?, DeviceContactExtendedData) -> Void) + case create(peer: EnginePeer?, contactData: DeviceContactExtendedData, isSharing: Bool, shareViaException: Bool, completion: (EnginePeer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) + + public var peer: EnginePeer? { switch self { case let .vcard(peer, _, _): return peer @@ -746,10 +760,9 @@ public enum PeerInfoControllerMode { } case generic - case calls(messages: [Message]) - case nearbyPeer(distance: Int32) - case group(PeerId) - case reaction(MessageId) + case calls(messages: [EngineMessage]) + case group(sourceMessageId: EngineMessage.Id) + case reaction(EngineMessage.Id) case forumTopic(thread: ChatReplyThreadMessage) case recommendedChannels case myProfile @@ -821,7 +834,7 @@ public enum ChatListSearchFilter: Equatable { case music case voice case instantVideo - case peer(PeerId, Bool, String, String) + case peer(EnginePeer.Id, Bool, String, String) case date(Int32?, Int32, String) case publicPosts @@ -1318,7 +1331,15 @@ public final class TextProcessingScreenSendContextActions { public enum TextProcessingScreenMode { case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: TextProcessingScreenSendContextActions?) - case translate(fromLanguage: String?) + case translate(fromLanguage: String?, applyResult: ((TextWithEntities) -> Void)?) + case preview(style: TelegramComposeAIMessageMode.CloudStyle.Custom, authorPeer: EnginePeer?, initialPreview: AIMessageStylePreview?, isAlreadyAdded: Bool, added: () -> Void) +} + +public enum EmojiStatusSelectionControllerMode { + case statusSelection + case backgroundSelection(completion: (TelegramMediaFile?) -> Void) + case customStatusSelection(completion: (TelegramMediaFile?, Int32?) -> Void) + case quickReactionSelection(completion: () -> Void) } public protocol SharedAccountContext: AnyObject { @@ -1364,17 +1385,16 @@ public protocol SharedAccountContext: AnyObject { func updateNotificationTokensRegistration() func setAccountUserInterfaceInUse(_ id: AccountRecordId) -> Disposable - func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) + func handleTextLinkAction(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) func openSearch(filter: ChatListSearchFilter, query: String?) - func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?) + func navigateToChat(accountId: AccountRecordId, peerId: EnginePeer.Id, messageId: EngineMessage.Id?) func openChatMessage(_ params: OpenChatMessageParams) -> Bool - func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic, tag: HistoryViewInputTag?) -> Signal<(MessageIndex?, Bool), NoError> + func messageFromPreloadedChatHistoryViewForLocation(id: EngineMessage.Id, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, chatLocationContextHolder: Atomic, tag: EngineHistoryViewInputTag?) -> Signal<(EngineMessage.Index?, Bool), NoError> - func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController - func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? - func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController? + func makeOverlayAudioPlayerController(context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, initialMessageId: EngineMessage.Id, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController + func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? + func makeChannelAdminController(context: AccountContext, peerId: EnginePeer.Id, adminId: EnginePeer.Id, initialParticipant: ChannelParticipant) -> ViewController? func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController - func makePeersNearbyController(context: AccountContext) -> ViewController func makeComposeController(context: AccountContext) -> ViewController func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode, params: ChatControllerParams?) -> ChatController @@ -1383,32 +1403,32 @@ public protocol SharedAccountContext: AnyObject { updatedPresentationData: (initial: PresentationData, signal: Signal), chatLocation: ChatLocation, chatLocationContextHolder: Atomic, - tag: HistoryViewInputTag?, + tag: EngineHistoryViewInputTag?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteractionProtocol, - selectedMessages: Signal?, NoError>, + selectedMessages: Signal?, NoError>, mode: ChatHistoryListMode ) -> ChatHistoryListNode func subscribeChatListData(context: AccountContext, location: ChatListControllerLocation) -> Signal - 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: ((UIView?, CGPoint?) -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: Peer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool, rank: String?, rankRole: ChatRankInfoScreenRole?) -> ListViewItem + func makeChatMessagePreviewItem(context: AccountContext, messages: [EngineRawMessage], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((EngineRawMessage) -> Void)?, clickThroughMessage: ((UIView?, CGPoint?) -> Void)?, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, accountPeer: EngineRawPeer?, isCentered: Bool, isPreview: Bool, isStandalone: Bool, rank: String?, rankRole: ChatRankInfoScreenRole?) -> ListViewItem func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader - func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: Peer, message: Message, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader - func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController? + func makeChatMessageAvatarHeaderItem(context: AccountContext, timestamp: Int32, peer: EngineRawPeer, message: EngineRawMessage, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader + func makePeerSharedMediaController(context: AccountContext, peerId: EnginePeer.Id) -> ViewController? func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController func makeContactMultiselectionController(_ params: ContactMultiselectionControllerParams) -> ContactMultiselectionController func makePeerSelectionController(_ params: PeerSelectionControllerParams) -> PeerSelectionController func makeProxySettingsController(context: AccountContext) -> ViewController func makeLocalizationListController(context: AccountContext) -> ViewController - func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController - func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController + func makeCreateGroupController(context: AccountContext, peerIds: [EnginePeer.Id], initialTitle: String?, mode: CreateGroupMode, completion: ((EnginePeer.Id, @escaping () -> Void) -> Void)?) -> ViewController + func makeChatRecentActionsController(context: AccountContext, peer: EnginePeer, adminPeerId: EnginePeer.Id?, starsState: StarsRevenueStats?) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func makeBioPrivacyController(context: AccountContext, settings: Promise, present: @escaping (ViewController) -> Void) func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController func makeStorageManagementController(context: AccountContext) -> ViewController func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void) -> AttachmentFileController - func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, pushViewController: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? + func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, pushViewController: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, getNavigationController: @escaping () -> NavigationController?) -> NSObject? func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController @@ -1438,15 +1458,16 @@ public protocol SharedAccountContext: AnyObject { func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal func chatControllerForForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal func openStorageUsage(context: AccountContext) - func openLocationScreen(context: AccountContext, messageId: MessageId, navigationController: NavigationController) + func openLocationScreen(context: AccountContext, messageId: EngineMessage.Id, navigationController: NavigationController) func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, keepUpdated: Bool) -> Signal func chatAvailableMessageActions(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineMessage], peers: [EnginePeer.Id: EnginePeer]) -> Signal - func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal - func resolveUrlWithProgress(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal - func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) + func resolveUrl(context: AccountContext, peerId: EnginePeer.Id?, url: String, skipUrlAuth: Bool) -> Signal + func resolveUrlWithProgress(context: AccountContext, peerId: EnginePeer.Id?, url: String, skipUrlAuth: Bool) -> Signal + func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((EnginePeer.Id, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) + func openUserGeneratedUrl(context: AccountContext, peerId: EnginePeer.Id?, url: String, webpage: TelegramMediaWebpage?, concealed: Bool, forceConcealed: Bool, skipUrlAuth: Bool, skipConcealedAlert: Bool, forceDark: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise?, alertDisplayUpdated: ((ViewController?) -> Void)?, concealedAlertOption: OpenUserGeneratedUrlConcealedAlertOption?) -> Disposable func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) - func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) + func openAddPersonContact(context: AccountContext, peerId: EnginePeer.Id, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void) func displaySetPhoto( @@ -1456,12 +1477,12 @@ public protocol SharedAccountContext: AnyObject { completion: @escaping (UIImage?) -> Void, completedWithUploadingImage: @escaping (UIImage, Signal) -> UIView? ) - func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) - func makeInstantPageController(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? + func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: EnginePeer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) + func makeInstantPageController(context: AccountContext, message: EngineRawMessage, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? func makeInstantPageController(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController - func openChatWallpaper(context: AccountContext, message: Message, present: @escaping (ViewController, Any?) -> Void) + func openChatWallpaper(context: AccountContext, message: EngineRawMessage, present: @escaping (ViewController, Any?) -> Void) func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController - func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController + func makeChatQrCodeScreen(context: AccountContext, peer: EnginePeer, threadId: Int64?, temporary: Bool) -> ViewController func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController @@ -1472,14 +1493,14 @@ public protocol SharedAccountContext: AnyObject { func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController - func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController + func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: (([StickerPackScreenActionResult]) -> Void)?) -> ViewController func makeCameraScreen(context: AccountContext, mode: CameraScreenMode, cameraHolder: Any?, transitionIn: CameraScreenTransitionIn?, transitionOut: @escaping (Bool) -> CameraScreenTransitionOut?, completion: @escaping (Any, @escaping () -> Void) -> Void, transitionedOut: (() -> Void)?) -> ViewController func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, remainingCount: Int32, completion: @escaping ([MediaEditorScreenResult], MediaEditorTransitionOutExternalState, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerEditorScreen(context: AccountContext, source: Any?, mode: StickerEditorMode, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController - func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) + func makeAvatarMediaPickerScreen(context: AccountContext, peerType: PeerType, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController @@ -1487,6 +1508,7 @@ public protocol SharedAccountContext: AnyObject { func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController func makeChannelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?) -> ViewController func makeMessagesStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, messageId: EngineMessage.Id) -> ViewController + func makePollStatsScreen(context: AccountContext, messageId: EngineMessage.Id) -> ViewController func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController func makeStarsTransactionsScreen(context: AccountContext, starsContext: StarsContext) -> ViewController func makeStarsPurchaseScreen(context: AccountContext, starsContext: StarsContext, options: [Any], purpose: StarsPurchasePurpose, targetPeerId: EnginePeer.Id?, customTheme: PresentationTheme?, completion: @escaping (Int64) -> Void) -> ViewController @@ -1524,6 +1546,7 @@ public protocol SharedAccountContext: AnyObject { func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?) + func openJoinChatWebView(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, webView: JoinChatWebView) func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController @@ -1533,7 +1556,7 @@ public protocol SharedAccountContext: AnyObject { func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController func makeCocoonInfoScreen(context: AccountContext) -> ViewController - func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController + func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?, TelegramMediaWebpage?) -> Void) -> ViewController @available(iOS 13.0, *) func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController @@ -1574,6 +1597,7 @@ public protocol SharedAccountContext: AnyObject { openAutomatically: Bool, completion: @escaping (EnginePeer.Id?) -> Void ) async -> ViewController? + func makeEmojiStatusSelectionController(context: AccountContext, mode: EmojiStatusSelectionControllerMode, sourceView: UIView, emojiContent: Signal, currentSelection: Int64?, color: UIColor?, destinationItemView: @escaping () -> UIView?) -> ViewController func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } @@ -1639,6 +1663,23 @@ public protocol ChatSendMessageActionSheetController: ViewController { typealias SendParameters = ChatSendMessageActionSheetControllerSendParameters } +public enum StickerPackScreenActionKind { + case add + case remove(positionInList: Int) +} + +public struct StickerPackScreenActionResult { + public let info: StickerPackCollectionInfo + public let items: [StickerPackItem] + public let action: StickerPackScreenActionKind + + public init(info: StickerPackCollectionInfo, items: [StickerPackItem], action: StickerPackScreenActionKind) { + self.info = info + self.items = items + self.action = action + } +} + public protocol AccountContext: AnyObject { var sharedContext: SharedAccountContext { get } var account: Account { get } @@ -1681,15 +1722,15 @@ public protocol AccountContext: AnyObject { func storeSecureIdPassword(password: String) func getStoredSecureIdPassword() -> String? - func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> ChatLocationInput - func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic) -> Signal + func chatLocationInput(for location: ChatLocation, contextHolder: Atomic) -> EngineChatLocationInput + func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic) -> Signal func chatLocationUnreadCount(for location: ChatLocation, contextHolder: Atomic) -> Signal - func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: MessageIndex) - - func scheduleGroupCall(peerId: PeerId, parentController: ViewController) - func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription) + func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic, messageIndex: EngineMessage.Index) + + func scheduleGroupCall(peerId: EnginePeer.Id, parentController: ViewController) + func joinGroupCall(peerId: EnginePeer.Id, invite: String?, requestJoinAsPeerId: ((@escaping (EnginePeer.Id?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription) func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool, unmuteByDefault: Bool) - func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void) + func requestCall(peerId: EnginePeer.Id, isVideo: Bool, completion: @escaping () -> Void) } public struct AntiSpamBotConfiguration { diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 12b6591828..09231d3c95 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import TextFormat import AsyncDisplayKit import Display @@ -66,6 +65,9 @@ public final class ChatMessageItemAssociatedData: Equatable { public let showSensitiveContent: Bool public let isSuspiciousPeer: Bool public let showTextAsPlaceholder: Bool + public let accountCountry: String? + public let isParticipant: Bool + public let invitedOn: Int32? public init( automaticDownloadPeerType: MediaAutoDownloadPeerType, @@ -102,7 +104,10 @@ public final class ChatMessageItemAssociatedData: Equatable { isInline: Bool = false, showSensitiveContent: Bool = false, isSuspiciousPeer: Bool = false, - showTextAsPlaceholder: Bool = false + showTextAsPlaceholder: Bool = false, + accountCountry: String? = nil, + isParticipant: Bool = false, + invitedOn: Int32? = nil ) { self.automaticDownloadPeerType = automaticDownloadPeerType self.automaticDownloadPeerId = automaticDownloadPeerId @@ -139,6 +144,9 @@ public final class ChatMessageItemAssociatedData: Equatable { self.showSensitiveContent = showSensitiveContent self.isSuspiciousPeer = isSuspiciousPeer self.showTextAsPlaceholder = showTextAsPlaceholder + self.accountCountry = accountCountry + self.isParticipant = isParticipant + self.invitedOn = invitedOn } public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool { @@ -235,6 +243,15 @@ public final class ChatMessageItemAssociatedData: Equatable { if lhs.isSuspiciousPeer != rhs.isSuspiciousPeer { return false } + if lhs.accountCountry != rhs.accountCountry { + return false + } + if lhs.isParticipant != rhs.isParticipant { + return false + } + if lhs.invitedOn != rhs.invitedOn { + return false + } return true } } @@ -247,6 +264,23 @@ public extension ChatMessageItemAssociatedData { return false } } + + func isPollVotingRestricted(poll: TelegramMediaPoll, accountTestingEnvironment: Bool, currentTimestamp: Int32) -> Bool { + if !poll.countries.isEmpty, let accountCountry = self.accountCountry, !poll.countries.contains(accountCountry) { + return true + } + + if poll.restrictToSubscribers { + let period: Int32 = accountTestingEnvironment ? 5 * 60 : 24 * 60 * 60 + if !self.isParticipant { + return true + } else if let invitedOn = self.invitedOn, invitedOn + period > currentTimestamp { + return true + } + } + + return false + } } public enum ChatControllerInteractionLongTapAction { @@ -799,7 +833,7 @@ public enum ChatControllerSubject: Equatable { } } - case tag(MessageTags) + case tag(EngineMessage.Tags) case message(id: MessageSubject, highlight: MessageHighlight?, timecode: Double?, setupReply: Bool) case scheduledMessages case pinnedMessages(id: EngineMessage.Id?) @@ -985,7 +1019,7 @@ public enum PeerInfoAvatarUploadStatus { } public protocol PeerInfoScreen: ViewController { - var peerId: PeerId { get } + var peerId: EnginePeer.Id { get } var privacySettings: Promise { get } var twoStepAuthData: Promise { get } var notificationExceptions: Promise { get } @@ -1003,7 +1037,7 @@ public protocol PeerInfoScreen: ViewController { func updateProfileVideo(_ image: UIImage, video: Any?, values: Any?, markup: UploadPeerPhotoMarkup?) } -public extension Peer { +public extension EngineRawPeer { func canSetupAutoremoveTimeout(accountPeerId: EnginePeer.Id) -> Bool { if let _ = self as? TelegramSecretChat { return false @@ -1099,6 +1133,7 @@ public protocol ChatController: ViewController { func activateSearch(domain: ChatSearchDomain, query: String) func activateInput(type: ChatControllerActivateInput) func beginClearHistory(type: InteractiveHistoryClearingType) + func presentReactionDeletionOptions(author: EnginePeer, messageId: EngineMessage.Id) func performScrollToTop() -> Bool func transferScrollingVelocity(_ velocity: CGFloat) @@ -1138,19 +1173,19 @@ public enum FileMediaResourceMediaStatus: Equatable { public protocol ChatMessageItemNodeProtocol: ListViewItemNode { func makeProgress() -> Promise? func targetReactionView(value: MessageReaction.Reaction) -> UIView? - func targetForStoryTransition(id: StoryId) -> UIView? + func targetForStoryTransition(id: EngineStoryId) -> UIView? func contentFrame() -> CGRect - func matchesMessage(id: MessageId) -> Bool + func matchesMessage(id: EngineMessage.Id) -> Bool func cancelInsertionAnimations() - func messages() -> [Message] + func messages() -> [EngineRawMessage] func updateHiddenMedia() } public final class ChatControllerNavigationData: CustomViewControllerNavigationData { - public let peerId: PeerId + public let peerId: EnginePeer.Id public let threadId: Int64? - - public init(peerId: PeerId, threadId: Int64?) { + + public init(peerId: EnginePeer.Id, threadId: Int64?) { self.peerId = peerId self.threadId = threadId } @@ -1193,8 +1228,8 @@ public enum ChatHistoryListSource { } case `default` - case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId?, quote: Quote?, isSavedMusic: Bool, canReorder: Bool, loadMore: (() -> Void)?) - case customView(historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError>) + case custom(messages: Signal<([EngineRawMessage], Int32, Bool), NoError>, messageId: EngineMessage.Id?, quote: Quote?, isSavedMusic: Bool, canReorder: Bool, loadMore: (() -> Void)?) + case customView(historyView: Signal<(EngineRawMessageHistoryView, EngineViewUpdateType), NoError>) } public enum ChatQuickReplyShortcutType { @@ -1211,7 +1246,7 @@ public enum ChatCustomContentsKind: Equatable { public protocol ChatCustomContentsProtocol: AnyObject { var kind: ChatCustomContentsKind { get } - var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { get } + var historyView: Signal<(EngineRawMessageHistoryView, EngineViewUpdateType), NoError> { get } var messageLimit: Int? { get } func enqueueMessages(messages: [EnqueueMessage]) @@ -1251,7 +1286,7 @@ public protocol ChatHistoryListNode: ListView { func scrollToEndOfHistory() func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) - func messageInCurrentHistoryView(_ id: MessageId) -> Message? + func messageInCurrentHistoryView(_ id: EngineMessage.Id) -> EngineMessage? var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set } } diff --git a/submodules/AccountContext/Sources/ChatHistoryLocation.swift b/submodules/AccountContext/Sources/ChatHistoryLocation.swift index 2ace788c81..795a0ee6f7 100644 --- a/submodules/AccountContext/Sources/ChatHistoryLocation.swift +++ b/submodules/AccountContext/Sources/ChatHistoryLocation.swift @@ -1,11 +1,10 @@ import Foundation -import Postbox import Display import TelegramCore public enum ChatHistoryInitialSearchLocation: Equatable { - case index(MessageIndex) - case id(MessageId) + case index(EngineMessage.Index) + case id(EngineMessage.Id) } public struct MessageHistoryScrollToSubject: Equatable { @@ -19,12 +18,12 @@ public struct MessageHistoryScrollToSubject: Equatable { } } - public var index: MessageHistoryAnchorIndex + public var index: EngineMessageHistoryAnchorIndex public var quote: Quote? public var subject: EngineMessageReplyInnerSubject? public var setupReply: Bool - public init(index: MessageHistoryAnchorIndex, quote: Quote? = nil, subject: EngineMessageReplyInnerSubject? = nil, setupReply: Bool = false) { + public init(index: EngineMessageHistoryAnchorIndex, quote: Quote? = nil, subject: EngineMessageReplyInnerSubject? = nil, setupReply: Bool = false) { self.index = index self.quote = quote self.subject = subject @@ -57,8 +56,8 @@ public struct MessageHistoryInitialSearchSubject: Equatable { public enum ChatHistoryLocation: Equatable { case Initial(count: Int) case InitialSearch(subject: MessageHistoryInitialSearchSubject, count: Int, highlight: Bool, setupReply: Bool) - case Navigation(index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, highlight: Bool) - case Scroll(subject: MessageHistoryScrollToSubject, anchorIndex: MessageHistoryAnchorIndex, sourceIndex: MessageHistoryAnchorIndex, scrollPosition: ListViewScrollPosition, animated: Bool, highlight: Bool, setupReply: Bool) + case Navigation(index: EngineMessageHistoryAnchorIndex, anchorIndex: EngineMessageHistoryAnchorIndex, count: Int, highlight: Bool) + case Scroll(subject: MessageHistoryScrollToSubject, anchorIndex: EngineMessageHistoryAnchorIndex, sourceIndex: EngineMessageHistoryAnchorIndex, scrollPosition: ListViewScrollPosition, animated: Bool, highlight: Bool, setupReply: Bool) } public struct ChatHistoryLocationInput: Equatable { diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index 54d5cb44ad..f655ac50ef 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -1,7 +1,6 @@ import Foundation import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData @@ -9,7 +8,7 @@ public protocol ContactSelectionController: ViewController { var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get } var displayProgress: Bool { get set } var dismissed: (() -> Void)? { get set } - var presentScheduleTimePicker: (@escaping (Int32, Int32?) -> Void) -> Void { get set } + var presentScheduleTimePicker: (@escaping (Int32, Int32?, Bool) -> Void) -> Void { get set } func dismissSearch() } @@ -47,7 +46,7 @@ public struct ContactListAdditionalOption: Equatable { } public enum ContactListPeerId: Hashable { - case peer(PeerId) + case peer(EnginePeer.Id) case deviceContact(DeviceContactStableId) } @@ -59,9 +58,9 @@ public enum ContactListAction: Equatable { } public enum ContactListPeer: Equatable { - case peer(peer: Peer, isGlobal: Bool, participantCount: Int32?) + case peer(peer: EnginePeer, isGlobal: Bool, participantCount: Int32?) case deviceContact(DeviceContactStableId, DeviceContactBasicData) - + public var id: ContactListPeerId { switch self { case let .peer(peer, _, _): @@ -70,8 +69,8 @@ public enum ContactListPeer: Equatable { return .deviceContact(id) } } - - public var indexName: PeerIndexNameRepresentation { + + public var indexName: EnginePeer.IndexName { switch self { case let .peer(peer, _, _): return peer.indexName @@ -79,11 +78,11 @@ public enum ContactListPeer: Equatable { return .personName(first: contact.firstName, last: contact.lastName, addressNames: [], phoneNumber: "") } } - + public static func ==(lhs: ContactListPeer, rhs: ContactListPeer) -> Bool { switch lhs { case let .peer(lhsPeer, lhsIsGlobal, lhsParticipantCount): - if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer.isEqual(rhsPeer), lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { + if case let .peer(rhsPeer, rhsIsGlobal, rhsParticipantCount) = rhs, lhsPeer == rhsPeer, lhsIsGlobal == rhsIsGlobal, lhsParticipantCount == rhsParticipantCount { return true } else { return false diff --git a/submodules/AccountContext/Sources/FetchManager.swift b/submodules/AccountContext/Sources/FetchManager.swift index 39ca3de10c..a705710c9c 100644 --- a/submodules/AccountContext/Sources/FetchManager.swift +++ b/submodules/AccountContext/Sources/FetchManager.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences @@ -13,7 +12,7 @@ public enum FetchManagerCategory: Int32 { } public enum FetchManagerLocationKey: Comparable, Hashable { - case messageId(MessageId) + case messageId(EngineMessage.Id) case free public static func <(lhs: FetchManagerLocationKey, rhs: FetchManagerLocationKey) -> Bool { @@ -87,7 +86,7 @@ public struct FetchManagerPriorityKey: Comparable { } public enum FetchManagerLocation: Hashable, CustomStringConvertible { - case chat(PeerId) + case chat(EnginePeer.Id) public var description: String { switch self { @@ -104,8 +103,8 @@ public enum FetchManagerForegroundDirection { public enum FetchManagerPriority: Comparable { case userInitiated - case foregroundPrefetch(direction: FetchManagerForegroundDirection, localOrder: MessageIndex) - case backgroundPrefetch(locationOrder: HistoryPreloadIndex, localOrder: MessageIndex) + case foregroundPrefetch(direction: FetchManagerForegroundDirection, localOrder: EngineMessage.Index) + case backgroundPrefetch(locationOrder: HistoryPreloadIndex, localOrder: EngineMessage.Index) public static func <(lhs: FetchManagerPriority, rhs: FetchManagerPriority) -> Bool { switch lhs { @@ -160,11 +159,11 @@ public protocol FetchManager { var queue: Queue { get } func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, mediaReference: AnyMediaReference?, resourceReference: MediaResourceReference, ranges: RangeSet, statsCategory: MediaResourceStatsCategory, elevatedPriority: Bool, userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerId: EnginePeer.Id?) -> Signal - func cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) + func cancelInteractiveFetches(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: EngineRawMediaResource) func cancelInteractiveFetches(resourceId: String) func toggleInteractiveFetchPaused(resourceId: String, isPaused: Bool) func raisePriority(resourceId: String) - func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: MediaResource) -> Signal + func fetchStatus(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, resource: EngineRawMediaResource) -> Signal } public protocol PrefetchManager { diff --git a/submodules/AccountContext/Sources/FetchMediaUtils.swift b/submodules/AccountContext/Sources/FetchMediaUtils.swift index d23937221a..b969890fbb 100644 --- a/submodules/AccountContext/Sources/FetchMediaUtils.swift +++ b/submodules/AccountContext/Sources/FetchMediaUtils.swift @@ -24,10 +24,6 @@ public func freeMediaFileResourceInteractiveFetched(postbox: Postbox, userLocati return fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(resource), range: range) } -public func cancelFreeMediaFileInteractiveFetch(account: Account, file: TelegramMediaFile) { - account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) -} - private func fetchCategoryForFile(_ file: TelegramMediaFile) -> FetchManagerCategory { if file.isVoice || file.isInstantVideo { return .voice @@ -78,7 +74,8 @@ public func messageMediaFileStatus(context: AccountContext, messageId: MessageId var thumbnailStatus: Signal = .single(nil) if let videoThumbnail = file.videoThumbnails.first { - thumbnailStatus = context.account.postbox.mediaBox.resourceStatus(videoThumbnail.resource) + thumbnailStatus = context.engine.resources.status(resource: EngineMediaResource(videoThumbnail.resource)) + |> map { $0._asStatus() } |> map(Optional.init) } diff --git a/submodules/AccountContext/Sources/GalleryController.swift b/submodules/AccountContext/Sources/GalleryController.swift index a18a697c26..02b81f7088 100644 --- a/submodules/AccountContext/Sources/GalleryController.swift +++ b/submodules/AccountContext/Sources/GalleryController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import TelegramCore @@ -10,12 +9,13 @@ public enum GalleryMediaSubject: Hashable { case pollDescription case pollOption(Data) case pollSolution + case instantPageMedia(EngineMedia.Id) } public enum GalleryControllerItemSource { - case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, customTag: MemoryBuffer?, chatLocationContextHolder: Atomic) - case standaloneMessage(Message, GalleryMediaSubject?) - case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?) + case peerMessagesAtId(messageId: EngineMessage.Id, chatLocation: ChatLocation, customTag: EngineMemoryBuffer?, chatLocationContextHolder: Atomic) + case standaloneMessage(EngineRawMessage, GalleryMediaSubject?) + case custom(messages: Signal<([EngineRawMessage], Int32, Bool), NoError>, messageId: EngineMessage.Id, loadMore: (() -> Void)?) } public final class GalleryControllerActionInteraction { @@ -25,10 +25,10 @@ public final class GalleryControllerActionInteraction { public let openPeer: (EnginePeer) -> Void public let openHashtag: (String?, String) -> Void public let openBotCommand: (String) -> Void - public let openAd: (MessageId) -> Void + public let openAd: (EngineMessage.Id) -> Void public let addContact: (String) -> Void - public let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void - public let editMedia: (MessageId, [UIView], @escaping () -> Void) -> Void + public let storeMediaPlaybackState: (EngineMessage.Id, Double?, Double) -> Void + public let editMedia: (EngineMessage.Id, [UIView], @escaping () -> Void) -> Void public let updateCanReadHistory: (Bool) -> Void public let sendSticker: ((FileMediaReference) -> Void)? @@ -39,10 +39,10 @@ public final class GalleryControllerActionInteraction { openPeer: @escaping (EnginePeer) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, - openAd: @escaping (MessageId) -> Void, + openAd: @escaping (EngineMessage.Id) -> Void, addContact: @escaping (String) -> Void, - storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, - editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void, + storeMediaPlaybackState: @escaping (EngineMessage.Id, Double?, Double) -> Void, + editMedia: @escaping (EngineMessage.Id, [UIView], @escaping () -> Void) -> Void, updateCanReadHistory: @escaping (Bool) -> Void, sendSticker: ((FileMediaReference) -> Void)? ) { diff --git a/submodules/AccountContext/Sources/IsMediaStreamable.swift b/submodules/AccountContext/Sources/IsMediaStreamable.swift index 34e9a2a029..109f3c138c 100644 --- a/submodules/AccountContext/Sources/IsMediaStreamable.swift +++ b/submodules/AccountContext/Sources/IsMediaStreamable.swift @@ -1,10 +1,9 @@ import Foundation -import Postbox import TelegramCore private let minimalStreamableSize: Int = 384 * 1024 -public func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Bool { +public func isMediaStreamable(message: EngineMessage, media: TelegramMediaFile) -> Bool { if message.containsSecretMedia { return false } @@ -51,7 +50,7 @@ public func isMediaStreamable(media: TelegramMediaFile) -> Bool { return false } -public func isMediaStreamable(resource: MediaResource) -> Bool { +public func isMediaStreamable(resource: TelegramMediaResource) -> Bool { if let size = resource.size, size >= minimalStreamableSize { return true } else { diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 66c1bbec49..e4dbd1e68c 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import UIKit @@ -9,10 +8,10 @@ import UniversalMediaPlayer import RangeSet public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { - case peer(PeerId) - case recentActions(PeerId) + case peer(EnginePeer.Id) + case recentActions(EnginePeer.Id) case feed(Int32) - case savedMusic(PeerId) + case savedMusic(EnginePeer.Id) case custom public func isEqual(to: SharedMediaPlaylistId) -> Bool { @@ -24,11 +23,11 @@ public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId { } public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation { - case messages(chatLocation: ChatLocation, tagMask: MessageTags, at: MessageId) - case singleMessage(MessageId) - case recentActions(Message) + case messages(chatLocation: ChatLocation, tagMask: EngineMessage.Tags, at: EngineMessage.Id) + case singleMessage(EngineMessage.Id) + case recentActions(EngineRawMessage) case savedMusic(context: ProfileSavedMusicContext, at: Int32, canReorder: Bool) - case custom(messages: Signal<([Message], Int32, Bool), NoError>, canReorder: Bool, at: MessageId, loadMore: (() -> Void)?, hidePanel: Bool) + case custom(messages: Signal<([EngineRawMessage], Int32, Bool), NoError>, canReorder: Bool, at: EngineMessage.Id, loadMore: (() -> Void)?, hidePanel: Bool) public var playlistId: PeerMessagesMediaPlaylistId { switch self { @@ -62,14 +61,14 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: savedMusicContext.peerId)) ) |> map { state, peer in - var messages: [Message] = [] - var peers = SimpleDictionary() + var messages: [EngineRawMessage] = [] + var peers = EngineSimpleDictionary() if let peer { peers[peerId] = peer._asPeer() } for file in state.files { let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max)) - messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) + messages.append(EngineRawMessage(stableId: stableId, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) } var canLoadMore = false if case let .ready(canLoadMoreValue) = state.dataState { @@ -78,7 +77,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation return (messages, Int32(messages.count), canLoadMore) }, canReorder: canReorder, - at: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: at), + at: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Local, id: at), loadMore: { [weak savedMusicContext] in guard let savedMusicContext else { return @@ -92,7 +91,7 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation } } - public var messageId: MessageId? { + public var messageId: EngineMessage.Id? { switch self { case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, _, messageId, _, _): return messageId @@ -231,7 +230,7 @@ public protocol MediaManager: AnyObject { } public enum GalleryHiddenMediaId: Hashable { - case chat(AccountRecordId, MessageId, Media) + case chat(AccountRecordId, EngineMessage.Id, EngineRawMedia) public static func ==(lhs: GalleryHiddenMediaId, rhs: GalleryHiddenMediaId) -> Bool { switch lhs { @@ -254,7 +253,7 @@ public enum GalleryHiddenMediaId: Hashable { } public protocol GalleryHiddenMediaTarget: AnyObject { - func getTransitionInfo(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? + func getTransitionInfo(messageId: EngineMessage.Id, media: EngineMedia) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? } public protocol GalleryHiddenMediaManager: AnyObject { @@ -263,7 +262,7 @@ public protocol GalleryHiddenMediaManager: AnyObject { func removeSource(_ index: Int) func addTarget(_ target: GalleryHiddenMediaTarget) func removeTarget(_ target: GalleryHiddenMediaTarget) - func findTarget(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? + func findTarget(messageId: EngineMessage.Id, media: EngineMedia) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? } public protocol UniversalVideoManager: AnyObject { diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index 43f9bf0caf..68b45fe0ed 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -22,9 +21,9 @@ public final class OpenChatMessageParams { public let context: AccountContext public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let chatLocation: ChatLocation? - public let chatFilterTag: MemoryBuffer? + public let chatFilterTag: EngineMemoryBuffer? public let chatLocationContextHolder: Atomic? - public let message: Message + public let message: EngineRawMessage public let mediaSubject: GalleryMediaSubject? public let standalone: Bool public let copyProtected: Bool @@ -34,21 +33,21 @@ public final class OpenChatMessageParams { public let modal: Bool public let dismissInput: () -> Void public let present: (ViewController, Any?, PresentationContextType) -> Void - public let transitionNode: (MessageId, Media, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + public let transitionNode: (EngineMessage.Id, EngineRawMedia, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? public let addToTransitionSurface: (UIView) -> Void public let openUrl: (String) -> Void - public let openPeer: (Peer, ChatControllerInteractionNavigateToPeer) -> Void - public let callPeer: (PeerId, Bool) -> Void - public let openConferenceCall: (Message) -> Void + public let openPeer: (EngineRawPeer, ChatControllerInteractionNavigateToPeer) -> Void + public let callPeer: (EnginePeer.Id, Bool) -> Void + public let openConferenceCall: (EngineRawMessage) -> Void public let enqueueMessage: (EnqueueMessage) -> Void public let sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)? public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? - public let setupTemporaryHiddenMedia: (Signal, Int, Media) -> Void - public let chatAvatarHiddenMedia: (Signal, Media) -> Void + public let setupTemporaryHiddenMedia: (Signal, Int, EngineRawMedia) -> Void + public let chatAvatarHiddenMedia: (Signal, EngineRawMedia) -> Void public let actionInteraction: GalleryControllerActionInteraction? public let playlistLocation: PeerMessagesPlaylistLocation? public let gallerySource: GalleryControllerItemSource? - public let centralItemUpdated: ((MessageId) -> Void)? + public let centralItemUpdated: ((EngineMessage.Id) -> Void)? public let navigateToMessageContext: ((EngineMessage) -> Void)? public let getSourceRect: (() -> CGRect?)? public let blockInteraction: Promise @@ -57,9 +56,9 @@ public final class OpenChatMessageParams { context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation?, - chatFilterTag: MemoryBuffer?, + chatFilterTag: EngineMemoryBuffer?, chatLocationContextHolder: Atomic?, - message: Message, + message: EngineRawMessage, mediaSubject: GalleryMediaSubject? = nil, standalone: Bool, copyProtected: Bool = false, @@ -69,21 +68,21 @@ public final class OpenChatMessageParams { modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?, PresentationContextType) -> Void, - transitionNode: @escaping (MessageId, Media, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, + transitionNode: @escaping (EngineMessage.Id, EngineRawMedia, Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, - openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, - callPeer: @escaping (PeerId, Bool) -> Void, - openConferenceCall: @escaping (Message) -> Void, + openPeer: @escaping (EngineRawPeer, ChatControllerInteractionNavigateToPeer) -> Void, + callPeer: @escaping (EnginePeer.Id, Bool) -> Void, + openConferenceCall: @escaping (EngineRawMessage) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, - setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, - chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, + setupTemporaryHiddenMedia: @escaping (Signal, Int, EngineRawMedia) -> Void, + chatAvatarHiddenMedia: @escaping (Signal, EngineRawMedia) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil, playlistLocation: PeerMessagesPlaylistLocation? = nil, gallerySource: GalleryControllerItemSource? = nil, - centralItemUpdated: ((MessageId) -> Void)? = nil, + centralItemUpdated: ((EngineMessage.Id) -> Void)? = nil, navigateToMessageContext: ((EngineMessage) -> Void)? = nil, getSourceRect: (() -> CGRect?)? = nil ) { diff --git a/submodules/AccountContext/Sources/PeerSelectionController.swift b/submodules/AccountContext/Sources/PeerSelectionController.swift index 547d98a0e6..19e8d7a3bc 100644 --- a/submodules/AccountContext/Sources/PeerSelectionController.swift +++ b/submodules/AccountContext/Sources/PeerSelectionController.swift @@ -2,7 +2,6 @@ import Foundation import Display import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import AnimationCache import MultiAnimationRenderer @@ -125,39 +124,6 @@ public enum AttachmentTextInputPanelSendMode { case whenOnline } -public enum PeerSelectionControllerContext { - public final class Custom { - public let accountPeerId: EnginePeer.Id - public let postbox: Postbox - public let network: Network - public let animationCache: AnimationCache - public let animationRenderer: MultiAnimationRenderer - public let presentationData: PresentationData - public let updatedPresentationData: Signal - - public init( - accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, - animationCache: AnimationCache, - animationRenderer: MultiAnimationRenderer, - presentationData: PresentationData, - updatedPresentationData: Signal - ) { - self.accountPeerId = accountPeerId - self.postbox = postbox - self.network = network - self.animationCache = animationCache - self.animationRenderer = animationRenderer - self.presentationData = presentationData - self.updatedPresentationData = updatedPresentationData - } - } - - case account(AccountContext) - case custom(Custom) -} - public protocol PeerSelectionController: ViewController { var peerSelected: ((EnginePeer, Int64?) -> Void)? { get set } var multiplePeersSelected: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? { get set } diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index cfce7d1d14..359d11a4ba 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -45,7 +45,7 @@ public enum PremiumIntroSource { case todo case copyProtection case aiTools - case auth(String) + case auth(String, Int32) case premiumGift(TelegramMediaFile) } diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 62bde63548..a3ac3bb3bd 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -309,22 +309,6 @@ public struct PresentationGroupCallSummaryState: Equatable { } } -public struct PresentationGroupCallMemberState: Equatable { - public var ssrc: UInt32 - public var muteState: GroupCallParticipantsContext.Participant.MuteState? - public var speaking: Bool - - public init( - ssrc: UInt32, - muteState: GroupCallParticipantsContext.Participant.MuteState?, - speaking: Bool - ) { - self.ssrc = ssrc - self.muteState = muteState - self.speaking = speaking - } -} - public enum PresentationGroupCallMuteAction: Equatable { case muted(isPushToTalkActive: Bool) case unmuted diff --git a/submodules/AccountContext/Sources/ShareController.swift b/submodules/AccountContext/Sources/ShareController.swift index 5ef521a5d5..a018a1f987 100644 --- a/submodules/AccountContext/Sources/ShareController.swift +++ b/submodules/AccountContext/Sources/ShareController.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -9,7 +8,7 @@ import MultiAnimationRenderer import Display public enum StorySharingSubject { - case messages([Message]) + case messages([EngineRawMessage]) case gift(StarGift.UniqueGift) } @@ -110,11 +109,11 @@ public enum ShareControllerSubject { case url(String) case text(String) case quote(text: String, url: String) - case messages([Message]) + case messages([EngineRawMessage]) case image([ImageRepresentationWithReference]) case media(AnyMediaReference, MediaParameters?) case mapMedia(TelegramMediaMap) - case fromExternal(Int, ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], String, ShareControllerAccountContext, Bool) -> Signal) + case fromExternal(Int, ([EnginePeer.Id], [EnginePeer.Id: Int64], [EnginePeer.Id: StarsAmount], String, ShareControllerAccountContext, Bool) -> Signal) } public struct ShareControllerAction { @@ -151,12 +150,12 @@ public final class ShareControllerParams { public let subject: ShareControllerSubject public let presetText: String? public let preferredAction: ShareControllerPreferredAction - public let showInChat: ((Message) -> Void)? + public let showInChat: ((EngineRawMessage) -> Void)? public let fromForeignApp: Bool public let segmentedValues: [ShareControllerSegmentedValue]? public let externalShare: Bool public let immediateExternalShare: Bool - public let immediatePeerId: PeerId? + public let immediatePeerId: EnginePeer.Id? public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let forceTheme: PresentationTheme? public let forcedActionTitle: String? @@ -165,8 +164,8 @@ public final class ShareControllerParams { public let actionCompleted: (() -> Void)? public let dismissed: ((Bool) -> Void)? - public let completed: (([PeerId]) -> Void)? - public let enqueued: (([PeerId], [Int64]) -> Void)? + public let completed: (([EnginePeer.Id]) -> Void)? + public let enqueued: (([EnginePeer.Id], [Int64]) -> Void)? public let shareStory: (() -> Void)? public let debugAction: (() -> Void)? public let onMediaTimestampLinkCopied: ((Int32?) -> Void)? @@ -177,12 +176,12 @@ public final class ShareControllerParams { subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, - showInChat: ((Message) -> Void)? = nil, + showInChat: ((EngineRawMessage) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, - immediatePeerId: PeerId? = nil, + immediatePeerId: EnginePeer.Id? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil, @@ -190,8 +189,8 @@ public final class ShareControllerParams { collectibleItemInfo: TelegramCollectibleItemInfo? = nil, actionCompleted: (() -> Void)? = nil, dismissed: ((Bool) -> Void)? = nil, - completed: (([PeerId]) -> Void)? = nil, - enqueued: (([PeerId], [Int64]) -> Void)? = nil, + completed: (([EnginePeer.Id]) -> Void)? = nil, + enqueued: (([EnginePeer.Id], [Int64]) -> Void)? = nil, shareStory: (() -> Void)? = nil, debugAction: (() -> Void)? = nil, onMediaTimestampLinkCopied: ((Int32?) -> Void)? = nil, diff --git a/submodules/ActionSheetPeerItem/BUILD b/submodules/ActionSheetPeerItem/BUILD index d3aa0cc103..cacfba4843 100644 --- a/submodules/ActionSheetPeerItem/BUILD +++ b/submodules/ActionSheetPeerItem/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/AvatarNode:AvatarNode", diff --git a/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift b/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift index c918a8712b..417ae1956f 100644 --- a/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift +++ b/submodules/ActionSheetPeerItem/Sources/ActionSheetPeerItem.swift @@ -3,15 +3,13 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AvatarNode import AccountContext public class ActionSheetPeerItem: ActionSheetItem { public let accountPeerId: EnginePeer.Id - public let postbox: Postbox - public let network: Network + public let stateManager: AccountStateManager public let contentSettings: ContentSettings public let peer: EnginePeer public let theme: PresentationTheme @@ -19,12 +17,11 @@ public class ActionSheetPeerItem: ActionSheetItem { public let isSelected: Bool public let strings: PresentationStrings public let action: () -> Void - + public convenience init(context: AccountContext, peer: EnginePeer, title: String, isSelected: Bool, strings: PresentationStrings, theme: PresentationTheme, action: @escaping () -> Void) { self.init( accountPeerId: context.account.peerId, - postbox: context.account.postbox, - network: context.account.network, + stateManager: context.account.stateManager, contentSettings: context.currentContentSettings.with { $0 }, peer: peer, title: title, @@ -34,11 +31,10 @@ public class ActionSheetPeerItem: ActionSheetItem { action: action ) } - + public init( accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + stateManager: AccountStateManager, contentSettings: ContentSettings, peer: EnginePeer, title: String, @@ -48,8 +44,7 @@ public class ActionSheetPeerItem: ActionSheetItem { action: @escaping () -> Void ) { self.accountPeerId = accountPeerId - self.postbox = postbox - self.network = network + self.stateManager = stateManager self.contentSettings = contentSettings self.peer = peer self.title = title @@ -154,7 +149,7 @@ public class ActionSheetPeerItemNode: ActionSheetItemNode { let textColor: UIColor = self.theme.primaryTextColor self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: textColor) - self.avatarNode.setPeer(accountPeerId: item.accountPeerId, postbox: item.postbox, network: item.network, contentSettings: item.contentSettings, theme: item.theme, peer: item.peer) + self.avatarNode.setPeer(accountPeerId: item.accountPeerId, postbox: item.stateManager.postbox, network: item.stateManager.network, contentSettings: item.contentSettings, theme: item.theme, peer: item.peer) self.checkNode.isHidden = !item.isSelected diff --git a/submodules/AdUI/Sources/AdInfoScreen.swift b/submodules/AdUI/Sources/AdInfoScreen.swift index 7cecf8224b..df5a2684fd 100644 --- a/submodules/AdUI/Sources/AdInfoScreen.swift +++ b/submodules/AdUI/Sources/AdInfoScreen.swift @@ -78,7 +78,7 @@ public final class AdInfoScreen: ViewController { self.scrollNode = ASScrollNode() self.scrollNode.view.showsVerticalScrollIndicator = true self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = true + self.scrollNode.view.scrollsToTop = false self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.canCancelContentTouches = true if #available(iOS 11.0, *) { diff --git a/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift b/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift index 598e68063a..c00eb947a1 100644 --- a/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift +++ b/submodules/AnimatedAvatarSetNode/Sources/AnimatedAvatarSetNode.swift @@ -91,7 +91,7 @@ private final class ContentNode: ASDisplayNode { self.addSubnode(self.clippedNode) if let peer = peer { - if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) { + if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) { let image = generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.lightGray.cgColor) @@ -328,7 +328,7 @@ public final class AnimatedAvatarSetView: UIView { self.addSubview(self.clippedView) if let peer = peer { - if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) { + if let representation = peer.smallProfileImage, let signal = peerAvatarImage(account: context.account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: synchronousLoad) { let image = generateImage(size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.lightGray.cgColor) diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm index ba05b9d895..e0d85c8863 100644 --- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm +++ b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm @@ -341,6 +341,7 @@ textView.opaque = NO; } textView.textContainerInset = self.textContainerInset; + textView.scrollsToTop = false; // Configure textView with UITextInputTraits { @@ -366,6 +367,7 @@ _placeholderTextKitComponents.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer]; _placeholderTextKitComponents.textView.userInteractionEnabled = NO; _placeholderTextKitComponents.textView.accessibilityElementsHidden = YES; + _placeholderTextKitComponents.textView.scrollsToTop = false; configureTextView(_placeholderTextKitComponents.textView); // Create and configure our text view. diff --git a/submodules/AttachmentTextInputPanelNode/BUILD b/submodules/AttachmentTextInputPanelNode/BUILD index 25e0d545af..095e118ae6 100644 --- a/submodules/AttachmentTextInputPanelNode/BUILD +++ b/submodules/AttachmentTextInputPanelNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TextFormat:TextFormat", @@ -28,6 +27,7 @@ swift_library( "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", "//submodules/Pasteboard:Pasteboard", "//submodules/ContextUI:ContextUI", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode:ChatEntityKeyboardInputNode", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index fbc3cb7cce..4d6f82e9f1 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -24,6 +24,7 @@ import AnimationCache import MultiAnimationRenderer import TextNodeWithEntities import ChatInputTextNode +import ChatEntityKeyboardInputNode private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) private let minInputFontSize: CGFloat = 5.0 @@ -44,15 +45,15 @@ private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPres } else { result = 31.0 } - + if case .regular = metrics.widthClass { result = max(33.0, result) } - + if glass { result = max(38.0, result) } - + return result } @@ -88,7 +89,7 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground return current.3 } } - + let image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in context.clear(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) @@ -103,7 +104,7 @@ private func textInputBackgroundImage(backgroundColor: UIColor?, inputBackground context.setFillColor(UIColor.clear.cgColor) } context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) - + if !caption { context.setBlendMode(.normal) context.setStrokeColor(strokeColor.cgColor) @@ -132,31 +133,29 @@ private class CaptionEditableTextNode: ChatInputTextNode { } } -public protocol AttachmentTextInputPanelInputView: UIView { - var insertText: ((NSAttributedString) -> Void)? { get set } - var deleteBackwards: (() -> Void)? { get set } - var switchToKeyboard: (() -> Void)? { get set } - var presentController: ((ViewController) -> Void)? { get set } +private enum AttachmentTextInputMode { + case text + case emoji } final class CustomEmojiContainerView: UIView { private let emojiViewProvider: (ChatTextInputTextCustomEmojiAttribute) -> UIView? - + private var emojiLayers: [InlineStickerItemLayer.Key: UIView] = [:] - + init(emojiViewProvider: @escaping (ChatTextInputTextCustomEmojiAttribute) -> UIView?) { self.emojiViewProvider = emojiViewProvider - + super.init(frame: CGRect()) } - + required init(coder: NSCoder) { preconditionFailure() } - + func update(emojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)]) { var nextIndexById: [Int64: Int] = [:] - + var validKeys = Set() for (rect, emoji) in emojiRects { let index: Int @@ -166,9 +165,9 @@ final class CustomEmojiContainerView: UIView { index = 0 } nextIndexById[emoji.fileId] = index + 1 - + let key = InlineStickerItemLayer.Key(id: emoji.fileId, index: index) - + let view: UIView if let current = self.emojiLayers[key] { view = current @@ -179,14 +178,14 @@ final class CustomEmojiContainerView: UIView { } else { continue } - + let size = CGSize(width: 24.0, height: 24.0) - + view.frame = CGRect(origin: CGPoint(x: floor(rect.midX - size.width / 2.0), y: floor(rect.midY - size.height / 2.0)), size: size) - + validKeys.insert(key) } - + var removeKeys: [InlineStickerItemLayer.Key] = [] for (key, view) in self.emojiLayers { if !validKeys.contains(key) { @@ -203,11 +202,11 @@ final class CustomEmojiContainerView: UIView { private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPresentationInterfaceState) -> ChatInputTextView.Theme { let lineStyle: ChatInputTextView.Theme.Quote.LineStyle let authorNameColor: UIColor - + if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .broadcast = peer.info, let nameColor = peer.nameColor { let colors = context.peerNameColors.get(nameColor) authorNameColor = colors.main - + if let secondary = colors.secondary, let tertiary = colors.tertiary { lineStyle = .tripleDashed(mainColor: colors.main, secondaryColor: secondary, tertiaryColor: tertiary) } else if let secondary = colors.secondary { @@ -217,7 +216,7 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre } } else if let accountPeerColor = interfaceState.accountPeerColor { authorNameColor = interfaceState.theme.list.itemAccentColor - + switch accountPeerColor.style { case .solid: lineStyle = .solid(color: authorNameColor) @@ -230,14 +229,14 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre lineStyle = .solid(color: interfaceState.theme.list.itemAccentColor) authorNameColor = interfaceState.theme.list.itemAccentColor } - + let codeBackgroundColor: UIColor if interfaceState.theme.overallDarkAppearance { codeBackgroundColor = UIColor(white: 1.0, alpha: 0.05) } else { codeBackgroundColor = UIColor(white: 0.0, alpha: 0.05) } - + return ChatInputTextView.Theme( quote: ChatInputTextView.Theme.Quote( background: authorNameColor.withMultipliedAlpha(interfaceState.theme.overallDarkAppearance ? 0.2 : 0.1), @@ -251,14 +250,16 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate { private let context: AccountContext - + private let glass: Bool private let isCaption: Bool private let isAttachment: Bool - + private let customEmojiAvailable: Bool + private let presentController: (ViewController) -> Void - private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? - + private let presentInGlobalOverlay: (ViewController) -> Void + private let getNavigationController: () -> NavigationController? + public var textPlaceholderNode: ImmediateTextNode private let textInputContainerBackgroundNode: ASImageNode public let textInputContainer: ASDisplayNode @@ -268,7 +269,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var oneLineNode: TextNodeWithEntities private var oneLineNodeAttributedText: NSAttributedString? private var oneLineDustNode: InvisibleInkDustNode? - + let textInputBackgroundNode: ASDisplayNode let textInputBackgroundImageNode: ASImageNode private var transparentTextInputBackgroundImage: UIImage? @@ -278,34 +279,49 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var aiButton: (button: HighlightTrackingButton, icon: UIImageView)? private var heightDependentAiButtonAlpha: CGFloat = 0.0 public var isAIEnabled: Bool = false - + public var opaqueActionButtons: ASDisplayNode { return self.actionButtons } - + public let inputModeView: ComponentHostView - private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? - + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, textFieldMaxHeight: CGFloat, availableHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)? + private var currentInputMode: AttachmentTextInputMode = .text + private var currentAdditionalInputHeight: CGFloat = 0.0 + private var currentSafeAreaInset: UIEdgeInsets = .zero + private var currentContainerBottomInset: CGFloat = 0.0 + private var usesContainerLayout = false + private var currentIsCaptionAbove = false + private var currentHeight: CGFloat? + private var isTransitioningToTextKeyboard = false + + private let inputMediaNodeDataPromise = Promise() + private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData? + private var inputMediaNodeDataDisposable: Disposable? + private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() + private var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction? + private var inputMediaNode: ChatEntityKeyboardInputNode? + public var sendMessage: (AttachmentTextInputPanelSendMode, ChatSendMessageActionSheetController.SendParameters?) -> Void = { _, _ in } public var invokeAICompose: (() -> Void)? public var updateHeight: (Bool) -> Void = { _ in } private var updatingInputState = false - + private var currentPlaceholder: String? - + public var effectivePresentationInterfaceState: (() -> ChatPresentationInterfaceState?)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var initializedPlaceholder = false - + private let inputMenu: TextInputMenu - + private var theme: PresentationTheme? private var strings: PresentationStrings? - + private let hapticFeedback = HapticFeedback() - + public var inputTextState: ChatTextInputState { if let textInputNode = self.textInputNode { let selectionRange: Range = textInputNode.selectedRange.location ..< (textInputNode.selectedRange.location + textInputNode.selectedRange.length) @@ -314,7 +330,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return ChatTextInputState() } } - + var storedInputLanguage: String? var effectiveInputLanguage: String? { if let textInputNode = textInputNode, textInputNode.isFirstResponder() { @@ -323,7 +339,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return self.storedInputLanguage } } - + var enablePredictiveInput: Bool = true { didSet { if let textInputNode = self.textInputNode { @@ -331,28 +347,28 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + public var interfaceInteraction: ChatPanelInterfaceInteraction? - + public func animateIn(transition: ContainedViewLayoutTransition) { self.actionButtons.animateIn(transition: transition) } - + public func updateSendButtonEnabled(_ enabled: Bool, animated: Bool) { self.actionButtons.isUserInteractionEnabled = enabled - + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate transition.updateAlpha(node: self.actionButtons, alpha: enabled ? 1.0 : 0.3) } - + public func updateInputTextState(_ state: ChatTextInputState, animated: Bool) { if state.inputText.length != 0 && self.textInputNode == nil { self.loadTextInputNode() } - + if let textInputNode = self.textInputNode, let _ = self.presentationInterfaceState, !self.skipUpdate { self.updatingInputState = true - + var textColor: UIColor = .black var accentTextColor: UIColor = .blue var baseFontSize: CGFloat = 17.0 @@ -370,7 +386,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.updateSpoiler() } } - + public var text: String { get { return self.textInputNode?.attributedText?.string ?? "" @@ -387,52 +403,54 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + public func caption() -> NSAttributedString { return self.textInputNode?.attributedText ?? NSAttributedString() } - + private let textInputViewInternalInsets = UIEdgeInsets(top: 1.0, left: 13.0, bottom: 1.0, right: 13.0) - + private var spoilersRevealed = false - + public var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - + private var maxCaptionLength: Int32? - - public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + + public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, customEmojiAvailable: Bool, presentController: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, getNavigationController: @escaping () -> NavigationController?) { self.context = context self.presentationInterfaceState = presentationInterfaceState self.glass = glass self.isCaption = isCaption self.isAttachment = isAttachment + self.customEmojiAvailable = customEmojiAvailable self.presentController = presentController - self.makeEntityInputView = makeEntityInputView - + self.presentInGlobalOverlay = presentInGlobalOverlay + self.getNavigationController = getNavigationController + self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - + var hasSpoilers = true - if presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { + if presentationInterfaceState.chatLocation.peerId?.isSecretChat == true { hasSpoilers = false } self.inputMenu = TextInputMenu(hasSpoilers: hasSpoilers) - + self.textInputContainerBackgroundNode = ASImageNode() self.textInputContainerBackgroundNode.isUserInteractionEnabled = false self.textInputContainerBackgroundNode.displaysAsynchronously = false - + self.textInputContainer = ASDisplayNode() if !isCaption && !glass { self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode) } - + self.inputModeView = ComponentHostView() self.textInputContainer.view.addSubview(self.inputModeView) self.textInputContainer.clipsToBounds = true - + self.textInputBackgroundNode = ASDisplayNode() self.textInputBackgroundImageNode = ASImageNode() self.textInputBackgroundImageNode.displaysAsynchronously = false @@ -440,16 +458,16 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.textPlaceholderNode = ImmediateTextNode() self.textPlaceholderNode.maximumNumberOfLines = 1 self.textPlaceholderNode.isUserInteractionEnabled = false - + self.oneLineNode = TextNodeWithEntities() self.oneLineNode.textNode.isUserInteractionEnabled = false - + self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, glass: glass, presentController: presentController) self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center - + super.init() - + if !isScheduledMessages { self.actionButtons.sendButtonLongPressed = { [weak self] node, gesture in self?.interfaceInteraction?.displaySendMessageOptions(node, gesture) @@ -458,27 +476,27 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } else { self.actionButtons.sendButtonLongPressEnabled = false } - + self.actionButtons.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside) self.actionButtons.sendButton.alpha = 1.0 self.actionButtons.updateAccessibility() - + self.addSubnode(self.textInputContainer) self.addSubnode(self.textInputBackgroundNode) - + if !glass { self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode) } - + self.addSubnode(self.textPlaceholderNode) - + self.addSubnode(self.actionButtons) self.addSubnode(self.counterTextNode) - + if isCaption { self.addSubnode(self.oneLineNode.textNode) } - + self.textInputBackgroundImageNode.clipsToBounds = true let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))) recognizer.touchDown = { [weak self] in @@ -490,7 +508,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS guard let strongSelf = self, let textInputNode = strongSelf.textInputNode else { return true } - + if textInputNode.textView.isFirstResponder { return true } else { @@ -498,17 +516,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } self.textInputBackgroundNode.view.addGestureRecognizer(recognizer) - + self.emojiViewProvider = { [weak self] emoji in guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState else { return UIView() } - + return EmojiTextAttachmentView(context: context, userLocation: .other, emoji: emoji, file: emoji.file, cache: strongSelf.animationCache, renderer: strongSelf.animationRenderer, placeholderColor: presentationInterfaceState.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12), pointSize: CGSize(width: 24.0, height: 24.0)) } - + self.updateSendButtonEnabled(isCaption || isAttachment, animated: false) - + if self.isCaption || self.isAttachment { let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) |> mapToSignal { peer -> Signal in @@ -525,45 +543,148 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self?.maxCaptionLength = maxCaptionLength }) } + + self.inputMediaNodeDataPromise.set( + ChatEntityKeyboardInputNode.inputData( + context: context, + chatPeerId: nil, + areCustomEmojiEnabled: customEmojiAvailable, + hasTrending: false, + hasStickers: false, + hasGifs: false, + sendGif: nil + ) + ) + self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let self else { + return + } + self.inputMediaNodeData = value + if case .emoji = self.currentInputMode { + self.requestRelayout(animated: false) + } + }) + self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction( + sendSticker: { _, _, _, _, _, _, _, _, _ in + return false + }, + sendEmoji: { _, _, _ in + }, + sendGif: { _, _, _, _, _ in + return false + }, + sendBotContextResultAsGif: { _, _, _, _, _, _ in + return false + }, + editGif: { _, _ in + }, + updateChoosingSticker: { _ in + }, + switchToTextInput: { [weak self] in + self?.activateInput() + }, + dismissTextInput: { + }, + insertText: { [weak self] text in + self?.insertTextFromInputMedia(text) + }, + backwardsDeleteText: { [weak self] in + self?.deleteBackwardsFromInputMedia() + }, + openStickerEditor: { + }, + presentController: { [weak self] controller, _ in + self?.presentController(controller) + }, + presentGlobalOverlayController: { [weak self] controller, _ in + self?.presentInGlobalOverlay(controller) + }, + getNavigationController: { [weak self] in + return self?.getNavigationController() + }, + requestLayout: { [weak self] transition in + self?.requestRelayout(animated: transition.isAnimated) + } + ) + self.inputMediaInteraction?.forceTheme = presentationInterfaceState.theme } - + public var sendPressed: ((NSAttributedString?) -> Void)? public var focusUpdated: ((Bool) -> Void)? public var heightUpdated: ((Bool) -> Void)? public var timerUpdated: ((NSNumber?) -> Void)? public var captionIsAboveUpdated: ((Bool) -> Void)? - + public var additionalInputHeight: CGFloat { + return self.currentAdditionalInputHeight + } + public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat { + self.currentSafeAreaInset = .zero + self.currentContainerBottomInset = 0.0 + self.usesContainerLayout = false guard let presentationInterfaceState = self.presentationInterfaceState else { return 0.0 } - return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: size.height, availableHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) } - + + @objc(updateContainerLayoutSize:safeAreaInset:bottomInset:keyboardHeight:animated:) + public func updateContainerLayoutSize(_ size: CGSize, safeAreaInset: UIEdgeInsets, bottomInset: CGFloat, keyboardHeight: CGFloat, animated: Bool) -> CGFloat { + self.currentSafeAreaInset = safeAreaInset + self.currentContainerBottomInset = bottomInset + self.usesContainerLayout = true + guard let presentationInterfaceState = self.presentationInterfaceState else { + return 0.0 + } + return self.updateLayout(width: size.width, leftInset: 0.0, rightInset: 0.0, bottomInset: safeAreaInset.bottom, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: size.height, availableHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + } + public func setCaption(_ caption: NSAttributedString?) { self.interfaceInteraction?.updateTextInputStateAndMode { state, inputMode in return (ChatTextInputState(inputText: caption ?? NSAttributedString()), inputMode) } } - + public func setTimeout(_ timeout: Int32, isVideo: Bool, isCaptionAbove: Bool) { + self.currentIsCaptionAbove = isCaptionAbove } - + public func animate(_ view: UIView, frame: CGRect) { - + let transition = ComponentTransition.spring(duration: 0.4) + transition.setFrame(view: view, frame: frame) } - + public func onAnimateOut() { } - + public func activateInput() { + self.loadTextInputNodeIfNeeded() + + let wasEmoji = self.currentInputMode == .emoji + self.currentInputMode = .text + if let textInputNode = self.textInputNode { + self.isTransitioningToTextKeyboard = wasEmoji && textInputNode.textView.isFirstResponder + self.applyCurrentInputMode(reload: textInputNode.textView.isFirstResponder) + if !textInputNode.textView.isFirstResponder { + textInputNode.textView.becomeFirstResponder() + } + } + self.requestRelayout(animated: wasEmoji) } - + public func dismissInput() -> Bool { - self.ensureUnfocused() + let wasEmoji = self.currentInputMode == .emoji + if wasEmoji { + self.currentInputMode = .text + self.isTransitioningToTextKeyboard = false + self.requestRelayout(animated: true) + } + self.textInputNode?.resignFirstResponder() + self.applyCurrentInputMode(reload: false) return true } - + public func baseHeight() -> CGFloat { return 45.0 } @@ -571,17 +692,21 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + + deinit { + self.inputMediaNodeDataDisposable?.dispose() + } + public func loadTextInputNodeIfNeeded() { if self.textInputNode == nil { self.loadTextInputNode() } } - + private func loadTextInputNode() { let textInputNode = CaptionEditableTextNode() textInputNode.initialPrimaryLanguage = self.presentationInterfaceState?.interfaceState.inputLanguage - + var textColor: UIColor = .black var tintColor: UIColor = .blue var baseFontSize: CGFloat = 17.0 @@ -591,17 +716,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS tintColor = presentationInterfaceState.theme.list.itemAccentColor baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) keyboardAppearance = presentationInterfaceState.theme.rootController.keyboardColor.keyboardAppearance - + textInputNode.textView.theme = makeTextInputTheme(context: self.context, interfaceState: presentationInterfaceState) } - + let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 1.0 paragraphStyle.lineHeightMultiple = 1.0 paragraphStyle.paragraphSpacing = 1.0 paragraphStyle.maximumLineHeight = 20.0 paragraphStyle.minimumLineHeight = 20.0 - + textInputNode.textView.typingAttributes = [NSAttributedString.Key.font: Font.regular(max(minInputFontSize, baseFontSize)), NSAttributedString.Key.foregroundColor: textColor, NSAttributedString.Key.paragraphStyle: paragraphStyle] textInputNode.clipsToBounds = false textInputNode.textView.clipsToBounds = false @@ -613,27 +738,27 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.textInputContainer.addSubnode(textInputNode) textInputNode.view.disablesInteractiveTransitionGestureRecognizer = true self.textInputNode = textInputNode - + textInputNode.textView.inputAssistantItem.leadingBarButtonGroups = [] textInputNode.textView.inputAssistantItem.trailingBarButtonGroups = [] - + if let presentationInterfaceState = self.presentationInterfaceState { refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } - + if !self.textInputContainer.bounds.size.width.isZero { let textInputFrame = self.textInputContainer.frame - + textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom)) textInputNode.view.layoutIfNeeded() textInputNode.textView.updateLayout(size: textInputNode.bounds.size) self.updateSpoiler() } - + self.textInputBackgroundNode.isUserInteractionEnabled = false self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0]) - + let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))) recognizer.touchDown = { [weak self] in if let strongSelf = self { @@ -644,7 +769,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS guard let strongSelf = self, let textInputNode = strongSelf.textInputNode else { return true } - + if textInputNode.textView.isFirstResponder { return true } else { @@ -652,48 +777,49 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } textInputNode.view.addGestureRecognizer(recognizer) - + textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string + self.applyCurrentInputMode(reload: false) } - + private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { let textFieldInsets = self.textFieldInsets(metrics: metrics) return max(self.glass ? 40.0 : 33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) } - + private func calculateTextFieldMetrics(width: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { var textFieldInsets = self.textFieldInsets(metrics: metrics) if self.actionButtons.frame.width > 44.0 { textFieldInsets.right = self.actionButtons.frame.width - 6.0 } let fieldMaxHeight = textFieldMaxHeight(maxHeight, metrics: metrics) - + var textFieldMinHeight: CGFloat = 35.0 var textFieldRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } - + let textFieldHeight: CGFloat if let textInputNode = self.textInputNode { let maxTextWidth = width - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - + let measuredHeight = textInputNode.textHeightForWidth(maxTextWidth, rightInset: textFieldRealInsets.right) let unboundTextFieldHeight = max(textFieldMinHeight, ceil(measuredHeight)) - + let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22) - + let updatedMaxHeight = (CGFloat(maxNumberOfLines) * (22.0 + 2.0) + 10.0) - + textFieldHeight = max(textFieldMinHeight, min(updatedMaxHeight, unboundTextFieldHeight)) } else { textFieldHeight = textFieldMinHeight } - + return (0.0, textFieldHeight) } - + private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets { var insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 42.0) if self.glass { @@ -707,13 +833,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return insets } - + private func panelHeight(textFieldHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { let textFieldInsets = self.textFieldInsets(metrics: metrics) let result = textFieldHeight + textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom return result } - + func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics) var minimalHeight: CGFloat = 14.0 + textFieldMinHeight @@ -722,7 +848,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return minimalHeight } - + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard self.isUserInteractionEnabled else { return nil @@ -736,38 +862,57 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if !self.inputModeView.isHidden, let result = self.inputModeView.hitTest(self.view.convert(point, to: self.inputModeView), with: event) { return result } - - return super.hitTest(point, with: event) + if let inputMediaNode = self.inputMediaNode, let result = inputMediaNode.view.hitTest(self.view.convert(point, to: inputMediaNode.view), with: event) { + return result + } + let result = super.hitTest(point, with: event) + if result === self.view { + return nil + } + return result } - + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: 0.0, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: maxHeight, availableHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: maxHeight, availableHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + } + + public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, textFieldMaxHeight: CGFloat, availableHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { let hadLayout = self.validLayout != nil - let previousAdditionalSideInsets = self.validLayout?.3 - self.validLayout = (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) - + let previousLayout = self.validLayout + self.validLayout = (width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, textFieldMaxHeight: textFieldMaxHeight, availableHeight: availableHeight, metrics: metrics, isSecondary: isSecondary) + + let previousAdditionalSideInsets = previousLayout?.additionalSideInsets let leftInset = leftInset + 8.0 let rightInset = rightInset + 8.0 - + var transition = transition - if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right { + if keyboardHeight.isZero, let previousKeyboardHeight = previousLayout?.keyboardHeight, previousKeyboardHeight > 0.0, !transition.isAnimated { + transition = .animated(duration: 0.4, curve: .spring) + } + if let previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right { if case .animated = transition { transition = .animated(duration: 0.2, curve: .easeInOut) } } - + if self.presentationInterfaceState != interfaceState || !hadLayout { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState - + self.inputMediaInteraction?.forceTheme = interfaceState.theme + let themeUpdated = previousState?.theme !== interfaceState.theme - + var updateSendButtonIcon = false if (previousState?.interfaceState.editMessage != nil) != (interfaceState.interfaceState.editMessage != nil) { updateSendButtonIcon = true } if self.theme !== interfaceState.theme { updateSendButtonIcon = true - + if self.theme == nil || !self.theme!.chat.inputPanel.inputTextColor.isEqual(interfaceState.theme.chat.inputPanel.inputTextColor) { let textColor = interfaceState.theme.chat.inputPanel.inputTextColor let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize) @@ -775,20 +920,20 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if let textInputNode = self.textInputNode { if let text = textInputNode.attributedText { let selectedRange = textInputNode.selectedRange - let textRange = NSMakeRange(0, (text.string as NSString).length) + let textRange = NSMakeRange(0, (text.string as NSString).length) let updatedText = NSMutableAttributedString(attributedString: text) updatedText.removeAttribute(.foregroundColor, range: textRange) updatedText.addAttribute(.foregroundColor, value: textColor, range: textRange) - + textInputNode.attributedText = updatedText textInputNode.selectedRange = selectedRange } textInputNode.textView.typingAttributes = [NSAttributedString.Key.font: Font.regular(baseFontSize), NSAttributedString.Key.foregroundColor: textColor] - + self.updateSpoiler() } } - + let keyboardAppearance = interfaceState.theme.rootController.keyboardColor.keyboardAppearance if let textInputNode = self.textInputNode, textInputNode.keyboardAppearance != keyboardAppearance, textInputNode.isFirstResponder() { if textInputNode.isCurrentlyEmoji() { @@ -797,36 +942,32 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } textInputNode.keyboardAppearance = keyboardAppearance } - + self.theme = interfaceState.theme - self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - + let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics) let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight - + let backgroundColor: UIColor if case let .color(color) = interfaceState.chatWallpaper, UIColor(rgb: color).isEqual(interfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper) { backgroundColor = interfaceState.theme.chat.inputPanel.panelBackgroundColorNoWallpaper } else { backgroundColor = interfaceState.theme.chat.inputPanel.panelBackgroundColor } - + self.textInputBackgroundImageNode.image = textInputBackgroundImage(backgroundColor: backgroundColor, inputBackgroundColor: nil, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight, caption: self.isCaption) self.transparentTextInputBackgroundImage = textInputBackgroundImage(backgroundColor: nil, inputBackgroundColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight, caption: self.isCaption) self.textInputContainerBackgroundNode.image = generateStretchableFilledCircleImage(diameter: minimalInputHeight, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor) - } else { - if self.strings !== interfaceState.strings { - self.strings = interfaceState.strings - self.inputMenu.updateStrings(interfaceState.strings) - } + } else if self.strings !== interfaceState.strings { + self.strings = interfaceState.strings + self.inputMenu.updateStrings(interfaceState.strings) } - + if themeUpdated || !self.initializedPlaceholder { self.initializedPlaceholder = true - + let placeholder = self.isCaption || self.isAttachment ? interfaceState.strings.MediaPicker_AddCaption : interfaceState.strings.Conversation_InputTextPlaceholder - if self.currentPlaceholder != placeholder || themeUpdated { self.currentPlaceholder = placeholder let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize) @@ -843,74 +984,76 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.textPlaceholderNode.frame = CGRect(origin: self.textPlaceholderNode.frame.origin, size: placeholderSize) } } - + let sendButtonHasApplyIcon = self.isCaption || interfaceState.interfaceState.editMessage != nil - - if updateSendButtonIcon { - if !self.actionButtons.animatingSendButton { - let imageNode = self.actionButtons.sendButton.imageNode - - if transition.isAnimated && !self.actionButtons.sendButton.alpha.isZero && self.actionButtons.sendButton.layer.animation(forKey: "opacity") == nil, let previousImage = imageNode.image { - let tempView = UIImageView(image: previousImage) - self.actionButtons.sendButton.view.addSubview(tempView) - tempView.frame = imageNode.frame - tempView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in - tempView?.removeFromSuperview() - }) - tempView.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, removeOnCompletion: false) - - imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - imageNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2) - } - self.actionButtons.sendButtonHasApplyIcon = sendButtonHasApplyIcon - if self.actionButtons.sendButtonHasApplyIcon { - self.actionButtons.setImage(PresentationResourcesChat.chatInputPanelApplyIconImage(interfaceState.theme)) - } else { - self.actionButtons.setImage(PresentationResourcesChat.chatInputPanelSendIconImage(interfaceState.theme)) - } + if updateSendButtonIcon, !self.actionButtons.animatingSendButton { + let imageNode = self.actionButtons.sendButton.imageNode + + if transition.isAnimated && !self.actionButtons.sendButton.alpha.isZero && self.actionButtons.sendButton.layer.animation(forKey: "opacity") == nil, let previousImage = imageNode.image { + let tempView = UIImageView(image: previousImage) + self.actionButtons.sendButton.view.addSubview(tempView) + tempView.frame = imageNode.frame + tempView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in + tempView?.removeFromSuperview() + }) + tempView.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, removeOnCompletion: false) + + imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + imageNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2) + } + self.actionButtons.sendButtonHasApplyIcon = sendButtonHasApplyIcon + if self.actionButtons.sendButtonHasApplyIcon { + self.actionButtons.setImage(PresentationResourcesChat.chatInputPanelApplyIconImage(interfaceState.theme)) + } else { + self.actionButtons.setImage(PresentationResourcesChat.chatInputPanelSendIconImage(interfaceState.theme)) } } } - + + let isLandscape = width > availableHeight + let deviceMetrics = DeviceMetrics(screenSize: CGSize(width: width, height: availableHeight), scale: UIScreen.main.scale, statusBarHeight: 0.0, onScreenNavigationHeight: nil) + let standardInputHeight = deviceMetrics.standardInputHeight(inLandscape: isLandscape) + if keyboardHeight > 0.0 || !self.isFocused { + self.isTransitioningToTextKeyboard = false + } + var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0 if let presentationInterfaceState = self.presentationInterfaceState { textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) } let minimalHeight: CGFloat = 14.0 + textFieldMinHeight - + let baseWidth = width - leftInset - rightInset - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics) - var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) - - self.updateCounterTextNode(transition: transition) - + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: textFieldMaxHeight, metrics: metrics) + var panelContentHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) + var inputHasText = false if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 { inputHasText = true } - + var textFieldInsets = self.textFieldInsets(metrics: metrics) if additionalSideInsets.right > 0.0 { textFieldInsets.right += additionalSideInsets.right / 3.0 } - + var textInputViewRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } - + if self.isCaption { if self.isFocused { self.oneLineNode.textNode.alpha = 0.0 self.oneLineDustNode?.alpha = 0.0 self.textInputNode?.alpha = 1.0 - + transition.updateAlpha(node: self.actionButtons, alpha: 1.0) transition.updateTransformScale(node: self.actionButtons, scale: 1.0) transition.updateAlpha(node: self.textInputBackgroundImageNode, alpha: 1.0) } else { - panelHeight = minimalHeight - + panelContentHeight = minimalHeight + transition.updateAlpha(node: self.oneLineNode.textNode, alpha: inputHasText ? 1.0 : 0.0) if let oneLineDustNode = self.oneLineDustNode { transition.updateAlpha(node: oneLineDustNode, alpha: inputHasText ? 1.0 : 0.0) @@ -918,12 +1061,142 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if let textInputNode = self.textInputNode { transition.updateAlpha(node: textInputNode, alpha: inputHasText ? 0.0 : 1.0) } - + transition.updateAlpha(node: self.actionButtons, alpha: 0.0) transition.updateTransformScale(node: self.actionButtons, scale: 0.001) transition.updateAlpha(node: self.textInputBackgroundImageNode, alpha: inputHasText ? 1.0 : 0.0) } + } + let inputPanelHeight = panelContentHeight + (self.glass ? 11.0 : 0.0) + var totalHeight = inputPanelHeight + var inputMediaHeight: CGFloat = 0.0 + self.currentAdditionalInputHeight = 0.0 + var inputMediaNodeForLayout: ChatEntityKeyboardInputNode? + var isNewInputMediaNode = false + + if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData { + let inputMediaNode: ChatEntityKeyboardInputNode + if let current = self.inputMediaNode { + inputMediaNode = current + } else { + isNewInputMediaNode = true + inputMediaNode = ChatEntityKeyboardInputNode( + context: self.context, + currentInputData: inputData, + updatedInputData: self.inputMediaNodeDataPromise.get(), + defaultToEmojiTab: true, + opaqueTopPanelBackground: false, + useOpaqueTheme: false, + interaction: self.inputMediaInteraction, + chatPeerId: nil, + stateContext: self.inputMediaNodeStateContext + ) + inputMediaNode.clipsToBounds = true + inputMediaNode.externalTopPanelContainerImpl = nil + inputMediaNode.useExternalSearchContainer = true + self.inputMediaNode = inputMediaNode + } + + if inputMediaNode.view.superview == nil { + self.view.addSubview(inputMediaNode.view) + } + inputMediaNodeForLayout = inputMediaNode + + let heightAndOverflow = inputMediaNode.updateLayout( + width: width, + leftInset: 0.0, + rightInset: 0.0, + bottomInset: bottomInset, + standardInputHeight: standardInputHeight, + inputHeight: 0.0, + maximumHeight: availableHeight, + inputPanelHeight: 0.0, + transition: .immediate, + interfaceState: interfaceState, + layoutMetrics: metrics, + deviceMetrics: deviceMetrics, + isVisible: true, + isExpanded: false + ) + inputMediaHeight = heightAndOverflow.0 + self.currentAdditionalInputHeight = inputMediaHeight + totalHeight += inputMediaHeight + } else if let inputMediaNode = self.inputMediaNode { + self.inputMediaNode = nil + + if transition.isAnimated { + var dismissingInputHeight = keyboardHeight + if self.isTransitioningToTextKeyboard && dismissingInputHeight.isZero && self.isFocused { + dismissingInputHeight = max(dismissingInputHeight, standardInputHeight) + } + let targetOriginY: CGFloat + if self.usesContainerLayout { + if dismissingInputHeight > 0.0 { + targetOriginY = availableHeight - dismissingInputHeight + } else { + targetOriginY = availableHeight + } + } else if dismissingInputHeight > 0.0 { + targetOriginY = inputPanelHeight + } else { + targetOriginY = inputPanelHeight + inputMediaNode.frame.height + } + let targetFrame = CGRect(origin: CGPoint(x: inputMediaNode.frame.minX, y: targetOriginY), size: inputMediaNode.frame.size) + transition.updateFrame(view: inputMediaNode.view, frame: targetFrame) + inputMediaNode.view.layer.animateAlpha(from: inputMediaNode.view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in + inputMediaNode?.view.removeFromSuperview() + }) + } else { + inputMediaNode.view.removeFromSuperview() + } + } + + var retainedInputHeight = keyboardHeight + var shouldRetainHiddenInputHeight = false + if self.isFocused { + if case .emoji = self.currentInputMode { + retainedInputHeight = max(retainedInputHeight, standardInputHeight) + shouldRetainHiddenInputHeight = true + } else if self.isTransitioningToTextKeyboard && retainedInputHeight.isZero { + retainedInputHeight = max(retainedInputHeight, standardInputHeight) + shouldRetainHiddenInputHeight = true + } + } + if self.currentAdditionalInputHeight.isZero && retainedInputHeight > 0.0 && shouldRetainHiddenInputHeight { + self.currentAdditionalInputHeight = retainedInputHeight + totalHeight += retainedInputHeight + } + + let isLandscapePhone = width > availableHeight && UIDevice.current.userInterfaceIdiom != .pad + let collapsedCaptionTopInset = self.currentSafeAreaInset.top + 48.0 + let expandedCaptionTopInset = self.currentSafeAreaInset.top + 8.0 + + var panelOriginY: CGFloat = 0.0 + var inputMediaFrame = CGRect(origin: CGPoint(x: 0.0, y: inputPanelHeight), size: CGSize(width: width, height: inputMediaHeight)) + if self.usesContainerLayout { + if isLandscapePhone { + panelOriginY = availableHeight + 16.0 + inputMediaFrame.origin.y = availableHeight + 16.0 + } else if case .emoji = self.currentInputMode { + inputMediaFrame.origin.y = availableHeight - inputMediaHeight + if self.currentIsCaptionAbove { + panelOriginY = expandedCaptionTopInset + } else { + panelOriginY = inputMediaFrame.minY - inputPanelHeight + } + } else { + if self.currentIsCaptionAbove { + panelOriginY = (retainedInputHeight > 0.0 ? expandedCaptionTopInset : collapsedCaptionTopInset) + } else { + let bottomOffset = max(self.currentContainerBottomInset, retainedInputHeight) + panelOriginY = availableHeight - inputPanelHeight - bottomOffset + } + inputMediaFrame.origin.y = availableHeight + } + } + + if self.isCaption { let makeOneLineLayout = TextNodeWithEntities.asyncLayout(self.oneLineNode) let (oneLineLayout, oneLineApply) = makeOneLineLayout(TextNodeLayoutArguments( attributedString: self.oneLineNodeAttributedText, @@ -942,8 +1215,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS displaySpoilers: false, displayEmbeddedItemsUnderSpoilers: false )) - - let oneLineFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: oneLineLayout.size) + + let oneLineFrame = CGRect( + origin: CGPoint( + x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, + y: panelOriginY + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel + ), + size: oneLineLayout.size + ) self.oneLineNode.textNode.frame = oneLineFrame let _ = oneLineApply(TextNodeWithEntities.Arguments( context: self.context, @@ -952,18 +1231,33 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS placeholderColor: self.presentationInterfaceState?.theme.chat.inputPanel.inputTextColor.withAlphaComponent(0.12) ?? .lightGray, attemptSynchronous: false )) - self.updateOneLineSpoiler() } + self.textPlaceholderNode.isHidden = inputHasText - - let additionalRightInset = self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: transition) - - let textInputFrame = CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right - additionalRightInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom) - transition.updateFrame(node: self.textInputContainer, frame: textInputFrame) - + let textInputFrame = CGRect( + x: leftInset + textFieldInsets.left, + y: panelOriginY + textFieldInsets.top, + width: baseWidth - textFieldInsets.left - textFieldInsets.right, + height: panelContentHeight - textFieldInsets.top - textFieldInsets.bottom + ) + let additionalRightInset = self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelContentHeight, panelOriginY: panelOriginY, textInputFrame: textInputFrame, transition: transition) + let updatedTextInputFrame = CGRect( + x: textInputFrame.minX, + y: textInputFrame.minY, + width: baseWidth - textFieldInsets.left - textFieldInsets.right - additionalRightInset, + height: textInputFrame.height + ) + transition.updateFrame(node: self.textInputContainer, frame: updatedTextInputFrame) + if let textInputNode = self.textInputNode { - let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) + let textFieldFrame = CGRect( + origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), + size: CGSize( + width: updatedTextInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), + height: updatedTextInputFrame.size.height - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom + ) + ) let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size if let presentationInterfaceState = self.presentationInterfaceState { textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) @@ -973,33 +1267,42 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS textInputNode.layout() } } - + + self.updateCounterTextNode(transition: transition, panelHeight: panelContentHeight, panelOriginY: panelOriginY) self.actionButtons.updateAccessibility() - - if self.glass { - panelHeight += 11.0 + + if let inputMediaNode = inputMediaNodeForLayout { + if isNewInputMediaNode && transition.isAnimated { + inputMediaNode.view.frame = inputMediaFrame.offsetBy(dx: 0.0, dy: inputMediaHeight) + inputMediaNode.view.alpha = 0.0 + inputMediaNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + inputMediaNode.view.alpha = 1.0 + transition.updateFrame(view: inputMediaNode.view, frame: inputMediaFrame) } - - return panelHeight + + self.currentHeight = totalHeight + return totalHeight } - - private func updateFieldAndButtonsLayout(inputHasText: Bool, panelHeight: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - guard let (width, leftInsetValue, rightInsetValue, additionalSideInsets, _, metrics, _) = self.validLayout else { + + private func updateFieldAndButtonsLayout(inputHasText: Bool, panelHeight: CGFloat, panelOriginY: CGFloat, textInputFrame: CGRect, transition: ContainedViewLayoutTransition) -> CGFloat { + guard let layout = self.validLayout else { return 0.0 } - - let leftInset = leftInsetValue + 8.0 - let rightInset = rightInsetValue + 8.0 - + + let width = layout.width + let leftInset = layout.leftInset + 8.0 + let rightInset = layout.rightInset + 8.0 + var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0 if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: layout.metrics) } var minimalHeight: CGFloat = textFieldMinHeight if !self.glass { minimalHeight += 14.0 } - + var panelHeight = panelHeight var composeButtonsOffset: CGFloat = 0.0 if self.isCaption { @@ -1010,15 +1313,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS panelHeight = minimalHeight } } - + let baseWidth = width - leftInset - rightInset - let textInputFrame = self.textInputContainer.frame - - var textFieldInsets = self.textFieldInsets(metrics: metrics) - if additionalSideInsets.right > 0.0 { - textFieldInsets.right += additionalSideInsets.right / 3.0 + + var textFieldInsets = self.textFieldInsets(metrics: layout.metrics) + if layout.additionalSideInsets.right > 0.0 { + textFieldInsets.right += layout.additionalSideInsets.right / 3.0 } - + var isPaidMessage = false var textBackgroundInset: CGFloat = 0.0 let actionButtonsSize: CGSize @@ -1039,16 +1341,16 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } else { actionButtonsSize = CGSize(width: 44.0, height: minimalHeight) } - + let actionButtonsOriginOffset: CGFloat = self.glass ? -6.0 : 0.0 - let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset + actionButtonsOriginOffset, y: panelHeight - minimalHeight - 1.0), size: actionButtonsSize) + let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset + actionButtonsOriginOffset, y: panelOriginY + panelHeight - minimalHeight - 1.0), size: actionButtonsSize) transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame) - + let textInputHeight = panelHeight - textFieldInsets.top - textFieldInsets.bottom let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: textInputHeight)) transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: textInputBackgroundFrame) - - transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)) + + transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: panelOriginY + textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)) transition.updateFrame(layer: self.textInputBackgroundImageNode.layer, frame: CGRect(x: 0.0, y: 0.0, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)) if self.isAIEnabled { @@ -1109,7 +1411,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var textInputViewRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) - + var colors: [String: UIColor] = [:] let colorKeys: [String] = [ "__allcolors__" @@ -1120,7 +1422,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } let animationComponent = LottieAnimationComponent( animation: LottieAnimationComponent.AnimationItem( - name: self.textInputNode?.textView.inputView == nil ? "input_anim_smileToKey" : "input_anim_keyToSmile", + name: self.currentInputMode == .text ? "input_anim_smileToKey" : "input_anim_keyToSmile", mode: .still(position: .begin) ), colors: colors, @@ -1140,20 +1442,20 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if self.glass { inputNodeOffset = CGPoint(x: -6.0, y: -4.0) } - transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width + inputNodeOffset.x, y: textInputBackgroundFrame.maxY - inputNodeSize.height + inputNodeOffset.y), size: inputNodeSize)) + transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width + inputNodeOffset.x, y: panelOriginY + textInputBackgroundFrame.maxY - inputNodeSize.height + inputNodeOffset.y), size: inputNodeSize)) } - + let placeholderFrame: CGRect if self.isCaption && !self.isFocused { - placeholderFrame = CGRect(origin: CGPoint(x: textInputFrame.minX + floorToScreenPixels((textInputBackgroundFrame.width - self.textPlaceholderNode.frame.width) / 2.0), y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size) + placeholderFrame = CGRect(origin: CGPoint(x: textInputFrame.minX + floorToScreenPixels((textInputBackgroundFrame.width - self.textPlaceholderNode.frame.width) / 2.0), y: panelOriginY + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size) } else { - placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size) + placeholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: panelOriginY + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size) } transition.updateFrame(node: self.textPlaceholderNode, frame: placeholderFrame) - + return isPaidMessage ? textBackgroundInset : 0.0 } - + private var skipUpdate = false public func chatInputTextNodeDidUpdateText() { if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { @@ -1162,24 +1464,22 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) }) refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) - + self.updateSpoiler() - + let inputTextState = self.inputTextState - + self.skipUpdate = true - + self.interfaceInteraction?.updateTextInputStateAndMode({ _, inputMode in return (inputTextState, inputMode) }) self.interfaceInteraction?.updateInputLanguage({ _ in return textInputNode.textInputMode?.primaryLanguage }) if self.isCaption, let presentationInterfaceState = self.presentationInterfaceState { self.presentationInterfaceState = presentationInterfaceState.updatedInterfaceState({ return $0.withUpdatedComposeInputState(inputTextState) }) - + } self.updateTextNodeText(animated: true) - - self.updateCounterTextNode(transition: .immediate) if let aiButton = self.aiButton { var aiButtonAlpha: CGFloat = self.heightDependentAiButtonAlpha @@ -1199,17 +1499,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS @objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { self.chatInputTextNodeDidUpdateText() } - + private func updateSpoiler() { guard let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { return } - + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor - + var rects: [CGRect] = [] var customEmojiRects: [(CGRect, ChatTextInputTextCustomEmojiAttribute)] = [] - + if let attributedText = textInputNode.attributedText { let beginning = textInputNode.textView.beginningOfDocument attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in @@ -1222,10 +1522,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + var startIndex: Int? var currentIndex: Int? - + let nsString = (attributedText.string as NSString) nsString.enumerateSubstrings(in: range, options: .byComposedCharacterSequences) { substring, range, _, _ in if let substring = substring, substring.rangeOfCharacter(from: .whitespacesAndNewlines) != nil { @@ -1239,14 +1539,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } currentIndex = range.location + range.length } - + if let currentStartIndex = startIndex, let currentIndex = currentIndex { startIndex = nil let endIndex = currentIndex addSpoiler(startIndex: currentStartIndex, endIndex: endIndex) } } - + if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { if let start = textInputNode.textView.position(from: beginning, offset: range.location), let end = textInputNode.textView.position(from: start, offset: range.length), let textRange = textInputNode.textView.textRange(from: start, to: end) { let textRects = textInputNode.textView.selectionRects(for: textRange) @@ -1258,7 +1558,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } }) } - + if !rects.isEmpty { let dustNode: InvisibleInkDustNode if let current = self.dustNode { @@ -1276,7 +1576,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS dustNode.removeFromSupernode() self.dustNode = nil } - + if !customEmojiRects.isEmpty { let customEmojiContainerView: CustomEmojiContainerView if let current = self.customEmojiContainerView { @@ -1292,21 +1592,21 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS textInputNode.textView.addSubview(customEmojiContainerView) self.customEmojiContainerView = customEmojiContainerView } - + customEmojiContainerView.update(emojiRects: customEmojiRects) } else if let customEmojiContainerView = self.customEmojiContainerView { customEmojiContainerView.removeFromSuperview() self.customEmojiContainerView = nil } } - + private func updateSpoilersRevealed(animated: Bool = true) { guard let textInputNode = self.textInputNode else { return } - + let selectionRange = textInputNode.textView.selectedRange - + var revealed = false if let attributedText = textInputNode.attributedText { attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, range, _ in @@ -1317,12 +1617,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } }) } - + guard self.spoilersRevealed != revealed else { return } self.spoilersRevealed = revealed - + if revealed { self.updateInternalSpoilersRevealed(true, animated: animated) } else { @@ -1331,26 +1631,26 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS }) } } - + private func updateInternalSpoilersRevealed(_ revealed: Bool, animated: Bool) { guard self.spoilersRevealed == revealed, let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState else { return } - + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - + textInputNode.textView.isScrollEnabled = false - + refreshChatTextInputAttributes(context: self.context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) }) - + textInputNode.attributedText = textAttributedStringForStateText(context: self.context, stateText: self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) }) - + if textInputNode.textView.subviews.count > 1, animated { let containerView = textInputNode.textView.subviews[1] if let canvasView = containerView.subviews.first { @@ -1371,7 +1671,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS Queue.mainQueue().after(0.1) { textInputNode.textView.isScrollEnabled = true } - + if animated { if revealed { let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear) @@ -1388,8 +1688,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS dustNode.alpha = revealed ? 0.0 : 1.0 } } - - private func updateCounterTextNode(transition: ContainedViewLayoutTransition) { + + private func updateCounterTextNode(transition: ContainedViewLayoutTransition, panelHeight: CGFloat, panelOriginY: CGFloat) { let inputTextMaxLength: Int32? if let maxCaptionLength = self.maxCaptionLength { inputTextMaxLength = maxCaptionLength @@ -1399,123 +1699,146 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let inputTextMaxLength = inputTextMaxLength { let textCount = Int32(textInputNode.textView.text.count) let counterColor: UIColor = textCount > inputTextMaxLength ? presentationInterfaceState.theme.chat.inputPanel.panelControlDestructiveColor : presentationInterfaceState.theme.chat.inputPanel.panelControlColor - + let remainingCount = max(-999, inputTextMaxLength - textCount) let counterText = remainingCount >= 5 ? "" : "\(remainingCount)" self.counterTextNode.attributedText = NSAttributedString(string: counterText, font: counterFont, textColor: counterColor) } else { self.counterTextNode.attributedText = NSAttributedString(string: "", font: counterFont, textColor: .black) } - - if let (width, leftInset, rightInset, _, maxHeight, metrics, _) = self.validLayout { + + if let layout = self.validLayout { let composeButtonsOffset: CGFloat = 0.0 - - let leftInset = leftInset + 8.0 - let rightInset = rightInset + 8.0 - - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset, maxHeight: maxHeight, metrics: metrics) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) + + let rightInset = layout.rightInset + 8.0 var textFieldMinHeight: CGFloat = 33.0 if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: layout.metrics) } let minimalHeight: CGFloat = 14.0 + textFieldMinHeight - + let counterSize = self.counterTextNode.updateLayout(CGSize(width: 44.0, height: 44.0)) - let actionButtonsOriginX = width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset - let counterFrame = CGRect(origin: CGPoint(x: actionButtonsOriginX, y: panelHeight - minimalHeight - counterSize.height + 3.0), size: CGSize(width: width - actionButtonsOriginX - rightInset, height: counterSize.height)) + let actionButtonsOriginX = layout.width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset + let counterFrame = CGRect(origin: CGPoint(x: actionButtonsOriginX, y: panelOriginY + panelHeight - minimalHeight - counterSize.height + 3.0), size: CGSize(width: layout.width - actionButtonsOriginX - rightInset, height: counterSize.height)) transition.updateFrame(node: self.counterTextNode, frame: counterFrame) } } - + private func toggleInputMode() { self.loadTextInputNodeIfNeeded() - + guard let textInputNode = self.textInputNode else { return } - - var shouldHaveInputView = false - if textInputNode.textView.isFirstResponder { - if textInputNode.textView.inputView == nil { - shouldHaveInputView = true + + switch self.currentInputMode { + case .text: + self.isTransitioningToTextKeyboard = false + self.currentInputMode = .emoji + self.applyCurrentInputMode(reload: textInputNode.textView.isFirstResponder) + if !textInputNode.textView.isFirstResponder { + textInputNode.textView.becomeFirstResponder() } - } else { - shouldHaveInputView = true - } - - if shouldHaveInputView { - let inputView = self.makeEntityInputView() - inputView?.insertText = { [weak self] text in - guard let strongSelf = self else { - return - } - - strongSelf.interfaceInteraction?.updateTextInputStateAndMode { textInputState, inputMode in - let inputText = NSMutableAttributedString(attributedString: textInputState.inputText) - - let range = textInputState.selectionRange - inputText.replaceCharacters(in: NSMakeRange(range.lowerBound, range.count), with: text) - - let selectionPosition = range.lowerBound + (text.string as NSString).length - - return (ChatTextInputState(inputText: inputText, selectionRange: selectionPosition ..< selectionPosition), inputMode) - } - } - inputView?.deleteBackwards = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.textInputNode?.textView.deleteBackward() - } - inputView?.switchToKeyboard = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.toggleInputMode() - } - inputView?.presentController = { [weak self] c in - guard let strongSelf = self else { - return - } - strongSelf.presentController(c) - } - - textInputNode.textView.inputView = inputView - } else { - textInputNode.textView.inputView = nil - } - - if textInputNode.textView.isFirstResponder { - textInputNode.textView.reloadInputViews() - } else { - textInputNode.textView.becomeFirstResponder() + self.requestRelayout(animated: true) + case .emoji: + self.activateInput() } } - + + private func requestRelayout(animated: Bool) { + self.updateHeight(animated) + self.heightUpdated?(animated) + } + + private func applyCurrentInputMode(reload: Bool) { + guard let textInputNode = self.textInputNode else { + return + } + + switch self.currentInputMode { + case .text: + if textInputNode.textView.inputView != nil { + textInputNode.textView.inputView = nil + if reload && textInputNode.textView.isFirstResponder { + textInputNode.textView.reloadInputViews() + } + } + case .emoji: + if !(textInputNode.textView.inputView is EmptyInputView) { + textInputNode.textView.inputView = EmptyInputView() + if reload && textInputNode.textView.isFirstResponder { + textInputNode.textView.reloadInputViews() + } + } + } + } + + private func insertTextFromInputMedia(_ text: NSAttributedString) { + self.loadTextInputNodeIfNeeded() + + guard let textInputNode = self.textInputNode else { + return + } + + let attributedText = NSMutableAttributedString(attributedString: textInputNode.attributedText ?? NSAttributedString()) + let range = textInputNode.selectedRange + attributedText.replaceCharacters(in: range, with: text) + let selectionPosition = range.lowerBound + text.length + textInputNode.attributedText = attributedText + textInputNode.selectedRange = NSRange(location: selectionPosition, length: 0) + self.chatInputTextNodeDidUpdateText() + } + + private func deleteBackwardsFromInputMedia() { + self.loadTextInputNodeIfNeeded() + + guard let textInputNode = self.textInputNode else { + return + } + if textInputNode.textView.isFirstResponder { + textInputNode.textView.deleteBackward() + return + } + + let attributedText = NSMutableAttributedString(attributedString: textInputNode.attributedText ?? NSAttributedString()) + let range = textInputNode.selectedRange + if range.length > 0 { + attributedText.deleteCharacters(in: range) + textInputNode.attributedText = attributedText + textInputNode.selectedRange = NSRange(location: range.location, length: 0) + self.chatInputTextNodeDidUpdateText() + } else if range.location > 0 { + let deleteRange = NSRange(location: range.location - 1, length: 1) + attributedText.deleteCharacters(in: deleteRange) + textInputNode.attributedText = attributedText + textInputNode.selectedRange = NSRange(location: deleteRange.location, length: 0) + self.chatInputTextNodeDidUpdateText() + } + } + private func updateTextNodeText(animated: Bool) { var inputHasText = false if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 { inputHasText = true } - + if let presentationInterfaceState = self.presentationInterfaceState { self.textPlaceholderNode.isHidden = inputHasText - + let textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) let textFont = Font.regular(baseFontSize) let accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor - + let attributedText = textAttributedStringForStateText(context: self.context, stateText: self.inputTextState.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: false, availableEmojis: Set(self.context.animatedEmojiStickersValue.keys), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) }) - + let range = (attributedText.string as NSString).range(of: "\n") if range.location != NSNotFound { let trimmedText = NSMutableAttributedString(attributedString: attributedText.attributedSubstring(from: NSMakeRange(0, range.location))) trimmedText.append(NSAttributedString(string: "\u{2026}", font: textFont, textColor: textColor)) - + self.oneLineNodeAttributedText = trimmedText } else { self.oneLineNodeAttributedText = attributedText @@ -1523,24 +1846,45 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } else { self.oneLineNodeAttributedText = nil } - + let panelHeight = self.updateTextHeight(animated: animated) - if self.isAttachment, let panelHeight = panelHeight { - let _ = self.updateFieldAndButtonsLayout(inputHasText: inputHasText, panelHeight: panelHeight, transition: .animated(duration: 0.2, curve: .easeInOut)) + if self.isAttachment, let panelHeight = panelHeight, let layout = self.validLayout { + let leftInset = layout.leftInset + 8.0 + let rightInset = layout.rightInset + 8.0 + let baseWidth = layout.width - leftInset - rightInset + + var textFieldInsets = self.textFieldInsets(metrics: layout.metrics) + if layout.additionalSideInsets.right > 0.0 { + textFieldInsets.right += layout.additionalSideInsets.right / 3.0 + } + + let textInputFrame = CGRect( + x: leftInset + textFieldInsets.left, + y: textFieldInsets.top, + width: baseWidth - textFieldInsets.left - textFieldInsets.right, + height: panelHeight - textFieldInsets.top - textFieldInsets.bottom + ) + let _ = self.updateFieldAndButtonsLayout( + inputHasText: inputHasText, + panelHeight: panelHeight, + panelOriginY: 0.0, + textInputFrame: textInputFrame, + transition: .animated(duration: 0.2, curve: .easeInOut) + ) } } - + private func updateOneLineSpoiler() { if let textLayout = self.oneLineNode.textNode.cachedLayout, !textLayout.spoilers.isEmpty { if self.oneLineDustNode == nil { let oneLineDustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.context.sharedContext.energyUsageSettings.fullTranslucency) self.oneLineDustNode = oneLineDustNode self.oneLineNode.textNode.supernode?.insertSubnode(oneLineDustNode, aboveSubnode: self.oneLineNode.textNode) - + } if let oneLineDustNode = self.oneLineDustNode { let textFrame = self.oneLineNode.textNode.frame.insetBy(dx: 0.0, dy: -3.0) - + oneLineDustNode.update(size: textFrame.size, color: .white, textColor: .white, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 0.0, dy: 3.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 0.0, dy: 3.0) }) oneLineDustNode.frame = textFrame } @@ -1551,39 +1895,40 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + private func updateTextHeight(animated: Bool) -> CGFloat? { - if let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, _) = self.validLayout { - let leftInset = leftInset + 8.0 - let rightInset = rightInset + 8.0 - - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right, maxHeight: maxHeight, metrics: metrics) - let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) - if !self.bounds.size.height.isEqual(to: panelHeight) { + if let layout = self.validLayout { + let leftInset = layout.leftInset + 8.0 + let rightInset = layout.rightInset + 8.0 + + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: layout.width - leftInset - rightInset - layout.additionalSideInsets.right, maxHeight: layout.textFieldMaxHeight, metrics: layout.metrics) + let panelContentHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: layout.metrics) + let totalHeight = panelContentHeight + (self.glass ? 11.0 : 0.0) + self.currentAdditionalInputHeight + if self.currentHeight != totalHeight { self.updateHeight(animated) self.heightUpdated?(animated) } - return panelHeight + return panelContentHeight } else { return nil } } - + public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool { if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero { self.sendButtonPressed() } return false } - + @objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool { return self.chatInputTextNodeShouldReturn(modifierFlags: []) } - + private func applyUpdateSendButtonIcon() { if let interfaceState = self.presentationInterfaceState { let sendButtonHasApplyIcon = interfaceState.interfaceState.editMessage != nil - + if sendButtonHasApplyIcon != self.actionButtons.sendButtonHasApplyIcon { self.actionButtons.sendButtonHasApplyIcon = sendButtonHasApplyIcon if self.actionButtons.sendButtonHasApplyIcon { @@ -1598,7 +1943,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + public func chatInputTextNodeDidChangeSelection(dueToEditing: Bool) { if !dueToEditing && !self.updatingInputState { let inputTextState = self.inputTextState @@ -1606,58 +1951,63 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.interfaceInteraction?.updateTextInputStateAndMode({ _, inputMode in return (inputTextState, inputMode) }) self.skipUpdate = false } - + if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState { if case .format = self.inputMenu.state { self.inputMenu.hide() } - + let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) - + self.updateSpoilersRevealed() } } - + @objc public func editableTextNodeDidChangeSelection(_ editableTextNode: ASEditableTextNode, fromSelectedRange: NSRange, toSelectedRange: NSRange, dueToEditing: Bool) { self.chatInputTextNodeDidChangeSelection(dueToEditing: dueToEditing) } - + public func chatInputTextNodeDidBeginEditing() { self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in return (.text, state.keyboardButtonsMessage?.id) }) self.inputMenu.activate() - + self.focusUpdated?(true) - - if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: false) + + if self.isCaption || self.currentInputMode != .text { + self.requestRelayout(animated: true) } } - + @objc public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) { self.chatInputTextNodeDidBeginEditing() } - + public func chatInputTextNodeDidFinishEditing() { guard let editableTextNode = self.textInputNode else { return } + let shouldUpdateLayout = self.isCaption || self.currentInputMode != .text self.storedInputLanguage = editableTextNode.textInputMode?.primaryLanguage self.inputMenu.deactivate() - + self.focusUpdated?(false) - - if self.isCaption, let (width, leftInset, rightInset, additionalSideInsets, maxHeight, metrics, isSecondary) = self.validLayout, let presentationInterfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: 0.0, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .animated(duration: 0.3, curve: .easeInOut), interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: false) + + if self.currentInputMode != .text { + self.currentInputMode = .text + self.applyCurrentInputMode(reload: false) + } + if shouldUpdateLayout { + self.requestRelayout(animated: true) } } - + public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) { self.chatInputTextNodeDidFinishEditing() } - + public func editableTextNodeTarget(forAction action: Selector) -> ASEditableTextNodeTargetForAction? { if action == makeSelectorFromString("_accessibilitySpeak:") { if case .format = self.inputMenu.state { @@ -1703,17 +2053,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return nil } - + @available(iOS 13.0, *) public func chatInputTextNodeMenu(forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu { guard let editableTextNode = self.textInputNode else { return UIMenu(children: suggestedActions) } - + var actions = suggestedActions - + if editableTextNode.attributedText == nil || editableTextNode.attributedText!.length == 0 || editableTextNode.selectedRange.length == 0 { - + } else { var children: [UIAction] = [ UIAction(title: self.strings?.TextFormat_Bold ?? "Bold", image: nil) { [weak self] (action) in @@ -1747,12 +2097,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } ] - + var hasSpoilers = true - if self.presentationInterfaceState?.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat { + if self.presentationInterfaceState?.chatLocation.peerId?.isSecretChat == true { hasSpoilers = false } - + if hasSpoilers { children.insert(UIAction(title: self.strings?.TextFormat_Quote ?? "Quote", image: nil) { [weak self] (action) in if let strongSelf = self { @@ -1770,18 +2120,18 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } }) } - + let formatMenu = UIMenu(title: self.strings?.TextFormat_Format ?? "Format", image: nil, children: children) actions.insert(formatMenu, at: 3) } return UIMenu(children: actions) } - + @available(iOS 16.0, *) public func editableTextNodeMenu(_ editableTextNode: ASEditableTextNode, forTextRange textRange: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu { return self.chatInputTextNodeMenu(forTextRange: textRange, suggestedActions: suggestedActions) } - + private var currentSpeechHolder: SpeechSynthesizerHolder? @objc func _accessibilitySpeak(_ sender: Any) { var text = "" @@ -1804,34 +2154,34 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS UIMenuController.shared.update() } } - + @objc func _showTextStyleOptions(_ sender: Any) { if let textInputNode = self.textInputNode { self.inputMenu.format(view: textInputNode.view, rect: textInputNode.selectionRect.offsetBy(dx: 0.0, dy: -textInputNode.textView.contentOffset.y).insetBy(dx: 0.0, dy: -1.0)) } } - + @objc func formatAttributesBold(_ sender: Any) { self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.bold, value: nil), inputMode) } } - + @objc func formatAttributesItalic(_ sender: Any) { self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.italic, value: nil), inputMode) } } - + @objc func formatAttributesMonospace(_ sender: Any) { self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.monospace, value: nil), inputMode) } } - + private var imitateFocus = false @objc func formatAttributesLink(_ sender: Any) { self.inputMenu.back() @@ -1840,24 +2190,24 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } self.interfaceInteraction?.openLinkEditing() } - + @objc func formatAttributesStrikethrough(_ sender: Any) { self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.strikethrough, value: nil), inputMode) } } - + @objc func formatAttributesUnderline(_ sender: Any) { self.inputMenu.back() self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.underline, value: nil), inputMode) } } - + @objc func formatAttributesSpoiler(_ sender: Any) { self.inputMenu.back() - + var animated = false if let attributedText = self.textInputNode?.attributedText { attributedText.enumerateAttributes(in: NSMakeRange(0, attributedText.length), options: [], using: { attributes, _, _ in @@ -1866,35 +2216,35 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } }) } - + self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.spoiler, value: nil), inputMode) } - + self.updateSpoilersRevealed(animated: animated) } - + @objc func formatAttributesQuote(_ sender: Any) { self.inputMenu.back() - + self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .quote, isCollapsed: false)), inputMode) } } - + @objc func formatAttributesCodeBlock(_ sender: Any) { self.inputMenu.back() - + self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in return (chatTextInputAddFormattingAttribute(current, attribute: ChatTextInputAttributes.block, value: ChatTextInputTextQuoteAttribute(kind: .code(language: nil), isCollapsed: false)), inputMode) } } - + public func chatInputTextNode(shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { guard let editableTextNode = self.textInputNode else { return true } - + var cleanText = text let removeSequences: [String] = ["\u{202d}", "\u{202c}"] for sequence in removeSequences { @@ -1906,7 +2256,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } } - + if cleanText != text { let string = NSMutableAttributedString(attributedString: editableTextNode.attributedText ?? NSAttributedString()) var textColor: UIColor = .black @@ -1928,11 +2278,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return true } - + @objc public func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { return self.chatInputTextNode(shouldChangeTextIn: range, replacementText: text) } - + public func chatInputTextNodeShouldCopy() -> Bool { self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in storeInputTextInPasteboard(current.inputText.attributedSubstring(from: NSMakeRange(current.selectionRange.lowerBound, current.selectionRange.count))) @@ -1940,21 +2290,21 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return false } - + @objc public func editableTextNodeShouldCopy(_ editableTextNode: ASEditableTextNode) -> Bool { return self.chatInputTextNodeShouldCopy() } - + public func chatInputTextNodeShouldPaste() -> Bool { let pasteboard = UIPasteboard.general - + var attributedString: NSAttributedString? if let data = pasteboard.data(forPasteboardType: kUTTypeRTF as String) { attributedString = chatInputStateStringFromRTF(data, type: NSAttributedString.DocumentType.rtf) } else if let data = pasteboard.data(forPasteboardType: "com.apple.flat-rtfd") { attributedString = chatInputStateStringFromRTF(data, type: NSAttributedString.DocumentType.rtfd) } - + if let attributedString = attributedString { self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in if let inputText = current.inputText.mutableCopy() as? NSMutableAttributedString { @@ -1969,22 +2319,22 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } return true } - + public func chatInputTextNodeShouldRespondToAction(action: Selector) -> Bool { return true } - + public func chatInputTextNodeTargetForAction(action: Selector) -> ChatInputTextNode.TargetForAction? { return nil } - + @objc public func editableTextNodeShouldPaste(_ editableTextNode: ASEditableTextNode) -> Bool { return self.chatInputTextNodeShouldPaste() } - + public func chatInputTextNodeBackspaceWhileEmpty() { } - + @objc private func aiButtonPressed() { self.invokeAICompose?() } @@ -2014,31 +2364,38 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } self.sendMessage(.generic, nil) } - + @objc func textInputBackgroundViewTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.ensureFocused() } } - + public var isFocused: Bool { if self.imitateFocus { return true } return self.textInputNode?.isFirstResponder() ?? false } - + public func ensureUnfocused() { + self.isTransitioningToTextKeyboard = false self.textInputNode?.resignFirstResponder() + if self.currentInputMode != .text { + self.currentInputMode = .text + self.requestRelayout(animated: true) + } + self.applyCurrentInputMode(reload: false) } - + public func ensureFocused() { self.imitateFocus = false - + if self.textInputNode == nil { self.loadTextInputNode() } - + + self.applyCurrentInputMode(reload: false) self.textInputNode?.becomeFirstResponder() } diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 2181367a5f..239607ee2e 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -26,7 +26,6 @@ swift_library( "//submodules/DirectionalPanGesture:DirectionalPanGesture", "//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode", "//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI", - "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI", "//submodules/ContextUI:ContextUI", "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/PhotoResources:PhotoResources", diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 88fd1b7b67..25d2bf0d7d 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -479,7 +479,9 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { if self.isDismissed { return } - self.bottomClipNode.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0 + + let containerCornerRadius = max(24.0, layout.deviceMetrics.screenCornerRadius) + self.bottomClipNode.cornerRadius = containerCornerRadius - 2.0 self.isUpdatingState = true diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index bb6cf603c6..6946cf7fa1 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import ComponentFlow import TelegramCore import TelegramPresentationData @@ -34,8 +33,9 @@ public enum AttachmentButtonType: Equatable { case sticker case emoji case audio + case link case standalone - + public var key: String { switch self { case .gallery: @@ -62,11 +62,13 @@ public enum AttachmentButtonType: Equatable { return "emoji" case .audio: return "audio" + case .link: + return "link" case .standalone: return "standalone" } } - + public static func ==(lhs: AttachmentButtonType, rhs: AttachmentButtonType) -> Bool { switch lhs { case .gallery: @@ -141,6 +143,12 @@ public enum AttachmentButtonType: Equatable { } else { return false } + case .link: + if case .link = rhs { + return true + } else { + return false + } case .standalone: if case .standalone = rhs { return true @@ -164,71 +172,71 @@ public protocol AttachmentContainable: ViewController, MinimizableController { var isInnerPanGestureEnabled: (() -> Bool)? { get } var mediaPickerContext: AttachmentMediaPickerContext? { get } var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { get } - + func isContainerPanningUpdated(_ panning: Bool) - + func resetForReuse() func prepareForReuse() - + func requestDismiss(completion: @escaping () -> Void) func shouldDismissImmediately() -> Bool - + func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) } public extension AttachmentContainable { func isContainerPanningUpdated(_ panning: Bool) { - + } - + func resetForReuse() { - + } - + func prepareForReuse() { - + } - + func requestDismiss(completion: @escaping () -> Void) { completion() } - + func shouldDismissImmediately() -> Bool { return true } - + func beforeMaximize(navigationController: NavigationController, completion: @escaping () -> Void) { completion() } - + var minimizedBounds: CGRect? { return nil } - + var isFullscreen: Bool { return false } - + var minimizedTopEdgeOffset: CGFloat? { return nil } - + var minimizedIcon: UIImage? { return nil } - + var minimizedProgress: Float? { return nil } - + var isPanGestureEnabled: (() -> Bool)? { return nil } - + var isInnerPanGestureEnabled: (() -> Bool)? { return nil } - + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? { return nil } @@ -248,25 +256,25 @@ public enum AttachmentMediaPickerAttachmentMode { public protocol AttachmentMediaPickerContext { var selectionCount: Signal { get } var caption: Signal { get } - + var hasCaption: Bool { get } var captionIsAboveMedia: Signal { get } func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void - + var canMakePaidContent: Bool { get } var price: Int64? { get } func setPrice(_ price: Int64) -> Void - + var hasTimers: Bool { get } - + var loadingProgress: Signal { get } var mainButtonState: Signal { get } var secondaryButtonState: Signal { get } var bottomPanelBackgroundColor: Signal { get } - + func mainButtonAction() func secondaryButtonAction() - + func setCaption(_ caption: NSAttributedString) func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) @@ -276,19 +284,19 @@ public extension AttachmentMediaPickerContext { var selectionCount: Signal { return .single(0) } - + var caption: Signal { return .single(nil) } - + var captionIsAboveMedia: Signal { return .single(false) } - + var hasCaption: Bool { return false } - + func setCaptionIsAboveMedia(_ captionIsAboveMedia: Bool) -> Void { } @@ -299,42 +307,42 @@ public extension AttachmentMediaPickerContext { var price: Int64? { return nil } - + func setPrice(_ price: Int64) -> Void { } - + var hasTimers: Bool { return false } - + var loadingProgress: Signal { return .single(nil) } - + var mainButtonState: Signal { return .single(nil) } - + var secondaryButtonState: Signal { return .single(nil) } - + var bottomPanelBackgroundColor: Signal { return .single(nil) } - + func setCaption(_ caption: NSAttributedString) { } - + func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { } - + func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { } - + func mainButtonAction() { } - + func secondaryButtonAction() { } } @@ -346,7 +354,7 @@ private func generateShadowImage(glass: Bool) -> UIImage? { let side = innerSide + middle + shadowInset * 2.0 return generateImage(CGSize(width: side, height: side), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - + context.saveGState() context.setShadow(offset: CGSize(), blur: glass ? 80.0 : 60.0, color: UIColor(white: 0.0, alpha: glass ? 0.3 : 0.4).cgColor) @@ -356,9 +364,9 @@ private func generateShadowImage(glass: Bool) -> UIImage? { context.addPath(firstPath) context.addPath(secondPath) context.fillPath() - + context.restoreGState() - + context.setBlendMode(.clear) context.addPath(firstPath) context.addPath(secondPath) @@ -367,9 +375,9 @@ private func generateShadowImage(glass: Bool) -> UIImage? { let path = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide, height: innerSide), cornerRadius: 10.0).cgPath context.addPath(path) context.fillPath() - + context.restoreGState() - + context.setBlendMode(.clear) context.addPath(path) context.fillPath() @@ -380,9 +388,9 @@ private func generateShadowImage(glass: Bool) -> UIImage? { private func generateMaskImage(glass: Bool) -> UIImage? { return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - + context.setFillColor(UIColor.white.cgColor) - + if glass { let firstPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 62.0).cgPath let secondPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 130.0), cornerRadius: 38.0).cgPath @@ -403,7 +411,7 @@ public class AttachmentController: ViewController, MinimizableController { case glass case legacy } - + private let context: AccountContext private let updatedPresentationData: (initial: PresentationData, signal: Signal)? private let style: Style @@ -414,12 +422,12 @@ public class AttachmentController: ViewController, MinimizableController { private let fromMenu: Bool private var hasTextInput: Bool private let isFullSize: Bool - private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? + private let customEmojiAvailable: Bool public var animateAppearance: Bool = false - + public var willDismiss: () -> Void = {} public var didDismiss: () -> Void = {} - + public var mediaPickerContext: AttachmentMediaPickerContext? { get { return self.node.mediaPickerContext @@ -428,49 +436,48 @@ public class AttachmentController: ViewController, MinimizableController { self.node.mediaPickerContext = newValue } } - + private let _ready = Promise() override public var ready: Promise { return self._ready } - + public private(set) var minimizedTopEdgeOffset: CGFloat? public private(set) var minimizedBounds: CGRect? public var minimizedIcon: UIImage? { return self.mainController.minimizedIcon } - + public var isFullscreen: Bool { return self.mainController.isFullscreen } - + public weak var attachmentButton: UIView? - + private final class Node: ASDisplayNode { private weak var controller: AttachmentController? fileprivate let dim: ASDisplayNode private let shadowNode: ASImageNode fileprivate let container: AttachmentContainer - private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? let panel: AttachmentPanel - + fileprivate var currentType: AttachmentButtonType? fileprivate var currentControllers: [AttachmentContainable] = [] - + private var validLayout: ContainerViewLayout? private var modalProgress: CGFloat = 0.0 fileprivate var isDismissing = false - + private let captionDisposable = MetaDisposable() private let mediaSelectionCountDisposable = MetaDisposable() - + private let loadingProgressDisposable = MetaDisposable() private let mainButtonStateDisposable = MetaDisposable() private let secondaryButtonStateDisposable = MetaDisposable() private let bottomPanelBackgroundColorDisposable = MetaDisposable() - + private var selectionCount: Int = 0 - + var mediaPickerContext: AttachmentMediaPickerContext? { didSet { if let mediaPickerContext = self.mediaPickerContext { @@ -553,37 +560,36 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + private let wrapperNode: ASDisplayNode - + private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - + private var isMinimizing = false - - init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + + init(controller: AttachmentController) { self.controller = controller - self.makeEntityInputView = makeEntityInputView - + if let updatedPresentationData = controller.updatedPresentationData { self.presentationData = updatedPresentationData.initial } else { self.presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } } - + self.dim = ASDisplayNode() self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) - + self.shadowNode = ASImageNode() self.shadowNode.isUserInteractionEnabled = false - + self.wrapperNode = ASDisplayNode() self.wrapperNode.clipsToBounds = true - + self.container = AttachmentContainer(presentationData: self.presentationData, isFullSize: controller.isFullSize, glass: controller._hasGlassStyle, hasPill: controller.style == .glass) self.container.canHaveKeyboardFocus = true - + let panelStyle: AttachmentPanel.Style switch controller.style { case .glass: @@ -591,37 +597,37 @@ public class AttachmentController: ViewController, MinimizableController { case .legacy: panelStyle = .legacy } - - self.panel = AttachmentPanel(controller: controller, style: panelStyle, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) + + self.panel = AttachmentPanel(controller: controller, style: panelStyle, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, customEmojiAvailable: controller.customEmojiAvailable, updatedPresentationData: controller.updatedPresentationData) self.panel.fromMenu = controller.fromMenu self.panel.isStandalone = controller.isStandalone - + super.init() - + self.clipsToBounds = false - + self.addSubnode(self.dim) self.addSubnode(self.shadowNode) self.addSubnode(self.wrapperNode) - + self.container.controllerRemoved = { [weak self] controller in if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing { strongSelf.currentControllers = strongSelf.currentControllers.filter { $0 !== controller } strongSelf.containerLayoutUpdated(layout, transition: .immediate) } } - + self.container.updateModalProgress = { [weak self] progress, topInset, bounds, transition in if let strongSelf = self, let controller = strongSelf.controller, let layout = strongSelf.validLayout, !strongSelf.isDismissing { var transition = transition if strongSelf.container.supernode == nil { transition = .animated(duration: 0.5, curve: .spring) } - + strongSelf.modalProgress = progress strongSelf.controller?.minimizedTopEdgeOffset = topInset strongSelf.controller?.minimizedBounds = bounds - + if !strongSelf.isMinimizing { if controller.style != .glass || strongSelf.didMaximizeOnce { strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) @@ -635,7 +641,7 @@ public class AttachmentController: ViewController, MinimizableController { strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } - + self.container.interactivelyDismissed = { [weak self] velocity in guard let self, let controller = self.controller, let layout = self.validLayout else { return true @@ -646,7 +652,7 @@ public class AttachmentController: ViewController, MinimizableController { delta -= minimizedTopEdgeOffset } let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0 - + if controller.shouldMinimizeOnSwipe?(self.currentType) == true { self.minimize(damping: damping, initialVelocity: initialVelocity) return false @@ -658,13 +664,13 @@ public class AttachmentController: ViewController, MinimizableController { } return true } - + self.container.isPanningUpdated = { [weak self] value in if let strongSelf = self, let currentController = strongSelf.currentControllers.last, !value { currentController.isContainerPanningUpdated(value) } } - + self.container.isPanGestureEnabled = { [weak self] in guard let self, let currentController = self.currentControllers.last else { return true @@ -675,7 +681,7 @@ public class AttachmentController: ViewController, MinimizableController { return true } } - + self.container.isInnerPanGestureEnabled = { [weak self] in guard let self, let currentController = self.currentControllers.last else { return true @@ -686,7 +692,7 @@ public class AttachmentController: ViewController, MinimizableController { return true } } - + self.container.shouldCancelPanGesture = { [weak self] in if let strongSelf = self, let currentController = strongSelf.currentControllers.last { if !currentController.shouldDismissImmediately() { @@ -698,7 +704,7 @@ public class AttachmentController: ViewController, MinimizableController { return false } } - + self.container.requestDismiss = { [weak self] in if let strongSelf = self, let currentController = strongSelf.currentControllers.last { currentController.requestDismiss { [weak self] in @@ -708,7 +714,7 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + self.panel.selectionChanged = { [weak self] type in if let strongSelf = self { return strongSelf.switchToController(type) @@ -716,25 +722,25 @@ public class AttachmentController: ViewController, MinimizableController { return false } } - + self.panel.longPressed = { [weak self] _ in if let strongSelf = self, let currentController = strongSelf.currentControllers.last { currentController.longTapWithTabBar?() } } - + self.panel.beganTextEditing = { [weak self] in if let strongSelf = self { strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring)) } } - + self.panel.textUpdated = { [weak self] text in if let strongSelf = self { strongSelf.mediaPickerContext?.setCaption(text) } } - + self.panel.sendMessagePressed = { [weak self] mode, parameters in if let strongSelf = self { switch mode { @@ -754,14 +760,14 @@ public class AttachmentController: ViewController, MinimizableController { guard let self, let controller = self.controller, let mediaPickerContext = self.mediaPickerContext else { return } - + guard let caption = await mediaPickerContext.caption.get() else { return } if caption.length == 0 { return } - + let textProcessingScreen = await controller.context.sharedContext.makeTextProcessingScreen( context: controller.context, theme: self.presentationData.theme, @@ -790,45 +796,51 @@ public class AttachmentController: ViewController, MinimizableController { self.controller?.push(textProcessingScreen) } } - + self.panel.onMainButtonPressed = { [weak self] in if let strongSelf = self { strongSelf.mediaPickerContext?.mainButtonAction() } } - + self.panel.onSecondaryButtonPressed = { [weak self] in if let strongSelf = self { strongSelf.mediaPickerContext?.secondaryButtonAction() } } - + self.panel.requestLayout = { [weak self] in if let strongSelf = self, let layout = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) } } - + self.panel.present = { [weak self] c in if let strongSelf = self { strongSelf.controller?.present(c, in: .window(.root)) } } - + self.panel.presentInGlobalOverlay = { [weak self] c in if let strongSelf = self { strongSelf.controller?.presentInGlobalOverlay(c, with: nil) } } - + self.panel.getNavigationController = { [weak self] in + guard let controller = self?.controller else { + return nil + } + return controller.navigationController as? NavigationController + } + self.panel.getCurrentSendMessageContextMediaPreview = { [weak self] in guard let self, let currentController = self.currentControllers.last else { return nil } - + return currentController.getCurrentSendMessageContextMediaPreview?() } - + if let updatedPresentationData = controller.updatedPresentationData { self.presentationDataDisposable = (updatedPresentationData.signal |> deliverOnMainQueue).start(next: { [weak self] presentationData in @@ -840,7 +852,7 @@ public class AttachmentController: ViewController, MinimizableController { }) } } - + deinit { self.captionDisposable.dispose() self.mediaSelectionCountDisposable.dispose() @@ -850,16 +862,16 @@ public class AttachmentController: ViewController, MinimizableController { self.bottomPanelBackgroundColorDisposable.dispose() self.presentationDataDisposable?.dispose() } - + private var inputContainerHeight: CGFloat? private var inputContainerNode: ASDisplayNode? override func didLoad() { super.didLoad() - + self.view.disablesInteractiveModalDismiss = true - + self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - + if let controller = self.controller { let _ = self.switchToController(controller.initialButton) if case let .app(bot) = controller.initialButton { @@ -884,14 +896,14 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + if let (inputContainerHeight, inputContainerNode, _) = self.controller?.getInputContainerNode() { self.inputContainerHeight = inputContainerHeight self.inputContainerNode = inputContainerNode self.addSubnode(inputContainerNode) } } - + private var didMaximizeOnce = false fileprivate func minimize(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil) { guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { @@ -911,25 +923,25 @@ public class AttachmentController: ViewController, MinimizableController { } return minimizedContainer }, animated: true) - + self.dim.isHidden = true - + self.isMinimizing = true self.container.update(isExpanded: true, force: true, transition: .immediate) self.isMinimizing = false - + Queue.mainQueue().after(0.45, { self.dim.isHidden = false }) } - + fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) { self.selectionCount = count if let layout = self.validLayout { self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) } } - + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { guard !self.isDismissing else { return @@ -948,7 +960,7 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool { guard self.currentType != type else { if self.isAnimating { @@ -1001,13 +1013,13 @@ public class AttachmentController: ViewController, MinimizableController { strongSelf.updateIsPanelVisible(isVisible, transition: transition) } } - + controller.cancelPanGesture = { [weak self] in if let strongSelf = self { strongSelf.container.cancelPanGesture() } } - + controller.isContainerPanning = { [weak self] in if let strongSelf = self { return strongSelf.container.isPanning @@ -1015,7 +1027,7 @@ public class AttachmentController: ViewController, MinimizableController { return false } } - + controller.isContainerExpanded = { [weak self] in if let strongSelf = self { return strongSelf.container.isExpanded @@ -1023,14 +1035,14 @@ public class AttachmentController: ViewController, MinimizableController { return false } } - + let previousController = strongSelf.currentControllers.last strongSelf.currentControllers = [controller] - + if previousType != nil && animated { strongSelf.animateSwitchTransition(controller, previousController: previousController) } - + if let layout = strongSelf.validLayout { strongSelf.switchingController = true strongSelf.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.3, curve: .spring) : .immediate) @@ -1045,7 +1057,7 @@ public class AttachmentController: ViewController, MinimizableController { } return shouldSwitch } - + private func animateSwitchTransition(_ controller: AttachmentContainable, previousController: AttachmentContainable?) { guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else { return @@ -1053,7 +1065,7 @@ public class AttachmentController: ViewController, MinimizableController { snapshotView.alpha = 0.0 snapshotView.frame = self.container.container.frame self.container.bottomClipNode.view.insertSubview(snapshotView, at: self.container.bottomClipNode.view.subviews.count) - + let _ = (controller.ready.get() |> filter { $0 @@ -1066,14 +1078,14 @@ public class AttachmentController: ViewController, MinimizableController { let _ = layout if case .compact = layout.metrics.widthClass { let offset = 10.0 - + let initialPosition = strongSelf.container.wrappingNode.layer.position let targetPosition = initialPosition.offsetBy(dx: 0.0, dy: offset) var startPosition = initialPosition if let presentation = strongSelf.container.wrappingNode.layer.presentation() { startPosition = presentation.position } - + strongSelf.container.wrappingNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in if let strongSelf = self, finished { strongSelf.container.wrappingNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.3, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in @@ -1084,26 +1096,26 @@ public class AttachmentController: ViewController, MinimizableController { } }) } - + snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.23, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() previousController?.resetForReuse() }) }) } - + private var isAnimating = false func animateIn() { guard let layout = self.validLayout, let controller = self.controller else { return } - + self.isAnimating = true if case .regular = layout.metrics.widthClass { if controller.animateAppearance { let targetPosition = self.position let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) - + self.position = startPosition let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition.animateView(allowUserInteraction: true, { @@ -1117,25 +1129,25 @@ public class AttachmentController: ViewController, MinimizableController { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1) } else { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) - + if case .glass = controller.style, let attachmentButton = controller.attachmentButton { let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: 110.0, initialVelocity: 1.1)) let cornersTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) let scaleTransition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .customSpring(damping: 110.0, initialVelocity: 0.0)) - + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) - + let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) - + let sourceButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view) let sourceButtonScale = sourceButtonFrame.width / targetFrame.width - + if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params { let containerView = ClipContainerView() containerView.update(bounds: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width)), topCornerRadius: targetFrame.width * 0.5, bottomCornerRadius: targetFrame.width * 0.5, boundsTransition: .immediate, cornersTransition: .immediate) containerView.frame = targetFrame self.view.addSubview(containerView) - + let localGlassView = GlassBackgroundView() localGlassView.update( size: targetFrame.size, @@ -1146,18 +1158,18 @@ public class AttachmentController: ViewController, MinimizableController { ) localGlassView.frame = CGRect(origin: .zero, size: targetFrame.size) containerView.contentView.addSubview(localGlassView) - + let initialContainerBounds = self.container.bounds let initialContainerFrame = self.container.frame let initialBottomClipRadius = self.container.bottomClipNode.cornerRadius - + let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view) self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((targetFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) self.container.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.container.bottomClipNode.cornerRadius = 0.0 containerView.contentView.addSubnode(self.container) - + let buttonIcon = GlassBackgroundView.ContentImageView() let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme) @@ -1169,8 +1181,9 @@ public class AttachmentController: ViewController, MinimizableController { } localGlassView.contentView.addSubview(buttonIcon) ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 0.0, toRadius: 10.0) - - containerView.update(bounds: CGRect(origin: .zero, size: targetFrame.size), topCornerRadius: 38.0, bottomCornerRadius: layout.deviceMetrics.screenCornerRadius - 2.0, boundsTransition: scaleTransition, cornersTransition: cornersTransition) + + let containerCornerRadius = max(24.0, layout.deviceMetrics.screenCornerRadius) + containerView.update(bounds: CGRect(origin: .zero, size: targetFrame.size), topCornerRadius: 38.0, bottomCornerRadius: containerCornerRadius - 2.0, boundsTransition: scaleTransition, cornersTransition: cornersTransition) scaleTransition.animateBounds(layer: containerView.layer, from: CGRect(origin: .zero, size: CGSize(width: targetFrame.width, height: targetFrame.width))) scaleTransition.animateTransformScale(view: containerView, from: sourceButtonScale) positionTransition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in @@ -1187,7 +1200,7 @@ public class AttachmentController: ViewController, MinimizableController { } else { let targetPosition = self.container.position let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) - + self.container.position = startPosition let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition.animateView(allowUserInteraction: true, { @@ -1198,19 +1211,19 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + func animateOut(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil, completion: @escaping () -> Void = {}) { guard let controller = self.controller else { return } self.isDismissing = true - + guard let layout = self.validLayout else { return } - + self.isAnimating = true - + switch layout.metrics.widthClass { case .regular: self.layer.allowsGroupOpacity = true @@ -1222,25 +1235,27 @@ public class AttachmentController: ViewController, MinimizableController { case .compact: let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) - + if case .glass = controller.style, let attachmentButton = controller.attachmentButton { let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: 110.0, initialVelocity: initialVelocity ?? 0.0)) let cornersTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity ?? 0.0)) let scaleTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity ?? 0.0)) - + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.22, curve: .easeInOut) let initialFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) - + let targetButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view) let targetButtonScale = targetButtonFrame.width / initialFrame.width - + if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params { let containerView = ClipContainerView() containerView.frame = initialFrame - containerView.update(bounds: CGRect(origin: .zero, size: initialFrame.size), topCornerRadius: 38.0, bottomCornerRadius: layout.deviceMetrics.screenCornerRadius - 2.0, boundsTransition: .immediate, cornersTransition: .immediate) - self.view.addSubview(containerView) + let containerCornerRadius = max(24.0, layout.deviceMetrics.screenCornerRadius) + containerView.update(bounds: CGRect(origin: .zero, size: initialFrame.size), topCornerRadius: 38.0, bottomCornerRadius: containerCornerRadius - 2.0, boundsTransition: .immediate, cornersTransition: .immediate) + self.view.addSubview(containerView) + let localGlassView = GlassBackgroundView() localGlassView.update( size: initialFrame.size, @@ -1251,14 +1266,14 @@ public class AttachmentController: ViewController, MinimizableController { ) localGlassView.frame = CGRect(origin: .zero, size: initialFrame.size) containerView.contentView.addSubview(localGlassView) - + let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view) self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((initialFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) self.container.isUserInteractionEnabled = false self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false) containerView.contentView.addSubnode(self.container) - + let buttonIcon = GlassBackgroundView.ContentImageView() let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme) @@ -1271,19 +1286,19 @@ public class AttachmentController: ViewController, MinimizableController { localGlassView.contentView.addSubview(buttonIcon) ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 10.0, toRadius: 0.0) ComponentTransition(buttonTransition).animateBlur(layer: sourceGlassView.contentView.layer, fromRadius: 10.0, toRadius: 0.0) - + containerView.update(bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width)), topCornerRadius: initialFrame.width * 0.5, bottomCornerRadius: initialFrame.width * 0.5, boundsTransition: scaleTransition, cornersTransition: cornersTransition) scaleTransition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: initialFrame.width, height: initialFrame.width))) - + scaleTransition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale) positionTransition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in let _ = self?.container.dismiss(transition: .immediate, completion: completion) self?.isAnimating = false }) - + localGlassView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, delay: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) scaleTransition.animateTransformScale(view: sourceGlassView, from: 1.0 / targetButtonScale) - + positionTransition.animatePosition(layer: sourceGlassView.layer, from: self.view.convert(initialFrame.center, to: sourceGlassView.superview), to: sourceGlassView.center) } } else { @@ -1292,11 +1307,11 @@ public class AttachmentController: ViewController, MinimizableController { let _ = self?.container.dismiss(transition: .immediate, completion: completion) self?.isAnimating = false }) - + if controller.style != .glass || self.didMaximizeOnce { self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) } - + if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() { self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition) self.containerLayoutUpdated(layout, transition: positionTransition) @@ -1304,11 +1319,11 @@ public class AttachmentController: ViewController, MinimizableController { } } } - + func scrollToTop() { self.currentControllers.last?.scrollToTop?() } - + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let controller = self.controller, controller.isInteractionDisabled() { return self.view @@ -1320,14 +1335,14 @@ public class AttachmentController: ViewController, MinimizableController { return result } } - + private var isUpdatingContainer = false private var switchingController = false - + private var hasButton = false - + private var isPanelVisible: Bool = true - + private func updateIsPanelVisible(_ isVisible: Bool, transition: ContainedViewLayoutTransition) { if self.isPanelVisible == isVisible { return @@ -1337,18 +1352,18 @@ public class AttachmentController: ViewController, MinimizableController { self.containerLayoutUpdated(layout, transition: transition) } } - + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout - + guard let controller = self.controller else { return } - + transition.updateFrame(node: self.dim, frame: CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 2.0))) - + let fromMenu = controller.fromMenu - + var containerLayout = layout let containerRect: CGRect if case .regular = layout.metrics.widthClass { @@ -1360,12 +1375,12 @@ public class AttachmentController: ViewController, MinimizableController { } else { let inputHeight = layout.inputHeight ?? 0.0 let availableHeight = layout.size.height - inputHeight - + let size = CGSize(width: 390.0, height: min(620.0, availableHeight)) - + let insets = layout.insets(options: [.input]) let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) - + let position: CGPoint let positionY = layout.size.height - size.height - insets.bottom - (inputHeight > 0.0 ? 0.0 : 54.0) if let sourceRect = controller.getSourceRect?() { @@ -1373,7 +1388,7 @@ public class AttachmentController: ViewController, MinimizableController { } else { position = CGPoint(x: masterWidth - 174.0, y: positionY) } - + if controller.isStandalone && !controller.forceSourceRect { var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0) if let inputHeight = layout.inputHeight, inputHeight > 88.0 { @@ -1386,7 +1401,7 @@ public class AttachmentController: ViewController, MinimizableController { containerLayout.size = containerRect.size containerLayout.intrinsicInsets.bottom = 12.0 containerLayout.inputHeight = nil - + if controller.isStandalone { self.wrapperNode.cornerRadius = 10.0 } else if self.wrapperNode.view.mask == nil { @@ -1395,11 +1410,11 @@ public class AttachmentController: ViewController, MinimizableController { maskView.contentMode = .scaleToFill self.wrapperNode.view.mask = maskView } - + if let maskView = self.wrapperNode.view.mask { transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size)) } - + self.shadowNode.alpha = 1.0 if self.shadowNode.image == nil { self.shadowNode.image = generateShadowImage(glass: controller.style == .glass) @@ -1417,13 +1432,13 @@ public class AttachmentController: ViewController, MinimizableController { containerHeight = layout.size.height } containerRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: containerHeight)) - + self.wrapperNode.cornerRadius = 0.0 self.shadowNode.alpha = 0.0 - + self.wrapperNode.view.mask = nil } - + var containerInsets = containerLayout.intrinsicInsets var hasPanel = false let hasButton = self.panel.isButtonVisible && !self.isDismissing @@ -1434,7 +1449,7 @@ public class AttachmentController: ViewController, MinimizableController { if !self.isPanelVisible && !self.panel.hasMediaAccessoryPanel { hasPanel = false } - + var panelOffset: CGFloat = 0.0 if case .glass = controller.style { if layout.metrics.isTablet { @@ -1443,14 +1458,14 @@ public class AttachmentController: ViewController, MinimizableController { panelOffset = 8.0 } } - + let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, hideButtons: !self.isPanelVisible && self.panel.hasMediaAccessoryPanel, transition: transition) - + if hasPanel || hasButton { containerInsets.bottom = panelHeight + panelOffset } - + var panelTransition = transition if isEffecitvelyCollapsedUpdated { if case .glass = controller.style { @@ -1466,59 +1481,59 @@ public class AttachmentController: ViewController, MinimizableController { panelY = containerRect.height } panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight))) - + var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0) shadowFrame.size.height -= 12.0 transition.updateFrame(node: self.shadowNode, frame: shadowFrame) transition.updateFrame(node: self.wrapperNode, frame: containerRect) - + if !self.isUpdatingContainer && !self.isDismissing { self.isUpdatingContainer = true - + let containerTransition: ContainedViewLayoutTransition if self.container.supernode == nil { containerTransition = .immediate } else { containerTransition = transition } - + let controllers = self.currentControllers if !self.isAnimating { containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) } - + let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) - + self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) - + if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing && !self.isAnimating { self.wrapperNode.addSubnode(self.container) - + if fromMenu, let _ = controller.getInputContainerNode() { self.addSubnode(self.panel) } else { self.container.addSubnode(self.panel) } - + self.animateIn() } - + self.isUpdatingContainer = false } } } - + public var requestController: (AttachmentButtonType, @escaping (AttachmentContainable?, AttachmentMediaPickerContext?) -> Void) -> Bool = { _, completion in completion(nil, nil) return true } - + public var getInputContainerNode: () -> (CGFloat, ASDisplayNode, () -> AttachmentController.InputPanelTransition?)? = { return nil } - + public var getSourceRect: (() -> CGRect?)? - + public var shouldMinimizeOnSwipe: ((AttachmentButtonType?) -> Bool)? - + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, @@ -1530,7 +1545,8 @@ public class AttachmentController: ViewController, MinimizableController { fromMenu: Bool = false, hasTextInput: Bool = true, isFullSize: Bool = false, - makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil }) + makeEntityInputView: @escaping () -> UIView? = { return nil }, + customEmojiAvailable: Bool = true) { self.context = context self.updatedPresentationData = updatedPresentationData @@ -1542,35 +1558,35 @@ public class AttachmentController: ViewController, MinimizableController { self.fromMenu = fromMenu self.hasTextInput = hasTextInput self.isFullSize = isFullSize - self.makeEntityInputView = makeEntityInputView - + self.customEmojiAvailable = customEmojiAvailable + super.init(navigationBarPresentationData: nil) - + self._hasGlassStyle = true //style == .glass - + self.statusBar.statusBarStyle = .Ignore self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true - + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.context.sharedContext.currentPresentationData.with { $0 }.strings.Common_Back, style: .plain, target: nil, action: nil) - + self.scrollToTop = { [weak self] in if let strongSelf = self { strongSelf.node.scrollToTop() } } } - + public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public var forceSourceRect = false - + fileprivate var isStandalone: Bool { return self.buttons.contains(.standalone) || self.buttons.count == 1 } - + public func convertToStandalone() { guard self.buttons != [.standalone] else { return @@ -1582,37 +1598,37 @@ public class AttachmentController: ViewController, MinimizableController { self.hasTextInput = false self.requestLayout(transition: .immediate) } - + public func minimizeIfNeeded() { if self.shouldMinimizeOnSwipe?(self.node.currentType) == true { self.node.minimize() } } - + public func updateSelectionCount(_ count: Int) { self.node.updateSelectionCount(count, animated: false) } - + private var node: Node { return self.displayNode as! Node } - + open override func loadDisplayNode() { - self.displayNode = Node(controller: self, makeEntityInputView: self.makeEntityInputView) + self.displayNode = Node(controller: self) self.displayNodeDidLoad() } - + private var dismissedFlag = false public func _dismiss() { super.dismiss(animated: false, completion: {}) } - + public var ensureUnfocused = true - + public func requestMinimize(topEdgeOffset: CGFloat?, initialVelocity: CGFloat?) { self.node.minimize() } - + public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if self.ensureUnfocused { self.view.endEditing(true) @@ -1638,31 +1654,31 @@ public class AttachmentController: ViewController, MinimizableController { self.node.container.removeFromSupernode() } } - + private func isInteractionDisabled() -> Bool { return false } - + public var isMinimized: Bool = false { didSet { self.mainController.isMinimized = self.isMinimized } } - + public var isMinimizable: Bool { return self.mainController.isMinimizable } - + public func shouldDismissImmediately() -> Bool { return self.mainController.shouldDismissImmediately() } - + private var validLayout: ContainerViewLayout? - + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let previousSize = self.validLayout?.size super.containerLayoutUpdated(layout, transition: transition) - + self.validLayout = layout if let previousSize, previousSize != layout.size { Queue.mainQueue().after(0.1) { @@ -1671,11 +1687,11 @@ public class AttachmentController: ViewController, MinimizableController { } self.node.containerLayoutUpdated(layout, transition: transition) } - + public var mainController: AttachmentContainable { return self.node.currentControllers.first! } - + public final class InputPanelTransition { let inputNode: ASDisplayNode let accessoryPanelNode: ASDisplayNode? @@ -1703,7 +1719,7 @@ public class AttachmentController: ViewController, MinimizableController { self.prepareForDismiss = prepareForDismiss } } - + public static func preloadAttachBotIcons(context: AccountContext) -> DisposableSet { let disposableSet = DisposableSet() let _ = (context.engine.messages.attachMenuBots() @@ -1711,17 +1727,17 @@ public class AttachmentController: ViewController, MinimizableController { |> deliverOnMainQueue).startStandalone(next: { bots in for bot in bots { for (name, file) in bot.icons { - if [.iOSAnimated, .placeholder].contains(name), let peer = PeerReference(bot.peer._asPeer()) { + if [.iOSAnimated, .placeholder].contains(name), let peer = PeerReference(bot.peer) { if case .placeholder = name { let path = context.account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) if !FileManager.default.fileExists(atPath: path) { let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in let accountResource = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) - - let fetchedFullSize = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file), reference: .media(media: .attachBot(peer: peer, media: file), resource: file.resource)) + + let fetchedFullSize = context.engine.resources.fetch(reference: .media(media: .attachBot(peer: peer, media: file), resource: file.resource), userLocation: .other, userContentType: MediaResourceUserContentType(file: file)) let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = accountResource.start() - + return ActionDisposable { fetchedFullSizeDisposable.dispose() fullSizeDisposable.dispose() @@ -1738,7 +1754,7 @@ public class AttachmentController: ViewController, MinimizableController { }) return disposableSet } - + public func makeContentSnapshotView() -> UIView? { let snapshotView = self.view.snapshotView(afterScreenUpdates: false) let navigationBarHeight = self.validLayout.flatMap { self.navigationLayout(layout: $0) }?.defaultContentHeight ?? 56.0 @@ -1766,30 +1782,30 @@ private func findParentGlassBackgroundView(_ view: UIView) -> GlassBackgroundVie private final class ClipContainerView: UIView { private let clipView = UIView() let contentView = UIView() - + override init(frame: CGRect) { super.init(frame: frame) - + self.clipView.clipsToBounds = true self.clipView.layer.cornerCurve = .continuous self.clipView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - + self.contentView.clipsToBounds = true self.contentView.layer.cornerCurve = .continuous self.contentView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] - + self.addSubview(self.clipView) self.clipView.addSubview(self.contentView) } - + required init?(coder: NSCoder) { preconditionFailure() } - + func update(bounds: CGRect, topCornerRadius: CGFloat, bottomCornerRadius: CGFloat, boundsTransition: ContainedViewLayoutTransition, cornersTransition: ContainedViewLayoutTransition) { cornersTransition.updateCornerRadius(layer: self.clipView.layer, cornerRadius: topCornerRadius) cornersTransition.updateCornerRadius(layer: self.contentView.layer, cornerRadius: bottomCornerRadius) - + boundsTransition.updateFrame(view: self.clipView, frame: CGRect(origin: .zero, size: bounds.size)) boundsTransition.updatePosition(layer: self.contentView.layer, position: CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)) boundsTransition.updateBounds(layer: self.contentView.layer, bounds: bounds) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 2b56d11c74..1927dd3991 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -12,7 +12,6 @@ import AccountContext import AttachmentTextInputPanelNode import ChatPresentationInterfaceState import ChatSendMessageActionUI -import ChatTextLinkEditUI import PhotoResources import AnimatedStickerComponent import SemanticStatusNode @@ -47,7 +46,7 @@ private final class IconComponent: Component { public let fileReference: FileMediaReference? public let animationName: String? public let tintColor: UIColor? - + public init(account: Account, name: String, fileReference: FileMediaReference?, animationName: String?, tintColor: UIColor?) { self.account = account self.name = name @@ -55,7 +54,7 @@ private final class IconComponent: Component { self.animationName = animationName self.tintColor = tintColor } - + public static func ==(lhs: IconComponent, rhs: IconComponent) -> Bool { if lhs.account !== rhs.account { return false @@ -74,23 +73,23 @@ private final class IconComponent: Component { } return false } - + public final class View: UIImageView { private var component: IconComponent? private var disposable: Disposable? - + override init(frame: CGRect) { super.init(frame: frame) } - + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + deinit { self.disposable?.dispose() } - + func update(component: IconComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { if self.component?.name != component.name || self.component?.fileReference?.media.fileId != component.fileReference?.media.fileId || self.component?.tintColor != component.tintColor { if let fileReference = component.fileReference { @@ -98,7 +97,7 @@ private final class IconComponent: Component { if !previousName.isEmpty { self.image = nil } - + self.disposable = (svgIconImageFile(account: component.account, fileReference: fileReference) |> runOn(Queue.concurrentDefaultQueue()) |> deliverOnMainQueue).startStrict(next: { [weak self] transform in @@ -120,15 +119,15 @@ private final class IconComponent: Component { } } self.component = component - + return availableSize } } - + public func makeView() -> View { return View(frame: CGRect()) } - + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, transition: transition) } @@ -149,7 +148,7 @@ private final class AttachButtonComponent: CombinedComponent { let theme: PresentationTheme let action: () -> Void let longPressAction: () -> Void - + init( context: AccountContext, style: Style, @@ -196,7 +195,7 @@ private final class AttachButtonComponent: CombinedComponent { } return true } - + static var body: Body { let icon = Child(IconComponent.self) let animatedIcon = Child(AnimatedStickerComponent.self) @@ -209,10 +208,10 @@ private final class AttachButtonComponent: CombinedComponent { var imageFile: TelegramMediaFile? var animationFile: TelegramMediaFile? var botPeer: EnginePeer? - + let component = context.component let strings = component.strings - + switch component.type { case .gallery: name = strings.Attachment_Gallery @@ -244,6 +243,9 @@ private final class AttachButtonComponent: CombinedComponent { case .audio: name = strings.Attachment_Audio imageName = "Chat/Attach Menu/Audio" + case .link: + name = strings.Attachment_Link + imageName = "Chat/Attach Menu/Link" case let .app(bot): botPeer = bot.peer name = bot.shortName @@ -271,7 +273,7 @@ private final class AttachButtonComponent: CombinedComponent { case .legacy: tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor } - + let iconSize = CGSize(width: 30.0, height: 30.0) var topInset: CGFloat = 4.0 + UIScreenPixel var spacing: CGFloat = 15.0 + UIScreenPixel @@ -279,7 +281,7 @@ private final class AttachButtonComponent: CombinedComponent { topInset += 5.0 spacing += UIScreenPixel } - + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - iconSize.width) / 2.0), y: topInset), size: iconSize) if let animationFile = animationFile { let icon = animatedIcon.update( @@ -302,10 +304,10 @@ private final class AttachButtonComponent: CombinedComponent { ) } else { var fileReference: FileMediaReference? - if let peer = botPeer.flatMap({ PeerReference($0._asPeer())}), let imageFile = imageFile { + if let peer = botPeer.flatMap({ PeerReference($0)}), let imageFile = imageFile { fileReference = .attachBot(peer: peer, media: imageFile) } - + let icon = icon.update( component: IconComponent( account: component.context.account, @@ -329,7 +331,7 @@ private final class AttachButtonComponent: CombinedComponent { case .legacy: titleFont = Font.regular(10.0) } - + let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString( @@ -344,9 +346,9 @@ private final class AttachButtonComponent: CombinedComponent { availableSize: CGSize(width: 64.0, height: context.availableSize.height), transition: .immediate ) - + let size = CGSize(width: max(component.isFirstOrLast ? context.availableSize.width : 64.0, title.size.width + 24.0), height: context.availableSize.height) - + let button = button.update( component: Rectangle( color: .clear, @@ -361,7 +363,7 @@ private final class AttachButtonComponent: CombinedComponent { context.add(title .position(CGPoint(x: titleFrame.midX, y: titleFrame.midY)) ) - + context.add(button .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) .gesture(.tap { @@ -373,7 +375,7 @@ private final class AttachButtonComponent: CombinedComponent { } })) ) - + return size } } @@ -385,58 +387,58 @@ private final class LoadingProgressNode: ASDisplayNode { self.foregroundNode.backgroundColor = self.color } } - + private let foregroundNode: ASDisplayNode - + init(color: UIColor) { self.color = color - + self.foregroundNode = ASDisplayNode() self.foregroundNode.backgroundColor = color - + super.init() - + self.addSubnode(self.foregroundNode) } - + private var _progress: CGFloat = 0.0 func updateProgress(_ progress: CGFloat, animated: Bool = false) { if self._progress == progress && animated { return } - + var animated = animated if (progress < self._progress && animated) { animated = false } - + let size = self.bounds.size - + self._progress = progress - + let transition: ContainedViewLayoutTransition if animated && progress > 0.0 { transition = .animated(duration: 0.7, curve: .spring) } else { transition = .immediate } - + let alpaTransition: ContainedViewLayoutTransition if animated { alpaTransition = .animated(duration: 0.3, curve: .easeInOut) } else { alpaTransition = .immediate } - + transition.updateFrame(node: self.foregroundNode, frame: CGRect(x: -2.0, y: 0.0, width: (size.width + 4.0) * progress, height: size.height)) - + let alpha: CGFloat = progress < 0.001 || progress > 0.999 ? 0.0 : 1.0 alpaTransition.updateAlpha(node: self.foregroundNode, alpha: alpha) } - + override func layout() { super.layout() - + self.foregroundNode.cornerRadius = self.frame.height / 2.0 } } @@ -445,42 +447,42 @@ private final class BadgeNode: ASDisplayNode { private var fillColor: UIColor private var strokeColor: UIColor private var textColor: UIColor - + private let textNode: ImmediateTextNode private let backgroundNode: ASImageNode - + private let font: UIFont = Font.with(size: 15.0, design: .round, weight: .bold) - + var text: String = "" { didSet { self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) self.invalidateCalculatedLayout() } } - + init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { self.fillColor = fillColor self.strokeColor = strokeColor self.textColor = textColor - + self.textNode = ImmediateTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false - + self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: nil, strokeWidth: 1.0) - + super.init() - + self.addSubnode(self.backgroundNode) self.addSubnode(self.textNode) - + self.isUserInteractionEnabled = false } - + func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { self.fillColor = fillColor self.strokeColor = strokeColor @@ -488,7 +490,7 @@ private final class BadgeNode: ASDisplayNode { self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) } - + func animateBump(incremented: Bool) { if incremented { let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) @@ -512,20 +514,20 @@ private final class BadgeNode: ASDisplayNode { }) } } - + func animateOut() { let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue self.backgroundNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) self.textNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) } - + func update(_ constrainedSize: CGSize) -> CGSize { let badgeSize = self.textNode.updateLayout(constrainedSize) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 8.0), height: 18.0) let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) self.backgroundNode.frame = backgroundFrame self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) - UIScreenPixel), size: badgeSize) - + return backgroundSize } } @@ -534,7 +536,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { private var state: AttachmentMainButtonState private var panelStyle: AttachmentPanel.Style? private var size: CGSize? - + private let backgroundAnimationNode: ASImageNode private var iconNode: ASImageNode? private var iconLayer: InlineStickerItemLayer? @@ -542,35 +544,35 @@ private final class MainButtonNode: HighlightTrackingButtonNode { private var badgeNode: BadgeNode? private let statusNode: SemanticStatusNode private var progressNode: ASImageNode? - + private var shimmerView: ShimmerEffectForegroundView? private var borderView: UIView? private var borderMaskView: UIView? private var borderShimmerView: ShimmerEffectForegroundView? - + private var context: AccountContext? - + override init(pointerStyle: PointerStyle? = nil) { self.state = AttachmentMainButtonState.initial - + self.backgroundAnimationNode = ASImageNode() self.backgroundAnimationNode.displaysAsynchronously = false - + self.textNode = ImmediateTextNode() self.textNode.textAlignment = .center self.textNode.displaysAsynchronously = false - + self.statusNode = SemanticStatusNode(backgroundNodeColor: .clear, foregroundNodeColor: .white) - + super.init(pointerStyle: pointerStyle) - + self.isExclusiveTouch = true self.clipsToBounds = true - + self.addSubnode(self.backgroundAnimationNode) self.addSubnode(self.textNode) self.addSubnode(self.statusNode) - + self.highligthedChanged = { [weak self] highlighted in if let self, self.state.isEnabled { if highlighted { @@ -583,22 +585,22 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } } } - + override func didLoad() { super.didLoad() - + if #available(iOS 13.0, *) { self.layer.cornerCurve = .continuous } } - + public func transitionToProgress() { guard self.progressNode == nil, let size = self.size else { return } - + self.isUserInteractionEnabled = false - + let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) rotationAnimation.duration = 1.0 @@ -607,88 +609,88 @@ private final class MainButtonNode: HighlightTrackingButtonNode { rotationAnimation.repeatCount = Float.infinity rotationAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) rotationAnimation.beginTime = 1.0 - + let buttonOffset: CGFloat = 0.0 let buttonWidth = size.width - + let progressNode = ASImageNode() - + let diameter: CGFloat = size.height - 22.0 let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - diameter) / 2.0), y: floorToScreenPixels((size.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)) progressNode.frame = progressFrame progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.state.textColor, diameter: diameter, lineWidth: 3.0) - + self.addSubnode(progressNode) - + progressNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) progressNode.layer.add(rotationAnimation, forKey: "progressRotation") self.progressNode = progressNode - + self.textNode.alpha = 0.0 self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - + self.iconLayer?.opacity = 0.0 self.iconLayer?.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - + self.shimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.borderShimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - + public func transitionFromProgress() { guard let progressNode = self.progressNode else { return } self.progressNode = nil - + progressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak progressNode, weak self] _ in progressNode?.removeFromSupernode() self?.isUserInteractionEnabled = true }) - + self.textNode.alpha = 1.0 self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - + self.iconLayer?.opacity = 1.0 self.iconLayer?.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - + self.shimmerView?.layer.removeAllAnimations() self.shimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.borderShimmerView?.layer.removeAllAnimations() self.borderShimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - + private func setupShimmering() { if self.state.hasShimmer { if self.shimmerView == nil { let shimmerView = ShimmerEffectForegroundView() shimmerView.isUserInteractionEnabled = false self.shimmerView = shimmerView - + shimmerView.layer.cornerRadius = 12.0 if #available(iOS 13.0, *) { shimmerView.layer.cornerCurve = .continuous } - + let borderView = UIView() borderView.isUserInteractionEnabled = false self.borderView = borderView - + let borderMaskView = UIView() borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel borderMaskView.layer.borderColor = UIColor.white.cgColor borderMaskView.layer.cornerRadius = 12.0 borderView.mask = borderMaskView self.borderMaskView = borderMaskView - + let borderShimmerView = ShimmerEffectForegroundView() self.borderShimmerView = borderShimmerView borderView.addSubview(borderShimmerView) - + self.view.addSubview(shimmerView) self.view.addSubview(borderView) - + self.updateShimmerParameters() - + if let size = self.size, let context = self.context, let style = self.panelStyle { self.updateLayout(size: size, context: context, style: style, state: state, transition: .immediate) } @@ -698,19 +700,19 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.borderView?.removeFromSuperview() self.borderMaskView?.removeFromSuperview() self.borderShimmerView?.removeFromSuperview() - + self.shimmerView = nil self.borderView = nil self.borderMaskView = nil self.borderShimmerView = nil } } - + func updateShimmerParameters() { guard let shimmerView = self.shimmerView, let borderShimmerView = self.borderShimmerView else { return } - + let color = UIColor.white let alpha: CGFloat let borderAlpha: CGFloat @@ -724,14 +726,14 @@ private final class MainButtonNode: HighlightTrackingButtonNode { borderAlpha = 0.3 compositingFilter = nil } - + shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true) borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true) - + shimmerView.layer.compositingFilter = compositingFilter borderShimmerView.layer.compositingFilter = compositingFilter } - + private func setupGradientAnimations() { if let _ = self.backgroundAnimationNode.layer.animation(forKey: "movement") { } else { @@ -742,15 +744,15 @@ private final class MainButtonNode: HighlightTrackingButtonNode { newValue -= self.backgroundAnimationNode.frame.width * 0.35 } self.backgroundAnimationNode.position = CGPoint(x: newValue, y: self.backgroundAnimationNode.bounds.size.height / 2.0) - + CATransaction.begin() - + let animation = CABasicAnimation(keyPath: "position.x") animation.duration = 4.5 animation.fromValue = previousValue animation.toValue = newValue animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) - + CATransaction.setCompletionBlock { [weak self] in self?.setupGradientAnimations() } @@ -759,31 +761,31 @@ private final class MainButtonNode: HighlightTrackingButtonNode { CATransaction.commit() } } - + func updateLayout(size: CGSize, context: AccountContext, style: AttachmentPanel.Style, state: AttachmentMainButtonState, animateBackground: Bool = false, transition: ContainedViewLayoutTransition) { let previousState = self.state self.context = context self.state = state self.panelStyle = style self.size = size - + switch style { case .glass: self.cornerRadius = size.height * 0.5 case .legacy: self.cornerRadius = 12.0 } - + self.isUserInteractionEnabled = state.isVisible - + self.setupShimmering() - + let colorUpdated = previousState.textColor != state.textColor if let progressNode = self.progressNode, colorUpdated { let diameter: CGFloat = size.height - 22.0 progressNode.image = generateIndefiniteActivityIndicatorImage(color: state.textColor, diameter: diameter, lineWidth: 3.0) } - + var textFrame: CGRect = .zero if let text = state.text { let font: UIFont @@ -794,10 +796,10 @@ private final class MainButtonNode: HighlightTrackingButtonNode { font = Font.semibold(17.0) } self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: state.textColor) - + let textSize = self.textNode.updateLayout(size) textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - + switch state.background { case let .color(backgroundColor): self.backgroundAnimationNode.image = nil @@ -821,7 +823,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { locations.append(delta * CGFloat(i)) } self.backgroundAnimationNode.image = generateGradientImage(size: CGSize(width: 200.0, height: 50.0), colors: backgroundColors, locations: locations, direction: .horizontal) - + self.backgroundAnimationNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width * 2.4, height: size.height)) if self.backgroundAnimationNode.layer.animation(forKey: "movement") == nil { self.backgroundAnimationNode.position = CGPoint(x: size.width * 2.4 / 2.0 - self.backgroundAnimationNode.frame.width * 0.35, y: size.height / 2.0) @@ -831,7 +833,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.backgroundColor = UIColor(rgb: 0x8878ff) } } - + if let badge = state.badge { let badgeNode: BadgeNode var badgeTransition = transition @@ -858,7 +860,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.badgeNode = nil badgeNode.removeFromSupernode() } - + if let iconName = state.iconName { let iconNode: ASImageNode if let current = self.iconNode { @@ -878,7 +880,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.iconNode = nil iconNode.removeFromSupernode() } - + if let iconCustomEmojiId = state.iconCustomEmojiId { let iconLayer: InlineStickerItemLayer if let current = self.iconLayer { @@ -898,7 +900,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { ) self.layer.addSublayer(iconLayer) self.iconLayer = iconLayer - + if transition.isAnimated { iconLayer.animateScale(from: 0.01, to: 1.0, duration: 0.2) iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -909,7 +911,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { iconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - iconSize.width - 6.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - iconSize.height) * 0.5)), size: iconSize) } else if let iconLayer = self.iconLayer { self.iconLayer = nil - + if transition.isAnimated { iconLayer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) iconLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in @@ -919,14 +921,14 @@ private final class MainButtonNode: HighlightTrackingButtonNode { iconLayer.removeFromSuperlayer() } } - + if self.textNode.frame.width.isZero { self.textNode.frame = textFrame } else { self.textNode.bounds = CGRect(origin: .zero, size: textFrame.size) transition.updatePosition(node: self.textNode, position: textFrame.center) } - + if previousState.progress != state.progress { if state.progress == .center { self.transitionToProgress() @@ -934,7 +936,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.transitionFromProgress() } } - + if let shimmerView = self.shimmerView, let borderView = self.borderView, let borderMaskView = self.borderMaskView, let borderShimmerView = self.borderShimmerView { let buttonFrame = CGRect(origin: .zero, size: size) let buttonWidth = size.width @@ -943,14 +945,14 @@ private final class MainButtonNode: HighlightTrackingButtonNode { transition.updateFrame(view: borderView, frame: buttonFrame) transition.updateFrame(view: borderMaskView, frame: buttonFrame) transition.updateFrame(view: borderShimmerView, frame: buttonFrame) - + shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: buttonWidth * 4.0, y: 0.0), size: size), within: CGSize(width: buttonWidth * 9.0, height: buttonHeight)) borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: buttonWidth * 4.0, y: 0.0), size: size), within: CGSize(width: buttonWidth * 9.0, height: buttonHeight)) } - + let statusSize = CGSize(width: 20.0, height: 20.0) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: size.width - statusSize.width - 15.0, y: floorToScreenPixels((size.height - statusSize.height) / 2.0)), size: statusSize)) - + self.statusNode.foregroundNodeColor = state.textColor self.statusNode.transitionToState(state.progress == .side ? .progress(value: nil, cancelEnabled: false, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 0.0, lineWidth: 2.0), animateRotation: true) : .none) } @@ -961,9 +963,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case glass case legacy } - + private let panelStyle: Style - + private weak var controller: AttachmentController? private let context: AccountContext private let isScheduledMessages: Bool @@ -973,47 +975,46 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog private var peerDisposable: Disposable? private var mediaStatusDisposable: Disposable? private var playlistPreloadDisposable: Disposable? - + private var iconDisposables: [MediaId: Disposable] = [:] private var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)? private var playlistLocation: SharedMediaPlaylistLocation? private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)? private var dismissingMediaAccessoryPanel: ASDisplayNode? - + private var presentationInterfaceState: ChatPresentationInterfaceState private var interfaceInteraction: ChatPanelInterfaceInteraction? - - private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? - + private let customEmojiAvailable: Bool + private let containerNode: ASDisplayNode private var backgroundView: GlassBackgroundView? private var liquidLensView: LiquidLensView? private let backgroundNode: NavigationBackgroundNode private let scrollNode: ASScrollNode private let separatorNode: ASDisplayNode - + private let selectionNode: ASImageNode - + private var itemsContainer = UIView() private var itemViews: [AnyHashable: ComponentHostView] = [:] private var selectedItemsContainer = UIView() private var selectedItemViews: [AnyHashable: ComponentHostView] = [:] private var itemSizes: [AnyHashable: CGSize] = [:] - + private var tabSelectionRecognizer: TabSelectionRecognizer? private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable, isLifted: Bool)? private var lensIsLifted = false - + private var textInputPanelNode: AttachmentTextInputPanelNode? private var progressNode: LoadingProgressNode? private var mainButtonNode: MainButtonNode private var secondaryButtonNode: MainButtonNode - + private var loadingProgress: CGFloat? private var mainButtonState: AttachmentMainButtonState = .initial private var secondaryButtonState: AttachmentMainButtonState = .initial private var customBottomPanelBackgroundColor: UIColor? - + private var hideButtons: Bool = false private var elevateProgress: Bool = false private var buttons: [AttachmentButtonType] = [] @@ -1021,7 +1022,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog private var selectionOverrideIndex: Int? private(set) var isSelecting: Bool = false private var selectionCount: Int = 0 - + private var _isButtonVisible: Bool = false var isButtonVisible: Bool { return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible @@ -1051,13 +1052,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog var hasMediaAccessoryPanel: Bool { return self.shouldDisplayMediaAccessoryPanel } - + private var validLayout: ContainerViewLayout? private var scrollLayout: (width: CGFloat, contentSize: CGSize)? - + var fromMenu: Bool = false var isStandalone: Bool = false - + var selectionChanged: (AttachmentButtonType) -> Bool = { _ in return false } var longPressed: (AttachmentButtonType) -> Void = { _ in } @@ -1068,43 +1069,43 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog var requestLayout: () -> Void = {} var present: (ViewController) -> Void = { _ in } var presentInGlobalOverlay: (ViewController) -> Void = { _ in } - + var getNavigationController: () -> NavigationController? = { nil } + var getCurrentSendMessageContextMediaPreview: (() -> ChatSendMessageContextScreenMediaPreview?)? - + var onMainButtonPressed: () -> Void = { } var onSecondaryButtonPressed: () -> Void = { } - - init(controller: AttachmentController, style: Style, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + + init(controller: AttachmentController, style: Style, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, customEmojiAvailable: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?) { self.controller = controller self.context = context self.panelStyle = style self.updatedPresentationData = updatedPresentationData self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.isScheduledMessages = isScheduledMessages - - self.makeEntityInputView = makeEntityInputView - - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) - + self.customEmojiAvailable = customEmojiAvailable + + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil).updatedCustomEmojiAvailable(customEmojiAvailable) + self.containerNode = ASDisplayNode() self.containerNode.clipsToBounds = false - + self.scrollNode = ASScrollNode() self.scrollNode.clipsToBounds = true - + self.selectionNode = ASImageNode() - + self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = self.presentationData.theme.rootController.tabBar.separatorColor - + self.mainButtonNode = MainButtonNode() self.secondaryButtonNode = MainButtonNode() - + super.init() - + self.addSubnode(self.containerNode) - + switch style { case .glass: self.scrollNode.cornerRadius = glassButtonSize.height * 0.5 @@ -1113,18 +1114,18 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.containerNode.addSubnode(self.separatorNode) self.containerNode.addSubnode(self.scrollNode) } - + self.addSubnode(self.secondaryButtonNode) self.addSubnode(self.mainButtonNode) - + self.mainButtonNode.addTarget(self, action: #selector(self.mainButtonPressed), forControlEvents: .touchUpInside) self.secondaryButtonNode.addTarget(self, action: #selector(self.secondaryButtonPressed), forControlEvents: .touchUpInside) - + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { + }, deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, reportMessages: { _, _ in }, blockMessageAuthor: { _, _ in @@ -1189,7 +1190,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }, finishMediaRecording: { _ in }, stopMediaRecording: { }, lockMediaRecording: { - }, resumeMediaRecording: { + }, resumeMediaRecording: { }, deleteRecordedMedia: { }, sendRecordedMedia: { _, _ in }, displayRestrictedInfo: { _, _ in @@ -1213,9 +1214,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }, toggleMessageStickerStarred: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in - }, getNavigationController: { - return nil - }, presentGlobalOverlayController: { _, _ in + }, getNavigationController: { [weak self] in + return self?.getNavigationController() + }, presentGlobalOverlayController: { [weak self] controller, _ in + self?.presentInGlobalOverlay(controller) }, navigateFeed: { }, openGrouping: { }, toggleSilentPost: { @@ -1237,7 +1239,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog inputMode = state.inputMode return state }) - + var link: String? if let text { text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in @@ -1248,7 +1250,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in + let controller = strongSelf.context.sharedContext.makeLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link, _ in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link = link { strongSelf.updateChatPresentationInterfaceState(animated: true, { state in @@ -1270,7 +1272,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog strongSelf.present(controller) } }, openDateEditing: { - + }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { [weak self] node, gesture in guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else { @@ -1280,12 +1282,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation?.peerId else { return } - + var hasEntityKeyboard = false if case .media = strongSelf.presentationInterfaceState.inputMode { hasEntityKeyboard = true } - + let effectItems: Signal<[ReactionItem]?, NoError> if strongSelf.presentationInterfaceState.chatLocation.peerId != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser { effectItems = effectMessageReactions(context: strongSelf.context) @@ -1293,7 +1295,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } else { effectItems = .single(nil) } - + let availableMessageEffects = strongSelf.context.availableMessageEffects |> take(1) let hasPremium = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) |> map { peer -> Bool in @@ -1302,7 +1304,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } return user.isPremium } - + let _ = (combineLatest( strongSelf.context.account.viewTracker.peerView(peerId) |> take(1), effectItems, @@ -1323,7 +1325,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if peer.id.isTelegramNotifications { sendWhenOnlineAvailable = false } - + let mediaPreview = strongSelf.getCurrentSendMessageContextMediaPreview?() let isReady: Signal if let mediaPreview { @@ -1334,7 +1336,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } else { isReady = .single(true) } - + var captionIsAboveMedia: Signal = .single(false) var canMakePaidContent = false var currentPrice: Int64? @@ -1345,7 +1347,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog currentPrice = mediaPickerContext.price hasTimers = mediaPickerContext.hasTimers } - + let _ = (combineLatest( isReady, captionIsAboveMedia |> take(1), @@ -1355,7 +1357,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog guard let strongSelf else { return } - + let controller = makeChatSendMessageActionSheetController( initialData: initialData, context: strongSelf.context, @@ -1420,7 +1422,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }) }) }, openScheduledMessages: { - }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { @@ -1437,7 +1438,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }, openSendAsPeer: { _, _ in }, presentChatRequestAdminInfo: { }, displayCopyProtectionTip: { _, _ in - }, openWebView: { _, _, _, _ in + }, openWebView: { _, _, _, _ in }, updateShowWebView: { _ in }, insertText: { _ in }, backwardsDeleteText: { @@ -1453,7 +1454,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }, openMessagePayment: { }, openBoostToUnrestrict: { }, updateRecordingTrimRange: { _, _, _, _ in - }, dismissAllTooltips: { + }, dismissAllTooltips: { }, editTodoMessage: { _, _, _ in }, dismissUrlPreview: { }, dismissForwardMessages: { @@ -1471,7 +1472,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }, chatController: { return nil }, statuses: nil) - + if case .glass = style { self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState |> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in @@ -1497,7 +1498,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 || strongSelf.peerMessagesPlaylistLocation != updatedPeerMessagesPlaylistLocation { - + if let playlistStateAndType { strongSelf.playlistStateAndType = ( playlistStateAndType.1.item, @@ -1512,7 +1513,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog strongSelf.playlistStateAndType = nil strongSelf.playlistLocation = nil } - + if hadMediaAccessoryPanel != strongSelf.hasMediaAccessoryPanel { strongSelf.requestLayout() } else if let layout = strongSelf.validLayout { @@ -1529,12 +1530,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } }) } - + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in if let strongSelf = self { strongSelf.presentationData = presentationData - + strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor strongSelf.selectionNode.backgroundColor = presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) @@ -1546,27 +1547,37 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog strongSelf.dismissingMediaAccessoryPanel = nil dismissingMediaAccessoryPanel.removeFromSupernode() } - + strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) }) - + if let layout = strongSelf.validLayout { let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, selectionCount: strongSelf.selectionCount, elevateProgress: strongSelf.elevateProgress, hideButtons: strongSelf.hideButtons, transition: .immediate) } } }).strict() - + if let peerId = chatLocation?.peerId { self.peerDisposable = ((self.context.account.viewTracker.peerView(peerId) |> map { view -> StarsAmount? in if let data = view.cachedData as? CachedUserData { return data.sendPaidMessageStars } else if let channel = peerViewMainPeer(view) as? TelegramChannel { - if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - return nil - } else if let cachedData = view.cachedData as? CachedChannelData, let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero { - return nil + if channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + return nil + } else if let cachedData = view.cachedData as? CachedChannelData, let value = cachedData.sendPaidMessageStars, value == .zero { + return nil + } else { + return channel.sendPaidMessageStars + } } else { - return channel.sendPaidMessageStars + if channel.flags.contains(.isCreator) || channel.adminRights != nil { + return nil + } else if let cachedData = view.cachedData as? CachedChannelData, let value = cachedData.sendPaidMessageStars { + return value == .zero ? nil : value + } else { + return channel.sendPaidMessageStars + } } } else { return nil @@ -1581,7 +1592,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog })) } } - + deinit { self.presentationDataDisposable?.dispose() self.peerDisposable?.dispose() @@ -1591,34 +1602,35 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog disposable.dispose() } } - + override func didLoad() { super.didLoad() if #available(iOS 13.0, *) { self.containerNode.layer.cornerCurve = .continuous } - + + self.scrollNode.view.scrollsToTop = false self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false - + self.view.accessibilityTraits = .tabBar } - + func requestLayout(transition: ContainedViewLayoutTransition) { if let layout = self.validLayout { let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, hideButtons: self.hideButtons, transition: transition) } } - + @objc private func mainButtonPressed() { self.onMainButtonPressed() } - + @objc private func secondaryButtonPressed() { self.onSecondaryButtonPressed() } - + private func mediaPlaybackStatusSignal() -> Signal { let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState |> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in @@ -1632,7 +1644,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog return .single(value) |> delay(0.1, queue: .mainQueue()) } } - + return delayedStatus |> map { state -> MediaPlayerStatus in if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading { @@ -1642,7 +1654,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } } } - + private func dismissMediaAccessoryPanel(transition: ContainedViewLayoutTransition) { guard let (mediaAccessoryPanel, _) = self.mediaAccessoryPanel else { return @@ -1656,12 +1668,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } }) } - + private func presentAudioRateTooltip(baseRate: AudioPlaybackRate, changeType: MediaNavigationAccessoryPanel.ChangeType) { guard let controller = self.controller else { return } - + var hasTooltip = false controller.forEachController({ controller in if let controller = controller as? UndoOverlayController { @@ -1670,7 +1682,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } return true }) - + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let text: String? let rate: CGFloat? @@ -1701,12 +1713,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog text = nil rate = nil } - + var showTooltip = true if case .sliderChange = changeType { showTooltip = false } - + if let rate, let text, showTooltip { controller.present( UndoOverlayController( @@ -1725,7 +1737,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog ) } } - + private func makeMediaAccessoryPanel() -> MediaNavigationAccessoryPanel { let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, presentationData: self.presentationData, displayBackground: false, customTintColor: self.presentationData.theme.rootController.tabBar.textColor) mediaAccessoryPanel.getController = { [weak self] in @@ -1745,7 +1757,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } let _ = (self.context.sharedContext.accountManager.transaction { transaction -> AudioPlaybackRate in let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings - + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) }) @@ -1778,13 +1790,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } return mediaAccessoryPanel } - + private func updateMediaAccessoryPanel(frame: CGRect?, transition: ContainedViewLayoutTransition) { guard case .glass = self.panelStyle, self.hasMediaAccessoryPanel, let frame, let (item, previousItem, nextItem, order, type, _) = self.playlistStateAndType else { self.dismissMediaAccessoryPanel(transition: transition) return } - + let mediaAccessoryPanel: MediaNavigationAccessoryPanel let isNewPanel: Bool if let (currentPanel, currentType) = self.mediaAccessoryPanel, currentType == type { @@ -1802,7 +1814,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.mediaAccessoryPanel = (mediaAccessoryPanel, type) isNewPanel = true } - + mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.mediaPlaybackStatusSignal() switch order { @@ -1813,7 +1825,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .random: mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, nil, nil) } - + let inset: CGFloat = self.hideButtons ? 19.0 : 0.0 if isNewPanel { mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: inset, rightInset: inset, isHidden: false, transition: .immediate) @@ -1822,15 +1834,15 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog transition.updateFrame(node: mediaAccessoryPanel, frame: frame) mediaAccessoryPanel.updateLayout(size: frame.size, leftInset: inset, rightInset: inset, isHidden: false, transition: transition) } - + transition.updateAlpha(node: mediaAccessoryPanel.containerNode.headerNode.separatorNode, alpha: self.hideButtons ? 0.0 : 1.0) } - + func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { transition.updateAlpha(node: self.separatorNode, alpha: alpha) transition.updateAlpha(node: self.backgroundNode, alpha: alpha) } - + func updateCaption(_ caption: NSAttributedString) { if !caption.string.isEmpty { self.loadTextNodeIfNeeded() @@ -1841,23 +1853,23 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, f, completion: completion) } - + private func updateChatPresentationInterfaceState(update: Bool = true, transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { let presentationInterfaceState = f(self.presentationInterfaceState) - + let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState - + self.presentationInterfaceState = presentationInterfaceState - + if update { if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated) - + self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText) } } } - + func updateSelectedIndex(_ index: Int) { let hadMediaAccessoryPanel = self.hasMediaAccessoryPanel self.selectedIndex = index @@ -1866,7 +1878,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.requestLayout(transition: .immediate) } } - + var buttonSize: CGSize { switch self.panelStyle { case .glass: @@ -1875,7 +1887,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog return legacyButtonSize } } - + private func item(at point: CGPoint) -> AnyHashable? { let contentOffset = self.scrollNode.view.contentOffset.x let point = point.offsetBy(dx: contentOffset, dy: 0.0) @@ -1896,7 +1908,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } return closestItem?.0 } - + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { guard let liquidLensView = self.liquidLensView else { return @@ -1907,7 +1919,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] { let startX = itemView.frame.minX self.selectionGestureState = (startX, startX, itemId, !self.scrollNode.view.isScrollEnabled) - + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } case .changed: @@ -1924,9 +1936,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if !selectionGestureState.isLifted { self.lensIsLifted = true self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) - + self.liquidLensView?.clipsToBounds = false - + Queue.mainQueue().after(0.1, { self.lensIsLifted = false self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) @@ -1935,7 +1947,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog }) }) } - + self.selectionGestureState = nil if case .ended = recognizer.state { guard let index = self.buttons.firstIndex(where: { AnyHashable($0.key) == selectionGestureState.itemId }) else { @@ -1948,7 +1960,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.selectedIndex = index if self.buttons.count > 5, let button = self.itemViews[button.key] { let transition = ComponentTransition.spring(duration: 0.4) - + let scrollView = self.scrollNode.view let targetRect = button.frame.insetBy(dx: -35.0, dy: 0.0) @@ -1976,22 +1988,24 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog break } } - + func updateViews(transition: ComponentTransition) { guard let layout = self.validLayout else { return } - + var buttons = self.buttons if buttons.count == 1 { buttons.removeAll() } - + var width = layout.size.width if buttons.count == 3 { width = smallPanelWidth + } else if buttons.count == 4 { + width = 300 } - + var panelSideInset: CGFloat switch self.panelStyle { case .glass: @@ -1999,9 +2013,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .legacy: panelSideInset = 3.0 } - + var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1))) - if buttons.count == 3 { + if buttons.count == 3 || buttons.count == 4 { distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, buttons.count - 1))) } let internalWidth = distanceBetweenNodes * CGFloat(max(0, buttons.count - 1)) @@ -2017,10 +2031,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .legacy: leftNodeOriginX = (width - internalWidth) / 2.0 } - if buttons.count == 3 { + if buttons.count == 3 || buttons.count == 4 { leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0 } - + if buttons.count > maxButtonsToFit && layout.size.width < layout.size.height { switch self.panelStyle { case .glass: @@ -2032,19 +2046,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } leftNodeOriginX = layout.safeInsets.left + buttonWidth / 2.0 } - + var validIds = Set() - + var selectionFrame = CGRect() var mostRightX = 0.0 for i in 0 ..< buttons.count { let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) let buttonFrame = CGRect(origin: CGPoint(x: originX, y: -3.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height)) mostRightX = buttonFrame.maxX - + let type = buttons[i] let _ = validIds.insert(type.key) - + var buttonTransition = transition let buttonView: ComponentHostView let selectedButtonView: ComponentHostView @@ -2056,27 +2070,27 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog buttonView = ComponentHostView() self.itemViews[type.key] = buttonView self.itemsContainer.addSubview(buttonView) - + selectedButtonView = ComponentHostView() self.selectedItemViews[type.key] = selectedButtonView self.selectedItemsContainer.addSubview(selectedButtonView) } - + if case let .app(bot) = type { for (name, file) in bot.icons { if [.default, .iOSAnimated, .iOSSettingsStatic, .placeholder].contains(name) { - if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(bot.peer._asPeer()) { + if self.iconDisposables[file.fileId] == nil, let peer = PeerReference(bot.peer) { if case .placeholder = name { let account = self.context.account let path = account.postbox.mediaBox.cachedRepresentationCompletePath(file.resource.id, representation: CachedPreparedSvgRepresentation()) if !FileManager.default.fileExists(atPath: path) { let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in let accountResource = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedPreparedSvgRepresentation(), complete: false, fetch: true) - + let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file), reference: .media(media: .attachBot(peer: peer, media: file), resource: file.resource)) let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = accountResource.start() - + return ActionDisposable { fetchedFullSizeDisposable.dispose() fullSizeDisposable.dispose() @@ -2107,7 +2121,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if strongSelf.selectionChanged(type) { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) - + if buttons.count > 5, let button = strongSelf.itemViews[type.key] { strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) } @@ -2124,7 +2138,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height) ) self.itemSizes[type.key] = actualButtonSize - + let _ = selectedButtonView.update( transition: buttonTransition, component: AnyComponent(AttachButtonComponent( @@ -2143,7 +2157,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog environment: {}, containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height) ) - + if i == self.selectedIndex { selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize) } @@ -2171,6 +2185,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog accessibilityTitle = "Emoji" case .audio: accessibilityTitle = self.presentationData.strings.Attachment_Audio + case .link: + accessibilityTitle = self.presentationData.strings.Attachment_Link case let .app(bot): accessibilityTitle = bot.shortName case .standalone: @@ -2192,20 +2208,20 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog for id in removeIds { self.itemViews.removeValue(forKey: id) } - + selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0) self.selectionNode.cornerRadius = selectionFrame.height * 0.5 transition.setFrame(view: self.selectionNode.view, frame: selectionFrame) - + mostRightX += layout.safeInsets.right + 3.0 - + let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height) if contentSize != self.scrollNode.view.contentSize && self.scrollNode.view.bounds.width > 0.0 { self.scrollNode.view.contentSize = contentSize self.scrollNode.view.isScrollEnabled = contentSize.width - self.scrollNode.view.bounds.width > 1.0 self.liquidLensView?.clipsToBounds = self.scrollNode.view.isScrollEnabled } - + if !self.hideButtons && self.itemsContainer.isHidden { Queue.mainQueue().after(0.3, { self.itemsContainer.isHidden = false @@ -2216,15 +2232,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.selectedItemsContainer.isHidden = self.hideButtons } } - + private func loadTextNodeIfNeeded() { if let _ = self.textInputPanelNode { } else { - let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, glass: self.panelStyle == .glass, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, glass: self.panelStyle == .glass, isAttachment: true, isScheduledMessages: self.isScheduledMessages, customEmojiAvailable: self.customEmojiAvailable, presentController: { [weak self] c in if let strongSelf = self { strongSelf.present(c) } - }, makeEntityInputView: self.makeEntityInputView) + }, presentInGlobalOverlay: { [weak self] c in + self?.presentInGlobalOverlay(c) + }, getNavigationController: { [weak self] in + return self?.getNavigationController() + }) if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_disable_ai_chat"] as? Double, value == 1.0 { } else if let peerId = self.presentationInterfaceState.chatLocation.peerId, peerId.namespace != Namespaces.Peer.SecretChat { textInputPanelNode.isAIEnabled = true @@ -2252,16 +2272,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } self.addSubnode(textInputPanelNode) self.textInputPanelNode = textInputPanelNode - + textInputPanelNode.alpha = self.isSelecting ? 1.0 : 0.0 textInputPanelNode.isUserInteractionEnabled = self.isSelecting } } - + func updateLoadingProgress(_ progress: CGFloat?) { self.loadingProgress = progress } - + func updateMainButtonState(_ mainButtonState: AttachmentMainButtonState?) { var currentButtonState = self.mainButtonState if mainButtonState == nil { @@ -2269,7 +2289,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } self.mainButtonState = mainButtonState ?? currentButtonState } - + func updateSecondaryButtonState(_ secondaryButtonState: AttachmentMainButtonState?) { var currentButtonState = self.secondaryButtonState if secondaryButtonState == nil { @@ -2277,19 +2297,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } self.secondaryButtonState = secondaryButtonState ?? currentButtonState } - + func updateCustomBottomPanelBackgroundColor(_ color: UIColor?) { self.customBottomPanelBackgroundColor = color self.backgroundNode.updateColor(color: self.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .animated(duration: 0.2, curve: .linear)) } - + let animatingTransitionPromise = ValuePromise(false) private(set) var animatingTransition = false { didSet { self.animatingTransitionPromise.set(self.animatingTransition) } } - + func animateTransitionIn(inputTransition: AttachmentController.InputPanelTransition, transition: ContainedViewLayoutTransition) { guard !self.animatingTransition, let inputNodeSnapshotView = inputTransition.inputNode.view.snapshotView(afterScreenUpdates: false) else { return @@ -2298,21 +2318,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog return } self.animatingTransition = true - + let targetButtonColor = self.mainButtonNode.backgroundColor self.mainButtonNode.backgroundColor = inputTransition.menuButtonBackgroundView.backgroundColor transition.updateBackgroundColor(node: self.mainButtonNode, color: targetButtonColor ?? .clear) - + transition.animateFrame(layer: self.mainButtonNode.layer, from: inputTransition.menuButtonNode.frame) transition.animatePosition(node: self.mainButtonNode.textNode, from: CGPoint(x: inputTransition.menuButtonNode.frame.width / 2.0, y: inputTransition.menuButtonNode.frame.height / 2.0)) - + let targetButtonCornerRadius = self.mainButtonNode.cornerRadius self.mainButtonNode.cornerRadius = inputTransition.menuButtonNode.cornerRadius transition.updateCornerRadius(node: self.mainButtonNode, cornerRadius: targetButtonCornerRadius) self.mainButtonNode.subnodeTransform = CATransform3DMakeScale(0.2, 0.2, 1.0) transition.updateSublayerTransformScale(node: self.mainButtonNode, scale: 1.0) self.mainButtonNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - + let menuContentDelta = (self.mainButtonNode.frame.width - inputTransition.menuButtonNode.frame.width) / 2.0 menuIconSnapshotView.frame = inputTransition.menuIconNode.frame.offsetBy(dx: inputTransition.menuButtonNode.frame.minX, dy: inputTransition.menuButtonNode.frame.minY) self.view.addSubview(menuIconSnapshotView) @@ -2320,26 +2340,26 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog menuIconSnapshotView?.removeFromSuperview() }) transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x + menuContentDelta, y: self.mainButtonNode.position.y)) - + menuTextSnapshotView.frame = inputTransition.menuTextNode.frame.offsetBy(dx: inputTransition.menuButtonNode.frame.minX + 19.0, dy: inputTransition.menuButtonNode.frame.minY) self.view.addSubview(menuTextSnapshotView) menuTextSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak menuTextSnapshotView] _ in menuTextSnapshotView?.removeFromSuperview() }) transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x + menuContentDelta, y: self.mainButtonNode.position.y)) - + inputNodeSnapshotView.clipsToBounds = true inputNodeSnapshotView.contentMode = .right inputNodeSnapshotView.frame = CGRect(x: inputTransition.menuButtonNode.frame.maxX, y: 0.0, width: inputNodeSnapshotView.frame.width - inputTransition.menuButtonNode.frame.maxX, height: inputNodeSnapshotView.frame.height) self.view.addSubview(inputNodeSnapshotView) - + let targetInputPosition = CGPoint(x: inputNodeSnapshotView.center.x + inputNodeSnapshotView.frame.width, y: self.mainButtonNode.position.y) transition.updatePosition(layer: inputNodeSnapshotView.layer, position: targetInputPosition, completion: { [weak inputNodeSnapshotView, weak self] _ in inputNodeSnapshotView?.removeFromSuperview() self?.animatingTransition = false }) } - + private var dismissed = false func animateTransitionOut(inputTransition: AttachmentController.InputPanelTransition, dismissed: Bool, transition: ContainedViewLayoutTransition) { guard !self.animatingTransition, let inputNodeSnapshotView = inputTransition.inputNode.view.snapshotView(afterScreenUpdates: false) else { @@ -2348,28 +2368,28 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if dismissed { inputTransition.prepareForDismiss() } - + self.animatingTransition = true self.dismissed = dismissed - + let action = { guard let menuIconSnapshotView = inputTransition.menuIconNode.view.snapshotView(afterScreenUpdates: false), let menuTextSnapshotView = inputTransition.menuTextNode.view.snapshotView(afterScreenUpdates: false) else { return } - + let sourceButtonColor = self.mainButtonNode.backgroundColor transition.updateBackgroundColor(node: self.mainButtonNode, color: inputTransition.menuButtonBackgroundView.backgroundColor ?? .clear) - + let sourceButtonFrame = self.mainButtonNode.frame transition.updateFrame(node: self.mainButtonNode, frame: inputTransition.menuButtonNode.frame) let sourceButtonTextPosition = self.mainButtonNode.textNode.position transition.updatePosition(node: self.mainButtonNode.textNode, position: CGPoint(x: inputTransition.menuButtonNode.frame.width / 2.0, y: inputTransition.menuButtonNode.frame.height / 2.0)) - + let sourceButtonCornerRadius = self.mainButtonNode.cornerRadius transition.updateCornerRadius(node: self.mainButtonNode, cornerRadius: inputTransition.menuButtonNode.cornerRadius) transition.updateSublayerTransformScale(node: self.mainButtonNode, scale: 0.2) self.mainButtonNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - + let menuContentDelta = (sourceButtonFrame.width - inputTransition.menuButtonNode.frame.width) / 2.0 var menuIconSnapshotViewFrame = inputTransition.menuIconNode.frame.offsetBy(dx: inputTransition.menuButtonNode.frame.minX + menuContentDelta, dy: inputTransition.menuButtonNode.frame.minY) menuIconSnapshotViewFrame.origin.y = self.mainButtonNode.position.y - menuIconSnapshotViewFrame.height / 2.0 @@ -2377,14 +2397,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog self.view.addSubview(menuIconSnapshotView) menuIconSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x - menuContentDelta, y: inputTransition.menuButtonNode.position.y)) - + var menuTextSnapshotViewFrame = inputTransition.menuTextNode.frame.offsetBy(dx: inputTransition.menuButtonNode.frame.minX + 19.0 + menuContentDelta, dy: inputTransition.menuButtonNode.frame.minY) menuTextSnapshotViewFrame.origin.y = self.mainButtonNode.position.y - menuTextSnapshotViewFrame.height / 2.0 menuTextSnapshotView.frame = menuTextSnapshotViewFrame self.view.addSubview(menuTextSnapshotView) menuTextSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x - menuContentDelta, y: inputTransition.menuButtonNode.position.y)) - + inputNodeSnapshotView.clipsToBounds = true inputNodeSnapshotView.contentMode = .right let targetInputFrame = CGRect(x: inputTransition.menuButtonNode.frame.maxX, y: 0.0, width: inputNodeSnapshotView.frame.width - inputTransition.menuButtonNode.frame.maxX, height: inputNodeSnapshotView.frame.height) @@ -2393,11 +2413,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog transition.updateFrame(layer: inputNodeSnapshotView.layer, frame: targetInputFrame, completion: { [weak inputNodeSnapshotView, weak menuIconSnapshotView, weak menuTextSnapshotView, weak self] _ in inputNodeSnapshotView?.removeFromSuperview() self?.animatingTransition = false - + if !dismissed { menuIconSnapshotView?.removeFromSuperview() menuTextSnapshotView?.removeFromSuperview() - + self?.mainButtonNode.backgroundColor = sourceButtonColor self?.mainButtonNode.frame = sourceButtonFrame self?.mainButtonNode.textNode.position = sourceButtonTextPosition @@ -2406,20 +2426,20 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } }) } - + if dismissed { Queue.mainQueue().after(0.01, action) } else { action() } } - + func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, selectionCount: Int, elevateProgress: Bool, hideButtons: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = layout self.buttons = buttons self.elevateProgress = elevateProgress self.hideButtons = hideButtons - + if selectionCount != self.selectionCount { self.selectionCount = selectionCount self.updateChatPresentationInterfaceState(update: false, transition: .immediate, { state in @@ -2432,54 +2452,55 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } }) } - + let isButtonVisibleUpdated = self._isButtonVisible != self.mainButtonState.isVisible self._isButtonVisible = self.mainButtonState.isVisible - + let isSelectingUpdated = self.isSelecting != isSelecting self.isSelecting = isSelecting - + self.scrollNode.isUserInteractionEnabled = !isSelecting - + let isAnyButtonVisible = self.mainButtonState.isVisible || self.secondaryButtonState.isVisible let isNarrowButton = isAnyButtonVisible && self.mainButtonState.font == .regular - + let isTwoVerticalButtons = self.mainButtonState.isVisible && self.secondaryButtonState.isVisible && [.top, .bottom].contains(self.secondaryButtonState.position) let isTwoHorizontalButtons = self.mainButtonState.isVisible && self.secondaryButtonState.isVisible && [.left, .right].contains(self.secondaryButtonState.position) - + var insets = layout.insets(options: []) if let inputHeight = layout.inputHeight, inputHeight > 0.0 && (isSelecting || isAnyButtonVisible) { insets.bottom = inputHeight } else if layout.intrinsicInsets.bottom > 0.0 { insets.bottom = layout.intrinsicInsets.bottom } - + let topAccessoryHeight: CGFloat if self.hasMediaAccessoryPanel { topAccessoryHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight } else { topAccessoryHeight = 0.0 } - + if isSelecting { self.loadTextNodeIfNeeded() } else { self.textInputPanelNode?.ensureUnfocused() } - + let textPanelSideInset: CGFloat = 16.0 let defaultPanelSideInset: CGFloat = glassPanelSideInset let panelSideInset: CGFloat = (isSelecting ? textPanelSideInset : defaultPanelSideInset) + layout.safeInsets.left var textPanelHeight: CGFloat = 0.0 + var visualTextPanelHeight: CGFloat = 0.0 var textPanelWidth: CGFloat = 0.0 if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = isSelecting - + var panelTransition = transition if textInputPanelNode.frame.width.isZero { panelTransition = .immediate } - let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: layout.safeInsets.bottom, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: layout.size.height / 2.0, availableHeight: layout.size.height, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) let panelFrame = CGRect(x: 0.0, y: topAccessoryHeight, width: layout.size.width, height: panelHeight) if textInputPanelNode.frame.width.isZero { textInputPanelNode.frame = panelFrame @@ -2487,14 +2508,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog transition.updateFrame(node: textInputPanelNode, frame: panelFrame) if panelFrame.height > 0.0 { textPanelHeight = panelFrame.height + visualTextPanelHeight = max(0.0, textPanelHeight - textInputPanelNode.additionalInputHeight) } else { textPanelHeight = self.panelStyle == .glass ? 40.0 : 45.0 + visualTextPanelHeight = textPanelHeight } textPanelWidth = layout.size.width - panelSideInset * 2.0 } - + self.updateViews(transition: .immediate) - + let glassPanelHeight: CGFloat = 62.0 var bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: topAccessoryHeight + self.buttonSize.height + insets.bottom)) if (buttons.count == 1 || hideButtons) && topAccessoryHeight > 0.0 { @@ -2510,43 +2533,45 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } else { backgroundView = GlassBackgroundView() self.backgroundView = backgroundView - + liquidLensView = LiquidLensView(kind: .noContainer) liquidLensView.clipsToBounds = true liquidLensView.layer.cornerRadius = 28.0 - + self.containerNode.view.addSubview(backgroundView) self.containerNode.view.addSubview(liquidLensView) self.containerNode.view.addSubview(self.scrollNode.view) self.liquidLensView = liquidLensView - + liquidLensView.contentView.addSubview(self.itemsContainer) liquidLensView.selectedContentView.addSubview(self.selectedItemsContainer) - + self.itemsContainer.clipsToBounds = true self.itemsContainer.layer.cornerRadius = 28.0 self.selectedItemsContainer.clipsToBounds = true self.selectedItemsContainer.layer.cornerRadius = 28.0 - + let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) tabSelectionRecognizer.delegate = self.wrappedGestureRecognizerDelegate self.tabSelectionRecognizer = tabSelectionRecognizer self.scrollNode.view.addGestureRecognizer(tabSelectionRecognizer) } - + let buttonsPanelWidth: CGFloat if buttons.count == 3 { buttonsPanelWidth = smallPanelWidth + } else if buttons.count == 4 { + buttonsPanelWidth = 300 } else { buttonsPanelWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0 } - - let basePanelHeight = isSelecting ? max(0.0, textPanelHeight - 11.0) : glassPanelHeight + + let basePanelHeight = isSelecting ? max(0.0, visualTextPanelHeight - 11.0) : glassPanelHeight var panelSize = CGSize(width: isSelecting ? textPanelWidth : buttonsPanelWidth, height: basePanelHeight + topAccessoryHeight) if !isSelecting && (buttons.count == 1 || hideButtons) && topAccessoryHeight > 0.0 { panelSize.height = topAccessoryHeight } - + let cornerRadius: CGFloat if isSelecting { cornerRadius = min(20.0, basePanelHeight * 0.5) @@ -2556,35 +2581,35 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog cornerRadius = glassPanelHeight * 0.5 } let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0) - + backgroundView.update(size: panelSize, cornerRadius: cornerRadius, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition)) - + let lensSideInset: CGFloat = defaultPanelSideInset + layout.safeInsets.left let lensPanelSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - lensSideInset * 2.0, height: glassPanelHeight) self.lensParams = (lensPanelSize, cornerRadius) self.updateLiquidLens(transition: ComponentTransition(transition)) - + transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: topAccessoryHeight + lensPanelSize.height * 0.5)) transition.updateBounds(layer: liquidLensView.layer, bounds: CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0))) - + transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5)) transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize)) - + let itemsContainerFrame = CGRect(origin: .zero, size: CGSize(width: lensPanelSize.width - 3.0 * 2.0, height: lensPanelSize.height - 3.0 * 2.0)) transition.updateBounds(layer: self.itemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size)) transition.updateBounds(layer: self.selectedItemsContainer.layer, bounds: CGRect(origin: .zero, size: itemsContainerFrame.size)) transition.updatePosition(layer: self.itemsContainer.layer, position: itemsContainerFrame.center) transition.updatePosition(layer: self.selectedItemsContainer.layer, position: itemsContainerFrame.center) - + if topAccessoryHeight > 0.0 { mediaAccessoryPanelFrame = CGRect(origin: CGPoint(x: backgroundOriginX, y: 0.0), size: CGSize(width: panelSize.width, height: topAccessoryHeight)) } } self.updateMediaAccessoryPanel(frame: mediaAccessoryPanelFrame, transition: transition) - + var containerTransition: ContainedViewLayoutTransition var containerFrame: CGRect - + let buttonSideInset: CGFloat let buttonSpacing: CGFloat = 16.0 let buttonHeight: CGFloat @@ -2596,11 +2621,18 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog buttonHeight = 50.0 buttonSideInset = 16.0 } - + if self.mainButtonState.hidesPanelBackground { self.backgroundView?.isHidden = true } - + + let effectiveBottomInset: CGFloat + if let textInputPanelNode = self.textInputPanelNode, textInputPanelNode.additionalInputHeight > 0.0 { + effectiveBottomInset = 0.0 + } else { + effectiveBottomInset = insets.bottom + } + if isAnyButtonVisible { var height: CGFloat if layout.intrinsicInsets.bottom > 0.0 && (layout.inputHeight ?? 0.0).isZero { @@ -2616,7 +2648,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog height = bounds.height + 8.0 } if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing { - + } else if !isNarrowButton { if case .glass = self.panelStyle { } else { @@ -2628,7 +2660,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height)) } else if isSelecting { - containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + insets.bottom + topAccessoryHeight)) + containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: textPanelHeight + effectiveBottomInset + topAccessoryHeight)) } else { containerFrame = bounds } @@ -2645,33 +2677,33 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) - + if let liquidLensView = self.liquidLensView { alphaTransition.updateAlpha(layer: liquidLensView.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) containerTransition.updateTransformScale(layer: liquidLensView.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) } - + if isSelectingUpdated { if isSelecting { self.loadTextNodeIfNeeded() if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = true if case .glass = self.panelStyle { - var textInputPanelHeight = textInputPanelNode.frame.height - if textInputPanelHeight < 1.0 { + var textInputPanelHeight = visualTextPanelHeight + if textInputPanelNode.frame.height < 1.0 { textInputPanelHeight = 51.0 } let heightDelta = glassPanelHeight - textInputPanelHeight - + let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0) - + let componentTransition = ComponentTransition.easeInOut(duration: 0.25) if let liquidLensView = self.liquidLensView { componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 0.0, toRadius: 10.0) transition.animatePositionAdditive(layer: liquidLensView.layer, offset: .zero, to: CGPoint(x: -27.0, y: 0.0)) } - + let blurTransition = ComponentTransition.easeInOut(duration: 0.18) transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01) blurTransition.animateBlur(layer: textInputPanelNode.opaqueActionButtons.layer, fromRadius: 4.0, toRadius: 0.0) @@ -2680,10 +2712,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 4.0, toRadius: 0.0) componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 0.0, to: 1.0) - + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: CGPoint(x: 6.0, y: heightDelta)) transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: CGPoint(x: 64.0, y: heightDelta)) - + textInputPanelNode.animateIn(transition: transition) } else { textInputPanelNode.alpha = 1.0 @@ -2695,21 +2727,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = false if case .glass = self.panelStyle { - var textInputPanelHeight = textInputPanelNode.frame.height - if textInputPanelHeight < 1.0 { + var textInputPanelHeight = visualTextPanelHeight + if textInputPanelNode.frame.height < 1.0 { textInputPanelHeight = 51.0 } let heightDelta = glassPanelHeight - textInputPanelHeight - + let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0) - + let componentTransition = ComponentTransition.easeInOut(duration: 0.25) if let liquidLensView = self.liquidLensView { componentTransition.animateBlur(layer: liquidLensView.layer, fromRadius: 10.0, toRadius: 0.0) transition.animatePositionAdditive(layer: liquidLensView.layer, offset: CGPoint(x: -27.0, y: 0.0)) } - + let blurTransition = ComponentTransition.easeInOut(duration: 0.18) transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01)) blurTransition.animateBlur(layer: textInputPanelNode.opaqueActionButtons.layer, fromRadius: 0.0, toRadius: 4.0) @@ -2718,7 +2750,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog blurTransition.animateBlur(layer: textInputPanelNode.inputModeView.layer, fromRadius: 0.0, toRadius: 4.0) componentTransition.animateAlpha(view: textInputPanelNode.inputModeView, from: 1.0, to: 0.0) - + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: .zero, to: CGPoint(x: 6.0, y: heightDelta)) transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: .zero, to: CGPoint(x: 64.0, y: heightDelta)) } else { @@ -2729,16 +2761,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog } } } - + if self.containerNode.frame.size.width.isZero { containerTransition = .immediate } - + containerTransition.updateFrame(node: self.containerNode, frame: containerFrame) containerTransition.updateFrame(node: self.backgroundNode, frame: containerBounds) self.backgroundNode.update(size: containerBounds.size, transition: transition) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) - + if case .glass = self.panelStyle { let scrollFrame = CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: topAccessoryHeight + (self.isSelecting ? -11.0 : 0.0)), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)) transition.updatePosition(node: self.scrollNode, position: scrollFrame.center) @@ -2757,7 +2789,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog let loadingProgressHeight: CGFloat = 2.0 let loadingProgressY: CGFloat = elevateProgress ? -loadingProgressHeight : -loadingProgressHeight / 2.0 transition.updateFrame(node: loadingProgressNode, frame: CGRect(origin: CGPoint(x: 0.0, y: loadingProgressY), size: CGSize(width: layout.size.width, height: loadingProgressHeight))) - + loadingProgressNode.updateProgress(progress, animated: true) } else if let progressNode = self.progressNode { self.progressNode = nil @@ -2765,7 +2797,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog progressNode?.removeFromSupernode() }) } - + var buttonSize = CGSize(width: layout.size.width - (buttonSideInset + layout.safeInsets.left) * 2.0, height: buttonHeight) if isTwoHorizontalButtons { buttonSize = CGSize(width: (buttonSize.width - buttonSideInset) / 2.0, height: buttonSize.height) @@ -2777,7 +2809,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .legacy: buttonTopInset = isNarrowButton ? 2.0 : 8.0 } - + if !self.animatingTransition { let buttonOriginX = layout.safeInsets.left + buttonSideInset let buttonOriginY = isAnyButtonVisible || self.fromMenu ? topAccessoryHeight + buttonTopInset : containerFrame.height @@ -2807,7 +2839,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) } } - + if let mainButtonFrame { if !self.dismissed { self.mainButtonNode.updateLayout(size: buttonSize, context: self.context, style: self.panelStyle, state: self.mainButtonState, animateBackground: self.mainButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) @@ -2835,73 +2867,73 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog transition.updateAlpha(node: self.secondaryButtonNode, alpha: 0.0) } } - + return containerFrame.height } - + func updateItemContainers(contentOffset: CGFloat, transition: ComponentTransition) { let transform = CATransform3DMakeTranslation(-contentOffset, 0.0, 0.0) transition.setSublayerTransform(view: self.itemsContainer, transform: transform) transition.setSublayerTransform(view: self.selectedItemsContainer, transform: transform) } - + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { if self.selectionGestureState != nil { self.selectionGestureState = nil self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) } } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateItemContainers(contentOffset: scrollView.contentOffset.x, transition: .immediate) self.updateViews(transition: .immediate) self.updateLiquidLens(transition: .immediate) } - + private var skipLensUpdate = false private var lensParams: (panelSize: CGSize, cornerRadius: CGFloat)? func updateLiquidLens(transition: ComponentTransition) { guard !self.skipLensUpdate, let liquidLensView = self.liquidLensView, let (panelSize, _) = self.lensParams else { return } - + var selectionFrame = CGRect() if self.selectedIndex >= 0 && self.selectedIndex < self.buttons.count, let itemView = self.itemViews[self.buttons[self.selectedIndex].key], let itemSize = self.itemSizes[self.buttons[self.selectedIndex].key] { let contentOffset = self.scrollNode.view.contentOffset.x selectionFrame = CGRect(origin: CGPoint(x: itemView.center.x - itemSize.width / 2.0 - contentOffset, y: itemView.frame.minY), size: itemSize) } - + var lensSelection: (x: CGFloat, width: CGFloat) if let selectionGestureState = self.selectionGestureState, selectionGestureState.isLifted { lensSelection = (selectionGestureState.currentX, selectionFrame.width) } else { lensSelection = (selectionFrame.minX, selectionFrame.width) } - + if !self.scrollNode.view.isScrollEnabled { lensSelection.x = max(0.0, min(lensSelection.x, panelSize.width - lensSelection.width)) } - + var isLifted = self.selectionGestureState?.isLifted == true || self.lensIsLifted if let widthClass = self.validLayout?.metrics.widthClass, case .regular = widthClass { isLifted = false } - + let inset: CGFloat = 3.0 liquidLensView.update(size: CGSize(width: panelSize.width - inset * 2.0, height: panelSize.height - inset * 2.0), cornerRadius: 28.0, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height - inset * 2.0), inset: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, isLifted: isLifted, isCollapsed: self.isSelecting || self.buttons.count < 2 || self.hideButtons, transition: transition) } - + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer == self.tabSelectionRecognizer { return true } return super.gestureRecognizerShouldBegin(gestureRecognizer) } - + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return true } - + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index cc280f1551..4757af3b84 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/Display:Display", "//submodules/SSignalKit/SSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -48,6 +47,7 @@ swift_library( "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/Utils/DeviceModel", "//submodules/PresentationDataUtils", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 70780df037..f1edb805dd 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import MtProtoKit @@ -34,7 +33,7 @@ private enum InnerState: Equatable { public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme { - return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, accentButtonColor: theme.list.itemCheckColors.fillColor, accentForegroundColor: theme.list.itemCheckColors.foregroundColor, style: .glass) + return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, accentButtonColor: theme.list.itemCheckColors.fillColor, accentDisabledButtonColor: theme.chat.inputPanel.panelControlDisabledColor, accentForegroundColor: theme.list.itemCheckColors.foregroundColor, style: .glass) } private let sharedContext: SharedAccountContext @@ -209,7 +208,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth |> deliverOnMainQueue).startStandalone(next: { [weak self] authorizationPushConfiguration in if let strongSelf = self { strongSelf.actionDisposable.set((sendAuthorizationCode(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, phoneNumber: number, apiId: strongSelf.apiId, apiHash: strongSelf.apiHash, pushNotificationConfiguration: authorizationPushConfiguration, firebaseSecretStream: strongSelf.sharedContext.firebaseSecretStream, syncContacts: syncContacts, disableAuthTokens: disableAuthTokens, forcedPasswordSetupNotice: { value in - guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { + guard let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry) @@ -327,7 +326,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth passkey: passkey, foreignDatacenter: nil, forcedPasswordSetupNotice: { value in - guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { + guard let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry) @@ -518,7 +517,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth })) } else { strongSelf.actionDisposable.set((authorizeWithCode(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, code: authorizationCode, termsOfService: termsOfService?.0, forcedPasswordSetupNotice: { value in - guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { + guard let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry) @@ -810,8 +809,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth return controller } - private func paymentController(number: String, phoneCodeHash: String, storeProduct: String, supportEmailAddress: String, supportEmailSubject: String) -> AuthorizationSequencePaymentScreen { - let controller = AuthorizationSequencePaymentScreen(sharedContext: self.sharedContext, engine: self.engine, presentationData: self.presentationData, inAppPurchaseManager: self.inAppPurchaseManager, phoneNumber: number, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, supportEmailAddress: supportEmailAddress, supportEmailSubject: supportEmailSubject, back: { [weak self] in + private func paymentController(number: String, phoneCodeHash: String, storeProduct: String, premiumDays: Int32, supportEmailAddress: String, supportEmailSubject: String) -> AuthorizationSequencePaymentScreen { + let controller = AuthorizationSequencePaymentScreen(sharedContext: self.sharedContext, engine: self.engine, presentationData: self.presentationData, inAppPurchaseManager: self.inAppPurchaseManager, phoneNumber: number, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, premiumDays: premiumDays, supportEmailAddress: supportEmailAddress, supportEmailSubject: supportEmailSubject, back: { [weak self] in guard let self else { return } @@ -857,7 +856,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } else { self.actionDisposable.set( authorizeWithCode(accountManager: self.sharedContext.accountManager, account: self.account, code: .emailVerification(.appleToken(token)), termsOfService: nil, forcedPasswordSetupNotice: { value in - guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { + guard let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry) @@ -997,7 +996,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.reset = { [weak self, weak controller] in if let strongSelf = self, let strongController = controller { strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: suggestReset ? strongSelf.presentationData.strings.TwoStepAuth_RecoveryFailed : strongSelf.presentationData.strings.TwoStepAuth_RecoveryUnavailable, actions: [ - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: { if let strongSelf = self, let strongController = controller { strongController.inProgress = true @@ -1084,7 +1083,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth controller.reset = { [weak self, weak controller] in if let strongSelf = self, let strongController = controller { strongController.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_ResetAccountConfirmation, actions: [ - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Login_ResetAccountProtected_Reset, action: { if let strongSelf = self, let strongController = controller { strongController.inProgress = true @@ -1159,7 +1158,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let avatarVideo: Signal? if let avatarAsset = avatarAsset as? AVAsset { let engine = strongSelf.engine - avatarVideo = Signal { subscriber in + avatarVideo = Signal { subscriber in let entityRenderer: LegacyPaintEntityRenderer? = avatarAdjustments.flatMap { adjustments in if let paintingData = adjustments.paintingData, paintingData.hasAnimation { return LegacyPaintEntityRenderer(postbox: nil, adjustments: adjustments) @@ -1177,8 +1176,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if stat(result.fileURL.path, &value) == 0 { if let data = try? Data(contentsOf: result.fileURL) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - engine.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - subscriber.putNext(resource) + engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + subscriber.putNext(EngineMediaResource(resource)) EngineTempBox.shared.dispose(tempFile) } @@ -1208,7 +1207,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } strongSelf.actionDisposable.set((signUpWithName(accountManager: strongSelf.sharedContext.accountManager, account: strongSelf.account, firstName: firstName, lastName: lastName, avatarData: avatarData, avatarVideo: avatarVideo, videoStartTimestamp: videoStartTimestamp, disableJoinNotifications: !announceSignUp, forcedPasswordSetupNotice: { value in - guard let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) else { + guard let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) else { return nil } return (ApplicationSpecificNotice.forcedPasswordSetupKey(), entry) @@ -1348,12 +1347,12 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth } controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel)) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) - case let .payment(number, codeHash, storeProduct, supportEmailAddress, supportEmailSubject, _): + case let .payment(number, codeHash, storeProduct, premiumDays, supportEmailAddress, supportEmailSubject, _): var controllers: [ViewController] = [] if !self.otherAccountPhoneNumbers.1.isEmpty { controllers.append(self.splashController()) } - controllers.append(self.paymentController(number: number, phoneCodeHash: codeHash, storeProduct: storeProduct, supportEmailAddress: supportEmailAddress, supportEmailSubject: supportEmailSubject)) + controllers.append(self.paymentController(number: number, phoneCodeHash: codeHash, storeProduct: storeProduct, premiumDays: premiumDays, supportEmailAddress: supportEmailAddress, supportEmailSubject: supportEmailSubject)) self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) } } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift index 61715e95a0..eee43282af 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -29,6 +28,7 @@ import PhoneNumberFormat import PlainButtonComponent import StoreKit import DeviceModel +import GlassBarButtonComponent final class AuthorizationSequencePaymentScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -40,6 +40,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { let phoneNumber: String let phoneCodeHash: String let storeProduct: String + let premiumDays: Int32 let supportEmailAddress: String let supportEmailSubject: String @@ -51,6 +52,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { phoneNumber: String, phoneCodeHash: String, storeProduct: String, + premiumDays: Int32, supportEmailAddress: String, supportEmailSubject: String ) { @@ -61,6 +63,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { self.phoneNumber = phoneNumber self.phoneCodeHash = phoneCodeHash self.storeProduct = storeProduct + self.premiumDays = premiumDays self.supportEmailAddress = supportEmailAddress self.supportEmailSubject = supportEmailSubject } @@ -116,7 +119,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { self.state?.updated() let (currency, amount) = storeProduct.priceCurrencyAndAmount - let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount) + let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, premiumDays: component.premiumDays, currency: currency, amount: amount) let _ = (component.engine.payments.canPurchasePremium(purpose: purpose) |> deliverOnMainQueue).start(next: { [weak self] available in guard let self else { @@ -169,7 +172,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { title: nil, text: errorText, actions: [ - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_PhoneNumberHelp, action: { [weak self] in guard let self else { return @@ -252,25 +255,27 @@ final class AuthorizationSequencePaymentScreenComponent: Component { let helpButtonSize = self.helpButton.update( transition: transition, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.Login_PhoneNumberHelp, font: Font.regular(17.0), textColor: environment.theme.list.itemAccentColor)) - )), - minSize: CGSize(width: 0.0, height: 44.0), - contentInsets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0), - action: { [weak self] in - guard let self else { - return + component: AnyComponent( + GlassBarButtonComponent( + size: nil, + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "label", component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.Login_PhoneNumberHelp, font: Font.regular(17.0), textColor: environment.theme.chat.inputPanel.panelControlColor)) + ))), + action: { [weak self] _ in + guard let self else { + return + } + self.displaySendEmail(error: nil, errorCode: nil) } - self.displaySendEmail(error: nil, errorCode: nil) - }, - animateScale: false, - animateContents: false - )), + ) + ), environment: {}, - containerSize: CGSize(width: 200.0, height: 100.0) + containerSize: CGSize(width: 200.0, height: 44.0) ) - let helpButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - 8.0 - helpButtonSize.width, y: environment.statusBarHeight), size: helpButtonSize) + let helpButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - helpButtonSize.width, y: environment.navigationHeight - helpButtonSize.height - 6.0), size: helpButtonSize) if let helpButtonView = self.helpButton.view { if helpButtonView.superview == nil { self.addSubview(helpButtonView) @@ -334,13 +339,24 @@ final class AuthorizationSequencePaymentScreenComponent: Component { )) ) ) + + let supportText: String + if component.premiumDays == 7 { + supportText = environment.strings.Login_Fee_Support_Text + } else if component.premiumDays > 0 { + let daysString = environment.strings.Login_Fee_Support_NewText_Days(component.premiumDays) + supportText = environment.strings.Login_Fee_Support_NewText(daysString).string + } else { + supportText = environment.strings.Login_Fee_Support_NewTextNone + } + items.append( AnyComponentWithIdentity( id: "support", component: AnyComponent(ParagraphComponent( title: environment.strings.Login_Fee_Support_Title, titleColor: textColor, - text: environment.strings.Login_Fee_Support_Text, + text: supportText, textColor: secondaryTextColor, iconName: "Premium/Authorization/Support", iconColor: linkColor, @@ -352,7 +368,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { sharedContext: component.sharedContext, engine: component.engine, inAppPurchaseManager: component.inAppPurchaseManager, - source: .auth(product.price), + source: .auth(product.price, component.premiumDays), proceed: { [weak self] in self?.proceed() } @@ -370,7 +386,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) ) - let buttonHeight: CGFloat = 50.0 + let buttonHeight: CGFloat = 52.0 let bottomPanelPadding: CGFloat = 12.0 let titleSpacing: CGFloat = -24.0 let listSpacing: CGFloat = 12.0 @@ -400,7 +416,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize) } - let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding + let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 10.0 : bottomPanelPadding let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset let priceString: String @@ -411,6 +427,16 @@ final class AuthorizationSequencePaymentScreenComponent: Component { } let buttonString = environment.strings.Login_Fee_SignUp(priceString).string + let buttonSubtitle: String + if component.premiumDays == 7 { + buttonSubtitle = environment.strings.Login_Fee_GetPremiumForAWeek + } else if component.premiumDays > 0 { + let daysString = environment.strings.Login_Fee_GetPremiumForDays_Days(component.premiumDays) + buttonSubtitle = environment.strings.Login_Fee_GetPremiumForDays(daysString).string + } else { + buttonSubtitle = environment.strings.Login_Fee_GetPremiumNone + } + let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let buttonSize = self.button.update( transition: transition, @@ -426,7 +452,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { component: AnyComponent( VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))), - AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Login_Fee_GetPremiumForAWeek, font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center))))) + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonSubtitle, font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center))))) ], spacing: 1.0) ) ), @@ -437,7 +463,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight) + containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: buttonHeight) ) if let buttonView = self.button.view { if buttonView.superview == nil { @@ -468,6 +494,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo phoneNumber: String, phoneCodeHash: String, storeProduct: String, + premiumDays: Int32, supportEmailAddress: String, supportEmailSubject: String, back: @escaping () -> Void @@ -480,6 +507,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, + premiumDays: premiumDays, supportEmailAddress: supportEmailAddress, supportEmailSubject: supportEmailSubject ), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData))) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift index c28585b3de..9f9d886649 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import PresentationDataUtils import ProgressNavigationButtonNode diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index 69a9fcc455..72bffa0e99 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -8,7 +8,6 @@ import PhoneInputNode import CountrySelectionUI import QrCode import SwiftSignalKit -import Postbox import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift index d09dee51a0..1b5082c959 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift @@ -10,7 +10,6 @@ import LegacyComponents import ProgressNavigationButtonNode import ImageCompression import LegacyMediaPickerUI -import Postbox import TextFormat import MoreButtonNode import ContextUI @@ -272,9 +271,9 @@ final class AuthorizationSequenceSignUpController: ViewController { if let name = name { self.signUpWithName?(name.0, name.1, self.controllerNode.currentPhoto.flatMap({ image in - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") let result = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return result }), self.avatarAsset, self.avatarAdjustments, self.announceSignUp) } diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift index c422320ddf..9056e2daab 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SSignalKit import SwiftSignalKit diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 115c8c0149..e690da30ad 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -643,7 +643,7 @@ public final class AvatarNode: ASDisplayNode { let parameters: AvatarNodeParameters - if let peer = peer, let signal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) { + if let peer = peer, let signal = peerAvatarImage(postbox: postbox, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) { self.contents = nil self.displaySuspended = true self.imageReady.set(self.imageNode.contentReady) @@ -763,7 +763,7 @@ public final class AvatarNode: ASDisplayNode { self.imageNode.view.mask = nil } - if let imageCache = genericContext.imageCache as? DirectMediaImageCache, let peer, let smallProfileImage = peer.smallProfileImage, let peerReference = PeerReference(peer._asPeer()) { + if let imageCache = genericContext.imageCache as? DirectMediaImageCache, let peer, let smallProfileImage = peer.smallProfileImage, let peerReference = PeerReference(peer) { if let result = imageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: peer.profileImageRepresentations.first?.immediateThumbnailData, size: Int(displayDimensions.width * UIScreenScale), synchronous: synchronousLoad) { if let image = result.image { self.imageNode.contents = image.cgImage @@ -852,7 +852,7 @@ public final class AvatarNode: ASDisplayNode { let account = account ?? genericContext.account - if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) { + if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) { self.contents = nil self.displaySuspended = true self.imageReady.set(self.imageNode.contentReady) diff --git a/submodules/AvatarNode/Sources/PeerAvatar.swift b/submodules/AvatarNode/Sources/PeerAvatar.swift index 17d3374616..caf06427e9 100644 --- a/submodules/AvatarNode/Sources/PeerAvatar.swift +++ b/submodules/AvatarNode/Sources/PeerAvatar.swift @@ -27,11 +27,7 @@ public enum PeerAvatarImageType { case complete } -public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? { - return peerAvatarImageData(postbox: account.postbox, network: account.network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) -} - -public func peerAvatarImageData(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? { +public func peerAvatarImageData(postbox: Postbox, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? { if let smallProfileImage = representation { let resourceData = postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad) let imageData = resourceData @@ -92,7 +88,6 @@ public func peerAvatarImageData(postbox: Postbox, network: Network, peerReferenc public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal { return peerAvatarCompleteImage( postbox: account.postbox, - network: account.network, peer: peer, forceProvidedRepresentation: forceProvidedRepresentation, representation: representation, @@ -105,7 +100,7 @@ public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, forcePro ) } -public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal { +public func peerAvatarCompleteImage(postbox: Postbox, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal { let iconSignal: Signal let clipStyle: AvatarNodeClipStyle @@ -126,8 +121,8 @@ public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: En thumbnailRepresentation = peer.profileImageRepresentations.first } - if let signal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: thumbnailRepresentation, displayDimensions: size, clipStyle: clipStyle, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) { - if fullSize, let fullSizeSignal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) { + if let signal = peerAvatarImage(postbox: postbox, peerReference: PeerReference(peer), authorOfMessage: nil,representation: thumbnailRepresentation, displayDimensions: size, clipStyle: clipStyle, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) { + if fullSize, let fullSizeSignal = peerAvatarImage(postbox: postbox, 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 in if let fullSizeImage = fullSizeImage { @@ -174,7 +169,6 @@ public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: En public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? { return peerAvatarImage( postbox: account.postbox, - network: account.network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, @@ -189,8 +183,8 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut ) } -public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? { - if let imageData = peerAvatarImageData(postbox: postbox, network: network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) { +public func peerAvatarImage(postbox: Postbox, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? { + if let imageData = peerAvatarImageData(postbox: postbox, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) { return imageData |> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in let generate = deferred { () -> Signal<(UIImage, UIImage)?, NoError> in diff --git a/submodules/AvatarVideoNode/BUILD b/submodules/AvatarVideoNode/BUILD index dd69602adc..1cd082613a 100644 --- a/submodules/AvatarVideoNode/BUILD +++ b/submodules/AvatarVideoNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AnimationUI:AnimationUI", diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index bb787e1836..c541eda360 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -206,7 +206,7 @@ public final class AvatarVideoNode: ASDisplayNode { self.internalSize = size if let markup = photo.emojiMarkup { self.update(markup: markup, size: size, useAnimationNode: false) - } else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) { + } else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer) { self.backgroundNode.image = nil let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() @@ -217,7 +217,7 @@ public final class AvatarVideoNode: ASDisplayNode { self.videoContent = videoContent self.videoFileDisposable?.dispose() - self.videoFileDisposable = fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .avatar, reference: videoFileReference.resourceReference(videoFileReference.media.resource)).startStrict() + self.videoFileDisposable = self.context.engine.resources.fetch(reference: videoFileReference.resourceReference(videoFileReference.media.resource), userLocation: .peer(peer.id), userContentType: .avatar).startStrict() } } } @@ -229,7 +229,7 @@ public final class AvatarVideoNode: ASDisplayNode { if isVisible, let animationNode = self.animationNode, let file = self.animationFile { if !self.didSetupAnimation { self.didSetupAnimation = true - let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm") diff --git a/submodules/BotPaymentsUI/BUILD b/submodules/BotPaymentsUI/BUILD index 45cfed992b..0b35e43a4f 100644 --- a/submodules/BotPaymentsUI/BUILD +++ b/submodules/BotPaymentsUI/BUILD @@ -11,10 +11,21 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/ComponentFlow:ComponentFlow", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/LocalAuth:LocalAuth", "//submodules/AccountContext:AccountContext", + "//submodules/Components/ViewControllerComponent:ViewControllerComponent", + "//submodules/Components/ResizableSheetComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent", "//submodules/ItemListUI:ItemListUI", "//submodules/PasswordSetupUI:PasswordSetupUI", "//submodules/PhotoResources:PhotoResources", diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift index e970e67688..c63254c00e 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift @@ -117,6 +117,7 @@ final class BotCheckoutActionButton: HighlightTrackingButtonNode { } else { applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black) } + applePayButton.cornerRadius = BotCheckoutActionButton.height * 0.5 applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside) self.view.addSubview(applePayButton) self.applePayButton = applePayButton diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift index 035fa49f99..dab1b83bd0 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift @@ -141,9 +141,10 @@ public final class BotCheckoutController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + self._hasGlassStyle = true var title = self.presentationData.strings.Checkout_Title if invoice.flags.contains(.isTest) { @@ -151,7 +152,7 @@ public final class BotCheckoutController: ViewController { } self.title = title - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index e81bf2e2a4..ee0a215277 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -1077,13 +1077,13 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz if methods.isEmpty { openNewCard(nil, nil) } else { - strongSelf.present(BotCheckoutPaymentMethodSheetController(context: strongSelf.context, currentMethod: strongSelf.currentPaymentMethod, methods: methods, applyValue: { method in + strongSelf.controller?.push(BotCheckoutPaymentMethodScreen(context: strongSelf.context, currentMethod: strongSelf.currentPaymentMethod, methods: methods, applyValue: { method in applyPaymentMethod(method) }, newCard: { openNewCard(nil, nil) }, otherMethod: { url, title in openNewCard(url, title) - }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + })) } } } @@ -1091,14 +1091,14 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz openShippingMethodImpl = { [weak self] in if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let shippingOptions = strongSelf.currentValidatedFormInfo?.shippingOptions, !shippingOptions.isEmpty { strongSelf.controller?.view.endEditing(true) - strongSelf.present(BotCheckoutPaymentShippingOptionSheetController(context: strongSelf.context, currency: paymentFormValue.invoice.currency, options: shippingOptions, currentId: strongSelf.currentShippingOptionId, applyValue: { id in + strongSelf.controller?.push(BotCheckoutShippingOptionScreen(context: strongSelf.context, currency: paymentFormValue.invoice.currency, options: shippingOptions, currentId: strongSelf.currentShippingOptionId, applyValue: { id in if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo { strongSelf.currentShippingOptionId = id strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod, strongSelf.currentTipAmount))) strongSelf.updateActionButton() } - }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + })) } } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift index 84ff228769..0172056d75 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift @@ -206,7 +206,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode { } let contentSize = CGSize(width: params.width, height: contentHeight) - let insets = itemListNeighborsPlainInsets(neighbors) + let insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift index bee72bda48..9a178d626c 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift @@ -60,14 +60,16 @@ final class BotCheckoutInfoController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.doneItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) self.title = self.presentationData.strings.CheckoutInfo_Title - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.rightBarButtonItem = self.doneItem self.doneItem?.isEnabled = false } @@ -84,13 +86,13 @@ final class BotCheckoutInfoController: ViewController { self?.presentingViewController?.dismiss(animated: false, completion: nil) }, openCountrySelection: { [weak self] in if let strongSelf = self { - let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false) + let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false, glass: true) controller.completeWithCountryCode = { _, id in if let strongSelf = self { strongSelf.controllerNode.updateCountry(id) } } - strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + strongSelf.push(controller) } }, updateStatus: { [weak self] status in if let strongSelf = self { diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift index 353b1adfbf..cbc2ae8aff 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift @@ -455,7 +455,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, ASScrollVi let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) var sideInset: CGFloat = 0.0 - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { sideInset = inset } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift index 74af758711..b1bc797121 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift @@ -55,14 +55,16 @@ final class BotCheckoutNativeCardEntryController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) + + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.doneItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) self.title = self.presentationData.strings.Checkout_NewCard_Title - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.rightBarButtonItem = self.doneItem self.doneItem?.isEnabled = false } @@ -78,13 +80,13 @@ final class BotCheckoutNativeCardEntryController: ViewController { self?.presentingViewController?.dismiss(animated: false, completion: nil) }, openCountrySelection: { [weak self] in if let strongSelf = self { - let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false) + let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false, glass: true) controller.completeWithCountryCode = { _, id in if let strongSelf = self { strongSelf.controllerNode.updateCountry(id) } } - strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + strongSelf.push(controller) } }, updateStatus: { [weak self] status in if let strongSelf = self { diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift index 7a0c7afc0c..32025f0f50 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift @@ -468,7 +468,7 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) var sideInset: CGFloat = 0.0 - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { sideInset = inset } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift index 7e08b985b8..644e8ce4a6 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift @@ -1,374 +1,96 @@ import Foundation import UIKit -import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit import TelegramPresentationData import AccountContext +import ComponentFlow +import AlertComponent +import AlertInputFieldComponent -private final class BotCheckoutPassworInputFieldNode: ASDisplayNode, UITextFieldDelegate { - private var theme: PresentationTheme - private let backgroundNode: ASImageNode - private let textInputNode: TextFieldNode - private let placeholderNode: ASTextNode - - var updateHeight: (() -> Void)? - var complete: (() -> Void)? - var textChanged: ((String) -> Void)? - - private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) - private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0) - - var text: String { - get { - return self.textInputNode.textField.text ?? "" - } - set { - self.textInputNode.textField.text = newValue - self.placeholderNode.isHidden = !newValue.isEmpty - } - } - - var placeholder: String = "" { - didSet { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - } - } - - init(theme: PresentationTheme, placeholder: String) { - self.theme = theme - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - - self.textInputNode = TextFieldNode() - self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor] - self.textInputNode.textField.clipsToBounds = true - self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) - self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textInputNode.textField.returnKeyType = .done - self.textInputNode.textField.isSecureTextEntry = true - self.textInputNode.textField.tintColor = theme.actionSheet.controlAccentColor - self.textInputNode.textField.textColor = theme.actionSheet.inputTextColor - - self.placeholderNode = ASTextNode() - self.placeholderNode.isUserInteractionEnabled = false - self.placeholderNode.displaysAsynchronously = false - self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - - super.init() - - self.textInputNode.textField.delegate = self - self.textInputNode.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged) - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textInputNode) - self.addSubnode(self.placeholderNode) - } - - func updateTheme(_ theme: PresentationTheme) { - self.theme = theme - - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) - self.textInputNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) - self.textInputNode.textField.tintColor = self.theme.actionSheet.controlAccentColor - self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor] - self.textInputNode.textField.textColor = self.theme.actionSheet.inputTextColor - } - - func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: width) - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - - let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - - let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) - transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) - - transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) - - return panelHeight - } - - func activateInput() { - self.textInputNode.becomeFirstResponder() - } - - func deactivateInput() { - self.textInputNode.resignFirstResponder() - } - - func shake() { - self.layer.addShakeAnimation() +func botCheckoutPasswordEntryController(context: AccountContext, strings: PresentationStrings, passwordTip: String?, cartTitle: String, period: Int32, requiresBiometrics: Bool, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let inputState = AlertInputFieldComponent.ExternalState() + let doneInProgressPromise = ValuePromise(false) + let doneIsEnabled: Signal = combineLatest(inputState.valueSignal, doneInProgressPromise.get()) + |> map { value, isInProgress in + return !value.isEmpty && !isInProgress } - @objc func textDidChange() { - self.updateTextNodeText(animated: true) - self.textChanged?(self.textInputNode.textField.text ?? "") - self.placeholderNode.isHidden = !(self.textInputNode.textField.text ?? "").isEmpty - } - - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if text == "\n" { - self.complete?() - return false - } - return true - } - - private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { - let backgroundInsets = self.backgroundInsets - let inputInsets = self.inputInsets - - let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) - - return min(61.0, max(33.0, unboundTextFieldHeight)) - } - - private func updateTextNodeText(animated: Bool) { - let backgroundInsets = self.backgroundInsets - - let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) - - let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom - if !self.bounds.size.height.isEqual(to: panelHeight) { - self.updateHeight?() - } - } - - @objc func clearPressed() { - self.textInputNode.textField.text = nil - self.deactivateInput() - } -} + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: strings.Checkout_PasswordEntry_Title) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(strings.Checkout_PasswordEntry_Text(cartTitle).string)) + ) + )) -private final class BotCheckoutPasswordAlertContentNode: AlertContentNode { - private let context: AccountContext - private let period: Int32 - private let requiresBiometrics: Bool - private let completion: (TemporaryTwoStepPasswordToken) -> Void - - private let titleNode: ASTextNode - private let textNode: ASTextNode - - private let actionNodesSeparator: ASDisplayNode - private let actionNodes: [TextAlertContentActionNode] - private let actionVerticalSeparators: [ASDisplayNode] - - private let cancelActionNode: TextAlertContentActionNode - private let doneActionNode: TextAlertContentActionNode - - let inputFieldNode: BotCheckoutPassworInputFieldNode - - private var validLayout: CGSize? - private var isVerifying = false - private let disposable = MetaDisposable() - - private let hapticFeedback = HapticFeedback() - - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, passwordTip: String?, cardTitle: String, period: Int32, requiresBiometrics: Bool, cancel: @escaping () -> Void, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) { - self.context = context - self.period = period - self.requiresBiometrics = requiresBiometrics - self.completion = completion - - let alertTheme = AlertControllerTheme(presentationTheme: theme, fontSize: .regular) - - let titleNode = ASTextNode() - titleNode.attributedText = NSAttributedString(string: strings.Checkout_PasswordEntry_Title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - titleNode.displaysAsynchronously = false - titleNode.isUserInteractionEnabled = false - titleNode.maximumNumberOfLines = 1 - titleNode.truncationMode = .byTruncatingTail - self.titleNode = titleNode - - self.textNode = ASTextNode() - self.textNode.attributedText = NSAttributedString(string: strings.Checkout_PasswordEntry_Text(cardTitle).string, font: Font.regular(13.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) - self.textNode.displaysAsynchronously = false - self.textNode.isUserInteractionEnabled = false - - self.inputFieldNode = BotCheckoutPassworInputFieldNode(theme: theme, placeholder: passwordTip ?? "") - - self.actionNodesSeparator = ASDisplayNode() - self.actionNodesSeparator.isLayerBacked = true - self.actionNodesSeparator.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor - - self.cancelActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: { - cancel() - })) - - var doneImpl: (() -> Void)? - self.doneActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .defaultAction, title: strings.Checkout_PasswordEntry_Pay, action: { - doneImpl?() - })) - - self.actionNodes = [self.cancelActionNode, self.doneActionNode] - - var actionVerticalSeparators: [ASDisplayNode] = [] - if self.actionNodes.count > 1 { - for _ in 0 ..< self.actionNodes.count - 1 { - let separatorNode = ASDisplayNode() - separatorNode.isLayerBacked = true - separatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor - actionVerticalSeparators.append(separatorNode) - } - } - self.actionVerticalSeparators = actionVerticalSeparators - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - - self.addSubnode(self.actionNodesSeparator) - - for actionNode in self.actionNodes { - self.addSubnode(actionNode) - } - - for separatorNode in self.actionVerticalSeparators { - self.addSubnode(separatorNode) - } - - self.addSubnode(self.inputFieldNode) - - self.inputFieldNode.textChanged = { [weak self] _ in - if let strongSelf = self { - strongSelf.updateState() - } - } - - self.updateState() - - doneImpl = { [weak self] in - self?.verify() - } + var applyImpl: (() -> Void)? + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertInputFieldComponent( + context: context, + placeholder: passwordTip ?? "", + isSecureTextEntry: true, + autocapitalizationType: .none, + autocorrectionType: .no, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + + var isVerifying = false + let disposable = MetaDisposable() + var dismissImpl: (() -> Void)? + let alertController = AlertScreen( + configuration: AlertScreen.Configuration(allowInputInset: true), + content: content, + actions: [ + .init(title: strings.Common_Cancel), + .init(title: strings.Checkout_PasswordEntry_Pay, type: .default, action: { + applyImpl?() + }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get()) + ], + updatedPresentationData: (initial: presentationData, signal: context.sharedContext.presentationData) + ) + alertController.dismissed = { _ in + disposable.dispose() } - - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let previousLayout = self.validLayout - self.validLayout = size - - let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) - - let titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) - let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) - - let actionsHeight: CGFloat = 44.0 - - var minActionsWidth: CGFloat = 0.0 - let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) - let actionTitleInsets: CGFloat = 8.0 - for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionsHeight)) - minActionsWidth += actionTitleSize.width + actionTitleInsets - } - - let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) - - let spacing: CGFloat = 6.0 - let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) - transition.updateFrame(node: titleNode, frame: titleFrame) - - let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - - let resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom + 46.0) - - let inputFieldWidth = resultSize.width - let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) - transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: resultSize.height - 36.0 - actionsHeight - insets.bottom, width: resultSize.width, height: inputFieldHeight)) - - self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) - - var actionOffset: CGFloat = 0.0 - let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) - var separatorIndex = -1 - var nodeIndex = 0 - for actionNode in self.actionNodes { - if separatorIndex >= 0 { - let separatorNode = self.actionVerticalSeparators[separatorIndex] - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) - } - separatorIndex += 1 - - let currentActionWidth: CGFloat - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth - } - - let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight)) - - actionOffset += currentActionWidth - transition.updateFrame(node: actionNode, frame: actionNodeFrame) - - nodeIndex += 1 - } - - if previousLayout == nil { - self.inputFieldNode.activateInput() - } - - return resultSize - } - - @objc func textFieldChanged(_ textField: UITextField) { - self.updateState() - } - - private func updateState() { - var enabled = true - if self.isVerifying || self.inputFieldNode.text.isEmpty { - enabled = false - } - self.doneActionNode.actionEnabled = enabled - } - - private func verify() { - let text = self.inputFieldNode.text - guard !text.isEmpty else { + + applyImpl = { + let password = inputState.value + guard !isVerifying, !password.isEmpty else { return } - - self.isVerifying = true - self.disposable.set((self.context.engine.auth.requestTemporaryTwoStepPasswordToken(password: text, period: self.period, requiresBiometrics: self.requiresBiometrics) |> deliverOnMainQueue).start(next: { [weak self] token in - if let strongSelf = self { - strongSelf.completion(token) - } - }, error: { [weak self] _ in - if let strongSelf = self { - strongSelf.inputFieldNode.shake() - strongSelf.hapticFeedback.error() - strongSelf.isVerifying = false - strongSelf.updateState() - } - })) - self.updateState() - } -} -func botCheckoutPasswordEntryController(context: AccountContext, strings: PresentationStrings, passwordTip: String?, cartTitle: String, period: Int32, requiresBiometrics: Bool, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) -> AlertController { - var dismissImpl: (() -> Void)? - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: BotCheckoutPasswordAlertContentNode(context: context, theme: presentationData.theme, strings: strings, passwordTip: passwordTip, cardTitle: cartTitle, period: period, requiresBiometrics: requiresBiometrics, cancel: { - dismissImpl?() - }, completion: { token in - completion(token) - dismissImpl?() - })) - dismissImpl = { [weak controller] in - controller?.dismissAnimated() + isVerifying = true + doneInProgressPromise.set(true) + + disposable.set((context.engine.auth.requestTemporaryTwoStepPasswordToken(password: password, period: period, requiresBiometrics: requiresBiometrics) + |> deliverOnMainQueue).start(next: { token in + completion(token) + dismissImpl?() + }, error: { _ in + inputState.animateError() + isVerifying = false + doneInProgressPromise.set(false) + })) } - return controller + dismissImpl = { [weak alertController] in + alertController?.dismiss(completion: nil) + } + return alertController } diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift new file mode 100644 index 0000000000..e9aff0ba7c --- /dev/null +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift @@ -0,0 +1,535 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import AccountContext +import ViewControllerComponent +import ResizableSheetComponent +import TelegramPresentationData +import PresentationDataUtils +import MultilineTextComponent +import ButtonComponent +import ListSectionComponent +import ListActionItemComponent +import GlassBarButtonComponent +import BundleIconComponent + +struct BotCheckoutPaymentWebToken: Equatable { + let title: String + let data: String + var saveOnServer: Bool +} + +enum BotCheckoutPaymentMethod: Equatable { + case savedCredentials(BotPaymentSavedCredentials) + case webToken(BotCheckoutPaymentWebToken) + case applePay + case other(BotPaymentMethod) + + var title: String { + switch self { + case let .savedCredentials(credentials): + switch credentials { + case let .card(_, title): + return title + } + case let .webToken(token): + return token.title + case .applePay: + return "Apple Pay" + case let .other(method): + return method.title + } + } +} + +private func splitSavedCardTitle(_ title: String) -> (String, String?) { + guard let separatorIndex = title.lastIndex(of: "*") else { + return (title, nil) + } + + let name = String(title[.. Void + let addCard: () -> Void + + init( + methods: [BotCheckoutPaymentMethod], + selectedMethod: BotCheckoutPaymentMethod?, + selectMethod: @escaping (BotCheckoutPaymentMethod) -> Void, + addCard: @escaping () -> Void + ) { + self.methods = methods + self.selectedMethod = selectedMethod + self.selectMethod = selectMethod + self.addCard = addCard + } + + static func ==(lhs: BotCheckoutPaymentMethodContentComponent, rhs: BotCheckoutPaymentMethodContentComponent) -> Bool { + if lhs.methods != rhs.methods { + return false + } + if lhs.selectedMethod != rhs.selectedMethod { + return false + } + return true + } + + final class View: UIView { + private let section = ComponentView() + + private var component: BotCheckoutPaymentMethodContentComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BotCheckoutPaymentMethodContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let theme = environment.theme.withModalBlocksBackground() + let itemFontSize: CGFloat = 17.0 + let sideInset: CGFloat = 16.0 + + var contentHeight: CGFloat = 76.0 + 9.0 + + var items: [AnyComponentWithIdentity] = [] + for i in 0 ..< component.methods.count { + let method = component.methods[i] + let isSelected = method == component.selectedMethod + + var title = method.title + var icon: ListActionItemComponent.Icon? + var accessory: ListActionItemComponent.Accessory? + + switch method { + case let .savedCredentials(credentials): + switch credentials { + case let .card(_, cardTitle): + let (cardName, cardSuffix) = splitSavedCardTitle(cardTitle) + title = cardName + if let cardSuffix { + accessory = .custom(ListActionItemComponent.CustomAccessory( + component: AnyComponentWithIdentity( + id: AnyHashable("card-suffix-\(i)-\(cardSuffix)"), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: cardSuffix, + font: Font.regular(itemFontSize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + ), + insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0) + )) + } + } + case .applePay: + title = "Apple Pay" + icon = ListActionItemComponent.Icon( + component: AnyComponentWithIdentity( + id: AnyHashable("apple-pay"), + component: AnyComponent(BundleIconComponent( + name: "Bot Payments/ApplePayLogo", + tintColor: nil + )) + ) + ) + case let .webToken(token): + let (cardName, cardSuffix) = splitSavedCardTitle(token.title) + title = cardName + if let cardSuffix { + accessory = .custom(ListActionItemComponent.CustomAccessory( + component: AnyComponentWithIdentity( + id: AnyHashable("card-suffix-\(i)-\(cardSuffix)"), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: cardSuffix, + font: Font.regular(itemFontSize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + ), + insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0) + )) + } + case let .other(method): + title = method.title + } + + items.append(AnyComponentWithIdentity(id: AnyHashable("method-\(i)"), component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: title, + font: Font.regular(itemFontSize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: isSelected, + toggle: { + component.selectMethod(method) + } + )), + icon: icon, + accessory: accessory, + action: { _ in + component.selectMethod(method) + } + )))) + } + + items.append(AnyComponentWithIdentity(id: AnyHashable("add-card"), component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Checkout_PaymentMethod_New, + font: Font.regular(itemFontSize), + textColor: theme.list.itemAccentColor + )), + maximumNumberOfLines: 1 + )), + contentInsets: UIEdgeInsets(top: 12.0, left: 45.0, bottom: 12.0, right: 0.0), + leftIcon: nil, + icon: nil, + accessory: nil, + action: { _ in + component.addCard() + } + )))) + + self.section.parentState = state + let sectionSize = self.section.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: items + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let sectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sectionSize) + if let sectionView = self.section.view { + if sectionView.superview == nil { + self.addSubview(sectionView) + } + transition.setFrame(view: sectionView, frame: sectionFrame) + } + contentHeight += sectionSize.height + contentHeight += 112.0 + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class BotCheckoutPaymentMethodScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let currentMethod: BotCheckoutPaymentMethod? + let methods: [BotCheckoutPaymentMethod] + let applyValue: (BotCheckoutPaymentMethod) -> Void + let newCard: () -> Void + let otherMethod: (String, String) -> Void + + init( + context: AccountContext, + currentMethod: BotCheckoutPaymentMethod?, + methods: [BotCheckoutPaymentMethod], + applyValue: @escaping (BotCheckoutPaymentMethod) -> Void, + newCard: @escaping () -> Void, + otherMethod: @escaping (String, String) -> Void + ) { + self.context = context + self.currentMethod = currentMethod + self.methods = methods + self.applyValue = applyValue + self.newCard = newCard + self.otherMethod = otherMethod + } + + static func ==(lhs: BotCheckoutPaymentMethodScreenComponent, rhs: BotCheckoutPaymentMethodScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.currentMethod != rhs.currentMethod { + return false + } + if lhs.methods != rhs.methods { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var component: BotCheckoutPaymentMethodScreenComponent? + private weak var state: EmptyComponentState? + private var selectedMethod: BotCheckoutPaymentMethod? + private var isDismissing = false + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BotCheckoutPaymentMethodScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + if let currentMethod = component.currentMethod, component.methods.contains(currentMethod) { + self.selectedMethod = currentMethod + } else { + self.selectedMethod = nil + } + } + + self.component = component + self.state = state + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() + + let dismiss: (Bool, (() -> Void)?) -> Void = { [weak self] animated, completion in + guard let self, !self.isDismissing else { + return + } + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? BotCheckoutPaymentMethodScreen { + controller.completePendingDismiss() + controller.dismiss(animated: false) + } + completion?() + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() + } + } + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(BotCheckoutPaymentMethodContentComponent( + methods: component.methods, + selectedMethod: self.selectedMethod, + selectMethod: { [weak self] method in + guard let self else { + return + } + self.selectedMethod = method + self.state?.updated(transition: .spring(duration: 0.35)) + }, + addCard: { [weak self] in + guard let self, let component = self.component else { + return + } + dismiss(true, { + component.newCard() + }) + } + )), + titleItem: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Checkout_PaymentMethod, + font: Font.semibold(17.0), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true, nil) + } + ) + ), + bottomItem: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("proceed"), + component: AnyComponent(ButtonTextContentComponent( + text: environment.strings.Checkout_PaymentMethod_Proceed, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )) + ), + isEnabled: self.selectedMethod != nil, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component, let selectedMethod = self.selectedMethod else { + return + } + dismiss(true, { + switch selectedMethod { + case let .other(method): + component.otherMethod(method.url, method.title) + default: + component.applyValue(selectedMethod) + } + }) + } + )), + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + animateOut: self.animateOut + )), + environment: { + environment + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, + inputHeight: 0.0, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated, nil) + } + ) + }, + forceUpdate: true, + containerSize: availableSize + ) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class BotCheckoutPaymentMethodScreen: ViewControllerComponentContainer { + private var isDismissed = false + private var dismissCompletion: (() -> Void)? + + init(context: AccountContext, currentMethod: BotCheckoutPaymentMethod?, methods: [BotCheckoutPaymentMethod], applyValue: @escaping (BotCheckoutPaymentMethod) -> Void, newCard: @escaping () -> Void, otherMethod: @escaping (String, String) -> Void) { + super.init( + context: context, + component: BotCheckoutPaymentMethodScreenComponent( + context: context, + currentMethod: currentMethod, + methods: methods, + applyValue: applyValue, + newCard: newCard, + otherMethod: otherMethod + ), + navigationBarAppearance: .none + ) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + fileprivate func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + + func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } + } + + override func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + self.dismissCompletion = completion + + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } else { + self.completePendingDismiss() + self.dismiss(animated: false) + } + } + } +} diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift deleted file mode 100644 index a728d78155..0000000000 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift +++ /dev/null @@ -1,262 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramCore -import AccountContext -import AppBundle - -struct BotCheckoutPaymentWebToken: Equatable { - let title: String - let data: String - var saveOnServer: Bool -} - -enum BotCheckoutPaymentMethod: Equatable { - case savedCredentials(BotPaymentSavedCredentials) - case webToken(BotCheckoutPaymentWebToken) - case applePay - case other(BotPaymentMethod) - - var title: String { - switch self { - case let .savedCredentials(credentials): - switch credentials { - case let .card(_, title): - return title - } - case let .webToken(token): - return token.title - case .applePay: - return "Apple Pay" - case let .other(method): - return method.title - } - } -} - -final class BotCheckoutPaymentMethodSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - init(context: AccountContext, currentMethod: BotCheckoutPaymentMethod?, methods: [BotCheckoutPaymentMethod], applyValue: @escaping (BotCheckoutPaymentMethod) -> Void, newCard: @escaping () -> Void, otherMethod: @escaping (String, String) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }).strict() - - var items: [ActionSheetItem] = [] - - items.append(ActionSheetTextItem(title: strings.Checkout_PaymentMethod)) - - for method in methods { - let title: String - let icon: UIImage? - switch method { - case let .savedCredentials(credentials): - switch credentials { - case let .card(_, cardTitle): - title = cardTitle - icon = nil - } - case let .webToken(token): - title = token.title - icon = nil - case .applePay: - title = "Apple Pay" - icon = UIImage(bundleImageName: "Bot Payments/ApplePayLogo")?.precomposed() - case let .other(method): - title = method.title - icon = nil - } - let value: Bool? - if let currentMethod = currentMethod { - value = method == currentMethod - } else { - value = nil - } - items.append(BotCheckoutPaymentMethodItem(title: title, icon: icon, value: value, action: { [weak self] _ in - if case let .other(method) = method { - otherMethod(method.url, method.title) - } else { - applyValue(method) - } - self?.dismissAnimated() - })) - } - - items.append(ActionSheetButtonItem(title: strings.Checkout_PaymentMethod_New, action: { [weak self] in - self?.dismissAnimated() - newCard() - })) - - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -public class BotCheckoutPaymentMethodItem: ActionSheetItem { - public let title: String - public let icon: UIImage? - public let value: Bool? - public let action: (Bool) -> Void - - public init(title: String, icon: UIImage?, value: Bool?, action: @escaping (Bool) -> Void) { - self.title = title - self.icon = icon - self.value = value - self.action = action - } - - public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - let node = BotCheckoutPaymentMethodItemNode(theme: theme) - node.setItem(self) - return node - } - - public func updateNode(_ node: ActionSheetItemNode) { - guard let node = node as? BotCheckoutPaymentMethodItemNode else { - assertionFailure() - return - } - - node.setItem(self) - node.requestLayoutUpdate() - } -} - -public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode { - private let defaultFont: UIFont - - private let theme: ActionSheetControllerTheme - - private var item: BotCheckoutPaymentMethodItem? - - private let button: HighlightTrackingButton - private let titleNode: ASTextNode - private let iconNode: ASImageNode - private let checkNode: ASImageNode - - public override init(theme: ActionSheetControllerTheme) { - self.theme = theme - self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0)) - - self.button = HighlightTrackingButton() - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - - self.checkNode = ASImageNode() - self.checkNode.isUserInteractionEnabled = false - self.checkNode.displayWithoutProcessing = true - self.checkNode.displaysAsynchronously = false - self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(theme.controlAccentColor.cgColor) - context.setLineWidth(2.0) - context.move(to: CGPoint(x: 12.0, y: 1.0)) - context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) - context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) - context.strokePath() - }) - - super.init(theme: theme) - - self.view.addSubview(self.button) - self.addSubnode(self.titleNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.checkNode) - - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = theme.itemHighlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = theme.itemBackgroundColor - }) - } - } - } - - self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } - - func setItem(_ item: BotCheckoutPaymentMethodItem) { - self.item = item - - self.titleNode.attributedText = NSAttributedString(string: item.title, font: self.defaultFont, textColor: self.theme.primaryTextColor) - self.iconNode.image = item.icon - if let value = item.value { - self.checkNode.isHidden = !value - } else { - self.checkNode.isHidden = true - } - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 57.0) - - self.button.frame = CGRect(origin: CGPoint(), size: size) - - var checkInset: CGFloat = 15.0 - if let _ = self.item?.value { - checkInset = 44.0 - } - - let iconSize: CGSize - if let image = self.iconNode.image { - iconSize = image.size - } else { - iconSize = CGSize() - } - let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - iconSize.width - 15.0 - 8.0, height: size.height)) - self.titleNode.frame = CGRect(origin: CGPoint(x: checkInset, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) - self.iconNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - iconSize.width, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) - - if let image = self.checkNode.image { - self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) - } - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc func buttonPressed() { - if let item = self.item { - let updatedValue: Bool - if let value = item.value { - updatedValue = !value - } else { - updatedValue = true - } - item.action(updatedValue) - } - } -} diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift deleted file mode 100644 index 10b86ab94f..0000000000 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift +++ /dev/null @@ -1,224 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramCore -import AccountContext -import TelegramStringFormatting - -final class BotCheckoutPaymentShippingOptionSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - init(context: AccountContext, currency: String, options: [BotPaymentShippingOption], currentId: String?, applyValue: @escaping (String) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }).strict() - - var items: [ActionSheetItem] = [] - - items.append(ActionSheetTextItem(title: strings.Checkout_ShippingMethod)) - - let dismissAction: () -> Void = { [weak self] in - self?.dismissAnimated() - } - - let toggleCheck: (String, Int) -> Void = { [weak self] id, itemIndex in - for i in 0 ..< options.count { - self?.updateItem(groupIndex: 0, itemIndex: i + 1, { item in - if let item = item as? BotCheckoutPaymentShippingOptionItem, let value = item.value { - return BotCheckoutPaymentShippingOptionItem(title: item.title, label: item.label, value: i == itemIndex ? !value : false, action: item.action) - } - return item - }) - } - applyValue(id) - dismissAction() - } - - var itemIndex = 0 - for option in options { - let index = itemIndex - var totalPrice: Int64 = 0 - for price in option.prices { - totalPrice += price.amount - } - let value: Bool? - if let currentId = currentId { - value = option.id == currentId - } else { - value = nil - } - items.append(BotCheckoutPaymentShippingOptionItem(title: option.title, label: formatCurrencyAmount(totalPrice, currency: currency), value: value, action: { value in - toggleCheck(option.id, index) - })) - itemIndex += 1 - } - - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -public class BotCheckoutPaymentShippingOptionItem: ActionSheetItem { - public let title: String - public let label: String - public let value: Bool? - public let action: (Bool) -> Void - - public init(title: String, label: String, value: Bool?, action: @escaping (Bool) -> Void) { - self.title = title - self.label = label - self.value = value - self.action = action - } - - public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - let node = BotCheckoutPaymentShippingOptionItemNode(theme: theme) - node.setItem(self) - return node - } - - public func updateNode(_ node: ActionSheetItemNode) { - guard let node = node as? BotCheckoutPaymentShippingOptionItemNode else { - assertionFailure() - return - } - - node.setItem(self) - node.requestLayoutUpdate() - } -} - -public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode { - private let defaultFont: UIFont - - private let theme: ActionSheetControllerTheme - - private var item: BotCheckoutPaymentShippingOptionItem? - - private let button: HighlightTrackingButton - private let titleNode: ASTextNode - private let labelNode: ASTextNode - private let checkNode: ASImageNode - - public override init(theme: ActionSheetControllerTheme) { - self.theme = theme - self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0)) - - self.button = HighlightTrackingButton() - - self.titleNode = ASTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - - self.labelNode = ASTextNode() - self.labelNode.maximumNumberOfLines = 1 - self.labelNode.isUserInteractionEnabled = false - self.labelNode.displaysAsynchronously = false - - self.checkNode = ASImageNode() - self.checkNode.isUserInteractionEnabled = false - self.checkNode.displayWithoutProcessing = true - self.checkNode.displaysAsynchronously = false - self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(theme.controlAccentColor.cgColor) - context.setLineWidth(2.0) - context.move(to: CGPoint(x: 12.0, y: 1.0)) - context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) - context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) - context.strokePath() - }) - - super.init(theme: theme) - - self.view.addSubview(self.button) - self.addSubnode(self.titleNode) - self.addSubnode(self.labelNode) - self.addSubnode(self.checkNode) - - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor - }) - } - } - } - - self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } - - func setItem(_ item: BotCheckoutPaymentShippingOptionItem) { - self.item = item - - self.titleNode.attributedText = NSAttributedString(string: item.title, font: self.defaultFont, textColor: self.theme.primaryTextColor) - self.labelNode.attributedText = NSAttributedString(string: item.label, font: self.defaultFont, textColor: self.theme.primaryTextColor) - if let value = item.value { - self.checkNode.isHidden = !value - } else { - self.checkNode.isHidden = true - } - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 57.0) - - self.button.frame = CGRect(origin: CGPoint(), size: size) - - var checkInset: CGFloat = 15.0 - if let _ = self.item?.value { - checkInset = 44.0 - } - - let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) - let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) - self.titleNode.frame = CGRect(origin: CGPoint(x: checkInset, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) - self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) - - if let image = self.checkNode.image { - self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) - } - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc func buttonPressed() { - if let item = self.item { - let updatedValue: Bool - if let value = item.value { - updatedValue = !value - } else { - updatedValue = true - } - item.action(updatedValue) - } - } -} diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift new file mode 100644 index 0000000000..f86a1496e5 --- /dev/null +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift @@ -0,0 +1,417 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import AccountContext +import TelegramStringFormatting +import ViewControllerComponent +import ResizableSheetComponent +import TelegramPresentationData +import PresentationDataUtils +import MultilineTextComponent +import ButtonComponent +import ListSectionComponent +import ListActionItemComponent +import GlassBarButtonComponent +import BundleIconComponent + +private final class BotCheckoutShippingOptionContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let currency: String + let options: [BotPaymentShippingOption] + let selectedId: String? + let selectOption: (String) -> Void + + init( + currency: String, + options: [BotPaymentShippingOption], + selectedId: String?, + selectOption: @escaping (String) -> Void + ) { + self.currency = currency + self.options = options + self.selectedId = selectedId + self.selectOption = selectOption + } + + static func ==(lhs: BotCheckoutShippingOptionContentComponent, rhs: BotCheckoutShippingOptionContentComponent) -> Bool { + if lhs.currency != rhs.currency { + return false + } + if lhs.options != rhs.options { + return false + } + if lhs.selectedId != rhs.selectedId { + return false + } + return true + } + + final class View: UIView { + private let section = ComponentView() + + private var component: BotCheckoutShippingOptionContentComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BotCheckoutShippingOptionContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let theme = environment.theme.withModalBlocksBackground() + let itemFontSize: CGFloat = 17.0 + let sideInset: CGFloat = 16.0 + + var contentHeight: CGFloat = 76.0 + 9.0 + + var items: [AnyComponentWithIdentity] = [] + for option in component.options { + let totalPrice = option.prices.reduce(Int64(0)) { current, price in + return current + price.amount + } + let priceText = formatCurrencyAmount(totalPrice, currency: component.currency) + let isSelected = option.id == component.selectedId + + items.append(AnyComponentWithIdentity(id: option.id, component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: option.title, + font: Font.regular(itemFontSize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: isSelected, + toggle: { + component.selectOption(option.id) + } + )), + icon: nil, + accessory: .custom(ListActionItemComponent.CustomAccessory( + component: AnyComponentWithIdentity( + id: AnyHashable("price-\(option.id)"), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: priceText, + font: Font.regular(itemFontSize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + ), + insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0) + )), + action: { _ in + component.selectOption(option.id) + } + )))) + } + + self.section.parentState = state + let sectionSize = self.section.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: items + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let sectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sectionSize) + if let sectionView = self.section.view { + if sectionView.superview == nil { + self.addSubview(sectionView) + } + transition.setFrame(view: sectionView, frame: sectionFrame) + } + contentHeight += sectionSize.height + contentHeight += 112.0 + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class BotCheckoutShippingOptionScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let currency: String + let options: [BotPaymentShippingOption] + let currentId: String? + let applyValue: (String) -> Void + + init( + context: AccountContext, + currency: String, + options: [BotPaymentShippingOption], + currentId: String?, + applyValue: @escaping (String) -> Void + ) { + self.context = context + self.currency = currency + self.options = options + self.currentId = currentId + self.applyValue = applyValue + } + + static func ==(lhs: BotCheckoutShippingOptionScreenComponent, rhs: BotCheckoutShippingOptionScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.currency != rhs.currency { + return false + } + if lhs.options != rhs.options { + return false + } + if lhs.currentId != rhs.currentId { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var component: BotCheckoutShippingOptionScreenComponent? + private weak var state: EmptyComponentState? + private var selectedId: String? + private var isDismissing = false + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BotCheckoutShippingOptionScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + if let currentId = component.currentId, component.options.contains(where: { $0.id == currentId }) { + self.selectedId = currentId + } else { + self.selectedId = nil + } + } + + self.component = component + self.state = state + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() + + let dismiss: (Bool) -> Void = { [weak self] animated in + guard let self, !self.isDismissing else { + return + } + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? BotCheckoutShippingOptionScreen { + controller.completePendingDismiss() + controller.dismiss(animated: false) + } + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() + } + } + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(BotCheckoutShippingOptionContentComponent( + currency: component.currency, + options: component.options, + selectedId: self.selectedId, + selectOption: { [weak self] id in + guard let self else { + return + } + self.selectedId = id + self.state?.updated(transition: .spring(duration: 0.35)) + } + )), + titleItem: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Checkout_ShippingMethod, + font: Font.semibold(17.0), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + bottomItem: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("proceed"), + component: AnyComponent(ButtonTextContentComponent( + text: environment.strings.Checkout_ShippingMethod_Proceed, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )) + ), + isEnabled: self.selectedId != nil, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component, let selectedId = self.selectedId else { + return + } + component.applyValue(selectedId) + dismiss(true) + } + )), + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + animateOut: self.animateOut + )), + environment: { + environment + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, + inputHeight: 0.0, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated) + } + ) + }, + forceUpdate: true, + containerSize: availableSize + ) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class BotCheckoutShippingOptionScreen: ViewControllerComponentContainer { + private var isDismissed = false + private var dismissCompletion: (() -> Void)? + + init(context: AccountContext, currency: String, options: [BotPaymentShippingOption], currentId: String?, applyValue: @escaping (String) -> Void) { + super.init( + context: context, + component: BotCheckoutShippingOptionScreenComponent( + context: context, + currency: currency, + options: options, + currentId: currentId, + applyValue: applyValue + ), + navigationBarAppearance: .none + ) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + fileprivate func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + + func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } + } + + override func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + self.dismissCompletion = completion + + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } else { + self.completePendingDismiss() + self.dismiss(animated: false) + } + } + } +} diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 74cd9f60b2..a240332122 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/ContextUI", "//submodules/UndoUI", "//submodules/TranslateUI", + "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", @@ -31,6 +32,7 @@ swift_library( "//submodules/TelegramUI/Components/MinimizedContainer", "//submodules/Pasteboard", "//submodules/SaveToCameraRoll", + "//submodules/TextFormat:TextFormat", "//submodules/TelegramUI/Components/NavigationStackComponent", "//submodules/LocationUI", "//submodules/OpenInExternalAppUI", diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index 23b1c03107..d92f3e29cd 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -139,7 +138,7 @@ final class BrowserAddressListComponent: Component { struct State { let recent: [TelegramMediaWebpage] let isRecentExpanded: Bool - let bookmarks: [Message] + let bookmarks: [EngineMessage] } private let outerView = UIButton() @@ -367,17 +366,22 @@ final class BrowserAddressListComponent: Component { ) } else { var webPage: TelegramMediaWebpage? - var itemMessage: Message? - + var itemMessage: EngineMessage? + if section.id == 0 { webPage = state.recent[i] } else if section.id == 1 { let message = state.bookmarks[i] if let primaryUrl = getPrimaryUrl(message: message) { - if let media = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage { - webPage = media + if let foundWebpage = message.engineMedia.compactMap({ engineMedia -> TelegramMediaWebpage? in + if case let .webpage(webpage) = engineMedia { + return webpage + } + return nil + }).first { + webPage = foundWebpage } else { - webPage = TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: primaryUrl, displayUrl: "", hash: 0, type: nil, websiteName: "", title: message.text, text: "", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))) + webPage = TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: primaryUrl, displayUrl: "", hash: 0, type: nil, websiteName: "", title: message.text, text: "", embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))) } itemMessage = message } else { @@ -523,9 +527,9 @@ final class BrowserAddressListComponent: Component { return } - var bookmarks: [Message] = [] + var bookmarks: [EngineMessage] = [] for entry in view.0.entries.reversed() { - bookmarks.append(entry.message) + bookmarks.append(EngineMessage(entry.message)) } let isFirstTime = self.stateValue == nil diff --git a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift index 49c9ece448..ac05e5953e 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListItemComponent.swift @@ -3,7 +3,6 @@ import UIKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import MultilineTextComponent import TelegramPresentationData @@ -26,21 +25,21 @@ final class BrowserAddressListItemComponent: Component { let context: AccountContext let theme: PresentationTheme let webPage: TelegramMediaWebpage - var message: Message? + var message: EngineMessage? let hasNext: Bool let insets: UIEdgeInsets let action: () -> Void - let contextAction: ((TelegramMediaWebpage, Message?, ContextExtractedContentContainingView, ContextGesture) -> Void)? - + let contextAction: ((TelegramMediaWebpage, EngineMessage?, ContextExtractedContentContainingView, ContextGesture) -> Void)? + init( context: AccountContext, theme: PresentationTheme, webPage: TelegramMediaWebpage, - message: Message?, + message: EngineMessage?, hasNext: Bool, insets: UIEdgeInsets, action: @escaping () -> Void, - contextAction: ((TelegramMediaWebpage, Message?, ContextExtractedContentContainingView, ContextGesture) -> Void)? + contextAction: ((TelegramMediaWebpage, EngineMessage?, ContextExtractedContentContainingView, ContextGesture) -> Void)? ) { self.context = context self.theme = theme @@ -190,7 +189,7 @@ final class BrowserAddressListItemComponent: Component { if let image = content.image { if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 80, height: 80)) { if let message = component.message { - iconImageReferenceAndRepresentation = (.message(message: MessageReference(message), media: image), representation) + iconImageReferenceAndRepresentation = (.message(message: MessageReference(message._asMessage()), media: image), representation) } else { iconImageReferenceAndRepresentation = (.standalone(media: image), representation) } @@ -198,7 +197,7 @@ final class BrowserAddressListItemComponent: Component { } else if let file = content.file { if let representation = smallestImageRepresentation(file.previewRepresentations) { if let message = component.message { - iconImageReferenceAndRepresentation = (.message(message: MessageReference(message), media: file), representation) + iconImageReferenceAndRepresentation = (.message(message: MessageReference(message._asMessage()), media: file), representation) } else { iconImageReferenceAndRepresentation = (.standalone(media: file), representation) } diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index d72f35d457..6ca5cb0f41 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -4,7 +4,6 @@ import AccountContext import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import PresentationDataUtils @@ -44,18 +43,18 @@ public final class BrowserBookmarksScreen: ViewController { self.controller = controller self.presentationData = presentationData - var openMessageImpl: ((Message) -> Bool)? - var openContextMenuImpl: ((Message, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void)? + var openMessageImpl: ((EngineMessage) -> Bool)? + var openContextMenuImpl: ((EngineMessage, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void)? self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in if let openMessageImpl = openMessageImpl { - return openMessageImpl(message) + return openMessageImpl(EngineMessage(message)) } else { return false } }, openPeer: { _, _, _, _ in }, openPeerMention: { _, _ in }, openMessageContextMenu: { message, _, sourceView, rect, gesture, _ in - openContextMenuImpl?(message, sourceView, rect, gesture) + openContextMenuImpl?(EngineMessage(message), sourceView, rect, gesture) }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _, _, _ in }, activateMessagePinch: { _ in @@ -66,7 +65,7 @@ public final class BrowserBookmarksScreen: ViewController { }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in - }, sendMessage: { _ in + }, sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in @@ -83,12 +82,13 @@ public final class BrowserBookmarksScreen: ViewController { controller.openUrl(url.url) controller.dismiss() } - }, shareCurrentLocation: { - }, shareAccountContact: { + }, openExternalInstantPage: { _ in + }, shareCurrentLocation: { _ in + }, shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { message, _ in if let openMessageImpl = openMessageImpl { - let _ = openMessageImpl(message) + let _ = openMessageImpl(EngineMessage(message)) } }, openWallpaper: { _ in }, openTheme: {_ in @@ -134,7 +134,7 @@ public final class BrowserBookmarksScreen: ViewController { }, displaySwipeToReplyHint: { }, dismissReplyMarkupMessage: { _ in }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in + }, openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, displayPollSolution: { _, _ in }, displayPsa: { _, _ in @@ -176,10 +176,11 @@ public final class BrowserBookmarksScreen: ViewController { }, sendGift: { _ in }, openUniqueGift: { _ in }, openMessageFeeException: { - }, requestMessageUpdate: { _, _ in + }, requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { - }, scrollToMessageId: { _ in + }, scrollToMessageId: { _, _ in + }, scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, attemptedNavigationToPrivateQuote: { _ in }, forceUpdateWarpContents: { @@ -189,10 +190,13 @@ public final class BrowserBookmarksScreen: ViewController { }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) - let tagMask: MessageTags = .webPage + let tagMask: EngineMessage.Tags = .webPage let chatLocationContextHolder = Atomic(value: nil) self.historyNode = context.sharedContext.makeChatHistoryListNode( context: context, @@ -256,7 +260,13 @@ public final class BrowserBookmarksScreen: ViewController { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } var itemList: [ContextMenuItem] = [] - if let webPage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, let url = webPage.content.url { + let foundWebpage = message.engineMedia.compactMap { engineMedia -> TelegramMediaWebpage? in + if case let .webpage(webpage) = engineMedia { + return webpage + } + return nil + }.first + if let webPage = foundWebpage, let url = webPage.content.url { itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.WebBrowser_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -294,7 +304,7 @@ public final class BrowserBookmarksScreen: ViewController { guard let (layout, navigationBarHeight, _) = self.validLayout, let navigationBar = self.controller?.navigationBar else { return } - let tagMask: MessageTags = .webPage + let tagMask: EngineMessage.Tags = .webPage self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .navigation, placeholder: self.presentationData.strings.Common_Search, hasBackground: true, contentNode: ChatHistorySearchContainerNode(context: self.context, peerId: self.context.account.peerId, threadId: nil, tagMask: tagMask, interfaceInteraction: self.controllerInteraction), cancel: { [weak self] in self?.controller?.deactivateSearch() @@ -392,7 +402,7 @@ public final class BrowserBookmarksScreen: ViewController { self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.title = self.presentationData.strings.WebBrowser_Bookmarks_Title self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in diff --git a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift index c0305c7761..0de7e4c7eb 100644 --- a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift +++ b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift @@ -3,7 +3,6 @@ import UIKit import Display import ComponentFlow import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences @@ -44,7 +43,7 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate var presentInGlobalOverlay: (ViewController) -> Void = { _ in } var getNavigationController: () -> NavigationController? = { return nil } - private var tempFile: TempBoxFile? + private var tempFile: EngineTempBoxFile? init(context: AccountContext, presentationData: PresentationData, file: FileMediaReference) { self.context = context @@ -62,10 +61,10 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate var title: String = "file" var url = "" - if let path = self.context.account.postbox.mediaBox.completedResourcePath(file.media.resource) { + if let path = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.media.resource.id)) { var updatedPath = path if let fileName = file.media.fileName { - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) updatedPath = tempFile.path self.tempFile = tempFile title = fileName diff --git a/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift b/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift index b3f347b7a4..40fd616490 100644 --- a/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift +++ b/submodules/BrowserUI/Sources/BrowserExceptionDomainAlertContentNode.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift index 52ff3cecfc..f075b42965 100644 --- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift +++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift @@ -13,6 +13,7 @@ import AppBundle import InstantPageUI import UndoUI import TranslateUI +import TextProcessingScreen import ContextUI import Pasteboard import SaveToCameraRoll @@ -20,6 +21,7 @@ import SafariServices import LocationUI import OpenInExternalAppUI import GalleryUI +import TextFormat final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDelegate { private let context: AccountContext @@ -65,6 +67,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg var currentWebEmbedHeights: [Int : CGFloat] = [:] var currentExpandedDetails: [Int : Bool]? var currentDetailsItems: [InstantPageDetailsItem] = [] + private var resolvedExternalMediaDimensions: [MediaId: PixelDimensions] = [:] + private var pendingResolvedExternalMediaDimensions = Set() + private var codeHighlight: CachedMessageSyntaxHighlight? + private var codeHighlightState: (specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)? var currentAccessibilityAreas: [AccessibilityAreaNode] = [] @@ -87,6 +93,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg private let loadWebpageDisposable = MetaDisposable() private let resolveUrlDisposable = MetaDisposable() private let updateLayoutDisposable = MetaDisposable() + private let updateExternalMediaDimensionsDisposable = MetaDisposable() + private let updateCodeHighlightDisposable = MetaDisposable() private let loadProgress = ValuePromise(1.0, ignoreRepeated: true) private let readingProgress = ValuePromise(0.0, ignoreRepeated: true) @@ -159,6 +167,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.delegate = self + self.scrollNode.view.scrollsToTop = false if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never @@ -179,12 +188,16 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } self.scrollNode.view.addGestureRecognizer(recognizer) - self.webpageDisposable = (actualizedWebpage(account: context.account, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] result in - guard let self else { - return - } - self.updateWebPage(result, anchor: self.initialAnchor) - }) + if case let .Loaded(content) = webPage.content, let scheme = URL(string: content.url)?.scheme?.lowercased(), scheme == "http" || scheme == "https" { + self.webpageDisposable = (actualizedWebpage(account: context.account, webpage: webPage) |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + self.updateWebPage(result, anchor: self.initialAnchor) + }) + } + + self.updateCodeHighlight() } deinit { @@ -193,6 +206,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.loadWebpageDisposable.dispose() self.resolveUrlDisposable.dispose() self.updateLayoutDisposable.dispose() + self.updateExternalMediaDimensionsDisposable.dispose() + self.updateCodeHighlightDisposable.dispose() } required init?(coder: NSCoder) { @@ -304,6 +319,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } else { self.webPage = nil } + self.resolvedExternalMediaDimensions.removeAll() + self.pendingResolvedExternalMediaDimensions.removeAll() if let anchor = anchor { self.initialAnchor = anchor.removingPercentEncoding } else if let state = state { @@ -317,6 +334,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } } self.currentLayout = nil + self.updateCodeHighlight() self.updatePageLayout() self.scrollNode.frame = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) @@ -385,6 +403,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.settings = InstantPagePresentationSettings( themeType: self.presentationData.theme.overallDarkAppearance ? .dark : .light, fontSize: fontSize, + lineSpacingFactor: 1.0, forceSerif: state.isSerif, autoNightMode: false, ignoreAutoNightModeUntil: 0 @@ -478,16 +497,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } private func updatePageLayout() { - guard let (size, insets, _) = self.containerLayout, let (webPage, instantPage) = self.webPage else { + guard let (size, insets, _) = self.containerLayout, let (webPage, instantPage) = self.resolvedWebPage() else { return } - let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: size.width, safeInset: insets.left, strings: self.presentationData.strings, theme: self.theme, dateTimeFormat: self.presentationData.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) - - for (_, tileNode) in self.visibleTiles { - tileNode.removeFromSupernode() - } - self.visibleTiles.removeAll() + let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: size.width, sideInset: 17.0, safeInset: insets.left, strings: self.presentationData.strings, theme: self.theme, dateTimeFormat: self.presentationData.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights, cachedMessageSyntaxHighlight: self.codeHighlight) let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: size.width) @@ -548,6 +562,96 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg self.scrollNode.view.contentSize = currentLayout.contentSize self.scrollNodeFooter.frame = CGRect(origin: CGPoint(x: 0.0, y: currentLayout.contentSize.height), size: CGSize(width: size.width, height: 2000.0)) } + + private func updateCodeHighlight() { + guard let instantPage = self.webPage?.instantPage else { + self.codeHighlight = nil + self.codeHighlightState = nil + self.updateCodeHighlightDisposable.set(nil) + return + } + + let specs = syntaxHighlightSpecs(for: instantPage.blocks) + if let currentState = self.codeHighlightState, currentState.specs == specs { + return + } + + if specs.isEmpty { + let hadHighlight = self.codeHighlight != nil + self.codeHighlight = nil + self.codeHighlightState = nil + self.updateCodeHighlightDisposable.set(nil) + if hadHighlight { + self.updatePageLayout() + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + return + } + + let disposable = MetaDisposable() + self.codeHighlightState = (specs, disposable) + self.updateCodeHighlightDisposable.set(disposable) + disposable.set((asyncStanaloneSyntaxHighlight(current: self.codeHighlight, specs: specs) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + if self.codeHighlight != result { + self.codeHighlight = result + self.updatePageLayout() + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + })) + } + + private func syntaxHighlightSpecs(for blocks: [InstantPageBlock]) -> [CachedMessageSyntaxHighlight.Spec] { + var specs: [CachedMessageSyntaxHighlight.Spec] = [] + var seen = Set() + + func collect(blocks: [InstantPageBlock]) { + for block in blocks { + switch block { + case let .preformatted(text, language): + guard let language = normalizedCodeBlockLanguage(language), !text.plainText.isEmpty else { + continue + } + let spec = CachedMessageSyntaxHighlight.Spec(language: language, text: text.plainText) + if seen.insert(spec).inserted { + specs.append(spec) + } + case let .cover(block): + collect(blocks: [block]) + case let .postEmbed(_, _, _, _, _, blocks, _): + collect(blocks: blocks) + case let .collage(items, _): + collect(blocks: items) + case let .slideshow(items, _): + collect(blocks: items) + case let .details(_, blocks, _): + collect(blocks: blocks) + case let .list(items, _): + for item in items { + if case let .blocks(blocks, _, _) = item { + collect(blocks: blocks) + } + } + default: + break + } + } + } + + collect(blocks: blocks) + return specs + } + + private func normalizedCodeBlockLanguage(_ language: String?) -> String? { + guard let language else { + return nil + } + let normalized = language.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return normalized.isEmpty ? nil : normalized + } func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) { var visibleTileIndices = Set() @@ -655,6 +759,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg topNode = newNode self.visibleItemsWithNodes[itemIndex] = newNode itemNode = newNode + self.configureExternalMediaDimensionsUpdates(for: newNode) if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.requestLayoutUpdate = { [weak self] animated in @@ -678,6 +783,10 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } } + if let itemNode = itemNode { + self.configureExternalMediaDimensionsUpdates(for: itemNode) + } + if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated) } @@ -708,8 +817,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg topNode = tileNode self.visibleTiles[tileIndex] = tileNode } else { - if visibleTiles[tileIndex]!.frame != tileFrame { - transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame) + if let tileNode = self.visibleTiles[tileIndex] { + tileNode.update(tile: tile, backgroundColor: theme.pageBackgroundColor) + if tileNode.frame != tileFrame { + transition.updateFrame(node: tileNode, frame: tileFrame) + } } } } @@ -929,6 +1041,385 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg return nil } + private func configureExternalMediaDimensionsUpdates(for itemNode: InstantPageNode) { + let update: (MediaId, PixelDimensions) -> Void = { [weak self] mediaId, dimensions in + self?.updateExternalMediaDimensions(mediaId, dimensions) + } + if let itemNode = itemNode as? InstantPageExternalMediaDimensionsNode { + itemNode.updateExternalMediaDimensions = update + } + if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.contentNode.updateExternalMediaDimensions = update + } + } + + private func updateExternalMediaDimensions(_ mediaId: MediaId, _ dimensions: PixelDimensions) { + if self.resolvedExternalMediaDimensions[mediaId] == dimensions { + return + } + self.resolvedExternalMediaDimensions[mediaId] = dimensions + self.pendingResolvedExternalMediaDimensions.insert(mediaId) + + let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + self.updateExternalMediaDimensionsDisposable.set(signal.start(completed: { [weak self] in + self?.relayoutForResolvedExternalMediaDimensions() + })) + } + + private func relayoutForResolvedExternalMediaDimensions() { + guard !self.pendingResolvedExternalMediaDimensions.isEmpty else { + return + } + + let mediaIds = Array(self.pendingResolvedExternalMediaDimensions) + self.pendingResolvedExternalMediaDimensions.removeAll() + + let detailsStateMaps = self.captureExpandedDetailsStateMaps() + let viewportTop = self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentInset.top + var oldFrames: [MediaId: CGRect] = [:] + for mediaId in mediaIds { + if let frame = self.effectiveFrameForMedia(mediaId, detailsStateMaps: detailsStateMaps) { + oldFrames[mediaId] = frame + } + } + + self.updatePageLayout() + + var newFrames: [MediaId: CGRect] = [:] + for mediaId in mediaIds { + if let frame = self.effectiveFrameForMedia(mediaId, detailsStateMaps: detailsStateMaps) { + newFrames[mediaId] = frame + } + } + + if let compensatedViewportTop = self.compensatedViewportTop(oldFrames: oldFrames, newFrames: newFrames, viewportTop: viewportTop) { + self.setViewportTop(compensatedViewportTop) + } + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + + private func setViewportTop(_ viewportTop: CGFloat) { + let scrollView = self.scrollNode.view + let minOffsetY = -scrollView.contentInset.top + let maxOffsetY = max(minOffsetY, scrollView.contentSize.height - scrollView.bounds.height + scrollView.contentInset.bottom) + let contentOffsetY = min(max(viewportTop - scrollView.contentInset.top, minOffsetY), maxOffsetY) + if contentOffsetY.isFinite { + scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: contentOffsetY) + } + } + + private func compensatedViewportTop(oldFrames: [MediaId: CGRect], newFrames: [MediaId: CGRect], viewportTop: CGFloat) -> CGFloat? { + var pairedFrames: [(old: CGRect, new: CGRect)] = [] + for (mediaId, oldFrame) in oldFrames { + if let newFrame = newFrames[mediaId] { + pairedFrames.append((oldFrame, newFrame)) + } + } + if pairedFrames.isEmpty { + return nil + } + + if let intersecting = pairedFrames + .filter({ $0.old.height > 0.0 && $0.new.height > 0.0 && viewportTop > $0.old.minY && viewportTop < $0.old.maxY }) + .max(by: { $0.old.minY < $1.old.minY }) { + let ratio = min(max((viewportTop - intersecting.old.minY) / intersecting.old.height, 0.0), 1.0) + return intersecting.new.minY + ratio * intersecting.new.height + } + + if let above = pairedFrames + .filter({ viewportTop >= $0.old.maxY }) + .max(by: { $0.old.maxY < $1.old.maxY }) { + return viewportTop + (above.new.maxY - above.old.maxY) + } + + return nil + } + + private func captureExpandedDetailsStateMaps() -> [String: [Int: Bool]] { + guard let currentLayout = self.currentLayout else { + return [:] + } + var result: [String: [Int: Bool]] = [:] + self.captureExpandedDetailsStateMaps(items: currentLayout.items, visibleItemsWithNodes: self.visibleItemsWithNodes, path: [], result: &result) + return result + } + + private func captureExpandedDetailsStateMaps(items: [InstantPageItem], visibleItemsWithNodes: [Int: InstantPageNode], path: [Int], result: inout [String: [Int: Bool]]) { + let detailsNodes = visibleItemsWithNodes.compactMap { $0.value as? InstantPageDetailsNode } + + var detailsIndex = -1 + for item in items { + guard let detailsItem = item as? InstantPageDetailsItem else { + continue + } + detailsIndex += 1 + + guard let detailsNode = detailsNodes.first(where: { $0.item === detailsItem }) else { + continue + } + let nextPath = path + [detailsIndex] + result[self.detailsStateKey(nextPath)] = detailsNode.contentNode.currentExpandedDetails ?? [:] + self.captureExpandedDetailsStateMaps(items: detailsItem.items, visibleItemsWithNodes: detailsNode.contentNode.visibleItemsWithNodes, path: nextPath, result: &result) + } + } + + private func detailsStateKey(_ path: [Int]) -> String { + if path.isEmpty { + return "" + } + return path.map(String.init).joined(separator: ".") + } + + private func effectiveFrameForMedia(_ mediaId: MediaId, detailsStateMaps: [String: [Int: Bool]]) -> CGRect? { + guard let currentLayout = self.currentLayout else { + return nil + } + return self.effectiveFrameForMedia(mediaId, items: currentLayout.items, origin: .zero, expandedDetails: self.currentExpandedDetails, path: [], detailsStateMaps: detailsStateMaps) + } + + private func effectiveFrameForMedia(_ mediaId: MediaId, items: [InstantPageItem], origin: CGPoint, expandedDetails: [Int: Bool]?, path: [Int], detailsStateMaps: [String: [Int: Bool]]) -> CGRect? { + var collapseOffset: CGFloat = 0.0 + var detailsIndex = -1 + + for item in items { + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + + var itemFrame = item.frame.offsetBy(dx: origin.x, dy: origin.y - collapseOffset) + if let detailsItem = item as? InstantPageDetailsItem { + let nextPath = path + [detailsIndex] + let nestedExpandedDetails = detailsStateMaps[self.detailsStateKey(nextPath)] + let expanded = expandedDetails?[detailsIndex] ?? detailsItem.initiallyExpanded + let height = expanded ? detailsItem.titleHeight + self.effectiveContentHeight(items: detailsItem.items, baseHeight: detailsItem.frame.height - detailsItem.titleHeight, expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) : detailsItem.titleHeight + collapseOffset += item.frame.height - height + itemFrame.size.height = height + + if expanded, let nestedFrame = self.effectiveFrameForMedia(mediaId, items: detailsItem.items, origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY + detailsItem.titleHeight), expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) { + return nestedFrame + } + continue + } + + if self.itemContainsMedia(item, mediaId: mediaId) { + return itemFrame + } + } + + return nil + } + + private func effectiveContentHeight(items: [InstantPageItem], baseHeight: CGFloat, expandedDetails: [Int: Bool]?, path: [Int], detailsStateMaps: [String: [Int: Bool]]) -> CGFloat { + var contentHeight = baseHeight + var detailsIndex = -1 + + for item in items { + guard let detailsItem = item as? InstantPageDetailsItem else { + continue + } + detailsIndex += 1 + + let nextPath = path + [detailsIndex] + let nestedExpandedDetails = detailsStateMaps[self.detailsStateKey(nextPath)] + let expanded = expandedDetails?[detailsIndex] ?? detailsItem.initiallyExpanded + let height = expanded ? detailsItem.titleHeight + self.effectiveContentHeight(items: detailsItem.items, baseHeight: detailsItem.frame.height - detailsItem.titleHeight, expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) : detailsItem.titleHeight + contentHeight += -detailsItem.frame.height + height + } + + return contentHeight + } + + private func itemContainsMedia(_ item: InstantPageItem, mediaId: MediaId) -> Bool { + for media in item.medias { + if media.media.id == mediaId { + return true + } + } + return false + } + + private func resolvedWebPage() -> (webPage: TelegramMediaWebpage, instantPage: InstantPage?)? { + guard let (webPage, instantPage) = self.webPage else { + return nil + } + guard !self.resolvedExternalMediaDimensions.isEmpty, case let .Loaded(content) = webPage.content else { + return (webPage, instantPage) + } + + var instantPageUpdated = false + var effectiveInstantPage = instantPage + if let instantPage { + var media = instantPage.media + for (mediaId, currentMedia) in instantPage.media { + if let updatedMedia = self.updatedMediaIfNeeded(currentMedia) { + media[mediaId] = updatedMedia + instantPageUpdated = true + } + } + if instantPageUpdated { + effectiveInstantPage = InstantPage(blocks: instantPage.blocks, media: media, isComplete: instantPage.isComplete, rtl: instantPage.rtl, url: instantPage.url, views: instantPage.views) + } + } + + var imageUpdated = false + let effectiveImage = content.image.map { image -> TelegramMediaImage in + if let updated = self.updatedImageIfNeeded(image) { + imageUpdated = true + return updated + } else { + return image + } + } + + var fileUpdated = false + let effectiveFile = content.file.map { file -> TelegramMediaFile in + if let updated = self.updatedFileIfNeeded(file) { + fileUpdated = true + return updated + } else { + return file + } + } + + if !instantPageUpdated && !imageUpdated && !fileUpdated { + return (webPage, instantPage) + } + + let effectiveContent = TelegramMediaWebpageLoadedContent( + url: content.url, + displayUrl: content.displayUrl, + hash: content.hash, + type: content.type, + websiteName: content.websiteName, + title: content.title, + text: content.text, + embedUrl: content.embedUrl, + embedType: content.embedType, + embedSize: content.embedSize, + duration: content.duration, + author: content.author, + isMediaLargeByDefault: content.isMediaLargeByDefault, + imageIsVideoCover: content.imageIsVideoCover, + image: effectiveImage, + file: effectiveFile, + story: content.story, + attributes: content.attributes, + instantPage: effectiveInstantPage + ) + return (TelegramMediaWebpage(webpageId: webPage.webpageId, content: .Loaded(effectiveContent)), effectiveInstantPage) + } + + private func updatedMediaIfNeeded(_ media: Media) -> Media? { + if let image = media as? TelegramMediaImage { + return self.updatedImageIfNeeded(image) + } else if let file = media as? TelegramMediaFile { + return self.updatedFileIfNeeded(file) + } else { + return nil + } + } + + private func updatedImageIfNeeded(_ image: TelegramMediaImage) -> TelegramMediaImage? { + guard let dimensions = self.resolvedExternalMediaDimensions[image.imageId] else { + return nil + } + + var updatedRepresentations = image.representations + var didUpdate = false + for i in 0 ..< updatedRepresentations.count { + let representation = updatedRepresentations[i] + guard representation.resource is InstantPageExternalMediaResource, representation.dimensions != dimensions else { + continue + } + updatedRepresentations[i] = TelegramMediaImageRepresentation( + dimensions: dimensions, + resource: representation.resource, + progressiveSizes: representation.progressiveSizes, + immediateThumbnailData: representation.immediateThumbnailData, + hasVideo: representation.hasVideo, + isPersonal: representation.isPersonal, + typeHint: representation.typeHint + ) + didUpdate = true + } + + guard didUpdate else { + return nil + } + return TelegramMediaImage( + imageId: image.imageId, + representations: updatedRepresentations, + videoRepresentations: image.videoRepresentations, + immediateThumbnailData: image.immediateThumbnailData, + emojiMarkup: image.emojiMarkup, + reference: image.reference, + partialReference: image.partialReference, + flags: image.flags, + video: image.video + ) + } + + private func updatedFileIfNeeded(_ file: TelegramMediaFile) -> TelegramMediaFile? { + guard let dimensions = self.resolvedExternalMediaDimensions[file.fileId], file.resource is InstantPageExternalMediaResource else { + return nil + } + + let (attributes, didUpdate) = self.fileAttributesWithResolvedDimensions(file.attributes, dimensions: dimensions) + guard didUpdate else { + return nil + } + + return TelegramMediaFile( + fileId: file.fileId, + partialReference: file.partialReference, + resource: file.resource, + previewRepresentations: file.previewRepresentations, + videoThumbnails: file.videoThumbnails, + videoCover: file.videoCover, + immediateThumbnailData: file.immediateThumbnailData, + mimeType: file.mimeType, + size: file.size, + attributes: attributes, + alternativeRepresentations: file.alternativeRepresentations + ) + } + + private func fileAttributesWithResolvedDimensions(_ attributes: [TelegramMediaFileAttribute], dimensions: PixelDimensions) -> ([TelegramMediaFileAttribute], Bool) { + var updatedAttributes: [TelegramMediaFileAttribute] = [] + var didUpdate = false + var hasSizeAttribute = false + + for attribute in attributes { + switch attribute { + case let .ImageSize(size): + hasSizeAttribute = true + if size != dimensions { + updatedAttributes.append(.ImageSize(size: dimensions)) + didUpdate = true + } else { + updatedAttributes.append(attribute) + } + case let .Video(duration, size, flags, preloadSize, coverTime, videoCodec): + hasSizeAttribute = true + if size != dimensions { + updatedAttributes.append(.Video(duration: duration, size: dimensions, flags: flags, preloadSize: preloadSize, coverTime: coverTime, videoCodec: videoCodec)) + didUpdate = true + } else { + updatedAttributes.append(attribute) + } + default: + updatedAttributes.append(attribute) + } + } + + if !hasSizeAttribute { + updatedAttributes.append(.ImageSize(size: dimensions)) + didUpdate = true + } + + return (updatedAttributes, didUpdate) + } + private func openUrl(_ url: InstantPageUrlItem) { var baseUrl = url.url var anchor: String? @@ -1010,7 +1501,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self, let peer = peer { - if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.getNavigationController()?.pushViewController(controller) } } @@ -1043,12 +1534,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: baseUrl), openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: self.context, item: .url(url: baseUrl), openUrl: { [weak self] url in if let self { self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) } }) - self.present(actionSheet, nil) + self.push(actionSheet) } private func openMedia(_ media: InstantPageMedia) { @@ -1099,7 +1590,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg } } } - self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) + self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(playlistId: .instantPage(webpageId: webPage.webpageId), webPage: webPage, messageReference: nil, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) return } @@ -1172,12 +1663,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in if let self, let image = media.media._asMedia() as? TelegramMediaImage { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - let _ = copyToPasteboard(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + let _ = copyToPasteboard(context: self.context, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_LinkDialogSave, accessibilityLabel: self.presentationData.strings.Conversation_LinkDialogSave), action: { [weak self] in if let self, let image = media.media._asMedia() as? TelegramMediaImage { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - let _ = saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + let _ = saveToCameraRoll(context: self.context, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in if let self, let (webPage, _) = self.webPage, let image = media.media._asMedia() as? TelegramMediaImage { @@ -1331,15 +1822,22 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) if canTranslate { actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in - let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language) - controller.pushController = { [weak self] c in - self?.getNavigationController()?._keepModalDismissProgress = true - self?.push(c) + Task { @MainActor [weak self] in + guard let self else { + return + } + let controller = await TextProcessingScreen( + context: context, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: text, entities: []), + copyResult: { [weak self] text in + storeMessageTextInPasteboard(text.text, entities: text.entities) + self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil) + }, + translateChat: nil + ) + self.present(controller, nil) } - controller.presentController = { [weak self] c in - self?.present(c, nil) - } - self?.present(controller, nil) })) } diff --git a/submodules/BrowserUI/Sources/BrowserMarkdown.swift b/submodules/BrowserUI/Sources/BrowserMarkdown.swift new file mode 100644 index 0000000000..cd2de7521f --- /dev/null +++ b/submodules/BrowserUI/Sources/BrowserMarkdown.swift @@ -0,0 +1,2511 @@ +import Foundation +import UIKit +import TelegramCore +import AccountContext +import InstantPageUI +import TextFormat + +private let markdownPresentationIntentAttribute = NSAttributedString.Key("NSPresentationIntent") +private let markdownInlinePresentationIntentAttribute = NSAttributedString.Key("NSInlinePresentationIntent") +private let markdownLinkAttribute = NSAttributedString.Key("NSLink") +private let markdownImageURLAttribute = NSAttributedString.Key("NSImageURL") +private let markdownAlternateDescriptionAttribute = NSAttributedString.Key("NSAlternateDescription") + +@available(iOS 15.0, *) +private let markdownSoftBreakInlineIntent = InlinePresentationIntent(rawValue: 1 << 6) +@available(iOS 15.0, *) +private let markdownHardBreakInlineIntent = InlinePresentationIntent(rawValue: 1 << 7) +@available(iOS 15.0, *) +private let markdownInlineHTMLInlineIntent = InlinePresentationIntent(rawValue: 1 << 8) + +private let markdownDefaultBlockImageDimensions = PixelDimensions(width: 1200, height: 900) +private let markdownDefaultInlineImageDimensions = PixelDimensions(width: 18, height: 18) +private let markdownImageParsingEnabled = false +private let markdownRawHTMLTagRegex = try! NSRegularExpression(pattern: #"]*?>"#) +private let markdownFormulaPlaceholderRegex = try! NSRegularExpression(pattern: #"TGMDMATH\d+TGMD"#) +private let markdownVoidHTMLTags: Set = [ + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr" +] + +private struct MarkdownSafetyLimits { + let maxFileSize = 524_288 + let maxLineLength = 32_768 + let maxBlockquoteDepth = 64 + let maxListIndent = 96 + let maxRawHTMLTagCount = 8_000 + let maxRawHTMLNestingDepth = 64 + let maxAttributedStringLength = 400_000 + let maxAttributeRuns = 20_000 + let maxPresentationIntentDepth = 128 + let maxIntentNodes = 10_000 + let maxEmittedBlocks = 5_000 + let maxTableColumns = 32 + let maxTableCells = 2_000 + let maxMediaItems = 100 + let maxInlineHTMLStyleDepth = 32 + let maxDataImageBytes = 2_097_152 + let maxDataImagePixelCount = 12_000_000 + let maxFormulas = 200 + let maxFormulaSourceCharacters = 20_000 + let maxInlineFormulaLength = 256 + let maxBlockFormulaLength = 4_096 +} + +private let markdownSafetyLimits = MarkdownSafetyLimits() + +private final class MarkdownConversionBudget { + let limits: MarkdownSafetyLimits + + private(set) var isExceeded = false + + private var attributeRunCount = 0 + private var intentNodeCount = 0 + private var tableCellCount = 0 + private var mediaItemCount = 0 + + init(limits: MarkdownSafetyLimits) { + self.limits = limits + } + + @discardableResult + func fail() -> Bool { + self.isExceeded = true + return false + } + + @discardableResult + func registerAttributedStringLength(_ length: Int) -> Bool { + guard length <= self.limits.maxAttributedStringLength else { + return self.fail() + } + return true + } + + @discardableResult + func registerAttributeRun() -> Bool { + self.attributeRunCount += 1 + guard self.attributeRunCount <= self.limits.maxAttributeRuns else { + return self.fail() + } + return true + } + + @discardableResult + func registerPresentationIntentDepth(_ depth: Int) -> Bool { + guard depth <= self.limits.maxPresentationIntentDepth else { + return self.fail() + } + return true + } + + @discardableResult + func registerIntentNode() -> Bool { + self.intentNodeCount += 1 + guard self.intentNodeCount <= self.limits.maxIntentNodes else { + return self.fail() + } + return true + } + + @discardableResult + func registerBlockDepth(_ depth: Int) -> Bool { + guard depth <= self.limits.maxPresentationIntentDepth else { + return self.fail() + } + return true + } + + @discardableResult + func registerTableColumns(_ count: Int) -> Bool { + guard count <= self.limits.maxTableColumns else { + return self.fail() + } + return true + } + + @discardableResult + func registerTableCells(_ count: Int) -> Bool { + self.tableCellCount += count + guard self.tableCellCount <= self.limits.maxTableCells else { + return self.fail() + } + return true + } + + @discardableResult + func registerMediaItem() -> Bool { + self.mediaItemCount += 1 + guard self.mediaItemCount <= self.limits.maxMediaItems else { + return self.fail() + } + return true + } + + @discardableResult + func registerInlineHTMLStyleDepth(_ depth: Int) -> Bool { + guard depth <= self.limits.maxInlineHTMLStyleDepth else { + return self.fail() + } + return true + } + + @discardableResult + func validateFinalBlocks(_ blocks: [InstantPageBlock]) -> Bool { + var count = 0 + return self.validate(blocks: blocks, depth: 0, count: &count) + } + + private func validate(blocks: [InstantPageBlock], depth: Int, count: inout Int) -> Bool { + guard self.registerBlockDepth(depth) else { + return false + } + for block in blocks { + count += 1 + guard count <= self.limits.maxEmittedBlocks else { + return self.fail() + } + switch block { + case let .list(items, _): + for item in items { + if !self.validate(listItem: item, depth: depth + 1, count: &count) { + return false + } + } + case let .details(_, nestedBlocks, _): + if !self.validate(blocks: nestedBlocks, depth: depth + 1, count: &count) { + return false + } + default: + break + } + } + return true + } + + private func validate(listItem: InstantPageListItem, depth: Int, count: inout Int) -> Bool { + guard self.registerBlockDepth(depth) else { + return false + } + if case let .blocks(blocks, _, _) = listItem { + return self.validate(blocks: blocks, depth: depth + 1, count: &count) + } else { + return true + } + } +} + +private struct MarkdownPageResult { + let blocks: [InstantPageBlock] + let media: [EngineMedia.Id: EngineRawMedia] +} + +private enum MarkdownFormulaMode { + case inline + case block +} + +private struct MarkdownFormulaDescriptor { + let placeholder: String + let latex: String + let mode: MarkdownFormulaMode +} + +private struct MarkdownPreparedSource { + let text: String + let formulasByPlaceholder: [String: MarkdownFormulaDescriptor] +} + +private enum MarkdownInlineTextSegment { + case plain(String) + case formula(MarkdownFormulaDescriptor) +} + +private enum MarkdownInlineFragment { + case richText(RichText) + case formula(MarkdownFormulaDescriptor, RichText) + case image(MarkdownResolvedImage) +} + +private struct MarkdownInlineContent { + let fragments: [MarkdownInlineFragment] + + var richText: RichText { + var result: [RichText] = [] + result.reserveCapacity(self.fragments.count) + + for fragment in self.fragments { + switch fragment { + case let .richText(text): + result.append(text) + case let .formula(_, text): + result.append(text) + case let .image(image): + var text: RichText = .image(id: image.mediaId, dimensions: image.inlineDimensions) + if let linkUrl = image.linkUrl { + text = .url(text: text, url: linkUrl, webpageId: nil) + } + result.append(text) + } + } + + return markdownCompact(result) + } + + var standaloneBlockFormula: MarkdownFormulaDescriptor? { + var result: MarkdownFormulaDescriptor? + + for fragment in self.fragments { + switch fragment { + case let .richText(text): + if !markdownIsWhitespaceOnly(text) { + return nil + } + case let .formula(descriptor, _): + guard descriptor.mode == .block else { + return nil + } + if result != nil { + return nil + } + result = descriptor + case .image: + return nil + } + } + + return result + } + + var standaloneImage: MarkdownResolvedImage? { + var result: MarkdownResolvedImage? + + for fragment in self.fragments { + switch fragment { + case let .richText(text): + if !markdownIsWhitespaceOnly(text) { + return nil + } + case .formula: + return nil + case let .image(image): + if result != nil { + return nil + } + result = image + } + } + + return result + } +} + +private struct MarkdownResolvedImage { + let mediaId: EngineMedia.Id + let inlineDimensions: PixelDimensions + let caption: InstantPageCaption + let linkUrl: String? +} + +private enum MarkdownResolvedImageSource { + case remote(String) + case data(Data, PixelDimensions) + case unsupported +} + +private enum MarkdownTaskListState { + case unchecked + case checked +} + +private final class MarkdownConversionContext { + private let context: AccountContext + fileprivate let documentURL: URL? + fileprivate let formulasByPlaceholder: [String: MarkdownFormulaDescriptor] + fileprivate let budget: MarkdownConversionBudget + private var nextRemoteMediaId: Int64 = 0 + private var nextLocalMediaId: Int64 = 0 + + private(set) var media: [EngineMedia.Id: EngineRawMedia] = [:] + + init(context: AccountContext, documentURL: URL?, formulasByPlaceholder: [String: MarkdownFormulaDescriptor], budget: MarkdownConversionBudget) { + self.context = context + self.documentURL = documentURL + self.formulasByPlaceholder = formulasByPlaceholder + self.budget = budget + } + + func makePageResult(blocks: [InstantPageBlock]) -> MarkdownPageResult { + return MarkdownPageResult(blocks: blocks, media: self.media) + } + + func resolveImage(attributes: [NSAttributedString.Key: Any]) -> MarkdownResolvedImage? { + guard markdownImageParsingEnabled else { + return nil + } + guard let imageUrl = markdownImageURL(attributes: attributes) else { + return nil + } + + let inlineDimensions = markdownInlineImageDimensions(attributes: attributes) + let caption = markdownImageCaption(markdownAlternateDescription(attributes: attributes)) + let linkUrl = markdownLink(attributes: attributes, documentURL: self.documentURL) + + switch markdownResolveImageSource(imageUrl, limits: self.budget.limits) { + case let .remote(url): + guard self.budget.registerMediaItem() else { + return nil + } + let mediaId = self.nextMediaId(namespace: Namespaces.Media.CloudImage) + self.media[mediaId] = TelegramMediaImage( + imageId: mediaId, + representations: [ + TelegramMediaImageRepresentation( + dimensions: markdownDefaultBlockImageDimensions, + resource: InstantPageExternalMediaResource(url: url), + progressiveSizes: [], + immediateThumbnailData: nil + ) + ], + immediateThumbnailData: nil, + reference: nil, + partialReference: nil, + flags: [] + ) + return MarkdownResolvedImage( + mediaId: mediaId, + inlineDimensions: inlineDimensions, + caption: caption, + linkUrl: linkUrl + ) + case let .data(data, dimensions): + guard self.budget.registerMediaItem() else { + return nil + } + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.count), isSecretRelated: false) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) + + let mediaId = self.nextMediaId(namespace: Namespaces.Media.LocalImage) + self.media[mediaId] = TelegramMediaImage( + imageId: mediaId, + representations: [ + TelegramMediaImageRepresentation( + dimensions: dimensions, + resource: resource, + progressiveSizes: [], + immediateThumbnailData: nil + ) + ], + immediateThumbnailData: nil, + reference: nil, + partialReference: nil, + flags: [] + ) + return MarkdownResolvedImage( + mediaId: mediaId, + inlineDimensions: inlineDimensions, + caption: caption, + linkUrl: linkUrl + ) + case .unsupported: + return nil + } + } + + private func nextMediaId(namespace: Int32) -> EngineMedia.Id { + switch namespace { + case Namespaces.Media.LocalImage: + self.nextLocalMediaId += 1 + return EngineMedia.Id(namespace: namespace, id: self.nextLocalMediaId) + default: + self.nextRemoteMediaId += 1 + return EngineMedia.Id(namespace: namespace, id: self.nextRemoteMediaId) + } + } +} + +private func markdownPassesPreflight(data: Data, limits: MarkdownSafetyLimits) -> Bool { + guard data.count <= limits.maxFileSize else { + return false + } + guard let text = markdownDecodedSourceText(data) else { + return false + } + return markdownPassesPreflight(text: text, limits: limits) +} + +private func markdownDecodedSourceText(_ data: Data) -> String? { + if let text = String(data: data, encoding: .utf8) { + return text + } + if data.starts(with: [0xff, 0xfe]) { + return String(data: data, encoding: .utf16LittleEndian) + } + if data.starts(with: [0xfe, 0xff]) { + return String(data: data, encoding: .utf16BigEndian) + } + return nil +} + +private func markdownPassesPreflight(text: String, limits: MarkdownSafetyLimits) -> Bool { + var activeFence: Character? + var htmlTagCount = 0 + var htmlDepth = 0 + + for rawLine in text.split(separator: "\n", omittingEmptySubsequences: false) { + var line = String(rawLine) + if line.hasSuffix("\r") { + line.removeLast() + } + + if (line as NSString).length > limits.maxLineLength { + return false + } + + if let fenceMarker = markdownFenceMarker(in: line) { + if activeFence == fenceMarker { + activeFence = nil + } else { + activeFence = fenceMarker + } + continue + } + + if activeFence != nil { + continue + } + + if markdownBlockquoteDepth(in: line) > limits.maxBlockquoteDepth { + return false + } + if markdownIndentWidth(in: line) > limits.maxListIndent { + return false + } + if !markdownScanRawHTMLTags(in: line, limits: limits, tagCount: &htmlTagCount, depth: &htmlDepth) { + return false + } + } + + return true +} + +private func markdownFenceMarker(in line: String) -> Character? { + let trimmed = line.drop(while: { $0 == " " || $0 == "\t" }) + guard let marker = trimmed.first, marker == "`" || marker == "~" else { + return nil + } + var count = 0 + var index = trimmed.startIndex + while index < trimmed.endIndex, trimmed[index] == marker { + count += 1 + index = trimmed.index(after: index) + } + return count >= 3 ? marker : nil +} + +private func markdownBlockquoteDepth(in line: String) -> Int { + var depth = 0 + var index = line.startIndex + + while index < line.endIndex { + switch line[index] { + case " ", "\t": + index = line.index(after: index) + case ">": + depth += 1 + index = line.index(after: index) + if index < line.endIndex, (line[index] == " " || line[index] == "\t") { + index = line.index(after: index) + } + default: + return depth + } + } + + return depth +} + +private func markdownIndentWidth(in line: String) -> Int { + var width = 0 + for character in line { + switch character { + case " ": + width += 1 + case "\t": + width += 4 + default: + return width + } + } + return width +} + +private func markdownScanRawHTMLTags(in line: String, limits: MarkdownSafetyLimits, tagCount: inout Int, depth: inout Int) -> Bool { + let nsLine = line as NSString + let matches = markdownRawHTMLTagRegex.matches(in: line, range: NSRange(location: 0, length: nsLine.length)) + + for match in matches { + tagCount += 1 + guard tagCount <= limits.maxRawHTMLTagCount else { + return false + } + + let tagText = nsLine.substring(with: match.range) + let tagName = nsLine.substring(with: match.range(at: 1)).lowercased() + let lowercasedTag = tagText.lowercased() + + if lowercasedTag.hasPrefix("") && !markdownVoidHTMLTags.contains(tagName) { + depth += 1 + guard depth <= limits.maxRawHTMLNestingDepth else { + return false + } + } + } + + return true +} + +private func markdownPreparedSource(text: String, limits: MarkdownSafetyLimits) -> MarkdownPreparedSource { + let lines = markdownLinesPreservingEndings(text) + + var activeFence: Character? + var formulasByPlaceholder: [String: MarkdownFormulaDescriptor] = [:] + var acceptedFormulaCount = 0 + var totalFormulaCharacters = 0 + var nextPlaceholderIndex = 0 + var result = "" + + var lineIndex = 0 + while lineIndex < lines.count { + let line = lines[lineIndex] + let (content, _) = markdownLineContentAndEnding(line) + + if let fenceMarker = markdownFenceMarker(in: content) { + if activeFence == fenceMarker { + activeFence = nil + } else { + activeFence = fenceMarker + } + result.append(contentsOf: line) + lineIndex += 1 + continue + } + + if activeFence != nil { + result.append(contentsOf: line) + lineIndex += 1 + continue + } + + if let replacement = markdownBlockFormulaReplacement( + in: lines, + startLineIndex: lineIndex, + limits: limits, + acceptedFormulaCount: &acceptedFormulaCount, + totalFormulaCharacters: &totalFormulaCharacters, + nextPlaceholderIndex: &nextPlaceholderIndex + ) { + result.append(contentsOf: replacement.replacement) + formulasByPlaceholder[replacement.descriptor.placeholder] = replacement.descriptor + lineIndex = replacement.nextLineIndex + continue + } + + let processedLine = markdownReplacingInlineFormulas( + in: line, + limits: limits, + acceptedFormulaCount: &acceptedFormulaCount, + totalFormulaCharacters: &totalFormulaCharacters, + nextPlaceholderIndex: &nextPlaceholderIndex, + formulasByPlaceholder: &formulasByPlaceholder + ) + result.append(contentsOf: processedLine) + lineIndex += 1 + } + + return MarkdownPreparedSource(text: result, formulasByPlaceholder: formulasByPlaceholder) +} + +private func markdownLinesPreservingEndings(_ text: String) -> [String] { + guard !text.isEmpty else { + return [] + } + + var result: [String] = [] + var lineStart = text.startIndex + var index = text.startIndex + + while index < text.endIndex { + if text[index] == "\n" { + let nextIndex = text.index(after: index) + result.append(String(text[lineStart ..< nextIndex])) + lineStart = nextIndex + } + index = text.index(after: index) + } + + if lineStart < text.endIndex { + result.append(String(text[lineStart...])) + } + + return result +} + +private func markdownLineContentAndEnding(_ line: String) -> (content: String, ending: String) { + if line.hasSuffix("\r\n") { + return (String(line.dropLast(2)), "\r\n") + } else if line.hasSuffix("\n") { + return (String(line.dropLast()), "\n") + } else { + return (line, "") + } +} + +private func markdownFormulaPlaceholder(_ index: Int) -> String { + return "TGMDMATH\(index)TGMD" +} + +private func markdownAcceptedFormulaDescriptor( + latex: String, + mode: MarkdownFormulaMode, + limits: MarkdownSafetyLimits, + acceptedFormulaCount: inout Int, + totalFormulaCharacters: inout Int, + nextPlaceholderIndex: inout Int +) -> MarkdownFormulaDescriptor? { + let normalizedLatex: String + switch mode { + case .inline: + normalizedLatex = latex + case .block: + normalizedLatex = latex.trimmingCharacters(in: .whitespacesAndNewlines) + } + + guard !normalizedLatex.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return nil + } + + let latexLength = (normalizedLatex as NSString).length + let maxLength = mode == .inline ? limits.maxInlineFormulaLength : limits.maxBlockFormulaLength + guard latexLength <= maxLength else { + return nil + } + guard acceptedFormulaCount < limits.maxFormulas else { + return nil + } + guard totalFormulaCharacters + latexLength <= limits.maxFormulaSourceCharacters else { + return nil + } + + let placeholder = markdownFormulaPlaceholder(nextPlaceholderIndex) + nextPlaceholderIndex += 1 + acceptedFormulaCount += 1 + totalFormulaCharacters += latexLength + return MarkdownFormulaDescriptor(placeholder: placeholder, latex: normalizedLatex, mode: mode) +} + +private func markdownBlockFormulaReplacement( + in lines: [String], + startLineIndex: Int, + limits: MarkdownSafetyLimits, + acceptedFormulaCount: inout Int, + totalFormulaCharacters: inout Int, + nextPlaceholderIndex: inout Int +) -> (replacement: String, nextLineIndex: Int, descriptor: MarkdownFormulaDescriptor)? { + guard startLineIndex >= 0, startLineIndex < lines.count else { + return nil + } + + let (content, _) = markdownLineContentAndEnding(lines[startLineIndex]) + let indentation = String(content.prefix { $0 == " " || $0 == "\t" }) + let trimmedStart = content.drop(while: { $0 == " " || $0 == "\t" }) + + let opener: String + let closer: String + if trimmedStart.hasPrefix("$$") { + opener = "$$" + closer = "$$" + } else if trimmedStart.hasPrefix("\\[") { + opener = "\\[" + closer = "\\]" + } else { + return nil + } + + let openerContent = String(trimmedStart.dropFirst(opener.count)) + // Block opener must be exactly `$$` (not `$$$`); a `$$$…` line is not a block and + // falls through to inline handling. + if opener == "$$", openerContent.first == "$" { + return nil + } + var latex = "" + + if let closeRange = markdownFirstUnescapedRange(of: closer, in: openerContent, from: openerContent.startIndex) { + let trailing = String(openerContent[closeRange.upperBound...]) + guard trailing.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { + return nil + } + latex = String(openerContent[.. String { + let (content, ending) = markdownLineContentAndEnding(line) + guard !content.isEmpty else { + return line + } + + var result = "" + var index = content.startIndex + var activeCodeDelimiterLength: Int? + + while index < content.endIndex { + if content[index] == "`" { + let delimiterEnd = markdownIndex(afterRepeating: "`", in: content, from: index) + let delimiterLength = content.distance(from: index, to: delimiterEnd) + result.append(contentsOf: content[index ..< delimiterEnd]) + if activeCodeDelimiterLength == delimiterLength { + activeCodeDelimiterLength = nil + } else { + activeCodeDelimiterLength = delimiterLength + } + index = delimiterEnd + continue + } + + if activeCodeDelimiterLength != nil { + result.append(content[index]) + index = content.index(after: index) + continue + } + + if content[index] == "\\", !markdownIsEscaped(content, at: index), let nextIndex = markdownIndex(content, offsetBy: 1, from: index), nextIndex < content.endIndex, content[nextIndex] == "(" { + let bodyStart = content.index(after: nextIndex) + if let closeRange = markdownFirstUnescapedRange(of: "\\)", in: content, from: bodyStart) { + let latex = String(content[bodyStart ..< closeRange.lowerBound]) + if let descriptor = markdownAcceptedFormulaDescriptor( + latex: latex, + mode: .inline, + limits: limits, + acceptedFormulaCount: &acceptedFormulaCount, + totalFormulaCharacters: &totalFormulaCharacters, + nextPlaceholderIndex: &nextPlaceholderIndex + ) { + result.append(contentsOf: descriptor.placeholder) + formulasByPlaceholder[descriptor.placeholder] = descriptor + index = closeRange.upperBound + continue + } + } + } + + if content[index] == "$", !markdownIsEscaped(content, at: index) { + // Outer boundary before the opener: line start, or a non-alphanumeric char. + // (Rejects `cost$5$total`, the `$` after `5` in `$5-$10`, etc.) + if index > content.startIndex { + let beforeOpener = content[content.index(before: index)] + if beforeOpener.isLetter || beforeOpener.isNumber { + result.append(content[index]) + index = content.index(after: index) + continue + } + } + // Opener: 1 or 2 leading `$` (a 3rd `$` becomes inner content). + let openerRunEnd = markdownIndex(afterRepeating: "$", in: content, from: index) + let openerCount = min(2, content.distance(from: index, to: openerRunEnd)) + let bodyStart = content.index(index, offsetBy: openerCount) + // Inner boundary after the opener: must exist and be non-whitespace. + guard bodyStart < content.endIndex, !content[bodyStart].isWhitespace else { + result.append(content[index]) + index = content.index(after: index) + continue + } + let closerPattern = String(repeating: "$", count: openerCount) + var searchIndex = bodyStart + var matchedRange: Range? + while let closeRange = markdownFirstUnescapedRange(of: closerPattern, in: content, from: searchIndex) { + // Inner boundary before the closer: non-whitespace. + let beforeCloser = content[content.index(before: closeRange.lowerBound)] + if beforeCloser.isWhitespace { + searchIndex = closeRange.upperBound + continue + } + // Outer boundary after the closer: line end, or a non-alphanumeric char. + if closeRange.upperBound < content.endIndex { + let afterCloser = content[closeRange.upperBound] + if afterCloser.isLetter || afterCloser.isNumber { + searchIndex = closeRange.upperBound + continue + } + } + matchedRange = closeRange + break + } + if let matchedRange { + let latex = String(content[bodyStart ..< matchedRange.lowerBound]) + if let descriptor = markdownAcceptedFormulaDescriptor( + latex: latex, + mode: .inline, + limits: limits, + acceptedFormulaCount: &acceptedFormulaCount, + totalFormulaCharacters: &totalFormulaCharacters, + nextPlaceholderIndex: &nextPlaceholderIndex + ) { + result.append(contentsOf: descriptor.placeholder) + formulasByPlaceholder[descriptor.placeholder] = descriptor + index = matchedRange.upperBound + continue + } + } + } + + result.append(content[index]) + index = content.index(after: index) + } + + result.append(contentsOf: ending) + return result +} + +private func markdownFirstUnescapedRange(of pattern: String, in text: String, from startIndex: String.Index) -> Range? { + guard !pattern.isEmpty, startIndex <= text.endIndex else { + return nil + } + + var searchIndex = startIndex + while searchIndex < text.endIndex { + guard let range = text.range(of: pattern, range: searchIndex ..< text.endIndex) else { + return nil + } + if !markdownIsEscaped(text, at: range.lowerBound) { + return range + } + searchIndex = range.upperBound + } + return nil +} + +private func markdownIsEscaped(_ text: String, at index: String.Index) -> Bool { + guard index > text.startIndex else { + return false + } + + var slashCount = 0 + var currentIndex = text.index(before: index) + while true { + if text[currentIndex] == "\\" { + slashCount += 1 + } else { + break + } + guard currentIndex > text.startIndex else { + break + } + currentIndex = text.index(before: currentIndex) + } + + return slashCount % 2 == 1 +} + +private func markdownIndex(_ text: String, offsetBy distance: Int, from index: String.Index) -> String.Index? { + guard distance >= 0 else { + return nil + } + return text.index(index, offsetBy: distance, limitedBy: text.endIndex) +} + +private func markdownIndex(afterRepeating character: Character, in text: String, from startIndex: String.Index) -> String.Index { + var index = startIndex + while index < text.endIndex, text[index] == character { + index = text.index(after: index) + } + return index +} + +func markdownWebpage(context: AccountContext, file: FileMediaReference) -> (webPage: TelegramMediaWebpage, fileURL: URL)? { + guard #available(iOS 15.0, *) else { + return nil + } + guard let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.media.resource.id)) else { + return nil + } + let fileURL = URL(fileURLWithPath: path) + guard let data = try? Data(contentsOf: fileURL) else { + return nil + } + guard let webPage = markdownWebpage(context: context, file: (file, fileURL), data: data) else { + return nil + } + return (webPage, fileURL) +} + +public func inputRichTextAttributeFromText(context: AccountContext, text: String) -> RichTextMessageAttribute? { + guard #available(iOS 15.0, *) else { + return nil + } + guard let data = text.data(using: .utf8) else { + return nil + } + guard let webpage = markdownWebpage(context: context, file: nil, data: data), case let .Loaded(content) = webpage.content, let instantPage = content.instantPage else { + return nil + } + return RichTextMessageAttribute(instantPage: instantPage._parse(), fullInstantPage: nil) +} + +// MARK: - Markdown classification (entity-expressible vs. rich layout) + +private let markdownBlockHeuristicRegex = try? NSRegularExpression( + pattern: "(^|\\n)[ \\t]*(#{1,6}[ \\t]|[-*+][ \\t]|\\d{1,9}[.)][ \\t]|-{1,}[ \\t]*(\\n|$))", + options: [] +) + +// Cheap necessary-condition pre-filter. A safe over-approximation: if it +// returns false, the text cannot contain a rich-only construct, so the +// expensive markdown parse is skipped and the entity path is used. +private func markdownMightNeedRichLayout(_ text: String) -> Bool { + // Tables ('|') and images ('![') can appear anywhere on a line. This over-approximates + // (prose containing '|' also triggers a parse); the parse + block inspection resolves it. + if text.contains("|") || text.contains("![") { + return true + } + // Math delimiters. Over-approximates (any `$` triggers a parse); the strict + // detection + gate decide whether a formula block is actually produced. + if text.contains("$") || text.contains("\\(") || text.contains("\\[") { + return true + } + // Setext H1 heading: a line of '=' underlining the previous line. + // (Setext H2 dash-underlines are caught by the dash-line branch in the regex.) + if text.contains("\n=") { + return true + } + // Line-anchored block markers: headings, list items, setext-H2 dash underlines. + if let regex = markdownBlockHeuristicRegex { + let range = NSRange(text.startIndex..., in: text) + if regex.firstMatch(in: text, options: [], range: range) != nil { + return true + } + } + return false +} + +// True when this inline RichText maps onto Telegram message entities. +// Returns false for inline content that forces the rich path (inline image, +// sub/superscript, highlight, formula). Formulas trigger the rich path; casual +// '$' usage is excluded by the strict boundary rule in the detection step, not here. +private func richTextIsEntityExpressible(_ text: RichText) -> Bool { + switch text { + case .empty, .plain: + return true + case .bold(let inner), .italic(let inner), .underline(let inner), .strikethrough(let inner), .fixed(let inner): + return richTextIsEntityExpressible(inner) + case .superscript, .marked, .`subscript`: + return false + case .url(let inner, _, _): + return richTextIsEntityExpressible(inner) + case .email(let inner, _): + return richTextIsEntityExpressible(inner) + case .concat(let items): + return items.allSatisfy(richTextIsEntityExpressible) + case .phone(let inner, _): + return richTextIsEntityExpressible(inner) + case .image: + return false + case .anchor(let inner, _): + return richTextIsEntityExpressible(inner) + case .formula: + return false + case .textCustomEmoji: + return true + case .textAutoEmail(let inner), .textAutoPhone(let inner), .textAutoUrl(let inner), .textBankCard(let inner), .textBotCommand(let inner), .textCashtag(let inner), .textHashtag(let inner), .textMention(let inner), .textSpoiler(let inner): + return richTextIsEntityExpressible(inner) + case .textMentionName(let inner, _): + return richTextIsEntityExpressible(inner) + case .textDate: + return false + } +} + +private func isEmptyRichText(_ text: RichText) -> Bool { + switch text { + case .empty: + return true + case .plain(let value): + return value.isEmpty + default: + return false + } +} + +// Block types that do NOT trigger a rich-layout message. Besides the genuinely +// entity-expressible blocks (paragraph/preformatted/blockQuote/anchor), dividers +// are intentionally excluded as triggers too ('---' is too common in casual text). +// Formulas DO trigger the rich path; casual '$' usage is excluded by the strict +// boundary rule in the detection step. Effective rich triggers are therefore +// headings, lists, tables, and formulas. +private func blockIsEntityExpressible(_ block: InstantPageBlock) -> Bool { + switch block { + case .paragraph(let text): + return richTextIsEntityExpressible(text) + case .preformatted(let text, _): + return richTextIsEntityExpressible(text) + case .blockQuote(let blocks, let caption): + guard isEmptyRichText(caption) else { return false } + return blocks.allSatisfy { child in + if case let .paragraph(text) = child { + return richTextIsEntityExpressible(text) + } + return false + } + case .anchor, .unsupported: + return true + case .divider: + return true + default: + return false + } +} + +private func instantPageNeedsRichLayout(_ blocks: [InstantPageBlock]) -> Bool { + return blocks.contains { !blockIsEntityExpressible($0) } +} + +// Rewrites each `ChatTextInputAttributes.customEmoji` run in the attributed +// input as a `[](tg://emoji?id=)` markdown link, leaving all other +// text (and its markdown syntax) verbatim. With no custom emoji present this +// returns `attributedText.string` unchanged, so non-emoji messages are +// unaffected. The marker is intercepted post-parse in markdownInlineContent. +private func markdownSourceInjectingCustomEmojiMarkers(_ attributedText: NSAttributedString) -> String { + let nsString = attributedText.string as NSString + var result = "" + attributedText.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, _ in + let substring = nsString.substring(with: range) + if let attribute = value as? ChatTextInputTextCustomEmojiAttribute { + // The link text must be non-empty: CommonMark drops `[](url)` (no + // run carries the link attribute), which would silently lose the + // emoji. Fall back to a space, matching the reattach helper. + let alt = substring.isEmpty ? " " : substring + result += "[\(escapeCustomEmojiMarkdownAlt(alt))](\(customEmojiMarkdownURL(fileId: attribute.fileId)))" + } else { + result += substring + } + } + return result +} + +// Returns a RichTextMessageAttribute IFF the markdown in `text` produces an +// InstantPage block with no entity equivalent. Returns nil (-> send via the +// regular entity path) for plain text, pre-iOS-15, oversize markdown, or +// markdown that maps cleanly onto entities. +public func richMarkdownAttributeIfNeeded(context: AccountContext, attributedText: NSAttributedString) -> RichTextMessageAttribute? { + // Custom emoji are rewritten to `[](tg://emoji?id=...)` link markers + // before classification + parse; the markers are intercepted back into + // .textCustomEmoji in markdownInlineContent. A link is entity-expressible, + // so an emoji-only message still classifies as not-rich (and falls through + // to the entity path, where its untouched attribute makes a .CustomEmoji + // entity) — custom emoji alone never forces a rich message. + let text = markdownSourceInjectingCustomEmojiMarkers(attributedText) + guard markdownMightNeedRichLayout(text) else { + return nil + } + guard let attribute = inputRichTextAttributeFromText(context: context, text: text) else { + return nil + } + guard instantPageNeedsRichLayout(attribute.instantPage.blocks) else { + return nil + } + return attribute +} + +@available(iOS 15.0, *) +private func markdownWebpage(context: AccountContext, file: (file: FileMediaReference, url: URL)?, data: Data) -> TelegramMediaWebpage? { + let limits = markdownSafetyLimits + guard markdownPassesPreflight(data: data, limits: limits) else { + return nil + } + guard let sourceText = markdownDecodedSourceText(data) else { + return nil + } + let preparedSource = markdownPreparedSource(text: sourceText, limits: limits) + + let attributedString: NSAttributedString + do { + let baseURL: URL? + if let file { + baseURL = file.url.deletingLastPathComponent() + } else { + baseURL = nil + } + attributedString = try NSAttributedString( + markdown: Data(preparedSource.text.utf8), + options: .init(), + baseURL: baseURL + ) + } catch { + return nil + } + + let budget = MarkdownConversionBudget(limits: limits) + let conversionContext = MarkdownConversionContext(context: context, documentURL: file?.url, formulasByPlaceholder: preparedSource.formulasByPlaceholder, budget: budget) + guard let pageResult = markdownPageResult(from: attributedString, context: conversionContext) else { + return nil + } + // Heading anchors exist for intra-document navigation ([link](#slug)); they are + // noise in a chat message, where they prepend an invisible block per heading. + // Only generate them for the document path (file != nil). + let blocks: [InstantPageBlock] + if file != nil { + blocks = markdownBlocksWithGeneratedAnchors(pageResult.blocks) + } else { + blocks = pageResult.blocks + } + guard !blocks.isEmpty, budget.validateFinalBlocks(blocks) else { + return nil + } + + var title: String? + if let file { + title = markdownTitle(from: blocks, file: file.file, fileURL: file.url) + } + let text = markdownFirstParagraphText(from: blocks) + let instantPage = InstantPage( + blocks: blocks, + media: pageResult.media, + isComplete: true, + rtl: false, + url: file?.url.absoluteString ?? "", + views: nil + ) + + return TelegramMediaWebpage( + webpageId: EngineMedia.Id(namespace: 0, id: 0), + content: .Loaded( + TelegramMediaWebpageLoadedContent( + url: file?.url.absoluteString ?? "", + displayUrl: file?.url.absoluteString ?? "", + hash: 0, + type: "article", + websiteName: nil, + title: title, + text: text, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: instantPage + ) + ) + ) +} + +@available(iOS 15.0, *) +private func markdownPageResult(from attributedString: NSAttributedString, context: MarkdownConversionContext) -> MarkdownPageResult? { + guard context.budget.registerAttributedStringLength(attributedString.length) else { + return nil + } + + var nodesByIdentity: [Int: MarkdownIntentNode] = [:] + var rootNodes: [MarkdownIntentNode] = [] + var rootIdentities: Set = [] + var didAbort = false + + attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, stop in + guard range.length > 0 else { + return + } + guard context.budget.registerAttributeRun() else { + didAbort = true + stop.pointee = true + return + } + guard let presentationIntent = attributes[markdownPresentationIntentAttribute] as? PresentationIntent else { + return + } + let components = presentationIntent.components + guard !components.isEmpty else { + return + } + guard context.budget.registerPresentationIntentDepth(components.count) else { + didAbort = true + stop.pointee = true + return + } + + var orderedNodes: [MarkdownIntentNode] = [] + for component in components.reversed() { + let node: MarkdownIntentNode + if let current = nodesByIdentity[component.identity] { + node = current + } else { + guard context.budget.registerIntentNode() else { + didAbort = true + stop.pointee = true + return + } + let created = MarkdownIntentNode(component: component) + nodesByIdentity[component.identity] = created + node = created + } + orderedNodes.append(node) + } + + if let rootNode = orderedNodes.first, rootIdentities.insert(rootNode.identity).inserted { + rootNodes.append(rootNode) + } + if orderedNodes.count >= 2 { + for index in 0 ..< (orderedNodes.count - 1) { + orderedNodes[index].append(child: orderedNodes[index + 1]) + } + } + if let leafNode = orderedNodes.last { + leafNode.append(text: attributedString.attributedSubstring(from: range)) + } + } + + guard !didAbort, !context.budget.isExceeded else { + return nil + } + guard let blocks = markdownBlocks(from: rootNodes, context: context, depth: 0), !context.budget.isExceeded else { + return nil + } + return context.makePageResult(blocks: blocks) +} + +private func markdownBlocks(from nodes: [MarkdownIntentNode], context: MarkdownConversionContext, depth: Int) -> [InstantPageBlock]? { + guard context.budget.registerBlockDepth(depth), !context.budget.isExceeded else { + return nil + } + + var result: [InstantPageBlock] = [] + for node in nodes { + guard let blocks = markdownBlocks(from: node, context: context, depth: depth + 1) else { + return nil + } + result.append(contentsOf: blocks) + if context.budget.isExceeded { + return nil + } + } + return result +} + +private func markdownBlocks(from node: MarkdownIntentNode, context: MarkdownConversionContext, depth: Int) -> [InstantPageBlock]? { + guard context.budget.registerBlockDepth(depth), !context.budget.isExceeded else { + return nil + } + + switch node.kind { + case let .table(alignments): + guard let rows = markdownTableRows(from: node.children, alignments: alignments, context: context, depth: depth + 1) else { + return nil + } + guard !rows.isEmpty else { + return [] + } + return [.table(title: .empty, rows: rows, bordered: true, striped: false)] + case let .header(level): + guard let text = markdownRichText(from: node.attributedText, context: context) else { + return nil + } + guard markdownHasDisplayableContent(text) else { + return [] + } + switch level { + case Int.min ... 1: + if context.documentURL == nil { + // Chat message: a single '#' is a normal heading, not a document title. + return [.heading(text: text, level: 1)] + } else { + return [.title(text)] + } + default: + return [.heading(text: text, level: Int32(max(2, min(level, 6))))] + } + case .paragraph: + guard let inlineContent = markdownInlineContent(from: node.attributedText, context: context) else { + return nil + } + if let formula = inlineContent.standaloneBlockFormula { + return [.formula(latex: formula.latex)] + } + if let image = inlineContent.standaloneImage { + return [ + .image( + id: image.mediaId, + caption: image.caption, + url: image.linkUrl, + webpageId: nil + ) + ] + } + let text = inlineContent.richText + guard markdownHasDisplayableContent(text) else { + return [] + } + return [.paragraph(text)] + case let .codeBlock(languageHint): + guard let text = markdownRichText(from: markdownTrimTrailingCodeBlockNewline(node.attributedText), context: context) else { + return nil + } + guard markdownHasDisplayableContent(text) else { + return [] + } + return [.preformatted(text: text, language: markdownNormalizedCodeBlockLanguage(languageHint))] + case .thematicBreak: + return [.divider] + case .blockQuote: + var childBlocks: [InstantPageBlock] = [] + for child in node.children { + guard let parsed = markdownBlocks(from: child, context: context, depth: depth + 1) else { + return nil + } + childBlocks.append(contentsOf: parsed) + } + guard !childBlocks.isEmpty else { + return [] + } + return [.blockQuote(blocks: childBlocks, caption: .empty)] + case .orderedList: + guard let items = markdownListItems(from: node.children, ordered: true, context: context, depth: depth + 1) else { + return nil + } + guard !items.isEmpty else { + return [] + } + return [.list(items: items, ordered: true)] + case .unorderedList: + guard let items = markdownListItems(from: node.children, ordered: false, context: context, depth: depth + 1) else { + return nil + } + guard !items.isEmpty else { + return [] + } + return [.list(items: items, ordered: false)] + case .listItem(_), .tableHeaderRow, .tableRow, .tableCell(_), .unknown: + return markdownBlocks(from: node.children, context: context, depth: depth + 1) + } +} + +private func markdownListItems(from nodes: [MarkdownIntentNode], ordered: Bool, context: MarkdownConversionContext, depth: Int) -> [InstantPageListItem]? { + guard context.budget.registerBlockDepth(depth), !context.budget.isExceeded else { + return nil + } + + var result: [InstantPageListItem] = [] + for node in nodes { + guard case let .listItem(ordinal) = node.kind else { + continue + } + guard var blocks = markdownBlocks(from: node.children, context: context, depth: depth + 1) else { + return nil + } + let taskListState = markdownApplyTaskListMarker(to: &blocks) + let checked: Bool? + switch taskListState { + case .unchecked: + checked = false + case .checked: + checked = true + case nil: + checked = nil + } + let number: String? = ordered ? "\(ordinal)" : nil + if blocks.isEmpty { + if checked != nil || number != nil { + result.append(.text(.plain(" "), number, checked)) + } + continue + } + if blocks.count == 1, case let .paragraph(text) = blocks[0] { + if markdownIsWhitespaceOnly(text) && (checked != nil || number != nil) { + result.append(.text(.plain(" "), number, checked)) + } else { + result.append(.text(text, number, checked)) + } + } else { + result.append(.blocks(blocks, number, checked)) + } + } + return result +} + +private func markdownApplyTaskListMarker(to blocks: inout [InstantPageBlock]) -> MarkdownTaskListState? { + guard !blocks.isEmpty, case let .paragraph(text) = blocks[0] else { + return nil + } + guard let (state, strippedText) = markdownStrippingTaskListMarker(from: text) else { + return nil + } + if blocks.count > 1 && markdownIsWhitespaceOnly(strippedText) { + blocks.removeFirst() + } else { + blocks[0] = .paragraph(strippedText) + } + return state +} + +private func markdownStrippingTaskListMarker(from text: RichText) -> (MarkdownTaskListState, RichText)? { + guard let (state, markerLength) = markdownTaskListMarker(in: text.plainText) else { + return nil + } + return (state, markdownDroppingPrefixLength(markerLength, from: text)) +} + +private func markdownTaskListMarker(in plainText: String) -> (MarkdownTaskListState, Int)? { + switch plainText { + case _ where plainText.hasPrefix("[ ] "): + return (.unchecked, 4) + case "[ ]": + return (.unchecked, 3) + case _ where plainText.hasPrefix("[x] "), _ where plainText.hasPrefix("[X] "): + return (.checked, 4) + case "[x]", "[X]": + return (.checked, 3) + default: + return nil + } +} + +private func markdownTableRows(from nodes: [MarkdownIntentNode], alignments: [TableHorizontalAlignment], context: MarkdownConversionContext, depth: Int) -> [InstantPageTableRow]? { + guard context.budget.registerBlockDepth(depth), !context.budget.isExceeded else { + return nil + } + + var result: [InstantPageTableRow] = [] + for node in nodes { + switch node.kind { + case .tableHeaderRow: + guard let cells = markdownTableCells(from: node.children, alignments: alignments, header: true, context: context, depth: depth + 1) else { + return nil + } + if !cells.isEmpty { + result.append(InstantPageTableRow(cells: cells)) + } + case .tableRow: + guard let cells = markdownTableCells(from: node.children, alignments: alignments, header: false, context: context, depth: depth + 1) else { + return nil + } + if !cells.isEmpty { + result.append(InstantPageTableRow(cells: cells)) + } + default: + continue + } + } + return context.budget.isExceeded ? nil : result +} + +private func markdownTableCells(from nodes: [MarkdownIntentNode], alignments: [TableHorizontalAlignment], header: Bool, context: MarkdownConversionContext, depth: Int) -> [InstantPageTableCell]? { + guard context.budget.registerBlockDepth(depth), !context.budget.isExceeded else { + return nil + } + + let maxColumnIndex = nodes.reduce(-1) { partialResult, node in + if case let .tableCell(column) = node.kind { + return max(partialResult, column) + } else { + return partialResult + } + } + let columnCount = max(alignments.count, maxColumnIndex + 1) + guard columnCount > 0 else { + return [] + } + guard context.budget.registerTableColumns(columnCount) else { + return nil + } + + var result: [InstantPageTableCell] = [] + var nextColumn = 0 + + for node in nodes { + guard case let .tableCell(column) = node.kind else { + continue + } + + while nextColumn < column { + result.append(markdownEmptyTableCell(header: header, alignment: markdownTableAlignment(at: nextColumn, from: alignments))) + nextColumn += 1 + } + + guard let text = markdownRichText(from: node.attributedText, context: context) else { + return nil + } + result.append( + InstantPageTableCell( + text: text, + header: header, + alignment: markdownTableAlignment(at: column, from: alignments), + verticalAlignment: .top, + colspan: 1, + rowspan: 1 + ) + ) + nextColumn = max(nextColumn, column + 1) + } + + while nextColumn < columnCount { + result.append(markdownEmptyTableCell(header: header, alignment: markdownTableAlignment(at: nextColumn, from: alignments))) + nextColumn += 1 + } + + guard context.budget.registerTableCells(result.count) else { + return nil + } + return result +} + +private func markdownEmptyTableCell(header: Bool, alignment: TableHorizontalAlignment) -> InstantPageTableCell { + return InstantPageTableCell( + text: .empty, + header: header, + alignment: alignment, + verticalAlignment: .top, + colspan: 1, + rowspan: 1 + ) +} + +private func markdownTableAlignment(at index: Int, from alignments: [TableHorizontalAlignment]) -> TableHorizontalAlignment { + guard index >= 0, index < alignments.count else { + return .left + } + return alignments[index] +} + +private func markdownRichText(from attributedString: NSAttributedString, context: MarkdownConversionContext) -> RichText? { + return markdownInlineContent(from: attributedString, context: context)?.richText +} + +private func markdownInlineContent(from attributedString: NSAttributedString, context: MarkdownConversionContext) -> MarkdownInlineContent? { + guard attributedString.length > 0, #available(iOS 15.0, *) else { + return MarkdownInlineContent(fragments: []) + } + + var fragments: [MarkdownInlineFragment] = [] + var htmlStyles: [MarkdownHTMLInlineStyle] = [] + var consumeNextSoftBreak = false + var didAbort = false + + attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, stop in + guard range.length > 0 else { + return + } + if context.budget.isExceeded { + didAbort = true + stop.pointee = true + return + } + + let text = attributedString.attributedSubstring(from: range).string + guard !text.isEmpty else { + return + } + + let inlineIntent: InlinePresentationIntent? + if let inlineIntentValue = attributes[markdownInlinePresentationIntentAttribute] as? InlinePresentationIntent { + inlineIntent = inlineIntentValue + } else if let inlineIntentValue = attributes[markdownInlinePresentationIntentAttribute] as? NSNumber { + inlineIntent = InlinePresentationIntent(rawValue: inlineIntentValue.uintValue) + } else { + inlineIntent = nil + } + + if let inlineIntent, inlineIntent.contains(markdownInlineHTMLInlineIntent), let directive = markdownHTMLDirective(for: text) { + switch directive { + case let .open(style): + htmlStyles.append(style) + guard context.budget.registerInlineHTMLStyleDepth(htmlStyles.count) else { + didAbort = true + stop.pointee = true + return + } + case let .close(style): + if let index = htmlStyles.lastIndex(of: style) { + htmlStyles.remove(at: index) + } + case .lineBreak: + fragments.append(.richText(markdownApplyHTMLStyles(htmlStyles, to: .plain("\n")))) + consumeNextSoftBreak = true + } + return + } + + if consumeNextSoftBreak { + if let inlineIntent, inlineIntent.contains(markdownSoftBreakInlineIntent), text == " " { + consumeNextSoftBreak = false + return + } + consumeNextSoftBreak = false + } + + if let image = context.resolveImage(attributes: attributes) { + fragments.append(.image(image)) + return + } + + if let linkUrl = markdownLink(attributes: attributes, documentURL: context.documentURL), + let fileId = parseCustomEmojiFileId(fromMarkdownURL: linkUrl) { + // `text` is the parsed (already-unescaped) link display text = the alt. + fragments.append(.richText(.textCustomEmoji(fileId: fileId, alt: text))) + return + } + + let segments = markdownInlineTextSegments(from: text, formulasByPlaceholder: context.formulasByPlaceholder) + for segment in segments { + let baseText: RichText + let descriptor: MarkdownFormulaDescriptor? + switch segment { + case let .plain(plainText): + baseText = .plain(plainText) + descriptor = nil + case let .formula(formulaDescriptor): + baseText = .formula(latex: formulaDescriptor.latex) + descriptor = formulaDescriptor + } + + var fragment = markdownApplyingInlineIntent(inlineIntent, to: baseText) + if let url = markdownLink(attributes: attributes, documentURL: context.documentURL) { + fragment = .url(text: fragment, url: url, webpageId: nil) + } + fragment = markdownApplyHTMLStyles(htmlStyles, to: fragment) + + if let descriptor { + fragments.append(.formula(descriptor, fragment)) + } else { + fragments.append(.richText(fragment)) + } + } + } + + guard !didAbort, !context.budget.isExceeded else { + return nil + } + return MarkdownInlineContent(fragments: fragments) +} + +private func markdownInlineTextSegments(from text: String, formulasByPlaceholder: [String: MarkdownFormulaDescriptor]) -> [MarkdownInlineTextSegment] { + guard !text.isEmpty else { + return [] + } + + let nsText = text as NSString + let matches = markdownFormulaPlaceholderRegex.matches(in: text, range: NSRange(location: 0, length: nsText.length)) + guard !matches.isEmpty else { + return [.plain(text)] + } + + var result: [MarkdownInlineTextSegment] = [] + var currentLocation = 0 + + for match in matches { + if match.range.location > currentLocation { + result.append(.plain(nsText.substring(with: NSRange(location: currentLocation, length: match.range.location - currentLocation)))) + } + + let placeholder = nsText.substring(with: match.range) + if let descriptor = formulasByPlaceholder[placeholder] { + result.append(.formula(descriptor)) + } else { + result.append(.plain(placeholder)) + } + currentLocation = match.range.location + match.range.length + } + + if currentLocation < nsText.length { + result.append(.plain(nsText.substring(from: currentLocation))) + } + + return result +} + +@available(iOS 15.0, *) +private func markdownApplyingInlineIntent(_ inlineIntent: InlinePresentationIntent?, to text: RichText) -> RichText { + guard let inlineIntent else { + return text + } + + var result = text + if inlineIntent.contains(.stronglyEmphasized) { + result = .bold(result) + } + if inlineIntent.contains(.emphasized) { + result = .italic(result) + } + if inlineIntent.contains(.strikethrough) { + result = .strikethrough(result) + } + if inlineIntent.contains(.code) { + result = .fixed(result) + } + if inlineIntent.contains(markdownHardBreakInlineIntent) { + result = .plain("\n") + } + return result +} + +private enum MarkdownHTMLInlineStyle: Equatable { + case underline + case `subscript` + case superscript + case marked +} + +private enum MarkdownHTMLDirective { + case open(MarkdownHTMLInlineStyle) + case close(MarkdownHTMLInlineStyle) + case lineBreak +} + +private func markdownHTMLDirective(for text: String) -> MarkdownHTMLDirective? { + switch text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { + case "": + return .open(.underline) + case "": + return .close(.underline) + case "": + return .open(.subscript) + case "": + return .close(.subscript) + case "": + return .open(.superscript) + case "": + return .close(.superscript) + case "": + return .open(.marked) + case "": + return .close(.marked) + case "
", "
", "
": + return .lineBreak + default: + return nil + } +} + +private func markdownApplyHTMLStyles(_ styles: [MarkdownHTMLInlineStyle], to text: RichText) -> RichText { + var result = text + for style in styles { + switch style { + case .underline: + result = .underline(result) + case .subscript: + result = .subscript(result) + case .superscript: + result = .superscript(result) + case .marked: + result = .marked(result) + } + } + return result +} + +private func markdownAlternateDescription(attributes: [NSAttributedString.Key: Any]) -> String? { + if let value = attributes[markdownAlternateDescriptionAttribute] as? String, !value.isEmpty { + return value + } + return nil +} + +private func markdownImageURL(attributes: [NSAttributedString.Key: Any]) -> String? { + if let value = attributes[markdownImageURLAttribute] as? URL { + return value.absoluteString + } + if let value = attributes[markdownImageURLAttribute] as? NSURL { + return (value as URL).absoluteString + } + if let value = attributes[markdownImageURLAttribute] as? String, !value.isEmpty { + return value + } + return nil +} + +private func markdownResolveImageSource(_ value: String, limits: MarkdownSafetyLimits) -> MarkdownResolvedImageSource { + if value.hasPrefix("//") { + return .remote("https:\(value)") + } + + if value.lowercased().hasPrefix("data:") { + return markdownResolveDataImageSource(value, limits: limits) + } + + guard let url = URL(string: value), let scheme = url.scheme?.lowercased() else { + return .unsupported + } + + switch scheme { + case "http", "https": + return .remote(url.absoluteString) + case "data": + return markdownResolveDataImageSource(url.absoluteString, limits: limits) + default: + return .unsupported + } +} + +private func markdownResolveDataImageSource(_ value: String, limits: MarkdownSafetyLimits) -> MarkdownResolvedImageSource { + guard value.lowercased().hasPrefix("data:"), + let commaIndex = value.firstIndex(of: ",") else { + return .unsupported + } + + let header = String(value[value.index(value.startIndex, offsetBy: 5) ..< commaIndex]) + let payloadStart = value.index(after: commaIndex) + let payload = String(value[payloadStart...]) + let isBase64 = header.lowercased().contains(";base64") + + let data: Data? + if isBase64 { + data = Data(base64Encoded: payload, options: [.ignoreUnknownCharacters]) + } else if let decodedPayload = payload.removingPercentEncoding { + data = decodedPayload.data(using: .utf8) + } else { + data = nil + } + + guard let data = data, data.count <= limits.maxDataImageBytes else { + return .unsupported + } + + guard let image = UIImage(data: data), + let dimensions = markdownImagePixelDimensions(image) else { + return .unsupported + } + + let pixelCount = Int64(dimensions.width) * Int64(dimensions.height) + guard pixelCount <= Int64(limits.maxDataImagePixelCount) else { + return .unsupported + } + + return .data(data, dimensions) +} + +private func markdownImagePixelDimensions(_ image: UIImage) -> PixelDimensions? { + if let cgImage = image.cgImage { + return PixelDimensions(width: Int32(cgImage.width), height: Int32(cgImage.height)) + } + + let width = max(1, Int32(ceil(image.size.width * image.scale))) + let height = max(1, Int32(ceil(image.size.height * image.scale))) + return PixelDimensions(width: width, height: height) +} + +private func markdownImageCaption(_ title: String?) -> InstantPageCaption { + if let title, !title.isEmpty { + return InstantPageCaption(text: .plain(title), credit: .empty) + } else { + return InstantPageCaption(text: .empty, credit: .empty) + } +} + +private func markdownInlineImageDimensions(attributes: [NSAttributedString.Key: Any]) -> PixelDimensions { + guard let font = attributes[.font] as? UIFont else { + return markdownDefaultInlineImageDimensions + } + + let side = max(markdownDefaultInlineImageDimensions.width, Int32(ceil(font.lineHeight))) + return PixelDimensions(width: side, height: side) +} + +private func markdownLink(attributes: [NSAttributedString.Key: Any], documentURL: URL?) -> String? { + if let value = attributes[markdownLinkAttribute] as? URL { + return markdownNormalizedLink(value, documentURL: documentURL) + } + if let value = attributes[markdownLinkAttribute] as? NSURL { + return markdownNormalizedLink(value as URL, documentURL: documentURL) + } + if let value = attributes[markdownLinkAttribute] as? String, !value.isEmpty { + if value.hasPrefix("#") { + return value + } + if let url = URL(string: value) { + return markdownNormalizedLink(url, documentURL: documentURL) + } + return value + } + return nil +} + +private func markdownNormalizedLink(_ url: URL, documentURL: URL?) -> String { + if url.baseURL != nil { + let relative = url.relativeString + if relative.hasPrefix("#") { + return relative + } + } + if let documentURL, let fragment = url.fragment, markdownMatchesDocument(url, documentURL: documentURL) { + return "#\(fragment)" + } + return url.absoluteString +} + +private func markdownMatchesDocument(_ url: URL, documentURL: URL) -> Bool { + let normalizedUrl = markdownURLWithoutFragment(url) + let normalizedDocumentURL = markdownURLWithoutFragment(documentURL) + + if normalizedUrl.isFileURL && normalizedDocumentURL.isFileURL { + return normalizedUrl.standardizedFileURL == normalizedDocumentURL.standardizedFileURL + } else { + return normalizedUrl == normalizedDocumentURL + } +} + +private func markdownURLWithoutFragment(_ url: URL) -> URL { + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { + return url + } + components.fragment = nil + return components.url ?? url +} + +private func markdownCompact(_ fragments: [RichText]) -> RichText { + var compacted: [RichText] = [] + for fragment in fragments { + switch fragment { + case .empty: + continue + case let .plain(text): + guard !text.isEmpty else { + continue + } + if let last = compacted.last, case let .plain(lastText) = last { + compacted[compacted.count - 1] = .plain(lastText + text) + } else { + compacted.append(fragment) + } + case let .concat(items): + let nested = markdownCompact(items) + switch nested { + case .empty: + continue + case let .plain(text): + if let last = compacted.last, case let .plain(lastText) = last { + compacted[compacted.count - 1] = .plain(lastText + text) + } else { + compacted.append(.plain(text)) + } + default: + compacted.append(nested) + } + default: + compacted.append(fragment) + } + } + if compacted.isEmpty { + return .empty + } else if compacted.count == 1 { + return compacted[0] + } else { + return .concat(compacted) + } +} + +private func markdownDroppingPrefixLength(_ length: Int, from text: RichText) -> RichText { + guard length > 0 else { + return text + } + switch text { + case .empty: + return .empty + case let .plain(string): + let nsString = string as NSString + if nsString.length <= length { + return .empty + } else { + return .plain(nsString.substring(from: length)) + } + case let .bold(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .bold(dropped) + case let .italic(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .italic(dropped) + case let .underline(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .underline(dropped) + case let .strikethrough(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .strikethrough(dropped) + case let .fixed(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .fixed(dropped) + case let .url(inner, url, webpageId): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .url(text: dropped, url: url, webpageId: webpageId) + case let .email(inner, email): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .email(text: dropped, email: email) + case let .concat(items): + var remainingLength = length + var result: [RichText] = [] + result.reserveCapacity(items.count) + for item in items { + if remainingLength > 0 { + let itemLength = (item.plainText as NSString).length + if itemLength <= remainingLength { + remainingLength -= itemLength + continue + } + result.append(markdownDroppingPrefixLength(remainingLength, from: item)) + remainingLength = 0 + } else { + result.append(item) + } + } + return markdownCompact(result) + case let .subscript(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .subscript(dropped) + case let .superscript(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .superscript(dropped) + case let .marked(inner): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .marked(dropped) + case let .phone(inner, phone): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .phone(text: dropped, phone: phone) + case .image: + return text + case let .formula(latex): + let nsLatex = latex as NSString + if nsLatex.length <= length { + return .empty + } else { + return .plain(nsLatex.substring(from: length)) + } + case let .anchor(inner, name): + let dropped = markdownDroppingPrefixLength(length, from: inner) + return dropped == .empty ? .empty : .anchor(text: dropped, name: name) + case .textCustomEmoji: + return text + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + return text + } +} + +private func markdownHasDisplayableContent(_ richText: RichText) -> Bool { + switch richText { + case .empty: + return false + case let .plain(text): + return !text.isEmpty + case let .bold(text), + let .italic(text), + let .underline(text), + let .strikethrough(text), + let .fixed(text), + let .subscript(text), + let .superscript(text), + let .marked(text), + let .anchor(text, _): + return markdownHasDisplayableContent(text) + case let .url(text, _, _), + let .email(text, _), + let .phone(text, _): + return markdownHasDisplayableContent(text) + case let .concat(items): + return items.contains(where: markdownHasDisplayableContent) + case .image: + return true + case let .formula(latex): + return !latex.isEmpty + case .textCustomEmoji: + return true + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + return true + } +} + +private func markdownIsWhitespaceOnly(_ richText: RichText) -> Bool { + switch richText { + case .empty: + return true + case let .plain(text): + return text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + case let .bold(text), + let .italic(text), + let .underline(text), + let .strikethrough(text), + let .fixed(text), + let .subscript(text), + let .superscript(text), + let .marked(text), + let .anchor(text, _): + return markdownIsWhitespaceOnly(text) + case let .url(text, _, _), + let .email(text, _), + let .phone(text, _): + return markdownIsWhitespaceOnly(text) + case let .concat(items): + return items.allSatisfy(markdownIsWhitespaceOnly) + case .image: + return false + case let .formula(latex): + return latex.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + case .textCustomEmoji: + return false + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + return false + } +} + +private func markdownPlainText(from block: InstantPageBlock, depth: Int = 0) -> String { + guard depth <= markdownSafetyLimits.maxPresentationIntentDepth else { + return "" + } + + switch block { + case let .title(text): + return text.plainText + case let .subtitle(text): + return text.plainText + case let .authorDate(author, _): + return author.plainText + case let .header(text): + return text.plainText + case let .subheader(text): + return text.plainText + case let .heading(text, _): + return text.plainText + case let .formula(latex): + return latex + case let .paragraph(text): + return text.plainText + case let .preformatted(text, _): + return text.plainText + case let .footer(text): + return text.plainText + case let .blockQuote(blocks, caption): + let blocksText = blocks.map { markdownPlainText(from: $0, depth: depth + 1) }.joined(separator: "\n") + return blocksText.isEmpty ? caption.plainText : blocksText + case let .pullQuote(text, caption): + return text.plainText.isEmpty ? caption.plainText : text.plainText + case let .kicker(text): + return text.plainText + case let .table(title, _, _, _): + return title.plainText + case let .details(title, _, _): + return title.plainText + case let .relatedArticles(title, _): + return title.plainText + default: + return "" + } +} + +private func markdownTitle(from blocks: [InstantPageBlock], file: FileMediaReference, fileURL: URL) -> String { + for block in blocks { + if case let .title(text) = block, !text.plainText.isEmpty { + return text.plainText + } + } + if let fileName = file.media.fileName, !fileName.isEmpty { + let baseName = URL(fileURLWithPath: fileName).deletingPathExtension().lastPathComponent + if !baseName.isEmpty { + return baseName + } + return fileName + } + let baseName = fileURL.deletingPathExtension().lastPathComponent + if !baseName.isEmpty { + return baseName + } + return fileURL.lastPathComponent +} + +private func markdownNormalizedCodeBlockLanguage(_ language: String?) -> String? { + guard let language else { + return nil + } + let normalized = language.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return normalized.isEmpty ? nil : normalized +} + +private func markdownFirstParagraphText(from blocks: [InstantPageBlock], depth: Int = 0) -> String? { + guard depth <= markdownSafetyLimits.maxPresentationIntentDepth else { + return nil + } + + for block in blocks { + switch block { + case let .formula(latex): + if !latex.isEmpty { + return latex + } + case let .paragraph(text): + if !text.plainText.isEmpty { + return text.plainText + } + case let .list(items, _): + for item in items { + switch item { + case let .text(text, _, _): + if !text.plainText.isEmpty { + return text.plainText + } + case let .blocks(blocks, _, _): + if let text = markdownFirstParagraphText(from: blocks, depth: depth + 1) { + return text + } + default: + break + } + } + case let .details(_, blocks, _): + if let text = markdownFirstParagraphText(from: blocks, depth: depth + 1) { + return text + } + default: + break + } + } + return nil +} + +private func markdownBlocksWithGeneratedAnchors(_ blocks: [InstantPageBlock]) -> [InstantPageBlock] { + var result: [InstantPageBlock] = [] + var slugCounts: [String: Int] = [:] + + for block in blocks { + if let headingText = markdownHeadingText(from: block), !headingText.isEmpty { + let baseSlug = markdownAnchorSlug(from: headingText) + if !baseSlug.isEmpty { + let count = slugCounts[baseSlug] ?? 0 + slugCounts[baseSlug] = count + 1 + + let slug: String + if count == 0 { + slug = baseSlug + } else { + slug = "\(baseSlug)-\(count)" + } + result.append(.anchor(slug)) + } + } + result.append(block) + } + + return result +} + +private func markdownHeadingText(from block: InstantPageBlock) -> String? { + switch block { + case let .title(text): + return text.plainText + case let .header(text): + return text.plainText + case let .subheader(text): + return text.plainText + case let .heading(text, _): + return text.plainText + default: + return nil + } +} + +private func markdownAnchorSlug(from text: String) -> String { + let normalized = text + .folding(options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive], locale: nil) + .lowercased() + + let dashScalar = "-".unicodeScalars.first! + let separatorSet = CharacterSet.whitespacesAndNewlines.union(CharacterSet(charactersIn: "-_")) + var scalars: [UnicodeScalar] = [] + var previousWasDash = false + + for scalar in normalized.unicodeScalars { + if CharacterSet.alphanumerics.contains(scalar) { + scalars.append(scalar) + previousWasDash = false + } else if separatorSet.contains(scalar) { + if !scalars.isEmpty && !previousWasDash { + scalars.append(dashScalar) + previousWasDash = true + } + } + } + + if scalars.last == dashScalar { + scalars.removeLast() + } + + return String(String.UnicodeScalarView(scalars)) +} + +private func markdownTrimTrailingCodeBlockNewline(_ attributedString: NSAttributedString) -> NSAttributedString { + guard attributedString.length > 0 else { + return attributedString + } + let mutable = NSMutableAttributedString(attributedString: attributedString) + let string = mutable.string + if string.hasSuffix("\r\n"), mutable.length >= 2 { + mutable.deleteCharacters(in: NSRange(location: mutable.length - 2, length: 2)) + } else if string.hasSuffix("\n") { + mutable.deleteCharacters(in: NSRange(location: mutable.length - 1, length: 1)) + } + return mutable +} + +private enum MarkdownIntentKind { + case table([TableHorizontalAlignment]) + case tableHeaderRow + case tableRow + case tableCell(Int) + case paragraph + case header(Int) + case codeBlock(String?) + case thematicBreak + case blockQuote + case unorderedList + case orderedList + case listItem(Int) + case unknown + + @available(iOS 15.0, *) + init(component: PresentationIntent.IntentType) { + switch component.kind { + case let .table(columns): + self = .table(columns.map(markdownTableColumnAlignment)) + case .tableHeaderRow: + self = .tableHeaderRow + case .tableRow(_): + self = .tableRow + case let .tableCell(column): + self = .tableCell(column) + case .paragraph: + self = .paragraph + case let .header(level): + self = .header(level) + case let .codeBlock(languageHint): + self = .codeBlock(languageHint) + case .thematicBreak: + self = .thematicBreak + case .blockQuote: + self = .blockQuote + case .unorderedList: + self = .unorderedList + case .orderedList: + self = .orderedList + case let .listItem(ordinal): + self = .listItem(ordinal) + default: + self = .unknown + } + } +} + +@available(iOS 15.0, *) +private func markdownTableColumnAlignment(_ column: PresentationIntent.TableColumn) -> TableHorizontalAlignment { + switch column.alignment { + case .left: + return .left + case .center: + return .center + case .right: + return .right + @unknown default: + return .left + } +} + +private final class MarkdownIntentNode { + let identity: Int + let kind: MarkdownIntentKind + + private(set) var children: [MarkdownIntentNode] = [] + private var childIdentities: Set = [] + private(set) var attributedText = NSMutableAttributedString(string: "") + + @available(iOS 15.0, *) + init(component: PresentationIntent.IntentType) { + self.identity = component.identity + self.kind = MarkdownIntentKind(component: component) + } + + func append(child: MarkdownIntentNode) { + if self.childIdentities.insert(child.identity).inserted { + self.children.append(child) + } + } + + func append(text: NSAttributedString) { + self.attributedText.append(text) + } +} diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index 922aa0ddf2..0537bb3715 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -3,7 +3,6 @@ import UIKit import Display import ComponentFlow import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences @@ -51,7 +50,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF var presentInGlobalOverlay: (ViewController) -> Void = { _ in } var getNavigationController: () -> NavigationController? = { return nil } - private var tempFile: TempBoxFile? + private var tempFile: EngineTempBoxFile? init(context: AccountContext, presentationData: PresentationData, file: FileMediaReference) { self.context = context @@ -82,10 +81,10 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF var title = "file" var url = "" - if let path = self.context.account.postbox.mediaBox.completedResourcePath(file.media.resource) { + if let path = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.media.resource.id)) { var updatedPath = path if let fileName = file.media.fileName { - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) updatedPath = tempFile.path self.tempFile = tempFile title = fileName @@ -364,7 +363,7 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF let pageIndicatorSize = self.pageIndicator.update( transition: .immediate, component: AnyComponent( - Text(text: "\(self.pageNumber?.0 ?? 1) of \(self.pageNumber?.1 ?? 1)", font: Font.with(size: 15.0, weight: .regular, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemPrimaryTextColor) + Text(text: self.presentationData.strings.Items_NOfM("\(self.pageNumber?.0 ?? 1)", "\(self.pageNumber?.1 ?? 1)").string, font: Font.with(size: 15.0, weight: .regular, traits: .monospacedNumbers), color: self.presentationData.theme.list.itemPrimaryTextColor) ), environment: {}, containerSize: size diff --git a/submodules/BrowserUI/Sources/BrowserReadability.swift b/submodules/BrowserUI/Sources/BrowserReadability.swift index 4f734d98e4..c3710f64c3 100644 --- a/submodules/BrowserUI/Sources/BrowserReadability.swift +++ b/submodules/BrowserUI/Sources/BrowserReadability.swift @@ -1,7 +1,6 @@ import Foundation import WebKit import AppBundle -import Postbox import TelegramCore import InstantPageUI @@ -35,7 +34,40 @@ public class Readability: NSObject, WKNavigationDelegate { if let (html, subresources) = extractHtmlString(from: archiveData) { self.subresources = subresources - self.webView.loadHTMLString(html, baseURL: url.baseURL) + self.sanitizeHtmlString(html) { [weak self] html in + guard let self else { + return + } + self.webView.loadHTMLString(html, baseURL: url.baseURL) + } + } + } + + private func sanitizeHtmlString(_ html: String, completion: @escaping (String) -> Void) { + guard let readerModeJS = loadFile(name: "ReaderMode", type: "js") else { + completion(htmlByRemovingScriptTags(html)) + return + } + + let domPurifyJS = extractDOMPurifyScript(from: readerModeJS) ?? readerModeJS + self.webView.evaluateJavaScript(domPurifyJS) { [weak self] _, error in + guard let self else { + return + } + + guard error == nil, let htmlLiteral = javascriptStringLiteral(html) else { + completion(htmlByRemovingScriptTags(html)) + return + } + + let sanitizeJS = """ + (function(html) { + return DOMPurify.sanitize(html, {WHOLE_DOCUMENT: true, ADD_TAGS: ["iframe"]}); + })(\(htmlLiteral)); + """ + self.webView.evaluateJavaScript(sanitizeJS) { result, _ in + completion((result as? String) ?? htmlByRemovingScriptTags(html)) + } } } @@ -50,6 +82,7 @@ public class Readability: NSObject, WKNavigationDelegate { return } guard let page = parseJson(result, url: self.url.absoluteString) else { + completion(nil, error) return } completion(page, nil) @@ -96,6 +129,32 @@ func loadFile(name: String, type: String) -> String? { return userScript } +private func extractDOMPurifyScript(from readerModeJS: String) -> String? { + guard let range = readerModeJS.range(of: "\n\n(function () {") else { + return nil + } + return String(readerModeJS[.. String? { + guard let data = try? JSONSerialization.data(withJSONObject: [input], options: []), + var arrayString = String(data: data, encoding: .utf8), + arrayString.count >= 2 else { + return nil + } + arrayString.removeFirst() + arrayString.removeLast() + return arrayString +} + +private func htmlByRemovingScriptTags(_ input: String) -> String { + guard let regex = try? NSRegularExpression(pattern: "]*>[\\s\\S]*?", options: [.caseInsensitive]) else { + return input + } + let range = NSRange(input.startIndex ..< input.endIndex, in: input) + return regex.stringByReplacingMatches(in: input, options: [], range: range, withTemplate: "") +} + private func extractHtmlString(from webArchiveData: Data) -> (String, [Any]?)? { if let webArchiveDict = try? PropertyListSerialization.propertyList(from: webArchiveData, format: nil) as? [String: Any], let mainResource = webArchiveDict["WebMainResource"] as? [String: Any], @@ -115,14 +174,14 @@ private func parseJson(_ input: [String: Any], url: String) -> TelegramMediaWebp let byline = input["byline"] as? String let excerpt = input["excerpt"] as? String - var media: [MediaId: Media] = [:] + var media: [EngineMedia.Id: EngineRawMedia] = [:] let blocks = parseContent(input, url, &media) guard !blocks.isEmpty else { return nil } return TelegramMediaWebpage( - webpageId: MediaId(namespace: 0, id: 0), + webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded( TelegramMediaWebpageLoadedContent( url: url, @@ -156,7 +215,7 @@ private func parseJson(_ input: [String: Any], url: String) -> TelegramMediaWebp ) } -private func parseContent(_ input: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> [InstantPageBlock] { +private func parseContent(_ input: [String: Any], _ url: String, _ media: inout [EngineMedia.Id: EngineRawMedia]) -> [InstantPageBlock] { let title = input["title"] as? String let byline = input["byline"] as? String let date = input["publishedTime"] as? String @@ -185,7 +244,7 @@ private func parseRichText(_ input: String) -> RichText { return .plain(input) } -private func parseRichText(_ input: [String: Any], _ media: inout [MediaId: Media]) -> RichText { +private func parseRichText(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> RichText { var text: RichText if let string = input["content"] as? String { text = parseRichText(string) @@ -204,7 +263,7 @@ private func parseRichText(_ input: [String: Any], _ media: inout [MediaId: Medi return text } -private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> RichText { +private func parseRichText(_ input: [Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> RichText { var result: [RichText] = [] for item in input { @@ -258,7 +317,7 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R } else { height = 0 } - let id = MediaId(namespace: Namespaces.Media.CloudFile, id: Int64(media.count)) + let id = EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: Int64(media.count)) media[id] = TelegramMediaImage( imageId: id, representations: [ @@ -343,6 +402,12 @@ private func trimStart(_ input: RichText) -> RichText { } case .image: break + case .formula: + break + case .textCustomEmoji: + break + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + break } return text } @@ -385,6 +450,12 @@ private func trimEnd(_ input: RichText) -> RichText { } case .image: break + case .formula: + break + case .textCustomEmoji: + break + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + break } return text } @@ -428,6 +499,12 @@ private func trim(_ input: RichText) -> RichText { } case .image: break + case .formula: + break + case .textCustomEmoji: + break + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + break } return text } @@ -470,6 +547,12 @@ private func addNewLine(_ input: RichText) -> RichText { } case .image: break + case let .formula(latex): + text = .concat([.formula(latex: latex), .plain("\n")]) + case .textCustomEmoji: + break + case .textAutoEmail, .textAutoPhone, .textAutoUrl, .textBankCard, .textBotCommand, .textCashtag, .textHashtag, .textMention, .textMentionName, .textSpoiler, .textDate: + break } return text } @@ -481,7 +564,7 @@ private func applyAnchor(_ input: RichText, item: [String: Any]) -> RichText { return .anchor(text: input, name: id) } -private func parseTable(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock { +private func parseTable(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock { let title = (input["title"] as? String) ?? "" return .table( title: trim(applyAnchor(parseRichText(title), item: input)), @@ -491,7 +574,7 @@ private func parseTable(_ input: [String: Any], _ media: inout [MediaId: Media]) ) } -private func parseTableRows(_ input: [Any], _ media: inout [MediaId: Media]) -> [InstantPageTableRow] { +private func parseTableRows(_ input: [Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> [InstantPageTableRow] { var result: [InstantPageTableRow] = [] for item in input { if let item = item as? [String: Any] { @@ -506,7 +589,7 @@ private func parseTableRows(_ input: [Any], _ media: inout [MediaId: Media]) -> return result } -private func parseTableRow(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageTableRow { +private func parseTableRow(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageTableRow { var cells: [InstantPageTableCell] = [] if let content = input["content"] as? [Any] { @@ -544,7 +627,7 @@ private func parseTableRow(_ input: [String: Any], _ media: inout [MediaId: Medi return InstantPageTableRow(cells: cells) } -private func parseDetails(_ item: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> InstantPageBlock? { +private func parseDetails(_ item: [String: Any], _ url: String, _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? { guard var content = item["contant"] as? [Any] else { return nil } @@ -568,7 +651,7 @@ private func parseDetails(_ item: [String: Any], _ url: String, _ media: inout [ } private let nonListCharacters = CharacterSet(charactersIn: "0123456789").inverted -private func parseList(_ input: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> InstantPageBlock? { +private func parseList(_ input: [String: Any], _ url: String, _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? { guard let content = input["content"] as? [Any], let tag = input["tag"] as? String else { return nil } @@ -587,17 +670,17 @@ private func parseList(_ input: [String: Any], _ url: String, _ media: inout [Me if parseAsBlocks { let blocks = parsePageBlocks(subcontent, url, &media) if !blocks.isEmpty { - items.append(.blocks(blocks, nil)) + items.append(.blocks(blocks, nil, nil)) } } else { - items.append(.text(trim(parseRichText(item, &media)), nil)) + items.append(.text(trim(parseRichText(item, &media)), nil, nil)) } } } let ordered = tag == "ol" var allEmpty = true for item in items { - if case let .text(text, _) = item { + if case let .text(text, _, _) = item { if case .empty = text { } else { let plainText = text.plainText @@ -617,7 +700,7 @@ private func parseList(_ input: [String: Any], _ url: String, _ media: inout [Me return .list(items: items, ordered: ordered) } -private func parseImage(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? { +private func parseImage(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? { guard let src = input["src"] as? String else { return nil } @@ -646,7 +729,7 @@ private func parseImage(_ input: [String: Any], _ media: inout [MediaId: Media]) height = 0 } - let id = MediaId(namespace: Namespaces.Media.CloudImage, id: Int64(media.count)) + let id = EngineMedia.Id(namespace: Namespaces.Media.CloudImage, id: Int64(media.count)) media[id] = TelegramMediaImage( imageId: id, representations: [ @@ -671,7 +754,7 @@ private func parseImage(_ input: [String: Any], _ media: inout [MediaId: Media]) ) } -private func parseVideo(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? { +private func parseVideo(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? { guard let src = input["src"] as? String else { return nil } @@ -701,30 +784,38 @@ private func parseVideo(_ input: [String: Any], _ media: inout [MediaId: Media]) ) } -private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? { +private func firstElement(withTag tag: String, in input: [Any], skippingSubtreesWithTag skippedTag: String? = nil) -> [String: Any]? { + for item in input { + guard let item = item as? [String: Any] else { + continue + } + let itemTag = item["tag"] as? String + if itemTag == tag { + return item + } + if itemTag == skippedTag { + continue + } + if let content = item["content"] as? [Any], let result = firstElement(withTag: tag, in: content, skippingSubtreesWithTag: skippedTag) { + return result + } + } + return nil +} + +private func parseFigure(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? { guard let content = input["content"] as? [Any] else { return nil } var block: InstantPageBlock? var caption: RichText? - for item in content { - if let item = item as? [String: Any], let tag = item["tag"] as? String { - if tag == "p", let content = item["content"] as? [Any] { - for item in content { - if let item = item as? [String: Any], let tag = item["tag"] as? String { - if tag == "iframe" { - block = parseVideo(item, &media) - } - } - } - } else if tag == "iframe" { - block = parseVideo(item, &media) - } else if tag == "img" { - block = parseImage(item, &media) - } else if tag == "figcaption" { - caption = trim(parseRichText(item, &media)) - } - } + if let iframe = firstElement(withTag: "iframe", in: content, skippingSubtreesWithTag: "figcaption") { + block = parseVideo(iframe, &media) + } else if let image = firstElement(withTag: "img", in: content, skippingSubtreesWithTag: "figcaption") { + block = parseImage(image, &media) + } + if let figcaption = firstElement(withTag: "figcaption", in: content) { + caption = trim(parseRichText(figcaption, &media)) } guard var block else { return nil @@ -735,7 +826,7 @@ private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media] return block } -private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [MediaId: Media]) -> [InstantPageBlock] { +private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [EngineMedia.Id: EngineRawMedia]) -> [InstantPageBlock] { var result: [InstantPageBlock] = [] for item in input { if let string = item as? String { @@ -747,12 +838,18 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi result.append(.paragraph(trim(parseRichText(item, &media)))) case "h1", "h2": result.append(.header(trim(parseRichText(item, &media)))) - case "h3", "h4", "h5", "h6": - result.append(.subheader(trim(parseRichText(item, &media)))) + case "h3": + result.append(.heading(text: trim(parseRichText(item, &media)), level: 3)) + case "h4": + result.append(.heading(text: trim(parseRichText(item, &media)), level: 4)) + case "h5": + result.append(.heading(text: trim(parseRichText(item, &media)), level: 5)) + case "h6": + result.append(.heading(text: trim(parseRichText(item, &media)), level: 6)) case "pre": - result.append(.preformatted(.fixed(trim(parseRichText(item, &media))))) + result.append(.preformatted(text: .fixed(trim(parseRichText(item, &media))), language: nil)) case "blockquote": - result.append(.blockQuote(text: .italic(trim(parseRichText(item, &media))), caption: .empty)) + result.append(.blockQuote(blocks: [.paragraph(.italic(trim(parseRichText(item, &media))))], caption: .empty)) case "img": if let image = parseImage(item, &media) { result.append(image) diff --git a/submodules/BrowserUI/Sources/BrowserRecentlyVisited.swift b/submodules/BrowserUI/Sources/BrowserRecentlyVisited.swift index fc8bb321f9..608c19e745 100644 --- a/submodules/BrowserUI/Sources/BrowserRecentlyVisited.swift +++ b/submodules/BrowserUI/Sources/BrowserRecentlyVisited.swift @@ -1,23 +1,22 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences private struct RecentlyVisitedLinkItemId { - public let rawValue: MemoryBuffer + public let rawValue: EngineMemoryBuffer var value: String { return String(data: self.rawValue.makeData(), encoding: .utf8) ?? "" } - init(_ rawValue: MemoryBuffer) { + init(_ rawValue: EngineMemoryBuffer) { self.rawValue = rawValue } init?(_ value: String) { if let data = value.data(using: .utf8) { - self.rawValue = MemoryBuffer(data: data) + self.rawValue = EngineMemoryBuffer(data: data) } else { return nil } @@ -38,7 +37,7 @@ public final class RecentVisitedLinkItem: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let webPageData = try container.decodeIfPresent(Data.self, forKey: .webPage) { - self.webPage = PostboxDecoder(buffer: MemoryBuffer(data: webPageData)).decodeRootObject() as! TelegramMediaWebpage + self.webPage = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: webPageData)).decodeRootObject() as! TelegramMediaWebpage } else { fatalError() } @@ -47,7 +46,7 @@ public final class RecentVisitedLinkItem: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - let encoder = PostboxEncoder() + let encoder = EnginePostboxEncoder() encoder.encodeRootObject(self.webPage) let webPageData = encoder.makeData() try container.encode(webPageData, forKey: .webPage) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 4349ea6aaa..ce7f1a6747 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SwiftSignalKit import Display -import Postbox import TelegramCore import TelegramPresentationData import ComponentFlow @@ -29,6 +28,7 @@ private final class BrowserScreenComponent: CombinedComponent { let context: AccountContext let contentState: BrowserContentState? let presentationState: BrowserPresentationState + let toolbarMode: BrowserToolbarMode let canShare: Bool let performAction: ActionSlot let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void @@ -38,6 +38,7 @@ private final class BrowserScreenComponent: CombinedComponent { context: AccountContext, contentState: BrowserContentState?, presentationState: BrowserPresentationState, + toolbarMode: BrowserToolbarMode, canShare: Bool, performAction: ActionSlot, performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void, @@ -46,6 +47,7 @@ private final class BrowserScreenComponent: CombinedComponent { self.context = context self.contentState = contentState self.presentationState = presentationState + self.toolbarMode = toolbarMode self.canShare = canShare self.performAction = performAction self.performHoldAction = performHoldAction @@ -62,6 +64,9 @@ private final class BrowserScreenComponent: CombinedComponent { if lhs.presentationState != rhs.presentationState { return false } + if lhs.toolbarMode != rhs.toolbarMode { + return false + } if lhs.canShare != rhs.canShare { return false } @@ -112,8 +117,7 @@ private final class BrowserScreenComponent: CombinedComponent { navigationLeftItems = [] navigationRightItems = [] } else { - let contentType = context.component.contentState?.contentType ?? .instantPage - switch contentType { + switch context.component.toolbarMode { case .webPage: navigationContent = AnyComponentWithIdentity( id: "addressBar", @@ -131,7 +135,7 @@ private final class BrowserScreenComponent: CombinedComponent { ) ) ) - case .instantPage, .document: + case .instantPage, .document, .markdown: let title = context.component.contentState?.title ?? "" navigationContent = AnyComponentWithIdentity( id: "titleBar_\(title)", @@ -190,47 +194,46 @@ private final class BrowserScreenComponent: CombinedComponent { // ) // ) // #endif - - let canGoBack = context.component.contentState?.canGoBack ?? false - let canGoForward = context.component.contentState?.canGoForward ?? false - - navigationLeftItems.append( - AnyComponentWithIdentity( - id: "back", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Instant View/Back", - tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) - ) - ), - action: { - performAction.invoke(.navigateBack) - } - ).minSize(CGSize(width: 44.0, height: 44.0)) + if context.component.toolbarMode != .markdown { + let canGoBack = context.component.contentState?.canGoBack ?? false + let canGoForward = context.component.contentState?.canGoForward ?? false + navigationLeftItems.append( + AnyComponentWithIdentity( + id: "back", + component: AnyComponent( + Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/Back", + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoBack ? 1.0 : 0.4) + ) + ), + action: { + performAction.invoke(.navigateBack) + } + ).minSize(CGSize(width: 44.0, height: 44.0)) + ) ) ) - ) - - navigationLeftItems.append( - AnyComponentWithIdentity( - id: "forward", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Instant View/Forward", - tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) - ) - ), - action: { - performAction.invoke(.navigateForward) - } - ).minSize(CGSize(width: 44.0, height: 44.0)) + navigationLeftItems.append( + AnyComponentWithIdentity( + id: "forward", + component: AnyComponent( + Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/Forward", + tintColor: environment.theme.chat.inputPanel.panelControlColor.withAlphaComponent(canGoForward ? 1.0 : 0.4) + ) + ), + action: { + performAction.invoke(.navigateForward) + } + ).minSize(CGSize(width: 44.0, height: 44.0)) + ) ) ) - ) + } } navigationRightItems = [ @@ -258,25 +261,27 @@ private final class BrowserScreenComponent: CombinedComponent { ] if isTablet { - navigationRightItems.insert( - AnyComponentWithIdentity( - id: "bookmarks", - component: AnyComponent( - Button( - content: AnyComponent( - BundleIconComponent( - name: "Instant View/Bookmark", - tintColor: environment.theme.chat.inputPanel.panelControlColor - ) - ), - action: { - performAction.invoke(.openBookmarks) - } - ).minSize(CGSize(width: 44.0, height: 44.0)) - ) - ), - at: 0 - ) + if context.component.toolbarMode != .markdown { + navigationRightItems.insert( + AnyComponentWithIdentity( + id: "bookmarks", + component: AnyComponent( + Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/Bookmark", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + ), + action: { + performAction.invoke(.openBookmarks) + } + ).minSize(CGSize(width: 44.0, height: 44.0)) + ) + ), + at: 0 + ) + } if context.component.canShare { navigationRightItems.insert( AnyComponentWithIdentity( @@ -369,7 +374,7 @@ private final class BrowserScreenComponent: CombinedComponent { canGoForward: context.component.contentState?.canGoForward ?? false, canOpenIn: canOpenIn, canShare: context.component.canShare, - isDocument: context.component.contentState?.contentType == .document, + mode: context.component.toolbarMode, performAction: performAction, performHoldAction: performHoldAction ) @@ -517,7 +522,45 @@ public class BrowserScreen: ViewController, MinimizableController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private var validLayout: (ContainerViewLayout, CGFloat)? - + + private var isMarkdownDocument: Bool { + guard let controller = self.controller else { + return false + } + if case .markdownDocument = controller.subject { + return true + } else { + return false + } + } + private var isMarkdownTopLevelContent: Bool { + return self.isMarkdownDocument && self.content.count <= 1 + } + + private var isMarkdownInstantPageContent: Bool { + return self.isMarkdownTopLevelContent && self.content.last is BrowserInstantPageContent + } + + private var toolbarMode: BrowserToolbarMode { + if self.isMarkdownInstantPageContent { + return .markdown + } + switch self.contentState?.contentType { + case .document: + return .document + case .webPage: + return .webPage + case .instantPage: + return .instantPage + case .none: + if self.content.last is BrowserDocumentContent || self.content.last is BrowserPdfContent { + return .document + } else { + return .instantPage + } + } + } + init(controller: BrowserScreen) { self.context = controller.context self.controller = controller @@ -535,7 +578,7 @@ public class BrowserScreen: ViewController, MinimizableController { super.init() self.pushContent(controller.subject, transition: .immediate) - if let content = self.content.last { + if let content = self.content.last, !self.isMarkdownDocument { content.addToRecentlyVisited() } @@ -562,7 +605,44 @@ public class BrowserScreen: ViewController, MinimizableController { let presentationData = self.presentationData let subject: ShareControllerSubject var isDocument = false - if let content = self.content.last { + if let controller = self.controller { + switch controller.subject { + case let .document(file, _), let .pdfDocument(file, _): + subject = .media(file.abstract, nil) + isDocument = true + case let .markdownDocument(file, _): + if self.isMarkdownTopLevelContent { + subject = .media(file.abstract, nil) + isDocument = true + } else if let content = self.content.last { + if let documentContent = content as? BrowserDocumentContent { + subject = .media(documentContent.file.abstract, nil) + isDocument = true + } else if let documentContent = content as? BrowserPdfContent { + subject = .media(documentContent.file.abstract, nil) + isDocument = true + } else { + subject = .url(url) + } + } else { + subject = .url(url) + } + default: + if let content = self.content.last { + if let documentContent = content as? BrowserDocumentContent { + subject = .media(documentContent.file.abstract, nil) + isDocument = true + } else if let documentContent = content as? BrowserPdfContent { + subject = .media(documentContent.file.abstract, nil) + isDocument = true + } else { + subject = .url(url) + } + } else { + subject = .url(url) + } + } + } else if let content = self.content.last { if let documentContent = content as? BrowserDocumentContent { subject = .media(documentContent.file.abstract, nil) isDocument = true @@ -641,7 +721,10 @@ public class BrowserScreen: ViewController, MinimizableController { var processed = false if let controller = self.controller { switch controller.subject { - case let .document(file, canShare), let .pdfDocument(file, canShare): + case let .document(file, canShare), let .pdfDocument(file, canShare), let .markdownDocument(file, canShare): + if case .markdownDocument = controller.subject, !self.isMarkdownTopLevelContent { + break + } processed = true controller.openDocument(file.media, canShare) default: @@ -855,6 +938,20 @@ public class BrowserScreen: ViewController, MinimizableController { browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file) case let .pdfDocument(file, _): browserContent = BrowserPdfContent(context: self.context, presentationData: self.presentationData, file: file) + case let .markdownDocument(file, _): + if let (webPage, fileURL) = markdownWebpage(context: self.context, file: file) { + browserContent = BrowserInstantPageContent( + context: self.context, + presentationData: self.presentationData, + webPage: webPage, + anchor: nil, + url: fileURL.absoluteString, + sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .otherPrivate), + preloadedResouces: nil + ) + } else { + browserContent = BrowserDocumentContent(context: self.context, presentationData: self.presentationData, file: file) + } } browserContent.pushContent = { [weak self] content, additionalContent in guard let self else { @@ -925,6 +1022,10 @@ public class BrowserScreen: ViewController, MinimizableController { } func popContent(transition: ComponentTransition) { + guard self.content.count > 1 else { + return + } + self.content.removeLast() self.requestLayout(transition: transition) @@ -1127,7 +1228,7 @@ public class BrowserScreen: ViewController, MinimizableController { ) var defaultWebBrowser: String? = settings.defaultWebBrowser - if defaultWebBrowser == nil || defaultWebBrowser == "inAppSafari" { + if defaultWebBrowser == nil || defaultWebBrowser == "inApp" || defaultWebBrowser == "inAppSafari" { defaultWebBrowser = "safari" } @@ -1148,10 +1249,11 @@ public class BrowserScreen: ViewController, MinimizableController { } let canOpenIn = !(self.contentState?.url.hasPrefix("tonsite") ?? false) + let toolbarMode = self.toolbarMode var canShare = true if let controller = self.controller { switch controller.subject { - case let .document(_, canShareValue), let .pdfDocument(_, canShareValue): + case let .document(_, canShareValue), let .pdfDocument(_, canShareValue), let .markdownDocument(_, canShareValue): canShare = canShareValue default: break @@ -1163,6 +1265,7 @@ public class BrowserScreen: ViewController, MinimizableController { } else { items.append(.custom(fontItem, false)) + items.append(.separator) if case .webPage = contentState.contentType { let isAvailable = contentState.hasInstantView @@ -1178,6 +1281,17 @@ public class BrowserScreen: ViewController, MinimizableController { } } + if toolbarMode != .markdown && [.webPage, .instantPage].contains(contentState.contentType) { + if !layout.metrics.isTablet && canOpenIn { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in + if let self { + self.context.sharedContext.applicationBindings.openUrl(openInUrl) + } + action(.default) + }))) + } + } + if !items.isEmpty { items.append(.separator) } @@ -1188,12 +1302,6 @@ public class BrowserScreen: ViewController, MinimizableController { action(.default) }))) } - if [.webPage, .document].contains(contentState.contentType) { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in - performAction.invoke(.updateSearchActive(true)) - action(.default) - }))) - } if canShare && !layout.metrics.isTablet { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in @@ -1202,7 +1310,16 @@ public class BrowserScreen: ViewController, MinimizableController { }))) } - if [.webPage, .instantPage].contains(contentState.contentType) { + if [.webPage, .document].contains(contentState.contentType) { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in + performAction.invoke(.updateSearchActive(true)) + action(.default) + }))) + } + + if toolbarMode != .markdown && [.webPage, .instantPage].contains(contentState.contentType) { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in performAction.invoke(.addBookmark) action(.default) @@ -1214,15 +1331,6 @@ public class BrowserScreen: ViewController, MinimizableController { action(.default) }))) } - - if !layout.metrics.isTablet && canOpenIn { - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in - if let self { - self.context.sharedContext.applicationBindings.openUrl(openInUrl) - } - action(.default) - }))) - } } return ContextController.Items(content: .list(items)) } @@ -1384,7 +1492,7 @@ public class BrowserScreen: ViewController, MinimizableController { var canShare = true if let controller = self.controller { switch controller.subject { - case let .document(_, canShareValue), let .pdfDocument(_, canShareValue): + case let .document(_, canShareValue), let .pdfDocument(_, canShareValue), let .markdownDocument(_, canShareValue): canShare = canShareValue default: break @@ -1398,6 +1506,7 @@ public class BrowserScreen: ViewController, MinimizableController { context: self.context, contentState: self.contentState, presentationState: self.presentationState, + toolbarMode: self.toolbarMode, canShare: canShare, performAction: self.performAction, performHoldAction: { [weak self] view, gesture, action in @@ -1477,10 +1586,11 @@ public class BrowserScreen: ViewController, MinimizableController { case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation, preloadedResources: [Any]?) case document(file: FileMediaReference, canShare: Bool) case pdfDocument(file: FileMediaReference, canShare: Bool) + case markdownDocument(file: FileMediaReference, canShare: Bool) - public var fileId: MediaId? { + public var fileId: EngineMedia.Id? { switch self { - case let .document(file, _), let .pdfDocument(file, _): + case let .document(file, _), let .pdfDocument(file, _), let .markdownDocument(file, _): return file.media.fileId default: return nil @@ -1506,7 +1616,10 @@ public class BrowserScreen: ViewController, MinimizableController { "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.template", - "application/vnd.openxmlformats-officedocument.presentationml.presentation" + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/markdown", + "text/x-markdown", + "text/x-web-markdown" ] public static let supportedDocumentExtensions: [String] = [ @@ -1517,7 +1630,8 @@ public class BrowserScreen: ViewController, MinimizableController { "docx", "xls", "xlsx", - "pptx" + "pptx", + "md" ] public init(context: AccountContext, subject: Subject, preferredConfiguration: WKWebViewConfiguration? = nil, openPreviousOnClose: Bool = false) { diff --git a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift index 5406d1d459..01546e4cd9 100644 --- a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift @@ -9,6 +9,13 @@ import ContextReferenceButtonComponent import GlassBackgroundComponent import EdgeEffect +enum BrowserToolbarMode: Equatable { + case webPage + case instantPage + case document + case markdown +} + final class BrowserToolbarComponent: CombinedComponent { let theme: PresentationTheme let bottomInset: CGFloat @@ -131,7 +138,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { let canGoForward: Bool let canOpenIn: Bool let canShare: Bool - let isDocument: Bool + let mode: BrowserToolbarMode let performAction: ActionSlot let performHoldAction: (UIView, ContextGesture?, BrowserScreen.Action) -> Void @@ -141,7 +148,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { canGoForward: Bool, canOpenIn: Bool, canShare: Bool, - isDocument: Bool, + mode: BrowserToolbarMode, performAction: ActionSlot, performHoldAction: @escaping (UIView, ContextGesture?, BrowserScreen.Action) -> Void ) { @@ -150,7 +157,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { self.canGoForward = canGoForward self.canOpenIn = canOpenIn self.canShare = canShare - self.isDocument = isDocument + self.mode = mode self.performAction = performAction self.performHoldAction = performHoldAction } @@ -171,7 +178,7 @@ final class NavigationToolbarContentComponent: CombinedComponent { if lhs.canShare != rhs.canShare { return false } - if lhs.isDocument != rhs.isDocument { + if lhs.mode != rhs.mode { return false } return true @@ -217,7 +224,8 @@ final class NavigationToolbarContentComponent: CombinedComponent { transition: .easeInOut(duration: 0.2) ) - if context.component.isDocument { + switch context.component.mode { + case .document: var originX: CGFloat = sideInset let search = search.update( @@ -270,7 +278,40 @@ final class NavigationToolbarContentComponent: CombinedComponent { .position(CGPoint(x: originX, y: availableSize.height / 2.0)) ) size.width = originX + sideInset - } else { + case .markdown: + var originX: CGFloat = sideInset + + if !context.component.canShare { + context.add(share + .position(CGPoint(x: availableSize.width / 2.0, y: 10000.0)) + ) + } else { + context.add(share + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) + ) + originX += spacing + } + + let quickLook = quickLook.update( + component: Button( + content: AnyComponent( + BundleIconComponent( + name: "Instant View/OpenDocument", + tintColor: textColor + ) + ), + action: { + performAction.invoke(.openIn) + } + ).minSize(buttonSize), + availableSize: buttonSize, + transition: .easeInOut(duration: 0.2) + ) + context.add(quickLook + .position(CGPoint(x: originX, y: availableSize.height / 2.0)) + ) + size.width = originX + sideInset + case .webPage, .instantPage: var originX: CGFloat = sideInset let canGoBack = context.component.canGoBack diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index afe1d9ed04..31353c2a99 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -3,7 +3,6 @@ import UIKit import Display import ComponentFlow import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences @@ -236,7 +235,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU var getNavigationController: () -> NavigationController? = { return nil } var cancelInteractiveTransitionGestures: () -> Void = {} - private var tempFile: TempBoxFile? + private var tempFile: EngineTempBoxFile? private var disposeTrustedDomain: (() -> Void)? init(context: AccountContext, presentationData: PresentationData, url: String, preferredConfiguration: WKWebViewConfiguration? = nil) { @@ -321,7 +320,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU var request: URLRequest? if url.hasPrefix("file://") { var updatedPath = url - let tempFile = TempBox.shared.file(path: url.replacingOccurrences(of: "file://", with: ""), fileName: "file.xlsx") + let tempFile = EngineTempBox.shared.file(path: url.replacingOccurrences(of: "file://", with: ""), fileName: "file.xlsx") updatedPath = tempFile.path self.tempFile = tempFile @@ -423,16 +422,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU case "oauth_request": let url = json?["url"] as? String if let url { - let securityOrigin = message.frameInfo.securityOrigin - var origin = "" - origin.append(securityOrigin.protocol) - origin.append("://") - origin.append(securityOrigin.host) - if securityOrigin.port != 0 { - origin.append(":") - origin.append("\(securityOrigin.port)") - } - + let origin = message.frameInfo.securityOriginString + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let subject: MessageActionUrlSubject = .url(url: url, inAppOrigin: origin) let _ = (self.context.engine.messages.requestMessageActionUrlAuth(subject: subject) @@ -544,7 +535,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU fileName = "default" } - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) let fileUrl = URL(fileURLWithPath: tempFile.path) let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: fileUrl, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in @@ -1050,7 +1041,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } if let (path, fileName) = self.downloadArguments { - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) let url = URL(fileURLWithPath: tempFile.path) if fileName.hasSuffix(".pkpass") { @@ -1188,7 +1179,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU } self.instantPage = webPage self.instantPageResources = resources - let _ = (updatedRemoteWebpage(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, webPage: WebpageReference(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: self._state.url, displayUrl: "", hash: 0, type: nil, websiteName: nil, title: nil, text: nil, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))))) + let _ = (updatedRemoteWebpage(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, webPage: WebpageReference(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: self._state.url, displayUrl: "", hash: 0, type: nil, websiteName: nil, title: nil, text: nil, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil))))) |> deliverOnMainQueue).start(next: { [weak self] webPage in guard let self, let webPage, case let .Loaded(result) = webPage.content, let _ = result.instantPage else { return @@ -1223,7 +1214,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU let path = NSTemporaryDirectory() + NSUUID().uuidString let _ = try? data.write(to: URL(fileURLWithPath: path), options: .atomic) - let tempFile = TempBox.shared.file(path: path, fileName: "\(self._state.title).webarchive") + let tempFile = EngineTempBox.shared.file(path: path, fileName: "\(self._state.title).webarchive") let url = URL(fileURLWithPath: tempFile.path) let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: url, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in @@ -1362,7 +1353,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU fileName = "default" } - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) let fileUrl = URL(fileURLWithPath: tempFile.path) let controller = legacyICloudFilePicker(theme: self.presentationData.theme, mode: .export, url: fileUrl, documentTypes: [], forceDarkTheme: false, dismissed: {}, completion: { _ in @@ -1681,9 +1672,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU if let favicon, let imageData = favicon.pngData() { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: imageData) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: imageData) image = TelegramMediaImage( - imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), + imageId: EngineMedia.Id(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: [ TelegramMediaImageRepresentation( dimensions: PixelDimensions(width: Int32(favicon.size.width), height: Int32(favicon.size.height)), @@ -1701,7 +1692,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU ) } - let webPage = TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent( + let webPage = TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent( url: self._state.url, displayUrl: self._state.url, hash: 0, @@ -2035,3 +2026,18 @@ private func findScrollView(view: UIView?) -> UIScrollView? { return nil } } + +private extension WKFrameInfo { + var securityOriginString: String { + let securityOrigin = self.securityOrigin + var origin = "" + origin.append(securityOrigin.protocol) + origin.append("://") + origin.append(securityOrigin.host) + if securityOrigin.port != 0 { + origin.append(":") + origin.append("\(securityOrigin.port)") + } + return origin + } +} diff --git a/submodules/BrowserUI/Sources/InstantPageToMarkdown.swift b/submodules/BrowserUI/Sources/InstantPageToMarkdown.swift new file mode 100644 index 0000000000..e5884e806a --- /dev/null +++ b/submodules/BrowserUI/Sources/InstantPageToMarkdown.swift @@ -0,0 +1,281 @@ +import Foundation +import TelegramCore +import TextFormat + +/// Reconstructs a markdown source string from an `InstantPage`. +/// +/// This is the inverse of the markdown→`InstantPage` conversion used when +/// sending rich messages. It is best-effort and never fails: media blocks +/// (images/videos/audio/embeds/maps) are skipped, and any block or inline node +/// without a CommonMark representation falls back to its plain text. +/// +/// Inline formatting is emitted as CommonMark (`**bold**`, `*italic*`, +/// `` `code` ``, `~~strike~~`, `[text](url)`) because the send-time classifier +/// re-parses the text through the rich (Apple CommonMark) path, not the entity +/// regex. +public func markdownStringFromInstantPage(_ instantPage: InstantPage) -> String { + return markdownString(from: instantPage.blocks) +} + +private func markdownString(from blocks: [InstantPageBlock]) -> String { + var pieces: [String] = [] + for block in blocks { + if let piece = markdownString(from: block) { + pieces.append(piece) + } + } + return pieces.joined(separator: "\n\n") +} + +private func markdownString(from block: InstantPageBlock) -> String? { + switch block { + case let .heading(text, level): + let hashes = String(repeating: "#", count: max(1, min(6, Int(level)))) + return "\(hashes) \(markdownInline(from: text))" + case let .title(text): + return "# \(markdownInline(from: text))" + case let .paragraph(text): + let rendered = markdownInline(from: text) + return rendered.isEmpty ? nil : escapeLeadingBlockMarker(rendered) + case let .preformatted(text, language): + let language = language ?? "" + // Code-fence body is raw text: the fences supply the code formatting, so do NOT run it + // through markdownInline (which re-wraps `.fixed` content in backticks and escapes markdown + // special chars). Use plainText to emit the literal source. + return "```\(language)\n\(text.plainText)\n```" + case let .blockQuote(blocks, _): + return markdownBlockQuoteBlocks(blocks) + case let .pullQuote(text, _): + return markdownBlockQuote(text) + case let .list(items, ordered): + return markdownList(items: items, ordered: ordered, indent: 0) + case let .table(_, rows, _, _): + return markdownTable(rows: rows) + case .divider: + return "---" + case let .formula(latex): + return "$\(latex)$" + case .anchor: + // Chat send already strips generated heading anchors; drop them here too. + return nil + case let .subtitle(text), let .header(text), let .subheader(text), let .footer(text), let .kicker(text): + let rendered = markdownInline(from: text) + return rendered.isEmpty ? nil : escapeLeadingBlockMarker(rendered) + default: + // Media and other structural blocks are skipped (out of scope). + return nil + } +} + +private func markdownBlockQuote(_ text: RichText) -> String { + let body = markdownInline(from: text) + let lines = body.split(separator: "\n", omittingEmptySubsequences: false) + return lines.map { "> \($0)" }.joined(separator: "\n") +} + +private func markdownBlockQuoteBlocks(_ blocks: [InstantPageBlock]) -> String { + var lines: [String] = [] + for block in blocks { + guard let body = markdownString(from: block) else { + continue + } + for line in body.split(separator: "\n", omittingEmptySubsequences: false) { + // Stack nested-quote markers without internal spaces (`>>` not `> >`): + // a line already starting with `>` (a nested quote) gets a bare `>`. + let text = String(line) + lines.append(text.hasPrefix(">") ? ">\(text)" : "> \(text)") + } + } + return lines.joined(separator: "\n") +} + +private func markdownInline(from richText: RichText) -> String { + switch richText { + case .empty: + return "" + case let .plain(string): + return escapeMarkdown(string) + case let .bold(text): + return "**\(markdownInline(from: text))**" + case let .italic(text): + return "*\(markdownInline(from: text))*" + case let .fixed(text): + return "`\(markdownInline(from: text))`" + case let .strikethrough(text): + return "~~\(markdownInline(from: text))~~" + case let .url(text, url, _): + return "[\(markdownInline(from: text))](\(url))" + case let .email(text, email): + return "[\(markdownInline(from: text))](mailto:\(email))" + case let .phone(text, phone): + return "[\(markdownInline(from: text))](tel:\(phone))" + case let .concat(parts): + return parts.map { markdownInline(from: $0) }.joined() + case let .anchor(text, _): + return markdownInline(from: text) + case let .formula(latex): + return "$\(latex)$" + // No CommonMark equivalent; emit inner text. These cannot arise from a + // markdown-composed message (Apple's markdown parser never produces them), + // so this is purely defensive. + case let .underline(text): + return markdownInline(from: text) + case let .marked(text): + return markdownInline(from: text) + case let .superscript(text): + return markdownInline(from: text) + case let .`subscript`(text): + return markdownInline(from: text) + case let .textCustomEmoji(fileId, alt): + return "[\(escapeCustomEmojiMarkdownAlt(alt))](\(customEmojiMarkdownURL(fileId: fileId)))" + default: + // .image and the entity cases (.textMention, .textHashtag, …): + // fall back to plain text. + return escapeMarkdown(richText.plainText) + } +} + +/// Backslash-escapes inline markdown-significant characters so literal text +/// does not re-parse as formatting. Pragmatic set, not full CommonMark. +private func escapeMarkdown(_ string: String) -> String { + var result = "" + result.reserveCapacity(string.count) + for character in string { + switch character { + case "\\", "*", "_", "`", "[", "]", "~", "|": + result.append("\\") + result.append(character) + default: + result.append(character) + } + } + return result +} + +/// Escapes a line-leading character that would otherwise be read as a block +/// marker (ATX heading, blockquote, or list item) when the text starts a line. +private func escapeLeadingBlockMarker(_ string: String) -> String { + guard let first = string.first else { + return string + } + if first == "#" || first == ">" || first == "-" || first == "+" { + return "\\" + string + } + // Ordered-list marker: one or more digits followed by '.' or ')'. + if first.isNumber { + var index = string.startIndex + while index < string.endIndex, string[index].isNumber { + index = string.index(after: index) + } + if index < string.endIndex, string[index] == "." || string[index] == ")" { + let prefix = string[string.startIndex ..< index] + let delimiter = string[index] + let rest = string[string.index(after: index)...] + return "\(prefix)\\\(delimiter)\(rest)" + } + } + return string +} + +/// Collapses newlines for a single-line GFM table cell. The `|` character is +/// already escaped by `escapeMarkdown` for `.plain` runs. +private func escapeTableCell(_ string: String) -> String { + return string.replacingOccurrences(of: "\n", with: " ") +} + +private func markdownList(items: [InstantPageListItem], ordered: Bool, indent: Int) -> String { + let indentString = String(repeating: " ", count: indent * 2) + var lines: [String] = [] + var index = 1 + for item in items { + // Ordered markers are regenerated from the running index (CommonMark renumbers + // anyway); the unordered marker is fixed. A task-list `checked` state is emitted + // as a GitHub task marker so re-classification on save re-parses it as a checkbox. + let listMarker = ordered ? "\(index). " : "- " + let taskMarker: String + switch item.checked { + case .some(false): + taskMarker = "[ ] " + case .some(true): + taskMarker = "[x] " + case .none: + taskMarker = "" + } + let marker = "\(listMarker)\(taskMarker)" + switch item { + case let .text(text, _, _): + lines.append("\(indentString)\(marker)\(markdownInline(from: text))") + case let .blocks(blocks, _, _): + var remainder = blocks + var markerLineText = "" + if case let .paragraph(text)? = remainder.first { + markerLineText = markdownInline(from: text) + remainder = Array(remainder.dropFirst()) + } + lines.append("\(indentString)\(marker)\(markerLineText)") + let childIndentString = String(repeating: " ", count: (indent + 1) * 2) + for block in remainder { + if case let .list(nestedItems, nestedOrdered) = block { + lines.append(markdownList(items: nestedItems, ordered: nestedOrdered, indent: indent + 1)) + } else if let rendered = markdownString(from: block) { + for line in rendered.split(separator: "\n", omittingEmptySubsequences: false) { + lines.append("\(childIndentString)\(line)") + } + } + } + case .unknown: + break + } + index += 1 + } + return lines.joined(separator: "\n") +} + +private func markdownTable(rows: [InstantPageTableRow]) -> String? { + guard !rows.isEmpty else { + return nil + } + let columnCount = rows.map { $0.cells.count }.max() ?? 0 + guard columnCount > 0 else { + return nil + } + + func renderRow(_ row: InstantPageTableRow) -> String { + var cellStrings: [String] = [] + for columnIndex in 0 ..< columnCount { + if columnIndex < row.cells.count, let text = row.cells[columnIndex].text { + cellStrings.append(escapeTableCell(markdownInline(from: text))) + } else { + cellStrings.append("") + } + } + return "| " + cellStrings.joined(separator: " | ") + " |" + } + + var lines: [String] = [] + lines.append(renderRow(rows[0])) + + var separators: [String] = [] + for columnIndex in 0 ..< columnCount { + let alignment: TableHorizontalAlignment + if columnIndex < rows[0].cells.count { + alignment = rows[0].cells[columnIndex].alignment + } else { + alignment = .left + } + switch alignment { + case .left: + separators.append("---") + case .center: + separators.append(":---:") + case .right: + separators.append("---:") + } + } + lines.append("| " + separators.joined(separator: " | ") + " |") + + for row in rows.dropFirst() { + lines.append(renderRow(row)) + } + return lines.joined(separator: "\n") +} diff --git a/submodules/BrowserUI/Sources/Utils.swift b/submodules/BrowserUI/Sources/Utils.swift index 71489f05a3..36de33b232 100644 --- a/submodules/BrowserUI/Sources/Utils.swift +++ b/submodules/BrowserUI/Sources/Utils.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TextFormat @@ -23,8 +22,6 @@ func fetchFavicon(context: AccountContext, url: String, size: CGSize) -> Signal< if let data { if let image = UIImage(data: data) { return image - } else if url.lowercased().contains(".svg"), let preparedData = prepareSvgImage(data, false), let image = renderPreparedImage(preparedData, size, .clear, UIScreenScale, false) { - return image } return nil } else { @@ -40,7 +37,7 @@ func fetchFavicon(context: AccountContext, url: String, size: CGSize) -> Signal< } } -func getPrimaryUrl(message: Message) -> String? { +func getPrimaryUrl(message: EngineMessage) -> String? { var primaryUrl: String? if let webPage = message.media.first(where: { $0 is TelegramMediaWebpage }) as? TelegramMediaWebpage, let url = webPage.content.url { primaryUrl = url diff --git a/submodules/CalendarMessageScreen/BUILD b/submodules/CalendarMessageScreen/BUILD index db198e2a0a..f91ea4c217 100644 --- a/submodules/CalendarMessageScreen/BUILD +++ b/submodules/CalendarMessageScreen/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/ComponentFlow:ComponentFlow", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index eb51c84e4b..28bf96d2a9 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -1868,7 +1868,7 @@ public final class CalendarMessageScreen: ViewController { self._hasGlassStyle = true self.navigationPresentation = .modal - self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false) + self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(dismissPressed)), animated: false) self.navigationItem.setTitle(self.presentationData.strings.MessageCalendar_Title, animated: false) if self.enableMessageRangeDeletion { @@ -1894,7 +1894,7 @@ public final class CalendarMessageScreen: ViewController { self.node.toggleSelectionMode() if self.node.selectionState != nil { - self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true) + self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true) } else { self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.toggleSelectPressed)), animated: true) } @@ -1904,7 +1904,7 @@ public final class CalendarMessageScreen: ViewController { self.node.selectDay(timestamp: timestamp) if self.node.selectionState != nil { - self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true) + self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true) } } diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index 507eeb5dd1..9f9b853ea7 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -785,7 +785,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { strongSelf.view.accessibilityCustomActions = [UIAccessibilityCustomAction(name: item.presentationData.strings.Common_Delete, target: strongSelf, selector: #selector(strongSelf.performLocalAccessibilityCustomAction(_:)))] - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) strongSelf.setRevealOptionsOpened(item.revealed, animated: animated) } }) diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 194c3c97da..97ec0f7cd9 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -213,7 +213,7 @@ public final class CallListController: TelegramBaseController { if let isEmpty = self.isEmpty, isEmpty { } else { if self.editingMode { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) } else { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) } @@ -222,7 +222,7 @@ public final class CallListController: TelegramBaseController { //self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed)) case .navigation: if self.editingMode { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) } else { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) } @@ -372,7 +372,7 @@ public final class CallListController: TelegramBaseController { TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).startStandalone(next: { peer in - if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .calls(messages: messages.map({ $0._asMessage() })), avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .calls(messages: messages), avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) } }) @@ -679,7 +679,7 @@ public final class CallListController: TelegramBaseController { switch self.mode { case .tab: - self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true) + self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)), animated: true) self.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true) self.navigationItem.rightBarButtonItem?.setCustomAction({ @@ -691,7 +691,7 @@ public final class CallListController: TelegramBaseController { pressedImpl?() }) - self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true) + self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)), animated: true) } self.controllerNode.updateState { state in diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index b4b90095fa..ca0a001822 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -940,7 +940,7 @@ final class CallListControllerNode: ASDisplayNode { insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top) let inset: CGFloat - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) } else { inset = 0.0 diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 6790ba59ec..b2fccc4bf1 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -146,7 +146,7 @@ private final class CameraContext { transform = CGAffineTransformTranslate(transform, 0.0, -size.height) ciImage = ciImage.transformed(by: transform) } - ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: Camera.isDualCameraSupported(forRoundVideo: true) ? 100.0 : 40.0).cropped(to: CGRect(origin: .zero, size: size)) + ciImage = ciImage.clampedToExtent().applyingGaussianBlur(sigma: Camera.isDualCameraSupported(forRoundVideo: true) ? 60.0 : 40.0).cropped(to: CGRect(origin: .zero, size: size)) if let cgImage = self.ciContext.createCGImage(ciImage, from: ciImage.extent) { let uiImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: .right) if front { @@ -189,6 +189,7 @@ private final class CameraContext { deinit { Logger.shared.log("CameraContext", "deinit") + NotificationCenter.default.removeObserver(self) } private var isSessionRunning = false @@ -202,7 +203,7 @@ private final class CameraContext { } func stopCapture(invalidate: Bool = false) { - Logger.shared.log("CameraContext", "startCapture(invalidate: \(invalidate))") + Logger.shared.log("CameraContext", "stopCapture(invalidate: \(invalidate))") if invalidate { self.mainDeviceContext?.device.resetZoom() @@ -212,6 +213,7 @@ private final class CameraContext { } self.session.session.stopRunning() + self.isSessionRunning = false } func focus(at point: CGPoint, autoFocus: Bool) { @@ -228,7 +230,7 @@ private final class CameraContext { } func setFps(_ fps: Float64) { - self.mainDeviceContext?.device.fps = fps + self.mainDeviceContext?.device.setFps(fps) } private var modeChange: Camera.ModeChange = .none { @@ -276,7 +278,6 @@ private final class CameraContext { self._positionPromise.set(targetPosition) self.modeChange = .position - let preferWide = self.initialConfiguration.preferWide || isRoundVideo let preferLowerFramerate = self.initialConfiguration.preferLowerFramerate || isRoundVideo @@ -565,6 +566,11 @@ private final class CameraContext { return .finished(mainImage, additionalImage, CACurrentMediaTime()) } } else { + if case .failed = main { + return .failed + } else if case .failed = additional { + return .failed + } return .began } } |> distinctUntilChanged @@ -583,6 +589,10 @@ private final class CameraContext { mainDeviceContext.device.setTorchMode(self._flashMode) } + let timestamp = CACurrentMediaTime() + 2.0 + self.lastSnapshotTimestamp = timestamp + self.lastAdditionalSnapshotTimestamp = timestamp + let orientation = self.simplePreviewView?.videoPreviewLayer.connection?.videoOrientation ?? .portrait if self.initialConfiguration.isRoundVideo { return mainDeviceContext.output.startRecording(mode: .roundVideo, orientation: DeviceModel.current.isIpad ? orientation : .portrait, additionalOutput: self.additionalDeviceContext?.output) @@ -788,6 +798,11 @@ public final class Camera { secondaryPreviewView.setSession(session.session, autoConnect: false) } + if #available(iOS 14.5, *), configuration.isRoundVideo { + AVCaptureDevice.centerStageControlMode = .app + AVCaptureDevice.isCenterStageEnabled = false + } + self.queue.async { let context = CameraContext(queue: self.queue, session: session, configuration: configuration, metrics: self.metrics, previewView: previewView, secondaryPreviewView: secondaryPreviewView) self.contextRef = Unmanaged.passRetained(context) @@ -801,6 +816,10 @@ public final class Camera { self.queue.async { contextRef?.release() } + + if #available(iOS 14.5, *) { + AVCaptureDevice.centerStageControlMode = .user + } } public func startCapture() { @@ -1131,7 +1150,7 @@ public final class Camera { public static func isDualCameraSupported(forRoundVideo: Bool = false) -> Bool { if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported && !DeviceModel.current.isIpad { - if forRoundVideo && DeviceModel.current == .iPhoneXR { + if forRoundVideo && (ProcessInfo.processInfo.isLowPowerModeEnabled || DeviceModel.current == .iPhoneXR) { return false } return true @@ -1170,6 +1189,7 @@ public struct CameraRecordingData { } public enum CameraRecordingError { + case videoRecorderInitializationError case audioInitializationError } diff --git a/submodules/Camera/Sources/CameraDevice.swift b/submodules/Camera/Sources/CameraDevice.swift index 6778c2dff5..c1d3eefbc6 100644 --- a/submodules/Camera/Sources/CameraDevice.swift +++ b/submodules/Camera/Sources/CameraDevice.swift @@ -141,10 +141,12 @@ final class CameraDevice { Logger.shared.log("Camera", "No format selected") } + #if DEBUG Logger.shared.log("Camera", "Available formats:") for format in device.formats { Logger.shared.log("Camera", format.description) } + #endif if let targetFPS = device.actualFPS(maxFramerate) { device.activeVideoMinFrameDuration = targetFPS.duration @@ -154,7 +156,7 @@ final class CameraDevice { if device.isLowLightBoostSupported { device.automaticallyEnablesLowLightBoostWhenAvailable = true } - + if device.isExposureModeSupported(.continuousAutoExposure) { device.exposureMode = .continuousAutoExposure } @@ -180,18 +182,16 @@ final class CameraDevice { self.setFocusPoint(CGPoint(x: 0.5, y: 0.5), focusMode: .continuousAutoFocus, exposureMode: .continuousAutoExposure, monitorSubjectAreaChange: false) } - var fps: Double = defaultFPS { - didSet { - guard let device = self.videoDevice, let targetFPS = device.actualFPS(Double(self.fps)) else { - return - } - - self.fps = targetFPS.fps - - self.transaction(device) { device in - device.activeVideoMinFrameDuration = targetFPS.duration - device.activeVideoMaxFrameDuration = targetFPS.duration - } + private(set) var fps: Double = defaultFPS + + func setFps(_ fps: Double) { + guard let device = self.videoDevice, let targetFPS = device.actualFPS(Double(fps)) else { + return + } + self.fps = targetFPS.fps + self.transaction(device) { device in + device.activeVideoMinFrameDuration = targetFPS.duration + device.activeVideoMaxFrameDuration = targetFPS.duration } } @@ -305,7 +305,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = max(device.neutralZoomFactor, min(10.0, device.neutralZoomFactor + zoomLevel)) + let target = device.neutralZoomFactor + zoomLevel + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } @@ -314,7 +315,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = max(1.0, min(10.0, device.videoZoomFactor * zoomDelta)) + let target = device.videoZoomFactor * zoomDelta + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } @@ -323,7 +325,8 @@ final class CameraDevice { return } self.transaction(device) { device in - device.ramp(toVideoZoomFactor: zoomLevel, withRate: Float(rate)) + let target = self.clampedZoomFactor(zoomLevel, for: device) + device.ramp(toVideoZoomFactor: target, withRate: Float(rate)) } } @@ -332,7 +335,14 @@ final class CameraDevice { return } self.transaction(device) { device in - device.videoZoomFactor = neutral ? device.neutralZoomFactor : device.minAvailableVideoZoomFactor + let target = neutral ? device.neutralZoomFactor : device.minAvailableVideoZoomFactor + device.videoZoomFactor = self.clampedZoomFactor(target, for: device) } } + + private func clampedZoomFactor(_ value: CGFloat, for device: AVCaptureDevice) -> CGFloat { + let minimum = max(1.0, device.minAvailableVideoZoomFactor) + let maximum = max(minimum, device.maxAvailableVideoZoomFactor) + return min(maximum, max(minimum, value)) + } } diff --git a/submodules/Camera/Sources/CameraInput.swift b/submodules/Camera/Sources/CameraInput.swift index 29adeca209..f9114e2db0 100644 --- a/submodules/Camera/Sources/CameraInput.swift +++ b/submodules/Camera/Sources/CameraInput.swift @@ -15,11 +15,14 @@ class CameraInput { } func invalidate(for session: CameraSession, switchAudio: Bool = true) { - for input in session.session.inputs { - if !switchAudio && input === self.audioInput { - continue - } - session.session.removeInput(input) + if let videoInput = self.videoInput, session.session.inputs.contains(where: { $0 === videoInput }) { + session.session.removeInput(videoInput) + } + self.videoInput = nil + + if switchAudio, let audioInput = self.audioInput, session.session.inputs.contains(where: { $0 === audioInput }) { + session.session.removeInput(audioInput) + self.audioInput = nil } } diff --git a/submodules/Camera/Sources/CameraOutput.swift b/submodules/Camera/Sources/CameraOutput.swift index a1f0a3fb1c..75dce41a14 100644 --- a/submodules/Camera/Sources/CameraOutput.swift +++ b/submodules/Camera/Sources/CameraOutput.swift @@ -93,6 +93,11 @@ public struct CameraCode: Equatable { } final class CameraOutput: NSObject { + private struct RoundVideoFormatDescriptionCacheEntry { + let sourceFormatDescription: CMFormatDescription + let outputFormatDescription: CMFormatDescription + } + let exclusive: Bool let ciContext: CIContext let colorSpace: CGColorSpace @@ -111,13 +116,14 @@ final class CameraOutput: NSObject { private var roundVideoFilter: CameraRoundLegacyVideoFilter? private let semaphore = DispatchSemaphore(value: 1) + private var roundVideoFormatDescriptionCache: [RoundVideoFormatDescriptionCacheEntry] = [] private let videoQueue = DispatchQueue(label: "", qos: .userInitiated) private let audioQueue = DispatchQueue(label: "") private let metadataQueue = DispatchQueue(label: "") - private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:] + private var photoCaptureRequests = Atomic<[Int64: PhotoCaptureContext]>(value: [:]) private var videoRecorder: VideoRecorder? private var captureOrientation: AVCaptureVideoOrientation = .portrait @@ -186,9 +192,9 @@ final class CameraOutput: NSObject { } if #available(iOS 13.0, *), session.hasMultiCam { - if let device = device.videoDevice, let ports = input.videoInput?.ports(for: AVMediaType.video, sourceDeviceType: device.deviceType, sourceDevicePosition: device.position) { + if let device = device.videoDevice, let ports = input.videoInput?.ports(for: AVMediaType.video, sourceDeviceType: device.deviceType, sourceDevicePosition: device.position), let firstPort = ports.first { if let previewView { - let previewConnection = AVCaptureConnection(inputPort: ports.first!, videoPreviewLayer: previewView.videoPreviewLayer) + let previewConnection = AVCaptureConnection(inputPort: firstPort, videoPreviewLayer: previewView.videoPreviewLayer) if session.session.canAddConnection(previewConnection) { session.session.addConnection(previewConnection) self.previewConnection = previewConnection @@ -268,8 +274,8 @@ final class CameraOutput: NSObject { return EmptyDisposable } subscriber.putNext(self.photoOutput.isFlashScene) - let observer = self.photoOutput.observe(\.isFlashScene, options: [.new], changeHandler: { device, _ in - subscriber.putNext(self.photoOutput.isFlashScene) + let observer = self.photoOutput.observe(\.isFlashScene, options: [.new], changeHandler: { output, _ in + subscriber.putNext(output.isFlashScene) }) return ActionDisposable { observer.invalidate() @@ -316,12 +322,20 @@ final class CameraOutput: NSObject { #else let uniqueId = settings.uniqueID let photoCapture = PhotoCaptureContext(ciContext: self.ciContext, settings: settings, orientation: orientation, mirror: mirror) - self.photoCaptureRequests[uniqueId] = photoCapture + let _ = self.photoCaptureRequests.modify { dict in + var dict = dict + dict[uniqueId] = photoCapture + return dict + } self.photoOutput.capturePhoto(with: settings, delegate: photoCapture) return photoCapture.signal |> afterDisposed { [weak self] in - self?.photoCaptureRequests.removeValue(forKey: uniqueId) + let _ = self?.photoCaptureRequests.modify { dict in + var dict = dict + dict.removeValue(forKey: uniqueId) + return dict + } } #endif } @@ -419,18 +433,21 @@ final class CameraOutput: NSObject { } } ) + guard let videoRecorder else { + return .fail(.videoRecorderInitializationError) + } - videoRecorder?.start() + videoRecorder.start() self.videoRecorder = videoRecorder if case .dualCamera = mode, let position { - videoRecorder?.markPositionChange(position: position, time: .zero) + videoRecorder.markPositionChange(position: position, time: .zero) } else if case .roundVideo = mode { additionalOutput?.masterOutput = self } return Signal { subscriber in - let timer = SwiftSignalKit.Timer(timeout: 0.033, repeat: true, completion: { [weak videoRecorder] in + let timer = SwiftSignalKit.Timer(timeout: 0.09, repeat: true, completion: { [weak videoRecorder] in let recordingData = CameraRecordingData(duration: videoRecorder?.duration ?? 0.0, filePath: outputFilePath) subscriber.putNext(recordingData) }, queue: Queue.mainQueue()) @@ -463,6 +480,43 @@ final class CameraOutput: NSObject { private var lastSampleTimestamp: CMTime? + private func roundVideoFormatDescription(for sourceFormatDescription: CMFormatDescription) -> CMFormatDescription? { + if let entry = self.roundVideoFormatDescriptionCache.first(where: { CFEqual($0.sourceFormatDescription, sourceFormatDescription) }) { + return entry.outputFormatDescription + } + + guard let extensions = CMFormatDescriptionGetExtensions(sourceFormatDescription) as? [String: Any] else { + return nil + } + + let mediaSubType = CMFormatDescriptionGetMediaSubType(sourceFormatDescription) + var updatedExtensions = extensions + updatedExtensions["CVBytesPerRow"] = videoMessageDimensions.width * 4 + + var outputFormatDescription: CMFormatDescription? + let status = CMVideoFormatDescriptionCreate( + allocator: nil, + codecType: mediaSubType, + width: videoMessageDimensions.width, + height: videoMessageDimensions.height, + extensions: updatedExtensions as CFDictionary, + formatDescriptionOut: &outputFormatDescription + ) + guard status == noErr, let outputFormatDescription else { + return nil + } + + self.roundVideoFormatDescriptionCache.append(RoundVideoFormatDescriptionCacheEntry( + sourceFormatDescription: sourceFormatDescription, + outputFormatDescription: outputFormatDescription + )) + if self.roundVideoFormatDescriptionCache.count > 4 { + self.roundVideoFormatDescriptionCache.removeFirst(self.roundVideoFormatDescriptionCache.count - 4) + } + + return outputFormatDescription + } + private var needsCrossfadeTransition = false private var crossfadeTransitionStart: Double = 0.0 @@ -564,17 +618,11 @@ final class CameraOutput: NSObject { return nil } self.semaphore.wait() - - let mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription) - let extensions = CMFormatDescriptionGetExtensions(formatDescription) as! [String: Any] - - var updatedExtensions = extensions - updatedExtensions["CVBytesPerRow"] = videoMessageDimensions.width * 4 - - var newFormatDescription: CMFormatDescription? - var status = CMVideoFormatDescriptionCreate(allocator: nil, codecType: mediaSubType, width: videoMessageDimensions.width, height: videoMessageDimensions.height, extensions: updatedExtensions as CFDictionary, formatDescriptionOut: &newFormatDescription) - guard status == noErr, let newFormatDescription else { + defer { self.semaphore.signal() + } + + guard let newFormatDescription = self.roundVideoFormatDescription(for: formatDescription) else { return nil } @@ -585,12 +633,11 @@ final class CameraOutput: NSObject { filter = CameraRoundLegacyVideoFilter(ciContext: self.ciContext, colorSpace: self.colorSpace, simple: self.exclusive) self.roundVideoFilter = filter } - if !filter.isPrepared { + if !filter.isPrepared || filter.inputFormatDescription.map({ !CFEqual($0, newFormatDescription) }) ?? true { filter.prepare(with: newFormatDescription, outputRetainedBufferCountHint: 4) } guard let newPixelBuffer = filter.render(pixelBuffer: videoPixelBuffer, additional: additional, captureOrientation: self.captureOrientation, transitionFactor: transitionFactor) else { - self.semaphore.signal() return nil } @@ -603,7 +650,7 @@ final class CameraOutput: NSObject { } var newSampleBuffer: CMSampleBuffer? - status = CMSampleBufferCreateForImageBuffer( + let status = CMSampleBufferCreateForImageBuffer( allocator: kCFAllocatorDefault, imageBuffer: newPixelBuffer, dataReady: true, @@ -615,10 +662,8 @@ final class CameraOutput: NSObject { ) if status == noErr, let newSampleBuffer { - self.semaphore.signal() return newSampleBuffer } - self.semaphore.signal() return nil } @@ -640,23 +685,23 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA guard CMSampleBufferDataIsReady(sampleBuffer) else { return } - - if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { - self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) - } else if sampleBuffer.type == kCMMediaType_Audio { - self.processAudioBuffer?(sampleBuffer) - } - + if let masterOutput = self.masterOutput { masterOutput.processVideoRecording(sampleBuffer, fromAdditionalOutput: true) } else { self.processVideoRecording(sampleBuffer, fromAdditionalOutput: false) } + + if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { + self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection) + } else if sampleBuffer.type == kCMMediaType_Audio { + self.processAudioBuffer?(sampleBuffer) + } } func captureOutput(_ output: AVCaptureOutput, didDrop sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { if #available(iOS 13.0, *) { - Logger.shared.log("VideoRecorder", "Dropped sample buffer \(sampleBuffer.attachments)") + Logger.shared.log("Camera", "Dropped sample buffer \(sampleBuffer.attachments)") } } } diff --git a/submodules/Camera/Sources/PhotoCaptureContext.swift b/submodules/Camera/Sources/PhotoCaptureContext.swift index c61fe73602..3e34fc5904 100644 --- a/submodules/Camera/Sources/PhotoCaptureContext.swift +++ b/submodules/Camera/Sources/PhotoCaptureContext.swift @@ -56,6 +56,7 @@ final class PhotoCaptureContext: NSObject, AVCapturePhotoCaptureDelegate { } else { guard let photoPixelBuffer = photo.pixelBuffer else { print("Error occurred while capturing photo: Missing pixel buffer (\(String(describing: error)))") + self.pipe.putNext(.failed) return } diff --git a/submodules/Camera/Sources/VideoRecorder.swift b/submodules/Camera/Sources/VideoRecorder.swift index a2d3a7cb83..e3772d446f 100644 --- a/submodules/Camera/Sources/VideoRecorder.swift +++ b/submodules/Camera/Sources/VideoRecorder.swift @@ -204,9 +204,7 @@ private final class VideoRecorderImpl { let maxDate = Date(timeIntervalSinceNow: 0.05) RunLoop.current.run(until: maxDate) } - } - if let videoInput = self.videoInput { let time = CACurrentMediaTime() // if let previousPresentationTime = self.previousPresentationTime, let previousAppendTime = self.previousAppendTime { // print("appending \(presentationTime.seconds) (\(presentationTime.seconds - previousPresentationTime) ) on \(time) (\(time - previousAppendTime)") @@ -234,7 +232,9 @@ private final class VideoRecorderImpl { } else if self.orientation == .portraitUpsideDown { orientation = .left } - self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation) + Queue.mainQueue().async { + self.transitionImage = UIImage(cgImage: cgImage, scale: 1.0, orientation: orientation) + } } else { self.savedTransitionImage = false } @@ -366,7 +366,7 @@ private final class VideoRecorderImpl { private func maybeFinish() { dispatchPrecondition(condition: .onQueue(self.queue)) - guard self.hasAllVideoBuffers && self.hasAllVideoBuffers && !self.stopped else { + guard self.hasAllVideoBuffers && (!self.configuration.hasAudio || self.hasAllAudioBuffers) && !self.stopped else { return } let _ = self._stopped.modify { _ in return true } @@ -377,21 +377,21 @@ private final class VideoRecorderImpl { dispatchPrecondition(condition: .onQueue(self.queue)) let completion = self.completion if self.recordingStopSampleTime == .invalid { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return } if let _ = self.error.with({ $0 }) { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return } if !self.tryAppendingPendingAudioBuffers() { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } return @@ -400,21 +400,21 @@ private final class VideoRecorderImpl { if self.assetWriter.status == .writing { self.assetWriter.finishWriting { if let _ = self.assetWriter.error { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } else { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(true, self.transitionImage, self.positionChangeTimestamps) } } } } else if let _ = self.assetWriter.error { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } else { - DispatchQueue.main.async { + Queue.mainQueue().async { completion(false, nil, nil) } } diff --git a/submodules/ChatImportUI/BUILD b/submodules/ChatImportUI/BUILD index 2ee541d7da..eca35fbd14 100644 --- a/submodules/ChatImportUI/BUILD +++ b/submodules/ChatImportUI/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AppBundle:AppBundle", "//third-party/ZipArchive:ZipArchive", diff --git a/submodules/ChatInterfaceState/BUILD b/submodules/ChatInterfaceState/BUILD index 4d2c0bdd17..c53e3d1d26 100644 --- a/submodules/ChatInterfaceState/BUILD +++ b/submodules/ChatInterfaceState/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TextFormat:TextFormat", "//submodules/AccountContext:AccountContext", diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index bd4ab384c5..a90930e9c5 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import TextFormat import AccountContext @@ -306,8 +305,8 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) - let resourceData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "r") - self.resource = LocalFileMediaResource(decoder: PostboxDecoder(buffer: MemoryBuffer(data: resourceData.data))) + let resourceData = try container.decode(EngineAdaptedPostboxDecoder.RawObjectData.self, forKey: "r") + self.resource = LocalFileMediaResource(decoder: EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: resourceData.data))) self.fileSize = try container.decode(Int32.self, forKey: "s") @@ -333,7 +332,7 @@ public enum ChatInterfaceMediaDraftState: Codable, Equatable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) - try container.encode(PostboxEncoder().encodeObjectToRawData(self.resource), forKey: "r") + try container.encode(EnginePostboxEncoder().encodeObjectToRawData(self.resource), forKey: "r") try container.encode(self.fileSize, forKey: "s") try container.encode(self.duration, forKey: "dd") try container.encode(self.waveform.samples, forKey: "wd") @@ -499,11 +498,11 @@ public final class ChatInterfaceState: Codable, Equatable { } public struct PostSuggestionState: Codable, Equatable { - public var editingOriginalMessageId: MessageId? + public var editingOriginalMessageId: EngineMessage.Id? public var price: CurrencyAmount? public var timestamp: Int32? - public init(editingOriginalMessageId: MessageId?, price: CurrencyAmount?, timestamp: Int32?) { + public init(editingOriginalMessageId: EngineMessage.Id?, price: CurrencyAmount?, timestamp: Int32?) { self.editingOriginalMessageId = editingOriginalMessageId self.price = price self.timestamp = timestamp diff --git a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift index f11b5a043f..7bdb6ddf5a 100644 --- a/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift +++ b/submodules/ChatListSearchRecentPeersNode/Sources/ChatListSearchRecentPeersNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import MergeLists import HorizontalPeerItem @@ -79,8 +78,7 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable { func item( accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + stateManager: AccountStateManager, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, @@ -96,8 +94,7 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable { strings: self.strings, mode: mode, accountPeerId: accountPeerId, - postbox: postbox, - network: network, + stateManager: stateManager, energyUsageSettings: energyUsageSettings, contentSettings: contentSettings, animationCache: animationCache, @@ -126,8 +123,7 @@ private struct ChatListSearchRecentNodeTransition { private func preparedRecentPeersTransition( accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + stateManager: AccountStateManager, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, @@ -148,8 +144,7 @@ private func preparedRecentPeersTransition( let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item( accountPeerId: accountPeerId, - postbox: postbox, - network: network, + stateManager: stateManager, energyUsageSettings: energyUsageSettings, contentSettings: contentSettings, animationCache: animationCache, @@ -162,8 +157,7 @@ private func preparedRecentPeersTransition( ), directionHint: .Down) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item( accountPeerId: accountPeerId, - postbox: postbox, - network: network, + stateManager: stateManager, energyUsageSettings: energyUsageSettings, contentSettings: contentSettings, animationCache: animationCache, @@ -204,8 +198,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { public init( accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + stateManager: AccountStateManager, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, @@ -238,7 +231,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { let peersDisposable = DisposableSet() - let recent: Signal<([EnginePeer], [EnginePeer.Id: (Int32, Bool)], [EnginePeer.Id : EnginePeer.Presence]), NoError> = _internal_recentPeers(accountPeerId: accountPeerId, postbox: postbox) + let recent: Signal<([EnginePeer], [EnginePeer.Id: (Int32, Bool)], [EnginePeer.Id : EnginePeer.Presence]), NoError> = _internal_recentPeers(accountPeerId: accountPeerId, postbox: stateManager.postbox) |> filter { value -> Bool in switch value { case .disabled: @@ -256,20 +249,20 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { peers.filter { !$0.isDeleted }.map { - postbox.peerView(id: $0.id) + stateManager.postbox.peerView(id: $0.id) } ) |> mapToSignal { peerViews -> Signal<([EnginePeer], [EnginePeer.Id: (Int32, Bool)], [EnginePeer.Id: EnginePeer.Presence]), NoError> in - return postbox.combinedView(keys: peerViews.map { item -> PostboxViewKey in - let key = PostboxViewKey.unreadCounts(items: [UnreadMessageCountsItem.peer(id: item.peerId, handleThreads: true)]) + return stateManager.postbox.combinedView(keys: peerViews.map { item -> EngineRawPostboxViewKey in + let key = EngineRawPostboxViewKey.unreadCounts(items: [EngineRawUnreadMessageCountsItem.peer(id: item.peerId, handleThreads: true)]) return key }) |> map { views -> [EnginePeer.Id: Int] in var result: [EnginePeer.Id: Int] = [:] for item in peerViews { - let key = PostboxViewKey.unreadCounts(items: [UnreadMessageCountsItem.peer(id: item.peerId, handleThreads: true)]) - - if let view = views.views[key] as? UnreadMessageCountsView { + let key = EngineRawPostboxViewKey.unreadCounts(items: [EngineRawUnreadMessageCountsItem.peer(id: item.peerId, handleThreads: true)]) + + if let view = views.views[key] as? EngineRawUnreadMessageCountsView { result[item.peerId] = Int(view.count(for: .peer(id: item.peerId, handleThreads: true)) ?? 0) } else { result[item.peerId] = 0 @@ -324,8 +317,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { let transition = preparedRecentPeersTransition( accountPeerId: accountPeerId, - postbox: postbox, - network: network, + stateManager: stateManager, energyUsageSettings: energyUsageSettings, contentSettings: contentSettings, animationCache: animationCache, @@ -345,7 +337,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode { } })) if case .actionSheet = mode { - peersDisposable.add(_internal_managedUpdatedRecentPeers(accountPeerId: accountPeerId, postbox: postbox, network: network).startStrict()) + peersDisposable.add(_internal_managedUpdatedRecentPeers(accountPeerId: accountPeerId, postbox: stateManager.postbox, network: stateManager.network).startStrict()) } self.disposable.set(peersDisposable) } diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index d3b4bd3af6..250bf011a9 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -30,6 +30,7 @@ swift_library( "//submodules/SearchBarNode:SearchBarNode", "//submodules/ChatListSearchRecentPeersNode:ChatListSearchRecentPeersNode", "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", + "//submodules/TelegramUI/Components/SectionTitleContextItem", "//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager", "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", "//submodules/PeerOnlineMarkerNode:PeerOnlineMarkerNode", @@ -125,9 +126,11 @@ swift_library( "//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode", "//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent", "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent", "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", "//submodules/TelegramUI/Components/AvatarComponent", "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index ef9229872e..0d0908f79e 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import ContextUI import AccountContext -import Postbox import TelegramCore import Display import TelegramUIPreferences @@ -17,11 +16,11 @@ import TelegramStringFormatting import ChatTimerScreen import NotificationPeerExceptionController -func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chatListController: ChatListControllerImpl?) -> Signal<[ContextMenuItem], NoError> { +func archiveContextMenuItems(context: AccountContext, group: EngineChatList.Group, chatListController: ChatListControllerImpl?) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings return combineLatest( - context.engine.messages.unreadChatListPeerIds(groupId: EngineChatList.Group(groupId), filterPredicate: nil), + context.engine.messages.unreadChatListPeerIds(groupId: group, filterPredicate: nil), context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.chatArchiveSettings) ) @@ -31,7 +30,7 @@ func archiveContextMenuItems(context: AccountContext, groupId: PeerGroupId, chat if !unreadChatListPeerIds.isEmpty { items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAllAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = (context.engine.messages.markAllChatsAsReadInteractively(items: [(groupId: EngineChatList.Group(groupId), filterPredicate: nil)]) + let _ = (context.engine.messages.markAllChatsAsReadInteractively(items: [(groupId: group, filterPredicate: nil)]) |> deliverOnMainQueue).startStandalone(completed: { f(.default) }) @@ -54,7 +53,7 @@ enum ChatContextMenuSource { case search(ChatListSearchContextActionSource) } -func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: ChatListNodeEntryPromoInfo?, source: ChatContextMenuSource, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { +func chatContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, promoInfo: ChatListNodeEntryPromoInfo?, source: ChatContextMenuSource, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings @@ -331,7 +330,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } return filters }).startStandalone() - chatListController?.present(UndoOverlayController( presentationData: presentationData, content: .chatAddedToFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in + chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) }) @@ -363,7 +362,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } - let archiveEnabled = !isSavedMessages && peerId != PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(777000)) && peerId == context.account.peerId + let archiveEnabled = !isSavedMessages && peerId != EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(777000)) && peerId == context.account.peerId if let group = peerGroup { if archiveEnabled { let isArchived = group == .archive @@ -504,14 +503,26 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch joinChannelDisposable.set(nil) } + var didJoin = false joinChannelDisposable.set((createSignal - |> deliverOnMainQueue).start(next: { _ in + |> deliverOnMainQueue).start(next: { result in + switch result { + case .joined: + didJoin = true + case let .webView(webView): + if let chatListController = chatListController { + context.sharedContext.openJoinChatWebView(context: context, parentController: chatListController, updatedPresentationData: nil, webView: webView) + } + } }, error: { _ in if let chatListController = chatListController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } chatListController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } }, completed: { + if !didJoin { + return + } let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).startStandalone(next: { peer in guard let peer = peer else { @@ -587,7 +598,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } -public func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ViewController?, joined: Bool, canSelect: Bool, customEdit: ((ContextController) -> Void)? = nil, customPinUnpin: ((ContextController) -> Void)? = nil, reorder: (() -> Void)? = nil, onDeleted: (() -> Void)? = nil) -> Signal<[ContextMenuItem], NoError> { +public func chatForumTopicMenuItems(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ViewController?, joined: Bool, canSelect: Bool, customEdit: ((ContextController) -> Void)? = nil, customPinUnpin: ((ContextController) -> Void)? = nil, reorder: (() -> Void)? = nil, onDeleted: (() -> Void)? = nil) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings @@ -822,30 +833,30 @@ public func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, thr TelegramEngine.EngineData.Item.NotificationSettings.Global() ) |> deliverOnMainQueue).startStandalone(next: { globalSettings in - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue } - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + let updatePeerNotificationInterval: (EnginePeer.Id, Int32?) -> Signal = { peerId, muteInterval in return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue } - let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { + let updatePeerDisplayPreviews: (EnginePeer.Id, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue } - let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { + let updatePeerStoriesMuted: (EnginePeer.Id, PeerStoryNotificationSettings.Mute) -> Signal = { peerId, mute in return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue } - let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { + let updatePeerStoriesHideSender: (EnginePeer.Id, PeerStoryNotificationSettings.HideSender) -> Signal = { peerId, hideSender in return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue } - let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerStorySound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue } @@ -979,11 +990,9 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, return combineLatest( context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(threadId)) + TelegramEngine.EngineData.Item.Peer.Peer(id: EnginePeer.Id(threadId)) ), - context.account.postbox.transaction { transaction -> [Int64] in - return transaction.getPeerPinnedThreads(peerId: context.account.peerId) - } + context.engine.peers.getForumChannelPinnedTopics(id: context.account.peerId) ) |> mapToSignal { [weak parentController] peer, pinnedThreadIds -> Signal<[ContextMenuItem], NoError> in var items: [ContextMenuItem] = [] @@ -1014,7 +1023,7 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, }))) items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in - deletePeerChat(PeerId(threadId)) + deletePeerChat(EnginePeer.Id(threadId)) f(.default) }))) @@ -1023,7 +1032,7 @@ public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, } private func openCustomMute(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, baseController: ViewController) { - let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in + let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .mute, currentTime: nil, completion: { [weak baseController] value in let presentationData = context.sharedContext.currentPresentationData.with { $0 } if value <= 0 { diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index 08b15d55bb..0d77a965ca 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -9,7 +9,6 @@ import SwiftSignalKit import AnimationCache import MultiAnimationRenderer import TelegramCore -import Postbox import ChatListHeaderComponent import ActionPanelComponent import ChatFolderLinkPreviewScreen diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 3ce4e72602..afb7a667c8 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -59,6 +59,9 @@ import ChatListFilterTabContainerNode import HeaderPanelContainerComponent import HorizontalTabsComponent import GlobalControlPanelsContext +import AlertComponent +import AlertHeaderComponent +import AvatarComponent private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController @@ -163,6 +166,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return false } } + + private var foldersCount: Int32 { + guard let tabContainerData = self.tabContainerData else { + return 0 + } + return Int32(tabContainerData.0.count(where: { entry in + if case .filter = entry { + return true + } else { + return false + } + })) + } private var hasDownloads: Bool = false private var activeDownloadsDisposable: Disposable? @@ -459,7 +475,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController var entry: FetchManagerEntrySummary var isRemoved: Bool = false var statusDisposable: Disposable? - var status: MediaResourceStatus? + var status: EngineMediaResource.FetchStatus? init(entry: FetchManagerEntrySummary) { self.entry = entry @@ -509,7 +525,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if context.statusDisposable == nil { - context.statusDisposable = (engine.account.postbox.mediaBox.resourceStatus(context.entry.resourceReference.resource) + context.statusDisposable = (engine.resources.status(resource: EngineMediaResource(context.entry.resourceReference.resource)) |> deliverOn(self.queue)).startStrict(next: { [weak self, weak context] status in guard let strongSelf = self, let context = context else { return @@ -736,9 +752,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.reloadFilters() } - self.storiesPostingAvailabilityDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.storiesPostingAvailabilityDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged @@ -1015,7 +1031,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if isDisabled { let context = self.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(self.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: self.foldersCount, action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -1059,7 +1075,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if isDisabled { let context = self.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(self.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: self.foldersCount, action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -1388,12 +1404,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let _ = (combineLatest(queue: .mainQueue(), - self.context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peer.id)]) - |> take(1), + self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.CachedData(id: peer.id)), forumSourcePeer ) - |> deliverOnMainQueue).start(next: { [weak self] combinedView, forumSourcePeer in - guard let self, let cachedDataView = combinedView.views[.cachedPeerData(peerId: peer.id)] as? CachedPeerDataView else { + |> deliverOnMainQueue).start(next: { [weak self] cachedPeerData, forumSourcePeer in + guard let self else { return } guard let navigationController = self.navigationController as? NavigationController else { @@ -1419,7 +1434,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else if case let .channel(channel) = peer, channel.flags.contains(.displayForumAsTabs) { openAsInlineForum = false } else { - if let cachedData = cachedDataView.cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages { + if let cachedData = cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages { openAsInlineForum = false } } @@ -1870,7 +1885,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController case let .groupReference(groupReference): let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .chatList(groupId: groupReference.groupId), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) chatListController.navigationPresentation = .master - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupReference.groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, group: groupReference.groupId, chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) case let .peer(peerData): let peer = peerData.peer @@ -1888,16 +1903,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } else { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) chatListController.navigationPresentation = .master - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } else if let peer = peer.peer, peer.id == strongSelf.context.account.peerId, peerData.displayAsTopicList { - if let peerInfoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let peerInfoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { let source: ContextContentSource if let location = location { source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location)) @@ -1905,7 +1920,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController source = .controller(ContextControllerContentSourceImpl(controller: peerInfoController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) } - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } else { @@ -1957,7 +1972,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } @@ -1992,7 +2007,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case let .channel(channel) = peer, channel.isForumOrMonoForum { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) chatListController.navigationPresentation = .master - let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = makeContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } else { let contextContentSource: ContextContentSource @@ -2377,7 +2392,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let context = strongSelf.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(strongSelf.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.foldersCount, action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -2698,11 +2713,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if !self.processedFeaturedFilters { - let initializedFeatured = self.context.account.postbox.preferencesView(keys: [ - PreferencesKeys.chatListFiltersFeaturedState - ]) + let initializedFeatured = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.chatListFiltersFeaturedState)) |> mapToSignal { view -> Signal in - if let entry = view.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) { + if let entry = view?.get(ChatListFiltersFeaturedState.self) { return .single(!entry.filters.isEmpty && !entry.isSeen) } else { return .complete() @@ -3295,7 +3308,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let self else { return } - guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } (self.navigationController as? NavigationController)?.pushViewController(controller) @@ -3303,7 +3316,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) }))) - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) items.append(.action(ContextMenuActionItem(text: isMuted ? self.presentationData.strings.StoryFeed_ContextNotifyOn : self.presentationData.strings.StoryFeed_ContextNotifyOff, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -3781,7 +3794,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).startStandalone(next: { peer in - guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let sourceController = sourceController, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } (sourceController.navigationController as? NavigationController)?.pushViewController(controller) @@ -3801,7 +3814,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let selectAddMemberDisposable = MetaDisposable() let addMemberDisposable = MetaDisposable() - context.sharedContext.openAddPeerMembers(context: context, updatedPresentationData: nil, parentController: sourceController, groupPeer: peer._asPeer(), selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable) + context.sharedContext.openAddPeerMembers(context: context, updatedPresentationData: nil, parentController: sourceController, groupPeer: peer, selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable) }) }))) } @@ -3953,13 +3966,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController resolvedItems = [] } - var wasEmpty = false - if let tabContainerData = strongSelf.tabContainerData { - wasEmpty = tabContainerData.0.count <= 1 || tabContainerData.1 - } else { - wasEmpty = true - } - let firstItem = countAndFilterItems.1.first?.0 ?? .allChats let firstItemEntryId: ChatListFilterTabEntryId switch firstItem { @@ -4028,17 +4034,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.initializedFilters = true } - let isEmpty = resolvedItems.count <= 1 - let animated = strongSelf.didSetupTabs strongSelf.didSetupTabs = true if let layout = strongSelf.validLayout { - if wasEmpty != isEmpty { - let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate - strongSelf.containerLayoutUpdated(layout, transition: transition) - (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) - } + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate + strongSelf.containerLayoutUpdated(layout, transition: transition) + (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) } if !notifiedFirstUpdate { @@ -4485,11 +4487,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController guard let self else { return } - guard let filter = filters.first(where: { $0.id == id }) else { + guard let filter = filters.first(where: { $0.id == id }), case let .filter(_, title, _, data) = filter else { return } - if case let .filter(_, title, _, data) = filter, data.isShared { + if data.isShared { let _ = (combineLatest( self.context.engine.data.get( EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))), @@ -4563,7 +4565,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { confirmDeleteFolder() }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }) ]), in: .window(.root)) } else { @@ -4571,24 +4573,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } }) } else { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation), - ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - apply() - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) + let alertController = textAlertController(context: context, title: self.presentationData.strings.ChatList_RemoveFolderConfirmationTitle(title.text).string, text: self.presentationData.strings.ChatList_RemoveFolderConfirmation, actions: [ + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.ChatList_RemoveFolderAction, action: { + apply() + }) ]) - self.present(actionSheet, in: .window(.root)) + self.present(alertController, in: .window(.root)) } }) } @@ -5200,15 +5192,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController |> delay(0.8, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - let signal: Signal = self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peerId, hash: nil) + let signal: Signal = self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peerId, hash: nil) |> afterDisposed { Queue.mainQueue().async { progressDisposable.dispose() } } + var didJoin = false self.joinForumDisposable.set((signal - |> deliverOnMainQueue).startStrict(error: { [weak self] error in + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self else { + return + } + switch result { + case .joined: + didJoin = true + case let .webView(webView): + self.context.sharedContext.openJoinChatWebView(context: self.context, parentController: self, updatedPresentationData: nil, webView: webView) + } + }, error: { [weak self] error in guard let strongSelf = self else { return } @@ -5242,36 +5245,39 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, completed: { [weak self] in - guard let self else { - return - } - Queue.mainQueue().after(0.5) { - let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let peer = peer?._asPeer() else { - return - } - var canEditRank = false - if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.editRank) { - canEditRank = true - } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { - canEditRank = true - } - if canEditRank { - let context = self.context - let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Chat_JoinedGroup_Text, cancel: self.presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { [weak self] action in - if let self, case .undo = action { - let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil, role: .member) - self.push(tagController) - } - return true - }) - self.present(controller, in: .current) - } - }) - } }) + }, completed: { [weak self] in + guard let self else { + return + } + if !didJoin { + return + } + Queue.mainQueue().after(0.5) { + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + guard let self, let peer = peer?._asPeer() else { + return + } + var canEditRank = false + if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.editRank) { + canEditRank = true + } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { + canEditRank = true + } + if canEditRank { + let context = self.context + let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Chat_JoinedGroup_Text, cancel: self.presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { [weak self] action in + if let self, case .undo = action { + let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil, role: .member) + self.push(tagController) + } + return true + }) + self.present(controller, in: .current) + } + }) + } })) case .savedMessagesChats: break @@ -5376,21 +5382,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case .broadcast = channel.info { canClear = false deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel - if channel.addressName == nil && channel.flags.contains(.isCreator) { - canRemoveGlobally = true - } } else { deleteTitle = strongSelf.presentationData.strings.Group_DeleteGroup - if channel.addressName == nil && channel.flags.contains(.isCreator) { - canRemoveGlobally = true - } + } + if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) { + canRemoveGlobally = true } if let addressName = channel.addressName, !addressName.isEmpty { canClear = false } } - } else if case let .legacyGroup(group) = chatPeer { - if case .creator = group.role { + } else if case .legacyGroup = chatPeer { + if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) { canRemoveGlobally = true } } else if case let .user(user) = chatPeer, user.botInfo != nil { @@ -5843,30 +5846,32 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController canRemoveGlobally = true } + if deleteGloballyIfPossible && self.canDeletePeerGloballyAsCreator(mainPeer) { + self.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: true, completion: { + removed() + }) + completion(true) + return + } + if canRemoveGlobally { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - var items: [ActionSheetItem] = [] - - items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder)) - + var actions: [AlertScreen.Action] = [] if joined || mainPeer.isDeleted { - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() + actions.append(.init(title: self.presentationData.strings.Common_Delete, type: .defaultDestructive, action: { [weak self] in self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { removed() }) completion(true) })) } else { - items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() + actions.append(.init(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, type: .destructive, action: { [weak self] in self?.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { removed() }) completion(true) })) - items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() + + actions.append(.init(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, type: .destructive, action: { [weak self] in guard let strongSelf = self else { return } @@ -5883,16 +5888,56 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ], parseMarkdown: true), in: .window(.root)) })) } - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - completion(false) - }) - ]) - ]) - self.present(actionSheet, in: .window(.root)) + actions.append(.init(title: self.presentationData.strings.Common_Cancel)) + + let title: String = self.presentationData.strings.ChatList_DeleteChat + var text: String + if mainPeer.id == self.context.account.peerId { + text = self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmation + } else if case let .legacyGroup(chatPeer) = mainPeer { + text = self.presentationData.strings.ChatList_LeaveGroupConfirmation("**\(chatPeer.title)**").string + } else if case let .channel(chatPeer) = mainPeer { + text = self.presentationData.strings.ChatList_LeaveGroupConfirmation("**\(chatPeer.title)**").string + } else if case .secretChat = chatPeer { + text = self.presentationData.strings.ChatList_DeleteSecretChatConfirmation("**\(chatPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))**").string + } else { + text = self.presentationData.strings.ChatList_DeleteChatConfirmation("**\(chatPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))**").string + } + + let alertScreen = AlertScreen( + context: self.context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: [ + AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertHeaderComponent( + component: AnyComponentWithIdentity(id: "user", component: AnyComponent( + AvatarComponent( + context: self.context, + theme: self.presentationData.theme, + peer: mainPeer + ) + )) + ) + ) + ), + AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: title) + ) + ), + AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + ) + ], + actions: actions + ) + self.present(alertScreen, in: .window(.root)) } else if peer.peerId == self.context.account.peerId { self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { @@ -5946,6 +5991,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } + private func canDeletePeerGloballyAsCreator(_ peer: EnginePeer) -> Bool { + if case let .channel(channel) = peer { + return !channel.isMonoForum && channel.flags.contains(.isCreator) && channel.addressName == nil + } else if case let .legacyGroup(group) = peer, case .creator = group.role { + return true + } else { + return false + } + } + func archiveChats(peerIds: [PeerId]) { guard !peerIds.isEmpty else { return @@ -6127,7 +6182,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case .chatList(.root) = self.chatListDisplayNode.mainContainerNode.location { super.setToolbar(toolbar, transition: transition) } else { - self.chatListDisplayNode.toolbar = toolbar + self.chatListDisplayNode.toolbarData = toolbar self.requestLayout(transition: transition) } } @@ -6251,7 +6306,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if isDisabled { let context = strongSelf.context var replaceImpl: ((ViewController) -> Void)? - let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(strongSelf.tabContainerData?.0.count ?? 0), action: { + let controller = PremiumLimitScreen(context: context, subject: .folders, count: strongSelf.foldersCount, action: { let controller = PremiumIntroScreen(context: context, source: .folders) replaceImpl?(controller) return true @@ -6833,7 +6888,7 @@ private final class ChatListLocationContext { return (nil, value) } } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId) |> map { value -> (total: Int32?, recent: Int32?) in return (value.total, value.recent) } @@ -7047,6 +7102,7 @@ private final class ChatListLocationContext { if case .chatList(.root) = self.location { self.rightButton = nil self.storyButton = nil + self.proxyButton = nil } let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle @@ -7062,6 +7118,7 @@ private final class ChatListLocationContext { if case .chatList(.root) = self.location { self.rightButton = nil self.storyButton = nil + self.proxyButton = nil } self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( content: .text(title: presentationData.strings.Common_Done, isBold: true), @@ -7287,7 +7344,7 @@ private final class ChatListLocationContext { dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, displayBackground: false, - content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), + content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, hidePeerStatus: false, isEnabled: true), activities: nil, networkState: nil, tapped: { [weak self] in @@ -7298,7 +7355,7 @@ private final class ChatListLocationContext { TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in - guard let self, let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let self, let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } (self.parentController?.navigationController as? NavigationController)?.pushViewController(controller) @@ -7389,3 +7446,25 @@ private final class AdsInfoContextReferenceContentSource: ContextReferenceConten return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) } } + +public struct ChatListNavigationTarget { + public let chatListController: ChatListControllerImpl + public let popToController: ViewController? +} + +public func resolveChatListNavigationTarget(navigationController: NavigationController, excluding excludedController: ViewController? = nil) -> ChatListNavigationTarget? { + for case let controller as ViewController in navigationController.viewControllers.reversed() { + if let excludedController, controller === excludedController { + continue + } + if let chatListController = controller as? ChatListControllerImpl, !chatListController.previewing { + return ChatListNavigationTarget(chatListController: chatListController, popToController: controller) + } + } + + if let controller = navigationController.viewControllers.first as? TabBarController, let chatListController = controller.currentController as? ChatListControllerImpl { + return ChatListNavigationTarget(chatListController: chatListController, popToController: nil) + } + + return nil +} diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 8281e29585..61cbefe701 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -28,6 +28,7 @@ import MediaPlaybackHeaderPanelComponent import LiveLocationHeaderPanelComponent import ChatListHeaderNoticeComponent import ChatListFilterTabContainerNode +import GlassControls public enum ChatListContainerNodeFilter: Equatable { case all @@ -1134,8 +1135,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { let navigationBarView = ComponentView() weak var controller: ChatListControllerImpl? - var toolbar: Toolbar? - private var toolbarNode: ToolbarNode? + private var toolbar: ComponentView? + var toolbarData: Toolbar? var toolbarActionSelected: ((ToolbarActionOption) -> Void)? private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch? @@ -1395,10 +1396,6 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.mainContainerNode.updatePresentationData(presentationData) self.inlineStackContainerNode?.updatePresentationData(presentationData) self.searchDisplayController?.updatePresentationData(presentationData) - - if let toolbarNode = self.toolbarNode { - toolbarNode.updateTheme(ToolbarTheme(rootControllerTheme: self.presentationData.theme)) - } } private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: ComponentTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { @@ -1435,6 +1432,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift(peers, birthdays) case .reviewLogin: break + case .reviewBotConnection: + break case let .starsSubscriptionLowBalance(amount, _): self.effectiveContainerNode.currentItemNode.interaction?.openStarsTopup(amount.value) case .setupPhoto: @@ -1458,6 +1457,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { switch notice { case let .reviewLogin(newSessionReview, _): self.effectiveContainerNode.currentItemNode.interaction?.performActiveSessionAction(newSessionReview, isPositive) + case let .reviewBotConnection(newBotConnectionReview, _, _): + self.effectiveContainerNode.currentItemNode.interaction?.performBotConnectionReviewAction(newBotConnectionReview, isPositive) default: break } @@ -1504,6 +1505,24 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if self.controller?.tabContainerData != nil || !panels.isEmpty { var tabs: AnyComponent? if let tabContainerData = self.controller?.tabContainerData, tabContainerData.0.count > 1 { + let folderFilterIndex: (ChatListFilterTabEntryId, [ChatListFilterTabEntry]) -> Int? = { id, entries in + var index = 0 + for entry in entries { + switch entry { + case .all: + if entry.id == id { + return nil + } + case .filter: + if entry.id == id { + return index + } + index += 1 + } + } + return nil + } + let selectedTab: HorizontalTabsComponent.Tab.Id switch self.effectiveContainerNode.currentItemFilter { case .all: @@ -1553,10 +1572,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { var isDisabled = false if let filtersLimit = tabContainerData.2 { - guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == mappedId }) else { - return + if let folderIndex = folderFilterIndex(mappedId, tabContainerData.0) { + isDisabled = !isPremium && folderIndex >= filtersLimit } - isDisabled = !isPremium && folderIndex >= filtersLimit } if isDisabled { @@ -1599,10 +1617,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { var isDisabled = false if let filtersLimit = tabContainerData.2 { - guard let folderIndex = tabContainerData.0.firstIndex(where: { $0.id == entry.id }) else { - return + if let folderIndex = folderFilterIndex(entry.id, tabContainerData.0) { + isDisabled = !isPremium && folderIndex >= filtersLimit } - isDisabled = !isPremium && folderIndex >= filtersLimit } self.controller?.tabContextGesture(id: mappedId, sourceNode: nil, sourceView: sourceView, gesture: gesture, keepInPlace: false, isDisabled: isDisabled) @@ -1822,53 +1839,101 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { insets.left += layout.safeInsets.left insets.right += layout.safeInsets.right - if let toolbar = self.toolbar { - var tabBarHeight: CGFloat - var options: ContainerViewLayoutInsetOptions = [] - if layout.metrics.widthClass == .regular { - options.insert(.input) + if let toolbarData = self.toolbarData { + var panelsBottomInset: CGFloat = layout.insets(options: []).bottom + if layout.metrics.widthClass == .regular, let inputHeight = layout.inputHeight, inputHeight != 0.0 { + panelsBottomInset = inputHeight + 8.0 } - - var heightInset: CGFloat = 0.0 - if case .forum = self.location { - heightInset = 4.0 - } - - let bottomInset: CGFloat = layout.insets(options: options).bottom - if !layout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset - insets.bottom += 34.0 + if panelsBottomInset == 0.0 { + panelsBottomInset = 8.0 } else { - tabBarHeight = 49.0 - heightInset + bottomInset - insets.bottom += 49.0 - heightInset + panelsBottomInset = max(panelsBottomInset, 8.0) } - let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) + let sideInset: CGFloat = 20.0 + let toolbarHeight = 44.0 + let toolbarFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelsBottomInset - toolbarHeight), size: CGSize(width: layout.size.width - sideInset * 2.0, height: toolbarHeight)) - if let toolbarNode = self.toolbarNode { - transition.updateFrame(node: toolbarNode, frame: toolbarFrame) - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition) + let toolbar: ComponentView + var toolbarTransition = ComponentTransition(transition) + if let current = self.toolbar { + toolbar = current } else { - let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in - self?.toolbarActionSelected?(.left) - }, right: { [weak self] in - self?.toolbarActionSelected?(.right) - }, middle: { [weak self] in - self?.toolbarActionSelected?(.middle) - }) - toolbarNode.frame = toolbarFrame - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) - self.addSubnode(toolbarNode) - self.toolbarNode = toolbarNode - if transition.isAnimated { - toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + toolbar = ComponentView() + self.toolbar = toolbar + toolbarTransition = .immediate + } + + let _ = toolbar.update( + transition: toolbarTransition, + component: AnyComponent(GlassControlPanelComponent( + theme: self.presentationData.theme, + leftItem: toolbarData.leftAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "left_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected?(.left) + } : nil + )], + background: .panel + ) + }, + centralItem: toolbarData.middleAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected?(.middle) + } : nil + )], + background: .panel + ) + }, + rightItem: toolbarData.rightAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self else { + return + } + self.toolbarActionSelected?(.right) + } : nil + )], + background: .panel + ) + }, + centerAlignmentIfPossible: true + )), + environment: {}, + containerSize: toolbarFrame.size + ) + + if let toolbarView = toolbar.view { + if toolbarView.superview == nil { + self.view.addSubview(toolbarView) + toolbarView.alpha = 0.0 } + toolbarTransition.setFrame(view: toolbarView, frame: toolbarFrame) + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 1.0) + } + } else if let toolbar = self.toolbar { + self.toolbar = nil + if let toolbarView = toolbar.view { + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 0.0, completion: { [weak toolbarView] _ in + toolbarView?.removeFromSuperview() + }) } - } else if let toolbarNode = self.toolbarNode { - self.toolbarNode = nil - transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in - toolbarNode?.removeFromSupernode() - }) } var childrenLayout = layout @@ -1986,6 +2051,9 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { contentNode.dismissSearch = { [weak self] in self?.dismissSearch?() } + contentNode.dismissSearchImmediately = { [weak self] in + self?.controller?.deactivateSearch(animated: false) + } contentNode.openAdInfo = { [weak self] node, adPeer in self?.controller?.openAdInfo(node: node, adPeer: adPeer) } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift index 6b75d9a15d..98fd3fb9f6 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift @@ -21,7 +21,7 @@ enum ChatListFilterCategoryIcon { case archived } -final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { +final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle let title: String @@ -31,6 +31,10 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { let sectionId: ItemListSectionId let updatedRevealedOptions: (Bool) -> Void let remove: () -> Void + + var hasActiveRevealOptions: Bool { + return self.isRevealed + } init( presentationData: ItemListPresentationData, @@ -110,6 +114,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL private var item: ChatListFilterPresetCategoryItem? private var layoutParams: ListViewItemLayoutParams? + private var isHighlighted = false private var editableControlNode: ItemListEditableControlNode? @@ -177,7 +182,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL var titleAttributedString: NSAttributedString? let peerRevealOptions: [ItemListRevealOption] - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] let rightInset: CGFloat = params.rightInset @@ -326,26 +331,29 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -362,10 +370,9 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL strongSelf.avatarNode.image = updatedAvatarImage } - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition) strongSelf.backgroundNode.isHidden = false - strongSelf.highlightedBackgroundNode.isHidden = true strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -379,39 +386,8 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -446,6 +422,12 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size)) } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } override func revealOptionsInteractivelyOpened() { if let item = self.item { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index d85f03cf88..54e099ffe6 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -1281,7 +1281,6 @@ private final class ChatListFilterPresetController: ItemListController { mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -2072,11 +2071,16 @@ public func chatListFilterPresetController(context: AccountContext, currentPrese ) |> deliverOnMainQueue |> map { presentationData, stateWithPeers, peerView, premiumLimits, sharedLinks, currentPreset -> (ItemListControllerState, (ItemListNodeState, Any)) in + var presentationData = presentationData + + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + let (state, includePeers, excludePeers) = stateWithPeers let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { if let attemptNavigationImpl { attemptNavigationImpl({ value in if value { @@ -2087,7 +2091,7 @@ public func chatListFilterPresetController(context: AccountContext, currentPrese dismissImpl?() } }) - let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: state.isComplete, action: { applyImpl?(false, { dismissImpl?() }) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 2d595caf56..c37f487cdb 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -461,13 +461,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch let _ = (context.engine.peers.currentChatListFilters() |> take(1) |> deliverOnMainQueue).start(next: { filters in - guard let filter = filters.first(where: { $0.id == id }) else { + guard let filter = filters.first(where: { $0.id == id }), case let .filter(_, title, _, data) = filter else { return } let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if case let .filter(_, title, _, data) = filter, data.isShared { + if data.isShared { let _ = (combineLatest( context.engine.data.get( EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))), @@ -539,31 +539,21 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch } }) } else { - let actionSheet = ActionSheetController(presentationData: presentationData) - - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation), - ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in - var filters = filters - if let index = filters.firstIndex(where: { $0.id == id }) { - filters.remove(at: index) - } - return filters + let alertController = textAlertController(context: context, title: presentationData.strings.ChatList_RemoveFolderConfirmationTitle(title.text).string, text: presentationData.strings.ChatList_RemoveFolderConfirmation, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: presentationData.strings.ChatList_RemoveFolderAction, action: { + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in + var filters = filters + if let index = filters.firstIndex(where: { $0.id == id }) { + filters.remove(at: index) } - |> deliverOnMainQueue).startStandalone() - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) + return filters + } + |> deliverOnMainQueue).startStandalone() + }) ]) - presentControllerImpl?(actionSheet) + presentControllerImpl?(alertController) } }) }, updateDisplayTags: { value in @@ -580,18 +570,18 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch pushControllerImpl?(controller) }) - let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) + let featuredFilters = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.chatListFiltersFeaturedState)) |> map { preferences -> [ChatListFeaturedFilter] in - guard let state = preferences.values[PreferencesKeys.chatListFiltersFeaturedState]?.get(ChatListFiltersFeaturedState.self) else { + guard let state = preferences?.get(ChatListFiltersFeaturedState.self) else { return [] } return state.filters } |> distinctUntilChanged - + let updatedFilterOrder = Promise<[Int32]?>(nil) - - let preferences = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatListFilterSettings]) + + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.chatListFilterSettings)) let previousDisplayTags = Atomic(value: nil) @@ -613,6 +603,11 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch ) ) |> map { presentationData, state, filtersWithCountsValue, preferences, updatedFilterOrderValue, suggestedFilters, peer, allLimits, displayTags -> (ItemListControllerState, (ItemListNodeState, Any)) in + var presentationData = presentationData + + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + let isPremium = peer?.isPremium ?? false let limits = allLimits.0 let premiumLimits = allLimits.1 @@ -622,13 +617,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch case .default: leftNavigationButton = nil case .modal: - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Close), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) } let rightNavigationButton: ItemListNavigationButton? if state.isEditing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { let _ = (updatedFilterOrder.get() |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak updatedFilterOrder] updatedFilterOrderValue in diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 84e5395075..37cba290b4 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -16,7 +16,7 @@ struct ChatListFilterPresetListItemEditing: Equatable { let revealed: Bool } -final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { +final class ChatListFilterPresetListItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let context: AccountContext let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle @@ -33,6 +33,10 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { let action: () -> Void let setItemWithRevealedOptions: (Int32?, Int32?) -> Void let remove: () -> Void + + var hasActiveRevealOptions: Bool { + return self.editing.revealed + } init( context: AccountContext, @@ -145,6 +149,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { private var item: ChatListFilterPresetListItem? private var layoutParams: ListViewItemLayoutParams? + private var isHighlighted = false override var canBeSelected: Bool { if self.editableControlNode != nil { @@ -242,7 +247,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] if item.editing.editable && item.canBeDeleted { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] } else { peerRevealOptions = [] } @@ -396,26 +401,29 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -425,9 +433,9 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 1.0), size: titleLayout.size)) - let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset), size: labelLayout.size) + let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset + 1.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0) @@ -496,7 +504,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -517,39 +525,8 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -605,6 +582,12 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(view: tagIconView, frame: tagIconFrame) } } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } override func revealOptionsInteractivelyOpened() { if let item = self.item { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift index 4cce378d6a..a2aa4bccb7 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift @@ -215,14 +215,8 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let verticalInset: CGFloat - switch item.systemStyle { - case .glass: - verticalInset = 15.0 - case .legacy: - verticalInset = 11.0 - } - let titleSpacing: CGFloat = 3.0 + let verticalInset: CGFloat = 11.0 + let titleSpacing: CGFloat = 2.0 let height: CGFloat height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height diff --git a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift index 0cac0a1bff..8cc7a4aabe 100644 --- a/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListRecentPeersListItem.swift @@ -122,8 +122,7 @@ class ChatListRecentPeersListItemNode: ListViewItemNode { } else { peersNode = ChatListSearchRecentPeersNode( accountPeerId: item.context.account.peerId, - postbox: item.context.account.postbox, - network: item.context.account.network, + stateManager: item.context.account.stateManager, energyUsageSettings: item.context.sharedContext.energyUsageSettings, contentSettings: item.context.currentContentSettings.with { $0 }, animationCache: item.context.animationCache, diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index aef3f58aa0..60a7915a4b 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -28,7 +28,6 @@ import InstantPageUI import ChatInterfaceState import UndoUI import TextFormat -import Postbox import TelegramAnimatedStickerNode import AnimationCache import MultiAnimationRenderer @@ -36,6 +35,7 @@ import PremiumUI import AvatarNode import StoryContainerScreen import ChatListSearchFiltersContainerNode +import SectionTitleContextItem import EdgeEffect import ComponentFlow import ComponentDisplayAdapters @@ -54,7 +54,7 @@ final class ChatListSearchInteraction { let openDisabledPeer: (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void let openMessage: (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void let openUrl: (String) -> Void - let clearRecentSearch: () -> Void + let clearRecentSearch: (ASDisplayNode) -> Void let addContact: (String) -> Void let toggleMessageSelection: (EngineMessage.Id, Bool) -> Void let messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void) @@ -63,12 +63,12 @@ final class ChatListSearchInteraction { let present: (ViewController, Any?) -> Void let dismissInput: () -> Void let getSelectedMessageIds: () -> Set? - let openStories: ((PeerId, ASDisplayNode) -> Void)? + let openStories: ((EnginePeer.Id, ASDisplayNode) -> Void)? let switchToFilter: (ChatListSearchPaneKey) -> Void let dismissSearch: () -> Void let openAdInfo: (ASDisplayNode, AdPeer) -> Void - init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping () -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set?, openStories: ((PeerId, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void) { + init(openPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, openUrl: @escaping (String) -> Void, clearRecentSearch: @escaping (ASDisplayNode) -> Void, addContact: @escaping (String) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void, messageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void), mediaMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void), peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, getSelectedMessageIds: @escaping () -> Set?, openStories: ((EnginePeer.Id, ASDisplayNode) -> Void)?, switchToFilter: @escaping (ChatListSearchPaneKey) -> Void, dismissSearch: @escaping () -> Void, openAdInfo: @escaping (ASDisplayNode, AdPeer) -> Void) { self.openPeer = openPeer self.openDisabledPeer = openDisabledPeer self.openMessage = openMessage @@ -89,6 +89,20 @@ final class ChatListSearchInteraction { } } +private final class ChatListSearchClearRecentReferenceContentSource: ContextReferenceContentSource { + private let sourceNode: ASDisplayNode + + let keepInPlace: Bool = true + + init(sourceNode: ASDisplayNode) { + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .bottom) + } +} + private struct ChatListSearchContainerNodeSearchState: Equatable { var selectedMessageIds: Set? @@ -110,6 +124,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo private let navigationController: NavigationController? var dismissSearch: (() -> Void)? + var dismissSearchImmediately: (() -> Void)? var openAdInfo: ((ASDisplayNode, AdPeer) -> Void)? private let edgeEffectView: EdgeEffectView @@ -172,6 +187,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo initialFilter = .topics } + var folder = folder + folder = nil + self.context = context self.peersFilter = filter self.requestPeerType = requestPeerType @@ -211,17 +229,17 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let interaction = ChatListSearchInteraction(openPeer: { peer, chatPeer, threadId, value in originalOpenPeer(peer, chatPeer, threadId, value) if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_peer", peerId: peer.id) + context.engine.accountData.addAppLogEvent(type: "search_global_open_peer") } }, openDisabledPeer: { peer, threadId, reason in openDisabledPeer(peer, threadId, reason) }, openMessage: { peer, threadId, messageId, deactivateOnAction in originalOpenMessage(peer, threadId, messageId, deactivateOnAction) if peer.id.namespace != Namespaces.Peer.SecretChat { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_open_message", peerId: peer.id, data: .dictionary(["msg_id": .number(Double(messageId.id))])) + context.engine.accountData.addAppLogEvent(type: "search_global_open_message", data: .dictionary(["msg_id": .number(Double(messageId.id))])) } }, openUrl: { [weak self] url in - let _ = openUserGeneratedUrl(context: context, peerId: nil, url: url, concealed: false, present: { c in + let _ = context.sharedContext.openUserGeneratedUrl(context: context, peerId: nil, url: url, webpage: nil, concealed: false, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { c in present(c, nil) }, openResolved: { [weak self] resolved in context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peerId, navigation in @@ -237,31 +255,35 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo }, dismissInput: { self?.dismissInput() }, contentContext: nil, progress: nil, completion: nil) - }) - }, clearRecentSearch: { [weak self] in + }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil) + }, clearRecentSearch: { [weak self] sourceNode in guard let strongSelf = self else { return } + let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil let presentationData = strongSelf.presentationData - let actionSheet = ActionSheetController(presentationData: presentationData) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: presentationData.strings.ChatList_ClearSearchHistory), - ActionSheetButtonItem(title: presentationData.strings.WebSearch_RecentSectionClear, color: .destructive, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - - guard let strongSelf = self else { - return + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: presentationData.strings.ChatList_ClearSearchHistory, textFont: .small, icon: { _ in return nil }, action: emptyAction)), + .action(ContextMenuActionItem(text: presentationData.strings.ChatList_ClearSearchHistory_Confirm, textColor: .destructive, icon: { theme in + return nil + }, action: { [weak self] c, _ in + let clearImpl = { [weak self] in + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.engine.peers.clearRecentlySearchedPeers() + |> deliverOnMainQueue).startStandalone() } - let _ = (strongSelf.context.engine.peers.clearRecentlySearchedPeers() - |> deliverOnMainQueue).startStandalone() - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) + if let c { + c.dismiss(completion: clearImpl) + } else { + clearImpl() + } + })) + ] + let controller = makeContextController(presentationData: presentationData, source: .reference(ChatListSearchClearRecentReferenceContentSource(sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) strongSelf.dismissInput() - strongSelf.present?(actionSheet, nil) + strongSelf.present?(controller, nil) }, addContact: { phoneNumber in addContact?(phoneNumber) }, toggleMessageSelection: { [weak self] messageId, selected in @@ -436,16 +458,23 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo suggestedPeers = .single([]) } - let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let accountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) - + self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterPromise.get(), self.searchQuery.get(), accountPeer) |> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([EnginePeer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, EnginePeer?), NoError> in if searchQuery?.isEmpty ?? true { - return .single((peers, dates, selectedFilter?.id, searchQuery, EnginePeer(accountPeer))) + return .single((peers, dates, selectedFilter?.id, searchQuery, accountPeer)) } else { return (.complete() |> delay(0.25, queue: Queue.mainQueue())) - |> then(.single((peers, dates, selectedFilter?.id, searchQuery, EnginePeer(accountPeer)))) + |> then(.single((peers, dates, selectedFilter?.id, searchQuery, accountPeer))) } } |> map { peers, dates, selectedFilter, searchQuery, accountPeer -> ([ChatListSearchFilter], Bool) in var suggestedFilters: [ChatListSearchFilter] = [] @@ -916,7 +945,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } var type: PeerType = .group for message in messages { - if let user = message.author?._asPeer() as? TelegramUser { + if case let .user(user) = message.author { if user.botInfo != nil && !user.id.isVerificationCodes { type = .bot } else { @@ -1055,7 +1084,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo if paneKey == .downloads { let isCachedValue: Signal if let downloadResource = downloadResource { - isCachedValue = self.context.account.postbox.mediaBox.resourceStatus(MediaResourceId(downloadResource.id), resourceSize: downloadResource.size) + isCachedValue = self.context.engine.resources.status(id: EngineMediaResource.Id(downloadResource.id), resourceSize: downloadResource.size) |> map { status -> Bool in switch status { case .Local: @@ -1103,7 +1132,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo f(.default) return } - let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources([MediaResourceId(downloadResource.id)], notify: true) + let _ = (strongSelf.context.engine.resources.removeCachedResources(ids: [EngineMediaResource.Id(downloadResource.id)], notify: true) |> deliverOnMainQueue).startStandalone(completed: { f(.dismissWithoutContent) }) @@ -1385,16 +1414,16 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return } - var resourceIds = Set() + var resourceIds = Set() for message in messages { - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { - resourceIds.insert(file.resource.id) + resourceIds.insert(EngineMediaResource.Id(file.resource.id)) } } } - - let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources(Array(resourceIds), force: true, notify: true) + + let _ = (strongSelf.context.engine.resources.removeCachedResources(ids: Array(resourceIds), force: true, notify: true) |> deliverOnMainQueue).startStandalone(completed: { guard let strongSelf = self else { return @@ -1657,7 +1686,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo if let strongSelf = self { let proceed: (ChatController) -> Void = { chatController in chatController.purposefulAction = { [weak self] in - self?.cancel?() + self?.dismissSearchImmediately?() } if let navigationController = strongSelf.navigationController { var viewControllers = navigationController.viewControllers diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 52b8ba2e16..020fc458f5 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -126,7 +126,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, - clearRecentlySearchedPeers: @escaping () -> Void, + clearRecentlySearchedPeers: @escaping (ASDisplayNode) -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, @@ -150,7 +150,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { let primaryPeer: EnginePeer var chatPeer: EnginePeer? let maybeChatPeer = EnginePeer(peer.peer.peers[peer.peer.peerId]!) - if case .secretChat = maybeChatPeer, let associatedPeerId = maybeChatPeer._asPeer().associatedPeerId, let associatedPeer = peer.peer.peers[associatedPeerId] { + if case .secretChat = maybeChatPeer, let associatedPeerId = maybeChatPeer.associatedPeerId, let associatedPeer = peer.peer.peers[associatedPeerId] { primaryPeer = EnginePeer(associatedPeer) chatPeer = maybeChatPeer } else if case .channel = maybeChatPeer, let mainChannel = peer.peer.chatOrMonoforumMainPeer { @@ -164,9 +164,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable { var enabled = true if filter.contains(.onlyWriteable) { if let peer = chatPeer { - enabled = canSendMessagesToPeer(peer._asPeer()) + enabled = canSendMessagesToPeer(peer) } else { - enabled = canSendMessagesToPeer(primaryPeer._asPeer()) + enabled = canSendMessagesToPeer(primaryPeer) } if requiresPremiumForMessaging { enabled = false @@ -294,8 +294,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else if case .globalPosts = key { header = ChatListSearchItemHeader(type: .text(strings.ChatList_HeaderPublicPosts, 0), theme: theme, strings: strings, actionTitle: nil, action: nil) } else { - header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: { _ in - clearRecentlySearchedPeers() + header = ChatListSearchItemHeader(type: .recentPeers, theme: theme, strings: strings, actionTitle: strings.WebSearch_RecentSectionClear, action: { sourceNode in + clearRecentlySearchedPeers(sourceNode) }) } @@ -753,7 +753,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { var enabled = true if filter.contains(.onlyWriteable) { if let peer = chatPeer { - enabled = canSendMessagesToPeer(peer._asPeer()) + enabled = canSendMessagesToPeer(peer) } else { enabled = false } @@ -892,7 +892,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { var enabled = true if filter.contains(.onlyWriteable) { if let peer = chatPeer { - enabled = canSendMessagesToPeer(peer._asPeer()) + enabled = canSendMessagesToPeer(peer) } else { enabled = false } @@ -1021,13 +1021,16 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } } if filter.contains(.onlyPrivateChats) { - if !(peer.peer is TelegramUser || peer.peer is TelegramSecretChat) { + switch peer.peer { + case .user, .secretChat: + break + default: enabled = false } } if filter.contains(.onlyGroups) { - if let _ = peer.peer as? TelegramGroup { - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + if case .legacyGroup = peer.peer { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { } else { enabled = false } @@ -1035,9 +1038,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable { var suffixString = "" if let subscribers = peer.subscribers, subscribers != 0 { - if peer.peer is TelegramUser { + if case .user = peer.peer { suffixString = ", \(strings.Conversation_StatusBotSubscribers(subscribers))" - } else if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info { suffixString = ", \(strings.Conversation_StatusSubscribers(subscribers))" } else { suffixString = ", \(strings.Conversation_StatusMembers(subscribers))" @@ -1072,13 +1075,13 @@ public enum ChatListSearchEntry: Comparable, Identifiable { isSavedMessages = true } - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in - interaction.peerSelected(EnginePeer(peer.peer), nil, nil, nil, false) + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch(isSavedMessages: isSavedMessages), peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, searchQuery: query, isAd: false, action: { _ in + interaction.peerSelected(peer.peer, nil, nil, nil, false) }, disabledAction: { _ in - interaction.disabledPeerSelected(EnginePeer(peer.peer), nil, requiresPremiumForMessaging ? .premiumRequired : .generic) + interaction.disabledPeerSelected(peer.peer, nil, requiresPremiumForMessaging ? .premiumRequired : .generic) }, contextAction: peerContextAction.flatMap { peerContextAction in return { node, gesture, location in - peerContextAction(EnginePeer(peer.peer), .search(nil), node, gesture, location) + peerContextAction(peer.peer, .search(nil), node, gesture, location) } }, animationCache: interaction.animationCache, animationRenderer: interaction.animationRenderer, storyStats: storyStats.flatMap { stats in return (stats.totalCount, stats.unseenCount, stats.hasUnseenCloseFriends, stats.hasLiveItems) @@ -1331,7 +1334,7 @@ private func chatListSearchContainerPreparedRecentTransition( peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void, disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, - clearRecentlySearchedPeers: @escaping () -> Void, + clearRecentlySearchedPeers: @escaping (ASDisplayNode) -> Void, deletePeer: @escaping (EnginePeer.Id) -> Void, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, @@ -1401,7 +1404,7 @@ private struct ChatListSearchListPaneNodeState: Equatable { private func doesPeerMatchFilter(peer: EnginePeer, filter: ChatListNodePeersFilter) -> Bool { var enabled = true - if filter.contains(.onlyWriteable), !canSendMessagesToPeer(peer._asPeer()) { + if filter.contains(.onlyWriteable), !canSendMessagesToPeer(peer) { enabled = false } if filter.contains(.onlyPrivateChats) { @@ -1497,14 +1500,14 @@ private func filteredPeerSearchQueryResults(value: ([FoundPeer], [FoundPeer]), s case .channels: return ( value.0.filter { peer in - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { return true } else { return false } }, value.1.filter { peer in - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { return true } else { return false @@ -2070,7 +2073,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var defaultFoundRemoteMessagesSignal: Signal<([FoundRemoteMessages], Bool), NoError> = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false)) if key == .globalPosts, let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_load_empty_global_posts"] as? Double, value != 0.0 { - let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil, limit: 50) + let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil, limit: 50) |> map { resultData -> ChatListSearchMessagesResult in let (result, updatedState) = resultData @@ -2103,7 +2106,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let queryTokens = stringIndexTokens(query ?? "", transliteration: .combined) func messageMatchesTokens(message: EngineMessage, tokens: [ValueBoxKey]) -> Bool { - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if let fileName = file.fileName { if matchStringIndexTokens(stringIndexTokens(fileName, transliteration: .none), with: tokens) { @@ -2192,7 +2195,16 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } - let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) + let accountPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + |> take(1) let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set), NoError> if case .savedMessagesChats = location { @@ -2606,28 +2618,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } let searchLocations: [SearchMessagesLocation] if key == .globalPosts { - searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil)] } else if let options { if case let .forum(peerId) = location { - searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: nil)] + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, groupId: nil, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: nil)] } else if let (peerId, _, _) = options.peer { searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1)] } else { - if case let .chatList(groupId) = location, case .archive = groupId { - searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] + if case let .chatList(groupId) = location { + searchLocations = [.general(scope: searchScope, groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] } else { - searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] + searchLocations = [.general(scope: searchScope, groupId: nil, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)] } } } else { if case .channels = key { - searchLocations = [.general(scope: .channels, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [.general(scope: .channels, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } else if case let .forum(peerId) = location { - searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] - } else if case let .chatList(groupId) = location, case .archive = groupId { - searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)] + searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + } else if case let .chatList(groupId) = location { + searchLocations = [.general(scope: searchScope, groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } else { - searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] + searchLocations = [.general(scope: searchScope, groupId: nil, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)] } } @@ -2720,13 +2732,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } else { if !finalQuery.isEmpty { - addAppLogEvent(postbox: context.account.postbox, type: "search_global_query") + context.engine.accountData.addAppLogEvent(type: "search_global_query") } let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>] if key == .globalPosts { - searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: finalQuery, state: nil, limit: 50)] + searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: finalQuery, state: nil, limit: 50)] } else { searchSignals = searchLocations.map { searchLocation in return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50) @@ -2822,9 +2834,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { |> mapToSignal { resolvedUrl -> Signal in if case let .channelMessage(_, messageId, _) = resolvedUrl { return context.engine.messages.downloadMessage(messageId: messageId) - |> map { message -> EngineMessage? in - return message.flatMap(EngineMessage.init) - } } else { return .single(nil) } @@ -3079,7 +3088,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } } for peer in foundRemotePeers.0 { - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) totalNumberOfLocalPeers += 1 } @@ -3087,7 +3096,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { var totalNumberOfGlobalPeers = 0 for peer in foundRemotePeers.1 { - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { totalNumberOfGlobalPeers += 1 } } @@ -3205,9 +3214,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { break } - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) - entries.append(.localPeer(EnginePeer(peer.peer), nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) + entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType, nil, false, false)) index += 1 numberOfLocalPeers += 1 } @@ -3232,7 +3241,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { break } - if !existingPeerIds.contains(peer.peer.id), filteredPeer(EnginePeer(peer.peer), EnginePeer(accountPeer)) { + if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, EnginePeer(accountPeer)) { existingPeerIds.insert(peer.peer.id) entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType, nil, false, finalQuery)) @@ -3508,6 +3517,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: { }, hideChatFolderUpdates: { }, openStories: { [weak self] subject, sourceNode in @@ -3711,7 +3721,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } case let .globalPeer(foundPeer, _, _, _, _, _, _, _, _, _, _): storyStatsIds.append(foundPeer.peer.id) - if let user = foundPeer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = foundPeer.peer, user.flags.contains(.requirePremium) { requiresPremiumForMessagingPeerIds.append(foundPeer.peer.id) } case let .message(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _): @@ -4161,7 +4171,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { result.append(.peer( index: result.count, peer: RecentlySearchedPeer( - peer: RenderedPeer(peer: peer._asPeer()), + peer: RenderedPeer(peer: peer), presence: nil, notificationSettings: peerNotificationSettings.flatMap({ $0._asNotificationSettings() }), unreadCount: unreadCount, @@ -4201,7 +4211,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { result.append(.peer( index: result.count, peer: RecentlySearchedPeer( - peer: RenderedPeer(peer: peer._asPeer()), + peer: RenderedPeer(peer: peer), presence: nil, notificationSettings: peerNotificationSettings.flatMap({ $0._asNotificationSettings() }), unreadCount: 0, @@ -4324,7 +4334,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { result.append(.peer( index: result.count, peer: RecentlySearchedPeer( - peer: RenderedPeer(peer: peer._asPeer()), + peer: RenderedPeer(peer: peer), presence: nil, notificationSettings: peerNotificationSettings.flatMap({ $0._asNotificationSettings() }), unreadCount: unreadCount, @@ -4359,7 +4369,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { result.append(.peer( index: result.count, peer: RecentlySearchedPeer( - peer: RenderedPeer(peer: peer._asPeer()), + peer: RenderedPeer(peer: peer), presence: nil, notificationSettings: peerNotificationSettings.flatMap({ $0._asNotificationSettings() }), unreadCount: 0, @@ -4452,7 +4462,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { keepStack: .always )) case .info: - if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(peerInfoScreen) } case .openApp: @@ -4514,8 +4524,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } else { gesture?.cancel() } - }, clearRecentlySearchedPeers: { - interaction.clearRecentSearch() + }, clearRecentlySearchedPeers: { sourceNode in + interaction.clearRecentSearch(sourceNode) }, deletePeer: { peerId in let _ = context.engine.peers.removeRecentlySearchedPeer(peerId: peerId).startStandalone() }, animationCache: strongSelf.animationCache, animationRenderer: strongSelf.animationRenderer, openStories: { peerId, avatarNode in @@ -5805,6 +5815,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in @@ -6531,10 +6542,10 @@ private final class EmptyResultsButton: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: component.theme.list.itemCheckColors.fillColor, foreground: component.theme.list.itemCheckColors.foregroundColor, - pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) ), content: buttonContent, isEnabled: isEnabled, @@ -6546,7 +6557,7 @@ private final class EmptyResultsButton: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width, height: 50.0) + containerSize: CGSize(width: availableSize.width, height: 52.0) ) if let buttonView = self.button.view { if buttonView.superview == nil { diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift index 6f0f69a246..363194410f 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -4,7 +4,6 @@ import UIKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -20,17 +19,17 @@ private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white private final class VisualMediaItemInteraction { - let openMessage: (Message) -> Void - let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void - let toggleSelection: (MessageId, Bool) -> Void - - var hiddenMedia: [MessageId: [Media]] = [:] - var selectedMessageIds: Set? - + let openMessage: (EngineRawMessage) -> Void + let openMessageContextActions: (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void + let toggleSelection: (EngineMessage.Id, Bool) -> Void + + var hiddenMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] + var selectedMessageIds: Set? + init( - openMessage: @escaping (Message) -> Void, - openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, - toggleSelection: @escaping (MessageId, Bool) -> Void + openMessage: @escaping (EngineRawMessage) -> Void, + openMessageContextActions: @escaping (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void, + toggleSelection: @escaping (EngineMessage.Id, Bool) -> Void ) { self.openMessage = openMessage self.openMessageContextActions = openMessageContextActions @@ -53,9 +52,9 @@ private final class VisualMediaItemNode: ASDisplayNode { private let fetchStatusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() - private var resourceStatus: MediaResourceStatus? + private var resourceStatus: EngineMediaResourceStatus? - private var item: (VisualMediaItem, Media?, CGSize, CGSize?)? + private var item: (VisualMediaItem, EngineRawMedia?, CGSize, CGSize?)? private var theme: PresentationTheme? private var hasVisibility: Bool = false @@ -117,8 +116,8 @@ private final class VisualMediaItemNode: ASDisplayNode { if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { if case .tap = gesture { if let _ = self.item { - var media: Media? - for value in message.media { + var media: EngineRawMedia? + for value in message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -127,10 +126,10 @@ private final class VisualMediaItemNode: ASDisplayNode { break } } - + if let media = media { if let file = media as? TelegramMediaFile { - if isMediaStreamable(message: message, media: file) { + if isMediaStreamable(message: EngineMessage(message), media: file) { self.interaction.openMessage(message) } else { self.progressPressed() @@ -150,8 +149,8 @@ private final class VisualMediaItemNode: ASDisplayNode { return } - var media: Media? - for value in message.media { + var media: EngineRawMedia? + for value in message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -160,7 +159,7 @@ private final class VisualMediaItemNode: ASDisplayNode { break } } - + if let resourceStatus = self.resourceStatus, let file = media as? TelegramMediaFile { switch resourceStatus { case .Fetching: @@ -185,9 +184,9 @@ private final class VisualMediaItemNode: ASDisplayNode { return } self.theme = theme - var media: Media? + var media: EngineRawMedia? if let message = item.message { - for value in message.media { + for value in message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -226,7 +225,7 @@ private final class VisualMediaItemNode: ASDisplayNode { if let strongSelf = self, let _ = strongSelf.item { strongSelf.resourceStatus = status - let isStreamable = isMediaStreamable(message: message, media: file) + let isStreamable = isMediaStreamable(message: EngineMessage(message), media: file) var statusState: RadialStatusNodeState = .none if isStreamable || file.isAnimated { @@ -404,17 +403,17 @@ private final class VisualMediaItemNode: ASDisplayNode { private final class VisualMediaItem { let index: UInt32? - let message: Message? + let message: EngineRawMessage? let dimensions: CGSize let aspectRatio: CGFloat - - init(message: Message, index: UInt32?) { + + init(message: EngineRawMessage, index: UInt32?) { self.index = index self.message = message var aspectRatio: CGFloat = 1.0 var dimensions = CGSize(width: 100.0, height: 100.0) - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if let dimensionsValue = file.dimensions, dimensions.height > 1 { dimensions = dimensionsValue.cgSize @@ -637,7 +636,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { public var beganInteractiveDragging: (() -> Void)? public var loadMore: (() -> Void)? - init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void, toggleMessageSelection: @escaping (MessageId, Bool) -> Void) { + init(context: AccountContext, contentType: ContentType, openMessage: @escaping (EngineRawMessage, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (EngineRawMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void, toggleMessageSelection: @escaping (EngineMessage.Id, Bool) -> Void) { self.context = context self.contentType = contentType @@ -675,7 +674,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { guard let strongSelf = self else { return } - var hiddenMedia: [MessageId: [Media]] = [:] + var hiddenMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] for id in ids { if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { hiddenMedia[messageId] = [media] @@ -694,7 +693,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { self.animationTimer?.invalidate() } - func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { + func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: EngineViewUpdateType) { switch updateType { case .FillHole: break @@ -733,7 +732,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { } } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineRawMessage? { for item in self.mediaItems { if item.message?.id == id { return item.message @@ -754,7 +753,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { } } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for item in self.mediaItems { if let message = item.message, message.id == messageId { if let itemNode = self.visibleMediaItems[message.stableId] { @@ -770,7 +769,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.addSubview(view) } - var selectedMessageIds: Set? { + var selectedMessageIds: Set? { didSet { self.itemInteraction.selectedMessageIds = self.selectedMessageIds } @@ -865,7 +864,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, ASScrollViewDelegate { let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) - var headerItem: Message? + var headerItem: EngineRawMessage? var validIds = Set() if minVisibleIndex <= maxVisibleIndex { diff --git a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift index cfe3225c41..316e3eaf3d 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMessageSelectionPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -31,8 +30,8 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode { private var validLayout: ContainerViewLayout? - var chatAvailableMessageActions: ((Set) -> Signal)? - var selectedMessages = Set() { + var chatAvailableMessageActions: ((Set) -> Signal)? + var selectedMessages = Set() { didSet { if oldValue != self.selectedMessages { self.forwardButton.isEnabled = self.selectedMessages.count != 0 diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 68b09cf72a..1189898132 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -156,7 +156,7 @@ public final class ChatListShimmerNode: ASDisplayNode { let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() - }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift index 411cd295b9..4abb01fb48 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift @@ -185,6 +185,7 @@ class ChatListArchiveInfoItemNode: ListViewItemNode, ASScrollViewDelegate { self.view.disablesInteractiveTransitionGestureRecognizer = true + self.scrollNode.view.scrollsToTop = false self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.delegate = self.wrappedScrollViewDelegate diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index af1a1be48c..67257066fe 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -530,10 +529,10 @@ public class ChatListItem: ListViewItem { public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { let node = ChatListItemNode() - let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) node.insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader) - let (nodeLayout, apply) = node.asyncLayout()(self, params, first, last, firstWithHeader, nextIsPinned) + let (nodeLayout, apply) = node.asyncLayout()(self, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) node.insets = nodeLayout.insets node.contentSize = nodeLayout.contentSize @@ -557,13 +556,13 @@ public class ChatListItem: ListViewItem { nodeValue.setupItem(item: self, synchronousLoads: false) let layout = nodeValue.asyncLayout() async { - let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) + let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem) var animated = true if case .None = animation { animated = false } - let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, nextIsPinned) + let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) Queue.mainQueue().async { completion(nodeLayout, { _ in apply(false, animated) @@ -601,7 +600,7 @@ public class ChatListItem: ListViewItem { } } - static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool) { + static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, nextHasActiveRevealControls: Bool) { var first = false var last = false var firstWithHeader = false @@ -618,14 +617,16 @@ public class ChatListItem: ListViewItem { firstWithHeader = item.header != nil } var nextIsPinned = false + var nextHasActiveRevealControls = false if let nextItem = nextItem as? ChatListItem { if case let .chatList(nextIndex) = nextItem.index, nextIndex.pinningIndex != nil { nextIsPinned = true } + nextHasActiveRevealControls = nextItem.hasActiveRevealControls } else { last = true } - return (first, last, firstWithHeader, nextIsPinned) + return (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) } } @@ -639,9 +640,9 @@ private let ungroupIcon = ItemListRevealOptionIcon.animation(animation: "anim_un private let readIcon = ItemListRevealOptionIcon.animation(animation: "anim_read", scale: 1.0, offset: 0.0, replaceColors: nil, flip: false) private let unreadIcon = ItemListRevealOptionIcon.animation(animation: "anim_unread", scale: 1.0, offset: 0.0, replaceColors: [0x2194fa], flip: false) private let archiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_archive", scale: 1.0, offset: 2.0, replaceColors: [0xa9a9ad], flip: false) -private let unarchiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_unarchive", scale: 0.642, offset: -9.0, replaceColors: [0xa9a9ad], flip: false) -private let hideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: 2.0, replaceColors: [0xbdbdc2], flip: false) -private let unhideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: -20.0, replaceColors: [0xbdbdc2], flip: true) +private let unarchiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_unarchive", scale: 0.52, offset: -6.0, replaceColors: [0xa9a9ad], flip: false) +private let hideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.1, offset: 2.0, replaceColors: [0xbdbdc2], flip: false) +private let unhideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: -15.0, replaceColors: [0xbdbdc2], flip: true) private let startIcon = ItemListRevealOptionIcon.animation(animation: "anim_play", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false) private let closeIcon = ItemListRevealOptionIcon.animation(animation: "anim_pause", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false) @@ -687,28 +688,28 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem if !isEditing { if case .savedMessagesChats = location { if isPinned { - options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } else if case .chatList(.archive) = location { if isPinned { - options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } else { if let isMuted = isMuted { if isMuted { - options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } } if canDelete { - options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, iconColor: theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } if case .savedMessagesChats = location { } else { @@ -730,10 +731,10 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem } if canArchive { if canArchivePeer(id: peerId, accountPeerId: accountPeerId) { - options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } else if canUnarchive { - options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } @@ -744,9 +745,9 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr var options: [ItemListRevealOption] = [] if !isEditing { if hiddenByDefault { - options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor)) } } return options @@ -757,9 +758,9 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres if !isEditing { if let isMuted = isMuted { if isMuted { - options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } @@ -768,16 +769,16 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres if !isClosed { } else { - options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } if canHide { if !isEditing { if hiddenByDefault { - options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } @@ -789,21 +790,21 @@ private func forumThreadRevealOptions(strings: PresentationStrings, theme: Prese if !isEditing { if let isMuted = isMuted { if isMuted { - options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } if canDelete { - options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, iconColor: theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } if canOpenClose { if !isEditing { if !isClosed { - options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { - options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } @@ -816,7 +817,7 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation if case .root = groupId { var options: [ItemListRevealOption] = [] if isUnread { - options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { var canMarkUnread = true if case let .channel(channel) = peer, channel.isForumOrMonoForum { @@ -824,15 +825,15 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation } if canMarkUnread { - options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, iconColor: theme.list.itemDisclosureActions.accent.foregroundColor, textColor: theme.chatList.dateTextColor)) } } if !isEditing { if isPinned { - options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } else { if filterData == nil || peer.id.namespace != Namespaces.Peer.SecretChat { - options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor)) } } } @@ -1388,9 +1389,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { private var cachedChatListQuoteSearchResult: CachedChatListSearchResult? private var cachedCustomTextEntities: CachedCustomTextEntities? - var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)? + var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, nextHasActiveRevealControls: Bool, ListViewItemLayoutParams, countersSize: CGFloat)? private var isHighlighted: Bool = false + private var nextHasActiveRevealControls: Bool = false private var skipFadeout: Bool = false private var customAnimationInProgress: Bool = false @@ -1687,7 +1689,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in if let strongSelf = self, let layoutParams = strongSelf.layoutParams { - let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.5, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4) + let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.6, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4, layoutParams.5) let _ = apply(false, false) } }) @@ -1969,6 +1971,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { profilePhoto = maybePhoto isKnown = true } + if profilePhoto == nil, case let .known(maybePhoto) = cachedPeerData.fallbackPhoto { + profilePhoto = maybePhoto + isKnown = true + } } if isKnown { @@ -2033,8 +2039,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { let layout = self.asyncLayout() - let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) - let (nodeLayout, apply) = layout(item as! ChatListItem, params, first, last, firstWithHeader, nextIsPinned) + let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem) + let (nodeLayout, apply) = layout(item as! ChatListItem, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) apply(false, false) self.contentSize = nodeLayout.contentSize self.insets = nodeLayout.insets @@ -2053,7 +2059,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } var reallyHighlighted: Bool { - var reallyHighlighted = self.isHighlighted + var reallyHighlighted = self.isHighlighted || self.isRevealOptionsActive if let item = self.item { if let itemChatLocation = item.content.chatLocation { if itemChatLocation == item.interaction.highlightedChatLocation?.location { @@ -2071,6 +2077,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { func updateIsHighlighted(transition: ContainedViewLayoutTransition) { let highlightProgress: CGFloat = self.item?.interaction.highlightedChatLocation?.progress ?? 1.0 + transition.updateCornerRadius(node: self.highlightedBackgroundNode, cornerRadius: self.isRevealOptionsActive ? 26.0 : 0.0) + self.updateSeparatorAlpha(transition: transition) if self.reallyHighlighted { if self.highlightedBackgroundNode.supernode == nil { @@ -2133,6 +2141,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } } + + private func updateSeparatorAlpha(transition: ContainedViewLayoutTransition, inlineNavigationProgress: CGFloat? = nil) { + let revealSeparatorAlpha: CGFloat = (self.isRevealOptionsActive || self.isNextRevealOptionsActive || self.nextHasActiveRevealControls) ? 0.0 : 1.0 + if let inlineNavigationProgress = inlineNavigationProgress ?? self.item?.interaction.inlineNavigationLocation?.progress { + transition.updateAlpha(node: self.separatorNode, alpha: (1.0 - inlineNavigationProgress) * revealSeparatorAlpha) + } else { + transition.updateAlpha(node: self.separatorNode, alpha: revealSeparatorAlpha) + } + } override public func tapped() { guard let item = self.item, item.editing else { @@ -2150,7 +2167,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } - func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { + func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool, _ nextHasActiveRevealControls: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) let textLayout = TextNodeWithEntities.asyncLayout(self.textNode) let makeTrailingTextBadgeLayout = TextNode.asyncLayout(self.trailingTextBadgeNode) @@ -2172,8 +2189,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let currentChatListQuoteSearchResult = self.cachedChatListQuoteSearchResult let currentCustomTextEntities = self.cachedCustomTextEntities - return { item, params, first, last, firstWithHeader, nextIsPinned in - let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) + return { item, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls in + let titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let italicTextFont = Font.italic(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) @@ -2384,11 +2401,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var currentStatusIconContent: EmojiStatusComponent.Content? var currentStatusIconParticleColor: UIColor? var currentSecretIconImage: UIImage? - var currentForwardedIcon: UIImage? - var currentStoryIcon: UIImage? - var currentGiftIcon: UIImage? - var currentLocationIcon: UIImage? - var currentPollIcon: UIImage? + var currentMessageTypeIcon: UIImage? + var currentMessageTypeIconOffset: CGPoint = .zero var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)? @@ -2450,7 +2464,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } else if !useChatListLayout { avatarLeftInset = 50.0 } else { - avatarLeftInset = 18.0 + avatarDiameter + avatarLeftInset = 24.0 + avatarDiameter } } @@ -2559,12 +2573,27 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var contentImageSpecs: [ContentImageSpec] = [] var avatarContentImageSpec: ContentImageSpec? var forumThread: (id: Int64, title: String, iconId: Int64?, iconColor: Int32, threadPeer: EnginePeer?, isUnread: Bool)? - - var displayForwardedIcon = false - var displayStoryReplyIcon = false - var displayGiftIcon = false - var displayLocationIcon = false - var displayPollIcon = false + + enum MessageTypeIcon { + enum CallType { + case voice + case video + } + enum CallDirection { + case incoming + case outgoing + } + case call(CallType, CallDirection) + case forward + case story + case gift + case location + case poll + case todo + case game + case voiceMessage + } + var messageTypeIcon: MessageTypeIcon? var ignoreForwardedIcon = false switch contentData { @@ -2573,6 +2602,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if case .user = itemPeer.chatMainPeer { isUser = true } + var isGuestChatAuthor = false + if let message = messages.last, case let .user(user) = message.author, user.id != message.id.peerId, let botInfo = user.botInfo, botInfo.flags.contains(.isGuestChat) { + isGuestChatAuthor = true + } var peerText: String? if case .savedMessagesChats = item.chatListLocation { @@ -2590,32 +2623,32 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if let message = messages.last, let forwardInfo = message.forwardInfo, let author = forwardInfo.author { peerText = EnginePeer(author).compactDisplayTitle } - } else if !isUser { + } else if !isUser || isGuestChatAuthor { if case let .channel(peer) = peer, case .broadcast = peer.info { } else if !displayAsMessage { if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { peerText = authorSignature } else { peerText = author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - authorIsCurrentChat = author.id == peer.id + authorIsCurrentChat = !isGuestChatAuthor && author.id == peer.id } } } } if case .chatList = item.chatListLocation, itemPeer.peerId == item.context.account.peerId, let message = messages.first { - var effectiveAuthor: Peer? = message.author?._asPeer() + var effectiveAuthor: EngineRawPeer? = message.author?._asPeer() if let forwardInfo = message.forwardInfo { effectiveAuthor = forwardInfo.author if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + effectiveAuthor = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo { if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] { effectiveAuthor = peer } else if let authorSignature = sourceAuthorInfo.originalAuthorName { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + effectiveAuthor = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } @@ -2874,24 +2907,33 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if !ignoreForwardedIcon { if case .savedMessagesChats = item.chatListLocation { - displayForwardedIcon = false } else if let forwardInfo = message.forwardInfo, !forwardInfo.flags.contains(.isImported) && !message.id.peerId.isVerificationCodes { - displayForwardedIcon = true + messageTypeIcon = .forward } else if let _ = message.attributes.first(where: { $0 is ReplyStoryAttribute }) { - displayStoryReplyIcon = true + messageTypeIcon = .story } else { for media in message.media { - if let _ = media as? TelegramMediaPoll { - displayPollIcon = true + if let file = media as? TelegramMediaFile { + if file.isVoice { + messageTypeIcon = .voiceMessage + } + } else if let _ = media as? TelegramMediaPoll { + messageTypeIcon = .poll + } else if let _ = media as? TelegramMediaTodo { + messageTypeIcon = .todo + } else if let _ = media as? TelegramMediaGame { + messageTypeIcon = .game } else if let _ = media as? TelegramMediaMap { - displayLocationIcon = true + messageTypeIcon = .location } else if let action = media as? TelegramMediaAction { switch action.action { + case let .phoneCall(_, _, _, isVideo): + messageTypeIcon = .call(isVideo ? .video : .voice, message.flags.contains(.Incoming) ? .incoming : .outgoing) case .giftPremium, .giftStars, .starGift, .starGiftUnique: - displayGiftIcon = true + messageTypeIcon = .gift case let .giftCode(_, _, _, boostPeerId, _, _, _, _, _, _, _): if boostPeerId == nil { - displayGiftIcon = true + messageTypeIcon = .gift } default: break @@ -2931,10 +2973,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { case let .preview(dimensions, immediateThumbnailData, videoDuration): if let immediateThumbnailData { if let videoDuration { - let thumbnailMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) + let thumbnailMedia = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) contentImageSpecs.append(ContentImageSpec(message: message, media: .file(thumbnailMedia), size: fitSize)) } else { - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: index), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let thumbnailMedia = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: index), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) contentImageSpecs.append(ContentImageSpec(message: message, media: .image(thumbnailMedia), size: fitSize)) } index += 1 @@ -3048,68 +3090,58 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { attributedText = textString } - if displayForwardedIcon { - currentForwardedIcon = PresentationResourcesChatList.forwardedIcon(item.presentationData.theme) - } - - if displayStoryReplyIcon { - currentStoryIcon = PresentationResourcesChatList.storyReplyIcon(item.presentationData.theme) - } - - if displayGiftIcon { - currentGiftIcon = PresentationResourcesChatList.giftIcon(item.presentationData.theme) - } - - if displayLocationIcon { - currentLocationIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme) - } - - if displayPollIcon { - currentPollIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme) - } - - if let currentForwardedIcon { - textLeftCutout += currentForwardedIcon.size.width - if !contentImageSpecs.isEmpty { - textLeftCutout += forwardedIconSpacing - } else { - textLeftCutout += contentImageTrailingSpace + switch messageTypeIcon { + case let .call(type, direction): + switch type { + case .voice: + switch direction { + case .incoming: + currentMessageTypeIcon = PresentationResourcesChatList.callIncomingIcon(item.presentationData.theme) + case .outgoing: + currentMessageTypeIcon = PresentationResourcesChatList.callOutgoingIcon(item.presentationData.theme) + } + case .video: + switch direction { + case .incoming: + currentMessageTypeIcon = PresentationResourcesChatList.callVideoIncomingIcon(item.presentationData.theme) + case .outgoing: + currentMessageTypeIcon = PresentationResourcesChatList.callVideoOutgoingIcon(item.presentationData.theme) + } } + case .forward: + currentMessageTypeIcon = PresentationResourcesChatList.forwardedIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = 3.0 + case .story: + currentMessageTypeIcon = PresentationResourcesChatList.storyReplyIcon(item.presentationData.theme) + case .gift: + currentMessageTypeIcon = PresentationResourcesChatList.giftIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -2.0 - UIScreenPixel + case .location: + currentMessageTypeIcon = PresentationResourcesChatList.locationIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -1.0 - UIScreenPixel + case .poll: + currentMessageTypeIcon = PresentationResourcesChatList.pollIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -1.0 + case .todo: + currentMessageTypeIcon = PresentationResourcesChatList.todoIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -1.0 + case .game: + currentMessageTypeIcon = PresentationResourcesChatList.gameIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -1.0 + case .voiceMessage: + currentMessageTypeIcon = PresentationResourcesChatList.voiceMessageIcon(item.presentationData.theme) + currentMessageTypeIconOffset.y = -1.0 + default: + break } + let messageTypeIconScale = min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0) - if let currentStoryIcon { - textLeftCutout += currentStoryIcon.size.width + if let currentMessageTypeIcon { + textLeftCutout += currentMessageTypeIcon.size.width * messageTypeIconScale if !contentImageSpecs.isEmpty { textLeftCutout += forwardedIconSpacing } else { - textLeftCutout += contentImageTrailingSpace - } - } - - if let currentGiftIcon { - textLeftCutout += currentGiftIcon.size.width - if !contentImageSpecs.isEmpty { - textLeftCutout += forwardedIconSpacing - } else { - textLeftCutout += contentImageTrailingSpace - } - } - - if let currentLocationIcon { - textLeftCutout += currentLocationIcon.size.width - if !contentImageSpecs.isEmpty { - textLeftCutout += forwardedIconSpacing - } else { - textLeftCutout += contentImageTrailingSpace - } - } - - if let currentPollIcon { - textLeftCutout += currentPollIcon.size.width - if !contentImageSpecs.isEmpty { - textLeftCutout += forwardedIconSpacing - } else { - textLeftCutout += contentImageTrailingSpace + textLeftCutout += contentImageTrailingSpace - 1.0 } } @@ -3178,7 +3210,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { textAttributedString = attributedText let dateText: String - var topIndex: MessageIndex? + var topIndex: EngineMessage.Index? switch item.content { case .loading: break @@ -3248,45 +3280,42 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } if unreadCount.unread { - if !isPeerGroup, let message = messages.last, message.tags.contains(.unseenPersonalMessage), unreadCount.count == 1 { + let badgeTextColor: UIColor + if unreadCount.muted { + if unreadCount.isProvisonal, case .forum = item.chatListLocation { + badgeTextColor = theme.unreadBadgeInactiveBackgroundColor + currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) + } else { + badgeTextColor = theme.unreadBadgeInactiveTextColor + currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: avatarBadgeDiameter) + } } else { - let badgeTextColor: UIColor - if unreadCount.muted { - if unreadCount.isProvisonal, case .forum = item.chatListLocation { - badgeTextColor = theme.unreadBadgeInactiveBackgroundColor - currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: badgeDiameter) - currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) - } else { - badgeTextColor = theme.unreadBadgeInactiveTextColor - currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: badgeDiameter) - currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: avatarBadgeDiameter) - } + if unreadCount.isProvisonal, case .forum = item.chatListLocation { + badgeTextColor = theme.unreadBadgeActiveBackgroundColor + currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) } else { - if unreadCount.isProvisonal, case .forum = item.chatListLocation { - badgeTextColor = theme.unreadBadgeActiveBackgroundColor - currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: badgeDiameter) - currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) - } else { - badgeTextColor = theme.unreadBadgeActiveTextColor - currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: badgeDiameter) - currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: avatarBadgeDiameter) - } - } - let unreadCountText = compactNumericCountString(Int(unreadCount.count), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) - if unreadCount.count > 0 { - badgeContent = .text(NSAttributedString(string: unreadCountText, font: badgeFont, textColor: badgeTextColor)) - } else if isPeerGroup { - badgeContent = .none - } else { - badgeContent = .blank - } - - if let mutedCount = unreadCount.mutedCount, mutedCount > 0 { - let mutedUnreadCountText = compactNumericCountString(Int(mutedCount), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) - currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: badgeDiameter) - mentionBadgeContent = .text(NSAttributedString(string: mutedUnreadCountText, font: badgeFont, textColor: theme.unreadBadgeInactiveTextColor)) + badgeTextColor = theme.unreadBadgeActiveTextColor + currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: avatarBadgeDiameter) } } + let unreadCountText = compactNumericCountString(Int(unreadCount.count), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) + if unreadCount.count > 0 { + badgeContent = .text(NSAttributedString(string: unreadCountText, font: badgeFont, textColor: badgeTextColor)) + } else if isPeerGroup { + badgeContent = .none + } else { + badgeContent = .blank + } + + if let mutedCount = unreadCount.mutedCount, mutedCount > 0 { + let mutedUnreadCountText = compactNumericCountString(Int(mutedCount), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) + currentMentionBadgeImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: badgeDiameter) + mentionBadgeContent = .text(NSAttributedString(string: mutedUnreadCountText, font: badgeFont, textColor: theme.unreadBadgeInactiveTextColor)) + } } if !isPeerGroup { @@ -3489,7 +3518,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let layoutOffset: CGFloat = 0.0 - let rawContentWidth = params.width - leftInset - params.rightInset - 10.0 - editingOffset + let rawContentWidth = params.width - leftInset - params.rightInset - 18.0 - editingOffset let (dateLayout, dateApply) = dateLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -3498,7 +3527,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let (mentionBadgeLayout, mentionBadgeApply) = mentionBadgeLayout(CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), badgeDiameter, badgeFont, currentMentionBadgeImage, mentionBadgeContent) var actionButtonTitleNodeLayoutAndApply: (TextNodeLayout, () -> TextNode)? - if case .none = badgeContent, case .none = mentionBadgeContent, case let .chat(itemPeer) = contentPeer, case let .user(user) = itemPeer.chatMainPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + if !item.editing, case .none = badgeContent, case .none = mentionBadgeContent, case let .chat(itemPeer) = contentPeer, case let .user(user) = itemPeer.chatMainPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { actionButtonTitleNodeLayoutAndApply = makeActionButtonTitleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.ChatList_InlineButtonOpenApp, font: Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)), textColor: theme.unreadBadgeActiveTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) } @@ -3626,7 +3655,18 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { textMaxWidth -= 18.0 } - let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: (authorAttributedString == nil && itemTags.isEmpty && forumThread == nil && topForumTopicItems.isEmpty) ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: textMaxWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))) + let textLineSpacing: CGFloat = min(0.2, item.presentationData.fontSize.itemListBaseFontSize * 0.2 / 17.0) + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments( + attributedString: textAttributedString, + backgroundColor: nil, + maximumNumberOfLines: (authorAttributedString == nil && itemTags.isEmpty && forumThread == nil && topForumTopicItems.isEmpty) ? 2 : 1, + truncationType: .end, + constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude), + alignment: .natural, + lineSpacing: textLineSpacing, + cutout: textCutout, + insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0) + )) let maxTitleLines: Int switch item.index { @@ -3756,15 +3796,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } } else if case .psa = promoInfo { peerRevealOptions = [ - ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor) + ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor) ] peerLeftRevealOptions = [] } else if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { peerLeftRevealOptions = [] if customMessageListData.commandPrefix != nil { peerRevealOptions = [ - ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor), - ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor) + ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor), + ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor) ] } else { peerRevealOptions = [] @@ -3786,13 +3826,13 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if actions.contains(.toggleUnread) { if unreadCount.unread { - peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Read, icon: readIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor)) + peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Read, icon: readIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor)) } else { - peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Unread, icon: unreadIcon, color: item.presentationData.theme.list.itemDisclosureActions.accent.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor)) + peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Unread, icon: unreadIcon, color: item.presentationData.theme.list.itemDisclosureActions.accent.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor)) } } if actions.contains(.delete) { - peerRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)) + peerRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor)) } } } else { @@ -3850,7 +3890,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { return (layout, { [weak self] synchronousLoads, animated in if let strongSelf = self { - strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize) + strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls, params, countersSize) + strongSelf.nextHasActiveRevealControls = nextHasActiveRevealControls strongSelf.currentItemHeight = itemHeight strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListSearchResult = chatListSearchResult @@ -3984,7 +4025,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) - let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) + let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + 6.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) var avatarScaleOffset: CGFloat = 0.0 var avatarScale: CGFloat = 1.0 if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { @@ -4277,7 +4318,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let _ = mentionBadgeApply(animateBadges, true) let _ = onlineApply(animateContent && animateOnline) - var dateFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size) + var dateFrame = CGRect(origin: CGPoint(x: contentRect.maxX - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size) if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.messageCount != nil, customMessageListData.commandPrefix == nil { dateFrame.origin.x -= 10.0 @@ -4341,16 +4382,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent) + let rightAccessoryVerticalOffset: CGFloat = floorToScreenPixels(-4.0 * min(1.0, item.presentationData.fontSize.itemListBaseFontSize / 17.0)) var nextBadgeX: CGFloat = contentRect.maxX if let _ = currentBadgeBackgroundImage { - let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0, width: badgeLayout.width, height: badgeLayout.height) + let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height) transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame) nextBadgeX -= badgeLayout.width + 6.0 } if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil { - let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height) + let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height) transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame) nextBadgeX -= mentionBadgeLayout.width + 6.0 @@ -4361,7 +4403,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.pinnedIconNode.isHidden = false let pinnedIconSize = currentPinnedIconImage.size - let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0, width: pinnedIconSize.width, height: pinnedIconSize.height) + let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height) strongSelf.pinnedIconNode.frame = pinnedIconFrame nextBadgeX -= pinnedIconSize.width + 6.0 @@ -4378,11 +4420,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let actionButtonSize = CGSize(width: actionButtonTitleNodeLayout.size.width + actionButtonSideInset * 2.0, height: actionButtonTitleNodeLayout.size.height + actionButtonTopInset + actionButtonBottomInset) var actionButtonFrame = CGRect(x: nextBadgeX - actionButtonSize.width, y: contentRect.minY + floor((contentRect.height - actionButtonSize.height) * 0.5), width: actionButtonSize.width, height: actionButtonSize.height) actionButtonFrame.origin.y = max(actionButtonFrame.origin.y, dateFrame.maxY + floor(item.presentationData.fontSize.itemListBaseFontSize * 4.0 / 17.0)) + actionButtonFrame.origin.y += 4.0 let actionButtonNode: HighlightableButtonNode + var animateActionButtonIn = false if let current = strongSelf.actionButtonNode { actionButtonNode = current } else { + animateActionButtonIn = true actionButtonNode = HighlightableButtonNode() strongSelf.actionButtonNode = actionButtonNode strongSelf.mainContentContainerNode.addSubnode(actionButtonNode) @@ -4411,23 +4456,40 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { actionButtonNode.addSubnode(actionButtonTitleNode) } + actionButtonNode.isUserInteractionEnabled = true actionButtonNode.frame = actionButtonFrame actionButtonBackgroundView.frame = CGRect(origin: CGPoint(), size: actionButtonFrame.size) actionButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((actionButtonFrame.width - actionButtonTitleNodeLayout.size.width) * 0.5), y: actionButtonTopInset), size: actionButtonTitleNodeLayout.size) + if animateActionButtonIn { + actionButtonNode.alpha = 0.0 + } + transition.updateAlpha(node: actionButtonNode, alpha: 1.0) nextBadgeX -= actionButtonSize.width + 6.0 } else { - if let actionButtonTitleNode = strongSelf.actionButtonTitleNode { - actionButtonTitleNode.removeFromSupernode() - strongSelf.actionButtonTitleNode = nil - } - if let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView { - actionButtonBackgroundView.removeFromSuperview() - strongSelf.actionButtonBackgroundView = nil - } if let actionButtonNode = strongSelf.actionButtonNode { - actionButtonNode.removeFromSupernode() + let actionButtonTitleNode = strongSelf.actionButtonTitleNode + let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView + actionButtonNode.isUserInteractionEnabled = false + + strongSelf.actionButtonTitleNode = nil + strongSelf.actionButtonBackgroundView = nil strongSelf.actionButtonNode = nil + + transition.updateAlpha(node: actionButtonNode, alpha: 0.0, completion: { [weak actionButtonNode, weak actionButtonTitleNode, weak actionButtonBackgroundView] _ in + actionButtonTitleNode?.removeFromSupernode() + actionButtonBackgroundView?.removeFromSuperview() + actionButtonNode?.removeFromSupernode() + }) + } else { + if let actionButtonTitleNode = strongSelf.actionButtonTitleNode { + actionButtonTitleNode.removeFromSupernode() + strongSelf.actionButtonTitleNode = nil + } + if let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView { + actionButtonBackgroundView.removeFromSuperview() + strongSelf.actionButtonBackgroundView = nil + } } } @@ -4456,9 +4518,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let titleFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: contentRect.origin.y + UIScreenPixel), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame - let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height), size: authorLayout) + let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 2.0), size: authorLayout) strongSelf.authorNode.frame = authorNodeFrame - let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.height.isZero ? 0.0 : (authorLayout.height - 3.0))), size: textLayout.size) + let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 2.0 + (authorLayout.height.isZero ? 0.0 : (authorLayout.height - 3.0))), size: textLayout.size) if let topForumTopicRect, !isSearching { let compoundHighlightingNode: LinkHighlightingNode @@ -4768,31 +4830,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: 1.0 + floor((measureLayout.size.height - contentImageSize.height) / 2.0)) - var messageTypeIcon: UIImage? - var messageTypeIconOffset = mediaPreviewOffset - if let currentForwardedIcon { - messageTypeIcon = currentForwardedIcon - messageTypeIconOffset.y += 3.0 - } else if let currentStoryIcon { - messageTypeIcon = currentStoryIcon - } else if let currentGiftIcon { - messageTypeIcon = currentGiftIcon - messageTypeIconOffset.y -= 2.0 - UIScreenPixel - } else if let currentLocationIcon { - messageTypeIcon = currentLocationIcon - messageTypeIconOffset.y -= 2.0 - UIScreenPixel - } else if let currentPollIcon { - messageTypeIcon = currentPollIcon - messageTypeIconOffset.y -= 2.0 - UIScreenPixel - } + let messageTypeIconImage = currentMessageTypeIcon + let messageTypeIconOffset = CGPoint(x: mediaPreviewOffset.x + currentMessageTypeIconOffset.x, y: mediaPreviewOffset.y + currentMessageTypeIconOffset.y) - if let messageTypeIcon { - strongSelf.forwardedIconNode.image = messageTypeIcon + if let messageTypeIconImage { + strongSelf.forwardedIconNode.image = messageTypeIconImage if strongSelf.forwardedIconNode.supernode == nil { strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode) } - transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: messageTypeIcon.size)) - mediaPreviewOffset.x += messageTypeIcon.size.width + forwardedIconSpacing + let iconSize = CGSize(width: messageTypeIconImage.size.width * messageTypeIconScale, height: messageTypeIconImage.size.height * messageTypeIconScale) + transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: messageTypeIconOffset, size: iconSize)) + mediaPreviewOffset.x += messageTypeIconImage.size.width * messageTypeIconScale + forwardedIconSpacing } else if strongSelf.forwardedIconNode.supernode != nil { strongSelf.forwardedIconNode.removeFromSupernode() } @@ -5048,11 +5096,11 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { nextTitleIconOrigin += 7.0 let titleBadgeFrame = CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: titleFrame.minY + floor((titleFrame.height - titleBadgeLayout.size.height) * 0.5)), size: titleBadgeLayout.size) nextTitleIconOrigin += titleBadgeLayout.size.width + 4.0 - titleBadgeNode.frame = titleBadgeFrame + transition.updateFrame(node: titleBadgeNode, frame: titleBadgeFrame) var titleBadgeBackgroundFrame = titleBadgeFrame.insetBy(dx: -4.0, dy: -2.0) titleBadgeBackgroundFrame.size.height -= 1.0 - backgroundView.frame = titleBadgeBackgroundFrame + transition.updateFrame(view: backgroundView, frame: titleBadgeBackgroundFrame) if item.presentationData.theme.overallDarkAppearance { backgroundView.tintColor = theme.titleColor.withMultipliedAlpha(0.1) } else { @@ -5064,20 +5112,24 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { titleBadge.textNode.removeFromSupernode() } - let separatorInset: CGFloat + let leftSeparatorInset: CGFloat + let rightSeparatorInset: CGFloat if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault { - separatorInset = 0.0 + leftSeparatorInset = 0.0 + rightSeparatorInset = 0.0 } else if (!nextIsPinned && isPinned) || last { - separatorInset = 0.0 + leftSeparatorInset = 0.0 + rightSeparatorInset = 0.0 } else { - separatorInset = editingOffset + leftInset + rawContentRect.origin.x + leftSeparatorInset = editingOffset + leftInset + rawContentRect.origin.x + rightSeparatorInset = 16.0 } - transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftSeparatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - leftSeparatorInset - rightSeparatorInset, height: separatorHeight))) if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { - transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0 - inlineNavigationLocation.progress) + strongSelf.updateSeparatorAlpha(transition: transition, inlineNavigationProgress: inlineNavigationLocation.progress) } else { - transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0) + strongSelf.updateSeparatorAlpha(transition: transition) } if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { @@ -5123,7 +5175,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.highlightedBackgroundNode.backgroundColor = highlightedBackgroundColor let topNegativeInset: CGFloat = 0.0 - 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)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: strongSelf.revealOffset, y: layoutOffset - separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset)) + transition.updateCornerRadius(node: strongSelf.highlightedBackgroundNode, cornerRadius: strongSelf.isRevealOptionsActive ? 26.0 : 0.0) if let peerPresence = peerPresence { strongSelf.peerPresenceManager?.reset(presence: EnginePeer.Presence(status: peerPresence.status, lastActivity: 0), isOnline: online) @@ -5267,8 +5320,23 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { super.updateRevealOffset(offset: offset, transition: transition) - + transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0)) + + let highlightedBackgroundFrame = self.highlightedBackgroundNode.frame + transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: offset, y: highlightedBackgroundFrame.minY), size: highlightedBackgroundFrame.size)) + } + + override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateIsHighlighted(transition: transition) + } + + override public func nextRevealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.nextRevealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateSeparatorAlpha(transition: transition) } override public func touchesToOtherItemsPrevented() { @@ -5472,7 +5540,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { @objc private func performLocalAccessibilityCustomAction(_ action: UIAccessibilityCustomAction) { if let action = action as? ChatListItemAccessibilityCustomAction { - self.revealOptionSelected(ItemListRevealOption(key: action.key, title: "", icon: .none, color: .black, textColor: .white), animated: false) + self.revealOptionSelected(ItemListRevealOption(key: action.key, title: "", icon: .none, color: .black, iconColor: .white, textColor: .white), animated: false) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 824887b5d4..8872c90184 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -103,7 +103,10 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: messageText = "" for message in messages { - if !message.text.isEmpty { + if let richText = message.richText { + messageText = richText.instantPage.previewText(strings: strings) + messageEntities = [] + } else if !message.text.isEmpty { messageText = message.text messageEntities = message._asMessage().textEntitiesAttribute?.entities ?? [] for entity in messageEntities { @@ -225,15 +228,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: break inner case let .Audio(isVoice, _, title, performer, _): if !message.text.isEmpty { - messageText = "🎤 \(messageText)" - processed = true - } else if isVoice { - if message.text.isEmpty { - messageText = strings.Message_Audio - } else { + if enableMediaEmoji { messageText = "🎤 \(messageText)" } processed = true + } else if isVoice { + messageText = strings.Message_Audio + processed = true break inner } else { let descriptionString: String @@ -311,7 +312,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: case _ as TelegramMediaContact: messageText = strings.Message_Contact case let game as TelegramMediaGame: - messageText = "🎮 \(game.title)" + messageText = game.title case let invoice as TelegramMediaInvoice: messageText = invoice.title case let action as TelegramMediaAction: @@ -438,15 +439,11 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: messageText = content.displayUrl } case let todo as TelegramMediaTodo: - let pollPrefix = "☑️ " - let entityOffset = (pollPrefix as NSString).length - messageText = "\(pollPrefix)\(todo.text)" + messageText = todo.text + customEmojiRanges = [] for entity in todo.textEntities { if case let .CustomEmoji(_, fileId) = entity.type { - if customEmojiRanges == nil { - customEmojiRanges = [] - } - let range = NSRange(location: entityOffset + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) customEmojiRanges?.append((range, attribute)) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 75a63892a7..2dc44bb69b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -16,7 +16,6 @@ import ChatListSearchItemHeader import PremiumUI import AnimationCache import MultiAnimationRenderer -import Postbox import ChatFolderLinkPreviewScreen import StoryContainerScreen import ChatListHeaderComponent @@ -107,6 +106,7 @@ public final class ChatListNodeInteraction { let openActiveSessions: () -> Void let openBirthdaySetup: () -> Void let performActiveSessionAction: (NewSessionReview, Bool) -> Void + let performBotConnectionReviewAction: (NewBotConnectionReview, Bool) -> Void let openChatFolderUpdates: () -> Void let hideChatFolderUpdates: () -> Void let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void @@ -167,6 +167,7 @@ public final class ChatListNodeInteraction { openActiveSessions: @escaping () -> Void, openBirthdaySetup: @escaping () -> Void, performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void, + performBotConnectionReviewAction: @escaping (NewBotConnectionReview, Bool) -> Void, openChatFolderUpdates: @escaping () -> Void, hideChatFolderUpdates: @escaping () -> Void, openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void, @@ -214,6 +215,7 @@ public final class ChatListNodeInteraction { self.openActiveSessions = openActiveSessions self.openBirthdaySetup = openBirthdaySetup self.performActiveSessionAction = performActiveSessionAction + self.performBotConnectionReviewAction = performBotConnectionReviewAction self.openChatFolderUpdates = openChatFolderUpdates self.hideChatFolderUpdates = hideChatFolderUpdates self.openStories = openStories @@ -503,7 +505,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } else { if filter.contains(.onlyWriteable) { if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { + if !canSendMessagesToPeer(peer) { enabled = false } if peerEntry.requiresPremiumForMessaging { @@ -864,7 +866,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } else { if filter.contains(.onlyWriteable) { if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { + if !canSendMessagesToPeer(peer) { enabled = false } if peerEntry.requiresPremiumForMessaging { @@ -1239,7 +1241,7 @@ public final class ChatListNode: ListViewImpl { case .Header, .Hole: return nil case let .PeerId(value): - return PeerId(value) + return EnginePeer.Id(value) case .ThreadId, .GroupId, .ContactId, .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .additionalCategory, .TopPeer: return nil } @@ -1797,6 +1799,17 @@ public final class ChatListNode: ListViewImpl { let _ = self.context.engine.privacy.terminateAnotherSession(id: newSessionReview.id).startStandalone() #endif } + }, performBotConnectionReviewAction: { [weak self] newBotConnectionReview, isPositive in + guard let self else { + return + } + + if isPositive { + let _ = self.context.engine.accountData.confirmBotConnectionReview(botId: newBotConnectionReview.botId).startStandalone() + } else { + let _ = removeNewBotConnectionReviews(postbox: self.context.account.postbox, botIds: [newBotConnectionReview.botId]).startStandalone() + let _ = self.context.engine.accountData.setAccountConnectedBot(bot: nil).startStandalone() + } }, openChatFolderUpdates: { [weak self] in guard let self else { return @@ -1884,18 +1897,22 @@ public final class ChatListNode: ListViewImpl { let savedMessagesPeer: Signal if case let .peers(filter, _, _, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location, self.chatListFilter == nil { - savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) - |> map(Optional.init) - |> map { peer in - return peer.flatMap(EnginePeer.init) + savedMessagesPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } } + |> map(Optional.init) } else { savedMessagesPeer = .single(nil) } - let hideArchivedFolderByDefault = context.account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.chatArchiveSettings]) + let hideArchivedFolderByDefault = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.chatArchiveSettings)) |> map { view -> Bool in - let settings: ChatArchiveSettings = view.values[ApplicationSpecificPreferencesKeys.chatArchiveSettings]?.get(ChatArchiveSettings.self) ?? .default + let settings: ChatArchiveSettings = view?.get(ChatArchiveSettings.self) ?? .default return settings.isHiddenByDefault } |> distinctUntilChanged @@ -2222,7 +2239,7 @@ public final class ChatListNode: ListViewImpl { if filter.contains(.onlyWriteable) && filter.contains(.excludeDisabled) { if let peer = peer.peers[peer.peerId] { - if !canSendMessagesToPeer(peer._asPeer()) { + if !canSendMessagesToPeer(peer) { return false } } else { @@ -2584,7 +2601,7 @@ public final class ChatListNode: ListViewImpl { } let presentationData = state.presentationData - return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode, forceAllUpdated: forceAllUpdated) + return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, scrollPosition: updatedScrollPosition, searchMode: searchMode, forceAllUpdated: forceAllUpdated) |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: accountIsPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, presentationData: presentationData, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } @@ -2614,7 +2631,7 @@ public final class ChatListNode: ListViewImpl { strongSelf.enqueueHistoryPreloadUpdate() } - var refreshStoryPeerIds: [PeerId] = [] + var refreshStoryPeerIds: [EnginePeer.Id] = [] var isHiddenItemVisible = false if let range = range.visibleRange { let entryCount = chatListView.filteredEntries.count diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index d98fddcd84..f9c4efa872 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import TelegramPresentationData import MergeLists diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index 630edbf917..8de41fe517 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -48,10 +47,10 @@ struct ChatListNodeViewTransition { } public enum ChatListNodeViewScrollPosition { - case index(index: ChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) + case index(index: EngineChatListIndex, position: ListViewScrollPosition, directionHint: ListViewScrollToItemDirectionHint, animated: Bool) } -func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, previewing: Bool, disableAnimations: Bool, account: Account, scrollPosition: ChatListNodeViewScrollPosition?, searchMode: Bool, forceAllUpdated: Bool) -> Signal { +func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toView: ChatListNodeView, reason: ChatListNodeViewTransitionReason, previewing: Bool, disableAnimations: Bool, scrollPosition: ChatListNodeViewScrollPosition?, searchMode: Bool, forceAllUpdated: Bool) -> Signal { return Signal { subscriber in let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromView?.filteredEntries ?? [], rightList: toView.filteredEntries, allUpdated: forceAllUpdated) diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index 6120890cf6..1e3df440cf 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -5,7 +5,6 @@ import SwiftSignalKit import AsyncDisplayKit import TelegramPresentationData import AccountContext -import Postbox import TelegramUIPreferences import TelegramCore @@ -13,10 +12,10 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL return context.engine.peers.updatedChatListFilters() |> distinctUntilChanged |> mapToSignal { filters -> Signal<(Int, [(ChatListFilter, Int, Bool)]), NoError> in - var unreadCountItems: [UnreadMessageCountsItem] = [] + var unreadCountItems: [EngineRawUnreadMessageCountsItem] = [] unreadCountItems.append(.totalInGroup(.root)) - var additionalPeerIds = Set() - var additionalGroupIds = Set() + var additionalPeerIds = Set() + var additionalGroupIds = Set() for case let .filter(_, _, _, data) in filters { additionalPeerIds.formUnion(data.includePeers.peers) additionalPeerIds.formUnion(data.excludePeers) @@ -33,9 +32,9 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL unreadCountItems.append(.totalInGroup(groupId)) } - let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications])) - let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems) - var keys: [PostboxViewKey] = [] + let globalNotificationsKey: EngineRawPostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications])) + let unreadKey: EngineRawPostboxViewKey = .unreadCounts(items: unreadCountItems) + var keys: [EngineRawPostboxViewKey] = [] keys.append(globalNotificationsKey) keys.append(unreadKey) for peerId in additionalPeerIds { @@ -44,12 +43,12 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL return context.account.postbox.combinedView(keys: keys) |> map { view -> (Int, [(ChatListFilter, Int, Bool)]) in - guard let unreadCounts = view.views[unreadKey] as? UnreadMessageCountsView else { + guard let unreadCounts = view.views[unreadKey] as? EngineRawUnreadMessageCountsView else { return (0, []) } var globalNotificationSettings: GlobalNotificationSettingsSet - if let settingsView = view.views[globalNotificationsKey] as? PreferencesView, let settings = settingsView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settingsView = view.views[globalNotificationsKey] as? EngineRawPreferencesView, let settings = settingsView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { globalNotificationSettings = settings.effective } else { globalNotificationSettings = GlobalNotificationSettings.defaultSettings.effective @@ -57,9 +56,9 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL var result: [(ChatListFilter, Int, Bool)] = [] - var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool, PeerGroupId?, Bool)] = [:] + var peerTagAndCount: [EnginePeer.Id: (EnginePeerSummaryCounterTags, Int, Bool, EnginePeerGroupId?, Bool)] = [:] - var totalStates: [PeerGroupId: ChatListTotalUnreadState] = [:] + var totalStates: [EnginePeerGroupId: EngineChatListTotalUnreadState] = [:] for entry in unreadCounts.entries { switch entry { case let .total(_, state): @@ -68,7 +67,7 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL totalStates[groupId] = state case let .peer(peerId, state): if let state = state, state.isUnread { - if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer { + if let peerView = view.views[.basicPeer(peerId)] as? EngineRawBasicPeerView, let peer = peerView.peer { let tag = context.account.postbox.seedConfiguration.peerSummaryCounterTags(peer, peerView.isContact) var peerCount = Int(state.count) @@ -113,7 +112,7 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL var count = 0 var unmutedUnreadCount = 0 if case let .filter(_, _, _, data) = filter { - var tags: [PeerSummaryCounterTags] = [] + var tags: [EnginePeerSummaryCounterTags] = [] if data.categories.contains(.contacts) { tags.append(.contact) } diff --git a/submodules/ChatMessageBackground/BUILD b/submodules/ChatMessageBackground/BUILD index 84fd9a5074..51dc28d4b1 100644 --- a/submodules/ChatMessageBackground/BUILD +++ b/submodules/ChatMessageBackground/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", ], diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift index 21d6e3905a..878ccf37c9 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatMediaInputNodeInteraction.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import TelegramUIPreferences @@ -22,27 +21,27 @@ public enum ChatMediaInputGifMode: Equatable { } public final class ChatMediaInputNodeInteraction { - public let navigateToCollectionId: (ItemCollectionId) -> Void + public let navigateToCollectionId: (EngineItemCollectionId) -> Void public let navigateBackToStickers: () -> Void public let setGifMode: (ChatMediaInputGifMode) -> Void public let openSettings: () -> Void - public let openTrending: (ItemCollectionId?) -> Void - public let dismissTrendingPacks: ([ItemCollectionId]) -> Void + public let openTrending: (EngineItemCollectionId?) -> Void + public let dismissTrendingPacks: ([EngineItemCollectionId]) -> Void public let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void public let openPeerSpecificSettings: () -> Void public let dismissPeerSpecificSettings: () -> Void public let clearRecentlyUsedStickers: () -> Void public var stickerSettings: ChatInterfaceStickerSettings? - public var highlightedStickerItemCollectionId: ItemCollectionId? - public var highlightedItemCollectionId: ItemCollectionId? + public var highlightedStickerEngineItemCollectionId: EngineItemCollectionId? + public var highlightedEngineItemCollectionId: EngineItemCollectionId? public var highlightedGifMode: ChatMediaInputGifMode = .recent public var previewedStickerPackItemFile: TelegramMediaFile? public var appearanceTransition: CGFloat = 1.0 public var displayStickerPlaceholder = true public var displayStickerPackManageControls = true - public init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { + public init(navigateToCollectionId: @escaping (EngineItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (EngineItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([EngineItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) { self.navigateToCollectionId = navigateToCollectionId self.navigateBackToStickers = navigateBackToStickers self.setGifMode = setGifMode diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 0833307f24..7a3b2fd374 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import Display @@ -71,18 +70,18 @@ public final class ChatPanelInterfaceInteraction { case editPrice } - public let setupReplyMessage: (MessageId?, EngineMessageReplyInnerSubject?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void - public let setupEditMessage: (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void - public let beginMessageSelection: ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let setupReplyMessage: (EngineMessage.Id?, EngineMessageReplyInnerSubject?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void + public let setupEditMessage: (EngineMessage.Id?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void + public let beginMessageSelection: ([EngineMessage.Id], @escaping (ContainedViewLayoutTransition) -> Void) -> Void public let cancelMessageSelection: (ContainedViewLayoutTransition) -> Void - public let deleteSelectedMessages: () -> Void + public let deleteSelectedMessages: (UIView?) -> Void public let reportSelectedMessages: () -> Void - public let reportMessages: ([Message], ContextControllerProtocol?) -> Void - public let blockMessageAuthor: (Message, ContextControllerProtocol?) -> Void - public let deleteMessages: ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void + public let reportMessages: ([EngineRawMessage], ContextControllerProtocol?) -> Void + public let blockMessageAuthor: (EngineRawMessage, ContextControllerProtocol?) -> Void + public let deleteMessages: ([EngineRawMessage], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void public let forwardSelectedMessages: () -> Void public let forwardCurrentForwardMessages: () -> Void - public let forwardMessages: ([Message]) -> Void + public let forwardMessages: ([EngineRawMessage]) -> Void public let updateForwardOptionsState: ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void public let presentForwardOptions: (UIView) -> Void public let presentReplyOptions: (UIView) -> Void @@ -90,7 +89,7 @@ public final class ChatPanelInterfaceInteraction { public let presentSuggestPostOptions: () -> Void public let shareSelectedMessages: () -> Void public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void - public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void + public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, EngineMessage.Id?)) -> Void public let openStickers: () -> Void public let editMessage: () -> Void public let beginMessageSearch: (ChatSearchDomain, String) -> Void @@ -100,17 +99,17 @@ public final class ChatPanelInterfaceInteraction { public let openSearchResults: () -> Void public let openCalendarSearch: () -> Void public let toggleMembersSearch: (Bool) -> Void - public let navigateToMessage: (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void - public let navigateToChat: (PeerId) -> Void - public let navigateToProfile: (PeerId) -> Void + public let navigateToMessage: (EngineMessage.Id, Bool, Bool, ChatLoadingMessageSubject) -> Void + public let navigateToChat: (EnginePeer.Id) -> Void + public let navigateToProfile: (EnginePeer.Id) -> Void public let openPeerInfo: () -> Void public let togglePeerNotifications: () -> Void public let sendContextResult: (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool - public let sendBotCommand: (Peer, String) -> Void + public let sendBotCommand: (EngineRawPeer, String) -> Void public let sendShortcut: (Int32) -> Void public let openEditShortcuts: () -> Void public let sendBotStart: (String?) -> Void - public let botSwitchChatWithPayload: (PeerId, String) -> Void + public let botSwitchChatWithPayload: (EnginePeer.Id, String) -> Void public let beginMediaRecording: (Bool) -> Void public let finishMediaRecording: (ChatFinishMediaRecordingAction) -> Void public let stopMediaRecording: () -> Void @@ -122,20 +121,20 @@ public final class ChatPanelInterfaceInteraction { public let displayVideoUnmuteTip: (CGPoint?) -> Void public let switchMediaRecordingMode: () -> Void public let setupMessageAutoremoveTimeout: () -> Void - public let sendSticker: (FileMediaReference, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool + public let sendSticker: (FileMediaReference, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool public let editSticker: (TelegramMediaFile) -> Void public let unblockPeer: () -> Void - public let pinMessage: (MessageId, ContextControllerProtocol?) -> Void - public let unpinMessage: (MessageId, Bool, ContextControllerProtocol?) -> Void + public let pinMessage: (EngineMessage.Id, ContextControllerProtocol?) -> Void + public let unpinMessage: (EngineMessage.Id, Bool, ContextControllerProtocol?) -> Void public let unpinAllMessages: () -> Void - public let openPinnedList: (MessageId) -> Void + public let openPinnedList: (EngineMessage.Id) -> Void public let shareAccountContact: () -> Void public let reportPeer: () -> Void public let presentPeerContact: () -> Void public let dismissReportPeer: () -> Void public let deleteChat: () -> Void public let beginCall: (Bool) -> Void - public let toggleMessageStickerStarred: (MessageId) -> Void + public let toggleMessageStickerStarred: (EngineMessage.Id) -> Void public let presentController: (ViewController, Any?) -> Void public let presentControllerInCurrent: (ViewController, Any?) -> Void public let getNavigationController: () -> NavigationController? @@ -143,8 +142,8 @@ public final class ChatPanelInterfaceInteraction { public let navigateFeed: () -> Void public let openGrouping: () -> Void public let toggleSilentPost: () -> Void - public let requestUnvoteInMessage: (MessageId) -> Void - public let requestStopPollInMessage: (MessageId) -> Void + public let requestUnvoteInMessage: (EngineMessage.Id) -> Void + public let requestStopPollInMessage: (EngineMessage.Id) -> Void public let updateInputLanguage: (@escaping (String?) -> String?) -> Void public let unarchiveChat: () -> Void public let openLinkEditing: () -> Void @@ -153,12 +152,11 @@ public final class ChatPanelInterfaceInteraction { public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void public let openScheduledMessages: () -> Void public let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void - public let openPeersNearby: () -> Void public let unarchivePeer: () -> Void public let scrollToTop: () -> Void - public let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void + public let viewReplies: (EngineMessage.Id?, ChatReplyThreadMessage) -> Void public let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void - public let editMessageMedia: (MessageId, Bool) -> Void + public let editMessageMedia: (EngineMessage.Id, Bool) -> Void public let joinGroupCall: (CachedChannelData.ActiveCall) -> Void public let presentInviteMembers: () -> Void public let presentGigagroupHelp: () -> Void @@ -179,7 +177,7 @@ public final class ChatPanelInterfaceInteraction { public let addDoNotTranslateLanguage: (String) -> Void public let hideTranslationPanel: () -> Void public let openPremiumGift: () -> Void - public let openSuggestPost: (Message?, OpenSuggestPostMode) -> Void + public let openSuggestPost: (EngineRawMessage?, OpenSuggestPostMode) -> Void public let openPremiumRequiredForMessaging: () -> Void public let openStarsPurchase: (Int64?) -> Void public let openMessagePayment: () -> Void @@ -190,7 +188,7 @@ public final class ChatPanelInterfaceInteraction { public let openBoostToUnrestrict: () -> Void public let updateRecordingTrimRange: (Double, Double, Bool, Bool) -> Void public let dismissAllTooltips: () -> Void - public let editTodoMessage: (MessageId, Int32?, Bool) -> Void + public let editTodoMessage: (EngineMessage.Id, Int32?, Bool) -> Void public let dismissUrlPreview: () -> Void public let dismissForwardMessages: () -> Void public let dismissSuggestPost: () -> Void @@ -204,18 +202,18 @@ public final class ChatPanelInterfaceInteraction { public let statuses: ChatPanelInterfaceInteractionStatuses? public init( - setupReplyMessage: @escaping (MessageId?, EngineMessageReplyInnerSubject?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void, - setupEditMessage: @escaping (MessageId?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, - beginMessageSelection: @escaping ([MessageId], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, + setupReplyMessage: @escaping (EngineMessage.Id?, EngineMessageReplyInnerSubject?, @escaping (ContainedViewLayoutTransition, @escaping () -> Void) -> Void) -> Void, + setupEditMessage: @escaping (EngineMessage.Id?, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, + beginMessageSelection: @escaping ([EngineMessage.Id], @escaping (ContainedViewLayoutTransition) -> Void) -> Void, cancelMessageSelection: @escaping (ContainedViewLayoutTransition) -> Void, - deleteSelectedMessages: @escaping () -> Void, + deleteSelectedMessages: @escaping (UIView?) -> Void, reportSelectedMessages: @escaping () -> Void, - reportMessages: @escaping ([Message], ContextControllerProtocol?) -> Void, - blockMessageAuthor: @escaping (Message, ContextControllerProtocol?) -> Void, - deleteMessages: @escaping ([Message], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void, + reportMessages: @escaping ([EngineRawMessage], ContextControllerProtocol?) -> Void, + blockMessageAuthor: @escaping (EngineRawMessage, ContextControllerProtocol?) -> Void, + deleteMessages: @escaping ([EngineRawMessage], ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void, forwardSelectedMessages: @escaping () -> Void, forwardCurrentForwardMessages: @escaping () -> Void, - forwardMessages: @escaping ([Message]) -> Void, + forwardMessages: @escaping ([EngineRawMessage]) -> Void, updateForwardOptionsState: @escaping ((ChatInterfaceForwardOptionsState) -> ChatInterfaceForwardOptionsState) -> Void, presentForwardOptions: @escaping (UIView) -> Void, presentReplyOptions: @escaping (UIView) -> Void, @@ -223,7 +221,7 @@ public final class ChatPanelInterfaceInteraction { presentSuggestPostOptions: @escaping () -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, - updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, + updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, EngineMessage.Id?)) -> Void, openStickers: @escaping () -> Void, editMessage: @escaping () -> Void, beginMessageSearch: @escaping (ChatSearchDomain, String) -> Void, @@ -233,17 +231,17 @@ public final class ChatPanelInterfaceInteraction { navigateMessageSearch: @escaping (ChatPanelSearchNavigationAction) -> Void, openCalendarSearch: @escaping () -> Void, toggleMembersSearch: @escaping (Bool) -> Void, - navigateToMessage: @escaping (MessageId, Bool, Bool, ChatLoadingMessageSubject) -> Void, - navigateToChat: @escaping (PeerId) -> Void, - navigateToProfile: @escaping (PeerId) -> Void, + navigateToMessage: @escaping (EngineMessage.Id, Bool, Bool, ChatLoadingMessageSubject) -> Void, + navigateToChat: @escaping (EnginePeer.Id) -> Void, + navigateToProfile: @escaping (EnginePeer.Id) -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, - sendBotCommand: @escaping (Peer, String) -> Void, + sendBotCommand: @escaping (EngineRawPeer, String) -> Void, sendShortcut: @escaping (Int32) -> Void, openEditShortcuts: @escaping () -> Void, sendBotStart: @escaping (String?) -> Void, - botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, + botSwitchChatWithPayload: @escaping (EnginePeer.Id, String) -> Void, beginMediaRecording: @escaping (Bool) -> Void, finishMediaRecording: @escaping (ChatFinishMediaRecordingAction) -> Void, stopMediaRecording: @escaping () -> Void, @@ -255,20 +253,20 @@ public final class ChatPanelInterfaceInteraction { displayVideoUnmuteTip: @escaping (CGPoint?) -> Void, switchMediaRecordingMode: @escaping () -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, - sendSticker: @escaping (FileMediaReference, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool, + sendSticker: @escaping (FileMediaReference, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool, editSticker: @escaping (TelegramMediaFile) -> Void, unblockPeer: @escaping () -> Void, - pinMessage: @escaping (MessageId, ContextControllerProtocol?) -> Void, - unpinMessage: @escaping (MessageId, Bool, ContextControllerProtocol?) -> Void, + pinMessage: @escaping (EngineMessage.Id, ContextControllerProtocol?) -> Void, + unpinMessage: @escaping (EngineMessage.Id, Bool, ContextControllerProtocol?) -> Void, unpinAllMessages: @escaping () -> Void, - openPinnedList: @escaping (MessageId) -> Void, + openPinnedList: @escaping (EngineMessage.Id) -> Void, shareAccountContact: @escaping () -> Void, reportPeer: @escaping () -> Void, presentPeerContact: @escaping () -> Void, dismissReportPeer: @escaping () -> Void, deleteChat: @escaping () -> Void, beginCall: @escaping (Bool) -> Void, - toggleMessageStickerStarred: @escaping (MessageId) -> Void, + toggleMessageStickerStarred: @escaping (EngineMessage.Id) -> Void, presentController: @escaping (ViewController, Any?) -> Void, presentControllerInCurrent: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?, @@ -276,8 +274,8 @@ public final class ChatPanelInterfaceInteraction { navigateFeed: @escaping () -> Void, openGrouping: @escaping () -> Void, toggleSilentPost: @escaping () -> Void, - requestUnvoteInMessage: @escaping (MessageId) -> Void, - requestStopPollInMessage: @escaping (MessageId) -> Void, + requestUnvoteInMessage: @escaping (EngineMessage.Id) -> Void, + requestStopPollInMessage: @escaping (EngineMessage.Id) -> Void, updateInputLanguage: @escaping ((String?) -> String?) -> Void, unarchiveChat: @escaping () -> Void, openLinkEditing: @escaping () -> Void, @@ -285,17 +283,16 @@ public final class ChatPanelInterfaceInteraction { displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void, displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void, openScheduledMessages: @escaping () -> Void, - openPeersNearby: @escaping () -> Void, displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void, unarchivePeer: @escaping () -> Void, scrollToTop: @escaping () -> Void, - viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, + viewReplies: @escaping (EngineMessage.Id?, ChatReplyThreadMessage) -> Void, activatePinnedListPreview: @escaping (ASDisplayNode, ContextGesture) -> Void, joinGroupCall: @escaping (CachedChannelData.ActiveCall) -> Void, presentInviteMembers: @escaping () -> Void, presentGigagroupHelp: @escaping () -> Void, openMonoforum: @escaping () -> Void, - editMessageMedia: @escaping (MessageId, Bool) -> Void, + editMessageMedia: @escaping (EngineMessage.Id, Bool) -> Void, updateShowCommands: @escaping ((Bool) -> Bool) -> Void, updateShowSendAsPeers: @escaping ((Bool) -> Bool) -> Void, openInviteRequests: @escaping () -> Void, @@ -312,14 +309,14 @@ public final class ChatPanelInterfaceInteraction { addDoNotTranslateLanguage: @escaping (String) -> Void, hideTranslationPanel: @escaping () -> Void, openPremiumGift: @escaping () -> Void, - openSuggestPost: @escaping (Message?, OpenSuggestPostMode) -> Void, + openSuggestPost: @escaping (EngineRawMessage?, OpenSuggestPostMode) -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, openStarsPurchase: @escaping (Int64?) -> Void, openMessagePayment: @escaping () -> Void, openBoostToUnrestrict: @escaping () -> Void, updateRecordingTrimRange: @escaping (Double, Double, Bool, Bool) -> Void, dismissAllTooltips: @escaping () -> Void, - editTodoMessage: @escaping (MessageId, Int32?, Bool) -> Void, + editTodoMessage: @escaping (EngineMessage.Id, Int32?, Bool) -> Void, dismissUrlPreview: @escaping () -> Void, dismissForwardMessages: @escaping () -> Void, dismissSuggestPost: @escaping () -> Void, @@ -417,7 +414,6 @@ public final class ChatPanelInterfaceInteraction { self.displaySlowmodeTooltip = displaySlowmodeTooltip self.displaySendMessageOptions = displaySendMessageOptions self.openScheduledMessages = openScheduledMessages - self.openPeersNearby = openPeersNearby self.displaySearchResultsTooltip = displaySearchResultsTooltip self.unarchivePeer = unarchivePeer self.scrollToTop = scrollToTop @@ -472,14 +468,14 @@ public final class ChatPanelInterfaceInteraction { public convenience init( updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, - updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, + updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, EngineMessage.Id?)) -> Void, openLinkEditing: @escaping () -> Void ) { self.init(setupReplyMessage: { _, _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { + }, deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, reportMessages: { _, _ in }, blockMessageAuthor: { _, _ in @@ -557,7 +553,6 @@ public final class ChatPanelInterfaceInteraction { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { - }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index f554ef297b..d43b03e3c9 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -541,7 +541,6 @@ public final class ChatPresentationInterfaceState: Equatable { public let hasScheduledMessages: Bool public let autoremoveTimeout: Int32? public let subject: ChatControllerSubject? - public let peerNearbyData: ChatPeerNearbyData? public let greetingData: ChatGreetingData? public let pendingUnpinnedAllMessages: Bool public let activeGroupCallInfo: ChatActiveGroupCallInfo? @@ -589,8 +588,9 @@ public final class ChatPresentationInterfaceState: Equatable { public let removePaidMessageFeeData: RemovePaidMessageFeeData? public let viewForumAsMessages: Bool public let hasTopics: Bool + public let canStopIncomingStreamingMessage: Bool - public init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, preferredGlassType: GlassType, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, threadData: ThreadData?, isGeneralThreadClosed: Bool?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, businessIntro: TelegramBusinessIntro?) { + public init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, preferredGlassType: GlassType, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, hasActiveGroupCall: Bool, threadData: ThreadData?, isGeneralThreadClosed: Bool?, replyMessage: Message?, accountPeerColor: AccountPeerColor?, businessIntro: TelegramBusinessIntro?) { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil @@ -639,7 +639,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.hasScheduledMessages = false self.autoremoveTimeout = nil self.subject = subject - self.peerNearbyData = peerNearbyData self.greetingData = greetingData self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages self.activeGroupCallInfo = activeGroupCallInfo @@ -689,6 +688,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.removePaidMessageFeeData = nil self.viewForumAsMessages = false self.hasTopics = false + self.canStopIncomingStreamingMessage = false } public init( @@ -740,7 +740,6 @@ public final class ChatPresentationInterfaceState: Equatable { hasScheduledMessages: Bool, autoremoveTimeout: Int32?, subject: ChatControllerSubject?, - peerNearbyData: ChatPeerNearbyData?, greetingData: ChatGreetingData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?, @@ -787,7 +786,8 @@ public final class ChatPresentationInterfaceState: Equatable { persistentData: PersistentPeerData, removePaidMessageFeeData: RemovePaidMessageFeeData?, viewForumAsMessages: Bool, - hasTopics: Bool + hasTopics: Bool, + canStopIncomingStreamingMessage: Bool ) { self.interfaceState = interfaceState self.chatLocation = chatLocation @@ -837,7 +837,6 @@ public final class ChatPresentationInterfaceState: Equatable { self.hasScheduledMessages = hasScheduledMessages self.autoremoveTimeout = autoremoveTimeout self.subject = subject - self.peerNearbyData = peerNearbyData self.greetingData = greetingData self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages self.activeGroupCallInfo = activeGroupCallInfo @@ -885,6 +884,7 @@ public final class ChatPresentationInterfaceState: Equatable { self.removePaidMessageFeeData = removePaidMessageFeeData self.viewForumAsMessages = viewForumAsMessages self.hasTopics = hasTopics + self.canStopIncomingStreamingMessage = canStopIncomingStreamingMessage } public static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool { @@ -1041,9 +1041,6 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.subject != rhs.subject { return false } - if lhs.peerNearbyData != rhs.peerNearbyData { - return false - } if lhs.greetingData != rhs.greetingData { return false } @@ -1185,47 +1182,50 @@ public final class ChatPresentationInterfaceState: Equatable { if lhs.hasTopics != rhs.hasTopics { return false } + if lhs.canStopIncomingStreamingMessage != rhs.canStopIncomingStreamingMessage { + return false + } return true } public func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedFocusedPollAddOptionMessageId(_ focusedPollAddOptionMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedChatLocation(_ chatLocation: ChatLocation) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsManagedBot(_ isManagedBot: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -1242,323 +1242,327 @@ public final class ChatPresentationInterfaceState: Equatable { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedUrlPreview(_ urlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedEditingUrlPreview(_ editingUrlPreview: UrlPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHistoryFilter(_ historyFilter: HistoryFilter?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedDisplayHistoryFilterAsList(_ displayHistoryFilterAsList: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPresentationReady(_ presentationReady: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPreferredGlassType(_ glassType: GlassType) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: glassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: glassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSubject(_ subject: ChatControllerSubject?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAutoremoveTimeout(_ autoremoveTimeout: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasActiveGroupCall(_ hasActiveGroupCall: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedReportReason(_ reportReason: ReportReasonData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedShowCommands(_ showCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasBotCommands(_ hasBotCommands: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedShowSendAsPeers(_ showSendAsPeers: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSendAsPeers(_ sendAsPeers: [SendAsPeer]?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedCurrentSendAsPeerId(_ currentSendAsPeerId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedBotMenuButton(_ botMenuButton: BotMenuButton) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedShowWebView(_ showWebView: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedCopyProtectionEnabled(_ copyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedMyCopyProtectionEnabled(_ myCopyProtectionEnabled: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasAtLeast3Messages(_ hasAtLeast3Messages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasPlentyOfMessages(_ hasPlentyOfMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsPremium(_ isPremium: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSuggestPremiumGift(_ suggestPremiumGift: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedForceInputCommandsHidden(_ forceInputCommandsHidden: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedCustomEmojiAvailable(_ customEmojiAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedThreadData(_ threadData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedForumTopicData(_ forumTopicData: ThreadData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsGeneralThreadClosed(_ isGeneralThreadClosed: Bool?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedTranslationState(_ translationState: ChatPresentationTranslationState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedReplyMessage(_ replyMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAccountPeerColor(_ accountPeerColor: AccountPeerColor?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSavedMessagesTopicPeer(_ savedMessagesTopicPeer: EnginePeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasSearchTags(_ hasSearchTags: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedIsPremiumRequiredForMessaging(_ isPremiumRequiredForMessaging: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedSendPaidMessageStars(_ sendPaidMessageStars: StarsAmount?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAcknowledgedPaidMessage(_ acknowledgedPaidMessage: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasSavedChats(_ hasSavedChats: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAppliedBoosts(_ appliedBoosts: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedBoostsToUnrestrict(_ boostsToUnrestrict: Int32?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedBusinessIntro(_ businessIntro: TelegramBusinessIntro?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasBirthdayToday(_ hasBirthdayToday: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAdMessage(_ adMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPeerVerification(_ peerVerification: PeerVerification?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedStarGiftsAvailable(_ starGiftsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedAlwaysShowGiftButton(_ alwaysShowGiftButton: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedDisallowedGifts(_ disallowedGifts: TelegramDisallowedGifts?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedPersistentData(_ persistentData: PersistentPeerData) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedRemovePaidMessageFeeData(_ removePaidMessageFeeData: RemovePaidMessageFeeData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedViewForumAsMessages(_ viewForumAsMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: viewForumAsMessages, hasTopics: self.hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) } public func updatedHasTopics(_ hasTopics: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, peerNearbyData: self.peerNearbyData, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: hasTopics) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: hasTopics, canStopIncomingStreamingMessage: self.canStopIncomingStreamingMessage) + } + + public func updatedCanStopIncomingStreamingMessage(_ canStopIncomingStreamingMessage: Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, isManagedBot: self.isManagedBot, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, focusedPollAddOptionMessageId: self.focusedPollAddOptionMessageId, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, historyFilter: self.historyFilter, displayHistoryFilterAsList: self.displayHistoryFilterAsList, presentationReady: self.presentationReady, chatWallpaper: self.chatWallpaper, theme: self.theme, preferredGlassType: self.preferredGlassType, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, subject: self.subject, greetingData: self.greetingData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo, hasActiveGroupCall: self.hasActiveGroupCall, reportReason: self.reportReason, showCommands: self.showCommands, hasBotCommands: self.hasBotCommands, showSendAsPeers: self.showSendAsPeers, sendAsPeers: self.sendAsPeers, botMenuButton: self.botMenuButton, showWebView: self.showWebView, currentSendAsPeerId: self.currentSendAsPeerId, copyProtectionEnabled: self.copyProtectionEnabled, myCopyProtectionEnabled: self.myCopyProtectionEnabled, hasAtLeast3Messages: self.hasAtLeast3Messages, hasPlentyOfMessages: self.hasPlentyOfMessages, isPremium: self.isPremium, premiumGiftOptions: self.premiumGiftOptions, suggestPremiumGift: self.suggestPremiumGift, forceInputCommandsHidden: self.forceInputCommandsHidden, voiceMessagesAvailable: self.voiceMessagesAvailable, customEmojiAvailable: self.customEmojiAvailable, threadData: self.threadData, forumTopicData: self.forumTopicData, isGeneralThreadClosed: self.isGeneralThreadClosed, translationState: self.translationState, replyMessage: self.replyMessage, accountPeerColor: self.accountPeerColor, savedMessagesTopicPeer: self.savedMessagesTopicPeer, hasSearchTags: self.hasSearchTags, isPremiumRequiredForMessaging: self.isPremiumRequiredForMessaging, sendPaidMessageStars: self.sendPaidMessageStars, acknowledgedPaidMessage: self.acknowledgedPaidMessage, hasSavedChats: self.hasSavedChats, appliedBoosts: self.appliedBoosts, boostsToUnrestrict: self.boostsToUnrestrict, businessIntro: self.businessIntro, hasBirthdayToday: self.hasBirthdayToday, adMessage: self.adMessage, peerVerification: self.peerVerification, starGiftsAvailable: self.starGiftsAvailable, alwaysShowGiftButton: self.alwaysShowGiftButton, disallowedGifts: self.disallowedGifts, persistentData: self.persistentData, removePaidMessageFeeData: self.removePaidMessageFeeData, viewForumAsMessages: self.viewForumAsMessages, hasTopics: self.hasTopics, canStopIncomingStreamingMessage: canStopIncomingStreamingMessage) } } @@ -1579,7 +1583,7 @@ public func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentati public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool { if let peer = state.renderedPeer?.peer { let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state) - if canSendMessagesToPeer(peer, ignoreDefault: canBypassRestrictions) { + if canSendMessagesToPeer(EnginePeer(peer), ignoreDefault: canBypassRestrictions) { return true } else { return false @@ -1598,3 +1602,22 @@ public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bo return false } } + +public func canSendReactionsToChat(_ state: ChatPresentationInterfaceState) -> Bool { + if let peer = state.renderedPeer?.peer { + let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state) + return canSendReactionsToPeer(EnginePeer(peer), ignoreDefault: canBypassRestrictions) + } else if case .customChatContents = state.chatLocation { + if case let .customChatContents(contents) = state.subject { + if case .hashTagSearch = contents.kind { + return false + } else { + return true + } + } else { + return true + } + } else { + return false + } +} diff --git a/submodules/ChatSendMessageActionUI/BUILD b/submodules/ChatSendMessageActionUI/BUILD index 58b4cdab9e..d2d4bb1471 100644 --- a/submodules/ChatSendMessageActionUI/BUILD +++ b/submodules/ChatSendMessageActionUI/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ContextUI:ContextUI", diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index fcb8304aab..fb8d70f0d8 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -90,7 +90,8 @@ public func makeChatSendMessageActionSheetController( openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]? = nil, availableMessageEffects: AvailableMessageEffects? = nil, - isPremium: Bool = false + isPremium: Bool = false, + richTextPreview: ChatSendMessageContextScreenRichTextPreview? = nil ) -> ChatSendMessageActionSheetController { return ChatSendMessageContextScreen( initialData: initialData, @@ -111,6 +112,7 @@ public func makeChatSendMessageActionSheetController( openPremiumPaywall: openPremiumPaywall, reactionItems: reactionItems, availableMessageEffects: availableMessageEffects, - isPremium: isPremium + isPremium: isPremium, + richTextPreview: richTextPreview ) } diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index 7e783df785..ceddd73c00 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -7,7 +7,6 @@ import TelegramPresentationData import AccountContext import ContextUI import TelegramCore -import Postbox import TextFormat import ReactionSelectionNode import ViewControllerComponent @@ -50,6 +49,13 @@ public protocol ChatSendMessageContextScreenMediaPreview: AnyObject { func update(containerSize: CGSize, transition: ComponentTransition) -> CGSize } +public protocol ChatSendMessageContextScreenRichTextPreview: AnyObject { + var view: UIView { get } + // Lays out the rich content for the given bubble width and theme, returning its content + // size. Called every layout pass; the implementation memoizes internally. + func update(boundingWidth: CGFloat, presentationData: PresentationData, transition: ComponentTransition) -> CGSize +} + final class ChatSendMessageContextScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -72,6 +78,7 @@ final class ChatSendMessageContextScreenComponent: Component { let reactionItems: [ReactionItem]? let availableMessageEffects: AvailableMessageEffects? let isPremium: Bool + let richTextPreview: ChatSendMessageContextScreenRichTextPreview? init( initialData: ChatSendMessageContextScreen.InitialData, @@ -92,7 +99,8 @@ final class ChatSendMessageContextScreenComponent: Component { openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]?, availableMessageEffects: AvailableMessageEffects?, - isPremium: Bool + isPremium: Bool, + richTextPreview: ChatSendMessageContextScreenRichTextPreview? = nil ) { self.initialData = initialData self.context = context @@ -113,6 +121,7 @@ final class ChatSendMessageContextScreenComponent: Component { self.reactionItems = reactionItems self.availableMessageEffects = availableMessageEffects self.isPremium = isPremium + self.richTextPreview = richTextPreview } static func ==(lhs: ChatSendMessageContextScreenComponent, rhs: ChatSendMessageContextScreenComponent) -> Bool { @@ -776,19 +785,35 @@ final class ChatSendMessageContextScreenComponent: Component { if case .editMessage = component.params { isEditMessage = true } - + + // The bubble's right edge is pinned to the send button (see readyMessageItemFrame). + // Constrain the rich layout to the span between that edge and a fixed left margin + // (independent of where the source text input sits) so it isn't laid out at the + // full container width. + let richSendButtonWidth: CGFloat + if component.sourceSendButton is ContextExtractedContentContainingView { + richSendButtonWidth = sourceSendButtonFrame.width + } else { + richSendButtonWidth = min(sourceSendButtonFrame.width, 40.0) + } + let richBubbleRightEdge = sourceSendButtonFrame.maxX - richSendButtonWidth + let richBubbleLeftMargin: CGFloat = 16.0 + let maxRichBubbleWidth = max(1.0, min(messageItemViewContainerSize.width, richBubbleRightEdge - richBubbleLeftMargin)) + let messageItemSize = messageItemView.update( context: component.context, presentationData: presentationData, backgroundNode: wallpaperBackgroundNode, textString: textString, + richTextPreview: component.richTextPreview, + maxRichBubbleWidth: maxRichBubbleWidth, sourceTextInputView: component.textInputView as? ChatInputTextView, emojiViewProvider: component.emojiViewProvider, sourceMediaPreview: mediaPreview, mediaCaptionIsAbove: self.mediaCaptionIsAbove, textInsets: messageTextInsets, explicitBackgroundSize: explicitMessageBackgroundSize, - maxTextWidth: localSourceTextInputViewFrame.width, + maxTextWidth: component.richTextPreview != nil ? maxRichBubbleWidth : localSourceTextInputViewFrame.width, maxTextHeight: 20000.0, containerSize: messageItemViewContainerSize, effect: self.presentationAnimationState.key == .animatedIn ? self.selectedMessageEffect : nil, @@ -970,7 +995,7 @@ final class ChatSendMessageContextScreenComponent: Component { }) } - var customEffectResource: (FileMediaReference, MediaResource)? + var customEffectResource: (FileMediaReference, TelegramMediaResource)? if let effectAnimation = messageEffect.effectAnimation?._parse() { customEffectResource = (FileMediaReference.standalone(media: effectAnimation), effectAnimation.resource) } else { @@ -988,7 +1013,7 @@ final class ChatSendMessageContextScreenComponent: Component { loadEffectAnimationSignal = Signal { subscriber in let fetchDisposable = freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: customEffectResourceFileReference, resource: customEffectResource).start() - let dataDisposabke = (context.account.postbox.mediaBox.resourceStatus(customEffectResource) + let dataDisposabke = (context.engine.resources.status(resource: EngineMediaResource(customEffectResource)) |> filter { status in if status == .Local { return true @@ -1051,7 +1076,7 @@ final class ChatSendMessageContextScreenComponent: Component { standaloneReactionAnimation.updateLayout(size: effectFrame.size) self.addSubnode(standaloneReactionAnimation) - let pathPrefix = component.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(customEffectResource.id) + let pathPrefix = component.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(customEffectResource.id)) let source = AnimatedStickerResourceSource(account: component.context.account, resource: customEffectResource, fitzModifier: nil) standaloneReactionAnimation.setup(source: source, width: Int(effectSize.width * effectiveScale), height: Int(effectSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) standaloneReactionAnimation.completed = { [weak self, weak standaloneReactionAnimation] _ in @@ -1470,10 +1495,11 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha openPremiumPaywall: @escaping (ViewController) -> Void, reactionItems: [ReactionItem]?, availableMessageEffects: AvailableMessageEffects?, - isPremium: Bool + isPremium: Bool, + richTextPreview: ChatSendMessageContextScreenRichTextPreview? = nil ) { self.context = context - + super.init( context: context, component: ChatSendMessageContextScreenComponent( @@ -1495,7 +1521,8 @@ public class ChatSendMessageContextScreen: ViewControllerComponentContainer, Cha openPremiumPaywall: openPremiumPaywall, reactionItems: reactionItems, availableMessageEffects: availableMessageEffects, - isPremium: isPremium + isPremium: isPremium, + richTextPreview: richTextPreview ), navigationBarAppearance: .none, statusBarStyle: .none, diff --git a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift index 0dee40ddfd..7e4ab90dbe 100644 --- a/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift +++ b/submodules/ChatSendMessageActionUI/Sources/MessageItemView.swift @@ -206,6 +206,7 @@ final class MessageItemView: UIView { private var customEmojiContainerView: CustomEmojiContainerView? private var emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)? + private var richTextPreviewView: UIView? private var mediaPreviewClippingView: UIView? private var mediaPreview: ChatSendMessageContextScreenMediaPreview? @@ -283,6 +284,8 @@ final class MessageItemView: UIView { presentationData: PresentationData, backgroundNode: WallpaperBackgroundNode?, textString: NSAttributedString, + richTextPreview: ChatSendMessageContextScreenRichTextPreview?, + maxRichBubbleWidth: CGFloat, sourceTextInputView: ChatInputTextView?, emojiViewProvider: ((ChatTextInputTextCustomEmojiAttribute) -> UIView)?, sourceMediaPreview: ChatSendMessageContextScreenMediaPreview?, @@ -690,9 +693,38 @@ final class MessageItemView: UIView { let size = CGSize(width: positionedTextSize.width + textInsets.left + textInsets.right, height: positionedTextSize.height + textInsets.top + textInsets.bottom) let textFrame = CGRect(origin: CGPoint(x: textInsets.left, y: textInsets.top), size: positionedTextSize) - - let backgroundSize = explicitBackgroundSize ?? size - + + // The outgoing bubble reserves space on the right for its tail (mirrors the + // plain-text path's `backgroundSize.width - 1 - 7`); the left keeps a 1pt border. + // The injected page lays out within this content width, so it never runs into the + // tail and the bubble can't exceed the available container width. + let richBubbleLeftInset: CGFloat = 1.0 + let richBubbleRightInset: CGFloat = 7.0 + + var richContentSize: CGSize? + if let richTextPreview { + let richView = richTextPreview.view + if richView.superview !== self { + richView.removeFromSuperview() + self.addSubview(richView) + self.richTextPreviewView = richView + } + let richBoundingWidth = max(1.0, maxRichBubbleWidth - richBubbleLeftInset - richBubbleRightInset) + richContentSize = richTextPreview.update(boundingWidth: richBoundingWidth, presentationData: presentationData, transition: transition) + } else if let richTextPreviewView = self.richTextPreviewView { + self.richTextPreviewView = nil + richTextPreviewView.removeFromSuperview() + } + + let settledContentSize: CGSize + if let richContentSize { + // Height = 1pt top inset + content + 5pt bottom inset (page sits at y = 1). + settledContentSize = CGSize(width: richContentSize.width + richBubbleLeftInset + richBubbleRightInset, height: richContentSize.height + 6.0) + } else { + settledContentSize = size + } + let backgroundSize = explicitBackgroundSize ?? settledContentSize + let previousSize = self.currentSize self.currentSize = backgroundSize @@ -711,7 +743,20 @@ final class MessageItemView: UIView { textNode.view.frame = CGRect(origin: CGPoint(x: textFrame.minX + textPositioningInsets.left - textClippingContainerFrame.minX, y: textFrame.minY + textPositioningInsets.top - textClippingContainerFrame.minY), size: CGSize(width: maxTextWidth, height: textHeight)) self.updateTextContents() - + + // Blend between the raw plain text (source/morph) and the injected rich layout + // (settled). `explicitBackgroundSize != nil` means source-morph; nil means settled. + let isSettled = explicitBackgroundSize == nil + if let richTextPreviewView = self.richTextPreviewView, let richContentSize { + let richAlpha: CGFloat = isSettled ? 1.0 : 0.0 + let plainAlpha: CGFloat = isSettled ? 0.0 : 1.0 + transition.setAlpha(view: richTextPreviewView, alpha: richAlpha) + transition.setAlpha(view: self.textClippingContainer, alpha: plainAlpha) + transition.setFrame(view: richTextPreviewView, frame: CGRect(origin: CGPoint(x: richBubbleLeftInset, y: 1.0), size: richContentSize)) + } else { + transition.setAlpha(view: self.textClippingContainer, alpha: 1.0) + } + if let effectIcon = self.effectIcon, let effectIconSize { if let effectIconView = effectIcon.view { var animateIn = false diff --git a/submodules/ChatTextLinkEditUI/BUILD b/submodules/ChatTextLinkEditUI/BUILD index 39f5f7832e..a49af0567e 100644 --- a/submodules/ChatTextLinkEditUI/BUILD +++ b/submodules/ChatTextLinkEditUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", @@ -21,6 +20,7 @@ swift_library( "//submodules/ComponentFlow", "//submodules/TelegramUI/Components/AlertComponent", "//submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift index 4229f74605..a6e2c31202 100644 --- a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift +++ b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift @@ -10,81 +10,142 @@ import UrlEscaping import ComponentFlow import AlertComponent import AlertMultilineInputFieldComponent +import AlertWebpagePreviewComponent public func chatTextLinkEditController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, link: String?, - apply: @escaping (String?) -> Void + preview: Bool = false, + apply: @escaping (String?, TelegramMediaWebpage?) -> Void ) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings - + let inputState = AlertMultilineInputFieldComponent.ExternalState() - - var content: [AnyComponentWithIdentity] = [] - content.append(AnyComponentWithIdentity( - id: "title", - component: AnyComponent( - AlertTitleComponent(title: link != nil ? strings.TextFormat_EditLinkTitle : strings.TextFormat_AddLinkTitle) - ) - )) - content.append(AnyComponentWithIdentity( - id: "text", - component: AnyComponent( - AlertTextComponent(content: .plain(strings.TextFormat_AddLinkText(text).string)) - ) - )) - + var applyImpl: (() -> Void)? - content.append(AnyComponentWithIdentity( - id: "input", - component: AnyComponent( - AlertMultilineInputFieldComponent( - context: context, - initialValue: link.flatMap { NSAttributedString(string: $0) }, - placeholder: strings.TextFormat_AddLinkPlaceholder, - returnKeyType: .done, - keyboardType: .URL, - autocapitalizationType: .none, - autocorrectionType: .no, - isInitiallyFocused: true, - externalState: inputState, - returnKeyAction: { - applyImpl?() - } - ) - ) - )) - + var effectiveUpdatedPresentationData: (PresentationData, Signal) if let updatedPresentationData { effectiveUpdatedPresentationData = updatedPresentationData } else { effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData) } - + + let makeContent: (TelegramMediaWebpage?) -> [AnyComponentWithIdentity] = { webpage in + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: link != nil ? strings.TextFormat_EditLinkTitle : strings.TextFormat_AddLinkTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(text)) + ) + )) + content.append(AnyComponentWithIdentity( + id: "input", + component: AnyComponent( + AlertMultilineInputFieldComponent( + context: context, + initialValue: link.flatMap { NSAttributedString(string: $0) }, + placeholder: strings.TextFormat_AddLinkPlaceholder, + returnKeyType: .done, + keyboardType: .URL, + autocapitalizationType: .none, + autocorrectionType: .no, + isInitiallyFocused: true, + externalState: inputState, + returnKeyAction: { + applyImpl?() + } + ) + ) + )) + if let webpage { + content.append(AnyComponentWithIdentity( + id: "webpagePreview", + component: AnyComponent(AlertWebpagePreviewComponent( + context: context, + presentationData: effectiveUpdatedPresentationData.0, + webpage: webpage + )) + )) + } + return content + } + + let contentPromise = Promise<[AnyComponentWithIdentity]>() + contentPromise.set(.single(makeContent(nil))) + var dismissImpl: (() -> Void)? let alertController = AlertScreen( configuration: AlertScreen.Configuration(allowInputInset: true), - content: content, - actions: [ + contentSignal: contentPromise.get(), + actionsSignal: .single([ .init(title: strings.Common_Cancel), .init(title: strings.Common_Done, type: .default, action: { applyImpl?() }, autoDismiss: false) - ], + ]), updatedPresentationData: effectiveUpdatedPresentationData ) + + let previewDisposable = MetaDisposable() + var currentPreview: (link: String, webpage: TelegramMediaWebpage)? + if preview { + var currentDisplayedPreview: (link: String, webpage: TelegramMediaWebpage?)? + previewDisposable.set((inputState.valueSignal + |> map { value -> String in + return explicitUrl(value.string) + } + |> distinctUntilChanged + |> mapToSignal { link -> Signal<(String, TelegramMediaWebpage?), NoError> in + guard !link.isEmpty && isValidUrl(link, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) else { + return .single((link, nil)) + } + + let previewSignal = webpagePreview(account: context.account, urls: [link]) + |> map { result -> (String, TelegramMediaWebpage?) in + guard case let .result(result) = result, let webpage = result?.webpage, case .Loaded = webpage.content else { + return (link, nil) + } + return (link, webpage) + } + + return .single((link, nil)) + |> then((.complete() |> delay(1.0, queue: Queue.mainQueue())) |> then(previewSignal)) + } + |> deliverOnMainQueue).startStrict(next: { link, webpage in + if let currentDisplayedPreview, currentDisplayedPreview.link == link && currentDisplayedPreview.webpage == webpage { + return + } + currentDisplayedPreview = (link, webpage) + if let webpage { + currentPreview = (link, webpage) + } else { + currentPreview = nil + } + contentPromise.set(.single(makeContent(webpage))) + })) + } + alertController.dismissed = { _ in + previewDisposable.dispose() + } + applyImpl = { let updatedLink = explicitUrl(inputState.value.string) if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) { dismissImpl?() - apply(updatedLink) + apply(updatedLink, currentPreview?.link == updatedLink ? currentPreview?.webpage : nil) } else if inputState.value.string.isEmpty { dismissImpl?() - apply("") + apply("", nil) } else { inputState.animateError() } @@ -92,5 +153,18 @@ public func chatTextLinkEditController( dismissImpl = { [weak alertController] in alertController?.dismiss(completion: nil) } + + if link == nil { + Queue.mainQueue().after(0.1, { + let pasteboard = UIPasteboard.general + if pasteboard.hasURLs { + if inputState.value.string.isEmpty, let url = pasteboard.url?.absoluteString, !url.isEmpty { + let value = NSAttributedString(string: url) + inputState.setValue(value, selectionRange: 0 ..< value.length) + } + } + }) + } + return alertController } diff --git a/submodules/CloudData/Sources/CloudData.swift b/submodules/CloudData/Sources/CloudData.swift index 12bb67f42b..94b898dcbe 100644 --- a/submodules/CloudData/Sources/CloudData.swift +++ b/submodules/CloudData/Sources/CloudData.swift @@ -12,9 +12,6 @@ private enum FetchError { @available(iOS 10.0, *) private func fetchRawData(prefix: String) -> Signal { return Signal { subscriber in - #if targetEnvironment(simulator) - return EmptyDisposable - #else let container = CKContainer.default() let publicDatabase = container.database(with: .public) let recordId = CKRecord.ID(recordName: "emergency-datacenter-\(prefix)") @@ -47,7 +44,6 @@ private func fetchRawData(prefix: String) -> Signal { return ActionDisposable { } - #endif } } diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 57b5f847e0..620dd7fdc8 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -67,6 +67,8 @@ private extension ComponentTransition.Animation.Curve { switch self { case .easeInOut: return CAMediaTimingFunction(name: .easeInEaseOut) + case .easeIn: + return CAMediaTimingFunction(name: .easeIn) case .linear: return CAMediaTimingFunction(name: .linear) case let .custom(a, b, c, d): @@ -82,6 +84,8 @@ private extension ComponentTransition.Animation.Curve { return [.curveLinear] case .easeInOut: return [.curveEaseInOut] + case .easeIn: + return [.curveEaseIn] case .spring: return UIView.AnimationOptions(rawValue: 7 << 16) case .custom: @@ -141,6 +145,7 @@ public struct ComponentTransition { public enum Animation { public enum Curve { case easeInOut + case easeIn case spring case linear case custom(Float, Float, Float, Float) @@ -150,6 +155,8 @@ public struct ComponentTransition { switch self { case .easeInOut: return listViewAnimationCurveEaseInOut(offset) + case .easeIn: + return listViewAnimationCurveEaseIn(offset) case .spring: return listViewAnimationCurveSystem(offset) case .linear: diff --git a/submodules/ComponentFlow/Source/Components/Image.swift b/submodules/ComponentFlow/Source/Components/Image.swift index f18aba2e18..3641cf4467 100644 --- a/submodules/ComponentFlow/Source/Components/Image.swift +++ b/submodules/ComponentFlow/Source/Components/Image.swift @@ -7,19 +7,22 @@ public final class Image: Component { public let size: CGSize? public let contentMode: UIImageView.ContentMode public let cornerRadius: CGFloat + public let flipHorizontally: Bool public init( image: UIImage?, tintColor: UIColor? = nil, size: CGSize? = nil, contentMode: UIImageView.ContentMode = .scaleToFill, - cornerRadius: CGFloat = 0.0 + cornerRadius: CGFloat = 0.0, + flipHorizontally: Bool = false ) { self.image = image self.tintColor = tintColor self.size = size self.contentMode = contentMode self.cornerRadius = cornerRadius + self.flipHorizontally = flipHorizontally } public static func ==(lhs: Image, rhs: Image) -> Bool { @@ -38,6 +41,9 @@ public final class Image: Component { if lhs.cornerRadius != rhs.cornerRadius { return false } + if lhs.flipHorizontally != rhs.flipHorizontally { + return false + } return true } @@ -51,7 +57,11 @@ public final class Image: Component { } func update(component: Image, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize { - self.image = component.image + if component.flipHorizontally, let cgImage = component.image?.cgImage { + self.image = UIImage(cgImage: cgImage, scale: component.image?.scale ?? 0.0, orientation: .upMirrored) + } else { + self.image = component.image + } self.contentMode = component.contentMode self.clipsToBounds = component.cornerRadius > 0.0 diff --git a/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift b/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift index c48f851ca9..36cc4388f6 100644 --- a/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift +++ b/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift @@ -11,15 +11,17 @@ public final class BundleIconComponent: Component { public let scaleFactor: CGFloat public let shadowColor: UIColor? public let shadowBlur: CGFloat + public let flipHorizontally: Bool public let flipVertically: Bool - public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, scaleFactor: CGFloat = 1.0, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0, flipVertically: Bool = false) { + public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, scaleFactor: CGFloat = 1.0, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0, flipHorizontally: Bool = false, flipVertically: Bool = false) { self.name = name self.tintColor = tintColor self.maxSize = maxSize self.scaleFactor = scaleFactor self.shadowColor = shadowColor self.shadowBlur = shadowBlur + self.flipHorizontally = flipHorizontally self.flipVertically = flipVertically } @@ -42,6 +44,9 @@ public final class BundleIconComponent: Component { if lhs.shadowBlur != rhs.shadowBlur { return false } + if lhs.flipHorizontally != rhs.flipHorizontally { + return false + } if lhs.flipVertically != rhs.flipVertically { return false } @@ -77,7 +82,9 @@ public final class BundleIconComponent: Component { } }) } - if component.flipVertically, let cgImage = image?.cgImage { + if component.flipHorizontally, let cgImage = image?.cgImage { + self.image = UIImage(cgImage: cgImage, scale: image?.scale ?? 0.0, orientation: .upMirrored) + } else if component.flipVertically, let cgImage = image?.cgImage { self.image = UIImage(cgImage: cgImage, scale: image?.scale ?? 0.0, orientation: .down) } else { self.image = image diff --git a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift index 7e2e790e57..29ae9b147f 100644 --- a/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift +++ b/submodules/Components/ComponentDisplayAdapters/Sources/ComponentDisplayAdapters.swift @@ -10,6 +10,8 @@ public extension ComponentTransition.Animation.Curve { self = .linear case .easeInOut: self = .easeInOut + case .easeIn: + self = .easeIn case let .custom(a, b, c, d): self = .custom(a, b, c, d) case .customSpring: @@ -25,6 +27,8 @@ public extension ComponentTransition.Animation.Curve { return .linear case .easeInOut: return .easeInOut + case .easeIn: + return .easeIn case .spring: return .spring case let .custom(a, b, c, d): diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index 31f38da7cd..5d480fd29f 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -1103,6 +1103,27 @@ public final class PagerComponent View { diff --git a/submodules/Components/ReactionButtonListComponent/BUILD b/submodules/Components/ReactionButtonListComponent/BUILD index 19425cd17d..c72042368a 100644 --- a/submodules/Components/ReactionButtonListComponent/BUILD +++ b/submodules/Components/ReactionButtonListComponent/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display:Display", "//submodules/ComponentFlow:ComponentFlow", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift index 9c4dc38f91..5d73c83e7c 100644 --- a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift +++ b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift @@ -18,7 +18,7 @@ public let sharedReactionStaticImage = Queue(name: "SharedReactionStaticImage", public func reactionStaticImage(context: AccountContext, animation: TelegramMediaFile, pixelSize: CGSize, queue: Queue) -> Signal { return context.engine.resources.custom(id: "\(animation.resource.id.stringRepresentation):reaction-static-\(pixelSize.width)x\(pixelSize.height)-v10", fetch: EngineMediaResource.Fetch { return Signal { subscriber in - let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: MediaResourceReference.standalone(resource: animation.resource)).start() + let fetchDisposable = context.engine.resources.fetch(reference: MediaResourceReference.standalone(resource: animation.resource), userLocation: .other, userContentType: .image).start() var customColor: UIColor? if animation.isCustomTemplateEmoji { @@ -158,13 +158,13 @@ public final class ReactionImageNode: ASDisplayNode { super.init() - self.disposable = (context.account.postbox.mediaBox.resourceData(file.resource) + self.disposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) |> deliverOnMainQueue).start(next: { [weak self] data in guard let strongSelf = self else { return } - - if data.complete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + + if data.isComplete, let dataValue = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = WebP.convert(fromWebP: dataValue) { strongSelf.iconNode.image = image } diff --git a/submodules/Components/ReactionListContextMenuContent/BUILD b/submodules/Components/ReactionListContextMenuContent/BUILD index 08714ce9a7..d91f85c4ab 100644 --- a/submodules/Components/ReactionListContextMenuContent/BUILD +++ b/submodules/Components/ReactionListContextMenuContent/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display:Display", "//submodules/ComponentFlow:ComponentFlow", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", @@ -21,6 +20,7 @@ swift_library( "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/ContextUI:ContextUI", "//submodules/AvatarNode:AvatarNode", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index 42d00f8d85..7d0c4b173f 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -2,6 +2,7 @@ import Foundation import AsyncDisplayKit import Display import ComponentFlow +import MultilineTextComponent import SwiftSignalKit import TelegramCore import AccountContext @@ -290,6 +291,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.scrollNode.view.contentInsetAdjustmentBehavior = .never } self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true + self.scrollNode.view.scrollsToTop = false self.itemNodes = reactions.map { reaction, count in return ItemNode(context: context, availableReactions: availableReactions, reaction: reaction, animationCache: animationCache, animationRenderer: animationRenderer, count: count) @@ -373,18 +375,26 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let availableReactions: AvailableReactions? let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer + private let highlightBackgroundView: UIView let avatarNode: AvatarNode let titleLabelNode: ImmediateTextNode let textLabelNode: ImmediateTextNode let readIconView: UIImageView var credibilityIconView: ComponentView? - + private var reactionLayer: InlineStickerItemLayer? private var iconFrame: CGRect? private var file: TelegramMediaFile? private var fileDisposable: Disposable? + private var longTapRecognizer: UILongPressGestureRecognizer? + private var skipNextTapAction = false let action: () -> Void + var longTapAction: (() -> Void)? { + didSet { + self.longTapRecognizer?.isEnabled = self.longTapAction != nil + } + } private var item: EngineMessageReactionListContext.Item? @@ -412,16 +422,41 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.readIconView = UIImageView(image: readIconImage) + self.highlightBackgroundView = UIView() + self.highlightBackgroundView.alpha = 0.0 + self.highlightBackgroundView.isUserInteractionEnabled = false + super.init() - + self.isAccessibilityElement = true - + + self.view.insertSubview(self.highlightBackgroundView, at: 0) + self.addSubnode(self.avatarNode) self.addSubnode(self.titleLabelNode) self.addSubnode(self.textLabelNode) self.view.addSubview(self.readIconView) self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.highlightBackgroundView.layer.removeAnimation(forKey: "opacity") + self.highlightBackgroundView.alpha = 0.1 + } else { + let currentAlpha = self.highlightBackgroundView.alpha + self.highlightBackgroundView.alpha = 0.0 + self.highlightBackgroundView.layer.animateAlpha(from: currentAlpha, to: 0.0, duration: 0.3) + } + } + + let longTapRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.longTapGesture(_:))) + longTapRecognizer.isEnabled = false + longTapRecognizer.cancelsTouchesInView = true + self.view.addGestureRecognizer(longTapRecognizer) + self.longTapRecognizer = longTapRecognizer } deinit { @@ -429,9 +464,30 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } @objc private func pressed() { + if self.skipNextTapAction { + self.skipNextTapAction = false + return + } self.action() } + @objc private func longTapGesture(_ recognizer: UILongPressGestureRecognizer) { + switch recognizer.state { + case .began: + guard let item = self.item, item.reaction != nil else { + return + } + self.skipNextTapAction = true + self.longTapAction?() + case .cancelled, .ended, .failed: + DispatchQueue.main.async { [weak self] in + self?.skipNextTapAction = false + } + default: + break + } + } + private func updateReactionLayer() { guard let file = self.file else { return @@ -503,7 +559,12 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let sideInset: CGFloat = 16.0 let reaction: MessageReaction.Reaction? = item.reaction - + + self.highlightBackgroundView.backgroundColor = presentationData.theme.overallDarkAppearance ? UIColor.white : UIColor.black + self.highlightBackgroundView.setMonochromaticEffect(tintColor: self.highlightBackgroundView.backgroundColor) + self.highlightBackgroundView.frame = CGRect(origin: CGPoint(x: 10.0, y: 5.0), size: CGSize(width: max(0.0, size.width - 20.0), height: size.height - 10.0)) + self.highlightBackgroundView.layer.cornerRadius = self.highlightBackgroundView.frame.height * 0.5 + if self.displayReactionIcon, reaction != self.item?.reaction { if let reaction = reaction { switch reaction { @@ -771,10 +832,12 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent private let requestUpdate: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void private let requestUpdateApparentHeight: (ReactionsTabNode, ContainedViewLayoutTransition) -> Void private let openPeer: (EnginePeer, Bool) -> Void + private let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? private var hasMore: Bool = false private let scrollNode: ASScrollNode + private let separatorNode: ASDisplayNode private var ignoreScrolling: Bool = false private var animateIn: Bool = false private var bottomScrollInset: CGFloat = 0.0 @@ -791,7 +854,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent private var placeholderItemImage: UIImage? private var placeholderLayers: [Int: SimpleLayer] = [:] - + + private let deleteReactionInfoText: ComponentView + init( context: AccountContext, displayReadTimestamps: Bool, @@ -804,7 +869,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent readStats: MessageReadStats?, requestUpdate: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void, requestUpdateApparentHeight: @escaping (ReactionsTabNode, ContainedViewLayoutTransition) -> Void, - openPeer: @escaping (EnginePeer, Bool) -> Void + openPeer: @escaping (EnginePeer, Bool) -> Void, + deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? ) { self.context = context self.displayReadTimestamps = displayReadTimestamps @@ -816,11 +882,15 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.requestUpdate = requestUpdate self.requestUpdateApparentHeight = requestUpdateApparentHeight self.openPeer = openPeer + self.deleteReaction = deleteReaction self.listContext = context.engine.messages.messageReactionList(message: message, readStats: readStats, reaction: reaction) self.state = ItemsState(listState: EngineMessageReactionListContext.State(message: message, readStats: readStats, reaction: reaction), readStats: readStats) self.scrollNode = ASScrollNode() + self.separatorNode = ASDisplayNode() + self.deleteReactionInfoText = ComponentView() + self.scrollNode.canCancelAllTouchesInViews = true self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.showsVerticalScrollIndicator = false @@ -828,12 +898,17 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.scrollNode.view.contentInsetAdjustmentBehavior = .never } self.scrollNode.clipsToBounds = false + self.scrollNode.view.scrollsToTop = false super.init() self.addSubnode(self.scrollNode) self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.separatorNode.isHidden = true + self.separatorNode.isUserInteractionEnabled = false + self.scrollNode.addSubnode(self.separatorNode) + self.clipsToBounds = true self.stateDisposable = (self.listContext.state @@ -928,6 +1003,14 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } itemNode.update(size: itemFrame.size, presentationData: presentationData, item: item, isLast: self.state.item(at: index + 1) == nil, syncronousLoad: syncronousLoad) + if let deleteReaction = self.deleteReaction, let reaction = item.reaction, item.peer.id != self.context.account.peerId { + let peer = item.peer + itemNode.longTapAction = { + deleteReaction(peer, reaction) + } + } else { + itemNode.longTapAction = nil + } itemNode.frame = itemFrame } else if index < self.state.totalCount { validPlaceholderIds.insert(index) @@ -1034,7 +1117,70 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent } self.presentationData = presentationData - let size = CGSize(width: constrainedSize.width, height: topInset + CGFloat(self.state.totalCount) * itemHeight + topInset) + let baseContentHeight = topInset + CGFloat(self.state.totalCount) * itemHeight + topInset + var footerHeight: CGFloat = 0.0 + let displayDeleteReactionInfoFooter: Bool + if self.deleteReaction != nil { + displayDeleteReactionInfoFooter = self.state.mergedItems.contains(where: { item in + return item.reaction != nil && item.peer.id != self.context.account.peerId + }) + } else { + displayDeleteReactionInfoFooter = false + } + + if displayDeleteReactionInfoFooter { + let separatorHeight: CGFloat = 20.0 + let horizontalInset: CGFloat = 18.0 + let textTopInset: CGFloat = 5.0 + let textBottomInset: CGFloat = 18.0 + let textFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)) + + self.separatorNode.isHidden = false + self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor + + var animateIn = false + var footerTransition = transition + if self.deleteReactionInfoText.view?.superview == nil, footerTransition.isAnimated { + footerTransition = .immediate + animateIn = true + } + footerTransition.updateFrame(node: self.separatorNode, frame: CGRect( + origin: CGPoint(x: horizontalInset, y: baseContentHeight + floorToScreenPixels((separatorHeight - UIScreenPixel) * 0.5)), + size: CGSize(width: max(0.0, constrainedSize.width - horizontalInset * 2.0), height: 1.0) + )) + + let textSize = self.deleteReactionInfoText.update( + transition: ComponentTransition(footerTransition), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: presentationData.strings.Chat_DeleteReactionInfo, + font: textFont, + textColor: presentationData.theme.contextMenu.primaryColor + )), + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: max(0.0, constrainedSize.width - horizontalInset * 2.0), height: .greatestFiniteMagnitude) + ) + if let textView = self.deleteReactionInfoText.view { + if textView.superview == nil { + self.scrollNode.view.addSubview(textView) + textView.isUserInteractionEnabled = false + } + footerTransition.updateFrame(view: textView, frame: CGRect(origin: CGPoint(x: horizontalInset, y: baseContentHeight + separatorHeight + textTopInset), size: textSize)) + + if animateIn { + self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + textView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + + footerHeight = separatorHeight + textTopInset + textSize.height + textBottomInset + } else { + self.separatorNode.isHidden = true + self.deleteReactionInfoText.view?.removeFromSuperview() + } + let size = CGSize(width: constrainedSize.width, height: baseContentHeight + footerHeight) let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height)) self.currentSize = containerSize @@ -1079,6 +1225,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent private let reactions: [(MessageReaction.Reaction?, Int)] private let requestUpdate: (ContainedViewLayoutTransition) -> Void private let requestUpdateApparentHeight: (ContainedViewLayoutTransition) -> Void + private let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? private var presentationData: PresentationData @@ -1111,7 +1258,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent requestUpdate: @escaping (ContainedViewLayoutTransition) -> Void, requestUpdateApparentHeight: @escaping (ContainedViewLayoutTransition) -> Void, back: (() -> Void)?, - openPeer: @escaping (EnginePeer, Bool) -> Void + openPeer: @escaping (EnginePeer, Bool) -> Void, + deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? ) { self.context = context self.displayReadTimestamps = displayReadTimestamps @@ -1126,6 +1274,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.requestUpdate = requestUpdate self.requestUpdateApparentHeight = requestUpdateApparentHeight + self.deleteReaction = deleteReaction if let back = back { self.backButtonNode = BackButtonNode() @@ -1332,7 +1481,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent strongSelf.requestUpdateApparentHeight(transition) } }, - openPeer: self.openPeer + openPeer: self.openPeer, + deleteReaction: self.deleteReaction ) self.addSubnode(tabNode) self.visibleTabNodes[index] = tabNode @@ -1426,6 +1576,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent let readStats: MessageReadStats? let back: (() -> Void)? let openPeer: (EnginePeer, Bool) -> Void + let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? public init( context: AccountContext, @@ -1437,7 +1588,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent reaction: MessageReaction.Reaction?, readStats: MessageReadStats?, back: (() -> Void)?, - openPeer: @escaping (EnginePeer, Bool) -> Void + openPeer: @escaping (EnginePeer, Bool) -> Void, + deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? = nil ) { self.context = context self.displayReadTimestamps = displayReadTimestamps @@ -1449,6 +1601,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent self.readStats = readStats self.back = back self.openPeer = openPeer + self.deleteReaction = deleteReaction } public func node( @@ -1467,7 +1620,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent requestUpdate: requestUpdate, requestUpdateApparentHeight: requestUpdateApparentHeight, back: self.back, - openPeer: self.openPeer + openPeer: self.openPeer, + deleteReaction: self.deleteReaction ) } } diff --git a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift index 852881033d..8437d60aab 100644 --- a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift +++ b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift @@ -13,7 +13,7 @@ public final class ResizableSheetComponentEnvironment: Equatable { public let bounds: CGRect public let isInteractive: Bool } - + public let theme: PresentationTheme public let statusBarHeight: CGFloat public let safeInsets: UIEdgeInsets @@ -26,7 +26,7 @@ public final class ResizableSheetComponentEnvironment: Equatable { public let regularMetricsSize: CGSize? public let dismiss: (Bool) -> Void public let boundsUpdated: ActionSlot - + public init( theme: PresentationTheme, statusBarHeight: CGFloat, @@ -54,7 +54,7 @@ public final class ResizableSheetComponentEnvironment: Equatable { self.dismiss = dismiss self.boundsUpdated = boundsUpdated } - + public static func ==(lhs: ResizableSheetComponentEnvironment, rhs: ResizableSheetComponentEnvironment) -> Bool { if lhs.theme != rhs.theme { return false @@ -92,19 +92,24 @@ public final class ResizableSheetComponentEnvironment: Equatable { public final class ResizableSheetComponent: Component { public typealias EnvironmentType = (ChildEnvironmentType, ResizableSheetComponentEnvironment) - + public class ExternalState { public fileprivate(set) var contentHeight: CGFloat - + fileprivate var trackedScrollViewUpdated: ((UIScrollView?) -> Void)? + public init() { self.contentHeight = 0.0 } + + public func setTrackedScrollView(_ scrollView: UIScrollView?) { + self.trackedScrollViewUpdated?(scrollView) + } } - + public enum BackgroundColor: Equatable { case color(UIColor) } - + public let content: AnyComponent public let titleItem: AnyComponent? public let leftItem: AnyComponent? @@ -113,9 +118,10 @@ public final class ResizableSheetComponent? public let backgroundColor: BackgroundColor public let isFullscreen: Bool + public let defaultHeight: CGFloat? public let externalState: ExternalState? public let animateOut: ActionSlot> - + public init( content: AnyComponent, titleItem: AnyComponent? = nil, @@ -125,6 +131,7 @@ public final class ResizableSheetComponent? = nil, backgroundColor: BackgroundColor, isFullscreen: Bool = false, + defaultHeight: CGFloat? = nil, externalState: ExternalState? = nil, animateOut: ActionSlot>, ) { @@ -136,10 +143,11 @@ public final class ResizableSheetComponent Bool { if lhs.content != rhs.content { return false @@ -165,12 +173,15 @@ public final class ResizableSheetComponent UIView? { return super.hitTest(point, with: event) } } - + public final class View: UIView, UIScrollViewDelegate, ComponentTaggedView, UIGestureRecognizerDelegate { public final class Tag { public init() { } } - + public func matches(tag: Any) -> Bool { if let _ = tag as? Tag { return true @@ -210,6 +221,10 @@ public final class ResizableSheetComponent - + private var titleItemView: ComponentView? private var leftItemView: ComponentView? private var rightItemView: ComponentView? private var bottomItemView: ComponentView? - + private let backgroundHandleView: UIImageView - + private var ignoreScrolling: Bool = false private var isDismissingInteractively: Bool = false private var dismissTranslation: CGFloat = 0.0 private var dismissStartTranslation: CGFloat? private var dismissPanGesture: UIPanGestureRecognizer? - + private var component: ResizableSheetComponent? private weak var state: EmptyComponentState? private var isUpdating: Bool = false private var environment: ResizableSheetComponentEnvironment? private var itemLayout: ItemLayout? - + private var registeredExternalState: ExternalState? + private weak var trackedScrollView: UIScrollView? + private var trackedScrollViewWasAtTopOnGestureBegan = false + override init(frame: CGRect) { self.dimView = UIView() self.containerView = UIView() - + self.containerView.clipsToBounds = true self.containerView.layer.cornerRadius = 40.0 self.containerView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] - + self.backgroundLayer = SimpleLayer() self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] self.backgroundLayer.cornerRadius = 40.0 - + self.backgroundHandleView = UIImageView() - + self.navigationBarContainer = SparseContainerView() self.bottomContainer = SparseContainerView() - + self.scrollView = ScrollView() - + self.scrollContentClippingView = SparseContainerView() self.scrollContentClippingView.clipsToBounds = true - + self.scrollContentView = UIView() - + self.topEdgeEffectView = EdgeEffectView() self.topEdgeEffectView.clipsToBounds = true self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] @@ -275,15 +293,16 @@ public final class ResizableSheetComponent UIView? { if !self.bounds.contains(point) { return nil @@ -331,7 +355,7 @@ public final class ResizableSheetComponent Bool { if gestureRecognizer === self.dismissPanGesture { let pan = gestureRecognizer as! UIPanGestureRecognizer @@ -352,22 +376,25 @@ public final class ResizableSheetComponent Bool { if gestureRecognizer === self.dismissPanGesture { if otherGestureRecognizer === self.scrollView.panGestureRecognizer { return true } + if otherGestureRecognizer === self.trackedScrollView?.panGestureRecognizer { + return true + } } return false } - + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.dismissAnimated() } } - + public func dismissAnimated() { guard let environment = self.environment else { return @@ -375,16 +402,16 @@ public final class ResizableSheetComponent threshold || velocityY > 1000.0 - + self.isDismissingInteractively = false self.scrollView.isScrollEnabled = !component.isFullscreen - + if shouldDismiss { let animateOffset = self.bounds.height - self.backgroundLayer.frame.minY let initialVelocity = animateOffset > 0.0 ? max(0.0, velocityY) / animateOffset : 0.0 @@ -448,73 +475,170 @@ public final class ResizableSheetComponent CGFloat { + return -scrollView.contentInset.top + } + + private func isSheetFullyExpanded(itemLayout: ItemLayout) -> Bool { + return itemLayout.topInset <= 0.5 || self.scrollView.contentOffset.y >= itemLayout.topInset - 0.5 + } + + private func pinTrackedScrollViewToTop(_ scrollView: UIScrollView) { + let topOffset = self.trackedScrollViewTopOffset(scrollView) + if abs(scrollView.contentOffset.y - topOffset) > 0.5 { + scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: topOffset) + } + } + + private func updateTrackedScrollViewLock() { + guard let component = self.component, let itemLayout = self.itemLayout, let trackedScrollView = self.trackedScrollView else { + return + } + trackedScrollView.isScrollEnabled = true + if component.isFullscreen { + return + } + if !self.isSheetFullyExpanded(itemLayout: itemLayout) || self.isDismissingInteractively { + self.pinTrackedScrollViewToTop(trackedScrollView) + } + } + + private func setTrackedScrollView(_ scrollView: UIScrollView?) { + if self.trackedScrollView === scrollView { + self.updateTrackedScrollViewLock() + return + } + + if let trackedScrollView = self.trackedScrollView { + trackedScrollView.panGestureRecognizer.removeTarget(self, action: #selector(self.trackedScrollViewPanGesture(_:))) + trackedScrollView.isScrollEnabled = true + } + + self.trackedScrollView = scrollView + + if let scrollView = scrollView { + scrollView.panGestureRecognizer.addTarget(self, action: #selector(self.trackedScrollViewPanGesture(_:))) + } + + self.trackedScrollViewWasAtTopOnGestureBegan = false + self.updateTrackedScrollViewLock() + } + + @objc private func trackedScrollViewPanGesture(_ recognizer: UIPanGestureRecognizer) { + guard let component = self.component, let itemLayout = self.itemLayout, let trackedScrollView = recognizer.view as? UIScrollView else { + return + } + guard !component.isFullscreen, itemLayout.topInset > 0.5 else { + return + } + + let topOffset = self.trackedScrollViewTopOffset(trackedScrollView) + let isAtTop = trackedScrollView.contentOffset.y <= topOffset + 8.0 + + switch recognizer.state { + case .began, .changed: + if recognizer.state == .began { + self.trackedScrollViewWasAtTopOnGestureBegan = isAtTop + } + let translation = recognizer.translation(in: trackedScrollView) + let currentSheetOffset = min(max(0.0, self.scrollView.contentOffset.y), itemLayout.topInset) + let shouldExpandSheet = self.trackedScrollViewWasAtTopOnGestureBegan && currentSheetOffset < itemLayout.topInset - 0.5 + + if translation.y < 0.0 && shouldExpandSheet { + let consumedOffset = min(itemLayout.topInset - currentSheetOffset, -translation.y) + if consumedOffset > 0.0 { + self.scrollView.contentOffset = CGPoint(x: self.scrollView.contentOffset.x, y: currentSheetOffset + consumedOffset) + self.pinTrackedScrollViewToTop(trackedScrollView) + recognizer.setTranslation(.zero, in: trackedScrollView) + } + } else if translation.y > 0.0 && isAtTop && currentSheetOffset > 0.5 { + let consumedOffset = min(currentSheetOffset, translation.y) + if consumedOffset > 0.0 { + self.scrollView.contentOffset = CGPoint(x: self.scrollView.contentOffset.x, y: currentSheetOffset - consumedOffset) + self.pinTrackedScrollViewToTop(trackedScrollView) + recognizer.setTranslation(.zero, in: trackedScrollView) + } + } else if self.isDismissingInteractively || (translation.y > 0.0 && isAtTop && currentSheetOffset <= 0.5) { + self.pinTrackedScrollViewToTop(trackedScrollView) + } + case .ended, .cancelled, .failed: + self.trackedScrollViewWasAtTopOnGestureBegan = false + if !self.isSheetFullyExpanded(itemLayout: itemLayout) || self.isDismissingInteractively { + self.pinTrackedScrollViewToTop(trackedScrollView) + } + default: + break + } + } + private func updateScrolling(transition: ComponentTransition) { - guard let itemLayout = self.itemLayout, let component = self.component else { + guard let itemLayout = self.itemLayout, let component = self.component, let environment = self.environment else { return } var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset topOffset = max(0.0, topOffset) transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) - + transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - + var topOffsetFraction = self.scrollView.bounds.minY / 100.0 topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - - if component.isFullscreen { + + if component.isFullscreen || environment.inputHeight > 0.0 { topOffsetFraction = 1.0 } - #if DEBUG// && false - if "".isEmpty { - topOffsetFraction = 1.0 - } - #endif - +// #if DEBUG// && false +// if "".isEmpty { +// topOffsetFraction = 1.0 +// } +// #endif + let minScale: CGFloat = itemLayout.isTablet ? 1.0 : (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width let minScaledTranslation: CGFloat = itemLayout.isTablet ? 0.0 : (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0 let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius - + let scale = minScale * (1.0 - topOffsetFraction) + 1.0 * topOffsetFraction let scaledTranslation = minScaledTranslation * (1.0 - topOffsetFraction) let scaledCornerRadius = minScaledCornerRadius * (1.0 - topOffsetFraction) + itemLayout.containerCornerRadius * topOffsetFraction - + var containerTransform = CATransform3DIdentity containerTransform = CATransform3DTranslate(containerTransform, 0.0, scaledTranslation, 0.0) containerTransform = CATransform3DScale(containerTransform, scale, scale, scale) containerTransform = CATransform3DTranslate(containerTransform, 0.0, self.dismissTranslation, 0.0) transition.setTransform(view: self.containerView, transform: containerTransform) transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: scaledCornerRadius) - + if component.isFullscreen { transition.setBounds(view: self.scrollView, bounds: CGRect(origin: .zero, size: self.scrollView.bounds.size)) self.scrollView.isScrollEnabled = false } else { self.scrollView.isScrollEnabled = !self.isDismissingInteractively } - + var bounds = self.scrollView.bounds bounds.size.width = itemLayout.fillingSize self.environment?.boundsUpdated.invoke(ResizableSheetComponentEnvironment.BoundsUpdate(bounds: bounds, isInteractive: self.scrollView.isTracking)) + self.updateTrackedScrollViewLock() } - + private var didPlayAppearanceAnimation = false func animateIn() { self.didPlayAppearanceAnimation = true - + self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY self.containerView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } - + func animateOut(initialVelocity: CGFloat? = nil, completion: @escaping () -> Void) { let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - + self.dimView.layer.animateAlpha(from: self.dimView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) if let initialVelocity = initialVelocity { let transition = ContainedViewLayoutTransition.animated(duration: 0.35, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) - + transition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.layer.position.x, y: self.containerView.layer.position.y + animateOffset), completion: { _ in completion() }) @@ -525,25 +649,26 @@ public final class ResizableSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { self.isUpdating = false } - + let sheetEnvironment = environment[ResizableSheetComponentEnvironment.self].value component.animateOut.connect { [weak self] completion in guard let self else { return } + self.endEditing(true) self.animateOut { completion(Void()) } } - + let resetScrolling = self.scrollView.bounds.width != availableSize.width - + let fillingSize: CGFloat if case .regular = sheetEnvironment.metrics.widthClass { fillingSize = min(availableSize.width, 414.0) - sheetEnvironment.safeInsets.left * 2.0 @@ -555,18 +680,30 @@ public final class ResizableSheetComponent 0.0 { + initialContentHeight = contentHeight + } else if let defaultHeight = component.defaultHeight { + initialContentHeight = min(contentHeight, max(0.0, defaultHeight)) + } else { + initialContentHeight = contentHeight + } + let edgeEffectHeight: CGFloat = 80.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight)) transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) @@ -615,7 +759,7 @@ public final class ResizableSheetComponent if let current = self.titleItemView { @@ -624,12 +768,12 @@ public final class ResizableSheetComponent() self.titleItemView = titleItemView } - + let titleItemSize = titleItemView.update( transition: transition, component: titleItem, environment: {}, - containerSize: CGSize(width: containerSize.width - 66.0 * 2.0, height: 66.0) + containerSize: CGSize(width: containerSize.width - 72.0 * 2.0, height: 66.0) ) let titleItemFrame = CGRect(origin: CGPoint(x: rawSideInset + floorToScreenPixels((containerSize.width - titleItemSize.width)) / 2.0, y: floorToScreenPixels(38.0 - titleItemSize.height * 0.5)), size: titleItemSize) if let view = titleItemView.view { @@ -642,7 +786,7 @@ public final class ResizableSheetComponent @@ -653,7 +797,7 @@ public final class ResizableSheetComponent() self.leftItemView = leftItemView } - + let leftItemSize = leftItemView.update( transition: leftItemTransition, component: leftItem, @@ -664,7 +808,7 @@ public final class ResizableSheetComponent @@ -694,7 +838,7 @@ public final class ResizableSheetComponent() self.rightItemView = rightItemView } - + let rightItemSize = rightItemView.update( transition: rightItemTransition, component: rightItem, @@ -705,7 +849,7 @@ public final class ResizableSheetComponent 0.0 { bottomInsets.left = 16.0 bottomInsets.right = 16.0 bottomInsets.bottom = sheetEnvironment.inputHeight + 8.0 } - + + var bottomEdgeEffectHeight = edgeEffectHeight if let bottomItem = component.bottomItem { var bottomItemTransition = transition let bottomItemView: ComponentView @@ -742,7 +887,7 @@ public final class ResizableSheetComponent() self.bottomItemView = bottomItemView } - + let bottomItemSize = bottomItemView.update( transition: bottomItemTransition, component: bottomItem, @@ -753,7 +898,7 @@ public final class ResizableSheetComponent View { return View(frame: CGRect()) } - + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index fc9c4aaadf..35500162d5 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -184,7 +184,7 @@ public final class SheetComponent: C private let dimView: UIView private let scrollView: ScrollView private let backgroundView: SheetBackgroundView - private var effectView: UIVisualEffectView? + private var effectView: SheetBackgroundBlurView? private let clipView: SheetBackgroundView private let contentView: ComponentView private var headerView: ComponentView? @@ -403,20 +403,21 @@ public final class SheetComponent: C if availableSize.width < availableSize.height { glassInset = 6.0 } - bottomCornerRadius = sheetEnvironment.deviceMetrics.screenCornerRadius - glassInset + let containerCornerRadius = max(24.0, sheetEnvironment.deviceMetrics.screenCornerRadius) + bottomCornerRadius = containerCornerRadius - 2.0 case .legacy: topCornerRadius = 12.0 bottomCornerRadius = 12.0 } var backgroundColor: UIColor = .clear + var blurEffectStyle: UIBlurEffect.Style? switch component.backgroundColor { case let .blur(style): + blurEffectStyle = style == .dark ? .dark : .light self.backgroundView.isHidden = true if self.effectView == nil { - let effectView = UIVisualEffectView(effect: UIBlurEffect(style: style == .dark ? .dark : .light)) - effectView.layer.cornerRadius = self.backgroundView.layer.cornerRadius - effectView.layer.masksToBounds = true + let effectView = SheetBackgroundBlurView() self.backgroundView.superview?.insertSubview(effectView, aboveSubview: self.backgroundView) self.effectView = effectView } @@ -470,8 +471,9 @@ public final class SheetComponent: C transition.setFrame(view: self.clipView, frame: clipFrame, completion: nil) transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), completion: nil) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) - if let effectView = self.effectView { + if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle { transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) + effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition) } self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition) } else { @@ -482,14 +484,19 @@ public final class SheetComponent: C transition.setFrame(view: self.clipView, frame: clipFrame) transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) + if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle { + transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) + effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition) + } case .legacy: let clipFrame = CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0)) self.clipView.update(size: clipFrame.size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition) transition.setFrame(view: self.clipView, frame: clipFrame) transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), completion: nil) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) - if let effectView = self.effectView { + if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle { transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + effectView.update(style: blurEffectStyle, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0), topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition) } } self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition) @@ -613,3 +620,31 @@ public final class SheetBackgroundView: UIView { transition.setBackgroundColor(view: self.bottomCornersView, color: color) } } + +private final class SheetBackgroundBlurView: UIView { + private let clipView = SheetBackgroundView() + private let effectView = UIVisualEffectView() + private var currentStyle: UIBlurEffect.Style? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addSubview(self.clipView) + self.clipView.bottomCornersView.addSubview(self.effectView) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + func update(style: UIBlurEffect.Style, size: CGSize, topCornerRadius: CGFloat, bottomCornerRadius: CGFloat, transition: ComponentTransition) { + if self.currentStyle != style { + self.currentStyle = style + self.effectView.effect = UIBlurEffect(style: style) + } + + self.clipView.update(size: size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition) + transition.setFrame(view: self.clipView, frame: CGRect(origin: .zero, size: size)) + transition.setFrame(view: self.effectView, frame: CGRect(origin: .zero, size: size)) + } +} diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index fa3d0c4468..ff00a21cb1 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -47,6 +47,7 @@ swift_library( "//submodules/ContextUI", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/SearchBarNode", ], diff --git a/submodules/ContactListUI/Sources/ContactContextMenus.swift b/submodules/ContactListUI/Sources/ContactContextMenus.swift index 2a04b9af4c..290b996321 100644 --- a/submodules/ContactListUI/Sources/ContactContextMenus.swift +++ b/submodules/ContactListUI/Sources/ContactContextMenus.swift @@ -39,7 +39,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } (contactsController?.navigationController as? NavigationController)?.pushViewController(controller) @@ -47,7 +47,7 @@ func contactContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, con }) }))) - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) items.append(.action(ContextMenuActionItem(text: isMuted ? strings.StoryFeed_ContextNotifyOn : strings.StoryFeed_ContextNotifyOff, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 3c6ce36fbb..69e68d1fbe 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import DeviceAccess @@ -180,11 +179,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if isGlobal, let _ = peer.addressName { status = .addressName("") } else { - if let _ = peer as? TelegramUser { + if case .user = peer { status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil) - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if case .group = channel.info { if let participantCount = participantCount, participantCount != 0 { status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(participantCount)), multiline: false, isActive: false, icon: nil) @@ -202,7 +201,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { status = .none } } - itemPeer = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) + itemPeer = .peer(peer: peer, chatPeer: peer) case let .deviceContact(id, contact): status = .none itemPeer = .deviceContact(stableId: id, contact: contact) @@ -250,7 +249,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { interaction.openPeer(peer, .generic, nil, nil) }, disabledAction: { _ in if case let .peer(peer, _, _) = peer { - interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) + interaction.openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) } }, itemHighlighting: interaction.itemHighlighting, contextAction: itemContextAction, storyStats: nil, openStories: { peer, sourceNode in if case let .peer(peerValue, _) = peer, let peerValue { @@ -436,7 +435,7 @@ private func contactListNodeEntries( displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], - peersWithStories: [EnginePeer.Id: PeerStoryStats], + peersWithStories: [EnginePeer.Id: EngineChatList.StoryStats], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, @@ -515,7 +514,7 @@ private func contactListNodeEntries( } case let .natural(options, _, _): let sortedPeers = peers.sorted(by: { lhs, rhs in - let result = EnginePeer.IndexName(lhs.indexName).isLessThan(other: EnginePeer.IndexName(rhs.indexName), ordering: sortOrder) + let result = lhs.indexName.isLessThan(other: rhs.indexName, ordering: sortOrder) if result == .orderedSame { if case let .peer(lhsPeer, _, _) = lhs, case let .peer(rhsPeer, _, _) = rhs { return lhsPeer.id < rhsPeer.id @@ -630,7 +629,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, nil)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, nil)) index += 1 } @@ -688,7 +687,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, hasActions, true, nil, false, nil)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, hasActions, true, nil, false, nil)) index += 1 } @@ -699,7 +698,7 @@ private func contactListNodeEntries( if showSelf, let accountPeer { if let peer = topPeers.first(where: { $0.id == accountPeer.id }) { let header = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_ThisIsYou.uppercased(), AnyHashable(10)), theme: theme, strings: strings) - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, selfSubtitle)) + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false, selfSubtitle)) existingPeerIds.insert(.peer(peer.id)) } } @@ -745,7 +744,7 @@ private func contactListNodeEntries( } let presence = presences[peer.id] - entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, peersWithStories[peer.id].flatMap { + entries.append(.peer(index, .peer(peer: peer, isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, peersWithStories[peer.id].flatMap { ContactListNodeEntry.StoryData(count: $0.totalCount, unseenCount: $0.unseenCount, hasUnseenCloseFriends: $0.hasUnseenCloseFriends) }, false, nil)) @@ -763,7 +762,7 @@ private func contactListNodeEntries( let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text("HIDDEN STORIES", AnyHashable(0)), theme: theme, strings: strings) for item in storySubscriptions.items { - entries.append(.peer(index, .peer(peer: item.peer._asPeer(), isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) + entries.append(.peer(index, .peer(peer: item.peer, isGlobal: false, participantCount: nil), nil, header, .none, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, ContactListNodeEntry.StoryData(count: item.storyCount, unseenCount: item.unseenCount, hasUnseenCloseFriends: item.hasUnseenCloseFriends))) index += 1 }*/ } @@ -842,7 +841,7 @@ private func contactListNodeEntries( enabled = false } - if let isPeerEnabled, !isPeerEnabled(EnginePeer(peer)) { + if let isPeerEnabled, !isPeerEnabled(peer) { enabled = false } default: @@ -1032,7 +1031,7 @@ public final class ContactListNode: ASDisplayNode { } private var didSetReady = false - private let contactPeersViewPromise = Promise<(EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: PeerStoryStats])>() + private let contactPeersViewPromise = Promise<(EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: EngineChatList.StoryStats])>() let storySubscriptions = Promise(nil) private let selectionStatePromise = Promise(nil) @@ -1115,7 +1114,7 @@ public final class ContactListNode: ASDisplayNode { contactsWithPremiumRequired = .single([:]) } - let contactsWithStories: Signal<[EnginePeer.Id: PeerStoryStats], NoError> = self.context.engine.data.subscribe( + let contactsWithStories: Signal<[EnginePeer.Id: EngineChatList.StoryStats], NoError> = self.context.engine.data.subscribe( TelegramEngine.EngineData.Item.Contacts.List(includePresences: false) ) |> map { contacts -> Set in @@ -1126,14 +1125,14 @@ public final class ContactListNode: ASDisplayNode { return result } |> distinctUntilChanged - |> mapToSignal { peerIds -> Signal<[EnginePeer.Id: PeerStoryStats], NoError> in + |> mapToSignal { peerIds -> Signal<[EnginePeer.Id: EngineChatList.StoryStats], NoError> in return context.engine.data.subscribe( EngineDataMap( peerIds.map(TelegramEngine.EngineData.Item.Peer.StoryStats.init(id:)) ) ) - |> map { result -> [EnginePeer.Id: PeerStoryStats] in - var filtered: [EnginePeer.Id: PeerStoryStats] = [:] + |> map { result -> [EnginePeer.Id: EngineChatList.StoryStats] in + var filtered: [EnginePeer.Id: EngineChatList.StoryStats] = [:] for (id, value) in result { if let value { filtered[id] = value @@ -1152,7 +1151,7 @@ public final class ContactListNode: ASDisplayNode { contactsWithPremiumRequired, contactsWithStories ) - |> mapToThrottled { next, contactsWithPremiumRequired, contactsWithStories -> Signal<(EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: PeerStoryStats]), NoError> in + |> mapToThrottled { next, contactsWithPremiumRequired, contactsWithStories -> Signal<(EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: EngineChatList.StoryStats]), NoError> in return .single((next.0, next.1, contactsWithPremiumRequired, contactsWithStories)) |> then( .complete() @@ -1165,7 +1164,7 @@ public final class ContactListNode: ASDisplayNode { TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.engine.account.peerId) ), contactsWithPremiumRequired, contactsWithStories) - |> map { next, contactsWithPremiumRequired, contactsWithStories -> (EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: PeerStoryStats]) in + |> map { next, contactsWithPremiumRequired, contactsWithStories -> (EngineContactList, EnginePeer?, [EnginePeer.Id: Bool], [EnginePeer.Id: EngineChatList.StoryStats]) in return (next.0, next.1, contactsWithPremiumRequired, contactsWithStories) } |> take(1)) @@ -1249,9 +1248,8 @@ public final class ContactListNode: ASDisplayNode { let contactsWarningSuppressed = Promise<(Bool, Bool)>() contactsWarningSuppressed.set(.single((false, false)) |> then( - combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings])) - |> map { noticeView, preferences -> (Bool, Bool) in - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings + combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ContactsSettings())) + |> map { noticeView, settings -> (Bool, Bool) in let synchronizeDeviceContacts: Bool = settings.synchronizeContacts let suppressed: Bool let timestamp = noticeView.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) }) @@ -1364,7 +1362,7 @@ public final class ContactListNode: ASDisplayNode { state = state.withToggledPeerId(.peer(peer.id)) } if value { - selectedPeerMap[id] = .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil) + selectedPeerMap[id] = .peer(peer: peer, isGlobal: false, participantCount: nil) } else { selectedPeerMap.removeValue(forKey: id) } @@ -1448,23 +1446,23 @@ public final class ContactListNode: ASDisplayNode { |> mapToSignal { query in let foundLocalContacts: Signal<([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> if searchChatList { - let foundChatListPeers = context.account.postbox.searchPeers(query: query.lowercased()) + let foundChatListPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased()) foundLocalContacts = foundChatListPeers |> mapToSignal { peers -> Signal<([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> in var resultPeers: [FoundPeer] = [] - + for peer in peers { if !displaySavedMessages { if peer.peerId == context.account.peerId { continue } } - + if searchGroups || searchChannels { let mainPeer = peer.chatMainPeer - if let _ = mainPeer as? TelegramUser { - } else if let _ = mainPeer as? TelegramGroup { - } else if let channel = mainPeer as? TelegramChannel { + if case .user = mainPeer { + } else if case .legacyGroup = mainPeer { + } else if case let .channel(channel) = mainPeer { if case .broadcast = channel.info { if !searchChannels { continue @@ -1481,7 +1479,7 @@ public final class ContactListNode: ASDisplayNode { if let mainPeer = peer.chatMainPeer { var matches = true if let isPeerEnabled = isPeerEnabled { - matches = isPeerEnabled(EnginePeer(mainPeer)) + matches = isPeerEnabled(mainPeer) } if matches { resultPeers.append(FoundPeer(peer: mainPeer, subscribers: nil)) @@ -1500,7 +1498,7 @@ public final class ContactListNode: ASDisplayNode { if let maybePresence = presenceMap[peer.peer.id], let presence = maybePresence { resultPresences[peer.peer.id] = presence } - if let _ = peer.peer as? TelegramChannel { + if case .channel = peer.peer { var subscribers: Int32? if let maybeMemberCount = participantCountMap[peer.peer.id], let memberCount = maybeMemberCount { subscribers = Int32(memberCount) @@ -1516,7 +1514,7 @@ public final class ContactListNode: ASDisplayNode { } else { foundLocalContacts = context.engine.contacts.searchContacts(query: query.lowercased()) |> map { peers, presences -> ([FoundPeer], [EnginePeer.Id: EnginePeer.Presence]) in - return (peers.map({ FoundPeer(peer: $0._asPeer(), subscribers: nil) }), presences) + return (peers.map({ FoundPeer(peer: $0, subscribers: nil) }), presences) } } var foundRemoteContacts: Signal<([FoundPeer], [FoundPeer]), NoError> = .single(([], [])) @@ -1535,7 +1533,7 @@ public final class ContactListNode: ASDisplayNode { foundDeviceContacts = .single([:]) } - let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) + let accountPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> take(1) struct FoundPeers { @@ -1562,18 +1560,18 @@ public final class ContactListNode: ASDisplayNode { var result = Set() for peer in foundPeers.foundLocalContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemoteContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemoteContacts.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -1642,7 +1640,7 @@ public final class ContactListNode: ASDisplayNode { } } - if !excludeSelf && !existingPeerIds.contains(accountPeer.id) { + if let accountPeer = accountPeer, !excludeSelf && !existingPeerIds.contains(accountPeer.id) { let lowercasedQuery = query.lowercased() if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { existingPeerIds.insert(accountPeer.id) @@ -1657,14 +1655,14 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } for peer in remotePeers.0 { let matches: Bool - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { let phone = user.phone ?? "" if requirePhoneNumbers && phone.isEmpty { matches = false @@ -1672,9 +1670,9 @@ public final class ContactListNode: ASDisplayNode { matches = true } } else if searchGroups || searchChannels { - if peer.peer is TelegramGroup && searchGroups { + if case .legacyGroup = peer.peer, searchGroups { matches = true - } else if let channel = peer.peer as? TelegramChannel { + } else if case let .channel(channel) = peer.peer { if case .group = channel.info { matches = searchGroups } else { @@ -1694,7 +1692,7 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -1702,7 +1700,7 @@ public final class ContactListNode: ASDisplayNode { } for peer in remotePeers.1 { let matches: Bool - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { let phone = user.phone ?? "" if requirePhoneNumbers && phone.isEmpty { matches = false @@ -1710,9 +1708,9 @@ public final class ContactListNode: ASDisplayNode { matches = true } } else if searchGroups || searchChannels { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { matches = searchGroups - } else if let channel = peer.peer as? TelegramChannel { + } else if case let .channel(channel) = peer.peer { if case .group = channel.info { matches = searchGroups } else { @@ -1732,7 +1730,7 @@ public final class ContactListNode: ASDisplayNode { existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) if searchDeviceContacts, - let user = peer.peer as? TelegramUser, + case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -1941,9 +1939,9 @@ public final class ContactListNode: ASDisplayNode { context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) } - var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) + var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0, isGlobal: false, participantCount: nil) }) for (peer, memberCount) in chatListPeers { - peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) + peers.append(.peer(peer: peer, isGlobal: false, participantCount: memberCount)) } var existingPeerIds = Set() var disabledPeerIds = Set() @@ -1967,7 +1965,7 @@ public final class ContactListNode: ASDisplayNode { switch contact { case let .peer(peer, _, _): if requirePhoneNumbers, - let user = peer as? TelegramUser { + case let .user(user) = peer { let phone = user.phone ?? "" if phone.isEmpty { return false diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index f8fbf6c94e..d74bf3fad2 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -192,9 +192,9 @@ public class ContactsController: ViewController { }).strict() if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(subject: .contacts), combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])) + self.authorizationDisposable = (combineLatest(DeviceAccess.authorizationStatus(subject: .contacts), combineLatest(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .contacts)!), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings])) |> map { noticeView, preferences, sharedData -> (Bool, ContactsSortOrder) in - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings let synchronizeDeviceContacts: Bool = settings.synchronizeContacts let contactsSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]?.get(ContactSynchronizationSettings.self) @@ -291,7 +291,7 @@ public class ContactsController: ViewController { scrollToEndIfExists = true } - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), purposefulAction: { [weak self] in + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), purposefulAction: { [weak self] in if fromSearch { self?.deactivateSearch(animated: false) self?.switchToChatsController?() @@ -375,49 +375,6 @@ public class ContactsController: ViewController { } } - self.contactsNode.openPeopleNearby = { [weak self] in - let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking)) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] status in - guard let strongSelf = self else { - return - } - let presentPeersNearby = { - let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context) - controller.navigationPresentation = .master - if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - var controllers = navigationController.viewControllers.filter { !($0 is PermissionController) } - controllers.append(controller) - navigationController.setViewControllers(controllers, animated: true) - strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) - } - } - - switch status { - case .allowed: - presentPeersNearby() - default: - let controller = PermissionController(context: strongSelf.context, splashScreen: false) - controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false) - controller.navigationPresentation = .master - controller.proceed = { result in - if result { - presentPeersNearby() - } else { - let _ = (strongSelf.navigationController as? NavigationController)?.popViewController(animated: true) - } - } - if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(controller, completion: { [weak self] in - if let strongSelf = self { - strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) - } - }) - } - } - }) - } - self.contactsNode.openInvite = { [weak self] in let _ = (DeviceAccess.authorizationStatus(subject: .contacts) |> take(1) @@ -437,7 +394,7 @@ public class ContactsController: ViewController { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) default: let presentationData = strongSelf.presentationData - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) @@ -465,7 +422,14 @@ public class ContactsController: ViewController { let controller = QrCodeScanScreen(context: strongSelf.context, subject: .peer) controller.showMyCode = { [weak self, weak controller] in if let strongSelf = self { - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self, weak controller] peer in if let strongSelf = self, let controller = controller { controller.present(strongSelf.context.sharedContext.makeChatQrCodeScreen(context: strongSelf.context, peer: peer, threadId: nil, temporary: false), in: .window(.root)) @@ -767,7 +731,7 @@ public class ContactsController: ViewController { } if let peer { Queue.mainQueue().async { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { navigationController.pushViewController(infoController) } @@ -787,7 +751,7 @@ public class ContactsController: ViewController { default: let presentationData = strongSelf.presentationData if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let topController = navigationController.topViewController as? ViewController { - topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) } diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 363ddfa3ea..4496c9e8ff 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -64,7 +64,6 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)? var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? var requestAddContact: ((String) -> Void)? - var openPeopleNearby: (() -> Void)? var openInvite: (() -> Void)? var openQrScan: (() -> Void)? var openStories: ((EnginePeer, ASDisplayNode) -> Void)? diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 01e8689d63..d3b7db7a98 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -161,8 +161,8 @@ private enum ContactListSearchEntry: Comparable, Identifiable { let peerItem: ContactsPeerItemPeer switch peer { case let .peer(peer, _, _): - peerItem = .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)) - nativePeer = EnginePeer(peer) + peerItem = .peer(peer: peer, chatPeer: peer) + nativePeer = peer case let .deviceContact(stableId, contact): peerItem = .deviceContact(stableId: stableId, contact: contact) } @@ -178,7 +178,7 @@ private enum ContactListSearchEntry: Comparable, Identifiable { openPeer(peer, .generic) }, disabledAction: { _ in if case let .peer(peer, _, _) = peer { - openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) + openDisabledPeer(peer, requiresPremiumForMessaging ? .premiumRequired : .generic) } }, contextAction: contextAction.flatMap { contextAction in return nativePeer.flatMap { nativePeer in @@ -404,12 +404,12 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo if let foundRemoteContacts = foundPeers.foundRemoteContacts { for peer in foundRemoteContacts.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundRemoteContacts.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -485,13 +485,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo var enabled = true var requiresPremiumForMessaging = false if onlyWriteable { - enabled = canSendMessagesToPeer(peer._asPeer()) + enabled = canSendMessagesToPeer(peer) if let value = peerRequiresPremiumForMessaging[peer.id], value { requiresPremiumForMessaging = true enabled = false } } - entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer, isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) if searchDeviceContacts, case let .user(user) = peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } @@ -499,14 +499,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } if let remotePeers = remotePeers { for peer in remotePeers.0 { - if !(peer.peer is TelegramUser) { - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, categories.contains(.channels) { - } else { - continue - } + if case .user = peer.peer { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info, categories.contains(.channels) { + } else { + continue } - if let user = peer.peer as? TelegramUser { + if case let .user(user) = peer.peer { if requirePhoneNumbers { let phone = user.phone ?? "" if phone.isEmpty { @@ -517,12 +516,12 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo if user.botInfo != nil { continue } - } + } } - + if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - + var enabled = true var requiresPremiumForMessaging = false if onlyWriteable { @@ -532,32 +531,31 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo enabled = false } } - + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } index += 1 } } for peer in remotePeers.1 { - if !(peer.peer is TelegramUser) { - if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info, categories.contains(.channels) { - } else { - continue - } + if case .user = peer.peer { + } else if case let .channel(channel) = peer.peer, case .broadcast = channel.info, categories.contains(.channels) { + } else { + continue } - - if let user = peer.peer as? TelegramUser, requirePhoneNumbers { + + if case let .user(user) = peer.peer, requirePhoneNumbers { let phone = user.phone ?? "" if phone.isEmpty { continue } } - + if !existingPeerIds.contains(peer.peer.id) { existingPeerIds.insert(peer.peer.id) - + var enabled = true var requiresPremiumForMessaging = false if onlyWriteable { @@ -567,9 +565,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo enabled = false } } - + entries.append(.peer(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled, requiresPremiumForMessaging: requiresPremiumForMessaging, displayCallIcons: displayCallIcons)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, case let .user(user) = peer.peer, let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } index += 1 diff --git a/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift b/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift index 021c4acf53..af68a8ea35 100644 --- a/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift +++ b/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift @@ -5,6 +5,7 @@ import Display import TelegramPresentationData import ComponentFlow import ButtonComponent +import AnimatedTextComponent import EdgeEffect final class InviteContactsCountPanelNode: ASDisplayNode { @@ -69,15 +70,11 @@ final class InviteContactsCountPanelNode: ASDisplayNode { ), content: AnyComponentWithIdentity( id: AnyHashable(0), - component: AnyComponent(ButtonTextContentComponent( - text: self.strings.Contacts_InviteContacts(Int32(self.count)), - badge: 0, - textColor: self.theme.list.itemCheckColors.foregroundColor, - badgeBackground: self.theme.list.itemCheckColors.foregroundColor, - badgeForeground: self.theme.list.itemCheckColors.fillColor, - badgeStyle: .roundedRectangle, - badgeIconName: nil, - combinedAlignment: true + component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 17.0, weight: .semibold, traits: .monospacedNumbers), + color: self.theme.list.itemCheckColors.foregroundColor, + items: [AnimatedTextComponent.Item(id: "text", content: .text(self.strings.Contacts_InviteContacts(Int32(max(1, self.count)))))], + noDelay: true )) ), isEnabled: true, diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index d46b90abf3..2ba7c55eb9 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -475,6 +475,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private var credibilityIconComponent: EmojiStatusComponent? private var verifiedIconView: ComponentHostView? private var verifiedIconComponent: EmojiStatusComponent? + private var emojiStatusIconView: ComponentHostView? + private var emojiStatusIconComponent: EmojiStatusComponent? public let statusNode: TextNodeWithEntities private var statusIconNode: ASImageNode? private var badgeBackgroundNode: ASImageNode? @@ -552,6 +554,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { containerSize: verifiedIconView.bounds.size ) } + if let emojiStatusIconView = self.emojiStatusIconView, let emojiStatusIconComponent = self.emojiStatusIconComponent { + let _ = emojiStatusIconView.update( + transition: .immediate, + component: AnyComponent(emojiStatusIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: emojiStatusIconView.bounds.size + ) + } if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent { let _ = avatarIconView.update( transition: .immediate, @@ -840,27 +850,29 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - var credibilityIcon: EmojiStatusComponent.Content? - var credibilityParticleColor: UIColor? + var credibilityStatusIcon: EmojiStatusComponent.Content? var verifiedIcon: EmojiStatusComponent.Content? + var emojiStatusIcon: EmojiStatusComponent.Content? + var emojiStatusParticleColor: UIColor? + switch item.peer { case let .peer(peer, _): if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList || item.aliasHandling == .standard) { if peer.isScam { - credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) + credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { - credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) + credibilityStatusIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) } else if let emojiStatus = peer.emojiStatus, !item.isAd { - credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) + emojiStatusIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) if let color = emojiStatus.color { - credibilityParticleColor = UIColor(rgb: UInt32(bitPattern: color)) + emojiStatusParticleColor = UIColor(rgb: UInt32(bitPattern: color)) } } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { - credibilityIcon = .premium(color: item.presentationData.theme.list.itemAccentColor) + credibilityStatusIcon = .premium(color: item.presentationData.theme.list.itemAccentColor) } if peer.isVerified { - credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + credibilityStatusIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) } if let verificationIconFileId = peer.verificationIconFileId { verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) @@ -1105,9 +1117,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += 16.0 } } - if let credibilityIcon { + if let credibilityStatusIcon { additionalTitleInset += 3.0 - switch credibilityIcon { + switch credibilityStatusIcon { case let .text(_, string): let textString = NSAttributedString(string: string, font: Font.bold(10.0), textColor: .black, paragraphAlignment: .center) let stringRect = textString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil) @@ -1116,6 +1128,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { additionalTitleInset += 16.0 } } + if let _ = emojiStatusIcon { + additionalTitleInset += 3.0 + additionalTitleInset += 16.0 + } if let actionButtons = actionButtons { additionalTitleInset += 3.0 for actionButton in actionButtons { @@ -1220,7 +1236,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor } - mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor)) + mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, iconColor: textColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)) index += 1 } peerRevealOptions = mappedOptions @@ -1587,7 +1603,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } - if let credibilityIcon { + if let credibilityStatusIcon { let animationCache = item.context.animationCache let animationRenderer = item.context.animationRenderer @@ -1604,8 +1620,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { context: item.context, animationCache: animationCache, animationRenderer: animationRenderer, - content: credibilityIcon, - particleColor: credibilityParticleColor, + content: credibilityStatusIcon, + particleColor: nil, isVisibleForAnimations: strongSelf.visibilityStatus, action: nil, emojiFileUpdated: nil @@ -1627,6 +1643,46 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { credibilityIconView.removeFromSuperview() } + if let emojiStatusIcon { + let animationCache = item.context.animationCache + let animationRenderer = item.context.animationRenderer + + let emojiStatusIconView: ComponentHostView + if let current = strongSelf.emojiStatusIconView { + emojiStatusIconView = current + } else { + emojiStatusIconView = ComponentHostView() + strongSelf.offsetContainerNode.view.addSubview(emojiStatusIconView) + strongSelf.emojiStatusIconView = emojiStatusIconView + } + + let emojiStatusIconComponent = EmojiStatusComponent( + context: item.context, + animationCache: animationCache, + animationRenderer: animationRenderer, + content: emojiStatusIcon, + particleColor: emojiStatusParticleColor, + isVisibleForAnimations: strongSelf.visibilityStatus, + action: nil, + emojiFileUpdated: nil + ) + strongSelf.emojiStatusIconComponent = emojiStatusIconComponent + + let iconSize = emojiStatusIconView.update( + transition: .immediate, + component: AnyComponent(emojiStatusIconComponent), + environment: {}, + containerSize: CGSize(width: 16.0, height: 16.0) + ) + + nextIconX += 4.0 + transition.updateFrame(view: emojiStatusIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + nextIconX += iconSize.width + } else if let emojiStatusIconView = strongSelf.emojiStatusIconView { + strongSelf.emojiStatusIconView = nil + emojiStatusIconView.removeFromSuperview() + } + if let (titleBadgeLayout, titleBadgeApply) = titleBadgeLayoutAndApply { let titleBadgeNode = titleBadgeApply() let backgroundView: UIImageView @@ -1946,7 +2002,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) if item.editing.editable { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated) } else { strongSelf.setRevealOptions((left: [], right: peerRevealOptions)) diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 6959fa0430..435072a929 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -349,25 +349,29 @@ public final class ContextControllerTakeViewInfo { case node(ContextExtractedContentContainingNode) case view(ContextExtractedContentContainingView) } - + public let containingItem: ContainingItem public let contentAreaInScreenSpace: CGRect public let maskView: UIView? - - public init(containingItem: ContainingItem, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) { + public let sourceTransitionSurface: UIView? + + public init(containingItem: ContainingItem, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil, sourceTransitionSurface: UIView? = nil) { self.containingItem = containingItem self.contentAreaInScreenSpace = contentAreaInScreenSpace self.maskView = maskView + self.sourceTransitionSurface = sourceTransitionSurface } } public final class ContextControllerPutBackViewInfo { public let contentAreaInScreenSpace: CGRect public let maskView: UIView? - - public init(contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) { + public let sourceTransitionSurface: UIView? + + public init(contentAreaInScreenSpace: CGRect, maskView: UIView? = nil, sourceTransitionSurface: UIView? = nil) { self.contentAreaInScreenSpace = contentAreaInScreenSpace self.maskView = maskView + self.sourceTransitionSurface = sourceTransitionSurface } } @@ -602,6 +606,7 @@ public enum ContextControllerTip: Equatable { case starsReactions(topCount: Int) case videoProcessing case collageReordering + case deleteReaction public static func ==(lhs: ContextControllerTip, rhs: ContextControllerTip) -> Bool { switch lhs { @@ -611,6 +616,12 @@ public enum ContextControllerTip: Equatable { } else { return false } + case .deleteReaction: + if case .deleteReaction = rhs { + return true + } else { + return false + } case .quoteSelection: if case .quoteSelection = rhs { return true diff --git a/submodules/DateSelectionUI/BUILD b/submodules/DateSelectionUI/BUILD deleted file mode 100644 index be8c15b508..0000000000 --- a/submodules/DateSelectionUI/BUILD +++ /dev/null @@ -1,24 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "DateSelectionUI", - module_name = "DateSelectionUI", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/AccountContext:AccountContext", - "//submodules/TelegramStringFormatting:TelegramStringFormatting", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift b/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift deleted file mode 100644 index ef88e03178..0000000000 --- a/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift +++ /dev/null @@ -1,146 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramPresentationData -import TelegramStringFormatting -import SwiftSignalKit -import AccountContext -import UIKitRuntimeUtils - -public final class DateSelectionActionSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override public var ready: Promise { - return self._ready - } - - public init(context: AccountContext, title: String?, currentValue: Int32, minimumDate: Date? = nil, maximumDate: Date? = nil, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }).strict() - - self._ready.set(.single(true)) - - var updatedValue = currentValue - var items: [ActionSheetItem] = [] - if let title = title { - items.append(ActionSheetTextItem(title: title)) - } - items.append(DateSelectionActionSheetItem(strings: strings, currentValue: currentValue, minimumDate: minimumDate, maximumDate: maximumDate, valueChanged: { value in - updatedValue = value - })) - if let emptyTitle = emptyTitle { - items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in - self?.dismissAnimated() - applyValue(nil) - })) - } - items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in - self?.dismissAnimated() - applyValue(updatedValue) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class DateSelectionActionSheetItem: ActionSheetItem { - let strings: PresentationStrings - - let currentValue: Int32 - let minimumDate: Date? - let maximumDate: Date? - let valueChanged: (Int32) -> Void - - init(strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) { - self.strings = strings - self.currentValue = roundDateToDays(currentValue) - self.minimumDate = minimumDate - self.maximumDate = maximumDate - self.valueChanged = valueChanged - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return DateSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, minimumDate: self.minimumDate, maximumDate: self.maximumDate, valueChanged: self.valueChanged) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class DateSelectionActionSheetItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - - private let valueChanged: (Int32) -> Void - private let pickerView: UIDatePicker - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme - self.strings = strings - self.valueChanged = valueChanged - - UILabel.setDateLabel(theme.primaryTextColor) - - self.pickerView = UIDatePicker() - self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) - self.pickerView.datePickerMode = .countDownTimer - self.pickerView.datePickerMode = .date - self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue))) - self.pickerView.locale = localeWithStrings(strings) - if #available(iOS 13.4, *) { - self.pickerView.preferredDatePickerStyle = .wheels - } - if let minimumDate = minimumDate { - self.pickerView.minimumDate = minimumDate - } - if let maximumDate = maximumDate { - self.pickerView.maximumDate = maximumDate - } else { - self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) - } - self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") - - super.init(theme: theme) - - self.view.addSubview(self.pickerView) - self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 216.0) - - self.pickerView.frame = CGRect(origin: CGPoint(), size: size) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc private func datePickerUpdated() { - self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970))) - } -} - diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index 553c5023c8..f0e6a2859a 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/MtProtoKit:MtProtoKit", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift index cf3dfd46c0..13729fcd29 100644 --- a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index d1bc366fcd..49e543eae8 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import MtProtoKit import MessageUI @@ -98,6 +97,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case fakeGlass(Bool) case forceClearGlass(Bool) case debugRipple(Bool) + case debugRichText(Bool) case browserExperiment(Bool) case allForumsHaveTabs(Bool) case enableReactionOverrides(Bool) @@ -137,7 +137,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.web.rawValue case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .debugRatingLayout, .crashOnSlowQueries, .crashOnMemoryPressure: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .forceClearGlass, .debugRipple, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation: + case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .forceClearGlass, .debugRipple, .debugRichText, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -234,44 +234,46 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 40 case .debugRipple: return 41 - case .browserExperiment: + case .debugRichText: return 42 - case .allForumsHaveTabs: + case .browserExperiment: return 43 - case .enableReactionOverrides: + case .allForumsHaveTabs: return 44 - case .restorePurchases: + case .enableReactionOverrides: return 45 - case .logTranslationRecognition: + case .restorePurchases: return 46 - case .resetTranslationStates: + case .logTranslationRecognition: return 47 - case .compressedEmojiCache: + case .resetTranslationStates: return 48 - case .storiesJpegExperiment: + case .compressedEmojiCache: return 49 - case .disableReloginTokens: + case .storiesJpegExperiment: return 50 - case .checkSerializedData: + case .disableReloginTokens: return 51 - case .enableQuickReactionSwitch: + case .checkSerializedData: return 52 - case .liveStreamV2: + case .enableQuickReactionSwitch: return 53 - case .experimentalCallMute: + case .liveStreamV2: return 54 - case .playerV2: + case .experimentalCallMute: return 55 - case .devRequests: + case .playerV2: return 56 - case .pwa: + case .devRequests: return 57 - case .enableLocalTranslation: + case .pwa: return 58 - case .enableUpdates: + case .enableLocalTranslation: return 59 + case .enableUpdates: + return 60 case let .preferredVideoCodec(index, _, _, _): - return 60 + index + return 61 + index case .disableVideoAspectScaling: return 100 case .enableNetworkFramework: @@ -342,8 +344,8 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) @@ -353,14 +355,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return } - TempBox.shared.dispose(tempSource) - TempBox.shared.dispose(tempZip) + EngineTempBox.shared.dispose(tempSource) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -438,9 +440,9 @@ private enum DebugControllerEntry: ItemListNodeEntry { let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(logData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: logData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: logData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(logData.count), attributes: [.FileName(fileName: "Log-iOS-Short.txt")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(logData.count), attributes: [.FileName(fileName: "Log-iOS-Short.txt")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -508,8 +510,8 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) @@ -519,14 +521,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return } - TempBox.shared.dispose(tempSource) - TempBox.shared.dispose(tempZip) + EngineTempBox.shared.dispose(tempSource) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -592,8 +594,8 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) @@ -603,14 +605,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return } - TempBox.shared.dispose(tempSource) - TempBox.shared.dispose(tempZip) + EngineTempBox.shared.dispose(tempSource) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -677,8 +679,8 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) @@ -688,14 +690,14 @@ private enum DebugControllerEntry: ItemListNodeEntry { return } - TempBox.shared.dispose(tempSource) - TempBox.shared.dispose(tempZip) + EngineTempBox.shared.dispose(tempSource) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -748,7 +750,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let messages = logs.map { (name, path) -> EnqueueMessage in let id = Int64.random(in: Int64.min ... Int64.max) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) return .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) } let _ = enqueueMessages(account: context.account, peerId: peerId, messages: messages).start() @@ -821,9 +823,9 @@ private enum DebugControllerEntry: ItemListNodeEntry { let lineFeed = "\n".data(using: .utf8)! - var tempSources: [TempBoxFile] = [] + var tempSources: [EngineTempBox.File] = [] for (type, logItems) in allLogs { - let tempSource = TempBox.shared.tempFile(fileName: "Log-\(type).txt") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log-\(type).txt") var rawLogData: Data = Data() for (name, path) in logItems { @@ -843,21 +845,21 @@ private enum DebugControllerEntry: ItemListNodeEntry { tempSources.append(tempSource) } - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: tempSources.map(\.path)) guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else { return } - tempSources.forEach(TempBox.shared.dispose) - TempBox.shared.dispose(tempZip) + tempSources.forEach(EngineTempBox.shared.dispose) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/zip", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-All.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/zip", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-All.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -910,9 +912,9 @@ private enum DebugControllerEntry: ItemListNodeEntry { let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(allStatsData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: allStatsData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: allStatsData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/zip", size: Int64(allStatsData.count), attributes: [.FileName(fileName: "StorageReport.txt")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/zip", size: Int64(allStatsData.count), attributes: [.FileName(fileName: "StorageReport.txt")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() @@ -1241,7 +1243,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.chatListPhotos = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1251,7 +1253,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.knockoutWallpaper = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1261,7 +1263,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.experimentalCompatibility = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1271,7 +1273,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.enableDebugDataDisplay = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1281,7 +1283,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.fakeGlass = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1291,7 +1293,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.forceClearGlass = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1301,7 +1303,17 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.debugRipple = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) + }) + }).start() + }) + case let .debugRichText(value): + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Debug Text", value: value, sectionId: self.section, style: .blocks, updated: { value in + let _ = arguments.sharedContext.accountManager.transaction ({ transaction in + transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in + var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings + settings.debugRichText = value + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1311,7 +1323,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.browserExperiment = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1321,7 +1333,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.allForumsHaveTabs = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1335,7 +1347,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { settings.accountReactionEffectOverrides.removeAll() settings.accountStickerEffectOverrides.removeAll() } - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1345,7 +1357,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.compressedEmojiCache = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1355,7 +1367,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.storiesJpegExperiment = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1365,7 +1377,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.checkSerializedData = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1375,7 +1387,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.disableQuickReaction = !value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1385,7 +1397,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.liveStreamV2 = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1395,7 +1407,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.experimentalCallMute = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1405,7 +1417,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.playerV2 = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1415,7 +1427,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.devRequests = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1425,7 +1437,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.enableUpdates = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1435,7 +1447,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.enablePWA = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1445,7 +1457,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.enableLocalTranslation = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1455,7 +1467,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.preferredVideoCodec = value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1465,7 +1477,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings settings.disableVideoAspectScaling = !value - return PreferencesEntry(settings) + return EnginePreferencesEntry(settings) }) }).start() }) @@ -1583,6 +1595,7 @@ private func debugControllerEntries(context: AccountContext?, sharedContext: Sha entries.append(.fakeGlass(experimentalSettings.fakeGlass)) entries.append(.forceClearGlass(experimentalSettings.forceClearGlass)) entries.append(.debugRipple(experimentalSettings.debugRipple)) + entries.append(.debugRichText(experimentalSettings.debugRichText)) #if DEBUG entries.append(.browserExperiment(experimentalSettings.browserExperiment)) #else @@ -1667,10 +1680,9 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun hasLegacyAppData = FileManager.default.fileExists(atPath: statusPath) } - let preferencesSignal: Signal + let preferencesSignal: Signal if let context = context { - preferencesSignal = context.account.postbox.preferencesView(keys: [PreferencesKeys.networkSettings]) - |> map(Optional.init) + preferencesSignal = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.networkSettings)) } else { preferencesSignal = .single(nil) } @@ -1693,11 +1705,11 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun let experimentalSettings: ExperimentalUISettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - let networkSettings: NetworkSettings? = preferences?.values[PreferencesKeys.networkSettings]?.get(NetworkSettings.self) + let networkSettings: NetworkSettings? = preferences?.get(NetworkSettings.self) var leftNavigationButton: ItemListNavigationButton? if modal { - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) } @@ -1763,8 +1775,8 @@ public func triggerDebugSendLogsUI(context: AccountContext, additionalInfo: Stri rawLogData.append("\(additionalInfo)".data(using: .utf8)!) } - let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") - let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + let tempSource = EngineTempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = EngineTempBox.shared.tempFile(fileName: "destination.zip") let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) @@ -1774,14 +1786,14 @@ public func triggerDebugSendLogsUI(context: AccountContext, additionalInfo: Stri return } - TempBox.shared.dispose(tempSource) - TempBox.shared.dispose(tempZip) + EngineTempBox.shared.dispose(tempSource) + EngineTempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(gzippedData.count), isSecretRelated: false) - context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: gzippedData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(gzippedData.count), attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index 625bd10f41..5ac46798f5 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -336,7 +336,7 @@ public final class DeviceAccess { case .ageVerification: text = presentationData.strings.AccessDenied_AgeVerificationCamera } - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -363,7 +363,7 @@ public final class DeviceAccess { text = presentationData.strings.AccessDenied_AgeVerificationCamera } } - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -392,7 +392,7 @@ public final class DeviceAccess { case .voiceCall: text = presentationData.strings.AccessDenied_CallMicrophone } - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) if case .voiceCall = microphoneSubject { @@ -421,7 +421,7 @@ public final class DeviceAccess { case .qrCode: text = presentationData.strings.AccessDenied_QrCode } - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -462,7 +462,7 @@ public final class DeviceAccess { completion(false) if let presentationData = presentationData { let text = presentationData.strings.AccessDenied_LocationPreciseDenied - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -477,7 +477,7 @@ public final class DeviceAccess { completion(false) if let presentationData = presentationData { let text = presentationData.strings.AccessDenied_LocationAlwaysDenied - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -498,7 +498,7 @@ public final class DeviceAccess { } else { text = presentationData.strings.AccessDenied_LocationDisabled } - 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: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -558,7 +558,7 @@ public final class DeviceAccess { } case .cellularData: if let presentationData = presentationData { - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } diff --git a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift index a3fcbc3fad..15288011d5 100644 --- a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift +++ b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift @@ -104,19 +104,19 @@ public final class DeviceLocationManager: NSObject { self.currentTopMode = topMode if let topMode = topMode { self.log?("setting mode \(topMode)") + switch topMode { + case .preciseForeground: + self.manager.allowsBackgroundLocationUpdates = false + case .preciseAlways: + self.manager.allowsBackgroundLocationUpdates = true + } + if previousTopMode == nil { if !self.requestedAuthorization { self.requestedAuthorization = true self.manager.requestAlwaysAuthorization() } - - switch topMode { - case .preciseForeground: - self.manager.allowsBackgroundLocationUpdates = false - case .preciseAlways: - self.manager.allowsBackgroundLocationUpdates = true - } - + self.manager.startUpdatingLocation() self.manager.startUpdatingHeading() } diff --git a/submodules/Display/Source/ActionSheetControllerNode.swift b/submodules/Display/Source/ActionSheetControllerNode.swift index c27cc399f8..fc4d61c752 100644 --- a/submodules/Display/Source/ActionSheetControllerNode.swift +++ b/submodules/Display/Source/ActionSheetControllerNode.swift @@ -88,6 +88,12 @@ final class ActionSheetControllerNode: ASDisplayNode, ASScrollViewDelegate { } } + override func didLoad() { + super.didLoad() + + self.scrollNode.view.scrollsToTop = false + } + func performHighlightedAction() { self.itemGroupsContainerNode.performHighlightedAction() } diff --git a/submodules/Display/Source/ActionSheetItemGroupNode.swift b/submodules/Display/Source/ActionSheetItemGroupNode.swift index cee228963b..af9d79fc27 100644 --- a/submodules/Display/Source/ActionSheetItemGroupNode.swift +++ b/submodules/Display/Source/ActionSheetItemGroupNode.swift @@ -52,6 +52,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.canCancelContentTouches = true self.scrollNode.view.showsVerticalScrollIndicator = false self.scrollNode.view.showsHorizontalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false super.init() diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index cd2a2a5198..613de4eaf7 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -72,8 +72,21 @@ public extension CALayer { func makeAnimation(from: Any?, to: Any, 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.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) { let components = timingFunction.components(separatedBy: "_") - let damping = Float(components[1]) ?? 100.0 - let initialVelocity = Float(components[2]) ?? 0.0 + let mass: Float + let stiffness: Float + let damping: Float + let initialVelocity: Float + if components.count >= 5 { + mass = Float(components[1]) ?? 5.0 + stiffness = Float(components[2]) ?? 900.0 + damping = Float(components[3]) ?? 100.0 + initialVelocity = Float(components[4]) ?? 0.0 + } else { + mass = 5.0 + stiffness = 900.0 + damping = components.count > 1 ? (Float(components[1]) ?? 100.0) : 100.0 + initialVelocity = components.count > 2 ? (Float(components[2]) ?? 0.0) : 0.0 + } let animation = CASpringAnimation(keyPath: keyPath) animation.fromValue = from @@ -83,10 +96,10 @@ public extension CALayer { if let completion = completion { animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } + animation.mass = CGFloat(mass) + animation.stiffness = CGFloat(stiffness) 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()) diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 30add28045..ef5b78b06b 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -12,8 +12,9 @@ extension CGRect { public enum ContainedViewLayoutTransitionCurve: Equatable, Hashable { case linear case easeInOut + case easeIn case spring - case customSpring(damping: CGFloat, initialVelocity: CGFloat) + case customSpring(mass: CGFloat = 5.0, stiffness: CGFloat = 900.0, damping: CGFloat, initialVelocity: CGFloat) case custom(Float, Float, Float, Float) public static var slide: ContainedViewLayoutTransitionCurve { @@ -28,6 +29,8 @@ public extension ContainedViewLayoutTransitionCurve { return offset case .easeInOut: return listViewAnimationCurveEaseInOut(offset) + case .easeIn: + return listViewAnimationCurveEaseIn(offset) case .spring: return listViewAnimationCurveSystem(offset) case .customSpring: @@ -45,10 +48,12 @@ public extension ContainedViewLayoutTransitionCurve { return CAMediaTimingFunctionName.linear.rawValue case .easeInOut: return CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + return CAMediaTimingFunctionName.easeIn.rawValue case .spring: return kCAMediaTimingFunctionSpring - case let .customSpring(damping, initialVelocity): - return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(damping)_\(initialVelocity)" + case let .customSpring(mass, stiffness, damping, initialVelocity): + return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(mass)_\(stiffness)_\(damping)_\(initialVelocity)" case .custom: return CAMediaTimingFunctionName.easeInEaseOut.rawValue } @@ -60,6 +65,8 @@ public extension ContainedViewLayoutTransitionCurve { return nil case .easeInOut: return nil + case .easeIn: + return nil case .spring: return nil case .customSpring: @@ -75,6 +82,8 @@ public extension ContainedViewLayoutTransitionCurve { return [.curveLinear] case .easeInOut: return [.curveEaseInOut] + case .easeIn: + return [.curveEaseIn] case .spring: return UIView.AnimationOptions(rawValue: 7 << 16) case .customSpring: @@ -115,8 +124,8 @@ private extension CALayer { let timingFunction: String let mediaTimingFunction: CAMediaTimingFunction? switch curve { - case .spring: - timingFunction = kCAMediaTimingFunctionSpring + case .spring, .customSpring: + timingFunction = curve.timingFunction mediaTimingFunction = nil default: timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue @@ -399,7 +408,7 @@ public extension ContainedViewLayoutTransition { } } - func updateBounds(layer: CALayer, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func updateBounds(layer: CALayer, bounds: CGRect, beginWithCurrentState: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if layer.bounds.equalTo(bounds) && !force { completion?(true) } else { @@ -411,7 +420,12 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - let previousBounds = layer.bounds + let previousBounds: CGRect + if beginWithCurrentState, layer.animation(forKey: "bounds") != nil, let presentation = layer.presentation() { + previousBounds = presentation.bounds + } else { + previousBounds = layer.bounds + } layer.bounds = bounds layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { @@ -450,7 +464,7 @@ public extension ContainedViewLayoutTransition { } } - func updatePosition(layer: CALayer, position: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func updatePosition(layer: CALayer, position: CGPoint, force: Bool = false, beginFromCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { if layer.position.equalTo(position) && !force { completion?(true) } else { @@ -462,7 +476,22 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - let previousPosition = layer.position + let previousPosition: CGPoint + if beginFromCurrentState, let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in + guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else { + return false + } + if animation.keyPath == "position" { + return true + } else { + return false + } + }) { + previousPosition = layer.presentation()?.position ?? layer.position + } else { + previousPosition = layer.position + } + layer.position = position layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { @@ -2758,7 +2787,7 @@ public final class ControlledTransition { } public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?) { - self.transition.updatePosition(layer: layer, position: position, completion: completion) + self.transition.updatePosition(layer: layer, position: position, beginFromCurrentState: true, completion: completion) } public func updateTransform(layer: CALayer, transform: CATransform3D, completion: ((Bool) -> Void)?) { @@ -2778,11 +2807,11 @@ public final class ControlledTransition { } public func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?) { - self.transition.updateBounds(layer: layer, bounds: bounds, completion: completion) + self.transition.updateBounds(layer: layer, bounds: bounds, beginWithCurrentState: true, completion: completion) } public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) { - self.transition.updateFrame(layer: layer, frame: frame, completion: completion) + self.transition.updateFrame(layer: layer, frame: frame, beginWithCurrentState: true, completion: completion) } public func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) { diff --git a/submodules/Display/Source/DeviceMetrics.swift b/submodules/Display/Source/DeviceMetrics.swift index c22f8975d5..9380ecba8a 100644 --- a/submodules/Display/Source/DeviceMetrics.swift +++ b/submodules/Display/Source/DeviceMetrics.swift @@ -307,7 +307,7 @@ public enum DeviceMetrics: CaseIterable, Equatable { var keyboardHeight = _keyboardHeight(inLandscape: inLandscape) if #available(iOS 26.0, *) { if !inLandscape { - keyboardHeight += 9.0 + keyboardHeight -= 1.0 } } return keyboardHeight diff --git a/submodules/Display/Source/Font.swift b/submodules/Display/Source/Font.swift index 9a93be6b1c..4f1a4c0cf8 100644 --- a/submodules/Display/Source/Font.swift +++ b/submodules/Display/Source/Font.swift @@ -224,7 +224,7 @@ public struct Font { } else { let font: UIFont switch design { - case .regular: + case .regular, .round: if traits.contains(.italic) { if let descriptor = UIFont.systemFont(ofSize: size, weight: weight.weight).fontDescriptor.withSymbolicTraits([.traitItalic]) { font = UIFont(descriptor: descriptor, size: size) @@ -254,8 +254,6 @@ public struct Font { } else { font = UIFont(name: "Menlo", size: size - 1.0) ?? UIFont.systemFont(ofSize: size) } - case .round: - font = UIFont(name: ".SFCompactRounded-Semibold", size: size) ?? UIFont.systemFont(ofSize: size) case .camera: func encodeText(string: String, key: Int16) -> String { let nsString = string as NSString diff --git a/submodules/Display/Source/LinkHighlightingNode.swift b/submodules/Display/Source/LinkHighlightingNode.swift index 99fa88c0a8..7f4dbfffa3 100644 --- a/submodules/Display/Source/LinkHighlightingNode.swift +++ b/submodules/Display/Source/LinkHighlightingNode.swift @@ -60,6 +60,17 @@ private func drawRectsImageContent(size: CGSize, context: CGContext, color: UICo } currentRects.removeAll() } else { + // The path-stitching loop below assumes consecutive rects in a group + // are adjacent (overlapping or sharing an edge). When they're disjoint + // — vertical gap, vertical inversion, or horizontal gap on the same + // line — it bridges them with a polygon perimeter that renders as a + // diagonal bridge across empty space. Split groups whenever the next + // rect doesn't intersect the last one (1pt slop in both axes matches + // the snap loop's adjacency test). + if let last = currentRects.last, !last.insetBy(dx: -1.0, dy: -1.0).intersects(rect) { + combinedRects.append(currentRects) + currentRects.removeAll() + } currentRects.append(rect) } } @@ -82,7 +93,7 @@ private func drawRectsImageContent(size: CGSize, context: CGContext, color: UICo rects[i + 1].size.height = rects[i + 1].maxY - midY hadChanges = true } - if rects[i].maxY >= rects[i + 1].minY && rects[i].insetBy(dx: 0.0, dy: 1.0).intersects(rects[i + 1]) { + if rects[i].maxY >= rects[i + 1].minY && rects[i].insetBy(dx: 0.0, dy: -1.0).intersects(rects[i + 1]) { if abs(rects[i].minX - rects[i + 1].minX) < minRadius { let commonMinX = min(rects[i].origin.x, rects[i + 1].origin.x) if rects[i].origin.x != commonMinX { @@ -124,7 +135,7 @@ private func drawRectsImageContent(size: CGSize, context: CGContext, color: UICo if rect.maxX == next.maxX { context.addLine(to: CGPoint(x: next.maxX, y: next.midY)) } else { - let nextRadius = min(outerRadius, floor(abs(rect.maxX - next.maxX) * 0.5)) + let nextRadius = min(outerRadius, ceil(abs(rect.maxX - next.maxX) * 0.5)) context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - nextRadius)) if next.maxX > rect.maxX { context.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), tangent2End: CGPoint(x: rect.maxX + nextRadius, y: rect.maxY), radius: nextRadius) @@ -151,7 +162,7 @@ private func drawRectsImageContent(size: CGSize, context: CGContext, color: UICo if rect.minX == prev.minX { context.addLine(to: CGPoint(x: prev.minX, y: prev.midY)) } else { - let prevRadius = min(outerRadius, floor(abs(rect.minX - prev.minX) * 0.5)) + let prevRadius = min(outerRadius, ceil(abs(rect.minX - prev.minX) * 0.5)) context.addLine(to: CGPoint(x: rect.minX, y: rect.minY + prevRadius)) if rect.minX < prev.minX { context.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.minX + prevRadius, y: rect.minY), radius: prevRadius) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 0a78640967..9378f7a4ce 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -212,6 +212,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur public final var rotated = false public final var experimentalSnapScrollToItem = false + public final var experimentalSnapScrollToPinnedItem = false public final var useMainQueueTransactions = false public final var scrollEnabled: Bool = true { @@ -508,6 +509,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur self.scroller.contentSize = CGSize(width: 0.0, height: infiniteScrollSize * 2.0) self.scroller.isHidden = true self.scroller.delegate = self.wrappedScrollViewDelegate + self.scroller.scrollsToTop = false self.view.addSubview(self.scroller) self.scroller.panGestureRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) @@ -863,6 +865,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur self.snapToBottomInsetUntilFirstInteraction = false } self.scrolledToItem = nil + self.experimentalSnapScrollToPinnedItem = false self.scroller.forceDecelerating = false self.isDragging = true @@ -1073,22 +1076,60 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur if remainingFactor.isLessThanOrEqualTo(0.0) { break } - + let itemFactor: CGFloat if CGFloat(1.0).isLessThanOrEqualTo(remainingFactor) { itemFactor = 1.0 } else { itemFactor = remainingFactor } - + additionalInverseTopInset += floor(itemNode.apparentBounds.height * itemFactor) - + remainingFactor -= 1.0 } } return additionalInverseTopInset } - + + private func calculatePinToEdgeTopInset() -> CGFloat { + var lowestPinnedIndex: Int = Int.max + for itemNode in self.itemNodes { + guard let index = itemNode.index, index >= 0, index < self.items.count else { continue } + if index < lowestPinnedIndex && self.items[index].pinToEdgeWithInset { + lowestPinnedIndex = index + } + } + guard lowestPinnedIndex != Int.max else { return 0.0 } + + let visibleArea = self.visibleSize.height - self.insets.top - self.insets.bottom + + var totalAboveAndPinned: CGFloat = 0.0 + var sawIndexZero = false + for itemNode in self.itemNodes { + guard let index = itemNode.index else { continue } + if index == 0 { + sawIndexZero = true + } + if index < lowestPinnedIndex { + totalAboveAndPinned += itemNode.apparentBounds.height + } else if index == lowestPinnedIndex { + let pinnedHeight = itemNode.apparentBounds.height + totalAboveAndPinned += pinnedHeight - self.pinToEdgeBottomExtension(forPinnedHeight: pinnedHeight) + } + } + guard sawIndexZero else { return 0.0 } + + return max(0.0, visibleArea - totalAboveAndPinned) + } + + private func pinToEdgeBottomExtension(forPinnedHeight pinnedHeight: CGFloat) -> CGFloat { + let visibleArea = self.visibleSize.height - self.insets.top - self.insets.bottom + let halfArea = visibleArea * 0.5 + guard halfArea > 0.0 else { return 0.0 } + return max(0.0, pinnedHeight - halfArea) + } + private func areAllItemsOnScreen() -> Bool { if self.itemNodes.count == 0 { return true @@ -1183,7 +1224,11 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) } - + let pinToEdgeTopInset = self.calculatePinToEdgeTopInset() + if pinToEdgeTopInset > 0.0 { + effectiveInsets.top = max(effectiveInsets.top, self.insets.top + pinToEdgeTopInset) + } + if topItemFound { topItemEdge = self.itemNodes[0].apparentFrame.origin.y - self.tempTopInset } @@ -1614,7 +1659,11 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) } - + let pinToEdgeTopInset = self.calculatePinToEdgeTopInset() + if pinToEdgeTopInset > 0.0 { + effectiveInsets.top = max(effectiveInsets.top, self.insets.top + pinToEdgeTopInset) + } + completeHeight = effectiveInsets.top + effectiveInsets.bottom if let index = self.itemNodes[self.itemNodes.count - 1].index, index == self.items.count - 1 { @@ -1819,7 +1868,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur }) } - public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { + public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, customAnimationTransition: ControlledTransition? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { if let updateOpaqueState = updateOpaqueState { self.opaqueTransactionState = updateOpaqueState @@ -1831,7 +1880,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, customAnimationTransition: updateSizeAndInsets?.customAnimationTransition, completion: { [weak strongSelf] in + strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, customAnimationTransition: customAnimationTransition ?? updateSizeAndInsets?.customAnimationTransition, completion: { [weak strongSelf] in completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() @@ -2627,6 +2676,34 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } } + private var nextAnimationId: Int = 0 + private func takeNextAnimationId() -> Int { + let value = self.nextAnimationId + self.nextAnimationId += 1 + return value + } + + public func isStrictlyScrolledToPinToEdgeItem() -> Bool { + guard let targetIndex = self.items.firstIndex(where: { $0.pinToEdgeWithInset }) else { + return false + } + let pinToEdgeTopInset = self.calculatePinToEdgeTopInset() + if pinToEdgeTopInset <= 0.0 { + return false + } + for itemNode in self.itemNodes { + if itemNode.index == targetIndex { + let extensionOffset = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height) + guard pinToEdgeTopInset > 0.0 || extensionOffset > 0.0 else { + return false + } + let expectedMaxY = (self.visibleSize.height - self.insets.bottom + extensionOffset) + itemNode.scrollPositioningInsets.bottom + return abs(itemNode.apparentFrame.maxY - expectedMaxY) < 0.5 + } + } + return false + } + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateFullTransition: Bool, customAnimationTransition: ControlledTransition?, synchronous: Bool, synchronousLoads: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, forceInvertOffsetDirection: Bool = false, completion: () -> Void) { var scrollToItem: ListViewScrollToItem? var isExperimentalSnapToScrollToItem = false @@ -2635,6 +2712,9 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur if self.experimentalSnapScrollToItem { self.scrolledToItem = (originalScrollToItem.index, originalScrollToItem.position) } + if originalScrollToItem.index < self.items.count && self.items[originalScrollToItem.index].pinToEdgeWithInset { + self.experimentalSnapScrollToPinnedItem = true + } } else if let scrolledToItem = self.scrolledToItem, self.experimentalSnapScrollToItem { var curve: ListViewAnimationCurve = .Default(duration: nil) var animated = false @@ -2644,6 +2724,23 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: animated, curve: curve, directionHint: .Down) isExperimentalSnapToScrollToItem = true + } else if self.experimentalSnapScrollToPinnedItem { + if let index = self.items.firstIndex(where: { $0.pinToEdgeWithInset }) { + isExperimentalSnapToScrollToItem = true + var curve: ListViewAnimationCurve = .Default(duration: nil) + var animated = false + if let updateSizeAndInsets = updateSizeAndInsets { + curve = updateSizeAndInsets.curve + animated = !updateSizeAndInsets.duration.isZero + } + scrollToItem = ListViewScrollToItem(index: index, position: self.rotated ? .bottom(0.0) : .top(0.0), animated: animated, curve: curve, directionHint: .Down) + } + } + + if scrollToItem == nil { + if self.itemNodes.isEmpty, self.items.contains(where: { $0.pinToEdgeWithInset }) { + scrollToItem = ListViewScrollToItem(index: 0, position: self.rotated ? .bottom(0.0) : .top(0.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) + } } weak var highlightedItemNode: ListViewItemNode? @@ -3022,13 +3119,33 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur if let scrollToItem = scrollToItem, !self.areAllItemsOnScreen() || !sizeOrInsetsUpdated { self.stopScrolling() - + for itemNode in self.itemNodes { if let index = itemNode.index, index == scrollToItem.index { let insets = self.insets// updateSizeAndInsets?.insets ?? self.insets - + + var isPinToEdgeTarget = false + if index >= 0, index < self.items.count, self.items[index].pinToEdgeWithInset { + let pinExtension = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height) + if self.calculatePinToEdgeTopInset() > 0.0 || pinExtension > 0.0 { + isPinToEdgeTarget = true + for otherNode in self.itemNodes { + guard let otherIndex = otherNode.index else { continue } + guard otherIndex >= 0, otherIndex < self.items.count else { continue } + if otherIndex < index, self.items[otherIndex].pinToEdgeWithInset { + isPinToEdgeTarget = false + break + } + } + } + } + var offset: CGFloat - switch scrollToItem.position { + if isPinToEdgeTarget { + let extensionOffset = self.pinToEdgeBottomExtension(forPinnedHeight: itemNode.apparentBounds.height) + offset = (self.visibleSize.height - insets.bottom + extensionOffset) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + } else { + switch scrollToItem.position { case let .bottom(additionalOffset): offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset case let .top(additionalOffset): @@ -3066,8 +3183,9 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur offset = 0.0 } } + } } - + for itemNode in self.itemNodes { var frame = itemNode.frame frame.origin.y += offset @@ -3126,37 +3244,36 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur if let updateSizeAndInsets = updateSizeAndInsets { if self.insets != updateSizeAndInsets.insets || self.headerInsets != updateSizeAndInsets.headerInsets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { + let previousPinToEdgeTopInset = self.calculatePinToEdgeTopInset() let previousVisibleSize = self.visibleSize self.visibleSize = updateSizeAndInsets.size - + var offsetFix: CGFloat + var offsetFixUsesEffectiveTopInset = false let insetDeltaOffsetFix: CGFloat = 0.0 if (self.isTracking && !self.allowInsetFixWhileTracking) || isExperimentalSnapToScrollToItem { offsetFix = 0.0 } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { - /*if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) { - offsetFix = 0.0 - } else {*/ - offsetFix = updateSizeAndInsets.insets.top - self.insets.top - //} + offsetFix = updateSizeAndInsets.insets.top - self.insets.top + offsetFixUsesEffectiveTopInset = true } - + offsetFix += additionalScrollDistance - - /*if let topItemNode = self.itemNodes.first(where: { $0.index == 0 }) { - let topEdge = self.scroller.contentOffset.y + updateSizeAndInsets.insets.top - offsetFix = -(topEdge - topItemNode.apparentFrame.minY) - }*/ - + self.insets = updateSizeAndInsets.insets self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets self.itemOffsetInsets = updateSizeAndInsets.itemOffsetInsets self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems self.visibleSize = updateSizeAndInsets.size - + + if offsetFixUsesEffectiveTopInset { + let updatedPinToEdgeTopInset = self.calculatePinToEdgeTopInset() + offsetFix += updatedPinToEdgeTopInset - previousPinToEdgeTopInset + } + for itemNode in self.itemNodes { itemNode.updateFrame(itemNode.frame.offsetBy(dx: 0.0, dy: offsetFix), within: self.visibleSize, transition: customAnimationTransition) } @@ -3239,16 +3356,18 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur animation = basicAnimation } - deferredUpdateVisible = true - animation.completion = { [weak self] _ in - self?.updateItemNodesVisibilities(onlyPositive: false) - } - self.layer.add(animation, forKey: nil) - if !completeOffset.isZero { - for itemNode in self.itemNodes { - itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration) + if customAnimationTransition == nil { + deferredUpdateVisible = true + animation.completion = { [weak self] _ in + self?.updateItemNodesVisibilities(onlyPositive: false) + } + self.layer.add(animation, forKey: "animation-\(self.takeNextAnimationId())") + if !completeOffset.isZero { + for itemNode in self.itemNodes { + itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration) + } + self.didScrollWithOffset?(-completeOffset, ContainedViewLayoutTransition.animated(duration: animationDuration, curve: animationCurve), nil, self.isTrackingOrDecelerating) } - self.didScrollWithOffset?(-completeOffset, ContainedViewLayoutTransition.animated(duration: animationDuration, curve: animationCurve), nil, self.isTrackingOrDecelerating) } } else { self.didScrollWithOffset?(-completeOffset, .immediate, nil, self.isTrackingOrDecelerating) @@ -3670,7 +3789,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur headerNode.removeFromSupernode() } } - self.layer.add(animation, forKey: nil) + self.layer.add(animation, forKey: "animation-\(self.takeNextAnimationId()))") } for itemNode in self.itemNodes { @@ -3988,6 +4107,12 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } else { headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) } + case .easeIn: + if transition.1 { + headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: ContainedViewLayoutTransitionCurve.slide.mediaTimingFunction) + } else { + headerNode.layer.animateBoundsOriginAdditive(from: offset, to: CGPoint(), duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)) + } } } @@ -4790,7 +4915,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur } if requestUpdateVisibleItems { - self.enqueueUpdateVisibleItems(synchronous: false) + self.enqueueUpdateVisibleItems(synchronous: self.experimentalSnapScrollToPinnedItem) } if scrollingForReorder { diff --git a/submodules/Display/Source/ListViewAnimation.swift b/submodules/Display/Source/ListViewAnimation.swift index 3ee91fdd68..52e3b4c9c8 100644 --- a/submodules/Display/Source/ListViewAnimation.swift +++ b/submodules/Display/Source/ListViewAnimation.swift @@ -117,6 +117,10 @@ public let listViewAnimationCurveEaseInOut: (CGFloat) -> CGFloat = { t in return bezierPoint(0.42, 0.0, 0.58, 1.0, t) } +public let listViewAnimationCurveEaseIn: (CGFloat) -> CGFloat = { t in + return bezierPoint(0.42, 0.0, 1.0, 1.0, t) +} + #if os(iOS) public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat { if animationOptions.rawValue == UInt(7 << 16) { @@ -215,6 +219,8 @@ public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTra return (animationDuration, .Default(duration: animationDuration)) case .easeInOut: return (animationDuration, .Default(duration: animationDuration)) + case .easeIn: + return (animationDuration, .Default(duration: animationDuration)) case .spring, .customSpring: return (animationDuration, .Spring(duration: animationDuration)) case let .custom(c1x, c1y, c2x, c2y): diff --git a/submodules/Display/Source/ListViewItem.swift b/submodules/Display/Source/ListViewItem.swift index 1228c055ac..7e022b561a 100644 --- a/submodules/Display/Source/ListViewItem.swift +++ b/submodules/Display/Source/ListViewItem.swift @@ -69,7 +69,7 @@ public final class ListViewItemApply { } } -public protocol ListViewItem { +public protocol ListViewItem: AnyObject { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) @@ -77,6 +77,7 @@ public protocol ListViewItem { var headerAccessoryItem: ListViewAccessoryItem? { get } var selectable: Bool { get } var approximateHeight: CGFloat { get } + var pinToEdgeWithInset: Bool { get } func selected(listView: ListView) } @@ -98,6 +99,10 @@ public extension ListViewItem { return 44.0 } + var pinToEdgeWithInset: Bool { + return false + } + func selected(listView: ListView) { } diff --git a/submodules/Display/Source/ListViewProtocol.swift b/submodules/Display/Source/ListViewProtocol.swift index 51a838d7e4..dbfac0920a 100644 --- a/submodules/Display/Source/ListViewProtocol.swift +++ b/submodules/Display/Source/ListViewProtocol.swift @@ -67,6 +67,7 @@ public protocol ListView: ASDisplayNode { additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, + customAnimationTransition: ControlledTransition?, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void ) @@ -102,6 +103,7 @@ public extension ListView { additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, + customAnimationTransition: ControlledTransition? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in } ) { @@ -114,6 +116,7 @@ public extension ListView { additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, + customAnimationTransition: customAnimationTransition, updateOpaqueState: updateOpaqueState, completion: completion ) diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 7f7f88679f..452418ad57 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -172,6 +172,7 @@ open class NavigationController: UINavigationController, ContainableController, private var inCallStatusBar: StatusBar? private var updateInCallStatusBarState: CallStatusBarNode? private var globalScrollToTopNode: ScrollToTopNode? + private var windowScrollToTopProxyViews: WindowScrollToTopProxyViews? private var rootContainer: RootContainer? private var rootModalFrame: NavigationModalFrame? private var modalContainers: [NavigationModalContainer] = [] @@ -331,6 +332,54 @@ open class NavigationController: UINavigationController, ContainableController, } deinit { + self.windowScrollToTopProxyViews?.update(window: nil, mode: .disabled, referenceView: nil) + } + + private func getWindowScrollToTopProxyViews() -> WindowScrollToTopProxyViews { + if let windowScrollToTopProxyViews = self.windowScrollToTopProxyViews { + return windowScrollToTopProxyViews + } + + let windowScrollToTopProxyViews = WindowScrollToTopProxyViews(scrollToTop: { [weak self] subject in + self?.scrollToTop(subject) + }) + self.windowScrollToTopProxyViews = windowScrollToTopProxyViews + return windowScrollToTopProxyViews + } + + private func windowScrollToTopReferenceView(window: UIWindow) -> UIView? { + var view: UIView? = self.view + while let currentView = view { + if currentView.superview === window { + return currentView + } + view = currentView.superview + } + return nil + } + + private func updateWindowScrollToTopProxyViews(layout: ContainerViewLayout) { + guard let window = self.view.window, let rootContainer = self.rootContainer else { + (self.globalScrollToTopNode?.view as? ScrollToTopView)?.scrollsToTop = true + self.windowScrollToTopProxyViews?.update(window: nil, mode: .disabled, referenceView: nil) + return + } + + (self.globalScrollToTopNode?.view as? ScrollToTopView)?.scrollsToTop = false + let referenceView = self.windowScrollToTopReferenceView(window: window) + let proxyViews = self.getWindowScrollToTopProxyViews() + switch rootContainer { + case .flat: + let scrollToTopHeight = max(layout.statusBarHeight ?? layout.safeInsets.top, 1.0) + let frame = self.view.convert(CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: scrollToTopHeight)), to: window) + proxyViews.update(window: window, mode: .flat(frame: frame), referenceView: referenceView) + case let .split(container): + let frames = container.scrollToTopProxyFrames(layout: layout) + proxyViews.update(window: window, mode: .split( + masterFrame: container.view.convert(frames.master, to: window), + detailFrame: container.view.convert(frames.detail, to: window) + ), referenceView: referenceView) + } } public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { @@ -1018,8 +1067,6 @@ open class NavigationController: UINavigationController, ContainableController, case let .flat(flatContainer): let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in self?.controllerRemoved(controller) - }, scrollToTop: { [weak self] subject in - self?.scrollToTop(subject) }) if let detailsPlaceholderNode = self.detailsPlaceholderNode { self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) @@ -1053,8 +1100,6 @@ open class NavigationController: UINavigationController, ContainableController, } else { let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in self?.controllerRemoved(controller) - }, scrollToTop: { [weak self] subject in - self?.scrollToTop(subject) }) if let detailsPlaceholderNode = self.detailsPlaceholderNode { self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode) @@ -1385,6 +1430,8 @@ open class NavigationController: UINavigationController, ContainableController, } } + self.updateWindowScrollToTopProxyViews(layout: layout) + self.isUpdatingContainers = false if notifyGlobalOverlayControllersUpdated { diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift index f9120ce6fe..7fd3ed95ba 100644 --- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -91,6 +91,7 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes self.scrollNode.view.clipsToBounds = false self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.scrollNode.view.tag = 0x5C4011 + self.scrollNode.view.scrollsToTop = false let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in guard let strongSelf = self, !strongSelf.isDismissed else { diff --git a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift index 1f1f795837..b3900d15c5 100644 --- a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift @@ -11,8 +11,6 @@ enum NavigationSplitContainerScrollToTop { final class NavigationSplitContainer: ASDisplayNode { private var theme: NavigationControllerTheme - private let masterScrollToTopView: ScrollToTopView - private let detailScrollToTopView: ScrollToTopView private let masterContainer: NavigationContainer private let detailContainer: NavigationContainer private let separator: ASDisplayNode @@ -39,18 +37,9 @@ final class NavigationSplitContainer: ASDisplayNode { self.detailContainer.isInFocus = isInFocus } - init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void, scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) { + init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) { self.theme = theme - self.masterScrollToTopView = ScrollToTopView(frame: CGRect()) - self.masterScrollToTopView.action = { - scrollToTop(.master) - } - self.detailScrollToTopView = ScrollToTopView(frame: CGRect()) - self.detailScrollToTopView.action = { - scrollToTop(.detail) - } - self.masterContainer = NavigationContainer(isFlat: false, controllerRemoved: controllerRemoved) self.masterContainer.clipsToBounds = true @@ -65,8 +54,6 @@ final class NavigationSplitContainer: ASDisplayNode { self.addSubnode(self.masterContainer) self.addSubnode(self.detailContainer) self.addSubnode(self.separator) - self.view.addSubview(self.masterScrollToTopView) - self.view.addSubview(self.detailScrollToTopView) } func hasNonReadyControllers() -> Bool { @@ -83,13 +70,21 @@ final class NavigationSplitContainer: ASDisplayNode { self.separator.backgroundColor = theme.navigationBar.separatorColor } + func scrollToTopProxyFrames(layout: ContainerViewLayout) -> (master: CGRect, detail: CGRect) { + let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) + let detailWidth = layout.size.width - masterWidth + let scrollToTopHeight = max(layout.statusBarHeight ?? layout.safeInsets.top, 1.0) + + return ( + master: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: scrollToTopHeight)), + detail: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: scrollToTopHeight)) + ) + } + func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], detailsPlaceholderNode: NavigationDetailsPlaceholderNode?, transition: ContainedViewLayoutTransition) { let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let detailWidth = layout.size.width - masterWidth - self.masterScrollToTopView.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: masterWidth, height: 1.0)) - self.detailScrollToTopView.frame = CGRect(origin: CGPoint(x: masterWidth, y: -1.0), size: CGSize(width: detailWidth, height: 1.0)) - transition.updateFrame(node: self.masterContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: layout.size.height))) transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height))) transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 37137b665e..00d2a9c12b 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -29,11 +29,12 @@ public final class NavigationBarTheme { public let badgeTextColor: UIColor public let edgeEffectColor: UIColor? public let accentButtonColor: UIColor + public let accentDisabledButtonColor: UIColor public let accentForegroundColor: UIColor public let style: NavigationBar.Style public let glassStyle: NavigationBar.GlassStyle - public init(overallDarkAppearance: Bool, buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, edgeEffectColor: UIColor? = nil, accentButtonColor: UIColor, accentForegroundColor: UIColor, style: NavigationBar.Style = .legacy, glassStyle: NavigationBar.GlassStyle = .default) { + public init(overallDarkAppearance: Bool, buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, edgeEffectColor: UIColor? = nil, accentButtonColor: UIColor, accentDisabledButtonColor: UIColor, accentForegroundColor: UIColor, style: NavigationBar.Style = .legacy, glassStyle: NavigationBar.GlassStyle = .default) { self.overallDarkAppearance = overallDarkAppearance self.buttonColor = buttonColor self.disabledButtonColor = disabledButtonColor @@ -47,17 +48,18 @@ public final class NavigationBarTheme { self.badgeTextColor = badgeTextColor self.edgeEffectColor = edgeEffectColor self.accentButtonColor = accentButtonColor + self.accentDisabledButtonColor = accentDisabledButtonColor self.accentForegroundColor = accentForegroundColor self.style = style self.glassStyle = glassStyle } public func withUpdatedBackgroundColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle) + return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentDisabledButtonColor: self.accentDisabledButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle) } public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle) + return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentDisabledButtonColor: self.accentDisabledButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle) } } @@ -177,6 +179,7 @@ public protocol NavigationBar: ASDisplayNode { var leftButtonNode: NavigationButtonNode { get } var rightButtonNode: NavigationButtonNode { get } var additionalContentNode: SparseNode { get } + var edgeEffectView: UIView? { get } func reattachAdditionalContentNode() diff --git a/submodules/Display/Source/ScrollToTopProxyView.swift b/submodules/Display/Source/ScrollToTopProxyView.swift index b7aa0d8d9c..ee95519900 100644 --- a/submodules/Display/Source/ScrollToTopProxyView.swift +++ b/submodules/Display/Source/ScrollToTopProxyView.swift @@ -8,11 +8,11 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate { super.init(frame: frame) self.isOpaque = false - self.backgroundColor = .clear self.delegate = self self.scrollsToTop = true - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.contentInsetAdjustmentBehavior = .never + self.contentInsetAdjustmentBehavior = .never + if #available(iOS 17.0, *) { + self.allowsKeyboardScrolling = false } } @@ -23,8 +23,8 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate { override var frame: CGRect { didSet { let frame = self.frame - self.contentSize = CGSize(width: frame.width, height: frame.height + 1.0) - self.contentOffset = CGPoint(x: 0.0, y: 1.0) + self.contentSize = CGSize(width: frame.width, height: frame.height + 1000.0) + self.contentOffset = CGPoint(x: 0.0, y: 1000.0) } } @@ -48,3 +48,83 @@ class ScrollToTopNode: ASDisplayNode { }) } } + +final class WindowScrollToTopProxyViews { + enum Mode { + case disabled + case flat(frame: CGRect) + case split(masterFrame: CGRect, detailFrame: CGRect) + } + + private let flatView: ScrollToTopView + private let masterView: ScrollToTopView + private let detailView: ScrollToTopView + + init(scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) { + self.flatView = ScrollToTopView(frame: CGRect()) + self.masterView = ScrollToTopView(frame: CGRect()) + self.detailView = ScrollToTopView(frame: CGRect()) + + self.flatView.action = { + scrollToTop(.master) + } + self.masterView.action = { + scrollToTop(.master) + } + self.detailView.action = { + scrollToTop(.detail) + } + } + + deinit { + self.disable(self.flatView) + self.disable(self.masterView) + self.disable(self.detailView) + } + + func update(window: UIWindow?, mode: Mode, referenceView: UIView?) { + guard let window else { + self.disable(self.flatView) + self.disable(self.masterView) + self.disable(self.detailView) + return + } + + switch mode { + case .disabled: + self.disable(self.flatView) + self.disable(self.masterView) + self.disable(self.detailView) + case let .flat(frame): + self.activate(self.flatView, in: window, frame: frame, referenceView: referenceView) + self.disable(self.masterView) + self.disable(self.detailView) + case let .split(masterFrame, detailFrame): + self.disable(self.flatView) + self.activate(self.masterView, in: window, frame: masterFrame, referenceView: referenceView) + self.activate(self.detailView, in: window, frame: detailFrame, referenceView: referenceView) + } + } + + private func activate(_ view: ScrollToTopView, in window: UIWindow, frame: CGRect, referenceView: UIView?) { + if let referenceView, referenceView.superview === window { + if view.superview !== window { + view.removeFromSuperview() + } + window.insertSubview(view, aboveSubview: referenceView) + } else if view.superview !== window { + view.removeFromSuperview() + window.addSubview(view) + } + + view.isHidden = false + view.scrollsToTop = true + view.frame = frame + } + + private func disable(_ view: ScrollToTopView) { + view.scrollsToTop = false + view.isHidden = true + view.removeFromSuperview() + } +} diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index bf254de0c4..413d8a6de0 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -306,14 +306,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { return self._ready } - private var scrollToTopView: ScrollToTopView? - public var scrollToTop: (() -> Void)? { - didSet { - if self.isViewLoaded { - self.updateScrollToTopView() - } - } - } + public var scrollToTop: (() -> Void)? public var scrollToTopWithTabBar: (() -> Void)? public var longTapWithTabBar: (() -> Void)? @@ -357,24 +350,6 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open func didAppearInContextPreview() { } - private func updateScrollToTopView() { - /*if self.scrollToTop != nil { - if let displayNode = self._displayNode , self.scrollToTopView == nil { - let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.bounds.size.width, height: 1.0)) - scrollToTopView.action = { [weak self] in - if let scrollToTop = self?.scrollToTop { - scrollToTop() - } - } - self.scrollToTopView = scrollToTopView - self.view.addSubview(scrollToTopView) - } - } else*/ if let scrollToTopView = self.scrollToTopView { - scrollToTopView.removeFromSuperview() - self.scrollToTopView = nil - } - } - public var titleSignal: Signal { return Signal { [weak self] subscriber in guard let self else { @@ -514,10 +489,6 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { if self.automaticallyControlPresentationContextLayout { self.presentationContext.containerLayoutUpdated(layout, transition: transition) } - - if let scrollToTopView = self.scrollToTopView { - scrollToTopView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 10.0) - } } open func navigationStackConfigurationUpdated(next: [ViewController]) { @@ -541,7 +512,6 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { } open func displayNodeDidLoad() { - self.updateScrollToTopView() if let backgroundColor = self.displayNode.backgroundColor, backgroundColor.alpha.isEqual(to: 1.0) { self.blocksBackgroundWhenInOverlay = true self.isOpaqueWhenInOverlay = true diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index b2be9d65ae..499389abb4 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -64,7 +64,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/LegacyComponents:LegacyComponents", @@ -108,6 +107,11 @@ swift_library( "//submodules/TelegramUI/Components/DynamicCornerRadiusView", "//submodules/TelegramUI/Components/StickerPickerScreen", "//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation", + "//submodules/TelegramUI/Components/SegmentControlComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/LiquidLens", + "//submodules/TelegramUI/Components/TabSelectionRecognizer", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift index dfcdf6bef8..206563fa1b 100644 --- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift +++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift @@ -10,10 +10,12 @@ import TelegramPresentationData import SheetComponent import ViewControllerComponent import BlurredBackgroundComponent -import SegmentedControlNode +import SegmentControlComponent import MultilineTextComponent import HexColor import MediaEditor +import GlassBarButtonComponent +import BundleIconComponent private let palleteColors: [UInt32] = [ 0xffffff, 0xebebeb, 0xd6d6d6, 0xc2c2c2, 0xadadad, 0x999999, 0x858585, 0x707070, 0x5c5c5c, 0x474747, 0x333333, 0x000000, @@ -1494,73 +1496,6 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: }) } -private class SegmentedControlComponent: Component { - let values: [String] - let selectedIndex: Int - let selectionChanged: (Int) -> Void - - init(values: [String], selectedIndex: Int, selectionChanged: @escaping (Int) -> Void) { - self.values = values - self.selectedIndex = selectedIndex - self.selectionChanged = selectionChanged - } - - static func ==(lhs: SegmentedControlComponent, rhs: SegmentedControlComponent) -> Bool { - if lhs.values != rhs.values { - return false - } - if lhs.selectedIndex != rhs.selectedIndex { - return false - } - return true - } - - final class View: UIView { - private let backgroundNode: NavigationBackgroundNode - private let node: SegmentedControlNode - - init() { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x888888, alpha: 0.1)) - self.node = SegmentedControlNode(theme: SegmentedControlTheme(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), items: [], selectedIndex: 0) - - super.init(frame: CGRect()) - - self.addSubview(self.backgroundNode.view) - self.addSubview(self.node.view) - } - - required init?(coder aDecoder: NSCoder) { - preconditionFailure() - } - - func update(component: SegmentedControlComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.node.items = component.values.map { SegmentedControlItem(title: $0) } - self.node.selectedIndex = component.selectedIndex - let selectionChanged = component.selectionChanged - self.node.selectedIndexChanged = { [weak self] index in - self?.window?.endEditing(true) - selectionChanged(index) - } - - let size = self.node.updateLayout(.stretchToFill(width: availableSize.width), transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.node.view, frame: CGRect(origin: CGPoint(), size: size)) - - transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) - self.backgroundNode.update(size: size, cornerRadius: 10.0, transition: .immediate) - - return size - } - } - - func makeView() -> View { - return View() - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} - final class ColorSwatchComponent: Component { enum SwatchType: Equatable { case main @@ -1886,30 +1821,6 @@ private final class ColorPickerContent: CombinedComponent { } final class State: ComponentState { - var cachedEyedropperImage: UIImage? - var eyedropperImage: UIImage { - let eyedropperImage: UIImage - if let image = self.cachedEyedropperImage { - eyedropperImage = image - } else { - eyedropperImage = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Eyedropper"), color: .white)! - self.cachedEyedropperImage = eyedropperImage - } - return eyedropperImage - } - - var cachedCloseImage: UIImage? - var closeImage: UIImage { - let closeImage: UIImage - if let image = self.cachedCloseImage { - closeImage = image - } else { - closeImage = generateCloseButtonImage(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xa8aab1))! - self.cachedCloseImage = closeImage - } - return closeImage - } - var selectedMode: Int = 0 var selectedColor: DrawingColor @@ -1951,10 +1862,10 @@ private final class ColorPickerContent: CombinedComponent { } static var body: Body { - let eyedropperButton = Child(Button.self) - let closeButton = Child(Button.self) + let eyedropperButton = Child(GlassBarButtonComponent.self) + let closeButton = Child(GlassBarButtonComponent.self) let title = Child(MultilineTextComponent.self) - let modeControl = Child(SegmentedControlComponent.self) + let modeControl = Child(SegmentControlComponent.self) let colorGrid = Child(ColorGridComponent.self) let colorSpectrum = Child(ColorSpectrumComponent.self) @@ -1985,50 +1896,45 @@ private final class ColorPickerContent: CombinedComponent { let sideInset: CGFloat = 16.0 let eyedropperButton = eyedropperButton.update( - component: Button( - content: AnyComponent( - Image(image: state.eyedropperImage) + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent(BundleIconComponent(name: "Media Editor/Eyedropper", tintColor: .white)) ), - action: { [weak component] in - component?.eyedropper() + action: { _ in + component.eyedropper() } - ).minSize(CGSize(width: 30.0, height: 30.0)), - availableSize: CGSize(width: 19.0, height: 19.0), + ), + availableSize: CGSize(width: 44.0, height: 44.0), transition: .immediate ) context.add(eyedropperButton - .position(CGPoint(x: environment.safeInsets.left + eyedropperButton.size.width + 1.0, y: 29.0)) + .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - eyedropperButton.size.width / 2.0 - 16.0, y: 16.0 + eyedropperButton.size.height / 2.0)) ) let closeButton = closeButton.update( - component: Button( - content: AnyComponent(ZStack([ - AnyComponentWithIdentity( - id: "background", - component: AnyComponent( - BlurredBackgroundComponent( - color: UIColor(rgb: 0x888888, alpha: 0.1) - ) - ) - ), - AnyComponentWithIdentity( - id: "icon", - component: AnyComponent( - Image(image: state.closeImage) - ) - ), - ])), - action: { [weak component] in - component?.dismiss() + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent(BundleIconComponent(name: "Navigation/Close", tintColor: .white)) + ), + action: { _ in + component.dismiss() } ), availableSize: CGSize(width: 30.0, height: 30.0), transition: .immediate ) context.add(closeButton - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - closeButton.size.width - 1.0, y: 29.0)) - .clipsToBounds(true) - .cornerRadius(15.0) + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) let title = title.update( @@ -2046,17 +1952,24 @@ private final class ColorPickerContent: CombinedComponent { transition: .immediate ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: 29.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: 16.0 + 22.0)) ) - var contentHeight: CGFloat = 58.0 + var contentHeight: CGFloat = 76.0 let modeControl = modeControl.update( - component: SegmentedControlComponent( - values: [strings.Paint_ColorGrid, strings.Paint_ColorSpectrum, strings.Paint_ColorSliders], - selectedIndex: 0, - selectionChanged: { [weak state] index in - state?.updateSelectedMode(index) + component: SegmentControlComponent( + theme: SegmentControlComponent.Theme(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.07), legacyBackgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), textColor: .white, dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), + items: [ + .init(id: 0, title: strings.Paint_ColorGrid), + .init(id: 1, title: strings.Paint_ColorSpectrum), + .init(id: 2, title: strings.Paint_ColorSliders), + ], + selectedId: state.selectedMode, + action: { [weak state] index in + if let value = index.base as? Int { + state?.updateSelectedMode(value) + } } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), @@ -2379,6 +2292,7 @@ private final class ColorPickerSheetComponent: CombinedComponent { }) } )), + style: .glass, backgroundColor: .blur(.dark), animateOut: animateOut ), diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 52827a4718..419309c549 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -6,7 +6,6 @@ import Display import ComponentFlow import LegacyComponents import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import AccountContext @@ -26,6 +25,9 @@ import FastBlur import MediaEditor import StickerPickerScreen import ImageObjectSeparation +import GlassBarButtonComponent +import GlassControls +import BundleIconComponent public struct DrawingResultData { public let data: Data? @@ -364,15 +366,17 @@ private final class ReferenceContentSource: ContextReferenceContentSource { private let sourceView: UIView private let contentArea: CGRect private let customPosition: CGPoint + private let actionsPosition: ContextControllerReferenceViewInfo.ActionsPosition - init(sourceView: UIView, contentArea: CGRect, customPosition: CGPoint) { + init(sourceView: UIView, contentArea: CGRect, customPosition: CGPoint, actionsPosition: ContextControllerReferenceViewInfo.ActionsPosition = .top) { self.sourceView = sourceView self.contentArea = contentArea self.customPosition = customPosition + self.actionsPosition = actionsPosition } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: self.customPosition, actionsPosition: .top) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: self.customPosition, actionsPosition: self.actionsPosition) } } @@ -468,13 +472,11 @@ private let bottomGradientTag = GenericComponentViewTag() private let undoButtonTag = GenericComponentViewTag() private let redoButtonTag = GenericComponentViewTag() private let clearAllButtonTag = GenericComponentViewTag() +private let topButtonsTag = GenericComponentViewTag() private let colorButtonTag = GenericComponentViewTag() private let addButtonTag = GenericComponentViewTag() private let toolsTag = GenericComponentViewTag() private let modeTag = GenericComponentViewTag() -private let flipButtonTag = GenericComponentViewTag() -private let fillButtonTag = GenericComponentViewTag() -private let zoomOutButtonTag = GenericComponentViewTag() private let textSettingsTag = GenericComponentViewTag() private let sizeSliderTag = GenericComponentViewTag() private let fontTag = GenericComponentViewTag() @@ -490,6 +492,12 @@ private let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag, private let cancelButtonTag = GenericComponentViewTag() private let doneButtonTag = GenericComponentViewTag() +enum DrawingMode: Int { + case drawing + case sticker + case text +} + private final class DrawingScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -617,7 +625,6 @@ private final class DrawingScreenComponent: CombinedComponent { final class State: ComponentState { enum ImageKey: Hashable { case undo - case redo case done case add case fill @@ -634,8 +641,6 @@ private final class DrawingScreenComponent: CombinedComponent { switch key { case .undo: image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Undo"), color: .white)! - case .redo: - image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Redo"), color: .white)! case .done: image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)! case .add: @@ -653,13 +658,7 @@ private final class DrawingScreenComponent: CombinedComponent { return image } } - - enum Mode { - case drawing - case sticker - case text - } - + private let context: AccountContext private let updateToolState: ActionSlot private let insertEntity: ActionSlot @@ -676,7 +675,7 @@ private final class DrawingScreenComponent: CombinedComponent { private let entityViewForEntity: (DrawingEntity) -> DrawingEntityView? private let present: (ViewController) -> Void - var currentMode: Mode + var currentMode: DrawingMode var drawingState: DrawingState var drawingViewState: DrawingView.NavigationState var currentColor: DrawingColor @@ -825,7 +824,7 @@ private final class DrawingScreenComponent: CombinedComponent { let tools = self.drawingState.tools let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Void in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.drawingSettings, { _ in - return PreferencesEntry(DrawingSettings(tools: tools, colors: [])) + return EnginePreferencesEntry(DrawingSettings(tools: tools, colors: [])) }) }).start() } @@ -993,7 +992,7 @@ private final class DrawingScreenComponent: CombinedComponent { self.present(contextController) } - func updateCurrentMode(_ mode: Mode, update: Bool = true) { + func updateCurrentMode(_ mode: DrawingMode, update: Bool = true) { self.currentMode = mode if let selectedEntity = self.selectedEntity { if selectedEntity is DrawingStickerEntity || selectedEntity is DrawingTextEntity { @@ -1059,15 +1058,13 @@ private final class DrawingScreenComponent: CombinedComponent { let topGradient = Child(BlurredGradientComponent.self) let bottomGradient = Child(BlurredGradientComponent.self) - let undoButton = Child(Button.self) + let undoButton = Child(GlassBarButtonComponent.self) + let redoButton = Child(GlassBarButtonComponent.self) + let clearAllButton = Child(GlassBarButtonComponent.self) + let topButtons = Child(GlassControlPanelComponent.self) - let redoButton = Child(Button.self) - let clearAllButton = Child(Button.self) - - let zoomOutButton = Child(Button.self) - let tools = Child(ToolsComponent.self) - let modeAndSize = Child(ModeAndSizeComponent.self) + let mode = Child(ModeComponent.self) let colorButton = Child(ColorSwatchComponent.self) @@ -1083,16 +1080,11 @@ private final class DrawingScreenComponent: CombinedComponent { let swatch8Button = Child(ColorSwatchComponent.self) let addButton = Child(Button.self) - - let flipButton = Child(Button.self) - let fillButton = Child(Button.self) - - let backButton = Child(Button.self) - let doneButton = Child(Button.self) + + let backButton = Child(GlassBarButtonComponent.self) + let doneButton = Child(GlassBarButtonComponent.self) let textSize = Child(TextSizeSliderComponent.self) - let textCancelButton = Child(Button.self) - let textDoneButton = Child(Button.self) let presetColors: [DrawingColor] = [ DrawingColor(rgb: 0xff453a), @@ -1221,6 +1213,8 @@ private final class DrawingScreenComponent: CombinedComponent { var additionalBottomInset: CGFloat = 0.0 if component.sourceHint == .storyEditor { additionalBottomInset = max(0.0, previewBottomInset - environment.safeInsets.bottom - 49.0) + } else { + additionalBottomInset = 8.0 } if let textEntity = state.selectedEntity as? DrawingTextEntity { @@ -1319,8 +1313,8 @@ private final class DrawingScreenComponent: CombinedComponent { ) } - let rightButtonPosition = rightEdge - 24.0 - var offsetX: CGFloat = leftEdge + 24.0 + let rightButtonPosition = rightEdge - 28.0 + var offsetX: CGFloat = leftEdge + 28.0 let delta: CGFloat = (rightButtonPosition - offsetX) / 7.0 let applySwatchColor: (DrawingColor) -> Void = { [weak state] color in @@ -1609,6 +1603,7 @@ private final class DrawingScreenComponent: CombinedComponent { } var hasTopButtons = false + var centerItems: [GlassControlGroupComponent.Item] = [] if let entity = state.selectedEntity { var isFilled: Bool? if let entity = entity as? DrawingSimpleShapeEntity { @@ -1626,12 +1621,13 @@ private final class DrawingScreenComponent: CombinedComponent { hasTopButtons = isFilled != nil || hasFlip - if let isFilled = isFilled { - let fillButton = fillButton.update( - component: Button( - content: AnyComponent( - Image(image: state.image(isFilled ? .fill : .stroke)) - ), + if controlsAreVisible { + if let isFilled = isFilled { + centerItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("fill"), + content: .customIcon(id: AnyHashable("fill"), component: AnyComponent( + Image(image: state.image(isFilled ? .fill : .stroke), size: CGSize(width: 30.0, height: 30.0)) + ), insets: .zero), action: { [weak state] in guard let state = state else { return @@ -1662,25 +1658,15 @@ private final class DrawingScreenComponent: CombinedComponent { } state.updated(transition: .easeInOut(duration: 0.2)) } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(fillButtonTag), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate - ) - context.add(fillButton - .position(CGPoint(x: context.availableSize.width / 2.0 - (hasFlip ? 46.0 : 0.0), y: topInset)) - .appear(.default(scale: true)) - .disappear(.default(scale: true)) - .opacity(!controlsAreVisible ? 0.0 : 1.0) - .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) - ) - } - - if hasFlip { - let flipButton = flipButton.update( - component: Button( - content: AnyComponent( - Image(image: state.image(.flip)) - ), + )) + } + + if hasFlip { + centerItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("flip"), + content: .customIcon(id: AnyHashable("flip"), component: AnyComponent( + Image(image: state.image(.flip), size: CGSize(width: 30.0, height: 30.0)) + ), insets: .zero), action: { [weak state] in guard let state = state else { return @@ -1696,17 +1682,8 @@ private final class DrawingScreenComponent: CombinedComponent { } state.updated(transition: .easeInOut(duration: 0.2)) } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(flipButtonTag), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate - ) - context.add(flipButton - .position(CGPoint(x: context.availableSize.width / 2.0 + (isFilled != nil ? 46.0 : 0.0), y: topInset)) - .appear(.default(scale: true)) - .disappear(.default(scale: true)) - .opacity(!controlsAreVisible ? 0.0 : 1.0) - .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) - ) + )) + } } } @@ -1731,28 +1708,24 @@ private final class DrawingScreenComponent: CombinedComponent { sizeValue = entity.lineWidth } } - if state.drawingViewState.canZoomOut && !hasTopButtons { - let zoomOutButton = zoomOutButton.update( - component: Button( - content: AnyComponent( - ZoomOutButtonContent( - title: strings.Paint_ZoomOut, - image: state.image(.zoomOut) - ) - ), - action: { - dismissEyedropper.invoke(Void()) - performAction.invoke(.zoomOut) - } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(zoomOutButtonTag), - availableSize: CGSize(width: 120.0, height: 33.0), - transition: .immediate - ) - context.add(zoomOutButton - .position(CGPoint(x: context.availableSize.width / 2.0, y: environment.safeInsets.top + 32.0 - UIScreenPixel)) - .appear(.default(scale: true, alpha: true)) - .disappear(.default(scale: true, alpha: true)) - ) + if state.drawingViewState.canZoomOut && !hasTopButtons && controlsAreVisible { + centerItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("zoomOut"), + content: .customIcon(id: AnyHashable("zoomOut"), component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "icon", component: AnyComponent( + Image(image: state.image(.zoomOut), size: CGSize(width: 24.0, height: 24.0)) + )), + AnyComponentWithIdentity(id: "label", component: AnyComponent( + Text(text: environment.strings.Paint_ZoomOut, font: Font.regular(17.0), color: .white) + )), + ], spacing: 2.0) + ), insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)), + action: { + dismissEyedropper.invoke(Void()) + performAction.invoke(.zoomOut) + } + )) } } if let sizeValue { @@ -1785,111 +1758,170 @@ private final class DrawingScreenComponent: CombinedComponent { .position(CGPoint(x: textSize.size.width / 2.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0)) .opacity(sizeSliderVisible && controlsAreVisible ? 1.0 : 0.0) ) - - let undoButton = undoButton.update( - component: Button( - content: AnyComponent( - Image(image: state.image(.undo)) - ), - isEnabled: state.drawingViewState.canUndo, - action: { + + var leftItems: [GlassControlGroupComponent.Item] = [] + var rightItems: [GlassControlGroupComponent.Item] = [] + + if !isEditingText && controlsAreVisible { + leftItems.append(GlassControlGroupComponent.Item( + id: "undo", + content: .customIcon(id: "undo", component: AnyComponent(Image(image: state.image(.undo), tintColor: state.drawingViewState.canUndo ? .white : .white.withAlphaComponent(0.4), size: CGSize(width: 30.0, height: 30.0))), insets: .zero), + action: state.drawingViewState.canUndo ? { dismissEyedropper.invoke(Void()) performAction.invoke(.undo) - } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(undoButtonTag), - availableSize: CGSize(width: 24.0, height: 24.0), + } : nil + )) + + if state.drawingViewState.canRedo { + leftItems.append(GlassControlGroupComponent.Item( + id: "redo", + content: .customIcon(id: "redo", component: AnyComponent(Image(image: state.image(.undo), tintColor: state.drawingViewState.canRedo ? .white : .white.withAlphaComponent(0.4), size: CGSize(width: 30.0, height: 30.0), flipHorizontally: true)), insets: .zero), + action: state.drawingViewState.canRedo ? { + dismissEyedropper.invoke(Void()) + performAction.invoke(.redo) + } : nil + )) + } + } + + let undoButton = undoButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent(Image(image: state.image(.undo))) + ), + action: { _ in + dismissEyedropper.invoke(Void()) + performAction.invoke(.undo) + }, + tag: undoButtonTag + ), + availableSize: CGSize(width: 44.0, height: 44.0), transition: context.transition ) context.add(undoButton - .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: topInset)) + .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 16.0, y: topInset)) .scale(isEditingText ? 0.01 : 1.0) - .opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0) - .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) + .opacity(0.0) + //.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0) ) - let redoButton = redoButton.update( - component: Button( - content: AnyComponent( - Image(image: state.image(.redo)) + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent(Image(image: state.image(.undo), flipHorizontally: true)) ), - action: { + action: { _ in dismissEyedropper.invoke(Void()) performAction.invoke(.redo) - } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(redoButtonTag), - availableSize: CGSize(width: 24.0, height: 24.0), + }, + tag: redoButtonTag + ), + availableSize: CGSize(width: 44.0, height: 44.0), transition: context.transition ) context.add(redoButton .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: topInset)) .scale(state.drawingViewState.canRedo && !isEditingText ? 1.0 : 0.01) - .opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0) - .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil) + .opacity(0.0) + //.opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0) ) let clearAllButton = clearAllButton.update( - component: Button( - content: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: strings.Paint_Clear, font: Font.regular(17.0), textColor: .white)), - textShadowColor: component.sourceHint == .storyEditor ? UIColor(rgb: 0x000000, alpha: 0.35) : nil, - textShadowBlur: 2.0 - ) + component: GlassBarButtonComponent( + size: nil, + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "label", + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Paint_Clear, font: Font.regular(17.0), textColor: .white)) + )) ), - isEnabled: state.drawingViewState.canClear, - action: { + action: { _ in dismissEyedropper.invoke(Void()) performAction.invoke(.clear) - } - ).tagged(clearAllButtonTag), + }, + tag: clearAllButtonTag + ), availableSize: CGSize(width: 180.0, height: 30.0), transition: context.transition ) context.add(clearAllButton .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: topInset)) .scale(isEditingText ? 0.01 : 1.0) - .opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0) + .opacity(0.0) + //.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0) ) - let textCancelButton = textCancelButton.update( - component: Button( - content: AnyComponent( - Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: .white) - ), + if !isEditingText && controlsAreVisible { + rightItems.append(GlassControlGroupComponent.Item( + id: "clearAll", + content: .text(strings.Paint_Clear), + action: state.drawingViewState.canClear ? { + dismissEyedropper.invoke(Void()) + performAction.invoke(.clear) + } : nil + )) + } + + if isEditingText { + leftItems.append(GlassControlGroupComponent.Item( + id: "cancel", + content: .text(strings.Common_Cancel), action: { [weak state] in if let entity = state?.selectedEntity as? DrawingTextEntity { endEditingTextEntityView.invoke((entity.uuid, true)) } } - ), - availableSize: CGSize(width: 100.0, height: 30.0), - transition: context.transition - ) - context.add(textCancelButton - .position(CGPoint(x: environment.safeInsets.left + textCancelButton.size.width / 2.0 + 13.0, y: topInset)) - .scale(isEditingText ? 1.0 : 0.01) - .opacity(isEditingText ? 1.0 : 0.0) - ) - - let textDoneButton = textDoneButton.update( - component: Button( - content: AnyComponent( - Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: .white) - ), + )) + + rightItems.append(GlassControlGroupComponent.Item( + id: "done", + content: .text(strings.Common_Done), action: { [weak state] in if let entity = state?.selectedEntity as? DrawingTextEntity { endEditingTextEntityView.invoke((entity.uuid, false)) } } + )) + } + + let topButtons = topButtons.update( + component: GlassControlPanelComponent( + theme: defaultDarkPresentationTheme, + leftItem: leftItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: leftItems, + background: .panel + ), + centralItem: centerItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: centerItems, + background: .panel + ), + rightItem: rightItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: rightItems, + background: .panel + ), + centerAlignmentIfPossible: true, + isDark: true, + tag: topButtonsTag ), - availableSize: CGSize(width: 100.0, height: 30.0), + availableSize: CGSize(width: context.availableSize.width - 32.0, height: 44.0), transition: context.transition ) - context.add(textDoneButton - .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - textDoneButton.size.width / 2.0 - 13.0, y: topInset)) - .scale(isEditingText ? 1.0 : 0.01) - .opacity(isEditingText ? 1.0 : 0.0) + context.add(topButtons + .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset)) + //.opacity(isEditingText ? 1.0 : 0.0) ) var color: DrawingColor? @@ -1931,7 +1963,7 @@ private final class DrawingScreenComponent: CombinedComponent { transition: context.transition ) context.add(colorButton - .position(CGPoint(x: leftEdge + colorButton.size.width / 2.0 + 2.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0 - additionalBottomInset)) + .position(CGPoint(x: leftEdge + colorButton.size.width / 2.0 + 6.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0 - additionalBottomInset)) .appear(.default(scale: true)) .disappear(.default(scale: true)) .opacity(controlsAreVisible ? 1.0 : 0.0) @@ -1980,7 +2012,7 @@ private final class DrawingScreenComponent: CombinedComponent { transition: .immediate ) context.add(addButton - .position(CGPoint(x: rightEdge - addButton.size.width / 2.0 - 2.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0 - additionalBottomInset)) + .position(CGPoint(x: rightEdge - addButton.size.width / 2.0 - 6.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0 - additionalBottomInset)) .appear(.default(scale: true)) .disappear(.default(scale: true)) .cornerRadius(12.0) @@ -1988,27 +2020,38 @@ private final class DrawingScreenComponent: CombinedComponent { ) let doneButton = doneButton.update( - component: Button( - content: AnyComponent( - Image(image: state.image(.done)) + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: UIColor(rgb: 0x0088ff), + isDark: true, + state: .tintedGlass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Done", tintColor: .white) + ) ), - action: { [weak state] in + action: { [weak state] _ in dismissEyedropper.invoke(Void()) state?.saveToolState() apply.invoke(Void()) - } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(doneButtonTag), - availableSize: CGSize(width: 33.0, height: 33.0), + }, + tag: doneButtonTag + ), + availableSize: CGSize(width: 44.0, height: 44.0), transition: .immediate ) - var doneButtonPosition = CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 3.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel) + var doneButtonPosition = CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 14.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel) if component.sourceHint == .storyEditor { doneButtonPosition.x = doneButtonPosition.x - 2.0 if case .regular = environment.metrics.widthClass { doneButtonPosition.x -= 20.0 } - doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + controlsBottomInset + doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + controlsBottomInset + 5.0 + } else { + doneButtonPosition.x = doneButtonPosition.x - 12.0 + doneButtonPosition.y -= 3.0 - UIScreenPixel } context.add(doneButton .position(doneButtonPosition) @@ -2028,116 +2071,87 @@ private final class DrawingScreenComponent: CombinedComponent { .opacity(controlsAreVisible ? 1.0 : 0.0) ) - let selectedIndex: Int - switch state.currentMode { - case .drawing: - selectedIndex = 0 - case .sticker: - selectedIndex = 1 - case .text: - selectedIndex = 2 - } - - var selectedSize: CGFloat = 0.0 - if let entity = state.selectedEntity { - selectedSize = entity.lineWidth + let modeConstrainedWidth: CGFloat + if component.sourceHint == .storyEditor { + modeConstrainedWidth = context.availableSize.width - 66.0 * 2.0 } else { - selectedSize = state.drawingState.toolState(for: state.drawingState.selectedTool).size ?? 0.0 + modeConstrainedWidth = context.availableSize.width - 76.0 * 2.0 } - - let modeAndSize = modeAndSize.update( - component: ModeAndSizeComponent( - values: [ strings.Paint_Draw, strings.Paint_Sticker, strings.Paint_Text], - sizeValue: selectedSize, - isEditing: false, - isEnabled: true, - rightInset: modeRightInset - 57.0, - tag: modeTag, - selectedIndex: selectedIndex, - selectionChanged: { [weak state] index in - dismissEyedropper.invoke(Void()) - guard let state = state else { - return - } - switch index { - case 1: - state.presentStickerPicker() - case 2: - state.addTextEntity() - default: - state.updateCurrentMode(.drawing) - } - }, - sizeUpdated: { [weak state] size in - if let state = state { + let mode = mode.update( + component: ModeComponent( + isTablet: false, + strings: environment.strings, + tintColor: .white, + availableModes: [.drawing, .sticker, .text], + currentMode: state.currentMode, + updatedMode: { [weak state] mode in + if let state { dismissEyedropper.invoke(Void()) - state.updateBrushSize(size) - if state.selectedEntity == nil { - previewBrushSize.invoke(size) + switch mode { + case .drawing: + state.updateCurrentMode(.drawing) + case .sticker: + state.presentStickerPicker() + case .text: + state.addTextEntity() } } }, - sizeReleased: { - previewBrushSize.invoke(nil) - } + tag: modeTag ), - availableSize: CGSize(width: availableWidth - 57.0 - modeRightInset, height: context.availableSize.height), + availableSize: CGSize(width: modeConstrainedWidth, height: 44.0), transition: context.transition ) - var modeAndSizePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - modeAndSize.size.height / 2.0 - 9.0) + var modePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - mode.size.height / 2.0 - 9.0) if component.sourceHint == .storyEditor { - modeAndSizePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + modeAndSize.size.height / 2.0) + controlsBottomInset + modePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + mode.size.height / 2.0) + controlsBottomInset + } else { + modePosition.y += 4.0 } - context.add(modeAndSize - .position(modeAndSizePosition) + context.add(mode + .position(modePosition) .opacity(controlsAreVisible ? 1.0 : 0.0) ) - var animatingOut = false - if let appearanceTransition = context.transition.userData(DrawingScreenTransition.self), case .animateOut = appearanceTransition { - animatingOut = true - } - - if animatingOut && component.sourceHint == .storyEditor { - - } else { - let backButton = backButton.update( - component: Button( - content: AnyComponent( - LottieAnimationComponent( - animation: LottieAnimationComponent.AnimationItem( - name: "media_backToCancel", - mode: .animating(loop: false), - range: animatingOut || component.isAvatar ? (0.5, 1.0) : (0.0, 0.5) - ), - colors: ["__allcolors__": .white], - size: CGSize(width: 33.0, height: 33.0) - ) - ), - action: { [weak state] in - if let state = state { - dismissEyedropper.invoke(Void()) - state.saveToolState() - dismiss.invoke(Void()) - } + let backButton = backButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Close", tintColor: .white) + ) + ), + action: { [weak state] _ in + if let state { + dismissEyedropper.invoke(Void()) + state.saveToolState() + dismiss.invoke(Void()) } - ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(cancelButtonTag), - availableSize: CGSize(width: 33.0, height: 33.0), - transition: .immediate - ) - var backButtonPosition = CGPoint(x: environment.safeInsets.left + backButton.size.width / 2.0 + 3.0, y: context.availableSize.height - environment.safeInsets.bottom - backButton.size.height / 2.0 - 2.0 - UIScreenPixel) - if component.sourceHint == .storyEditor { - backButtonPosition.x = backButtonPosition.x + 2.0 - if case .regular = environment.metrics.widthClass { - backButtonPosition.x += 20.0 - } - backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + controlsBottomInset + }, + tag: cancelButtonTag + ), + availableSize: CGSize(width: 44.0, height: 44.0), + transition: .immediate + ) + var backButtonPosition = CGPoint(x: environment.safeInsets.left + backButton.size.width / 2.0 + 14.0, y: context.availableSize.height - environment.safeInsets.bottom - backButton.size.height / 2.0 - 2.0 - UIScreenPixel) + if component.sourceHint == .storyEditor { + backButtonPosition.x = backButtonPosition.x + 2.0 + if case .regular = environment.metrics.widthClass { + backButtonPosition.x += 20.0 } - context.add(backButton - .position(backButtonPosition) - .opacity(controlsAreVisible ? 1.0 : 0.0) - ) + backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + controlsBottomInset + 5.0 + } else { + backButtonPosition.x = backButtonPosition.x + 12.0 + backButtonPosition.y -= 3.0 - UIScreenPixel } + context.add(backButton + .position(backButtonPosition) + .opacity(controlsAreVisible ? 1.0 : 0.0) + ) return context.availableSize } @@ -2225,22 +2239,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U self.performAction.connect { [weak self] action in if let self { if case .clear = action { - let actionSheet = ActionSheetController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Paint_ClearConfirm, color: .destructive, action: { [weak actionSheet, weak self] in - actionSheet?.dismissAnimated() - - self?._drawingView?.performAction(action) - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - self.controller?.present(actionSheet, in: .window(.root)) + let sourceView: UIView + if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View, let rightItemView = topButtonsView.rightItemView, let clearAllView = rightItemView.itemView(id: AnyHashable("clearAll")) { + sourceView = clearAllView + } else { + sourceView = self.view + } + + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: self.presentationData.strings.Paint_ClearConfirm, textColor: .destructive, icon: { _ in + return nil + }, action: { [weak self] f in + f.dismissWithResult(.default) + self?._drawingView?.performAction(.clear) + })) + ] + let presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme) + let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(), actionsPosition: .bottom)), items: .single(ContextController.Items(content: .list(items)))) + self.controller?.present(contextController, in: .window(.root)) } else { self._drawingView?.performAction(action) } @@ -2404,22 +2420,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U self.dismiss.connect { [weak self] _ in if let strongSelf = self { if strongSelf.drawingView.canUndo || strongSelf.entitiesView.hasChanges { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.PhotoEditor_DiscardChanges, color: .accent, action: { [weak actionSheet, weak self] in - actionSheet?.dismissAnimated() - - self?.controller?.requestDismiss() - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - strongSelf.controller?.present(actionSheet, in: .window(.root)) + let sourceView = strongSelf.componentHost.findTaggedView(tag: cancelButtonTag) ?? strongSelf.view + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PhotoEditor_DiscardChanges, textColor: .destructive, icon: { _ in + return nil + }, action: { [weak self] f in + f.dismissWithResult(.default) + self?.controller?.requestDismiss() + })) + ] + let presentationData = strongSelf.presentationData.withUpdated(theme: defaultDarkPresentationTheme) + let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint())), items: .single(ContextController.Items(content: .list(items)))) + strongSelf.controller?.present(contextController, in: .window(.root)) } else { strongSelf.controller?.requestDismiss() } @@ -2498,17 +2510,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U if let view = self.componentHost.findTaggedView(tag: bottomGradientTag) { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } - if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) { - buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View { + topButtonsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + + if let leftItemView = topButtonsView.leftItemView { + leftItemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + leftItemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + } + if let rightItemView = topButtonsView.rightItemView { + rightItemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + rightItemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + } } - if let buttonView = self.componentHost.findTaggedView(tag: clearAllButtonTag) { - buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + if let view = self.componentHost.findTaggedView(tag: modeTag) { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } - if let buttonView = self.componentHost.findTaggedView(tag: addButtonTag) { - buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + if let view = self.componentHost.findTaggedView(tag: addButtonTag) { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) } var delay: Double = 0.0 for tag in colorTags { @@ -2521,6 +2540,15 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) { view.layer.animatePosition(from: CGPoint(x: -33.0, y: 0.0), to: CGPoint(), duration: 0.3, additive: true) } + + if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + } + if let view = self.componentHost.findTaggedView(tag: doneButtonTag) { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3) + } } func animateOut(completion: @escaping () -> Void) { @@ -2537,43 +2565,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U } view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) } - if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) - } - if let buttonView = self.componentHost.findTaggedView(tag: redoButtonTag), buttonView.alpha > 0.0 { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) - } - if let buttonView = self.componentHost.findTaggedView(tag: clearAllButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) + if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View { + topButtonsView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + + if let view = topButtonsView.leftItemView { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false) + } + if let view = topButtonsView.rightItemView { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false) + } } if let view = self.componentHost.findTaggedView(tag: colorButtonTag) as? ColorSwatchComponent.View { view.animateOut() } - if let buttonView = self.componentHost.findTaggedView(tag: addButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) - } - if let buttonView = self.componentHost.findTaggedView(tag: flipButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) - } - if let buttonView = self.componentHost.findTaggedView(tag: fillButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) - } - if let buttonView = self.componentHost.findTaggedView(tag: zoomOutButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) + if let view = self.componentHost.findTaggedView(tag: addButtonTag) { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false) } if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) { view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -33.0, y: 0.0), duration: 0.3, removeOnCompletion: false, additive: true) @@ -2597,12 +2606,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U }) } - if let view = self.componentHost.findTaggedView(tag: modeTag) as? ModeAndSizeComponent.View { - view.animateOut() + if let view = self.componentHost.findTaggedView(tag: modeTag) { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) } - if let buttonView = self.componentHost.findTaggedView(tag: doneButtonTag) { - buttonView.alpha = 0.0 - buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + + if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false) + } + + if let view = self.componentHost.findTaggedView(tag: doneButtonTag) { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3) } } @@ -2893,13 +2908,13 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U var stickers: [Any] = [] for entity in self.entitiesView.entities { if let sticker = entity as? DrawingStickerEntity, case let .file(file, _) = sticker.content { - let coder = PostboxEncoder() + let coder = EnginePostboxEncoder() coder.encodeRootObject(file.media) stickers.append(coder.makeData()) } else if let text = entity as? DrawingTextEntity, let subEntities = text.renderSubEntities { for sticker in subEntities { if let sticker = sticker as? DrawingStickerEntity, case let .file(file, _) = sticker.content { - let coder = PostboxEncoder() + let coder = EnginePostboxEncoder() coder.encodeRootObject(file.media) stickers.append(coder.makeData()) } diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index 9b6e92d61f..9ad46ba877 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -478,7 +478,7 @@ public class DrawingStickerEntityView: DrawingEntityView { let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm") - let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) let playbackMode: AnimatedStickerPlaybackMode = .loop self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: playbackMode, mode: .direct(cachePathPrefix: pathPrefix)) diff --git a/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift b/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift index 5bd68600bd..6cd3b91c06 100644 --- a/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift +++ b/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift @@ -8,7 +8,7 @@ import AVFoundation public final class EntityVideoRecorder { private weak var mediaEditor: MediaEditor? private weak var entitiesView: DrawingEntitiesView? - + private let maxDuration: Double private let camera: Camera @@ -21,6 +21,9 @@ public final class EntityVideoRecorder { private let micLevelPromise = Promise() private var changingPositionDisposable: Disposable? + private var positionDisposable: Disposable? + private var currentCameraPosition: Camera.Position = .front + private var recordingInitialPosition: Camera.Position = .front public var duration: Signal { return self.durationPromise.get() @@ -35,7 +38,7 @@ public final class EntityVideoRecorder { public init(mediaEditor: MediaEditor, entitiesView: DrawingEntitiesView) { self.mediaEditor = mediaEditor self.entitiesView = entitiesView - + self.maxDuration = min(60.0, mediaEditor.duration ?? 60.0) self.previewView = CameraSimplePreviewView(frame: .zero, main: true) @@ -86,6 +89,10 @@ public final class EntityVideoRecorder { } self.micLevelPromise.set(camera.audioLevel) + self.positionDisposable = (camera.position + |> deliverOnMainQueue).start(next: { [weak self] position in + self?.currentCameraPosition = position + }) let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0 mediaEditor.stop() @@ -107,6 +114,7 @@ public final class EntityVideoRecorder { deinit { self.recordingDisposable.dispose() self.changingPositionDisposable?.dispose() + self.positionDisposable?.dispose() } public func setup( @@ -145,6 +153,7 @@ public final class EntityVideoRecorder { mediaEditor.maybeMuteVideo() mediaEditor.play() + self.recordingInitialPosition = self.currentCameraPosition self.start = CACurrentMediaTime() self.recordingDisposable.set((self.camera.startRecording() |> deliverOnMainQueue).startStrict(next: { [weak self] recordingData in @@ -169,16 +178,20 @@ public final class EntityVideoRecorder { } self.recordingDisposable.set((self.camera.stopRecording() |> deliverOnMainQueue).startStrict(next: { [weak self] result in - guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, _, _) = result else { + guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, positionChangeTimestamps, _) = result else { return } if save { let duration = AVURLAsset(url: URL(fileURLWithPath: mainResult.path)).duration + let mirroringChanges = self.additionalVideoMirroringChanges( + initialPosition: self.recordingInitialPosition, + positionChangeTimestamps: positionChangeTimestamps + ) let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0 mediaEditor.setAdditionalVideoOffset(-start, apply: false) mediaEditor.setAdditionalVideoTrimRange(0 ..< duration.seconds, apply: true) - mediaEditor.setAdditionalVideo(mainResult.path, positionChanges: []) + mediaEditor.setAdditionalVideo(mainResult.path, mirroringChanges: mirroringChanges, positionChanges: []) mediaEditor.stop() Queue.mainQueue().justDispatch { @@ -217,7 +230,7 @@ public final class EntityVideoRecorder { self.mediaEditor?.maybeUnmuteVideo() self.entitiesView?.remove(uuid: self.entity.uuid, animated: true) - self.mediaEditor?.setAdditionalVideo(nil, positionChanges: []) + self.mediaEditor?.setAdditionalVideo(nil, mirroringChanges: [], positionChanges: []) completion() } @@ -226,4 +239,22 @@ public final class EntityVideoRecorder { public func togglePosition() { self.camera.togglePosition() } + + private func additionalVideoMirroringChanges(initialPosition: Camera.Position, positionChangeTimestamps: [(Bool, Double)]) -> [VideoMirroringChange] { + var result: [VideoMirroringChange] = [ + VideoMirroringChange(isMirrored: initialPosition == .front, timestamp: 0.0) + ] + for (isMirrored, timestamp) in positionChangeTimestamps.sorted(by: { $0.1 < $1.1 }) { + result.append(VideoMirroringChange(isMirrored: isMirrored, timestamp: timestamp)) + } + + var deduplicated: [VideoMirroringChange] = [] + for change in result { + if deduplicated.last?.isMirrored == change.isMirrored { + continue + } + deduplicated.append(change) + } + return deduplicated + } } diff --git a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift index a8822e5230..eabf7ba305 100644 --- a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift +++ b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift @@ -2,95 +2,123 @@ import Foundation import UIKit import Display import ComponentFlow -import LegacyComponents -import TelegramCore -import SegmentedControlNode +import MultilineTextComponent +import TelegramPresentationData +import GlassBackgroundComponent +import LiquidLens +import TabSelectionRecognizer -private func generateMaskPath(size: CGSize, leftRadius: CGFloat, rightRadius: CGFloat) -> UIBezierPath { - let path = UIBezierPath() - path.addArc(withCenter: CGPoint(x: leftRadius, y: size.height / 2.0), radius: leftRadius, startAngle: .pi * 0.5, endAngle: -.pi * 0.5, clockwise: true) - path.addArc(withCenter: CGPoint(x: size.width - rightRadius, y: size.height / 2.0), radius: rightRadius, startAngle: -.pi * 0.5, endAngle: .pi * 0.5, clockwise: true) - path.close() - return path +private let buttonSize = CGSize(width: 55.0, height: 44.0) +private let tabletButtonSize = CGSize(width: 55.0, height: 44.0) + +extension DrawingMode { + func title(strings: PresentationStrings) -> String { + switch self { + case .drawing: + return strings.Paint_Draw + case .sticker: + return strings.Paint_Sticker + case .text: + return strings.Paint_Text + } + } } -private func generateKnobImage() -> UIImage? { - let side: CGFloat = 28.0 - let margin: CGFloat = 10.0 - - let image = generateImage(CGSize(width: side + margin * 2.0, height: side + margin * 2.0), opaque: false, rotatedContext: { size, context in - context.clear(CGRect(origin: .zero, size: size)) - - context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 9.0, color: UIColor(rgb: 0x000000, alpha: 0.3).cgColor) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: margin, y: margin), size: CGSize(width: side, height: side))) - }) - return image?.stretchableImage(withLeftCapWidth: Int(margin + side * 0.5), topCapHeight: Int(margin + side * 0.5)) -} - -final class ModeAndSizeComponent: Component { - let values: [String] - let sizeValue: CGFloat - let isEditing: Bool - let isEnabled: Bool - let rightInset: CGFloat +final class ModeComponent: Component { + let isTablet: Bool + let strings: PresentationStrings + let tintColor: UIColor + let availableModes: [DrawingMode] + let currentMode: DrawingMode + let updatedMode: (DrawingMode) -> Void let tag: AnyObject? - let selectedIndex: Int - let selectionChanged: (Int) -> Void - let sizeUpdated: (CGFloat) -> Void - let sizeReleased: () -> Void - init(values: [String], sizeValue: CGFloat, isEditing: Bool, isEnabled: Bool, rightInset: CGFloat, tag: AnyObject?, selectedIndex: Int, selectionChanged: @escaping (Int) -> Void, sizeUpdated: @escaping (CGFloat) -> Void, sizeReleased: @escaping () -> Void) { - self.values = values - self.sizeValue = sizeValue - self.isEditing = isEditing - self.isEnabled = isEnabled - self.rightInset = rightInset + init( + isTablet: Bool, + strings: PresentationStrings, + tintColor: UIColor, + availableModes: [DrawingMode], + currentMode: DrawingMode, + updatedMode: @escaping (DrawingMode) -> Void, + tag: AnyObject? + ) { + self.isTablet = isTablet + self.strings = strings + self.tintColor = tintColor + self.availableModes = availableModes + self.currentMode = currentMode + self.updatedMode = updatedMode self.tag = tag - self.selectedIndex = selectedIndex - self.selectionChanged = selectionChanged - self.sizeUpdated = sizeUpdated - self.sizeReleased = sizeReleased } - static func ==(lhs: ModeAndSizeComponent, rhs: ModeAndSizeComponent) -> Bool { - if lhs.values != rhs.values { + static func ==(lhs: ModeComponent, rhs: ModeComponent) -> Bool { + if lhs.isTablet != rhs.isTablet { return false } - if lhs.sizeValue != rhs.sizeValue { + if lhs.strings !== rhs.strings { return false } - if lhs.isEditing != rhs.isEditing { + if lhs.tintColor != rhs.tintColor { return false } - if lhs.isEnabled != rhs.isEnabled { + if lhs.availableModes != rhs.availableModes { return false } - if lhs.rightInset != rhs.rightInset { - return false - } - if lhs.selectedIndex != rhs.selectedIndex { + if lhs.currentMode != rhs.currentMode { return false } return true } - - final class View: UIView, UIGestureRecognizerDelegate, ComponentTaggedView { - private let backgroundNode: NavigationBackgroundNode - private let node: SegmentedControlNode + + final class View: UIView, ComponentTaggedView, UIScrollViewDelegate, UIGestureRecognizerDelegate { + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } - private var knob: UIImageView + private struct LayoutData { + var containerSize: CGSize + var selectedFrame: CGRect + var cornerRadius: CGFloat? + var isTablet: Bool + } - private let maskLayer = SimpleShapeLayer() + private var component: ModeComponent? + private var state: EmptyComponentState? - private var isEditing: Bool? - private var isControlEnabled: Bool? - private var sliderWidth: CGFloat = 0.0 + final class ItemView: HighlightTrackingButton { + init() { + super.init(frame: .zero) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + func update(isTablet: Bool, value: String, selected: Bool, tintColor: UIColor) -> CGSize { + let title = NSMutableAttributedString(string: value, font: Font.with(size: 15.0, design: .regular, weight: .medium), textColor: UIColor(rgb: 0xffffff), paragraphAlignment: .center) + self.setAttributedTitle(title, for: .normal) + self.sizeToFit() + return CGSize(width: self.titleLabel?.bounds.size.width ?? 0.0, height: buttonSize.height) + } + } - fileprivate var updated: (CGFloat) -> Void = { _ in } - fileprivate var released: () -> Void = { } + private var backgroundView = UIView() + private var backgroundContainer = GlassBackgroundContainerView() + + private var liquidLensView: LiquidLensView? + private let scrollView = ScrollView() + private let selectedScrollView = UIView() + private var ignoreScrolling = false + private var layoutData: LayoutData? + + private var itemViews: [AnyHashable: ItemView] = [:] + private var selectedItemViews: [AnyHashable: ItemView] = [:] + + private var tabSelectionRecognizer: TabSelectionRecognizer? + private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)? - private var component: ModeAndSizeComponent? public func matches(tag: Any) -> Bool { if let component = self.component, let componentTag = component.tag { let tag = tag as AnyObject @@ -102,164 +130,350 @@ final class ModeAndSizeComponent: Component { } init() { - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x888888, alpha: 0.3)) - self.node = SegmentedControlNode(theme: SegmentedControlTheme(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), items: [], selectedIndex: 0, cornerRadius: 16.0) - - self.knob = UIImageView(image: generateKnobImage()) - super.init(frame: CGRect()) + self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.09) + self.backgroundView.layer.cornerRadius = 22.0 + self.layer.allowsGroupOpacity = true - - self.addSubview(self.backgroundNode.view) - self.addSubview(self.node.view) - self.addSubview(self.knob) - self.backgroundNode.layer.mask = self.maskLayer + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = false + self.scrollView.scrollsToTop = false + self.scrollView.clipsToBounds = true + self.scrollView.delegate = self + self.scrollView.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in + guard let self else { + return false + } + return self.scrollView.contentOffset.x > .ulpOfOne + } - let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePress(_:))) - pressGestureRecognizer.minimumPressDuration = 0.01 - pressGestureRecognizer.delegate = self - self.addGestureRecognizer(pressGestureRecognizer) + self.selectedScrollView.clipsToBounds = true + self.selectedScrollView.isUserInteractionEnabled = false - let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:))) - panGestureRecognizer.delegate = self - self.addGestureRecognizer(panGestureRecognizer) + self.addSubview(self.backgroundView) + self.backgroundView.addSubview(self.backgroundContainer) } required init?(coder aDecoder: NSCoder) { preconditionFailure() } - @objc func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) { - let location = gestureRecognizer.location(in: self).offsetBy(dx: -12.0, dy: 0.0) - guard self.frame.width > 0.0, case .began = gestureRecognizer.state else { - return - } - let value = max(0.0, min(1.0, location.x / (self.frame.width - 24.0))) - self.updated(value) + private var animatedOut = false + func animateOutToEditor(transition: ComponentTransition) { + self.animatedOut = true + + transition.setAlpha(view: self.backgroundView, alpha: 0.0) + transition.setSublayerTransform(view: self, transform: CATransform3DMakeTranslation(0.0, -buttonSize.height, 0.0)) } - @objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { - switch gestureRecognizer.state { - case .changed: - let location = gestureRecognizer.location(in: self).offsetBy(dx: -12.0, dy: 0.0) - guard self.frame.width > 0.0 else { - return + func animateInFromEditor(transition: ComponentTransition) { + self.animatedOut = false + + transition.setAlpha(view: self.backgroundView, alpha: 1.0) + transition.setSublayerTransform(view: self, transform: CATransform3DIdentity) + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.backgroundView.frame.contains(point) + } + + private func item(at point: CGPoint, in view: UIView) -> AnyHashable? { + var closestItem: (AnyHashable, CGFloat)? + for (id, itemView) in self.itemViews { + let itemFrame = itemView.convert(itemView.bounds, to: view) + if itemFrame.contains(point) { + return id + } else { + let distance = abs(point.x - itemFrame.midX) + if let closestItemValue = closestItem { + if closestItemValue.1 > distance { + closestItem = (id, distance) + } + } else { + closestItem = (id, distance) + } + } + } + return closestItem?.0 + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { + return + } + self.updateScrolling(transition: .immediate) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer === self.tabSelectionRecognizer && otherGestureRecognizer === self.scrollView.panGestureRecognizer { + return true + } + if otherGestureRecognizer === self.tabSelectionRecognizer && gestureRecognizer === self.scrollView.panGestureRecognizer { + return true + } + return false + } + + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let component = self.component else { + return + } + let location = recognizer.location(in: self) + switch recognizer.state { + case .began: + if let itemId = self.item(at: location, in: self), let itemView = self.itemViews[itemId] { + let startX = itemView.frame.minX - 4.0 + self.selectionGestureState = (startX, startX, itemId) + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } + case .changed: + if var selectionGestureState = self.selectionGestureState { + let translation = recognizer.translation(in: self) + if !component.isTablet && self.scrollView.isScrollEnabled && abs(translation.x) > 6.0 && abs(translation.x) > abs(translation.y) { + self.selectionGestureState = nil + recognizer.state = .cancelled + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + return + } + selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x + if let itemId = self.item(at: location, in: self) { + selectionGestureState.itemId = itemId + } + self.selectionGestureState = selectionGestureState + self.state?.updated(transition: .immediate, isLocal: true) } - let value = max(0.0, min(1.0, location.x / (self.frame.width - 24.0))) - self.updated(value) case .ended, .cancelled: - self.released() + if let selectionGestureState = self.selectionGestureState { + self.selectionGestureState = nil + if case .ended = recognizer.state { + guard let item = component.availableModes.first(where: { AnyHashable($0.rawValue) == selectionGestureState.itemId }) else { + return + } + component.updatedMode(item) + } + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + } default: break } } - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if let isEditing = self.isEditing, let isControlEnabled = self.isControlEnabled { - return isEditing && isControlEnabled + private func updateScrolling(transition: ComponentTransition) { + guard let component = self.component, let liquidLensView = self.liquidLensView, let layoutData = self.layoutData else { + return + } + + let contentOffsetX = layoutData.isTablet ? 0.0 : self.scrollView.bounds.minX + var lensSelection = (origin: layoutData.selectedFrame.origin, size: layoutData.selectedFrame.size) + if let selectionGestureState = self.selectionGestureState, !layoutData.isTablet { + lensSelection.origin = CGPoint(x: selectionGestureState.currentX, y: 0.0) + } + + if layoutData.isTablet { + lensSelection.size.width = layoutData.containerSize.width } else { - return false + lensSelection.origin.x -= contentOffsetX + lensSelection.origin.y = 0.0 + lensSelection.size.height = layoutData.containerSize.height } + + let maxSelectionOriginX = max(0.0, layoutData.containerSize.width - lensSelection.size.width) + transition.setFrame(view: self.selectedScrollView, frame: CGRect(origin: .zero, size: layoutData.containerSize)) + transition.setBounds(view: self.selectedScrollView, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: layoutData.containerSize)) + + liquidLensView.update(size: layoutData.containerSize, cornerRadius: layoutData.cornerRadius, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.origin.x, maxSelectionOriginX)), y: lensSelection.origin.y), selectionSize: lensSelection.size, inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil && !layoutData.isTablet, isCollapsed: false, transition: transition) + self.backgroundContainer.update(size: layoutData.containerSize, isDark: true, transition: .immediate) + + self.scrollView.isScrollEnabled = !component.isTablet && self.scrollView.contentSize.width > self.scrollView.bounds.width + .ulpOfOne } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } - - func animateIn() { - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - - func animateOut() { - self.node.alpha = 0.0 - self.node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - - self.backgroundNode.alpha = 0.0 - self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - - func update(component: ModeAndSizeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.component = component - - self.updated = component.sizeUpdated - self.released = component.sizeReleased - - let previousIsEditing = self.isEditing - self.isEditing = component.isEditing - self.isControlEnabled = component.isEnabled - - if component.isEditing { - self.sliderWidth = availableSize.width - } - - self.node.items = component.values.map { SegmentedControlItem(title: $0) } - self.node.setSelectedIndex(component.selectedIndex, animated: !transition.animation.isImmediate) - let selectionChanged = component.selectionChanged - self.node.selectedIndexChanged = { [weak self] index in - self?.window?.endEditing(true) - selectionChanged(index) - } - - let nodeSize = self.node.updateLayout(.stretchToFill(width: availableSize.width + component.rightInset), transition: transition.containedViewLayoutTransition) - let size = CGSize(width: availableSize.width, height: nodeSize.height) - transition.setFrame(view: self.node.view, frame: CGRect(origin: CGPoint(), size: nodeSize)) - - var isDismissingEditing = false - if component.isEditing != previousIsEditing && !component.isEditing { - isDismissingEditing = true - } - - self.knob.alpha = component.isEditing ? 1.0 : 0.0 - if !isDismissingEditing { - self.knob.frame = CGRect(origin: CGPoint(x: -12.0 + floorToScreenPixels((self.sliderWidth + 24.0 - self.knob.frame.size.width) * component.sizeValue), y: floorToScreenPixels((size.height - self.knob.frame.size.height) / 2.0)), size: self.knob.frame.size) - } - if component.isEditing != previousIsEditing { - let containedTransition = transition.containedViewLayoutTransition - let maskPath: UIBezierPath - if component.isEditing { - maskPath = generateMaskPath(size: size, leftRadius: 2.0, rightRadius: 11.5) - let selectionFrame = self.node.animateSelection(to: self.knob.center, transition: containedTransition) - containedTransition.animateFrame(layer: self.knob.layer, from: selectionFrame.insetBy(dx: -9.0, dy: -9.0)) - - self.knob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + self.state = state + + let isTablet = component.isTablet + + let liquidLensView: LiquidLensView + if let current = self.liquidLensView { + liquidLensView = current + } else { + liquidLensView = LiquidLensView(kind: isTablet ? .noContainer : .externalContainer) + self.liquidLensView = liquidLensView + self.backgroundContainer.contentView.addSubview(liquidLensView) + liquidLensView.contentView.addSubview(self.scrollView) + liquidLensView.selectedContentView.addSubview(self.selectedScrollView) + + let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) + tabSelectionRecognizer.delegate = self + tabSelectionRecognizer.cancelsTouchesInView = false + self.tabSelectionRecognizer = tabSelectionRecognizer + liquidLensView.addGestureRecognizer(tabSelectionRecognizer) + } + if self.scrollView.superview == nil { + liquidLensView.contentView.addSubview(self.scrollView) + } + if self.selectedScrollView.superview == nil { + liquidLensView.selectedContentView.addSubview(self.selectedScrollView) + } + + self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11) + + var inset: CGFloat = 23.0 + let spacing: CGFloat + if isTablet { + spacing = 9.0 + } else { + if availableSize.width < 270.0 { + inset = 16.0 + spacing = 20.0 } else { - maskPath = generateMaskPath(size: size, leftRadius: 16.0, rightRadius: 16.0) - if previousIsEditing != nil { - let selectionFrame = self.node.animateSelection(from: self.knob.center, transition: containedTransition) - containedTransition.animateFrame(layer: self.knob.layer, from: self.knob.frame, to: selectionFrame.insetBy(dx: -9.0, dy: -9.0)) - self.knob.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + spacing = 30.0 + } + } + + var i = 0 + var itemFrame = CGRect(origin: isTablet ? .zero : CGPoint(x: inset, y: 0.0), size: buttonSize) + var selectedFrame = itemFrame + + var validKeys: Set = Set() + for mode in component.availableModes { + let id = mode.rawValue + validKeys.insert(id) + + let itemView: ItemView + let selectedItemView: ItemView + if let current = self.itemViews[id], let currentSelected = self.selectedItemViews[id] { + itemView = current + selectedItemView = currentSelected + } else { + itemView = ItemView() + itemView.isUserInteractionEnabled = false + self.itemViews[id] = itemView + + selectedItemView = ItemView() + selectedItemView.isUserInteractionEnabled = false + self.selectedItemViews[id] = selectedItemView + } + if itemView.superview !== self.scrollView { + self.scrollView.addSubview(itemView) + } + if selectedItemView.superview !== self.selectedScrollView { + self.selectedScrollView.addSubview(selectedItemView) + } + + let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor) + itemView.bounds = CGRect(origin: .zero, size: itemSize) + + let _ = selectedItemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: true, tintColor: component.tintColor) + selectedItemView.bounds = CGRect(origin: .zero, size: itemSize) + + itemFrame = CGRect(origin: itemFrame.origin, size: itemSize) + + if mode == component.currentMode { + selectedFrame = itemFrame + } + + if isTablet { + itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY) + selectedItemView.center = itemView.center + itemFrame = itemFrame.offsetBy(dx: 0.0, dy: tabletButtonSize.height + spacing) + } else { + itemView.center = CGPoint(x: itemFrame.midX, y: itemFrame.midY) + selectedItemView.center = itemView.center + itemFrame = itemFrame.offsetBy(dx: itemFrame.width + spacing, dy: 0.0) + } + i += 1 + } + + var removeKeys: [AnyHashable] = [] + for (id, itemView) in self.itemViews { + if !validKeys.contains(id) { + removeKeys.append(id) + + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + + if let selectedItemView = self.selectedItemViews[id] { + transition.setAlpha(view: selectedItemView, alpha: 0.0, completion: { _ in + selectedItemView.removeFromSuperview() + }) } } - transition.setShapeLayerPath(layer: self.maskLayer, path: maskPath.cgPath) } - - transition.setFrame(layer: self.maskLayer, frame: CGRect(origin: .zero, size: nodeSize)) + for id in removeKeys { + self.itemViews.removeValue(forKey: id) + self.selectedItemViews.removeValue(forKey: id) + } - transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) - self.backgroundNode.update(size: size, transition: transition.containedViewLayoutTransition) + let totalSize: CGSize + let size: CGSize + let contentSize: CGSize + var cornerRadius: CGFloat? + if isTablet { + totalSize = CGSize(width: availableSize.width, height: tabletButtonSize.height * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1)) + size = CGSize(width: availableSize.width, height: availableSize.height) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: totalSize)) + contentSize = totalSize + cornerRadius = 20.0 + } else { + size = CGSize(width: availableSize.width, height: buttonSize.height) + totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height) + let visibleSize = CGSize(width: min(availableSize.width, totalSize.width), height: totalSize.height) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - visibleSize.width) / 2.0), y: 0.0), size: visibleSize)) + contentSize = totalSize + } - if let screenTransition = transition.userData(DrawingScreenTransition.self) { - switch screenTransition { - case .animateIn: - self.animateIn() - case .animateOut: - self.animateOut() + let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size) + transition.setFrame(view: self.backgroundContainer, frame: containerFrame) + transition.setFrame(view: liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size)) + + let scrollViewFrame = CGRect(origin: .zero, size: containerFrame.size) + transition.setFrame(view: self.scrollView, frame: scrollViewFrame) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + self.scrollView.isScrollEnabled = !isTablet && contentSize.width > scrollViewFrame.width + .ulpOfOne + + self.layoutData = LayoutData(containerSize: containerFrame.size, selectedFrame: selectedFrame.insetBy(dx: -inset, dy: 3.0), cornerRadius: cornerRadius, isTablet: isTablet) + + self.ignoreScrolling = true + var scrollViewBounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollViewFrame.size) + let maxContentOffsetX = max(0.0, contentSize.width - scrollViewFrame.width) + let shouldFocusOnSelectedItem = previousComponent?.currentMode != component.currentMode || previousComponent?.availableModes != component.availableModes || self.scrollView.bounds.size != scrollViewFrame.size + if self.scrollView.isScrollEnabled && shouldFocusOnSelectedItem { + let scrollLookahead = min(60.0, scrollViewBounds.width * 0.25) + if scrollViewBounds.minX + scrollViewBounds.width - scrollLookahead < selectedFrame.maxX { + scrollViewBounds.origin.x = selectedFrame.maxX - scrollViewBounds.width + scrollLookahead + } + if scrollViewBounds.minX > selectedFrame.minX - scrollLookahead { + scrollViewBounds.origin.x = selectedFrame.minX - scrollLookahead } } + scrollViewBounds.origin.x = max(0.0, min(scrollViewBounds.origin.x, maxContentOffsetX)) + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) return size } } - + func makeView() -> View { return View() } func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) + return view.update(component: self, availableSize: availableSize, state: state, transition: transition) } } diff --git a/submodules/Emoji/Sources/EmojiUtils.swift b/submodules/Emoji/Sources/EmojiUtils.swift index 4b5033e7b8..b31549fcd6 100644 --- a/submodules/Emoji/Sources/EmojiUtils.swift +++ b/submodules/Emoji/Sources/EmojiUtils.swift @@ -57,6 +57,18 @@ public extension UnicodeScalar { private final class FrameworkClass: NSObject { } +private let allowedEmojiLikeSymbols: Set = [ + "\u{2640}", + "\u{2640}\u{FE0E}", + "\u{2640}\u{FE0F}", + "\u{2642}", + "\u{2642}\u{FE0E}", + "\u{2642}\u{FE0F}", + "\u{26A7}", + "\u{26A7}\u{FE0E}", + "\u{26A7}\u{FE0F}" +] + public extension String { func trimmingTrailingSpaces() -> String { var t = self @@ -74,6 +86,20 @@ public extension String { return self.contains { $0.isEmoji } } + var containsGraphicEmoji: Bool { + var containsEmoji = false + self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, options: .byComposedCharacterSequences) { substring, _, _, stop in + guard let substring else { + return + } + if substring.containsEmoji && !allowedEmojiLikeSymbols.contains(substring) { + containsEmoji = true + stop = true + } + } + return containsEmoji + } + var containsOnlyEmoji: Bool { return !self.isEmpty && !self.contains { !$0.isEmoji } } diff --git a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h index f1749fabbb..29d8632f65 100644 --- a/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h +++ b/submodules/FFMpegBinding/Public/FFMpegBinding/FFMpegAVFrame.h @@ -9,7 +9,8 @@ typedef NS_ENUM(NSUInteger, FFMpegAVFrameColorRange) { typedef NS_ENUM(NSUInteger, FFMpegAVFramePixelFormat) { FFMpegAVFramePixelFormatYUV, - FFMpegAVFramePixelFormatYUVA + FFMpegAVFramePixelFormatYUVA, + FFMpegAVFramePixelFormatUnsupported }; typedef NS_ENUM(NSUInteger, FFMpegAVFrameNativePixelFormat) { @@ -29,7 +30,7 @@ typedef NS_ENUM(NSUInteger, FFMpegAVFrameNativePixelFormat) { @property (nonatomic, readonly) FFMpegAVFramePixelFormat pixelFormat; - (instancetype)init; -- (instancetype)initWithPixelFormat:(FFMpegAVFramePixelFormat)pixelFormat width:(int32_t)width height:(int32_t)height; +- (instancetype _Nullable)initWithPixelFormat:(FFMpegAVFramePixelFormat)pixelFormat width:(int32_t)width height:(int32_t)height; - (void *)impl; - (FFMpegAVFrameNativePixelFormat)nativePixelFormat; diff --git a/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m index fda8224dc7..d5bf139206 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m +++ b/submodules/FFMpegBinding/Sources/FFMpegAVFrame.m @@ -29,6 +29,8 @@ case FFMpegAVFramePixelFormatYUVA: _impl->format = AV_PIX_FMT_YUVA420P; break; + case FFMpegAVFramePixelFormatUnsupported: + return nil; } _impl->width = width; _impl->height = height; @@ -101,8 +103,11 @@ switch (_impl->format) { case AV_PIX_FMT_YUVA420P: return FFMpegAVFramePixelFormatYUVA; - default: + case AV_PIX_FMT_YUV420P: + case AV_PIX_FMT_YUVJ420P: return FFMpegAVFramePixelFormatYUV; + default: + return FFMpegAVFramePixelFormatUnsupported; } } diff --git a/submodules/FFMpegBinding/Sources/FFMpegSWResample.m b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m index c6771b6db3..a2391be99a 100644 --- a/submodules/FFMpegBinding/Sources/FFMpegSWResample.m +++ b/submodules/FFMpegBinding/Sources/FFMpegSWResample.m @@ -111,22 +111,37 @@ return nil; } + int64_t outSamples = (int64_t)frameImpl->nb_samples * (int64_t)_ratio; + // Cap at 8M samples per frame: ~10× larger than any legitimate codec + // frame × resample ratio, bounds the per-frame buffer to ~32 MB (s16 stereo), + // and rejects pathological inputs (e.g. 1 Hz source with a 65 KB FLAC block). + if (outSamples <= 0 || outSamples > 8 * 1024 * 1024) { + return nil; + } + int bufSize = av_samples_get_buffer_size(NULL, (int)_destinationChannelCount, - frameImpl->nb_samples * (int)_ratio, + (int)outSamples, (enum AVSampleFormat)_destinationSampleFormat, 1); - - if (!_buffer || _bufferSize < bufSize) { - _bufferSize = bufSize; - _buffer = realloc(_buffer, _bufferSize); + if (bufSize <= 0) { + return nil; } - + + if (!_buffer || _bufferSize < bufSize) { + void *newBuffer = realloc(_buffer, bufSize); + if (!newBuffer) { + return nil; + } + _buffer = newBuffer; + _bufferSize = bufSize; + } + Byte *outbuf[2] = { _buffer, 0 }; - + int numFrames = swr_convert(_context, outbuf, - frameImpl->nb_samples * (int)_ratio, + (int)outSamples, (const uint8_t **)frameImpl->data, frameImpl->nb_samples); if (numFrames <= 0) { diff --git a/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift index 44beeb380e..e9d3014d93 100644 --- a/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift +++ b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -14,13 +13,13 @@ import UndoUI import StickerResources public final class TrendingPaneInteraction { - public let installPack: (ItemCollectionInfo) -> Void - public let openPack: (ItemCollectionInfo) -> Void + public let installPack: (EngineRawItemCollectionInfo) -> Void + public let openPack: (EngineRawItemCollectionInfo) -> Void public let getItemIsPreviewed: (StickerPackItem) -> Bool public let openSearch: () -> Void public let itemContext = StickerPaneSearchGlobalItemContext() - - public init(installPack: @escaping (ItemCollectionInfo) -> Void, openPack: @escaping (ItemCollectionInfo) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, openSearch: @escaping () -> Void) { + + public init(installPack: @escaping (EngineRawItemCollectionInfo) -> Void, openPack: @escaping (EngineRawItemCollectionInfo) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, openSearch: @escaping () -> Void) { self.installPack = installPack self.openPack = openPack self.getItemIsPreviewed = getItemIsPreviewed @@ -49,7 +48,7 @@ public final class TrendingPanePackEntry: Identifiable, Comparable { self.topSeparator = topSeparator } - public var stableId: ItemCollectionId { + public var stableId: EngineItemCollectionId { return self.info.id } @@ -99,7 +98,7 @@ public final class TrendingPanePackEntry: Identifiable, Comparable { private enum TrendingPaneEntryId: Hashable { case search - case pack(ItemCollectionId) + case pack(EngineItemCollectionId) } private enum TrendingPaneEntry: Identifiable, Comparable { @@ -175,7 +174,7 @@ private func preparedTransition(from fromEntries: [TrendingPaneEntry], to toEntr return TrendingPaneTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial) } -private func trendingPaneEntries(trendingEntries: [FeaturedStickerPackItem], installedPacks: Set, theme: PresentationTheme, strings: PresentationStrings, isPane: Bool) -> [TrendingPaneEntry] { +private func trendingPaneEntries(trendingEntries: [FeaturedStickerPackItem], installedPacks: Set, theme: PresentationTheme, strings: PresentationStrings, isPane: Bool) -> [TrendingPaneEntry] { var result: [TrendingPaneEntry] = [] var index = 0 if isPane { @@ -192,12 +191,12 @@ private func trendingPaneEntries(trendingEntries: [FeaturedStickerPackItem], ins public final class ChatMediaInputTrendingPane: ChatMediaInputPane { public final class Interaction { - let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool + let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool let presentController: (ViewController, Any?) -> Void let getNavigationController: () -> NavigationController? - + public init( - sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool, + sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool, presentController: @escaping (ViewController, Any?) -> Void, getNavigationController: @escaping () -> NavigationController? ) { @@ -255,6 +254,70 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane { self.disposable?.dispose() self.installDisposable.dispose() } + + private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult]) { + guard let action = actions.first else { + return + } + + var animateInAsReplacement = false + if let navigationController = self.interaction.getNavigationController() { + for controller in navigationController.overlayControllers { + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitActionAndReplacementAnimation() + animateInAsReplacement = true + } + } + } + + var presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if let forceTheme = self.forceTheme { + presentationData = presentationData.withUpdated(theme: forceTheme) + } + + let controller: UndoOverlayController + switch action.action { + case .add: + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_AddedTitle, + text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string, + undo: false, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { _ in + return true + } + ) + case let .remove(positionInList): + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_RemovedTitle, + text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string, + undo: true, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { [weak self] overlayAction in + if case .undo = overlayAction { + let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start() + } + return true + } + ) + } + + self.interaction.getNavigationController()?.presentOverlay(controller: controller) + } public func activate() { if self.isActivated { @@ -374,7 +437,9 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane { return false } }, - actionPerformed: nil + actionPerformed: { [weak self] actions in + self?.presentStickerPackActionOverlay(actions) + } ) strongSelf.interaction.presentController(controller, nil) } @@ -388,19 +453,15 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane { let previousEntries = Atomic<[TrendingPaneEntry]?>(value: nil) let context = self.context let forceTheme = self.forceTheme - self.disposable = (combineLatest(context.account.viewTracker.featuredStickerPacks(), context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]), context.sharedContext.presentationData) - |> map { trendingEntries, view, presentationData -> TrendingPaneTransition in + self.disposable = (combineLatest(context.account.viewTracker.featuredStickerPacks(), context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks)), context.sharedContext.presentationData) + |> map { trendingEntries, packsEntries, presentationData -> TrendingPaneTransition in var presentationData = presentationData if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - for entry in packsEntries { - installedPacks.insert(entry.id) - } - } + var installedPacks = Set() + for entry in packsEntries { + installedPacks.insert(entry.id) } let entries = trendingPaneEntries(trendingEntries: trendingEntries, installedPacks: installedPacks, theme: presentationData.theme, strings: presentationData.strings, isPane: isPane) let previous = previousEntries.swap(entries) diff --git a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift index b91f1d7a65..e36d275667 100644 --- a/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift +++ b/submodules/FeaturedStickersScreen/Sources/FeaturedStickersScreen.swift @@ -389,23 +389,16 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { self.disposable = (combineLatest(queue: .mainQueue(), mappedFeatured, self.additionalPacks.get(), - context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackIds(namespace: Namespaces.ItemCollection.CloudStickerPacks)), context.sharedContext.presentationData ) - |> map { featuredEntries, additionalPacks, view, presentationData -> FeaturedTransition in + |> map { featuredEntries, additionalPacks, installedPackIds, presentationData -> FeaturedTransition in var presentationData = presentationData if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) } - - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - for entry in packsEntries { - installedPacks.insert(entry.id) - } - } - } + + let installedPacks = Set(installedPackIds) let entries = featuredScreenEntries(featuredEntries: featuredEntries.0, installedPacks: installedPacks, theme: presentationData.theme, strings: presentationData.strings, fixedUnread: featuredEntries.1, additionalPacks: additionalPacks) let previous = previousEntries.swap(entries) @@ -863,6 +856,8 @@ public final class FeaturedStickersScreen: ViewController { fileprivate var searchNavigationNode: SearchNavigationContentNode? + private var eventsDisposable: Disposable? + public init(context: AccountContext, highlightedPackId: ItemCollectionId?, forceTheme: PresentationTheme? = nil, stickerActionTitle: String? = nil, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)? = nil) { self.context = context self.highlightedPackId = highlightedPackId @@ -906,6 +901,18 @@ public final class FeaturedStickersScreen: ViewController { } } }) + + self.eventsDisposable = (context.account.stateManager.installedStickerPacksArchivedEvents + |> deliverOnMainQueue).startStrict(next: { [weak self] count in + guard let self else { + return + } + if count == 0 { + return + } + let presentationData = self.presentationData + self.push(textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + }) } required init(coder aDecoder: NSCoder) { @@ -914,6 +921,7 @@ public final class FeaturedStickersScreen: ViewController { deinit { self.presentationDataDisposable?.dispose() + self.eventsDisposable?.dispose() } private func updatePresentationData() { @@ -1167,7 +1175,8 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { private var enqueuedTransitions: [FeaturedSearchGridTransition] = [] private let searchDisposable = MetaDisposable() - + private var stickerSearchContext: StickerSearchContext? + private let queue = Queue() private let currentEntries = Atomic<[FeaturedSearchEntry]?>(value: nil) private let currentRemotePacks = Atomic(value: nil) @@ -1222,6 +1231,14 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { self.gridNode.scrollingInitiated = { [weak self] in self?.deactivateSearchBar?() } + self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in + guard let self, let (bottomIndex, _) = visibleItems.bottomVisible else { + return + } + if bottomIndex >= self.gridNode.items.count - 15 { + self.stickerSearchContext?.loadMore() + } + } self.interaction = StickerPaneSearchInteraction(open: { [weak self] info in if let strongSelf = self { @@ -1284,56 +1301,52 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { } func updateText(_ text: String, languageCode: String?) { - let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError> + let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?, Bool, StickerSearchContext?)?, NoError> if !text.isEmpty { let context = self.context - let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in - var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) - - let query = text.trimmingCharacters(in: .whitespacesAndNewlines) - if query.isSingleEmoji { - signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0], inputLanguageCode: "") - |> map { (nil, $0.items) }]) - } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { - var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) - if !languageCode.lowercased().hasPrefix("en") { - signal = signal - |> mapToSignal { keywords in - return .single(keywords) - |> then( - context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) - |> map { englishKeywords in - return keywords + englishKeywords - } - ) - } - } - signals = signal - |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in - let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } - return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode) - |> map { (nil, $0.items) }] + let query = text.trimmingCharacters(in: .whitespacesAndNewlines) + let _ = self.currentRemotePacks.swap(nil) + self.stickerSearchContext = nil + + let stickers: Signal<(items: [(String?, FoundStickerItem)], isSearching: Bool, searchContext: StickerSearchContext?), NoError> + if query.isSingleEmoji { + let searchContext = context.engine.stickers.stickerSearchContext(query: nil, emoticon: [query.basicEmoji.0], inputLanguageCode: "") + stickers = searchContext.state + |> map { state -> (items: [(String?, FoundStickerItem)], isSearching: Bool, searchContext: StickerSearchContext?) in + return (state.items.map { (nil, $0) }, state.items.isEmpty && state.isLoadingMore, searchContext) + } + } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { + var keywordsSignal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) + if !languageCode.lowercased().hasPrefix("en") { + keywordsSignal = keywordsSignal + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) } } - - return (signals - |> mapToSignal { signals in - return combineLatest(signals) - }).start(next: { results in - var result: [(String?, FoundStickerItem)] = [] - for (emoji, stickers) in results { - for sticker in stickers { - result.append((emoji, sticker)) - } + stickers = keywordsSignal + |> mapToSignal { keywords -> Signal<(items: [(String?, FoundStickerItem)], isSearching: Bool, searchContext: StickerSearchContext?), NoError> in + let emoticon = Array(Set(keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 })) + guard !emoticon.isEmpty else { + return .single(([], false, nil)) } - subscriber.putNext(result) - }, completed: { - subscriber.putCompletion() - }) + let searchContext = context.engine.stickers.stickerSearchContext(query: query, emoticon: emoticon, inputLanguageCode: languageCode) + return searchContext.state + |> map { state -> (items: [(String?, FoundStickerItem)], isSearching: Bool, searchContext: StickerSearchContext?) in + return (state.items.map { (nil, $0) }, state.items.isEmpty && state.isLoadingMore, searchContext) + } + } + } else { + stickers = .single(([], false, nil)) } - let local = context.engine.stickers.searchStickerSets(query: text) - let remote = context.engine.stickers.searchStickerSetsRemotely(query: text) + let local = context.engine.stickers.searchStickerSets(query: query) + let remote = context.engine.stickers.searchStickerSetsRemotely(query: query) |> delay(0.2, queue: Queue.mainQueue()) let rawPacks = local |> mapToSignal { result -> Signal<(FoundStickerSets, Bool, FoundStickerSets?), NoError> in @@ -1350,17 +1363,9 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { ) } - let installedPackIds = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]) - |> map { view -> Set in - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - for entry in packsEntries { - installedPacks.insert(entry.id) - } - } - } - return installedPacks + let installedPackIds = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackIds(namespace: Namespaces.ItemCollection.CloudStickerPacks)) + |> map { installedPackIds -> Set in + return Set(installedPackIds) } |> distinctUntilChanged let packs = combineLatest(rawPacks, installedPackIds) @@ -1387,11 +1392,12 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { } signal = combineLatest(stickers, packs) - |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in - return (stickers, packs.0, packs.1, packs.2) + |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?, Bool, StickerSearchContext?)? in + return (stickers.items, packs.0, packs.1, packs.2, stickers.isSearching, stickers.searchContext) } - self.updateActivity?(true) } else { + self.stickerSearchContext = nil + let _ = self.currentRemotePacks.swap(nil) signal = .single(nil) self.updateActivity?(false) } @@ -1406,14 +1412,13 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { var displayResults: Bool = false var entries: [FeaturedSearchEntry] = [] - if let (stickers, packs, final, remote) = result { + if let (stickers, packs, final, remote, isSearching, searchContext) = result { + strongSelf.stickerSearchContext = searchContext if let remote = remote { let _ = strongSelf.currentRemotePacks.swap(remote) } - if final { - strongSelf.updateActivity?(false) - } + strongSelf.updateActivity?(isSearching) var index = 0 var existingStickerIds = Set() @@ -1444,12 +1449,15 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode { } } - if final || !entries.isEmpty { + if !isSearching && (final || !entries.isEmpty) { strongSelf.notFoundNode.isHidden = !entries.isEmpty + } else { + strongSelf.notFoundNode.isHidden = true } displayResults = true } else { + strongSelf.stickerSearchContext = nil let _ = strongSelf.currentRemotePacks.swap(nil) strongSelf.updateActivity?(false) } diff --git a/submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift b/submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift index 59228b5fff..c466760248 100644 --- a/submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift +++ b/submodules/FeaturedStickersScreen/Sources/MediaInputPaneTrendingItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import StickerResources diff --git a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift index 7cf0cfffe2..e36198db62 100644 --- a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift +++ b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchGlobaltem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import ListSectionHeaderNode import AccountContext diff --git a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift index d1521567eb..2e12bc2331 100644 --- a/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift +++ b/submodules/FeaturedStickersScreen/Sources/StickerPaneSearchStickerItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import StickerResources import AccountContext diff --git a/submodules/FileMediaResourceStatus/BUILD b/submodules/FileMediaResourceStatus/BUILD index 44d70774c6..12da9fa5c8 100644 --- a/submodules/FileMediaResourceStatus/BUILD +++ b/submodules/FileMediaResourceStatus/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/MediaPlayer:UniversalMediaPlayer", diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD index ec0ae7d4d8..83e10f7cc2 100644 --- a/submodules/GalleryData/BUILD +++ b/submodules/GalleryData/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 91ed759a29..f542f70350 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -1,7 +1,6 @@ import Foundation import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import PassKit @@ -19,20 +18,20 @@ import StoryContainerScreen public enum ChatMessageGalleryControllerData { case url(String) case pass(TelegramMediaFile) - case instantPage(InstantPageGalleryController, Int, Media) + case instantPage(InstantPageGalleryController, Int, EngineRawMedia) case map(TelegramMediaMap) case stickerPack(StickerPackReference, TelegramMediaFile?) case audio(TelegramMediaFile) case document(TelegramMediaFile, Bool) case gallery(Signal) case secretGallery(SecretMediaPreviewController) - case chatAvatars(AvatarGalleryController, Media) + case chatAvatars(AvatarGalleryController, EngineRawMedia) case theme(TelegramMediaFile) - case other(Media) + case other(EngineRawMedia) case story(Signal) } -private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { +private func instantPageBlockMedia(pageId: EngineMedia.Id, block: InstantPageBlock, media: [EngineMedia.Id: EngineRawMedia], counter: inout Int) -> [InstantPageGalleryEntry] { switch block { case let .image(id, caption, _, _): if let m = media[id] { @@ -64,7 +63,7 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med return [] } -public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage.Accessor, galleryMedia: Media) -> [InstantPageGalleryEntry] { +public func instantPageGalleryMedia(webpageId: EngineMedia.Id, page: InstantPage.Accessor, galleryMedia: EngineRawMedia) -> [InstantPageGalleryEntry] { var result: [InstantPageGalleryEntry] = [] var counter: Int = 0 @@ -96,9 +95,9 @@ public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage.Access public func chatMessageGalleryControllerData( context: AccountContext, chatLocation: ChatLocation?, - chatFilterTag: MemoryBuffer?, + chatFilterTag: EngineMemoryBuffer?, chatLocationContextHolder: Atomic?, - message: Message, + message: EngineRawMessage, mediaSubject: GalleryMediaSubject? = nil, navigationController: NavigationController?, standalone: Bool, @@ -113,10 +112,10 @@ public func chatMessageGalleryControllerData( standalone = true } - var galleryMedia: Media? - var otherMedia: Media? + var galleryMedia: EngineRawMedia? + var otherMedia: EngineRawMedia? var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])? - if message.media.isEmpty, let entities = message.textEntitiesAttribute?.entities, entities.count == 1, let firstEntity = entities.first, case let .CustomEmoji(_, fileId) = firstEntity.type, let file = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { + if message.media.isEmpty, let entities = message.textEntitiesAttribute?.entities, entities.count == 1, let firstEntity = entities.first, case let .CustomEmoji(_, fileId) = firstEntity.type, let file = message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { for attribute in file.attributes { if case let .CustomEmoji(_, _, _, reference) = attribute { if let reference = reference { @@ -126,7 +125,7 @@ public func chatMessageGalleryControllerData( } } } - for media in message.media { + for media in message.effectiveMedia { if let poll = media as? TelegramMediaPoll { standalone = true galleryMedia = poll @@ -174,15 +173,26 @@ public func chatMessageGalleryControllerData( } } - if let instantPage = content.instantPage, let galleryMedia = galleryMedia { - switch instantPageType(of: content) { - case .album: - let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) - if medias.count > 1 { + if let instantPage = content.instantPage { + if case let .instantPageMedia(tappedMediaId) = mediaSubject { + let parsedPage = instantPage._parse() + if let tappedMedia = parsedPage.media[tappedMediaId] { + let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: tappedMedia) + if !medias.isEmpty { instantPageMedia = (webpage, medias) + galleryMedia = tappedMedia } - default: - break + } + } else if let galleryMedia = galleryMedia { + switch instantPageType(of: content) { + case .album: + let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia) + if medias.count > 1 { + instantPageMedia = (webpage, medias) + } + default: + break + } } } } else if let mapMedia = media as? TelegramMediaMap { @@ -220,7 +230,7 @@ public func chatMessageGalleryControllerData( } } - let gallery = InstantPageGalleryController(context: context, userLocation: chatLocation?.peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in + let gallery = InstantPageGalleryController(context: context, userLocation: chatLocation?.peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, webPage: webPage, message: EngineMessage(message), entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in if let navigationController = navigationController { navigationController.replaceTopController(controller, animated: false, ready: ready) } @@ -345,25 +355,11 @@ public func chatMessageGalleryControllerData( } public enum ChatMessagePreviewControllerData { - case instantPage(InstantPageGalleryController, Int, Media) + case instantPage(InstantPageGalleryController, Int, EngineRawMedia) case gallery(GalleryController) } -public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? { - if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { - switch mediaData { - case .gallery: - break - case let .instantPage(gallery, centralIndex, galleryMedia): - return .instantPage(gallery, centralIndex, galleryMedia) - default: - break - } - } - return nil -} - -public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: MemoryBuffer?, chatLocationContextHolder: Atomic?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { +public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatFilterTag: EngineMemoryBuffer?, chatLocationContextHolder: Atomic?, message: EngineRawMessage, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal { if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatFilterTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) { switch mediaData { case let .gallery(gallery): diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index 50b974a980..bb4781735b 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -40,6 +40,7 @@ swift_library( "//submodules/UndoUI:UndoUI", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", "//submodules/TranslateUI:TranslateUI", + "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/Utils/RangeSet:RangeSet", "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 1b5551a902..3ca685b5c4 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import Photos @@ -27,6 +26,7 @@ import MultiAnimationRenderer import Pasteboard import Speak import TranslateUI +import TextProcessingScreen import TelegramNotices import SolidRoundedButtonNode import UrlHandling @@ -49,7 +49,7 @@ private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: enum ChatItemGalleryFooterContent: Equatable { case info - case fetch(status: MediaResourceStatus, seekable: Bool) + case fetch(status: EngineMediaResource.FetchStatus, seekable: Bool) case playback(paused: Bool, seekable: Bool) static func ==(lhs: ChatItemGalleryFooterContent, rhs: ChatItemGalleryFooterContent) -> Bool { @@ -80,11 +80,11 @@ enum ChatItemGalleryFooterContentTapAction { case none case url(url: String, concealed: Bool) case textMention(String) - case peerMention(PeerId, String) + case peerMention(EnginePeer.Id, String) case botCommand(String) case hashtag(String?, String) case instantPage - case call(PeerId) + case call(EnginePeer.Id) case openMessage case ignore } @@ -169,8 +169,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll private var currentMessageText: NSAttributedString? - private var currentMessage: Message? - private var currentWebPageAndMedia: (TelegramMediaWebpage, Media)? + private var currentMessage: EngineRawMessage? + private var currentWebPageAndMedia: (TelegramMediaWebpage, EngineRawMedia)? private var mediaSubject: GalleryMediaSubject? private let messageContextDisposable = MetaDisposable() @@ -208,7 +208,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll private var currentSpeechHolder: SpeechSynthesizerHolder? var performAction: ((GalleryControllerInteractionTapAction) -> Void)? - var openActionOptions: ((GalleryControllerInteractionTapAction, Message) -> Void)? + var openActionOptions: ((GalleryControllerInteractionTapAction, EngineRawMessage) -> Void)? private var isAd: Bool { if self.currentMessage?.adAttribute != nil { @@ -547,34 +547,42 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll let (_, language) = canTranslateText(context: self.context, text: text.string, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages) let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: self.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start() - - let translateController = TranslateScreen(context: self.context, forceTheme: defaultDarkPresentationTheme, text: text.string, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - translateController.pushController = { [weak self] c in + + Task { @MainActor [weak self] in guard let self else { return } - self.controllerInteraction?.pushController(c) + let translateController = await TextProcessingScreen( + context: self.context, + theme: defaultDarkPresentationTheme, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: text.string, entities: []), + copyResult: { [weak self] text in + guard let self else { + return + } + storeMessageTextInPasteboard(text.text, entities: text.entities) + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let undoController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, appearance: UndoOverlayController.Appearance(isBlurred: true), action: { _ in true }) + self.controllerInteraction?.presentController(undoController, nil) + }, + translateChat: nil + ) + + //self.actionSheet = translateController + //view.updateIsProgressPaused() + + /*translateController.wasDismissed = { [weak self, weak view] in + guard let self, let view else { + return + } + self.actionSheet = nil + view.updateIsProgressPaused() + }*/ + + //component.controller()?.present(translateController, in: .window(.root)) + self.controllerInteraction?.presentController(translateController, nil) } - translateController.presentController = { [weak self] c in - guard let self else { - return - } - self.controllerInteraction?.presentController(c, nil) - } - - //self.actionSheet = translateController - //view.updateIsProgressPaused() - - /*translateController.wasDismissed = { [weak self, weak view] in - guard let self, let view else { - return - } - self.actionSheet = nil - view.updateIsProgressPaused() - }*/ - - //component.controller()?.present(translateController, in: .window(.root)) - self.controllerInteraction?.presentController(translateController, nil) }) case .quote: break @@ -676,6 +684,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll super.didLoad() self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false let backwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekBackwardLongPress(_:))) backwardLongPressGestureRecognizer.minimumPressDuration = 0.3 @@ -846,7 +855,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } - func setMessage(_ message: Message, mediaSubject: GalleryMediaSubject? = nil, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, settingsButtonState: SettingsButtonState? = nil, displayTextRecognitionButton: Bool = false, displayStickersButton: Bool = false, animated: Bool = false) { + func setMessage(_ message: EngineRawMessage, mediaSubject: GalleryMediaSubject? = nil, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, settingsButtonState: SettingsButtonState? = nil, displayTextRecognitionButton: Bool = false, displayStickersButton: Bool = false, animated: Bool = false) { self.currentMessage = message self.mediaSubject = mediaSubject @@ -861,7 +870,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll var canEdit = false var isImage = false var isVideo = false - for media in message.media { + for media in message.effectiveMedia { if media is TelegramMediaImage { canEdit = true isImage = true @@ -1141,7 +1150,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } - func setWebPage(_ webPage: TelegramMediaWebpage, media: Media) { + func setWebPage(_ webPage: TelegramMediaWebpage, media: EngineRawMedia) { self.currentWebPageAndMedia = (webPage, media) } @@ -1357,7 +1366,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll speed: settingsButtonState.speed, quality: settingsButtonState.quality, isOpen: false - ))), + )), insets: .zero), action: { [weak self] in guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View else { return @@ -1665,7 +1674,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll var items: [ActionSheetItem] = [] var personalPeerName: String? var isChannel = false - let peerId: PeerId = messages[0].id.peerId + let peerId: EnginePeer.Id = messages[0].id.peerId if let user = messages[0].peers[messages[0].id.peerId] as? TelegramUser { personalPeerName = EnginePeer(user).compactDisplayTitle } else if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel, case .broadcast = channel.info { @@ -1743,7 +1752,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll for message in messages { var currentKind = messageContentKind(contentSettings: strongSelf.context.currentContentSettings.with { $0 }, message: message, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat, accountPeerId: strongSelf.context.account.peerId) if case .poll = currentKind, let poll = message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll { - var media: Media? + var media: EngineRawMedia? switch strongSelf.mediaSubject { case .pollDescription: media = poll.attachedMedia @@ -1820,12 +1829,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll if availableOpenInOptions(context: strongSelf.context, item: item).count > 1 { preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in if let strongSelf = self { - let openInController = OpenInActionSheetController(context: strongSelf.context, forceTheme: defaultDarkColorPresentationTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in + let openInController = OpenInOptionsScreen(context: strongSelf.context, forceTheme: defaultDarkColorPresentationTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in if let strongSelf = self { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) } }) - strongSelf.controllerInteraction?.presentController(openInController, nil) + strongSelf.controllerInteraction?.pushController(openInController) } })) } else { @@ -1993,7 +2002,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } - let shareAction: ([Message]) -> Void = { messages in + let shareAction: ([EngineRawMessage]) -> Void = { messages in if let strongSelf = self { let shareController = strongSelf.context.sharedContext.makeShareController(context: strongSelf.context, params: ShareControllerParams(subject: .messages(messages), preferredAction: preferredAction, forceTheme: forceTheme, actionCompleted: { [weak self] in if let strongSelf = self { @@ -2125,12 +2134,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll if availableOpenInOptions(context: self.context, item: item).count > 1 { preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in if let strongSelf = self { - let openInController = OpenInActionSheetController(context: strongSelf.context, forceTheme: forceTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in + let openInController = OpenInOptionsScreen(context: strongSelf.context, forceTheme: forceTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in if let strongSelf = self { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) } }) - strongSelf.controllerInteraction?.presentController(openInController, nil) + strongSelf.controllerInteraction?.pushController(openInController) } })) } else { diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index 58f740c35b..ea0aaa8fa3 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import Display import UniversalMediaPlayer import TelegramPresentationData @@ -304,7 +303,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.scrubberNode.bufferingStatus = status } - func setFetchStatusSignal(_ fetchStatus: Signal?, strings: PresentationStrings, decimalSeparator: String, fileSize: Int64?) { + func setFetchStatusSignal(_ fetchStatus: Signal?, strings: PresentationStrings, decimalSeparator: String, fileSize: Int64?) { let formatting = DataSizeStringFormatting(strings: strings, decimalSeparator: decimalSeparator) if let fileSize = fileSize { if let fetchStatus = fetchStatus { diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 769144c9a8..018b5fc9f8 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -18,26 +18,27 @@ import UndoUI import TranslateUI private func tagsForMessage(_ message: Message) -> MessageTags? { - for media in message.media { + //TODO:rewrite to take all media (effectiveMedia returns all rich-text media; we stop at the first) + for media in message.effectiveMedia { switch media { - case _ as TelegramMediaImage: - return .photoOrVideo - case let file as TelegramMediaFile: - if file.isVideo { - if file.isAnimated { - return .gif - } else { - return .photoOrVideo - } - } else if file.isVoice { - return .voiceOrInstantVideo - } else if file.isSticker { - return nil + case _ as TelegramMediaImage: + return .photoOrVideo + case let file as TelegramMediaFile: + if file.isVideo { + if file.isAnimated { + return .gif } else { - return .file + return .photoOrVideo } - default: - break + } else if file.isVoice { + return .voiceOrInstantVideo + } else if file.isSticker { + return nil + } else { + return .file + } + default: + break } } return nil @@ -61,7 +62,8 @@ private func galleryMediaForMedia(media: Media) -> Media? { } func mediaForMessage(message: Message, mediaSubject: GalleryMediaSubject? = nil) -> [(Media, TelegramMediaImage?)] { - for media in message.media { + //TODO:rewrite to take all media (effectiveMedia returns all rich-text media; we return the first match) + for media in message.effectiveMedia { if let result = galleryMediaForMedia(media: media) { return [(result, nil)] } else if let poll = media as? TelegramMediaPoll { @@ -115,6 +117,7 @@ func mediaForMessage(message: Message, mediaSubject: GalleryMediaSubject? = nil) } } } + return [] } @@ -690,7 +693,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn } public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol { - public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, edgeEffectColor: .clear, accentButtonColor: .white, accentForegroundColor: .black, style: .glass) + public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, edgeEffectColor: .clear, accentButtonColor: .white, accentDisabledButtonColor: .white, accentForegroundColor: .black, style: .glass) private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode @@ -932,7 +935,7 @@ public class GalleryController: ViewController, StandalonePresentableController, var isFirstTime = true self.disposable.set(combineLatest( messageView, - self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1), + self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1), translateToLanguage |> take(1) ).start(next: { [weak self] view, preferencesView, translateToLanguage in let f: () -> Void = { @@ -940,7 +943,7 @@ public class GalleryController: ViewController, StandalonePresentableController, if let view = view { strongSelf.peerIsCopyProtected = view.peerIsCopyProtected - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue let configuration = GalleryConfiguration.with(appConfiguration: appConfiguration) strongSelf.configuration = configuration diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 094fadb5fc..618021a6c9 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import SwipeToDismissGesture import AccountContext import UndoUI diff --git a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift index 881abc1297..d36e7674c4 100644 --- a/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/GalleryFooterContentNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox +import TelegramCore import ComponentFlow import ComponentDisplayAdapters @@ -12,11 +12,11 @@ public final class GalleryControllerInteraction { public let pushController: (ViewController) -> Void public let dismissController: () -> Void public let replaceRootController: (ViewController, Promise?) -> Void - public let editMedia: (MessageId) -> Void + public let editMedia: (EngineMessage.Id) -> Void public let controller: () -> ViewController? public let currentItemNode: () -> GalleryItemNode? - public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise?) -> Void, editMedia: @escaping (MessageId) -> Void, controller: @escaping () -> ViewController?, currentItemNode: @escaping () -> GalleryItemNode?) { + public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise?) -> Void, editMedia: @escaping (EngineMessage.Id) -> Void, controller: @escaping () -> ViewController?, currentItemNode: @escaping () -> GalleryItemNode?) { self.presentController = presentController self.pushController = pushController self.dismissController = dismissController diff --git a/submodules/GalleryUI/Sources/GalleryItemNode.swift b/submodules/GalleryUI/Sources/GalleryItemNode.swift index 8bbb4e686b..4c94ab8aad 100644 --- a/submodules/GalleryUI/Sources/GalleryItemNode.swift +++ b/submodules/GalleryUI/Sources/GalleryItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox public enum GalleryItemNodeNavigationStyle { case light diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift index af98d0480d..ac62f7640f 100644 --- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox private func edgeWidth(width: CGFloat) -> CGFloat { return min(44.0, floor(width / 6.0)) diff --git a/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift b/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift index 1f7e9247e8..1c5ef1273b 100644 --- a/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift @@ -83,6 +83,7 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, ASScrollViewDel self.scrollNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false self.addSubnode(self.scrollNode) } diff --git a/submodules/GalleryUI/Sources/GalleryTitleView.swift b/submodules/GalleryUI/Sources/GalleryTitleView.swift index e75948f5fc..c38569eda7 100644 --- a/submodules/GalleryUI/Sources/GalleryTitleView.swift +++ b/submodules/GalleryUI/Sources/GalleryTitleView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -17,12 +16,24 @@ private let dateFont = Font.regular(12.0) public final class GalleryTitleView: UIView, NavigationBarTitleView { public final class Content: Equatable { - let message: EngineMessage + let message: EngineMessage? + let authorTitle: String? + let dateTitle: String? let title: String? let action: (() -> Void)? - + public init(message: EngineMessage, title: String?, action: (() -> Void)?) { self.message = message + self.authorTitle = nil + self.dateTitle = nil + self.title = title + self.action = action + } + + public init(authorTitle: String?, dateTitle: String?, title: String?, action: (() -> Void)?) { + self.message = nil + self.authorTitle = authorTitle + self.dateTitle = dateTitle self.title = title self.action = action } @@ -31,6 +42,12 @@ public final class GalleryTitleView: UIView, NavigationBarTitleView { if lhs.message != rhs.message { return false } + if lhs.authorTitle != rhs.authorTitle { + return false + } + if lhs.dateTitle != rhs.dateTitle { + return false + } if lhs.title != rhs.title { return false } @@ -115,18 +132,38 @@ public final class GalleryTitleView: UIView, NavigationBarTitleView { public func setContent(content: Content?) { self.content = content - self.backgroundContainer.isHidden = self.content == nil + let authorNameText: String? + let dateText: String? if let content { - let authorNameText = stringForFullAuthorName(message: content.message, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, accountPeerId: self.context.account.peerId).first ?? "" - let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: content.message.timestamp).string - - self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white) - self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5)) + if let message = content.message { + authorNameText = stringForFullAuthorName(message: message, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, accountPeerId: self.context.account.peerId).first ?? "" + dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: message.timestamp).string + } else { + authorNameText = content.authorTitle + dateText = content.dateTitle + } + } else { + authorNameText = nil + dateText = nil } - + + if let authorNameText { + self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white) + } else { + self.authorNameNode.attributedText = nil + } + + if let dateText { + self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: UIColor(white: 1.0, alpha: 0.5)) + } else { + self.dateNode.attributedText = nil + } + + self.backgroundContainer.isHidden = (authorNameText?.isEmpty ?? true) && (dateText?.isEmpty ?? true) + self.titleString = content?.title - + if !self.bounds.isEmpty { let _ = self.updateLayout(availableSize: self.bounds.size, transition: .immediate) } diff --git a/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift index f7e42b59d4..8fe07decc8 100644 --- a/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatAnimationGalleryItem.swift @@ -33,7 +33,7 @@ class ChatAnimationGalleryItem: GalleryItem { func node(synchronous: Bool) -> GalleryItemNode { let node = ChatAnimationGalleryItemNode(context: self.context, presentationData: self.presentationData) - for media in self.message.media { + for media in self.message.effectiveMedia { if let file = media as? TelegramMediaFile { node.setFile(context: self.context, fileReference: .message(message: MessageReference(self.message), media: file)) break @@ -93,7 +93,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { private var disposable = MetaDisposable() private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { self.context = context @@ -195,7 +195,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { } private func setupStatus(resource: MediaResource) { - self.statusDisposable.set((self.context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((self.context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status @@ -341,7 +341,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode { if let resource = resource { switch status { case .Fetching: - self.context.account.postbox.mediaBox.cancelInteractiveResourceFetch(resource.resource) + self.context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(resource.resource.id)) case .Remote: self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file, reference: resource, statsCategory: statsCategory ?? .generic).start()) default: diff --git a/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift index 19eadbaba7..b6e519f6c8 100644 --- a/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatDocumentGalleryItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import SwiftSignalKit import WebKit @@ -17,10 +16,10 @@ class ChatDocumentGalleryItem: GalleryItem { let context: AccountContext let presentationData: PresentationData - let message: Message - let location: MessageHistoryEntryLocation? + let message: EngineRawMessage + let location: EngineMessageHistoryEntryLocation? - init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?) { + init(context: AccountContext, presentationData: PresentationData, message: EngineRawMessage, location: EngineMessageHistoryEntryLocation?) { self.context = context self.presentationData = presentationData self.message = message @@ -30,7 +29,7 @@ class ChatDocumentGalleryItem: GalleryItem { func node(synchronous: Bool) -> GalleryItemNode { let node = ChatDocumentGalleryItemNode(context: self.context, presentationData: self.presentationData) - for media in self.message.media { + for media in self.message.effectiveMedia { if let file = media as? TelegramMediaFile { node.setFile(context: context, fileReference: .message(message: MessageReference(self.message), media: file)) break @@ -104,13 +103,13 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD private var itemIsVisible = false - private var message: Message? + private var message: EngineRawMessage? private let footerContentNode: ChatItemGalleryFooterContentNode private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { @@ -163,7 +162,7 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize)) } - fileprivate func setMessage(_ message: Message) { + fileprivate func setMessage(_ message: EngineRawMessage) { self.footerContentNode.setMessage(message) } @@ -183,12 +182,12 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD self.maybeLoadContent() self.setupStatus(context: context, resource: fileReference.media.resource) - self.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + self.fetchDisposable.set(context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file).start()) } } - private func setupStatus(context: AccountContext, resource: MediaResource) { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(resource) + private func setupStatus(context: AccountContext, resource: EngineRawMediaResource) { + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status @@ -238,11 +237,11 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD if let fileName = fileReference.media.fileName { pathExtension = (fileName as NSString).pathExtension } - let data = context.account.postbox.mediaBox.resourceData(fileReference.media.resource, pathExtension: pathExtension, option: .complete(waitUntilFetchStatus: false)) + let data = context.engine.resources.data(resource: EngineMediaResource(fileReference.media.resource), pathExtension: pathExtension) |> deliverOnMainQueue self.dataDisposable.set(data.start(next: { [weak self] data in if let strongSelf = self { - if data.complete { + if data.isComplete { if let webView = strongSelf.webView as? WKWebView { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { let blockRules = """ @@ -386,9 +385,9 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD if let (context, fileReference) = self.contextAndFile, let status = self.status { switch status { case .Fetching: - context.account.postbox.mediaBox.cancelInteractiveResourceFetch(fileReference.media.resource) + context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(fileReference.media.resource.id)) case .Remote: - self.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + self.fetchDisposable.set(context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file).start()) default: break } diff --git a/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift index 445d760853..80aa995bef 100644 --- a/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatExternalFileGalleryItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import SwiftSignalKit import WebKit @@ -17,10 +16,10 @@ class ChatExternalFileGalleryItem: GalleryItem { let context: AccountContext let presentationData: PresentationData - let message: Message - let location: MessageHistoryEntryLocation? - - init(context: AccountContext, presentationData: PresentationData, message: Message, location: MessageHistoryEntryLocation?) { + let message: EngineRawMessage + let location: EngineMessageHistoryEntryLocation? + + init(context: AccountContext, presentationData: PresentationData, message: EngineRawMessage, location: EngineMessageHistoryEntryLocation?) { self.context = context self.presentationData = presentationData self.message = message @@ -30,7 +29,7 @@ class ChatExternalFileGalleryItem: GalleryItem { func node(synchronous: Bool) -> GalleryItemNode { let node = ChatExternalFileGalleryItemNode(context: self.context, presentationData: self.presentationData) - for media in self.message.media { + for media in self.message.effectiveMedia { if let file = media as? TelegramMediaFile { node.setFile(context: context, fileReference: .message(message: MessageReference(self.message), media: file)) break @@ -78,13 +77,13 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { private var itemIsVisible = false - private var message: Message? + private var message: EngineRawMessage? private let footerContentNode: ChatItemGalleryFooterContentNode private var fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? init(context: AccountContext, presentationData: PresentationData) { self.containerNode = ASDisplayNode() @@ -164,7 +163,7 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize)) } - fileprivate func setMessage(_ message: Message) { + fileprivate func setMessage(_ message: EngineRawMessage) { self.message = message self.footerContentNode.setMessage(message) } @@ -182,8 +181,8 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { } } - private func setupStatus(context: AccountContext, resource: MediaResource) { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(resource) + private func setupStatus(context: AccountContext, resource: EngineRawMediaResource) { + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status @@ -320,9 +319,9 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode { if let (context, fileReference) = self.contextAndFile, let status = self.status { switch status { case .Fetching: - context.account.postbox.mediaBox.cancelInteractiveResourceFetch(fileReference.media.resource) + context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(fileReference.media.resource.id)) case .Remote: - self.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + self.fetchDisposable.set(context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .file).start()) default: break } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index fc38976031..337480c990 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -17,6 +17,7 @@ import ImageContentAnalysis import TextSelectionNode import Speak import TranslateUI +import TextProcessingScreen import UndoUI import ContextUI import SaveToCameraRoll @@ -249,7 +250,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let statusDisposable = MetaDisposable() private let dataDisposable = MetaDisposable() private let recognitionDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? private var fetchedDimensions: PixelDimensions? private let pagingEnabledPromise = ValuePromise(true) @@ -408,15 +409,24 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } case .translate: if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController { - let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil) - controller.pushController = { [weak parentController] c in - (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true - parentController?.push(c) + Task { @MainActor [weak parentController, weak strongSelf] in + guard let strongSelf else { + return + } + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 }) + let controller = await TextProcessingScreen( + context: strongSelf.context, + mode: .translate(fromLanguage: nil, applyResult: nil), + inputText: TextWithEntities(text: string, entities: []), + copyResult: { [weak parentController] text in + storeMessageTextInPasteboard(text.text, entities: text.entities) + let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }) + parentController?.present(tooltipController, in: .window(.root)) + }, + translateChat: nil + ) + parentController?.present(controller, in: .window(.root)) } - controller.presentController = { [weak parentController] c in - parentController?.present(c, in: .window(.root)) - } - parentController.present(controller, in: .window(.root)) } } }) @@ -507,7 +517,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.zoomableContent = (largestSize.dimensions.cgSize, self.imageNode) - self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .image, reference: imageReference.resourceReference(largestSize.resource)).start()) + self.fetchDisposable.set(self.context.engine.resources.fetch(reference: imageReference.resourceReference(largestSize.resource), userLocation: userLocation, userContentType: .image).start()) self.setupStatus(resource: largestSize.resource) } else { self._ready.set(.single(Void())) @@ -729,9 +739,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { guard let self else { return } - let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media) + let _ = (fetchMediaData(context: context, userLocation: .other, mediaReference: media) |> deliverOnMainQueue).start(next: { [weak self] (value, isImage) in - guard let self, case let .data(data) = value, data.complete, isImage, let image = UIImage(contentsOfFile: data.path) else { + guard let self, case let .data(data) = value, data.isComplete, isImage, let image = UIImage(contentsOfFile: data.path) else { return } let sendSticker = self.sendSticker @@ -755,7 +765,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Gallery_SaveImage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) - let _ = (SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: media) + let _ = (SaveToCameraRoll.saveToCameraRoll(context: context, userLocation: .peer(message.id.peerId), mediaReference: media) |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { return @@ -769,7 +779,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } } - if let peer, let message = self.message, canSendMessagesToPeer(peer._asPeer()) { + if let peer, let message = self.message, canSendMessagesToPeer(peer) { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in if let self, let navigationController = self.baseNavigationController() { self.beginCustomDismiss(.simpleAnimation) @@ -932,7 +942,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } self._rightBarButtonItems.set(.single(barButtonItems)) - self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .image, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + self.fetchDisposable.set(self.context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: userLocation, userContentType: .image).start()) } else { let _ = (chatMessageFileDatas(account: context.account, userLocation: userLocation, fileReference: fileReference, progressive: false, fetched: true) |> mapToSignal { value -> Signal in @@ -957,7 +967,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } private func setupStatus(resource: MediaResource) { - self.statusDisposable.set((self.context.account.postbox.mediaBox.resourceStatus(resource) + self.statusDisposable.set((self.context.engine.resources.status(resource: EngineMediaResource(resource)) |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self { let previousStatus = strongSelf.status @@ -1196,7 +1206,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { if let resource = resource { switch status { case .Fetching: - self.context.account.postbox.mediaBox.cancelInteractiveResourceFetch(resource.resource) + self.context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(resource.resource.id)) case .Remote: self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: (self.message?.id.peerId).flatMap(MediaResourceUserLocation.peer) ?? .other, userContentType: .image, reference: resource, statsCategory: statsCategory ?? .generic).start()) default: diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index cc7163e468..c21dab581d 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -419,6 +419,9 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN adView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) adView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in adView.removeFromSuperview() + if self.adView.view === adView { + self.adView = ComponentView() + } Queue.mainQueue().after(0.1) { adView.layer.removeAllAnimations() } @@ -444,7 +447,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN return result } } - if let adView = self.adView.view, adView.frame.contains(point) { + if let adView = self.adView.view, adView.superview === self.view, !self.isAnimatingOut, adView.frame.contains(point) { return super.hitTest(point, with: event) } return nil @@ -1436,7 +1439,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if let content = item.content as? NativeVideoContent { isAnimated = content.fileReference.media.isAnimated self.videoFramePreview = MediaPlayerFramePreview(postbox: item.context.account.postbox, userLocation: content.userLocation, userContentType: .video, fileReference: content.fileReference) - if case let .message(message, _) = item.contentInfo, let _ = message.media.first(where: { $0 is TelegramMediaImage }) { + if case let .message(message, _) = item.contentInfo, let _ = message.effectiveMedia.first(where: { $0 is TelegramMediaImage }) { self.isLivePhoto = true disablePlayerControls = true isAnimated = false @@ -1632,7 +1635,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var file: TelegramMediaFile? var isWebpage = false - for m in message.media { + for m in message.effectiveMedia { if let m = m as? TelegramMediaFile, m.isVideo { file = m break @@ -1651,10 +1654,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file) if !isWebpage && message.adAttribute == nil && !NativeVideoContent.isHLSVideo(file: file) { - scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size) + scrubberView.setFetchStatusSignal(status |> map(EngineMediaResource.FetchStatus.init), strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size) } - self.requiresDownload = !isMediaStreamable(message: message, media: file) + self.requiresDownload = !isMediaStreamable(message: EngineMessage(message), media: file) mediaFileStatus = status |> map(Optional.init) self.fetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { @@ -1860,7 +1863,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if item.content is HLSVideoContent { footerContent = .playback(paused: true, seekable: seekable) } else { - footerContent = .fetch(status: fetchStatus, seekable: seekable) + footerContent = .fetch(status: EngineMediaResource.FetchStatus(fetchStatus), seekable: seekable) } } else { footerContent = .info @@ -1882,7 +1885,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.zoomableContent = (videoSize, videoNode) - if case let .message(message, _) = item.contentInfo, let content = item.content as? NativeVideoContent, let image = message.media.first(where: { $0 is TelegramMediaImage }), let imageReference = content.fileReference.abstract.withUpdatedMedia(image).concrete(TelegramMediaImage.self) { + if case let .message(message, _) = item.contentInfo, let content = item.content as? NativeVideoContent, let image = message.effectiveMedia.first(where: { $0 is TelegramMediaImage }), let imageReference = content.fileReference.abstract.withUpdatedMedia(image).concrete(TelegramMediaImage.self) { let imageNode = TransformImageNode() imageNode.alpha = 1.0 imageNode.isUserInteractionEnabled = false @@ -2150,7 +2153,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } var isStreamable = false if let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo { - isStreamable = isMediaStreamable(message: message, media: content.fileReference.media) + isStreamable = isMediaStreamable(message: EngineMessage(message), media: content.fileReference.media) } else { isStreamable = isMediaStreamable(media: content.fileReference.media) } @@ -2949,7 +2952,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } if swipeUpToClose { - addAppLogEvent(postbox: self.context.account.postbox, type: "swipe_up_close", peerId: self.context.account.peerId) + self.context.engine.accountData.addAppLogEvent(type: "swipe_up_close") return false } @@ -2957,7 +2960,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if #available(iOS 15.0, *) { if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { - addAppLogEvent(postbox: self.context.account.postbox, type: "swipe_up_pip", peerId: self.context.account.peerId) + self.context.engine.accountData.addAppLogEvent(type: "swipe_up_pip") nativePictureInPictureContent.beginPictureInPicture() return true } @@ -2966,7 +2969,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } override func maybePerformActionForSwipeDownDismiss() -> Bool { - addAppLogEvent(postbox: self.context.account.postbox, type: "swipe_down_close", peerId: self.context.account.peerId) + self.context.engine.accountData.addAppLogEvent(type: "swipe_down_close") return false } @@ -3016,7 +3019,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let baseNavigationController = self.baseNavigationController() let mediaManager = self.context.sharedContext.mediaManager var expandImpl: (() -> Void)? - let overlayNode = OverlayUniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: { + let overlayNode = OverlayUniversalVideoNode(context: self.context, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, expand: { expandImpl?() }, close: { [weak mediaManager] in mediaManager?.setOverlayVideoNode(nil) @@ -3053,7 +3056,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } overlayNode.canAttachContent = false }) - } else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) { + } else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: EngineMedia(media)) { return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, { return info.2() }), addToTransitionSurface: info.0) @@ -3131,7 +3134,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { var hiddenMedia: (MessageId, Media)? = nil switch item.contentInfo { case let .message(message, _): - for media in message.media { + for media in message.effectiveMedia { if let media = media as? TelegramMediaImage { hiddenMedia = (message.id, media) } else if let media = media as? TelegramMediaFile, media.isVideo { @@ -3184,7 +3187,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.activePictureInPictureController = nil self.activePictureInPictureNavigationController = nil - addAppLogEvent(postbox: self.context.account.postbox, type: "pip_close_btn", peerId: self.context.account.peerId) + self.context.engine.accountData.addAppLogEvent(type: "pip_close_btn") } }, expand: { [weak self] completion in didExpand = true @@ -3234,7 +3237,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if #available(iOS 15.0, *) { if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl { - addAppLogEvent(postbox: self.context.account.postbox, type: "pip_btn", peerId: self.context.account.peerId) + self.context.engine.accountData.addAppLogEvent(type: "pip_btn") nativePictureInPictureContent.beginPictureInPicture() return } @@ -3704,7 +3707,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { allFiles.append(contentsOf: qualitySet.qualityFiles.values) let qualitySignals = allFiles.map { file -> Signal<(fileId: MediaId, isCached: Bool), NoError> in - return self.context.account.postbox.mediaBox.resourceStatus(file.media.resource) + return self.context.engine.resources.status(resource: EngineMediaResource(file.media.resource)) |> take(1) |> map { status -> (fileId: MediaId, isCached: Bool) in return (file.media.fileId, status == .Local) @@ -3761,7 +3764,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let stringSaved = self.presentationData.strings.Story_TooltipSaved let saveFileReference: AnyMediaReference = qualityFile.abstract - let saveSignal = SaveToCameraRoll.saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: saveFileReference) + let saveSignal = SaveToCameraRoll.saveToCameraRoll(context: self.context, userLocation: .peer(message.id.peerId), mediaReference: saveFileReference) let disposable = (saveSignal |> deliverOnMainQueue).start(next: { [weak saveScreen] progress in @@ -3807,7 +3810,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { switch self.fetchStatus { case .Local: - let _ = (SaveToCameraRoll.saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: file)) + let _ = (SaveToCameraRoll.saveToCameraRoll(context: self.context, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: file)) |> deliverOnMainQueue).start(completed: { [weak self] in guard let self else { return @@ -3855,7 +3858,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { }))) } - if let (message, _, _) = strongSelf.contentInfo(), let image = message.media.first(where: { $0 is TelegramMediaImage }) as? TelegramMediaImage, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil { + if let (message, _, _) = strongSelf.contentInfo(), let image = message.effectiveMedia.first(where: { $0 is TelegramMediaImage }) as? TelegramMediaImage, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil { let context = strongSelf.context var videoReference: AnyMediaReference? if let video = image.video { @@ -3864,7 +3867,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveImage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) - let _ = (SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: image), video: videoReference) + let _ = (SaveToCameraRoll.saveToCameraRoll(context: context, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: image), video: videoReference) |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { return @@ -3892,12 +3895,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { if !presentationData.theme.overallDarkAppearance { presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) } - let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in if let strongSelf = self { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {}) } }) - controller.present(actionSheet, in: .window(.root)) + controller.push(actionSheet) } }))) break @@ -3905,7 +3908,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer._asPeer()) { + if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer) { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in if let self, let navigationController = self.baseNavigationController() { self.beginCustomDismiss(.simpleAnimation) diff --git a/submodules/GalleryUI/Sources/Items/VideoAdComponent.swift b/submodules/GalleryUI/Sources/Items/VideoAdComponent.swift index 5bdb216175..31b54db45f 100644 --- a/submodules/GalleryUI/Sources/Items/VideoAdComponent.swift +++ b/submodules/GalleryUI/Sources/Items/VideoAdComponent.swift @@ -5,7 +5,6 @@ import SwiftSignalKit import ComponentFlow import MultilineTextComponent import MultilineTextWithEntitiesComponent -import Postbox import TelegramCore import TelegramPresentationData import ContextUI @@ -111,7 +110,7 @@ final class VideoAdComponent: Component { let titleString = component.message.author?.compactDisplayTitle ?? "" - var media: Media? + var media: EngineRawMedia? if let photo = component.message.media.first as? TelegramMediaImage { media = photo } else if let file = component.message.media.first as? TelegramMediaFile { diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift index c9812c4e2c..3390fbcace 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewController.swift @@ -30,7 +30,7 @@ private func galleryMediaForMedia(media: Media) -> Media? { } private func mediaForMessage(message: Message) -> Media? { - for media in message.media { + for media in message.effectiveMedia { if let result = galleryMediaForMedia(media: media) { return result } else if let webpage = media as? TelegramMediaWebpage { @@ -516,7 +516,7 @@ public final class SecretMediaPreviewController: ViewController { var duration: Double = 0.0 for media in message.media { if let file = media as? TelegramMediaFile { - if let path = self.context.account.postbox.mediaBox.completedResourcePath(file.resource) { + if let path = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.resource.id)) { let tempFile = TempBox.shared.file(path: path, fileName: file.fileName ?? "file") self.tempFile = tempFile tempFilePath = tempFile.path diff --git a/submodules/GalleryUI/Sources/SecretMediaPreviewFooterContentNode.swift b/submodules/GalleryUI/Sources/SecretMediaPreviewFooterContentNode.swift index f82b066de1..2797b42d66 100644 --- a/submodules/GalleryUI/Sources/SecretMediaPreviewFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/SecretMediaPreviewFooterContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit diff --git a/submodules/GlassButtonNode/Info.plist b/submodules/GlassButtonNode/Info.plist deleted file mode 100644 index e1fe4cfb7b..0000000000 --- a/submodules/GlassButtonNode/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/submodules/GraphCore/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift b/submodules/GraphCore/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift index 4da43fc1a0..cf017eb9a0 100644 --- a/submodules/GraphCore/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift +++ b/submodules/GraphCore/Sources/Charts/Controllers/Lines/GeneralLinesChartController.swift @@ -34,6 +34,8 @@ public class GeneralLinesChartController: BaseLinesChartController { private var prevoiusHorizontalStrideInterval: Int = 1 private(set) var chartLines: [LinesChartRenderer.LineData] = [] + + public var min5 = false override public init(chartsCollection: ChartsCollection) { self.initialChartCollection = chartsCollection @@ -217,9 +219,18 @@ public class GeneralLinesChartController: BaseLinesChartController { } func updateHorizontalLimits(horizontalRange: ClosedRange, animated: Bool) { - if let (stride, labels) = horizontalLimitsLabels(horizontalRange: horizontalRange, - scaleType: isZoomed ? .hour : .day, - prevoiusHorizontalStrideInterval: prevoiusHorizontalStrideInterval) { + var scaleType: ChartScaleType = .day + if self.min5 { + scaleType = .minutes5 + } else if isZoomed { + scaleType = .hour + } + + if let (stride, labels) = horizontalLimitsLabels( + horizontalRange: horizontalRange, + scaleType: scaleType, + prevoiusHorizontalStrideInterval: prevoiusHorizontalStrideInterval + ) { self.horizontalScalesRenderer.setup(labels: labels, animated: animated) self.prevoiusHorizontalStrideInterval = stride } diff --git a/submodules/GraphUI/Sources/ChartNode.swift b/submodules/GraphUI/Sources/ChartNode.swift index 6391759d9f..bd8d521f40 100644 --- a/submodules/GraphUI/Sources/ChartNode.swift +++ b/submodules/GraphUI/Sources/ChartNode.swift @@ -20,6 +20,7 @@ public enum ChartType { case twoAxis5MinStep case currency case stars + case lines5Min } public extension ChartTheme { @@ -142,6 +143,11 @@ public func createChartController(_ data: String, type: ChartType, rate: Double stepController.min5 = true controller = stepController controller.isZoomable = false + case .lines5Min: + let linesController = GeneralLinesChartController(chartsCollection: collection) + linesController.min5 = true + controller = linesController + controller.isZoomable = false } controller.getDetailsData = { date, completion in getDetailsData(date, { detailsData in diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift index c9869932c8..d67f0eaf64 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchGlobalChatContents.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext @@ -20,11 +19,11 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { private let publicPosts: Bool private var currentSearchState: SearchMessagesState? - private(set) var mergedHistoryView: MessageHistoryView? - private var sourceHistoryView: MessageHistoryView? - + private(set) var mergedHistoryView: EngineRawMessageHistoryView? + private var sourceHistoryView: EngineRawMessageHistoryView? + private var historyViewDisposable: Disposable? - let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>() + let historyViewStream = ValuePipe<(EngineRawMessageHistoryView, EngineViewUpdateType)>() private var nextUpdateIsHoleFill: Bool = false var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in } @@ -54,7 +53,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { if self.publicPosts { search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil) } else { - search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: nil) + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: nil) } self.isSearchingPromise.set(true) @@ -64,9 +63,9 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { return } - let updateType: ViewUpdateType = .Initial - - let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) + let updateType: EngineViewUpdateType = .Initial + + let historyView = EngineRawMessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { EngineRawMessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: EngineRawMutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) self.sourceHistoryView = historyView self.updateHistoryView(updateType: updateType) @@ -83,11 +82,11 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { }) } - private func updateHistoryView(updateType: ViewUpdateType) { + private func updateHistoryView(updateType: EngineViewUpdateType) { var entries = self.sourceHistoryView?.entries ?? [] entries.sort(by: { $0.message.index < $1.message.index }) - - let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: entries, holeEarlier: self.sourceHistoryView?.holeEarlier ?? false, holeLater: false, isLoading: false) + + let mergedHistoryView = EngineRawMessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: entries, holeEarlier: self.sourceHistoryView?.holeEarlier ?? false, holeLater: false, isLoading: false) self.mergedHistoryView = mergedHistoryView self.historyViewStream.putNext((mergedHistoryView, updateType)) @@ -102,7 +101,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { if self.publicPosts { search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState) } else { - search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: currentSearchState) + search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: currentSearchState) } self.historyViewDisposable?.dispose() @@ -112,9 +111,9 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { return } - let updateType: ViewUpdateType = .FillHole - - let historyView = MessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) + let updateType: EngineViewUpdateType = .FillHole + + let historyView = EngineRawMessageHistoryView(tag: nil, namespaces: .just(Set([Namespaces.Message.Cloud])), entries: result.0.messages.reversed().map { EngineRawMessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: EngineRawMutableMessageHistoryEntryAttributes(authorIsContact: false)) }, holeEarlier: !result.0.completed, holeLater: false, isLoading: false) self.sourceHistoryView = historyView self.updateHistoryView(updateType: updateType) @@ -143,7 +142,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol { var kind: ChatCustomContentsKind - var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { + var historyView: Signal<(EngineRawMessageHistoryView, EngineViewUpdateType), NoError> { return self.impl.signalWith({ impl, subscriber in if let mergedHistoryView = impl.mergedHistoryView { subscriber.putNext((mergedHistoryView, .Initial)) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift index 6d5dfd858b..4e74271f81 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchNavigationContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import SearchBarNode diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift index 4e395aaf5a..b2d7db0d72 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift @@ -307,7 +307,7 @@ final class HashtagSearchRecentQueryItemNode: ItemListRevealOptionsItemNode { if item.clear { strongSelf.setRevealOptions((left: [], right: [])) } else { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.theme.list.itemSecondaryTextColor)])) } } }) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift index bca1b83497..46d70f8a5e 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentQueries.swift @@ -1,23 +1,22 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences private struct HashtagSearchRecentQueryItemId { - public let rawValue: MemoryBuffer + public let rawValue: EngineMemoryBuffer var value: String { return String(data: self.rawValue.makeData(), encoding: .utf8) ?? "" } - init(_ rawValue: MemoryBuffer) { + init(_ rawValue: EngineMemoryBuffer) { self.rawValue = rawValue } init?(_ value: String) { if let data = value.data(using: .utf8) { - self.rawValue = MemoryBuffer(data: data) + self.rawValue = EngineMemoryBuffer(data: data) } else { return nil } diff --git a/submodules/HorizontalPeerItem/BUILD b/submodules/HorizontalPeerItem/BUILD index 36c2ddebc8..b6ad5e8864 100644 --- a/submodules/HorizontalPeerItem/BUILD +++ b/submodules/HorizontalPeerItem/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift index d7c20dfd8e..2e095f765e 100644 --- a/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift +++ b/submodules/HorizontalPeerItem/Sources/HorizontalPeerItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramStringFormatting @@ -27,8 +26,7 @@ public final class HorizontalPeerItem: ListViewItem { let strings: PresentationStrings let mode: HorizontalPeerItemMode let accountPeerId: EnginePeer.Id - let postbox: Postbox - let network: Network + let stateManager: AccountStateManager let energyUsageSettings: EnergyUsageSettings let contentSettings: ContentSettings let animationCache: AnimationCache @@ -48,8 +46,7 @@ public final class HorizontalPeerItem: ListViewItem { strings: PresentationStrings, mode: HorizontalPeerItemMode, accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + stateManager: AccountStateManager, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, @@ -67,8 +64,7 @@ public final class HorizontalPeerItem: ListViewItem { self.strings = strings self.mode = mode self.accountPeerId = accountPeerId - self.postbox = postbox - self.network = network + self.stateManager = stateManager self.energyUsageSettings = energyUsageSettings self.contentSettings = contentSettings self.animationCache = animationCache @@ -228,7 +224,7 @@ public final class HorizontalPeerItemNode: ListViewItemNode { } else { strongSelf.peerNode.compact = false } - strongSelf.peerNode.setup(accountPeerId: item.accountPeerId, postbox: item.postbox, network: item.network, energyUsageSettings: item.energyUsageSettings, contentSettings: item.contentSettings, animationCache: item.animationCache, animationRenderer: item.animationRenderer, resolveInlineStickers: item.resolveInlineStickers, theme: item.theme, strings: item.strings, peer: EngineRenderedPeer(peer: item.peer), requiresPremiumForMessaging: false, numberOfLines: 1, synchronousLoad: synchronousLoads) + strongSelf.peerNode.setup(accountPeerId: item.accountPeerId, stateManager: item.stateManager, energyUsageSettings: item.energyUsageSettings, contentSettings: item.contentSettings, animationCache: item.animationCache, animationRenderer: item.animationRenderer, resolveInlineStickers: item.resolveInlineStickers, theme: item.theme, strings: item.strings, peer: EngineRenderedPeer(peer: item.peer), requiresPremiumForMessaging: false, numberOfLines: 1, synchronousLoad: synchronousLoads) strongSelf.peerNode.frame = CGRect(origin: CGPoint(), size: itemLayout.size) strongSelf.peerNode.updateSelection(selected: item.isPeerSelected(item.peer.id), animated: false) diff --git a/submodules/ICloudResources/BUILD b/submodules/ICloudResources/BUILD index b282708cee..9b4faf0021 100644 --- a/submodules/ICloudResources/BUILD +++ b/submodules/ICloudResources/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/Pdf", diff --git a/submodules/ICloudResources/Sources/ICloudResources.swift b/submodules/ICloudResources/Sources/ICloudResources.swift index 2ef67e5e9d..b13f4dbbd0 100644 --- a/submodules/ICloudResources/Sources/ICloudResources.swift +++ b/submodules/ICloudResources/Sources/ICloudResources.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -13,9 +12,9 @@ public struct ICloudFileResourceId { public var uniqueId: String { if self.thumbnail { - return "icloud-thumb-\(persistentHash32(self.urlData))" + return "icloud-thumb-\(enginePersistentHash32(self.urlData))" } else { - return "icloud-\(persistentHash32(self.urlData))" + return "icloud-\(enginePersistentHash32(self.urlData))" } } @@ -37,21 +36,21 @@ public class ICloudFileResource: TelegramMediaResource { self.thumbnail = thumbnail } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.urlData = decoder.decodeStringForKey("url", orElse: "") self.thumbnail = decoder.decodeBoolForKey("thumb", orElse: false) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.urlData, forKey: "url") encoder.encodeBool(self.thumbnail, forKey: "thumb") } - public var id: MediaResourceId { - return MediaResourceId(ICloudFileResourceId(urlData: self.urlData, thumbnail: self.thumbnail).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(ICloudFileResourceId(urlData: self.urlData, thumbnail: self.thumbnail).uniqueId) } - - public func isEqual(to: MediaResource) -> Bool { + + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? ICloudFileResource { if self.urlData != to.urlData || self.thumbnail != to.thumbnail { return false @@ -68,6 +67,7 @@ public struct ICloudFileDescription { public let title: String? public let performer: String? public let duration: Int + public let hasAudioArtwork: Bool } public let urlData: String @@ -76,6 +76,51 @@ public struct ICloudFileDescription { public let audioMetadata: AudioMetadata? } +private let audioFileExtensions: Set = ["mp3", "m4a", "aac", "flac"] + +private func validatedAudioArtworkData(_ data: Data?) -> Data? { + guard let data, UIImage(data: data) != nil else { + return nil + } + return data +} + +private func audioArtworkData(from metadataItem: AVMetadataItem) -> Data? { + if let data = validatedAudioArtworkData(metadataItem.value(forKey: "dataValue") as? Data) { + return data + } + if let data = metadataItem.value(forKey: "value") as? Data { + return validatedAudioArtworkData(data) + } + if let data = metadataItem.value(forKey: "value") as? NSData { + return validatedAudioArtworkData(data as Data) + } + return nil +} + +private func audioArtworkData(from asset: AVURLAsset) -> Data? { + func firstArtworkData(in metadataItems: [AVMetadataItem]) -> Data? { + for item in metadataItems { + if item.commonKey == AVMetadataKey.commonKeyArtwork, let data = audioArtworkData(from: item) { + return data + } + } + return nil + } + + if let data = firstArtworkData(in: asset.commonMetadata) { + return data + } + + for format in asset.availableMetadataFormats { + if let data = firstArtworkData(in: asset.metadata(forFormat: format)) { + return data + } + } + + return nil +} + private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? { if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { guard url.startAccessingSecurityScopedResource() else { @@ -95,17 +140,50 @@ private func descriptionWithUrl(_ url: URL) -> ICloudFileDescription? { } var audioMetadata: ICloudFileDescription.AudioMetadata? - if ["mp3", "m4a"].contains(url.pathExtension.lowercased()) { + let fileExtension = url.pathExtension.lowercased() + var hasAudioArtwork = false + let audioAsset: AVURLAsset? + if audioFileExtensions.contains(fileExtension) { let asset = AVURLAsset(url: url) + audioAsset = asset + hasAudioArtwork = audioArtworkData(from: asset) != nil + } else { + audioAsset = nil + } + if ["mp3", "m4a"].contains(fileExtension), let asset = audioAsset { let title = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyTitle, keySpace: AVMetadataKeySpace.common).first?.stringValue let performer = AVMetadataItem.metadataItems(from: asset.commonMetadata, withKey: AVMetadataKey.commonKeyArtist, keySpace: AVMetadataKeySpace.common).first?.stringValue let duration = CMTimeGetSeconds(asset.duration) if duration > 0 { - audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration)) + audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration), hasAudioArtwork: hasAudioArtwork) + } + } else if fileExtension == "flac", let asset = audioAsset { + var title: String? + var performer: String? + let vorbisComment = AVMetadataFormat(rawValue: "org.xiph.vorbis-comment") + if asset.availableMetadataFormats.contains(vorbisComment) { + let items = asset.metadata(forFormat: vorbisComment) + for item in items { + if item.commonKey == AVMetadataKey.commonKeyTitle { + title = item.stringValue + } + if item.commonKey == AVMetadataKey.commonKeyArtist { + performer = item.stringValue + } + } + } + let duration = CMTimeGetSeconds(asset.duration) + if duration > 0 { + audioMetadata = ICloudFileDescription.AudioMetadata(title: title, performer: performer, duration: Int(duration), hasAudioArtwork: hasAudioArtwork) } } - let result = ICloudFileDescription(urlData: urlData.base64EncodedString(), fileName: fileName, fileSize: fileSize, audioMetadata: audioMetadata) + let result = ICloudFileDescription( + urlData: urlData.base64EncodedString(), + fileName: fileName, + fileSize: fileSize, + audioMetadata: audioMetadata + ) url.stopAccessingSecurityScopedResource() @@ -173,7 +251,7 @@ public func iCloudFileDescription(_ url: URL) -> Signal Signal { +public func fetchICloudFileResource(resource: ICloudFileResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) @@ -227,13 +305,19 @@ public func fetchICloudFileResource(resource: ICloudFileResource) -> Signal mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var signals: [Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError>] = [] for sticker in strongSelf.stickerPack.stickers { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { - signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource._asResource(), thumbnail: nil, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), duration: nil, mimeType: sticker.mimeType) + signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, thumbnail: nil, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), duration: nil, mimeType: sticker.mimeType) |> map { result -> (UUID, StickerVerificationStatus, EngineMediaResource?) in switch result { case .progress: return (sticker.uuid, .loading, nil) case let .complete(resource, mimeType): if ["application/x-tgsticker", "video/webm"].contains(mimeType) { - return (sticker.uuid, .verified, EngineMediaResource(resource)) + return (sticker.uuid, .verified, resource) } else { return (sticker.uuid, .declined, nil) } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index fe689873f6..31ea269639 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -110,6 +110,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -623,7 +624,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll } if let resource = self.uploadedStickerResources[item.stickerItem.uuid] { if let localResource = item.stickerItem.resource { - self.context.account.postbox.mediaBox.copyResourceData(from: localResource._asResource().id, to: resource._asResource().id) + self.context.engine.resources.copyResourceData(from: localResource.id, to: resource.id) } stickers.append(ImportSticker(resource: .standalone(resource: resource._asResource()), emojis: item.stickerItem.emojis, dimensions: dimensions, duration: nil, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } else if let resource = item.stickerItem.resource { @@ -637,7 +638,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll dimensions = PixelDimensions(image.size) } let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: thumbnail.data) thumbnailSticker = ImportSticker(resource: .standalone(resource: resource), emojis: [], dimensions: dimensions, duration: nil, mimeType: thumbnail.mimeType, keywords: thumbnail.keywords) } @@ -796,7 +797,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll item.resource = resource } else { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: item.data) item.resource = EngineMediaResource(resource) self.stickerResources[item.uuid] = EngineMediaResource(resource) } diff --git a/submodules/InAppPurchaseManager/BUILD b/submodules/InAppPurchaseManager/BUILD index 8cbb34b326..700597a7c3 100644 --- a/submodules/InAppPurchaseManager/BUILD +++ b/submodules/InAppPurchaseManager/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramUIPreferences:TelegramUIPreferences", diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index be9aee83dc..23ea3eb290 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -3,7 +3,6 @@ import CoreLocation import SwiftSignalKit import StoreKit import TelegramCore -import Postbox import TelegramStringFormatting import TelegramUIPreferences import PersistentStringHash @@ -29,6 +28,7 @@ private let productIdentifiers = [ "org.telegram.telegramPremium.twelveMonths.code_x10", "org.telegram.telegramPremium.oneWeek.auth", + "org.telegram.telegramPremium.threeDays.auth", "org.telegram.telegramStars.topup.x15", "org.telegram.telegramStars.topup.x25", @@ -623,9 +623,9 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver { } let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false) - engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData) + engine.resources.storeResourceData(id: EngineMediaResource.Id(fileResource.id), data: receiptData) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: engine.account, peerId: engine.account.peerId, messages: [message]).start() @@ -663,6 +663,7 @@ private final class PendingInAppPurchaseState: Codable { case restore case phoneNumber case phoneCodeHash + case premiumDays } enum PurposeType: Int32 { @@ -687,7 +688,7 @@ private final class PendingInAppPurchaseState: Codable { case stars(count: Int64, peerId: EnginePeer.Id?) case starsGift(peerId: EnginePeer.Id, count: Int64) case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, users: Int32) - case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String) + case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, premiumDays: Int32) public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -749,7 +750,8 @@ private final class PendingInAppPurchaseState: Codable { self = .authCode( restore: try container.decode(Bool.self, forKey: .restore), phoneNumber: try container.decode(String.self, forKey: .phoneNumber), - phoneCodeHash: try container.decode(String.self, forKey: .phoneCodeHash) + phoneCodeHash: try container.decode(String.self, forKey: .phoneCodeHash), + premiumDays: try container.decode(Int32.self, forKey: .premiumDays), ) default: throw DecodingError.generic @@ -805,11 +807,12 @@ private final class PendingInAppPurchaseState: Codable { try container.encode(randomId, forKey: .randomId) try container.encode(untilDate, forKey: .untilDate) try container.encode(users, forKey: .users) - case let .authCode(restore, phoneNumber, phoneCodeHash): + case let .authCode(restore, phoneNumber, phoneCodeHash, premiumDays): try container.encode(PurposeType.authCode.rawValue, forKey: .type) try container.encode(restore, forKey: .restore) try container.encode(phoneNumber, forKey: .phoneNumber) try container.encode(phoneCodeHash, forKey: .phoneCodeHash) + try container.encode(premiumDays, forKey: .premiumDays) } } @@ -833,8 +836,8 @@ private final class PendingInAppPurchaseState: Codable { self = .starsGift(peerId: peerId, count: count) case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _, users): self = .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, users: users) - case let .authCode(restore, phoneNumber, phoneCodeHash, _, _): - self = .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash) + case let .authCode(restore, phoneNumber, phoneCodeHash, premiumDays, _, _): + self = .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, premiumDays: premiumDays) } } @@ -859,8 +862,8 @@ private final class PendingInAppPurchaseState: Codable { return .starsGift(peerId: peerId, count: count, currency: currency, amount: amount) case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, users): return .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users) - case let .authCode(restore, phoneNumber, phoneCodeHash): - return .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount) + case let .authCode(restore, phoneNumber, phoneCodeHash, premiumDays): + return .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, premiumDays: premiumDays, currency: currency, amount: amount) } } } diff --git a/submodules/InAppPurchaseManager/Sources/Receipt.swift b/submodules/InAppPurchaseManager/Sources/Receipt.swift index fbb5d1b9d4..f16667bcbd 100644 --- a/submodules/InAppPurchaseManager/Sources/Receipt.swift +++ b/submodules/InAppPurchaseManager/Sources/Receipt.swift @@ -16,97 +16,157 @@ private struct Asn1Entry { let length: Int } -private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry { +private func dataByte(_ data: Data, at index: Int) -> UInt8? { + guard index >= 0 && index < data.count else { + return nil + } + return data[index] +} + +private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry? { + guard startIndex >= 0 && startIndex < data.count else { + return nil + } + var index = startIndex - var value = data[index] + guard var value = dataByte(data, at: index) else { + return nil + } index += 1 var tagValue = Int32(value & 0x1f) if tagValue == 31 { - value = data[index] - index += 1 - while (value & 0x80) != 0 { + repeat { + guard let nextValue = dataByte(data, at: index) else { + return nil + } + value = nextValue + index += 1 + guard tagValue <= Int32.max >> 8 else { + return nil + } tagValue <<= 8 tagValue |= Int32(value & 0x7f) - value = data[index] - index += 1 - } - tagValue <<= 8 - tagValue |= Int32(value & 0x7f) + } while (value & 0x80) != 0 } var length = 0 - var nextTag = 0 - value = data[index] + guard let lengthValue = dataByte(data, at: index) else { + return nil + } + value = lengthValue + let isIndefiniteLength = value == 0x80 index += 1 - if value & 0x80 == 0 { + if !isIndefiniteLength && value & 0x80 == 0 { length = Int(value) - nextTag = index + length - } else if value != 0x80 { + } else if !isIndefiniteLength { let octetsCount = Int(value & 0x7f) + guard octetsCount > 0 else { + return nil + } for _ in 0 ..< octetsCount { + guard length <= Int.max >> 8 else { + return nil + } length <<= 8 - value = data[index] + guard let nextValue = dataByte(data, at: index) else { + return nil + } + value = nextValue index += 1 length |= Int(value) & 0xff } - nextTag = index + length } else { var scanIndex = index - while data[scanIndex] != 0 && data[scanIndex + 1] != 0 { + while true { + guard scanIndex + 1 < data.count else { + return nil + } + if data[scanIndex] == 0 && data[scanIndex + 1] == 0 { + break + } scanIndex += 1 } length = scanIndex - index - nextTag = scanIndex + 2 } - return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< (index + length)), length: nextTag - startIndex) + + guard length >= 0, length <= data.count - index else { + return nil + } + + let payloadEndIndex = index + length + let entryEndIndex: Int + if isIndefiniteLength { + entryEndIndex = payloadEndIndex + 2 + guard entryEndIndex <= data.count else { + return nil + } + } else { + entryEndIndex = payloadEndIndex + } + + return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< payloadEndIndex), length: entryEndIndex - startIndex) } -private func parseSequence(_ data: Data) -> [Asn1Entry] { +private func parseSequence(_ data: Data) -> [Asn1Entry]? { var result : [Asn1Entry] = [] var index = 0 while index < data.count { - let entry = parse(data, startIndex: index) + guard let entry = parse(data, startIndex: index), entry.length > 0 else { + return nil + } result.append(entry) index += entry.length } return result } -private func parseInteger(_ data: Data) -> Int32 { - let length = data.count - var value: Int32 = 0 - for i in 0 ..< length { - if i == 0 { - value = Int32(data[i] & 0x7f) - } else { - value <<= 8 - value |= Int32(data[i]) - } +private func parseInteger(_ data: Data) -> Int32? { + guard !data.isEmpty, data.count <= MemoryLayout.size else { + return nil } - if length > 0 && data[0] & 0x80 != 0 { - let complement: Int32 = 1 << (length * 8) - value -= complement + + var value: UInt32 = 0 + for byte in data { + value = (value << 8) | UInt32(byte) } - return value + + if let firstByte = data.first, firstByte & 0x80 != 0 && data.count < MemoryLayout.size { + let shift = UInt32(data.count * 8) + value |= ~UInt32(0) << shift + } + + return Int32(bitPattern: value) } -private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32] { +private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32]? { let dataLen = length ?? data.count + guard startIndex >= 0, dataLen > 0, startIndex <= data.count, dataLen <= data.count - startIndex else { + return nil + } + + let endIndex = startIndex + dataLen var index = startIndex var identifier: [Int32] = [] - while index < startIndex + dataLen { - var subidentifier: Int32 = 0 - var value = data[index] - index += 1 - while (value & 0x80) != 0 { - subidentifier <<= 7 - subidentifier |= Int32(value & 0x7f) - value = data[index] + while index < endIndex { + var subidentifier: Int64 = 0 + while true { + guard let value = dataByte(data, at: index) else { + return nil + } index += 1 + guard subidentifier <= Int64(Int32.max >> 7) else { + return nil + } + subidentifier <<= 7 + subidentifier |= Int64(value & 0x7f) + if (value & 0x80) == 0 { + break + } + guard index < endIndex else { + return nil + } } - subidentifier <<= 7 - subidentifier |= Int32(value & 0x7f) - identifier.append(subidentifier) + identifier.append(Int32(subidentifier)) } return identifier } @@ -137,51 +197,71 @@ struct Receipt { } func parseReceipt(_ data: Data) -> Receipt? { - let root = parseSequence(data) + guard let root = parseSequence(data) else { + return nil + } guard root.count == 1 && root[0].tag == Asn1Tag.sequence else { return nil } - let rootSeq = parseSequence(root[0].data) + guard let rootSeq = parseSequence(root[0].data) else { + return nil + } guard rootSeq.count == 2 && rootSeq[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(rootSeq[0].data) == ObjectIdentifier.pkcs7SignedData else { return nil } - let signedData = parseSequence(rootSeq[1].data) + guard let signedData = parseSequence(rootSeq[1].data) else { + return nil + } guard signedData.count == 1 && signedData[0].tag == Asn1Tag.sequence else { return nil } - let signedDataSeq = parseSequence(signedData[0].data) + guard let signedDataSeq = parseSequence(signedData[0].data) else { + return nil + } guard signedDataSeq.count > 3 && signedDataSeq[2].tag == Asn1Tag.sequence else { return nil } - let contentData = parseSequence(signedDataSeq[2].data) + guard let contentData = parseSequence(signedDataSeq[2].data) else { + return nil + } guard contentData.count == 2 && contentData[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(contentData[0].data) == ObjectIdentifier.pkcs7Data else { return nil } - let payload = parse(contentData[1].data) + guard let payload = parse(contentData[1].data) else { + return nil + } guard payload.tag == Asn1Tag.octetString else { return nil } - let payloadRoot = parse(payload.data) + guard let payloadRoot = parse(payload.data) else { + return nil + } guard payloadRoot.tag == Asn1Tag.set else { return nil } var purchases: [Receipt.Purchase] = [] - let receiptAttributes = parseSequence(payloadRoot.data) + guard let receiptAttributes = parseSequence(payloadRoot.data) else { + return nil + } for attribute in receiptAttributes { if attribute.tag != Asn1Tag.sequence { continue } - let attributeEntries = parseSequence(attribute.data) + guard let attributeEntries = parseSequence(attribute.data) else { + return nil + } guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil } - let type = parseInteger(attributeEntries[0].data) + guard let type = parseInteger(attributeEntries[0].data) else { + return nil + } let value = attributeEntries[2].data switch (type) { case Receipt.Tag.purchases: @@ -202,22 +282,41 @@ private func parseRfc3339Date(_ str: String) -> Date? { formatter1.locale = posixLocale formatter1.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssX5" formatter1.timeZone = TimeZone(secondsFromGMT: 0) - - let result = formatter1.date(from: str) + + var result = formatter1.date(from: str) if result != nil { return result } - + let formatter2 = DateFormatter() formatter2.locale = posixLocale formatter2.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSSSSX5" formatter2.timeZone = TimeZone(secondsFromGMT: 0) + + result = formatter2.date(from: str) + if result != nil { + return result + } - return formatter2.date(from: str) + let formatterWithFractionalSeconds = ISO8601DateFormatter() + formatterWithFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0) + formatterWithFractionalSeconds.formatOptions = [.withInternetDateTime, .withFractionalSeconds] + + if let result = formatterWithFractionalSeconds.date(from: str) { + return result + } + + let formatterWithoutFractionalSeconds = ISO8601DateFormatter() + formatterWithoutFractionalSeconds.timeZone = TimeZone(secondsFromGMT: 0) + formatterWithoutFractionalSeconds.formatOptions = [.withInternetDateTime] + + return formatterWithoutFractionalSeconds.date(from: str) } private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? { - let root = parse(data) + guard let root = parse(data) else { + return nil + } guard root.tag == Asn1Tag.set else { return nil } @@ -226,26 +325,38 @@ private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? { var transactionId: String? var expirationDate: Date? - let receiptAttributes = parseSequence(root.data) + guard let receiptAttributes = parseSequence(root.data) else { + return nil + } for attribute in receiptAttributes { if attribute.tag != Asn1Tag.sequence { continue } - let attributeEntries = parseSequence(attribute.data) + guard let attributeEntries = parseSequence(attribute.data) else { + return nil + } guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil } - let type = parseInteger(attributeEntries[0].data) + guard let type = parseInteger(attributeEntries[0].data) else { + return nil + } let value = attributeEntries[2].data switch (type) { case Receipt.Purchase.Tag.productIdentifier: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.utf8String else { return nil } productId = String(bytes: valEntry.data, encoding: .utf8) case Receipt.Purchase.Tag.transactionIdentifier: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.utf8String else { return nil } transactionId = String(bytes: valEntry.data, encoding: .utf8) case Receipt.Purchase.Tag.expirationDate: - let valEntry = parse(value) + guard let valEntry = parse(value) else { + return nil + } guard valEntry.tag == Asn1Tag.date else { return nil } expirationDate = parseRfc3339Date(String(bytes: valEntry.data, encoding: .utf8) ?? "") default: diff --git a/submodules/InstantPageCache/BUILD b/submodules/InstantPageCache/BUILD index 0b2afced14..fa64e1b83f 100644 --- a/submodules/InstantPageCache/BUILD +++ b/submodules/InstantPageCache/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/PersistentStringHash:PersistentStringHash", diff --git a/submodules/InstantPageCache/Sources/CachedInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInstantPages.swift index 182e3d47bf..a263ad1cf6 100644 --- a/submodules/InstantPageCache/Sources/CachedInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInstantPages.swift @@ -2,40 +2,39 @@ import Foundation import UIKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramUIPreferences import PersistentStringHash public final class CachedInstantPage: Codable { public let webPage: TelegramMediaWebpage public let timestamp: Int32 - + public init(webPage: TelegramMediaWebpage, timestamp: Int32) { self.webPage = webPage self.timestamp = timestamp } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) - let webPageData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "webpage") - self.webPage = TelegramMediaWebpage(decoder: PostboxDecoder(buffer: MemoryBuffer(data: webPageData.data))) + let webPageData = try container.decode(EngineAdaptedPostboxDecoder.RawObjectData.self, forKey: "webpage") + self.webPage = TelegramMediaWebpage(decoder: EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: webPageData.data))) self.timestamp = try container.decode(Int32.self, forKey: "timestamp") } - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) - try container.encode(PostboxEncoder().encodeObjectToRawData(self.webPage), forKey: "webpage") + try container.encode(EnginePostboxEncoder().encodeObjectToRawData(self.webPage), forKey: "webpage") try container.encode(self.timestamp, forKey: "timestamp") } } public func cachedInstantPage(engine: TelegramEngine, url: String) -> Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: Int64(bitPattern: url.persistentHashValue)) - + return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.cachedInstantPages, id: key)) |> map { entry -> CachedInstantPage? in return entry?.get(CachedInstantPage.self) @@ -43,7 +42,7 @@ public func cachedInstantPage(engine: TelegramEngine, url: String) -> Signal Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: Int64(bitPattern: url.persistentHashValue)) if let webPage = webPage { diff --git a/submodules/InstantPageUI/BUILD b/submodules/InstantPageUI/BUILD index 52f8e2ec5f..feb1d67ef6 100644 --- a/submodules/InstantPageUI/BUILD +++ b/submodules/InstantPageUI/BUILD @@ -12,13 +12,20 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/CheckNode:CheckNode", "//submodules/Display:Display", "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/TextFormat:TextFormat", + "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/GalleryUI:GalleryUI", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", + "//submodules/SemanticStatusNode:SemanticStatusNode", "//submodules/LiveLocationPositionNode:LiveLocationPositionNode", "//submodules/MosaicLayout:MosaicLayout", "//submodules/LocationUI:LocationUI", @@ -26,7 +33,12 @@ swift_library( "//submodules/LocationResources:LocationResources", "//submodules/UndoUI:UndoUI", "//submodules/TranslateUI:TranslateUI", + "//submodules/TelegramUI/Components/TextProcessingScreen", + "//submodules/Pasteboard", "//submodules/Tuples:Tuples", + "//third-party/SwiftMath:SwiftMath", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/TelegramUI/Components/ShimmeringMask:ShimmeringMask", ], visibility = [ "//visibility:public", diff --git a/submodules/InstantPageUI/Sources/InstantImageGalleryItem.swift b/submodules/InstantPageUI/Sources/InstantImageGalleryItem.swift index 1ed0820c7b..e81269ba8b 100644 --- a/submodules/InstantPageUI/Sources/InstantImageGalleryItem.swift +++ b/submodules/InstantPageUI/Sources/InstantImageGalleryItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -183,7 +182,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { }) } else { self.imageNode.setSignal(chatMessagePhoto(postbox: self.context.account.postbox, userLocation: userLocation, photoReference: imageReference), dispatchOnDisplayLink: false) - self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .image, reference: imageReference.resourceReference(largestSize.resource)).start()) + self.fetchDisposable.set(self.context.engine.resources.fetch(reference: imageReference.resourceReference(largestSize.resource), userLocation: userLocation, userContentType: .image).start()) } } else { self._ready.set(.single(Void())) @@ -248,7 +247,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame { - surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in + surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedSurfaceFinalFrame.midX, y: transformedSurfaceFinalFrame.midY), duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in surfaceCopyView?.removeFromSuperview() }) let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height) @@ -339,7 +338,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode { if let (context, media) = self.contextAndMedia, let fileReference = media.concrete(TelegramMediaFile.self) { if isVisible { - self.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: self.userLocation ?? .other, userContentType: .file, reference: fileReference.resourceReference(fileReference.media.resource)).start()) + self.fetchDisposable.set(context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: self.userLocation ?? .other, userContentType: .file).start()) } else { self.fetchDisposable.set(nil) } diff --git a/submodules/InstantPageUI/Sources/InstantPageAnchorPath.swift b/submodules/InstantPageUI/Sources/InstantPageAnchorPath.swift new file mode 100644 index 0000000000..ccb453ae50 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageAnchorPath.swift @@ -0,0 +1,155 @@ +import Foundation +import TelegramCore + +/// Recurses the `InstantPage` block tree to locate the anchor `name`, returning the +/// details-sibling-ordinal path of enclosing `
` blocks (outermost first). +/// +/// - `nil` — the anchor exists nowhere. +/// - `[]` — the anchor exists outside any `
` (top level, a table cell, a quote, etc.). +/// - `[2,0]`— inside the 3rd top-level `
`, then that details' 1st nested `
`. +/// +/// Ordinals (not layout indices) because the layout's details index counter is +/// expansion-dependent. Consumers map ordinals to live indices via +/// `InstantPageV2View.firstCollapsedDetails(forOrdinalPath:)`. +public func instantPageAnchorPath(in instantPage: InstantPage, name: String) -> [Int]? { + var ordinal = 0 + return instantPageAnchorPathSearch(instantPage.blocks, name: name, detailsOrdinal: &ordinal) +} + +/// Searches `blocks` at a single details-nesting level. `detailsOrdinal` is the running count of +/// `
` blocks already passed at this level; container blocks that flatten in the layout +/// (`.blockQuote`/`.list`/`.cover`/`.postEmbed`) recurse sharing this counter, while a `
` +/// recurses with a fresh counter (a new level). +private func instantPageAnchorPathSearch( + _ blocks: [InstantPageBlock], + name: String, + detailsOrdinal: inout Int +) -> [Int]? { + for block in blocks { + switch block { + case let .anchor(anchorName): + if anchorName == name { return [] } + case let .title(text), let .subtitle(text), let .header(text), let .subheader(text), + let .paragraph(text), let .footer(text), let .kicker(text), let .thinking(text): + if richTextContainsAnchor(text, name: name) { return [] } + case let .heading(text, _): + if richTextContainsAnchor(text, name: name) { return [] } + case let .authorDate(author, _): + if richTextContainsAnchor(author, name: name) { return [] } + case let .preformatted(text, _): + if richTextContainsAnchor(text, name: name) { return [] } + case let .pullQuote(text, caption): + if richTextContainsAnchor(text, name: name) || richTextContainsAnchor(caption, name: name) { return [] } + case let .details(title, childBlocks, _): + if richTextContainsAnchor(title, name: name) { return [] } // title is always laid out + var childOrdinal = 0 + if let sub = instantPageAnchorPathSearch(childBlocks, name: name, detailsOrdinal: &childOrdinal) { + return [detailsOrdinal] + sub + } + detailsOrdinal += 1 + case let .blockQuote(quoteBlocks, caption): + if richTextContainsAnchor(caption, name: name) { return [] } + if let r = instantPageAnchorPathSearch(quoteBlocks, name: name, detailsOrdinal: &detailsOrdinal) { return r } + case let .list(items, _): + for listItem in items { + switch listItem { + case let .text(text, _, _): + if richTextContainsAnchor(text, name: name) { return [] } + case let .blocks(itemBlocks, _, _): + if let r = instantPageAnchorPathSearch(itemBlocks, name: name, detailsOrdinal: &detailsOrdinal) { return r } + case .unknown: + break + } + } + case let .cover(inner): + if let r = instantPageAnchorPathSearch([inner], name: name, detailsOrdinal: &detailsOrdinal) { return r } + case let .table(title, rows, _, _): + if richTextContainsAnchor(title, name: name) { return [] } + for row in rows { + for cell in row.cells { + if let cellText = cell.text, richTextContainsAnchor(cellText, name: name) { return [] } + } + } + default: + // .unsupported/.divider/.formula/.image/.video/.audio/.webEmbed/.channelBanner/.map — + // leaf/media blocks with no anchor-bearing text. (.relatedArticles also lands here: the + // V2 layout discards its title and lays out only the article media, so its title text is + // never rendered.) + // + // CRITICAL — the recursion set here must match the containers the V2 layout recurses + // through layoutBlock (and thus counts
in via detailsIndexCounter). Those are + // exactly .blockQuote, .cover, and .list's .blocks items — all handled above, sharing + // detailsOrdinal. The following carry [InstantPageBlock] children but are deliberately + // NOT recursed because the V2 layout does NOT lay their children out as blocks, so it + // never counts a nested
in them — recursing here while sharing detailsOrdinal + // would desync our ordinals from the layout: + // • .collage/.slideshow — layoutCollage/layoutSlideshow lay out only .image/.video children. + // • .postEmbed — layoutMediaWithCaption lays out only its caption (a real .text item, + // so a caption anchor is found by anchorFrame directly); its `blocks` are ignored. + // Any anchor inside a non-laid-out child is unresolvable by anchorFrame anyway, so + // skipping it here is a no-op either way. + break + } + } + return nil +} + +/// True if the `RichText` tree contains an inline `.anchor` whose name equals `name`. +private func richTextContainsAnchor(_ text: RichText, name: String) -> Bool { + switch text { + case .empty, .plain, .image, .formula, .textCustomEmoji: + return false + case let .anchor(inner, anchorName): + if anchorName == name { return true } + return richTextContainsAnchor(inner, name: name) + case let .concat(parts): + for part in parts { + if richTextContainsAnchor(part, name: name) { return true } + } + return false + case let .bold(inner): + return richTextContainsAnchor(inner, name: name) + case let .italic(inner): + return richTextContainsAnchor(inner, name: name) + case let .underline(inner): + return richTextContainsAnchor(inner, name: name) + case let .strikethrough(inner): + return richTextContainsAnchor(inner, name: name) + case let .fixed(inner): + return richTextContainsAnchor(inner, name: name) + case let .marked(inner): + return richTextContainsAnchor(inner, name: name) + case let .`subscript`(inner): + return richTextContainsAnchor(inner, name: name) + case let .superscript(inner): + return richTextContainsAnchor(inner, name: name) + case let .textAutoEmail(inner): + return richTextContainsAnchor(inner, name: name) + case let .textAutoPhone(inner): + return richTextContainsAnchor(inner, name: name) + case let .textAutoUrl(inner): + return richTextContainsAnchor(inner, name: name) + case let .textBankCard(inner): + return richTextContainsAnchor(inner, name: name) + case let .textBotCommand(inner): + return richTextContainsAnchor(inner, name: name) + case let .textCashtag(inner): + return richTextContainsAnchor(inner, name: name) + case let .textHashtag(inner): + return richTextContainsAnchor(inner, name: name) + case let .textMention(inner): + return richTextContainsAnchor(inner, name: name) + case let .textSpoiler(inner): + return richTextContainsAnchor(inner, name: name) + case let .url(inner, _, _): + return richTextContainsAnchor(inner, name: name) + case let .email(inner, _): + return richTextContainsAnchor(inner, name: name) + case let .phone(inner, _): + return richTextContainsAnchor(inner, name: name) + case let .textMentionName(inner, _): + return richTextContainsAnchor(inner, name: name) + case let .textDate(inner, _, _): + return richTextContainsAnchor(inner, name: name) + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift index f007db8530..1085daf8e4 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift @@ -24,7 +24,7 @@ public final class InstantPageAudioItem: InstantPageItem { } public func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? { - return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, openMedia: openMedia) + return InstantPageAudioNode(context: context, strings: strings, theme: theme, webPage: self.webpage, media: self.media, playlistId: .instantPage(webpageId: self.webpage.webpageId), openMedia: openMedia) } public func matchesAnchor(_ anchor: String) -> Bool { diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift index 4da50a07c0..573281233d 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift @@ -66,7 +66,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { private var playImage: UIImage private var pauseImage: UIImage - private let buttonNode: HighlightableButtonNode + private let buttonView: UIView private let statusNode: RadialStatusNode private let titleNode: ASTextNode private let scrubbingNode: MediaPlayerScrubbingNode @@ -76,7 +76,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { private var isPlaying: Bool = false private var playbackState: SharedMediaPlayerItemPlaybackState? - init(context: AccountContext, strings: PresentationStrings, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, openMedia: @escaping (InstantPageMedia) -> Void) { + init(context: AccountContext, strings: PresentationStrings, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, playlistId: InstantPageMediaPlaylistId, openMedia: @escaping (InstantPageMedia) -> Void) { self.context = context self.strings = strings self.theme = theme @@ -86,7 +86,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { self.playImage = generatePlayButton(color: theme.textCategories.paragraph.color)! self.pauseImage = generatePauseButton(color: theme.textCategories.paragraph.color)! - self.buttonNode = HighlightableButtonNode() + self.buttonView = UIView() self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) self.titleNode = ASTextNode() self.titleNode.maximumNumberOfLines = 1 @@ -112,25 +112,11 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { self.titleNode.attributedText = titleString(media: media, theme: theme, strings: strings) self.addSubnode(self.statusNode) - self.addSubnode(self.buttonNode) self.addSubnode(self.titleNode) self.addSubnode(self.scrubbingNode) - + self.statusNode.transitionToState(RadialStatusNodeState.customIcon(self.playImage), animated: false, completion: {}) - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.statusNode.layer.removeAnimation(forKey: "opacity") - strongSelf.statusNode.alpha = 0.4 - } else { - strongSelf.statusNode.alpha = 1.0 - strongSelf.statusNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.scrubbingNode.seek = { [weak self] timestamp in if let strongSelf = self { if let _ = strongSelf.playbackState { @@ -178,12 +164,12 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { } })*/ - self.scrubbingNode.status = context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: InstantPageMediaPlaylistId(webpageId: webPage.webpageId), itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: self.playlistType) + self.scrubbingNode.status = context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: self.playlistType) |> map { playbackState -> MediaPlayerStatus in return playbackState?.status ?? MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) } - self.playerStatusDisposable = (context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: InstantPageMediaPlaylistId(webpageId: webPage.webpageId), itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: playlistType) + self.playerStatusDisposable = (context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: InstantPageMediaPlaylistItemId(index: self.media.index), type: playlistType) |> deliverOnMainQueue).start(next: { [weak self] playbackState in guard let strongSelf = self else { return @@ -213,7 +199,21 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { deinit { self.playerStatusDisposable?.dispose() } - + + override func didLoad() { + super.didLoad() + // The play/pause tap target is a plain view + UITapGestureRecognizer, NOT an ASControl + // button. An ASControl's `.touchUpInside` is cancelled by the chat ListView's gesture + // system (the control highlights on touch-down, but the action never fires), so an + // embedded audio control in a rich-message bubble could never start playback. A gesture + // recognizer coordinates with the list's gestures and fires reliably — matching the V2 + // image node, the details-title hit view, and the regular file/music message. The plain + // view sits above `statusNode` and is positioned over the icon in `layout()`. (Works in + // V1's full-page Instant View too; gesture recognizers fire inside its scroll view.) + self.view.addSubview(self.buttonView) + self.buttonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.buttonPressed))) + } + func update(strings: PresentationStrings, theme: InstantPageTheme) { if self.strings !== strings || self.theme !== theme { let themeUpdated = self.theme !== theme @@ -268,7 +268,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { let titleSize = self.titleNode.measure(CGSize(width: maxTitleWidth, height: size.height)) self.titleNode.frame = CGRect(origin: CGPoint(x: insets.left + leftInset, y: 2.0), size: titleSize) - self.buttonNode.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: CGSize(width: 48.0, height: 48.0)) + self.buttonView.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: CGSize(width: 48.0, height: 48.0)) self.statusNode.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: CGSize(width: 48.0, height: 48.0)) var topOffset: CGFloat = 0.0 diff --git a/submodules/InstantPageUI/Sources/InstantPageChecklistMarkerItem.swift b/submodules/InstantPageUI/Sources/InstantPageChecklistMarkerItem.swift new file mode 100644 index 0000000000..ea145d81a2 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageChecklistMarkerItem.swift @@ -0,0 +1,101 @@ +import Foundation +import UIKit +import TelegramCore +import AsyncDisplayKit +import Display +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import ContextUI +import CheckNode + +final class InstantPageChecklistMarkerItem: InstantPageItem { + var frame: CGRect + let checked: Bool + + let wantsNode: Bool = true + let separatesTiles: Bool = false + let medias: [InstantPageMedia] = [] + + init(frame: CGRect, checked: Bool) { + self.frame = frame + self.checked = checked + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func drawInTile(context: CGContext) { + } + + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? { + return InstantPageChecklistMarkerNode(theme: theme, checked: self.checked) + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if let node = node as? InstantPageChecklistMarkerNode { + return node.checked == self.checked + } else { + return false + } + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } +} + +private func instantPageChecklistMarkerTheme(theme: InstantPageTheme) -> CheckNodeTheme { + return CheckNodeTheme( + backgroundColor: theme.panelAccentColor, + strokeColor: theme.pageBackgroundColor, + borderColor: theme.controlColor, + overlayBorder: false, + hasInset: false, + hasShadow: false + ) +} + +final class InstantPageChecklistMarkerNode: ASDisplayNode, InstantPageNode { + let checked: Bool + private let checkNode: CheckNode + + init(theme: InstantPageTheme, checked: Bool) { + self.checked = checked + self.checkNode = CheckNode(theme: instantPageChecklistMarkerTheme(theme: theme), content: .check(isRectangle: true)) + + super.init() + + self.isUserInteractionEnabled = false + self.checkNode.isUserInteractionEnabled = false + self.addSubnode(self.checkNode) + self.checkNode.setSelected(checked, animated: false) + } + + func updateIsVisible(_ isVisible: Bool) { + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { + self.checkNode.theme = instantPageChecklistMarkerTheme(theme: theme) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.checkNode, frame: CGRect(origin: .zero, size: size)) + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift index 71505c13ee..debdbc8044 100644 --- a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift @@ -9,7 +9,7 @@ import TelegramUIPreferences import AccountContext import ContextUI -public final class InstantPageContentNode : ASDisplayNode { +public final class InstantPageContentNode : ASDisplayNode, InstantPageExternalMediaDimensionsNode { private let context: AccountContext private let strings: PresentationStrings private let nameDisplayOrder: PresentationPersonNameOrder @@ -29,13 +29,20 @@ public final class InstantPageContentNode : ASDisplayNode { var distanceThresholdGroupCount: [Int: Int] = [:] var visibleTiles: [Int: InstantPageTileNode] = [:] - var visibleItemsWithNodes: [Int: InstantPageNode] = [:] + public var visibleItemsWithNodes: [Int: InstantPageNode] = [:] var currentWebEmbedHeights: [Int : CGFloat] = [:] - var currentExpandedDetails: [Int : Bool]? + public var currentExpandedDetails: [Int : Bool]? var currentDetailsItems: [InstantPageDetailsItem] = [] var requestLayoutUpdate: ((Bool) -> Void)? + public var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? { + didSet { + for (_, itemNode) in self.visibleItemsWithNodes { + self.applyExternalMediaDimensionsUpdater(to: itemNode) + } + } + } var currentLayout: InstantPageLayout let contentSize: CGSize @@ -223,6 +230,7 @@ public final class InstantPageContentNode : ASDisplayNode { topNode = newNode self.visibleItemsWithNodes[itemIndex] = newNode itemNode = newNode + self.applyExternalMediaDimensionsUpdater(to: newNode) if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.requestLayoutUpdate = { [weak self] animated in @@ -303,6 +311,16 @@ public final class InstantPageContentNode : ASDisplayNode { } } + private func applyExternalMediaDimensionsUpdater(to itemNode: InstantPageNode) { + if let itemNode = itemNode as? InstantPageImageNode { + itemNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } else if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.contentNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } else if let itemNode = itemNode as? InstantPageSlideshowNode { + itemNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } + } + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { // let currentHeight = self.currentWebEmbedHeights[index] // if height != currentHeight { diff --git a/submodules/InstantPageUI/Sources/InstantPageController.swift b/submodules/InstantPageUI/Sources/InstantPageController.swift index 716dc6a464..02b499cf30 100644 --- a/submodules/InstantPageUI/Sources/InstantPageController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageController.swift @@ -1,14 +1,13 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import TelegramPresentationData import TelegramUIPreferences import AccountContext -public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? { +public func instantPageAndAnchor(message: EngineMessage) -> (TelegramMediaWebpage, String?)? { for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { if let _ = content.instantPage { diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 9e55514eeb..9236b3aec3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -16,6 +16,8 @@ import LocationUI import UndoUI import ContextUI import TranslateUI +import TextProcessingScreen +import Pasteboard final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { private weak var controller: InstantPageController? @@ -65,13 +67,14 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { var currentWebEmbedHeights: [Int : CGFloat] = [:] var currentExpandedDetails: [Int : Bool]? var currentDetailsItems: [InstantPageDetailsItem] = [] + private var resolvedExternalMediaDimensions: [MediaId: PixelDimensions] = [:] + private var pendingResolvedExternalMediaDimensions = Set() var currentAccessibilityAreas: [AccessibilityAreaNode] = [] private var previousContentOffset: CGPoint? private var isDeceleratingBecauseOfDragging = false - private let hiddenMediaDisposable = MetaDisposable() private let resolveUrlDisposable = MetaDisposable() private let loadWebpageDisposable = MetaDisposable() @@ -79,6 +82,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { private let loadProgressDisposable = MetaDisposable() private let updateLayoutDisposable = MetaDisposable() + private let updateExternalMediaDimensionsDisposable = MetaDisposable() private var themeReferenceDate: Date? @@ -137,6 +141,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { self.addSubnode(self.navigationBar) self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false self.navigationBar.back = navigateBack self.navigationBar.share = { [weak self] in @@ -220,10 +225,10 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } deinit { - self.hiddenMediaDisposable.dispose() self.resolveUrlDisposable.dispose() self.loadWebpageDisposable.dispose() self.loadProgressDisposable.dispose() + self.updateExternalMediaDimensionsDisposable.dispose() } func update(settings: InstantPagePresentationSettings, themeSettings: PresentationThemeSettings?, strings: PresentationStrings) { @@ -252,7 +257,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { updateLayout = true animated = true } - if previousSettings.fontSize != settings.fontSize || previousSettings.forceSerif != settings.forceSerif { + if previousSettings.fontSize != settings.fontSize || previousSettings.lineSpacingFactor != settings.lineSpacingFactor || previousSettings.forceSerif != settings.forceSerif { animated = false updateLayout = true } @@ -362,6 +367,8 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { self.webPage = nil } + self.resolvedExternalMediaDimensions.removeAll() + self.pendingResolvedExternalMediaDimensions.removeAll() if let anchor = anchor { self.initialAnchor = anchor.removingPercentEncoding } else if let state = state { @@ -465,16 +472,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } private func updateLayout() { - guard let containerLayout = self.containerLayout, let (webPage, instantPage) = self.webPage, let theme = self.theme else { + guard let containerLayout = self.containerLayout, let (webPage, instantPage) = self.resolvedWebPage(), let theme = self.theme else { return } - let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) - - for (_, tileNode) in self.visibleTiles { - tileNode.removeFromSupernode() - } - self.visibleTiles.removeAll() + let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, sideInset: 17.0, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights) let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: containerLayout.size.width) @@ -665,6 +667,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { topNode = newNode self.visibleItemsWithNodes[itemIndex] = newNode itemNode = newNode + self.configureExternalMediaDimensionsUpdates(for: newNode) if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.requestLayoutUpdate = { [weak self] animated in @@ -688,6 +691,10 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } } + if let itemNode = itemNode { + self.configureExternalMediaDimensionsUpdates(for: itemNode) + } + if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated) } @@ -718,8 +725,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { topNode = tileNode self.visibleTiles[tileIndex] = tileNode } else { - if visibleTiles[tileIndex]!.frame != tileFrame { - transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame) + if let tileNode = self.visibleTiles[tileIndex] { + tileNode.update(tile: tile, backgroundColor: theme.pageBackgroundColor) + if tileNode.frame != tileFrame { + transition.updateFrame(node: tileNode, frame: tileFrame) + } } } } @@ -1020,16 +1030,398 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { return nil } + private func configureExternalMediaDimensionsUpdates(for itemNode: InstantPageNode) { + let update: (MediaId, PixelDimensions) -> Void = { [weak self] mediaId, dimensions in + self?.updateExternalMediaDimensions(mediaId, dimensions) + } + if let itemNode = itemNode as? InstantPageExternalMediaDimensionsNode { + itemNode.updateExternalMediaDimensions = update + } + if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.contentNode.updateExternalMediaDimensions = update + } + } + + private func updateExternalMediaDimensions(_ mediaId: MediaId, _ dimensions: PixelDimensions) { + if self.resolvedExternalMediaDimensions[mediaId] == dimensions { + return + } + self.resolvedExternalMediaDimensions[mediaId] = dimensions + self.pendingResolvedExternalMediaDimensions.insert(mediaId) + + let signal: Signal = (.complete() |> delay(0.08, queue: Queue.mainQueue())) + self.updateExternalMediaDimensionsDisposable.set(signal.start(completed: { [weak self] in + self?.relayoutForResolvedExternalMediaDimensions() + })) + } + + private func relayoutForResolvedExternalMediaDimensions() { + guard !self.pendingResolvedExternalMediaDimensions.isEmpty else { + return + } + + let mediaIds = Array(self.pendingResolvedExternalMediaDimensions) + self.pendingResolvedExternalMediaDimensions.removeAll() + + let detailsStateMaps = self.captureExpandedDetailsStateMaps() + let viewportTop = self.scrollNode.view.contentOffset.y + self.scrollNode.view.contentInset.top + var oldFrames: [MediaId: CGRect] = [:] + for mediaId in mediaIds { + if let frame = self.effectiveFrameForMedia(mediaId, detailsStateMaps: detailsStateMaps) { + oldFrames[mediaId] = frame + } + } + + self.updateLayout() + + var newFrames: [MediaId: CGRect] = [:] + for mediaId in mediaIds { + if let frame = self.effectiveFrameForMedia(mediaId, detailsStateMaps: detailsStateMaps) { + newFrames[mediaId] = frame + } + } + + if let compensatedViewportTop = self.compensatedViewportTop(oldFrames: oldFrames, newFrames: newFrames, viewportTop: viewportTop) { + self.setViewportTop(compensatedViewportTop) + } + self.updateVisibleItems(visibleBounds: self.scrollNode.view.bounds) + } + + private func setViewportTop(_ viewportTop: CGFloat) { + let scrollView = self.scrollNode.view + let minOffsetY = -scrollView.contentInset.top + let maxOffsetY = max(minOffsetY, scrollView.contentSize.height - scrollView.bounds.height + scrollView.contentInset.bottom) + let contentOffsetY = min(max(viewportTop - scrollView.contentInset.top, minOffsetY), maxOffsetY) + if contentOffsetY.isFinite { + let contentOffset = CGPoint(x: scrollView.contentOffset.x, y: contentOffsetY) + scrollView.contentOffset = contentOffset + self.previousContentOffset = contentOffset + self.updateNavigationBar() + } + } + + private func compensatedViewportTop(oldFrames: [MediaId: CGRect], newFrames: [MediaId: CGRect], viewportTop: CGFloat) -> CGFloat? { + var pairedFrames: [(old: CGRect, new: CGRect)] = [] + for (mediaId, oldFrame) in oldFrames { + if let newFrame = newFrames[mediaId] { + pairedFrames.append((oldFrame, newFrame)) + } + } + if pairedFrames.isEmpty { + return nil + } + + if let intersecting = pairedFrames + .filter({ $0.old.height > 0.0 && $0.new.height > 0.0 && viewportTop > $0.old.minY && viewportTop < $0.old.maxY }) + .max(by: { $0.old.minY < $1.old.minY }) { + let ratio = min(max((viewportTop - intersecting.old.minY) / intersecting.old.height, 0.0), 1.0) + return intersecting.new.minY + ratio * intersecting.new.height + } + + if let above = pairedFrames + .filter({ viewportTop >= $0.old.maxY }) + .max(by: { $0.old.maxY < $1.old.maxY }) { + return viewportTop + (above.new.maxY - above.old.maxY) + } + + return nil + } + + private func captureExpandedDetailsStateMaps() -> [String: [Int: Bool]] { + guard let currentLayout = self.currentLayout else { + return [:] + } + var result: [String: [Int: Bool]] = [:] + self.captureExpandedDetailsStateMaps(items: currentLayout.items, visibleItemsWithNodes: self.visibleItemsWithNodes, path: [], result: &result) + return result + } + + private func captureExpandedDetailsStateMaps(items: [InstantPageItem], visibleItemsWithNodes: [Int: InstantPageNode], path: [Int], result: inout [String: [Int: Bool]]) { + let detailsNodes = visibleItemsWithNodes.compactMap { $0.value as? InstantPageDetailsNode } + + var detailsIndex = -1 + for item in items { + guard let detailsItem = item as? InstantPageDetailsItem else { + continue + } + detailsIndex += 1 + + guard let detailsNode = detailsNodes.first(where: { $0.item === detailsItem }) else { + continue + } + let nextPath = path + [detailsIndex] + result[self.detailsStateKey(nextPath)] = detailsNode.contentNode.currentExpandedDetails ?? [:] + self.captureExpandedDetailsStateMaps(items: detailsItem.items, visibleItemsWithNodes: detailsNode.contentNode.visibleItemsWithNodes, path: nextPath, result: &result) + } + } + + private func detailsStateKey(_ path: [Int]) -> String { + if path.isEmpty { + return "" + } + return path.map(String.init).joined(separator: ".") + } + + private func effectiveFrameForMedia(_ mediaId: MediaId, detailsStateMaps: [String: [Int: Bool]]) -> CGRect? { + guard let currentLayout = self.currentLayout else { + return nil + } + return self.effectiveFrameForMedia(mediaId, items: currentLayout.items, origin: .zero, expandedDetails: self.currentExpandedDetails, path: [], detailsStateMaps: detailsStateMaps) + } + + private func effectiveFrameForMedia(_ mediaId: MediaId, items: [InstantPageItem], origin: CGPoint, expandedDetails: [Int: Bool]?, path: [Int], detailsStateMaps: [String: [Int: Bool]]) -> CGRect? { + var collapseOffset: CGFloat = 0.0 + var detailsIndex = -1 + + for item in items { + if item is InstantPageDetailsItem { + detailsIndex += 1 + } + + var itemFrame = item.frame.offsetBy(dx: origin.x, dy: origin.y - collapseOffset) + if let detailsItem = item as? InstantPageDetailsItem { + let nextPath = path + [detailsIndex] + let nestedExpandedDetails = detailsStateMaps[self.detailsStateKey(nextPath)] + let expanded = expandedDetails?[detailsIndex] ?? detailsItem.initiallyExpanded + let height = expanded ? detailsItem.titleHeight + self.effectiveContentHeight(items: detailsItem.items, baseHeight: detailsItem.frame.height - detailsItem.titleHeight, expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) : detailsItem.titleHeight + collapseOffset += item.frame.height - height + itemFrame.size.height = height + + if expanded, let nestedFrame = self.effectiveFrameForMedia(mediaId, items: detailsItem.items, origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY + detailsItem.titleHeight), expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) { + return nestedFrame + } + continue + } + + if self.itemContainsMedia(item, mediaId: mediaId) { + return itemFrame + } + } + + return nil + } + + private func effectiveContentHeight(items: [InstantPageItem], baseHeight: CGFloat, expandedDetails: [Int: Bool]?, path: [Int], detailsStateMaps: [String: [Int: Bool]]) -> CGFloat { + var contentHeight = baseHeight + var detailsIndex = -1 + + for item in items { + guard let detailsItem = item as? InstantPageDetailsItem else { + continue + } + detailsIndex += 1 + + let nextPath = path + [detailsIndex] + let nestedExpandedDetails = detailsStateMaps[self.detailsStateKey(nextPath)] + let expanded = expandedDetails?[detailsIndex] ?? detailsItem.initiallyExpanded + let height = expanded ? detailsItem.titleHeight + self.effectiveContentHeight(items: detailsItem.items, baseHeight: detailsItem.frame.height - detailsItem.titleHeight, expandedDetails: nestedExpandedDetails, path: nextPath, detailsStateMaps: detailsStateMaps) : detailsItem.titleHeight + contentHeight += -detailsItem.frame.height + height + } + + return contentHeight + } + + private func itemContainsMedia(_ item: InstantPageItem, mediaId: MediaId) -> Bool { + for media in item.medias { + if media.media.id == mediaId { + return true + } + } + return false + } + + private func resolvedWebPage() -> (webPage: TelegramMediaWebpage, instantPage: InstantPage?)? { + guard let (webPage, instantPage) = self.webPage else { + return nil + } + guard !self.resolvedExternalMediaDimensions.isEmpty, case let .Loaded(content) = webPage.content else { + return (webPage, instantPage) + } + + var instantPageUpdated = false + var effectiveInstantPage = instantPage + if let instantPage { + var media = instantPage.media + for (mediaId, currentMedia) in instantPage.media { + if let updatedMedia = self.updatedMediaIfNeeded(currentMedia) { + media[mediaId] = updatedMedia + instantPageUpdated = true + } + } + if instantPageUpdated { + effectiveInstantPage = InstantPage(blocks: instantPage.blocks, media: media, isComplete: instantPage.isComplete, rtl: instantPage.rtl, url: instantPage.url, views: instantPage.views) + } + } + + var imageUpdated = false + let effectiveImage = content.image.map { image -> TelegramMediaImage in + if let updated = self.updatedImageIfNeeded(image) { + imageUpdated = true + return updated + } else { + return image + } + } + + var fileUpdated = false + let effectiveFile = content.file.map { file -> TelegramMediaFile in + if let updated = self.updatedFileIfNeeded(file) { + fileUpdated = true + return updated + } else { + return file + } + } + + if !instantPageUpdated && !imageUpdated && !fileUpdated { + return (webPage, instantPage) + } + + let effectiveContent = TelegramMediaWebpageLoadedContent( + url: content.url, + displayUrl: content.displayUrl, + hash: content.hash, + type: content.type, + websiteName: content.websiteName, + title: content.title, + text: content.text, + embedUrl: content.embedUrl, + embedType: content.embedType, + embedSize: content.embedSize, + duration: content.duration, + author: content.author, + isMediaLargeByDefault: content.isMediaLargeByDefault, + imageIsVideoCover: content.imageIsVideoCover, + image: effectiveImage, + file: effectiveFile, + story: content.story, + attributes: content.attributes, + instantPage: effectiveInstantPage + ) + return (TelegramMediaWebpage(webpageId: webPage.webpageId, content: .Loaded(effectiveContent)), effectiveInstantPage) + } + + private func updatedMediaIfNeeded(_ media: Media) -> Media? { + if let image = media as? TelegramMediaImage { + return self.updatedImageIfNeeded(image) + } else if let file = media as? TelegramMediaFile { + return self.updatedFileIfNeeded(file) + } else { + return nil + } + } + + private func updatedImageIfNeeded(_ image: TelegramMediaImage) -> TelegramMediaImage? { + guard let dimensions = self.resolvedExternalMediaDimensions[image.imageId] else { + return nil + } + + var updatedRepresentations = image.representations + var didUpdate = false + for i in 0 ..< updatedRepresentations.count { + let representation = updatedRepresentations[i] + guard representation.resource is InstantPageExternalMediaResource, representation.dimensions != dimensions else { + continue + } + updatedRepresentations[i] = TelegramMediaImageRepresentation( + dimensions: dimensions, + resource: representation.resource, + progressiveSizes: representation.progressiveSizes, + immediateThumbnailData: representation.immediateThumbnailData, + hasVideo: representation.hasVideo, + isPersonal: representation.isPersonal, + typeHint: representation.typeHint + ) + didUpdate = true + } + + guard didUpdate else { + return nil + } + return TelegramMediaImage( + imageId: image.imageId, + representations: updatedRepresentations, + videoRepresentations: image.videoRepresentations, + immediateThumbnailData: image.immediateThumbnailData, + emojiMarkup: image.emojiMarkup, + reference: image.reference, + partialReference: image.partialReference, + flags: image.flags, + video: image.video + ) + } + + private func updatedFileIfNeeded(_ file: TelegramMediaFile) -> TelegramMediaFile? { + guard let dimensions = self.resolvedExternalMediaDimensions[file.fileId], file.resource is InstantPageExternalMediaResource else { + return nil + } + + let (attributes, didUpdate) = self.fileAttributesWithResolvedDimensions(file.attributes, dimensions: dimensions) + guard didUpdate else { + return nil + } + + return TelegramMediaFile( + fileId: file.fileId, + partialReference: file.partialReference, + resource: file.resource, + previewRepresentations: file.previewRepresentations, + videoThumbnails: file.videoThumbnails, + videoCover: file.videoCover, + immediateThumbnailData: file.immediateThumbnailData, + mimeType: file.mimeType, + size: file.size, + attributes: attributes, + alternativeRepresentations: file.alternativeRepresentations + ) + } + + private func fileAttributesWithResolvedDimensions(_ attributes: [TelegramMediaFileAttribute], dimensions: PixelDimensions) -> ([TelegramMediaFileAttribute], Bool) { + var updatedAttributes: [TelegramMediaFileAttribute] = [] + var didUpdate = false + var hasSizeAttribute = false + + for attribute in attributes { + switch attribute { + case let .ImageSize(size): + hasSizeAttribute = true + if size != dimensions { + updatedAttributes.append(.ImageSize(size: dimensions)) + didUpdate = true + } else { + updatedAttributes.append(attribute) + } + case let .Video(duration, size, flags, preloadSize, coverTime, videoCodec): + hasSizeAttribute = true + if size != dimensions { + updatedAttributes.append(.Video(duration: duration, size: dimensions, flags: flags, preloadSize: preloadSize, coverTime: coverTime, videoCodec: videoCodec)) + didUpdate = true + } else { + updatedAttributes.append(attribute) + } + default: + updatedAttributes.append(attribute) + } + } + + if !hasSizeAttribute { + updatedAttributes.append(.ImageSize(size: dimensions)) + didUpdate = true + } + + return (updatedAttributes, didUpdate) + } + private func longPressMedia(_ media: InstantPageMedia) { let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - let _ = copyToPasteboard(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + let _ = copyToPasteboard(context: strongSelf.context, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_LinkDialogSave, accessibilityLabel: self.strings.Conversation_LinkDialogSave), action: { [weak self] in if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() + let _ = saveToCameraRoll(context: strongSelf.context, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .image(image) = media.media { @@ -1138,11 +1530,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { translationSettings = TranslationSettings.defaultSettings } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } var actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuCopy, accessibilityLabel: strings.Conversation_ContextMenuCopy), action: { [weak self] in UIPasteboard.general.string = text if let strongSelf = self { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) } }), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in @@ -1154,15 +1546,22 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) if canTranslate { actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in - let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - controller.pushController = { [weak self] c in - (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true - self?.controller?.push(c) + Task { @MainActor [weak self] in + guard let self else { + return + } + let controller = await TextProcessingScreen( + context: context, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: text, entities: []), + copyResult: { [weak self] text in + storeMessageTextInPasteboard(text.text, entities: text.entities) + self?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil) + }, + translateChat: nil + ) + self.present(controller, nil) } - controller.presentController = { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - } - self?.present(controller, nil) })) } @@ -1372,7 +1771,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) |> deliverOnMainQueue).start(next: { peer in if let strongSelf = self, let peer = peer { - if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.getNavigationController()?.pushViewController(controller) } } @@ -1398,12 +1797,12 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { private func openUrlIn(_ url: InstantPageUrlItem) { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: url.url), openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: self.context, item: .url(url: url.url), openUrl: { [weak self] url in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) } }) - self.present(actionSheet, nil) + self.pushController(actionSheet) } private func mediasFromItems(_ items: [InstantPageItem]) -> [InstantPageMedia] { @@ -1426,21 +1825,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { guard let items = self.currentLayout?.items, let (webPage, _) = self.webPage else { return } - - if case let .geo(map) = media.media { - let controllerParams = LocationViewParams(sendLiveLocation: { _ in - }, stopLiveLocation: { _ in - }, openUrl: { _ in }, openPeer: { _ in - }, showAll: false) - - let peer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - - let controller = LocationViewController(context: self.context, subject: EngineMessage(message), params: controllerParams) - self.pushController(controller) - return - } - + if case let .file(file) = media.media, (file.isVoice || file.isMusic) { var medias: [InstantPageMedia] = [] var initialIndex = 0 @@ -1454,71 +1839,48 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { } } } - self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(webPage: webPage, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) + self.context.sharedContext.mediaManager.setPlaylist((self.context, InstantPageMediaPlaylist(playlistId: .instantPage(webpageId: webPage.webpageId), webPage: webPage, messageReference: nil, items: medias, initialItemIndex: initialIndex)), type: file.isVoice ? .voice : .music, control: .playback(.play)) return } - - var fromPlayingVideo = false - - var entries: [InstantPageGalleryEntry] = [] - if case let .webpage(webPage) = media.media { - entries.append(InstantPageGalleryEntry(index: 0, pageId: webPage.webpageId, media: media, caption: nil, credit: nil, location: nil)) - } else if case let .file(file) = media.media, file.isAnimated { - fromPlayingVideo = true - entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil)) - } else { - fromPlayingVideo = true - var medias: [InstantPageMedia] = mediasFromItems(items) - medias = medias.filter { item in - switch item.media { - case .image, .file: - return true - default: - return false - } - } - - for media in medias { - entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(medias.count)))) - } - } - - var centralIndex: Int? - for i in 0 ..< entries.count { - if entries[i].media == media { - centralIndex = i - break - } - } - - if let centralIndex = centralIndex { - let controller = InstantPageGalleryController(context: self.context, userLocation: self.sourceLocation.userLocation, webPage: webPage, entries: entries, centralIndex: centralIndex, fromPlayingVideo: fromPlayingVideo, replaceRootController: { _, _ in - }, baseNavigationController: self.getNavigationController()) - self.hiddenMediaDisposable.set((controller.hiddenMedia |> deliverOnMainQueue).start(next: { [weak self] entry in - if let strongSelf = self { - for (_, itemNode) in strongSelf.visibleItemsWithNodes { - itemNode.updateHiddenMedia(media: entry?.media) - } - } - })) - controller.openUrl = { [weak self] url in + + openInstantPageMedia( + media: media, + allMedias: self.mediasFromItems(items), + webPage: webPage, + context: self.context, + userLocation: self.sourceLocation.userLocation, + present: { [weak self] controller, args in + self?.present(controller, args) + }, + push: { [weak self] controller in + self?.pushController(controller) + }, + openUrl: { [weak self] url in self?.openUrl(url) - } - self.present(controller, InstantPageGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in - if let strongSelf = self { - for (_, itemNode) in strongSelf.visibleItemsWithNodes { - if let transitionNode = itemNode.transitionNode(media: entry.media) { - return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { view in - if let strongSelf = self { - strongSelf.scrollNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.scrollNode.view) - } - }) - } + }, + baseNavigationController: { [weak self] in + self?.getNavigationController() + }, + transitionArgsForMedia: { [weak self] tappedMedia -> GalleryTransitionArguments? in + guard let strongSelf = self else { return nil } + for (_, itemNode) in strongSelf.visibleItemsWithNodes { + if let transitionNode = itemNode.transitionNode(media: tappedMedia) { + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: { view in + if let strongSelf = self { + strongSelf.scrollNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.scrollNode.view) + } + }) } } return nil - })) - } + }, + hiddenMediaCallback: { [weak self] hidden in + guard let strongSelf = self else { return } + for (_, itemNode) in strongSelf.visibleItemsWithNodes { + itemNode.updateHiddenMedia(media: hidden) + } + } + ) } private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { diff --git a/submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift b/submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift index f26d07a080..b70e96f9bb 100644 --- a/submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift +++ b/submodules/InstantPageUI/Sources/InstantPageExternalMediaResource.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import PersistentStringHash @@ -7,9 +6,9 @@ public struct InstantPageExternalMediaResourceId { public let url: String public var uniqueId: String { - return "instantpage-media-\(persistentHash32(self.url))" + return "instantpage-media-\(enginePersistentHash32(self.url))" } - + public var hashValue: Int { return self.uniqueId.hashValue } @@ -17,28 +16,28 @@ public struct InstantPageExternalMediaResourceId { public class InstantPageExternalMediaResource: TelegramMediaResource { public let url: String - + public var size: Int64? { return nil } - + public init(url: String) { self.url = url } - - public required init(decoder: PostboxDecoder) { + + public required init(decoder: EnginePostboxDecoder) { self.url = decoder.decodeStringForKey("u", orElse: "") } - - public func encode(_ encoder: PostboxEncoder) { + + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.url, forKey: "u") } - - public var id: MediaResourceId { - return MediaResourceId(InstantPageExternalMediaResourceId(url: self.url).uniqueId) + + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(InstantPageExternalMediaResourceId(url: self.url).uniqueId) } - - public func isEqual(to: MediaResource) -> Bool { + + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? InstantPageExternalMediaResource { return self.url == to.url } else { diff --git a/submodules/InstantPageUI/Sources/InstantPageFormulaItem.swift b/submodules/InstantPageUI/Sources/InstantPageFormulaItem.swift new file mode 100644 index 0000000000..3c6d8d1cbb --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageFormulaItem.swift @@ -0,0 +1,185 @@ +import Foundation +import UIKit +import TelegramCore +import AsyncDisplayKit +import Display +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import ContextUI + +final class InstantPageFormulaNode: ASImageNode, InstantPageNode { + let attachment: InstantPageMathAttachment + + init(attachment: InstantPageMathAttachment) { + self.attachment = attachment + super.init() + + self.isUserInteractionEnabled = false + self.displaysAsynchronously = false + self.image = attachment.rendered.image + } + + func updateIsVisible(_ isVisible: Bool) { + } + + func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return nil + } + + func updateHiddenMedia(media: InstantPageMedia?) { + } + + func update(strings: PresentationStrings, theme: InstantPageTheme) { + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + } +} + +final class InstantPageTextFormulaItem: InstantPageItem { + var frame: CGRect + let attachment: InstantPageMathAttachment + + let wantsNode: Bool = true + let separatesTiles: Bool = false + let medias: [InstantPageMedia] = [] + + init(frame: CGRect, attachment: InstantPageMathAttachment) { + self.frame = frame + self.attachment = attachment + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func drawInTile(context: CGContext) { + } + + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? { + return InstantPageFormulaNode(attachment: self.attachment) + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + guard let node = node as? InstantPageFormulaNode else { + return false + } + return self.attachment.isEqual(to: node.attachment) + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } +} + +final class InstantPageFormulaItem: InstantPageItem { + var frame: CGRect + let attachment: InstantPageMathAttachment + + let wantsNode: Bool = true + let separatesTiles: Bool = false + let medias: [InstantPageMedia] = [] + + init(frame: CGRect, attachment: InstantPageMathAttachment) { + self.frame = frame + self.attachment = attachment + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func drawInTile(context: CGContext) { + } + + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? { + return InstantPageFormulaNode(attachment: self.attachment) + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + guard let node = node as? InstantPageFormulaNode else { + return false + } + return self.attachment.isEqual(to: node.attachment) + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } +} + +final class InstantPageScrollableFormulaItem: InstantPageScrollableItem { + var frame: CGRect + let attachment: InstantPageMathAttachment + let totalWidth: CGFloat + let horizontalInset: CGFloat + + let medias: [InstantPageMedia] = [] + let wantsNode: Bool = true + let separatesTiles: Bool = false + let isRTL: Bool = false + + init(frame: CGRect, attachment: InstantPageMathAttachment, totalWidth: CGFloat, horizontalInset: CGFloat) { + self.frame = frame + self.attachment = attachment + self.totalWidth = totalWidth + self.horizontalInset = horizontalInset + } + + var contentSize: CGSize { + return CGSize(width: self.totalWidth, height: self.frame.height) + } + + func matchesAnchor(_ anchor: String) -> Bool { + return false + } + + func drawInTile(context: CGContext) { + } + + func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?, getPreloadedResource: @escaping (String) -> Data?) -> InstantPageNode? { + let node = InstantPageFormulaNode(attachment: self.attachment) + node.frame = CGRect(origin: .zero, size: self.attachment.rendered.size) + return InstantPageScrollableNode(item: self, additionalNodes: [node]) + } + + func matchesNode(_ node: InstantPageNode) -> Bool { + if let node = node as? InstantPageScrollableNode { + return node.item === self + } + return false + } + + func linkSelectionRects(at point: CGPoint) -> [CGRect] { + return [] + } + + func distanceThresholdGroup() -> Int? { + return nil + } + + func distanceThresholdWithGroupCount(_ count: Int) -> CGFloat { + return 0.0 + } + + func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { + return nil + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift index 52b551f63d..f0d432adc1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import QuickLook -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -29,13 +28,13 @@ public struct InstantPageGalleryEntryLocation: Equatable { public struct InstantPageGalleryEntry: Equatable { public let index: Int32 - public let pageId: MediaId + public let pageId: EngineMedia.Id public let media: InstantPageMedia public let caption: RichText? public let credit: RichText? public let location: InstantPageGalleryEntryLocation? - public init(index: Int32, pageId: MediaId, media: InstantPageMedia, caption: RichText?, credit: RichText?, location: InstantPageGalleryEntryLocation?) { + public init(index: Int32, pageId: EngineMedia.Id, media: InstantPageMedia, caption: RichText?, credit: RichText?, location: InstantPageGalleryEntryLocation?) { self.index = index self.pageId = pageId self.media = media @@ -48,7 +47,7 @@ public struct InstantPageGalleryEntry: Equatable { return lhs.index == rhs.index && lhs.pageId == rhs.pageId && lhs.media == rhs.media && lhs.caption == rhs.caption && lhs.credit == rhs.credit && lhs.location == rhs.location } - func item(context: AccountContext, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, message: Message?, presentationData: PresentationData, fromPlayingVideo: Bool, landscape: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void, getPreloadedResource: @escaping (String) -> Data?) -> GalleryItem { + func item(context: AccountContext, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, message: EngineMessage?, presentationData: PresentationData, fromPlayingVideo: Bool, landscape: Bool, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlOptions: @escaping (InstantPageUrlItem) -> Void, getPreloadedResource: @escaping (String) -> Data?) -> GalleryItem { let caption: NSAttributedString let credit: NSAttributedString @@ -119,7 +118,7 @@ public struct InstantPageGalleryEntry: Equatable { if let dimensions = file.dimensions { representations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let image = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, userLocation: userLocation, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions, getPreloadedResource: getPreloadedResource) } } else if case let .webpage(embedWebpage) = self.media.media, case let .Loaded(webpageContent) = embedWebpage.content { @@ -166,7 +165,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable private let context: AccountContext private let userLocation: MediaResourceUserLocation private let webPage: TelegramMediaWebpage - private let message: Message? + private let message: EngineMessage? private var presentationData: PresentationData private let _ready = Promise() @@ -203,7 +202,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable private var openUrlOptions: (InstantPageUrlItem) -> Void private let getPreloadedResource: (String) -> Data? - public init(context: AccountContext, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, message: Message? = nil, entries: [InstantPageGalleryEntry], centralIndex: Int, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, baseNavigationController: NavigationController?, getPreloadedResource: @escaping (String) -> Data? = { _ in return nil }) { + public init(context: AccountContext, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, message: EngineMessage? = nil, entries: [InstantPageGalleryEntry], centralIndex: Int, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, baseNavigationController: NavigationController?, getPreloadedResource: @escaping (String) -> Data? = { _ in return nil }) { self.context = context self.userLocation = userLocation self.webPage = webPage diff --git a/submodules/InstantPageUI/Sources/InstantPageGalleryFooterContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageGalleryFooterContentNode.swift index 4d6a0a3140..49ac9776c2 100644 --- a/submodules/InstantPageUI/Sources/InstantPageGalleryFooterContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageGalleryFooterContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import Photos diff --git a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift index e29fcab2d7..ee18fe8320 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift @@ -7,12 +7,17 @@ import TelegramUIPreferences import AccountContext import ContextUI -protocol InstantPageImageAttribute { +public protocol InstantPageImageAttribute { } -struct InstantPageMapAttribute: InstantPageImageAttribute { - let zoom: Int32 - let dimensions: CGSize +public struct InstantPageMapAttribute: InstantPageImageAttribute { + public let zoom: Int32 + public let dimensions: CGSize + + public init(zoom: Int32, dimensions: CGSize) { + self.zoom = zoom + self.dimensions = dimensions + } } public final class InstantPageImageItem: InstantPageItem { @@ -54,7 +59,7 @@ public final class InstantPageImageItem: InstantPageItem { public func matchesNode(_ node: InstantPageNode) -> Bool { if let node = node as? InstantPageImageNode { - return node.media == self.media + return instantPageMediaMatchesNodeIdentity(node.media, self.media) } else { return false } diff --git a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift index 76fc85e0fc..6593f2ef89 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import ImageIO import AsyncDisplayKit import Display import TelegramCore @@ -21,7 +22,35 @@ private struct FetchControls { let cancel: () -> Void } -final class InstantPageImageNode: ASDisplayNode, InstantPageNode { +private enum ExternalImageLoadState { + case loading + case ready + case failed +} + +private func externalImagePixelDimensions(data: Data) -> PixelDimensions? { + guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { + return nil + } + guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any] else { + return nil + } + guard let pixelWidth = (properties[kCGImagePropertyPixelWidth] as? NSNumber)?.int32Value, + let pixelHeight = (properties[kCGImagePropertyPixelHeight] as? NSNumber)?.int32Value, + pixelWidth > 0, pixelHeight > 0 else { + return nil + } + + let orientation = imageOrientationFromSource(source) + switch orientation { + case .left, .right, .leftMirrored, .rightMirrored: + return PixelDimensions(width: pixelHeight, height: pixelWidth) + default: + return PixelDimensions(width: pixelWidth, height: pixelHeight) + } +} + +final class InstantPageImageNode: ASDisplayNode, InstantPageNode, InstantPageExternalMediaDimensionsNode { private let context: AccountContext private let webPage: TelegramMediaWebpage private var theme: InstantPageTheme @@ -32,8 +61,13 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private let fit: Bool private let openMedia: (InstantPageMedia) -> Void private let longPressMedia: (InstantPageMedia) -> Void + private let getPreloadedResource: (String) -> Data? private var fetchControls: FetchControls? + private var externalImageLoadState: ExternalImageLoadState? + var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? + private var externalMediaDimensions: PixelDimensions? + private var didReportExternalMediaDimensions = false private let pinchContainerNode: PinchSourceContainerNode private let imageNode: TransformImageNode @@ -48,8 +82,9 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var statusDisposable = MetaDisposable() private var themeUpdated: Bool = false + private var externalMediaDimensionsUpdated: Bool = false - init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, getPreloadedResource: @escaping (String) -> Data?) { + init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, media: InstantPageMedia, attributes: [InstantPageImageAttribute], interactive: Bool, roundCorners: Bool, fit: Bool, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, imageReferenceForMedia: ((TelegramMediaImage) -> ImageMediaReference)? = nil, fileReferenceForMedia: ((TelegramMediaFile) -> FileMediaReference)? = nil, getPreloadedResource: @escaping (String) -> Data?) { self.context = context self.theme = theme self.webPage = webPage @@ -60,6 +95,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.fit = fit self.openMedia = openMedia self.longPressMedia = longPressMedia + self.getPreloadedResource = getPreloadedResource self.pinchContainerNode = PinchSourceContainerNode() self.imageNode = TransformImageNode() @@ -72,35 +108,16 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.pinchContainerNode.contentNode.addSubnode(self.imageNode) self.addSubnode(self.pinchContainerNode) + if interactive, media.url != nil { + self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink") + self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode) + } + if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) { if let externalResource = largest.resource as? InstantPageExternalMediaResource { - var url = externalResource.url - if !url.hasPrefix("http") && !url.hasPrefix("https") && url.hasPrefix("//") { - url = "https:\(url)" - } - let photoData: Signal, NoError> - if let preloadedData = getPreloadedResource(externalResource.url) { - photoData = .single(Tuple4(nil, preloadedData, .full, true)) - } else { - photoData = context.engine.resources.httpData(url: url, preserveExactUrl: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> map { data in - if let data { - return Tuple4(nil, data, .full, true) - } else { - return Tuple4(nil, nil, .full, false) - } - } - } - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: photoData) - |> map { _, _, generate in - return generate - }) + self.loadExternalImage(resourceUrl: externalResource.url) } else { - let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) + let imageReference = imageReferenceForMedia?(image) ?? ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: image) { @@ -116,48 +133,23 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { }) if interactive { - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(largest.resource)) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) + strongSelf.fetchStatus = status strongSelf.updateFetchStatus() } } })) - - if media.url != nil { - self.linkIconNode.image = UIImage(bundleImageName: "Instant View/ImageLink") - self.pinchContainerNode.contentNode.addSubnode(self.linkIconNode) - } - + self.pinchContainerNode.contentNode.addSubnode(self.statusNode) } } } else if case let .file(file) = media.media { if let externalResource = file.resource as? InstantPageExternalMediaResource { - let photoData: Signal, NoError> - if let preloadedData = getPreloadedResource(externalResource.url) { - photoData = .single(Tuple4(nil, preloadedData, .full, true)) - } else { - photoData = context.engine.resources.httpData(url: externalResource.url, preserveExactUrl: true) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> map { data in - if let data { - return Tuple4(nil, data, .full, true) - } else { - return Tuple4(nil, nil, .full, false) - } - } - } - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: photoData) - |> map { _, _, generate in - return generate - }) + self.loadExternalImage(resourceUrl: externalResource.url) } else { - let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) + let fileReference = fileReferenceForMedia?(file) ?? FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) if file.mimeType.hasPrefix("image/") { if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) { _ = freeMediaFileInteractiveFetched(account: context.account, userLocation: sourceLocation.userLocation, fileReference: fileReference).start() @@ -184,7 +176,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height)) self.imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) } else if case let .webpage(webPage) = media.media, case let .Loaded(content) = webPage.content, let image = content.image { - let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) + let imageReference = imageReferenceForMedia?(image) ?? ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start()) self.statusNode.transitionToState(.play(.white), animated: false, completion: {}) @@ -235,6 +227,114 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } } + private func loadExternalImage(resourceUrl: String) { + self.externalImageLoadState = .loading + self.updateExternalImageLoadState() + + var requestUrl = resourceUrl + if !requestUrl.hasPrefix("http") && !requestUrl.hasPrefix("https") && requestUrl.hasPrefix("//") { + requestUrl = "https:\(requestUrl)" + } + + let photoData: Signal, NoError> + if let preloadedData = self.getPreloadedResource(resourceUrl) { + photoData = .single(Tuple4(nil, preloadedData, .full, true)) + } else { + photoData = self.context.engine.resources.httpData(url: requestUrl, preserveExactUrl: true) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> map { data in + if let data { + return Tuple4(nil, data, .full, true) + } else { + return Tuple4(nil, nil, .full, false) + } + } + } + + let stateAwarePhotoData = photoData + |> deliverOnMainQueue + |> afterNext { [weak self] value in + guard let strongSelf = self else { + return + } + if let data = value._1 ?? value._0, UIImage(data: data) != nil { + if let dimensions = externalImagePixelDimensions(data: data) { + strongSelf.externalMediaDimensions = dimensions + strongSelf.externalMediaDimensionsUpdated = true + strongSelf.maybeUpdateExternalMediaDimensions(dimensions) + } + strongSelf.externalImageLoadState = .ready + strongSelf.setNeedsLayout() + } else { + strongSelf.externalImageLoadState = .failed + } + strongSelf.updateExternalImageLoadState() + } + + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: stateAwarePhotoData) + |> map { _, _, generate in + return generate + }) + + self.fetchControls = FetchControls(fetch: { [weak self] _ in + self?.loadExternalImage(resourceUrl: resourceUrl) + }, cancel: {}) + } + + private func currentMediaDimensions() -> PixelDimensions? { + if case let .image(image) = self.media.media, let largest = largestImageRepresentation(image.representations) { + return largest.dimensions + } else if case let .file(file) = self.media.media { + return file.dimensions + } else { + return nil + } + } + + private func effectiveMediaDimensions() -> PixelDimensions? { + return self.externalMediaDimensions ?? self.currentMediaDimensions() + } + + private func maybeUpdateExternalMediaDimensions(_ dimensions: PixelDimensions) { + guard !self.didReportExternalMediaDimensions, let mediaId = self.media.media.id else { + return + } + if let currentDimensions = self.currentMediaDimensions(), currentDimensions == dimensions { + return + } + self.didReportExternalMediaDimensions = true + self.updateExternalMediaDimensions?(mediaId, dimensions) + } + + private func updateExternalImageLoadState() { + guard let externalImageLoadState = self.externalImageLoadState else { + return + } + + if self.statusNode.supernode == nil { + self.pinchContainerNode.contentNode.addSubnode(self.statusNode) + } + + let state: RadialStatusNodeState + switch externalImageLoadState { + case .loading: + state = .progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true) + case .ready: + state = .none + case .failed: + state = .none + } + + self.statusNode.transitionToState(state, completion: { [weak statusNode] in + if state == .none { + statusNode?.removeFromSupernode() + } + }) + } + private func updateFetchStatus() { var state: RadialStatusNodeState = .none if let fetchStatus = self.fetchStatus { @@ -260,19 +360,20 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let size = self.bounds.size - if self.currentSize != size || self.themeUpdated { + if self.currentSize != size || self.themeUpdated || self.externalMediaDimensionsUpdated { self.currentSize = size self.themeUpdated = false + self.externalMediaDimensionsUpdated = false self.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchContainerNode.update(size: size, transition: .immediate) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) - let radialStatusSize: CGFloat = 50.0 + let radialStatusSize: CGFloat = max(18.0, min(50.0, floor(min(size.width, size.height) * 0.7))) self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize) - if case let .image(image) = self.media.media, let largest = largestImageRepresentation(image.representations) { - let imageSize = largest.dimensions.cgSize.aspectFilled(size) + if case .image = self.media.media, let dimensions = self.effectiveMediaDimensions() { + let imageSize = dimensions.cgSize.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 let makeLayout = self.imageNode.asyncLayout() @@ -280,7 +381,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { apply() self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0) - } else if case let .file(file) = self.media.media, let dimensions = file.dimensions { + } else if case let .file(file) = self.media.media, let dimensions = self.effectiveMediaDimensions() { let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageTintColor : nil let imageSize = dimensions.cgSize.aspectFilled(size) @@ -318,7 +419,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { - if media == self.media { + if instantPageMediaMatchesNodeIdentity(media, self.media) { let imageNode = self.imageNode return (self.imageNode, self.imageNode.bounds, { [weak imageNode] in return (imageNode?.view.snapshotContentTree(unhide: true), nil) @@ -329,14 +430,32 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } func updateHiddenMedia(media: InstantPageMedia?) { - self.imageNode.isHidden = self.media == media + if let media { + self.imageNode.isHidden = instantPageMediaMatchesNodeIdentity(self.media, media) + } else { + self.imageNode.isHidden = false + } self.statusNode.isHidden = self.imageNode.isHidden + self.linkIconNode.isHidden = self.imageNode.isHidden } @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { switch recognizer.state { case .ended: if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { + if let externalImageLoadState = self.externalImageLoadState { + switch externalImageLoadState { + case .loading: + return + case .failed: + if case .tap = gesture { + self.fetchControls?.fetch(true) + } + return + case .ready: + break + } + } if let fetchStatus = self.fetchStatus { switch fetchStatus { case .Local: diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index a19a069f7f..3a149628f7 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -6,6 +6,7 @@ import TelegramPresentationData import TelegramUIPreferences import TelegramStringFormatting import MosaicLayout +import TextFormat public final class InstantPageLayout { public let origin: CGPoint @@ -28,17 +29,18 @@ public final class InstantPageLayout { } } -private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, category: InstantPageTextCategoryType, link: Bool) { - let attributes = theme.textCategories.attributes(type: category, link: link) +private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, attributes: InstantPageTextAttributes) { stack.push(.textColor(attributes.color)) stack.push(.markerColor(theme.markerColor)) stack.push(.linkColor(theme.linkColor)) stack.push(.linkMarkerColor(theme.linkHighlightColor)) switch attributes.font.style { - case .sans: - stack.push(.fontSerif(false)) - case .serif: - stack.push(.fontSerif(true)) + case .sans: + stack.push(.fontSerif(false)) + case .serif: + stack.push(.fontSerif(true)) + case .monospace: + stack.push(.fontFixed(true)) } stack.push(.fontSize(attributes.font.size)) stack.push(.lineSpacingFactor(attributes.font.lineSpacingFactor)) @@ -47,8 +49,122 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP } } -public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [EngineMedia.Id: EngineMedia], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], excludeCaptions: Bool) -> InstantPageLayout { - +private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, category: InstantPageTextCategoryType, link: Bool) { + setupStyleStack(stack, theme: theme, attributes: theme.textCategories.attributes(type: category, link: link)) +} + +private func instantPageFont(style: InstantPageTextAttributes, bold: Bool = false, italic: Bool = false, fixed: Bool = false) -> UIFont { + let size = style.font.size + if fixed { + if bold && italic { + return UIFont(name: "Menlo-BoldItalic", size: size) ?? Font.semiboldItalic(size) + } else if bold { + return UIFont(name: "Menlo-Bold", size: size) ?? Font.bold(size) + } else if italic { + return UIFont(name: "Menlo-Italic", size: size) ?? Font.italic(size) + } else { + return UIFont(name: "Menlo", size: size) ?? Font.regular(size) + } + } + switch style.font.style { + case .serif: + if bold && italic { + return UIFont(name: "Georgia-BoldItalic", size: size) ?? Font.semiboldItalic(size) + } else if bold { + return UIFont(name: "Georgia-Bold", size: size) ?? Font.bold(size) + } else if italic { + return UIFont(name: "Georgia-Italic", size: size) ?? Font.italic(size) + } else { + return UIFont(name: "Georgia", size: size) ?? Font.regular(size) + } + case .sans: + if bold && italic { + return Font.semiboldItalic(size) + } else if bold { + return Font.bold(size) + } else if italic { + return Font.italic(size) + } else { + return Font.regular(size) + } + case .monospace: + if bold && italic { + return Font.semiboldItalicMonospace(size) + } else if bold { + return Font.semiboldMonospace(size) + } else if italic { + return Font.italicMonospace(size) + } else { + return Font.monospace(size) + } + } +} + +private func attributedStringForPreformattedText(_ text: RichText, language: String?, theme: InstantPageTheme, cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight?) -> NSAttributedString { + let paragraphAttributes = theme.textCategories.attributes(type: .paragraph, link: false) + let textValue = text.plainText + guard !textValue.isEmpty else { + return NSAttributedString( + string: "", + attributes: [ + .font: instantPageFont(style: paragraphAttributes, fixed: true), + .foregroundColor: paragraphAttributes.color, + NSAttributedString.Key(rawValue: InstantPageLineSpacingFactorAttribute): paragraphAttributes.font.lineSpacingFactor as NSNumber + ] + ) + } + + let attributedString = stringWithAppliedEntities( + textValue, + entities: [ + MessageTextEntity(range: 0 ..< (textValue as NSString).length, type: .Pre(language: language)) + ], + baseColor: paragraphAttributes.color, + linkColor: theme.linkColor, + codeBlockTitleColor: paragraphAttributes.color, + codeBlockAccentColor: paragraphAttributes.color, + codeBlockBackgroundColor: theme.codeBlockBackgroundColor, + baseFont: instantPageFont(style: paragraphAttributes), + linkFont: instantPageFont(style: paragraphAttributes), + boldFont: instantPageFont(style: paragraphAttributes, bold: true), + italicFont: instantPageFont(style: paragraphAttributes, italic: true), + boldItalicFont: instantPageFont(style: paragraphAttributes, bold: true, italic: true), + fixedFont: instantPageFont(style: paragraphAttributes, fixed: true), + blockQuoteFont: instantPageFont(style: paragraphAttributes), + underlineLinks: false, + message: nil, + cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight + ).mutableCopy() as! NSMutableAttributedString + attributedString.addAttribute( + NSAttributedString.Key(rawValue: InstantPageLineSpacingFactorAttribute), + value: paragraphAttributes.font.lineSpacingFactor as NSNumber, + range: NSRange(location: 0, length: attributedString.length) + ) + return attributedString +} + +private let instantPageChecklistMarkerSize = CGSize(width: 18.0, height: 18.0) + +private func instantPageFirstTextLineMidY(in items: [InstantPageItem]) -> CGFloat? { + for item in items { + if let textItem = item as? InstantPageTextItem { + if let line = textItem.lines.first { + return textItem.frame.minY + line.frame.midY + } else { + return textItem.frame.midY + } + } else if let scrollableTextItem = item as? InstantPageScrollableTextItem { + if let line = scrollableTextItem.item.lines.first { + return scrollableTextItem.frame.minY + scrollableTextItem.item.frame.minY + line.frame.midY + } else { + return scrollableTextItem.frame.midY + } + } + } + return nil +} + +public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [EngineMedia.Id: EngineMedia], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil, excludeCaptions: Bool, isLast: Bool, fitToWidth: Bool) -> InstantPageLayout { let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in var items: [InstantPageItem] = [] var offset = contentSize.height @@ -60,7 +176,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: offset += 14.0 let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - let (textItem, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage) + let (textItem, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage, fitToWidth: fitToWidth) contentSize.height += captionContentSize.height offset += captionContentSize.height items.append(contentsOf: captionItems) @@ -79,7 +195,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .credit, link: false) - let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.credit, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: rtl ? .right : .natural, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage) + let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption.credit, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: rtl ? .right : .natural, offset: CGPoint(x: horizontalInset, y: offset), media: media, webpage: webpage, fitToWidth: fitToWidth) contentSize.height += captionContentSize.height offset += captionContentSize.height items.append(contentsOf: captionItems) @@ -100,16 +216,16 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: switch block { case let .cover(block): - return layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToSize: fillToSize, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: false) + return layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: true, previousItems:previousItems, fillToSize: fillToSize, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: false, isLast: true, fitToWidth: fitToWidth) case let .title(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subtitle(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .subheader, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .authorDate(author: author, date: date): let styleStack = InstantPageTextStyleStack() @@ -148,7 +264,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: if let previousItem = previousItems.last as? InstantPageTextItem, previousItem.containsRTL { previousItemHasRTL = true } - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: rtl || previousItemHasRTL ? .right : .natural, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: rtl || previousItemHasRTL ? .right : .natural, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } else { return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) @@ -156,28 +272,78 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: case let .kicker(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .kicker, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .header(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .header, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .subheader(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .subheader, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .heading(text, level): + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, attributes: theme.headingTextAttributes(level: level, link: false)) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + case let .formula(latex): + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + let attributes = styleStack.textAttributes() + let textColor = (attributes[NSAttributedString.Key.foregroundColor] as? UIColor) ?? theme.textCategories.paragraph.color + let fontSize = (attributes[NSAttributedString.Key.font] as? UIFont)?.pointSize ?? theme.textCategories.paragraph.font.size + + guard let attachment = instantPageMathAttachment(latex: latex, fontSize: fontSize, textColor: textColor, mode: .block) else { + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(latex), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) + } + + let availableWidth = boundingWidth - horizontalInset * 2.0 + if attachment.rendered.size.width > availableWidth { + let scrollableItem = InstantPageScrollableFormulaItem( + frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: attachment.rendered.size.height)), + attachment: attachment, + totalWidth: attachment.rendered.size.width, + horizontalInset: horizontalInset + ) + return InstantPageLayout(origin: CGPoint(), contentSize: scrollableItem.frame.size, items: [scrollableItem]) + } else { + let item = InstantPageFormulaItem( + frame: CGRect( + origin: CGPoint(x: horizontalInset + floor((availableWidth - attachment.rendered.size.width) / 2.0), y: 0.0), + size: attachment.rendered.size + ), + attachment: attachment + ) + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(width: boundingWidth, height: attachment.rendered.size.height), items: [item]) + } case let .paragraph(text): let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: horizontalInset, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) + let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, horizontalInset: horizontalInset, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage, fitToWidth: fitToWidth) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) - case let .preformatted(text): - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + case let .preformatted(text, language): let backgroundInset: CGFloat = 14.0 - let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: 17.0, y: backgroundInset), media: media, webpage: webpage, opaqueBackground: true) + let attributedString: NSAttributedString + if let language, !language.isEmpty { + attributedString = attributedStringForPreformattedText(text, language: language, theme: theme, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight) + } else { + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + attributedString = attributedStringForRichText(text, styleStack: styleStack) + } + let (_, items, contentSize) = layoutTextItemWithString( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, + offset: CGPoint(x: 17.0, y: backgroundInset), + media: media, + webpage: webpage, + fitToWidth: fitToWidth, + opaqueBackground: true + ) let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: contentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.codeBlockBackgroundColor) var allItems: [InstantPageItem] = [backgroundItem] allItems.append(contentsOf: items) @@ -188,20 +354,33 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: let (_, items, contentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(x: horizontalInset, y: 0.0), media: media, webpage: webpage) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case .divider: - let lineWidth = floor(boundingWidth / 2.0) - let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - lineWidth) / 2.0), y: 0.0), size: CGSize(width: lineWidth, height: 1.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: 1.0)), shape: .rect, color: theme.textCategories.caption.color) - return InstantPageLayout(origin: CGPoint(), contentSize: shapeItem.frame.size, items: [shapeItem]) + if isLast { + return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) + } else { + let lineWidth = floor(boundingWidth / 2.0) + let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - lineWidth) / 2.0), y: 0.0), size: CGSize(width: lineWidth, height: 1.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: lineWidth, height: 1.0)), shape: .rect, color: theme.textCategories.caption.color) + return InstantPageLayout(origin: CGPoint(), contentSize: shapeItem.frame.size, items: [shapeItem]) + } case let .list(contentItems, ordered): var contentSize = CGSize(width: boundingWidth, height: 0.0) var maxIndexWidth: CGFloat = 0.0 var listItems: [InstantPageItem] = [] var indexItems: [InstantPageItem] = [] + var hasTaskMarkers = false var hasNums = false if ordered { for item in contentItems { - if let num = item.num, !num.isEmpty { + if item.checked != nil { + hasTaskMarkers = true + } else if let num = item.num, !num.isEmpty { hasNums = true + } + } + } else { + for item in contentItems { + if item.checked != nil { + hasTaskMarkers = true break } } @@ -209,7 +388,13 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: for i in 0 ..< contentItems.count { let item = contentItems[i] - if ordered { + if let checked = item.checked { + let checklistItem = InstantPageChecklistMarkerItem(frame: CGRect(origin: .zero, size: instantPageChecklistMarkerSize), checked: checked) + if ordered { + maxIndexWidth = max(maxIndexWidth, instantPageChecklistMarkerSize.width) + } + indexItems.append(checklistItem) + } else if ordered { let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) let value: String @@ -222,7 +407,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } else { value = "\(i + 1)." } - let (textItem, _, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint()) + let (textItem, _, _) = layoutTextItemWithString(attributedStringForRichText(.plain(value), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, offset: CGPoint(), fitToWidth: fitToWidth) if let textItem = textItem, let line = textItem.lines.first { textItem.selectable = false maxIndexWidth = max(maxIndexWidth, line.frame.width) @@ -233,21 +418,25 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: indexItems.append(shapeItem) } } - let indexSpacing: CGFloat = ordered ? 12.0 : 20.0 + let indexSpacing: CGFloat = ordered ? (hasTaskMarkers ? 16.0 : 12.0) : (hasTaskMarkers ? 24.0 : 20.0) for (i, item) in contentItems.enumerated() { - if (i != 0) { - contentSize.height += 18.0 + if i != 0 { + if fitToWidth { + contentSize.height += 12.0 + } else { + contentSize.height += 18.0 + } } let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) var effectiveItem = item - if case let .blocks(blocks, num) = effectiveItem, blocks.isEmpty { - effectiveItem = .text(.plain(" "), num) + if case let .blocks(blocks, num, checked) = effectiveItem, blocks.isEmpty { + effectiveItem = .text(.plain(" "), num, checked) } switch effectiveItem { - case let .text(text, _): - let (textItem, textItems, textItemSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, offset: CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height), media: media, webpage: webpage) + case let .text(text, _, _): + let (textItem, textItems, textItemSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, offset: CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height), media: media, webpage: webpage, fitToWidth: fitToWidth) contentSize.height += textItemSize.height let indexItem = indexItems[i] @@ -264,23 +453,34 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: if let textIndexItem = indexItem as? InstantPageTextItem, let line = textIndexItem.lines.first { itemFrame = itemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - line.frame.width, dy: floorToScreenPixels(lineMidY - (itemFrame.height / 2.0))) + } else if indexItem is InstantPageChecklistMarkerItem { + if ordered { + itemFrame = itemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - itemFrame.width, dy: floorToScreenPixels(lineMidY - (itemFrame.height / 2.0))) + } else { + itemFrame = itemFrame.offsetBy(dx: horizontalInset, dy: floorToScreenPixels(lineMidY - (itemFrame.height / 2.0))) + } } else { itemFrame = itemFrame.offsetBy(dx: horizontalInset, dy: floorToScreenPixels(lineMidY - itemFrame.height / 2.0)) } indexItems[i].frame = itemFrame listItems.append(indexItems[i]) listItems.append(contentsOf: textItems) - case let .blocks(blocks, _): + case let .blocks(blocks, _, _): var previousBlock: InstantPageBlock? var originY: CGFloat = contentSize.height - for subBlock in blocks { - let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: false) + var firstBlockLineMidY: CGFloat? + for i in 0 ..< blocks.count { + let subBlock = blocks[i] + let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: false, isLast: i == blocks.count - 1, fitToWidth: fitToWidth) - let spacing: CGFloat = previousBlock != nil && subLayout.contentSize.height > 0.0 ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0 + let spacing: CGFloat = previousBlock != nil && subLayout.contentSize.height > 0.0 ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock, fitToWidth: fitToWidth, kind: .topLevel) : 0.0 let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing)) if previousBlock == nil { originY += spacing } + if firstBlockLineMidY == nil { + firstBlockLineMidY = instantPageFirstTextLineMidY(in: blockItems) + } listItems.append(contentsOf: blockItems) contentSize.height += subLayout.contentSize.height + spacing previousBlock = subBlock @@ -289,6 +489,18 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var indexItemFrame = indexItem.frame if let textIndexItem = indexItem as? InstantPageTextItem, let line = textIndexItem.lines.first { indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - line.frame.width, dy: originY) + } else if indexItem is InstantPageChecklistMarkerItem { + let markerOriginY: CGFloat + if let firstBlockLineMidY { + markerOriginY = floorToScreenPixels(firstBlockLineMidY - indexItemFrame.height / 2.0) + } else { + markerOriginY = originY + } + if ordered { + indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset + maxIndexWidth - indexItemFrame.width, dy: markerOriginY) + } else { + indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset, dy: markerOriginY) + } } else { indexItemFrame = indexItemFrame.offsetBy(dx: horizontalInset, dy: originY) } @@ -301,40 +513,65 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } } return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: listItems) - case let .blockQuote(text, caption): + case let .blockQuote(blocks, caption): let lineInset: CGFloat = 20.0 let verticalInset: CGFloat = 4.0 var contentSize = CGSize(width: boundingWidth, height: verticalInset) - + var items: [InstantPageItem] = [] - - let styleStack = InstantPageTextStyleStack() - setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) - styleStack.push(.italic) - - let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, offset: CGPoint(x: horizontalInset + lineInset, y: contentSize.height), media: media, webpage: webpage) - - contentSize.height += textContentSize.height - items.append(contentsOf: textItems) - + + let innerBoundingWidth = boundingWidth - horizontalInset * 2.0 - lineInset + let innerHorizontalInset = horizontalInset + lineInset + + if blocks.count == 1, case let .paragraph(text) = blocks[0] { + // Legacy single-paragraph fast path: italicized body (unchanged from prior behavior). + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) + styleStack.push(.italic) + + let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: innerBoundingWidth, offset: CGPoint(x: innerHorizontalInset, y: contentSize.height), media: media, webpage: webpage, fitToWidth: fitToWidth) + + contentSize.height += textContentSize.height + items.append(contentsOf: textItems) + } else { + var previousChildItems: [InstantPageItem] = [] + // Fixed, compact gap between a quote's child blocks (the full page-flow + // spacing around quotes is too airy when nested); the first child hugs + // the top (only verticalInset above it). + let childSpacing: CGFloat = 10.0 + for (i, child) in blocks.enumerated() { + let childLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: child, boundingWidth: innerBoundingWidth, horizontalInset: innerHorizontalInset, safeInset: safeInset, isCover: false, previousItems: previousChildItems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: excludeCaptions, isLast: i == blocks.count - 1 && isLast, fitToWidth: fitToWidth) + let spacing: CGFloat = i == 0 ? 0.0 : childSpacing + contentSize.height += spacing + var childItems: [InstantPageItem] = [] + for var item in childLayout.items { + item.frame = item.frame.offsetBy(dx: 0.0, dy: contentSize.height) + childItems.append(item) + } + items.append(contentsOf: childItems) + previousChildItems = childItems + contentSize.height += childLayout.contentSize.height + } + } + if case .empty = caption { } else { contentSize.height += 14.0 - + let styleStack = InstantPageTextStyleStack() setupStyleStack(styleStack, theme: theme, category: .caption, link: false) - - let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, offset: CGPoint(x: horizontalInset + lineInset, y: contentSize.height), media: media, webpage: webpage) - + + let (_, captionItems, captionContentSize) = layoutTextItemWithString(attributedStringForRichText(caption, styleStack: styleStack), boundingWidth: innerBoundingWidth, offset: CGPoint(x: innerHorizontalInset, y: contentSize.height), media: media, webpage: webpage) + contentSize.height += captionContentSize.height items.append(contentsOf: captionItems) } contentSize.height += verticalInset - + let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: horizontalInset, y: 0.0), size: CGSize(width: 3.0, height: contentSize.height)), shapeFrame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 3.0, height: contentSize.height)), shape: .roundLine, color: theme.textCategories.paragraph.color) - + items.append(shapeItem) - + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .pullQuote(text, caption): let verticalInset: CGFloat = 4.0 @@ -346,7 +583,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.italic) - let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: .center, offset: CGPoint(x: 0.0, y: contentSize.height), media: media, webpage: webpage) + let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0, alignment: .center, offset: CGPoint(x: 0.0, y: contentSize.height), media: media, webpage: webpage, fitToWidth: fitToWidth) for var item in textItems { item.frame = item.frame.offsetBy(dx: horizontalInset, dy: 0.0) } @@ -476,7 +713,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var i = 0 for subItem in innerItems { let frame = mosaicLayout[i].0 - let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subItem, boundingWidth: frame.width, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: frame.size, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: true) + let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subItem, boundingWidth: frame.width, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: frame.size, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: true, isLast: false, fitToWidth: false) items.append(contentsOf: subLayout.flattenedItemsWithOrigin(frame.origin)) i += 1 } @@ -523,7 +760,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.bold) - let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(author), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset, offset: CGPoint(x: horizontalInset + lineInset + avatarInset, y: contentSize.height + avatarVerticalInset), media: media, webpage: webpage) + let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(.plain(author), styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset - avatarInset, offset: CGPoint(x: horizontalInset + lineInset + avatarInset, y: contentSize.height + avatarVerticalInset), media: media, webpage: webpage, fitToWidth: fitToWidth) items.append(contentsOf: textItems) contentSize.height += textContentSize.height + avatarVerticalInset @@ -549,10 +786,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } var previousBlock: InstantPageBlock? - for subBlock in blocks { - let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: false) + for i in 0 ..< blocks.count { + let subBlock = blocks[i] + let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - lineInset, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: items, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: false, isLast: i == blocks.count - 1, fitToWidth: fitToWidth) - let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock, fitToWidth: false, kind: .topLevel) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + lineInset, y: contentSize.height + spacing)) items.append(contentsOf: blockItems) contentSize.height += subLayout.contentSize.height + spacing @@ -732,10 +970,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var subDetailsIndex = 0 var previousBlock: InstantPageBlock? - for subBlock in blocks { - let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: false, previousItems: subitems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &subDetailsIndex, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: false) + for i in 0 ..< blocks.count { + let subBlock = blocks[i] + let subLayout = layoutInstantPageBlock(webpage: webpage, userLocation: userLocation, rtl: rtl, block: subBlock, boundingWidth: boundingWidth, horizontalInset: horizontalInset, safeInset: safeInset, isCover: false, previousItems: subitems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &subDetailsIndex, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: false, isLast: i == blocks.count - 1, fitToWidth: fitToWidth) - let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock) + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: subBlock, fitToWidth: false, kind: .topLevel) let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) subitems.append(contentsOf: blockItems) contentSize.height += subLayout.contentSize.height + spacing @@ -743,7 +982,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } if !blocks.isEmpty { - let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) + let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil, fitToWidth: false, kind: .topLevel) contentSize.height += closingSpacing } @@ -761,7 +1000,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) styleStack.push(.bold) let backgroundInset: CGFloat = 14.0 - let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage, opaqueBackground: true) + let (_, textItems, textContentSize) = layoutTextItemWithString(attributedStringForRichText(title, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0, offset: CGPoint(x: horizontalInset, y: backgroundInset), media: media, webpage: webpage, fitToWidth: fitToWidth, opaqueBackground: true) let backgroundItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shapeFrame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: textContentSize.height + backgroundInset * 2.0)), shape: .rect, color: theme.panelBackgroundColor) items.append(backgroundItem) items.append(contentsOf: textItems) @@ -803,10 +1042,12 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: contentSize.height += item.frame.height items.append(item) - let inset: CGFloat = i == articles.count - 1 ? 0.0 : 17.0 - let lineSize = CGSize(width: boundingWidth - inset, height: UIScreenPixel) - let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: rtl || item.rtl ? 0.0 : inset, y: contentSize.height - lineSize.height), size: lineSize), shapeFrame: CGRect(origin: CGPoint(), size: lineSize), shape: .rect, color: theme.controlColor) - items.append(shapeItem) + if !isLast { + let inset: CGFloat = i == articles.count - 1 ? 0.0 : 17.0 + let lineSize = CGSize(width: boundingWidth - inset, height: UIScreenPixel) + let shapeItem = InstantPageShapeItem(frame: CGRect(origin: CGPoint(x: rtl || item.rtl ? 0.0 : inset, y: contentSize.height - lineSize.height), size: lineSize), shapeFrame: CGRect(origin: CGPoint(), size: lineSize), shape: .rect, color: theme.controlColor) + items.append(shapeItem) + } } return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .map(latitude, longitude, zoom, dimensions, caption): @@ -842,7 +1083,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } } -public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instantPage: InstantPage?, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout { +public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instantPage: InstantPage?, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, sideInset: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil, addFeedback: Bool = true, fitToWidth: Bool = false) -> InstantPageLayout { var maybeLoadedContent: TelegramMediaWebpageLoadedContent? if case let .Loaded(content) = webPage.content { maybeLoadedContent = content @@ -870,9 +1111,10 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instant var detailsIndexCounter: Int = 0 var previousBlock: InstantPageBlock? - for block in pageBlocks { - let blockLayout = layoutInstantPageBlock(webpage: webPage, userLocation: userLocation, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: 17.0 + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, excludeCaptions: false) - let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block) + for i in 0 ..< pageBlocks.count { + let block = pageBlocks[i] + let blockLayout = layoutInstantPageBlock(webpage: webPage, userLocation: userLocation, rtl: rtl, block: block, boundingWidth: boundingWidth, horizontalInset: sideInset + safeInset, safeInset: safeInset, isCover: false, previousItems: items, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, excludeCaptions: false, isLast: i == pageBlocks.count - 1, fitToWidth: fitToWidth) + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block, fitToWidth: fitToWidth, kind: .topLevel) let blockItems = blockLayout.flattenedItemsWithOrigin(CGPoint(x: 0.0, y: contentSize.height + spacing)) items.append(contentsOf: blockItems) if CGFloat(0.0).isLess(than: blockLayout.contentSize.height) { @@ -881,14 +1123,22 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instant } } - let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil) + let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil, fitToWidth: fitToWidth, kind: .topLevel) contentSize.height += closingSpacing - if webPage.webpageId.id != 0 { + if webPage.webpageId.id != 0 && addFeedback { let feedbackItem = InstantPageFeedbackItem(frame: CGRect(x: 0.0, y: contentSize.height, width: boundingWidth, height: 40.0), webPage: webPage) contentSize.height += feedbackItem.frame.height items.append(feedbackItem) } + if fitToWidth { + contentSize.width = 0.0 + for item in items { + contentSize.width = max(contentSize.width, ceil(item.frame.maxX) + sideInset) + } + contentSize.width = min(contentSize.width, boundingWidth) + } + return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) } diff --git a/submodules/InstantPageUI/Sources/InstantPageLayoutSpacings.swift b/submodules/InstantPageUI/Sources/InstantPageLayoutSpacings.swift index 6ad2c61e7c..71c8f99377 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayoutSpacings.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayoutSpacings.swift @@ -2,64 +2,158 @@ import Foundation import UIKit import TelegramCore -func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) -> CGFloat { - if let upper = upper, let lower = lower { +enum BlockSequenceKind { + case topLevel + case detail + case cell + case list +} + +func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?, fitToWidth: Bool, kind: BlockSequenceKind) -> CGFloat { + if let upper, let lower { switch (upper, lower) { - case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, _), (_, .anchor): - return 0.0 - case (.divider, _), (_, .divider): + case (_, .cover), (_, .channelBanner), (.details, .details), (.relatedArticles, _), (_, .anchor): + return 0.0 + case (.divider, _), (_, .divider): + if fitToWidth { + return 21.0 + } else { return 25.0 - case (_, .blockQuote), (.blockQuote, _), (_, .pullQuote), (.pullQuote, _): + } + case (_, .blockQuote), (.blockQuote, _): + if fitToWidth { + return 11.0 + } else { return 27.0 - case (.kicker, .title), (.cover, .title): - return 16.0 - case (_, .title): - return 20.0 - case (.title, .authorDate), (.subtitle, .authorDate): - return 18.0 - case (_, .authorDate): - return 20.0 - case (.title, .paragraph), (.authorDate, .paragraph): - return 34.0 - case (.header, .paragraph), (.subheader, .paragraph): + } + case (_, .pullQuote), (.pullQuote, _): + if fitToWidth { + return 14.0 + } else { + return 27.0 + } + case (.kicker, .title), (.cover, .title): + return 16.0 + case (_, .title): + return 20.0 + case (.title, .authorDate), (.subtitle, .authorDate): + return 18.0 + case (_, .authorDate): + return 20.0 + case (.title, .paragraph), (.authorDate, .paragraph): + return 34.0 + case (.header, .paragraph), (.subheader, .paragraph), (.heading, .paragraph): + if fitToWidth { + return 14.0 + } else { return 25.0 - case (.list, .paragraph): + } + case (.list, .paragraph): + if fitToWidth { + return 14.0 + } else { return 31.0 - case (.preformatted, .paragraph): - return 19.0 - case (.paragraph, .paragraph): - return 25.0 - case (_, .paragraph): - return 20.0 - case (.title, .list), (.authorDate, .list): - return 34.0 - case (.header, .list), (.subheader, .list): + } + case (.paragraph, .list): + if fitToWidth { + return 14.0 + } else { return 31.0 - case (.preformatted, .list): - return 19.0 - case (_, .list): + } + case (.formula, .paragraph): + return 19.0 + case (.paragraph, .paragraph): + if fitToWidth { + return 2.0 + } else { return 25.0 - case (.paragraph, .preformatted): + } + case (.title, .formula), (.authorDate, .formula): + return 34.0 + case (.header, .formula), (.subheader, .formula), (.heading, .formula): + if fitToWidth { + return 10.0 + } else { + return 25.0 + } + case (.list, .formula): + return 31.0 + case (.paragraph, .formula): + return 19.0 + case (_, .formula): + return 20.0 + case (.title, .list), (.authorDate, .list): + return 34.0 + case (.header, .list), (.subheader, .list), (.heading, .list): + return 31.0 + case (.preformatted, _), (_, .preformatted): + if fitToWidth { + return 12.0 + } else { return 19.0 - case (_, .preformatted): - return 20.0 - case (_, .header), (_, .subheader): - return 32.0 - default: - return 20.0 + } + case (.formula, .list): + if fitToWidth { + return 10.0 + } else { + return 25.0 + } + case (_, .list): + if fitToWidth { + return 10.0 + } else { + return 25.0 + } + case (_, .header), (_, .subheader), (_, .heading): + return 32.0 + default: + return 20.0 } - } else if let lower = lower { + } else if let lower { switch lower { - case .cover, .channelBanner, .details, .anchor: - return 0.0 - default: + case .cover, .channelBanner, .details, .anchor: + return 0.0 + default: + if fitToWidth { + switch kind { + case .topLevel: + switch lower { + case .heading: + return 6.0 + case .table: + return 10.0 + default: + return 5.0 + } + case .cell: + return 0.0 + case .detail, .list: + return 4.0 + } + } else { return 25.0 + } + } + } else if let upper { + switch kind { + case .topLevel: + if case .relatedArticles = upper { + return 0.0 + } else if case .thinking = upper { + return 2.0 + } else { + if fitToWidth { + return 5.0 + } else { + return 25.0 + } + } + case .detail, .list: + return 16.0 + case .cell: + return 0.0 } } else { - if let upper = upper, case .relatedArticles = upper { - return 0.0 - } else { - return 25.0 - } + return 0.0 } } diff --git a/submodules/InstantPageUI/Sources/InstantPageMath.swift b/submodules/InstantPageUI/Sources/InstantPageMath.swift new file mode 100644 index 0000000000..80acbe8624 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageMath.swift @@ -0,0 +1,79 @@ +import Foundation +import UIKit +import SwiftMath + +public enum InstantPageMathMode { + case inline + case block +} + +public struct InstantPageMathRenderResult { + public let image: UIImage + public let size: CGSize + public let width: CGFloat + public let ascent: CGFloat + public let descent: CGFloat +} + +public final class InstantPageMathAttachment: NSObject { + public let latex: String + public let fontSize: CGFloat + public let textColor: UIColor + public let mode: InstantPageMathMode + public let rendered: InstantPageMathRenderResult + + public init(latex: String, fontSize: CGFloat, textColor: UIColor, mode: InstantPageMathMode, rendered: InstantPageMathRenderResult) { + self.latex = latex + self.fontSize = fontSize + self.textColor = textColor + self.mode = mode + self.rendered = rendered + } + + func isEqual(to other: InstantPageMathAttachment) -> Bool { + return self.latex == other.latex + && self.fontSize == other.fontSize + && self.mode == other.mode + && self.textColor.isEqual(other.textColor) + && self.rendered.size == other.rendered.size + && self.rendered.ascent == other.rendered.ascent + && self.rendered.descent == other.rendered.descent + } +} + +func instantPageMathAttachment(latex: String, fontSize: CGFloat, textColor: UIColor, mode: InstantPageMathMode) -> InstantPageMathAttachment? { + let effectiveFontSize: CGFloat + let multiplier: CGFloat = 1.12 + switch mode { + case .inline: + effectiveFontSize = fontSize * multiplier + case .block: + effectiveFontSize = fontSize * multiplier + } + + guard let rendered = instantPageRenderMath(latex: latex, fontSize: effectiveFontSize, textColor: textColor, mode: mode) else { + return nil + } + return InstantPageMathAttachment(latex: latex, fontSize: effectiveFontSize, textColor: textColor, mode: mode, rendered: rendered) +} + +private func instantPageRenderMath(latex: String, fontSize: CGFloat, textColor: UIColor, mode: InstantPageMathMode) -> InstantPageMathRenderResult? { + let renderMode: MTMathUILabelMode + switch mode { + case .inline: + renderMode = .text + case .block: + renderMode = .display + } + + guard let rendered = MTMathRenderer.render(latex: latex, fontSize: fontSize, textColor: textColor, mode: renderMode) else { + return nil + } + return InstantPageMathRenderResult( + image: rendered.image, + size: rendered.size, + width: rendered.width, + ascent: rendered.ascent, + descent: rendered.descent + ) +} diff --git a/submodules/InstantPageUI/Sources/InstantPageMedia.swift b/submodules/InstantPageUI/Sources/InstantPageMedia.swift index ff9b2bc3f5..a7813f63bd 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMedia.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMedia.swift @@ -20,3 +20,28 @@ public struct InstantPageMedia: Equatable { return lhs.index == rhs.index && lhs.media == rhs.media && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit } } + +func instantPageMediaMatchesNodeIdentity(_ lhs: InstantPageMedia, _ rhs: InstantPageMedia) -> Bool { + if lhs.index != rhs.index { + return false + } + if lhs.url != rhs.url || lhs.caption != rhs.caption || lhs.credit != rhs.credit { + return false + } + if let lhsId = lhs.media.id, let rhsId = rhs.media.id { + return lhsId == rhsId + } + return lhs == rhs +} + +func instantPageMediaArraysMatchNodeIdentity(_ lhs: [InstantPageMedia], _ rhs: [InstantPageMedia]) -> Bool { + if lhs.count != rhs.count { + return false + } + for i in 0 ..< lhs.count { + if !instantPageMediaMatchesNodeIdentity(lhs[i], rhs[i]) { + return false + } + } + return true +} diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaOpen.swift b/submodules/InstantPageUI/Sources/InstantPageMediaOpen.swift new file mode 100644 index 0000000000..22ec793857 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageMediaOpen.swift @@ -0,0 +1,130 @@ +import Foundation +import Display +import Postbox +import SwiftSignalKit +import AccountContext +import TelegramCore +import TelegramUIPreferences +import GalleryUI +import LocationUI + +/// Routes a media tap (originating from V1's full-page Instant View, or from V2's in-bubble preview) +/// to the appropriate viewer: +/// - `.geo(map)` → pushes `LocationViewController` via `push`. +/// - `.webpage(webPage)` cover → single-entry `InstantPageGalleryController`. +/// - `.file(file)` with `isAnimated` → single-entry gallery in "playing video" mode. +/// - Default → multi-entry gallery built from `allMedias` (filtered to `.image` and non-audio +/// `.file` — audio/music siblings are excluded), centered on the tapped media; "playing +/// video" mode is on. +/// +/// Behavior matches V1's `InstantPageControllerNode.openMedia(_:)` bit-for-bit. +/// +/// - Parameters: +/// - allMedias: every laid-out media on the page, in laid-out order. Used to build sibling +/// entries when the gallery needs them. Callers may pass `[]` for paths that don't need +/// siblings (e.g. webpage-cover single-entry gallery), but it's safer to always pass the +/// full list — the helper filters/uses it only on the default branch. +/// - transitionArgsForMedia: invoked by the gallery presentation to find the source rect for +/// the swipe-back animation; return `nil` if the source view is not on screen. +/// - hiddenMediaCallback: invoked while the gallery is foregrounded so callers can hide the +/// source so the gallery's transitioning image isn't double-visible. +public func openInstantPageMedia( + media: InstantPageMedia, + allMedias: [InstantPageMedia], + webPage: TelegramMediaWebpage, + context: AccountContext, + userLocation: MediaResourceUserLocation, + present: (ViewController, Any?) -> Void, + push: (ViewController) -> Void, + openUrl: @escaping (InstantPageUrlItem) -> Void, + baseNavigationController: () -> NavigationController?, + transitionArgsForMedia: @escaping (InstantPageMedia) -> GalleryTransitionArguments?, + hiddenMediaCallback: @escaping (InstantPageMedia?) -> Void +) { + if case let .geo(map) = media.media { + let controllerParams = LocationViewParams(sendLiveLocation: { _ in + }, stopLiveLocation: { _ in + }, openUrl: { _ in }, openPeer: { _ in + }, showAll: false) + + let peer = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + + let controller = LocationViewController(context: context, subject: EngineMessage(message), params: controllerParams) + push(controller) + return + } + + var fromPlayingVideo = false + + var entries: [InstantPageGalleryEntry] = [] + if case let .webpage(webPage) = media.media { + entries.append(InstantPageGalleryEntry(index: 0, pageId: webPage.webpageId, media: media, caption: nil, credit: nil, location: nil)) + } else if case let .file(file) = media.media, file.isAnimated { + fromPlayingVideo = true + entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil)) + } else { + fromPlayingVideo = true + let filteredMedias = allMedias.filter { item in + switch item.media { + case .image: + return true + case let .file(file): + // Audio/music files are enrolled in `allMedias()` only so the audio playlist can + // gather its siblings (see `handleOpenAudioTap`); they are not visual gallery + // content, so keep videos/gifs/documents but exclude music and voice. + return !file.isMusic && !file.isVoice + default: + return false + } + } + + for media in filteredMedias { + entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: InstantPageGalleryEntryLocation(position: Int32(entries.count), totalCount: Int32(filteredMedias.count)))) + } + } + + var centralIndex: Int? + for i in 0 ..< entries.count { + if entries[i].media == media { + centralIndex = i + break + } + } + + if let centralIndex = centralIndex { + let controller = InstantPageGalleryController(context: context, userLocation: userLocation, webPage: webPage, entries: entries, centralIndex: centralIndex, fromPlayingVideo: fromPlayingVideo, replaceRootController: { _, _ in + }, baseNavigationController: baseNavigationController()) + let hiddenMediaDisposable = MetaDisposable() + hiddenMediaDisposable.set((controller.hiddenMedia |> deliverOnMainQueue).start(next: { entry in + hiddenMediaCallback(entry?.media) + })) + controller.openUrl = openUrl + + // The disposable lives as long as the gallery controller. Bind its lifetime to the + // controller by attaching it as an associated object so it survives until dismissal. + InstantPageMediaOpenDisposableBox.attach(disposable: hiddenMediaDisposable, to: controller) + + present(controller, InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry -> GalleryTransitionArguments? in + return transitionArgsForMedia(entry.media) + })) + } +} + +// MARK: - Disposable lifetime helper + +/// Holds a `MetaDisposable` that subscribes to the gallery controller's `hiddenMedia` signal. +/// Without this, the disposable would deallocate after `openInstantPageMedia` returns and the +/// subscription would stop firing. We attach it to the gallery controller via objc associated +/// objects so it lives as long as the controller does. +private final class InstantPageMediaOpenDisposableBox { + private static var key: UInt8 = 0 + let disposable: MetaDisposable + init(_ disposable: MetaDisposable) { self.disposable = disposable } + deinit { self.disposable.dispose() } + + static func attach(disposable: MetaDisposable, to controller: ViewController) { + let box = InstantPageMediaOpenDisposableBox(disposable) + objc_setAssociatedObject(controller, &key, box, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index 7d939eeb13..c7dc21c521 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -30,15 +30,28 @@ private func extractFileMedia(_ item: InstantPageMedia) -> TelegramMediaFile? { final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { let webPage: TelegramMediaWebpage + let messageReference: MessageReference? let id: SharedMediaPlaylistItemId let item: InstantPageMedia - - init(webPage: TelegramMediaWebpage, item: InstantPageMedia) { + + init(webPage: TelegramMediaWebpage, messageReference: MessageReference?, item: InstantPageMedia) { self.webPage = webPage + self.messageReference = messageReference self.id = InstantPageMediaPlaylistItemId(index: item.index) self.item = item } - + + private func fileReference(_ file: TelegramMediaFile) -> FileMediaReference { + // Require a resolvable message id (mirrors the playlist-key fallback in + // InstantPageV2MediaAudioView): a `.none`-content reference can't revalidate, so fall + // back to the webpage reference in that case. + if let messageReference = self.messageReference, messageReference.id != nil { + return .message(message: messageReference, media: file) + } else { + return .webPage(webPage: WebpageReference(self.webPage), media: file) + } + } + var stableId: AnyHashable { return self.item.index } @@ -49,13 +62,13 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { switch attribute { case let .Audio(isVoice, _, _, _, _): if isVoice { - return SharedMediaPlaybackData(type: .voice, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) + return SharedMediaPlaybackData(type: .voice, source: .telegramFile(reference: self.fileReference(file), isCopyProtected: false, isViewOnce: false)) } else { - return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) + return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: self.fileReference(file), isCopyProtected: false, isViewOnce: false)) } case let .Video(_, _, flags, _, _, _): if flags.contains(.instantRoundVideo) { - return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) + return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: self.fileReference(file), isCopyProtected: false, isViewOnce: false)) } else { return nil } @@ -64,12 +77,12 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } } if file.mimeType.hasPrefix("audio/") { - return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) + return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: self.fileReference(file), isCopyProtected: false, isViewOnce: false)) } if let fileName = file.fileName { let ext = (fileName as NSString).pathExtension.lowercased() if ext == "wav" || ext == "opus" { - return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) + return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: self.fileReference(file), isCopyProtected: false, isViewOnce: false)) } } } @@ -116,14 +129,15 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } } -struct InstantPageMediaPlaylistId: SharedMediaPlaylistId { - let webpageId: EngineMedia.Id - - func isEqual(to: SharedMediaPlaylistId) -> Bool { - if let to = to as? InstantPageMediaPlaylistId { - return self.webpageId == to.webpageId +public enum InstantPageMediaPlaylistId: Equatable, SharedMediaPlaylistId { + case instantPage(webpageId: EngineMedia.Id) + case richMessage(messageId: EngineMessage.Id) + + public func isEqual(to: SharedMediaPlaylistId) -> Bool { + guard let to = to as? InstantPageMediaPlaylistId else { + return false } - return false + return self == to } } @@ -134,15 +148,13 @@ struct InstantPagePlaylistLocation: Equatable, SharedMediaPlaylistLocation { guard let to = to as? InstantPagePlaylistLocation else { return false } - if self.webpageId == to.webpageId { - return false - } - return true + return self.webpageId == to.webpageId } } public final class InstantPageMediaPlaylist: SharedMediaPlaylist { private let webPage: TelegramMediaWebpage + private let messageReference: MessageReference? private let items: [InstantPageMedia] private let initialItemIndex: Int @@ -164,15 +176,16 @@ public final class InstantPageMediaPlaylist: SharedMediaPlaylist { return self.stateValue.get() } - public init(webPage: TelegramMediaWebpage, items: [InstantPageMedia], initialItemIndex: Int) { + public init(playlistId: InstantPageMediaPlaylistId, webPage: TelegramMediaWebpage, messageReference: MessageReference?, items: [InstantPageMedia], initialItemIndex: Int) { assert(Queue.mainQueue().isCurrent()) - - self.id = InstantPageMediaPlaylistId(webpageId: webPage.webpageId) - + + self.id = playlistId + self.webPage = webPage + self.messageReference = messageReference self.items = items self.initialItemIndex = initialItemIndex - + self.control(.next) } @@ -243,7 +256,7 @@ public final class InstantPageMediaPlaylist: SharedMediaPlaylist { } private func updateState() { - self.stateValue.set(.single(SharedMediaPlaylistState(loading: false, playedToEnd: self.playedToEnd, item: self.currentItem.flatMap({ InstantPageMediaPlaylistItem(webPage: self.webPage, item: $0) }), nextItem: nil, previousItem: nil, order: self.order, looping: self.looping))) + self.stateValue.set(.single(SharedMediaPlaylistState(loading: false, playedToEnd: self.playedToEnd, item: self.currentItem.flatMap({ InstantPageMediaPlaylistItem(webPage: self.webPage, messageReference: self.messageReference, item: $0) }), nextItem: nil, previousItem: nil, order: self.order, looping: self.looping))) } public func onItemPlaybackStarted(_ item: SharedMediaPlaylistItem) { diff --git a/submodules/InstantPageUI/Sources/InstantPageMultiTextAdapter.swift b/submodules/InstantPageUI/Sources/InstantPageMultiTextAdapter.swift new file mode 100644 index 0000000000..b394f154ea --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageMultiTextAdapter.swift @@ -0,0 +1,349 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import TextFormat + +public final class InstantPageMultiTextAdapter: ASDisplayNode, TextNodeProtocol { + public struct Entry { + public let item: InstantPageTextItem + public let frameOrigin: CGPoint + + public init(item: InstantPageTextItem, frameOrigin: CGPoint) { + self.item = item + self.frameOrigin = frameOrigin + } + } + + private struct InternalEntry { + let item: InstantPageTextItem + let charOffset: Int + let frameOrigin: CGPoint + } + + private let entries: [InternalEntry] + private let combinedString: NSAttributedString + + public init(entries: [Entry]) { + let separator = NSAttributedString(string: "\n\n") + let combined = NSMutableAttributedString() + var internalEntries: [InternalEntry] = [] + for (index, entry) in entries.enumerated() { + let charOffset = combined.length + internalEntries.append(InternalEntry(item: entry.item, charOffset: charOffset, frameOrigin: entry.frameOrigin)) + combined.append(entry.item.attributedString) + if index != entries.count - 1 { + combined.append(separator) + } + } + self.entries = internalEntries + self.combinedString = combined + super.init() + self.isUserInteractionEnabled = false + } + + public var currentText: NSAttributedString? { + return self.combinedString + } + + public func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? { + for entry in self.entries { + let localPoint = CGPoint(x: point.x - entry.frameOrigin.x, y: point.y - entry.frameOrigin.y) + if let (localIndex, attrs) = entry.item.attributesAtPoint(localPoint, orNearest: false) { + return (entry.charOffset + localIndex, attrs) + } + } + guard orNearest, !self.entries.isEmpty else { + return nil + } + var nearestEntry = self.entries[0] + var nearestDistance = CGFloat.greatestFiniteMagnitude + for entry in self.entries { + let frame = CGRect(origin: entry.frameOrigin, size: entry.item.frame.size) + let distance: CGFloat + if point.y < frame.minY { + distance = frame.minY - point.y + } else if point.y > frame.maxY { + distance = point.y - frame.maxY + } else { + distance = 0.0 + } + if distance < nearestDistance { + nearestDistance = distance + nearestEntry = entry + } + } + let localPoint = CGPoint(x: point.x - nearestEntry.frameOrigin.x, y: point.y - nearestEntry.frameOrigin.y) + if let (localIndex, attrs) = nearestEntry.item.attributesAtPoint(localPoint, orNearest: true) { + return (nearestEntry.charOffset + localIndex, attrs) + } + return nil + } + + public func textRangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + var allRects: [CGRect] = [] + var startEdge: TextRangeRectEdge? + var endEdge: TextRangeRectEdge? + for entry in self.entries { + let itemLength = entry.item.attributedString.length + let entryRange = NSRange(location: entry.charOffset, length: itemLength) + let intersection = NSIntersectionRange(range, entryRange) + if intersection.length == 0 { + continue + } + let localRange = NSRange(location: intersection.location - entry.charOffset, length: intersection.length) + guard let result = entry.item.textRangeRects(in: localRange) else { + continue + } + for rect in result.rects { + allRects.append(rect.offsetBy(dx: entry.frameOrigin.x, dy: entry.frameOrigin.y)) + } + let translatedStart = TextRangeRectEdge(x: result.start.x + entry.frameOrigin.x, y: result.start.y + entry.frameOrigin.y, height: result.start.height) + let translatedEnd = TextRangeRectEdge(x: result.end.x + entry.frameOrigin.x, y: result.end.y + entry.frameOrigin.y, height: result.end.height) + if startEdge == nil { + startEdge = translatedStart + } + endEdge = translatedEnd + } + guard !allRects.isEmpty, let start = startEdge, let end = endEdge else { + return nil + } + return (allRects, start, end) + } + + public func markdownForRange(_ range: NSRange) -> String { + struct Segment { + let context: InstantPageMarkdownBlockContext? + let inline: String + } + + var segments: [Segment] = [] + for entry in self.entries { + let entryRange = NSRange(location: entry.charOffset, length: entry.item.attributedString.length) + let intersection = NSIntersectionRange(range, entryRange) + if intersection.length == 0 { + continue + } + let localRange = NSRange(location: intersection.location - entry.charOffset, length: intersection.length) + let slice = entry.item.attributedString.attributedSubstring(from: localRange) + let inline = inlineMarkdown(from: slice) + if inline.isEmpty { + continue + } + segments.append(Segment(context: entry.item.markdownContext, inline: inline)) + } + if segments.isEmpty { + return "" + } + + func quotePrefixed(_ text: String, depth: Int) -> String { + guard depth > 0 else { return text } + let q = String(repeating: ">", count: depth) + " " + return text.split(separator: "\n", omittingEmptySubsequences: false).map { q + String($0) }.joined(separator: "\n") + } + + func taskMarker(_ c: Bool?) -> String { + switch c { + case .some(false): return "[ ] " + case .some(true): return "[x] " + case .none: return "" + } + } + + // Renders one segment's block content WITHOUT the quote prefix and without + // cross-segment coalescing. Used for segments inside a quoted run, where each + // line carries its own depth and the whole quote is one `\n`-joined block. + func renderContent(_ seg: Segment) -> String { + switch seg.context?.kind { + case let .code(language): + return "```\(language ?? "")\n\(seg.inline)\n```" + case .tableCell: + return "| " + escapeSelectionTableCell(seg.inline) + " |" + case let .listItem(_, marker, checked): + return "\(marker) \(taskMarker(checked))" + seg.inline.replacingOccurrences(of: "\n", with: " ") + case let .heading(level): + let hashes = String(repeating: "#", count: max(1, min(6, level))) + return "\(hashes) \(seg.inline)" + case .title: + return "# \(seg.inline)" + case .paragraph, .none: + return seg.inline + } + } + + var groups: [String] = [] + var index = 0 + while index < segments.count { + let seg = segments[index] + let depth = seg.context?.quoteDepth ?? 0 + + // A blockquote is exploded by the layout into one text item per child line, + // each stamped with quoteDepth > 0. Re-coalesce a run of consecutive quoted + // segments into a single block whose lines are joined by `\n` (each carrying + // its own `> ` depth), matching the whole-message converter — otherwise every + // quote line would become its own block and be separated by a blank line. + if depth > 0 { + var lines: [String] = [] + var j = index + while j < segments.count, let d = segments[j].context?.quoteDepth, d > 0 { + lines.append(quotePrefixed(renderContent(segments[j]), depth: d)) + j += 1 + } + groups.append(lines.joined(separator: "\n")) + index = j + continue + } + + switch seg.context?.kind { + case let .code(language): + var body = [seg.inline] + var j = index + 1 + while j < segments.count, + case let .code(lang2)? = segments[j].context?.kind, + lang2 == language, + (segments[j].context?.quoteDepth ?? 0) == depth { + body.append(segments[j].inline) + j += 1 + } + let fence = "```\(language ?? "")\n" + body.joined(separator: "\n") + "\n```" + groups.append(quotePrefixed(fence, depth: depth)) + index = j + case let .tableCell(row, _, _): + var rows: [[String]] = [[escapeSelectionTableCell(seg.inline)]] + var currentRow = row + var j = index + 1 + while j < segments.count, + case let .tableCell(row2, _, _)? = segments[j].context?.kind, + (segments[j].context?.quoteDepth ?? 0) == depth { + if row2 != currentRow { + rows.append([]) + currentRow = row2 + } + rows[rows.count - 1].append(escapeSelectionTableCell(segments[j].inline)) + j += 1 + } + let tableBlock = rows.map { "| " + $0.joined(separator: " | ") + " |" }.joined(separator: "\n") + groups.append(quotePrefixed(tableBlock, depth: depth)) + index = j + case let .listItem(_, marker, checked): + let firstLine = "\(marker) \(taskMarker(checked))" + seg.inline.replacingOccurrences(of: "\n", with: " ") + var lines = [firstLine] + var j = index + 1 + while j < segments.count, case let .listItem(_, marker2, checked2)? = segments[j].context?.kind, (segments[j].context?.quoteDepth ?? 0) == depth { + lines.append("\(marker2) \(taskMarker(checked2))" + segments[j].inline.replacingOccurrences(of: "\n", with: " ")) + j += 1 + } + groups.append(quotePrefixed(lines.joined(separator: "\n"), depth: depth)) + index = j + case let .heading(level): + let hashes = String(repeating: "#", count: max(1, min(6, level))) + groups.append(quotePrefixed("\(hashes) \(seg.inline)", depth: depth)) + index += 1 + case .title: + groups.append(quotePrefixed("# \(seg.inline)", depth: depth)) + index += 1 + case .paragraph, .none: + groups.append(quotePrefixed(seg.inline, depth: depth)) + index += 1 + } + } + + return groups.joined(separator: "\n\n") + } +} + +private func escapeSelectionMarkdown(_ string: String) -> String { + var result = "" + result.reserveCapacity(string.count) + for character in string { + switch character { + case "\\", "*", "_", "`", "[", "]", "~", "|": + result.append("\\") + result.append(character) + default: + result.append(character) + } + } + return result +} + +private func escapeSelectionTableCell(_ string: String) -> String { + return string.replacingOccurrences(of: "\n", with: " ") +} + +/// Converts a styled slice of an InstantPage text item into inline markdown, +/// reading the same attributes the renderer wrote (font-based bold/italic/mono, +/// strikethrough style, the TelegramTextAttributes.URL link item, custom emoji). +private func inlineMarkdown(from slice: NSAttributedString) -> String { + let fullRange = NSRange(location: 0, length: slice.length) + var result = "" + + slice.enumerateAttributes(in: fullRange, options: []) { attributes, range, _ in + let substring = (slice.string as NSString).substring(with: range) + + // Custom emoji: emit the shared marker carrying the fileId. The display + // placeholder may have no real alt (often a single space), so alt is + // best-effort; whole-message copy / edit reconstruction have the true alt. + if let emojiAttribute = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + // Non-empty link text required: CommonMark drops `[](url)` on re-parse. + let alt = substring.isEmpty ? " " : substring + result += "[\(escapeCustomEmojiMarkdownAlt(alt))](\(customEmojiMarkdownURL(fileId: emojiAttribute.fileId)))" + return + } + + var bold = false + var italic = false + var mono = false + if let font = attributes[.font] as? UIFont { + let name = font.fontName.lowercased() + if name.hasPrefix(".sfui") || name.hasPrefix(".applesystemui") { + let traits = font.fontDescriptor.symbolicTraits + if traits.contains(.traitMonoSpace) { + mono = true + } else { + bold = traits.contains(.traitBold) + italic = traits.contains(.traitItalic) + } + } else if name.contains("menlo") || name.contains("courier") || name.contains("sfmono") { + mono = true + } else { + if name.contains("bolditalic") { + bold = true; italic = true + } else if name.contains("bold") { + bold = true + } else if name.contains("italic") { + italic = true + } + } + } + let strike = attributes[.strikethroughStyle] != nil + + var inner: String + if mono { + // Inline code takes no nested emphasis; emit the raw text in backticks. + inner = "`\(substring)`" + } else { + inner = escapeSelectionMarkdown(substring) + if strike { inner = "~~\(inner)~~" } + if bold && italic { + inner = "***\(inner)***" + } else if bold { + inner = "**\(inner)**" + } else if italic { + inner = "*\(inner)*" + } + } + + if let urlItem = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? InstantPageUrlItem { + let url = urlItem.url + let needsBrackets = url.contains("(") || url.contains(")") || url.contains(" ") + let destination = needsBrackets ? "<\(url)>" : url + inner = "[\(inner)](\(destination))" + } + + result += inner + } + + return result +} diff --git a/submodules/InstantPageUI/Sources/InstantPageNode.swift b/submodules/InstantPageUI/Sources/InstantPageNode.swift index c235614961..fb011c454f 100644 --- a/submodules/InstantPageUI/Sources/InstantPageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageNode.swift @@ -2,8 +2,13 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import TelegramCore import TelegramPresentationData +public protocol InstantPageExternalMediaDimensionsNode: AnyObject { + var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? { get set } +} + public protocol InstantPageNode: ASDisplayNode { func updateIsVisible(_ isVisible: Bool) diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift index dee69b4aac..8fe23019a6 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift @@ -309,7 +309,21 @@ public final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode @objc func joinPressed() { if let peer = self.peer, case .notJoined = self.joinState { self.updateJoinState(.inProgress) - self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in + self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(next: { [weak self] result in + guard let strongSelf = self else { + return + } + switch result { + case .joined: + break + case let .webView(webView): + if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let controller = navigationController.viewControllers.last as? ViewController { + strongSelf.context.sharedContext.openJoinChatWebView(context: strongSelf.context, parentController: controller, updatedPresentationData: nil, webView: webView) + } else if case .inProgress = strongSelf.joinState { + strongSelf.updateJoinState(.notJoined) + } + } + }, error: { [weak self] _ in if let strongSelf = self { if case .inProgress = strongSelf.joinState { strongSelf.updateJoinState(.notJoined) diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift index 97a545133d..fc3d3b47f9 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift @@ -68,12 +68,12 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler self.addSubnode(self.videoNode) if case let .file(file) = media.media { - self.fetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource)).start()) + self.fetchedDisposable.set(context.engine.resources.fetch(reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource), userLocation: userLocation, userContentType: .video).start()) - self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: { [weak self] status in + self.statusDisposable.set((context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) + strongSelf.fetchStatus = status strongSelf.updateFetchStatus() } } diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift index 50c1222805..c60553a9c8 100644 --- a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift @@ -52,6 +52,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/InstantPageUI/Sources/InstantPageRenderer.swift b/submodules/InstantPageUI/Sources/InstantPageRenderer.swift new file mode 100644 index 0000000000..23d8d00a07 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageRenderer.swift @@ -0,0 +1,2469 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import CheckNode +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import GalleryUI +import ComponentFlow +import TextFormat +import EmojiTextAttachmentView +import AnimationCache +import MultiAnimationRenderer +import InvisibleInkDustNode +import ShimmeringMask + +// MARK: - Stable item identity (for view reuse on re-layouts) + +/// Stable identity for an `InstantPageV2LaidOutItem` across `update()` calls. The renderer +/// uses this to harvest existing item views and reuse them when the new layout still has +/// an item with the same id — preventing the media wrappers from torching their fetch +/// signals + image content on every chat-bubble re-apply. +/// +/// Media items use their `media.index` (already unique within a page and used as the +/// gallery registry key). Details items use their `details.index`. Other items have no +/// intrinsic identity, so the renderer assigns them a `(case-tag, positional-index-in-items)` +/// pair. +public enum InstantPageV2StableItemId: Hashable { + case media(Int) // media.index (4 media cases share this namespace) + case details(Int) // details.index + case thinking(Int) // thinking-block sequence index (own namespace) + case positional(InstantPageV2ItemKind, Int) // (caseTag, items-array position) +} + +public enum InstantPageV2ItemKind: Hashable { + case text, codeBlock, divider, listMarker, blockQuoteBar, shape, mediaPlaceholder, table, anchor, formula, slideshow +} + +// MARK: - Render context + +/// Bundle of render-time dependencies required to display real media inside an InstantPage V2 +/// view. Tied to an `InstantPageV2View` for the view's lifetime — if any field would change +/// (typically because the bubble was recycled with a different webpage), the caller must +/// rebuild the V2View with a fresh render context. +/// +/// `renderContext == nil` is permitted: the V2View falls back to grey-placeholder rendering +/// for the four media kinds (image/video/map/coverImage). This keeps the existing zero-arg +/// `InstantPageV2View()` constructor usable. +public final class InstantPageV2RenderContext { + public let context: AccountContext + public private(set) var webpage: TelegramMediaWebpage + public let sourceLocation: InstantPageSourceLocation + public let imageReference: (TelegramMediaImage) -> ImageMediaReference + public let fileReference: (TelegramMediaFile) -> FileMediaReference + public let present: (ViewController, Any?) -> Void + public let push: (ViewController) -> Void + public let openUrl: (InstantPageUrlItem) -> Void + public let baseNavigationController: () -> NavigationController? + /// A reference to the message hosting this page, when rendered inside a chat bubble. Used to + /// key audio playback per message (`.richMessage(message.id)`) AND to fetch audio files via a + /// message reference (so a stale file reference can revalidate); `nil` in the send preview, + /// which falls back to the webpage-keyed playlist id + webpage file reference. + public let message: MessageReference? + + public init( + context: AccountContext, + webpage: TelegramMediaWebpage, + sourceLocation: InstantPageSourceLocation, + imageReference: @escaping (TelegramMediaImage) -> ImageMediaReference, + fileReference: @escaping (TelegramMediaFile) -> FileMediaReference, + present: @escaping (ViewController, Any?) -> Void, + push: @escaping (ViewController) -> Void, + openUrl: @escaping (InstantPageUrlItem) -> Void, + baseNavigationController: @escaping () -> NavigationController?, + message: MessageReference? + ) { + self.context = context + self.webpage = webpage + self.sourceLocation = sourceLocation + self.imageReference = imageReference + self.fileReference = fileReference + self.present = present + self.push = push + self.openUrl = openUrl + self.baseNavigationController = baseNavigationController + self.message = message + } + + /// Update the content-bearing fields for a later chunk of the SAME message. Enables the + /// streaming bubble to reuse one V2View across `stableVersion` bumps instead of rebuilding. + /// Only `webpage` changes across chunks; the `imageReference`/`fileReference` closures keep + /// their construction-time `MessageReference` snapshot, which is acceptable because the message + /// id is stable across chunks (media resolves by id) and streamed AI content carries no media. + public func updateContent(webpage: TelegramMediaWebpage) { + self.webpage = webpage + } +} + +// MARK: - Inline image view data + +private struct InlineImageKey: Hashable { + let fileId: Int64 + let occurrenceIndex: Int +} + +/// Per-inline-image state owned by `InstantPageV2View`. +/// `textView` is a weak reference back to the parent so `updateImageReveal` can +/// look up the current per-text-view character count. +private final class InstantPageInlineImageData { + let view: InstantPageV2InlineImageView + weak var textView: InstantPageV2TextView? + var charIndexInItem: Int = 0 + var revealed: Bool = false + + init(view: InstantPageV2InlineImageView) { + self.view = view + } +} + +// MARK: - Emoji layer data + +private final class InstantPageEmojiLayerData { + let itemLayer: InlineStickerItemLayer + weak var textView: InstantPageV2TextView? + var charIndexInItem: Int = 0 + var revealed: Bool = false + + init(itemLayer: InlineStickerItemLayer) { + self.itemLayer = itemLayer + } +} + +// MARK: - Public renderer + +public final class InstantPageV2View: UIView { + public private(set) var currentLayout: InstantPageV2Layout? + public private(set) var currentTheme: InstantPageTheme? + + /// Invoked when a details title is tapped. Bubble routes to its expand-state mutation + requestUpdate. + public var detailsTapped: ((_ index: Int) -> Void)? + + var itemViews: [InstantPageItemView] = [] + private var itemViewStableIds: [InstantPageV2StableItemId] = [] + + public let renderContext: InstantPageV2RenderContext? + + private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InstantPageEmojiLayerData] = [:] + private var inlineImageViews: [InlineImageKey: InstantPageInlineImageData] = [:] + private var emojiEnableLooping: Bool = true + + /// Scroll-visibility rect in this view's coordinate space; gates emoji animation looping. + /// `nil` means "not visible" → emoji don't animate. The root rect is set by the bubble and + /// propagated down the nested tree (details/table) by `propagateVisibilityRect`. + public var visibilityRect: CGRect? { + didSet { + if oldValue != self.visibilityRect { + self.updateEmojiVisibility() + } + } + } + public private(set) var displayContentsUnderSpoilers: Bool = false + + // Weak references to every media wrapper in the tree, keyed by `InstantPageMedia.index`. + // Used by `transitionArgsFor` and `applyHiddenMedia` so the gallery transition + hidden-source + // state can find a wrapper without walking the view hierarchy. Nested V2Views (details body, + // table cells) forward their registrations to the root via `rootMediaRegistryHost`. + var mediaRegistry: [Int: Weak] = [:] + + // Pointer to the root V2View's registry host. The root sets this to `self`; nested views + // inherit it via `propagateRegistryHost(to:)` in `update(layout:theme:animation:)`. + weak var rootMediaRegistryHost: InstantPageV2View? + + var effectiveRegistryHost: InstantPageV2View { + return self.rootMediaRegistryHost ?? self + } + + /// Walks the `rootMediaRegistryHost` chain transitively until it finds a self-referencing + /// host (the true root). Necessary because nested details blocks can leave an inner body's + /// `rootMediaRegistryHost` pointing at an intermediate body rather than the outer root — + /// `propagateRegistryHost(to:)` only walks one hop, so the chain must be followed at lookup. + var trueRegistryRoot: InstantPageV2View { + var host: InstantPageV2View = self + while let next = host.rootMediaRegistryHost, next !== host { + host = next + } + return host + } + + public init(renderContext: InstantPageV2RenderContext?) { + self.renderContext = renderContext + super.init(frame: .zero) + self.backgroundColor = .clear + self.isOpaque = false + self.rootMediaRegistryHost = self + } + + public convenience init() { + self.init(renderContext: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + /// Rebuilds the child view hierarchy from `layout`. The caller is responsible for + /// sizing `self.frame` to `layout.contentSize`; this method does not touch its own frame. + /// + /// Reuse pass: existing item views are harvested into a `[stableId: view]` map keyed + /// off `itemViewStableIds`. For each new item we look up its stable id; on a hit (and + /// matching concrete view class) the existing view is reused via its typed + /// `update(item:theme:[renderContext:])`, on a miss we fall back to `makeItemView`. Un-reused + /// views are removed from the superview at the end. This preserves the four media wrappers + /// (and any nested V2Views inside details/table) across chat-bubble re-applies, which would + /// otherwise torch in-flight image fetches on every list update. + public func update( + layout: InstantPageV2Layout, + theme: InstantPageTheme, + animation: ListViewItemUpdateAnimation + ) { + // Build map of existing views by stable id. + var oldViewsById: [InstantPageV2StableItemId: InstantPageItemView] = [:] + for (oldIndex, oldId) in self.itemViewStableIds.enumerated() { + oldViewsById[oldId] = self.itemViews[oldIndex] + } + + var newItemViews: [InstantPageItemView] = [] + var newStableIds: [InstantPageV2StableItemId] = [] + var reusedIds: Set = [] + + // Two independent position counters so thinking-block churn never renumbers content + // blocks' stable ids (requirement: adding/removing a thinking block must not affect other + // blocks). Content items are numbered ignoring thinking items; thinking items get their + // own .thinking(index) namespace. + var contentPosition = 0 + var thinkingPosition = 0 + for item in layout.items { + let id: InstantPageV2StableItemId + if case .thinking = item { + id = InstantPageV2View.stableId(for: item, atPosition: thinkingPosition) + thinkingPosition += 1 + } else { + id = InstantPageV2View.stableId(for: item, atPosition: contentPosition) + contentPosition += 1 + } + + if let existing = oldViewsById[id], let reusedView = self.reuse(existingView: existing, for: item, theme: theme, animation: animation) { + let newFrame = InstantPageV2View.actualFrame(forItem: item) // parent positions child + if animation.isAnimated && reusedView.frame != newFrame { + // A collapsing details view keeps its body alive; remove it once this + // frame-shrink (the clip that hides it) finishes — see finalizePendingCollapse(). + let detailsView = reusedView as? InstantPageV2DetailsView + animation.animator.updateFrame(layer: reusedView.layer, frame: newFrame, completion: { [weak detailsView] _ in + detailsView?.finalizePendingCollapse() + }) + } else { + reusedView.frame = newFrame + (reusedView as? InstantPageV2DetailsView)?.finalizePendingCollapse() + } + newItemViews.append(reusedView) + newStableIds.append(id) + reusedIds.insert(id) + // Already in subviews from the previous update; just keep it. + } else { + guard let newView = self.makeItemView(for: item, theme: theme) else { continue } + newItemViews.append(newView) + newStableIds.append(id) + self.addSubview(newView) + self.propagateRegistryHost(to: newView) + } + } + + // Remove views that weren't reused. + for (id, view) in oldViewsById where !reusedIds.contains(id) { + view.removeFromSuperview() + } + + // Z-order: bring the reused views to the front in declaration order so the + // sublayer/subview stack matches `layout.items` order. + for view in newItemViews { + self.bringSubviewToFront(view) + } + + self.itemViews = newItemViews + self.itemViewStableIds = newStableIds + self.currentLayout = layout + self.currentTheme = theme + self.updateInlineImages() + self.updateInlineEmoji() + let enableSpoilerAnimations = self.renderContext.map { $0.context.sharedContext.energyUsageSettings.fullTranslucency } ?? true + for view in self.itemViews { + if let textView = view as? InstantPageV2TextView { + // Both fresh (makeItemView) and reused text views now build their dust through the + // single init→update→updateSpoiler path, so we only push the external animation + // setting here; its didSet rebuilds the dust if the value actually changed. + textView.enableSpoilerAnimations = enableSpoilerAnimations + } + } + // Force the current reveal state (true OR false) onto every text view every layout, so a + // positionally-reused text view cannot retain a stale reveal flag from prior content. + self.setDisplayContentsUnderSpoilers(self.displayContentsUnderSpoilers, atLocation: nil, animated: false) + } + + func updateInlineEmoji() { + guard let rc = self.renderContext else { return } + let context = rc.context + let cache = context.animationCache + let renderer = context.animationRenderer + self.emojiEnableLooping = context.sharedContext.energyUsageSettings.loopEmoji + + var nextIndexById: [Int64: Int] = [:] + var validIds: [InlineStickerItemLayer.Key] = [] + + for view in self.itemViews { + // Top-level `.text` items host their emoji directly. The thinking block hosts emoji on + // its shimmer-wrapped inner text view, which the page never sees as a top-level item — + // so without this it is skipped and the emoji never get layers (invisible). Nested V2 + // sub-layouts (details bodies, table cells) instead run their own updateInlineEmoji. + let textView: InstantPageV2TextView + if let topLevelTextView = view as? InstantPageV2TextView { + textView = topLevelTextView + } else if let thinkingView = view as? InstantPageV2ThinkingView { + textView = thinkingView.textView + } else { + continue + } + let textItem = textView.item.textItem + let boundsWidth = textItem.frame.size.width + for line in textItem.lines { + if line.emojiItems.isEmpty { continue } + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + for emojiItem in line.emojiItems { + let index = nextIndexById[emojiItem.emoji.fileId] ?? 0 + nextIndexById[emojiItem.emoji.fileId] = index + 1 + let id = InlineStickerItemLayer.Key(id: emojiItem.emoji.fileId, index: index) + validIds.append(id) + + let itemSize = emojiItem.frame.width + let localX = lineFrame.minX + (emojiItem.frame.minX - line.frame.minX) + let itemFrame = CGRect( + x: localX + v2TextViewClippingInset, + y: emojiItem.frame.minY + v2TextViewClippingInset, + width: itemSize, + height: itemSize + ) + + var textColor: UIColor? + if emojiItem.range.location < textItem.attributedString.length { + textColor = textItem.attributedString.attribute(.foregroundColor, at: emojiItem.range.location, effectiveRange: nil) as? UIColor + } + + let data: InstantPageEmojiLayerData + if let existing = self.inlineStickerItemLayers[id] { + data = existing + if data.itemLayer.superlayer !== textView.emojiContainerView.layer { + textView.emojiContainerView.layer.addSublayer(data.itemLayer) + } + } else { + let pointSize = floor(itemSize * 1.3) + let layer = InlineStickerItemLayer( + context: context, + userLocation: .other, + attemptSynchronousLoad: false, + emoji: emojiItem.emoji, + file: emojiItem.emoji.file, + cache: cache, + renderer: renderer, + placeholderColor: UIColor(white: 0.5, alpha: 0.3), + pointSize: CGSize(width: pointSize, height: pointSize), + dynamicColor: textColor + ) + layer.opacity = 0.0 + data = InstantPageEmojiLayerData(itemLayer: layer) + self.inlineStickerItemLayers[id] = data + textView.emojiContainerView.layer.addSublayer(layer) + } + + data.itemLayer.dynamicColor = textColor + data.itemLayer.frame = itemFrame + data.textView = textView + data.charIndexInItem = emojiItem.range.location + } + } + } + + var removeKeys: [InlineStickerItemLayer.Key] = [] + for (key, data) in self.inlineStickerItemLayers where !validIds.contains(key) { + removeKeys.append(key) + data.itemLayer.removeFromSuperlayer() + } + for key in removeKeys { + self.inlineStickerItemLayers.removeValue(forKey: key) + } + + self.updateEmojiReveal(animated: false) + } + + func updateInlineImages() { + guard let renderContext = self.renderContext, + let layout = self.currentLayout, + let theme = self.currentTheme + else { return } + let context = renderContext.context + + var nextIndexById: [Int64: Int] = [:] + var validKeys: Set = [] + + for view in self.itemViews { + // Same nesting as updateInlineEmoji: top-level `.text` items host their inline images + // directly; the thinking block hosts them on its shimmer-wrapped inner text view, which + // the page never sees as a top-level item. Nested V2 sub-layouts run their own pass. + let textView: InstantPageV2TextView + if let topLevelTextView = view as? InstantPageV2TextView { + textView = topLevelTextView + } else if let thinkingView = view as? InstantPageV2ThinkingView { + textView = thinkingView.textView + } else { + continue + } + let textItem = textView.item.textItem + let boundsWidth = textItem.frame.size.width + for line in textItem.lines { + if line.imageItems.isEmpty { continue } + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + for imageItem in line.imageItems { + let index = nextIndexById[imageItem.id.id] ?? 0 + nextIndexById[imageItem.id.id] = index + 1 + let key = InlineImageKey(fileId: imageItem.id.id, occurrenceIndex: index) + guard let media = layout.media[imageItem.id] else { continue } + validKeys.insert(key) + + let localX = lineFrame.minX + (imageItem.frame.minX - line.frame.minX) + let itemFrame = CGRect( + x: localX + v2TextViewClippingInset, + y: imageItem.frame.minY + v2TextViewClippingInset, + width: imageItem.frame.width, + height: imageItem.frame.height + ) + + let data: InstantPageInlineImageData + if let existing = self.inlineImageViews[key] { + data = existing + if data.view.superview !== textView.imageContainerView { + textView.imageContainerView.addSubview(data.view) + } + } else { + let newView = InstantPageV2InlineImageView( + media: media, + webpage: layout.webpage, + frame: itemFrame, + context: context, + userLocation: .other, + theme: theme + ) + // Image starts hidden; updateImageReveal pops it in when the streaming + // cursor crosses its char-index. For non-streaming pages (no + // `TypingDraftMessageAttribute`), `revealed = true` (via the fallback in + // updateImageReveal) sets alpha = 1 unconditionally on the next call. + newView.alpha = 0.0 + data = InstantPageInlineImageData(view: newView) + self.inlineImageViews[key] = data + textView.imageContainerView.addSubview(newView) + } + + data.view.frame = itemFrame + data.textView = textView + data.charIndexInItem = imageItem.range.location + } + } + } + + var removeKeys: [InlineImageKey] = [] + for (key, data) in self.inlineImageViews where !validKeys.contains(key) { + removeKeys.append(key) + data.view.removeFromSuperview() + } + for key in removeKeys { + self.inlineImageViews.removeValue(forKey: key) + } + + self.updateImageReveal(animated: false) + } + + func updateEmojiReveal(animated: Bool) { + for (_, data) in self.inlineStickerItemLayers { + let revealed: Bool + if let textView = data.textView, let count = textView.currentRevealCharacterCount { + revealed = data.charIndexInItem < count + } else { + revealed = true + } + if data.revealed == revealed { + continue + } + data.revealed = revealed + if revealed { + if animated { + data.itemLayer.opacity = 1.0 + let opacityAnim = CABasicAnimation(keyPath: "opacity") + opacityAnim.fromValue = 0.0 + opacityAnim.toValue = 1.0 + opacityAnim.duration = 0.2 + data.itemLayer.add(opacityAnim, forKey: "emojiRevealOpacity") + let scaleAnim = CABasicAnimation(keyPath: "transform.scale") + scaleAnim.fromValue = 0.1 + scaleAnim.toValue = 1.0 + scaleAnim.duration = 0.2 + scaleAnim.timingFunction = CAMediaTimingFunction(name: .easeOut) + data.itemLayer.add(scaleAnim, forKey: "emojiRevealScale") + } else { + data.itemLayer.opacity = 1.0 + } + } else { + data.itemLayer.opacity = 0.0 + } + } + self.updateEmojiVisibility() + } + + func updateImageReveal(animated: Bool) { + for (_, data) in self.inlineImageViews { + let revealed: Bool + if let textView = data.textView, let count = textView.currentRevealCharacterCount { + revealed = data.charIndexInItem < count + } else { + revealed = true + } + if data.revealed == revealed { + continue + } + data.revealed = revealed + if revealed { + if animated { + data.view.layer.opacity = 1.0 + let opacityAnim = CABasicAnimation(keyPath: "opacity") + opacityAnim.fromValue = 0.0 + opacityAnim.toValue = 1.0 + opacityAnim.duration = 0.2 + data.view.layer.add(opacityAnim, forKey: "inlineImageRevealOpacity") + let scaleAnim = CABasicAnimation(keyPath: "transform.scale") + scaleAnim.fromValue = 0.1 + scaleAnim.toValue = 1.0 + scaleAnim.duration = 0.2 + scaleAnim.timingFunction = CAMediaTimingFunction(name: .easeOut) + data.view.layer.add(scaleAnim, forKey: "inlineImageRevealScale") + } else { + data.view.layer.opacity = 1.0 + } + } else { + data.view.layer.opacity = 0.0 + } + } + } + + func updateEmojiVisibility() { + for (_, data) in self.inlineStickerItemLayers { + let onScreen: Bool + if let visibilityRect = self.visibilityRect, let textView = data.textView { + let rectInSelf = textView.convert(data.itemLayer.frame, to: self) + onScreen = rectInSelf.intersects(visibilityRect) + } else { + // No visibility rect == not tracked / off-screen → don't animate. The root view's + // rect is propagated down the nested tree (details bodies, table cells/title) by + // `propagateVisibilityRect`, so a nil here genuinely means "not visible". + onScreen = false + } + data.itemLayer.isVisibleForAnimations = self.emojiEnableLooping && data.revealed && onScreen + } + self.propagateVisibilityRect() + } + + // Pushes this view's `visibilityRect` down into every nested V2 view (details body, table + // title + cells), converted into each child's coordinate space. Each child's `visibilityRect` + // didSet re-runs `updateEmojiVisibility`, which propagates one level further — so a single + // root assignment fans out across the whole tree. + private func propagateVisibilityRect() { + for view in self.itemViews { + for nested in InstantPageV2View.nestedV2Views(of: view) { + let childRect = self.visibilityRect.map { self.convert($0, to: nested) } + if nested.visibilityRect != childRect { + nested.visibilityRect = childRect + } + } + } + } + + /// Reveals (or re-hides) every spoiler in this view and nested details/table V2 views. + /// `location` (this view's coords) drives the dust explosion origin for whichever text view + /// contains it; the rest just toggle. Single-flag behavior mirrors ChatMessageTextBubbleContentNode. + public func setDisplayContentsUnderSpoilers(_ value: Bool, atLocation location: CGPoint?, animated: Bool) { + self.displayContentsUnderSpoilers = value + for view in self.itemViews { + if let textView = view as? InstantPageV2TextView { + let childLocation = location.map { self.convert($0, to: textView) } + textView.setDisplayContentsUnderSpoilers(value, atLocation: childLocation, animated: animated) + } + for nested in InstantPageV2View.nestedV2Views(of: view) { + let childLocation = location.map { self.convert($0, to: nested) } + nested.setDisplayContentsUnderSpoilers(value, atLocation: childLocation, animated: animated) + } + } + } + + private static func nestedV2Views(of view: InstantPageItemView) -> [InstantPageV2View] { + if let detailsView = view as? InstantPageV2DetailsView { + return detailsView.bodyView.map { [$0] } ?? [] + } else if let tableView = view as? InstantPageV2TableView { + var result: [InstantPageV2View] = [] + if let titleSubView = tableView.titleSubView { + result.append(titleSubView) + } + result.append(contentsOf: tableView.cellSubViews) + return result + } + return [] + } + + /// Returns the input view typed-updated against `item`, or `nil` if the existing view's + /// concrete class doesn't match the item's case (e.g. a `text` slot has been replaced by + /// a `divider` in the new layout). Caller falls back to `makeItemView`. + private func reuse(existingView: InstantPageItemView, for item: InstantPageV2LaidOutItem, theme: InstantPageTheme, animation: ListViewItemUpdateAnimation) -> InstantPageItemView? { + switch item { + case let .text(text): + guard let v = existingView as? InstantPageV2TextView else { return nil } + v.update(item: text, theme: theme) + return v + case let .codeBlock(block): + guard let v = existingView as? InstantPageV2CodeBlockView else { return nil } + v.update(item: block, theme: theme) + return v + case let .divider(divider): + guard let v = existingView as? InstantPageV2DividerView else { return nil } + v.update(item: divider, theme: theme) + return v + case let .listMarker(marker): + guard let v = existingView as? InstantPageV2ListMarkerView else { return nil } + v.update(item: marker, theme: theme) + return v + case let .blockQuoteBar(bar): + guard let v = existingView as? InstantPageV2BlockQuoteBarView else { return nil } + v.update(item: bar, theme: theme) + return v + case let .shape(shape): + guard let v = existingView as? InstantPageV2ShapeView else { return nil } + v.update(item: shape, theme: theme) + return v + case let .mediaPlaceholder(media): + guard let v = existingView as? InstantPageV2MediaPlaceholderView else { return nil } + v.update(item: media, theme: theme) + return v + case let .details(details): + guard let v = existingView as? InstantPageV2DetailsView else { return nil } + v.update(item: details, theme: theme, renderContext: self.renderContext, animation: animation) + return v + case let .table(table): + guard let v = existingView as? InstantPageV2TableView else { return nil } + v.update(item: table, theme: theme) + return v + case let .anchor(anchor): + guard let v = existingView as? InstantPageV2AnchorView else { return nil } + v.update(item: anchor, theme: theme) + return v + case let .formula(formula): + guard let v = existingView as? InstantPageV2FormulaView else { return nil } + v.update(item: formula, theme: theme) + return v + case let .mediaImage(media): + guard let v = existingView as? InstantPageV2MediaImageView, let rc = self.renderContext else { return nil } + v.update(item: media, theme: theme, renderContext: rc) + return v + case let .mediaVideo(media): + guard let v = existingView as? InstantPageV2MediaVideoView, let rc = self.renderContext else { return nil } + v.update(item: media, theme: theme, renderContext: rc) + return v + case let .mediaMap(media): + guard let v = existingView as? InstantPageV2MediaMapView, let rc = self.renderContext else { return nil } + v.update(item: media, theme: theme, renderContext: rc) + return v + case let .mediaCoverImage(media): + guard let v = existingView as? InstantPageV2MediaCoverImageView, let rc = self.renderContext else { return nil } + v.update(item: media, theme: theme, renderContext: rc) + return v + case let .mediaAudio(media): + guard let v = existingView as? InstantPageV2MediaAudioView, let rc = self.renderContext else { return nil } + v.update(item: media, theme: theme, renderContext: rc) + return v + case let .thinking(thinking): + guard let v = existingView as? InstantPageV2ThinkingView else { return nil } + v.update(item: thinking, theme: theme) + return v + case let .slideshow(slideshow): + guard let v = existingView as? InstantPageV2SlideshowView, let rc = self.renderContext else { return nil } + v.update(item: slideshow, theme: theme, renderContext: rc) + return v + } + } + + static func stableId(for item: InstantPageV2LaidOutItem, atPosition position: Int) -> InstantPageV2StableItemId { + switch item { + case let .mediaImage(m): return .media(m.media.index) + case let .mediaVideo(m): return .media(m.media.index) + case let .mediaMap(m): return .media(m.media.index) + case let .mediaCoverImage(m): return .media(m.media.index) + case let .mediaAudio(m): return .media(m.media.index) + case let .details(d): return .details(d.index) + case .text: return .positional(.text, position) + case .codeBlock: return .positional(.codeBlock, position) + case .divider: return .positional(.divider, position) + case .listMarker: return .positional(.listMarker, position) + case .blockQuoteBar: return .positional(.blockQuoteBar, position) + case .shape: return .positional(.shape, position) + case .mediaPlaceholder: return .positional(.mediaPlaceholder, position) + case .table: return .positional(.table, position) + case .anchor: return .positional(.anchor, position) + case .formula: return .positional(.formula, position) + case .thinking: return .thinking(position) + case .slideshow: return .positional(.slideshow, position) + } + } + + private func propagateRegistryHost(to view: InstantPageItemView) { + let host = self.effectiveRegistryHost + if let details = view as? InstantPageV2DetailsView { + details.forEachSubLayoutView { sub in + sub.rootMediaRegistryHost = host + } + } + if let table = view as? InstantPageV2TableView { + table.forEachSubLayoutView { sub in + sub.rootMediaRegistryHost = host + } + } + } + + /// Looks up the wrapper view registered under `media.index` and returns gallery transition + /// arguments backed by its wrapped `InstantPageImageNode`. Returns `nil` if the wrapper is + /// not currently registered (e.g. the media is inside a collapsed details block). + func transitionArgsFor(_ media: InstantPageMedia, addToTransitionSurface: @escaping (UIView) -> Void) -> GalleryTransitionArguments? { + guard let wrapperBox = self.trueRegistryRoot.mediaRegistry[media.index], let wrapper = wrapperBox.value else { + return nil + } + guard let itemView = wrapper as? InstantPageItemView else { return nil } + guard let transitionNode = itemView.instantPageTransitionNode(for: media) else { return nil } + return GalleryTransitionArguments(transitionNode: transitionNode, addToTransitionSurface: addToTransitionSurface) + } + + /// Forwards a hidden-media tick from the gallery's `hiddenMedia` signal to every registered + /// wrapper, calling `updateHiddenMedia(media:)` on each wrapped image node. + func applyHiddenMedia(_ hidden: InstantPageMedia?) { + for (_, weakBox) in self.trueRegistryRoot.mediaRegistry { + guard let wrapper = weakBox.value else { continue } + (wrapper as? InstantPageItemView)?.instantPageUpdateHiddenMedia(hidden) + } + } + + private func makeItemView(for item: InstantPageV2LaidOutItem, theme: InstantPageTheme) -> InstantPageItemView? { + switch item { + case let .text(text): + return InstantPageV2TextView(item: text, theme: theme) + case let .divider(divider): + return InstantPageV2DividerView(item: divider, theme: theme) + case let .anchor(anchor): + return InstantPageV2AnchorView(item: anchor, theme: theme) + case let .listMarker(marker): + return InstantPageV2ListMarkerView(item: marker, theme: theme) + case let .codeBlock(block): + return InstantPageV2CodeBlockView(item: block, theme: theme) + case let .blockQuoteBar(bar): + return InstantPageV2BlockQuoteBarView(item: bar, theme: theme) + case let .shape(shape): + return InstantPageV2ShapeView(item: shape, theme: theme) + case let .mediaPlaceholder(media): + return InstantPageV2MediaPlaceholderView(item: media, theme: theme) + case let .details(details): + let view = InstantPageV2DetailsView(item: details, theme: theme, renderContext: self.renderContext) + view.onTitleTapped = { [weak self] index in + self?.detailsTapped?(index) + } + return view + case let .table(table): + return InstantPageV2TableView(item: table, theme: theme, renderContext: self.renderContext) + case let .mediaImage(media): + if let renderContext = self.renderContext { + return InstantPageV2MediaImageView(item: media, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: placeholderFallback(for: media), theme: theme) + } + case let .mediaVideo(media): + if let renderContext = self.renderContext { + return InstantPageV2MediaVideoView(item: media, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: placeholderFallback(for: media), theme: theme) + } + case let .mediaMap(media): + if let renderContext = self.renderContext { + return InstantPageV2MediaMapView(item: media, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: placeholderFallback(for: media), theme: theme) + } + case let .mediaCoverImage(media): + if let renderContext = self.renderContext { + return InstantPageV2MediaCoverImageView(item: media, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: placeholderFallback(for: media), theme: theme) + } + case let .mediaAudio(media): + if let renderContext = self.renderContext { + return InstantPageV2MediaAudioView(item: media, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: InstantPageV2MediaPlaceholderItem(frame: media.frame, kind: .audio, cornerRadius: 0.0), theme: theme) + } + case let .formula(formula): + return InstantPageV2FormulaView(item: formula, theme: theme) + case let .thinking(thinking): + return InstantPageV2ThinkingView(item: thinking, theme: theme) + case let .slideshow(slideshow): + if let renderContext = self.renderContext { + return InstantPageV2SlideshowView(item: slideshow, renderContext: renderContext, theme: theme) + } else { + return InstantPageV2MediaPlaceholderView(item: InstantPageV2MediaPlaceholderItem(frame: slideshow.frame, kind: .slideshow, cornerRadius: 0.0), theme: theme) + } + } + } + + /// Returns the frame the parent should assign to the view for `item`. + /// + /// For most item types this is `item.frame`. `InstantPageV2TextView` widens its backing store + /// by `v2TextViewClippingInset` on every side to accommodate glyph overhang and underline + /// rendering past the text's logical `maxY` — the same inset its `init` applies when + /// constructing the view. The reuse path must apply the same expansion so that re-layout + /// (theme change, bubble resize, etc.) does not clip italic glyphs or underlines. + /// + /// Keep this helper aligned with each view class's init-time frame computation. + private static func actualFrame(forItem item: InstantPageV2LaidOutItem) -> CGRect { + switch item { + case let .text(textItem): + return textItem.frame.insetBy(dx: -v2TextViewClippingInset, dy: -v2TextViewClippingInset) + default: + return item.frame + } + } +} + +// MARK: - Placeholder fallbacks for the four typed media items +// +// Used by `makeItemView` when `renderContext == nil` (the zero-arg V2View constructor): +// we still need to emit a sized grey rectangle for image/video/map/coverImage so the +// surrounding layout doesn't collapse. Each helper synthesizes a placeholder item with +// the same frame + cornerRadius as the typed item, picking the kind that matches the +// closest existing placeholder visual. + +private func placeholderFallback(for item: InstantPageV2MediaImageItem) -> InstantPageV2MediaPlaceholderItem { + return InstantPageV2MediaPlaceholderItem(frame: item.frame, kind: .image, cornerRadius: item.cornerRadius) +} + +private func placeholderFallback(for item: InstantPageV2MediaVideoItem) -> InstantPageV2MediaPlaceholderItem { + return InstantPageV2MediaPlaceholderItem(frame: item.frame, kind: .video, cornerRadius: item.cornerRadius) +} + +private func placeholderFallback(for item: InstantPageV2MediaMapItem) -> InstantPageV2MediaPlaceholderItem { + return InstantPageV2MediaPlaceholderItem(frame: item.frame, kind: .map, cornerRadius: item.cornerRadius) +} + +private func placeholderFallback(for item: InstantPageV2MediaCoverImageItem) -> InstantPageV2MediaPlaceholderItem { + return InstantPageV2MediaPlaceholderItem(frame: item.frame, kind: .webEmbed, cornerRadius: item.cornerRadius) +} + +// MARK: - Item view protocol + +protocol InstantPageItemView: UIView { + /// Frame in the parent V2 view's coordinate space (== `item.frame`). + var itemFrame: CGRect { get } + /// Recursion hook for nested layouts (details body, table cells, table title). + var subLayoutView: InstantPageV2View? { get } + /// Gallery open: the transition source for `media` if this view (or a descendant) shows it. + /// Default nil (non-media views). Media views forward to their wrapped `InstantPageImageNode`; + /// the slideshow forwards to its matching page. + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + /// Gallery hidden-media tick: hide/show the source for `media`. Default no-op. + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) +} + +extension InstantPageItemView { + var subLayoutView: InstantPageV2View? { return nil } + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { } +} + +// MARK: - Text view (port of V1 InstantPageTextItem.drawInTile) + +/// Per-side padding applied to `InstantPageV2TextView`'s backing store, beyond the +/// item's typographic frame. Marker rounded rects extend ±2pt past their run, italic +/// or accented glyphs can overhang past the line's advance width, and the last line's +/// underline sits 2pt below `lineFrame.maxY`. The view grows by this amount on each +/// side and the draw context translates by the same amount so visual position is +/// unchanged. +private let v2TextViewClippingInset: CGFloat = 4.0 + +final class InstantPageV2TextView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2TextItem + var itemFrame: CGRect { return self.item.frame } + + private let renderContainer: UIView + private let renderView: TextRenderView + /// Sibling of `renderContainer`, holds inline `InstantPageV2InlineImageView`s + /// owned by the parent `InstantPageV2View`. Sits BELOW `emojiContainerView` + /// (so a colocated emoji renders above an image) and ABOVE `renderContainer` + /// (so the reveal mask wipes text without clipping images). + let imageContainerView: UIView = UIView() + let emojiContainerView: UIView = UIView() + let spoilerContainerView: UIView = UIView() + private var dustNode: InvisibleInkDustNode? + private var displayContentsUnderSpoilers: Bool = false + var enableSpoilerAnimations: Bool = true { + didSet { + if oldValue != self.enableSpoilerAnimations, let dustNode = self.dustNode { + self.dustNode = nil + dustNode.view.removeFromSuperview() + self.updateSpoiler(animated: false) + } + } + } + + // Reveal mask state — populated in Task 5. + private var maxCharacterDrawCount: Int? + private var previousMaxCharacterDrawCount: Int = 0 + private var revealMaskLayer: SimpleLayer? + private var revealLineMaskLayers: [SimpleLayer] = [] + private var animatingSnippetLayers: [SnippetLayer] = [] + + init(item: InstantPageV2TextItem, theme: InstantPageTheme) { + self.item = item + self.renderContainer = UIView() + self.renderView = TextRenderView(item: item) + super.init(frame: item.frame.insetBy(dx: -v2TextViewClippingInset, dy: -v2TextViewClippingInset)) + // Structural wiring only (one-time); all frames/content live in update(item:theme:). + self.backgroundColor = .clear + self.isOpaque = false + self.renderContainer.backgroundColor = .clear + self.renderContainer.isOpaque = false + self.addSubview(self.renderContainer) + self.renderContainer.addSubview(self.renderView) + self.imageContainerView.isUserInteractionEnabled = false + self.addSubview(self.imageContainerView) + self.emojiContainerView.isUserInteractionEnabled = false + self.addSubview(self.emojiContainerView) + self.spoilerContainerView.isUserInteractionEnabled = false + self.addSubview(self.spoilerContainerView) + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(item: InstantPageV2TextItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + // Lay every container out from the item's own (clipping-inset-expanded) frame rather than + // self.bounds, so the single path is correct regardless of when the parent assigns our + // frame — and so a reused text view that changed size (e.g. AI streaming) re-frames its + // renderContainer/renderView too, which the old update path skipped. + let containerBounds = CGRect(origin: .zero, size: item.frame.insetBy(dx: -v2TextViewClippingInset, dy: -v2TextViewClippingInset).size) + self.renderContainer.frame = containerBounds + self.renderView.frame = containerBounds + self.renderView.item = item + self.renderView.setNeedsDisplay() + self.imageContainerView.frame = containerBounds + self.emojiContainerView.frame = containerBounds + self.spoilerContainerView.frame = containerBounds + self.renderView.displayContentsUnderSpoilers = self.displayContentsUnderSpoilers + self.updateSpoiler(animated: false) + } + + private func spoilerRectsInContainer() -> [CGRect] { + let textItem = self.item.textItem + let boundsWidth = textItem.frame.size.width + var rects: [CGRect] = [] + for line in textItem.lines { + if line.spoilerItems.isEmpty { continue } + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + for spoiler in line.spoilerItems { + let localX = lineFrame.minX + (spoiler.frame.minX - line.frame.minX) + rects.append(CGRect(x: localX + v2TextViewClippingInset, y: spoiler.frame.minY + v2TextViewClippingInset, width: spoiler.frame.width, height: spoiler.frame.height)) + } + } + return rects + } + + func updateSpoiler(animated: Bool) { + let rects = self.spoilerRectsInContainer() + if rects.isEmpty || self.displayContentsUnderSpoilers { + if let dustNode = self.dustNode { + self.dustNode = nil + dustNode.view.removeFromSuperview() + } + return + } + + let dustNode: InvisibleInkDustNode + if let current = self.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: nil, enableAnimations: self.enableSpoilerAnimations) + self.dustNode = dustNode + self.spoilerContainerView.addSubview(dustNode.view) + } + + var color: UIColor = UIColor(white: 0.0, alpha: 1.0) + let textItem = self.item.textItem + if let firstRange = textItem.lines.first(where: { !$0.spoilerItems.isEmpty })?.spoilerItems.first?.range, firstRange.location < textItem.attributedString.length { + if let fg = textItem.attributedString.attribute(.foregroundColor, at: firstRange.location, effectiveRange: nil) as? UIColor { + color = fg + } + } + + dustNode.view.frame = self.spoilerContainerView.bounds + dustNode.update(size: self.spoilerContainerView.bounds.size, color: color, textColor: color, rects: rects, wordRects: rects) + } + + func setDisplayContentsUnderSpoilers(_ value: Bool, atLocation location: CGPoint?, animated: Bool) { + if self.displayContentsUnderSpoilers == value { + return + } + self.displayContentsUnderSpoilers = value + self.renderView.displayContentsUnderSpoilers = value + self.renderView.setNeedsDisplay() + if value { + if let dustNode = self.dustNode { + if let location, animated { + dustNode.revealAtLocation(self.convert(location, to: self.spoilerContainerView)) + } else { + dustNode.update(revealed: true, animated: animated) + } + } + } else { + self.updateSpoiler(animated: animated) + } + } + + func updateRevealCharacterCount(value: Int?, animated: Bool) { + if self.maxCharacterDrawCount == value { + return + } + self.maxCharacterDrawCount = value + self.updateRevealMask(animateNewSegments: animated) + } + + var currentRevealCharacterCount: Int? { + return self.maxCharacterDrawCount + } + + private struct RevealLineInfo { + let lineFrame: CGRect + let lineHeight: CGFloat + let revealedWidth: CGFloat + let isFull: Bool + let isRTL: Bool + } + + private func computeRevealedLines(characterLimit: Int) -> [RevealLineInfo] { + var result: [RevealLineInfo] = [] + var remainingCharacters = characterLimit + + let textItem = self.item.textItem + let boundsWidth = textItem.frame.size.width + + for line in textItem.lines { + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + // Translate from textItem-local to renderView-local coords (renderView's draw(_) translates by the inset). + let renderLocalLineFrame = lineFrame.offsetBy(dx: v2TextViewClippingInset, dy: v2TextViewClippingInset) + let lineHeight = lineFrame.size.height + + guard let characterRects = line.characterRects else { + let revealedWidth: CGFloat = remainingCharacters > 0 ? renderLocalLineFrame.width : 0.0 + result.append(RevealLineInfo(lineFrame: renderLocalLineFrame, lineHeight: lineHeight, revealedWidth: revealedWidth, isFull: remainingCharacters > 0, isRTL: line.isRTL)) + if remainingCharacters > 0 { + remainingCharacters -= line.range.length + } + continue + } + + if remainingCharacters <= 0 { + result.append(RevealLineInfo(lineFrame: renderLocalLineFrame, lineHeight: lineHeight, revealedWidth: 0.0, isFull: false, isRTL: line.isRTL)) + continue + } + + let revealCount = min(characterRects.count, remainingCharacters) + var revealedWidth: CGFloat = 0.0 + if line.isRTL { + var minX: CGFloat = .greatestFiniteMagnitude + for j in 0 ..< revealCount { + let rect = characterRects[j] + if !rect.isEmpty { + minX = min(minX, rect.minX) + } + } + if minX != .greatestFiniteMagnitude { + revealedWidth = ceil(renderLocalLineFrame.width - minX) + } + } else { + for j in 0 ..< revealCount { + let rect = characterRects[j] + if !rect.isEmpty { + revealedWidth = max(revealedWidth, rect.maxX) + } + } + revealedWidth = ceil(revealedWidth) + } + + remainingCharacters -= characterRects.count + let isFull = remainingCharacters >= 0 + + result.append(RevealLineInfo(lineFrame: renderLocalLineFrame, lineHeight: lineHeight, revealedWidth: revealedWidth, isFull: isFull, isRTL: line.isRTL)) + } + + return result + } + + private func updateRevealMask(animateNewSegments: Bool) { + let textItem = self.item.textItem + let lines = textItem.lines + let boundsWidth = textItem.frame.size.width + let layerSize = self.renderContainer.bounds.size + + let effectiveCharacterDrawCount: Int + if let maxCharacterDrawCount = self.maxCharacterDrawCount { + effectiveCharacterDrawCount = maxCharacterDrawCount + } else { + if self.previousMaxCharacterDrawCount > 0 || !self.animatingSnippetLayers.isEmpty { + var totalCharCount = 0 + for line in lines { + if let characterRects = line.characterRects { + totalCharCount += characterRects.count + } else { + totalCharCount += line.range.length + } + } + effectiveCharacterDrawCount = totalCharCount + } else { + if let _ = self.revealMaskLayer { + self.renderContainer.layer.mask = nil + self.revealMaskLayer = nil + self.revealLineMaskLayers.removeAll() + } + self.previousMaxCharacterDrawCount = 0 + return + } + } + + let revealMaskLayer: SimpleLayer + if let existing = self.revealMaskLayer { + revealMaskLayer = existing + } else { + revealMaskLayer = SimpleLayer() + revealMaskLayer.backgroundColor = UIColor.clear.cgColor + self.revealMaskLayer = revealMaskLayer + self.renderContainer.layer.mask = revealMaskLayer + } + revealMaskLayer.frame = CGRect(origin: .zero, size: layerSize) + + let currentLineInfos = self.computeRevealedLines(characterLimit: effectiveCharacterDrawCount) + + // Snippet spawn pass — animate newly-revealed characters. + if self.previousMaxCharacterDrawCount < effectiveCharacterDrawCount, + let contents = self.renderView.layer.contents, + animateNewSegments { + let containerOrigin = self.renderContainer.frame.origin + var previousRemaining = self.previousMaxCharacterDrawCount + var currentRemaining = effectiveCharacterDrawCount + var globalCharIndex = 0 + + for i in 0 ..< lines.count { + let line = lines[i] + let lineInfo = currentLineInfos[i] + guard let characterRects = line.characterRects else { continue } + + let lineCharCount = characterRects.count + let prevCount = min(max(0, previousRemaining), lineCharCount) + let curCount = min(max(0, currentRemaining), lineCharCount) + + previousRemaining -= lineCharCount + currentRemaining -= lineCharCount + + if curCount <= prevCount { + globalCharIndex += lineCharCount + continue + } + + for j in prevCount ..< curCount { + let charRect = characterRects[j] + if charRect.isEmpty { continue } + + let snippetRect = CGRect( + x: lineInfo.lineFrame.minX + charRect.origin.x, + y: lineInfo.lineFrame.minY, + width: charRect.width, + height: lineInfo.lineHeight + ) + + if snippetRect.width < 0.5 { continue } + + let contentsRect = CGRect( + x: snippetRect.minX / layerSize.width, + y: snippetRect.minY / layerSize.height, + width: snippetRect.width / layerSize.width, + height: snippetRect.height / layerSize.height + ) + + let snippetLayer = SnippetLayer(characterIndex: globalCharIndex + j) + snippetLayer.contents = contents + snippetLayer.contentsRect = contentsRect + snippetLayer.contentsScale = self.renderView.layer.contentsScale + snippetLayer.contentsGravity = self.renderView.layer.contentsGravity + snippetLayer.frame = snippetRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y) + + self.layer.addSublayer(snippetLayer) + self.animatingSnippetLayers.append(snippetLayer) + + ComponentTransition(animation: .curve(duration: 0.22, curve: .easeInOut)).animateBlur(layer: snippetLayer, fromRadius: 2.0, toRadius: 0.0) + snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + snippetLayer.animatePosition(from: CGPoint(x: 0.0, y: 6.0), to: .zero, duration: 0.2, additive: true) + snippetLayer.animateScale(from: 0.5, to: 1.0, duration: 0.2, completion: { [weak self, weak snippetLayer] _ in + guard let self, let snippetLayer else { return } + snippetLayer.removeFromSuperlayer() + self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) + self.updateRevealMask(animateNewSegments: false) + }) + } + globalCharIndex += lineCharCount + } + } + + // Mask rebuild — when snippets are in flight, clamp to the lowest animating one + // (so the mask never exposes a char a snippet is still flying for). With no animations + // in flight, snap directly to the current target — `previousMaxCharacterDrawCount` + // would lag by one call (it's updated at the end of this function) and is 0 on a fresh + // view, which would hide every char until the next tick. + let maskCharacterLimit: Int + if let lowestAnimating = self.animatingSnippetLayers.min(by: { $0.characterIndex < $1.characterIndex })?.characterIndex { + maskCharacterLimit = lowestAnimating + } else { + maskCharacterLimit = effectiveCharacterDrawCount + } + // Build mask rects from each revealed glyph's ink bbox, unioned per line. + // Per-character rects are stored in line-local CT coords (y positive-up, + // baseline-relative; rect.minY is negative for descenders); convert to + // renderContainer-local UIKit coords as: + // x = renderLocalLineFrame.minX + rect.minX + // y = renderLocalLineFrame.minY + lineAscent - rect.maxY + // where `lineAscent = lineFrame.size.height` (top of line frame → baseline). + // + // Per-glyph rect captures descenders, italic overhang, accents exactly. Per + // line we accumulate the union of revealed glyphs into one mask rect (one + // CALayer sublayer per line), and consecutive fully-revealed lines collapse + // further into a single rect — so a fully-revealed prefix is always one + // sublayer regardless of line count. + // + // Lines without per-character data (computeRevealCharacterRects == false on + // a non-streaming layout) fall back to a line-spanning rect, treated as a + // full line for merging. + var maskRects: [CGRect] = [] + var pendingFullPrefix: CGRect? = nil + var remainingChars = maskCharacterLimit + + for line in lines { + if remainingChars <= 0 { + break + } + + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + let renderLocalLineFrame = lineFrame.offsetBy(dx: v2TextViewClippingInset, dy: v2TextViewClippingInset) + let lineAscent = lineFrame.size.height + + let lineUnion: CGRect? + let isFullLine: Bool + if let characterRects = line.characterRects { + let revealCount = min(characterRects.count, remainingChars) + isFullLine = revealCount >= characterRects.count + var union: CGRect? = nil + for j in 0 ..< revealCount { + let rect = characterRects[j] + if rect.isEmpty { + continue + } + let glyphRect = CGRect( + x: renderLocalLineFrame.minX + rect.minX, + y: renderLocalLineFrame.minY + lineAscent - rect.maxY, + width: rect.width, + height: rect.height + ) + union = union?.union(glyphRect) ?? glyphRect + } + lineUnion = union + remainingChars -= characterRects.count + } else { + // No per-character data — expose the whole line. + lineUnion = line.range.length > 0 ? renderLocalLineFrame : nil + isFullLine = remainingChars >= line.range.length + remainingChars -= line.range.length + } + + guard let lineUnion else { + continue + } + + if isFullLine { + pendingFullPrefix = pendingFullPrefix?.union(lineUnion) ?? lineUnion + } else { + if let pending = pendingFullPrefix { + maskRects.append(pending) + pendingFullPrefix = nil + } + maskRects.append(lineUnion) + } + } + if let pending = pendingFullPrefix { + maskRects.append(pending) + } + + while self.revealLineMaskLayers.count < maskRects.count { + let childLayer = SimpleLayer() + childLayer.backgroundColor = UIColor.white.cgColor + revealMaskLayer.addSublayer(childLayer) + self.revealLineMaskLayers.append(childLayer) + } + while self.revealLineMaskLayers.count > maskRects.count { + let removed = self.revealLineMaskLayers.removeLast() + removed.removeFromSuperlayer() + } + for i in 0 ..< maskRects.count { + self.revealLineMaskLayers[i].frame = maskRects[i] + } + + self.previousMaxCharacterDrawCount = effectiveCharacterDrawCount + + if self.maxCharacterDrawCount == nil && self.animatingSnippetLayers.isEmpty { + self.renderContainer.layer.mask = nil + self.revealMaskLayer = nil + self.revealLineMaskLayers.removeAll() + } + } +} + +private final class TextRenderView: UIView { + var item: InstantPageV2TextItem + var displayContentsUnderSpoilers: Bool = false + + init(item: InstantPageV2TextItem) { + self.item = item + super.init(frame: .zero) + self.backgroundColor = .clear + self.isOpaque = false + self.contentMode = .redraw + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + guard let context = UIGraphicsGetCurrentContext() else { return } + + context.saveGState() + context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + context.translateBy(x: v2TextViewClippingInset, y: v2TextViewClippingInset) + + if !self.displayContentsUnderSpoilers { + let textItemForClip = self.item.textItem + let clipBoundsWidth = textItemForClip.frame.size.width + var spoilerRects: [CGRect] = [] + for line in textItemForClip.lines { + if line.spoilerItems.isEmpty { continue } + let lineFrame = v2FrameForLine(line, boundingWidth: clipBoundsWidth, alignment: textItemForClip.alignment) + for spoiler in line.spoilerItems { + spoilerRects.append(spoiler.frame.offsetBy(dx: lineFrame.minX - line.frame.minX, dy: 0.0)) + } + } + if !spoilerRects.isEmpty { + // Even-odd subtracts each spoiler rect from the full-bounds rect. Spoiler rects never + // overlap (same-line ranges are horizontally monotonic; cross-line rects differ in y), + // so no hole is accidentally re-included by an odd crossing count. + let path = CGMutablePath() + path.addRect(CGRect(origin: .zero, size: textItemForClip.frame.size).insetBy(dx: -v2TextViewClippingInset, dy: -v2TextViewClippingInset)) + for rect in spoilerRects { + path.addRect(rect) + } + context.addPath(path) + context.clip(using: .evenOdd) + } + } + + let textItem = self.item.textItem + let boundsWidth = textItem.frame.size.width + let intersectRect = rect.offsetBy(dx: -v2TextViewClippingInset, dy: -v2TextViewClippingInset) + + for line in textItem.lines { + let lineFrame = v2FrameForLine(line, boundingWidth: boundsWidth, alignment: textItem.alignment) + if !intersectRect.intersects(lineFrame) { + continue + } + + let lineOrigin = lineFrame.origin + context.textPosition = CGPoint(x: lineOrigin.x, y: lineOrigin.y + lineFrame.size.height) + + if !line.markedItems.isEmpty { + context.saveGState() + for item in line.markedItems { + let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0) + context.setFillColor(item.color.cgColor) + + let height = floor(item.frame.size.height * 2.2) + let markRect = CGRect(x: itemFrame.minX - 2.0, y: floor(itemFrame.minY + (itemFrame.height - height) / 2.0), width: itemFrame.width + 4.0, height: height) + let path = UIBezierPath(roundedRect: markRect, cornerRadius: 3.0) + context.addPath(path.cgPath) + context.fillPath() + } + context.restoreGState() + } + + if textItem.opaqueBackground { + context.setBlendMode(.normal) + } + + let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray + if glyphRuns.count != 0 { + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) + } + } + + if textItem.opaqueBackground { + context.setBlendMode(.copy) + } + + if !line.strikethroughItems.isEmpty { + for item in line.strikethroughItems { + let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0) + context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + floor((itemFrame.size.height / 2.0) + 1.0), width: itemFrame.size.width, height: 1.0)) + } + } + + if !line.underlineItems.isEmpty { + for item in line.underlineItems { + var color: UIColor? = item.color + if color == nil { + textItem.attributedString.enumerateAttributes(in: item.range, options: []) { attributes, _, _ in + if let foreground = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { + color = foreground + } + } + } + if let color { + context.setFillColor(color.cgColor) + } + let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0) + context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + itemFrame.size.height + 2.0, width: itemFrame.size.width, height: 1.0)) + } + } + } + + context.restoreGState() + } +} + +/// Private snippet layer for the streaming reveal pop-in animation. +/// Each instance represents one character cropped from the renderView's backing texture, +/// animating in (blur + alpha + position + scale) before the mask absorbs its rect. +private final class SnippetLayer: SimpleLayer { + let characterIndex: Int + + init(characterIndex: Int) { + self.characterIndex = characterIndex + super.init() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + if let other = layer as? SnippetLayer { + self.characterIndex = other.characterIndex + } else { + self.characterIndex = 0 + } + super.init(layer: layer) + } +} + +// MARK: - Divider view + +final class InstantPageV2DividerView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2DividerItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2DividerItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2DividerItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + self.backgroundColor = item.color + } +} + +// MARK: - Anchor view (zero-height; nothing to render) + +final class InstantPageV2AnchorView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2AnchorItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2AnchorItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.isHidden = true // structural: zero-height, never renders + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2AnchorItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + } +} + +// MARK: - List marker view + +final class InstantPageV2ListMarkerView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2ListMarkerItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2ListMarkerItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.isOpaque = false // structural + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2ListMarkerItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + self.rebuildContents() + } + + private func rebuildContents() { + for subview in Array(self.subviews) { + subview.removeFromSuperview() + } + if let sublayers = self.layer.sublayers { + for sublayer in Array(sublayers) { + sublayer.removeFromSuperlayer() + } + } + + let item = self.item + switch item.kind { + case .bullet: + let radius: CGFloat = min(item.frame.width, item.frame.height) / 2.0 + let dot = CALayer() + dot.backgroundColor = item.color.cgColor + dot.frame = CGRect( + x: (item.frame.width - radius * 2.0) / 2.0, + y: (item.frame.height - radius * 2.0) / 2.0, + width: radius * 2.0, + height: radius * 2.0 + ) + dot.cornerRadius = radius + self.layer.addSublayer(dot) + case let .number(text): + let label = UILabel() + label.text = text + label.textColor = item.color + label.font = UIFont.systemFont(ofSize: 17.0) + label.textAlignment = .right + label.frame = CGRect(origin: .zero, size: item.frame.size) + self.addSubview(label) + case let .checklist(checked, colors): + let checkNodeTheme = CheckNodeTheme( + backgroundColor: colors.background, + strokeColor: colors.stroke, + borderColor: colors.border, + overlayBorder: false, + hasInset: false, + hasShadow: false + ) + let checkNode = CheckNode(theme: checkNodeTheme, content: .check(isRectangle: true)) + checkNode.displaysAsynchronously = false + checkNode.isUserInteractionEnabled = false + checkNode.frame = CGRect(origin: .zero, size: item.frame.size) + checkNode.setSelected(checked, animated: false) + self.addSubview(checkNode.view) + } + } +} + +// MARK: - Quote bar view + +final class InstantPageV2BlockQuoteBarView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2BarItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2BarItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2BarItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + self.backgroundColor = item.color + self.layer.cornerRadius = item.cornerRadius + } +} + +// MARK: - Shape view (for pullQuote line ornaments) + +final class InstantPageV2ShapeView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2ShapeItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2ShapeItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2ShapeItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + self.applyKind() + } + + private func applyKind() { + switch self.item.kind { + case let .roundedRect(cornerRadius): + self.backgroundColor = self.item.color + self.layer.cornerRadius = cornerRadius + case .line(_): + self.backgroundColor = self.item.color + self.layer.cornerRadius = 0.0 + } + } +} + +// MARK: - Media placeholder view (V0: gray rectangle) + +final class InstantPageV2MediaPlaceholderView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaPlaceholderItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2MediaPlaceholderItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2MediaPlaceholderItem, theme: InstantPageTheme) { + self.item = item + self.backgroundColor = theme.imageTintColor?.withAlphaComponent(0.2) ?? UIColor(white: 0.85, alpha: 1.0) + self.layer.cornerRadius = item.cornerRadius + self.clipsToBounds = item.cornerRadius > 0.0 + } +} + +// MARK: - Details view + +final class InstantPageV2DetailsView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2DetailsItem + var itemFrame: CGRect { return self.item.frame } + + let titleTextView: InstantPageV2TextView + private let chevronView: UIImageView + private let separator: UIView + var bodyView: InstantPageV2View? + private let titleHitView: UIView + + // The expanded chevron is the collapsed one rotated 180° (down → up). + private static let expandedChevronTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + // On an animated collapse the body is kept until the toggle animation finishes (so the + // shrinking clip can hide it), then removed in finalizePendingCollapse() — which the parent + // (InstantPageV2View.update) calls from the completion of the frame-shrink (clip) animation. + private var bodyPendingRemoval = false + + var onTitleTapped: ((Int) -> Void)? + + var subLayoutView: InstantPageV2View? { return self.bodyView } + + func forEachSubLayoutView(_ body: (InstantPageV2View) -> Void) { + if let bodyView = self.bodyView { body(bodyView) } + } + + init(item: InstantPageV2DetailsItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext?) { + self.item = item + + let titleV2Item = InstantPageV2TextItem( + frame: item.titleTextItem.frame, + textItem: item.titleTextItem + ) + self.titleTextView = InstantPageV2TextView(item: titleV2Item, theme: theme) + self.titleTextView.isUserInteractionEnabled = false + + self.chevronView = UIImageView() + self.chevronView.image = UIImage(bundleImageName: "Item List/ExpandingItemVerticalRegularArrow")?.withRenderingMode(.alwaysTemplate) + self.chevronView.contentMode = .scaleAspectFit + // Decorative: let taps fall through to titleHitView (which carries the toggle gesture). + self.chevronView.isUserInteractionEnabled = false + + self.separator = UIView() + self.separator.isUserInteractionEnabled = false + + self.titleHitView = UIView() + self.titleHitView.backgroundColor = .clear + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.clipsToBounds = true // structural — the parent's frame-height animation clips the body + + self.addSubview(self.titleTextView) + self.addSubview(self.chevronView) + self.addSubview(self.separator) + + let tap = UITapGestureRecognizer(target: self, action: #selector(self.titleTapped)) + self.insertSubview(self.titleHitView, at: 0) + self.titleHitView.addGestureRecognizer(tap) + + // All content (title, chevron tint/position, separator, titleHit frame, body) flows through + // update — its expanded branch lazily creates the body, so init no longer builds it itself. + self.update(item: item, theme: theme, renderContext: renderContext, animation: .None) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + @objc private func titleTapped() { + self.onTitleTapped?(self.item.index) + } + + func update(item: InstantPageV2DetailsItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext?, animation: ListViewItemUpdateAnimation) { + self.item = item + + let titleV2Item = InstantPageV2TextItem( + frame: item.titleTextItem.frame, + textItem: item.titleTextItem + ) + self.titleTextView.update(item: titleV2Item, theme: theme) + + self.chevronView.tintColor = theme.secondaryControlColor + let chevronSize = CGSize(width: 18.0, height: 18.0) + self.chevronView.bounds = CGRect(origin: .zero, size: chevronSize) + self.chevronView.center = CGPoint( + x: item.rtl ? (item.frame.width - item.sideInset - chevronSize.width / 2.0) : (item.sideInset + chevronSize.width / 2.0), + y: item.titleFrame.midY + 1.0 + ) + + self.titleHitView.frame = item.titleFrame + + // Body lifecycle. The reveal/hide of the body is produced by the parent animating this + // view's own frame height (clipsToBounds = true), not by the body itself — see + // InstantPageV2View.update. The body's internal layout is forwarded `animation` so a + // *nested* details block inside the body can also animate its own toggle. + let blockHeight: CGFloat + if item.isExpanded { + if let innerLayout = item.innerLayout { + let body: InstantPageV2View + if let existingBody = self.bodyView { + // Reuse: covers expanded→expanded content updates and a re-expand that + // interrupts a still-pending collapse removal. + body = existingBody + self.bodyPendingRemoval = false + } else { + body = InstantPageV2View(renderContext: renderContext) + self.addSubview(body) + self.bodyView = body + } + // Forward taps on details NESTED inside this body up to the same toggle handler this + // view uses: makeItemView wired our onTitleTapped to the owning InstantPageV2View's + // detailsTapped, so chaining through onTitleTapped reaches the bubble's toggle handler. + // Without this, a nested details' tap hits the body view's nil detailsTapped and is dropped. + body.detailsTapped = { [weak self] index in + self?.onTitleTapped?(index) + } + body.update(layout: innerLayout, theme: theme, animation: animation) + body.frame = CGRect( + origin: CGPoint(x: 0.0, y: item.titleFrame.maxY), + size: innerLayout.contentSize + ) + blockHeight = body.frame.maxY + } else { + blockHeight = item.titleFrame.maxY + } + } else { + if let existingBody = self.bodyView { + if animation.isAnimated { + // Keep the body visible while the parent's frame-shrink clips it away; + // it is removed in finalizePendingCollapse() (called from that clip animation's + // completion in InstantPageV2View.update). + self.bodyPendingRemoval = true + } else { + existingBody.removeFromSuperview() + self.bodyView = nil + } + } + blockHeight = item.titleFrame.maxY + } + + self.separator.backgroundColor = item.separatorColor + animation.animator.updateFrame(layer: self.separator.layer, frame: CGRect( + x: 8.0, + y: blockHeight - UIScreenPixel, + width: item.frame.width - 8.0 * 2.0, + height: UIScreenPixel + ), completion: nil) + + // Chevron rotation. The body teardown on collapse is NOT tied to this completion — see + // finalizePendingCollapse(), which the parent calls from the frame-shrink (clip) animation. + let targetTransform = item.isExpanded ? InstantPageV2DetailsView.expandedChevronTransform : CATransform3DIdentity + animation.animator.updateTransform(layer: self.chevronView.layer, transform: targetTransform, completion: nil) + } + + /// Removes the body kept alive across an animated collapse. The parent (InstantPageV2View.update) + /// calls this from the completion of the frame-shrink animation that clips the body away, so the + /// body is torn down exactly when it finishes being hidden. The guard makes a re-expand that + /// interrupts the collapse safe — the re-expand clears `bodyPendingRemoval` first. + func finalizePendingCollapse() { + if !self.item.isExpanded, self.bodyPendingRemoval { + self.bodyView?.removeFromSuperview() + self.bodyView = nil + self.bodyPendingRemoval = false + } + } +} + +// MARK: - Code block view + +final class InstantPageV2CodeBlockView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2CodeBlockItem + var itemFrame: CGRect { return self.item.frame } + + private let backgroundLayer: CALayer + let textView: InstantPageV2TextView + + init(item: InstantPageV2CodeBlockItem, theme: InstantPageTheme) { + self.item = item + + self.backgroundLayer = CALayer() + + // item.textItem.frame is already in code-block content-area coords (x=17, y=backgroundInset). + let innerV2TextItem = InstantPageV2TextItem( + frame: item.textItem.frame, + textItem: item.textItem + ) + self.textView = InstantPageV2TextView(item: innerV2TextItem, theme: theme) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.layer.addSublayer(self.backgroundLayer) // structural + self.addSubview(self.textView) // structural + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2CodeBlockItem, theme: InstantPageTheme) { + self.item = item + self.backgroundLayer.backgroundColor = item.backgroundColor.cgColor + self.backgroundLayer.cornerRadius = item.cornerRadius + self.backgroundLayer.frame = CGRect(origin: .zero, size: item.frame.size) + + let innerV2TextItem = InstantPageV2TextItem( + frame: item.textItem.frame, + textItem: item.textItem + ) + self.textView.update(item: innerV2TextItem, theme: theme) + } +} + +// MARK: - Thinking view (dimmed shimmering reasoning block) + +/// A top-level thinking block: dimmed text drawn fully, masked by a continuously-running +/// `ShimmeringMaskView`. Reveal is whole-block alpha (driven from the cost map), NOT char-by-char, +/// and the block contributes zero reveal cost. Structure mirrors `InstantPageV2CodeBlockView` +/// (container hosting an inner `InstantPageV2TextView`). +final class InstantPageV2ThinkingView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2ThinkingItem + var itemFrame: CGRect { return self.item.frame } + + private let shimmerView: ShimmeringMaskView + let textView: InstantPageV2TextView // exposed so the parent V2 view can host its inline emoji + + init(item: InstantPageV2ThinkingItem, theme: InstantPageTheme) { + self.item = item + self.shimmerView = ShimmeringMaskView(peakAlpha: 0.3, duration: 1.0) + let innerV2TextItem = InstantPageV2TextItem(frame: item.textItem.frame, textItem: item.textItem) + self.textView = InstantPageV2TextView(item: innerV2TextItem, theme: theme) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.shimmerView) // structural + self.shimmerView.contentView.addSubview(self.textView) // structural + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + /// Parent positions self at the item frame (the bare line box). The shimmer and its gradient + /// mask are sized to the text view's clipping-inset-EXPANDED frame and shifted to + /// `(-inset, -inset)`, so the mask doesn't crop the glyph overhang the inset reserves (tall + /// ascenders, descenders, the last line's underline) — the symptom of sizing the mask to the + /// bare line box. The inner text view fills the shimmer; its `+inset` render translate lands the + /// glyphs back at self's origin, so the text position is unchanged. Mirrors how a `.text` view's + /// frame is inset-expanded (`actualFrame` / `InstantPageV2TextView.init`). + private func layoutContents() { + let inset = v2TextViewClippingInset + let expandedSize = CGSize(width: self.item.frame.size.width + inset * 2.0, + height: self.item.frame.size.height + inset * 2.0) + self.shimmerView.frame = CGRect(x: -inset, y: -inset, width: expandedSize.width, height: expandedSize.height) + self.textView.frame = CGRect(origin: .zero, size: expandedSize) + self.shimmerView.update( + size: expandedSize, + containerWidth: expandedSize.width, + offsetX: 0.0, + gradientWidth: 200.0, + transition: .immediate + ) + } + + func update(item: InstantPageV2ThinkingItem, theme: InstantPageTheme) { + self.item = item + let innerV2TextItem = InstantPageV2TextItem(frame: item.textItem.frame, textItem: item.textItem) + self.textView.update(item: innerV2TextItem, theme: theme) + self.layoutContents() + } +} + +// MARK: - Table view + +/// The set of grid corners a cell occupies, so a filled corner cell's stripe can be rounded to +/// follow the table's rounded outer border. `cellFrame` is table-grid-local (pre-`gridOffsetY`). +private func tableStripeCornerMask(cellFrame: CGRect, gridWidth: CGFloat, gridHeight: CGFloat, effectiveBorderWidth: CGFloat) -> CACornerMask { + let edge = effectiveBorderWidth / 2.0 + 0.5 + let firstCol = cellFrame.minX <= edge + let firstRow = cellFrame.minY <= edge + let lastCol = cellFrame.maxX >= gridWidth - edge + let lastRow = cellFrame.maxY >= gridHeight - edge + var mask: CACornerMask = [] + if firstRow && firstCol { mask.insert(.layerMinXMinYCorner) } + if firstRow && lastCol { mask.insert(.layerMaxXMinYCorner) } + if lastRow && firstCol { mask.insert(.layerMinXMaxYCorner) } + if lastRow && lastCol { mask.insert(.layerMaxXMaxYCorner) } + return mask +} + +final class InstantPageV2TableView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2TableItem + var itemFrame: CGRect { return self.item.frame } + + private let scrollView: UIScrollView + let contentView: UIView + var titleSubView: InstantPageV2View? + var cellSubViews: [InstantPageV2View] = [] + private var stripeLayers: [CALayer] = [] + private var lineLayers: [CALayer] = [] + + var subLayoutView: InstantPageV2View? { return nil } + + func forEachSubLayoutView(_ body: (InstantPageV2View) -> Void) { + if let titleView = self.titleSubView { body(titleView) } + for cellView in self.cellSubViews { body(cellView) } + } + + init(item: InstantPageV2TableItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext?) { + self.item = item + self.scrollView = UIScrollView() + self.contentView = UIView() + super.init(frame: item.frame) + self.backgroundColor = .clear + + // Structural, one-time scroll-view configuration. Frames / contentSize / indicator + // visibility all depend on the item and are (re)applied by update(item:theme:). + // Scrollable tables clip to the full width with no inset on the clip; the inset lives inside + // the scroll content width as a margin on BOTH sides (`contentInset * 2.0`, mirroring V1's + // `InstantPageScrollableNode`), so a scrolled-to-the-end table keeps a symmetric trailing + // inset instead of jamming its right border flush against the screen edge. + self.scrollView.clipsToBounds = true + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.disablesInteractiveTransitionGestureRecognizer = true + self.addSubview(self.scrollView) + self.scrollView.addSubview(self.contentView) + + // Build the (content-less) child structure sized to the construction-time item; update fills + // every frame / colour / sub-layout below. Insertion order matches the original interleaved + // build so the layer/subview z-order is unchanged (stripes at the bottom, then the title and + // cell sub-views, then the inner grid lines). Cell-count changes on later reuse are not + // reconciled here (pre-existing limitation) — update's index-guarded loops refresh in place. + if item.titleSubLayout != nil { + let v = InstantPageV2View(renderContext: renderContext) + self.contentView.addSubview(v) + self.titleSubView = v + } + for cell in item.cells { + if cell.backgroundColor != nil { + let stripe = CALayer() + self.contentView.layer.insertSublayer(stripe, at: 0) + self.stripeLayers.append(stripe) + } + if cell.subLayout != nil { + let v = InstantPageV2View(renderContext: renderContext) + self.contentView.addSubview(v) + self.cellSubViews.append(v) + } + } + if item.bordered { + for _ in item.horizontalLines + item.verticalLines { + let line = CALayer() + self.contentView.layer.addSublayer(line) + self.lineLayers.append(line) + } + } + + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2TableItem, theme: InstantPageTheme) { + self.item = item + + self.scrollView.frame = CGRect(origin: .zero, size: item.frame.size) + self.scrollView.contentSize = CGSize(width: item.contentSize.width + item.contentInset * 2.0, height: item.contentSize.height) + self.scrollView.showsHorizontalScrollIndicator = item.contentSize.width + item.contentInset * 2.0 > item.frame.width + self.contentView.frame = CGRect(x: item.contentInset, y: 0.0, width: item.contentSize.width, height: item.contentSize.height) + + // Forward updates to nested V2 sub-layouts (title + each cell). Recursive update + // propagation. Cell-count or title-presence changes fall back to rebuild via the + // V2View's internal `update(layout:theme:animation:)` (task B refines). + if let titleLayout = item.titleSubLayout, let titleView = self.titleSubView, let titleFrame = item.titleFrame { + titleView.update(layout: titleLayout, theme: theme, animation: .None) + titleView.frame = CGRect( + x: v2TableCellInsets.left, + y: titleFrame.minY + v2TableCellInsets.top, + width: titleLayout.contentSize.width, + height: titleLayout.contentSize.height + ) + } + + let gridOffsetY = item.titleFrame?.height ?? 0.0 + var cellLayoutIndex = 0 + for cell in item.cells { + if let subLayout = cell.subLayout, cellLayoutIndex < self.cellSubViews.count { + let cellView = self.cellSubViews[cellLayoutIndex] + cellView.update(layout: subLayout, theme: theme, animation: .None) + cellView.frame = cell.frame.offsetBy(dx: 0.0, dy: gridOffsetY) + cellLayoutIndex += 1 + } + } + + // Stripe layers (cell backgrounds) — update color + frame + corner rounding in original order. + let effectiveBorderWidth = item.bordered ? v2TableBorderWidth : 0.0 + let gridHeight = item.contentSize.height - gridOffsetY + var stripeIndex = 0 + for cell in item.cells { + if let bg = cell.backgroundColor, stripeIndex < self.stripeLayers.count { + let stripe = self.stripeLayers[stripeIndex] + stripe.backgroundColor = bg.cgColor + stripe.frame = cell.frame.offsetBy(dx: 0.0, dy: gridOffsetY) + let cornerMask = tableStripeCornerMask(cellFrame: cell.frame, gridWidth: item.contentSize.width, gridHeight: gridHeight, effectiveBorderWidth: effectiveBorderWidth) + if cornerMask.isEmpty { + stripe.cornerRadius = 0.0 + stripe.maskedCorners = [] + } else { + stripe.cornerRadius = max(0.0, v2TableCornerRadius - effectiveBorderWidth) + stripe.maskedCorners = cornerMask + } + stripeIndex += 1 + } + } + + // Inner line layers — refresh colour AND frame in place. (`lineLayers` holds only inner grid + // lines; the outer border is the contentView layer's own rounded border, refreshed below.) + // Frames are set here (not in init) so reuse with a different grid re-positions the lines. + let lineRects = item.horizontalLines + item.verticalLines + for (i, line) in self.lineLayers.enumerated() { + line.backgroundColor = item.borderColor.cgColor + if i < lineRects.count { + line.frame = lineRects[i].offsetBy(dx: 0.0, dy: gridOffsetY) + } + } + + // Rounded outer border — refresh radius/color/width (theme or `bordered` flag may change). + self.contentView.layer.cornerRadius = v2TableCornerRadius + self.contentView.layer.borderColor = item.borderColor.cgColor + self.contentView.layer.borderWidth = item.bordered ? v2TableBorderWidth : 0.0 + } +} + +// MARK: - Public helpers on InstantPageV2View + +public extension InstantPageV2View { + func lastTextLineFrame() -> CGRect? { + guard let layout = self.currentLayout else { return nil } + return InstantPageUI.lastTextLineFrame(in: layout) + } + + func textItemAt(point: CGPoint) -> (item: InstantPageTextItem, parentOffset: CGPoint)? { + guard let layout = self.currentLayout else { return nil } + return findTextItem(in: layout, point: point, accumulatedOffset: .zero) + } + + func urlItemAt(point: CGPoint) -> (urlItem: InstantPageUrlItem, item: InstantPageTextItem, + parentOffset: CGPoint, localPoint: CGPoint)? { + guard let hit = self.textItemAt(point: point) else { return nil } + let localPoint = CGPoint(x: point.x - hit.parentOffset.x, y: point.y - hit.parentOffset.y) + guard let url = hit.item.urlAttribute(at: localPoint) else { return nil } + return (urlItem: url, item: hit.item, parentOffset: hit.parentOffset, localPoint: localPoint) + } + + func selectableTextItems() -> [(item: InstantPageTextItem, parentOffset: CGPoint)] { + guard let layout = self.currentLayout else { return [] } + var result: [(InstantPageTextItem, CGPoint)] = [] + collectSelectableTextItems(in: layout, accumulatedOffset: .zero, into: &result) + return result.map { (item: $0.0, parentOffset: $0.1) } + } + + func detailsItem(atIndex index: Int) -> (frame: CGRect, titleFrame: CGRect)? { + guard let layout = self.currentLayout else { return nil } + for item in layout.items { + if case let .details(d) = item, d.index == index { + return (frame: d.frame, titleFrame: d.titleFrame.offsetBy(dx: d.frame.minX, dy: d.frame.minY)) + } + } + return nil + } + + /// The frame (pageView-space) of the anchor `name` in the *currently laid-out* layout. + /// Returns nil if the anchor isn't present — e.g. it's inside a collapsed `
` + /// (whose inner blocks aren't laid out) or doesn't exist. Mirrors `findTextItem`. + func anchorFrame(name: String) -> CGRect? { + guard let layout = self.currentLayout else { return nil } + return findAnchorFrame(in: layout, name: name, accumulatedOffset: .zero) + } + + /// Given a details-sibling-ordinal path (from `instantPageAnchorPath`), walk the live layout + /// and return the `currentExpandedDetails` index of the FIRST not-yet-expanded `
` on + /// the path. Returns nil if every details on the path is already expanded, or the path doesn't + /// match the live layout. Reads indices from the laid-out items — never reproduces them. + func firstCollapsedDetails(forOrdinalPath path: [Int]) -> Int? { + guard let layout = self.currentLayout else { return nil } + var currentItems = layout.items + for ordinal in path { + var seen = 0 + var found: InstantPageV2DetailsItem? + for item in currentItems { + if case let .details(details) = item { + if seen == ordinal { + found = details + break + } + seen += 1 + } + } + guard let details = found else { return nil } + if !details.isExpanded { + return details.index + } + guard let inner = details.innerLayout else { return nil } + currentItems = inner.items + } + return nil + } +} + +// MARK: - Private recursion helpers + +private func findTextItem( + in layout: InstantPageV2Layout, + point: CGPoint, + accumulatedOffset: CGPoint +) -> (item: InstantPageTextItem, parentOffset: CGPoint)? { + for item in layout.items { + let f = item.frame.offsetBy(dx: accumulatedOffset.x, dy: accumulatedOffset.y) + if !f.contains(point) { continue } + switch item { + case let .text(text): + return (item: text.textItem, parentOffset: CGPoint(x: f.minX, y: f.minY)) + case let .codeBlock(block): + let textOrigin = CGPoint( + x: f.minX + block.textItem.frame.minX, + y: f.minY + block.textItem.frame.minY + ) + return (item: block.textItem, parentOffset: textOrigin) + case let .details(details): + if details.titleFrame.offsetBy(dx: f.minX, dy: f.minY).contains(point) { + let titleOrigin = CGPoint( + x: f.minX + details.titleTextItem.frame.minX, + y: f.minY + details.titleTextItem.frame.minY + ) + return (item: details.titleTextItem, parentOffset: titleOrigin) + } + if let inner = details.innerLayout { + let innerOffset = CGPoint(x: f.minX, y: f.minY + details.titleFrame.maxY) + if let hit = findTextItem(in: inner, point: point, accumulatedOffset: innerOffset) { + return hit + } + } + case let .table(table): + for cell in table.cells { + let cellAbs = cell.frame.offsetBy(dx: f.minX + table.contentInset, dy: f.minY) + if !cellAbs.contains(point) { continue } + if let sub = cell.subLayout { + if let hit = findTextItem(in: sub, point: point, + accumulatedOffset: CGPoint(x: cellAbs.minX, y: cellAbs.minY)) { + return hit + } + } + } + if let titleLayout = table.titleSubLayout, let titleFrame = table.titleFrame { + let titleAbs = titleFrame.offsetBy(dx: f.minX + table.contentInset, dy: f.minY) + if titleAbs.contains(point) { + if let hit = findTextItem(in: titleLayout, point: point, + accumulatedOffset: CGPoint(x: titleAbs.minX, y: titleAbs.minY)) { + return hit + } + } + } + default: + continue + } + } + return nil +} + +private func findAnchorFrame( + in layout: InstantPageV2Layout, + name: String, + accumulatedOffset: CGPoint +) -> CGRect? { + for item in layout.items { + let f = item.frame.offsetBy(dx: accumulatedOffset.x, dy: accumulatedOffset.y) + switch item { + case let .anchor(anchor): + if anchor.name == name { + return CGRect(x: f.minX, y: f.minY, width: 0.0, height: 0.0) + } + case let .text(text): + if let (lineIndex, _) = text.textItem.anchors[name], lineIndex < text.textItem.lines.count { + let line = text.textItem.lines[lineIndex].frame + return CGRect(x: f.minX + line.minX, y: f.minY + line.minY, width: line.width, height: line.height) + } + case let .codeBlock(block): + if let (lineIndex, _) = block.textItem.anchors[name], lineIndex < block.textItem.lines.count { + let line = block.textItem.lines[lineIndex].frame + return CGRect( + x: f.minX + block.textItem.frame.minX + line.minX, + y: f.minY + block.textItem.frame.minY + line.minY, + width: line.width, height: line.height + ) + } + case let .thinking(thinking): + if let (lineIndex, _) = thinking.textItem.anchors[name], lineIndex < thinking.textItem.lines.count { + let line = thinking.textItem.lines[lineIndex].frame + return CGRect( + x: f.minX + thinking.textItem.frame.minX + line.minX, + y: f.minY + thinking.textItem.frame.minY + line.minY, + width: line.width, height: line.height + ) + } + case let .details(details): + if let (lineIndex, _) = details.titleTextItem.anchors[name], lineIndex < details.titleTextItem.lines.count { + let line = details.titleTextItem.lines[lineIndex].frame + return CGRect( + x: f.minX + details.titleTextItem.frame.minX + line.minX, + y: f.minY + details.titleTextItem.frame.minY + line.minY, + width: line.width, height: line.height + ) + } + if let inner = details.innerLayout { + let innerOffset = CGPoint(x: f.minX, y: f.minY + details.titleFrame.maxY) + if let hit = findAnchorFrame(in: inner, name: name, accumulatedOffset: innerOffset) { + return hit + } + } + case let .table(table): + for cell in table.cells { + if let sub = cell.subLayout { + let cellOffset = CGPoint(x: f.minX + table.contentInset + cell.frame.minX, y: f.minY + cell.frame.minY) + if let hit = findAnchorFrame(in: sub, name: name, accumulatedOffset: cellOffset) { + return hit + } + } + } + if let titleLayout = table.titleSubLayout, let titleFrame = table.titleFrame { + let titleOffset = CGPoint(x: f.minX + table.contentInset + titleFrame.minX, y: f.minY + titleFrame.minY) + if let hit = findAnchorFrame(in: titleLayout, name: name, accumulatedOffset: titleOffset) { + return hit + } + } + default: + continue + } + } + return nil +} + +private func collectSelectableTextItems( + in layout: InstantPageV2Layout, + accumulatedOffset: CGPoint, + into result: inout [(InstantPageTextItem, CGPoint)] +) { + for item in layout.items { + switch item { + case let .text(text): + if text.textItem.selectable && !text.textItem.attributedString.string.isEmpty { + result.append((text.textItem, CGPoint( + x: accumulatedOffset.x + text.frame.minX, + y: accumulatedOffset.y + text.frame.minY + ))) + } + case let .codeBlock(block): + if block.textItem.selectable && !block.textItem.attributedString.string.isEmpty { + result.append((block.textItem, CGPoint( + x: accumulatedOffset.x + block.frame.minX + block.textItem.frame.minX, + y: accumulatedOffset.y + block.frame.minY + block.textItem.frame.minY + ))) + } + case let .details(details): + if details.titleTextItem.selectable && !details.titleTextItem.attributedString.string.isEmpty { + result.append((details.titleTextItem, CGPoint( + x: accumulatedOffset.x + details.frame.minX + details.titleTextItem.frame.minX, + y: accumulatedOffset.y + details.frame.minY + details.titleTextItem.frame.minY + ))) + } + if let inner = details.innerLayout { + let innerOffset = CGPoint( + x: accumulatedOffset.x + details.frame.minX, + y: accumulatedOffset.y + details.frame.minY + details.titleFrame.maxY + ) + collectSelectableTextItems(in: inner, accumulatedOffset: innerOffset, into: &result) + } + case let .table(table): + if let titleLayout = table.titleSubLayout, let titleFrame = table.titleFrame { + let titleOffset = CGPoint( + x: accumulatedOffset.x + table.frame.minX + table.contentInset + titleFrame.minX, + y: accumulatedOffset.y + table.frame.minY + titleFrame.minY + ) + collectSelectableTextItems(in: titleLayout, accumulatedOffset: titleOffset, into: &result) + } + for cell in table.cells { + if let sub = cell.subLayout { + let cellOffset = CGPoint( + x: accumulatedOffset.x + table.frame.minX + table.contentInset + cell.frame.minX, + y: accumulatedOffset.y + table.frame.minY + cell.frame.minY + ) + collectSelectableTextItems(in: sub, accumulatedOffset: cellOffset, into: &result) + } + } + default: + continue + } + } +} + +// MARK: - Formula view + +/// Renders both block (`InstantPageBlock.formula(latex:)`) and inline (`InstantPageFormulaAttribute`) +/// math, sourcing the pre-rendered Retina image from `InstantPageMathAttachment.rendered`. +/// +/// For block formulas wider than the bubble's available width, the layout sets +/// `isScrollable = true`; this view then wraps the image in a horizontal `UIScrollView` +/// matching V1's `InstantPageScrollableNode` (no bounce on non-overflowing content, +/// scroll indicator hidden — appropriate for content embedded inside a chat bubble). +final class InstantPageV2FormulaView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2FormulaItem + var itemFrame: CGRect { return self.item.frame } + + init(item: InstantPageV2FormulaItem, theme: InstantPageTheme) { + self.item = item + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.isOpaque = false // structural + self.update(item: item, theme: theme) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func update(item: InstantPageV2FormulaItem, theme: InstantPageTheme) { + let _ = theme + self.item = item + + // Image content and scroll/non-scroll shape may change with width; rebuild. On the first + // call (from init) there is nothing to tear down, so this collapses to a plain build. + for sub in self.subviews { sub.removeFromSuperview() } + if let sublayers = self.layer.sublayers { + for layer in sublayers { layer.removeFromSuperlayer() } + } + self.buildContents() + } + + private func buildContents() { + let item = self.item + let imageLayer = CALayer() + imageLayer.contents = item.attachment.rendered.image.cgImage + imageLayer.contentsScale = item.attachment.rendered.image.scale + imageLayer.contentsGravity = .resizeAspect + imageLayer.frame = item.imageFrame + + if item.isScrollable { + self.clipsToBounds = true + self.isUserInteractionEnabled = true + let scroll = UIScrollView(frame: CGRect(origin: .zero, size: item.frame.size)) + scroll.autoresizingMask = [.flexibleWidth, .flexibleHeight] + scroll.contentSize = item.scrollContentSize + scroll.showsHorizontalScrollIndicator = false + scroll.showsVerticalScrollIndicator = false + scroll.alwaysBounceHorizontal = false + scroll.alwaysBounceVertical = false + scroll.contentInsetAdjustmentBehavior = .never + scroll.disablesInteractiveTransitionGestureRecognizer = true + self.addSubview(scroll) + + // Layers don't autoresize with their superview; host the image layer inside a UIView + // so the scroll view's content-size growth keeps the image positioned correctly. + let imageHost = UIView(frame: CGRect(origin: .zero, size: item.scrollContentSize)) + imageHost.layer.addSublayer(imageLayer) + scroll.addSubview(imageHost) + } else { + // Inline and centered-block formulas don't accept touches; the bubble's link/long-press + // recognizers run against the underlying text view instead. + self.isUserInteractionEnabled = false + self.layer.addSublayer(imageLayer) + } + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift index d9a39cabfc..560185cd86 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift @@ -30,7 +30,7 @@ final class InstantPageSlideshowItem: InstantPageItem { func matchesNode(_ node: InstantPageNode) -> Bool { if let node = node as? InstantPageSlideshowNode { - return self.medias == node.medias + return instantPageMediaArraysMatchNodeIdentity(self.medias, node.medias) } else { return false } @@ -55,4 +55,3 @@ final class InstantPageSlideshowItem: InstantPageItem { func drawInTile(context: CGContext) { } } - diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift index 6baa616027..6fcc36030f 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift @@ -61,6 +61,12 @@ private final class InstantPageSlideshowItemNode: ASDisplayNode { } return nil } + + func updateExternalMediaDimensions(_ update: ((EngineMedia.Id, PixelDimensions) -> Void)?) { + if let node = self.contentNode as? InstantPageImageNode { + node.updateExternalMediaDimensions = update + } + } } private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDelegate { @@ -88,6 +94,13 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDe } private var containerLayout: ContainerViewLayout? + var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? { + didSet { + for node in self.itemNodes { + node.updateExternalMediaDimensions(self.updateExternalMediaDimensions) + } + } + } var centralItemIndexUpdated: (Int?) -> Void = { _ in } @@ -195,6 +208,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDe } let node = InstantPageSlideshowItemNode(contentNode: contentNode) + node.updateExternalMediaDimensions(self.updateExternalMediaDimensions) node.index = index return node @@ -380,8 +394,13 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, ASScrollViewDe } } -final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode { +final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode, InstantPageExternalMediaDimensionsNode { var medias: [InstantPageMedia] = [] + var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? { + didSet { + self.pagerNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } + } private let pagerNode: InstantPageSlideshowPagerNode private let pageControlNode: PageControlNode diff --git a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift index 30bdf1c6d9..d3633c7f9b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift @@ -8,7 +8,7 @@ import TelegramPresentationData import TelegramUIPreferences import AccountContext -final class InstantPageSubContentNode : ASDisplayNode { +final class InstantPageSubContentNode : ASDisplayNode, InstantPageExternalMediaDimensionsNode { private let context: AccountContext private let strings: PresentationStrings private let nameDisplayOrder: PresentationPersonNameOrder @@ -32,6 +32,13 @@ final class InstantPageSubContentNode : ASDisplayNode { var currentDetailsItems: [InstantPageDetailsItem] = [] var requestLayoutUpdate: ((Bool) -> Void)? + var updateExternalMediaDimensions: ((EngineMedia.Id, PixelDimensions) -> Void)? { + didSet { + for (_, itemNode) in self.visibleItemsWithNodes { + self.applyExternalMediaDimensionsUpdater(to: itemNode) + } + } + } var currentLayout: InstantPageLayout let contentSize: CGSize @@ -209,6 +216,7 @@ final class InstantPageSubContentNode : ASDisplayNode { topNode = newNode self.visibleItemsWithNodes[itemIndex] = newNode itemNode = newNode + self.applyExternalMediaDimensionsUpdater(to: newNode) if let itemNode = itemNode as? InstantPageDetailsNode { itemNode.requestLayoutUpdate = { [weak self] animated in @@ -289,6 +297,16 @@ final class InstantPageSubContentNode : ASDisplayNode { } } + private func applyExternalMediaDimensionsUpdater(to itemNode: InstantPageNode) { + if let itemNode = itemNode as? InstantPageImageNode { + itemNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } else if let itemNode = itemNode as? InstantPageDetailsNode { + itemNode.contentNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } else if let itemNode = itemNode as? InstantPageSlideshowNode { + itemNode.updateExternalMediaDimensions = self.updateExternalMediaDimensions + } + } + private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) { // let currentHeight = self.currentWebEmbedHeights[index] // if height != currentHeight { diff --git a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift index 8be13ed574..56c5161f69 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift @@ -144,54 +144,66 @@ public final class InstantPageTableItem: InstantPageScrollableItem { } public func drawInTile(context: CGContext) { + let hasBorder = self.borderWidth > 0.0 + + if hasBorder { + context.setStrokeColor(self.theme.tableBorderColor.cgColor) + context.setLineWidth(self.borderWidth) + } + context.setFillColor(self.theme.tableHeaderColor.cgColor) + + let borderPath = CGMutablePath() + for cell in self.cells { - if cell.cell.text == nil { - continue - } context.saveGState() context.translateBy(x: cell.frame.minX, y: cell.frame.minY) - - let hasBorder = self.borderWidth > 0.0 + let bounds = CGRect(origin: CGPoint(), size: cell.frame.size) var path: UIBezierPath? if !cell.adjacentSides.isEmpty { path = UIBezierPath(roundedRect: bounds, byRoundingCorners: cell.adjacentSides.uiRectCorner, cornerRadii: CGSize(width: tableCornerRadius, height: tableCornerRadius)) } - if cell.filled { - context.setFillColor(self.theme.tableHeaderColor.cgColor) - } - if self.borderWidth > 0.0 { - context.setStrokeColor(self.theme.tableBorderColor.cgColor) - context.setLineWidth(borderWidth) - } - if let path = path { - context.addPath(path.cgPath) - var drawMode: CGPathDrawingMode? - switch (cell.filled, hasBorder) { - case (true, false): - drawMode = .fill - case (true, true): - drawMode = .fillStroke - case (false, true): - drawMode = .stroke - default: - break - } - if let drawMode = drawMode { - context.drawPath(using: drawMode) - } - } else { - if cell.filled { + + if cell.filled && cell.cell.text != nil { + if let path = path { + context.addPath(path.cgPath) + context.fillPath() + } else { context.fill(bounds) } - if hasBorder { - context.stroke(bounds) - } } + if let textItem = cell.textItem { textItem.drawInTile(context: context) } + context.restoreGState() + + if hasBorder { + if !cell.adjacentSides.contains(.top) { + borderPath.move(to: CGPoint(x: cell.frame.minX, y: cell.frame.minY)) + borderPath.addLine(to: CGPoint(x: cell.frame.maxX, y: cell.frame.minY)) + } + if !cell.adjacentSides.contains(.left) { + borderPath.move(to: CGPoint(x: cell.frame.minX, y: cell.frame.minY)) + borderPath.addLine(to: CGPoint(x: cell.frame.minX, y: cell.frame.maxY)) + } + } + } + + if hasBorder && self.totalWidth > 0.0 { + let outerRect = CGRect( + x: self.borderWidth / 2.0, + y: self.borderWidth / 2.0, + width: self.totalWidth - self.borderWidth, + height: self.frame.height - self.borderWidth + ) + borderPath.addPath(UIBezierPath(roundedRect: outerRect, cornerRadius: tableCornerRadius).cgPath) + } + + if hasBorder { + context.addPath(borderPath) + context.strokePath() } } diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index 1b4c08197c..b11e0b988a 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -26,18 +26,42 @@ public final class InstantPageUrlItem: Equatable { struct InstantPageTextMarkedItem { let frame: CGRect let color: UIColor + let range: NSRange +} + +struct InstantPageTextSpoilerItem { + let frame: CGRect + let range: NSRange } struct InstantPageTextStrikethroughItem { let frame: CGRect } +struct InstantPageTextUnderlineItem { + let frame: CGRect + let range: NSRange + let color: UIColor? +} + struct InstantPageTextImageItem { let frame: CGRect let range: NSRange let id: EngineMedia.Id } +struct InstantPageTextFormulaRun { + let frame: CGRect + let range: NSRange + let attachment: InstantPageMathAttachment +} + +struct InstantPageTextEmojiItem { + let frame: CGRect + let range: NSRange + let emoji: ChatTextInputTextCustomEmojiAttribute +} + public struct InstantPageTextAnchorItem { public let name: String public let anchorText: NSAttributedString? @@ -61,20 +85,30 @@ public final class InstantPageTextLine { let range: NSRange public let frame: CGRect let strikethroughItems: [InstantPageTextStrikethroughItem] + let underlineItems: [InstantPageTextUnderlineItem] let markedItems: [InstantPageTextMarkedItem] + let spoilerItems: [InstantPageTextSpoilerItem] let imageItems: [InstantPageTextImageItem] + let formulaItems: [InstantPageTextFormulaRun] + let emojiItems: [InstantPageTextEmojiItem] public let anchorItems: [InstantPageTextAnchorItem] let isRTL: Bool - - init(line: CTLine, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], markedItems: [InstantPageTextMarkedItem], imageItems: [InstantPageTextImageItem], anchorItems: [InstantPageTextAnchorItem], isRTL: Bool) { + public let characterRects: [CGRect]? // line-local, one rect per character in `range`; nil = not computed + + init(line: CTLine, range: NSRange, frame: CGRect, strikethroughItems: [InstantPageTextStrikethroughItem], underlineItems: [InstantPageTextUnderlineItem], markedItems: [InstantPageTextMarkedItem], spoilerItems: [InstantPageTextSpoilerItem] = [], imageItems: [InstantPageTextImageItem], formulaItems: [InstantPageTextFormulaRun], emojiItems: [InstantPageTextEmojiItem] = [], anchorItems: [InstantPageTextAnchorItem], isRTL: Bool, characterRects: [CGRect]? = nil) { self.line = line self.range = range self.frame = frame self.strikethroughItems = strikethroughItems + self.underlineItems = underlineItems self.markedItems = markedItems + self.spoilerItems = spoilerItems self.imageItems = imageItems + self.formulaItems = formulaItems + self.emojiItems = emojiItems self.anchorItems = anchorItems self.isRTL = isRTL + self.characterRects = characterRects } } @@ -88,8 +122,100 @@ private func frameForLine(_ line: InstantPageTextLine, boundingWidth: CGFloat, a return lineFrame } +private func expandedFrameForLine(_ line: InstantPageTextLine, boundingWidth: CGFloat, alignment: NSTextAlignment) -> CGRect { + var lineFrame = line.frame + for imageItem in line.imageItems { + if imageItem.frame.minY < lineFrame.minY { + let delta = lineFrame.minY - imageItem.frame.minY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY - delta, width: lineFrame.width, height: lineFrame.height + delta) + } + if imageItem.frame.maxY > lineFrame.maxY { + let delta = imageItem.frame.maxY - lineFrame.maxY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY, width: lineFrame.width, height: lineFrame.height + delta) + } + } + for formulaItem in line.formulaItems { + if formulaItem.frame.minY < lineFrame.minY { + let delta = lineFrame.minY - formulaItem.frame.minY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY - delta, width: lineFrame.width, height: lineFrame.height + delta) + } + if formulaItem.frame.maxY > lineFrame.maxY { + let delta = formulaItem.frame.maxY - lineFrame.maxY - 2.0 + lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY, width: lineFrame.width, height: lineFrame.height + delta) + } + } + lineFrame = lineFrame.insetBy(dx: 0.0, dy: -4.0) + if alignment == .center { + lineFrame.origin.x = floor((boundingWidth - lineFrame.size.width) / 2.0) + } else if alignment == .right || (alignment == .natural && line.isRTL) { + lineFrame.origin.x = boundingWidth - lineFrame.size.width + } + return lineFrame +} + +private func alignedAttachmentFrame(_ frame: CGRect, line: InstantPageTextLine, boundingWidth: CGFloat, alignment: NSTextAlignment) -> CGRect { + let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) + return frame.offsetBy(dx: lineFrame.minX - line.frame.minX, dy: 0.0) +} + +private func localAttachmentBoundsForRange(_ range: NSRange, imageItems: [InstantPageTextImageItem], formulaItems: [InstantPageTextFormulaRun]) -> CGRect? { + var result: CGRect? + + for imageItem in imageItems { + if NSIntersectionRange(range, imageItem.range).length != 0 { + if let current = result { + result = current.union(imageItem.frame) + } else { + result = imageItem.frame + } + } + } + + for formulaItem in formulaItems { + if NSIntersectionRange(range, formulaItem.range).length != 0 { + if let current = result { + result = current.union(formulaItem.frame) + } else { + result = formulaItem.frame + } + } + } + + return result +} + +private func attachmentBoundsForRange(_ range: NSRange, line: InstantPageTextLine, boundingWidth: CGFloat, alignment: NSTextAlignment) -> CGRect? { + guard let localBounds = localAttachmentBoundsForRange(range, imageItems: line.imageItems, formulaItems: line.formulaItems) else { + return nil + } + return alignedAttachmentFrame(localBounds, line: line, boundingWidth: boundingWidth, alignment: alignment) +} + +/// Block-level role of a text item, used to reconstruct markdown from a +/// selection. `kind` is the primary role; `quoteDepth` (0 = not quoted) is +/// orthogonal so a heading/list/code line inside a blockquote can be emitted +/// as e.g. `> ## Title`. +public struct InstantPageMarkdownBlockContext: Equatable { + public enum Kind: Equatable { + case paragraph + case heading(level: Int) // 1...6 + case title // InstantPageBlock.title → "# " + case listItem(ordered: Bool, marker: String, checked: Bool?) + case code(language: String?) + case tableCell(row: Int, column: Int, isHeader: Bool) + } + + public var kind: Kind + public var quoteDepth: Int + + public init(kind: Kind, quoteDepth: Int = 0) { + self.kind = kind + self.quoteDepth = quoteDepth + } +} + public final class InstantPageTextItem: InstantPageItem { - let attributedString: NSAttributedString + public let attributedString: NSAttributedString public let lines: [InstantPageTextLine] let rtlLineIndices: Set public var frame: CGRect @@ -100,7 +226,8 @@ public final class InstantPageTextItem: InstantPageItem { public let wantsNode: Bool = false public let separatesTiles: Bool = false public var selectable: Bool = true - + public var markdownContext: InstantPageMarkdownBlockContext? = nil + var containsRTL: Bool { return !self.rtlLineIndices.isEmpty } @@ -186,6 +313,24 @@ public final class InstantPageTextItem: InstantPageItem { context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + floor((lineFrame.size.height / 2.0) + 1.0), width: itemFrame.size.width, height: 1.0)) } } + + if !line.underlineItems.isEmpty { + for item in line.underlineItems { + var color: UIColor? = item.color + if color == nil { + self.attributedString.enumerateAttributes(in: item.range, options: []) { attributes, _, _ in + if let foreground = attributes[NSAttributedString.Key.foregroundColor] as? UIColor { + color = foreground + } + } + } + if let color { + context.setFillColor(color.cgColor) + } + let itemFrame = item.frame.offsetBy(dx: lineFrame.minX, dy: 0.0) + context.fill(CGRect(x: itemFrame.minX, y: itemFrame.minY + lineFrame.size.height + 2.0, width: itemFrame.size.width, height: 1.0)) + } + } } context.restoreGState() @@ -196,8 +341,8 @@ public final class InstantPageTextItem: InstantPageItem { let boundsWidth = self.frame.width for i in 0 ..< self.lines.count { let line = self.lines[i] - - let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) + + let lineFrame = expandedFrameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) if lineFrame.insetBy(dx: -5.0, dy: -5.0).contains(transformedPoint) { var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) if index == self.attributedString.length { @@ -217,7 +362,66 @@ public final class InstantPageTextItem: InstantPageItem { } return nil } - + + public func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? { + // Hit-testing (taps on links/entities) wants the character under the finger — keep the + // strict, clamping behavior. + if !orNearest { + return self.attributesAtPoint(point) + } + guard !self.lines.isEmpty else { + return nil + } + + // Selection drags can travel outside the text bounds, so pick the vertically-closest line. + let boundsWidth = self.frame.width + var nearestLineIndex = 0 + var nearestDistance = CGFloat.greatestFiniteMagnitude + for i in 0 ..< self.lines.count { + let lineFrame = expandedFrameForLine(self.lines[i], boundingWidth: boundsWidth, alignment: self.alignment) + let distance: CGFloat + if point.y < lineFrame.minY { + distance = lineFrame.minY - point.y + } else if point.y > lineFrame.maxY { + distance = point.y - lineFrame.maxY + } else { + distance = 0.0 + } + if distance < nearestDistance { + nearestDistance = distance + nearestLineIndex = i + } + } + + let line = self.lines[nearestLineIndex] + let lineFrame = expandedFrameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) + let lineRange = CTLineGetStringRange(line.line) + var index: Int + if point.x <= lineFrame.minX { + index = lineRange.location + } else if point.x >= lineFrame.maxX { + // Trailing edge: return the line's upper bound (one past its last character) so a + // right-handle drag can include the last character/item of the line. The selection + // upper bound is exclusive, so clamping to the last character's index — as the strict + // path does — would always leave it unselected. Mirrors Display.TextNode. + index = lineRange.location + lineRange.length + } else { + index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: point.x - lineFrame.minX, y: 0.0)) + if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if point.x - lineFrame.minX < glyphStart { + index -= 1 + } + } + } + guard index >= 0, index <= self.attributedString.length else { + return nil + } + let attributes = index < self.attributedString.length ? self.attributedString.attributes(at: index, effectiveRange: nil) : [:] + return (index, attributes) + } + private func attributeRects(name: NSAttributedString.Key, at index: Int) -> [CGRect]? { var range = NSRange() let _ = self.attributedString.attribute(name, at: index, effectiveRange: &range) @@ -238,8 +442,20 @@ public final class InstantPageTextItem: InstantPageItem { } let lineFrame = frameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) let width = abs(rightOffset - leftOffset) + + var rect: CGRect? if width > 1.0 { - rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + (leftOffset < rightOffset ? leftOffset : rightOffset), y: lineFrame.minY), size: CGSize(width: width, height: lineFrame.size.height))) + rect = CGRect(origin: CGPoint(x: lineFrame.minX + (leftOffset < rightOffset ? leftOffset : rightOffset), y: lineFrame.minY), size: CGSize(width: width, height: lineFrame.size.height)) + } + if let attachmentBounds = attachmentBoundsForRange(lineRange, line: line, boundingWidth: boundsWidth, alignment: self.alignment) { + if rect != nil { + rect = rect!.union(attachmentBounds) + } else { + rect = attachmentBounds + } + } + if let rect, rect.width > 1.0 { + rects.append(rect) } } } @@ -252,8 +468,18 @@ public final class InstantPageTextItem: InstantPageItem { public func linkSelectionRects(at point: CGPoint) -> [CGRect] { if let (index, dict) = self.attributesAtPoint(point) { - if let _ = dict[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - if let rects = self.attributeRects(name: NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), at: index) { + let interactiveKeys = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag, + TelegramTextAttributes.BankCard, + TelegramTextAttributes.Date + ] + for key in interactiveKeys { + let attrKey = NSAttributedString.Key(rawValue: key) + if dict[attrKey] != nil, let rects = self.attributeRects(name: attrKey, at: index) { return rects.compactMap { rect in if rect.width > 5.0 { return rect.insetBy(dx: 0.0, dy: -3.0) @@ -280,9 +506,9 @@ public final class InstantPageTextItem: InstantPageItem { guard range.length != 0 else { return nil } - + let boundsWidth = self.frame.width - + var rects: [(CGRect, CGRect)] = [] var startEdge: InstantPageTextRangeRectEdge? var endEdge: InstantPageTextRangeRectEdge? @@ -303,29 +529,11 @@ public final class InstantPageTextItem: InstantPageItem { rightOffset = ceil(secondaryOffset) } } - - var lineFrame = line.frame - for imageItem in line.imageItems { - if imageItem.frame.minY < lineFrame.minY { - let delta = lineFrame.minY - imageItem.frame.minY - 2.0 - lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY - delta, width: lineFrame.width, height: lineFrame.height + delta) - } - if imageItem.frame.maxY > lineFrame.maxY { - let delta = imageItem.frame.maxY - lineFrame.maxY - 2.0 - lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY, width: lineFrame.width, height: lineFrame.height + delta) - } - } - lineFrame = lineFrame.insetBy(dx: 0.0, dy: -4.0) - if self.alignment == .center { - lineFrame.origin.x = floor((boundsWidth - lineFrame.size.width) / 2.0) - } else if self.alignment == .right { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } else if self.alignment == .natural && self.rtlLineIndices.contains(i) { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } - + + let lineFrame = expandedFrameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) + let width = max(0.0, abs(rightOffset - leftOffset)) - + if line.range.contains(range.lowerBound) { let offsetX = floor(CTLineGetOffsetForStringIndex(line.line, range.lowerBound, nil)) startEdge = InstantPageTextRangeRectEdge(x: lineFrame.minX + offsetX, y: lineFrame.minY, height: lineFrame.height) @@ -339,7 +547,7 @@ public final class InstantPageTextItem: InstantPageItem { let primaryOffset = floor(CTLineGetOffsetForStringIndex(line.line, range.upperBound - 1, &secondaryOffset)) secondaryOffset = floor(secondaryOffset) let nextOffet = floor(CTLineGetOffsetForStringIndex(line.line, range.upperBound, &secondaryOffset)) - + if primaryOffset != secondaryOffset { offsetX = secondaryOffset } else { @@ -348,7 +556,7 @@ public final class InstantPageTextItem: InstantPageItem { } endEdge = InstantPageTextRangeRectEdge(x: lineFrame.minX + offsetX, y: lineFrame.minY, height: lineFrame.height) } - + rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + min(leftOffset, rightOffset), y: lineFrame.minY), size: CGSize(width: width, height: lineFrame.size.height)))) } } @@ -357,7 +565,16 @@ public final class InstantPageTextItem: InstantPageItem { } return nil } - + + public func textRangeRects(in range: NSRange) -> (rects: [CGRect], start: TextRangeRectEdge, end: TextRangeRectEdge)? { + guard let result = self.rangeRects(in: range), let start = result.start, let end = result.end, !result.rects.isEmpty else { + return nil + } + let startEdge = TextRangeRectEdge(x: start.x, y: start.y, height: start.height) + let endEdge = TextRangeRectEdge(x: end.x, y: end.y, height: end.height) + return (result.rects, startEdge, endEdge) + } + public func lineRects() -> [CGRect] { let boundsWidth = self.frame.width var rects: [CGRect] = [] @@ -368,25 +585,7 @@ public final class InstantPageTextItem: InstantPageItem { for i in 0 ..< self.lines.count { let line = self.lines[i] - var lineFrame = line.frame - for imageItem in line.imageItems { - if imageItem.frame.minY < lineFrame.minY { - let delta = lineFrame.minY - imageItem.frame.minY - 2.0 - lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY - delta, width: lineFrame.width, height: lineFrame.height + delta) - } - if imageItem.frame.maxY > lineFrame.maxY { - let delta = imageItem.frame.maxY - lineFrame.maxY - 2.0 - lineFrame = CGRect(x: lineFrame.minX, y: lineFrame.minY, width: lineFrame.width, height: lineFrame.height + delta) - } - } - lineFrame = lineFrame.insetBy(dx: 0.0, dy: -4.0) - if self.alignment == .center { - lineFrame.origin.x = floor((boundsWidth - lineFrame.size.width) / 2.0) - } else if self.alignment == .right { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } else if self.alignment == .natural && self.rtlLineIndices.contains(i) { - lineFrame.origin.x = boundsWidth - lineFrame.size.width - } + let lineFrame = expandedFrameForLine(line, boundingWidth: boundsWidth, alignment: self.alignment) if lineFrame.minX < topLeft.x { topLeft = CGPoint(x: lineFrame.minX, y: topLeft.y) @@ -529,7 +728,7 @@ final class InstantPageScrollableTextItem: InstantPageScrollableItem { } } -func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil, boundingWidth: CGFloat? = nil) -> NSAttributedString { +func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil, boundingWidth: CGFloat? = nil, formatDate: ((Int32, MessageTextEntityType.DateTimeFormat) -> String)? = nil) -> NSAttributedString { switch text { case .empty: return NSAttributedString(string: "", attributes: styleStack.textAttributes()) @@ -541,67 +740,67 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt return NSAttributedString(string: string, attributes: attributes) case let .bold(text): styleStack.push(.bold) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .italic(text): styleStack.push(.italic) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .underline(text): styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .strikethrough(text): styleStack.push(.strikethrough) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .fixed(text): styleStack.push(.fontFixed(true)) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .url(text, url, webpageId): styleStack.push(.link(webpageId != nil)) - let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: url, webpageId: webpageId)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: url, webpageId: webpageId), formatDate: formatDate) styleStack.pop() return result case let .email(text, email): styleStack.push(.bold) styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "mailto:\(email)", webpageId: nil)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "mailto:\(email)", webpageId: nil), formatDate: formatDate) styleStack.pop() styleStack.pop() return result case let .concat(texts): let string = NSMutableAttributedString() for text in texts { - let substring = attributedStringForRichText(text, styleStack: styleStack, url: url, boundingWidth: boundingWidth) + let substring = attributedStringForRichText(text, styleStack: styleStack, url: url, boundingWidth: boundingWidth, formatDate: formatDate) string.append(substring) } return string case let .subscript(text): styleStack.push(.subscript) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .superscript(text): styleStack.push(.superscript) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .marked(text): styleStack.push(.marker) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result case let .phone(text, phone): styleStack.push(.bold) styleStack.push(.underline) - let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "tel:\(phone)", webpageId: nil)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "tel:\(phone)", webpageId: nil), formatDate: formatDate) styleStack.pop() styleStack.pop() return result @@ -617,7 +816,8 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt } let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) extentBuffer.initialize(to: RunStruct(ascent: 0.0, descent: 0.0, width: dimensions.cgSize.width)) - var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { pointer in + pointer.assumingMemoryBound(to: RunStruct.self).deallocate() }, getAscent: { (pointer) -> CGFloat in let d = pointer.assumingMemoryBound(to: RunStruct.self) return d.pointee.ascent @@ -630,9 +830,47 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt }) let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedString.Key): (delegate as Any), NSAttributedString.Key(rawValue: InstantPageMediaIdAttribute): id.id, NSAttributedString.Key(rawValue: InstantPageMediaDimensionsAttribute): dimensions] - let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url).mutableCopy() as! NSMutableAttributedString + let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url, formatDate: formatDate).mutableCopy() as! NSMutableAttributedString mutableAttributedString.addAttributes(attrDictionaryDelegate, range: NSMakeRange(0, mutableAttributedString.length)) return mutableAttributedString + case let .formula(latex): + let attributes = styleStack.textAttributes() + let textColor = (attributes[NSAttributedString.Key.foregroundColor] as? UIColor) ?? UIColor.black + let fontSize = (attributes[NSAttributedString.Key.font] as? UIFont)?.pointSize ?? 16.0 + guard let attachment = instantPageMathAttachment(latex: latex, fontSize: fontSize, textColor: textColor, mode: .inline) else { + var fallbackAttributes = attributes + if let url = url { + fallbackAttributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] = url + } + return NSAttributedString(string: latex, attributes: fallbackAttributes) + } + + struct RunStruct { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + } + let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) + extentBuffer.initialize(to: RunStruct(ascent: attachment.rendered.ascent, descent: attachment.rendered.descent, width: attachment.rendered.size.width)) + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { pointer in + pointer.assumingMemoryBound(to: RunStruct.self).deallocate() + }, getAscent: { pointer -> CGFloat in + let data = pointer.assumingMemoryBound(to: RunStruct.self) + return data.pointee.ascent + }, getDescent: { pointer -> CGFloat in + let data = pointer.assumingMemoryBound(to: RunStruct.self) + return data.pointee.descent + }, getWidth: { pointer -> CGFloat in + let data = pointer.assumingMemoryBound(to: RunStruct.self) + return data.pointee.width + }) + let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) + let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url, formatDate: formatDate).mutableCopy() as! NSMutableAttributedString + mutableAttributedString.addAttributes([ + kCTRunDelegateAttributeName as NSAttributedString.Key: delegate as Any, + NSAttributedString.Key(rawValue: InstantPageFormulaAttribute): attachment + ], range: NSMakeRange(0, mutableAttributedString.length)) + return mutableAttributedString case let .anchor(text, name): var empty = false var text = text @@ -640,21 +878,146 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt empty = true text = .plain("\u{200b}") } - let anchorText = !empty ? attributedStringForRichText(text, styleStack: styleStack, url: url) : nil + let anchorText = !empty ? attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) : nil styleStack.push(.anchor(name, anchorText, empty)) - let result = attributedStringForRichText(text, styleStack: styleStack, url: url) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) styleStack.pop() return result + case let .textAutoUrl(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: text.plainText, webpageId: nil), formatDate: formatDate) + styleStack.pop() + return result + case let .textAutoEmail(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "mailto:\(text.plainText)", webpageId: nil), formatDate: formatDate) + styleStack.pop() + return result + case let .textAutoPhone(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: "tel:\(text.plainText)", webpageId: nil), formatDate: formatDate) + styleStack.pop() + return result + case let .textMention(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention), value: mutable.string, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textMentionName(text, peerId): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + let mention = TelegramPeerMention(peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(peerId)), mention: mutable.string) + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: mention, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textHashtag(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag), value: TelegramHashtag(peerName: nil, hashtag: mutable.string), range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textCashtag(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag), value: TelegramHashtag(peerName: nil, hashtag: mutable.string), range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textBotCommand(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand), value: mutable.string, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textBankCard(text): + styleStack.push(.link(false)) + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + styleStack.pop() + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard), value: mutable.string, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textCustomEmoji(fileId, _): + struct RunStruct { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + } + let attributes = styleStack.textAttributes() + let font = (attributes[NSAttributedString.Key.font] as? UIFont) ?? UIFont.systemFont(ofSize: 17.0) + // Size the inline emoji to the font's line height (A + D) plus a 4pt bump at the 17pt + // body font (scaled proportionally). Must match the V2 layout's emoji cell size + // (InstantPageV2Layout.swift). The run delegate still reports the font's own + // ascent/descent (below), so the line height is unchanged — only the emoji width changes. + let itemSize = font.ascender - font.descender + 4.0 * font.pointSize / 17.0 + let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) + extentBuffer.initialize(to: RunStruct(ascent: font.ascender, descent: font.descender, width: itemSize)) + var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { pointer in + pointer.assumingMemoryBound(to: RunStruct.self).deallocate() + }, getAscent: { pointer -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.ascent + }, getDescent: { pointer -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.descent + }, getWidth: { pointer -> CGFloat in + let d = pointer.assumingMemoryBound(to: RunStruct.self) + return d.pointee.width + }) + let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) + let emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil) + let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url, formatDate: formatDate).mutableCopy() as! NSMutableAttributedString + mutableAttributedString.addAttributes([ + kCTRunDelegateAttributeName as NSAttributedString.Key: delegate as Any, + ChatTextInputAttributes.customEmoji: emojiAttribute + ], range: NSMakeRange(0, mutableAttributedString.length)) + return mutableAttributedString + case let .textSpoiler(text): + let result = attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + case let .textDate(text, date, format): + if let format, let formatDate { + let formatted = formatDate(date, format) + let result = attributedStringForRichText(.plain(formatted), styleStack: styleStack, url: url, formatDate: formatDate) + let mutable = result.mutableCopy() as! NSMutableAttributedString + if mutable.length != 0 { + mutable.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Date), value: date, range: NSRange(location: 0, length: mutable.length)) + } + return mutable + } else { + return attributedStringForRichText(text, styleStack: styleStack, url: url, formatDate: formatDate) + } } } -func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [EngineMedia.Id: EngineMedia] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { +func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [EngineMedia.Id: EngineMedia] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, fitToWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { if string.length == 0 { return (nil, [], CGSize()) } var lines: [InstantPageTextLine] = [] var imageItems: [InstantPageTextImageItem] = [] + var formulaItems: [InstantPageTextFormulaItem] = [] var font = string.attribute(NSAttributedString.Key.font, at: 0, effectiveRange: nil) as? UIFont if font == nil { let range = NSMakeRange(0, string.length) @@ -665,7 +1028,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } } let image = string.attribute(NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute), at: 0, effectiveRange: nil) - guard font != nil || image != nil else { + let formula = string.attribute(NSAttributedString.Key(rawValue: InstantPageFormulaAttribute), at: 0, effectiveRange: nil) + guard font != nil || image != nil || formula != nil else { return (nil, [], CGSize()) } @@ -686,7 +1050,6 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo var hasAnchors = false var maxLineWidth: CGFloat = 0.0 - var maxImageHeight: CGFloat = 0.0 var extraDescent: CGFloat = 0.0 let text = string.string var indexOffset: CFIndex? @@ -741,6 +1104,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo let hadExtraDescent = extraDescent > 0.0 extraDescent = 0.0 var lineImageItems: [InstantPageTextImageItem] = [] + var lineFormulaItems: [InstantPageTextFormulaRun] = [] + var lineMaxAttachmentHeight: CGFloat = 0.0 var isRTL = false if let glyphRuns = CTLineGetGlyphRuns(line) as? [CTRun], !glyphRuns.isEmpty { if let run = glyphRuns.first, CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { @@ -769,26 +1134,56 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo if !fontLineHeight.isZero { extraDescent = max(extraDescent, imageFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing)) } - maxImageHeight = max(maxImageHeight, imageFrame.height) + lineMaxAttachmentHeight = max(lineMaxAttachmentHeight, imageFrame.height) lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: id))) + } else if let attachment = attributes[NSAttributedString.Key(rawValue: InstantPageFormulaAttribute)] as? InstantPageMathAttachment { + let xOffset = CTLineGetOffsetForStringIndex(line, range.location, nil) + let baselineOffset = (attributes[NSAttributedString.Key.baselineOffset] as? CGFloat) ?? 0.0 + var formulaFrame = CGRect( + origin: CGPoint( + x: workingLineOrigin.x + xOffset, + y: workingLineOrigin.y + fontLineHeight + baselineOffset - attachment.rendered.ascent + ), + size: attachment.rendered.size + ) + + let minSpacing = fontLineSpacing - 4.0 + let delta = workingLineOrigin.y - minSpacing - formulaFrame.minY - appliedLineOffset + if !fontAscent.isZero && delta > 0.0 { + workingLineOrigin.y += delta + appliedLineOffset += delta + formulaFrame.origin = formulaFrame.origin.offsetBy(dx: 0.0, dy: delta) + } + if !fontLineHeight.isZero { + extraDescent = max(extraDescent, formulaFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing)) + } + lineMaxAttachmentHeight = max(lineMaxAttachmentHeight, formulaFrame.height) + lineFormulaItems.append(InstantPageTextFormulaRun(frame: formulaFrame, range: range, attachment: attachment)) } } } } - if substring.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && lineImageItems.count > 0 { + if substring.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && (!lineImageItems.isEmpty || !lineFormulaItems.isEmpty) { extraDescent += max(6.0, fontLineSpacing / 2.0) } - if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 5.0, let imageItem = lineImageItems.last { - indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound) - continue + if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 5.0 { + if let imageItem = lineImageItems.last { + indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound) + continue + } + if let formulaItem = lineFormulaItems.last { + indexOffset = -(lastIndex + lineCharacterCount - formulaItem.range.lowerBound) + continue + } } var strikethroughItems: [InstantPageTextStrikethroughItem] = [] + var underlineItems: [InstantPageTextUnderlineItem] = [] var markedItems: [InstantPageTextMarkedItem] = [] var anchorItems: [InstantPageTextAnchorItem] = [] - + string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) @@ -796,6 +1191,16 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo let x = lowerX < upperX ? lowerX : upperX strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y, width: abs(upperX - lowerX), height: fontLineHeight))) } + if let _ = attributes[NSAttributedString.Key.underlineStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + underlineItems.append(InstantPageTextUnderlineItem( + frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y, width: abs(upperX - lowerX), height: fontLineHeight), + range: range, + color: attributes[NSAttributedString.Key.underlineColor] as? UIColor + )) + } if let color = attributes[NSAttributedString.Key.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor { var lineHeight = fontLineHeight var delta: CGFloat = 0.0 @@ -807,7 +1212,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) let x = lowerX < upperX ? lowerX : upperX - markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + delta, width: abs(upperX - lowerX), height: lineHeight), color: color)) + markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + delta, width: abs(upperX - lowerX), height: lineHeight), color: color, range: range)) } if let item = attributes[NSAttributedString.Key.init(rawValue: InstantPageAnchorAttribute)] as? Dictionary, let name = item["name"] as? String, let empty = item["empty"] as? Bool { anchorItems.append(InstantPageTextAnchorItem(name: name, anchorText: item["text"] as? NSAttributedString, empty: empty)) @@ -822,11 +1227,35 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo workingLineOrigin.y += fontLineSpacing } - let height = !fontLineHeight.isZero ? fontLineHeight : maxImageHeight - let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: workingLineOrigin.x, y: workingLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, markedItems: markedItems, imageItems: lineImageItems, anchorItems: anchorItems, isRTL: isRTL) + let height = !fontLineHeight.isZero ? max(fontLineHeight, lineMaxAttachmentHeight) : lineMaxAttachmentHeight + if !lineFormulaItems.isEmpty { + let baselineAdjustment = height - fontLineHeight + if !baselineAdjustment.isZero { + lineFormulaItems = lineFormulaItems.map { item in + InstantPageTextFormulaRun( + frame: item.frame.offsetBy(dx: 0.0, dy: baselineAdjustment), + range: item.range, + attachment: item.attachment + ) + } + } + } + if !markedItems.isEmpty { + markedItems = markedItems.map { item in + if let attachmentBounds = localAttachmentBoundsForRange(item.range, imageItems: lineImageItems, formulaItems: lineFormulaItems) { + return InstantPageTextMarkedItem(frame: attachmentBounds, color: item.color, range: item.range) + } else { + return item + } + } + } + let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: workingLineOrigin.x, y: workingLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, underlineItems: underlineItems, markedItems: markedItems, imageItems: lineImageItems, formulaItems: lineFormulaItems, anchorItems: anchorItems, isRTL: isRTL) lines.append(textLine) imageItems.append(contentsOf: lineImageItems) + for formulaItem in lineFormulaItems { + formulaItems.append(InstantPageTextFormulaItem(frame: formulaItem.frame, attachment: formulaItem.attachment)) + } if lineWidth > maxLineWidth { maxLineWidth = lineWidth @@ -852,8 +1281,11 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } var textWidth = boundingWidth + if fitToWidth { + textWidth = maxLineWidth + } var requiresScroll = false - if !imageItems.isEmpty && maxLineWidth > boundingWidth + 10.0 { + if (!imageItems.isEmpty || !formulaItems.isEmpty) && maxLineWidth > boundingWidth + 10.0 { textWidth = maxLineWidth requiresScroll = true } @@ -870,13 +1302,13 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo var topInset: CGFloat = 0.0 var bottomInset: CGFloat = 0.0 var additionalItems: [InstantPageItem] = [] - if let webpage = webpage { - let offset = requiresScroll ? CGPoint() : offset - for line in textItem.lines { - let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) + let effectiveOffset = requiresScroll ? CGPoint() : offset + for line in textItem.lines { + let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) + if let webpage = webpage { for imageItem in line.imageItems { if let media = media[imageItem.id] { - let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: media, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + effectiveOffset.x, dy: effectiveOffset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: media, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) additionalItems.append(item) if item.frame.minY < topInset { @@ -888,6 +1320,17 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo } } } + for formulaItem in line.formulaItems { + let item = InstantPageTextFormulaItem(frame: formulaItem.frame.offsetBy(dx: lineFrame.minX + effectiveOffset.x, dy: effectiveOffset.y), attachment: formulaItem.attachment) + additionalItems.append(item) + + if item.frame.minY < topInset { + topInset = item.frame.minY + } + if item.frame.maxY > height { + bottomInset = max(bottomInset, item.frame.maxY - height) + } + } } if requiresScroll { diff --git a/submodules/InstantPageUI/Sources/InstantPageTextStyleStack.swift b/submodules/InstantPageUI/Sources/InstantPageTextStyleStack.swift index 413ff55840..8b49bbfcdd 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextStyleStack.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextStyleStack.swift @@ -28,6 +28,7 @@ let InstantPageMarkerColorAttribute = "MarkerColorAttribute" let InstantPageMediaIdAttribute = "MediaIdAttribute" let InstantPageMediaDimensionsAttribute = "MediaDimensionsAttribute" let InstantPageAnchorAttribute = "AnchorAttribute" +let InstantPageFormulaAttribute = "FormulaAttribute" final class InstantPageTextStyleStack { private var items: [InstantPageTextStyle] = [] @@ -196,6 +197,9 @@ final class InstantPageTextStyleStack { if let link = link, let linkColor = linkColor { attributes[NSAttributedString.Key.foregroundColor] = linkColor + if linkColor == color { + attributes[NSAttributedString.Key.underlineStyle] = NSUnderlineStyle.single.rawValue as NSNumber + } if link, let linkMarkerColor = linkMarkerColor { attributes[NSAttributedString.Key(rawValue: InstantPageMarkerColorAttribute)] = linkMarkerColor } diff --git a/submodules/InstantPageUI/Sources/InstantPageTheme.swift b/submodules/InstantPageUI/Sources/InstantPageTheme.swift index d1305895e4..d3cd70e803 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTheme.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTheme.swift @@ -1,27 +1,33 @@ import Foundation import UIKit -import Postbox import Display import TelegramPresentationData import TelegramUIPreferences -enum InstantPageFontStyle { +public enum InstantPageFontStyle { case sans case serif + case monospace } -struct InstantPageFont { +public struct InstantPageFont { let style: InstantPageFontStyle let size: CGFloat let lineSpacingFactor: CGFloat + + public init(style: InstantPageFontStyle, size: CGFloat, lineSpacingFactor: CGFloat) { + self.style = style + self.size = size + self.lineSpacingFactor = lineSpacingFactor + } } -struct InstantPageTextAttributes { +public struct InstantPageTextAttributes { let font: InstantPageFont let color: UIColor let underline: Bool - init(font: InstantPageFont, color: UIColor, underline: Bool = false) { + public init(font: InstantPageFont, color: UIColor, underline: Bool = false) { self.font = font self.color = color self.underline = underline @@ -31,8 +37,8 @@ struct InstantPageTextAttributes { return InstantPageTextAttributes(font: self.font, color: self.color, underline: underline) } - func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextAttributes { - return InstantPageTextAttributes(font: InstantPageFont(style: forceSerif ? .serif : self.font.style, size: floor(self.font.size * sizeMultiplier), lineSpacingFactor: self.font.lineSpacingFactor), color: self.color, underline: self.underline) + func withUpdatedFontStyles(sizeMultiplier: CGFloat, lineSpacingFactor: CGFloat, forceSerif: Bool) -> InstantPageTextAttributes { + return InstantPageTextAttributes(font: InstantPageFont(style: forceSerif ? .serif : self.font.style, size: floor(self.font.size * sizeMultiplier), lineSpacingFactor: self.font.lineSpacingFactor * lineSpacingFactor), color: self.color, underline: self.underline) } } @@ -45,6 +51,7 @@ enum InstantPageTextCategoryType { case credit case table case article + case codeBlock } public struct InstantPageTextCategories { @@ -56,30 +63,55 @@ public struct InstantPageTextCategories { let credit: InstantPageTextAttributes let table: InstantPageTextAttributes let article: InstantPageTextAttributes + let codeBlock: InstantPageTextAttributes + + public init(kicker: InstantPageTextAttributes, header: InstantPageTextAttributes, subheader: InstantPageTextAttributes, paragraph: InstantPageTextAttributes, caption: InstantPageTextAttributes, credit: InstantPageTextAttributes, table: InstantPageTextAttributes, article: InstantPageTextAttributes, codeBlock: InstantPageTextAttributes) { + self.kicker = kicker + self.header = header + self.subheader = subheader + self.paragraph = paragraph + self.caption = caption + self.credit = credit + self.table = table + self.article = article + self.codeBlock = codeBlock + } func attributes(type: InstantPageTextCategoryType, link: Bool) -> InstantPageTextAttributes { switch type { - case .kicker: - return self.kicker.withUnderline(link) - case .header: - return self.header.withUnderline(link) - case .subheader: - return self.subheader.withUnderline(link) - case .paragraph: - return self.paragraph.withUnderline(link) - case .caption: - return self.caption.withUnderline(link) - case .credit: - return self.credit.withUnderline(link) - case .table: - return self.table.withUnderline(link) - case .article: - return self.article.withUnderline(link) + case .kicker: + return self.kicker.withUnderline(link) + case .header: + return self.header.withUnderline(link) + case .subheader: + return self.subheader.withUnderline(link) + case .paragraph: + return self.paragraph.withUnderline(link) + case .caption: + return self.caption.withUnderline(link) + case .credit: + return self.credit.withUnderline(link) + case .table: + return self.table.withUnderline(link) + case .article: + return self.article.withUnderline(link) + case .codeBlock: + return self.codeBlock.withUnderline(link) } } - func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTextCategories { - return InstantPageTextCategories(kicker: self.kicker.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), credit: self.credit.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), article: self.article.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif)) + func withUpdatedFontStyles(sizeMultiplier: CGFloat, lineSpacingFactor: CGFloat, forceSerif: Bool) -> InstantPageTextCategories { + return InstantPageTextCategories( + kicker: self.kicker.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + header: self.header.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + subheader: self.subheader.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + paragraph: self.paragraph.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + caption: self.caption.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + credit: self.credit.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + table: self.table.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + article: self.article.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), + codeBlock: self.codeBlock.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif) + ) } } @@ -106,12 +138,12 @@ public final class InstantPageTheme { public let tableBorderColor: UIColor public let tableHeaderColor: UIColor public let controlColor: UIColor - public let imageTintColor: UIColor? - public let overlayPanelColor: UIColor + public let separatorColor: UIColor + public let secondaryControlColor: UIColor - public init(type: InstantPageThemeType, pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?, overlayPanelColor: UIColor) { + public init(type: InstantPageThemeType, pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: UIColor, linkColor: UIColor, textHighlightColor: UIColor, linkHighlightColor: UIColor, markerColor: UIColor, panelBackgroundColor: UIColor, panelHighlightedBackgroundColor: UIColor, panelPrimaryColor: UIColor, panelSecondaryColor: UIColor, panelAccentColor: UIColor, tableBorderColor: UIColor, tableHeaderColor: UIColor, controlColor: UIColor, imageTintColor: UIColor?, overlayPanelColor: UIColor, separatorColor: UIColor, secondaryControlColor: UIColor) { self.type = type self.pageBackgroundColor = pageBackgroundColor self.textCategories = textCategories @@ -131,10 +163,50 @@ public final class InstantPageTheme { self.controlColor = controlColor self.imageTintColor = imageTintColor self.overlayPanelColor = overlayPanelColor + self.separatorColor = separatorColor + self.secondaryControlColor = secondaryControlColor } - public func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme { - return InstantPageTheme(type: type, pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageTintColor: imageTintColor, overlayPanelColor: overlayPanelColor) + public func withUpdatedFontStyles(sizeMultiplier: CGFloat, lineSpacingFactor: CGFloat, forceSerif: Bool) -> InstantPageTheme { + return InstantPageTheme(type: type, pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, lineSpacingFactor: lineSpacingFactor, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, linkColor: linkColor, textHighlightColor: textHighlightColor, linkHighlightColor: linkHighlightColor, markerColor: markerColor, panelBackgroundColor: panelBackgroundColor, panelHighlightedBackgroundColor: panelHighlightedBackgroundColor, panelPrimaryColor: panelPrimaryColor, panelSecondaryColor: panelSecondaryColor, panelAccentColor: panelAccentColor, tableBorderColor: tableBorderColor, tableHeaderColor: tableHeaderColor, controlColor: controlColor, imageTintColor: imageTintColor, overlayPanelColor: overlayPanelColor, separatorColor: separatorColor, secondaryControlColor: secondaryControlColor) + } + + func headingTextAttributes(level: Int32, link: Bool) -> InstantPageTextAttributes { + let clampedLevel = max(Int32(1), min(level, Int32(6))) + + // H1/H2 reuse the theme's existing big-text categories verbatim, so they + // pick up the theme color, line-spacing, and any dynamic-type scaling. + switch clampedLevel { + case 1: + return self.textCategories.header.withUnderline(link) + case 2: + return self.textCategories.subheader.withUnderline(link) + default: + break + } + + // H3–H6: serif at a per-level base size, scaled by the same dynamic-type + // multiplier the subheader category uses (subheader.size / 19.0). + let subheaderAttributes = self.textCategories.subheader + let baseSize: CGFloat + switch clampedLevel { + case 3: + baseSize = 17.0 + case 4: + baseSize = 16.0 + case 5: + baseSize = 15.0 + default: + baseSize = 13.0 + } + + let sizeMultiplier = subheaderAttributes.font.size / 18.0 + let attributes = InstantPageTextAttributes( + font: InstantPageFont(style: .serif, size: floor(baseSize * sizeMultiplier), lineSpacingFactor: subheaderAttributes.font.lineSpacingFactor), + color: subheaderAttributes.color, + underline: subheaderAttributes.underline + ) + return attributes.withUnderline(link) } } @@ -149,7 +221,8 @@ private let lightTheme = InstantPageTheme( caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)), credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x79828b)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: .black), - article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: .black) + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: .black), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: .black) ), serif: false, codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc), @@ -166,7 +239,9 @@ private let lightTheme = InstantPageTheme( tableHeaderColor: UIColor(rgb: 0xf4f4f4), controlColor: UIColor(rgb: 0xc7c7cd), imageTintColor: nil, - overlayPanelColor: .white + overlayPanelColor: .white, + separatorColor: UIColor(rgb: 0xe2e2e2), + secondaryControlColor: .black ) private let sepiaTheme = InstantPageTheme( @@ -180,7 +255,8 @@ private let sepiaTheme = InstantPageTheme( caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)), credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x927e6b)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)), - article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)) + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x4f321d)) ), serif: false, codeBlockBackgroundColor: UIColor(rgb: 0xefe7d6), @@ -197,7 +273,9 @@ private let sepiaTheme = InstantPageTheme( tableHeaderColor: UIColor(rgb: 0xf0e7d4), controlColor: UIColor(rgb: 0xddd1b8), imageTintColor: nil, - overlayPanelColor: UIColor(rgb: 0xf8f1e2) + overlayPanelColor: UIColor(rgb: 0xf8f1e2), + separatorColor: UIColor(rgb: 0xe2e2e2), + secondaryControlColor: .black ) private let grayTheme = InstantPageTheme( @@ -211,7 +289,8 @@ private let grayTheme = InstantPageTheme( caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)), credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xa0a0a0)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)), - article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)) + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xcecece)) ), serif: false, codeBlockBackgroundColor: UIColor(rgb: 0x555556), @@ -228,7 +307,9 @@ private let grayTheme = InstantPageTheme( tableHeaderColor: UIColor(rgb: 0x555556), controlColor: UIColor(rgb: 0x484848), imageTintColor: UIColor(rgb: 0xcecece), - overlayPanelColor: UIColor(rgb: 0x5a5a5c) + overlayPanelColor: UIColor(rgb: 0x5a5a5c), + separatorColor: UIColor(rgb: 0x484848), + secondaryControlColor: .black ) private let darkTheme = InstantPageTheme( @@ -242,7 +323,8 @@ private let darkTheme = InstantPageTheme( caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)), credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0x6a6a6a)), table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)), - article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)) + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: UIColor(rgb: 0xb0b0b0)) ), serif: false, codeBlockBackgroundColor: UIColor(rgb: 0x131313), @@ -259,7 +341,9 @@ private let darkTheme = InstantPageTheme( tableHeaderColor: UIColor(rgb: 0x131313), controlColor: UIColor(rgb: 0x303030), imageTintColor: UIColor(rgb: 0xb0b0b0), - overlayPanelColor: UIColor(rgb: 0x232323) + overlayPanelColor: UIColor(rgb: 0x232323), + separatorColor: UIColor(rgb: 0x303030), + secondaryControlColor: UIColor(rgb: 0xb0b0b0) ) private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat { @@ -314,14 +398,14 @@ func instantPageThemeTypeForSettingsAndTime(themeSettings: PresentationThemeSett public func instantPageThemeForType(_ type: InstantPageThemeType, settings: InstantPagePresentationSettings) -> InstantPageTheme { switch type { - case .light: - return lightTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif) - case .sepia: - return sepiaTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif) - case .gray: - return grayTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif) - case .dark: - return darkTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), forceSerif: settings.forceSerif) + case .light: + return lightTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), lineSpacingFactor: settings.lineSpacingFactor, forceSerif: settings.forceSerif) + case .sepia: + return sepiaTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), lineSpacingFactor: settings.lineSpacingFactor, forceSerif: settings.forceSerif) + case .gray: + return grayTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), lineSpacingFactor: settings.lineSpacingFactor, forceSerif: settings.forceSerif) + case .dark: + return darkTheme.withUpdatedFontStyles(sizeMultiplier: fontSizeMultiplierForVariant(settings.fontSize), lineSpacingFactor: settings.lineSpacingFactor, forceSerif: settings.forceSerif) } } diff --git a/submodules/InstantPageUI/Sources/InstantPageTileNode.swift b/submodules/InstantPageUI/Sources/InstantPageTileNode.swift index c16bcac1dd..2da701bd59 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTileNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTileNode.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import AsyncDisplayKit +import Display private final class InstantPageTileNodeParameters: NSObject { let tile: InstantPageTile @@ -15,10 +16,12 @@ private final class InstantPageTileNodeParameters: NSObject { } public final class InstantPageTileNode: ASDisplayNode { - private let tile: InstantPageTile + private var tile: InstantPageTile + private var tileBackgroundColor: UIColor public init(tile: InstantPageTile, backgroundColor: UIColor) { self.tile = tile + self.tileBackgroundColor = backgroundColor super.init() @@ -27,8 +30,15 @@ public final class InstantPageTileNode: ASDisplayNode { self.backgroundColor = backgroundColor } + public func update(tile: InstantPageTile, backgroundColor: UIColor) { + self.tile = tile + self.tileBackgroundColor = backgroundColor + self.backgroundColor = backgroundColor + self.setNeedsDisplay() + } + public override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - return InstantPageTileNodeParameters(tile: self.tile, backgroundColor: self.backgroundColor ?? UIColor.white) + return InstantPageTileNodeParameters(tile: self.tile, backgroundColor: self.tileBackgroundColor) } @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -36,9 +46,11 @@ public final class InstantPageTileNode: ASDisplayNode { if let parameters = parameters as? InstantPageTileNodeParameters { if !isRasterizing { - context.setBlendMode(.copy) - context.setFillColor(parameters.backgroundColor.cgColor) - context.fill(bounds) + if !parameters.backgroundColor.alpha.isZero { + context.setBlendMode(.copy) + context.setFillColor(parameters.backgroundColor.cgColor) + context.fill(bounds) + } } parameters.tile.draw(context: context) diff --git a/submodules/InstantPageUI/Sources/InstantPageV2AudioContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageV2AudioContentNode.swift new file mode 100644 index 0000000000..a3b0dd0db5 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2AudioContentNode.swift @@ -0,0 +1,278 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import AccountContext +import UniversalMediaPlayer +import SemanticStatusNode +import MusicAlbumArtResources +import PhotoResources + +// Renders an InstantPage audio block to match the standard music message bubble +// (ChatMessageInteractiveFileNode, non-thumbnail/non-voice music branch). Visual only differs +// from the file node in that playback is driven by InstantPageMediaPlaylist (our `play` closure) +// rather than the peer-messages model. V1's InstantPageAudioNode is unaffected. +final class InstantPageV2AudioContentNode: ASDisplayNode { + private let context: AccountContext + private let message: MessageReference? + private let file: TelegramMediaFile + private let incoming: Bool + + private let statusNode: SemanticStatusNode + private let streamingStatusNode: SemanticStatusNode + private let titleNode: TextNode + private let descriptionNode: TextNode + private let tapView: UIView + + // TextNode (unlike ASTextNode) has no stored `attributedText`; the string is an argument to + // `TextNode.asyncLayout`. Keep the current strings here and feed them in `updateLayout`. + private var titleAttributedString: NSAttributedString? + private var descriptionAttributedString: NSAttributedString? + + var play: () -> Void = {} + var togglePlayPause: () -> Void = {} + var fetch: () -> Void = {} + + private var resourceStatusDisposable: Disposable? + // EngineMediaResourceStatus is the TelegramCore typealias for Postbox's MediaResourceStatus; + // using it keeps this file off `import Postbox` (TelegramCore doesn't re-export Postbox). + private var fetchStatus: EngineMediaResourceStatus? + + private var playbackStatusDisposable: Disposable? + private(set) var isPlaying: Bool = false + + // Theme-refresh state. `incoming` is already a `let` stored above; `incomingValue` tracks the + // last theme-update's incoming flag so `updatePresentationData` can guard on change. + private var presentationData: PresentationData + private var incomingValue: Bool + + private static let progressDiameter: CGFloat = 40.0 + // Shifted +9pt right of the original x=3 (→ 12); the Ø40 control is vertically centered in the + // 44pt row (y = (44 − 40)/2 = 2). + private static let progressOrigin = CGPoint(x: 12.0, y: 2.0) + private static let controlAreaWidth: CGFloat = 12.0 + 40.0 + 8.0 + private static let normHeight: CGFloat = 44.0 + + init(context: AccountContext, message: MessageReference?, file: TelegramMediaFile, incoming: Bool, presentationData: PresentationData) { + self.context = context + self.message = message + self.file = file + self.incoming = incoming + self.presentationData = presentationData + self.incomingValue = incoming + + let messageTheme = incoming ? presentationData.theme.chat.message.incoming : presentationData.theme.chat.message.outgoing + + let backgroundNodeColor = messageTheme.mediaActiveControlColor + let foregroundNodeColor: UIColor = (incoming && messageTheme.mediaActiveControlColor.rgb != 0xffffff) ? .white : .clear + + var title: String? + var performer: String? + for attribute in file.attributes { + if case let .Audio(_, _, t, p, _) = attribute { title = t; performer = p; break } + } + let albumArtImage: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? + if file.isMusic, file.fileName?.lowercased().hasSuffix(".ogg") != true, let message = message { + let fileRef: FileMediaReference = .message(message: message, media: file) + albumArtImage = playerAlbumArt( + engine: context.engine, + fileReference: fileRef, + albumArt: SharedMediaPlaybackAlbumArt( + thumbnailResource: ExternalMusicAlbumArtResource(file: fileRef, title: title ?? "", performer: performer ?? "", isThumbnail: true), + fullSizeResource: ExternalMusicAlbumArtResource(file: fileRef, title: title ?? "", performer: performer ?? "", isThumbnail: false) + ), + thumbnail: true, + overlayColor: UIColor(white: 0.0, alpha: 0.3), + drawPlaceholderWhenEmpty: false, + attemptSynchronously: false + ) + } else { + albumArtImage = nil + } + + self.statusNode = SemanticStatusNode( + backgroundNodeColor: backgroundNodeColor, + foregroundNodeColor: foregroundNodeColor, + image: albumArtImage, + overlayForegroundNodeColor: presentationData.theme.chat.message.mediaOverlayControlColors.foregroundColor + ) + self.streamingStatusNode = SemanticStatusNode(backgroundNodeColor: .clear, foregroundNodeColor: messageTheme.mediaActiveControlColor) + + self.titleNode = TextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.isUserInteractionEnabled = false + self.descriptionNode = TextNode() + self.descriptionNode.displaysAsynchronously = false + self.descriptionNode.isUserInteractionEnabled = false + + self.tapView = UIView() + + super.init() + + self.titleAttributedString = InstantPageV2AudioContentNode.titleString(file: file, incoming: incoming, presentationData: presentationData) + self.descriptionAttributedString = InstantPageV2AudioContentNode.descriptionString(file: file, incoming: incoming, presentationData: presentationData) + + self.addSubnode(self.statusNode) + self.addSubnode(self.streamingStatusNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.descriptionNode) + + self.statusNode.transitionToState(.play, animated: false) + self.streamingStatusNode.transitionToState(.none, animated: false) + + if let messageId = self.message?.id { + self.resourceStatusDisposable = (messageMediaFileStatus(context: context, messageId: messageId, file: file) + |> deliverOnMainQueue).startStrict(next: { [weak self] status in + self?.fetchStatus = status + self?.updateStreamingState() + }) + } + } + + override func didLoad() { + super.didLoad() + // Tap target = plain view + UITapGestureRecognizer, NOT an ASControl: ASControl + // .touchUpInside is cancelled by the chat ListView's gesture system (see InstantPageAudioNode + // for the same reason). + self.view.addSubview(self.tapView) + self.tapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped))) + } + + @objc private func tapped() { + self.controlTapped() + } + + func controlTapped() { + switch self.fetchStatus { + case .Remote, .Paused: + self.fetch() + case .none, .Local, .Fetching: + if self.isPlaying { + self.togglePlayPause() + } else { + self.play() + } + } + } + + deinit { + self.resourceStatusDisposable?.dispose() + self.playbackStatusDisposable?.dispose() + } + + // Drives the big control's play/pause icon from the playlist state filtered to our + // playlistId + itemId. Mirrors InstantPageAudioNode's subscription shape. + func setPlaybackStatusSignal(_ signal: Signal) { + self.playbackStatusDisposable?.dispose() // defensive: a re-call must not leak the prior subscription + self.playbackStatusDisposable = (signal |> deliverOnMainQueue).startStrict(next: { [weak self] state in + guard let self else { return } + let isPlaying: Bool + if let status = state?.status { + if case .playing = status.status { + isPlaying = true + } else { + isPlaying = false + } + } else { + isPlaying = false + } + self.isPlaying = isPlaying + self.statusNode.transitionToState(isPlaying ? .pause : .play) + }) + } + + // Refreshes title/description attributed strings and the statusNode tint/foreground/overlay + // colors when the theme or incoming direction changes. Called from the host view's + // update(item:theme:renderContext:). + func updatePresentationData(_ presentationData: PresentationData, incoming: Bool) { + if self.presentationData.theme === presentationData.theme && self.incomingValue == incoming { return } + self.presentationData = presentationData + self.incomingValue = incoming + self.titleAttributedString = InstantPageV2AudioContentNode.titleString(file: self.file, incoming: incoming, presentationData: presentationData) + self.descriptionAttributedString = InstantPageV2AudioContentNode.descriptionString(file: self.file, incoming: incoming, presentationData: presentationData) + let messageTheme = incoming ? presentationData.theme.chat.message.incoming : presentationData.theme.chat.message.outgoing + self.statusNode.backgroundNodeColor = messageTheme.mediaActiveControlColor + // foreground/overlay also depend on incoming + theme (set at construction) — refresh them + // too so the play glyph isn't miscolored after an in-place theme/direction change. + self.statusNode.foregroundNodeColor = (incoming && messageTheme.mediaActiveControlColor.rgb != 0xffffff) ? .white : .clear + self.statusNode.overlayForegroundNodeColor = presentationData.theme.chat.message.mediaOverlayControlColors.foregroundColor + + // No setNeedsLayout(): this node doesn't override layout(); the host calls updateLayout(width:) + // right after updatePresentationData, which re-runs the text layout with the rebuilt strings. + } + + private func updateStreamingState() { + let state: SemanticStatusNodeState + switch self.fetchStatus { + case .none, .Local: + state = .none + case let .Fetching(_, progress): + state = .progress(value: CGFloat(max(progress, 0.027)), cancelEnabled: true, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 1.0, lineWidth: 2.0), animateRotation: true) + case .Remote, .Paused: + state = .download + } + self.streamingStatusNode.transitionToState(state) + } + + // Line 1: track title at 17pt (= baseDisplaySize at the default font setting; scales with it). + private static func titleString(file: TelegramMediaFile, incoming: Bool, presentationData: PresentationData) -> NSAttributedString { + let messageTheme = incoming ? presentationData.theme.chat.message.incoming : presentationData.theme.chat.message.outgoing + let titleFont = Font.regular(floor(presentationData.chatFontSize.baseDisplaySize * 17.0 / 17.0)) + var title = file.fileName ?? "Unknown Track" + for attribute in file.attributes { + if case let .Audio(false, _, t, _, _) = attribute { title = t ?? title; break } + } + return NSAttributedString(string: title, font: titleFont, textColor: messageTheme.fileTitleColor) + } + + // Line 2: " · " at 15pt (omits the "· performer" tail when there's no + // performer; omits the duration when it's absent). + private static func descriptionString(file: TelegramMediaFile, incoming: Bool, presentationData: PresentationData) -> NSAttributedString { + let messageTheme = incoming ? presentationData.theme.chat.message.incoming : presentationData.theme.chat.message.outgoing + let descriptionFont = Font.with(size: floor(presentationData.chatFontSize.baseDisplaySize * 15.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers]) + var performer = "" + var durationSeconds: Int = 0 + for attribute in file.attributes { + if case let .Audio(false, duration, _, p, _) = attribute { + performer = (p ?? "").trimmingCharacters(in: .whitespaces) + durationSeconds = duration + break + } + } + var text = "" + if durationSeconds > 0 { + text = String(format: "%d:%02d", Int32(durationSeconds / 60), Int32(durationSeconds % 60)) + } + if !performer.isEmpty { + text += text.isEmpty ? performer : " · \(performer)" + } + return NSAttributedString(string: text, font: descriptionFont, textColor: messageTheme.fileDescriptionColor) + } + + func updateLayout(width: CGFloat) { + let progressFrame = CGRect(origin: InstantPageV2AudioContentNode.progressOrigin, size: CGSize(width: InstantPageV2AudioContentNode.progressDiameter, height: InstantPageV2AudioContentNode.progressDiameter)) + self.statusNode.frame = progressFrame + let streamingDiameter: CGFloat = 24.0 + self.streamingStatusNode.frame = CGRect(origin: CGPoint(x: progressFrame.maxX - streamingDiameter + 2.0, y: progressFrame.maxY - streamingDiameter + 2.0), size: CGSize(width: streamingDiameter, height: streamingDiameter)) + + let controlAreaWidth = InstantPageV2AudioContentNode.controlAreaWidth + let textWidth = max(1.0, width - controlAreaWidth - 8.0) + let (titleLayout, titleApply) = TextNode.asyncLayout(self.titleNode)(TextNodeLayoutArguments(attributedString: self.titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: textWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (descLayout, descApply) = TextNode.asyncLayout(self.descriptionNode)(TextNodeLayoutArguments(attributedString: self.descriptionAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let _ = titleApply() + let _ = descApply() + + let titleAndDescriptionHeight = titleLayout.size.height - 1.0 + descLayout.size.height + let normHeight = InstantPageV2AudioContentNode.normHeight + let titleFrame = CGRect(origin: CGPoint(x: controlAreaWidth, y: floor((normHeight - titleAndDescriptionHeight) / 2.0)), size: titleLayout.size) + self.titleNode.frame = titleFrame + self.descriptionNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY - 1.0), size: descLayout.size) + + // No scrubber. The tapView covers the full row so a tap anywhere toggles playback (there is + // no scrubber pan to conflict with anymore). + self.tapView.frame = CGRect(origin: .zero, size: CGSize(width: width, height: normHeight)) + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageV2InlineImageView.swift b/submodules/InstantPageUI/Sources/InstantPageV2InlineImageView.swift new file mode 100644 index 0000000000..d0cffb82ee --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2InlineImageView.swift @@ -0,0 +1,134 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import SwiftSignalKit +import TelegramPresentationData +import AccountContext +import PhotoResources +import MediaResources + +/// Lightweight inline image view for InstantPage V2 — wraps a `TransformImageNode` +/// to render a single `RichText.image` cell inside a text view. +/// +/// Owned by `InstantPageV2View` (not an `InstantPageItemView` conformer; not in +/// the view-factory switch). Hosted inside the parent text view's +/// `imageContainerView` (sibling of `renderContainer`, above the reveal mask, +/// below `emojiContainerView`), so the streaming reveal can wipe text glyphs +/// while the image pops in independently. Non-interactive — taps pass through +/// to the text view, so a URL-wrapping `RichText.url(text: .image(...))` +/// continues to route taps to the URL handler. +final class InstantPageV2InlineImageView: UIView { + let fileId: Int64 + private let imageNode: TransformImageNode + private let media: EngineMedia + private let theme: InstantPageTheme + private let fetchedDisposable = MetaDisposable() + + init(media: EngineMedia, + webpage: TelegramMediaWebpage?, + frame: CGRect, + context: AccountContext, + userLocation: MediaResourceUserLocation, + theme: InstantPageTheme) { + self.media = media + self.theme = theme + self.fileId = media.id?.id ?? 0 + self.imageNode = TransformImageNode() + + super.init(frame: frame) + self.isUserInteractionEnabled = false + self.addSubview(self.imageNode.view) + + self.bindSignal(webpage: webpage, context: context, userLocation: userLocation) + self.applyLayout(size: frame.size) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + self.fetchedDisposable.dispose() + } + + private func bindSignal(webpage: TelegramMediaWebpage?, + context: AccountContext, + userLocation: MediaResourceUserLocation) { + // Without a webpage we can't form a `WebpageReference` for the standard + // chat-message signals, so the image node stays at its empty colour. + guard let webpage = webpage else { return } + let webPageRef = WebpageReference(webpage) + + switch self.media { + case let .image(image): + let imageReference = ImageMediaReference.webPage(webPage: webPageRef, media: image) + self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, + userLocation: userLocation, + photoReference: imageReference)) + // Non-interactive: always auto-fetch so the image arrives without a tap. + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, + userLocation: userLocation, + photoReference: imageReference, + displayAtSize: nil, + storeToDownloadsPeerId: nil).start()) + case let .file(file): + let fileReference = FileMediaReference.webPage(webPage: webPageRef, media: file) + if file.mimeType.hasPrefix("image/") { + self.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: context.account, + userLocation: userLocation, + fileReference: fileReference).start()) + self.imageNode.setSignal(instantPageImageFile(account: context.account, + userLocation: userLocation, + fileReference: fileReference, + fetched: true)) + } else { + // Video / animated file: render a single still frame. No play overlay. + self.imageNode.setSignal(chatMessageVideo(postbox: context.account.postbox, + userLocation: userLocation, + videoReference: fileReference)) + } + default: + // RichText.image's MediaId resolves to .image or .file in practice; other + // EngineMedia kinds (geo, webpage, story, ...) leave the image node blank. + break + } + } + + private func applyLayout(size: CGSize) { + guard size.width > 0, size.height > 0 else { return } + self.imageNode.frame = CGRect(origin: .zero, size: size) + + let intrinsic: CGSize + switch self.media { + case let .image(image): + if let largest = largestImageRepresentation(image.representations) { + intrinsic = largest.dimensions.cgSize + } else { + intrinsic = size + } + case let .file(file): + intrinsic = file.dimensions?.cgSize ?? size + default: + intrinsic = size + } + + let imageSize = intrinsic.aspectFilled(size) + let arguments = TransformImageArguments( + corners: ImageCorners(), + imageSize: imageSize, + boundingSize: size, + intrinsicInsets: UIEdgeInsets(), + emptyColor: nil + ) + let apply = self.imageNode.asyncLayout()(arguments) + apply() + } + + override func layoutSubviews() { + super.layoutSubviews() + if self.imageNode.frame.size != self.bounds.size { + self.applyLayout(size: self.bounds.size) + } + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageV2Layout.swift b/submodules/InstantPageUI/Sources/InstantPageV2Layout.swift new file mode 100644 index 0000000000..2f20740493 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2Layout.swift @@ -0,0 +1,3503 @@ +import Foundation +import UIKit +import TelegramCore +import Display +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import TelegramStringFormatting +import MosaicLayout + +// MARK: - Public layout data types + +public struct InstantPageV2Layout { + public let contentSize: CGSize + public let items: [InstantPageV2LaidOutItem] + /// Snapshot of the `index` values of every `.details` item present in `items`, captured at layout time. + public let detailsIndices: [Int] + /// Media dictionary inherited from the page's `LayoutContext.media`. Used by + /// `InstantPageV2View.updateInlineImages()` to resolve each text view's + /// `line.imageItems` MediaIds at update time. Nested layouts (details body, + /// table cells, table title) carry the parent's same map. + public let media: [EngineMedia.Id: EngineMedia] + /// Webpage carried for the same reason — `updateInlineImages()` needs it to + /// form the `WebpageReference` for `ImageMediaReference.webPage(...)`. May + /// be nil for non-webpage-anchored layouts; in that case the lookup proceeds + /// but no fetch signal can be bound (image view simply isn't created). + public let webpage: TelegramMediaWebpage? + + /// Set by `layoutInstantPageV2` when the page contains at least one `.relative` `textDate`. + /// The minimum refresh period (seconds, >=10) across all relative dates; the rich-data bubble + /// schedules a timer on it to keep "N minutes ago" fresh. nil => no relative date => no timer. + public var formattedDateUpdatePeriod: Int32? = nil + + public init(contentSize: CGSize, items: [InstantPageV2LaidOutItem], detailsIndices: [Int], media: [EngineMedia.Id: EngineMedia] = [:], webpage: TelegramMediaWebpage? = nil) { + self.contentSize = contentSize + self.items = items + self.detailsIndices = detailsIndices + self.media = media + self.webpage = webpage + } + + /// Returns every `InstantPageMedia` produced by this layout (or its nested sub-layouts) + /// in laid-out order. Used by the gallery helper to enumerate sibling medias and find + /// central index, mirroring V1's `mediasFromItems(_:)`. + public func allMedias() -> [InstantPageMedia] { + var result: [InstantPageMedia] = [] + InstantPageV2Layout.collectMedias(in: self.items, into: &result) + return result + } + + private static func collectMedias(in items: [InstantPageV2LaidOutItem], into result: inout [InstantPageMedia]) { + for item in items { + switch item { + case let .mediaImage(m): result.append(m.media) + case let .mediaVideo(m): result.append(m.media) + case let .mediaMap(m): result.append(m.media) + case let .mediaCoverImage(m): result.append(m.media) + case let .mediaAudio(m): result.append(m.media) + case let .slideshow(s): result.append(contentsOf: s.medias) + case let .details(d): + if let inner = d.innerLayout { + collectMedias(in: inner.items, into: &result) + } + case let .table(t): + if let title = t.titleSubLayout { + collectMedias(in: title.items, into: &result) + } + for cell in t.cells { + if let sub = cell.subLayout { + collectMedias(in: sub.items, into: &result) + } + } + default: + continue + } + } + } +} + +public enum InstantPageV2LaidOutItem { + case text(InstantPageV2TextItem) + case codeBlock(InstantPageV2CodeBlockItem) + case divider(InstantPageV2DividerItem) + case listMarker(InstantPageV2ListMarkerItem) + case blockQuoteBar(InstantPageV2BarItem) + case shape(InstantPageV2ShapeItem) + case mediaPlaceholder(InstantPageV2MediaPlaceholderItem) + case details(InstantPageV2DetailsItem) + case table(InstantPageV2TableItem) + case anchor(InstantPageV2AnchorItem) + case mediaImage(InstantPageV2MediaImageItem) + case mediaVideo(InstantPageV2MediaVideoItem) + case mediaMap(InstantPageV2MediaMapItem) + case mediaCoverImage(InstantPageV2MediaCoverImageItem) + case mediaAudio(InstantPageV2MediaAudioItem) + case formula(InstantPageV2FormulaItem) + case thinking(InstantPageV2ThinkingItem) + case slideshow(InstantPageV2SlideshowItem) + + public var frame: CGRect { + switch self { + case let .text(item): return item.frame + case let .codeBlock(item): return item.frame + case let .divider(item): return item.frame + case let .listMarker(item): return item.frame + case let .blockQuoteBar(item): return item.frame + case let .shape(item): return item.frame + case let .mediaPlaceholder(item): return item.frame + case let .details(item): return item.frame + case let .table(item): return item.frame + case let .anchor(item): return item.frame + case let .mediaImage(item): return item.frame + case let .mediaVideo(item): return item.frame + case let .mediaMap(item): return item.frame + case let .mediaCoverImage(item): return item.frame + case let .mediaAudio(item): return item.frame + case let .formula(item): return item.frame + case let .thinking(item): return item.frame + case let .slideshow(item): return item.frame + } + } + + /// Returns a copy of `self` with its top-level frame translated by `delta`. + /// Sub-layouts inside details/table cells are not re-translated — they're already + /// expressed in their parent's local coordinates. + public func offsetBy(_ delta: CGPoint) -> InstantPageV2LaidOutItem { + switch self { + case var .text(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .text(item) + case var .codeBlock(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .codeBlock(item) + case var .divider(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .divider(item) + case var .listMarker(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .listMarker(item) + case var .blockQuoteBar(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .blockQuoteBar(item) + case var .shape(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .shape(item) + case var .mediaPlaceholder(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaPlaceholder(item) + case var .details(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .details(item) + case var .table(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .table(item) + case var .anchor(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .anchor(item) + case var .mediaImage(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaImage(item) + case var .mediaVideo(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaVideo(item) + case var .mediaMap(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaMap(item) + case var .mediaCoverImage(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaCoverImage(item) + case var .mediaAudio(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .mediaAudio(item) + case var .formula(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .formula(item) + case var .thinking(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .thinking(item) + case var .slideshow(item): item.frame = item.frame.offsetBy(dx: delta.x, dy: delta.y); return .slideshow(item) + } + } +} + +public struct InstantPageV2TextItem { + public var frame: CGRect + public let textItem: InstantPageTextItem // V1 type reused as payload +} + +public struct InstantPageV2CodeBlockItem { + public var frame: CGRect + public let backgroundColor: UIColor + public let cornerRadius: CGFloat + public let textItem: InstantPageTextItem + public let inset: UIEdgeInsets +} + +public struct InstantPageV2ThinkingItem { + public var frame: CGRect + /// The dimmed thinking text, laid out in block-local coordinates. Drawn fully (never + /// char-reveal-masked); the shimmer + whole-block fade are the only animations. + public let textItem: InstantPageTextItem +} + +public struct InstantPageV2DividerItem { + public var frame: CGRect + public let color: UIColor +} + +public struct InstantPageV2CheckboxColors { + public let background: UIColor + public let stroke: UIColor + public let border: UIColor + + public init(background: UIColor, stroke: UIColor, border: UIColor) { + self.background = background + self.stroke = stroke + self.border = border + } +} + +public enum InstantPageV2ListMarkerKind { + case bullet + case number(String) + case checklist(checked: Bool, colors: InstantPageV2CheckboxColors) +} + +public struct InstantPageV2ListMarkerItem { + public var frame: CGRect + public let kind: InstantPageV2ListMarkerKind + public let color: UIColor +} + +public struct InstantPageV2BarItem { + public var frame: CGRect + public let color: UIColor + public let cornerRadius: CGFloat +} + +public enum InstantPageV2ShapeKind { + case roundedRect(cornerRadius: CGFloat) + case line(thickness: CGFloat) +} + +public struct InstantPageV2ShapeItem { + public var frame: CGRect + public let kind: InstantPageV2ShapeKind + public let color: UIColor +} + +public struct InstantPageV2FormulaItem { + public var frame: CGRect // outer frame in parent coords + public let attachment: InstantPageMathAttachment // rendered image + dimensions (theme-baked) + public let isScrollable: Bool // true only for block formulas wider than bounds + public let imageFrame: CGRect // image rect in this item's local coords; size must equal attachment.rendered.size for pixel-perfect rendering + public let scrollContentSize: CGSize // == frame.size unless isScrollable +} + +public enum InstantPageV2MediaPlaceholderKind { + case image + case video + case audio + case webEmbed + case postEmbed + case collage + case slideshow + case channelBanner + case map + case relatedArticles +} + +public struct InstantPageV2MediaImageItem { + public var frame: CGRect + public let cornerRadius: CGFloat + public let media: InstantPageMedia + public let webPage: TelegramMediaWebpage + public let attributes: [InstantPageImageAttribute] // always empty for image; kept for symmetry + + public init(frame: CGRect, cornerRadius: CGFloat, media: InstantPageMedia, webPage: TelegramMediaWebpage, attributes: [InstantPageImageAttribute]) { + self.frame = frame + self.cornerRadius = cornerRadius + self.media = media + self.webPage = webPage + self.attributes = attributes + } +} + +public struct InstantPageV2MediaAudioItem { + public var frame: CGRect + public let media: InstantPageMedia + public let webPage: TelegramMediaWebpage + + public init(frame: CGRect, media: InstantPageMedia, webPage: TelegramMediaWebpage) { + self.frame = frame + self.media = media + self.webPage = webPage + } +} + +public struct InstantPageV2MediaVideoItem { + public var frame: CGRect + public let cornerRadius: CGFloat + public let media: InstantPageMedia + public let webPage: TelegramMediaWebpage + public let attributes: [InstantPageImageAttribute] // always empty + + public init(frame: CGRect, cornerRadius: CGFloat, media: InstantPageMedia, webPage: TelegramMediaWebpage, attributes: [InstantPageImageAttribute]) { + self.frame = frame + self.cornerRadius = cornerRadius + self.media = media + self.webPage = webPage + self.attributes = attributes + } +} + +public struct InstantPageV2MediaMapItem { + public var frame: CGRect + public let cornerRadius: CGFloat + public let media: InstantPageMedia + public let webPage: TelegramMediaWebpage + public let attributes: [InstantPageImageAttribute] // [InstantPageMapAttribute] with zoom + dimensions + + public init(frame: CGRect, cornerRadius: CGFloat, media: InstantPageMedia, webPage: TelegramMediaWebpage, attributes: [InstantPageImageAttribute]) { + self.frame = frame + self.cornerRadius = cornerRadius + self.media = media + self.webPage = webPage + self.attributes = attributes + } +} + +public struct InstantPageV2MediaCoverImageItem { + public var frame: CGRect + public let cornerRadius: CGFloat + public let media: InstantPageMedia // media.media == .webpage(synthesized fake webpage with cover image) + public let webPage: TelegramMediaWebpage // the parent IV's webpage (for WebpageReference) + public let attributes: [InstantPageImageAttribute] // always empty + + public init(frame: CGRect, cornerRadius: CGFloat, media: InstantPageMedia, webPage: TelegramMediaWebpage, attributes: [InstantPageImageAttribute]) { + self.frame = frame + self.cornerRadius = cornerRadius + self.media = media + self.webPage = webPage + self.attributes = attributes + } +} + +public struct InstantPageV2MediaPlaceholderItem { + public var frame: CGRect + public let kind: InstantPageV2MediaPlaceholderKind + public let cornerRadius: CGFloat +} + +public struct InstantPageV2SlideshowItem { + public var frame: CGRect + public let medias: [InstantPageMedia] + public let webPage: TelegramMediaWebpage + + public init(frame: CGRect, medias: [InstantPageMedia], webPage: TelegramMediaWebpage) { + self.frame = frame + self.medias = medias + self.webPage = webPage + } +} + +public struct InstantPageV2DetailsItem { + public var frame: CGRect + public let index: Int + public let sideInset: CGFloat + public let titleTextItem: InstantPageTextItem + public let titleFrame: CGRect // local to this item's frame + public let separatorColor: UIColor + public let isExpanded: Bool + public let innerLayout: InstantPageV2Layout? + public let defaultExpanded: Bool // from the InstantPageBlock model + public let rtl: Bool // mirror chevron + title onto the trailing edge +} + +public enum InstantPageV2TableVerticalAlignment { + case top, middle, bottom +} + +public struct InstantPageV2TableCell { + public let frame: CGRect // local to the table's content area + public let isHeader: Bool + public let horizontalAlignment: NSTextAlignment + public let verticalAlignment: InstantPageV2TableVerticalAlignment + public let backgroundColor: UIColor? + public let subLayout: InstantPageV2Layout? +} + +public struct InstantPageV2TableItem { + public var frame: CGRect + public let titleSubLayout: InstantPageV2Layout? + public let titleFrame: CGRect? + public let contentSize: CGSize // grid intrinsic size; may exceed frame.width → scroll + public let contentInset: CGFloat // page horizontalInset; the renderer shifts the grid right by it and pads the scroll content by it on BOTH sides + public let cells: [InstantPageV2TableCell] + public let horizontalLines: [CGRect] + public let verticalLines: [CGRect] + public let bordered: Bool + public let striped: Bool + public let borderColor: UIColor +} + +public struct InstantPageV2AnchorItem { + public var frame: CGRect // zero-height + public let name: String +} + +// MARK: - Public entry points + +public func layoutInstantPageV2( + webpage: TelegramMediaWebpage, + instantPage: InstantPage, + userLocation: MediaResourceUserLocation, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + theme: InstantPageTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight?, + expandedDetails: [Int: Bool], + fitToWidth: Bool, + computeRevealCharacterRects: Bool = false +) -> InstantPageV2Layout { + guard case let .Loaded(loadedContent) = webpage.content else { + return InstantPageV2Layout(contentSize: .zero, items: [], detailsIndices: []) + } + + var media = instantPage.media.mapValues(EngineMedia.init) + if let image = loadedContent.image, let id = image.id { + media[id] = .image(image) + } + if let video = loadedContent.file, let id = video.id { + media[id] = .file(video) + } + + let dateAccumulator = DateUpdateAccumulator() + let formatDate: (Int32, MessageTextEntityType.DateTimeFormat) -> String = { timestamp, format in + if case .relative = format { + let now = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + let age = abs(now - timestamp) + // Cap the fastest bucket at 10s (the message-entity reference uses 1s for <120s). + let period: Int32 = age < 120 ? 10 : (age <= 60 * 60 ? 60 : 30 * 60) + dateAccumulator.period = dateAccumulator.period.map { min($0, period) } ?? period + } + return stringForEntityFormattedDate(timestamp: timestamp, format: format, strings: strings, dateTimeFormat: dateTimeFormat) + } + + var context = LayoutContext( + theme: theme, + strings: strings, + dateTimeFormat: dateTimeFormat, + formatDate: formatDate, + userLocation: userLocation, + webpage: webpage, + media: media, + cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight, + rtl: instantPage.rtl, + fitToWidth: fitToWidth, + computeRevealCharacterRects: computeRevealCharacterRects, + pageHorizontalInset: horizontalInset, + mediaIndexCounter: 0, + detailsIndexCounter: 0, + expandedDetails: expandedDetails + ) + + var result = layoutBlockSequence( + instantPage.blocks, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + kind: .topLevel, + context: &context + ) + result.formattedDateUpdatePeriod = dateAccumulator.period + return result +} + +/// Used by `ChatMessageRichDataBubbleContentNode` to anchor the date/checks status node at the +/// bottom-right of the last text line in the bubble. +public func lastTextLineFrame(in layout: InstantPageV2Layout) -> CGRect? { + // Walk items in reverse; descend into the LAST sub-layout when the last container has one. + for item in layout.items.reversed() { + switch item { + case let .text(text): + if let last = text.textItem.lines.last { + return last.frame.offsetBy(dx: text.frame.minX, dy: text.frame.minY) + } + case let .codeBlock(block): + if let last = block.textItem.lines.last { + return last.frame + .offsetBy(dx: block.textItem.frame.minX, dy: block.textItem.frame.minY) + .offsetBy(dx: block.frame.minX, dy: block.frame.minY) + } + case let .details(details): + if let inner = details.innerLayout, let innerFrame = lastTextLineFrame(in: inner) { + return innerFrame.offsetBy(dx: details.frame.minX, dy: details.frame.minY + details.titleFrame.maxY) + } + // Title fallback + if let last = details.titleTextItem.lines.last { + return last.frame + .offsetBy(dx: details.titleTextItem.frame.minX, dy: details.titleTextItem.frame.minY) + .offsetBy(dx: details.frame.minX, dy: details.frame.minY) + } + case let .table(table): + // Walk cells in reverse row-major order (last cell of last row first). + // The renderer shifts cells down by gridOffsetY (title height) — match that here. + let gridOffsetY = table.titleFrame?.height ?? 0.0 + for cell in table.cells.reversed() { + if let subLayout = cell.subLayout, let frame = lastTextLineFrame(in: subLayout) { + return frame + .offsetBy(dx: cell.frame.minX, dy: cell.frame.minY + gridOffsetY) + .offsetBy(dx: table.frame.minX, dy: table.frame.minY) + } + } + default: + continue + } + } + return nil +} + +/// Variant of `lastTextLineFrame(in:)` that returns the last text line frame only when the +/// bottom-most top-level item in the layout is itself a text item. When the layout ends in a +/// non-text item (table, image, divider, list marker, details, …) it returns nil, so callers +/// place the trailing status below all content rather than beside text buried above the final +/// item. +/// +/// Also returns `trailingBottomPadding`: the renderer draws the baseline at the line frame's maxY, +/// so the visible text of a plain line sits ~5pt below it. A status that *trails on the line* should +/// anchor at `maxY + trailingBottomPadding` to align with where the text actually renders. The pad +/// is 0 when the line is taller than its font line height (a tall inline attachment, e.g. a formula, +/// already pushes maxY down to the right spot). Callers should NOT apply the pad when the status +/// wraps onto its own line below the text — there it should sit at the bare maxY. +public func lastTextLineFrameIfLastItemIsText(in layout: InstantPageV2Layout) -> (frame: CGRect, trailingBottomPadding: CGFloat)? { + guard let bottomItem = layout.items.max(by: { $0.frame.maxY < $1.frame.maxY }), + case let .text(text) = bottomItem, + let last = text.textItem.lines.last + else { + return nil + } + // The stored line frame always has minX = 0 — alignment (center / right / natural-RTL) is + // applied at render time by `v2FrameForLine`. Apply the same correction here so the returned + // frame's `maxX` reflects the line's actual on-screen right edge, not just its width anchored + // at the textItem's left. Without this, a right-aligned or RTL last line — whose visible right + // edge sits at `textItem.width`, all the way at the right text inset — would feed the status + // node a `contentWidth` equal to just `lineWidth`. The trail/wrap decision would then think + // the date fits trailing the line, and place it directly on top of the line at the right text + // inset where the line itself ends. The width is unchanged; only `origin.x` shifts. + let displayedLineFrame = v2FrameForLine(last, boundingWidth: text.textItem.frame.width, alignment: text.textItem.alignment) + let lineFrame = displayedLineFrame.offsetBy(dx: text.frame.minX, dy: text.frame.minY) + var ascent: CGFloat = 0.0 + var descent: CGFloat = 0.0 + var leading: CGFloat = 0.0 + _ = CTLineGetTypographicBounds(last.line, &ascent, &descent, &leading) + let isInflatedByAttachment = lineFrame.height > ascent + descent + 1.0 + return (lineFrame, isInflatedByAttachment ? 0.0 : 5.0) +} + +// MARK: - Layout context + +private final class DateUpdateAccumulator { + var period: Int32? +} + +private struct LayoutContext { + let theme: InstantPageTheme + let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat + let formatDate: (Int32, MessageTextEntityType.DateTimeFormat) -> String + let userLocation: MediaResourceUserLocation + let webpage: TelegramMediaWebpage + let media: [EngineMedia.Id: EngineMedia] + let cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? + let rtl: Bool + let fitToWidth: Bool + let computeRevealCharacterRects: Bool + let pageHorizontalInset: CGFloat + + var mediaIndexCounter: Int = 0 + var detailsIndexCounter: Int = 0 + + let expandedDetails: [Int: Bool] +} + +// MARK: - Driver + +private func layoutBlockSequence( + _ blocks: [InstantPageBlock], + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + context: inout LayoutContext +) -> InstantPageV2Layout { + var items: [InstantPageV2LaidOutItem] = [] + var detailsIndices: [Int] = [] + var contentHeight: CGFloat = 0.0 + var previousBlock: InstantPageBlock? + + for (i, block) in blocks.enumerated() { + let spacing = spacingBetweenBlocks(upper: previousBlock, lower: block, fitToWidth: context.fitToWidth, kind: kind) + let localItems = layoutBlock( + block, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + kind: kind, + isCover: false, + previousItems: items, + isLast: i == blocks.count - 1, + context: &context + ) + + // Translate local items by (0, contentHeight + spacing) and append. + let dy = contentHeight + spacing + var blockMaxY: CGFloat = 0.0 + for var item in localItems { + item = item.offsetBy(CGPoint(x: 0.0, y: dy)) + if case let .details(d) = item { + detailsIndices.append(d.index) + } + let f = item.frame + if f.maxY > blockMaxY { + blockMaxY = f.maxY + } + items.append(item) + } + + if blockMaxY > contentHeight { + contentHeight = blockMaxY + previousBlock = block + } + } + + let closingSpacing = spacingBetweenBlocks(upper: previousBlock, lower: nil, fitToWidth: context.fitToWidth, kind: kind) + contentHeight += closingSpacing + + var contentSize = CGSize(width: boundingWidth, height: contentHeight) + if context.fitToWidth { + // Match V1 InstantPageLayout.swift:1114 — include `+ horizontalInset` so the contentSize + // reserves a right margin equal to the left inset. Without this, the longest text item's + // right edge equals contentSize.width, and the bubble's containerNode (sized to + // boundingSize.width - 2) clips the last 2pt of text. + var maxX: CGFloat = 0.0 + for item in items { + maxX = max(maxX, ceil(item.frame.maxX) + horizontalInset) + } + contentSize.width = min(maxX, boundingWidth) + } + + return InstantPageV2Layout(contentSize: contentSize, items: items, detailsIndices: detailsIndices, media: context.media, webpage: context.webpage) +} + +// MARK: - Markdown block context stamping helpers + +private func stampMarkdownContext(_ items: [InstantPageV2LaidOutItem], kind: InstantPageMarkdownBlockContext.Kind) { + for item in items { + switch item { + case let .text(textItem): + if textItem.textItem.markdownContext == nil { + textItem.textItem.markdownContext = InstantPageMarkdownBlockContext(kind: kind) + } + case let .codeBlock(block): + if block.textItem.markdownContext == nil { + block.textItem.markdownContext = InstantPageMarkdownBlockContext(kind: kind) + } + default: + break + } + } +} + +/// Marks every text item produced by a blockquote's children as quoted (depth + 1), +/// preserving each child's own kind (heading/list/paragraph/…). +private func bumpQuoteDepth(_ items: [InstantPageV2LaidOutItem]) { + for item in items { + let target: InstantPageTextItem? + switch item { + case let .text(textItem): target = textItem.textItem + case let .codeBlock(block): target = block.textItem + default: target = nil + } + guard let target else { continue } + if var ctx = target.markdownContext { + ctx.quoteDepth += 1 + target.markdownContext = ctx + } else { + target.markdownContext = InstantPageMarkdownBlockContext(kind: .paragraph, quoteDepth: 1) + } + } +} + +private func layoutBlock( + _ block: InstantPageBlock, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + isCover: Bool, + previousItems: [InstantPageV2LaidOutItem], + isLast: Bool, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let _ = isLast // reserved for Tasks 7–9 + switch block { + case let .cover(inner): + return layoutBlock(inner, boundingWidth: boundingWidth, horizontalInset: horizontalInset, kind: kind, + isCover: true, previousItems: previousItems, isLast: isLast, context: &context) + case let .title(text): + let titleItems = layoutSimpleText(text, category: .header, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + stampMarkdownContext(titleItems, kind: .title) + return titleItems + case let .subtitle(text): + return layoutSimpleText(text, category: .subheader, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .kicker(text): + return layoutSimpleText(text, category: .kicker, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .header(text): + return layoutSimpleText(text, category: .header, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .subheader(text): + return layoutSimpleText(text, category: .subheader, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .heading(text, level): + return layoutHeading(text, level: level, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .footer(text): + return layoutSimpleText(text, category: .caption, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case let .paragraph(text): + return layoutParagraph(text, boundingWidth: boundingWidth, horizontalInset: horizontalInset, kind: kind, + previousItems: previousItems, context: &context) + case let .authorDate(author, date): + return layoutAuthorDate(author: author, date: date, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, previousItems: previousItems, context: &context) + case .divider: + return layoutDivider(boundingWidth: boundingWidth, context: context) + case let .anchor(name): + return [.anchor(InstantPageV2AnchorItem(frame: CGRect(x: 0.0, y: 0.0, width: 0.0, height: 0.0), name: name))] + + case let .list(items, ordered): + return layoutList(items, ordered: ordered, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, kind: kind, context: &context) + + case let .preformatted(text, language): + return layoutCodeBlock(text, language: language, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + + case let .blockQuote(blocks, caption): + return layoutBlockQuote(blocks: blocks, caption: caption, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, kind: kind, + isLast: isLast, context: &context) + case let .pullQuote(text, caption): + return layoutQuoteText(text: text, caption: caption, isPull: true, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, + context: &context) + + case let .image(id, caption, url, webpageId): + if case let .image(image) = context.media[id], let largest = largestImageRepresentation(image.representations) { + let naturalSize = CGSize(width: CGFloat(largest.dimensions.width), height: CGFloat(largest.dimensions.height)) + let mediaUrl: InstantPageUrlItem? = url.flatMap { InstantPageUrlItem(url: $0, webpageId: webpageId) } + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let instantPageMedia = InstantPageMedia( + index: mediaIndex, + media: .image(image), + url: mediaUrl, + caption: caption.text, + credit: caption.credit + ) + let webpage = context.webpage + return layoutTypedMediaWithCaption( + produceItem: { frame, cornerRadius in + .mediaImage(InstantPageV2MediaImageItem( + frame: frame, + cornerRadius: cornerRadius, + media: instantPageMedia, + webPage: webpage, + attributes: [] + )) + }, + naturalSize: naturalSize, + caption: caption, + isCover: isCover, + cornerRadius: 8.0, + flush: true, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + } else { + // Fallback when the image is not present in the page's media dict — preserve V1 + // behavior, which returns an empty layout for unknown image IDs (V1 + // InstantPageLayout.swift:623). The existing layoutMediaWithCaption would emit a + // grey rectangle; matching V1 instead. + return [] + } + + case let .video(id, caption, _, _): + if case let .file(file) = context.media[id], let dimensions = file.dimensions { + let naturalSize = CGSize(width: CGFloat(dimensions.width), height: CGFloat(dimensions.height)) + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let instantPageMedia = InstantPageMedia( + index: mediaIndex, + media: .file(file), + url: nil, + caption: caption.text, + credit: caption.credit + ) + let webpage = context.webpage + return layoutTypedMediaWithCaption( + produceItem: { frame, cornerRadius in + .mediaVideo(InstantPageV2MediaVideoItem( + frame: frame, + cornerRadius: cornerRadius, + media: instantPageMedia, + webPage: webpage, + attributes: [] + )) + }, + naturalSize: naturalSize, + caption: caption, + isCover: isCover, + cornerRadius: 8.0, + flush: true, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + } else { + return [] + } + + case let .audio(audioId, caption): + guard case let .file(file) = context.media[audioId] else { + return [] + } + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let instantPageMedia = InstantPageMedia( + index: mediaIndex, + media: .file(file), + url: nil, + caption: nil, + credit: nil + ) + let audioFrame = CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: 44.0) + var result: [InstantPageV2LaidOutItem] = [.mediaAudio(InstantPageV2MediaAudioItem( + frame: audioFrame, + media: instantPageMedia, + webPage: context.webpage + ))] + let (captionItems, _) = layoutCaptionAndCredit( + caption, + offset: audioFrame.height, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + result.append(contentsOf: captionItems) + return result + + case let .webEmbed(url, _, dimensions, caption, _, _, coverId): + // V1 (InstantPageLayout.swift:848): if the embed has a URL and a resolvable cover image, + // V1 synthesizes a fake webpage holding the cover image and renders it as an image cell + // with a play overlay. Otherwise V1 produces a WKWebView embed (out of scope for V2). + if let url = url, + let coverId = coverId, + case let .image(coverImage) = context.media[coverId] { + let embedHeight: CGFloat = CGFloat(dimensions?.height ?? 240) + let naturalSize = CGSize(width: boundingWidth, height: embedHeight) + let size = PixelDimensions(width: Int32(naturalSize.width), height: Int32(naturalSize.height)) + let loadedContent = TelegramMediaWebpageLoadedContent( + url: url, + displayUrl: url, + hash: 0, + type: "video", + websiteName: nil, + title: nil, + text: nil, + embedUrl: url, + embedType: "video", + embedSize: size, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: coverImage, + file: nil, + story: nil, + attributes: [], + instantPage: nil + ) + let coverWebpage = TelegramMediaWebpage( + webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalWebpage, id: -1), + content: .Loaded(loadedContent) + ) + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let instantPageMedia = InstantPageMedia( + index: mediaIndex, + media: .webpage(coverWebpage), + url: nil, + caption: caption.text, + credit: caption.credit + ) + let webpage = context.webpage + return layoutTypedMediaWithCaption( + produceItem: { frame, cornerRadius in + .mediaCoverImage(InstantPageV2MediaCoverImageItem( + frame: frame, + cornerRadius: cornerRadius, + media: instantPageMedia, + webPage: webpage, + attributes: [] + )) + }, + naturalSize: naturalSize, + caption: caption, + isCover: false, + cornerRadius: 0.0, + flush: true, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + } else { + // No cover image → keep the existing grey-placeholder path (plain web embed). + let h: CGFloat = CGFloat(dimensions?.height ?? 240) + return layoutMediaWithCaption(kind: .webEmbed, + naturalSize: CGSize(width: boundingWidth, height: h), caption: caption, + isCover: false, cornerRadius: 0.0, flush: true, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + } + + case let .postEmbed(_, _, _, _, _, _, caption): + return layoutMediaWithCaption(kind: .postEmbed, + naturalSize: CGSize(width: boundingWidth, height: 140.0), caption: caption, + isCover: false, cornerRadius: 8.0, flush: true, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + + case let .collage(items, caption): + return layoutCollage(items: items, caption: caption, isCover: isCover, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, context: &context) + + case let .slideshow(items, caption): + return layoutSlideshow(items: items, caption: caption, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, context: &context) + + case let .channelBanner(channel): + if channel == nil { return [] } + return layoutMediaWithCaption(kind: .channelBanner, + naturalSize: CGSize(width: boundingWidth, height: 60.0), + caption: InstantPageCaption(text: .empty, credit: .empty), + isCover: false, cornerRadius: 0.0, flush: true, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + + case let .map(latitude, longitude, zoom, dimensions, caption): + // AI/server-sent `.map` blocks can arrive with zero `dimensions` (the wire `w`/`h` are + // required, but the sender may put 0). A zero `naturalSize.height` collapses the media + // frame to height 0 (`instantPageV2MediaFrame`'s else branch) — the map takes no space, + // the caption slides up into it, and the pin floats over the caption — and a zero-sized + // `MapSnapshotMediaResource` makes `MKMapSnapshotter` render nothing. Substitute a sensible + // default (a 2:1 map strip) for BOTH the layout size and the snapshot resource. Real web + // articles (the V1 renderer) always carry real dimensions, so only the rich-message path + // hits this; the fallback is scoped here rather than in V1 or the wire/parse layer. + let effectiveDimensions: PixelDimensions + if dimensions.width > 0 && dimensions.height > 0 { + effectiveDimensions = dimensions + } else { + effectiveDimensions = PixelDimensions(width: 600, height: 300) + } + let naturalSize = CGSize(width: CGFloat(effectiveDimensions.width), height: CGFloat(effectiveDimensions.height)) + let map = TelegramMediaMap( + latitude: latitude, + longitude: longitude, + heading: nil, + accuracyRadius: nil, + venue: nil, + liveBroadcastingTimeout: nil, + liveProximityNotificationRadius: nil + ) + let mapAttributes: [InstantPageImageAttribute] = [InstantPageMapAttribute(zoom: zoom, dimensions: effectiveDimensions.cgSize)] + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let instantPageMedia = InstantPageMedia( + index: mediaIndex, + media: .geo(map), + url: nil, + caption: caption.text, + credit: caption.credit + ) + let webpage = context.webpage + return layoutTypedMediaWithCaption( + produceItem: { frame, cornerRadius in + .mediaMap(InstantPageV2MediaMapItem( + frame: frame, + cornerRadius: cornerRadius, + media: instantPageMedia, + webPage: webpage, + attributes: mapAttributes + )) + }, + naturalSize: naturalSize, + caption: caption, + isCover: false, + cornerRadius: 8.0, + flush: true, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + + case let .relatedArticles(_, articles): + let h = min(CGFloat(articles.count) * 80.0, 320.0) + return layoutMediaWithCaption(kind: .relatedArticles, + naturalSize: CGSize(width: boundingWidth, height: max(h, 80.0)), + caption: InstantPageCaption(text: .empty, credit: .empty), + isCover: false, cornerRadius: 0.0, flush: true, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + + case let .formula(latex): + return layoutFormulaBlock(latex: latex, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, kind: kind, + context: &context) + + case let .details(title, blocks, expanded): + return layoutDetails(title: title, blocks: blocks, defaultExpanded: expanded, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, + context: &context) + + case let .table(title, rows, bordered, striped): + return layoutTable(title: title, rows: rows, bordered: bordered, striped: striped, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, + context: &context) + + case let .thinking(text): + return layoutThinking(text, boundingWidth: boundingWidth, + horizontalInset: horizontalInset, context: &context) + case .unsupported: + return [] + } +} + +// MARK: - Formula block layout + +/// Lays out a top-level `InstantPageBlock.formula(latex:)`. The latex is rendered synchronously +/// through `instantPageMathAttachment(...)` (SwiftMath → `MTMathRenderer`) using the current +/// paragraph theme's color and font size; the resulting pre-rendered `UIImage` is wrapped in +/// an `InstantPageV2FormulaItem`. Wide formulas set `isScrollable = true`; on render failure +/// the raw latex source is laid out as a regular paragraph (matches V1 fallback). +private func layoutFormulaBlock( + latex: String, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // Style stack matches V1's per-block formula (paragraph category, not header). + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .paragraph, link: false) + let attributes = styleStack.textAttributes() + let textColor = (attributes[.foregroundColor] as? UIColor) + ?? context.theme.textCategories.paragraph.color + let fontSize = (attributes[.font] as? UIFont)?.pointSize + ?? context.theme.textCategories.paragraph.font.size + + guard let attachment = instantPageMathAttachment(latex: latex, + fontSize: fontSize, + textColor: textColor, + mode: .block) else { + // Render failure: fall back to the raw latex source as a regular paragraph. + return layoutParagraph(.plain(latex), + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + kind: kind, + previousItems: [], + context: &context) + } + + let availableWidth = boundingWidth - horizontalInset * 2.0 + let renderedSize = attachment.rendered.size + + if renderedSize.width > availableWidth { + // Wide formula: scroll view fills the bubble's available width so the image can pan inside. + let frame = CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: renderedSize.height) + let item = InstantPageV2FormulaItem( + frame: frame, + attachment: attachment, + isScrollable: true, + imageFrame: CGRect(x: horizontalInset, y: 0.0, + width: renderedSize.width, height: renderedSize.height), + scrollContentSize: CGSize(width: renderedSize.width + horizontalInset * 2.0, + height: renderedSize.height) + ) + return [.formula(item)] + } else { + // Narrow formula: report the image's natural extent (left-inset + width) so the + // bubble's `fitToWidth` contentSize shrinks instead of stretching to `boundingWidth`. + // When the formula is the widest item, the bubble centers itself in the chat row. + let frame = CGRect(x: horizontalInset, y: 0.0, + width: renderedSize.width, height: renderedSize.height) + let item = InstantPageV2FormulaItem( + frame: frame, + attachment: attachment, + isScrollable: false, + imageFrame: CGRect(origin: .zero, size: renderedSize), + scrollContentSize: renderedSize + ) + return [.formula(item)] + } +} + +// MARK: - Details layout + +private func layoutDetails( + title: RichText, + blocks: [InstantPageBlock], + defaultExpanded: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let index = context.detailsIndexCounter + context.detailsIndexCounter += 1 + + // Title text item at top. + // V1 (InstantPageDetailsItem.swift:98–101): boundingWidth - detailsInset*2 - titleInset, titleHeight = max(44, titleSize.height + 26). + let titleStyleStack = InstantPageTextStyleStack() + setupStyleStack(titleStyleStack, theme: context.theme, category: .paragraph, link: false) + let (titleTextItem, _, _) = layoutTextItem( + attributedStringForRichText(title, styleStack: titleStyleStack, formatDate: context.formatDate), + boundingWidth: boundingWidth - horizontalInset * 2.0 - 32.0, // reserve right edge for chevron + offset: CGPoint(x: 0.0, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + guard let titleTextItem = titleTextItem else { return [] } + + let titleHeight = max(44.0, titleTextItem.frame.height + 26.0) + titleTextItem.frame.origin.x = context.rtl + ? (boundingWidth - horizontalInset - 23.0 - titleTextItem.frame.width) + : (horizontalInset + 23.0) + titleTextItem.frame.origin.y = floorToScreenPixels((titleHeight - titleTextItem.frame.height) * 0.5) + + let isExpanded = context.expandedDetails[index] ?? defaultExpanded + + // V1 uses max(44.0, titleSize.height + 26.0); matched here. + let titleFrame = CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: titleHeight) + + var innerLayout: InstantPageV2Layout? + var totalHeight = titleHeight + if isExpanded { + let layout = layoutBlockSequence( + blocks, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + kind: .detail, + context: &context + ) + innerLayout = layout + totalHeight += layout.contentSize.height + } + + let item = InstantPageV2DetailsItem( + frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: totalHeight), + index: index, + sideInset: horizontalInset, + titleTextItem: titleTextItem, + titleFrame: titleFrame, + separatorColor: context.theme.separatorColor, + isExpanded: isExpanded, + innerLayout: innerLayout, + defaultExpanded: defaultExpanded, + rtl: context.rtl + ) + return [.details(item)] +} + +// MARK: - Table layout + +private struct V2TableRow { + var minColumnWidths: [Int: CGFloat] + var maxColumnWidths: [Int: CGFloat] +} + +let v2TableCellInsets: UIEdgeInsets = { + return UIEdgeInsets(top: 15.0, left: 13.0, bottom: 15.0, right: 13.0) +}() +let v2TableBorderWidth: CGFloat = { + return UIScreenPixel * 2.0 +}() +let v2TableCornerRadius: CGFloat = 10.0 + +private func layoutTable( + title: RichText, + rows: [InstantPageTableRow], + bordered: Bool, + striped: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + if rows.isEmpty { + return [] + } + + // Style stack shared across all cell text measurements. + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .table, link: false) + + let borderWidth = bordered ? v2TableBorderWidth : 0.0 + // Size columns against the inset content width (mirrors V1's `boundingWidth - horizontalInset*2`), + // so a fitting table aligns with body text on both sides. The item frame stays full-width (flush) + // and the renderer bakes the inset back in as a left margin on the scroll content. + let contentBoundingWidth = boundingWidth - horizontalInset * 2.0 + let totalCellPadding = v2TableCellInsets.left + v2TableCellInsets.right + let cellWidthLimit = contentBoundingWidth - totalCellPadding + + var tableRows: [V2TableRow] = [] + var columnCount: Int = 0 + + var columnSpans: [Range: (CGFloat, CGFloat)] = [:] + var rowSpans: [Int: [(Int, Int)]] = [:] + + // Pass 1: measure min/max intrinsic width per cell. + var r: Int = 0 + for row in rows { + var minColumnWidths: [Int: CGFloat] = [:] + var maxColumnWidths: [Int: CGFloat] = [:] + var i: Int = 0 + + for cell in row.cells { + // Advance i past any rowspan-inherited columns (matches V1 lines 311–319). + if let rowSpan = rowSpans[r] { + for columnAndSpan in rowSpan { + if columnAndSpan.0 == i { + i += columnAndSpan.1 + } else { + break + } + } + } + + var minCellWidth: CGFloat = 1.0 + var maxCellWidth: CGFloat = 1.0 + if let text = cell.text { + // Mirror V1 (`InstantPageTableItem.layoutTableItem`): `attributedStringForRichText`'s + // boundingWidth sizes inline attachments to `cellWidthLimit - totalCellPadding`, while + // the line-break budget passed to `layoutTextItem` is the full `cellWidthLimit`. (V1 + // subtracts `totalCellPadding` only on the attribute-string arg, not the layout arg.) + let attrStr = attributedStringForRichText(text, styleStack: styleStack, boundingWidth: cellWidthLimit - totalCellPadding, formatDate: context.formatDate) + if let shortestItem = layoutTextItem( + attrStr, + boundingWidth: cellWidthLimit, + offset: CGPoint(), + minimizeWidth: true, + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ).0 { + minCellWidth = shortestItem.effectiveWidth() + totalCellPadding + } + if let longestItem = layoutTextItem( + attrStr, + boundingWidth: cellWidthLimit, + offset: CGPoint(), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ).0 { + maxCellWidth = max(minCellWidth, longestItem.effectiveWidth() + totalCellPadding) + } + } + + if cell.colspan > 1 { + minColumnWidths[i] = 1.0 + maxColumnWidths[i] = 1.0 + let spanRange = i ..< i + Int(cell.colspan) + if let (minSW, maxSW) = columnSpans[spanRange] { + columnSpans[spanRange] = (max(minSW, minCellWidth), max(maxSW, maxCellWidth)) + } else { + columnSpans[spanRange] = (minCellWidth, maxCellWidth) + } + } else { + minColumnWidths[i] = minCellWidth + maxColumnWidths[i] = maxCellWidth + } + + let colspan = cell.colspan > 1 ? Int(clamping: cell.colspan) : 1 + if cell.rowspan > 1 { + for j in r ..< r + Int(cell.rowspan) { + if rowSpans[j] == nil { + rowSpans[j] = [(i, colspan)] + } else { + rowSpans[j]!.append((i, colspan)) + } + } + } + + i += colspan + } + tableRows.append(V2TableRow(minColumnWidths: minColumnWidths, maxColumnWidths: maxColumnWidths)) + columnCount = max(columnCount, i) + r += 1 + } + + // Aggregate column min/max across all rows. + let maxContentWidth = contentBoundingWidth - borderWidth + var availableWidth = maxContentWidth + var minColumnWidths: [Int: CGFloat] = [:] + var maxColumnWidths: [Int: CGFloat] = [:] + var maxTotalWidth: CGFloat = 0.0 + for i in 0 ..< columnCount { + var minWidth: CGFloat = 1.0 + var maxWidth: CGFloat = 1.0 + for row in tableRows { + if let w = row.minColumnWidths[i] { minWidth = max(minWidth, w) } + if let w = row.maxColumnWidths[i] { maxWidth = max(maxWidth, w) } + } + minColumnWidths[i] = minWidth + maxColumnWidths[i] = maxWidth + availableWidth -= minWidth + maxTotalWidth += maxWidth + } + + // Apply colspan constraints. + for (range, span) in columnSpans { + let (minSpanWidth, maxSpanWidth) = span + var minWidth: CGFloat = 0.0 + var maxWidth: CGFloat = 0.0 + for i in range { + if let w = minColumnWidths[i] { minWidth += w } + if let w = maxColumnWidths[i] { maxWidth += w } + } + if minWidth < minSpanWidth { + let delta = minSpanWidth - minWidth + for i in range { + if let w = minColumnWidths[i] { + let growth = floor(delta / CGFloat(range.count)) + minColumnWidths[i] = w + growth + availableWidth -= growth + } + } + } + if maxWidth < maxSpanWidth { + let delta = maxSpanWidth - maxWidth + for i in range { + if let w = maxColumnWidths[i] { + let growth = round(delta / CGFloat(range.count)) + maxColumnWidths[i] = w + growth + maxTotalWidth += growth + } + } + } + } + + // Width allocation: distribute available width across columns. + var totalWidth = maxTotalWidth + var finalColumnWidths: [Int: CGFloat] + let widthToDistribute: CGFloat + if availableWidth > 0 { + widthToDistribute = availableWidth + finalColumnWidths = minColumnWidths + } else { + widthToDistribute = maxContentWidth - maxTotalWidth + finalColumnWidths = maxColumnWidths + } + + if widthToDistribute > 0.0 { + var distributedWidth = widthToDistribute + for i in 0 ..< finalColumnWidths.count { + var width = finalColumnWidths[i]! + let maxWidth = maxColumnWidths[i]! + let growth = min(round(widthToDistribute * maxWidth / maxTotalWidth), distributedWidth) + width += growth + distributedWidth -= growth + finalColumnWidths[i] = width + } + totalWidth = contentBoundingWidth + } else { + totalWidth += borderWidth + } + + // Pass 2 & 3: produce per-cell frames + sub-layouts. + // Private struct to hold an in-progress cell before row height is known. + struct PendingCell { + let rowIndex: Int + let column: Int + let colspan: Int + let rowspan: Int + let cell: InstantPageTableCell + var frame: CGRect // height is tentative until row height resolved + let isHeader: Bool + let isFilled: Bool // background fill (header or stripe) + let subLayout: InstantPageV2Layout? + let subLayoutContentHeight: CGFloat // height of cell content (before padding) + } + + var finalizedCells: [InstantPageV2TableCell] = [] + var origin = CGPoint(x: borderWidth / 2.0, y: borderWidth / 2.0) + var totalHeight: CGFloat = 0.0 + var rowHeights: [Int: CGFloat] = [:] + + var awaitingSpanCells: [Int: [(Int, PendingCell)]] = [:] + + for i in 0 ..< rows.count { + let row = rows[i] + var maxRowHeight: CGFloat = 0.0 + var isEmptyRow = true + origin.x = borderWidth / 2.0 + + var k: Int = 0 + var rowCells: [PendingCell] = [] + + for cell in row.cells { + // Skip columns occupied by row spans. + if let cells = awaitingSpanCells[i] { + for colAndCell in cells { + if colAndCell.1.column == k { + for j in 0 ..< colAndCell.1.colspan { + if let width = finalColumnWidths[k + j] { + origin.x += width + } + } + k += colAndCell.1.colspan + } else { + break + } + } + } + + let colspan: Int = cell.colspan > 1 ? Int(clamping: cell.colspan) : 1 + let rowspan: Int = cell.rowspan > 1 ? Int(clamping: cell.rowspan) : 1 + + var cellWidth: CGFloat = 0.0 + for j in 0 ..< colspan { + if let width = finalColumnWidths[k + j] { + cellWidth += width + } + } + + // Build cell sub-layout via recursive layoutBlockSequence. + var subLayout: InstantPageV2Layout? + var subLayoutHeight: CGFloat = 0.0 + if let cellText = cell.text { + let cellContentWidth = cellWidth - totalCellPadding + if cellContentWidth > 0.0 { + let cellLayout = layoutBlockSequence( + [.paragraph(cellText)], + boundingWidth: cellContentWidth, + horizontalInset: 0.0, + kind: .cell, + context: &context + ) + stampMarkdownContext(cellLayout.items, kind: .tableCell(row: i, column: k, isHeader: cell.header)) + subLayout = cellLayout + subLayoutHeight = cellLayout.contentSize.height + isEmptyRow = false + } + } + + var cellHeight: CGFloat? + if subLayout != nil { + cellHeight = ceil(subLayoutHeight) + v2TableCellInsets.top + v2TableCellInsets.bottom + } + + var isFilled = cell.header + if !isFilled && striped { + isFilled = i % 2 == 0 + } + + let pendingCell = PendingCell( + rowIndex: i, + column: k, + colspan: colspan, + rowspan: rowspan, + cell: cell, + frame: CGRect(x: origin.x, y: origin.y, width: cellWidth, height: cellHeight ?? 20.0), + isHeader: cell.header, + isFilled: isFilled, + subLayout: subLayout, + subLayoutContentHeight: subLayoutHeight + ) + + if rowspan == 1 { + rowCells.append(pendingCell) + if let ch = cellHeight { + maxRowHeight = max(maxRowHeight, ch) + } + } else { + for j in i ..< i + rowspan { + if awaitingSpanCells[j] == nil { + awaitingSpanCells[j] = [(k, pendingCell)] + } else { + awaitingSpanCells[j]!.append((k, pendingCell)) + } + } + } + + k += colspan + origin.x += cellWidth + } + + // Capture theme color value before the closure to avoid capturing `inout context`. + let tableHeaderColor = context.theme.tableHeaderColor + + // Helper: finalize a pending cell with known row height → produce InstantPageV2TableCell. + let finalizeCell: (PendingCell, CGFloat) -> InstantPageV2TableCell = { pending, height in + let finalFrame = CGRect(x: pending.frame.minX, y: pending.frame.minY, + width: pending.frame.width, height: height) + + // Compute sub-layout frame within the cell (horizontal inset + vertical alignment). + var subLayout = pending.subLayout + if var sl = subLayout { + let textHeight = pending.subLayoutContentHeight + let vertOffset: CGFloat + switch pending.cell.verticalAlignment { + case .top: + vertOffset = v2TableCellInsets.top + case .middle: + vertOffset = max(v2TableCellInsets.top, (height - textHeight) / 2.0) + case .bottom: + vertOffset = max(v2TableCellInsets.top, height - textHeight - v2TableCellInsets.bottom) + } + let horizOffset: CGFloat + switch pending.cell.alignment { + case .left: + horizOffset = v2TableCellInsets.left + case .center: + horizOffset = (pending.frame.width - sl.contentSize.width) / 2.0 + case .right: + horizOffset = pending.frame.width - sl.contentSize.width - v2TableCellInsets.right + } + // Translate all items in the sub-layout by the inset. + let delta = CGPoint(x: horizOffset, y: vertOffset) + let translatedItems = sl.items.map { $0.offsetBy(delta) } + sl = InstantPageV2Layout(contentSize: sl.contentSize, items: translatedItems, detailsIndices: sl.detailsIndices, media: sl.media, webpage: sl.webpage) + subLayout = sl + } + + let bgColor: UIColor? = pending.isFilled ? tableHeaderColor : nil + let hAlign: NSTextAlignment + switch pending.cell.alignment { + case .left: hAlign = .left + case .center: hAlign = .center + case .right: hAlign = .right + } + let vAlign: InstantPageV2TableVerticalAlignment + switch pending.cell.verticalAlignment { + case .top: vAlign = .top + case .middle: vAlign = .middle + case .bottom: vAlign = .bottom + } + + return InstantPageV2TableCell( + frame: finalFrame, + isHeader: pending.isHeader, + horizontalAlignment: hAlign, + verticalAlignment: vAlign, + backgroundColor: bgColor, + subLayout: subLayout + ) + } + + if !isEmptyRow { + rowHeights[i] = maxRowHeight + } else { + rowHeights[i] = 0.0 + maxRowHeight = 0.0 + } + + // Resolve any row-spanning cells whose bottom row is now known. + var completedSpans = [Int: Set]() + if let cells = awaitingSpanCells[i] { + isEmptyRow = false + for colAndCell in cells { + let pending = colAndCell.1 + let utmostRow = pending.rowIndex + pending.rowspan - 1 + if rowHeights[utmostRow] == nil { + continue + } + + var cellHeight: CGFloat = 0.0 + for k in pending.rowIndex ..< utmostRow + 1 { + if let h = rowHeights[k] { cellHeight += h } + if completedSpans[k] == nil { + completedSpans[k] = Set([colAndCell.0]) + } else { + completedSpans[k]!.insert(colAndCell.0) + } + } + + if pending.frame.height > cellHeight { + let delta = pending.frame.height - cellHeight + cellHeight = pending.frame.height + maxRowHeight += delta + rowHeights[i] = maxRowHeight + } + + finalizedCells.append(finalizeCell(pending, cellHeight)) + } + } + + for pending in rowCells { + finalizedCells.append(finalizeCell(pending, maxRowHeight)) + } + + // Remove completed span cells from awaitingSpanCells. + if !completedSpans.isEmpty { + awaitingSpanCells = awaitingSpanCells.reduce([Int: [(Int, PendingCell)]]()) { current, rowAndValue in + var result = current + let cells = rowAndValue.value.filter { column, _ in + if let completedSet = completedSpans[rowAndValue.key] { + return !completedSet.contains(column) + } + return true + } + if !cells.isEmpty { result[rowAndValue.key] = cells } + return result + } + } + + if !isEmptyRow { + totalHeight += maxRowHeight + origin.y += maxRowHeight + } + } + totalHeight += borderWidth + + // RTL: flip all cell frames horizontally within totalWidth. + if context.rtl { + finalizedCells = finalizedCells.map { cell in + let flippedX = totalWidth - cell.frame.minX - cell.frame.width + let flippedFrame = CGRect(x: flippedX, y: cell.frame.minY, + width: cell.frame.width, height: cell.frame.height) + return InstantPageV2TableCell( + frame: flippedFrame, + isHeader: cell.isHeader, + horizontalAlignment: cell.horizontalAlignment, + verticalAlignment: cell.verticalAlignment, + backgroundColor: cell.backgroundColor, + subLayout: cell.subLayout + ) + } + } + + // Build border lines (table-local coords). + var horizontalLines: [CGRect] = [] + var verticalLines: [CGRect] = [] + if bordered { + // Interior lines: for each cell, emit a top line if not in the first row, + // and a left line if not in the first column. + for cell in finalizedCells { + let isFirstRow = cell.frame.minY <= borderWidth / 2.0 + 0.5 + let isFirstCol = cell.frame.minX <= borderWidth / 2.0 + 0.5 + if !isFirstRow { + horizontalLines.append(CGRect(x: cell.frame.minX, y: cell.frame.minY, + width: cell.frame.width, height: borderWidth)) + } + if !isFirstCol { + verticalLines.append(CGRect(x: cell.frame.minX, y: cell.frame.minY, + width: borderWidth, height: cell.frame.height)) + } + } + } + + // Title sub-layout (above the grid). + var titleSubLayout: InstantPageV2Layout? + var titleFrame: CGRect? + if case .empty = title { + // no title + } else { + let titleLayout = layoutBlockSequence( + [.paragraph(title)], + boundingWidth: totalWidth - v2TableCellInsets.left * 2.0, + horizontalInset: 0.0, + kind: .cell, + context: &context + ) + titleSubLayout = titleLayout + let titleHeight = titleLayout.contentSize.height + v2TableCellInsets.top + v2TableCellInsets.bottom + titleFrame = CGRect(x: 0.0, y: 0.0, width: totalWidth, height: titleHeight) + } + + // The table item frame spans the full visible bubble interior (`boundingWidth`); the scroll + // viewport equals what is actually visible. contentSize.width is the intrinsic grid width + // (may exceed frame.width → horizontal scroll); the renderer adds the inset on both sides. + let tableFrame = CGRect(x: 0.0, y: 0.0, + width: boundingWidth, + height: totalHeight + (titleFrame?.height ?? 0.0)) + let contentSize = CGSize( + width: totalWidth, + height: totalHeight + (titleFrame?.height ?? 0.0) + ) + + let tableItem = InstantPageV2TableItem( + frame: tableFrame, + titleSubLayout: titleSubLayout, + titleFrame: titleFrame, + contentSize: contentSize, + contentInset: horizontalInset, + cells: finalizedCells, + horizontalLines: horizontalLines, + verticalLines: verticalLines, + bordered: bordered, + striped: striped, + borderColor: context.theme.tableBorderColor + ) + return [.table(tableItem)] +} + +// MARK: - Media placeholder layout + +/// Caption+credit sub-helper. Items are positioned in block-global coordinates (y measured from the +/// top of the media block); `offset` is the y-position of the bottom of the placeholder, so +/// caption items start at `offset + topPadding`. The caller uses the returned total height to +/// compute block size. Returns (items, totalHeight). +private func layoutCaptionAndCredit( + _ caption: InstantPageCaption, + offset: CGFloat, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> ([InstantPageV2LaidOutItem], CGFloat) { + var items: [InstantPageV2LaidOutItem] = [] + var y = offset + var totalHeight: CGFloat = 0.0 + var rtl = context.rtl + var captionIsEmpty = true + + if case .empty = caption.text { + // no caption text + } else { + captionIsEmpty = false + totalHeight += 14.0 + y += 14.0 + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .caption, link: false) + let attributedString = attributedStringForRichText(caption.text, styleStack: styleStack, formatDate: context.formatDate) + let (textItem, captionItems, captionSize) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + offset: CGPoint(x: horizontalInset, y: y), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + totalHeight += captionSize.height + y += captionSize.height + items.append(contentsOf: captionItems) + rtl = textItem?.containsRTL ?? rtl + } + + if case .empty = caption.credit { + // no credit text + } else { + if captionIsEmpty { + totalHeight += 14.0 + y += 14.0 + } else { + totalHeight += 10.0 + y += 10.0 + } + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .credit, link: false) + let attributedString = attributedStringForRichText(caption.credit, styleStack: styleStack, formatDate: context.formatDate) + let (_, creditItems, creditSize) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + alignment: rtl ? .right : .natural, + offset: CGPoint(x: horizontalInset, y: y), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + totalHeight += creditSize.height + items.append(contentsOf: creditItems) + } + + return (items, totalHeight) +} + +// How many points a full-width flush media item bleeds past the bubble interior on the +// trailing edge so the rounded `containerNode` clip (see ChatMessageRichDataBubbleContentNode) rounds +// the trailing corners with no 1px background sliver. Harmless: the +// `contentSize.width = min(maxX, boundingWidth)` clamp keeps it from widening the bubble. +private let instantPageV2MediaEdgeBleed: CGFloat = 4.0 + +// Computes the laid-out frame for a block-media item. +// +// `flush == true` (every current caller): the media is edge-to-edge (x = 0, full +// `boundingWidth`) with corner radius forced to 0, relying on the bubble's rounded clipping +// container to round media that meets the bubble's top/bottom edge. A media item that fills the +// full width is widened by `instantPageV2MediaEdgeBleed` on the trailing edge (see the constant). +// A media item narrower than the full width (a small image — NOT upscaled, the `min(_, 1.0)` +// scale cap is kept) stays at its natural size, flush-left at x = 0, with no bleed. +// (The `cornerRadius` argument is ignored when `flush == true` — flush media is always +// un-rounded; callers may still pass their legacy radius, it has no effect.) +// +// `flush == false`: DEAD as of the V2 audio port — audio was its last caller and now has its +// own `layoutAudio` arm (in `layoutBlock`), so this branch is currently unreachable (follow-up: +// drop the `flush` parameter and this branch). Legacy behavior was: inset by `horizontalInset` +// on each side with the caller-supplied corner radius. +// +// Returns the frame, the un-bled scaled content size (the caption is offset by +// `scaledSize.height`), and the effective corner radius to stamp on the item. +private func instantPageV2MediaFrame( + naturalSize: CGSize, + flush: Bool, + cornerRadius: CGFloat, + boundingWidth: CGFloat, + horizontalInset: CGFloat +) -> (frame: CGRect, scaledSize: CGSize, cornerRadius: CGFloat) { + let availableWidth = flush ? boundingWidth : (boundingWidth - horizontalInset * 2.0) + let scaledSize: CGSize + if naturalSize.width > 0.0 && naturalSize.height > 0.0 { + let scale = min(availableWidth / naturalSize.width, 1.0) + scaledSize = CGSize(width: floor(naturalSize.width * scale), height: floor(naturalSize.height * scale)) + } else { + scaledSize = CGSize(width: availableWidth, height: naturalSize.height) + } + + if flush { + // `floor(x) > x - 1` always, so a full-width item (scaledSize.width == floor(availableWidth)) + // always trips this; a genuinely smaller image does not. (availableWidth == boundingWidth + // in the flush branch, so the bleed below extends past the full bounding width.) + let fillsWidth = scaledSize.width >= availableWidth - 1.0 + let frameWidth = fillsWidth ? boundingWidth + instantPageV2MediaEdgeBleed : scaledSize.width + let frame = CGRect(x: 0.0, y: 0.0, width: frameWidth, height: scaledSize.height) + return (frame, scaledSize, 0.0) + } else { + let frame = CGRect(x: horizontalInset, y: 0.0, width: scaledSize.width, height: scaledSize.height) + return (frame, scaledSize, cornerRadius) + } +} + +/// Variant of `layoutMediaWithCaption` that emits a caller-produced typed media item +/// instead of a `.mediaPlaceholder`. The frame-fitting logic + caption/credit text item +/// layout is otherwise identical. +private func layoutTypedMediaWithCaption( + produceItem: (CGRect, CGFloat) -> InstantPageV2LaidOutItem, + naturalSize: CGSize, + caption: InstantPageCaption, + isCover: Bool, + cornerRadius: CGFloat, + flush: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let (mediaFrame, scaledSize, effectiveCornerRadius) = instantPageV2MediaFrame( + naturalSize: naturalSize, + flush: flush, + cornerRadius: cornerRadius, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset + ) + var result: [InstantPageV2LaidOutItem] = [produceItem(mediaFrame, effectiveCornerRadius)] + + let (captionItems, captionHeight) = layoutCaptionAndCredit( + caption, + offset: scaledSize.height, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + result.append(contentsOf: captionItems) + + // Same cover-padding logic as layoutMediaWithCaption: extend the last text item's frame by + // 14pt when isCover && captionHeight > 0.0. + if isCover && captionHeight > 0.0 { + if let lastIndex = result.lastIndex(where: { if case .text = $0 { return true } else { return false } }) { + if case var .text(lastText) = result[lastIndex] { + lastText.frame = CGRect( + origin: lastText.frame.origin, + size: CGSize(width: lastText.frame.width, height: lastText.frame.height + 14.0) + ) + result[lastIndex] = .text(lastText) + } + } + } + + return result +} + +/// Lays out an `InstantPageBlock.collage(items:caption:)`. Mirrors V1 +/// (InstantPageLayout.swift:692-727): compute a mosaic over the inner image/video sizes, then emit +/// one existing typed media item per cell at its mosaic frame, flush (cornerRadius 0) so the bubble's +/// rounded clip handles the outer corners and the 1pt mosaic spacing handles the interior gaps. A +/// single caption renders below the whole mosaic. Cells are top-level `.mediaImage`/`.mediaVideo` +/// items, so gallery / reveal / registry / hidden-media all work with no extra code. +private func layoutCollage( + items innerBlocks: [InstantPageBlock], + caption: InstantPageCaption, + isCover: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // 1. One size per inner block (zero for unresolved — V1 still reserves a mosaic slot). + var itemSizes: [CGSize] = [] + for block in innerBlocks { + switch block { + case let .image(id, _, _, _): + if case let .image(image) = context.media[id], let largest = largestImageRepresentation(image.representations) { + itemSizes.append(CGSize(width: CGFloat(largest.dimensions.width), height: CGFloat(largest.dimensions.height))) + } else { + itemSizes.append(CGSize()) + } + case let .video(id, _, _, _): + if case let .file(file) = context.media[id], let dimensions = file.dimensions { + itemSizes.append(CGSize(width: CGFloat(dimensions.width), height: CGFloat(dimensions.height))) + } else { + itemSizes.append(CGSize()) + } + default: + itemSizes.append(CGSize()) + } + } + + // 2. Mosaic geometry — the same engine V1 uses. + let (mosaic, mosaicSize) = chatMessageBubbleMosaicLayout(maxSize: CGSize(width: boundingWidth, height: boundingWidth), itemSizes: itemSizes) + + // 3. One typed media item per resolvable cell, at its mosaic frame. + var result: [InstantPageV2LaidOutItem] = [] + let webpage = context.webpage + for (i, block) in innerBlocks.enumerated() { + guard i < mosaic.count else { break } + let (cellFrame, position) = mosaic[i] + // Right-edge cells bleed 4pt so the bubble's rounded clip leaves no trailing sliver. + var frame = cellFrame + if position.contains(.right) { + frame.size.width += instantPageV2MediaEdgeBleed + } + switch block { + case let .image(id, blockCaption, url, webpageId): + guard case let .image(image) = context.media[id] else { continue } + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let mediaUrl: InstantPageUrlItem? = url.flatMap { InstantPageUrlItem(url: $0, webpageId: webpageId) } + let media = InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: blockCaption.text, credit: blockCaption.credit) + result.append(.mediaImage(InstantPageV2MediaImageItem(frame: frame, cornerRadius: 0.0, media: media, webPage: webpage, attributes: []))) + case let .video(id, blockCaption, _, _): + guard case let .file(file) = context.media[id] else { continue } + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let media = InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: blockCaption.text, credit: blockCaption.credit) + result.append(.mediaVideo(InstantPageV2MediaVideoItem(frame: frame, cornerRadius: 0.0, media: media, webPage: webpage, attributes: []))) + default: + continue + } + } + + // 4. Caption below the mosaic. + let (captionItems, captionHeight) = layoutCaptionAndCredit(caption, offset: mosaicSize.height, boundingWidth: boundingWidth, horizontalInset: horizontalInset, context: &context) + result.append(contentsOf: captionItems) + + // Cover-caption padding parity with layoutTypedMediaWithCaption. + if isCover && captionHeight > 0.0 { + if let lastIndex = result.lastIndex(where: { if case .text = $0 { return true } else { return false } }) { + if case var .text(lastText) = result[lastIndex] { + lastText.frame = CGRect(origin: lastText.frame.origin, size: CGSize(width: lastText.frame.width, height: lastText.frame.height + 14.0)) + result[lastIndex] = .text(lastText) + } + } + } + + return result +} + +/// Lays out an `InstantPageBlock.slideshow(items:caption:)`. Mirrors V1 +/// (InstantPageLayout.swift:809-843): collect the inner image medias, size the block to the tallest +/// image fitted into the bounding width (cap 1200), emit a single full-width slideshow carousel item, +/// caption below. Only `.image` inner blocks contribute (matches V1). +private func layoutSlideshow( + items innerBlocks: [InstantPageBlock], + caption: InstantPageCaption, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + var medias: [InstantPageMedia] = [] + var height: CGFloat = 0.0 + for block in innerBlocks { + switch block { + case let .image(id, blockCaption, url, webpageId): + if case let .image(image) = context.media[id], let imageSize = largestImageRepresentation(image.representations)?.dimensions { + let mediaIndex = context.mediaIndexCounter + context.mediaIndexCounter += 1 + let filledSize = imageSize.cgSize.fitted(CGSize(width: boundingWidth, height: 1200.0)) + height = max(height, filledSize.height) + let mediaUrl: InstantPageUrlItem? = url.flatMap { InstantPageUrlItem(url: $0, webpageId: webpageId) } + medias.append(InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: blockCaption.text, credit: blockCaption.credit)) + } + default: + break + } + } + + var result: [InstantPageV2LaidOutItem] = [] + result.append(.slideshow(InstantPageV2SlideshowItem( + frame: CGRect(x: 0.0, y: 0.0, width: boundingWidth, height: height), + medias: medias, + webPage: context.webpage + ))) + + let (captionItems, _) = layoutCaptionAndCredit(caption, offset: height, boundingWidth: boundingWidth, horizontalInset: horizontalInset, context: &context) + result.append(contentsOf: captionItems) + return result +} + +private func layoutMediaWithCaption( + kind: InstantPageV2MediaPlaceholderKind, + naturalSize: CGSize, + caption: InstantPageCaption, + isCover: Bool, + cornerRadius: CGFloat, + flush: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let (placeholderFrame, scaledSize, effectiveCornerRadius) = instantPageV2MediaFrame( + naturalSize: naturalSize, + flush: flush, + cornerRadius: cornerRadius, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset + ) + let placeholderItem = InstantPageV2MediaPlaceholderItem( + frame: placeholderFrame, + kind: kind, + cornerRadius: effectiveCornerRadius + ) + + var result: [InstantPageV2LaidOutItem] = [.mediaPlaceholder(placeholderItem)] + + let (captionItems, captionHeight) = layoutCaptionAndCredit( + caption, + offset: scaledSize.height, + boundingWidth: boundingWidth, + horizontalInset: horizontalInset, + context: &context + ) + result.append(contentsOf: captionItems) + + // isCover adds extra 14pt bottom padding — but only when caption/credit text was actually + // rendered (matches V1 lines 204-206: `contentSize.height > 0 && isCover`). For an + // empty-caption cover image no padding is added. + // Implemented by extending the last text item's frame rather than emitting an invisible shape + // view that would silently consume tap area. + if isCover && captionHeight > 0.0 { + if let lastIndex = result.lastIndex(where: { if case .text = $0 { return true } else { return false } }) { + if case var .text(lastText) = result[lastIndex] { + lastText.frame = CGRect( + origin: lastText.frame.origin, + size: CGSize(width: lastText.frame.width, height: lastText.frame.height + 14.0) + ) + result[lastIndex] = .text(lastText) + } + } + } + + return result +} + +// MARK: - Simple-block layout functions + +private func layoutSimpleText( + _ text: RichText, + category: InstantPageTextCategoryType, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: category, link: false) + let attributedString = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + let (_, items, _) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + alignment: context.rtl ? .right : .natural, + offset: CGPoint(x: horizontalInset, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + return items +} + +private func layoutHeading( + _ text: RichText, + level: Int32, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, attributes: context.theme.headingTextAttributes(level: level, link: false)) + let attributedString = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + let (_, items, _) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + alignment: context.rtl ? .right : .natural, + offset: CGPoint(x: horizontalInset, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + stampMarkdownContext(items, kind: .heading(level: max(1, min(6, Int(level))))) + return items +} + +private func layoutParagraph( + _ text: RichText, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + previousItems: [InstantPageV2LaidOutItem], + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let _ = previousItems + + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: kind == .cell ? .table : .paragraph, link: false) + let attributedString = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + + let (_, items, _) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + alignment: context.rtl ? .right : .natural, + offset: CGPoint(x: horizontalInset, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + return items +} + +private func layoutAuthorDate( + author: RichText, + date: Int32, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + previousItems: [InstantPageV2LaidOutItem], + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // Literal port of V1 InstantPageLayout.swift case .authorDate (lines 231–272). + // Reads context.strings, formats date via DateFormatter with locale from localeWithStrings, + // splices author and date into InstantPage_AuthorAndDateTitle format string. + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .caption, link: false) + + // Capture strings by value to avoid capturing inout context in an escaping closure. + let strings = context.strings + let stringForDate: (Int32) -> String = { d in + let formatter = DateFormatter() + formatter.locale = localeWithStrings(strings) + formatter.dateStyle = .long + formatter.timeStyle = .none + return formatter.string(from: Date(timeIntervalSince1970: Double(d))) + } + + var text: RichText? + if case .empty = author { + if date != 0 { + text = .plain(stringForDate(date)) + } + } else { + if date != 0 { + let dateText = RichText.plain(stringForDate(date)) + let formatString = context.strings.InstantPage_AuthorAndDateTitle("%1$@", "%2$@").string + let authorRange = formatString.range(of: "%1$@")! + let dateRange = formatString.range(of: "%2$@")! + if authorRange.lowerBound < dateRange.lowerBound { + let byPart = String(formatString[formatString.startIndex ..< authorRange.lowerBound]) + let middlePart = String(formatString[authorRange.upperBound ..< dateRange.lowerBound]) + let endPart = String(formatString[dateRange.upperBound...]) + text = .concat([.plain(byPart), author, .plain(middlePart), dateText, .plain(endPart)]) + } else { + let beforePart = String(formatString[formatString.startIndex ..< dateRange.lowerBound]) + let middlePart = String(formatString[dateRange.upperBound ..< authorRange.lowerBound]) + let endPart = String(formatString[authorRange.upperBound...]) + text = .concat([.plain(beforePart), dateText, .plain(middlePart), author, .plain(endPart)]) + } + } else { + text = author + } + } + + guard let resolvedText = text else { return [] } + + var previousItemHasRTL = false + if case let .text(prev) = previousItems.last, prev.textItem.containsRTL { + previousItemHasRTL = true + } + let alignment: NSTextAlignment = (context.rtl || previousItemHasRTL) ? .right : .natural + + let (_, items, _) = layoutTextItem( + attributedStringForRichText(resolvedText, styleStack: styleStack, formatDate: context.formatDate), + boundingWidth: boundingWidth - horizontalInset * 2.0, + alignment: alignment, + offset: CGPoint(x: horizontalInset, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + return items +} + +private func layoutDivider( + boundingWidth: CGFloat, + context: LayoutContext +) -> [InstantPageV2LaidOutItem] { + let lineWidth = floor(boundingWidth / 2.0) + let frame = CGRect( + x: floor((boundingWidth - lineWidth) / 2.0), + y: 0.0, + width: lineWidth, + height: UIScreenPixel + ) + return [.divider(InstantPageV2DividerItem(frame: frame, color: context.theme.separatorColor))] +} + +// MARK: - Code block layout (ported from V1 InstantPageLayout.swift lines 329–351) + +private func layoutCodeBlock( + _ text: RichText, + language: String?, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + let backgroundInset: CGFloat = 15.0 + let textXOffset: CGFloat = 11.0 + + let attributedString: NSAttributedString + if let language, !language.isEmpty { + // V1 line 333: call attributedStringForPreformattedText with language. + attributedString = attributedStringForPreformattedText( + text, + language: language, + theme: context.theme, + cachedMessageSyntaxHighlight: context.cachedMessageSyntaxHighlight + ) + } else { + // V1 lines 335–338: fall back to plain paragraph style when no language. + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .codeBlock, link: false) + attributedString = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + } + + // V1 line 341: text bounding width excludes horizontalInset×2 and backgroundInset×2. + let innerWidth = boundingWidth - horizontalInset * 2.0 - backgroundInset * 2.0 + // V1 line 342: offset is (17.0, backgroundInset); V2 lays the text in block-local coords. + let (textItem, _, textSize) = layoutTextItem( + attributedString, + boundingWidth: innerWidth, + offset: CGPoint(x: 0.0, y: 0.0), + fitToWidth: context.fitToWidth, + opaqueBackground: true, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + guard let textItem = textItem else { return [] } + textItem.markdownContext = InstantPageMarkdownBlockContext(kind: .code(language: language)) + + // Position text within the block's content area. + // V1 line 342: x=17.0, y=backgroundInset (block-local). + textItem.frame = CGRect( + x: textXOffset, + y: backgroundInset, + width: textItem.frame.width, + height: textItem.frame.height + ) + + // Top-level (and
) code blocks span the full boundingWidth flush (x=0), matching V1 + // (line 348). Inside a blockquote the child inset is raised above the page inset (by + // lineInset), so honor it here — otherwise the full-width background bleeds out under the + // quote bar instead of insetting to the quote's content gutter like the quote's text does. + let blockHeight = textSize.height + backgroundInset * 2.0 + let isNestedInQuote = horizontalInset > context.pageHorizontalInset + // Inset (quote-nested) code blocks get an 8pt rounded background; flush (top-level / details) + // ones stay square — the bubble's own rounded clip handles their edges. + let cornerRadius: CGFloat = isNestedInQuote ? 8.0 : 0.0 + let blockFrame = CGRect( + x: isNestedInQuote ? horizontalInset : 0.0, + y: 0.0, + width: isNestedInQuote ? (boundingWidth - horizontalInset * 2.0) : boundingWidth, + height: blockHeight + ) + + return [.codeBlock(InstantPageV2CodeBlockItem( + frame: blockFrame, + backgroundColor: context.theme.codeBlockBackgroundColor, + cornerRadius: cornerRadius, + textItem: textItem, + inset: UIEdgeInsets(top: backgroundInset, left: textXOffset, bottom: backgroundInset, right: backgroundInset) + ))] +} + +private func layoutThinking( + _ text: RichText, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // Dimmed/secondary base color: the paragraph body color at reduced alpha. RichText keeps + // its own bold/italic/link/inline-emoji formatting on top of this base (mirrors the old + // hardcoded "Thinking…" header, which used the message theme's dimmed description color). + let base = context.theme.textCategories.paragraph + let dimmedAttributes = InstantPageTextAttributes( + font: base.font, + color: base.color.withAlphaComponent(0.55), + underline: false + ) + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, attributes: dimmedAttributes) + let attributedString = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + + // Mirror a normal `.text` item's sizing: lay the text out flush (offset .zero) and put the page + // inset onto the BLOCK frame, so the `.thinking` item's frame == a `.text` item's frame + // (`(horizontalInset, 0, textWidth, height)`) instead of a full-bleed `(0, 0, boundingWidth, …)` + // box. The shimmer (sized to `item.frame.size`) then hugs the text rather than the whole page + // width; the rendered text stays at the same place (`horizontalInset`) since the block carries + // the inset. + let (textItem, _, textSize) = layoutTextItem( + attributedString, + boundingWidth: boundingWidth - horizontalInset * 2.0, + offset: CGPoint(x: 0.0, y: 0.0), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + guard let textItem = textItem else { return [] } + + let blockFrame = CGRect(x: horizontalInset, y: 0.0, width: textSize.width, height: textSize.height) + return [.thinking(InstantPageV2ThinkingItem(frame: blockFrame, textItem: textItem))] +} + +// MARK: - Block quote / pull quote layout (ported from V1 InstantPageLayout.swift lines 517–586) + +private func layoutBlockQuote( + blocks: [InstantPageBlock], + caption: RichText, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + isLast: Bool, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // Legacy single-paragraph fast path: preserve today's italicized body styling. + if blocks.count == 1, case let .paragraph(text) = blocks[0] { + return layoutQuoteText(text: text, caption: caption, isPull: false, + boundingWidth: boundingWidth, horizontalInset: horizontalInset, + context: &context) + } + + let verticalInset: CGFloat = 4.0 + let lineInset: CGFloat = context.fitToWidth ? 12.0 : 20.0 + let barWidth: CGFloat = 3.0 + + let innerBoundingWidth = boundingWidth - horizontalInset * 2.0 - lineInset + let innerHorizontalInset = horizontalInset + lineInset + // RTL: rigid-translate the child band so its gutter (lineInset) lands on the trailing edge, + // faithfully mirroring the existing (intentionally preserved) LTR band. Width is preserved, + // so a single x-delta moves the whole band correctly. + let bandOffsetX: CGFloat = context.rtl ? (2.0 * horizontalInset + lineInset) : 0.0 + + var result: [InstantPageV2LaidOutItem] = [] + var contentHeight: CGFloat = verticalInset + + // Fixed, compact gap between a quote's child blocks. The full page-flow spacing + // (spacingBetweenBlocks ~27pt around quotes) is too airy when nested; the first + // child hugs the top (only verticalInset above it). + let childSpacing: CGFloat = 10.0 + for (i, child) in blocks.enumerated() { + let spacing: CGFloat = i == 0 ? 0.0 : childSpacing + let childItems = layoutBlock( + child, + boundingWidth: innerBoundingWidth, + horizontalInset: innerHorizontalInset, + kind: kind, + isCover: false, + previousItems: result, + isLast: i == blocks.count - 1 && isLast, + context: &context + ) + let dy = contentHeight + spacing + let offsetItems = childItems.map { $0.offsetBy(CGPoint(x: bandOffsetX, y: dy)) } + let childMaxY = offsetItems.map { $0.frame.maxY }.max() ?? dy + contentHeight = max(contentHeight, childMaxY) + result.append(contentsOf: offsetItems) + } + + // Optional caption (mirrors layoutQuoteText's caption branch). + if case .empty = caption { + // no caption + } else { + contentHeight += 14.0 + let captionStyleStack = InstantPageTextStyleStack() + setupStyleStack(captionStyleStack, theme: context.theme, category: .caption, link: false) + let attributedCaption = attributedStringForRichText(caption, styleStack: captionStyleStack, formatDate: context.formatDate) + let (_, captionItems, captionSize) = layoutTextItem( + attributedCaption, + boundingWidth: innerBoundingWidth, + alignment: context.rtl ? .right : .natural, + // The caption is single-inset (band [H+lineInset, B-H]), unlike the double-inset + // child band, so it needs its own RTL mirror delta of -lineInset (→ [H, B-H-lineInset], + // tucked under the trailing bar) — NOT the children's bandOffsetX. + offset: CGPoint(x: innerHorizontalInset + (context.rtl ? -lineInset : 0.0), y: contentHeight), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + result.append(contentsOf: captionItems) + contentHeight += captionSize.height + } + + contentHeight += verticalInset + + // Vertical bar on the leading edge (matches the blockQuote branch of layoutQuoteText). + let bar = InstantPageV2BarItem( + frame: CGRect(x: instantPageV2LeadingEdgeX(boundingWidth: boundingWidth, horizontalInset: horizontalInset, elementWidth: barWidth, rtl: context.rtl), y: 0.0, width: barWidth, height: contentHeight), + color: context.theme.textCategories.paragraph.color, + cornerRadius: barWidth / 2.0 + ) + result.append(.blockQuoteBar(bar)) + + // Caption items (appended above) are also bumped to quoteDepth 1 and will render with a + // `>` prefix. The whole-message markdown converter drops blockquote captions entirely, and + // markdown-sent quotes carry empty captions, so this is benign. + bumpQuoteDepth(result) + return result +} + +private func layoutQuoteText( + text: RichText, + caption: RichText, + isPull: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // V1 line 518/553: verticalInset = 4.0 for both variants. + let verticalInset: CGFloat = 4.0 + // V1 line 518: lineInset = 20.0 (blockQuote only; pullQuote uses full width). + let lineInset: CGFloat = isPull ? 0.0 : (context.fitToWidth ? 12.0 : 20.0) + + var result: [InstantPageV2LaidOutItem] = [] + var contentHeight: CGFloat = verticalInset // V1 line 520/554: starts at verticalInset + + // Body text style: paragraph + italic (V1 lines 524–526 / 558–560). + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .paragraph, link: false) + styleStack.push(.italic) + + if isPull { + // pullQuote: top horizontal rule (plan-specified; matches V1 divider geometry for caption color). + // V1 doesn't emit shape ornaments for pullQuote, but the plan calls for them. + // Geometry mirrors V1 divider (InstantPageLayout.swift line 362): + // lineWidth = boundingWidth - horizontalInset * 2.0, x = horizontalInset, h = 1.0. + let lineWidth = boundingWidth - horizontalInset * 2.0 + let topLine = InstantPageV2ShapeItem( + frame: CGRect(x: horizontalInset, y: contentHeight, width: lineWidth, height: 1.0), + kind: .line(thickness: UIScreenPixel), + color: context.theme.separatorColor + ) + result.append(.shape(topLine)) + contentHeight += 1.0 + verticalInset // rule + small gap before body text + } + + // Body text (V1 line 528 / 562). + let textBoundingWidth = boundingWidth - horizontalInset * 2.0 - lineInset + let textX: CGFloat = instantPageV2ContentColumnX(horizontalInset: horizontalInset, gutter: lineInset, rtl: context.rtl) + let textAlignment: NSTextAlignment = isPull ? .center : (context.rtl ? .right : .natural) + + let attributedBody = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + let (_, bodyItems, bodySize) = layoutTextItem( + attributedBody, + boundingWidth: textBoundingWidth, + alignment: textAlignment, + offset: CGPoint(x: textX, y: contentHeight), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + result.append(contentsOf: bodyItems) + contentHeight += bodySize.height // V1 line 530/567 + + // Optional caption (V1 lines 533–544 / 570–583). + if case .empty = caption { + // no caption + } else { + contentHeight += 14.0 // V1 lines 535/572: 14pt gap before caption + + let captionStyleStack = InstantPageTextStyleStack() + setupStyleStack(captionStyleStack, theme: context.theme, category: .caption, link: false) + + let attributedCaption = attributedStringForRichText(caption, styleStack: captionStyleStack, formatDate: context.formatDate) + let (_, captionItems, captionSize) = layoutTextItem( + attributedCaption, + boundingWidth: textBoundingWidth, + alignment: textAlignment, + offset: CGPoint(x: textX, y: contentHeight), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + result.append(contentsOf: captionItems) + contentHeight += captionSize.height // V1 lines 542/582 + } + + contentHeight += verticalInset // V1 lines 545/585 + + if isPull { + // pullQuote: bottom horizontal rule (plan-specified; mirrors top rule). + let lineWidth = boundingWidth - horizontalInset * 2.0 + let bottomLine = InstantPageV2ShapeItem( + frame: CGRect(x: horizontalInset, y: contentHeight - 1.0, width: lineWidth, height: 1.0), + kind: .line(thickness: 1.0), + color: context.theme.textCategories.caption.color + ) + // Insert bottom rule before caption trailing space is consumed — append after final verticalInset. + result.append(.shape(bottomLine)) + } else { + // blockQuote: vertical bar on the leading edge (V1 lines 547–549). + // V1: frame = CGRect(x: horizontalInset, y: 0.0, width: 3.0, height: contentSize.height) + // V1 shape: .roundLine (rounded caps) → cornerRadius = barWidth / 2 = 1.5. + let barWidth: CGFloat = 3.0 // V1 line 547 + let bar = InstantPageV2BarItem( + frame: CGRect(x: instantPageV2LeadingEdgeX(boundingWidth: boundingWidth, horizontalInset: horizontalInset, elementWidth: barWidth, rtl: context.rtl), y: 0.0, width: barWidth, height: contentHeight), + color: context.theme.textCategories.paragraph.color, // V1 line 547 + cornerRadius: barWidth / 2.0 // V1 .roundLine ≈ half-width rounded caps + ) + result.append(.blockQuoteBar(bar)) + } + + // Tag this quote's produced text items at quote depth 1 so the markdown + // converter renders them with a `> ` prefix. Applies to BOTH block quotes + // (single-paragraph fast path) and pull quotes — the whole-message markdown + // converter renders both flavors as `> `. Nested quotes are lifted further + // by the outer multi-block path's own bumpQuoteDepth(result) call. + bumpQuoteDepth(result) + + return result +} + +// MARK: - List layout (ported from V1 InstantPageLayout.swift lines 365–516) + +private func layoutList( + _ listItems: [InstantPageListItem], + ordered: Bool, + boundingWidth: CGFloat, + horizontalInset: CGFloat, + kind: BlockSequenceKind, + context: inout LayoutContext +) -> [InstantPageV2LaidOutItem] { + // Determine marker characteristics. + var maxIndexWidth: CGFloat = 0.0 + // hasNums: at least one ordered item carries an explicit `num` — in which case items + // without one fall back to a blank " " (preserves the source's numbering gaps) rather + // than auto-generated `(i + 1).`. Unordered lists never auto-generate numbers, so this + // flag is only meaningful when `ordered` is true. (`hasTaskMarkers` is no longer derived + // — the uniform 8pt gap below replaced the per-list `indexSpacing` ternary that consumed + // it; column right-alignment handles mixed bullet/checkbox lists without flagging.) + var hasNums = false + if ordered { + for item in listItems { + if item.checked == nil, let num = item.num, !num.isEmpty { + hasNums = true + break + } + } + } + + // Build per-item marker descriptors and measure their natural widths. + struct MarkerInfo { + let kind: InstantPageV2ListMarkerKind + let naturalWidth: CGFloat // for right-aligning number markers + } + let checklistMarkerSize = CGSize(width: 18.0, height: 18.0) + + let checkboxColors = InstantPageV2CheckboxColors( + background: context.theme.panelAccentColor, + stroke: context.theme.pageBackgroundColor, + border: context.theme.controlColor + ) + // Track maxIndexWidth for ALL marker kinds (ordered + unordered, all three shapes), not + // just ordered as V1/older V2 did. With every kind contributing to the marker column width + // we can right-align every marker to a single shared column edge — so in a mixed unordered + // list (bullets + checkboxes) both right-align flush to the same x, and the same uniform + // gap separates them from the text. The column width simply equals the widest marker; for + // a pure bullet list `maxIndexWidth == 6` and the bullet sits at `horizontalInset` (visually + // identical to the pre-change formula), and for a pure checkbox list `maxIndexWidth == 18` + // matches the previous left-aligned placement too. + var markerInfos: [MarkerInfo] = [] + for (i, item) in listItems.enumerated() { + if let checked = item.checked { + maxIndexWidth = max(maxIndexWidth, checklistMarkerSize.width) + markerInfos.append(MarkerInfo(kind: .checklist(checked: checked, colors: checkboxColors), naturalWidth: checklistMarkerSize.width)) + } else if ordered { + let value: String + if hasNums { + if let num = item.num { + value = "\(num)." + } else { + value = " " + } + } else { + value = "\(i + 1)." + } + // Measure using a UILabel to get the expected label width. + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .paragraph, link: false) + let attrStr = attributedStringForRichText(.plain(value), styleStack: styleStack, formatDate: context.formatDate) + let (textItem, _, _) = layoutTextItem( + attrStr, + boundingWidth: boundingWidth - horizontalInset * 2.0, + offset: .zero, + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + let w: CGFloat + if let textItem = textItem, let firstLine = textItem.lines.first { + w = firstLine.frame.width + } else { + w = 0.0 + } + maxIndexWidth = max(maxIndexWidth, w) + markerInfos.append(MarkerInfo(kind: .number(value), naturalWidth: w)) + } else { + // Bullet: 6×6 ellipse (matches V1 InstantPageShapeItem dimensions). + maxIndexWidth = max(maxIndexWidth, 6.0) + markerInfos.append(MarkerInfo(kind: .bullet, naturalWidth: 6.0)) + } + } + + // Uniform 8pt marker→text gap across all four cases (ordered/unordered × bullet/number/ + // checkbox). With markers right-aligned to a shared column of width `maxIndexWidth`, text + // starts at `horizontalInset + maxIndexWidth + indexSpacing` — so `indexSpacing` IS the + // gap, regardless of marker shape. V1 used 12/16/20/24 (a mix of marker-area-width and + // gap-after-marker, depending on alignment); the four gaps came out to 12/16/14/6 — far + // from uniform, and a 14pt bullet gap looked especially loose. 8pt is a standard iOS list + // gap; it tightens bullets (14→8), numbers (12→8) and ordered-checkbox (16→8), and only + // loosens unordered-checkbox very slightly (6→8) so all four kinds match. + let indexSpacing: CGFloat = 8.0 + + // Layout each item. + var result: [InstantPageV2LaidOutItem] = [] + var contentHeight: CGFloat = 0.0 + + for (i, item) in listItems.enumerated() { + // Inter-item spacing (matches V1: 18pt normal, 12pt fitToWidth). + if i != 0 { + contentHeight += context.fitToWidth ? 12.0 : 18.0 + } + + let markerInfo = markerInfos[i] + + // Effective item: if a .blocks item is empty, treat as a single space. + var effectiveItem = item + if case let .blocks(blocks, num, checked) = effectiveItem, blocks.isEmpty { + effectiveItem = .text(.plain(" "), num, checked) + } + + // Derive the markdown marker string and checked state from the original item. + let markdownMarker: String + switch markerInfo.kind { + case let .number(value): markdownMarker = value + default: markdownMarker = "-" + } + let markdownChecked: Bool? = item.checked + + switch effectiveItem { + case let .text(text, _, _): + // Layout text content. + let styleStack = InstantPageTextStyleStack() + setupStyleStack(styleStack, theme: context.theme, category: .paragraph, link: false) + let attrStr = attributedStringForRichText(text, styleStack: styleStack, formatDate: context.formatDate) + let textX = instantPageV2ContentColumnX(horizontalInset: horizontalInset, gutter: indexSpacing + maxIndexWidth, rtl: context.rtl) + let textWidth = boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth + let (textItem, textLaidOutItems, textSize) = layoutTextItem( + attrStr, + boundingWidth: textWidth, + alignment: context.rtl ? .right : .natural, + offset: CGPoint(x: textX, y: contentHeight), + fitToWidth: context.fitToWidth, + computeRevealCharacterRects: context.computeRevealCharacterRects + ) + + // Compute marker vertical position: align to mid of first text line. + var lineMidY: CGFloat = contentHeight + if let textItem = textItem { + if let firstLine = textItem.lines.first { + lineMidY = textItem.frame.minY + firstLine.frame.midY + } else { + lineMidY = textItem.frame.midY + } + } + + // Compute marker frame. + let markerFrame = markerFrameFor( + kind: markerInfo.kind, + naturalWidth: markerInfo.naturalWidth, + maxIndexWidth: maxIndexWidth, + horizontalInset: horizontalInset, + checklistMarkerSize: checklistMarkerSize, + lineMidY: lineMidY, + rtl: context.rtl, + boundingWidth: boundingWidth + ) + + result.append(.listMarker(InstantPageV2ListMarkerItem( + frame: markerFrame, + kind: markerInfo.kind, + color: context.theme.textCategories.paragraph.color + ))) + stampMarkdownContext(textLaidOutItems, kind: .listItem(ordered: ordered, marker: markdownMarker, checked: markdownChecked)) + result.append(contentsOf: textLaidOutItems) + contentHeight += textSize.height + + case let .blocks(blocks, _, _): + // Nested block content (e.g. sub-list, paragraphs). + var previousBlock: InstantPageBlock? + let originY = contentHeight + var firstBlockLineMidY: CGFloat? + + for (j, subBlock) in blocks.enumerated() { + let subItems = layoutBlock( + subBlock, + boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, + horizontalInset: 0.0, + kind: kind, + isCover: false, + previousItems: result, + isLast: j == blocks.count - 1, + context: &context + ) + let subLocalMaxY: CGFloat = subItems.map { $0.frame.maxY }.max() ?? 0.0 + let spacing: CGFloat = (previousBlock != nil && subLocalMaxY > 0.0) ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock, fitToWidth: context.fitToWidth, kind: .list) : 0.0 + let offsetX = instantPageV2ContentColumnX(horizontalInset: horizontalInset, gutter: indexSpacing + maxIndexWidth, rtl: context.rtl) + let offsetY = contentHeight + spacing + let translatedItems = subItems.map { $0.offsetBy(CGPoint(x: offsetX, y: offsetY)) } + + if firstBlockLineMidY == nil { + // Find the mid-Y of the first text line in the first block. + for translated in translatedItems { + if case let .text(tv) = translated { + if let firstLine = tv.textItem.lines.first { + firstBlockLineMidY = tv.frame.minY + firstLine.frame.midY + } else { + firstBlockLineMidY = tv.frame.midY + } + break + } + } + } + + // Compute block height contribution. + // offsetY = contentHeight + spacing, so blockMaxY already accounts for spacing. + var blockMaxY: CGFloat = offsetY + for ti in translatedItems { + blockMaxY = max(blockMaxY, ti.frame.maxY) + } + + // Nil-guard in stampMarkdownContext preserves any richer kind (e.g. .heading) + // already stamped by a child block's own layout. So a heading nested inside a + // .blocks list item keeps .heading, not .listItem — multi-block list items are + // a documented best-effort case for markdown reconstruction. + stampMarkdownContext(translatedItems, kind: .listItem(ordered: ordered, marker: markdownMarker, checked: markdownChecked)) + result.append(contentsOf: translatedItems) + contentHeight = blockMaxY + previousBlock = subBlock + } + + // Mirror the .text case above (and what .checklist already does here): use the + // first text line's midY for centering. `originY` is the sub-block's TOP, NOT a + // line midpoint — `markerFrameFor` then subtracts `size.height / 2`, so feeding + // `originY` placed the marker straddling the sub-block boundary, ½·marker-height + // ABOVE the first text line. V1 hid the same arithmetic under a 6×12 shape with a + // 3pt internal offset (matching ½·fontLineHeight for 17pt paragraph text), which + // by coincidence equals `firstBlockLineMidY`. Using firstBlockLineMidY directly + // makes the alignment explicit, unifies the three marker kinds, and matches the + // .text case exactly. Fallback to `originY` when no text is in the first sub-block + // (image-first lists are rare); mirrors the existing .checklist fallback. + let markerLineMidY: CGFloat = firstBlockLineMidY ?? originY + let markerFrame = markerFrameFor( + kind: markerInfo.kind, + naturalWidth: markerInfo.naturalWidth, + maxIndexWidth: maxIndexWidth, + horizontalInset: horizontalInset, + checklistMarkerSize: checklistMarkerSize, + lineMidY: markerLineMidY, + rtl: context.rtl, + boundingWidth: boundingWidth + ) + + result.append(.listMarker(InstantPageV2ListMarkerItem( + frame: markerFrame, + kind: markerInfo.kind, + color: context.theme.textCategories.paragraph.color + ))) + + default: + break + } + } + + return result +} + +/// Computes the frame for a list marker, handling RTL and all three marker kinds. +/// +/// All marker kinds are right-aligned within the shared `[horizontalInset, horizontalInset + +/// maxIndexWidth]` column (LTR) or left-aligned within the mirrored column on the right (RTL). +/// For a pure-kind list `maxIndexWidth == markerWidth`, so the marker lands at `horizontalInset` +/// exactly as before; for mixed unordered lists, bullets and checkboxes align flush at the +/// column's inner edge. Column right-alignment is the single rule across every marker shape +/// — no `ordered` / `indexSpacing` split — which is why those parameters dropped. +private func markerFrameFor( + kind: InstantPageV2ListMarkerKind, + naturalWidth: CGFloat, + maxIndexWidth: CGFloat, + horizontalInset: CGFloat, + checklistMarkerSize: CGSize, + lineMidY: CGFloat, + rtl: Bool, + boundingWidth: CGFloat +) -> CGRect { + let size: CGSize + switch kind { + case .bullet: + size = CGSize(width: 6.0, height: 6.0) + case .number: + size = CGSize(width: naturalWidth, height: 20.0) + case .checklist: + size = checklistMarkerSize + } + let x: CGFloat + if rtl { + x = boundingWidth - horizontalInset - maxIndexWidth + } else { + x = horizontalInset + maxIndexWidth - size.width + } + return CGRect(x: x, y: floorToScreenPixels(lineMidY - size.height / 2.0), width: size.width, height: size.height) +} + +/// Leading/trailing geometry helpers — the single source of truth for "which side is the +/// block gutter on", gated on the page's explicit `rtl` flag. The `rtl == false` branch returns +/// the pre-existing literal so non-RTL pages are byte-identical. + +/// X origin of a block's content column, given a leading gutter of width `gutter` +/// (the marker column, or the quote bar+inset band). Column width is unchanged either way. +/// LTR: content sits after the gutter → horizontalInset + gutter +/// RTL: content sits at the inset; the gutter is mirrored onto the trailing edge → horizontalInset +func instantPageV2ContentColumnX(horizontalInset: CGFloat, gutter: CGFloat, rtl: Bool) -> CGFloat { + return rtl ? horizontalInset : horizontalInset + gutter +} + +/// X origin of a leading-edge element of width `elementWidth` (e.g. the quote bar), hugging the +/// trailing edge of the gutter band in RTL. +/// LTR: horizontalInset +/// RTL: boundingWidth - horizontalInset - elementWidth +func instantPageV2LeadingEdgeX(boundingWidth: CGFloat, horizontalInset: CGFloat, elementWidth: CGFloat, rtl: Bool) -> CGFloat { + return rtl ? (boundingWidth - horizontalInset - elementWidth) : horizontalInset +} + +// MARK: - Style helpers (ported from V1 InstantPageLayout.swift lines 32–88) + +private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, attributes: InstantPageTextAttributes) { + stack.push(.textColor(attributes.color)) + stack.push(.markerColor(theme.markerColor)) + stack.push(.linkColor(theme.linkColor)) + stack.push(.linkMarkerColor(theme.linkHighlightColor)) + switch attributes.font.style { + case .sans: + stack.push(.fontSerif(false)) + case .serif: + stack.push(.fontSerif(true)) + case .monospace: + stack.push(.fontFixed(true)) + } + stack.push(.fontSize(attributes.font.size)) + stack.push(.lineSpacingFactor(attributes.font.lineSpacingFactor)) + if attributes.underline { + stack.push(.underline) + } +} + +private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantPageTheme, category: InstantPageTextCategoryType, link: Bool) { + setupStyleStack(stack, theme: theme, attributes: theme.textCategories.attributes(type: category, link: link)) +} + +private func instantPageFont(style: InstantPageTextAttributes, bold: Bool = false, italic: Bool = false, fixed: Bool = false) -> UIFont { + let size = style.font.size + if fixed { + if bold && italic { + return UIFont(name: "Menlo-BoldItalic", size: size) ?? Font.semiboldItalic(size) + } else if bold { + return UIFont(name: "Menlo-Bold", size: size) ?? Font.bold(size) + } else if italic { + return UIFont(name: "Menlo-Italic", size: size) ?? Font.italic(size) + } else { + return UIFont(name: "Menlo", size: size) ?? Font.regular(size) + } + } + switch style.font.style { + case .serif: + if bold && italic { + return UIFont(name: "Georgia-BoldItalic", size: size) ?? Font.semiboldItalic(size) + } else if bold { + return UIFont(name: "Georgia-Bold", size: size) ?? Font.bold(size) + } else if italic { + return UIFont(name: "Georgia-Italic", size: size) ?? Font.italic(size) + } else { + return UIFont(name: "Georgia", size: size) ?? Font.regular(size) + } + case .sans: + if bold && italic { + return Font.semiboldItalic(size) + } else if bold { + return Font.bold(size) + } else if italic { + return Font.italic(size) + } else { + return Font.regular(size) + } + case .monospace: + if bold && italic { + return Font.semiboldItalicMonospace(size) + } else if bold { + return Font.semiboldMonospace(size) + } else if italic { + return Font.italicMonospace(size) + } else { + return Font.monospace(size) + } + } +} + +// MARK: - Preformatted text (ported from V1 InstantPageLayout.swift lines 91–132) + +private func attributedStringForPreformattedText(_ text: RichText, language: String?, theme: InstantPageTheme, cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight?) -> NSAttributedString { + let paragraphAttributes = theme.textCategories.attributes(type: .paragraph, link: false) + let textValue = text.plainText + guard !textValue.isEmpty else { + return NSAttributedString( + string: "", + attributes: [ + .font: instantPageFont(style: paragraphAttributes, fixed: true), + .foregroundColor: paragraphAttributes.color, + NSAttributedString.Key(rawValue: InstantPageLineSpacingFactorAttribute): paragraphAttributes.font.lineSpacingFactor as NSNumber + ] + ) + } + + let attributedString = stringWithAppliedEntities( + textValue, + entities: [ + MessageTextEntity(range: 0 ..< (textValue as NSString).length, type: .Pre(language: language)) + ], + baseColor: paragraphAttributes.color, + linkColor: theme.linkColor, + codeBlockTitleColor: paragraphAttributes.color, + codeBlockAccentColor: paragraphAttributes.color, + codeBlockBackgroundColor: theme.codeBlockBackgroundColor, + baseFont: instantPageFont(style: paragraphAttributes), + linkFont: instantPageFont(style: paragraphAttributes), + boldFont: instantPageFont(style: paragraphAttributes, bold: true), + italicFont: instantPageFont(style: paragraphAttributes, italic: true), + boldItalicFont: instantPageFont(style: paragraphAttributes, bold: true, italic: true), + fixedFont: instantPageFont(style: paragraphAttributes, fixed: true), + blockQuoteFont: instantPageFont(style: paragraphAttributes), + underlineLinks: false, + message: nil, + cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight + ).mutableCopy() as! NSMutableAttributedString + attributedString.addAttribute( + NSAttributedString.Key(rawValue: InstantPageLineSpacingFactorAttribute), + value: paragraphAttributes.font.lineSpacingFactor as NSNumber, + range: NSRange(location: 0, length: attributedString.length) + ) + return attributedString +} + +// MARK: - V2 text-item layout (ported from V1 InstantPageTextItem.swift layoutTextItemWithString) +// +// V0 difference from V1: +// * Inline image runs are NOT emitted as items here. They are discovered at view-update time +// by `InstantPageV2View.updateInlineImages()`, which walks each text view's `line.imageItems` +// and creates `InstantPageV2InlineImageView`s attached to the text view's `imageContainerView` +// (the pop-in animation mirrors the inline custom-emoji ownership model). +// * Inline formula runs produce `.formula(InstantPageV2FormulaItem(...))` items carrying the +// rendered math image (see `InstantPageV2FormulaView`); the line's `formulaItems` field +// already provides the attachment + frame. +// * No `InstantPageScrollableTextItem` wrapping: even if `requiresScroll` would be true in V1, +// V2 takes the non-scroll path (text item kept flat; long preformatted lines simply clip +// outside the bubble width). Deferred to a future iteration. + +// Internal helpers ported from V1 InstantPageTextItem.swift (declared private there; copied here). +// `internal` (not private) so that InstantPageRenderer.swift can call them from the same module. +func v2FrameForLine(_ line: InstantPageTextLine, boundingWidth: CGFloat, alignment: NSTextAlignment) -> CGRect { + var lineFrame = line.frame + if alignment == .center { + lineFrame.origin.x = floor((boundingWidth - lineFrame.size.width) / 2.0) + } else if alignment == .right || (alignment == .natural && line.isRTL) { + lineFrame.origin.x = boundingWidth - lineFrame.size.width + } + return lineFrame +} + +// Returns the leading-edge x offset (line-origin-relative) for an inline-attachment's string +// `range`, correct for both LTR and RTL runs. `CTLineGetOffsetForStringIndex` at the start index +// gives the glyph's LEFT edge in LTR text, but its RIGHT edge in RTL text (increasing string index +// moves leftward) — so using the start-index offset alone as the left edge shoves an RTL attachment +// ~one advance too far right. Taking the min of the start- and end-index offsets yields the true +// leading (left) edge in both directions. Mirrors `Display.TextNode`'s `addEmbeddedItem`, including +// the directional-boundary secondary-offset handling. For a pure-LTR line this returns exactly the +// start-index offset (primary == secondary, and start-offset < end-offset), so LTR layout is +// byte-identical to the previous single-offset behavior. +private func v2LeadingOffsetForRange(_ line: CTLine, range: NSRange) -> CGFloat { + var secondaryStartOffset: CGFloat = 0.0 + let rawStartOffset = CTLineGetOffsetForStringIndex(line, range.location, &secondaryStartOffset) + var startOffset = rawStartOffset + if !rawStartOffset.isEqual(to: secondaryStartOffset) { + startOffset = secondaryStartOffset + } + + var secondaryEndOffset: CGFloat = 0.0 + let rawEndOffset = CTLineGetOffsetForStringIndex(line, range.location + range.length, &secondaryEndOffset) + var endOffset = rawEndOffset + if !rawEndOffset.isEqual(to: secondaryEndOffset) { + endOffset = secondaryEndOffset + } + + return min(startOffset, endOffset) +} + +private func v2LocalAttachmentBoundsForRange(_ range: NSRange, imageItems: [InstantPageTextImageItem], formulaItems: [InstantPageTextFormulaRun]) -> CGRect? { + var result: CGRect? + + for imageItem in imageItems { + if NSIntersectionRange(range, imageItem.range).length != 0 { + if let current = result { + result = current.union(imageItem.frame) + } else { + result = imageItem.frame + } + } + } + + for formulaItem in formulaItems { + if NSIntersectionRange(range, formulaItem.range).length != 0 { + if let current = result { + result = current.union(formulaItem.frame) + } else { + result = formulaItem.frame + } + } + } + + return result +} + +private struct PendingV2ImageAttachment { + let xOffset: CGFloat + let range: NSRange + let id: Int64 + let size: CGSize +} + +private struct PendingV2FormulaAttachment { + let xOffset: CGFloat + let range: NSRange + let attachment: InstantPageMathAttachment + let baselineOffset: CGFloat +} + +private struct PendingV2EmojiAttachment { + let xOffset: CGFloat + let range: NSRange + let emoji: ChatTextInputTextCustomEmojiAttribute + let size: CGFloat +} + +func layoutTextItem( + _ string: NSAttributedString, + boundingWidth: CGFloat, + horizontalInset: CGFloat = 0.0, + alignment: NSTextAlignment = .natural, + offset: CGPoint, + minimizeWidth: Bool = false, + fitToWidth: Bool = false, + maxNumberOfLines: Int = 0, + opaqueBackground: Bool = false, + computeRevealCharacterRects: Bool = false +) -> (InstantPageTextItem?, [InstantPageV2LaidOutItem], CGSize) { + if string.length == 0 { + return (nil, [], CGSize()) + } + + var lines: [InstantPageTextLine] = [] + var imageItems: [InstantPageTextImageItem] = [] + var hasFormulaItems: Bool = false + var font = string.attribute(NSAttributedString.Key.font, at: 0, effectiveRange: nil) as? UIFont + if font == nil { + let range = NSMakeRange(0, string.length) + string.enumerateAttributes(in: range, options: []) { attributes, range, _ in + if font == nil, let furtherFont = attributes[NSAttributedString.Key.font] as? UIFont { + font = furtherFont + } + } + } + let image = string.attribute(NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute), at: 0, effectiveRange: nil) + let formula = string.attribute(NSAttributedString.Key(rawValue: InstantPageFormulaAttribute), at: 0, effectiveRange: nil) + guard font != nil || image != nil || formula != nil else { + return (nil, [], CGSize()) + } + + var lineSpacingFactor: CGFloat = 1.12 + if let lineSpacingFactorAttribute = string.attribute(NSAttributedString.Key(rawValue: InstantPageLineSpacingFactorAttribute), at: 0, effectiveRange: nil) { + lineSpacingFactor = CGFloat((lineSpacingFactorAttribute as! NSNumber).floatValue) + } + + let typesetter = CTTypesetterCreateWithAttributedString(string) + let fontAscent = font?.ascender ?? 0.0 + let fontDescent = font?.descender ?? 0.0 + + let fontLineHeight = floor(fontAscent + fontDescent) + let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) + let fontDescentBelowBaseline = max(0.0, -fontDescent) + // True font-height line box: shift the whole line stack down by the ascender headroom above + // the cap line (A − L) and pad the final height by the descender (D) below the last baseline, + // so a single-line item measures exactly A + D. Exact (not pixel-snapped): this is an + // intra-item line offset; crispness rides on the item's own pixel-snapped frame origin, and + // intra-item line positions may already be fractional (e.g. after a non-integral extraDescent). + // Inter-line advance is unchanged. (Named `lineBoxTopInset` to avoid colliding with the + // formula-bleed `topInset` local near the end of this function.) + let lineBoxTopInset = max(0.0, fontAscent - fontLineHeight) + let baselineToNextTopSlack = max(0.0, fontLineSpacing - 4.0) + + var lastIndex: CFIndex = 0 + var currentLineOrigin = CGPoint(x: 0.0, y: lineBoxTopInset) + + var hasAnchors = false + var maxLineWidth: CGFloat = 0.0 + var extraDescent: CGFloat = 0.0 + let text = string.string + var indexOffset: CFIndex? + while true { + var workingLineOrigin = currentLineOrigin + + let currentMaxWidth = boundingWidth - workingLineOrigin.x + var lineCharacterCount: CFIndex + var hadIndexOffset = false + if minimizeWidth { + var count = 0 + for ch in text.suffix(text.count - lastIndex) { + count += 1 + if ch == " " || ch == "\n" || ch == "\t" { + break + } + } + lineCharacterCount = count + } else { + let suggestedLineBreak = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth)) + if let offset = indexOffset { + lineCharacterCount = suggestedLineBreak + offset + if lineCharacterCount <= 0 { + lineCharacterCount = suggestedLineBreak + } + indexOffset = nil + hadIndexOffset = true + } else { + lineCharacterCount = suggestedLineBreak + } + } + if lineCharacterCount > 0 { + var line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0) + var lineWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil)) + let lineRange = NSMakeRange(lastIndex, lineCharacterCount) + + var stop = false + if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 && lastIndex + lineCharacterCount < string.length { + let attributes = string.attributes(at: lastIndex + lineCharacterCount - 1, effectiveRange: nil) + if let truncateString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, attributes as CFDictionary) { + let truncateToken = CTLineCreateWithAttributedString(truncateString) + let tokenWidth = CGFloat(CTLineGetTypographicBounds(truncateToken, nil, nil, nil) + 3.0) + if let truncatedLine = CTLineCreateTruncatedLine(line, Double(lineWidth - tokenWidth), .end, truncateToken) { + lineWidth += tokenWidth + line = truncatedLine + } + } + stop = true + } + + let hadExtraDescent = extraDescent > 0.0 + extraDescent = 0.0 + var lineImageItems: [InstantPageTextImageItem] = [] + var lineFormulaItems: [InstantPageTextFormulaRun] = [] + var pendingImages: [PendingV2ImageAttachment] = [] + var pendingFormulas: [PendingV2FormulaAttachment] = [] + var lineEmojiItems: [InstantPageTextEmojiItem] = [] + var pendingEmoji: [PendingV2EmojiAttachment] = [] + var isRTL = false + if let glyphRuns = CTLineGetGlyphRuns(line) as? [CTRun], !glyphRuns.isEmpty { + if let run = glyphRuns.first, CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + + for run in glyphRuns { + let cfRunRange = CTRunGetStringRange(run) + let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length) + string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in + if let id = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaDimensionsAttribute)] as? PixelDimensions { + let imageSize = dimensions.cgSize.fitted(CGSize(width: boundingWidth, height: boundingWidth)) + let xOffset = v2LeadingOffsetForRange(line, range: range) + pendingImages.append(PendingV2ImageAttachment(xOffset: xOffset, range: range, id: id, size: imageSize)) + } else if let attachment = attributes[NSAttributedString.Key(rawValue: InstantPageFormulaAttribute)] as? InstantPageMathAttachment { + let xOffset = v2LeadingOffsetForRange(line, range: range) + let baselineOffset = (attributes[NSAttributedString.Key.baselineOffset] as? CGFloat) ?? 0.0 + pendingFormulas.append(PendingV2FormulaAttachment(xOffset: xOffset, range: range, attachment: attachment, baselineOffset: baselineOffset)) + } else if let emoji = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute { + let xOffset = v2LeadingOffsetForRange(line, range: range) + let font = (attributes[NSAttributedString.Key.font] as? UIFont) ?? UIFont.systemFont(ofSize: 17.0) + // Size the inline emoji to the font's line height (A + D = the true + // line-box height) plus a 4pt bump at the 17pt body font (scaled + // proportionally) so it reads a touch larger than the bare line box. + // The line is NOT inflated (lineAscent stays fontLineHeight). Must match + // the run-delegate width in attributedStringForRichText (InstantPageTextItem.swift). + let itemSize = font.ascender - font.descender + 4.0 * font.pointSize / 17.0 + pendingEmoji.append(PendingV2EmojiAttachment(xOffset: xOffset, range: range, emoji: emoji, size: itemSize)) + } + } + } + } + + // Inline emoji and images do NOT inflate the line: they are centered on the font + // line box and allowed to bleed above/below (mirroring V1 `layoutTextItemWithString` + // and the chat `InteractiveTextComponent`). Their run delegates already report the + // font's own ascent/descent, so CoreText lays the line out at the normal height — the + // old `lineAscent = emoji.size` inflation both doubled the line height and (because the + // baseline sits at the bottom of the box) shoved the text baseline down. Only formulas, + // which carry their own typographic metrics, are allowed to grow the line. + var lineAscent: CGFloat = fontLineHeight + var lineDescent: CGFloat = fontDescentBelowBaseline + for formula in pendingFormulas { + let formulaAscent = formula.attachment.rendered.size.height - formula.attachment.rendered.descent + if formulaAscent > lineAscent { + lineAscent = formulaAscent + } + if formula.attachment.rendered.descent > lineDescent { + lineDescent = formula.attachment.rendered.descent + } + } + let baselineY = workingLineOrigin.y + lineAscent + + for image in pendingImages { + // Center on the font line box (baseline − fontLineHeight/2), matching V1's + // `(fontLineHeight - imageHeight) / 2` offset, instead of bottom-aligning on the + // baseline. Keeps the text baseline put and lets the image bleed symmetrically. + let imageFrame = CGRect( + x: workingLineOrigin.x + image.xOffset, + y: floorToScreenPixels(baselineY - fontLineHeight / 2.0 - image.size.height / 2.0), + width: image.size.width, + height: image.size.height + ) + lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: image.range, id: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: image.id))) + } + for formula in pendingFormulas { + let attachment = formula.attachment + let formulaAscent = attachment.rendered.size.height - attachment.rendered.descent + let formulaFrame = CGRect( + x: workingLineOrigin.x + formula.xOffset, + y: baselineY - formulaAscent + formula.baselineOffset, + width: attachment.rendered.size.width, + height: attachment.rendered.size.height + ) + lineFormulaItems.append(InstantPageTextFormulaRun(frame: formulaFrame, range: formula.range, attachment: attachment)) + } + for emoji in pendingEmoji { + // Center on the font line box (baseline − fontLineHeight/2) so a 24pt emoji on a + // ~17pt line bleeds symmetrically rather than forcing the line taller and pushing + // the text baseline down. Matches the chat `InteractiveTextComponent` placement. + let emojiFrame = CGRect( + x: workingLineOrigin.x + emoji.xOffset, + y: floorToScreenPixels(baselineY - fontLineHeight / 2.0 - emoji.size / 2.0), + width: emoji.size, + height: emoji.size + ) + lineEmojiItems.append(InstantPageTextEmojiItem(frame: emojiFrame, range: emoji.range, emoji: emoji.emoji)) + } + + extraDescent = max(0.0, lineDescent - baselineToNextTopSlack) + // A centered attachment taller than the line bleeds below the baseline; grow the + // descent so the following line isn't overlapped (mirrors V1's extraDescent handling). + // Emoji sized to the font line height (A + D) fit the line box, so they contribute nothing. + for imageItem in lineImageItems { + extraDescent = max(extraDescent, imageItem.frame.maxY - (baselineY + baselineToNextTopSlack)) + } + for emojiItem in lineEmojiItems { + extraDescent = max(extraDescent, emojiItem.frame.maxY - (baselineY + baselineToNextTopSlack)) + } + + if !minimizeWidth && !hadIndexOffset && lineCharacterCount > 1 && lineWidth > currentMaxWidth + 5.0 { + if let imageItem = lineImageItems.last { + indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound) + continue + } + if let formulaItem = lineFormulaItems.last { + indexOffset = -(lastIndex + lineCharacterCount - formulaItem.range.lowerBound) + continue + } + } + + var strikethroughItems: [InstantPageTextStrikethroughItem] = [] + var underlineItems: [InstantPageTextUnderlineItem] = [] + var markedItems: [InstantPageTextMarkedItem] = [] + var spoilerItems: [InstantPageTextSpoilerItem] = [] + var anchorItems: [InstantPageTextAnchorItem] = [] + + string.enumerateAttributes(in: lineRange, options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedString.Key.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughItems.append(InstantPageTextStrikethroughItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + (lineAscent - fontLineHeight), width: abs(upperX - lowerX), height: fontLineHeight))) + } + if let _ = attributes[NSAttributedString.Key.underlineStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + underlineItems.append(InstantPageTextUnderlineItem( + frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + (lineAscent - fontLineHeight), width: abs(upperX - lowerX), height: fontLineHeight), + range: range, + color: attributes[NSAttributedString.Key.underlineColor] as? UIColor + )) + } + if let color = attributes[NSAttributedString.Key.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor { + var lineHeight = fontLineHeight + var delta: CGFloat = 0.0 + + if let offset = attributes[NSAttributedString.Key.baselineOffset] as? CGFloat { + lineHeight = floorToScreenPixels(lineHeight * 0.85) + delta = offset * 0.6 + } + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + markedItems.append(InstantPageTextMarkedItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + (lineAscent - fontLineHeight) + delta, width: abs(upperX - lowerX), height: lineHeight), color: color, range: range)) + } + if attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] != nil { + let lowerX = floor(CTLineGetOffsetForStringIndex(line, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(line, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + spoilerItems.append(InstantPageTextSpoilerItem(frame: CGRect(x: workingLineOrigin.x + x, y: workingLineOrigin.y + (lineAscent - fontLineHeight), width: abs(upperX - lowerX), height: fontLineHeight), range: range)) + } + if let item = attributes[NSAttributedString.Key.init(rawValue: InstantPageAnchorAttribute)] as? Dictionary, let name = item["name"] as? String, let empty = item["empty"] as? Bool { + anchorItems.append(InstantPageTextAnchorItem(name: name, anchorText: item["text"] as? NSAttributedString, empty: empty)) + } + } + + if !anchorItems.isEmpty { + hasAnchors = true + } + + if hadExtraDescent && extraDescent > 0 { + workingLineOrigin.y += fontLineSpacing + } + + let height = lineAscent + if !markedItems.isEmpty { + markedItems = markedItems.map { item in + if let attachmentBounds = v2LocalAttachmentBoundsForRange(item.range, imageItems: lineImageItems, formulaItems: lineFormulaItems) { + return InstantPageTextMarkedItem(frame: attachmentBounds, color: item.color, range: item.range) + } else { + return item + } + } + } + // Per-character rects use each glyph's actual ink bounds via + // CTFontGetBoundingRectsForGlyphs — caret-position advance-width + // math (CTLineGetOffsetForStringIndex) is too tight for italics, + // accented marks, and any glyph with side bearings, which causes + // the reveal mask to visibly clip the glyph edges. Mirrors + // InteractiveTextComponent.computeCharacterRectsForLine. + // + // For ligatures (one glyph for multiple chars), only the first + // char's slot is populated; the rest stay CGRect.zero and the + // consumer's `rect.isEmpty` guard skips them. + let lineCharacterRects: [CGRect]? + if computeRevealCharacterRects { + var rects = [CGRect](repeating: CGRect.zero, count: lineRange.length) + let glyphRuns = CTLineGetGlyphRuns(line) as NSArray + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + if glyphCount == 0 { + continue + } + + var glyphs = [CGGlyph](repeating: 0, count: glyphCount) + CTRunGetGlyphs(run, CFRangeMake(0, glyphCount), &glyphs) + + var positions = [CGPoint](repeating: CGPoint.zero, count: glyphCount) + CTRunGetPositions(run, CFRangeMake(0, glyphCount), &positions) + + var stringIndices = [CFIndex](repeating: 0, count: glyphCount) + CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), &stringIndices) + + let attributes = CTRunGetAttributes(run) as NSDictionary + guard let font = attributes[kCTFontAttributeName] as! CTFont? else { + continue + } + + var boundingRects = [CGRect](repeating: CGRect.zero, count: glyphCount) + CTFontGetBoundingRectsForGlyphs(font, .default, &glyphs, &boundingRects, glyphCount) + + for i in 0 ..< glyphCount { + let charIndex = stringIndices[i] - lineRange.location + if charIndex >= 0 && charIndex < lineRange.length { + let pos = positions[i] + let bbox = boundingRects[i] + rects[charIndex] = CGRect( + x: pos.x + bbox.origin.x, + y: pos.y + bbox.origin.y, + width: bbox.width, + height: bbox.height + ) + } + } + } + for emoji in pendingEmoji { + let localIndex = emoji.range.location - lineRange.location + if localIndex >= 0 && localIndex < rects.count { + let x = v2LeadingOffsetForRange(line, range: emoji.range) + // characterRects are baseline-relative (positive-up). The emoji cell is now + // centered on the font line box (see frame loop), so in baseline-relative + // coords it spans [fontLineHeight/2 − size/2, fontLineHeight/2 + size/2]. + // Width feeds the reveal cost map; maxY feeds the reveal-mask y conversion in + // the renderer (lineAscent − maxY), keeping the mask tracking the centered cell. + rects[localIndex] = CGRect(x: x, y: fontLineHeight / 2.0 - emoji.size / 2.0, width: emoji.size, height: emoji.size) + } + } + for image in pendingImages { + let localIndex = image.range.location - lineRange.location + if localIndex >= 0 && localIndex < rects.count { + let x = v2LeadingOffsetForRange(line, range: image.range) + // Image cell is centered on the font line box (see frame loop). Baseline-relative + // cell spans [fontLineHeight/2 − height/2, fontLineHeight/2 + height/2]; the full + // width feeds the reveal cost map so the streaming cursor is charged the image's + // width when crossing it — same as an emoji cell. + rects[localIndex] = CGRect(x: x, y: fontLineHeight / 2.0 - image.size.height / 2.0, width: image.size.width, height: image.size.height) + } + } + lineCharacterRects = rects + } else { + lineCharacterRects = nil + } + let textLine = InstantPageTextLine(line: line, range: lineRange, frame: CGRect(x: workingLineOrigin.x, y: workingLineOrigin.y, width: lineWidth, height: height), strikethroughItems: strikethroughItems, underlineItems: underlineItems, markedItems: markedItems, spoilerItems: spoilerItems, imageItems: lineImageItems, formulaItems: lineFormulaItems, emojiItems: lineEmojiItems, anchorItems: anchorItems, isRTL: isRTL, characterRects: lineCharacterRects) + + lines.append(textLine) + imageItems.append(contentsOf: lineImageItems) + if !lineFormulaItems.isEmpty { + hasFormulaItems = true + } + + if lineWidth > maxLineWidth { + maxLineWidth = lineWidth + } + + workingLineOrigin.x = 0.0 + workingLineOrigin.y += lineAscent + fontLineSpacing + extraDescent + currentLineOrigin = workingLineOrigin + + lastIndex += lineCharacterCount + + if stop { + break + } + } else { + break + } + } + + var height: CGFloat = 0.0 + if !lines.isEmpty && !(string.string == "\u{200b}" && hasAnchors) { + // + fontDescentBelowBaseline: contain the last line's descender below its baseline, so + // (with the topInset shift) a single-line item measures exactly A + D = true font height. + height = lines.last!.frame.maxY + extraDescent + fontDescentBelowBaseline + } + + var textWidth = boundingWidth + // Shrinking the box to content width anchors it at the leading `offset.x`, which makes any + // non-leading display-time alignment a no-op (the block stays pinned to the leading edge and + // only redistributes internally). Only `.natural` is leading-anchored; `.right` (RTL text) + // and `.center` (pull quotes) must keep the full bounding width so `v2FrameForLine` lands + // each line at the true trailing / centered position. `.right`/`.center` reach here only via + // RTL text and pull quotes respectively, so plain LTR `.natural` body text is unaffected. + if fitToWidth && alignment == .natural { + textWidth = maxLineWidth + } + if (!imageItems.isEmpty || hasFormulaItems) && maxLineWidth > boundingWidth + 10.0 { + textWidth = maxLineWidth + } + + let textItem = InstantPageTextItem(frame: CGRect(x: 0.0, y: 0.0, width: textWidth, height: height), attributedString: string, alignment: alignment, opaqueBackground: opaqueBackground, lines: lines) + textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y) + var items: [InstantPageV2LaidOutItem] = [] + if imageItems.isEmpty || string.length > 1 { + items.append(.text(InstantPageV2TextItem(frame: textItem.frame, textItem: textItem))) + } + + var topInset: CGFloat = 0.0 + var bottomInset: CGFloat = 0.0 + var additionalItems: [InstantPageV2LaidOutItem] = [] + let effectiveOffset = offset + for line in textItem.lines { + let lineFrame = v2FrameForLine(line, boundingWidth: boundingWidth, alignment: alignment) + // Inline images (RichText.image) are NOT emitted as top-level items here. They are + // discovered at view-update time by InstantPageV2View.updateInlineImages(), which + // walks each text view's `line.imageItems` and creates an InstantPageV2InlineImageView + // attached to the text view's imageContainerView. The pop-in animation reuses the + // emoji-style ownership model. See the inline-image design doc: + // docs/superpowers/specs/2026-05-28-instantpage-v2-inline-image-design.md. + for formulaItem in line.formulaItems { + let formulaFrame = formulaItem.frame.offsetBy(dx: lineFrame.minX + effectiveOffset.x, dy: effectiveOffset.y) + let item = InstantPageV2FormulaItem( + frame: formulaFrame, + attachment: formulaItem.attachment, + isScrollable: false, + imageFrame: CGRect(origin: .zero, size: formulaFrame.size), + scrollContentSize: formulaFrame.size + ) + additionalItems.append(.formula(item)) + if formulaFrame.minY < topInset { topInset = formulaFrame.minY } + if formulaFrame.maxY > height { bottomInset = max(bottomInset, formulaFrame.maxY - height) } + } + } + + let _ = topInset + let _ = bottomInset + items.append(contentsOf: additionalItems) + + return (textItem, items, textItem.frame.size) +} diff --git a/submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift b/submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift new file mode 100644 index 0000000000..89ec1c04f3 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2MediaViews.swift @@ -0,0 +1,495 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import AccountContext +import TelegramCore +import TelegramPresentationData +import GalleryUI +import UniversalMediaPlayer + +// Mutable weak box: lets a wrapper hand its `openMedia` closure a back-reference to itself, +// filled in after `super.init` (when `self` becomes usable). SwiftSignalKit's `Weak` requires +// a non-optional value at init time, so it can't be used here. +private final class WrapperRef { + weak var view: UIView? +} + +// MARK: - Shared media node factory + +// Hosts a V1 `InstantPageImageNode` inside a V2 UIView wrapper. The caller sizes its own +// frame from `item.frame` and adds the returned node's view as a subview. +func makeMediaWrapper( + frame: CGRect, + media: InstantPageMedia, + webPage: TelegramMediaWebpage, + attributes: [InstantPageImageAttribute], + renderContext: InstantPageV2RenderContext, + theme: InstantPageTheme, + openMedia: @escaping (InstantPageMedia) -> Void, + longPressMedia: @escaping (InstantPageMedia) -> Void +) -> InstantPageImageNode { + let imageNode = InstantPageImageNode( + context: renderContext.context, + sourceLocation: renderContext.sourceLocation, + theme: theme, + webPage: webPage, + media: media, + attributes: attributes, + interactive: true, + roundCorners: false, + fit: false, + openMedia: openMedia, + longPressMedia: longPressMedia, + activatePinchPreview: nil, + pinchPreviewFinished: nil, + imageReferenceForMedia: renderContext.imageReference, + fileReferenceForMedia: renderContext.fileReference, + getPreloadedResource: { _ in nil } + ) + imageNode.frame = CGRect(origin: .zero, size: frame.size) + return imageNode +} + +// Walks up the superview chain from `start` to find the nearest enclosing `InstantPageV2View`. +private func findEnclosingV2View(from start: UIView?) -> InstantPageV2View? { + var view: UIView? = start + while view != nil { + if let v2 = view as? InstantPageV2View { + return v2 + } + view = view?.superview + } + return nil +} + +// Registers `wrapper` in the root V2View's `mediaRegistry` under `mediaIndex`. The root is +// reached by walking up the superview chain to the nearest `InstantPageV2View`, then walking +// its `rootMediaRegistryHost` chain transitively (nested details blocks can leave an inner +// body's host pointing at an intermediate body — see `trueRegistryRoot`). No-op if the wrapper +// isn't yet attached to a V2View ancestor. +func registerInRootRegistry(wrapper: UIView, mediaIndex: Int) { + guard let v2 = findEnclosingV2View(from: wrapper.superview) else { return } + v2.trueRegistryRoot.mediaRegistry[mediaIndex] = Weak(wrapper) +} + +// Routes a tap on `tapped` through `openInstantPageMedia`, sourcing sibling medias from the +// root V2View's `currentLayout`. No-op if the wrapper isn't currently in a V2View tree. +func handleOpenMediaTap( + tapped: InstantPageMedia, + wrapper: UIView, + renderContext: InstantPageV2RenderContext +) { + guard let v2 = findEnclosingV2View(from: wrapper.superview) else { return } + let root = v2.trueRegistryRoot + guard let layout = root.currentLayout else { return } + openInstantPageMedia( + media: tapped, + allMedias: layout.allMedias(), + webPage: renderContext.webpage, + context: renderContext.context, + userLocation: renderContext.sourceLocation.userLocation, + present: renderContext.present, + push: renderContext.push, + openUrl: renderContext.openUrl, + baseNavigationController: renderContext.baseNavigationController, + transitionArgsForMedia: { [weak root] tappedSibling -> GalleryTransitionArguments? in + guard let root else { return nil } + return root.transitionArgsFor(tappedSibling, addToTransitionSurface: { [weak root] view in + root?.superview?.addSubview(view) + }) + }, + hiddenMediaCallback: { [weak root] hidden in + root?.applyHiddenMedia(hidden) + } + ) +} + +// MARK: - Concrete wrapper classes + +final class InstantPageV2MediaImageView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaImageItem + var itemFrame: CGRect { return self.item.frame } + let wrappedNode: InstantPageImageNode + + init(item: InstantPageV2MediaImageItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + + // The tap closure can't capture `[weak self]` before `super.init`, so we route through + // a `WrapperRef` box that gets filled in after `super.init`. The box's weak storage + // breaks the wrapper → wrappedNode → closure → wrapper retain cycle that would otherwise + // form (the wrapper owns wrappedNode, which owns the closure, which holds the wrapper). + let wrapperRef = WrapperRef() + let renderContextRef = renderContext + let openMedia: (InstantPageMedia) -> Void = { tapped in + guard let wrapper = wrapperRef.view else { return } + handleOpenMediaTap(tapped: tapped, wrapper: wrapper, renderContext: renderContextRef) + } + self.wrappedNode = makeMediaWrapper( + frame: item.frame, + media: item.media, + webPage: item.webPage, + attributes: item.attributes, + renderContext: renderContext, + theme: theme, + openMedia: openMedia, + longPressMedia: { _ in } + ) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.wrappedNode.view) // structural + wrapperRef.view = self // structural: back-reference for the openMedia closure + self.update(item: item, theme: theme, renderContext: renderContext) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func didMoveToWindow() { + super.didMoveToWindow() + guard self.window != nil else { return } + registerInRootRegistry(wrapper: self, mediaIndex: self.item.media.index) + } + + override func layoutSubviews() { + super.layoutSubviews() + self.wrappedNode.frame = self.bounds + } + + func update(item: InstantPageV2MediaImageItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + self.item = item + self.layer.cornerRadius = item.cornerRadius + self.clipsToBounds = item.cornerRadius > 0.0 + let strings = renderContext.context.sharedContext.currentPresentationData.with { $0 }.strings + self.wrappedNode.update(strings: strings, theme: theme) + } + + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return self.wrappedNode.transitionNode(media: media) + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + self.wrappedNode.updateHiddenMedia(media: media) + } +} + +final class InstantPageV2MediaVideoView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaVideoItem + var itemFrame: CGRect { return self.item.frame } + let wrappedNode: InstantPageImageNode + + init(item: InstantPageV2MediaVideoItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + + let wrapperRef = WrapperRef() + let renderContextRef = renderContext + let openMedia: (InstantPageMedia) -> Void = { tapped in + guard let wrapper = wrapperRef.view else { return } + handleOpenMediaTap(tapped: tapped, wrapper: wrapper, renderContext: renderContextRef) + } + self.wrappedNode = makeMediaWrapper( + frame: item.frame, + media: item.media, + webPage: item.webPage, + attributes: item.attributes, + renderContext: renderContext, + theme: theme, + openMedia: openMedia, + longPressMedia: { _ in } + ) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.wrappedNode.view) // structural + wrapperRef.view = self // structural: back-reference for the openMedia closure + self.update(item: item, theme: theme, renderContext: renderContext) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func didMoveToWindow() { + super.didMoveToWindow() + guard self.window != nil else { return } + registerInRootRegistry(wrapper: self, mediaIndex: self.item.media.index) + } + + override func layoutSubviews() { + super.layoutSubviews() + self.wrappedNode.frame = self.bounds + } + + func update(item: InstantPageV2MediaVideoItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + self.item = item + self.layer.cornerRadius = item.cornerRadius + self.clipsToBounds = item.cornerRadius > 0.0 + let strings = renderContext.context.sharedContext.currentPresentationData.with { $0 }.strings + self.wrappedNode.update(strings: strings, theme: theme) + } + + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return self.wrappedNode.transitionNode(media: media) + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + self.wrappedNode.updateHiddenMedia(media: media) + } +} + +final class InstantPageV2MediaMapView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaMapItem + var itemFrame: CGRect { return self.item.frame } + let wrappedNode: InstantPageImageNode + + init(item: InstantPageV2MediaMapItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + + let wrapperRef = WrapperRef() + let renderContextRef = renderContext + let openMedia: (InstantPageMedia) -> Void = { tapped in + guard let wrapper = wrapperRef.view else { return } + handleOpenMediaTap(tapped: tapped, wrapper: wrapper, renderContext: renderContextRef) + } + self.wrappedNode = makeMediaWrapper( + frame: item.frame, + media: item.media, + webPage: item.webPage, + attributes: item.attributes, + renderContext: renderContext, + theme: theme, + openMedia: openMedia, + longPressMedia: { _ in } + ) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.wrappedNode.view) // structural + wrapperRef.view = self // structural: back-reference for the openMedia closure + self.update(item: item, theme: theme, renderContext: renderContext) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func didMoveToWindow() { + super.didMoveToWindow() + guard self.window != nil else { return } + registerInRootRegistry(wrapper: self, mediaIndex: self.item.media.index) + } + + override func layoutSubviews() { + super.layoutSubviews() + self.wrappedNode.frame = self.bounds + } + + func update(item: InstantPageV2MediaMapItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + self.item = item + self.layer.cornerRadius = item.cornerRadius + self.clipsToBounds = item.cornerRadius > 0.0 + let strings = renderContext.context.sharedContext.currentPresentationData.with { $0 }.strings + self.wrappedNode.update(strings: strings, theme: theme) + } + + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return self.wrappedNode.transitionNode(media: media) + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + self.wrappedNode.updateHiddenMedia(media: media) + } +} + +final class InstantPageV2MediaCoverImageView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaCoverImageItem + var itemFrame: CGRect { return self.item.frame } + let wrappedNode: InstantPageImageNode + + init(item: InstantPageV2MediaCoverImageItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + + let wrapperRef = WrapperRef() + let renderContextRef = renderContext + let openMedia: (InstantPageMedia) -> Void = { tapped in + guard let wrapper = wrapperRef.view else { return } + handleOpenMediaTap(tapped: tapped, wrapper: wrapper, renderContext: renderContextRef) + } + self.wrappedNode = makeMediaWrapper( + frame: item.frame, + media: item.media, + webPage: item.webPage, + attributes: item.attributes, + renderContext: renderContext, + theme: theme, + openMedia: openMedia, + longPressMedia: { _ in } + ) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.wrappedNode.view) // structural + wrapperRef.view = self // structural: back-reference for the openMedia closure + self.update(item: item, theme: theme, renderContext: renderContext) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func didMoveToWindow() { + super.didMoveToWindow() + guard self.window != nil else { return } + registerInRootRegistry(wrapper: self, mediaIndex: self.item.media.index) + } + + override func layoutSubviews() { + super.layoutSubviews() + self.wrappedNode.frame = self.bounds + } + + func update(item: InstantPageV2MediaCoverImageItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + self.item = item + self.layer.cornerRadius = item.cornerRadius + self.clipsToBounds = item.cornerRadius > 0.0 + let strings = renderContext.context.sharedContext.currentPresentationData.with { $0 }.strings + self.wrappedNode.update(strings: strings, theme: theme) + } + + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return self.wrappedNode.transitionNode(media: media) + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + self.wrappedNode.updateHiddenMedia(media: media) + } +} + +// Sets up shared-media playback for an audio tap. Mirrors V1's +// `InstantPageControllerNode.openMedia(_:)` audio branch: collect the page's voice/music +// medias from the root V2View's current layout, build an `InstantPageMediaPlaylist` keyed by +// `playlistId`, and start playback. No-op if the wrapper isn't currently in a V2View tree. +func handleOpenAudioTap( + tapped: InstantPageMedia, + wrapper: UIView, + renderContext: InstantPageV2RenderContext, + playlistId: InstantPageMediaPlaylistId +) { + guard case let .file(tappedFile) = tapped.media, tappedFile.isVoice || tappedFile.isMusic else { return } + guard let v2 = findEnclosingV2View(from: wrapper.superview) else { return } + let root = v2.trueRegistryRoot + guard let layout = root.currentLayout else { return } + + var audioMedias: [InstantPageMedia] = [] + var initialIndex = 0 + for media in layout.allMedias() { + if case let .file(file) = media.media, (file.isVoice || file.isMusic) { + if media.index == tapped.index { + initialIndex = audioMedias.count + } + audioMedias.append(media) + } + } + + let playlist = InstantPageMediaPlaylist( + playlistId: playlistId, + webPage: renderContext.webpage, + messageReference: renderContext.message, + items: audioMedias, + initialItemIndex: initialIndex + ) + renderContext.context.sharedContext.mediaManager.setPlaylist( + (renderContext.context, playlist), + type: tappedFile.isVoice ? .voice : .music, + control: .playback(.play) + ) +} + +final class InstantPageV2MediaAudioView: UIView, InstantPageItemView { + private(set) var item: InstantPageV2MediaAudioItem + var itemFrame: CGRect { return self.item.frame } + private let audioNode: InstantPageV2AudioContentNode + + init(item: InstantPageV2MediaAudioItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + + // `.richMessage(messageId)` isolates playback state per chat message; the preview (no + // message) falls back to the webpage-keyed id (only one preview is ever on screen). + let playlistId: InstantPageMediaPlaylistId + if let messageId = renderContext.message?.id { + playlistId = .richMessage(messageId: messageId) + } else { + playlistId = .instantPage(webpageId: renderContext.webpage.webpageId) + } + + let wrapperRef = WrapperRef() + let renderContextRef = renderContext + let itemMedia = item.media + + let presentationData = renderContext.context.sharedContext.currentPresentationData.with { $0 } + let incoming = renderContext.message?.isIncoming == true + let audioFile: TelegramMediaFile + if case let .file(f) = item.media.media { audioFile = f } else { audioFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/mpeg", size: nil, attributes: [], alternativeRepresentations: []) } + self.audioNode = InstantPageV2AudioContentNode(context: renderContext.context, message: renderContext.message, file: audioFile, incoming: incoming, presentationData: presentationData) + + super.init(frame: item.frame) + self.backgroundColor = .clear // structural + self.addSubview(self.audioNode.view) // structural + wrapperRef.view = self // structural: back-reference for the play closure + + self.audioNode.play = { + guard let wrapper = wrapperRef.view else { return } + handleOpenAudioTap(tapped: itemMedia, wrapper: wrapper, renderContext: renderContextRef, playlistId: playlistId) + } + + let fetchContext = renderContext.context + let fetchMessage = renderContext.message + let fetchMedia = item.media + self.audioNode.fetch = { + guard case let .file(file) = fetchMedia.media, let message = fetchMessage, let messageId = message.id else { return } + // Route through the fetch manager (not freeMediaFileInteractiveFetched) so the + // messageMediaFileStatus signal — which keys progress off the fetch manager's + // `hasEntry` — surfaces .Fetching, letting the overlay show the animated ring. + let _ = messageMediaFileInteractiveFetched(fetchManager: fetchContext.fetchManager, messageId: messageId, messageReference: message, file: file, userInitiated: true, priority: .userInitiated).startStandalone() + } + + let mediaForPlayback = item.media + let playlistTypeForPlayback: MediaManagerPlayerType + if case let .file(f) = mediaForPlayback.media, f.isVoice { playlistTypeForPlayback = .voice } else { playlistTypeForPlayback = .music } + let contextForPlayback = renderContext.context + + self.audioNode.togglePlayPause = { + contextForPlayback.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: playlistTypeForPlayback) + } + + let stateSignal = contextForPlayback.sharedContext.mediaManager.filteredPlaylistState(accountId: contextForPlayback.account.id, playlistId: playlistId, itemId: InstantPageMediaPlaylistItemId(index: mediaForPlayback.index), type: playlistTypeForPlayback) + self.audioNode.setPlaybackStatusSignal(stateSignal) + + self.update(item: item, theme: theme, renderContext: renderContext) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + override func layoutSubviews() { + super.layoutSubviews() + self.audioNode.frame = self.bounds + self.audioNode.updateLayout(width: self.bounds.width) + } + + func update(item: InstantPageV2MediaAudioItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + self.item = item + let presentationData = renderContext.context.sharedContext.currentPresentationData.with { $0 } + let incoming = renderContext.message?.isIncoming == true + self.audioNode.updatePresentationData(presentationData, incoming: incoming) + self.audioNode.updateLayout(width: self.bounds.width) + } + + // Audio is not a gallery item: explicit nil/no-op witnesses (per the existing pattern of + // explicit per-class witnesses rather than a shared protocol-extension override). + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + return nil + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + } +} diff --git a/submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift b/submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift new file mode 100644 index 0000000000..caae5152be --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2RevealCost.swift @@ -0,0 +1,570 @@ +import Foundation +import UIKit +import QuartzCore + +/// Opaque per-layout reveal-cost description. Public surface is just `total` (the running +/// length used to drive `TextRevealController`); internals are coupled to `applyReveal`'s +/// view-tree walk (same module, same file). +public struct InstantPageV2RevealCostMap { + public let total: Int + fileprivate let topLevelEntries: [Entry] +} + +extension InstantPageV2RevealCostMap { + fileprivate enum Entry { + case text(start: Int, end: Int) + case nonText(start: Int, end: Int) + case thinking(start: Int) + case details(start: Int, end: Int, body: InstantPageV2RevealCostMap?) + case codeBlock(start: Int, end: Int) + case table(start: Int, end: Int, rows: [TableRow], title: InstantPageV2RevealCostMap?) + } + + fileprivate struct TableRow { + let startCount: Int + let cells: [InstantPageV2RevealCostMap?] + } +} + +// Reveal cost is in width units (points along the reading direction). The unit is uniform +// across all item kinds, so the streaming reveal pace ("points per second") is visually +// consistent — wide tables and media take proportionally longer than narrow inline text. +// +// For text items, the cost is the sum of glyph ink widths across all lines (the total ink +// extent in reading direction). For non-text items, the cost is `item.frame.width`. Zero-width +// items (e.g. anchors) contribute 0 and are revealed instantly when the cursor reaches them. + +private func textInkWidth(_ textItem: InstantPageTextItem) -> Int { + var total: CGFloat = 0.0 + for line in textItem.lines { + if let chars = line.characterRects { + for r in chars where !r.isEmpty { + total += r.width + } + } else { + total += line.frame.width + } + } + return max(0, Int(total.rounded())) +} + +private func itemWidthCost(_ item: InstantPageV2LaidOutItem) -> Int { + return max(0, Int(item.frame.width.rounded())) +} + +/// Convert a width-budget (cost in points) into a character count for a text item. +/// Walks the item's lines and per-glyph rects accumulating widths until the budget runs out; +/// returns the character index of the first glyph not yet fully covered. Used to bridge the +/// width-based cost map to the per-character mask API on `InstantPageV2TextView`. +private func charCountForWidthBudget(textItem: InstantPageTextItem, widthBudget: Int) -> Int { + var remaining = CGFloat(widthBudget) + var count = 0 + for line in textItem.lines { + if remaining <= 0 { break } + guard let chars = line.characterRects else { + let lineWidth = line.frame.width + if remaining >= lineWidth { + count += line.range.length + remaining -= lineWidth + } else { + let frac = remaining / max(lineWidth, 0.001) + count += Int((CGFloat(line.range.length) * frac).rounded(.down)) + remaining = 0 + } + continue + } + var lineDone = false + for r in chars { + let w = max(0, r.width) + if remaining >= w { + remaining -= w + count += 1 + } else { + lineDone = true + break + } + } + if lineDone { + break + } + } + return count +} + +public extension InstantPageV2Layout { + func computeRevealCostMap() -> InstantPageV2RevealCostMap { + var cursor = 0 + let entries = computeEntries(items: self.items, cursor: &cursor) + return InstantPageV2RevealCostMap(total: cursor, topLevelEntries: entries) + } +} + +public extension InstantPageV2RevealCostMap { + /// Size that the layout would occupy if only the items revealed up to `revealedCount` + /// were visible. Width is the layout's full content width (we don't shrink horizontally, + /// since text wraps to the layout-decided width and shrinking would cause visual jitter). + /// Height is the bottom y of the revealed prefix: + /// + /// * Fully-revealed items contribute their full frame.maxY. + /// * Partially-revealed text items contribute the bottom y of their last revealed line. + /// * Partially-revealed tables contribute up to the last revealed row's bottom y. + /// * Non-text items (formula, media, etc.) contribute only once revealedCount >= entry.endCount. + /// * `details` / `codeBlock`: the whole container appears as soon as revealedCount > entry.start + /// (background and chrome pop in atomically, then inner text reveals char-by-char). + /// + /// Used by the rich-data bubble to size itself to the revealed prefix during AI streaming, + /// mirroring TextBubble's `clippedGlyphCountLayout = textLayout.layoutForCharacterCount(...)`. + func revealedContentSize(revealedCount: Int, layout: InstantPageV2Layout) -> CGSize { + let bounds = computeRevealedBounds(items: layout.items, entries: self.topLevelEntries, revealedCount: revealedCount) + if bounds.maxY <= 0.0 { + return CGSize(width: layout.contentSize.width, height: 0.0) + } + // The full layout reserves a closing spacing after the last top-level block (see + // `closingSpacing` in layoutBlockSequence). Mirror that so a partially-revealed + // bubble has the same bottom padding as a fully-revealed one — otherwise the last + // revealed line sits flush against the bubble's bottom edge. + let lastItemMaxY = layout.items.map { $0.frame.maxY }.max() ?? 0.0 + let closingPad = max(0.0, layout.contentSize.height - lastItemMaxY) + return CGSize(width: layout.contentSize.width, height: bounds.maxY + closingPad) + } + + /// Returns the maxY of revealed items in `layout` coords (no closing pad). Use this to + /// size the InstantPageV2View itself so its content never overflows past the revealed + /// extent — the bubble's closing pad sits in containerNode space *outside* the pageView, + /// not inside it (where unrevealed items would otherwise draw). + func revealedItemsMaxY(revealedCount: Int, layout: InstantPageV2Layout) -> CGFloat { + let bounds = computeRevealedBounds(items: layout.items, entries: self.topLevelEntries, revealedCount: revealedCount) + return max(0.0, bounds.maxY) + } +} + +private func computeRevealedBounds(items: [InstantPageV2LaidOutItem], entries: [InstantPageV2RevealCostMap.Entry], revealedCount: Int) -> CGRect { + var bounds: CGRect = .zero + var initialized = false + let n = min(items.count, entries.count) + for i in 0 ..< n { + guard let extent = revealedExtent(entry: entries[i], item: items[i], revealedCount: revealedCount) else { + continue + } + if initialized { + bounds = bounds.union(extent) + } else { + bounds = extent + initialized = true + } + } + return bounds +} + +private func revealedExtent(entry: InstantPageV2RevealCostMap.Entry, item: InstantPageV2LaidOutItem, revealedCount: Int) -> CGRect? { + switch entry { + case let .text(start, end): + if revealedCount <= start { return nil } + if revealedCount >= end { return item.frame } + if case let .text(textItem) = item { + return revealedTextExtent(textItem: textItem.textItem, itemFrame: item.frame, localRevealedCount: revealedCount - start) + } + return item.frame + case let .nonText(start, end): + let _ = start + if revealedCount < end { return nil } + return item.frame + case let .thinking(start): + // Revealed (and contributes its full height) once the cursor reaches its index position. + // A top thinking block (start == 0) is revealed from the first frame. + if revealedCount < start { return nil } + return item.frame + case let .codeBlock(start, _): + if revealedCount <= start { return nil } + // Block backdrop appears atomically once revealing reaches the block; inner text + // is char-revealed inside. Bubble height grows by the full block height in one + // step rather than mid-block. + return item.frame + case let .details(start, _, body): + if revealedCount <= start { return nil } + if case let .details(detailsItem) = item { + // Title region appears as soon as the details block is reached. + var bounds = CGRect( + x: detailsItem.frame.minX, + y: detailsItem.frame.minY, + width: detailsItem.frame.width, + height: detailsItem.titleFrame.maxY + ) + if let body, let innerLayout = detailsItem.innerLayout, detailsItem.isExpanded { + let bodyBounds = computeRevealedBounds(items: innerLayout.items, entries: body.topLevelEntries, revealedCount: revealedCount) + if !bodyBounds.isEmpty { + let bodyAbs = bodyBounds.offsetBy(dx: detailsItem.frame.minX, dy: detailsItem.frame.minY + detailsItem.titleFrame.maxY) + bounds = bounds.union(bodyAbs) + } + } + return bounds + } + return item.frame + case let .table(start, end, rows, _): + if revealedCount <= start { return nil } + if revealedCount >= end { return item.frame } + if case let .table(tableItem) = item { + let gridOffsetY = tableItem.titleFrame?.height ?? 0.0 + var lastRevealedRowIndex: Int? = nil + for (rowIdx, row) in rows.enumerated() { + if revealedCount >= row.startCount { + lastRevealedRowIndex = rowIdx + } else { + break + } + } + let groupedByY = Dictionary(grouping: tableItem.cells, by: { $0.frame.minY }) + let sortedRowYs = groupedByY.keys.sorted() + let heightInTable: CGFloat + if let idx = lastRevealedRowIndex, idx < sortedRowYs.count { + let rowY = sortedRowYs[idx] + let cellsInRow = groupedByY[rowY] ?? [] + let rowMaxY = cellsInRow.map { $0.frame.maxY }.max() ?? 0.0 + heightInTable = gridOffsetY + rowMaxY + } else { + heightInTable = gridOffsetY + } + return CGRect( + x: tableItem.frame.minX, + y: tableItem.frame.minY, + width: tableItem.frame.width, + height: heightInTable + ) + } + return item.frame + } +} + +/// Returns the y-extent (= bottom of the last revealed line) of a text item given a +/// width-based local cursor. Walks lines summing per-line ink widths; the last line whose +/// preceding-line-widths-sum is < cursor is the last revealed line. +private func revealedTextExtent(textItem: InstantPageTextItem, itemFrame: CGRect, localRevealedCount: Int) -> CGRect { + var remaining = CGFloat(localRevealedCount) + var lastRevealedLineMaxY: CGFloat = 0.0 + for line in textItem.lines { + if remaining <= 0 { break } + lastRevealedLineMaxY = max(lastRevealedLineMaxY, line.frame.maxY) + let lineWidth: CGFloat + if let chars = line.characterRects { + var sum: CGFloat = 0 + for r in chars where !r.isEmpty { + sum += r.width + } + lineWidth = sum + } else { + lineWidth = line.frame.width + } + remaining -= lineWidth + } + return CGRect(x: itemFrame.minX, y: itemFrame.minY, width: itemFrame.width, height: lastRevealedLineMaxY) +} + +private func computeEntries(items: [InstantPageV2LaidOutItem], cursor: inout Int) -> [InstantPageV2RevealCostMap.Entry] { + var entries: [InstantPageV2RevealCostMap.Entry] = [] + for item in items { + switch item { + case let .text(text): + let start = cursor + cursor += textInkWidth(text.textItem) + entries.append(.text(start: start, end: cursor)) + case let .codeBlock(block): + let start = cursor + cursor += textInkWidth(block.textItem) + entries.append(.codeBlock(start: start, end: cursor)) + case let .details(details): + let start = cursor + cursor += textInkWidth(details.titleTextItem) + var body: InstantPageV2RevealCostMap? = nil + if details.isExpanded, let inner = details.innerLayout { + var innerCursor = cursor + let innerEntries = computeEntries(items: inner.items, cursor: &innerCursor) + let innerTotal = innerCursor - cursor + cursor = innerCursor + body = InstantPageV2RevealCostMap(total: innerTotal, topLevelEntries: innerEntries) + } + entries.append(.details(start: start, end: cursor, body: body)) + case let .table(table): + let start = cursor + + var titleMap: InstantPageV2RevealCostMap? = nil + if let titleLayout = table.titleSubLayout { + var titleCursor = cursor + let titleEntries = computeEntries(items: titleLayout.items, cursor: &titleCursor) + let titleTotal = titleCursor - cursor + cursor = titleCursor + titleMap = InstantPageV2RevealCostMap(total: titleTotal, topLevelEntries: titleEntries) + } + + // Group cells by frame.minY (rows in top-to-bottom order); within each row, left-to-right. + let groupedByY = Dictionary(grouping: table.cells, by: { $0.frame.minY }) + let sortedRowYs = groupedByY.keys.sorted() + + var rows: [InstantPageV2RevealCostMap.TableRow] = [] + for rowY in sortedRowYs { + let cellsInRow = (groupedByY[rowY] ?? []).sorted(by: { $0.frame.minX < $1.frame.minX }) + let rowStart = cursor + var cellMaps: [InstantPageV2RevealCostMap?] = [] + for cell in cellsInRow { + // Each cell consumes at least its frame.width worth of cursor advance, + // even if its inner text ink width is smaller (or it has no subLayout + // at all). Without this floor, narrow- or empty-cell tables ran through + // the cursor much faster than their visual width warrants — a 3-column + // table of "1"/"2"/"3" costs ~30pt while occupying ~200pt visually. + // Text inside a cell still char-reveals against its own ink widths; the + // "extra" cost (cell width − inner ink) is filler time during which the + // cell's text is fully revealed and the cursor is moving through padding + // before reaching the next cell. + let cellWidthFloor = max(0, Int(cell.frame.width.rounded())) + if let subLayout = cell.subLayout { + var cellCursor = cursor + let cellEntries = computeEntries(items: subLayout.items, cursor: &cellCursor) + let cellInnerCost = cellCursor - cursor + cursor += max(cellInnerCost, cellWidthFloor) + cellMaps.append(InstantPageV2RevealCostMap(total: cellInnerCost, topLevelEntries: cellEntries)) + } else { + cellMaps.append(nil) + cursor += cellWidthFloor + } + } + rows.append(InstantPageV2RevealCostMap.TableRow(startCount: rowStart, cells: cellMaps)) + } + entries.append(.table(start: start, end: cursor, rows: rows, title: titleMap)) + case .thinking: + // Zero cost: do NOT advance the cursor. This is the linchpin — answer-content cursor + // positions are identical whether or not thinking blocks are present, so adding/ + // removing a thinking block never jumps the answer's reveal position. + entries.append(.thinking(start: cursor)) + case .formula, .mediaImage, .mediaVideo, .mediaMap, .mediaCoverImage, .mediaAudio, .mediaPlaceholder, .slideshow, + .divider, .listMarker, .blockQuoteBar, .shape, .anchor: + let start = cursor + cursor += itemWidthCost(item) + entries.append(.nonText(start: start, end: cursor)) + } + } + return entries +} + +public extension InstantPageV2View { + /// Push reveal state into the V2 view tree. + /// + /// - `revealedCount == nil` (and `costMap == nil`): clear all reveal state, removing + /// masks and showing every item fully. + /// - Otherwise: walk `self.itemViews` alongside the cost map's `topLevelEntries`, updating + /// text views' per-character reveal, non-text views' visibility, table row masks, and + /// recursing into nested V2 views (details body, table cells, table title). + /// + /// `animated` controls non-text visibility cross-fades and table-row-mask growth; per-text-view + /// glyph counts are written directly (smoothness comes from the display-link tick frequency). + func applyReveal(revealedCount: Int?, costMap: InstantPageV2RevealCostMap?, animated: Bool) { + guard let costMap, let revealedCount else { + // Clear path + for view in self.itemViews { + clearRevealOn(view: view, animated: animated) + } + self.updateEmojiReveal(animated: animated) + self.updateImageReveal(animated: animated) + return + } + + // Walk views + entries in lockstep. The orderings of itemViews and topLevelEntries + // match because both are produced by walking layout.items in array order. + let entries = costMap.topLevelEntries + let entryCount = min(entries.count, self.itemViews.count) + for i in 0 ..< entryCount { + let view = self.itemViews[i] + let entry = entries[i] + applyRevealEntry(view: view, entry: entry, revealedCount: revealedCount, animated: animated) + } + // If counts mismatch (shouldn't in a clean apply right after update(layout:)), clear extras. + if self.itemViews.count > entryCount { + for i in entryCount ..< self.itemViews.count { + clearRevealOn(view: self.itemViews[i], animated: animated) + } + } + self.updateEmojiReveal(animated: animated) + self.updateImageReveal(animated: animated) + } +} + +private func applyRevealEntry(view: InstantPageItemView, entry: InstantPageV2RevealCostMap.Entry, + revealedCount: Int, animated: Bool) { + switch entry { + case let .text(start, end): + if let textView = view as? InstantPageV2TextView { + let localWidth = max(0, min(revealedCount - start, end - start)) + let charCount = (localWidth >= (end - start)) + ? textView.item.textItem.attributedString.length + : charCountForWidthBudget(textItem: textView.item.textItem, widthBudget: localWidth) + textView.updateRevealCharacterCount(value: charCount, animated: animated) + } + case let .codeBlock(start, end): + if let codeView = view as? InstantPageV2CodeBlockView { + let localWidth = max(0, min(revealedCount - start, end - start)) + let charCount = (localWidth >= (end - start)) + ? codeView.textView.item.textItem.attributedString.length + : charCountForWidthBudget(textItem: codeView.textView.item.textItem, widthBudget: localWidth) + codeView.textView.updateRevealCharacterCount(value: charCount, animated: animated) + let visible = revealedCount >= start + applyVisibility(view: codeView, visible: visible, animated: animated) + } + case let .details(start, end, body): + if let detailsView = view as? InstantPageV2DetailsView { + let titleTextItem = detailsView.titleTextView.item.textItem + let titleWidth = textInkWidth(titleTextItem) + let titleLocal = max(0, min(revealedCount - start, titleWidth)) + let charCount = (titleLocal >= titleWidth) + ? titleTextItem.attributedString.length + : charCountForWidthBudget(textItem: titleTextItem, widthBudget: titleLocal) + detailsView.titleTextView.updateRevealCharacterCount(value: charCount, animated: animated) + if let body, let bodyView = detailsView.bodyView { + bodyView.applyReveal(revealedCount: revealedCount, costMap: body, animated: animated) + } + let _ = end + } + case let .table(start, end, rows, title): + if let tableView = view as? InstantPageV2TableView { + applyTableReveal(tableView: tableView, start: start, end: end, rows: rows, title: title, + revealedCount: revealedCount, animated: animated) + } + case let .nonText(start, end): + let visible = revealedCount >= end + applyVisibility(view: view, visible: visible, animated: animated) + let _ = start + case let .thinking(start): + // Whole-block 0.12s alpha fade-in at the index position; inner text is drawn fully + // (never char-reveal-masked) — the shimmer is the only ongoing animation. + let visible = revealedCount >= start + applyVisibility(view: view, visible: visible, animated: animated) + } +} + +private func applyVisibility(view: UIView, visible: Bool, animated: Bool) { + let targetAlpha: CGFloat = visible ? 1.0 : 0.0 + if !animated { + view.alpha = targetAlpha + view.isHidden = !visible + return + } + if visible { + if view.alpha == 1.0 && !view.isHidden { return } + view.isHidden = false + let from = view.alpha + view.alpha = 1.0 + let anim = CABasicAnimation(keyPath: "opacity") + anim.fromValue = from + anim.toValue = 1.0 + anim.duration = 0.12 + view.layer.add(anim, forKey: "opacity") + } else { + if view.alpha == 0.0 || view.isHidden { return } + let from = view.alpha + view.alpha = 0.0 + let anim = CABasicAnimation(keyPath: "opacity") + anim.fromValue = from + anim.toValue = 0.0 + anim.duration = 0.12 + view.layer.add(anim, forKey: "opacity") + // Don't set isHidden so the animation can run; UIKit treats alpha==0 + hitTest as non-blocking. + } +} + +private func applyTableReveal(tableView: InstantPageV2TableView, start: Int, end: Int, + rows: [InstantPageV2RevealCostMap.TableRow], + title: InstantPageV2RevealCostMap?, + revealedCount: Int, animated: Bool) { + let _ = start + let _ = end + + // Title sub-view, if present, recurses with the title's own sub-map. + if let title, let titleSub = tableView.titleSubView { + titleSub.applyReveal(revealedCount: revealedCount, costMap: title, animated: animated) + } + + // Determine which row index the cursor is currently in (or past). + // A row is "revealed" once revealedCount >= rowStartCount. + var lastRevealedRowIndex: Int? = nil + for (idx, row) in rows.enumerated() { + if revealedCount >= row.startCount { + lastRevealedRowIndex = idx + } else { + break + } + } + + // Recurse into each cell sub-view (each non-nil cell map corresponds to a cellSubView, + // in the order they were registered when InstantPageV2TableView was constructed: + // cell-sub-views are added in iteration order of `item.cells` for cells with subLayout). + var cellSubViewIndex = 0 + for row in rows { + for cellMap in row.cells { + if let cellMap, cellSubViewIndex < tableView.cellSubViews.count { + let cellSubView = tableView.cellSubViews[cellSubViewIndex] + cellSubView.applyReveal(revealedCount: revealedCount, costMap: cellMap, animated: animated) + cellSubViewIndex += 1 + } + } + } + + // Apply the row-mask on contentView. The mask exposes rows [0 ... lastRevealedRowIndex]. + // Compute the y-bound by taking the max maxY across cells in those rows. + let gridOffsetY = tableView.item.titleFrame?.height ?? 0.0 + let maskHeight: CGFloat + if let lastRevealedRowIndex { + // Find the maxY among cells in rows [0 ... lastRevealedRowIndex]. Use the actual table item's + // cells (the layout-time data, not the cost-map's logical row groupings). + let groupedByY = Dictionary(grouping: tableView.item.cells, by: { $0.frame.minY }) + let sortedRowYs = groupedByY.keys.sorted() + let safeIndex = min(lastRevealedRowIndex, sortedRowYs.count - 1) + let rowY = sortedRowYs[safeIndex] + let cellsInRow = groupedByY[rowY] ?? [] + let rowMaxY = cellsInRow.map { $0.frame.maxY }.max() ?? 0.0 + maskHeight = gridOffsetY + rowMaxY + } else { + // No rows revealed yet — but the title (if any) is still visible above the grid. + maskHeight = (title != nil) ? gridOffsetY : 0.0 + } + + let maskFrame = CGRect(x: 0.0, y: 0.0, width: tableView.contentView.bounds.width, height: maskHeight) + + let maskLayer: CALayer + if let existing = tableView.contentView.layer.mask { + maskLayer = existing + } else { + let new = CALayer() + new.backgroundColor = UIColor.white.cgColor + tableView.contentView.layer.mask = new + maskLayer = new + } + + if animated && maskLayer.frame != maskFrame { + let anim = CABasicAnimation(keyPath: "bounds") + anim.fromValue = maskLayer.bounds + anim.toValue = CGRect(origin: .zero, size: maskFrame.size) + anim.duration = 0.12 + maskLayer.add(anim, forKey: "bounds") + } + maskLayer.frame = maskFrame +} + +private func clearRevealOn(view: InstantPageItemView, animated: Bool) { + if let textView = view as? InstantPageV2TextView { + textView.updateRevealCharacterCount(value: nil, animated: animated) + } + if let codeView = view as? InstantPageV2CodeBlockView { + codeView.textView.updateRevealCharacterCount(value: nil, animated: animated) + applyVisibility(view: codeView, visible: true, animated: animated) + } + if let detailsView = view as? InstantPageV2DetailsView { + detailsView.titleTextView.updateRevealCharacterCount(value: nil, animated: animated) + detailsView.bodyView?.applyReveal(revealedCount: nil, costMap: nil, animated: animated) + } + if let tableView = view as? InstantPageV2TableView { + tableView.titleSubView?.applyReveal(revealedCount: nil, costMap: nil, animated: animated) + for cell in tableView.cellSubViews { + cell.applyReveal(revealedCount: nil, costMap: nil, animated: animated) + } + tableView.contentView.layer.mask = nil + } + // Non-text item: ensure visible. + applyVisibility(view: view, visible: true, animated: animated) +} diff --git a/submodules/InstantPageUI/Sources/InstantPageV2SlideshowView.swift b/submodules/InstantPageUI/Sources/InstantPageV2SlideshowView.swift new file mode 100644 index 0000000000..25b963c0f4 --- /dev/null +++ b/submodules/InstantPageUI/Sources/InstantPageV2SlideshowView.swift @@ -0,0 +1,185 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import AccountContext +import TelegramCore +import TelegramPresentationData + +// A paged carousel for an `InstantPageBlock.slideshow`. Ports V1's InstantPageSlideshowNode / +// InstantPageSlideshowPagerNode (InstantPageSlideshowItemNode.swift), simplified to create all pages +// eagerly (slideshows are short; this avoids V1's central±1 index bookkeeping and makes the gallery +// transition source available for every page). Each image page hosts an `InstantPageImageNode` exactly +// like the static media views; non-image medias render an empty page (matches V1). +final class InstantPageV2SlideshowView: UIView, InstantPageItemView, UIScrollViewDelegate { + private(set) var item: InstantPageV2SlideshowItem + var itemFrame: CGRect { return self.item.frame } + + private let renderContext: InstantPageV2RenderContext + private var theme: InstantPageTheme + + private let scrollView: UIScrollView + private let pageControlNode: PageControlNode + + // One wrapper view per media (so page count stays aligned with the page control). `pageImageNodes` + // holds only the real image nodes; it may be shorter than `pageViews` if a non-image media appears + // (which `layoutSlideshow` currently filters out). Nothing relies on positional correspondence. + private var pageViews: [UIView] = [] + private var pageImageNodes: [InstantPageImageNode] = [] + + init(item: InstantPageV2SlideshowItem, renderContext: InstantPageV2RenderContext, theme: InstantPageTheme) { + self.item = item + self.renderContext = renderContext + self.theme = theme + self.scrollView = UIScrollView() + self.pageControlNode = PageControlNode(dotColor: .white, inactiveDotColor: UIColor(white: 1.0, alpha: 0.5)) + + super.init(frame: item.frame) + + self.backgroundColor = theme.panelSecondaryColor // structural + self.clipsToBounds = true // structural + + self.scrollView.disablesInteractiveTransitionGestureRecognizer = true + + self.scrollView.isPagingEnabled = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.scrollsToTop = false + if #available(iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + self.scrollView.delegate = self + self.addSubview(self.scrollView) // structural + + self.pageControlNode.isUserInteractionEnabled = false + self.addSubview(self.pageControlNode.view) // structural + + self.rebuildPages() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private func rebuildPages() { + for view in self.pageViews { + view.removeFromSuperview() + } + self.pageViews = [] + self.pageImageNodes = [] + + let renderContext = self.renderContext + // The image node owns this closure, and is owned (transitively) by self — capture weakly. + let openMedia: (InstantPageMedia) -> Void = { [weak self] tapped in + guard let self else { return } + handleOpenMediaTap(tapped: tapped, wrapper: self, renderContext: renderContext) + } + + for media in self.item.medias { + let pageView = UIView() + pageView.clipsToBounds = true + if case .image = media.media { + let node = makeMediaWrapper( + frame: CGRect(origin: .zero, size: self.item.frame.size), + media: media, + webPage: self.item.webPage, + attributes: [], + renderContext: self.renderContext, + theme: self.theme, + openMedia: openMedia, + longPressMedia: { _ in } + ) + pageView.addSubview(node.view) + self.pageImageNodes.append(node) + } + // Non-image medias (none in practice — layoutSlideshow filters to images) get an empty page + // to keep page indices aligned with the page control. + self.scrollView.addSubview(pageView) + self.pageViews.append(pageView) + } + + self.pageControlNode.pagesCount = self.item.medias.count + self.pageControlNode.setPage(0.0) + // Re-register media indices when rebuilding while already on-window (positional reuse with + // changed content); no-ops before the view is attached, where didMoveToWindow handles it. + self.registerMedias() + self.setNeedsLayout() + } + + private func registerMedias() { + guard self.window != nil else { return } + // Register under every contained media index so transitionArgsFor(media) can find this view. + for media in self.item.medias { + registerInRootRegistry(wrapper: self, mediaIndex: media.index) + } + } + + override func didMoveToWindow() { + super.didMoveToWindow() + self.registerMedias() + } + + override func layoutSubviews() { + super.layoutSubviews() + let size = self.bounds.size + guard size.width > 0.0, size.height > 0.0 else { return } + + self.scrollView.frame = CGRect(origin: .zero, size: size) + for (i, pageView) in self.pageViews.enumerated() { + pageView.frame = CGRect(x: CGFloat(i) * size.width, y: 0.0, width: size.width, height: size.height) + } + for node in self.pageImageNodes { + node.frame = CGRect(origin: .zero, size: size) + } + self.scrollView.contentSize = CGSize(width: CGFloat(self.pageViews.count) * size.width, height: size.height) + + self.pageControlNode.layer.transform = CATransform3DIdentity + self.pageControlNode.frame = CGRect(x: 0.0, y: size.height - 20.0, width: size.width, height: 20.0) + let maxWidth = size.width - 36.0 + let pageControlSize = self.pageControlNode.calculateSizeThatFits(size) + if pageControlSize.width > maxWidth, pageControlSize.width > 0.0 { + let scale = maxWidth / pageControlSize.width + self.pageControlNode.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let width = self.bounds.size.width + guard width > 0.0, !self.item.medias.isEmpty else { return } + let page = Int((scrollView.contentOffset.x + width / 2.0) / width) + let clamped = max(0, min(self.item.medias.count - 1, page)) + self.pageControlNode.setPage(CGFloat(clamped)) + } + + func update(item: InstantPageV2SlideshowItem, theme: InstantPageTheme, renderContext: InstantPageV2RenderContext) { + let mediasChanged = self.item.medias.map { $0.index } != item.medias.map { $0.index } + self.item = item + self.theme = theme + self.backgroundColor = theme.panelSecondaryColor + if mediasChanged { + self.rebuildPages() + } else { + let strings = renderContext.context.sharedContext.currentPresentationData.with { $0 }.strings + for node in self.pageImageNodes { + node.update(strings: strings, theme: theme) + } + } + self.setNeedsLayout() + } + + // MARK: InstantPageItemView gallery hooks + + func instantPageTransitionNode(for media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + for node in self.pageImageNodes { + if let transition = node.transitionNode(media: media) { + return transition + } + } + return nil + } + + func instantPageUpdateHiddenMedia(_ media: InstantPageMedia?) { + for node in self.pageImageNodes { + node.updateHiddenMedia(media: media) + } + } +} diff --git a/submodules/InviteLinksUI/BUILD b/submodules/InviteLinksUI/BUILD index bed130117c..f5c6a4fb6b 100644 --- a/submodules/InviteLinksUI/BUILD +++ b/submodules/InviteLinksUI/BUILD @@ -60,6 +60,7 @@ swift_library( "//submodules/PromptUI", "//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem", "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/ComponentFlow", "//submodules/Components/MultilineTextComponent", ], diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift index bde2b78d3a..d37959cda9 100644 --- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift @@ -711,7 +711,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese saveEnabled = true } - doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Save), style: .bold, enabled: !state.selectedPeerIds.isEmpty && saveEnabled, action: { + doneButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: !state.selectedPeerIds.isEmpty && saveEnabled, action: { applyChangesImpl?() }) } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index 79564f02cb..e0c4440b86 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -355,7 +355,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry { case let .timeHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .timePicker(_, value, enabled): - return ItemListInviteLinkTimeLimitItem(theme: presentationData.theme, strings: presentationData.strings, value: value, enabled: enabled, sectionId: self.section, updated: { value in + return ItemListInviteLinkTimeLimitItem(theme: presentationData.theme, systemStyle: .glass, strings: presentationData.strings, value: value, enabled: enabled, sectionId: self.section, updated: { value in arguments.updateState({ state in var updatedState = state if value != updatedState.time { @@ -418,7 +418,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry { case let .usageHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .usagePicker(_, dateTimeFormat, value, enabled): - return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, value: value, enabled: enabled, sectionId: self.section, updated: { value in + return ItemListInviteLinkUsageLimitItem(theme: presentationData.theme, systemStyle: .glass, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, value: value, enabled: enabled, sectionId: self.section, updated: { value in arguments.dismissInput() arguments.updateState({ state in var updatedState = state @@ -479,7 +479,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry { } } -private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, presentationData: PresentationData, configuration: StarsSubscriptionConfiguration) -> [InviteLinksEditEntry] { +private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, globalRequestApproval: Bool, presentationData: PresentationData, configuration: StarsSubscriptionConfiguration) -> [InviteLinksEditEntry] { var entries: [InviteLinksEditEntry] = [] entries.append(.titleHeader(presentationData.theme, presentationData.strings.InviteLink_Create_LinkNameTitle.uppercased())) @@ -507,17 +507,14 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: entries.append(.subscriptionFeeInfo(presentationData.theme, infoText)) } - if !isPublic { - entries.append(.requestApproval(presentationData.theme, presentationData.strings.InviteLink_Create_RequestApproval, state.requestApproval, isEditingEnabled && !isSubscription)) - var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel + if !isPublic || isGroup { + let requestApprovalEnabled = isEditingEnabled && !isSubscription && !(isPublic && isGroup && !globalRequestApproval) + entries.append(.requestApproval(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers, state.requestApproval, requestApprovalEnabled)) + var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalInfo if isSubscription { requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalFeeUnavailable - } else { - if state.requestApproval { - requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOnInfoChannel - } else { - requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel - } + } else if isPublic && isGroup && !globalRequestApproval { + requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalPublicGroupUnavailable } entries.append(.requestApprovalInfo(presentationData.theme, requestApprovalInfoText)) } @@ -538,7 +535,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: } entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo)) - if !state.requestApproval || isPublic { + if !state.requestApproval { entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased())) entries.append(.usagePicker(presentationData.theme, presentationData.dateTimeFormat, state.usage, isEditingEnabled)) @@ -624,10 +621,17 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio guard let inviteLink = invite?.link else { return } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -689,7 +693,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio |> map { presentationData, state, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in let isPublic = !(peer?.addressName?.isEmpty ?? true) - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -700,7 +704,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio } } - let rightNavigationButton = ItemListNavigationButton(content: .text(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), style: state.updating ? .activity : .bold, enabled: doneIsEnabled, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: state.updating ? .activity : .bold, enabled: doneIsEnabled, action: { updateState { state in var updatedState = state updatedState.updating = true @@ -720,7 +724,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio let titleString = state.title.trimmingCharacters(in: .whitespacesAndNewlines) let title = titleString.isEmpty ? nil : titleString var usageLimit = state.usage.value - var requestNeeded: Bool? = state.requestApproval && !isPublic + var requestNeeded: Bool? = state.requestApproval if invite == nil { let subscriptionPricing: StarsSubscriptionPricing? @@ -785,14 +789,20 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio } let isGroup: Bool + let globalRequestApproval: Bool if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false + globalRequestApproval = channel.flags.contains(.requestToJoin) + } else if case let .channel(channel) = peer { + isGroup = true + globalRequestApproval = channel.flags.contains(.requestToJoin) } else { isGroup = true + globalRequestApproval = false } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(invite == nil ? presentationData.strings.InviteLink_Create_Title : presentationData.strings.InviteLink_Create_EditTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, presentationData: presentationData, configuration: configuration), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, globalRequestApproval: globalRequestApproval, presentationData: presentationData, configuration: configuration), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index 9533a925ec..b338627fe6 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -7,14 +7,12 @@ import AsyncDisplayKit import TelegramCore import Display import AccountContext -import SolidRoundedButtonNode import ItemListUI import ItemListPeerItem import SectionHeaderItem import TelegramStringFormatting import MergeLists import ContextUI - import OverlayStatusController import PresentationDataUtils import DirectionalPanGesture @@ -411,13 +409,20 @@ public final class InviteLinkInviteController: ViewController { if let invite { if case let .groupOrChannel(peerId) = self.mode { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -443,10 +448,17 @@ public final class InviteLinkInviteController: ViewController { return } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 08f3c04323..24b8c0660d 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -498,109 +498,144 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio guard let node = node as? ContextReferenceContentNode, let controller = getControllerImpl?(), let invite = invite else { return } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - dismissTooltipsImpl?() - - UIPasteboard.general.string = invite.link - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - }))) - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) - - let _ = (context.account.postbox.loadedPeerWithId(peerId) - |> deliverOnMainQueue).start(next: { peer in - let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - isGroup = false + let creatorIsBot: Signal + if let adminPeer = admin?.peer.peer as? TelegramUser { + creatorIsBot = .single(adminPeer.botInfo != nil) + } else if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0 { + creatorIsBot = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId)) + |> map { peer -> Bool in + if let peer, case let .user(user) = peer, user.botInfo != nil { + return true } else { - isGroup = true + return false } - presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) - }) - }))) + } + } else { + creatorIsBot = .single(false) + } - if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0 { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + let _ = (creatorIsBot + |> take(1) + |> deliverOnMainQueue).start(next: { creatorIsBot in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + + dismissTooltipsImpl?() + + UIPasteboard.general.string = invite.link + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + }))) + + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true } - - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text), - ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { - dismissAction() - - var revoke = false - updateState { state in - if !state.revokingPrivateLink { - revoke = true - var updatedState = state - updatedState.revokingPrivateLink = true - return updatedState - } else { - return state - } - } - if revoke, let inviteLink = invite.link { - revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in - updateState { state in - var updatedState = state - updatedState.revokingPrivateLink = false - return updatedState - } - if let result = result { - switch result { - case let .update(newInvite): - invitesContext.remove(newInvite) - revokedInvitesContext.add(newInvite) - case let .replace(previousInvite, newInvite): - revokedInvitesContext.add(previousInvite) - invitesContext.remove(previousInvite) - invitesContext.add(newInvite) - } - } - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) - })) - } - }) - ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) }) }))) - } - let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - presentInGlobalOverlayImpl?(contextController) + if case let .link(_, _, _, _, _, adminId, _, _, _, _, _, _, _) = invite, adminId.toInt64() != 0, !creatorIsBot { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + }, action: { _, f in + f(.dismissWithoutContent) + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> deliverOnMainQueue).start(next: { peer in + let isGroup: Bool + if case let .channel(channel) = peer, case .broadcast = channel.info { + isGroup = false + } else { + isGroup = true + } + + let controller = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: isGroup ? presentationData.strings.GroupInfo_InviteLink_RevokeAlert_Text : presentationData.strings.ChannelInfo_InviteLink_RevokeAlert_Text), + ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { + dismissAction() + + var revoke = false + updateState { state in + if !state.revokingPrivateLink { + revoke = true + var updatedState = state + updatedState.revokingPrivateLink = true + return updatedState + } else { + return state + } + } + if revoke, let inviteLink = invite.link { + revokeLinkDisposable.set((context.engine.peers.revokePeerExportedInvitation(peerId: peerId, link: inviteLink) |> deliverOnMainQueue).start(next: { result in + updateState { state in + var updatedState = state + updatedState.revokingPrivateLink = false + return updatedState + } + if let result = result { + switch result { + case let .update(newInvite): + invitesContext.remove(newInvite) + revokedInvitesContext.add(newInvite) + case let .replace(previousInvite, newInvite): + revokedInvitesContext.add(previousInvite) + invitesContext.remove(previousInvite) + invitesContext.add(newInvite) + } + } + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + })) + } + }) + ]), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + }) + }))) + } + + let contextController = makeContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + presentInGlobalOverlayImpl?(contextController) + }) }, createLink: { let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in if let invite = invite { @@ -705,10 +740,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.default) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -775,10 +817,17 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio }, action: { _, f in f(.default) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index e60ff909ad..a5908e95b8 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -7,14 +7,12 @@ import AsyncDisplayKit import TelegramCore import Display import AccountContext -import SolidRoundedButtonNode import ItemListUI import ItemListPeerItem import SectionHeaderItem import TelegramStringFormatting import MergeLists import ContextUI - import OverlayStatusController import PresentationDataUtils import DirectionalPanGesture @@ -741,13 +739,20 @@ public final class InviteLinkViewController: ViewController { }, action: { [weak self] _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self, let parentController = strongSelf.controller else { return } let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -763,10 +768,17 @@ public final class InviteLinkViewController: ViewController { }, action: { [weak self] _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -823,11 +835,19 @@ public final class InviteLinkViewController: ViewController { } if case let .link(_, _, _, _, _, adminId, date, _, _, usageLimit, _, _, _) = invite { + let creatorPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: adminId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } self.disposable = (combineLatest( self.presentationDataPromise.get(), self.importersContext.state, requestsState, - context.account.postbox.loadedPeerWithId(adminId) + creatorPeerSignal ) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in if let strongSelf = self { let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0 @@ -849,7 +869,7 @@ public final class InviteLinkViewController: ViewController { } entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased())) - entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, EnginePeer(creatorPeer), date)) + entries.append(.creator(presentationData.theme, presentationData.dateTimeFormat, creatorPeer, date)) if !requestsState.importers.isEmpty || (state.isLoadingMore && requestsState.count > 0) { entries.append(.requestHeader(presentationData.theme, presentationData.strings.MemberRequests_PeopleRequested(Int32(requestsState.count)).uppercased(), "", false)) diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift index a2ebd4868b..6e38456b86 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift @@ -376,7 +376,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio } } navigateToProfileImpl = { [weak controller] peer in - if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) { + if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller) } } diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift index 5382dfed9c..04c68fdc70 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift @@ -6,10 +6,11 @@ import SwiftSignalKit import AccountContext import TelegramPresentationData import ItemListUI -import SolidRoundedButtonNode import AnimatedAvatarSetNode import ShimmerEffect import TelegramCore +import ComponentFlow +import ButtonComponent private func actionButtonImage(color: UIColor) -> UIImage? { return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in @@ -141,8 +142,8 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod private let addressButtonNode: HighlightTrackingButtonNode private let addressButtonIconNode: ASImageNode private var addressShimmerNode: ShimmerEffectNode? - private var shareButtonNode: SolidRoundedButtonNode? - private var secondaryButtonNode: SolidRoundedButtonNode? + private var shareButton: ComponentView? + private var secondaryButton: ComponentView? private let avatarsButtonNode: HighlightTrackingButtonNode private let avatarsContext: AnimatedAvatarSetContext @@ -250,11 +251,6 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod } } } - self.shareButtonNode?.pressed = { [weak self] in - if let strongSelf = self, let item = strongSelf.item { - item.shareAction?() - } - } self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { @@ -458,64 +454,94 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod strongSelf.referenceContainerNode.frame = strongSelf.containerNode.bounds strongSelf.addressButtonIconNode.frame = strongSelf.containerNode.bounds - let shareButtonNode: SolidRoundedButtonNode - if let currentShareButtonNode = strongSelf.shareButtonNode { - shareButtonNode = currentShareButtonNode - } else { - let buttonTheme: SolidRoundedButtonTheme - if let buttonColor = item.buttonColor { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } else { - buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - } - shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: buttonHeight, cornerRadius: buttonHeight * 0.5) - shareButtonNode.pressed = { [weak self] in - self?.item?.shareAction?() - } - strongSelf.addSubnode(shareButtonNode) - strongSelf.shareButtonNode = shareButtonNode - } - shareButtonNode.title = item.buttonTitle + let buttonBackgroundColor = item.buttonColor ?? item.presentationData.theme.list.itemCheckColors.fillColor + let buttonForegroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor + let buttonBackground = ButtonComponent.Background( + style: item.systemStyle == .glass ? .glass : .legacy, + color: buttonBackgroundColor, + foreground: buttonForegroundColor, + pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.8), + cornerRadius: buttonHeight * 0.5 + ) - if let secondaryButtonTitle = item.secondaryButtonTitle { - let secondaryButtonNode: SolidRoundedButtonNode - if let current = strongSelf.secondaryButtonNode { - secondaryButtonNode = current - } else { - let buttonTheme: SolidRoundedButtonTheme - if let buttonColor = item.buttonColor { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } else { - buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - } - secondaryButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: buttonHeight, cornerRadius: buttonHeight * 0.5) - secondaryButtonNode.pressed = { [weak self] in - self?.item?.secondaryAction?() - } - strongSelf.addSubnode(secondaryButtonNode) - strongSelf.secondaryButtonNode = secondaryButtonNode - } - secondaryButtonNode.title = secondaryButtonTitle + let shareButton: ComponentView + if let currentShareButton = strongSelf.shareButton { + shareButton = currentShareButton } else { - if let secondaryButtonNode = strongSelf.secondaryButtonNode { - strongSelf.secondaryButtonNode = nil - secondaryButtonNode.removeFromSupernode() + shareButton = ComponentView() + strongSelf.shareButton = shareButton + } + + if item.secondaryButtonTitle != nil { + if strongSelf.secondaryButton == nil { + strongSelf.secondaryButton = ComponentView() + } + } else { + if let secondaryButton = strongSelf.secondaryButton { + strongSelf.secondaryButton = nil + secondaryButton.view?.removeFromSuperview() } } var buttonWidth = contentSize.width - leftInset - rightInset let totalButtonWidth = buttonWidth let buttonSpacing: CGFloat = 8.0 - if strongSelf.secondaryButtonNode != nil { + if strongSelf.secondaryButton != nil { buttonWidth = floor((buttonWidth - 8.0) / 2.0) } - let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate) - shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight) + let shareButtonSize = shareButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity(id: AnyHashable(item.buttonTitle), component: AnyComponent(Text(text: item.buttonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))), + isEnabled: item.enableButton, + tintWhenDisabled: false, + action: { [weak self] in + self?.item?.shareAction?() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + if let shareButtonView = shareButton.view { + if shareButtonView.superview == nil { + strongSelf.view.addSubview(shareButtonView) + } + shareButtonView.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing), size: shareButtonSize) + shareButtonView.isHidden = !item.displayButton + shareButtonView.alpha = item.enableButton ? 1.0 : 0.4 + shareButtonView.isAccessibilityElement = true + shareButtonView.accessibilityLabel = item.buttonTitle + shareButtonView.accessibilityTraits = item.enableButton ? [.button] : [.button, .notEnabled] + } - if let secondaryButtonNode = strongSelf.secondaryButtonNode { - let _ = secondaryButtonNode.updateLayout(width: totalButtonWidth - buttonWidth - buttonSpacing, transition: .immediate) - secondaryButtonNode.frame = CGRect(x: leftInset + buttonWidth + buttonSpacing, y: verticalInset + fieldHeight + fieldSpacing, width: totalButtonWidth - buttonWidth - buttonSpacing, height: buttonHeight) + if let secondaryButton = strongSelf.secondaryButton, let secondaryButtonTitle = item.secondaryButtonTitle { + let secondaryButtonWidth = totalButtonWidth - buttonWidth - buttonSpacing + let secondaryButtonSize = secondaryButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity(id: AnyHashable(secondaryButtonTitle), component: AnyComponent(Text(text: secondaryButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))), + tintWhenDisabled: false, + action: { [weak self] in + self?.item?.secondaryAction?() + } + )), + environment: {}, + containerSize: CGSize(width: secondaryButtonWidth, height: buttonHeight) + ) + if let secondaryButtonView = secondaryButton.view { + if secondaryButtonView.superview == nil { + strongSelf.view.addSubview(secondaryButtonView) + } + secondaryButtonView.frame = CGRect(origin: CGPoint(x: leftInset + buttonWidth + buttonSpacing, y: verticalInset + fieldHeight + fieldSpacing), size: secondaryButtonSize) + secondaryButtonView.isHidden = !item.displayButton + secondaryButtonView.alpha = 1.0 + secondaryButtonView.isAccessibilityElement = true + secondaryButtonView.accessibilityLabel = secondaryButtonTitle + secondaryButtonView.accessibilityTraits = [.button] + } } var totalWidth = invitedPeersLayout.size.width @@ -544,9 +570,6 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0 - strongSelf.shareButtonNode?.isUserInteractionEnabled = item.enableButton - strongSelf.shareButtonNode?.alpha = item.enableButton ? 1.0 : 0.4 - strongSelf.shareButtonNode?.isHidden = !item.displayButton strongSelf.avatarsButtonNode.isHidden = !item.displayImporters strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift index 6ccdd7c2b1..36238ff383 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift @@ -521,7 +521,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) if item.removeAction != nil { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) } else { strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift index 3555521e4e..fe0dd5935e 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkDateLimitItem.swift @@ -73,14 +73,16 @@ enum InviteLinkTimeLimit: Equatable { final class ItemListInviteLinkTimeLimitItem: ListViewItem, ItemListItem { let theme: PresentationTheme + let systemStyle: ItemListSystemStyle let strings: PresentationStrings let value: InviteLinkTimeLimit let enabled: Bool let sectionId: ItemListSectionId let updated: (InviteLinkTimeLimit) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: InviteLinkTimeLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkTimeLimit) -> Void) { + init(theme: PresentationTheme, systemStyle: ItemListSystemStyle, strings: PresentationStrings, value: InviteLinkTimeLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkTimeLimit) -> Void) { self.theme = theme + self.systemStyle = systemStyle self.strings = strings self.value = value self.enabled = enabled @@ -326,7 +328,7 @@ private final class ItemListInviteLinkTimeLimitItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift index a00ee4df31..3ca0541df6 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift @@ -377,7 +377,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { var pricingAttributedText: NSMutableAttributedString? var timerValue: TimerNode.Value? - if let invite = item.invite, case let .link(_, title, _, _, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, subscriptionPricing) = invite { + if let invite = item.invite, case let .link(_, title, _, requestApproval, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, subscriptionPricing) = invite { if let title = title, !title.isEmpty { titleText = title } @@ -405,6 +405,12 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { } subtitleText += item.presentationData.strings.MemberRequests_PeopleRequestedShort(requestedCount) } + if requestApproval { + if !subtitleText.isEmpty { + subtitleText += " • " + } + subtitleText += item.presentationData.strings.InviteLink_ApprovalRequired + } if let subscriptionPricing { let text = NSMutableAttributedString() diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift index 3bc5994529..8b297264d9 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkUsageLimitItem.swift @@ -87,6 +87,7 @@ enum InviteLinkUsageLimit: Equatable { final class ItemListInviteLinkUsageLimitItem: ListViewItem, ItemListItem { let theme: PresentationTheme + let systemStyle: ItemListSystemStyle let strings: PresentationStrings let dateTimeFormat: PresentationDateTimeFormat let value: InviteLinkUsageLimit @@ -94,8 +95,9 @@ final class ItemListInviteLinkUsageLimitItem: ListViewItem, ItemListItem { let sectionId: ItemListSectionId let updated: (InviteLinkUsageLimit) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, value: InviteLinkUsageLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkUsageLimit) -> Void) { + init(theme: PresentationTheme, systemStyle: ItemListSystemStyle, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, value: InviteLinkUsageLimit, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (InviteLinkUsageLimit) -> Void) { self.theme = theme + self.systemStyle = systemStyle self.strings = strings self.dateTimeFormat = dateTimeFormat self.value = value @@ -336,7 +338,7 @@ private final class ItemListInviteLinkUsageLimitItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift index 8af202a948..8a1e59dea5 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -722,7 +721,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift index 1761e1f681..8563d9dab8 100644 --- a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift @@ -6,13 +6,13 @@ import SwiftSignalKit import AccountContext import TelegramPresentationData import ItemListUI -import SolidRoundedButtonNode import AnimatedAvatarSetNode import ShimmerEffect import TelegramCore import Markdown import TextFormat import ComponentFlow +import ButtonComponent import MultilineTextComponent import TextNodeWithEntities @@ -143,8 +143,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem private let addressButtonNode: HighlightTrackingButtonNode private let addressButtonIconNode: ASImageNode private var addressShimmerNode: ShimmerEffectNode? - private var copyButtonNode: SolidRoundedButtonNode? - private var shareButtonNode: SolidRoundedButtonNode? + private var copyButton: ComponentView? + private var shareButton: ComponentView? private let avatarsButtonNode: HighlightTrackingButtonNode private let avatarsContext: AnimatedAvatarSetContext @@ -257,16 +257,6 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem } } } - self.copyButtonNode?.pressed = { [weak self] in - if let strongSelf = self, let item = strongSelf.item { - item.copyAction?() - } - } - self.shareButtonNode?.pressed = { [weak self] in - if let strongSelf = self, let item = strongSelf.item { - item.shareAction?() - } - } self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { @@ -535,46 +525,38 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem effectiveSeparateButtons = false } - let copyButtonNode: SolidRoundedButtonNode - if let currentCopyButtonNode = strongSelf.copyButtonNode { - copyButtonNode = currentCopyButtonNode + let buttonBackgroundColor = item.buttonColor ?? item.presentationData.theme.list.itemCheckColors.fillColor + let buttonForegroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor + let buttonBackground = ButtonComponent.Background( + style: item.systemStyle == .glass ? .glass : .legacy, + color: buttonBackgroundColor, + foreground: buttonForegroundColor, + pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.8), + cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0 + ) + + let copyButtonTitle = item.presentationData.strings.InviteLink_CopyShort + let copyButton: ComponentView + if let currentCopyButton = strongSelf.copyButton { + copyButton = currentCopyButton } else { - let buttonTheme: SolidRoundedButtonTheme - if let buttonColor = item.buttonColor { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } else { - buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - } - copyButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: 52.0, cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0) - copyButtonNode.title = item.presentationData.strings.InviteLink_CopyShort - copyButtonNode.pressed = { [weak self] in - self?.item?.copyAction?() - } - strongSelf.addSubnode(copyButtonNode) - strongSelf.copyButtonNode = copyButtonNode + copyButton = ComponentView() + strongSelf.copyButton = copyButton } - let shareButtonNode: SolidRoundedButtonNode - if let currentShareButtonNode = strongSelf.shareButtonNode { - shareButtonNode = currentShareButtonNode + let shareButtonTitle: String + if let invite = item.invite, invitationAvailability(invite).isZero { + shareButtonTitle = item.presentationData.strings.InviteLink_ReactivateLink } else { - let buttonTheme: SolidRoundedButtonTheme - if let buttonColor = item.buttonColor { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } else { - buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - } - shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: 52.0, cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0) - if let invite = item.invite, invitationAvailability(invite).isZero { - shareButtonNode.title = item.presentationData.strings.InviteLink_ReactivateLink - } else { - shareButtonNode.title = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share - } - shareButtonNode.pressed = { [weak self] in - self?.item?.shareAction?() - } - strongSelf.addSubnode(shareButtonNode) - strongSelf.shareButtonNode = shareButtonNode + shareButtonTitle = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share + } + + let shareButton: ComponentView + if let currentShareButton = strongSelf.shareButton { + shareButton = currentShareButton + } else { + shareButton = ComponentView() + strongSelf.shareButton = shareButton } let buttonSpacing: CGFloat = 8.0 @@ -585,11 +567,57 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem shareButtonOriginX = leftInset + buttonWidth + buttonSpacing } - let _ = copyButtonNode.updateLayout(width: buttonWidth, transition: .immediate) - copyButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight) + let copyButtonSize = copyButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity(id: AnyHashable(copyButtonTitle), component: AnyComponent(Text(text: copyButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))), + isEnabled: item.invite != nil, + tintWhenDisabled: false, + action: { [weak self] in + self?.item?.copyAction?() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + if let copyButtonView = copyButton.view { + if copyButtonView.superview == nil { + strongSelf.view.addSubview(copyButtonView) + } + copyButtonView.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing), size: copyButtonSize) + copyButtonView.isHidden = !item.displayButton || !effectiveSeparateButtons + copyButtonView.alpha = item.invite != nil ? 1.0 : 0.4 + copyButtonView.isAccessibilityElement = true + copyButtonView.accessibilityLabel = copyButtonTitle + copyButtonView.accessibilityTraits = item.invite != nil ? [.button] : [.button, .notEnabled] + } - let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate) - shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight) + let shareButtonSize = shareButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity(id: AnyHashable(shareButtonTitle), component: AnyComponent(Text(text: shareButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))), + isEnabled: item.invite != nil, + tintWhenDisabled: false, + action: { [weak self] in + self?.item?.shareAction?() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + if let shareButtonView = shareButton.view { + if shareButtonView.superview == nil { + strongSelf.view.addSubview(shareButtonView) + } + shareButtonView.frame = CGRect(origin: CGPoint(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing), size: shareButtonSize) + shareButtonView.isHidden = !item.displayButton + shareButtonView.alpha = item.invite != nil ? 1.0 : 0.4 + shareButtonView.isAccessibilityElement = true + shareButtonView.accessibilityLabel = shareButtonTitle + shareButtonView.accessibilityTraits = item.invite != nil ? [.button] : [.button, .notEnabled] + } if let justCreatedCallTextNodeLayout { if let justCreatedCallTextNode = justCreatedCallTextNodeLayout.1(TextNodeWithEntities.Arguments( @@ -608,7 +636,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem justCreatedCallTextNode.textNode.view.addGestureRecognizer(UITapGestureRecognizer(target: strongSelf, action: #selector(strongSelf.justCreatedCallTextTap(_:)))) } - let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height)) + let buttonMaxY = verticalInset + fieldHeight + fieldSpacing + buttonHeight + let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: buttonMaxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height)) justCreatedCallTextNode.textNode.frame = justCreatedCallTextNodeFrame let justCreatedCallSeparatorText: ComponentView @@ -648,7 +677,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem environment: {}, containerSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0) ) - let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: shareButtonNode.frame.maxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize) + let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: buttonMaxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize) if let justCreatedCallSeparatorTextView = justCreatedCallSeparatorText.view { if justCreatedCallSeparatorTextView.superview == nil { strongSelf.view.addSubview(justCreatedCallSeparatorTextView) @@ -703,14 +732,6 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0 - strongSelf.copyButtonNode?.isUserInteractionEnabled = item.invite != nil - strongSelf.copyButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4 - strongSelf.copyButtonNode?.isHidden = !item.displayButton || !effectiveSeparateButtons - - strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil - strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4 - strongSelf.shareButtonNode?.isHidden = !item.displayButton - strongSelf.avatarsButtonNode.isHidden = !item.displayImporters strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil diff --git a/submodules/ItemListAvatarAndNameInfoItem/BUILD b/submodules/ItemListAvatarAndNameInfoItem/BUILD index 35a105dd3c..c43af7de09 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/BUILD +++ b/submodules/ItemListAvatarAndNameInfoItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/PeerPresenceStatusManager:PeerPresenceStatusManager", diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index 3c92955fe6..170c5af092 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -135,7 +134,7 @@ public enum ItemListAvatarAndNameInfoItemMode { public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { public enum ItemContext { case accountContext(AccountContext) - case other(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network) + case other(accountPeerId: EnginePeer.Id, stateManager: AccountStateManager) var accountContext: AccountContext? { if case let .accountContext(accountContext) = self { @@ -758,8 +757,8 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo switch item.itemContext { case let .accountContext(context): strongSelf.avatarNode.setPeer(context: context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) - case let .other(accountPeerId, postbox, network): - strongSelf.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: .default, theme: item.presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) + case let .other(accountPeerId, stateManager): + strongSelf.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: stateManager.postbox, network: stateManager.network, contentSettings: .default, theme: item.presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: overrideImage, emptyColor: ignoreEmpty ? nil : item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads) } } diff --git a/submodules/ItemListPeerItem/BUILD b/submodules/ItemListPeerItem/BUILD index b5fb4ad41c..464ec3f91b 100644 --- a/submodules/ItemListPeerItem/BUILD +++ b/submodules/ItemListPeerItem/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/AvatarNode:AvatarNode", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramStringFormatting:TelegramStringFormatting", diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index e7612f066c..e1f339963a 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -322,29 +321,26 @@ public struct ItemListPeerItemShimmering { } } -public final class ItemListPeerItem: ListViewItem, ItemListItem { +public final class ItemListPeerItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { public enum Context { public final class Custom { public let accountPeerId: EnginePeer.Id - public let postbox: Postbox - public let network: Network + public let engine: TelegramEngine public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer public let isPremiumDisabled: Bool public let resolveInlineStickers: ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> - + public init( accountPeerId: EnginePeer.Id, - postbox: Postbox, - network: Network, + engine: TelegramEngine, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, isPremiumDisabled: Bool, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> ) { self.accountPeerId = accountPeerId - self.postbox = postbox - self.network = network + self.engine = engine self.animationCache = animationCache self.animationRenderer = animationRenderer self.isPremiumDisabled = isPremiumDisabled @@ -364,21 +360,12 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { } } - public var postbox: Postbox { + public var engine: TelegramEngine { switch self { case let .account(context): - return context.account.postbox + return context.engine case let .custom(custom): - return custom.postbox - } - } - - public var network: Network { - switch self { - case let .account(context): - return context.account.network - case let .custom(custom): - return custom.network + return custom.engine } } @@ -479,8 +466,12 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { let displayDecorations: Bool let displayBackground: Bool let disableInteractiveTransitionIfNecessary: Bool - let storyStats: PeerStoryStats? + let storyStats: EnginePeerStoryStats? let openStories: ((UIView) -> Void)? + + public var hasActiveRevealOptions: Bool { + return self.editing.revealed == true + } public init( presentationData: ItemListPresentationData, @@ -523,7 +514,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { displayDecorations: Bool = true, displayBackground: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false, - storyStats: PeerStoryStats? = nil, + storyStats: EnginePeerStoryStats? = nil, openStories: ((UIView) -> Void)? = nil ) { self.presentationData = presentationData @@ -611,7 +602,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { displayDecorations: Bool = true, displayBackground: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false, - storyStats: PeerStoryStats? = nil, + storyStats: EnginePeerStoryStats? = nil, openStories: ((UIView) -> Void)? = nil ) { self.presentationData = presentationData @@ -952,6 +943,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.isVerified { credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + credibilityParticleColor = nil } if let verificationIconFileId = item.peer.verificationIconFileId { verifiedIcon = .animation(content: .customEmoji(fileId: verificationIconFileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(0)) @@ -1025,12 +1017,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor } - mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor)) + mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, iconColor: textColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)) index += 1 } peerRevealOptions = mappedOptions } else { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] } } else { peerRevealOptions = [] @@ -1446,26 +1438,29 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noCorners var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = !item.displayDecorations || hasCorners || !item.hasTopStripe + topStripeIsHidden = !item.displayDecorations || hasCorners || !item.hasTopStripe } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = !item.displayDecorations + bottomStripeIsHidden = !item.displayDecorations default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations + bottomStripeIsHidden = hasCorners || !item.displayDecorations } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -1494,7 +1489,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } let verifiedIconComponent = EmojiStatusComponent( - postbox: item.context.postbox, + postbox: item.context.engine.account.postbox, energyUsageSettings: item.context.energyUsageSettings, resolveInlineStickers: item.context.resolveInlineStickers, animationCache: animationCache, @@ -1542,7 +1537,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } let credibilityIconComponent = EmojiStatusComponent( - postbox: item.context.postbox, + postbox: item.context.engine.account.postbox, energyUsageSettings: item.context.energyUsageSettings, resolveInlineStickers: item.context.resolveInlineStickers, animationCache: animationCache, @@ -1701,7 +1696,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } let avatarIconComponent = EmojiStatusComponent( - postbox: item.context.postbox, + postbox: item.context.engine.account.postbox, energyUsageSettings: item.context.energyUsageSettings, resolveInlineStickers: item.context.resolveInlineStickers, animationCache: item.context.animationCache, @@ -1729,8 +1724,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo if item.peer.id == item.context.accountPeerId, case .threatSelfAsSaved = item.aliasHandling { strongSelf.avatarNode.setPeer( accountPeerId: item.context.accountPeerId, - postbox: item.context.postbox, - network: item.context.network, + postbox: item.context.engine.account.postbox, + network: item.context.engine.account.network, contentSettings: item.context.contentSettings, theme: item.presentationData.theme, peer: item.peer, @@ -1741,8 +1736,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } else if item.peer.id.isReplies { strongSelf.avatarNode.setPeer( accountPeerId: item.context.accountPeerId, - postbox: item.context.postbox, - network: item.context.network, + postbox: item.context.engine.account.postbox, + network: item.context.engine.account.network, contentSettings: item.context.contentSettings, theme: item.presentationData.theme, peer: item.peer, @@ -1764,8 +1759,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo strongSelf.avatarNode.setPeer( accountPeerId: item.context.accountPeerId, - postbox: item.context.postbox, - network: item.context.network, + postbox: item.context.engine.account.postbox, + network: item.context.engine.account.network, contentSettings: item.context.contentSettings, theme: item.presentationData.theme, peer: item.peer, @@ -1790,7 +1785,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } } - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition) if let presence = item.presence { strongSelf.peerPresenceManager?.reset(presence: presence) @@ -1872,7 +1867,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo var isHighlighted = false var reallyHighlighted: Bool { - var reallyHighlighted = self.isHighlighted + var reallyHighlighted = self.isHighlighted || self.isRevealOptionsActive if let (item, _, _, _) = self.layoutParams, item.highlighted { reallyHighlighted = true } @@ -1880,39 +1875,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } func updateIsHighlighted(transition: ContainedViewLayoutTransition) { - if self.reallyHighlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if transition.isAnimated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.reallyHighlighted, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { @@ -2014,6 +1977,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo transition.updateFrame(view: avatarIconComponentView, frame: threadIconFrame) } } + + override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateIsHighlighted(transition: transition) + } override public func revealOptionsInteractivelyOpened() { if let (item, _, _, _) = self.layoutParams { diff --git a/submodules/ItemListStickerPackItem/BUILD b/submodules/ItemListStickerPackItem/BUILD index 5752bd50bc..f82bb003b7 100644 --- a/submodules/ItemListStickerPackItem/BUILD +++ b/submodules/ItemListStickerPackItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ItemListUI:ItemListUI", diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 620f6d9657..daf52b4de4 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -37,7 +36,7 @@ public enum ItemListStickerPackItemControl: Equatable { case check(checked: Bool) } -public final class ItemListStickerPackItem: ListViewItem, ItemListItem { +public final class ItemListStickerPackItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let presentationData: ItemListPresentationData let context: AccountContext let systemStyle: ItemListSystemStyle @@ -52,12 +51,16 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { let style: ItemListStyle public let sectionId: ItemListSectionId let action: (() -> Void)? - let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void + let setPackIdWithRevealedOptions: (EngineItemCollectionId?, EngineItemCollectionId?) -> Void let addPack: () -> Void let removePack: () -> Void let toggleSelected: () -> Void - public init(presentationData: ItemListPresentationData, context: AccountContext, systemStyle: ItemListSystemStyle = .legacy, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { + public var hasActiveRevealOptions: Bool { + return self.editing.revealed + } + + public init(presentationData: ItemListPresentationData, context: AccountContext, systemStyle: ItemListSystemStyle = .legacy, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (EngineItemCollectionId?, EngineItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { self.presentationData = presentationData self.context = context self.systemStyle = systemStyle @@ -126,8 +129,8 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { public enum StickerPackThumbnailItem: Equatable { case still(TelegramMediaImageRepresentation) - case animated(MediaResource, PixelDimensions, Bool, Bool) - + case animated(EngineMediaResource, PixelDimensions, Bool, Bool) + public static func ==(lhs: StickerPackThumbnailItem, rhs: StickerPackThumbnailItem) -> Bool { switch lhs { case let .still(representation): @@ -137,7 +140,7 @@ public enum StickerPackThumbnailItem: Equatable { return false } case let .animated(lhsResource, lhsDimensions, lhsIsVideo, lhsTinted): - if case let .animated(rhsResource, rhsDimensions, rhsIsVideo, rhsTinted) = rhs, lhsResource.isEqual(to: rhsResource), lhsDimensions == rhsDimensions, lhsIsVideo == rhsIsVideo, lhsTinted == rhsTinted { + if case let .animated(rhsResource, rhsDimensions, rhsIsVideo, rhsTinted) = rhs, lhsResource == rhsResource, lhsDimensions == rhsDimensions, lhsIsVideo == rhsIsVideo, lhsTinted == rhsTinted { return true } else { return false @@ -181,6 +184,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { private let activateArea: AccessibilityAreaNode private let fetchDisposable = MetaDisposable() + private var isHighlighted: Bool = false override var canBeSelected: Bool { if self.selectableControlNode != nil || self.editableControlNode != nil || self.disabledOverlayNode != nil { @@ -373,7 +377,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let packRevealOptions: [ItemListRevealOption] if item.editing.editable && item.enabled && !item.editing.editing { - packRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + packRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] } else { packRevealOptions = [] } @@ -411,14 +415,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = 65.0 + params.leftInset - let verticalInset: CGFloat - switch item.systemStyle { - case .glass: - verticalInset = 13.0 - case .legacy: - verticalInset = 11.0 - } - + let verticalInset: CGFloat = 11.0 let titleSpacing: CGFloat = 2.0 let separatorHeight = UIScreenPixel @@ -500,7 +497,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { var resourceReference: MediaResourceReference? if item.packInfo.hasThumbnail, let thumbnail = item.packInfo._parse().thumbnail { if thumbnail.typeHint != .generic { - thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji)) + thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji)) } else { thumbnailItem = .still(thumbnail) } @@ -508,7 +505,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } else if let item = item.topItem { let itemFile = item.file._parse() if itemFile.isAnimatedSticker || itemFile.isVideoSticker { - thumbnailItem = .animated(itemFile.resource, itemFile.dimensions ?? PixelDimensions(width: 100, height: 100), itemFile.isVideoSticker, itemFile.isCustomTemplateEmoji) + thumbnailItem = .animated(EngineMediaResource(itemFile.resource), itemFile.dimensions ?? PixelDimensions(width: 100, height: 100), itemFile.isVideoSticker, itemFile.isCustomTemplateEmoji) resourceReference = MediaResourceReference.media(media: .standalone(media: itemFile), resource: itemFile.resource) } else if let dimensions = itemFile.dimensions, let resource = chatMessageStickerResource(file: itemFile, small: true) as? TelegramMediaResource { thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) @@ -517,7 +514,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchSignal: Signal? + var updatedFetchSignal: Signal? let imageBoundingSize = CGSize(width: 34.0, height: 34.0) var imageApply: (() -> Void)? @@ -537,14 +534,14 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } case let .animated(resource, dimensions, _, _): imageSize = dimensions.cgSize.aspectFitted(imageBoundingSize) - + if fileUpdated { imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageBoundingSize, boundingSize: imageBoundingSize, intrinsicInsets: UIEdgeInsets())) - updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: resource, animated: true, nilIfEmpty: true) + updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: resource._asResource(), animated: true, nilIfEmpty: true) } } if fileUpdated, let resourceReference = resourceReference { - updatedFetchSignal = fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: resourceReference) + updatedFetchSignal = item.context.engine.resources.fetch(reference: resourceReference, userLocation: .other, userContentType: .sticker) } } else { updatedImageSignal = .single({ _ in return nil }) @@ -765,26 +762,29 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -829,9 +829,9 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { } strongSelf.animationNode = animationNode strongSelf.addSubnode(animationNode) - - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) - animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: resource, isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) + + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: resource.id) + animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: resource._asResource(), isVideo: isVideo), width: 80, height: 80, playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) } animationNode.visibility = strongSelf.visibility != .none && item.playAnimatedStickers animationNode.isHidden = !item.playAnimatedStickers @@ -878,7 +878,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { strongSelf.imageNode.setSignal(updatedImageSignal) } - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)), transition: transition) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -896,39 +896,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -968,6 +937,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: animationNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 15.0 + floor((boundingSize.width - animationNode.frame.size.width) / 2.0), y: animationNode.frame.minY), size: animationNode.frame.size)) } } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } override func revealOptionsInteractivelyOpened() { if let (item, _, _) = self.layoutParams { diff --git a/submodules/ItemListUI/BUILD b/submodules/ItemListUI/BUILD index cbd9e07683..0b641c47f8 100644 --- a/submodules/ItemListUI/BUILD +++ b/submodules/ItemListUI/BUILD @@ -35,6 +35,7 @@ swift_library( "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/ListItemComponentAdaptor", "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassControls", "//submodules/TelegramUI/Components/HorizontalTabsComponent", ], visibility = [ diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index dbc2384f58..9b83218c2d 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -25,6 +25,8 @@ public enum ItemListNavigationButtonContentIcon { case search case add case action + case close + case done } public enum ItemListNavigationButtonContent: Equatable { @@ -426,16 +428,27 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable case let .text(value): item = UIBarButtonItem(title: value, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) case let .icon(icon): - var image: UIImage? - switch icon { + if [.close, .done].contains(icon) { + switch icon { + case .done: + item = UIBarButtonItem(title: "___done", style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) + default: + item = UIBarButtonItem(title: "___close", style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) + } + } else { + var image: UIImage? + switch icon { case .search: image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.presentationData.theme) case .add: image = PresentationResourcesRootController.navigationAddIcon(controllerState.presentationData.theme) case .action: image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme) + default: + image = nil + } + item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) } - item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed)) case let .node(node): item = UIBarButtonItem(customDisplayNode: node) item.setCustomAction({ [weak self] in @@ -488,16 +501,27 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable case let .text(value): item = UIBarButtonItem(title: value, style: style.barButtonItemStyle, target: strongSelf, action: action) case let .icon(icon): - var image: UIImage? - switch icon { + if [.close, .done].contains(icon) { + switch icon { + case .done: + item = UIBarButtonItem(title: "___done", style: style.barButtonItemStyle, target: strongSelf, action: action) + default: + item = UIBarButtonItem(title: "___close", style: style.barButtonItemStyle, target: strongSelf, action: action) + } + } else { + var image: UIImage? + switch icon { case .search: image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.presentationData.theme) case .add: image = PresentationResourcesRootController.navigationAddIcon(controllerState.presentationData.theme) case .action: image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme) + default: + image = nil + } + item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action) } - item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action) case let .node(node): item = UIBarButtonItem(customDisplayNode: node) item.setCustomAction({ [weak self] in @@ -665,6 +689,16 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } + public func itemNode(forTag tag: ItemListItemTag) -> ListViewItemNode? { + var result: ListViewItemNode? + self.forEachItemNode { itemNode in + if result == nil, let taggedItemNode = itemNode as? ItemListItemNode, let itemTag = taggedItemNode.tag, itemTag.isEqual(to: tag) { + result = itemNode + } + } + return result + } + public func ensureItemNodeVisible(_ itemNode: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0, atTop: Bool = false, curve: ListViewAnimationCurve = .Default(duration: 0.25)) { self.controllerNode.listNode.ensureItemNodeVisible(itemNode, animated: animated, overflow: overflow, atTop: atTop, curve: curve) } diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 76901d6706..6b9efaf342 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -6,6 +6,8 @@ import SwiftSignalKit import TelegramCore import TelegramPresentationData import MergeLists +import ComponentFlow +import GlassControls public protocol ItemListHeaderItemNode: AnyObject { func updateTheme(theme: PresentationTheme) @@ -255,7 +257,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { private var emptyStateItem: ItemListControllerEmptyStateItem? private var emptyStateNode: ItemListControllerEmptyStateItemNode? - private var toolbarNode: ToolbarNode? + private var toolbar: ComponentView? private var searchItem: ItemListControllerSearch? private var searchNode: ItemListControllerSearchNode? @@ -654,7 +656,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { insets.bottom = max(insets.bottom, additionalInsets.bottom) let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { insets.left += inset insets.right += inset } @@ -666,60 +668,165 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.listNodeContainer.insertSubnode(self.leftOverlayNode, aboveSubnode: self.listNode) } - if let toolbarItem = self.toolbarItem { - var tabBarHeight: CGFloat - let bottomInset: CGFloat = insets.bottom - if !layout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset - insets.bottom += 34.0 + if let toolbarData = self.toolbarItem, let theme = self.theme { + var panelsBottomInset: CGFloat = layout.insets(options: []).bottom + if layout.metrics.widthClass == .regular, let inputHeight = layout.inputHeight, inputHeight != 0.0 { + panelsBottomInset = inputHeight + 8.0 + } + if panelsBottomInset == 0.0 { + panelsBottomInset = 8.0 } else { - tabBarHeight = 49.0 + bottomInset - insets.bottom += 49.0 + panelsBottomInset = max(panelsBottomInset, 8.0) } - let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) + let sideInset: CGFloat = 20.0 + let toolbarHeight = 44.0 + let toolbarFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelsBottomInset - toolbarHeight), size: CGSize(width: layout.size.width - sideInset * 2.0, height: toolbarHeight)) - if let toolbarNode = self.toolbarNode { - transition.updateFrame(node: toolbarNode, frame: toolbarFrame) - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition) - } else if let theme = self.theme { - let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: theme), displaySeparator: true) - toolbarNode.frame = toolbarFrame - toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate) - self.addSubnode(toolbarNode) - self.toolbarNode = toolbarNode - if case let .animated(duration, curve) = transition { - toolbarNode.layer.animatePosition(from: CGPoint(x: 0.0, y: toolbarFrame.height), to: CGPoint(), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, additive: true) + let toolbar: ComponentView + var toolbarTransition = ComponentTransition(transition) + if let current = self.toolbar { + toolbar = current + } else { + toolbar = ComponentView() + self.toolbar = toolbar + toolbarTransition = .immediate + } + + let _ = toolbar.update( + transition: toolbarTransition, + component: AnyComponent(GlassControlPanelComponent( + theme: theme, + leftItem: toolbarData.toolbar.leftAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "left_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self, let toolbarData = self.toolbarItem else { + return + } + toolbarData.actions[0].action() + } : nil + )], + background: .panel + ) + }, + centralItem: toolbarData.toolbar.middleAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self, let toolbarData = self.toolbarItem else { + return + } + if toolbarData.actions.count == 1 { + toolbarData.actions[0].action() + } else if toolbarData.actions.count == 3 { + toolbarData.actions[1].action() + } + } : nil + )], + background: .panel + ) + }, + rightItem: toolbarData.toolbar.rightAction.flatMap { value in + return GlassControlPanelComponent.Item( + items: [GlassControlGroupComponent.Item( + id: "right_" + value.title, + content: .text(value.title), + action: value.isEnabled ? { [weak self] in + guard let self, let toolbarData = self.toolbarItem else { + return + } + if toolbarData.actions.count == 2 { + toolbarData.actions[1].action() + } else if toolbarData.actions.count == 3 { + toolbarData.actions[2].action() + } + } : nil + )], + background: .panel + ) + }, + centerAlignmentIfPossible: true + )), + environment: {}, + containerSize: toolbarFrame.size + ) + + if let toolbarView = toolbar.view { + if toolbarView.superview == nil { + self.view.addSubview(toolbarView) + toolbarView.alpha = 0.0 } + toolbarTransition.setFrame(view: toolbarView, frame: toolbarFrame) + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 1.0) } - - self.toolbarNode?.left = { - toolbarItem.actions[0].action() - } - self.toolbarNode?.right = { - if toolbarItem.actions.count == 2 { - toolbarItem.actions[1].action() - } else if toolbarItem.actions.count == 3 { - toolbarItem.actions[2].action() - } - } - self.toolbarNode?.middle = { - if toolbarItem.actions.count == 1 { - toolbarItem.actions[0].action() - } else if toolbarItem.actions.count == 3 { - toolbarItem.actions[1].action() - } - } - } else if let toolbarNode = self.toolbarNode { - self.toolbarNode = nil - if case let .animated(duration, curve) = transition { - toolbarNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: toolbarNode.frame.size.height), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: false, additive: true, completion: { [weak toolbarNode] _ in - toolbarNode?.removeFromSupernode() + } else if let toolbar = self.toolbar { + self.toolbar = nil + if let toolbarView = toolbar.view { + ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 0.0, completion: { [weak toolbarView] _ in + toolbarView?.removeFromSuperview() }) - } else { - toolbarNode.removeFromSupernode() } } + +// if let toolbarItem = self.toolbarItem { +// var tabBarHeight: CGFloat +// let bottomInset: CGFloat = insets.bottom +// if !layout.safeInsets.left.isZero { +// tabBarHeight = 34.0 + bottomInset +// insets.bottom += 34.0 +// } else { +// tabBarHeight = 49.0 + bottomInset +// insets.bottom += 49.0 +// } +// +// let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) +// +// if let toolbarNode = self.toolbarNode { +// transition.updateFrame(node: toolbarNode, frame: toolbarFrame) +// toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition) +// } else if let theme = self.theme { +// let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: theme), displaySeparator: true) +// toolbarNode.frame = toolbarFrame +// toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate) +// self.addSubnode(toolbarNode) +// self.toolbarNode = toolbarNode +// if case let .animated(duration, curve) = transition { +// toolbarNode.layer.animatePosition(from: CGPoint(x: 0.0, y: toolbarFrame.height), to: CGPoint(), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, additive: true) +// } +// } +// +// self.toolbarNode?.left = { +// toolbarItem.actions[0].action() +// } +// self.toolbarNode?.right = { +// if toolbarItem.actions.count == 2 { +// toolbarItem.actions[1].action() +// } else if toolbarItem.actions.count == 3 { +// toolbarItem.actions[2].action() +// } +// } +// self.toolbarNode?.middle = { +// if toolbarItem.actions.count == 1 { +// toolbarItem.actions[0].action() +// } else if toolbarItem.actions.count == 3 { +// toolbarItem.actions[1].action() +// } +// } +// } else if let toolbarNode = self.toolbarNode { +// self.toolbarNode = nil +// if case let .animated(duration, curve) = transition { +// toolbarNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: toolbarNode.frame.size.height), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: false, additive: true, completion: { [weak toolbarNode] _ in +// toolbarNode?.removeFromSupernode() +// }) +// } else { +// toolbarNode.removeFromSupernode() +// } +// } if let headerItemNode = self.headerItemNode { let headerHeight = headerItemNode.updateLayout(layout: layout, transition: transition) @@ -977,7 +1084,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { insets.bottom = footerHeight let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { insets.left += inset insets.right += inset } diff --git a/submodules/ItemListUI/Sources/ItemListItem.swift b/submodules/ItemListUI/Sources/ItemListItem.swift index bbf501135c..6f4f3d824b 100644 --- a/submodules/ItemListUI/Sources/ItemListItem.swift +++ b/submodules/ItemListUI/Sources/ItemListItem.swift @@ -15,6 +15,10 @@ public protocol ItemListItem { var requestsNoInset: Bool { get } } +public protocol ItemListRevealOptionsStatefulItem: ItemListItem { + var hasActiveRevealOptions: Bool { get } +} + public extension ItemListItem { //let accessoryItem: ListViewAccessoryItem? @@ -62,10 +66,14 @@ public enum ItemListNeighbor { public struct ItemListNeighbors { public var top: ItemListNeighbor public var bottom: ItemListNeighbor + public var topHasActiveRevealOptions: Bool + public var bottomHasActiveRevealOptions: Bool - public init(top: ItemListNeighbor, bottom: ItemListNeighbor) { + public init(top: ItemListNeighbor, bottom: ItemListNeighbor, topHasActiveRevealOptions: Bool = false, bottomHasActiveRevealOptions: Bool = false) { self.top = top self.bottom = bottom + self.topHasActiveRevealOptions = topHasActiveRevealOptions + self.bottomHasActiveRevealOptions = bottomHasActiveRevealOptions } } @@ -108,7 +116,12 @@ public func itemListNeighbors(item: ItemListItem, topItem: ItemListItem?, bottom bottomNeighbor = .none } - return ItemListNeighbors(top: topNeighbor, bottom: bottomNeighbor) + return ItemListNeighbors( + top: topNeighbor, + bottom: bottomNeighbor, + topHasActiveRevealOptions: (topItem as? ItemListRevealOptionsStatefulItem)?.hasActiveRevealOptions ?? false, + bottomHasActiveRevealOptions: (bottomItem as? ItemListRevealOptionsStatefulItem)?.hasActiveRevealOptions ?? false + ) } public func itemListNeighborsPlainInsets(_ neighbors: ItemListNeighbors) -> UIEdgeInsets { @@ -164,7 +177,7 @@ public func itemListNeighborsGroupedInsets(_ neighbors: ItemListNeighbors, _ par } public func itemListHasRoundedBlockLayout(_ params: ListViewItemLayoutParams) -> Bool { - return params.width >= 350.0 + return params.width >= 320.0 } public final class ItemListPresentationData: Equatable { diff --git a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift index 0554cbac28..5f2d5e233b 100644 --- a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift +++ b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift @@ -8,7 +8,7 @@ public enum ItemListRevealOptionIcon: Equatable { case none case image(image: UIImage) case animation(animation: String, scale: CGFloat, offset: CGFloat, replaceColors: [UInt32]?, flip: Bool) - + public static func ==(lhs: ItemListRevealOptionIcon, rhs: ItemListRevealOptionIcon) -> Bool { switch lhs { case .none: @@ -38,16 +38,18 @@ public struct ItemListRevealOption: Equatable { public let title: String public let icon: ItemListRevealOptionIcon public let color: UIColor + public let iconColor: UIColor public let textColor: UIColor - - public init(key: Int32, title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) { + + public init(key: Int32, title: String, icon: ItemListRevealOptionIcon, color: UIColor, iconColor: UIColor, textColor: UIColor) { self.key = key self.title = title self.icon = icon self.color = color + self.iconColor = iconColor self.textColor = textColor } - + public static func ==(lhs: ItemListRevealOption, rhs: ItemListRevealOption) -> Bool { if lhs.key != rhs.key { return false @@ -58,6 +60,9 @@ public struct ItemListRevealOption: Equatable { if !lhs.color.isEqual(rhs.color) { return false } + if !lhs.iconColor.isEqual(rhs.iconColor) { + return false + } if !lhs.textColor.isEqual(rhs.textColor) { return false } @@ -68,45 +73,156 @@ public struct ItemListRevealOption: Equatable { } } -private let titleFontWithIcon = Font.medium(13.0) -private let titleFontWithoutIcon = Font.regular(17.0) +private let titleFont = Font.regular(11.0) +private let iconlessTitleFont = Font.regular(13.0) -private enum ItemListRevealOptionAlignment { - case left - case right +private let spacing: CGFloat = 10.0 +private let edgeInset: CGFloat = 10.0 +private let ritleSpacing: CGFloat = 4.0 +private let revealStartOverlap: CGFloat = 12.0 +private let revealEndDistance: CGFloat = 10.0 +private let expandedActivationWidthFactor: CGFloat = 3.0 +private let expandedTransitionDistance: CGFloat = 16.0 +private let iconlessTitleExpandedHorizontalPadding: CGFloat = 8.0 +private let iconlessTitleHorizontalPadding: CGFloat = 8.0 +private let iconAnimationResponse: CGFloat = 18.0 +private let iconAnimationSnapDistance: CGFloat = 0.5 + +private extension ItemListRevealOptionIcon { + var hasVisualIcon: Bool { + switch self { + case .none: + return false + case .image, .animation: + return true + } + } +} + +private struct ItemListRevealOptionLayoutMetrics { + let shapeSize: CGSize + let slotWidth: CGFloat + let titleWidth: CGFloat + let iconMaxSide: CGFloat + let cornerRadius: CGFloat + let expandedIconInset: CGFloat + + var contentHeight: CGFloat { + return self.shapeSize.height + ritleSpacing + ceil(titleFont.lineHeight) + } + + var slotShapeInset: CGFloat { + return floorToScreenPixels((self.slotWidth - self.shapeSize.width) / 2.0) + } + + static func metrics(for height: CGFloat, hasVisualIcons: Bool) -> ItemListRevealOptionLayoutMetrics { + let regularShapeSize = CGSize(width: 50.0, height: 50.0) + let compactShapeSize = CGSize(width: 60.0, height: 32.0) + let regularContentHeight = regularShapeSize.height + ritleSpacing + ceil(titleFont.lineHeight) + if height < regularContentHeight || !hasVisualIcons { + return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 24.0, cornerRadius: 16.0, expandedIconInset: 16.0) + } else { + return ItemListRevealOptionLayoutMetrics(shapeSize: regularShapeSize, slotWidth: 60.0, titleWidth: 60.0, iconMaxSide: 40.0, cornerRadius: 25.0, expandedIconInset: 20.0) + } + } + + func withGroupTitleWidth(_ maxTitleWidth: CGFloat) -> ItemListRevealOptionLayoutMetrics { + if maxTitleWidth <= self.shapeSize.width - iconlessTitleExpandedHorizontalPadding { + return self + } + + let updatedShapeWidth = ceil(maxTitleWidth + iconlessTitleExpandedHorizontalPadding) + let slotWidthDelta = self.slotWidth - self.shapeSize.width + return ItemListRevealOptionLayoutMetrics( + shapeSize: CGSize(width: updatedShapeWidth, height: self.shapeSize.height), + slotWidth: updatedShapeWidth + slotWidthDelta, + titleWidth: max(self.titleWidth, updatedShapeWidth - iconlessTitleExpandedHorizontalPadding), + iconMaxSide: self.iconMaxSide, + cornerRadius: self.cornerRadius, + expandedIconInset: self.expandedIconInset + ) + } + + func revealWidth(count: Int) -> CGFloat { + if count == 0 { + return 0.0 + } + return edgeInset * 2.0 + self.shapeSize.width * CGFloat(count) + spacing * CGFloat(count - 1) + } +} + +private func clampToUnitInterval(_ value: CGFloat) -> CGFloat { + return max(0.0, min(1.0, value)) +} + +private func frameCenter(_ frame: CGRect) -> CGPoint { + return CGPoint(x: frame.midX, y: frame.midY) } private final class ItemListRevealOptionNode: ASDisplayNode { + private let contentContainerNode: ASDisplayNode private let backgroundNode: ASDisplayNode private let highlightNode: ASDisplayNode - private let titleNode: ASTextNode + private let titleNode: ImmediateTextNode private let iconNode: ASImageNode? private let animationNode: SimpleAnimationNode? - + private let enableAnimations: Bool - + private let displaysTitleInsidePill: Bool + private var animationScale: CGFloat = 1.0 private var animationNodeOffset: CGFloat = 0.0 private var animationNodeFlip = false - var alignment: ItemListRevealOptionAlignment? + + private var contentAnimationLink: SharedDisplayLinkDriver.Link? + private weak var manuallyAnimatedContentNode: ASDisplayNode? + private var currentContentCenter: CGPoint? + private var targetContentCenter: CGPoint? + + private var didApplyLayout = false var isExpanded: Bool = false - - init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor, enableAnimations: Bool) { + + var hasAppliedLayout: Bool { + return self.didApplyLayout + } + + var titleWidthForGroupPillSizing: CGFloat { + var titleWidth = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width + if self.displaysTitleInsidePill { + titleWidth += iconlessTitleHorizontalPadding + } + return titleWidth + } + + init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, iconColor: UIColor, textColor: UIColor, enableAnimations: Bool) { + self.contentContainerNode = ASDisplayNode() self.backgroundNode = ASDisplayNode() self.highlightNode = ASDisplayNode() - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: title, font: icon == .none ? titleFontWithoutIcon : titleFontWithIcon, textColor: textColor) - + + self.titleNode = ImmediateTextNode() + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.truncationMode = .byTruncatingTail + self.titleNode.textAlignment = .center + + let displaysTitleInsidePill: Bool + if case .none = icon { + displaysTitleInsidePill = true + } else { + displaysTitleInsidePill = false + } + self.displaysTitleInsidePill = displaysTitleInsidePill + self.titleNode.attributedText = NSAttributedString(string: title, font: displaysTitleInsidePill ? iconlessTitleFont : titleFont, textColor: displaysTitleInsidePill ? iconColor : textColor) + self.enableAnimations = enableAnimations - + switch icon { case let .image(image): let iconNode = ASImageNode() - iconNode.image = generateTintedImage(image: image, color: textColor) + iconNode.image = generateTintedImage(image: image, color: iconColor) self.iconNode = iconNode self.animationNode = nil - + case let .animation(animation, scale, offset, replaceColors, flip): self.animationScale = scale self.iconNode = nil @@ -116,7 +232,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode { colors[colorToReplace] = color.rgb } } - self.animationNode = SimpleAnimationNode(animationName: animation, replaceColors: colors, size: CGSize(width: 79.0, height: 79.0), playOnce: true) + self.animationNode = SimpleAnimationNode(animationName: animation, replaceColors: colors, size: CGSize(width: 66.0, height: 66.0), playOnce: true) if !enableAnimations { self.animationNode!.seekToEnd() } @@ -126,28 +242,34 @@ private final class ItemListRevealOptionNode: ASDisplayNode { self.animationNodeOffset = offset self.animationNodeFlip = flip break - + case .none: self.iconNode = nil self.animationNode = nil } - + super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.titleNode) + + self.contentContainerNode.layer.allowsGroupOpacity = true + self.addSubnode(self.contentContainerNode) + self.contentContainerNode.addSubnode(self.backgroundNode) + self.contentContainerNode.addSubnode(self.titleNode) if let iconNode = self.iconNode { - self.addSubnode(iconNode) + self.contentContainerNode.addSubnode(iconNode) } else if let animationNode = self.animationNode { - self.addSubnode(animationNode) + self.contentContainerNode.addSubnode(animationNode) } self.backgroundNode.backgroundColor = color self.highlightNode.backgroundColor = color.withMultipliedBrightnessBy(0.9) } - + + deinit { + self.stopManualContentAnimation() + } + func setHighlighted(_ highlighted: Bool) { if highlighted { - self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode) + self.contentContainerNode.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode) self.highlightNode.layer.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3) self.highlightNode.alpha = 1.0 } else { @@ -155,145 +277,294 @@ private final class ItemListRevealOptionNode: ASDisplayNode { self.highlightNode.alpha = 0.0 } } - + func resetAnimation() { self.animationNode?.reset() + self.stopManualContentAnimation() } - - func updateLayout(isFirst: Bool, isLeft: Bool, baseSize: CGSize, alignment: ItemListRevealOptionAlignment, isExpanded: Bool, extendedWidth: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool, revealFactor: CGFloat, animateIconMovement: Bool) { - self.highlightNode.frame = CGRect(origin: CGPoint(), size: baseSize) - - var animateAdditive = false - if additive && transition.isAnimated && self.isExpanded != isExpanded { - animateAdditive = true + + private func currentContentPresentationCenter(contentNode: ASDisplayNode) -> CGPoint { + return contentNode.layer.presentation()?.position ?? contentNode.position + } + + private func isManualContentAnimationAtTarget(center: CGPoint) -> Bool { + guard let targetContentCenter = self.targetContentCenter else { + return true } - + let centerDeltaX = targetContentCenter.x - center.x + let centerDeltaY = targetContentCenter.y - center.y + let centerDistance = sqrt(centerDeltaX * centerDeltaX + centerDeltaY * centerDeltaY) + return centerDistance <= iconAnimationSnapDistance + } + + private func stopManualContentAnimation() { + self.contentAnimationLink?.isPaused = true + self.contentAnimationLink?.invalidate() + self.contentAnimationLink = nil + self.manuallyAnimatedContentNode = nil + self.currentContentCenter = nil + self.targetContentCenter = nil + } + + private func updateManualContentCenter(contentNode: ASDisplayNode, targetCenter: CGPoint, forceImmediate: Bool) { + contentNode.layer.removeAnimation(forKey: "position") + + if self.manuallyAnimatedContentNode !== contentNode || self.currentContentCenter == nil { + self.currentContentCenter = self.currentContentPresentationCenter(contentNode: contentNode) + self.manuallyAnimatedContentNode = contentNode + } + + self.targetContentCenter = targetCenter + + if forceImmediate { + contentNode.position = targetCenter + self.stopManualContentAnimation() + return + } + + if let currentContentCenter = self.currentContentCenter, self.isManualContentAnimationAtTarget(center: currentContentCenter) { + contentNode.position = targetCenter + self.stopManualContentAnimation() + return + } + + if self.contentAnimationLink == nil { + self.contentAnimationLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in + self?.tickManualContentAnimation(deltaTime: deltaTime) + }) + self.contentAnimationLink?.isPaused = false + } + } + + private func tickManualContentAnimation(deltaTime: CGFloat) { + guard let contentNode = self.manuallyAnimatedContentNode, let currentContentCenter = self.currentContentCenter, let targetContentCenter = self.targetContentCenter else { + self.stopManualContentAnimation() + return + } + + let clampedDeltaTime = min(0.05, max(0.0, deltaTime)) + let progress = 1.0 - exp(-clampedDeltaTime * iconAnimationResponse) + let updatedCenter = CGPoint( + x: currentContentCenter.x + (targetContentCenter.x - currentContentCenter.x) * progress, + y: currentContentCenter.y + (targetContentCenter.y - currentContentCenter.y) * progress + ) + + if self.isManualContentAnimationAtTarget(center: updatedCenter) { + contentNode.position = targetContentCenter + self.stopManualContentAnimation() + } else { + self.currentContentCenter = updatedCenter + contentNode.position = updatedCenter + } + } + + func updateLayout(isLeft: Bool, isPrimary: Bool, metrics: ItemListRevealOptionLayoutMetrics, revealProgress: CGFloat, overswipeProgress: CGFloat, expandedProgress: CGFloat, isStretched: Bool, isExpanded: Bool, transition: ContainedViewLayoutTransition) { + let didApplyLayout = self.didApplyLayout + let bounds = CGRect(origin: CGPoint(), size: self.bounds.size) + transition.updateFrame(node: self.contentContainerNode, frame: bounds) + + let titleSize: CGSize + if self.titleNode.frame.isEmpty { + titleSize = self.titleNode.updateLayout(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + } else { + titleSize = self.titleNode.cachedLayout?.size ?? CGSize() + } + + let pillSize = metrics.shapeSize + let shapeY: CGFloat + if self.displaysTitleInsidePill { + shapeY = floorToScreenPixels((bounds.height - pillSize.height) / 2.0) + } else { + let contentHeight = pillSize.height + ritleSpacing + titleSize.height + shapeY = floorToScreenPixels((bounds.height - contentHeight) / 2.0) + } + + let shapeFrameX: CGFloat + if isStretched { + shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width + } else { + shapeFrameX = floorToScreenPixels((metrics.slotWidth - pillSize.width) / 2.0) + } + let shapeFrame = CGRect(origin: CGPoint(x: shapeFrameX, y: shapeY), size: pillSize) let backgroundFrame: CGRect - if isFirst { - backgroundFrame = CGRect(origin: CGPoint(x: isLeft ? -400.0 : 0.0, y: 0.0), size: CGSize(width: extendedWidth + 400.0, height: baseSize.height)) + if isStretched { + backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: shapeY), size: CGSize(width: bounds.width, height: pillSize.height)) } else { - backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height)) + backgroundFrame = shapeFrame } - let deltaX: CGFloat - if animateAdditive { - let previousFrame = self.backgroundNode.frame - self.backgroundNode.frame = backgroundFrame - if isLeft { - deltaX = previousFrame.width - backgroundFrame.width - } else { - deltaX = -(previousFrame.width - backgroundFrame.width) - } - if !animateIconMovement { - transition.animatePositionAdditive(node: self.backgroundNode, offset: CGPoint(x: deltaX, y: 0.0)) - } - } else { - deltaX = 0.0 - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - } - - self.alignment = alignment + + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + transition.updateFrame(node: self.highlightNode, frame: backgroundFrame) + transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: metrics.cornerRadius) + transition.updateCornerRadius(node: self.highlightNode, cornerRadius: metrics.cornerRadius) + + let wasExpanded = self.isExpanded self.isExpanded = isExpanded - let titleSize = self.titleNode.calculatedSize - var contentRect = CGRect(origin: CGPoint(), size: baseSize) - switch alignment { - case .left: - contentRect.origin.x = 0.0 - case .right: - contentRect.origin.x = extendedWidth - contentRect.width + self.didApplyLayout = true + let contentAlpha: CGFloat + if isPrimary { + contentAlpha = revealProgress + } else { + contentAlpha = revealProgress * (1.0 - 0.3 * overswipeProgress) } - + let contentScale = 0.3 + 0.7 * revealProgress + transition.updateAlpha(node: self.contentContainerNode, alpha: contentAlpha) + transition.updateTransform(node: self.contentContainerNode, transform: CGAffineTransform(scaleX: contentScale, y: contentScale)) + + let titleAlpha: CGFloat = isPrimary && !self.displaysTitleInsidePill ? (1.0 - expandedProgress) : 1.0 + var didApplyManualContentCenter = false + + let centeredIconCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX + let iconCenterX: CGFloat + if isPrimary && expandedProgress > 0.0 { + let expandedIconCenterX: CGFloat + if isLeft { + expandedIconCenterX = backgroundFrame.maxX - metrics.expandedIconInset + } else { + expandedIconCenterX = backgroundFrame.minX + metrics.expandedIconInset + } + iconCenterX = centeredIconCenterX + (expandedIconCenterX - centeredIconCenterX) * expandedProgress + } else { + iconCenterX = centeredIconCenterX + } + let iconCenterY = backgroundFrame.midY + if let animationNode = self.animationNode { - let imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale) - let iconOffset: CGFloat = -2.0 + self.animationNodeOffset - let titleIconSpacing: CGFloat = 11.0 - let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize) - if animateAdditive { - let iconOffsetX = animateIconMovement ? animationNode.frame.minX - iconFrame.minX : deltaX - animationNode.frame = iconFrame - transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: iconOffsetX, y: 0.0)) + var imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale) + let imageMaxSide = max(imageSize.width, imageSize.height) + if imageMaxSide > metrics.iconMaxSide { + let imageScale = metrics.iconMaxSide / imageMaxSide * 1.5 + imageSize = CGSize(width: floorToScreenPixels(imageSize.width * imageScale), height: floorToScreenPixels(imageSize.height * imageScale)) + } + let scaleFraction = imageSize.height / 56.0 + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + (6.0 + self.animationNodeOffset) * scaleFraction), size: imageSize) + + if isPrimary { + didApplyManualContentCenter = true + transition.updateBounds(node: animationNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + let targetCenter = frameCenter(iconFrame) + if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: animationNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === animationNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: animationNode, targetCenter: targetCenter, forceImmediate: false) + } else { + self.stopManualContentAnimation() + transition.updatePosition(node: animationNode, position: targetCenter) + } } else { transition.updateFrame(node: animationNode, frame: iconFrame) } - - let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize) - if animateAdditive { - let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX - self.titleNode.frame = titleFrame - transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0)) - } else { - transition.updateFrame(node: self.titleNode, frame: titleFrame) - } - if self.enableAnimations { - if (abs(revealFactor) >= 0.4) { + if revealProgress >= 0.4 { animationNode.play() - } else if abs(revealFactor) < CGFloat.ulpOfOne && !transition.isAnimated { + } else if revealProgress < CGFloat.ulpOfOne && !transition.isAnimated { animationNode.reset() } } } else if let iconNode = self.iconNode, let imageSize = iconNode.image?.size { - let iconOffset: CGFloat = -9.0 - let titleIconSpacing: CGFloat = 11.0 - let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize) - if animateAdditive { - let iconOffsetX = animateIconMovement ? iconNode.frame.minX - iconFrame.minX : deltaX - iconNode.frame = iconFrame - transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: iconOffsetX, y: 0.0)) + var fittedSize = imageSize + let imageMaxSide = max(fittedSize.width, fittedSize.height) + if imageMaxSide > metrics.iconMaxSide { + let imageScale = metrics.iconMaxSide / imageMaxSide + fittedSize = CGSize(width: floorToScreenPixels(fittedSize.width * imageScale), height: floorToScreenPixels(fittedSize.height * imageScale)) + } + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - fittedSize.width / 2.0), y: floorToScreenPixels(iconCenterY - fittedSize.height / 2.0)), size: fittedSize) + if isPrimary { + didApplyManualContentCenter = true + transition.updateBounds(node: iconNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + let targetCenter = frameCenter(iconFrame) + if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: iconNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === iconNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: iconNode, targetCenter: targetCenter, forceImmediate: false) + } else { + self.stopManualContentAnimation() + transition.updatePosition(node: iconNode, position: targetCenter) + } } else { transition.updateFrame(node: iconNode, frame: iconFrame) } - - let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize) - if animateAdditive { - let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX - self.titleNode.frame = titleFrame - transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0)) + } + transition.updateAlpha(node: self.titleNode, alpha: titleAlpha) + + let titleFrame: CGRect + if self.displaysTitleInsidePill { + titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - titleSize.width / 2.0), y: floorToScreenPixels(backgroundFrame.midY - titleSize.height / 2.0)), size: titleSize) + if isPrimary { + didApplyManualContentCenter = true + transition.updateBounds(node: self.titleNode, bounds: CGRect(origin: CGPoint(), size: titleFrame.size)) + let titleCenterX: CGFloat + if expandedProgress > 0.0 { + let titleEdgeInset = max(metrics.expandedIconInset, titleSize.width / 2.0 + iconlessTitleExpandedHorizontalPadding) + let expandedTitleCenterX: CGFloat + if isLeft { + expandedTitleCenterX = backgroundFrame.maxX - titleEdgeInset + } else { + expandedTitleCenterX = backgroundFrame.minX + titleEdgeInset + } + titleCenterX = backgroundFrame.midX + (expandedTitleCenterX - backgroundFrame.midX) * expandedProgress + } else { + titleCenterX = backgroundFrame.midX + } + let targetCenter = CGPoint(x: titleCenterX, y: backgroundFrame.midY) + if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: self.titleNode, targetCenter: targetCenter, forceImmediate: false) + } else if self.manuallyAnimatedContentNode === self.titleNode && self.contentAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne { + self.updateManualContentCenter(contentNode: self.titleNode, targetCenter: targetCenter, forceImmediate: false) + } else { + self.stopManualContentAnimation() + transition.updatePosition(node: self.titleNode, position: targetCenter) + } } else { transition.updateFrame(node: self.titleNode, frame: titleFrame) } } else { - let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.minY + floor((baseSize.height - titleSize.height) / 2.0)), size: titleSize) - if animateAdditive { - let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX - self.titleNode.frame = titleFrame - transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0)) - } else { - transition.updateFrame(node: self.titleNode, frame: titleFrame) - } + let titleCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX + titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(titleCenterX - titleSize.width / 2.0), y: shapeFrame.maxY + ritleSpacing), size: titleSize) + transition.updateFrame(node: self.titleNode, frame: titleFrame) + } + + if !didApplyManualContentCenter { + self.stopManualContentAnimation() } } - + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - let titleSize = self.titleNode.measure(constrainedSize) - var maxWidth = titleSize.width - if let iconNode = self.iconNode, let image = iconNode.image { - maxWidth = max(image.size.width, maxWidth) - } - return CGSize(width: max(74.0, maxWidth + 20.0), height: constrainedSize.height) + let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: !self.displaysTitleInsidePill) + return CGSize(width: metrics.slotWidth, height: constrainedSize.height) } } public final class ItemListRevealOptionsNode: ASDisplayNode { private let optionSelected: (ItemListRevealOption) -> Void private let tapticAction: () -> Void - + private let clippingContainerNode: ASDisplayNode + private let optionsContainerNode: ASDisplayNode + private var options: [ItemListRevealOption] = [] private var isLeft: Bool = false - + private var optionNodes: [ItemListRevealOptionNode] = [] private var revealOffset: CGFloat = 0.0 private var sideInset: CGFloat = 0.0 - + private var currentMetrics: (containerSize: CGSize, metrics: ItemListRevealOptionLayoutMetrics)? + public init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) { self.optionSelected = optionSelected self.tapticAction = tapticAction - + self.clippingContainerNode = ASDisplayNode() + self.optionsContainerNode = ASDisplayNode() + super.init() + + self.clippingContainerNode.clipsToBounds = true + self.addSubnode(self.clippingContainerNode) + self.clippingContainerNode.addSubnode(self.optionsContainerNode) } - + override public func didLoad() { super.didLoad() - + let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) gestureRecognizer.highlight = { [weak self] location in guard let strongSelf = self, let location = location else { @@ -311,7 +582,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { } self.view.addGestureRecognizer(gestureRecognizer) } - + public func setOptions(_ options: [ItemListRevealOption], isLeft: Bool, enableAnimations: Bool) { if self.options != options || self.isLeft != isLeft { self.options = options @@ -320,124 +591,147 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { node.removeFromSupernode() } self.optionNodes = options.map { option in - return ItemListRevealOptionNode(title: option.title, icon: option.icon, color: option.color, textColor: option.textColor, enableAnimations: enableAnimations) + return ItemListRevealOptionNode(title: option.title, icon: option.icon, color: option.color, iconColor: option.iconColor, textColor: option.textColor, enableAnimations: enableAnimations) } if isLeft { for node in self.optionNodes.reversed() { - self.addSubnode(node) + self.optionsContainerNode.addSubnode(node) } } else { for node in self.optionNodes { - self.addSubnode(node) + self.optionsContainerNode.addSubnode(node) } } + self.currentMetrics = nil self.invalidateCalculatedLayout() } } - - override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - var maxWidth: CGFloat = 0.0 - for node in self.optionNodes { - let nodeSize = node.measure(constrainedSize) - maxWidth = max(nodeSize.width, maxWidth) + + private func layoutMetrics(for containerSize: CGSize) -> ItemListRevealOptionLayoutMetrics { + if let currentMetrics = self.currentMetrics, currentMetrics.containerSize == containerSize { + return currentMetrics.metrics } - return CGSize(width: maxWidth * CGFloat(self.optionNodes.count), height: constrainedSize.height) + + let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: containerSize.height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon })) + let maxTitleWidth = self.optionNodes.reduce(0.0) { result, node in + return max(result, node.titleWidthForGroupPillSizing) + } + let updatedMetrics = metrics.withGroupTitleWidth(maxTitleWidth) + self.currentMetrics = (containerSize, updatedMetrics) + return updatedMetrics } - + + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let metrics = self.layoutMetrics(for: constrainedSize) + for node in self.optionNodes { + let _ = node.measure(constrainedSize) + } + return CGSize(width: metrics.revealWidth(count: self.optionNodes.count), height: constrainedSize.height) + } + public func updateRevealOffset(offset: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition) { self.revealOffset = offset self.sideInset = sideInset self.updateNodesLayout(transition: transition) } - + private func updateNodesLayout(transition: ContainedViewLayoutTransition) { let size = self.bounds.size if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty { return } - let basicNodeWidth = floor((size.width - abs(self.sideInset)) / CGFloat(self.optionNodes.count)) - let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionNodes.count - 1) - let revealFactor = self.revealOffset / size.width - let boundaryRevealFactor: CGFloat - if self.optionNodes.count > 2 { - boundaryRevealFactor = 1.0 + 16.0 / size.width - } else { - boundaryRevealFactor = 1.0 + basicNodeWidth / size.width - } - let startingOffset: CGFloat + let metrics = self.layoutMetrics(for: size) + let revealedDistance = abs(self.revealOffset) + let boundedRevealedDistance = min(revealedDistance, size.width) + let overswipeDistance = max(0.0, revealedDistance - size.width) + let overswipeProgress = clampToUnitInterval(overswipeDistance / expandedTransitionDistance) + let expandedActivationDistance = 50.0 * (expandedActivationWidthFactor - 1.0) + let primaryIndex = self.isLeft ? 0 : self.optionNodes.count - 1 + let stride = metrics.shapeSize.width + spacing + + let clippingFrameX: CGFloat if self.isLeft { - startingOffset = size.width + max(0.0, abs(revealFactor) - 1.0) * size.width + clippingFrameX = max(0.0, size.width - revealedDistance) } else { - startingOffset = 0.0 + clippingFrameX = 0.0 } - + let clippingFrame = CGRect(origin: CGPoint(x: clippingFrameX, y: 0.0), size: CGSize(width: revealedDistance, height: size.height)) + transition.updateFrame(node: self.clippingContainerNode, frame: clippingFrame) + transition.updateFrame(node: self.optionsContainerNode, frame: CGRect(origin: CGPoint(x: -clippingFrameX, y: 0.0), size: CGSize(width: max(size.width, revealedDistance), height: size.height))) + let animated = transition.isAnimated var completionCount = self.optionNodes.count let intermediateCompletion = { - if completionCount == 0 && animated && abs(revealFactor) < CGFloat.ulpOfOne { + if completionCount == 0 && animated && revealedDistance < CGFloat.ulpOfOne { for node in self.optionNodes { node.resetAnimation() } } } - + var i = self.isLeft ? (self.optionNodes.count - 1) : 0 while i >= 0 && i < self.optionNodes.count { let node = self.optionNodes[i] - let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth - var nodeTransition = transition - var isExpanded = false - if (self.isLeft && i == 0) || (!self.isLeft && i == self.optionNodes.count - 1) { - if abs(revealFactor) > boundaryRevealFactor { - isExpanded = true + let isPrimary = i == primaryIndex + let isStretched = isPrimary && overswipeDistance > CGFloat.ulpOfOne + let isExpanded = isPrimary && overswipeDistance > expandedActivationDistance + let expandedProgress: CGFloat = isExpanded ? 1.0 : 0.0 + if node.hasAppliedLayout && node.isExpanded != isExpanded && !transition.isAnimated { + self.tapticAction() + } + + let baseCircleFrame: CGRect + let nodeFrame: CGRect + let revealProgress: CGFloat + + if self.isLeft { + let baseCircleLeft = size.width - boundedRevealedDistance + self.sideInset + edgeInset + CGFloat(i) * stride + baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleLeft, y: 0.0), size: metrics.shapeSize) + let distanceFromShutterEdge = size.width - baseCircleFrame.maxX + revealProgress = clampToUnitInterval((distanceFromShutterEdge + revealStartOverlap) / (revealStartOverlap + revealEndDistance)) + + if isStretched { + let primaryLeft = size.width - boundedRevealedDistance + self.sideInset + edgeInset + let primaryRight: CGFloat + if self.optionNodes.count > 1 { + let neighborLeft = primaryLeft + stride + overswipeDistance + primaryRight = max(primaryLeft + metrics.shapeSize.width, neighborLeft - spacing) + } else { + primaryRight = primaryLeft + metrics.shapeSize.width + overswipeDistance + } + nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(primaryLeft), y: 0.0), size: CGSize(width: max(metrics.shapeSize.width, primaryRight - primaryLeft), height: size.height)) + } else { + let circleLeft = baseCircleLeft + (isPrimary ? 0.0 : overswipeDistance) + nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(circleLeft - metrics.slotShapeInset), y: 0.0), size: CGSize(width: metrics.slotWidth, height: size.height)) } - } - if let _ = node.alignment, node.isExpanded != isExpanded { - nodeTransition = transition.isAnimated ? transition : .animated(duration: 0.2, curve: .easeInOut) - if !transition.isAnimated { - self.tapticAction() - } - } - - var sideInset: CGFloat = 0.0 - if i == self.optionNodes.count - 1 { - sideInset = self.sideInset - } - - let extendedWidth: CGFloat - let nodeLeftOffset: CGFloat - if isExpanded { - nodeLeftOffset = 0.0 - extendedWidth = size.width * max(1.0, abs(revealFactor)) - } else if self.isLeft { - let offset = basicNodeWidth * CGFloat(self.optionNodes.count - 1 - i) - extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor)) - nodeLeftOffset = startingOffset - extendedWidth - floorToScreenPixels(offset * abs(revealFactor)) } else { - let offset = basicNodeWidth * CGFloat(i) - extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor)) - nodeLeftOffset = startingOffset + floorToScreenPixels(offset * abs(revealFactor)) + let baseCircleRight = revealedDistance + self.sideInset - edgeInset - CGFloat(self.optionNodes.count - 1 - i) * stride + baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleRight - metrics.shapeSize.width, y: 0.0), size: metrics.shapeSize) + revealProgress = clampToUnitInterval((baseCircleFrame.minX + revealStartOverlap) / (revealStartOverlap + revealEndDistance)) + + if isStretched { + let primaryRight = revealedDistance + self.sideInset - edgeInset + let primaryLeft: CGFloat + if self.optionNodes.count > 1 { + let neighborRight = primaryRight - stride - overswipeDistance + primaryLeft = min(primaryRight - metrics.shapeSize.width, neighborRight + spacing) + } else { + primaryLeft = primaryRight - metrics.shapeSize.width - overswipeDistance + } + nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(primaryLeft), y: 0.0), size: CGSize(width: max(metrics.shapeSize.width, primaryRight - primaryLeft), height: size.height)) + } else { + let circleLeft = baseCircleFrame.minX - (isPrimary ? 0.0 : overswipeDistance) + nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(circleLeft - metrics.slotShapeInset), y: 0.0), size: CGSize(width: metrics.slotWidth, height: size.height)) + } } - - transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: nodeLeftOffset, y: 0.0), size: CGSize(width: extendedWidth, height: size.height)), completion: { _ in + + transition.updateFrame(node: node, frame: nodeFrame, completion: { _ in completionCount -= 1 intermediateCompletion() - }) - - var nodeAlignment: ItemListRevealOptionAlignment - if (self.optionNodes.count > 1) { - nodeAlignment = self.isLeft ? .right : .left - } else { - if (self.isLeft) { - nodeAlignment = isExpanded ? .right : .left - } else { - nodeAlignment = isExpanded ? .left : .right - } - } - let animateIconMovement = self.optionNodes.count == 1 - node.updateLayout(isFirst: (self.isLeft && i == 0) || (!self.isLeft && i == self.optionNodes.count - 1), isLeft: self.isLeft, baseSize: CGSize(width: nodeWidth, height: size.height), alignment: nodeAlignment, isExpanded: isExpanded, extendedWidth: extendedWidth, sideInset: sideInset, transition: nodeTransition, additive: !transition.isAnimated, revealFactor: revealFactor, animateIconMovement: animateIconMovement) - + + node.updateLayout(isLeft: self.isLeft, isPrimary: isPrimary, metrics: metrics, revealProgress: revealProgress, overswipeProgress: overswipeProgress, expandedProgress: expandedProgress, isStretched: isStretched, isExpanded: isExpanded, transition: transition) + if self.isLeft { i -= 1 } else { @@ -445,12 +739,12 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { } } } - + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0, case .tap = gesture { let location = recognizer.location(in: self.view) var selectedOption: Int? - + var i = self.isLeft ? 0 : (self.optionNodes.count - 1) while i >= 0 && i < self.optionNodes.count { self.optionNodes[i].setHighlighted(false) @@ -469,7 +763,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode { } } } - + public func isDisplayingExtendedAction() -> Bool { return self.optionNodes.contains(where: { $0.isExpanded }) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift index 5017682d7b..d59871b99b 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift @@ -124,6 +124,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { private let subtitleNode: TextNode private var item: ItemListCheckboxItem? + private var isHighlighted = false override public var controlsContainer: ASDisplayNode { return self.contentParentNode @@ -336,27 +337,31 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat + let bottomStripeIsHidden: Bool if item.zeroSeparatorInsets { bottomStripeInset = 0.0 + bottomStripeIsHidden = false } else { switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -384,12 +389,12 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { strongSelf.imageNode.image = nil } - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) if item.deleteAction != nil { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) } else { strongSelf.setRevealOptions((left: [], right: [])) } @@ -401,39 +406,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted && (self.item?.enabled ?? false) { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted && (self.item?.enabled ?? false) + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.4, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -446,10 +420,16 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { super.updateRevealOffset(offset: offset, transition: transition) - + transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(x: offset, y: self.contentContainerNode.frame.minY), size: self.contentContainerNode.bounds.size)) } + override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } + override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { self.setRevealOptionsOpened(false, animated: true) self.revealOptionsInteractivelyClosed() diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index fc62243b86..ab096a8865 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -74,7 +74,33 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemCompone public let tag: ItemListItemTag? public let shimmeringIndex: Int? - public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { + public init( + presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, + icon: UIImage? = nil, + context: AccountContext? = nil, + iconPeer: EnginePeer? = nil, + title: String, + attributedTitle: NSAttributedString? = nil, + enabled: Bool = true, + titleColor: ItemListDisclosureItemTitleColor = .primary, + titleFont: ItemListDisclosureItemTitleFont = .regular, + titleIcon: UIImage? = nil, + titleBadge: String? = nil, + label: String, + attributedLabel: NSAttributedString? = nil, + labelStyle: ItemListDisclosureLabelStyle = .text, + additionalDetailLabel: String? = nil, + additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, + sectionId: ItemListSectionId, + style: ItemListStyle, + disclosureStyle: ItemListDisclosureStyle = .arrow, + noInsets: Bool = false, + action: (() -> Void)?, + clearHighlightAutomatically: Bool = true, + tag: ItemListItemTag? = nil, + shimmeringIndex: Int? = nil + ) { self.presentationData = presentationData self.systemStyle = systemStyle self.icon = icon @@ -709,7 +735,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { centralContentHeight += additionalDetailLabelInfo.0.size.height } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - centralContentHeight) / 2.0) + 1.0), size: titleLayout.size) strongSelf.titleNode.textNode.frame = titleFrame if let updateBadgeImage = updatedLabelBadgeImage { @@ -739,7 +765,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { case .detailText, .multilineDetailText: labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) default: - labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floor((height - labelLayout.size.height) / 2.0)), size: labelLayout.size) + labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floorToScreenPixels((height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size) } strongSelf.labelNode.frame = labelFrame diff --git a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift index df2372c151..955a0d6201 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift @@ -67,17 +67,173 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD private var enableAnimations: Bool = true private var initialRevealOffset: CGFloat = 0.0 + private var hasActiveRevealGestureOffset: Bool = false public private(set) var revealOffset: CGFloat = 0.0 - + public private(set) var isRevealOptionsActive: Bool = false + public private(set) var isNextRevealOptionsActive: Bool = false + private weak var revealOptionsActivePreviousItemNode: ItemListRevealOptionsItemNode? + + private weak var revealOptionsTopSeparatorNode: ASDisplayNode? + private weak var revealOptionsBottomSeparatorNode: ASDisplayNode? + private var revealOptionsTopSeparatorBaseIsHidden: Bool = false + private var revealOptionsBottomSeparatorBaseIsHidden: Bool = false + private var revealOptionsTopSeparatorHiddenByPreviousRevealOptions: Bool = false + private var revealOptionsBottomSeparatorHiddenByNextRevealOptions: Bool = false + + private weak var revealOptionsHighlightedBackgroundNode: ASDisplayNode? + private var revealOptionsHighlightedBackgroundBaseFrame: CGRect? + private var revealOptionsHighlightedBackgroundTracksRevealOffset: Bool = false + private var revealOptionsHighlightedBackgroundActiveCornerRadius: CGFloat = 0.0 + private var recognizer: ItemListRevealOptionsGestureRecognizer? private var tapRecognizer: UITapGestureRecognizer? private var hapticFeedback: HapticFeedback? private var allowAnyDirection = false - + public var isDisplayingRevealedOptions: Bool { return !self.revealOffset.isZero } + + private func updateRevealOptionsActive(_ isActive: Bool, transition: ContainedViewLayoutTransition) { + if self.isRevealOptionsActive != isActive { + self.isRevealOptionsActive = isActive + self.updatePreviousRevealOptionsItemNode(isActive: isActive, transition: transition) + self.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + } + } + + private func updateNextRevealOptionsActive(_ isActive: Bool, transition: ContainedViewLayoutTransition) { + if self.isNextRevealOptionsActive != isActive { + self.isNextRevealOptionsActive = isActive + self.nextRevealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + } + } + + private func previousRevealOptionsItemNode() -> ItemListRevealOptionsItemNode? { + guard let subnodes = self.supernode?.subnodes else { + return nil + } + + if let index = self.index { + var candidate: ItemListRevealOptionsItemNode? + for subnode in subnodes { + guard let itemNode = subnode as? ItemListRevealOptionsItemNode, itemNode !== self, let itemIndex = itemNode.index, itemIndex < index else { + continue + } + if let currentCandidate = candidate, let candidateIndex = currentCandidate.index { + if itemIndex > candidateIndex { + candidate = itemNode + } + } else { + candidate = itemNode + } + } + if let candidate = candidate { + return candidate + } + } + + var candidate: ItemListRevealOptionsItemNode? + for subnode in subnodes { + guard let itemNode = subnode as? ItemListRevealOptionsItemNode, itemNode !== self else { + continue + } + if itemNode.frame.maxY <= self.frame.minY + UIScreenPixel { + if let currentCandidate = candidate { + if itemNode.frame.maxY > currentCandidate.frame.maxY { + candidate = itemNode + } + } else { + candidate = itemNode + } + } + } + return candidate + } + + private func updatePreviousRevealOptionsItemNode(isActive: Bool, transition: ContainedViewLayoutTransition) { + let previousItemNode = isActive ? self.previousRevealOptionsItemNode() : nil + if self.revealOptionsActivePreviousItemNode !== previousItemNode { + self.revealOptionsActivePreviousItemNode?.updateNextRevealOptionsActive(false, transition: transition) + self.revealOptionsActivePreviousItemNode = previousItemNode + } + previousItemNode?.updateNextRevealOptionsActive(isActive, transition: transition) + if !isActive { + self.revealOptionsActivePreviousItemNode = nil + } + } + + private func updateRevealOptionsSeparatorVisibility() { + self.revealOptionsTopSeparatorNode?.isHidden = self.revealOptionsTopSeparatorBaseIsHidden || self.revealOptionsTopSeparatorHiddenByPreviousRevealOptions || self.isRevealOptionsActive + self.revealOptionsBottomSeparatorNode?.isHidden = self.revealOptionsBottomSeparatorBaseIsHidden || self.revealOptionsBottomSeparatorHiddenByNextRevealOptions || self.isRevealOptionsActive || self.isNextRevealOptionsActive + } + + private func updateRevealOptionsHighlightedBackgroundGeometry(transition: ContainedViewLayoutTransition) { + guard let highlightedBackgroundNode = self.revealOptionsHighlightedBackgroundNode, let baseFrame = self.revealOptionsHighlightedBackgroundBaseFrame else { + return + } + + let frame: CGRect + if self.revealOptionsHighlightedBackgroundTracksRevealOffset && self.isRevealOptionsActive { + frame = baseFrame.offsetBy(dx: self.revealOffset, dy: 0.0) + } else { + frame = baseFrame + } + transition.updateFrame(node: highlightedBackgroundNode, frame: frame) + transition.updateCornerRadius(node: highlightedBackgroundNode, cornerRadius: self.isRevealOptionsActive ? self.revealOptionsHighlightedBackgroundActiveCornerRadius : 0.0) + } + + public func updateRevealOptionsSeparatorNodes(top: ASDisplayNode?, bottom: ASDisplayNode?, topIsHidden: Bool, bottomIsHidden: Bool, topHiddenByPreviousRevealOptions: Bool = false, bottomHiddenByNextRevealOptions: Bool = false) { + self.revealOptionsTopSeparatorNode = top + self.revealOptionsBottomSeparatorNode = bottom + self.revealOptionsTopSeparatorBaseIsHidden = topIsHidden + self.revealOptionsBottomSeparatorBaseIsHidden = bottomIsHidden + self.revealOptionsTopSeparatorHiddenByPreviousRevealOptions = topHiddenByPreviousRevealOptions + self.revealOptionsBottomSeparatorHiddenByNextRevealOptions = bottomHiddenByNextRevealOptions + self.updateRevealOptionsSeparatorVisibility() + } + + public func updateRevealOptionsHighlightedBackgroundFrame(_ highlightedBackgroundNode: ASDisplayNode, frame: CGRect, tracksRevealOffset: Bool = true, activeCornerRadius: CGFloat = 26.0, transition: ContainedViewLayoutTransition = .immediate) { + self.revealOptionsHighlightedBackgroundNode = highlightedBackgroundNode + self.revealOptionsHighlightedBackgroundBaseFrame = frame + self.revealOptionsHighlightedBackgroundTracksRevealOffset = tracksRevealOffset + self.revealOptionsHighlightedBackgroundActiveCornerRadius = activeCornerRadius + self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition) + } + + public func updateRevealOptionsHighlightedBackgroundNode(_ highlightedBackgroundNode: ASDisplayNode, isHighlighted: Bool, transition: ContainedViewLayoutTransition, aboveNodes: [ASDisplayNode?]) { + self.revealOptionsHighlightedBackgroundNode = highlightedBackgroundNode + if isHighlighted { + highlightedBackgroundNode.alpha = 1.0 + if highlightedBackgroundNode.supernode == nil { + var anchorNode: ASDisplayNode? + for node in aboveNodes { + if let node = node, node.supernode != nil { + anchorNode = node + break + } + } + if let anchorNode = anchorNode { + self.insertSubnode(highlightedBackgroundNode, aboveSubnode: anchorNode) + } else { + self.addSubnode(highlightedBackgroundNode) + } + } + } else if highlightedBackgroundNode.supernode != nil { + if transition.isAnimated { + highlightedBackgroundNode.layer.animateAlpha(from: highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak highlightedBackgroundNode] completed in + if completed { + highlightedBackgroundNode?.removeFromSupernode() + } + }) + highlightedBackgroundNode.alpha = 0.0 + } else { + highlightedBackgroundNode.removeFromSupernode() + } + } + self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition) + } override open var canBeSelected: Bool { return !self.isDisplayingRevealedOptions @@ -86,6 +242,10 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD override public init(layerBacked: Bool, rotated: Bool, seeThrough: Bool) { super.init(layerBacked: layerBacked, rotated: rotated, seeThrough: seeThrough) } + + deinit { + self.revealOptionsActivePreviousItemNode?.updateNextRevealOptionsActive(false, transition: .immediate) + } open var controlsContainer: ASDisplayNode { return self @@ -185,25 +345,27 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD } open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - /*if gestureRecognizer === self.recognizer && otherGestureRecognizer is InteractiveTransitionGestureRecognizer { - return true - }*/ return false } @objc private func revealTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring)) + self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0))) self.revealOptionsInteractivelyClosed() } } + private func revealSpringCurve(initialVelocity: CGFloat = 0.0) -> ContainedViewLayoutTransitionCurve { + return .customSpring(mass: 2.0, stiffness: 200.0, damping: 100.0, initialVelocity: initialVelocity) + } + @objc private func revealGesture(_ recognizer: ItemListRevealOptionsGestureRecognizer) { guard let (size, _, _) = self.validLayout else { return } switch recognizer.state { case .began: + self.hasActiveRevealGestureOffset = !self.revealOffset.isZero if let leftRevealNode = self.leftRevealNode { let revealSize = leftRevealNode.bounds.size let location = recognizer.location(in: self.view) @@ -235,16 +397,26 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD if self.leftRevealNode == nil && CGFloat(0.0).isLess(than: translation.x) { self.setupAndAddLeftRevealNode() self.revealOptionsInteractivelyOpened() + self.hasActiveRevealGestureOffset = true } else if self.rightRevealNode == nil && translation.x.isLess(than: 0.0) { self.setupAndAddRightRevealNode() self.revealOptionsInteractivelyOpened() + self.hasActiveRevealGestureOffset = true + } + if !translation.x.isZero { + self.hasActiveRevealGestureOffset = true } self.updateRevealOffsetInternal(offset: translation.x, transition: .immediate) - if self.leftRevealNode == nil && self.rightRevealNode == nil { + if self.leftRevealNode == nil && self.rightRevealNode == nil && !self.hasActiveRevealGestureOffset { self.revealOptionsInteractivelyClosed() } case .ended, .cancelled: + let hasActiveRevealGestureOffset = self.hasActiveRevealGestureOffset + self.hasActiveRevealGestureOffset = false guard let recognizer = self.recognizer else { + if hasActiveRevealGestureOffset { + self.revealOptionsInteractivelyClosed() + } break } @@ -273,7 +445,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD reveal = false selectedOption = self.revealOptions.left.first } else { - self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) + self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0))) } if let selectedOption = selectedOption { @@ -308,7 +480,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD reveal = false selectedOption = self.revealOptions.right.last } else { - self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring)) + self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0))) } if let selectedOption = selectedOption { @@ -318,6 +490,8 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD self.revealOptionsInteractivelyClosed() } } + } else if hasActiveRevealGestureOffset { + self.revealOptionsInteractivelyClosed() } default: break @@ -370,6 +544,9 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { self.validLayout = (size, leftInset, rightInset) + if self.isRevealOptionsActive { + self.updatePreviousRevealOptionsItemNode(isActive: true, transition: .immediate) + } if let leftRevealNode = self.leftRevealNode { var revealSize = leftRevealNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height)) @@ -386,6 +563,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD open func updateRevealOffsetInternal(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) { self.revealOffset = offset + self.updateRevealOptionsActive(!offset.isZero, transition: transition) guard let (size, leftInset, rightInset) = self.validLayout else { return } @@ -455,11 +633,21 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD } self.updateRevealOffset(offset: offset, transition: transition) + self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition) } open func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { } + + open func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + self.updateRevealOptionsSeparatorVisibility() + self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition) + } + + open func nextRevealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + self.updateRevealOptionsSeparatorVisibility() + } open func revealOptionsInteractivelyOpened() { @@ -476,7 +664,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD } let transition: ContainedViewLayoutTransition if animated { - transition = .animated(duration: 0.3, curve: .spring) + transition = .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)) } else { transition = .immediate } @@ -498,7 +686,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD open func animateRevealOptionsFill(completion: (() -> Void)? = nil) { if let validLayout = self.validLayout { self.layer.allowsGroupOpacity = true - self.updateRevealOffsetInternal(offset: -validLayout.0.width - 74.0, transition: .animated(duration: 0.3, curve: .spring), completion: { + self.updateRevealOffsetInternal(offset: -validLayout.0.width - 74.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)), completion: { self.layer.allowsGroupOpacity = false completion?() }) diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index b21392f866..c238ddb3a3 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -174,12 +174,15 @@ public class InfoItemNode: ListViewItemNode { self.labelNode = TextNode() self.labelNode.isUserInteractionEnabled = false + self.labelNode.displaysAsynchronously = false self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false self.textNode = TextNode() self.textNode.isUserInteractionEnabled = false + self.textNode.displaysAsynchronously = false self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = .staticText diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index bb6552e96f..3a05467b15 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -171,6 +171,10 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { public var tag: ItemListItemTag? { return self.item?.tag } + + public var contextSourceView: UIView { + return self.textNode?.view ?? self.switchNode.view + } public init(type: ItemListSwitchItemNodeType) { self.backgroundNode = ASDisplayNode() @@ -251,6 +255,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { }) }) } + + public func updateHasContextMenu(hasContextMenu: Bool) { + let transition: ContainedViewLayoutTransition + if hasContextMenu { + transition = .immediate + } else { + transition = .animated(duration: 0.3, curve: .easeInOut) + } + if let textNode = self.textNode { + transition.updateAlpha(node: textNode, alpha: hasContextMenu ? 0.5 : 1.0) + } else { + transition.updateAlpha(node: self.switchNode, alpha: hasContextMenu ? 0.5 : 1.0) + } + } func asyncLayout() -> (_ item: ItemListSwitchItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) @@ -488,7 +506,11 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size) + var titleOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0 + if textLayoutAndApply != nil { + titleOriginY = topInset + 1.0 + } + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleOriginY), size: titleLayout.size) transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin) strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift index 5b23c18ed4..63c7349d2d 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift @@ -139,16 +139,21 @@ public final class LegacyJoinLinkPreviewController: ViewController { } private func join() { - self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] peer in + self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { - if strongSelf.isRequest { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - } else { - if let peer = peer { - strongSelf.navigateToPeer(peer, nil) + switch result { + case let .joined(peer): + if strongSelf.isRequest { + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } else { + if let peer { + strongSelf.navigateToPeer(peer, nil) + } } + strongSelf.dismiss() + case let .webView(webView): + strongSelf.context.sharedContext.openJoinChatWebView(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: nil, webView: webView) } - strongSelf.dismiss() } }, error: { [weak self] error in if let strongSelf = self { diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift index 7516769aef..302c8ceae6 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift @@ -81,6 +81,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, ASScrollVi self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift index a0ce070a00..2a3f2b576b 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift @@ -133,6 +133,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer self.descriptionNode.textAlignment = .center self.peersScrollNode = ASScrollNode() self.peersScrollNode.view.showsHorizontalScrollIndicator = false + self.peersScrollNode.view.scrollsToTop = false self.actionButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 11.0) diff --git a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift index 95fd3d3982..53382b7d12 100644 --- a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift +++ b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift @@ -76,6 +76,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, ASScro self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/NSInputStream+TL.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/NSInputStream+TL.h index aacf17f8e8..5c9c0105b4 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/NSInputStream+TL.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/NSInputStream+TL.h @@ -4,11 +4,8 @@ @interface NSInputStream (TL) -- (int32_t)readInt32; - (int32_t)readInt32:(bool *)failed __attribute__((nonnull(1))); -- (int64_t)readInt64; - (int64_t)readInt64:(bool *)failed __attribute__((nonnull(1))); -- (double)readDouble; - (double)readDouble:(bool *)failed __attribute__((nonnull(1))); - (NSData *)readData:(int)length; - (NSData *)readData:(int)length failed:(bool *)failed __attribute__((nonnull(2))); diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h index 38d1fdfa9f..3ce8d62e49 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h @@ -35,7 +35,7 @@ @property (nonatomic) bool reminder; @property (nonatomic) bool forum; @property (nonatomic) bool isSuggesting; -@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t, bool)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); @property (nonatomic, strong) NSArray *underlyingViews; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h index d1e0b2fcc0..7e1164ee05 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGCameraController.h @@ -63,7 +63,7 @@ typedef enum { @property (nonatomic, copy) void(^finishedTransitionOut)(void); @property (nonatomic, copy) void(^customPresentOverlayController)(TGOverlayController *(^)(id)); -@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t, bool)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); - (instancetype)initWithContext:(id)context saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 178f043270..1578e0f772 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -70,7 +70,7 @@ typedef enum @property (nonatomic, assign) bool forum; @property (nonatomic, assign) bool isSuggesting; -@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t, bool)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); @property (nonatomic, assign) bool liveVideoUploadEnabled; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h index c256f0832c..42ade02f88 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h @@ -35,7 +35,7 @@ @property (nonatomic, assign) bool forum; @property (nonatomic, assign) bool isSuggesting; -@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t, bool)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); @property (nonatomic, assign) CGFloat topInset; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h index 847e2b5c7c..da6e7831eb 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h @@ -14,7 +14,7 @@ @property (nonatomic, copy) void (^captionSet)(id, NSAttributedString *); @property (nonatomic, copy) void (^donePressed)(id); -@property (nonatomic, copy) void (^doneLongPressed)(id); +@property (nonatomic, copy) void (^doneLongPressed)(id, UIView *); @property (nonatomic, copy) void (^photoStripItemSelected)(NSInteger index); @@ -56,6 +56,7 @@ - (void)editorTransitionIn; - (void)editorTransitionOut; +- (bool)canBeginEditingCaption; - (void)beginEditingCaption; - (void)setupGifEditing; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h index 24f4876030..46aacd32a1 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h @@ -28,7 +28,7 @@ @property (nonatomic, copy) void (^editorOpened)(void); @property (nonatomic, copy) void (^editorClosed)(void); -@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(bool, void (^)(int32_t, bool)); @property (nonatomic, copy) void (^presentTimerController)(void (^)(int32_t)); - (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder hasCoverButton:(bool)hasCoverButton stickersContext:(id)stickersContext; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h index a21f36027d..f2ae886fe8 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryInterfaceView.h @@ -39,4 +39,7 @@ - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration; +- (bool)canBeginEditingCaption; +- (void)beginEditingCaption; + @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h index 44d2e574b2..bd58b0f294 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoCaptionInputMixin.h @@ -8,7 +8,6 @@ @interface TGPhotoCaptionInputMixin : NSObject @property (nonatomic, strong) id stickersContext; -@property (nonatomic, readonly) UIView *backgroundView; @property (nonatomic, readonly) id inputPanel; @property (nonatomic, readonly) UIView *inputPanelView; @property (nonatomic, readonly) UIView *dismissView; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h index 05095af3af..1e8c90aa47 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -1,6 +1,7 @@ #import #import #import +#import @class TGPaintingData; @class TGStickerMaskDescription; @@ -37,6 +38,7 @@ @protocol TGCaptionPanelView @property (nonatomic, readonly) UIView * _Nonnull view; +@property (nonatomic, readonly) CGFloat additionalInputHeight; - (void)setTimeout:(int32_t)timeout isVideo:(bool)isVideo isCaptionAbove:(bool)isCaptionAbove; @@ -55,6 +57,10 @@ @property (nonatomic, copy) void(^ _Nullable timerUpdated)(NSNumber * _Nullable value); @property (nonatomic, copy) void(^ _Nullable captionIsAboveUpdated)(BOOL value); +@optional +- (CGFloat)updateContainerLayoutSize:(CGSize)size safeAreaInset:(UIEdgeInsets)safeAreaInset bottomInset:(CGFloat)bottomInset keyboardHeight:(CGFloat)keyboardHeight animated:(bool)animated; + +@required - (CGFloat)updateLayoutSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight sideInset:(CGFloat)sideInset animated:(bool)animated; - (CGFloat)baseHeight; @@ -137,6 +143,8 @@ @property (nonatomic, copy) id _Nullable(^ _Nullable captionPanelView)(void); @property (nonatomic, copy) id _Nullable(^ _Nullable livePhotoButton)(void); +@property (nonatomic, copy) UIView *_Nullable(^ _Nullable photoToolbarView)(TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, bool hasSendStarsButton); +@property (nonatomic, copy) bool (^ _Nullable presentMediaPickerSendActionMenu)(UIView * _Nonnull sourceView, bool canSendSilently, bool canSendWhenOnline, bool canSchedule, bool reminder, bool hasTimer, void (^ _Nonnull sendSilently)(void), void (^ _Nonnull sendWhenOnline)(void), void (^ _Nonnull schedule)(void), void (^ _Nonnull sendWithTimer)(void)); @property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull)); diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index 4860d82e10..90e8982d23 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -1,44 +1,14 @@ #import +#import #import @protocol TGPhotoPaintStickersContext; -typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { - TGPhotoEditorNoneTab = 0, - TGPhotoEditorCropTab = 1 << 0, - TGPhotoEditorRotateTab = 1 << 1, - TGPhotoEditorMirrorTab = 1 << 2, - TGPhotoEditorPaintTab = 1 << 3, - TGPhotoEditorEraserTab = 1 << 4, - TGPhotoEditorStickerTab = 1 << 5, - TGPhotoEditorTextTab = 1 << 6, - TGPhotoEditorToolsTab = 1 << 7, - TGPhotoEditorQualityTab = 1 << 8, - TGPhotoEditorTimerTab = 1 << 9, - TGPhotoEditorAspectRatioTab = 1 << 10, - TGPhotoEditorTintTab = 1 << 11, - TGPhotoEditorBlurTab = 1 << 12, - TGPhotoEditorCurvesTab = 1 << 13 -}; - -typedef enum -{ - TGPhotoEditorBackButtonBack, - TGPhotoEditorBackButtonCancel -} TGPhotoEditorBackButton; - -typedef enum -{ - TGPhotoEditorDoneButtonSend, - TGPhotoEditorDoneButtonCheck, - TGPhotoEditorDoneButtonDone, - TGPhotoEditorDoneButtonSchedule -} TGPhotoEditorDoneButton; - -@interface TGPhotoToolbarView : UIView +@interface TGPhotoToolbarView : UIView @property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation; +@property (nonatomic, assign) CGFloat bottomInset; @property (nonatomic, readonly) UIButton *doneButton; @@ -79,8 +49,12 @@ typedef enum - (void)setActiveTab:(TGPhotoEditorTab)tab; +- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset; +- (void)setTimerButtonValue:(NSInteger)value; + - (void)setInfoString:(NSString *)string; +- (UIView *)viewForTab:(TGPhotoEditorTab)tab; - (TGPhotoEditorButton *)buttonForTab:(TGPhotoEditorTab)tab; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h new file mode 100644 index 0000000000..043207594e --- /dev/null +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h @@ -0,0 +1,83 @@ +#import +#import + +typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { + TGPhotoEditorNoneTab = 0, + TGPhotoEditorCropTab = 1 << 0, + TGPhotoEditorRotateTab = 1 << 1, + TGPhotoEditorMirrorTab = 1 << 2, + TGPhotoEditorPaintTab = 1 << 3, + TGPhotoEditorEraserTab = 1 << 4, + TGPhotoEditorStickerTab = 1 << 5, + TGPhotoEditorTextTab = 1 << 6, + TGPhotoEditorToolsTab = 1 << 7, + TGPhotoEditorQualityTab = 1 << 8, + TGPhotoEditorTimerTab = 1 << 9, + TGPhotoEditorAspectRatioTab = 1 << 10, + TGPhotoEditorTintTab = 1 << 11, + TGPhotoEditorBlurTab = 1 << 12, + TGPhotoEditorCurvesTab = 1 << 13 +}; + +typedef enum +{ + TGPhotoEditorBackButtonBack, + TGPhotoEditorBackButtonCancel +} TGPhotoEditorBackButton; + +typedef enum +{ + TGPhotoEditorDoneButtonSend, + TGPhotoEditorDoneButtonCheck, + TGPhotoEditorDoneButtonDone, + TGPhotoEditorDoneButtonSchedule +} TGPhotoEditorDoneButton; + +@protocol TGPhotoToolbarViewProtocol + +@property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation; +@property (nonatomic, assign) CGFloat bottomInset; + +@property (nonatomic, readonly) UIView *doneButton; + +@property (nonatomic, copy) void(^cancelPressed)(void); +@property (nonatomic, copy) void(^donePressed)(void); +@property (nonatomic, copy) void(^doneLongPressed)(id sender); +@property (nonatomic, copy) void(^tabPressed)(TGPhotoEditorTab tab); + +@property (nonatomic, readonly) CGRect cancelButtonFrame; +@property (nonatomic, readonly) CGRect doneButtonFrame; + +@property (nonatomic, assign) TGPhotoEditorBackButton backButtonType; +@property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType; + +@property (nonatomic, assign) int64_t sendPaidMessageStars; + +@property (nonatomic, readonly) TGPhotoEditorTab currentTabs; + +- (void)transitionInAnimated:(bool)animated; +- (void)transitionInAnimated:(bool)animated transparent:(bool)transparent; +- (void)transitionOutAnimated:(bool)animated; +- (void)transitionOutAnimated:(bool)animated transparent:(bool)transparent hideOnCompletion:(bool)hideOnCompletion; + +- (void)setDoneButtonEnabled:(bool)enabled animated:(bool)animated; +- (void)setEditButtonsEnabled:(bool)enabled animated:(bool)animated; +- (void)setEditButtonsHidden:(bool)hidden animated:(bool)animated; +- (void)setEditButtonsHighlighted:(TGPhotoEditorTab)buttons; +- (void)setEditButtonsDisabled:(TGPhotoEditorTab)buttons; + +- (void)setCenterButtonsHidden:(bool)hidden animated:(bool)animated; +- (void)setAllButtonsHidden:(bool)hidden animated:(bool)animated; +- (void)setCancelDoneButtonsHidden:(bool)hidden animated:(bool)animated; + +- (void)setToolbarTabs:(TGPhotoEditorTab)tabs animated:(bool)animated; +- (void)setActiveTab:(TGPhotoEditorTab)tab; + +- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset; +- (void)setTimerButtonValue:(NSInteger)value; + +- (void)setInfoString:(NSString *)string; + +- (UIView *)viewForTab:(TGPhotoEditorTab)tab; + +@end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h index ef21ecd0b6..2031d9610e 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h @@ -7,12 +7,20 @@ #import #import +@class TGModernGalleryController; + +typedef void (^ _Nonnull TGPhotoVideoEditorSchedulePickerCompletion)(int32_t time, bool silentPosting); +typedef void (^ _Nonnull TGPhotoVideoEditorSchedulePicker)(bool media, TGPhotoVideoEditorSchedulePickerCompletion _Nonnull done); +typedef void (^ _Nonnull TGPhotoVideoEditorCompletion)(id _Nonnull item, TGMediaEditingContext * _Nonnull editingContext, bool silentPosting, int32_t scheduleTime); + @interface TGPhotoVideoEditor : NSObject -+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView senderName:(NSString *)senderName didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed; ++ (void)presentWithContext:(id _Nonnull)context parentController:(TGViewController * _Nonnull)parentController image:(UIImage * _Nullable)image video:(NSURL * _Nullable)video stickersContext:(id _Nullable)stickersContext transitionView:(UIView * _Nullable)transitionView senderName:(NSString * _Nullable)senderName didFinishWithImage:(void (^ _Nullable)(UIImage * _Nonnull image))didFinishWithImage didFinishWithVideo:(void (^ _Nullable)(UIImage * _Nonnull image, NSURL * _Nonnull url, TGVideoEditAdjustments * _Nullable adjustments))didFinishWithVideo dismissed:(void (^ _Nonnull)(void))dismissed; -+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed; ++ (TGModernGalleryController * _Nonnull)controllerWithContext:(id _Nonnull)context caption:(NSAttributedString * _Nonnull)caption withItem:(id _Nonnull)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString * _Nonnull)recipientName stickersContext:(id _Nullable)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView * _Nullable)mainSnapshot snapshots:(NSArray * _Nonnull)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder presentSchedulePicker:(TGPhotoVideoEditorSchedulePicker _Nonnull)presentSchedulePicker appeared:(void (^ _Nonnull)(void))appeared completion:(TGPhotoVideoEditorCompletion _Nonnull)completion dismissed:(void (^ _Nonnull)(void))dismissed; -+ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)(bool))beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut; ++ (void)presentWithContext:(id _Nonnull)context controller:(TGViewController * _Nonnull)controller caption:(NSAttributedString * _Nonnull)caption withItem:(id _Nonnull)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString * _Nonnull)recipientName stickersContext:(id _Nullable)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView * _Nullable)mainSnapshot snapshots:(NSArray * _Nonnull)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder presentSchedulePicker:(TGPhotoVideoEditorSchedulePicker _Nonnull)presentSchedulePicker appeared:(void (^ _Nonnull)(void))appeared completion:(TGPhotoVideoEditorCompletion _Nonnull)completion dismissed:(void (^ _Nonnull)(void))dismissed; + ++ (void)presentEditorWithContext:(id _Nonnull)context controller:(TGViewController * _Nonnull)controller withItem:(id _Nonnull)item cropRect:(CGRect)cropRect adjustments:(id _Nullable)adjustments referenceView:(UIView * _Nonnull)referenceView completion:(void (^ _Nonnull)(UIImage * _Nonnull image, id _Nullable adjustments))completion fullSizeCompletion:(void (^ _Nonnull)(UIImage * _Nonnull image))fullSizeCompletion beginTransitionOut:(void (^ _Nullable)(bool saving))beginTransitionOut finishTransitionOut:(void (^ _Nullable)(void))finishTransitionOut; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h index 1538db8304..99af708e39 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoMessageCaptureController.h @@ -31,7 +31,7 @@ @property (nonatomic, copy) void(^didDismiss)(void); @property (nonatomic, copy) void(^didStop)(void); @property (nonatomic, copy) void(^displaySlowmodeTooltip)(void); -@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); +@property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t, bool)); - (instancetype)initWithContext:(id)context forStory:(bool)forStory assets:(TGVideoMessageCaptureControllerAssets *)assets transitionInView:(UIView *(^)(void))transitionInView parentController:(TGViewController *)parentController controlsFrame:(CGRect)controlsFrame isAlreadyLocked:(bool (^)(void))isAlreadyLocked liveUploadInterface:(id)liveUploadInterface pallete:(TGModernConversationInputMicPallete *)pallete slowmodeTimestamp:(int32_t)slowmodeTimestamp slowmodeView:(UIView *(^)(void))slowmodeView canSendSilently:(bool)canSendSilently canSchedule:(bool)canSchedule reminder:(bool)reminder; diff --git a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@2x.png b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@2x.png deleted file mode 100644 index 7f9680c2be..0000000000 Binary files a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@2x.png and /dev/null differ diff --git a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@3x.png b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@3x.png deleted file mode 100644 index de8d7887ac..0000000000 Binary files a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/VideoMessageShadow@3x.png and /dev/null differ diff --git a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/ocr_nn.bin b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/ocr_nn.bin deleted file mode 100755 index ccab89e554..0000000000 Binary files a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/ocr_nn.bin and /dev/null differ diff --git a/submodules/LegacyComponents/Sources/NSInputStream+TL.m b/submodules/LegacyComponents/Sources/NSInputStream+TL.m index 36fd423570..a27517b88b 100644 --- a/submodules/LegacyComponents/Sources/NSInputStream+TL.m +++ b/submodules/LegacyComponents/Sources/NSInputStream+TL.m @@ -22,27 +22,6 @@ static inline int roundUpInput(int numToRound, int multiple) @implementation NSInputStream (TL) -- (int32_t)readInt32 -{ - int32_t value = 0; - - if ([self read:(uint8_t *)&value maxLength:4] != 4) - { - TGLegacyLog(@"***** Couldn't read int32"); - - @throw [[NSException alloc] initWithName:@"NSInputStream+TLException" reason:@"readInt32 end of stream" userInfo:@{}]; - } - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -# error "Big endian is not implemented" -#else -# error "Unknown byte order" -#endif - - return value; -} - - (int32_t)readInt32:(bool *)failed { int32_t value = 0; diff --git a/submodules/LegacyComponents/Sources/TGAuthorSignatureMediaAttachment.m b/submodules/LegacyComponents/Sources/TGAuthorSignatureMediaAttachment.m index 4cd356c201..f24d9ef706 100644 --- a/submodules/LegacyComponents/Sources/TGAuthorSignatureMediaAttachment.m +++ b/submodules/LegacyComponents/Sources/TGAuthorSignatureMediaAttachment.m @@ -34,8 +34,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/submodules/LegacyComponents/Sources/TGBotContextResultAttachment.m b/submodules/LegacyComponents/Sources/TGBotContextResultAttachment.m index bd97cf7b3b..502f98e44c 100644 --- a/submodules/LegacyComponents/Sources/TGBotContextResultAttachment.m +++ b/submodules/LegacyComponents/Sources/TGBotContextResultAttachment.m @@ -36,8 +36,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m index fd9b44075e..10f823ceda 100644 --- a/submodules/LegacyComponents/Sources/TGCameraController.m +++ b/submodules/LegacyComponents/Sources/TGCameraController.m @@ -1548,7 +1548,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus __weak TGModernGalleryController *weakGalleryController = galleryController; __weak TGMediaPickerGalleryModel *weakModel = model; - model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) { + model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView) { __strong TGCameraController *strongSelf = weakSelf; __strong TGMediaPickerGalleryModel *strongModel = weakModel; if (strongSelf == nil || !(strongSelf.hasSilentPosting || strongSelf.hasSchedule) || strongSelf->_shortcut) @@ -1656,7 +1656,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus if (strongSelf == nil) return; - strongSelf.presentScheduleController(true, ^(int32_t time) { + strongSelf.presentScheduleController(true, ^(int32_t time, bool silentPosting) { __strong TGCameraController *strongSelf = weakSelf; __strong TGMediaPickerGalleryModel *strongModel = weakModel; @@ -1678,7 +1678,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus [[NSUserDefaults standardUserDefaults] setObject:@(!strongSelf->_selectionContext.grouping) forKey:@"TG_mediaGroupingDisabled_v0"]; if (strongSelf.finishedWithResults != nil) - strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, false, time); + strongSelf.finishedWithResults(strongController, strongSelf->_selectionContext, strongSelf->_editingContext, item.asset, silentPosting, time); [strongSelf _dismissTransitionForResultController:strongController]; }); @@ -1720,6 +1720,21 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus [strongSelf _dismissTransitionForResultController:strongController]; }); }; + if (sourceView != nil && strongSelf.stickersContext.presentMediaPickerSendActionMenu != nil && strongSelf.stickersContext.presentMediaPickerSendActionMenu(sourceView, strongSelf->_hasSilentPosting, effectiveHasSchedule, effectiveHasSchedule, strongSelf->_reminder, strongSelf->_hasTimer, ^{ + if (controller.sendSilently != nil) + controller.sendSilently(); + }, ^{ + if (controller.sendWhenOnline != nil) + controller.sendWhenOnline(); + }, ^{ + if (controller.schedule != nil) + controller.schedule(); + }, ^{ + if (controller.sendWithTimer != nil) + controller.sendWithTimer(); + })) { + return; + } id windowManager = nil; windowManager = [strongSelf->_context makeOverlayWindowManager]; @@ -2751,6 +2766,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); else if (widescreenWidth == 926.0f) return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); + else if (widescreenWidth == 912.0f) + return CGRectMake(0, 82, screenSize.width, screenSize.height - 82 - 83); else if (widescreenWidth == 896.0f) return CGRectMake(0, 77, screenSize.width, screenSize.height - 77 - 83); else if (widescreenWidth == 874.0f) @@ -2784,6 +2801,8 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 223); else if (widescreenWidth == 926.0f) return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 234); + else if (widescreenWidth == 912.0f) + return CGRectMake(0, 136, screenSize.width, screenSize.height - 136 - 216); else if (widescreenWidth == 896.0f) return CGRectMake(0, 121, screenSize.width, screenSize.height - 121 - 223); else if (widescreenWidth == 874.0f) diff --git a/submodules/LegacyComponents/Sources/TGFont.mm b/submodules/LegacyComponents/Sources/TGFont.mm index a5d261521f..311787d42e 100644 --- a/submodules/LegacyComponents/Sources/TGFont.mm +++ b/submodules/LegacyComponents/Sources/TGFont.mm @@ -116,13 +116,9 @@ UIFont *TGFixedSystemFontOfSize(CGFloat size) + (UIFont *)roundedFontOfSize:(CGFloat)size { - if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) { - UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize:size].fontDescriptor; - descriptor = [descriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded]; - return [UIFont fontWithDescriptor:descriptor size:size]; - } else { - return [UIFont fontWithName:@".SFCompactRounded-Semibold" size:size]; - } + UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize:size].fontDescriptor; + descriptor = [descriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded]; + return [UIFont fontWithDescriptor:descriptor size:size]; } @end diff --git a/submodules/LegacyComponents/Sources/TGGameMediaAttachment.m b/submodules/LegacyComponents/Sources/TGGameMediaAttachment.m index 365ff1d221..d2ccf31bb2 100644 --- a/submodules/LegacyComponents/Sources/TGGameMediaAttachment.m +++ b/submodules/LegacyComponents/Sources/TGGameMediaAttachment.m @@ -48,8 +48,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGInvoiceMediaAttachment.m b/submodules/LegacyComponents/Sources/TGInvoiceMediaAttachment.m index 1d5f1bc4b4..89220c4470 100644 --- a/submodules/LegacyComponents/Sources/TGInvoiceMediaAttachment.m +++ b/submodules/LegacyComponents/Sources/TGInvoiceMediaAttachment.m @@ -87,8 +87,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 1522c48030..4ff21ec6d9 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -478,7 +478,7 @@ static TGVideoEditAdjustments *TGMediaAssetsPatchedLivePhotoAdjustments(PGPhotoE self.pickerController.isSuggesting = isSuggesting; } -- (void)setPresentScheduleController:(void (^)(bool, void (^)(int32_t)))presentScheduleController { +- (void)setPresentScheduleController:(void (^)(bool, void (^)(int32_t, bool)))presentScheduleController { _presentScheduleController = [presentScheduleController copy]; self.pickerController.presentScheduleController = presentScheduleController; } @@ -1890,8 +1890,8 @@ static TGVideoEditAdjustments *TGMediaAssetsPatchedLivePhotoAdjustments(PGPhotoE - (void)schedule:(bool)media { __weak TGMediaAssetsController *weakSelf = self; - self.presentScheduleController(media, ^(int32_t scheduleTime) { - [weakSelf completeWithCurrentItem:nil silentPosting:false scheduleTime:scheduleTime]; + self.presentScheduleController(media, ^(int32_t scheduleTime, bool silentPosting) { + [weakSelf completeWithCurrentItem:nil silentPosting:silentPosting scheduleTime:scheduleTime]; }); } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index f8863cee7c..b10092651a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -42,6 +42,19 @@ #import #import +#import + +static UIView *TGMediaPickerCreatePhotoToolbarView(id context, TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, id stickersContext, bool hasSendStarsButton) +{ + if (stickersContext.photoToolbarView != nil) + { + UIView *toolbarView = stickersContext.photoToolbarView(backButton, doneButton, solidBackground, hasSendStarsButton); + if (toolbarView != nil) + return toolbarView; + } + + return [[TGPhotoToolbarView alloc] initWithContext:context backButton:backButton doneButton:doneButton solidBackground:solidBackground stickersContext:hasSendStarsButton ? stickersContext : nil]; +} static TGMediaAsset *TGMediaPickerGalleryLivePhotoAsset(id editableMediaItem) { @@ -101,8 +114,8 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * UIView *_wrapperView; UIView *_headerWrapperView; - TGPhotoToolbarView *_portraitToolbarView; - TGPhotoToolbarView *_landscapeToolbarView; + UIView *_portraitToolbarView; + UIView *_landscapeToolbarView; UIImageView *_arrowView; UILabel *_recipientLabel; @@ -224,8 +237,10 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * return; [strongSelf.window endEditing:true]; - if (strongSelf->_doneLongPressed != nil) - strongSelf->_doneLongPressed(strongSelf->_currentItem); + if (strongSelf->_doneLongPressed != nil) { + UIView *sourceView = [sender isKindOfClass:[UIView class]] ? (UIView *)sender : nil; + strongSelf->_doneLongPressed(strongSelf->_currentItem, sourceView); + } [[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"]; }; @@ -439,7 +454,6 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * }; _captionMixin.stickersContext = stickersContext; - [_captionMixin createInputPanelIfNeeded]; _headerWrapperView = [[TGMediaPickerGalleryWrapperView alloc] init]; [_wrapperView addSubview:_headerWrapperView]; @@ -472,13 +486,13 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * TGPhotoEditorDoneButton doneButton = isScheduledMessages ? TGPhotoEditorDoneButtonSchedule : TGPhotoEditorDoneButtonSend; - _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:editingContext.sendPaidMessageStars > 0 ? stickersContext : nil]; + _portraitToolbarView = TGMediaPickerCreatePhotoToolbarView(_context, TGPhotoEditorBackButtonBack, doneButton, false, stickersContext, editingContext.sendPaidMessageStars > 0); _portraitToolbarView.cancelPressed = toolbarCancelPressed; _portraitToolbarView.donePressed = toolbarDonePressed; _portraitToolbarView.doneLongPressed = toolbarDoneLongPressed; [_wrapperView addSubview:_portraitToolbarView]; - _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:nil]; + _landscapeToolbarView = TGMediaPickerCreatePhotoToolbarView(_context, TGPhotoEditorBackButtonBack, doneButton, false, stickersContext, false); _landscapeToolbarView.cancelPressed = toolbarCancelPressed; _landscapeToolbarView.donePressed = toolbarDonePressed; _landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed; @@ -486,6 +500,8 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) [_wrapperView addSubview:_landscapeToolbarView]; + [_captionMixin createInputPanelIfNeeded]; + if (hasCoverButton) { _cancelCoverButton = [[TGModernButton alloc] init]; _cancelCoverButton.hidden = true; @@ -577,6 +593,10 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * } +- (bool)canBeginEditingCaption { + return _hasCaptions && _captionMixin != nil && _captionMixin.inputPanel != nil && !_captionMixin.inputPanelView.hidden && !_captionMixin.editing; +} + - (void)beginEditingCaption { [_captionMixin activateInput]; } @@ -640,17 +660,17 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * - (UIView *)timerButton { if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) - return [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab]; + return [_portraitToolbarView viewForTab:TGPhotoEditorTimerTab]; else - return [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab]; + return [_landscapeToolbarView viewForTab:TGPhotoEditorTimerTab]; } - (UIView *)qualityButton { if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) - return [_portraitToolbarView buttonForTab:TGPhotoEditorQualityTab]; + return [_portraitToolbarView viewForTab:TGPhotoEditorQualityTab]; else - return [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab]; + return [_landscapeToolbarView viewForTab:TGPhotoEditorQualityTab]; } - (void)setSelectedItemsModel:(TGMediaPickerGallerySelectedItemsModel *)selectedItemsModel @@ -1187,19 +1207,15 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _muteButton.selected = adjustments.sendAsGif; - TGPhotoEditorButton *qualityButton = [_portraitToolbarView buttonForTab:TGPhotoEditorQualityTab]; + UIView *qualityButton = [_portraitToolbarView viewForTab:TGPhotoEditorQualityTab]; if (qualityButton != nil) { bool isPhoto = [_currentItemView isKindOfClass:[TGMediaPickerGalleryPhotoItemView class]] || [_currentItem isKindOfClass:[TGCameraCapturedPhoto class]]; + bool isHd = false; + TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetCompressedMedium; if (isPhoto) { - bool isHd = _editingContext.isHighQualityPhoto; - UIImage *icon = [TGPhotoEditorInterfaceAssets qualityIconForHighQuality:isHd filled: false]; - qualityButton.iconImage = icon; - - qualityButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab]; - qualityButton.iconImage = icon; + isHd = _editingContext.isHighQualityPhoto; } else { - TGMediaVideoConversionPreset preset = 0; TGMediaVideoConversionPreset adjustmentsPreset = TGMediaVideoConversionPresetCompressedDefault; if ([adjustments isKindOfClass:[TGMediaVideoEditAdjustments class]]) adjustmentsPreset = ((TGMediaVideoEditAdjustments *)adjustments).preset; @@ -1220,28 +1236,19 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * TGMediaVideoConversionPreset bestPreset = [TGMediaVideoConverter bestAvailablePresetForDimensions:dimensions]; if (preset > bestPreset) preset = bestPreset; - - UIImage *icon = [TGPhotoEditorInterfaceAssets qualityIconForPreset:preset]; - qualityButton.iconImage = icon; - - qualityButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab]; - qualityButton.iconImage = icon; } + + [_portraitToolbarView setQualityButtonIsPhoto:isPhoto highQuality:isHd videoPreset:preset]; + [_landscapeToolbarView setQualityButtonIsPhoto:isPhoto highQuality:isHd videoPreset:preset]; } - TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab]; + UIView *timerButton = [_portraitToolbarView viewForTab:TGPhotoEditorTimerTab]; if (timerButton != nil) { NSInteger value = [timer integerValue]; - UIImage *defaultIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:0]; - UIImage *icon = [TGPhotoEditorInterfaceAssets timerIconForValue:value]; - [timerButton setIconImage:defaultIcon activeIconImage:icon]; - - TGPhotoEditorButton *landscapeTimerButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab]; - - timerButton = landscapeTimerButton; - [timerButton setIconImage:defaultIcon activeIconImage:icon]; + [_portraitToolbarView setTimerButtonValue:value]; + [_landscapeToolbarView setTimerButtonValue:value]; if (value > 0) highlightedButtons |= TGPhotoEditorTimerTab; @@ -1412,6 +1419,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _coverButton.alpha = alpha; _arrowView.alpha = alpha * 0.6f; _recipientLabel.alpha = alpha * 0.6; + _captionMixin.livePhotoButtonView.alpha = alpha; } completion:^(BOOL finished) { if (finished) @@ -1419,6 +1427,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _checkButton.userInteractionEnabled = !hidden; _muteButton.userInteractionEnabled = !hidden; _coverButton.userInteractionEnabled = !hidden; + _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } }]; @@ -1447,6 +1456,9 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _arrowView.alpha = alpha * 0.6f; _recipientLabel.alpha = alpha * 0.6; + + _captionMixin.livePhotoButtonView.alpha = alpha; + _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } if (hidden) @@ -1480,7 +1492,6 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _portraitToolbarView.alpha = alpha; _landscapeToolbarView.alpha = alpha; _captionMixin.inputPanelView.alpha = alpha; - _captionMixin.backgroundView.alpha = alpha; _captionMixin.livePhotoButtonView.alpha = alpha; } completion:^(BOOL finished) { @@ -1492,7 +1503,6 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _portraitToolbarView.userInteractionEnabled = !hidden; _landscapeToolbarView.userInteractionEnabled = !hidden; _captionMixin.inputPanelView.userInteractionEnabled = !hidden; - _captionMixin.backgroundView.userInteractionEnabled = !hidden; _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } }]; @@ -1532,9 +1542,6 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _captionMixin.inputPanelView.alpha = alpha; _captionMixin.inputPanelView.userInteractionEnabled = !hidden; - _captionMixin.backgroundView.alpha = alpha; - _captionMixin.backgroundView.userInteractionEnabled = !hidden; - _captionMixin.livePhotoButtonView.alpha = alpha; _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden; } @@ -1722,7 +1729,6 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * - (void)immediateEditorTransitionIn { [self setSelectionInterfaceHidden:true animated:false]; _captionMixin.inputPanelView.alpha = 0.0f; - _captionMixin.backgroundView.alpha = 0.0f; _portraitToolbarView.doneButton.alpha = 0.0f; _landscapeToolbarView.doneButton.alpha = 0.0f; @@ -1740,12 +1746,11 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * { [self setSelectionInterfaceHidden:true animated:true]; - [UIView animateWithDuration:0.2 animations:^ + [UIView animateWithDuration:0.3 animations:^ { _captionMixin.inputPanelView.alpha = 0.0f; - _captionMixin.backgroundView.alpha = 0.0f; - _portraitToolbarView.doneButton.alpha = 0.0f; - _landscapeToolbarView.doneButton.alpha = 0.0f; + _portraitToolbarView.alpha = 0.0f; + _landscapeToolbarView.alpha = 0.0f; }]; } @@ -1753,12 +1758,11 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * { [self setSelectionInterfaceHidden:false animated:true]; - [UIView animateWithDuration:0.3 animations:^ + [UIView animateWithDuration:0.2 animations:^ { _captionMixin.inputPanelView.alpha = 1.0f; - _captionMixin.backgroundView.alpha = 1.0f; - _portraitToolbarView.doneButton.alpha = 1.0f; - _landscapeToolbarView.doneButton.alpha = 1.0f; + _portraitToolbarView.alpha = 1.0f; + _landscapeToolbarView.alpha = 1.0f; }]; } @@ -1868,7 +1872,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * break; default: - frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 26 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height); + frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 26 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 74.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height); break; } @@ -1979,7 +1983,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * break; default: - frame = CGRectMake(screenEdges.right - 46 - _safeAreaInset.right - buttonInset, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 45 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), 44, 44); + frame = CGRectMake(screenEdges.right - 46 - _safeAreaInset.right - buttonInset, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 50 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), 44, 44); break; } @@ -2092,6 +2096,8 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * CGFloat screenSide = MAX(screenSize.width, screenSize.height); UIEdgeInsets screenEdges = UIEdgeInsetsZero; + _portraitToolbarView.bottomInset = _safeAreaInset.bottom; + if (TGIsPad()) { _landscapeToolbarView.hidden = true; @@ -2125,7 +2131,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * _coverTitleLabel.frame = CGRectMake(screenEdges.left + floor((self.frame.size.width - _coverTitleLabel.frame.size.width) / 2.0), coverTitleTopY + 26, _coverTitleLabel.frame.size.width, _coverTitleLabel.frame.size.height); UIEdgeInsets captionEdgeInsets = screenEdges; - captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height; + captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height + 10.0; [_captionMixin updateLayoutWithFrame:self.bounds edgeInsets:captionEdgeInsets animated:false]; switch (orientation) @@ -2164,14 +2170,14 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber * { [UIView performWithoutAnimation:^ { - _photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 40 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), 64, 38); + _photoCounterButton.frame = CGRectMake(screenEdges.right - 64 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 50 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), 64, 38); - _selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 54 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize); + _selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 64 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize); }]; _landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, self.frame.size.height); - _headerWrapperView.frame = CGRectMake(screenEdges.left, _portraitToolbarView.frame.origin.y - 64.0 - [_captionMixin.inputPanel baseHeight], self.frame.size.width, 72.0); + _headerWrapperView.frame = CGRectMake(screenEdges.left, _portraitToolbarView.frame.origin.y - 74.0 - [_captionMixin.inputPanel baseHeight], self.frame.size.width, 72.0); } break; } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m index a1f83e5124..12edfa97bd 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m @@ -142,7 +142,7 @@ strongSelf.completeWithItem(item, false, 0); }; - model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) { + model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView) { __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; if (strongSelf == nil || !(hasSilentPosting || hasSchedule)) return; @@ -201,7 +201,7 @@ if (strongSelf == nil) return; - strongSelf.presentScheduleController(true, ^(int32_t time) { + strongSelf.presentScheduleController(true, ^(int32_t time, bool silentPosting) { __strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf; if (strongSelf == nil) return; @@ -209,7 +209,7 @@ strongSelf->_galleryModel.dismiss(true, false); if (strongSelf.completeWithItem != nil) - strongSelf.completeWithItem(item, false, time); + strongSelf.completeWithItem(item, silentPosting, time); }); }; controller.sendWithTimer = ^{ @@ -236,6 +236,19 @@ strongSelf.completeWithItem(item, false, 0); }); }; + if (sourceView != nil && strongSelf->_stickersContext.presentMediaPickerSendActionMenu != nil && strongSelf->_stickersContext.presentMediaPickerSendActionMenu(sourceView, hasSilentPosting, effectiveHasSchedule, effectiveHasSchedule, reminder, false, ^{ + if (controller.sendSilently != nil) + controller.sendSilently(); + }, ^{ + if (controller.sendWhenOnline != nil) + controller.sendWhenOnline(); + }, ^{ + if (controller.schedule != nil) + controller.schedule(); + }, ^{ + })) { + return; + } TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[strongSelf->_context makeOverlayWindowManager] parentController:strongSelf->_parentController contentController:controller]; controllerWindow.hidden = false; diff --git a/submodules/LegacyComponents/Sources/TGMessageEntitiesAttachment.m b/submodules/LegacyComponents/Sources/TGMessageEntitiesAttachment.m index 02e8ce7701..fd959f11b3 100644 --- a/submodules/LegacyComponents/Sources/TGMessageEntitiesAttachment.m +++ b/submodules/LegacyComponents/Sources/TGMessageEntitiesAttachment.m @@ -54,8 +54,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m index d30009fbb9..bc020a1f1d 100644 --- a/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m +++ b/submodules/LegacyComponents/Sources/TGModernConversationInputMicButton.m @@ -265,7 +265,7 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _innerCircleView.center = centerPoint; _outerCircleView.center = centerPoint; _decoration.center = centerPoint; - _innerIconWrapperView.center = CGPointMake(_decoration.frame.size.width / 2.0f, _decoration.frame.size.height / 2.0f); + _innerIconWrapperView.center = CGPointMake(CGRectGetMidX(_decoration.bounds), CGRectGetMidY(_decoration.bounds)); _lockPanelWrapperView.frame = CGRectMake(floor(centerPoint.x - _lockPanelWrapperView.frame.size.width / 2.0f), floor(centerPoint.y - 122.0f - _lockPanelWrapperView.frame.size.height / 2.0f), _lockPanelWrapperView.frame.size.width, _lockPanelWrapperView.frame.size.height); @@ -918,6 +918,8 @@ static const CGFloat outerCircleMinScale = innerCircleRadius / outerCircleRadius _currentTranslation = MIN(0.0, _currentTranslation * 0.7f + _targetTranslation * 0.3f); _cancelTranslation = MIN(0.0, _cancelTranslation * 0.7f + _cancelTargetTranslation * 0.3f); + [self updateOverlay]; + if (t > _animationStartTime) { CGFloat outerScale = outerCircleMinScale + _currentLevel * (1.0f - outerCircleMinScale); CGAffineTransform translation = CGAffineTransformMakeTranslation(0, _currentTranslation); diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryController.m b/submodules/LegacyComponents/Sources/TGModernGalleryController.m index 6b6f83d8da..5f05c2a3cd 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryController.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryController.m @@ -44,6 +44,20 @@ static void adjustFrameRate(CAAnimation *animation) { } } +static bool TGModernGalleryViewHasFirstResponder(UIView *view) { + if (view.isFirstResponder) { + return true; + } + + for (UIView *subview in view.subviews) { + if (TGModernGalleryViewHasFirstResponder(subview)) { + return true; + } + } + + return false; +} + @interface TGModernGalleryController () { NSMutableDictionary *_reusableItemViewsByIdentifier; @@ -1759,6 +1773,28 @@ static CGFloat transformRotation(CGAffineTransform transform) } } +- (bool)_canBeginEditingCaptionFromKeyCommand +{ + UIView *interfaceView = _view.interfaceView; + if (interfaceView == nil) { + return false; + } + + if (![interfaceView respondsToSelector:@selector(beginEditingCaption)]) { + return false; + } + + if ([interfaceView respondsToSelector:@selector(canBeginEditingCaption)] && ![interfaceView canBeginEditingCaption]) { + return false; + } + + if (TGModernGalleryViewHasFirstResponder(_view)) { + return false; + } + + return true; +} + - (void)processKeyCommand:(UIKeyCommand *)keyCommand { if ([keyCommand.input isEqualToString:UIKeyInputLeftArrow]) @@ -1777,17 +1813,27 @@ static CGFloat transformRotation(CGAffineTransform transform) { _view.transitionOut(0.0f); } + else if ([keyCommand.input isEqualToString:@"\r"]) + { + if ([self _canBeginEditingCaptionFromKeyCommand]) + [_view.interfaceView beginEditingCaption]; + } } - (NSArray *)availableKeyCommands { - return @ + NSMutableArray *commands = [[NSMutableArray alloc] initWithArray:@ [ [TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputLeftArrow modifierFlags:0], [TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputRightArrow modifierFlags:0], [TGKeyCommand keyCommandWithTitle:nil input:UIKeyInputEscape modifierFlags:0], [TGKeyCommand keyCommandWithTitle:nil input:@"\t" modifierFlags:0] - ]; + ]]; + + if ([self _canBeginEditingCaptionFromKeyCommand]) + [commands addObject:[TGKeyCommand keyCommandWithTitle:nil input:@"\r" modifierFlags:0]]; + + return commands; } - (bool)isExclusive diff --git a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m index 7c5c7528f8..f92214b2b1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCaptionInputMixin.m @@ -10,13 +10,14 @@ { TGObserverProxy *_keyboardWillChangeFrameProxy; bool _editing; - + UIGestureRecognizer *_dismissTapRecognizer; - + CGRect _currentFrame; UIEdgeInsets _currentEdgeInsets; - + bool _currentIsCaptionAbove; + CGFloat _effectiveKeyboardHeight; } @end @@ -48,7 +49,7 @@ { if (_inputPanel != nil) return; - + UIView *parentView = [self _parentView]; id inputPanel = nil; @@ -63,7 +64,7 @@ [TGViewController enableAutorotation]; strongSelf->_editing = false; - + if (strongSelf.finishedWithCaption != nil) strongSelf.finishedWithCaption(string); }; @@ -104,10 +105,9 @@ }; _inputPanelView = inputPanel.view; + _inputPanelView.frame = parentView.bounds; + _inputPanelView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _backgroundView = [[UIView alloc] init]; - _backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor]; - //[parentView addSubview:_backgroundView]; [parentView addSubview:_inputPanelView]; if (_stickersContext && _stickersContext.livePhotoButton != nil) { @@ -138,6 +138,10 @@ - (void)createDismissViewIfNeeded { + if (_dismissView != nil) { + return; + } + UIView *parentView = [self _parentView]; _dismissView = [[UIView alloc] initWithFrame:parentView.bounds]; @@ -207,11 +211,36 @@ _dismissTapRecognizer.enabled = true; } +- (CGFloat)currentEffectiveKeyboardHeight +{ + return MAX(_keyboardHeight, _inputPanel.additionalInputHeight); +} + +- (bool)usesContainerLayout +{ + return [_inputPanel respondsToSelector:@selector(updateContainerLayoutSize:safeAreaInset:bottomInset:keyboardHeight:animated:)]; +} + +- (CGFloat)updateInputPanelLayoutForFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets keyboardHeight:(CGFloat)keyboardHeight animated:(bool)animated +{ + if ([self usesContainerLayout]) { + return [_inputPanel updateContainerLayoutSize:frame.size safeAreaInset:_safeAreaInset bottomInset:edgeInsets.bottom keyboardHeight:keyboardHeight animated:animated]; + } else { + return [_inputPanel updateLayoutSize:frame.size keyboardHeight:keyboardHeight sideInset:0.0 animated:animated]; + } +} + #pragma mark - - (void)finishEditing { if ([self.inputPanel dismissInput]) { _editing = false; + + [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + _dismissView.alpha = 0.0f; + } completion:^(BOOL finished) { + + }]; if (self.finishedWithCaption != nil) self.finishedWithCaption([_inputPanel caption]); @@ -261,9 +290,19 @@ } _keyboardHeight = keyboardHeight; - + if (!UIInterfaceOrientationIsPortrait([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]) && !TGIsPad()) + return; + + CGRect frame = _currentFrame; + UIEdgeInsets edgeInsets = _currentEdgeInsets; + bool usesContainerLayout = [self usesContainerLayout]; + CGRect containerFrame = CGRectMake(edgeInsets.left, 0.0, frame.size.width, frame.size.height); + _inputPanelView.frame = containerFrame; + CGFloat panelHeight = [self updateInputPanelLayoutForFrame:frame edgeInsets:edgeInsets keyboardHeight:keyboardHeight animated:usesContainerLayout]; + CGFloat effectiveKeyboardHeight = [self currentEffectiveKeyboardHeight]; + _effectiveKeyboardHeight = effectiveKeyboardHeight; CGFloat fadeAlpha = 1.0; - if (keyboardHeight < FLT_EPSILON) { + if (effectiveKeyboardHeight < FLT_EPSILON) { fadeAlpha = 0.0; } @@ -275,87 +314,90 @@ }]; } - if (!UIInterfaceOrientationIsPortrait([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]) && !TGIsPad()) - return; - - CGRect frame = _currentFrame; - UIEdgeInsets edgeInsets = _currentEdgeInsets; - CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:keyboardHeight sideInset:0.0 animated:false]; - - CGFloat panelY = 0.0; - if (frame.size.width > frame.size.height && !TGIsPad()) { - panelY = edgeInsets.top + frame.size.height; - } else { - if (_currentIsCaptionAbove) { - if (_keyboardHeight > 0.0) { - panelY = _safeAreaInset.top + 8.0; - } else { - panelY = _safeAreaInset.top + 8.0 + 40.0; - } + if (!usesContainerLayout) { + CGFloat panelY = 0.0; + if (frame.size.width > frame.size.height && !TGIsPad()) { + panelY = edgeInsets.top + frame.size.height; } else { - panelY = edgeInsets.top + frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight); + if (_currentIsCaptionAbove) { + if (effectiveKeyboardHeight > 0.0) { + panelY = _safeAreaInset.top + 8.0; + } else { + panelY = _safeAreaInset.top + 8.0 + 40.0; + } + } else { + panelY = edgeInsets.top + frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight); + } } + + [UIView animateWithDuration:duration delay:0.0f options:(curve << 16) animations:^{ + _inputPanelView.frame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight); + } completion:nil]; } - - CGFloat backgroundHeight = panelHeight; - if (_keyboardHeight > 0.0) { - backgroundHeight += _keyboardHeight - edgeInsets.bottom; - } - - [UIView animateWithDuration:duration delay:0.0f options:(curve << 16) animations:^{ - _inputPanelView.frame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight); - _backgroundView.frame = CGRectMake(edgeInsets.left, panelY, frame.size.width, backgroundHeight); - } completion:nil]; if (self.keyboardHeightChanged != nil) - self.keyboardHeightChanged(keyboardHeight, duration, curve); + self.keyboardHeightChanged(effectiveKeyboardHeight, duration, curve); } - (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated { _currentFrame = frame; _currentEdgeInsets = edgeInsets; - - CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:_keyboardHeight sideInset:0.0 animated:animated]; - - CGFloat panelY = 0.0; - if (frame.size.width > frame.size.height && !TGIsPad()) { - panelY = edgeInsets.top + frame.size.height; - } else { - if (_currentIsCaptionAbove) { - if (_keyboardHeight > 0.0) { - panelY = _safeAreaInset.top + 8.0; - } else { - panelY = _safeAreaInset.top + 8.0 + 40.0; - } + + bool usesContainerLayout = [self usesContainerLayout]; + CGRect containerFrame = CGRectMake(edgeInsets.left, 0.0, frame.size.width, frame.size.height); + CGFloat panelHeight = [self updateInputPanelLayoutForFrame:frame edgeInsets:edgeInsets keyboardHeight:_keyboardHeight animated:animated]; + CGFloat effectiveKeyboardHeight = [self currentEffectiveKeyboardHeight]; + CGFloat fadeAlpha = effectiveKeyboardHeight < FLT_EPSILON ? 0.0 : 1.0; + if (ABS(_dismissView.alpha - fadeAlpha) > FLT_EPSILON) { + if (animated) { + [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ + _dismissView.alpha = fadeAlpha; + } completion:nil]; } else { - panelY = edgeInsets.top + frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight); + _dismissView.alpha = fadeAlpha; } } - CGFloat backgroundHeight = panelHeight; - if (_keyboardHeight > 0.0) { - backgroundHeight += _keyboardHeight - edgeInsets.bottom; - } - CGFloat livePhotoY = 0.0; if (frame.size.width > frame.size.height && !TGIsPad()) { livePhotoY = 48.0; } else { - livePhotoY = edgeInsets.top + 145.0 - _keyboardHeight; + livePhotoY = edgeInsets.top + 145.0 - effectiveKeyboardHeight; } CGRect livePhotoButtonFrame = CGRectMake(_safeAreaInset.left + edgeInsets.left + 16.0, livePhotoY, 160.0, 18.0); _livePhotoButtonView.frame = livePhotoButtonFrame; - - CGRect panelFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight); - CGRect backgroundFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, backgroundHeight); - - if (animated) { - [_inputPanel animateView:_inputPanelView frame:panelFrame]; - [_inputPanel animateView:_backgroundView frame:backgroundFrame]; + + if (usesContainerLayout) { + _inputPanelView.frame = containerFrame; } else { - _inputPanelView.frame = panelFrame; - _backgroundView.frame = backgroundFrame; + CGFloat panelY = 0.0; + if (frame.size.width > frame.size.height && !TGIsPad()) { + panelY = edgeInsets.top + frame.size.height; + } else { + if (_currentIsCaptionAbove) { + if (effectiveKeyboardHeight > 0.0) { + panelY = _safeAreaInset.top + 8.0; + } else { + panelY = _safeAreaInset.top + 8.0 + 40.0; + } + } else { + panelY = edgeInsets.top + frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight); + } + } + + CGRect panelFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight); + if (animated) { + [_inputPanel animateView:_inputPanelView frame:panelFrame]; + } else { + _inputPanelView.frame = panelFrame; + } + } + if (ABS(_effectiveKeyboardHeight - effectiveKeyboardHeight) > FLT_EPSILON) { + _effectiveKeyboardHeight = effectiveKeyboardHeight; + if (self.keyboardHeightChanged != nil) { + self.keyboardHeightChanged(effectiveKeyboardHeight, animated ? 0.3 : 0.0, UIViewAnimationCurveEaseInOut); + } } } diff --git a/submodules/LegacyComponents/Sources/TGPhotoCropController.h b/submodules/LegacyComponents/Sources/TGPhotoCropController.h index 1a32504b05..6d6a873680 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCropController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoCropController.h @@ -21,6 +21,7 @@ - (void)rotate; - (void)mirror; - (void)aspectRatioButtonPressed; +- (void)aspectRatioButtonPressedWithSourceView:(UIView *)sourceView; - (void)setImage:(UIImage *)image; - (void)setSnapshotImage:(UIImage *)snapshotImage; diff --git a/submodules/LegacyComponents/Sources/TGPhotoCropController.m b/submodules/LegacyComponents/Sources/TGPhotoCropController.m index f197b0418f..eb854e0fa7 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoCropController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoCropController.m @@ -22,8 +22,6 @@ #import "TGPhotoCropView.h" #import -#import - const CGFloat TGPhotoCropButtonsWrapperSize = 61.0f; const CGSize TGPhotoCropAreaInsetSize = { 9, 9 }; @@ -535,6 +533,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original"; } - (void)aspectRatioButtonPressed +{ + [self aspectRatioButtonPressedWithSourceView:nil]; +} + +- (void)aspectRatioButtonPressedWithSourceView:(UIView *)sourceView { if (_cropView.isAnimating) return; @@ -547,16 +550,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original"; { [_cropView performConfirmAnimated:true]; - TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false]; - controller.dismissesByOutsideTap = true; - controller.hasSwipeGesture = true; - __weak TGMenuSheetController *weakController = controller; __weak TGPhotoCropController *weakSelf = self; void (^action)(NSString *) = ^(NSString *ratioString) { __strong TGPhotoCropController *strongSelf = weakSelf; - __strong TGMenuSheetController *strongController = weakController; if (strongSelf == nil) return; @@ -569,7 +567,7 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original"; else { aspectRatio = [ratioString floatValue]; - if (_cropView.cropOrientation == UIImageOrientationLeft || _cropView.cropOrientation == UIImageOrientationRight) + if (strongSelf->_cropView.cropOrientation == UIImageOrientationLeft || strongSelf->_cropView.cropOrientation == UIImageOrientationRight) aspectRatio = 1.0f / aspectRatio; } @@ -588,13 +586,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original"; else TGDispatchAfter(0.1f, dispatch_get_main_queue(), setAspectRatioBlock); #pragma clang diagnostic pop - - [strongController dismissAnimated:true]; }; - NSMutableArray *items = [[NSMutableArray alloc] init]; - [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioOriginal") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action(TGPhotoCropOriginalAspectRatio); }]]; - [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioSquare") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action(@"1.0"); }]]; + NSMutableArray *actions = [[NSMutableArray alloc] init]; + [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioOriginal") action:TGPhotoCropOriginalAspectRatio]]; + [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioSquare") action:@"1.0"]]; CGSize croppedImageSize = _cropView.cropRect.size; if (_cropView.cropOrientation == UIImageOrientationLeft || _cropView.cropOrientation == UIImageOrientationRight) @@ -634,26 +630,14 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original"; ratio = heightComponent / widthComponent; - [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:[NSString stringWithFormat:@"%d:%d", (int)widthComponent, (int)heightComponent] type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action([NSString stringWithFormat:@"%f", ratio]); }]]; + [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:[NSString stringWithFormat:@"%d:%d", (int)widthComponent, (int)heightComponent] action:[NSString stringWithFormat:@"%f", ratio]]]; } - [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ + [_context presentActionSheet:actions view:(sourceView ?: self.view) completion:^(LegacyComponentsActionSheetAction *selectedAction) { - __strong TGMenuSheetController *strongController = weakController; - if (strongController != nil) - [strongController dismissAnimated:true]; - }]]; - - [controller setItemViews:items]; -// controller.sourceRect = ^CGRect -// { -// __strong TGPhotoCropController *strongSelf = weakSelf; -// if (strongSelf != nil) -// return [strongSelf.view convertRect:strongSelf->_aspectRatioButton.frame fromView:strongSelf->_aspectRatioButton.superview]; -// -// return CGRectZero; -// }; - [controller presentInViewController:self.parentViewController sourceView:self.view animated:true]; + if (selectedAction != nil) + action(selectedAction.action); + }]; } [self _updateTabs]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m index c85605aee8..0fdaa96527 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m @@ -169,25 +169,7 @@ - (void)blurButtonPressed:(TGPhotoEditorBlurTypeButton *)sender { -// if (sender.tag != 0 && sender.tag == _currentType) -// { -// _editingIntensity = true; -// _startIntensity = [(PGBlurToolValue *)self.value intensity]; -// -// PGBlurToolValue *value = [(PGBlurToolValue *)self.value copy]; -// value.editingIntensity = true; -// -// _value = value; -// -// if (self.valueChanged != nil) -// self.valueChanged(value); -// -// [self setIntensitySliderHidden:false animated:true]; -// } -// else -// { - [self setSelectedBlurType:(PGBlurToolType)sender.tag update:true]; -// } + [self setSelectedBlurType:(PGBlurToolType)sender.tag update:true]; } - (void)setValue:(id)value diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index f61c9a9f19..c35be4ef10 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -27,6 +27,7 @@ #import #import +#import #import "TGPhotoEditorPreviewView.h" #import @@ -46,11 +47,21 @@ #import "TGMediaPickerGalleryVideoScrubber.h" #import "TGMediaPickerGalleryVideoScrubberThumbnailView.h" -#import - #import #import +static UIView *TGPhotoEditorCreatePhotoToolbarView(id context, TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, id stickersContext) +{ + if (stickersContext.photoToolbarView != nil) + { + UIView *toolbarView = stickersContext.photoToolbarView(backButton, doneButton, solidBackground, false); + if (toolbarView != nil) + return toolbarView; + } + + return [[TGPhotoToolbarView alloc] initWithContext:context backButton:backButton doneButton:doneButton solidBackground:solidBackground stickersContext:nil]; +} + @interface TGPhotoEditorController () { bool _switchingTab; @@ -64,8 +75,8 @@ UIView *_containerView; UIView *_wrapperView; UIView *_transitionWrapperView; - TGPhotoToolbarView *_portraitToolbarView; - TGPhotoToolbarView *_landscapeToolbarView; + UIView *_portraitToolbarView; + UIView *_landscapeToolbarView; TGPhotoEditorPreviewView *_previewView; PGPhotoEditorView *_fullPreviewView; UIView *_fullEntitiesView; @@ -285,16 +296,27 @@ case TGPhotoEditorRotateTab: case TGPhotoEditorMirrorTab: - case TGPhotoEditorAspectRatioTab: if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoCropController class]] || [strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) [strongSelf->_currentTabController handleTabAction:tab]; break; + + case TGPhotoEditorAspectRatioTab: + if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoCropController class]]) + { + UIView *toolbarView = UIInterfaceOrientationIsPortrait(strongSelf.effectiveOrientation) ? strongSelf->_portraitToolbarView : strongSelf->_landscapeToolbarView; + [(TGPhotoCropController *)strongSelf->_currentTabController aspectRatioButtonPressedWithSourceView:[toolbarView viewForTab:TGPhotoEditorAspectRatioTab]]; + } + else if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) + { + [strongSelf->_currentTabController handleTabAction:tab]; + } + break; } }; TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel; TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck; - _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil]; + _portraitToolbarView = TGPhotoEditorCreatePhotoToolbarView(_context, backButton, doneButton, true, _stickersContext); [_portraitToolbarView setToolbarTabs:_availableTabs animated:false]; [_portraitToolbarView setActiveTab:_currentTab]; _portraitToolbarView.cancelPressed = toolbarCancelPressed; @@ -303,7 +325,7 @@ _portraitToolbarView.tabPressed = toolbarTabPressed; [_wrapperView addSubview:_portraitToolbarView]; - _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil]; + _landscapeToolbarView = TGPhotoEditorCreatePhotoToolbarView(_context, backButton, doneButton, true, _stickersContext); [_landscapeToolbarView setToolbarTabs:_availableTabs animated:false]; [_landscapeToolbarView setActiveTab:_currentTab]; _landscapeToolbarView.cancelPressed = toolbarCancelPressed; @@ -1974,34 +1996,12 @@ if ((_initialAdjustments == nil && (![editorValues isDefaultValuesForAvatar:[self presentedForAvatarCreation]] || editorValues.cropOrientation != UIImageOrientationUp)) || (_initialAdjustments != nil && ![editorValues isEqual:_initialAdjustments])) { - TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false]; - controller.dismissesByOutsideTap = true; - controller.narrowInLandscape = true; - __weak TGMenuSheetController *weakController = controller; - - NSArray *items = @ + NSArray *actions = @ [ - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.DiscardChanges") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController == nil) - return; - - [strongController dismissAnimated:true manual:false completion:^ - { - dismiss(); - }]; - }], - [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^ - { - __strong TGMenuSheetController *strongController = weakController; - if (strongController != nil) - [strongController dismissAnimated:true]; - }] + [[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.DiscardChanges") action:@"discard" type:LegacyComponentsActionSheetActionTypeDestructive] ]; - [controller setItemViews:items]; - controller.sourceRect = ^ + [_context presentActionSheet:actions view:self.view sourceRect:^CGRect { __strong TGPhotoEditorController *strongSelf = weakSelf; if (strongSelf == nil) @@ -2011,8 +2011,11 @@ return [strongSelf.view convertRect:strongSelf->_portraitToolbarView.cancelButtonFrame fromView:strongSelf->_portraitToolbarView]; else return [strongSelf.view convertRect:strongSelf->_landscapeToolbarView.cancelButtonFrame fromView:strongSelf->_landscapeToolbarView]; - }; - [controller presentInViewController:self sourceView:self.view animated:true]; + } completion:^(LegacyComponentsActionSheetAction *selectedAction) + { + if ([selectedAction.action isEqualToString:@"discard"]) + dismiss(); + }]; } else { diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m index 9ab2a97410..8a22a46a4c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m @@ -30,11 +30,11 @@ + (UIColor *)accentColor { - TGMediaAssetsPallete *pallete = nil; - if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) - pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete]; - - return pallete.maybeAccentColor ?: UIColorRGB(0x65b3ff); +// TGMediaAssetsPallete *pallete = nil; +// if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)]) +// pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete]; +// + return UIColorRGB(0xffd300); //pallete.maybeAccentColor ?: UIColorRGB(0x65b3ff); } + (UIColor *)panelBackgroundColor @@ -374,7 +374,7 @@ + (UIFont *)editorItemTitleFont { - return [TGFont systemFontOfSize:14]; + return [TGFont boldSystemFontOfSize:13]; //[TGFont systemFontOfSize:1]; } + (UIColor *)filterSelectionColor diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m index 8b519b5860..2c98f01c3b 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m @@ -31,6 +31,7 @@ bool _animatingCancelDoneButtons; } + @end @implementation TGPhotoToolbarView @@ -635,6 +636,34 @@ return nil; } +- (UIView *)viewForTab:(TGPhotoEditorTab)tab +{ + return [self buttonForTab:tab]; +} + +- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset +{ + TGPhotoEditorButton *qualityButton = [self buttonForTab:TGPhotoEditorQualityTab]; + if (qualityButton == nil) + return; + + if (isPhoto) + qualityButton.iconImage = [TGPhotoEditorInterfaceAssets qualityIconForHighQuality:highQuality filled:false]; + else + qualityButton.iconImage = [TGPhotoEditorInterfaceAssets qualityIconForPreset:(TGMediaVideoConversionPreset)videoPreset]; +} + +- (void)setTimerButtonValue:(NSInteger)value +{ + TGPhotoEditorButton *timerButton = [self buttonForTab:TGPhotoEditorTimerTab]; + if (timerButton == nil) + return; + + UIImage *defaultIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:0]; + UIImage *activeIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:value]; + [timerButton setIconImage:defaultIcon activeIconImage:activeIcon]; +} + - (void)layoutSubviews { CGRect backgroundFrame = self.bounds; diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m index 4f81c2f1fe..b894c687d6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m +++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m @@ -5,6 +5,7 @@ #import #import +#import #import #import @@ -156,21 +157,18 @@ } } -+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed ++ (TGModernGalleryController *)_configuredControllerWithContext:(id _Nonnull)context caption:(NSAttributedString * _Nonnull)caption withItem:(id _Nonnull)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString * _Nonnull)recipientName stickersContext:(id _Nullable)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView * _Nullable)__unused mainSnapshot snapshots:(NSArray * _Nonnull)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder presentSchedulePicker:(TGPhotoVideoEditorSchedulePicker _Nonnull)presentSchedulePicker appeared:(void (^ _Nonnull)(void))appeared completion:(TGPhotoVideoEditorCompletion _Nonnull)completion completedDismiss:(void (^ _Nullable)(void))completedDismiss customDismiss:(void (^ _Nullable)(void))customDismiss { - id windowManager = [context makeOverlayWindowManager]; - id windowContext = [windowManager context]; - TGMediaEditingContext *editingContext = [[TGMediaEditingContext alloc] init]; [editingContext setForcedCaption:caption]; - TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:windowContext]; + TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:context]; galleryController.adjustsStatusBarVisibility = true; galleryController.animateTransition = !immediate; galleryController.finishedTransitionIn = ^(id item, TGModernGalleryItemView *itemView) { appeared(); }; - //galleryController.hasFadeOutTransition = true; + galleryController.customDismissBlock = customDismiss; id galleryItem = nil; if (item.isVideo) { @@ -181,7 +179,7 @@ galleryItem.editingContext = editingContext; galleryItem.stickersContext = stickersContext; - TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName isScheduledMessages:false hasCoverButton:false]; + TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:context items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName isScheduledMessages:false hasCoverButton:false]; model.controller = galleryController; model.stickersContext = stickersContext; @@ -213,6 +211,7 @@ galleryController.model = model; __weak TGModernGalleryController *weakGalleryController = galleryController; + __weak TGMediaPickerGalleryModel *weakModel = model; [model.interfaceView updateSelectionInterface:1 counterVisible:false animated:false]; model.interfaceView.thumbnailSignalForItem = ^SSignal *(id item) @@ -233,10 +232,75 @@ } if (completion != nil) - completion(item.asset, editingContext); + completion(item.asset, editingContext, false, 0); [strongController dismissWhenReadyAnimated:true]; }; + model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView) + { + __strong TGModernGalleryController *strongController = weakGalleryController; + __strong TGMediaPickerGalleryModel *strongModel = weakModel; + if (strongController == nil || strongModel == nil || !(hasSilentPosting || hasSchedule)) + return; + + if (iosMajorVersion() >= 10) { + UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium]; + [generator impactOccurred]; + } + + TGMediaPickerSendActionSheetController *sendController = [[TGMediaPickerSendActionSheetController alloc] initWithContext:context isDark:true sendButtonFrame:strongModel.interfaceView.doneButtonFrame canSendSilently:hasSilentPosting canSendWhenOnline:hasSchedule canSchedule:hasSchedule reminder:reminder hasTimer:false]; + sendController.modalPresentationStyle = UIModalPresentationOverFullScreen; + sendController.customDismissBlock = ^{ + __strong TGModernGalleryController *strongController = weakGalleryController; + [strongController dismissViewControllerAnimated:false completion:nil]; + }; + void (^complete)(bool, int32_t) = ^(bool silentPosting, int32_t scheduleTime) + { + __strong TGModernGalleryController *strongController = weakGalleryController; + if (strongController == nil) + return; + + if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]]) + { + TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item]; + [itemView stop]; + [itemView setPlayButtonHidden:true animated:true]; + } + + if (completion != nil) + completion(item.asset, editingContext, silentPosting, scheduleTime); + + [strongController dismissWhenReadyAnimated:true]; + }; + sendController.send = ^{ + complete(false, 0); + }; + sendController.sendSilently = ^{ + complete(true, 0); + }; + sendController.sendWhenOnline = ^{ + complete(false, 0x7ffffffe); + }; + sendController.schedule = ^{ + presentSchedulePicker(true, ^(int32_t time, bool silentPosting) { + complete(silentPosting, time); + }); + }; + if (sourceView != nil && stickersContext.presentMediaPickerSendActionMenu != nil && stickersContext.presentMediaPickerSendActionMenu(sourceView, hasSilentPosting, hasSchedule, hasSchedule, reminder, false, ^{ + if (sendController.sendSilently != nil) + sendController.sendSilently(); + }, ^{ + if (sendController.sendWhenOnline != nil) + sendController.sendWhenOnline(); + }, ^{ + if (sendController.schedule != nil) + sendController.schedule(); + }, ^{ + })) { + return; + } + [strongController presentViewController:sendController animated:false completion:nil]; + }; galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView) { @@ -247,21 +311,12 @@ { return nil; }; - - galleryController.completedTransitionOut = ^ - { - TGModernGalleryController *strongGalleryController = weakGalleryController; - if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil) + if (completedDismiss != nil) { + galleryController.completedTransitionOut = ^ { - TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController; - TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window; - if ([window isKindOfClass:[TGOverlayControllerWindow class]]) - [window dismiss]; - } - if (dismissed) { - dismissed(); - } - }; + completedDismiss(); + }; + } if (paint || adjustments) { [model.interfaceView immediateEditorTransitionIn]; @@ -271,8 +326,6 @@ [galleryController.view addSubview:view]; } - TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController]; - controllerWindow.hidden = false; galleryController.view.clipsToBounds = true; if (isGif) { @@ -292,9 +345,33 @@ [model beginEditingCaption]; }); } + + return galleryController; } -+ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)(bool))beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut; ++ (TGModernGalleryController * _Nonnull)controllerWithContext:(id _Nonnull)context caption:(NSAttributedString * _Nonnull)caption withItem:(id _Nonnull)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString * _Nonnull)recipientName stickersContext:(id _Nullable)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView * _Nullable)mainSnapshot snapshots:(NSArray * _Nonnull)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder presentSchedulePicker:(TGPhotoVideoEditorSchedulePicker _Nonnull)presentSchedulePicker appeared:(void (^ _Nonnull)(void))appeared completion:(TGPhotoVideoEditorCompletion _Nonnull)completion dismissed:(void (^ _Nonnull)(void))dismissed +{ + TGModernGalleryController *galleryController = [self _configuredControllerWithContext:context caption:caption withItem:item paint:paint adjustments:adjustments recipientName:recipientName stickersContext:stickersContext fromRect:fromRect mainSnapshot:mainSnapshot snapshots:snapshots immediate:immediate activateInput:activateInput isGif:isGif hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder presentSchedulePicker:presentSchedulePicker appeared:appeared completion:completion completedDismiss:dismissed customDismiss:nil]; + galleryController.asyncTransitionIn = true; + return galleryController; +} + ++ (void)presentWithContext:(id _Nonnull)context controller:(TGViewController * _Nonnull)controller caption:(NSAttributedString * _Nonnull)caption withItem:(id _Nonnull)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString * _Nonnull)recipientName stickersContext:(id _Nullable)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView * _Nullable)mainSnapshot snapshots:(NSArray * _Nonnull)snapshots immediate:(bool)immediate activateInput:(bool)activateInput isGif:(bool)isGif hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder presentSchedulePicker:(TGPhotoVideoEditorSchedulePicker _Nonnull)presentSchedulePicker appeared:(void (^ _Nonnull)(void))appeared completion:(TGPhotoVideoEditorCompletion _Nonnull)completion dismissed:(void (^ _Nonnull)(void))dismissed +{ + __weak TGViewController *weakController = controller; + TGModernGalleryController *galleryController = [self _configuredControllerWithContext:context caption:caption withItem:item paint:paint adjustments:adjustments recipientName:recipientName stickersContext:stickersContext fromRect:fromRect mainSnapshot:mainSnapshot snapshots:snapshots immediate:immediate activateInput:activateInput isGif:isGif hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder presentSchedulePicker:presentSchedulePicker appeared:appeared completion:completion completedDismiss:nil customDismiss:^{ + __strong TGViewController *strongController = weakController; + [strongController dismissViewControllerAnimated:false completion:^{ + if (dismissed) { + dismissed(); + } + }]; + }]; + galleryController.modalPresentationStyle = UIModalPresentationFullScreen; + [controller presentViewController:galleryController animated:false completion:nil]; +} + ++ (void)presentEditorWithContext:(id _Nonnull)context controller:(TGViewController * _Nonnull)controller withItem:(id _Nonnull)item cropRect:(CGRect)cropRect adjustments:(id _Nullable)adjustments referenceView:(UIView * _Nonnull)referenceView completion:(void (^ _Nonnull)(UIImage * _Nonnull image, id _Nullable adjustments))completion fullSizeCompletion:(void (^ _Nonnull)(UIImage * _Nonnull image))fullSizeCompletion beginTransitionOut:(void (^ _Nullable)(bool saving))beginTransitionOut finishTransitionOut:(void (^ _Nullable)(void))finishTransitionOut { id windowManager = [context makeOverlayWindowManager]; diff --git a/submodules/LegacyComponents/Sources/TGReplyMarkupAttachment.m b/submodules/LegacyComponents/Sources/TGReplyMarkupAttachment.m index 6a4dac0ad7..cb88e10a15 100644 --- a/submodules/LegacyComponents/Sources/TGReplyMarkupAttachment.m +++ b/submodules/LegacyComponents/Sources/TGReplyMarkupAttachment.m @@ -64,8 +64,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGViaUserAttachment.m b/submodules/LegacyComponents/Sources/TGViaUserAttachment.m index 51fb7394d8..964941c4e6 100644 --- a/submodules/LegacyComponents/Sources/TGViaUserAttachment.m +++ b/submodules/LegacyComponents/Sources/TGViaUserAttachment.m @@ -36,8 +36,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [NSKeyedUnarchiver unarchiveObjectWithData:data]; diff --git a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m index 3a410ff2d7..4e0f81e1bc 100644 --- a/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m +++ b/submodules/LegacyComponents/Sources/TGVideoMessageCaptureController.m @@ -330,11 +330,7 @@ typedef enum _circleWrapperView.alpha = 0.0f; _circleWrapperView.clipsToBounds = false; [_wrapperView addSubview:_circleWrapperView]; - - _shadowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"VideoMessageShadow")]; - _shadowView.frame = _circleWrapperView.bounds; - [_circleWrapperView addSubview:_shadowView]; - + _circleView = [[UIView alloc] initWithFrame:CGRectInset(_circleWrapperView.bounds, shadowSize, shadowSize)]; _circleView.clipsToBounds = true; _circleView.layer.cornerRadius = _circleView.frame.size.width / 2.0f; @@ -844,13 +840,13 @@ typedef enum } if (strongSelf.presentScheduleController) { - strongSelf.presentScheduleController(^(int32_t time) { + strongSelf.presentScheduleController(^(int32_t time, bool silentPosting) { __strong TGVideoMessageCaptureController *strongSelf = weakSelf; if (strongSelf == nil) { return; } - [strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:false scheduleTimestamp:time]; + [strongSelf finishWithURL:strongSelf->_url dimensions:CGSizeMake(240.0f, 240.0f) duration:strongSelf->_duration liveUploadData:strongSelf->_liveUploadData thumbnailImage:strongSelf->_thumbnailImage isSilent:silentPosting scheduleTimestamp:time]; _automaticDismiss = true; [strongSelf dismiss:false]; diff --git a/submodules/LegacyComponents/Sources/TGWebPageMediaAttachment.m b/submodules/LegacyComponents/Sources/TGWebPageMediaAttachment.m index 0550d67ced..413f423174 100644 --- a/submodules/LegacyComponents/Sources/TGWebPageMediaAttachment.m +++ b/submodules/LegacyComponents/Sources/TGWebPageMediaAttachment.m @@ -157,8 +157,15 @@ - (TGMediaAttachment *)parseMediaAttachment:(NSInputStream *)is { - int32_t length = [is readInt32]; - NSData *data = [is readData:length]; + bool readingError = false; + int32_t length = [is readInt32:&readingError]; + if (readingError) { + return nil; + } + NSData *data = [is readData:length failed:&readingError]; + if (readingError) { + return nil; + } @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/submodules/LegacyComponents/Sources/ocr.mm b/submodules/LegacyComponents/Sources/ocr.mm index b81744efe8..6ba13ae008 100755 --- a/submodules/LegacyComponents/Sources/ocr.mm +++ b/submodules/LegacyComponents/Sources/ocr.mm @@ -677,6 +677,9 @@ UIImage *normalizeImage(UIImage *image) NSString *recognizeMRZ(UIImage *input, CGRect *outBoundingRect) { + if (@"".length == 0) { + return nil; + } input = normalizeImage(input); UIImage *binaryImage; diff --git a/submodules/LegacyDataImport/BUILD b/submodules/LegacyDataImport/BUILD deleted file mode 100644 index 199ce4f386..0000000000 --- a/submodules/LegacyDataImport/BUILD +++ /dev/null @@ -1,23 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "LegacyDataImport", - module_name = "LegacyDataImport", - srcs = glob([ - "Sources/**/*.swift", - ]), - deps = [ - "//submodules/TelegramCore:TelegramCore", - "//submodules/SyncCore:SyncCore", - "//submodules/Postbox:Postbox", - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/TelegramNotices:TelegramNotices", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/RadialStatusNode:RadialStatusNode", - "//submodules/LegacyComponents:LegacyComponents", - "//submodules/LegacyDataImport/Impl:LegacyDataImportImpl", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/LegacyDataImportImpl.h b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/LegacyDataImportImpl.h deleted file mode 100644 index 7a94876eb4..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/LegacyDataImportImpl.h +++ /dev/null @@ -1,7 +0,0 @@ -#import - -#import -#import -#import - - diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.h b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.h deleted file mode 100644 index 975ffbd435..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.h +++ /dev/null @@ -1,76 +0,0 @@ -#import - -typedef enum { - TGNetworkTypeUnknown, - TGNetworkTypeNone, - TGNetworkTypeGPRS, - TGNetworkTypeEdge, - TGNetworkType3G, - TGNetworkTypeLTE, - TGNetworkTypeWiFi, -} TGNetworkType; - -typedef enum { - TGAutoDownloadModeNone = 0, - - TGAutoDownloadModeCellularContacts = 1 << 0, - TGAutoDownloadModeWifiContacts = 1 << 1, - - TGAutoDownloadModeCellularPrivateChats = 1 << 2, - TGAutoDownloadModeWifiPrivateChats = 1 << 3, - - TGAutoDownloadModeCellularGroups = 1 << 4, - TGAutoDownloadModeWifiGroups = 1 << 5, - - TGAutoDownloadModeCellularChannels = 1 << 6, - TGAutoDownloadModeWifiChannels = 1 << 7, - - TGAutoDownloadModeAutosavePhotosAll = TGAutoDownloadModeCellularContacts | TGAutoDownloadModeCellularPrivateChats | TGAutoDownloadModeCellularGroups | TGAutoDownloadModeCellularChannels, - - TGAutoDownloadModeAllPrivateChats = TGAutoDownloadModeCellularContacts | TGAutoDownloadModeWifiContacts | TGAutoDownloadModeCellularPrivateChats | TGAutoDownloadModeWifiPrivateChats, - TGAutoDownloadModeAllGroups = TGAutoDownloadModeCellularGroups | TGAutoDownloadModeWifiGroups | TGAutoDownloadModeCellularChannels | TGAutoDownloadModeWifiChannels, - TGAutoDownloadModeAll = TGAutoDownloadModeCellularContacts | TGAutoDownloadModeWifiContacts | TGAutoDownloadModeCellularPrivateChats | TGAutoDownloadModeWifiPrivateChats | TGAutoDownloadModeCellularGroups | TGAutoDownloadModeWifiGroups | TGAutoDownloadModeCellularChannels | TGAutoDownloadModeWifiChannels -} TGAutoDownloadMode; - -typedef enum { - TGAutoDownloadChatContact, - TGAutoDownloadChatOtherPrivateChat, - TGAutoDownloadChatGroup, - TGAutoDownloadChatChannel -} TGAutoDownloadChat; - -@interface TGAutoDownloadPreferences : NSObject - -@property (nonatomic, readonly) bool disabled; - -@property (nonatomic, readonly) TGAutoDownloadMode photos; -@property (nonatomic, readonly) TGAutoDownloadMode videos; -@property (nonatomic, readonly) int32_t maximumVideoSize; -@property (nonatomic, readonly) TGAutoDownloadMode documents; -@property (nonatomic, readonly) int32_t maximumDocumentSize; -@property (nonatomic, readonly) TGAutoDownloadMode gifs; -@property (nonatomic, readonly) TGAutoDownloadMode voiceMessages; -@property (nonatomic, readonly) TGAutoDownloadMode videoMessages; - -- (instancetype)updateDisabled:(bool)disabled; -- (instancetype)updatePhotosMode:(TGAutoDownloadMode)mode; -- (instancetype)updateVideosMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize; -- (instancetype)updateDocumentsMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize; -- (instancetype)updateGifsMode:(TGAutoDownloadMode)mode; -- (instancetype)updateVoiceMessagesMode:(TGAutoDownloadMode)mode; -- (instancetype)updateVideoMessagesMode:(TGAutoDownloadMode)mode; - -+ (bool)shouldDownload:(TGAutoDownloadMode)mode inChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadPhotoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadVideoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadDocumentInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadGifInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadVoiceMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; -- (bool)shouldDownloadVideoMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType; - -- (bool)isDefaultPreferences; - -+ (instancetype)defaultPreferences; -+ (instancetype)preferencesWithLegacyDownloadPrivatePhotos:(bool)privatePhotos groupPhotos:(bool)groupPhotos privateVoiceMessages:(bool)privateVoiceMessages groupVoiceMessages:(bool)groupVoiceMessages privateVideoMessages:(bool)privateVideoMessages groupVideoMessages:(bool)groupVideoMessages; - -@end diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.m b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.m deleted file mode 100644 index 64c8cca6c7..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGAutoDownloadPreferences.m +++ /dev/null @@ -1,250 +0,0 @@ -#import - -@implementation TGAutoDownloadPreferences - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if (self != nil) - { - _disabled = [aDecoder decodeBoolForKey:@"disabled"]; - _photos = [aDecoder decodeInt32ForKey:@"photos"]; - _videos = [aDecoder decodeInt32ForKey:@"videos"]; - _maximumVideoSize = [aDecoder decodeInt32ForKey:@"maxVideoSize"]; - _documents = [aDecoder decodeInt32ForKey:@"documents"]; - _maximumDocumentSize = [aDecoder decodeInt32ForKey:@"maxDocumentSize"]; - _voiceMessages = [aDecoder decodeInt32ForKey:@"voiceMessages"]; - _videoMessages = [aDecoder decodeInt32ForKey:@"videoMessages"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeBool:_disabled forKey:@"disabled"]; - [aCoder encodeInt32:_photos forKey:@"photos"]; - [aCoder encodeInt32:_videos forKey:@"videos"]; - [aCoder encodeInt32:_maximumVideoSize forKey:@"maxVideoSize"]; - [aCoder encodeInt32:_documents forKey:@"documents"]; - [aCoder encodeInt32:_maximumDocumentSize forKey:@"maxDocumentSize"]; - [aCoder encodeInt32:_voiceMessages forKey:@"voiceMessages"]; - [aCoder encodeInt32:_videoMessages forKey:@"videoMessages"]; -} - -+ (instancetype)defaultPreferences -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = TGAutoDownloadModeAll; - preferences->_videos = TGAutoDownloadModeNone; - preferences->_maximumVideoSize = 10; - preferences->_documents = TGAutoDownloadModeNone; - preferences->_maximumDocumentSize = 10; - preferences->_voiceMessages = TGAutoDownloadModeAll; - preferences->_videoMessages = TGAutoDownloadModeAll; - return preferences; -} - -+ (instancetype)preferencesWithLegacyDownloadPrivatePhotos:(bool)privatePhotos groupPhotos:(bool)groupPhotos privateVoiceMessages:(bool)privateVoiceMessages groupVoiceMessages:(bool)groupVoiceMessages privateVideoMessages:(bool)privateVideoMessages groupVideoMessages:(bool)groupVideoMessages -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - - if (privatePhotos) - preferences->_photos |= TGAutoDownloadModeAllPrivateChats; - if (groupPhotos) - preferences->_photos |= TGAutoDownloadModeAllGroups; - - if (privateVoiceMessages) - preferences->_voiceMessages |= TGAutoDownloadModeAllPrivateChats; - if (groupVoiceMessages) - preferences->_voiceMessages |= TGAutoDownloadModeAllGroups; - - if (privateVideoMessages) - preferences->_videoMessages |= TGAutoDownloadModeAllPrivateChats; - if (groupVideoMessages) - preferences->_videoMessages |= TGAutoDownloadModeAllGroups; - - preferences->_maximumVideoSize = 10; - preferences->_maximumDocumentSize = 10; - - return preferences; -} - -- (instancetype)updateDisabled:(bool)disabled -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_disabled = disabled; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updatePhotosMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = mode; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVideosMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = mode; - preferences->_maximumVideoSize = maximumSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateDocumentsMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = mode; - preferences->_maximumDocumentSize = maximumSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVoiceMessagesMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = mode; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVideoMessagesMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = mode; - return preferences; -} - -+ (bool)shouldDownload:(TGAutoDownloadMode)mode inChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - bool isWiFi = networkType == TGNetworkTypeWiFi; - bool isCellular = !isWiFi && networkType != TGNetworkTypeNone; - - bool shouldDownload = false; - switch (chat) - { - case TGAutoDownloadChatContact: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularContacts) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiContacts) != 0; - break; - - case TGAutoDownloadChatOtherPrivateChat: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularPrivateChats) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiPrivateChats) != 0; - break; - - case TGAutoDownloadChatGroup: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularGroups) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiGroups) != 0; - break; - - case TGAutoDownloadChatChannel: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularChannels) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiChannels) != 0; - break; - - default: - break; - } - return shouldDownload; -} - -- (bool)shouldDownloadPhotoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_photos inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVideoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_videos inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadDocumentInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_documents inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVoiceMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_voiceMessages inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVideoMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_videoMessages inChat:chat networkType:networkType]; -} - -- (bool)isDefaultPreferences -{ - return [self isEqual:[TGAutoDownloadPreferences defaultPreferences]]; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) - return YES; - - if (!object || ![object isKindOfClass:[self class]]) - return NO; - - TGAutoDownloadPreferences *preferences = (TGAutoDownloadPreferences *)object; - return preferences.photos == _photos && preferences.videos == _videos && preferences.documents == _documents && preferences.voiceMessages == _voiceMessages && preferences.videoMessages == _videoMessages && preferences.maximumVideoSize == _maximumVideoSize && preferences.maximumDocumentSize == _maximumDocumentSize && preferences.disabled == _disabled; -} - -@end diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.h b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.h deleted file mode 100644 index 1a403c5215..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.h +++ /dev/null @@ -1,31 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef enum -{ - TGPresentationAutoNightModeDisabled, - TGPresentationAutoNightModeBrightness, - TGPresentationAutoNightModeScheduled, - TGPresentationAutoNightModeSunsetSunrise -} TGPresentationAutoNightMode; - -@interface TGPresentationAutoNightPreferences : NSObject - -@property (nonatomic, readonly) TGPresentationAutoNightMode mode; - -@property (nonatomic, readonly) CGFloat brightnessThreshold; - -@property (nonatomic, readonly) int32_t scheduleStart; -@property (nonatomic, readonly) int32_t scheduleEnd; - -@property (nonatomic, readonly) CGFloat latitude; -@property (nonatomic, readonly) CGFloat longitude; -@property (nonatomic, readonly) NSString *cachedLocationName; - -@property (nonatomic, readonly) int32_t preferredPalette; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.m b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.m deleted file mode 100644 index b53b768e38..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGPresentationAutoNightPreferences.m +++ /dev/null @@ -1,23 +0,0 @@ -#import - -@implementation TGPresentationAutoNightPreferences - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - if (self != nil) { - _mode = [aDecoder decodeInt32ForKey:@"m"]; - _brightnessThreshold = [aDecoder decodeDoubleForKey:@"b"]; - _scheduleStart = [aDecoder decodeInt32ForKey:@"ss"]; - _scheduleEnd = [aDecoder decodeInt32ForKey:@"se"]; - _latitude = [aDecoder decodeDoubleForKey:@"lat"]; - _longitude = [aDecoder decodeDoubleForKey:@"lon"]; - _cachedLocationName = [aDecoder decodeObjectForKey:@"loc"]; - _preferredPalette = [aDecoder decodeInt32ForKey:@"p"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { -} - -@end diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.h b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.h deleted file mode 100644 index 72c1b76fd3..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.h +++ /dev/null @@ -1,15 +0,0 @@ -#import - -@interface TGProxyItem : NSObject - -@property (nonatomic, readonly) NSString *server; -@property (nonatomic, readonly) int16_t port; -@property (nonatomic, readonly) NSString *username; -@property (nonatomic, readonly) NSString *password; -@property (nonatomic, readonly) NSString *secret; - -@property (nonatomic, readonly) bool isMTProxy; - -- (instancetype)initWithServer:(NSString *)server port:(int16_t)port username:(NSString *)username password:(NSString *)password secret:(NSString *)secret; - -@end diff --git a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.m b/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.m deleted file mode 100644 index 4dfc1d6c93..0000000000 --- a/submodules/LegacyDataImport/Impl/PublicHeaders/LegacyDataImportImpl/TGProxyItem.m +++ /dev/null @@ -1,73 +0,0 @@ -#import - -static bool TGObjectCompare(id obj1, id obj2) { - if (obj1 == nil && obj2 == nil) - return true; - - return [obj1 isEqual:obj2]; -} - -@implementation TGProxyItem - -- (instancetype)initWithServer:(NSString *)server port:(int16_t)port username:(NSString *)username password:(NSString *)password secret:(NSString *)secret -{ - self = [super init]; - if (self != nil) - { - _server = server; - _port = port; - _username = username; - _password = password; - _secret = secret; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - return [self initWithServer:[aDecoder decodeObjectForKey:@"server"] port:(int16_t)[aDecoder decodeInt32ForKey:@"port"] username:[aDecoder decodeObjectForKey:@"user"] password:[aDecoder decodeObjectForKey:@"pass"] secret:[aDecoder decodeObjectForKey:@"secret"]]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_server forKey:@"server"]; - [aCoder encodeInt32:_port forKey:@"port"]; - [aCoder encodeObject:_username forKey:@"user"]; - [aCoder encodeObject:_password forKey:@"pass"]; - [aCoder encodeObject:_secret forKey:@"secret"]; -} - -- (bool)isMTProxy -{ - return _secret.length > 0; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) - return true; - - if (!object || ![object isKindOfClass:[self class]]) - return false; - - TGProxyItem *proxy = (TGProxyItem *)object; - - if (![_server isEqualToString:proxy.server]) - return false; - - if (_port != proxy.port) - return false; - - if (!TGObjectCompare(_username ?: @"", proxy.username ?: @"")) - return false; - - if (!TGObjectCompare(_password ?: @"", proxy.password ?: @"")) - return false; - - if (!TGObjectCompare(_secret ?: @"", proxy.secret ?: @"")) - return false; - - return true; -} - -@end diff --git a/submodules/LegacyDataImport/Impl/Sources/TGAutoDownloadPreferences.m b/submodules/LegacyDataImport/Impl/Sources/TGAutoDownloadPreferences.m deleted file mode 100644 index 64c8cca6c7..0000000000 --- a/submodules/LegacyDataImport/Impl/Sources/TGAutoDownloadPreferences.m +++ /dev/null @@ -1,250 +0,0 @@ -#import - -@implementation TGAutoDownloadPreferences - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if (self != nil) - { - _disabled = [aDecoder decodeBoolForKey:@"disabled"]; - _photos = [aDecoder decodeInt32ForKey:@"photos"]; - _videos = [aDecoder decodeInt32ForKey:@"videos"]; - _maximumVideoSize = [aDecoder decodeInt32ForKey:@"maxVideoSize"]; - _documents = [aDecoder decodeInt32ForKey:@"documents"]; - _maximumDocumentSize = [aDecoder decodeInt32ForKey:@"maxDocumentSize"]; - _voiceMessages = [aDecoder decodeInt32ForKey:@"voiceMessages"]; - _videoMessages = [aDecoder decodeInt32ForKey:@"videoMessages"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeBool:_disabled forKey:@"disabled"]; - [aCoder encodeInt32:_photos forKey:@"photos"]; - [aCoder encodeInt32:_videos forKey:@"videos"]; - [aCoder encodeInt32:_maximumVideoSize forKey:@"maxVideoSize"]; - [aCoder encodeInt32:_documents forKey:@"documents"]; - [aCoder encodeInt32:_maximumDocumentSize forKey:@"maxDocumentSize"]; - [aCoder encodeInt32:_voiceMessages forKey:@"voiceMessages"]; - [aCoder encodeInt32:_videoMessages forKey:@"videoMessages"]; -} - -+ (instancetype)defaultPreferences -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = TGAutoDownloadModeAll; - preferences->_videos = TGAutoDownloadModeNone; - preferences->_maximumVideoSize = 10; - preferences->_documents = TGAutoDownloadModeNone; - preferences->_maximumDocumentSize = 10; - preferences->_voiceMessages = TGAutoDownloadModeAll; - preferences->_videoMessages = TGAutoDownloadModeAll; - return preferences; -} - -+ (instancetype)preferencesWithLegacyDownloadPrivatePhotos:(bool)privatePhotos groupPhotos:(bool)groupPhotos privateVoiceMessages:(bool)privateVoiceMessages groupVoiceMessages:(bool)groupVoiceMessages privateVideoMessages:(bool)privateVideoMessages groupVideoMessages:(bool)groupVideoMessages -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - - if (privatePhotos) - preferences->_photos |= TGAutoDownloadModeAllPrivateChats; - if (groupPhotos) - preferences->_photos |= TGAutoDownloadModeAllGroups; - - if (privateVoiceMessages) - preferences->_voiceMessages |= TGAutoDownloadModeAllPrivateChats; - if (groupVoiceMessages) - preferences->_voiceMessages |= TGAutoDownloadModeAllGroups; - - if (privateVideoMessages) - preferences->_videoMessages |= TGAutoDownloadModeAllPrivateChats; - if (groupVideoMessages) - preferences->_videoMessages |= TGAutoDownloadModeAllGroups; - - preferences->_maximumVideoSize = 10; - preferences->_maximumDocumentSize = 10; - - return preferences; -} - -- (instancetype)updateDisabled:(bool)disabled -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_disabled = disabled; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updatePhotosMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = mode; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVideosMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = mode; - preferences->_maximumVideoSize = maximumSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateDocumentsMode:(TGAutoDownloadMode)mode maximumSize:(int32_t)maximumSize -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = mode; - preferences->_maximumDocumentSize = maximumSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVoiceMessagesMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = mode; - preferences->_videoMessages = _videoMessages; - return preferences; -} - -- (instancetype)updateVideoMessagesMode:(TGAutoDownloadMode)mode -{ - TGAutoDownloadPreferences *preferences = [[TGAutoDownloadPreferences alloc] init]; - preferences->_photos = _photos; - preferences->_videos = _videos; - preferences->_maximumVideoSize = _maximumVideoSize; - preferences->_documents = _documents; - preferences->_maximumDocumentSize = _maximumDocumentSize; - preferences->_voiceMessages = _voiceMessages; - preferences->_videoMessages = mode; - return preferences; -} - -+ (bool)shouldDownload:(TGAutoDownloadMode)mode inChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - bool isWiFi = networkType == TGNetworkTypeWiFi; - bool isCellular = !isWiFi && networkType != TGNetworkTypeNone; - - bool shouldDownload = false; - switch (chat) - { - case TGAutoDownloadChatContact: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularContacts) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiContacts) != 0; - break; - - case TGAutoDownloadChatOtherPrivateChat: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularPrivateChats) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiPrivateChats) != 0; - break; - - case TGAutoDownloadChatGroup: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularGroups) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiGroups) != 0; - break; - - case TGAutoDownloadChatChannel: - if (isCellular) - shouldDownload = (mode & TGAutoDownloadModeCellularChannels) != 0; - else if (isWiFi) - shouldDownload = (mode & TGAutoDownloadModeWifiChannels) != 0; - break; - - default: - break; - } - return shouldDownload; -} - -- (bool)shouldDownloadPhotoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_photos inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVideoInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_videos inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadDocumentInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_documents inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVoiceMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_voiceMessages inChat:chat networkType:networkType]; -} - -- (bool)shouldDownloadVideoMessageInChat:(TGAutoDownloadChat)chat networkType:(TGNetworkType)networkType -{ - if (self.disabled) - return false; - - return [TGAutoDownloadPreferences shouldDownload:_videoMessages inChat:chat networkType:networkType]; -} - -- (bool)isDefaultPreferences -{ - return [self isEqual:[TGAutoDownloadPreferences defaultPreferences]]; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) - return YES; - - if (!object || ![object isKindOfClass:[self class]]) - return NO; - - TGAutoDownloadPreferences *preferences = (TGAutoDownloadPreferences *)object; - return preferences.photos == _photos && preferences.videos == _videos && preferences.documents == _documents && preferences.voiceMessages == _voiceMessages && preferences.videoMessages == _videoMessages && preferences.maximumVideoSize == _maximumVideoSize && preferences.maximumDocumentSize == _maximumDocumentSize && preferences.disabled == _disabled; -} - -@end diff --git a/submodules/LegacyDataImport/Impl/Sources/TGPresentationAutoNightPreferences.m b/submodules/LegacyDataImport/Impl/Sources/TGPresentationAutoNightPreferences.m deleted file mode 100644 index b53b768e38..0000000000 --- a/submodules/LegacyDataImport/Impl/Sources/TGPresentationAutoNightPreferences.m +++ /dev/null @@ -1,23 +0,0 @@ -#import - -@implementation TGPresentationAutoNightPreferences - -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super init]; - if (self != nil) { - _mode = [aDecoder decodeInt32ForKey:@"m"]; - _brightnessThreshold = [aDecoder decodeDoubleForKey:@"b"]; - _scheduleStart = [aDecoder decodeInt32ForKey:@"ss"]; - _scheduleEnd = [aDecoder decodeInt32ForKey:@"se"]; - _latitude = [aDecoder decodeDoubleForKey:@"lat"]; - _longitude = [aDecoder decodeDoubleForKey:@"lon"]; - _cachedLocationName = [aDecoder decodeObjectForKey:@"loc"]; - _preferredPalette = [aDecoder decodeInt32ForKey:@"p"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder { -} - -@end diff --git a/submodules/LegacyDataImport/Impl/Sources/TGProxyItem.m b/submodules/LegacyDataImport/Impl/Sources/TGProxyItem.m deleted file mode 100644 index 4dfc1d6c93..0000000000 --- a/submodules/LegacyDataImport/Impl/Sources/TGProxyItem.m +++ /dev/null @@ -1,73 +0,0 @@ -#import - -static bool TGObjectCompare(id obj1, id obj2) { - if (obj1 == nil && obj2 == nil) - return true; - - return [obj1 isEqual:obj2]; -} - -@implementation TGProxyItem - -- (instancetype)initWithServer:(NSString *)server port:(int16_t)port username:(NSString *)username password:(NSString *)password secret:(NSString *)secret -{ - self = [super init]; - if (self != nil) - { - _server = server; - _port = port; - _username = username; - _password = password; - _secret = secret; - } - return self; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - return [self initWithServer:[aDecoder decodeObjectForKey:@"server"] port:(int16_t)[aDecoder decodeInt32ForKey:@"port"] username:[aDecoder decodeObjectForKey:@"user"] password:[aDecoder decodeObjectForKey:@"pass"] secret:[aDecoder decodeObjectForKey:@"secret"]]; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_server forKey:@"server"]; - [aCoder encodeInt32:_port forKey:@"port"]; - [aCoder encodeObject:_username forKey:@"user"]; - [aCoder encodeObject:_password forKey:@"pass"]; - [aCoder encodeObject:_secret forKey:@"secret"]; -} - -- (bool)isMTProxy -{ - return _secret.length > 0; -} - -- (BOOL)isEqual:(id)object -{ - if (object == self) - return true; - - if (!object || ![object isKindOfClass:[self class]]) - return false; - - TGProxyItem *proxy = (TGProxyItem *)object; - - if (![_server isEqualToString:proxy.server]) - return false; - - if (_port != proxy.port) - return false; - - if (!TGObjectCompare(_username ?: @"", proxy.username ?: @"")) - return false; - - if (!TGObjectCompare(_password ?: @"", proxy.password ?: @"")) - return false; - - if (!TGObjectCompare(_secret ?: @"", proxy.secret ?: @"")) - return false; - - return true; -} - -@end diff --git a/submodules/LegacyDataImport/Sources/LegacyBuffer.swift b/submodules/LegacyDataImport/Sources/LegacyBuffer.swift deleted file mode 100644 index cebc964f39..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyBuffer.swift +++ /dev/null @@ -1,197 +0,0 @@ -import Foundation - -class LegacyBuffer: CustomStringConvertible { - var data: UnsafeMutableRawPointer? - var _size: UInt = 0 - private var capacity: UInt = 0 - private let freeWhenDone: Bool - - var size: Int { - return Int(self._size) - } - - deinit { - if self.freeWhenDone { - free(self.data) - } - } - - init(memory: UnsafeMutableRawPointer?, size: Int, capacity: Int, freeWhenDone: Bool) { - self.data = memory - self._size = UInt(size) - self.capacity = UInt(capacity) - self.freeWhenDone = freeWhenDone - } - - init() { - self.data = nil - self._size = 0 - self.capacity = 0 - self.freeWhenDone = true - } - - convenience init(data: Data?) { - self.init() - - if let data = data { - data.withUnsafeBytes { bytes in - self.appendBytes(bytes, length: UInt(data.count)) - } - } - } - - func makeData() -> Data { - return self.withUnsafeMutablePointer { pointer, size -> Data in - if let pointer = pointer { - return Data(bytes: pointer.assumingMemoryBound(to: UInt8.self), count: Int(size)) - } else { - return Data() - } - } - } - - var description: String { - get { - var string = "" - if let data = self.data { - var i: UInt = 0 - let bytes = data.assumingMemoryBound(to: UInt8.self) - while i < _size && i < 8 { - string += String(format: "%02x", Int(bytes.advanced(by: Int(i)).pointee)) - i += 1 - } - if i < _size { - string += "...\(_size)b" - } - } else { - string += "" - } - return string - } - } - - func appendBytes(_ bytes: UnsafeRawPointer, length: UInt) { - if self.capacity < self._size + length { - self.capacity = self._size + length + 128 - if self.data == nil { - self.data = malloc(Int(self.capacity))! - } - else { - self.data = realloc(self.data, Int(self.capacity))! - } - } - - memcpy(self.data?.advanced(by: Int(self._size)), bytes, Int(length)) - self._size += length - } - - func appendBuffer(_ buffer: LegacyBuffer) { - if self.capacity < self._size + buffer._size { - self.capacity = self._size + buffer._size + 128 - if self.data == nil { - self.data = malloc(Int(self.capacity))! - } - else { - self.data = realloc(self.data, Int(self.capacity))! - } - } - - memcpy(self.data?.advanced(by: Int(self._size)), buffer.data, Int(buffer._size)) - } - - func appendInt32(_ value: Int32) { - var v = value - self.appendBytes(&v, length: 4) - } - - func appendInt64(_ value: Int64) { - var v = value - self.appendBytes(&v, length: 8) - } - - func appendDouble(_ value: Double) { - var v = value - self.appendBytes(&v, length: 8) - } - - func withUnsafeMutablePointer(_ f: (UnsafeMutableRawPointer?, UInt) -> R) -> R { - return f(self.data, self._size) - } -} - -class LegacyBufferReader { - private let buffer: LegacyBuffer - private(set) var offset: UInt = 0 - - init(_ buffer: LegacyBuffer) { - self.buffer = buffer - } - - func reset() { - self.offset = 0 - } - - func skip(_ count: Int) { - self.offset = min(self.buffer._size, self.offset + UInt(count)) - } - - func readInt32() -> Int32? { - if self.offset + 4 <= self.buffer._size { - let value: Int32 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int32.self).pointee - self.offset += 4 - return value - } - return nil - } - - func readInt64() -> Int64? { - if self.offset + 8 <= self.buffer._size { - let value: Int64 = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Int64.self).pointee - self.offset += 8 - return value - } - return nil - } - - func readDouble() -> Double? { - if self.offset + 8 <= self.buffer._size { - let value: Double = buffer.data!.advanced(by: Int(self.offset)).assumingMemoryBound(to: Double.self).pointee - self.offset += 8 - return value - } - return nil - } - - func readBytesAsInt32(_ count: Int) -> Int32? { - if count == 0 { - return 0 - } - guard count > 0, count <= 4, self.offset + UInt(count) <= self.buffer._size else { - return nil - } - guard let bufferData = self.buffer.data else { - return nil - } - var value: Int32 = 0 - memcpy(&value, bufferData.advanced(by: Int(self.offset)), count) - self.offset += UInt(count) - return value - } - - func readBuffer(_ count: Int) -> LegacyBuffer? { - if count >= 0 && self.offset + UInt(count) <= self.buffer._size { - let buffer = LegacyBuffer() - buffer.appendBytes((self.buffer.data?.advanced(by: Int(self.offset)))!, length: UInt(count)) - self.offset += UInt(count) - return buffer - } - return nil - } - - func withReadBufferNoCopy(_ count: Int, _ f: (LegacyBuffer) -> T) -> T? { - if count >= 0 && self.offset + UInt(count) <= self.buffer._size { - return f(LegacyBuffer(memory: self.buffer.data!.advanced(by: Int(self.offset)), size: count, capacity: count, freeWhenDone: false)) - } - return nil - } -} diff --git a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift b/submodules/LegacyDataImport/Sources/LegacyChatImport.swift deleted file mode 100644 index 9a50a1471a..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyChatImport.swift +++ /dev/null @@ -1,780 +0,0 @@ -import Foundation -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import LegacyComponents - -private let reportedLayer_hash: Int32 = -717538193 -private let layer_hash: Int32 = 849537378 -private let seq_out_hash: Int32 = -737765753 -private let seq_in_hash: Int32 = -7646011 - -private let defaultPrime: Data = { - let bytes: [UInt8] = [ - 0xc7, 0x1c, 0xae, 0xb9, 0xc6, 0xb1, 0xc9, 0x04, 0x8e, 0x6c, 0x52, 0x2f, - 0x70, 0xf1, 0x3f, 0x73, 0x98, 0x0d, 0x40, 0x23, 0x8e, 0x3e, 0x21, 0xc1, - 0x49, 0x34, 0xd0, 0x37, 0x56, 0x3d, 0x93, 0x0f, 0x48, 0x19, 0x8a, 0x0a, - 0xa7, 0xc1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xd2, 0x25, 0x30, 0xf4, 0xdb, - 0xfa, 0x33, 0x6f, 0x6e, 0x0a, 0xc9, 0x25, 0x13, 0x95, 0x43, 0xae, 0xd4, - 0x4c, 0xce, 0x7c, 0x37, 0x20, 0xfd, 0x51, 0xf6, 0x94, 0x58, 0x70, 0x5a, - 0xc6, 0x8c, 0xd4, 0xfe, 0x6b, 0x6b, 0x13, 0xab, 0xdc, 0x97, 0x46, 0x51, - 0x29, 0x69, 0x32, 0x84, 0x54, 0xf1, 0x8f, 0xaf, 0x8c, 0x59, 0x5f, 0x64, - 0x24, 0x77, 0xfe, 0x96, 0xbb, 0x2a, 0x94, 0x1d, 0x5b, 0xcd, 0x1d, 0x4a, - 0xc8, 0xcc, 0x49, 0x88, 0x07, 0x08, 0xfa, 0x9b, 0x37, 0x8e, 0x3c, 0x4f, - 0x3a, 0x90, 0x60, 0xbe, 0xe6, 0x7c, 0xf9, 0xa4, 0xa4, 0xa6, 0x95, 0x81, - 0x10, 0x51, 0x90, 0x7e, 0x16, 0x27, 0x53, 0xb5, 0x6b, 0x0f, 0x6b, 0x41, - 0x0d, 0xba, 0x74, 0xd8, 0xa8, 0x4b, 0x2a, 0x14, 0xb3, 0x14, 0x4e, 0x0e, - 0xf1, 0x28, 0x47, 0x54, 0xfd, 0x17, 0xed, 0x95, 0x0d, 0x59, 0x65, 0xb4, - 0xb9, 0xdd, 0x46, 0x58, 0x2d, 0xb1, 0x17, 0x8d, 0x16, 0x9c, 0x6b, 0xc4, - 0x65, 0xb0, 0xd6, 0xff, 0x9c, 0xa3, 0x92, 0x8f, 0xef, 0x5b, 0x9a, 0xe4, - 0xe4, 0x18, 0xfc, 0x15, 0xe8, 0x3e, 0xbe, 0xa0, 0xf8, 0x7f, 0xa9, 0xff, - 0x5e, 0xed, 0x70, 0x05, 0x0d, 0xed, 0x28, 0x49, 0xf4, 0x7b, 0xf9, 0x59, - 0xd9, 0x56, 0x85, 0x0c, 0xe9, 0x29, 0x85, 0x1f, 0x0d, 0x81, 0x15, 0xf6, - 0x35, 0xb1, 0x05, 0xee, 0x2e, 0x4e, 0x15, 0xd0, 0x4b, 0x24, 0x54, 0xbf, - 0x6f, 0x4f, 0xad, 0xf0, 0x34, 0xb1, 0x04, 0x03, 0x11, 0x9c, 0xd8, 0xe3, - 0xb9, 0x2f, 0xcc, 0x5b - ] - var data = Data(count: bytes.count) - data.withUnsafeMutableBytes { (dst: UnsafeMutablePointer) -> Void in - for i in 0 ..< bytes.count { - dst.advanced(by: i).pointee = bytes[i] - } - } - return data -}() - -@objc(TGEncryptionKeyData) private final class TGEncryptionKeyData: NSObject, NSCoding { - let keyId: Int64 - let key: Data - let firstSeqOut: Int32 - - init?(coder aDecoder: NSCoder) { - self.keyId = aDecoder.decodeInt64(forKey: "keyId") - self.key = (aDecoder.decodeObject(forKey: "key") as? Data) ?? Data() - self.firstSeqOut = aDecoder.decodeInt32(forKey: "firstSeqOut") - } - - func encode(with aCoder: NSCoder) { - assertionFailure() - } -} - -private struct SecretChatData { - let accessHash: Int64 - let handshakeState: Int32 - let rekeyState: SecretChatRekeySessionState? -} - -private func readSecretChatParticipantData(accountPeerId: PeerId, data: Data) -> (SecretChatRole, PeerId)? { - let reader = LegacyBufferReader(LegacyBuffer(data: data)) - - guard reader.readInt32() == Int32(bitPattern: 0xabcdef12) else { - return nil - } - guard let formatVersion = reader.readInt32(), formatVersion >= 2 else { - return nil - } - reader.skip(4) - - guard let adminId = reader.readInt32() else { - return nil - } - guard let count = reader.readInt32() else { - return nil - } - var ids: [Int32] = [] - for _ in 0 ..< Int(count) { - guard let id = reader.readInt32() else { - return nil - } - reader.skip(4) - reader.skip(4) - ids.append(id) - } - - guard let otherPeerId = ids.first else { - return nil - } - - return (adminId == accountPeerId.id ? .creator : .participant, PeerId(namespace: Namespaces.Peer.CloudUser, id: otherPeerId)) -} - -private func readSecretChatData(reader: LegacyBufferReader) -> SecretChatData? { - guard let version = reader.readBytesAsInt32(1) else { - return nil - } - if version != 3 { - return nil - } - reader.skip(8) - - guard let accessHash = reader.readInt64() else { - return nil - } - guard let _ = reader.readInt64() else { - return nil - } - guard let handshakeState = reader.readInt32() else { - return nil - } - guard let currentRekeyExchangeId = reader.readInt64() else { - return nil - } - guard let currentRekeyIsInitiatedByLocalClient = reader.readBytesAsInt32(1) else { - return nil - } - guard let currentRekeyNumberLength = reader.readInt32() else { - return nil - } - var currentRekeyNumber: Data? - if currentRekeyNumberLength > 0 { - guard let value = reader.readBuffer(Int(currentRekeyNumberLength))?.makeData() else { - return nil - } - currentRekeyNumber = value - } - guard let currentRekeyKeyLength = reader.readInt32() else { - return nil - } - var currentRekeyKey: Data? - if currentRekeyKeyLength > 0 { - guard let value = reader.readBuffer(Int(currentRekeyKeyLength))?.makeData() else { - return nil - } - currentRekeyKey = value - } - guard let currentRekeyKeyId = reader.readInt64() else { - return nil - } - - var rekeyState: SecretChatRekeySessionState? - if currentRekeyExchangeId != 0 { - let innerState: SecretChatRekeySessionData? - if currentRekeyIsInitiatedByLocalClient != 0, let currentRekeyNumber = currentRekeyNumber { - innerState = .requested(a: MemoryBuffer(data: currentRekeyNumber), config: SecretChatEncryptionConfig(g: 3, p: MemoryBuffer(data: defaultPrime), version: 0)) - } else if currentRekeyIsInitiatedByLocalClient == 0, let currentRekeyKey = currentRekeyKey, currentRekeyKeyId != 0 { - innerState = .accepted(key: MemoryBuffer(data: currentRekeyKey), keyFingerprint: currentRekeyKeyId) - } else { - innerState = nil - } - if let innerState = innerState { - rekeyState = SecretChatRekeySessionState(id: currentRekeyExchangeId, data: innerState) - } - } - - return SecretChatData(accessHash: accessHash, handshakeState: handshakeState, rekeyState: rekeyState) -} - -let registeredAttachmentParsers: Bool = { - let parsers: [(Int32, TGMediaAttachmentParser)] = [ - (TGActionMediaAttachmentType, TGActionMediaAttachment()), - (TGImageMediaAttachmentType, TGImageMediaAttachment()), - (TGLocationMediaAttachmentType, TGLocationMediaAttachment()), - (TGVideoMediaAttachmentType, TGVideoMediaAttachment()), - (Int32(bitPattern: 0xB90A5663), TGContactMediaAttachment()), - (Int32(bitPattern: 0xE6C64318), TGDocumentMediaAttachment()), - (TGAudioMediaAttachmentType, TGAudioMediaAttachment()), - (Int32(bitPattern: 0x8C2E3CCE), TGMessageEntitiesAttachment()), - (Int32(bitPattern: 0x944DE6B6), TGLocalMessageMetaMediaAttachment()), - (TGAuthorSignatureMediaAttachmentType, TGAuthorSignatureMediaAttachment()), - (TGInvoiceMediaAttachmentType, TGInvoiceMediaAttachment()), - (TGGameAttachmentType, TGGameMediaAttachment()), - (Int32(bitPattern: 0xA3F4C8F5), TGViaUserAttachment()), - (TGBotContextResultAttachmentType, TGBotContextResultAttachment()), - (TGReplyMarkupAttachmentType, TGReplyMarkupAttachment()), - (TGWebPageMediaAttachmentType, TGWebPageMediaAttachment()), - (TGReplyMessageMediaAttachmentType, TGReplyMessageMediaAttachment()), - (TGAudioMediaAttachmentType, TGAudioMediaAttachment()), - (Int32(bitPattern: 0xaa1050c1), TGForwardedMessageMediaAttachment()) - ] - for (id, parser) in parsers { - TGMessage.registerMediaAttachmentParser(id, parser: parser) - } - return true -}() - -private func parseSecretChatData(peerId: PeerId, data: Data, unreadCount: Int32) -> (SecretChatData, [MessageId.Namespace: PeerReadState], Int32)? { - let reader = LegacyBufferReader(LegacyBuffer(data: data)) - guard let magic = reader.readInt32() else { - return nil - } - var version: Int32 = 1 - if magic == 0x7acde441 { - guard let value = reader.readInt32() else { - return nil - } - version = value - } - - if version < 2 { - return nil - } - - for _ in 0 ..< 3 { - guard let length = reader.readInt32() else { - return nil - } - reader.skip(Int(length)) - } - - guard let hasEncryptedData = reader.readBytesAsInt32(1), hasEncryptedData == 1 else { - return nil - } - guard let secretChatData = readSecretChatData(reader: reader) else { - return nil - } - reader.skip(4) - reader.skip(4) - reader.skip(8) - reader.skip(4) - reader.skip(4) - reader.skip(4) - reader.skip(4) - guard let maxReadDate = reader.readInt32() else { - return nil - } - guard let maxOutgoingReadDate = reader.readInt32() else { - return nil - } - guard let messageDate = reader.readInt32() else { - return nil - } - guard let minMessageDate = reader.readInt32() else { - return nil - } - - let readStates: [MessageId.Namespace: PeerReadState] = [ - Namespaces.Message.SecretIncoming: .indexBased(maxIncomingReadIndex: MessageIndex(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: 1), timestamp: maxReadDate), maxOutgoingReadIndex: MessageIndex.lowerBound(peerId: peerId), count: 0, markedUnread: false), - Namespaces.Message.Local: .indexBased(maxIncomingReadIndex: MessageIndex.lowerBound(peerId: peerId), maxOutgoingReadIndex: MessageIndex(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 1), timestamp: maxOutgoingReadDate), count: 0, markedUnread: false) - ] - return (secretChatData, readStates, max(messageDate, minMessageDate)) -} - -private enum CustomPropertyKey { - case string(String) - case hash(Int32) -} - -private func loadLegacyPeerCustomProperyData(database: SqliteInterface, peerId: Int64, key: CustomPropertyKey) -> Data? { - var propertiesData: Data? - database.select("SELECT custom_properties FROM peers_v29 WHERE pid=\(peerId)", { cursor in - propertiesData = cursor.getData(at: 0) - return false - }) - if let propertiesData = propertiesData { - let keyHash: Int32 - switch key { - case let .string(string): - keyHash = HashFunctions.murMurHash32(string) - case let .hash(hash): - keyHash = hash - } - let reader = LegacyBufferReader(LegacyBuffer(data: propertiesData)) - - guard let _ = reader.readInt32() else { - return nil - } - guard let count = reader.readInt32() else { - return nil - } - for _ in 0 ..< Int(count) { - guard let valueKey = reader.readInt32() else { - return nil - } - guard let valueLength = reader.readInt32() else { - return nil - } - if valueKey == keyHash { - return reader.readBuffer(Int(valueLength))?.makeData() - } - reader.skip(Int(valueLength)) - } - } - return nil -} - -private func loadLegacyPeerCustomProperyInt32(database: SqliteInterface, peerId: Int64, key: CustomPropertyKey) -> Int32? { - guard let data = loadLegacyPeerCustomProperyData(database: database, peerId: peerId, key: key), data.count == 4 else { - return nil - } - var result: Int32 = 0 - withUnsafeMutablePointer(to: &result, { bytes -> Void in - data.copyBytes(to: UnsafeMutableRawPointer(bytes).assumingMemoryBound(to: UInt8.self), from: 0 ..< 4) - }) - return result -} - -private func loadLegacyMessages(account: TemporaryAccount, basePath: String, accountPeerId: PeerId, peerId: PeerId, userPeerId: PeerId, database: SqliteInterface, conversationId: Int64, expectedTotalCount: Int32) -> Signal { - return Signal { subscriber in - subscriber.putNext(0.0) - - var copyLocalFiles: [(MediaResource, String)] = [] - var messages: [StoreMessage] = [] - - Logger.shared.log("loadLegacyMessages", "begin peerId \(peerId) conversationId \(conversationId) count \(expectedTotalCount)") - - database.select("CREATE INDEX IF NOT EXISTS random_ids_mid ON random_ids_v29 (mid)", { _ in - return true - }) - - var messageIndex: Int32 = -1 - let reportBase = max(1, expectedTotalCount / 100) - - database.select("SELECT mid, message, media, from_id, dstate, date, flags, localMid, content_properties FROM messages_v29 WHERE cid=\(conversationId)", { cursor in - messageIndex += 1 - - #if DEBUG - //usleep(500000) - #endif - - if messageIndex % reportBase == 0 { - subscriber.putNext(min(1.0, Float(messageIndex) / Float(expectedTotalCount))) - } - - let messageId = cursor.getInt32(at: 0) - - //Logger.shared.log("loadLegacyMessages", "import message \(messageId)") - - var globallyUniqueId: Int64? - database.select("SELECT random_id FROM random_ids_v29 where mid=\(messageId)", { innerCursor in - globallyUniqueId = innerCursor.getInt64(at: 0) - return false - }) - - let text = cursor.getString(at: 1) - let fromId = cursor.getInt64(at: 3) - let deliveryState = cursor.getInt32(at: 4) - let timestamp = cursor.getInt32(at: 5) - let autoremoveTimeout = cursor.getInt32(at: 7) - let contentPropertiesData = cursor.getData(at: 8) - - let parsedAuthorId: PeerId - let parsedId: StoreMessageId - var parsedFlags: StoreMessageFlags = [] - var parsedAttributes: [MessageAttribute] = [] - var parsedMedia: [Media] = [] - var parsedGroupingKey: Int64? - - if fromId == accountPeerId.id { - parsedAuthorId = accountPeerId - parsedId = .Partial(peerId, Namespaces.Message.Local) - } else { - parsedAuthorId = userPeerId - parsedId = .Partial(peerId, Namespaces.Message.SecretIncoming) - parsedFlags.insert(.Incoming) - } - - if deliveryState != 0 { - return true - } - - if !contentPropertiesData.isEmpty { - if let contentProperties = TGMessage.parseContentProperties(contentPropertiesData) { - for (_, value) in contentProperties { - if let value = value as? TGMessageGroupedIdContentProperty { - parsedGroupingKey = value.groupedId - } - } - } - } - - //Logger.shared.log("loadLegacyMessages", "message \(messageId) read content properties") - - let media = cursor.getData(at: 2) - if let mediaList = TGMessage.parseMediaAttachments(media) { - for item in mediaList { - if let item = item as? TGImageMediaAttachment { - let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()) - var representations: [TelegramMediaImageRepresentation] = [] - if let allSizes = item.imageInfo?.allSizes() as? [String: NSValue] { - - for (imageUrl, sizeValue) in allSizes { - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - var resourcePath: String? - if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) { - resource = updatedResource - copyLocalFiles.append((updatedResource, path)) - resourcePath = path - } else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path { - copyLocalFiles.append((resource, path)) - resourcePath = path - } - - var dimensions = sizeValue.cgSizeValue - if let resourcePath = resourcePath, let image = UIImage(contentsOfFile: resourcePath) { - dimensions = image.size - } - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(dimensions), resource: resource, progressiveSizes: [])) - } - } - - if item.localImageId != 0 { - let fullSizePath = basePath + "/Documents/files/image-local-\(String(item.localImageId, radix: 16))/image.jpg" - if let image = UIImage(contentsOfFile: fullSizePath) { - let resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - copyLocalFiles.append((resource, fullSizePath)) - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [])) - } - } - - parsedMedia.append(TelegramMediaImage(imageId: mediaId, representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])) - } else if let item = item as? TGVideoMediaAttachment { - let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()) - var representations: [TelegramMediaImageRepresentation] = [] - if let allSizes = item.thumbnailInfo?.allSizes() as? [String: NSValue] { - for (imageUrl, sizeValue) in allSizes { - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) { - resource = updatedResource - copyLocalFiles.append((updatedResource, path)) - } else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path { - copyLocalFiles.append((resource, path)) - } - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource, progressiveSizes: [])) - } - } - - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - - var attributes: [TelegramMediaFileAttribute] = [] - attributes.append(.Video(duration: Int(item.duration), size: PixelDimensions(item.dimensions), flags: item.roundMessage ? .instantRoundVideo : [])) - - var size: Int32 = 0 - if let videoUrl = item.videoInfo?.url(withQuality: 1, actualQuality: nil, actualSize: &size) { - if let path = pathFromLegacyLocalVideoUrl(basePath: basePath, url: videoUrl) { - copyLocalFiles.append((resource, path)) - } else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: videoUrl, type: .video) { - resource = updatedResource - copyLocalFiles.append((updatedResource, path)) - } else if videoUrl.hasPrefix("file://"), let path = URL(string: videoUrl)?.path { - copyLocalFiles.append((resource, path)) - } - } - parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size == 0 ? nil : Int(size), attributes: attributes)) - } else if let item = item as? TGAudioMediaAttachment { - let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()) - let representations: [TelegramMediaImageRepresentation] = [] - - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - - var attributes: [TelegramMediaFileAttribute] = [] - attributes.append(.Audio(isVoice: true, duration: Int(item.duration), title: nil, performer: nil, waveform: nil)) - - let size: Int32 = item.fileSize - let audioUrl = item.audioUri ?? "" - - if let path = pathFromLegacyLocalVideoUrl(basePath: basePath, url: audioUrl) { - copyLocalFiles.append((resource, path)) - } else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: audioUrl, type: .audio) { - resource = updatedResource - copyLocalFiles.append((updatedResource, path)) - } else if audioUrl.hasPrefix("file://"), let path = URL(string: audioUrl)?.path { - copyLocalFiles.append((resource, path)) - } - parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: size == 0 ? nil : Int(size), attributes: attributes)) - } else if let item = item as? TGDocumentMediaAttachment { - let mediaId = MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()) - var representations: [TelegramMediaImageRepresentation] = [] - if let allSizes = (item.thumbnailInfo?.allSizes()) as? [String: NSValue] { - for (imageUrl, sizeValue) in allSizes { - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: imageUrl, type: .image) { - resource = updatedResource - copyLocalFiles.append((updatedResource, path)) - } else if imageUrl.hasPrefix("file://"), let path = URL(string: imageUrl)?.path { - copyLocalFiles.append((resource, path)) - } else if let updatedResource = resourceFromLegacyImageUrl(imageUrl) { - resource = updatedResource - copyLocalFiles.append((resource, pathFromLegacyImageUrl(basePath: basePath, url: imageUrl))) - } - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(sizeValue.cgSizeValue), resource: resource, progressiveSizes: [])) - } - } - - var resource: TelegramMediaResource = LocalFileMediaResource(fileId: arc4random64()) - - var attributes: [TelegramMediaFileAttribute] = [] - var fileName = "file" - if let itemAttributes = item.attributes { - for attribute in itemAttributes { - if let attribute = attribute as? TGDocumentAttributeFilename { - attributes.append(.FileName(fileName: attribute.filename ?? "file")) - fileName = attribute.filename ?? "file" - } else if let attribute = attribute as? TGDocumentAttributeAudio { - let title = attribute.title ?? "" - let performer = attribute.performer ?? "" - var waveform: MemoryBuffer? - if let data = attribute.waveform { - waveform = MemoryBuffer(data: data.bitstream()!) - } - attributes.append(.Audio(isVoice: attribute.isVoice, duration: Int(attribute.duration), title: title.isEmpty ? nil : title, performer: performer.isEmpty ? nil : performer, waveform: waveform)) - } else if let _ = attribute as? TGDocumentAttributeAnimated { - attributes.append(.Animated) - } else if let attribute = attribute as? TGDocumentAttributeVideo { - attributes.append(.Video(duration: Int(attribute.duration), size: PixelDimensions(attribute.size), flags: attribute.isRoundMessage ? .instantRoundVideo : [])) - } else if let attribute = attribute as? TGDocumentAttributeSticker { - var packReference: StickerPackReference? - if let reference = attribute.packReference as? TGStickerPackIdReference { - packReference = .id(id: reference.packId, accessHash: reference.packAccessHash) - } else if let reference = attribute.packReference as? TGStickerPackShortnameReference { - packReference = .name(reference.shortName ?? "") - } - attributes.append(.Sticker(displayText: attribute.alt ?? "", packReference: packReference, maskData: nil)) - } else if let attribute = attribute as? TGDocumentAttributeImageSize { - attributes.append(.ImageSize(size: PixelDimensions(attribute.size))) - } - } - } - - let documentUri = item.documentUri ?? "" - - let size: Int32 = item.size - if documentUri.hasPrefix("file://"), let path = URL(string: documentUri)?.path { - copyLocalFiles.append((resource, path)) - } else if let (path, updatedResource) = pathAndResourceFromEncryptedFileUrl(basePath: basePath, url: documentUri, type: .document(fileName: fileName)) { - resource = updatedResource - copyLocalFiles.append((resource, path)) - } else if item.localDocumentId != 0 { - copyLocalFiles.append((resource, pathFromLegacyFile(basePath: basePath, fileId: item.localDocumentId, isLocal: true, fileName: TGDocumentMediaAttachment.safeFileName(forFileName: fileName) ?? ""))) - } else if item.documentId != 0 { - copyLocalFiles.append((resource, pathFromLegacyFile(basePath: basePath, fileId: item.documentId, isLocal: false, fileName: TGDocumentMediaAttachment.safeFileName(forFileName: fileName) ?? ""))) - } - parsedMedia.append(TelegramMediaFile(fileId: mediaId, partialReference: nil, resource: resource, previewRepresentations: representations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: item.mimeType ?? "application/octet-stream", size: size == 0 ? nil : Int(size), attributes: attributes)) - } else if let item = item as? TGActionMediaAttachment { - if item.actionType == TGMessageActionEncryptedChatMessageLifetime, let actionData = item.actionData, let timeout = actionData["messageLifetime"] as? Int32 { - - parsedMedia.append(TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(timeout))) - } - } else if let item = item as? TGContactMediaAttachment { - parsedMedia.append(TelegramMediaContact(firstName: item.firstName ?? "", lastName: item.lastName ?? "", phoneNumber: item.phoneNumber ?? "", peerId: nil, vCardData: nil)) - } else if let item = item as? TGLocationMediaAttachment { - var venue: MapVenue? - if let v = item.venue { - venue = MapVenue(title: v.title ?? "", address: v.address ?? "", provider: v.provider == "" ? nil : v.provider, id: v.venueId == "" ? nil : v.venueId, type: v.type == "" ? nil : v.type) - } - parsedMedia.append(TelegramMediaMap(latitude: item.latitude, longitude: item.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)) - } - } - } - - //Logger.shared.log("loadLegacyMessages", "message \(messageId) read media") - - if autoremoveTimeout != 0 { - var countdownBeginTime: Int32? - database.select("SELECT date FROM selfdestruct_v29 where mid=\(messageId)", { innerCursor in - countdownBeginTime = innerCursor.getInt32(at: 0) - autoremoveTimeout - return false - }) - parsedAttributes.append(AutoremoveTimeoutMessageAttribute(timeout: autoremoveTimeout, countdownBeginTime: countdownBeginTime)) - } - - let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil, isPinned: false) - messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, threadId: nil, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia)) - - //Logger.shared.log("loadLegacyMessages", "message \(messageId) completed") - - return true - }) - - let disposable = (account.postbox.transaction { transaction -> Void in - //Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) storing messages") - let _ = transaction.addMessages(messages, location: .UpperHistoryBlock) - - //Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) copying \(copyLocalFiles.count) files") - - for (resource, path) in copyLocalFiles { - account.postbox.mediaBox.copyResourceData(resource.id, fromTempPath: path) - } - - Logger.shared.log("loadLegacyMessages", "conversation \(conversationId) done") - }).start(completed: { - subscriber.putCompletion() - }) - - return disposable - } -} - -private func importChannelBroadcastPreferences(account: TemporaryAccount, basePath: String, database: SqliteInterface) -> Signal { - return deferred { () -> Signal in - var peerIds: [Int64] = [] - database.select("SELECT cid FROM channel_conversations_v29", { cursor in - peerIds.append(cursor.getInt64(at: 0)) - return true - }) - var peerIdsWithMutedMessages: [Int64] = [] - for peerId in peerIds { - if let data = loadLegacyPeerCustomProperyData(database: database, peerId: peerId, key: .hash(0x374BF349)), !data.isEmpty { - let reader = LegacyBufferReader(LegacyBuffer(data: data)) - guard let version = reader.readBytesAsInt32(1) else { - continue - } - guard let _ = reader.readBytesAsInt32(1) else { - continue - } - if version >= 2 { - guard let messagesMuted = reader.readBytesAsInt32(1) else { - continue - } - if messagesMuted == 1 { - peerIdsWithMutedMessages.append(peerId) - } - } - } - } - - return .complete() - } -} - -func loadLegacySecretChats(account: TemporaryAccount, basePath: String, accountPeerId: PeerId, database: SqliteInterface) -> Signal { - return deferred { () -> Signal in - var peerIdToConversationId: [PeerId: Int64] = [:] - database.select("SELECT encrypted_id, cid FROM encrypted_cids_v29", { cursor in - peerIdToConversationId[PeerId(namespace: Namespaces.Peer.SecretChat, id: cursor.getInt32(at: 0))] = cursor.getInt64(at: 1) - return true - }) - var chatInfos: [(TelegramSecretChat, SecretChatState, Int32?, [MessageId.Namespace: PeerReadState], Int64)] = [] - for (peerId, conversationId) in peerIdToConversationId { - database.select("SELECT chat_photo, unread_count, participants, date FROM convesations_v29 WHERE cid=\(conversationId)", { cursor in - guard let (secretChatData, readStates, minMessageDate) = parseSecretChatData(peerId: peerId, data: cursor.getData(at: 0), unreadCount: cursor.getInt32(at: 1)) else { - return false - } - guard let (role, userPeerId) = readSecretChatParticipantData(accountPeerId: accountPeerId, data: cursor.getData(at: 2)) else { - return false - } - let chatMessageDate = cursor.getInt32(at: 3) - let messageDate = min(minMessageDate, chatMessageDate) - - let messageLifetime = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .string("messageLifetime")) ?? 0 - - let state: SecretChatState - var seqOut: Int32? - - switch secretChatData.handshakeState { - case 1: //requested - guard let a = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("a")), !a.isEmpty else { - return false - } - state = SecretChatState(role: .creator, embeddedState: .handshake(.requested(g: 3, p: MemoryBuffer(data: defaultPrime), a: MemoryBuffer(data: a))), keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil) - case 2: //accepting - return false - case 3: //terminated - state = SecretChatState(role: .creator, embeddedState: .terminated, keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil) - case 4: - guard let sha1Fingerprint = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeySha1")) else { - return false - } - guard let sha256Fingerprint = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeySha256")) else { - return false - } - - guard let keysData = loadLegacyPeerCustomProperyData(database: database, peerId: conversationId, key: .string("encryptionKeys")) else { - return false - } - guard let keysArray = NSKeyedUnarchiver.unarchiveObject(with: keysData) as? [TGEncryptionKeyData] else { - return false - } - let parsedKeys: [SecretChatKey] = keysArray.map({ key in - return SecretChatKey(fingerprint: key.keyId, key: MemoryBuffer(data: key.key), validity: .sequenceBasedIndexRange(fromCanonicalIndex: key.firstSeqOut), useCount: 1) - }) - let requestedLayerValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(reportedLayer_hash)) ?? 0 - let appliedSeqInValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(seq_in_hash)) ?? 0 - guard let seqOutValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(seq_out_hash)) else { - return false - } - seqOut = seqOutValue - guard let activeLayerValue = loadLegacyPeerCustomProperyInt32(database: database, peerId: conversationId, key: .hash(layer_hash)) else { - return false - } - guard let activeLayer = SecretChatSequenceBasedLayer(rawValue: activeLayerValue) else { - return false - } - let rekeyState: SecretChatRekeySessionState? = secretChatData.rekeyState - let embeddedState: SecretChatEmbeddedState = .sequenceBasedLayer(SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: activeLayer, locallyRequestedLayer: requestedLayerValue == 0 ? nil : requestedLayerValue, remotelyRequestedLayer: nil), rekeyState: rekeyState, baseIncomingOperationIndex: 0, baseOutgoingOperationIndex: 0, topProcessedCanonicalIncomingOperationIndex: appliedSeqInValue == 0 ? nil : max(0, appliedSeqInValue - 1))) - state = SecretChatState(role: role, embeddedState: embeddedState, keychain: SecretChatKeychain(keys: parsedKeys), keyFingerprint: SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Fingerprint), sha256: SecretChatKeySha256Fingerprint(digest: sha256Fingerprint)), messageAutoremoveTimeout: messageLifetime == 0 ? nil : messageLifetime) - default: - return false - } - - let secretChat = TelegramSecretChat(id: peerId, creationDate: messageDate, regularPeerId: userPeerId, accessHash: secretChatData.accessHash, role: role, embeddedState: state.embeddedState.peerState, messageAutoremoveTimeout: messageLifetime == 0 ? nil : messageLifetime) - - chatInfos.append((secretChat, state, seqOut, readStates, conversationId)) - - return false - }) - } - var userPeers: [PeerId: Peer] = [:] - var presences: [PeerId: PeerPresence] = [:] - for info in chatInfos { - if let (peer, presence) = loadLegacyUser(database: database, id: info.0.regularPeerId.id) { - userPeers[peer.id] = peer - presences[peer.id] = presence - } - } - - let storedChats = account.postbox.transaction { transaction -> Void in - updatePeers(transaction: transaction, peers: Array(userPeers.values), update: { _, updated in - return updated - }) - transaction.updatePeerPresencesInternal(presences: presences, merge: { _, updated in return updated }) - for (peer, state, seqOutValue, readStates, _) in chatInfos { - if userPeers[peer.regularPeerId] == nil { - continue - } - updatePeers(transaction: transaction, peers: [peer], update: { _, updated in - return updated - }) - transaction.setPeerChatState(peer.id, state: state) - switch state.embeddedState { - case .sequenceBasedLayer: - if let seqOutValue = seqOutValue { - transaction.operationLogResetIndices(peerId: peer.id, tag: OperationLogTags.SecretOutgoing, nextTagLocalIndex: seqOutValue + 1) - } - default: - break - } - transaction.resetIncomingReadStates([peer.id: readStates]) - } - } - |> ignoreValues - - let _ = registeredAttachmentParsers - - var countByConversationId: [Int64: Int32] = [:] - var totalCount: Int32 = 0 - - for info in chatInfos { - database.select("SELECT COUNT(*) FROM messages_v29 WHERE cid=\(info.4)", { cursor in - let count = cursor.getInt32(at: 0) - countByConversationId[info.4] = count - totalCount += count - return true - }) - } - - var storedMessagesSignals: Signal = .single(0.0) - var cumulativeCount: Int32 = 0 - for info in chatInfos { - let localBaseline = cumulativeCount - let localCount = countByConversationId[info.4] ?? 0 - storedMessagesSignals = storedMessagesSignals - |> then( - loadLegacyMessages(account: account, basePath: basePath, accountPeerId: accountPeerId, peerId: info.0.id, userPeerId: info.0.regularPeerId, database: database, conversationId: info.4, expectedTotalCount: localCount) - |> map { localProgress -> Float in - if totalCount <= 0 { - return 0.0 - } - let globalCount = localBaseline + Int32(localProgress * Float(localCount)) - return Float(globalCount) / Float(totalCount) - } - ) - cumulativeCount += countByConversationId[info.4] ?? 0 - } - - return storedChats - |> map { _ -> Float in return 0.0 } - |> then(storedMessagesSignals) - } -} diff --git a/submodules/LegacyDataImport/Sources/LegacyDataImport.swift b/submodules/LegacyDataImport/Sources/LegacyDataImport.swift deleted file mode 100644 index 4676e4b16a..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyDataImport.swift +++ /dev/null @@ -1,245 +0,0 @@ -import Foundation -import UIKit -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import MtProtoKit -import LegacyDataImportImpl - -public enum AccountImportError: Error { - case generic -} - -public enum AccountImportProgressType { - case generic - case messages - case media -} - -private func importedAccountData(basePath: String, documentsPath: String, accountManager: AccountManager, account: TemporaryAccount, database: SqliteInterface) -> Signal<(AccountImportProgressType, Float), AccountImportError> { - return deferred { () -> Signal<(AccountImportProgressType, Float), AccountImportError> in - let keychain = MTFileBasedKeychain(name: "Telegram", documentsPath: documentsPath) - guard let masterDatacenterId = keychain.object(forKey: "defaultDatacenterId", group: "persistent") as? Int else { - return .fail(.generic) - } - let keychainContents = keychain.contents(forGroup: "persistent") - - let importKeychain = account.postbox.transaction { transaction -> Void in - for (key, value) in keychainContents { - let data = NSKeyedArchiver.archivedData(withRootObject: value) - transaction.setKeychainEntry(data, forKey: "persistent" + ":" + key) - } - } - |> ignoreValues - |> castError(AccountImportError.self) - - let importData = importPreferencesData(documentsPath: documentsPath, masterDatacenterId: Int32(masterDatacenterId), account: account, database: database) - |> mapToSignal { accountUserId -> Signal<(AccountImportProgressType, Float), AccountImportError> in - return importDatabaseData(accountManager: accountManager, account: account, basePath: basePath, database: database, accountUserId: accountUserId) - } - - return importKeychain - |> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) } - |> then(importData) - } -} - -private func importPreferencesData(documentsPath: String, masterDatacenterId: Int32, account: TemporaryAccount, database: SqliteInterface) -> Signal { - return deferred { () -> Signal in - let defaultsPath = documentsPath + "/standard.defaults" - var parsedAccountUserId: Int32? - if let data = try? Data(contentsOf: URL(fileURLWithPath: defaultsPath)), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any], let id = dict["telegraphUserId"] as? Int { - parsedAccountUserId = Int32(id) - } - if parsedAccountUserId == nil { - if let id = UserDefaults.standard.object(forKey: "telegraphUserId") as? Int { - parsedAccountUserId = Int32(id) - } - } - - if let parsedAccountUserId = parsedAccountUserId { - return account.postbox.transaction { transaction -> Int32 in - transaction.setState(AuthorizedAccountState(isTestingEnvironment: false, masterDatacenterId: masterDatacenterId, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: parsedAccountUserId), state: nil)) - return parsedAccountUserId - } - |> castError(AccountImportError.self) - } else { - return .fail(.generic) - } - } -} - -private func importDatabaseData(accountManager: AccountManager, account: TemporaryAccount, basePath: String, database: SqliteInterface, accountUserId: Int32) -> Signal<(AccountImportProgressType, Float), AccountImportError> { - return deferred { () -> Signal<(AccountImportProgressType, Float), AccountImportError> in - var importedAccountUser: Signal = .complete() - if let (user, presence) = loadLegacyUser(database: database, id: accountUserId) { - importedAccountUser = account.postbox.transaction { transaction -> Void in - updatePeers(transaction: transaction, peers: [user], update: { _, updated in updated }) - transaction.updatePeerPresencesInternal(presences: [user.id: presence], merge: { _, updated in return updated }) - } - |> ignoreValues - |> castError(AccountImportError.self) - } - - let importedSecretChats = loadLegacySecretChats(account: account, basePath: basePath, accountPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: accountUserId), database: database) - |> castError(AccountImportError.self) - - /*let importedFiles = loadLegacyFiles(account: account, basePath: basePath, accountPeerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: accountUserId), database: database) - |> castError(AccountImportError.self)*/ - - let importedLegacyPreferences = importLegacyPreferences(accountManager: accountManager, account: account, documentsPath: basePath + "/Documents", database: database) - |> castError(AccountImportError.self) - - return importedAccountUser - |> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) } - |> then( - importedLegacyPreferences - |> map { _ -> (AccountImportProgressType, Float) in return (.generic, 0.0) } - ) - |> then( - importedSecretChats - |> map { value -> (AccountImportProgressType, Float) in return (.messages, value) } - ) - } -} - -public enum ImportedLegacyAccountEvent { - case progress(AccountImportProgressType, Float) - case result(AccountRecordId?) -} - -public func importedLegacyAccount(basePath: String, accountManager: AccountManager, encryptionParameters: ValueBoxEncryptionParameters, present: @escaping (UIViewController) -> Void) -> Signal { - let queue = Queue() - return deferred { () -> Signal in - let documentsPath = basePath + "/Documents" - if FileManager.default.fileExists(atPath: documentsPath + "/importcompleted") { - return .single(.result(nil)) - } - - let unlockedDatabasePathAndKey: Signal<(String, Data?)?, AccountImportError> - if FileManager.default.fileExists(atPath: documentsPath + "/tgdata.db.y") { - let databasePath = documentsPath + "/tgdata.db.y" - let unlockDatabase = Signal<(String, Data?)?, AccountImportError> { subscriber in - let alertController = UIAlertController(title: nil, message: "Enter your passcode", preferredStyle: .alert) - - let confirmAction = UIAlertAction(title: "Enter", style: .default) { _ in - let passcode = alertController.textFields?[0].text - - func checkPasscode(_ value: String) -> Bool { - guard let database = SqliteInterface(databasePath: databasePath) else { - return false - } - let key = value.data(using: .utf8)! - if !database.unlock(password: hexString(key).data(using: .utf8)!) { - return false - } - - return true - } - - if checkPasscode(passcode ?? "") { - subscriber.putNext((databasePath, (passcode ?? "").data(using: .utf8)!)) - subscriber.putCompletion() - } else { - let alertController = UIAlertController(title: nil, message: "Invalid passcode. Please try again.", preferredStyle: .alert) - - let confirmAction = UIAlertAction(title: "OK", style: .default) { _ in - subscriber.putCompletion() - } - - alertController.addAction(confirmAction) - - present(alertController) - } - } - - let cancelAction = UIAlertAction(title: "Skip", style: .cancel) { _ in - subscriber.putNext(nil) - subscriber.putCompletion() - } - - alertController.addTextField { textField in - textField.placeholder = "Passcode" - } - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - present(alertController) - return EmptyDisposable - } - |> runOn(Queue.mainQueue()) - - unlockedDatabasePathAndKey = (unlockDatabase - |> mapToSignal { result -> Signal<(String, Data?)?, AccountImportError> in - if let result = result { - return .single(result) - } else { - let askAgain = Signal<(String, Data?)?, AccountImportError> { subscriber in - let alertController = UIAlertController(title: "Warning", message: "If you continue without entering your passcode, all your secret chats will be lost.", preferredStyle: .alert) - - let confirmAction = UIAlertAction(title: "Skip", style: .destructive) { _ in - subscriber.putError(.generic) - } - - let cancelAction = UIAlertAction(title: "Try Again", style: .cancel) { _ in - subscriber.putCompletion() - } - - alertController.addAction(confirmAction) - alertController.addAction(cancelAction) - - present(alertController) - return EmptyDisposable - } - |> runOn(Queue.mainQueue()) - return askAgain - } - }) - |> restart - |> take(1) - } else if FileManager.default.fileExists(atPath: documentsPath + "/tgdata.db") { - unlockedDatabasePathAndKey = .single((documentsPath + "/tgdata.db", nil)) - } else { - return .single(.result(nil)) - } - - return unlockedDatabasePathAndKey - |> mapToSignal { pathAndKey -> Signal in - guard let pathAndKey = pathAndKey else { - return .fail(.generic) - } - - guard let database = SqliteInterface(databasePath: pathAndKey.0) else { - return .fail(.generic) - } - - if let key = pathAndKey.1 { - if !database.unlock(password: hexString(key).data(using: .utf8)!) { - return .fail(.generic) - } - } - - return temporaryAccount(manager: accountManager, rootPath: rootPathForBasePath(basePath), encryptionParameters: encryptionParameters) - |> castError(AccountImportError.self) - |> mapToSignal { account -> Signal in - let actions = importedAccountData(basePath: basePath, documentsPath: documentsPath, accountManager: accountManager, account: account, database: database) - var result = actions - |> map { typeAndProgress -> ImportedLegacyAccountEvent in - return .progress(typeAndProgress.0, typeAndProgress.1) - } - #if DEBUG - //result = result - //|> then(.never()) - #endif - - result = result - |> then(.single(.result(account.id))) - - return result - } - } - } - |> runOn(queue) -} diff --git a/submodules/LegacyDataImport/Sources/LegacyDataImportSplash.swift b/submodules/LegacyDataImport/Sources/LegacyDataImportSplash.swift deleted file mode 100644 index f121307d0e..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyDataImportSplash.swift +++ /dev/null @@ -1,89 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import TelegramPresentationData -import RadialStatusNode - -public protocol LegacyDataImportSplash: WindowCoveringView { - var progress: (AccountImportProgressType, Float) { get set } - var serviceAction: (() -> Void)? { get set } -} - -private final class LegacyDataImportSplashImpl: WindowCoveringView, LegacyDataImportSplash { - private let theme: PresentationTheme? - private let strings: PresentationStrings? - - public var progress: (AccountImportProgressType, Float) = (.generic, 0.0) { - didSet { - if self.progress.0 != oldValue.0 { - if let size = self.validSize { - switch self.progress.0 { - case .generic: - self.textNode.attributedText = NSAttributedString(string: self.strings?.AppUpgrade_Running ?? "Optimizing...", font: Font.regular(17.0), textColor: self.theme?.list.itemPrimaryTextColor ?? .black) - case .media: - self.textNode.attributedText = NSAttributedString(string: "Optimizing cache", font: Font.regular(17.0), textColor: self.theme?.list.itemPrimaryTextColor ?? .black) - case .messages: - self.textNode.attributedText = NSAttributedString(string: "Optimizing database", font: Font.regular(17.0), textColor: self.theme?.list.itemPrimaryTextColor ?? .black) - } - self.updateLayout(size) - } - } - self.progressNode.transitionToState(.progress(color: self.theme?.list.itemAccentColor ?? UIColor(rgb: 0x007ee5), lineWidth: 2.0, value: CGFloat(max(0.025, self.progress.1)), cancelEnabled: false, animateRotation: true), animated: false, completion: {}) - } - } - - public var serviceAction: (() -> Void)? - - private let progressNode: RadialStatusNode - private let textNode: ImmediateTextNode - - private var validSize: CGSize? - - public init(theme: PresentationTheme?, strings: PresentationStrings?) { - self.theme = theme - self.strings = strings - - self.progressNode = RadialStatusNode(backgroundNodeColor: theme?.list.plainBackgroundColor ?? .white) - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 0 - self.textNode.textAlignment = .center - self.textNode.attributedText = NSAttributedString(string: self.strings?.AppUpgrade_Running ?? "Optimizing...", font: Font.regular(17.0), textColor: self.theme?.list.itemPrimaryTextColor ?? .black) - - super.init(frame: CGRect()) - - self.backgroundColor = self.theme?.list.plainBackgroundColor ?? .white - - self.addSubnode(self.progressNode) - self.progressNode.isUserInteractionEnabled = false - self.addSubnode(self.textNode) - self.textNode.isUserInteractionEnabled = false - - self.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))) - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func updateLayout(_ size: CGSize) { - self.validSize = size - - let progressSize = CGSize(width: 60.0, height: 60.0) - - let textSize = self.textNode.updateLayout(CGSize(width: size.width - 20.0, height: .greatestFiniteMagnitude)) - - let progressFrame = CGRect(origin: CGPoint(x: floor((size.width - progressSize.width) / 2.0), y: floor((size.height - progressSize.height - 15.0 - textSize.height) / 2.0)), size: progressSize) - self.progressNode.frame = progressFrame - self.textNode.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: progressFrame.maxY + 15.0), size: textSize) - } - - @objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) { - if case .began = recognizer.state { - self.serviceAction?() - } - } -} - -public func makeLegacyDataImportSplash(theme: PresentationTheme?, strings: PresentationStrings?) -> LegacyDataImportSplash { - return LegacyDataImportSplashImpl(theme: theme, strings: strings) -} diff --git a/submodules/LegacyDataImport/Sources/LegacyFileImport.swift b/submodules/LegacyDataImport/Sources/LegacyFileImport.swift deleted file mode 100644 index 56308641f2..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyFileImport.swift +++ /dev/null @@ -1,189 +0,0 @@ -import Foundation -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import LegacyComponents - -private func importMediaFromMessageData(_ data: Data, basePath: String, copyLocalFiles: inout [(MediaResource, String)], cache: TGCache) { - if let message = TGMessage(keyValueCoder: PSKeyValueDecoder(data: data)) { - if let mediaAttachments = message.mediaAttachments { - importMediaFromMediaList(mediaAttachments, basePath: basePath, copyLocalFiles: ©LocalFiles, cache: cache) - } - } -} - -private func importMediaFromMediaData(_ data: Data, basePath: String, copyLocalFiles: inout [(MediaResource, String)], cache: TGCache) { - if let mediaAttachments = TGMessage.parseMediaAttachments(data) { - importMediaFromMediaList(mediaAttachments, basePath: basePath, copyLocalFiles: ©LocalFiles, cache: cache) - } -} - -private func importMediaFromMediaList(_ mediaAttachments: [Any], basePath: String, copyLocalFiles: inout [(MediaResource, String)], cache: TGCache) { - for media in mediaAttachments { - if let media = media as? TGDocumentMediaAttachment { - var fileName = "file" - if let itemAttributes = media.attributes { - for attribute in itemAttributes { - if let attribute = attribute as? TGDocumentAttributeFilename { - fileName = attribute.filename ?? "file" - } - } - } - - if media.documentId != 0 { - let filePath = pathFromLegacyFile(basePath: basePath, fileId: media.documentId, isLocal: false, fileName: TGDocumentMediaAttachment.safeFileName(forFileName: fileName) ?? "") - if FileManager.default.fileExists(atPath: filePath) { - copyLocalFiles.append((CloudDocumentMediaResource(datacenterId: Int(media.datacenterId), fileId: media.documentId, accessHash: media.accessHash, size: nil, fileReference: nil, fileName: nil), filePath)) - } - } - } else if let media = media as? TGVideoMediaAttachment { - if media.videoId != 0, let videoUrl = media.videoInfo?.url(withQuality: 1, actualQuality: nil, actualSize: nil) { - if let (id, accessHash, datacenterId, path) = pathFromLegacyVideoUrl(basePath: basePath, url: videoUrl) { - copyLocalFiles.append((CloudDocumentMediaResource(datacenterId: Int(datacenterId), fileId: id, accessHash: accessHash, size: nil, fileReference: nil, fileName: nil), path)) - } - } - } else if let media = media as? TGImageMediaAttachment { - if let allSizes = media.imageInfo?.allSizes() as? [String: NSValue] { - for (imageUrl, _) in allSizes { - if let path = cache.path(forCachedData: imageUrl), let resource = resourceFromLegacyImageUrl(imageUrl), FileManager.default.fileExists(atPath: path) { - copyLocalFiles.append((resource, path)) - } - } - } - } - } -} - -private func makeMessageSortKey(tag: Int32, conversationId: Int64, space: Int8, timestamp: Int32, messageId: Int32) -> Data { - let key = ValueBoxKey(length: 4 + 8 + 1 + 4 + 4) - key.setInt32(0, value: tag.byteSwapped) - key.setInt64(4, value: conversationId.byteSwapped) - key.setInt8(4 + 8, value: space) - key.setInt32(4 + 8 + 1, value: timestamp) - key.setInt32(4 + 8 + 1 + 4, value: messageId.byteSwapped) - return Data(bytes: key.memory, count: key.length) -} - -func loadLegacyFiles(account: TemporaryAccount, basePath: String, accountPeerId: PeerId, database: SqliteInterface) -> Signal { - return Signal { subscriber in - let _ = registeredAttachmentParsers - - subscriber.putNext(0.0) - - var channelIds: [Int64] = [] - database.select("SELECT DISTINCT cid FROM channel_message_tags_v29", { cursor in - channelIds.append(cursor.getInt64(at: 0)) - return true - }) - print(database.explain("SELECT DISTINCT cid FROM channel_message_tags_v29")) - - var channelMessageIds: [(Int64, Int32)] = [] - - print(database.explain("SELECT mid FROM channel_message_tags_v29 WHERE tag_sort_key<100 AND tag_sort_key>0 ORDER BY tag_sort_key DESC LIMIT 4000")) - - if !channelIds.isEmpty { - /* - TGSharedMediaCacheItemTypePhoto = 0, - TGSharedMediaCacheItemTypeVideo = 1, - TGSharedMediaCacheItemTypeFile = 2, - TGSharedMediaCacheItemTypePhotoVideo = 3, - TGSharedMediaCacheItemTypePhotoVideoFile = 4, - TGSharedMediaCacheItemTypeAudio = 5, - TGSharedMediaCacheItemTypeLink = 6, - TGSharedMediaCacheItemTypeSticker = 7, - TGSharedMediaCacheItemTypeGif = 8, - TGSharedMediaCacheItemTypeVoiceVideoMessage = 9 - */ - let tags: [Int32] = [ - 2, // File - 5, // Audio - 3, // PhotoVideo - ] - database.withStatement("SELECT mid FROM channel_message_tags_v29 WHERE tag_sort_key? ORDER BY tag_sort_key DESC LIMIT 4000", { select in - for channelId in channelIds { - for tag in tags { - select([.data(makeMessageSortKey(tag: tag, conversationId: channelId, space: 0, timestamp: Int32.max - 1, messageId: 0)), .data(makeMessageSortKey(tag: tag, conversationId: channelId, space: 0, timestamp: 0, messageId: 0))], { cursor in - channelMessageIds.append((channelId, cursor.getInt32(at: 0))) - return true - }) - select([.data(makeMessageSortKey(tag: tag, conversationId: channelId, space: 1, timestamp: Int32.max - 1, messageId: 0)), .data(makeMessageSortKey(tag: tag, conversationId: channelId, space: 1, timestamp: 0, messageId: 0))], { cursor in - channelMessageIds.append((channelId, cursor.getInt32(at: 0))) - return true - }) - } - } - }) - } - - var chatMessageIds: [Int32] = [] - let mediaTypes: [Int32] = [ - 1, // video - 2, // image - 3, // file - ] - for type in mediaTypes { - database.select("SELECT mids FROM media_cache_v29 WHERE media_type=\(type) ORDER BY date DESC LIMIT 32000", { cursor in - let midsData = cursor.getData(at: 0) - let reader = LegacyBufferReader(LegacyBuffer(data: midsData)) - while true { - if let mid = reader.readInt32() { - chatMessageIds.append(mid) - } else { - break - } - } - return true - }) - } - - var copyLocalFiles: [(MediaResource, String)] = [] - - let totalCount = channelMessageIds.count + chatMessageIds.count - let reportBase = max(1, totalCount / 100) - - var itemIndex = -1 - - let cache = TGCache(cachesPath: basePath + "/Caches")! - - if !channelMessageIds.isEmpty { - database.withStatement("SELECT data FROM channel_messages_v29 WHERE cid=? AND mid=?", { select in - for (peerId, messageId) in channelMessageIds { - itemIndex += 1 - if itemIndex % reportBase == 0 { - subscriber.putNext(Float(itemIndex) / Float(totalCount)) - } - select([.int64(peerId), .int32(messageId)], { cursor in - let data = cursor.getData(at: 0) - importMediaFromMessageData(data, basePath: basePath, copyLocalFiles: ©LocalFiles, cache: cache) - return true - }) - } - }) - } - - if !chatMessageIds.isEmpty { - database.withStatement("SELECT media FROM messages_v29 WHERE mid=?", { select in - for messageId in chatMessageIds { - itemIndex += 1 - if itemIndex % reportBase == 0 { - subscriber.putNext(Float(itemIndex) / Float(totalCount)) - } - select([.int32(messageId)], { cursor in - let data = cursor.getData(at: 0) - importMediaFromMediaData(data, basePath: basePath, copyLocalFiles: ©LocalFiles, cache: cache) - return true - }) - } - }) - } - - for (resource, path) in copyLocalFiles { - account.postbox.mediaBox.copyResourceData(resource.id, fromTempPath: path) - } - - subscriber.putCompletion() - - return EmptyDisposable - } -} diff --git a/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift b/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift deleted file mode 100644 index d403653fb5..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyPreferencesImport.swift +++ /dev/null @@ -1,439 +0,0 @@ -import Foundation -import UIKit -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import MtProtoKit -import TelegramUIPreferences -import LegacyComponents -import TelegramNotices -import LegacyDataImportImpl - -@objc(TGPresentationState) private final class TGPresentationState: NSObject, NSCoding { - let pallete: Int32 - let userInfo: Int32 - let fontSize: Int32 - - init?(coder aDecoder: NSCoder) { - self.pallete = aDecoder.decodeInt32(forKey: "p") - self.userInfo = aDecoder.decodeInt32(forKey: "u") - self.fontSize = aDecoder.decodeInt32(forKey: "f") - } - - func encode(with aCoder: NSCoder) { - assertionFailure() - } -} - -private enum PreferencesProvider { - case dict([String: Any]) - case standard(UserDefaults) - - subscript(_ key: String) -> Any? { - get { - switch self { - case let .dict(dict): - return dict[key] - case let .standard(standard): - return standard.object(forKey: key) - } - } - } -} - -private func loadLegacyCustomProperyData(database: SqliteInterface, key: String) -> Data? { - var result: Data? - database.select("SELECT value FROM service_v29 WHERE key=\(HashFunctions.murMurHash32(key))", { cursor in - result = cursor.getData(at: 0) - return false - }) - return result -} - -private func convertLegacyProxyPort(_ value: Int) -> Int32 { - if value < 0 { - return Int32(UInt16(bitPattern: Int16(clamping: value))) - } else { - return Int32(clamping: value) - } -} - -func importLegacyPreferences(accountManager: AccountManager, account: TemporaryAccount, documentsPath: String, database: SqliteInterface) -> Signal { - return deferred { () -> Signal in - var presentationState: TGPresentationState? - if let value = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/presentation.dat") as? TGPresentationState { - presentationState = value - } - - var autoNightPreferences: TGPresentationAutoNightPreferences? - if let value = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/autonight.dat") as? TGPresentationAutoNightPreferences { - autoNightPreferences = value - } - - let autoDownloadPreferences: TGAutoDownloadPreferences? = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/autoDownload.pref") as? TGAutoDownloadPreferences - - let preferencesProvider: PreferencesProvider - let defaultsPath = documentsPath + "/standard.defaults" - - let standardPreferences = PreferencesProvider.standard(UserDefaults.standard) - if let data = try? Data(contentsOf: URL(fileURLWithPath: defaultsPath)), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any] { - preferencesProvider = .dict(dict) - } else { - preferencesProvider = standardPreferences - } - - var showCallsTab: Bool? - if let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/enablecalls.tab")), !data.isEmpty { - showCallsTab = data.withUnsafeBytes { (bytes: UnsafePointer) -> Bool in - return bytes.pointee != 0 - } - } - - let parsedAutoplayGifs: Bool? = preferencesProvider["autoPlayAnimations"] as? Bool - let soundEnabled: Bool? = preferencesProvider["soundEnabled"] as? Bool - let vibrationEnabled: Bool? = preferencesProvider["vibrationEnabled"] as? Bool - let bannerEnabled: Bool? = preferencesProvider["bannerEnabled"] as? Bool - let callsDataUsageMode: Int? = preferencesProvider["callsDataUsageMode"] as? Int - let callsDisableCallKit: Bool? = preferencesProvider["callsDisableCallKit"] as? Bool - let callsUseProxy: Bool? = preferencesProvider["callsUseProxy"] as? Bool - let contactsInhibitSync: Bool? = preferencesProvider["contactsInhibitSync"] as? Bool - let stickersSuggestMode: Int? = preferencesProvider["stickersSuggestMode"] as? Int - - let allowSecretWebpages: Bool? = preferencesProvider["allowSecretWebpages"] as? Bool - let allowSecretWebpagesInitialized: Bool? = preferencesProvider["allowSecretWebpagesInitialized"] as? Bool - let secretInlineBotsInitialized: Bool? = preferencesProvider["secretInlineBotsInitialized"] as? Bool - - let musicPlayerOrderType: Int? = standardPreferences["musicPlayerOrderType_v1"] as? Int - let musicPlayerRepeatType: Int? = standardPreferences["musicPlayerRepeatType_v1"] as? Int - - let instantPageFontSize: Float? = standardPreferences["instantPage_fontMultiplier_v0"] as? Float - let instantPageFontSerif: Int? = standardPreferences["instantPage_fontSerif_v0"] as? Int - let instantPageTheme: Int? = standardPreferences["instantPage_theme_v0"] as? Int - let instantPageAutoNightMode: Int? = standardPreferences["instantPage_autoNightTheme_v0"] as? Int - - let proxyList = NSKeyedUnarchiver.unarchiveObject(withFile: documentsPath + "/proxies.data") as? [TGProxyItem] - var selectedProxy: (ProxyServerSettings, Bool)? - if let data = loadLegacyCustomProperyData(database: database, key: "socksProxyData"), let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Any], let host = dict["ip"] as? String, let port = dict["port"] as? Int { - let inactive = (dict["inactive"] as? Bool) ?? true - var connection: ProxyServerConnection? - if let secretString = dict["secret"] as? String { - let secret = MTProxySecret.parse(secretString) - if let secret = secret { - connection = .mtp(secret: secret.serialize()) - } - } else { - connection = .socks5(username: (dict["username"] as? String) ?? "", password: (dict["password"] as? String) ?? "") - } - if let connection = connection { - selectedProxy = (ProxyServerSettings(host: host, port: convertLegacyProxyPort(port), connection: connection), !inactive) - } - } - - var passcodeChallenge: PostboxAccessChallengeData? - if let data = try? Data(contentsOf: URL(fileURLWithPath: documentsPath + "/x.y")) { - let reader = LegacyBufferReader(LegacyBuffer(data: data)) - if let mode = reader.readBytesAsInt32(1), let length = reader.readInt32(), let passwordData = reader.readBuffer(Int(length))?.makeData(), let passwordText = String(data: passwordData, encoding: .utf8) { - var lockTimeout: Int32? - if let value = UserDefaults.standard.object(forKey: "Passcode_lockTimeout") as? Int { - if value == 0 { - lockTimeout = nil - } else { - lockTimeout = max(60, Int32(clamping: value)) - } - } else { - lockTimeout = 1 * 60 * 60 - } - - if mode == 3 { - passcodeChallenge = .numericalPassword(value: passwordText) - } else if mode == 4 { - passcodeChallenge = PostboxAccessChallengeData.plaintextPassword(value: passwordText) - } - } - } - - var passcodeEnableBiometrics: Bool = true - if let value = UserDefaults.standard.object(forKey: "Passcode_useTouchId") as? Bool { - passcodeEnableBiometrics = value - } - - var localization: TGLocalization? - if let nativeDocumentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first { - localization = NSKeyedUnarchiver.unarchiveObject(withFile: nativeDocumentsPath + "/localization") as? TGLocalization - } - - return accountManager.transaction { transaction -> Signal in - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationThemeSettings, { current in - var settings = (current as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings - if let presentationState = presentationState { - switch presentationState.pallete { - case 1: - settings.theme = .builtin(.day) - - if presentationState.userInfo != 0 { - //themeSpecificAccentColors: current.themeSpecificAccentColors - //settings.themeAccentColor = presentationState.userInfo - } - settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0xffffff) - case 2: - settings.theme = .builtin(.night) - settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x000000) - case 3: - settings.theme = .builtin(.nightAccent) - settings.themeSpecificChatWallpapers[settings.theme.index] = .color(0x18222d) - default: - settings.theme = .builtin(.dayClassic) - settings.themeSpecificChatWallpapers[settings.theme.index] = .builtin(WallpaperSettings()) - } - let fontSizeMap: [Int32: PresentationFontSize] = [ - 14: .extraSmall, - 15: .small, - 16: .medium, - 17: .regular, - 19: .large, - 23: .extraLarge, - 26: .extraLargeX2 - ] - settings.fontSize = fontSizeMap[presentationState.fontSize] ?? .regular - - if presentationState.userInfo != 0 { - //themeSpecificAccentColors: current.themeSpecificAccentColors - //settings.themeAccentColor = presentationState.userInfo - } - } - - if let autoNightPreferences = autoNightPreferences { - let nightTheme: PresentationBuiltinThemeReference - switch autoNightPreferences.preferredPalette { - case 1: - nightTheme = .night - default: - nightTheme = .nightAccent - } - switch autoNightPreferences.mode { - case TGPresentationAutoNightModeSunsetSunrise: - settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .automatic(latitude: Double(autoNightPreferences.latitude), longitude: Double(autoNightPreferences.longitude), localizedName: autoNightPreferences.cachedLocationName)), theme: .builtin(nightTheme)) - case TGPresentationAutoNightModeScheduled: - settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .timeBased(setting: .manual(fromSeconds: autoNightPreferences.scheduleStart, toSeconds: autoNightPreferences.scheduleEnd)), theme: .builtin(nightTheme)) - case TGPresentationAutoNightModeBrightness: - settings.automaticThemeSwitchSetting = AutomaticThemeSwitchSetting(trigger: .brightness(threshold: Double(autoNightPreferences.brightnessThreshold)), theme: .builtin(nightTheme)) - default: - break - } - } - - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.automaticMediaDownloadSettings, { current in - var settings: MediaAutoDownloadSettings = current as? MediaAutoDownloadSettings ?? .defaultSettings - - if let preferences = autoDownloadPreferences, !preferences.isDefaultPreferences() { - settings.cellular.enabled = !preferences.disabled - settings.wifi.enabled = !preferences.disabled - } - - if let parsedAutoplayGifs = parsedAutoplayGifs { - settings.autoplayGifs = parsedAutoplayGifs - } - - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings, { current in - var settings: InAppNotificationSettings = current as? InAppNotificationSettings ?? .defaultSettings - if let soundEnabled = soundEnabled { - settings.playSounds = soundEnabled - } - if let vibrationEnabled = vibrationEnabled { - settings.vibrate = vibrationEnabled - } - if let bannerEnabled = bannerEnabled { - settings.displayPreviews = bannerEnabled - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.voiceCallSettings, { current in - var settings: VoiceCallSettings = current as? VoiceCallSettings ?? .defaultSettings - if let callsDataUsageMode = callsDataUsageMode { - switch callsDataUsageMode { - case 1: - settings.dataSaving = .cellular - case 2: - settings.dataSaving = .always - default: - settings.dataSaving = .never - } - } - if let callsDisableCallKit = callsDisableCallKit, callsDisableCallKit { - settings.enableSystemIntegration = false - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.callListSettings, { current in - var settings: CallListSettings = current as? CallListSettings ?? .defaultSettings - if let showCallsTab = showCallsTab { - settings.showTab = showCallsTab - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.presentationPasscodeSettings, { current in - var settings: PresentationPasscodeSettings = current as? PresentationPasscodeSettings ?? .defaultSettings - if let passcodeChallenge = passcodeChallenge { - transaction.setAccessChallengeData(passcodeChallenge) - settings.enableBiometrics = passcodeEnableBiometrics - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.stickerSettings, { current in - var settings: StickerSettings = current as? StickerSettings ?? .defaultSettings - if let stickersSuggestMode = stickersSuggestMode { - switch stickersSuggestMode { - case 1: - settings.emojiStickerSuggestionMode = .installed - case 2: - settings.emojiStickerSuggestionMode = .none - default: - settings.emojiStickerSuggestionMode = .all - } - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { current in - var settings: MusicPlaybackSettings = current as? MusicPlaybackSettings ?? .defaultSettings - if let musicPlayerOrderType = musicPlayerOrderType { - switch musicPlayerOrderType { - case 1: - settings.order = .reversed - case 2: - settings.order = .random - default: - settings.order = .regular - } - } - if let musicPlayerRepeatType = musicPlayerRepeatType { - switch musicPlayerRepeatType { - case 1: - settings.looping = .all - case 2: - settings.looping = .item - default: - settings.looping = .none - } - } - return settings - }) - - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.instantPagePresentationSettings, { current in - let settings: InstantPagePresentationSettings = current as? InstantPagePresentationSettings ?? .defaultSettings - if let instantPageFontSize = instantPageFontSize { - switch instantPageFontSize { - case 0.85: - settings.fontSize = .small - case 1.15: - settings.fontSize = .large - case 1.30: - settings.fontSize = .xlarge - case 1.50: - settings.fontSize = .xxlarge - default: - settings.fontSize = .standard - } - } - if let instantPageFontSerif = instantPageFontSerif { - settings.forceSerif = instantPageFontSerif == 1 - } - if let instantPageTheme = instantPageTheme { - switch instantPageTheme { - case 1: - settings.themeType = .sepia - case 2: - settings.themeType = .gray - case 3: - settings.themeType = .dark - default: - settings.themeType = .light - } - } - if let instantPageAutoNightMode = instantPageAutoNightMode { - settings.autoNightMode = instantPageAutoNightMode == 1 - } - return settings - }) - - if let localization = localization { - transaction.updateSharedData(SharedDataKeys.localizationSettings, { _ in - var entries: [LocalizationEntry] = [] - for (key, value) in localization.dict() { - entries.append(LocalizationEntry.string(key: key, value: value)) - } - return LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: localization.code, localizedName: "", localization: Localization(version: 0, entries: entries), customPluralizationCode: nil), secondaryComponent: nil) - }) - } - - transaction.updateSharedData(SharedDataKeys.proxySettings, { current in - var settings: ProxySettings = current as? ProxySettings ?? .defaultSettings - if let callsUseProxy = callsUseProxy { - settings.useForCalls = callsUseProxy - } - - if let proxyList = proxyList { - for item in proxyList { - let connection: ProxyServerConnection? - if item.isMTProxy, let secret = item.secret { - let parsedSecret = MTProxySecret.parse(secret) - if let parsedSecret = parsedSecret { - connection = .mtp(secret: parsedSecret.serialize()) - } else { - connection = nil - } - } else if !item.isMTProxy { - connection = .socks5(username: item.username ?? "", password: item.password ?? "") - } else { - connection = nil - } - if let connection = connection { - settings.servers.append(ProxyServerSettings(host: item.server, port: convertLegacyProxyPort(Int(item.port)), connection: connection)) - } - } - } - - if let (server, active) = selectedProxy { - if !settings.servers.contains(server) { - settings.servers.insert(server, at: 0) - } - settings.activeServer = server - settings.enabled = active - } - - return settings - }) - - if let secretInlineBotsInitialized = secretInlineBotsInitialized, secretInlineBotsInitialized { - ApplicationSpecificNotice.setSecretChatInlineBotUsage(transaction: transaction) - } - - if let allowSecretWebpagesInitialized = allowSecretWebpagesInitialized, allowSecretWebpagesInitialized, let allowSecretWebpages = allowSecretWebpages { - ApplicationSpecificNotice.setSecretChatLinkPreviews(transaction: transaction, value: allowSecretWebpages) - } - - return account.postbox.transaction { transaction -> Void in - transaction.updatePreferencesEntry(key: PreferencesKeys.contactsSettings, { current in - var settings = current as? ContactsSettings ?? ContactsSettings.defaultSettings - if let contactsInhibitSync = contactsInhibitSync, contactsInhibitSync { - settings.synchronizeContacts = false - } - return settings - }) - } - } - |> switchToLatest - |> ignoreValues - } -} diff --git a/submodules/LegacyDataImport/Sources/LegacyResourceImport.swift b/submodules/LegacyDataImport/Sources/LegacyResourceImport.swift deleted file mode 100644 index fd4bc6f2e0..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyResourceImport.swift +++ /dev/null @@ -1,138 +0,0 @@ -import Foundation -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox -import LegacyComponents - -func resourceFromLegacyImageUrl(_ fileRef: String) -> TelegramMediaResource? { - if fileRef.isEmpty { - return nil - } - let components = fileRef.components(separatedBy: "_") - if components.count != 4 { - return nil - } - - guard let datacenterId = Int32(components[0]) else { - return nil - } - guard let volumeId = Int64(components[1]) else { - return nil - } - guard let localId = Int32(components[2]) else { - return nil - } - guard let secret = Int64(components[3]) else { - return nil - } - - return CloudFileMediaResource(datacenterId: Int(datacenterId), volumeId: volumeId, localId: localId, secret: secret, size: nil, fileReference: nil) -} - -func pathFromLegacyImageUrl(basePath: String, url: String) -> String { - let cache = TGCache(cachesPath: basePath + "/Caches")! - return cache.path(forCachedData: url) -} - -func pathFromLegacyVideoUrl(basePath: String, url: String) -> (id: Int64, accessHash: Int64, datacenterId: Int32, path: String)? { - if !url.hasPrefix("video:") { - return nil - } - //[videoInfo addVideoWithQuality:1 url:[[NSString alloc] initWithFormat:@"video:%lld:%lld:%d:%d", videoMedia.videoId, videoMedia.accessHash, concreteResult.document.datacenterId, concreteResult.document.size] size:concreteResult.document.size]; - let components = url.components(separatedBy: ":") - if components.count != 5 { - return nil - } - guard let videoId = Int64(components[1]) else { - return nil - } - guard let accessHash = Int64(components[2]) else { - return nil - } - guard let datacenterId = Int32(components[3]) else { - return nil - } - let documentsPath = basePath + "/Documents" - let videoPath = documentsPath + "/video/remote\(String(videoId, radix: 16)).mov" - return (videoId, accessHash, datacenterId, videoPath) -} - -func pathFromLegacyLocalVideoUrl(basePath: String, url: String) -> String? { - let documentsPath = basePath + "/Documents" - if !url.hasPrefix("local-video:") { - return nil - } - let videoPath = documentsPath + "/video/" + String(url[url.index(url.startIndex, offsetBy: "local-video:".count)...]) - return videoPath -} - -func pathFromLegacyFile(basePath: String, fileId: Int64, isLocal: Bool, fileName: String) -> String { - let documentsPath = basePath + "/Documents" - let filePath = documentsPath + "/files/" + (isLocal ? "local" : "") + "\(String(fileId, radix: 16))/\(fileName)" - return filePath -} - -enum EncryptedFileType { - case image - case video - case document(fileName: String) - case audio -} - -func pathAndResourceFromEncryptedFileUrl(basePath: String, url: String, type: EncryptedFileType) -> (String, TelegramMediaResource)? { - let cache = TGCache(cachesPath: basePath + "/Caches")! - - if url.hasPrefix("encryptedThumbnail:") { - let path = cache.path(forCachedData: url)! - return (path, LocalFileMediaResource(fileId: arc4random64())) - } - - if !url.hasPrefix("mt-encrypted-file://?") { - return nil - } - guard let dict = TGStringUtils.argumentDictionary(inUrlString: String(url[url.index(url.startIndex, offsetBy: "mt-encrypted-file://?".count)...])) else { - return nil - } - guard let idString = dict["id"] as? String, let id = Int64(idString) else { - return nil - } - guard let datacenterIdString = dict["dc"] as? String, let datacenterId = Int32(datacenterIdString) else { - return nil - } - guard let accessHashString = dict["accessHash"] as? String, let accessHash = Int64(accessHashString) else { - return nil - } - guard let sizeString = dict["size"] as? String, let size = Int32(sizeString) else { - return nil - } - guard let decryptedSizeString = dict["decryptedSize"] as? String, let decryptedSize = Int32(decryptedSizeString) else { - return nil - } - guard let keyFingerprintString = dict["fingerprint"] as? String, let _ = Int32(keyFingerprintString) else { - return nil - } - guard let keyString = dict["key"] as? String else { - return nil - } - let keyData = dataWithHexString(keyString) - guard keyData.count == 64 else { - return nil - } - - let resource = SecretFileMediaResource(fileId: id, accessHash: accessHash, containerSize: size, decryptedSize: decryptedSize, datacenterId: Int(datacenterId), key: SecretFileEncryptionKey(aesKey: keyData.subdata(in: 0 ..< 32), aesIv: keyData.subdata(in: 32 ..< 64))) - - let filePath: String - switch type { - case .video: - filePath = basePath + "Documents/video/remote\(String(id, radix: 16)).mov" - case .image: - filePath = cache.path(forCachedData: url) - case let .document(fileName): - filePath = basePath + "Documents/files/\(String(id, radix: 16))/\(TGDocumentMediaAttachment.safeFileName(forFileName: fileName)!)" - case .audio: - filePath = basePath + "Documents/audio/\(String(id, radix: 16))" - } - - return (filePath, resource) -} diff --git a/submodules/LegacyDataImport/Sources/LegacyUserDataImport.swift b/submodules/LegacyDataImport/Sources/LegacyUserDataImport.swift deleted file mode 100644 index d3ba00070f..0000000000 --- a/submodules/LegacyDataImport/Sources/LegacyUserDataImport.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation -import UIKit -import TelegramCore -import SyncCore -import SwiftSignalKit -import Postbox - -func loadLegacyUser(database: SqliteInterface, id: Int32) -> (TelegramUser, TelegramUserPresence)? { - var result: (TelegramUser, TelegramUserPresence)? - database.select("SELECT uid, first_name, last_name, phone_number, access_hash, photo_small, photo_big, last_seen, username FROM users_v29 WHERE uid=\(id)", { cursor in - let accessHash = cursor.getInt64(at: 4) - let firstName = cursor.getString(at: 1) - let lastName = cursor.getString(at: 2) - let username = cursor.getString(at: 8) - let phone = cursor.getString(at: 3) - - let photoSmall = cursor.getString(at: 5) - let photoBig = cursor.getString(at: 6) - var photo: [TelegramMediaImageRepresentation] = [] - if let resource = resourceFromLegacyImageUrl(photoSmall) { - photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 80, height: 80), resource: resource, progressiveSizes: [])) - } - if let resource = resourceFromLegacyImageUrl(photoBig) { - photo.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 600, height: 600), resource: resource, progressiveSizes: [])) - } - - let user = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: cursor.getInt32(at: 0)), accessHash: accessHash == 0 ? nil : .personal(accessHash), firstName: firstName.isEmpty ? nil : firstName, lastName: lastName.isEmpty ? nil : lastName, username: username.isEmpty ? nil : username, phone: phone.isEmpty ? nil : phone, photo: photo, botInfo: nil, restrictionInfo: nil, flags: []) - - let status: UserPresenceStatus - let lastSeen = cursor.getInt32(at: 7) - if lastSeen == -2 { - status = .recently - } else if lastSeen == -3 { - status = .lastWeek - } else if lastSeen == -4 { - status = .lastMonth - } else if lastSeen <= 0 { - status = .none - } else { - status = .present(until: lastSeen) - } - - let presence = TelegramUserPresence(status: status, lastActivity: 0) - result = (user, presence) - return false - }) - return result -} diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD index 7cdc998863..951f5a14ff 100644 --- a/submodules/LegacyMediaPickerUI/BUILD +++ b/submodules/LegacyMediaPickerUI/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyUI:LegacyUI", + "//submodules/ContextUI:ContextUI", "//submodules/MimeTypes:MimeTypes", "//submodules/LocalMediaResources:LocalMediaResources", "//submodules/SearchPeerMembers:SearchPeerMembers", diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index acc28dacf4..87c5001af8 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -3,7 +3,6 @@ import UIKit import LegacyComponents import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import DeviceAccess @@ -131,12 +130,12 @@ public func legacyStoryMediaEditor(context: AccountContext, item: TGMediaEditabl present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, adjustments: false, recipientName: "", stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [] as [Any], immediate: true, activateInput: false, isGif: false, appeared: { + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, adjustments: false, recipientName: "", stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [] as [Any], immediate: true, activateInput: false, isGif: false, hasSilentPosting: false, hasSchedule: false, reminder: false, presentSchedulePicker: { _, _ in }, appeared: { - }, completion: { result, editingContext in + }, completion: { result, editingContext, _, _ in var completionResult: Signal if let photo = result as? TGCameraCapturedPhoto { - if let _ = editingContext?.adjustments(for: result) { + if let _ = editingContext.adjustments(for: result) { completionResult = .single(.image(photo.existingImage)) } else { completionResult = .single(.image(photo.existingImage)) @@ -159,7 +158,7 @@ public func legacyStoryMediaEditor(context: AccountContext, item: TGMediaEditabl public func legacyMediaEditor( context: AccountContext, - peer: Peer, + peer: EnginePeer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, @@ -167,12 +166,17 @@ public func legacyMediaEditor( snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, + photoToolbarView: ((TGPhotoEditorBackButton, TGPhotoEditorDoneButton, Bool, Bool) -> (UIView & TGPhotoToolbarViewProtocol)?)? = nil, + hasSilentPosting: Bool = false, + hasSchedule: Bool = false, + reminder: Bool = false, + presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void = { _, _ in }, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void ) { - let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media) + let _ = (fetchMediaData(context: context, userLocation: .other, mediaReference: media) |> deliverOnMainQueue).start(next: { (value, isImage) in - guard case let .data(data) = value, data.complete else { + guard case let .data(data) = value, data.isComplete else { return } @@ -188,6 +192,7 @@ public func legacyMediaEditor( paintStickersContext.captionPanelView = { return getCaptionPanelView() } + paintStickersContext.photoToolbarView = photoToolbarView let presentationData = context.sharedContext.currentPresentationData.with { $0 } let recipientName: String @@ -197,7 +202,7 @@ public func legacyMediaEditor( if peer.id == context.account.peerId { recipientName = presentationData.strings.DialogList_SavedMessages } else { - recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } } @@ -205,42 +210,76 @@ public func legacyMediaEditor( legacyController.blocksBackgroundWhenInOverlay = true legacyController.acceptsFocusWhenInOverlay = true legacyController.statusBar.statusBarStyle = .Ignore + paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: presentationData, presentInGlobalOverlay: { [weak legacyController] controller in + if let legacyController { + legacyController.presentInGlobalOverlay(controller) + } else if let mainWindow = context.sharedContext.mainWindow { + mainWindow.presentInGlobalOverlay(controller) + } else { + context.sharedContext.presentGlobalController(controller, nil) + } + }) legacyController.controllerLoaded = { [weak legacyController] in legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true } - - let emptyController = LegacyEmptyController(context: legacyController.context)! - emptyController.navigationBarShouldBeHidden = true - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - legacyController.bind(controller: navigationController) - - legacyController.enableSizeClassSignal = true + legacyController.presentationCompleted = { + Queue.mainQueue().after(0.1) { + transitionCompletion?() + } + } - present(legacyController, nil) - - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: mode == .draw, adjustments: mode == .adjustments, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, activateInput: mode == .caption, isGif: isGif, appeared: { - transitionCompletion?() - }, completion: { result, editingContext in + let schedulePicker: (Bool, @escaping (Int32, Bool) -> Void) -> Void = { media, done in + presentSchedulePicker(media, done) + } + let appeared: () -> Void = { + } + let completion: (TGMediaEditableItem, TGMediaEditingContext, Bool, Int32) -> Void = { result, editingContext, silentPosting, scheduleTime in let nativeGenerator = legacyAssetPickerItemGenerator() var selectableResult: TGMediaSelectableItem? - if let result = result { - selectableResult = unsafeDowncast(result, to: TGMediaSelectableItem.self) - } + selectableResult = unsafeDowncast(result, to: TGMediaSelectableItem.self) + let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: selectableResult, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3 in nativeGenerator(_1, _2, _3, nil) }) - let isCaptionAbove = editingContext?.isCaptionAbove() ?? false - sendMessagesWithSignals(signals, false, 0, isCaptionAbove) - }, dismissed: { [weak legacyController] in + let isCaptionAbove = editingContext.isCaptionAbove() + sendMessagesWithSignals(signals, silentPosting, scheduleTime, isCaptionAbove) + } + let dismissed: () -> Void = { [weak legacyController] in legacyController?.dismiss() - }) + } + + legacyController.enableSizeClassSignal = true + + let galleryController = TGPhotoVideoEditor.controller( + with: legacyController.context, + caption: initialCaption, + withItem: item, + paint: mode == .draw, + adjustments: mode == .adjustments, + recipientName: recipientName, + stickersContext: paintStickersContext, + from: .zero, + mainSnapshot: nil, + snapshots: snapshots as [Any], + immediate: transitionCompletion != nil, + activateInput: mode == .caption, + isGif: isGif, + hasSilentPosting: hasSilentPosting, + hasSchedule: hasSchedule, + reminder: reminder, + presentSchedulePicker: schedulePicker, + appeared: appeared, + completion: completion, + dismissed: dismissed + ) + legacyController.bind(controller: galleryController) + present(legacyController, nil) }) } public func legacyAttachmentMenu( context: AccountContext, - peer: Peer?, + peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, @@ -251,7 +290,7 @@ public func legacyAttachmentMenu( canSendPolls: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal), parentController: LegacyController, - recentlyUsedInlineBots: [Peer], + recentlyUsedInlineBots: [EnginePeer], initialCaption: NSAttributedString, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, @@ -263,10 +302,10 @@ public func legacyAttachmentMenu( presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentJpegConversionAlert: @escaping (@escaping (Bool) -> Void) -> Void, - presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, + presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, - selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, + selectRecentlyUsedInlineBot: @escaping (EnginePeer) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void ) -> TGMenuSheetController { @@ -281,7 +320,7 @@ public func legacyAttachmentMenu( if peer.id == context.account.peerId { recipientName = presentationData.strings.DialogList_SavedMessages } else { - recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } } else { recipientName = "" @@ -325,12 +364,21 @@ public func legacyAttachmentMenu( var selectionLimit: Int32 = 100 var slowModeEnabled = false - if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { + if case let .channel(channel) = peer, channel.isRestrictedBySlowmode { slowModeEnabled = true selectionLimit = 10 } let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: updatedPresentationData.initial, presentInGlobalOverlay: { [weak parentController] controller in + if let parentController { + parentController.presentInGlobalOverlay(controller) + } else if let mainWindow = context.sharedContext.mainWindow { + mainWindow.presentInGlobalOverlay(controller) + } else { + context.sharedContext.presentGlobalController(controller, nil) + } + }) paintStickersContext.captionPanelView = { return getCaptionPanelView() } @@ -364,7 +412,7 @@ public func legacyAttachmentMenu( presentSelectionLimitExceeded() } if let peer, peer.id != context.account.peerId { - if peer is TelegramUser { + if case .user = peer { carouselItem.hasTimer = hasSchedule } carouselItem.hasSilentPosting = true @@ -372,8 +420,8 @@ public func legacyAttachmentMenu( carouselItem.hasSchedule = hasSchedule carouselItem.reminder = peer?.id == context.account.peerId carouselItem.presentScheduleController = { media, done in - presentSchedulePicker(media, { time in - done?(time) + presentSchedulePicker(media, { time, silentPosting in + done?(time, silentPosting) }) } carouselItem.presentTimerController = { done in @@ -487,9 +535,9 @@ public func legacyAttachmentMenu( let editCurrentItem = TGMenuSheetButtonItemView(title: title, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in controller?.dismiss(animated: true) - let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: editCurrentMedia) + let _ = (fetchMediaData(context: context, userLocation: .other, mediaReference: editCurrentMedia) |> deliverOnMainQueue).start(next: { (value, isImage) in - guard case let .data(data) = value, data.complete else { + guard case let .data(data) = value, data.isComplete else { return } @@ -514,7 +562,7 @@ public func legacyAttachmentMenu( let recipientName: String if let peer { - recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } else { recipientName = "" } @@ -530,13 +578,11 @@ public func legacyAttachmentMenu( present(legacyController, nil) - TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, activateInput: false, isGif: false, appeared: { - }, completion: { result, editingContext in + TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, activateInput: false, isGif: false, hasSilentPosting: false, hasSchedule: false, reminder: false, presentSchedulePicker: { _, _ in }, appeared: { + }, completion: { result, editingContext, _, _ in let nativeGenerator = legacyAssetPickerItemGenerator() - var selectableResult: TGMediaSelectableItem? - if let result = result { - selectableResult = unsafeDowncast(result, to: TGMediaSelectableItem.self) - } + let selectableResult: TGMediaSelectableItem? = unsafeDowncast(result, to: TGMediaSelectableItem.self) + let signals = TGCameraController.resultSignals(for: nil, editingContext: editingContext, currentItem: selectableResult, storeAssets: false, saveEditedPhotos: false, descriptionGenerator: { _1, _2, _3 in nativeGenerator(_1, _2, _3, nil) }) @@ -558,10 +604,13 @@ public func legacyAttachmentMenu( var peerSupportsPolls = false if let peer { - if peer is TelegramGroup || peer is TelegramChannel { + switch peer { + case .legacyGroup, .channel: peerSupportsPolls = true - } else if let user = peer as? TelegramUser, let _ = user.botInfo { + case let .user(user) where user.botInfo != nil: peerSupportsPolls = true + default: + break } } if let peer, peerSupportsPolls, canSendMessagesToPeer(peer) && canSendPolls { diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift index d367e03a9d..779e0cffee 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAvatarPicker.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -55,18 +54,18 @@ public func presentLegacyAvatarPicker(holder: Atomic, signup: Bool, t public func legacyAvatarEditor(context: AccountContext, media: AnyMediaReference, transitionView: UIView?, senderName: String? = nil, present: @escaping (ViewController, Any?) -> Void, imageCompletion: @escaping (UIImage) -> Void, videoCompletion: @escaping (UIImage, URL, TGVideoEditAdjustments) -> Void) { let isVideo = !((media.media as? TelegramMediaImage)?.videoRepresentations.isEmpty ?? true) - let imageSignal = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media, forceVideo: false) + let imageSignal = fetchMediaData(context: context, userLocation: .other, mediaReference: media, forceVideo: false) |> map { (value, _) -> (UIImage?, Bool) in - if case let .data(data) = value, data.complete { + if case let .data(data) = value, data.isComplete { return (UIImage(contentsOfFile: data.path), true) } else { return (nil, false) } } - let videoSignal = isVideo ? fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media, forceVideo: true) + let videoSignal = isVideo ? fetchMediaData(context: context, userLocation: .other, mediaReference: media, forceVideo: true) |> map { (value, isImage) -> (URL?, Bool) in - if case let .data(data) = value, data.complete && !isImage { + if case let .data(data) = value, data.isComplete && !isImage { return (URL(fileURLWithPath: data.path), true) } else { return (nil, false) @@ -108,11 +107,9 @@ public func legacyAvatarEditor(context: AccountContext, media: AnyMediaReference present(legacyController, nil) TGPhotoVideoEditor.present(with: legacyController.context, parentController: emptyController, image: image.0, video: video.0, stickersContext: paintStickersContext, transitionView: transitionView, senderName: senderName, didFinishWithImage: { image in - if let image = image { - imageCompletion(image) - } + imageCompletion(image) }, didFinishWithVideo: { image, url, adjustments in - if let image = image, let url = url, let adjustments = adjustments { + if let adjustments = adjustments { videoCompletion(image, url, adjustments) } }, dismissed: { [weak legacyController] in diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 17ab15a722..4d452670b9 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -3,7 +3,6 @@ import UIKit import LegacyComponents import SwiftSignalKit import TelegramCore -import Postbox import SSignalKit import Display import TelegramPresentationData @@ -20,8 +19,9 @@ public func guessMimeTypeByFileExtension(_ ext: String) -> String { return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary" } -public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, chatLocation: ChatLocation, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: NSAttributedString, hasSchedule: Bool, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?) { +public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: EngineRawPeer, chatLocation: ChatLocation, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: NSAttributedString, hasSchedule: Bool, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?) { let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context) paintStickersContext.captionPanelView = { return getCaptionPanelView() } @@ -43,8 +43,8 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co controller.hasCoverButton = true } controller.presentScheduleController = { media, done in - presentSchedulePicker(media, { time in - done?(time) + presentSchedulePicker(media, { time, silentPosting in + done?(time, silentPosting) }) } controller.presentTimerController = { done in @@ -65,7 +65,7 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co } } -public func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, threadTitle: String?, saveEditedPhotos: Bool, allowGrouping: Bool, selectionLimit: Int) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { +public func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: EngineRawPeer?, threadTitle: String?, saveEditedPhotos: Bool, allowGrouping: Bool, selectionLimit: Int) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> { let isSecretChat = (peer?.id.namespace._internalGetInt32Value() ?? 0) == Namespaces.Peer.SecretChat._internalGetInt32Value() let recipientName: String? @@ -162,7 +162,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, NSAttributedString?, Str return { anyDict, caption, hash, uniqueId in let dict = anyDict as! NSDictionary let stickers = (dict["stickers"] as? [Data])?.compactMap { data -> FileMediaReference? in - let decoder = PostboxDecoder(buffer: MemoryBuffer(data: data)) + let decoder = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: data)) if let file = decoder.decodeRootObject() as? TelegramMediaFile { return FileMediaReference.standalone(media: file) } else { @@ -350,49 +350,7 @@ public func legacyEnqueueGifMessage(account: Account, data: Data, correlationId: fileAttributes.append(.FileName(fileName: fileName)) fileAttributes.append(.Animated) - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) - subscriber.putNext(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])) - subscriber.putCompletion() - } else { - subscriber.putError(Void()) - } - - return EmptyDisposable - } |> runOn(Queue.concurrentDefaultQueue()) -} - -public func legacyEnqueueVideoMessage(account: Account, data: Data, correlationId: Int64? = nil) -> Signal { - return Signal { subscriber in - if let previewImage = UIImage(data: data) { - let dimensions = previewImage.size - var previewRepresentations: [TelegramMediaImageRepresentation] = [] - - let thumbnailSize = dimensions.aspectFitted(CGSize(width: 320.0, height: 320.0)) - let thumbnailImage = TGScaleImageToPixelSize(previewImage, thumbnailSize)! - if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData) - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - - var randomId: Int64 = 0 - arc4random_buf(&randomId, 8) - let tempFilePath = NSTemporaryDirectory() + "\(randomId).mp4" - - let _ = try? FileManager.default.removeItem(atPath: tempFilePath) - let _ = try? data.write(to: URL(fileURLWithPath: tempFilePath), options: [.atomic]) - - let resource = LocalFileGifMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: tempFilePath) - let fileName: String = "video.mp4" - - let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation) - - var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil, videoCodec: nil)) - fileAttributes.append(.FileName(fileName: fileName)) - fileAttributes.append(.Animated) - - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) subscriber.putNext(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])) subscriber.putCompletion() } else { @@ -423,7 +381,7 @@ public func legacyAssetPickerEnqueueMessages( var price: Int64 var text: String var entities: [MessageTextEntity] - var media: [Media] + var media: [EngineRawMedia] } var paidMessage: EnqueuePaidMessage? @@ -451,9 +409,9 @@ public func legacyAssetPickerEnqueueMessages( let scaledSize = image.size.aspectFittedOrSmaller(maxSize) if let scaledImage = TGScaleImageToPixelSize(image, scaledSize) { - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } if let scaledImageData = compressImageToJPEG(scaledImage, quality: 0.6, tempFilePath: tempFile.path) { let _ = try? scaledImageData.write(to: URL(fileURLWithPath: tempFilePath)) @@ -463,7 +421,7 @@ public func legacyAssetPickerEnqueueMessages( var imageFlags: TelegramMediaImageFlags = [] - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] var stickerFiles: [TelegramMediaFile] = [] if !stickers.isEmpty { @@ -491,8 +449,8 @@ public func legacyAssetPickerEnqueueMessages( } if let dict = adjustments.dictionary(), let data = try? NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false) { - let adjustmentsData = MemoryBuffer(data: data) - let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) + let adjustmentsData = EngineMemoryBuffer(data: data) + let digest = EngineMemoryBuffer(data: adjustmentsData.md5Digest()) resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest, isStory: false) } } @@ -505,11 +463,11 @@ public func legacyAssetPickerEnqueueMessages( if estimatedSize > 10 * 1024 * 1024 { fileAttributes.append(.hintFileIsLarge) } - videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], videoCover: nil, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) + videoFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], videoCover: nil, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) imageFlags.insert(.isLivePhoto) } - let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags, video: videoFile) + let media = TelegramMediaImage(imageId: EngineMedia.Id(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags, video: videoFile) if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } @@ -522,7 +480,7 @@ public func legacyAssetPickerEnqueueMessages( if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -532,7 +490,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -594,13 +552,13 @@ public func legacyAssetPickerEnqueueMessages( let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight)) let scaledSize = size.aspectFittedOrSmaller(CGSize(width: CGFloat(sizeSide), height: CGFloat(sizeSide))) - let media: Media - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: format == .jxl ? "image/jxl" : "image/jpeg", size: nil, attributes: [ + let media: EngineRawMedia + media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: format == .jxl ? "image/jxl" : "image/jpeg", size: nil, attributes: [ .FileName(fileName: format == .jxl ? "image\(sizeSide)-q\(quality).jxl" : "image\(sizeSide)-q\(quality).jpg"), .ImageSize(size: PixelDimensions(scaledSize)) ], alternativeRepresentations: []) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } @@ -614,7 +572,7 @@ public func legacyAssetPickerEnqueueMessages( attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -624,7 +582,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -664,11 +622,11 @@ public func legacyAssetPickerEnqueueMessages( let scaledSize = size.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0)) let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max), forceHd: item.forceHd) - let media: Media + let media: EngineRawMedia representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + media = TelegramMediaImage(imageId: EngineMedia.Id(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil)) } @@ -682,7 +640,7 @@ public func legacyAssetPickerEnqueueMessages( attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -692,7 +650,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -743,16 +701,16 @@ public func legacyAssetPickerEnqueueMessages( var randomId: Int64 = 0 arc4random_buf(&randomId, 8) let resource = LocalFileReferenceMediaResource(localFilePath: path, randomId: randomId) - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: fileSize(path), attributes: [.FileName(fileName: name)], alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: engineFileSize(path), attributes: [.FileName(fileName: name)], alternativeRepresentations: []) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString())) let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -762,7 +720,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -796,16 +754,16 @@ public func legacyAssetPickerEnqueueMessages( var randomId: Int64 = 0 arc4random_buf(&randomId, 8) let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max)) - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] let text = trimChatInputText(convertMarkdownToAttributes(caption ?? NSAttributedString())) let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -815,7 +773,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -900,7 +858,7 @@ public func legacyAssetPickerEnqueueMessages( if let coverData = coverImage.jpegData(compressionQuality: 0.87) { account.postbox.mediaBox.storeResourceData(resource.id, data: coverData) videoCover = TelegramMediaImage( - imageId: MediaId(namespace: 0, id: 0), + imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [ TelegramMediaImageRepresentation(dimensions: PixelDimensions(coverSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) ], @@ -933,8 +891,8 @@ public func legacyAssetPickerEnqueueMessages( } if let dict = adjustments.dictionary(), let data = try? NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false) { - let adjustmentsData = MemoryBuffer(data: data) - let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) + let adjustmentsData = EngineMemoryBuffer(data: data) + let digest = EngineMemoryBuffer(data: adjustmentsData.md5Digest()) resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest, isStory: false) } } @@ -949,7 +907,7 @@ public func legacyAssetPickerEnqueueMessages( resource = VideoLibraryMediaResource(localIdentifier: asset.backingAsset.localIdentifier, conversion: asFile ? .passthrough : .compress(resourceAdjustments)) case let .tempFile(path, _, _): if asFile || (asAnimation && !path.contains(".jpg")) { - if let size = fileSize(path) { + if let size = engineFileSize(path) { resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: size) account.postbox.mediaBox.moveResourceData(resource.id, fromTempPath: path) } else { @@ -989,7 +947,7 @@ public func legacyAssetPickerEnqueueMessages( } } - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] var stickerFiles: [TelegramMediaFile] = [] if !stickers.isEmpty { @@ -1002,13 +960,13 @@ public func legacyAssetPickerEnqueueMessages( fileAttributes.append(.HasLinkedStickers) } - let media: Media + let media: EngineRawMedia let mediaReference: AnyMediaReference if let adjustments, adjustments.isDefaultValuesForGif(), let originalMediaReference { media = originalMediaReference.media mediaReference = originalMediaReference } else { - media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], videoCover: videoCover, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) + media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], videoCover: videoCover, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes, alternativeRepresentations: []) mediaReference = .standalone(media: media) } @@ -1025,7 +983,7 @@ public func legacyAssetPickerEnqueueMessages( attributes.append(TextEntitiesMessageAttribute(entities: entities)) } - var bubbleUpEmojiOrStickersetsById: [Int64: ItemCollectionId] = [:] + var bubbleUpEmojiOrStickersetsById: [Int64: EngineItemCollectionId] = [:] text.enumerateAttribute(ChatTextInputAttributes.customEmoji, in: NSRange(location: 0, length: text.length), using: { value, _, _ in if let value = value as? ChatTextInputTextCustomEmojiAttribute { if let file = value.file { @@ -1035,7 +993,7 @@ public func legacyAssetPickerEnqueueMessages( } } }) - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { if let packId = bubbleUpEmojiOrStickersetsById[fileId] { @@ -1070,7 +1028,7 @@ public func legacyAssetPickerEnqueueMessages( } if let paidMessage { - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if !paidMessage.entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: paidMessage.entities)) } diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index f10578a5ff..bdf89b9fc7 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -13,6 +13,7 @@ import SolidRoundedButtonNode import MediaEditor import DrawingUI import TelegramPresentationData +import ContextUI import AnimatedCountLabelNode import CoreMedia @@ -571,15 +572,118 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender } } +public typealias LegacyMediaPickerSendActionMenuPresenter = ( + UIView, + Bool, + Bool, + Bool, + Bool, + Bool, + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void, + @escaping () -> Void +) -> Bool + +private final class LegacyMediaPickerSendActionMenuReferenceContentSource: ContextReferenceContentSource { + private weak var sourceView: UIView? + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + guard let sourceView = self.sourceView else { + return nil + } + return ContextControllerReferenceViewInfo(referenceView: sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top) + } +} + +public func makeLegacyMediaPickerSendActionMenuPresenter( + context: AccountContext, + presentationData: PresentationData? = nil, + presentInGlobalOverlay: ((ViewController) -> Void)? = nil +) -> LegacyMediaPickerSendActionMenuPresenter { + return { sourceView, canSendSilently, canSendWhenOnline, canSchedule, reminder, hasTimer, sendSilently, sendWhenOnline, schedule, sendWithTimer in + guard sourceView.window != nil else { + return false + } + + let presentationData = (presentationData ?? context.sharedContext.currentPresentationData.with { $0 }).withUpdated(theme: defaultDarkPresentationTheme) + var items: [ContextMenuItem] = [] + + if canSendSilently { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + sendSilently() + }))) + } + + if canSendWhenOnline { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + sendWhenOnline() + }))) + } + + if canSchedule { + items.append(.action(ContextMenuActionItem(text: reminder ? presentationData.strings.Conversation_SendMessage_SetReminder : presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + schedule() + }))) + } + + if hasTimer { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Timer_Send, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + sendWithTimer() + }))) + } + + guard !items.isEmpty else { + return false + } + + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(LegacyMediaPickerSendActionMenuReferenceContentSource(sourceView: sourceView)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + if let presentInGlobalOverlay { + presentInGlobalOverlay(contextController) + } else if let mainWindow = context.sharedContext.mainWindow { + mainWindow.presentInGlobalOverlay(contextController) + } else { + context.sharedContext.presentGlobalController(contextController, nil) + } + return true + } +} + public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { public var captionPanelView: (() -> TGCaptionPanelView?)? public var livePhotoButton: (() -> TGLivePhotoButton?)? + public var photoToolbarView: ((TGPhotoEditorBackButton, TGPhotoEditorDoneButton, Bool, Bool) -> (UIView & TGPhotoToolbarViewProtocol)?)? + public var presentMediaPickerSendActionMenu: LegacyMediaPickerSendActionMenuPresenter? public var editCover: ((CGSize, @escaping (UIImage) -> Void) -> Void)? - + private let context: AccountContext - + public init(context: AccountContext) { self.context = context + super.init() + + self.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context) } class LegacyDrawingAdapter: NSObject, TGPhotoDrawingAdapter { diff --git a/submodules/LegacyUI/BUILD b/submodules/LegacyUI/BUILD index 57bdeb0623..9fd41d27b1 100644 --- a/submodules/LegacyUI/BUILD +++ b/submodules/LegacyUI/BUILD @@ -12,9 +12,9 @@ swift_library( deps = [ "//submodules/SSignalKit/SSignalKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/ContextUI:ContextUI", "//submodules/Display:Display", "//submodules/AccountContext:AccountContext", "//submodules/TelegramAudio:TelegramAudio", diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift index 0259dd86b6..dae16ea446 100644 --- a/submodules/LegacyUI/Sources/LegacyController.swift +++ b/submodules/LegacyUI/Sources/LegacyController.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import ContextUI import Display import SSignalKit import SwiftSignalKit @@ -28,6 +29,18 @@ private func passControllerAppearanceAnimated(in: Bool, presentation: LegacyCont } } +private final class LegacyActionSheetContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top) + } +} + private final class LegacyComponentsOverlayWindowManagerImpl: NSObject, LegacyComponentsOverlayWindowManager { private weak var contentController: UIViewController? private weak var parentController: ViewController? @@ -260,11 +273,80 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext { } public func presentActionSheet(_ actions: [LegacyComponentsActionSheetAction]!, view: UIView!, completion: ((LegacyComponentsActionSheetAction?) -> Void)!) { - + self.presentActionSheet(actions, view: view, sourceRect: nil, completion: completion) } public func presentActionSheet(_ actions: [LegacyComponentsActionSheetAction]!, view: UIView!, sourceRect: (() -> CGRect)!, completion: ((LegacyComponentsActionSheetAction?) -> Void)!) { + guard let controller = self.controller, let view = view else { + completion?(nil) + return + } + let presentationData: PresentationData + if let context = legacyContextGet() { + presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + } else { + presentationData = defaultPresentationData().withUpdated(theme: defaultDarkColorPresentationTheme) + } + + let anchorView: UIView? + let referenceView: UIView + if let sourceRect = sourceRect { + let anchor = UIView(frame: sourceRect()) + anchor.isUserInteractionEnabled = false + anchor.backgroundColor = .clear + view.addSubview(anchor) + anchorView = anchor + referenceView = anchor + } else { + anchorView = nil + referenceView = view + } + + var didSelectAction = false + var items: [ContextMenuItem] = [] + for legacyAction in actions ?? [] { + if legacyAction.type == LegacyComponentsActionSheetActionTypeCancel { + continue + } + guard let title = legacyAction.title else { + continue + } + let textColor: ContextMenuActionItemTextColor = legacyAction.type == LegacyComponentsActionSheetActionTypeDestructive ? .destructive : .primary + items.append(.action(ContextMenuActionItem(text: title, textColor: textColor, icon: { _ in + return nil + }, action: { actionContext in + didSelectAction = true + if let contextController = actionContext.controller { + contextController.dismiss(result: .default, completion: { + completion?(legacyAction) + }) + } else { + anchorView?.removeFromSuperview() + completion?(legacyAction) + } + }))) + } + + if items.isEmpty { + anchorView?.removeFromSuperview() + completion?(nil) + return + } + + let contextController = makeContextController( + context: legacyContextGet(), + presentationData: presentationData, + source: .reference(LegacyActionSheetContextReferenceContentSource(sourceView: referenceView)), + items: .single(ContextController.Items(content: .list(items))) + ) + contextController.dismissed = { [weak anchorView] in + anchorView?.removeFromSuperview() + if !didSelectAction { + completion?(nil) + } + } + controller.present(contextController, in: .window(.root)) } public func presentTooltip(_ text: String!, icon: UIImage!, sourceRect: CGRect) { @@ -680,3 +762,50 @@ open class LegacyController: ViewController, PresentableController { } } } + +extension LegacyController: KeyShortcutResponder { + public var keyShortcuts: [KeyShortcut] { + guard TGKeyCommandController.keyCommandsSupported(), let legacyController = self.legacyController as? TGViewController else { + return [] + } + + var shortcuts: [KeyShortcut] = [] + var hasExclusiveResponder = false + + legacyController.enumerateChildViewControllersRecursively { viewController in + if hasExclusiveResponder { + return + } + + guard let viewController = viewController, let responder = viewController as? TGKeyCommandResponder else { + return + } + + if responder.isExclusive?() == true { + hasExclusiveResponder = true + shortcuts.removeAll() + } + + guard let commands = responder.availableKeyCommands() as? [TGKeyCommand] else { + return + } + + for command in commands { + let title = command.title ?? "" + let input = command.input ?? "" + let modifiers = command.modifierFlags + let shortcut = KeyShortcut(title: title, input: input, modifiers: modifiers, action: { [weak viewController] in + guard let responder = viewController as? TGKeyCommandResponder else { + return + } + let keyCommand = UIKeyCommand(input: input, modifierFlags: modifiers, action: #selector(TGKeyCommandResponder.processKeyCommand(_:))) + responder.processKeyCommand(keyCommand) + }) + shortcuts.removeAll(where: { $0 == shortcut }) + shortcuts.append(shortcut) + } + } + + return shortcuts + } +} diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 75d4055d15..83543e5cf4 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -121,7 +120,7 @@ private struct FetchControls { } private enum FileIconImage: Equatable { - case imageRepresentation(Media, TelegramMediaImageRepresentation) + case imageRepresentation(EngineRawMedia, TelegramMediaImageRepresentation) case albumArt(TelegramMediaFile, SharedMediaPlaybackAlbumArt) case roundVideo(TelegramMediaFile) @@ -171,7 +170,7 @@ final class CachedChatListSearchResult { } } -private func selectStoryMedia(item: Stories.Item, preferredHighQuality: Bool) -> Media? { +private func selectStoryMedia(item: Stories.Item, preferredHighQuality: Bool) -> EngineRawMedia? { if !preferredHighQuality, let alternativeMediaValue = item.alternativeMediaList.first { return alternativeMediaValue } else { @@ -372,11 +371,11 @@ public final class ListMessageFileItemNode: ListMessageNode { private let restrictionNode: ASDisplayNode private var currentIconImage: FileIconImage? - public var currentMedia: Media? + public var currentMedia: EngineRawMedia? private let statusDisposable = MetaDisposable() private let fetchControls = Atomic(value: nil) - private var fetchStatus: MediaResourceStatus? + private var fetchStatus: EngineMediaResourceStatus? private var resourceStatus: FileMediaResourceMediaStatus? private let fetchDisposable = MetaDisposable() private let playbackStatusDisposable = MetaDisposable() @@ -389,7 +388,7 @@ public final class ListMessageFileItemNode: ListMessageNode { private var absoluteLocation: (CGRect, CGSize)? private var context: AccountContext? - private(set) var message: Message? + private(set) var message: EngineRawMessage? private var appliedItem: ListMessageItem? private var layoutParams: ListViewItemLayoutParams? @@ -681,9 +680,9 @@ public final class ListMessageFileItemNode: ListMessageNode { var descriptionExtraData: (title: NSAttributedString, showIcon: Bool, iconId: Int64?, iconColor: Int32)? = nil var globalAuthorTitle: String? - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let message = message { - var effectiveMessageMedia = message.media + var effectiveMessageMedia = message.effectiveMedia for media in message.media { if let storyMedia = media as? TelegramMediaStory { if let story = message.associatedStories[storyMedia.storyId], !story.data.isEmpty, case let .item(storyItem) = story.get(Stories.StoredItem.self), let media = selectStoryMedia(item: storyItem, preferredHighQuality: item.interaction.preferredStoryHighQuality) { @@ -1093,7 +1092,7 @@ public final class ListMessageFileItemNode: ListMessageNode { updateIconImageSignal = .complete() } case let .albumArt(file, albumArt): - updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, engine: item.context.engine, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor) + updateIconImageSignal = playerAlbumArt(engine: item.context.engine, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor) case let .roundVideo(file): updateIconImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: FileMediaReference.message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3)) } @@ -1526,7 +1525,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } } - override public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, let message = item.message, message.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in @@ -1559,7 +1558,7 @@ public final class ListMessageFileItemNode: ListMessageNode { var downloadingString: String? if let resourceStatus = self.resourceStatus, !item.isAttachMusic { - var maybeFetchStatus: MediaResourceStatus = .Local + var maybeFetchStatus: EngineMediaResourceStatus = .Local switch resourceStatus { case .playbackStatus: break diff --git a/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift b/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift index f4960900a3..95850378d5 100644 --- a/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageHoleItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox final class ListMessageHoleItem: ListViewItem { public init() { @@ -103,7 +102,7 @@ final class ListMessageHoleItemNode: ListViewItemNode { } } - func transitionNode(id: MessageId, media: Media) -> ASDisplayNode? { + func transitionNode(id: EngineMessage.Id, media: EngineMedia) -> ASDisplayNode? { return nil } diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index 3a764bd287..fd5296891a 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -4,34 +4,33 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import TelegramUIPreferences import ItemListUI public final class ListMessageItemInteraction { - public let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool - public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void - public let toggleMessagesSelection: ([MessageId], Bool) -> Void - public let toggleMediaPlayback: ((Message) -> Void)? - let openUrl: (String, Bool, Bool?, Message?) -> Void - let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void - let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void - let getHiddenMedia: () -> [MessageId: [Media]] + public let openMessage: (EngineRawMessage, ChatControllerInteractionOpenMessageMode) -> Bool + public let openMessageContextMenu: (EngineRawMessage, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void + public let toggleMessagesSelection: ([EngineMessage.Id], Bool) -> Void + public let toggleMediaPlayback: ((EngineRawMessage) -> Void)? + let openUrl: (String, Bool, Bool?, EngineRawMessage?) -> Void + let openInstantPage: (EngineRawMessage, ChatMessageItemAssociatedData?) -> Void + let longTap: (ChatControllerInteractionLongTapAction, EngineRawMessage?) -> Void + let getHiddenMedia: () -> [EngineMessage.Id: [EngineRawMedia]] public var searchTextHighightState: String? public var preferredStoryHighQuality: Bool = false public init( - openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, - openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, - toggleMediaPlayback: ((Message) -> Void)?, - toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, - openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, - openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, - longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, - getHiddenMedia: @escaping () -> [MessageId: [Media]] + openMessage: @escaping (EngineRawMessage, ChatControllerInteractionOpenMessageMode) -> Bool, + openMessageContextMenu: @escaping (EngineRawMessage, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, + toggleMediaPlayback: ((EngineRawMessage) -> Void)?, + toggleMessagesSelection: @escaping ([EngineMessage.Id], Bool) -> Void, + openUrl: @escaping (String, Bool, Bool?, EngineRawMessage?) -> Void, + openInstantPage: @escaping (EngineRawMessage, ChatMessageItemAssociatedData?) -> Void, + longTap: @escaping (ChatControllerInteractionLongTapAction, EngineRawMessage?) -> Void, + getHiddenMedia: @escaping () -> [EngineMessage.Id: [EngineRawMedia]] ) { self.openMessage = openMessage self.openMessageContextMenu = openMessageContextMenu @@ -51,7 +50,7 @@ public final class ListMessageItemInteraction { openUrl: { _, _, _, _ in }, openInstantPage: { _, _ in }, longTap: { _, _ in - }, getHiddenMedia: { () -> [MessageId : [Media]] in + }, getHiddenMedia: { () -> [EngineMessage.Id : [EngineRawMedia]] in return [:] }) } @@ -67,7 +66,7 @@ public final class ListMessageItem: ListViewItem, ItemListItem { let context: AccountContext let chatLocation: ChatLocation let interaction: ListMessageItemInteraction - let message: Message? + let message: EngineRawMessage? let translateToLanguage: String? public let selection: ChatHistoryMessageSelection public let selectionSide: ListMessageItemSelectionSide @@ -94,7 +93,7 @@ public final class ListMessageItem: ListViewItem, ItemListItem { context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, - message: Message?, + message: EngineRawMessage?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, selectionSide: ListMessageItemSelectionSide = .right, @@ -146,7 +145,7 @@ public final class ListMessageItem: ListViewItem, ItemListItem { if !self.hintIsLink { if let message = self.message { - for media in message.media { + for media in message.effectiveMedia { if let _ = media as? TelegramMediaFile { viewClassName = ListMessageFileItemNode.self break diff --git a/submodules/ListMessageItem/Sources/ListMessageNode.swift b/submodules/ListMessageItem/Sources/ListMessageNode.swift index 19d1b2ee17..625908c2f7 100644 --- a/submodules/ListMessageItem/Sources/ListMessageNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageNode.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox +import TelegramCore import AccountContext public class ListMessageNode: ListViewItemNode { @@ -28,7 +28,7 @@ public class ListMessageNode: ListViewItemNode { } } - public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(id: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index b743aaf3fe..dfb3bb7f37 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -57,7 +56,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { private let iconImageNode: TransformImageNode private var currentIconImageRepresentation: TelegramMediaImageRepresentation? - private var currentMedia: Media? + private var currentMedia: EngineRawMedia? public var currentPrimaryUrl: String? private var currentIsInstantView: Bool? @@ -861,7 +860,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { } } - override public func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(id: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let item = self.item, item.message?.id == id, self.iconImageNode.supernode != nil { let iconImageNode = self.iconImageNode return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in diff --git a/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift index ce767dadd3..d5aff40e4f 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationSummaryManager.swift @@ -167,7 +167,7 @@ private final class LiveLocationPeerSummaryContext { for message in messages { if let author = message.author { if author.id != strongSelf.accountPeerId && message.flags.contains(.Incoming) { - peersAndMessages.append((EnginePeer(author), EngineMessage(message))) + peersAndMessages.append((author, message)) } } } diff --git a/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift b/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift index c8d8e43912..0a5f677cf3 100644 --- a/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift +++ b/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift @@ -4,6 +4,10 @@ import AsyncDisplayKit import Display import TelegramPresentationData +private let compactInfinityFont = Font.with(size: 14.0, design: .round, weight: .bold) +private let compactTextFont = Font.with(size: 12.0, design: .round, weight: .bold) +private let compactSmallTextFont = Font.with(size: 10.0, design: .round, weight: .bold) + private let infinityFont = Font.with(size: 15.0, design: .round, weight: .bold) private let textFont = Font.with(size: 13.0, design: .round, weight: .bold) private let smallTextFont = Font.with(size: 11.0, design: .round, weight: .bold) @@ -134,11 +138,11 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode { let font: UIFont if parameters.string == "∞" { - font = infinityFont + font = bounds.width < 28.0 ? compactInfinityFont : infinityFont } else if parameters.string.count > 2 { - font = smallTextFont + font = bounds.width < 28.0 ? compactSmallTextFont : smallTextFont } else { - font = textFont + font = bounds.width < 28.0 ? compactTextFont : textFont } let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: parameters.foregroundColor] @@ -147,7 +151,7 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode { var offset = CGPoint() if parameters.string == "∞" { - offset = CGPoint(x: 1.0, y: -1.0) + offset = bounds.width < 28.0 ? CGPoint(x: 1.0 - UIScreenPixel, y: 0.0) : CGPoint(x: 1.0, y: -1.0) } else if parameters.string.count > 2 { offset = CGPoint(x: 0.0, y: UIScreenPixel) } diff --git a/submodules/LocalMediaResources/BUILD b/submodules/LocalMediaResources/BUILD index b0f3f832fe..cc0d5b719a 100644 --- a/submodules/LocalMediaResources/BUILD +++ b/submodules/LocalMediaResources/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/ImageCompression:ImageCompression", "//submodules/PersistentStringHash:PersistentStringHash", diff --git a/submodules/LocalMediaResources/Sources/FetchPhotoLibraryImageResource.swift b/submodules/LocalMediaResources/Sources/FetchPhotoLibraryImageResource.swift index f19199c1aa..fe3d654156 100644 --- a/submodules/LocalMediaResources/Sources/FetchPhotoLibraryImageResource.swift +++ b/submodules/LocalMediaResources/Sources/FetchPhotoLibraryImageResource.swift @@ -1,8 +1,8 @@ import Foundation import UIKit import Photos -import Postbox import SwiftSignalKit +import TelegramCore import ImageCompression import Accelerate.vImage import CoreImage @@ -104,7 +104,7 @@ extension UIImage.Orientation { private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2) -public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, height: Int32?, format: MediaImageFormat?, quality: Int32?, hd: Bool, useExif: Bool) -> Signal { +public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, height: Int32?, format: MediaImageFormat?, quality: Int32?, hd: Bool, useExif: Bool) -> Signal { return Signal { subscriber in let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers) @@ -178,9 +178,9 @@ public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, he switch format { case .none, .jpeg: - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6, tempFilePath: tempFile.path) { #if DEBUG diff --git a/submodules/LocalMediaResources/Sources/MediaResources.swift b/submodules/LocalMediaResources/Sources/MediaResources.swift index 6e4d1490bf..9cf14404ee 100644 --- a/submodules/LocalMediaResources/Sources/MediaResources.swift +++ b/submodules/LocalMediaResources/Sources/MediaResources.swift @@ -1,27 +1,26 @@ import Foundation import UIKit -import Postbox import TelegramCore import PersistentStringHash -public final class VideoMediaResourceAdjustments: PostboxCoding, Equatable { - public let data: MemoryBuffer - public let digest: MemoryBuffer +public final class VideoMediaResourceAdjustments: EnginePostboxCoding, Equatable { + public let data: EngineMemoryBuffer + public let digest: EngineMemoryBuffer public let isStory: Bool - public init(data: MemoryBuffer, digest: MemoryBuffer, isStory: Bool = false) { + public init(data: EngineMemoryBuffer, digest: EngineMemoryBuffer, isStory: Bool = false) { self.data = data self.digest = digest self.isStory = isStory } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.data = decoder.decodeBytesForKey("d")! self.digest = decoder.decodeBytesForKey("h")! self.isStory = decoder.decodeBoolForKey("s", orElse: false) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeBytes(self.data, forKey: "d") encoder.encodeBytes(self.digest, forKey: "h") encoder.encodeBool(self.isStory, forKey: "s") @@ -34,7 +33,7 @@ public final class VideoMediaResourceAdjustments: PostboxCoding, Equatable { public struct VideoLibraryMediaResourceId { public let localIdentifier: String - public let adjustmentsDigest: MemoryBuffer? + public let adjustmentsDigest: EngineMemoryBuffer? public var uniqueId: String { if let adjustmentsDigest = self.adjustmentsDigest { @@ -49,11 +48,11 @@ public struct VideoLibraryMediaResourceId { } } -public enum VideoLibraryMediaResourceConversion: PostboxCoding, Equatable { +public enum VideoLibraryMediaResourceConversion: EnginePostboxCoding, Equatable { case passthrough case compress(VideoMediaResourceAdjustments?) - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { case 0: self = .passthrough @@ -64,7 +63,7 @@ public enum VideoLibraryMediaResourceConversion: PostboxCoding, Equatable { } } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { switch self { case .passthrough: encoder.encodeInt32(0, forKey: "v") @@ -113,28 +112,28 @@ public final class VideoLibraryMediaResource: TelegramMediaResource { self.conversion = conversion } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.localIdentifier = decoder.decodeStringForKey("i", orElse: "") self.conversion = (decoder.decodeObjectForKey("conv", decoder: { VideoLibraryMediaResourceConversion(decoder: $0) }) as? VideoLibraryMediaResourceConversion) ?? .compress(nil) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.localIdentifier, forKey: "i") encoder.encodeObject(self.conversion, forKey: "conv") } - public var id: MediaResourceId { - var adjustmentsDigest: MemoryBuffer? + public var id: EngineRawMediaResourceId { + var adjustmentsDigest: EngineMemoryBuffer? switch self.conversion { case .passthrough: break case let .compress(adjustments): adjustmentsDigest = adjustments?.digest } - return MediaResourceId(VideoLibraryMediaResourceId(localIdentifier: self.localIdentifier, adjustmentsDigest: adjustmentsDigest).uniqueId) + return EngineRawMediaResourceId(VideoLibraryMediaResourceId(localIdentifier: self.localIdentifier, adjustmentsDigest: adjustmentsDigest).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? VideoLibraryMediaResource { return self.localIdentifier == to.localIdentifier && self.conversion == to.conversion } else { @@ -180,7 +179,7 @@ public final class LocalFileVideoMediaResource: TelegramMediaResource { self.adjustments = adjustments } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) let paths = decoder.decodeStringArrayForKey("ps") if !paths.isEmpty { @@ -191,7 +190,7 @@ public final class LocalFileVideoMediaResource: TelegramMediaResource { self.adjustments = decoder.decodeObjectForKey("a", decoder: { VideoMediaResourceAdjustments(decoder: $0) }) as? VideoMediaResourceAdjustments } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeInt64(self.randomId, forKey: "i") encoder.encodeStringArray(self.paths, forKey: "ps") if let adjustments = self.adjustments { @@ -201,11 +200,11 @@ public final class LocalFileVideoMediaResource: TelegramMediaResource { } } - public var id: MediaResourceId { - return MediaResourceId(LocalFileVideoMediaResourceId(randomId: self.randomId).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(LocalFileVideoMediaResourceId(randomId: self.randomId).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? LocalFileVideoMediaResource { return self.randomId == to.randomId && self.paths == to.paths && self.adjustments == to.adjustments } else { @@ -245,7 +244,7 @@ public final class LocalFileAudioMediaResource: TelegramMediaResource { self.trimRange = trimRange } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) self.path = decoder.decodeStringForKey("p", orElse: "") @@ -256,7 +255,7 @@ public final class LocalFileAudioMediaResource: TelegramMediaResource { } } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeInt64(self.randomId, forKey: "i") encoder.encodeString(self.path, forKey: "p") @@ -269,11 +268,11 @@ public final class LocalFileAudioMediaResource: TelegramMediaResource { } } - public var id: MediaResourceId { - return MediaResourceId(LocalFileAudioMediaResourceId(randomId: self.randomId).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(LocalFileAudioMediaResourceId(randomId: self.randomId).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? LocalFileAudioMediaResource { return self.randomId == to.randomId && self.path == to.path && self.trimRange == to.trimRange } else { @@ -327,7 +326,7 @@ public class PhotoLibraryMediaResource: TelegramMediaResource { self.forceHd = forceHd } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.localIdentifier = decoder.decodeStringForKey("i", orElse: "") self.uniqueId = decoder.decodeInt64ForKey("uid", orElse: 0) self.width = decoder.decodeOptionalInt32ForKey("w") @@ -337,7 +336,7 @@ public class PhotoLibraryMediaResource: TelegramMediaResource { self.forceHd = decoder.decodeBoolForKey("hd", orElse: false) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.localIdentifier, forKey: "i") encoder.encodeInt64(self.uniqueId, forKey: "uid") if let width = self.width { @@ -363,11 +362,11 @@ public class PhotoLibraryMediaResource: TelegramMediaResource { encoder.encodeBool(self.forceHd, forKey: "hd") } - public var id: MediaResourceId { - return MediaResourceId(PhotoLibraryMediaResourceId(localIdentifier: self.localIdentifier, resourceId: self.uniqueId).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(PhotoLibraryMediaResourceId(localIdentifier: self.localIdentifier, resourceId: self.uniqueId).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? PhotoLibraryMediaResource { if self.localIdentifier != to.localIdentifier { return false @@ -426,21 +425,21 @@ public final class LocalFileGifMediaResource: TelegramMediaResource { self.path = path } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.randomId = decoder.decodeInt64ForKey("i", orElse: 0) self.path = decoder.decodeStringForKey("p", orElse: "") } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeInt64(self.randomId, forKey: "i") encoder.encodeString(self.path, forKey: "p") } - public var id: MediaResourceId { - return MediaResourceId(LocalFileGifMediaResourceId(randomId: self.randomId).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(LocalFileGifMediaResourceId(randomId: self.randomId).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? LocalFileGifMediaResource { return self.randomId == to.randomId && self.path == to.path } else { @@ -475,21 +474,21 @@ public class BundleResource: TelegramMediaResource { self.path = path } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.nameHash = decoder.decodeInt64ForKey("h", orElse: 0) self.path = decoder.decodeStringForKey("p", orElse: "") } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeInt64(self.nameHash, forKey: "h") encoder.encodeString(self.path, forKey: "p") } - public var id: MediaResourceId { - return MediaResourceId(BundleResourceId(nameHash: self.nameHash).uniqueId) + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(BundleResourceId(nameHash: self.nameHash).uniqueId) } - public func isEqual(to: MediaResource) -> Bool { + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? BundleResource { return self.nameHash == to.nameHash } else { diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index eb9f0aee55..39d33fd6d0 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -13,10 +13,10 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/LegacyComponents:LegacyComponents", + "//submodules/ContextUI:ContextUI", "//submodules/ShareController:ShareController", "//submodules/AccountContext:AccountContext", "//submodules/OpenInExternalAppUI:OpenInExternalAppUI", @@ -42,13 +42,20 @@ swift_library( "//submodules/TelegramNotices:TelegramNotices", "//submodules/TooltipUI:TooltipUI", "//submodules/UndoUI:UndoUI", + "//submodules/Weather", "//submodules/AttachmentUI:AttachmentUI", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/ComponentFlow", "//submodules/Components/BundleIconComponent", + "//submodules/Components/SheetComponent", + "//submodules/Components/ViewControllerComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/GlassControls", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/SearchInputPanelComponent", "//submodules/TelegramUI/Components/ButtonComponent", diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index ea37651e03..a4c5121a60 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -141,16 +141,18 @@ final class LocationActionListItem: ListViewItem { let title: String let subtitle: String let icon: LocationActionListItemIcon + let isOpaque: Bool let beginTimeAndTimeout: (Double, Double)? let action: () -> Void let highlighted: (Bool) -> Void - public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) { + public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, isOpaque: Bool = true, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) { self.presentationData = presentationData self.engine = engine self.title = title self.subtitle = subtitle self.icon = icon + self.isOpaque = isOpaque self.beginTimeAndTimeout = beginTimeAndTimeout self.action = action self.highlighted = highlighted @@ -216,6 +218,7 @@ final class LocationActionListItemNode: ListViewItemNode { self.separatorNode.isLayerBacked = true self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.clipsToBounds = true self.highlightedBackgroundNode.isLayerBacked = true self.iconNode = ASImageNode() @@ -232,6 +235,21 @@ final class LocationActionListItemNode: ListViewItemNode { self.addSubnode(self.venueIconNode) } + func liveLocationContextSourceView(extend: Bool) -> UIView? { + guard let icon = self.item?.icon else { + return nil + } + + switch icon { + case .liveLocation: + return extend ? nil : self.view + case .extendLiveLocation: + return extend ? self.view : nil + default: + return nil + } + } + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { if let item = self.item { let makeLayout = self.asyncLayout() @@ -278,7 +296,7 @@ final class LocationActionListItemNode: ListViewItemNode { let iconLayout = self.venueIconNode.asyncLayout() return { [weak self] item, params, hasSeparator in - let leftInset: CGFloat = 65.0 + params.leftInset + let leftInset: CGFloat = (item.isOpaque ? 65.0 : 72.0 ) + params.leftInset let rightInset: CGFloat = params.rightInset let verticalInset: CGFloat = 8.0 let iconSize: CGFloat = 40.0 @@ -292,7 +310,7 @@ final class LocationActionListItemNode: ListViewItemNode { let subtitleAttributedString = NSAttributedString(string: item.subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let titleSpacing: CGFloat = 1.0 + let titleSpacing: CGFloat = 0.0 let bottomInset: CGFloat = hasSeparator ? 0.0 : 4.0 var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset) if hasSeparator { @@ -300,6 +318,8 @@ final class LocationActionListItemNode: ListViewItemNode { } let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) + var hasSeparator = hasSeparator + return (nodeLayout, { [weak self] in var updatedTheme: PresentationTheme? if currentItem?.presentationData.theme !== item.presentationData.theme { @@ -315,11 +335,15 @@ final class LocationActionListItemNode: ListViewItemNode { if let strongSelf = self { strongSelf.item = item strongSelf.layoutParams = params - + if let _ = updatedTheme { strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor - strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + if item.isOpaque { + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + } else { + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.contextMenu.itemHighlightedBackgroundColor + } } var arguments: TransformImageCustomArguments? @@ -394,15 +418,34 @@ final class LocationActionListItemNode: ListViewItemNode { let topHighlightInset: CGFloat = separatorHeight let separatorRightInset: CGFloat = 16.0 - let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)) + let contentLeftInset: CGFloat = item.isOpaque ? 0.0 : 7.0 + + let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0 + contentLeftInset, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)) strongSelf.iconNode.frame = iconNodeFrame strongSelf.venueIconNode.frame = iconNodeFrame - strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0)) + strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0 + contentLeftInset, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) + + let highlightFrame: CGRect + let highlightCornerRadius: CGFloat + if item.isOpaque { + highlightFrame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) + highlightCornerRadius = 0.0 + } else { + highlightFrame = CGRect(origin: CGPoint(x: 14.0, y: 2.0), size: CGSize(width: contentSize.width - 14.0 * 2.0, height: 52.0)) + highlightCornerRadius = highlightFrame.height * 0.5 + } + + strongSelf.highlightedBackgroundNode.frame = highlightFrame + strongSelf.highlightedBackgroundNode.cornerRadius = highlightCornerRadius strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight)) + + if !item.isOpaque { + hasSeparator = false + } + strongSelf.backgroundNode.isHidden = !item.isOpaque strongSelf.separatorNode.isHidden = !hasSeparator if let (beginTimestamp, timeout) = item.beginTimeAndTimeout { @@ -414,9 +457,9 @@ final class LocationActionListItemNode: ListViewItemNode { strongSelf.addSubnode(timerNode) strongSelf.timerNode = timerNode } - let timerSize = CGSize(width: 28.0, height: 28.0) + let timerSize = CGSize(width: 24.0, height: 24.0) timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: Int32(timeout) == liveLocationIndefinitePeriod ? -1.0 : timeout, strings: item.presentationData.strings) - timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize) + timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 15.0 - contentLeftInset - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize) } else if let timerNode = strongSelf.timerNode { strongSelf.timerNode = nil timerNode.removeFromSupernode() diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index a9957db257..207e31cbc6 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -177,6 +177,7 @@ public class LocationPinAnnotationView: MKAnnotationView { var hasPulse = false var headingKvoToken: NSKeyValueObservation? + private var mapHeading: CGFloat = 0.0 override public class var layerClass: AnyClass { return LocationPinAnnotationLayer.self @@ -291,12 +292,10 @@ public class LocationPinAnnotationView: MKAnnotationView { headingKvoToken.invalidate() } - self.headingKvoToken = annotation.observe(\.heading, options: .new) { [weak self] (_, change) in - guard let heading = change.newValue else { - return - } - self?.updateHeading(heading) + self.headingKvoToken = annotation.observe(\.heading, options: .new) { [weak self] (_, _) in + self?.updateHeading() } + self.updateHeading() } else if let peer = annotation.peer { self.iconNode.isHidden = true @@ -310,7 +309,7 @@ public class LocationPinAnnotationView: MKAnnotationView { self.headingKvoToken = nil headingKvoToken.invalidate() } - self.updateHeading(nil) + self.updateHeading() } else if let location = annotation.location { let venueType = location.venue?.type ?? "" let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType) @@ -347,16 +346,23 @@ public class LocationPinAnnotationView: MKAnnotationView { self.headingKvoToken = nil headingKvoToken.invalidate() } - self.updateHeading(nil) + self.updateHeading() } } } } - private func updateHeading(_ heading: NSNumber?) { - if let heading = heading?.int32Value { + func updateMapHeading(_ mapHeading: CGFloat) { + if self.mapHeading != mapHeading { + self.mapHeading = mapHeading + self.updateHeading() + } + } + + private func updateHeading() { + if let heading = (self.annotation as? LocationPinAnnotation)?.heading?.doubleValue { self.arrowNode.isHidden = false - self.arrowNode.transform = CATransform3DMakeRotation(CGFloat(heading) / 180.0 * CGFloat.pi, 0.0, 0.0, 1.0) + self.arrowNode.transform = CATransform3DMakeRotation((CGFloat(heading) - self.mapHeading) / 180.0 * CGFloat.pi, 0.0, 0.0, 1.0) } else { self.arrowNode.isHidden = true self.arrowNode.transform = CATransform3DIdentity diff --git a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift index 3547383093..8e56deae75 100644 --- a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift +++ b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift @@ -1,123 +1,84 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import TelegramCore import SwiftSignalKit import AccountContext -import SolidRoundedButtonNode import TelegramPresentationData import TelegramStringFormatting import PresentationDataUtils -import CoreLocation +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import ButtonComponent +import GlassBarButtonComponent +import AnimatedTextComponent +import BundleIconComponent enum LocationDistancePickerScreenStyle { case `default` case media } -final class LocationDistancePickerScreen: ViewController { - private var controllerNode: LocationDistancePickerScreenNode { - return self.displayNode as! LocationDistancePickerScreenNode - } - - private var animatedIn = false - - private let context: AccountContext - private let style: LocationDistancePickerScreenStyle - private let distances: Signal<[Double], NoError> - private let compactDisplayTitle: String? - private let updated: (Int32?) -> Void - private let completion: (Int32, @escaping () -> Void) -> Void - private let willDismiss: () -> Void - - private var presentationDataDisposable: Disposable? +final class LocationDistancePickerScreen: ViewControllerComponentContainer { + private let willDismissImpl: () -> Void + private var didCallWillDismiss = false init(context: AccountContext, style: LocationDistancePickerScreenStyle, compactDisplayTitle: String?, distances: Signal<[Double], NoError>, updated: @escaping (Int32?) -> Void, completion: @escaping (Int32, @escaping () -> Void) -> Void, willDismiss: @escaping () -> Void) { - self.context = context - self.style = style - self.distances = distances - self.compactDisplayTitle = compactDisplayTitle - self.updated = updated - self.completion = completion - self.willDismiss = willDismiss + self.willDismissImpl = willDismiss - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore + super.init( + context: context, + component: LocationDistancePickerScreenComponent( + context: context, + style: style, + compactDisplayTitle: compactDisplayTitle, + distances: distances, + updated: { distance in + updated(distance) + }, + completion: completion, + willDismiss: willDismiss + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore + ) self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = (context.sharedContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - - self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - deinit { - self.presentationDataDisposable?.dispose() - } - - override public func loadDisplayNode() { - self.displayNode = LocationDistancePickerScreenNode(context: self.context, style: self.style, compactDisplayTitle: self.compactDisplayTitle, distances: self.distances) - self.controllerNode.updated = { [weak self] distance in - guard let strongSelf = self else { - return - } - strongSelf.updated(distance) - } - self.controllerNode.completion = { [weak self] distance in - guard let strongSelf = self else { - return - } - strongSelf.completion(distance, { - strongSelf.dismiss() - }) - } - self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - self?.dismiss() - } - - let _ = self.controllerNode.update() - } - - override public func loadView() { - super.loadView() - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() + fileprivate func performWillDismissOnce() { + if self.didCallWillDismiss { + return } + self.didCallWillDismiss = true + self.willDismissImpl() } override public func dismiss(completion: (() -> Void)? = nil) { - self.willDismiss() - self.controllerNode.animateOut(completion: completion) + if let componentView = self.node.hostView.componentView as? LocationDistancePickerScreenComponent.View { + componentView.requestDismiss(completion: completion) + } else { + self.performWillDismissOnce() + super.dismiss(animated: false, completion: completion) + } } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + if flag { + self.dismiss(completion: completion) + } else { + super.dismiss(animated: false, completion: completion) + } } } -private class TimerPickerView: UIPickerView { +private final class TimerPickerView: UIPickerView { var selectorColor: UIColor? = nil { didSet { for subview in self.subviews { @@ -151,7 +112,7 @@ private class TimerPickerView: UIPickerView { } } -private var unitValues: [Int32] = { +private let unitValues: [Int32] = { var values: [Int32] = [] for i in 0 ..< 99 { values.append(Int32(i)) @@ -159,7 +120,7 @@ private var unitValues: [Int32] = { return values }() -private var smallUnitValues: [Int32] = { +private let smallUnitValues: [Int32] = { var values: [Int32] = [] values.append(0) values.append(5) @@ -169,213 +130,318 @@ private var smallUnitValues: [Int32] = { return values }() -class LocationDistancePickerScreenNode: ViewControllerTracingNode, ASScrollViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate { - private let context: AccountContext - private let controllerStyle: LocationDistancePickerScreenStyle - private var presentationData: PresentationData - private var compactDisplayTitle: String? - private var distances: [Double] = [] +private final class LocationDistancePickerScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let effectNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let textNode: ImmediateTextNode - private let cancelButton: HighlightableButtonNode - private let doneButton: SolidRoundedButtonNode + let context: AccountContext + let style: LocationDistancePickerScreenStyle + let compactDisplayTitle: String? + let distances: Signal<[Double], NoError> + let updated: (Int32) -> Void + let completion: (Int32, @escaping () -> Void) -> Void + let willDismiss: () -> Void - private let measureButtonTitleNode: ImmediateTextNode - - private var pickerView: TimerPickerView? - private let unitLabelNode: ImmediateTextNode - private let smallUnitLabelNode: ImmediateTextNode - - private var pickerTimer: SwiftSignalKit.Timer? - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - private var distancesDisposable: Disposable? - - var updated: ((Int32) -> Void)? - var completion: ((Int32) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, style: LocationDistancePickerScreenStyle, compactDisplayTitle: String?, distances: Signal<[Double], NoError>) { + init( + context: AccountContext, + style: LocationDistancePickerScreenStyle, + compactDisplayTitle: String?, + distances: Signal<[Double], NoError>, + updated: @escaping (Int32) -> Void, + completion: @escaping (Int32, @escaping () -> Void) -> Void, + willDismiss: @escaping () -> Void + ) { self.context = context - self.controllerStyle = style - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.style = style self.compactDisplayTitle = compactDisplayTitle - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 - - let backgroundColor: UIColor - let textColor: UIColor - let accentColor: UIColor - let blurStyle: UIBlurEffect.Style - switch style { - case .default: - backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - textColor = self.presentationData.theme.actionSheet.primaryTextColor - accentColor = self.presentationData.theme.actionSheet.controlAccentColor - blurStyle = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark - case .media: - backgroundColor = UIColor(rgb: 0x1c1c1e) - textColor = .white - accentColor = self.presentationData.theme.actionSheet.controlAccentColor - blurStyle = .dark - } - - self.effectNode = ASDisplayNode(viewBlock: { - return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) - }) - - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_Title, font: Font.bold(17.0), textColor: textColor) - - self.textNode = ImmediateTextNode() - self.textNode.alpha = 0.0 - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) - - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0) - self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send - - self.unitLabelNode = ImmediateTextNode() - self.smallUnitLabelNode = ImmediateTextNode() - - self.measureButtonTitleNode = ImmediateTextNode() - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.unitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceKM : self.presentationData.strings.Location_ProximityNotification_DistanceMI, font: Font.regular(15.0), textColor: textColor) - self.smallUnitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceM : "", font: Font.regular(15.0), textColor: textColor) - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - - self.backgroundNode.addSubnode(self.effectNode) - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.cancelButton) - self.contentContainerNode.addSubnode(self.doneButton) - - self.contentContainerNode.addSubnode(self.unitLabelNode) - self.contentContainerNode.addSubnode(self.smallUnitLabelNode) - - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.doneButton.pressed = { [weak self] in - if let strongSelf = self, let pickerView = strongSelf.pickerView { - strongSelf.doneButton.isUserInteractionEnabled = false - - let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)] - let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)] - var value = largeValue * 1000 + smallValue * 10 - if !strongSelf.usesMetricSystem { - value = Int32(Double(value) * 1.60934) - } - strongSelf.completion?(value) - } - } - - self.setupPickerView() - - self.distancesDisposable = (distances - |> deliverOnMainQueue).start(next: { [weak self] distances in - if let strongSelf = self { - strongSelf.distances = distances - strongSelf.updateDoneButtonTitle() - } - }) + self.distances = distances + self.updated = updated + self.completion = completion + self.willDismiss = willDismiss } - deinit { - self.distancesDisposable?.dispose() - - self.pickerTimer?.invalidate() - } - - func setupPickerView() { - if let pickerView = self.pickerView { - pickerView.removeFromSuperview() - } - - let pickerView = TimerPickerView() - pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18) - pickerView.dataSource = self - pickerView.delegate = self - pickerView.selectRow(0, inComponent: 0, animated: false) - - if self.usesMetricSystem { - pickerView.selectRow(6, inComponent: 1, animated: false) - } else { - pickerView.selectRow(4, inComponent: 1, animated: false) - } - self.contentContainerNode.view.addSubview(pickerView) - self.pickerView = pickerView - - self.contentContainerNode.addSubnode(self.unitLabelNode) - self.contentContainerNode.addSubnode(self.smallUnitLabelNode) - - self.pickerTimer?.invalidate() - - let pickerTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: true, completion: { [weak self] in - if let strongSelf = self { - if strongSelf.update() { - strongSelf.updateDoneButtonTitle() - } - } - }, queue: Queue.mainQueue()) - self.pickerTimer = pickerTimer - pickerTimer.start() - - self.updateDoneButtonTitle() - } - - private var usesMetricSystem: Bool { - let locale = localeWithStrings(self.presentationData.strings) - if locale.identifier.hasSuffix("GB") { + static func ==(lhs: LocationDistancePickerScreenComponent, rhs: LocationDistancePickerScreenComponent) -> Bool { + if lhs.context !== rhs.context { return false } - return locale.usesMetricSystem + if lhs.style != rhs.style { + return false + } + if lhs.compactDisplayTitle != rhs.compactDisplayTitle { + return false + } + return true } - func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 2 + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var component: LocationDistancePickerScreenComponent? + private var environment: ViewControllerComponentContainer.Environment? + private var isDismissed = false + private var didCallWillDismiss = false + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func performWillDismissOnce() { + if self.didCallWillDismiss { + return + } + self.didCallWillDismiss = true + + if let controller = self.environment?.controller() as? LocationDistancePickerScreen { + controller.performWillDismissOnce() + } else { + self.component?.willDismiss() + } + } + + func requestDismiss(completion: (() -> Void)? = nil) { + self.performWillDismissOnce() + + if self.isDismissed { + completion?() + return + } + self.isDismissed = true + + self.sheetAnimateOut.invoke(Action { [weak self] _ in + guard let self else { + completion?() + return + } + if let controller = self.environment?.controller() { + controller.dismiss(animated: false, completion: completion) + } else { + completion?() + } + }) + } + + func update(component: LocationDistancePickerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let environment = environment[EnvironmentType.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] _ in + self?.requestDismiss() + } + ) + + let backgroundColor: SheetComponent.BackgroundColor + switch component.style { + case .default: + backgroundColor = .color(environment.theme.list.modalPlainBackgroundColor) + case .media: + backgroundColor = .color(UIColor(rgb: 0x1c1c1e)) + } + + let _ = self.sheet.update( + transition: transition, + component: AnyComponent( + SheetComponent( + content: AnyComponent( + LocationDistancePickerContentComponent( + style: component.style, + compactDisplayTitle: component.compactDisplayTitle, + distances: component.distances, + updated: component.updated, + completion: { [weak self] distance in + guard let self, let component = self.component else { + return + } + component.completion(distance, { [weak self] in + self?.requestDismiss() + }) + }, + dismiss: { [weak self] in + self?.requestDismiss() + } + ) + ), + style: .glass, + backgroundColor: backgroundColor, + hasDimView: false, + animateOut: self.sheetAnimateOut, + willDismiss: { [weak self] in + self?.performWillDismissOnce() + } + ) + ), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + return availableSize + } } - private func updateDoneButtonTitle() { - if let pickerView = self.pickerView { + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class LocationDistancePickerContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let style: LocationDistancePickerScreenStyle + let compactDisplayTitle: String? + let distances: Signal<[Double], NoError> + let updated: (Int32) -> Void + let completion: (Int32) -> Void + let dismiss: () -> Void + + init( + style: LocationDistancePickerScreenStyle, + compactDisplayTitle: String?, + distances: Signal<[Double], NoError>, + updated: @escaping (Int32) -> Void, + completion: @escaping (Int32) -> Void, + dismiss: @escaping () -> Void + ) { + self.style = style + self.compactDisplayTitle = compactDisplayTitle + self.distances = distances + self.updated = updated + self.completion = completion + self.dismiss = dismiss + } + + static func ==(lhs: LocationDistancePickerContentComponent, rhs: LocationDistancePickerContentComponent) -> Bool { + if lhs.style != rhs.style { + return false + } + if lhs.compactDisplayTitle != rhs.compactDisplayTitle { + return false + } + return true + } + + final class View: UIView, UIPickerViewDataSource, UIPickerViewDelegate { + private let closeButton = ComponentView() + private let title = ComponentView() + private let unitLabel = ComponentView() + private let smallUnitLabel = ComponentView() + private let button = ComponentView() + private let warningText = ComponentView() + + private var pickerView: TimerPickerView? + private var pickerTimer: SwiftSignalKit.Timer? + private var distancesDisposable: Disposable? + + private var component: LocationDistancePickerContentComponent? + private weak var state: EmptyComponentState? + private var environment: EnvironmentType? + private var distances: [Double] = [] + private var previousReportedValue: Int32? + private var isCompleting = false + + private var isUpdating = false + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.distancesDisposable?.dispose() + self.pickerTimer?.invalidate() + } + + private var usesMetricSystem: Bool { + guard let environment = self.environment else { + return true + } + let locale = localeWithStrings(environment.strings) + if locale.identifier.hasSuffix("GB") { + return false + } + return locale.usesMetricSystem + } + + private func setupDistancesIfNeeded(component: LocationDistancePickerContentComponent) { + if self.distancesDisposable != nil { + return + } + self.distancesDisposable = (component.distances + |> deliverOnMainQueue).start(next: { [weak self] distances in + guard let self else { + return + } + self.distances = distances + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + }) + } + + private func setupPickerViewIfNeeded() { + if self.pickerView != nil { + return + } + + let pickerView = TimerPickerView() + pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18) + pickerView.dataSource = self + pickerView.delegate = self + pickerView.selectRow(0, inComponent: 0, animated: false) + if self.usesMetricSystem { + pickerView.selectRow(6, inComponent: 1, animated: false) + } else { + pickerView.selectRow(4, inComponent: 1, animated: false) + } + self.addSubview(pickerView) + self.pickerView = pickerView + + let pickerTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: true, completion: { [weak self] in + guard let self else { + return + } + if self.reportSelectedValue() { + self.state?.updated(transition: .immediate) + } + }, queue: Queue.mainQueue()) + self.pickerTimer = pickerTimer + pickerTimer.start() + + let _ = self.reportSelectedValue() + } + + private func selectedDistance() -> (value: Int32, convertedValue: Int32, convertedDistance: Double, distanceText: String)? { + guard let pickerView = self.pickerView, let environment = self.environment else { + return nil + } + let selectedLargeRow = pickerView.selectedRow(inComponent: 0) var selectedSmallRow = pickerView.selectedRow(inComponent: 1) if selectedLargeRow == 0 && selectedSmallRow == 0 { @@ -390,275 +456,309 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, ASScrollViewD if smallValue == 5 { formattedValue = formattedValue.replacingOccurrences(of: ".1", with: ".05").replacingOccurrences(of: ",1", with: ",05") } - let distance = self.usesMetricSystem ? "\(formattedValue) \(self.presentationData.strings.Location_ProximityNotification_DistanceKM)" : "\(formattedValue) \(self.presentationData.strings.Location_ProximityNotification_DistanceMI)" + let distanceText = self.usesMetricSystem ? "\(formattedValue) \(environment.strings.Location_ProximityNotification_DistanceKM)" : "\(formattedValue) \(environment.strings.Location_ProximityNotification_DistanceMI)" - let shortTitle = self.presentationData.strings.Location_ProximityNotification_Notify(distance).string - var longTitle: String? - if let displayTitle = self.compactDisplayTitle, let (layout, _) = self.containerLayout { - let title = self.presentationData.strings.Location_ProximityNotification_NotifyLong(displayTitle, distance).string - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - self.measureButtonTitleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: .black) - let titleSize = self.measureButtonTitleNode.updateLayout(CGSize(width: width * 2.0, height: 50.0)) - if titleSize.width < width - 70.0 { - longTitle = title + var convertedDistance = Double(value) + if !self.usesMetricSystem { + convertedDistance = convertedDistance * 1.60934 + } + + return (value, Int32(convertedDistance), convertedDistance, distanceText) + } + + private func reportSelectedValue() -> Bool { + guard let selectedDistance = self.selectedDistance(), let component = self.component else { + return false + } + if let previousReportedValue = self.previousReportedValue, selectedDistance.convertedValue == previousReportedValue { + return false + } + self.previousReportedValue = selectedDistance.convertedValue + component.updated(selectedDistance.convertedValue) + return true + } + + func update(component: LocationDistancePickerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let previousEnvironment = self.environment + self.component = component + self.state = state + + let environment = environment[EnvironmentType.self].value + self.environment = environment + + self.setupDistancesIfNeeded(component: component) + self.setupPickerViewIfNeeded() + + if let previousEnvironment, previousEnvironment.strings !== environment.strings || previousEnvironment.theme !== environment.theme { + self.pickerView?.reloadAllComponents() + } + + let textColor: UIColor + let secondaryTextColor: UIColor + let buttonFillColor: UIColor + let buttonForegroundColor: UIColor + switch component.style { + case .default: + textColor = environment.theme.actionSheet.primaryTextColor + secondaryTextColor = environment.theme.actionSheet.secondaryTextColor + buttonFillColor = environment.theme.list.itemCheckColors.fillColor + buttonForegroundColor = environment.theme.list.itemCheckColors.foregroundColor + case .media: + textColor = .white + secondaryTextColor = UIColor(white: 1.0, alpha: 0.7) + buttonFillColor = environment.theme.list.itemCheckColors.fillColor + buttonForegroundColor = environment.theme.list.itemCheckColors.foregroundColor + } + + let sideInset: CGFloat = 16.0 + let topInset: CGFloat = 16.0 + let titleHeight: CGFloat = 54.0 + let pickerHeight: CGFloat = 216.0 + let buttonHeight: CGFloat = 52.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: buttonHeight, sideInset: 30.0) + let buttonWidth = availableSize.width - buttonInsets.left - buttonInsets.right + + let selectedDistance = self.selectedDistance() + let distanceText = selectedDistance?.distanceText ?? "" + let isTooFar: Bool + if let selectedDistance, let maximumDistance = self.distances.last { + isTooFar = selectedDistance.convertedDistance > maximumDistance + } else { + isTooFar = false + } + + let closeButtonSize = CGSize(width: 44.0, height: 44.0) + let closeSize = self.closeButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: closeButtonSize, + backgroundColor: nil, + isDark: component.style == .media ? true : environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in + self?.component?.dismiss() + } + ) + ), + environment: {}, + containerSize: closeButtonSize + ) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.addSubview(closeButtonView) + } + transition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: sideInset, y: topInset), size: closeSize)) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(Text( + text: environment.strings.Location_ProximityNotification_Title, + font: Font.bold(17.0), + color: textColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - 120.0, height: titleHeight) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) * 0.5), y: topInset + floorToScreenPixels((closeButtonSize.height - titleSize.height) * 0.5)), size: titleSize)) + } + + let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + titleHeight), size: CGSize(width: availableSize.width, height: pickerHeight)) + if let pickerView = self.pickerView { + transition.setFrame(view: pickerView, frame: pickerFrame) + } + + let unitLabelSize = self.unitLabel.update( + transition: transition, + component: AnyComponent(Text( + text: self.usesMetricSystem ? environment.strings.Location_ProximityNotification_DistanceKM : environment.strings.Location_ProximityNotification_DistanceMI, + font: Font.regular(15.0), + color: textColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: titleHeight) + ) + if let unitLabelView = self.unitLabel.view { + if unitLabelView.superview == nil { + self.addSubview(unitLabelView) + } + transition.setFrame(view: unitLabelView, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0) + 50.0, y: floor(pickerFrame.midY - unitLabelSize.height / 2.0)), size: unitLabelSize)) + } + + let smallUnitLabelSize = self.smallUnitLabel.update( + transition: transition, + component: AnyComponent(Text( + text: self.usesMetricSystem ? environment.strings.Location_ProximityNotification_DistanceM : "", + font: Font.regular(15.0), + color: textColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: titleHeight) + ) + if let smallUnitLabelView = self.smallUnitLabel.view { + if smallUnitLabelView.superview == nil { + self.addSubview(smallUnitLabelView) + } + transition.setFrame(view: smallUnitLabelView, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0 * 3.0) + 50.0, y: floor(pickerFrame.midY - smallUnitLabelSize.height / 2.0)), size: smallUnitLabelSize)) + } + + let bottomY = pickerFrame.maxY + 17.0 + var buttonTitle = environment.strings.Location_ProximityNotification_Notify(distanceText).string + if let displayTitle = component.compactDisplayTitle { + let longTitle = environment.strings.Location_ProximityNotification_NotifyLong(displayTitle, distanceText).string + let titleSize = NSAttributedString(string: longTitle, font: Font.semibold(17.0), textColor: .black).boundingRect(with: CGSize(width: availableSize.width * 2.0, height: 50.0), options: .usesLineFragmentOrigin, context: nil).size + if titleSize.width < availableSize.width - 70.0 { + buttonTitle = longTitle } } - self.doneButton.title = longTitle ?? shortTitle - - self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_AlreadyClose(distance).string, font: Font.regular(14.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) - if let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + + var buttonTransition = transition + if transition.animation.isImmediate { + buttonTransition = buttonTransition.withAnimation(.curve(duration: 0.2, curve: .easeInOut)) + } + let buttonSize = self.button.update( + transition: buttonTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: buttonFillColor, + foreground: buttonForegroundColor, + pressedColor: buttonFillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable("title"), component: AnyComponent( + AnimatedTextComponent( + font: Font.semibold(17.0), + color: buttonForegroundColor, + items: [ + AnimatedTextComponent.Item(id: AnyHashable("title"), content: .text(buttonTitle)) + ], + noDelay: true + ) + )), + isEnabled: !isTooFar && !self.isCompleting, + tintWhenDisabled: false, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component, let selectedDistance = self.selectedDistance() else { + return + } + self.isCompleting = true + self.state?.updated(transition: .immediate) + component.completion(selectedDistance.convertedValue) + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: bottomY), size: buttonSize)) + buttonTransition.setAlpha(view: buttonView, alpha: isTooFar ? 0.0 : 1.0) } - var convertedValue = Double(value) - if !self.usesMetricSystem { - convertedValue = Double(convertedValue) * 1.60934 + let warningSize = self.warningText.update( + transition: transition, + component: AnyComponent(Text( + text: environment.strings.Location_ProximityNotification_AlreadyClose(distanceText).string, + font: Font.regular(14.0), + color: secondaryTextColor + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight) + ) + if let warningTextView = self.warningText.view { + if warningTextView.superview == nil { + self.addSubview(warningTextView) + } + transition.setFrame(view: warningTextView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - warningSize.width) * 0.5), y: bottomY + floorToScreenPixels((buttonHeight - warningSize.height) * 0.5)), size: warningSize)) + buttonTransition.setAlpha(view: warningTextView, alpha: isTooFar ? 1.0 : 0.0) } - if let distance = self.distances.last, convertedValue > distance { - self.doneButton.alpha = 0.0 - self.doneButton.isUserInteractionEnabled = false - self.textNode.alpha = 1.0 + return CGSize(width: availableSize.width, height: bottomY + buttonHeight + buttonInsets.bottom) + } + + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 2 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + if component == 0 { + return unitValues.count + } else if component == 1 { + return smallUnitValues.count } else { - self.doneButton.alpha = 1.0 - self.doneButton.isUserInteractionEnabled = true - self.textNode.alpha = 0.0 + return 1 } } - } - - var previousReportedValue: Int32? - fileprivate func update() -> Bool { - if let pickerView = self.pickerView { - let selectedLargeRow = pickerView.selectedRow(inComponent: 0) - var selectedSmallRow = pickerView.selectedRow(inComponent: 1) - if selectedLargeRow == 0 && selectedSmallRow == 0 { - selectedSmallRow = 1 + + func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { + guard let environment = self.environment else { + return nil } - let largeValue = unitValues[selectedLargeRow] - let smallValue = smallUnitValues[selectedSmallRow] - - var value = largeValue * 1000 + smallValue * 10 - if !self.usesMetricSystem { - value = Int32(Double(value) * 1.60934) - } - - if let previousReportedValue = self.previousReportedValue, value == previousReportedValue { - return false - } else { - self.updated?(value) - self.previousReportedValue = value - return true - } - } else { - return false - } - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - if pickerView.selectedRow(inComponent: 0) == 0 && pickerView.selectedRow(inComponent: 1) == 0 { - pickerView.selectRow(1, inComponent: 1, animated: true) - } - self.updateDoneButtonTitle() - let _ = self.update() - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - if component == 0 { - return unitValues.count - } else if component == 1 { - return smallUnitValues.count - } else { - return 1 - } - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - let font = Font.regular(17.0) - let string: String - if component == 0 { - let value = unitValues[row] - string = "\(value)" - } else { - if self.usesMetricSystem { - let value = String(format: "%d", smallUnitValues[row] * 10) + let font = Font.regular(17.0) + let string: String + if component == 0 { + let value = unitValues[row] string = "\(value)" } else { - let value = smallUnitValues[row] - if value == 0 { - string = ".0" - } else if value == 5 { - string = ".05" + if self.usesMetricSystem { + let value = String(format: "%d", smallUnitValues[row] * 10) + string = "\(value)" } else { - string = ".\(value / 10)" + let value = smallUnitValues[row] + if value == 0 { + string = ".0" + } else if value == 5 { + string = ".05" + } else { + string = ".\(value / 10)" + } } } - } - return NSAttributedString(string: string, font: font, textColor: self.presentationData.theme.actionSheet.primaryTextColor) - } - - func updatePresentationData(_ presentationData: PresentationData) { - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - guard case .default = self.controllerStyle else { - return - } - - if let effectView = self.effectNode.view as? UIVisualEffectView { - effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) - } - - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.setupPickerView() - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) - self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - - self.updateDoneButtonTitle() - - self.unitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceKM : self.presentationData.strings.Location_ProximityNotification_DistanceMI, font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.smallUnitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceM : "", font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancelButtonPressed() - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - - let offset = self.contentContainerNode.frame.height - let position = self.wrappingScrollNode.position - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - self.wrappingScrollNode.position = CGPoint(x: position.x, y: position.y + offset) - transition.animateView({ - self.wrappingScrollNode.position = position - }) - } - - func animateOut(completion: (() -> Void)? = nil) { - var dimCompleted = false - var offsetCompleted = false - - let internalCompletion: () -> Void = { [weak self] in - if let strongSelf = self, dimCompleted && offsetCompleted { - strongSelf.dismiss?() + + let textColor: UIColor + switch self.component?.style { + case .media: + textColor = .white + default: + textColor = environment.theme.actionSheet.primaryTextColor } - completion?() + return NSAttributedString(string: string, font: font, textColor: textColor) } - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in - dimCompleted = true - internalCompletion() - }) - - let offset = self.contentContainerNode.frame.height - self.wrappingScrollNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in - offsetCompleted = true - internalCompletion() - }) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - return self.dimNode.view + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + if pickerView.selectedRow(inComponent: 0) == 0 && pickerView.selectedRow(inComponent: 1) == 0 { + pickerView.selectRow(1, inComponent: 1, animated: true) } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() + let _ = self.reportSelectedValue() + self.state?.updated(transition: .immediate) } } - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - let hadValidLayout = self.containerLayout != nil - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - let cleanInsets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - - let buttonOffset: CGFloat = 0.0 - let bottomInset: CGFloat = 10.0 + cleanInsets.bottom - let titleHeight: CGFloat = 54.0 - var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 - let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight) - contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight + buttonOffset - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: insets.top + 66.0 + UIScreenPixel))) - - let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight)) - let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - - let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - let doneButtonFrame = CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight) - transition.updateFrame(node: self.doneButton, frame: doneButtonFrame) - - let textSize = self.textNode.updateLayout(CGSize(width: width, height: titleHeight)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor(doneButtonFrame.center.y - textSize.height / 2.0)), size: textSize)) - - let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight)) - self.pickerView?.frame = pickerFrame - - let unitLabelSize = self.unitLabelNode.updateLayout(CGSize(width: width, height: titleHeight)) - transition.updateFrame(node: self.unitLabelNode, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0) + 50.0, y: floor(pickerFrame.center.y - unitLabelSize.height / 2.0)), size: unitLabelSize)) - - let smallUnitLabelSize = self.smallUnitLabelNode.updateLayout(CGSize(width: width, height: titleHeight)) - transition.updateFrame(node: self.smallUnitLabelNode, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0 * 3.0) + 50.0, y: floor(pickerFrame.center.y - smallUnitLabelSize.height / 2.0)), size: smallUnitLabelSize)) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - - if !hadValidLayout { - self.updateDoneButtonTitle() - } + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } diff --git a/submodules/LocationUI/Sources/LocationInfoListItem.swift b/submodules/LocationUI/Sources/LocationInfoListItem.swift index fda4a00e51..d87f8c99a1 100644 --- a/submodules/LocationUI/Sources/LocationInfoListItem.swift +++ b/submodules/LocationUI/Sources/LocationInfoListItem.swift @@ -7,9 +7,10 @@ import TelegramCore import TelegramPresentationData import ItemListUI import LocationResources -import AppBundle -import SolidRoundedButtonNode import ShimmerEffect +import ComponentFlow +import ButtonComponent +import BundleIconComponent public final class LocationInfoListItem: ListViewItem { let presentationData: ItemListPresentationData @@ -18,27 +19,35 @@ public final class LocationInfoListItem: ListViewItem { let address: String? let distance: String? let drivingTime: ExpectedTravelTime - let transitTime: ExpectedTravelTime let walkingTime: ExpectedTravelTime let hasEta: Bool let action: () -> Void let drivingAction: () -> Void - let transitAction: () -> Void let walkingAction: () -> Void - public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, drivingTime: ExpectedTravelTime, transitTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, hasEta: Bool, action: @escaping () -> Void, drivingAction: @escaping () -> Void, transitAction: @escaping () -> Void, walkingAction: @escaping () -> Void) { + public init( + presentationData: ItemListPresentationData, + engine: TelegramEngine, + location: TelegramMediaMap, + address: String?, + distance: String?, + drivingTime: ExpectedTravelTime, + walkingTime: ExpectedTravelTime, + hasEta: Bool, + action: @escaping () -> Void, + drivingAction: @escaping () -> Void, + walkingAction: @escaping () -> Void + ) { self.presentationData = presentationData self.engine = engine self.location = location self.address = address self.distance = distance self.drivingTime = drivingTime - self.transitTime = transitTime self.walkingTime = walkingTime self.hasEta = hasEta self.action = action self.drivingAction = drivingAction - self.transitAction = transitAction self.walkingAction = walkingAction } @@ -76,31 +85,26 @@ public final class LocationInfoListItem: ListViewItem { } public final class LocationInfoListItemNode: ListViewItemNode { - private let backgroundNode: ASDisplayNode private var titleNode: TextNode? private var subtitleNode: TextNode? private let venueIconNode: TransformImageNode private let buttonNode: HighlightableButtonNode private var placeholderNode: ShimmerEffectNode? - private var drivingButtonNode: SolidRoundedButtonNode? - private var transitButtonNode: SolidRoundedButtonNode? - private var walkingButtonNode: SolidRoundedButtonNode? + private let drivingButton = ComponentView() + private let walkingButton = ComponentView() private var item: LocationInfoListItem? private var layoutParams: ListViewItemLayoutParams? private var absoluteLocation: (CGRect, CGSize)? required public init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true self.buttonNode = HighlightableButtonNode() self.venueIconNode = TransformImageNode() self.venueIconNode.isUserInteractionEnabled = false super.init(layerBacked: false, rotated: false, seeThrough: false) - self.addSubnode(self.backgroundNode) self.addSubnode(self.buttonNode) self.addSubnode(self.venueIconNode) @@ -145,14 +149,15 @@ public final class LocationInfoListItemNode: ListViewItemNode { let iconLayout = self.venueIconNode.asyncLayout() return { [weak self] item, params in - let leftInset: CGFloat = 75.0 + params.leftInset + let leftInset: CGFloat = 78.0 + params.leftInset let rightInset: CGFloat = params.rightInset let verticalInset: CGFloat = 14.0 - let iconSize: CGFloat = 48.0 - let inset: CGFloat = 15.0 + let iconSize: CGFloat = 40.0 + let directionsButtonHeight: CGFloat = 52.0 + let directionsTopInset: CGFloat = 18.0 - let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) - let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) + let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let title: String let subtitle: String @@ -179,10 +184,11 @@ public final class LocationInfoListItemNode: ListViewItemNode { let subtitleAttributedString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let titleSpacing: CGFloat = 1.0 - let bottomInset: CGFloat = 4.0 + let titleSpacing: CGFloat = 0.0 + let bottomInset: CGFloat = 16.0 let textContentSize = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset - let contentSize = CGSize(width: params.width, height: item.hasEta ? max(100.0, textContentSize) : textContentSize) + let etaContentSize = verticalInset + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + directionsTopInset + directionsButtonHeight + bottomInset + let contentSize = CGSize(width: params.width, height: item.hasEta ? max(etaContentSize, textContentSize) : textContentSize) let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets()) return (nodeLayout, { [weak self] in @@ -200,13 +206,7 @@ public final class LocationInfoListItemNode: ListViewItemNode { if let strongSelf = self { strongSelf.item = item strongSelf.layoutParams = params - - if let _ = updatedTheme { - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor - } - - strongSelf.backgroundNode.isHidden = params.isStandalone - + let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor) if let updatedLocation = updatedLocation { strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: updatedLocation.venue?.type ?? "", background: true)) @@ -229,107 +229,126 @@ public final class LocationInfoListItemNode: ListViewItemNode { strongSelf.addSubnode(subtitleNode) } - let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - if strongSelf.drivingButtonNode == nil { - strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.drivingButtonNode?.iconSpacing = 5.0 - strongSelf.drivingButtonNode?.alpha = 0.0 - strongSelf.drivingButtonNode?.allowsGroupOpacity = true - strongSelf.drivingButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.drivingAction() - } - } - strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) } - - strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.transitButtonNode?.iconSpacing = 2.0 - strongSelf.transitButtonNode?.alpha = 0.0 - strongSelf.transitButtonNode?.allowsGroupOpacity = true - strongSelf.transitButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.transitAction() - } - } - strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) } - - strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.walkingButtonNode?.iconSpacing = 2.0 - strongSelf.walkingButtonNode?.alpha = 0.0 - strongSelf.walkingButtonNode?.allowsGroupOpacity = true - strongSelf.walkingButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.walkingAction() - } - } - strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) } - } else if let _ = updatedTheme { - strongSelf.drivingButtonNode?.updateTheme(buttonTheme) - strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - - strongSelf.transitButtonNode?.updateTheme(buttonTheme) - strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - - strongSelf.walkingButtonNode?.updateTheme(buttonTheme) - strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) titleNode.frame = titleFrame let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size) subtitleNode.frame = subtitleFrame - let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize)) + let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 26.0, y: 14.0), size: CGSize(width: iconSize, height: iconSize)) strongSelf.venueIconNode.frame = iconNodeFrame - var directionsWidth: CGFloat = 93.0 + let glassInset: CGFloat = 6.0 + let buttonSideInset: CGFloat = 30.0 + let buttonSpacing: CGFloat = 10.0 + var directionsWidth: CGFloat = floorToScreenPixels((params.width - glassInset * 2.0 - buttonSideInset * 2.0 - buttonSpacing) / 2.0) if item.hasEta { - if item.drivingTime == .unknown && item.transitTime == .unknown && item.walkingTime == .unknown { - strongSelf.drivingButtonNode?.icon = nil - strongSelf.drivingButtonNode?.title = item.presentationData.strings.Map_GetDirections - if let drivingButtonNode = strongSelf.drivingButtonNode { - let buttonSize = drivingButtonNode.sizeThatFits(contentSize) - directionsWidth = buttonSize.width - } + let buttonBackground = ButtonComponent.Background( + style: .glass, + color: item.presentationData.theme.list.itemCheckColors.fillColor, + foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ) + let foregroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor + + var drivingButtonTitle = "" + var walkingButtonTitle = "" + var drivingButtonHasIcon = true + var drivingButtonVisible = false + var walkingButtonVisible = false + + if item.drivingTime == .unknown && item.walkingTime == .unknown { + drivingButtonHasIcon = false + drivingButtonTitle = item.presentationData.strings.Map_GetDirections + drivingButtonVisible = true if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime { - strongSelf.drivingButtonNode?.alpha = 1.0 - strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.drivingButton.view?.alpha = 1.0 + strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } else { if case let .ready(drivingTime) = item.drivingTime { - strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) + drivingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) ?? "" + drivingButtonVisible = true if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime { - strongSelf.drivingButtonNode?.alpha = 1.0 - strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - - if case let .ready(transitTime) = item.transitTime { - strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 }) - - if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime { - strongSelf.transitButtonNode?.alpha = 1.0 - strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.drivingButton.view?.alpha = 1.0 + strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } if case let .ready(walkingTime) = item.walkingTime { - strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) + walkingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) ?? "" + walkingButtonVisible = true if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime { - strongSelf.walkingButtonNode?.alpha = 1.0 - strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.walkingButton.view?.alpha = 1.0 + strongSelf.walkingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } } + + let drivingButtonContent: AnyComponent + if drivingButtonHasIcon { + drivingButtonContent = AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsDriving", tintColor: foregroundColor))), + AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(17.0), color: foregroundColor))) + ], spacing: 5.0) + ) + } else { + drivingButtonContent = AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(17.0), color: foregroundColor)) + } - let directionsSpacing: CGFloat = 8.0 + let drivingButtonSize = strongSelf.drivingButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity( + id: AnyHashable("driving-\(drivingButtonHasIcon)-\(drivingButtonTitle)"), + component: drivingButtonContent + ), + action: { [weak self] in + if let item = self?.item { + item.drivingAction() + } + } + )), + environment: {}, + containerSize: CGSize(width: drivingButtonHasIcon ? directionsWidth : contentSize.width - glassInset * 2.0 - buttonSideInset * 2.0, height: directionsButtonHeight) + ) + if !drivingButtonHasIcon { + directionsWidth = drivingButtonSize.width + } - if case .calculating = item.drivingTime, case .calculating = item.transitTime, case .calculating = item.walkingTime { + let walkingButtonSize = strongSelf.walkingButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity( + id: AnyHashable("walking-\(walkingButtonTitle)"), + component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsWalking", tintColor: foregroundColor))), + AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: walkingButtonTitle, font: Font.semibold(17.0), color: foregroundColor))) + ], spacing: 2.0) + ) + ), + contentInsets: UIEdgeInsets(), + action: { [weak self] in + if let item = self?.item { + item.walkingAction() + } + } + )), + environment: {}, + containerSize: CGSize(width: directionsWidth, height: directionsButtonHeight) + ) + + var buttonOrigin = glassInset + buttonSideInset + + if case .calculating = item.drivingTime, case .calculating = item.walkingTime { let shimmerNode: ShimmerEffectNode if let current = strongSelf.placeholderNode { shimmerNode = current @@ -338,17 +357,17 @@ public final class LocationInfoListItemNode: ListViewItemNode { strongSelf.placeholderNode = shimmerNode strongSelf.addSubnode(shimmerNode) } - shimmerNode.frame = CGRect(origin: CGPoint(x: leftInset, y: subtitleFrame.maxY + 12.0), size: CGSize(width: contentSize.width - leftInset, height: 32.0)) + shimmerNode.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: contentSize.width - buttonOrigin * 2.0, height: directionsButtonHeight)) if let (rect, size) = strongSelf.absoluteLocation { shimmerNode.updateAbsoluteRect(rect, within: size) } var shapes: [ShimmerEffectNode.Shape] = [] - shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: 0.0), width: directionsWidth, diameter: 32.0)) - shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + directionsSpacing, y: 0.0), width: directionsWidth, diameter: 32.0)) - shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + directionsSpacing + directionsWidth + directionsSpacing, y: 0.0), width: directionsWidth, diameter: 32.0)) + shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight)) + shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + buttonSpacing, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight)) + shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + buttonSpacing + directionsWidth + buttonSpacing, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight)) - shimmerNode.update(backgroundColor: item.presentationData.theme.list.plainBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size) + shimmerNode.update(backgroundColor: .clear, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size, mask: true) } else if let shimmerNode = strongSelf.placeholderNode { strongSelf.placeholderNode = nil shimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak shimmerNode] _ in @@ -356,30 +375,42 @@ public final class LocationInfoListItemNode: ListViewItemNode { }) } - let drivingHeight = strongSelf.drivingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 - let transitHeight = strongSelf.transitButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 - let walkingHeight = strongSelf.walkingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 - var buttonOrigin = leftInset - strongSelf.drivingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: drivingHeight)) + let drivingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: directionsWidth, height: drivingButtonSize.height)) + if let drivingButtonView = strongSelf.drivingButton.view { + if drivingButtonView.superview == nil { + strongSelf.view.addSubview(drivingButtonView) + } + drivingButtonView.frame = drivingButtonFrame + if drivingButtonView.layer.animation(forKey: "opacity") == nil { + drivingButtonView.alpha = drivingButtonVisible ? 1.0 : 0.0 + } + } if case .ready = item.drivingTime { - buttonOrigin += directionsWidth + directionsSpacing + buttonOrigin += directionsWidth + buttonSpacing } - strongSelf.transitButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: transitHeight)) - - if case .ready = item.transitTime { - buttonOrigin += directionsWidth + directionsSpacing + let walkingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: directionsWidth, height: walkingButtonSize.height)) + if let walkingButtonView = strongSelf.walkingButton.view { + if walkingButtonView.superview == nil { + strongSelf.view.addSubview(walkingButtonView) + } + walkingButtonView.frame = walkingButtonFrame + if walkingButtonView.layer.animation(forKey: "opacity") == nil { + walkingButtonView.alpha = walkingButtonVisible ? 1.0 : 0.0 + } } - - strongSelf.walkingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: walkingHeight)) } else { - + strongSelf.drivingButton.view?.alpha = 0.0 + strongSelf.walkingButton.view?.alpha = 0.0 + if let shimmerNode = strongSelf.placeholderNode { + strongSelf.placeholderNode = nil + shimmerNode.removeFromSupernode() + } } strongSelf.buttonNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: 72.0) - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) } }) }) diff --git a/submodules/LocationUI/Sources/LocationLiveListItem.swift b/submodules/LocationUI/Sources/LocationLiveListItem.swift index 5aaaf511fc..be96523c7c 100644 --- a/submodules/LocationUI/Sources/LocationLiveListItem.swift +++ b/submodules/LocationUI/Sources/LocationLiveListItem.swift @@ -10,10 +10,11 @@ import TelegramUIPreferences import TelegramStringFormatting import ItemListUI import LocationResources -import AppBundle import AvatarNode import LiveLocationTimerNode -import SolidRoundedButtonNode +import ComponentFlow +import ButtonComponent +import BundleIconComponent final class LocationLiveListItem: ListViewItem { let presentationData: ItemListPresentationData @@ -24,17 +25,15 @@ final class LocationLiveListItem: ListViewItem { let distance: Double? let drivingTime: ExpectedTravelTime - let transitTime: ExpectedTravelTime let walkingTime: ExpectedTravelTime let action: () -> Void let longTapAction: () -> Void let drivingAction: () -> Void - let transitAction: () -> Void let walkingAction: () -> Void - public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, message: EngineMessage, distance: Double?, drivingTime: ExpectedTravelTime, transitTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }, drivingAction: @escaping () -> Void, transitAction: @escaping () -> Void, walkingAction: @escaping () -> Void) { + public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, message: EngineMessage, distance: Double?, drivingTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }, drivingAction: @escaping () -> Void, walkingAction: @escaping () -> Void) { self.presentationData = presentationData self.dateTimeFormat = dateTimeFormat self.nameDisplayOrder = nameDisplayOrder @@ -42,12 +41,10 @@ final class LocationLiveListItem: ListViewItem { self.message = message self.distance = distance self.drivingTime = drivingTime - self.transitTime = transitTime self.walkingTime = walkingTime self.action = action self.longTapAction = longTapAction self.drivingAction = drivingAction - self.transitAction = transitAction self.walkingAction = walkingAction } @@ -91,28 +88,19 @@ final class LocationLiveListItem: ListViewItem { private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)) final class LocationLiveListItemNode: ListViewItemNode { - private let backgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode private var titleNode: TextNode? private var subtitleNode: TextNode? private let avatarNode: AvatarNode private var timerNode: ChatMessageLiveLocationTimerNode? - private var drivingButtonNode: SolidRoundedButtonNode? - private var transitButtonNode: SolidRoundedButtonNode? - private var walkingButtonNode: SolidRoundedButtonNode? + private let drivingButton = ComponentView() + private let walkingButton = ComponentView() private var item: LocationLiveListItem? private var layoutParams: ListViewItemLayoutParams? required init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - self.highlightedBackgroundNode = ASDisplayNode() self.highlightedBackgroundNode.isLayerBacked = true @@ -121,8 +109,6 @@ final class LocationLiveListItemNode: ListViewItemNode { super.init(layerBacked: false, rotated: false, seeThrough: false) - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) self.addSubnode(self.avatarNode) } @@ -142,7 +128,7 @@ final class LocationLiveListItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + self.insertSubnode(self.highlightedBackgroundNode, at: 0) } } else { if self.highlightedBackgroundNode.supernode != nil { @@ -169,7 +155,7 @@ final class LocationLiveListItemNode: ListViewItemNode { let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) return { [weak self] item, params, hasSeparator in - let leftInset: CGFloat = 65.0 + params.leftInset + let leftInset: CGFloat = 72.0 + params.leftInset let rightInset: CGFloat = params.rightInset let verticalInset: CGFloat = 8.0 @@ -203,18 +189,17 @@ final class LocationLiveListItemNode: ListViewItemNode { let subtitleAttributedString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 54.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let titleSpacing: CGFloat = 1.0 + let titleSpacing: CGFloat = 0.0 var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height) - let hasEta: Bool + var hasEta: Bool if case .ready = item.drivingTime { hasEta = true - } else if case .ready = item.transitTime { - hasEta = true } else if case .ready = item.walkingTime { hasEta = true } else { hasEta = false } + hasEta = true if hasEta { contentSize.height += 46.0 } @@ -232,9 +217,7 @@ final class LocationLiveListItemNode: ListViewItemNode { strongSelf.layoutParams = params if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor - strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.contextMenu.itemHighlightedBackgroundColor } let titleNode = titleApply() @@ -249,72 +232,23 @@ final class LocationLiveListItemNode: ListViewItemNode { strongSelf.addSubnode(subtitleNode) } - let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) - if strongSelf.drivingButtonNode == nil { - strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.drivingButtonNode?.alpha = 0.0 - strongSelf.drivingButtonNode?.iconSpacing = 5.0 - strongSelf.drivingButtonNode?.allowsGroupOpacity = true - strongSelf.drivingButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.drivingAction() - } - } - strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) } - - strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.transitButtonNode?.alpha = 0.0 - strongSelf.transitButtonNode?.iconSpacing = 2.0 - strongSelf.transitButtonNode?.allowsGroupOpacity = true - strongSelf.transitButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.transitAction() - } - } - strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) } - - strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0) - strongSelf.walkingButtonNode?.alpha = 0.0 - strongSelf.walkingButtonNode?.iconSpacing = 2.0 - strongSelf.walkingButtonNode?.allowsGroupOpacity = true - strongSelf.walkingButtonNode?.pressed = { [weak self] in - if let item = self?.item { - item.walkingAction() - } - } - strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) } - } else if let _ = updatedTheme { - strongSelf.drivingButtonNode?.updateTheme(buttonTheme) - strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - - strongSelf.transitButtonNode?.updateTheme(buttonTheme) - strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - - strongSelf.walkingButtonNode?.updateTheme(buttonTheme) - strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) - } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) titleNode.frame = titleFrame let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size) subtitleNode.frame = subtitleFrame - let separatorHeight = UIScreenPixel - let topHighlightInset: CGFloat = separatorHeight - let separatorRightInset: CGFloat = 16.0 let avatarSize: CGFloat = 40.0 - if let peer = item.message.author { strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: nil, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: false) } - strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 8.0), size: CGSize(width: avatarSize, height: avatarSize)) + strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 22.0, y: 8.0), size: CGSize(width: avatarSize, height: avatarSize)) - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight)) - strongSelf.separatorNode.isHidden = !hasSeparator + let highlightFrame = CGRect(origin: CGPoint(x: 14.0, y: 2.0), size: CGSize(width: contentSize.width - 14.0 * 2.0, height: 52.0)) + let highlightCornerRadius = highlightFrame.height * 0.5 + strongSelf.highlightedBackgroundNode.frame = highlightFrame + strongSelf.highlightedBackgroundNode.cornerRadius = highlightCornerRadius var liveBroadcastingTimeout: Int32 = 0 if let location = getLocation(from: item.message), let timeout = location.liveBroadcastingTimeout { @@ -338,61 +272,134 @@ final class LocationLiveListItemNode: ListViewItemNode { strongSelf.addSubnode(timerNode) strongSelf.timerNode = timerNode } - let timerSize = CGSize(width: 28.0, height: 28.0) + let timerSize = CGSize(width: 24.0, height: 24.0) timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: Double(item.message.timestamp), timeout: Int32(liveBroadcastingTimeout) == liveLocationIndefinitePeriod ? -1.0 : Double(liveBroadcastingTimeout), strings: item.presentationData.strings) - timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: 14.0), size: timerSize) + timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 26.0 - timerSize.width, y: floorToScreenPixels((56.0 - timerSize.height) / 2.0)), size: timerSize) } else if let timerNode = strongSelf.timerNode { strongSelf.timerNode = nil timerNode.removeFromSupernode() } + let buttonBackground = ButtonComponent.Background( + style: .glass, + color: item.presentationData.theme.list.itemCheckColors.fillColor, + foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ) + let foregroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor + var directionsSize = CGSize(width: 96.0, height: 36.0) + let directionsSpacing: CGFloat = 8.0 + + var drivingButtonTitle = "" + var drivingButtonHasIcon = true + var walkingButtonTitle = "" + var drivingButtonVisible = false + var walkingButtonVisible = false + if case let .ready(drivingTime) = item.drivingTime { - strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) + drivingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) ?? "" + drivingButtonVisible = true if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime { - strongSelf.drivingButtonNode?.alpha = 1.0 - strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.drivingButton.view?.alpha = 1.0 + strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - } - - if case let .ready(transitTime) = item.transitTime { - strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 }) - - if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime { - strongSelf.transitButtonNode?.alpha = 1.0 - strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } else { + drivingButtonVisible = true + if case .unknown = item.walkingTime { + drivingButtonHasIcon = false + drivingButtonTitle = item.presentationData.strings.Map_GetDirections + directionsSize.width = contentSize.width - leftInset * 2.0 } } if case let .ready(walkingTime) = item.walkingTime { - strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) + walkingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) ?? "" + walkingButtonVisible = true if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime { - strongSelf.walkingButtonNode?.alpha = 1.0 - strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + strongSelf.walkingButton.view?.alpha = 1.0 + strongSelf.walkingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } - let directionsWidth: CGFloat = 93.0 - let directionsSpacing: CGFloat = 8.0 - let drivingHeight = strongSelf.drivingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 - let transitHeight = strongSelf.transitButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 - let walkingHeight = strongSelf.walkingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0 + var drivingButtonContent: [AnyComponentWithIdentity] = [] + if drivingButtonHasIcon { + drivingButtonContent.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsDriving", tintColor: foregroundColor)))) + } + drivingButtonContent.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(14.0), color: foregroundColor)))) + + let drivingButtonSize = strongSelf.drivingButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity( + id: AnyHashable("driving-\(drivingButtonTitle)"), + component: AnyComponent( + HStack(drivingButtonContent, spacing: 2.0) + ) + ), + contentInsets: UIEdgeInsets(), + action: { [weak self] in + if let item = self?.item { + item.drivingAction() + } + } + )), + environment: {}, + containerSize: directionsSize + ) + + let walkingButtonSize = strongSelf.walkingButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity( + id: AnyHashable("walking-\(walkingButtonTitle)"), + component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsWalking", tintColor: foregroundColor))), + AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: walkingButtonTitle, font: Font.semibold(14.0), color: foregroundColor))) + ], spacing: 0.0) + ) + ), + contentInsets: UIEdgeInsets(), + action: { [weak self] in + if let item = self?.item { + item.walkingAction() + } + } + )), + environment: {}, + containerSize: directionsSize + ) var buttonOrigin = leftInset - strongSelf.drivingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: drivingHeight)) + let drivingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: drivingButtonSize) + if let drivingButtonView = strongSelf.drivingButton.view { + if drivingButtonView.superview == nil { + strongSelf.view.addSubview(drivingButtonView) + } + drivingButtonView.frame = drivingButtonFrame + if drivingButtonView.layer.animation(forKey: "opacity") == nil { + drivingButtonView.alpha = drivingButtonVisible ? 1.0 : 0.0 + } + } if case .ready = item.drivingTime { - buttonOrigin += directionsWidth + directionsSpacing + buttonOrigin += directionsSize.width + directionsSpacing } - strongSelf.transitButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: transitHeight)) - - if case .ready = item.transitTime { - buttonOrigin += directionsWidth + directionsSpacing + let walkingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: walkingButtonSize) + if let walkingButtonView = strongSelf.walkingButton.view { + if walkingButtonView.superview == nil { + strongSelf.view.addSubview(walkingButtonView) + } + walkingButtonView.frame = walkingButtonFrame + if walkingButtonView.layer.animation(forKey: "opacity") == nil { + walkingButtonView.alpha = walkingButtonVisible ? 1.0 : 0.0 + } } - - strongSelf.walkingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: walkingHeight)) } }) }) diff --git a/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift b/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift new file mode 100644 index 0000000000..d79e5bb5e1 --- /dev/null +++ b/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift @@ -0,0 +1,68 @@ +import UIKit +import Display +import ContextUI +import TelegramPresentationData +import TelegramCore + +private final class LocationLiveLocationReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + +func makeLiveLocationDurationContextController( + presentationData: PresentationData, + sourceView: UIView, + title: String, + selectPeriod: @escaping (Int32) -> Void +) -> ViewController { + let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem( + text: title, + textLayout: .multiline, + textFont: .small, + parseMarkdown: true, + icon: { _ in + return nil + }, + action: noAction + ))) + + items.append(.separator) + + let periodItems: [(String, Int32)] = [ + (presentationData.strings.Map_LiveLocationForMinutes(15), 15 * 60), + (presentationData.strings.Map_LiveLocationForHours(1), 60 * 60 - 1), + (presentationData.strings.Map_LiveLocationForHours(8), 8 * 60 * 60), + (presentationData.strings.Map_LiveLocationIndefinite, liveLocationIndefinitePeriod) + ] + + for (text, period) in periodItems { + items.append(.action(ContextMenuActionItem( + text: text, + icon: { _ in + return nil + }, + action: { _, f in + f(.default) + selectPeriod(period) + } + ))) + } + + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(LocationLiveLocationReferenceContentSource(sourceView: sourceView)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + return contextController +} diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index 34d88662f9..7645090d71 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -2,6 +2,9 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import AccountContext +import TelegramCore +import SwiftSignalKit import TelegramPresentationData import AppBundle import CoreLocation @@ -10,6 +13,8 @@ import GlassBackgroundComponent import PlainButtonComponent import BundleIconComponent import MultilineTextComponent +import LottieComponent +import LottieComponentResourceContent private let panelInset: CGFloat = 4.0 private let panelButtonSize = CGSize(width: 46.0, height: 46.0) @@ -48,6 +53,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { private let goToUserLocation: () -> Void private let showPlacesInThisArea: () -> Void private let setupProximityNotification: (Bool) -> Void + private let weatherPressed: () -> Void private var displayingPlacesButton = false private var proximityNotification: Bool? @@ -57,7 +63,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { private let options: ComponentView? - private let optionsBackgroundView: GlassBackgroundView? + private let optionsBackgroundView: GlassContextExtractableContainer? private let optionsBackgroundNode: ASImageNode private let optionsSeparatorNode: ASDisplayNode private let optionsSecondSeparatorNode: ASDisplayNode @@ -66,11 +72,21 @@ public final class LocationMapHeaderNode: ASDisplayNode { private let notificationButtonNode: HighlightableButtonNode private let placesBackgroundView: GlassBackgroundView? private let placesBackgroundNode: ASImageNode - private let placesButtonNode: HighlightableButtonNode - private let shadowNode: ASImageNode + private let placesButtonNode: HighlightTrackingButtonNode + private let weatherBackgroundView: GlassBackgroundView? + private let weatherIcon = ComponentView() + private let weatherEmojiLabel = ComponentView() + private let weatherTemperatureLabel = ComponentView() + private let weatherButton: HighlightTrackingButton + private var weatherEmoji: String? + private var weatherTemperature: String? + private var weatherEmojiFile: TelegramMediaFile? + private weak var weatherContext: AccountContext? + private let weatherEmojiLoadDisposable = MetaDisposable() + private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)? - + public init( presentationData: PresentationData, glass: Bool, @@ -79,7 +95,8 @@ public final class LocationMapHeaderNode: ASDisplayNode { updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (Bool) -> Void = { _ in }, - showPlacesInThisArea: @escaping () -> Void = {} + showPlacesInThisArea: @escaping () -> Void = {}, + weatherPressed: @escaping () -> Void = {} ) { self.presentationData = presentationData self.glass = glass @@ -88,6 +105,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.goToUserLocation = goToUserLocation self.setupProximityNotification = setupProximityNotification self.showPlacesInThisArea = showPlacesInThisArea + self.weatherPressed = weatherPressed self.mapNode = LocationMapNode() @@ -131,29 +149,35 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.placesBackgroundNode.displayWithoutProcessing = true self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.placesBackgroundNode.isUserInteractionEnabled = true - - self.placesButtonNode = HighlightableButtonNode() + + self.placesButtonNode = HighlightTrackingButtonNode() self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.medium(17.0), with: self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor : buttonColor, for: .normal) - - self.shadowNode = ASImageNode() - self.shadowNode.contentMode = .scaleToFill - self.shadowNode.displaysAsynchronously = false - self.shadowNode.displayWithoutProcessing = true - self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) - + + self.weatherButton = HighlightTrackingButton() + if glass { - self.optionsBackgroundView = GlassBackgroundView() + self.optionsBackgroundView = GlassContextExtractableContainer() self.optionsBackgroundNode.image = nil self.placesBackgroundView = GlassBackgroundView() self.placesBackgroundNode.image = nil + + self.weatherBackgroundView = GlassBackgroundView() } else { self.optionsBackgroundView = nil self.placesBackgroundView = nil + self.weatherBackgroundView = nil } - + super.init() - + + self.mapNode.visibleRegionDidChange = { [weak self] in + guard let self, self.glass, self.mapNode.mapMode == .satellite else { + return + } + self.requestLayout(transition: .immediate) + } + self.clipsToBounds = true self.addSubnode(self.mapNode) @@ -169,22 +193,35 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.view.addSubview(optionsBackgroundView) } self.addSubnode(self.optionsBackgroundNode) - self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode) - self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode) - self.optionsBackgroundNode.addSubnode(self.infoButtonNode) - self.optionsBackgroundNode.addSubnode(self.locationButtonNode) - self.optionsBackgroundNode.addSubnode(self.notificationButtonNode) + self.optionsBackgroundView?.contentView.addSubview(self.optionsSeparatorNode.view) + self.optionsBackgroundView?.contentView.addSubview(self.optionsSecondSeparatorNode.view) + self.optionsBackgroundView?.contentView.addSubview(self.infoButtonNode.view) + self.optionsBackgroundView?.contentView.addSubview(self.locationButtonNode.view) + self.optionsBackgroundView?.contentView.addSubview(self.notificationButtonNode.view) } } - + self.addSubnode(self.placesBackgroundNode) - self.placesBackgroundNode.addSubnode(self.placesButtonNode) - //self.addSubnode(self.shadowNode) - + self.placesBackgroundView?.contentView.addSubview(self.placesButtonNode.view) + if let weatherBackgroundView = self.weatherBackgroundView { + weatherBackgroundView.isHidden = true + self.view.addSubview(weatherBackgroundView) + } + self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside) self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside) self.notificationButtonNode.addTarget(self, action: #selector(self.notificationPressed), forControlEvents: .touchUpInside) self.placesButtonNode.addTarget(self, action: #selector(self.placesPressed), forControlEvents: .touchUpInside) + + self.weatherButton.addTarget(self, action: #selector(self.weatherButtonPressed), for: .touchUpInside) + } + + deinit { + self.weatherEmojiLoadDisposable.dispose() + } + + @objc private func weatherButtonPressed() { + self.weatherPressed() } public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) { @@ -214,6 +251,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor + self.mapNode.isDark = presentationData.theme.overallDarkAppearance self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor @@ -227,9 +265,45 @@ public final class LocationMapHeaderNode: ASDisplayNode { if !self.glass { self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) } - self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) } + func updateWeatherData(context: AccountContext, emoji: String, temperature: String, animated: Bool) { + let emojiFile = context.animatedEmojiStickersValue[emoji]?.first?.file._parse() + if self.weatherEmoji == emoji && self.weatherTemperature == temperature && self.weatherEmojiFile?.fileId == emojiFile?.fileId { + return + } + + if self.weatherEmojiFile?.fileId != emojiFile?.fileId { + if let emojiFile { + self.weatherEmojiLoadDisposable.set(context.engine.resources.fetch(reference: .standalone(resource: emojiFile.resource), userLocation: .other, userContentType: .sticker).start()) + } else { + self.weatherEmojiLoadDisposable.set(nil) + } + } + + self.weatherContext = context + self.weatherEmoji = emoji + self.weatherTemperature = temperature + self.weatherEmojiFile = emojiFile + self.requestLayout(transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate) + } + + func clearWeatherData(animated: Bool) { + if self.weatherEmoji == nil && self.weatherTemperature == nil { + return + } + self.weatherEmoji = nil + self.weatherTemperature = nil + self.weatherEmojiFile = nil + self.weatherContext = nil + self.weatherEmojiLoadDisposable.set(nil) + if let weatherIconView = self.weatherIcon.view as? LottieComponent.View { + weatherIconView.externalShouldPlay = false + } + self.weatherIcon.view?.removeFromSuperview() + self.requestLayout(transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate) + } + private func iconForTracking() -> UIImage? { switch self.trackingMode { case .none: @@ -241,7 +315,23 @@ public final class LocationMapHeaderNode: ASDisplayNode { } } - public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, controlsBottomPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) { + func requestLayout(transition: ContainedViewLayoutTransition) { + guard let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout else { + return + } + self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: transition) + } + + public func updateLayout( + layout: ContainerViewLayout, + navigationBarHeight: CGFloat, + topPadding: CGFloat, + controlsTopPadding: CGFloat, + controlsBottomPadding: CGFloat, + offset: CGFloat, + size: CGSize, + transition: ContainedViewLayoutTransition + ) { self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) let mapHeight: CGFloat = floor(layout.size.height * 1.3) + layout.intrinsicInsets.top * 2.0 @@ -252,21 +342,139 @@ public final class LocationMapHeaderNode: ASDisplayNode { let inset: CGFloat = 6.0 let placesButtonSize = CGSize(width: 180.0 + panelInset * 2.0, height: 45.0 + panelInset * 2.0) - let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: navigationBarHeight + topPadding - 6.0), size: placesButtonSize) + let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: navigationBarHeight + topPadding - 6.0), size: placesButtonSize).insetBy(dx: 5.0, dy: 6.0) transition.updateFrame(node: self.placesBackgroundNode, frame: placesButtonFrame) if let placesBackgroundView = self.placesBackgroundView { - let backgroundViewFrame = CGRect(origin: .zero, size: placesButtonFrame.size).insetBy(dx: 5.0, dy: 6.0) + let backgroundViewFrame = CGRect(origin: .zero, size: placesButtonFrame.size) transition.updateFrame(view: placesBackgroundView, frame: backgroundViewFrame) - placesBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: .immediate) + placesBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate) } - transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonSize)) + transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonFrame.size)) transition.updateAlpha(node: self.placesBackgroundNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) transition.updateAlpha(node: self.placesButtonNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) - transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0)) - + if let weatherBackgroundView = self.weatherBackgroundView { + let panelHeight: CGFloat = 36.0 + let horizontalInset: CGFloat = 10.0 + let labelSpacing: CGFloat = 5.0 + let iconSize = CGSize(width: floor(panelHeight * 0.71), height: floor(panelHeight * 0.71)) + let componentTransition = ComponentTransition(transition) + let temperatureSize = self.weatherTemperatureLabel.update( + transition: componentTransition, + component: AnyComponent(Text( + text: self.weatherTemperature ?? "", + font: Font.semibold(15.0), + color: self.presentationData.theme.rootController.navigationBar.primaryTextColor + )), + environment: {}, + containerSize: CGSize(width: 72.0, height: panelHeight) + ) + let emojiSize = self.weatherEmojiLabel.update( + transition: componentTransition, + component: AnyComponent(Text( + text: self.weatherEmoji ?? "", + font: Font.regular(18.0), + color: self.presentationData.theme.rootController.navigationBar.primaryTextColor + )), + environment: {}, + containerSize: iconSize + ) + let panelWidth = max(62.0, ceil(horizontalInset * 2.0 + iconSize.width + labelSpacing + temperatureSize.width)) + let panelFrame = CGRect( + x: layout.safeInsets.left + 16.0, + y: navigationBarHeight + topPadding + 14.0, + width: panelWidth, + height: panelHeight + ) + transition.updateFrame(view: weatherBackgroundView, frame: panelFrame) + + let weatherAlpha: CGFloat = self.weatherEmoji != nil && self.weatherTemperature != nil && size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0 + if weatherBackgroundView.isHidden && weatherAlpha > 0.0 { + weatherBackgroundView.isHidden = false + } + weatherBackgroundView.update(size: panelFrame.size, cornerRadius: panelFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, isVisible: weatherAlpha > 0.0, transition: ComponentTransition(transition)) + + let iconFrame = CGRect( + x: horizontalInset, + y: floorToScreenPixels((panelHeight - iconSize.height) / 2.0), + width: iconSize.width, + height: iconSize.height + ) + let temperatureFrame = CGRect( + x: iconFrame.maxX + labelSpacing, + y: floorToScreenPixels((panelHeight - temperatureSize.height) / 2.0), + width: temperatureSize.width, + height: temperatureSize.height + ) + let emojiFrame = CGRect( + x: iconFrame.minX + floorToScreenPixels((iconFrame.width - emojiSize.width) / 2.0), + y: iconFrame.minY + floorToScreenPixels((iconFrame.height - emojiSize.height) / 2.0), + width: emojiSize.width, + height: emojiSize.height + ) + if let weatherEmojiView = self.weatherEmojiLabel.view { + if weatherEmojiView.superview == nil { + weatherBackgroundView.contentView.addSubview(weatherEmojiView) + } + transition.updateFrame(view: weatherEmojiView, frame: emojiFrame) + } + if let weatherTemperatureView = self.weatherTemperatureLabel.view { + if weatherTemperatureView.superview == nil { + weatherBackgroundView.contentView.addSubview(weatherTemperatureView) + } + transition.updateFrame(view: weatherTemperatureView, frame: temperatureFrame) + } + + if let weatherContext = self.weatherContext, let weatherEmojiFile = self.weatherEmojiFile { + var weatherIconTransition = transition + let _ = self.weatherIcon.update( + transition: ComponentTransition(transition), + component: AnyComponent( + LottieComponent( + content: LottieComponent.ResourceContent(context: weatherContext, file: weatherEmojiFile, attemptSynchronously: false, providesPlaceholder: true), + placeholderColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.1), + renderingScale: 2.0, + loop: true + ) + ), + environment: {}, + containerSize: iconSize + ) + if let weatherIconView = self.weatherIcon.view { + if weatherIconView.superview == nil { + weatherIconTransition = .immediate + weatherBackgroundView.contentView.addSubview(weatherIconView) + } + weatherIconTransition.updateFrame(view: weatherIconView, frame: iconFrame) + ComponentTransition(transition).setAlpha(view: weatherIconView, alpha: 1.0) + if let weatherIconView = weatherIconView as? LottieComponent.View { + weatherIconView.externalShouldPlay = weatherAlpha > 0.0 + } + } + if let weatherEmojiView = self.weatherEmojiLabel.view { + componentTransition.setAlpha(view: weatherEmojiView, alpha: 0.0) + } + } else { + if let weatherIconView = self.weatherIcon.view { + componentTransition.setAlpha(view: weatherIconView, alpha: 0.0) + if let weatherIconView = weatherIconView as? LottieComponent.View { + weatherIconView.externalShouldPlay = false + } + } + if let weatherEmojiView = self.weatherEmojiLabel.view { + componentTransition.setAlpha(view: weatherEmojiView, alpha: 1.0) + } + } + componentTransition.setAlpha(view: weatherBackgroundView, alpha: weatherAlpha) + + if self.weatherButton.superview == nil { + weatherBackgroundView.contentView.addSubview(self.weatherButton) + } + self.weatherButton.frame = CGRect(origin: .zero, size: panelFrame.size) + } + if let options = self.options { let optionsSize = options.update( transition: ComponentTransition(transition), @@ -275,24 +483,35 @@ public final class LocationMapHeaderNode: ASDisplayNode { theme: self.presentationData.theme, strings: self.presentationData.strings, mapMode: self.mapNode.mapMode, + currentCoordinate: self.mapNode.mapCenterCoordinate, + trackingMode: self.mapNode.trackingMode, showMapModes: self.infoButtonNode.isSelected, + proximityNotification: self.proximityNotification, updateMapMode: { [weak self] mode in guard let self else { return } self.updateMapMode(mode) + self.requestLayout(transition: .immediate) }, goToUserLocation: { [weak self] in guard let self else { return } self.goToUserLocation() + self.requestLayout(transition: .immediate) }, requestedMapModes: { [weak self] in guard let self else { return } self.toggleMapModeSelection() + }, + setupProximityNotification: { [weak self] reset in + guard let self else { + return + } + self.setupProximityNotification(reset) } ) ), @@ -327,7 +546,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { if let optionsBackgroundView = self.optionsBackgroundView { let backgroundViewFrame = backgroundFrame.insetBy(dx: 4.0, dy: 4.0) transition.updateFrame(view: optionsBackgroundView, frame: backgroundViewFrame) - optionsBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.width * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: .immediate) + optionsBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.width * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate) } let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) @@ -343,12 +562,17 @@ public final class LocationMapHeaderNode: ASDisplayNode { } } } - - public func updateHighlight(_ highlighted: Bool) { - self.shadowNode.image = generateShadowImage(theme: self.presentationData.theme, highlighted: highlighted) - } - + public func proximityButtonFrame() -> CGRect? { + if let options = self.options { + guard let optionsView = options.view as? LocationOptionsComponent.View else { + return nil + } + return optionsView.proximityButtonFrame().flatMap { frame in + return optionsView.convert(frame, to: self.view) + } + } + if self.notificationButtonNode.alpha > 0.0 { return self.optionsBackgroundNode.view.convert(self.notificationButtonNode.frame, to: self.view) } else { @@ -376,34 +600,46 @@ public final class LocationMapHeaderNode: ASDisplayNode { } -public final class LocationOptionsComponent: Component { - public let theme: PresentationTheme - public let strings: PresentationStrings - public let mapMode: LocationMapMode - public let showMapModes: Bool - public let updateMapMode: (LocationMapMode) -> Void - public let goToUserLocation: () -> Void - public let requestedMapModes: () -> Void +final class LocationOptionsComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let mapMode: LocationMapMode + let currentCoordinate: CLLocationCoordinate2D? + let trackingMode: LocationTrackingMode + let showMapModes: Bool + let proximityNotification: Bool? + let updateMapMode: (LocationMapMode) -> Void + let goToUserLocation: () -> Void + let requestedMapModes: () -> Void + let setupProximityNotification: (Bool) -> Void - public init( + init( theme: PresentationTheme, strings: PresentationStrings, mapMode: LocationMapMode, + currentCoordinate: CLLocationCoordinate2D?, + trackingMode: LocationTrackingMode, showMapModes: Bool, + proximityNotification: Bool?, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, - requestedMapModes: @escaping () -> Void + requestedMapModes: @escaping () -> Void, + setupProximityNotification: @escaping (Bool) -> Void ) { self.theme = theme self.strings = strings self.mapMode = mapMode + self.currentCoordinate = currentCoordinate + self.trackingMode = trackingMode self.showMapModes = showMapModes + self.proximityNotification = proximityNotification self.updateMapMode = updateMapMode self.goToUserLocation = goToUserLocation self.requestedMapModes = requestedMapModes + self.setupProximityNotification = setupProximityNotification } - public static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool { + static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool { if lhs.theme !== rhs.theme { return false } @@ -413,13 +649,43 @@ public final class LocationOptionsComponent: Component { if lhs.mapMode != rhs.mapMode { return false } + if lhs.mapMode == .satellite && LocationOptionsComponent.satelliteIconName(coordinate: lhs.currentCoordinate) != LocationOptionsComponent.satelliteIconName(coordinate: rhs.currentCoordinate) { + return false + } + if lhs.trackingMode != rhs.trackingMode { + return false + } if lhs.showMapModes != rhs.showMapModes { return false } + if lhs.proximityNotification != rhs.proximityNotification { + return false + } return true } - public final class View: HighlightTrackingButton { + private static func satelliteIconName(coordinate: CLLocationCoordinate2D?) -> String { + guard let coordinate else { + return "Location/OptionGlobeEurope" + } + + var longitude = coordinate.longitude.truncatingRemainder(dividingBy: 360.0) + if longitude < -180.0 { + longitude += 360.0 + } else if longitude > 180.0 { + longitude -= 360.0 + } + + if longitude >= -170.0 && longitude < -25.0 { + return "Location/OptionGlobeAmerica" + } else if longitude >= -25.0 && longitude < 60.0 { + return "Location/OptionGlobeEurope" + } else { + return "Location/OptionGlobeAsia" + } + } + + final class View: HighlightTrackingButton { private let containerView: GlassBackgroundContainerView private let backgroundView: GlassBackgroundView private let clippingView: UIView @@ -427,6 +693,7 @@ public final class LocationOptionsComponent: Component { private let collapsedContainerView = UIView() private var mapModeButton = ComponentView() private var trackingButton = ComponentView() + private var notificationButton = ComponentView() private let expandedContainerView = UIView() private let checkIcon = UIImageView() @@ -448,7 +715,7 @@ public final class LocationOptionsComponent: Component { self.addSubview(self.containerView) self.containerView.contentView.addSubview(self.backgroundView) - self.addSubview(self.clippingView) + self.backgroundView.contentView.addSubview(self.clippingView) self.clippingView.addSubview(self.collapsedContainerView) self.clippingView.addSubview(self.expandedContainerView) } @@ -566,7 +833,7 @@ public final class LocationOptionsComponent: Component { transition.setFrame(view: hybridButtonView, frame: hybridButtonFrame) } - let normalSize = CGSize(width: 40.0, height: 80.0) + let normalSize = CGSize(width: 40.0, height: component.proximityNotification != nil ? 120.0 : 80.0) let expandedFrame = CGRect(origin: .zero, size: expandedSize) let collapsedFrame = CGRect(origin: CGPoint(x: expandedSize.width - normalSize.width, y: expandedSize.height - normalSize.height), size: normalSize) @@ -575,17 +842,26 @@ public final class LocationOptionsComponent: Component { self.backgroundView.update(size: effectiveBackgroundFrame.size, cornerRadius: cornerRadius, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition) transition.setFrame(view: self.backgroundView, frame: effectiveBackgroundFrame) - transition.setFrame(view: self.clippingView, frame: effectiveBackgroundFrame) + transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: effectiveBackgroundFrame.size)) transition.setFrame(view: self.expandedContainerView, frame: expandedFrame.offsetBy(dx: effectiveBackgroundFrame.width - expandedFrame.width, dy: effectiveBackgroundFrame.height - expandedFrame.height)) transition.setFrame(view: self.collapsedContainerView, frame: collapsedFrame.offsetBy(dx: -effectiveBackgroundFrame.minX, dy: -effectiveBackgroundFrame.minY)) + var mapModeIconName: String + switch component.mapMode { + case .map: + mapModeIconName = "Location/OptionMap" + case .satellite: + mapModeIconName = LocationOptionsComponent.satelliteIconName(coordinate: component.currentCoordinate) + case .hybrid: + mapModeIconName = "Location/OptionHybrid" + } let mapModeButtonSize = self.mapModeButton.update( transition: transition, component: AnyComponent( PlainButtonComponent( content: AnyComponent( - BundleIconComponent(name: "Location/OptionMap", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62)) + BundleIconComponent(name: mapModeIconName, tintColor: component.theme.chat.inputPanel.panelControlColor) ), minSize: CGSize(width: 40.0, height: 40.0), action: { [weak self] in @@ -608,13 +884,22 @@ public final class LocationOptionsComponent: Component { } transition.setFrame(view: mapModeButtonView, frame: mapModeButtonFrame) } - + + let trackingModeIconName: String + switch component.trackingMode { + case .none: + trackingModeIconName = "Location/OptionLocate" + case .follow: + trackingModeIconName = "Location/OptionLocating" + case .followWithHeading: + trackingModeIconName = "Location/OptionTracking" + } let trackingButtonSize = self.trackingButton.update( transition: transition, component: AnyComponent( PlainButtonComponent( content: AnyComponent( - BundleIconComponent(name: "Location/OptionLocate", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62)) + BundleIconComponent(name: trackingModeIconName, tintColor: component.theme.chat.inputPanel.panelControlColor) ), minSize: CGSize(width: 40.0, height: 40.0), action: { [weak self] in @@ -638,6 +923,41 @@ public final class LocationOptionsComponent: Component { transition.setFrame(view: trackingButtonView, frame: trackingButtonFrame) } + if let proximityNotification = component.proximityNotification { + let notificationIconName = proximityNotification ? "Chat/Title Panels/MuteIcon" : "Location/NotificationIcon" + let notificationButtonSize = self.notificationButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + BundleIconComponent(name: notificationIconName, tintColor: component.theme.chat.inputPanel.panelControlColor) + ), + minSize: CGSize(width: 40.0, height: 40.0), + action: { [weak self] in + guard let self, let component = self.component, let proximityNotification = component.proximityNotification else { + return + } + component.setupProximityNotification(proximityNotification) + }, + animateAlpha: true, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let notificationButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 80.0), size: notificationButtonSize) + if let notificationButtonView = self.notificationButton.view { + if notificationButtonView.superview == nil { + self.collapsedContainerView.addSubview(notificationButtonView) + } + transition.setFrame(view: notificationButtonView, frame: notificationButtonFrame) + transition.setAlpha(view: notificationButtonView, alpha: 1.0) + } + } else if let notificationButtonView = self.notificationButton.view { + transition.setAlpha(view: notificationButtonView, alpha: 0.0) + } + transition.setAlpha(view: self.collapsedContainerView, alpha: component.showMapModes ? 0.0 : 1.0) transition.setAlpha(view: self.expandedContainerView, alpha: component.showMapModes ? 1.0 : 0.0) @@ -650,6 +970,16 @@ public final class LocationOptionsComponent: Component { public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { return self.backgroundView.frame.contains(point) } + + func proximityButtonFrame() -> CGRect? { + guard let component = self.component, component.proximityNotification != nil, !component.showMapModes else { + return nil + } + guard let notificationButtonView = self.notificationButton.view, notificationButtonView.superview != nil, notificationButtonView.alpha > 0.0 else { + return nil + } + return notificationButtonView.convert(notificationButtonView.bounds, to: self) + } } public func makeView() -> View { diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 072865eccd..35caf43aef 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -15,9 +15,9 @@ public enum LocationMapMode { var mapType: MKMapType { switch self { case .satellite: - return .satellite + return .satelliteFlyover case .hybrid: - return .hybrid + return .hybridFlyover default: return .standard } @@ -135,6 +135,7 @@ private func generateProximityDim(size: CGSize) -> UIImage { } protocol MKMapViewDelegateTarget: AnyObject { + func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) @@ -155,6 +156,10 @@ private final class MKMapViewDelegateImpl: NSObject, MKMapViewDelegate { super.init() } + func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + self.target?.mapViewDidChangeVisibleRegion(mapView) + } + func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { self.target?.mapView(mapView, regionWillChangeAnimated: animated) } @@ -237,12 +242,41 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { return self.view as? LocationMapView } + private func updateUserHeadingTransform(mapHeading: CGFloat) { + if let heading = self.userHeading { + self.headingArrowView?.isHidden = false + self.headingArrowView?.transform = CGAffineTransform(rotationAngle: (heading - mapHeading) / 180.0 * CGFloat.pi) + } else { + self.headingArrowView?.isHidden = true + self.headingArrowView?.transform = CGAffineTransform.identity + } + } + + private func updateHeadingTransforms(mapHeading: CGFloat? = nil) { + guard let mapView = self.mapView else { + return + } + + let mapHeading = mapHeading ?? CGFloat(mapView.camera.heading) + self.updateUserHeadingTransform(mapHeading: mapHeading) + + for annotation in mapView.annotations { + if let annotationView = mapView.view(for: annotation) as? LocationPinAnnotationView { + annotationView.updateMapHeading(mapHeading) + } + } + + self.pickerAnnotationView?.updateMapHeading(mapHeading) + self.customUserLocationAnnotationView?.updateMapHeading(mapHeading) + } + var returnedToUserLocation = true var ignoreRegionChanges = false var isDragging = false var onTouch: (() -> Void)? var beganInteractiveDragging: (() -> Void)? var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)? + var visibleRegionDidChange: (() -> Void)? var disableHorizontalTransitionGesture = false var annotationSelected: ((LocationPinAnnotation?) -> Void)? @@ -369,6 +403,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { self.onTouch?() } self.view.addSubview(self.pickerAnnotationContainerView) + self.updateHeadingTransforms() } var isRotateEnabled: Bool = true { @@ -383,6 +418,12 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { } } + var isDark: Bool = false { + didSet { + self.mapView?.overrideUserInterfaceStyle = self.isDark ? .dark : .light + } + } + public var trackingMode: LocationTrackingMode = .none { didSet { self.mapView?.userTrackingMode = self.trackingMode.userTrackingMode @@ -391,6 +432,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { } else if self.trackingMode != .followWithHeading && self.headingArrowView?.image == nil { self.headingArrowView?.image = generateHeadingArrowImage() } + self.updateHeadingTransforms() } } @@ -460,6 +502,10 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { } } + public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { + self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading)) + } + public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) { guard !self.ignoreRegionChanges, let scrollView = mapView.subviews.first, let gestureRecognizers = scrollView.gestureRecognizers else { return @@ -478,6 +524,9 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { } public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading)) + self.visibleRegionDidChange?() + let wasDragging = self.isDragging if self.isDragging { self.isDragging = false @@ -543,6 +592,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { container.insertSubview(self.proximityDimView, at: 0) } } + self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading)) } public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { @@ -658,6 +708,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0 + 16.0) self.pickerAnnotationContainerView.addSubview(pickerAnnotationView) self.pickerAnnotationView = pickerAnnotationView + self.updateHeadingTransforms() } else { self.pickerAnnotationView?.removeFromSuperview() self.pickerAnnotationView = nil @@ -696,6 +747,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { self.customUserLocationAnnotationView = annotationView self.pickerAnnotationView?.annotation = annotation + self.updateHeadingTransforms() } else { self.customUserLocationAnnotationView?.removeFromSuperview() self.customUserLocationAnnotationView = nil @@ -705,13 +757,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { public var userHeading: CGFloat? = nil { didSet { - if let heading = self.userHeading { - self.headingArrowView?.isHidden = false - self.headingArrowView?.transform = CGAffineTransform(rotationAngle: CGFloat(heading / 180.0 * CGFloat.pi)) - } else { - self.headingArrowView?.isHidden = true - self.headingArrowView?.transform = CGAffineTransform.identity - } + self.updateHeadingTransforms() } } @@ -869,7 +915,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget { } if let compassView = self.compassView { - transition.updateFrame(view: compassView, frame: CGRect(origin: CGPoint(x: size.width - compassView.frame.width - 11.0, y: inset + 110.0 + topPadding), size: compassView.frame.size)) + transition.updateFrame(view: compassView, frame: CGRect(origin: CGPoint(x: size.width - compassView.frame.width - 16.0, y: inset + 14.0 + topPadding), size: compassView.frame.size)) } self.applyPendingSetMapCenter() diff --git a/submodules/LocationUI/Sources/LocationOptionsNode.swift b/submodules/LocationUI/Sources/LocationOptionsNode.swift deleted file mode 100644 index 1036a19a41..0000000000 --- a/submodules/LocationUI/Sources/LocationOptionsNode.swift +++ /dev/null @@ -1,64 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import TelegramPresentationData -import SegmentedControlNode - -public final class LocationOptionsNode: ASDisplayNode { - private var presentationData: PresentationData - - private let backgroundNode: NavigationBackgroundNode - private let separatorNode: ASDisplayNode - private let segmentedControlNode: SegmentedControlNode - - public init(presentationData: PresentationData, hasBackground: Bool = true, updateMapMode: @escaping (LocationMapMode) -> Void) { - self.presentationData = presentationData - - self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor) - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - - self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: self.presentationData.theme), items: [SegmentedControlItem(title: self.presentationData.strings.Map_Map), SegmentedControlItem(title: self.presentationData.strings.Map_Satellite), SegmentedControlItem(title: self.presentationData.strings.Map_Hybrid)], selectedIndex: 0) - - super.init() - - if hasBackground { - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - } - - self.addSubnode(self.segmentedControlNode) - - self.segmentedControlNode.selectedIndexChanged = { index in - switch index { - case 0: - updateMapMode(.map) - case 1: - updateMapMode(.satellite) - case 2: - updateMapMode(.hybrid) - default: - break - } - } - } - - public func updatePresentationData(_ presentationData: PresentationData) { - self.presentationData = presentationData - self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor - self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme)) - } - - public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) - self.backgroundNode.update(size: size, transition: transition) - - transition.updateFrame(node: self.separatorNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) - - let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width - 16.0 - leftInset - rightInset), transition: .immediate) - self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: 0.0), size: controlSize) - } -} diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 634e4c5d3f..09634f18a5 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -30,11 +30,10 @@ class LocationPickerInteraction { let updateSearchQuery: (String) -> Void let dismissSearch: () -> Void let dismissInput: () -> Void - let updateSendActionHighlight: (Bool) -> Void let openHomeWorkInfo: () -> Void let showPlacesInThisArea: () -> Void - init(sendLocation: @escaping (CLLocationCoordinate2D, String?, MapGeoAddress?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D, Bool) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) { + init(sendLocation: @escaping (CLLocationCoordinate2D, String?, MapGeoAddress?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D, Bool) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) { self.sendLocation = sendLocation self.sendLiveLocation = sendLiveLocation self.sendVenue = sendVenue @@ -46,7 +45,6 @@ class LocationPickerInteraction { self.updateSearchQuery = updateSearchQuery self.dismissSearch = dismissSearch self.dismissInput = dismissInput - self.updateSendActionHighlight = updateSendActionHighlight self.openHomeWorkInfo = openHomeWorkInfo self.showPlacesInThisArea = showPlacesInThisArea } @@ -104,7 +102,15 @@ public final class LocationPickerController: ViewController, AttachmentContainab case legacy } - public init(context: AccountContext, style: Style = .legacy, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) { + public init( + context: AccountContext, + style: Style = .legacy, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + mode: LocationPickerMode, + source: Source = .generic, + initialLocation: CLLocationCoordinate2D? = nil, + completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void + ) { self.context = context self.style = style self.mode = mode @@ -119,11 +125,12 @@ public final class LocationPickerController: ViewController, AttachmentContainab super.init(navigationBarPresentationData: navigationBarPresentationData) self.navigationPresentation = .modal + self._hasGlassStyle = true - if case .glass = style { + if case .share = mode { self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) } else { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) } self.updateBarButtons() @@ -179,44 +186,25 @@ public final class LocationPickerController: ViewController, AttachmentContainab guard let strongSelf = self, authorized else { return } - let controller = ActionSheetController(presentationData: strongSelf.presentationData) var title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription if case let .share(peer, _, _) = strongSelf.mode, let peer = peer, case .user = peer { title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string } - let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in - controller?.dismissAnimated() - guard let self, let controller else { - return + let sourceView = strongSelf.controllerNode.liveLocationActionSourceView(extend: false) ?? strongSelf.view + let controller = makeLiveLocationDurationContextController( + presentationData: strongSelf.presentationData, + sourceView: sourceView!, + title: title, + selectPeriod: { [weak self] period in + guard let self else { + return + } + self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil) + self.dismiss() } - controller.dismissAnimated() - self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil) - self.dismiss() - } - - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: title, font: .large, parseMarkdown: true), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: { sendLiveLocationImpl(15 * 60) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: { - sendLiveLocationImpl(60 * 60 - 1) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: { - sendLiveLocationImpl(8 * 60 * 60) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: { - sendLiveLocationImpl(liveLocationIndefinitePeriod) - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in - controller?.dismissAnimated() - }) - ]) - ]) - strongSelf.present(controller, in: .window(.root)) + ) + strongSelf.presentInGlobalOverlay(controller) }) }, sendVenue: { [weak self] venue, queryId, resultId in guard let strongSelf = self else { @@ -316,11 +304,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab } self.searchNavigationContentNode?.deactivate() self.controllerNode.deactivateInput() - }, updateSendActionHighlight: { [weak self] highlighted in - guard let strongSelf = self else { - return - } - strongSelf.controllerNode.updateSendActionHighlight(highlighted) }, openHomeWorkInfo: { [weak self] in guard let strongSelf = self else { return diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index b9a825cc98..5123a26635 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -171,10 +171,9 @@ private enum LocationPickerEntry: Comparable, Identifiable { if let coordinate = coordinate { interaction?.sendLocation(coordinate, name, address?.withUpdated(street: nil)) } - }, highlighted: { highlighted in - interaction?.updateSendActionHighlight(highlighted) + }, highlighted: { _ in }) - case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, address, isTop): + case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, address, _): let icon: LocationActionListItemIcon if let venue = venue { icon = .venue(venue) @@ -187,10 +186,7 @@ private enum LocationPickerEntry: Comparable, Identifiable { } else if let coordinate { interaction?.sendLocation(coordinate, name, address) } - }, highlighted: { highlighted in - if isTop { - interaction?.updateSendActionHighlight(highlighted) - } + }, highlighted: { _ in }) case let .liveLocation(_, title, subtitle, coordinate): return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: { @@ -361,7 +357,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private var sendButton: ComponentView? - private let optionsNode: LocationOptionsNode private(set) var searchContainerNode: LocationSearchContainerNode? private var placeholderBackgroundNode: NavigationBackgroundNode? @@ -422,9 +417,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM showPlacesInThisArea: interaction.showPlacesInThisArea ) self.headerNode.mapNode.isRotateEnabled = false - - self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode) - + self.shadeNode = ASDisplayNode() self.shadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.shadeNode.alpha = 0.0 @@ -443,7 +436,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.addSubnode(self.listNode) self.addSubnode(self.headerNode) - //self.addSubnode(self.optionsNode) + self.listNode.addSubnode(self.emptyResultsTextNode) self.shadeNode.addSubnode(self.innerShadeNode) self.addSubnode(self.shadeNode) @@ -1099,7 +1092,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.headerNode.updatePresentationData(self.presentationData) - self.optionsNode.updatePresentationData(self.presentationData) self.shadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.searchContainerNode?.updatePresentationData(self.presentationData) @@ -1214,6 +1206,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM return (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround } + func liveLocationActionSourceView(extend: Bool) -> UIView? { + var result: UIView? + self.listNode.forEachItemNode { itemNode in + if result == nil, let itemNode = itemNode as? LocationActionListItemNode { + result = itemNode.liveLocationContextSourceView(extend: extend) + } + } + return result + } + func requestLayout(transition: ContainedViewLayoutTransition) { if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition) @@ -1291,12 +1293,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } } - let optionsOffset: CGFloat = self.state.displayingMapModeOptions ? navigationHeight : navigationHeight - optionsHeight - let optionsFrame = CGRect(x: 0.0, y: optionsOffset, width: layout.size.width, height: optionsHeight) - transition.updateFrame(node: self.optionsNode, frame: optionsFrame) - self.optionsNode.updateLayout(size: optionsFrame.size, leftInset: insets.left, rightInset: insets.right, transition: transition) - self.optionsNode.isUserInteractionEnabled = self.state.displayingMapModeOptions - if let searchContainerNode = self.searchContainerNode { searchContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) searchContainerNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationHeight, transition: transition) @@ -1655,11 +1651,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } } - func updateSendActionHighlight(_ highlighted: Bool) { - self.headerNode.updateHighlight(highlighted) - self.shadeNode.backgroundColor = highlighted ? self.presentationData.theme.list.itemHighlightedBackgroundColor : self.presentationData.theme.list.plainBackgroundColor - } - func goToUserLocation() { guard let controller = self.controller else { return diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index df3b15b229..6b74ecc142 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -11,7 +11,6 @@ import AppBundle import CoreLocation import PresentationDataUtils import OpenInExternalAppUI - import DeviceAccess import UndoUI import MapKit @@ -32,13 +31,7 @@ public class LocationViewParams { } } -enum LocationViewRightBarButton { - case none - case share - case showAll -} - -class LocationViewInteraction { +final class LocationViewInteraction { let toggleMapModeSelection: () -> Void let updateMapMode: (LocationMapMode) -> Void let toggleTrackingMode: () -> Void @@ -46,13 +39,11 @@ class LocationViewInteraction { let requestDirections: (TelegramMediaMap, String?, OpenInLocationDirections) -> Void let share: () -> Void let setupProximityNotification: (Bool, EngineMessage.Id?) -> Void - let updateSendActionHighlight: (Bool) -> Void let sendLiveLocation: (Int32?, Bool, EngineMessage.Id?) -> Void let stopLiveLocation: () -> Void - let updateRightBarButton: (LocationViewRightBarButton) -> Void let present: (ViewController) -> Void - init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?, Bool, EngineMessage.Id?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void, present: @escaping (ViewController) -> Void) { + init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, sendLiveLocation: @escaping (Int32?, Bool, EngineMessage.Id?) -> Void, stopLiveLocation: @escaping () -> Void, present: @escaping (ViewController) -> Void) { self.toggleMapModeSelection = toggleMapModeSelection self.updateMapMode = updateMapMode self.toggleTrackingMode = toggleTrackingMode @@ -60,10 +51,8 @@ class LocationViewInteraction { self.requestDirections = requestDirections self.share = share self.setupProximityNotification = setupProximityNotification - self.updateSendActionHighlight = updateSendActionHighlight self.sendLiveLocation = sendLiveLocation self.stopLiveLocation = stopLiveLocation - self.updateRightBarButton = updateRightBarButton self.present = present } } @@ -74,59 +63,49 @@ public final class LocationViewController: ViewController { } private let context: AccountContext public var subject: EngineMessage + private var basePresentationData: PresentationData private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + private var currentMapMode: LocationMapMode = .map private var showAll: Bool - private let isStoryLocation: Bool private let isPreview: Bool private let locationManager = LocationManager() private var interaction: LocationViewInteraction? - - private var rightBarButtonAction: LocationViewRightBarButton = .none - + public var dismissed: () -> Void = {} - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: EngineMessage, isStoryLocation: Bool = false, isPreview: Bool = false, params: LocationViewParams) { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + subject: EngineMessage, + isPreview: Bool = false, + params: LocationViewParams + ) { self.context = context self.subject = subject self.showAll = params.showAll - self.isStoryLocation = isStoryLocation self.isPreview = isPreview - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + let initialPresentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + self.basePresentationData = initialPresentationData + self.presentationData = LocationViewController.effectivePresentationData(initialPresentationData, mapMode: .map) - let navigationBarPresentationData: NavigationBarPresentationData? - if !isPreview { - navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) - } else { - navigationBarPresentationData = nil - } + super.init(navigationBarPresentationData: nil) - super.init(navigationBarPresentationData: navigationBarPresentationData) + self._hasGlassStyle = true + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationPresentation = .modal - - if !self.isPreview { - self.title = self.presentationData.strings.Map_LocationTitle - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) - } - + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) |> deliverOnMainQueue).start(next: { [weak self] presentationData in - guard let strongSelf = self, strongSelf.presentationData.theme !== presentationData.theme else { + guard let strongSelf = self, strongSelf.basePresentationData.theme !== presentationData.theme else { return } - strongSelf.presentationData = presentationData - - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)), transition: .immediate) - - strongSelf.updateRightBarButton() - - if strongSelf.isNodeLoaded { - strongSelf.controllerNode.updatePresentationData(presentationData) - } + strongSelf.basePresentationData = presentationData + strongSelf.updateEffectivePresentationData(animated: true) }) self.interaction = LocationViewInteraction(toggleMapModeSelection: { [weak self] in @@ -142,12 +121,14 @@ public final class LocationViewController: ViewController { guard let strongSelf = self else { return } + strongSelf.currentMapMode = mode strongSelf.controllerNode.updateState { state in var state = state state.mapMode = mode state.displayingMapModeOptions = false return state } + strongSelf.updateEffectivePresentationData(animated: true) }, toggleTrackingMode: { [weak self] in guard let strongSelf = self else { return @@ -199,7 +180,7 @@ public final class LocationViewController: ViewController { } } } else { - strongSelf.present(OpenInActionSheetController(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: directions), additionalAction: nil, openUrl: params.openUrl), in: .window(.root), with: nil) + strongSelf.push(OpenInOptionsScreen(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: directions), additionalAction: nil, openUrl: params.openUrl)) } }, share: { [weak self] in guard let strongSelf = self else { @@ -209,7 +190,7 @@ public final class LocationViewController: ViewController { let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: { strongSelf.present(context.sharedContext.makeShareController(context: context, params: ShareControllerParams(subject: .mapMedia(location), externalShare: true)), in: .window(.root), with: nil) }) - strongSelf.present(OpenInActionSheetController(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: nil), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil) + strongSelf.push(OpenInOptionsScreen(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: nil), additionalAction: shareAction, openUrl: params.openUrl)) } }, setupProximityNotification: { [weak self] reset, messageId in guard let strongSelf = self else { @@ -265,17 +246,24 @@ public final class LocationViewController: ViewController { } strongSelf.controllerNode.setProximityIndicator(radius: 0) - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.subject.id.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var compactDisplayTitle: String? - if let peer = peer as? TelegramUser { - compactDisplayTitle = EnginePeer(peer).compactDisplayTitle + if case .user = peer { + compactDisplayTitle = peer.compactDisplayTitle } - + let controller = LocationDistancePickerScreen(context: context, style: .default, compactDisplayTitle: compactDisplayTitle, distances: strongSelf.controllerNode.headerNode.mapNode.distancesToAllAnnotations, updated: { [weak self] distance in guard let strongSelf = self else { return @@ -346,11 +334,6 @@ public final class LocationViewController: ViewController { }) }) } - }, updateSendActionHighlight: { [weak self] highlighted in - guard let strongSelf = self else { - return - } - strongSelf.controllerNode.updateSendActionHighlight(highlighted) }, sendLiveLocation: { [weak self] distance, extend, messageId in guard let strongSelf = self else { return @@ -370,17 +353,24 @@ public final class LocationViewController: ViewController { params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 30 * 60, proximityNotificationRadius: distance)) }) - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.subject.id.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var compactDisplayTitle: String? - if let peer = peer as? TelegramUser { - compactDisplayTitle = EnginePeer(peer).compactDisplayTitle + if case .user = peer { + compactDisplayTitle = peer.compactDisplayTitle } - + var text: String let distanceString = shortStringForDistance(strings: strongSelf.presentationData.strings, distance: distance) if let compactDisplayTitle = compactDisplayTitle { @@ -407,74 +397,56 @@ public final class LocationViewController: ViewController { ) }) } else { - let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in - let controller = ActionSheetController(presentationData: strongSelf.presentationData) var title: String if extend { title = strongSelf.presentationData.strings.Map_LiveLocationExtendDescription } else { title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription - if let user = peer as? TelegramUser { - title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(EnginePeer(user).compactDisplayTitle).string + if case .user = peer { + title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string } } - - let sendLiveLocationImpl: (Int32) -> Void = { [weak controller] period in - controller?.dismissAnimated() - - if extend { - if let messageId { - let _ = context.engine.messages.requestEditLiveLocation(messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: nil, extendPeriod: period).start() + + let sourceView = strongSelf.controllerNode.liveLocationActionSourceView(extend: extend) ?? strongSelf.view + let controller = makeLiveLocationDurationContextController( + presentationData: strongSelf.presentationData, + sourceView: sourceView!, + title: title, + selectPeriod: { [weak self] period in + guard let strongSelf = self else { + return } - } else { - let _ = (strongSelf.controllerNode.coordinate - |> deliverOnMainQueue).start(next: { coordinate in - params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period)) - }) - strongSelf.controllerNode.showAll() + if extend { + if let messageId { + let _ = context.engine.messages.requestEditLiveLocation(messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: nil, extendPeriod: period).start() + } + } else { + let _ = (strongSelf.controllerNode.coordinate + |> deliverOnMainQueue).start(next: { coordinate in + params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period)) + }) + + strongSelf.controllerNode.showAll() + } } - } - - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: title, font: .large, parseMarkdown: true), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: { - sendLiveLocationImpl(15 * 60) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: { - sendLiveLocationImpl(60 * 60 - 1) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: { - sendLiveLocationImpl(8 * 60 * 60) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: { - sendLiveLocationImpl(liveLocationIndefinitePeriod) - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in - controller?.dismissAnimated() - }) - ]) - ]) - strongSelf.present(controller, in: .window(.root)) + ) + strongSelf.presentInGlobalOverlay(controller) }) } }) }, stopLiveLocation: { [weak self] in params.stopLiveLocation(nil) self?.dismiss() - }, updateRightBarButton: { [weak self] action in - guard let strongSelf = self else { - return - } - - if action != strongSelf.rightBarButtonAction { - strongSelf.rightBarButtonAction = action - strongSelf.updateRightBarButton() - } }, present: { [weak self] c in if let strongSelf = self { strongSelf.present(c, in: .window(.root)) @@ -495,7 +467,40 @@ public final class LocationViewController: ViewController { deinit { self.presentationDataDisposable?.dispose() } - + + private static func effectivePresentationData(_ presentationData: PresentationData, mapMode: LocationMapMode) -> PresentationData { + switch mapMode { + case .satellite, .hybrid: + if presentationData.theme.overallDarkAppearance { + return presentationData + } + let darkTheme = customizeDefaultDarkPresentationTheme( + theme: defaultDarkPresentationTheme, + editing: false, + title: nil, + accentColor: presentationData.theme.list.itemAccentColor, + backgroundColors: [], + bubbleColors: [], + animateBubbleColors: false, + wallpaper: nil, + baseColor: nil + ) + return presentationData.withUpdated(theme: darkTheme) + case .map: + return presentationData + } + } + + private func updateEffectivePresentationData(animated: Bool) { + let presentationData = LocationViewController.effectivePresentationData(self.basePresentationData, mapMode: self.currentMapMode) + self.presentationData = presentationData + self.statusBar.updateStatusBarStyle(presentationData.theme.rootController.statusBarStyle.style, animated: animated) + + if self.isNodeLoaded { + self.controllerNode.updatePresentationData(presentationData) + } + } + public func goToUserLocation(visibleRadius: Double? = nil) { } @@ -515,7 +520,7 @@ public final class LocationViewController: ViewController { return } - self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager, isStoryLocation: self.isStoryLocation, isPreview: self.isPreview) + self.displayNode = LocationViewControllerNode(context: self.context, controller: self, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager, isPreview: self.isPreview) self.displayNodeDidLoad() self.controllerNode.onAnnotationsReady = { [weak self] in @@ -524,23 +529,6 @@ public final class LocationViewController: ViewController { } strongSelf.controllerNode.showAll() } - - self.controllerNode.headerNode.mapNode.disableHorizontalTransitionGesture = self.isStoryLocation - } - - private func updateRightBarButton() { - guard !self.isPreview else { - return - } - switch self.rightBarButtonAction { - case .none: - self.navigationItem.rightBarButtonItem = nil - case .share: - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationShareIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.sharePressed)) - self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.VoiceOver_MessageContextShare - case .showAll: - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Map_LiveLocationShowAll, style: .plain, target: self, action: #selector(self.showAllPressed)) - } } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -548,19 +536,7 @@ public final class LocationViewController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } - - @objc private func cancelPressed() { - self.dismiss() - } - - @objc private func sharePressed() { - self.interaction?.share() - } - - @objc private func showAllPressed() { - self.controllerNode.showAll() - } - + private var didDismiss = false public override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) @@ -574,4 +550,3 @@ public final class LocationViewController: ViewController { super.dismiss(completion: completion) } } - diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index f8c143d244..bb3bcafb7d 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -17,6 +17,13 @@ import CoreLocation import Geocoding import DeviceAccess import TooltipUI +import ComponentFlow +import GlassControls +import BundleIconComponent +import EdgeEffect +import MultilineTextComponent +import GlassBackgroundComponent +import Weather func getLocation(from message: EngineMessage) -> TelegramMediaMap? { if let poll = message.media.first(where: { $0 is TelegramMediaPoll } ) as? TelegramMediaPoll, let map = poll.attachedMedia as? TelegramMediaMap { @@ -52,9 +59,9 @@ public enum LocationViewEntryId: Hashable { } public enum LocationViewEntry: Comparable, Identifiable { - case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Bool) + case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, Bool) case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?, Bool, EngineMessage.Id?) - case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Int) + case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, Int) public var stableId: LocationViewEntryId { switch self { @@ -62,15 +69,15 @@ public enum LocationViewEntry: Comparable, Identifiable { return .info case let .toggleLiveLocation(_, _, _, _, _, additional, _): return .toggleLiveLocation(additional) - case let .liveLocation(_, _, _, message, _, _, _, _, _): + case let .liveLocation(_, _, _, message, _, _, _, _): return .liveLocation(message.stableId) } } public static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool { switch lhs { - case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsHasEta): - if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta { + case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsWalkingTime, lhsHasEta): + if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta { return true } else { return false @@ -81,8 +88,8 @@ public enum LocationViewEntry: Comparable, Identifiable { } else { return false } - case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsIndex): - if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex { + case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsWalkingTime, lhsIndex): + if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex { return true } else { return false @@ -108,11 +115,11 @@ public enum LocationViewEntry: Comparable, Identifiable { case .liveLocation: return true } - case let .liveLocation(_, _, _, _, _, _, _, _, lhsIndex): + case let .liveLocation(_, _, _, _, _, _, _, lhsIndex): switch rhs { case .info, .toggleLiveLocation: return false - case let .liveLocation(_, _, _, _, _, _, _, _, rhsIndex): + case let .liveLocation(_, _, _, _, _, _, _, rhsIndex): return lhsIndex < rhsIndex } } @@ -120,7 +127,7 @@ public enum LocationViewEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem { switch self { - case let .info(_, location, address, distance, drivingTime, transitTime, walkingTime, hasEta): + case let .info(_, location, address, distance, drivingTime, walkingTime, hasEta): let addressString: String? if let address = address { addressString = address @@ -133,12 +140,10 @@ public enum LocationViewEntry: Comparable, Identifiable { } else { distanceString = nil } - return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, hasEta: hasEta, action: { + return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, walkingTime: walkingTime, hasEta: hasEta, action: { interaction?.goToCoordinate(location.coordinate) }, drivingAction: { interaction?.requestDirections(location, nil, .driving) - }, transitAction: { - interaction?.requestDirections(location, nil, .transit) }, walkingAction: { interaction?.requestDirections(location, nil, .walking) }) @@ -159,7 +164,7 @@ public enum LocationViewEntry: Comparable, Identifiable { icon = .liveLocation } - return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: { + return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, isOpaque: false, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: { if beginTimeAndTimeout != nil { if let timeout, Int32(timeout) != liveLocationIndefinitePeriod { if additional { @@ -173,15 +178,14 @@ public enum LocationViewEntry: Comparable, Identifiable { } else { interaction?.sendLiveLocation(nil, false, nil) } - }, highlighted: { highlight in - interaction?.updateSendActionHighlight(highlight) + }, highlighted: { _ in }) - case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, _): + case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, walkingTime, _): var title: String? if let author = message.author { title = author.displayTitle(strings: presentationData.strings, displayOrder: nameDisplayOrder) } - return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, action: { + return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, walkingTime: walkingTime, action: { if let location = getLocation(from: message) { interaction?.goToCoordinate(location.coordinate) } @@ -189,10 +193,6 @@ public enum LocationViewEntry: Comparable, Identifiable { if let location = getLocation(from: message) { interaction?.requestDirections(location, title, .driving) } - }, transitAction: { - if let location = getLocation(from: message) { - interaction?.requestDirections(location, title, .transit) - } }, walkingAction: { if let location = getLocation(from: message) { interaction?.requestDirections(location, title, .walking) @@ -212,6 +212,12 @@ private func preparedTransition(from fromEntries: [LocationViewEntry], to toEntr return LocationViewTransaction(deletions: deletions, insertions: insertions, updates: updates, gotTravelTimes: gotTravelTimes, count: toEntries.count, animated: animated) } +enum LocationViewRightBarButton { + case none + case share + case showAll +} + public enum LocationViewLocation: Equatable { case initial case user @@ -268,21 +274,28 @@ public struct LocationViewState { final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationManagerDelegate { private let context: AccountContext + private weak var controller: LocationViewController? private var presentationData: PresentationData private let presentationDataPromise: Promise private var subject: EngineMessage private let interaction: LocationViewInteraction private let locationManager: LocationManager - private let isStoryLocation: Bool private let isPreview: Bool + private var rightBarButtonAction: LocationViewRightBarButton = .none + + private let topEdgeEffectView = EdgeEffectView() + private let buttons = ComponentView() + private let title = ComponentView() + private let listNode: ListView + let backgroundView = GlassBackgroundView() let headerNode: LocationMapHeaderNode - private let optionsNode: LocationOptionsNode private var enqueuedTransitions: [LocationViewTransaction] = [] private var disposable: Disposable? + private let weatherDisposable = MetaDisposable() private var state: LocationViewState private let statePromise: Promise @@ -295,28 +308,29 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan var onAnnotationsReady: (() -> Void)? private let travelDisposables = DisposableSet() - private var travelTimes: [EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime)] = [:] { + private var travelTimes: [EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime)] = [:] { didSet { self.travelTimesPromise.set(.single(self.travelTimes)) } } - private let travelTimesPromise = Promise<[EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime)]>([:]) + private let travelTimesPromise = Promise<[EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime)]>([:]) - init(context: AccountContext, presentationData: PresentationData, subject: EngineMessage, interaction: LocationViewInteraction, locationManager: LocationManager, isStoryLocation: Bool, isPreview: Bool) { + init(context: AccountContext, controller: LocationViewController, presentationData: PresentationData, subject: EngineMessage, interaction: LocationViewInteraction, locationManager: LocationManager, isPreview: Bool) { self.context = context + self.controller = controller self.presentationData = presentationData self.presentationDataPromise = Promise(presentationData) self.subject = subject self.interaction = interaction self.locationManager = locationManager - self.isStoryLocation = isStoryLocation self.isPreview = isPreview self.state = LocationViewState() self.statePromise = Promise(self.state) self.listNode = ListViewImpl() - self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.listNode.backgroundColor = .clear + self.listNode.limitHitTestToNodes = true self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3) self.listNode.verticalScrollIndicatorFollowsOverscroll = true self.listNode.accessibilityPageScrolledString = { row, count in @@ -324,30 +338,29 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } var setupProximityNotificationImpl: ((Bool) -> Void)? + var weatherPressedImpl: (() -> Void)? self.headerNode = LocationMapHeaderNode( presentationData: presentationData, - glass: false, - isPreview: true, + glass: true, + isPreview: self.isPreview, toggleMapModeSelection: interaction.toggleMapModeSelection, updateMapMode: interaction.updateMapMode, goToUserLocation: interaction.toggleTrackingMode, setupProximityNotification: { reset in - setupProximityNotificationImpl?(reset) - }) - //self.headerNode.mapNode.isRotateEnabled = false - - self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode) - + setupProximityNotificationImpl?(reset) + }, + weatherPressed: { + weatherPressedImpl?() + } + ) + super.init() self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - if !self.isPreview { - self.addSubnode(self.listNode) - } self.addSubnode(self.headerNode) if !self.isPreview { - self.addSubnode(self.optionsNode) + self.addSubnode(self.listNode) } let userLocation: Signal = .single(nil) @@ -355,25 +368,32 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan throttledUserLocation(self.headerNode.mapNode.userLocation) ) - var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating, .calculating)) + var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating)) var address: Signal = .single(nil) + let subjectLocation = getLocation(from: subject) + let isStaticLocationView: Bool + if let subjectLocation { + isStaticLocationView = subjectLocation.liveBroadcastingTimeout == nil + } else { + isStaticLocationView = false + } + let locale = localeWithStrings(presentationData.strings) - if let location = getLocation(from: subject), location.liveBroadcastingTimeout == nil { - eta = .single((.calculating, .calculating, .calculating)) - |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)) - |> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in + if let location = subjectLocation, isStaticLocationView { + self.headerNode.mapNode.setMapCenter(coordinate: location.coordinate, span: LocationMapNode.viewMapSpan, animated: false) + + eta = .single((.calculating, .calculating)) + |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)) + |> mapToSignal { drivingTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> in if case .calculating = drivingTime { return .complete() } - if case .calculating = transitTime { - return .complete() - } if case .calculating = walkingTime { return .complete() } - return .single((drivingTime, transitTime, walkingTime)) + return .single((drivingTime, walkingTime)) }) if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty { @@ -389,13 +409,21 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } } - let liveLocations = context.engine.messages.topPeerActiveLiveLocationMessages(peerId: subject.id.peerId) + let actualLiveLocations = context.engine.messages.topPeerActiveLiveLocationMessages(peerId: subject.id.peerId) |> map { _, messages -> [EngineMessage] in - return messages.map(EngineMessage.init) + return messages } + let renderLiveLocations: Signal<[EngineMessage], NoError> + if isStaticLocationView { + renderLiveLocations = .single([]) + |> then(actualLiveLocations) + } else { + renderLiveLocations = actualLiveLocations + } + setupProximityNotificationImpl = { reset in - let _ = (liveLocations + let _ = (actualLiveLocations |> take(1) |> deliverOnMainQueue).start(next: { messages in var ownMessageId: EngineMessage.Id? @@ -411,15 +439,36 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan }) } + weatherPressedImpl = { + if let location = subjectLocation { + context.sharedContext.openExternalUrl( + context: context, + urlContext: .generic, + url: "https://weather.apple.com/?lat=\(location.latitude)&long=\(location.longitude)", + forceExternal: true, + presentationData: presentationData, + navigationController: nil, + dismissInput: {} + ) + } + } + let previousState = Atomic(value: nil) let previousUserAnnotation = Atomic(value: nil) let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: []) let previousEntries = Atomic<[LocationViewEntry]?>(value: nil) let previousHadTravelTimes = Atomic(value: false) - let selfPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + let actualSelfPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + let renderSelfPeer: Signal + if isStaticLocationView { + renderSelfPeer = .single(nil) + |> then(actualSelfPeer) + } else { + renderSelfPeer = actualSelfPeer + } - self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta, self.travelTimesPromise.get()) + self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), renderSelfPeer, renderLiveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta, self.travelTimesPromise.get()) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta, travelTimes in if let strongSelf = self, let location = getLocation(from: subject) { var entries: [LocationViewEntry] = [] @@ -440,7 +489,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude) let distance = userLocation.flatMap { subjectLocation.distance(from: $0) } - entries.append(.info(presentationData.theme, location, address, distance, eta.0, eta.1, eta.2, !isStoryLocation)) + entries.append(.info(presentationData.theme, location, address, distance, eta.0, eta.1, true)) annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, location: location, queryId: nil, resultId: nil, forcedSelection: true)) } else { @@ -516,7 +565,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan for message in effectiveLiveLocations { if let location = getLocation(from: message) { - if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, message.threadId != nil { + if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, let threadId = message.threadId, threadId != 1 { continue } @@ -542,60 +591,57 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan userAnnotation = LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: true, heading: location.heading) } else { var drivingTime: ExpectedTravelTime = .unknown - var transitTime: ExpectedTravelTime = .unknown var walkingTime: ExpectedTravelTime = .unknown if !isLocationView && message.author?.id != context.account.peerId { - let signal = combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)) - |> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in + let signal = combineLatest( + queue: Queue.mainQueue(), + getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), + getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking) + ) + |> mapToSignal { drivingTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> in if case .calculating = drivingTime { return .complete() } - if case .calculating = transitTime { - return .complete() - } if case .calculating = walkingTime { return .complete() } - - return .single((drivingTime, transitTime, walkingTime)) + return .single((drivingTime, walkingTime)) } - if let (previousTimestamp, maybeDrivingTime, maybeTransitTime, maybeWalkingTime) = travelTimes[message.id] { + if let (previousTimestamp, maybeDrivingTime, maybeWalkingTime) = travelTimes[message.id] { drivingTime = maybeDrivingTime - transitTime = maybeTransitTime walkingTime = maybeWalkingTime if timestamp > previousTimestamp + 60.0 { - strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, transitTime, walkingTime in + strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, walkingTime in guard let strongSelf = self else { return } let timestamp = CACurrentMediaTime() var travelTimes = strongSelf.travelTimes - travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime) + travelTimes[message.id] = (timestamp, drivingTime, walkingTime) strongSelf.travelTimes = travelTimes })) } } else { drivingTime = .calculating - transitTime = .calculating walkingTime = .calculating - strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, transitTime, walkingTime in + strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, walkingTime in guard let strongSelf = self else { return } let timestamp = CACurrentMediaTime() var travelTimes = strongSelf.travelTimes - travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime) + travelTimes[message.id] = (timestamp, drivingTime, walkingTime) strongSelf.travelTimes = travelTimes })) } } annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: message.author?.id == context.account.peerId, heading: location.heading)) - entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, index)) + entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, drivingTime, walkingTime, index)) } index += 1 } @@ -643,7 +689,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let transition = preparedTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, interaction: strongSelf.interaction, gotTravelTimes: !travelTimes.isEmpty && !previousHadTravelTimes, animated: animated) strongSelf.enqueueTransition(transition) - strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: false) + strongSelf.headerNode.updateState(mapMode: state.mapMode, trackingMode: state.trackingMode, displayingMapModeOptions: state.displayingMapModeOptions, displayingPlacesButton: false, proximityNotification: proximityNotification, animated: true) if let proximityNotification = proximityNotification, !proximityNotification && !strongSelf.displayedProximityAlertTooltip { strongSelf.displayedProximityAlertTooltip = true @@ -707,7 +753,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } else { rightBarButtonAction = .share } - strongSelf.interaction.updateRightBarButton(rightBarButtonAction) + strongSelf.rightBarButtonAction = rightBarButtonAction if let (layout, navigationBarHeight) = strongSelf.validLayout { var updateLayout = false @@ -725,14 +771,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan if !isPreview { self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in - guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else { + guard let self, self.listNode.scrollEnabled else { return } - let overlap: CGFloat = 0.0 - strongSelf.listOffset = max(0.0, offset) - let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap))) - listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame) - strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) + self.listOffset = max(0.0, offset) + self.updateHeader(transition: listTransition) } } @@ -777,14 +820,25 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan self.locationManager.manager.startUpdatingHeading() self.locationManager.manager.delegate = self + + if !self.isPreview, let location = getLocation(from: subject) { + self.requestWeatherData(coordinate: location.coordinate) + } } deinit { self.disposable?.dispose() + self.weatherDisposable.dispose() self.travelDisposables.dispose() self.locationManager.manager.stopUpdatingHeading() } + override func didLoad() { + super.didLoad() + + self.view.insertSubview(self.backgroundView, aboveSubview: self.headerNode.view) + } + func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) { if newHeading.headingAccuracy < 0.0 { self.headerNode.mapNode.userHeading = nil @@ -801,18 +855,70 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan self.presentationDataPromise.set(.single(presentationData)) self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.listNode.backgroundColor = .clear self.headerNode.updatePresentationData(self.presentationData) - self.optionsNode.updatePresentationData(self.presentationData) + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: .immediate) + } } func updateState(_ f: (LocationViewState) -> LocationViewState) { self.state = f(self.state) self.statePromise.set(.single(self.state)) } - - func updateSendActionHighlight(_ highlighted: Bool) { - self.headerNode.updateHighlight(highlighted) + + private func requestWeatherData(coordinate: CLLocationCoordinate2D) { + self.weatherDisposable.set((Weather.requestWeatherData(context: self.context, location: coordinate) + |> deliverOnMainQueue).start(next: { [weak self] weatherData in + guard let self else { + return + } + if let weatherData { + self.headerNode.updateWeatherData(context: self.context, emoji: weatherData.emoji, temperature: stringForTemperature(weatherData.temperature), animated: true) + } else { + self.headerNode.clearWeatherData(animated: true) + } + })) + } + + func updateHeader(transition: ContainedViewLayoutTransition) { + guard let (layout, navigationBarHeight) = self.validLayout else { + return + } + let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)) + transition.updateFrame(node: self.headerNode, frame: headerFrame) + + let headerHeight: CGFloat + if self.isPreview { + headerHeight = layout.size.height + } else if let listOffset = self.listOffset { + headerHeight = max(0.0, listOffset) + } else { + headerHeight = headerFrame.height + } + let headerSize = CGSize(width: headerFrame.width, height: headerHeight) + self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: 0.0, controlsTopPadding: 0.0, controlsBottomPadding: 6.0, offset: 0.0, size: headerSize, transition: transition) + + let backgroundHeight = layout.size.height - headerHeight + + let glassInset: CGFloat = 6.0 + let backgroundSize = CGSize(width: layout.size.width - glassInset * 2.0, height: backgroundHeight) + + let bottomCornerRadius = max(24.0, layout.deviceMetrics.screenCornerRadius) - 2.0 + + self.backgroundView.update( + size: backgroundSize, + cornerRadii: .init( + topLeft: 38.0, + topRight: 38.0, + bottomLeft: bottomCornerRadius, + bottomRight: bottomCornerRadius + ), + isDark: self.presentationData.theme.overallDarkAppearance, + tintColor: .init(kind: .panel), + transition: ComponentTransition(transition) + ) + transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: layout.size.height - backgroundSize.height - glassInset), size: backgroundSize)) } private func enqueueTransition(_ transition: LocationViewTransaction) { @@ -915,6 +1021,16 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan self.headerNode.mapNode.showAll() } + func liveLocationActionSourceView(extend: Bool) -> UIView? { + var result: UIView? + self.listNode.forEachItemNode { itemNode in + if result == nil, let itemNode = itemNode as? LocationActionListItemNode { + result = itemNode.liveLocationContextSourceView(extend: extend) + } + } + return result + } + private func displayProximityAlertTooltip() { guard let location = self.headerNode.proximityButtonFrame().flatMap({ frame -> CGRect in return self.headerNode.view.convert(frame, to: nil) @@ -922,15 +1038,22 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan return } - let _ = (self.context.account.postbox.loadedPeerWithId(self.subject.id.peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.subject.id.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + var text: String = strongSelf.presentationData.strings.Location_ProximityGroupTip if peer.id.namespace == Namespaces.Peer.CloudUser { - text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string + text = strongSelf.presentationData.strings.Location_ProximityTip(peer.compactDisplayTitle).string } strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: text), icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _, _ in @@ -943,7 +1066,6 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let isFirstLayout = self.validLayout == nil self.validLayout = (layout, navigationHeight) - let optionsHeight: CGFloat = 38.0 var actionHeight: CGFloat? self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? LocationActionListItemNode { @@ -955,25 +1077,16 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let overlap: CGFloat = 0.0 var topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - overlap - if !self.isStoryLocation { - topInset -= 100.0 - } + topInset -= 100.0 + if let location = getLocation(from: self.subject), location.liveBroadcastingTimeout != nil { topInset += 66.0 } - let headerHeight: CGFloat - if self.isPreview { - headerHeight = layout.size.height - } else if let listOffset = self.listOffset { - headerHeight = max(0.0, listOffset + overlap) - } else { - headerHeight = topInset + overlap + if self.listOffset == nil { + self.listOffset = topInset } - let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight)) - transition.updateFrame(node: self.headerNode, frame: headerFrame) - - self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: transition) + self.updateHeader(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) @@ -982,18 +1095,118 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let listFrame: CGRect = CGRect(origin: CGPoint(), size: layout.size) transition.updateFrame(node: self.listNode, frame: listFrame) + + if !self.isPreview { + let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 80.0)) + transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: !self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + if self.topEdgeEffectView.superview == nil { + self.view.addSubview(self.topEdgeEffectView) + } + + let leftControlItems: [GlassControlGroupComponent.Item] = [ + GlassControlGroupComponent.Item( + id: AnyHashable("close"), + content: .icon("Navigation/Close"), + action: { [weak self] in + guard let self else { + return + } + self.controller?.dismiss() + } + ) + ] + var rightControlItems: [GlassControlGroupComponent.Item] = [] + switch self.rightBarButtonAction { + case .none: + break + case .share: + rightControlItems.append( + GlassControlGroupComponent.Item( + id: AnyHashable("share"), + content: .icon("Navigation/Share"), + action: { [weak self] in + guard let self else { + return + } + self.interaction.share() + } + ) + ) + case .showAll: + rightControlItems.append( + GlassControlGroupComponent.Item( + id: AnyHashable("share"), + content: .text(self.presentationData.strings.Map_LiveLocationShowAll), + action: { [weak self] in + guard let self else { + return + } + self.showAll() + } + ) + ) + } + + let barButtonSideInset: CGFloat = 16.0 + let buttonsSize = self.buttons.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassControlPanelComponent( + theme: self.presentationData.theme, + leftItem: GlassControlPanelComponent.Item( + items: leftControlItems, + background: .panel + ), + centralItem: nil, + rightItem: rightControlItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: rightControlItems, + background: .panel + ), + centerAlignmentIfPossible: true, + isDark: self.presentationData.theme.overallDarkAppearance + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - barButtonSideInset * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: 44.0) + ) + let buttonsFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: buttonsSize) + if let view = self.buttons.view { + if view.superview == nil { + self.view.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: buttonsFrame.size) + view.center = buttonsFrame.center + } + + let titleSize = self.title.update( + transition: ComponentTransition(transition), + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: self.presentationData.strings.Map_LocationTitle, + font: Font.semibold(17.0), + textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor : .white + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.view.addSubview(titleView) + } + transition.updateFrame(view: titleView, frame: titleFrame) + } + } if isFirstLayout { while !self.enqueuedTransitions.isEmpty { self.dequeueTransition() } } - - let optionsOffset: CGFloat = self.state.displayingMapModeOptions ? navigationHeight : navigationHeight - optionsHeight - let optionsFrame = CGRect(x: 0.0, y: optionsOffset, width: layout.size.width, height: optionsHeight) - transition.updateFrame(node: self.optionsNode, frame: optionsFrame) - self.optionsNode.updateLayout(size: optionsFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) - self.optionsNode.isUserInteractionEnabled = self.state.displayingMapModeOptions } var coordinate: Signal { diff --git a/submodules/MapResourceToAvatarSizes/BUILD b/submodules/MapResourceToAvatarSizes/BUILD index 0db6335885..48fecbac65 100644 --- a/submodules/MapResourceToAvatarSizes/BUILD +++ b/submodules/MapResourceToAvatarSizes/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/Display:Display", ], diff --git a/submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift b/submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift index 440581e686..5ff1f0b855 100644 --- a/submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift +++ b/submodules/MapResourceToAvatarSizes/Sources/MapResourceToAvatarSizes.swift @@ -1,15 +1,14 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import Display -public func mapResourceToAvatarSizes(postbox: Postbox, resource: MediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> { - return postbox.mediaBox.resourceData(resource) +public func mapResourceToAvatarSizes(engine: TelegramEngine, resource: EngineMediaResource, representations: [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError> { + return engine.resources.data(id: resource.id) |> take(1) |> map { data -> [Int: Data] in - guard data.complete, let image = UIImage(contentsOfFile: data.path) else { + guard data.isComplete, let image = UIImage(contentsOfFile: data.path) else { return [:] } var result: [Int: Data] = [:] diff --git a/submodules/MediaPasteboardUI/BUILD b/submodules/MediaPasteboardUI/BUILD index ced1985f41..e68615cb1a 100644 --- a/submodules/MediaPasteboardUI/BUILD +++ b/submodules/MediaPasteboardUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift index 71f95913ff..027d14ff00 100644 --- a/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift +++ b/submodules/MediaPasteboardUI/Sources/MediaPasteboardScreen.swift @@ -8,7 +8,6 @@ import AttachmentUI import MediaPickerUI import AccountContext import LegacyComponents -import AttachmentTextInputPanelNode public func mediaPasteboardScreen( context: AccountContext, @@ -17,9 +16,17 @@ public func mediaPasteboardScreen( subjects: [MediaPickerScreenImpl.Subject.Media], presentMediaPicker: @escaping (_ subject: MediaPickerScreenImpl.Subject, _ saveEditedPhotos: Bool, _ bannedSendPhotos: (Int32, Bool)?, _ bannedSendVideos: (Int32, Bool)?, _ present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void) -> Void, getSourceRect: (() -> CGRect?)? = nil, - makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil } + customEmojiAvailable: Bool = true ) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, style: .glass, chatLocation: .peer(id: peer.id), buttons: [.standalone], initialButton: .standalone, makeEntityInputView: makeEntityInputView) + let controller = AttachmentController( + context: context, + updatedPresentationData: updatedPresentationData, + style: .glass, + chatLocation: .peer(id: peer.id), + buttons: [.standalone], + initialButton: .standalone, + customEmojiAvailable: customEmojiAvailable + ) controller.requestController = { _, present in presentMediaPicker(.media(subjects), false, nil, nil, { mediaPicker, mediaPickerContext in present(mediaPicker, mediaPickerContext) diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index 8088d37de4..eb9d00e7bf 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -36,7 +36,6 @@ swift_library( "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", - "//submodules/WebSearchUI:WebSearchUI", "//submodules/ChatMessageBackground:ChatMessageBackground", "//submodules/SparseItemGrid:SparseItemGrid", "//submodules/UndoUI:UndoUI", @@ -57,6 +56,7 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/GlassControls", + "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/AlertComponent", "//submodules/Components/BundleIconComponent", diff --git a/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift b/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift index b65ab55594..83c5a88320 100644 --- a/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift +++ b/submodules/MediaPickerUI/Sources/AvatarEditorPreviewView.swift @@ -3,7 +3,6 @@ import UIKit import Display import SwiftSignalKit import TelegramCore -import Postbox import AvatarBackground import AccountContext import EmojiTextAttachmentView @@ -37,28 +36,25 @@ final class AvatarEditorPreviewView: UIView { self.addSubview(self.backgroundView) - let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji) - self.disposable = (context.account.postbox.combinedView(keys: [stickersKey]) + self.disposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji)) |> runOn(Queue.concurrentDefaultQueue()) - |> deliverOnMainQueue).start(next: { [weak self] views in + |> deliverOnMainQueue).start(next: { [weak self] items in guard let self else { return } - if let view = views.views[stickersKey] as? OrderedItemListView { - var files: [TelegramMediaFile] = [] - for item in view.items.prefix(8) { - if let mediaItem = item.contents.get(RecentMediaItem.self) { - let file = mediaItem.media._parse() - files.append(file) - - self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) - } - } - self.files = files - if let size = self.currentSize { - self.updateLayout(size: size) + var files: [TelegramMediaFile] = [] + for item in items.prefix(8) { + if let mediaItem = item.contents.get(RecentMediaItem.self) { + let file = mediaItem.media._parse() + files.append(file) + + self.preloadDisposableSet.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) } } + self.files = files + if let size = self.currentSize { + self.updateLayout(size: size) + } }) let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap)) diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index 01ecb788f3..7d939ef00c 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -3,7 +3,6 @@ import UIKit import Display import SwiftSignalKit import TelegramCore -import Postbox import SSignalKit import TelegramPresentationData import AccountContext @@ -131,7 +130,7 @@ func presentLegacyMediaPickerGallery( transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, - presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, + presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, @@ -153,6 +152,15 @@ func presentLegacyMediaPickerGallery( legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: presentationData, presentInGlobalOverlay: { [weak legacyController] controller in + if let legacyController { + legacyController.presentInGlobalOverlay(controller) + } else if let mainWindow = context.sharedContext.mainWindow { + mainWindow.presentInGlobalOverlay(controller) + } else { + context.sharedContext.presentGlobalController(controller, nil) + } + }) paintStickersContext.captionPanelView = { return getCaptionPanelView() } @@ -166,6 +174,9 @@ func presentLegacyMediaPickerGallery( livePhotoButton.present = present return livePhotoButton } + paintStickersContext.photoToolbarView = { backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) + } paintStickersContext.editCover = { dimensions, completion in editCover(dimensions, completion) } @@ -299,7 +310,7 @@ func presentLegacyMediaPickerGallery( } } if !isScheduledMessages && peer != nil { - model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in + model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model, weak paintStickersContext] item, sourceView in if let legacyController = legacyController, let item = item as? TGMediaPickerGalleryItem, let model = model, let selectionContext = selectionContext { var effectiveHasSchedule = hasSchedule @@ -345,37 +356,35 @@ func presentLegacyMediaPickerGallery( let _ = (sendWhenOnlineAvailable |> take(1) |> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in - let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) - let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer) let dismissImpl = { [weak model] in model?.dismiss(true, false) dismissAll() } - sheetController.send = { + let send = { completed(item.asset, false, nil, { dismissImpl() }) } - sheetController.sendSilently = { [weak model] in + let sendSilently = { [weak model] in model?.interfaceView.onDismiss() completed(item.asset, true, nil, { dismissImpl() }) } - sheetController.sendWhenOnline = { + let sendWhenOnline = { completed(item.asset, false, scheduleWhenOnlineTimestamp, { dismissImpl() }) } - sheetController.schedule = { - presentSchedulePicker(true, { time in - completed(item.asset, false, time, { + let schedule = { + presentSchedulePicker(true, { time, silentPosting in + completed(item.asset, silentPosting, time, { dismissImpl() }) }) } - sheetController.sendWithTimer = { + let sendWithTimer = { presentTimerPicker { time in var items = selectionContext.selectedItems() ?? [] items.append(item.asset as Any) @@ -389,6 +398,20 @@ func presentLegacyMediaPickerGallery( }) } } + + if let sourceView, let paintStickersContext, paintStickersContext.presentMediaPickerSendActionMenu?(sourceView, hasSilentPosting, sendWhenOnlineAvailable && effectiveHasSchedule, effectiveHasSchedule, reminder, hasTimer, sendSilently, sendWhenOnline, schedule, sendWithTimer) == true { + let hapticFeedback = HapticFeedback() + hapticFeedback.impact() + return + } + + let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) + let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer) + sheetController.send = send + sheetController.sendSilently = sendSilently + sheetController.sendWhenOnline = sendWhenOnline + sheetController.schedule = schedule + sheetController.sendWithTimer = sendWithTimer sheetController.customDismissBlock = { [weak legacySheetController] in legacySheetController?.dismiss() } diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift b/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift index e2b5f31a29..8f2d4df733 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift @@ -167,6 +167,7 @@ final class MediaGroupsContextMenuContent: ContextControllerItemsContent { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } self.scrollNode.clipsToBounds = false + self.scrollNode.view.scrollsToTop = false super.init() diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index ebb74d6763..8171f784fc 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -471,7 +470,7 @@ public final class MediaGroupsScreen: ViewController, AttachmentContainable { self.updateNavigationStack { current in var mediaPickerContext: AttachmentMediaPickerContext? if let first = current.first as? MediaPickerScreenImpl { - mediaPickerContext = first.webSearchController?.mediaPickerContext ?? first.mediaPickerContext + mediaPickerContext = first.mediaPickerContext } return (current.filter { $0 !== self }, mediaPickerContext) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index e7e9c914fb..5f04d5f4ca 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import AccountContext import TelegramPresentationData import TelegramStringFormatting diff --git a/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift index c48fc25276..a26d7c686a 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift @@ -15,7 +15,7 @@ final class MediaPickerManageNode: ASDisplayNode { private let measureButtonNode: ImmediateTextNode private let buttonNode: SolidRoundedButtonNode - var pressed: () -> Void = {} + var pressed: (UIView) -> Void = { _ in } override init() { self.textNode = ImmediateTextNode() @@ -33,7 +33,10 @@ final class MediaPickerManageNode: ASDisplayNode { self.addSubnode(self.buttonNode) self.buttonNode.pressed = { [weak self] in - self?.pressed() + guard let self else { + return + } + self.pressed(self.buttonNode.view) } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift new file mode 100644 index 0000000000..cf16d90804 --- /dev/null +++ b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift @@ -0,0 +1,1187 @@ +import Foundation +import UIKit +import Display +import LegacyComponents +import AccountContext +import TelegramPresentationData +import ComponentFlow +import GlassBackgroundComponent +import GlassBarButtonComponent +import PlainButtonComponent +import BundleIconComponent + +private let toolbarButtonSide: CGFloat = 44.0 +private let toolbarSideButtonSide: CGFloat = 44.0 +private let centerButtonSpacing: CGFloat = 10.0 + +private let toolbarTabOrder: [TGPhotoEditorTab] = [ + .cropTab, + .stickerTab, + .paintTab, + .eraserTab, + .textTab, + .toolsTab, + .rotateTab, + .qualityTab, + .timerTab, + .mirrorTab, + .aspectRatioTab, + .tintTab, + .blurTab, + .curvesTab +] + +private let dontHighlightOnSelectionTabs = Set([ + TGPhotoEditorTab.rotateTab.rawValue, + TGPhotoEditorTab.stickerTab.rawValue, + TGPhotoEditorTab.textTab.rawValue, + TGPhotoEditorTab.qualityTab.rawValue, + TGPhotoEditorTab.timerTab.rawValue, + TGPhotoEditorTab.mirrorTab.rawValue, + TGPhotoEditorTab.aspectRatioTab.rawValue +]) + + +private final class MediaPickerPhotoToolbarImageCache { + private var images: [String: UIImage] = [:] + + func qualityIcon(isPhoto: Bool, highQuality: Bool, preset: Int, color: UIColor) -> UIImage? { + let key = "quality-\(isPhoto)-\(highQuality)-\(preset)-\(self.colorKey(color))" + if let image = self.images[key] { + return image + } + let image = generateQualityIcon(isPhoto: isPhoto, highQuality: highQuality, preset: preset, color: color) + self.images[key] = image + return image + } + + func timerIcon(value: Int, color: UIColor) -> UIImage? { + let key = "timer-\(value)-\(self.colorKey(color))" + if let image = self.images[key] { + return image + } + let image = generateTimerIcon(value: value, color: color) + self.images[key] = image + return image + } + + private func colorKey(_ color: UIColor) -> String { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) { + return "\(Int(red * 255.0))-\(Int(green * 255.0))-\(Int(blue * 255.0))-\(Int(alpha * 255.0))" + } + var white: CGFloat = 0.0 + if color.getWhite(&white, alpha: &alpha) { + return "\(Int(white * 255.0))-\(Int(alpha * 255.0))" + } + return color.description + } +} + +private func generateQualityIcon(isPhoto: Bool, highQuality: Bool, preset: Int, color: UIColor) -> UIImage? { + let label: String + if isPhoto { + label = highQuality ? "HD" : "SD" + } else { + switch preset { + case 1: + label = "240" + case 2: + label = "360" + case 3: + label = "480" + case 4: + label = "720" + case 5: + label = "HD" + default: + label = "480" + } + } + + let size = CGSize(width: isPhoto ? 24.0 : 28.0, height: 24.0) + let lineWidth = 2.0 - UIScreenPixel + let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0) + + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + guard let context = UIGraphicsGetCurrentContext() else { + UIGraphicsEndImageContext() + return nil + } + + context.setStrokeColor(color.cgColor) + context.setLineWidth(lineWidth) + context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 7.0).cgPath) + context.strokePath() + + let font = Font.with(size: 11.0, design: .round, weight: .bold) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: color, + .kern: -0.7 + ] + let textSize = label.size(withAttributes: attributes) + label.draw( + in: CGRect( + x: floorToScreenPixels((size.width - textSize.width) / 2.0) + UIScreenPixel, + y: 5.0, + width: textSize.width, + height: textSize.height + ), + withAttributes: attributes + ) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image +} + +private func generateTimerIcon(value: Int, color: UIColor) -> UIImage? { + let size = CGSize(width: 24.0, height: 24.0) + let lineWidth = 2.0 - UIScreenPixel + + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) + guard let context = UIGraphicsGetCurrentContext() else { + UIGraphicsEndImageContext() + return nil + } + + context.setStrokeColor(color.cgColor) + context.setLineWidth(lineWidth) + context.setLineCap(.round) + context.setLineJoin(.round) + + let bodyRect = CGRect(x: 4.0, y: 5.0, width: 16.0, height: 16.0).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0) + context.strokeEllipse(in: bodyRect) + + context.move(to: CGPoint(x: 9.0, y: 2.5)) + context.addLine(to: CGPoint(x: 15.0, y: 2.5)) + context.strokePath() + + context.move(to: CGPoint(x: 12.0, y: 5.0)) + context.addLine(to: CGPoint(x: 12.0, y: 8.0)) + context.strokePath() + + if value == 0 { + context.move(to: CGPoint(x: 12.0, y: 13.0)) + context.addLine(to: CGPoint(x: 12.0, y: 9.0)) + context.move(to: CGPoint(x: 12.0, y: 13.0)) + context.addLine(to: CGPoint(x: 15.0, y: 13.0)) + context.strokePath() + } else { + let label = "\(value)" + let font = Font.with(size: 10.0, design: .round, weight: .semibold) + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: color + ] + let textSize = label.size(withAttributes: attributes) + label.draw( + in: CGRect( + x: floorToScreenPixels((size.width - textSize.width) / 2.0), + y: 9.0, + width: textSize.width, + height: textSize.height + ), + withAttributes: attributes + ) + } + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image +} + +private final class MediaPickerPhotoToolbarComponent: Component { + let context: AccountContext + let solidBackground: Bool + let backButtonType: TGPhotoEditorBackButton + let doneButtonType: TGPhotoEditorDoneButton + let currentTabs: TGPhotoEditorTab + let activeTab: TGPhotoEditorTab + let highlightedTabs: TGPhotoEditorTab + let disabledTabs: TGPhotoEditorTab + let qualityIsPhoto: Bool + let qualityHighQuality: Bool + let qualityPreset: Int + let timerValue: Int + let hasSendStarsButton: Bool + let sendPaidMessageStars: Int64 + let editButtonsHidden: Bool + let editButtonsEnabled: Bool + let centerButtonsHidden: Bool + let allButtonsHidden: Bool + let cancelDoneButtonsHidden: Bool + let doneButtonEnabled: Bool + let interfaceOrientation: UIInterfaceOrientation + let bottomInset: CGFloat + let infoString: String? + let cancelPressed: (() -> Void)? + let donePressed: (() -> Void)? + let doneLongPressed: ((Any?) -> Void)? + let tabPressed: ((TGPhotoEditorTab) -> Void)? + + init( + context: AccountContext, + solidBackground: Bool, + backButtonType: TGPhotoEditorBackButton, + doneButtonType: TGPhotoEditorDoneButton, + currentTabs: TGPhotoEditorTab, + activeTab: TGPhotoEditorTab, + highlightedTabs: TGPhotoEditorTab, + disabledTabs: TGPhotoEditorTab, + qualityIsPhoto: Bool, + qualityHighQuality: Bool, + qualityPreset: Int, + timerValue: Int, + hasSendStarsButton: Bool, + sendPaidMessageStars: Int64, + editButtonsHidden: Bool, + editButtonsEnabled: Bool, + centerButtonsHidden: Bool, + allButtonsHidden: Bool, + cancelDoneButtonsHidden: Bool, + doneButtonEnabled: Bool, + interfaceOrientation: UIInterfaceOrientation, + bottomInset: CGFloat, + infoString: String?, + cancelPressed: (() -> Void)?, + donePressed: (() -> Void)?, + doneLongPressed: ((Any?) -> Void)?, + tabPressed: ((TGPhotoEditorTab) -> Void)? + ) { + self.context = context + self.solidBackground = solidBackground + self.backButtonType = backButtonType + self.doneButtonType = doneButtonType + self.currentTabs = currentTabs + self.activeTab = activeTab + self.highlightedTabs = highlightedTabs + self.disabledTabs = disabledTabs + self.qualityIsPhoto = qualityIsPhoto + self.qualityHighQuality = qualityHighQuality + self.qualityPreset = qualityPreset + self.timerValue = timerValue + self.hasSendStarsButton = hasSendStarsButton + self.sendPaidMessageStars = sendPaidMessageStars + self.editButtonsHidden = editButtonsHidden + self.editButtonsEnabled = editButtonsEnabled + self.centerButtonsHidden = centerButtonsHidden + self.allButtonsHidden = allButtonsHidden + self.cancelDoneButtonsHidden = cancelDoneButtonsHidden + self.doneButtonEnabled = doneButtonEnabled + self.interfaceOrientation = interfaceOrientation + self.bottomInset = bottomInset + self.infoString = infoString + self.cancelPressed = cancelPressed + self.donePressed = donePressed + self.doneLongPressed = doneLongPressed + self.tabPressed = tabPressed + } + + static func ==(lhs: MediaPickerPhotoToolbarComponent, rhs: MediaPickerPhotoToolbarComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.solidBackground != rhs.solidBackground { + return false + } + if lhs.backButtonType != rhs.backButtonType { + return false + } + if lhs.doneButtonType != rhs.doneButtonType { + return false + } + if lhs.currentTabs != rhs.currentTabs { + return false + } + if lhs.activeTab != rhs.activeTab { + return false + } + if lhs.highlightedTabs != rhs.highlightedTabs { + return false + } + if lhs.disabledTabs != rhs.disabledTabs { + return false + } + if lhs.qualityIsPhoto != rhs.qualityIsPhoto { + return false + } + if lhs.qualityHighQuality != rhs.qualityHighQuality { + return false + } + if lhs.qualityPreset != rhs.qualityPreset { + return false + } + if lhs.timerValue != rhs.timerValue { + return false + } + if lhs.hasSendStarsButton != rhs.hasSendStarsButton { + return false + } + if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + return false + } + if lhs.editButtonsHidden != rhs.editButtonsHidden { + return false + } + if lhs.editButtonsEnabled != rhs.editButtonsEnabled { + return false + } + if lhs.centerButtonsHidden != rhs.centerButtonsHidden { + return false + } + if lhs.allButtonsHidden != rhs.allButtonsHidden { + return false + } + if lhs.cancelDoneButtonsHidden != rhs.cancelDoneButtonsHidden { + return false + } + if lhs.doneButtonEnabled != rhs.doneButtonEnabled { + return false + } + if lhs.interfaceOrientation != rhs.interfaceOrientation { + return false + } + if lhs.bottomInset != rhs.bottomInset { + return false + } + if lhs.infoString != rhs.infoString { + return false + } + return true + } + + final class View: UIView { + fileprivate let cancelButton = ComponentView() + fileprivate let doneButton = ComponentView() + private let doneButtonContextView = ContextControllerSourceView() + + private let buttonsBackgroundView = GlassBackgroundView() + private let selectionView = UIView() + private let infoLabel = UILabel() + private let imageCache = MediaPickerPhotoToolbarImageCache() + private var centerButtonViews: [UInt: ComponentView] = [:] + + private var component: MediaPickerPhotoToolbarComponent? + private var cancelPressed: (() -> Void)? + private var donePressed: (() -> Void)? + private var doneLongPressed: ((Any?) -> Void)? + private var tabPressed: ((TGPhotoEditorTab) -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.clipsToBounds = false + self.addSubview(self.buttonsBackgroundView) + + self.selectionView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.15) + self.selectionView.isUserInteractionEnabled = false + self.selectionView.alpha = 0.0 + + self.infoLabel.backgroundColor = .clear + self.infoLabel.textAlignment = .center + self.infoLabel.textColor = .white + self.infoLabel.font = Font.regular(13.0) + self.infoLabel.isUserInteractionEnabled = false + self.addSubview(self.infoLabel) + + self.doneButtonContextView.beginDelay = 0.4 + self.doneButtonContextView.activated = { [weak self] gesture, _ in + guard let self else { + gesture.cancel() + return + } + self.doneLongPressed?(self.doneButtonContextView) + } + self.doneButtonContextView.shouldBegin = { [weak self] _ in + guard let self, let component = self.component else { + return false + } + return self.doneLongPressed != nil && component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden + } + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + fileprivate var cancelButtonFrame: CGRect { + return self.cancelButton.view?.frame ?? .zero + } + + fileprivate var doneButtonFrame: CGRect { + return self.doneButtonContextView.frame + } + + fileprivate var doneButtonSourceView: UIView { + return self.doneButtonContextView + } + + fileprivate func viewForTab(_ tab: TGPhotoEditorTab) -> UIView? { + return self.centerButtonViews[tab.rawValue]?.view + } + + func update(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.cancelPressed = component.cancelPressed + self.donePressed = component.donePressed + self.doneLongPressed = component.doneLongPressed + self.tabPressed = component.tabPressed + + //let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let accentColor = UIColor(rgb: 0xffd300) //presentationData.theme.list.itemAccentColor + let selectedIconColor = UIColor.white //(rgb: 0xffd300) + + self.backgroundColor = .clear //component.solidBackground ? UIColor(rgb: 0x000000, alpha: 0.55) : .clear + + let cancelSize = self.cancelButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: toolbarButtonSide, height: toolbarButtonSide), + backgroundColor: nil, + isDark: true, + state: .glass, + isEnabled: !component.cancelDoneButtonsHidden && !component.allButtonsHidden, + isVisible: true, + component: AnyComponentWithIdentity( + id: "close", + component: AnyComponent( + BundleIconComponent( + name: component.backButtonType == TGPhotoEditorBackButtonBack ? "Navigation/Back" : "Navigation/Close", + tintColor: .white + ) + ) + ), + action: { [weak self] _ in + self?.cancelPressed?() + } + ) + ), + environment: {}, + containerSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide) + ) + + let doneIconName: String + let doneBackgroundColor: UIColor? = UIColor(rgb: 0x0088ff) + var doneWidth: CGFloat = toolbarButtonSide + switch component.doneButtonType { + case TGPhotoEditorDoneButtonSend: + doneIconName = "Media Editor/Send" + doneWidth = 46.0 + case TGPhotoEditorDoneButtonSchedule: + doneIconName = "Chat/Input/ScheduleIcon" + doneWidth = 46.0 + case TGPhotoEditorDoneButtonDone: + doneIconName = "Navigation/Done" + default: + doneIconName = "Navigation/Done" + } + + let doneSize = self.doneButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: doneWidth, height: toolbarButtonSide), + backgroundColor: doneBackgroundColor, + isDark: true, + state: doneBackgroundColor != nil ? .tintedGlass : .glass, + isEnabled: component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden, + isVisible: true, + component: AnyComponentWithIdentity( + id: "done", + component: AnyComponent( + BundleIconComponent( + name: doneIconName, + tintColor: .white + ) + ) + ), + action: { [weak self] _ in + self?.donePressed?() + } + ) + ), + environment: {}, + containerSize: CGSize(width: doneWidth, height: toolbarButtonSide) + ) + +// let doneContent: AnyComponentWithIdentity +// let doneFixedSize: CGSize? +// let doneAvailableSize: CGSize +// if component.hasSendStarsButton { +// let text = "\u{2b50}\u{fe0f} \(component.sendPaidMessageStars)" +// doneContent = AnyComponentWithIdentity( +// id: "stars-\(component.sendPaidMessageStars)", +// component: AnyComponent(Text(text: text, font: Font.with(size: 17.0, design: .round, weight: .semibold, traits: .monospacedNumbers), color: .white)) +// ) +// doneFixedSize = nil +// doneAvailableSize = CGSize(width: 180.0, height: toolbarSideButtonSide) +// } else { +// doneContent = self.sideButtonContent(iconName: self.doneIconName(component.doneButtonType), color: .white, id: "done-\(component.doneButtonType.rawValue)") +// doneFixedSize = CGSize(width: toolbarSideButtonSide, height: toolbarSideButtonSide) +// doneAvailableSize = CGSize(width: toolbarSideButtonSide, height: toolbarSideButtonSide) +// } +// let doneSize = self.doneButton.update( +// content: doneContent, +// fixedSize: doneFixedSize, +// availableSize: doneAvailableSize, +// isEnabled: component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden, +// transition: transition +// ) + + let sideAlpha = component.allButtonsHidden ? 0.0 : 1.0 + let sideFrames = self.sideButtonFrames(availableSize: availableSize, cancelSize: cancelSize, doneSize: doneSize, component: component) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setAlpha(view: cancelButtonView, alpha: sideAlpha) + transition.setFrame(view: cancelButtonView, frame: sideFrames.cancel) + } + if let doneButtonView = self.doneButton.view { + if self.doneButtonContextView.superview == nil { + self.addSubview(self.doneButtonContextView) + } + if doneButtonView.superview !== self.doneButtonContextView { + self.doneButtonContextView.addSubview(doneButtonView) + } + self.doneButtonContextView.targetViewForActivationProgress = doneButtonView + self.doneButtonContextView.isGestureEnabled = component.doneLongPressed != nil && component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden + transition.setAlpha(view: self.doneButtonContextView, alpha: sideAlpha * (component.doneButtonEnabled ? 1.0 : 0.2)) + transition.setFrame(view: self.doneButtonContextView, frame: sideFrames.done) + transition.setFrame(view: doneButtonView, frame: CGRect(origin: .zero, size: sideFrames.done.size)) + } + + self.updateCenterButtons( + component: component, + availableSize: availableSize, + doneSize: doneSize, + selectedIconColor: selectedIconColor, + accentColor: accentColor, + transition: transition + ) + + self.updateInfoLabel(component: component, availableSize: availableSize, transition: transition) + + return availableSize + } + + private func doneIconName(_ type: TGPhotoEditorDoneButton) -> String { + switch type.rawValue { + case 1: + return "Editor/Commit" + case 2: + return "Editor/Commit" + case 3: + return "PhotoPickerSendIcon" + default: + return "PhotoPickerSendIcon" + } + } + + private func sideButtonFrames(availableSize: CGSize, cancelSize: CGSize, doneSize: CGSize, component: MediaPickerPhotoToolbarComponent) -> (cancel: CGRect, done: CGRect) { + let sideInset: CGFloat = 26.0 + if availableSize.width > availableSize.height { + let cancelFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: cancelSize) + let doneFrame: CGRect + if component.hasSendStarsButton { + doneFrame = CGRect(x: availableSize.width - doneSize.width - 2.0, y: 0.0, width: doneSize.width, height: doneSize.height) + } else { + doneFrame = CGRect(x: availableSize.width - doneSize.width - sideInset, y: 0.0, width: doneSize.width, height: doneSize.height) + } + return (cancelFrame, doneFrame) + } else { + let offset: CGFloat = component.interfaceOrientation == .landscapeLeft ? availableSize.width - toolbarSideButtonSide : 0.0 + let cancelFrame = CGRect(x: offset, y: availableSize.height - toolbarSideButtonSide, width: toolbarSideButtonSide, height: toolbarSideButtonSide) + let doneFrame = CGRect(x: offset, y: 0.0, width: toolbarSideButtonSide, height: toolbarSideButtonSide) + return (cancelFrame, doneFrame) + } + } + + private func updateCenterButtons(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, doneSize: CGSize, selectedIconColor: UIColor, accentColor: UIColor, transition: ComponentTransition) { + let tabs = toolbarTabOrder.filter { component.currentTabs.contains($0) } + let visibleRawValues = Set(tabs.map { $0.rawValue }) + + for (rawValue, buttonView) in Array(self.centerButtonViews) { + if !visibleRawValues.contains(rawValue) { + if let view = buttonView.view { + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + self.centerButtonViews.removeValue(forKey: rawValue) + } + } + + guard !tabs.isEmpty else { + transition.setAlpha(view: self.buttonsBackgroundView, alpha: 0.0) + transition.setAlpha(view: self.selectionView, alpha: 0.0) + self.buttonsBackgroundView.isUserInteractionEnabled = false + return + } + + let buttonFrames = self.centerButtonFrames(tabs: tabs, availableSize: availableSize, doneSize: doneSize, component: component) + var backgroundFrame = CGRect.null + for tab in tabs { + if let frame = buttonFrames[tab.rawValue] { + backgroundFrame = backgroundFrame.union(frame) + } + } + if backgroundFrame.isNull { + transition.setAlpha(view: self.buttonsBackgroundView, alpha: 0.0) + transition.setAlpha(view: self.selectionView, alpha: 0.0) + self.buttonsBackgroundView.isUserInteractionEnabled = false + return + } + backgroundFrame = CGRect( + x: floorToScreenPixels(backgroundFrame.minX), + y: floorToScreenPixels(backgroundFrame.minY), + width: ceil(backgroundFrame.width), + height: ceil(backgroundFrame.height) + ) + + let centerHidden = component.allButtonsHidden || component.centerButtonsHidden || component.editButtonsHidden + let centerAlpha: CGFloat = centerHidden ? 0.0 : (component.editButtonsEnabled ? 1.0 : 0.2) + self.buttonsBackgroundView.isUserInteractionEnabled = !centerHidden && component.editButtonsEnabled + transition.setAlpha(view: self.buttonsBackgroundView, alpha: centerAlpha) + transition.setFrame(view: self.buttonsBackgroundView, frame: backgroundFrame) + + let minSide = min(backgroundFrame.width, backgroundFrame.height) + self.buttonsBackgroundView.update(size: backgroundFrame.size, cornerRadius: minSide * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !centerHidden, transition: transition) + + if self.selectionView.superview == nil { + self.buttonsBackgroundView.contentView.insertSubview(self.selectionView, at: 0) + } else { + self.buttonsBackgroundView.contentView.sendSubviewToBack(self.selectionView) + } + + var selectionFrame: CGRect? + + for tab in tabs { + guard let buttonFrame = buttonFrames[tab.rawValue] else { + continue + } + + let rawValue = tab.rawValue + let buttonView: ComponentView + if let current = self.centerButtonViews[rawValue] { + buttonView = current + } else { + buttonView = ComponentView() + self.centerButtonViews[rawValue] = buttonView + } + + let isDisabled = component.disabledTabs.contains(tab) + let isHighlighted = component.highlightedTabs.contains(tab) + let selectedVisual = component.activeTab == tab && !dontHighlightOnSelectionTabs.contains(rawValue) + let iconColor: UIColor + if selectedVisual { + iconColor = selectedIconColor + } else if isHighlighted && !isDisabled { + iconColor = accentColor + } else { + iconColor = .white + } + + let content: AnyComponent + switch tab { + case .qualityTab: + content = AnyComponent( + Image( + image: self.imageCache.qualityIcon( + isPhoto: component.qualityIsPhoto, + highQuality: component.qualityHighQuality, + preset: component.qualityPreset, + color: iconColor + ), + size: CGSize(width: 28.0, height: 22.0), + contentMode: .center + ) + ) + case .timerTab: + content = AnyComponent( + Image( + image: self.imageCache.timerIcon(value: component.timerValue, color: iconColor), + size: CGSize(width: 24.0, height: 24.0), + contentMode: .center + ) + ) + default: + content = AnyComponent( + BundleIconComponent( + name: self.iconName(tab), + tintColor: iconColor, + maxSize: CGSize(width: 28.0, height: 28.0) + ) + ) + } + let buttonSize = buttonView.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: content, + minSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide), + action: { [weak self] in + self?.tabPressed?(tab) + }, + isEnabled: component.editButtonsEnabled && !isDisabled, + animateAlpha: true, + animateScale: false, + animateContents: false + ) + ), + environment: {}, + containerSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide) + ) + + if let view = buttonView.view { + if view.superview == nil { + self.buttonsBackgroundView.contentView.addSubview(view) + } + let localFrame = CGRect( + x: buttonFrame.minX - backgroundFrame.minX, + y: buttonFrame.minY - backgroundFrame.minY, + width: buttonSize.width, + height: buttonSize.height + ) + if selectedVisual { + let selectionSide = max(0.0, min(backgroundFrame.width, backgroundFrame.height) - 6.0) + selectionFrame = CGRect( + x: floorToScreenPixels(localFrame.midX - selectionSide / 2.0), + y: floorToScreenPixels(localFrame.midY - selectionSide / 2.0), + width: selectionSide, + height: selectionSide + ) + } + transition.setFrame(view: view, frame: localFrame) + transition.setAlpha(view: view, alpha: isDisabled ? 0.2 : 1.0) + } + } + + if let selectionFrame = selectionFrame { + self.selectionView.layer.cornerRadius = selectionFrame.width * 0.5 + transition.setFrame(view: self.selectionView, frame: selectionFrame) + transition.setAlpha(view: self.selectionView, alpha: centerHidden ? 0.0 : 1.0) + } else { + transition.setAlpha(view: self.selectionView, alpha: 0.0) + } + } + + private func iconName(_ tab: TGPhotoEditorTab) -> String { + switch tab { + case .cropTab: + return "Media Editor/Crop" + case .toolsTab: + return "Media Editor/Adjustments" + case .rotateTab: + return "Editor/Rotate" + case .paintTab: + return "Media Editor/Pencil" + case .stickerTab: + return "Media Editor/AddSticker" + case .textTab: + return "Media Editor/AddText" + case .eraserTab: + return "Editor/Eraser" + case .mirrorTab: + return "Media Editor/Mirror" + case .aspectRatioTab: + return "Editor/AspectRatio" + case .tintTab: + return "Media Editor/Tint" + case .blurTab: + return "Media Editor/Blur" + case .curvesTab: + return "Media Editor/Curves" + default: + return "Media Editor/Crop" + } + } + + private func centerButtonFrames(tabs: [TGPhotoEditorTab], availableSize: CGSize, doneSize: CGSize, component: MediaPickerPhotoToolbarComponent) -> [UInt: CGRect] { + var result: [UInt: CGRect] = [:] + let count = tabs.count + guard count != 0 else { + return result + } + + let totalLength = CGFloat(count) * toolbarButtonSide + CGFloat(count - 1) * centerButtonSpacing + let step = toolbarButtonSide + centerButtonSpacing + + if availableSize.width > availableSize.height { + let leftEdge = toolbarSideButtonSide + let rightEdge: CGFloat = component.hasSendStarsButton ? doneSize.width + 2.0 : toolbarSideButtonSide + let availableWidth = availableSize.width - leftEdge - rightEdge + let startX = floorToScreenPixels(leftEdge + (availableWidth - totalLength) / 2.0) + + for i in 0 ..< count { + result[tabs[i].rawValue] = CGRect( + x: startX + CGFloat(i) * step, + y: 0.0, + width: toolbarButtonSide, + height: toolbarButtonSide + ) + } + } else { + let x: CGFloat + if component.interfaceOrientation == .landscapeLeft { + x = availableSize.width - toolbarButtonSide - 8.0 + } else { + x = 8.0 + } + + let topInset = toolbarSideButtonSide + let bottomInset = toolbarSideButtonSide + let availableHeight = availableSize.height - topInset - bottomInset + let startY = floorToScreenPixels(topInset + (availableHeight - totalLength) / 2.0) + + for i in 0 ..< count { + result[tabs[i].rawValue] = CGRect( + x: x, + y: startY + CGFloat(i) * step, + width: toolbarButtonSide, + height: toolbarButtonSide + ) + } + } + + return result + } + + private func updateInfoLabel(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, transition: ComponentTransition) { + self.infoLabel.text = component.infoString + self.infoLabel.isHidden = component.infoString == nil + guard component.infoString != nil else { + return + } + + if availableSize.width > availableSize.height { + self.infoLabel.transform = .identity + transition.setFrame(view: self.infoLabel, frame: CGRect(x: toolbarSideButtonSide + 10.0, y: 0.0, width: availableSize.width - (toolbarSideButtonSide + 10.0) * 2.0, height: toolbarSideButtonSide)) + } else { + let bounds = CGRect(x: 0.0, y: 0.0, width: availableSize.height - (toolbarSideButtonSide + 10.0) * 2.0, height: availableSize.width) + self.infoLabel.bounds = bounds + self.infoLabel.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) + if component.interfaceOrientation == .landscapeLeft { + self.infoLabel.transform = CGAffineTransform(rotationAngle: .pi / 2.0) + } else if component.interfaceOrientation == .landscapeRight { + self.infoLabel.transform = CGAffineTransform(rotationAngle: -.pi / 2.0) + } else { + self.infoLabel.transform = .identity + } + } + } + } + + func makeView() -> View { + return View(frame: .zero) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public func makeMediaPickerPhotoToolbarView(context: AccountContext, backButton: TGPhotoEditorBackButton, doneButton: TGPhotoEditorDoneButton, solidBackground: Bool, hasSendStarsButton: Bool) -> (UIView & TGPhotoToolbarViewProtocol)? { + return MediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) +} + +final class MediaPickerPhotoToolbarView: UIView, TGPhotoToolbarViewProtocol { + private let context: AccountContext + private let solidBackground: Bool + private let hasSendStarsButton: Bool + private let rootView = ComponentView() + + private var transitionedOut = false + + var cancelPressed: (() -> Void)? { + didSet { + self.update(transition: .immediate) + } + } + var donePressed: (() -> Void)? { + didSet { + self.update(transition: .immediate) + } + } + var doneLongPressed: ((Any?) -> Void)? { + didSet { + self.update(transition: .immediate) + } + } + var tabPressed: ((TGPhotoEditorTab) -> Void)? { + didSet { + self.update(transition: .immediate) + } + } + + var interfaceOrientation: UIInterfaceOrientation = .portrait { + didSet { + self.update(transition: .immediate) + } + } + + var bottomInset: CGFloat = 0.0 + + var backButtonType: TGPhotoEditorBackButton { + didSet { + self.update(transition: .immediate) + } + } + + var doneButtonType: TGPhotoEditorDoneButton { + didSet { + self.update(transition: .immediate) + } + } + + var sendPaidMessageStars: Int64 = 0 { + didSet { + self.update(transition: ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))) + } + } + + private(set) var currentTabs: TGPhotoEditorTab = [] + private var activeTab: TGPhotoEditorTab = [] + private var highlightedTabs: TGPhotoEditorTab = [] + private var disabledTabs: TGPhotoEditorTab = [] + private var editButtonsHidden = false + private var editButtonsEnabled = true + private var centerButtonsHidden = false + private var allButtonsHidden = false + private var cancelDoneButtonsHidden = false + private var doneButtonEnabled = true + private var qualityIsPhoto = false + private var qualityHighQuality = false + private var qualityPreset = 3 + private var timerValue = 0 + private var infoString: String? + + var doneButton: UIView { + return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.doneButtonSourceView ?? UIView() + } + + var cancelButtonFrame: CGRect { + return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.cancelButtonFrame ?? .zero + } + + var doneButtonFrame: CGRect { + return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.doneButtonFrame ?? .zero + } + + init(context: AccountContext, backButton: TGPhotoEditorBackButton, doneButton: TGPhotoEditorDoneButton, solidBackground: Bool, hasSendStarsButton: Bool) { + self.context = context + self.backButtonType = backButton + self.doneButtonType = doneButton + self.solidBackground = solidBackground + self.hasSendStarsButton = hasSendStarsButton + + super.init(frame: .zero) + + self.clipsToBounds = false + self.backgroundColor = .clear + self.update(transition: .immediate) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.update(transition: .immediate) + self.updateRootFrame(transition: .immediate) + } + + func transitionIn(animated: Bool) { + self.transitionIn(animated: animated, transparent: false) + } + + func transitionIn(animated: Bool, transparent: Bool) { + self.transitionedOut = false + self.isHidden = false + if !transparent { + self.backgroundColor = UIColor(rgb: 0x000000) + } + + let transition = animated ? ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)) : .immediate + self.updateRootFrame(transition: transition) + + if !transparent { + self.backgroundColor = .clear + } + } + + func transitionOut(animated: Bool) { + self.transitionOut(animated: animated, transparent: false, hideOnCompletion: false) + } + + func transitionOut(animated: Bool, transparent: Bool, hideOnCompletion: Bool) { + self.transitionedOut = true + if !transparent { + self.backgroundColor = UIColor(rgb: 0x000000) + } + + let duration: Double = animated ? 0.3 : 0.0 + let transition = animated ? ComponentTransition(animation: .curve(duration: duration, curve: .easeInOut)) : .immediate + self.updateRootFrame(transition: transition) + + if hideOnCompletion { + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { [weak self] in + self?.isHidden = true + } + } + } + + func setDoneButtonEnabled(_ enabled: Bool, animated: Bool) { + self.doneButtonEnabled = enabled + self.update(transition: self.transition(animated: animated)) + } + + func setEditButtonsEnabled(_ enabled: Bool, animated: Bool) { + self.editButtonsEnabled = enabled + self.update(transition: self.transition(animated: animated)) + } + + func setEditButtonsHidden(_ hidden: Bool, animated: Bool) { + self.editButtonsHidden = hidden + self.update(transition: self.transition(animated: animated)) + } + + func setEditButtonsHighlighted(_ buttons: TGPhotoEditorTab) { + self.highlightedTabs = buttons + self.update(transition: .immediate) + } + + func setEditButtonsDisabled(_ buttons: TGPhotoEditorTab) { + self.disabledTabs = buttons + self.update(transition: .immediate) + } + + func setCenterButtonsHidden(_ hidden: Bool, animated: Bool) { + self.centerButtonsHidden = hidden + self.update(transition: self.transition(animated: animated)) + } + + func setAllButtonsHidden(_ hidden: Bool, animated: Bool) { + self.allButtonsHidden = hidden + self.update(transition: self.transition(animated: animated)) + } + + func setCancelDoneButtonsHidden(_ hidden: Bool, animated: Bool) { + self.cancelDoneButtonsHidden = hidden + self.update(transition: self.transition(animated: animated)) + } + + func setToolbarTabs(_ tabs: TGPhotoEditorTab, animated: Bool) { + self.currentTabs = tabs + self.update(transition: self.transition(animated: animated)) + } + + func setActiveTab(_ tab: TGPhotoEditorTab) { + self.activeTab = tab + self.update(transition: .spring(duration: 0.4)) + } + + func setQualityButtonIsPhoto(_ isPhoto: Bool, highQuality: Bool, videoPreset: Int) { + self.qualityIsPhoto = isPhoto + self.qualityHighQuality = highQuality + self.qualityPreset = videoPreset + self.update(transition: .immediate) + } + + func setTimerButtonValue(_ value: Int) { + self.timerValue = value + self.update(transition: .immediate) + } + + func setInfoString(_ string: String?) { + self.infoString = string + self.update(transition: .immediate) + } + + @objc(viewForTab:) + func view(for tab: TGPhotoEditorTab) -> UIView? { + return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.viewForTab(tab) + } + + private func transition(animated: Bool) -> ComponentTransition { + if animated { + return ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)) + } else { + return .immediate + } + } + + private func update(transition: ComponentTransition) { + let size = self.bounds.size + let _ = self.rootView.update( + transition: transition, + component: AnyComponent( + MediaPickerPhotoToolbarComponent( + context: self.context, + solidBackground: self.solidBackground, + backButtonType: self.backButtonType, + doneButtonType: self.doneButtonType, + currentTabs: self.currentTabs, + activeTab: self.activeTab, + highlightedTabs: self.highlightedTabs, + disabledTabs: self.disabledTabs, + qualityIsPhoto: self.qualityIsPhoto, + qualityHighQuality: self.qualityHighQuality, + qualityPreset: self.qualityPreset, + timerValue: self.timerValue, + hasSendStarsButton: self.hasSendStarsButton, + sendPaidMessageStars: self.sendPaidMessageStars, + editButtonsHidden: self.editButtonsHidden, + editButtonsEnabled: self.editButtonsEnabled, + centerButtonsHidden: self.centerButtonsHidden, + allButtonsHidden: self.allButtonsHidden, + cancelDoneButtonsHidden: self.cancelDoneButtonsHidden, + doneButtonEnabled: self.doneButtonEnabled, + interfaceOrientation: self.interfaceOrientation, + bottomInset: self.bottomInset, + infoString: self.infoString, + cancelPressed: self.cancelPressed, + donePressed: self.donePressed, + doneLongPressed: self.doneLongPressed, + tabPressed: self.tabPressed + ) + ), + environment: {}, + forceUpdate: true, + containerSize: size + ) + + if let view = self.rootView.view { + if view.superview == nil { + self.addSubview(view) + } + self.updateRootFrame(transition: transition) + } + } + + private func updateRootFrame(transition: ComponentTransition) { + guard let view = self.rootView.view else { + return + } + + var frame = CGRect(origin: .zero, size: self.bounds.size) + if self.transitionedOut { + if self.bounds.width > self.bounds.height { + frame.origin.y = self.bounds.height + } else if self.interfaceOrientation == .landscapeLeft { + frame.origin.x = -self.bounds.width + } else { + frame.origin.x = self.bounds.width + } + } + transition.setFrame(view: view, frame: frame) + } +} diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c7b6ffae17..a95317f79d 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -16,7 +15,6 @@ import LegacyComponents import LegacyMediaPickerUI import AttachmentUI import ContextUI -import WebSearchUI import SparseItemGrid import UndoUI import PresentationDataUtils @@ -193,7 +191,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att case addImage case cover case createSticker - case createAvatar + case createAvatar(mode: PeerType) case poll(mode: PollMode, asFile: Bool) } @@ -240,19 +238,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private let cancelButtonNode: WebAppCancelButtonNode private var buttons: ComponentView? - private var cancelButton: ComponentView? - private var rightButton: ComponentView? private let moreButtonPlayOnce = ActionSlot() private let moreButtonNode: MoreButtonNode private let selectedButtonNode: SelectedButtonNode - public weak var webSearchController: WebSearchController? - public var openCamera: ((Any?) -> Void)? - public var presentSchedulePicker: (Bool, @escaping (Int32) -> Void) -> Void = { _, _ in } + public var presentSchedulePicker: (Bool, @escaping (Int32, Bool) -> Void) -> Void = { _, _ in } public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in } - public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } public var openBoost: () -> Void = { } @@ -708,8 +701,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att var useModernCamera = false if case .assets(nil, .default) = controller.subject { useLegacyCamera = true - } else if case .assets(nil, let mode) = controller.subject, [.createSticker, .createAvatar].contains(mode) { - useModernCamera = true + } else if case .assets(nil, let mode) = controller.subject { + switch mode { + case .createSticker, .createAvatar: + useModernCamera = true + default: + break + } } if useLegacyCamera { @@ -1520,28 +1518,32 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } } - private func openLimitedMediaOptions() { - let presentationData = self.presentationData - let controller = ActionSheetController(presentationData: self.presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() + private func openLimitedMediaOptions(sourceView: UIView) { + guard let controller = self.controller else { + return } - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Media_LimitedAccessSelectMore, color: .accent, action: { [weak self] in - dismissAction() - if #available(iOS 14.0, *), let strongController = self?.controller { - PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: strongController) - } - }), - ActionSheetButtonItem(title: presentationData.strings.Media_LimitedAccessChangeSettings, color: .accent, action: { [weak self] in - dismissAction() - self?.controller?.context.sharedContext.applicationBindings.openSettings() - }) - ]), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - self.controller?.present(controller, in: .window(.root)) + + let presentationData = self.presentationData + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: presentationData.strings.Media_LimitedAccessSelectMore, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + if #available(iOS 14.0, *), let controller = self?.controller { + PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: controller) + } + })), + .action(ContextMenuActionItem(text: presentationData.strings.Media_LimitedAccessChangeSettings, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + self?.controller?.context.sharedContext.applicationBindings.openSettings() + })) + ] + let contextController = makeContextController(presentationData: presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) + controller.presentInGlobalOverlay(contextController) } private func getItemSnapshot(_ identifier: String) -> UIView? { @@ -1773,9 +1775,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att manageNode = current } else { manageNode = MediaPickerManageNode() - manageNode.pressed = { [weak self] in + manageNode.pressed = { [weak self] sourceView in if let strongSelf = self { - strongSelf.openLimitedMediaOptions() + strongSelf.openLimitedMediaOptions(sourceView: sourceView) } } self.manageNode = manageNode @@ -1791,7 +1793,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att manageNode = current } else { manageNode = MediaPickerManageNode() - manageNode.pressed = { [weak self] in + manageNode.pressed = { [weak self] _ in self?.controller?.context.sharedContext.applicationBindings.openSettings() } self.manageNode = manageNode @@ -2073,9 +2075,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.titleView.title = presentationData.strings.MediaPicker_Recents self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker self.titleView.isEnabled = true - case .createAvatar: + case let .createAvatar(avatarMode): self.titleView.title = presentationData.strings.MediaPicker_Recents - self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewPhoto + switch avatarMode { + case .user: + self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewPhoto + case .group: + self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewGroupPhoto + case .channel: + self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewChannelPhoto + } self.titleView.isEnabled = true case .story: self.titleView.title = presentationData.strings.MediaPicker_Recents @@ -2104,7 +2113,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } if case .glass = style { - self.cancelButton = ComponentView() self.buttons = ComponentView() } self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings) @@ -2213,21 +2221,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.scrollToTop = { [weak self] in if let strongSelf = self { - if let webSearchController = strongSelf.webSearchController { - webSearchController.scrollToTop?() - } else { - strongSelf.controllerNode.scrollToTop(animated: true) - } + strongSelf.controllerNode.scrollToTop(animated: true) } } self.scrollToTopWithTabBar = { [weak self] in if let strongSelf = self { - if let webSearchController = strongSelf.webSearchController { - webSearchController.cancel() - } else { - strongSelf.scrollToTop?() - } + strongSelf.scrollToTop?() } } @@ -2301,8 +2301,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } }, schedule: { [weak self] parameters in if let strongSelf = self { - strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.interaction?.sendSelected(nil, false, time, true, parameters, {}) + strongSelf.presentSchedulePicker(false, { [weak self] time, silentPosting in + self?.interaction?.sendSelected(nil, silentPosting, time, true, parameters, {}) }) } }, dismissInput: { [weak self] in @@ -2826,7 +2826,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.updateNavigationStack { current in var mediaPickerContext: AttachmentMediaPickerContext? if let first = current.first as? MediaPickerScreenImpl { - mediaPickerContext = first.webSearchController?.mediaPickerContext ?? first.mediaPickerContext + mediaPickerContext = first.mediaPickerContext } return (current.filter { $0 !== self }, mediaPickerContext) } @@ -2909,10 +2909,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } public func resetForReuse() { - if let webSearchController = self.webSearchController { - self.webSearchController = nil - webSearchController.dismiss() - } self.scrollToTop?() self.controllerNode.isSuspended = true @@ -2975,8 +2971,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if case .story = mode { self.updateNavigationStack({ _ in return ([self, groupsController], self.mediaPickerContext)}) - } else { - self.presentWebSearch(groupsController, activateOnDisplay) } self.groupsController = groupsController } @@ -3593,26 +3587,6 @@ public func mediaPickerController( completion(result) controller.dismiss(animated: true) } - if hasSearch { - mediaPickerController.presentWebSearch = { [weak mediaPickerController] groups, activateOnDisplay in - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> deliverOnMainQueue).start(next: { configuration in - let webSearchController = WebSearchController( - context: context, - updatedPresentationData: updatedPresentationData, - peer: nil, - chatLocation: nil, - configuration: configuration, - mode: .editor(completion: { [weak mediaPickerController] image in - completion(image) - mediaPickerController?.dismiss(animated: true) - }), - activateOnDisplay: activateOnDisplay - ) - mediaPickerController?.present(webSearchController, in: .current) - }) - } - } present(mediaPickerController, mediaPickerController.mediaPickerContext) return true } @@ -3913,6 +3887,7 @@ public func stickerMediaPickerController( public func avatarMediaPickerController( context: AccountContext, + peerType: PeerType, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, @@ -3923,21 +3898,35 @@ public func avatarMediaPickerController( final class PickerDelegate: NSObject, PHPickerViewControllerDelegate { var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)? - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true) - - for item in results { - if item.itemProvider.canLoadObject(ofClass: UIImage.self) { - item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in - if let uiImage = image as? UIImage { - Queue.mainQueue().async { - self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) - } + private func resolveResult(_ result: PHPickerResult) { + if let assetIdentifier = result.assetIdentifier { + let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil) + if let asset = fetchResult.firstObject { + Queue.mainQueue().async { + self.completion?(asset, nil, CGRect(), nil, false, { _ in return nil }, {}) + } + return + } + } + + if result.itemProvider.canLoadObject(ofClass: UIImage.self) { + result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in + if let uiImage = image as? UIImage { + Queue.mainQueue().async { + self?.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) } } } } } + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true) + + if let result = results.first { + self.resolveResult(result) + } + } } let holder = PickerDelegate() @@ -3945,7 +3934,7 @@ public func avatarMediaPickerController( let openMediaPicker = { var configuration = PHPickerConfiguration(photoLibrary: .shared()) - configuration.filter = .images + configuration.filter = .any(of: [.images, .videos]) configuration.selectionLimit = 1 let picker = PHPickerViewController(configuration: configuration) @@ -4012,7 +4001,7 @@ public func avatarMediaPickerController( chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, - subject: .assets(nil, .createAvatar), + subject: .assets(nil, .createAvatar(mode: peerType)), mainButtonState: mainButtonState, mainButtonAction: { [weak controller] in controller?.dismiss(animated: true) diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 31622e2b02..2f58275139 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -663,6 +663,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false self.view.addGestureRecognizer(ReorderingGestureRecognizer(animateOnTouch: !self.persistentItems, shouldBegin: { [weak self] point in if let strongSelf = self, !strongSelf.scrollNode.view.isDragging && strongSelf.itemNodes.count > 1 { diff --git a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift index 74fce2874b..1a5c25b737 100644 --- a/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift +++ b/submodules/MediaPlayer/Sources/ChunkMediaPlayerDirectFetchSourceImpl.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import FFMpegBinding import RangeSet diff --git a/submodules/MediaPlayer/Sources/FFMpegAudioFrameDecoder.swift b/submodules/MediaPlayer/Sources/FFMpegAudioFrameDecoder.swift index 16e9619170..67a184a3b9 100644 --- a/submodules/MediaPlayer/Sources/FFMpegAudioFrameDecoder.swift +++ b/submodules/MediaPlayer/Sources/FFMpegAudioFrameDecoder.swift @@ -7,7 +7,7 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { private let swrContext: FFMpegSWResample private var timescale: CMTimeScale = 44000 - private let audioFrame: FFMpegAVFrame + private let audioFrame: FFMpegAVFrame? private var resetDecoderOnNextFrame = true private let formatDescription: CMAudioFormatDescription @@ -43,11 +43,15 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { } func decodeRaw(frame: MediaTrackDecodableFrame) -> Data? { + guard let audioFrame = self.audioFrame else { + return nil + } + let status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { - let result = self.codecContext.receive(into: self.audioFrame) + let result = self.codecContext.receive(into: audioFrame) if case .success = result { - guard let data = self.swrContext.resample(self.audioFrame) else { + guard let data = self.swrContext.resample(audioFrame) else { return nil } @@ -67,10 +71,14 @@ final class FFMpegAudioFrameDecoder: MediaTrackFrameDecoder { } func decode() -> MediaTrackFrame? { + guard let audioFrame = self.audioFrame else { + return nil + } + while true { - let result = self.codecContext.receive(into: self.audioFrame) + let result = self.codecContext.receive(into: audioFrame) if case .success = result { - if let convertedFrame = convertAudioFrame(self.audioFrame) { + if let convertedFrame = convertAudioFrame(audioFrame) { self.delayedFrames.append(convertedFrame) } } else { diff --git a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift index 598916b345..efad3a182f 100644 --- a/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift +++ b/submodules/MediaPlayer/Sources/FFMpegMediaVideoFrameDecoder.swift @@ -47,7 +47,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { private let codecContext: FFMpegAVCodecContext - private let videoFrame: FFMpegAVFrame + private let videoFrame: FFMpegAVFrame? private var resetDecoderOnNextFrame = true private var isError = false @@ -91,27 +91,29 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } public func receiveFromDecoder(ptsOffset: CMTime?, displayImmediately: Bool = true) -> ReceiveResult { + guard let videoFrame = self.videoFrame else { + return .error + } if self.isError { return .error } - guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { return .error } - let receiveResult = self.codecContext.receive(into: self.videoFrame) + let receiveResult = self.codecContext.receive(into: videoFrame) switch receiveResult { case .success: - if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + if videoFrame.width * videoFrame.height > 4 * 1024 * 4 * 1024 { self.isError = true return .error } - var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale) + var pts = CMTimeMake(value: videoFrame.pts, timescale: defaultTimescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) } - if let convertedFrame = convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: self.videoFrame.duration > 0 ? CMTimeMake(value: self.videoFrame.duration, timescale: defaultTimescale) : defaultDuration, displayImmediately: displayImmediately) { + if let convertedFrame = convertVideoFrame(videoFrame, pts: pts, dts: pts, duration: videoFrame.duration > 0 ? CMTimeMake(value: videoFrame.duration, timescale: defaultTimescale) : defaultDuration, displayImmediately: displayImmediately) { return .result(convertedFrame) } else { return .error @@ -137,6 +139,9 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } public func decode(ptsOffset: CMTime?, forceARGB: Bool = false, unpremultiplyAlpha: Bool = true, displayImmediately: Bool = true) -> MediaTrackFrame? { + guard let videoFrame = self.videoFrame else { + return nil + } if self.isError { return nil } @@ -144,23 +149,26 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { return nil } - if self.codecContext.receive(into: self.videoFrame) == .success { - if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + if self.codecContext.receive(into: videoFrame) == .success { + if videoFrame.width * videoFrame.height > 4 * 1024 * 4 * 1024 { self.isError = true return nil } - var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale) + var pts = CMTimeMake(value: videoFrame.pts, timescale: defaultTimescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) } - return convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: defaultDuration, forceARGB: forceARGB, unpremultiplyAlpha: unpremultiplyAlpha, displayImmediately: displayImmediately) + return convertVideoFrame(videoFrame, pts: pts, dts: pts, duration: defaultDuration, forceARGB: forceARGB, unpremultiplyAlpha: unpremultiplyAlpha, displayImmediately: displayImmediately) } return nil } public func receiveRemainingFrames(ptsOffset: CMTime?) -> [MediaTrackFrame] { + guard let videoFrame = self.videoFrame else { + return [] + } guard let defaultTimescale = self.defaultTimescale, let defaultDuration = self.defaultDuration else { return [] } @@ -173,17 +181,17 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { self.delayedFrames.removeAll() while true { - if case .success = self.codecContext.receive(into: self.videoFrame) { - if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + if case .success = self.codecContext.receive(into: videoFrame) { + if videoFrame.width * videoFrame.height > 4 * 1024 * 4 * 1024 { self.isError = true return [] } - var pts = CMTimeMake(value: self.videoFrame.pts, timescale: defaultTimescale) + var pts = CMTimeMake(value: videoFrame.pts, timescale: defaultTimescale) if let ptsOffset = ptsOffset { pts = CMTimeAdd(pts, ptsOffset) } - if let convertedFrame = convertVideoFrame(self.videoFrame, pts: pts, dts: pts, duration: self.videoFrame.duration > 0 ? CMTimeMake(value: self.videoFrame.duration, timescale: defaultTimescale) : defaultDuration) { + if let convertedFrame = convertVideoFrame(videoFrame, pts: pts, dts: pts, duration: videoFrame.duration > 0 ? CMTimeMake(value: videoFrame.duration, timescale: defaultTimescale) : defaultDuration) { result.append(convertedFrame) } } else { @@ -194,15 +202,18 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } public func render(frame: MediaTrackDecodableFrame) -> UIImage? { + guard let videoFrame = self.videoFrame else { + return nil + } let status = frame.packet.send(toDecoder: self.codecContext) if status == 0 { - if case .success = self.codecContext.receive(into: self.videoFrame) { - if self.videoFrame.width * self.videoFrame.height > 4 * 1024 * 4 * 1024 { + if case .success = self.codecContext.receive(into: videoFrame) { + if videoFrame.width * videoFrame.height > 4 * 1024 * 4 * 1024 { self.isError = true return nil } - return convertVideoFrameToImage(self.videoFrame) + return convertVideoFrameToImage(videoFrame) } } @@ -229,7 +240,39 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { } } + private static func isPlanarYUV420Safe(_ frame: FFMpegAVFrame) -> Bool { + // Layout must be a planar YUV/YUVA 420 variant. Anything else (BGRA, NV12, gray8, + // YUV422P, …) must not reach the planar 420 vImage conversion or the planar copy + // path that force-unwraps data[1]/data[2]. + let format = frame.pixelFormat + guard format == .YUV || format == .YUVA else { + return false + } + // Reject bottom-up / negative-stride frames before any row-copy loop — ffmpeg + // rawdec flips linesize[0] for codec_tag WRAW/cyuv/BottomUp, and other decoders + // (dxtory, mimic, mjpegdec, scpr) flip the chroma planes too. + if frame.lineSize[0] < 0 || frame.lineSize[1] < 0 || frame.lineSize[2] < 0 { + return false + } + if format == .YUVA && frame.lineSize[3] < 0 { + return false + } + if frame.data[0] == nil || frame.data[1] == nil || frame.data[2] == nil { + return false + } + if format == .YUVA && frame.data[3] == nil { + return false + } + if frame.width <= 0 || frame.height <= 0 { + return false + } + return true + } + private func convertVideoFrameToImage(_ frame: FFMpegAVFrame) -> UIImage? { + guard FFMpegMediaVideoFrameDecoder.isPlanarYUV420Safe(frame) else { + return nil + } var info = vImage_YpCbCrToARGB() var pixelRange: vImage_YpCbCrPixelRange @@ -314,7 +357,7 @@ public final class FFMpegMediaVideoFrameDecoder: MediaTrackFrameDecoder { return MediaTrackFrame(type: .video, sampleBuffer: sampleBuffer!, resetDecoder: resetDecoder, decoded: true) } - if frame.data[0] == nil { + guard FFMpegMediaVideoFrameDecoder.isPlanarYUV420Safe(frame) else { return nil } if frame.lineSize[1] != frame.lineSize[2] { diff --git a/submodules/MediaPlayer/Sources/MediaDataReader.swift b/submodules/MediaPlayer/Sources/MediaDataReader.swift index b07c6ec251..89717fc27e 100644 --- a/submodules/MediaPlayer/Sources/MediaDataReader.swift +++ b/submodules/MediaPlayer/Sources/MediaDataReader.swift @@ -3,7 +3,6 @@ import AVFoundation import CoreMedia import FFMpegBinding import VideoToolbox -import Postbox #if os(macOS) public let internal_isHardwareAv1Supported: Bool = { diff --git a/submodules/MediaResources/BUILD b/submodules/MediaResources/BUILD index 16e9f6ac37..5ce6f52464 100644 --- a/submodules/MediaResources/BUILD +++ b/submodules/MediaResources/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramUIPreferences:TelegramUIPreferences", ], diff --git a/submodules/MediaResources/Sources/CachedResourceRepresentations.swift b/submodules/MediaResources/Sources/CachedResourceRepresentations.swift index 32db1d2948..2483da6dd4 100644 --- a/submodules/MediaResources/Sources/CachedResourceRepresentations.swift +++ b/submodules/MediaResources/Sources/CachedResourceRepresentations.swift @@ -1,11 +1,11 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit +import TelegramCore -public final class CachedStickerAJpegRepresentation: CachedMediaResourceRepresentation { +public final class CachedStickerAJpegRepresentation: EngineRawCachedMediaResourceRepresentation { public let size: CGSize? - public let keepDuration: CachedMediaRepresentationKeepDuration = .general + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { if let size = self.size { @@ -19,7 +19,7 @@ public final class CachedStickerAJpegRepresentation: CachedMediaResourceRepresen self.size = size } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedStickerAJpegRepresentation { return self.size == to.size } else { @@ -33,8 +33,8 @@ public enum CachedScaledImageRepresentationMode: Int32 { case aspectFit = 1 } -public final class CachedScaledImageRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedScaledImageRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let size: CGSize public let mode: CachedScaledImageRepresentationMode @@ -48,7 +48,7 @@ public final class CachedScaledImageRepresentation: CachedMediaResourceRepresent self.mode = mode } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedScaledImageRepresentation { return self.size == to.size && self.mode == to.mode } else { @@ -57,8 +57,8 @@ public final class CachedScaledImageRepresentation: CachedMediaResourceRepresent } } -public final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedVideoFirstFrameRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { return "first-frame" @@ -67,7 +67,7 @@ public final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepre public init() { } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if to is CachedVideoFirstFrameRepresentation { return true } else { @@ -76,8 +76,8 @@ public final class CachedVideoFirstFrameRepresentation: CachedMediaResourceRepre } } -public final class CachedVideoPrefixFirstFrameRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedVideoPrefixFirstFrameRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { return "prefix-first-frame" @@ -89,7 +89,7 @@ public final class CachedVideoPrefixFirstFrameRepresentation: CachedMediaResourc self.prefixLength = prefixLength } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedVideoPrefixFirstFrameRepresentation { if self.prefixLength != to.prefixLength { return false @@ -101,8 +101,8 @@ public final class CachedVideoPrefixFirstFrameRepresentation: CachedMediaResourc } } -public final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedScaledVideoFirstFrameRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let size: CGSize @@ -114,7 +114,7 @@ public final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourc self.size = size } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedScaledVideoFirstFrameRepresentation { return self.size == to.size } else { @@ -123,8 +123,8 @@ public final class CachedScaledVideoFirstFrameRepresentation: CachedMediaResourc } } -public final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedBlurredWallpaperRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { return "blurred-wallpaper" @@ -133,7 +133,7 @@ public final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepr public init() { } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if to is CachedBlurredWallpaperRepresentation { return true } else { @@ -142,8 +142,8 @@ public final class CachedBlurredWallpaperRepresentation: CachedMediaResourceRepr } } -public final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedAlbumArtworkRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let size: CGSize? @@ -159,7 +159,7 @@ public final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresen self.size = size } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedAlbumArtworkRepresentation { return self.size == to.size } else { @@ -168,8 +168,8 @@ public final class CachedAlbumArtworkRepresentation: CachedMediaResourceRepresen } } -public final class CachedEmojiThumbnailRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedEmojiThumbnailRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let outline: Bool @@ -181,7 +181,7 @@ public final class CachedEmojiThumbnailRepresentation: CachedMediaResourceRepres self.outline = outline } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedEmojiThumbnailRepresentation { return self.outline == to.outline } else { @@ -190,8 +190,8 @@ public final class CachedEmojiThumbnailRepresentation: CachedMediaResourceRepres } } -public final class CachedEmojiRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedEmojiRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let tile: UInt8 public let outline: Bool @@ -205,7 +205,7 @@ public final class CachedEmojiRepresentation: CachedMediaResourceRepresentation self.outline = outline } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let to = to as? CachedEmojiRepresentation { return self.tile == to.tile && self.outline == to.outline } else { @@ -239,8 +239,8 @@ public enum EmojiFitzModifier: Int32, Equatable { } } -public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedAnimatedStickerFirstFrameRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public let width: Int32 public let height: Int32 @@ -261,7 +261,7 @@ public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaRes } } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let other = to as? CachedAnimatedStickerFirstFrameRepresentation { if other.width != self.width { return false @@ -279,8 +279,8 @@ public final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaRes } } -public final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived +public final class CachedAnimatedStickerRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .shortLived public let width: Int32 public let height: Int32 @@ -301,7 +301,7 @@ public final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepre self.fitzModifier = fitzModifier } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let other = to as? CachedAnimatedStickerRepresentation { if other.width != self.width { return false @@ -319,8 +319,8 @@ public final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepre } } -public final class CachedVideoStickerRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived +public final class CachedVideoStickerRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .shortLived public let width: Int32 public let height: Int32 @@ -336,7 +336,7 @@ public final class CachedVideoStickerRepresentation: CachedMediaResourceRepresen self.height = height } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if let other = to as? CachedVideoStickerRepresentation { if other.width != self.width { return false @@ -351,8 +351,8 @@ public final class CachedVideoStickerRepresentation: CachedMediaResourceRepresen } } -public final class CachedPreparedPatternWallpaperRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedPreparedPatternWallpaperRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { return "prepared-pattern-wallpaper" @@ -361,7 +361,7 @@ public final class CachedPreparedPatternWallpaperRepresentation: CachedMediaReso public init() { } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if to is CachedPreparedPatternWallpaperRepresentation { return true } else { @@ -371,8 +371,8 @@ public final class CachedPreparedPatternWallpaperRepresentation: CachedMediaReso } -public final class CachedPreparedSvgRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .general +public final class CachedPreparedSvgRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .general public var uniqueId: String { return "prepared-svg" @@ -381,7 +381,7 @@ public final class CachedPreparedSvgRepresentation: CachedMediaResourceRepresent public init() { } - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if to is CachedPreparedSvgRepresentation { return true } else { diff --git a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift index bdb545a9e2..1bc2629737 100644 --- a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift +++ b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import TelegramCore import TelegramUIPreferences -import Postbox public final class MediaPlaybackStoredState: Codable { public let timestamp: Double @@ -47,7 +46,7 @@ public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: EngineMe public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: EngineMessage.Id, state: MediaPlaybackStoredState?) -> Signal { return engine.messages.updateLocallyDerivedData(messageId: messageId, update: { data in var data = data - if let state, let entry = CodableEntry(state) { + if let state, let entry = EngineCodableEntry(state) { data["mps"] = entry } else { data.removeValue(forKey: "mps") diff --git a/submodules/MetalEngine/Sources/MetalEngine.swift b/submodules/MetalEngine/Sources/MetalEngine.swift index 7fc7064245..f79be16666 100644 --- a/submodules/MetalEngine/Sources/MetalEngine.swift +++ b/submodules/MetalEngine/Sources/MetalEngine.swift @@ -1068,21 +1068,7 @@ public final class MetalEngine { for id in removeSurfaceIds { self.surfaces.removeValue(forKey: id) } - - #if DEBUG - #if targetEnvironment(simulator) - if #available(iOS 13.0, *) { - if let drawable = self.layer.nextDrawable() { - commandBuffer.present(drawable) - } - } - #else - if let drawable = self.layer.nextDrawable() { - commandBuffer.present(drawable) - } - #endif - #endif - + commandBuffer.commit() commandBuffer.waitUntilScheduled() } diff --git a/submodules/MimeTypes/Sources/TGMimeTypeMap.m b/submodules/MimeTypes/Sources/TGMimeTypeMap.m index 89cd7548ef..894c12aa66 100644 --- a/submodules/MimeTypes/Sources/TGMimeTypeMap.m +++ b/submodules/MimeTypes/Sources/TGMimeTypeMap.m @@ -95,7 +95,7 @@ static void initializeMapping() mimeToExtension[@"application/x-dms"] = @"dms"; extensionToMime[@"dms"] = @"application/x-dms"; mimeToExtension[@"application/x-doom"] = @"wad"; extensionToMime[@"wad"] = @"application/x-doom"; mimeToExtension[@"application/x-dvi"] = @"dvi"; extensionToMime[@"dvi"] = @"application/x-dvi"; - mimeToExtension[@"application/x-flac"] = @"flac"; extensionToMime[@"flac"] = @"application/x-flac"; + mimeToExtension[@"audio/flac"] = @"flac"; extensionToMime[@"flac"] = @"audio/flac"; mimeToExtension[@"application/x-font"] = @"pfa"; extensionToMime[@"pfa"] = @"application/x-font"; mimeToExtension[@"application/x-font"] = @"pfb"; extensionToMime[@"pfb"] = @"application/x-font"; mimeToExtension[@"application/x-font"] = @"gsf"; extensionToMime[@"gsf"] = @"application/x-font"; @@ -250,6 +250,7 @@ static void initializeMapping() mimeToExtension[@"text/plain"] = @"text"; extensionToMime[@"text"] = @"text/plain"; mimeToExtension[@"text/plain"] = @"diff"; extensionToMime[@"diff"] = @"text/plain"; mimeToExtension[@"text/plain"] = @"po"; extensionToMime[@"po"] = @"text/plain"; // reserve "pot" for vnd.ms-powerpoint + mimeToExtension[@"text/markdown"] = @"md"; extensionToMime[@"md"] = @"text/markdown"; mimeToExtension[@"text/richtext"] = @"rtx"; extensionToMime[@"rtx"] = @"text/richtext"; mimeToExtension[@"text/rtf"] = @"rtf"; extensionToMime[@"rtf"] = @"text/rtf"; mimeToExtension[@"text/texmacs"] = @"ts"; extensionToMime[@"ts"] = @"text/texmacs"; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTInputStream.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTInputStream.h index bc0bf67327..d8d8f35e7a 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTInputStream.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTInputStream.h @@ -7,19 +7,12 @@ - (instancetype)initWithData:(NSData *)data; - (NSInputStream *)wrappedInputStream; -- (int32_t)readInt32; - (int32_t)readInt32:(bool *)failed __attribute__((nonnull(1))); -- (int64_t)readInt64; - (int64_t)readInt64:(bool *)failed __attribute__((nonnull(1))); -- (double)readDouble; - (double)readDouble:(bool *)failed __attribute__((nonnull(1))); -- (NSData *)readData:(int)length; - (NSData *)readData:(int)length failed:(bool *)failed __attribute__((nonnull(2))); -- (NSMutableData *)readMutableData:(NSUInteger)length; - (NSMutableData *)readMutableData:(NSUInteger)length failed:(bool *)failed __attribute__((nonnull(2))); -- (NSString *)readString; - (NSString *)readString:(bool *)failed __attribute__((nonnull(1))); -- (NSData *)readBytes; - (NSData *)readBytes:(bool *)failed __attribute__((nonnull(1))); @end diff --git a/submodules/MtProtoKit/Sources/MTApiEnvironment.m b/submodules/MtProtoKit/Sources/MTApiEnvironment.m index 92b6ab69cf..36da88228b 100644 --- a/submodules/MtProtoKit/Sources/MTApiEnvironment.m +++ b/submodules/MtProtoKit/Sources/MTApiEnvironment.m @@ -737,6 +737,14 @@ NSString *suffix = @""; [platform isEqualToString:@"iPad16,6"]) return @"iPad Pro 12.9 inch (7th gen)"; + if ([platform isEqualToString:@"iPad16,8"] || + [platform isEqualToString:@"iPad16,9"]) + return @"iPad Air 11 inch (8th gen)"; + + if ([platform isEqualToString:@"iPad16,10"] || + [platform isEqualToString:@"iPad16,11"]) + return @"iPad Air 13 inch (8th gen)"; + if ([platform hasPrefix:@"iPhone"]) return @"Unknown iPhone"; if ([platform hasPrefix:@"iPod"]) diff --git a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m index 68e2f921ed..cbafb7b87c 100644 --- a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m +++ b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m @@ -95,7 +95,6 @@ static NSData *base64_decode(NSString *str) { + (MTSignal *)fetchBackupIpsResolveGoogle:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext addressOverride:(NSString *)addressOverride { NSArray *hosts = @[ @[@"dns.google.com", @""], - @[@"www.google.com", @"dns.google.com"], ]; id encryptionProvider = currentContext.encryptionProvider; @@ -153,6 +152,9 @@ static NSData *base64_decode(NSString *str) { for (NSString *string in strings) { finalString = [finalString stringByAppendingString:[string stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]]; } + while (finalString.length % 4 != 0) { + finalString = [finalString stringByAppendingString:@"="]; + } NSData *result = base64_decode(finalString); NSMutableData *finalData = [[NSMutableData alloc] initWithData:result]; @@ -193,90 +195,6 @@ static NSString *makeRandomPadding() { return string; } -+ (MTSignal *)fetchBackupIpsResolveCloudflare:(bool)isTesting phoneNumber:(NSString *)phoneNumber currentContext:(MTContext *)currentContext addressOverride:(NSString *)addressOverride { - id encryptionProvider = currentContext.encryptionProvider; - - NSArray *hosts = @[ - @[@"mozilla.cloudflare-dns.com", @""], - ]; - - NSMutableArray *signals = [[NSMutableArray alloc] init]; - for (NSArray *hostAndHostname in hosts) { - NSString *host = hostAndHostname[0]; - NSString *hostName = hostAndHostname[1]; - NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; - headers[@"accept"] = @"application/dns-json"; - if ([hostName length] != 0) { - headers[@"Host"] = hostName; - } - NSString *apvHost = @"apv3.stel.com"; - if (addressOverride != nil) { - apvHost = addressOverride; - } - MTSignal *signal = [[[MTHttpRequestOperation dataForHttpUrl:[NSURL URLWithString:[NSString stringWithFormat:@"https://%@/dns-query?name=%@&type=16&random_padding=%@", host, isTesting ? @"tapv3.stel.com" : apvHost, makeRandomPadding()]] headers:headers] mapToSignal:^MTSignal *(MTHttpResponse *response) { - NSString *dateHeader = response.headers[@"Date"]; - if ([dateHeader isKindOfClass:[NSString class]]) { - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; - [formatter setLocale:usLocale]; - [formatter setDateFormat:@"EEE',' dd' 'MMM' 'yyyy HH':'mm':'ss zzz"]; - NSDate *date = [formatter dateFromString:dateHeader]; - if (date != nil) { - double difference = [date timeIntervalSince1970] - [[NSDate date] timeIntervalSince1970]; - [MTContext setFixedTimeDifference:(int32_t)difference]; - } - } - - NSData *data = response.data; - - NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; - if ([dict respondsToSelector:@selector(objectForKey:)]) { - NSArray *answer = dict[@"Answer"]; - NSMutableArray *strings = [[NSMutableArray alloc] init]; - if ([answer respondsToSelector:@selector(objectAtIndex:)]) { - for (NSDictionary *value in answer) { - if ([value respondsToSelector:@selector(objectForKey:)]) { - NSString *part = value[@"data"]; - if ([part respondsToSelector:@selector(characterAtIndex:)]) { - [strings addObject:part]; - } - } - } - [strings sortUsingComparator:^NSComparisonResult(NSString *lhs, NSString *rhs) { - if (lhs.length > rhs.length) { - return NSOrderedAscending; - } else { - return NSOrderedDescending; - } - }]; - - NSString *finalString = @""; - for (NSString *string in strings) { - finalString = [finalString stringByAppendingString:[string stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"="]]]; - } - - NSData *result = base64_decode(finalString); - NSMutableData *finalData = [[NSMutableData alloc] initWithData:result]; - [finalData setLength:256]; - MTBackupDatacenterData *datacenterData = MTIPDataDecode(encryptionProvider, finalData, phoneNumber); - if (datacenterData != nil && [self checkIpData:datacenterData timestamp:(int32_t)[currentContext globalTime] source:@"resolveCloudflare"]) { - return [MTSignal single:datacenterData]; - } - } - } - return [MTSignal complete]; - }] catch:^MTSignal *(__unused id error) { - return [MTSignal complete]; - }]; - if (signals.count != 0) { - signal = [signal delay:signals.count onQueue:[[MTQueue alloc] init]]; - } - [signals addObject:signal]; - } - - return [[MTSignal mergeSignals:signals] take:1]; -} - MTAtomic *sharedFetchConfigKeychains() { static MTAtomic *value = nil; static dispatch_once_t onceToken; @@ -377,7 +295,6 @@ MTAtomic *sharedFetchConfigKeychains() { + (MTSignal * _Nonnull)fetchBackupIps:(bool)isTestingEnvironment currentContext:(MTContext * _Nonnull)currentContext additionalSource:(MTSignal * _Nullable)additionalSource phoneNumber:(NSString * _Nullable)phoneNumber mainDatacenterId:(NSInteger)mainDatacenterId { NSMutableArray *signals = [[NSMutableArray alloc] init]; [signals addObject:[self fetchBackupIpsResolveGoogle:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]]; - [signals addObject:[self fetchBackupIpsResolveCloudflare:isTestingEnvironment phoneNumber:phoneNumber currentContext:currentContext addressOverride:currentContext.apiEnvironment.accessHostOverride]]; if (additionalSource != nil) { [signals addObject:[additionalSource mapToSignal:^MTSignal *(MTBackupDatacenterData *datacenterData) { if (![datacenterData isKindOfClass:[MTBackupDatacenterData class]]) { diff --git a/submodules/MtProtoKit/Sources/MTInputStream.m b/submodules/MtProtoKit/Sources/MTInputStream.m index 509427debe..9b470518c7 100644 --- a/submodules/MtProtoKit/Sources/MTInputStream.m +++ b/submodules/MtProtoKit/Sources/MTInputStream.m @@ -53,29 +53,6 @@ static inline int roundUpInput(int numToRound, int multiple) return _wrappedInputStream; } -- (int32_t)readInt32 -{ - int32_t value = 0; - - if ([_wrappedInputStream read:(uint8_t *)&value maxLength:4] != 4) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read int32"); - - @throw [[NSException alloc] initWithName:@"MTInputStreamException" reason:@"readInt32 end of stream" userInfo:@{}]; - } - } - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -# error "Big endian is not implemented" -#else -# error "Unknown byte order" -#endif - - return value; -} - - (int32_t)readInt32:(bool *)failed { int32_t value = 0; @@ -96,27 +73,6 @@ static inline int roundUpInput(int numToRound, int multiple) return value; } -- (int64_t)readInt64 -{ - int64_t value = 0; - - if ([_wrappedInputStream read:(uint8_t *)&value maxLength:8] != 8) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read int64"); - } - } - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -# error "Big endian is not implemented" -#else -# error "Unknown byte order" -#endif - - return value; -} - - (int64_t)readInt64:(bool *)failed { int64_t value = 0; @@ -137,27 +93,6 @@ static inline int roundUpInput(int numToRound, int multiple) return value; } -- (double)readDouble -{ - double value = 0.0; - - if ([_wrappedInputStream read:(uint8_t *)&value maxLength:8] != 8) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read double"); - } - } - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -# error "Big endian is not implemented" -#else -# error "Unknown byte order" -#endif - - return value; -} - - (double)readDouble:(bool *)failed { double value = 0.0; @@ -178,20 +113,6 @@ static inline int roundUpInput(int numToRound, int multiple) return value; } -- (NSData *)readData:(int)length -{ - uint8_t *bytes = (uint8_t *)malloc(length); - NSInteger readLen = [_wrappedInputStream read:bytes maxLength:length]; - if (readLen != length) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read %d bytes", length); - } - } - NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true]; - return data; -} - - (NSData *)readData:(int)length failed:(bool *)failed { uint8_t *bytes = (uint8_t *)malloc(length); @@ -206,20 +127,6 @@ static inline int roundUpInput(int numToRound, int multiple) return data; } -- (NSMutableData *)readMutableData:(NSUInteger)length -{ - uint8_t *bytes = (uint8_t *)malloc(length); - NSInteger readLen = [_wrappedInputStream read:bytes maxLength:length]; - if (readLen != length) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read %d bytes", length); - } - } - NSMutableData *data = [[NSMutableData alloc] initWithBytesNoCopy:bytes length:length freeWhenDone:true]; - return data; -} - - (NSMutableData *)readMutableData:(NSUInteger)length failed:(bool *)failed { uint8_t *bytes = (uint8_t *)malloc(length); @@ -234,60 +141,6 @@ static inline int roundUpInput(int numToRound, int multiple) return data; } -- (NSString *)readString -{ - uint8_t tmp = 0; - [_wrappedInputStream read:&tmp maxLength:1]; - - int paddingBytes = 0; - - int32_t length = tmp; - if (length == 254) - { - length = 0; - [_wrappedInputStream read:((uint8_t *)&length) + 1 maxLength:3]; - length >>= 8; - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#elif __BYTE_ORDER == __BIG_ENDIAN -# error "Big endian is not implemented" -#else -# error "Unknown byte order" -#endif - - paddingBytes = roundUpInput(length, 4) - length; - } - else - { - paddingBytes = roundUpInput(length + 1, 4) - (length + 1); - } - - NSString *string = nil; - - if (length > 0) - { - uint8_t *bytes = (uint8_t *)malloc(length); - NSInteger readLen = [_wrappedInputStream read:bytes maxLength:length]; - if (readLen != length) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read %d bytes", length); - } - } - - string = [[NSString alloc] initWithBytesNoCopy:bytes length:length encoding:NSUTF8StringEncoding freeWhenDone:true]; - } - else - { - string = @""; - } - - for (int i = 0; i < paddingBytes; i++) - [_wrappedInputStream read:&tmp maxLength:1]; - - return string; -} - - (NSString *)readString:(bool *)failed { uint8_t tmp = 0; @@ -342,44 +195,6 @@ static inline int roundUpInput(int numToRound, int multiple) return string; } -- (NSData *)readBytes -{ - uint8_t tmp = 0; - [_wrappedInputStream read:&tmp maxLength:1]; - - int paddingBytes = 0; - - int32_t length = tmp; - if (length == 254) - { - length = 0; - [_wrappedInputStream read:((uint8_t *)&length) + 1 maxLength:3]; - length >>= 8; - - paddingBytes = roundUpInput(length, 4) - length; - } - else - { - paddingBytes = roundUpInput(length + 1, 4) - (length + 1); - } - - uint8_t *bytes = (uint8_t *)malloc(length); - NSInteger readLen = [_wrappedInputStream read:bytes maxLength:length]; - if (readLen != length) - { - if (MTLogEnabled()) { - MTLog(@"***** Couldn't read %d bytes", length); - } - } - - NSData *result = [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:true]; - - for (int i = 0; i < paddingBytes; i++) - [_wrappedInputStream read:&tmp maxLength:1]; - - return result; -} - - (NSData *)readBytes:(bool *)failed { uint8_t tmp = 0; diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index efc50c77da..71ac74b747 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -1800,17 +1800,30 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; MTInputStream *is = [[MTInputStream alloc] initWithData:data]; - int64_t keyId = [is readInt64]; + bool readingError = false; + + int64_t keyId = [is readInt64:&readingError]; + if (readingError) { + return; + } if (keyId == authKey.authKeyId) { - NSData *messageKey = [is readData:16]; + + NSData *messageKey = [is readData:16 failed:&readingError]; + if (readingError) { + return; + } MTMessageEncryptionKey *encryptionKey = [MTMessageEncryptionKey messageEncryptionKeyV2ForAuthKey:effectiveAuthKey.authKey messageKey:messageKey toClient:true]; if (data.length <= 24) { return; } - NSMutableData *encryptedMessageData = [is readMutableData:(data.length - 24)]; + NSMutableData *encryptedMessageData = [is readMutableData:(data.length - 24) failed:&readingError]; + if (readingError) { + return; + } + while (encryptedMessageData.length % 16 != 0) { [encryptedMessageData setLength:encryptedMessageData.length - 1]; } @@ -1819,47 +1832,36 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; NSData *decryptedData = MTAesDecrypt(encryptedMessageData, encryptionKey.key, encryptionKey.iv); MTInputStream *messageIs = [[MTInputStream alloc] initWithData:decryptedData]; - [messageIs readInt64]; - [messageIs readInt64]; - - [messageIs readInt64]; - [messageIs readInt32]; - [messageIs readInt32]; bool stop = false; + if (!stop) { + [messageIs readInt64:&stop]; + } + if (!stop) { + [messageIs readInt64:&stop]; + } + if (!stop) { + [messageIs readInt64:&stop]; + } + if (!stop) { + [messageIs readInt32:&stop]; + } + if (!stop) { + [messageIs readInt32:&stop]; + } + int64_t reqMsgId = 0; - /*if (true) - {*/ - while (!stop && reqMsgId == 0) - { - int32_t signature = [messageIs readInt32:&stop]; + while (!stop && reqMsgId == 0) { + int32_t signature = [messageIs readInt32:&stop]; + if (!stop) { [self findReqMsgId:messageIs signature:signature reqMsgId:&reqMsgId failed:&stop]; } - /*} - else - { - int32_t signature = [messageIs readInt32]; - if (signature == (int)0xf35c6d01) - reqMsgId = [messageIs readInt64]; - else if (signature == (int)0x73f1f8dc) - { - int count = [messageIs readInt32]; - if (count != 0) - { - [messageIs readInt64]; - [messageIs readInt32]; - [messageIs readInt32]; - - signature = [messageIs readInt32]; - if (signature == (int)0xf35c6d01) - reqMsgId = [messageIs readInt64]; - } - } - }*/ + } - if (reqMsgId != 0) + if (reqMsgId != 0) { completion(token, @(reqMsgId)); + } } } }]; @@ -2306,38 +2308,48 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { if (_useUnauthorizedMode) { - int64_t authKeyId = [is readInt64]; + bool readingError = false; + int64_t authKeyId = [is readInt64:&readingError]; + if (readingError) { + if (parseError != NULL) { + *parseError = true; + } + return nil; + } if (authKeyId != 0) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } embeddedMessageId = [is readInt64:&readError]; if (readError) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } topMessageSize = [is readInt32:&readError]; if (readError || topMessageSize < 4) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } - if (dataMessageId != 0) + if (dataMessageId != 0) { *dataMessageId = embeddedMessageId; + } } else { embeddedSalt = [is readInt64:&readError]; - if (readError) - { + if (readError) { if (parseError != NULL) *parseError = true; return nil; @@ -2346,39 +2358,44 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { embeddedSessionId = [is readInt64:&readError]; if (readError) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } if (embeddedSessionId != _sessionInfo.sessionId) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } embeddedMessageId = [is readInt64:&readError]; if (readError) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } embeddedSeqNo = [is readInt32:&readError]; if (readError) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } [is readInt32:&readError]; if (readError) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } } @@ -2388,31 +2405,33 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { while (true) { NSInteger readBytes = [[is wrappedInputStream] read:buffer maxLength:128]; - if (readBytes <= 0) + if (readBytes <= 0) { break; + } [topMessageData appendBytes:buffer length:readBytes]; } id topObject = [self parseMessage:topMessageData]; if (topObject == nil) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } NSMutableArray *messages = [[NSMutableArray alloc] init]; NSTimeInterval timestamp = embeddedMessageId / 4294967296.0; - if ([topObject isKindOfClass:[MTMsgContainerMessage class]]) - { + if ([topObject isKindOfClass:[MTMsgContainerMessage class]]) { for (MTMessage *subMessage in ((MTMsgContainerMessage *)topObject).messages) { id subObject = [self parseMessage:subMessage.data]; if (subObject == nil) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } @@ -2421,15 +2440,14 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { int32_t subMessageLength = (int32_t)subMessage.data.length; [messages addObject:[[MTIncomingMessage alloc] initWithMessageId:subMessageId seqNo:subMessageSeqNo authKeyId:embeddedAuthKeyId sessionId:embeddedSessionId salt:embeddedSalt timestamp:timestamp size:subMessageLength body:subObject]]; } - } - else if ([topObject isKindOfClass:[MTMessage class]]) - { + } else if ([topObject isKindOfClass:[MTMessage class]]) { MTMessage *message = topObject; id subObject = [self parseMessage:message.data]; if (subObject == nil) { - if (parseError != NULL) + if (parseError != NULL) { *parseError = true; + } return nil; } @@ -2437,9 +2455,9 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { int32_t subMessageSeqNo = message.seqNo; int32_t subMessageLength = (int32_t)message.data.length; [messages addObject:[[MTIncomingMessage alloc] initWithMessageId:subMessageId seqNo:subMessageSeqNo authKeyId:embeddedAuthKeyId sessionId:embeddedSessionId salt:embeddedSalt timestamp:timestamp size:subMessageLength body:subObject]]; - } - else + } else { [messages addObject:[[MTIncomingMessage alloc] initWithMessageId:embeddedMessageId seqNo:embeddedSeqNo authKeyId:embeddedAuthKeyId sessionId:embeddedSessionId salt:embeddedSalt timestamp:timestamp size:topMessageSize body:topObject]]; + } return messages; } diff --git a/submodules/NotificationMuteSettingsUI/BUILD b/submodules/NotificationMuteSettingsUI/BUILD index bb062cc530..b2ac776926 100644 --- a/submodules/NotificationMuteSettingsUI/BUILD +++ b/submodules/NotificationMuteSettingsUI/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramStringFormatting:TelegramStringFormatting", diff --git a/submodules/NotificationSoundSelectionUI/BUILD b/submodules/NotificationSoundSelectionUI/BUILD index 0f9fe3632f..6be86d8138 100644 --- a/submodules/NotificationSoundSelectionUI/BUILD +++ b/submodules/NotificationSoundSelectionUI/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/ItemListUI:ItemListUI", diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index a744c229dd..656f3c7938 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -405,11 +405,11 @@ public func notificationSoundSelectionController(context: AccountContext, update let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let signal = combineLatest(presentationData, statePromise.get(), context.engine.peers.notificationSoundList()) |> map { presentationData, state, notificationSoundList -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { arguments.cancel() }) - let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { arguments.complete() }) @@ -476,7 +476,7 @@ public func notificationSoundSelectionController(context: AccountContext, update break } }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }) ], parseMarkdown: true), in: .window(.root)) } diff --git a/submodules/OpenInExternalAppUI/BUILD b/submodules/OpenInExternalAppUI/BUILD index ea1b5232ae..7d78a66292 100644 --- a/submodules/OpenInExternalAppUI/BUILD +++ b/submodules/OpenInExternalAppUI/BUILD @@ -11,14 +11,19 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/ComponentFlow:ComponentFlow", "//submodules/Display:Display", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/PhotoResources:PhotoResources", "//submodules/UrlEscaping:UrlEscaping", "//submodules/AppBundle:AppBundle", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/SheetComponent", + "//submodules/Components/ViewControllerComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift b/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift deleted file mode 100644 index f6771facb0..0000000000 --- a/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift +++ /dev/null @@ -1,264 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramCore -import MapKit -import TelegramPresentationData -import AccountContext -import PhotoResources -import AppBundle - -public struct OpenInControllerAction { - public let title: String - public let action: () -> Void - - public init(title: String, action: @escaping () -> Void) { - self.title = title - self.action = action - } -} - -public final class OpenInActionSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override public var ready: Promise { - return self._ready - } - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, item: OpenInItem, additionalAction: OpenInControllerAction? = nil, openUrl: @escaping (String) -> Void) { - var presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - if let forceTheme = forceTheme { - presentationData = presentationData.withUpdated(theme: forceTheme) - } - - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak self] presentationData in - if let strongSelf = self { - var presentationData = presentationData - if let forceTheme = forceTheme { - presentationData = presentationData.withUpdated(theme: forceTheme) - } - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - - self._ready.set(.single(true)) - - let invokeActionImpl: (OpenInAction) -> Void = { action in - switch action { - case let .openUrl(url): - openUrl(url) - case let .openLocation(latitude, longitude, directions): - let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(latitude, longitude), addressDictionary: [:]) - let mapItem = MKMapItem(placemark: placemark) - - if let directions = directions { - let options = [ MKLaunchOptionsDirectionsModeKey: directions.launchOptions ] - MKMapItem.openMaps(with: [MKMapItem.forCurrentLocation(), mapItem], launchOptions: options) - } else { - mapItem.openInMaps(launchOptions: nil) - } - default: - break - } - } - - var items: [ActionSheetItem] = [] - items.append(OpenInActionSheetItem(context: context, strings: strings, options: availableOpenInOptions(context: context, item: item), invokeAction: invokeActionImpl)) - - if let action = additionalAction { - items.append(ActionSheetButtonItem(title: action.title, action: { [weak self] in - action.action() - self?.dismissAnimated() - })) - } - - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }) - ]) - ]) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class OpenInActionSheetItem: ActionSheetItem { - let context: AccountContext - let strings: PresentationStrings - let options: [OpenInOption] - let invokeAction: (OpenInAction) -> Void - - init(context: AccountContext, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) { - self.context = context - self.strings = strings - self.options = options - self.invokeAction = invokeAction - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return OpenInActionSheetItemNode(context: self.context, theme: theme, strings: self.strings, options: self.options, invokeAction: self.invokeAction) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class OpenInActionSheetItemNode: ActionSheetItemNode { - let theme: ActionSheetControllerTheme - let strings: PresentationStrings - - let titleNode: ASTextNode - let scrollNode: ASScrollNode - - let openInNodes: [OpenInAppNode] - - init(context: AccountContext, theme: ActionSheetControllerTheme, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) { - self.theme = theme - self.strings = strings - - let titleFont = Font.medium(floor(theme.baseFontSize * 20.0 / 17.0)) - - self.titleNode = ASTextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = true - self.titleNode.attributedText = NSAttributedString(string: strings.Map_OpenIn, font: titleFont, textColor: theme.primaryTextColor, paragraphAlignment: .center) - - self.scrollNode = ASScrollNode() - self.scrollNode.view.showsVerticalScrollIndicator = false - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.clipsToBounds = false - self.scrollNode.view.scrollsToTop = false - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.scrollableDirections = [.left, .right] - - self.openInNodes = options.map { option in - let node = OpenInAppNode() - node.setup(context: context, theme: theme, option: option, invokeAction: invokeAction) - return node - } - - super.init(theme: theme) - - self.addSubnode(self.titleNode) - - if !self.openInNodes.isEmpty { - for openInNode in openInNodes { - self.scrollNode.addSubnode(openInNode) - } - self.addSubnode(self.scrollNode) - } - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 148.0) - - let titleSize = self.titleNode.measure(size) - self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 16.0), size: CGSize(width: size.width, height: titleSize.height)) - - self.scrollNode.frame = CGRect(origin: CGPoint(x: 0, y: 36.0), size: CGSize(width: size.width, height: size.height - 36.0)) - - let nodeInset: CGFloat = 2.0 - let nodeSize = CGSize(width: 80.0, height: 112.0) - var nodeOffset = nodeInset - - for node in self.openInNodes { - node.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize) - nodeOffset += nodeSize.width - } - - if let lastNode = self.openInNodes.last { - let contentSize = CGSize(width: lastNode.frame.maxX + nodeInset, height: self.scrollNode.frame.height) - if self.scrollNode.view.contentSize != contentSize { - self.scrollNode.view.contentSize = contentSize - } - } - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } -} - -private final class OpenInAppNode : ASDisplayNode { - private let iconNode: TransformImageNode - private let textNode: ASTextNode - private var action: (() -> Void)? - - override init() { - self.iconNode = TransformImageNode() - self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0)) - self.iconNode.isLayerBacked = true - - self.textNode = ASTextNode() - self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = true - - super.init() - - self.addSubnode(self.iconNode) - self.addSubnode(self.textNode) - } - - func setup(context: AccountContext, theme: ActionSheetControllerTheme, option: OpenInOption, invokeAction: @escaping (OpenInAction) -> Void) { - let textFont = Font.regular(floor(theme.baseFontSize * 11.0 / 17.0)) - self.textNode.attributedText = NSAttributedString(string: option.title, font: textFont, textColor: theme.primaryTextColor, paragraphAlignment: .center) - - let iconSize = CGSize(width: 60.0, height: 60.0) - let makeLayout = self.iconNode.asyncLayout() - let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets())) - applyLayout() - - switch option.application { - case .safari: - if let image = UIImage(bundleImageName: "Open In/Safari") { - self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image))) - } - case .maps: - if let image = UIImage(bundleImageName: "Open In/Maps") { - self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image))) - } - case let .other(_, identifier, _, store): - self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .resource(resource: OpenInAppIconResource(appStoreId: identifier, store: store)))) - } - - self.action = { - invokeAction(option.action()) - } - } - - override func didLoad() { - super.didLoad() - - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) - } - - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.action?() - } - } - - override func layout() { - super.layout() - - let bounds = self.bounds - - self.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 60.0, height: 60.0)) - self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 14.0 + 60.0 + 4.0), size: CGSize(width: bounds.size.width, height: 16.0)) - } -} diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift index 9af464ac9d..1fc779008e 100644 --- a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift +++ b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift @@ -3,6 +3,7 @@ import UIKit import TelegramCore import SwiftSignalKit import Display +import TelegramPresentationData public struct OpenInAppIconResourceId { public let appStoreId: Int64 @@ -131,7 +132,7 @@ private func drawOpenInAppIconBorder(into c: CGContext, arguments: TransformImag c.strokePath() } -public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon, withChrome: Bool = true) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { switch appIcon { case let .resource(resource): return openInAppIconData(engine: engine, appIcon: resource) |> map { data in @@ -166,14 +167,17 @@ public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Sig guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { return nil } - + context.withFlippedContext { c in c.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: arguments.drawingSize)) - drawOpenInAppIconBorder(into: c, arguments: arguments) + if withChrome { + drawOpenInAppIconBorder(into: c, arguments: arguments) + } } - addCorners(context, arguments: arguments) - + if withChrome { + addCorners(context, arguments: arguments) + } return context }) } diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift index 7721f5bdb1..dc427f26f2 100644 --- a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift +++ b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift @@ -90,24 +90,20 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope var options: [OpenInOption] = [] switch item { case let .url(url): - var skipSafari = false if url.contains("youtube.com/") || url.contains("youtu.be/") { let updatedUrl = url.replacingOccurrences(of: "https://", with: "youtube://").replacingOccurrences(of: "http://", with: "youtube://") options.append(OpenInOption(identifier: "youtube", application: .other(title: "YouTube", identifier: 544007664, scheme: "youtube", store: nil), action: { return .openUrl(url: updatedUrl) })) - skipSafari = true } - if !skipSafari { - options.append(OpenInOption(identifier: "safari", application: .safari, action: { - var url = url - if url.hasPrefix("https://") { - url = url.replacingOccurrences(of: "https://", with: "x-safari-https://") - } - return .openUrl(url: url) - })) - } + options.append(OpenInOption(identifier: "safari", application: .safari, action: { + var url = url + if url.hasPrefix("https://") { + url = url.replacingOccurrences(of: "https://", with: "x-safari-https://") + } + return .openUrl(url: url) + })) options.append(OpenInOption(identifier: "chrome", application: .other(title: "Chrome", identifier: 535886823, scheme: "googlechrome", store: nil), action: { if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) { @@ -202,6 +198,10 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope options.append(OpenInOption(identifier: "alook", application: .other(title: "Alook Browser", identifier: 1261944766, scheme: "alook", store: nil), action: { return .openUrl(url: "alook://\(url)") })) + + options.append(OpenInOption(identifier: "vivaldi", application: .other(title: "Vivaldi", identifier: 1633234600, scheme: "vivaldi", store: "us"), action: { + return .openUrl(url: "vivaldi://\(url)") + })) case let .location(location, directions): let lat = location.latitude let lon = location.longitude @@ -334,6 +334,14 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope return .openUrl(url: url) } })) + + options.append(OpenInOption(identifier: "yandexGo", application: .other(title: "Yandex Go", identifier: 472650686, scheme: "yandextaxi", store: nil), action: { + return .openUrl(url: "yandextaxi://route?end-lat=\(lat)&end-lon=\(lon)") + })) + + options.append(OpenInOption(identifier: "yango", application: .other(title: "Yango", identifier: 1437157286, scheme: "yangoride", store: nil), action: { + return .openUrl(url: "yangoride://route?end-lat=\(lat)&end-lon=\(lon)") + })) } return options } diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift b/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift new file mode 100644 index 0000000000..d740cba975 --- /dev/null +++ b/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift @@ -0,0 +1,544 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import MapKit +import TelegramPresentationData +import AccountContext +import AppBundle +import ViewControllerComponent +import SheetComponent +import ButtonComponent +import GlassBarButtonComponent +import BundleIconComponent +import MultilineTextComponent + +public struct OpenInControllerAction { + public let title: String + public let action: () -> Void + + public init(title: String, action: @escaping () -> Void) { + self.title = title + self.action = action + } +} + +public final class OpenInOptionsScreen: ViewControllerComponentContainer { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + forceTheme: PresentationTheme? = nil, + item: OpenInItem, + additionalAction: OpenInControllerAction? = nil, + openUrl: @escaping (String) -> Void + ) { + let invokeAction: (OpenInAction) -> Void = { action in + switch action { + case let .openUrl(url): + openUrl(url) + case let .openLocation(latitude, longitude, directions): + let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(latitude, longitude), addressDictionary: [:]) + let mapItem = MKMapItem(placemark: placemark) + + if let directions = directions { + let options = [MKLaunchOptionsDirectionsModeKey: directions.launchOptions] + MKMapItem.openMaps(with: [MKMapItem.forCurrentLocation(), mapItem], launchOptions: options) + } else { + mapItem.openInMaps(launchOptions: nil) + } + default: + break + } + } + + let effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal)? + if let forceTheme { + let initial = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + let signal = updatedPresentationData?.signal ?? context.sharedContext.presentationData + effectiveUpdatedPresentationData = ( + initial: initial.withUpdated(theme: forceTheme), + signal: signal |> map { presentationData in + presentationData.withUpdated(theme: forceTheme) + } + ) + } else { + effectiveUpdatedPresentationData = updatedPresentationData + } + + super.init( + context: context, + component: OpenInOptionsScreenComponent( + context: context, + options: availableOpenInOptions(context: context, item: item), + additionalAction: additionalAction, + invokeAction: invokeAction + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + updatedPresentationData: effectiveUpdatedPresentationData + ) + + self.blocksBackgroundWhenInOverlay = true + self.navigationPresentation = .flatModal + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } + } +} + +private final class OpenInOptionsScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let options: [OpenInOption] + let additionalAction: OpenInControllerAction? + let invokeAction: (OpenInAction) -> Void + + init( + context: AccountContext, + options: [OpenInOption], + additionalAction: OpenInControllerAction?, + invokeAction: @escaping (OpenInAction) -> Void + ) { + self.context = context + self.options = options + self.additionalAction = additionalAction + self.invokeAction = invokeAction + } + + static func ==(lhs: OpenInOptionsScreenComponent, rhs: OpenInOptionsScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.options.map(\.identifier) != rhs.options.map(\.identifier) { + return false + } + if lhs.additionalAction?.title != rhs.additionalAction?.title { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func dismiss(animated: Bool) { + guard let controller = self.environment?.controller() else { + return + } + + if animated { + self.sheetAnimateOut.invoke(Action { _ in + controller.dismiss(completion: nil) + }) + } else { + controller.dismiss(animated: false, completion: nil) + } + } + + func update(component: OpenInOptionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] animated in + self?.dismiss(animated: animated) + } + ) + + let _ = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(OpenInOptionsSheetContentComponent( + context: component.context, + options: component.options, + additionalAction: component.additionalAction, + invokeAction: component.invokeAction, + dismiss: { [weak self] in + self?.dismiss(animated: true) + } + )), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + animateOut: self.sheetAnimateOut + )), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class OpenInOptionsSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let options: [OpenInOption] + let additionalAction: OpenInControllerAction? + let invokeAction: (OpenInAction) -> Void + let dismiss: () -> Void + + init( + context: AccountContext, + options: [OpenInOption], + additionalAction: OpenInControllerAction?, + invokeAction: @escaping (OpenInAction) -> Void, + dismiss: @escaping () -> Void + ) { + self.context = context + self.options = options + self.additionalAction = additionalAction + self.invokeAction = invokeAction + self.dismiss = dismiss + } + + static func ==(lhs: OpenInOptionsSheetContentComponent, rhs: OpenInOptionsSheetContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.options.map(\.identifier) != rhs.options.map(\.identifier) { + return false + } + if lhs.additionalAction?.title != rhs.additionalAction?.title { + return false + } + return true + } + + final class View: UIView { + private let closeButton = ComponentView() + private let title = ComponentView() + private let scrollView: UIScrollView + private var optionViews: [String: OpenInAppView] = [:] + private var shareButton: ComponentView? + + override init(frame: CGRect) { + self.scrollView = UIScrollView() + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.clipsToBounds = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.alwaysBounceHorizontal = true + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + + super.init(frame: frame) + + self.addSubview(self.scrollView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: OpenInOptionsSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let theme = environment.theme + let strings = environment.strings + + let closeButtonSize = self.closeButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + component.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.addSubview(closeButtonView) + } + transition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize)) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.Map_OpenIn, + font: Font.semibold(17.0), + textColor: theme.actionSheet.primaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + )), + environment: {}, + containerSize: CGSize(width: max(1.0, availableSize.width - 32.0 - 60.0), height: CGFloat.greatestFiniteMagnitude) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect( + origin: CGPoint( + x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), + y: floorToScreenPixels(38.0 - titleSize.height / 2.0) + ), + size: titleSize + )) + } + + let optionInset: CGFloat = 2.0 + var optionSpacing: CGFloat = 8.0 + if component.options.count == 3 { + optionSpacing = 32.0 + } else if component.options.count == 2 { + optionSpacing = 64.0 + } + + let optionSize = CGSize(width: 80.0, height: 112.0) + let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 82.0), size: CGSize(width: availableSize.width, height: optionSize.height)) + transition.setFrame(view: self.scrollView, frame: scrollFrame) + + var validIds = Set() + for (index, option) in component.options.enumerated() { + validIds.insert(option.identifier) + + let optionView: OpenInAppView + if let current = self.optionViews[option.identifier] { + optionView = current + } else { + optionView = OpenInAppView() + self.optionViews[option.identifier] = optionView + self.scrollView.addSubview(optionView) + } + + optionView.update(context: component.context, theme: theme, option: option, action: { + component.invokeAction(option.action()) + component.dismiss() + }) + + let optionOriginX: CGFloat + if component.options.count < 5 { + optionOriginX = floorToScreenPixels(max(0.0, (availableSize.width - optionSize.width * CGFloat(component.options.count) - optionSpacing * CGFloat(component.options.count - 1)) / 2.0)) + CGFloat(index) * (optionSize.width + optionSpacing) + } else { + optionOriginX = optionInset + CGFloat(index) * (optionSize.width + optionSpacing) + } + + transition.setFrame(view: optionView, frame: CGRect( + origin: CGPoint(x: optionOriginX, y: 0.0), + size: optionSize + )) + } + + for id in Array(self.optionViews.keys) { + if !validIds.contains(id) { + let optionView = self.optionViews.removeValue(forKey: id) + optionView?.removeFromSuperview() + } + } + + let optionsContentWidth: CGFloat + if component.options.isEmpty { + optionsContentWidth = availableSize.width + } else { + optionsContentWidth = optionInset * 2.0 + CGFloat(component.options.count) * (optionSize.width + optionSpacing) - optionSpacing + } + self.scrollView.contentSize = CGSize(width: max(availableSize.width, optionsContentWidth), height: optionSize.height) + + var contentHeight = scrollFrame.maxY + 22.0 + if let additionalAction = component.additionalAction { + let shareButton: ComponentView + if let current = self.shareButton { + shareButton = current + } else { + shareButton = ComponentView() + self.shareButton = shareButton + } + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let buttonSize = shareButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(additionalAction.title), + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: additionalAction.title, + font: Font.semibold(17.0), + textColor: theme.list.itemCheckColors.foregroundColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + )) + ), + action: { + additionalAction.action() + component.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) + ) + + if let shareButtonView = shareButton.view { + if shareButtonView.superview == nil { + self.addSubview(shareButtonView) + } + transition.setFrame(view: shareButtonView, frame: CGRect( + origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: contentHeight), + size: buttonSize + )) + } + contentHeight += buttonSize.height + contentHeight += buttonInsets.bottom + } else { + if let shareButton = self.shareButton { + self.shareButton = nil + shareButton.view?.removeFromSuperview() + } + contentHeight += max(20.0, environment.safeInsets.bottom + 12.0) + } + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class OpenInAppView: UIControl { + private let iconView: TransformImageView + private let titleLabel: UILabel + + private var currentIdentifier: String? + private var action: (() -> Void)? + + override init(frame: CGRect) { + self.iconView = TransformImageView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 64.0, height: 64.0))) + self.titleLabel = UILabel() + + super.init(frame: frame) + + self.titleLabel.textAlignment = .center + self.titleLabel.numberOfLines = 1 + self.titleLabel.adjustsFontSizeToFitWidth = true + self.titleLabel.minimumScaleFactor = 0.75 + + self.isAccessibilityElement = true + + self.addSubview(self.iconView) + self.addSubview(self.titleLabel) + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(context: AccountContext, theme: PresentationTheme, option: OpenInOption, action: @escaping () -> Void) { + self.action = action + self.accessibilityLabel = option.title + self.titleLabel.attributedText = NSAttributedString(string: option.title, font: Font.medium(12.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center) + + let iconSize = CGSize(width: 64.0, height: 64.0) + let makeLayout = self.iconView.asyncLayout() + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets())) + applyLayout() + + if self.currentIdentifier != option.identifier { + self.currentIdentifier = option.identifier + + switch option.application { + case .safari: + if let image = UIImage(bundleImageName: "Open In/Safari") { + self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image))) + } + case .maps: + if let image = UIImage(bundleImageName: "Open In/Maps") { + self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image))) + } + case let .other(_, identifier, _, store): + self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .resource(resource: OpenInAppIconResource(appStoreId: identifier, store: store)))) + } + } + } + + @objc private func pressed() { + self.action?() + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.iconView.frame = CGRect(origin: CGPoint(x: 8.0, y: 12.0), size: CGSize(width: 64.0, height: 64.0)) + self.titleLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 80.0), size: CGSize(width: self.bounds.width, height: 16.0)) + } +} diff --git a/submodules/PasscodeUI/BUILD b/submodules/PasscodeUI/BUILD index 4230fc328a..4acaed6dd7 100644 --- a/submodules/PasscodeUI/BUILD +++ b/submodules/PasscodeUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramUIPreferences:TelegramUIPreferences", diff --git a/submodules/PassportUI/BUILD b/submodules/PassportUI/BUILD index ab6d2f27cd..28b67dd27e 100644 --- a/submodules/PassportUI/BUILD +++ b/submodules/PassportUI/BUILD @@ -24,12 +24,12 @@ swift_library( "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/LegacyUI:LegacyUI", "//submodules/ImageCompression:ImageCompression", - "//submodules/DateSelectionUI:DateSelectionUI", "//submodules/PasswordSetupUI:PasswordSetupUI", "//submodules/AppBundle:AppBundle", "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/Markdown:Markdown", "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/TelegramUI/Components/ChatTimerScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/PassportUI/Sources/LegacySecureIdAttachmentMenu.swift b/submodules/PassportUI/Sources/LegacySecureIdAttachmentMenu.swift index a3ef9dc4fc..2264149d6d 100644 --- a/submodules/PassportUI/Sources/LegacySecureIdAttachmentMenu.swift +++ b/submodules/PassportUI/Sources/LegacySecureIdAttachmentMenu.swift @@ -76,7 +76,7 @@ func presentLegacySecureIdAttachmentMenu(context: AccountContext, present: @esca |> runOn(.mainQueue()) |> delay(0.1, queue: .mainQueue())).start() - let _ = (processedLegacySecureIdAttachmentItems(postbox: context.account.postbox, signal: signal) + let _ = (processedLegacySecureIdAttachmentItems(signal: signal) |> mapToSignal { resources -> Signal<([TelegramMediaResource], SecureIdRecognizedDocumentData?), NoError> in switch type { case .generic, .idCard: @@ -116,7 +116,7 @@ private enum AttachmentItem { case iCloud(URL) } -private func processedLegacySecureIdAttachmentItems(postbox: Postbox, signal: SSignal) -> Signal<[TelegramMediaResource], NoError> { +private func processedLegacySecureIdAttachmentItems(signal: SSignal) -> Signal<[TelegramMediaResource], NoError> { let nativeSignal = Signal { subscriber in let disposable = signal.start(next: { next in if let dict = next as? [String: Any], let image = dict["image"] as? UIImage { diff --git a/submodules/PassportUI/Sources/SecureIdAuthAcceptNode.swift b/submodules/PassportUI/Sources/SecureIdAuthAcceptNode.swift index 6bf9688bfb..b8714b2e63 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthAcceptNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthAcceptNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/PassportUI/Sources/SecureIdAuthController.swift b/submodules/PassportUI/Sources/SecureIdAuthController.swift index 6acb72a6af..ce312d33d6 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthController.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TextFormat @@ -19,7 +18,7 @@ public enum SecureIdRequestResult: String { case error = "error" } -public func secureIdCallbackUrl(with baseUrl: String, peerId: PeerId, result: SecureIdRequestResult, parameters: [String : String]) -> String { +public func secureIdCallbackUrl(with baseUrl: String, peerId: EnginePeer.Id, result: SecureIdRequestResult, parameters: [String : String]) -> String { var query = (parameters.compactMap({ (key, value) -> String in return "\(key)=\(value)" }) as Array).joined(separator: "&") @@ -64,7 +63,7 @@ final class SecureIdAuthControllerInteraction { } public enum SecureIdAuthControllerMode { - case form(peerId: PeerId, scope: String, publicKey: String, callbackUrl: String?, opaquePayload: Data, opaqueNonce: Data) + case form(peerId: EnginePeer.Id, scope: String, publicKey: String, callbackUrl: String?, opaquePayload: Data, opaqueNonce: Data) case list } @@ -174,7 +173,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable } })) - let handleError: (Any, String?, PeerId?) -> Void = { [weak self] error, callbackUrl, peerId in + let handleError: (Any, String?, EnginePeer.Id?) -> Void = { [weak self] error, callbackUrl, peerId in if let strongSelf = self { var passError: String? var appUpdateRequired = false @@ -227,7 +226,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable } let primaryLanguageByCountry = configuration.nativeLanguageByCountry - return .single(SecureIdEncryptedFormData(form: form, primaryLanguageByCountry: primaryLanguageByCountry, accountPeer: accountPeer._asPeer(), servicePeer: servicePeer._asPeer())) + return .single(SecureIdEncryptedFormData(form: form, primaryLanguageByCountry: primaryLanguageByCountry, accountPeer: accountPeer, servicePeer: servicePeer)) } } |> deliverOnMainQueue).start(next: { [weak self] formData in @@ -268,7 +267,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable case .form: break case var .list(list): - list.accountPeer = accountPeer._asPeer() + list.accountPeer = accountPeer list.primaryLanguageByCountry = primaryLanguageByCountry list.encryptedValues = values return .list(list) @@ -328,7 +327,14 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable guard let strongSelf = self else { return } - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(mention.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: mention.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in guard let strongSelf = self else { return diff --git a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift index a4e0ff2753..ee691a9d42 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift @@ -51,6 +51,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { self.addSubnode(self.activityIndicator) self.scrollNode.view.alwaysBounceVertical = true + self.scrollNode.view.scrollsToTop = false self.addSubnode(self.scrollNode) self.backgroundColor = presentationData.theme.list.blocksBackgroundColor @@ -303,7 +304,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { current.updateValues(formData.values) contentNode = current } else { - let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peer: EnginePeer(encryptedFormData.servicePeer), privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, openField: { [weak self] field in + let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peer: encryptedFormData.servicePeer, privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, openField: { [weak self] field in if let strongSelf = self { switch field { case .identity, .address: @@ -684,7 +685,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { var currentValue: SecureIdValueWithContext? switch type { case .phone: - if let peer = form.encryptedFormData?.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty { + if case let .user(peer)? = form.encryptedFormData?.accountPeer, let phone = peer.phone, !phone.isEmpty { immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: phone)) } currentValue = findValue(formData.values, key: .phone)?.1 @@ -936,7 +937,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { deleteField(.phone) } else { var immediatelyAvailableValue: SecureIdValue? - if let peer = list.accountPeer as? TelegramUser, let phone = peer.phone, !phone.isEmpty { + if case let .user(peer)? = list.accountPeer, let phone = peer.phone, !phone.isEmpty { immediatelyAvailableValue = .phone(SecureIdPhoneValue(phone: phone)) } self.interaction.push(SecureIdPlaintextFormController(context: self.context, secureIdContext: secureIdContext, type: .phone, immediatelyAvailableValue: immediatelyAvailableValue, updatedValue: { value in diff --git a/submodules/PassportUI/Sources/SecureIdAuthControllerState.swift b/submodules/PassportUI/Sources/SecureIdAuthControllerState.swift index 632a2de9bb..f2cc65c53f 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthControllerState.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthControllerState.swift @@ -1,13 +1,12 @@ import Foundation import UIKit -import Postbox import TelegramCore struct SecureIdEncryptedFormData { let form: EncryptedSecureIdForm let primaryLanguageByCountry: [String: String] - let accountPeer: Peer - let servicePeer: Peer + let accountPeer: EnginePeer + let servicePeer: EnginePeer } enum SecureIdAuthPasswordChallengeState { @@ -64,7 +63,7 @@ struct SecureIdAuthControllerFormState: Equatable { } struct SecureIdAuthControllerListState: Equatable { - var accountPeer: Peer? + var accountPeer: EnginePeer? var twoStepEmail: String? var verificationState: SecureIdAuthControllerVerificationState? var encryptedValues: EncryptedAllSecureIdValues? @@ -73,7 +72,7 @@ struct SecureIdAuthControllerListState: Equatable { var removingValues: Bool = false static func ==(lhs: SecureIdAuthControllerListState, rhs: SecureIdAuthControllerListState) -> Bool { - if !arePeersEqual(lhs.accountPeer, rhs.accountPeer) { + if lhs.accountPeer != rhs.accountPeer { return false } if let lhsTwoStepEmail = lhs.twoStepEmail, let rhsTwoStepEmail = rhs.twoStepEmail, lhsTwoStepEmail != rhsTwoStepEmail { diff --git a/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift b/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift index 732cfa73af..5744bbaedf 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift @@ -52,8 +52,8 @@ final class SecureIdAuthHeaderNode: ASDisplayNode { func updateState(formData: SecureIdEncryptedFormData?, verificationState: SecureIdAuthControllerVerificationState) { if let formData = formData { - self.serviceAvatarNode.setPeer(context: self.context, theme: self.theme, peer: EnginePeer(formData.servicePeer)) - let titleData = self.strings.Passport_RequestHeader(EnginePeer(formData.servicePeer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)) + self.serviceAvatarNode.setPeer(context: self.context, theme: self.theme, peer: formData.servicePeer) + let titleData = self.strings.Passport_RequestHeader(formData.servicePeer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)) let titleString = NSMutableAttributedString() titleString.append(NSAttributedString(string: titleData.string, font: textFont, textColor: self.theme.list.freeTextColor)) diff --git a/submodules/PassportUI/Sources/SecureIdAuthListContentNode.swift b/submodules/PassportUI/Sources/SecureIdAuthListContentNode.swift index 5dc349a96d..4c087691bb 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthListContentNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthListContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData diff --git a/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift b/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift index 1cd73e5d88..8444f283e4 100644 --- a/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift +++ b/submodules/PassportUI/Sources/SecureIdDocumentFormController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ProgressNavigationButtonNode diff --git a/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift b/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift index e3f85c953c..de0a6c4d05 100644 --- a/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift +++ b/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift @@ -3,15 +3,14 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramStringFormatting import AccountContext import GalleryUI import CountrySelectionUI -import DateSelectionUI import AppBundle +import ChatTimerScreen private enum SecureIdDocumentFormTextField { case identifier @@ -2169,7 +2168,7 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode Void)? - self.uploadContext = SecureIdVerificationDocumentsContext(postbox: self.context.account.postbox, network: self.context.account.network, context: self.secureIdContext, update: { id, state in + self.uploadContext = SecureIdVerificationDocumentsContext(engine: self.context.engine, context: self.secureIdContext, update: { id, state in updateImpl?(id, state) }) @@ -2369,49 +2368,76 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode Void private var contexts: [Int64: DocumentContext] = [:] private(set) var uploadedFiles: [Data: Data] = [:] - init(postbox: Postbox, network: Network, context: SecureIdAccessContext, update: @escaping (Int64, SecureIdVerificationLocalDocumentState) -> Void) { - self.postbox = postbox - self.network = network + init(engine: TelegramEngine, context: SecureIdAccessContext, update: @escaping (Int64, SecureIdVerificationLocalDocumentState) -> Void) { + self.engine = engine self.context = context self.update = update } @@ -40,7 +37,7 @@ final class SecureIdVerificationDocumentsContext { if self.contexts[info.id] == nil { let disposable = MetaDisposable() self.contexts[info.id] = DocumentContext(disposable: disposable) - disposable.set((uploadSecureIdFile(context: self.context, postbox: self.postbox, network: self.network, resource: info.resource) + disposable.set((uploadSecureIdFile(context: self.context, engine: self.engine, resource: EngineMediaResource(info.resource)) |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { switch result { diff --git a/submodules/PasswordSetupUI/BUILD b/submodules/PasswordSetupUI/BUILD index 00943960e7..a57d6a5239 100644 --- a/submodules/PasswordSetupUI/BUILD +++ b/submodules/PasswordSetupUI/BUILD @@ -27,7 +27,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift index d5d78af6e1..2376250682 100644 --- a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift +++ b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift @@ -160,14 +160,14 @@ public func resetPasswordController(context: AccountContext, emailPattern: Strin |> deliverOnMainQueue |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) var rightNavigationButton: ItemListNavigationButton? if state.checking { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.code.isEmpty, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: !state.code.isEmpty, action: { var state: ResetPasswordControllerState? updateState { s in state = s diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift index 56f4938f09..01405a99a2 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift @@ -39,7 +39,7 @@ public class SetupTwoStepVerificationController: ViewController { self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentForegroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentDisabledButtonColor: self.presentationData.theme.chat.inputPanel.panelControlDisabledColor, accentForegroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift index 692d1e07cf..aa64693954 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift @@ -697,7 +697,7 @@ final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { }, transition: .animated(duration: 0.5, curve: .spring)) } if case let .enterEmail(enterEmailState, enterEmailEmail)? = self.innerState.data.state, case .create = enterEmailState, enterEmailEmail.isEmpty { - self.present(textAlertController(sharedContext: self.context.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: { + self.present(textAlertController(sharedContext: self.context.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: { continueImpl() })]), nil) } else { diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index d42a2103b9..aea80e2cf4 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -71,7 +71,7 @@ public final class TwoFactorDataInputScreen: ViewController { self.presentationData = self.sharedContext.currentPresentationData.with { $0 } let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) - let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor) + let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentDisabledButtonColor: defaultTheme.accentDisabledButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) @@ -534,7 +534,7 @@ public final class TwoFactorDataInputScreen: ViewController { strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: alertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }) }), - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) ]), in: .window(.root)) case let .passwordHint(recovery, password, doneText): if let recovery = recovery { @@ -550,7 +550,7 @@ public final class TwoFactorDataInputScreen: ViewController { } strongSelf.performRecovery(recovery: recovery, password: "", hint: "") }), - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) ]), in: .window(.root)) case let .rememberPassword(doneText): guard case let .authorized(engine) = strongSelf.engine else { @@ -1819,6 +1819,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS self.scrollNode.view.contentInsetAdjustmentBehavior = .never } self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false } func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift index f94f1ee2cf..670043340b 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift @@ -57,7 +57,7 @@ public final class TwoFactorAuthSplashScreen: ViewController { self.presentationData = self.sharedContext.currentPresentationData.with { $0 } let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) - let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor) + let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentDisabledButtonColor: defaultTheme.accentDisabledButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) diff --git a/submodules/PaymentMethodUI/BUILD b/submodules/PaymentMethodUI/BUILD index 50a49ff478..171123463a 100644 --- a/submodules/PaymentMethodUI/BUILD +++ b/submodules/PaymentMethodUI/BUILD @@ -29,7 +29,6 @@ swift_library( "//submodules/InviteLinksUI:InviteLinksUI", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramUIPreferences:TelegramUIPreferences", "//submodules/ItemListUI:ItemListUI", diff --git a/submodules/PeerAvatarGalleryUI/BUILD b/submodules/PeerAvatarGalleryUI/BUILD index 9e7b1b5a5f..8a9a4c8983 100644 --- a/submodules/PeerAvatarGalleryUI/BUILD +++ b/submodules/PeerAvatarGalleryUI/BUILD @@ -12,8 +12,9 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/ComponentFlow", + "//submodules/ContextUI:ContextUI", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", @@ -23,6 +24,7 @@ swift_library( "//submodules/PhotoResources:PhotoResources", "//submodules/RadialStatusNode:RadialStatusNode", "//submodules/ShareController:ShareController", + "//submodules/TelegramUI/Components/GlassControls", "//submodules/AppBundle:AppBundle", "//submodules/LegacyComponents:LegacyComponents", "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 53f8b99fb7..49f8e79494 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -8,11 +8,13 @@ import TelegramCore import TelegramPresentationData import AccountContext import GalleryUI +import ContextUI import LegacyComponents import LegacyMediaPickerUI import SaveToCameraRoll import OverlayStatusController import PresentationDataUtils +import AppBundle public enum AvatarGalleryEntryId: Hashable { case topImage @@ -20,6 +22,42 @@ public enum AvatarGalleryEntryId: Hashable { case resource(String) } +private final class AvatarGalleryContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + private let actionsOnTop: Bool + + init(sourceView: UIView, actionsOnTop: Bool = false) { + self.sourceView = sourceView + self.actionsOnTop = actionsOnTop + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.actionsOnTop ? .top : .bottom) + } +} + +private func avatarGalleryEntryMatchesImage(_ entry: AvatarGalleryEntry, _ image: TelegramMediaImage) -> Bool { + guard + let entryRepresentation = largestImageRepresentation(entry.representations.map({ $0.representation })), + let imageRepresentation = largestImageRepresentation(image.representations) + else { + return false + } + guard let entryResource = entryRepresentation.resource as? CloudPeerPhotoSizeMediaResource, let imageResource = imageRepresentation.resource as? CloudPhotoSizeMediaResource else { + return false + } + return entryResource.photoId == imageResource.photoId +} + +private func avatarGalleryEntryWithVideoRepresentations(_ entry: AvatarGalleryEntry, videoRepresentations: [VideoRepresentationWithReference], immediateThumbnailData: Data?) -> AvatarGalleryEntry { + switch entry { + case let .topImage(representations, _, peer, indexData, _, category): + return .topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category) + default: + return entry + } +} + public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id) -> Signal { return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> mapToSignal { peer -> Signal<[AvatarGalleryEntry]?, NoError> in @@ -48,6 +86,12 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id } if case let .known(photo) = cachedData.fallbackPhoto { lastEntry = photo + if let photo, firstEntry.videoRepresentations.isEmpty, !photo.videoRepresentations.isEmpty, avatarGalleryEntryMatchesImage(firstEntry, photo), let peerReference = PeerReference(peer) { + let videoRepresentations = photo.videoRepresentations.map { + VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) + } + firstEntry = avatarGalleryEntryWithVideoRepresentations(firstEntry, videoRepresentations: videoRepresentations, immediateThumbnailData: firstEntry.immediateThumbnailData ?? photo.immediateThumbnailData) + } } } return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: EnginePeer(peer), firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry) @@ -205,11 +249,11 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine, peer: EnginePeer) -> Signal<[AvatarGalleryEntry]?, NoError> { var initialEntries: [AvatarGalleryEntry] = [] - if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer._asPeer()) { + if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) { initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], peer, nil, nil, nil)) } - guard let peerReference = PeerReference(peer._asPeer()) else { + guard let peerReference = PeerReference(peer) else { return .single(initialEntries) } switch peer { @@ -255,7 +299,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account var result: [AvatarGalleryEntry] = [] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer._asPeer()) { + } else if let peerReference = PeerReference(peer) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { var initialMediaIds = Set() @@ -311,7 +355,13 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account let initialEntries = [firstEntry] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer._asPeer()) { + if let lastEntry, let firstEntry = result.first, firstEntry.videoRepresentations.isEmpty, !lastEntry.videoRepresentations.isEmpty, avatarGalleryEntryMatchesImage(firstEntry, lastEntry), let peerReference = PeerReference(peer) { + let videoRepresentations = lastEntry.videoRepresentations.map { + VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) + } + result[0] = avatarGalleryEntryWithVideoRepresentations(firstEntry, videoRepresentations: videoRepresentations, immediateThumbnailData: firstEntry.immediateThumbnailData ?? lastEntry.immediateThumbnailData) + } + } else if let peerReference = PeerReference(peer) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { @@ -501,12 +551,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } if strongSelf.isViewLoaded { - strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? { - self?.deleteEntry(entry) + strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: sourceCorners, delete: strongSelf.canDelete ? { [weak self] sourceView in + self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) - }, edit: { [weak self] in - self?.editEntry(entry) + }, edit: { [weak self] sourceView, gesture in + self?.editEntry(entry, sourceView: sourceView, gesture: gesture) }) }), centralItemIndex: strongSelf.centralEntryIndex, synchronous: !isFirstTime) @@ -670,12 +720,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } let presentationData = self.presentationData - self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in - self?.deleteEntry(entry) + self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in + self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) - }, edit: { [weak self] in - self?.editEntry(entry) + }, edit: { [weak self] sourceView, gesture in + self?.editEntry(entry, sourceView: sourceView, gesture: gesture) }) }), centralItemIndex: self.centralEntryIndex) self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in @@ -789,12 +839,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private func replaceEntries(_ entries: [AvatarGalleryEntry]) { self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = true - self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in - self?.deleteEntry(entry) + self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in + self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil) } : nil, setMain: { [weak self] in self?.setMainEntry(entry) - }, edit: { [weak self] in - self?.editEntry(entry) + }, edit: { [weak self] sourceView, gesture in + self?.editEntry(entry, sourceView: sourceView, gesture: gesture) }) }), centralItemIndex: 0, synchronous: true) self.entries = entries self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = false @@ -812,7 +862,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } else { } case let .image(_, reference, _, _, _, _, _, _, _, _, _, _): - if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer._asPeer()) { + if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { if let reference = reference { let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) |> deliverOnMainQueue).start(next: { [weak self] photo in @@ -821,12 +871,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr for (lhs, rhs) in zip(firstEntry.representations, updatedEntry.representations) { if lhs.representation.dimensions == rhs.representation.dimensions { - strongSelf.context.account.postbox.mediaBox.copyResourceData(from: lhs.representation.resource.id, to: rhs.representation.resource.id, synchronous: true) + strongSelf.context.engine.resources.copyResourceData(from: EngineMediaResource.Id(lhs.representation.resource.id), to: EngineMediaResource.Id(rhs.representation.resource.id), synchronous: true) } } for (lhs, rhs) in zip(firstEntry.videoRepresentations, updatedEntry.videoRepresentations) { if lhs.representation.dimensions == rhs.representation.dimensions { - strongSelf.context.account.postbox.mediaBox.copyResourceData(from: lhs.representation.resource.id, to: rhs.representation.resource.id, synchronous: true) + strongSelf.context.engine.resources.copyResourceData(from: EngineMediaResource.Id(lhs.representation.resource.id), to: EngineMediaResource.Id(rhs.representation.resource.id), synchronous: true) } } @@ -861,158 +911,189 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } } - private func editEntry(_ rawEntry: AvatarGalleryEntry) { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - let dismissAction: () -> Void = { [weak actionSheet] in - actionSheet?.dismissAnimated() - } - - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in - dismissAction() - self?.openAvatarSetup?({ [weak self] in - self?.dismissImmediately() + private func editEntry(_ rawEntry: AvatarGalleryEntry, sourceView rawSourceView: UIView, gesture: ContextGesture?) { + let presentationData = self.presentationData + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Settings_SetNewProfilePhotoOrVideo, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + self?.openAvatarSetup?({ [weak self] in + self?.dismissImmediately() + }) }) - })) - + }))) + var isFallback = false if case let .image(_, _, _, _, _, _, _, _, _, _, isFallbackValue, _) = rawEntry { isFallback = isFallbackValue } - + if self.peer.id == self.context.account.peerId, let position = rawEntry.indexData?.position, position > 0 || isFallback { let title: String if let _ = rawEntry.videoRepresentations.last { - title = self.presentationData.strings.ProfilePhoto_SetMainVideo + title = presentationData.strings.ProfilePhoto_SetMainVideo } else { - title = self.presentationData.strings.ProfilePhoto_SetMainPhoto + title = presentationData.strings.ProfilePhoto_SetMainPhoto } - items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in - dismissAction() - self?.setMainEntry(rawEntry) - })) + items.append(.action(ContextMenuActionItem(text: title, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + self?.setMainEntry(rawEntry) + }) + }))) } let deleteTitle: String if let _ = rawEntry.videoRepresentations.last { - deleteTitle = self.presentationData.strings.Settings_RemoveVideo + deleteTitle = presentationData.strings.Settings_RemoveVideo } else { - deleteTitle = self.presentationData.strings.GroupInfo_SetGroupPhotoDelete + deleteTitle = presentationData.strings.GroupInfo_SetGroupPhotoDelete } - items.append(ActionSheetButtonItem(title: deleteTitle, color: .destructive, action: { [weak self] in - dismissAction() - self?.deleteEntry(rawEntry) - })) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - self.view.endEditing(true) - self.present(actionSheet, in: .window(.root)) - } - - private func deleteEntry(_ rawEntry: AvatarGalleryEntry) { - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let proceed = { - var entry = rawEntry - if case .topImage = entry, !self.entries.isEmpty { - entry = self.entries[0] + items.append(.action(ContextMenuActionItem(text: deleteTitle, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] c, _ in + guard let self, let c else { + return } - - self.removedEntry?(rawEntry) - - var focusOnItem: Int? - var updatedEntries = self.entries - var replaceItems = false - var dismiss = false - - switch entry { - case .topImage: - if self.peer.id == self.context.account.peerId { + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: presentationData.strings.Settings_RemoveConfirmation, textColor: .destructive, icon: { _ in + return nil + }, action: { [weak self] c, _ in + if let c { + c.dismiss(completion: { [weak self] in + self?.performDeleteEntry(rawEntry) + }) } else { - if entry == self.entries.first { - let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() - dismiss = true - } else { - if let index = self.entries.firstIndex(of: entry) { - self.entries.remove(at: index) - self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) - } - } - } - case let .image(_, reference, _, _, _, _, _, messageId, _, _, isFallback, _): - if self.peer.id == self.context.account.peerId { - if isFallback { - let _ = self.context.engine.accountData.updateFallbackPhoto(resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() - } else if let reference = reference { - let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() - } - - if entry == self.entries.first { - dismiss = true - } else { - if let index = self.entries.firstIndex(of: entry) { - replaceItems = true - updatedEntries.remove(at: index) - focusOnItem = index - 1 - } - } - } else { - if let messageId = messageId { - let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: [messageId], type: .forEveryone).start() - } - - if entry == self.entries.first { - let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() - dismiss = true - } else { - if let index = self.entries.firstIndex(of: entry) { - replaceItems = true - updatedEntries.remove(at: index) - focusOnItem = index - 1 - } - } + self?.performDeleteEntry(rawEntry) } - } - - if replaceItems { - updatedEntries = normalizeEntries(updatedEntries) - self.galleryNode.pager.replaceItems(updatedEntries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in - self?.deleteEntry(entry) - } : nil, setMain: { [weak self] in - self?.setMainEntry(entry) - }, edit: { [weak self] in - self?.editEntry(entry) - }) }), centralItemIndex: focusOnItem, synchronous: true) - self.entries = updatedEntries - } - if dismiss { - self._hiddenMedia.set(.single(nil)) - Queue.mainQueue().after(0.2) { - self.dismiss(forceAway: true) + })) + ] + c.pushItems(items: .single(ContextController.Items(content: .list(items)))) + }))) + + self.view.endEditing(true) + + let sourceView = self.navigationBar?.navigationButtonContextContainer(sourceView: rawSourceView) ?? rawSourceView + let contextController = makeContextController( + presentationData: presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), + source: .reference(AvatarGalleryContextReferenceContentSource(sourceView: sourceView)), + items: .single(ContextController.Items(content: .list(items))), + gesture: gesture + ) + self.presentInGlobalOverlay(contextController) + } + + private func presentDeleteEntryConfirmation(_ rawEntry: AvatarGalleryEntry, sourceView: UIView, gesture: ContextGesture?) { + self.view.endEditing(true) + + let items: [ContextMenuItem] = [ + .action(ContextMenuActionItem(text: self.presentationData.strings.Settings_RemoveConfirmation, textColor: .destructive, icon: { _ in + return nil + }, action: { [weak self] c, _ in + if let c { + c.dismiss(completion: { [weak self] in + self?.performDeleteEntry(rawEntry) + }) + } else { + self?.performDeleteEntry(rawEntry) } - } else { - if let firstEntry = self.entries.first { - self._hiddenMedia.set(.single(firstEntry)) - } - } - } - let actionSheet = ActionSheetController(presentationData: presentationData) - let items: [ActionSheetItem] = [ - ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - proceed() - }) + })) ] - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - self.present(actionSheet, in: .window(.root)) + let contextController = makeContextController( + presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), + source: .reference(AvatarGalleryContextReferenceContentSource(sourceView: sourceView, actionsOnTop: true)), + items: .single(ContextController.Items(content: .list(items))), + gesture: gesture + ) + self.presentInGlobalOverlay(contextController) + } + + private func performDeleteEntry(_ rawEntry: AvatarGalleryEntry) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + var entry = rawEntry + if case .topImage = entry, !self.entries.isEmpty { + entry = self.entries[0] + } + + self.removedEntry?(rawEntry) + + var focusOnItem: Int? + var updatedEntries = self.entries + var replaceItems = false + var dismiss = false + + switch entry { + case .topImage: + if self.peer.id == self.context.account.peerId { + } else { + if entry == self.entries.first { + let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() + dismiss = true + } else { + if let index = self.entries.firstIndex(of: entry) { + self.entries.remove(at: index) + self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) + } + } + } + case let .image(_, reference, _, _, _, _, _, messageId, _, _, isFallback, _): + if self.peer.id == self.context.account.peerId { + if isFallback { + let _ = self.context.engine.accountData.updateFallbackPhoto(resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() + } else if let reference = reference { + let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() + } + + if entry == self.entries.first { + dismiss = true + } else { + if let index = self.entries.firstIndex(of: entry) { + replaceItems = true + updatedEntries.remove(at: index) + focusOnItem = index - 1 + } + } + } else { + if let messageId = messageId { + let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: [messageId], type: .forEveryone).start() + } + + if entry == self.entries.first { + let _ = self.context.engine.peers.updatePeerPhoto(peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start() + dismiss = true + } else { + if let index = self.entries.firstIndex(of: entry) { + replaceItems = true + updatedEntries.remove(at: index) + focusOnItem = index - 1 + } + } + } + } + + if replaceItems { + updatedEntries = normalizeEntries(updatedEntries) + self.galleryNode.pager.replaceItems(updatedEntries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] sourceView in + self?.presentDeleteEntryConfirmation(entry, sourceView: sourceView, gesture: nil) + } : nil, setMain: { [weak self] in + self?.setMainEntry(entry) + }, edit: { [weak self] sourceView, gesture in + self?.editEntry(entry, sourceView: sourceView, gesture: gesture) + }) }), centralItemIndex: focusOnItem, synchronous: true) + self.entries = updatedEntries + } + if dismiss { + self._hiddenMedia.set(.single(nil)) + Queue.mainQueue().after(0.2) { + self.dismiss(forceAway: true) + } + } else { + if let firstEntry = self.entries.first { + self._hiddenMedia.set(.single(firstEntry)) + } + } } } diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift index 7bea9520d7..fc01d9507f 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift @@ -4,19 +4,11 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Photos import TelegramPresentationData -import TelegramUIPreferences -import TelegramStringFormatting import AccountContext import GalleryUI -import AppBundle - -private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white) -private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white) - -private let nameFont = Font.medium(15.0) -private let dateFont = Font.regular(14.0) +import ComponentFlow +import GlassControls enum AvatarGalleryItemFooterContent { case info @@ -27,24 +19,19 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { private let context: AccountContext private var presentationData: PresentationData private var strings: PresentationStrings - private var dateTimeFormat: PresentationDateTimeFormat - private let deleteButton: UIButton - private let actionButton: UIButton - private let nameNode: ASTextNode - private let dateNode: ASTextNode + private let buttonPanel = ComponentView() private let mainNode: ASTextNode private let setMainButton: HighlightableButtonNode - private var currentNameText: String? - private var currentDateText: String? private var currentTypeText: String? + private var displayActionButton: Bool = true private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)? - var delete: (() -> Void)? { + var delete: ((UIView) -> Void)? { didSet { - self.deleteButton.isHidden = self.delete == nil + self.requestLayout?(.immediate) } } @@ -56,25 +43,6 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { self.context = context self.presentationData = presentationData self.strings = presentationData.strings - self.dateTimeFormat = presentationData.dateTimeFormat - - self.deleteButton = UIButton() - self.deleteButton.isHidden = true - - self.actionButton = UIButton() - - self.deleteButton.setImage(deleteImage, for: [.normal]) - self.actionButton.setImage(actionImage, for: [.normal]) - - self.nameNode = ASTextNode() - self.nameNode.maximumNumberOfLines = 1 - self.nameNode.isUserInteractionEnabled = false - self.nameNode.displaysAsynchronously = false - - self.dateNode = ASTextNode() - self.dateNode.maximumNumberOfLines = 1 - self.dateNode.isUserInteractionEnabled = false - self.dateNode.displaysAsynchronously = false self.setMainButton = HighlightableButtonNode() self.setMainButton.isHidden = true @@ -86,36 +54,18 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { super.init() - self.view.addSubview(self.deleteButton) - self.view.addSubview(self.actionButton) - - self.addSubnode(self.nameNode) - self.addSubnode(self.dateNode) self.addSubnode(self.setMainButton) self.addSubnode(self.mainNode) - self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside]) - self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside]) self.setMainButton.addTarget(self, action: #selector(self.setMainButtonPressed), forControlEvents: .touchUpInside) } func setEntry(_ entry: AvatarGalleryEntry, content: AvatarGalleryItemFooterContent) { - var nameText: String? - var dateText: String? var typeText: String? var buttonText: String? var canShare = true switch entry { - case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _): - if date != 0 || isFallback { - nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" - } - if let date = date, date != 0 { - dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string - } else if isFallback { - dateText = !videoRepresentations.isEmpty ? self.strings.ProfilePhoto_PublicVideo : self.strings.ProfilePhoto_PublicPhoto - } - + case let .image(_, _, _, videoRepresentations, peer, _, _, _, _, _, _, _): if (!videoRepresentations.isEmpty) { typeText = self.strings.ProfilePhoto_MainVideo buttonText = self.strings.ProfilePhoto_SetMainVideo @@ -125,29 +75,12 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { } if let peer = peer { - canShare = !peer._asPeer().isCopyProtectionEnabled + canShare = !peer.isCopyProtectionEnabled } default: break } - if self.currentNameText != nameText || self.currentDateText != dateText { - self.currentNameText = nameText - self.currentDateText = dateText - - if let nameText = nameText { - self.nameNode.attributedText = NSAttributedString(string: nameText, font: nameFont, textColor: .white) - } else { - self.nameNode.attributedText = nil - } - - if let dateText = dateText { - self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white) - } else { - self.dateNode.attributedText = nil - } - } - if self.currentTypeText != typeText { self.currentTypeText = typeText @@ -159,17 +92,16 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { } } - self.actionButton.isHidden = !canShare + if self.displayActionButton != canShare { + self.displayActionButton = canShare + self.requestLayout?(.immediate) + } switch content { case .info: - self.nameNode.isHidden = false - self.dateNode.isHidden = false self.mainNode.isHidden = true self.setMainButton.isHidden = true case let .own(isMainPhoto): - self.nameNode.isHidden = true - self.dateNode.isHidden = true self.mainNode.isHidden = !isMainPhoto self.setMainButton.isHidden = isMainPhoto } @@ -179,52 +111,116 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset) let width = size.width - var panelHeight: CGFloat = 44.0 + bottomInset + var panelHeight: CGFloat = 54.0 + bottomInset panelHeight += contentInset - self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) - self.deleteButton.frame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0)) - - let constrainedSize = CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude) - let nameSize = self.nameNode.measure(constrainedSize) - let dateSize = self.dateNode.measure(constrainedSize) - - if nameSize.height.isZero { - self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize) - } else { - let labelsSpacing: CGFloat = 0.0 - self.nameNode.frame = CGRect(origin: CGPoint(x: floor((width - nameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0)), size: nameSize) - self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - nameSize.height - labelsSpacing) / 2.0) + nameSize.height + labelsSpacing), size: dateSize) + var buttonPanelInsets = UIEdgeInsets() + buttonPanelInsets.left = 8.0 + buttonPanelInsets.right = 8.0 + buttonPanelInsets.bottom = bottomInset + 8.0 + if bottomInset <= 32.0 { + buttonPanelInsets.left += 18.0 + buttonPanelInsets.right += 18.0 } + let constrainedSize = CGSize(width: width - 44.0 * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude) + let controlsY = panelHeight - buttonPanelInsets.bottom - 44.0 + let mainSize = self.mainNode.measure(constrainedSize) - self.mainNode.frame = CGRect(origin: CGPoint(x: floor((width - mainSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainSize.height) / 2.0)), size: mainSize) - + self.mainNode.frame = CGRect(origin: CGPoint(x: floor((width - mainSize.width) / 2.0), y: controlsY + floor((44.0 - mainSize.height) / 2.0)), size: mainSize) + let mainButtonSize = self.setMainButton.measure(constrainedSize) - self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize) + self.setMainButton.frame = CGRect(origin: CGPoint(x: floor((width - mainButtonSize.width) / 2.0), y: controlsY + floor((44.0 - mainButtonSize.height) / 2.0)), size: mainButtonSize) + + var leftControlItems: [GlassControlGroupComponent.Item] = [] + var rightControlItems: [GlassControlGroupComponent.Item] = [] + + if self.displayActionButton { + leftControlItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("forward"), + content: .icon("Chat/Input/Accessory Panels/MessageSelectionForward"), + action: { [weak self] in + guard let self else { + return + } + self.actionButtonPressed() + } + )) + } + + if self.delete != nil { + rightControlItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("delete"), + content: .icon("Chat/Input/Accessory Panels/MessageSelectionTrash"), + action: { [weak self] in + guard let self else { + return + } + self.deleteButtonPressed() + } + )) + } + + if leftControlItems.isEmpty && rightControlItems.isEmpty { + self.buttonPanel.view?.removeFromSuperview() + } else { + let buttonPanelSize = self.buttonPanel.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassControlPanelComponent( + theme: defaultDarkColorPresentationTheme, + leftItem: leftControlItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: leftControlItems, + background: .panel + ), + centralItem: nil, + rightItem: rightControlItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: rightControlItems, + background: .panel + ), + centerAlignmentIfPossible: true + )), + environment: {}, + containerSize: CGSize(width: size.width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0) + ) + let buttonPanelFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: panelHeight - buttonPanelInsets.bottom - buttonPanelSize.height), size: buttonPanelSize) + if let buttonPanelView = self.buttonPanel.view { + if buttonPanelView.superview == nil { + self.view.addSubview(buttonPanelView) + } + ComponentTransition(transition).setFrame(view: buttonPanelView, frame: buttonPanelFrame) + } + } return LayoutInfo(height: panelHeight, needsShadow: false) } override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) { - self.deleteButton.alpha = 1.0 - self.actionButton.alpha = 1.0 - self.nameNode.alpha = 1.0 - self.dateNode.alpha = 1.0 + if let buttonPanelView = self.buttonPanel.view { + buttonPanelView.alpha = 1.0 + } + self.mainNode.alpha = 1.0 self.setMainButton.alpha = 1.0 } override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - self.deleteButton.alpha = 0.0 - self.actionButton.alpha = 0.0 - self.nameNode.alpha = 0.0 - self.dateNode.alpha = 0.0 + if let buttonPanelView = self.buttonPanel.view { + buttonPanelView.alpha = 0.0 + } + self.mainNode.alpha = 0.0 self.setMainButton.alpha = 0.0 completion() } @objc private func deleteButtonPressed() { - self.delete?() + let sourceView: UIView + if let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View, let itemView = buttonPanelView.rightItemView?.itemView(id: AnyHashable("delete")) { + sourceView = itemView + } else if let buttonPanelView = self.buttonPanel.view { + sourceView = buttonPanelView + } else { + sourceView = self.view + } + self.delete?(sourceView) } @objc private func actionButtonPressed() { diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 911c1cd0d6..4fa5671393 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -5,6 +5,7 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData +import TelegramStringFormatting import AccountContext import RadialStatusNode import PhotoResources @@ -50,11 +51,11 @@ class PeerAvatarImageGalleryItem: GalleryItem { let presentationData: PresentationData let entry: AvatarGalleryEntry let sourceCorners: AvatarGalleryController.SourceCorners - let delete: (() -> Void)? + let delete: ((UIView) -> Void)? let setMain: (() -> Void)? - let edit: (() -> Void)? + let edit: ((UIView, ContextGesture?) -> Void)? - init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) { + init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: ((UIView) -> Void)?, setMain: (() -> Void)?, edit: ((UIView, ContextGesture?) -> Void)?) { self.context = context self.peer = peer self.presentationData = presentationData @@ -68,10 +69,6 @@ class PeerAvatarImageGalleryItem: GalleryItem { func node(synchronous: Bool) -> GalleryItemNode { let node = PeerAvatarImageGalleryItemNode(context: self.context, presentationData: self.presentationData, peer: self.peer, sourceCorners: self.sourceCorners) - if let indexData = self.entry.indexData { - node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string)) - } - node.setEntry(self.entry, synchronous: synchronous) node.footerContentNode.delete = self.delete node.footerContentNode.setMain = self.setMain @@ -82,9 +79,6 @@ class PeerAvatarImageGalleryItem: GalleryItem { func updateNode(node: GalleryItemNode, synchronous: Bool) { if let node = node as? PeerAvatarImageGalleryItemNode { - if let indexData = self.entry.indexData { - node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string)) - } let previousContentAnimations = node.imageNode.contentAnimations if synchronous { node.imageNode.contentAnimations = [] @@ -124,6 +118,63 @@ private class PeerAvatarImageGalleryContentNode: ASDisplayNode { } } +private final class AvatarGalleryEditButtonNode: HighlightableButtonNode { + let referenceNode: ContextReferenceContentNode + let containerNode: ContextControllerSourceNode + + private let textNode: ASTextNode + + var contextAction: ((UIView, ContextGesture?) -> Void)? + + init(title: String) { + self.referenceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + + self.textNode = ASTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.isUserInteractionEnabled = false + self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: .white) + + super.init() + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.textNode) + + self.containerNode.shouldBegin = { [weak self] _ in + guard let self else { + return false + } + return self.contextAction != nil + } + self.containerNode.activated = { [weak self] gesture, _ in + guard let self else { + return + } + self.contextAction?(self.referenceNode.view, gesture) + } + + self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let textSize = self.textNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)) + let size = CGSize(width: ceil(textSize.width) + 20.0, height: 44.0) + let bounds = CGRect(origin: CGPoint(), size: size) + + self.containerNode.frame = bounds + self.referenceNode.frame = bounds + self.textNode.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) + + return size + } + + @objc private func buttonPressed() { + self.contextAction?(self.referenceNode.view, nil) + } +} + final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let context: AccountContext private let presentationData: PresentationData @@ -140,6 +191,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { fileprivate let _ready = Promise() fileprivate let _title = Promise() + fileprivate let _titleContent = Promise(nil) fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>() private let statusNodeContainer: HighlightableButtonNode @@ -151,7 +203,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private var status: EngineMediaResource.FetchStatus? private let playbackStatusDisposable = MetaDisposable() - fileprivate var edit: (() -> Void)? + fileprivate var edit: ((UIView, ContextGesture?) -> Void)? init(context: AccountContext, presentationData: PresentationData, peer: EnginePeer, sourceCorners: AvatarGalleryController.SourceCorners) { self.context = context @@ -162,15 +214,17 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { self.contentNode = PeerAvatarImageGalleryContentNode() self.imageNode = TransformImageNode() self.footerContentNode = AvatarGalleryItemFooterContentNode(context: context, presentationData: presentationData) - + self.statusNodeContainer = HighlightableButtonNode() self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) self.statusNode.isHidden = true super.init() - + + self._title.set(.single("")) + self.contentNode.addSubnode(self.imageNode) - + self.imageNode.contentAnimations = .subsequentUpdates self.imageNode.view.contentMode = .scaleAspectFill self.imageNode.clipsToBounds = true @@ -185,7 +239,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self, let entry = strongSelf.entry, !entry.representations.isEmpty { let subject: ShareControllerSubject var actionCompletionText: String? - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer) { let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) subject = .media(videoFileReference.abstract, nil) actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved @@ -227,7 +281,36 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { transition.updateFrame(node: self.statusNodeContainer, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: floor((layout.size.height - statusSize.height) / 2.0)), size: statusSize)) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize)) } - + + private func makeTitleContent(entry: AvatarGalleryEntry) -> GalleryTitleView.Content? { + var counterText: String? + if let indexData = entry.indexData { + counterText = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string + } + + var authorTitle: String? + var dateTitle: String? + switch entry { + case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _): + if date != 0 || isFallback { + authorTitle = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + } + if let date = date, date != 0 { + dateTitle = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: date).string + } else if isFallback { + dateTitle = !videoRepresentations.isEmpty ? self.presentationData.strings.ProfilePhoto_PublicVideo : self.presentationData.strings.ProfilePhoto_PublicPhoto + } + default: + break + } + + if counterText == nil && authorTitle == nil && dateTitle == nil { + return nil + } + + return GalleryTitleView.Content(authorTitle: authorTitle, dateTitle: dateTitle, title: counterText, action: nil) + } + fileprivate func setEntry(_ entry: AvatarGalleryEntry, synchronous: Bool) { let previousRepresentations = self.entry?.representations let previousVideoRepresentations = self.entry?.videoRepresentations @@ -237,10 +320,17 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { var barButtonItems: [UIBarButtonItem] = [] let footerContent: AvatarGalleryItemFooterContent = .info if self.peer.id == self.context.account.peerId { - let rightBarButtonItem = UIBarButtonItem(title: entry.videoRepresentations.isEmpty ? self.presentationData.strings.Settings_EditPhoto : self.presentationData.strings.Settings_EditVideo, style: .plain, target: self, action: #selector(self.editPressed)) + let editTitle = entry.videoRepresentations.isEmpty ? self.presentationData.strings.Settings_EditPhoto : self.presentationData.strings.Settings_EditVideo + let editButtonNode = AvatarGalleryEditButtonNode(title: editTitle) + editButtonNode.contextAction = { [weak self] sourceView, gesture in + self?.edit?(sourceView, gesture) + } + let rightBarButtonItem = UIBarButtonItem(customDisplayNode: editButtonNode)! + rightBarButtonItem.accessibilityLabel = editTitle barButtonItems.append(rightBarButtonItem) } self._rightBarButtonItems.set(.single(barButtonItems)) + self._titleContent.set(.single(self.makeTitleContent(entry: entry))) self.footerContentNode.setEntry(entry, content: footerContent) @@ -260,7 +350,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { self.zoomableContent = (largestSize.dimensions.cgSize, self.contentNode) if let largestIndex = representations.firstIndex(where: { $0.representation == largestSize }) { - self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: representations[largestIndex].reference).start()) + self.fetchDisposable.set(self.context.engine.resources.fetch(reference: representations[largestIndex].reference, userLocation: .other, userContentType: .image).start()) } var id: Int64 @@ -274,7 +364,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { id = id &+ resource.photoId } } - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer) { if video != previousVideoRepresentations?.last { let mediaManager = self.context.sharedContext.mediaManager let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) @@ -590,6 +680,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { override func title() -> Signal { return self._title.get() } + + override func titleContent() -> Signal { + return self._titleContent.get() + } override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> { return self._rightBarButtonItems.get() @@ -599,7 +693,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let entry = self.entry, let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })), let status = self.status { switch status { case .Fetching: - self.context.account.postbox.mediaBox.cancelInteractiveResourceFetch(largestSize.resource) + self.context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(largestSize.resource.id)) case .Remote: let representations: [ImageRepresentationWithReference] switch entry { @@ -610,7 +704,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } if let largestIndex = representations.firstIndex(where: { $0.representation == largestSize }) { - self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: representations[largestIndex].reference).start()) + self.fetchDisposable.set(self.context.engine.resources.fetch(reference: representations[largestIndex].reference, userLocation: .other, userContentType: .image).start()) } default: break @@ -618,10 +712,6 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { } } - @objc private func editPressed() { - self.edit?() - } - override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { return .single((self.footerContentNode, nil)) } diff --git a/submodules/PeerInfoAvatarListNode/BUILD b/submodules/PeerInfoAvatarListNode/BUILD index 64d85a6c6d..e772d8c798 100644 --- a/submodules/PeerInfoAvatarListNode/BUILD +++ b/submodules/PeerInfoAvatarListNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AvatarNode:AvatarNode", diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 6d2ebebd3c..f9d6c51e3a 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -534,7 +534,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.didSetReady = true self.isReady.set(.single(true)) } - } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { + } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) { let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD index 5a25b9755f..0ee49ffed5 100644 --- a/submodules/PeerInfoUI/BUILD +++ b/submodules/PeerInfoUI/BUILD @@ -34,7 +34,6 @@ swift_library( "//submodules/LegacyUI:LegacyUI", "//submodules/GalleryUI:GalleryUI", "//submodules/ShareController:ShareController", - "//submodules/WebSearchUI:WebSearchUI", "//submodules/SearchBarNode:SearchBarNode", "//submodules/ContactsPeerItem:ContactsPeerItem", "//submodules/PasswordSetupUI:PasswordSetupUI", @@ -80,10 +79,14 @@ swift_library( "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", "//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", + "//submodules/TelegramUI/Components/AvatarComponent", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", "//submodules/CounterControllerTitleView", + "//submodules/TelegramUI/Components/ChatTimerScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD index ff18627371..ecd04a15a4 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/AccountContext", diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift index 10bf515648..dc1bce65f1 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift @@ -164,7 +164,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let text: String text = presentationData.strings.Login_UnknownError - baseController?.present(textAlertController(context: strongSelf.context, updatedPresentationData: nil, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + baseController?.present(textAlertController(context: strongSelf.context, forceTheme: defaultDarkPresentationTheme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) })) } @@ -191,17 +191,6 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let credentialsSection = Child(ListSectionComponent.self) -// let credentialsBackground = Child(RoundedRectangle.self) -// let credentialsStripe = Child(Rectangle.self) -// let credentialsURLTitle = Child(MultilineTextComponent.self) -// let credentialsURLText = Child(MultilineTextComponent.self) -// -// let credentialsKeyTitle = Child(MultilineTextComponent.self) -// let credentialsKeyText = Child(MultilineTextComponent.self) -// -// let credentialsCopyURLButton = Child(Button.self) -// let credentialsCopyKeyButton = Child(Button.self) - return { context in let topInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0 @@ -477,12 +466,13 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent maximumNumberOfLines: 1 )), titleAlignment: .center, + accessory: .none, action: { [weak state] _ in guard let state = state else { return } - let alertController = textAlertController(context: component.context, title: nil, text: environment.strings.CreateExternalStream_Revoke_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { - }), TextAlertAction(type: .defaultAction, title: environment.strings.CreateExternalStream_Revoke_Revoke, action: { [weak state] in + let alertController = textAlertController(context: component.context, forceTheme: theme, title: nil, text: environment.strings.CreateExternalStream_Revoke_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: { + }), TextAlertAction(type: .defaultDestructiveAction, title: environment.strings.CreateExternalStream_Revoke_Revoke, action: { [weak state] in state?.getCredentials(revoke: true) })]) environment.controller()?.present(alertController, in: .window(.root)) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index e592965d63..c4076f2345 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -16,6 +16,10 @@ import Markdown import SendInviteLinkScreen import OwnershipTransferController import OldChannelsController +import ComponentFlow +import AlertComponent +import AlertTransferHeaderComponent +import AvatarComponent private let rankMaxLength: Int32 = 16 @@ -23,6 +27,7 @@ private final class ChannelAdminControllerArguments { let context: AccountContext let updateAdminRights: (Bool) -> Void let toggleRight: (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void + let updateProcessJoinRequests: (Bool) -> Void let toggleRightWhileDisabled: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void let transferOwnership: () -> Void let updateRank: (String, String) -> Void @@ -33,10 +38,11 @@ private final class ChannelAdminControllerArguments { let toggleIsOptionExpanded: (RightsItem.Sub) -> Void let openPeer: () -> Void - init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void, openPeer: @escaping () -> Void) { + init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, updateProcessJoinRequests: @escaping (Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void, openPeer: @escaping () -> Void) { self.context = context self.updateAdminRights = updateAdminRights self.toggleRight = toggleRight + self.updateProcessJoinRequests = updateProcessJoinRequests self.toggleRightWhileDisabled = toggleRightWhileDisabled self.transferOwnership = transferOwnership self.updateRank = updateRank @@ -80,6 +86,8 @@ private enum ChannelAdminEntryStableId: Hashable { case rightsTitle case right(RightsItem) case addAdminsInfo + case processJoinRequests + case processJoinRequestsInfo case transfer case dismiss } @@ -123,6 +131,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry { case rightsTitle(PresentationTheme, String) case rightItem(PresentationTheme, Int, String, RightsItem, TelegramChatAdminRightsFlags, Bool, Bool, [AdminSubPermission], Bool) case addAdminsInfo(PresentationTheme, String) + case processJoinRequests(PresentationTheme, String, Bool, Bool) + case processJoinRequestsInfo(PresentationTheme, String) case transfer(PresentationTheme, String) case dismiss(PresentationTheme, String) @@ -134,7 +144,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { return ChannelAdminSection.rank.rawValue case .adminRights: return ChannelAdminSection.adminRights.rawValue - case .rightsTitle, .rightItem, .addAdminsInfo: + case .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo: return ChannelAdminSection.rights.rawValue case .transfer: return ChannelAdminSection.transfer.rawValue @@ -163,6 +173,10 @@ private enum ChannelAdminEntry: ItemListNodeEntry { return .right(right) case .addAdminsInfo: return .addAdminsInfo + case .processJoinRequests: + return .processJoinRequests + case .processJoinRequestsInfo: + return .processJoinRequestsInfo case .transfer: return .transfer case .dismiss: @@ -269,6 +283,18 @@ private enum ChannelAdminEntry: ItemListNodeEntry { } else { return false } + case let .processJoinRequests(lhsTheme, lhsText, lhsValue, lhsEnabled): + if case let .processJoinRequests(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled { + return true + } else { + return false + } + case let .processJoinRequestsInfo(lhsTheme, lhsText): + if case let .processJoinRequestsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .transfer(lhsTheme, lhsText): if case let .transfer(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -323,37 +349,51 @@ private enum ChannelAdminEntry: ItemListNodeEntry { default: return true } + case .processJoinRequests: + switch rhs { + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests: + return false + default: + return true + } + case .processJoinRequestsInfo: + switch rhs { + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo: + return false + default: + return true + } case .transfer: switch rhs { - case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer: + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer: return false default: return true } case .rankTitle: switch rhs { - case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle: + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle: return false default: return true } case .rankPreview: switch rhs { - case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview: + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview: return false default: return true } case .rank: switch rhs { - case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank: + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview, .rank: return false default: return true } case .rankInfo: switch rhs { - case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank, .rankInfo: + case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview, .rank, .rankInfo: return false default: return true @@ -385,7 +425,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry { return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelAdminEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in arguments.updateRank(text, updatedText) }, shouldUpdateText: { text in - if text.containsEmoji { + if text.containsGraphicEmoji { arguments.animateError() return false } @@ -448,6 +488,13 @@ private enum ChannelAdminEntry: ItemListNodeEntry { } case let .addAdminsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .processJoinRequests(_, text, value, enabled): + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + arguments.updateProcessJoinRequests(value) + }, activatedWhileDisabled: { + }) + case let .processJoinRequestsInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .transfer(_, text): return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.transferOwnership() @@ -464,14 +511,16 @@ private struct ChannelAdminControllerState: Equatable { var adminRights: Bool var updatedFlags: TelegramChatAdminRightsFlags? var updatedRank: String? + var processJoinRequests: Bool? var updating: Bool var focusedOnRank: Bool var expandedPermissions: Set = Set() - init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, updating: Bool = false, focusedOnRank: Bool = false, expandedPermissions: Set = Set()) { + init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, processJoinRequests: Bool? = nil, updating: Bool = false, focusedOnRank: Bool = false, expandedPermissions: Set = Set()) { self.adminRights = adminRights self.updatedFlags = updatedFlags self.updatedRank = updatedRank + self.processJoinRequests = processJoinRequests self.updating = updating self.focusedOnRank = focusedOnRank self.expandedPermissions = expandedPermissions @@ -487,6 +536,9 @@ private struct ChannelAdminControllerState: Equatable { if lhs.updatedRank != rhs.updatedRank { return false } + if lhs.processJoinRequests != rhs.processJoinRequests { + return false + } if lhs.updating != rhs.updating { return false } @@ -500,23 +552,27 @@ private struct ChannelAdminControllerState: Equatable { } func withUpdatedAdminRights(_ adminRights: Bool) -> ChannelAdminControllerState { - return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) + return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) } func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChatAdminRightsFlags?) -> ChannelAdminControllerState { - return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) + return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) } - + func withUpdatedUpdatedRank(_ updatedRank: String?) -> ChannelAdminControllerState { - return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) + return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) + } + + func withUpdatedProcessJoinRequests(_ processJoinRequests: Bool) -> ChannelAdminControllerState { + return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) } func withUpdatedUpdating(_ updating: Bool) -> ChannelAdminControllerState { - return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) + return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions) } func withUpdatedFocusedOnRank(_ focusedOnRank: Bool) -> ChannelAdminControllerState { - return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: focusedOnRank, expandedPermissions: self.expandedPermissions) + return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: focusedOnRank, expandedPermissions: self.expandedPermissions) } } @@ -624,7 +680,68 @@ private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, pee return TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except) } -private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { +private func guardBotAdminAlertText(_ text: String, presentationData: PresentationData) -> NSAttributedString { + return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor), + linkAttribute: { _ in return nil } + ), textAlignment: .center) +} + +private func guardBotReplacementAlertController(context: AccountContext, presentationData: PresentationData, currentBot: EnginePeer, newBot: EnginePeer, commit: @escaping () -> Void) -> AlertScreen { + let currentBotName = currentBot.compactDisplayTitle + let newBotName = newBot.compactDisplayTitle + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "header", + component: AnyComponent( + AlertTransferHeaderComponent( + fromComponent: AnyComponentWithIdentity(id: "currentBot", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: currentBot + ) + )), + toComponent: AnyComponentWithIdentity(id: "newBot", component: AnyComponent( + AvatarComponent( + context: context, + theme: presentationData.theme, + peer: newBot + ) + )), + type: .transfer + ) + ) + )) + content.append(AnyComponentWithIdentity( + id: "title", + component: AnyComponent( + AlertTitleComponent(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceTitle) + ) + )) + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .attributed(guardBotAdminAlertText(presentationData.strings.Channel_EditAdmin_GuardBotReplaceText(currentBotName, newBotName).string, presentationData: presentationData))) + ) + )) + + return AlertScreen( + context: context, + configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false), + content: content, + actions: [ + .init(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceKeep(currentBotName).string), + .init(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceUse(newBotName).string, type: .default, action: { + commit() + }) + ] + ) +} + +private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, currentGuardBotId: EnginePeer.Id?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { var entries: [ChannelAdminEntry] = [] if case let .channel(channel) = channelPeer, let admin = adminPeer { @@ -634,7 +751,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s if case .broadcast = channel.info { isChannel = true } - + var isCreator = false if let initialParticipant = initialParticipant, case .creator = initialParticipant { isCreator = true @@ -642,6 +759,12 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var canTransfer = false var canDismiss = false + let canEditProcessJoinRequests: Bool + if !isChannel, case let .user(user) = admin, user.botInfo?.flags.contains(.isGuardBot) == true { + canEditProcessJoinRequests = canEdit && user.id != accountPeerId + } else { + canEditProcessJoinRequests = false + } let isGroup: Bool var maskRightsFlags: TelegramChatAdminRightsFlags @@ -673,6 +796,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .direct(.canManageRanks), .direct(.canPinMessages), .direct(.canManageTopics), + .sub(.stories, storiesRelatedFlags), .direct(.canManageCalls), .direct(.canBeAnonymous), .direct(.canAddAdmins) @@ -696,7 +820,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s if isCreator { if isGroup { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) - + let accountUserRightsFlags: TelegramChatAdminRightsFlags if channel.flags.contains(.isCreator) { accountUserRightsFlags = maskRightsFlags @@ -844,7 +968,11 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s index += 1 } - if accountUserRightsFlags.contains(.canAddAdmins) { + if canEditProcessJoinRequests { + let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == admin.id) + entries.append(.processJoinRequests(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionProcessJoinRequests, processJoinRequests, !state.updating)) + entries.append(.processJoinRequestsInfo(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionProcessJoinRequestsInfo)) + } else if accountUserRightsFlags.contains(.canAddAdmins) { entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff)) } @@ -911,7 +1039,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } } } - + if !invite || state.adminRights { if canTransfer { entries.append(.transfer(presentationData.theme, isGroup ? presentationData.strings.Group_EditAdmin_TransferOwnership : presentationData.strings.Channel_EditAdmin_TransferOwnership)) @@ -1064,6 +1192,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let transferOwnershipDisposable = MetaDisposable() actionsDisposable.add(transferOwnershipDisposable) + + let guardBotDisposable = MetaDisposable() + actionsDisposable.add(guardBotDisposable) var dismissImpl: (() -> Void)? var dismissInputImpl: (() -> Void)? @@ -1104,6 +1235,10 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } return current.withUpdatedUpdatedFlags(updated) } + }, updateProcessJoinRequests: { value in + updateState { current in + return current.withUpdatedProcessJoinRequests(value) + } }, toggleRightWhileDisabled: { right, _ in let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), @@ -1229,7 +1364,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD guard let peer else { return } - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } }) @@ -1247,16 +1382,140 @@ public func channelAdminController(context: AccountContext, updatedPresentationD ), context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId) + ), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.CachedData(id: peerId) ) ) |> deliverOnMainQueue - |> map { presentationData, state, peerInfoData, exportedInvitation -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, peerInfoData, exportedInvitation, cachedData -> (ItemListControllerState, (ItemListNodeState, Any)) in let channelPeer = peerInfoData.0.flatMap { $0 } let adminPeer = peerInfoData.1.flatMap { $0 } let adminPresence = peerInfoData.2 + let cachedChannelData = cachedData.flatMap { $0 as? CachedChannelData } + let currentGuardBotId = cachedChannelData?.guardBotId let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!, initialParticipant: initialParticipant) + let channelIsGroup: Bool + let requestToJoinEnabled: Bool + if case let .channel(channel) = channelPeer { + if case .group = channel.info { + channelIsGroup = true + } else { + channelIsGroup = false + } + requestToJoinEnabled = channel.flags.contains(.requestToJoin) + } else { + channelIsGroup = true + requestToJoinEnabled = false + } + let canUseProcessJoinRequests: Bool + if case let .user(user) = adminPeer, user.botInfo?.flags.contains(.isGuardBot) == true, case .channel = channelPeer, canEdit, !invite || state.adminRights { + canUseProcessJoinRequests = true + } else { + canUseProcessJoinRequests = false + } + + let finishAfterSaving: (TelegramChatAdminRights?, Bool) -> Void = { adminRights, notifyUpdated in + let _ = (statePromise.get() + |> take(1)).start(next: { state in + let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == adminId) + + let complete: () -> Void = { + if notifyUpdated { + updated(adminRights) + } + dismissImpl?() + } + guard canUseProcessJoinRequests else { + complete() + return + } + + if processJoinRequests { + if currentGuardBotId == adminId { + complete() + return + } + if notifyUpdated { + updated(adminRights) + } + updateState { current in + return current.withUpdatedUpdating(true) + } + guardBotDisposable.set((context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: true, guardBotId: adminId, applyToInvites: false, clearGuardBot: false) + |> deliverOnMainQueue).start(error: { _ in + updateState { current in + return current.withUpdatedUpdating(false) + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + }, completed: { + dismissImpl?() + })) + } else if currentGuardBotId == adminId { + if notifyUpdated { + updated(adminRights) + } + updateState { current in + return current.withUpdatedUpdating(true) + } + guardBotDisposable.set((context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: requestToJoinEnabled, guardBotId: nil, applyToInvites: false, clearGuardBot: true) + |> deliverOnMainQueue).start(error: { _ in + updateState { current in + return current.withUpdatedUpdating(false) + } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + }, completed: { + dismissImpl?() + })) + } else { + complete() + } + }) + } + + let confirmProcessJoinRequests: (@escaping () -> Void) -> Void = { commit in + let _ = (statePromise.get() + |> take(1)).start(next: { state in + let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == adminId) + + guard canUseProcessJoinRequests, processJoinRequests else { + commit() + return + } + if currentGuardBotId == nil { + guard let adminPeer else { + commit() + return + } + let title = channelIsGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers + let text = channelIsGroup ? presentationData.strings.Channel_EditAdmin_GuardBotEnableMembersText(adminPeer.compactDisplayTitle).string : presentationData.strings.Channel_EditAdmin_GuardBotEnableSubscribersText(adminPeer.compactDisplayTitle).string + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: title, text: text, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_EditAdmin_GuardBotEnable, action: { + commit() + }) + ], parseMarkdown: true), nil) + } else if currentGuardBotId != adminId, let currentGuardBotId, let adminPeer { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: currentGuardBotId)) + |> deliverOnMainQueue).start(next: { currentGuardPeer in + guard let currentGuardPeer else { + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + return + } + presentControllerImpl?(guardBotReplacementAlertController(context: context, presentationData: presentationData, currentBot: currentGuardPeer, newBot: adminPeer, commit: { + commit() + }), nil) + }) + } else { + commit() + } + }) + } let rightButtonActionImpl = { + confirmProcessJoinRequests { if invite && !state.adminRights { updateState { current in return current.withUpdatedUpdating(true) @@ -1328,7 +1587,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return current } - if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsEmoji { + if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsGraphicEmoji { errorImpl?() return } @@ -1360,7 +1619,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } let effectiveRank = updateRank ?? currentRank - if effectiveRank?.containsEmoji ?? false { + if effectiveRank?.containsGraphicEmoji ?? false { errorImpl?() return } @@ -1410,8 +1669,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { - updated(TelegramChatAdminRights(rights: updateFlags)) - dismissImpl?() + finishAfterSaving(TelegramChatAdminRights(rights: updateFlags), true) })) } else if let updateRank = updateRank, let currentFlags = currentFlags { updateState { current in @@ -1420,11 +1678,10 @@ public func channelAdminController(context: AccountContext, updatedPresentationD updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: currentFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { _ in }, completed: { - updated(TelegramChatAdminRights(rights: currentFlags)) - dismissImpl?() + finishAfterSaving(TelegramChatAdminRights(rights: currentFlags), true) })) } else { - dismissImpl?() + finishAfterSaving(nil, false) } } else if canEdit { var updateFlags: TelegramChatAdminRightsFlags? @@ -1435,7 +1692,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return current } - if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsEmoji { + if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsGraphicEmoji { errorImpl?() return } @@ -1499,8 +1756,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } dismissImpl?() }, completed: { - updated(TelegramChatAdminRights(rights: updateFlags)) - dismissImpl?() + finishAfterSaving(TelegramChatAdminRights(rights: updateFlags), true) })) } } @@ -1515,7 +1771,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return current } - if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsEmoji { + if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsGraphicEmoji { errorImpl?() return } @@ -1553,6 +1809,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD dismissImpl?() }, completed: { + updated(TelegramChatAdminRights(rights: updateFlags)) dismissImpl?() })) } else if updateFlags != defaultFlags || updateRank != nil { @@ -1629,6 +1886,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD dismissImpl?() } } + } } var footerButtonTitle: String = presentationData.strings.Channel_Management_SaveChanges @@ -1699,7 +1957,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let rightNavigationButton: ItemListNavigationButton? if state.focusedOnRank { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { rightButtonActionImpl() }) footerItem = nil @@ -1709,7 +1967,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, adminPeer: adminPeer, adminPresence: adminPresence, initialParticipant: initialParticipant, invite: invite, canEdit: canEdit), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, footerItem: footerItem, animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, adminPeer: adminPeer, adminPresence: adminPresence, currentGuardBotId: currentGuardBotId, initialParticipant: initialParticipant, invite: invite, canEdit: canEdit), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, footerItem: footerItem, animateChanges: true) return (controllerState, (listState, arguments)) } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index a554523895..705c240543 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -294,7 +293,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { if peer.id == participant.peer.id { peerText = strings.Channel_Management_LabelAdministrator } else { - peerText = strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string + peerText = strings.Channel_Management_PromotedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string } } else { peerText = "" @@ -323,7 +322,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { .init(type: .destructive, title: presentationData.strings.Channel_Management_DismissAdmin, action: { arguments.removeAdmin(participant.peer.id) }) ]) - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: participant.presences[participant.peer.id].flatMap { EnginePeer.Presence($0) }, text: peerText.isEmpty ? .presence : .text(peerText, .secondary), label: label, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) @@ -731,7 +730,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation } if case .legacyGroup = peer { } else { - pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil, starsState: nil)) + pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: nil, starsState: nil)) } }) }) @@ -845,7 +844,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation |> deliverOnMainQueue).start(next: { peerId in if peerId.namespace == Namespaces.Peer.CloudChannel { var didReportLoadCompleted = false - let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) { membersState in + let membersAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { adminsPromise.set(.single(nil)) } else { @@ -912,18 +911,18 @@ public func channelAdminsController(context: AccountContext, updatedPresentation } for (participant, peer, presence) in participants { if let peer { - var presences: [PeerId: PeerPresence] = [:] + var presences: [EnginePeer.Id: EngineRawPeerPresence] = [:] if let presence { presences[peer.id] = presence._asPresence() } switch participant { case let .creator(_, rank): - result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer._asPeer(), presences: presences)) + result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: rank), peer: peer, presences: presences)) case let .admin(_, _, _, rank): var peers: [EnginePeer.Id: EnginePeer] = [:] peers[creator.id] = creator peers[peer.id] = peer - result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: rank, subscriptionUntilDate: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() }), presences: presences)) + result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: rank, subscriptionUntilDate: nil), peer: peer, peers: peers, presences: presences)) case .member: break } @@ -974,7 +973,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation var secondaryRightNavigationButton: ItemListNavigationButton? if let admins = admins, admins.count > 1 { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift index 2c0fe628f0..96110577b5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -14,6 +13,8 @@ import AlertUI import PresentationDataUtils import ItemListAvatarAndNameInfoItem import OldChannelsController +import ChatTimerScreen +import ContextUI private let rankMaxLength: Int32 = 16 @@ -55,6 +56,7 @@ private enum ChannelBannedMemberSection: Int32 { private enum ChannelBannedMemberEntryTag: ItemListItemTag { case rank + case timeout func isEqual(to other: ItemListItemTag) -> Bool { if let other = other as? ChannelBannedMemberEntryTag, self == other { @@ -362,7 +364,7 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry { case let .timeout(_, text, value): return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openTimeout() - }) + }, tag: ChannelBannedMemberEntryTag.timeout) case let .exceptionInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .delete(_, text): @@ -382,7 +384,7 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry { return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "", textColor: .black), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: true), spacing: 0.0, clearType: enabled ? .always : .none, enabled: enabled, tag: ChannelBannedMemberEntryTag.rank, sectionId: self.section, textUpdated: { updatedText in arguments.updateRank(text, updatedText) }, shouldUpdateText: { text in - if text.containsEmoji { + if text.containsGraphicEmoji { arguments.animateError() return false } @@ -408,6 +410,18 @@ private struct ChannelBannedMemberControllerState: Equatable { var focusedOnRank: Bool } +private final class ChannelBannedMemberContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags { var result = flags result.remove(.banReadMessages) @@ -425,7 +439,7 @@ func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBanne return result } -private func channelBannedMemberControllerEntries(presentationData: PresentationData, state: ChannelBannedMemberControllerState, accountPeerId: PeerId, channelPeer: EnginePeer?, memberPeer: EnginePeer?, memberPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, initialBannedBy: EnginePeer?, editMember: Bool) -> [ChannelBannedMemberEntry] { +private func channelBannedMemberControllerEntries(presentationData: PresentationData, state: ChannelBannedMemberControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, memberPeer: EnginePeer?, memberPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, initialBannedBy: EnginePeer?, editMember: Bool) -> [ChannelBannedMemberEntry] { var entries: [ChannelBannedMemberEntry] = [] if case let .channel(channel) = channelPeer, let defaultBannedRights = channel.defaultBannedRights, let member = memberPeer { @@ -586,7 +600,7 @@ private func channelBannedMemberControllerEntries(presentationData: Presentation return entries } -public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, memberId: PeerId, editMember: Bool = false, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatBannedRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController { +public func channelBannedMemberController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, memberId: EnginePeer.Id, editMember: Bool = false, initialParticipant: ChannelParticipant?, updated: @escaping (TelegramChatBannedRights?) -> Void, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void) -> ViewController { let initialState = ChannelBannedMemberControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970), updatedFlags: nil, updatedTimeout: nil, updating: false, updatedRank: nil, focusedOnRank: false) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -604,25 +618,27 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen var dismissImpl: (() -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var dismissInputImpl: (() -> Void)? var errorImpl: (() -> Void)? var scrollToRankImpl: (() -> Void)? + var findTimeoutReferenceNode: (() -> ItemListDisclosureItemNode?)? - let peerView = Promise() - peerView.set(context.account.viewTracker.peerView(peerId)) + let peerSignal = Promise() + peerSignal.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))) let arguments = ChannelBannedMemberControllerArguments(context: context, toggleRight: { rights, value in - let _ = (peerView.get() + let _ = (peerSignal.get() |> take(1) - |> deliverOnMainQueue).start(next: { view in + |> deliverOnMainQueue).start(next: { peer in var defaultBannedRightsFlagsValue: TelegramChatBannedRightsFlags? - guard let peer = view.peers[peerId] else { + guard let peer else { return } - if let channel = peer as? TelegramChannel, let initialRightFlags = channel.defaultBannedRights?.flags { + if case let .channel(channel) = peer, let initialRightFlags = channel.defaultBannedRights?.flags { defaultBannedRightsFlagsValue = initialRightFlags - } else if let group = peer as? TelegramGroup, let initialRightFlags = group.defaultBannedRights?.flags { + } else if case let .legacyGroup(group) = peer, let initialRightFlags = group.defaultBannedRights?.flags { defaultBannedRightsFlagsValue = initialRightFlags } guard let defaultBannedRightsFlags = defaultBannedRightsFlagsValue else { @@ -648,7 +664,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen } } else { effectiveRightsFlags.insert(rights) - for (right, _) in allGroupPermissionList(peer: EnginePeer(peer), expandMedia: false) { + for (right, _) in allGroupPermissionList(peer: peer, expandMedia: false) { if groupPermissionDependencies(right).contains(rights) { effectiveRightsFlags.insert(right) } @@ -656,7 +672,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen for item in banSendMediaSubList() { effectiveRightsFlags.insert(item.0) - for (right, _) in allGroupPermissionList(peer: EnginePeer(peer), expandMedia: false) { + for (right, _) in allGroupPermissionList(peer: peer, expandMedia: false) { if groupPermissionDependencies(right).contains(item.0) { effectiveRightsFlags.insert(right) } @@ -669,7 +685,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen effectiveRightsFlags = effectiveRightsFlags.subtracting(groupPermissionDependencies(rights)) } else { effectiveRightsFlags.insert(rights) - for (right, _) in allGroupPermissionList(peer: EnginePeer(peer), expandMedia: false) { + for (right, _) in allGroupPermissionList(peer: peer, expandMedia: false) { if groupPermissionDependencies(right).contains(rights) { effectiveRightsFlags.insert(right) } @@ -681,10 +697,10 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen } }) }, toggleRightWhileDisabled: { right in - let _ = (peerView.get() + let _ = (peerSignal.get() |> take(1) - |> deliverOnMainQueue).start(next: { view in - guard let channel = view.peers[view.peerId] as? TelegramChannel else { + |> deliverOnMainQueue).start(next: { peer in + guard case let .channel(channel) = peer else { return } guard let defaultBannedRights = channel.defaultBannedRights else { @@ -715,12 +731,20 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen } }, openTimeout: { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController(presentationData: presentationData) let intervals: [Int32] = [ 1 * 60 * 60 * 24, 7 * 60 * 60 * 24, 30 * 60 * 60 * 24 ] + let currentTimeout: Int32 = stateValue.with { state in + if let updatedTimeout = state.updatedTimeout { + return updatedTimeout + } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo { + return banInfo.rights.untilDate + } else { + return Int32.max + } + } let applyValue: (Int32?) -> Void = { value in updateState { state in var state = state @@ -728,29 +752,71 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen return state } } - var items: [ActionSheetItem] = [] + var items: [ContextMenuItem] = [] for interval in intervals { - items.append(ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: interval), color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - applyValue(initialState.referenceTimestamp + interval) - })) + let timeoutValue = initialState.referenceTimestamp + interval + items.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: presentationData.strings, value: interval), icon: { theme in + if currentTimeout == timeoutValue { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) + applyValue(timeoutValue) + }))) } - items.append(ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Forever, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() + items.append(.action(ContextMenuActionItem(text: presentationData.strings.MessageTimer_Forever, icon: { theme in + if currentTimeout == 0 || currentTimeout == Int32.max { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) applyValue(Int32.max) - })) - items.append(ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Custom, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - presentControllerImpl?(PeerBanTimeoutController(context: context, updatedPresentationData: updatedPresentationData, currentValue: Int32(Date().timeIntervalSince1970), applyValue: { value in - applyValue(value) - }), nil) - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - presentControllerImpl?(actionSheet, nil) + }))) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.MessageTimer_Custom, icon: { _ in + return nil + }, action: { _, f in + f(.default) + let controller = ChatTimerScreen( + context: context, + updatedPresentationData: updatedPresentationData, + configuration: ChatTimerScreen.Configuration( + style: .default, + picker: .date, + currentValue: Int32(Date().timeIntervalSince1970), + minimumDate: Date(), + maximumDate: Date(timeIntervalSince1970: Double(Int32.max - 1)), + pickerValueMapping: .roundDateToDaysUTC, + primaryActionTitle: { strings, _, _ in + strings.Wallpaper_Set + } + ), + completion: { value in + guard let value else { + return + } + applyValue(value) + } + ) + presentControllerImpl?(controller, nil) + }))) + guard let sourceNode = findTimeoutReferenceNode?() else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(ChannelBannedMemberContextReferenceContentSource(sourceView: sourceNode.labelNode.view)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) }, delete: { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) @@ -782,7 +848,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen guard let peer else { return } - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } }) @@ -839,16 +905,16 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen var footerButtonTitle: String = presentationData.strings.GroupPermission_SaveChanges let rightButtonActionImpl = { - let _ = (peerView.get() + let _ = (peerSignal.get() |> take(1) - |> deliverOnMainQueue).start(next: { view in + |> deliverOnMainQueue).start(next: { peer in var defaultBannedRightsFlagsValue: TelegramChatBannedRightsFlags? - guard let peer = view.peers[peerId] else { + guard let peer else { return } - if let channel = peer as? TelegramChannel, let initialRightFlags = channel.defaultBannedRights?.flags { + if case let .channel(channel) = peer, let initialRightFlags = channel.defaultBannedRights?.flags { defaultBannedRightsFlagsValue = initialRightFlags - } else if let group = peer as? TelegramGroup, let initialRightFlags = group.defaultBannedRights?.flags { + } else if case let .legacyGroup(group) = peer, let initialRightFlags = group.defaultBannedRights?.flags { defaultBannedRightsFlagsValue = initialRightFlags } guard let defaultBannedRightsFlags = defaultBannedRightsFlagsValue else { @@ -860,7 +926,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen updateRank = current.updatedRank?.trimmingCharacters(in: .whitespacesAndNewlines) return current } - if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsEmoji { + if let updateRank = updateRank, updateRank.count > rankMaxLength || updateRank.containsGraphicEmoji { errorImpl?() return } @@ -933,7 +999,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen previousRights = banInfo?.rights } - let updateRankSignal: (PeerId) -> Signal + let updateRankSignal: (EnginePeer.Id) -> Signal if let updateRank { updateRankSignal = { peerId in return context.peerChannelMemberCategoriesContextsManager.updateMemberRank(engine: context.engine, peerId: peerId, memberId: memberId, rank: updateRank) @@ -965,7 +1031,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen if peerId.namespace == Namespaces.Peer.CloudGroup { let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) |> map(Optional.init) - |> `catch` { error -> Signal in + |> `catch` { error -> Signal in switch error { case .tooManyChannels: Queue.mainQueue().async { @@ -976,7 +1042,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen } return .single(nil) } - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in guard let upgradedPeerId = upgradedPeerId else { return .single(nil) } @@ -984,9 +1050,9 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen let rankSignal = updateRankSignal(upgradedPeerId) return context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(engine: context.engine, peerId: upgradedPeerId, memberId: memberId, bannedRights: cleanResolvedRights) - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return rankSignal - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } } @@ -1056,7 +1122,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen let rightNavigationButton: ItemListNavigationButton? let footerItem: ItemListControllerFooterItem? if state.focusedOnRank { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { rightButtonActionImpl() }) footerItem = nil @@ -1085,9 +1151,15 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen presentControllerImpl = { [weak controller] value, presentationArguments in controller?.present(value, in: .window(.root), with: presentationArguments) } + presentInGlobalOverlayImpl = { [weak controller] value in + controller?.presentInGlobalOverlay(value, with: nil) + } pushControllerImpl = { [weak controller] c in controller?.push(c) } + findTimeoutReferenceNode = { [weak controller] in + return controller?.itemNode(forTag: ChannelBannedMemberEntryTag.timeout) as? ItemListDisclosureItemNode + } let hapticFeedback = HapticFeedback() errorImpl = { [weak controller] in diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 86dd2e6d09..b85436332f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -162,12 +162,12 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { switch participant.participant { case let .member(_, _, _, banInfo, _, _): if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - text = .text(strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string, .secondary) + text = .text(strings.Channel_Management_RemovedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).string, .secondary) } default: break } - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { arguments.openPeer(participant) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -313,9 +313,16 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat } } } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { channel in - guard let _ = channel as? TelegramChannel else { + guard case .channel = channel else { return } @@ -356,20 +363,20 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat let presentationData = context.sharedContext.currentPresentationData.with { $0 } let actionSheet = ActionSheetController(presentationData: presentationData) var items: [ActionSheetItem] = [] - if !EnginePeer(participant.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).isEmpty { - items.append(ActionSheetTextItem(title: EnginePeer(participant.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))) + if !participant.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).isEmpty { + items.append(ActionSheetTextItem(title: participant.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))) } let viewInfoTitle: String - if participant.peer is TelegramChannel { + if case .channel = participant.peer { viewInfoTitle = presentationData.strings.GroupRemoved_ViewChannelInfo } else { viewInfoTitle = presentationData.strings.GroupRemoved_ViewUserInfo } items.append(ActionSheetButtonItem(title: viewInfoTitle, action: { [weak actionSheet] in actionSheet?.dismissAnimated() - if participant.peer is TelegramChannel { + if case .channel = participant.peer { if let navigationController = getNavigationControllerImpl?() { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(EnginePeer(participant.peer)))) + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(participant.peer))) } } else if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(infoController) @@ -424,7 +431,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat }) }) - let (listDisposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in + let (listDisposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, updated: { listState in if case .loading(true) = listState.loadingState, listState.list.isEmpty { blacklistPromise.set(.single(nil)) } else { @@ -447,7 +454,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat var secondaryRightNavigationButton: ItemListNavigationButton? if let participants = participants, !participants.isEmpty { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupActionSheetItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupActionSheetItem.swift index 2418f972f1..e4d23c55a5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupActionSheetItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupActionSheetItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import UIKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -12,12 +11,12 @@ import AccountContext final class ChannelDiscussionGroupActionSheetItem: ActionSheetItem { let context: AccountContext - let channelPeer: Peer - let groupPeer: Peer + let channelPeer: EnginePeer + let groupPeer: EnginePeer let strings: PresentationStrings let nameDisplayOrder: PresentationPersonNameOrder - - init(context: AccountContext, channelPeer: Peer, groupPeer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { + + init(context: AccountContext, channelPeer: EnginePeer, groupPeer: EnginePeer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { self.context = context self.channelPeer = channelPeer self.groupPeer = groupPeer @@ -43,7 +42,7 @@ private final class ChannelDiscussionGroupActionSheetItemNode: ActionSheetItemNo private let groupAvatarNode: AvatarNode private let textNode: ImmediateTextNode - init(theme: ActionSheetControllerTheme, context: AccountContext, channelPeer: Peer, groupPeer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { + init(theme: ActionSheetControllerTheme, context: AccountContext, channelPeer: EnginePeer, groupPeer: EnginePeer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) { self.theme = theme self.channelAvatarNode = AvatarNode(font: avatarFont) @@ -65,14 +64,14 @@ private final class ChannelDiscussionGroupActionSheetItemNode: ActionSheetItemNo self.addSubnode(self.channelAvatarNode) self.addSubnode(self.textNode) - self.channelAvatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(channelPeer)) - self.groupAvatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(groupPeer)) - + self.channelAvatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: channelPeer) + self.groupAvatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: groupPeer) + let text: PresentationStrings.FormattedString - if let channelPeer = channelPeer as? TelegramChannel, let addressName = channelPeer.addressName, !addressName.isEmpty { - text = strings.Channel_DiscussionGroup_PublicChannelLink(EnginePeer(groupPeer).displayTitle(strings: strings, displayOrder: nameDisplayOrder), EnginePeer(channelPeer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)) + if case let .channel(channel) = channelPeer, let addressName = channel.addressName, !addressName.isEmpty { + text = strings.Channel_DiscussionGroup_PublicChannelLink(groupPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder), channelPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)) } else { - text = strings.Channel_DiscussionGroup_PrivateChannelLink(EnginePeer(groupPeer).displayTitle(strings: strings, displayOrder: nameDisplayOrder), EnginePeer(channelPeer).displayTitle(strings: strings, displayOrder: nameDisplayOrder)) + text = strings.Channel_DiscussionGroup_PrivateChannelLink(groupPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder), channelPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)) } let textFont = Font.regular(floor(theme.baseFontSize * 14.0 / 17.0)) diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index 6f78a09e33..579db2640f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -355,7 +355,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat let actionSheet = ActionSheetController(presentationData: presentationData) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ChannelDiscussionGroupActionSheetItem(context: context, channelPeer: channelPeer._asPeer(), groupPeer: groupPeer._asPeer(), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), + ChannelDiscussionGroupActionSheetItem(context: context, channelPeer: channelPeer, groupPeer: groupPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder), ActionSheetButtonItem(title: presentationData.strings.Channel_DiscussionGroup_LinkGroup, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index 036bdd4a28..a9311e1547 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -15,7 +15,6 @@ import ItemListPeerActionItem import InviteLinksUI import UndoUI import SendInviteLinkScreen -import Postbox private final class ChannelMembersControllerArguments { let context: AccountContext @@ -265,10 +264,19 @@ private enum ChannelMembersEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ChannelMembersControllerArguments switch self { - case let .hideMembers(text, disabledReason, isInteractive, value): - return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in + case let .hideMembers(text, disabledReason, isInteractive, currentValue): + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: currentValue, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !currentValue && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in if let disabledReason { - arguments.displayHideMembersTip(disabledReason) + switch disabledReason { + case .notEnoughMembers: + if currentValue && !value { + arguments.updateHideMembers(value) + } else { + arguments.displayHideMembersTip(disabledReason) + } + case .notAllowed: + arguments.displayHideMembersTip(disabledReason) + } } else { arguments.updateHideMembers(value) } @@ -293,7 +301,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled, _, isGroup): let text: ItemListPeerItemText - if let user = participant.peer as? TelegramUser, let _ = user.botInfo { + if case let .user(user) = participant.peer, let _ = user.botInfo { text = .text(strings.Bot_GenericBotStatus, .secondary) } else { text = .presence @@ -321,7 +329,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { label = .none } - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: label, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { arguments.openParticipant(participant, isGroup) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -385,7 +393,7 @@ private struct ChannelMembersControllerState: Equatable { } } -private func channelMembersControllerEntries(context: AccountContext, presentationData: PresentationData, view: PeerView, state: ChannelMembersControllerState, contacts: [RenderedChannelParticipant]?, participants: [RenderedChannelParticipant]?, isGroup: Bool) -> [ChannelMembersEntry] { +private func channelMembersControllerEntries(context: AccountContext, presentationData: PresentationData, view: EngineRawPeerView, state: ChannelMembersControllerState, contacts: [RenderedChannelParticipant]?, participants: [RenderedChannelParticipant]?, isGroup: Bool) -> [ChannelMembersEntry] { if participants == nil || participants?.count == nil { return [] } @@ -712,10 +720,10 @@ public func channelMembersController(context: AccountContext, updatedPresentatio let peerView = context.account.viewTracker.peerView(peerId) - let (contactsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in + let (contactsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in contactsPromise.set(.single(state.list)) }) - let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in peersPromise.set(.single(state.list)) }) actionsDisposable.add(disposable) @@ -744,7 +752,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio } if !isEmpty { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } @@ -773,7 +781,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio return state.withUpdatedSearchingMembers(false) } }, openPeer: { peer, _ in - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(infoController) } }, pushController: { c in diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 13fd8cad95..eceacff875 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -209,7 +209,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { displayOrder: nameDisplayOrder, context: context, peerMode: .peer, - peer: .peer(peer: EnginePeer(participant.peer), chatPeer: EnginePeer(participant.peer)), + peer: .peer(peer: participant.peer, chatPeer: participant.peer), status: status, rightLabelText: label.flatMap { .init(text: $0, color: labelColor, hasBackground: labelBackground) }, enabled: enabled, @@ -220,7 +220,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) }), action: { _ in - interaction.peerSelected(EnginePeer(participant.peer), participant) + interaction.peerSelected(participant.peer, participant) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in interaction.setPeerIdWithRevealedOptions(RevealedPeerId(peerId: participant.peer.id, section: self.section), fromPeerId.flatMap({ RevealedPeerId(peerId: $0, section: self.section) })) @@ -263,13 +263,13 @@ private func categorySignal(context: AccountContext, peerId: EnginePeer.Id, cate } switch category { case .admins: - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) case .contacts: - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) case .bots: - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.bots(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.bots(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) case .members: - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: processListState) } let (disposable, _) = disposableAndLoadMoreControl @@ -452,7 +452,14 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon state.revealedPeerId = nil return state } - let signal = context.account.postbox.loadedPeerWithId(memberId) + let signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: memberId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue |> mapToSignal { peer -> Signal in let result = ValuePromise() @@ -630,7 +637,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon switch mode { case .searchMembers, .banAndPromoteActions: foundGroupMembers = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) } @@ -645,7 +652,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon |> map { $0 ?? [] } case .searchAdmins: foundGroupMembers = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) } @@ -655,7 +662,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon foundMembers = .single([]) case .searchBanned: foundGroupMembers = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) subscriber.putCompletion() @@ -665,7 +672,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } |> runOn(Queue.mainQueue()) foundMembers = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list.filter({ participant in return participant.peer.id != context.account.peerId @@ -677,7 +684,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon |> runOn(Queue.mainQueue()) case .searchKicked: foundGroupMembers = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.banned(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: query, updated: { state in if case .ready = state.loadingState { subscriber.putNext(state.list) subscriber.putCompletion() @@ -742,7 +749,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon continue } - if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil { continue } @@ -829,7 +836,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if peer.id == participant.peer.id { label = presentationData.strings.Channel_Management_LabelAdministrator } else { - label = presentationData.strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + label = presentationData.strings.Channel_Management_PromotedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } } } @@ -860,7 +867,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon switch participant.participant { case let .member(_, _, _, banInfo, _, _): if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + label = presentationData.strings.Channel_Management_RemovedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } default: break @@ -874,7 +881,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } for participant in foundMembers { - if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil { continue } @@ -918,27 +925,27 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon for foundPeer in foundRemotePeers.0 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } @@ -977,18 +984,18 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer)) case .admin: var peers: [EnginePeer.Id: EnginePeer] = [:] if let creator = creatorPeer { peers[creator.id] = creator } peers[peer.id] = EnginePeer(peer) - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() })) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers) case .member: var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer.id] = EnginePeer(peer) - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() })) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers) } matchingMembers.append(renderedParticipant) } @@ -1042,7 +1049,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon var index = 0 for participant in foundGroupMembers { - if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil { continue } @@ -1081,7 +1088,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if peer.id == participant.peer.id { label = presentationData.strings.Channel_Management_LabelAdministrator } else { - label = presentationData.strings.Channel_Management_PromotedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + label = presentationData.strings.Channel_Management_PromotedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } } } @@ -1112,7 +1119,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon switch participant.participant { case let .member(_, _, _, banInfo, _, _): if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - label = presentationData.strings.Channel_Management_RemovedBy(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + label = presentationData.strings.Channel_Management_RemovedBy(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } default: break @@ -1126,7 +1133,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } for participant in foundMembers { - if excludeBots, let user = participant.peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = participant.peer, user.botInfo != nil { continue } @@ -1160,28 +1167,28 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon for foundPeer in foundRemotePeers.0 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer - - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } - - if !existingPeerIds.contains(peer.id) && peer is TelegramUser { + + if !existingPeerIds.contains(peer.id), case .user = peer { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index 2925bbd07f..fd86e031e0 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -144,8 +144,8 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { headerType = isChannel ? .subscribers : .groupMembers } - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in - interaction.openPeer(EnginePeer(participant.peer), participant) + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: participant.peer, chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in + interaction.openPeer(participant.peer, participant) }) case let .contact(_, peer, presence): let status: ContactsPeerItemStatus @@ -401,16 +401,16 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { let renderedParticipant: RenderedChannelParticipant switch participant { case .creator: - renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer, presences: peerView.peerPresences) + renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: EnginePeer(peer), presences: peerView.peerPresences) case .admin: var peers: [EnginePeer.Id: EnginePeer] = [:] peers[creator.id] = creator peers[peer.id] = EnginePeer(peer) - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers, presences: peerView.peerPresences) case .member: var peers: [EnginePeer.Id: EnginePeer] = [:] peers[peer.id] = EnginePeer(peer) - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: EnginePeer(peer), peers: peers, presences: peerView.peerPresences) } entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false)) @@ -439,12 +439,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } else { let membersState = Promise() - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in membersState.set(.single(state)) }) let contactsState = Promise() - contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in + contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in contactsState.set(.single(state)) }) @@ -513,7 +513,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { case .excludeNonMembers: break case .excludeBots: - if let user = participant.peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = participant.peer, user.botInfo != nil { continue contactsLoop } } @@ -555,7 +555,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { case .excludeNonMembers: break case .excludeBots: - if let user = participant.peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = participant.peer, user.botInfo != nil { continue participantsLoop } } @@ -568,7 +568,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { if participant.peer.id == context.account.peerId { continue } - if let user = participant.peer as? TelegramUser, user.botInfo != nil || user.flags.contains(.isSupport) { + if case let .user(user) = participant.peer, user.botInfo != nil || user.flags.contains(.isSupport) { continue } for filter in filters { @@ -584,7 +584,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { case .excludeNonMembers: break case .excludeBots: - if let user = participant.peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = participant.peer, user.botInfo != nil { continue participantsLoop } } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index d03290333d..6cd1c9491c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -18,7 +18,6 @@ import TelegramPermissionsUI import ItemListPeerActionItem import Markdown import UndoUI -import Postbox import OldChannelsController import MessagePriceItem @@ -477,10 +476,10 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { default: break } - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? { arguments.openPeer(participant.participant) } : { - arguments.openPeerInfo(EnginePeer(participant.peer)) + arguments.openPeerInfo(participant.peer) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -534,6 +533,8 @@ func stringForGroupPermission(strings: PresentationStrings, right: TelegramChatB return strings.Channel_BanUser_PermissionSendVoiceMessage } else if right.contains(.banSendInstantVideos) { return strings.Channel_BanUser_PermissionSendVideoMessage + } else if right.contains(.banSendReactions) { + return strings.Channel_BanUser_PermissionSendReactions } else if right.contains(.banEditRank) { if defaultPermissions { return strings.Channel_BanUser_PermissionEditOwnRank @@ -568,6 +569,8 @@ func compactStringForGroupPermission(strings: PresentationStrings, right: Telegr return strings.GroupPermission_NoSendLinks } else if right.contains(.banSendPolls) { return strings.GroupPermission_NoSendPolls + } else if right.contains(.banSendReactions) { + return strings.GroupPermission_NoSendReactions } else if right.contains(.banChangeInfo) { return strings.GroupPermission_NoChangeInfo } else if right.contains(.banAddMembers) { @@ -595,6 +598,7 @@ private let internal_allPossibleGroupPermissionList: [(TelegramChatBannedRightsF (.banSendInstantVideos, .banMembers), (.banEmbedLinks, .banMembers), (.banSendPolls, .banMembers), + (.banSendReactions, .banMembers), (.banAddMembers, .banMembers), (.banPinMessages, .pinMessages), (.banManageTopics, .manageTopics), @@ -647,6 +651,7 @@ public func banSendMediaSubList() -> [(TelegramChatBannedRightsFlags, TelegramCh (.banSendInstantVideos, .banMembers), (.banEmbedLinks, .banMembers), (.banSendPolls, .banMembers), + (.banSendReactions, .banMembers) ] } @@ -677,7 +682,7 @@ func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> Tele } } -private func channelPermissionsControllerEntries(context: AccountContext, presentationData: PresentationData, view: PeerView, state: ChannelPermissionsControllerState, participants: [RenderedChannelParticipant]?, configuration: StarsSubscriptionConfiguration) -> [ChannelPermissionsEntry] { +private func channelPermissionsControllerEntries(context: AccountContext, presentationData: PresentationData, view: EngineRawPeerView, state: ChannelPermissionsControllerState, participants: [RenderedChannelParticipant]?, configuration: StarsSubscriptionConfiguration) -> [ChannelPermissionsEntry] { var entries: [ChannelPermissionsEntry] = [] if let channel = view.peers[view.peerId] as? TelegramChannel, let participants = participants, let cachedData = view.cachedData as? CachedChannelData, let defaultBannedRights = channel.defaultBannedRights { @@ -864,7 +869,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent peersPromise.set(.single((peerId, nil))) } else { var loadCompletedCalled = false - let disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in + let disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.restricted(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in if case .loading(true) = state.loadingState, !updated { peersPromise.set(.single((peerId, nil))) } else { @@ -891,7 +896,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent let updateSendPaidMessageStarsDisposable = MetaDisposable() actionsDisposable.add(updateSendPaidMessageStarsDisposable) - let peerView = Promise() + let peerView = Promise() peerView.set(sourcePeerId.get() |> mapToSignal(context.account.viewTracker.peerView)) @@ -1051,7 +1056,14 @@ public func channelPermissionsController(context: AccountContext, updatedPresent } } } - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { channel in dismissController?() presentControllerImpl?(channelBannedMemberController(context: context, peerId: peerId, memberId: peer.id, initialParticipant: participant?.participant, updated: { _ in @@ -1095,7 +1107,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) }, openPeerInfo: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } }, openKicked: { @@ -1300,7 +1312,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent let previousParticipants = Atomic<[RenderedChannelParticipant]?>(value: nil) let viewAndParticipants = combineLatest(queue: .mainQueue(), sourcePeerId.get(), peerView.get(), peersPromise.get()) - |> mapToSignal { peerIdAndChanged, view, peers -> Signal<(PeerView, [RenderedChannelParticipant]?), NoError> in + |> mapToSignal { peerIdAndChanged, view, peers -> Signal<(EngineRawPeerView, [RenderedChannelParticipant]?), NoError> in let (peerId, changed) = peerIdAndChanged if view.peerId != peerId { return .complete() diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index d79218d64a..4cf3f97bc9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -42,11 +41,12 @@ private final class ChannelVisibilityControllerArguments { let toggleForwarding: (Bool) -> Void let updateJoinToSend: (CurrentChannelJoinToSend) -> Void let toggleApproveMembers: (Bool) -> Void + let openGuardBot: (EnginePeer.Id) -> Void let activateLink: (String) -> Void let deactivateLink: (String) -> Void let openAuction: (String) -> Void - init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { + init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, openGuardBot: @escaping (EnginePeer.Id) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { self.context = context self.updateCurrentType = updateCurrentType self.updatePublicLinkText = updatePublicLinkText @@ -61,6 +61,7 @@ private final class ChannelVisibilityControllerArguments { self.toggleForwarding = toggleForwarding self.updateJoinToSend = updateJoinToSend self.toggleApproveMembers = toggleApproveMembers + self.openGuardBot = openGuardBot self.activateLink = activateLink self.deactivateLink = deactivateLink self.openAuction = openAuction @@ -127,7 +128,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case joinToSendMembers(PresentationTheme, String, Bool) case approveMembers(PresentationTheme, String, Bool) - case approveMembersInfo(PresentationTheme, String) + case approveMembersInfo(PresentationTheme, String, EnginePeer.Id?) case forwardingHeader(PresentationTheme, String) case forwardingDisabled(PresentationTheme, String, Bool) @@ -387,8 +388,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } - case let .approveMembersInfo(lhsTheme, lhsText): - if case let .approveMembersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .approveMembersInfo(lhsTheme, lhsText, lhsGuardBotId): + if case let .approveMembersInfo(rhsTheme, rhsText, rhsGuardBotId) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsGuardBotId == rhsGuardBotId { return true } else { return false @@ -741,8 +742,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleApproveMembers(value) }) - case let .approveMembersInfo(_, text): - return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .approveMembersInfo(_, text, guardBotId): + return ItemListTextItem(presentationData: presentationData, text: guardBotId == nil ? .plain(text) : .markdown(text), sectionId: self.section, linkAction: { _ in + if let guardBotId { + arguments.openGuardBot(guardBotId) + } + }) case let .forwardingHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .forwardingDisabled(_, text, selected): @@ -781,6 +786,7 @@ private struct ChannelVisibilityControllerState: Equatable { let forwardingEnabled: Bool? let joinToSend: CurrentChannelJoinToSend? let approveMembers: Bool? + let approveMembersApplyToInvites: Bool? init() { self.selectedType = nil @@ -793,9 +799,10 @@ private struct ChannelVisibilityControllerState: Equatable { self.forwardingEnabled = nil self.joinToSend = nil self.approveMembers = nil + self.approveMembersApplyToInvites = nil } - init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: PeerId?, revokingPeerId: PeerId?, revokingPrivateLink: Bool, forwardingEnabled: Bool?, joinToSend: CurrentChannelJoinToSend?, approveMembers: Bool?) { + init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: EnginePeer.Id?, revokingPeerId: EnginePeer.Id?, revokingPrivateLink: Bool, forwardingEnabled: Bool?, joinToSend: CurrentChannelJoinToSend?, approveMembers: Bool?, approveMembersApplyToInvites: Bool?) { self.selectedType = selectedType self.editingPublicLinkText = editingPublicLinkText self.addressNameValidationStatus = addressNameValidationStatus @@ -806,6 +813,7 @@ private struct ChannelVisibilityControllerState: Equatable { self.forwardingEnabled = forwardingEnabled self.joinToSend = joinToSend self.approveMembers = approveMembers + self.approveMembersApplyToInvites = approveMembersApplyToInvites } static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool { @@ -839,51 +847,58 @@ private struct ChannelVisibilityControllerState: Equatable { if lhs.approveMembers != rhs.approveMembers { return false } + if lhs.approveMembersApplyToInvites != rhs.approveMembersApplyToInvites { + return false + } return true } func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedRevokingPeerId(_ revokingPeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedRevokingPrivateLink(_ revokingPrivateLink: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedForwardingEnabled(_ forwardingEnabled: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedJoinToSend(_ joinToSend: CurrentChannelJoinToSend?) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: joinToSend, approveMembers: self.approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites) } func withUpdatedApproveMembers(_ approveMembers: Bool) -> ChannelVisibilityControllerState { - return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: approveMembers) + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: approveMembers, approveMembersApplyToInvites: nil) + } + + func withUpdatedApproveMembersApplyToInvites(_ approveMembersApplyToInvites: Bool) -> ChannelVisibilityControllerState { + return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: approveMembersApplyToInvites) } } -private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [EnginePeer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] { +private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: EngineRawPeerView, publicChannelsToRevoke: [EnginePeer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] { var entries: [ChannelVisibilityEntry] = [] let isInitialSetup: Bool @@ -1155,11 +1170,20 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone)) entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members)) } - - if !isDiscussion || joinToSend == .members { - entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers)) - entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo)) - } + } + + let approveMembersTitle = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers + var approveMembersInfo = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembersInfo : presentationData.strings.Channel_Setup_ApproveNewSubscribersInfo + var guardBotId: EnginePeer.Id? + if let cachedChannelData = view.cachedData as? CachedChannelData, let currentGuardBotId = cachedChannelData.guardBotId, let guardBotPeer = view.peers[currentGuardBotId].flatMap(EnginePeer.init), let guardBotUsername = guardBotPeer.addressName, !guardBotUsername.isEmpty { + guardBotId = currentGuardBotId + approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string + } + + if !isGroup && selectedType == .publicChannel { + } else { + entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers)) + entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId)) } entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased())) @@ -1321,7 +1345,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa return entries } -private func effectiveChannelType(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: TelegramChannel, cachedData: CachedPeerData?) -> CurrentChannelType { +private func effectiveChannelType(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: TelegramChannel, cachedData: EngineCachedPeerData?) -> CurrentChannelType { let selectedType: CurrentChannelType if let current = state.selectedType { selectedType = current @@ -1339,7 +1363,7 @@ private func effectiveChannelType(mode: ChannelVisibilityControllerMode, state: return selectedType } -private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: EnginePeer, cachedData: CachedPeerData?) -> String? { +private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: EnginePeer, cachedData: EngineCachedPeerData?) -> String? { if case let .channel(peer) = peer { let selectedType = effectiveChannelType(mode: mode, state: state, peer: peer, cachedData: cachedData) @@ -1459,6 +1483,13 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta actionsDisposable.add(toggleRequestToJoinDisposable) let temporaryOrder = Promise<[String]?>(nil) + let activeInviteLinksCount = Atomic(value: 0) + let currentPeerIsGroup = Atomic(value: true) + let activeInviteLinksContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: false) + actionsDisposable.add((activeInviteLinksContext.state + |> deliverOnMainQueue).start(next: { state in + let _ = activeInviteLinksCount.swap(state.hasLoadedOnce ? state.count : 0) + })) let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in if type == .publicChannel { @@ -1475,7 +1506,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let (limits, premiumLimits) = data let isPremium = accountPeer?.isPremium ?? false - let hasAdditionalUsernames = (peer?._asPeer().usernames.firstIndex(where: { !$0.flags.contains(.isEditable) }) ?? nil) != nil + let hasAdditionalUsernames = (peer?.usernames.firstIndex(where: { !$0.flags.contains(.isEditable) }) ?? nil) != nil if let peers = peers { let count = Int32(peers.count) @@ -1600,10 +1631,17 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)) |> deliverOnMainQueue).start(next: { invite in if let invite = invite { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -1619,10 +1657,17 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta }, action: { _, f in f(.dismissWithoutContent) - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { peer in let isGroup: Bool - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } else { isGroup = true @@ -1684,9 +1729,34 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta return state.withUpdatedJoinToSend(value) } }, toggleApproveMembers: { value in - updateState { state in - return state.withUpdatedApproveMembers(value) + let updateApproveMembers: (Bool) -> Void = { applyToInvites in + updateState { state in + return state.withUpdatedApproveMembers(value).withUpdatedApproveMembersApplyToInvites(applyToInvites) + } } + + let inviteLinksCount = activeInviteLinksCount.with { $0 } + if inviteLinksCount > 0 { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let isGroup = currentPeerIsGroup.with { $0 } + let approvalTitle = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers + let action = value ? presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksEnable : presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksDisable + let peerType = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksPeerGroup : presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksPeerChannel + let text = presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksText(action, approvalTitle, "\(inviteLinksCount)", peerType).string + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksTitle, text: text, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { + updateApproveMembers(false) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { + updateApproveMembers(true) + }) + ], parseMarkdown: true), nil) + } else { + updateApproveMembers(false) + } + }, openGuardBot: { guardBotId in + let controller = channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: guardBotId, initialParticipant: nil, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: { _ in }) + pushControllerImpl?(controller) }, activateLink: { name in dismissInputImpl?() let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) @@ -1823,6 +1893,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } else if let _ = peer as? TelegramGroup { isGroup = true } + let _ = currentPeerIsGroup.swap(isGroup) var rightNavigationButton: ItemListNavigationButton? if case .revokeNames = mode { @@ -1866,7 +1937,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta isInitialSetup = false } - rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { + rightNavigationButton = ItemListNavigationButton(content: isInitialSetup ? .text(presentationData.strings.Common_Next) : .icon(.done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { var updatedAddressNameValue: String? updateState { state in updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .channel(peer), cachedData: view.cachedData) @@ -1886,7 +1957,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } if let updatedApproveMembers = state.approveMembers { - toggleRequestToJoinDisposable.set(context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: updatedApproveMembers).start()) + toggleRequestToJoinDisposable.set(context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: updatedApproveMembers, guardBotId: nil, applyToInvites: state.approveMembersApplyToInvites ?? false).start()) } if let updatedAddressNameValue = updatedAddressNameValue { @@ -1962,7 +2033,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { var updatedAddressNameValue: String? updateState { state in updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .legacyGroup(peer), cachedData: nil) @@ -1981,12 +2052,12 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta _ = ApplicationSpecificNotice.markAsSeenSetPublicChannelLink(accountManager: context.sharedContext.accountManager).start() let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in return context.engine.peers.updateAddressName(domain: .peer(upgradedPeerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue) |> `catch` { _ -> Signal in return .complete() } - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(upgradedPeerId)) @@ -2050,7 +2121,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta case .initialSetup: leftNavigationButton = nil case .generic, .privateLink, .revokeNames: - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) } @@ -2291,7 +2362,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta peerIds = peerIdsValue } - let filteredPeerIds = peerIds.compactMap({ peerId -> PeerId? in + let filteredPeerIds = peerIds.compactMap({ peerId -> EnginePeer.Id? in if case let .peer(id) = peerId { return id } else { diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 75985d1823..c7c27627a6 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -24,7 +24,6 @@ import PhoneNumberFormat import UndoUI import GalleryUI import PeerAvatarGalleryUI -import Postbox import ContextUI private enum DeviceContactInfoAction { @@ -410,7 +409,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { if let context = arguments.context as? ShareControllerAppAccountContext { itemContext = .accountContext(context.context) } else { - itemContext = .other(accountPeerId: arguments.context.accountPeerId, postbox: arguments.context.stateManager.postbox, network: arguments.context.stateManager.network) + itemContext = .other(accountPeerId: arguments.context.accountPeerId, stateManager: arguments.context.stateManager) } return ItemListAvatarAndNameInfoItem(itemContext: itemContext, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) @@ -846,7 +845,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, var peerPhoneNumber: String? var firstName = contactData.basicData.firstName var lastName = contactData.basicData.lastName - if let peer = peer as? TelegramUser { + if case let .user(peer) = peer { firstName = peer.firstName ?? "" lastName = peer.lastName ?? "" if let phone = peer.phone { @@ -939,11 +938,11 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, var shareViaException = false switch subject { case let .vcard(peer, id, data): - contactData = .single((peer.flatMap(EnginePeer.init), id, data)) + contactData = .single((peer, id, data)) case let .filter(peer, id, data, _): - contactData = .single((peer.flatMap(EnginePeer.init), id, data)) + contactData = .single((peer, id, data)) case let .create(peer, data, share, shareViaExceptionValue, _): - contactData = .single((peer.flatMap(EnginePeer.init), nil, data)) + contactData = .single((peer, nil, data)) isShare = share shareViaException = shareViaExceptionValue } @@ -1090,7 +1089,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, case .vcard: break case .filter, .create: - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?(true) cancelled?() }) @@ -1102,7 +1101,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, } else if case let .filter(_, _, _, completion) = subject { let filteredData = filteredContactData(contactData: peerAndContactData.2, excludedComponents: state.excludedComponents) rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.ShareMenu_Send), style: .bold, enabled: !filteredData.basicData.phoneNumbers.isEmpty, action: { - completion(peerAndContactData.0?._asPeer(), filteredData) + completion(peerAndContactData.0, filteredData) dismissImpl?(true) }) } else if case let .create(createForPeer, _, _, _, completion) = subject { @@ -1132,7 +1131,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, composedContactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: filteredPhoneNumbers), middleName: filteredData.middleName, prefix: filteredData.prefix, suffix: filteredData.suffix, organization: filteredData.organization, jobTitle: filteredData.jobTitle, department: filteredData.department, emailAddresses: filteredData.emailAddresses, urls: urls, addresses: filteredData.addresses, birthdayDate: filteredData.birthdayDate, socialProfiles: filteredData.socialProfiles, instantMessagingProfiles: filteredData.instantMessagingProfiles, note: filteredData.note) } - rightNavigationButton = ItemListNavigationButton(content: .text(isShare ? presentationData.strings.Common_Done : presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: { + rightNavigationButton = ItemListNavigationButton(content: isShare ? .icon(.done) : .text(presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: { if let composedContactData = composedContactData { guard let context = (context as? ShareControllerAppAccountContext)?.context else { return @@ -1221,7 +1220,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, return state } if let contactIdAndData = contactIdAndData { - completion(contactIdAndData.2?._asPeer(), contactIdAndData.0, contactIdAndData.1) + completion(contactIdAndData.2, contactIdAndData.0, contactIdAndData.1) } completed?() dismissImpl?(true) @@ -1286,7 +1285,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, return } addContactToExisting(context: accountContext, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in - replaceControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) + replaceControllerImpl?(deviceContactInfoController(context: context, environment: environment, subject: .vcard(peer, contactId, contactData), completed: nil, cancelled: nil)) }) } openChatImpl = { [weak controller] peerId in @@ -1416,7 +1415,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let dataSignal: Signal<(EnginePeer?, DeviceContactStableId?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } dataSignal = (context.sharedContext.contactDataManager?.basicData() ?? .single([:])) @@ -1440,7 +1439,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let _ = (dataSignal |> deliverOnMainQueue).start(next: { peer, stableId in guard let stableId = stableId else { - parentController.present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in + parentController.present(deviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in }), completed: nil, cancelled: nil), in: .window(.root)) return } @@ -1486,7 +1485,7 @@ func addContactOptionsController(context: AccountContext, peer: EnginePeer?, con controller.setItemGroups([ ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in - controller?.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + controller?.present(context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: context), environment: ShareControllerAppEnvironment(sharedContext: context.sharedContext), subject: .create(peer: peer, contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissAction() }), diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift index eb85784b40..5fb1aaf5d9 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchNavigationContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift index 4e5a223763..b7c3ca891f 100644 --- a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift +++ b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -117,7 +116,7 @@ private func groupPreHistorySetupEntries(isSupergroup: Bool, presentationData: P return entries } -public func groupPreHistorySetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void) -> ViewController { +public func groupPreHistorySetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void) -> ViewController { let statePromise = ValuePromise(GroupPreHistorySetupState(), ignoreRepeated: true) let stateValue = Atomic(value: GroupPreHistorySetupState()) let updateState: ((GroupPreHistorySetupState) -> GroupPreHistorySetupState) -> Void = { f in @@ -145,14 +144,14 @@ public func groupPreHistorySetupController(context: AccountContext, updatedPrese |> deliverOnMainQueue |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, Any)) in let defaultValue: Bool = (view.cachedData as? CachedChannelData)?.flags.contains(.preHistoryEnabled) ?? false - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) var rightNavigationButton: ItemListNavigationButton? if state.applyingSetting { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { var value: Bool? updateState { state in var state = state @@ -163,12 +162,12 @@ public func groupPreHistorySetupController(context: AccountContext, updatedPrese if let value = value, value != defaultValue { if peerId.namespace == Namespaces.Peer.CloudGroup { let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in return context.engine.peers.updateChannelHistoryAvailabilitySettingsInteractively(peerId: upgradedPeerId, historyAvailableForNewMembers: value) |> `catch` { _ -> Signal in return .complete() } - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(upgradedPeerId)) diff --git a/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift b/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift index c45e08ed99..0548e30b82 100644 --- a/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListCallListItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -14,12 +13,12 @@ import TextFormat public class ItemListCallListItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let dateTimeFormat: PresentationDateTimeFormat - let messages: [Message] + let messages: [EngineMessage] public let sectionId: ItemListSectionId let style: ItemListStyle let displayDecorations: Bool - - public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, messages: [Message], sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true) { + + public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true) { self.presentationData = presentationData self.dateTimeFormat = dateTimeFormat self.messages = messages @@ -62,7 +61,7 @@ public class ItemListCallListItem: ListViewItem, ItemListItem { } } -private func stringForCallType(message: Message, strings: PresentationStrings) -> String { +private func stringForCallType(message: EngineMessage, strings: PresentationStrings) -> String { var string = "" for media in message.media { switch media { diff --git a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift index 64133463f7..3683dcf37b 100644 --- a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift @@ -383,7 +383,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { imageNode.update(size: imageFitSize) } - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) if let switchView = strongSelf.switchNode.view as? UISwitch { if strongSelf.switchNode.bounds.size.width.isZero { switchView.sizeToFit() diff --git a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift index a07c119fb2..a673b6fb51 100644 --- a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift +++ b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -274,14 +273,14 @@ private struct PeerAllowedReactionListControllerState: Equatable { private func peerAllowedReactionListControllerEntries( presentationData: PresentationData, availableReactions: AvailableReactions?, - peer: Peer?, - cachedData: CachedPeerData?, + peer: EnginePeer?, + cachedData: EngineCachedPeerData?, state: PeerAllowedReactionListControllerState ) -> [PeerAllowedReactionListControllerEntry] { var entries: [PeerAllowedReactionListControllerEntry] = [] - + if let peer = peer, let availableReactions = availableReactions, let allowedReactions = state.updatedAllowedReactions, let mode = state.updatedMode { - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { entries.append(.allowSwitch(text: presentationData.strings.PeerInfo_AllowedReactions_AllowAllText, value: mode != .empty)) entries.append(.itemsHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader)) @@ -301,7 +300,7 @@ private func peerAllowedReactionListControllerEntries( entries.append(.allowNone(text: presentationData.strings.PeerInfo_AllowedReactions_OptionNoReactions, isEnabled: mode == .empty)) let allInfoText: String - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = peer, case .broadcast = peer.info { switch mode { case .all: allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionAllInfo @@ -343,7 +342,7 @@ private func peerAllowedReactionListControllerEntries( public func peerAllowedReactionListController( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, - peerId: PeerId + peerId: EnginePeer.Id ) -> ViewController { let statePromise = ValuePromise(PeerAllowedReactionListControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: PeerAllowedReactionListControllerState()) @@ -476,7 +475,7 @@ public func peerAllowedReactionListController( let entries = peerAllowedReactionListControllerEntries( presentationData: presentationData, availableReactions: availableReactions, - peer: peerView.peers[peerId], + peer: peerView.peers[peerId].flatMap(EnginePeer.init), cachedData: peerView.cachedData, state: state ) diff --git a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift index ec009e1670..6e4983cbcf 100644 --- a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift +++ b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift @@ -187,14 +187,14 @@ public func peerAutoremoveSetupScreen(context: AccountContext, updatedPresentati let peer = view.peers[view.peerId] - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) var rightNavigationButton: ItemListNavigationButton? if state.applyingSetting { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { var changedValue: Int32? updateState { state in var state = state diff --git a/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift b/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift deleted file mode 100644 index 396e10a4d6..0000000000 --- a/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift +++ /dev/null @@ -1,127 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import UIKit -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import TelegramStringFormatting -import AccountContext -import UIKitRuntimeUtils - -final class PeerBanTimeoutController: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, currentValue: Int32, applyValue: @escaping (Int32?) -> Void) { - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self._ready.set(.single(true)) - - self.presentationDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - - var updatedValue = currentValue - var items: [ActionSheetItem] = [] - items.append(PeerBanTimeoutActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in - updatedValue = value - })) - items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in - self?.dismissAnimated() - applyValue(updatedValue) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class PeerBanTimeoutActionSheetItem: ActionSheetItem { - let strings: PresentationStrings - - let currentValue: Int32 - let valueChanged: (Int32) -> Void - - init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.strings = strings - self.currentValue = roundDateToDays(currentValue) - self.valueChanged = valueChanged - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return PeerBanTimeoutActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class PeerBanTimeoutActionSheetItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - - private let valueChanged: (Int32) -> Void - private let pickerView: UIDatePicker - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme - self.strings = strings - self.valueChanged = valueChanged - - UILabel.setDateLabel(theme.primaryTextColor) - - self.pickerView = UIDatePicker() - self.pickerView.datePickerMode = .countDownTimer - self.pickerView.datePickerMode = .date - self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue))) - self.pickerView.locale = localeWithStrings(strings) - self.pickerView.minimumDate = Date() - self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) - if #available(iOS 13.4, *) { - self.pickerView.preferredDatePickerStyle = .wheels - } - self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") - - super.init(theme: theme) - - self.view.addSubview(self.pickerView) - self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 216.0) - - self.pickerView.frame = CGRect(origin: CGPoint(), size: size) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc private func datePickerUpdated() { - self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970))) - } -} diff --git a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift index 5bb4a86ac6..ce2b21fb4a 100644 --- a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift +++ b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift @@ -110,11 +110,11 @@ public func phoneLabelController(context: AccountContext, currentLabel: String, let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { arguments.cancel() }) - let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { arguments.complete() }) diff --git a/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift b/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift index ebafd19083..6d0510add2 100644 --- a/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift +++ b/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift @@ -24,9 +24,10 @@ public final class SecretChatKeyController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.navigationPresentation = .modal + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.title = self.presentationData.strings.EncryptionKey_Title diff --git a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift index 985a4bf850..7f75d032ee 100644 --- a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift @@ -73,6 +73,8 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { override func didLoad() { super.didLoad() + self.scrollNode.view.scrollsToTop = false + self.infoNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.infoTap(_:)))) } diff --git a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift index c3ff6f4c80..33b4bb07be 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift @@ -284,7 +284,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) } }) } diff --git a/submodules/PeersNearbyIconNode/BUILD b/submodules/PeersNearbyIconNode/BUILD deleted file mode 100644 index bcb7f6d667..0000000000 --- a/submodules/PeersNearbyIconNode/BUILD +++ /dev/null @@ -1,21 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "PeersNearbyIconNode", - module_name = "PeersNearbyIconNode", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/LegacyComponents:LegacyComponents", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift b/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift deleted file mode 100644 index 22b2a8f99b..0000000000 --- a/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift +++ /dev/null @@ -1,209 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import TelegramPresentationData -import LegacyComponents - -private final class PeersNearbyIconWavesNodeParams: NSObject { - let color: UIColor - let progress: CGFloat - - init(color: UIColor, progress: CGFloat) { - self.color = color - self.progress = progress - - super.init() - } -} - -private func degToRad(_ degrees: CGFloat) -> CGFloat { - return degrees * CGFloat.pi / 180.0 -} - -public final class PeersNearbyIconWavesNode: ASDisplayNode { - public var color: UIColor { - didSet { - self.setNeedsDisplay() - } - } - - private var effectiveProgress: CGFloat = 0.0 { - didSet { - self.setNeedsDisplay() - } - } - - public init(color: UIColor) { - self.color = color - - super.init() - - self.isLayerBacked = true - self.isOpaque = false - } - - override public func willEnterHierarchy() { - super.willEnterHierarchy() - - self.pop_removeAnimation(forKey: "indefiniteProgress") - - let animation = POPBasicAnimation() - animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in - property?.readBlock = { node, values in - values?.pointee = (node as! PeersNearbyIconWavesNode).effectiveProgress - } - property?.writeBlock = { node, values in - (node as! PeersNearbyIconWavesNode).effectiveProgress = values!.pointee - } - property?.threshold = 0.01 - }) as! POPAnimatableProperty) - animation.fromValue = CGFloat(0.0) as NSNumber - animation.toValue = CGFloat(1.0) as NSNumber - animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - animation.duration = 3.5 - animation.repeatForever = true - self.pop_add(animation, forKey: "indefiniteProgress") - } - - override public func didExitHierarchy() { - super.didExitHierarchy() - - self.pop_removeAnimation(forKey: "indefiniteProgress") - } - - override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { - let t = CACurrentMediaTime() - let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0 - return PeersNearbyIconWavesNodeParams(color: self.color, progress: value) - } - - @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { - let context = UIGraphicsGetCurrentContext()! - - if !isRasterizing { - context.setBlendMode(.copy) - context.setFillColor(UIColor.clear.cgColor) - context.fill(bounds) - } - - if let parameters = parameters as? PeersNearbyIconWavesNodeParams { - let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0) - let radius: CGFloat = bounds.width * 0.3333 - let range: CGFloat = (bounds.width - radius * 2.0) / 2.0 - - context.setFillColor(parameters.color.cgColor) - - let draw: (CGContext, CGFloat) -> Void = { context, pos in - let path = CGMutablePath() - - let pathRadius: CGFloat = bounds.width * 0.3333 + range * pos - path.addEllipse(in: CGRect(x: center.x - pathRadius, y: center.y - pathRadius, width: pathRadius * 2.0, height: pathRadius * 2.0)) - - let strokedPath = path.copy(strokingWithWidth: 1.0, lineCap: .round, lineJoin: .miter, miterLimit: 10.0) - context.addPath(strokedPath) - context.fillPath() - } - - let position = parameters.progress - var alpha = position / 0.5 - if alpha > 1.0 { - alpha = 2.0 - alpha - } - context.setAlpha(alpha * 0.7) - - draw(context, position) - - var progress = parameters.progress + 0.3333 - if progress > 1.0 { - progress = progress - 1.0 - } - - var largerPos = progress - var largerAlpha = largerPos / 0.5 - if largerAlpha > 1.0 { - largerAlpha = 2.0 - largerAlpha - } - context.setAlpha(largerAlpha * 0.7) - - draw(context, largerPos) - - progress = parameters.progress + 0.6666 - if progress > 1.0 { - progress = progress - 1.0 - } - - largerPos = progress - largerAlpha = largerPos / 0.5 - if largerAlpha > 1.0 { - largerAlpha = 2.0 - largerAlpha - } - context.setAlpha(largerAlpha * 0.7) - - draw(context, largerPos) - } - } -} - -private func generateIcon(size: CGSize, color: UIColor, contentColor: UIColor) -> UIImage { - return generateImage(size, rotatedContext: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - context.clear(bounds) - - context.setFillColor(color.cgColor) - context.fillEllipse(in: bounds) - - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.scaleBy(x: size.width / 120.0, y: size.height / 120.0) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - context.translateBy(x: 0.0, y: 6.0) - context.setFillColor(contentColor.cgColor) - - if size.width == 120.0 { - context.translateBy(x: 30.0, y: 30.0) - } - - let _ = try? drawSvgPath(context, path: "M27.8628211,52.2347452 L27.8628211,27.1373017 L2.76505663,27.1373017 C1.55217431,27.1373017 0.568938916,26.1540663 0.568938916,24.941184 C0.568938916,24.0832172 1.06857435,23.3038117 1.84819149,22.9456161 L51.2643819,0.241311309 C52.586928,-0.366333451 54.1516568,0.213208572 54.7593016,1.53575465 C55.0801868,2.23416513 55.080181,3.03785964 54.7592857,3.7362655 L32.0544935,53.1516391 C31.548107,54.2537536 30.2441593,54.7366865 29.1420449,54.2302999 C28.3624433,53.8720978 27.8628211,53.0927006 27.8628211,52.2347452 Z ") - })! -} - -public final class PeersNearbyIconNode: ASDisplayNode { - private var theme: PresentationTheme - - private var iconNode: ASImageNode - private var wavesNode: PeersNearbyIconWavesNode - - public init(theme: PresentationTheme) { - self.theme = theme - - self.iconNode = ASImageNode() - self.iconNode.isOpaque = false - self.wavesNode = PeersNearbyIconWavesNode(color: theme.list.itemAccentColor) - - super.init() - - self.addSubnode(self.iconNode) - self.addSubnode(self.wavesNode) - } - - public func updateTheme(_ theme: PresentationTheme) { - guard self.theme !== theme else { - return - } - self.theme = theme - - self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor) - self.wavesNode.color = theme.list.itemAccentColor - } - - override public func layout() { - super.layout() - - if let image = self.iconNode.image, image.size.width == self.bounds.width { - } else { - self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor) - } - self.iconNode.frame = self.bounds - self.wavesNode.frame = self.bounds.insetBy(dx: -self.bounds.width * 0.3, dy: -self.bounds.height * 0.3) - } -} diff --git a/submodules/PeersNearbyUI/BUILD b/submodules/PeersNearbyUI/BUILD deleted file mode 100644 index 4ebbdcdbb5..0000000000 --- a/submodules/PeersNearbyUI/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "PeersNearbyUI", - module_name = "PeersNearbyUI", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/TelegramCore:TelegramCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/AccountContext:AccountContext", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/ItemListUI:ItemListUI", - "//submodules/OverlayStatusController:OverlayStatusController", - "//submodules/DeviceLocationManager:DeviceLocationManager", - "//submodules/AlertUI:AlertUI", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/ItemListPeerItem:ItemListPeerItem", - "//submodules/TelegramPermissionsUI:TelegramPermissionsUI", - "//submodules/ItemListPeerActionItem:ItemListPeerActionItem", - "//submodules/PeersNearbyIconNode:PeersNearbyIconNode", - "//submodules/Geocoding:Geocoding", - "//submodules/AppBundle:AppBundle", - "//submodules/AnimatedStickerNode:AnimatedStickerNode", - "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", - "//submodules/TelegramStringFormatting:TelegramStringFormatting", - "//submodules/TelegramNotices:TelegramNotices", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift deleted file mode 100644 index 28fc481656..0000000000 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift +++ /dev/null @@ -1,659 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import SwiftSignalKit -import TelegramCore -import MapKit -import TelegramPresentationData -import TelegramUIPreferences -import ItemListUI -import PresentationDataUtils -import OverlayStatusController -import DeviceLocationManager -import AccountContext -import AlertUI -import PresentationDataUtils -import ItemListPeerItem -import TelegramPermissionsUI -import ItemListPeerActionItem -import Geocoding -import AppBundle -import ContextUI -import TelegramNotices -import TelegramStringFormatting - -private let maxUsersDisplayedLimit: Int32 = 5 - -private struct PeerNearbyEntry { - let peer: EnginePeer - let memberCount: Int32? - let expires: Int32 - let distance: Int32 -} - -private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool { - if let lhs = lhs, let rhs = rhs { - return lhs.peer == rhs.peer && lhs.expires == rhs.expires && lhs.distance == rhs.distance - } else { - return (lhs != nil) == (rhs != nil) - } -} - -private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNearbyEntry]) -> Bool { - if lhs.count != rhs.count { - return false - } - for i in 0 ..< lhs.count { - if lhs[i].peer != rhs[i].peer || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance { - return false - } - } - return true -} - -private final class PeersNearbyControllerArguments { - let context: AccountContext - let toggleVisibility: (Bool) -> Void - let openProfile: (EnginePeer, Int32) -> Void - let openChat: (EnginePeer) -> Void - let openCreateGroup: (Double, Double, String?) -> Void - let contextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void - let expandUsers: () -> Void - - init(context: AccountContext, toggleVisibility: @escaping (Bool) -> Void, openProfile: @escaping (EnginePeer, Int32) -> Void, openChat: @escaping (EnginePeer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void, contextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, expandUsers: @escaping () -> Void) { - self.context = context - self.toggleVisibility = toggleVisibility - self.openProfile = openProfile - self.openChat = openChat - self.openCreateGroup = openCreateGroup - self.contextAction = contextAction - self.expandUsers = expandUsers - } -} - -private enum PeersNearbySection: Int32 { - case header - case users - case groups - case channels -} - -private enum PeersNearbyEntry: ItemListNodeEntry { - case header(PresentationTheme, String) - - case usersHeader(PresentationTheme, String, Bool) - case empty(PresentationTheme, String) - case visibility(PresentationTheme, String, Bool) - case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry) - case expand(PresentationTheme, String) - - case groupsHeader(PresentationTheme, String, Bool) - case createGroup(PresentationTheme, String, Double?, Double?, String?) - case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool) - - case channelsHeader(PresentationTheme, String) - case channel(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool) - - var section: ItemListSectionId { - switch self { - case .header: - return PeersNearbySection.header.rawValue - case .usersHeader, .empty, .visibility, .user, .expand: - return PeersNearbySection.users.rawValue - case .groupsHeader, .createGroup, .group: - return PeersNearbySection.groups.rawValue - case .channelsHeader, .channel: - return PeersNearbySection.channels.rawValue - } - } - - var stableId: Int32 { - switch self { - case .header: - return 0 - case .usersHeader: - return 1 - case .empty: - return 2 - case .visibility: - return 3 - case let .user(index, _, _, _, _, _): - return 4 + index - case .expand: - return 1000 - case .groupsHeader: - return 1001 - case .createGroup: - return 1002 - case let .group(index, _, _, _, _, _, _): - return 1003 + index - case .channelsHeader: - return 2000 - case let .channel(index, _, _, _, _, _, _): - return 2001 + index - } - } - - static func ==(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool { - switch lhs { - case let .header(lhsTheme, lhsText): - if case let .header(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .usersHeader(lhsTheme, lhsText, lhsLoading): - if case let .usersHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading { - return true - } else { - return false - } - case let .empty(lhsTheme, lhsText): - if case let .empty(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .visibility(lhsTheme, lhsText, lhsStop): - if case let .visibility(rhsTheme, rhsText, rhsStop) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStop == rhsStop { - return true - } else { - return false - } - - case let .user(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer): - if case let .user(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) { - return true - } else { - return false - } - case let .expand(lhsTheme, lhsText): - if case let .expand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .groupsHeader(lhsTheme, lhsText, lhsLoading): - if case let .groupsHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading { - return true - } else { - return false - } - case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude, lhsAddress): - if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude && lhsAddress == rhsAddress { - return true - } else { - return false - } - case let .group(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted): - if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted { - return true - } else { - return false - } - case let .channelsHeader(lhsTheme, lhsText): - if case let .channelsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { - return true - } else { - return false - } - case let .channel(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted): - if case let .channel(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted { - return true - } else { - return false - } - } - } - - static func <(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool { - return lhs.stableId < rhs.stableId - } - - func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { - let arguments = arguments as! PeersNearbyControllerArguments - switch self { - case let .header(theme, text): - return PeersNearbyHeaderItem(context: arguments.context, theme: theme, text: text, sectionId: self.section) - case let .usersHeader(_, text, loading): - return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section) - case let .empty(theme, text): - return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks) - case let .visibility(theme, title, stop): - return ItemListPeerActionItem(presentationData: presentationData, icon: stop ? PresentationResourcesItemList.makeInvisibleIcon(theme) : PresentationResourcesItemList.makeVisibleIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, color: stop ? .destructive : .accent, editing: false, action: { - arguments.toggleVisibility(!stop) - }) - case let .user(_, _, strings, dateTimeFormat, nameDisplayOrder, peer): - var text = strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string - let isSelfPeer = peer.peer.id == arguments.context.account.peerId - if isSelfPeer { - text = strings.PeopleNearby_VisibleUntil(humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: peer.expires).string).string - } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: { - if !isSelfPeer { - arguments.openProfile(peer.peer, peer.distance) - } - }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil, hasTopGroupInset: false, tag: nil) - case let .expand(theme, title): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: { - arguments.expandUsers() - }) - case let .groupsHeader(_, text, loading): - return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section) - case let .createGroup(theme, title, latitude, longitude, address): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: { - if let latitude = latitude, let longitude = longitude { - arguments.openCreateGroup(latitude, longitude, address) - } - }) - case let .group(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted): - var text: ItemListPeerItemText - if let memberCount = peer.memberCount { - text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)", .secondary) - } else { - text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary) - } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: { - arguments.openChat(peer.peer) - }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in - arguments.contextAction(peer.peer, node, gesture) - }, hasTopGroupInset: false, tag: nil) - case let .channelsHeader(_, text): - return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .channel(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted): - var text: ItemListPeerItemText - if let memberCount = peer.memberCount { - text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(strings.Conversation_StatusSubscribers(memberCount))", .secondary) - } else { - text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary) - } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: { - arguments.openChat(peer.peer) - }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in - arguments.contextAction(peer.peer, node, gesture) - }, hasTopGroupInset: false, tag: nil) - } - } -} - -private struct PeersNearbyData: Equatable { - let latitude: Double - let longitude: Double - let address: String? - let visible: Bool - let accountPeerId: EnginePeer.Id - let users: [PeerNearbyEntry] - let groups: [PeerNearbyEntry] - let channels: [PeerNearbyEntry] - - init(latitude: Double, longitude: Double, address: String?, visible: Bool, accountPeerId: EnginePeer.Id, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) { - self.latitude = latitude - self.longitude = longitude - self.address = address - self.visible = visible - self.accountPeerId = accountPeerId - self.users = users - self.groups = groups - self.channels = channels - } - - static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool { - return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.address == rhs.address && lhs.visible == rhs.visible && lhs.accountPeerId == rhs.accountPeerId && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels) - } -} - -private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNearbyState, presentationData: PresentationData, displayLoading: Bool, expanded: Bool, chatLocation: ChatLocation?) -> [PeersNearbyEntry] { - var entries: [PeersNearbyEntry] = [] - - entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_DiscoverDescription)) - entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased(), displayLoading && data == nil)) - - let visible = state.visibilityExpires != nil - entries.append(.visibility(presentationData.theme, visible ? presentationData.strings.PeopleNearby_MakeInvisible : presentationData.strings.PeopleNearby_MakeVisible, visible)) - - if let data = data, !data.users.isEmpty { - var index: Int32 = 0 - var users = data.users.filter { $0.peer.id != data.accountPeerId } - var effectiveExpanded = expanded - if users.count > maxUsersDisplayedLimit && !expanded { - users = Array(users.prefix(Int(maxUsersDisplayedLimit))) - } else { - effectiveExpanded = true - } - - for user in users { - entries.append(.user(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, user)) - index += 1 - } - - if !effectiveExpanded { - entries.append(.expand(presentationData.theme, presentationData.strings.PeopleNearby_ShowMorePeople(Int32(data.users.count) - maxUsersDisplayedLimit))) - } - } - - var highlightedPeerId: EnginePeer.Id? - if let chatLocation = chatLocation, case let .peer(peerId) = chatLocation { - highlightedPeerId = peerId - } - - entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased(), displayLoading && data == nil)) - entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude, data?.address)) - if let data = data, !data.groups.isEmpty { - var i: Int32 = 0 - for group in data.groups { - entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group, highlightedPeerId == group.peer.id)) - i += 1 - } - } - - if let data = data, !data.channels.isEmpty { - var i: Int32 = 0 - for channel in data.channels { - entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel, highlightedPeerId == channel.peer.id)) - i += 1 - } - } - - return entries -} - -private final class ContextControllerContentSourceImpl: ContextControllerContentSource { - let controller: ViewController - weak var sourceNode: ASDisplayNode? - - let navigationController: NavigationController? = nil - - let passthroughTouches: Bool = true - - init(controller: ViewController, sourceNode: ASDisplayNode?) { - self.controller = controller - self.sourceNode = sourceNode - } - - func transitionInfo() -> ContextControllerTakeControllerInfo? { - let sourceNode = self.sourceNode - return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in - if let sourceNode = sourceNode { - return (sourceNode.view, sourceNode.bounds) - } else { - return nil - } - }) - } - - func animatedIn() { - } -} - -private func peerNearbyContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, present: @escaping (ViewController) -> Void) -> Signal<[ContextMenuItem], NoError> { - return .single([]) -} - -private class PeersNearbyControllerImpl: ItemListController { - fileprivate let chatLocation = Promise(nil) - - public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { - if self.isNodeLoaded { - self.chatLocation.set(.single(data as? ChatLocation)) - } - } -} - -public func peersNearbyController(context: AccountContext) -> ViewController { - var pushControllerImpl: ((ViewController) -> Void)? - var replaceTopControllerImpl: ((ViewController) -> Void)? - var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? - var presentInGlobalOverlayImpl: ((ViewController) -> Void)? - var navigateToProfileImpl: ((EnginePeer, Int32) -> Void)? - var navigateToChatImpl: ((EnginePeer) -> Void)? - - let actionsDisposable = DisposableSet() - let checkCreationAvailabilityDisposable = MetaDisposable() - actionsDisposable.add(checkCreationAvailabilityDisposable) - - let dataPromise = Promise(nil) - let addressPromise = Promise(nil) - let expandedPromise = ValuePromise(false) - - let chatLocationPromise = Promise(nil) - - let coordinatePromise = Promise(nil) - coordinatePromise.set(.single(nil) |> then(currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0))) - - let arguments = PeersNearbyControllerArguments(context: context, toggleVisibility: { visible in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - if visible { - presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.PeopleNearby_MakeVisibleTitle, text: presentationData.strings.PeopleNearby_MakeVisibleDescription, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { - let _ = (coordinatePromise.get() - |> deliverOnMainQueue).start(next: { coordinate in - if let coordinate = coordinate { - let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .visible(latitude: coordinate.latitude, longitude: coordinate.longitude), background: false).start() - } - }) - })]), nil) - - - } else { - let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .invisible, background: false).start() - } - }, openProfile: { peer, distance in - navigateToProfileImpl?(peer, distance) - }, openChat: { peer in - navigateToChatImpl?(peer) - }, openCreateGroup: { latitude, longitude, address in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - var cancelImpl: (() -> Void)? - let progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - presentControllerImpl?(controller, nil) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.5, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - cancelImpl = { - checkCreationAvailabilityDisposable.set(nil) - } - checkCreationAvailabilityDisposable.set((context.engine.peers.checkPublicChannelCreationAvailability(location: true) - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { available in - if available { - let controller = PermissionController(context: context, splashScreen: true) - controller.navigationPresentation = .modalInLargeLayout - controller.setState(.custom(icon: .icon(PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Location/LocalGroupLightIcon"), dark: UIImage(bundleImageName: "Location/LocalGroupDarkIcon"))), title: presentationData.strings.LocalGroup_Title, subtitle: address, text: presentationData.strings.LocalGroup_Text, buttonTitle: presentationData.strings.LocalGroup_ButtonTitle, secondaryButtonTitle: nil, footerText: presentationData.strings.LocalGroup_IrrelevantWarning), animated: false) - controller.proceed = { result in - let controller = context.sharedContext.makeCreateGroupController(context: context, peerIds: [], initialTitle: nil, mode: .locatedGroup(latitude: latitude, longitude: longitude, address: address), completion: nil) - controller.navigationPresentation = .modalInLargeLayout - replaceTopControllerImpl?(controller) - } - pushControllerImpl?(controller) - } else { - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreateGroup_ErrorLocatedGroupsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) - } - })) - }, contextAction: { peer, node, gesture in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil) - chatController.canReadHistory.set(false) - let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in - presentControllerImpl?(c, nil) - }) |> map { ContextController.Items(content: .list($0), animationCache: nil) }, gesture: gesture) - presentInGlobalOverlayImpl?(contextController) - }, expandUsers: { - expandedPromise.set(true) - }) - - let dataSignal: Signal = coordinatePromise.get() - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs?.latitude == rhs?.latitude && lhs?.longitude == rhs?.longitude - }) - |> mapToSignal { coordinate -> Signal in - guard let coordinate = coordinate else { - return .single(nil) - /*let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: nil) - return peersNearbyContext.get() - |> map { peersNearby -> PeersNearbyData in - var isVisible = false - if let peersNearby { - for peer in peersNearby { - if case .selfPeer = peer { - isVisible = true - break - } - } - } - return PeersNearbyData(latitude: 0.0, longitude: 0.0, address: nil, visible: isVisible, accountPeerId: context.account.peerId, users: [], groups: [], channels: []) - }*/ - } - - return Signal { subscriber in - let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude)) - - let peersNearby: Signal = combineLatest(peersNearbyContext.get(), addressPromise.get()) - |> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), NoError> in - if let address = address { - return .single((peersNearby, address)) - } else { - return reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) - |> map { placemark in - return (peersNearby, placemark?.fullAddress) - } - } - } - |> mapToSignal { peersNearby, address -> Signal in - guard let peersNearby = peersNearby else { - return .single(nil) - } - let peerIds = peersNearby.map { entry -> EnginePeer.Id in - switch entry { - case let .peer(id, _, _): - return id - case .selfPeer: - return context.account.peerId - } - } - return context.engine.data.get( - EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)), - EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init)) - ) - |> map { peerMap, participantCountMap -> PeersNearbyData? in - var users: [PeerNearbyEntry] = [] - var groups: [PeerNearbyEntry] = [] - var visible = false - for peerNearby in peersNearby { - switch peerNearby { - case let .peer(id, expires, distance): - if let maybePeer = peerMap[id], let peer = maybePeer { - if id.namespace == Namespaces.Peer.CloudUser { - users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: distance)) - } else { - var participantCount: Int32? - if let maybeParticipantCount = participantCountMap[id] { - participantCount = maybeParticipantCount.flatMap(Int32.init) - } - groups.append(PeerNearbyEntry(peer: peer, memberCount: participantCount, expires: expires, distance: distance)) - } - } - case let .selfPeer(expires): - visible = true - if let maybePeer = peerMap[context.account.peerId], let peer = maybePeer { - users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: 0)) - } - } - } - return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, visible: visible, accountPeerId: context.account.peerId, users: users, groups: groups, channels: []) - } - } - - let disposable = peersNearby.start(next: { data in - subscriber.putNext(data) - }) - - return ActionDisposable { - disposable.dispose() - let _ = peersNearbyContext.get() - } - } - } - dataPromise.set(.single(nil) |> then(dataSignal)) - - let previousData = Atomic(value: nil) - let displayLoading: Signal = .single(false) - |> then( - .single(true) - |> delay(1.0, queue: Queue.mainQueue()) - ) - - let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), chatLocationPromise.get(), displayLoading, expandedPromise.get(), context.account.postbox.preferencesView(keys: [PreferencesKeys.peersNearby])) - |> deliverOnMainQueue - |> map { presentationData, data, chatLocation, displayLoading, expanded, view -> (ItemListControllerState, (ItemListNodeState, Any)) in - let previous = previousData.swap(data) - let state = view.values[PreferencesKeys.peersNearby]?.get(PeersNearbyState.self) ?? .default - - var crossfade = false - if (data?.users.isEmpty ?? true) != (previous?.users.isEmpty ?? true) { - crossfade = true - } - if (data?.groups.isEmpty ?? true) != (previous?.groups.isEmpty ?? true) { - crossfade = true - } - - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PeopleNearby_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: peersNearbyControllerEntries(data: data, state: state, presentationData: presentationData, displayLoading: displayLoading, expanded: expanded, chatLocation: chatLocation), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: !crossfade) - - return (controllerState, (listState, arguments)) - } - |> afterDisposed { - actionsDisposable.dispose() - } - - let controller = PeersNearbyControllerImpl(context: context, state: signal) - chatLocationPromise.set(controller.chatLocation.get()) - controller.didDisappear = { [weak controller] _ in - controller?.clearItemNodesHighlight(animated: true) - } - navigateToProfileImpl = { [weak controller] peer, distance in - if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) { - navigationController.pushViewController(controller) - } - } - navigateToChatImpl = { [weak controller] peer in - if let navigationController = controller?.navigationController as? NavigationController { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil)) - } - } - pushControllerImpl = { [weak controller] c in - if let controller = controller { - (controller.navigationController as? NavigationController)?.pushViewController(c, animated: true) - } - } - replaceTopControllerImpl = { [weak controller] c in - if let controller = controller { - (controller.navigationController as? NavigationController)?.replaceTopController(c, animated: true) - } - } - presentControllerImpl = { [weak controller] c, p in - if let controller = controller { - controller.present(c, in: .window(.root), with: p) - } - } - presentInGlobalOverlayImpl = { [weak controller] c in - if let controller = controller { - controller.presentInGlobalOverlay(c) - } - } - return controller -} diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift deleted file mode 100644 index e1b70cb30c..0000000000 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift +++ /dev/null @@ -1,127 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramPresentationData -import ItemListUI -import PresentationDataUtils -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import AccountContext - -class PeersNearbyHeaderItem: ListViewItem, ItemListItem { - let context: AccountContext - let theme: PresentationTheme - let text: String - let sectionId: ItemListSectionId - - init(context: AccountContext, theme: PresentationTheme, text: String, sectionId: ItemListSectionId) { - self.context = context - self.theme = theme - self.text = text - self.sectionId = sectionId - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = PeersNearbyHeaderItemNode() - let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) - - node.contentSize = layout.contentSize - node.insets = layout.insets - - Queue.mainQueue().async { - completion(node, { - return (nil, { _ in apply() }) - }) - } - } - } - - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - guard let nodeValue = node() as? PeersNearbyHeaderItemNode else { - assertionFailure() - return - } - - let makeLayout = nodeValue.asyncLayout() - - async { - let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) - Queue.mainQueue().async { - completion(layout, { _ in - apply() - }) - } - } - } - } -} - -private let titleFont = Font.regular(13.0) - -class PeersNearbyHeaderItemNode: ListViewItemNode { - private let titleNode: TextNode - private var animationNode: AnimatedStickerNode - - private var item: PeersNearbyHeaderItem? - - init() { - self.titleNode = TextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.contentMode = .left - self.titleNode.contentsScale = UIScreen.main.scale - - self.animationNode = DefaultAnimatedStickerNodeImpl() - - super.init(layerBacked: false) - - self.addSubnode(self.titleNode) - self.addSubnode(self.animationNode) - } - - func asyncLayout() -> (_ item: PeersNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { - let makeTitleLayout = TextNode.asyncLayout(self.titleNode) - - return { item, params, neighbors in - let leftInset: CGFloat = 32.0 + params.leftInset - let topInset: CGFloat = 92.0 - - let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - - let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height) - let insets = itemListNeighborsGroupedInsets(neighbors, params) - - let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) - - return (layout, { [weak self] in - if let strongSelf = self { - if strongSelf.item == nil { - strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Compass"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) - strongSelf.animationNode.visibility = true - } - strongSelf.item = item - strongSelf.accessibilityLabel = attributedText.string - - let iconSize = CGSize(width: 96.0, height: 96.0) - strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize) - strongSelf.animationNode.updateLayout(size: iconSize) - - let _ = titleApply() - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size) - } - }) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) - } -} diff --git a/submodules/PhotoResources/BUILD b/submodules/PhotoResources/BUILD index 31205c31e0..5afacafb6d 100644 --- a/submodules/PhotoResources/BUILD +++ b/submodules/PhotoResources/BUILD @@ -24,7 +24,7 @@ swift_library( "//submodules/WebPBinding:WebPBinding", "//submodules/AppBundle:AppBundle", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", - "//submodules/Svg:Svg", + "//submodules/Svg/LegacyImpl", "//submodules/Utils/RangeSet:RangeSet", "//submodules/ImageCompression", ], diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 5d66b24aa6..75ad209daf 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -17,10 +17,10 @@ import TinyThumbnail import ImageTransparency import AppBundle import MusicAlbumArtResources -import Svg import RangeSet import Accelerate import ImageCompression +import LegacyImpl private enum ResourceFileData { case data(Data) @@ -844,24 +844,6 @@ private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResource return signal } -public func rawMessagePhoto(postbox: Postbox, userLocation: MediaResourceUserLocation, photoReference: ImageMediaReference) -> Signal { - return chatMessagePhotoDatas(postbox: postbox, userLocation: userLocation, photoReference: photoReference, autoFetchFullSize: true) - |> map { value -> UIImage? in - let thumbnailData = value._0 - let fullSizeData = value._1 - let fullSizeComplete = value._3 - if let fullSizeData = fullSizeData { - if fullSizeComplete { - return UIImage(data: fullSizeData)?.precomposed() - } - } - if let thumbnailData = thumbnailData { - return UIImage(data: thumbnailData)?.precomposed() - } - return nil - } -} - public func chatMessagePhoto(postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType customUserContentType: MediaResourceUserContentType? = nil, photoReference: ImageMediaReference, synchronousLoad: Bool = false, highQuality: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad) |> map { _, _, generate in @@ -1766,79 +1748,6 @@ public func mediaGridMessagePhoto(account: Account, userLocation: MediaResourceU } } -public func gifPaneVideoThumbnail(account: Account, videoReference: FileMediaReference) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - if let smallestRepresentation = smallestImageRepresentation(videoReference.media.previewRepresentations) { - let thumbnailResource = smallestRepresentation.resource - - let thumbnail = Signal { subscriber in - let data = account.postbox.mediaBox.resourceData(thumbnailResource).start(next: { data in - subscriber.putNext(data) - }, completed: { - subscriber.putCompletion() - }) - let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: videoReference.resourceReference(thumbnailResource)).start() - return ActionDisposable { - data.dispose() - fetched.dispose() - } - } - - return thumbnail - |> map { data in - let thumbnailData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) - return { arguments in - guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { - return nil - } - - let drawingRect = arguments.drawingRect - let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) - let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) - - var thumbnailImage: CGImage? - if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { - thumbnailImage = image - } - - var blurredThumbnailImage: UIImage? - if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() - } - } - - context.withFlippedContext { c in - c.setBlendMode(.copy) - if arguments.boundingSize != arguments.imageSize { - c.fill(arguments.drawingRect) - } - - c.setBlendMode(.copy) - if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { - c.interpolationQuality = .low - drawImage(context: c, image: cgImage, orientation: .up, in: fittedRect) - c.setBlendMode(.normal) - } - } - - addCorners(context, arguments: arguments) - - return context - } - } - } else { - return .never() - } -} - public func mediaGridMessageVideo(postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType customUserContentType: MediaResourceUserContentType? = nil, videoReference: FileMediaReference, hlsFiles: [(playlist: TelegramMediaFile, video: TelegramMediaFile)] = [], onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil, nilForEmptyResult: Bool = false, useMiniThumbnailIfAvailable: Bool = false, blurred: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return internalMediaGridMessageVideo(postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, videoReference: videoReference, hlsFiles: hlsFiles, onlyFullSize: onlyFullSize, useLargeThumbnail: useLargeThumbnail, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail, overlayColor: overlayColor, nilForEmptyResult: nilForEmptyResult, useMiniThumbnailIfAvailable: useMiniThumbnailIfAvailable) |> map { @@ -2076,7 +1985,7 @@ public func chatMessagePhotoStatus(context: AccountContext, messageId: MessageId if let range = representationFetchRangeForDisplayAtSize(representation: largestRepresentation, dimension: displayAtSize) { return combineLatest( context.fetchManager.fetchStatus(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: largestRepresentation.resource), - context.account.postbox.mediaBox.resourceRangesStatus(largestRepresentation.resource) + context.engine.resources.resourceRangesStatus(resource: EngineMediaResource(largestRepresentation.resource)) ) |> map { status, rangeStatus -> MediaResourceStatus in if rangeStatus.isSuperset(of: RangeSet(range)) { @@ -2703,25 +2612,6 @@ public func chatMessageImageFile(account: Account, userLocation: MediaResourceUs } } -public func preloadedBotIcon(account: Account, fileReference: FileMediaReference) -> Signal { - let signal = Signal { subscriber in - let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)).start() - let dataDisposable = account.postbox.mediaBox.resourceData(fileReference.media.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in - if data.complete { - subscriber.putNext(true) - subscriber.putCompletion() - } else { - subscriber.putNext(false) - } - }) - return ActionDisposable { - fetched.dispose() - dataDisposable.dispose() - } - } - return signal -} - public func instantPageImageFile(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessageFileDatas(account: account, userLocation: userLocation, fileReference: fileReference, progressive: false, fetched: fetched) |> map { value in @@ -3212,7 +3102,8 @@ private func drawAlbumArtPlaceholder(into c: CGContext, arguments: TransformImag } } -public func playerAlbumArt(postbox: Postbox, engine: TelegramEngine, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil, drawPlaceholderWhenEmpty: Bool = true, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func playerAlbumArt(engine: TelegramEngine, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool, overlayColor: UIColor? = nil, emptyColor: UIColor? = nil, drawPlaceholderWhenEmpty: Bool = true, attemptSynchronously: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let postbox = engine.account.postbox var fileArtworkData: Signal = .single(nil) if let fileReference = fileReference { let size = thumbnail ? CGSize(width: 48.0, height: 48.0) : CGSize(width: 320.0, height: 320.0) diff --git a/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift b/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift index cdafe9a37c..79e6ffaa8a 100644 --- a/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift +++ b/submodules/PlatformRestrictionMatching/Sources/PlatformRestrictionMatching.swift @@ -1,8 +1,7 @@ import Foundation import TelegramCore -import Postbox -public extension Message { +public extension EngineRawMessage { func isRestricted(platform: String, contentSettings: ContentSettings) -> Bool { return self.restrictionReason(platform: platform, contentSettings: contentSettings) != nil } diff --git a/submodules/Postbox/Sources/AllTypingDraftsView.swift b/submodules/Postbox/Sources/AllTypingDraftsView.swift new file mode 100644 index 0000000000..5290154e63 --- /dev/null +++ b/submodules/Postbox/Sources/AllTypingDraftsView.swift @@ -0,0 +1,49 @@ +import Foundation + +final class MutableAllTypingDraftsView: MutablePostboxView { + fileprivate var keys: Set + + init(postbox: PostboxImpl) { + self.keys = Set(postbox.currentTypingDrafts.keys) + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + if transaction.updatedTypingDrafts.isEmpty { + return false + } + var updated = false + for (key, update) in transaction.updatedTypingDrafts { + if update.value != nil { + if self.keys.insert(key).inserted { + updated = true + } + } else { + if self.keys.remove(key) != nil { + updated = true + } + } + } + return updated + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + let new = Set(postbox.currentTypingDrafts.keys) + if new == self.keys { + return false + } + self.keys = new + return true + } + + func immutableView() -> PostboxView { + return AllTypingDraftsView(self) + } +} + +public final class AllTypingDraftsView: PostboxView { + public let keys: Set + + init(_ view: MutableAllTypingDraftsView) { + self.keys = view.keys + } +} diff --git a/submodules/Postbox/Sources/Media.swift b/submodules/Postbox/Sources/Media.swift index 567e6ccfd2..136df74c98 100644 --- a/submodules/Postbox/Sources/Media.swift +++ b/submodules/Postbox/Sources/Media.swift @@ -80,6 +80,7 @@ public protocol Media: AnyObject, PostboxCoding { var id: MediaId? { get } var peerIds: [PeerId] { get } var storyIds: [StoryId] { get } + var mediaIds: [MediaId] { get } var indexableText: String? { get } @@ -95,6 +96,9 @@ public extension Media { var storyIds: [StoryId] { return [] } + var mediaIds: [MediaId] { + return [] + } } public func areMediaArraysEqual(_ lhs: [Media], _ rhs: [Media]) -> Bool { diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index cd004d82cc..e20aef448b 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -1263,13 +1263,13 @@ final class MessageHistoryTable: Table { if let mediaId = media.id { let mediaInsertResult = self.messageMediaTable.set(media, index: message.index, messageHistoryTable: self) switch mediaInsertResult { - case let .Embed(media): - embeddedMedia.append(media) - case .Reference: - referencedMedia.append(mediaId) - if media.isLikelyToBeUpdated() { - updateExistingMedia[mediaId] = media - } + case let .Embed(media): + embeddedMedia.append(media) + case .Reference: + referencedMedia.append(mediaId) + if media.isLikelyToBeUpdated() { + updateExistingMedia[mediaId] = media + } } } else { embeddedMedia.append(media) @@ -2759,12 +2759,6 @@ final class MessageHistoryTable: Table { } } - /*#if DEBUG - for key in associatedStories.keys { - associatedStories[key] = CodableEntry(data: Data()) - } - #endif*/ - associatedMessageIds.append(contentsOf: attribute.associatedMessageIds) if addAssociatedMessages { for messageId in attribute.associatedMessageIds { @@ -2786,6 +2780,16 @@ final class MessageHistoryTable: Table { peers[possibleThreadPeer.id] = possibleThreadPeer } + for media in parsedMedia { + for id in media.mediaIds { + if associatedMedia[id] == nil { + if let media = self.getMedia(id) { + associatedMedia[id] = media + } + } + } + } + return Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, threadId: message.threadId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: message.customTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: parsedAttributes, media: parsedMedia, peers: peers, associatedMessages: associatedMessages, associatedMessageIds: associatedMessageIds, associatedMedia: associatedMedia, associatedThreadInfo: associatedThreadInfo, associatedStories: associatedStories) } diff --git a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift index cb8976110b..40a72ada71 100644 --- a/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift +++ b/submodules/Postbox/Sources/MessageHistoryThreadIndexView.swift @@ -1,39 +1,72 @@ import Foundation -final class MutableMessageHistoryThreadIndexView: MutablePostboxView { - final class Item { - let id: Int64 - let pinnedIndex: Int? - let index: MessageIndex - var info: CodableEntry - var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] - var topMessage: Message? - var embeddedInterfaceState: StoredPeerChatInterfaceState? - - init( - id: Int64, - pinnedIndex: Int?, - index: MessageIndex, - info: CodableEntry, - tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], - topMessage: Message?, - embeddedInterfaceState: StoredPeerChatInterfaceState? - ) { - self.id = id - self.pinnedIndex = pinnedIndex - self.index = index - self.info = info - self.tagSummaryInfo = tagSummaryInfo - self.topMessage = topMessage - self.embeddedInterfaceState = embeddedInterfaceState - } +public final class MessageHistoryThreadIndexItem: Equatable { + public let id: Int64 + public let pinnedIndex: Int? + public let index: MessageIndex + public var info: CodableEntry + public var tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] + public var topMessage: Message? + public var embeddedInterfaceState: StoredPeerChatInterfaceState? + + public init( + id: Int64, + pinnedIndex: Int?, + index: MessageIndex, + info: CodableEntry, + tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], + topMessage: Message?, + embeddedInterfaceState: StoredPeerChatInterfaceState? + ) { + self.id = id + self.pinnedIndex = pinnedIndex + self.index = index + self.info = info + self.tagSummaryInfo = tagSummaryInfo + self.topMessage = topMessage + self.embeddedInterfaceState = embeddedInterfaceState } - + + public static func ==(lhs: MessageHistoryThreadIndexItem, rhs: MessageHistoryThreadIndexItem) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs.pinnedIndex != rhs.pinnedIndex { + return false + } + if lhs.index != rhs.index { + return false + } + if lhs.info != rhs.info { + return false + } + if lhs.tagSummaryInfo != rhs.tagSummaryInfo { + return false + } + if let lhsMessage = lhs.topMessage, let rhsMessage = rhs.topMessage { + if lhsMessage.index != rhsMessage.index { + return false + } + if lhsMessage.stableVersion != rhsMessage.stableVersion { + return false + } + } else if (lhs.topMessage == nil) != (rhs.topMessage == nil) { + return false + } + if lhs.embeddedInterfaceState != rhs.embeddedInterfaceState { + return false + } + + return true + } +} + +final class MutableMessageHistoryThreadIndexView: MutablePostboxView { fileprivate let peerId: PeerId fileprivate let summaryComponents: ChatListEntrySummaryComponents fileprivate var peer: Peer? fileprivate var peerNotificationSettings: PeerNotificationSettings? - fileprivate var items: [Item] = [] + fileprivate var items: [MessageHistoryThreadIndexItem] = [] private var hole: ForumTopicListHolesEntry? fileprivate var isLoading: Bool = false @@ -99,7 +132,7 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { var embeddedInterfaceState: StoredPeerChatInterfaceState? embeddedInterfaceState = postbox.peerChatThreadInterfaceStateTable.get(PeerChatThreadId(peerId: self.peerId, threadId: item.threadId)) - self.items.append(Item( + self.items.append(MessageHistoryThreadIndexItem( id: item.threadId, pinnedIndex: pinnedIndex, index: item.index, @@ -152,93 +185,16 @@ final class MutableMessageHistoryThreadIndexView: MutablePostboxView { } } -public final class EngineMessageHistoryThread { - public final class Item: Equatable { - public let id: Int64 - public let pinnedIndex: Int? - public let index: MessageIndex - public let info: CodableEntry - public let tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] - public let topMessage: Message? - public let embeddedInterfaceState: StoredPeerChatInterfaceState? - - public init( - id: Int64, - pinnedIndex: Int?, - index: MessageIndex, - info: CodableEntry, - tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], - topMessage: Message?, - embeddedInterfaceState: StoredPeerChatInterfaceState? - ) { - self.id = id - self.pinnedIndex = pinnedIndex - self.index = index - self.info = info - self.tagSummaryInfo = tagSummaryInfo - self.topMessage = topMessage - self.embeddedInterfaceState = embeddedInterfaceState - } - - public static func ==(lhs: Item, rhs: Item) -> Bool { - if lhs.id != rhs.id { - return false - } - if lhs.pinnedIndex != rhs.pinnedIndex { - return false - } - if lhs.index != rhs.index { - return false - } - if lhs.info != rhs.info { - return false - } - if lhs.tagSummaryInfo != rhs.tagSummaryInfo { - return false - } - if let lhsMessage = lhs.topMessage, let rhsMessage = rhs.topMessage { - if lhsMessage.index != rhsMessage.index { - return false - } - if lhsMessage.stableVersion != rhsMessage.stableVersion { - return false - } - } else if (lhs.topMessage == nil) != (rhs.topMessage == nil) { - return false - } - if lhs.embeddedInterfaceState != rhs.embeddedInterfaceState { - return false - } - - return true - } - } -} - public final class MessageHistoryThreadIndexView: PostboxView { public let peer: Peer? public let peerNotificationSettings: PeerNotificationSettings? - public let items: [EngineMessageHistoryThread.Item] + public let items: [MessageHistoryThreadIndexItem] public let isLoading: Bool - + init(_ view: MutableMessageHistoryThreadIndexView) { self.peer = view.peer self.peerNotificationSettings = view.peerNotificationSettings - - var items: [EngineMessageHistoryThread.Item] = [] - for item in view.items { - items.append(EngineMessageHistoryThread.Item( - id: item.id, - pinnedIndex: item.pinnedIndex, - index: item.index, - info: item.info, - tagSummaryInfo: item.tagSummaryInfo, - topMessage: item.topMessage, - embeddedInterfaceState: item.embeddedInterfaceState - )) - } - self.items = items - + self.items = view.items self.isLoading = view.isLoading } } diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index f9e4792913..f517f5b3d9 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -1069,28 +1069,30 @@ final class MutableMessageHistoryView: MutablePostboxView { } } - switch self.peerIds { - case let .single(peerId, threadId): - let location = PeerAndThreadId(peerId: peerId, threadId: threadId) - if let typingDraftUpdate = transaction.updatedTypingDrafts[location] { - if let typingDraft = typingDraftUpdate.value, self.namespaces.contains(typingDraft.namespace) { - self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) - } else { - self.typingDraft = nil + if self.tag == nil { + switch self.peerIds { + case let .single(peerId, threadId): + let location = PeerAndThreadId(peerId: peerId, threadId: threadId) + if let typingDraftUpdate = transaction.updatedTypingDrafts[location] { + if let typingDraft = typingDraftUpdate.value, self.namespaces.contains(typingDraft.namespace) { + self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) + } else { + self.typingDraft = nil + } + hasChanges = true } - hasChanges = true + case .external: + break + case .associated: + break } - case .external: - break - case .associated: - break } return hasChanges } private func reloadTypingDraft(postbox: PostboxImpl) { - guard case let .single(peerId, threadId) = self.peerIds else { + guard case let .single(peerId, threadId) = self.peerIds, self.tag == nil else { self.typingDraft = nil return } @@ -1558,13 +1560,15 @@ public final class MessageHistoryView: PostboxView { } if !self.holeLater, let typingDraft = mutableView.typingDraft { - entries.append(MessageHistoryEntry( + let newEntry = MessageHistoryEntry( message: typingDraft, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false) - )) + ) + entries.append(newEntry) + entries.sort() } self.entries = entries diff --git a/submodules/Postbox/Sources/TypingDraftsView.swift b/submodules/Postbox/Sources/TypingDraftsView.swift new file mode 100644 index 0000000000..cb8a1bf361 --- /dev/null +++ b/submodules/Postbox/Sources/TypingDraftsView.swift @@ -0,0 +1,98 @@ +import Foundation + +final class MutableTypingDraftsView: MutablePostboxView { + fileprivate let peerAndThreadId: PeerAndThreadId + fileprivate var typingDraft: Message? + + init(postbox: PostboxImpl, peerAndThreadId: PeerAndThreadId) { + self.peerAndThreadId = peerAndThreadId + + self.reload(postbox: postbox) + } + + private func reload(postbox: PostboxImpl) { + if let typingDraft = postbox.currentTypingDrafts[self.peerAndThreadId] { + self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) + } else { + self.typingDraft = nil + } + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + + if let typingDraftUpdate = transaction.updatedTypingDrafts[self.peerAndThreadId] { + if let typingDraft = typingDraftUpdate.value { + self.typingDraft = self.renderTypingDraft(postbox: postbox, typingDraft: typingDraft) + } else { + self.typingDraft = nil + } + updated = true + } + + return updated + } + + private func renderTypingDraft(postbox: PostboxImpl, typingDraft: PostboxImpl.TypingDraft) -> Message? { + guard let peer = postbox.peerTable.get(self.peerAndThreadId.peerId), let author = postbox.peerTable.get(typingDraft.authorId) else { + return nil + } + + var peers = SimpleDictionary() + peers[peer.id] = peer + peers[author.id] = author + + var associatedThreadInfo: Message.AssociatedThreadInfo? + if let threadId = typingDraft.threadId, let data = postbox.messageHistoryThreadIndexTable.get(peerId: self.peerAndThreadId.peerId, threadId: threadId) { + associatedThreadInfo = postbox.seedConfiguration.decodeMessageThreadInfo(data.data) + } + + return Message( + stableId: typingDraft.stableId, + stableVersion: typingDraft.stableVersion, + id: MessageId( + peerId: self.peerAndThreadId.peerId, + namespace: 1, + id: Int32.max - 50000), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: typingDraft.threadId, + timestamp: typingDraft.timestamp, + flags: [.Incoming], + tags: [], + globalTags: [], + localTags: [], + customTags: [], + forwardInfo: nil, + author: author, + text: typingDraft.text, + attributes: typingDraft.attributes, + media: [], + peers: peers, + associatedMessages: SimpleDictionary(), + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: associatedThreadInfo, + associatedStories: [:] + ) + } + + func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool { + self.reload(postbox: postbox) + + return true + } + + func immutableView() -> PostboxView { + return TypingDraftsView(self) + } +} + +public final class TypingDraftsView: PostboxView { + public let typingDraft: Message? + + init(_ view: MutableTypingDraftsView) { + self.typingDraft = view.typingDraft + } +} diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index ab21dcf429..6d5453d416 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -102,6 +102,8 @@ public enum PostboxViewKey: Hashable { case savedMessagesStats(peerId: PeerId) case chatInterfaceState(peerId: PeerId) case historyView(HistoryView) + case typingDrafts(PeerAndThreadId) + case allTypingDrafts public func hash(into hasher: inout Hasher) { switch self { @@ -187,7 +189,7 @@ public enum PostboxViewKey: Hashable { case let .topChatMessage(peerIds): hasher.combine(peerIds) case .contacts: - hasher.combine(16) + hasher.combine(24) case let .deletedMessages(peerId): hasher.combine(peerId) case let .notice(key): @@ -228,6 +230,11 @@ public enum PostboxViewKey: Hashable { hasher.combine(20) hasher.combine(historyView.peerId) hasher.combine(historyView.threadId) + case let .typingDrafts(peerId): + hasher.combine(23) + hasher.combine(peerId) + case .allTypingDrafts: + hasher.combine(22) } } @@ -545,6 +552,18 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .typingDrafts(peerId): + if case .typingDrafts(peerId) = rhs { + return true + } else { + return false + } + case .allTypingDrafts: + if case .allTypingDrafts = rhs { + return true + } else { + return false + } } } } @@ -672,5 +691,9 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost topTaggedMessages: [:], additionalDatas: [] ) + case let .typingDrafts(peerId): + return MutableTypingDraftsView(postbox: postbox, peerAndThreadId: peerId) + case .allTypingDrafts: + return MutableAllTypingDraftsView(postbox: postbox) } } diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index 6271348638..c05f255806 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -1607,6 +1607,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio let stateContext = CountriesMultiselectionScreen.StateContext( context: context, subject: .countries, + maxCount: context.userLimits.maxGiveawayCountriesCount, initialSelectedCountries: state.countries ) let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in diff --git a/submodules/PremiumUI/Sources/GiftOptionItem.swift b/submodules/PremiumUI/Sources/GiftOptionItem.swift index 5dee8b306b..adee7db497 100644 --- a/submodules/PremiumUI/Sources/GiftOptionItem.swift +++ b/submodules/PremiumUI/Sources/GiftOptionItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 9190c1d178..cd063c8afd 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import AccountContext import TelegramPresentationData @@ -1439,7 +1438,7 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent { ) ) }, - backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + backgroundColor: .color(theme.list.modalPlainBackgroundColor), animateOut: animateOut ), environment: { @@ -1473,306 +1472,6 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent { } } -//private final class BoostLevelsContainerComponent: CombinedComponent { -// class ExternalState { -// var isGroup: Bool = false -// var contentHeight: CGFloat = 0.0 -// } -// -// let context: AccountContext -// let theme: PresentationTheme -// let strings: PresentationStrings -// let externalState: ExternalState -// let peerId: EnginePeer.Id -// let mode: PremiumBoostLevelsScreen.Mode -// let status: ChannelBoostStatus? -// let boostState: InternalBoostState.DisplayData? -// let boost: () -> Void -// let copyLink: (String) -> Void -// let dismiss: () -> Void -// let openStats: (() -> Void)? -// let openGift: (() -> Void)? -// let openPeer: ((EnginePeer) -> Void)? -// let updated: () -> Void -// -// init( -// context: AccountContext, -// theme: PresentationTheme, -// strings: PresentationStrings, -// externalState: ExternalState, -// peerId: EnginePeer.Id, -// mode: PremiumBoostLevelsScreen.Mode, -// status: ChannelBoostStatus?, -// boostState: InternalBoostState.DisplayData?, -// boost: @escaping () -> Void, -// copyLink: @escaping (String) -> Void, -// dismiss: @escaping () -> Void, -// openStats: (() -> Void)?, -// openGift: (() -> Void)?, -// openPeer: ((EnginePeer) -> Void)?, -// updated: @escaping () -> Void -// ) { -// self.context = context -// self.theme = theme -// self.strings = strings -// self.externalState = externalState -// self.peerId = peerId -// self.mode = mode -// self.status = status -// self.boostState = boostState -// self.boost = boost -// self.copyLink = copyLink -// self.dismiss = dismiss -// self.openStats = openStats -// self.openGift = openGift -// self.openPeer = openPeer -// self.updated = updated -// } -// -// static func ==(lhs: BoostLevelsContainerComponent, rhs: BoostLevelsContainerComponent) -> Bool { -// if lhs.context !== rhs.context { -// return false -// } -// if lhs.theme !== rhs.theme { -// return false -// } -// if lhs.peerId != rhs.peerId { -// return false -// } -// if lhs.mode != rhs.mode { -// return false -// } -// if lhs.status != rhs.status { -// return false -// } -// if lhs.boostState != rhs.boostState { -// return false -// } -// return true -// } -// -// final class State: ComponentState { -// var topContentOffset: CGFloat = 0.0 -// var cachedStatsImage: (UIImage, PresentationTheme)? -// var cachedCloseImage: (UIImage, PresentationTheme)? -// -// private var disposable: Disposable? -// private(set) var peer: EnginePeer? -// -// init(context: AccountContext, peerId: EnginePeer.Id, updated: @escaping () -> Void) { -// super.init() -// -// self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) -// |> deliverOnMainQueue).startStrict(next: { [weak self] peer in -// guard let self else { -// return -// } -// self.peer = peer -// updated() -// }) -// } -// -// deinit { -// self.disposable?.dispose() -// } -// } -// -// func makeState() -> State { -// return State(context: self.context, peerId: self.peerId, updated: self.updated) -// } -// -// static var body: Body { -// let background = Child(Rectangle.self) -// let scroll = Child(ScrollComponent.self) -// let topPanel = Child(BlurredBackgroundComponent.self) -// let topSeparator = Child(Rectangle.self) -// let title = Child(MultilineTextComponent.self) -// let statsButton = Child(Button.self) -// let closeButton = Child(Button.self) -// -// let externalScrollState = ScrollComponent.ExternalState() -// -// return { context in -// let state = context.state -// -// let theme = context.component.theme -// let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings -// -// let topInset: CGFloat = 56.0 -// -// let component = context.component -// -// var isGroup: Bool? -// if let peer = state.peer { -// if case let .channel(channel) = peer, case .group = channel.info { -// isGroup = true -// } else { -// isGroup = false -// } -// } -// -// if let isGroup { -// component.externalState.isGroup = isGroup -// let updated = component.updated -// let scroll = scroll.update( -// component: ScrollComponent( -// content: AnyComponent( -// SheetContent( -// context: component.context, -// theme: component.theme, -// strings: component.strings, -// insets: .zero, -// peerId: component.peerId, -// isGroup: isGroup, -// mode: component.mode, -// status: component.status, -// boostState: component.boostState, -// boost: component.boost, -// copyLink: component.copyLink, -// dismiss: component.dismiss, -// openStats: component.openStats, -// openGift: component.openGift, -// openPeer: component.openPeer, -// updated: { [weak state] in -// updated() -// } -// ) -// ), -// externalState: externalScrollState, -// contentInsets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0), -// contentOffsetUpdated: { [weak state] topContentOffset, _ in -// state?.topContentOffset = topContentOffset -// Queue.mainQueue().justDispatch { -// state?.updated(transition: .immediate) -// } -// }, -// contentOffsetWillCommit: { _ in } -// ), -// availableSize: context.availableSize, -// transition: context.transition -// ) -// component.externalState.contentHeight = externalScrollState.contentHeight -// -// let background = background.update( -// component: Rectangle(color: theme.overallDarkAppearance ? theme.list.blocksBackgroundColor : theme.list.plainBackgroundColor), -// availableSize: scroll.size, -// transition: context.transition -// ) -// context.add(background -// .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) -// ) -// -// context.add(scroll -// .position(CGPoint(x: context.availableSize.width / 2.0, y: scroll.size.height / 2.0)) -// ) -// } -// -// let titleString: String -// var titleFont = Font.semibold(17.0) -// -// switch component.mode { -// case let .owner(subject): -// if let status = component.status, let _ = status.nextLevelBoosts { -// if let subject { -// switch subject { -// case .stories: -// if status.level == 0 { -// titleString = strings.ChannelBoost_EnableStories -// } else { -// titleString = strings.ChannelBoost_IncreaseLimit -// } -// case .nameColors: -// titleString = strings.ChannelBoost_NameColor -// case .nameIcon: -// titleString = strings.ChannelBoost_NameIcon -// case .profileColors: -// titleString = strings.ChannelBoost_ProfileColor -// case .profileIcon: -// titleString = strings.ChannelBoost_ProfileIcon -// case .channelReactions: -// titleString = strings.ChannelBoost_CustomReactions -// case .emojiStatus: -// titleString = strings.ChannelBoost_EmojiStatus -// case .wallpaper: -// titleString = strings.ChannelBoost_Wallpaper -// case .customWallpaper: -// titleString = strings.ChannelBoost_CustomWallpaper -// case .audioTranscription: -// titleString = strings.GroupBoost_AudioTranscription -// case .emojiPack: -// titleString = strings.GroupBoost_EmojiPack -// case .noAds: -// titleString = strings.ChannelBoost_NoAds -// case .wearGift: -// titleString = strings.ChannelBoost_WearGift -// case .autoTranslate: -// titleString = strings.ChannelBoost_AutoTranslate -// } -// } else { -// titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current -// } -// } else { -// titleString = strings.ChannelBoost_MaxLevelReached -// } -// case let .user(mode): -// var remaining: Int? -// if let status = component.status, let nextLevelBoosts = status.nextLevelBoosts { -// remaining = nextLevelBoosts - status.boosts -// } -// -// if let _ = remaining { -// if case .current = mode { -// titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current -// } else { -// titleString = isGroup == true ? strings.GroupBoost_Title_Other : strings.ChannelBoost_Title_Other -// } -// } else { -// titleString = strings.ChannelBoost_MaxLevelReached -// } -// case .features: -// titleString = strings.GroupBoost_AdditionalFeatures -// titleFont = Font.semibold(20.0) -// } -// -// let title = title.update( -// component: MultilineTextComponent( -// text: .plain(NSAttributedString(string: titleString, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)), -// horizontalAlignment: .center, -// truncationType: .end, -// maximumNumberOfLines: 1 -// ), -// availableSize: context.availableSize, -// transition: context.transition -// ) -// -// let topPanelAlpha: CGFloat -// let titleOriginY: CGFloat -// let titleScale: CGFloat -// if case .features = component.mode { -// if state.topContentOffset > 78.0 { -// topPanelAlpha = min(30.0, state.topContentOffset - 78.0) / 30.0 -// } else { -// topPanelAlpha = 0.0 -// } -// -// let titleTopOriginY = topPanel.size.height / 2.0 -// let titleBottomOriginY: CGFloat = 146.0 -// let titleOriginDelta = titleTopOriginY - titleBottomOriginY -// -// let fraction = min(1.0, state.topContentOffset / abs(titleOriginDelta)) -// titleOriginY = titleBottomOriginY + fraction * titleOriginDelta -// titleScale = 1.0 - max(0.0, fraction * 0.2) -// } else { -// topPanelAlpha = min(30.0, state.topContentOffset) / 30.0 -// titleOriginY = topPanel.size.height / 2.0 -// titleScale = 1.0 -// } -// - -// } -// } -//} - public class PremiumBoostLevelsScreen: ViewControllerComponentContainer { public enum Mode: Equatable { public enum UserMode: Equatable { @@ -1845,1067 +1544,6 @@ public class PremiumBoostLevelsScreen: ViewControllerComponentContainer { } } -//public class PremiumBoostLevelsScreen: ViewController { -// public enum Mode: Equatable { -// public enum UserMode: Equatable { -// case external -// case current -// case groupPeer(EnginePeer.Id, Int) -// case unrestrict(Int) -// } -// case user(mode: UserMode) -// case owner(subject: BoostSubject?) -// case features -// } -// -// final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate { -// private var presentationData: PresentationData -// private weak var controller: PremiumBoostLevelsScreen? -// -// let dim: ASDisplayNode -// let wrappingView: UIView -// let containerView: UIView -// -// let contentView: ComponentHostView -// let footerContainerView: UIView -// let footerView: ComponentHostView -// -// private let containerExternalState = BoostLevelsContainerComponent.ExternalState() -// -// private(set) var isExpanded = false -// private var panGestureRecognizer: UIPanGestureRecognizer? -// private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?)? -// -// private let hapticFeedback = HapticFeedback() -// -// private var currentIsVisible: Bool = false -// private var currentLayout: ContainerViewLayout? -// -// init(context: AccountContext, controller: PremiumBoostLevelsScreen) { -// self.presentationData = context.sharedContext.currentPresentationData.with { $0 } -// if controller.forceDark { -// self.presentationData = self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) -// } -// self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground()) -// -// self.controller = controller -// -// self.dim = ASDisplayNode() -// self.dim.alpha = 0.0 -// self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) -// -// self.wrappingView = UIView() -// self.containerView = UIView() -// self.contentView = ComponentHostView() -// -// self.footerContainerView = UIView() -// self.footerView = ComponentHostView() -// -// super.init() -// -// self.containerView.clipsToBounds = true -// self.containerView.backgroundColor = self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.list.plainBackgroundColor -// -// self.addSubnode(self.dim) -// -// self.view.addSubview(self.wrappingView) -// self.wrappingView.addSubview(self.containerView) -// self.containerView.addSubview(self.contentView) -// -// if case .user = controller.mode { -// self.containerView.addSubview(self.footerContainerView) -// self.footerContainerView.addSubview(self.footerView) -// } -// -// if let status = controller.status, let myBoostStatus = controller.myBoostStatus { -// var myBoostCount: Int32 = 0 -// var currentMyBoostCount: Int32 = 0 -// var availableBoosts: [MyBoostStatus.Boost] = [] -// var occupiedBoosts: [MyBoostStatus.Boost] = [] -// -// for boost in myBoostStatus.boosts { -// if let boostPeer = boost.peer { -// if boostPeer.id == controller.peerId { -// myBoostCount += 1 -// } else { -// occupiedBoosts.append(boost) -// } -// } else { -// availableBoosts.append(boost) -// } -// } -// -// let boosts = max(Int32(status.boosts), myBoostCount) -// let initialState = InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts) -// self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: controller.replacedBoosts?.0) -// -// self.updatedState.set(.single(InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts + 1))) -// -// if let (replacedBoosts, sourcePeers) = controller.replacedBoosts { -// currentMyBoostCount += 1 -// -// self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 1) -// Queue.mainQueue().justDispatch { -// self.updated(transition: .easeInOut(duration: 0.2)) -// } -// -// Queue.mainQueue().after(0.3) { -// let presentationData = context.sharedContext.currentPresentationData.with { $0 } -// -// var groupCount: Int32 = 0 -// var channelCount: Int32 = 0 -// for peer in sourcePeers { -// if case let .channel(channel) = peer { -// switch channel.info { -// case .broadcast: -// channelCount += 1 -// case .group: -// groupCount += 1 -// } -// } -// } -// let otherText: String -// if channelCount > 0 && groupCount == 0 { -// otherText = presentationData.strings.ReassignBoost_OtherChannels(channelCount) -// } else if groupCount > 0 && channelCount == 0 { -// otherText = presentationData.strings.ReassignBoost_OtherGroups(groupCount) -// } else { -// otherText = presentationData.strings.ReassignBoost_OtherGroupsAndChannels(Int32(sourcePeers.count)) -// } -// let text = presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), otherText).string -// let undoController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "BoostReplace", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil, timeout: 4.0), elevatedLayout: false, position: .top, action: { _ in return true }) -// controller.present(undoController, in: .current) -// } -// } -// -// self.availableBoosts = availableBoosts -// self.occupiedBoosts = occupiedBoosts -// self.myBoostCount = myBoostCount -// self.currentMyBoostCount = currentMyBoostCount -// } -// } -// -// override func didLoad() { -// super.didLoad() -// -// let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) -// panRecognizer.delegate = self.wrappedGestureRecognizerDelegate -// panRecognizer.delaysTouchesBegan = false -// panRecognizer.cancelsTouchesInView = true -// self.panGestureRecognizer = panRecognizer -// self.wrappingView.addGestureRecognizer(panRecognizer) -// -// self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) -// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) -// } -// -// @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { -// if case .ended = recognizer.state { -// self.controller?.dismiss(animated: true) -// } -// } -// -// override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { -// if let layout = self.currentLayout { -// if case .regular = layout.metrics.widthClass { -// return false -// } -// } -// return true -// } -// -// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { -// if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { -// if let scrollView = otherGestureRecognizer.view as? UIScrollView { -// if scrollView.contentSize.width > scrollView.contentSize.height { -// return false -// } -// } -// return true -// } -// return false -// } -// -// private var isDismissing = false -// func animateIn() { -// ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) -// -// let targetPosition = self.containerView.center -// let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) -// -// self.containerView.center = startPosition -// let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) -// transition.animateView(allowUserInteraction: true, { -// self.containerView.center = targetPosition -// }, completion: { _ in -// }) -// } -// -// func animateOut(completion: @escaping () -> Void = {}) { -// self.isDismissing = true -// -// let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) -// positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in -// self?.controller?.dismiss(animated: false, completion: completion) -// }) -// let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) -// alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) -// -// self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) -// } -// -// func requestLayout(transition: ComponentTransition) { -// guard let layout = self.currentLayout else { -// return -// } -// self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition) -// } -// -// private var dismissOffset: CGFloat? -// func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, transition: ComponentTransition) { -// guard !self.isDismissing else { -// return -// } -// self.currentLayout = layout -// -// self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) -// -// let isLandscape = layout.orientation == .landscape -// -// var containerTopInset: CGFloat = 0.0 -// let clipFrame: CGRect -// if layout.metrics.widthClass == .compact { -// self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) -// if isLandscape { -// self.containerView.layer.cornerRadius = 0.0 -// } else { -// self.containerView.layer.cornerRadius = 10.0 -// } -// -// if #available(iOS 11.0, *) { -// if layout.safeInsets.bottom.isZero { -// self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] -// } else { -// self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] -// } -// } -// -// if isLandscape { -// clipFrame = CGRect(origin: CGPoint(), size: layout.size) -// } else { -// let coveredByModalTransition: CGFloat = 0.0 -// containerTopInset = 10.0 -// if let statusBarHeight = layout.statusBarHeight { -// containerTopInset += statusBarHeight -// } -// -// let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset)) -// let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width -// let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition -// let maxScaledTopInset: CGFloat = containerTopInset - 10.0 -// let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition -// let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) -// -// clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) -// } -// } else { -// self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) -// self.containerView.layer.cornerRadius = 10.0 -// -// let verticalInset: CGFloat = 44.0 -// -// let maxSide = max(layout.size.width, layout.size.height) -// let minSide = min(layout.size.width, layout.size.height) -// let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) -// clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) -// } -// -// transition.setFrame(view: self.containerView, frame: clipFrame) -// -// var effectiveExpanded = self.isExpanded -// if case .regular = layout.metrics.widthClass { -// effectiveExpanded = true -// } -// -// self.updated(transition: transition, forceUpdate: forceUpdate) -// -// let contentHeight = self.containerExternalState.contentHeight -// if contentHeight > 0.0 && contentHeight < 400.0, let view = self.footerView.componentView as? FooterComponent.View { -// view.backgroundView.alpha = 0.0 -// view.separator.opacity = 0.0 -// } -// let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset -// -// let topInset: CGFloat -// if let (panInitialTopInset, panOffset, _) = self.panGestureArguments { -// if effectiveExpanded { -// topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) -// } else { -// topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) -// } -// } else if let dismissOffset = self.dismissOffset, !dismissOffset.isZero { -// topInset = edgeTopInset * dismissOffset -// } else { -// topInset = effectiveExpanded ? 0.0 : edgeTopInset -// } -// transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil) -// -// let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset) -// self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition) -// -// let footerHeight = self.footerHeight -// let convertedFooterFrame = self.view.convert(CGRect(origin: CGPoint(x: clipFrame.minX, y: clipFrame.maxY - footerHeight), size: CGSize(width: clipFrame.width, height: footerHeight)), to: self.containerView) -// transition.setFrame(view: self.footerContainerView, frame: convertedFooterFrame) -// } -// -// private var boostState: InternalBoostState.DisplayData? -// func updated(transition: ComponentTransition, forceUpdate: Bool = false) { -// guard let controller = self.controller else { -// return -// } -// let contentSize = self.contentView.update( -// transition: transition, -// component: AnyComponent( -// BoostLevelsContainerComponent( -// context: controller.context, -// theme: self.presentationData.theme, -// strings: self.presentationData.strings, -// externalState: self.containerExternalState, -// peerId: controller.peerId, -// mode: controller.mode, -// status: controller.status, -// boostState: self.boostState, -// boost: { [weak controller] in -// guard let controller else { -// return -// } -// controller.node.updateBoostState() -// }, -// copyLink: { [weak self, weak controller] link in -// guard let self else { -// return -// } -// UIPasteboard.general.string = link -// -// if let previousController = controller?.navigationController?.viewControllers.reversed().first(where: { $0 !== controller }) as? ViewController { -// previousController.present(UndoOverlayController(presentationData: self.presentationData, content: .linkCopied(title: nil, text: self.presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: true, position: .top, animateInAsReplacement: false, action: { _ in return false }), in: .current) -// } -// }, -// dismiss: { [weak controller] in -// controller?.dismiss(animated: true) -// }, -// openStats: controller.openStats, -// openGift: controller.openGift, -// openPeer: controller.openPeer, -// updated: { [weak self] in -// self?.requestLayout(transition: .immediate) -// } -// ) -// ), -// environment: {}, -// forceUpdate: forceUpdate, -// containerSize: self.containerView.bounds.size -// ) -// self.contentView.frame = CGRect(origin: .zero, size: contentSize) -// -// let footerHeight = self.footerHeight -// -// let actionTitle: String -// if self.currentMyBoostCount > 0 { -// actionTitle = self.presentationData.strings.ChannelBoost_BoostAgain -// } else { -// actionTitle = self.containerExternalState.isGroup ? self.presentationData.strings.GroupBoost_BoostGroup : self.presentationData.strings.ChannelBoost_BoostChannel -// } -// -// let footerSize = self.footerView.update( -// transition: .immediate, -// component: AnyComponent( -// FooterComponent( -// context: controller.context, -// theme: self.presentationData.theme, -// title: actionTitle, -// action: { [weak self] in -// guard let self else { -// return -// } -// self.buttonPressed() -// } -// ) -// ), -// environment: {}, -// containerSize: CGSize(width: self.containerView.bounds.width, height: footerHeight) -// ) -// self.footerView.frame = CGRect(origin: .zero, size: footerSize) -// } -// -// private var didPlayAppearAnimation = false -// func updateIsVisible(isVisible: Bool) { -// if self.currentIsVisible == isVisible { -// return -// } -// self.currentIsVisible = isVisible -// -// guard let layout = self.currentLayout else { -// return -// } -// self.containerLayoutUpdated(layout: layout, transition: .immediate) -// -// if !self.didPlayAppearAnimation { -// self.didPlayAppearAnimation = true -// self.animateIn() -// } -// } -// -// private var footerHeight: CGFloat { -// if let mode = self.controller?.mode, case .owner = mode { -// return 0.0 -// } -// -// guard let layout = self.currentLayout else { -// return 58.0 -// } -// -// var footerHeight: CGFloat = 8.0 + 50.0 -// footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0 -// return footerHeight -// } -// -// private var defaultTopInset: CGFloat { -// guard let layout = self.currentLayout else { -// return 210.0 -// } -// if case .compact = layout.metrics.widthClass { -// let bottomPanelPadding: CGFloat = 12.0 -// let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding -// let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0 -// -// var defaultTopInset = layout.size.height - layout.size.width - 128.0 - panelHeight -// -// let containerTopInset = 10.0 + (layout.statusBarHeight ?? 0.0) -// let contentHeight = self.containerExternalState.contentHeight -// let footerHeight = self.footerHeight -// if contentHeight > 0.0 { -// let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0 -// if delta > 0.0 { -// defaultTopInset += delta -// } -// } -// return defaultTopInset -// } else { -// return 210.0 -// } -// } -// -// private func findVerticalScrollView(view: UIView?) -> UIScrollView? { -// if let view = view { -// if let view = view as? UIScrollView, view.contentSize.height > view.contentSize.width { -// return view -// } -// return findVerticalScrollView(view: view.superview) -// } else { -// return nil -// } -// } -// -// @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { -// guard let layout = self.currentLayout else { -// return -// } -// -// let isLandscape = layout.orientation == .landscape -// let edgeTopInset = isLandscape ? 0.0 : defaultTopInset -// -// switch recognizer.state { -// case .began: -// let point = recognizer.location(in: self.view) -// let currentHitView = self.hitTest(point, with: nil) -// -// var scrollView = self.findVerticalScrollView(view: currentHitView) -// if scrollView?.frame.height == self.frame.width { -// scrollView = nil -// } -// if scrollView?.isDescendant(of: self.view) == false { -// scrollView = nil -// } -// -// let topInset: CGFloat -// if self.isExpanded { -// topInset = 0.0 -// } else { -// topInset = edgeTopInset -// } -// -// self.panGestureArguments = (topInset, 0.0, scrollView) -// case .changed: -// guard let (topInset, panOffset, scrollView) = self.panGestureArguments else { -// return -// } -// let contentOffset = scrollView?.contentOffset.y ?? 0.0 -// -// var translation = recognizer.translation(in: self.view).y -// -// var currentOffset = topInset + translation -// -// let epsilon = 1.0 -// if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { -// scrollView.bounces = false -// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) -// } else if let scrollView = scrollView { -// translation = panOffset -// currentOffset = topInset + translation -// if self.isExpanded { -// recognizer.setTranslation(CGPoint(), in: self.view) -// } else if currentOffset > 0.0 { -// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) -// } -// } -// -// if scrollView == nil { -// translation = max(0.0, translation) -// } -// -// self.panGestureArguments = (topInset, translation, scrollView) -// -// if !self.isExpanded { -// if currentOffset > 0.0, let scrollView = scrollView { -// scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) -// } -// } -// -// var bounds = self.bounds -// if self.isExpanded { -// bounds.origin.y = -max(0.0, translation - edgeTopInset) -// } else { -// bounds.origin.y = -translation -// } -// bounds.origin.y = min(0.0, bounds.origin.y) -// self.bounds = bounds -// -// self.containerLayoutUpdated(layout: layout, transition: .immediate) -// case .ended: -// guard let (currentTopInset, panOffset, scrollView) = self.panGestureArguments else { -// return -// } -// self.panGestureArguments = nil -// -// let contentOffset = scrollView?.contentOffset.y ?? 0.0 -// -// let translation = recognizer.translation(in: self.view).y -// var velocity = recognizer.velocity(in: self.view) -// -// if self.isExpanded { -// if contentOffset > 0.1 { -// velocity = CGPoint() -// } -// } -// -// var bounds = self.bounds -// if self.isExpanded { -// bounds.origin.y = -max(0.0, translation - edgeTopInset) -// } else { -// bounds.origin.y = -translation -// } -// bounds.origin.y = min(0.0, bounds.origin.y) -// -// scrollView?.bounces = true -// -// let offset = currentTopInset + panOffset -// let topInset: CGFloat = edgeTopInset -// -// var dismissing = false -// if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) { -// self.controller?.dismiss(animated: true, completion: nil) -// dismissing = true -// } else if self.isExpanded { -// if velocity.y > 300.0 || offset > topInset / 2.0 { -// self.isExpanded = false -// if let scrollView = scrollView { -// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) -// } -// -// let distance = topInset - offset -// let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) -// let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) -// -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) -// } else { -// self.isExpanded = true -// -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) -// } -// } else if scrollView != nil, (velocity.y < -300.0 || offset < topInset / 2.0) { -// let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) -// let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) -// self.isExpanded = true -// -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) -// } else { -// if let scrollView = scrollView { -// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) -// } -// -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) -// } -// -// if !dismissing { -// var bounds = self.bounds -// let previousBounds = bounds -// bounds.origin.y = 0.0 -// self.bounds = bounds -// self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) -// } -// case .cancelled: -// self.panGestureArguments = nil -// -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) -// default: -// break -// } -// } -// -// func updateDismissOffset(_ offset: CGFloat) { -// guard self.isExpanded, let layout = self.currentLayout else { -// return -// } -// -// self.dismissOffset = offset -// self.containerLayoutUpdated(layout: layout, transition: .immediate) -// } -// -// func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { -// guard isExpanded != self.isExpanded else { -// return -// } -// self.dismissOffset = nil -// self.isExpanded = isExpanded -// -// guard let layout = self.currentLayout else { -// return -// } -// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) -// } -// -// private var currentMyBoostCount: Int32 = 0 -// private var myBoostCount: Int32 = 0 -// private var availableBoosts: [MyBoostStatus.Boost] = [] -// private var occupiedBoosts: [MyBoostStatus.Boost] = [] -// private let updatedState = Promise() -// -// private func updateBoostState() { -// guard let controller = self.controller else { -// return -// } -// let context = controller.context -// let peerId = controller.peerId -// let mode = controller.mode -// let status = controller.status -// let isPremium = controller.context.isPremium -// let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 })) -// let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0 -// let presentationData = self.presentationData -// let forceDark = controller.forceDark -// let boostStatusUpdated = controller.boostStatusUpdated -// -// if let _ = status?.nextLevelBoosts { -// if let availableBoost = self.availableBoosts.first { -// self.currentMyBoostCount += 1 -// self.myBoostCount += 1 -// -// let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]) -// |> deliverOnMainQueue).startStandalone(next: { [weak self] myBoostStatus in -// self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId) -// |> beforeNext { [weak self] boostStatus in -// if let self, let boostStatus, let myBoostStatus { -// Queue.mainQueue().async { -// self.controller?.boostStatusUpdated(boostStatus, myBoostStatus) -// } -// } -// } -// |> map { status in -// if let status { -// return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1)) -// } else { -// return nil -// } -// }) -// }) -// -// let _ = (self.updatedState.get() -// |> take(1) -// |> deliverOnMainQueue).startStandalone(next: { [weak self] state in -// guard let self, let state else { -// return -// } -// self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) -// self.updated(transition: .easeInOut(duration: 0.2)) -// -// self.animateSuccess() -// }) -// -// self.availableBoosts.removeFirst() -// } else if !self.occupiedBoosts.isEmpty, let myBoostStatus = controller.myBoostStatus { -// if canBoostAgain { -// let navigationController = controller.navigationController -// let openPeer = controller.openPeer -// -// var dismissReplaceImpl: (() -> Void)? -// let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in -// var sourcePeerIds = Set() -// var sourcePeers: [EnginePeer] = [] -// for boost in myBoostStatus.boosts { -// if slots.contains(boost.slot) { -// if let peer = boost.peer { -// if !sourcePeerIds.contains(peer.id) { -// sourcePeerIds.insert(peer.id) -// sourcePeers.append(peer) -// } -// } -// } -// } -// -// let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: { -// let _ = combineLatest( -// queue: Queue.mainQueue(), -// context.engine.peers.getChannelBoostStatus(peerId: peerId), -// context.engine.peers.getMyBoostStatus() -// ).startStandalone(next: { boostStatus, myBoostStatus in -// dismissReplaceImpl?() -// -// if let boostStatus, let myBoostStatus { -// boostStatusUpdated(boostStatus, myBoostStatus) -// } -// -// let levelsController = PremiumBoostLevelsScreen( -// context: context, -// peerId: peerId, -// mode: mode, -// status: boostStatus, -// myBoostStatus: myBoostStatus, -// replacedBoosts: (Int32(slots.count), sourcePeers), -// openStats: nil, -// openGift: nil, -// openPeer: openPeer, -// forceDark: forceDark -// ) -// levelsController.boostStatusUpdated = boostStatusUpdated -// if let navigationController { -// navigationController.pushViewController(levelsController, animated: true) -// } -// }) -// }) -// }) -// -// if let navigationController = controller.navigationController { -// controller.dismiss(animated: true) -// navigationController.pushViewController(replaceController, animated: true) -// } -// -// dismissReplaceImpl = { [weak replaceController] in -// replaceController?.dismiss(animated: true) -// } -// } else if let boost = self.occupiedBoosts.first, let occupiedPeer = boost.peer { -// if let cooldown = boost.cooldownUntil { -// let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) -// let timeout = cooldown - currentTime -// let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false) -// let alertController = textAlertController( -// sharedContext: context.sharedContext, -// updatedPresentationData: nil, -// title: presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle, -// text: self.containerExternalState.isGroup ? presentationData.strings.GroupBoost_Error_BoostTooOftenText(valueText).string : presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string, -// actions: [ -// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) -// ], -// parseMarkdown: true -// ) -// controller.present(alertController, in: .window(.root)) -// } else { -// let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) -// |> deliverOnMainQueue).start(next: { [weak controller] peer in -// guard let peer, let controller else { -// return -// } -// let replaceController = replaceBoostConfirmationController(context: context, fromPeers: [occupiedPeer], toPeer: peer, commit: { [weak self] in -// self?.currentMyBoostCount += 1 -// self?.myBoostCount += 1 -// let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [boost.slot]) -// |> deliverOnMainQueue).startStandalone(completed: { [weak self] in -// guard let self else { -// return -// } -// let _ = (self.updatedState.get() -// |> take(1) -// |> deliverOnMainQueue).startStandalone(next: { [weak self] state in -// guard let self, let state else { -// return -// } -// self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount) -// self.updated(transition: .easeInOut(duration: 0.2)) -// -// self.animateSuccess() -// }) -// }) -// }) -// controller.present(replaceController, in: .window(.root)) -// }) -// } -// } else { -// controller.dismiss(animated: true, completion: nil) -// } -// } else { -// if isPremium { -// if !canBoostAgain { -// controller.dismiss(animated: true, completion: nil) -// } else { -// let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) -// |> deliverOnMainQueue).start(next: { [weak controller] peer in -// guard let peer, let controller else { -// return -// } -// let alertController = textAlertController( -// sharedContext: context.sharedContext, -// updatedPresentationData: nil, -// title: presentationData.strings.ChannelBoost_MoreBoosts_Title, -// text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string, -// actions: [ -// TextAlertAction(type: .defaultAction, title: presentationData.strings.ChannelBoost_MoreBoosts_Gift, action: { [weak controller] in -// if let navigationController = controller?.navigationController { -// controller?.dismiss(animated: true, completion: nil) -// -// Queue.mainQueue().after(0.4) { -// let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil) -// navigationController.pushViewController(giftController, animated: true) -// } -// } -// }), -// TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Close, action: {}) -// ], -// actionLayout: .vertical, -// parseMarkdown: true -// ) -// controller.present(alertController, in: .window(.root)) -// }) -// } -// } else { -// let alertController = textAlertController( -// sharedContext: context.sharedContext, -// updatedPresentationData: nil, -// title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle, -// text: self.containerExternalState.isGroup ? presentationData.strings.GroupBoost_Error_PremiumNeededText : presentationData.strings.ChannelBoost_Error_PremiumNeededText, -// actions: [ -// TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), -// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { [weak controller] in -// if let navigationController = controller?.navigationController { -// controller?.dismiss(animated: true) -// -// let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil) -// navigationController.pushViewController(premiumController, animated: true) -// } -// }) -// ], -// parseMarkdown: true -// ) -// controller.present(alertController, in: .window(.root)) -// } -// } -// } else { -// controller.dismiss(animated: true) -// } -// } -// -// func buttonPressed() { -// self.updateBoostState() -// } -// -// private func animateSuccess() { -// self.hapticFeedback.impact() -// self.view.addSubview(ConfettiView(frame: self.view.bounds)) -// -// if self.isExpanded { -// self.update(isExpanded: false, transition: .animated(duration: 0.4, curve: .spring)) -// } -// } -// } -// -// var node: Node { -// return self.displayNode as! Node -// } -// -// private let context: AccountContext -// private let peerId: EnginePeer.Id -// private let mode: Mode -// private let status: ChannelBoostStatus? -// private let myBoostStatus: MyBoostStatus? -// private let replacedBoosts: (Int32, [EnginePeer])? -// private let openStats: (() -> Void)? -// private let openGift: (() -> Void)? -// private let openPeer: ((EnginePeer) -> Void)? -// private let forceDark: Bool -// -// private var currentLayout: ContainerViewLayout? -// -// public var boostStatusUpdated: (ChannelBoostStatus, MyBoostStatus) -> Void = { _, _ in } -// public var disposed: () -> Void = {} -// -// public init( -// context: AccountContext, -// peerId: EnginePeer.Id, -// mode: Mode, -// status: ChannelBoostStatus?, -// myBoostStatus: MyBoostStatus? = nil, -// replacedBoosts: (Int32, [EnginePeer])? = nil, -// openStats: (() -> Void)? = nil, -// openGift: (() -> Void)? = nil, -// openPeer: ((EnginePeer) -> Void)? = nil, -// forceDark: Bool = false -// ) { -// self.context = context -// self.peerId = peerId -// self.mode = mode -// self.status = status -// self.myBoostStatus = myBoostStatus -// self.replacedBoosts = replacedBoosts -// self.openStats = openStats -// self.openGift = openGift -// self.openPeer = openPeer -// self.forceDark = forceDark -// -// super.init(navigationBarPresentationData: nil) -// -// self.navigationPresentation = .flatModal -// self.statusBar.statusBarStyle = .Ignore -// -// self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) -// } -// -// required public init(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// deinit { -// self.disposed() -// } -// -//} - -//private final class FooterComponent: Component { -// let context: AccountContext -// let theme: PresentationTheme -// let title: String -// let action: () -> Void -// -// init(context: AccountContext, theme: PresentationTheme, title: String, action: @escaping () -> Void) { -// self.context = context -// self.theme = theme -// self.title = title -// self.action = action -// } -// -// static func ==(lhs: FooterComponent, rhs: FooterComponent) -> Bool { -// if lhs.context !== rhs.context { -// return false -// } -// if lhs.theme !== rhs.theme { -// return false -// } -// if lhs.title != rhs.title { -// return false -// } -// return true -// } -// -// final class View: UIView { -// let backgroundView: BlurredBackgroundView -// let separator = SimpleLayer() -// -// private let button = ComponentView() -// -// private var component: FooterComponent? -// private weak var state: EmptyComponentState? -// -// override init(frame: CGRect) { -// self.backgroundView = BlurredBackgroundView(color: nil) -// -// super.init(frame: frame) -// -// self.backgroundView.clipsToBounds = true -// -// self.addSubview(self.backgroundView) -// self.layer.addSublayer(self.separator) -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// func update(component: FooterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { -// self.component = component -// self.state = state -// -// let bounds = CGRect(origin: .zero, size: availableSize) -// -// self.backgroundView.updateColor(color: component.theme.rootController.tabBar.backgroundColor, transition: transition.containedViewLayoutTransition) -// self.backgroundView.update(size: bounds.size, transition: transition.containedViewLayoutTransition) -// transition.setFrame(view: self.backgroundView, frame: bounds) -// -// self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor -// transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel))) -// -// let gradientColors = [ -// UIColor(rgb: 0x0077ff), -// UIColor(rgb: 0x6b93ff), -// UIColor(rgb: 0x8878ff), -// UIColor(rgb: 0xe46ace) -// ] -// -// let buttonSize = self.button.update( -// transition: .immediate, -// component: AnyComponent( -// SolidRoundedButtonComponent( -// title: component.title, -// theme: SolidRoundedButtonComponent.Theme( -// backgroundColor: .black, -// backgroundColors: gradientColors, -// foregroundColor: .white -// ), -// font: .bold, -// fontSize: 17.0, -// height: 50.0, -// cornerRadius: 10.0, -// gloss: true, -// iconName: "Premium/BoostChannel", -// animationName: nil, -// iconPosition: .left, -// action: { -// component.action() -// } -// ) -// ), -// environment: {}, -// containerSize: CGSize(width: availableSize.width - 32.0, height: availableSize.height) -// ) -// -// if let view = self.button.view { -// if view.superview == nil { -// self.addSubview(view) -// } -// let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: buttonSize) -// view.frame = buttonFrame -// } -// -// return availableSize -// } -// } -// -// func makeView() -> View { -// return View(frame: CGRect()) -// } -// -// func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { -// return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) -// } -//} - private struct InternalBoostState: Equatable { let level: Int32 let currentLevelBoosts: Int32 diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index def7028de2..e9fe7c4fd2 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -13,11 +12,12 @@ import ViewControllerComponent import SheetComponent import MultilineTextComponent import BundleIconComponent -import SolidRoundedButtonComponent +import ButtonComponent import BlurredBackgroundComponent import Markdown import TelegramUIPreferences import GlassBarButtonComponent +import LottieComponent public final class PremiumGradientBackgroundComponent: Component { public let colors: [UIColor] @@ -556,21 +556,10 @@ private final class DemoSheetContent: CombinedComponent { EngineDataMap(accountSpecificStickerOverrides.map(\.messageId).map(TelegramEngine.EngineData.Item.Messages.Message.init)) ) - let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) self.disposable = (combineLatest( queue: Queue.mainQueue(), self.context.engine.stickers.availableReactions(), - self.context.account.postbox.combinedView(keys: [stickersKey]) - |> map { views -> [OrderedItemListEntry]? in - if let view = views.views[stickersKey] as? OrderedItemListView { - return view.items - } else { - return nil - } - } - |> filter { items in - return items != nil - } + self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudPremiumStickers)) |> take(1), self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), @@ -604,11 +593,9 @@ private final class DemoSheetContent: CombinedComponent { if let reactions = reactions { var result: [TelegramMediaFile] = [] - if let items = items { - for item in items { - if let mediaItem = item.contents.get(RecentMediaItem.self) { - result.append(mediaItem.media._parse()) - } + for item in items { + if let mediaItem = item.contents.get(RecentMediaItem.self) { + result.append(mediaItem.media._parse()) } } return (reactions.reactions.filter({ $0.isPremium }).map { reaction -> AvailableReactions.Reaction in @@ -685,7 +672,7 @@ private final class DemoSheetContent: CombinedComponent { let closeButton = Child(GlassBarButtonComponent.self) let background = Child(PremiumGradientBackgroundComponent.self) let pager = Child(DemoPagerComponent.self) - let button = Child(SolidRoundedButtonComponent.self) + let button = Child(ButtonComponent.self) let measureText = Child(MultilineTextComponent.self) return { context in @@ -1340,28 +1327,46 @@ private final class DemoSheetContent: CombinedComponent { } let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let premiumGradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + var buttonTitle: [AnyComponentWithIdentity] = [] + buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ButtonTextContentComponent( + text: buttonText, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: premiumGradientColors[0] + )))) + if isStandalone, let buttonAnimationName { + buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: buttonAnimationName), + color: .white, + startingPosition: .begin, + size: CGSize(width: 30.0, height: 30.0), + loop: true + )))) + } let button = button.update( - component: SolidRoundedButtonComponent( - title: buttonText, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - foregroundColor: .white + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: premiumGradientColors[0], + foreground: .white, + pressedColor: premiumGradientColors[0], + isShimmering: state.isPremium != true, + gradient: ButtonComponent.Background.Gradient( + colors: premiumGradientColors, + animation: .horizontalShift(duration: 4.5) + ) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("\(buttonText)-\(isStandalone ? buttonAnimationName ?? "" : "")"), + component: AnyComponent(HStack(buttonTitle, spacing: 4.0)) ), - font: .bold, - fontSize: 17.0, - height: 52.0, - cornerRadius: 26.0, - gloss: state.isPremium != true, - glass: true, - animationName: isStandalone ? buttonAnimationName : nil, - iconPosition: .right, - iconSpacing: 4.0, action: { [weak component, weak state] in guard let component = component else { return diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index c11551e0ef..7d95e05186 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -4,15 +4,13 @@ import Display import ComponentFlow import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import PresentationDataUtils import ViewControllerComponent import AccountContext -import SolidRoundedButtonComponent import MultilineTextComponent import BundleIconComponent -import SolidRoundedButtonComponent +import ButtonComponent import BlurredBackgroundComponent import Markdown import InAppPurchaseManager @@ -142,7 +140,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { jsonString += "]}}" if let data = jsonString.data(using: .utf8), let json = JSON(data: data) { - addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium_gift.promo_screen_show", data: json) + strongSelf.context.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_show", data: json) } } @@ -154,13 +152,13 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { let _ = updatePremiumPromoConfigurationOnce(account: context.account).start() - let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) + let stickersKey: EngineRawPostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) self.stickersDisposable = (self.context.account.postbox.combinedView(keys: [stickersKey]) |> deliverOnMainQueue).start(next: { [weak self] views in guard let strongSelf = self else { return } - if let view = views.views[stickersKey] as? OrderedItemListView { + if let view = views.views[stickersKey] as? EngineRawOrderedItemListView { for item in view.items { if let mediaItem = item.contents.get(RecentMediaItem.self) { let file = mediaItem.media._parse() @@ -568,7 +566,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { controller?.dismiss(animated: true, completion: nil) } - addAppLogEvent(postbox: accountContext.account.postbox, type: "premium_gift.promo_screen_tap", data: ["item": perk.identifier]) + accountContext.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_tap", data: ["item": perk.identifier]) } )) i += 1 @@ -900,7 +898,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent { let (currency, amount) = product.storeProduct.priceCurrencyAndAmount let duration = product.months - addAppLogEvent(postbox: self.context.account.postbox, type: "premium_gift.promo_screen_accept") + self.context.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_accept") self.inProgress = true self.updateInProgress(true) @@ -968,7 +966,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent { } if let errorText = errorText { - addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium_gift.promo_screen_fail") + strongSelf.context.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_fail") let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) strongSelf.present(alertController) @@ -1016,7 +1014,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent { let secondaryTitle = Child(MultilineTextComponent.self) let bottomPanel = Child(BlurredBackgroundComponent.self) let bottomSeparator = Child(Rectangle.self) - let button = Child(SolidRoundedButtonComponent.self) + let button = Child(ButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self].value @@ -1234,28 +1232,38 @@ private final class PremiumGiftScreenComponent: CombinedComponent { buttonText = environment.strings.Premium_Gift_GiftSubscription(price ?? "—").string } + let buttonGradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] let button = button.update( - component: SolidRoundedButtonComponent( - title: buttonText, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: UIColor(rgb: 0x8878ff), - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - foregroundColor: .white + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: buttonGradientColors[0], + foreground: .white, + pressedColor: buttonGradientColors[0], + isShimmering: gloss, + gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors) ), - height: 50.0, - cornerRadius: 11.0, - gloss: gloss, - isLoading: state.inProgress, + content: AnyComponentWithIdentity( + id: AnyHashable(buttonText), + component: AnyComponent(ButtonTextContentComponent( + text: buttonText, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: buttonGradientColors[0] + )) + ), + displaysProgress: state.inProgress, action: { state.buy() } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 52.0), transition: context.transition) let bottomPanel = bottomPanel.update( diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 7eabf03dfd..e825015ca7 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -10,7 +10,6 @@ import TelegramPresentationData import PresentationDataUtils import ViewControllerComponent import AccountContext -import SolidRoundedButtonComponent import ButtonComponent import MultilineTextComponent import MultilineTextWithEntitiesComponent @@ -329,8 +328,8 @@ public enum PremiumSource: Equatable { } else { return false } - case let .auth(lhsPrice): - if case let .auth(rhsPrice) = rhs, lhsPrice == rhsPrice { + case let .auth(lhsPrice, lhsDays): + if case let .auth(rhsPrice, rhsDays) = rhs, lhsPrice == rhsPrice, lhsDays == rhsDays { return true } else { return false @@ -391,7 +390,7 @@ public enum PremiumSource: Equatable { case todo case copyProtection case aiTools - case auth(String) + case auth(String, Int32) case premiumGift(TelegramMediaFile) var identifier: String? { @@ -1682,7 +1681,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { jsonString += "]}}" if let context = screenContext.context, let data = jsonString.data(using: .utf8), let json = JSON(data: data) { - addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_show", data: json) + context.engine.accountData.addAppLogEvent(type: "premium.promo_screen_show", data: json) } } }) @@ -2260,7 +2259,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } updateIsFocused(true) - addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) + accountContext.engine.accountData.addAppLogEvent(type: "premium.promo_screen_tap", data: ["item": perk.identifier]) }, highlighting: accountContext != nil ? .default : .disabled )))) @@ -3262,7 +3261,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } if let context = self.screenContext.context { - addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_accept") + context.engine.accountData.addAppLogEvent(type: "premium.promo_screen_accept") } self.inProgress = true @@ -3316,7 +3315,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { self.updated(transition: .immediate) if let context = self.screenContext.context { - addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail") + context.engine.accountData.addAppLogEvent(type: "premium.promo_screen_fail") } let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown @@ -3368,7 +3367,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if let errorText = errorText { if let context = self.screenContext.context { - addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail") + context.engine.accountData.addAppLogEvent(type: "premium.promo_screen_fail") } let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) @@ -3416,7 +3415,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let title = Child(MultilineTextComponent.self) let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self) let bottomEdgeEffect = Child(EdgeEffectComponent.self) - let button = Child(SolidRoundedButtonComponent.self) + let button = Child(ButtonComponent.self) var updatedInstalled: Bool? @@ -3645,8 +3644,16 @@ private final class PremiumIntroScreenComponent: CombinedComponent { let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in return false - }, actionPerformed: { added in - updatedInstalled = added + }, actionPerformed: { actions in + guard let action = actions.first?.action else { + return + } + switch action { + case .add: + updatedInstalled = true + case .remove: + updatedInstalled = false + } }) presentController(controller) break @@ -3786,9 +3793,16 @@ private final class PremiumIntroScreenComponent: CombinedComponent { if !buttonIsHidden { let buttonTitle: String var buttonSubtitle: String? - if case let .auth(price) = context.component.source { + if case let .auth(price, days) = context.component.source { buttonTitle = environment.strings.Premium_Week_SignUp(price).string - buttonSubtitle = environment.strings.Premium_Week_SignUpInfo + if days == 7 { + buttonSubtitle = environment.strings.Premium_Week_SignUpInfo + } else if days > 0 { + let daysString = environment.strings.Premium_SignUp_SignUpNewInfo_Days(days) + buttonSubtitle = environment.strings.Premium_SignUp_SignUpNewInfo(daysString).string + } else { + buttonSubtitle = environment.strings.Premium_SignUp_SignUpNewInfoNone + } } else if isUnusedGift { buttonTitle = environment.strings.Premium_Gift_ApplyLink } else if state.isPremium == true && state.canUpgrade { @@ -3810,25 +3824,51 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } let controller = environment.controller + let buttonGradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] + let buttonContent: AnyComponent + if let buttonSubtitle { + buttonContent = AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(Text( + text: buttonTitle, + font: Font.semibold(17.0), + color: .white + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(Text( + text: buttonSubtitle, + font: Font.medium(11.0), + color: UIColor.white.withAlphaComponent(0.7) + ))) + ], spacing: 1.0)) + } else { + buttonContent = AnyComponent(ButtonTextContentComponent( + text: buttonTitle, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: buttonGradientColors[0] + )) + } let button = button.update( - component: SolidRoundedButtonComponent( - title: buttonTitle, - subtitle: buttonSubtitle, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: UIColor(rgb: 0x8878ff), - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - foregroundColor: .white + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: UIColor(rgb: 0x8878ff), + foreground: .white, + pressedColor: UIColor(rgb: 0x8878ff).withMultipliedAlpha(0.8), + cornerRadius: 26.0, + isShimmering: true, + gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors) ), - height: 52.0, - cornerRadius: 26.0, - gloss: true, - glass: true, - isLoading: state.inProgress, + content: AnyComponentWithIdentity( + id: AnyHashable("\(buttonTitle)-\(buttonSubtitle ?? "")"), + component: buttonContent + ), + displaysProgress: state.inProgress, action: { if let controller = controller() as? PremiumIntroScreen, let customProceed = controller.customProceed { controller.dismiss() diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index b64edab58f..d60ecf31bd 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -13,6 +13,8 @@ import SheetComponent import MultilineTextComponent import BundleIconComponent import SolidRoundedButtonComponent +import ButtonComponent +import LottieComponent import Markdown import BalancedTextComponent import ConfettiEffect @@ -448,6 +450,34 @@ public class PremiumLimitDisplayComponent: Component { } view.frame = CGRect(origin: CGPoint(x: activityPosition - 12.0 - inactiveValueSize.width, y: floorToScreenPixels((lineHeight - inactiveValueSize.height) / 2.0)), size: inactiveValueSize) } + + let activeValueSize = self.activeValueLabel.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: component.activeValue, + font: Font.semibold(15.0), + textColor: rightTextColor + ) + ) + ) + ), + environment: {}, + containerSize: availableSize + ) + let activeValueLabelFrame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize) + if let view = self.activeValueLabel.view { + if view.superview == nil { + self.container.addSubview(view) + + if component.invertProgress { + self.container.bringSubviewToFront(self.activeContainer) + } + } + view.frame = activeValueLabelFrame + } let activeTitleSize = self.activeTitleLabel.update( transition: .immediate, @@ -470,33 +500,12 @@ public class PremiumLimitDisplayComponent: Component { self.container.addSubview(view) } view.frame = CGRect(origin: CGPoint(x: activityPosition + 12.0, y: floorToScreenPixels((lineHeight - activeTitleSize.height) / 2.0)), size: activeTitleSize) - } - - let activeValueSize = self.activeValueLabel.update( - transition: .immediate, - component: AnyComponent( - MultilineTextComponent( - text: .plain( - NSAttributedString( - string: component.activeValue, - font: Font.semibold(15.0), - textColor: rightTextColor - ) - ) - ) - ), - environment: {}, - containerSize: availableSize - ) - if let view = self.activeValueLabel.view { - if view.superview == nil { - self.container.addSubview(view) - - if component.invertProgress { - self.container.bringSubviewToFront(self.activeContainer) - } + + if view.frame.maxX > activeValueLabelFrame.minX - 8.0 { + view.alpha = 0.0 + } else { + view.alpha = 1.0 } - view.frame = CGRect(origin: CGPoint(x: containerFrame.width - 12.0 - activeValueSize.width, y: floorToScreenPixels((lineHeight - activeValueSize.height) / 2.0)), size: activeValueSize) } } @@ -825,7 +834,7 @@ private final class LimitSheetContent: CombinedComponent { let alternateText = Child(List.self) let limit = Child(PremiumLimitDisplayComponent.self) let linkButton = Child(SolidRoundedButtonComponent.self) - let button = Child(SolidRoundedButtonComponent.self) + let button = Child(ButtonComponent.self) let peerShortcut = Child(Button.self) let statsButton = Child(Button.self) @@ -1449,23 +1458,44 @@ private final class LimitSheetContent: CombinedComponent { let isIncreaseButton = !reachedMaximumLimit && !isPremiumDisabled let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let buttonTitle = actionButtonText ?? (isIncreaseButton ? strings.Premium_IncreaseLimit : strings.Common_OK) + var buttonContentItems: [AnyComponentWithIdentity] = [] + if let buttonIconName { + buttonContentItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent( + name: buttonIconName, + tintColor: .white + )))) + } + buttonContentItems.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(ButtonTextContentComponent( + text: buttonTitle, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: buttonGradientColors[0] + )))) + if buttonIconName == nil, isIncreaseButton, let buttonAnimationName { + buttonContentItems.append(AnyComponentWithIdentity(id: "animation", component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: buttonAnimationName), + color: .white, + startingPosition: .begin, + size: CGSize(width: 30.0, height: 30.0), + loop: true + )))) + } let button = button.update( - component: SolidRoundedButtonComponent( - title: actionButtonText ?? (isIncreaseButton ? strings.Premium_IncreaseLimit : strings.Common_OK), - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: buttonGradientColors, - foregroundColor: .white + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: buttonGradientColors[0], + foreground: .white, + pressedColor: buttonGradientColors[0], + isShimmering: isIncreaseButton && actionButtonHasGloss, + gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("\(buttonTitle)-\(buttonIconName ?? "")-\(isIncreaseButton ? buttonAnimationName ?? "" : "")"), + component: AnyComponent(HStack(buttonContentItems, spacing: 4.0)) ), - font: .bold, - fontSize: 17.0, - height: 52.0, - cornerRadius: 26.0, - gloss: isIncreaseButton && actionButtonHasGloss, - glass: true, - iconName: buttonIconName, - animationName: isIncreaseButton ? buttonAnimationName : nil, - iconPosition: buttonIconName != nil ? .left : .right, action: { if isIncreaseButton { if component.action() { diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index f5c6e6af7b..5b24d6a6e0 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import AccountContext import TelegramPresentationData @@ -103,20 +102,9 @@ public class PremiumLimitsListScreen: ViewController { self.appIcons = controller.context.sharedContext.applicationBindings.getAvailableAlternateIcons() - let stickersKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudPremiumStickers) self.disposable = (combineLatest( queue: Queue.mainQueue(), - context.account.postbox.combinedView(keys: [stickersKey]) - |> map { views -> [OrderedItemListEntry]? in - if let view = views.views[stickersKey] as? OrderedItemListView { - return view.items - } else { - return nil - } - } - |> filter { items in - return items != nil - } + context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudPremiumStickers)) |> take(1), context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), @@ -137,11 +125,9 @@ public class PremiumLimitsListScreen: ViewController { } var result: [TelegramMediaFile.Accessor] = [] - if let items = items { - for item in items { - if let mediaItem = item.contents.get(RecentMediaItem.self) { - result.append(mediaItem.media) - } + for item in items { + if let mediaItem = item.contents.get(RecentMediaItem.self) { + result.append(mediaItem.media) } } return (result.map { file -> TelegramMediaFile in diff --git a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift index 7fc6496107..af9c412f2b 100644 --- a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift @@ -13,6 +13,7 @@ import BundleIconComponent import BalancedTextComponent import MultilineTextComponent import SolidRoundedButtonComponent +import ButtonComponent import LottieComponent import AccountContext import GlassBarButtonComponent @@ -110,7 +111,7 @@ private final class SheetContent: CombinedComponent { let premiumTitle = Child(BalancedTextComponent.self) let premiumText = Child(BalancedTextComponent.self) - let premiumButton = Child(SolidRoundedButtonComponent.self) + let premiumButton = Child(ButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self] @@ -342,28 +343,32 @@ private final class SheetContent: CombinedComponent { contentSize.height += premiumText.size.height contentSize.height += spacing + 5.0 + let premiumGradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] let premiumButton = premiumButton.update( - component: SolidRoundedButtonComponent( - title: premiumButtonTitle, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - foregroundColor: .white + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: premiumGradientColors[0], + foreground: .white, + pressedColor: premiumGradientColors[0], + isShimmering: false, + gradient: ButtonComponent.Background.Gradient(colors: premiumGradientColors) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(premiumButtonTitle), + component: AnyComponent(ButtonTextContentComponent( + text: premiumButtonTitle, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: premiumGradientColors[0] + )) ), - font: .bold, - fontSize: 17.0, - height: 52.0, - cornerRadius: 26.0, - gloss: false, - glass: true, - iconName: nil, - animationName: nil, - iconPosition: .left, action: { component.openPremiumIntro() component.dismiss() diff --git a/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift b/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift index 22b4fe779e..43eff73bee 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostConfirmationController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift index 4b12336fea..bd014a9bdb 100644 --- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift @@ -116,7 +116,7 @@ private class StickerNode: ASDisplayNode { let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 240.0, height: 240.0)) - let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .other, file: file, small: false, size: fittedDimensions)) @@ -133,7 +133,7 @@ private class StickerNode: ASDisplayNode { additionalAnimationNode = DirectAnimatedStickerNode() var pathPrefix: String? - pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id) + pathPrefix = context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(effect.resource.id)) pathPrefix = nil additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 1.5), height: Int(fittedDimensions.height * 1.5), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) @@ -335,6 +335,7 @@ private class StickersCarouselNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false self.scrollNode.view.canCancelContentTouches = true + self.scrollNode.view.scrollsToTop = false self.tapNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.stickerTapped(_:)))) } diff --git a/submodules/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD index b1861ea36d..69e2a085c5 100644 --- a/submodules/PresentationDataUtils/BUILD +++ b/submodules/PresentationDataUtils/BUILD @@ -12,15 +12,14 @@ swift_library( deps = [ "//submodules/Display:Display", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AlertUI:AlertUI", "//submodules/ItemListUI:ItemListUI", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/OverlayStatusController:OverlayStatusController", - "//submodules/UrlWhitelist:UrlWhitelist", "//submodules/TelegramUI/Components/AlertComponent", - "//submodules/UrlHandling", ], visibility = [ "//visibility:public", diff --git a/submodules/PromptUI/BUILD b/submodules/PromptUI/BUILD index 80d83f292f..66de9a799b 100644 --- a/submodules/PromptUI/BUILD +++ b/submodules/PromptUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift index d359be83de..27f583cee8 100644 --- a/submodules/PromptUI/Sources/PromptController.swift +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/QrCodeUI/BUILD b/submodules/QrCodeUI/BUILD index e7eb7e221b..6bb6fe0349 100644 --- a/submodules/QrCodeUI/BUILD +++ b/submodules/QrCodeUI/BUILD @@ -11,6 +11,7 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", + "//submodules/MtProtoKit:MtProtoKit", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", @@ -22,7 +23,6 @@ swift_library( "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/GlassButtonNode:GlassButtonNode", "//submodules/TextFormat:TextFormat", "//submodules/Markdown:Markdown", "//submodules/UndoUI:UndoUI", @@ -34,12 +34,15 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Components/SheetComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/Components/BundleIconComponent", "//submodules/Components/BalancedTextComponent", "//submodules/Components/MultilineTextComponent", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/SegmentControlComponent", + "//submodules/UrlEscaping:UrlEscaping", ], visibility = [ "//visibility:public", diff --git a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift b/submodules/QrCodeUI/Sources/GlassButtonNode.swift similarity index 72% rename from submodules/GlassButtonNode/Sources/GlassButtonNode.swift rename to submodules/QrCodeUI/Sources/GlassButtonNode.swift index d1f39743df..9bdb0d776d 100644 --- a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift +++ b/submodules/QrCodeUI/Sources/GlassButtonNode.swift @@ -3,6 +3,8 @@ import Display import UIKit import AsyncDisplayKit import SwiftSignalKit +import ComponentFlow +import GlassBackgroundComponent private let largeButtonSize = CGSize(width: 72.0, height: 72.0) private let smallButtonSize = CGSize(width: 60.0, height: 60.0) @@ -29,15 +31,15 @@ private func generateEmptyButtonImage(icon: UIImage?, strokeColor: UIColor?, fil } let imageSize = icon.size let imageRect = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.width - imageSize.height) / 2.0)), size: imageSize) + + context.setBlendMode(.copy) + context.clip(to: imageRect, mask: icon.cgImage!) if knockout { - context.setBlendMode(.copy) - context.clip(to: imageRect, mask: icon.cgImage!) context.setFillColor(UIColor.clear.cgColor) - context.fill(imageRect) } else { - context.setBlendMode(.normal) - context.draw(icon.cgImage!, in: imageRect) + context.setFillColor(UIColor.white.cgColor) } + context.fill(imageRect) } }) } @@ -60,45 +62,44 @@ private func generateFilledButtonImage(color: UIColor, icon: UIImage?, angle: CG }) } -private let emptyHighlightedFill = UIColor(white: 1.0, alpha: 0.3) -private let invertedFill = UIColor(white: 1.0, alpha: 1.0) - private let largeLabelFont = Font.regular(14.5) private let smallLabelFont = Font.regular(11.5) -public final class GlassButtonNode: HighlightTrackingButtonNode { +final class GlassButtonNode: ASDisplayNode { private var regularImage: UIImage? - private var highlightedImage: UIImage? private var filledImage: UIImage? - private let blurView: UIVisualEffectView + private let backgroundView: GlassBackgroundView private let iconNode: ASImageNode private var labelNode: ImmediateTextNode? - public init(icon: UIImage, label: String?) { - let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - blurView.clipsToBounds = true - blurView.isUserInteractionEnabled = false - self.blurView = blurView + private let button: HighlightTrackingButton + + var pressed: () -> Void = {} + + init(icon: UIImage, label: String?) { + self.backgroundView = GlassBackgroundView() + + self.button = HighlightTrackingButton() self.iconNode = ASImageNode() - self.iconNode.isLayerBacked = true self.iconNode.displayWithoutProcessing = false self.iconNode.displaysAsynchronously = false + self.iconNode.isUserInteractionEnabled = false self.regularImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: .clear, buttonSize: largeButtonSize) - self.highlightedImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: emptyHighlightedFill, buttonSize: largeButtonSize) - self.filledImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: invertedFill, knockout: true, buttonSize: largeButtonSize) + self.filledImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: .white, knockout: true, buttonSize: largeButtonSize) if let label = label { let labelNode = ImmediateTextNode() let labelFont: UIFont - if let image = regularImage, image.size.width < 70.0 { + if let image = self.regularImage, image.size.width < 70.0 { labelFont = smallLabelFont } else { labelFont = largeLabelFont } labelNode.attributedText = NSAttributedString(string: label, font: labelFont, textColor: .white) + labelNode.isUserInteractionEnabled = false self.labelNode = labelNode } else { self.labelNode = nil @@ -106,38 +107,34 @@ public final class GlassButtonNode: HighlightTrackingButtonNode { super.init() - self.view.addSubview(blurView) - self.addSubnode(self.iconNode) + self.view.addSubview(self.backgroundView) + self.backgroundView.contentView.addSubview(self.button) + self.backgroundView.contentView.addSubview(self.iconNode.view) if let labelNode = self.labelNode { - self.addSubnode(labelNode) + self.backgroundView.contentView.addSubview(labelNode.view) } - self.iconNode.image = regularImage - self.currentImage = regularImage + self.iconNode.image = self.regularImage + self.currentImage = self.regularImage - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - strongSelf.internalHighlighted = highlighted - strongSelf.updateState(highlighted: highlighted, selected: strongSelf.isSelected) - } - } + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) } - private var internalHighlighted = false + @objc private func buttonPressed() { + self.pressed() + } - override public var isSelected: Bool { + var isSelected: Bool = false { didSet { - self.updateState(highlighted: self.internalHighlighted, selected: self.isSelected) + self.updateState(selected: self.isSelected) } } private var currentImage: UIImage? - private func updateState(highlighted: Bool, selected: Bool) { + private func updateState(selected: Bool) { let image: UIImage? if selected { image = self.filledImage - } else if highlighted { - image = self.highlightedImage } else { image = self.regularImage } @@ -160,8 +157,10 @@ public final class GlassButtonNode: HighlightTrackingButtonNode { let size = self.bounds.size - self.blurView.layer.cornerRadius = size.width / 2.0 - blurView.frame = self.bounds + self.button.frame = self.bounds + + self.backgroundView.frame = self.bounds + self.backgroundView.update(size: size, cornerRadius: size.width / 2.0, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate) self.iconNode.frame = self.bounds diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index 7ede3135e3..bee53f957d 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -3,9 +3,9 @@ import UIKit import AccountContext import AsyncDisplayKit import Display +import ComponentFlow import SwiftSignalKit import Camera -import GlassButtonNode import CoreImage import AlertUI import TelegramPresentationData @@ -18,6 +18,8 @@ import LegacyComponents import LegacyMediaPickerUI import ImageContentAnalysis import PresentationDataUtils +import BundleIconComponent +import GlassBarButtonComponent private func parseAuthTransferUrl(_ url: URL) -> Data? { var tokenString: String? @@ -77,18 +79,14 @@ public final class QrCodeScanScreen: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: .white, accentForegroundColor: .black) - - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) + super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .White self.navigationPresentation = .modalInLargeLayout self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.navigationBar?.intrinsicCanTransitionInline = false - - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - + self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground |> deliverOnMainQueue).start(next: { [weak self] inForeground in guard let strongSelf = self else { @@ -96,14 +94,6 @@ public final class QrCodeScanScreen: ViewController { } (strongSelf.displayNode as! QrCodeScanScreenNode).updateInForeground(inForeground) }) - - if case .custom = subject { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - } else { - #if DEBUG - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, target: self, action: #selector(self.testPressed)) - #endif - } } required init(coder aDecoder: NSCoder) { @@ -116,19 +106,11 @@ public final class QrCodeScanScreen: ViewController { self.approveDisposable.dispose() } - @objc private func cancelPressed() { + @objc fileprivate func cancelPressed() { self.completion(nil) self.dismissAnimated() } - @objc private func myCodePressed() { - self.showMyCode() - } - - @objc private func testPressed() { - self.dismissWithSession(session: nil) - } - private var animatedIn = false public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -370,6 +352,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie private let titleNode: ImmediateTextNode private let textNode: ImmediateTextNode private let errorTextNode: ImmediateTextNode + private let topNavigationButton = ComponentView() private let camera: Camera private let codeDisposable = MetaDisposable() @@ -510,10 +493,15 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie self.addSubnode(self.titleNode) self.addSubnode(self.textNode) self.addSubnode(self.errorTextNode) - - self.galleryButtonNode.addTarget(self, action: #selector(self.galleryPressed), forControlEvents: .touchUpInside) - self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside) - + + self.galleryButtonNode.pressed = { [weak self] in + self?.galleryPressed() + } + + self.torchButtonNode.pressed = { [weak self] in + self?.torchPressed() + } + self.previewView.resetPlaceholder(front: false) if #available(iOS 13.0, *) { let _ = (self.previewView.isPreviewing @@ -666,6 +654,56 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie transition.updateFrame(view: self.previewView, frame: bounds) transition.updateFrame(node: self.fadeNode, frame: bounds) + let topNavigationIconName: String + if case .custom = self.subject { + topNavigationIconName = "Navigation/Close" + } else { + topNavigationIconName = "Navigation/Back" + } + let topNavigationButtonSide = CGSize(width: 44.0, height: 44.0) + let topNavigationButtonSize = self.topNavigationButton.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassBarButtonComponent( + size: topNavigationButtonSide, + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity(id: topNavigationIconName, component: AnyComponent( + BundleIconComponent( + name: topNavigationIconName, + tintColor: .white + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + if case .custom = self.subject { + self.controller?.cancelPressed() + } else { + self.controller?.dismiss() + } + } + )), + environment: {}, + containerSize: topNavigationButtonSide + ) + if let topNavigationButtonView = self.topNavigationButton.view { + if topNavigationButtonView.superview == nil { + self.view.addSubview(topNavigationButtonView) + } + transition.updateFrame( + view: topNavigationButtonView, + frame: CGRect( + origin: CGPoint( + x: 16.0 + layout.safeInsets.left, + y: max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top) + 5.0 + ), + size: topNavigationButtonSize + ) + ) + } + let frameSide = max(240.0, layout.size.width - sideInset * 2.0) let animateInScale: CGFloat = 0.4 var effectiveFrameSide = frameSide @@ -897,4 +935,3 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie return true } } - diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index 0323caf18b..fd01c51c1a 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -18,47 +18,56 @@ import Markdown import TextFormat import QrCode import LottieComponent +import MtProtoKit +import SegmentControlComponent +import UrlEscaping -private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) { - let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl) - |> map { _, generator -> UIImage? in - let imageSize = CGSize(width: 768.0, height: 768.0) - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0)) - return context?.generateImage() - } - |> deliverOnMainQueue).start(next: { image in - guard let image = image else { - return - } - - let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil) +private func shareQrCode(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject, asImage: Bool, view: UIView) { + let shareImpl: (Any) -> Void = { item in + let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil) if let window = view.window { activityController.popoverPresentationController?.sourceView = window activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) } - context.sharedContext.applicationBindings.presentNativeController(activityController) - }) + sharedContext.applicationBindings.presentNativeController(activityController) + } + if asImage { + let _ = (qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: subject.icon, ecl: subject.ecl) + |> map { _, generator -> UIImage? in + let imageSize = CGSize(width: 768.0, height: 768.0) + let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0)) + return context?.generateImage() + } + |> deliverOnMainQueue).start(next: { image in + guard let image else { + return + } + shareImpl(image) + }) + } else { + shareImpl(subject.link) + } } private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment - let context: AccountContext + let sharedContext: SharedAccountContext let subject: QrCodeScreen.Subject let dismiss: () -> Void init( - context: AccountContext, + sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject, dismiss: @escaping () -> Void ) { - self.context = context + self.sharedContext = sharedContext self.subject = subject self.dismiss = dismiss } static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { - if lhs.context !== rhs.context { + if lhs.sharedContext !== rhs.sharedContext { return false } return true @@ -70,11 +79,19 @@ private final class SheetContent: CombinedComponent { private var initialBrightness: CGFloat? private var brightnessArguments: (Double, Double, CGFloat, CGFloat)? private var animator: ConstantDisplayLinkAnimator? - - init(context: AccountContext) { + + var selectedProxyExternalLink: Bool + + init(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject) { + if case let .proxy(_, externalLink) = subject { + self.selectedProxyExternalLink = externalLink + } else { + self.selectedProxyExternalLink = false + } + super.init() - self.idleTimerExtensionDisposable.set(context.sharedContext.applicationBindings.pushIdleTimerExtension()) + self.idleTimerExtensionDisposable.set(sharedContext.applicationBindings.pushIdleTimerExtension()) self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in self?.updateBrightness() @@ -116,16 +133,18 @@ private final class SheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context) + return State(sharedContext: self.sharedContext, subject: self.subject) } static var body: Body { let qrCode = Child(PlainButtonComponent.self) let closeButton = Child(GlassBarButtonComponent.self) let title = Child(Text.self) + let segmentControl = Child(SegmentControlComponent.self) let text = Child(BalancedTextComponent.self) let button = Child(ButtonComponent.self) + let secondaryButton = Child(ButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self] @@ -134,10 +153,15 @@ private final class SheetContent: CombinedComponent { let theme = environment.theme let strings = environment.strings - - let link = component.subject.link - let ecl = component.subject.ecl - + let state = context.state + + let effectiveSubject: QrCodeScreen.Subject + if case let .proxy(server, _) = component.subject { + effectiveSubject = .proxy(server: server, externalLink: state.selectedProxyExternalLink) + } else { + effectiveSubject = component.subject + } + let titleString: String let textString: String switch component.subject { @@ -154,6 +178,9 @@ private final class SheetContent: CombinedComponent { case .chatFolder: titleString = strings.InviteLink_QRCodeFolder_Title textString = strings.InviteLink_QRCodeFolder_Text + case .proxy: + titleString = "" + textString = strings.SocksProxySetup_ShareQRCodeInfo default: titleString = "" textString = "" @@ -185,24 +212,66 @@ private final class SheetContent: CombinedComponent { ) let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 - - let title = title.update( - component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor), - availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), - transition: .immediate - ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) - ) - contentSize.height += title.size.height + + if case .proxy = component.subject { + let constrainedSegmentWidth = min(constrainedTitleWidth, max(200.0, context.availableSize.width - 144.0)) + + let theme = SegmentControlComponent.Theme( + backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor, + legacyBackgroundColor: theme.overallDarkAppearance ? theme.list.itemBlocksBackgroundColor : theme.rootController.navigationBar.segmentedBackgroundColor, + foregroundColor: theme.actionSheet.opaqueItemBackgroundColor, + textColor: theme.rootController.navigationBar.segmentedTextColor, + dividerColor: theme.rootController.navigationBar.segmentedDividerColor + ) + + let segmentControl = segmentControl.update( + component: SegmentControlComponent( + theme: theme, + items: [ + SegmentControlComponent.Item(id: AnyHashable(false), title: strings.SocksProxySetup_QrCode_TgLink), + SegmentControlComponent.Item(id: AnyHashable(true), title: strings.SocksProxySetup_QrCode_TMeLink) + ], + selectedId: AnyHashable(state.selectedProxyExternalLink), + action: { id in + guard let externalLink = id.base as? Bool else { + return + } + if state.selectedProxyExternalLink != externalLink { + state.selectedProxyExternalLink = externalLink + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) + } + } + ), + availableSize: CGSize(width: constrainedSegmentWidth, height: 36.0), + transition: .immediate + ) + context.add(segmentControl + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) + ) + contentSize.height += segmentControl.size.height + } else { + let title = title.update( + component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor), + availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), + transition: .immediate + ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) + ) + contentSize.height += title.size.height + } contentSize.height += 13.0 let qrCode = qrCode.update( component: PlainButtonComponent( - content: AnyComponent(QrCodeComponent(context: component.context, link: link, ecl: ecl)), + content: AnyComponent( + QrCodeComponent( + subject: effectiveSubject + ) + ), action: { [weak controller] in if let view = controller?.view { - shareQrCode(context: component.context, link: link, ecl: ecl, view: view) + shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view) } }, animateScale: false @@ -250,8 +319,7 @@ private final class SheetContent: CombinedComponent { style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -261,7 +329,7 @@ private final class SheetContent: CombinedComponent { displaysProgress: false, action: { [weak controller] in if let view = controller?.view { - shareQrCode(context: component.context, link: link, ecl: ecl, view: view) + shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view) } } ), @@ -272,8 +340,42 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height + + if case .proxy = component.subject { + contentSize.height += 8.0 + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let secondaryButton = secondaryButton.update( + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemAccentColor.withMultipliedAlpha(0.1), + foreground: theme.list.itemAccentColor, + pressedColor: theme.list.itemAccentColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.SocksProxySetup_ShareLink, font: Font.semibold(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center)))) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak controller] in + if let view = controller?.view { + shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: false, view: view) + } + } + ), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), + transition: .immediate + ) + context.add(secondaryButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + secondaryButton.size.height / 2.0)) + ) + contentSize.height += secondaryButton.size.height + } + contentSize.height += buttonInsets.bottom - + return contentSize } } @@ -282,19 +384,19 @@ private final class SheetContent: CombinedComponent { private final class QrCodeSheetComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment - private let context: AccountContext + private let sharedContext: SharedAccountContext private let subject: QrCodeScreen.Subject init( - context: AccountContext, + sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject ) { - self.context = context + self.sharedContext = sharedContext self.subject = subject } static func ==(lhs: QrCodeSheetComponent, rhs: QrCodeSheetComponent) -> Bool { - if lhs.context !== rhs.context { + if lhs.sharedContext !== rhs.sharedContext { return false } return true @@ -312,7 +414,7 @@ private final class QrCodeSheetComponent: CombinedComponent { let sheet = sheet.update( component: SheetComponent( content: AnyComponent(SheetContent( - context: context.component.context, + sharedContext: context.component.sharedContext, subject: context.component.subject, dismiss: { animateOut.invoke(Action { _ in @@ -372,10 +474,11 @@ public final class QrCodeScreen: ViewControllerComponentContainer { case groupCall } - public enum Subject { + public enum Subject: Equatable { case peer(peer: EnginePeer) case invite(invite: ExportedInvitation, type: SubjectType) case chatFolder(slug: String) + case proxy(server: ProxyServerSettings, externalLink: Bool) var link: String { switch self { @@ -389,39 +492,79 @@ public final class QrCodeScreen: ViewControllerComponentContainer { } else { return "https://t.me/addlist/\(slug)" } + case let .proxy(server, externalLink): + var link: String + let serverHost = server.host.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "" + switch server.connection { + case let .mtp(secret): + let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? "" + link = "\(externalLink ? "https://t.me/proxy" : "tg://proxy")?server=\(serverHost)&port=\(server.port)" + link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")" + case let .socks5(username, password): + link = "\(externalLink ? "https://t.me/socks" : "tg://socks")?server=\(serverHost)&port=\(server.port)" + if let username, !username.isEmpty { + link += "&user=\(username.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")" + } + if let password, !password.isEmpty { + link += "&pass=\(password.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")" + } + } + return link } } var ecl: String { switch self { - case .peer: - return "Q" - case .invite: - return "Q" - case .chatFolder: + case .peer, .invite, .chatFolder, .proxy: return "Q" } } + + var icon: QrCodeIcon { + switch self { + case .peer, .invite, .chatFolder: + return .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")) + case .proxy: + return .proxy + } + } } - - private let context: AccountContext - + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: QrCodeScreen.Subject ) { - self.context = context - super.init( context: context, component: QrCodeSheetComponent( - context: context, + sharedContext: context.sharedContext, subject: subject ), navigationBarAppearance: .none, statusBarStyle: .ignore, - theme: .default // + theme: .default, + updatedPresentationData: updatedPresentationData + ) + + self.navigationPresentation = .flatModal + } + + public init( + sharedContext: SharedAccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal), + subject: QrCodeScreen.Subject + ) { + super.init( + component: QrCodeSheetComponent( + sharedContext: sharedContext, + subject: subject + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + presentationMode: .default, + theme: .default, + updatedPresentationData: updatedPresentationData ) self.navigationPresentation = .flatModal @@ -439,28 +582,16 @@ public final class QrCodeScreen: ViewControllerComponentContainer { } private final class QrCodeComponent: Component { - let context: AccountContext - let link: String - let ecl: String + let subject: QrCodeScreen.Subject init( - context: AccountContext, - link: String, - ecl: String + subject: QrCodeScreen.Subject ) { - self.context = context - self.link = link - self.ecl = ecl + self.subject = subject } static func ==(lhs: QrCodeComponent, rhs: QrCodeComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.link != rhs.link { - return false - } - if lhs.ecl != rhs.ecl { + if lhs.subject != rhs.subject { return false } return true @@ -502,9 +633,14 @@ private final class QrCodeComponent: Component { let previousComponent = self.component self.component = component self.state = state - - if previousComponent?.link != component.link { - self.imageNode.setSignal(qrCode(string: component.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: component.ecl) |> beforeNext { [weak self] size, _ in + + var isProxy = false + if case .proxy = component.subject { + isProxy = true + } + + if previousComponent?.subject != component.subject { + self.imageNode.setSignal(qrCode(string: component.subject.link, color: .black, backgroundColor: .white, icon: isProxy ? .proxy : .cutout, ecl: component.subject.ecl) |> beforeNext { [weak self] size, _ in guard let self else { return } @@ -524,7 +660,7 @@ private final class QrCodeComponent: Component { let imageFrame = CGRect(origin: CGPoint(x: (size.width - imageSize.width) / 2.0, y: (size.height - imageSize.height) / 2.0), size: imageSize) self.imageNode.frame = imageFrame - if let qrCodeSize = self.qrCodeSize { + if !isProxy, let qrCodeSize = self.qrCodeSize { let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil) let _ = self.icon.update( diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index c31f36300b..bcb2e7d26d 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -295,6 +295,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -425,6 +426,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { self.emojiSearchState.set(.single(self.emojiSearchStateValue)) } } + private var emojiSearchContext: EmojiSearchContext? private var emptyResultEmojis: [TelegramMediaFile] = [] private var stableEmptyResultEmoji: TelegramMediaFile? @@ -473,8 +475,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } return Signal { subscriber in let fetchDisposable = freeMediaFileInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file)).start() - let dataDisposable = (context.account.postbox.mediaBox.resourceData(file.resource) - |> filter(\.complete) + let dataDisposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) + |> filter(\.isComplete) |> take(1)).start(next: { data in subscriber.putNext(data.path) subscriber.putCompletion() @@ -531,6 +533,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.scrollsToTop = false self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.canCancelContentTouches = true + self.scrollNode.view.scrollsToTop = false self.scrollNode.clipsToBounds = false if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never @@ -641,16 +644,15 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } if let getEmojiContent = getEmojiContent, !self.reactionsLocked { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - self.stableEmptyResultEmojiDisposable.set((self.context.account.postbox.combinedView(keys: [viewKey]) + self.stableEmptyResultEmojiDisposable.set((self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] views in - guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else { + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let strongSelf = self else { return } var filteredFiles: [TelegramMediaFile] = [] let filterList: [String] = ["😖", "😫", "🫠", "😨", "❓"] - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredEmojiPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { for item in featuredEmojiPack.topItems { if let alt = item.file.customEmojiAlt { if filterList.contains(alt) { @@ -689,7 +691,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } else { strongSelf.stableEmptyResultEmoji = nil } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active, canLoadMore: emojiSearchResult.canLoadMore) } else { strongSelf.stableEmptyResultEmoji = nil @@ -1741,20 +1743,19 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { return } - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (strongSelf.context.account.postbox.combinedView(keys: [viewKey]) + let _ = (strongSelf.context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else { + |> deliverOnMainQueue).start(next: { items in + guard let strongSelf = self else { return } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredEmojiPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredEmojiPack.info.id == collectionId { if let strongSelf = self { strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true)) } let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start() - + break } } @@ -1813,17 +1814,20 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { let context = self.context let isEmojiOnly = self.isEmojiOnly + self.emojiSearchContext = nil var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { @@ -1848,10 +1852,10 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } |> distinctUntilChanged - let resultSignal: Signal<[EmojiPagerContentComponent.ItemGroup], NoError> + let resultSignal: Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> if self.isMessageEffects { resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { @@ -1863,9 +1867,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { context.availableMessageEffects |> take(1), hasPremium |> take(1) ) - |> mapToSignal { availableMessageEffects, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { availableMessageEffects, hasPremium -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in guard let availableMessageEffects else { - return .single([]) + return .single(([], false, false, nil)) } var filteredEffects: [AvailableMessageEffects.MessageEffect] = [] @@ -1972,7 +1976,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { ) } - return .single(allItemGroups) + return .single((allItemGroups, false, false, nil)) } } } else { @@ -1985,7 +1989,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { let localPacksSignal: Signal = context.engine.stickers.searchEmojiSets(query: query) resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { @@ -2028,19 +2032,20 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { fillWithLoadingPlaceholders: false, items: items )) - return .single(resultGroups) + return .single((resultGroups, false, false, nil)) } else { - let remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let emojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let remoteSignal = emojiSearchContext.state return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), + context.engine.itemCollections.allItems(namespace: Namespaces.ItemCollection.CloudEmojiPacks) |> take(1), context.engine.stickers.availableReactions() |> take(1), hasPremium |> take(1), remotePacksSignal, remoteSignal, localPacksSignal ) - |> map { view, availableReactions, hasPremium, foundPacks, foundEmoji, foundLocalPacks -> [EmojiPagerContentComponent.ItemGroup] in + |> map { rawItems, availableReactions, hasPremium, foundPacks, foundEmoji, foundLocalPacks -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in var result: [(String, TelegramMediaFile.Accessor?, String)] = [] var allEmoticons: [String: String] = [:] @@ -2065,8 +2070,8 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } } - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { + for rawItem in rawItems { + guard let item = rawItem as? StickerPackItem else { continue } if !item.file.isPremiumEmoji { @@ -2183,7 +2188,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { )) } } - return resultGroups + return (resultGroups, foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } } @@ -2198,12 +2203,14 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): let context = self.context + self.emojiSearchContext = nil let resultSignal: Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> if self.isMessageEffects { let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) @@ -2414,11 +2421,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -2426,6 +2433,9 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { updateScrollingToItemGroup: { }, onScroll: {}, + loadMore: { [weak self] in + self?.emojiSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: emojiContentLayout, @@ -2758,7 +2768,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { } let additionalAnimationFile = additionalAnimation._parse() - additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimationFile.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimationFile.resource.id))) + additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: itemNode.context.account, resource: additionalAnimationFile.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(additionalAnimationFile.resource.id)))) additionalAnimationNodeValue.frame = effectFrame additionalAnimationNodeValue.updateLayout(size: effectFrame.size) self.addSubnode(additionalAnimationNodeValue) @@ -3910,7 +3920,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { } } - additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(additionalAnimation.resource.id))) + additionalAnimationNodeValue.setup(source: AnimatedStickerResourceSource(account: context.account, resource: additionalAnimation.resource), width: Int(effectFrame.width * 2.0), height: Int(effectFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(additionalAnimation.resource.id)))) additionalAnimationNodeValue.frame = effectFrame additionalAnimationNodeValue.updateLayout(size: effectFrame.size) self.addSubnode(additionalAnimationNodeValue) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 3ee76da2e9..65e1e35966 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AppBundle @@ -239,11 +238,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { strongSelf.animateInAnimationNode = nil } - self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: item.appearAnimation._parse().resource)).start() - self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: item.stillAnimation._parse().resource)).start() - self.fetchStickerDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: item.listAnimation._parse().resource)).start() + self.fetchStickerDisposable = context.engine.resources.fetch(reference: .standalone(resource: item.appearAnimation._parse().resource), userLocation: .other, userContentType: .sticker).start() + self.fetchStickerDisposable = context.engine.resources.fetch(reference: .standalone(resource: item.stillAnimation._parse().resource), userLocation: .other, userContentType: .sticker).start() + self.fetchStickerDisposable = context.engine.resources.fetch(reference: .standalone(resource: item.listAnimation._parse().resource), userLocation: .other, userContentType: .sticker).start() if let applicationAnimation = item.applicationAnimation { - self.fetchFullAnimationDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: applicationAnimation._parse().resource)).start() + self.fetchFullAnimationDisposable = context.engine.resources.fetch(reference: .standalone(resource: applicationAnimation._parse().resource), userLocation: .other, userContentType: .sticker).start() } if self.isLocked { @@ -360,10 +359,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { if largeExpanded { let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation._parse().resource, isVideo: self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji) - animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation._parse().resource.id))) + animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.largeListAnimation._parse().resource.id)))) } else { let source = AnimatedStickerResourceSource(account: self.context.account, resource: self.item.listAnimation._parse().resource, isVideo: self.item.listAnimation.isVideoSticker || self.item.listAnimation.isVideoEmoji || self.item.listAnimation.isVideoSticker || self.item.listAnimation.isStaticSticker || self.item.listAnimation.isStaticEmoji) - animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.listAnimation._parse().resource.id))) + animationNode.setup(source: source, width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.listAnimation._parse().resource.id)))) } animationNode.frame = expandedAnimationFrame animationNode.updateLayout(size: expandedAnimationFrame.size) @@ -447,7 +446,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { self.stillAnimationNode = stillAnimationNode self.addSubnode(stillAnimationNode) - stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation._parse().resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: self.loopIdle ? .loop : .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation._parse().resource.id))) + stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation._parse().resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: self.loopIdle ? .loop : .still(.start), mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.stillAnimation._parse().resource.id)))) stillAnimationNode.position = animationFrame.center stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) stillAnimationNode.updateLayout(size: animationFrame.size) @@ -532,9 +531,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { self.staticAnimationNode.automaticallyLoadFirstFrame = true if !self.hasAppearAnimation { - self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation._parse().resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation._parse().resource.id))) + self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation._parse().resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.largeListAnimation._parse().resource.id)))) } else { - self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation._parse().resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation._parse().resource.id))) + self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation._parse().resource, isVideo: self.item.stillAnimation.isVideoEmoji || self.item.stillAnimation.isVideoSticker || self.item.stillAnimation.isStaticSticker || self.item.stillAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.stillAnimation._parse().resource.id)))) } self.staticAnimationNode.position = animationFrame.center self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) @@ -547,7 +546,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { } if let animateInAnimationNode = self.animateInAnimationNode { - animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation._parse().resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker || self.item.appearAnimation.isStaticSticker || self.item.appearAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation._parse().resource.id))) + animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation._parse().resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker || self.item.appearAnimation.isStaticSticker || self.item.appearAnimation.isStaticEmoji), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(self.item.appearAnimation._parse().resource.id)))) animateInAnimationNode.position = animationFrame.center animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) animateInAnimationNode.updateLayout(size: animationFrame.size) diff --git a/submodules/SaveToCameraRoll/BUILD b/submodules/SaveToCameraRoll/BUILD index d7db544f62..f5410229c0 100644 --- a/submodules/SaveToCameraRoll/BUILD +++ b/submodules/SaveToCameraRoll/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/Display:Display", "//submodules/AccountContext:AccountContext", diff --git a/submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift b/submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift index eaa06ce4d7..2f8b30b155 100644 --- a/submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift +++ b/submodules/SaveToCameraRoll/Sources/SaveToCameraRoll.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import Photos import Display @@ -12,11 +11,11 @@ import LegacyComponents public enum FetchMediaDataState { case progress(Float) - case data(MediaResourceData) + case data(EngineMediaResource.ResourceData) } -public func fetchMediaData(context: AccountContext, postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, mediaReference: AnyMediaReference, forceVideo: Bool = false) -> Signal<(FetchMediaDataState, Bool), NoError> { - var resource: MediaResource? +public func fetchMediaData(context: AccountContext, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, mediaReference: AnyMediaReference, forceVideo: Bool = false) -> Signal<(FetchMediaDataState, Bool), NoError> { + var resource: TelegramMediaResource? var isImage = true var fileExtension: String? var userContentType: MediaResourceUserContentType = .other @@ -53,11 +52,16 @@ public func fetchMediaData(context: AccountContext, postbox: Postbox, userLocati if let customUserContentType { userContentType = customUserContentType } - + if let resource = resource { + let engineResource = EngineMediaResource(resource) let fetchedData: Signal = Signal { subscriber in - let fetched = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: userContentType, reference: mediaReference.resourceReference(resource)).start() - let status = postbox.mediaBox.resourceStatus(resource).start(next: { status in + let fetched = context.engine.resources.fetch( + reference: mediaReference.resourceReference(resource), + userLocation: userLocation, + userContentType: userContentType + ).start() + let status = context.engine.resources.status(resource: engineResource).start(next: { status in switch status { case .Local: subscriber.putNext(.progress(1.0)) @@ -69,7 +73,11 @@ public func fetchMediaData(context: AccountContext, postbox: Postbox, userLocati subscriber.putNext(.progress(progress)) } }) - let data = postbox.mediaBox.resourceData(resource, pathExtension: fileExtension, option: .complete(waitUntilFetchStatus: true)).start(next: { next in + let data = context.engine.resources.data( + resource: engineResource, + pathExtension: fileExtension, + waitUntilFetchStatus: true + ).start(next: { next in subscriber.putNext(.data(next)) }, completed: { subscriber.putCompletion() @@ -89,11 +97,11 @@ public func fetchMediaData(context: AccountContext, postbox: Postbox, userLocati } } -public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, mediaReference: AnyMediaReference, video: AnyMediaReference? = nil) -> Signal { - let mediaData: Signal<(FetchMediaDataState, Bool), NoError> = fetchMediaData(context: context, postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, mediaReference: mediaReference) +public func saveToCameraRoll(context: AccountContext, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, mediaReference: AnyMediaReference, video: AnyMediaReference? = nil) -> Signal { + let mediaData: Signal<(FetchMediaDataState, Bool), NoError> = fetchMediaData(context: context, userLocation: userLocation, customUserContentType: customUserContentType, mediaReference: mediaReference) let videoData: Signal if let video { - videoData = fetchMediaData(context: context, postbox: postbox, userLocation: userLocation, customUserContentType: customUserContentType, mediaReference: video) + videoData = fetchMediaData(context: context, userLocation: userLocation, customUserContentType: customUserContentType, mediaReference: video) |> map { state, _ in return state } @@ -101,7 +109,7 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca } else { videoData = .single(nil) } - + return combineLatest( queue: Queue.mainQueue(), mediaData, @@ -109,8 +117,8 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca ) |> mapToSignal { stateAndIsImage, videoStateAndIsImage -> Signal in let isImage = stateAndIsImage.1 - var mainData: MediaResourceData? - var videoData: MediaResourceData? + var mainData: EngineMediaResource.ResourceData? + var videoData: EngineMediaResource.ResourceData? var waitForVideo = false if let videoState = videoStateAndIsImage { switch videoState { @@ -134,7 +142,7 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca mainData = data } } - if let mainData, mainData.complete, videoData != nil || !waitForVideo { + if let mainData, mainData.isComplete, videoData != nil || !waitForVideo { return Signal { subscriber in DeviceAccess.authorizeAccess(to: .mediaLibrary(.save), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: { c, a in context.sharedContext.presentGlobalController(c, a) @@ -143,16 +151,16 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca subscriber.putCompletion() return } - + let tempVideoPath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).mp4" if isImage, let videoData, let imageData = try? Data(contentsOf: URL(fileURLWithPath: mainData.path)) { let id = UUID().uuidString let jpegWithID = addAssetIdentifierToJPEG(imageData, assetIdentifier: id)! let outputVideoURL = URL(fileURLWithPath: NSTemporaryDirectory() + "\(id).mov") - + try? FileManager.default.copyItem(atPath: videoData.path, toPath: tempVideoPath) - + addAssetIdentifierToVideo(inputURL: URL(fileURLWithPath: tempVideoPath), outputURL: outputVideoURL, assetIdentifier: id) { success in guard success else { return } @@ -188,7 +196,7 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca }) } }) - + return ActionDisposable { } } @@ -198,13 +206,13 @@ public func saveToCameraRoll(context: AccountContext, postbox: Postbox, userLoca } } -public func copyToPasteboard(context: AccountContext, postbox: Postbox, userLocation: MediaResourceUserLocation, mediaReference: AnyMediaReference) -> Signal { - return fetchMediaData(context: context, postbox: postbox, userLocation: userLocation, mediaReference: mediaReference) +public func copyToPasteboard(context: AccountContext, userLocation: MediaResourceUserLocation, mediaReference: AnyMediaReference) -> Signal { + return fetchMediaData(context: context, userLocation: userLocation, mediaReference: mediaReference) |> mapToSignal { state, isImage -> Signal in - if case let .data(data) = state, data.complete { + if case let .data(data) = state, data.isComplete { return Signal { subscriber in let pasteboard = UIPasteboard.general - + if mediaReference.media is TelegramMediaImage { if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: .mappedIfSafe) { pasteboard.setData(fileData, forPasteboardType: kUTTypeJPEG as String) @@ -212,7 +220,7 @@ public func copyToPasteboard(context: AccountContext, postbox: Postbox, userLoca } subscriber.putNext(Void()) subscriber.putCompletion() - + return EmptyDisposable } } else { diff --git a/submodules/SearchPeerMembers/BUILD b/submodules/SearchPeerMembers/BUILD index 2dfde4f9ac..ac0a190373 100644 --- a/submodules/SearchPeerMembers/BUILD +++ b/submodules/SearchPeerMembers/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/StringTransliteration:StringTransliteration", diff --git a/submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift b/submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift index 5581cdb9d3..e1f0532fdf 100644 --- a/submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift +++ b/submodules/SearchPeerMembers/Sources/SearchPeerMembers.swift @@ -20,20 +20,20 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch |> mapToSignal { participantCount -> Signal<([EnginePeer], Bool), NoError> in if case .peer = chatLocation, let memberCount = participantCount, memberCount <= 64 { return Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, requestUpdate: false, updated: { state in if case .ready = state.loadingState { subscriber.putNext((state.list.compactMap { participant -> EnginePeer? in if participant.peer.isDeleted { return nil } if normalizedQuery.isEmpty { - return EnginePeer(participant.peer) + return participant.peer } else { if participant.peer.indexName.matchesByTokens(normalizedQuery) || participant.peer.indexName.matchesByTokens(transformedQuery) { - return EnginePeer(participant.peer) + return participant.peer } if let addressName = participant.peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) || addressName.lowercased().hasPrefix(transformedQuery) { - return EnginePeer(participant.peer) + return participant.peer } return nil @@ -52,13 +52,13 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch return Signal { subscriber in switch chatLocation { case let .peer(peerId): - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: normalizedQuery.isEmpty ? nil : normalizedQuery, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: normalizedQuery.isEmpty ? nil : normalizedQuery, updated: { state in if case .ready = state.loadingState { subscriber.putNext((state.list.compactMap { participant in if participant.peer.isDeleted { return nil } - return EnginePeer(participant.peer) + return participant.peer }, true)) } }) @@ -67,13 +67,13 @@ public func searchPeerMembers(context: AccountContext, peerId: EnginePeer.Id, ch disposable.dispose() } case let .replyThread(replyThreadMessage): - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.mentions(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, threadMessageId: EngineMessage.Id(peerId: replyThreadMessage.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessage.threadId)), searchQuery: normalizedQuery.isEmpty ? nil : normalizedQuery, updated: { state in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.mentions(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, threadMessageId: EngineMessage.Id(peerId: replyThreadMessage.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: replyThreadMessage.threadId)), searchQuery: normalizedQuery.isEmpty ? nil : normalizedQuery, updated: { state in if case .ready = state.loadingState { subscriber.putNext((state.list.compactMap { participant in if participant.peer.isDeleted { return nil } - return EnginePeer(participant.peer) + return participant.peer }, true)) } }) diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index 53cdd002ce..518b730a05 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -312,6 +312,11 @@ public final class SearchDisplayController { if let searchBar = self.searchBar { searchBar.deactivate(clear: false) } + if !self.searchBarIsExternal, let searchBar = self.searchBar { + searchBar.isUserInteractionEnabled = false + } + self.backgroundNode.isUserInteractionEnabled = false + self.contentNode.isUserInteractionEnabled = false if !self.searchBarIsExternal, let searchBar = self.searchBar { if let placeholder = placeholder { diff --git a/submodules/SelectablePeerNode/BUILD b/submodules/SelectablePeerNode/BUILD index b13c4278f4..add7a0f4d8 100644 --- a/submodules/SelectablePeerNode/BUILD +++ b/submodules/SelectablePeerNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index f4ba6fd9fc..13eee759a7 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import AvatarNode @@ -108,7 +107,7 @@ public final class SelectablePeerNode: ASDisplayNode { didSet { if !self.theme.isEqual(to: oldValue) { if let peer = self.peer, let mainPeer = peer.chatMainPeer { - self.textNode.attributedText = NSAttributedString(string: mainPeer.debugDisplayTitle, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : (peer.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: mainPeer.debugDisplayTitle, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : (peer.peerId.isSecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center) } } } @@ -159,8 +158,7 @@ public final class SelectablePeerNode: ASDisplayNode { public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) { self.setup( accountPeerId: context.account.peerId, - postbox: context.account.postbox, - network: context.account.network, + stateManager: context.account.stateManager, energyUsageSettings: context.sharedContext.energyUsageSettings, contentSettings: context.currentContentSettings.with { $0 }, animationCache: context.animationCache, @@ -182,7 +180,7 @@ public final class SelectablePeerNode: ASDisplayNode { ) } - public func setupStoryRepost(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool, storyMode: StoryMode) { + public func setupStoryRepost(accountPeerId: EnginePeer.Id, stateManager: AccountStateManager, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool, storyMode: StoryMode) { self.peer = nil let title: String @@ -202,14 +200,14 @@ public final class SelectablePeerNode: ASDisplayNode { self.textNode.maximumNumberOfLines = 2 self.textNode.attributedText = NSAttributedString(string: title, font: textFont, textColor: self.theme.textColor, paragraphAlignment: .center) - self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: ContentSettings.default, theme: theme, peer: nil, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: .round, synchronousLoad: synchronousLoad) + self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: stateManager.postbox, network: stateManager.network, contentSettings: ContentSettings.default, theme: theme, peer: nil, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: .round, synchronousLoad: synchronousLoad) if case .repostIcon = overrideImage { self.avatarNode.playRepostAnimation() } } - public func setup(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) { + public func setup(accountPeerId: EnginePeer.Id, stateManager: AccountStateManager, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) { let isFirstTime = self.peer == nil self.peer = peer guard let mainPeer = peer.chatOrMonoforumMainPeer else { @@ -222,7 +220,7 @@ public final class SelectablePeerNode: ASDisplayNode { if requiresPremiumForMessaging { defaultColor = self.theme.textColor.withMultipliedAlpha(0.4) } else { - defaultColor = peer.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor + defaultColor = peer.peerId.isSecretChat ? self.theme.secretTextColor : self.theme.textColor } var isForum = false @@ -256,7 +254,7 @@ public final class SelectablePeerNode: ASDisplayNode { } else { clipStyle = .round } - self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: contentSettings, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad) + self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: stateManager.postbox, network: stateManager.network, contentSettings: contentSettings, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoad) if let requiresStars { let avatarBadgeOutline: UIImageView @@ -373,7 +371,7 @@ public final class SelectablePeerNode: ASDisplayNode { let iconSize = self.iconView.update( transition: .easeInOut(duration: 0.2), component: AnyComponent(EmojiStatusComponent( - postbox: postbox, + postbox: stateManager.postbox, energyUsageSettings: energyUsageSettings, resolveInlineStickers: resolveInlineStickers, animationCache: animationCache, @@ -404,7 +402,7 @@ public final class SelectablePeerNode: ASDisplayNode { self.currentSelected = selected if let attributedText = self.textNode.attributedText { - self.textNode.attributedText = NSAttributedString(string: attributedText.string, font: textFont, textColor: selected ? self.theme.selectedTextColor : (self.peer?.peerId.namespace == Namespaces.Peer.SecretChat ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: attributedText.string, font: textFont, textColor: selected ? self.theme.selectedTextColor : ((self.peer?.peerId.isSecretChat ?? false) ? self.theme.secretTextColor : self.theme.textColor), paragraphAlignment: .center) } var isForum = false diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index b0ecc4368e..b8a0540a12 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -54,6 +54,7 @@ swift_library( "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", "//submodules/PhoneInputNode:PhoneInputNode", "//submodules/PhotoResources:PhotoResources", + "//submodules/StickerResources:StickerResources", "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", "//submodules/RadialStatusNode:RadialStatusNode", "//submodules/SearchBarNode:SearchBarNode", @@ -67,7 +68,6 @@ swift_library( "//submodules/TextFormat:TextFormat", "//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/UrlEscaping:UrlEscaping", - "//submodules/WebSearchUI:WebSearchUI", "//submodules/UrlHandling:UrlHandling", "//submodules/HexColor:HexColor", "//submodules/QrCode:QrCode", @@ -128,6 +128,7 @@ swift_library( "//submodules/TelegramUI/Components/Settings/PasskeysScreen", "//submodules/TelegramUI/Components/FaceScanScreen", "//submodules/ComponentFlow", + "//submodules/Components/ResizableSheetComponent", "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/ButtonComponent", @@ -137,6 +138,7 @@ swift_library( "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/AvatarEditorScreen", "//submodules/TelegramUI/Components/Settings/PeerSelectionScreen", + "//submodules/TelegramUI/Components/Settings/ChatbotSetupScreen", "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/Utils/DeviceModel", diff --git a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift index f92f453a1e..95b321126e 100644 --- a/submodules/SettingsUI/Sources/ArchiveSettingsController.swift +++ b/submodules/SettingsUI/Sources/ArchiveSettingsController.swift @@ -178,6 +178,11 @@ public func archiveSettingsController(context: AccountContext) -> ViewController ) |> deliverOnMainQueue |> map { presentationData, settings, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in + var presentationData = presentationData + + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + let isPremium = accountPeer?.isPremium ?? false let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: appConfiguration).isPremiumDisabled diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift index 4b2c5e68fe..e02fd7938c 100644 --- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift +++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -134,6 +133,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, ASScrollViewDel self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.delegate = self.wrappedScrollViewDelegate self.scrollNode.view.alwaysBounceHorizontal = false + self.scrollNode.view.scrollsToTop = false } func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -159,30 +159,30 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, ASScrollViewDel let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder) var items: [ListViewItem] = [] - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let replyMessageId = EngineMessage.Id(peerId: peerId, namespace: 0, id: 3) + messages[replyMessageId] = EngineRawMessage(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message1 = EngineRawMessage(stableId: 4, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = EngineRawMessage(stableId: 3, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: Data(base64Encoded: waveformBase64)!)] - let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) - - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let voiceMedia = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) + + let message3 = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message4 = EngineRawMessage(stableId: 2, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift index 91024939fa..3cc1dec724 100644 --- a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift +++ b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift @@ -38,7 +38,7 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal Void let toggleRaiseToListen: (Bool) -> Void let toggleDownloadInBackground: (Bool) -> Void - let openBrowserSelection: () -> Void let openIntents: () -> Void let toggleSensitiveContent: (Bool) -> Void @@ -50,7 +47,6 @@ private final class DataAndStorageControllerArguments { togglePauseMusicOnRecording: @escaping (Bool) -> Void, toggleRaiseToListen: @escaping (Bool) -> Void, toggleDownloadInBackground: @escaping (Bool) -> Void, - openBrowserSelection: @escaping () -> Void, openIntents: @escaping () -> Void, toggleSensitiveContent: @escaping (Bool) -> Void ) { @@ -65,7 +61,6 @@ private final class DataAndStorageControllerArguments { self.togglePauseMusicOnRecording = togglePauseMusicOnRecording self.toggleRaiseToListen = toggleRaiseToListen self.toggleDownloadInBackground = toggleDownloadInBackground - self.openBrowserSelection = openBrowserSelection self.openIntents = openIntents self.toggleSensitiveContent = toggleSensitiveContent } @@ -119,7 +114,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case useLessVoiceData(PresentationTheme, String, Bool) case useLessVoiceDataInfo(PresentationTheme, String) case otherHeader(PresentationTheme, String) - case openLinksIn(PresentationTheme, String, String) case shareSheet(PresentationTheme, String) case saveEditedPhotos(PresentationTheme, String, Bool) case pauseMusicOnRecording(PresentationTheme, String, Bool) @@ -144,7 +138,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return DataAndStorageSection.backgroundDownload.rawValue case .useLessVoiceData, .useLessVoiceDataInfo: return DataAndStorageSection.voiceCalls.rawValue - case .otherHeader, .openLinksIn, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo: + case .otherHeader, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo: return DataAndStorageSection.other.rawValue case .sensitiveContent, .sensitiveContentInfo: return DataAndStorageSection.sensitiveContent.rawValue @@ -183,8 +177,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return 24 case .otherHeader: return 29 - case .openLinksIn: - return 30 case .shareSheet: return 31 case .saveEditedPhotos: @@ -280,12 +272,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry { } else { return false } - case let .openLinksIn(lhsTheme, lhsText, lhsValue): - if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { - return true - } else { - return false - } case let .shareSheet(lhsTheme, lhsText): if case let .shareSheet(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -415,10 +401,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .otherHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .openLinksIn(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { - arguments.openBrowserSelection() - }) case let .shareSheet(_, text): return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openIntents() @@ -619,7 +601,7 @@ private func autosaveLabelAndValue(presentationData: PresentationData, settings: return (label, value) } -private func dataAndStorageControllerEntries(context: AccountContext, state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] { +private func dataAndStorageControllerEntries(context: AccountContext, state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] { var entries: [DataAndStorageEntry] = [] entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData)))) @@ -649,7 +631,6 @@ private func dataAndStorageControllerEntries(context: AccountContext, state: Dat entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription)) entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other)) - entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser)) if #available(iOSApplicationExtension 13.2, iOS 13.2, *) { entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings)) } @@ -897,9 +878,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da settings.downloadInBackground = value return settings }).start() - }, openBrowserSelection: { - let controller = webBrowserSettingsController(context: context) - pushControllerImpl?(controller) }, openIntents: { let controller = intentsSettingsController(context: context) pushControllerImpl?(controller) @@ -933,13 +911,9 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da } }) - let preferencesKey: PostboxViewKey = .preferences(keys: Set([ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings])) - let preferences = context.account.postbox.combinedView(keys: [preferencesKey]) - |> map { views -> MediaAutoSaveSettings in - guard let view = views.views[preferencesKey] as? PreferencesView else { - return .default - } - return view.values[ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings)) + |> map { entry -> MediaAutoSaveSettings in + return entry?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default } let autosaveExceptionPeers: Signal<[EnginePeer.Id: EnginePeer?], NoError> = preferences @@ -957,26 +931,15 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da context.sharedContext.presentationData, statePromise.get(), dataAndStorageDataPromise.get(), - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), contentSettingsConfiguration.get(), preferences, usageSignal, autosaveExceptionPeers ) |> map { presentationData, state, dataAndStorageData, sharedData, contentSettingsConfiguration, mediaAutoSaveSettings, usageSignal, autosaveExceptionPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in - let webBrowserSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings - let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org")) - let defaultWebBrowser: String - if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) { - defaultWebBrowser = option.title - } else if webBrowserSettings.defaultWebBrowser == "inApp" { - defaultWebBrowser = presentationData.strings.WebBrowser_InAppSafari - } else { - defaultWebBrowser = presentationData.strings.WebBrowser_Telegram - } - let previousSensitiveContent = sensitiveContent.swap(contentSettingsConfiguration?.sensitiveContentEnabled) var animateChanges = false if previousSensitiveContent != contentSettingsConfiguration?.sensitiveContentEnabled { @@ -989,7 +952,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da let showSensitiveContentSetting = canAdjustSensitiveContent.with { $0 } ?? false let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(context: context, state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(context: context, state: state, data: dataAndStorageData, presentationData: presentationData, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift index 4ea381815b..cea27124d2 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 44e310c99e..1d6821467c 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import MtProtoKit @@ -260,7 +259,11 @@ private func proxySettingsControllerEntries(theme: PresentationTheme, strings: P entries.append(.serversHeader(theme, strings.SocksProxySetup_SavedProxies)) entries.append(.addServer(theme, strings.SocksProxySetup_AddProxy, state.editing)) var index = 0 + var existingServers = Set() for server in proxySettings.servers { + if !existingServers.insert(server).inserted { + continue + } let status: ProxyServerStatus = statuses[server] ?? .checking let displayStatus: DisplayProxyServerStatus if proxySettings.enabled && server == proxySettings.activeServer { @@ -301,7 +304,7 @@ private func proxySettingsControllerEntries(theme: PresentationTheme, strings: P entries.append(.server(index, theme, strings, server, server == proxySettings.activeServer, displayStatus, ProxySettingsServerItemEditing(editable: true, editing: state.editing, revealed: state.revealedServer == server), proxySettings.enabled)) index += 1 } - if !proxySettings.servers.isEmpty { + if !existingServers.isEmpty { entries.append(.shareProxyList(theme, strings.SocksProxySetup_ShareProxyList)) } @@ -325,10 +328,10 @@ public enum ProxySettingsControllerMode { public func proxySettingsController(context: AccountContext, mode: ProxySettingsControllerMode = .default, focusOnItemTag: ProxySettingsEntryTag? = nil) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, postbox: context.account.postbox, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, focusOnItemTag: focusOnItemTag) + return proxySettingsController(accountManager: context.sharedContext.accountManager, sharedContext: context.sharedContext, context: context, network: context.account.network, mode: mode, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, focusOnItemTag: focusOnItemTag) } -public func proxySettingsController(accountManager: AccountManager, sharedContext: SharedAccountContext, context: AccountContext? = nil, postbox: Postbox, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal, focusOnItemTag: ProxySettingsEntryTag? = nil) -> ViewController { +public func proxySettingsController(accountManager: AccountManager, sharedContext: SharedAccountContext, context: AccountContext? = nil, network: Network, mode: ProxySettingsControllerMode, presentationData: PresentationData, updatedPresentationData: Signal, focusOnItemTag: ProxySettingsEntryTag? = nil) -> ViewController { var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? let stateValue = Atomic(value: ProxySettingsControllerState()) @@ -425,9 +428,13 @@ public func proxySettingsController(accountManager: AccountManager map { presentationData, state, proxySettings, statuses, connectionStatus -> (ItemListControllerState, (ItemListNodeState, Any)) in + var presentationData = presentationData + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + var leftNavigationButton: ItemListNavigationButton? if case .modal = mode { - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) } @@ -436,7 +443,7 @@ public func proxySettingsController(accountManager: AccountManager() - override public var ready: Promise { - return self._ready - } - - private var isDismissed: Bool = false - - convenience public init(context: AccountContext, server: ProxyServerSettings) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.init(sharedContext: context.sharedContext, presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData) - } - - public init(sharedContext: SharedAccountContext, presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) { - self.sharedContext = sharedContext - let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData) - super.init(theme: sheetTheme) - - self._ready.set(.single(true)) - - var items: [ActionSheetItem] = [] - if case .mtp = server.connection { - items.append(ActionSheetTextItem(title: presentationData.strings.SocksProxySetup_AdNoticeHelp)) - } - items.append(ProxyServerInfoItem(strings: presentationData.strings, network: network, server: server)) - items.append(ProxyServerActionItem(sharedContext: sharedContext, accountManager:accountManager, postbox: postbox, network: network, presentationData: presentationData, server: server, dismiss: { [weak self] success in - guard let strongSelf = self, !strongSelf.isDismissed else { - return - } - strongSelf.isDismissed = true - if success { - strongSelf.present(OverlayStatusController(theme: presentationData.theme, type: .shieldSuccess(presentationData.strings.SocksProxySetup_ProxyEnabled, false)), in: .window(.root)) - } - strongSelf.dismissAnimated() - }, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }) - ]) - ]) - - if let updatedPresentationData = updatedPresentationData { - self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - } - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class ProxyServerInfoItem: ActionSheetItem { - private let strings: PresentationStrings - private let network: Network - private let server: ProxyServerSettings - - init(strings: PresentationStrings, network: Network, server: ProxyServerSettings) { - self.strings = strings - self.network = network - self.server = server - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return ProxyServerInfoItemNode(theme: theme, strings: self.strings, network: self.network, server: self.server) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private enum ProxyServerInfoStatusType { - case generic(String) - case failed(String) -} - -private final class ProxyServerInfoItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - private let textFont: UIFont - - private let network: Network - private let server: ProxyServerSettings - - private let fieldNodes: [(ImmediateTextNode, ImmediateTextNode)] - private let statusTextNode: ImmediateTextNode - - private let statusDisposable = MetaDisposable() - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, network: Network, server: ProxyServerSettings) { - self.theme = theme - self.strings = strings - self.network = network - self.server = server - - self.textFont = Font.regular(floor(theme.baseFontSize * 16.0 / 17.0)) - - var fieldNodes: [(ImmediateTextNode, ImmediateTextNode)] = [] - let serverTitleNode = ImmediateTextNode() - serverTitleNode.isUserInteractionEnabled = false - serverTitleNode.displaysAsynchronously = false - serverTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Hostname, font: textFont, textColor: theme.secondaryTextColor) - let serverTextNode = ImmediateTextNode() - serverTextNode.isUserInteractionEnabled = false - serverTextNode.displaysAsynchronously = false - serverTextNode.attributedText = NSAttributedString(string: urlEncodedStringFromString(server.host), font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((serverTitleNode, serverTextNode)) - - let portTitleNode = ImmediateTextNode() - portTitleNode.isUserInteractionEnabled = false - portTitleNode.displaysAsynchronously = false - portTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Port, font: textFont, textColor: theme.secondaryTextColor) - let portTextNode = ImmediateTextNode() - portTextNode.isUserInteractionEnabled = false - portTextNode.displaysAsynchronously = false - portTextNode.attributedText = NSAttributedString(string: "\(server.port)", font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((portTitleNode, portTextNode)) - - switch server.connection { - case let .socks5(username, password): - if let username = username { - let usernameTitleNode = ImmediateTextNode() - usernameTitleNode.isUserInteractionEnabled = false - usernameTitleNode.displaysAsynchronously = false - usernameTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Username, font: textFont, textColor: theme.secondaryTextColor) - let usernameTextNode = ImmediateTextNode() - usernameTextNode.isUserInteractionEnabled = false - usernameTextNode.displaysAsynchronously = false - usernameTextNode.attributedText = NSAttributedString(string: username, font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((usernameTitleNode, usernameTextNode)) - } - - if let password = password { - let passwordTitleNode = ImmediateTextNode() - passwordTitleNode.isUserInteractionEnabled = false - passwordTitleNode.displaysAsynchronously = false - passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Password, font: textFont, textColor: theme.secondaryTextColor) - let passwordTextNode = ImmediateTextNode() - passwordTextNode.isUserInteractionEnabled = false - passwordTextNode.displaysAsynchronously = false - passwordTextNode.attributedText = NSAttributedString(string: password, font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((passwordTitleNode, passwordTextNode)) - } - case .mtp: - let passwordTitleNode = ImmediateTextNode() - passwordTitleNode.isUserInteractionEnabled = false - passwordTitleNode.displaysAsynchronously = false - passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Secret, font: textFont, textColor: theme.secondaryTextColor) - let passwordTextNode = ImmediateTextNode() - passwordTextNode.isUserInteractionEnabled = false - passwordTextNode.displaysAsynchronously = false - passwordTextNode.attributedText = NSAttributedString(string: "•••••", font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((passwordTitleNode, passwordTextNode)) - } - - let statusTitleNode = ImmediateTextNode() - statusTitleNode.isUserInteractionEnabled = false - statusTitleNode.displaysAsynchronously = false - statusTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Status, font: textFont, textColor: theme.secondaryTextColor) - let statusTextNode = ImmediateTextNode() - statusTextNode.isUserInteractionEnabled = false - statusTextNode.displaysAsynchronously = false - statusTextNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_ProxyStatusChecking, font: textFont, textColor: theme.primaryTextColor) - fieldNodes.append((statusTitleNode, statusTextNode)) - - self.fieldNodes = fieldNodes - self.statusTextNode = statusTextNode - - super.init(theme: theme) - - for (lhs, rhs) in fieldNodes { - self.addSubnode(lhs) - self.addSubnode(rhs) - } - } - - deinit { - self.statusDisposable.dispose() - } - - override func didLoad() { - super.didLoad() - - let statusesContext = ProxyServersStatuses(network: network, servers: .single([self.server])) - self.statusDisposable.set((statusesContext.statuses() - |> map { return $0.first?.value } - |> distinctUntilChanged - |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self, let status = status { - let statusType: ProxyServerInfoStatusType - switch status { - case .checking: - statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusChecking) - case let .available(rtt): - let pingTime = Int(rtt * 1000.0) - statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusPing("\(pingTime)").string) - case .notAvailable: - statusType = .failed(strongSelf.strings.SocksProxySetup_ProxyStatusUnavailable) - } - strongSelf.setStatus(statusType) - } - })) - } - - func setStatus(_ status: ProxyServerInfoStatusType) { - let attributedString: NSAttributedString - switch status { - case let .generic(text): - attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.primaryTextColor) - case let .failed(text): - attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.destructiveActionTextColor) - } - self.statusTextNode.attributedText = attributedString - self.requestLayoutUpdate() - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 36.0 * CGFloat(self.fieldNodes.count) + 12.0) - - var offset: CGFloat = 15.0 - for (lhs, rhs) in self.fieldNodes { - let lhsSize = lhs.updateLayout(CGSize(width: size.width - 18.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) - lhs.frame = CGRect(origin: CGPoint(x: 18, y: offset), size: lhsSize) - - let rhsSize = rhs.updateLayout(CGSize(width: max(1.0, size.width - 18 * 2.0 - lhsSize.width - 4.0), height: CGFloat.greatestFiniteMagnitude)) - rhs.frame = CGRect(origin: CGPoint(x: size.width - 18 - rhsSize.width, y: offset), size: rhsSize) - - offset += 36.0 - } - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } -} - -private final class ProxyServerActionItem: ActionSheetItem { - private let sharedContext: SharedAccountContext - private let accountManager: AccountManager - private let postbox: Postbox - private let network: Network - private let presentationData: PresentationData - private let server: ProxyServerSettings - private let dismiss: (Bool) -> Void - private let present: (ViewController, Any?) -> Void - - init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { - self.sharedContext = sharedContext - self.accountManager = accountManager - self.postbox = postbox - self.network = network - self.presentationData = presentationData - self.server = server - self.dismiss = dismiss - self.present = present - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return ProxyServerActionItemNode(sharedContext: self.sharedContext, accountManager: self.accountManager, postbox: self.postbox, network: self.network, presentationData: self.presentationData, theme: theme, server: self.server, dismiss: self.dismiss, present: self.present) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class ProxyServerActionItemNode: ActionSheetItemNode { - private let sharedContext: SharedAccountContext - private let accountManager: AccountManager - private let postbox: Postbox - private let network: Network - private let presentationData: PresentationData - private let theme: ActionSheetControllerTheme - private let server: ProxyServerSettings - private let dismiss: (Bool) -> Void - private let present: (ViewController, Any?) -> Void - - private let buttonNode: HighlightableButtonNode - private let titleNode: ImmediateTextNode - private let activityIndicator: ActivityIndicator - - private let disposable = MetaDisposable() - private var revertSettings: ProxySettings? - - init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) { - self.sharedContext = sharedContext - self.accountManager = accountManager - self.postbox = postbox - self.network = network - self.theme = theme - self.presentationData = presentationData - self.server = server - self.dismiss = dismiss - self.present = present - - let titleFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0)) - - self.titleNode = ImmediateTextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.SocksProxySetup_ConnectAndSave, font: titleFont, textColor: theme.controlAccentColor) - - self.activityIndicator = ActivityIndicator(type: .custom(theme.controlAccentColor, 22.0, 1.5, false)) - self.activityIndicator.isHidden = true - - self.buttonNode = HighlightableButtonNode() - - super.init(theme: theme) - - self.addSubnode(self.titleNode) - self.addSubnode(self.activityIndicator) - self.addSubnode(self.buttonNode) - - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor - }) - } - } - } - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - } - - deinit { - self.disposable.dispose() - if let revertSettings = self.revertSettings { - let _ = updateProxySettingsInteractively(accountManager: self.accountManager, { _ in - return revertSettings - }) - } - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 57.0) - - self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) - - let labelSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) - let activitySize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0)) - self.titleNode.frame = titleFrame - self.activityIndicator.frame = CGRect(origin: CGPoint(x: 14.0, y: titleFrame.minY - 0.0), size: activitySize) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc private func buttonPressed() { - let proxyServerSettings = self.server - let _ = (self.accountManager.transaction { transaction -> ProxySettings in - var currentSettings: ProxySettings? - let _ = updateProxySettingsInteractively(transaction: transaction, { settings in - currentSettings = settings - var settings = settings - if let index = settings.servers.firstIndex(of: proxyServerSettings) { - settings.servers[index] = proxyServerSettings - settings.activeServer = proxyServerSettings - } else { - settings.servers.insert(proxyServerSettings, at: 0) - settings.activeServer = proxyServerSettings - } - settings.enabled = true - return settings - }) - return currentSettings ?? ProxySettings.defaultSettings - } |> deliverOnMainQueue).start(next: { [weak self] previousSettings in - if let strongSelf = self { - strongSelf.revertSettings = previousSettings - strongSelf.buttonNode.isUserInteractionEnabled = false - strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_Connecting, font: Font.regular(20.0), textColor: strongSelf.theme.primaryTextColor) - strongSelf.activityIndicator.isHidden = false - strongSelf.requestLayoutUpdate() - - let signal = strongSelf.network.connectionStatus - |> filter { status in - switch status { - case let .online(proxyAddress): - if proxyAddress == proxyServerSettings.host { - return true - } else { - return false - } - default: - return false - } - } - |> map { _ -> Bool in - return true - } - |> timeout(15.0, queue: Queue.mainQueue(), alternate: .single(false)) - |> deliverOnMainQueue - strongSelf.disposable.set(signal.start(next: { value in - if let strongSelf = self { - strongSelf.activityIndicator.isHidden = true - strongSelf.revertSettings = nil - if value { - strongSelf.dismiss(true) - } else { - let _ = updateProxySettingsInteractively(accountManager: strongSelf.accountManager, { _ in - return previousSettings - }) - strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_ConnectAndSave, font: Font.regular(20.0), textColor: strongSelf.theme.controlAccentColor) - strongSelf.buttonNode.isUserInteractionEnabled = true - strongSelf.requestLayoutUpdate() - - strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) - } - } - })) - } - }) - } -} diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift index 62f826c99e..985e7d3fde 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift @@ -10,20 +10,7 @@ import PresentationDataUtils import AccountContext import UrlEscaping import UrlHandling - -private func shareLink(for server: ProxyServerSettings) -> String { - var link: String - switch server.connection { - case let .mtp(secret): - let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? "" - link = "tg://proxy?server=\(server.host)&port=\(server.port)" - link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")" - case let .socks5(username, password): - link = "https://t.me/socks?server=\(server.host)&port=\(server.port)" - link += "&user=\(username?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")&pass=\(password?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")" - } - return link -} +import QrCodeUI private final class ProxyServerSettingsControllerArguments { let updateState: ((ProxyServerSettingsControllerState) -> ProxyServerSettingsControllerState) -> Void @@ -300,7 +287,7 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context: statePromise.set(stateValue.modify { f($0) }) } - var presentControllerImpl: ((ViewController, Any?) -> Void)? + var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? var shareImpl: (() -> Void)? @@ -332,10 +319,14 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context: let signal = combineLatest(updatedPresentationData, statePromise.get()) |> deliverOnMainQueue |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text("___close"), style: .regular, enabled: true, action: { + var presentationData = presentationData + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) - let rightNavigationButton = ItemListNavigationButton(content: .text("___done"), style: .bold, enabled: state.isComplete, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: state.isComplete, action: { if let proxyServerSettings = proxyServerSettings(with: state) { let _ = (updateProxySettingsInteractively(accountManager: accountManager, { settings in var settings = settings @@ -347,8 +338,11 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context: } } } else { - settings.servers.append(proxyServerSettings) - if settings.servers.count == 1 { + let wasEmpty = settings.servers.isEmpty + if !settings.servers.contains(proxyServerSettings) { + settings.servers.append(proxyServerSettings) + } + if wasEmpty && settings.servers.count == 1 { settings.activeServer = proxyServerSettings } } @@ -367,8 +361,8 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context: let controller = ItemListController(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: updatedPresentationData |> map(ItemListPresentationData.init(_:)), state: signal, tabBarItem: nil) controller.navigationPresentation = .modal - presentControllerImpl = { [weak controller] c, d in - controller?.present(c, in: .window(.root), with: d) + pushControllerImpl = { [weak controller] c in + controller?.push(c) } dismissImpl = { [weak controller] in let _ = controller?.dismiss() @@ -378,14 +372,15 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context: guard let server = proxyServerSettings(with: state) else { return } - - let link = shareLink(for: server) controller?.view.endEditing(true) - let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link) - presentControllerImpl?(controller, nil) + let controller = QrCodeScreen( + sharedContext: sharedContext, + updatedPresentationData: (presentationData, updatedPresentationData), + subject: .proxy(server: server, externalLink: false) + ) + pushControllerImpl?(controller) } return controller } - diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift index 3108d64985..a9dd996e54 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift @@ -18,7 +18,7 @@ struct ProxySettingsServerItemEditing: Equatable { let revealed: Bool } -final class ProxySettingsServerItem: ListViewItem, ItemListItem { +final class ProxySettingsServerItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let theme: PresentationTheme let strings: PresentationStrings let systemStyle: ItemListSystemStyle @@ -34,6 +34,10 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem { let infoAction: () -> Void let setServerWithRevealedOptions: (ProxyServerSettings?, ProxyServerSettings?) -> Void let removeServer: (ProxyServerSettings) -> Void + + var hasActiveRevealOptions: Bool { + return self.editing.revealed + } init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, server: ProxyServerSettings, activity: Bool, active: Bool, color: ItemListCheckboxItemColor, label: String, labelAccent: Bool, editing: ProxySettingsServerItemEditing, sectionId: ItemListSectionId, action: @escaping () -> Void, infoAction: @escaping () -> Void, setServerWithRevealedOptions: @escaping (ProxyServerSettings?, ProxyServerSettings?) -> Void, removeServer: @escaping (ProxyServerSettings) -> Void) { self.theme = theme @@ -123,6 +127,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { private var item: ProxySettingsServerItem? private var layoutParams: ListViewItemLayoutParams? + private var isHighlighted = false override var canBeSelected: Bool { if self.editableControlNode != nil { @@ -232,7 +237,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] if item.editing.editable { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.theme.list.itemSecondaryTextColor)] } else { peerRevealOptions = [] } @@ -383,26 +388,29 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -432,7 +440,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { strongSelf.infoButtonNode.isUserInteractionEnabled = revealOffset.isZero && !item.editing.editing strongSelf.infoButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 55.0, y: 0.0), size: CGSize(width: 55.0, height: layout.contentSize.height)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -446,39 +454,8 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.4, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -522,10 +499,16 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { var infoIconFrame = self.infoIconNode.frame infoIconFrame.origin.x = offset + params.width - params.rightInset - 55.0 + floor((55.0 - infoIconFrame.width) / 2.0) transition.updateFrame(node: self.infoIconNode, frame: infoIconFrame) - + self.infoButtonNode.isUserInteractionEnabled = offset.isZero } - + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } + override func revealOptionsInteractivelyOpened() { if let item = self.item { item.setServerWithRevealedOptions(item.server, nil) diff --git a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift index c127449e59..6a7d098788 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -515,13 +514,9 @@ public func saveIncomingMediaController(context: AccountContext, scope: SaveInco controller.peerSelected = { [weak controller] peer, _ in let peerId = peer.id - let preferencesKey: PostboxViewKey = .preferences(keys: Set([ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings])) - let preferences = context.account.postbox.combinedView(keys: [preferencesKey]) - |> map { views -> MediaAutoSaveSettings in - guard let view = views.views[preferencesKey] as? PreferencesView else { - return .default - } - return view.values[ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings)) + |> map { entry -> MediaAutoSaveSettings in + return entry?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default } let _ = (preferences @@ -622,13 +617,9 @@ public func saveIncomingMediaController(context: AccountContext, scope: SaveInco } ) - let preferencesKey: PostboxViewKey = .preferences(keys: Set([ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings])) - let preferences = context.account.postbox.combinedView(keys: [preferencesKey]) - |> map { views -> MediaAutoSaveSettings in - guard let view = views.views[preferencesKey] as? PreferencesView else { - return .default - } - return view.values[ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings]?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: ApplicationSpecificPreferencesKeys.mediaAutoSaveSettings)) + |> map { entry -> MediaAutoSaveSettings in + return entry?.get(MediaAutoSaveSettings.self) ?? MediaAutoSaveSettings.default } let peer: Signal<(EnginePeer?, EnginePeer.Presence?), NoError> @@ -669,7 +660,7 @@ public func saveIncomingMediaController(context: AccountContext, scope: SaveInco switch scope { case .peer, .addPeer: - rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { switch scope { case let .addPeer(_, completion): let configuration = stateValue.with({ $0 }).pendingConfiguration diff --git a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift b/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift deleted file mode 100644 index 1ac427d213..0000000000 --- a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift +++ /dev/null @@ -1,184 +0,0 @@ -import Foundation -import UIKit -import Display -import TelegramCore -import AsyncDisplayKit -import UIKit -import SwiftSignalKit -import TelegramPresentationData -import QrCode - -public final class ShareProxyServerActionSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override public var ready: Promise { - return self._ready - } - - private var isDismissed: Bool = false - - public init(presentationData: PresentationData, updatedPresentationData: Signal, link: String) { - let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData) - super.init(theme: sheetTheme) - - let presentActivityController: (Any) -> Void = { [weak self] item in - let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil) - if let window = self?.view.window, let rootViewController = window.rootViewController { - activityController.popoverPresentationController?.sourceView = window - activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) - rootViewController.present(activityController, animated: true, completion: nil) - } - } - - var items: [ActionSheetItem] = [] - items.append(ProxyServerQRCodeItem(strings: presentationData.strings, link: link, ready: { [weak self] in - self?._ready.set(.single(true)) - })) - items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareQRCode, action: { [weak self] in - self?.dismissAnimated() - let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy) - |> map { _, generator -> UIImage? in - let imageSize = CGSize(width: 768.0, height: 768.0) - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0)) - return context?.generateImage() - } - |> deliverOnMainQueue).start(next: { image in - if let image = image { - presentActivityController(image) - } - }) - })) - items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareLink, action: { [weak self] in - self?.dismissAnimated() - presentActivityController(link) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }) - ]) - ]) - - self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class ProxyServerQRCodeItem: ActionSheetItem { - private let strings: PresentationStrings - private let link: String - private let ready: () -> Void - - init(strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) { - self.strings = strings - self.link = link - self.ready = ready - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return ProxyServerQRCodeItemNode(theme: theme, strings: self.strings, link: self.link, ready: self.ready) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class ProxyServerQRCodeItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - private let link: String - - private let label: ASTextNode - private let imageNode: TransformImageNode - - private let ready: () -> Void - - private var cachedHasLabel = true - private var cachedHasImage = true - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) { - self.theme = theme - self.strings = strings - self.link = link - self.ready = ready - - let textFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)) - - self.label = ASTextNode() - self.label.isUserInteractionEnabled = false - self.label.maximumNumberOfLines = 0 - self.label.displaysAsynchronously = false - self.label.truncationMode = .byTruncatingTail - self.label.isUserInteractionEnabled = false - self.label.attributedText = NSAttributedString(string: strings.SocksProxySetup_ShareQRCodeInfo, font: textFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center) - - self.imageNode = TransformImageNode() - self.imageNode.clipsToBounds = true - self.imageNode.setSignal(qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy) |> map { $0.1 }, attemptSynchronously: true) - self.imageNode.cornerRadius = 14.0 - - super.init(theme: theme) - - self.addSubnode(self.label) - self.addSubnode(self.imageNode) - } - - override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let imageInset: CGFloat = 44.0 - let side = constrainedSize.width - imageInset * 2.0 - var imageSize = CGSize(width: side, height: side) - - let makeLayout = self.imageNode.asyncLayout() - let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil)) - apply() - - var labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 64.0), height: constrainedSize.height)) - - self.cachedHasImage = constrainedSize.width < constrainedSize.height - if !self.cachedHasImage { - imageSize = CGSize() - } - - self.ready() - - self.cachedHasLabel = constrainedSize.height > 480 || !self.cachedHasImage - if !self.cachedHasLabel { - labelSize = CGSize() - } - let size = CGSize(width: constrainedSize.width, height: 14.0 + (labelSize.height > 0.0 ? labelSize.height + 14.0 : 0.0) + (imageSize.height > 0.0 ? imageSize.height + 14.0 : 8.0)) - - let inset: CGFloat = 32.0 - let spacing: CGFloat = 18.0 - if self.cachedHasLabel { - labelSize = self.label.measure(CGSize(width: max(1.0, size.width - inset * 2.0), height: size.height)) - self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: spacing), size: labelSize) - } else { - labelSize = CGSize() - } - - if !self.cachedHasImage { - imageSize = CGSize() - } else { - imageSize = CGSize(width: size.width - imageInset * 2.0, height: size.width - imageInset * 2.0) - } - let imageOrigin = CGPoint(x: imageInset, y: self.label.frame.maxY + spacing - 4.0) - self.imageNode.frame = CGRect(origin: imageOrigin, size: imageSize) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } -} diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift index 7be3cda903..1460732cce 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift @@ -149,7 +149,7 @@ private enum StorageUsageExceptionsEntry: ItemListNodeEntry { switch self { case let .addException(text): let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme) - return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, alwaysPlain: false, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: text, alwaysPlain: false, sectionId: self.section, editing: false, action: { arguments.openAddException() }) case let .exceptionsHeader(text): @@ -170,10 +170,10 @@ private enum StorageUsageExceptionsEntry: ItemListNodeEntry { if peer.peer.id == arguments.context.account.peerId { title = presentationData.strings.DialogList_SavedMessages } else { - title = EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: .firstLast) + title = peer.peer.displayTitle(strings: presentationData.strings, displayOrder: .firstLast) } - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: EnginePeer(peer.peer), title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, context: arguments.context, iconPeer: peer.peer, title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: { arguments.openPeerMenu(peer.peer.id, value) }, tag: StorageUsageExceptionsEntryTag.peer(peer.peer.id)) } @@ -230,17 +230,9 @@ public func storageUsageExceptionsScreen( return cacheSettings }) - let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings])) - let accountSpecificSettings: Signal = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> AccountSpecificCacheStorageSettings in - let cacheSettings: AccountSpecificCacheStorageSettings - if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) { - cacheSettings = value - } else { - cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings - } - - return cacheSettings + let accountSpecificSettings: Signal = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.accountSpecificCacheStorageSettings)) + |> map { entry -> AccountSpecificCacheStorageSettings in + return entry?.get(AccountSpecificCacheStorageSettings.self) ?? AccountSpecificCacheStorageSettings.defaultSettings } |> distinctUntilChanged @@ -285,7 +277,7 @@ public func storageUsageExceptionsScreen( continue } - result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value)) + result.append((peer: FoundPeer(peer: EnginePeer(peer), subscribers: subscriberCount), value: value)) } return result.sorted(by: { lhs, rhs in @@ -297,15 +289,9 @@ public func storageUsageExceptionsScreen( } } - var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)? - let _ = presentControllerImpl var pushControllerImpl: ((ViewController) -> Void)? - var findPeerReferenceNode: ((EnginePeer.Id) -> ItemListDisclosureItemNode?)? - let _ = findPeerReferenceNode - var presentInGlobalOverlay: ((ViewController) -> Void)? - let _ = presentInGlobalOverlay let actionDisposables = DisposableSet() @@ -448,7 +434,7 @@ public func storageUsageExceptionsScreen( ) |> deliverOnMainQueue |> map { presentationData, peerExceptions, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = isModal ? ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) : nil @@ -466,9 +452,6 @@ public func storageUsageExceptionsScreen( controller.navigationPresentation = .modal controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) } - presentControllerImpl = { [weak controller] c, contextType, a in - controller?.present(c, in: contextType, with: a) - } pushControllerImpl = { [weak controller] c in controller?.push(c) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift b/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift index 2c85919d39..53db4bc1c9 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift index 29197201c1..09f072481a 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift @@ -7,19 +7,28 @@ import TelegramPresentationData import TelegramCore import AccountContext import ItemListUI -import PhotoResources +import StickerResources private enum RevealOptionKey: Int32 { case delete } +private func webBrowserDomainExceptionPlaceholderLetter(_ title: String) -> String { + let trimmedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines) + if let firstCharacter = trimmedTitle.first { + return String(firstCharacter).uppercased() + } else { + return "#" + } +} + final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle let context: AccountContext let title: String let label: String - let icon: TelegramMediaImage? + let favicon: Int64? let sectionId: ItemListSectionId let style: ItemListStyle let deleted: (() -> Void)? @@ -30,7 +39,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { context: AccountContext, title: String, label: String, - icon: TelegramMediaImage?, + favicon: Int64?, sectionId: ItemListSectionId, style: ItemListStyle, deleted: (() -> Void)? @@ -40,7 +49,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { self.context = context self.title = title self.label = label - self.icon = icon + self.favicon = favicon self.sectionId = sectionId self.style = style self.deleted = deleted @@ -92,13 +101,17 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It private let maskNode: ASImageNode let iconNode: TransformImageNode + private let iconPlaceholderNode: ASDisplayNode + private let iconPlaceholderTextNode: TextNode let titleNode: TextNode let labelNode: TextNode private let activateArea: AccessibilityAreaNode + private let iconDisposable = MetaDisposable() private var item: WebBrowserDomainExceptionItem? private var layoutParams: ListViewItemLayoutParams? + private var currentIconFile: TelegramMediaFile? override public var canBeSelected: Bool { return false @@ -124,6 +137,15 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It self.iconNode.isLayerBacked = true self.iconNode.displaysAsynchronously = false + self.iconPlaceholderNode = ASDisplayNode() + self.iconPlaceholderNode.clipsToBounds = true + self.iconPlaceholderNode.cornerRadius = 7.0 + + self.iconPlaceholderTextNode = TextNode() + self.iconPlaceholderTextNode.isUserInteractionEnabled = false + self.iconPlaceholderTextNode.contentMode = .center + self.iconPlaceholderTextNode.contentsScale = UIScreen.main.scale + self.titleNode = TextNode() self.titleNode.isUserInteractionEnabled = false @@ -134,16 +156,23 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It super.init(layerBacked: false, rotated: false, seeThrough: false) + self.addSubnode(self.iconPlaceholderNode) + self.iconPlaceholderNode.addSubnode(self.iconPlaceholderTextNode) self.addSubnode(self.iconNode) self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) self.addSubnode(self.activateArea) } + + deinit { + self.iconDisposable.dispose() + } func asyncLayout() -> (_ item: WebBrowserDomainExceptionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode) + let makeIconPlaceholderTextLayout = TextNode.asyncLayout(self.iconPlaceholderTextNode) let currentItem = self.item @@ -157,6 +186,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let insets: UIEdgeInsets let separatorHeight = UIScreenPixel let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let iconSize = CGSize(width: 30.0, height: 30.0) let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -175,6 +205,10 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let iconPlaceholderText = webBrowserDomainExceptionPlaceholderLetter(item.title) + let iconPlaceholderFont = Font.with(size: 17.0, design: .round, weight: .semibold) + let (iconPlaceholderTextLayout, iconPlaceholderTextApply) = makeIconPlaceholderTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: iconPlaceholderText, font: iconPlaceholderFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: iconSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let verticalInset: CGFloat switch item.systemStyle { case .glass: @@ -214,19 +248,45 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.topStripeNode.backgroundColor = itemSeparatorColor strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor + strongSelf.iconPlaceholderNode.backgroundColor = item.presentationData.theme.list.mediaPlaceholderColor } - let iconSize = CGSize(width: 40.0, height: 40.0) - var imageSize = iconSize - if currentItem?.icon?.id != item.icon?.id, let icon = item.icon { - strongSelf.iconNode.setSignal(chatMessagePhoto(mediaBox: item.context.sharedContext.accountManager.mediaBox, userLocation: .other, photoReference: .standalone(media: icon))) + if currentItem?.favicon != item.favicon { + strongSelf.currentIconFile = nil + strongSelf.iconDisposable.set(nil) + strongSelf.iconNode.reset() + + if let favicon = item.favicon { + strongSelf.iconDisposable.set((item.context.engine.stickers.resolveInlineStickers(fileIds: [favicon]) + |> deliverOnMainQueue).start(next: { [weak strongSelf] files in + guard let strongSelf, strongSelf.item?.favicon == favicon, let file = files[favicon] else { + return + } + strongSelf.currentIconFile = file + var resolvedImageSize = iconSize + if let dimensions = file.dimensions?.cgSize { + resolvedImageSize = dimensions.aspectFilled(resolvedImageSize) + } + if file.isAnimatedSticker || file.isVideoSticker { + strongSelf.iconNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, userLocation: .other, file: file, small: false, size: resolvedImageSize, fetched: true)) + } else { + strongSelf.iconNode.setSignal(chatMessageSticker(account: item.context.account, userLocation: .other, file: file, small: false, fetched: true)) + } + strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: resolvedImageSize, boundingSize: iconSize, intrinsicInsets: .zero))() + strongSelf.iconPlaceholderNode.isHidden = true + strongSelf.iconNode.isHidden = false + })) + } } - if let icon = item.icon, let dimensions = largestImageRepresentation(icon.representations)?.dimensions.cgSize { + var imageSize = iconSize + if strongSelf.currentIconFile?.fileId.id == item.favicon, let dimensions = strongSelf.currentIconFile?.dimensions?.cgSize { imageSize = dimensions.aspectFilled(imageSize) } + let hasResolvedIcon = item.favicon != nil && strongSelf.currentIconFile?.fileId.id == item.favicon let _ = titleApply() let _ = labelApply() + let _ = iconPlaceholderTextApply() switch item.style { case .plain: @@ -296,7 +356,11 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let labelFrame = CGRect(origin: CGPoint(x: leftInset + strongSelf.revealOffset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame - let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 11.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 16.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize) + strongSelf.iconPlaceholderNode.frame = iconFrame + strongSelf.iconPlaceholderTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((iconSize.width - iconPlaceholderTextLayout.size.width) / 2.0) + UIScreenPixel, y: floorToScreenPixels((iconSize.height - iconPlaceholderTextLayout.size.height) / 2.0) + 1.0), size: iconPlaceholderTextLayout.size) + strongSelf.iconPlaceholderNode.isHidden = hasResolvedIcon + strongSelf.iconNode.isHidden = !hasResolvedIcon strongSelf.iconNode.frame = iconFrame strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 7.0), imageSize: imageSize, boundingSize: iconSize, intrinsicInsets: .zero))() @@ -304,7 +368,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) var revealOptions: [ItemListRevealOption] = [] - revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)) + revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)) strongSelf.setRevealOptions((left: [], right: revealOptions)) } }) @@ -330,9 +394,13 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let leftInset: CGFloat = 16.0 + params.leftInset + 46.0 var iconFrame = self.iconNode.frame - iconFrame.origin.x = params.leftInset + 11.0 + offset + iconFrame.origin.x = params.leftInset + 16.0 + offset transition.updateFrame(node: self.iconNode, frame: iconFrame) + var iconPlaceholderFrame = self.iconPlaceholderNode.frame + iconPlaceholderFrame.origin.x = params.leftInset + 16.0 + offset + transition.updateFrame(node: self.iconPlaceholderNode, frame: iconPlaceholderFrame) + var titleFrame = self.titleNode.frame titleFrame.origin.x = leftInset + offset transition.updateFrame(node: self.titleNode, frame: titleFrame) diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift index ecaa103547..44f4a7c967 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift @@ -11,22 +11,24 @@ import OpenInExternalAppUI import AccountContext import AppBundle -class WebBrowserItem: ListViewItem, ItemListItem { +final class WebBrowserItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle let title: String let application: OpenInApplication? + let identifier: String? let checked: Bool public let sectionId: ItemListSectionId let action: () -> Void - public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, title: String, application: OpenInApplication?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { + public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, title: String, application: OpenInApplication?, identifier: String?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { self.context = context self.presentationData = presentationData self.systemStyle = systemStyle self.title = title self.application = application + self.identifier = identifier self.checked = checked self.sectionId = sectionId self.action = action @@ -148,11 +150,16 @@ private final class WebBrowserItemNode: ListViewItemNode { if currentItem == nil { switch item.application { case .none: - let icons = item.context.sharedContext.applicationBindings.getAvailableAlternateIcons() - let current = item.context.sharedContext.applicationBindings.getAlternateIconName() - let currentIcon = icons.first(where: { $0.name == current })?.imageName ?? "BlueIcon" - if let image = UIImage(named: currentIcon, in: getAppBundle(), compatibleWith: nil) { - updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image)) + if item.identifier == "default" { + let image = renderSettingsIcon(name: "Chat/Context Menu/Globe", backgroundColors: [UIColor(rgb: 0x8E8E93)])! + updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image), withChrome: false) + } else { + let icons = item.context.sharedContext.applicationBindings.getAvailableAlternateIcons() + let current = item.context.sharedContext.applicationBindings.getAlternateIconName() + let currentIcon = icons.first(where: { $0.name == current })?.imageName ?? "BlueIcon" + if let image = UIImage(named: currentIcon, in: getAppBundle(), compatibleWith: nil) { + updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image)) + } } case .safari: if let image = UIImage(bundleImageName: "Open In/Safari") { diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift index 85fd21740e..eacdffcb4f 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -13,10 +12,7 @@ import OpenInExternalAppUI import ItemListPeerActionItem import UndoUI import WebKit -import LinkPresentation -import CoreServices import PersistentStringHash -import UrlHandling private final class WebBrowserSettingsControllerArguments { let context: AccountContext @@ -24,7 +20,7 @@ private final class WebBrowserSettingsControllerArguments { let clearCookies: () -> Void let clearCache: () -> Void let addException: () -> Void - let removeException: (String) -> Void + let removeException: (AccountWebBrowserException) -> Void let clearExceptions: () -> Void init( @@ -33,7 +29,7 @@ private final class WebBrowserSettingsControllerArguments { clearCookies: @escaping () -> Void, clearCache: @escaping () -> Void, addException: @escaping () -> Void, - removeException: @escaping (String) -> Void, + removeException: @escaping (AccountWebBrowserException) -> Void, clearExceptions: @escaping () -> Void ) { self.context = context @@ -49,31 +45,44 @@ private final class WebBrowserSettingsControllerArguments { private enum WebBrowserSettingsSection: Int32 { case browsers case clearCookies - case exceptions + case neverExceptions + case alwaysExceptions + case clear } private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { case browserHeader(PresentationTheme, String) case browser(PresentationTheme, String, OpenInApplication?, String?, Bool, Int32) + case browserInfo(PresentationTheme, String) case clearCookies(PresentationTheme, String) case clearCache(PresentationTheme, String) case clearCookiesInfo(PresentationTheme, String) - case exceptionsHeader(PresentationTheme, String) - case exceptionsAdd(PresentationTheme, String) - case exception(Int32, PresentationTheme, WebBrowserException) - case exceptionsClear(PresentationTheme, String) - case exceptionsInfo(PresentationTheme, String) + case neverHeader(PresentationTheme, String) + case neverAdd(PresentationTheme, String) + case neverException(Int32, PresentationTheme, AccountWebBrowserException) + case neverExceptionsInfo(PresentationTheme, String) + case neverExceptionsClear(PresentationTheme, String) + + case alwaysHeader(PresentationTheme, String) + case alwaysAdd(PresentationTheme, String) + case alwaysException(Int32, PresentationTheme, AccountWebBrowserException) + case alwaysExceptionsInfo(PresentationTheme, String) + case alwaysExceptionsClear(PresentationTheme, String) var section: ItemListSectionId { switch self { - case .browserHeader, .browser: + case .browserHeader, .browser, .browserInfo: return WebBrowserSettingsSection.browsers.rawValue case .clearCookies, .clearCache, .clearCookiesInfo: return WebBrowserSettingsSection.clearCookies.rawValue - case .exceptionsHeader, .exceptionsAdd, .exception, .exceptionsClear, .exceptionsInfo: - return WebBrowserSettingsSection.exceptions.rawValue + case .neverHeader, .neverAdd, .neverException, .neverExceptionsInfo: + return WebBrowserSettingsSection.neverExceptions.rawValue + case .alwaysHeader, .alwaysAdd, .alwaysException, .alwaysExceptionsInfo: + return WebBrowserSettingsSection.alwaysExceptions.rawValue + case .neverExceptionsClear, .alwaysExceptionsClear: + return WebBrowserSettingsSection.clear.rawValue } } @@ -83,22 +92,34 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { return 0 case let .browser(_, _, _, _, _, index): return UInt64(1 + index) + case .browserInfo: + return 101 case .clearCookies: return 102 case .clearCache: return 103 case .clearCookiesInfo: return 104 - case .exceptionsHeader: + case .neverHeader: return 105 - case .exceptionsAdd: + case .neverAdd: return 106 - case let .exception(_, _, exception): - return 2000 + exception.domain.persistentHashValue - case .exceptionsClear: - return 1000 - case .exceptionsInfo: + case let .neverException(_, _, exception): + return 107 + exception.domain.persistentHashValue + case .neverExceptionsInfo: return 1001 + case .neverExceptionsClear: + return 1002 + case .alwaysHeader: + return 1003 + case .alwaysAdd: + return 1004 + case let .alwaysException(_, _, exception): + return 1005 + exception.domain.persistentHashValue + case .alwaysExceptionsInfo: + return 2000 + case .alwaysExceptionsClear: + return 3000 } } @@ -108,22 +129,34 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { return 0 case let .browser(_, _, _, _, _, index): return 1 + index + case .browserInfo: + return 101 case .clearCookies: return 102 case .clearCache: return 103 case .clearCookiesInfo: return 104 - case .exceptionsHeader: + case .neverHeader: return 105 - case .exceptionsAdd: + case .neverAdd: return 106 - case let .exception(index, _, _): + case let .neverException(index, _, _): return 107 + index - case .exceptionsClear: - return 1000 - case .exceptionsInfo: + case .neverExceptionsInfo: return 1001 + case .neverExceptionsClear: + return 1002 + case .alwaysHeader: + return 1003 + case .alwaysAdd: + return 1004 + case let .alwaysException(index, _, _): + return 1005 + index + case .alwaysExceptionsInfo: + return 2000 + case .alwaysExceptionsClear: + return 3000 } } @@ -141,6 +174,12 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { } else { return false } + case let .browserInfo(lhsTheme, lhsText): + if case let .browserInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .clearCookies(lhsTheme, lhsText): if case let .clearCookies(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -159,32 +198,62 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { } else { return false } - case let .exceptionsHeader(lhsTheme, lhsText): - if case let .exceptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .neverHeader(lhsTheme, lhsText): + if case let .neverHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } - case let .exception(lhsIndex, lhsTheme, lhsException): - if case let .exception(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException { + case let .neverException(lhsIndex, lhsTheme, lhsException): + if case let .neverException(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException { return true } else { return false } - case let .exceptionsAdd(lhsTheme, lhsText): - if case let .exceptionsAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .neverAdd(lhsTheme, lhsText): + if case let .neverAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } - case let .exceptionsClear(lhsTheme, lhsText): - if case let .exceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .neverExceptionsInfo(lhsTheme, lhsText): + if case let .neverExceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false } - case let .exceptionsInfo(lhsTheme, lhsText): - if case let .exceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .neverExceptionsClear(lhsTheme, lhsText): + if case let .neverExceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .alwaysHeader(lhsTheme, lhsText): + if case let .alwaysHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .alwaysException(lhsIndex, lhsTheme, lhsException): + if case let .alwaysException(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException { + return true + } else { + return false + } + case let .alwaysAdd(lhsTheme, lhsText): + if case let .alwaysAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .alwaysExceptionsInfo(lhsTheme, lhsText): + if case let .alwaysExceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .alwaysExceptionsClear(lhsTheme, lhsText): + if case let .alwaysExceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false @@ -202,11 +271,13 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { case let .browserHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .browser(_, title, application, identifier, selected, _): - return WebBrowserItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, title: title, application: application, checked: selected, sectionId: self.section) { + return WebBrowserItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, title: title, application: application, identifier: identifier, checked: selected, sectionId: self.section) { arguments.updateDefaultBrowser(identifier) } + case let .browserInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .clearCookies(_, text): - return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.clearCookies() }) case let .clearCache(_, text): @@ -215,61 +286,93 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { }) case let .clearCookiesInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .exceptionsHeader(_, text): + case let .neverHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .exception(_, _, exception): - return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, icon: exception.icon, sectionId: self.section, style: .blocks, deleted: { - arguments.removeException(exception.domain) + case let .neverException(_, _, exception): + return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, favicon: exception.favicon, sectionId: self.section, style: .blocks, deleted: { + arguments.removeException(exception) }) - case let .exceptionsAdd(_, text): + case let .neverAdd(_, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.addException() }) - case let .exceptionsClear(_, text): - return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + case let .neverExceptionsInfo(_, text): + return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .neverExceptionsClear(_, text): + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.clearExceptions() }) - case let .exceptionsInfo(_, text): + case let .alwaysHeader(_, text): + return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) + case let .alwaysException(_, _, exception): + return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, favicon: exception.favicon, sectionId: self.section, style: .blocks, deleted: { + arguments.removeException(exception) + }) + case let .alwaysAdd(_, text): + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + arguments.addException() + }) + case let .alwaysExceptionsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .alwaysExceptionsClear(_, text): + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { + arguments.clearExceptions() + }) } } } -private func webBrowserSettingsControllerEntries(context: AccountContext, presentationData: PresentationData, settings: WebBrowserSettings) -> [WebBrowserSettingsControllerEntry] { +private func webBrowserSettingsControllerEntries(context: AccountContext, presentationData: PresentationData, localSettings: WebBrowserSettings, accountSettings: AccountWebBrowserSettings) -> [WebBrowserSettingsControllerEntry] { var entries: [WebBrowserSettingsControllerEntry] = [] - let options = availableOpenInOptions(context: context, item: .url(url: "http://telegram.org")) + let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org")) + let defaultExternalBrowser = localSettings.defaultWebBrowser ?? "default" entries.append(.browserHeader(presentationData.theme, presentationData.strings.WebBrowser_OpenLinksIn_Title)) - entries.append(.browser(presentationData.theme, presentationData.strings.WebBrowser_Telegram, nil, nil, settings.defaultWebBrowser == nil, 0)) - + entries.append(.browser(presentationData.theme, presentationData.strings.WebBrowser_Telegram, nil, nil, !accountSettings.openExternalBrowser, 0)) + var index: Int32 = 1 for option in options { - entries.append(.browser(presentationData.theme, option.title, option.application, option.identifier, option.identifier == settings.defaultWebBrowser, index)) + entries.append(.browser(presentationData.theme, option.title, option.application, option.identifier, accountSettings.openExternalBrowser && option.identifier == defaultExternalBrowser, index)) index += 1 } - if settings.defaultWebBrowser == nil { - entries.append(.clearCookies(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies)) -// entries.append(.clearCache(presentationData.theme, presentationData.strings.WebBrowser_ClearCache)) - entries.append(.clearCookiesInfo(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies_Info)) - - entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Title)) - entries.append(.exceptionsAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException)) + entries.append(.browserInfo(presentationData.theme, presentationData.strings.WebBrowser_OpenLinksInfo)) + + entries.append(.clearCookies(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies)) + entries.append(.clearCookiesInfo(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies_Info)) + + //TODO:localize + if accountSettings.openExternalBrowser { + entries.append(.neverHeader(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_OpenInApp)) + entries.append(.neverAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException)) var exceptionIndex: Int32 = 0 - for exception in settings.exceptions.reversed() { - entries.append(.exception(exceptionIndex, presentationData.theme, exception)) + for exception in accountSettings.inAppExceptions.reversed() { + entries.append(.neverException(exceptionIndex, presentationData.theme, exception)) exceptionIndex += 1 } + entries.append(.neverExceptionsInfo(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_InAppInfo)) - if !settings.exceptions.isEmpty { - entries.append(.exceptionsClear(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Clear)) + if !accountSettings.inAppExceptions.isEmpty { + entries.append(.neverExceptionsClear(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_DeleteAll)) } + } else { + entries.append(.alwaysHeader(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_DontOpenInApp)) + entries.append(.alwaysAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException)) - entries.append(.exceptionsInfo(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Info)) + var exceptionIndex: Int32 = 0 + for exception in accountSettings.externalExceptions.reversed() { + entries.append(.alwaysException(exceptionIndex, presentationData.theme, exception)) + exceptionIndex += 1 + } + entries.append(.alwaysExceptionsInfo(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Info)) + + if !accountSettings.externalExceptions.isEmpty { + entries.append(.alwaysExceptionsClear(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_DeleteAll)) + } } - + return entries } @@ -277,15 +380,21 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl var clearCookiesImpl: (() -> Void)? var clearCacheImpl: (() -> Void)? var addExceptionImpl: (() -> Void)? - var removeExceptionImpl: ((String) -> Void)? + var removeExceptionImpl: ((AccountWebBrowserException) -> Void)? var clearExceptionsImpl: (() -> Void)? let arguments = WebBrowserSettingsControllerArguments( context: context, updateDefaultBrowser: { identifier in - let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { - $0.withUpdatedDefaultWebBrowser(identifier) - }).start() + let openExternalBrowser = identifier != nil + if let identifier { + let _ = (updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { + $0.withUpdatedDefaultWebBrowser(identifier) + }) + |> then(updateRemoteWebBrowserSettings(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: openExternalBrowser))).start() + } else { + let _ = updateRemoteWebBrowserSettings(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: openExternalBrowser).start() + } }, clearCookies: { clearCookiesImpl?() @@ -296,37 +405,39 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl addException: { addExceptionImpl?() }, - removeException: { domain in - removeExceptionImpl?(domain) + removeException: { exception in + removeExceptionImpl?(exception) }, clearExceptions: { clearExceptionsImpl?() } ) - let previousSettings = Atomic(value: nil) + let previousSettings = Atomic<(WebBrowserSettings, AccountWebBrowserSettings)?>(value: nil) let signal = combineLatest( context.sharedContext.presentationData, - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]) + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.webBrowserSettings)) ) |> deliverOnMainQueue - |> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in - let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings - let previousSettings = previousSettings.swap(settings) + |> map { presentationData, sharedData, accountSettingsEntry -> (ItemListControllerState, (ItemListNodeState, Any)) in + let localSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings + let accountSettings = accountSettingsEntry?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + let previousSettings = previousSettings.swap((localSettings, accountSettings)) var animateChanges = false if let previousSettings { - if previousSettings.defaultWebBrowser != settings.defaultWebBrowser { + if previousSettings.0.defaultWebBrowser != localSettings.defaultWebBrowser || previousSettings.1.openExternalBrowser != accountSettings.openExternalBrowser { animateChanges = true } - if previousSettings.exceptions.count != settings.exceptions.count { + if previousSettings.1.externalExceptions.count != accountSettings.externalExceptions.count || previousSettings.1.inAppExceptions.count != accountSettings.inAppExceptions.count { animateChanges = true } } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.WebBrowser_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: webBrowserSettingsControllerEntries(context: context, presentationData: presentationData, settings: settings), style: .blocks, animateChanges: animateChanges) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: webBrowserSettingsControllerEntries(context: context, presentationData: presentationData, localSettings: localSettings, accountSettings: accountSettings), style: .blocks, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } @@ -401,18 +512,13 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl var dismissImpl: (() -> Void)? let linkController = webBrowserDomainController(context: context, apply: { url in if let url { - let _ = (fetchDomainExceptionInfo(context: context, url: url) - |> deliverOnMainQueue).startStandalone(next: { newException in - let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in - var currentExceptions = currentSettings.exceptions - for exception in currentExceptions { - if exception.domain == newException.domain { - return currentSettings - } - } - currentExceptions.append(newException) - return currentSettings.withUpdatedExceptions(currentExceptions) - }).start() + let _ = (context.account.postbox.transaction { transaction -> AccountWebBrowserSettings in + return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + } + |> mapToSignal { settings -> Signal in + return toggleWebBrowserSettingsException(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: !settings.openExternalBrowser, delete: false, url: url) + } + |> deliverOnMainQueue).startStandalone(next: { _ in dismissImpl?() }) } @@ -424,10 +530,13 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl controller?.present(linkController, in: .window(.root)) } - removeExceptionImpl = { domain in - let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in - let updatedExceptions = currentSettings.exceptions.filter { $0.domain != domain } - return currentSettings.withUpdatedExceptions(updatedExceptions) + removeExceptionImpl = { exception in + let url = exception.url.isEmpty ? exception.domain : exception.url + let _ = (context.account.postbox.transaction { transaction -> AccountWebBrowserSettings in + return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + } + |> mapToSignal { settings -> Signal in + return toggleWebBrowserSettingsException(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: !settings.openExternalBrowser, delete: true, url: url) }).start() } @@ -442,9 +551,7 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Exceptions_ClearConfirmation_Clear, action: { - let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in - return currentSettings.withUpdatedExceptions([]) - }).start() + let _ = deleteWebBrowserSettingsExceptions(postbox: context.account.postbox, network: context.account.network).start() }) ] ) @@ -453,60 +560,3 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl return controller } - -private func fetchDomainExceptionInfo(context: AccountContext, url: String) -> Signal { - let (domain, domainUrl) = cleanDomain(url: url) - if #available(iOS 13.0, *), let url = URL(string: domainUrl) { - return Signal { subscriber in - let metadataProvider = LPMetadataProvider() - metadataProvider.shouldFetchSubresources = true - metadataProvider.startFetchingMetadata(for: url, completionHandler: { metadata, _ in - let completeWithImage: (Data?) -> Void = { imageData in - var image: TelegramMediaImage? - if let imageData, let parsedImage = UIImage(data: imageData) { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: imageData) - image = TelegramMediaImage( - imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), - representations: [ - TelegramMediaImageRepresentation( - dimensions: PixelDimensions(width: Int32(parsedImage.size.width), height: Int32(parsedImage.size.height)), - resource: resource, - progressiveSizes: [], - immediateThumbnailData: nil, - hasVideo: false, - isPersonal: false - ) - ], - immediateThumbnailData: nil, - reference: nil, - partialReference: nil, - flags: [] - ) - } - - let title = metadata?.value(forKey: "_siteName") as? String ?? metadata?.title - subscriber.putNext(WebBrowserException(domain: domain, title: title ?? domain, icon: image)) - subscriber.putCompletion() - } - - if let imageProvider = metadata?.iconProvider { - imageProvider.loadFileRepresentation(forTypeIdentifier: kUTTypeImage as String, completionHandler: { imageUrl, _ in - guard let imageUrl, let imageData = try? Data(contentsOf: imageUrl) else { - completeWithImage(nil) - return - } - completeWithImage(imageData) - }) - } else { - completeWithImage(nil) - } - }) - return ActionDisposable { - metadataProvider.cancel() - } - } - } else { - return .single(WebBrowserException(domain: domain, title: domain, icon: nil)) - } -} diff --git a/submodules/SettingsUI/Sources/DeleteAccountDataController.swift b/submodules/SettingsUI/Sources/DeleteAccountDataController.swift index f7517c7427..03ca221700 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountDataController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountDataController.swift @@ -296,15 +296,15 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat switch mode { case .peers: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_cloud_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_cloud_cancel") case .groups: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_groups_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_groups_cancel") case .messages: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_messages_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_messages_cancel") case .phone: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_phone_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_phone_cancel") case .password: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_2fa_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_2fa_cancel") } } @@ -315,7 +315,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat statePromise.get() ) |> map { presentationData, peers, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { cancelImpl() }) @@ -437,10 +437,10 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat let controller = deleteAccountDataController(context: context, mode: nextMode, twoStepAuthData: twoStepAuthData) replaceTopControllerImpl?(controller) } else { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_confirmation_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_confirmation_show") presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.DeleteAccount_ConfirmationAlertTitle, text: presentationData.strings.DeleteAccount_ConfirmationAlertText, actions: [TextAlertAction(type: .destructiveAction, title: presentationData.strings.DeleteAccount_ConfirmationAlertDelete, action: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.final") + context.engine.accountData.addAppLogEvent(type: "deactivate.final") invokeAppLogEventsSynchronization(postbox: context.account.postbox) @@ -473,7 +473,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat }) }) }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_confirmation_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_confirmation_cancel") dismissImpl?() })], actionLayout: .vertical)) @@ -581,15 +581,15 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat switch mode { case .peers: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_cloud_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_cloud_show") case .groups: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_groups_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_groups_show") case .messages: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_messages_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_messages_show") case .phone: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_phone_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_phone_show") case .password: - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.step_2fa_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.step_2fa_show") } return controller diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index 0e23345ba7..75adcc0e24 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -181,7 +181,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo let supportPeerDisposable = MetaDisposable() let arguments = DeleteAccountOptionsArguments(changePhoneNumber: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_phone_change_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_phone_change_tap") let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.engine.account.peerId)) |> deliverOnMainQueue).start(next: { accountPeer in @@ -195,7 +195,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo dismissImpl?() }) }, addAccount: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_add_account_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_add_account_tap") let _ = (activeAccountsAndPeers(context: context) |> take(1) @@ -233,11 +233,11 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo } }) }, setupPrivacy: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_privacy_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_privacy_tap") replaceTopControllerImpl?(makePrivacyAndSecurityController(context: context), false) }, setupTwoStepAuth: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_2fa_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_2fa_tap") if let data = twoStepAuthData { switch data { @@ -263,7 +263,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo let controller = twoStepVerificationUnlockSettingsController(context: context, mode: .access(intro: false, data: twoStepAuthData.flatMap({ Signal.single(.access(configuration: $0)) }))) replaceTopControllerImpl?(controller, false) }, setPasscode: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_passcode_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_passcode_tap") let _ = passcodeOptionsAccessController(context: context, pushController: { controller in replaceTopControllerImpl?(controller, false) @@ -276,18 +276,18 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo }) dismissImpl?() }, clearCache: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_clear_cache_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_clear_cache_tap") pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in return storageUsageExceptionsScreen(context: context, category: category) })) dismissImpl?() }, clearSyncedContacts: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_clear_contacts_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_clear_contacts_tap") replaceTopControllerImpl?(dataPrivacyController(context: context), false) }, deleteChats: { - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_delete_chats_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_delete_chats_tap") let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -325,7 +325,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo openFaq(resolvedUrlPromise) }, contactSupport: { [weak navigationController] in - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_support_tap") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_support_tap") let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -394,7 +394,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo ] ) alertController.dismissed = { _ in - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_support_cancel") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_support_cancel") } presentControllerImpl?(alertController, nil) }, deleteAccount: { @@ -408,7 +408,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo activeAccountsAndPeers(context: context) ) |> map { presentationData, accessChallengeData, accountsAndPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -464,7 +464,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo let _ = controller?.dismiss() } - addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_show") + context.engine.accountData.addAppLogEvent(type: "deactivate.options_show") return controller } diff --git a/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift b/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift index 7ccbca5f40..9f8db50830 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountPeersItem.swift @@ -48,8 +48,7 @@ private struct PeersEntry: Comparable, Identifiable { strings: self.strings, mode: .list(compact: true), accountPeerId: context.account.peerId, - postbox: context.account.postbox, - network: context.account.network, + stateManager: context.account.stateManager, energyUsageSettings: context.sharedContext.energyUsageSettings, contentSettings: context.currentContentSettings.with { $0 }, animationCache: context.animationCache, diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift index 4a60f7bc7f..103e3af4b7 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -54,7 +53,7 @@ public class LocalizationListController: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) - self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed)) + self.editItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.editPressed)) self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift index 2cb090e83a..ed9aef7f0c 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -678,7 +677,7 @@ final class LocalizationListControllerNode: ViewControllerTracingNode { var listInsets = layout.insets(options: [.input]) listInsets.top += navigationBarHeight - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) listInsets.left += inset listInsets.right += inset diff --git a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift index 5da4449a77..1c5976d031 100644 --- a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index 9dc6416674..bc029a99ae 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -292,7 +292,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle navigationController?.pushViewController(value, animated: false) } presentControllerImpl = { [weak controller] value, arguments in - controller?.present(value, in: .window(.root), with: arguments ?? ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + controller?.present(value, in: .window(.root), with: arguments) } replaceTopControllerImpl = { [weak navigationController] c in navigationController?.replaceTopController(c, animated: true) diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 4caf2e68c7..555deef539 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -791,10 +791,10 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { self?.view.endEditing(true) } - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) - + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) + let previousEntriesHolder = Atomic<([NotificationExceptionEntry], PresentationTheme, PresentationStrings)?>(value: nil) - + self.listDisposable = (combineLatest(context.sharedContext.presentationData, statePromise.get(), preferences, context.engine.peers.notificationSoundList()) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, prefs, notificationSoundList in let entries = notificationsExceptionEntries(presentationData: presentationData, notificationSoundList: notificationSoundList, state: state) let previousEntriesAndPresentationData = previousEntriesHolder.swap((entries, presentationData.theme, presentationData.strings)) @@ -1045,10 +1045,10 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont } - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) - + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) + let previousEntriesHolder = Atomic<([NotificationExceptionEntry], PresentationTheme, PresentationStrings)?>(value: nil) - + let stateQuery = stateAndPeers |> map { stateAndPeers -> String? in return stateAndPeers.1 @@ -1056,7 +1056,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont |> distinctUntilChanged let searchSignal = stateQuery - |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesView, [EngineRenderedPeer]), NoError> in + |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesEntry?, [EngineRenderedPeer]), NoError> in var contactsSignal: Signal<[EngineRenderedPeer], NoError> = .single([]) if let query = query { contactsSignal = context.account.postbox.searchPeers(query: query) diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift index c2ff00d359..6c97429dc4 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptions.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -44,7 +43,7 @@ public class NotificationExceptionsController: ViewController { self._hasGlassStyle = true self.removeAllItem = UIBarButtonItem(title: self.presentationData.strings.Notification_Exceptions_DeleteAll, style: .plain, target: self, action: #selector(self.removeAllPressed)) - self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed)) + self.editItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.editPressed)) self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -99,7 +98,7 @@ public class NotificationExceptionsController: ViewController { self.controllerNode.updatePresentationData(self.presentationData) let removeAllItem = UIBarButtonItem(title: self.presentationData.strings.Notification_Exceptions_DeleteAll, style: .plain, target: self, action: #selector(self.removeAllPressed)) - let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed)) + let editItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.editPressed)) let doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) if self.navigationItem.rightBarButtonItem === self.editItem { self.navigationItem.rightBarButtonItem = editItem diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift index 0109d7090e..9703406855 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,7 +23,7 @@ private struct CounterTagSettings: OptionSet { self.rawValue = rawValue } - init(summaryTags: PeerSummaryCounterTags) { + init(summaryTags: EnginePeerSummaryCounterTags) { var result = CounterTagSettings() if summaryTags.contains(.contact) { result.insert(.regularChatsAndGroups) @@ -35,8 +34,8 @@ private struct CounterTagSettings: OptionSet { self = result } - func toSumaryTags() -> PeerSummaryCounterTags { - var result = PeerSummaryCounterTags() + func toSumaryTags() -> EnginePeerSummaryCounterTags { + var result = EnginePeerSummaryCounterTags() if self.contains(.regularChatsAndGroups) { result.insert(.contact) result.insert(.nonContact) @@ -779,22 +778,22 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions }) let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) let exceptionsSignal = Signal.single(exceptionsList) |> then(context.engine.peers.notificationExceptionsList() |> map(Optional.init)) let defaultStorySettings = PeerStoryNotificationSettings.default notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in - var users:[PeerId : NotificationExceptionWrapper] = [:] - var groups: [PeerId : NotificationExceptionWrapper] = [:] - var channels: [PeerId : NotificationExceptionWrapper] = [:] - var stories: [PeerId : NotificationExceptionWrapper] = [:] + var users:[EnginePeer.Id : NotificationExceptionWrapper] = [:] + var groups: [EnginePeer.Id : NotificationExceptionWrapper] = [:] + var channels: [EnginePeer.Id : NotificationExceptionWrapper] = [:] + var stories: [EnginePeer.Id : NotificationExceptionWrapper] = [:] if let list = list { for (key, value) in list.settings { if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId { if value.storySettings != defaultStorySettings { - stories[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + stories[key] = NotificationExceptionWrapper(settings: value, peer: peer) } switch value.muteState { @@ -805,24 +804,24 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + users[key] = NotificationExceptionWrapper(settings: value, peer: peer) default: - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = peer, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) } } } default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + users[key] = NotificationExceptionWrapper(settings: value, peer: peer) default: - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = peer, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) } } } @@ -858,7 +857,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions |> map { presentationData, sharedData, view, exceptions, authorizationStatus, warningSuppressed, hasMoreThanOneAccount -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index 984938fda7..51ffe12b41 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -1007,7 +1007,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor }) let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings]) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) var automaticData: Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.NotificationSettings]), NoError> = .single(([], [:])) if case .stories = category { @@ -1039,7 +1039,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor let signal = combineLatest(context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), sharedData, preferences, statePromise.get(), automaticData) |> map { presentationData, notificationSoundList, sharedData, view, state, automaticData -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings @@ -1063,7 +1063,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor if !state.mode.peerIds.isEmpty { if state.editing { leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}) - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { value in return value.withUpdatedEditing(false) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift index 0e01ee3e27..172bd2081c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -15,12 +14,12 @@ import ItemListPeerActionItem private final class BlockedPeersControllerArguments { let context: AccountContext - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void let addPeer: () -> Void - let removePeer: (PeerId) -> Void - let openPeer: (Peer) -> Void - - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void) { + let removePeer: (EnginePeer.Id) -> Void + let openPeer: (EnginePeer) -> Void + + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.addPeer = addPeer @@ -36,12 +35,12 @@ private enum BlockedPeersSection: Int32 { private enum BlockedPeersEntryStableId: Hashable { case add - case peer(PeerId) + case peer(EnginePeer.Id) } private enum BlockedPeersEntry: ItemListNodeEntry { case add(PresentationTheme, String) - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) + case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, EnginePeer, ItemListPeerItemEditing, Bool) var section: ItemListSectionId { switch self { @@ -86,7 +85,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry { if lhsNameOrder != rhsNameOrder { return false } - if !lhsPeer.isEqual(rhsPeer) { + if lhsPeer != rhsPeer { return false } if lhsEditing != rhsEditing { @@ -132,7 +131,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry { arguments.removePeer(peer.id) })]) - return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .none, label: .none, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .none, label: .none, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -145,8 +144,8 @@ private enum BlockedPeersEntry: ItemListNodeEntry { private struct BlockedPeersControllerState: Equatable { let editing: Bool - let peerIdWithRevealedOptions: PeerId? - let removingPeerId: PeerId? + let peerIdWithRevealedOptions: EnginePeer.Id? + let removingPeerId: EnginePeer.Id? init() { self.editing = false @@ -154,7 +153,7 @@ private struct BlockedPeersControllerState: Equatable { self.removingPeerId = nil } - init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?) { + init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?, removingPeerId: EnginePeer.Id?) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId @@ -178,11 +177,11 @@ private struct BlockedPeersControllerState: Equatable { return BlockedPeersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> BlockedPeersControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> BlockedPeersControllerState { return BlockedPeersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId) } - func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> BlockedPeersControllerState { + func withUpdatedRemovingPeerId(_ removingPeerId: EnginePeer.Id?) -> BlockedPeersControllerState { return BlockedPeersControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId) } } @@ -195,7 +194,7 @@ private func blockedPeersControllerEntries(presentationData: PresentationData, s var index: Int32 = 0 for peer in blockedPeersState.peers { - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer.peer!, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.peerId == state.peerIdWithRevealedOptions), state.removingPeerId != peer.peerId)) + entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, EnginePeer(peer.peer!), ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.peerId == state.peerIdWithRevealedOptions), state.removingPeerId != peer.peerId)) index += 1 } } @@ -280,7 +279,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext: var rightNavigationButton: ItemListNavigationButton? if !blockedPeersState.peers.isEmpty { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift index bbc704e546..e99a3803a4 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift @@ -292,7 +292,7 @@ public func confirmPhoneNumberCodeController(context: AccountContext, phoneNumbe let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, currentDataPromise.get() |> deliverOnMainQueue, timeout.get() |> deliverOnMainQueue) |> deliverOnMainQueue |> map { presentationData, state, data, timeout -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) var rightNavigationButton: ItemListNavigationButton? diff --git a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift index d0b5427be0..520a405c55 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift @@ -313,7 +313,7 @@ func createPasswordController(context: AccountContext, createPasswordContext: Cr } if emailAlert { - presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: { + presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.TwoStepAuth_EmailSkip, action: { saveImpl() })]), nil) } else { @@ -377,7 +377,7 @@ func createPasswordController(context: AccountContext, createPasswordContext: Cr let signal = combineLatest(context.sharedContext.presentationData, statePromise.get()) |> deliverOnMainQueue |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) var rightNavigationButton: ItemListNavigationButton? @@ -386,7 +386,7 @@ func createPasswordController(context: AccountContext, createPasswordContext: Cr } else { switch state.state { case .setup: - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.passwordText.isEmpty, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: !state.passwordText.isEmpty, action: { saveImpl() }) case .pendingVerification: diff --git a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift index 9d290f5328..3a217663c8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -547,11 +546,11 @@ public func dataPrivacyController(context: AccountContext, focusOnItemTag: DataP } |> distinctUntilChanged - let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]), context.engine.peers.recentPeers(), hasBotSettings) + let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)), context.engine.peers.recentPeers(), hasBotSettings) |> map { presentationData, state, noticeView, sharedData, preferences, recentPeers, hasBotSettings -> (ItemListControllerState, (ItemListNodeState, Any)) in let secretChatLinkPreviews = noticeView.value.flatMap({ ApplicationSpecificNotice.getSecretChatLinkPreviews($0) }) - - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings + + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? ContactsSettings.defaultSettings let synchronizeDeviceContacts: Bool = settings.synchronizeContacts diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 2efd3336d2..13dea1935b 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -142,16 +141,16 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - - var peers = SimpleDictionary() - let messages = SimpleDictionary() + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) + + var peers = EngineSimpleDictionary() + let messages = EngineSimpleDictionary() peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: item.peerName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - let forwardInfo = MessageForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil, flags: []) - - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil) + let forwardInfo = EngineMessage.ForwardInfo(author: item.linkEnabled ? peers[peerId] : nil, source: nil, sourceMessageId: nil, date: 0, authorSignature: item.linkEnabled ? nil : item.peerName, psaType: nil, flags: []) + + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: forwardInfo, author: nil, text: item.strings.Privacy_Forwards_PreviewMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil) var node: ListViewItemNode? if let current = currentNode { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index 7ccb1d9560..66aaae7efe 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -303,7 +303,7 @@ public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, }, openCustomValue: { let currentValue = stateValue.with({ $0 }).updatedValue - let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in + let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, completion: { value in updateValue(value) }) presentControllerImpl?(controller, nil) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 0d423438f9..f390f231b6 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -340,7 +340,7 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP } } - updatedPeers[peer.id] = SelectivePrivacyPeer(peer: peer._asPeer(), participantCount: participantCount) + updatedPeers[peer.id] = SelectivePrivacyPeer(peer: peer, participantCount: participantCount) } } return updatedPeers diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift index 7ce3e638f5..8fa2031bbd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import LocalAuthentication @@ -15,6 +14,7 @@ import LocalAuth import PasscodeUI import TelegramStringFormatting import TelegramIntents +import ContextUI private final class PasscodeOptionsControllerArguments { let turnPasscodeOff: () -> Void @@ -154,6 +154,18 @@ private struct PasscodeOptionsControllerState: Equatable { } } +private final class PasscodeOptionsContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + private struct PasscodeOptionsData: Equatable { let accessChallenge: PostboxAccessChallengeData let presentationSettings: PresentationPasscodeSettings @@ -227,9 +239,12 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode let statePromise = ValuePromise(initialState, ignoreRepeated: true) var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var popControllerImpl: (() -> Void)? var replaceTopControllerImpl: ((ViewController, Bool) -> Void)? + var findAutolockReferenceNode: (() -> ItemListDisclosureItemNode?)? + var currentAutolockTimeout: Int32? let actionsDisposable = DisposableSet() @@ -324,8 +339,6 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode }) }, changePasscodeTimeout: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = ActionSheetController(presentationData: presentationData) - var items: [ActionSheetItem] = [] let setAction: (Int32?) -> Void = { value in let _ = (passcodeOptionsDataPromise.get() |> take(1)).start(next: { [weak passcodeOptionsDataPromise] data in @@ -343,24 +356,38 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode values.sort() #endif + var items: [ContextMenuItem] = [] for value in values { var t: Int32? if value != 0 { t = value } - items.append(ActionSheetButtonItem(title: autolockStringForTimeout(strings: presentationData.strings, timeout: t), color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - + items.append(.action(ContextMenuActionItem(text: autolockStringForTimeout(strings: presentationData.strings, timeout: t), icon: { theme in + if currentAutolockTimeout == t { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) setAction(t) - })) + }))) } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - presentControllerImpl?(actionSheet, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + guard let sourceNode = findAutolockReferenceNode?() else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(PasscodeOptionsContextReferenceContentSource(sourceView: sourceNode.labelNode.view)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) }, changeTouchId: { value in let _ = (passcodeOptionsDataPromise.get() |> take(1)).start(next: { [weak passcodeOptionsDataPromise] data in passcodeOptionsDataPromise?.set(.single(data.withUpdatedPresentationSettings(data.presentationSettings.withUpdatedEnableBiometrics(value)))) @@ -373,7 +400,8 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), passcodeOptionsDataPromise.get()) |> deliverOnMainQueue |> map { presentationData, state, passcodeOptionsData -> (ItemListControllerState, (ItemListNodeState, Any)) in - + currentAutolockTimeout = passcodeOptionsData.presentationSettings.autolockTimeout + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PasscodeSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: passcodeOptionsControllerEntries(presentationData: presentationData, state: state, passcodeOptionsData: passcodeOptionsData), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: false) @@ -388,6 +416,9 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode controller.present(c, in: .window(.root), with: p) } } + presentInGlobalOverlayImpl = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) + } pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) } @@ -397,6 +428,9 @@ func passcodeOptionsController(context: AccountContext, focusOnItemTag: Passcode replaceTopControllerImpl = { [weak controller] c, animated in (controller?.navigationController as? NavigationController)?.replaceTopController(c, animated: animated) } + findAutolockReferenceNode = { [weak controller] in + return controller?.itemNode(forTag: PasscodeOptionsEntryTag.autolock) as? ItemListDisclosureItemNode + } if let focusOnItemTag { var didFocusOnItem = false diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index e024a7d320..4ed39ba4d5 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -13,6 +13,7 @@ import AccountContext import TelegramNotices import LocalAuth import AppBundle +import OpenInExternalAppUI import PasswordSetupUI import UndoUI import PremiumUI @@ -20,6 +21,7 @@ import AuthorizationUI import AuthenticationServices import ChatTimerScreen import PasskeysScreen +import ContextUI private final class PrivacyAndSecurityControllerArguments { let account: Account @@ -42,11 +44,12 @@ private final class PrivacyAndSecurityControllerArguments { let setupAccountAutoremove: () -> Void let setupMessageAutoremove: () -> Void let openDataSettings: () -> Void + let openBrowserSelection: () -> Void let openEmailSettings: (String?) -> Void let openMessagePrivacy: () -> Void let openGiftsPrivacy: () -> Void - init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openBioPrivacy: @escaping () -> Void, openBirthdayPrivacy: @escaping () -> Void, openSavedMusicPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openPasskeys: @escaping () -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, setupMessageAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void, openMessagePrivacy: @escaping () -> Void, openGiftsPrivacy: @escaping () -> Void) { + init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openBioPrivacy: @escaping () -> Void, openBirthdayPrivacy: @escaping () -> Void, openSavedMusicPrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openPasskeys: @escaping () -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, setupMessageAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openBrowserSelection: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void, openMessagePrivacy: @escaping () -> Void, openGiftsPrivacy: @escaping () -> Void) { self.account = account self.openBlockedUsers = openBlockedUsers self.openLastSeenPrivacy = openLastSeenPrivacy @@ -67,6 +70,7 @@ private final class PrivacyAndSecurityControllerArguments { self.setupAccountAutoremove = setupAccountAutoremove self.setupMessageAutoremove = setupMessageAutoremove self.openDataSettings = openDataSettings + self.openBrowserSelection = openBrowserSelection self.openEmailSettings = openEmailSettings self.openMessagePrivacy = openMessagePrivacy self.openGiftsPrivacy = openGiftsPrivacy @@ -81,6 +85,7 @@ private enum PrivacyAndSecuritySection: Int32 { case messageAutoremove case dataSettings case loginEmail + case linkHandling } public enum PrivacyAndSecurityEntryTag: ItemListItemTag { @@ -130,6 +135,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case messageAutoremoveInfo(PresentationTheme, String) case dataSettings(PresentationTheme, String) case dataSettingsInfo(PresentationTheme, String) + case openLinksIn(PresentationTheme, String, String) var section: ItemListSectionId { switch self { @@ -145,6 +151,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return PrivacyAndSecuritySection.account.rawValue case .dataSettings, .dataSettingsInfo: return PrivacyAndSecuritySection.dataSettings.rawValue + case .openLinksIn: + return PrivacyAndSecuritySection.linkHandling.rawValue } } @@ -214,6 +222,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return 31 case .dataSettingsInfo: return 32 + case .openLinksIn: + return 33 } } @@ -411,6 +421,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { } else { return false } + case let .openLinksIn(lhsTheme, lhsText, lhsValue): + if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } } } @@ -530,6 +546,10 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { }) case let .dataSettingsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) + case let .openLinksIn(_, text, value): + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { + arguments.openBrowserSelection() + }) } } } @@ -541,6 +561,18 @@ private struct PrivacyAndSecurityControllerState: Equatable { var updatingOnlyAllowPremiumNonContacts: Bool? = nil } +private final class PrivacyAndSecurityContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + private func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> Int { var result = 0 for (_, peer) in peers { @@ -628,6 +660,7 @@ private func privacyAndSecurityControllerEntries( isPremium: Bool, loginEmail: String?, accountPeer: EnginePeer?, + defaultWebBrowser: String, appConfiguration: AppConfiguration ) -> [PrivacyAndSecurityEntry] { var entries: [PrivacyAndSecurityEntry] = [] @@ -790,6 +823,8 @@ private func privacyAndSecurityControllerEntries( entries.append(.dataSettings(presentationData.theme, presentationData.strings.PrivacySettings_DataSettings)) entries.append(.dataSettingsInfo(presentationData.theme, presentationData.strings.PrivacySettings_DataSettingsHelp)) + entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser)) + return entries } @@ -838,7 +873,9 @@ public func privacyAndSecurityController( var pushControllerImpl: ((ViewController, Bool) -> Void)? var replaceTopControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var getNavigationControllerImpl: (() -> NavigationController?)? + var findAccountTimeoutReferenceNode: (() -> ItemListDisclosureItemNode?)? let actionsDisposable = DisposableSet() @@ -1021,12 +1058,12 @@ public func privacyAndSecurityController( let privacySignal = privacySettingsPromise.get() |> take(1) - let callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration])) + let callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration))) |> take(1) |> map { sharedData, view -> (VoiceCallSettings, VoipConfiguration) in let voiceCallSettings: VoiceCallSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) ?? .defaultSettings - let voipConfiguration = view.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue - + let voipConfiguration = view?.get(VoipConfiguration.self) ?? .defaultValue + return (voiceCallSettings, voipConfiguration) } @@ -1313,12 +1350,8 @@ public func privacyAndSecurityController( |> take(1) |> deliverOnMainQueue updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable] privacySettingsValue in - if let _ = privacySettingsValue { + if let privacySettingsValue = privacySettingsValue { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } let timeoutAction: (Int32) -> Void = { timeout in if let updateAccountTimeoutDisposable = updateAccountTimeoutDisposable { updateState { state in @@ -1355,15 +1388,24 @@ public func privacyAndSecurityController( 548 * 24 * 60 * 60, 730 * 24 * 60 * 60 ] - var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in - return ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))), action: { - dismissAction() + var timeoutItems: [ContextMenuItem] = timeoutValues.map { value in + return .action(ContextMenuActionItem(text: presentationData.strings.MessageTimer_Months(max(1, value / (60 * 60 * 24 * 30))), icon: { theme in + if privacySettingsValue.accountRemovalTimeout == value { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) timeoutAction(value) - }) + })) } - timeoutItems.append(ActionSheetButtonItem(title: presentationData.strings.PrivacySettings_DeleteAccountNow, color: .destructive, action: { - dismissAction() - + timeoutItems.append(.separator) + timeoutItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PrivacySettings_DeleteAccountNow, textColor: .destructive, icon: { _ in + return nil + }, action: { _, f in + f(.default) + guard let navigationController = getNavigationControllerImpl?() else { return } @@ -1374,12 +1416,22 @@ public func privacyAndSecurityController( let optionsController = deleteAccountOptionsController(context: context, navigationController: navigationController, hasTwoStepAuth: hasTwoStepAuth ?? false, twoStepAuthData: twoStepAuthData) pushControllerImpl?(optionsController, true) }) - })) - controller.setItemGroups([ - ActionSheetItemGroup(items: timeoutItems), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller) + }))) + + guard let sourceNode = findAccountTimeoutReferenceNode?() else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(PrivacyAndSecurityContextReferenceContentSource(sourceView: sourceNode.labelNode.view)), + items: .single(ContextController.Items(content: .list(timeoutItems))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) } })) }, setupMessageAutoremove: { @@ -1405,6 +1457,9 @@ public func privacyAndSecurityController( })) }, openDataSettings: { pushControllerImpl?(dataPrivacyController(context: context), true) + }, openBrowserSelection: { + let controller = webBrowserSettingsController(context: context) + pushControllerImpl?(controller, true) }, openEmailSettings: { emailPattern in if let emailPattern, !emailPattern.contains(" ") { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1524,13 +1579,18 @@ public func privacyAndSecurityController( } } + let webBrowserData = combineLatest( + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings, ApplicationSpecificSharedDataKeys.webBrowserSettings]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.webBrowserSettings)) + ) + let signal = combineLatest( queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), privacySettingsPromise.get(), context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.secretChatLinkPreviewsKey()), - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.contactSynchronizationSettings]), + webBrowserData, context.engine.peers.recentPeers(), blockedPeersState.get(), webSessionsContext.state, @@ -1541,7 +1601,7 @@ public func privacyAndSecurityController( context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), loginEmail ) - |> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, passkeys, appConfiguration, accountPeer, loginEmail -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, privacySettings, noticeView, webBrowserData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, passkeys, appConfiguration, accountPeer, loginEmail -> (ItemListControllerState, (ItemListNodeState, Any)) in var canAutoarchive = false if let data = appConfiguration.data, let hasAutoarchive = data["autoarchive_setting_available"] as? Bool { canAutoarchive = hasAutoarchive @@ -1556,8 +1616,26 @@ public func privacyAndSecurityController( let isPremium = accountPeer?.isPremium ?? false let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }).isPremiumDisabled + let localWebBrowserSettings = webBrowserData.0.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings + let accountWebBrowserSettings = webBrowserData.1?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + + let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org")) + let defaultWebBrowser: String + if accountWebBrowserSettings.openExternalBrowser { + var defaultExternalBrowser = localWebBrowserSettings.defaultWebBrowser + if defaultExternalBrowser == nil || defaultExternalBrowser == "inApp" || defaultExternalBrowser == "inAppSafari" { + defaultExternalBrowser = "safari" + } + if let option = options.first(where: { $0.identifier == defaultExternalBrowser }) { + defaultWebBrowser = option.title + } else { + defaultWebBrowser = "Safari" + } + } else { + defaultWebBrowser = presentationData.strings.WebBrowser_Telegram + } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, hasPasskeys: passkeys.0, displayPasskeys: displayPasskeys, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium, loginEmail: loginEmail, accountPeer: accountPeer, appConfiguration: appConfiguration), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, hasPasskeys: passkeys.0, displayPasskeys: displayPasskeys, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium, loginEmail: loginEmail, accountPeer: accountPeer, defaultWebBrowser: defaultWebBrowser, appConfiguration: appConfiguration), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -1575,9 +1653,15 @@ public func privacyAndSecurityController( presentControllerImpl = { [weak controller] c in controller?.present(c, in: .window(.root), with: nil) } + presentInGlobalOverlayImpl = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) + } getNavigationControllerImpl = { [weak controller] in return (controller?.navigationController as? NavigationController) } + findAccountTimeoutReferenceNode = { [weak controller] in + return controller?.itemNode(forTag: PrivacyAndSecurityEntryTag.accountTimeout) as? ItemListDisclosureItemNode + } controller.didAppear = { _ in updateHasTwoStepAuth() diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift index b6c12d70df..7f309d72a7 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index e8847e7d57..96e61a27ee 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -166,7 +166,7 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { let noticeSize = self.noticeNode.measure(CGSize(width: layout.size.width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude)) let buttonInset: CGFloat - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { buttonInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) } else { buttonInset = 0.0 diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift index c07fe99849..757ce671e2 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift @@ -34,7 +34,7 @@ enum ItemListRecentSessionItemText { case none } -final class ItemListRecentSessionItem: ListViewItem, ItemListItem { +final class ItemListRecentSessionItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat @@ -47,6 +47,10 @@ final class ItemListRecentSessionItem: ListViewItem, ItemListItem { let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void let removeSession: (Int64) -> Void let action: (() -> Void)? + + var hasActiveRevealOptions: Bool { + return self.revealed + } init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editable: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { self.presentationData = presentationData @@ -193,6 +197,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { private var layoutParams: (ItemListRecentSessionItem, ListViewItemLayoutParams, ItemListNeighbors)? private var editableControlNode: ItemListEditableControlNode? + private var isHighlighted = false override public var canBeSelected: Bool { if let item = self.layoutParams?.0, let _ = item.action { @@ -265,19 +270,12 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { return { item, params, neighbors in var updatedTheme: PresentationTheme? - let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) - let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) - - let verticalInset: CGFloat - switch item.systemStyle { - case .glass: - verticalInset = 14.0 - case .legacy: - verticalInset = 10.0 - } + let titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0)) + let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) + let verticalInset: CGFloat = 11.0 let titleSpacing: CGFloat = 1.0 - let textSpacing: CGFloat = 3.0 + let textSpacing: CGFloat = 2.0 if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme @@ -289,7 +287,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] if item.editable && item.enabled { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_Terminate, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_Terminate, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] } else { peerRevealOptions = [] } @@ -307,13 +305,6 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { deviceString = item.session.deviceModel } -// if !item.session.platform.isEmpty { -// if !deviceString.isEmpty { -// deviceString += ", " -// } -// deviceString += item.session.platform -// } - var updatedIcon: UIImage? if item.session != currentItem?.session { updatedIcon = iconForSession(item.session).0 @@ -352,7 +343,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + appLayout.size.height + textSpacing + locationLayout.size.height) + let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + appLayout.size.height + textSpacing + locationLayout.size.height - 4.0) let separatorHeight = UIScreenPixel let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 @@ -487,26 +478,29 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -516,12 +510,12 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 15.0), size: CGSize(width: 30.0, height: 30.0))) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset - 2.0), size: titleLayout.size)) transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: appLayout.size)) transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.appNode.frame.maxY + textSpacing), size: locationLayout.size)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))), transition: transition) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -535,39 +529,8 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted && (self.layoutParams?.0.enabled ?? false) { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted && (self.layoutParams?.0.enabled ?? false) + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -602,6 +565,12 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size)) transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: self.locationNode.frame.minY), size: self.locationNode.bounds.size)) } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } override func revealOptionsInteractivelyOpened() { if let (item, _, _) = self.layoutParams { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift index 1dc5be3814..7e9fa8e762 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -13,6 +12,7 @@ import AvatarNode import TelegramStringFormatting import LocalizedPeerData import AccountContext +import AppBundle struct ItemListWebsiteItemEditing: Equatable { let editing: Bool @@ -29,14 +29,14 @@ struct ItemListWebsiteItemEditing: Equatable { } } -final class ItemListWebsiteItem: ListViewItem, ItemListItem { +final class ItemListWebsiteItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem { let context: AccountContext let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let nameDisplayOrder: PresentationPersonNameOrder let website: WebAuthorization - let peer: Peer? + let peer: EnginePeer? let enabled: Bool let editing: Bool let revealed: Bool @@ -44,8 +44,12 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem { let setSessionIdWithRevealedOptions: (Int64?, Int64?) -> Void let removeSession: (Int64) -> Void let action: (() -> Void)? + + var hasActiveRevealOptions: Bool { + return self.revealed + } - init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { + init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: EnginePeer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { self.context = context self.presentationData = presentationData self.systemStyle = systemStyle @@ -110,7 +114,7 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem { } } -private let avatarFont = avatarPlaceholderFont(size: 11.0) +private let avatarFont = avatarPlaceholderFont(size: 13.0) private func trimmedLocationName(_ session: WebAuthorization) -> String { var country = session.region @@ -142,6 +146,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { private var layoutParams: (ItemListWebsiteItem, ListViewItemLayoutParams, ItemListNeighbors)? private var editableControlNode: ItemListEditableControlNode? + private var isHighlighted = false override public var canBeSelected: Bool { if let item = self.layoutParams?.0, let _ = item.action { @@ -214,8 +219,12 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { return { item, params, neighbors in var updatedTheme: PresentationTheme? - let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) - let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0)) + let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) + + let verticalInset: CGFloat = 11.0 + let titleSpacing: CGFloat = 1.0 + let textSpacing: CGFloat = 2.0 if currentItem?.presentationData !== item.presentationData.theme { updatedTheme = item.presentationData.theme @@ -225,12 +234,12 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { var appAttributedString: NSAttributedString? var locationAttributedString: NSAttributedString? - let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_LogOut, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)] + let peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.AuthSessions_LogOut, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)] let rightInset: CGFloat = params.rightInset - if let user = item.peer as? TelegramUser { - titleAttributedString = NSAttributedString(string: EnginePeer(user).displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) + if let peer = item.peer, case .user = peer { + titleAttributedString = NSAttributedString(string: peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder), font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) } var appString = "" @@ -272,17 +281,12 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { editingOffset = 0.0 } - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset - 5.0 - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - var verticalInset: CGFloat = 0.0 - if case .glass = item.systemStyle { - verticalInset = 4.0 - } - let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 75.0 + verticalInset * 2.0) + let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + appLayout.size.height + textSpacing + locationLayout.size.height - 4.0) let separatorHeight = UIScreenPixel let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 @@ -332,7 +336,7 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { } if let peer = item.peer { - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(peer), authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false) + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false) } let revealOffset = strongSelf.revealOffset @@ -411,26 +415,29 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { let hasCorners = itemListHasRoundedBlockLayout(params) var hasTopCorners = false var hasBottomCorners = false + let topStripeIsHidden: Bool switch neighbors.top { case .sameSection(false): - strongSelf.topStripeNode.isHidden = true + topStripeIsHidden = true default: hasTopCorners = true - strongSelf.topStripeNode.isHidden = hasCorners + topStripeIsHidden = hasCorners } let bottomStripeInset: CGFloat let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool switch neighbors.bottom { case .sameSection(false): bottomStripeInset = leftInset + editingOffset bottomStripeOffset = -separatorHeight - strongSelf.bottomStripeNode.isHidden = false + bottomStripeIsHidden = false default: bottomStripeInset = 0.0 bottomStripeOffset = 0.0 hasBottomCorners = true - strongSelf.bottomStripeNode.isHidden = hasCorners + bottomStripeIsHidden = hasCorners } + strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil @@ -441,13 +448,13 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: verticalInset + 12.0), size: CGSize(width: 30.0, height: 30.0))) + transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 15.0), size: CGSize(width: 30.0, height: 30.0))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 10.0), size: titleLayout.size)) - transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 30.0), size: appLayout.size)) - transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 50.0), size: locationLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset - 2.0), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: appLayout.size)) + transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: strongSelf.appNode.frame.maxY + textSpacing), size: locationLayout.size)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))), transition: transition) strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) @@ -461,39 +468,8 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) - if highlighted && (self.layoutParams?.0.enabled ?? false) { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - var anchorNode: ASDisplayNode? - if self.bottomStripeNode.supernode != nil { - anchorNode = self.bottomStripeNode - } else if self.topStripeNode.supernode != nil { - anchorNode = self.topStripeNode - } else if self.backgroundNode.supernode != nil { - anchorNode = self.backgroundNode - } - if let anchorNode = anchorNode { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode) - } else { - self.addSubnode(self.highlightedBackgroundNode) - } - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } + self.isHighlighted = highlighted && (self.layoutParams?.0.enabled ?? false) + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) } override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { @@ -528,6 +504,12 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset, y: self.appNode.frame.minY), size: self.appNode.bounds.size)) transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + self.revealOffset + editingOffset, y: self.locationNode.frame.minY), size: self.locationNode.bounds.size)) } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } override func revealOptionsInteractivelyOpened() { if let (item, _, _) = self.layoutParams { @@ -550,3 +532,376 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { } } } + +final class ItemListConnectedBotSessionItem: ListViewItem, ItemListItem { + let context: AccountContext + let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle + let dateTimeFormat: PresentationDateTimeFormat + let nameDisplayOrder: PresentationPersonNameOrder + let bot: TelegramAccountConnectedBot + let peer: EnginePeer? + let enabled: Bool + let sectionId: ItemListSectionId + let action: (() -> Void)? + + init( + context: AccountContext, + presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, + dateTimeFormat: PresentationDateTimeFormat, + nameDisplayOrder: PresentationPersonNameOrder, + bot: TelegramAccountConnectedBot, + peer: EnginePeer?, + enabled: Bool, + sectionId: ItemListSectionId, + action: (() -> Void)? + ) { + self.context = context + self.presentationData = presentationData + self.systemStyle = systemStyle + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.bot = bot + self.peer = peer + self.enabled = enabled + self.sectionId = sectionId + self.action = action + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ItemListConnectedBotSessionItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply(false) }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? ItemListConnectedBotSessionItemNode { + let makeLayout = nodeValue.asyncLayout() + + let animated: Bool + if case .None = animation { + animated = false + } else { + animated = true + } + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply(animated) + }) + } + } + } + } + } + + public var selectable: Bool = true + public func selected(listView: ListView) { + listView.clearHighlightAnimated(true) + + if self.enabled { + self.action?() + } + } +} + +private final class ItemListConnectedBotSessionItemNode: ItemListRevealOptionsItemNode { + private let backgroundNode: ASDisplayNode + private let topStripeNode: ASDisplayNode + private let bottomStripeNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private var disabledOverlayNode: ASDisplayNode? + private let maskNode: ASImageNode + + private let avatarNode: AvatarNode + private let titleNode: TextNode + private let appNode: TextNode + private let locationNode: TextNode + + private let containerNode: ASDisplayNode + override var controlsContainer: ASDisplayNode { + return self.containerNode + } + + private let activateArea: AccessibilityAreaNode + + private var layoutParams: (ItemListConnectedBotSessionItem, ListViewItemLayoutParams, ItemListNeighbors)? + private var isHighlighted = false + + override public var canBeSelected: Bool { + if let item = self.layoutParams?.0, let _ = item.action { + return true + } else { + return false + } + } + + init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + + self.topStripeNode = ASDisplayNode() + self.topStripeNode.isLayerBacked = true + + self.bottomStripeNode = ASDisplayNode() + self.bottomStripeNode.isLayerBacked = true + + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + + self.containerNode = ASDisplayNode() + + self.avatarNode = AvatarNode(font: avatarFont) + self.avatarNode.cornerRadius = 7.0 + self.avatarNode.clipsToBounds = true + + self.titleNode = TextNode() + self.titleNode.isUserInteractionEnabled = false + self.titleNode.contentMode = .left + self.titleNode.contentsScale = UIScreen.main.scale + + self.appNode = TextNode() + self.appNode.isUserInteractionEnabled = false + self.appNode.contentMode = .left + self.appNode.contentsScale = UIScreen.main.scale + + self.locationNode = TextNode() + self.locationNode.isUserInteractionEnabled = false + self.locationNode.contentMode = .left + self.locationNode.contentsScale = UIScreen.main.scale + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + + self.activateArea = AccessibilityAreaNode() + + super.init(layerBacked: false, rotated: false, seeThrough: false) + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.avatarNode) + self.containerNode.addSubnode(self.titleNode) + self.containerNode.addSubnode(self.appNode) + self.containerNode.addSubnode(self.locationNode) + + self.addSubnode(self.activateArea) + } + + func asyncLayout() -> (_ item: ItemListConnectedBotSessionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeAppLayout = TextNode.asyncLayout(self.appNode) + let makeLocationLayout = TextNode.asyncLayout(self.locationNode) + + var currentDisabledOverlayNode = self.disabledOverlayNode + let currentItem = self.layoutParams?.0 + + return { item, params, neighbors in + var updatedTheme: PresentationTheme? + + let titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0)) + let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) + + let verticalInset: CGFloat = 11.0 + let titleSpacing: CGFloat = 1.0 + let textSpacing: CGFloat = 2.0 + + if currentItem?.presentationData.theme !== item.presentationData.theme { + updatedTheme = item.presentationData.theme + } + + let titleString = item.peer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.nameDisplayOrder) ?? "Bot" + let titleAttributedString = NSAttributedString(string: titleString, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) + + let appAttributedString = NSAttributedString( + string: item.presentationData.strings.RecentSession_ConnectedBot_Subtitle, + font: textFont, + textColor: item.presentationData.theme.list.itemPrimaryTextColor + ) + + let timeString: String + if let date = item.bot.date { + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + timeString = stringForRelativeActivityTimestamp(strings: item.presentationData.strings, dateTimeFormat: item.dateTimeFormat, relativeTimestamp: date, relativeTo: timestamp) + } else { + timeString = "" + } + let locationAttributedString = NSAttributedString(string: item.presentationData.strings.RecentSessions_ConnectedBot_ConnectedTime(timeString).string, font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + + let rightInset: CGFloat = params.rightInset + let leftInset: CGFloat = 59.0 + params.leftInset + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - rightInset - 5.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let insets = itemListNeighborsGroupedInsets(neighbors, params) + let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + appLayout.size.height + textSpacing + locationLayout.size.height - 4.0) + let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + let layoutSize = layout.size + + if !item.enabled { + if currentDisabledOverlayNode == nil { + currentDisabledOverlayNode = ASDisplayNode() + currentDisabledOverlayNode?.backgroundColor = UIColor(white: 1.0, alpha: 0.5) + } + } else { + currentDisabledOverlayNode = nil + } + + return (layout, { [weak self] animated in + guard let self else { + return + } + + self.layoutParams = (item, params, neighbors) + + self.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height)) + self.activateArea.accessibilityLabel = titleAttributedString.string + + var accessibilityValue = appAttributedString.string + if !locationAttributedString.string.isEmpty { + accessibilityValue += "\n\(locationAttributedString.string)" + } + self.activateArea.accessibilityValue = accessibilityValue + self.activateArea.accessibilityTraits = item.enabled ? [] : .notEnabled + + if let updatedTheme { + let _ = updatedTheme + self.topStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + self.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemBlocksSeparatorColor + self.backgroundNode.backgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor + self.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + } + + if let peer = item.peer { + self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false) + self.avatarNode.alpha = 1.0 + } else { + self.avatarNode.alpha = 0.0 + } + + let transition: ContainedViewLayoutTransition + if animated { + transition = .animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + + if let currentDisabledOverlayNode { + if currentDisabledOverlayNode != self.disabledOverlayNode { + self.disabledOverlayNode = currentDisabledOverlayNode + self.addSubnode(currentDisabledOverlayNode) + currentDisabledOverlayNode.alpha = 0.0 + transition.updateAlpha(node: currentDisabledOverlayNode, alpha: 1.0) + currentDisabledOverlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height - separatorHeight)) + } else { + transition.updateFrame(node: currentDisabledOverlayNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height - separatorHeight))) + } + } else if let disabledOverlayNode = self.disabledOverlayNode { + transition.updateAlpha(node: disabledOverlayNode, alpha: 0.0, completion: { [weak disabledOverlayNode] _ in + disabledOverlayNode?.removeFromSupernode() + }) + self.disabledOverlayNode = nil + } + + let _ = titleApply() + let _ = appApply() + let _ = locationApply() + + if self.backgroundNode.supernode == nil { + self.insertSubnode(self.backgroundNode, at: 0) + } + if self.topStripeNode.supernode == nil { + self.insertSubnode(self.topStripeNode, at: 1) + } + if self.bottomStripeNode.supernode == nil { + self.insertSubnode(self.bottomStripeNode, at: 2) + } + if self.maskNode.supernode == nil { + self.addSubnode(self.maskNode) + } + + let hasCorners = itemListHasRoundedBlockLayout(params) + var hasTopCorners = false + var hasBottomCorners = false + let topStripeIsHidden: Bool + switch neighbors.top { + case .sameSection(false): + topStripeIsHidden = true + default: + hasTopCorners = true + topStripeIsHidden = hasCorners + } + let bottomStripeInset: CGFloat + let bottomStripeOffset: CGFloat + let bottomStripeIsHidden: Bool + switch neighbors.bottom { + case .sameSection(false): + bottomStripeInset = leftInset + bottomStripeOffset = -separatorHeight + bottomStripeIsHidden = false + default: + bottomStripeInset = 0.0 + bottomStripeOffset = 0.0 + hasBottomCorners = true + bottomStripeIsHidden = hasCorners + } + self.updateRevealOptionsSeparatorNodes(top: self.topStripeNode, bottom: self.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions) + + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil + + self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) + self.containerNode.frame = CGRect(origin: CGPoint(), size: self.backgroundNode.frame.size) + self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) + transition.updateFrame(node: self.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) + transition.updateFrame(node: self.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) + + transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: 15.0), size: CGSize(width: 30.0, height: 30.0))) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset - 2.0), size: titleLayout.size)) + transition.updateFrame(node: self.appNode, frame: CGRect(origin: CGPoint(x: leftInset, y: self.titleNode.frame.maxY + titleSpacing), size: appLayout.size)) + transition.updateFrame(node: self.locationNode, frame: CGRect(origin: CGPoint(x: leftInset, y: self.appNode.frame.maxY + textSpacing), size: locationLayout.size)) + + self.updateRevealOptionsHighlightedBackgroundFrame(self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))), transition: transition) + self.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + self.setRevealOptions((left: [], right: [])) + }) + } + } + + override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + super.setHighlighted(highlighted, at: point, animated: animated) + + self.isHighlighted = highlighted && (self.layoutParams?.0.enabled ?? false) + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.4, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } + + override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) { + super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition) + + self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode]) + } + + override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift index 0730050b3b..a177304ec6 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -12,6 +11,11 @@ import AccountContext import ItemListPeerActionItem import DeviceAccess import QrCodeUI +import ComponentFlow +import AlertComponent +import AlertCheckComponent +import AppBundle +import ContextUI private final class RecentSessionsControllerArguments { let context: AccountContext @@ -21,7 +25,8 @@ private final class RecentSessionsControllerArguments { let terminateOtherSessions: () -> Void let openSession: (RecentAccountSession) -> Void - let openWebSession: (WebAuthorization, Peer?) -> Void + let openConnectedBotSession: (TelegramAccountConnectedBot) -> Void + let openWebSession: (WebAuthorization, EnginePeer?) -> Void let removeWebSession: (Int64) -> Void let terminateAllWebSessions: () -> Void @@ -34,13 +39,14 @@ private final class RecentSessionsControllerArguments { let openDesktopLink: () -> Void let openWebLink: () -> Void - init(context: AccountContext, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, openSession: @escaping (RecentAccountSession) -> Void, openWebSession: @escaping (WebAuthorization, Peer?) -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void, openOtherAppsUrl: @escaping () -> Void, setupAuthorizationTTL: @escaping () -> Void, openDesktopLink: @escaping () -> Void, openWebLink: @escaping () -> Void) { + init(context: AccountContext, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, openSession: @escaping (RecentAccountSession) -> Void, openConnectedBotSession: @escaping (TelegramAccountConnectedBot) -> Void, openWebSession: @escaping (WebAuthorization, EnginePeer?) -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void, openOtherAppsUrl: @escaping () -> Void, setupAuthorizationTTL: @escaping () -> Void, openDesktopLink: @escaping () -> Void, openWebLink: @escaping () -> Void) { self.context = context self.setSessionIdWithRevealedOptions = setSessionIdWithRevealedOptions self.removeSession = removeSession self.terminateOtherSessions = terminateOtherSessions self.openSession = openSession + self.openConnectedBotSession = openConnectedBotSession self.openWebSession = openWebSession self.removeWebSession = removeWebSession @@ -72,6 +78,7 @@ private enum RecentSessionsSection: Int32 { private enum RecentSessionsEntryStableId: Hashable { case session(Int64) + case connectedBot(EnginePeer.Id) case index(Int32) case devicesInfo case ttl(Int32) @@ -116,8 +123,9 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case pendingSessionsInfo(SortIndex, String) case otherSessionsHeader(SortIndex, String) case addDevice(SortIndex, String) + case connectedBot(sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, bot: TelegramAccountConnectedBot, peer: EnginePeer?, enabled: Bool) case session(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool) - case website(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool) + case website(index: Int32, sortIndex: SortIndex, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: EnginePeer?, enabled: Bool, editing: Bool, revealed: Bool) case devicesInfo(SortIndex, String) case ttlHeader(SortIndex, String) case ttlTimeout(SortIndex, String, String) @@ -130,7 +138,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return RecentSessionsSection.currentSession.rawValue case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo: return RecentSessionsSection.pendingSessions.rawValue - case .otherSessionsHeader, .addDevice, .session, .website, .devicesInfo: + case .otherSessionsHeader, .addDevice, .connectedBot, .session, .website, .devicesInfo: return RecentSessionsSection.otherSessions.rawValue case .ttlHeader, .ttlTimeout: return RecentSessionsSection.ttl.rawValue @@ -163,6 +171,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return .index(9) case .addDevice: return .index(10) + case let .connectedBot(_, _, _, _, bot, _, _): + return .connectedBot(bot.id) case let .session(_, _, _, _, session, _, _, _): return .session(session.hash) case let .website(_, _, _, _, _, website, _, _, _, _): @@ -202,6 +212,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return index case let .addDevice(index, _): return index + case let .connectedBot(index, _, _, _, _, _, _): + return index case let .session(_, index, _, _, _, _, _, _): return index case let .website(_, index, _, _, _, _, _, _, _, _): @@ -283,6 +295,12 @@ private enum RecentSessionsEntry: ItemListNodeEntry { } else { return false } + case let .connectedBot(lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsBot, lhsPeer, lhsEnabled): + if case let .connectedBot(rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsBot, rhsPeer, rhsEnabled) = rhs, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, lhsBot == rhsBot, lhsPeer == rhsPeer, lhsEnabled == rhsEnabled { + return true + } else { + return false + } case let .currentSession(lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsSession): if case let .currentSession(rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsSession) = rhs, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession { return true @@ -296,7 +314,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return false } case let .website(lhsIndex, lhsSortIndex, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed): - if case let .website(rhsIndex, rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { + if case let .website(rhsIndex, rhsSortIndex, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsSortIndex == rhsSortIndex, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, lhsPeer == rhsPeer, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { return true } else { return false @@ -389,6 +407,21 @@ private enum RecentSessionsEntry: ItemListNodeEntry { return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.addDevice() }) + case let .connectedBot(_, _, dateTimeFormat, nameDisplayOrder, bot, peer, enabled): + return ItemListConnectedBotSessionItem( + context: arguments.context, + presentationData: presentationData, + systemStyle: .glass, + dateTimeFormat: dateTimeFormat, + nameDisplayOrder: nameDisplayOrder, + bot: bot, + peer: peer, + enabled: enabled, + sectionId: self.section, + action: { + arguments.openConnectedBotSession(bot) + } + ) case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed): return ItemListRecentSessionItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) @@ -476,7 +509,7 @@ private struct RecentSessionsControllerState: Equatable { } } -private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, sessionsState: ActiveSessionsContextState, enableQRLogin: Bool) -> [RecentSessionsEntry] { +private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, sessionsState: ActiveSessionsContextState, connectedBot: TelegramAccountConnectedBot?, connectedBotPeer: EnginePeer?, enableQRLogin: Bool) -> [RecentSessionsEntry] { var entries: [RecentSessionsEntry] = [] entries.append(.header(SortIndex(section: 0, item: 0), presentationData.strings.AuthSessions_HeaderInfo)) @@ -484,16 +517,19 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, if !sessionsState.sessions.isEmpty { var existingSessionIds = Set() entries.append(.currentSessionHeader(SortIndex(section: 1, item: 0), presentationData.strings.AuthSessions_CurrentSession)) + var currentSessionItemIndex = 1 if let index = sessionsState.sessions.firstIndex(where: { $0.hash == 0 }) { existingSessionIds.insert(sessionsState.sessions[index].hash) - entries.append(.currentSession(SortIndex(section: 1, item: 1), presentationData.strings, presentationData.dateTimeFormat, sessionsState.sessions[index])) + entries.append(.currentSession(SortIndex(section: 1, item: currentSessionItemIndex), presentationData.strings, presentationData.dateTimeFormat, sessionsState.sessions[index])) + currentSessionItemIndex += 1 } var hasAddDevice = false - if sessionsState.sessions.count > 1 || enableQRLogin { - if sessionsState.sessions.count > 1 { - entries.append(.terminateOtherSessions(SortIndex(section: 1, item: 2), presentationData.strings.AuthSessions_TerminateOtherSessions)) - entries.append(.currentSessionInfo(SortIndex(section: 1, item: 3), presentationData.strings.AuthSessions_TerminateOtherSessionsHelp)) + if sessionsState.sessions.count > 1 || enableQRLogin || connectedBot != nil { + if sessionsState.sessions.count > 1 || connectedBot != nil { + entries.append(.terminateOtherSessions(SortIndex(section: 1, item: currentSessionItemIndex), presentationData.strings.AuthSessions_TerminateOtherSessions)) + currentSessionItemIndex += 1 + entries.append(.currentSessionInfo(SortIndex(section: 1, item: currentSessionItemIndex), presentationData.strings.AuthSessions_TerminateOtherSessionsHelp)) } else if enableQRLogin { hasAddDevice = true // entries.append(.currentAddDevice(SortIndex(section: 1, item: 4), presentationData.strings.AuthSessions_AddDevice)) @@ -512,7 +548,7 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, entries.append(.pendingSessionsInfo(SortIndex(section: 3, item: 0), presentationData.strings.AuthSessions_IncompleteAttemptsInfo)) } - if sessionsState.sessions.count > 1 { + if sessionsState.sessions.count > 1 || connectedBot != nil { entries.append(.otherSessionsHeader(SortIndex(section: 4, item: 0), presentationData.strings.AuthSessions_OtherSessions)) } @@ -524,10 +560,16 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, return lhs.activityDate > rhs.activityDate }) + var otherSessionItemIndex = 0 + if let connectedBot { + entries.append(.connectedBot(sortIndex: SortIndex(section: 5, item: otherSessionItemIndex), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, bot: connectedBot, peer: connectedBotPeer, enabled: !state.terminatingOtherSessions)) + otherSessionItemIndex += 1 + } for i in 0 ..< filteredSessions.count { if !existingSessionIds.contains(filteredSessions[i].hash) { existingSessionIds.insert(filteredSessions[i].hash) - entries.append(.session(index: Int32(i), sortIndex: SortIndex(section: 5, item: i), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredSessions[i], enabled: state.removingSessionId != filteredSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredSessions[i].hash)) + entries.append(.session(index: Int32(i), sortIndex: SortIndex(section: 5, item: otherSessionItemIndex), strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredSessions[i], enabled: state.removingSessionId != filteredSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredSessions[i].hash)) + otherSessionItemIndex += 1 } } @@ -543,7 +585,7 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, return entries } -private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, websites: [WebAuthorization]?, peers: [PeerId : Peer]?) -> [RecentSessionsEntry] { +private func recentSessionsControllerEntries(presentationData: PresentationData, state: RecentSessionsControllerState, websites: [WebAuthorization]?, peers: [EnginePeer.Id: EnginePeer]?) -> [RecentSessionsEntry] { var entries: [RecentSessionsEntry] = [] if let websites = websites, let peers = peers { @@ -574,6 +616,18 @@ private func recentSessionsControllerEntries(presentationData: PresentationData, private final class RecentSessionsControllerImpl: ItemListController, RecentSessionsController { } +private final class RecentSessionsContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + public func recentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext, webSessionsContext: WebSessionsContext, websitesOnly: Bool, focusOnItemTag: RecentSessionsEntryTag? = nil) -> ViewController & RecentSessionsController { let statePromise = ValuePromise(RecentSessionsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: RecentSessionsControllerState()) @@ -591,8 +645,11 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont webSessionsContext.loadMore() var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var dismissImpl: (() -> Void)? + var findAutoTerminateReferenceNode: (() -> ItemListDisclosureItemNode?)? + var currentAuthorizationTTLDays: Int32? let actionsDisposable = DisposableSet() @@ -676,6 +733,8 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont let updateSessionDisposable = MetaDisposable() actionsDisposable.add(updateSessionDisposable) + let connectedBotAndPeerValue = Atomic<(TelegramAccountConnectedBot?, EnginePeer?)>(value: (nil, nil)) + let arguments = RecentSessionsControllerArguments(context: context, setSessionIdWithRevealedOptions: { sessionId, fromSessionId in updateState { state in if (sessionId == nil && fromSessionId == state.sessionIdWithRevealedOptions) || (sessionId != nil && fromSessionId == nil) { @@ -688,33 +747,98 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont removeSessionImpl(sessionId, {}) }, terminateOtherSessions: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = textAlertController( - context: context, - title: nil, - text: presentationData.strings.AuthSessions_TerminateOtherSessionsText, - actions: [ - TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateOtherSessions, action: { - updateState { - return $0.withUpdatedTerminatingOtherSessions(true) - } - - terminateOtherSessionsDisposable.set((activeSessionsContext.removeOther() - |> deliverOnMainQueue).start(error: { _ in - updateState { - return $0.withUpdatedTerminatingOtherSessions(false) + let performTerminate: (Bool) -> Void = { terminateBot in + updateState { + return $0.withUpdatedTerminatingOtherSessions(true) + } + + let signal: Signal + if terminateBot { + signal = activeSessionsContext.removeOther() + |> then(context.engine.accountData.setAccountConnectedBot(bot: nil) |> castError(TerminateSessionError.self)) + } else { + signal = activeSessionsContext.removeOther() + } + + terminateOtherSessionsDisposable.set((signal + |> deliverOnMainQueue).start(error: { _ in + updateState { + return $0.withUpdatedTerminatingOtherSessions(false) + } + }, completed: { + updateState { + return $0.withUpdatedTerminatingOtherSessions(false) + } + context.sharedContext.updateNotificationTokensRegistration() + })) + } + + let (connectedBot, connectedBotPeer) = connectedBotAndPeerValue.with { $0 } + if connectedBot != nil { + let checkState = AlertCheckComponent.ExternalState() + let botUsername = connectedBotPeer?.addressName.flatMap { addressName -> String? in + if addressName.isEmpty { + return nil + } else { + return "@\(addressName)" + } + } ?? connectedBotPeer?.compactDisplayTitle ?? "" + + var content: [AnyComponentWithIdentity] = [] + content.append(AnyComponentWithIdentity( + id: "text", + component: AnyComponent( + AlertTextComponent(content: .plain(presentationData.strings.AuthSessions_TerminateOtherSessionsText)) + ) + )) + var controller: AlertScreen? + content.append(AnyComponentWithIdentity( + id: "check", + component: AnyComponent( + AlertCheckComponent(title: presentationData.strings.RecentSessions_ConnectedBot_TerminateCheckbox(botUsername.isEmpty ? "" : "[\(botUsername)](peer)").string, initialValue: false, externalState: checkState, linkAction: { + guard let connectedBotPeer, let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: connectedBotPeer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return } - }, completed: { - updateState { - return $0.withUpdatedTerminatingOtherSessions(false) + if let controller { + controller.dismiss(completion: { + pushControllerImpl?(infoController) + }) + } else { + pushControllerImpl?(infoController) } - context.sharedContext.updateNotificationTokensRegistration() - })) - }), - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}) - ], - actionLayout: .vertical - ) - presentControllerImpl?(controller, nil) + }) + ) + )) + + controller = AlertScreen( + configuration: AlertScreen.Configuration(actionAlignment: .vertical), + content: content, + actions: [ + .init(title: presentationData.strings.AuthSessions_TerminateOtherSessions, type: .defaultDestructive, action: { + performTerminate(checkState.value) + }), + .init(title: presentationData.strings.Common_Cancel) + ], + updatedPresentationData: (presentationData, context.sharedContext.presentationData) + ) + if let controller { + presentControllerImpl?(controller, nil) + } + } else { + let controller = textAlertController( + context: context, + title: nil, + text: presentationData.strings.AuthSessions_TerminateOtherSessionsText, + actions: [ + TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateOtherSessions, action: { + performTerminate(false) + }), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}) + ], + actionLayout: .vertical + ) + presentControllerImpl?(controller, nil) + } }, openSession: { session in let controller = RecentSessionScreen(context: context, subject: .session(session), updateAcceptSecretChats: { value in updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsSecretChats(session, accepts: value).start()) @@ -725,13 +849,20 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont completion() }) }) - presentControllerImpl?(controller, nil) + pushControllerImpl?(controller) + }, openConnectedBotSession: { bot in + let controller = RecentSessionScreen(context: context, subject: .connectedBot(bot), updateAcceptSecretChats: { _ in + }, updateAcceptIncomingCalls: { _ in + }, remove: { completion in + completion() + }) + pushControllerImpl?(controller) }, openWebSession: { session, peer in - let controller = RecentSessionScreen(context: context, subject: .website(session, peer.flatMap(EnginePeer.init)), updateAcceptSecretChats: { _ in }, updateAcceptIncomingCalls: { _ in }, remove: { completion in + let controller = RecentSessionScreen(context: context, subject: .website(session, peer), updateAcceptSecretChats: { _ in }, updateAcceptIncomingCalls: { _ in }, remove: { completion in removeWebSessionImpl(session.hash) completion() }) - presentControllerImpl?(controller, nil) + pushControllerImpl?(controller) }, removeWebSession: { sessionId in removeWebSessionImpl(sessionId) }, terminateAllWebSessions: { @@ -779,10 +910,6 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/apps", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) }, setupAuthorizationTTL: { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } let ttlAction: (Int32) -> Void = { ttl in updateAuthorizationTTLDisposable.set(activeSessionsContext.updateAuthorizationTTL(days: ttl).start()) } @@ -792,17 +919,32 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont 90, 180 ] - let timeoutItems: [ActionSheetItem] = timeoutValues.map { value in - return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value * 24 * 60 * 60), action: { - dismissAction() + let timeoutItems: [ContextMenuItem] = timeoutValues.map { value in + return .action(ContextMenuActionItem(text: timeIntervalString(strings: presentationData.strings, value: value * 24 * 60 * 60), icon: { theme in + if currentAuthorizationTTLDays == value { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) ttlAction(value) - }) + })) } - controller.setItemGroups([ - ActionSheetItemGroup(items: timeoutItems), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller, nil) + guard let sourceNode = findAutoTerminateReferenceNode?() else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(RecentSessionsContextReferenceContentSource(sourceView: sourceNode.labelNode.view)), + items: .single(ContextController.Items(content: .list(timeoutItems))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) }, openDesktopLink: { context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://getdesktop.telegram.org", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) }, openWebLink: { @@ -811,9 +953,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont let previousMode = Atomic(value: .sessions) - let enableQRLogin = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let enableQRLogin = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> Bool in - guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else { + guard let appConfiguration = view?.get(AppConfiguration.self) else { return false } guard let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR else { @@ -823,9 +965,29 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont } |> distinctUntilChanged - let signal = combineLatest(context.sharedContext.presentationData, mode.get(), statePromise.get(), activeSessionsContext.state, webSessionsContext.state, enableQRLogin) + let connectedBotAndPeer = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: context.account.peerId) + ) + |> mapToSignal { connectedBot -> Signal<(TelegramAccountConnectedBot?, EnginePeer?), NoError> in + guard let connectedBot else { + return .single((nil, nil)) + } + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: connectedBot.id) + ) + |> map { peer in + return (connectedBot, peer) + } + } + |> afterNext { value in + let _ = connectedBotAndPeerValue.swap(value) + } + + let signal = combineLatest(context.sharedContext.presentationData, mode.get(), statePromise.get(), activeSessionsContext.state, webSessionsContext.state, enableQRLogin, connectedBotAndPeer) |> deliverOnMainQueue - |> map { presentationData, mode, state, sessionsState, websitesAndPeers, enableQRLogin -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, mode, state, sessionsState, websitesAndPeers, enableQRLogin, connectedBotAndPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in + currentAuthorizationTTLDays = sessionsState.ttlDays + var rightNavigationButton: ItemListNavigationButton? let websites = websitesAndPeers.sessions let peers = websitesAndPeers.peers @@ -834,7 +996,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont if state.terminatingOtherSessions { rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {}) } else if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in return state.withUpdatedEditing(false) } @@ -849,11 +1011,6 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont } let emptyStateItem: ItemListControllerEmptyStateItem? = nil -// if sessionsState.sessions.count == 1 && mode == .sessions { -// emptyStateItem = RecentSessionsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings) -// } else { -// emptyStateItem = nil -// } let title: ItemListControllerTitle let entries: [RecentSessionsEntry] @@ -868,7 +1025,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont case (.websites, let websites, let peers): entries = recentSessionsControllerEntries(presentationData: presentationData, state: state, websites: websites, peers: peers) default: - entries = recentSessionsControllerEntries(presentationData: presentationData, state: state, sessionsState: sessionsState, enableQRLogin: enableQRLogin) + entries = recentSessionsControllerEntries(presentationData: presentationData, state: state, sessionsState: sessionsState, connectedBot: connectedBotAndPeer.0, connectedBotPeer: connectedBotAndPeer.1, enableQRLogin: enableQRLogin) } let previousMode = previousMode.swap(mode) @@ -899,12 +1056,18 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont controller.present(c, in: .window(.root), with: p) } } + presentInGlobalOverlayImpl = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) + } pushControllerImpl = { [weak controller] c in controller?.push(c) } dismissImpl = { [weak controller] in controller?.dismiss() } + findAutoTerminateReferenceNode = { [weak controller] in + return controller?.itemNode(forTag: RecentSessionsEntryTag.autoTerminate) as? ItemListDisclosureItemNode + } if let focusOnItemTag { var didFocusOnItem = false diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift index 0b9dab7e70..2b6d4b53cd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift @@ -72,7 +72,7 @@ class RecentSessionsHeaderItem: ListViewItem, ItemListItem { } } -private let titleFont = Font.regular(13.0) +private let titleFont = Font.regular(14.0) class RecentSessionsHeaderItemNode: ListViewItemNode { private let titleNode: TextNode @@ -129,11 +129,11 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { updatedTheme = item.theme } - let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.freeTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in + let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in return (TelegramTextAttributes.URL, contents) })) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.2, cutout: nil, insets: UIEdgeInsets())) let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height + 69.0) let insets = itemListNeighborsGroupedInsets(neighbors, params) @@ -158,7 +158,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme)) } - let buttonSideInset: CGFloat = 36.0 + let buttonSideInset: CGFloat = 16.0 let buttonWidth = min(375, contentSize.width - buttonSideInset * 2.0) let buttonHeight = 52.0 let buttonFrame = CGRect(x: floorToScreenPixels((params.width - buttonWidth) / 2.0), y: contentSize.height - buttonHeight + 4.0, width: buttonWidth, height: buttonHeight) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift index 8ba28212a7..65ffcd5549 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift @@ -8,7 +8,7 @@ import AccountContext import TelegramPresentationData import ComponentFlow import ViewControllerComponent -import SheetComponent +import ResizableSheetComponent import MultilineTextComponent import BalancedTextComponent import GlassBarButtonComponent @@ -22,6 +22,39 @@ import ListActionItemComponent import AvatarComponent import TelegramStringFormatting import Markdown +import AppBundle +import TextFormat +import ChatbotSetupScreen + +private func botRecipientsCategoryCount(_ categories: TelegramBusinessRecipients.Categories) -> Int { + var count = 0 + if categories.contains(.existingChats) { + count += 1 + } + if categories.contains(.newChats) { + count += 1 + } + if categories.contains(.contacts) { + count += 1 + } + if categories.contains(.nonContacts) { + count += 1 + } + return count +} + +private let recentSessionCheckIcon: UIImage = { + return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.white.cgColor) + context.setLineWidth(1.98) + context.setLineCap(.round) + context.setLineJoin(.round) + context.translateBy(x: 1.0, y: 1.0) + + let _ = try? drawSvgPath(context, path: "M0.215053763,4.36080467 L3.31621263,7.70466293 L3.31621263,7.70466293 C3.35339229,7.74475231 3.41603123,7.74711109 3.45612061,7.70993143 C3.45920681,7.70706923 3.46210733,7.70401312 3.46480451,7.70078171 L9.89247312,0 S ") + })!.withRenderingMode(.alwaysTemplate) +}() private final class RecentSessionSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -48,12 +81,28 @@ private final class RecentSessionSheetContent: CombinedComponent { } final class State: ComponentState { + fileprivate struct ConnectedBotRecipientsState { + var categories: TelegramBusinessRecipients.Categories + var peers: [BusinessRecipientListScreen.ResolvedPeer] + var excludePeers: [BusinessRecipientListScreen.ResolvedPeer] + var excludeByDefault: Bool + } + + private let context: AccountContext + private let subject: RecentSessionScreen.Subject + private let botPeerDisposable = MetaDisposable() + var allowSecretChats: Bool? var allowIncomingCalls: Bool? + var botPeer: EnginePeer? + fileprivate var connectedBotRecipients: ConnectedBotRecipientsState? weak var controller: RecentSessionScreen? - init(subject: RecentSessionScreen.Subject) { + init(context: AccountContext, subject: RecentSessionScreen.Subject) { + self.context = context + self.subject = subject + super.init() switch subject { @@ -67,9 +116,61 @@ private final class RecentSessionSheetContent: CombinedComponent { } case .website: break + case let .connectedBot(connectedBot): + var additionalPeerIds = Set() + additionalPeerIds.formUnion(connectedBot.recipients.additionalPeers) + additionalPeerIds.formUnion(connectedBot.recipients.excludePeers) + + self.botPeerDisposable.set(( + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: connectedBot.id), + EngineDataMap(additionalPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))), + EngineDataMap(additionalPeerIds.map(TelegramEngine.EngineData.Item.Peer.IsContact.init(id:))) + ) + |> deliverOnMainQueue + ).start(next: { [weak self] peer, peers, isContacts in + guard let self else { + return + } + self.botPeer = peer + + var additionalPeers: [BusinessRecipientListScreen.ResolvedPeer] = [] + for peerId in connectedBot.recipients.additionalPeers.sorted() { + guard let maybePeer = peers[peerId], let peer = maybePeer else { + continue + } + additionalPeers.append(BusinessRecipientListScreen.ResolvedPeer( + peer: peer, + isContact: isContacts[peerId] ?? false + )) + } + + var excludePeers: [BusinessRecipientListScreen.ResolvedPeer] = [] + for peerId in connectedBot.recipients.excludePeers.sorted() { + guard let maybePeer = peers[peerId], let peer = maybePeer else { + continue + } + excludePeers.append(BusinessRecipientListScreen.ResolvedPeer( + peer: peer, + isContact: isContacts[peerId] ?? false + )) + } + + self.connectedBotRecipients = ConnectedBotRecipientsState( + categories: connectedBot.recipients.categories, + peers: additionalPeers, + excludePeers: excludePeers, + excludeByDefault: connectedBot.recipients.exclude + ) + self.updated() + })) } } + deinit { + self.botPeerDisposable.dispose() + } + func toggleAllowSecretChats() { guard let controller = self.controller else { return @@ -103,31 +204,96 @@ private final class RecentSessionSheetContent: CombinedComponent { return } self.updated() + controller.terminate() + } + + private func applyConnectedBotRecipientsUpdate() { + guard case let .connectedBot(connectedBot) = self.subject, let connectedBotRecipients = self.connectedBotRecipients else { + return + } - controller.remove({ [weak controller] in - controller?.dismissAnimated() - }) + let recipients = TelegramBusinessRecipients( + categories: connectedBotRecipients.categories, + additionalPeers: Set(connectedBotRecipients.peers.map(\.peer.id)), + excludePeers: Set(connectedBotRecipients.excludePeers.map(\.peer.id)), + exclude: connectedBotRecipients.excludeByDefault + ) + let _ = self.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot( + id: connectedBot.id, + recipients: recipients, + rights: connectedBot.rights, + device: connectedBot.device, + date: connectedBot.date, + location: connectedBot.location + )).startStandalone() + } + + func setConnectedBotAccessMode(excludeByDefault: Bool) { + guard var connectedBotRecipients = self.connectedBotRecipients else { + return + } + guard connectedBotRecipients.excludeByDefault != excludeByDefault else { + return + } + connectedBotRecipients.excludeByDefault = excludeByDefault + connectedBotRecipients.categories = [] //removeAll() + connectedBotRecipients.peers.removeAll() + self.connectedBotRecipients = connectedBotRecipients + self.applyConnectedBotRecipientsUpdate() + self.updated() + } + + func openConnectedBotRecipients(isExclude: Bool) { + guard let controller = self.controller, let connectedBotRecipients = self.connectedBotRecipients else { + return + } + + BusinessRecipientListScreen.openSetupFlow( + context: self.context, + from: controller, + state: BusinessRecipientListScreen.PeerListState( + categories: connectedBotRecipients.categories, + peers: connectedBotRecipients.peers, + excludePeers: connectedBotRecipients.excludePeers, + excludeByDefault: connectedBotRecipients.excludeByDefault + ), + isExclude: isExclude, + update: { [weak self] updatedState in + guard let self else { + return + } + self.connectedBotRecipients = ConnectedBotRecipientsState( + categories: updatedState.categories, + peers: updatedState.peers, + excludePeers: updatedState.excludePeers, + excludeByDefault: updatedState.excludeByDefault + ) + self.applyConnectedBotRecipientsUpdate() + self.updated() + } + ) } } func makeState() -> State { - return State(subject: self.subject) + return State(context: self.context, subject: self.subject) } static var body: Body { - let closeButton = Child(GlassBarButtonComponent.self) let icon = Child(ZStack.self) let avatar = Child(AvatarComponent.self) let title = Child(BalancedTextComponent.self) let description = Child(MultilineTextComponent.self) + let recipientsModeSection = Child(ListSectionComponent.self) + let recipientsSummarySection = Child(ListSectionComponent.self) + let recipientsExcludedSection = Child(ListSectionComponent.self) let clientSection = Child(ListSectionComponent.self) let optionsSection = Child(ListSectionComponent.self) - let button = Child(ButtonComponent.self) return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let component = context.component - let theme = environment.theme + let theme = environment.theme.withModalBlocksBackground() let strings = environment.strings let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let state = context.state @@ -137,29 +303,6 @@ private final class RecentSessionSheetContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let closeButton = closeButton.update( - component: GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "close", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Close", - tintColor: theme.chat.inputPanel.panelControlColor - ) - )), - action: { _ in - component.cancel(true) - } - ), - availableSize: CGSize(width: 44.0, height: 44.0), - transition: .immediate - ) - context.add(closeButton - .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) - ) - var contentHeight: CGFloat = 32.0 switch component.subject { case let .session(session): @@ -223,7 +366,25 @@ private final class RecentSessionSheetContent: CombinedComponent { let avatar = avatar.update( component: AvatarComponent( context: component.context, - theme: environment.theme, + theme: theme, + peer: peer, + clipStyle: .roundedRect + ), + availableSize: CGSize(width: 92.0, height: 92.0), + transition: .immediate + ) + context.add(avatar + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + avatar.size.height / 2.0)) + ) + contentHeight += avatar.size.height + contentHeight += 18.0 + } + case .connectedBot: + if let peer = state.botPeer { + let avatar = avatar.update( + component: AvatarComponent( + context: component.context, + theme: theme, peer: peer, clipStyle: .roundedRect ), @@ -239,37 +400,54 @@ private final class RecentSessionSheetContent: CombinedComponent { } let titleString: String - let subtitleString: String - let subtitleActive: Bool + let subtitleText: MultilineTextComponent.TextContent + let subtitleHighlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? + let subtitleTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? let applicationTitle: String let applicationString: String let ipString: String? + let dateString: String? let locationString: String let buttonString: String? + let clientSectionHeader: String? + let clientSectionFooter: String? + let connectedBot: TelegramAccountConnectedBot? + + let openBotProfile: () -> Void = { + guard let botPeer = state.botPeer, let navigationController = environment.controller()?.navigationController as? NavigationController, let peerInfoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: botPeer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + navigationController.pushViewController(peerInfoController) + } switch component.subject { case let .session(session): titleString = session.deviceModel if session.isCurrent { - subtitleString = strings.Presence_online - subtitleActive = true + subtitleText = .plain(NSAttributedString(string: strings.Presence_online, font: Font.regular(15.0), textColor: theme.actionSheet.controlAccentColor)) } else { let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) - subtitleString = stringForRelativeActivityTimestamp(strings: strings, dateTimeFormat: presentationData.dateTimeFormat, relativeTimestamp: session.activityDate, relativeTo: timestamp) - subtitleActive = false + subtitleText = .plain(NSAttributedString(string: stringForRelativeActivityTimestamp(strings: strings, dateTimeFormat: presentationData.dateTimeFormat, relativeTimestamp: session.activityDate, relativeTo: timestamp), font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor)) } + subtitleHighlightAction = nil + subtitleTapAction = nil var appVersion = session.appVersion appVersion = appVersion.replacingOccurrences(of: "APPSTORE", with: "").replacingOccurrences(of: "BETA", with: "Beta").trimmingTrailingSpaces() applicationTitle = strings.AuthSessions_View_Application applicationString = "\(session.appName) \(appVersion)" ipString = nil + dateString = nil locationString = session.country buttonString = !session.isCurrent ? strings.AuthSessions_View_TerminateSession : nil + clientSectionHeader = nil + clientSectionFooter = strings.AuthSessions_View_LocationInfo + connectedBot = nil case let .website(website, peer): titleString = peer?.compactDisplayTitle ?? "" - subtitleString = website.domain - subtitleActive = false + subtitleText = .plain(NSAttributedString(string: website.domain, font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor)) + subtitleHighlightAction = nil + subtitleTapAction = nil var deviceString = "" if !website.browser.isEmpty { @@ -284,9 +462,68 @@ private final class RecentSessionSheetContent: CombinedComponent { applicationTitle = strings.AuthSessions_View_Browser applicationString = deviceString ipString = website.ip + dateString = nil locationString = website.region buttonString = strings.AuthSessions_View_Logout + clientSectionHeader = nil + clientSectionFooter = strings.AuthSessions_View_LocationInfo + connectedBot = nil + case let .connectedBot(subjectConnectedBot): + let botUsername = state.botPeer?.addressName.flatMap { addressName -> String? in + if addressName.isEmpty { + return nil + } else { + return "@\(addressName)" + } + } ?? "" + titleString = state.botPeer?.compactDisplayTitle ?? "Bot" + + let subtitleString = strings.RecentSession_ConnectedBot_Subtitle + if botUsername.isEmpty { + subtitleText = .plain(NSAttributedString(string: subtitleString, font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor)) + subtitleHighlightAction = nil + subtitleTapAction = nil + } else { + subtitleText = .markdown( + text: "\(subtitleString)\n[\(botUsername)](peer)", + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor), + bold: MarkdownAttributeSet(font: Font.regular(15.0), textColor: theme.actionSheet.secondaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: theme.actionSheet.controlAccentColor), + linkAttribute: { _ in + return (TelegramTextAttributes.URL, "peer") + } + ) + ) + subtitleHighlightAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + } + subtitleTapAction = { attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + openBotProfile() + } + } + } + + applicationTitle = strings.RecentSession_ConnectedBot_Device + applicationString = subjectConnectedBot.device ?? "" + ipString = nil + if let date = subjectConnectedBot.date { + dateString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: date, alwaysShowTime: true, allowYesterday: true).string + } else { + dateString = "" + } + locationString = subjectConnectedBot.location ?? "" + + buttonString = strings.AuthSessions_View_TerminateSession + clientSectionHeader = strings.RecentSession_ConnectedBot_ConnectedFrom + clientSectionFooter = nil + connectedBot = subjectConnectedBot } let titleFont = Font.bold(24.0) @@ -305,13 +542,15 @@ private final class RecentSessionSheetContent: CombinedComponent { contentHeight += title.size.height contentHeight += 2.0 - let textFont = Font.regular(15.0) let description = description.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: subtitleString, font: textFont, textColor: subtitleActive ? theme.actionSheet.controlAccentColor : theme.actionSheet.secondaryTextColor)), + text: subtitleText, horizontalAlignment: .center, maximumNumberOfLines: 3, - lineSpacing: 0.2 + lineSpacing: 0.2, + highlightColor: theme.actionSheet.controlAccentColor.withAlphaComponent(0.1), + highlightAction: subtitleHighlightAction, + tapAction: subtitleTapAction ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate @@ -424,19 +663,65 @@ private final class RecentSessionSheetContent: CombinedComponent { )) ) + if let dateString { + clientSectionItems.append( + AnyComponentWithIdentity(id: "date", component: AnyComponent( + ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.RecentSession_ConnectedBot_Date, + font: Font.regular(17.0), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + accessory: .custom(ListActionItemComponent.CustomAccessory( + component: AnyComponentWithIdentity( + id: "info", + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: dateString, + font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + ), + insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 14.0), + isInteractive: false + )), + action: nil + ) + )) + ) + } + let clientSection = clientSection.update( component: ListSectionComponent( theme: theme, style: .glass, - header: nil, - footer: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.AuthSessions_View_LocationInfo, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), + header: clientSectionHeader.flatMap { header in + AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: header, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )) + }, + footer: clientSectionFooter.flatMap { footer in + AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: footer, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )) + }, items: clientSectionItems ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), @@ -447,6 +732,227 @@ private final class RecentSessionSheetContent: CombinedComponent { ) contentHeight += clientSection.size.height + if let connectedBot { + contentHeight += 32.0 + + let recipientCategories = state.connectedBotRecipients?.categories ?? connectedBot.recipients.categories + let hasAccessToAllChatsByDefault = state.connectedBotRecipients?.excludeByDefault ?? connectedBot.recipients.exclude + let categoriesAndUsersItemCount = botRecipientsCategoryCount(recipientCategories) + (state.connectedBotRecipients?.peers.count ?? connectedBot.recipients.additionalPeers.count) + let excludedSectionValue: String + if categoriesAndUsersItemCount == 0 { + excludedSectionValue = strings.ChatbotSetup_RecipientSummary_ValueEmpty + } else { + excludedSectionValue = strings.ChatbotSetup_RecipientSummary_ValueItems(Int32(categoriesAndUsersItemCount)) + } + + let excludedUsersItemCount = state.connectedBotRecipients?.excludePeers.count ?? connectedBot.recipients.excludePeers.count + let excludedUsersValue: String + if excludedUsersItemCount == 0 { + excludedUsersValue = strings.ChatbotSetup_RecipientSummary_ValueEmpty + } else { + excludedUsersValue = strings.ChatbotSetup_RecipientSummary_ValueItems(Int32(excludedUsersItemCount)) + } + + let recipientsModeSection = recipientsModeSection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.ChatbotSetup_RecipientsSectionHeader, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: [ + AnyComponentWithIdentity(id: "allExcept", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.BusinessMessageSetup_RecipientsOptionAllExcept, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(Image( + image: recentSessionCheckIcon, + tintColor: !hasAccessToAllChatsByDefault ? .clear : theme.list.itemAccentColor, + contentMode: .center + ))), false), + accessory: nil, + action: { [weak state] _ in + if !hasAccessToAllChatsByDefault { + state?.setConnectedBotAccessMode(excludeByDefault: true) + } + } + ))), + AnyComponentWithIdentity(id: "onlySelected", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.BusinessMessageSetup_RecipientsOptionOnly, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent(Image( + image: recentSessionCheckIcon, + tintColor: hasAccessToAllChatsByDefault ? .clear : theme.list.itemAccentColor, + contentMode: .center + ))), false), + accessory: nil, + action: { [weak state] _ in + if hasAccessToAllChatsByDefault { + state?.setConnectedBotAccessMode(excludeByDefault: false) + } + } + ))) + ] + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(recipientsModeSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + recipientsModeSection.size.height / 2.0)) + ) + contentHeight += recipientsModeSection.size.height + contentHeight += 32.0 + + let recipientsSummarySection = recipientsSummarySection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .markdown( + text: hasAccessToAllChatsByDefault ? strings.ChatbotSetup_Recipients_ExcludedSectionFooter : strings.ChatbotSetup_Recipients_IncludedSectionFooter, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.itemAccentColor), + linkAttribute: { _ in + return nil + } + ) + ), + maximumNumberOfLines: 0 + )), + items: [ + AnyComponentWithIdentity(id: "summary", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: hasAccessToAllChatsByDefault ? strings.ChatbotSetup_RecipientSummary_ExcludedChatsItem : strings.ChatbotSetup_RecipientSummary_IncludedChatsItem, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: nil, + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity( + id: "value", + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: excludedSectionValue, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + )), + accessory: .arrow, + action: { [weak state] _ in + state?.openConnectedBotRecipients(isExclude: false) + } + ))) + ] + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(recipientsSummarySection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + recipientsSummarySection.size.height / 2.0)) + ) + contentHeight += recipientsSummarySection.size.height + contentHeight += 32.0 + + if !hasAccessToAllChatsByDefault { + let recipientsExcludedSection = recipientsExcludedSection.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: AnyComponent(MultilineTextComponent( + text: .markdown( + text: strings.ChatbotSetup_Recipients_ExcludedSectionFooter, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: theme.list.itemAccentColor), + linkAttribute: { _ in + return nil + } + ) + ), + maximumNumberOfLines: 0 + )), + items: [ + AnyComponentWithIdentity(id: "excluded", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.ChatbotSetup_RecipientSummary_ExcludedChatsItem, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: nil, + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity( + id: "value", + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: excludedUsersValue, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )) + )), + accessory: .arrow, + action: { [weak state] _ in + state?.openConnectedBotRecipients(isExclude: true) + } + ))) + ] + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), + transition: context.transition + ) + context.add(recipientsExcludedSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + recipientsExcludedSection.size.height / 2.0)) + ) + contentHeight += recipientsExcludedSection.size.height + contentHeight += 32.0 + } + } + if state.allowSecretChats != nil || state.allowIncomingCalls != nil { contentHeight += 38.0 @@ -523,31 +1029,9 @@ private final class RecentSessionSheetContent: CombinedComponent { } contentHeight += 32.0 - if let buttonString { + if buttonString != nil { let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) - let button = button.update( - component: ButtonComponent( - background: ButtonComponent.Background( - style: .glass, - color: theme.list.itemDestructiveColor, - foreground: .white, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) - ), - content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) - ), - action: { [weak state] in - state?.terminate() - } - ), - availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), - transition: .immediate - ) - context.add(button - .position(CGPoint(x: context.availableSize.width / 2.0 , y: contentHeight + button.size.height / 2.0)) - ) - contentHeight += button.size.height + contentHeight += 52.0 contentHeight += buttonInsets.bottom } @@ -578,57 +1062,103 @@ private final class RecentSessionSheetComponent: CombinedComponent { } static var body: Body { - let sheet = Child(SheetComponent.self) + let sheet = Child(ResizableSheetComponent.self) let animateOut = StoredActionSlot(Action.self) return { context in let environment = context.environment[EnvironmentType.self] let controller = environment.controller + let dismiss: (Bool) -> Void = { animated in + if animated { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } else if let controller = controller() { + controller.dismiss(completion: nil) + } + } + + let buttonTitle: String? + switch context.component.subject { + case let .session(session): + buttonTitle = !session.isCurrent ? environment.strings.AuthSessions_View_TerminateSession : nil + case .website: + buttonTitle = environment.strings.AuthSessions_View_Logout + case .connectedBot: + buttonTitle = environment.strings.AuthSessions_View_TerminateSession + } + let sheet = sheet.update( - component: SheetComponent( + component: ResizableSheetComponent( content: AnyComponent(RecentSessionSheetContent( context: context.component.context, subject: context.component.subject, cancel: { animate in - if animate { - animateOut.invoke(Action { _ in - if let controller = controller() { - controller.dismiss(completion: nil) - } - }) - } else if let controller = controller() { - controller.dismiss(animated: false, completion: nil) - } + dismiss(animate) } )), - style: .glass, + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + hasTopEdgeEffect: false, + bottomItem: buttonTitle.flatMap { buttonTitle in + AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: environment.theme.list.itemDestructiveColor, + foreground: .white, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent( + MultilineTextComponent( + text: .plain(NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)) + ) + ) + ), + action: { + (controller() as? RecentSessionScreen)?.terminate() + } + ) + ) + }, backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), - followContentSizeChanges: true, - clipsContent: true, animateOut: animateOut ), environment: { environment - SheetComponentEnvironment( + ResizableSheetComponentEnvironment( + theme: environment.theme, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, + inputHeight: environment.inputHeight, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, isDisplaying: environment.value.isVisible, isCentered: environment.metrics.widthClass == .regular, - hasInputHeight: !environment.inputHeight.isZero, + screenSize: context.availableSize, regularMetricsSize: CGSize(width: 430.0, height: 900.0), dismiss: { animated in - if animated { - animateOut.invoke(Action { _ in - if let controller = controller() { - controller.dismiss(completion: nil) - } - }) - } else { - if let controller = controller() { - controller.dismiss(completion: nil) - } - } + dismiss(animated) } ) }, @@ -649,9 +1179,11 @@ public class RecentSessionScreen: ViewControllerComponentContainer { public enum Subject { case session(RecentAccountSession) case website(WebAuthorization, EnginePeer?) + case connectedBot(TelegramAccountConnectedBot) } private let context: AccountContext + private let subject: Subject fileprivate let updateAcceptSecretChats: (Bool) -> Void fileprivate let updateAcceptIncomingCalls: (Bool) -> Void fileprivate let remove: (@escaping () -> Void) -> Void @@ -664,6 +1196,7 @@ public class RecentSessionScreen: ViewControllerComponentContainer { remove: @escaping (@escaping () -> Void) -> Void ) { self.context = context + self.subject = subject self.updateAcceptSecretChats = updateAcceptSecretChats self.updateAcceptIncomingCalls = updateAcceptIncomingCalls self.remove = remove @@ -692,8 +1225,22 @@ public class RecentSessionScreen: ViewControllerComponentContainer { self.view.disablesInteractiveModalDismiss = true } + fileprivate func terminate() { + switch self.subject { + case .session, .website: + self.remove({ [weak self] in + self?.dismissAnimated() + }) + case .connectedBot: + let _ = (self.context.engine.accountData.setAccountConnectedBot(bot: nil) + |> deliverOnMainQueue).startStandalone(completed: { [weak self] in + self?.dismissAnimated() + }) + } + } + public func dismissAnimated() { - if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { view.dismissAnimated() } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 8c9b007e92..cce4170d0c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -1478,7 +1478,7 @@ public func selectivePrivacySettingsController( } } - updatedPeers[peer.id] = SelectivePrivacyPeer(peer: peer._asPeer(), participantCount: participantCount) + updatedPeers[peer.id] = SelectivePrivacyPeer(peer: peer, participantCount: participantCount) } } return updatedPeers diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 6a758582ad..13a05e43ea 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -475,7 +475,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri } } - updatedPeers.append(SelectivePrivacyPeer(peer: peer._asPeer(), participantCount: participantCount)) + updatedPeers.append(SelectivePrivacyPeer(peer: peer, participantCount: participantCount)) } } return updatedPeers @@ -506,7 +506,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }, openPeer: { peer in - guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } pushControllerImpl?(controller) @@ -608,7 +608,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri var rightNavigationButton: ItemListNavigationButton? if !peers.isEmpty { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { state in var state = state state.editing = false diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift index 97c2ab8bca..3a3f2e8053 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift index 0463ea0ad6..1727e136ba 100644 --- a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift +++ b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift @@ -15,6 +15,7 @@ import PresentationDataUtils import TelegramNotices import NotificationSoundSelectionUI import TelegramStringFormatting +import ContextUI private final class ReactionNotificationSettingsControllerArguments { let context: AccountContext @@ -270,12 +271,26 @@ private struct ReactionNotificationSettingsState: Equatable { } } +private final class ReactionNotificationSettingsContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + public func reactionNotificationSettingsController( context: AccountContext, focusOnItemTag: ReactionNotificationSettingsEntryTag? = nil ) -> ViewController { - var presentControllerImpl: ((ViewController, Any?) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? + var findCategoryReferenceNode: ((ReactionNotificationSettingsEntryTag) -> ItemListSwitchItemNode?)? + var currentReactionSettings = PeerReactionNotificationSettings.default let stateValue = Atomic(value: ReactionNotificationSettingsState()) let statePromise: ValuePromise = ValuePromise(ignoreRepeated: true) @@ -290,49 +305,48 @@ public func reactionNotificationSettingsController( let openCategory: (Bool) -> Void = { isMessages in let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let text: String - if isMessages { - text = presentationData.strings.Notifications_Reactions_SheetTitleMessages - } else { - text = presentationData.strings.Notifications_Reactions_SheetTitleStories + + let currentValue: PeerReactionNotificationSettings.Sources = isMessages ? currentReactionSettings.messages : currentReactionSettings.stories + let options: [(PeerReactionNotificationSettings.Sources, String)] = [ + (.everyone, presentationData.strings.Notifications_Reactions_SheetValueEveryone), + (.contacts, presentationData.strings.Notifications_Reactions_SheetValueContacts) + ] + let items: [ContextMenuItem] = options.map { value, title in + return .action(ContextMenuActionItem(text: title, icon: { theme in + if currentValue == value { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) + let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in + var settings = settings + if isMessages { + settings.reactionSettings.messages = value + } else { + settings.reactionSettings.stories = value + } + return settings + }).start() + })) } - - let actionSheet = ActionSheetController(presentationData: presentationData) - actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: text), - ActionSheetButtonItem(title: presentationData.strings.Notifications_Reactions_SheetValueEveryone, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in - var settings = settings - if isMessages { - settings.reactionSettings.messages = .everyone - } else { - settings.reactionSettings.stories = .everyone - } - return settings - }).start() - }), - ActionSheetButtonItem(title: presentationData.strings.Notifications_Reactions_SheetValueContacts, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - - let _ = updateGlobalNotificationSettingsInteractively(postbox: context.account.postbox, { settings in - var settings = settings - if isMessages { - settings.reactionSettings.messages = .contacts - } else { - settings.reactionSettings.stories = .contacts - } - return settings - }).start() - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - presentControllerImpl?(actionSheet, nil) + + let sourceTag: ReactionNotificationSettingsEntryTag = isMessages ? .messages : .stories + guard let sourceNode = findCategoryReferenceNode?(sourceTag) else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(ReactionNotificationSettingsContextReferenceContentSource(sourceView: sourceNode.contextSourceView)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) } let arguments = ReactionNotificationSettingsControllerArguments( @@ -384,7 +398,7 @@ public func reactionNotificationSettingsController( } ) - let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let preferences = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, @@ -394,11 +408,12 @@ public func reactionNotificationSettingsController( ) |> map { presentationData, notificationSoundList, preferencesView, state -> (ItemListControllerState, (ItemListNodeState, Any)) in let viewSettings: GlobalNotificationSettingsSet - if let settings = preferencesView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = preferencesView?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings } + currentReactionSettings = viewSettings.reactionSettings let entries = reactionNotificationSettingsEntries( globalSettings: viewSettings, @@ -422,12 +437,15 @@ public func reactionNotificationSettingsController( } let controller = ItemListController(context: context, state: signal) - presentControllerImpl = { [weak controller] c, a in - controller?.present(c, in: .window(.root), with: a) + presentInGlobalOverlayImpl = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) } pushControllerImpl = { [weak controller] c in (controller?.navigationController as? NavigationController)?.pushViewController(c) } + findCategoryReferenceNode = { [weak controller] tag in + return controller?.itemNode(forTag: tag) as? ItemListSwitchItemNode + } if let focusOnItemTag { var didFocusOnItem = false diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 8c3e310f23..8d1bd13a19 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift index 65d87903ed..63a4d5a893 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import SwiftSignalKit import TelegramCore @@ -232,7 +231,7 @@ class SettingsSearchRecentItemNode: ItemListRevealOptionsItemNode { var revealOptions: [ItemListRevealOption] = [] if item.isFaq { } else { - revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)) + revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.theme.list.itemSecondaryTextColor)) } strongSelf.setRevealOptions((left: [], right: revealOptions)) } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentQueries.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentQueries.swift index d150d523ec..b8082a755c 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchRecentQueries.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchRecentQueries.swift @@ -1,12 +1,11 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences private struct SettingsSearchRecentQueryItemId { - public let rawValue: MemoryBuffer + public let rawValue: EngineMemoryBuffer var value: Int64 { return self.rawValue.makeData().withUnsafeBytes { buffer -> Int64 in @@ -17,13 +16,13 @@ private struct SettingsSearchRecentQueryItemId { } } - init(_ rawValue: MemoryBuffer) { + init(_ rawValue: EngineMemoryBuffer) { self.rawValue = rawValue } init(_ value: Int64) { var value = value - self.rawValue = MemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) + self.rawValue = EngineMemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) } } @@ -42,7 +41,7 @@ func addRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) { guard let id = item.base as? String, let data = id.data(using: .ascii) else { return } - let itemId = MemoryBuffer(data: data) + let itemId = EngineMemoryBuffer(data: data) let _ = engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId, item: RecentSettingsSearchQueryItem(), removeTailIfCountExceeds: 100).start() } @@ -50,7 +49,7 @@ func removeRecentSettingsSearchItem(engine: TelegramEngine, item: AnyHashable) { guard let id = item.base as? String, let data = id.data(using: .ascii) else { return } - let itemId = MemoryBuffer(data: data) + let itemId = EngineMemoryBuffer(data: data) let _ = engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.settingsSearchRecentItems, id: itemId).start() } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 92cf70cf2f..ef18cdaa6e 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import TelegramCallsUI @@ -968,7 +967,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc present: { context, _, present in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer?._asPeer() else { + guard let peer = peer else { return } let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false) @@ -977,7 +976,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc } ) ) - + //TODO:fix items.append( SettingsSearchableItem( @@ -986,7 +985,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc present: { context, _, present in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer?._asPeer() else { + guard let peer = peer else { return } let controller = context.sharedContext.makeChatQrCodeScreen(context: context, peer: peer, threadId: nil, temporary: false) @@ -1017,7 +1016,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc present: { context, _, present in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer?._asPeer() else { + guard let peer = peer else { return } let controller = context.sharedContext.makePeerInfoController( @@ -1034,7 +1033,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc } ) ) - + items.append( SettingsSearchableItem( id: "my-profile/edit", @@ -1043,7 +1042,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc present: { context, _, present in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer?._asPeer() else { + guard let peer = peer else { return } let controller = context.sharedContext.makePeerInfoController( @@ -1056,7 +1055,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc requestsContext: nil ) present(.push, controller) - + Queue.mainQueue().justDispatch { if let controller = controller as? PeerInfoScreen { controller.activateEdit() @@ -1077,7 +1076,7 @@ private func myProfileSearchableItems(context: AccountContext) -> [SettingsSearc present: { context, _, present in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer?._asPeer() else { + guard let peer = peer else { return } let controller = context.sharedContext.makePeerInfoController( @@ -1542,16 +1541,16 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob let defaultStorySettings = PeerStoryNotificationSettings.default let exceptions = { () -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in - var users:[PeerId : NotificationExceptionWrapper] = [:] - var groups: [PeerId : NotificationExceptionWrapper] = [:] - var channels: [PeerId : NotificationExceptionWrapper] = [:] - var stories: [PeerId : NotificationExceptionWrapper] = [:] + var users:[EnginePeer.Id: NotificationExceptionWrapper] = [:] + var groups: [EnginePeer.Id: NotificationExceptionWrapper] = [:] + var channels: [EnginePeer.Id: NotificationExceptionWrapper] = [:] + var stories: [EnginePeer.Id: NotificationExceptionWrapper] = [:] if let list = exceptionsList { for (key, value) in list.settings { if let peer = list.peers[key], !peer.debugDisplayTitle.isEmpty, peer.id != context.account.peerId { if value.storySettings != defaultStorySettings { - stories[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + stories[key] = NotificationExceptionWrapper(settings: value, peer: peer) } switch value.muteState { @@ -1562,24 +1561,24 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + users[key] = NotificationExceptionWrapper(settings: value, peer: peer) default: - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = peer, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) } } } default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + users[key] = NotificationExceptionWrapper(settings: value, peer: peer) default: - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = peer, case .broadcast = peer.info { channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) + groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) } } } @@ -2073,11 +2072,11 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac } let callsSignal: Signal<(VoiceCallSettings, VoipConfiguration)?, NoError> if case .voiceCalls = kind { - callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration])) + callsSignal = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.voiceCallSettings]), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration))) |> take(1) |> map { sharedData, view -> (VoiceCallSettings, VoipConfiguration)? in let voiceCallSettings: VoiceCallSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.voiceCallSettings]?.get(VoiceCallSettings.self) ?? .defaultSettings - let voipConfiguration = view.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + let voipConfiguration = view?.get(VoipConfiguration.self) ?? .defaultValue return (voiceCallSettings, voipConfiguration) } } else { @@ -3134,6 +3133,18 @@ private func privacySearchableItems(context: AccountContext, privacySettings: Ac } ) ) + items.append( + SettingsSearchableItem( + id: "privacy/open-links", + title: strings.ChatSettings_OpenLinksIn, + alternate: synonyms(strings.SettingsSearch_Synonyms_ChatSettings_OpenLinksIn), + icon: icon, + breadcrumbs: [strings.Settings_PrivacySettings], + present: { context, _, present in + present(.push, webBrowserSettingsController(context: context)) + } + ) + ) items.append( SettingsSearchableItem( id: "privacy/data-settings/delete-synced", @@ -3671,16 +3682,6 @@ private func dataSearchableItems(context: AccountContext) -> [SettingsSearchable presentDataSettings(context, present, .sensitiveContent) } ), - SettingsSearchableItem( - id: "data/open-links", - title: strings.ChatSettings_OpenLinksIn, - alternate: synonyms(strings.SettingsSearch_Synonyms_ChatSettings_OpenLinksIn), - icon: icon, - breadcrumbs: [strings.Settings_ChatSettings], - present: { context, _, present in - present(.push, webBrowserSettingsController(context: context)) - } - ), SettingsSearchableItem( id: "data/share-sheet", title: strings.ChatSettings_IntentsSettings, @@ -4317,11 +4318,11 @@ func settingsSearchableItems( return accountsAndPeers.1.count + 1 < maximumNumberOfAccounts } - let notificationSettings = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications]) + let notificationSettings = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.globalNotifications)) |> take(1) |> map { view -> GlobalNotificationSettingsSet in let viewSettings: GlobalNotificationSettingsSet - if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + if let settings = view?.get(GlobalNotificationSettings.self) { viewSettings = settings.effective } else { viewSettings = GlobalNotificationSettingsSet.defaultSettings @@ -4515,20 +4516,20 @@ func settingsSearchableItems( } } -private func stringTokens(_ string: String) -> [ValueBoxKey] { +private func stringTokens(_ string: String) -> [EngineDataBuffer] { let nsString = string.folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString let flag = UInt(kCFStringTokenizerUnitWord) let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent()) var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) - var tokens: [ValueBoxKey] = [] + var tokens: [EngineDataBuffer] = [] - var addedTokens = Set() + var addedTokens = Set() while tokenType != [] { let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer) if currentTokenRange.location >= 0 && currentTokenRange.length != 0 { - let token = ValueBoxKey(length: currentTokenRange.length * 2) + let token = EngineDataBuffer(length: currentTokenRange.length * 2) nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length)) if !addedTokens.contains(token) { tokens.append(token) @@ -4541,7 +4542,7 @@ private func stringTokens(_ string: String) -> [ValueBoxKey] { return tokens } -private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool { +private func matchStringTokens(_ tokens: [EngineDataBuffer], with other: [EngineDataBuffer]) -> Bool { if other.isEmpty { return false } else if other.count == 1 { diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 2acd061193..1b27fbd354 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import PasswordSetupUI diff --git a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift index db9186d25d..d0d017c115 100644 --- a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,12 +23,12 @@ private final class ArchivedStickerPacksControllerArguments { let context: AccountContext let openStickerPack: (StickerPackCollectionInfo) -> Void - let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void + let setPackIdWithRevealedOptions: (EngineItemCollectionId?, EngineItemCollectionId?) -> Void let addPack: (StickerPackCollectionInfo) -> Void let removePack: (StickerPackCollectionInfo) -> Void - let togglePackSelected: (ItemCollectionId) -> Void - - init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void, togglePackSelected: @escaping (ItemCollectionId) -> Void) { + let togglePackSelected: (EngineItemCollectionId) -> Void + + init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (EngineItemCollectionId?, EngineItemCollectionId?) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void, removePack: @escaping (StickerPackCollectionInfo) -> Void, togglePackSelected: @escaping (EngineItemCollectionId) -> Void) { self.context = context self.openStickerPack = openStickerPack self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions @@ -45,7 +44,7 @@ private enum ArchivedStickerPacksSection: Int32 { private enum ArchivedStickerPacksEntryId: Hashable { case index(Int32) - case pack(ItemCollectionId) + case pack(EngineItemCollectionId) } private enum ArchivedStickerPacksEntry: ItemListNodeEntry { @@ -157,18 +156,18 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry { private struct ArchivedStickerPacksControllerState: Equatable { let editing: Bool - let selectedPackIds: Set? - let packIdWithRevealedOptions: ItemCollectionId? - let removingPackIds: Set - + let selectedPackIds: Set? + let packIdWithRevealedOptions: EngineItemCollectionId? + let removingPackIds: Set + init() { self.editing = false self.selectedPackIds = nil self.packIdWithRevealedOptions = nil self.removingPackIds = Set() } - - init(editing: Bool, selectedPackIds: Set?, packIdWithRevealedOptions: ItemCollectionId?, removingPackIds: Set) { + + init(editing: Bool, selectedPackIds: Set?, packIdWithRevealedOptions: EngineItemCollectionId?, removingPackIds: Set) { self.editing = editing self.selectedPackIds = selectedPackIds self.packIdWithRevealedOptions = packIdWithRevealedOptions @@ -196,22 +195,22 @@ private struct ArchivedStickerPacksControllerState: Equatable { return ArchivedStickerPacksControllerState(editing: editing, selectedPackIds: self.selectedPackIds, packIdWithRevealedOptions: self.packIdWithRevealedOptions, removingPackIds: self.removingPackIds) } - func withUpdatedSelectedPackIds(_ selectedPackIds: Set?) -> ArchivedStickerPacksControllerState { + func withUpdatedSelectedPackIds(_ selectedPackIds: Set?) -> ArchivedStickerPacksControllerState { return ArchivedStickerPacksControllerState(editing: self.editing, selectedPackIds: selectedPackIds, packIdWithRevealedOptions: self.packIdWithRevealedOptions, removingPackIds: self.removingPackIds) } - - func withUpdatedPackIdWithRevealedOptions(_ packIdWithRevealedOptions: ItemCollectionId?) -> ArchivedStickerPacksControllerState { + + func withUpdatedPackIdWithRevealedOptions(_ packIdWithRevealedOptions: EngineItemCollectionId?) -> ArchivedStickerPacksControllerState { return ArchivedStickerPacksControllerState(editing: self.editing, selectedPackIds: self.selectedPackIds, packIdWithRevealedOptions: packIdWithRevealedOptions, removingPackIds: self.removingPackIds) } - - func withUpdatedRemovingPackIds(_ removingPackIds: Set) -> ArchivedStickerPacksControllerState { + + func withUpdatedRemovingPackIds(_ removingPackIds: Set) -> ArchivedStickerPacksControllerState { return ArchivedStickerPacksControllerState(editing: self.editing, selectedPackIds: self.selectedPackIds, packIdWithRevealedOptions: self.packIdWithRevealedOptions, removingPackIds: removingPackIds) } } -private func archivedStickerPacksControllerEntries(context: AccountContext, mode: ArchivedStickerPacksControllerMode, presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedView: CombinedView, stickerSettings: StickerSettings) -> [ArchivedStickerPacksEntry] { +private func archivedStickerPacksControllerEntries(context: AccountContext, mode: ArchivedStickerPacksControllerMode, presentationData: PresentationData, state: ArchivedStickerPacksControllerState, packs: [ArchivedStickerPackItem]?, installedIds: [EngineItemCollectionId], stickerSettings: StickerSettings) -> [ArchivedStickerPacksEntry] { var entries: [ArchivedStickerPacksEntry] = [] - + if let packs = packs { let info: String switch mode { @@ -221,11 +220,8 @@ private func archivedStickerPacksControllerEntries(context: AccountContext, mode info = presentationData.strings.StickerPacksSettings_ArchivedPacks_Info } entries.append(.info(presentationData.theme, info + "\n\n")) - - var installedIds = Set() - if let view = installedView.views[.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionIdsView, let ids = view.idsByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - installedIds = ids - } + + let installedIds = Set(installedIds) var index: Int32 = 0 for item in packs { @@ -257,7 +253,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv if forceEdit { updateState { - $0.withUpdatedEditing(true) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } } @@ -269,7 +265,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv let resolveDisposable = MetaDisposable() actionsDisposable.add(resolveDisposable) - let removePackDisposables = DisposableDict() + let removePackDisposables = DisposableDict() actionsDisposable.add(removePackDisposables) let namespace: ArchivedStickerPacksNamespace @@ -288,8 +284,8 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv updatedPacks(packs) })) - let installedStickerPacks = Promise() - installedStickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + let installedStickerPacks = Promise<[EngineItemCollectionId]>() + installedStickerPacks.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackIds(namespace: Namespaces.ItemCollection.CloudStickerPacks))) var presentationData = context.sharedContext.currentPresentationData.with { $0 } if let forceTheme { @@ -428,7 +424,8 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv } }, togglePackSelected: { packId in updateState { state in - if var selectedPackIds = state.selectedPackIds { + if state.editing { + var selectedPackIds = state.selectedPackIds ?? Set() if selectedPackIds.contains(packId) { selectedPackIds.remove(packId) } else { @@ -445,7 +442,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, installedStickerPacks.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue) |> deliverOnMainQueue - |> map { presentationData, state, packs, installedView, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, packs, installedIds, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData if let forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) @@ -460,7 +457,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv var toolbarItem: ItemListToolbarItem? if let packs = packs, packs.count != 0 { if state.editing { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { $0.withUpdatedEditing(false) } @@ -537,7 +534,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv $0.withUpdatedEditing(false).withUpdatedSelectedPackIds(nil) } - var packIds: [ItemCollectionId] = [] + var packIds: [EngineItemCollectionId] = [] for entry in packs { if let selectedPackIds = state.selectedPackIds, selectedPackIds.contains(entry.info.id) { packIds.append(entry.info.id) @@ -554,7 +551,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv presentControllerImpl?(actionSheet, nil) }), .init(title: presentationData.strings.StickerPacks_ActionShare, isEnabled: selectedCount > 0, action: { updateState { - $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(nil) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } var packNames: [String] = [] @@ -594,7 +591,7 @@ public func archivedStickerPacksController(context: AccountContext, mode: Archiv let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archivedStickerPacksControllerEntries(context: context, mode: mode, presentationData: presentationData, state: state, packs: packs, installedView: installedView, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, toolbarItem: toolbarItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10)) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: archivedStickerPacksControllerEntries(context: context, mode: mode, presentationData: presentationData, state: state, packs: packs, installedIds: installedIds, stickerSettings: stickerSettings), style: .blocks, emptyStateItem: emptyStateItem, toolbarItem: toolbarItem, animateChanges: previous != nil && packs != nil && (previous! != 0 && previous! >= packs!.count - 10)) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/submodules/SettingsUI/Sources/Stickers/FeaturedStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/FeaturedStickerPacksController.swift index e0baafe727..a190949068 100644 --- a/submodules/SettingsUI/Sources/Stickers/FeaturedStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/FeaturedStickerPacksController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -30,7 +29,7 @@ private enum FeaturedStickerPacksSection: Int32 { } private enum FeaturedStickerPacksEntryId: Hashable { - case pack(ItemCollectionId) + case pack(EngineItemCollectionId) } private enum FeaturedStickerPacksEntry: ItemListNodeEntry { @@ -123,37 +122,35 @@ private struct FeaturedStickerPacksControllerState: Equatable { } } -private func featuredStickerPacksControllerEntries(context: AccountContext, presentationData: PresentationData, state: FeaturedStickerPacksControllerState, view: CombinedView, featured: [FeaturedStickerPackItem], unreadPacks: [ItemCollectionId: Bool], stickerSettings: StickerSettings) -> [FeaturedStickerPacksEntry] { +private func featuredStickerPacksControllerEntries(context: AccountContext, presentationData: PresentationData, state: FeaturedStickerPacksControllerState, view: [EngineRawItemCollectionInfoEntry], featured: [FeaturedStickerPackItem], unreadPacks: [EngineItemCollectionId: Bool], stickerSettings: StickerSettings) -> [FeaturedStickerPacksEntry] { var entries: [FeaturedStickerPacksEntry] = [] - - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView, !featured.isEmpty { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - var installedPacks = Set() - for entry in packsEntries { - installedPacks.insert(entry.id) + + if !featured.isEmpty { + var installedPacks = Set() + for entry in view { + installedPacks.insert(entry.id) + } + var index: Int32 = 0 + for item in featured { + var unread = false + if let value = unreadPacks[item.info.id] { + unread = value } - var index: Int32 = 0 - for item in featured { - var unread = false - if let value = unreadPacks[item.info.id] { - unread = value - } - - let countTitle: String - if item.info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - countTitle = presentationData.strings.StickerPack_EmojiCount(item.info.count) - } else if item.info.id.namespace == Namespaces.ItemCollection.CloudMaskPacks { - countTitle = presentationData.strings.StickerPack_MaskCount(item.info.count) - } else { - countTitle = presentationData.strings.StickerPack_StickerCount(item.info.count) - } - - entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, unread, item.topItems.first, countTitle, context.sharedContext.energyUsageSettings.loopStickers, installedPacks.contains(item.info.id))) - index += 1 + + let countTitle: String + if item.info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + countTitle = presentationData.strings.StickerPack_EmojiCount(item.info.count) + } else if item.info.id.namespace == Namespaces.ItemCollection.CloudMaskPacks { + countTitle = presentationData.strings.StickerPack_MaskCount(item.info.count) + } else { + countTitle = presentationData.strings.StickerPack_StickerCount(item.info.count) } + + entries.append(.pack(index, presentationData.theme, presentationData.strings, item.info, unread, item.topItems.first, countTitle, context.sharedContext.energyUsageSettings.loopStickers, installedPacks.contains(item.info.id))) + index += 1 } } - + return entries } @@ -172,7 +169,7 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr presentStickerPackController?(info) }, addPack: { info in let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: info.id.id, accessHash: info.accessHash), forceActualized: false) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in switch result { case let .result(info, items, installed): if installed { @@ -186,16 +183,25 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr break } return .complete() - } |> deliverOnMainQueue).start() + } |> deliverOnMainQueue).startStandalone() }) - let stickerPacks = Promise() - stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) + actionsDisposable.add((context.account.stateManager.installedStickerPacksArchivedEvents + |> deliverOnMainQueue).startStandalone(next: { count in + if count == 0 { + return + } + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + presentControllerImpl?(textAlertController(context: context, updatedPresentationData: nil, title: nil, text: presentationData.strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + })) + + let stickerPacks = Promise<[EngineRawItemCollectionInfoEntry]>() + stickerPacks.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks))) let featured = Promise<[FeaturedStickerPackItem]>() featured.set(context.account.viewTracker.featuredStickerPacks()) - var initialUnreadPacks: [ItemCollectionId: Bool] = [:] + var initialUnreadPacks: [EngineItemCollectionId: Bool] = [:] let signal = combineLatest(context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue) |> deliverOnMainQueue @@ -223,10 +229,10 @@ public func featuredStickerPacksController(context: AccountContext) -> ViewContr let controller = ItemListController(context: context, state: signal) - var alreadyReadIds = Set() + var alreadyReadIds = Set() controller.visibleEntriesUpdated = { entries in - var unreadIds: [ItemCollectionId] = [] + var unreadIds: [EngineItemCollectionId] = [] for entry in entries { if let entry = entry as? FeaturedStickerPacksEntry { switch entry { diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index b017259335..b30559b2a7 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -14,6 +13,7 @@ import StickerPackPreviewUI import ItemListStickerPackItem import ItemListPeerActionItem import UndoUI +import ContextUI import WebPBinding import ReactionImageComponent @@ -24,7 +24,7 @@ private final class InstalledStickerPacksControllerArguments { let context: AccountContext let openStickerPack: (StickerPackCollectionInfo) -> Void - let setPackIdWithRevealedOptions: (ItemCollectionId?, ItemCollectionId?) -> Void + let setPackIdWithRevealedOptions: (EngineItemCollectionId?, EngineItemCollectionId?) -> Void let removePack: (ArchivedStickerPackItem) -> Void let openStickersBot: () -> Void let openMasks: () -> Void @@ -34,12 +34,12 @@ private final class InstalledStickerPacksControllerArguments { let openArchived: ([ArchivedStickerPackItem]?) -> Void let openSuggestOptions: () -> Void let toggleSuggestAnimatedEmoji: (Bool) -> Void - let togglePackSelected: (ItemCollectionId) -> Void + let togglePackSelected: (EngineItemCollectionId) -> Void let toggleLargeEmoji: (Bool) -> Void let toggleDynamicPackOrder: (Bool) -> Void let addPack: (StickerPackCollectionInfo) -> Void - init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openEmoji: @escaping () -> Void, openQuickReaction: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void, toggleSuggestAnimatedEmoji: @escaping (Bool) -> Void, togglePackSelected: @escaping (ItemCollectionId) -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, toggleDynamicPackOrder: @escaping (Bool) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) { + init(context: AccountContext, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (EngineItemCollectionId?, EngineItemCollectionId?) -> Void, removePack: @escaping (ArchivedStickerPackItem) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openEmoji: @escaping () -> Void, openQuickReaction: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void, toggleSuggestAnimatedEmoji: @escaping (Bool) -> Void, togglePackSelected: @escaping (EngineItemCollectionId) -> Void, toggleLargeEmoji: @escaping (Bool) -> Void, toggleDynamicPackOrder: @escaping (Bool) -> Void, addPack: @escaping (StickerPackCollectionInfo) -> Void) { self.context = context self.openStickerPack = openStickerPack self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions @@ -84,7 +84,7 @@ public enum InstalledStickerPacksEntryTag: ItemListItemTag { private enum InstalledStickerPacksEntryId: Hashable { case index(Int32) - case pack(ItemCollectionId) + case pack(EngineItemCollectionId) } private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { @@ -457,18 +457,18 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { private struct InstalledStickerPacksControllerState: Equatable { let editing: Bool - let selectedPackIds: Set? - let packIdWithRevealedOptions: ItemCollectionId? + let selectedPackIds: Set? + let packIdWithRevealedOptions: EngineItemCollectionId? let trendingPacksExpanded: Bool - + init() { self.editing = false self.selectedPackIds = nil self.packIdWithRevealedOptions = nil self.trendingPacksExpanded = false } - - init(editing: Bool, selectedPackIds: Set?, packIdWithRevealedOptions: ItemCollectionId?, trendingPacksExpanded: Bool) { + + init(editing: Bool, selectedPackIds: Set?, packIdWithRevealedOptions: EngineItemCollectionId?, trendingPacksExpanded: Bool) { self.editing = editing self.selectedPackIds = selectedPackIds self.packIdWithRevealedOptions = packIdWithRevealedOptions @@ -495,11 +495,11 @@ private struct InstalledStickerPacksControllerState: Equatable { return InstalledStickerPacksControllerState(editing: editing, selectedPackIds: self.selectedPackIds, packIdWithRevealedOptions: self.packIdWithRevealedOptions, trendingPacksExpanded: self.trendingPacksExpanded) } - func withUpdatedSelectedPackIds(_ selectedPackIds: Set?) -> InstalledStickerPacksControllerState { + func withUpdatedSelectedPackIds(_ selectedPackIds: Set?) -> InstalledStickerPacksControllerState { return InstalledStickerPacksControllerState(editing: editing, selectedPackIds: selectedPackIds, packIdWithRevealedOptions: self.packIdWithRevealedOptions, trendingPacksExpanded: self.trendingPacksExpanded) } - - func withUpdatedPackIdWithRevealedOptions(_ packIdWithRevealedOptions: ItemCollectionId?) -> InstalledStickerPacksControllerState { + + func withUpdatedPackIdWithRevealedOptions(_ packIdWithRevealedOptions: EngineItemCollectionId?) -> InstalledStickerPacksControllerState { return InstalledStickerPacksControllerState(editing: self.editing, selectedPackIds: self.selectedPackIds, packIdWithRevealedOptions: packIdWithRevealedOptions, trendingPacksExpanded: self.trendingPacksExpanded) } @@ -508,7 +508,19 @@ private struct InstalledStickerPacksControllerState: Equatable { } } -private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> ItemCollectionId.Namespace { +private final class InstalledStickerPacksSuggestOptionsContextReferenceContentSource: ContextReferenceContentSource { + private let sourceView: UIView + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0)) + } +} + +private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> EngineItemCollectionId.Namespace { switch mode { case .general, .modal: return Namespaces.ItemCollection.CloudStickerPacks @@ -521,18 +533,16 @@ private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> It private let maxTrendingPacksDisplayedLimit: Int32 = 3 -private func installedStickerPacksControllerEntries(context: AccountContext, presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, temporaryPackOrder: [ItemCollectionId]?, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?, stickerSettings: StickerSettings, quickReaction: MessageReaction.Reaction?, availableReactions: AvailableReactions?, emojiCount: Int32) -> [InstalledStickerPacksEntry] { +private func installedStickerPacksControllerEntries(context: AccountContext, presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: [EngineRawItemCollectionInfoEntry], temporaryPackOrder: [EngineItemCollectionId]?, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?, stickerSettings: StickerSettings, quickReaction: MessageReaction.Reaction?, availableReactions: AvailableReactions?, emojiCount: Int32) -> [InstalledStickerPacksEntry] { var entries: [InstalledStickerPacksEntry] = [] - - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[namespaceForMode(mode)] { - var sortedPacks: [ItemCollectionInfoEntry] = [] - for entry in packsEntries { - if let _ = entry.info as? StickerPackCollectionInfo { - installedPacks.insert(entry.id) - sortedPacks.append(entry) - } + + var installedPacks = Set() + do { + var sortedPacks: [EngineRawItemCollectionInfoEntry] = [] + for entry in view { + if let _ = entry.info as? StickerPackCollectionInfo { + installedPacks.insert(entry.id) + sortedPacks.append(entry) } } } @@ -580,45 +590,43 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre entries.append(.suggestAnimatedEmojiInfo(presentationData.theme, presentationData.strings.StickerPacksSettings_SuggestAnimatedEmojiInfo)) } - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[namespaceForMode(mode)] { - var sortedPacks: [ItemCollectionInfoEntry] = [] - for entry in packsEntries { - if let _ = entry.info as? StickerPackCollectionInfo { - sortedPacks.append(entry) + do { + var sortedPacks: [EngineRawItemCollectionInfoEntry] = [] + for entry in view { + if let _ = entry.info as? StickerPackCollectionInfo { + sortedPacks.append(entry) + } + } + if let temporaryPackOrder = temporaryPackOrder { + var packDict: [EngineItemCollectionId: Int] = [:] + for i in 0 ..< sortedPacks.count { + packDict[sortedPacks[i].id] = i + } + var tempSortedPacks: [EngineRawItemCollectionInfoEntry] = [] + var processedPacks = Set() + for id in temporaryPackOrder { + if let index = packDict[id] { + tempSortedPacks.append(sortedPacks[index]) + processedPacks.insert(id) } } - if let temporaryPackOrder = temporaryPackOrder { - var packDict: [ItemCollectionId: Int] = [:] - for i in 0 ..< sortedPacks.count { - packDict[sortedPacks[i].id] = i - } - var tempSortedPacks: [ItemCollectionInfoEntry] = [] - var processedPacks = Set() - for id in temporaryPackOrder { - if let index = packDict[id] { - tempSortedPacks.append(sortedPacks[index]) - processedPacks.insert(id) - } - } - let restPacks = sortedPacks.filter { !processedPacks.contains($0.id) } - sortedPacks = restPacks + tempSortedPacks - } - var index: Int32 = 0 - for entry in sortedPacks { - if let info = entry.info as? StickerPackCollectionInfo { - let countTitle: String - if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - countTitle = presentationData.strings.StickerPack_EmojiCount(info.count == 0 ? entry.count : info.count) - } else if info.id.namespace == Namespaces.ItemCollection.CloudMaskPacks { - countTitle = presentationData.strings.StickerPack_MaskCount(info.count == 0 ? entry.count : info.count) - } else { - countTitle = presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count) - } - - entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, countTitle, context.sharedContext.energyUsageSettings.loopStickers, true, ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == entry.id, reorderable: true, selectable: true), state.selectedPackIds?.contains(info.id))) - index += 1 + let restPacks = sortedPacks.filter { !processedPacks.contains($0.id) } + sortedPacks = restPacks + tempSortedPacks + } + var index: Int32 = 0 + for entry in sortedPacks { + if let info = entry.info as? StickerPackCollectionInfo { + let countTitle: String + if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { + countTitle = presentationData.strings.StickerPack_EmojiCount(info.count == 0 ? entry.count : info.count) + } else if info.id.namespace == Namespaces.ItemCollection.CloudMaskPacks { + countTitle = presentationData.strings.StickerPack_MaskCount(info.count == 0 ? entry.count : info.count) + } else { + countTitle = presentationData.strings.StickerPack_StickerCount(info.count == 0 ? entry.count : info.count) } + + entries.append(.pack(index, presentationData.theme, presentationData.strings, info, entry.firstItem as? StickerPackItem, countTitle, context.sharedContext.energyUsageSettings.loopStickers, true, ItemListStickerPackItemEditing(editable: true, editing: state.editing, revealed: state.packIdWithRevealedOptions == entry.id, reorderable: true, selectable: true), state.selectedPackIds?.contains(info.id))) + index += 1 } } } @@ -657,7 +665,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if focusOnItemTag == InstalledStickerPacksEntryTag.edit { updateState { - $0.withUpdatedEditing(true) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } } @@ -667,9 +675,12 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta } var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)? + var presentInGlobalOverlayImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? - var navigateToChatControllerImpl: ((PeerId) -> Void)? + var navigateToChatControllerImpl: ((EnginePeer.Id) -> Void)? var dismissImpl: (() -> Void)? + var findSuggestOptionsReferenceNode: (() -> ItemListDisclosureItemNode?)? + var currentEmojiStickerSuggestionMode = StickerSettings.defaultSettings.emojiStickerSuggestionMode let actionsDisposable = DisposableSet() @@ -806,37 +817,49 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta updatedPacks(packs) })) }, openSuggestOptions: { - let controller = ActionSheetController(presentationData: presentationData) - let dismissAction: () -> Void = { [weak controller] in - controller?.dismissAnimated() - } let options: [(EmojiStickerSuggestionMode, String)] = [ (.all, presentationData.strings.Stickers_SuggestAll), (.installed, presentationData.strings.Stickers_SuggestAdded), (.none, presentationData.strings.Stickers_SuggestNone) ] - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: presentationData.strings.Stickers_SuggestStickers)) + var items: [ContextMenuItem] = [] for (option, title) in options { - items.append(ActionSheetButtonItem(title: title, color: .accent, action: { - dismissAction() + items.append(.action(ContextMenuActionItem(text: title, icon: { theme in + if currentEmojiStickerSuggestionMode == option { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) + } else { + return UIImage() + } + }, action: { _, f in + f(.default) let _ = updateStickerSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in return current.withUpdatedEmojiStickerSuggestionMode(option) }).start() - })) + }))) } - controller.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + + guard let sourceNode = findSuggestOptionsReferenceNode?() else { + return + } + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(InstalledStickerPacksSuggestOptionsContextReferenceContentSource(sourceView: sourceNode.labelNode.view)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + sourceNode.updateHasContextMenu(hasContextMenu: true) + contextController.dismissed = { [weak sourceNode] in + sourceNode?.updateHasContextMenu(hasContextMenu: false) + } + presentInGlobalOverlayImpl?(contextController) }, toggleSuggestAnimatedEmoji: { value in let _ = updateStickerSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in return current.withUpdatedSuggestAnimatedEmoji(value) }).start() }, togglePackSelected: { packId in updateState { state in - if var selectedPackIds = state.selectedPackIds { + if state.editing { + var selectedPackIds = state.selectedPackIds ?? Set() if selectedPackIds.contains(packId) { selectedPackIds.remove(packId) } else { @@ -863,7 +886,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -873,9 +896,9 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta return .complete() } |> deliverOnMainQueue).start() }) - let stickerPacks = Promise() - stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])])) - let temporaryPackOrder = Promise<[ItemCollectionId]?>(nil) + let stickerPacks = Promise<[EngineRawItemCollectionInfoEntry]>() + stickerPacks.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: namespaceForMode(mode)))) + let temporaryPackOrder = Promise<[EngineItemCollectionId]?>(nil) let featured = Promise<[FeaturedStickerPackItem]>() let quickReaction: Signal @@ -887,11 +910,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta archivedPromise.set(.single(archivedPacks) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init))) quickReaction = combineLatest( context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), - context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) ) |> map { peer, preferencesView -> MessageReaction.Reaction? in let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default @@ -903,14 +926,11 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta return reactionSettings.effectiveQuickReaction(hasPremium: hasPremium) } |> distinctUntilChanged - emojiCount.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudEmojiPacks])]) - |> map { view in - if let info = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudEmojiPacks])] as? ItemCollectionInfosView, let entries = info.entriesByNamespace[Namespaces.ItemCollection.CloudEmojiPacks] { - return Int32(entries.count) - } else { - return 0 - } - }) + emojiCount.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: Namespaces.ItemCollection.CloudEmojiPacks)) + |> map { entries in + return Int32(entries.count) + }) + case .masks: featured.set(.single([])) archivedPromise.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks(namespace: .masks) |> map(Optional.init))) @@ -945,12 +965,13 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { stickerSettings = value } + currentEmojiStickerSuggestionMode = stickerSettings.emojiStickerSuggestionMode var packCount: Int? = nil - var stickerPacks: [ItemCollectionInfoEntry] = [] - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView, let entries = stickerPacksView.entriesByNamespace[namespaceForMode(mode)] { - packCount = entries.count - stickerPacks = entries + var stickerPacks: [EngineRawItemCollectionInfoEntry] = [] + do { + packCount = view.count + stickerPacks = view } let leftNavigationButton: ItemListNavigationButton? = nil @@ -961,7 +982,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if case .modal = mode { rightNavigationButton = nil } else { - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { $0.withUpdatedEditing(false).withUpdatedSelectedPackIds(nil) } @@ -989,7 +1010,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if case .modal = mode { updateState { - $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(nil) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } } else { updateState { @@ -997,7 +1018,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta } } - var packIds: [ItemCollectionId] = [] + var packIds: [EngineItemCollectionId] = [] for entry in stickerPacks { if let selectedPackIds = state.selectedPackIds, selectedPackIds.contains(entry.id) { packIds.append(entry.id) @@ -1020,7 +1041,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta if case .modal = mode { updateState { - $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(nil) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } } else { updateState { @@ -1028,7 +1049,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta } } - var packIds: [ItemCollectionId] = [] + var packIds: [EngineItemCollectionId] = [] for entry in stickerPacks { if let selectedPackIds = state.selectedPackIds, selectedPackIds.contains(entry.id) { packIds.append(entry.id) @@ -1046,7 +1067,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta }), .init(title: presentationData.strings.StickerPacks_ActionShare, isEnabled: selectedCount > 0, action: { if case .modal = mode { updateState { - $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(nil) + $0.withUpdatedEditing(true).withUpdatedSelectedPackIds(Set()) } } else { updateState { @@ -1111,7 +1132,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta guard case let .pack(_, _, _, fromPackInfo, _, _, _, _, _, _) = fromEntry else { return .single(false) } - var referenceId: ItemCollectionId? + var referenceId: EngineItemCollectionId? var beforeAll = false var afterAll = false if toIndex < entries.count { @@ -1129,7 +1150,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta afterAll = true } - var currentIds: [ItemCollectionId] = [] + var currentIds: [EngineItemCollectionId] = [] for entry in entries { switch entry { case let .pack(_, _, _, info, _, _, _, _, _, _): @@ -1183,7 +1204,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta }) controller.setReorderCompleted({ (entries: [InstalledStickerPacksEntry]) -> Void in - var currentIds: [ItemCollectionId] = [] + var currentIds: [EngineItemCollectionId] = [] for entry in entries { switch entry { case let .pack(_, _, _, info, _, _, _, _, _, _): @@ -1220,13 +1241,13 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta controller.present(c, in: .window(.root), with: p) } } + presentInGlobalOverlayImpl = { [weak controller] c in + controller?.presentInGlobalOverlay(c, with: nil) + } presentStickerPackController = { [weak controller] info in let _ = (stickerPacks.get() |> take(1) - |> deliverOnMainQueue).start(next: { view in - guard let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespaceForMode(mode)])] as? ItemCollectionInfosView, let entries = stickerPacksView.entriesByNamespace[namespaceForMode(mode)] else { - return - } + |> deliverOnMainQueue).start(next: { entries in var mainStickerPack: StickerPackReference? var packs: [StickerPackReference] = [] for entry in entries { @@ -1308,6 +1329,9 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta dismissImpl = { [weak controller] in controller?.dismiss() } + findSuggestOptionsReferenceNode = { [weak controller] in + return controller?.itemNode(forTag: InstalledStickerPacksEntryTag.suggestOptions) as? ItemListDisclosureItemNode + } if let focusOnItemTag { var didFocusOnItem = false diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift index 2317be18fb..b85aaeea8c 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift @@ -118,7 +118,7 @@ public class TermsOfServiceController: ViewController, StandalonePresentableCont } strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.PrivacyPolicy_Decline, text: text, actions: [TextAlertAction(type: .destructiveAction, title: declineTitle, action: { self?.decline() - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { })], actionLayout: .vertical), in: .window(.root)) }, rightAction: { [weak self] in guard let strongSelf = self else { diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift index 6243b349b6..6855051a2a 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift @@ -199,6 +199,12 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { deinit { } + override func didLoad() { + super.didLoad() + + self.scrollNode.view.scrollsToTop = false + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = (layout, navigationBarHeight) diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 631148e302..540b821cf4 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -4,7 +4,6 @@ import Display import SwiftSignalKit import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext @@ -189,6 +188,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false self.pageControlNode.setPage(0.0) } @@ -240,6 +240,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in @@ -268,7 +269,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView context: self.context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), + index: .chatList(EngineChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( @@ -334,13 +335,13 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView } let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) - let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) - let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer2: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer3: EnginePeer = .channel(TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) + let peer3Author: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer4: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer5: EnginePeer = .channel(TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) + let peer6: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.SecretChat, id: EnginePeer.Id.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) let timestamp = self.referenceTimestamp @@ -452,30 +453,30 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder) var items: [ListViewItem] = [] - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let replyMessageId = EngineMessage.Id(peerId: peerId, namespace: 0, id: 3) + messages[replyMessageId] = EngineRawMessage(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - let message1 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message1 = EngineRawMessage(stableId: 4, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) - let message2 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = EngineRawMessage(stableId: 3, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: Data(base64Encoded: waveformBase64)!)] - let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) + let voiceMedia = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) - let message3 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message3 = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: FileMediaResourceStatus(mediaStatus: .playbackStatus(.paused), fetchStatus: .Local), tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) - let message4 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message4 = EngineRawMessage(stableId: 2, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message4], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.chatBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) let width: CGFloat diff --git a/submodules/SettingsUI/Sources/ThemePickerController.swift b/submodules/SettingsUI/Sources/ThemePickerController.swift index b40046b0a5..5da8ce64b3 100644 --- a/submodules/SettingsUI/Sources/ThemePickerController.swift +++ b/submodules/SettingsUI/Sources/ThemePickerController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -350,7 +349,7 @@ private final class ThemePickerControllerImpl: ItemListController, ThemePickerCo public func themePickerController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController { #if DEBUG - BuiltinWallpaperData.generate(account: context.account) + BuiltinWallpaperData.generate(network: context.account.network) #endif var pushControllerImpl: ((ViewController) -> Void)? @@ -364,10 +363,10 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme var selectAccentColorImpl: ((TelegramBaseTheme?, PresentationThemeAccentColor?) -> Void)? var openAccentColorPickerImpl: ((PresentationThemeReference, Bool) -> Void)? - let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start() + let _ = context.engine.themes.wallpapers().start() let cloudThemes = Promise<[TelegramTheme]>() - let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + let updatedCloudThemes = context.engine.themes.themes(accountManager: context.sharedContext.accountManager) cloudThemes.set(updatedCloudThemes) let removedThemeIndexesPromise = Promise>(Set()) @@ -518,7 +517,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme } |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationThemeAccentColor?, TelegramWallpaper), NoError> in if case let .file(file) = wallpaper, file.id == 0 { - return cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + return cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper in if let wallpaper = cachedWallpaper?.wallpaper, case .file = wallpaper { return (accentColor, wallpaper) @@ -579,7 +578,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme let resolvedWallpaper: Signal if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper -> TelegramWallpaper in return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper } @@ -746,7 +745,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme let wallpaperSignal: Signal if case let .file(file) = effectiveWallpaper, file.id == 0 { - wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + wallpaperSignal = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper in return cachedWallpaper?.wallpaper ?? effectiveWallpaper } @@ -826,7 +825,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme let resolvedWallpaper: Signal if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper -> TelegramWallpaper in return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper } @@ -1130,7 +1129,7 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme let resolvedWallpaper: Signal if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper } @@ -1227,10 +1226,10 @@ public func themePickerController(context: AccountContext, focusOnItemTag: Theme selectAccentColorImpl = { currentBaseTheme, accentColor in var wallpaperSignal: Signal = .single(nil) if let colorWallpaper = accentColor?.wallpaper, case let .file(file) = colorWallpaper { - wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: colorWallpaper.settings) + wallpaperSignal = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: colorWallpaper.settings) |> mapToSignal { cachedWallpaper in if let wallpaper = cachedWallpaper?.wallpaper, case let .file(file) = wallpaper { - let _ = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)).start() + let _ = context.engine.resources.fetch(reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource), userLocation: .other, userContentType: .other).start() return .single(wallpaper) diff --git a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift index aae20daead..4b0d52dd75 100644 --- a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift +++ b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import MergeLists @@ -26,7 +25,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if let image = cachedBorderImages[key] { return image } else { - let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let image = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -52,7 +51,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec context.setLineWidth(lineWidth) context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) } - })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 16) cachedBorderImages[key] = image return image } @@ -81,7 +80,7 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { self.imageNode = TransformImageNode() self.imageNode.isLayerBacked = true - self.imageNode.cornerRadius = 8.0 + self.imageNode.cornerRadius = 16.0 self.imageNode.clipsToBounds = true self.overlayNode = ASImageNode() @@ -154,11 +153,6 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { } } -// override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { -// let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) -// self.placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + emojiFrame.minX, y: rect.minY + emojiFrame.minY), size: emojiFrame.size), within: containerSize) -// } - func setup(item: ThemeCarouselThemeIconItem, size: CGSize) { let currentItem = self.item let currentSize = self.size @@ -269,7 +263,7 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { } self.animatedStickerNode = animatedStickerNode self.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: self.placeholderNode) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) @@ -277,7 +271,7 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { animatedStickerNode.autoplay = true animatedStickerNode.visibility = true - self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + self.stickerFetchedDisposable.set(item.context.engine.resources.fetch(reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource), userLocation: .other, userContentType: .other).start()) let thumbnailDimensions = PixelDimensions(width: 512, height: 512) self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) @@ -418,6 +412,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let padding: CGFloat = 12.0 let minSpacing: CGFloat = 6.0 let referenceImageSize: CGSize @@ -429,18 +424,13 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { } let totalWidth = params.width - params.leftInset - params.rightInset let imageCount = Int((totalWidth - minSpacing) / (referenceImageSize.width + minSpacing)) - var itemSize = referenceImageSize.aspectFilled(CGSize(width: floorToScreenPixels((totalWidth - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) + var itemSize = referenceImageSize.aspectFilled(CGSize(width: floorToScreenPixels((totalWidth - padding * 2.0 - CGFloat(imageCount - 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) itemSize.height = referenceImageSize.height - let itemSpacing = floorToScreenPixels((totalWidth - CGFloat(imageCount) * itemSize.width) / CGFloat(imageCount + 1)) - - var spacingOffset: CGFloat = 0.0 - if totalWidth - CGFloat(imageCount) * itemSize.width - CGFloat(imageCount + 1) * itemSpacing == 1.0 { - spacingOffset = UIScreenPixel - } - + let itemSpacing = floorToScreenPixels((totalWidth - padding * 2.0 - CGFloat(imageCount) * itemSize.width) / CGFloat(imageCount - 1)) + let rows = ceil(CGFloat(item.themes.count) / CGFloat(imageCount)) - contentSize = CGSize(width: params.width, height: minSpacing + rows * (itemSize.height + itemSpacing)) + contentSize = CGSize(width: params.width, height: padding * 2.0 + rows * itemSize.height + (rows - 1.0) * itemSpacing) insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -451,6 +441,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { strongSelf.item = item strongSelf.layoutParams = params + strongSelf.scrollNode.view.scrollsToTop = false strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor @@ -493,7 +484,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { } strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height) - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) @@ -526,7 +517,7 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { let col = CGFloat(index % imageCount) let row = floor(CGFloat(index) / CGFloat(imageCount)) - let itemFrame = CGRect(origin: CGPoint(x: params.leftInset + spacingOffset + itemSpacing + (itemSize.width + itemSpacing) * col, y: minSpacing + (itemSize.height + itemSpacing) * row), size: itemSize) + let itemFrame = CGRect(origin: CGPoint(x: params.leftInset + padding + (itemSize.width + itemSpacing) * col, y: padding + (itemSize.height + itemSpacing) * row), size: itemSize) itemNode.frame = itemFrame index += 1 diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index d4ac7c9af5..062d165781 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramUIPreferences diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index e00bdb0fee..697a1ca703 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -303,7 +302,7 @@ private func editThemeControllerEntries(presentationData: PresentationData, stat return entries } -public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((PeerId) -> Void)? = nil, completion: ((PresentationThemeReference) -> Void)? = nil) -> ViewController { +public func editThemeController(context: AccountContext, mode: EditThemeControllerMode, navigateToChat: ((EnginePeer.Id) -> Void)? = nil, completion: ((PresentationThemeReference) -> Void)? = nil) -> ViewController { let initialState: EditThemeControllerState let previewThemePromise = Promise() let settingsPromise = Promise(nil) @@ -334,9 +333,9 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll case let .edit(info): hasSettings = info.theme.settings != nil settingsPromise.set(.single(info.theme.settings?.first)) - if let file = info.theme.file, let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { + if let file = info.theme.file, let path = context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(file.resource)), let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let theme = makePresentationTheme(data: data, resolvedWallpaper: info.resolvedWallpaper) { if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - previewThemePromise.set(cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + previewThemePromise.set(cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map ({ wallpaper -> PresentationTheme in if let wallpaper = wallpaper { return theme.withUpdated(name: nil, defaultWallpaper: wallpaper.wallpaper) @@ -405,7 +404,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll if let url = urls.first{ if let data = try? Data(contentsOf: url), let theme = makePresentationTheme(data: data) { if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - let _ = (cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + let _ = (cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> mapToSignal { wallpaper -> Signal in if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { var convertedRepresentations: [ImageRepresentationWithReference] = [] @@ -415,7 +414,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll guard complete, let fullSizeData = fullSizeData else { return .complete() } - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(file.file.resource.id), data: fullSizeData, synchronous: true) return .single(wallpaper.wallpaper) } } else { @@ -497,7 +496,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, statePromise.get(), previewThemePromise.get(), settingsPromise.get()) |> map { presentationData, state, previewTheme, currentSettings -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -517,7 +516,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll isComplete = !state.title.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty && !state.slug.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: isComplete, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: isComplete, action: { if state.title.count > 128 { errorImpl?(.title) return @@ -538,7 +537,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let _ = (combineLatest(queue: Queue.mainQueue(), previewThemePromise.get(), settingsPromise.get()) |> take(1)).start(next: { previewTheme, settings in let saveThemeTemplateFile: (String, LocalFileMediaResource, @escaping () -> Void) -> Void = { title, resource, completion in - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: resource.fileId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/x-tgtheme-ios", size: nil, attributes: [.FileName(fileName: "\(title).tgios-theme")], alternativeRepresentations: []) let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: context.account.peerId, messages: [message]).start() @@ -575,8 +574,8 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let themeThumbnailData: Data? if let theme = theme, let themeString = encodePresentationTheme(theme), let data = themeString.data(using: .utf8) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) themeResource = resource themeData = data @@ -612,14 +611,14 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let resource = file.file.resource var data: Data? - if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + if let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { data = maybeData - } else if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + } else if let path = context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { data = maybeData } if let data = data { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) prepare = .complete() } else { prepare = .complete() @@ -637,7 +636,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in if let resource = resultTheme.file?.resource, let data = themeData { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) } let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id)) @@ -671,7 +670,7 @@ public func editThemeController(context: AccountContext, mode: EditThemeControll let _ = applyTheme(accountManager: context.sharedContext.accountManager, account: context.account, theme: resultTheme).start() let _ = (updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in if let resource = resultTheme.file?.resource, let data = themeData { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) } let themeReference: PresentationThemeReference = .cloud(PresentationCloudTheme(theme: resultTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: context.account.id)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift index 7e5f7fded1..1f206a2bfb 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -16,6 +15,7 @@ import Geocoding import WallpaperResources import Sunrise import ThemeSettingsThemeItem +import ChatTimerScreen private enum TriggerMode { case system @@ -488,32 +488,44 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon break } - presentControllerImpl?(ThemeAutoNightTimeSelectionActionSheet(context: context, currentValue: currentValue, applyValue: { value in - guard let value = value else { - return - } - updateSettings { settings in - var settings = settings - switch settings.trigger { - case let .timeBased(setting): - switch setting { - case var .manual(fromSeconds, toSeconds): - switch field { - case .from: - fromSeconds = value - case .to: - toSeconds = value + let controller = ChatTimerScreen( + context: context, + configuration: ChatTimerScreen.Configuration( + style: .default, + picker: .timeOfDay, + currentValue: currentValue, + pickerValueMapping: .secondsFromMidnightGMT, + primaryActionTitle: { strings, _, _ in + strings.Wallpaper_Set + } + ), + completion: { value in + guard let value = value else { + return + } + updateSettings { settings in + var settings = settings + switch settings.trigger { + case let .timeBased(setting): + switch setting { + case var .manual(fromSeconds, toSeconds): + switch field { + case .from: + fromSeconds = value + case .to: + toSeconds = value + } + settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds)) + default: + break } - settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds)) default: break - } - default: - break + } + return settings } - return settings - } - })) + }) + presentControllerImpl?(controller) return settings } @@ -540,7 +552,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon let resolvedWallpaper: Signal if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper } @@ -566,7 +578,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon }) let cloudThemes = Promise<[TelegramTheme]>() - let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + let updatedCloudThemes = context.engine.themes.themes(accountManager: context.sharedContext.accountManager) cloudThemes.set(updatedCloudThemes) let signal = combineLatest(context.sharedContext.presentationData |> deliverOnMainQueue, sharedData |> deliverOnMainQueue, cloudThemes.get() |> deliverOnMainQueue, stagingSettingsPromise.get() |> deliverOnMainQueue) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightTimeSelectionActionSheet.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightTimeSelectionActionSheet.swift deleted file mode 100644 index b357293898..0000000000 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightTimeSelectionActionSheet.swift +++ /dev/null @@ -1,132 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import UIKit -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import TelegramStringFormatting -import AccountContext -import UIKitRuntimeUtils - -final class ThemeAutoNightTimeSelectionActionSheet: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - - init(context: AccountContext, currentValue: Int32, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - - self._ready.set(.single(true)) - - var updatedValue = currentValue - var items: [ActionSheetItem] = [] - items.append(ThemeAutoNightTimeSelectionActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in - updatedValue = value - })) - if let emptyTitle = emptyTitle { - items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in - self?.dismissAnimated() - applyValue(nil) - })) - } - items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in - self?.dismissAnimated() - applyValue(updatedValue) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class ThemeAutoNightTimeSelectionActionSheetItem: ActionSheetItem { - let strings: PresentationStrings - - let currentValue: Int32 - let valueChanged: (Int32) -> Void - - init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.strings = strings - self.currentValue = currentValue - self.valueChanged = valueChanged - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return ThemeAutoNightTimeSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - - private let valueChanged: (Int32) -> Void - private let pickerView: UIDatePicker - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme - self.strings = strings - self.valueChanged = valueChanged - - UILabel.setDateLabel(theme.primaryTextColor) - - self.pickerView = UIDatePicker() - self.pickerView.datePickerMode = .countDownTimer - self.pickerView.datePickerMode = .time - self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) - self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue)) - self.pickerView.locale = Locale.current - if #available(iOS 13.4, *) { - self.pickerView.preferredDatePickerStyle = .wheels - } - self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") - - super.init(theme: theme) - - self.view.addSubview(self.pickerView) - self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 216.0) - - self.pickerView.frame = CGRect(origin: CGPoint(), size: size) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc private func datePickerUpdated() { - self.valueChanged(Int32(self.pickerView.date.timeIntervalSince1970)) - } -} - diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift index dc564fdf2f..077a07ec0c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -60,7 +59,9 @@ public final class ThemePreviewController: ViewController { self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationTheme.set(.single(previewTheme)) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings)) + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.previewTheme, presentationStrings: self.presentationData.strings, style: .glass)) + + self._hasGlassStyle = true self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true @@ -141,13 +142,13 @@ public final class ThemePreviewController: ViewController { let titleView = CounterControllerTitleView(theme: self.previewTheme) titleView.title = CounterControllerTitle(title: themeName, counter: hasInstallsCount ? " " : "") self.navigationItem.titleView = titleView - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.statusBar.statusBarStyle = self.previewTheme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) if !isPreview { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: self.previewTheme.rootController.navigationBar.accentTextColor), style: .plain, target: self, action: #selector(self.actionPressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Navigation/Share"), color: self.previewTheme.chat.inputPanel.panelControlColor), style: .plain, target: self, action: #selector(self.actionPressed)) } self.disposable = (combineLatest(self.theme.get(), self.presentationTheme.get()) @@ -156,7 +157,7 @@ public final class ThemePreviewController: ViewController { let titleView = CounterControllerTitleView(theme: strongSelf.previewTheme) titleView.title = CounterControllerTitle(title: themeName, counter: hasInstallsCount ? strongSelf.presentationData.strings.Theme_UsersCount(max(1, theme.installCount ?? 0)) : "") strongSelf.navigationItem.titleView = titleView - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings), transition: .immediate) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: presentationTheme, presentationStrings: strongSelf.presentationData.strings, style: .glass), transition: .immediate) } }) @@ -222,7 +223,7 @@ public final class ThemePreviewController: ViewController { if let initialWallpaper = initialWallpaper { self.controllerNode.wallpaperPromise.set(.single(initialWallpaper)) } else if case let .file(file) = previewTheme.chat.defaultWallpaper, file.id == 0 { - self.controllerNode.wallpaperPromise.set(cachedWallpaper(account: self.context.account, slug: file.slug, settings: file.settings) + self.controllerNode.wallpaperPromise.set(cachedWallpaper(engine: self.context.engine, network: self.context.account.network, slug: file.slug, settings: file.settings) |> mapToSignal { wallpaper in return .single(wallpaper?.wallpaper ?? .color(previewTheme.chatList.backgroundColor.argb)) }) @@ -267,8 +268,8 @@ public final class ThemePreviewController: ViewController { case .media: if let strings = encodePresentationTheme(previewTheme), let data = strings.data(using: .utf8) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) theme = .single(.local(PresentationLocalTheme(title: previewTheme.name.string, resource: resource, resolvedWallpaper: nil))) } else { theme = .single(.builtin(.dayClassic)) @@ -285,7 +286,7 @@ public final class ThemePreviewController: ViewController { switch theme { case let .cloud(info): resolvedWallpaper = info.resolvedWallpaper - return telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + return context.engine.themes.themes(accountManager: context.sharedContext.accountManager) |> take(1) |> map { themes -> Bool in if let _ = themes.first(where: { $0.id == info.theme.id }) { @@ -318,7 +319,7 @@ public final class ThemePreviewController: ViewController { }, scale: 1.0) let themeThumbnailData = themeThumbnail?.jpegData(compressionQuality: 0.6) - return telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + return context.engine.themes.themes(accountManager: context.sharedContext.accountManager) |> take(1) |> mapToSignal { themes -> Signal<(PresentationThemeReference, Bool), NoError> in let similarTheme = themes.first(where: { $0.isCreator && $0.title == info.title }) @@ -334,7 +335,7 @@ public final class ThemePreviewController: ViewController { return .single((.local(updatedTheme), true)) } if case let .result(theme) = result, let file = theme.file { - context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + context.sharedContext.accountManager.resources.moveResourceData(from: EngineMediaResource.Id(info.resource.id), to: EngineMediaResource.Id(file.resource.id)) return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: theme.isCreator ? context.account.id : nil)), true)) } else { return .complete() @@ -353,7 +354,7 @@ public final class ThemePreviewController: ViewController { return .single((.local(updatedTheme), true)) } if case let .result(updatedTheme) = result, let file = updatedTheme.file { - context.sharedContext.accountManager.mediaBox.moveResourceData(from: info.resource.id, to: file.resource.id) + context.sharedContext.accountManager.resources.moveResourceData(from: EngineMediaResource.Id(info.resource.id), to: EngineMediaResource.Id(file.resource.id)) return .single((.cloud(PresentationCloudTheme(theme: updatedTheme, resolvedWallpaper: resolvedWallpaper, creatorAccountId: updatedTheme.isCreator ? context.account.id : nil)), true)) } else { return .complete() @@ -411,7 +412,7 @@ public final class ThemePreviewController: ViewController { var themeSpecificChatWallpapers = updatedSettings.themeSpecificChatWallpapers themeSpecificChatWallpapers[updatedTheme.index] = nil - return PreferencesEntry(updatedSettings.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers).withUpdatedThemeSpecificAccentColors(themeSpecificAccentColors)) + return EnginePreferencesEntry(updatedSettings.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers).withUpdatedThemeSpecificAccentColors(themeSpecificAccentColors)) }) return previousDefaultTheme } diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index c3bd534963..bbad3bbf56 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -265,16 +264,16 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { } strongSelf.remoteChatBackgroundNode.setSignal(signal) - strongSelf.fetchDisposable.set(fetchedMediaResource(mediaBox: context.sharedContext.accountManager.mediaBox, userLocation: .other, userContentType: .other, reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)).start()) - - let account = strongSelf.context.account - let statusSignal = strongSelf.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource) + strongSelf.fetchDisposable.set(context.sharedContext.accountManager.resources.fetch(reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource), userLocation: .other, userContentType: .other).start()) + + let engine = strongSelf.context.engine + let statusSignal = strongSelf.context.sharedContext.accountManager.resources.status(resource: EngineMediaResource(file.file.resource)) |> take(1) - |> mapToSignal { status -> Signal in + |> mapToSignal { status -> Signal in if case .Local = status { return .single(status) } else { - return account.postbox.mediaBox.resourceStatus(file.file.resource) + return engine.resources.status(resource: EngineMediaResource(file.file.resource)) } } @@ -299,7 +298,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { } strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))() - strongSelf.toolbarNode.dark = useDarkButton + strongSelf.toolbarNode.dark = false + let _ = useDarkButton } }) } @@ -319,6 +319,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false self.pageControlNode.setPage(0.0) } @@ -374,6 +375,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in @@ -400,7 +402,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { context: self.context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), + index: .chatList(EngineChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( @@ -468,14 +470,14 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { let chatListPresentationData = ChatListPresentationData(theme: self.previewTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) - let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) - let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer2: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer3: EnginePeer = .channel(TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) + let peer3Author: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer4: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer5: EnginePeer = .channel(TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) + let peer6: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.SecretChat, id: EnginePeer.Id.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer7: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) let timestamp = self.referenceTimestamp @@ -592,43 +594,43 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.previewTheme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder) var items: [ListViewItem] = [] - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - var sampleMessages: [Message] = [] + var sampleMessages: [EngineRawMessage] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message1 = EngineRawMessage(stableId:1, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = EngineRawMessage(stableId:2, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message3 = EngineRawMessage(stableId:3, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message4 = EngineRawMessage(stableId:4, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message4.id] = message4 sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message5 = EngineRawMessage(stableId:5, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message5.id] = message5 sampleMessages.append(message5) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: Data(base64Encoded: waveformBase64)!)] - let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) + let voiceMedia = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message6 = EngineRawMessage(stableId:6, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message7 = EngineRawMessage(stableId:7, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message8 = EngineRawMessage(stableId:8, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index fad4ad8e12..17c4abb873 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -842,7 +842,7 @@ class ThemeSettingsAccentColorItemNode: ListViewItemNode, ItemListItemNode { } strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height) - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift index 8140064aff..3db095dc8c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift @@ -179,7 +179,7 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { self.iconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - iconSize.width) / 2.0), y: 13.0), size: iconSize) self.overlayNode.frame = self.iconNode.frame - let textSize = self.textNode.updateLayout(bounds.size) + let textSize = self.textNode.updateLayout(CGSize(width: bounds.size.width + 8.0, height: bounds.size.height)) let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width) / 2.0), y: 81.0), size: textSize) self.textNode.frame = textFrame diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index e96aed13d8..58e3acebfc 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -156,20 +155,20 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - let otherPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) + let otherPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)) var items: [ListViewItem] = [] for messageItem in item.messageItems.reversed() { - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) + let replyMessageId = EngineMessage.Id(peerId: peerId, namespace: 0, id: 3) if let (author, text) = messageItem.reply { peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: author, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + messages[replyMessageId] = EngineRawMessage(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: messageItem.outgoing ? otherPeerId : peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: messageItem.outgoing ? TelegramUser(id: otherPeerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) : nil, text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)] : [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index ffe197cc66..965bca3666 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -425,7 +424,7 @@ private func themeSettingsControllerEntries( var authorName = presentationData.strings.Appearance_PreviewReplyAuthor if let accountPeer { nameColor = accountPeer.nameColor ?? .preset(.blue) - if accountPeer._asPeer().hasCustomNameColor { + if accountPeer.hasCustomNameColor { authorName = accountPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) } profileColor = accountPeer.effectiveProfileColor @@ -507,7 +506,7 @@ private final class ThemeSettingsControllerImpl: ItemListController, ThemeSettin public func themeSettingsController(context: AccountContext, focusOnItemTag: ThemeSettingsEntryTag? = nil) -> ViewController { #if DEBUG - BuiltinWallpaperData.generate(account: context.account) + BuiltinWallpaperData.generate(network: context.account.network) #endif var pushControllerImpl: ((ViewController) -> Void)? @@ -521,7 +520,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The var selectAccentColorImpl: ((PresentationThemeAccentColor?) -> Void)? var openAccentColorPickerImpl: ((PresentationThemeReference, Bool) -> Void)? - let _ = telegramWallpapers(postbox: context.account.postbox, network: context.account.network).start() + let _ = context.engine.themes.wallpapers().start() let currentAppIcon: PresentationAppIcon? var appIcons = context.sharedContext.applicationBindings.getAvailableAlternateIcons() @@ -541,7 +540,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The currentAppIconName.set(currentAppIcon?.name ?? "Blue") let cloudThemes = Promise<[TelegramTheme]>() - let updatedCloudThemes = telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + let updatedCloudThemes = context.engine.themes.themes(accountManager: context.sharedContext.accountManager) cloudThemes.set(updatedCloudThemes) let removedThemeIndexesPromise = Promise>(Set()) @@ -678,7 +677,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The } |> mapToSignal { accentColor, wallpaper -> Signal<(PresentationThemeAccentColor?, TelegramWallpaper), NoError> in if case let .file(file) = wallpaper, file.id == 0 { - return cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + return cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper in if let wallpaper = cachedWallpaper?.wallpaper, case .file = wallpaper { return (accentColor, wallpaper) @@ -732,7 +731,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let resolvedWallpaper: Signal if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper -> TelegramWallpaper in return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper } @@ -897,7 +896,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let wallpaperSignal: Signal if case let .file(file) = effectiveWallpaper, file.id == 0 { - wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + wallpaperSignal = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper in return cachedWallpaper?.wallpaper ?? effectiveWallpaper } @@ -977,7 +976,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let resolvedWallpaper: Signal if case let .file(file) = theme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { cachedWallpaper -> TelegramWallpaper in return cachedWallpaper?.wallpaper ?? theme.chat.defaultWallpaper } @@ -1243,7 +1242,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The let resolvedWallpaper: Signal if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper } @@ -1320,10 +1319,10 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The selectAccentColorImpl = { accentColor in var wallpaperSignal: Signal = .single(nil) if let colorWallpaper = accentColor?.wallpaper, case let .file(file) = colorWallpaper { - wallpaperSignal = cachedWallpaper(account: context.account, slug: file.slug, settings: colorWallpaper.settings) + wallpaperSignal = cachedWallpaper(engine: context.engine, network: context.account.network, slug: file.slug, settings: colorWallpaper.settings) |> mapToSignal { cachedWallpaper in if let wallpaper = cachedWallpaper?.wallpaper, case let .file(file) = wallpaper { - let _ = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)).start() + let _ = context.engine.resources.fetch(reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource), userLocation: .other, userContentType: .other).start() return .single(wallpaper) diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index c3d3b1fef0..a605c29dbd 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -10,18 +10,15 @@ import AccountContext import UndoUI import InviteLinksUI import TextFormat -import Postbox private final class UsernameSetupControllerArguments { - let account: Account let updatePublicLinkText: (String?, String) -> Void let shareLink: () -> Void let activateLink: (String) -> Void let deactivateLink: (String) -> Void let openAuction: (String) -> Void - - init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { - self.account = account + + init(updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { self.updatePublicLinkText = updatePublicLinkText self.shareLink = shareLink self.activateLink = activateLink @@ -291,10 +288,10 @@ private struct UsernameSetupControllerState: Equatable { } } -private func usernameSetupControllerEntries(presentationData: PresentationData, view: PeerView, state: UsernameSetupControllerState, temporaryOrder: [String]?, mode: UsernameSetupMode) -> [UsernameSetupEntry] { +private func usernameSetupControllerEntries(presentationData: PresentationData, peer: EnginePeer?, state: UsernameSetupControllerState, temporaryOrder: [String]?, mode: UsernameSetupMode) -> [UsernameSetupEntry] { var entries: [UsernameSetupEntry] = [] - - if let peer = view.peers[view.peerId] as? TelegramUser { + + if case let .user(peer) = peer { let currentUsername: String if let current = state.editingPublicLinkText { currentUsername = current @@ -444,7 +441,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup peerId = botPeerId } - let arguments = UsernameSetupControllerArguments(account: context.account, updatePublicLinkText: { currentText, text in + let arguments = UsernameSetupControllerArguments(updatePublicLinkText: { currentText, text in if text.isEmpty { checkAddressNameDisposable.set(nil) updateState { state in @@ -468,10 +465,17 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup })) } }, shareLink: { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in - if let user = peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = peer, user.botInfo != nil { context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) } else { var currentAddressName: String = peer.addressName ?? "" @@ -537,20 +541,18 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup let temporaryOrder = Promise<[String]?>(nil) - let peerView = context.account.viewTracker.peerView(peerId) + let peerSignal = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue - + let signal = combineLatest( context.sharedContext.presentationData, statePromise.get() |> deliverOnMainQueue, - peerView, + peerSignal, temporaryOrder.get() ) - |> map { presentationData, state, view, temporaryOrder -> (ItemListControllerState, (ItemListNodeState, Any)) in - let peer = peerViewMainPeer(view) - + |> map { presentationData, state, peer, temporaryOrder -> (ItemListControllerState, (ItemListNodeState, Any)) in var rightNavigationButton: ItemListNavigationButton? - if let peer = peer as? TelegramUser { + if case let .user(peer) = peer { var doneEnabled = true if let addressNameValidationStatus = state.addressNameValidationStatus { @@ -562,7 +564,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup } } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { var updatedAddressNameValue: String? updateState { state in if state.editingPublicLinkText != peer.addressName { @@ -595,7 +597,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup }) } - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) @@ -606,7 +608,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup title = presentationData.strings.Username_Title } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: usernameSetupControllerEntries(presentationData: presentationData, view: view, state: state, temporaryOrder: temporaryOrder, mode: mode), style: .blocks, focusItemTag: mode == .account ? UsernameEntryTag.username : nil, animateChanges: true) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: usernameSetupControllerEntries(presentationData: presentationData, peer: peer, state: state, temporaryOrder: temporaryOrder, mode: mode), style: .blocks, focusItemTag: mode == .account ? UsernameEntryTag.username : nil, animateChanges: true) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 76d9fff2c7..b69ebe61d2 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -299,18 +299,18 @@ public final class ShareControllerAppEnvironment: ShareControllerEnvironment { public final class ShareControllerSwitchableAccount: Equatable { public let account: ShareControllerAccountContext - public let peer: Peer - - public init(account: ShareControllerAccountContext, peer: Peer) { + public let peer: EnginePeer + + public init(account: ShareControllerAccountContext, peer: EnginePeer) { self.account = account self.peer = peer } - + public static func ==(lhs: ShareControllerSwitchableAccount, rhs: ShareControllerSwitchableAccount) -> Bool { if lhs.account !== rhs.account { return false } - if !arePeersEqual(lhs.peer, rhs.peer) { + if lhs.peer != rhs.peer { return false } return true @@ -1145,11 +1145,10 @@ public final class ShareController: ViewController { for info in strongSelf.switchableAccounts { items.append(ActionSheetPeerItem( accountPeerId: info.account.accountPeerId, - postbox: info.account.stateManager.postbox, - network: info.account.stateManager.network, + stateManager: info.account.stateManager, contentSettings: info.account.contentSettings, - peer: EnginePeer(info.peer), - title: EnginePeer(info.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), + peer: info.peer, + title: info.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), isSelected: info.account.accountId == strongSelf.currentContext.accountId, strings: presentationData.strings, theme: presentationData.theme, @@ -2400,10 +2399,9 @@ public final class ShareController: ViewController { } let context = accountContext.context - let postbox = self.currentContext.stateManager.postbox let signals: [Signal] = messages.compactMap { message -> Signal? in - if let media = message.media.first { - return SaveToCameraRoll.saveToCameraRoll(context: context, postbox: postbox, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: media)) + if let media = message.effectiveMedia.first { + return SaveToCameraRoll.saveToCameraRoll(context: context, userLocation: .peer(message.id.peerId), mediaReference: .message(message: MessageReference(message), media: media)) } else { return nil } @@ -2429,7 +2427,7 @@ public final class ShareController: ViewController { let context = accountContext.context let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: .standalone(media: media)) |> map(Optional.init), dismissImmediately: true, completion: {}) + self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, userLocation: .other, mediaReference: .standalone(media: media)) |> map(Optional.init), dismissImmediately: true, completion: {}) } private func saveToCameraRoll(mediaReference: AnyMediaReference, completion: (() -> Void)?) { @@ -2438,7 +2436,7 @@ public final class ShareController: ViewController { } let context = accountContext.context - self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: mediaReference) |> map(Optional.init), dismissImmediately: completion == nil, completion: completion ?? {}) + self.controllerNode.transitionToProgressWithValue(signal: SaveToCameraRoll.saveToCameraRoll(context: context, userLocation: .other, mediaReference: mediaReference) |> map(Optional.init), dismissImmediately: completion == nil, completion: completion ?? {}) } private func switchToAccount(account: ShareControllerAccountContext, animateIn: Bool) { @@ -2496,7 +2494,7 @@ public final class ShareController: ViewController { for entry in view.0.entries.reversed() { switch entry { case let .MessageEntry(entryData): - if let peer = entryData.renderedPeer.peers[entryData.renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) { + if let peer = entryData.renderedPeer.peers[entryData.renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(EnginePeer(peer)) { peers.append(EngineRenderedPeer(entryData.renderedPeer)) if let user = peer as? TelegramUser, user.flags.contains(.requirePremium) || user.flags.contains(.requireStars) { possiblePremiumRequiredPeers.insert(user.id) diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 043a07e64e..7566da5364 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -456,6 +456,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() if self.fromForeignApp { @@ -1497,7 +1498,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate if fromForeignApp, case let .preparing(long) = status, !transitioned { transitioned = true if long { - strongSelf.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, forceNativeAppearance: true, postbox: strongSelf.context?.stateManager.postbox, environment: strongSelf.environment), fastOut: true) + strongSelf.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, forceNativeAppearance: true, environment: strongSelf.environment), fastOut: true) } else { strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true) } @@ -1880,7 +1881,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate transition.updateAlpha(node: startAtTimestampNode, alpha: 0.0) } - self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true, postbox: self.context?.stateManager.postbox, environment: self.environment), fastOut: true) + self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true, environment: self.environment), fastOut: true) let timestamp = CACurrentMediaTime() self.shareDisposable.set(signal.start(completed: { [weak self] in let minDelay = 0.6 diff --git a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift index f9ee2a4670..1389f89037 100644 --- a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift @@ -236,8 +236,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode { let resolveInlineStickers = context.resolveInlineStickers self.peerNode.setup( accountPeerId: context.accountPeerId, - postbox: context.stateManager.postbox, - network: context.stateManager.network, + stateManager: context.stateManager, energyUsageSettings: environment.energyUsageSettings, contentSettings: context.contentSettings, animationCache: context.animationCache, @@ -272,8 +271,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode { } self.peerNode.setupStoryRepost( accountPeerId: context.accountPeerId, - postbox: context.stateManager.postbox, - network: context.stateManager.network, + stateManager: context.stateManager, theme: theme, strings: strings, synchronousLoad: synchronousLoad, diff --git a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift index 074eb85846..a0c5f9a07c 100644 --- a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift @@ -63,8 +63,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode { } else { peersNode = ChatListSearchRecentPeersNode( accountPeerId: context.accountPeerId, - postbox: context.stateManager.postbox, - network: context.stateManager.network, + stateManager: context.stateManager, energyUsageSettings: environment.energyUsageSettings, contentSettings: context.contentSettings, animationCache: context.animationCache, diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index e6bd8fd778..06ef0c4c47 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -11,7 +11,6 @@ import TelegramAnimatedStickerNode import AppBundle import TelegramUniversalVideoContent import TelegramCore -import Postbox import AccountContext private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { @@ -235,7 +234,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte return self.elapsedTime + 3.0 + 0.15 } - public init(theme: PresentationTheme, strings: PresentationStrings, forceNativeAppearance: Bool, postbox: Postbox?, environment: ShareControllerEnvironment) { + public init(theme: PresentationTheme, strings: PresentationStrings, forceNativeAppearance: Bool, environment: ShareControllerEnvironment) { self.theme = theme self.strings = strings @@ -276,8 +275,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte } })) - if let postbox, let mediaManager = environment.mediaManager, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { - let _ = postbox + if let mediaManager = environment.mediaManager, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let _ = mediaManager let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index 63caa2459c..1bc44a39f9 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -232,7 +232,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { network: context.stateManager.network, contentSettings: context.contentSettings, theme: theme, - peer: EnginePeer(info.peer), + peer: info.peer, emptyColor: nil, synchronousLoad: false ) @@ -373,7 +373,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentOffsetUpdated = f } - private func calculateMetrics(size: CGSize, additionalBottomInset: CGFloat) -> (topInset: CGFloat, itemWidth: CGFloat) { + private func calculateMetrics(size: CGSize, additionalBottomInset: CGFloat, isEmbedded: Bool) -> (topInset: CGFloat, itemWidth: CGFloat) { let itemCount = self.entries.count let itemInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0) @@ -394,7 +394,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount)) - let gridTopInset = max(0.0, size.height - floor(initiallyRevealedRowCount * itemWidth) - 14.0 - additionalBottomInset) + let gridTopInset = isEmbedded ? 136.0 : max(0.0, size.height - floor(initiallyRevealedRowCount * itemWidth) - 14.0 - additionalBottomInset) return (gridTopInset, itemWidth) } @@ -569,10 +569,15 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } } + var isEmbedded = false func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { let firstLayout = self.validLayout == nil self.validLayout = (size, bottomInset) + self.contentTitleNode.isHidden = self.isEmbedded + self.contentSubtitleNode.isHidden = self.isEmbedded + self.searchButtonNode.isHidden = self.isEmbedded + let gridLayoutTransition: ContainedViewLayoutTransition if firstLayout { gridLayoutTransition = .immediate @@ -582,7 +587,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.overrideGridOffsetTransition = nil } - let (gridTopInset, itemWidth) = self.calculateMetrics(size: size, additionalBottomInset: bottomInset) + let (gridTopInset, itemWidth) = self.calculateMetrics(size: size, additionalBottomInset: bottomInset, isEmbedded: self.isEmbedded) var scrollToItem: GridNodeScrollToItem? if let ensurePeerVisibleOnLayout = self.ensurePeerVisibleOnLayout { @@ -680,8 +685,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentTitleNode.isHidden = true self.contentSubtitleNode.isHidden = true } else { - self.contentTitleNode.isHidden = false - self.contentSubtitleNode.isHidden = false + self.contentTitleNode.isHidden = self.isEmbedded + self.contentSubtitleNode.isHidden = self.isEmbedded var subtitleText = self.strings.ShareMenu_SelectChats if !self.controllerInteraction.selectedPeers.isEmpty { diff --git a/submodules/ShareController/Sources/ShareSearchContainerNode.swift b/submodules/ShareController/Sources/ShareSearchContainerNode.swift index ee3b966d15..faef506e64 100644 --- a/submodules/ShareController/Sources/ShareSearchContainerNode.swift +++ b/submodules/ShareController/Sources/ShareSearchContainerNode.swift @@ -286,7 +286,16 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let foundItems = combineLatest(self.searchQuery.get(), self.themePromise.get()) |> mapToSignal { query, theme -> Signal<([ShareSearchPeerEntry]?, Bool), NoError> in if !query.isEmpty { - let accountPeer = context.stateManager.postbox.loadedPeerWithId(context.accountPeerId) |> take(1) + let accountPeer = context.engineData.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.accountPeerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + |> take(1) let foundLocalPeers = context.stateManager.postbox.searchPeers(query: query.lowercased()) let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError> = .single(([], [], true)) |> then( @@ -326,12 +335,12 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } for peer in foundPeers.foundRemotePeers.0 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } for peer in foundPeers.foundRemotePeers.1 { - if let user = peer.peer as? TelegramUser, user.flags.contains(.requirePremium) { + if case let .user(user) = peer.peer, user.flags.contains(.requirePremium) { result.insert(user.id) } } @@ -372,7 +381,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { for renderedPeer in foundLocalPeers { if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id { - if !existingPeerIds.contains(renderedPeer.peerId) && canSendMessagesToPeer(peer) { + if !existingPeerIds.contains(renderedPeer.peerId) && canSendMessagesToPeer(EnginePeer(peer)) { existingPeerIds.insert(renderedPeer.peerId) entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(renderedPeer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false)) index += 1 @@ -392,16 +401,16 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: foundPeer.peer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false)) index += 1 } } - + for foundPeer in foundRemotePeers.1 { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: true)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: peer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: true)) index += 1 } } @@ -465,8 +474,8 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } var index = 0 for (peer, requiresPremiumForMessaging) in recentPeerList { - if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer._asPeer()) { - recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: nil, strings: strings)) + if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer) { + recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer.associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: nil, strings: strings)) index += 1 } } diff --git a/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m b/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m index 92aeb2c25e..7989e9583e 100644 --- a/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m +++ b/submodules/ShareItems/Impl/Sources/TGItemProviderSignals.m @@ -184,13 +184,13 @@ __unused static CGSize TGFitSize(CGSize size, CGSize maxSize) { if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) { [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:imageOptions completionHandler:^(id _Nullable item, NSError * _Null_unspecified error) { if (error != nil && ![(NSObject *)item respondsToSelector:@selector(CGImage)] && ![(NSObject *)item respondsToSelector:@selector(absoluteString)]) { - [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(UIImage *image, NSError *error) + [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(NSData *data, NSError *error) { if (error != nil) [subscriber putError:nil]; else { - [subscriber putNext:@{@"image": image}]; + [subscriber putNext:@{@"data": data}]; [subscriber putCompletion]; } }]; @@ -263,13 +263,13 @@ __unused static CGSize TGFitSize(CGSize size, CGSize maxSize) { } }]; } else { - [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(UIImage *image, NSError *error) + [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeData options:nil completionHandler:^(NSData *data, NSError *error) { if (error != nil) [subscriber putError:nil]; else { - [subscriber putNext:@{@"image": image}]; + [subscriber putNext:@{@"data": data}]; [subscriber putCompletion]; } }]; diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 0b8208a0d2..689da92243 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -478,6 +478,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net var mediaMessageCount = 0 var consumedText = false + var captionAssigned = false for item in items { switch item { case let .text(text): @@ -493,11 +494,22 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net case let .media(media): switch media { case let .media(reference): + let captionText: String + if !captionAssigned { + if let file = reference.media as? TelegramMediaFile, file.isInstantVideo { + captionText = "" + } else { + captionText = additionalText + captionAssigned = true + } + } else { + captionText = "" + } var message = StandaloneSendEnqueueMessage( content: .arbitraryMedia( media: reference, text: StandaloneSendEnqueueMessage.Text( - string: additionalText, + string: captionText, entities: [] ) ), diff --git a/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift index 7353213b80..9dc246a920 100644 --- a/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift +++ b/submodules/SoftwareVideo/Sources/SoftwareVideoLayerFrameManager.swift @@ -1,246 +1,5 @@ import Foundation -import UIKit -import Postbox -import TelegramCore import SwiftSignalKit -import CoreMedia -import UniversalMediaPlayer -import AVFoundation public let softwareVideoApplyQueue = Queue() public let softwareVideoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2) -private var nextWorker = 0 - -public final class SoftwareVideoLayerFrameManager { - private let fetchDisposable: Disposable - private var dataDisposable = MetaDisposable() - private let source = Atomic(value: nil) - private let hintVP9: Bool - - private var baseTimestamp: Double? - private var frames: [MediaTrackFrame] = [] - private var minPts: CMTime? - private var maxPts: CMTime? - - private let account: Account - private let resource: MediaResource - private let secondaryResource: MediaResource? - private let queue: ThreadPoolQueue - private let layerHolder: SampleBufferLayer? - private weak var layer: AVSampleBufferDisplayLayer? - - private var rotationAngle: CGFloat = 0.0 - private var aspect: CGFloat = 1.0 - - private var layerRotationAngleAndAspect: (CGFloat, CGFloat)? - - private var didStart = false - public var started: () -> Void = { } - - public init(account: Account, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, fileReference: FileMediaReference, layerHolder: SampleBufferLayer?, layer: AVSampleBufferDisplayLayer? = nil, hintVP9: Bool = false) { - var resource = fileReference.media.resource - var secondaryResource: MediaResource? - for attribute in fileReference.media.attributes { - if case .Video = attribute { - if let thumbnail = fileReference.media.videoThumbnails.first { - resource = thumbnail.resource - secondaryResource = fileReference.media.resource - } - } - } - - nextWorker += 1 - self.account = account - self.resource = resource - self.hintVP9 = hintVP9 - self.secondaryResource = secondaryResource - self.queue = ThreadPoolQueue(threadPool: softwareVideoWorkers) - self.layerHolder = layerHolder - self.layer = layer ?? layerHolder?.layer - self.layer?.videoGravity = .resizeAspectFill - self.layer?.masksToBounds = true - self.fetchDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: userLocation, userContentType: userContentType, reference: fileReference.resourceReference(resource)).start() - } - - deinit { - self.fetchDisposable.dispose() - self.dataDisposable.dispose() - } - - public func start() { - func stringForResource(_ resource: MediaResource?) -> String { - guard let resource = resource else { - return "" - } - if let resource = resource as? WebFileReferenceMediaResource { - return resource.url - } else { - return resource.id.stringRepresentation - } - } - Logger.shared.log("SoftwareVideo", "load video from \(stringForResource(self.resource)) or \(stringForResource(self.secondaryResource))") - let secondarySignal: Signal<(String, MediaResource)?, NoError> - if let secondaryResource = self.secondaryResource { - secondarySignal = self.account.postbox.mediaBox.resourceData(secondaryResource, option: .complete(waitUntilFetchStatus: false)) - |> map { data -> (String, MediaResource)? in - if data.complete { - return (data.path, secondaryResource) - } else { - return nil - } - } - } else { - secondarySignal = .single(nil) - } - - let firstResource = self.resource - - let firstReady: Signal<(String, MediaResource), NoError> = combineLatest( - self.account.postbox.mediaBox.resourceData(self.resource, option: .complete(waitUntilFetchStatus: false)), - secondarySignal - ) - |> mapToSignal { first, second -> Signal<(String, MediaResource), NoError> in - if first.complete { - return .single((first.path, firstResource)) - } else if let second = second { - return .single(second) - } else { - return .complete() - } - } - |> take(1) - - self.dataDisposable.set((firstReady - |> deliverOn(softwareVideoApplyQueue)).start(next: { [weak self] path, resource in - if let strongSelf = self { - let size = fileSize(path) - Logger.shared.log("SoftwareVideo", "loaded video from \(stringForResource(resource)) (file size: \(String(describing: size))") - - let _ = strongSelf.source.swap(SoftwareVideoSource(path: path, hintVP9: strongSelf.hintVP9, unpremultiplyAlpha: true)) - } - })) - } - - public func tick(timestamp: Double) { - softwareVideoApplyQueue.async { - if self.baseTimestamp == nil && !self.frames.isEmpty { - self.baseTimestamp = timestamp - } - - if let baseTimestamp = self.baseTimestamp { - var index = 0 - var latestFrameIndex: Int? - while index < self.frames.count { - if baseTimestamp + self.frames[index].position.seconds + self.frames[index].duration.seconds <= timestamp { - latestFrameIndex = index - //print("latestFrameIndex = \(index)") - } - index += 1 - } - if let latestFrameIndex = latestFrameIndex { - let frame = self.frames[latestFrameIndex] - for i in (0 ... latestFrameIndex).reversed() { - self.frames.remove(at: i) - } - if self.layer?.status == .failed { - self.layer?.flush() - } - /*if self.layerRotationAngleAndAspect?.0 != self.rotationAngle || self.layerRotationAngleAndAspect?.1 != self.aspect { - self.layerRotationAngleAndAspect = (self.rotationAngle, self.aspect) - var transform = CGAffineTransform(rotationAngle: CGFloat(self.rotationAngle)) - if !self.rotationAngle.isZero { - transform = transform.scaledBy(x: CGFloat(self.aspect), y: CGFloat(1.0 / self.aspect)) - } - self.layerHolder.layer.setAffineTransform(transform) - }*/ - self.layer?.enqueue(frame.sampleBuffer) - - if !self.didStart { - self.didStart = true - Queue.mainQueue().async { - self.started() - } - } - } - } - - self.poll() - } - } - - private var polling = false - - private func poll() { - if self.frames.count < 2 && !self.polling { - self.polling = true - let minPts = self.minPts - let maxPts = self.maxPts - self.queue.addTask(ThreadPoolTask { [weak self] state in - if state.cancelled.with({ $0 }) { - return - } - if let strongSelf = self { - var frameAndLoop: (MediaTrackFrame?, CGFloat, CGFloat, Bool)? - - var hadLoop = false - for _ in 0 ..< 1 { - frameAndLoop = (strongSelf.source.with { $0 })?.readFrame(maxPts: maxPts) - if let frameAndLoop = frameAndLoop { - if frameAndLoop.0 != nil || minPts != nil { - break - } else { - if frameAndLoop.3 { - hadLoop = true - } - //print("skip nil frame loop: \(frameAndLoop.3)") - } - } else { - break - } - } - if let loop = frameAndLoop?.3, loop { - hadLoop = true - } - - softwareVideoApplyQueue.async { - if let strongSelf = self { - strongSelf.polling = false - if let (_, rotationAngle, aspect, _) = frameAndLoop { - strongSelf.rotationAngle = rotationAngle - strongSelf.aspect = aspect - } - if let frame = frameAndLoop?.0 { - if strongSelf.minPts == nil || CMTimeCompare(strongSelf.minPts!, frame.position) < 0 { - var position = CMTimeAdd(frame.position, frame.duration) - for _ in 0 ..< 1 { - position = CMTimeAdd(position, frame.duration) - } - strongSelf.minPts = position - } - strongSelf.frames.append(frame) - strongSelf.frames.sort(by: { lhs, rhs in - if CMTimeCompare(lhs.position, rhs.position) < 0 { - return true - } else { - return false - } - }) - //print("add frame at \(CMTimeGetSeconds(frame.position))") - //let positions = strongSelf.frames.map { CMTimeGetSeconds($0.position) } - //print("frames: \(positions)") - } else { - //print("not adding frames") - } - if hadLoop { - strongSelf.maxPts = strongSelf.minPts - strongSelf.minPts = nil - //print("loop at \(strongSelf.minPts)") - } - strongSelf.poll() - } - } - } - }) - } - } -} diff --git a/submodules/SparseItemGrid/BUILD b/submodules/SparseItemGrid/BUILD index 302566dbbb..75ad547e78 100644 --- a/submodules/SparseItemGrid/BUILD +++ b/submodules/SparseItemGrid/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/AnimationUI:AnimationUI", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift index 311fdf4d1d..b40d79b9b1 100644 --- a/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift @@ -351,6 +351,8 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { let indicatorSize = self.dateIndicator.update( transition: .immediate, component: AnyComponent(SparseItemGridScrollingIndicatorComponent( + isDark: theme.overallDarkAppearance, + isVisible: true, backgroundColor: theme.list.itemBlocksBackgroundColor, shadowColor: .black, foregroundColor: theme.list.itemPrimaryTextColor, diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift index 4b5eba4437..af2fc0010a 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -6,6 +6,7 @@ import ComponentFlow import SwiftSignalKit import AnimationUI import TelegramPresentationData +import GlassBackgroundComponent public final class MultilineText: Component { public let text: String @@ -709,6 +710,8 @@ public final class RollingText: Component { final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { + let isDark: Bool + let isVisible: Bool let backgroundColor: UIColor let shadowColor: UIColor let foregroundColor: UIColor @@ -716,12 +719,16 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { let previousDate: (String, Int32)? init( + isDark: Bool, + isVisible: Bool, backgroundColor: UIColor, shadowColor: UIColor, foregroundColor: UIColor, date: (String, Int32), previousDate: (String, Int32)? ) { + self.isDark = isDark + self.isVisible = isVisible self.backgroundColor = backgroundColor self.shadowColor = shadowColor self.foregroundColor = foregroundColor @@ -730,6 +737,12 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { } static func ==(lhs: SparseItemGridScrollingIndicatorComponent, rhs: SparseItemGridScrollingIndicatorComponent) -> Bool { + if lhs.isDark != rhs.isDark { + return false + } + if lhs.isVisible != rhs.isVisible { + return false + } if lhs.backgroundColor != rhs.backgroundColor { return false } @@ -749,13 +762,11 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { } static var body: Body { - let rect = Child(ShadowRoundedRectangle.self) + let rect = Child(GlassBackgroundComponent.self) let textMonth = Child(RollingText.self) let textYear = Child(RollingText.self) return { context in - context.view.clipsToBounds = true - let date = context.component.date let components = date.0.components(separatedBy: " ") @@ -804,11 +815,17 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { transition: .immediate ) + let rectSize = CGSize(width: textMonth.size.width + 3.0 + textYear.size.width + 26.0, height: 32.0) let rect = rect.update( - component: ShadowRoundedRectangle( - color: context.component.backgroundColor + component: GlassBackgroundComponent( + size: rectSize, + cornerRadius: rectSize.height * 0.5, + isDark: context.component.isDark, + tintColor: .init(kind: .panel), + isInteractive: false, + isVisible: context.component.isVisible ), - availableSize: CGSize(width: textMonth.size.width + 3.0 + textYear.size.width + 26.0, height: 32.0), + availableSize: rectSize, transition: .easeInOut(duration: 0.2) ) @@ -818,7 +835,7 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { )) context.add(rect - .position(CGPoint(x: rectFrame.midX + shadowInset, y: rectFrame.midY + shadowInset)) + .position(CGPoint(x: rectFrame.midX, y: rectFrame.midY)) ) let offset = CGSize(width: textMonth.size.width + 3.0 + textYear.size.width, height: textMonth.size.height).centered(in: rectFrame) @@ -827,9 +844,11 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { let yearTextFrame = textYear.size.leftCentered(in: rectFrame).offsetBy(dx: offset.minX + monthTextFrame.width + 3.0, dy: 0.0) context.add(textMonth .position(CGPoint(x: monthTextFrame.midX, y: monthTextFrame.midY)) + .opacity(context.component.isVisible ? 1.0 : 0.0) ) context.add(textYear .position(CGPoint(x: yearTextFrame.midX, y: yearTextFrame.midY)) + .opacity(context.component.isVisible ? 1.0 : 0.0) ) return rect.size @@ -1029,7 +1048,6 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { self.dateIndicator = ComponentHostView() self.lineIndicator = ComponentHostView() - self.dateIndicator.alpha = 0.0 self.lineIndicator.alpha = 0.0 super.init() @@ -1159,6 +1177,36 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { private var currentDate: (String, Int32)? private var isScrolling: Bool = false + private var isDateIndicatorVisible = false + + private func requestUpdate(animated: Bool) { + guard let containerSize = self.containerSize else { + return + } + let _ = self.updateIndicator(containerSize: containerSize, animated: animated) + } + + private func updateIndicator(containerSize: CGSize, previousDate: (String, Int32)? = nil, animated: Bool) -> CGSize { + guard let theme = self.theme, let date = self.currentDate else { + return .zero + } + let indicatorSize = self.dateIndicator.update( + transition: animated ? .easeInOut(duration: 0.2) : .immediate, + component: AnyComponent(SparseItemGridScrollingIndicatorComponent( + isDark: theme.overallDarkAppearance, + isVisible: self.isDateIndicatorVisible, + backgroundColor: theme.list.itemBlocksBackgroundColor, + shadowColor: .black, + foregroundColor: theme.list.itemPrimaryTextColor, + date: date, + previousDate: previousDate + )), + environment: {}, + containerSize: containerSize + ) + return indicatorSize + } + public func update( containerSize: CGSize, containerInsets: UIEdgeInsets, @@ -1186,18 +1234,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { } let animateIndicatorFrame = previousDate != nil && previousDate?.1 != date.1 - let indicatorSize = self.dateIndicator.update( - transition: animateIndicatorFrame ? .easeInOut(duration: 0.2) : .immediate, - component: AnyComponent(SparseItemGridScrollingIndicatorComponent( - backgroundColor: theme.list.itemBlocksBackgroundColor, - shadowColor: .black, - foregroundColor: theme.list.itemPrimaryTextColor, - date: date, - previousDate: previousDate - )), - environment: {}, - containerSize: containerSize - ) + let indicatorSize = self.updateIndicator(containerSize: containerSize, previousDate: previousDate, animated: animateIndicatorFrame) let scrollIndicatorHeightFraction = min(1.0, max(0.0, (containerSize.height - containerInsets.top - containerInsets.bottom) / contentHeight)) if scrollIndicatorHeightFraction >= 0.55 - .ulpOfOne { @@ -1244,7 +1281,6 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { if isScrolling { let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0) transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0) } @@ -1284,10 +1320,13 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { private func updateActivityTimer(isScrolling: Bool) { self.activityTimer?.invalidate() + + if isScrolling || self.isDragging { + self.isDateIndicatorVisible = true + } if self.isDragging { let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0) transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0) } else { self.activityTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in @@ -1295,9 +1334,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { return } let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0) transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0) + strongSelf.isDateIndicatorVisible = false + strongSelf.requestUpdate(animated: true) + strongSelf.dismissLineTooltip() }, queue: .mainQueue()) self.activityTimer?.start() @@ -1372,7 +1413,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.dateIndicator.alpha <= 0.01 { + if self.dateIndicator.alpha <= 0.01 || !self.isDateIndicatorVisible { return nil } if self.dateIndicator.frame.offsetBy(dx: self.dateIndicatorContainer.frame.minX, dy: self.dateIndicatorContainer.frame.minY).contains(point) { @@ -1391,9 +1432,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { public func hideScroller() { let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 0.0) transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 0.0) + self.isDateIndicatorVisible = false + self.requestUpdate(animated: true) + self.dismissLineTooltip() } } diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index f470163bb9..0fa30c747c 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -205,7 +205,7 @@ private enum StatsEntry: ItemListNodeEntry { case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case postsTitle(PresentationTheme, String) - case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, StatsPostItem, ChannelStatsPostInteractions) + case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, StatsPostItem, ChannelStatsPostInteractions) case boostLevel(PresentationTheme, Int32, Int32, CGFloat) @@ -636,7 +636,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .post(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsPost, lhsInteractions): - if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsPost, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsPost == rhsPost, lhsInteractions == rhsInteractions { + if case let .post(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsPost, rhsInteractions) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsPost == rhsPost, lhsInteractions == rhsInteractions { return true } else { return false @@ -960,7 +960,7 @@ private enum StatsEntry: ItemListNodeEntry { }, sectionId: self.section, style: .blocks) case let .post(_, _, _, _, peer, post, interactions): return StatsMessageItem(context: arguments.context, presentationData: presentationData, peer: peer, item: post, views: interactions.views, reactions: interactions.reactions, forwards: interactions.forwards, sectionId: self.section, style: .blocks, action: { - arguments.openPostStats(EnginePeer(peer), post) + arguments.openPostStats(peer, post) }, openStory: { sourceView in if case let .story(_, story) = post { arguments.openStory(story, sourceView) @@ -1426,11 +1426,11 @@ private func statsEntries( switch post { case let .message(message): if let interactions = interactions[.message(id: message.id)] { - entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), post, interactions)) + entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, post, interactions)) } case let .story(_, story): if let interactions = interactions[.story(peerId: peer.id, id: story.id)] { - entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), post, interactions)) + entries.append(.post(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, post, interactions)) } } index += 1 diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 7109e9d382..a868f71de3 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -826,7 +826,15 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let previousData = Atomic(value: nil) let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - let signal = combineLatest(statePromise.get(), presentationData, dataPromise.get(), context.account.postbox.loadedPeerWithId(peerId), peersPromise.get(), longLoadingSignal) + let loadedChannelPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + let signal = combineLatest(statePromise.get(), presentationData, dataPromise.get(), loadedChannelPeer, peersPromise.get(), longLoadingSignal) |> deliverOnMainQueue |> map { state, presentationData, data, channelPeer, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in let previous = previousData.swap(data) @@ -838,9 +846,9 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } } - + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: EnginePeer(channelPeer), peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: channelPeer, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -862,7 +870,14 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } openPeerImpl = { [weak controller] peer in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (context.account.postbox.loadedPeerWithId(peer.id) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { @@ -887,7 +902,14 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } openPeerAdminActionsImpl = { [weak controller] participantPeerId in if let navigationController = controller?.navigationController as? NavigationController { - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { peer in let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId, starsState: nil) diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index a1d3cf316b..cca0be9acb 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -3,7 +3,6 @@ import UIKit import Display import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -36,6 +35,7 @@ private final class MessageStatsControllerArguments { private enum StatsSection: Int32 { case overview + case votes case interactions case reactions case publicForwards @@ -45,6 +45,9 @@ private enum StatsEntry: ItemListNodeEntry { case overviewTitle(PresentationTheme, String) case overview(PresentationTheme, PostStats, EngineStoryItem.Views?, Int32?) + case votesTitle(PresentationTheme, String) + case votesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType, Bool) + case interactionsTitle(PresentationTheme, String) case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType, Bool) @@ -58,6 +61,8 @@ private enum StatsEntry: ItemListNodeEntry { switch self { case .overviewTitle, .overview: return StatsSection.overview.rawValue + case .votesTitle, .votesGraph: + return StatsSection.votes.rawValue case .interactionsTitle, .interactionsGraph: return StatsSection.interactions.rawValue case .reactionsTitle, .reactionsGraph: @@ -73,18 +78,22 @@ private enum StatsEntry: ItemListNodeEntry { return 0 case .overview: return 1 - case .interactionsTitle: + case .votesTitle: return 2 - case .interactionsGraph: + case .votesGraph: return 3 - case .reactionsTitle: + case .interactionsTitle: return 4 - case .reactionsGraph: + case .interactionsGraph: return 5 - case .publicForwardsTitle: + case .reactionsTitle: return 6 + case .reactionsGraph: + return 7 + case .publicForwardsTitle: + return 8 case let .publicForward(index, _, _, _, _): - return 7 + index + return 9 + index } } @@ -108,6 +117,18 @@ private enum StatsEntry: ItemListNodeEntry { } else { return false } + case let .votesTitle(lhsTheme, lhsText): + if case let .votesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } + case let .votesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType, lhsNoInitialZoom): + if case let .votesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType, rhsNoInitialZoom) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType, lhsNoInitialZoom == rhsNoInitialZoom { + return true + } else { + return false + } case let .interactionsTitle(lhsTheme, lhsText): if case let .interactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -155,13 +176,14 @@ private enum StatsEntry: ItemListNodeEntry { let arguments = arguments as! MessageStatsControllerArguments switch self { case let .overviewTitle(_, text), + let .votesTitle(_, text), let .interactionsTitle(_, text), let .reactionsTitle(_, text), let .publicForwardsTitle(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .overview(_, stats, storyViews, publicShares): return StatsOverviewItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, isGroup: false, stats: stats as? Stats, storyViews: storyViews, publicShares: publicShares, sectionId: self.section, style: .blocks) - case let .interactionsGraph(_, _, _, graph, type, noInitialZoom), let .reactionsGraph(_, _, _, graph, type, noInitialZoom): + case let .votesGraph(_, _, _, graph, type, noInitialZoom), let .interactionsGraph(_, _, _, graph, type, noInitialZoom), let .reactionsGraph(_, _, _, graph, type, noInitialZoom): return StatsGraphItem(presentationData: presentationData, systemStyle: .glass, graph: graph, type: type, noInitialZoom: noInitialZoom, getDetailsData: { date, completion in let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in if let graph = graph, case let .Loaded(_, data) = graph { @@ -175,10 +197,10 @@ private enum StatsEntry: ItemListNodeEntry { var reactions: Int32 = 0 var isStory = false - let peer: Peer + let peer: EnginePeer switch item { case let .message(message): - peer = message.peers[message.id.peerId]! + peer = EnginePeer(message.peers[message.id.peerId]!) for attribute in message.attributes { if let viewsAttribute = attribute as? ViewCountMessageAttribute { views = Int32(viewsAttribute.count) @@ -191,7 +213,7 @@ private enum StatsEntry: ItemListNodeEntry { } } case let .story(peerValue, story): - peer = peerValue._asPeer() + peer = peerValue views = Int32(story.views?.seenCount ?? 0) forwards = Int32(story.views?.forwardCount ?? 0) reactions = Int32(story.views?.reactedCount ?? 0) @@ -215,7 +237,7 @@ private enum StatsEntry: ItemListNodeEntry { } } -private func messageStatsControllerEntries(data: PostStats?, storyViews: EngineStoryItem.Views?, forwards: StoryStatsPublicForwardsContext.State?, presentationData: PresentationData) -> [StatsEntry] { +private func messageStatsControllerEntries(data: PostStats?, pollData: PollStats?, storyViews: EngineStoryItem.Views?, forwards: StoryStatsPublicForwardsContext.State?, presentationData: PresentationData) -> [StatsEntry] { var entries: [StatsEntry] = [] if let data = data { @@ -227,6 +249,11 @@ private func messageStatsControllerEntries(data: PostStats?, storyViews: EngineS } entries.append(.overview(presentationData.theme, data, storyViews, publicShares)) + if let pollData, !pollData.votesGraph.isEmpty { + entries.append(.votesTitle(presentationData.theme, presentationData.strings.PollStats_GraphHeader.uppercased())) + entries.append(.votesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, pollData.votesGraph, .lines5Min, false)) + } + var isStories = false if let _ = data as? StoryStats { isStories = true @@ -295,8 +322,10 @@ public func messageStatsController(context: AccountContext, updatedPresentationD let actionsDisposable = DisposableSet() let dataPromise = Promise(nil) let forwardsPromise = Promise(nil) + let pollDataPromise = Promise(nil) let anyStatsContext: Any + var pollStatsContext: Any? let dataSignal: Signal var loadDetailedGraphImpl: ((StatsGraph, Int64) -> Signal)? var openStoryImpl: ((EnginePeer.Id, EngineStoryItem, UIView) -> Void)? @@ -320,6 +349,31 @@ public func messageStatsController(context: AccountContext, updatedPresentationD anyStatsContext = statsContext forwardsContext = StoryStatsPublicForwardsContext(account: context.account, subject: .message(messageId: id)) + + pollDataPromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id)) + |> map { message -> PollStatsContext? in + guard let message else { + return nil + } + for media in message.media { + if let poll = media as? TelegramMediaPoll, poll.results.canViewStats { + return PollStatsContext(account: context.account, messageId: message.id) + } + } + return nil + } + |> afterNext { statsContext in + pollStatsContext = statsContext + } + |> mapToSignal { pollStatsContext in + guard let pollStatsContext else { + return .single(nil) + } + return pollStatsContext.state + |> map { state in + return state.stats + } + }) case let .story(peerIdValue, id, item, _): peerId = peerIdValue storyItem = item @@ -338,13 +392,11 @@ public func messageStatsController(context: AccountContext, updatedPresentationD forwardsContext = StoryStatsPublicForwardsContext(account: context.account, subject: .story(peerId: peerId, id: id)) } - forwardsPromise.set( - .single(nil) - |> then( - forwardsContext.state - |> map(Optional.init) - ) - ) + forwardsPromise.set(.single(nil) + |> then( + forwardsContext.state + |> map(Optional.init) + )) let arguments = MessageStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal in return loadDetailedGraphImpl?(graph, x) ?? .single(nil) @@ -384,12 +436,13 @@ public func messageStatsController(context: AccountContext, updatedPresentationD let signal = combineLatest( presentationData, dataPromise.get(), + pollDataPromise.get(), forwardsPromise.get(), longLoadingSignal, iconNodePromise.get() ) |> deliverOnMainQueue - |> map { presentationData, data, forwards, longLoading, iconNode -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, data, pollData, forwards, longLoading, iconNode -> (ItemListControllerState, (ItemListNodeState, Any)) in let previous = previousData.swap(data) var emptyStateItem: ItemListControllerEmptyStateItem? if data == nil { @@ -415,13 +468,14 @@ public func messageStatsController(context: AccountContext, updatedPresentationD openStoryImpl?(peerId, storyItem, iconNode.view) } }) }, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: messageStatsControllerEntries(data: data, storyViews: storyViews, forwards: forwards, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: messageStatsControllerEntries(data: data, pollData: pollData, storyViews: storyViews, forwards: forwards, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() let _ = anyStatsContext + let _ = pollStatsContext let _ = forwardsContext } @@ -455,7 +509,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD }) } openStoryImpl = { [weak controller] peerId, story, sourceView in - let storyContent = SingleStoryContentContextImpl(context: context, storyId: StoryId(peerId: peerId, id: story.id), storyItem: story, readGlobally: false) + let storyContent = SingleStoryContentContextImpl(context: context, storyId: EngineStoryId(peerId: peerId, id: story.id), storyItem: story, readGlobally: false) let _ = (storyContent.state |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak controller, weak sourceView] _ in @@ -546,7 +600,7 @@ public func messageStatsController(context: AccountContext, updatedPresentationD return } if case .user = peer { - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller) } } else { diff --git a/submodules/StatisticsUI/Sources/StatsGraphItem.swift b/submodules/StatisticsUI/Sources/StatsGraphItem.swift index 8fb221d919..0209f6637b 100644 --- a/submodules/StatisticsUI/Sources/StatsGraphItem.swift +++ b/submodules/StatisticsUI/Sources/StatsGraphItem.swift @@ -109,6 +109,7 @@ public final class StatsGraphItemNode: ListViewItemNode { let chartNode: ChartNode private let activityIndicator: ActivityIndicator + private let errorTextNode: TextNode private var item: StatsGraphItem? private var visibilityHeight: CGFloat? @@ -134,11 +135,15 @@ public final class StatsGraphItemNode: ListViewItemNode { self.chartNode = ChartNode() self.activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(.black, 16.0, 2.0, false)) self.activityIndicator.isHidden = true + self.errorTextNode = TextNode() + self.errorTextNode.isUserInteractionEnabled = false + self.errorTextNode.isHidden = true super.init(layerBacked: false) self.chartContainerNode.addSubnode(self.chartNode) self.chartContainerNode.addSubnode(self.activityIndicator) + self.chartContainerNode.addSubnode(self.errorTextNode) } public override func didLoad() { @@ -154,12 +159,14 @@ public final class StatsGraphItemNode: ListViewItemNode { } func asyncLayout() -> (_ item: StatsGraphItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeErrorTextLayout = TextNode.asyncLayout(self.errorTextNode) let currentItem = self.item let currentVisibilityHeight = self.visibilityHeight return { item, params, neighbors in let leftInset = params.leftInset let rightInset: CGFloat = params.rightInset + let errorTextFont = Font.regular(item.presentationData.fontSize.itemListBaseLabelFontSize / 14.0 * 16.0) var updatedTheme: PresentationTheme? var updatedGraph: StatsGraph? var updatedController: BaseChartController? @@ -197,6 +204,27 @@ public final class StatsGraphItemNode: ListViewItemNode { contentSize = CGSize(width: params.width, height: 361.0) insets = itemListNeighborsGroupedInsets(neighbors, params) } + + let errorText: String + if case let .Failed(text) = item.graph { + errorText = text + } else { + errorText = "" + } + let (errorTextLayout, errorTextApply) = makeErrorTextLayout(TextNodeLayoutArguments( + attributedString: NSAttributedString( + string: errorText, + font: errorTextFont, + textColor: item.presentationData.theme.list.itemSecondaryTextColor + ), + backgroundColor: nil, + maximumNumberOfLines: 0, + truncationType: .end, + constrainedSize: CGSize(width: max(1.0, params.width - leftInset - rightInset - 32.0), height: contentSize.height), + alignment: .center, + cutout: nil, + insets: UIEdgeInsets() + )) var visibilityHeight = currentVisibilityHeight if let updatedController = updatedController { @@ -292,9 +320,17 @@ public final class StatsGraphItemNode: ListViewItemNode { strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) - strongSelf.activityIndicator.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - 16.0) / 2.0), y: floor((layout.size.height - 16.0) / 2.0)), size: CGSize(width: 16.0, height: 16.0)) + strongSelf.activityIndicator.frame = CGRect(origin: CGPoint(x: floor((strongSelf.chartContainerNode.frame.width - 16.0) / 2.0), y: floor((strongSelf.chartContainerNode.frame.height - 16.0) / 2.0)), size: CGSize(width: 16.0, height: 16.0)) + strongSelf.errorTextNode.frame = CGRect( + origin: CGPoint( + x: floorToScreenPixels((strongSelf.chartContainerNode.bounds.width - errorTextLayout.size.width) / 2.0), + y: floorToScreenPixels((strongSelf.chartContainerNode.bounds.height - errorTextLayout.size.height) / 2.0) + ), + size: errorTextLayout.size + ) } + let _ = errorTextApply() strongSelf.activityIndicator.type = .custom(item.presentationData.theme.list.itemSecondaryTextColor, 16.0, 2.0, false) if let updatedTheme = updatedTheme { @@ -310,15 +346,27 @@ public final class StatsGraphItemNode: ListViewItemNode { ) } - if let updatedGraph = updatedGraph { - if case .Loaded = updatedGraph, let updatedController = updatedController { - strongSelf.chartNode.setup(controller: updatedController, noInitialZoom: item.noInitialZoom) - strongSelf.activityIndicator.isHidden = true - strongSelf.chartNode.isHidden = false - } else if case .OnDemand = updatedGraph { - strongSelf.activityIndicator.isHidden = false - strongSelf.chartNode.isHidden = true - } + if let updatedGraph = updatedGraph, case .Loaded = updatedGraph, let updatedController = updatedController { + strongSelf.chartNode.setup(controller: updatedController, noInitialZoom: item.noInitialZoom) + } + + switch item.graph { + case .Loaded: + strongSelf.activityIndicator.isHidden = true + strongSelf.chartNode.isHidden = false + strongSelf.errorTextNode.isHidden = true + case .OnDemand: + strongSelf.activityIndicator.isHidden = false + strongSelf.chartNode.isHidden = true + strongSelf.errorTextNode.isHidden = true + case let .Failed(error): + strongSelf.activityIndicator.isHidden = true + strongSelf.chartNode.isHidden = true + strongSelf.errorTextNode.isHidden = error.isEmpty + case .Empty: + strongSelf.activityIndicator.isHidden = true + strongSelf.chartNode.isHidden = true + strongSelf.errorTextNode.isHidden = true } } }) @@ -337,4 +385,3 @@ public final class StatsGraphItemNode: ListViewItemNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } } - diff --git a/submodules/StatisticsUI/Sources/StatsMessageItem.swift b/submodules/StatisticsUI/Sources/StatsMessageItem.swift index b7aa9dc8fb..b3aee6ff57 100644 --- a/submodules/StatisticsUI/Sources/StatsMessageItem.swift +++ b/submodules/StatisticsUI/Sources/StatsMessageItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -19,7 +18,7 @@ public class StatsMessageItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData let systemStyle: ItemListSystemStyle - let peer: Peer + let peer: EnginePeer let item: StatsPostItem let views: Int32 let reactions: Int32 @@ -31,7 +30,7 @@ public class StatsMessageItem: ListViewItem, ItemListItem { let openStory: (UIView) -> Void let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? - init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .glass, peer: Peer, item: StatsPostItem, views: Int32, reactions: Int32, forwards: Int32, isPeer: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, openStory: @escaping (UIView) -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) { + init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .glass, peer: EnginePeer, item: StatsPostItem, views: Int32, reactions: Int32, forwards: Int32, isPeer: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, openStory: @escaping (UIView) -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?) { self.context = context self.presentationData = presentationData self.systemStyle = systemStyle @@ -124,7 +123,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { private let activateArea: AccessibilityAreaNode private var item: StatsMessageItem? - private var contentImageMedia: Media? + private var contentImageMedia: EngineRawMedia? override public var canBeSelected: Bool { return true @@ -307,7 +306,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } var text: String - var contentImageMedia: Media? + var contentImageMedia: EngineRawMedia? let timestamp: Int32 switch item.item { @@ -351,7 +350,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { } if item.isPeer { - text = EnginePeer(item.peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + text = item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) } else { text = foldLineBreaks(text) } @@ -476,7 +475,7 @@ final class StatsMessageItemNode: ListViewItemNode, ItemListItemNode { strongSelf.offsetContainerNode.addSubnode(avatarNode) strongSelf.avatarNode = avatarNode } - avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer)) + avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer) if case .story = item.item { contentImageInset += 3.0 diff --git a/submodules/StatisticsUI/Sources/StoryIconNode.swift b/submodules/StatisticsUI/Sources/StoryIconNode.swift index 7fec70ab34..7246e29bc6 100644 --- a/submodules/StatisticsUI/Sources/StoryIconNode.swift +++ b/submodules/StatisticsUI/Sources/StoryIconNode.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import PhotoResources import AvatarStoryIndicatorComponent @@ -15,7 +14,7 @@ final class StoryIconNode: ASDisplayNode { private let imageNode = TransformImageNode() private let storyIndicator = ComponentView() - init(context: AccountContext, theme: PresentationTheme, peer: Peer, storyItem: EngineStoryItem) { + init(context: AccountContext, theme: PresentationTheme, peer: EngineRawPeer, storyItem: EngineStoryItem) { self.imageNode.displaysAsynchronously = false super.init() @@ -29,7 +28,7 @@ final class StoryIconNode: ASDisplayNode { self.imageNode.frame = bounds.insetBy(dx: 3.0, dy: 3.0) self.frame = bounds - let media: Media? + let media: EngineRawMedia? switch storyItem.media { case let .image(image): media = image diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 67a5eab359..32e8851557 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 62b02b164a..514091663d 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import StickerResources import AccountContext import AnimatedStickerNode diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index 2ff682e76c..5fbf1ed03e 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import StickerResources import AccountContext import AnimatedStickerNode @@ -16,16 +15,16 @@ import TextFormat final class StickerPackPreviewInteraction { var previewedItem: StickerPreviewPeekItem? - var reorderingFileId: MediaId? + var reorderingFileId: EngineMedia.Id? var playAnimatedStickers: Bool let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void let removeStickerPack: (StickerPackCollectionInfo) -> Void let emojiSelected: (String, ChatTextInputTextCustomEmojiAttribute) -> Void let emojiLongPressed: (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void - let addPressed: () -> Void + let addPressed: (UIView) -> Void - init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void, addPressed: @escaping () -> Void) { + init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void, addPressed: @escaping (UIView) -> Void) { self.playAnimatedStickers = playAnimatedStickers self.addStickerPack = addStickerPack self.removeStickerPack = removeStickerPack @@ -129,7 +128,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.containerNode = ASDisplayNode() self.imageNode = TransformImageNode() - self.imageNode.isLayerBacked = !smartInvertColorsEnabled() self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode.isUserInteractionEnabled = false @@ -195,7 +193,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { } @objc private func handleAddTap() { - self.interaction?.addPressed() + self.interaction?.addPressed(self.imageNode.view) } private var setupTimestamp: Double? diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index ac6179b47e..8aeb09cc88 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -270,7 +269,7 @@ private final class StickerPackContainer: ASDisplayNode { var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)? var emojiSelectedImpl: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? var emojiLongPressedImpl: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)? - var addPressedImpl: (() -> Void)? + var addPressedImpl: ((UIView) -> Void)? self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in addStickerPackImpl?(info, items) }, removeStickerPack: { info in @@ -279,8 +278,8 @@ private final class StickerPackContainer: ASDisplayNode { emojiSelectedImpl?(text, attribute) }, emojiLongPressed: { text, attribute, node, frame in emojiLongPressedImpl?(text, attribute, node, frame) - }, addPressed: { - addPressedImpl?() + }, addPressed: { sourceView in + addPressedImpl?(sourceView) }) super.init() @@ -327,33 +326,39 @@ private final class StickerPackContainer: ASDisplayNode { return targetOffset } - let insets = strongSelf.gridNode.scrollView.contentInset + guard let (layout, _, titleAreaInset, gridInsets) = strongSelf.validLayout else { + return targetOffset + } + + let collapsedOffset = -gridInsets.top + let expandedOffset = strongSelf.expandedContentOffset(layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets) var modalProgress: CGFloat = 0.0 var updatedOffset = targetOffset var resetOffset = false - if targetOffset.y < 0.0 && targetOffset.y >= -insets.top { - if contentOffset.y > 0.0 { - updatedOffset = CGPoint(x: 0.0, y: 0.0) + if targetOffset.y < expandedOffset && targetOffset.y >= collapsedOffset { + if contentOffset.y > expandedOffset { + updatedOffset = CGPoint(x: 0.0, y: expandedOffset) modalProgress = 1.0 } else { - if targetOffset.y > -insets.top / 2.0 || velocity.y <= -100.0 { + let middleOffset = collapsedOffset + (expandedOffset - collapsedOffset) / 2.0 + if targetOffset.y > middleOffset || velocity.y <= -100.0 { modalProgress = 1.0 resetOffset = true } else { modalProgress = 0.0 - if contentOffset.y > -insets.top { + if contentOffset.y > collapsedOffset { resetOffset = true } } } - } else if targetOffset.y >= 0.0 { + } else if targetOffset.y >= expandedOffset { modalProgress = 1.0 } if abs(strongSelf.modalProgress - modalProgress) > CGFloat.ulpOfOne { - if contentOffset.y > 0.0 && targetOffset.y > 0.0 { + if contentOffset.y > expandedOffset && targetOffset.y > expandedOffset { } else { resetOffset = true } @@ -365,10 +370,10 @@ private final class StickerPackContainer: ASDisplayNode { let offset: CGPoint let isVelocityAligned: Bool if modalProgress.isZero { - offset = CGPoint(x: 0.0, y: -insets.top) + offset = CGPoint(x: 0.0, y: collapsedOffset) isVelocityAligned = velocity.y < 0.0 } else { - offset = CGPoint(x: 0.0, y: 0.0) + offset = CGPoint(x: 0.0, y: expandedOffset) isVelocityAligned = velocity.y > 0.0 } @@ -460,8 +465,8 @@ private final class StickerPackContainer: ASDisplayNode { longPressEmoji?(text, attribute, node, frame) } - addPressedImpl = { [weak self] in - self?.presentAddStickerOptions() + addPressedImpl = { [weak self] sourceView in + self?.presentAddStickerOptions(sourceView: sourceView) } } @@ -802,19 +807,19 @@ private final class StickerPackContainer: ASDisplayNode { private func emojiSuggestionPeekContent(itemLayer: CALayer, file: TelegramMediaFile) -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError> { let context = self.context - var collectionId: ItemCollectionId? + var collectionId: EngineItemCollectionId? for attribute in file.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute { switch packReference { case let .id(id, _): - collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + collectionId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) default: break } } } - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] if let collectionId { bubbleUpEmojiOrStickersets.append(collectionId) } @@ -859,9 +864,9 @@ private final class StickerPackContainer: ASDisplayNode { case let .CustomEmoji(_, _, displayText, stickerPackReference): text = displayText - var packId: ItemCollectionId? + var packId: EngineItemCollectionId? if case let .id(id, _) = stickerPackReference { - packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + packId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) } emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file) break loop @@ -1210,33 +1215,44 @@ private final class StickerPackContainer: ASDisplayNode { } private let stickerPickerInputData = Promise() - private func presentAddStickerOptions() { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - var items: [ActionSheetItem] = [] - items.append(ActionSheetButtonItem(title: self.presentationData.strings.StickerPack_CreateNew, color: .accent, action: { [weak actionSheet, weak self] in - actionSheet?.dismissAnimated() - + private func presentAddStickerOptions(sourceView: UIView) { + guard let controller = self.controller else { + return + } + + let presentationData = self.presentationData + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: presentationData.strings.StickerPack_CreateNew, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + guard let self, let controller = self.controller else { return } self.presentCreateSticker() controller.controllerNode.dismiss() - })) - items.append(ActionSheetButtonItem(title: self.presentationData.strings.StickerPack_AddExisting, color: .accent, action: { [weak actionSheet, weak self] in - actionSheet?.dismissAnimated() + }))) + items.append(.action(ContextMenuActionItem(text: presentationData.strings.StickerPack_AddExisting, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSticker"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) guard let self, let controller = self.controller else { return } self.presentAddExistingSticker() controller.controllerNode.dismiss() - })) - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - self.presentInGlobalOverlay(actionSheet, nil) + }))) + + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(StickerPackContextReferenceContentSource(controller: controller, sourceView: sourceView)), + items: .single(ContextController.Items(content: .list(items))), + recognizer: nil, + gesture: nil + ) + self.presentInGlobalOverlay(contextController, nil) let stickerItems = EmojiPagerContentComponent.stickerInputData( context: self.context, @@ -1468,12 +1484,12 @@ private final class StickerPackContainer: ASDisplayNode { } if installedCount == self.currentStickerPacks.count { - var removedPacks: Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> = .single([]) + var removedPacks: Signal<[(info: EngineRawItemCollectionInfo, index: Int, items: [EngineRawItemCollectionItem])], NoError> = .single([]) for (info, _, _) in self.currentStickerPacks { removedPacks = removedPacks - |> mapToSignal { current -> Signal<[(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])], NoError> in + |> mapToSignal { current -> Signal<[(info: EngineRawItemCollectionInfo, index: Int, items: [EngineRawItemCollectionItem])], NoError> in return self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) - |> map { result -> [(info: ItemCollectionInfo, index: Int, items: [ItemCollectionItem])] in + |> map { result -> [(info: EngineRawItemCollectionInfo, index: Int, items: [EngineRawItemCollectionItem])] in if let result = result { return current + [(info, result.0, result.1)] } else { @@ -1517,13 +1533,20 @@ private final class StickerPackContainer: ASDisplayNode { if let (info, items, installed) = self.currentStickerPack { var dismissed = false switch self.decideNextAction(self, installed ? .remove : .add) { - case .dismiss: - self.requestDismiss() - dismissed = true - case .navigatedNext, .ignored: - self.updateStickerPackContents([.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: !installed)], hasPremium: false) + case .dismiss: + self.requestDismiss() + dismissed = true + case .navigatedNext, .ignored: + self.updateStickerPackContents([.result(info: StickerPackCollectionInfo.Accessor(info), items: items, installed: !installed)], hasPremium: false) } + guard let controller = self.controller else { + return + } + let navigationController = controller.parentNavigationController ?? (controller.navigationController as? NavigationController) + let context = self.context + let strings = self.presentationData.strings + let actionPerformed = self.controller?.actionPerformed if installed { let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) @@ -1536,7 +1559,19 @@ private final class StickerPackContainer: ASDisplayNode { } }) } else { - let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start() + let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items, noDelay: true).startStandalone() + let _ = (self.context.account.stateManager.installedStickerPacksArchivedEvents + |> deliverOnMainQueue + |> take(1) + |> timeout(3.0, queue: .mainQueue(), alternate: .single(0))).startStandalone(next: { [weak navigationController] count in + if count == 0 { + return + } + guard let navigationController else { + return + } + navigationController.pushViewController(textAlertController(context: context, updatedPresentationData: controller.updatedPresentationData, title: nil, text: strings.ArchivedPacksAlert_Title, actions: [TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {})])) + }) if dismissed { actionPerformed?([(info, items, .add)]) } @@ -1580,8 +1615,8 @@ private final class StickerPackContainer: ASDisplayNode { self.onReady() if !contents.isEmpty && self.currentStickerPacks.isEmpty { - if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false) + if let (layout, _, titleAreaInset, gridInsets) = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne { + scrollToItem = GridNodeScrollToItem(index: 0, position: .top(self.expandedContentOffset(layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets)), transition: .immediate, directionHint: .up, adjustForSection: false) } } @@ -1653,8 +1688,8 @@ private final class StickerPackContainer: ASDisplayNode { let info = info._parse() if !items.isEmpty && self.currentStickerPack == nil { - if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne { - scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false) + if let (layout, _, titleAreaInset, gridInsets) = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne { + scrollToItem = GridNodeScrollToItem(index: 0, position: .top(self.expandedContentOffset(layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets)), transition: .immediate, directionHint: .up, adjustForSection: false) } } @@ -1857,12 +1892,37 @@ private final class StickerPackContainer: ASDisplayNode { return min(self.backgroundNode.frame.minY, gridFrame.minY + gridInsets.top - titleAreaInset) } + private func expandedContentOffset(layout: ContainerViewLayout, titleAreaInset: CGFloat, gridInsets: UIEdgeInsets) -> CGFloat { + if case .regular = layout.metrics.widthClass { + return 0.0 + } else { + return max(-gridInsets.top, -titleAreaInset) + } + } + + private func expandProgress(contentOffset: CGFloat, layout: ContainerViewLayout, titleAreaInset: CGFloat, gridInsets: UIEdgeInsets) -> CGFloat { + let collapsedOffset = -gridInsets.top + let expandedOffset = self.expandedContentOffset(layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets) + let distance = expandedOffset - collapsedOffset + if distance <= CGFloat.ulpOfOne { + return 1.0 + } + return max(0.0, min(1.0, (contentOffset - collapsedOffset) / distance)) + } + + private func contentOffsetForExpandProgress(_ expandProgress: CGFloat, layout: ContainerViewLayout, titleAreaInset: CGFloat, gridInsets: UIEdgeInsets) -> CGFloat { + let collapsedOffset = -gridInsets.top + let expandedOffset = self.expandedContentOffset(layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets) + let progress = max(0.0, min(1.0, expandProgress)) + return collapsedOffset + (expandedOffset - collapsedOffset) * progress + } + func syncExpandProgress(expandScrollProgress: CGFloat, expandProgress: CGFloat, modalProgress: CGFloat, transition: ContainedViewLayoutTransition) { - guard let (_, _, _, gridInsets) = self.validLayout else { + guard let (layout, _, titleAreaInset, gridInsets) = self.validLayout else { return } - let contentOffset = (1.0 - expandScrollProgress) * (-gridInsets.top) + let contentOffset = self.contentOffsetForExpandProgress(expandScrollProgress, layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets) if case let .animated(duration, _) = transition { self.gridNode.autoscroll(toOffset: CGPoint(x: 0.0, y: contentOffset), duration: duration) } else { @@ -2134,11 +2194,11 @@ private final class StickerPackContainer: ASDisplayNode { let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 90.0)) transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) - self.topEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + self.topEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.9, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: actionAreaHeight - 90.0), size: CGSize(width: layout.size.width, height: 90.0)) transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) - self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) + self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.9, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) if firstTime { while !self.enqueuedTransactions.isEmpty { @@ -2160,10 +2220,8 @@ private final class StickerPackContainer: ASDisplayNode { let minBackgroundY = gridFrame.minY let unclippedBackgroundY = gridFrame.minY - presentationLayout.contentOffset.y - titleAreaInset - let offsetFromInitialPosition = presentationLayout.contentOffset.y + gridInsets.top - let expandHeight: CGFloat = 100.0 - let expandProgress = max(0.0, min(1.0, offsetFromInitialPosition / expandHeight)) - let expandScrollProgress = 1.0 - max(0.0, min(1.0, presentationLayout.contentOffset.y / (-gridInsets.top))) + let expandProgress = self.expandProgress(contentOffset: presentationLayout.contentOffset.y, layout: layout, titleAreaInset: titleAreaInset, gridInsets: gridInsets) + let expandScrollProgress = expandProgress let modalProgress = max(0.0, min(1.0, expandScrollProgress)) let expandProgressTransition = transition @@ -2949,13 +3007,6 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { } return .single(result) } - |> mapToSignal { peer -> Signal in - if let peer = peer { - return .single(peer._asPeer()) - } else { - return .single(nil) - } - } |> deliverOnMainQueue).start(next: { peer in guard let strongSelf = self else { return @@ -2963,7 +3014,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { if let peer { if let parentNavigationController = strongSelf.parentNavigationController { strongSelf.controllerNode.dismiss() - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .always, animated: true)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .always, animated: true)) } } else { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewController.swift index 9d39bd02fa..b4c6928544 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewController.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPreviewControllerNode.swift index 526ea1281c..f0cda8dd1c 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPreviewControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/StickerPeekUI/BUILD b/submodules/StickerPeekUI/BUILD index 33f9618a5b..de35047057 100644 --- a/submodules/StickerPeekUI/BUILD +++ b/submodules/StickerPeekUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index 749f133e9b..0d8028d905 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import StickerResources diff --git a/submodules/StickerResources/Sources/StickerResources.swift b/submodules/StickerResources/Sources/StickerResources.swift index 229dbc7f6b..d435924b58 100644 --- a/submodules/StickerResources/Sources/StickerResources.swift +++ b/submodules/StickerResources/Sources/StickerResources.swift @@ -260,74 +260,6 @@ public func chatMessageAnimatedStickerBackingData(postbox: Postbox, fileReferenc } } -public func chatMessageLegacySticker(account: Account, userLocation: MediaResourceUserLocation, file: TelegramMediaFile, small: Bool, fitSize: CGSize, fetched: Bool = false, onlyFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMessageStickerDatas(postbox: account.postbox, userLocation: userLocation, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: false) - return signal |> map { value in - let fullSizeData = value._1 - let fullSizeComplete = value._2 - return { preArguments in - var fullSizeImage: (UIImage, UIImage)? - if let fullSizeData = fullSizeData, fullSizeComplete { - if let image = imageFromAJpeg(data: fullSizeData) { - fullSizeImage = image - } - } - - if let fullSizeImage = fullSizeImage { - var updatedFitSize = fitSize - if updatedFitSize.width.isEqual(to: 1.0) { - updatedFitSize = fullSizeImage.0.size - } - - let contextSize = fullSizeImage.0.size.aspectFitted(updatedFitSize) - - let arguments = TransformImageArguments(corners: preArguments.corners, imageSize: contextSize, boundingSize: contextSize, intrinsicInsets: preArguments.intrinsicInsets) - - guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { - return nil - } - - let thumbnailImage: CGImage? = nil - - var blurredThumbnailImage: UIImage? - if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() - } - } - - context.withFlippedContext { c in - c.setBlendMode(.copy) - if let blurredThumbnailImage = blurredThumbnailImage { - c.interpolationQuality = .low - c.draw(blurredThumbnailImage.cgImage!, in: arguments.drawingRect) - } - - if let cgImage = fullSizeImage.0.cgImage, let cgImageAlpha = fullSizeImage.1.cgImage { - c.setBlendMode(.normal) - c.interpolationQuality = .medium - - let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) - - c.draw(cgImage.masking(mask!)!, in: arguments.drawingRect) - } - } - - return context - } else { - return nil - } - } - } -} public func chatMessageSticker(account: Account, userLocation: MediaResourceUserLocation, file: TelegramMediaFile, small: Bool, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false, colorSpace: CGColorSpace? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return chatMessageSticker(postbox: account.postbox, userLocation: userLocation, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, thumbnail: thumbnail, synchronousLoad: synchronousLoad, colorSpace: colorSpace) diff --git a/submodules/Svg/BUILD b/submodules/Svg/BUILD index eba76650f4..b559ec3b70 100644 --- a/submodules/Svg/BUILD +++ b/submodules/Svg/BUILD @@ -1,21 +1,16 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -objc_library( +swift_library( name = "Svg", - enable_modules = True, module_name = "Svg", srcs = glob([ - "Sources/**/*.m", - "Sources/**/*.c", - "Sources/**/*.h", + "Sources/**/*.swift", ]), - hdrs = glob([ - "PublicHeaders/**/*.h", - ]), - includes = [ - "PublicHeaders", + copts = [ + "-warnings-as-errors", ], - sdk_frameworks = [ - "Foundation", + deps = [ + "//submodules/Svg/LegacyImpl", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyDataImport/Impl/BUILD b/submodules/Svg/LegacyImpl/BUILD similarity index 80% rename from submodules/LegacyDataImport/Impl/BUILD rename to submodules/Svg/LegacyImpl/BUILD index 4bab57e0ce..6808c17505 100644 --- a/submodules/LegacyDataImport/Impl/BUILD +++ b/submodules/Svg/LegacyImpl/BUILD @@ -1,10 +1,11 @@ objc_library( - name = "LegacyDataImportImpl", + name = "LegacyImpl", enable_modules = True, - module_name = "LegacyDataImportImpl", + module_name = "LegacyImpl", srcs = glob([ "Sources/**/*.m", + "Sources/**/*.c", "Sources/**/*.h", ]), hdrs = glob([ diff --git a/submodules/Svg/PublicHeaders/Svg/Svg.h b/submodules/Svg/LegacyImpl/PublicHeaders/Svg/Svg.h similarity index 84% rename from submodules/Svg/PublicHeaders/Svg/Svg.h rename to submodules/Svg/LegacyImpl/PublicHeaders/Svg/Svg.h index 39080185f3..acb4abd69f 100755 --- a/submodules/Svg/PublicHeaders/Svg/Svg.h +++ b/submodules/Svg/LegacyImpl/PublicHeaders/Svg/Svg.h @@ -27,6 +27,6 @@ GiftPatternData * _Nullable getGiftPatternData(NSData * _Nonnull data); UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit); UIImage * _Nullable renderPreparedImageWithSymbol(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit, UIImage * _Nullable symbolImage, int32_t modelRectIndex); -UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque); +UIImage * _Nullable drawSvgImageImpl(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque); #endif /* Lottie_h */ diff --git a/submodules/Svg/Sources/Svg.m b/submodules/Svg/LegacyImpl/Sources/Svg.m similarity index 99% rename from submodules/Svg/Sources/Svg.m rename to submodules/Svg/LegacyImpl/Sources/Svg.m index d3c4edee41..a7c73827b1 100755 --- a/submodules/Svg/Sources/Svg.m +++ b/submodules/Svg/LegacyImpl/Sources/Svg.m @@ -302,7 +302,7 @@ void renderShape(NSVGshape *shape, CGContextRef context, UIColor *foregroundColo } } -UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor, CGFloat canvasScale, bool opaque) { +UIImage * _Nullable drawSvgImageImpl(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, UIColor *foregroundColor, CGFloat canvasScale, bool opaque) { if (!data || data.length == 0) return nil; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; diff --git a/submodules/Svg/Sources/nanosvg.c b/submodules/Svg/LegacyImpl/Sources/nanosvg.c similarity index 100% rename from submodules/Svg/Sources/nanosvg.c rename to submodules/Svg/LegacyImpl/Sources/nanosvg.c diff --git a/submodules/Svg/Sources/nanosvg.h b/submodules/Svg/LegacyImpl/Sources/nanosvg.h similarity index 100% rename from submodules/Svg/Sources/nanosvg.h rename to submodules/Svg/LegacyImpl/Sources/nanosvg.h diff --git a/submodules/Svg/Sources/Svg.swift b/submodules/Svg/Sources/Svg.swift new file mode 100644 index 0000000000..3d70dbddd7 --- /dev/null +++ b/submodules/Svg/Sources/Svg.swift @@ -0,0 +1,7 @@ +import Foundation +import UIKit +import LegacyImpl + +public func drawSvgImage(data: Data, size: CGSize, backgroundColor: UIColor?, foregroundColor: UIColor?, scale: CGFloat, opaque: Bool) -> UIImage? { + return drawSvgImageImpl(data, size, backgroundColor, foregroundColor, scale, opaque) +} diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift index c9d2fda55e..caeb85c732 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift @@ -2,7 +2,6 @@ import Foundation import CoreMedia import UIKit import SwiftSignalKit -import Postbox import Display import TelegramCore import Compression @@ -17,7 +16,7 @@ import ManagedFile import UniversalMediaPlayer import SoftwareVideo -public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { +public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { return Signal({ subscriber in let queue = Queue() @@ -97,7 +96,7 @@ public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzM finalData.append(&alphaSize, length: 4) finalData.append(alphaData as Data) - let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg") + let tempFile = EngineTempBox.shared.tempFile(fileName: "image.ajpg") let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: []) subscriber.putNext(tempFile) subscriber.putCompletion() @@ -117,7 +116,7 @@ private let threadPool: ThreadPool = { return ThreadPool(threadCount: 3, threadPriority: 0.5) }() -public func cacheAnimatedStickerFrames(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { +public func cacheAnimatedStickerFrames(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal { return Signal({ subscriber in let cancelled = Atomic(value: false) @@ -144,7 +143,7 @@ public func cacheAnimatedStickerFrames(data: Data, size: CGSize, fitzModifier: E var currentFrame: Int32 = 0 - let tempFile = TempBox.shared.tempFile(fileName: "result.asticker") + let tempFile = EngineTempBox.shared.tempFile(fileName: "result.asticker") guard let file = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) else { return } @@ -258,7 +257,7 @@ public func cacheAnimatedStickerFrames(data: Data, size: CGSize, fitzModifier: E }) } -public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String) -> Signal { +public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String) -> Signal { return Signal { subscriber in let cancelled = Atomic(value: false) @@ -278,7 +277,7 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String var currentFrame: Int32 = 0 - let tempFile = TempBox.shared.tempFile(fileName: "result.vsticker") + let tempFile = EngineTempBox.shared.tempFile(fileName: "result.vsticker") guard let file = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) else { return } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 6a56740d86..ec029e5835 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -1,6 +1,7 @@ public enum Api { public enum account {} + public enum aicompose {} public enum auth {} public enum bots {} public enum channels {} @@ -23,6 +24,7 @@ public enum Api { public enum users {} public enum functions { public enum account {} + public enum aicompose {} public enum auth {} public enum bots {} public enum channels {} @@ -55,6 +57,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[0x0929C32F] = { return parseInt256($0) } dict[-1255641564] = { return parseString($0) } dict[-1194283041] = { return Api.AccountDaysTTL.parse_accountDaysTTL($0) } + dict[-805945687] = { return Api.AiComposeTone.parse_aiComposeTone($0) } + dict[-1683135468] = { return Api.AiComposeTone.parse_aiComposeToneDefault($0) } + dict[-237623060] = { return Api.AiComposeToneExample.parse_aiComposeToneExample($0) } dict[-653423106] = { return Api.AttachMenuBot.parse_attachMenuBot($0) } dict[-1297663893] = { return Api.AttachMenuBotIcon.parse_attachMenuBotIcon($0) } dict[1165423600] = { return Api.AttachMenuBotIconColor.parse_attachMenuBotIconColor($0) } @@ -102,6 +107,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[894081801] = { return Api.BotInlineMessage.parse_botInlineMessageMediaInvoice($0) } dict[-1970903652] = { return Api.BotInlineMessage.parse_botInlineMessageMediaVenue($0) } dict[-2137335386] = { return Api.BotInlineMessage.parse_botInlineMessageMediaWebPage($0) } + dict[174161531] = { return Api.BotInlineMessage.parse_botInlineMessageRichMessage($0) } dict[-1937807902] = { return Api.BotInlineMessage.parse_botInlineMessageText($0) } dict[400266251] = { return Api.BotInlineResult.parse_botInlineMediaResult($0) } dict[295067450] = { return Api.BotInlineResult.parse_botInlineResult($0) } @@ -206,7 +212,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1605510357] = { return Api.ChatAdminRights.parse_chatAdminRights($0) } dict[-219353309] = { return Api.ChatAdminWithInvites.parse_chatAdminWithInvites($0) } dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) } - dict[-455036259] = { return Api.ChatFull.parse_channelFull($0) } + dict[-1605464774] = { return Api.ChatFull.parse_channelFull($0) } dict[640893467] = { return Api.ChatFull.parse_chatFull($0) } dict[1553807106] = { return Api.ChatInvite.parse_chatInvite($0) } dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) } @@ -227,7 +233,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[878246344] = { return Api.ChatTheme.parse_chatThemeUniqueGift($0) } dict[-1390068360] = { return Api.CodeSettings.parse_codeSettings($0) } dict[-870702050] = { return Api.Config.parse_config($0) } - dict[-849058964] = { return Api.ConnectedBot.parse_connectedBot($0) } + dict[54448129] = { return Api.ConnectedBot.parse_connectedBot($0) } dict[429997937] = { return Api.ConnectedBotStarRef.parse_connectedBotStarRef($0) } dict[341499403] = { return Api.Contact.parse_contact($0) } dict[496600883] = { return Api.ContactBirthday.parse_contactBirthday($0) } @@ -254,7 +260,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1815593308] = { return Api.DocumentAttribute.parse_documentAttributeImageSize($0) } dict[1662637586] = { return Api.DocumentAttribute.parse_documentAttributeSticker($0) } dict[1137015880] = { return Api.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[-1763006997] = { return Api.DraftMessage.parse_draftMessage($0) } + dict[1627271828] = { return Api.DraftMessage.parse_draftMessage($0) } dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) } dict[-1764723459] = { return Api.EmailVerification.parse_emailVerificationApple($0) } dict[-1842457175] = { return Api.EmailVerification.parse_emailVerificationCode($0) } @@ -321,6 +327,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1589952067] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeMegagroup($0) } dict[-2093215828] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypePM($0) } dict[813821341] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeSameBotPM($0) } + dict[535407039] = { return Api.InputAiComposeTone.parse_inputAiComposeToneDefault($0) } + dict[125026432] = { return Api.InputAiComposeTone.parse_inputAiComposeToneID($0) } + dict[530584407] = { return Api.InputAiComposeTone.parse_inputAiComposeToneSlug($0) } dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) } dict[-1457472134] = { return Api.InputBotApp.parse_inputBotAppID($0) } dict[-1869872121] = { return Api.InputBotApp.parse_inputBotAppShortName($0) } @@ -331,6 +340,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-672693723] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaInvoice($0) } dict[1098628881] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaVenue($0) } dict[-1109605104] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaWebPage($0) } + dict[-1271007892] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageRichMessage($0) } dict[1036876423] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageText($0) } dict[-1995686519] = { return Api.InputBotInlineMessageID.parse_inputBotInlineMessageID($0) } dict[-1227287081] = { return Api.InputBotInlineMessageID.parse_inputBotInlineMessageID64($0) } @@ -485,6 +495,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1003796418] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) } dict[1775660101] = { return Api.InputReplyTo.parse_inputReplyToMonoForum($0) } dict[1484862010] = { return Api.InputReplyTo.parse_inputReplyToStory($0) } + dict[-456898052] = { return Api.InputRichMessage.parse_inputRichMessage($0) } + dict[-722815663] = { return Api.InputRichMessage.parse_inputRichMessageHTML($0) } + dict[162300294] = { return Api.InputRichMessage.parse_inputRichMessageMarkdown($0) } dict[-251549057] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftChat($0) } dict[545636920] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftSlug($0) } dict[1764202389] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftUser($0) } @@ -510,7 +523,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[853188252] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) } dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) } dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) } - dict[-1682807955] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentAuthCode($0) } + dict[1069645911] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentAuthCode($0) } dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) } dict[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) } dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) } @@ -540,6 +553,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[736157604] = { return Api.JSONValue.parse_jsonNumber($0) } dict[-1715350371] = { return Api.JSONValue.parse_jsonObject($0) } dict[-1222740358] = { return Api.JSONValue.parse_jsonString($0) } + dict[-1374344599] = { return Api.JoinChatBotResult.parse_joinChatBotResultApproved($0) } + dict[251265428] = { return Api.JoinChatBotResult.parse_joinChatBotResultDeclined($0) } + dict[-1734105024] = { return Api.JoinChatBotResult.parse_joinChatBotResultQueued($0) } + dict[-689719277] = { return Api.JoinChatBotResult.parse_joinChatBotResultWebView($0) } dict[45580630] = { return Api.KeyboardButton.parse_inputKeyboardButtonRequestPeer($0) } dict[1744911986] = { return Api.KeyboardButton.parse_inputKeyboardButtonUrlAuth($0) } dict[2103314375] = { return Api.KeyboardButton.parse_inputKeyboardButtonUserProfile($0) } @@ -577,7 +594,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[988112002] = { return Api.Message.parse_message($0) } + dict[1979759059] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[2055212554] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -738,10 +755,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1261946036] = { return Api.NotifyPeer.parse_notifyUsers($0) } dict[1001931436] = { return Api.OutboxReadDate.parse_outboxReadDate($0) } dict[-1738178803] = { return Api.Page.parse_page($0) } + dict[1464557951] = { return Api.PageBlock.parse_inputPageBlockMap($0) } dict[-837994576] = { return Api.PageBlock.parse_pageBlockAnchor($0) } dict[-2143067670] = { return Api.PageBlock.parse_pageBlockAudio($0) } dict[-1162877472] = { return Api.PageBlock.parse_pageBlockAuthorDate($0) } dict[641563686] = { return Api.PageBlock.parse_pageBlockBlockquote($0) } + dict[242108356] = { return Api.PageBlock.parse_pageBlockBlockquoteBlocks($0) } dict[-283684427] = { return Api.PageBlock.parse_pageBlockChannel($0) } dict[1705048653] = { return Api.PageBlock.parse_pageBlockCollage($0) } dict[972174080] = { return Api.PageBlock.parse_pageBlockCover($0) } @@ -751,10 +770,17 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-229005301] = { return Api.PageBlock.parse_pageBlockEmbedPost($0) } dict[1216809369] = { return Api.PageBlock.parse_pageBlockFooter($0) } dict[-1076861716] = { return Api.PageBlock.parse_pageBlockHeader($0) } + dict[-1157691601] = { return Api.PageBlock.parse_pageBlockHeading1($0) } + dict[158018284] = { return Api.PageBlock.parse_pageBlockHeading2($0) } + dict[1743204781] = { return Api.PageBlock.parse_pageBlockHeading3($0) } + dict[-1254983893] = { return Api.PageBlock.parse_pageBlockHeading4($0) } + dict[-608277398] = { return Api.PageBlock.parse_pageBlockHeading5($0) } + dict[1747599785] = { return Api.PageBlock.parse_pageBlockHeading6($0) } dict[504660880] = { return Api.PageBlock.parse_pageBlockKicker($0) } dict[-454524911] = { return Api.PageBlock.parse_pageBlockList($0) } dict[-1538310410] = { return Api.PageBlock.parse_pageBlockMap($0) } - dict[-1702174239] = { return Api.PageBlock.parse_pageBlockOrderedList($0) } + dict[1493699616] = { return Api.PageBlock.parse_pageBlockMath($0) } + dict[534181569] = { return Api.PageBlock.parse_pageBlockOrderedList($0) } dict[1182402406] = { return Api.PageBlock.parse_pageBlockParagraph($0) } dict[391759200] = { return Api.PageBlock.parse_pageBlockPhoto($0) } dict[-1066346178] = { return Api.PageBlock.parse_pageBlockPreformatted($0) } @@ -764,14 +790,15 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-248793375] = { return Api.PageBlock.parse_pageBlockSubheader($0) } dict[-1879401953] = { return Api.PageBlock.parse_pageBlockSubtitle($0) } dict[-1085412734] = { return Api.PageBlock.parse_pageBlockTable($0) } + dict[1009361890] = { return Api.PageBlock.parse_pageBlockThinking($0) } dict[1890305021] = { return Api.PageBlock.parse_pageBlockTitle($0) } dict[324435594] = { return Api.PageBlock.parse_pageBlockUnsupported($0) } dict[2089805750] = { return Api.PageBlock.parse_pageBlockVideo($0) } dict[1869903447] = { return Api.PageCaption.parse_pageCaption($0) } - dict[635466748] = { return Api.PageListItem.parse_pageListItemBlocks($0) } - dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) } - dict[-1730311882] = { return Api.PageListOrderedItem.parse_pageListOrderedItemBlocks($0) } - dict[1577484359] = { return Api.PageListOrderedItem.parse_pageListOrderedItemText($0) } + dict[1674209194] = { return Api.PageListItem.parse_pageListItemBlocks($0) } + dict[794323004] = { return Api.PageListItem.parse_pageListItemText($0) } + dict[-1879910928] = { return Api.PageListOrderedItem.parse_pageListOrderedItemBlocks($0) } + dict[352522633] = { return Api.PageListOrderedItem.parse_pageListOrderedItemText($0) } dict[-1282352120] = { return Api.PageRelatedArticle.parse_pageRelatedArticle($0) } dict[878078826] = { return Api.PageTableCell.parse_pageTableCell($0) } dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) } @@ -820,7 +847,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[236446268] = { return Api.PhotoSize.parse_photoSizeEmpty($0) } dict[-96535659] = { return Api.PhotoSize.parse_photoSizeProgressive($0) } dict[-525288402] = { return Api.PhotoSize.parse_photoStrippedSize($0) } - dict[-1203610647] = { return Api.Poll.parse_poll($0) } + dict[-1771164225] = { return Api.Poll.parse_poll($0) } dict[429911446] = { return Api.PollAnswer.parse_inputPollAnswer($0) } dict[1266514026] = { return Api.PollAnswer.parse_pollAnswer($0) } dict[910500618] = { return Api.PollAnswerVoters.parse_pollAnswerVoters($0) } @@ -914,17 +941,31 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1258914157] = { return Api.RequirementToContact.parse_requirementToContactPaidMessages($0) } dict[-444472087] = { return Api.RequirementToContact.parse_requirementToContactPremium($0) } dict[-797791052] = { return Api.RestrictionReason.parse_restrictionReason($0) } + dict[-1158439541] = { return Api.RichMessage.parse_richMessage($0) } dict[894777186] = { return Api.RichText.parse_textAnchor($0) } + dict[-984177571] = { return Api.RichText.parse_textAutoEmail($0) } + dict[616720265] = { return Api.RichText.parse_textAutoPhone($0) } + dict[-1402305622] = { return Api.RichText.parse_textAutoUrl($0) } + dict[-1185513171] = { return Api.RichText.parse_textBankCard($0) } dict[1730456516] = { return Api.RichText.parse_textBold($0) } + dict[50276819] = { return Api.RichText.parse_textBotCommand($0) } + dict[2073958401] = { return Api.RichText.parse_textCashtag($0) } dict[2120376535] = { return Api.RichText.parse_textConcat($0) } + dict[-1570679104] = { return Api.RichText.parse_textCustomEmoji($0) } + dict[-1514906069] = { return Api.RichText.parse_textDate($0) } dict[-564523562] = { return Api.RichText.parse_textEmail($0) } dict[-599948721] = { return Api.RichText.parse_textEmpty($0) } dict[1816074681] = { return Api.RichText.parse_textFixed($0) } + dict[1368728810] = { return Api.RichText.parse_textHashtag($0) } dict[136105807] = { return Api.RichText.parse_textImage($0) } dict[-653089380] = { return Api.RichText.parse_textItalic($0) } dict[55281185] = { return Api.RichText.parse_textMarked($0) } + dict[-1657885545] = { return Api.RichText.parse_textMath($0) } + dict[-853225660] = { return Api.RichText.parse_textMention($0) } + dict[27917308] = { return Api.RichText.parse_textMentionName($0) } dict[483104362] = { return Api.RichText.parse_textPhone($0) } dict[1950782688] = { return Api.RichText.parse_textPlain($0) } + dict[1277844834] = { return Api.RichText.parse_textSpoiler($0) } dict[-1678197867] = { return Api.RichText.parse_textStrike($0) } dict[-311786236] = { return Api.RichText.parse_textSubscript($0) } dict[-939827711] = { return Api.RichText.parse_textSuperscript($0) } @@ -975,6 +1016,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-368907213] = { return Api.SecureValueType.parse_secureValueTypeTemporaryRegistration($0) } dict[-63531698] = { return Api.SecureValueType.parse_secureValueTypeUtilityBill($0) } dict[-1206095820] = { return Api.SendAsPeer.parse_sendAsPeer($0) } + dict[-491635887] = { return Api.SendMessageAction.parse_inputSendMessageRichMessageDraftAction($0) } dict[-44119819] = { return Api.SendMessageAction.parse_sendMessageCancelAction($0) } dict[1653390447] = { return Api.SendMessageAction.parse_sendMessageChooseContactAction($0) } dict[-1336228175] = { return Api.SendMessageAction.parse_sendMessageChooseStickerAction($0) } @@ -986,6 +1028,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-718310409] = { return Api.SendMessageAction.parse_sendMessageRecordAudioAction($0) } dict[-1997373508] = { return Api.SendMessageAction.parse_sendMessageRecordRoundAction($0) } dict[-1584933265] = { return Api.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[-1563745031] = { return Api.SendMessageAction.parse_sendMessageRichMessageDraftAction($0) } dict[929929052] = { return Api.SendMessageAction.parse_sendMessageTextDraftAction($0) } dict[381645902] = { return Api.SendMessageAction.parse_sendMessageTypingAction($0) } dict[-212740181] = { return Api.SendMessageAction.parse_sendMessageUploadAudioAction($0) } @@ -1085,6 +1128,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1236871718] = { return Api.TodoList.parse_todoList($0) } dict[-305282981] = { return Api.TopPeer.parse_topPeer($0) } dict[-39945236] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsApp($0) } + dict[1814361053] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsGuestChat($0) } dict[344356834] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsInline($0) } dict[-1419371685] = { return Api.TopPeerCategory.parse_topPeerCategoryBotsPM($0) } dict[371037736] = { return Api.TopPeerCategory.parse_topPeerCategoryChannels($0) } @@ -1094,15 +1138,17 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1122524854] = { return Api.TopPeerCategory.parse_topPeerCategoryGroups($0) } dict[511092620] = { return Api.TopPeerCategory.parse_topPeerCategoryPhoneCalls($0) } dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) } + dict[-1945136645] = { return Api.Update.parse_updateAiComposeTones($0) } dict[397910539] = { return Api.Update.parse_updateAttachMenuBots($0) } dict[-335171433] = { return Api.Update.parse_updateAutoSaveSettings($0) } dict[-1964652166] = { return Api.Update.parse_updateBotBusinessConnect($0) } dict[-1177566067] = { return Api.Update.parse_updateBotCallbackQuery($0) } dict[-1873947492] = { return Api.Update.parse_updateBotChatBoost($0) } - dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) } + dict[2092125561] = { return Api.Update.parse_updateBotChatInviteRequester($0) } dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) } dict[-1607821266] = { return Api.Update.parse_updateBotDeleteBusinessMessage($0) } dict[132077692] = { return Api.Update.parse_updateBotEditBusinessMessage($0) } + dict[-841742019] = { return Api.Update.parse_updateBotGuestChatQuery($0) } dict[1232025500] = { return Api.Update.parse_updateBotInlineQuery($0) } dict[317794823] = { return Api.Update.parse_updateBotInlineSend($0) } dict[347625491] = { return Api.Update.parse_updateBotMenuButton($0) } @@ -1166,6 +1212,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-667783411] = { return Api.Update.parse_updateGroupCallMessage($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } + dict[-1112768912] = { return Api.Update.parse_updateJoinChatWebViewDecision($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } dict[1180041828] = { return Api.Update.parse_updateLangPackTooLong($0) } dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) } @@ -1178,6 +1225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1618924792] = { return Api.Update.parse_updateMonoForumNoPaidException($0) } dict[-2030252155] = { return Api.Update.parse_updateMoveStickerSetToTop($0) } dict[-1991136273] = { return Api.Update.parse_updateNewAuthorization($0) } + dict[-1306491994] = { return Api.Update.parse_updateNewBotConnection($0) } dict[1656358105] = { return Api.Update.parse_updateNewChannelMessage($0) } dict[314359194] = { return Api.Update.parse_updateNewEncryptedMessage($0) } dict[522914557] = { return Api.Update.parse_updateNewMessage($0) } @@ -1246,6 +1294,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[88680979] = { return Api.Update.parse_updateUserPhone($0) } dict[-440534818] = { return Api.Update.parse_updateUserStatus($0) } dict[706199388] = { return Api.Update.parse_updateUserTyping($0) } + dict[335872721] = { return Api.Update.parse_updateWebBrowserException($0) } + dict[-1013306658] = { return Api.Update.parse_updateWebBrowserSettings($0) } dict[2139689491] = { return Api.Update.parse_updateWebPage($0) } dict[361936797] = { return Api.Update.parse_updateWebViewResultSent($0) } dict[2027216577] = { return Api.Updates.parse_updateShort($0) } @@ -1279,10 +1329,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1493633966] = { return Api.WebAuthorization.parse_webAuthorization($0) } dict[475467473] = { return Api.WebDocument.parse_webDocument($0) } dict[-104284986] = { return Api.WebDocument.parse_webDocumentNoProxy($0) } + dict[-1824741993] = { return Api.WebDomainException.parse_webDomainException($0) } dict[-392411726] = { return Api.WebPage.parse_webPage($0) } dict[555358088] = { return Api.WebPage.parse_webPageEmpty($0) } dict[1930545681] = { return Api.WebPage.parse_webPageNotModified($0) } dict[-1328464313] = { return Api.WebPage.parse_webPagePending($0) } + dict[2005007896] = { return Api.WebPageAttribute.parse_webPageAttributeAiComposeTone($0) } dict[29770178] = { return Api.WebPageAttribute.parse_webPageAttributeStarGiftAuction($0) } dict[835375875] = { return Api.WebPageAttribute.parse_webPageAttributeStarGiftCollection($0) } dict[1355547603] = { return Api.WebPageAttribute.parse_webPageAttributeStickerSet($0) } @@ -1329,6 +1381,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) } dict[471437699] = { return Api.account.WallPapers.parse_wallPapersNotModified($0) } dict[-313079300] = { return Api.account.WebAuthorizations.parse_webAuthorizations($0) } + dict[2045480115] = { return Api.account.WebBrowserSettings.parse_webBrowserSettings($0) } + dict[-1021538482] = { return Api.account.WebBrowserSettings.parse_webBrowserSettingsNotModified($0) } + dict[1822232318] = { return Api.aicompose.Tones.parse_tones($0) } + dict[-1040948989] = { return Api.aicompose.Tones.parse_tonesNotModified($0) } dict[782418132] = { return Api.auth.Authorization.parse_authorization($0) } dict[1148485274] = { return Api.auth.Authorization.parse_authorizationSignUpRequired($0) } dict[1948046307] = { return Api.auth.CodeType.parse_codeTypeCall($0) } @@ -1344,7 +1400,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-503089271] = { return Api.auth.PasskeyLoginOptions.parse_passkeyLoginOptions($0) } dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) } - dict[-527082948] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) } + dict[-125665601] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) } dict[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) } dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) } dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) } @@ -1357,6 +1413,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1073693790] = { return Api.auth.SentCodeType.parse_sentCodeTypeSms($0) } dict[-1284008785] = { return Api.auth.SentCodeType.parse_sentCodeTypeSmsPhrase($0) } dict[-1542017919] = { return Api.auth.SentCodeType.parse_sentCodeTypeSmsWord($0) } + dict[-585121901] = { return Api.bots.AccessSettings.parse_accessSettings($0) } dict[-391678544] = { return Api.bots.BotInfo.parse_botInfo($0) } dict[1012971041] = { return Api.bots.ExportedBotToken.parse_exportedBotToken($0) } dict[428978491] = { return Api.bots.PopularAppBots.parse_popularAppBots($0) } @@ -1437,6 +1494,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1231326505] = { return Api.messages.ChatAdminsWithInvites.parse_chatAdminsWithInvites($0) } dict[-438840932] = { return Api.messages.ChatFull.parse_chatFull($0) } dict[-2118733814] = { return Api.messages.ChatInviteImporters.parse_chatInviteImporters($0) } + dict[1146512295] = { return Api.messages.ChatInviteJoinResult.parse_chatInviteJoinResultOk($0) } + dict[793887543] = { return Api.messages.ChatInviteJoinResult.parse_chatInviteJoinResultWebView($0) } dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } dict[-1571952873] = { return Api.messages.CheckedHistoryImportPeer.parse_checkedHistoryImportPeer($0) } @@ -1568,6 +1627,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[963421692] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) } dict[-276825834] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) } dict[2145983508] = { return Api.stats.MessageStats.parse_messageStats($0) } + dict[697941741] = { return Api.stats.PollStats.parse_pollStats($0) } dict[-1828487648] = { return Api.stats.PublicForwards.parse_publicForwards($0) } dict[1355613820] = { return Api.stats.StoryStats.parse_storyStats($0) } dict[-2046910401] = { return Api.stickers.SuggestedShortName.parse_suggestedShortName($0) } @@ -1668,6 +1728,10 @@ public extension Api { switch object { case let _1 as Api.AccountDaysTTL: _1.serialize(buffer, boxed) + case let _1 as Api.AiComposeTone: + _1.serialize(buffer, boxed) + case let _1 as Api.AiComposeToneExample: + _1.serialize(buffer, boxed) case let _1 as Api.AttachMenuBot: _1.serialize(buffer, boxed) case let _1 as Api.AttachMenuBotIcon: @@ -1908,6 +1972,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InlineQueryPeerType: _1.serialize(buffer, boxed) + case let _1 as Api.InputAiComposeTone: + _1.serialize(buffer, boxed) case let _1 as Api.InputAppEvent: _1.serialize(buffer, boxed) case let _1 as Api.InputBotApp: @@ -1998,6 +2064,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputReplyTo: _1.serialize(buffer, boxed) + case let _1 as Api.InputRichMessage: + _1.serialize(buffer, boxed) case let _1 as Api.InputSavedStarGift: _1.serialize(buffer, boxed) case let _1 as Api.InputSecureFile: @@ -2036,6 +2104,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.JSONValue: _1.serialize(buffer, boxed) + case let _1 as Api.JoinChatBotResult: + _1.serialize(buffer, boxed) case let _1 as Api.KeyboardButton: _1.serialize(buffer, boxed) case let _1 as Api.KeyboardButtonRow: @@ -2218,6 +2288,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.RestrictionReason: _1.serialize(buffer, boxed) + case let _1 as Api.RichMessage: + _1.serialize(buffer, boxed) case let _1 as Api.RichText: _1.serialize(buffer, boxed) case let _1 as Api.SavedContact: @@ -2406,6 +2478,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.WebDocument: _1.serialize(buffer, boxed) + case let _1 as Api.WebDomainException: + _1.serialize(buffer, boxed) case let _1 as Api.WebPage: _1.serialize(buffer, boxed) case let _1 as Api.WebPageAttribute: @@ -2470,6 +2544,10 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.account.WebAuthorizations: _1.serialize(buffer, boxed) + case let _1 as Api.account.WebBrowserSettings: + _1.serialize(buffer, boxed) + case let _1 as Api.aicompose.Tones: + _1.serialize(buffer, boxed) case let _1 as Api.auth.Authorization: _1.serialize(buffer, boxed) case let _1 as Api.auth.CodeType: @@ -2488,6 +2566,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.auth.SentCodeType: _1.serialize(buffer, boxed) + case let _1 as Api.bots.AccessSettings: + _1.serialize(buffer, boxed) case let _1 as Api.bots.BotInfo: _1.serialize(buffer, boxed) case let _1 as Api.bots.ExportedBotToken: @@ -2602,6 +2682,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.ChatInviteImporters: _1.serialize(buffer, boxed) + case let _1 as Api.messages.ChatInviteJoinResult: + _1.serialize(buffer, boxed) case let _1 as Api.messages.Chats: _1.serialize(buffer, boxed) case let _1 as Api.messages.CheckedHistoryImportPeer: @@ -2792,6 +2874,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.stats.MessageStats: _1.serialize(buffer, boxed) + case let _1 as Api.stats.PollStats: + _1.serialize(buffer, boxed) case let _1 as Api.stats.PublicForwards: _1.serialize(buffer, boxed) case let _1 as Api.stats.StoryStats: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index b7c6682f33..aac4ebc52e 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -42,6 +42,221 @@ public extension Api { } } } +public extension Api { + enum AiComposeTone: TypeConstructorDescription { + public class Cons_aiComposeTone: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var slug: String + public var title: String + public var emojiId: Int64? + public var prompt: String? + public var installsCount: Int32? + public var authorId: Int64? + public var exampleEnglish: Api.AiComposeToneExample? + public init(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, emojiId: Int64?, prompt: String?, installsCount: Int32?, authorId: Int64?, exampleEnglish: Api.AiComposeToneExample?) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.slug = slug + self.title = title + self.emojiId = emojiId + self.prompt = prompt + self.installsCount = installsCount + self.authorId = authorId + self.exampleEnglish = exampleEnglish + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("aiComposeTone", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("slug", ConstructorParameterDescription(self.slug)), ("title", ConstructorParameterDescription(self.title)), ("emojiId", ConstructorParameterDescription(self.emojiId)), ("prompt", ConstructorParameterDescription(self.prompt)), ("installsCount", ConstructorParameterDescription(self.installsCount)), ("authorId", ConstructorParameterDescription(self.authorId)), ("exampleEnglish", ConstructorParameterDescription(self.exampleEnglish))]) + } + } + public class Cons_aiComposeToneDefault: TypeConstructorDescription { + public var tone: String + public var emojiId: Int64 + public var title: String + public init(tone: String, emojiId: Int64, title: String) { + self.tone = tone + self.emojiId = emojiId + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("aiComposeToneDefault", [("tone", ConstructorParameterDescription(self.tone)), ("emojiId", ConstructorParameterDescription(self.emojiId)), ("title", ConstructorParameterDescription(self.title))]) + } + } + case aiComposeTone(Cons_aiComposeTone) + case aiComposeToneDefault(Cons_aiComposeToneDefault) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .aiComposeTone(let _data): + if boxed { + buffer.appendInt32(-805945687) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeString(_data.slug, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt64(_data.emojiId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.prompt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.installsCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt64(_data.authorId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.exampleEnglish!.serialize(buffer, true) + } + break + case .aiComposeToneDefault(let _data): + if boxed { + buffer.appendInt32(-1683135468) + } + serializeString(_data.tone, buffer: buffer, boxed: false) + serializeInt64(_data.emojiId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .aiComposeTone(let _data): + return ("aiComposeTone", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("slug", ConstructorParameterDescription(_data.slug)), ("title", ConstructorParameterDescription(_data.title)), ("emojiId", ConstructorParameterDescription(_data.emojiId)), ("prompt", ConstructorParameterDescription(_data.prompt)), ("installsCount", ConstructorParameterDescription(_data.installsCount)), ("authorId", ConstructorParameterDescription(_data.authorId)), ("exampleEnglish", ConstructorParameterDescription(_data.exampleEnglish))]) + case .aiComposeToneDefault(let _data): + return ("aiComposeToneDefault", [("tone", ConstructorParameterDescription(_data.tone)), ("emojiId", ConstructorParameterDescription(_data.emojiId)), ("title", ConstructorParameterDescription(_data.title))]) + } + } + + public static func parse_aiComposeTone(_ reader: BufferReader) -> AiComposeTone? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt64() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _7 = parseString(reader) + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _8 = reader.readInt32() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _9 = reader.readInt64() + } + var _10: Api.AiComposeToneExample? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.AiComposeToneExample + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.AiComposeTone.aiComposeTone(Cons_aiComposeTone(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, emojiId: _6, prompt: _7, installsCount: _8, authorId: _9, exampleEnglish: _10)) + } + else { + return nil + } + } + public static func parse_aiComposeToneDefault(_ reader: BufferReader) -> AiComposeTone? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.AiComposeTone.aiComposeToneDefault(Cons_aiComposeToneDefault(tone: _1!, emojiId: _2!, title: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum AiComposeToneExample: TypeConstructorDescription { + public class Cons_aiComposeToneExample: TypeConstructorDescription { + public var from: Api.TextWithEntities + public var to: Api.TextWithEntities + public init(from: Api.TextWithEntities, to: Api.TextWithEntities) { + self.from = from + self.to = to + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("aiComposeToneExample", [("from", ConstructorParameterDescription(self.from)), ("to", ConstructorParameterDescription(self.to))]) + } + } + case aiComposeToneExample(Cons_aiComposeToneExample) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .aiComposeToneExample(let _data): + if boxed { + buffer.appendInt32(-237623060) + } + _data.from.serialize(buffer, true) + _data.to.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .aiComposeToneExample(let _data): + return ("aiComposeToneExample", [("from", ConstructorParameterDescription(_data.from)), ("to", ConstructorParameterDescription(_data.to))]) + } + } + + public static func parse_aiComposeToneExample(_ reader: BufferReader) -> AiComposeToneExample? { + var _1: Api.TextWithEntities? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.AiComposeToneExample.aiComposeToneExample(Cons_aiComposeToneExample(from: _1!, to: _2!)) + } + else { + return nil + } + } + } +} public extension Api { enum AttachMenuBot: TypeConstructorDescription { public class Cons_attachMenuBot: TypeConstructorDescription { @@ -103,7 +318,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: [Api.AttachMenuPeerType]? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AttachMenuPeerType.self) } @@ -115,7 +330,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.AttachMenuBot.attachMenuBot(Cons_attachMenuBot(flags: _1!, botId: _2!, shortName: _3!, peerTypes: _4, icons: _5!)) @@ -182,7 +397,7 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.Document } var _4: [Api.AttachMenuBotIconColor]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AttachMenuBotIconColor.self) } @@ -190,7 +405,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.AttachMenuBotIcon.attachMenuBotIcon(Cons_attachMenuBotIcon(flags: _1!, name: _2!, icon: _3!, colors: _4)) } @@ -803,11 +1018,11 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int64? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _2 = reader.readInt64() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil if _c1 && _c2 { return Api.AutoSaveSettings.autoSaveSettings(Cons_autoSaveSettings(flags: _1!, videoMaxSize: _2)) } @@ -875,21 +1090,21 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = reader.readInt64() } var _5: Int64? _5 = reader.readInt64() var _6: Int64? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _6 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.AvailableEffect.availableEffect(Cons_availableEffect(flags: _1!, id: _2!, emoticon: _3!, staticIconId: _4, effectStickerId: _5!, effectAnimationId: _6)) } @@ -989,13 +1204,13 @@ public extension Api { _8 = Api.parse(reader, signature: signature) as? Api.Document } var _9: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.Document } } var _10: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.Document } @@ -1008,8 +1223,8 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.AvailableReaction.availableReaction(Cons_availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!, aroundAnimation: _9, centerIcon: _10)) } @@ -1189,13 +1404,13 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.Birthday.birthday(Cons_birthday(flags: _1!, day: _2!, month: _3!, year: _4)) } @@ -1313,11 +1528,11 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: Int64? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt64() } var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _4 = reader.readInt32() } var _5: Int32? @@ -1325,26 +1540,26 @@ public extension Api { var _6: Int32? _6 = reader.readInt32() var _7: String? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _7 = parseString(reader) } var _8: Int32? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _8 = reader.readInt32() } var _9: Int64? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { _9 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.Boost.boost(Cons_boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8, stars: _9)) } @@ -1354,208 +1569,3 @@ public extension Api { } } } -public extension Api { - enum BotApp: TypeConstructorDescription { - public class Cons_botApp: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var shortName: String - public var title: String - public var description: String - public var photo: Api.Photo - public var document: Api.Document? - public var hash: Int64 - public init(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?, hash: Int64) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.shortName = shortName - self.title = title - self.description = description - self.photo = photo - self.document = document - self.hash = hash - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botApp", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("shortName", ConstructorParameterDescription(self.shortName)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("document", ConstructorParameterDescription(self.document)), ("hash", ConstructorParameterDescription(self.hash))]) - } - } - case botApp(Cons_botApp) - case botAppNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botApp(let _data): - if boxed { - buffer.appendInt32(-1778593322) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeString(_data.shortName, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - _data.photo.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.document!.serialize(buffer, true) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - break - case .botAppNotModified: - if boxed { - buffer.appendInt32(1571189943) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botApp(let _data): - return ("botApp", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("shortName", ConstructorParameterDescription(_data.shortName)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("document", ConstructorParameterDescription(_data.document)), ("hash", ConstructorParameterDescription(_data.hash))]) - case .botAppNotModified: - return ("botAppNotModified", []) - } - } - - public static func parse_botApp(_ reader: BufferReader) -> BotApp? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.Photo? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo - } - var _8: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Document - } - } - var _9: Int64? - _9 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.BotApp.botApp(Cons_botApp(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8, hash: _9!)) - } - else { - return nil - } - } - public static func parse_botAppNotModified(_ reader: BufferReader) -> BotApp? { - return Api.BotApp.botAppNotModified - } - } -} -public extension Api { - enum BotAppSettings: TypeConstructorDescription { - public class Cons_botAppSettings: TypeConstructorDescription { - public var flags: Int32 - public var placeholderPath: Buffer? - public var backgroundColor: Int32? - public var backgroundDarkColor: Int32? - public var headerColor: Int32? - public var headerDarkColor: Int32? - public init(flags: Int32, placeholderPath: Buffer?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) { - self.flags = flags - self.placeholderPath = placeholderPath - self.backgroundColor = backgroundColor - self.backgroundDarkColor = backgroundDarkColor - self.headerColor = headerColor - self.headerDarkColor = headerDarkColor - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botAppSettings", [("flags", ConstructorParameterDescription(self.flags)), ("placeholderPath", ConstructorParameterDescription(self.placeholderPath)), ("backgroundColor", ConstructorParameterDescription(self.backgroundColor)), ("backgroundDarkColor", ConstructorParameterDescription(self.backgroundDarkColor)), ("headerColor", ConstructorParameterDescription(self.headerColor)), ("headerDarkColor", ConstructorParameterDescription(self.headerDarkColor))]) - } - } - case botAppSettings(Cons_botAppSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botAppSettings(let _data): - if boxed { - buffer.appendInt32(-912582320) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.placeholderPath!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.backgroundColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.backgroundDarkColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.headerColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.headerDarkColor!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botAppSettings(let _data): - return ("botAppSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("placeholderPath", ConstructorParameterDescription(_data.placeholderPath)), ("backgroundColor", ConstructorParameterDescription(_data.backgroundColor)), ("backgroundDarkColor", ConstructorParameterDescription(_data.backgroundDarkColor)), ("headerColor", ConstructorParameterDescription(_data.headerColor)), ("headerDarkColor", ConstructorParameterDescription(_data.headerDarkColor))]) - } - } - - public static func parse_botAppSettings(_ reader: BufferReader) -> BotAppSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseBytes(reader) - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BotAppSettings.botAppSettings(Cons_botAppSettings(flags: _1!, placeholderPath: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6)) - } - else { - return nil - } - } - } -} diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index 1aa0f1f987..f9dbfbe0e0 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -323,19 +323,19 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = reader.readInt32() } var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.InputFileLocation.inputGroupCallStream(Cons_inputGroupCallStream(flags: _1!, call: _2!, timeMs: _3!, scale: _4!, videoChannel: _5, videoQuality: _6)) } @@ -640,13 +640,13 @@ public extension Api { var _3: Double? _3 = reader.readDouble() var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputGeoPoint.inputGeoPoint(Cons_inputGeoPoint(flags: _1!, lat: _2!, long: _3!, accuracyRadius: _4)) } @@ -1196,7 +1196,7 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: Api.TextWithEntities? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } @@ -1204,7 +1204,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputInvoice.inputInvoicePremiumGiftStars(Cons_inputInvoicePremiumGiftStars(flags: _1!, userId: _2!, months: _3!, message: _4)) } @@ -1233,7 +1233,7 @@ public extension Api { var _3: Int64? _3 = reader.readInt64() var _4: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } @@ -1241,7 +1241,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputInvoice.inputInvoiceStarGift(Cons_inputInvoiceStarGift(flags: _1!, peer: _2!, giftId: _3!, message: _4)) } @@ -1253,7 +1253,7 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.InputPeer? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.InputPeer } @@ -1263,16 +1263,16 @@ public extension Api { var _4: Int64? _4 = reader.readInt64() var _5: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputInvoice.inputInvoiceStarGiftAuctionBid(Cons_inputInvoiceStarGiftAuctionBid(flags: _1!, peer: _2, giftId: _3!, bidAmount: _4!, message: _5)) } diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 5c9e6d7e3d..dc425a13f2 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -666,29 +666,29 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputDocument } var _3: Api.InputPhoto? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.InputPhoto } } var _4: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _4 = reader.readInt32() } var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = reader.readInt32() } var _6: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.InputMedia.inputMediaDocument(Cons_inputMediaDocument(flags: _1!, id: _2!, videoCover: _3, videoTimestamp: _4, ttlSeconds: _5, query: _6)) } @@ -702,24 +702,24 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } var _4: Api.InputPhoto? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputPhoto } } var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputMedia.inputMediaDocumentExternal(Cons_inputMediaDocumentExternal(flags: _1!, url: _2!, ttlSeconds: _3, videoCover: _4, videoTimestamp: _5)) } @@ -751,22 +751,22 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint } var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _3 = reader.readInt32() } var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = reader.readInt32() } var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputMedia.inputMediaGeoLive(Cons_inputMediaGeoLive(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5)) } @@ -795,7 +795,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.InputWebDocument? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputWebDocument } @@ -807,7 +807,7 @@ public extension Api { var _6: Buffer? _6 = parseBytes(reader) var _7: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _7 = parseString(reader) } var _8: Api.DataJSON? @@ -815,11 +815,11 @@ public extension Api { _8 = Api.parse(reader, signature: signature) as? Api.DataJSON } var _9: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _9 = parseString(reader) } var _10: Api.InputMedia? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.InputMedia } @@ -827,13 +827,13 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.InputMedia.inputMediaInvoice(Cons_inputMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7, providerData: _8!, startParam: _9, extendedMedia: _10)) } @@ -851,13 +851,13 @@ public extension Api { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputMedia.self) } var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputMedia.inputMediaPaidMedia(Cons_inputMediaPaidMedia(flags: _1!, starsAmount: _2!, extendedMedia: _3!, payload: _4)) } @@ -873,19 +873,19 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputPhoto } var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } var _4: Api.InputDocument? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputDocument } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputMedia.inputMediaPhoto(Cons_inputMediaPhoto(flags: _1!, id: _2!, ttlSeconds: _3, video: _4)) } @@ -899,12 +899,12 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.InputMedia.inputMediaPhotoExternal(Cons_inputMediaPhotoExternal(flags: _1!, url: _2!, ttlSeconds: _3)) } @@ -920,40 +920,40 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.Poll } var _3: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } var _4: Api.InputMedia? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputMedia } } var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _5 = parseString(reader) } var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _7: Api.InputMedia? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.InputMedia } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.InputMedia.inputMediaPoll(Cons_inputMediaPoll(flags: _1!, poll: _2!, correctAnswers: _3, attachedMedia: _4, solution: _5, solutionEntities: _6, solutionMedia: _7)) } @@ -1015,7 +1015,7 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputFile } var _3: Api.InputFile? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.InputFile } @@ -1027,34 +1027,34 @@ public extension Api { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) } var _6: [Api.InputDocument]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) } } var _7: Api.InputPhoto? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.InputPhoto } } var _8: Int32? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _8 = reader.readInt32() } var _9: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _9 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.InputMedia.inputMediaUploadedDocument(Cons_inputMediaUploadedDocument(flags: _1!, file: _2!, thumb: _3, mimeType: _4!, attributes: _5!, stickers: _6, videoCover: _7, videoTimestamp: _8, ttlSeconds: _9)) } @@ -1070,26 +1070,26 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputFile } var _3: [Api.InputDocument]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) } } var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = reader.readInt32() } var _5: Api.InputDocument? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.InputDocument } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputMedia.inputMediaUploadedPhoto(Cons_inputMediaUploadedPhoto(flags: _1!, file: _2!, stickers: _3, ttlSeconds: _4, video: _5)) } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 0fd5371719..89a9c99847 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -540,53 +540,53 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Bool } } var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.Bool } } var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _4 = reader.readInt32() } var _5: Api.NotificationSound? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound } } var _6: Api.Bool? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.Bool } } var _7: Api.Bool? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.Bool } } var _8: Api.NotificationSound? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.NotificationSound } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.InputPeerNotifySettings.inputPeerNotifySettings(Cons_inputPeerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, sound: _5, storiesMuted: _6, storiesHideSender: _7, storiesSound: _8)) } diff --git a/submodules/TelegramApi/Sources/Api13.swift b/submodules/TelegramApi/Sources/Api13.swift index 1501d492d3..2373270d99 100644 --- a/submodules/TelegramApi/Sources/Api13.swift +++ b/submodules/TelegramApi/Sources/Api13.swift @@ -194,53 +194,53 @@ public extension Api { var _2: Int32? _2 = reader.readInt32() var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } var _4: Api.InputPeer? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputPeer } } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _7: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _7 = reader.readInt32() } var _8: Api.InputPeer? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.InputPeer } } var _9: Int32? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { _9 = reader.readInt32() } var _10: Buffer? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _10 = parseBytes(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.InputReplyTo.inputReplyToMessage(Cons_inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6, quoteOffset: _7, monoforumPeerId: _8, todoItemId: _9, pollOption: _10)) } @@ -279,6 +279,276 @@ public extension Api { } } } +public extension Api { + enum InputRichMessage: TypeConstructorDescription { + public class Cons_inputRichMessage: TypeConstructorDescription { + public var flags: Int32 + public var blocks: [Api.PageBlock] + public var photos: [Api.InputPhoto]? + public var documents: [Api.InputDocument]? + public var users: [Api.InputUser]? + public init(flags: Int32, blocks: [Api.PageBlock], photos: [Api.InputPhoto]?, documents: [Api.InputDocument]?, users: [Api.InputUser]?) { + self.flags = flags + self.blocks = blocks + self.photos = photos + self.documents = documents + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputRichMessage", [("flags", ConstructorParameterDescription(self.flags)), ("blocks", ConstructorParameterDescription(self.blocks)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_inputRichMessageHTML: TypeConstructorDescription { + public var flags: Int32 + public var html: String + public var photos: [Api.InputPhoto]? + public var documents: [Api.InputDocument]? + public var users: [Api.InputUser]? + public init(flags: Int32, html: String, photos: [Api.InputPhoto]?, documents: [Api.InputDocument]?, users: [Api.InputUser]?) { + self.flags = flags + self.html = html + self.photos = photos + self.documents = documents + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputRichMessageHTML", [("flags", ConstructorParameterDescription(self.flags)), ("html", ConstructorParameterDescription(self.html)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_inputRichMessageMarkdown: TypeConstructorDescription { + public var flags: Int32 + public var markdown: String + public var photos: [Api.InputPhoto]? + public var documents: [Api.InputDocument]? + public var users: [Api.InputUser]? + public init(flags: Int32, markdown: String, photos: [Api.InputPhoto]?, documents: [Api.InputDocument]?, users: [Api.InputUser]?) { + self.flags = flags + self.markdown = markdown + self.photos = photos + self.documents = documents + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputRichMessageMarkdown", [("flags", ConstructorParameterDescription(self.flags)), ("markdown", ConstructorParameterDescription(self.markdown)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case inputRichMessage(Cons_inputRichMessage) + case inputRichMessageHTML(Cons_inputRichMessageHTML) + case inputRichMessageMarkdown(Cons_inputRichMessageMarkdown) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputRichMessage(let _data): + if boxed { + buffer.appendInt32(-456898052) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos!.count)) + for item in _data.photos! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents!.count)) + for item in _data.documents! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users!.count)) + for item in _data.users! { + item.serialize(buffer, true) + } + } + break + case .inputRichMessageHTML(let _data): + if boxed { + buffer.appendInt32(-722815663) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.html, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos!.count)) + for item in _data.photos! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents!.count)) + for item in _data.documents! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users!.count)) + for item in _data.users! { + item.serialize(buffer, true) + } + } + break + case .inputRichMessageMarkdown(let _data): + if boxed { + buffer.appendInt32(162300294) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.markdown, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos!.count)) + for item in _data.photos! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents!.count)) + for item in _data.documents! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users!.count)) + for item in _data.users! { + item.serialize(buffer, true) + } + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputRichMessage(let _data): + return ("inputRichMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents)), ("users", ConstructorParameterDescription(_data.users))]) + case .inputRichMessageHTML(let _data): + return ("inputRichMessageHTML", [("flags", ConstructorParameterDescription(_data.flags)), ("html", ConstructorParameterDescription(_data.html)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents)), ("users", ConstructorParameterDescription(_data.users))]) + case .inputRichMessageMarkdown(let _data): + return ("inputRichMessageMarkdown", [("flags", ConstructorParameterDescription(_data.flags)), ("markdown", ConstructorParameterDescription(_data.markdown)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_inputRichMessage(_ reader: BufferReader) -> InputRichMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PageBlock]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _3: [Api.InputPhoto]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPhoto.self) + } + } + var _4: [Api.InputDocument]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) + } + } + var _5: [Api.InputUser]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputRichMessage.inputRichMessage(Cons_inputRichMessage(flags: _1!, blocks: _2!, photos: _3, documents: _4, users: _5)) + } + else { + return nil + } + } + public static func parse_inputRichMessageHTML(_ reader: BufferReader) -> InputRichMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: [Api.InputPhoto]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPhoto.self) + } + } + var _4: [Api.InputDocument]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) + } + } + var _5: [Api.InputUser]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputRichMessage.inputRichMessageHTML(Cons_inputRichMessageHTML(flags: _1!, html: _2!, photos: _3, documents: _4, users: _5)) + } + else { + return nil + } + } + public static func parse_inputRichMessageMarkdown(_ reader: BufferReader) -> InputRichMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: [Api.InputPhoto]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPhoto.self) + } + } + var _4: [Api.InputDocument]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputDocument.self) + } + } + var _5: [Api.InputUser]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputRichMessage.inputRichMessageMarkdown(Cons_inputRichMessageMarkdown(flags: _1!, markdown: _2!, photos: _3, documents: _4, users: _5)) + } + else { + return nil + } + } + } +} public extension Api { indirect enum InputSavedStarGift: TypeConstructorDescription { public class Cons_inputSavedStarGiftChat: TypeConstructorDescription { @@ -577,56 +847,56 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType } var _3: Api.SecureData? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.SecureData } } var _4: Api.InputSecureFile? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputSecureFile } } var _5: Api.InputSecureFile? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.InputSecureFile } } var _6: Api.InputSecureFile? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.InputSecureFile } } var _7: [Api.InputSecureFile]? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let _ = reader.readInt32() { _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self) } } var _8: [Api.InputSecureFile]? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let _ = reader.readInt32() { _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputSecureFile.self) } } var _9: Api.SecurePlainData? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.InputSecureValue.inputSecureValue(Cons_inputSecureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9)) } @@ -697,7 +967,7 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } @@ -706,7 +976,7 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputSingleMedia.inputSingleMedia(Cons_inputSingleMedia(flags: _1!, media: _2!, randomId: _3!, message: _4!, entities: _5)) } @@ -1103,20 +1373,20 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.MaskCoords? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.MaskCoords } } var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _5 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputStickerSetItem.inputStickerSetItem(Cons_inputStickerSetItem(flags: _1!, document: _2!, emoji: _3!, maskCoords: _4, keywords: _5)) } @@ -1203,546 +1473,3 @@ public extension Api { } } } -public extension Api { - indirect enum InputStorePaymentPurpose: TypeConstructorDescription { - public class Cons_inputStorePaymentAuthCode: TypeConstructorDescription { - public var flags: Int32 - public var phoneNumber: String - public var phoneCodeHash: String - public var currency: String - public var amount: Int64 - public init(flags: Int32, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64) { - self.flags = flags - self.phoneNumber = phoneNumber - self.phoneCodeHash = phoneCodeHash - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentAuthCode", [("flags", ConstructorParameterDescription(self.flags)), ("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - public class Cons_inputStorePaymentGiftPremium: TypeConstructorDescription { - public var userId: Api.InputUser - public var currency: String - public var amount: Int64 - public init(userId: Api.InputUser, currency: String, amount: Int64) { - self.userId = userId - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentGiftPremium", [("userId", ConstructorParameterDescription(self.userId)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - public class Cons_inputStorePaymentPremiumGiftCode: TypeConstructorDescription { - public var flags: Int32 - public var users: [Api.InputUser] - public var boostPeer: Api.InputPeer? - public var currency: String - public var amount: Int64 - public var message: Api.TextWithEntities? - public init(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64, message: Api.TextWithEntities?) { - self.flags = flags - self.users = users - self.boostPeer = boostPeer - self.currency = currency - self.amount = amount - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentPremiumGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_inputStorePaymentPremiumGiveaway: TypeConstructorDescription { - public var flags: Int32 - public var boostPeer: Api.InputPeer - public var additionalPeers: [Api.InputPeer]? - public var countriesIso2: [String]? - public var prizeDescription: String? - public var randomId: Int64 - public var untilDate: Int32 - public var currency: String - public var amount: Int64 - public init(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) { - self.flags = flags - self.boostPeer = boostPeer - self.additionalPeers = additionalPeers - self.countriesIso2 = countriesIso2 - self.prizeDescription = prizeDescription - self.randomId = randomId - self.untilDate = untilDate - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentPremiumGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("additionalPeers", ConstructorParameterDescription(self.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("randomId", ConstructorParameterDescription(self.randomId)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - public class Cons_inputStorePaymentPremiumSubscription: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentPremiumSubscription", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_inputStorePaymentStarsGift: TypeConstructorDescription { - public var userId: Api.InputUser - public var stars: Int64 - public var currency: String - public var amount: Int64 - public init(userId: Api.InputUser, stars: Int64, currency: String, amount: Int64) { - self.userId = userId - self.stars = stars - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentStarsGift", [("userId", ConstructorParameterDescription(self.userId)), ("stars", ConstructorParameterDescription(self.stars)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - public class Cons_inputStorePaymentStarsGiveaway: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var boostPeer: Api.InputPeer - public var additionalPeers: [Api.InputPeer]? - public var countriesIso2: [String]? - public var prizeDescription: String? - public var randomId: Int64 - public var untilDate: Int32 - public var currency: String - public var amount: Int64 - public var users: Int32 - public init(flags: Int32, stars: Int64, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32) { - self.flags = flags - self.stars = stars - self.boostPeer = boostPeer - self.additionalPeers = additionalPeers - self.countriesIso2 = countriesIso2 - self.prizeDescription = prizeDescription - self.randomId = randomId - self.untilDate = untilDate - self.currency = currency - self.amount = amount - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentStarsGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("additionalPeers", ConstructorParameterDescription(self.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("randomId", ConstructorParameterDescription(self.randomId)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_inputStorePaymentStarsTopup: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var currency: String - public var amount: Int64 - public var spendPurposePeer: Api.InputPeer? - public init(flags: Int32, stars: Int64, currency: String, amount: Int64, spendPurposePeer: Api.InputPeer?) { - self.flags = flags - self.stars = stars - self.currency = currency - self.amount = amount - self.spendPurposePeer = spendPurposePeer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputStorePaymentStarsTopup", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("spendPurposePeer", ConstructorParameterDescription(self.spendPurposePeer))]) - } - } - case inputStorePaymentAuthCode(Cons_inputStorePaymentAuthCode) - case inputStorePaymentGiftPremium(Cons_inputStorePaymentGiftPremium) - case inputStorePaymentPremiumGiftCode(Cons_inputStorePaymentPremiumGiftCode) - case inputStorePaymentPremiumGiveaway(Cons_inputStorePaymentPremiumGiveaway) - case inputStorePaymentPremiumSubscription(Cons_inputStorePaymentPremiumSubscription) - case inputStorePaymentStarsGift(Cons_inputStorePaymentStarsGift) - case inputStorePaymentStarsGiveaway(Cons_inputStorePaymentStarsGiveaway) - case inputStorePaymentStarsTopup(Cons_inputStorePaymentStarsTopup) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStorePaymentAuthCode(let _data): - if boxed { - buffer.appendInt32(-1682807955) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.phoneNumber, buffer: buffer, boxed: false) - serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - case .inputStorePaymentGiftPremium(let _data): - if boxed { - buffer.appendInt32(1634697192) - } - _data.userId.serialize(buffer, true) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - case .inputStorePaymentPremiumGiftCode(let _data): - if boxed { - buffer.appendInt32(-75955309) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.boostPeer!.serialize(buffer, true) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.message!.serialize(buffer, true) - } - break - case .inputStorePaymentPremiumGiveaway(let _data): - if boxed { - buffer.appendInt32(369444042) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.boostPeer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.additionalPeers!.count)) - for item in _data.additionalPeers! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.countriesIso2!.count)) - for item in _data.countriesIso2! { - serializeString(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) - } - serializeInt64(_data.randomId, buffer: buffer, boxed: false) - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - case .inputStorePaymentPremiumSubscription(let _data): - if boxed { - buffer.appendInt32(-1502273946) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .inputStorePaymentStarsGift(let _data): - if boxed { - buffer.appendInt32(494149367) - } - _data.userId.serialize(buffer, true) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - case .inputStorePaymentStarsGiveaway(let _data): - if boxed { - buffer.appendInt32(1964968186) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - _data.boostPeer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.additionalPeers!.count)) - for item in _data.additionalPeers! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.countriesIso2!.count)) - for item in _data.countriesIso2! { - serializeString(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) - } - serializeInt64(_data.randomId, buffer: buffer, boxed: false) - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeInt32(_data.users, buffer: buffer, boxed: false) - break - case .inputStorePaymentStarsTopup(let _data): - if boxed { - buffer.appendInt32(-106780981) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.spendPurposePeer!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputStorePaymentAuthCode(let _data): - return ("inputStorePaymentAuthCode", [("flags", ConstructorParameterDescription(_data.flags)), ("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - case .inputStorePaymentGiftPremium(let _data): - return ("inputStorePaymentGiftPremium", [("userId", ConstructorParameterDescription(_data.userId)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - case .inputStorePaymentPremiumGiftCode(let _data): - return ("inputStorePaymentPremiumGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("message", ConstructorParameterDescription(_data.message))]) - case .inputStorePaymentPremiumGiveaway(let _data): - return ("inputStorePaymentPremiumGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("additionalPeers", ConstructorParameterDescription(_data.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - case .inputStorePaymentPremiumSubscription(let _data): - return ("inputStorePaymentPremiumSubscription", [("flags", ConstructorParameterDescription(_data.flags))]) - case .inputStorePaymentStarsGift(let _data): - return ("inputStorePaymentStarsGift", [("userId", ConstructorParameterDescription(_data.userId)), ("stars", ConstructorParameterDescription(_data.stars)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - case .inputStorePaymentStarsGiveaway(let _data): - return ("inputStorePaymentStarsGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("additionalPeers", ConstructorParameterDescription(_data.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("users", ConstructorParameterDescription(_data.users))]) - case .inputStorePaymentStarsTopup(let _data): - return ("inputStorePaymentStarsTopup", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("spendPurposePeer", ConstructorParameterDescription(_data.spendPurposePeer))]) - } - } - - public static func parse_inputStorePaymentAuthCode(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(Cons_inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, currency: _4!, amount: _5!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Api.InputUser? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputUser - } - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputStorePaymentPurpose.inputStorePaymentGiftPremium(Cons_inputStorePaymentGiftPremium(userId: _1!, currency: _2!, amount: _3!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentPremiumGiftCode(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.InputUser]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) - } - var _3: Api.InputPeer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - } - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(Cons_inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!, message: _6)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentPremiumGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _3: [Api.InputPeer]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) - } - } - var _4: [String]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - } - var _5: String? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = parseString(reader) - } - var _6: Int64? - _6 = reader.readInt64() - var _7: Int32? - _7 = reader.readInt32() - var _8: String? - _8 = parseString(reader) - var _9: Int64? - _9 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(Cons_inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, prizeDescription: _5, randomId: _6!, untilDate: _7!, currency: _8!, amount: _9!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentPremiumSubscription(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription(Cons_inputStorePaymentPremiumSubscription(flags: _1!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentStarsGift(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Api.InputUser? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputUser - } - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Int64? - _4 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsGift(Cons_inputStorePaymentStarsGift(userId: _1!, stars: _2!, currency: _3!, amount: _4!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentStarsGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.InputPeer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - var _4: [Api.InputPeer]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) - } - } - var _5: [String]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - } - var _6: String? - if Int(_1!) & Int(1 << 4) != 0 { - _6 = parseString(reader) - } - var _7: Int64? - _7 = reader.readInt64() - var _8: Int32? - _8 = reader.readInt32() - var _9: String? - _9 = parseString(reader) - var _10: Int64? - _10 = reader.readInt64() - var _11: Int32? - _11 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsGiveaway(Cons_inputStorePaymentStarsGiveaway(flags: _1!, stars: _2!, boostPeer: _3!, additionalPeers: _4, countriesIso2: _5, prizeDescription: _6, randomId: _7!, untilDate: _8!, currency: _9!, amount: _10!, users: _11!)) - } - else { - return nil - } - } - public static func parse_inputStorePaymentStarsTopup(_ reader: BufferReader) -> InputStorePaymentPurpose? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Int64? - _4 = reader.readInt64() - var _5: Api.InputPeer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.InputPeer - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(Cons_inputStorePaymentStarsTopup(flags: _1!, stars: _2!, currency: _3!, amount: _4!, spendPurposePeer: _5)) - } - else { - return nil - } - } - } -} diff --git a/submodules/TelegramApi/Sources/Api14.swift b/submodules/TelegramApi/Sources/Api14.swift index 3d3e270c25..21323b5388 100644 --- a/submodules/TelegramApi/Sources/Api14.swift +++ b/submodules/TelegramApi/Sources/Api14.swift @@ -1,3 +1,552 @@ +public extension Api { + indirect enum InputStorePaymentPurpose: TypeConstructorDescription { + public class Cons_inputStorePaymentAuthCode: TypeConstructorDescription { + public var flags: Int32 + public var phoneNumber: String + public var phoneCodeHash: String + public var premiumDays: Int32 + public var currency: String + public var amount: Int64 + public init(flags: Int32, phoneNumber: String, phoneCodeHash: String, premiumDays: Int32, currency: String, amount: Int64) { + self.flags = flags + self.phoneNumber = phoneNumber + self.phoneCodeHash = phoneCodeHash + self.premiumDays = premiumDays + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentAuthCode", [("flags", ConstructorParameterDescription(self.flags)), ("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("premiumDays", ConstructorParameterDescription(self.premiumDays)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + public class Cons_inputStorePaymentGiftPremium: TypeConstructorDescription { + public var userId: Api.InputUser + public var currency: String + public var amount: Int64 + public init(userId: Api.InputUser, currency: String, amount: Int64) { + self.userId = userId + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentGiftPremium", [("userId", ConstructorParameterDescription(self.userId)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + public class Cons_inputStorePaymentPremiumGiftCode: TypeConstructorDescription { + public var flags: Int32 + public var users: [Api.InputUser] + public var boostPeer: Api.InputPeer? + public var currency: String + public var amount: Int64 + public var message: Api.TextWithEntities? + public init(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64, message: Api.TextWithEntities?) { + self.flags = flags + self.users = users + self.boostPeer = boostPeer + self.currency = currency + self.amount = amount + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentPremiumGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_inputStorePaymentPremiumGiveaway: TypeConstructorDescription { + public var flags: Int32 + public var boostPeer: Api.InputPeer + public var additionalPeers: [Api.InputPeer]? + public var countriesIso2: [String]? + public var prizeDescription: String? + public var randomId: Int64 + public var untilDate: Int32 + public var currency: String + public var amount: Int64 + public init(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) { + self.flags = flags + self.boostPeer = boostPeer + self.additionalPeers = additionalPeers + self.countriesIso2 = countriesIso2 + self.prizeDescription = prizeDescription + self.randomId = randomId + self.untilDate = untilDate + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentPremiumGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("additionalPeers", ConstructorParameterDescription(self.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("randomId", ConstructorParameterDescription(self.randomId)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + public class Cons_inputStorePaymentPremiumSubscription: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentPremiumSubscription", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_inputStorePaymentStarsGift: TypeConstructorDescription { + public var userId: Api.InputUser + public var stars: Int64 + public var currency: String + public var amount: Int64 + public init(userId: Api.InputUser, stars: Int64, currency: String, amount: Int64) { + self.userId = userId + self.stars = stars + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentStarsGift", [("userId", ConstructorParameterDescription(self.userId)), ("stars", ConstructorParameterDescription(self.stars)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + public class Cons_inputStorePaymentStarsGiveaway: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var boostPeer: Api.InputPeer + public var additionalPeers: [Api.InputPeer]? + public var countriesIso2: [String]? + public var prizeDescription: String? + public var randomId: Int64 + public var untilDate: Int32 + public var currency: String + public var amount: Int64 + public var users: Int32 + public init(flags: Int32, stars: Int64, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32) { + self.flags = flags + self.stars = stars + self.boostPeer = boostPeer + self.additionalPeers = additionalPeers + self.countriesIso2 = countriesIso2 + self.prizeDescription = prizeDescription + self.randomId = randomId + self.untilDate = untilDate + self.currency = currency + self.amount = amount + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentStarsGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("additionalPeers", ConstructorParameterDescription(self.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("randomId", ConstructorParameterDescription(self.randomId)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_inputStorePaymentStarsTopup: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var currency: String + public var amount: Int64 + public var spendPurposePeer: Api.InputPeer? + public init(flags: Int32, stars: Int64, currency: String, amount: Int64, spendPurposePeer: Api.InputPeer?) { + self.flags = flags + self.stars = stars + self.currency = currency + self.amount = amount + self.spendPurposePeer = spendPurposePeer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputStorePaymentStarsTopup", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("spendPurposePeer", ConstructorParameterDescription(self.spendPurposePeer))]) + } + } + case inputStorePaymentAuthCode(Cons_inputStorePaymentAuthCode) + case inputStorePaymentGiftPremium(Cons_inputStorePaymentGiftPremium) + case inputStorePaymentPremiumGiftCode(Cons_inputStorePaymentPremiumGiftCode) + case inputStorePaymentPremiumGiveaway(Cons_inputStorePaymentPremiumGiveaway) + case inputStorePaymentPremiumSubscription(Cons_inputStorePaymentPremiumSubscription) + case inputStorePaymentStarsGift(Cons_inputStorePaymentStarsGift) + case inputStorePaymentStarsGiveaway(Cons_inputStorePaymentStarsGiveaway) + case inputStorePaymentStarsTopup(Cons_inputStorePaymentStarsTopup) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStorePaymentAuthCode(let _data): + if boxed { + buffer.appendInt32(1069645911) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.phoneNumber, buffer: buffer, boxed: false) + serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) + serializeInt32(_data.premiumDays, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + case .inputStorePaymentGiftPremium(let _data): + if boxed { + buffer.appendInt32(1634697192) + } + _data.userId.serialize(buffer, true) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + case .inputStorePaymentPremiumGiftCode(let _data): + if boxed { + buffer.appendInt32(-75955309) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.boostPeer!.serialize(buffer, true) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.message!.serialize(buffer, true) + } + break + case .inputStorePaymentPremiumGiveaway(let _data): + if boxed { + buffer.appendInt32(369444042) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.boostPeer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.additionalPeers!.count)) + for item in _data.additionalPeers! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.countriesIso2!.count)) + for item in _data.countriesIso2! { + serializeString(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) + } + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + case .inputStorePaymentPremiumSubscription(let _data): + if boxed { + buffer.appendInt32(-1502273946) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .inputStorePaymentStarsGift(let _data): + if boxed { + buffer.appendInt32(494149367) + } + _data.userId.serialize(buffer, true) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + case .inputStorePaymentStarsGiveaway(let _data): + if boxed { + buffer.appendInt32(1964968186) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + _data.boostPeer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.additionalPeers!.count)) + for item in _data.additionalPeers! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.countriesIso2!.count)) + for item in _data.countriesIso2! { + serializeString(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) + } + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeInt32(_data.users, buffer: buffer, boxed: false) + break + case .inputStorePaymentStarsTopup(let _data): + if boxed { + buffer.appendInt32(-106780981) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.spendPurposePeer!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputStorePaymentAuthCode(let _data): + return ("inputStorePaymentAuthCode", [("flags", ConstructorParameterDescription(_data.flags)), ("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("premiumDays", ConstructorParameterDescription(_data.premiumDays)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .inputStorePaymentGiftPremium(let _data): + return ("inputStorePaymentGiftPremium", [("userId", ConstructorParameterDescription(_data.userId)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .inputStorePaymentPremiumGiftCode(let _data): + return ("inputStorePaymentPremiumGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("message", ConstructorParameterDescription(_data.message))]) + case .inputStorePaymentPremiumGiveaway(let _data): + return ("inputStorePaymentPremiumGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("additionalPeers", ConstructorParameterDescription(_data.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .inputStorePaymentPremiumSubscription(let _data): + return ("inputStorePaymentPremiumSubscription", [("flags", ConstructorParameterDescription(_data.flags))]) + case .inputStorePaymentStarsGift(let _data): + return ("inputStorePaymentStarsGift", [("userId", ConstructorParameterDescription(_data.userId)), ("stars", ConstructorParameterDescription(_data.stars)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .inputStorePaymentStarsGiveaway(let _data): + return ("inputStorePaymentStarsGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("additionalPeers", ConstructorParameterDescription(_data.additionalPeers)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("users", ConstructorParameterDescription(_data.users))]) + case .inputStorePaymentStarsTopup(let _data): + return ("inputStorePaymentStarsTopup", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("spendPurposePeer", ConstructorParameterDescription(_data.spendPurposePeer))]) + } + } + + public static func parse_inputStorePaymentAuthCode(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int64? + _6 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(Cons_inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, premiumDays: _4!, currency: _5!, amount: _6!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Api.InputUser? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputUser + } + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputStorePaymentPurpose.inputStorePaymentGiftPremium(Cons_inputStorePaymentGiftPremium(userId: _1!, currency: _2!, amount: _3!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentPremiumGiftCode(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.InputUser]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) + } + var _3: Api.InputPeer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + } + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(Cons_inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!, message: _6)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentPremiumGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _3: [Api.InputPeer]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } + } + var _4: [String]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = parseString(reader) + } + var _6: Int64? + _6 = reader.readInt64() + var _7: Int32? + _7 = reader.readInt32() + var _8: String? + _8 = parseString(reader) + var _9: Int64? + _9 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(Cons_inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, countriesIso2: _4, prizeDescription: _5, randomId: _6!, untilDate: _7!, currency: _8!, amount: _9!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentPremiumSubscription(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription(Cons_inputStorePaymentPremiumSubscription(flags: _1!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentStarsGift(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Api.InputUser? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputUser + } + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputStorePaymentPurpose.inputStorePaymentStarsGift(Cons_inputStorePaymentStarsGift(userId: _1!, stars: _2!, currency: _3!, amount: _4!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentStarsGiveaway(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.InputPeer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + var _4: [Api.InputPeer]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } + } + var _5: [String]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _6 = parseString(reader) + } + var _7: Int64? + _7 = reader.readInt64() + var _8: Int32? + _8 = reader.readInt32() + var _9: String? + _9 = parseString(reader) + var _10: Int64? + _10 = reader.readInt64() + var _11: Int32? + _11 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.InputStorePaymentPurpose.inputStorePaymentStarsGiveaway(Cons_inputStorePaymentStarsGiveaway(flags: _1!, stars: _2!, boostPeer: _3!, additionalPeers: _4, countriesIso2: _5, prizeDescription: _6, randomId: _7!, untilDate: _8!, currency: _9!, amount: _10!, users: _11!)) + } + else { + return nil + } + } + public static func parse_inputStorePaymentStarsTopup(_ reader: BufferReader) -> InputStorePaymentPurpose? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + var _5: Api.InputPeer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.InputStorePaymentPurpose.inputStorePaymentStarsTopup(Cons_inputStorePaymentStarsTopup(flags: _1!, stars: _2!, currency: _3!, amount: _4!, spendPurposePeer: _5)) + } + else { + return nil + } + } + } +} public extension Api { enum InputTheme: TypeConstructorDescription { public class Cons_inputTheme: TypeConstructorDescription { @@ -148,23 +697,23 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _4 = reader.readInt32() } var _5: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } var _6: Api.InputWallPaper? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.InputWallPaper } } var _7: Api.WallPaperSettings? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings } @@ -172,10 +721,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.InputThemeSettings.inputThemeSettings(Cons_inputThemeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6, wallpaperSettings: _7)) } @@ -580,23 +1129,23 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.InputDocument? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.InputDocument } } var _3: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _3 = parseString(reader) } var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputWebFileLocation.inputWebFileAudioAlbumThumbLocation(Cons_inputWebFileAudioAlbumThumbLocation(flags: _1!, document: _2, title: _3, performer: _4)) } @@ -723,30 +1272,30 @@ public extension Api { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) } var _4: Int64? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { _4 = reader.readInt64() } var _5: [Int64]? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } } var _6: String? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { _6 = parseString(reader) } var _7: Int32? - if Int(_1!) & Int(1 << 11) != 0 { + if Int(_1 ?? 0) & Int(1 << 11) != 0 { _7 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 10) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.Invoice.invoice(Cons_invoice(flags: _1!, currency: _2!, prices: _3!, maxTipAmount: _4, suggestedTipAmounts: _5, termsUrl: _6, subscriptionPeriod: _7)) } @@ -994,518 +1543,42 @@ public extension Api { } } public extension Api { - indirect enum KeyboardButton: TypeConstructorDescription { - public class Cons_inputKeyboardButtonRequestPeer: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var buttonId: Int32 - public var peerType: Api.RequestPeerType - public var maxQuantity: Int32 - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) { - self.flags = flags - self.style = style - self.text = text - self.buttonId = buttonId - self.peerType = peerType - self.maxQuantity = maxQuantity - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputKeyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("buttonId", ConstructorParameterDescription(self.buttonId)), ("peerType", ConstructorParameterDescription(self.peerType)), ("maxQuantity", ConstructorParameterDescription(self.maxQuantity))]) - } - } - public class Cons_inputKeyboardButtonUrlAuth: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var fwdText: String? + enum JoinChatBotResult: TypeConstructorDescription { + public class Cons_joinChatBotResultWebView: TypeConstructorDescription { public var url: String - public var bot: Api.InputUser - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, fwdText: String?, url: String, bot: Api.InputUser) { - self.flags = flags - self.style = style - self.text = text - self.fwdText = fwdText - self.url = url - self.bot = bot - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputKeyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("fwdText", ConstructorParameterDescription(self.fwdText)), ("url", ConstructorParameterDescription(self.url)), ("bot", ConstructorParameterDescription(self.bot))]) - } - } - public class Cons_inputKeyboardButtonUserProfile: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var userId: Api.InputUser - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, userId: Api.InputUser) { - self.flags = flags - self.style = style - self.text = text - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputKeyboardButtonUserProfile", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_keyboardButton: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { - self.flags = flags - self.style = style - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButton", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonBuy: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { - self.flags = flags - self.style = style - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonBuy", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonCallback: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var data: Buffer - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, data: Buffer) { - self.flags = flags - self.style = style - self.text = text - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonCallback", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("data", ConstructorParameterDescription(self.data))]) - } - } - public class Cons_keyboardButtonCopy: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var copyText: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, copyText: String) { - self.flags = flags - self.style = style - self.text = text - self.copyText = copyText - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonCopy", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("copyText", ConstructorParameterDescription(self.copyText))]) - } - } - public class Cons_keyboardButtonGame: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { - self.flags = flags - self.style = style - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonGame", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonRequestGeoLocation: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { - self.flags = flags - self.style = style - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonRequestGeoLocation", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonRequestPeer: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var buttonId: Int32 - public var peerType: Api.RequestPeerType - public var maxQuantity: Int32 - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) { - self.flags = flags - self.style = style - self.text = text - self.buttonId = buttonId - self.peerType = peerType - self.maxQuantity = maxQuantity - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("buttonId", ConstructorParameterDescription(self.buttonId)), ("peerType", ConstructorParameterDescription(self.peerType)), ("maxQuantity", ConstructorParameterDescription(self.maxQuantity))]) - } - } - public class Cons_keyboardButtonRequestPhone: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { - self.flags = flags - self.style = style - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonRequestPhone", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonRequestPoll: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var quiz: Api.Bool? - public var text: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, quiz: Api.Bool?, text: String) { - self.flags = flags - self.style = style - self.quiz = quiz - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonRequestPoll", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("quiz", ConstructorParameterDescription(self.quiz)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_keyboardButtonSimpleWebView: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var url: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { - self.flags = flags - self.style = style - self.text = text + public init(url: String) { self.url = url } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonSimpleWebView", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) + return ("joinChatBotResultWebView", [("url", ConstructorParameterDescription(self.url))]) } } - public class Cons_keyboardButtonSwitchInline: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var query: String - public var peerTypes: [Api.InlineQueryPeerType]? - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, query: String, peerTypes: [Api.InlineQueryPeerType]?) { - self.flags = flags - self.style = style - self.text = text - self.query = query - self.peerTypes = peerTypes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonSwitchInline", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("query", ConstructorParameterDescription(self.query)), ("peerTypes", ConstructorParameterDescription(self.peerTypes))]) - } - } - public class Cons_keyboardButtonUrl: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var url: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { - self.flags = flags - self.style = style - self.text = text - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonUrl", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) - } - } - public class Cons_keyboardButtonUrlAuth: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var fwdText: String? - public var url: String - public var buttonId: Int32 - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, fwdText: String?, url: String, buttonId: Int32) { - self.flags = flags - self.style = style - self.text = text - self.fwdText = fwdText - self.url = url - self.buttonId = buttonId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("fwdText", ConstructorParameterDescription(self.fwdText)), ("url", ConstructorParameterDescription(self.url)), ("buttonId", ConstructorParameterDescription(self.buttonId))]) - } - } - public class Cons_keyboardButtonUserProfile: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var userId: Int64 - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, userId: Int64) { - self.flags = flags - self.style = style - self.text = text - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonUserProfile", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_keyboardButtonWebView: TypeConstructorDescription { - public var flags: Int32 - public var style: Api.KeyboardButtonStyle? - public var text: String - public var url: String - public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { - self.flags = flags - self.style = style - self.text = text - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("keyboardButtonWebView", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) - } - } - case inputKeyboardButtonRequestPeer(Cons_inputKeyboardButtonRequestPeer) - case inputKeyboardButtonUrlAuth(Cons_inputKeyboardButtonUrlAuth) - case inputKeyboardButtonUserProfile(Cons_inputKeyboardButtonUserProfile) - case keyboardButton(Cons_keyboardButton) - case keyboardButtonBuy(Cons_keyboardButtonBuy) - case keyboardButtonCallback(Cons_keyboardButtonCallback) - case keyboardButtonCopy(Cons_keyboardButtonCopy) - case keyboardButtonGame(Cons_keyboardButtonGame) - case keyboardButtonRequestGeoLocation(Cons_keyboardButtonRequestGeoLocation) - case keyboardButtonRequestPeer(Cons_keyboardButtonRequestPeer) - case keyboardButtonRequestPhone(Cons_keyboardButtonRequestPhone) - case keyboardButtonRequestPoll(Cons_keyboardButtonRequestPoll) - case keyboardButtonSimpleWebView(Cons_keyboardButtonSimpleWebView) - case keyboardButtonSwitchInline(Cons_keyboardButtonSwitchInline) - case keyboardButtonUrl(Cons_keyboardButtonUrl) - case keyboardButtonUrlAuth(Cons_keyboardButtonUrlAuth) - case keyboardButtonUserProfile(Cons_keyboardButtonUserProfile) - case keyboardButtonWebView(Cons_keyboardButtonWebView) + case joinChatBotResultApproved + case joinChatBotResultDeclined + case joinChatBotResultQueued + case joinChatBotResultWebView(Cons_joinChatBotResultWebView) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputKeyboardButtonRequestPeer(let _data): + case .joinChatBotResultApproved: if boxed { - buffer.appendInt32(45580630) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeInt32(_data.buttonId, buffer: buffer, boxed: false) - _data.peerType.serialize(buffer, true) - serializeInt32(_data.maxQuantity, buffer: buffer, boxed: false) - break - case .inputKeyboardButtonUrlAuth(let _data): - if boxed { - buffer.appendInt32(1744911986) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.fwdText!, buffer: buffer, boxed: false) - } - serializeString(_data.url, buffer: buffer, boxed: false) - _data.bot.serialize(buffer, true) - break - case .inputKeyboardButtonUserProfile(let _data): - if boxed { - buffer.appendInt32(2103314375) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - _data.userId.serialize(buffer, true) - break - case .keyboardButton(let _data): - if boxed { - buffer.appendInt32(2098662655) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonBuy(let _data): - if boxed { - buffer.appendInt32(1067792645) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonCallback(let _data): - if boxed { - buffer.appendInt32(-433338016) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeBytes(_data.data, buffer: buffer, boxed: false) - break - case .keyboardButtonCopy(let _data): - if boxed { - buffer.appendInt32(-1127960816) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeString(_data.copyText, buffer: buffer, boxed: false) - break - case .keyboardButtonGame(let _data): - if boxed { - buffer.appendInt32(-1983540999) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestGeoLocation(let _data): - if boxed { - buffer.appendInt32(-1438582451) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPeer(let _data): - if boxed { - buffer.appendInt32(1527715317) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeInt32(_data.buttonId, buffer: buffer, boxed: false) - _data.peerType.serialize(buffer, true) - serializeInt32(_data.maxQuantity, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPhone(let _data): - if boxed { - buffer.appendInt32(1098841487) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonRequestPoll(let _data): - if boxed { - buffer.appendInt32(2047989634) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.quiz!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .keyboardButtonSimpleWebView(let _data): - if boxed { - buffer.appendInt32(-514047120) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - break - case .keyboardButtonSwitchInline(let _data): - if boxed { - buffer.appendInt32(-1726768644) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeString(_data.query, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peerTypes!.count)) - for item in _data.peerTypes! { - item.serialize(buffer, true) - } + buffer.appendInt32(-1374344599) } break - case .keyboardButtonUrl(let _data): + case .joinChatBotResultDeclined: if boxed { - buffer.appendInt32(-670292500) + buffer.appendInt32(251265428) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) break - case .keyboardButtonUrlAuth(let _data): + case .joinChatBotResultQueued: if boxed { - buffer.appendInt32(-183499015) + buffer.appendInt32(-1734105024) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.fwdText!, buffer: buffer, boxed: false) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt32(_data.buttonId, buffer: buffer, boxed: false) break - case .keyboardButtonUserProfile(let _data): + case .joinChatBotResultWebView(let _data): if boxed { - buffer.appendInt32(-1057137399) + buffer.appendInt32(-689719277) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - case .keyboardButtonWebView(let _data): - if boxed { - buffer.appendInt32(-398020192) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.style!.serialize(buffer, true) - } - serializeString(_data.text, buffer: buffer, boxed: false) serializeString(_data.url, buffer: buffer, boxed: false) break } @@ -1513,504 +1586,32 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .inputKeyboardButtonRequestPeer(let _data): - return ("inputKeyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("maxQuantity", ConstructorParameterDescription(_data.maxQuantity))]) - case .inputKeyboardButtonUrlAuth(let _data): - return ("inputKeyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("fwdText", ConstructorParameterDescription(_data.fwdText)), ("url", ConstructorParameterDescription(_data.url)), ("bot", ConstructorParameterDescription(_data.bot))]) - case .inputKeyboardButtonUserProfile(let _data): - return ("inputKeyboardButtonUserProfile", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("userId", ConstructorParameterDescription(_data.userId))]) - case .keyboardButton(let _data): - return ("keyboardButton", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonBuy(let _data): - return ("keyboardButtonBuy", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonCallback(let _data): - return ("keyboardButtonCallback", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("data", ConstructorParameterDescription(_data.data))]) - case .keyboardButtonCopy(let _data): - return ("keyboardButtonCopy", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("copyText", ConstructorParameterDescription(_data.copyText))]) - case .keyboardButtonGame(let _data): - return ("keyboardButtonGame", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonRequestGeoLocation(let _data): - return ("keyboardButtonRequestGeoLocation", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonRequestPeer(let _data): - return ("keyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("maxQuantity", ConstructorParameterDescription(_data.maxQuantity))]) - case .keyboardButtonRequestPhone(let _data): - return ("keyboardButtonRequestPhone", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonRequestPoll(let _data): - return ("keyboardButtonRequestPoll", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("quiz", ConstructorParameterDescription(_data.quiz)), ("text", ConstructorParameterDescription(_data.text))]) - case .keyboardButtonSimpleWebView(let _data): - return ("keyboardButtonSimpleWebView", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) - case .keyboardButtonSwitchInline(let _data): - return ("keyboardButtonSwitchInline", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("query", ConstructorParameterDescription(_data.query)), ("peerTypes", ConstructorParameterDescription(_data.peerTypes))]) - case .keyboardButtonUrl(let _data): - return ("keyboardButtonUrl", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) - case .keyboardButtonUrlAuth(let _data): - return ("keyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("fwdText", ConstructorParameterDescription(_data.fwdText)), ("url", ConstructorParameterDescription(_data.url)), ("buttonId", ConstructorParameterDescription(_data.buttonId))]) - case .keyboardButtonUserProfile(let _data): - return ("keyboardButtonUserProfile", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("userId", ConstructorParameterDescription(_data.userId))]) - case .keyboardButtonWebView(let _data): - return ("keyboardButtonWebView", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) + case .joinChatBotResultApproved: + return ("joinChatBotResultApproved", []) + case .joinChatBotResultDeclined: + return ("joinChatBotResultDeclined", []) + case .joinChatBotResultQueued: + return ("joinChatBotResultQueued", []) + case .joinChatBotResultWebView(let _data): + return ("joinChatBotResultWebView", [("url", ConstructorParameterDescription(_data.url))]) } } - public static func parse_inputKeyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.RequestPeerType? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.RequestPeerType - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.KeyboardButton.inputKeyboardButtonRequestPeer(Cons_inputKeyboardButtonRequestPeer(flags: _1!, style: _2, text: _3!, buttonId: _4!, peerType: _5!, maxQuantity: _6!)) - } - else { - return nil - } + public static func parse_joinChatBotResultApproved(_ reader: BufferReader) -> JoinChatBotResult? { + return Api.JoinChatBotResult.joinChatBotResultApproved } - public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) - } - var _5: String? - _5 = parseString(reader) - var _6: Api.InputUser? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.KeyboardButton.inputKeyboardButtonUrlAuth(Cons_inputKeyboardButtonUrlAuth(flags: _1!, style: _2, text: _3!, fwdText: _4, url: _5!, bot: _6!)) - } - else { - return nil - } + public static func parse_joinChatBotResultDeclined(_ reader: BufferReader) -> JoinChatBotResult? { + return Api.JoinChatBotResult.joinChatBotResultDeclined } - public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: Api.InputUser? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.inputKeyboardButtonUserProfile(Cons_inputKeyboardButtonUserProfile(flags: _1!, style: _2, text: _3!, userId: _4!)) - } - else { - return nil - } + public static func parse_joinChatBotResultQueued(_ reader: BufferReader) -> JoinChatBotResult? { + return Api.JoinChatBotResult.joinChatBotResultQueued } - public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) + public static func parse_joinChatBotResultWebView(_ reader: BufferReader) -> JoinChatBotResult? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButton(Cons_keyboardButton(flags: _1!, style: _2, text: _3!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonBuy(Cons_keyboardButtonBuy(flags: _1!, style: _2, text: _3!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonCallback(Cons_keyboardButtonCallback(flags: _1!, style: _2, text: _3!, data: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonCopy(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonCopy(Cons_keyboardButtonCopy(flags: _1!, style: _2, text: _3!, copyText: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonGame(Cons_keyboardButtonGame(flags: _1!, style: _2, text: _3!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonRequestGeoLocation(Cons_keyboardButtonRequestGeoLocation(flags: _1!, style: _2, text: _3!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.RequestPeerType? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.RequestPeerType - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.KeyboardButton.keyboardButtonRequestPeer(Cons_keyboardButtonRequestPeer(flags: _1!, style: _2, text: _3!, buttonId: _4!, peerType: _5!, maxQuantity: _6!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.KeyboardButton.keyboardButtonRequestPhone(Cons_keyboardButtonRequestPhone(flags: _1!, style: _2, text: _3!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonRequestPoll(Cons_keyboardButtonRequestPoll(flags: _1!, style: _2, quiz: _3, text: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonSimpleWebView(Cons_keyboardButtonSimpleWebView(flags: _1!, style: _2, text: _3!, url: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: [Api.InlineQueryPeerType]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.KeyboardButton.keyboardButtonSwitchInline(Cons_keyboardButtonSwitchInline(flags: _1!, style: _2, text: _3!, query: _4!, peerTypes: _5)) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonUrl(Cons_keyboardButtonUrl(flags: _1!, style: _2, text: _3!, url: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.KeyboardButton.keyboardButtonUrlAuth(Cons_keyboardButtonUrlAuth(flags: _1!, style: _2, text: _3!, fwdText: _4, url: _5!, buttonId: _6!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: Int64? - _4 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonUserProfile(Cons_keyboardButtonUserProfile(flags: _1!, style: _2, text: _3!, userId: _4!)) - } - else { - return nil - } - } - public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.KeyboardButtonStyle? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle - } - } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 10) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.KeyboardButton.keyboardButtonWebView(Cons_keyboardButtonWebView(flags: _1!, style: _2, text: _3!, url: _4!)) + if _c1 { + return Api.JoinChatBotResult.joinChatBotResultWebView(Cons_joinChatBotResultWebView(url: _1!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 2fb0d8d59e..7df4305b1c 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -1,3 +1,1028 @@ +public extension Api { + indirect enum KeyboardButton: TypeConstructorDescription { + public class Cons_inputKeyboardButtonRequestPeer: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var buttonId: Int32 + public var peerType: Api.RequestPeerType + public var maxQuantity: Int32 + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) { + self.flags = flags + self.style = style + self.text = text + self.buttonId = buttonId + self.peerType = peerType + self.maxQuantity = maxQuantity + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputKeyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("buttonId", ConstructorParameterDescription(self.buttonId)), ("peerType", ConstructorParameterDescription(self.peerType)), ("maxQuantity", ConstructorParameterDescription(self.maxQuantity))]) + } + } + public class Cons_inputKeyboardButtonUrlAuth: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var fwdText: String? + public var url: String + public var bot: Api.InputUser + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, fwdText: String?, url: String, bot: Api.InputUser) { + self.flags = flags + self.style = style + self.text = text + self.fwdText = fwdText + self.url = url + self.bot = bot + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputKeyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("fwdText", ConstructorParameterDescription(self.fwdText)), ("url", ConstructorParameterDescription(self.url)), ("bot", ConstructorParameterDescription(self.bot))]) + } + } + public class Cons_inputKeyboardButtonUserProfile: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var userId: Api.InputUser + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, userId: Api.InputUser) { + self.flags = flags + self.style = style + self.text = text + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputKeyboardButtonUserProfile", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_keyboardButton: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { + self.flags = flags + self.style = style + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButton", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonBuy: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { + self.flags = flags + self.style = style + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonBuy", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonCallback: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var data: Buffer + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, data: Buffer) { + self.flags = flags + self.style = style + self.text = text + self.data = data + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonCallback", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("data", ConstructorParameterDescription(self.data))]) + } + } + public class Cons_keyboardButtonCopy: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var copyText: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, copyText: String) { + self.flags = flags + self.style = style + self.text = text + self.copyText = copyText + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonCopy", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("copyText", ConstructorParameterDescription(self.copyText))]) + } + } + public class Cons_keyboardButtonGame: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { + self.flags = flags + self.style = style + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonGame", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonRequestGeoLocation: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { + self.flags = flags + self.style = style + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonRequestGeoLocation", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonRequestPeer: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var buttonId: Int32 + public var peerType: Api.RequestPeerType + public var maxQuantity: Int32 + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32) { + self.flags = flags + self.style = style + self.text = text + self.buttonId = buttonId + self.peerType = peerType + self.maxQuantity = maxQuantity + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("buttonId", ConstructorParameterDescription(self.buttonId)), ("peerType", ConstructorParameterDescription(self.peerType)), ("maxQuantity", ConstructorParameterDescription(self.maxQuantity))]) + } + } + public class Cons_keyboardButtonRequestPhone: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String) { + self.flags = flags + self.style = style + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonRequestPhone", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonRequestPoll: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var quiz: Api.Bool? + public var text: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, quiz: Api.Bool?, text: String) { + self.flags = flags + self.style = style + self.quiz = quiz + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonRequestPoll", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("quiz", ConstructorParameterDescription(self.quiz)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_keyboardButtonSimpleWebView: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var url: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { + self.flags = flags + self.style = style + self.text = text + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonSimpleWebView", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_keyboardButtonSwitchInline: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var query: String + public var peerTypes: [Api.InlineQueryPeerType]? + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, query: String, peerTypes: [Api.InlineQueryPeerType]?) { + self.flags = flags + self.style = style + self.text = text + self.query = query + self.peerTypes = peerTypes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonSwitchInline", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("query", ConstructorParameterDescription(self.query)), ("peerTypes", ConstructorParameterDescription(self.peerTypes))]) + } + } + public class Cons_keyboardButtonUrl: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var url: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { + self.flags = flags + self.style = style + self.text = text + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonUrl", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_keyboardButtonUrlAuth: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var fwdText: String? + public var url: String + public var buttonId: Int32 + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, fwdText: String?, url: String, buttonId: Int32) { + self.flags = flags + self.style = style + self.text = text + self.fwdText = fwdText + self.url = url + self.buttonId = buttonId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("fwdText", ConstructorParameterDescription(self.fwdText)), ("url", ConstructorParameterDescription(self.url)), ("buttonId", ConstructorParameterDescription(self.buttonId))]) + } + } + public class Cons_keyboardButtonUserProfile: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var userId: Int64 + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, userId: Int64) { + self.flags = flags + self.style = style + self.text = text + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonUserProfile", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_keyboardButtonWebView: TypeConstructorDescription { + public var flags: Int32 + public var style: Api.KeyboardButtonStyle? + public var text: String + public var url: String + public init(flags: Int32, style: Api.KeyboardButtonStyle?, text: String, url: String) { + self.flags = flags + self.style = style + self.text = text + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("keyboardButtonWebView", [("flags", ConstructorParameterDescription(self.flags)), ("style", ConstructorParameterDescription(self.style)), ("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url))]) + } + } + case inputKeyboardButtonRequestPeer(Cons_inputKeyboardButtonRequestPeer) + case inputKeyboardButtonUrlAuth(Cons_inputKeyboardButtonUrlAuth) + case inputKeyboardButtonUserProfile(Cons_inputKeyboardButtonUserProfile) + case keyboardButton(Cons_keyboardButton) + case keyboardButtonBuy(Cons_keyboardButtonBuy) + case keyboardButtonCallback(Cons_keyboardButtonCallback) + case keyboardButtonCopy(Cons_keyboardButtonCopy) + case keyboardButtonGame(Cons_keyboardButtonGame) + case keyboardButtonRequestGeoLocation(Cons_keyboardButtonRequestGeoLocation) + case keyboardButtonRequestPeer(Cons_keyboardButtonRequestPeer) + case keyboardButtonRequestPhone(Cons_keyboardButtonRequestPhone) + case keyboardButtonRequestPoll(Cons_keyboardButtonRequestPoll) + case keyboardButtonSimpleWebView(Cons_keyboardButtonSimpleWebView) + case keyboardButtonSwitchInline(Cons_keyboardButtonSwitchInline) + case keyboardButtonUrl(Cons_keyboardButtonUrl) + case keyboardButtonUrlAuth(Cons_keyboardButtonUrlAuth) + case keyboardButtonUserProfile(Cons_keyboardButtonUserProfile) + case keyboardButtonWebView(Cons_keyboardButtonWebView) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputKeyboardButtonRequestPeer(let _data): + if boxed { + buffer.appendInt32(45580630) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeInt32(_data.buttonId, buffer: buffer, boxed: false) + _data.peerType.serialize(buffer, true) + serializeInt32(_data.maxQuantity, buffer: buffer, boxed: false) + break + case .inputKeyboardButtonUrlAuth(let _data): + if boxed { + buffer.appendInt32(1744911986) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.fwdText!, buffer: buffer, boxed: false) + } + serializeString(_data.url, buffer: buffer, boxed: false) + _data.bot.serialize(buffer, true) + break + case .inputKeyboardButtonUserProfile(let _data): + if boxed { + buffer.appendInt32(2103314375) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + _data.userId.serialize(buffer, true) + break + case .keyboardButton(let _data): + if boxed { + buffer.appendInt32(2098662655) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonBuy(let _data): + if boxed { + buffer.appendInt32(1067792645) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonCallback(let _data): + if boxed { + buffer.appendInt32(-433338016) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeBytes(_data.data, buffer: buffer, boxed: false) + break + case .keyboardButtonCopy(let _data): + if boxed { + buffer.appendInt32(-1127960816) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.copyText, buffer: buffer, boxed: false) + break + case .keyboardButtonGame(let _data): + if boxed { + buffer.appendInt32(-1983540999) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestGeoLocation(let _data): + if boxed { + buffer.appendInt32(-1438582451) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPeer(let _data): + if boxed { + buffer.appendInt32(1527715317) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeInt32(_data.buttonId, buffer: buffer, boxed: false) + _data.peerType.serialize(buffer, true) + serializeInt32(_data.maxQuantity, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPhone(let _data): + if boxed { + buffer.appendInt32(1098841487) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonRequestPoll(let _data): + if boxed { + buffer.appendInt32(2047989634) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.quiz!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .keyboardButtonSimpleWebView(let _data): + if boxed { + buffer.appendInt32(-514047120) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + break + case .keyboardButtonSwitchInline(let _data): + if boxed { + buffer.appendInt32(-1726768644) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.query, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peerTypes!.count)) + for item in _data.peerTypes! { + item.serialize(buffer, true) + } + } + break + case .keyboardButtonUrl(let _data): + if boxed { + buffer.appendInt32(-670292500) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + break + case .keyboardButtonUrlAuth(let _data): + if boxed { + buffer.appendInt32(-183499015) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.fwdText!, buffer: buffer, boxed: false) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt32(_data.buttonId, buffer: buffer, boxed: false) + break + case .keyboardButtonUserProfile(let _data): + if boxed { + buffer.appendInt32(-1057137399) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .keyboardButtonWebView(let _data): + if boxed { + buffer.appendInt32(-398020192) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.style!.serialize(buffer, true) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputKeyboardButtonRequestPeer(let _data): + return ("inputKeyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("maxQuantity", ConstructorParameterDescription(_data.maxQuantity))]) + case .inputKeyboardButtonUrlAuth(let _data): + return ("inputKeyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("fwdText", ConstructorParameterDescription(_data.fwdText)), ("url", ConstructorParameterDescription(_data.url)), ("bot", ConstructorParameterDescription(_data.bot))]) + case .inputKeyboardButtonUserProfile(let _data): + return ("inputKeyboardButtonUserProfile", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .keyboardButton(let _data): + return ("keyboardButton", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonBuy(let _data): + return ("keyboardButtonBuy", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonCallback(let _data): + return ("keyboardButtonCallback", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("data", ConstructorParameterDescription(_data.data))]) + case .keyboardButtonCopy(let _data): + return ("keyboardButtonCopy", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("copyText", ConstructorParameterDescription(_data.copyText))]) + case .keyboardButtonGame(let _data): + return ("keyboardButtonGame", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonRequestGeoLocation(let _data): + return ("keyboardButtonRequestGeoLocation", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonRequestPeer(let _data): + return ("keyboardButtonRequestPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("maxQuantity", ConstructorParameterDescription(_data.maxQuantity))]) + case .keyboardButtonRequestPhone(let _data): + return ("keyboardButtonRequestPhone", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonRequestPoll(let _data): + return ("keyboardButtonRequestPoll", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("quiz", ConstructorParameterDescription(_data.quiz)), ("text", ConstructorParameterDescription(_data.text))]) + case .keyboardButtonSimpleWebView(let _data): + return ("keyboardButtonSimpleWebView", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) + case .keyboardButtonSwitchInline(let _data): + return ("keyboardButtonSwitchInline", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("query", ConstructorParameterDescription(_data.query)), ("peerTypes", ConstructorParameterDescription(_data.peerTypes))]) + case .keyboardButtonUrl(let _data): + return ("keyboardButtonUrl", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) + case .keyboardButtonUrlAuth(let _data): + return ("keyboardButtonUrlAuth", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("fwdText", ConstructorParameterDescription(_data.fwdText)), ("url", ConstructorParameterDescription(_data.url)), ("buttonId", ConstructorParameterDescription(_data.buttonId))]) + case .keyboardButtonUserProfile(let _data): + return ("keyboardButtonUserProfile", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .keyboardButtonWebView(let _data): + return ("keyboardButtonWebView", [("flags", ConstructorParameterDescription(_data.flags)), ("style", ConstructorParameterDescription(_data.style)), ("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_inputKeyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.RequestPeerType? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.RequestPeerType + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.KeyboardButton.inputKeyboardButtonRequestPeer(Cons_inputKeyboardButtonRequestPeer(flags: _1!, style: _2, text: _3!, buttonId: _4!, peerType: _5!, maxQuantity: _6!)) + } + else { + return nil + } + } + public static func parse_inputKeyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: String? + _5 = parseString(reader) + var _6: Api.InputUser? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InputUser + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.KeyboardButton.inputKeyboardButtonUrlAuth(Cons_inputKeyboardButtonUrlAuth(flags: _1!, style: _2, text: _3!, fwdText: _4, url: _5!, bot: _6!)) + } + else { + return nil + } + } + public static func parse_inputKeyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: Api.InputUser? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputUser + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.inputKeyboardButtonUserProfile(Cons_inputKeyboardButtonUserProfile(flags: _1!, style: _2, text: _3!, userId: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButton(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButton(Cons_keyboardButton(flags: _1!, style: _2, text: _3!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonBuy(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonBuy(Cons_keyboardButtonBuy(flags: _1!, style: _2, text: _3!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonCallback(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonCallback(Cons_keyboardButtonCallback(flags: _1!, style: _2, text: _3!, data: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonCopy(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonCopy(Cons_keyboardButtonCopy(flags: _1!, style: _2, text: _3!, copyText: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonGame(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonGame(Cons_keyboardButtonGame(flags: _1!, style: _2, text: _3!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestGeoLocation(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonRequestGeoLocation(Cons_keyboardButtonRequestGeoLocation(flags: _1!, style: _2, text: _3!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPeer(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.RequestPeerType? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.RequestPeerType + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.KeyboardButton.keyboardButtonRequestPeer(Cons_keyboardButtonRequestPeer(flags: _1!, style: _2, text: _3!, buttonId: _4!, peerType: _5!, maxQuantity: _6!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPhone(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.KeyboardButton.keyboardButtonRequestPhone(Cons_keyboardButtonRequestPhone(flags: _1!, style: _2, text: _3!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonRequestPoll(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonRequestPoll(Cons_keyboardButtonRequestPoll(flags: _1!, style: _2, quiz: _3, text: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonSimpleWebView(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonSimpleWebView(Cons_keyboardButtonSimpleWebView(flags: _1!, style: _2, text: _3!, url: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonSwitchInline(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: [Api.InlineQueryPeerType]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.KeyboardButton.keyboardButtonSwitchInline(Cons_keyboardButtonSwitchInline(flags: _1!, style: _2, text: _3!, query: _4!, peerTypes: _5)) + } + else { + return nil + } + } + public static func parse_keyboardButtonUrl(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonUrl(Cons_keyboardButtonUrl(flags: _1!, style: _2, text: _3!, url: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonUrlAuth(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.KeyboardButton.keyboardButtonUrlAuth(Cons_keyboardButtonUrlAuth(flags: _1!, style: _2, text: _3!, fwdText: _4, url: _5!, buttonId: _6!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonUserProfile(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonUserProfile(Cons_keyboardButtonUserProfile(flags: _1!, style: _2, text: _3!, userId: _4!)) + } + else { + return nil + } + } + public static func parse_keyboardButtonWebView(_ reader: BufferReader) -> KeyboardButton? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.KeyboardButtonStyle? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.KeyboardButtonStyle + } + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.KeyboardButton.keyboardButtonWebView(Cons_keyboardButtonWebView(flags: _1!, style: _2, text: _3!, url: _4!)) + } + else { + return nil + } + } + } +} public extension Api { enum KeyboardButtonRow: TypeConstructorDescription { public class Cons_keyboardButtonRow: TypeConstructorDescription { @@ -88,11 +1113,11 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int64? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _2 = reader.readInt64() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil if _c1 && _c2 { return Api.KeyboardButtonStyle.keyboardButtonStyle(Cons_keyboardButtonStyle(flags: _1!, icon: _2)) } @@ -287,7 +1312,7 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _5 = parseString(reader) } var _6: String? @@ -302,7 +1327,7 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil @@ -448,34 +1473,34 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = parseString(reader) } var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } var _6: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _6 = parseString(reader) } var _7: String? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _7 = parseString(reader) } var _8: String? _8 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil let _c8 = _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.LangPackString.langPackStringPluralized(Cons_langPackStringPluralized(flags: _1!, key: _2!, zeroValue: _3, oneValue: _4, twoValue: _5, fewValue: _6, manyValue: _7, otherValue: _8!)) @@ -862,7 +1887,7 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.GeoPoint } var _4: Api.GeoPointAddress? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.GeoPointAddress } @@ -870,7 +1895,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.MediaArea.mediaAreaGeoPoint(Cons_mediaAreaGeoPoint(flags: _1!, coordinates: _2!, geo: _3!, address: _4)) } @@ -988,3547 +2013,3 @@ public extension Api { } } } -public extension Api { - enum MediaAreaCoordinates: TypeConstructorDescription { - public class Cons_mediaAreaCoordinates: TypeConstructorDescription { - public var flags: Int32 - public var x: Double - public var y: Double - public var w: Double - public var h: Double - public var rotation: Double - public var radius: Double? - public init(flags: Int32, x: Double, y: Double, w: Double, h: Double, rotation: Double, radius: Double?) { - self.flags = flags - self.x = x - self.y = y - self.w = w - self.h = h - self.rotation = rotation - self.radius = radius - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("mediaAreaCoordinates", [("flags", ConstructorParameterDescription(self.flags)), ("x", ConstructorParameterDescription(self.x)), ("y", ConstructorParameterDescription(self.y)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("rotation", ConstructorParameterDescription(self.rotation)), ("radius", ConstructorParameterDescription(self.radius))]) - } - } - case mediaAreaCoordinates(Cons_mediaAreaCoordinates) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .mediaAreaCoordinates(let _data): - if boxed { - buffer.appendInt32(-808853502) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeDouble(_data.x, buffer: buffer, boxed: false) - serializeDouble(_data.y, buffer: buffer, boxed: false) - serializeDouble(_data.w, buffer: buffer, boxed: false) - serializeDouble(_data.h, buffer: buffer, boxed: false) - serializeDouble(_data.rotation, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeDouble(_data.radius!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .mediaAreaCoordinates(let _data): - return ("mediaAreaCoordinates", [("flags", ConstructorParameterDescription(_data.flags)), ("x", ConstructorParameterDescription(_data.x)), ("y", ConstructorParameterDescription(_data.y)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("rotation", ConstructorParameterDescription(_data.rotation)), ("radius", ConstructorParameterDescription(_data.radius))]) - } - } - - public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Double? - _2 = reader.readDouble() - var _3: Double? - _3 = reader.readDouble() - var _4: Double? - _4 = reader.readDouble() - var _5: Double? - _5 = reader.readDouble() - var _6: Double? - _6 = reader.readDouble() - var _7: Double? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = reader.readDouble() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MediaAreaCoordinates.mediaAreaCoordinates(Cons_mediaAreaCoordinates(flags: _1!, x: _2!, y: _3!, w: _4!, h: _5!, rotation: _6!, radius: _7)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum Message: TypeConstructorDescription { - public class Cons_message: TypeConstructorDescription { - public var flags: Int32 - public var flags2: Int32 - public var id: Int32 - public var fromId: Api.Peer? - public var fromBoostsApplied: Int32? - public var fromRank: String? - public var peerId: Api.Peer - public var savedPeerId: Api.Peer? - public var fwdFrom: Api.MessageFwdHeader? - public var viaBotId: Int64? - public var viaBusinessBotId: Int64? - public var replyTo: Api.MessageReplyHeader? - public var date: Int32 - public var message: String - public var media: Api.MessageMedia? - public var replyMarkup: Api.ReplyMarkup? - public var entities: [Api.MessageEntity]? - public var views: Int32? - public var forwards: Int32? - public var replies: Api.MessageReplies? - public var editDate: Int32? - public var postAuthor: String? - public var groupedId: Int64? - public var reactions: Api.MessageReactions? - public var restrictionReason: [Api.RestrictionReason]? - public var ttlPeriod: Int32? - public var quickReplyShortcutId: Int32? - public var effect: Int64? - public var factcheck: Api.FactCheck? - public var reportDeliveryUntilDate: Int32? - public var paidMessageStars: Int64? - public var suggestedPost: Api.SuggestedPost? - public var scheduleRepeatPeriod: Int32? - public var summaryFromLanguage: String? - public init(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, fromRank: String?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?, summaryFromLanguage: String?) { - self.flags = flags - self.flags2 = flags2 - self.id = id - self.fromId = fromId - self.fromBoostsApplied = fromBoostsApplied - self.fromRank = fromRank - self.peerId = peerId - self.savedPeerId = savedPeerId - self.fwdFrom = fwdFrom - self.viaBotId = viaBotId - self.viaBusinessBotId = viaBusinessBotId - self.replyTo = replyTo - self.date = date - self.message = message - self.media = media - self.replyMarkup = replyMarkup - self.entities = entities - self.views = views - self.forwards = forwards - self.replies = replies - self.editDate = editDate - self.postAuthor = postAuthor - self.groupedId = groupedId - self.reactions = reactions - self.restrictionReason = restrictionReason - self.ttlPeriod = ttlPeriod - self.quickReplyShortcutId = quickReplyShortcutId - self.effect = effect - self.factcheck = factcheck - self.reportDeliveryUntilDate = reportDeliveryUntilDate - self.paidMessageStars = paidMessageStars - self.suggestedPost = suggestedPost - self.scheduleRepeatPeriod = scheduleRepeatPeriod - self.summaryFromLanguage = summaryFromLanguage - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("message", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fromBoostsApplied", ConstructorParameterDescription(self.fromBoostsApplied)), ("fromRank", ConstructorParameterDescription(self.fromRank)), ("peerId", ConstructorParameterDescription(self.peerId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("viaBusinessBotId", ConstructorParameterDescription(self.viaBusinessBotId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("date", ConstructorParameterDescription(self.date)), ("message", ConstructorParameterDescription(self.message)), ("media", ConstructorParameterDescription(self.media)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup)), ("entities", ConstructorParameterDescription(self.entities)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("replies", ConstructorParameterDescription(self.replies)), ("editDate", ConstructorParameterDescription(self.editDate)), ("postAuthor", ConstructorParameterDescription(self.postAuthor)), ("groupedId", ConstructorParameterDescription(self.groupedId)), ("reactions", ConstructorParameterDescription(self.reactions)), ("restrictionReason", ConstructorParameterDescription(self.restrictionReason)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(self.quickReplyShortcutId)), ("effect", ConstructorParameterDescription(self.effect)), ("factcheck", ConstructorParameterDescription(self.factcheck)), ("reportDeliveryUntilDate", ConstructorParameterDescription(self.reportDeliveryUntilDate)), ("paidMessageStars", ConstructorParameterDescription(self.paidMessageStars)), ("suggestedPost", ConstructorParameterDescription(self.suggestedPost)), ("scheduleRepeatPeriod", ConstructorParameterDescription(self.scheduleRepeatPeriod)), ("summaryFromLanguage", ConstructorParameterDescription(self.summaryFromLanguage))]) - } - } - public class Cons_messageEmpty: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var peerId: Api.Peer? - public init(flags: Int32, id: Int32, peerId: Api.Peer?) { - self.flags = flags - self.id = id - self.peerId = peerId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("peerId", ConstructorParameterDescription(self.peerId))]) - } - } - public class Cons_messageService: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var fromId: Api.Peer? - public var peerId: Api.Peer - public var savedPeerId: Api.Peer? - public var replyTo: Api.MessageReplyHeader? - public var date: Int32 - public var action: Api.MessageAction - public var reactions: Api.MessageReactions? - public var ttlPeriod: Int32? - public init(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) { - self.flags = flags - self.id = id - self.fromId = fromId - self.peerId = peerId - self.savedPeerId = savedPeerId - self.replyTo = replyTo - self.date = date - self.action = action - self.reactions = reactions - self.ttlPeriod = ttlPeriod - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageService", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peerId", ConstructorParameterDescription(self.peerId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("date", ConstructorParameterDescription(self.date)), ("action", ConstructorParameterDescription(self.action)), ("reactions", ConstructorParameterDescription(self.reactions)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) - } - } - case message(Cons_message) - case messageEmpty(Cons_messageEmpty) - case messageService(Cons_messageService) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .message(let _data): - if boxed { - buffer.appendInt32(988112002) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.flags2, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 29) != 0 { - serializeInt32(_data.fromBoostsApplied!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 12) != 0 { - serializeString(_data.fromRank!, buffer: buffer, boxed: false) - } - _data.peerId.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 28) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.fwdFrom!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 0) != 0 { - serializeInt64(_data.viaBusinessBotId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.replyTo!.serialize(buffer, true) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 9) != 0 { - _data.media!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.replyMarkup!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.views!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.forwards!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 23) != 0 { - _data.replies!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.editDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeString(_data.postAuthor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - serializeInt64(_data.groupedId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 20) != 0 { - _data.reactions!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 22) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.restrictionReason!.count)) - for item in _data.restrictionReason! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 25) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 30) != 0 { - serializeInt32(_data.quickReplyShortcutId!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 2) != 0 { - serializeInt64(_data.effect!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 3) != 0 { - _data.factcheck!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 5) != 0 { - serializeInt32(_data.reportDeliveryUntilDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 6) != 0 { - serializeInt64(_data.paidMessageStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 7) != 0 { - _data.suggestedPost!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 10) != 0 { - serializeInt32(_data.scheduleRepeatPeriod!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 11) != 0 { - serializeString(_data.summaryFromLanguage!, buffer: buffer, boxed: false) - } - break - case .messageEmpty(let _data): - if boxed { - buffer.appendInt32(-1868117372) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.peerId!.serialize(buffer, true) - } - break - case .messageService(let _data): - if boxed { - buffer.appendInt32(2055212554) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.fromId!.serialize(buffer, true) - } - _data.peerId.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 28) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.replyTo!.serialize(buffer, true) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.action.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 20) != 0 { - _data.reactions!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 25) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .message(let _data): - return ("message", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fromBoostsApplied", ConstructorParameterDescription(_data.fromBoostsApplied)), ("fromRank", ConstructorParameterDescription(_data.fromRank)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("viaBusinessBotId", ConstructorParameterDescription(_data.viaBusinessBotId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("date", ConstructorParameterDescription(_data.date)), ("message", ConstructorParameterDescription(_data.message)), ("media", ConstructorParameterDescription(_data.media)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup)), ("entities", ConstructorParameterDescription(_data.entities)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("replies", ConstructorParameterDescription(_data.replies)), ("editDate", ConstructorParameterDescription(_data.editDate)), ("postAuthor", ConstructorParameterDescription(_data.postAuthor)), ("groupedId", ConstructorParameterDescription(_data.groupedId)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("restrictionReason", ConstructorParameterDescription(_data.restrictionReason)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(_data.quickReplyShortcutId)), ("effect", ConstructorParameterDescription(_data.effect)), ("factcheck", ConstructorParameterDescription(_data.factcheck)), ("reportDeliveryUntilDate", ConstructorParameterDescription(_data.reportDeliveryUntilDate)), ("paidMessageStars", ConstructorParameterDescription(_data.paidMessageStars)), ("suggestedPost", ConstructorParameterDescription(_data.suggestedPost)), ("scheduleRepeatPeriod", ConstructorParameterDescription(_data.scheduleRepeatPeriod)), ("summaryFromLanguage", ConstructorParameterDescription(_data.summaryFromLanguage))]) - case .messageEmpty(let _data): - return ("messageEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("peerId", ConstructorParameterDescription(_data.peerId))]) - case .messageService(let _data): - return ("messageService", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("date", ConstructorParameterDescription(_data.date)), ("action", ConstructorParameterDescription(_data.action)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) - } - } - - public static func parse_message(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 29) != 0 { - _5 = reader.readInt32() - } - var _6: String? - if Int(_2!) & Int(1 << 12) != 0 { - _6 = parseString(reader) - } - var _7: Api.Peer? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _8: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _9: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } - } - var _10: Int64? - if Int(_1!) & Int(1 << 11) != 0 { - _10 = reader.readInt64() - } - var _11: Int64? - if Int(_2!) & Int(1 << 0) != 0 { - _11 = reader.readInt64() - } - var _12: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } - } - var _13: Int32? - _13 = reader.readInt32() - var _14: String? - _14 = parseString(reader) - var _15: Api.MessageMedia? - if Int(_1!) & Int(1 << 9) != 0 { - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - var _16: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup - } - } - var _17: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 { - if let _ = reader.readInt32() { - _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _18: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _18 = reader.readInt32() - } - var _19: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _19 = reader.readInt32() - } - var _20: Api.MessageReplies? - if Int(_1!) & Int(1 << 23) != 0 { - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.MessageReplies - } - } - var _21: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _21 = reader.readInt32() - } - var _22: String? - if Int(_1!) & Int(1 << 16) != 0 { - _22 = parseString(reader) - } - var _23: Int64? - if Int(_1!) & Int(1 << 17) != 0 { - _23 = reader.readInt64() - } - var _24: Api.MessageReactions? - if Int(_1!) & Int(1 << 20) != 0 { - if let signature = reader.readInt32() { - _24 = Api.parse(reader, signature: signature) as? Api.MessageReactions - } - } - var _25: [Api.RestrictionReason]? - if Int(_1!) & Int(1 << 22) != 0 { - if let _ = reader.readInt32() { - _25 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) - } - } - var _26: Int32? - if Int(_1!) & Int(1 << 25) != 0 { - _26 = reader.readInt32() - } - var _27: Int32? - if Int(_1!) & Int(1 << 30) != 0 { - _27 = reader.readInt32() - } - var _28: Int64? - if Int(_2!) & Int(1 << 2) != 0 { - _28 = reader.readInt64() - } - var _29: Api.FactCheck? - if Int(_2!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _29 = Api.parse(reader, signature: signature) as? Api.FactCheck - } - } - var _30: Int32? - if Int(_2!) & Int(1 << 5) != 0 { - _30 = reader.readInt32() - } - var _31: Int64? - if Int(_2!) & Int(1 << 6) != 0 { - _31 = reader.readInt64() - } - var _32: Api.SuggestedPost? - if Int(_2!) & Int(1 << 7) != 0 { - if let signature = reader.readInt32() { - _32 = Api.parse(reader, signature: signature) as? Api.SuggestedPost - } - } - var _33: Int32? - if Int(_2!) & Int(1 << 10) != 0 { - _33 = reader.readInt32() - } - var _34: String? - if Int(_2!) & Int(1 << 11) != 0 { - _34 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 29) == 0) || _5 != nil - let _c6 = (Int(_2!) & Int(1 << 12) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 28) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_2!) & Int(1 << 0) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = (Int(_1!) & Int(1 << 9) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 6) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 7) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 10) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 23) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 15) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 16) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 17) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 20) == 0) || _24 != nil - let _c25 = (Int(_1!) & Int(1 << 22) == 0) || _25 != nil - let _c26 = (Int(_1!) & Int(1 << 25) == 0) || _26 != nil - let _c27 = (Int(_1!) & Int(1 << 30) == 0) || _27 != nil - let _c28 = (Int(_2!) & Int(1 << 2) == 0) || _28 != nil - let _c29 = (Int(_2!) & Int(1 << 3) == 0) || _29 != nil - let _c30 = (Int(_2!) & Int(1 << 5) == 0) || _30 != nil - let _c31 = (Int(_2!) & Int(1 << 6) == 0) || _31 != nil - let _c32 = (Int(_2!) & Int(1 << 7) == 0) || _32 != nil - let _c33 = (Int(_2!) & Int(1 << 10) == 0) || _33 != nil - let _c34 = (Int(_2!) & Int(1 << 11) == 0) || _34 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 { - return Api.Message.message(Cons_message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, fromRank: _6, peerId: _7!, savedPeerId: _8, fwdFrom: _9, viaBotId: _10, viaBusinessBotId: _11, replyTo: _12, date: _13!, message: _14!, media: _15, replyMarkup: _16, entities: _17, views: _18, forwards: _19, replies: _20, editDate: _21, postAuthor: _22, groupedId: _23, reactions: _24, restrictionReason: _25, ttlPeriod: _26, quickReplyShortcutId: _27, effect: _28, factcheck: _29, reportDeliveryUntilDate: _30, paidMessageStars: _31, suggestedPost: _32, scheduleRepeatPeriod: _33, summaryFromLanguage: _34)) - } - else { - return nil - } - } - public static func parse_messageEmpty(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Message.messageEmpty(Cons_messageEmpty(flags: _1!, id: _2!, peerId: _3)) - } - else { - return nil - } - } - public static func parse_messageService(_ reader: BufferReader) -> Message? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Api.Peer? - if Int(_1!) & Int(1 << 28) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _6: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } - } - var _7: Int32? - _7 = reader.readInt32() - var _8: Api.MessageAction? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageAction - } - var _9: Api.MessageReactions? - if Int(_1!) & Int(1 << 20) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageReactions - } - } - var _10: Int32? - if Int(_1!) & Int(1 << 25) != 0 { - _10 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 28) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 20) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 25) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.Message.messageService(Cons_messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, replyTo: _6, date: _7!, action: _8!, reactions: _9, ttlPeriod: _10)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum MessageAction: TypeConstructorDescription { - public class Cons_messageActionBoostApply: TypeConstructorDescription { - public var boosts: Int32 - public init(boosts: Int32) { - self.boosts = boosts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionBoostApply", [("boosts", ConstructorParameterDescription(self.boosts))]) - } - } - public class Cons_messageActionBotAllowed: TypeConstructorDescription { - public var flags: Int32 - public var domain: String? - public var app: Api.BotApp? - public init(flags: Int32, domain: String?, app: Api.BotApp?) { - self.flags = flags - self.domain = domain - self.app = app - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionBotAllowed", [("flags", ConstructorParameterDescription(self.flags)), ("domain", ConstructorParameterDescription(self.domain)), ("app", ConstructorParameterDescription(self.app))]) - } - } - public class Cons_messageActionChangeCreator: TypeConstructorDescription { - public var newCreatorId: Int64 - public init(newCreatorId: Int64) { - self.newCreatorId = newCreatorId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChangeCreator", [("newCreatorId", ConstructorParameterDescription(self.newCreatorId))]) - } - } - public class Cons_messageActionChannelCreate: TypeConstructorDescription { - public var title: String - public init(title: String) { - self.title = title - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChannelCreate", [("title", ConstructorParameterDescription(self.title))]) - } - } - public class Cons_messageActionChannelMigrateFrom: TypeConstructorDescription { - public var title: String - public var chatId: Int64 - public init(title: String, chatId: Int64) { - self.title = title - self.chatId = chatId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChannelMigrateFrom", [("title", ConstructorParameterDescription(self.title)), ("chatId", ConstructorParameterDescription(self.chatId))]) - } - } - public class Cons_messageActionChatAddUser: TypeConstructorDescription { - public var users: [Int64] - public init(users: [Int64]) { - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatAddUser", [("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_messageActionChatCreate: TypeConstructorDescription { - public var title: String - public var users: [Int64] - public init(title: String, users: [Int64]) { - self.title = title - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatCreate", [("title", ConstructorParameterDescription(self.title)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_messageActionChatDeleteUser: TypeConstructorDescription { - public var userId: Int64 - public init(userId: Int64) { - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatDeleteUser", [("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_messageActionChatEditPhoto: TypeConstructorDescription { - public var photo: Api.Photo - public init(photo: Api.Photo) { - self.photo = photo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatEditPhoto", [("photo", ConstructorParameterDescription(self.photo))]) - } - } - public class Cons_messageActionChatEditTitle: TypeConstructorDescription { - public var title: String - public init(title: String) { - self.title = title - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatEditTitle", [("title", ConstructorParameterDescription(self.title))]) - } - } - public class Cons_messageActionChatJoinedByLink: TypeConstructorDescription { - public var inviterId: Int64 - public init(inviterId: Int64) { - self.inviterId = inviterId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatJoinedByLink", [("inviterId", ConstructorParameterDescription(self.inviterId))]) - } - } - public class Cons_messageActionChatMigrateTo: TypeConstructorDescription { - public var channelId: Int64 - public init(channelId: Int64) { - self.channelId = channelId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionChatMigrateTo", [("channelId", ConstructorParameterDescription(self.channelId))]) - } - } - public class Cons_messageActionConferenceCall: TypeConstructorDescription { - public var flags: Int32 - public var callId: Int64 - public var duration: Int32? - public var otherParticipants: [Api.Peer]? - public init(flags: Int32, callId: Int64, duration: Int32?, otherParticipants: [Api.Peer]?) { - self.flags = flags - self.callId = callId - self.duration = duration - self.otherParticipants = otherParticipants - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionConferenceCall", [("flags", ConstructorParameterDescription(self.flags)), ("callId", ConstructorParameterDescription(self.callId)), ("duration", ConstructorParameterDescription(self.duration)), ("otherParticipants", ConstructorParameterDescription(self.otherParticipants))]) - } - } - public class Cons_messageActionCustomAction: TypeConstructorDescription { - public var message: String - public init(message: String) { - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionCustomAction", [("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_messageActionGameScore: TypeConstructorDescription { - public var gameId: Int64 - public var score: Int32 - public init(gameId: Int64, score: Int32) { - self.gameId = gameId - self.score = score - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGameScore", [("gameId", ConstructorParameterDescription(self.gameId)), ("score", ConstructorParameterDescription(self.score))]) - } - } - public class Cons_messageActionGeoProximityReached: TypeConstructorDescription { - public var fromId: Api.Peer - public var toId: Api.Peer - public var distance: Int32 - public init(fromId: Api.Peer, toId: Api.Peer, distance: Int32) { - self.fromId = fromId - self.toId = toId - self.distance = distance - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGeoProximityReached", [("fromId", ConstructorParameterDescription(self.fromId)), ("toId", ConstructorParameterDescription(self.toId)), ("distance", ConstructorParameterDescription(self.distance))]) - } - } - public class Cons_messageActionGiftCode: TypeConstructorDescription { - public var flags: Int32 - public var boostPeer: Api.Peer? - public var days: Int32 - public var slug: String - public var currency: String? - public var amount: Int64? - public var cryptoCurrency: String? - public var cryptoAmount: Int64? - public var message: Api.TextWithEntities? - public init(flags: Int32, boostPeer: Api.Peer?, days: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) { - self.flags = flags - self.boostPeer = boostPeer - self.days = days - self.slug = slug - self.currency = currency - self.amount = amount - self.cryptoCurrency = cryptoCurrency - self.cryptoAmount = cryptoAmount - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("days", ConstructorParameterDescription(self.days)), ("slug", ConstructorParameterDescription(self.slug)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_messageActionGiftPremium: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var amount: Int64 - public var days: Int32 - public var cryptoCurrency: String? - public var cryptoAmount: Int64? - public var message: Api.TextWithEntities? - public init(flags: Int32, currency: String, amount: Int64, days: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) { - self.flags = flags - self.currency = currency - self.amount = amount - self.days = days - self.cryptoCurrency = cryptoCurrency - self.cryptoAmount = cryptoAmount - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiftPremium", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("days", ConstructorParameterDescription(self.days)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_messageActionGiftStars: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var amount: Int64 - public var stars: Int64 - public var cryptoCurrency: String? - public var cryptoAmount: Int64? - public var transactionId: String? - public init(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) { - self.flags = flags - self.currency = currency - self.amount = amount - self.stars = stars - self.cryptoCurrency = cryptoCurrency - self.cryptoAmount = cryptoAmount - self.transactionId = transactionId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiftStars", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("stars", ConstructorParameterDescription(self.stars)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId))]) - } - } - public class Cons_messageActionGiftTon: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var amount: Int64 - public var cryptoCurrency: String - public var cryptoAmount: Int64 - public var transactionId: String? - public init(flags: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, transactionId: String?) { - self.flags = flags - self.currency = currency - self.amount = amount - self.cryptoCurrency = cryptoCurrency - self.cryptoAmount = cryptoAmount - self.transactionId = transactionId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiftTon", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId))]) - } - } - public class Cons_messageActionGiveawayLaunch: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64? - public init(flags: Int32, stars: Int64?) { - self.flags = flags - self.stars = stars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiveawayLaunch", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars))]) - } - } - public class Cons_messageActionGiveawayResults: TypeConstructorDescription { - public var flags: Int32 - public var winnersCount: Int32 - public var unclaimedCount: Int32 - public init(flags: Int32, winnersCount: Int32, unclaimedCount: Int32) { - self.flags = flags - self.winnersCount = winnersCount - self.unclaimedCount = unclaimedCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGiveawayResults", [("flags", ConstructorParameterDescription(self.flags)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(self.unclaimedCount))]) - } - } - public class Cons_messageActionGroupCall: TypeConstructorDescription { - public var flags: Int32 - public var call: Api.InputGroupCall - public var duration: Int32? - public init(flags: Int32, call: Api.InputGroupCall, duration: Int32?) { - self.flags = flags - self.call = call - self.duration = duration - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGroupCall", [("flags", ConstructorParameterDescription(self.flags)), ("call", ConstructorParameterDescription(self.call)), ("duration", ConstructorParameterDescription(self.duration))]) - } - } - public class Cons_messageActionGroupCallScheduled: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var scheduleDate: Int32 - public init(call: Api.InputGroupCall, scheduleDate: Int32) { - self.call = call - self.scheduleDate = scheduleDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionGroupCallScheduled", [("call", ConstructorParameterDescription(self.call)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate))]) - } - } - public class Cons_messageActionInviteToGroupCall: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var users: [Int64] - public init(call: Api.InputGroupCall, users: [Int64]) { - self.call = call - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionInviteToGroupCall", [("call", ConstructorParameterDescription(self.call)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_messageActionManagedBotCreated: TypeConstructorDescription { - public var botId: Int64 - public init(botId: Int64) { - self.botId = botId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionManagedBotCreated", [("botId", ConstructorParameterDescription(self.botId))]) - } - } - public class Cons_messageActionNewCreatorPending: TypeConstructorDescription { - public var newCreatorId: Int64 - public init(newCreatorId: Int64) { - self.newCreatorId = newCreatorId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionNewCreatorPending", [("newCreatorId", ConstructorParameterDescription(self.newCreatorId))]) - } - } - public class Cons_messageActionNoForwardsRequest: TypeConstructorDescription { - public var flags: Int32 - public var prevValue: Api.Bool - public var newValue: Api.Bool - public init(flags: Int32, prevValue: Api.Bool, newValue: Api.Bool) { - self.flags = flags - self.prevValue = prevValue - self.newValue = newValue - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionNoForwardsRequest", [("flags", ConstructorParameterDescription(self.flags)), ("prevValue", ConstructorParameterDescription(self.prevValue)), ("newValue", ConstructorParameterDescription(self.newValue))]) - } - } - public class Cons_messageActionNoForwardsToggle: TypeConstructorDescription { - public var prevValue: Api.Bool - public var newValue: Api.Bool - public init(prevValue: Api.Bool, newValue: Api.Bool) { - self.prevValue = prevValue - self.newValue = newValue - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionNoForwardsToggle", [("prevValue", ConstructorParameterDescription(self.prevValue)), ("newValue", ConstructorParameterDescription(self.newValue))]) - } - } - public class Cons_messageActionPaidMessagesPrice: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public init(flags: Int32, stars: Int64) { - self.flags = flags - self.stars = stars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPaidMessagesPrice", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars))]) - } - } - public class Cons_messageActionPaidMessagesRefunded: TypeConstructorDescription { - public var count: Int32 - public var stars: Int64 - public init(count: Int32, stars: Int64) { - self.count = count - self.stars = stars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPaidMessagesRefunded", [("count", ConstructorParameterDescription(self.count)), ("stars", ConstructorParameterDescription(self.stars))]) - } - } - public class Cons_messageActionPaymentRefunded: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var currency: String - public var totalAmount: Int64 - public var payload: Buffer? - public var charge: Api.PaymentCharge - public init(flags: Int32, peer: Api.Peer, currency: String, totalAmount: Int64, payload: Buffer?, charge: Api.PaymentCharge) { - self.flags = flags - self.peer = peer - self.currency = currency - self.totalAmount = totalAmount - self.payload = payload - self.charge = charge - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPaymentRefunded", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("payload", ConstructorParameterDescription(self.payload)), ("charge", ConstructorParameterDescription(self.charge))]) - } - } - public class Cons_messageActionPaymentSent: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var totalAmount: Int64 - public var invoiceSlug: String? - public var subscriptionUntilDate: Int32? - public init(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?, subscriptionUntilDate: Int32?) { - self.flags = flags - self.currency = currency - self.totalAmount = totalAmount - self.invoiceSlug = invoiceSlug - self.subscriptionUntilDate = subscriptionUntilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPaymentSent", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("invoiceSlug", ConstructorParameterDescription(self.invoiceSlug)), ("subscriptionUntilDate", ConstructorParameterDescription(self.subscriptionUntilDate))]) - } - } - public class Cons_messageActionPaymentSentMe: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var totalAmount: Int64 - public var payload: Buffer - public var info: Api.PaymentRequestedInfo? - public var shippingOptionId: String? - public var charge: Api.PaymentCharge - public var subscriptionUntilDate: Int32? - public init(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge, subscriptionUntilDate: Int32?) { - self.flags = flags - self.currency = currency - self.totalAmount = totalAmount - self.payload = payload - self.info = info - self.shippingOptionId = shippingOptionId - self.charge = charge - self.subscriptionUntilDate = subscriptionUntilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPaymentSentMe", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("payload", ConstructorParameterDescription(self.payload)), ("info", ConstructorParameterDescription(self.info)), ("shippingOptionId", ConstructorParameterDescription(self.shippingOptionId)), ("charge", ConstructorParameterDescription(self.charge)), ("subscriptionUntilDate", ConstructorParameterDescription(self.subscriptionUntilDate))]) - } - } - public class Cons_messageActionPhoneCall: TypeConstructorDescription { - public var flags: Int32 - public var callId: Int64 - public var reason: Api.PhoneCallDiscardReason? - public var duration: Int32? - public init(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) { - self.flags = flags - self.callId = callId - self.reason = reason - self.duration = duration - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPhoneCall", [("flags", ConstructorParameterDescription(self.flags)), ("callId", ConstructorParameterDescription(self.callId)), ("reason", ConstructorParameterDescription(self.reason)), ("duration", ConstructorParameterDescription(self.duration))]) - } - } - public class Cons_messageActionPollAppendAnswer: TypeConstructorDescription { - public var answer: Api.PollAnswer - public init(answer: Api.PollAnswer) { - self.answer = answer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPollAppendAnswer", [("answer", ConstructorParameterDescription(self.answer))]) - } - } - public class Cons_messageActionPollDeleteAnswer: TypeConstructorDescription { - public var answer: Api.PollAnswer - public init(answer: Api.PollAnswer) { - self.answer = answer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPollDeleteAnswer", [("answer", ConstructorParameterDescription(self.answer))]) - } - } - public class Cons_messageActionPrizeStars: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var transactionId: String - public var boostPeer: Api.Peer - public var giveawayMsgId: Int32 - public init(flags: Int32, stars: Int64, transactionId: String, boostPeer: Api.Peer, giveawayMsgId: Int32) { - self.flags = flags - self.stars = stars - self.transactionId = transactionId - self.boostPeer = boostPeer - self.giveawayMsgId = giveawayMsgId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionPrizeStars", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("transactionId", ConstructorParameterDescription(self.transactionId)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("giveawayMsgId", ConstructorParameterDescription(self.giveawayMsgId))]) - } - } - public class Cons_messageActionRequestedPeer: TypeConstructorDescription { - public var buttonId: Int32 - public var peers: [Api.Peer] - public init(buttonId: Int32, peers: [Api.Peer]) { - self.buttonId = buttonId - self.peers = peers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionRequestedPeer", [("buttonId", ConstructorParameterDescription(self.buttonId)), ("peers", ConstructorParameterDescription(self.peers))]) - } - } - public class Cons_messageActionRequestedPeerSentMe: TypeConstructorDescription { - public var buttonId: Int32 - public var peers: [Api.RequestedPeer] - public init(buttonId: Int32, peers: [Api.RequestedPeer]) { - self.buttonId = buttonId - self.peers = peers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionRequestedPeerSentMe", [("buttonId", ConstructorParameterDescription(self.buttonId)), ("peers", ConstructorParameterDescription(self.peers))]) - } - } - public class Cons_messageActionSecureValuesSent: TypeConstructorDescription { - public var types: [Api.SecureValueType] - public init(types: [Api.SecureValueType]) { - self.types = types - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSecureValuesSent", [("types", ConstructorParameterDescription(self.types))]) - } - } - public class Cons_messageActionSecureValuesSentMe: TypeConstructorDescription { - public var values: [Api.SecureValue] - public var credentials: Api.SecureCredentialsEncrypted - public init(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) { - self.values = values - self.credentials = credentials - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSecureValuesSentMe", [("values", ConstructorParameterDescription(self.values)), ("credentials", ConstructorParameterDescription(self.credentials))]) - } - } - public class Cons_messageActionSetChatTheme: TypeConstructorDescription { - public var theme: Api.ChatTheme - public init(theme: Api.ChatTheme) { - self.theme = theme - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSetChatTheme", [("theme", ConstructorParameterDescription(self.theme))]) - } - } - public class Cons_messageActionSetChatWallPaper: TypeConstructorDescription { - public var flags: Int32 - public var wallpaper: Api.WallPaper - public init(flags: Int32, wallpaper: Api.WallPaper) { - self.flags = flags - self.wallpaper = wallpaper - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSetChatWallPaper", [("flags", ConstructorParameterDescription(self.flags)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) - } - } - public class Cons_messageActionSetMessagesTTL: TypeConstructorDescription { - public var flags: Int32 - public var period: Int32 - public var autoSettingFrom: Int64? - public init(flags: Int32, period: Int32, autoSettingFrom: Int64?) { - self.flags = flags - self.period = period - self.autoSettingFrom = autoSettingFrom - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSetMessagesTTL", [("flags", ConstructorParameterDescription(self.flags)), ("period", ConstructorParameterDescription(self.period)), ("autoSettingFrom", ConstructorParameterDescription(self.autoSettingFrom))]) - } - } - public class Cons_messageActionStarGift: TypeConstructorDescription { - public var flags: Int32 - public var gift: Api.StarGift - public var message: Api.TextWithEntities? - public var convertStars: Int64? - public var upgradeMsgId: Int32? - public var upgradeStars: Int64? - public var fromId: Api.Peer? - public var peer: Api.Peer? - public var savedId: Int64? - public var prepaidUpgradeHash: String? - public var giftMsgId: Int32? - public var toId: Api.Peer? - public var giftNum: Int32? - public init(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?, toId: Api.Peer?, giftNum: Int32?) { - self.flags = flags - self.gift = gift - self.message = message - self.convertStars = convertStars - self.upgradeMsgId = upgradeMsgId - self.upgradeStars = upgradeStars - self.fromId = fromId - self.peer = peer - self.savedId = savedId - self.prepaidUpgradeHash = prepaidUpgradeHash - self.giftMsgId = giftMsgId - self.toId = toId - self.giftNum = giftNum - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionStarGift", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("message", ConstructorParameterDescription(self.message)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("upgradeMsgId", ConstructorParameterDescription(self.upgradeMsgId)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peer", ConstructorParameterDescription(self.peer)), ("savedId", ConstructorParameterDescription(self.savedId)), ("prepaidUpgradeHash", ConstructorParameterDescription(self.prepaidUpgradeHash)), ("giftMsgId", ConstructorParameterDescription(self.giftMsgId)), ("toId", ConstructorParameterDescription(self.toId)), ("giftNum", ConstructorParameterDescription(self.giftNum))]) - } - } - public class Cons_messageActionStarGiftPurchaseOffer: TypeConstructorDescription { - public var flags: Int32 - public var gift: Api.StarGift - public var price: Api.StarsAmount - public var expiresAt: Int32 - public init(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount, expiresAt: Int32) { - self.flags = flags - self.gift = gift - self.price = price - self.expiresAt = expiresAt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionStarGiftPurchaseOffer", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("price", ConstructorParameterDescription(self.price)), ("expiresAt", ConstructorParameterDescription(self.expiresAt))]) - } - } - public class Cons_messageActionStarGiftPurchaseOfferDeclined: TypeConstructorDescription { - public var flags: Int32 - public var gift: Api.StarGift - public var price: Api.StarsAmount - public init(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount) { - self.flags = flags - self.gift = gift - self.price = price - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionStarGiftPurchaseOfferDeclined", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("price", ConstructorParameterDescription(self.price))]) - } - } - public class Cons_messageActionStarGiftUnique: TypeConstructorDescription { - public var flags: Int32 - public var gift: Api.StarGift - public var canExportAt: Int32? - public var transferStars: Int64? - public var fromId: Api.Peer? - public var peer: Api.Peer? - public var savedId: Int64? - public var resaleAmount: Api.StarsAmount? - public var canTransferAt: Int32? - public var canResellAt: Int32? - public var dropOriginalDetailsStars: Int64? - public var canCraftAt: Int32? - public init(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?, canCraftAt: Int32?) { - self.flags = flags - self.gift = gift - self.canExportAt = canExportAt - self.transferStars = transferStars - self.fromId = fromId - self.peer = peer - self.savedId = savedId - self.resaleAmount = resaleAmount - self.canTransferAt = canTransferAt - self.canResellAt = canResellAt - self.dropOriginalDetailsStars = dropOriginalDetailsStars - self.canCraftAt = canCraftAt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionStarGiftUnique", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("canExportAt", ConstructorParameterDescription(self.canExportAt)), ("transferStars", ConstructorParameterDescription(self.transferStars)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peer", ConstructorParameterDescription(self.peer)), ("savedId", ConstructorParameterDescription(self.savedId)), ("resaleAmount", ConstructorParameterDescription(self.resaleAmount)), ("canTransferAt", ConstructorParameterDescription(self.canTransferAt)), ("canResellAt", ConstructorParameterDescription(self.canResellAt)), ("dropOriginalDetailsStars", ConstructorParameterDescription(self.dropOriginalDetailsStars)), ("canCraftAt", ConstructorParameterDescription(self.canCraftAt))]) - } - } - public class Cons_messageActionSuggestBirthday: TypeConstructorDescription { - public var birthday: Api.Birthday - public init(birthday: Api.Birthday) { - self.birthday = birthday - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSuggestBirthday", [("birthday", ConstructorParameterDescription(self.birthday))]) - } - } - public class Cons_messageActionSuggestProfilePhoto: TypeConstructorDescription { - public var photo: Api.Photo - public init(photo: Api.Photo) { - self.photo = photo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSuggestProfilePhoto", [("photo", ConstructorParameterDescription(self.photo))]) - } - } - public class Cons_messageActionSuggestedPostApproval: TypeConstructorDescription { - public var flags: Int32 - public var rejectComment: String? - public var scheduleDate: Int32? - public var price: Api.StarsAmount? - public init(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) { - self.flags = flags - self.rejectComment = rejectComment - self.scheduleDate = scheduleDate - self.price = price - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSuggestedPostApproval", [("flags", ConstructorParameterDescription(self.flags)), ("rejectComment", ConstructorParameterDescription(self.rejectComment)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate)), ("price", ConstructorParameterDescription(self.price))]) - } - } - public class Cons_messageActionSuggestedPostRefund: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSuggestedPostRefund", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_messageActionSuggestedPostSuccess: TypeConstructorDescription { - public var price: Api.StarsAmount - public init(price: Api.StarsAmount) { - self.price = price - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionSuggestedPostSuccess", [("price", ConstructorParameterDescription(self.price))]) - } - } - public class Cons_messageActionTodoAppendTasks: TypeConstructorDescription { - public var list: [Api.TodoItem] - public init(list: [Api.TodoItem]) { - self.list = list - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionTodoAppendTasks", [("list", ConstructorParameterDescription(self.list))]) - } - } - public class Cons_messageActionTodoCompletions: TypeConstructorDescription { - public var completed: [Int32] - public var incompleted: [Int32] - public init(completed: [Int32], incompleted: [Int32]) { - self.completed = completed - self.incompleted = incompleted - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionTodoCompletions", [("completed", ConstructorParameterDescription(self.completed)), ("incompleted", ConstructorParameterDescription(self.incompleted))]) - } - } - public class Cons_messageActionTopicCreate: TypeConstructorDescription { - public var flags: Int32 - public var title: String - public var iconColor: Int32 - public var iconEmojiId: Int64? - public init(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) { - self.flags = flags - self.title = title - self.iconColor = iconColor - self.iconEmojiId = iconEmojiId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionTopicCreate", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("iconColor", ConstructorParameterDescription(self.iconColor)), ("iconEmojiId", ConstructorParameterDescription(self.iconEmojiId))]) - } - } - public class Cons_messageActionTopicEdit: TypeConstructorDescription { - public var flags: Int32 - public var title: String? - public var iconEmojiId: Int64? - public var closed: Api.Bool? - public var hidden: Api.Bool? - public init(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) { - self.flags = flags - self.title = title - self.iconEmojiId = iconEmojiId - self.closed = closed - self.hidden = hidden - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionTopicEdit", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("iconEmojiId", ConstructorParameterDescription(self.iconEmojiId)), ("closed", ConstructorParameterDescription(self.closed)), ("hidden", ConstructorParameterDescription(self.hidden))]) - } - } - public class Cons_messageActionWebViewDataSent: TypeConstructorDescription { - public var text: String - public init(text: String) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionWebViewDataSent", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_messageActionWebViewDataSentMe: TypeConstructorDescription { - public var text: String - public var data: String - public init(text: String, data: String) { - self.text = text - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageActionWebViewDataSentMe", [("text", ConstructorParameterDescription(self.text)), ("data", ConstructorParameterDescription(self.data))]) - } - } - case messageActionBoostApply(Cons_messageActionBoostApply) - case messageActionBotAllowed(Cons_messageActionBotAllowed) - case messageActionChangeCreator(Cons_messageActionChangeCreator) - case messageActionChannelCreate(Cons_messageActionChannelCreate) - case messageActionChannelMigrateFrom(Cons_messageActionChannelMigrateFrom) - case messageActionChatAddUser(Cons_messageActionChatAddUser) - case messageActionChatCreate(Cons_messageActionChatCreate) - case messageActionChatDeletePhoto - case messageActionChatDeleteUser(Cons_messageActionChatDeleteUser) - case messageActionChatEditPhoto(Cons_messageActionChatEditPhoto) - case messageActionChatEditTitle(Cons_messageActionChatEditTitle) - case messageActionChatJoinedByLink(Cons_messageActionChatJoinedByLink) - case messageActionChatJoinedByRequest - case messageActionChatMigrateTo(Cons_messageActionChatMigrateTo) - case messageActionConferenceCall(Cons_messageActionConferenceCall) - case messageActionContactSignUp - case messageActionCustomAction(Cons_messageActionCustomAction) - case messageActionEmpty - case messageActionGameScore(Cons_messageActionGameScore) - case messageActionGeoProximityReached(Cons_messageActionGeoProximityReached) - case messageActionGiftCode(Cons_messageActionGiftCode) - case messageActionGiftPremium(Cons_messageActionGiftPremium) - case messageActionGiftStars(Cons_messageActionGiftStars) - case messageActionGiftTon(Cons_messageActionGiftTon) - case messageActionGiveawayLaunch(Cons_messageActionGiveawayLaunch) - case messageActionGiveawayResults(Cons_messageActionGiveawayResults) - case messageActionGroupCall(Cons_messageActionGroupCall) - case messageActionGroupCallScheduled(Cons_messageActionGroupCallScheduled) - case messageActionHistoryClear - case messageActionInviteToGroupCall(Cons_messageActionInviteToGroupCall) - case messageActionManagedBotCreated(Cons_messageActionManagedBotCreated) - case messageActionNewCreatorPending(Cons_messageActionNewCreatorPending) - case messageActionNoForwardsRequest(Cons_messageActionNoForwardsRequest) - case messageActionNoForwardsToggle(Cons_messageActionNoForwardsToggle) - case messageActionPaidMessagesPrice(Cons_messageActionPaidMessagesPrice) - case messageActionPaidMessagesRefunded(Cons_messageActionPaidMessagesRefunded) - case messageActionPaymentRefunded(Cons_messageActionPaymentRefunded) - case messageActionPaymentSent(Cons_messageActionPaymentSent) - case messageActionPaymentSentMe(Cons_messageActionPaymentSentMe) - case messageActionPhoneCall(Cons_messageActionPhoneCall) - case messageActionPinMessage - case messageActionPollAppendAnswer(Cons_messageActionPollAppendAnswer) - case messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer) - case messageActionPrizeStars(Cons_messageActionPrizeStars) - case messageActionRequestedPeer(Cons_messageActionRequestedPeer) - case messageActionRequestedPeerSentMe(Cons_messageActionRequestedPeerSentMe) - case messageActionScreenshotTaken - case messageActionSecureValuesSent(Cons_messageActionSecureValuesSent) - case messageActionSecureValuesSentMe(Cons_messageActionSecureValuesSentMe) - case messageActionSetChatTheme(Cons_messageActionSetChatTheme) - case messageActionSetChatWallPaper(Cons_messageActionSetChatWallPaper) - case messageActionSetMessagesTTL(Cons_messageActionSetMessagesTTL) - case messageActionStarGift(Cons_messageActionStarGift) - case messageActionStarGiftPurchaseOffer(Cons_messageActionStarGiftPurchaseOffer) - case messageActionStarGiftPurchaseOfferDeclined(Cons_messageActionStarGiftPurchaseOfferDeclined) - case messageActionStarGiftUnique(Cons_messageActionStarGiftUnique) - case messageActionSuggestBirthday(Cons_messageActionSuggestBirthday) - case messageActionSuggestProfilePhoto(Cons_messageActionSuggestProfilePhoto) - case messageActionSuggestedPostApproval(Cons_messageActionSuggestedPostApproval) - case messageActionSuggestedPostRefund(Cons_messageActionSuggestedPostRefund) - case messageActionSuggestedPostSuccess(Cons_messageActionSuggestedPostSuccess) - case messageActionTodoAppendTasks(Cons_messageActionTodoAppendTasks) - case messageActionTodoCompletions(Cons_messageActionTodoCompletions) - case messageActionTopicCreate(Cons_messageActionTopicCreate) - case messageActionTopicEdit(Cons_messageActionTopicEdit) - case messageActionWebViewDataSent(Cons_messageActionWebViewDataSent) - case messageActionWebViewDataSentMe(Cons_messageActionWebViewDataSentMe) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageActionBoostApply(let _data): - if boxed { - buffer.appendInt32(-872240531) - } - serializeInt32(_data.boosts, buffer: buffer, boxed: false) - break - case .messageActionBotAllowed(let _data): - if boxed { - buffer.appendInt32(-988359047) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.domain!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.app!.serialize(buffer, true) - } - break - case .messageActionChangeCreator(let _data): - if boxed { - buffer.appendInt32(-511160261) - } - serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false) - break - case .messageActionChannelCreate(let _data): - if boxed { - buffer.appendInt32(-1781355374) - } - serializeString(_data.title, buffer: buffer, boxed: false) - break - case .messageActionChannelMigrateFrom(let _data): - if boxed { - buffer.appendInt32(-365344535) - } - serializeString(_data.title, buffer: buffer, boxed: false) - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - break - case .messageActionChatAddUser(let _data): - if boxed { - buffer.appendInt32(365886720) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionChatCreate(let _data): - if boxed { - buffer.appendInt32(-1119368275) - } - serializeString(_data.title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionChatDeletePhoto: - if boxed { - buffer.appendInt32(-1780220945) - } - break - case .messageActionChatDeleteUser(let _data): - if boxed { - buffer.appendInt32(-1539362612) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - case .messageActionChatEditPhoto(let _data): - if boxed { - buffer.appendInt32(2144015272) - } - _data.photo.serialize(buffer, true) - break - case .messageActionChatEditTitle(let _data): - if boxed { - buffer.appendInt32(-1247687078) - } - serializeString(_data.title, buffer: buffer, boxed: false) - break - case .messageActionChatJoinedByLink(let _data): - if boxed { - buffer.appendInt32(51520707) - } - serializeInt64(_data.inviterId, buffer: buffer, boxed: false) - break - case .messageActionChatJoinedByRequest: - if boxed { - buffer.appendInt32(-339958837) - } - break - case .messageActionChatMigrateTo(let _data): - if boxed { - buffer.appendInt32(-519864430) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - break - case .messageActionConferenceCall(let _data): - if boxed { - buffer.appendInt32(805187450) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.callId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.duration!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.otherParticipants!.count)) - for item in _data.otherParticipants! { - item.serialize(buffer, true) - } - } - break - case .messageActionContactSignUp: - if boxed { - buffer.appendInt32(-202219658) - } - break - case .messageActionCustomAction(let _data): - if boxed { - buffer.appendInt32(-85549226) - } - serializeString(_data.message, buffer: buffer, boxed: false) - break - case .messageActionEmpty: - if boxed { - buffer.appendInt32(-1230047312) - } - break - case .messageActionGameScore(let _data): - if boxed { - buffer.appendInt32(-1834538890) - } - serializeInt64(_data.gameId, buffer: buffer, boxed: false) - serializeInt32(_data.score, buffer: buffer, boxed: false) - break - case .messageActionGeoProximityReached(let _data): - if boxed { - buffer.appendInt32(-1730095465) - } - _data.fromId.serialize(buffer, true) - _data.toId.serialize(buffer, true) - serializeInt32(_data.distance, buffer: buffer, boxed: false) - break - case .messageActionGiftCode(let _data): - if boxed { - buffer.appendInt32(834962247) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.boostPeer!.serialize(buffer, true) - } - serializeInt32(_data.days, buffer: buffer, boxed: false) - serializeString(_data.slug, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.currency!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.amount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.message!.serialize(buffer, true) - } - break - case .messageActionGiftPremium(let _data): - if boxed { - buffer.appendInt32(1223234306) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeInt32(_data.days, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.message!.serialize(buffer, true) - } - break - case .messageActionGiftStars(let _data): - if boxed { - buffer.appendInt32(1171632161) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.transactionId!, buffer: buffer, boxed: false) - } - break - case .messageActionGiftTon(let _data): - if boxed { - buffer.appendInt32(-1465661799) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeString(_data.cryptoCurrency, buffer: buffer, boxed: false) - serializeInt64(_data.cryptoAmount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.transactionId!, buffer: buffer, boxed: false) - } - break - case .messageActionGiveawayLaunch(let _data): - if boxed { - buffer.appendInt32(-1475391004) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.stars!, buffer: buffer, boxed: false) - } - break - case .messageActionGiveawayResults(let _data): - if boxed { - buffer.appendInt32(-2015170219) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) - serializeInt32(_data.unclaimedCount, buffer: buffer, boxed: false) - break - case .messageActionGroupCall(let _data): - if boxed { - buffer.appendInt32(2047704898) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.call.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.duration!, buffer: buffer, boxed: false) - } - break - case .messageActionGroupCallScheduled(let _data): - if boxed { - buffer.appendInt32(-1281329567) - } - _data.call.serialize(buffer, true) - serializeInt32(_data.scheduleDate, buffer: buffer, boxed: false) - break - case .messageActionHistoryClear: - if boxed { - buffer.appendInt32(-1615153660) - } - break - case .messageActionInviteToGroupCall(let _data): - if boxed { - buffer.appendInt32(1345295095) - } - _data.call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .messageActionManagedBotCreated(let _data): - if boxed { - buffer.appendInt32(375414334) - } - serializeInt64(_data.botId, buffer: buffer, boxed: false) - break - case .messageActionNewCreatorPending(let _data): - if boxed { - buffer.appendInt32(-1333866363) - } - serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false) - break - case .messageActionNoForwardsRequest(let _data): - if boxed { - buffer.appendInt32(1042781114) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.prevValue.serialize(buffer, true) - _data.newValue.serialize(buffer, true) - break - case .messageActionNoForwardsToggle(let _data): - if boxed { - buffer.appendInt32(-1082301070) - } - _data.prevValue.serialize(buffer, true) - _data.newValue.serialize(buffer, true) - break - case .messageActionPaidMessagesPrice(let _data): - if boxed { - buffer.appendInt32(-2068281992) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - break - case .messageActionPaidMessagesRefunded(let _data): - if boxed { - buffer.appendInt32(-1407246387) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - break - case .messageActionPaymentRefunded(let _data): - if boxed { - buffer.appendInt32(1102307842) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.payload!, buffer: buffer, boxed: false) - } - _data.charge.serialize(buffer, true) - break - case .messageActionPaymentSent(let _data): - if boxed { - buffer.appendInt32(-970673810) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.invoiceSlug!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.subscriptionUntilDate!, buffer: buffer, boxed: false) - } - break - case .messageActionPaymentSentMe(let _data): - if boxed { - buffer.appendInt32(-6288180) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - serializeBytes(_data.payload, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.info!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.shippingOptionId!, buffer: buffer, boxed: false) - } - _data.charge.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.subscriptionUntilDate!, buffer: buffer, boxed: false) - } - break - case .messageActionPhoneCall(let _data): - if boxed { - buffer.appendInt32(-2132731265) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.callId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.reason!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.duration!, buffer: buffer, boxed: false) - } - break - case .messageActionPinMessage: - if boxed { - buffer.appendInt32(-1799538451) - } - break - case .messageActionPollAppendAnswer(let _data): - if boxed { - buffer.appendInt32(-1650340500) - } - _data.answer.serialize(buffer, true) - break - case .messageActionPollDeleteAnswer(let _data): - if boxed { - buffer.appendInt32(966161628) - } - _data.answer.serialize(buffer, true) - break - case .messageActionPrizeStars(let _data): - if boxed { - buffer.appendInt32(-1341372510) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - serializeString(_data.transactionId, buffer: buffer, boxed: false) - _data.boostPeer.serialize(buffer, true) - serializeInt32(_data.giveawayMsgId, buffer: buffer, boxed: false) - break - case .messageActionRequestedPeer(let _data): - if boxed { - buffer.appendInt32(827428507) - } - serializeInt32(_data.buttonId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - break - case .messageActionRequestedPeerSentMe(let _data): - if boxed { - buffer.appendInt32(-1816979384) - } - serializeInt32(_data.buttonId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - break - case .messageActionScreenshotTaken: - if boxed { - buffer.appendInt32(1200788123) - } - break - case .messageActionSecureValuesSent(let _data): - if boxed { - buffer.appendInt32(-648257196) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.types.count)) - for item in _data.types { - item.serialize(buffer, true) - } - break - case .messageActionSecureValuesSentMe(let _data): - if boxed { - buffer.appendInt32(455635795) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.values.count)) - for item in _data.values { - item.serialize(buffer, true) - } - _data.credentials.serialize(buffer, true) - break - case .messageActionSetChatTheme(let _data): - if boxed { - buffer.appendInt32(-1189364422) - } - _data.theme.serialize(buffer, true) - break - case .messageActionSetChatWallPaper(let _data): - if boxed { - buffer.appendInt32(1348510708) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.wallpaper.serialize(buffer, true) - break - case .messageActionSetMessagesTTL(let _data): - if boxed { - buffer.appendInt32(1007897979) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.period, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.autoSettingFrom!, buffer: buffer, boxed: false) - } - break - case .messageActionStarGift(let _data): - if boxed { - buffer.appendInt32(-366202413) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.gift.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.message!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.convertStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.upgradeMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - _data.peer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - serializeInt64(_data.savedId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - serializeString(_data.prepaidUpgradeHash!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.giftMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 18) != 0 { - _data.toId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 19) != 0 { - serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) - } - break - case .messageActionStarGiftPurchaseOffer(let _data): - if boxed { - buffer.appendInt32(2000845012) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.gift.serialize(buffer, true) - _data.price.serialize(buffer, true) - serializeInt32(_data.expiresAt, buffer: buffer, boxed: false) - break - case .messageActionStarGiftPurchaseOfferDeclined(let _data): - if boxed { - buffer.appendInt32(1940760427) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.gift.serialize(buffer, true) - _data.price.serialize(buffer, true) - break - case .messageActionStarGiftUnique(let _data): - if boxed { - buffer.appendInt32(-423422686) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.gift.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.canExportAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.transferStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - _data.peer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeInt64(_data.savedId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.resaleAmount!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - serializeInt32(_data.canTransferAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.canResellAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - serializeInt64(_data.dropOriginalDetailsStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.canCraftAt!, buffer: buffer, boxed: false) - } - break - case .messageActionSuggestBirthday(let _data): - if boxed { - buffer.appendInt32(747579941) - } - _data.birthday.serialize(buffer, true) - break - case .messageActionSuggestProfilePhoto(let _data): - if boxed { - buffer.appendInt32(1474192222) - } - _data.photo.serialize(buffer, true) - break - case .messageActionSuggestedPostApproval(let _data): - if boxed { - buffer.appendInt32(-293988970) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.rejectComment!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.scheduleDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.price!.serialize(buffer, true) - } - break - case .messageActionSuggestedPostRefund(let _data): - if boxed { - buffer.appendInt32(1777932024) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .messageActionSuggestedPostSuccess(let _data): - if boxed { - buffer.appendInt32(-1780625559) - } - _data.price.serialize(buffer, true) - break - case .messageActionTodoAppendTasks(let _data): - if boxed { - buffer.appendInt32(-940721021) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.list.count)) - for item in _data.list { - item.serialize(buffer, true) - } - break - case .messageActionTodoCompletions(let _data): - if boxed { - buffer.appendInt32(-864265079) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.completed.count)) - for item in _data.completed { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.incompleted.count)) - for item in _data.incompleted { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .messageActionTopicCreate(let _data): - if boxed { - buffer.appendInt32(228168278) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeInt32(_data.iconColor, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.iconEmojiId!, buffer: buffer, boxed: false) - } - break - case .messageActionTopicEdit(let _data): - if boxed { - buffer.appendInt32(-1064024032) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt64(_data.iconEmojiId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.closed!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.hidden!.serialize(buffer, true) - } - break - case .messageActionWebViewDataSent(let _data): - if boxed { - buffer.appendInt32(-1262252875) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .messageActionWebViewDataSentMe(let _data): - if boxed { - buffer.appendInt32(1205698681) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeString(_data.data, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageActionBoostApply(let _data): - return ("messageActionBoostApply", [("boosts", ConstructorParameterDescription(_data.boosts))]) - case .messageActionBotAllowed(let _data): - return ("messageActionBotAllowed", [("flags", ConstructorParameterDescription(_data.flags)), ("domain", ConstructorParameterDescription(_data.domain)), ("app", ConstructorParameterDescription(_data.app))]) - case .messageActionChangeCreator(let _data): - return ("messageActionChangeCreator", [("newCreatorId", ConstructorParameterDescription(_data.newCreatorId))]) - case .messageActionChannelCreate(let _data): - return ("messageActionChannelCreate", [("title", ConstructorParameterDescription(_data.title))]) - case .messageActionChannelMigrateFrom(let _data): - return ("messageActionChannelMigrateFrom", [("title", ConstructorParameterDescription(_data.title)), ("chatId", ConstructorParameterDescription(_data.chatId))]) - case .messageActionChatAddUser(let _data): - return ("messageActionChatAddUser", [("users", ConstructorParameterDescription(_data.users))]) - case .messageActionChatCreate(let _data): - return ("messageActionChatCreate", [("title", ConstructorParameterDescription(_data.title)), ("users", ConstructorParameterDescription(_data.users))]) - case .messageActionChatDeletePhoto: - return ("messageActionChatDeletePhoto", []) - case .messageActionChatDeleteUser(let _data): - return ("messageActionChatDeleteUser", [("userId", ConstructorParameterDescription(_data.userId))]) - case .messageActionChatEditPhoto(let _data): - return ("messageActionChatEditPhoto", [("photo", ConstructorParameterDescription(_data.photo))]) - case .messageActionChatEditTitle(let _data): - return ("messageActionChatEditTitle", [("title", ConstructorParameterDescription(_data.title))]) - case .messageActionChatJoinedByLink(let _data): - return ("messageActionChatJoinedByLink", [("inviterId", ConstructorParameterDescription(_data.inviterId))]) - case .messageActionChatJoinedByRequest: - return ("messageActionChatJoinedByRequest", []) - case .messageActionChatMigrateTo(let _data): - return ("messageActionChatMigrateTo", [("channelId", ConstructorParameterDescription(_data.channelId))]) - case .messageActionConferenceCall(let _data): - return ("messageActionConferenceCall", [("flags", ConstructorParameterDescription(_data.flags)), ("callId", ConstructorParameterDescription(_data.callId)), ("duration", ConstructorParameterDescription(_data.duration)), ("otherParticipants", ConstructorParameterDescription(_data.otherParticipants))]) - case .messageActionContactSignUp: - return ("messageActionContactSignUp", []) - case .messageActionCustomAction(let _data): - return ("messageActionCustomAction", [("message", ConstructorParameterDescription(_data.message))]) - case .messageActionEmpty: - return ("messageActionEmpty", []) - case .messageActionGameScore(let _data): - return ("messageActionGameScore", [("gameId", ConstructorParameterDescription(_data.gameId)), ("score", ConstructorParameterDescription(_data.score))]) - case .messageActionGeoProximityReached(let _data): - return ("messageActionGeoProximityReached", [("fromId", ConstructorParameterDescription(_data.fromId)), ("toId", ConstructorParameterDescription(_data.toId)), ("distance", ConstructorParameterDescription(_data.distance))]) - case .messageActionGiftCode(let _data): - return ("messageActionGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("days", ConstructorParameterDescription(_data.days)), ("slug", ConstructorParameterDescription(_data.slug)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("message", ConstructorParameterDescription(_data.message))]) - case .messageActionGiftPremium(let _data): - return ("messageActionGiftPremium", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("days", ConstructorParameterDescription(_data.days)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("message", ConstructorParameterDescription(_data.message))]) - case .messageActionGiftStars(let _data): - return ("messageActionGiftStars", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("stars", ConstructorParameterDescription(_data.stars)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId))]) - case .messageActionGiftTon(let _data): - return ("messageActionGiftTon", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId))]) - case .messageActionGiveawayLaunch(let _data): - return ("messageActionGiveawayLaunch", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars))]) - case .messageActionGiveawayResults(let _data): - return ("messageActionGiveawayResults", [("flags", ConstructorParameterDescription(_data.flags)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(_data.unclaimedCount))]) - case .messageActionGroupCall(let _data): - return ("messageActionGroupCall", [("flags", ConstructorParameterDescription(_data.flags)), ("call", ConstructorParameterDescription(_data.call)), ("duration", ConstructorParameterDescription(_data.duration))]) - case .messageActionGroupCallScheduled(let _data): - return ("messageActionGroupCallScheduled", [("call", ConstructorParameterDescription(_data.call)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate))]) - case .messageActionHistoryClear: - return ("messageActionHistoryClear", []) - case .messageActionInviteToGroupCall(let _data): - return ("messageActionInviteToGroupCall", [("call", ConstructorParameterDescription(_data.call)), ("users", ConstructorParameterDescription(_data.users))]) - case .messageActionManagedBotCreated(let _data): - return ("messageActionManagedBotCreated", [("botId", ConstructorParameterDescription(_data.botId))]) - case .messageActionNewCreatorPending(let _data): - return ("messageActionNewCreatorPending", [("newCreatorId", ConstructorParameterDescription(_data.newCreatorId))]) - case .messageActionNoForwardsRequest(let _data): - return ("messageActionNoForwardsRequest", [("flags", ConstructorParameterDescription(_data.flags)), ("prevValue", ConstructorParameterDescription(_data.prevValue)), ("newValue", ConstructorParameterDescription(_data.newValue))]) - case .messageActionNoForwardsToggle(let _data): - return ("messageActionNoForwardsToggle", [("prevValue", ConstructorParameterDescription(_data.prevValue)), ("newValue", ConstructorParameterDescription(_data.newValue))]) - case .messageActionPaidMessagesPrice(let _data): - return ("messageActionPaidMessagesPrice", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars))]) - case .messageActionPaidMessagesRefunded(let _data): - return ("messageActionPaidMessagesRefunded", [("count", ConstructorParameterDescription(_data.count)), ("stars", ConstructorParameterDescription(_data.stars))]) - case .messageActionPaymentRefunded(let _data): - return ("messageActionPaymentRefunded", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("payload", ConstructorParameterDescription(_data.payload)), ("charge", ConstructorParameterDescription(_data.charge))]) - case .messageActionPaymentSent(let _data): - return ("messageActionPaymentSent", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("invoiceSlug", ConstructorParameterDescription(_data.invoiceSlug)), ("subscriptionUntilDate", ConstructorParameterDescription(_data.subscriptionUntilDate))]) - case .messageActionPaymentSentMe(let _data): - return ("messageActionPaymentSentMe", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("payload", ConstructorParameterDescription(_data.payload)), ("info", ConstructorParameterDescription(_data.info)), ("shippingOptionId", ConstructorParameterDescription(_data.shippingOptionId)), ("charge", ConstructorParameterDescription(_data.charge)), ("subscriptionUntilDate", ConstructorParameterDescription(_data.subscriptionUntilDate))]) - case .messageActionPhoneCall(let _data): - return ("messageActionPhoneCall", [("flags", ConstructorParameterDescription(_data.flags)), ("callId", ConstructorParameterDescription(_data.callId)), ("reason", ConstructorParameterDescription(_data.reason)), ("duration", ConstructorParameterDescription(_data.duration))]) - case .messageActionPinMessage: - return ("messageActionPinMessage", []) - case .messageActionPollAppendAnswer(let _data): - return ("messageActionPollAppendAnswer", [("answer", ConstructorParameterDescription(_data.answer))]) - case .messageActionPollDeleteAnswer(let _data): - return ("messageActionPollDeleteAnswer", [("answer", ConstructorParameterDescription(_data.answer))]) - case .messageActionPrizeStars(let _data): - return ("messageActionPrizeStars", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("transactionId", ConstructorParameterDescription(_data.transactionId)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("giveawayMsgId", ConstructorParameterDescription(_data.giveawayMsgId))]) - case .messageActionRequestedPeer(let _data): - return ("messageActionRequestedPeer", [("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peers", ConstructorParameterDescription(_data.peers))]) - case .messageActionRequestedPeerSentMe(let _data): - return ("messageActionRequestedPeerSentMe", [("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peers", ConstructorParameterDescription(_data.peers))]) - case .messageActionScreenshotTaken: - return ("messageActionScreenshotTaken", []) - case .messageActionSecureValuesSent(let _data): - return ("messageActionSecureValuesSent", [("types", ConstructorParameterDescription(_data.types))]) - case .messageActionSecureValuesSentMe(let _data): - return ("messageActionSecureValuesSentMe", [("values", ConstructorParameterDescription(_data.values)), ("credentials", ConstructorParameterDescription(_data.credentials))]) - case .messageActionSetChatTheme(let _data): - return ("messageActionSetChatTheme", [("theme", ConstructorParameterDescription(_data.theme))]) - case .messageActionSetChatWallPaper(let _data): - return ("messageActionSetChatWallPaper", [("flags", ConstructorParameterDescription(_data.flags)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) - case .messageActionSetMessagesTTL(let _data): - return ("messageActionSetMessagesTTL", [("flags", ConstructorParameterDescription(_data.flags)), ("period", ConstructorParameterDescription(_data.period)), ("autoSettingFrom", ConstructorParameterDescription(_data.autoSettingFrom))]) - case .messageActionStarGift(let _data): - return ("messageActionStarGift", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("message", ConstructorParameterDescription(_data.message)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("upgradeMsgId", ConstructorParameterDescription(_data.upgradeMsgId)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("prepaidUpgradeHash", ConstructorParameterDescription(_data.prepaidUpgradeHash)), ("giftMsgId", ConstructorParameterDescription(_data.giftMsgId)), ("toId", ConstructorParameterDescription(_data.toId)), ("giftNum", ConstructorParameterDescription(_data.giftNum))]) - case .messageActionStarGiftPurchaseOffer(let _data): - return ("messageActionStarGiftPurchaseOffer", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("price", ConstructorParameterDescription(_data.price)), ("expiresAt", ConstructorParameterDescription(_data.expiresAt))]) - case .messageActionStarGiftPurchaseOfferDeclined(let _data): - return ("messageActionStarGiftPurchaseOfferDeclined", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("price", ConstructorParameterDescription(_data.price))]) - case .messageActionStarGiftUnique(let _data): - return ("messageActionStarGiftUnique", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("canExportAt", ConstructorParameterDescription(_data.canExportAt)), ("transferStars", ConstructorParameterDescription(_data.transferStars)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("resaleAmount", ConstructorParameterDescription(_data.resaleAmount)), ("canTransferAt", ConstructorParameterDescription(_data.canTransferAt)), ("canResellAt", ConstructorParameterDescription(_data.canResellAt)), ("dropOriginalDetailsStars", ConstructorParameterDescription(_data.dropOriginalDetailsStars)), ("canCraftAt", ConstructorParameterDescription(_data.canCraftAt))]) - case .messageActionSuggestBirthday(let _data): - return ("messageActionSuggestBirthday", [("birthday", ConstructorParameterDescription(_data.birthday))]) - case .messageActionSuggestProfilePhoto(let _data): - return ("messageActionSuggestProfilePhoto", [("photo", ConstructorParameterDescription(_data.photo))]) - case .messageActionSuggestedPostApproval(let _data): - return ("messageActionSuggestedPostApproval", [("flags", ConstructorParameterDescription(_data.flags)), ("rejectComment", ConstructorParameterDescription(_data.rejectComment)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate)), ("price", ConstructorParameterDescription(_data.price))]) - case .messageActionSuggestedPostRefund(let _data): - return ("messageActionSuggestedPostRefund", [("flags", ConstructorParameterDescription(_data.flags))]) - case .messageActionSuggestedPostSuccess(let _data): - return ("messageActionSuggestedPostSuccess", [("price", ConstructorParameterDescription(_data.price))]) - case .messageActionTodoAppendTasks(let _data): - return ("messageActionTodoAppendTasks", [("list", ConstructorParameterDescription(_data.list))]) - case .messageActionTodoCompletions(let _data): - return ("messageActionTodoCompletions", [("completed", ConstructorParameterDescription(_data.completed)), ("incompleted", ConstructorParameterDescription(_data.incompleted))]) - case .messageActionTopicCreate(let _data): - return ("messageActionTopicCreate", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("iconColor", ConstructorParameterDescription(_data.iconColor)), ("iconEmojiId", ConstructorParameterDescription(_data.iconEmojiId))]) - case .messageActionTopicEdit(let _data): - return ("messageActionTopicEdit", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("iconEmojiId", ConstructorParameterDescription(_data.iconEmojiId)), ("closed", ConstructorParameterDescription(_data.closed)), ("hidden", ConstructorParameterDescription(_data.hidden))]) - case .messageActionWebViewDataSent(let _data): - return ("messageActionWebViewDataSent", [("text", ConstructorParameterDescription(_data.text))]) - case .messageActionWebViewDataSentMe(let _data): - return ("messageActionWebViewDataSentMe", [("text", ConstructorParameterDescription(_data.text)), ("data", ConstructorParameterDescription(_data.data))]) - } - } - - public static func parse_messageActionBoostApply(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionBoostApply(Cons_messageActionBoostApply(boosts: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - var _3: Api.BotApp? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.BotApp - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionBotAllowed(Cons_messageActionBotAllowed(flags: _1!, domain: _2, app: _3)) - } - else { - return nil - } - } - public static func parse_messageActionChangeCreator(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChangeCreator(Cons_messageActionChangeCreator(newCreatorId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChannelCreate(Cons_messageActionChannelCreate(title: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChannelMigrateFrom(Cons_messageActionChannelMigrateFrom(title: _1!, chatId: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatAddUser(Cons_messageActionChatAddUser(users: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionChatCreate(Cons_messageActionChatCreate(title: _1!, users: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionChatDeletePhoto(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionChatDeletePhoto - } - public static func parse_messageActionChatDeleteUser(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatDeleteUser(Cons_messageActionChatDeleteUser(userId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChatEditPhoto(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditPhoto(Cons_messageActionChatEditPhoto(photo: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChatEditTitle(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatEditTitle(Cons_messageActionChatEditTitle(title: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatJoinedByLink(Cons_messageActionChatJoinedByLink(inviterId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionChatJoinedByRequest - } - public static func parse_messageActionChatMigrateTo(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionChatMigrateTo(Cons_messageActionChatMigrateTo(channelId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionConferenceCall(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionConferenceCall(Cons_messageActionConferenceCall(flags: _1!, callId: _2!, duration: _3, otherParticipants: _4)) - } - else { - return nil - } - } - public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionContactSignUp - } - public static func parse_messageActionCustomAction(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionCustomAction(Cons_messageActionCustomAction(message: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionEmpty(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionEmpty - } - public static func parse_messageActionGameScore(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGameScore(Cons_messageActionGameScore(gameId: _1!, score: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionGeoProximityReached(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGeoProximityReached(Cons_messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!)) - } - else { - return nil - } - } - public static func parse_messageActionGiftCode(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = parseString(reader) - } - var _6: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = reader.readInt64() - } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 { - _7 = parseString(reader) - } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 { - _8 = reader.readInt64() - } - var _9: Api.TextWithEntities? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageAction.messageActionGiftCode(Cons_messageActionGiftCode(flags: _1!, boostPeer: _2, days: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8, message: _9)) - } - else { - return nil - } - } - public static func parse_messageActionGiftPremium(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseString(reader) - } - var _6: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt64() - } - var _7: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionGiftPremium(Cons_messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7)) - } - else { - return nil - } - } - public static func parse_messageActionGiftStars(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseString(reader) - } - var _6: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt64() - } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageAction.messageActionGiftStars(Cons_messageActionGiftStars(flags: _1!, currency: _2!, amount: _3!, stars: _4!, cryptoCurrency: _5, cryptoAmount: _6, transactionId: _7)) - } - else { - return nil - } - } - public static func parse_messageActionGiftTon(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionGiftTon(Cons_messageActionGiftTon(flags: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, transactionId: _6)) - } - else { - return nil - } - } - public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGiveawayLaunch(Cons_messageActionGiveawayLaunch(flags: _1!, stars: _2)) - } - else { - return nil - } - } - public static func parse_messageActionGiveawayResults(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGiveawayResults(Cons_messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!)) - } - else { - return nil - } - } - public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGroupCall? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionGroupCall(Cons_messageActionGroupCall(flags: _1!, call: _2!, duration: _3)) - } - else { - return nil - } - } - public static func parse_messageActionGroupCallScheduled(_ reader: BufferReader) -> MessageAction? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionGroupCallScheduled(Cons_messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionHistoryClear(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionHistoryClear - } - public static func parse_messageActionInviteToGroupCall(_ reader: BufferReader) -> MessageAction? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionInviteToGroupCall(Cons_messageActionInviteToGroupCall(call: _1!, users: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionManagedBotCreated(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionManagedBotCreated(Cons_messageActionManagedBotCreated(botId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionNewCreatorPending(_ reader: BufferReader) -> MessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionNewCreatorPending(Cons_messageActionNewCreatorPending(newCreatorId: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionNoForwardsRequest(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _3: Api.Bool? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionNoForwardsRequest(Cons_messageActionNoForwardsRequest(flags: _1!, prevValue: _2!, newValue: _3!)) - } - else { - return nil - } - } - public static func parse_messageActionNoForwardsToggle(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Bool? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _2: Api.Bool? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionNoForwardsToggle(Cons_messageActionNoForwardsToggle(prevValue: _1!, newValue: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionPaidMessagesPrice(Cons_messageActionPaidMessagesPrice(flags: _1!, stars: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionPaidMessagesRefunded(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionPaidMessagesRefunded(Cons_messageActionPaidMessagesRefunded(count: _1!, stars: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionPaymentRefunded(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: String? - _3 = parseString(reader) - var _4: Int64? - _4 = reader.readInt64() - var _5: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseBytes(reader) - } - var _6: Api.PaymentCharge? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.PaymentCharge - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageAction.messageActionPaymentRefunded(Cons_messageActionPaymentRefunded(flags: _1!, peer: _2!, currency: _3!, totalAmount: _4!, payload: _5, charge: _6!)) - } - else { - return nil - } - } - public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionPaymentSent(Cons_messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4, subscriptionUntilDate: _5)) - } - else { - return nil - } - } - public static func parse_messageActionPaymentSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } - } - var _6: String? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = parseString(reader) - } - var _7: Api.PaymentCharge? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PaymentCharge - } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.MessageAction.messageActionPaymentSentMe(Cons_messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!, subscriptionUntilDate: _8)) - } - else { - return nil - } - } - public static func parse_messageActionPhoneCall(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PhoneCallDiscardReason? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason - } - } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionPhoneCall(Cons_messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4)) - } - else { - return nil - } - } - public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionPinMessage - } - public static func parse_messageActionPollAppendAnswer(_ reader: BufferReader) -> MessageAction? { - var _1: Api.PollAnswer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PollAnswer - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionPollAppendAnswer(Cons_messageActionPollAppendAnswer(answer: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionPollDeleteAnswer(_ reader: BufferReader) -> MessageAction? { - var _1: Api.PollAnswer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PollAnswer - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer(answer: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionPrizeStars(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionPrizeStars(Cons_messageActionPrizeStars(flags: _1!, stars: _2!, transactionId: _3!, boostPeer: _4!, giveawayMsgId: _5!)) - } - else { - return nil - } - } - public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeer(Cons_messageActionRequestedPeer(buttonId: _1!, peers: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionRequestedPeerSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.RequestedPeer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequestedPeer.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionRequestedPeerSentMe(Cons_messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionScreenshotTaken(_ reader: BufferReader) -> MessageAction? { - return Api.MessageAction.messageActionScreenshotTaken - } - public static func parse_messageActionSecureValuesSent(_ reader: BufferReader) -> MessageAction? { - var _1: [Api.SecureValueType]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueType.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSecureValuesSent(Cons_messageActionSecureValuesSent(types: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionSecureValuesSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: [Api.SecureValue]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - var _2: Api.SecureCredentialsEncrypted? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureCredentialsEncrypted - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionSecureValuesSentMe(Cons_messageActionSecureValuesSentMe(values: _1!, credentials: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { - var _1: Api.ChatTheme? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChatTheme - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSetChatTheme(Cons_messageActionSetChatTheme(theme: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.WallPaper? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WallPaper - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionSetChatWallPaper(Cons_messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionSetMessagesTTL(Cons_messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3)) - } - else { - return nil - } - } - public static func parse_messageActionStarGift(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarGift? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _3: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - var _4: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _4 = reader.readInt64() - } - var _5: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _5 = reader.readInt32() - } - var _6: Int64? - if Int(_1!) & Int(1 << 8) != 0 { - _6 = reader.readInt64() - } - var _7: Api.Peer? - if Int(_1!) & Int(1 << 11) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _8: Api.Peer? - if Int(_1!) & Int(1 << 12) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _9: Int64? - if Int(_1!) & Int(1 << 12) != 0 { - _9 = reader.readInt64() - } - var _10: String? - if Int(_1!) & Int(1 << 14) != 0 { - _10 = parseString(reader) - } - var _11: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _11 = reader.readInt32() - } - var _12: Api.Peer? - if Int(_1!) & Int(1 << 18) != 0 { - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _13: Int32? - if Int(_1!) & Int(1 << 19) != 0 { - _13 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 8) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 12) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 12) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 15) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 19) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.MessageAction.messageActionStarGift(Cons_messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6, fromId: _7, peer: _8, savedId: _9, prepaidUpgradeHash: _10, giftMsgId: _11, toId: _12, giftNum: _13)) - } - else { - return nil - } - } - public static func parse_messageActionStarGiftPurchaseOffer(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarGift? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _3: Api.StarsAmount? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionStarGiftPurchaseOffer(Cons_messageActionStarGiftPurchaseOffer(flags: _1!, gift: _2!, price: _3!, expiresAt: _4!)) - } - else { - return nil - } - } - public static func parse_messageActionStarGiftPurchaseOfferDeclined(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarGift? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _3: Api.StarsAmount? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageAction.messageActionStarGiftPurchaseOfferDeclined(Cons_messageActionStarGiftPurchaseOfferDeclined(flags: _1!, gift: _2!, price: _3!)) - } - else { - return nil - } - } - public static func parse_messageActionStarGiftUnique(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarGift? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _3 = reader.readInt32() - } - var _4: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _4 = reader.readInt64() - } - var _5: Api.Peer? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _6: Api.Peer? - if Int(_1!) & Int(1 << 7) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _7: Int64? - if Int(_1!) & Int(1 << 7) != 0 { - _7 = reader.readInt64() - } - var _8: Api.StarsAmount? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - } - var _9: Int32? - if Int(_1!) & Int(1 << 9) != 0 { - _9 = reader.readInt32() - } - var _10: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _10 = reader.readInt32() - } - var _11: Int64? - if Int(_1!) & Int(1 << 12) != 0 { - _11 = reader.readInt64() - } - var _12: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _12 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 15) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.MessageAction.messageActionStarGiftUnique(Cons_messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11, canCraftAt: _12)) - } - else { - return nil - } - } - public static func parse_messageActionSuggestBirthday(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Birthday? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Birthday - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestBirthday(Cons_messageActionSuggestBirthday(birthday: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestProfilePhoto(Cons_messageActionSuggestProfilePhoto(photo: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 2) != 0 { - _2 = parseString(reader) - } - var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _3 = reader.readInt32() - } - var _4: Api.StarsAmount? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionSuggestedPostApproval(Cons_messageActionSuggestedPostApproval(flags: _1!, rejectComment: _2, scheduleDate: _3, price: _4)) - } - else { - return nil - } - } - public static func parse_messageActionSuggestedPostRefund(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestedPostRefund(Cons_messageActionSuggestedPostRefund(flags: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionSuggestedPostSuccess(_ reader: BufferReader) -> MessageAction? { - var _1: Api.StarsAmount? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionSuggestedPostSuccess(Cons_messageActionSuggestedPostSuccess(price: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? { - var _1: [Api.TodoItem]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionTodoAppendTasks(Cons_messageActionTodoAppendTasks(list: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionTodoCompletions(_ reader: BufferReader) -> MessageAction? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionTodoCompletions(Cons_messageActionTodoCompletions(completed: _1!, incompleted: _2!)) - } - else { - return nil - } - } - public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionTopicCreate(Cons_messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4)) - } - else { - return nil - } - } - public static func parse_messageActionTopicEdit(_ reader: BufferReader) -> MessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt64() - } - var _4: Api.Bool? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _5: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionTopicEdit(Cons_messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5)) - } - else { - return nil - } - } - public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.MessageAction.messageActionWebViewDataSent(Cons_messageActionWebViewDataSent(text: _1!)) - } - else { - return nil - } - } - public static func parse_messageActionWebViewDataSentMe(_ reader: BufferReader) -> MessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageAction.messageActionWebViewDataSentMe(Cons_messageActionWebViewDataSentMe(text: _1!, data: _2!)) - } - else { - return nil - } - } - } -} diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index c774f8f008..f1b7b7f6e2 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -1,1004 +1,42 @@ public extension Api { - indirect enum MessageEntity: TypeConstructorDescription { - public class Cons_inputMessageEntityMentionName: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var userId: Api.InputUser - public init(offset: Int32, length: Int32, userId: Api.InputUser) { - self.offset = offset - self.length = length - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputMessageEntityMentionName", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_messageEntityBankCard: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityBankCard", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityBlockquote: TypeConstructorDescription { + enum MediaAreaCoordinates: TypeConstructorDescription { + public class Cons_mediaAreaCoordinates: TypeConstructorDescription { public var flags: Int32 - public var offset: Int32 - public var length: Int32 - public init(flags: Int32, offset: Int32, length: Int32) { - self.flags = flags - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityBlockquote", [("flags", ConstructorParameterDescription(self.flags)), ("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityBold: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityBold", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityBotCommand: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityBotCommand", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityCashtag: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityCashtag", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityCode: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityCode", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityCustomEmoji: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var documentId: Int64 - public init(offset: Int32, length: Int32, documentId: Int64) { - self.offset = offset - self.length = length - self.documentId = documentId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityCustomEmoji", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("documentId", ConstructorParameterDescription(self.documentId))]) - } - } - public class Cons_messageEntityDiffDelete: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityDiffDelete", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityDiffInsert: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityDiffInsert", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityDiffReplace: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var oldText: String - public init(offset: Int32, length: Int32, oldText: String) { - self.offset = offset - self.length = length - self.oldText = oldText - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityDiffReplace", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("oldText", ConstructorParameterDescription(self.oldText))]) - } - } - public class Cons_messageEntityEmail: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityEmail", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityFormattedDate: TypeConstructorDescription { - public var flags: Int32 - public var offset: Int32 - public var length: Int32 - public var date: Int32 - public init(flags: Int32, offset: Int32, length: Int32, date: Int32) { - self.flags = flags - self.offset = offset - self.length = length - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityFormattedDate", [("flags", ConstructorParameterDescription(self.flags)), ("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_messageEntityHashtag: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityHashtag", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityItalic: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityItalic", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityMention: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityMention", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityMentionName: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var userId: Int64 - public init(offset: Int32, length: Int32, userId: Int64) { - self.offset = offset - self.length = length - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityMentionName", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_messageEntityPhone: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityPhone", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityPre: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var language: String - public init(offset: Int32, length: Int32, language: String) { - self.offset = offset - self.length = length - self.language = language - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityPre", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("language", ConstructorParameterDescription(self.language))]) - } - } - public class Cons_messageEntitySpoiler: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntitySpoiler", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityStrike: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityStrike", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityTextUrl: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public var url: String - public init(offset: Int32, length: Int32, url: String) { - self.offset = offset - self.length = length - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityTextUrl", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("url", ConstructorParameterDescription(self.url))]) - } - } - public class Cons_messageEntityUnderline: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityUnderline", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityUnknown: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityUnknown", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_messageEntityUrl: TypeConstructorDescription { - public var offset: Int32 - public var length: Int32 - public init(offset: Int32, length: Int32) { - self.offset = offset - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEntityUrl", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) - } - } - case inputMessageEntityMentionName(Cons_inputMessageEntityMentionName) - case messageEntityBankCard(Cons_messageEntityBankCard) - case messageEntityBlockquote(Cons_messageEntityBlockquote) - case messageEntityBold(Cons_messageEntityBold) - case messageEntityBotCommand(Cons_messageEntityBotCommand) - case messageEntityCashtag(Cons_messageEntityCashtag) - case messageEntityCode(Cons_messageEntityCode) - case messageEntityCustomEmoji(Cons_messageEntityCustomEmoji) - case messageEntityDiffDelete(Cons_messageEntityDiffDelete) - case messageEntityDiffInsert(Cons_messageEntityDiffInsert) - case messageEntityDiffReplace(Cons_messageEntityDiffReplace) - case messageEntityEmail(Cons_messageEntityEmail) - case messageEntityFormattedDate(Cons_messageEntityFormattedDate) - case messageEntityHashtag(Cons_messageEntityHashtag) - case messageEntityItalic(Cons_messageEntityItalic) - case messageEntityMention(Cons_messageEntityMention) - case messageEntityMentionName(Cons_messageEntityMentionName) - case messageEntityPhone(Cons_messageEntityPhone) - case messageEntityPre(Cons_messageEntityPre) - case messageEntitySpoiler(Cons_messageEntitySpoiler) - case messageEntityStrike(Cons_messageEntityStrike) - case messageEntityTextUrl(Cons_messageEntityTextUrl) - case messageEntityUnderline(Cons_messageEntityUnderline) - case messageEntityUnknown(Cons_messageEntityUnknown) - case messageEntityUrl(Cons_messageEntityUrl) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputMessageEntityMentionName(let _data): - if boxed { - buffer.appendInt32(546203849) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - _data.userId.serialize(buffer, true) - break - case .messageEntityBankCard(let _data): - if boxed { - buffer.appendInt32(1981704948) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityBlockquote(let _data): - if boxed { - buffer.appendInt32(-238245204) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let _data): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let _data): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityCashtag(let _data): - if boxed { - buffer.appendInt32(1280209983) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let _data): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityCustomEmoji(let _data): - if boxed { - buffer.appendInt32(-925956616) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - break - case .messageEntityDiffDelete(let _data): - if boxed { - buffer.appendInt32(106086853) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityDiffInsert(let _data): - if boxed { - buffer.appendInt32(1903653142) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityDiffReplace(let _data): - if boxed { - buffer.appendInt32(-960371289) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeString(_data.oldText, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let _data): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityFormattedDate(let _data): - if boxed { - buffer.appendInt32(-1874147385) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let _data): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let _data): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let _data): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityMentionName(let _data): - if boxed { - buffer.appendInt32(-595914432) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - case .messageEntityPhone(let _data): - if boxed { - buffer.appendInt32(-1687559349) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let _data): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeString(_data.language, buffer: buffer, boxed: false) - break - case .messageEntitySpoiler(let _data): - if boxed { - buffer.appendInt32(852137487) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityStrike(let _data): - if boxed { - buffer.appendInt32(-1090087980) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityTextUrl(let _data): - if boxed { - buffer.appendInt32(1990644519) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - break - case .messageEntityUnderline(let _data): - if boxed { - buffer.appendInt32(-1672577397) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityUnknown(let _data): - if boxed { - buffer.appendInt32(-1148011883) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .messageEntityUrl(let _data): - if boxed { - buffer.appendInt32(1859134776) - } - serializeInt32(_data.offset, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputMessageEntityMentionName(let _data): - return ("inputMessageEntityMentionName", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("userId", ConstructorParameterDescription(_data.userId))]) - case .messageEntityBankCard(let _data): - return ("messageEntityBankCard", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityBlockquote(let _data): - return ("messageEntityBlockquote", [("flags", ConstructorParameterDescription(_data.flags)), ("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityBold(let _data): - return ("messageEntityBold", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityBotCommand(let _data): - return ("messageEntityBotCommand", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityCashtag(let _data): - return ("messageEntityCashtag", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityCode(let _data): - return ("messageEntityCode", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityCustomEmoji(let _data): - return ("messageEntityCustomEmoji", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("documentId", ConstructorParameterDescription(_data.documentId))]) - case .messageEntityDiffDelete(let _data): - return ("messageEntityDiffDelete", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityDiffInsert(let _data): - return ("messageEntityDiffInsert", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityDiffReplace(let _data): - return ("messageEntityDiffReplace", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("oldText", ConstructorParameterDescription(_data.oldText))]) - case .messageEntityEmail(let _data): - return ("messageEntityEmail", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityFormattedDate(let _data): - return ("messageEntityFormattedDate", [("flags", ConstructorParameterDescription(_data.flags)), ("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("date", ConstructorParameterDescription(_data.date))]) - case .messageEntityHashtag(let _data): - return ("messageEntityHashtag", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityItalic(let _data): - return ("messageEntityItalic", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityMention(let _data): - return ("messageEntityMention", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityMentionName(let _data): - return ("messageEntityMentionName", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("userId", ConstructorParameterDescription(_data.userId))]) - case .messageEntityPhone(let _data): - return ("messageEntityPhone", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityPre(let _data): - return ("messageEntityPre", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("language", ConstructorParameterDescription(_data.language))]) - case .messageEntitySpoiler(let _data): - return ("messageEntitySpoiler", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityStrike(let _data): - return ("messageEntityStrike", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityTextUrl(let _data): - return ("messageEntityTextUrl", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("url", ConstructorParameterDescription(_data.url))]) - case .messageEntityUnderline(let _data): - return ("messageEntityUnderline", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityUnknown(let _data): - return ("messageEntityUnknown", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - case .messageEntityUrl(let _data): - return ("messageEntityUrl", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) - } - } - - public static func parse_inputMessageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.InputUser? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputUser - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.inputMessageEntityMentionName(Cons_inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityBankCard(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBankCard(Cons_messageEntityBankCard(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityBlockquote(Cons_messageEntityBlockquote(flags: _1!, offset: _2!, length: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBold(Cons_messageEntityBold(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityBotCommand(Cons_messageEntityBotCommand(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityCashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCashtag(Cons_messageEntityCashtag(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityCode(Cons_messageEntityCode(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityCustomEmoji(Cons_messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityDiffDelete(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityDiffDelete(Cons_messageEntityDiffDelete(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityDiffInsert(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityDiffInsert(Cons_messageEntityDiffInsert(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityDiffReplace(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityDiffReplace(Cons_messageEntityDiffReplace(offset: _1!, length: _2!, oldText: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityEmail(Cons_messageEntityEmail(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityFormattedDate(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageEntity.messageEntityFormattedDate(Cons_messageEntityFormattedDate(flags: _1!, offset: _2!, length: _3!, date: _4!)) - } - else { - return nil - } - } - public static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityHashtag(Cons_messageEntityHashtag(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityItalic(Cons_messageEntityItalic(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityMention(Cons_messageEntityMention(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityMentionName(Cons_messageEntityMentionName(offset: _1!, length: _2!, userId: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityPhone(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityPhone(Cons_messageEntityPhone(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityPre(Cons_messageEntityPre(offset: _1!, length: _2!, language: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntitySpoiler(Cons_messageEntitySpoiler(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityStrike(Cons_messageEntityStrike(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageEntity.messageEntityTextUrl(Cons_messageEntityTextUrl(offset: _1!, length: _2!, url: _3!)) - } - else { - return nil - } - } - public static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnderline(Cons_messageEntityUnderline(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUnknown(Cons_messageEntityUnknown(offset: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageEntity.messageEntityUrl(Cons_messageEntityUrl(offset: _1!, length: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum MessageExtendedMedia: TypeConstructorDescription { - public class Cons_messageExtendedMedia: TypeConstructorDescription { - public var media: Api.MessageMedia - public init(media: Api.MessageMedia) { - self.media = media - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageExtendedMedia", [("media", ConstructorParameterDescription(self.media))]) - } - } - public class Cons_messageExtendedMediaPreview: TypeConstructorDescription { - public var flags: Int32 - public var w: Int32? - public var h: Int32? - public var thumb: Api.PhotoSize? - public var videoDuration: Int32? - public init(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?) { + public var x: Double + public var y: Double + public var w: Double + public var h: Double + public var rotation: Double + public var radius: Double? + public init(flags: Int32, x: Double, y: Double, w: Double, h: Double, rotation: Double, radius: Double?) { self.flags = flags + self.x = x + self.y = y self.w = w self.h = h - self.thumb = thumb - self.videoDuration = videoDuration + self.rotation = rotation + self.radius = radius } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageExtendedMediaPreview", [("flags", ConstructorParameterDescription(self.flags)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("thumb", ConstructorParameterDescription(self.thumb)), ("videoDuration", ConstructorParameterDescription(self.videoDuration))]) + return ("mediaAreaCoordinates", [("flags", ConstructorParameterDescription(self.flags)), ("x", ConstructorParameterDescription(self.x)), ("y", ConstructorParameterDescription(self.y)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("rotation", ConstructorParameterDescription(self.rotation)), ("radius", ConstructorParameterDescription(self.radius))]) } } - case messageExtendedMedia(Cons_messageExtendedMedia) - case messageExtendedMediaPreview(Cons_messageExtendedMediaPreview) + case mediaAreaCoordinates(Cons_mediaAreaCoordinates) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageExtendedMedia(let _data): + case .mediaAreaCoordinates(let _data): if boxed { - buffer.appendInt32(-297296796) - } - _data.media.serialize(buffer, true) - break - case .messageExtendedMediaPreview(let _data): - if boxed { - buffer.appendInt32(-1386050360) + buffer.appendInt32(-808853502) } serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeDouble(_data.x, buffer: buffer, boxed: false) + serializeDouble(_data.y, buffer: buffer, boxed: false) + serializeDouble(_data.w, buffer: buffer, boxed: false) + serializeDouble(_data.h, buffer: buffer, boxed: false) + serializeDouble(_data.rotation, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.w!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.h!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.thumb!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.videoDuration!, buffer: buffer, boxed: false) + serializeDouble(_data.radius!, buffer: buffer, boxed: false) } break } @@ -1006,1274 +44,3521 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .messageExtendedMedia(let _data): - return ("messageExtendedMedia", [("media", ConstructorParameterDescription(_data.media))]) - case .messageExtendedMediaPreview(let _data): - return ("messageExtendedMediaPreview", [("flags", ConstructorParameterDescription(_data.flags)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("thumb", ConstructorParameterDescription(_data.thumb)), ("videoDuration", ConstructorParameterDescription(_data.videoDuration))]) + case .mediaAreaCoordinates(let _data): + return ("mediaAreaCoordinates", [("flags", ConstructorParameterDescription(_data.flags)), ("x", ConstructorParameterDescription(_data.x)), ("y", ConstructorParameterDescription(_data.y)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("rotation", ConstructorParameterDescription(_data.rotation)), ("radius", ConstructorParameterDescription(_data.radius))]) } } - public static func parse_messageExtendedMedia(_ reader: BufferReader) -> MessageExtendedMedia? { - var _1: Api.MessageMedia? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageExtendedMedia.messageExtendedMedia(Cons_messageExtendedMedia(media: _1!)) - } - else { - return nil - } - } - public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? { + public static func parse_mediaAreaCoordinates(_ reader: BufferReader) -> MediaAreaCoordinates? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() + var _2: Double? + _2 = reader.readDouble() + var _3: Double? + _3 = reader.readDouble() + var _4: Double? + _4 = reader.readDouble() + var _5: Double? + _5 = reader.readDouble() + var _6: Double? + _6 = reader.readDouble() + var _7: Double? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = reader.readDouble() } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Api.PhotoSize? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.PhotoSize - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageExtendedMedia.messageExtendedMediaPreview(Cons_messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageFwdHeader: TypeConstructorDescription { - public class Cons_messageFwdHeader: TypeConstructorDescription { - public var flags: Int32 - public var fromId: Api.Peer? - public var fromName: String? - public var date: Int32 - public var channelPost: Int32? - public var postAuthor: String? - public var savedFromPeer: Api.Peer? - public var savedFromMsgId: Int32? - public var savedFromId: Api.Peer? - public var savedFromName: String? - public var savedDate: Int32? - public var psaType: String? - public init(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, savedFromId: Api.Peer?, savedFromName: String?, savedDate: Int32?, psaType: String?) { - self.flags = flags - self.fromId = fromId - self.fromName = fromName - self.date = date - self.channelPost = channelPost - self.postAuthor = postAuthor - self.savedFromPeer = savedFromPeer - self.savedFromMsgId = savedFromMsgId - self.savedFromId = savedFromId - self.savedFromName = savedFromName - self.savedDate = savedDate - self.psaType = psaType - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageFwdHeader", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fromName", ConstructorParameterDescription(self.fromName)), ("date", ConstructorParameterDescription(self.date)), ("channelPost", ConstructorParameterDescription(self.channelPost)), ("postAuthor", ConstructorParameterDescription(self.postAuthor)), ("savedFromPeer", ConstructorParameterDescription(self.savedFromPeer)), ("savedFromMsgId", ConstructorParameterDescription(self.savedFromMsgId)), ("savedFromId", ConstructorParameterDescription(self.savedFromId)), ("savedFromName", ConstructorParameterDescription(self.savedFromName)), ("savedDate", ConstructorParameterDescription(self.savedDate)), ("psaType", ConstructorParameterDescription(self.psaType))]) - } - } - case messageFwdHeader(Cons_messageFwdHeader) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageFwdHeader(let _data): - if boxed { - buffer.appendInt32(1313731771) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.fromName!, buffer: buffer, boxed: false) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.channelPost!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.postAuthor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.savedFromPeer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.savedFromMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.savedFromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - serializeString(_data.savedFromName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.savedDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeString(_data.psaType!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageFwdHeader(let _data): - return ("messageFwdHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fromName", ConstructorParameterDescription(_data.fromName)), ("date", ConstructorParameterDescription(_data.date)), ("channelPost", ConstructorParameterDescription(_data.channelPost)), ("postAuthor", ConstructorParameterDescription(_data.postAuthor)), ("savedFromPeer", ConstructorParameterDescription(_data.savedFromPeer)), ("savedFromMsgId", ConstructorParameterDescription(_data.savedFromMsgId)), ("savedFromId", ConstructorParameterDescription(_data.savedFromId)), ("savedFromName", ConstructorParameterDescription(_data.savedFromName)), ("savedDate", ConstructorParameterDescription(_data.savedDate)), ("psaType", ConstructorParameterDescription(_data.psaType))]) - } - } - - public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: String? - if Int(_1!) & Int(1 << 5) != 0 { - _3 = parseString(reader) - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = reader.readInt32() - } - var _6: String? - if Int(_1!) & Int(1 << 3) != 0 { - _6 = parseString(reader) - } - var _7: Api.Peer? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt32() - } - var _9: Api.Peer? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _10: String? - if Int(_1!) & Int(1 << 9) != 0 { - _10 = parseString(reader) - } - var _11: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _11 = reader.readInt32() - } - var _12: String? - if Int(_1!) & Int(1 << 6) != 0 { - _12 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 10) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.MessageFwdHeader.messageFwdHeader(Cons_messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum MessageMedia: TypeConstructorDescription { - public class Cons_messageMediaContact: TypeConstructorDescription { - public var phoneNumber: String - public var firstName: String - public var lastName: String - public var vcard: String - public var userId: Int64 - public init(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) { - self.phoneNumber = phoneNumber - self.firstName = firstName - self.lastName = lastName - self.vcard = vcard - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaContact", [("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("vcard", ConstructorParameterDescription(self.vcard)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_messageMediaDice: TypeConstructorDescription { - public var flags: Int32 - public var value: Int32 - public var emoticon: String - public var gameOutcome: Api.messages.EmojiGameOutcome? - public init(flags: Int32, value: Int32, emoticon: String, gameOutcome: Api.messages.EmojiGameOutcome?) { - self.flags = flags - self.value = value - self.emoticon = emoticon - self.gameOutcome = gameOutcome - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaDice", [("flags", ConstructorParameterDescription(self.flags)), ("value", ConstructorParameterDescription(self.value)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("gameOutcome", ConstructorParameterDescription(self.gameOutcome))]) - } - } - public class Cons_messageMediaDocument: TypeConstructorDescription { - public var flags: Int32 - public var document: Api.Document? - public var altDocuments: [Api.Document]? - public var videoCover: Api.Photo? - public var videoTimestamp: Int32? - public var ttlSeconds: Int32? - public init(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, videoCover: Api.Photo?, videoTimestamp: Int32?, ttlSeconds: Int32?) { - self.flags = flags - self.document = document - self.altDocuments = altDocuments - self.videoCover = videoCover - self.videoTimestamp = videoTimestamp - self.ttlSeconds = ttlSeconds - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaDocument", [("flags", ConstructorParameterDescription(self.flags)), ("document", ConstructorParameterDescription(self.document)), ("altDocuments", ConstructorParameterDescription(self.altDocuments)), ("videoCover", ConstructorParameterDescription(self.videoCover)), ("videoTimestamp", ConstructorParameterDescription(self.videoTimestamp)), ("ttlSeconds", ConstructorParameterDescription(self.ttlSeconds))]) - } - } - public class Cons_messageMediaGame: TypeConstructorDescription { - public var game: Api.Game - public init(game: Api.Game) { - self.game = game - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaGame", [("game", ConstructorParameterDescription(self.game))]) - } - } - public class Cons_messageMediaGeo: TypeConstructorDescription { - public var geo: Api.GeoPoint - public init(geo: Api.GeoPoint) { - self.geo = geo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaGeo", [("geo", ConstructorParameterDescription(self.geo))]) - } - } - public class Cons_messageMediaGeoLive: TypeConstructorDescription { - public var flags: Int32 - public var geo: Api.GeoPoint - public var heading: Int32? - public var period: Int32 - public var proximityNotificationRadius: Int32? - public init(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?) { - self.flags = flags - self.geo = geo - self.heading = heading - self.period = period - self.proximityNotificationRadius = proximityNotificationRadius - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaGeoLive", [("flags", ConstructorParameterDescription(self.flags)), ("geo", ConstructorParameterDescription(self.geo)), ("heading", ConstructorParameterDescription(self.heading)), ("period", ConstructorParameterDescription(self.period)), ("proximityNotificationRadius", ConstructorParameterDescription(self.proximityNotificationRadius))]) - } - } - public class Cons_messageMediaGiveaway: TypeConstructorDescription { - public var flags: Int32 - public var channels: [Int64] - public var countriesIso2: [String]? - public var prizeDescription: String? - public var quantity: Int32 - public var months: Int32? - public var stars: Int64? - public var untilDate: Int32 - public init(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32?, stars: Int64?, untilDate: Int32) { - self.flags = flags - self.channels = channels - self.countriesIso2 = countriesIso2 - self.prizeDescription = prizeDescription - self.quantity = quantity - self.months = months - self.stars = stars - self.untilDate = untilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("channels", ConstructorParameterDescription(self.channels)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("quantity", ConstructorParameterDescription(self.quantity)), ("months", ConstructorParameterDescription(self.months)), ("stars", ConstructorParameterDescription(self.stars)), ("untilDate", ConstructorParameterDescription(self.untilDate))]) - } - } - public class Cons_messageMediaGiveawayResults: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var additionalPeersCount: Int32? - public var launchMsgId: Int32 - public var winnersCount: Int32 - public var unclaimedCount: Int32 - public var winners: [Int64] - public var months: Int32? - public var stars: Int64? - public var prizeDescription: String? - public var untilDate: Int32 - public init(flags: Int32, channelId: Int64, additionalPeersCount: Int32?, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32?, stars: Int64?, prizeDescription: String?, untilDate: Int32) { - self.flags = flags - self.channelId = channelId - self.additionalPeersCount = additionalPeersCount - self.launchMsgId = launchMsgId - self.winnersCount = winnersCount - self.unclaimedCount = unclaimedCount - self.winners = winners - self.months = months - self.stars = stars - self.prizeDescription = prizeDescription - self.untilDate = untilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaGiveawayResults", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("additionalPeersCount", ConstructorParameterDescription(self.additionalPeersCount)), ("launchMsgId", ConstructorParameterDescription(self.launchMsgId)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(self.unclaimedCount)), ("winners", ConstructorParameterDescription(self.winners)), ("months", ConstructorParameterDescription(self.months)), ("stars", ConstructorParameterDescription(self.stars)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("untilDate", ConstructorParameterDescription(self.untilDate))]) - } - } - public class Cons_messageMediaInvoice: TypeConstructorDescription { - public var flags: Int32 - public var title: String - public var description: String - public var photo: Api.WebDocument? - public var receiptMsgId: Int32? - public var currency: String - public var totalAmount: Int64 - public var startParam: String - public var extendedMedia: Api.MessageExtendedMedia? - public init(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?) { - self.flags = flags - self.title = title - self.description = description - self.photo = photo - self.receiptMsgId = receiptMsgId - self.currency = currency - self.totalAmount = totalAmount - self.startParam = startParam - self.extendedMedia = extendedMedia - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaInvoice", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("receiptMsgId", ConstructorParameterDescription(self.receiptMsgId)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("startParam", ConstructorParameterDescription(self.startParam)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) - } - } - public class Cons_messageMediaPaidMedia: TypeConstructorDescription { - public var starsAmount: Int64 - public var extendedMedia: [Api.MessageExtendedMedia] - public init(starsAmount: Int64, extendedMedia: [Api.MessageExtendedMedia]) { - self.starsAmount = starsAmount - self.extendedMedia = extendedMedia - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaPaidMedia", [("starsAmount", ConstructorParameterDescription(self.starsAmount)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) - } - } - public class Cons_messageMediaPhoto: TypeConstructorDescription { - public var flags: Int32 - public var photo: Api.Photo? - public var ttlSeconds: Int32? - public var video: Api.Document? - public init(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?, video: Api.Document?) { - self.flags = flags - self.photo = photo - self.ttlSeconds = ttlSeconds - self.video = video - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaPhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photo", ConstructorParameterDescription(self.photo)), ("ttlSeconds", ConstructorParameterDescription(self.ttlSeconds)), ("video", ConstructorParameterDescription(self.video))]) - } - } - public class Cons_messageMediaPoll: TypeConstructorDescription { - public var flags: Int32 - public var poll: Api.Poll - public var results: Api.PollResults - public var attachedMedia: Api.MessageMedia? - public init(flags: Int32, poll: Api.Poll, results: Api.PollResults, attachedMedia: Api.MessageMedia?) { - self.flags = flags - self.poll = poll - self.results = results - self.attachedMedia = attachedMedia - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaPoll", [("flags", ConstructorParameterDescription(self.flags)), ("poll", ConstructorParameterDescription(self.poll)), ("results", ConstructorParameterDescription(self.results)), ("attachedMedia", ConstructorParameterDescription(self.attachedMedia))]) - } - } - public class Cons_messageMediaStory: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var id: Int32 - public var story: Api.StoryItem? - public init(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) { - self.flags = flags - self.peer = peer - self.id = id - self.story = story - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaStory", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("id", ConstructorParameterDescription(self.id)), ("story", ConstructorParameterDescription(self.story))]) - } - } - public class Cons_messageMediaToDo: TypeConstructorDescription { - public var flags: Int32 - public var todo: Api.TodoList - public var completions: [Api.TodoCompletion]? - public init(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?) { - self.flags = flags - self.todo = todo - self.completions = completions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaToDo", [("flags", ConstructorParameterDescription(self.flags)), ("todo", ConstructorParameterDescription(self.todo)), ("completions", ConstructorParameterDescription(self.completions))]) - } - } - public class Cons_messageMediaVenue: TypeConstructorDescription { - public var geo: Api.GeoPoint - public var title: String - public var address: String - public var provider: String - public var venueId: String - public var venueType: String - public init(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) { - self.geo = geo - self.title = title - self.address = address - self.provider = provider - self.venueId = venueId - self.venueType = venueType - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaVenue", [("geo", ConstructorParameterDescription(self.geo)), ("title", ConstructorParameterDescription(self.title)), ("address", ConstructorParameterDescription(self.address)), ("provider", ConstructorParameterDescription(self.provider)), ("venueId", ConstructorParameterDescription(self.venueId)), ("venueType", ConstructorParameterDescription(self.venueType))]) - } - } - public class Cons_messageMediaVideoStream: TypeConstructorDescription { - public var flags: Int32 - public var call: Api.InputGroupCall - public init(flags: Int32, call: Api.InputGroupCall) { - self.flags = flags - self.call = call - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaVideoStream", [("flags", ConstructorParameterDescription(self.flags)), ("call", ConstructorParameterDescription(self.call))]) - } - } - public class Cons_messageMediaWebPage: TypeConstructorDescription { - public var flags: Int32 - public var webpage: Api.WebPage - public init(flags: Int32, webpage: Api.WebPage) { - self.flags = flags - self.webpage = webpage - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageMediaWebPage", [("flags", ConstructorParameterDescription(self.flags)), ("webpage", ConstructorParameterDescription(self.webpage))]) - } - } - case messageMediaContact(Cons_messageMediaContact) - case messageMediaDice(Cons_messageMediaDice) - case messageMediaDocument(Cons_messageMediaDocument) - case messageMediaEmpty - case messageMediaGame(Cons_messageMediaGame) - case messageMediaGeo(Cons_messageMediaGeo) - case messageMediaGeoLive(Cons_messageMediaGeoLive) - case messageMediaGiveaway(Cons_messageMediaGiveaway) - case messageMediaGiveawayResults(Cons_messageMediaGiveawayResults) - case messageMediaInvoice(Cons_messageMediaInvoice) - case messageMediaPaidMedia(Cons_messageMediaPaidMedia) - case messageMediaPhoto(Cons_messageMediaPhoto) - case messageMediaPoll(Cons_messageMediaPoll) - case messageMediaStory(Cons_messageMediaStory) - case messageMediaToDo(Cons_messageMediaToDo) - case messageMediaUnsupported - case messageMediaVenue(Cons_messageMediaVenue) - case messageMediaVideoStream(Cons_messageMediaVideoStream) - case messageMediaWebPage(Cons_messageMediaWebPage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageMediaContact(let _data): - if boxed { - buffer.appendInt32(1882335561) - } - serializeString(_data.phoneNumber, buffer: buffer, boxed: false) - serializeString(_data.firstName, buffer: buffer, boxed: false) - serializeString(_data.lastName, buffer: buffer, boxed: false) - serializeString(_data.vcard, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - case .messageMediaDice(let _data): - if boxed { - buffer.appendInt32(147581959) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.value, buffer: buffer, boxed: false) - serializeString(_data.emoticon, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.gameOutcome!.serialize(buffer, true) - } - break - case .messageMediaDocument(let _data): - if boxed { - buffer.appendInt32(1389939929) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.document!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.altDocuments!.count)) - for item in _data.altDocuments! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 9) != 0 { - _data.videoCover!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.videoTimestamp!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.ttlSeconds!, buffer: buffer, boxed: false) - } - break - case .messageMediaEmpty: - if boxed { - buffer.appendInt32(1038967584) - } - break - case .messageMediaGame(let _data): - if boxed { - buffer.appendInt32(-38694904) - } - _data.game.serialize(buffer, true) - break - case .messageMediaGeo(let _data): - if boxed { - buffer.appendInt32(1457575028) - } - _data.geo.serialize(buffer, true) - break - case .messageMediaGeoLive(let _data): - if boxed { - buffer.appendInt32(-1186937242) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.geo.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.heading!, buffer: buffer, boxed: false) - } - serializeInt32(_data.period, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.proximityNotificationRadius!, buffer: buffer, boxed: false) - } - break - case .messageMediaGiveaway(let _data): - if boxed { - buffer.appendInt32(-1442366485) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.channels.count)) - for item in _data.channels { - serializeInt64(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.countriesIso2!.count)) - for item in _data.countriesIso2! { - serializeString(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) - } - serializeInt32(_data.quantity, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.months!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt64(_data.stars!, buffer: buffer, boxed: false) - } - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - break - case .messageMediaGiveawayResults(let _data): - if boxed { - buffer.appendInt32(-827703647) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.additionalPeersCount!, buffer: buffer, boxed: false) - } - serializeInt32(_data.launchMsgId, buffer: buffer, boxed: false) - serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) - serializeInt32(_data.unclaimedCount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.winners.count)) - for item in _data.winners { - serializeInt64(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.months!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt64(_data.stars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) - } - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - break - case .messageMediaInvoice(let _data): - if boxed { - buffer.appendInt32(-156940077) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.receiptMsgId!, buffer: buffer, boxed: false) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - serializeString(_data.startParam, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.extendedMedia!.serialize(buffer, true) - } - break - case .messageMediaPaidMedia(let _data): - if boxed { - buffer.appendInt32(-1467669359) - } - serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.extendedMedia.count)) - for item in _data.extendedMedia { - item.serialize(buffer, true) - } - break - case .messageMediaPhoto(let _data): - if boxed { - buffer.appendInt32(-501814429) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.ttlSeconds!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.video!.serialize(buffer, true) - } - break - case .messageMediaPoll(let _data): - if boxed { - buffer.appendInt32(2000637542) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.poll.serialize(buffer, true) - _data.results.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.attachedMedia!.serialize(buffer, true) - } - break - case .messageMediaStory(let _data): - if boxed { - buffer.appendInt32(1758159491) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.story!.serialize(buffer, true) - } - break - case .messageMediaToDo(let _data): - if boxed { - buffer.appendInt32(-1974226924) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.todo.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.completions!.count)) - for item in _data.completions! { - item.serialize(buffer, true) - } - } - break - case .messageMediaUnsupported: - if boxed { - buffer.appendInt32(-1618676578) - } - break - case .messageMediaVenue(let _data): - if boxed { - buffer.appendInt32(784356159) - } - _data.geo.serialize(buffer, true) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.address, buffer: buffer, boxed: false) - serializeString(_data.provider, buffer: buffer, boxed: false) - serializeString(_data.venueId, buffer: buffer, boxed: false) - serializeString(_data.venueType, buffer: buffer, boxed: false) - break - case .messageMediaVideoStream(let _data): - if boxed { - buffer.appendInt32(-899896439) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.call.serialize(buffer, true) - break - case .messageMediaWebPage(let _data): - if boxed { - buffer.appendInt32(-571405253) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.webpage.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageMediaContact(let _data): - return ("messageMediaContact", [("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("vcard", ConstructorParameterDescription(_data.vcard)), ("userId", ConstructorParameterDescription(_data.userId))]) - case .messageMediaDice(let _data): - return ("messageMediaDice", [("flags", ConstructorParameterDescription(_data.flags)), ("value", ConstructorParameterDescription(_data.value)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("gameOutcome", ConstructorParameterDescription(_data.gameOutcome))]) - case .messageMediaDocument(let _data): - return ("messageMediaDocument", [("flags", ConstructorParameterDescription(_data.flags)), ("document", ConstructorParameterDescription(_data.document)), ("altDocuments", ConstructorParameterDescription(_data.altDocuments)), ("videoCover", ConstructorParameterDescription(_data.videoCover)), ("videoTimestamp", ConstructorParameterDescription(_data.videoTimestamp)), ("ttlSeconds", ConstructorParameterDescription(_data.ttlSeconds))]) - case .messageMediaEmpty: - return ("messageMediaEmpty", []) - case .messageMediaGame(let _data): - return ("messageMediaGame", [("game", ConstructorParameterDescription(_data.game))]) - case .messageMediaGeo(let _data): - return ("messageMediaGeo", [("geo", ConstructorParameterDescription(_data.geo))]) - case .messageMediaGeoLive(let _data): - return ("messageMediaGeoLive", [("flags", ConstructorParameterDescription(_data.flags)), ("geo", ConstructorParameterDescription(_data.geo)), ("heading", ConstructorParameterDescription(_data.heading)), ("period", ConstructorParameterDescription(_data.period)), ("proximityNotificationRadius", ConstructorParameterDescription(_data.proximityNotificationRadius))]) - case .messageMediaGiveaway(let _data): - return ("messageMediaGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("channels", ConstructorParameterDescription(_data.channels)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("months", ConstructorParameterDescription(_data.months)), ("stars", ConstructorParameterDescription(_data.stars)), ("untilDate", ConstructorParameterDescription(_data.untilDate))]) - case .messageMediaGiveawayResults(let _data): - return ("messageMediaGiveawayResults", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("additionalPeersCount", ConstructorParameterDescription(_data.additionalPeersCount)), ("launchMsgId", ConstructorParameterDescription(_data.launchMsgId)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(_data.unclaimedCount)), ("winners", ConstructorParameterDescription(_data.winners)), ("months", ConstructorParameterDescription(_data.months)), ("stars", ConstructorParameterDescription(_data.stars)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("untilDate", ConstructorParameterDescription(_data.untilDate))]) - case .messageMediaInvoice(let _data): - return ("messageMediaInvoice", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("receiptMsgId", ConstructorParameterDescription(_data.receiptMsgId)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("startParam", ConstructorParameterDescription(_data.startParam)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) - case .messageMediaPaidMedia(let _data): - return ("messageMediaPaidMedia", [("starsAmount", ConstructorParameterDescription(_data.starsAmount)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) - case .messageMediaPhoto(let _data): - return ("messageMediaPhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photo", ConstructorParameterDescription(_data.photo)), ("ttlSeconds", ConstructorParameterDescription(_data.ttlSeconds)), ("video", ConstructorParameterDescription(_data.video))]) - case .messageMediaPoll(let _data): - return ("messageMediaPoll", [("flags", ConstructorParameterDescription(_data.flags)), ("poll", ConstructorParameterDescription(_data.poll)), ("results", ConstructorParameterDescription(_data.results)), ("attachedMedia", ConstructorParameterDescription(_data.attachedMedia))]) - case .messageMediaStory(let _data): - return ("messageMediaStory", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("id", ConstructorParameterDescription(_data.id)), ("story", ConstructorParameterDescription(_data.story))]) - case .messageMediaToDo(let _data): - return ("messageMediaToDo", [("flags", ConstructorParameterDescription(_data.flags)), ("todo", ConstructorParameterDescription(_data.todo)), ("completions", ConstructorParameterDescription(_data.completions))]) - case .messageMediaUnsupported: - return ("messageMediaUnsupported", []) - case .messageMediaVenue(let _data): - return ("messageMediaVenue", [("geo", ConstructorParameterDescription(_data.geo)), ("title", ConstructorParameterDescription(_data.title)), ("address", ConstructorParameterDescription(_data.address)), ("provider", ConstructorParameterDescription(_data.provider)), ("venueId", ConstructorParameterDescription(_data.venueId)), ("venueType", ConstructorParameterDescription(_data.venueType))]) - case .messageMediaVideoStream(let _data): - return ("messageMediaVideoStream", [("flags", ConstructorParameterDescription(_data.flags)), ("call", ConstructorParameterDescription(_data.call))]) - case .messageMediaWebPage(let _data): - return ("messageMediaWebPage", [("flags", ConstructorParameterDescription(_data.flags)), ("webpage", ConstructorParameterDescription(_data.webpage))]) - } - } - - public static func parse_messageMediaContact(_ reader: BufferReader) -> MessageMedia? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaContact(Cons_messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!)) + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MediaAreaCoordinates.mediaAreaCoordinates(Cons_mediaAreaCoordinates(flags: _1!, x: _2!, y: _3!, w: _4!, h: _5!, rotation: _6!, radius: _7)) } else { return nil } } - public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? { + } +} +public extension Api { + indirect enum Message: TypeConstructorDescription { + public class Cons_message: TypeConstructorDescription { + public var flags: Int32 + public var flags2: Int32 + public var id: Int32 + public var fromId: Api.Peer? + public var fromBoostsApplied: Int32? + public var fromRank: String? + public var peerId: Api.Peer + public var savedPeerId: Api.Peer? + public var fwdFrom: Api.MessageFwdHeader? + public var viaBotId: Int64? + public var viaBusinessBotId: Int64? + public var guestchatViaFrom: Api.Peer? + public var replyTo: Api.MessageReplyHeader? + public var date: Int32 + public var message: String + public var media: Api.MessageMedia? + public var replyMarkup: Api.ReplyMarkup? + public var entities: [Api.MessageEntity]? + public var views: Int32? + public var forwards: Int32? + public var replies: Api.MessageReplies? + public var editDate: Int32? + public var postAuthor: String? + public var groupedId: Int64? + public var reactions: Api.MessageReactions? + public var restrictionReason: [Api.RestrictionReason]? + public var ttlPeriod: Int32? + public var quickReplyShortcutId: Int32? + public var effect: Int64? + public var factcheck: Api.FactCheck? + public var reportDeliveryUntilDate: Int32? + public var paidMessageStars: Int64? + public var suggestedPost: Api.SuggestedPost? + public var scheduleRepeatPeriod: Int32? + public var summaryFromLanguage: String? + public var richMessage: Api.RichMessage? + public init(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, fromRank: String?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, guestchatViaFrom: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?, paidMessageStars: Int64?, suggestedPost: Api.SuggestedPost?, scheduleRepeatPeriod: Int32?, summaryFromLanguage: String?, richMessage: Api.RichMessage?) { + self.flags = flags + self.flags2 = flags2 + self.id = id + self.fromId = fromId + self.fromBoostsApplied = fromBoostsApplied + self.fromRank = fromRank + self.peerId = peerId + self.savedPeerId = savedPeerId + self.fwdFrom = fwdFrom + self.viaBotId = viaBotId + self.viaBusinessBotId = viaBusinessBotId + self.guestchatViaFrom = guestchatViaFrom + self.replyTo = replyTo + self.date = date + self.message = message + self.media = media + self.replyMarkup = replyMarkup + self.entities = entities + self.views = views + self.forwards = forwards + self.replies = replies + self.editDate = editDate + self.postAuthor = postAuthor + self.groupedId = groupedId + self.reactions = reactions + self.restrictionReason = restrictionReason + self.ttlPeriod = ttlPeriod + self.quickReplyShortcutId = quickReplyShortcutId + self.effect = effect + self.factcheck = factcheck + self.reportDeliveryUntilDate = reportDeliveryUntilDate + self.paidMessageStars = paidMessageStars + self.suggestedPost = suggestedPost + self.scheduleRepeatPeriod = scheduleRepeatPeriod + self.summaryFromLanguage = summaryFromLanguage + self.richMessage = richMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("message", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fromBoostsApplied", ConstructorParameterDescription(self.fromBoostsApplied)), ("fromRank", ConstructorParameterDescription(self.fromRank)), ("peerId", ConstructorParameterDescription(self.peerId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("viaBusinessBotId", ConstructorParameterDescription(self.viaBusinessBotId)), ("guestchatViaFrom", ConstructorParameterDescription(self.guestchatViaFrom)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("date", ConstructorParameterDescription(self.date)), ("message", ConstructorParameterDescription(self.message)), ("media", ConstructorParameterDescription(self.media)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup)), ("entities", ConstructorParameterDescription(self.entities)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("replies", ConstructorParameterDescription(self.replies)), ("editDate", ConstructorParameterDescription(self.editDate)), ("postAuthor", ConstructorParameterDescription(self.postAuthor)), ("groupedId", ConstructorParameterDescription(self.groupedId)), ("reactions", ConstructorParameterDescription(self.reactions)), ("restrictionReason", ConstructorParameterDescription(self.restrictionReason)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(self.quickReplyShortcutId)), ("effect", ConstructorParameterDescription(self.effect)), ("factcheck", ConstructorParameterDescription(self.factcheck)), ("reportDeliveryUntilDate", ConstructorParameterDescription(self.reportDeliveryUntilDate)), ("paidMessageStars", ConstructorParameterDescription(self.paidMessageStars)), ("suggestedPost", ConstructorParameterDescription(self.suggestedPost)), ("scheduleRepeatPeriod", ConstructorParameterDescription(self.scheduleRepeatPeriod)), ("summaryFromLanguage", ConstructorParameterDescription(self.summaryFromLanguage)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) + } + } + public class Cons_messageEmpty: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var peerId: Api.Peer? + public init(flags: Int32, id: Int32, peerId: Api.Peer?) { + self.flags = flags + self.id = id + self.peerId = peerId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("peerId", ConstructorParameterDescription(self.peerId))]) + } + } + public class Cons_messageService: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var fromId: Api.Peer? + public var peerId: Api.Peer + public var savedPeerId: Api.Peer? + public var replyTo: Api.MessageReplyHeader? + public var date: Int32 + public var action: Api.MessageAction + public var reactions: Api.MessageReactions? + public var ttlPeriod: Int32? + public init(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, savedPeerId: Api.Peer?, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) { + self.flags = flags + self.id = id + self.fromId = fromId + self.peerId = peerId + self.savedPeerId = savedPeerId + self.replyTo = replyTo + self.date = date + self.action = action + self.reactions = reactions + self.ttlPeriod = ttlPeriod + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageService", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peerId", ConstructorParameterDescription(self.peerId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("date", ConstructorParameterDescription(self.date)), ("action", ConstructorParameterDescription(self.action)), ("reactions", ConstructorParameterDescription(self.reactions)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) + } + } + case message(Cons_message) + case messageEmpty(Cons_messageEmpty) + case messageService(Cons_messageService) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .message(let _data): + if boxed { + buffer.appendInt32(1979759059) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.flags2, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 29) != 0 { + serializeInt32(_data.fromBoostsApplied!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 12) != 0 { + serializeString(_data.fromRank!, buffer: buffer, boxed: false) + } + _data.peerId.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 28) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.fwdFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 0) != 0 { + serializeInt64(_data.viaBusinessBotId!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 19) != 0 { + _data.guestchatViaFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.replyTo!.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.media!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + _data.replyMarkup!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.views!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.forwards!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 23) != 0 { + _data.replies!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.editDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeString(_data.postAuthor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + serializeInt64(_data.groupedId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 20) != 0 { + _data.reactions!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 22) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.restrictionReason!.count)) + for item in _data.restrictionReason! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 25) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 30) != 0 { + serializeInt32(_data.quickReplyShortcutId!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 2) != 0 { + serializeInt64(_data.effect!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 3) != 0 { + _data.factcheck!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 5) != 0 { + serializeInt32(_data.reportDeliveryUntilDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 6) != 0 { + serializeInt64(_data.paidMessageStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 7) != 0 { + _data.suggestedPost!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 10) != 0 { + serializeInt32(_data.scheduleRepeatPeriod!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 11) != 0 { + serializeString(_data.summaryFromLanguage!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 13) != 0 { + _data.richMessage!.serialize(buffer, true) + } + break + case .messageEmpty(let _data): + if boxed { + buffer.appendInt32(-1868117372) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.peerId!.serialize(buffer, true) + } + break + case .messageService(let _data): + if boxed { + buffer.appendInt32(2055212554) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.fromId!.serialize(buffer, true) + } + _data.peerId.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 28) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.replyTo!.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.action.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 20) != 0 { + _data.reactions!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 25) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .message(let _data): + return ("message", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fromBoostsApplied", ConstructorParameterDescription(_data.fromBoostsApplied)), ("fromRank", ConstructorParameterDescription(_data.fromRank)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("viaBusinessBotId", ConstructorParameterDescription(_data.viaBusinessBotId)), ("guestchatViaFrom", ConstructorParameterDescription(_data.guestchatViaFrom)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("date", ConstructorParameterDescription(_data.date)), ("message", ConstructorParameterDescription(_data.message)), ("media", ConstructorParameterDescription(_data.media)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup)), ("entities", ConstructorParameterDescription(_data.entities)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("replies", ConstructorParameterDescription(_data.replies)), ("editDate", ConstructorParameterDescription(_data.editDate)), ("postAuthor", ConstructorParameterDescription(_data.postAuthor)), ("groupedId", ConstructorParameterDescription(_data.groupedId)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("restrictionReason", ConstructorParameterDescription(_data.restrictionReason)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(_data.quickReplyShortcutId)), ("effect", ConstructorParameterDescription(_data.effect)), ("factcheck", ConstructorParameterDescription(_data.factcheck)), ("reportDeliveryUntilDate", ConstructorParameterDescription(_data.reportDeliveryUntilDate)), ("paidMessageStars", ConstructorParameterDescription(_data.paidMessageStars)), ("suggestedPost", ConstructorParameterDescription(_data.suggestedPost)), ("scheduleRepeatPeriod", ConstructorParameterDescription(_data.scheduleRepeatPeriod)), ("summaryFromLanguage", ConstructorParameterDescription(_data.summaryFromLanguage)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) + case .messageEmpty(let _data): + return ("messageEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("peerId", ConstructorParameterDescription(_data.peerId))]) + case .messageService(let _data): + return ("messageService", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("date", ConstructorParameterDescription(_data.date)), ("action", ConstructorParameterDescription(_data.action)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) + } + } + + public static func parse_message(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: Api.messages.EmojiGameOutcome? - if Int(_1!) & Int(1 << 0) != 0 { + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameOutcome + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 29) != 0 { + _5 = reader.readInt32() + } + var _6: String? + if Int(_2 ?? 0) & Int(1 << 12) != 0 { + _6 = parseString(reader) + } + var _7: Api.Peer? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _8: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 28) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _9: Api.MessageFwdHeader? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } + } + var _10: Int64? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _10 = reader.readInt64() + } + var _11: Int64? + if Int(_2 ?? 0) & Int(1 << 0) != 0 { + _11 = reader.readInt64() + } + var _12: Api.Peer? + if Int(_2 ?? 0) & Int(1 << 19) != 0 { + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _13: Api.MessageReplyHeader? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } + } + var _14: Int32? + _14 = reader.readInt32() + var _15: String? + _15 = parseString(reader) + var _16: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + var _17: Api.ReplyMarkup? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } + } + var _18: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _19: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _19 = reader.readInt32() + } + var _20: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _20 = reader.readInt32() + } + var _21: Api.MessageReplies? + if Int(_1 ?? 0) & Int(1 << 23) != 0 { + if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } + } + var _22: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _22 = reader.readInt32() + } + var _23: String? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _23 = parseString(reader) + } + var _24: Int64? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _24 = reader.readInt64() + } + var _25: Api.MessageReactions? + if Int(_1 ?? 0) & Int(1 << 20) != 0 { + if let signature = reader.readInt32() { + _25 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } + } + var _26: [Api.RestrictionReason]? + if Int(_1 ?? 0) & Int(1 << 22) != 0 { + if let _ = reader.readInt32() { + _26 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + } + } + var _27: Int32? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + _27 = reader.readInt32() + } + var _28: Int32? + if Int(_1 ?? 0) & Int(1 << 30) != 0 { + _28 = reader.readInt32() + } + var _29: Int64? + if Int(_2 ?? 0) & Int(1 << 2) != 0 { + _29 = reader.readInt64() + } + var _30: Api.FactCheck? + if Int(_2 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _30 = Api.parse(reader, signature: signature) as? Api.FactCheck + } + } + var _31: Int32? + if Int(_2 ?? 0) & Int(1 << 5) != 0 { + _31 = reader.readInt32() + } + var _32: Int64? + if Int(_2 ?? 0) & Int(1 << 6) != 0 { + _32 = reader.readInt64() + } + var _33: Api.SuggestedPost? + if Int(_2 ?? 0) & Int(1 << 7) != 0 { + if let signature = reader.readInt32() { + _33 = Api.parse(reader, signature: signature) as? Api.SuggestedPost + } + } + var _34: Int32? + if Int(_2 ?? 0) & Int(1 << 10) != 0 { + _34 = reader.readInt32() + } + var _35: String? + if Int(_2 ?? 0) & Int(1 << 11) != 0 { + _35 = parseString(reader) + } + var _36: Api.RichMessage? + if Int(_2 ?? 0) & Int(1 << 13) != 0 { + if let signature = reader.readInt32() { + _36 = Api.parse(reader, signature: signature) as? Api.RichMessage } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaDice(Cons_messageMediaDice(flags: _1!, value: _2!, emoticon: _3!, gameOutcome: _4)) + let _c4 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 29) == 0) || _5 != nil + let _c6 = (Int(_2 ?? 0) & Int(1 << 12) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 28) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_2 ?? 0) & Int(1 << 0) == 0) || _11 != nil + let _c12 = (Int(_2 ?? 0) & Int(1 << 19) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _20 != nil + let _c21 = (Int(_1 ?? 0) & Int(1 << 23) == 0) || _21 != nil + let _c22 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _22 != nil + let _c23 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _23 != nil + let _c24 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _24 != nil + let _c25 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _25 != nil + let _c26 = (Int(_1 ?? 0) & Int(1 << 22) == 0) || _26 != nil + let _c27 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _27 != nil + let _c28 = (Int(_1 ?? 0) & Int(1 << 30) == 0) || _28 != nil + let _c29 = (Int(_2 ?? 0) & Int(1 << 2) == 0) || _29 != nil + let _c30 = (Int(_2 ?? 0) & Int(1 << 3) == 0) || _30 != nil + let _c31 = (Int(_2 ?? 0) & Int(1 << 5) == 0) || _31 != nil + let _c32 = (Int(_2 ?? 0) & Int(1 << 6) == 0) || _32 != nil + let _c33 = (Int(_2 ?? 0) & Int(1 << 7) == 0) || _33 != nil + let _c34 = (Int(_2 ?? 0) & Int(1 << 10) == 0) || _34 != nil + let _c35 = (Int(_2 ?? 0) & Int(1 << 11) == 0) || _35 != nil + let _c36 = (Int(_2 ?? 0) & Int(1 << 13) == 0) || _36 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 { + return Api.Message.message(Cons_message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, fromRank: _6, peerId: _7!, savedPeerId: _8, fwdFrom: _9, viaBotId: _10, viaBusinessBotId: _11, guestchatViaFrom: _12, replyTo: _13, date: _14!, message: _15!, media: _16, replyMarkup: _17, entities: _18, views: _19, forwards: _20, replies: _21, editDate: _22, postAuthor: _23, groupedId: _24, reactions: _25, restrictionReason: _26, ttlPeriod: _27, quickReplyShortcutId: _28, effect: _29, factcheck: _30, reportDeliveryUntilDate: _31, paidMessageStars: _32, suggestedPost: _33, scheduleRepeatPeriod: _34, summaryFromLanguage: _35, richMessage: _36)) } else { return nil } } - public static func parse_messageMediaDocument(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageEmpty(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 { + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document + _3 = Api.parse(reader, signature: signature) as? Api.Peer } } - var _3: [Api.Document]? - if Int(_1!) & Int(1 << 5) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - } - var _4: Api.Photo? - if Int(_1!) & Int(1 << 9) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 10) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageMedia.messageMediaDocument(Cons_messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, videoCover: _4, videoTimestamp: _5, ttlSeconds: _6)) - } - else { - return nil - } - } - public static func parse_messageMediaEmpty(_ reader: BufferReader) -> MessageMedia? { - return Api.MessageMedia.messageMediaEmpty - } - public static func parse_messageMediaGame(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.Game? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Game - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGame(Cons_messageMediaGame(game: _1!)) - } - else { - return nil - } - } - public static func parse_messageMediaGeo(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.GeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - let _c1 = _1 != nil - if _c1 { - return Api.MessageMedia.messageMediaGeo(Cons_messageMediaGeo(geo: _1!)) - } - else { - return nil - } - } - public static func parse_messageMediaGeoLive(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.GeoPoint? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() - } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageMedia.messageMediaGeoLive(Cons_messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Message.messageEmpty(Cons_messageEmpty(flags: _1!, id: _2!, peerId: _3)) } else { return nil } } - public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageService(_ reader: BufferReader) -> Message? { var _1: Int32? _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 28) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _6: Api.MessageReplyHeader? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } + } + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.MessageAction? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageAction + } + var _9: Api.MessageReactions? + if Int(_1 ?? 0) & Int(1 << 20) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageReactions + } + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + _10 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 28) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.Message.messageService(Cons_messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, savedPeerId: _5, replyTo: _6, date: _7!, action: _8!, reactions: _9, ttlPeriod: _10)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum MessageAction: TypeConstructorDescription { + public class Cons_messageActionBoostApply: TypeConstructorDescription { + public var boosts: Int32 + public init(boosts: Int32) { + self.boosts = boosts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionBoostApply", [("boosts", ConstructorParameterDescription(self.boosts))]) + } + } + public class Cons_messageActionBotAllowed: TypeConstructorDescription { + public var flags: Int32 + public var domain: String? + public var app: Api.BotApp? + public init(flags: Int32, domain: String?, app: Api.BotApp?) { + self.flags = flags + self.domain = domain + self.app = app + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionBotAllowed", [("flags", ConstructorParameterDescription(self.flags)), ("domain", ConstructorParameterDescription(self.domain)), ("app", ConstructorParameterDescription(self.app))]) + } + } + public class Cons_messageActionChangeCreator: TypeConstructorDescription { + public var newCreatorId: Int64 + public init(newCreatorId: Int64) { + self.newCreatorId = newCreatorId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChangeCreator", [("newCreatorId", ConstructorParameterDescription(self.newCreatorId))]) + } + } + public class Cons_messageActionChannelCreate: TypeConstructorDescription { + public var title: String + public init(title: String) { + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChannelCreate", [("title", ConstructorParameterDescription(self.title))]) + } + } + public class Cons_messageActionChannelMigrateFrom: TypeConstructorDescription { + public var title: String + public var chatId: Int64 + public init(title: String, chatId: Int64) { + self.title = title + self.chatId = chatId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChannelMigrateFrom", [("title", ConstructorParameterDescription(self.title)), ("chatId", ConstructorParameterDescription(self.chatId))]) + } + } + public class Cons_messageActionChatAddUser: TypeConstructorDescription { + public var users: [Int64] + public init(users: [Int64]) { + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatAddUser", [("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_messageActionChatCreate: TypeConstructorDescription { + public var title: String + public var users: [Int64] + public init(title: String, users: [Int64]) { + self.title = title + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatCreate", [("title", ConstructorParameterDescription(self.title)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_messageActionChatDeleteUser: TypeConstructorDescription { + public var userId: Int64 + public init(userId: Int64) { + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatDeleteUser", [("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_messageActionChatEditPhoto: TypeConstructorDescription { + public var photo: Api.Photo + public init(photo: Api.Photo) { + self.photo = photo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatEditPhoto", [("photo", ConstructorParameterDescription(self.photo))]) + } + } + public class Cons_messageActionChatEditTitle: TypeConstructorDescription { + public var title: String + public init(title: String) { + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatEditTitle", [("title", ConstructorParameterDescription(self.title))]) + } + } + public class Cons_messageActionChatJoinedByLink: TypeConstructorDescription { + public var inviterId: Int64 + public init(inviterId: Int64) { + self.inviterId = inviterId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatJoinedByLink", [("inviterId", ConstructorParameterDescription(self.inviterId))]) + } + } + public class Cons_messageActionChatMigrateTo: TypeConstructorDescription { + public var channelId: Int64 + public init(channelId: Int64) { + self.channelId = channelId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionChatMigrateTo", [("channelId", ConstructorParameterDescription(self.channelId))]) + } + } + public class Cons_messageActionConferenceCall: TypeConstructorDescription { + public var flags: Int32 + public var callId: Int64 + public var duration: Int32? + public var otherParticipants: [Api.Peer]? + public init(flags: Int32, callId: Int64, duration: Int32?, otherParticipants: [Api.Peer]?) { + self.flags = flags + self.callId = callId + self.duration = duration + self.otherParticipants = otherParticipants + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionConferenceCall", [("flags", ConstructorParameterDescription(self.flags)), ("callId", ConstructorParameterDescription(self.callId)), ("duration", ConstructorParameterDescription(self.duration)), ("otherParticipants", ConstructorParameterDescription(self.otherParticipants))]) + } + } + public class Cons_messageActionCustomAction: TypeConstructorDescription { + public var message: String + public init(message: String) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionCustomAction", [("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_messageActionGameScore: TypeConstructorDescription { + public var gameId: Int64 + public var score: Int32 + public init(gameId: Int64, score: Int32) { + self.gameId = gameId + self.score = score + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGameScore", [("gameId", ConstructorParameterDescription(self.gameId)), ("score", ConstructorParameterDescription(self.score))]) + } + } + public class Cons_messageActionGeoProximityReached: TypeConstructorDescription { + public var fromId: Api.Peer + public var toId: Api.Peer + public var distance: Int32 + public init(fromId: Api.Peer, toId: Api.Peer, distance: Int32) { + self.fromId = fromId + self.toId = toId + self.distance = distance + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGeoProximityReached", [("fromId", ConstructorParameterDescription(self.fromId)), ("toId", ConstructorParameterDescription(self.toId)), ("distance", ConstructorParameterDescription(self.distance))]) + } + } + public class Cons_messageActionGiftCode: TypeConstructorDescription { + public var flags: Int32 + public var boostPeer: Api.Peer? + public var days: Int32 + public var slug: String + public var currency: String? + public var amount: Int64? + public var cryptoCurrency: String? + public var cryptoAmount: Int64? + public var message: Api.TextWithEntities? + public init(flags: Int32, boostPeer: Api.Peer?, days: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) { + self.flags = flags + self.boostPeer = boostPeer + self.days = days + self.slug = slug + self.currency = currency + self.amount = amount + self.cryptoCurrency = cryptoCurrency + self.cryptoAmount = cryptoAmount + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("days", ConstructorParameterDescription(self.days)), ("slug", ConstructorParameterDescription(self.slug)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_messageActionGiftPremium: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var amount: Int64 + public var days: Int32 + public var cryptoCurrency: String? + public var cryptoAmount: Int64? + public var message: Api.TextWithEntities? + public init(flags: Int32, currency: String, amount: Int64, days: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?) { + self.flags = flags + self.currency = currency + self.amount = amount + self.days = days + self.cryptoCurrency = cryptoCurrency + self.cryptoAmount = cryptoAmount + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiftPremium", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("days", ConstructorParameterDescription(self.days)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_messageActionGiftStars: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var amount: Int64 + public var stars: Int64 + public var cryptoCurrency: String? + public var cryptoAmount: Int64? + public var transactionId: String? + public init(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) { + self.flags = flags + self.currency = currency + self.amount = amount + self.stars = stars + self.cryptoCurrency = cryptoCurrency + self.cryptoAmount = cryptoAmount + self.transactionId = transactionId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiftStars", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("stars", ConstructorParameterDescription(self.stars)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId))]) + } + } + public class Cons_messageActionGiftTon: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var amount: Int64 + public var cryptoCurrency: String + public var cryptoAmount: Int64 + public var transactionId: String? + public init(flags: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, transactionId: String?) { + self.flags = flags + self.currency = currency + self.amount = amount + self.cryptoCurrency = cryptoCurrency + self.cryptoAmount = cryptoAmount + self.transactionId = transactionId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiftTon", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId))]) + } + } + public class Cons_messageActionGiveawayLaunch: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64? + public init(flags: Int32, stars: Int64?) { + self.flags = flags + self.stars = stars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiveawayLaunch", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars))]) + } + } + public class Cons_messageActionGiveawayResults: TypeConstructorDescription { + public var flags: Int32 + public var winnersCount: Int32 + public var unclaimedCount: Int32 + public init(flags: Int32, winnersCount: Int32, unclaimedCount: Int32) { + self.flags = flags + self.winnersCount = winnersCount + self.unclaimedCount = unclaimedCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGiveawayResults", [("flags", ConstructorParameterDescription(self.flags)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(self.unclaimedCount))]) + } + } + public class Cons_messageActionGroupCall: TypeConstructorDescription { + public var flags: Int32 + public var call: Api.InputGroupCall + public var duration: Int32? + public init(flags: Int32, call: Api.InputGroupCall, duration: Int32?) { + self.flags = flags + self.call = call + self.duration = duration + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGroupCall", [("flags", ConstructorParameterDescription(self.flags)), ("call", ConstructorParameterDescription(self.call)), ("duration", ConstructorParameterDescription(self.duration))]) + } + } + public class Cons_messageActionGroupCallScheduled: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var scheduleDate: Int32 + public init(call: Api.InputGroupCall, scheduleDate: Int32) { + self.call = call + self.scheduleDate = scheduleDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionGroupCallScheduled", [("call", ConstructorParameterDescription(self.call)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate))]) + } + } + public class Cons_messageActionInviteToGroupCall: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var users: [Int64] + public init(call: Api.InputGroupCall, users: [Int64]) { + self.call = call + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionInviteToGroupCall", [("call", ConstructorParameterDescription(self.call)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_messageActionManagedBotCreated: TypeConstructorDescription { + public var botId: Int64 + public init(botId: Int64) { + self.botId = botId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionManagedBotCreated", [("botId", ConstructorParameterDescription(self.botId))]) + } + } + public class Cons_messageActionNewCreatorPending: TypeConstructorDescription { + public var newCreatorId: Int64 + public init(newCreatorId: Int64) { + self.newCreatorId = newCreatorId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionNewCreatorPending", [("newCreatorId", ConstructorParameterDescription(self.newCreatorId))]) + } + } + public class Cons_messageActionNoForwardsRequest: TypeConstructorDescription { + public var flags: Int32 + public var prevValue: Api.Bool + public var newValue: Api.Bool + public init(flags: Int32, prevValue: Api.Bool, newValue: Api.Bool) { + self.flags = flags + self.prevValue = prevValue + self.newValue = newValue + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionNoForwardsRequest", [("flags", ConstructorParameterDescription(self.flags)), ("prevValue", ConstructorParameterDescription(self.prevValue)), ("newValue", ConstructorParameterDescription(self.newValue))]) + } + } + public class Cons_messageActionNoForwardsToggle: TypeConstructorDescription { + public var prevValue: Api.Bool + public var newValue: Api.Bool + public init(prevValue: Api.Bool, newValue: Api.Bool) { + self.prevValue = prevValue + self.newValue = newValue + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionNoForwardsToggle", [("prevValue", ConstructorParameterDescription(self.prevValue)), ("newValue", ConstructorParameterDescription(self.newValue))]) + } + } + public class Cons_messageActionPaidMessagesPrice: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public init(flags: Int32, stars: Int64) { + self.flags = flags + self.stars = stars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPaidMessagesPrice", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars))]) + } + } + public class Cons_messageActionPaidMessagesRefunded: TypeConstructorDescription { + public var count: Int32 + public var stars: Int64 + public init(count: Int32, stars: Int64) { + self.count = count + self.stars = stars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPaidMessagesRefunded", [("count", ConstructorParameterDescription(self.count)), ("stars", ConstructorParameterDescription(self.stars))]) + } + } + public class Cons_messageActionPaymentRefunded: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var currency: String + public var totalAmount: Int64 + public var payload: Buffer? + public var charge: Api.PaymentCharge + public init(flags: Int32, peer: Api.Peer, currency: String, totalAmount: Int64, payload: Buffer?, charge: Api.PaymentCharge) { + self.flags = flags + self.peer = peer + self.currency = currency + self.totalAmount = totalAmount + self.payload = payload + self.charge = charge + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPaymentRefunded", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("payload", ConstructorParameterDescription(self.payload)), ("charge", ConstructorParameterDescription(self.charge))]) + } + } + public class Cons_messageActionPaymentSent: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var totalAmount: Int64 + public var invoiceSlug: String? + public var subscriptionUntilDate: Int32? + public init(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?, subscriptionUntilDate: Int32?) { + self.flags = flags + self.currency = currency + self.totalAmount = totalAmount + self.invoiceSlug = invoiceSlug + self.subscriptionUntilDate = subscriptionUntilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPaymentSent", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("invoiceSlug", ConstructorParameterDescription(self.invoiceSlug)), ("subscriptionUntilDate", ConstructorParameterDescription(self.subscriptionUntilDate))]) + } + } + public class Cons_messageActionPaymentSentMe: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var totalAmount: Int64 + public var payload: Buffer + public var info: Api.PaymentRequestedInfo? + public var shippingOptionId: String? + public var charge: Api.PaymentCharge + public var subscriptionUntilDate: Int32? + public init(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge, subscriptionUntilDate: Int32?) { + self.flags = flags + self.currency = currency + self.totalAmount = totalAmount + self.payload = payload + self.info = info + self.shippingOptionId = shippingOptionId + self.charge = charge + self.subscriptionUntilDate = subscriptionUntilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPaymentSentMe", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("payload", ConstructorParameterDescription(self.payload)), ("info", ConstructorParameterDescription(self.info)), ("shippingOptionId", ConstructorParameterDescription(self.shippingOptionId)), ("charge", ConstructorParameterDescription(self.charge)), ("subscriptionUntilDate", ConstructorParameterDescription(self.subscriptionUntilDate))]) + } + } + public class Cons_messageActionPhoneCall: TypeConstructorDescription { + public var flags: Int32 + public var callId: Int64 + public var reason: Api.PhoneCallDiscardReason? + public var duration: Int32? + public init(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) { + self.flags = flags + self.callId = callId + self.reason = reason + self.duration = duration + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPhoneCall", [("flags", ConstructorParameterDescription(self.flags)), ("callId", ConstructorParameterDescription(self.callId)), ("reason", ConstructorParameterDescription(self.reason)), ("duration", ConstructorParameterDescription(self.duration))]) + } + } + public class Cons_messageActionPollAppendAnswer: TypeConstructorDescription { + public var answer: Api.PollAnswer + public init(answer: Api.PollAnswer) { + self.answer = answer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPollAppendAnswer", [("answer", ConstructorParameterDescription(self.answer))]) + } + } + public class Cons_messageActionPollDeleteAnswer: TypeConstructorDescription { + public var answer: Api.PollAnswer + public init(answer: Api.PollAnswer) { + self.answer = answer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPollDeleteAnswer", [("answer", ConstructorParameterDescription(self.answer))]) + } + } + public class Cons_messageActionPrizeStars: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var transactionId: String + public var boostPeer: Api.Peer + public var giveawayMsgId: Int32 + public init(flags: Int32, stars: Int64, transactionId: String, boostPeer: Api.Peer, giveawayMsgId: Int32) { + self.flags = flags + self.stars = stars + self.transactionId = transactionId + self.boostPeer = boostPeer + self.giveawayMsgId = giveawayMsgId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionPrizeStars", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("transactionId", ConstructorParameterDescription(self.transactionId)), ("boostPeer", ConstructorParameterDescription(self.boostPeer)), ("giveawayMsgId", ConstructorParameterDescription(self.giveawayMsgId))]) + } + } + public class Cons_messageActionRequestedPeer: TypeConstructorDescription { + public var buttonId: Int32 + public var peers: [Api.Peer] + public init(buttonId: Int32, peers: [Api.Peer]) { + self.buttonId = buttonId + self.peers = peers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionRequestedPeer", [("buttonId", ConstructorParameterDescription(self.buttonId)), ("peers", ConstructorParameterDescription(self.peers))]) + } + } + public class Cons_messageActionRequestedPeerSentMe: TypeConstructorDescription { + public var buttonId: Int32 + public var peers: [Api.RequestedPeer] + public init(buttonId: Int32, peers: [Api.RequestedPeer]) { + self.buttonId = buttonId + self.peers = peers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionRequestedPeerSentMe", [("buttonId", ConstructorParameterDescription(self.buttonId)), ("peers", ConstructorParameterDescription(self.peers))]) + } + } + public class Cons_messageActionSecureValuesSent: TypeConstructorDescription { + public var types: [Api.SecureValueType] + public init(types: [Api.SecureValueType]) { + self.types = types + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSecureValuesSent", [("types", ConstructorParameterDescription(self.types))]) + } + } + public class Cons_messageActionSecureValuesSentMe: TypeConstructorDescription { + public var values: [Api.SecureValue] + public var credentials: Api.SecureCredentialsEncrypted + public init(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) { + self.values = values + self.credentials = credentials + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSecureValuesSentMe", [("values", ConstructorParameterDescription(self.values)), ("credentials", ConstructorParameterDescription(self.credentials))]) + } + } + public class Cons_messageActionSetChatTheme: TypeConstructorDescription { + public var theme: Api.ChatTheme + public init(theme: Api.ChatTheme) { + self.theme = theme + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSetChatTheme", [("theme", ConstructorParameterDescription(self.theme))]) + } + } + public class Cons_messageActionSetChatWallPaper: TypeConstructorDescription { + public var flags: Int32 + public var wallpaper: Api.WallPaper + public init(flags: Int32, wallpaper: Api.WallPaper) { + self.flags = flags + self.wallpaper = wallpaper + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSetChatWallPaper", [("flags", ConstructorParameterDescription(self.flags)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) + } + } + public class Cons_messageActionSetMessagesTTL: TypeConstructorDescription { + public var flags: Int32 + public var period: Int32 + public var autoSettingFrom: Int64? + public init(flags: Int32, period: Int32, autoSettingFrom: Int64?) { + self.flags = flags + self.period = period + self.autoSettingFrom = autoSettingFrom + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSetMessagesTTL", [("flags", ConstructorParameterDescription(self.flags)), ("period", ConstructorParameterDescription(self.period)), ("autoSettingFrom", ConstructorParameterDescription(self.autoSettingFrom))]) + } + } + public class Cons_messageActionStarGift: TypeConstructorDescription { + public var flags: Int32 + public var gift: Api.StarGift + public var message: Api.TextWithEntities? + public var convertStars: Int64? + public var upgradeMsgId: Int32? + public var upgradeStars: Int64? + public var fromId: Api.Peer? + public var peer: Api.Peer? + public var savedId: Int64? + public var prepaidUpgradeHash: String? + public var giftMsgId: Int32? + public var toId: Api.Peer? + public var giftNum: Int32? + public init(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?, toId: Api.Peer?, giftNum: Int32?) { + self.flags = flags + self.gift = gift + self.message = message + self.convertStars = convertStars + self.upgradeMsgId = upgradeMsgId + self.upgradeStars = upgradeStars + self.fromId = fromId + self.peer = peer + self.savedId = savedId + self.prepaidUpgradeHash = prepaidUpgradeHash + self.giftMsgId = giftMsgId + self.toId = toId + self.giftNum = giftNum + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionStarGift", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("message", ConstructorParameterDescription(self.message)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("upgradeMsgId", ConstructorParameterDescription(self.upgradeMsgId)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peer", ConstructorParameterDescription(self.peer)), ("savedId", ConstructorParameterDescription(self.savedId)), ("prepaidUpgradeHash", ConstructorParameterDescription(self.prepaidUpgradeHash)), ("giftMsgId", ConstructorParameterDescription(self.giftMsgId)), ("toId", ConstructorParameterDescription(self.toId)), ("giftNum", ConstructorParameterDescription(self.giftNum))]) + } + } + public class Cons_messageActionStarGiftPurchaseOffer: TypeConstructorDescription { + public var flags: Int32 + public var gift: Api.StarGift + public var price: Api.StarsAmount + public var expiresAt: Int32 + public init(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount, expiresAt: Int32) { + self.flags = flags + self.gift = gift + self.price = price + self.expiresAt = expiresAt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionStarGiftPurchaseOffer", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("price", ConstructorParameterDescription(self.price)), ("expiresAt", ConstructorParameterDescription(self.expiresAt))]) + } + } + public class Cons_messageActionStarGiftPurchaseOfferDeclined: TypeConstructorDescription { + public var flags: Int32 + public var gift: Api.StarGift + public var price: Api.StarsAmount + public init(flags: Int32, gift: Api.StarGift, price: Api.StarsAmount) { + self.flags = flags + self.gift = gift + self.price = price + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionStarGiftPurchaseOfferDeclined", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("price", ConstructorParameterDescription(self.price))]) + } + } + public class Cons_messageActionStarGiftUnique: TypeConstructorDescription { + public var flags: Int32 + public var gift: Api.StarGift + public var canExportAt: Int32? + public var transferStars: Int64? + public var fromId: Api.Peer? + public var peer: Api.Peer? + public var savedId: Int64? + public var resaleAmount: Api.StarsAmount? + public var canTransferAt: Int32? + public var canResellAt: Int32? + public var dropOriginalDetailsStars: Int64? + public var canCraftAt: Int32? + public init(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?, canCraftAt: Int32?) { + self.flags = flags + self.gift = gift + self.canExportAt = canExportAt + self.transferStars = transferStars + self.fromId = fromId + self.peer = peer + self.savedId = savedId + self.resaleAmount = resaleAmount + self.canTransferAt = canTransferAt + self.canResellAt = canResellAt + self.dropOriginalDetailsStars = dropOriginalDetailsStars + self.canCraftAt = canCraftAt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionStarGiftUnique", [("flags", ConstructorParameterDescription(self.flags)), ("gift", ConstructorParameterDescription(self.gift)), ("canExportAt", ConstructorParameterDescription(self.canExportAt)), ("transferStars", ConstructorParameterDescription(self.transferStars)), ("fromId", ConstructorParameterDescription(self.fromId)), ("peer", ConstructorParameterDescription(self.peer)), ("savedId", ConstructorParameterDescription(self.savedId)), ("resaleAmount", ConstructorParameterDescription(self.resaleAmount)), ("canTransferAt", ConstructorParameterDescription(self.canTransferAt)), ("canResellAt", ConstructorParameterDescription(self.canResellAt)), ("dropOriginalDetailsStars", ConstructorParameterDescription(self.dropOriginalDetailsStars)), ("canCraftAt", ConstructorParameterDescription(self.canCraftAt))]) + } + } + public class Cons_messageActionSuggestBirthday: TypeConstructorDescription { + public var birthday: Api.Birthday + public init(birthday: Api.Birthday) { + self.birthday = birthday + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSuggestBirthday", [("birthday", ConstructorParameterDescription(self.birthday))]) + } + } + public class Cons_messageActionSuggestProfilePhoto: TypeConstructorDescription { + public var photo: Api.Photo + public init(photo: Api.Photo) { + self.photo = photo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSuggestProfilePhoto", [("photo", ConstructorParameterDescription(self.photo))]) + } + } + public class Cons_messageActionSuggestedPostApproval: TypeConstructorDescription { + public var flags: Int32 + public var rejectComment: String? + public var scheduleDate: Int32? + public var price: Api.StarsAmount? + public init(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) { + self.flags = flags + self.rejectComment = rejectComment + self.scheduleDate = scheduleDate + self.price = price + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSuggestedPostApproval", [("flags", ConstructorParameterDescription(self.flags)), ("rejectComment", ConstructorParameterDescription(self.rejectComment)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate)), ("price", ConstructorParameterDescription(self.price))]) + } + } + public class Cons_messageActionSuggestedPostRefund: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSuggestedPostRefund", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_messageActionSuggestedPostSuccess: TypeConstructorDescription { + public var price: Api.StarsAmount + public init(price: Api.StarsAmount) { + self.price = price + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionSuggestedPostSuccess", [("price", ConstructorParameterDescription(self.price))]) + } + } + public class Cons_messageActionTodoAppendTasks: TypeConstructorDescription { + public var list: [Api.TodoItem] + public init(list: [Api.TodoItem]) { + self.list = list + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionTodoAppendTasks", [("list", ConstructorParameterDescription(self.list))]) + } + } + public class Cons_messageActionTodoCompletions: TypeConstructorDescription { + public var completed: [Int32] + public var incompleted: [Int32] + public init(completed: [Int32], incompleted: [Int32]) { + self.completed = completed + self.incompleted = incompleted + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionTodoCompletions", [("completed", ConstructorParameterDescription(self.completed)), ("incompleted", ConstructorParameterDescription(self.incompleted))]) + } + } + public class Cons_messageActionTopicCreate: TypeConstructorDescription { + public var flags: Int32 + public var title: String + public var iconColor: Int32 + public var iconEmojiId: Int64? + public init(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) { + self.flags = flags + self.title = title + self.iconColor = iconColor + self.iconEmojiId = iconEmojiId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionTopicCreate", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("iconColor", ConstructorParameterDescription(self.iconColor)), ("iconEmojiId", ConstructorParameterDescription(self.iconEmojiId))]) + } + } + public class Cons_messageActionTopicEdit: TypeConstructorDescription { + public var flags: Int32 + public var title: String? + public var iconEmojiId: Int64? + public var closed: Api.Bool? + public var hidden: Api.Bool? + public init(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) { + self.flags = flags + self.title = title + self.iconEmojiId = iconEmojiId + self.closed = closed + self.hidden = hidden + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionTopicEdit", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("iconEmojiId", ConstructorParameterDescription(self.iconEmojiId)), ("closed", ConstructorParameterDescription(self.closed)), ("hidden", ConstructorParameterDescription(self.hidden))]) + } + } + public class Cons_messageActionWebViewDataSent: TypeConstructorDescription { + public var text: String + public init(text: String) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionWebViewDataSent", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_messageActionWebViewDataSentMe: TypeConstructorDescription { + public var text: String + public var data: String + public init(text: String, data: String) { + self.text = text + self.data = data + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageActionWebViewDataSentMe", [("text", ConstructorParameterDescription(self.text)), ("data", ConstructorParameterDescription(self.data))]) + } + } + case messageActionBoostApply(Cons_messageActionBoostApply) + case messageActionBotAllowed(Cons_messageActionBotAllowed) + case messageActionChangeCreator(Cons_messageActionChangeCreator) + case messageActionChannelCreate(Cons_messageActionChannelCreate) + case messageActionChannelMigrateFrom(Cons_messageActionChannelMigrateFrom) + case messageActionChatAddUser(Cons_messageActionChatAddUser) + case messageActionChatCreate(Cons_messageActionChatCreate) + case messageActionChatDeletePhoto + case messageActionChatDeleteUser(Cons_messageActionChatDeleteUser) + case messageActionChatEditPhoto(Cons_messageActionChatEditPhoto) + case messageActionChatEditTitle(Cons_messageActionChatEditTitle) + case messageActionChatJoinedByLink(Cons_messageActionChatJoinedByLink) + case messageActionChatJoinedByRequest + case messageActionChatMigrateTo(Cons_messageActionChatMigrateTo) + case messageActionConferenceCall(Cons_messageActionConferenceCall) + case messageActionContactSignUp + case messageActionCustomAction(Cons_messageActionCustomAction) + case messageActionEmpty + case messageActionGameScore(Cons_messageActionGameScore) + case messageActionGeoProximityReached(Cons_messageActionGeoProximityReached) + case messageActionGiftCode(Cons_messageActionGiftCode) + case messageActionGiftPremium(Cons_messageActionGiftPremium) + case messageActionGiftStars(Cons_messageActionGiftStars) + case messageActionGiftTon(Cons_messageActionGiftTon) + case messageActionGiveawayLaunch(Cons_messageActionGiveawayLaunch) + case messageActionGiveawayResults(Cons_messageActionGiveawayResults) + case messageActionGroupCall(Cons_messageActionGroupCall) + case messageActionGroupCallScheduled(Cons_messageActionGroupCallScheduled) + case messageActionHistoryClear + case messageActionInviteToGroupCall(Cons_messageActionInviteToGroupCall) + case messageActionManagedBotCreated(Cons_messageActionManagedBotCreated) + case messageActionNewCreatorPending(Cons_messageActionNewCreatorPending) + case messageActionNoForwardsRequest(Cons_messageActionNoForwardsRequest) + case messageActionNoForwardsToggle(Cons_messageActionNoForwardsToggle) + case messageActionPaidMessagesPrice(Cons_messageActionPaidMessagesPrice) + case messageActionPaidMessagesRefunded(Cons_messageActionPaidMessagesRefunded) + case messageActionPaymentRefunded(Cons_messageActionPaymentRefunded) + case messageActionPaymentSent(Cons_messageActionPaymentSent) + case messageActionPaymentSentMe(Cons_messageActionPaymentSentMe) + case messageActionPhoneCall(Cons_messageActionPhoneCall) + case messageActionPinMessage + case messageActionPollAppendAnswer(Cons_messageActionPollAppendAnswer) + case messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer) + case messageActionPrizeStars(Cons_messageActionPrizeStars) + case messageActionRequestedPeer(Cons_messageActionRequestedPeer) + case messageActionRequestedPeerSentMe(Cons_messageActionRequestedPeerSentMe) + case messageActionScreenshotTaken + case messageActionSecureValuesSent(Cons_messageActionSecureValuesSent) + case messageActionSecureValuesSentMe(Cons_messageActionSecureValuesSentMe) + case messageActionSetChatTheme(Cons_messageActionSetChatTheme) + case messageActionSetChatWallPaper(Cons_messageActionSetChatWallPaper) + case messageActionSetMessagesTTL(Cons_messageActionSetMessagesTTL) + case messageActionStarGift(Cons_messageActionStarGift) + case messageActionStarGiftPurchaseOffer(Cons_messageActionStarGiftPurchaseOffer) + case messageActionStarGiftPurchaseOfferDeclined(Cons_messageActionStarGiftPurchaseOfferDeclined) + case messageActionStarGiftUnique(Cons_messageActionStarGiftUnique) + case messageActionSuggestBirthday(Cons_messageActionSuggestBirthday) + case messageActionSuggestProfilePhoto(Cons_messageActionSuggestProfilePhoto) + case messageActionSuggestedPostApproval(Cons_messageActionSuggestedPostApproval) + case messageActionSuggestedPostRefund(Cons_messageActionSuggestedPostRefund) + case messageActionSuggestedPostSuccess(Cons_messageActionSuggestedPostSuccess) + case messageActionTodoAppendTasks(Cons_messageActionTodoAppendTasks) + case messageActionTodoCompletions(Cons_messageActionTodoCompletions) + case messageActionTopicCreate(Cons_messageActionTopicCreate) + case messageActionTopicEdit(Cons_messageActionTopicEdit) + case messageActionWebViewDataSent(Cons_messageActionWebViewDataSent) + case messageActionWebViewDataSentMe(Cons_messageActionWebViewDataSentMe) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageActionBoostApply(let _data): + if boxed { + buffer.appendInt32(-872240531) + } + serializeInt32(_data.boosts, buffer: buffer, boxed: false) + break + case .messageActionBotAllowed(let _data): + if boxed { + buffer.appendInt32(-988359047) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.domain!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.app!.serialize(buffer, true) + } + break + case .messageActionChangeCreator(let _data): + if boxed { + buffer.appendInt32(-511160261) + } + serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false) + break + case .messageActionChannelCreate(let _data): + if boxed { + buffer.appendInt32(-1781355374) + } + serializeString(_data.title, buffer: buffer, boxed: false) + break + case .messageActionChannelMigrateFrom(let _data): + if boxed { + buffer.appendInt32(-365344535) + } + serializeString(_data.title, buffer: buffer, boxed: false) + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + break + case .messageActionChatAddUser(let _data): + if boxed { + buffer.appendInt32(365886720) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .messageActionChatCreate(let _data): + if boxed { + buffer.appendInt32(-1119368275) + } + serializeString(_data.title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .messageActionChatDeletePhoto: + if boxed { + buffer.appendInt32(-1780220945) + } + break + case .messageActionChatDeleteUser(let _data): + if boxed { + buffer.appendInt32(-1539362612) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .messageActionChatEditPhoto(let _data): + if boxed { + buffer.appendInt32(2144015272) + } + _data.photo.serialize(buffer, true) + break + case .messageActionChatEditTitle(let _data): + if boxed { + buffer.appendInt32(-1247687078) + } + serializeString(_data.title, buffer: buffer, boxed: false) + break + case .messageActionChatJoinedByLink(let _data): + if boxed { + buffer.appendInt32(51520707) + } + serializeInt64(_data.inviterId, buffer: buffer, boxed: false) + break + case .messageActionChatJoinedByRequest: + if boxed { + buffer.appendInt32(-339958837) + } + break + case .messageActionChatMigrateTo(let _data): + if boxed { + buffer.appendInt32(-519864430) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + break + case .messageActionConferenceCall(let _data): + if boxed { + buffer.appendInt32(805187450) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.callId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.duration!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.otherParticipants!.count)) + for item in _data.otherParticipants! { + item.serialize(buffer, true) + } + } + break + case .messageActionContactSignUp: + if boxed { + buffer.appendInt32(-202219658) + } + break + case .messageActionCustomAction(let _data): + if boxed { + buffer.appendInt32(-85549226) + } + serializeString(_data.message, buffer: buffer, boxed: false) + break + case .messageActionEmpty: + if boxed { + buffer.appendInt32(-1230047312) + } + break + case .messageActionGameScore(let _data): + if boxed { + buffer.appendInt32(-1834538890) + } + serializeInt64(_data.gameId, buffer: buffer, boxed: false) + serializeInt32(_data.score, buffer: buffer, boxed: false) + break + case .messageActionGeoProximityReached(let _data): + if boxed { + buffer.appendInt32(-1730095465) + } + _data.fromId.serialize(buffer, true) + _data.toId.serialize(buffer, true) + serializeInt32(_data.distance, buffer: buffer, boxed: false) + break + case .messageActionGiftCode(let _data): + if boxed { + buffer.appendInt32(834962247) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.boostPeer!.serialize(buffer, true) + } + serializeInt32(_data.days, buffer: buffer, boxed: false) + serializeString(_data.slug, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.currency!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.amount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.message!.serialize(buffer, true) + } + break + case .messageActionGiftPremium(let _data): + if boxed { + buffer.appendInt32(1223234306) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeInt32(_data.days, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.message!.serialize(buffer, true) + } + break + case .messageActionGiftStars(let _data): + if boxed { + buffer.appendInt32(1171632161) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.cryptoCurrency!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.cryptoAmount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.transactionId!, buffer: buffer, boxed: false) + } + break + case .messageActionGiftTon(let _data): + if boxed { + buffer.appendInt32(-1465661799) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeString(_data.cryptoCurrency, buffer: buffer, boxed: false) + serializeInt64(_data.cryptoAmount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.transactionId!, buffer: buffer, boxed: false) + } + break + case .messageActionGiveawayLaunch(let _data): + if boxed { + buffer.appendInt32(-1475391004) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.stars!, buffer: buffer, boxed: false) + } + break + case .messageActionGiveawayResults(let _data): + if boxed { + buffer.appendInt32(-2015170219) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) + serializeInt32(_data.unclaimedCount, buffer: buffer, boxed: false) + break + case .messageActionGroupCall(let _data): + if boxed { + buffer.appendInt32(2047704898) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.call.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.duration!, buffer: buffer, boxed: false) + } + break + case .messageActionGroupCallScheduled(let _data): + if boxed { + buffer.appendInt32(-1281329567) + } + _data.call.serialize(buffer, true) + serializeInt32(_data.scheduleDate, buffer: buffer, boxed: false) + break + case .messageActionHistoryClear: + if boxed { + buffer.appendInt32(-1615153660) + } + break + case .messageActionInviteToGroupCall(let _data): + if boxed { + buffer.appendInt32(1345295095) + } + _data.call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .messageActionManagedBotCreated(let _data): + if boxed { + buffer.appendInt32(375414334) + } + serializeInt64(_data.botId, buffer: buffer, boxed: false) + break + case .messageActionNewCreatorPending(let _data): + if boxed { + buffer.appendInt32(-1333866363) + } + serializeInt64(_data.newCreatorId, buffer: buffer, boxed: false) + break + case .messageActionNoForwardsRequest(let _data): + if boxed { + buffer.appendInt32(1042781114) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.prevValue.serialize(buffer, true) + _data.newValue.serialize(buffer, true) + break + case .messageActionNoForwardsToggle(let _data): + if boxed { + buffer.appendInt32(-1082301070) + } + _data.prevValue.serialize(buffer, true) + _data.newValue.serialize(buffer, true) + break + case .messageActionPaidMessagesPrice(let _data): + if boxed { + buffer.appendInt32(-2068281992) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + break + case .messageActionPaidMessagesRefunded(let _data): + if boxed { + buffer.appendInt32(-1407246387) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + break + case .messageActionPaymentRefunded(let _data): + if boxed { + buffer.appendInt32(1102307842) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.payload!, buffer: buffer, boxed: false) + } + _data.charge.serialize(buffer, true) + break + case .messageActionPaymentSent(let _data): + if boxed { + buffer.appendInt32(-970673810) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.invoiceSlug!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.subscriptionUntilDate!, buffer: buffer, boxed: false) + } + break + case .messageActionPaymentSentMe(let _data): + if boxed { + buffer.appendInt32(-6288180) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + serializeBytes(_data.payload, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.info!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.shippingOptionId!, buffer: buffer, boxed: false) + } + _data.charge.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.subscriptionUntilDate!, buffer: buffer, boxed: false) + } + break + case .messageActionPhoneCall(let _data): + if boxed { + buffer.appendInt32(-2132731265) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.callId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.reason!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.duration!, buffer: buffer, boxed: false) + } + break + case .messageActionPinMessage: + if boxed { + buffer.appendInt32(-1799538451) + } + break + case .messageActionPollAppendAnswer(let _data): + if boxed { + buffer.appendInt32(-1650340500) + } + _data.answer.serialize(buffer, true) + break + case .messageActionPollDeleteAnswer(let _data): + if boxed { + buffer.appendInt32(966161628) + } + _data.answer.serialize(buffer, true) + break + case .messageActionPrizeStars(let _data): + if boxed { + buffer.appendInt32(-1341372510) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + serializeString(_data.transactionId, buffer: buffer, boxed: false) + _data.boostPeer.serialize(buffer, true) + serializeInt32(_data.giveawayMsgId, buffer: buffer, boxed: false) + break + case .messageActionRequestedPeer(let _data): + if boxed { + buffer.appendInt32(827428507) + } + serializeInt32(_data.buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + break + case .messageActionRequestedPeerSentMe(let _data): + if boxed { + buffer.appendInt32(-1816979384) + } + serializeInt32(_data.buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + break + case .messageActionScreenshotTaken: + if boxed { + buffer.appendInt32(1200788123) + } + break + case .messageActionSecureValuesSent(let _data): + if boxed { + buffer.appendInt32(-648257196) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.types.count)) + for item in _data.types { + item.serialize(buffer, true) + } + break + case .messageActionSecureValuesSentMe(let _data): + if boxed { + buffer.appendInt32(455635795) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.values.count)) + for item in _data.values { + item.serialize(buffer, true) + } + _data.credentials.serialize(buffer, true) + break + case .messageActionSetChatTheme(let _data): + if boxed { + buffer.appendInt32(-1189364422) + } + _data.theme.serialize(buffer, true) + break + case .messageActionSetChatWallPaper(let _data): + if boxed { + buffer.appendInt32(1348510708) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.wallpaper.serialize(buffer, true) + break + case .messageActionSetMessagesTTL(let _data): + if boxed { + buffer.appendInt32(1007897979) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.period, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.autoSettingFrom!, buffer: buffer, boxed: false) + } + break + case .messageActionStarGift(let _data): + if boxed { + buffer.appendInt32(-366202413) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.gift.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.message!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.convertStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.upgradeMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + _data.peer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + serializeInt64(_data.savedId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + serializeString(_data.prepaidUpgradeHash!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.giftMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 18) != 0 { + _data.toId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 19) != 0 { + serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) + } + break + case .messageActionStarGiftPurchaseOffer(let _data): + if boxed { + buffer.appendInt32(2000845012) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.gift.serialize(buffer, true) + _data.price.serialize(buffer, true) + serializeInt32(_data.expiresAt, buffer: buffer, boxed: false) + break + case .messageActionStarGiftPurchaseOfferDeclined(let _data): + if boxed { + buffer.appendInt32(1940760427) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.gift.serialize(buffer, true) + _data.price.serialize(buffer, true) + break + case .messageActionStarGiftUnique(let _data): + if boxed { + buffer.appendInt32(-423422686) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.gift.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.canExportAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.transferStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + _data.peer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeInt64(_data.savedId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.resaleAmount!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + serializeInt32(_data.canTransferAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.canResellAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + serializeInt64(_data.dropOriginalDetailsStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.canCraftAt!, buffer: buffer, boxed: false) + } + break + case .messageActionSuggestBirthday(let _data): + if boxed { + buffer.appendInt32(747579941) + } + _data.birthday.serialize(buffer, true) + break + case .messageActionSuggestProfilePhoto(let _data): + if boxed { + buffer.appendInt32(1474192222) + } + _data.photo.serialize(buffer, true) + break + case .messageActionSuggestedPostApproval(let _data): + if boxed { + buffer.appendInt32(-293988970) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.rejectComment!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.scheduleDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.price!.serialize(buffer, true) + } + break + case .messageActionSuggestedPostRefund(let _data): + if boxed { + buffer.appendInt32(1777932024) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .messageActionSuggestedPostSuccess(let _data): + if boxed { + buffer.appendInt32(-1780625559) + } + _data.price.serialize(buffer, true) + break + case .messageActionTodoAppendTasks(let _data): + if boxed { + buffer.appendInt32(-940721021) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.list.count)) + for item in _data.list { + item.serialize(buffer, true) + } + break + case .messageActionTodoCompletions(let _data): + if boxed { + buffer.appendInt32(-864265079) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.completed.count)) + for item in _data.completed { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.incompleted.count)) + for item in _data.incompleted { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .messageActionTopicCreate(let _data): + if boxed { + buffer.appendInt32(228168278) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeInt32(_data.iconColor, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.iconEmojiId!, buffer: buffer, boxed: false) + } + break + case .messageActionTopicEdit(let _data): + if boxed { + buffer.appendInt32(-1064024032) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt64(_data.iconEmojiId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.closed!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.hidden!.serialize(buffer, true) + } + break + case .messageActionWebViewDataSent(let _data): + if boxed { + buffer.appendInt32(-1262252875) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .messageActionWebViewDataSentMe(let _data): + if boxed { + buffer.appendInt32(1205698681) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeString(_data.data, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageActionBoostApply(let _data): + return ("messageActionBoostApply", [("boosts", ConstructorParameterDescription(_data.boosts))]) + case .messageActionBotAllowed(let _data): + return ("messageActionBotAllowed", [("flags", ConstructorParameterDescription(_data.flags)), ("domain", ConstructorParameterDescription(_data.domain)), ("app", ConstructorParameterDescription(_data.app))]) + case .messageActionChangeCreator(let _data): + return ("messageActionChangeCreator", [("newCreatorId", ConstructorParameterDescription(_data.newCreatorId))]) + case .messageActionChannelCreate(let _data): + return ("messageActionChannelCreate", [("title", ConstructorParameterDescription(_data.title))]) + case .messageActionChannelMigrateFrom(let _data): + return ("messageActionChannelMigrateFrom", [("title", ConstructorParameterDescription(_data.title)), ("chatId", ConstructorParameterDescription(_data.chatId))]) + case .messageActionChatAddUser(let _data): + return ("messageActionChatAddUser", [("users", ConstructorParameterDescription(_data.users))]) + case .messageActionChatCreate(let _data): + return ("messageActionChatCreate", [("title", ConstructorParameterDescription(_data.title)), ("users", ConstructorParameterDescription(_data.users))]) + case .messageActionChatDeletePhoto: + return ("messageActionChatDeletePhoto", []) + case .messageActionChatDeleteUser(let _data): + return ("messageActionChatDeleteUser", [("userId", ConstructorParameterDescription(_data.userId))]) + case .messageActionChatEditPhoto(let _data): + return ("messageActionChatEditPhoto", [("photo", ConstructorParameterDescription(_data.photo))]) + case .messageActionChatEditTitle(let _data): + return ("messageActionChatEditTitle", [("title", ConstructorParameterDescription(_data.title))]) + case .messageActionChatJoinedByLink(let _data): + return ("messageActionChatJoinedByLink", [("inviterId", ConstructorParameterDescription(_data.inviterId))]) + case .messageActionChatJoinedByRequest: + return ("messageActionChatJoinedByRequest", []) + case .messageActionChatMigrateTo(let _data): + return ("messageActionChatMigrateTo", [("channelId", ConstructorParameterDescription(_data.channelId))]) + case .messageActionConferenceCall(let _data): + return ("messageActionConferenceCall", [("flags", ConstructorParameterDescription(_data.flags)), ("callId", ConstructorParameterDescription(_data.callId)), ("duration", ConstructorParameterDescription(_data.duration)), ("otherParticipants", ConstructorParameterDescription(_data.otherParticipants))]) + case .messageActionContactSignUp: + return ("messageActionContactSignUp", []) + case .messageActionCustomAction(let _data): + return ("messageActionCustomAction", [("message", ConstructorParameterDescription(_data.message))]) + case .messageActionEmpty: + return ("messageActionEmpty", []) + case .messageActionGameScore(let _data): + return ("messageActionGameScore", [("gameId", ConstructorParameterDescription(_data.gameId)), ("score", ConstructorParameterDescription(_data.score))]) + case .messageActionGeoProximityReached(let _data): + return ("messageActionGeoProximityReached", [("fromId", ConstructorParameterDescription(_data.fromId)), ("toId", ConstructorParameterDescription(_data.toId)), ("distance", ConstructorParameterDescription(_data.distance))]) + case .messageActionGiftCode(let _data): + return ("messageActionGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("days", ConstructorParameterDescription(_data.days)), ("slug", ConstructorParameterDescription(_data.slug)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("message", ConstructorParameterDescription(_data.message))]) + case .messageActionGiftPremium(let _data): + return ("messageActionGiftPremium", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("days", ConstructorParameterDescription(_data.days)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("message", ConstructorParameterDescription(_data.message))]) + case .messageActionGiftStars(let _data): + return ("messageActionGiftStars", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("stars", ConstructorParameterDescription(_data.stars)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId))]) + case .messageActionGiftTon(let _data): + return ("messageActionGiftTon", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId))]) + case .messageActionGiveawayLaunch(let _data): + return ("messageActionGiveawayLaunch", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars))]) + case .messageActionGiveawayResults(let _data): + return ("messageActionGiveawayResults", [("flags", ConstructorParameterDescription(_data.flags)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(_data.unclaimedCount))]) + case .messageActionGroupCall(let _data): + return ("messageActionGroupCall", [("flags", ConstructorParameterDescription(_data.flags)), ("call", ConstructorParameterDescription(_data.call)), ("duration", ConstructorParameterDescription(_data.duration))]) + case .messageActionGroupCallScheduled(let _data): + return ("messageActionGroupCallScheduled", [("call", ConstructorParameterDescription(_data.call)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate))]) + case .messageActionHistoryClear: + return ("messageActionHistoryClear", []) + case .messageActionInviteToGroupCall(let _data): + return ("messageActionInviteToGroupCall", [("call", ConstructorParameterDescription(_data.call)), ("users", ConstructorParameterDescription(_data.users))]) + case .messageActionManagedBotCreated(let _data): + return ("messageActionManagedBotCreated", [("botId", ConstructorParameterDescription(_data.botId))]) + case .messageActionNewCreatorPending(let _data): + return ("messageActionNewCreatorPending", [("newCreatorId", ConstructorParameterDescription(_data.newCreatorId))]) + case .messageActionNoForwardsRequest(let _data): + return ("messageActionNoForwardsRequest", [("flags", ConstructorParameterDescription(_data.flags)), ("prevValue", ConstructorParameterDescription(_data.prevValue)), ("newValue", ConstructorParameterDescription(_data.newValue))]) + case .messageActionNoForwardsToggle(let _data): + return ("messageActionNoForwardsToggle", [("prevValue", ConstructorParameterDescription(_data.prevValue)), ("newValue", ConstructorParameterDescription(_data.newValue))]) + case .messageActionPaidMessagesPrice(let _data): + return ("messageActionPaidMessagesPrice", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars))]) + case .messageActionPaidMessagesRefunded(let _data): + return ("messageActionPaidMessagesRefunded", [("count", ConstructorParameterDescription(_data.count)), ("stars", ConstructorParameterDescription(_data.stars))]) + case .messageActionPaymentRefunded(let _data): + return ("messageActionPaymentRefunded", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("payload", ConstructorParameterDescription(_data.payload)), ("charge", ConstructorParameterDescription(_data.charge))]) + case .messageActionPaymentSent(let _data): + return ("messageActionPaymentSent", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("invoiceSlug", ConstructorParameterDescription(_data.invoiceSlug)), ("subscriptionUntilDate", ConstructorParameterDescription(_data.subscriptionUntilDate))]) + case .messageActionPaymentSentMe(let _data): + return ("messageActionPaymentSentMe", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("payload", ConstructorParameterDescription(_data.payload)), ("info", ConstructorParameterDescription(_data.info)), ("shippingOptionId", ConstructorParameterDescription(_data.shippingOptionId)), ("charge", ConstructorParameterDescription(_data.charge)), ("subscriptionUntilDate", ConstructorParameterDescription(_data.subscriptionUntilDate))]) + case .messageActionPhoneCall(let _data): + return ("messageActionPhoneCall", [("flags", ConstructorParameterDescription(_data.flags)), ("callId", ConstructorParameterDescription(_data.callId)), ("reason", ConstructorParameterDescription(_data.reason)), ("duration", ConstructorParameterDescription(_data.duration))]) + case .messageActionPinMessage: + return ("messageActionPinMessage", []) + case .messageActionPollAppendAnswer(let _data): + return ("messageActionPollAppendAnswer", [("answer", ConstructorParameterDescription(_data.answer))]) + case .messageActionPollDeleteAnswer(let _data): + return ("messageActionPollDeleteAnswer", [("answer", ConstructorParameterDescription(_data.answer))]) + case .messageActionPrizeStars(let _data): + return ("messageActionPrizeStars", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("transactionId", ConstructorParameterDescription(_data.transactionId)), ("boostPeer", ConstructorParameterDescription(_data.boostPeer)), ("giveawayMsgId", ConstructorParameterDescription(_data.giveawayMsgId))]) + case .messageActionRequestedPeer(let _data): + return ("messageActionRequestedPeer", [("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peers", ConstructorParameterDescription(_data.peers))]) + case .messageActionRequestedPeerSentMe(let _data): + return ("messageActionRequestedPeerSentMe", [("buttonId", ConstructorParameterDescription(_data.buttonId)), ("peers", ConstructorParameterDescription(_data.peers))]) + case .messageActionScreenshotTaken: + return ("messageActionScreenshotTaken", []) + case .messageActionSecureValuesSent(let _data): + return ("messageActionSecureValuesSent", [("types", ConstructorParameterDescription(_data.types))]) + case .messageActionSecureValuesSentMe(let _data): + return ("messageActionSecureValuesSentMe", [("values", ConstructorParameterDescription(_data.values)), ("credentials", ConstructorParameterDescription(_data.credentials))]) + case .messageActionSetChatTheme(let _data): + return ("messageActionSetChatTheme", [("theme", ConstructorParameterDescription(_data.theme))]) + case .messageActionSetChatWallPaper(let _data): + return ("messageActionSetChatWallPaper", [("flags", ConstructorParameterDescription(_data.flags)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) + case .messageActionSetMessagesTTL(let _data): + return ("messageActionSetMessagesTTL", [("flags", ConstructorParameterDescription(_data.flags)), ("period", ConstructorParameterDescription(_data.period)), ("autoSettingFrom", ConstructorParameterDescription(_data.autoSettingFrom))]) + case .messageActionStarGift(let _data): + return ("messageActionStarGift", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("message", ConstructorParameterDescription(_data.message)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("upgradeMsgId", ConstructorParameterDescription(_data.upgradeMsgId)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("prepaidUpgradeHash", ConstructorParameterDescription(_data.prepaidUpgradeHash)), ("giftMsgId", ConstructorParameterDescription(_data.giftMsgId)), ("toId", ConstructorParameterDescription(_data.toId)), ("giftNum", ConstructorParameterDescription(_data.giftNum))]) + case .messageActionStarGiftPurchaseOffer(let _data): + return ("messageActionStarGiftPurchaseOffer", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("price", ConstructorParameterDescription(_data.price)), ("expiresAt", ConstructorParameterDescription(_data.expiresAt))]) + case .messageActionStarGiftPurchaseOfferDeclined(let _data): + return ("messageActionStarGiftPurchaseOfferDeclined", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("price", ConstructorParameterDescription(_data.price))]) + case .messageActionStarGiftUnique(let _data): + return ("messageActionStarGiftUnique", [("flags", ConstructorParameterDescription(_data.flags)), ("gift", ConstructorParameterDescription(_data.gift)), ("canExportAt", ConstructorParameterDescription(_data.canExportAt)), ("transferStars", ConstructorParameterDescription(_data.transferStars)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("resaleAmount", ConstructorParameterDescription(_data.resaleAmount)), ("canTransferAt", ConstructorParameterDescription(_data.canTransferAt)), ("canResellAt", ConstructorParameterDescription(_data.canResellAt)), ("dropOriginalDetailsStars", ConstructorParameterDescription(_data.dropOriginalDetailsStars)), ("canCraftAt", ConstructorParameterDescription(_data.canCraftAt))]) + case .messageActionSuggestBirthday(let _data): + return ("messageActionSuggestBirthday", [("birthday", ConstructorParameterDescription(_data.birthday))]) + case .messageActionSuggestProfilePhoto(let _data): + return ("messageActionSuggestProfilePhoto", [("photo", ConstructorParameterDescription(_data.photo))]) + case .messageActionSuggestedPostApproval(let _data): + return ("messageActionSuggestedPostApproval", [("flags", ConstructorParameterDescription(_data.flags)), ("rejectComment", ConstructorParameterDescription(_data.rejectComment)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate)), ("price", ConstructorParameterDescription(_data.price))]) + case .messageActionSuggestedPostRefund(let _data): + return ("messageActionSuggestedPostRefund", [("flags", ConstructorParameterDescription(_data.flags))]) + case .messageActionSuggestedPostSuccess(let _data): + return ("messageActionSuggestedPostSuccess", [("price", ConstructorParameterDescription(_data.price))]) + case .messageActionTodoAppendTasks(let _data): + return ("messageActionTodoAppendTasks", [("list", ConstructorParameterDescription(_data.list))]) + case .messageActionTodoCompletions(let _data): + return ("messageActionTodoCompletions", [("completed", ConstructorParameterDescription(_data.completed)), ("incompleted", ConstructorParameterDescription(_data.incompleted))]) + case .messageActionTopicCreate(let _data): + return ("messageActionTopicCreate", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("iconColor", ConstructorParameterDescription(_data.iconColor)), ("iconEmojiId", ConstructorParameterDescription(_data.iconEmojiId))]) + case .messageActionTopicEdit(let _data): + return ("messageActionTopicEdit", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("iconEmojiId", ConstructorParameterDescription(_data.iconEmojiId)), ("closed", ConstructorParameterDescription(_data.closed)), ("hidden", ConstructorParameterDescription(_data.hidden))]) + case .messageActionWebViewDataSent(let _data): + return ("messageActionWebViewDataSent", [("text", ConstructorParameterDescription(_data.text))]) + case .messageActionWebViewDataSentMe(let _data): + return ("messageActionWebViewDataSentMe", [("text", ConstructorParameterDescription(_data.text)), ("data", ConstructorParameterDescription(_data.data))]) + } + } + + public static func parse_messageActionBoostApply(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionBoostApply(Cons_messageActionBoostApply(boosts: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: Api.BotApp? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BotApp + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionBotAllowed(Cons_messageActionBotAllowed(flags: _1!, domain: _2, app: _3)) + } + else { + return nil + } + } + public static func parse_messageActionChangeCreator(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChangeCreator(Cons_messageActionChangeCreator(newCreatorId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChannelCreate(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChannelCreate(Cons_messageActionChannelCreate(title: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChannelMigrateFrom(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionChannelMigrateFrom(Cons_messageActionChannelMigrateFrom(title: _1!, chatId: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionChatAddUser(_ reader: BufferReader) -> MessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatAddUser(Cons_messageActionChatAddUser(users: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChatCreate(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) var _2: [Int64]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } - var _3: [String]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - } - var _4: String? - if Int(_1!) & Int(1 << 3) != 0 { - _4 = parseString(reader) - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _6 = reader.readInt32() - } - var _7: Int64? - if Int(_1!) & Int(1 << 5) != 0 { - _7 = reader.readInt64() - } - var _8: Int32? - _8 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.MessageMedia.messageMediaGiveaway(Cons_messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6, stars: _7, untilDate: _8!)) + if _c1 && _c2 { + return Api.MessageAction.messageActionChatCreate(Cons_messageActionChatCreate(title: _1!, users: _2!)) } else { return nil } } - public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionChatDeletePhoto(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionChatDeletePhoto + } + public static func parse_messageActionChatDeleteUser(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatDeleteUser(Cons_messageActionChatDeleteUser(userId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChatEditPhoto(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Photo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Photo + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatEditPhoto(Cons_messageActionChatEditPhoto(photo: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChatEditTitle(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatEditTitle(Cons_messageActionChatEditTitle(title: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChatJoinedByLink(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatJoinedByLink(Cons_messageActionChatJoinedByLink(inviterId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionChatJoinedByRequest(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionChatJoinedByRequest + } + public static func parse_messageActionChatMigrateTo(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionChatMigrateTo(Cons_messageActionChatMigrateTo(channelId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionConferenceCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _3 = reader.readInt32() } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: [Int64]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + var _4: [Api.Peer]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt32() - } - var _9: Int64? - if Int(_1!) & Int(1 << 5) != 0 { - _9 = reader.readInt64() - } - var _10: String? - if Int(_1!) & Int(1 << 1) != 0 { - _10 = parseString(reader) - } - var _11: Int32? - _11 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.MessageMedia.messageMediaGiveawayResults(Cons_messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8, stars: _9, prizeDescription: _10, untilDate: _11!)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionConferenceCall(Cons_messageActionConferenceCall(flags: _1!, callId: _2!, duration: _3, otherParticipants: _4)) } else { return nil } } - public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Api.WebDocument? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.WebDocument - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = reader.readInt32() - } - var _6: String? - _6 = parseString(reader) - var _7: Int64? - _7 = reader.readInt64() - var _8: String? - _8 = parseString(reader) - var _9: Api.MessageExtendedMedia? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia - } - } + public static func parse_messageActionContactSignUp(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionContactSignUp + } + public static func parse_messageActionCustomAction(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.MessageMedia.messageMediaInvoice(Cons_messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9)) + if _c1 { + return Api.MessageAction.messageActionCustomAction(Cons_messageActionCustomAction(message: _1!)) } else { return nil } } - public static func parse_messageMediaPaidMedia(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionEmpty(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionEmpty + } + public static func parse_messageActionGameScore(_ reader: BufferReader) -> MessageAction? { var _1: Int64? _1 = reader.readInt64() - var _2: [Api.MessageExtendedMedia]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self) - } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageMedia.messageMediaPaidMedia(Cons_messageMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!)) + return Api.MessageAction.messageActionGameScore(Cons_messageActionGameScore(gameId: _1!, score: _2!)) } else { return nil } } - public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Photo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt32() - } - var _4: Api.Document? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Document - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaPhoto(Cons_messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3, video: _4)) - } - else { - return nil - } - } - public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Poll? + public static func parse_messageActionGeoProximityReached(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Peer? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Poll + _1 = Api.parse(reader, signature: signature) as? Api.Peer } - var _3: Api.PollResults? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PollResults - } - var _4: Api.MessageMedia? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaPoll(Cons_messageMediaPoll(flags: _1!, poll: _2!, results: _3!, attachedMedia: _4)) - } - else { - return nil - } - } - public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() var _2: Api.Peer? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Peer } var _3: Int32? _3 = reader.readInt32() - var _4: Api.StoryItem? - if Int(_1!) & Int(1 << 0) != 0 { + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGeoProximityReached(Cons_messageActionGeoProximityReached(fromId: _1!, toId: _2!, distance: _3!)) + } + else { + return nil + } + } + public static func parse_messageActionGiftCode(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StoryItem + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseString(reader) + } + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = reader.readInt64() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + var _9: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.MessageAction.messageActionGiftCode(Cons_messageActionGiftCode(flags: _1!, boostPeer: _2, days: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8, message: _9)) + } + else { + return nil + } + } + public static func parse_messageActionGiftPremium(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) + } + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt64() + } + var _7: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageMedia.messageMediaStory(Cons_messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4)) + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageAction.messageActionGiftPremium(Cons_messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, days: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7)) } else { return nil } } - public static func parse_messageMediaToDo(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionGiftStars(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.TodoList? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TodoList + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) } - var _3: [Api.TodoCompletion]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoCompletion.self) - } + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt64() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageMedia.messageMediaToDo(Cons_messageMediaToDo(flags: _1!, todo: _2!, completions: _3)) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageAction.messageActionGiftStars(Cons_messageActionGiftStars(flags: _1!, currency: _2!, amount: _3!, stars: _4!, cryptoCurrency: _5, cryptoAmount: _6, transactionId: _7)) } else { return nil } } - public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? { - return Api.MessageMedia.messageMediaUnsupported - } - public static func parse_messageMediaVenue(_ reader: BufferReader) -> MessageMedia? { - var _1: Api.GeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } + public static func parse_messageActionGiftTon(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() var _4: String? _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() var _6: String? - _6 = parseString(reader) + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MessageMedia.messageMediaVenue(Cons_messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!)) + return Api.MessageAction.messageActionGiftTon(Cons_messageActionGiftTon(flags: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, transactionId: _6)) } else { return nil } } - public static func parse_messageMediaVideoStream(_ reader: BufferReader) -> MessageMedia? { + public static func parse_messageActionGiveawayLaunch(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionGiveawayLaunch(Cons_messageActionGiveawayLaunch(flags: _1!, stars: _2)) + } + else { + return nil + } + } + public static func parse_messageActionGiveawayResults(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGiveawayResults(Cons_messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!)) + } + else { + return nil + } + } + public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() var _2: Api.InputGroupCall? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageMedia.messageMediaVideoStream(Cons_messageMediaVideoStream(flags: _1!, call: _2!)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGroupCall(Cons_messageActionGroupCall(flags: _1!, call: _2!, duration: _3)) } else { return nil } } - public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.WebPage? + public static func parse_messageActionGroupCallScheduled(_ reader: BufferReader) -> MessageAction? { + var _1: Api.InputGroupCall? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WebPage + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionGroupCallScheduled(Cons_messageActionGroupCallScheduled(call: _1!, scheduleDate: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionHistoryClear(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionHistoryClear + } + public static func parse_messageActionInviteToGroupCall(_ reader: BufferReader) -> MessageAction? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.MessageMedia.messageMediaWebPage(Cons_messageMediaWebPage(flags: _1!, webpage: _2!)) + return Api.MessageAction.messageActionInviteToGroupCall(Cons_messageActionInviteToGroupCall(call: _1!, users: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionManagedBotCreated(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionManagedBotCreated(Cons_messageActionManagedBotCreated(botId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionNewCreatorPending(_ reader: BufferReader) -> MessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionNewCreatorPending(Cons_messageActionNewCreatorPending(newCreatorId: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionNoForwardsRequest(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + var _3: Api.Bool? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionNoForwardsRequest(Cons_messageActionNoForwardsRequest(flags: _1!, prevValue: _2!, newValue: _3!)) + } + else { + return nil + } + } + public static func parse_messageActionNoForwardsToggle(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Bool? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Bool + } + var _2: Api.Bool? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionNoForwardsToggle(Cons_messageActionNoForwardsToggle(prevValue: _1!, newValue: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionPaidMessagesPrice(Cons_messageActionPaidMessagesPrice(flags: _1!, stars: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionPaidMessagesRefunded(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionPaidMessagesRefunded(Cons_messageActionPaidMessagesRefunded(count: _1!, stars: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionPaymentRefunded(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseBytes(reader) + } + var _6: Api.PaymentCharge? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.PaymentCharge + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageAction.messageActionPaymentRefunded(Cons_messageActionPaymentRefunded(flags: _1!, peer: _2!, currency: _3!, totalAmount: _4!, payload: _5, charge: _6!)) + } + else { + return nil + } + } + public static func parse_messageActionPaymentSent(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionPaymentSent(Cons_messageActionPaymentSent(flags: _1!, currency: _2!, totalAmount: _3!, invoiceSlug: _4, subscriptionUntilDate: _5)) + } + else { + return nil + } + } + public static func parse_messageActionPaymentSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Api.PaymentRequestedInfo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = parseString(reader) + } + var _7: Api.PaymentCharge? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PaymentCharge + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.MessageAction.messageActionPaymentSentMe(Cons_messageActionPaymentSentMe(flags: _1!, currency: _2!, totalAmount: _3!, payload: _4!, info: _5, shippingOptionId: _6, charge: _7!, subscriptionUntilDate: _8)) + } + else { + return nil + } + } + public static func parse_messageActionPhoneCall(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.PhoneCallDiscardReason? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason + } + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionPhoneCall(Cons_messageActionPhoneCall(flags: _1!, callId: _2!, reason: _3, duration: _4)) + } + else { + return nil + } + } + public static func parse_messageActionPinMessage(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionPinMessage + } + public static func parse_messageActionPollAppendAnswer(_ reader: BufferReader) -> MessageAction? { + var _1: Api.PollAnswer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PollAnswer + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionPollAppendAnswer(Cons_messageActionPollAppendAnswer(answer: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionPollDeleteAnswer(_ reader: BufferReader) -> MessageAction? { + var _1: Api.PollAnswer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PollAnswer + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionPollDeleteAnswer(Cons_messageActionPollDeleteAnswer(answer: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionPrizeStars(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionPrizeStars(Cons_messageActionPrizeStars(flags: _1!, stars: _2!, transactionId: _3!, boostPeer: _4!, giveawayMsgId: _5!)) + } + else { + return nil + } + } + public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionRequestedPeer(Cons_messageActionRequestedPeer(buttonId: _1!, peers: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionRequestedPeerSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.RequestedPeer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequestedPeer.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionRequestedPeerSentMe(Cons_messageActionRequestedPeerSentMe(buttonId: _1!, peers: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionScreenshotTaken(_ reader: BufferReader) -> MessageAction? { + return Api.MessageAction.messageActionScreenshotTaken + } + public static func parse_messageActionSecureValuesSent(_ reader: BufferReader) -> MessageAction? { + var _1: [Api.SecureValueType]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueType.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSecureValuesSent(Cons_messageActionSecureValuesSent(types: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionSecureValuesSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: [Api.SecureValue]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + var _2: Api.SecureCredentialsEncrypted? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SecureCredentialsEncrypted + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionSecureValuesSentMe(Cons_messageActionSecureValuesSentMe(values: _1!, credentials: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionSetChatTheme(_ reader: BufferReader) -> MessageAction? { + var _1: Api.ChatTheme? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChatTheme + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSetChatTheme(Cons_messageActionSetChatTheme(theme: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.WallPaper? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionSetChatWallPaper(Cons_messageActionSetChatWallPaper(flags: _1!, wallpaper: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionSetMessagesTTL(Cons_messageActionSetMessagesTTL(flags: _1!, period: _2!, autoSettingFrom: _3)) + } + else { + return nil + } + } + public static func parse_messageActionStarGift(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _4 = reader.readInt64() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _5 = reader.readInt32() + } + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _6 = reader.readInt64() + } + var _7: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _8: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + _9 = reader.readInt64() + } + var _10: String? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + _10 = parseString(reader) + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _11 = reader.readInt32() + } + var _12: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 19) != 0 { + _13 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 19) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.MessageAction.messageActionStarGift(Cons_messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6, fromId: _7, peer: _8, savedId: _9, prepaidUpgradeHash: _10, giftMsgId: _11, toId: _12, giftNum: _13)) + } + else { + return nil + } + } + public static func parse_messageActionStarGiftPurchaseOffer(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionStarGiftPurchaseOffer(Cons_messageActionStarGiftPurchaseOffer(flags: _1!, gift: _2!, price: _3!, expiresAt: _4!)) + } + else { + return nil + } + } + public static func parse_messageActionStarGiftPurchaseOfferDeclined(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionStarGiftPurchaseOfferDeclined(Cons_messageActionStarGiftPurchaseOfferDeclined(flags: _1!, gift: _2!, price: _3!)) + } + else { + return nil + } + } + public static func parse_messageActionStarGiftUnique(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarGift? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = reader.readInt32() + } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _4 = reader.readInt64() + } + var _5: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _6: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _7: Int64? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _7 = reader.readInt64() + } + var _8: Api.StarsAmount? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + } + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + _9 = reader.readInt32() + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _10 = reader.readInt32() + } + var _11: Int64? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + _11 = reader.readInt64() + } + var _12: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _12 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.MessageAction.messageActionStarGiftUnique(Cons_messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11, canCraftAt: _12)) + } + else { + return nil + } + } + public static func parse_messageActionSuggestBirthday(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Birthday? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Birthday + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestBirthday(Cons_messageActionSuggestBirthday(birthday: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? { + var _1: Api.Photo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Photo + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestProfilePhoto(Cons_messageActionSuggestProfilePhoto(photo: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionSuggestedPostApproval(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _2 = parseString(reader) + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = reader.readInt32() + } + var _4: Api.StarsAmount? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionSuggestedPostApproval(Cons_messageActionSuggestedPostApproval(flags: _1!, rejectComment: _2, scheduleDate: _3, price: _4)) + } + else { + return nil + } + } + public static func parse_messageActionSuggestedPostRefund(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestedPostRefund(Cons_messageActionSuggestedPostRefund(flags: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionSuggestedPostSuccess(_ reader: BufferReader) -> MessageAction? { + var _1: Api.StarsAmount? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestedPostSuccess(Cons_messageActionSuggestedPostSuccess(price: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? { + var _1: [Api.TodoItem]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionTodoAppendTasks(Cons_messageActionTodoAppendTasks(list: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionTodoCompletions(_ reader: BufferReader) -> MessageAction? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionTodoCompletions(Cons_messageActionTodoCompletions(completed: _1!, incompleted: _2!)) + } + else { + return nil + } + } + public static func parse_messageActionTopicCreate(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageAction.messageActionTopicCreate(Cons_messageActionTopicCreate(flags: _1!, title: _2!, iconColor: _3!, iconEmojiId: _4)) + } + else { + return nil + } + } + public static func parse_messageActionTopicEdit(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: Int64? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt64() + } + var _4: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _5: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionTopicEdit(Cons_messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5)) + } + else { + return nil + } + } + public static func parse_messageActionWebViewDataSent(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionWebViewDataSent(Cons_messageActionWebViewDataSent(text: _1!)) + } + else { + return nil + } + } + public static func parse_messageActionWebViewDataSentMe(_ reader: BufferReader) -> MessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageAction.messageActionWebViewDataSentMe(Cons_messageActionWebViewDataSentMe(text: _1!, data: _2!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api17.swift b/submodules/TelegramApi/Sources/Api17.swift index cde9dbb53c..bf75b3d074 100644 --- a/submodules/TelegramApi/Sources/Api17.swift +++ b/submodules/TelegramApi/Sources/Api17.swift @@ -1,44 +1,2173 @@ public extension Api { - enum MessagePeerReaction: TypeConstructorDescription { - public class Cons_messagePeerReaction: TypeConstructorDescription { - public var flags: Int32 - public var peerId: Api.Peer - public var date: Int32 - public var reaction: Api.Reaction - public init(flags: Int32, peerId: Api.Peer, date: Int32, reaction: Api.Reaction) { - self.flags = flags - self.peerId = peerId - self.date = date - self.reaction = reaction + indirect enum MessageEntity: TypeConstructorDescription { + public class Cons_inputMessageEntityMentionName: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var userId: Api.InputUser + public init(offset: Int32, length: Int32, userId: Api.InputUser) { + self.offset = offset + self.length = length + self.userId = userId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagePeerReaction", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) + return ("inputMessageEntityMentionName", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("userId", ConstructorParameterDescription(self.userId))]) } } - case messagePeerReaction(Cons_messagePeerReaction) + public class Cons_messageEntityBankCard: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityBankCard", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityBlockquote: TypeConstructorDescription { + public var flags: Int32 + public var offset: Int32 + public var length: Int32 + public init(flags: Int32, offset: Int32, length: Int32) { + self.flags = flags + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityBlockquote", [("flags", ConstructorParameterDescription(self.flags)), ("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityBold: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityBold", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityBotCommand: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityBotCommand", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityCashtag: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityCashtag", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityCode: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityCode", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityCustomEmoji: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var documentId: Int64 + public init(offset: Int32, length: Int32, documentId: Int64) { + self.offset = offset + self.length = length + self.documentId = documentId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityCustomEmoji", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("documentId", ConstructorParameterDescription(self.documentId))]) + } + } + public class Cons_messageEntityDiffDelete: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityDiffDelete", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityDiffInsert: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityDiffInsert", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityDiffReplace: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var oldText: String + public init(offset: Int32, length: Int32, oldText: String) { + self.offset = offset + self.length = length + self.oldText = oldText + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityDiffReplace", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("oldText", ConstructorParameterDescription(self.oldText))]) + } + } + public class Cons_messageEntityEmail: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityEmail", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityFormattedDate: TypeConstructorDescription { + public var flags: Int32 + public var offset: Int32 + public var length: Int32 + public var date: Int32 + public init(flags: Int32, offset: Int32, length: Int32, date: Int32) { + self.flags = flags + self.offset = offset + self.length = length + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityFormattedDate", [("flags", ConstructorParameterDescription(self.flags)), ("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_messageEntityHashtag: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityHashtag", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityItalic: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityItalic", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityMention: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityMention", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityMentionName: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var userId: Int64 + public init(offset: Int32, length: Int32, userId: Int64) { + self.offset = offset + self.length = length + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityMentionName", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_messageEntityPhone: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityPhone", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityPre: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var language: String + public init(offset: Int32, length: Int32, language: String) { + self.offset = offset + self.length = length + self.language = language + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityPre", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("language", ConstructorParameterDescription(self.language))]) + } + } + public class Cons_messageEntitySpoiler: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntitySpoiler", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityStrike: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityStrike", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityTextUrl: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public var url: String + public init(offset: Int32, length: Int32, url: String) { + self.offset = offset + self.length = length + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityTextUrl", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length)), ("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_messageEntityUnderline: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityUnderline", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityUnknown: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityUnknown", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_messageEntityUrl: TypeConstructorDescription { + public var offset: Int32 + public var length: Int32 + public init(offset: Int32, length: Int32) { + self.offset = offset + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEntityUrl", [("offset", ConstructorParameterDescription(self.offset)), ("length", ConstructorParameterDescription(self.length))]) + } + } + case inputMessageEntityMentionName(Cons_inputMessageEntityMentionName) + case messageEntityBankCard(Cons_messageEntityBankCard) + case messageEntityBlockquote(Cons_messageEntityBlockquote) + case messageEntityBold(Cons_messageEntityBold) + case messageEntityBotCommand(Cons_messageEntityBotCommand) + case messageEntityCashtag(Cons_messageEntityCashtag) + case messageEntityCode(Cons_messageEntityCode) + case messageEntityCustomEmoji(Cons_messageEntityCustomEmoji) + case messageEntityDiffDelete(Cons_messageEntityDiffDelete) + case messageEntityDiffInsert(Cons_messageEntityDiffInsert) + case messageEntityDiffReplace(Cons_messageEntityDiffReplace) + case messageEntityEmail(Cons_messageEntityEmail) + case messageEntityFormattedDate(Cons_messageEntityFormattedDate) + case messageEntityHashtag(Cons_messageEntityHashtag) + case messageEntityItalic(Cons_messageEntityItalic) + case messageEntityMention(Cons_messageEntityMention) + case messageEntityMentionName(Cons_messageEntityMentionName) + case messageEntityPhone(Cons_messageEntityPhone) + case messageEntityPre(Cons_messageEntityPre) + case messageEntitySpoiler(Cons_messageEntitySpoiler) + case messageEntityStrike(Cons_messageEntityStrike) + case messageEntityTextUrl(Cons_messageEntityTextUrl) + case messageEntityUnderline(Cons_messageEntityUnderline) + case messageEntityUnknown(Cons_messageEntityUnknown) + case messageEntityUrl(Cons_messageEntityUrl) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messagePeerReaction(let _data): + case .inputMessageEntityMentionName(let _data): if boxed { - buffer.appendInt32(-1938180548) + buffer.appendInt32(546203849) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + _data.userId.serialize(buffer, true) + break + case .messageEntityBankCard(let _data): + if boxed { + buffer.appendInt32(1981704948) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityBlockquote(let _data): + if boxed { + buffer.appendInt32(-238245204) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peerId.serialize(buffer, true) + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityBold(let _data): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let _data): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityCashtag(let _data): + if boxed { + buffer.appendInt32(1280209983) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let _data): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityCustomEmoji(let _data): + if boxed { + buffer.appendInt32(-925956616) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + break + case .messageEntityDiffDelete(let _data): + if boxed { + buffer.appendInt32(106086853) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityDiffInsert(let _data): + if boxed { + buffer.appendInt32(1903653142) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityDiffReplace(let _data): + if boxed { + buffer.appendInt32(-960371289) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + serializeString(_data.oldText, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let _data): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityFormattedDate(let _data): + if boxed { + buffer.appendInt32(-1874147385) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.reaction.serialize(buffer, true) + break + case .messageEntityHashtag(let _data): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let _data): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let _data): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityMentionName(let _data): + if boxed { + buffer.appendInt32(-595914432) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .messageEntityPhone(let _data): + if boxed { + buffer.appendInt32(-1687559349) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let _data): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + serializeString(_data.language, buffer: buffer, boxed: false) + break + case .messageEntitySpoiler(let _data): + if boxed { + buffer.appendInt32(852137487) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityStrike(let _data): + if boxed { + buffer.appendInt32(-1090087980) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let _data): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + break + case .messageEntityUnderline(let _data): + if boxed { + buffer.appendInt32(-1672577397) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let _data): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let _data): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(_data.offset, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .messagePeerReaction(let _data): - return ("messagePeerReaction", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) + case .inputMessageEntityMentionName(let _data): + return ("inputMessageEntityMentionName", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .messageEntityBankCard(let _data): + return ("messageEntityBankCard", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityBlockquote(let _data): + return ("messageEntityBlockquote", [("flags", ConstructorParameterDescription(_data.flags)), ("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityBold(let _data): + return ("messageEntityBold", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityBotCommand(let _data): + return ("messageEntityBotCommand", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityCashtag(let _data): + return ("messageEntityCashtag", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityCode(let _data): + return ("messageEntityCode", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityCustomEmoji(let _data): + return ("messageEntityCustomEmoji", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("documentId", ConstructorParameterDescription(_data.documentId))]) + case .messageEntityDiffDelete(let _data): + return ("messageEntityDiffDelete", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityDiffInsert(let _data): + return ("messageEntityDiffInsert", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityDiffReplace(let _data): + return ("messageEntityDiffReplace", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("oldText", ConstructorParameterDescription(_data.oldText))]) + case .messageEntityEmail(let _data): + return ("messageEntityEmail", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityFormattedDate(let _data): + return ("messageEntityFormattedDate", [("flags", ConstructorParameterDescription(_data.flags)), ("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("date", ConstructorParameterDescription(_data.date))]) + case .messageEntityHashtag(let _data): + return ("messageEntityHashtag", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityItalic(let _data): + return ("messageEntityItalic", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityMention(let _data): + return ("messageEntityMention", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityMentionName(let _data): + return ("messageEntityMentionName", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .messageEntityPhone(let _data): + return ("messageEntityPhone", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityPre(let _data): + return ("messageEntityPre", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("language", ConstructorParameterDescription(_data.language))]) + case .messageEntitySpoiler(let _data): + return ("messageEntitySpoiler", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityStrike(let _data): + return ("messageEntityStrike", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityTextUrl(let _data): + return ("messageEntityTextUrl", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length)), ("url", ConstructorParameterDescription(_data.url))]) + case .messageEntityUnderline(let _data): + return ("messageEntityUnderline", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityUnknown(let _data): + return ("messageEntityUnknown", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) + case .messageEntityUrl(let _data): + return ("messageEntityUrl", [("offset", ConstructorParameterDescription(_data.offset)), ("length", ConstructorParameterDescription(_data.length))]) } } - public static func parse_messagePeerReaction(_ reader: BufferReader) -> MessagePeerReaction? { + public static func parse_inputMessageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.InputUser? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputUser + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.inputMessageEntityMentionName(Cons_inputMessageEntityMentionName(offset: _1!, length: _2!, userId: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityBankCard(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityBankCard(Cons_messageEntityBankCard(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityBlockquote(Cons_messageEntityBlockquote(flags: _1!, offset: _2!, length: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityBold(Cons_messageEntityBold(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityBotCommand(Cons_messageEntityBotCommand(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityCashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityCashtag(Cons_messageEntityCashtag(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityCode(Cons_messageEntityCode(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityCustomEmoji(Cons_messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityDiffDelete(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityDiffDelete(Cons_messageEntityDiffDelete(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityDiffInsert(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityDiffInsert(Cons_messageEntityDiffInsert(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityDiffReplace(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityDiffReplace(Cons_messageEntityDiffReplace(offset: _1!, length: _2!, oldText: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityEmail(Cons_messageEntityEmail(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityFormattedDate(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageEntity.messageEntityFormattedDate(Cons_messageEntityFormattedDate(flags: _1!, offset: _2!, length: _3!, date: _4!)) + } + else { + return nil + } + } + public static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityHashtag(Cons_messageEntityHashtag(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityItalic(Cons_messageEntityItalic(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityMention(Cons_messageEntityMention(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityMentionName(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityMentionName(Cons_messageEntityMentionName(offset: _1!, length: _2!, userId: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityPhone(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityPhone(Cons_messageEntityPhone(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityPre(Cons_messageEntityPre(offset: _1!, length: _2!, language: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntitySpoiler(Cons_messageEntitySpoiler(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityStrike(Cons_messageEntityStrike(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageEntity.messageEntityTextUrl(Cons_messageEntityTextUrl(offset: _1!, length: _2!, url: _3!)) + } + else { + return nil + } + } + public static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUnderline(Cons_messageEntityUnderline(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUnknown(Cons_messageEntityUnknown(offset: _1!, length: _2!)) + } + else { + return nil + } + } + public static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageEntity.messageEntityUrl(Cons_messageEntityUrl(offset: _1!, length: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum MessageExtendedMedia: TypeConstructorDescription { + public class Cons_messageExtendedMedia: TypeConstructorDescription { + public var media: Api.MessageMedia + public init(media: Api.MessageMedia) { + self.media = media + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageExtendedMedia", [("media", ConstructorParameterDescription(self.media))]) + } + } + public class Cons_messageExtendedMediaPreview: TypeConstructorDescription { + public var flags: Int32 + public var w: Int32? + public var h: Int32? + public var thumb: Api.PhotoSize? + public var videoDuration: Int32? + public init(flags: Int32, w: Int32?, h: Int32?, thumb: Api.PhotoSize?, videoDuration: Int32?) { + self.flags = flags + self.w = w + self.h = h + self.thumb = thumb + self.videoDuration = videoDuration + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageExtendedMediaPreview", [("flags", ConstructorParameterDescription(self.flags)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("thumb", ConstructorParameterDescription(self.thumb)), ("videoDuration", ConstructorParameterDescription(self.videoDuration))]) + } + } + case messageExtendedMedia(Cons_messageExtendedMedia) + case messageExtendedMediaPreview(Cons_messageExtendedMediaPreview) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageExtendedMedia(let _data): + if boxed { + buffer.appendInt32(-297296796) + } + _data.media.serialize(buffer, true) + break + case .messageExtendedMediaPreview(let _data): + if boxed { + buffer.appendInt32(-1386050360) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.w!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.h!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.thumb!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.videoDuration!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageExtendedMedia(let _data): + return ("messageExtendedMedia", [("media", ConstructorParameterDescription(_data.media))]) + case .messageExtendedMediaPreview(let _data): + return ("messageExtendedMediaPreview", [("flags", ConstructorParameterDescription(_data.flags)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("thumb", ConstructorParameterDescription(_data.thumb)), ("videoDuration", ConstructorParameterDescription(_data.videoDuration))]) + } + } + + public static func parse_messageExtendedMedia(_ reader: BufferReader) -> MessageExtendedMedia? { + var _1: Api.MessageMedia? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageExtendedMedia.messageExtendedMedia(Cons_messageExtendedMedia(media: _1!)) + } + else { + return nil + } + } + public static func parse_messageExtendedMediaPreview(_ reader: BufferReader) -> MessageExtendedMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Api.PhotoSize? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.PhotoSize + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageExtendedMedia.messageExtendedMediaPreview(Cons_messageExtendedMediaPreview(flags: _1!, w: _2, h: _3, thumb: _4, videoDuration: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessageFwdHeader: TypeConstructorDescription { + public class Cons_messageFwdHeader: TypeConstructorDescription { + public var flags: Int32 + public var fromId: Api.Peer? + public var fromName: String? + public var date: Int32 + public var channelPost: Int32? + public var postAuthor: String? + public var savedFromPeer: Api.Peer? + public var savedFromMsgId: Int32? + public var savedFromId: Api.Peer? + public var savedFromName: String? + public var savedDate: Int32? + public var psaType: String? + public init(flags: Int32, fromId: Api.Peer?, fromName: String?, date: Int32, channelPost: Int32?, postAuthor: String?, savedFromPeer: Api.Peer?, savedFromMsgId: Int32?, savedFromId: Api.Peer?, savedFromName: String?, savedDate: Int32?, psaType: String?) { + self.flags = flags + self.fromId = fromId + self.fromName = fromName + self.date = date + self.channelPost = channelPost + self.postAuthor = postAuthor + self.savedFromPeer = savedFromPeer + self.savedFromMsgId = savedFromMsgId + self.savedFromId = savedFromId + self.savedFromName = savedFromName + self.savedDate = savedDate + self.psaType = psaType + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageFwdHeader", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fromName", ConstructorParameterDescription(self.fromName)), ("date", ConstructorParameterDescription(self.date)), ("channelPost", ConstructorParameterDescription(self.channelPost)), ("postAuthor", ConstructorParameterDescription(self.postAuthor)), ("savedFromPeer", ConstructorParameterDescription(self.savedFromPeer)), ("savedFromMsgId", ConstructorParameterDescription(self.savedFromMsgId)), ("savedFromId", ConstructorParameterDescription(self.savedFromId)), ("savedFromName", ConstructorParameterDescription(self.savedFromName)), ("savedDate", ConstructorParameterDescription(self.savedDate)), ("psaType", ConstructorParameterDescription(self.psaType))]) + } + } + case messageFwdHeader(Cons_messageFwdHeader) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageFwdHeader(let _data): + if boxed { + buffer.appendInt32(1313731771) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.fromName!, buffer: buffer, boxed: false) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.channelPost!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.postAuthor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.savedFromPeer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.savedFromMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.savedFromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + serializeString(_data.savedFromName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.savedDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeString(_data.psaType!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageFwdHeader(let _data): + return ("messageFwdHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fromName", ConstructorParameterDescription(_data.fromName)), ("date", ConstructorParameterDescription(_data.date)), ("channelPost", ConstructorParameterDescription(_data.channelPost)), ("postAuthor", ConstructorParameterDescription(_data.postAuthor)), ("savedFromPeer", ConstructorParameterDescription(_data.savedFromPeer)), ("savedFromMsgId", ConstructorParameterDescription(_data.savedFromMsgId)), ("savedFromId", ConstructorParameterDescription(_data.savedFromId)), ("savedFromName", ConstructorParameterDescription(_data.savedFromName)), ("savedDate", ConstructorParameterDescription(_data.savedDate)), ("psaType", ConstructorParameterDescription(_data.psaType))]) + } + } + + public static func parse_messageFwdHeader(_ reader: BufferReader) -> MessageFwdHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _3 = parseString(reader) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = reader.readInt32() + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _6 = parseString(reader) + } + var _7: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt32() + } + var _9: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _10: String? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + _10 = parseString(reader) + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _11 = reader.readInt32() + } + var _12: String? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _12 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.MessageFwdHeader.messageFwdHeader(Cons_messageFwdHeader(flags: _1!, fromId: _2, fromName: _3, date: _4!, channelPost: _5, postAuthor: _6, savedFromPeer: _7, savedFromMsgId: _8, savedFromId: _9, savedFromName: _10, savedDate: _11, psaType: _12)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum MessageMedia: TypeConstructorDescription { + public class Cons_messageMediaContact: TypeConstructorDescription { + public var phoneNumber: String + public var firstName: String + public var lastName: String + public var vcard: String + public var userId: Int64 + public init(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64) { + self.phoneNumber = phoneNumber + self.firstName = firstName + self.lastName = lastName + self.vcard = vcard + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaContact", [("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("vcard", ConstructorParameterDescription(self.vcard)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_messageMediaDice: TypeConstructorDescription { + public var flags: Int32 + public var value: Int32 + public var emoticon: String + public var gameOutcome: Api.messages.EmojiGameOutcome? + public init(flags: Int32, value: Int32, emoticon: String, gameOutcome: Api.messages.EmojiGameOutcome?) { + self.flags = flags + self.value = value + self.emoticon = emoticon + self.gameOutcome = gameOutcome + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaDice", [("flags", ConstructorParameterDescription(self.flags)), ("value", ConstructorParameterDescription(self.value)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("gameOutcome", ConstructorParameterDescription(self.gameOutcome))]) + } + } + public class Cons_messageMediaDocument: TypeConstructorDescription { + public var flags: Int32 + public var document: Api.Document? + public var altDocuments: [Api.Document]? + public var videoCover: Api.Photo? + public var videoTimestamp: Int32? + public var ttlSeconds: Int32? + public init(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, videoCover: Api.Photo?, videoTimestamp: Int32?, ttlSeconds: Int32?) { + self.flags = flags + self.document = document + self.altDocuments = altDocuments + self.videoCover = videoCover + self.videoTimestamp = videoTimestamp + self.ttlSeconds = ttlSeconds + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaDocument", [("flags", ConstructorParameterDescription(self.flags)), ("document", ConstructorParameterDescription(self.document)), ("altDocuments", ConstructorParameterDescription(self.altDocuments)), ("videoCover", ConstructorParameterDescription(self.videoCover)), ("videoTimestamp", ConstructorParameterDescription(self.videoTimestamp)), ("ttlSeconds", ConstructorParameterDescription(self.ttlSeconds))]) + } + } + public class Cons_messageMediaGame: TypeConstructorDescription { + public var game: Api.Game + public init(game: Api.Game) { + self.game = game + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaGame", [("game", ConstructorParameterDescription(self.game))]) + } + } + public class Cons_messageMediaGeo: TypeConstructorDescription { + public var geo: Api.GeoPoint + public init(geo: Api.GeoPoint) { + self.geo = geo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaGeo", [("geo", ConstructorParameterDescription(self.geo))]) + } + } + public class Cons_messageMediaGeoLive: TypeConstructorDescription { + public var flags: Int32 + public var geo: Api.GeoPoint + public var heading: Int32? + public var period: Int32 + public var proximityNotificationRadius: Int32? + public init(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?) { + self.flags = flags + self.geo = geo + self.heading = heading + self.period = period + self.proximityNotificationRadius = proximityNotificationRadius + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaGeoLive", [("flags", ConstructorParameterDescription(self.flags)), ("geo", ConstructorParameterDescription(self.geo)), ("heading", ConstructorParameterDescription(self.heading)), ("period", ConstructorParameterDescription(self.period)), ("proximityNotificationRadius", ConstructorParameterDescription(self.proximityNotificationRadius))]) + } + } + public class Cons_messageMediaGiveaway: TypeConstructorDescription { + public var flags: Int32 + public var channels: [Int64] + public var countriesIso2: [String]? + public var prizeDescription: String? + public var quantity: Int32 + public var months: Int32? + public var stars: Int64? + public var untilDate: Int32 + public init(flags: Int32, channels: [Int64], countriesIso2: [String]?, prizeDescription: String?, quantity: Int32, months: Int32?, stars: Int64?, untilDate: Int32) { + self.flags = flags + self.channels = channels + self.countriesIso2 = countriesIso2 + self.prizeDescription = prizeDescription + self.quantity = quantity + self.months = months + self.stars = stars + self.untilDate = untilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaGiveaway", [("flags", ConstructorParameterDescription(self.flags)), ("channels", ConstructorParameterDescription(self.channels)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("quantity", ConstructorParameterDescription(self.quantity)), ("months", ConstructorParameterDescription(self.months)), ("stars", ConstructorParameterDescription(self.stars)), ("untilDate", ConstructorParameterDescription(self.untilDate))]) + } + } + public class Cons_messageMediaGiveawayResults: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var additionalPeersCount: Int32? + public var launchMsgId: Int32 + public var winnersCount: Int32 + public var unclaimedCount: Int32 + public var winners: [Int64] + public var months: Int32? + public var stars: Int64? + public var prizeDescription: String? + public var untilDate: Int32 + public init(flags: Int32, channelId: Int64, additionalPeersCount: Int32?, launchMsgId: Int32, winnersCount: Int32, unclaimedCount: Int32, winners: [Int64], months: Int32?, stars: Int64?, prizeDescription: String?, untilDate: Int32) { + self.flags = flags + self.channelId = channelId + self.additionalPeersCount = additionalPeersCount + self.launchMsgId = launchMsgId + self.winnersCount = winnersCount + self.unclaimedCount = unclaimedCount + self.winners = winners + self.months = months + self.stars = stars + self.prizeDescription = prizeDescription + self.untilDate = untilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaGiveawayResults", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("additionalPeersCount", ConstructorParameterDescription(self.additionalPeersCount)), ("launchMsgId", ConstructorParameterDescription(self.launchMsgId)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(self.unclaimedCount)), ("winners", ConstructorParameterDescription(self.winners)), ("months", ConstructorParameterDescription(self.months)), ("stars", ConstructorParameterDescription(self.stars)), ("prizeDescription", ConstructorParameterDescription(self.prizeDescription)), ("untilDate", ConstructorParameterDescription(self.untilDate))]) + } + } + public class Cons_messageMediaInvoice: TypeConstructorDescription { + public var flags: Int32 + public var title: String + public var description: String + public var photo: Api.WebDocument? + public var receiptMsgId: Int32? + public var currency: String + public var totalAmount: Int64 + public var startParam: String + public var extendedMedia: Api.MessageExtendedMedia? + public init(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?) { + self.flags = flags + self.title = title + self.description = description + self.photo = photo + self.receiptMsgId = receiptMsgId + self.currency = currency + self.totalAmount = totalAmount + self.startParam = startParam + self.extendedMedia = extendedMedia + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaInvoice", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("receiptMsgId", ConstructorParameterDescription(self.receiptMsgId)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("startParam", ConstructorParameterDescription(self.startParam)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) + } + } + public class Cons_messageMediaPaidMedia: TypeConstructorDescription { + public var starsAmount: Int64 + public var extendedMedia: [Api.MessageExtendedMedia] + public init(starsAmount: Int64, extendedMedia: [Api.MessageExtendedMedia]) { + self.starsAmount = starsAmount + self.extendedMedia = extendedMedia + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaPaidMedia", [("starsAmount", ConstructorParameterDescription(self.starsAmount)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) + } + } + public class Cons_messageMediaPhoto: TypeConstructorDescription { + public var flags: Int32 + public var photo: Api.Photo? + public var ttlSeconds: Int32? + public var video: Api.Document? + public init(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?, video: Api.Document?) { + self.flags = flags + self.photo = photo + self.ttlSeconds = ttlSeconds + self.video = video + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaPhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photo", ConstructorParameterDescription(self.photo)), ("ttlSeconds", ConstructorParameterDescription(self.ttlSeconds)), ("video", ConstructorParameterDescription(self.video))]) + } + } + public class Cons_messageMediaPoll: TypeConstructorDescription { + public var flags: Int32 + public var poll: Api.Poll + public var results: Api.PollResults + public var attachedMedia: Api.MessageMedia? + public init(flags: Int32, poll: Api.Poll, results: Api.PollResults, attachedMedia: Api.MessageMedia?) { + self.flags = flags + self.poll = poll + self.results = results + self.attachedMedia = attachedMedia + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaPoll", [("flags", ConstructorParameterDescription(self.flags)), ("poll", ConstructorParameterDescription(self.poll)), ("results", ConstructorParameterDescription(self.results)), ("attachedMedia", ConstructorParameterDescription(self.attachedMedia))]) + } + } + public class Cons_messageMediaStory: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var id: Int32 + public var story: Api.StoryItem? + public init(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) { + self.flags = flags + self.peer = peer + self.id = id + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaStory", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("id", ConstructorParameterDescription(self.id)), ("story", ConstructorParameterDescription(self.story))]) + } + } + public class Cons_messageMediaToDo: TypeConstructorDescription { + public var flags: Int32 + public var todo: Api.TodoList + public var completions: [Api.TodoCompletion]? + public init(flags: Int32, todo: Api.TodoList, completions: [Api.TodoCompletion]?) { + self.flags = flags + self.todo = todo + self.completions = completions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaToDo", [("flags", ConstructorParameterDescription(self.flags)), ("todo", ConstructorParameterDescription(self.todo)), ("completions", ConstructorParameterDescription(self.completions))]) + } + } + public class Cons_messageMediaVenue: TypeConstructorDescription { + public var geo: Api.GeoPoint + public var title: String + public var address: String + public var provider: String + public var venueId: String + public var venueType: String + public init(geo: Api.GeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String) { + self.geo = geo + self.title = title + self.address = address + self.provider = provider + self.venueId = venueId + self.venueType = venueType + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaVenue", [("geo", ConstructorParameterDescription(self.geo)), ("title", ConstructorParameterDescription(self.title)), ("address", ConstructorParameterDescription(self.address)), ("provider", ConstructorParameterDescription(self.provider)), ("venueId", ConstructorParameterDescription(self.venueId)), ("venueType", ConstructorParameterDescription(self.venueType))]) + } + } + public class Cons_messageMediaVideoStream: TypeConstructorDescription { + public var flags: Int32 + public var call: Api.InputGroupCall + public init(flags: Int32, call: Api.InputGroupCall) { + self.flags = flags + self.call = call + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaVideoStream", [("flags", ConstructorParameterDescription(self.flags)), ("call", ConstructorParameterDescription(self.call))]) + } + } + public class Cons_messageMediaWebPage: TypeConstructorDescription { + public var flags: Int32 + public var webpage: Api.WebPage + public init(flags: Int32, webpage: Api.WebPage) { + self.flags = flags + self.webpage = webpage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageMediaWebPage", [("flags", ConstructorParameterDescription(self.flags)), ("webpage", ConstructorParameterDescription(self.webpage))]) + } + } + case messageMediaContact(Cons_messageMediaContact) + case messageMediaDice(Cons_messageMediaDice) + case messageMediaDocument(Cons_messageMediaDocument) + case messageMediaEmpty + case messageMediaGame(Cons_messageMediaGame) + case messageMediaGeo(Cons_messageMediaGeo) + case messageMediaGeoLive(Cons_messageMediaGeoLive) + case messageMediaGiveaway(Cons_messageMediaGiveaway) + case messageMediaGiveawayResults(Cons_messageMediaGiveawayResults) + case messageMediaInvoice(Cons_messageMediaInvoice) + case messageMediaPaidMedia(Cons_messageMediaPaidMedia) + case messageMediaPhoto(Cons_messageMediaPhoto) + case messageMediaPoll(Cons_messageMediaPoll) + case messageMediaStory(Cons_messageMediaStory) + case messageMediaToDo(Cons_messageMediaToDo) + case messageMediaUnsupported + case messageMediaVenue(Cons_messageMediaVenue) + case messageMediaVideoStream(Cons_messageMediaVideoStream) + case messageMediaWebPage(Cons_messageMediaWebPage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageMediaContact(let _data): + if boxed { + buffer.appendInt32(1882335561) + } + serializeString(_data.phoneNumber, buffer: buffer, boxed: false) + serializeString(_data.firstName, buffer: buffer, boxed: false) + serializeString(_data.lastName, buffer: buffer, boxed: false) + serializeString(_data.vcard, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .messageMediaDice(let _data): + if boxed { + buffer.appendInt32(147581959) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.value, buffer: buffer, boxed: false) + serializeString(_data.emoticon, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.gameOutcome!.serialize(buffer, true) + } + break + case .messageMediaDocument(let _data): + if boxed { + buffer.appendInt32(1389939929) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.document!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.altDocuments!.count)) + for item in _data.altDocuments! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.videoCover!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.videoTimestamp!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.ttlSeconds!, buffer: buffer, boxed: false) + } + break + case .messageMediaEmpty: + if boxed { + buffer.appendInt32(1038967584) + } + break + case .messageMediaGame(let _data): + if boxed { + buffer.appendInt32(-38694904) + } + _data.game.serialize(buffer, true) + break + case .messageMediaGeo(let _data): + if boxed { + buffer.appendInt32(1457575028) + } + _data.geo.serialize(buffer, true) + break + case .messageMediaGeoLive(let _data): + if boxed { + buffer.appendInt32(-1186937242) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.geo.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.heading!, buffer: buffer, boxed: false) + } + serializeInt32(_data.period, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.proximityNotificationRadius!, buffer: buffer, boxed: false) + } + break + case .messageMediaGiveaway(let _data): + if boxed { + buffer.appendInt32(-1442366485) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.channels.count)) + for item in _data.channels { + serializeInt64(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.countriesIso2!.count)) + for item in _data.countriesIso2! { + serializeString(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) + } + serializeInt32(_data.quantity, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.months!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt64(_data.stars!, buffer: buffer, boxed: false) + } + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + break + case .messageMediaGiveawayResults(let _data): + if boxed { + buffer.appendInt32(-827703647) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.additionalPeersCount!, buffer: buffer, boxed: false) + } + serializeInt32(_data.launchMsgId, buffer: buffer, boxed: false) + serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) + serializeInt32(_data.unclaimedCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.winners.count)) + for item in _data.winners { + serializeInt64(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.months!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt64(_data.stars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.prizeDescription!, buffer: buffer, boxed: false) + } + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + break + case .messageMediaInvoice(let _data): + if boxed { + buffer.appendInt32(-156940077) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.receiptMsgId!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + serializeString(_data.startParam, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.extendedMedia!.serialize(buffer, true) + } + break + case .messageMediaPaidMedia(let _data): + if boxed { + buffer.appendInt32(-1467669359) + } + serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.extendedMedia.count)) + for item in _data.extendedMedia { + item.serialize(buffer, true) + } + break + case .messageMediaPhoto(let _data): + if boxed { + buffer.appendInt32(-501814429) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.ttlSeconds!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.video!.serialize(buffer, true) + } + break + case .messageMediaPoll(let _data): + if boxed { + buffer.appendInt32(2000637542) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.poll.serialize(buffer, true) + _data.results.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.attachedMedia!.serialize(buffer, true) + } + break + case .messageMediaStory(let _data): + if boxed { + buffer.appendInt32(1758159491) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.story!.serialize(buffer, true) + } + break + case .messageMediaToDo(let _data): + if boxed { + buffer.appendInt32(-1974226924) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.todo.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.completions!.count)) + for item in _data.completions! { + item.serialize(buffer, true) + } + } + break + case .messageMediaUnsupported: + if boxed { + buffer.appendInt32(-1618676578) + } + break + case .messageMediaVenue(let _data): + if boxed { + buffer.appendInt32(784356159) + } + _data.geo.serialize(buffer, true) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.address, buffer: buffer, boxed: false) + serializeString(_data.provider, buffer: buffer, boxed: false) + serializeString(_data.venueId, buffer: buffer, boxed: false) + serializeString(_data.venueType, buffer: buffer, boxed: false) + break + case .messageMediaVideoStream(let _data): + if boxed { + buffer.appendInt32(-899896439) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.call.serialize(buffer, true) + break + case .messageMediaWebPage(let _data): + if boxed { + buffer.appendInt32(-571405253) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.webpage.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageMediaContact(let _data): + return ("messageMediaContact", [("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("vcard", ConstructorParameterDescription(_data.vcard)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .messageMediaDice(let _data): + return ("messageMediaDice", [("flags", ConstructorParameterDescription(_data.flags)), ("value", ConstructorParameterDescription(_data.value)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("gameOutcome", ConstructorParameterDescription(_data.gameOutcome))]) + case .messageMediaDocument(let _data): + return ("messageMediaDocument", [("flags", ConstructorParameterDescription(_data.flags)), ("document", ConstructorParameterDescription(_data.document)), ("altDocuments", ConstructorParameterDescription(_data.altDocuments)), ("videoCover", ConstructorParameterDescription(_data.videoCover)), ("videoTimestamp", ConstructorParameterDescription(_data.videoTimestamp)), ("ttlSeconds", ConstructorParameterDescription(_data.ttlSeconds))]) + case .messageMediaEmpty: + return ("messageMediaEmpty", []) + case .messageMediaGame(let _data): + return ("messageMediaGame", [("game", ConstructorParameterDescription(_data.game))]) + case .messageMediaGeo(let _data): + return ("messageMediaGeo", [("geo", ConstructorParameterDescription(_data.geo))]) + case .messageMediaGeoLive(let _data): + return ("messageMediaGeoLive", [("flags", ConstructorParameterDescription(_data.flags)), ("geo", ConstructorParameterDescription(_data.geo)), ("heading", ConstructorParameterDescription(_data.heading)), ("period", ConstructorParameterDescription(_data.period)), ("proximityNotificationRadius", ConstructorParameterDescription(_data.proximityNotificationRadius))]) + case .messageMediaGiveaway(let _data): + return ("messageMediaGiveaway", [("flags", ConstructorParameterDescription(_data.flags)), ("channels", ConstructorParameterDescription(_data.channels)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("months", ConstructorParameterDescription(_data.months)), ("stars", ConstructorParameterDescription(_data.stars)), ("untilDate", ConstructorParameterDescription(_data.untilDate))]) + case .messageMediaGiveawayResults(let _data): + return ("messageMediaGiveawayResults", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("additionalPeersCount", ConstructorParameterDescription(_data.additionalPeersCount)), ("launchMsgId", ConstructorParameterDescription(_data.launchMsgId)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("unclaimedCount", ConstructorParameterDescription(_data.unclaimedCount)), ("winners", ConstructorParameterDescription(_data.winners)), ("months", ConstructorParameterDescription(_data.months)), ("stars", ConstructorParameterDescription(_data.stars)), ("prizeDescription", ConstructorParameterDescription(_data.prizeDescription)), ("untilDate", ConstructorParameterDescription(_data.untilDate))]) + case .messageMediaInvoice(let _data): + return ("messageMediaInvoice", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("receiptMsgId", ConstructorParameterDescription(_data.receiptMsgId)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("startParam", ConstructorParameterDescription(_data.startParam)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) + case .messageMediaPaidMedia(let _data): + return ("messageMediaPaidMedia", [("starsAmount", ConstructorParameterDescription(_data.starsAmount)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) + case .messageMediaPhoto(let _data): + return ("messageMediaPhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photo", ConstructorParameterDescription(_data.photo)), ("ttlSeconds", ConstructorParameterDescription(_data.ttlSeconds)), ("video", ConstructorParameterDescription(_data.video))]) + case .messageMediaPoll(let _data): + return ("messageMediaPoll", [("flags", ConstructorParameterDescription(_data.flags)), ("poll", ConstructorParameterDescription(_data.poll)), ("results", ConstructorParameterDescription(_data.results)), ("attachedMedia", ConstructorParameterDescription(_data.attachedMedia))]) + case .messageMediaStory(let _data): + return ("messageMediaStory", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("id", ConstructorParameterDescription(_data.id)), ("story", ConstructorParameterDescription(_data.story))]) + case .messageMediaToDo(let _data): + return ("messageMediaToDo", [("flags", ConstructorParameterDescription(_data.flags)), ("todo", ConstructorParameterDescription(_data.todo)), ("completions", ConstructorParameterDescription(_data.completions))]) + case .messageMediaUnsupported: + return ("messageMediaUnsupported", []) + case .messageMediaVenue(let _data): + return ("messageMediaVenue", [("geo", ConstructorParameterDescription(_data.geo)), ("title", ConstructorParameterDescription(_data.title)), ("address", ConstructorParameterDescription(_data.address)), ("provider", ConstructorParameterDescription(_data.provider)), ("venueId", ConstructorParameterDescription(_data.venueId)), ("venueType", ConstructorParameterDescription(_data.venueType))]) + case .messageMediaVideoStream(let _data): + return ("messageMediaVideoStream", [("flags", ConstructorParameterDescription(_data.flags)), ("call", ConstructorParameterDescription(_data.call))]) + case .messageMediaWebPage(let _data): + return ("messageMediaWebPage", [("flags", ConstructorParameterDescription(_data.flags)), ("webpage", ConstructorParameterDescription(_data.webpage))]) + } + } + + public static func parse_messageMediaContact(_ reader: BufferReader) -> MessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageMedia.messageMediaContact(Cons_messageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, vcard: _4!, userId: _5!)) + } + else { + return nil + } + } + public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Api.messages.EmojiGameOutcome? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameOutcome + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaDice(Cons_messageMediaDice(flags: _1!, value: _2!, emoticon: _3!, gameOutcome: _4)) + } + else { + return nil + } + } + public static func parse_messageMediaDocument(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Document? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _3: [Api.Document]? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + } + var _4: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageMedia.messageMediaDocument(Cons_messageMediaDocument(flags: _1!, document: _2, altDocuments: _3, videoCover: _4, videoTimestamp: _5, ttlSeconds: _6)) + } + else { + return nil + } + } + public static func parse_messageMediaEmpty(_ reader: BufferReader) -> MessageMedia? { + return Api.MessageMedia.messageMediaEmpty + } + public static func parse_messageMediaGame(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.Game? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Game + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageMedia.messageMediaGame(Cons_messageMediaGame(game: _1!)) + } + else { + return nil + } + } + public static func parse_messageMediaGeo(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.GeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageMedia.messageMediaGeo(Cons_messageMediaGeo(geo: _1!)) + } + else { + return nil + } + } + public static func parse_messageMediaGeoLive(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.GeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageMedia.messageMediaGeoLive(Cons_messageMediaGeoLive(flags: _1!, geo: _2!, heading: _3, period: _4!, proximityNotificationRadius: _5)) + } + else { + return nil + } + } + public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _3: [String]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _4 = parseString(reader) + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _6 = reader.readInt32() + } + var _7: Int64? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _7 = reader.readInt64() + } + var _8: Int32? + _8 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.MessageMedia.messageMediaGiveaway(Cons_messageMediaGiveaway(flags: _1!, channels: _2!, countriesIso2: _3, prizeDescription: _4, quantity: _5!, months: _6, stars: _7, untilDate: _8!)) + } + else { + return nil + } + } + public static func parse_messageMediaGiveawayResults(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: [Int64]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt32() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _9 = reader.readInt64() + } + var _10: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _10 = parseString(reader) + } + var _11: Int32? + _11 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.MessageMedia.messageMediaGiveawayResults(Cons_messageMediaGiveawayResults(flags: _1!, channelId: _2!, additionalPeersCount: _3, launchMsgId: _4!, winnersCount: _5!, unclaimedCount: _6!, winners: _7!, months: _8, stars: _9, prizeDescription: _10, untilDate: _11!)) + } + else { + return nil + } + } + public static func parse_messageMediaInvoice(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = reader.readInt32() + } + var _6: String? + _6 = parseString(reader) + var _7: Int64? + _7 = reader.readInt64() + var _8: String? + _8 = parseString(reader) + var _9: Api.MessageExtendedMedia? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageExtendedMedia + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.MessageMedia.messageMediaInvoice(Cons_messageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, receiptMsgId: _5, currency: _6!, totalAmount: _7!, startParam: _8!, extendedMedia: _9)) + } + else { + return nil + } + } + public static func parse_messageMediaPaidMedia(_ reader: BufferReader) -> MessageMedia? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.MessageExtendedMedia]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaPaidMedia(Cons_messageMediaPaidMedia(starsAmount: _1!, extendedMedia: _2!)) + } + else { + return nil + } + } + public static func parse_messageMediaPhoto(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = reader.readInt32() + } + var _4: Api.Document? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Document + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaPhoto(Cons_messageMediaPhoto(flags: _1!, photo: _2, ttlSeconds: _3, video: _4)) + } + else { + return nil + } + } + public static func parse_messageMediaPoll(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Poll? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Poll + } + var _3: Api.PollResults? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PollResults + } + var _4: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageMedia.messageMediaPoll(Cons_messageMediaPoll(flags: _1!, poll: _2!, results: _3!, attachedMedia: _4)) + } + else { + return nil + } + } + public static func parse_messageMediaStory(_ reader: BufferReader) -> MessageMedia? { var _1: Int32? _1 = reader.readInt32() var _2: Api.Peer? @@ -47,1146 +2176,104 @@ public extension Api { } var _3: Int32? _3 = reader.readInt32() - var _4: Api.Reaction? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction + var _4: Api.StoryItem? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StoryItem + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.MessagePeerReaction.messagePeerReaction(Cons_messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!)) + return Api.MessageMedia.messageMediaStory(Cons_messageMediaStory(flags: _1!, peer: _2!, id: _3!, story: _4)) } else { return nil } } - } -} -public extension Api { - enum MessagePeerVote: TypeConstructorDescription { - public class Cons_messagePeerVote: TypeConstructorDescription { - public var peer: Api.Peer - public var option: Buffer - public var date: Int32 - public init(peer: Api.Peer, option: Buffer, date: Int32) { - self.peer = peer - self.option = option - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagePeerVote", [("peer", ConstructorParameterDescription(self.peer)), ("option", ConstructorParameterDescription(self.option)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_messagePeerVoteInputOption: TypeConstructorDescription { - public var peer: Api.Peer - public var date: Int32 - public init(peer: Api.Peer, date: Int32) { - self.peer = peer - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagePeerVoteInputOption", [("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_messagePeerVoteMultiple: TypeConstructorDescription { - public var peer: Api.Peer - public var options: [Buffer] - public var date: Int32 - public init(peer: Api.Peer, options: [Buffer], date: Int32) { - self.peer = peer - self.options = options - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagePeerVoteMultiple", [("peer", ConstructorParameterDescription(self.peer)), ("options", ConstructorParameterDescription(self.options)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case messagePeerVote(Cons_messagePeerVote) - case messagePeerVoteInputOption(Cons_messagePeerVoteInputOption) - case messagePeerVoteMultiple(Cons_messagePeerVoteMultiple) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messagePeerVote(let _data): - if boxed { - buffer.appendInt32(-1228133028) - } - _data.peer.serialize(buffer, true) - serializeBytes(_data.option, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .messagePeerVoteInputOption(let _data): - if boxed { - buffer.appendInt32(1959634180) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .messagePeerVoteMultiple(let _data): - if boxed { - buffer.appendInt32(1177089766) - } - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.options.count)) - for item in _data.options { - serializeBytes(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messagePeerVote(let _data): - return ("messagePeerVote", [("peer", ConstructorParameterDescription(_data.peer)), ("option", ConstructorParameterDescription(_data.option)), ("date", ConstructorParameterDescription(_data.date))]) - case .messagePeerVoteInputOption(let _data): - return ("messagePeerVoteInputOption", [("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date))]) - case .messagePeerVoteMultiple(let _data): - return ("messagePeerVoteMultiple", [("peer", ConstructorParameterDescription(_data.peer)), ("options", ConstructorParameterDescription(_data.options)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_messagePeerVote(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? + public static func parse_messageMediaToDo(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TodoList? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _2 = Api.parse(reader, signature: signature) as? Api.TodoList + } + var _3: [Api.TodoCompletion]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoCompletion.self) + } } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVote(Cons_messagePeerVote(peer: _1!, option: _2!, date: _3!)) + return Api.MessageMedia.messageMediaToDo(Cons_messageMediaToDo(flags: _1!, todo: _2!, completions: _3)) } else { return nil } } - public static func parse_messagePeerVoteInputOption(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessagePeerVote.messagePeerVoteInputOption(Cons_messagePeerVoteInputOption(peer: _1!, date: _2!)) - } - else { - return nil - } + public static func parse_messageMediaUnsupported(_ reader: BufferReader) -> MessageMedia? { + return Api.MessageMedia.messageMediaUnsupported } - public static func parse_messagePeerVoteMultiple(_ reader: BufferReader) -> MessagePeerVote? { - var _1: Api.Peer? + public static func parse_messageMediaVenue(_ reader: BufferReader) -> MessageMedia? { + var _1: Api.GeoPoint? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _3: Int32? - _3 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessagePeerVote.messagePeerVoteMultiple(Cons_messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageRange: TypeConstructorDescription { - public class Cons_messageRange: TypeConstructorDescription { - public var minId: Int32 - public var maxId: Int32 - public init(minId: Int32, maxId: Int32) { - self.minId = minId - self.maxId = maxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageRange", [("minId", ConstructorParameterDescription(self.minId)), ("maxId", ConstructorParameterDescription(self.maxId))]) - } - } - case messageRange(Cons_messageRange) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageRange(let _data): - if boxed { - buffer.appendInt32(182649427) - } - serializeInt32(_data.minId, buffer: buffer, boxed: false) - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageRange(let _data): - return ("messageRange", [("minId", ConstructorParameterDescription(_data.minId)), ("maxId", ConstructorParameterDescription(_data.maxId))]) - } - } - - public static func parse_messageRange(_ reader: BufferReader) -> MessageRange? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageRange.messageRange(Cons_messageRange(minId: _1!, maxId: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageReactions: TypeConstructorDescription { - public class Cons_messageReactions: TypeConstructorDescription { - public var flags: Int32 - public var results: [Api.ReactionCount] - public var recentReactions: [Api.MessagePeerReaction]? - public var topReactors: [Api.MessageReactor]? - public init(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?, topReactors: [Api.MessageReactor]?) { - self.flags = flags - self.results = results - self.recentReactions = recentReactions - self.topReactors = topReactors - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReactions", [("flags", ConstructorParameterDescription(self.flags)), ("results", ConstructorParameterDescription(self.results)), ("recentReactions", ConstructorParameterDescription(self.recentReactions)), ("topReactors", ConstructorParameterDescription(self.topReactors))]) - } - } - case messageReactions(Cons_messageReactions) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReactions(let _data): - if boxed { - buffer.appendInt32(171155211) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.results.count)) - for item in _data.results { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentReactions!.count)) - for item in _data.recentReactions! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topReactors!.count)) - for item in _data.topReactors! { - item.serialize(buffer, true) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageReactions(let _data): - return ("messageReactions", [("flags", ConstructorParameterDescription(_data.flags)), ("results", ConstructorParameterDescription(_data.results)), ("recentReactions", ConstructorParameterDescription(_data.recentReactions)), ("topReactors", ConstructorParameterDescription(_data.topReactors))]) - } - } - - public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.ReactionCount]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } - var _3: [Api.MessagePeerReaction]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) - } - } - var _4: [Api.MessageReactor]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageReactor.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageReactions.messageReactions(Cons_messageReactions(flags: _1!, results: _2!, recentReactions: _3, topReactors: _4)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageReactor: TypeConstructorDescription { - public class Cons_messageReactor: TypeConstructorDescription { - public var flags: Int32 - public var peerId: Api.Peer? - public var count: Int32 - public init(flags: Int32, peerId: Api.Peer?, count: Int32) { - self.flags = flags - self.peerId = peerId - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReactor", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case messageReactor(Cons_messageReactor) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReactor(let _data): - if boxed { - buffer.appendInt32(1269016922) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.peerId!.serialize(buffer, true) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageReactor(let _data): - return ("messageReactor", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_messageReactor(_ reader: BufferReader) -> MessageReactor? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.MessageReactor.messageReactor(Cons_messageReactor(flags: _1!, peerId: _2, count: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageReplies: TypeConstructorDescription { - public class Cons_messageReplies: TypeConstructorDescription { - public var flags: Int32 - public var replies: Int32 - public var repliesPts: Int32 - public var recentRepliers: [Api.Peer]? - public var channelId: Int64? - public var maxId: Int32? - public var readMaxId: Int32? - public init(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int64?, maxId: Int32?, readMaxId: Int32?) { - self.flags = flags - self.replies = replies - self.repliesPts = repliesPts - self.recentRepliers = recentRepliers - self.channelId = channelId - self.maxId = maxId - self.readMaxId = readMaxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReplies", [("flags", ConstructorParameterDescription(self.flags)), ("replies", ConstructorParameterDescription(self.replies)), ("repliesPts", ConstructorParameterDescription(self.repliesPts)), ("recentRepliers", ConstructorParameterDescription(self.recentRepliers)), ("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) - } - } - case messageReplies(Cons_messageReplies) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplies(let _data): - if boxed { - buffer.appendInt32(-2083123262) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.replies, buffer: buffer, boxed: false) - serializeInt32(_data.repliesPts, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentRepliers!.count)) - for item in _data.recentRepliers! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.channelId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.maxId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.readMaxId!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageReplies(let _data): - return ("messageReplies", [("flags", ConstructorParameterDescription(_data.flags)), ("replies", ConstructorParameterDescription(_data.replies)), ("repliesPts", ConstructorParameterDescription(_data.repliesPts)), ("recentRepliers", ConstructorParameterDescription(_data.recentRepliers)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) - } - } - - public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - } - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt64() - } - var _6: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = reader.readInt32() - } - var _7: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _7 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.MessageReplies.messageReplies(Cons_messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum MessageReplyHeader: TypeConstructorDescription { - public class Cons_messageReplyHeader: TypeConstructorDescription { - public var flags: Int32 - public var replyToMsgId: Int32? - public var replyToPeerId: Api.Peer? - public var replyFrom: Api.MessageFwdHeader? - public var replyMedia: Api.MessageMedia? - public var replyToTopId: Int32? - public var quoteText: String? - public var quoteEntities: [Api.MessageEntity]? - public var quoteOffset: Int32? - public var todoItemId: Int32? - public var pollOption: Buffer? - public init(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?, todoItemId: Int32?, pollOption: Buffer?) { - self.flags = flags - self.replyToMsgId = replyToMsgId - self.replyToPeerId = replyToPeerId - self.replyFrom = replyFrom - self.replyMedia = replyMedia - self.replyToTopId = replyToTopId - self.quoteText = quoteText - self.quoteEntities = quoteEntities - self.quoteOffset = quoteOffset - self.todoItemId = todoItemId - self.pollOption = pollOption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReplyHeader", [("flags", ConstructorParameterDescription(self.flags)), ("replyToMsgId", ConstructorParameterDescription(self.replyToMsgId)), ("replyToPeerId", ConstructorParameterDescription(self.replyToPeerId)), ("replyFrom", ConstructorParameterDescription(self.replyFrom)), ("replyMedia", ConstructorParameterDescription(self.replyMedia)), ("replyToTopId", ConstructorParameterDescription(self.replyToTopId)), ("quoteText", ConstructorParameterDescription(self.quoteText)), ("quoteEntities", ConstructorParameterDescription(self.quoteEntities)), ("quoteOffset", ConstructorParameterDescription(self.quoteOffset)), ("todoItemId", ConstructorParameterDescription(self.todoItemId)), ("pollOption", ConstructorParameterDescription(self.pollOption))]) - } - } - public class Cons_messageReplyStoryHeader: TypeConstructorDescription { - public var peer: Api.Peer - public var storyId: Int32 - public init(peer: Api.Peer, storyId: Int32) { - self.peer = peer - self.storyId = storyId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReplyStoryHeader", [("peer", ConstructorParameterDescription(self.peer)), ("storyId", ConstructorParameterDescription(self.storyId))]) - } - } - case messageReplyHeader(Cons_messageReplyHeader) - case messageReplyStoryHeader(Cons_messageReplyStoryHeader) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReplyHeader(let _data): - if boxed { - buffer.appendInt32(462937446) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.replyToMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.replyToPeerId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.replyFrom!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.replyMedia!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.replyToTopId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeString(_data.quoteText!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.quoteEntities!.count)) - for item in _data.quoteEntities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 10) != 0 { - serializeInt32(_data.quoteOffset!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt32(_data.todoItemId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - serializeBytes(_data.pollOption!, buffer: buffer, boxed: false) - } - break - case .messageReplyStoryHeader(let _data): - if boxed { - buffer.appendInt32(240843065) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.storyId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageReplyHeader(let _data): - return ("messageReplyHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("replyToMsgId", ConstructorParameterDescription(_data.replyToMsgId)), ("replyToPeerId", ConstructorParameterDescription(_data.replyToPeerId)), ("replyFrom", ConstructorParameterDescription(_data.replyFrom)), ("replyMedia", ConstructorParameterDescription(_data.replyMedia)), ("replyToTopId", ConstructorParameterDescription(_data.replyToTopId)), ("quoteText", ConstructorParameterDescription(_data.quoteText)), ("quoteEntities", ConstructorParameterDescription(_data.quoteEntities)), ("quoteOffset", ConstructorParameterDescription(_data.quoteOffset)), ("todoItemId", ConstructorParameterDescription(_data.todoItemId)), ("pollOption", ConstructorParameterDescription(_data.pollOption))]) - case .messageReplyStoryHeader(let _data): - return ("messageReplyStoryHeader", [("peer", ConstructorParameterDescription(_data.peer)), ("storyId", ConstructorParameterDescription(_data.storyId))]) - } - } - - public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _2 = reader.readInt32() - } - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _4: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } - } - var _5: Api.MessageMedia? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt32() - } - var _7: String? - if Int(_1!) & Int(1 << 6) != 0 { - _7 = parseString(reader) - } - var _8: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _9: Int32? - if Int(_1!) & Int(1 << 10) != 0 { - _9 = reader.readInt32() - } - var _10: Int32? - if Int(_1!) & Int(1 << 11) != 0 { - _10 = reader.readInt32() - } - var _11: Buffer? - if Int(_1!) & Int(1 << 12) != 0 { - _11 = parseBytes(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.MessageReplyHeader.messageReplyHeader(Cons_messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9, todoItemId: _10, pollOption: _11)) - } - else { - return nil - } - } - public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageReplyHeader.messageReplyStoryHeader(Cons_messageReplyStoryHeader(peer: _1!, storyId: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageReportOption: TypeConstructorDescription { - public class Cons_messageReportOption: TypeConstructorDescription { - public var text: String - public var option: Buffer - public init(text: String, option: Buffer) { - self.text = text - self.option = option - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReportOption", [("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option))]) - } - } - case messageReportOption(Cons_messageReportOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageReportOption(let _data): - if boxed { - buffer.appendInt32(2030298073) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeBytes(_data.option, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageReportOption(let _data): - return ("messageReportOption", [("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option))]) - } - } - - public static func parse_messageReportOption(_ reader: BufferReader) -> MessageReportOption? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MessageReportOption.messageReportOption(Cons_messageReportOption(text: _1!, option: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessageViews: TypeConstructorDescription { - public class Cons_messageViews: TypeConstructorDescription { - public var flags: Int32 - public var views: Int32? - public var forwards: Int32? - public var replies: Api.MessageReplies? - public init(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) { - self.flags = flags - self.views = views - self.forwards = forwards - self.replies = replies - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageViews", [("flags", ConstructorParameterDescription(self.flags)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("replies", ConstructorParameterDescription(self.replies))]) - } - } - case messageViews(Cons_messageViews) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageViews(let _data): - if boxed { - buffer.appendInt32(1163625789) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.views!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.forwards!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.replies!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageViews(let _data): - return ("messageViews", [("flags", ConstructorParameterDescription(_data.flags)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("replies", ConstructorParameterDescription(_data.replies))]) - } - } - - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Api.MessageReplies? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageViews.messageViews(Cons_messageViews(flags: _1!, views: _2, forwards: _3, replies: _4)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MessagesFilter: TypeConstructorDescription { - public class Cons_inputMessagesFilterPhoneCalls: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputMessagesFilterPhoneCalls", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - case inputMessagesFilterChatPhotos - case inputMessagesFilterContacts - case inputMessagesFilterDocument - case inputMessagesFilterEmpty - case inputMessagesFilterGeo - case inputMessagesFilterGif - case inputMessagesFilterMusic - case inputMessagesFilterMyMentions - case inputMessagesFilterPhoneCalls(Cons_inputMessagesFilterPhoneCalls) - case inputMessagesFilterPhotoVideo - case inputMessagesFilterPhotos - case inputMessagesFilterPinned - case inputMessagesFilterPoll - case inputMessagesFilterRoundVideo - case inputMessagesFilterRoundVoice - case inputMessagesFilterUrl - case inputMessagesFilterVideo - case inputMessagesFilterVoice - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputMessagesFilterChatPhotos: - if boxed { - buffer.appendInt32(975236280) - } - break - case .inputMessagesFilterContacts: - if boxed { - buffer.appendInt32(-530392189) - } - break - case .inputMessagesFilterDocument: - if boxed { - buffer.appendInt32(-1629621880) - } - break - case .inputMessagesFilterEmpty: - if boxed { - buffer.appendInt32(1474492012) - } - break - case .inputMessagesFilterGeo: - if boxed { - buffer.appendInt32(-419271411) - } - break - case .inputMessagesFilterGif: - if boxed { - buffer.appendInt32(-3644025) - } - break - case .inputMessagesFilterMusic: - if boxed { - buffer.appendInt32(928101534) - } - break - case .inputMessagesFilterMyMentions: - if boxed { - buffer.appendInt32(-1040652646) - } - break - case .inputMessagesFilterPhoneCalls(let _data): - if boxed { - buffer.appendInt32(-2134272152) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .inputMessagesFilterPhotoVideo: - if boxed { - buffer.appendInt32(1458172132) - } - break - case .inputMessagesFilterPhotos: - if boxed { - buffer.appendInt32(-1777752804) - } - break - case .inputMessagesFilterPinned: - if boxed { - buffer.appendInt32(464520273) - } - break - case .inputMessagesFilterPoll: - if boxed { - buffer.appendInt32(-97793782) - } - break - case .inputMessagesFilterRoundVideo: - if boxed { - buffer.appendInt32(-1253451181) - } - break - case .inputMessagesFilterRoundVoice: - if boxed { - buffer.appendInt32(2054952868) - } - break - case .inputMessagesFilterUrl: - if boxed { - buffer.appendInt32(2129714567) - } - break - case .inputMessagesFilterVideo: - if boxed { - buffer.appendInt32(-1614803355) - } - break - case .inputMessagesFilterVoice: - if boxed { - buffer.appendInt32(1358283666) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputMessagesFilterChatPhotos: - return ("inputMessagesFilterChatPhotos", []) - case .inputMessagesFilterContacts: - return ("inputMessagesFilterContacts", []) - case .inputMessagesFilterDocument: - return ("inputMessagesFilterDocument", []) - case .inputMessagesFilterEmpty: - return ("inputMessagesFilterEmpty", []) - case .inputMessagesFilterGeo: - return ("inputMessagesFilterGeo", []) - case .inputMessagesFilterGif: - return ("inputMessagesFilterGif", []) - case .inputMessagesFilterMusic: - return ("inputMessagesFilterMusic", []) - case .inputMessagesFilterMyMentions: - return ("inputMessagesFilterMyMentions", []) - case .inputMessagesFilterPhoneCalls(let _data): - return ("inputMessagesFilterPhoneCalls", [("flags", ConstructorParameterDescription(_data.flags))]) - case .inputMessagesFilterPhotoVideo: - return ("inputMessagesFilterPhotoVideo", []) - case .inputMessagesFilterPhotos: - return ("inputMessagesFilterPhotos", []) - case .inputMessagesFilterPinned: - return ("inputMessagesFilterPinned", []) - case .inputMessagesFilterPoll: - return ("inputMessagesFilterPoll", []) - case .inputMessagesFilterRoundVideo: - return ("inputMessagesFilterRoundVideo", []) - case .inputMessagesFilterRoundVoice: - return ("inputMessagesFilterRoundVoice", []) - case .inputMessagesFilterUrl: - return ("inputMessagesFilterUrl", []) - case .inputMessagesFilterVideo: - return ("inputMessagesFilterVideo", []) - case .inputMessagesFilterVoice: - return ("inputMessagesFilterVoice", []) - } - } - - public static func parse_inputMessagesFilterChatPhotos(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterChatPhotos - } - public static func parse_inputMessagesFilterContacts(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterContacts - } - public static func parse_inputMessagesFilterDocument(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterDocument - } - public static func parse_inputMessagesFilterEmpty(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterEmpty - } - public static func parse_inputMessagesFilterGeo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterGeo - } - public static func parse_inputMessagesFilterGif(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterGif - } - public static func parse_inputMessagesFilterMusic(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterMusic - } - public static func parse_inputMessagesFilterMyMentions(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterMyMentions - } - public static func parse_inputMessagesFilterPhoneCalls(_ reader: BufferReader) -> MessagesFilter? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.MessagesFilter.inputMessagesFilterPhoneCalls(Cons_inputMessagesFilterPhoneCalls(flags: _1!)) - } - else { - return nil - } - } - public static func parse_inputMessagesFilterPhotoVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPhotoVideo - } - public static func parse_inputMessagesFilterPhotos(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPhotos - } - public static func parse_inputMessagesFilterPinned(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPinned - } - public static func parse_inputMessagesFilterPoll(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterPoll - } - public static func parse_inputMessagesFilterRoundVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterRoundVideo - } - public static func parse_inputMessagesFilterRoundVoice(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterRoundVoice - } - public static func parse_inputMessagesFilterUrl(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterUrl - } - public static func parse_inputMessagesFilterVideo(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterVideo - } - public static func parse_inputMessagesFilterVoice(_ reader: BufferReader) -> MessagesFilter? { - return Api.MessagesFilter.inputMessagesFilterVoice - } - } -} -public extension Api { - enum MissingInvitee: TypeConstructorDescription { - public class Cons_missingInvitee: TypeConstructorDescription { - public var flags: Int32 - public var userId: Int64 - public init(flags: Int32, userId: Int64) { - self.flags = flags - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("missingInvitee", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - case missingInvitee(Cons_missingInvitee) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .missingInvitee(let _data): - if boxed { - buffer.appendInt32(1653379620) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .missingInvitee(let _data): - return ("missingInvitee", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId))]) - } - } - - public static func parse_missingInvitee(_ reader: BufferReader) -> MissingInvitee? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.MissingInvitee.missingInvitee(Cons_missingInvitee(flags: _1!, userId: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum MyBoost: TypeConstructorDescription { - public class Cons_myBoost: TypeConstructorDescription { - public var flags: Int32 - public var slot: Int32 - public var peer: Api.Peer? - public var date: Int32 - public var expires: Int32 - public var cooldownUntilDate: Int32? - public init(flags: Int32, slot: Int32, peer: Api.Peer?, date: Int32, expires: Int32, cooldownUntilDate: Int32?) { - self.flags = flags - self.slot = slot - self.peer = peer - self.date = date - self.expires = expires - self.cooldownUntilDate = cooldownUntilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("myBoost", [("flags", ConstructorParameterDescription(self.flags)), ("slot", ConstructorParameterDescription(self.slot)), ("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("expires", ConstructorParameterDescription(self.expires)), ("cooldownUntilDate", ConstructorParameterDescription(self.cooldownUntilDate))]) - } - } - case myBoost(Cons_myBoost) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .myBoost(let _data): - if boxed { - buffer.appendInt32(-1001897636) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.slot, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.peer!.serialize(buffer, true) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.expires, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.cooldownUntilDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .myBoost(let _data): - return ("myBoost", [("flags", ConstructorParameterDescription(_data.flags)), ("slot", ConstructorParameterDescription(_data.slot)), ("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("expires", ConstructorParameterDescription(_data.expires)), ("cooldownUntilDate", ConstructorParameterDescription(_data.cooldownUntilDate))]) - } - } - - public static func parse_myBoost(_ reader: BufferReader) -> MyBoost? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c6 = _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.MyBoost.myBoost(Cons_myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6)) + return Api.MessageMedia.messageMediaVenue(Cons_messageMediaVenue(geo: _1!, title: _2!, address: _3!, provider: _4!, venueId: _5!, venueType: _6!)) + } + else { + return nil + } + } + public static func parse_messageMediaVideoStream(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaVideoStream(Cons_messageMediaVideoStream(flags: _1!, call: _2!)) + } + else { + return nil + } + } + public static func parse_messageMediaWebPage(_ reader: BufferReader) -> MessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.WebPage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WebPage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageMedia.messageMediaWebPage(Cons_messageMediaWebPage(flags: _1!, webpage: _2!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api18.swift b/submodules/TelegramApi/Sources/Api18.swift index 4cdba8c6d6..f371ca3819 100644 --- a/submodules/TelegramApi/Sources/Api18.swift +++ b/submodules/TelegramApi/Sources/Api18.swift @@ -1,52 +1,62 @@ public extension Api { - enum NearestDc: TypeConstructorDescription { - public class Cons_nearestDc: TypeConstructorDescription { - public var country: String - public var thisDc: Int32 - public var nearestDc: Int32 - public init(country: String, thisDc: Int32, nearestDc: Int32) { - self.country = country - self.thisDc = thisDc - self.nearestDc = nearestDc + enum MessagePeerReaction: TypeConstructorDescription { + public class Cons_messagePeerReaction: TypeConstructorDescription { + public var flags: Int32 + public var peerId: Api.Peer + public var date: Int32 + public var reaction: Api.Reaction + public init(flags: Int32, peerId: Api.Peer, date: Int32, reaction: Api.Reaction) { + self.flags = flags + self.peerId = peerId + self.date = date + self.reaction = reaction } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("nearestDc", [("country", ConstructorParameterDescription(self.country)), ("thisDc", ConstructorParameterDescription(self.thisDc)), ("nearestDc", ConstructorParameterDescription(self.nearestDc))]) + return ("messagePeerReaction", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) } } - case nearestDc(Cons_nearestDc) + case messagePeerReaction(Cons_messagePeerReaction) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .nearestDc(let _data): + case .messagePeerReaction(let _data): if boxed { - buffer.appendInt32(-1910892683) + buffer.appendInt32(-1938180548) } - serializeString(_data.country, buffer: buffer, boxed: false) - serializeInt32(_data.thisDc, buffer: buffer, boxed: false) - serializeInt32(_data.nearestDc, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peerId.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.reaction.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .nearestDc(let _data): - return ("nearestDc", [("country", ConstructorParameterDescription(_data.country)), ("thisDc", ConstructorParameterDescription(_data.thisDc)), ("nearestDc", ConstructorParameterDescription(_data.nearestDc))]) + case .messagePeerReaction(let _data): + return ("messagePeerReaction", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) } } - public static func parse_nearestDc(_ reader: BufferReader) -> NearestDc? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + public static func parse_messagePeerReaction(_ reader: BufferReader) -> MessagePeerReaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } var _3: Int32? _3 = reader.readInt32() + var _4: Api.Reaction? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.NearestDc.nearestDc(Cons_nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!)) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessagePeerReaction.messagePeerReaction(Cons_messagePeerReaction(flags: _1!, peerId: _2!, date: _3!, reaction: _4!)) } else { return nil @@ -55,240 +65,74 @@ public extension Api { } } public extension Api { - enum NotificationSound: TypeConstructorDescription { - public class Cons_notificationSoundLocal: TypeConstructorDescription { - public var title: String - public var data: String - public init(title: String, data: String) { - self.title = title - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("notificationSoundLocal", [("title", ConstructorParameterDescription(self.title)), ("data", ConstructorParameterDescription(self.data))]) - } - } - public class Cons_notificationSoundRingtone: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("notificationSoundRingtone", [("id", ConstructorParameterDescription(self.id))]) - } - } - case notificationSoundDefault - case notificationSoundLocal(Cons_notificationSoundLocal) - case notificationSoundNone - case notificationSoundRingtone(Cons_notificationSoundRingtone) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .notificationSoundDefault: - if boxed { - buffer.appendInt32(-1746354498) - } - break - case .notificationSoundLocal(let _data): - if boxed { - buffer.appendInt32(-2096391452) - } - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.data, buffer: buffer, boxed: false) - break - case .notificationSoundNone: - if boxed { - buffer.appendInt32(1863070943) - } - break - case .notificationSoundRingtone(let _data): - if boxed { - buffer.appendInt32(-9666487) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .notificationSoundDefault: - return ("notificationSoundDefault", []) - case .notificationSoundLocal(let _data): - return ("notificationSoundLocal", [("title", ConstructorParameterDescription(_data.title)), ("data", ConstructorParameterDescription(_data.data))]) - case .notificationSoundNone: - return ("notificationSoundNone", []) - case .notificationSoundRingtone(let _data): - return ("notificationSoundRingtone", [("id", ConstructorParameterDescription(_data.id))]) - } - } - - public static func parse_notificationSoundDefault(_ reader: BufferReader) -> NotificationSound? { - return Api.NotificationSound.notificationSoundDefault - } - public static func parse_notificationSoundLocal(_ reader: BufferReader) -> NotificationSound? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotificationSound.notificationSoundLocal(Cons_notificationSoundLocal(title: _1!, data: _2!)) - } - else { - return nil - } - } - public static func parse_notificationSoundNone(_ reader: BufferReader) -> NotificationSound? { - return Api.NotificationSound.notificationSoundNone - } - public static func parse_notificationSoundRingtone(_ reader: BufferReader) -> NotificationSound? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.NotificationSound.notificationSoundRingtone(Cons_notificationSoundRingtone(id: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum NotifyPeer: TypeConstructorDescription { - public class Cons_notifyForumTopic: TypeConstructorDescription { + enum MessagePeerVote: TypeConstructorDescription { + public class Cons_messagePeerVote: TypeConstructorDescription { public var peer: Api.Peer - public var topMsgId: Int32 - public init(peer: Api.Peer, topMsgId: Int32) { - self.peer = peer - self.topMsgId = topMsgId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("notifyForumTopic", [("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId))]) - } - } - public class Cons_notifyPeer: TypeConstructorDescription { - public var peer: Api.Peer - public init(peer: Api.Peer) { - self.peer = peer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("notifyPeer", [("peer", ConstructorParameterDescription(self.peer))]) - } - } - case notifyBroadcasts - case notifyChats - case notifyForumTopic(Cons_notifyForumTopic) - case notifyPeer(Cons_notifyPeer) - case notifyUsers - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .notifyBroadcasts: - if boxed { - buffer.appendInt32(-703403793) - } - break - case .notifyChats: - if boxed { - buffer.appendInt32(-1073230141) - } - break - case .notifyForumTopic(let _data): - if boxed { - buffer.appendInt32(577659656) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) - break - case .notifyPeer(let _data): - if boxed { - buffer.appendInt32(-1613493288) - } - _data.peer.serialize(buffer, true) - break - case .notifyUsers: - if boxed { - buffer.appendInt32(-1261946036) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .notifyBroadcasts: - return ("notifyBroadcasts", []) - case .notifyChats: - return ("notifyChats", []) - case .notifyForumTopic(let _data): - return ("notifyForumTopic", [("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId))]) - case .notifyPeer(let _data): - return ("notifyPeer", [("peer", ConstructorParameterDescription(_data.peer))]) - case .notifyUsers: - return ("notifyUsers", []) - } - } - - public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyBroadcasts - } - public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyChats - } - public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.NotifyPeer.notifyForumTopic(Cons_notifyForumTopic(peer: _1!, topMsgId: _2!)) - } - else { - return nil - } - } - public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - if _c1 { - return Api.NotifyPeer.notifyPeer(Cons_notifyPeer(peer: _1!)) - } - else { - return nil - } - } - public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { - return Api.NotifyPeer.notifyUsers - } - } -} -public extension Api { - enum OutboxReadDate: TypeConstructorDescription { - public class Cons_outboxReadDate: TypeConstructorDescription { + public var option: Buffer public var date: Int32 - public init(date: Int32) { + public init(peer: Api.Peer, option: Buffer, date: Int32) { + self.peer = peer + self.option = option self.date = date } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("outboxReadDate", [("date", ConstructorParameterDescription(self.date))]) + return ("messagePeerVote", [("peer", ConstructorParameterDescription(self.peer)), ("option", ConstructorParameterDescription(self.option)), ("date", ConstructorParameterDescription(self.date))]) } } - case outboxReadDate(Cons_outboxReadDate) + public class Cons_messagePeerVoteInputOption: TypeConstructorDescription { + public var peer: Api.Peer + public var date: Int32 + public init(peer: Api.Peer, date: Int32) { + self.peer = peer + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messagePeerVoteInputOption", [("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_messagePeerVoteMultiple: TypeConstructorDescription { + public var peer: Api.Peer + public var options: [Buffer] + public var date: Int32 + public init(peer: Api.Peer, options: [Buffer], date: Int32) { + self.peer = peer + self.options = options + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messagePeerVoteMultiple", [("peer", ConstructorParameterDescription(self.peer)), ("options", ConstructorParameterDescription(self.options)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case messagePeerVote(Cons_messagePeerVote) + case messagePeerVoteInputOption(Cons_messagePeerVoteInputOption) + case messagePeerVoteMultiple(Cons_messagePeerVoteMultiple) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .outboxReadDate(let _data): + case .messagePeerVote(let _data): if boxed { - buffer.appendInt32(1001931436) + buffer.appendInt32(-1228133028) + } + _data.peer.serialize(buffer, true) + serializeBytes(_data.option, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .messagePeerVoteInputOption(let _data): + if boxed { + buffer.appendInt32(1959634180) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .messagePeerVoteMultiple(let _data): + if boxed { + buffer.appendInt32(1177089766) + } + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.options.count)) + for item in _data.options { + serializeBytes(item, buffer: buffer, boxed: false) } serializeInt32(_data.date, buffer: buffer, boxed: false) break @@ -297,17 +141,66 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .outboxReadDate(let _data): - return ("outboxReadDate", [("date", ConstructorParameterDescription(_data.date))]) + case .messagePeerVote(let _data): + return ("messagePeerVote", [("peer", ConstructorParameterDescription(_data.peer)), ("option", ConstructorParameterDescription(_data.option)), ("date", ConstructorParameterDescription(_data.date))]) + case .messagePeerVoteInputOption(let _data): + return ("messagePeerVoteInputOption", [("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date))]) + case .messagePeerVoteMultiple(let _data): + return ("messagePeerVoteMultiple", [("peer", ConstructorParameterDescription(_data.peer)), ("options", ConstructorParameterDescription(_data.options)), ("date", ConstructorParameterDescription(_data.date))]) } } - public static func parse_outboxReadDate(_ reader: BufferReader) -> OutboxReadDate? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_messagePeerVote(_ reader: BufferReader) -> MessagePeerVote? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil - if _c1 { - return Api.OutboxReadDate.outboxReadDate(Cons_outboxReadDate(date: _1!)) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessagePeerVote.messagePeerVote(Cons_messagePeerVote(peer: _1!, option: _2!, date: _3!)) + } + else { + return nil + } + } + public static func parse_messagePeerVoteInputOption(_ reader: BufferReader) -> MessagePeerVote? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessagePeerVote.messagePeerVoteInputOption(Cons_messagePeerVoteInputOption(peer: _1!, date: _2!)) + } + else { + return nil + } + } + public static func parse_messagePeerVoteMultiple(_ reader: BufferReader) -> MessagePeerVote? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessagePeerVote.messagePeerVoteMultiple(Cons_messagePeerVoteMultiple(peer: _1!, options: _2!, date: _3!)) } else { return nil @@ -316,94 +209,48 @@ public extension Api { } } public extension Api { - enum Page: TypeConstructorDescription { - public class Cons_page: TypeConstructorDescription { - public var flags: Int32 - public var url: String - public var blocks: [Api.PageBlock] - public var photos: [Api.Photo] - public var documents: [Api.Document] - public var views: Int32? - public init(flags: Int32, url: String, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document], views: Int32?) { - self.flags = flags - self.url = url - self.blocks = blocks - self.photos = photos - self.documents = documents - self.views = views + enum MessageRange: TypeConstructorDescription { + public class Cons_messageRange: TypeConstructorDescription { + public var minId: Int32 + public var maxId: Int32 + public init(minId: Int32, maxId: Int32) { + self.minId = minId + self.maxId = maxId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("page", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("blocks", ConstructorParameterDescription(self.blocks)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents)), ("views", ConstructorParameterDescription(self.views))]) + return ("messageRange", [("minId", ConstructorParameterDescription(self.minId)), ("maxId", ConstructorParameterDescription(self.maxId))]) } } - case page(Cons_page) + case messageRange(Cons_messageRange) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .page(let _data): + case .messageRange(let _data): if boxed { - buffer.appendInt32(-1738178803) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.photos.count)) - for item in _data.photos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.views!, buffer: buffer, boxed: false) + buffer.appendInt32(182649427) } + serializeInt32(_data.minId, buffer: buffer, boxed: false) + serializeInt32(_data.maxId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .page(let _data): - return ("page", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents)), ("views", ConstructorParameterDescription(_data.views))]) + case .messageRange(let _data): + return ("messageRange", [("minId", ConstructorParameterDescription(_data.minId)), ("maxId", ConstructorParameterDescription(_data.maxId))]) } } - public static func parse_page(_ reader: BufferReader) -> Page? { + public static func parse_messageRange(_ reader: BufferReader) -> MessageRange? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: [Api.PageBlock]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _4: [Api.Photo]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _5: [Api.Document]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _6: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _6 = reader.readInt32() - } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Page.page(Cons_page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6)) + if _c1 && _c2 { + return Api.MessageRange.messageRange(Cons_messageRange(minId: _1!, maxId: _2!)) } else { return nil @@ -412,1152 +259,934 @@ public extension Api { } } public extension Api { - indirect enum PageBlock: TypeConstructorDescription { - public class Cons_pageBlockAnchor: TypeConstructorDescription { - public var name: String - public init(name: String) { - self.name = name - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockAnchor", [("name", ConstructorParameterDescription(self.name))]) - } - } - public class Cons_pageBlockAudio: TypeConstructorDescription { - public var audioId: Int64 - public var caption: Api.PageCaption - public init(audioId: Int64, caption: Api.PageCaption) { - self.audioId = audioId - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockAudio", [("audioId", ConstructorParameterDescription(self.audioId)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockAuthorDate: TypeConstructorDescription { - public var author: Api.RichText - public var publishedDate: Int32 - public init(author: Api.RichText, publishedDate: Int32) { - self.author = author - self.publishedDate = publishedDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockAuthorDate", [("author", ConstructorParameterDescription(self.author)), ("publishedDate", ConstructorParameterDescription(self.publishedDate))]) - } - } - public class Cons_pageBlockBlockquote: TypeConstructorDescription { - public var text: Api.RichText - public var caption: Api.RichText - public init(text: Api.RichText, caption: Api.RichText) { - self.text = text - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockBlockquote", [("text", ConstructorParameterDescription(self.text)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockChannel: TypeConstructorDescription { - public var channel: Api.Chat - public init(channel: Api.Chat) { - self.channel = channel - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockChannel", [("channel", ConstructorParameterDescription(self.channel))]) - } - } - public class Cons_pageBlockCollage: TypeConstructorDescription { - public var items: [Api.PageBlock] - public var caption: Api.PageCaption - public init(items: [Api.PageBlock], caption: Api.PageCaption) { - self.items = items - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockCollage", [("items", ConstructorParameterDescription(self.items)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockCover: TypeConstructorDescription { - public var cover: Api.PageBlock - public init(cover: Api.PageBlock) { - self.cover = cover - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockCover", [("cover", ConstructorParameterDescription(self.cover))]) - } - } - public class Cons_pageBlockDetails: TypeConstructorDescription { + enum MessageReactions: TypeConstructorDescription { + public class Cons_messageReactions: TypeConstructorDescription { public var flags: Int32 - public var blocks: [Api.PageBlock] - public var title: Api.RichText - public init(flags: Int32, blocks: [Api.PageBlock], title: Api.RichText) { + public var results: [Api.ReactionCount] + public var recentReactions: [Api.MessagePeerReaction]? + public var topReactors: [Api.MessageReactor]? + public init(flags: Int32, results: [Api.ReactionCount], recentReactions: [Api.MessagePeerReaction]?, topReactors: [Api.MessageReactor]?) { self.flags = flags - self.blocks = blocks - self.title = title + self.results = results + self.recentReactions = recentReactions + self.topReactors = topReactors } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockDetails", [("flags", ConstructorParameterDescription(self.flags)), ("blocks", ConstructorParameterDescription(self.blocks)), ("title", ConstructorParameterDescription(self.title))]) + return ("messageReactions", [("flags", ConstructorParameterDescription(self.flags)), ("results", ConstructorParameterDescription(self.results)), ("recentReactions", ConstructorParameterDescription(self.recentReactions)), ("topReactors", ConstructorParameterDescription(self.topReactors))]) } } - public class Cons_pageBlockEmbed: TypeConstructorDescription { - public var flags: Int32 - public var url: String? - public var html: String? - public var posterPhotoId: Int64? - public var w: Int32? - public var h: Int32? - public var caption: Api.PageCaption - public init(flags: Int32, url: String?, html: String?, posterPhotoId: Int64?, w: Int32?, h: Int32?, caption: Api.PageCaption) { - self.flags = flags - self.url = url - self.html = html - self.posterPhotoId = posterPhotoId - self.w = w - self.h = h - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockEmbed", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("html", ConstructorParameterDescription(self.html)), ("posterPhotoId", ConstructorParameterDescription(self.posterPhotoId)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockEmbedPost: TypeConstructorDescription { - public var url: String - public var webpageId: Int64 - public var authorPhotoId: Int64 - public var author: String - public var date: Int32 - public var blocks: [Api.PageBlock] - public var caption: Api.PageCaption - public init(url: String, webpageId: Int64, authorPhotoId: Int64, author: String, date: Int32, blocks: [Api.PageBlock], caption: Api.PageCaption) { - self.url = url - self.webpageId = webpageId - self.authorPhotoId = authorPhotoId - self.author = author - self.date = date - self.blocks = blocks - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockEmbedPost", [("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId)), ("authorPhotoId", ConstructorParameterDescription(self.authorPhotoId)), ("author", ConstructorParameterDescription(self.author)), ("date", ConstructorParameterDescription(self.date)), ("blocks", ConstructorParameterDescription(self.blocks)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockFooter: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockFooter", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockHeader: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockHeader", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockKicker: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockKicker", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockList: TypeConstructorDescription { - public var items: [Api.PageListItem] - public init(items: [Api.PageListItem]) { - self.items = items - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockList", [("items", ConstructorParameterDescription(self.items))]) - } - } - public class Cons_pageBlockMap: TypeConstructorDescription { - public var geo: Api.GeoPoint - public var zoom: Int32 - public var w: Int32 - public var h: Int32 - public var caption: Api.PageCaption - public init(geo: Api.GeoPoint, zoom: Int32, w: Int32, h: Int32, caption: Api.PageCaption) { - self.geo = geo - self.zoom = zoom - self.w = w - self.h = h - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockMap", [("geo", ConstructorParameterDescription(self.geo)), ("zoom", ConstructorParameterDescription(self.zoom)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockOrderedList: TypeConstructorDescription { - public var items: [Api.PageListOrderedItem] - public init(items: [Api.PageListOrderedItem]) { - self.items = items - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockOrderedList", [("items", ConstructorParameterDescription(self.items))]) - } - } - public class Cons_pageBlockParagraph: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockParagraph", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockPhoto: TypeConstructorDescription { - public var flags: Int32 - public var photoId: Int64 - public var caption: Api.PageCaption - public var url: String? - public var webpageId: Int64? - public init(flags: Int32, photoId: Int64, caption: Api.PageCaption, url: String?, webpageId: Int64?) { - self.flags = flags - self.photoId = photoId - self.caption = caption - self.url = url - self.webpageId = webpageId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockPhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photoId", ConstructorParameterDescription(self.photoId)), ("caption", ConstructorParameterDescription(self.caption)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId))]) - } - } - public class Cons_pageBlockPreformatted: TypeConstructorDescription { - public var text: Api.RichText - public var language: String - public init(text: Api.RichText, language: String) { - self.text = text - self.language = language - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockPreformatted", [("text", ConstructorParameterDescription(self.text)), ("language", ConstructorParameterDescription(self.language))]) - } - } - public class Cons_pageBlockPullquote: TypeConstructorDescription { - public var text: Api.RichText - public var caption: Api.RichText - public init(text: Api.RichText, caption: Api.RichText) { - self.text = text - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockPullquote", [("text", ConstructorParameterDescription(self.text)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockRelatedArticles: TypeConstructorDescription { - public var title: Api.RichText - public var articles: [Api.PageRelatedArticle] - public init(title: Api.RichText, articles: [Api.PageRelatedArticle]) { - self.title = title - self.articles = articles - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockRelatedArticles", [("title", ConstructorParameterDescription(self.title)), ("articles", ConstructorParameterDescription(self.articles))]) - } - } - public class Cons_pageBlockSlideshow: TypeConstructorDescription { - public var items: [Api.PageBlock] - public var caption: Api.PageCaption - public init(items: [Api.PageBlock], caption: Api.PageCaption) { - self.items = items - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockSlideshow", [("items", ConstructorParameterDescription(self.items)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - public class Cons_pageBlockSubheader: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockSubheader", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockSubtitle: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockSubtitle", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockTable: TypeConstructorDescription { - public var flags: Int32 - public var title: Api.RichText - public var rows: [Api.PageTableRow] - public init(flags: Int32, title: Api.RichText, rows: [Api.PageTableRow]) { - self.flags = flags - self.title = title - self.rows = rows - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockTable", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("rows", ConstructorParameterDescription(self.rows))]) - } - } - public class Cons_pageBlockTitle: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockTitle", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_pageBlockVideo: TypeConstructorDescription { - public var flags: Int32 - public var videoId: Int64 - public var caption: Api.PageCaption - public init(flags: Int32, videoId: Int64, caption: Api.PageCaption) { - self.flags = flags - self.videoId = videoId - self.caption = caption - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageBlockVideo", [("flags", ConstructorParameterDescription(self.flags)), ("videoId", ConstructorParameterDescription(self.videoId)), ("caption", ConstructorParameterDescription(self.caption))]) - } - } - case pageBlockAnchor(Cons_pageBlockAnchor) - case pageBlockAudio(Cons_pageBlockAudio) - case pageBlockAuthorDate(Cons_pageBlockAuthorDate) - case pageBlockBlockquote(Cons_pageBlockBlockquote) - case pageBlockChannel(Cons_pageBlockChannel) - case pageBlockCollage(Cons_pageBlockCollage) - case pageBlockCover(Cons_pageBlockCover) - case pageBlockDetails(Cons_pageBlockDetails) - case pageBlockDivider - case pageBlockEmbed(Cons_pageBlockEmbed) - case pageBlockEmbedPost(Cons_pageBlockEmbedPost) - case pageBlockFooter(Cons_pageBlockFooter) - case pageBlockHeader(Cons_pageBlockHeader) - case pageBlockKicker(Cons_pageBlockKicker) - case pageBlockList(Cons_pageBlockList) - case pageBlockMap(Cons_pageBlockMap) - case pageBlockOrderedList(Cons_pageBlockOrderedList) - case pageBlockParagraph(Cons_pageBlockParagraph) - case pageBlockPhoto(Cons_pageBlockPhoto) - case pageBlockPreformatted(Cons_pageBlockPreformatted) - case pageBlockPullquote(Cons_pageBlockPullquote) - case pageBlockRelatedArticles(Cons_pageBlockRelatedArticles) - case pageBlockSlideshow(Cons_pageBlockSlideshow) - case pageBlockSubheader(Cons_pageBlockSubheader) - case pageBlockSubtitle(Cons_pageBlockSubtitle) - case pageBlockTable(Cons_pageBlockTable) - case pageBlockTitle(Cons_pageBlockTitle) - case pageBlockUnsupported - case pageBlockVideo(Cons_pageBlockVideo) + case messageReactions(Cons_messageReactions) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageBlockAnchor(let _data): + case .messageReactions(let _data): if boxed { - buffer.appendInt32(-837994576) - } - serializeString(_data.name, buffer: buffer, boxed: false) - break - case .pageBlockAudio(let _data): - if boxed { - buffer.appendInt32(-2143067670) - } - serializeInt64(_data.audioId, buffer: buffer, boxed: false) - _data.caption.serialize(buffer, true) - break - case .pageBlockAuthorDate(let _data): - if boxed { - buffer.appendInt32(-1162877472) - } - _data.author.serialize(buffer, true) - serializeInt32(_data.publishedDate, buffer: buffer, boxed: false) - break - case .pageBlockBlockquote(let _data): - if boxed { - buffer.appendInt32(641563686) - } - _data.text.serialize(buffer, true) - _data.caption.serialize(buffer, true) - break - case .pageBlockChannel(let _data): - if boxed { - buffer.appendInt32(-283684427) - } - _data.channel.serialize(buffer, true) - break - case .pageBlockCollage(let _data): - if boxed { - buffer.appendInt32(1705048653) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.items.count)) - for item in _data.items { - item.serialize(buffer, true) - } - _data.caption.serialize(buffer, true) - break - case .pageBlockCover(let _data): - if boxed { - buffer.appendInt32(972174080) - } - _data.cover.serialize(buffer, true) - break - case .pageBlockDetails(let _data): - if boxed { - buffer.appendInt32(1987480557) + buffer.appendInt32(171155211) } serializeInt32(_data.flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { + buffer.appendInt32(Int32(_data.results.count)) + for item in _data.results { item.serialize(buffer, true) } - _data.title.serialize(buffer, true) - break - case .pageBlockDivider: - if boxed { - buffer.appendInt32(-618614392) - } - break - case .pageBlockEmbed(let _data): - if boxed { - buffer.appendInt32(-1468953147) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.html!, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentReactions!.count)) + for item in _data.recentReactions! { + item.serialize(buffer, true) + } } if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.posterPhotoId!, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topReactors!.count)) + for item in _data.topReactors! { + item.serialize(buffer, true) + } } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.w!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.h!, buffer: buffer, boxed: false) - } - _data.caption.serialize(buffer, true) - break - case .pageBlockEmbedPost(let _data): - if boxed { - buffer.appendInt32(-229005301) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.webpageId, buffer: buffer, boxed: false) - serializeInt64(_data.authorPhotoId, buffer: buffer, boxed: false) - serializeString(_data.author, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { - item.serialize(buffer, true) - } - _data.caption.serialize(buffer, true) - break - case .pageBlockFooter(let _data): - if boxed { - buffer.appendInt32(1216809369) - } - _data.text.serialize(buffer, true) - break - case .pageBlockHeader(let _data): - if boxed { - buffer.appendInt32(-1076861716) - } - _data.text.serialize(buffer, true) - break - case .pageBlockKicker(let _data): - if boxed { - buffer.appendInt32(504660880) - } - _data.text.serialize(buffer, true) - break - case .pageBlockList(let _data): - if boxed { - buffer.appendInt32(-454524911) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.items.count)) - for item in _data.items { - item.serialize(buffer, true) - } - break - case .pageBlockMap(let _data): - if boxed { - buffer.appendInt32(-1538310410) - } - _data.geo.serialize(buffer, true) - serializeInt32(_data.zoom, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - _data.caption.serialize(buffer, true) - break - case .pageBlockOrderedList(let _data): - if boxed { - buffer.appendInt32(-1702174239) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.items.count)) - for item in _data.items { - item.serialize(buffer, true) - } - break - case .pageBlockParagraph(let _data): - if boxed { - buffer.appendInt32(1182402406) - } - _data.text.serialize(buffer, true) - break - case .pageBlockPhoto(let _data): - if boxed { - buffer.appendInt32(391759200) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.photoId, buffer: buffer, boxed: false) - _data.caption.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.webpageId!, buffer: buffer, boxed: false) - } - break - case .pageBlockPreformatted(let _data): - if boxed { - buffer.appendInt32(-1066346178) - } - _data.text.serialize(buffer, true) - serializeString(_data.language, buffer: buffer, boxed: false) - break - case .pageBlockPullquote(let _data): - if boxed { - buffer.appendInt32(1329878739) - } - _data.text.serialize(buffer, true) - _data.caption.serialize(buffer, true) - break - case .pageBlockRelatedArticles(let _data): - if boxed { - buffer.appendInt32(370236054) - } - _data.title.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.articles.count)) - for item in _data.articles { - item.serialize(buffer, true) - } - break - case .pageBlockSlideshow(let _data): - if boxed { - buffer.appendInt32(52401552) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.items.count)) - for item in _data.items { - item.serialize(buffer, true) - } - _data.caption.serialize(buffer, true) - break - case .pageBlockSubheader(let _data): - if boxed { - buffer.appendInt32(-248793375) - } - _data.text.serialize(buffer, true) - break - case .pageBlockSubtitle(let _data): - if boxed { - buffer.appendInt32(-1879401953) - } - _data.text.serialize(buffer, true) - break - case .pageBlockTable(let _data): - if boxed { - buffer.appendInt32(-1085412734) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.title.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rows.count)) - for item in _data.rows { - item.serialize(buffer, true) - } - break - case .pageBlockTitle(let _data): - if boxed { - buffer.appendInt32(1890305021) - } - _data.text.serialize(buffer, true) - break - case .pageBlockUnsupported: - if boxed { - buffer.appendInt32(324435594) - } - break - case .pageBlockVideo(let _data): - if boxed { - buffer.appendInt32(2089805750) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.videoId, buffer: buffer, boxed: false) - _data.caption.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .pageBlockAnchor(let _data): - return ("pageBlockAnchor", [("name", ConstructorParameterDescription(_data.name))]) - case .pageBlockAudio(let _data): - return ("pageBlockAudio", [("audioId", ConstructorParameterDescription(_data.audioId)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockAuthorDate(let _data): - return ("pageBlockAuthorDate", [("author", ConstructorParameterDescription(_data.author)), ("publishedDate", ConstructorParameterDescription(_data.publishedDate))]) - case .pageBlockBlockquote(let _data): - return ("pageBlockBlockquote", [("text", ConstructorParameterDescription(_data.text)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockChannel(let _data): - return ("pageBlockChannel", [("channel", ConstructorParameterDescription(_data.channel))]) - case .pageBlockCollage(let _data): - return ("pageBlockCollage", [("items", ConstructorParameterDescription(_data.items)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockCover(let _data): - return ("pageBlockCover", [("cover", ConstructorParameterDescription(_data.cover))]) - case .pageBlockDetails(let _data): - return ("pageBlockDetails", [("flags", ConstructorParameterDescription(_data.flags)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("title", ConstructorParameterDescription(_data.title))]) - case .pageBlockDivider: - return ("pageBlockDivider", []) - case .pageBlockEmbed(let _data): - return ("pageBlockEmbed", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("html", ConstructorParameterDescription(_data.html)), ("posterPhotoId", ConstructorParameterDescription(_data.posterPhotoId)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockEmbedPost(let _data): - return ("pageBlockEmbedPost", [("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId)), ("authorPhotoId", ConstructorParameterDescription(_data.authorPhotoId)), ("author", ConstructorParameterDescription(_data.author)), ("date", ConstructorParameterDescription(_data.date)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockFooter(let _data): - return ("pageBlockFooter", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockHeader(let _data): - return ("pageBlockHeader", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockKicker(let _data): - return ("pageBlockKicker", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockList(let _data): - return ("pageBlockList", [("items", ConstructorParameterDescription(_data.items))]) - case .pageBlockMap(let _data): - return ("pageBlockMap", [("geo", ConstructorParameterDescription(_data.geo)), ("zoom", ConstructorParameterDescription(_data.zoom)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockOrderedList(let _data): - return ("pageBlockOrderedList", [("items", ConstructorParameterDescription(_data.items))]) - case .pageBlockParagraph(let _data): - return ("pageBlockParagraph", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockPhoto(let _data): - return ("pageBlockPhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("caption", ConstructorParameterDescription(_data.caption)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId))]) - case .pageBlockPreformatted(let _data): - return ("pageBlockPreformatted", [("text", ConstructorParameterDescription(_data.text)), ("language", ConstructorParameterDescription(_data.language))]) - case .pageBlockPullquote(let _data): - return ("pageBlockPullquote", [("text", ConstructorParameterDescription(_data.text)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockRelatedArticles(let _data): - return ("pageBlockRelatedArticles", [("title", ConstructorParameterDescription(_data.title)), ("articles", ConstructorParameterDescription(_data.articles))]) - case .pageBlockSlideshow(let _data): - return ("pageBlockSlideshow", [("items", ConstructorParameterDescription(_data.items)), ("caption", ConstructorParameterDescription(_data.caption))]) - case .pageBlockSubheader(let _data): - return ("pageBlockSubheader", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockSubtitle(let _data): - return ("pageBlockSubtitle", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockTable(let _data): - return ("pageBlockTable", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("rows", ConstructorParameterDescription(_data.rows))]) - case .pageBlockTitle(let _data): - return ("pageBlockTitle", [("text", ConstructorParameterDescription(_data.text))]) - case .pageBlockUnsupported: - return ("pageBlockUnsupported", []) - case .pageBlockVideo(let _data): - return ("pageBlockVideo", [("flags", ConstructorParameterDescription(_data.flags)), ("videoId", ConstructorParameterDescription(_data.videoId)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .messageReactions(let _data): + return ("messageReactions", [("flags", ConstructorParameterDescription(_data.flags)), ("results", ConstructorParameterDescription(_data.results)), ("recentReactions", ConstructorParameterDescription(_data.recentReactions)), ("topReactors", ConstructorParameterDescription(_data.topReactors))]) } } - public static func parse_pageBlockAnchor(_ reader: BufferReader) -> PageBlock? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockAnchor(Cons_pageBlockAnchor(name: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAudio(Cons_pageBlockAudio(audioId: _1!, caption: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockAuthorDate(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockAuthorDate(Cons_pageBlockAuthorDate(author: _1!, publishedDate: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockBlockquote(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockBlockquote(Cons_pageBlockBlockquote(text: _1!, caption: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockChannel(_ reader: BufferReader) -> PageBlock? { - var _1: Api.Chat? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Chat - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockChannel(Cons_pageBlockChannel(channel: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockCollage(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockCollage(Cons_pageBlockCollage(items: _1!, caption: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockCover(_ reader: BufferReader) -> PageBlock? { - var _1: Api.PageBlock? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PageBlock - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockCover(Cons_pageBlockCover(cover: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockDetails(_ reader: BufferReader) -> PageBlock? { + public static func parse_messageReactions(_ reader: BufferReader) -> MessageReactions? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.PageBlock]? + var _2: [Api.ReactionCount]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) } - var _3: Api.RichText? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.RichText + var _3: [Api.MessagePeerReaction]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) + } + } + var _4: [Api.MessageReactor]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageReactor.self) + } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockDetails(Cons_pageBlockDetails(flags: _1!, blocks: _2!, title: _3!)) - } - else { - return nil - } - } - public static func parse_pageBlockDivider(_ reader: BufferReader) -> PageBlock? { - return Api.PageBlock.pageBlockDivider - } - public static func parse_pageBlockEmbed(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = parseString(reader) - } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = parseString(reader) - } - var _4: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _4 = reader.readInt64() - } - var _5: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _6 = reader.readInt32() - } - var _7: Api.PageCaption? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbed(Cons_pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!)) - } - else { - return nil - } - } - public static func parse_pageBlockEmbedPost(_ reader: BufferReader) -> PageBlock? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: [Api.PageBlock]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _7: Api.PageCaption? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PageBlock.pageBlockEmbedPost(Cons_pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!)) - } - else { - return nil - } - } - public static func parse_pageBlockFooter(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockFooter(Cons_pageBlockFooter(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockHeader(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockHeader(Cons_pageBlockHeader(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockKicker(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockKicker(Cons_pageBlockKicker(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockList(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageListItem]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListItem.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockList(Cons_pageBlockList(items: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockMap(_ reader: BufferReader) -> PageBlock? { - var _1: Api.GeoPoint? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.PageCaption? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockMap(Cons_pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!)) - } - else { - return nil - } - } - public static func parse_pageBlockOrderedList(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageListOrderedItem]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListOrderedItem.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockOrderedList(Cons_pageBlockOrderedList(items: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockParagraph(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockParagraph(Cons_pageBlockParagraph(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockPhoto(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PageCaption? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PageBlock.pageBlockPhoto(Cons_pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5)) - } - else { - return nil - } - } - public static func parse_pageBlockPreformatted(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockPreformatted(Cons_pageBlockPreformatted(text: _1!, language: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockPullquote(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockPullquote(Cons_pageBlockPullquote(text: _1!, caption: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockRelatedArticles(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: [Api.PageRelatedArticle]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageRelatedArticle.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockRelatedArticles(Cons_pageBlockRelatedArticles(title: _1!, articles: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockSlideshow(_ reader: BufferReader) -> PageBlock? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - var _2: Api.PageCaption? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageBlock.pageBlockSlideshow(Cons_pageBlockSlideshow(items: _1!, caption: _2!)) - } - else { - return nil - } - } - public static func parse_pageBlockSubheader(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubheader(Cons_pageBlockSubheader(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockSubtitle(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockSubtitle(Cons_pageBlockSubtitle(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockTable(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _3: [Api.PageTableRow]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableRow.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockTable(Cons_pageBlockTable(flags: _1!, title: _2!, rows: _3!)) - } - else { - return nil - } - } - public static func parse_pageBlockTitle(_ reader: BufferReader) -> PageBlock? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageBlock.pageBlockTitle(Cons_pageBlockTitle(text: _1!)) - } - else { - return nil - } - } - public static func parse_pageBlockUnsupported(_ reader: BufferReader) -> PageBlock? { - return Api.PageBlock.pageBlockUnsupported - } - public static func parse_pageBlockVideo(_ reader: BufferReader) -> PageBlock? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PageCaption? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PageCaption - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.PageBlock.pageBlockVideo(Cons_pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageReactions.messageReactions(Cons_messageReactions(flags: _1!, results: _2!, recentReactions: _3, topReactors: _4)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessageReactor: TypeConstructorDescription { + public class Cons_messageReactor: TypeConstructorDescription { + public var flags: Int32 + public var peerId: Api.Peer? + public var count: Int32 + public init(flags: Int32, peerId: Api.Peer?, count: Int32) { + self.flags = flags + self.peerId = peerId + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReactor", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("count", ConstructorParameterDescription(self.count))]) + } + } + case messageReactor(Cons_messageReactor) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReactor(let _data): + if boxed { + buffer.appendInt32(1269016922) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.peerId!.serialize(buffer, true) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageReactor(let _data): + return ("messageReactor", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_messageReactor(_ reader: BufferReader) -> MessageReactor? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageReactor.messageReactor(Cons_messageReactor(flags: _1!, peerId: _2, count: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessageReplies: TypeConstructorDescription { + public class Cons_messageReplies: TypeConstructorDescription { + public var flags: Int32 + public var replies: Int32 + public var repliesPts: Int32 + public var recentRepliers: [Api.Peer]? + public var channelId: Int64? + public var maxId: Int32? + public var readMaxId: Int32? + public init(flags: Int32, replies: Int32, repliesPts: Int32, recentRepliers: [Api.Peer]?, channelId: Int64?, maxId: Int32?, readMaxId: Int32?) { + self.flags = flags + self.replies = replies + self.repliesPts = repliesPts + self.recentRepliers = recentRepliers + self.channelId = channelId + self.maxId = maxId + self.readMaxId = readMaxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReplies", [("flags", ConstructorParameterDescription(self.flags)), ("replies", ConstructorParameterDescription(self.replies)), ("repliesPts", ConstructorParameterDescription(self.repliesPts)), ("recentRepliers", ConstructorParameterDescription(self.recentRepliers)), ("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) + } + } + case messageReplies(Cons_messageReplies) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReplies(let _data): + if boxed { + buffer.appendInt32(-2083123262) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.replies, buffer: buffer, boxed: false) + serializeInt32(_data.repliesPts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentRepliers!.count)) + for item in _data.recentRepliers! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.channelId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.maxId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.readMaxId!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageReplies(let _data): + return ("messageReplies", [("flags", ConstructorParameterDescription(_data.flags)), ("replies", ConstructorParameterDescription(_data.replies)), ("repliesPts", ConstructorParameterDescription(_data.repliesPts)), ("recentRepliers", ConstructorParameterDescription(_data.recentRepliers)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) + } + } + + public static func parse_messageReplies(_ reader: BufferReader) -> MessageReplies? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.Peer]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + } + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = reader.readInt32() + } + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _7 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.MessageReplies.messageReplies(Cons_messageReplies(flags: _1!, replies: _2!, repliesPts: _3!, recentRepliers: _4, channelId: _5, maxId: _6, readMaxId: _7)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum MessageReplyHeader: TypeConstructorDescription { + public class Cons_messageReplyHeader: TypeConstructorDescription { + public var flags: Int32 + public var replyToMsgId: Int32? + public var replyToPeerId: Api.Peer? + public var replyFrom: Api.MessageFwdHeader? + public var replyMedia: Api.MessageMedia? + public var replyToTopId: Int32? + public var quoteText: String? + public var quoteEntities: [Api.MessageEntity]? + public var quoteOffset: Int32? + public var todoItemId: Int32? + public var pollOption: Buffer? + public init(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyFrom: Api.MessageFwdHeader?, replyMedia: Api.MessageMedia?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?, quoteOffset: Int32?, todoItemId: Int32?, pollOption: Buffer?) { + self.flags = flags + self.replyToMsgId = replyToMsgId + self.replyToPeerId = replyToPeerId + self.replyFrom = replyFrom + self.replyMedia = replyMedia + self.replyToTopId = replyToTopId + self.quoteText = quoteText + self.quoteEntities = quoteEntities + self.quoteOffset = quoteOffset + self.todoItemId = todoItemId + self.pollOption = pollOption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReplyHeader", [("flags", ConstructorParameterDescription(self.flags)), ("replyToMsgId", ConstructorParameterDescription(self.replyToMsgId)), ("replyToPeerId", ConstructorParameterDescription(self.replyToPeerId)), ("replyFrom", ConstructorParameterDescription(self.replyFrom)), ("replyMedia", ConstructorParameterDescription(self.replyMedia)), ("replyToTopId", ConstructorParameterDescription(self.replyToTopId)), ("quoteText", ConstructorParameterDescription(self.quoteText)), ("quoteEntities", ConstructorParameterDescription(self.quoteEntities)), ("quoteOffset", ConstructorParameterDescription(self.quoteOffset)), ("todoItemId", ConstructorParameterDescription(self.todoItemId)), ("pollOption", ConstructorParameterDescription(self.pollOption))]) + } + } + public class Cons_messageReplyStoryHeader: TypeConstructorDescription { + public var peer: Api.Peer + public var storyId: Int32 + public init(peer: Api.Peer, storyId: Int32) { + self.peer = peer + self.storyId = storyId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReplyStoryHeader", [("peer", ConstructorParameterDescription(self.peer)), ("storyId", ConstructorParameterDescription(self.storyId))]) + } + } + case messageReplyHeader(Cons_messageReplyHeader) + case messageReplyStoryHeader(Cons_messageReplyStoryHeader) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReplyHeader(let _data): + if boxed { + buffer.appendInt32(462937446) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.replyToMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.replyToPeerId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.replyFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.replyMedia!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.replyToTopId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeString(_data.quoteText!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.quoteEntities!.count)) + for item in _data.quoteEntities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 10) != 0 { + serializeInt32(_data.quoteOffset!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt32(_data.todoItemId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + serializeBytes(_data.pollOption!, buffer: buffer, boxed: false) + } + break + case .messageReplyStoryHeader(let _data): + if boxed { + buffer.appendInt32(240843065) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.storyId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageReplyHeader(let _data): + return ("messageReplyHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("replyToMsgId", ConstructorParameterDescription(_data.replyToMsgId)), ("replyToPeerId", ConstructorParameterDescription(_data.replyToPeerId)), ("replyFrom", ConstructorParameterDescription(_data.replyFrom)), ("replyMedia", ConstructorParameterDescription(_data.replyMedia)), ("replyToTopId", ConstructorParameterDescription(_data.replyToTopId)), ("quoteText", ConstructorParameterDescription(_data.quoteText)), ("quoteEntities", ConstructorParameterDescription(_data.quoteEntities)), ("quoteOffset", ConstructorParameterDescription(_data.quoteOffset)), ("todoItemId", ConstructorParameterDescription(_data.todoItemId)), ("pollOption", ConstructorParameterDescription(_data.pollOption))]) + case .messageReplyStoryHeader(let _data): + return ("messageReplyStoryHeader", [("peer", ConstructorParameterDescription(_data.peer)), ("storyId", ConstructorParameterDescription(_data.storyId))]) + } + } + + public static func parse_messageReplyHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _2 = reader.readInt32() + } + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _4: Api.MessageFwdHeader? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } + } + var _5: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _7 = parseString(reader) + } + var _8: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + _9 = reader.readInt32() + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _10 = reader.readInt32() + } + var _11: Buffer? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + _11 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.MessageReplyHeader.messageReplyHeader(Cons_messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyFrom: _4, replyMedia: _5, replyToTopId: _6, quoteText: _7, quoteEntities: _8, quoteOffset: _9, todoItemId: _10, pollOption: _11)) + } + else { + return nil + } + } + public static func parse_messageReplyStoryHeader(_ reader: BufferReader) -> MessageReplyHeader? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageReplyHeader.messageReplyStoryHeader(Cons_messageReplyStoryHeader(peer: _1!, storyId: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessageReportOption: TypeConstructorDescription { + public class Cons_messageReportOption: TypeConstructorDescription { + public var text: String + public var option: Buffer + public init(text: String, option: Buffer) { + self.text = text + self.option = option + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReportOption", [("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option))]) + } + } + case messageReportOption(Cons_messageReportOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReportOption(let _data): + if boxed { + buffer.appendInt32(2030298073) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeBytes(_data.option, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageReportOption(let _data): + return ("messageReportOption", [("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option))]) + } + } + + public static func parse_messageReportOption(_ reader: BufferReader) -> MessageReportOption? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MessageReportOption.messageReportOption(Cons_messageReportOption(text: _1!, option: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessageViews: TypeConstructorDescription { + public class Cons_messageViews: TypeConstructorDescription { + public var flags: Int32 + public var views: Int32? + public var forwards: Int32? + public var replies: Api.MessageReplies? + public init(flags: Int32, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?) { + self.flags = flags + self.views = views + self.forwards = forwards + self.replies = replies + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageViews", [("flags", ConstructorParameterDescription(self.flags)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("replies", ConstructorParameterDescription(self.replies))]) + } + } + case messageViews(Cons_messageViews) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let _data): + if boxed { + buffer.appendInt32(1163625789) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.views!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.forwards!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.replies!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageViews(let _data): + return ("messageViews", [("flags", ConstructorParameterDescription(_data.flags)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("replies", ConstructorParameterDescription(_data.replies))]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: Api.MessageReplies? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageReplies + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.MessageViews.messageViews(Cons_messageViews(flags: _1!, views: _2, forwards: _3, replies: _4)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MessagesFilter: TypeConstructorDescription { + public class Cons_inputMessagesFilterPhoneCalls: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputMessagesFilterPhoneCalls", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + case inputMessagesFilterChatPhotos + case inputMessagesFilterContacts + case inputMessagesFilterDocument + case inputMessagesFilterEmpty + case inputMessagesFilterGeo + case inputMessagesFilterGif + case inputMessagesFilterMusic + case inputMessagesFilterMyMentions + case inputMessagesFilterPhoneCalls(Cons_inputMessagesFilterPhoneCalls) + case inputMessagesFilterPhotoVideo + case inputMessagesFilterPhotos + case inputMessagesFilterPinned + case inputMessagesFilterPoll + case inputMessagesFilterRoundVideo + case inputMessagesFilterRoundVoice + case inputMessagesFilterUrl + case inputMessagesFilterVideo + case inputMessagesFilterVoice + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputMessagesFilterChatPhotos: + if boxed { + buffer.appendInt32(975236280) + } + break + case .inputMessagesFilterContacts: + if boxed { + buffer.appendInt32(-530392189) + } + break + case .inputMessagesFilterDocument: + if boxed { + buffer.appendInt32(-1629621880) + } + break + case .inputMessagesFilterEmpty: + if boxed { + buffer.appendInt32(1474492012) + } + break + case .inputMessagesFilterGeo: + if boxed { + buffer.appendInt32(-419271411) + } + break + case .inputMessagesFilterGif: + if boxed { + buffer.appendInt32(-3644025) + } + break + case .inputMessagesFilterMusic: + if boxed { + buffer.appendInt32(928101534) + } + break + case .inputMessagesFilterMyMentions: + if boxed { + buffer.appendInt32(-1040652646) + } + break + case .inputMessagesFilterPhoneCalls(let _data): + if boxed { + buffer.appendInt32(-2134272152) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .inputMessagesFilterPhotoVideo: + if boxed { + buffer.appendInt32(1458172132) + } + break + case .inputMessagesFilterPhotos: + if boxed { + buffer.appendInt32(-1777752804) + } + break + case .inputMessagesFilterPinned: + if boxed { + buffer.appendInt32(464520273) + } + break + case .inputMessagesFilterPoll: + if boxed { + buffer.appendInt32(-97793782) + } + break + case .inputMessagesFilterRoundVideo: + if boxed { + buffer.appendInt32(-1253451181) + } + break + case .inputMessagesFilterRoundVoice: + if boxed { + buffer.appendInt32(2054952868) + } + break + case .inputMessagesFilterUrl: + if boxed { + buffer.appendInt32(2129714567) + } + break + case .inputMessagesFilterVideo: + if boxed { + buffer.appendInt32(-1614803355) + } + break + case .inputMessagesFilterVoice: + if boxed { + buffer.appendInt32(1358283666) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputMessagesFilterChatPhotos: + return ("inputMessagesFilterChatPhotos", []) + case .inputMessagesFilterContacts: + return ("inputMessagesFilterContacts", []) + case .inputMessagesFilterDocument: + return ("inputMessagesFilterDocument", []) + case .inputMessagesFilterEmpty: + return ("inputMessagesFilterEmpty", []) + case .inputMessagesFilterGeo: + return ("inputMessagesFilterGeo", []) + case .inputMessagesFilterGif: + return ("inputMessagesFilterGif", []) + case .inputMessagesFilterMusic: + return ("inputMessagesFilterMusic", []) + case .inputMessagesFilterMyMentions: + return ("inputMessagesFilterMyMentions", []) + case .inputMessagesFilterPhoneCalls(let _data): + return ("inputMessagesFilterPhoneCalls", [("flags", ConstructorParameterDescription(_data.flags))]) + case .inputMessagesFilterPhotoVideo: + return ("inputMessagesFilterPhotoVideo", []) + case .inputMessagesFilterPhotos: + return ("inputMessagesFilterPhotos", []) + case .inputMessagesFilterPinned: + return ("inputMessagesFilterPinned", []) + case .inputMessagesFilterPoll: + return ("inputMessagesFilterPoll", []) + case .inputMessagesFilterRoundVideo: + return ("inputMessagesFilterRoundVideo", []) + case .inputMessagesFilterRoundVoice: + return ("inputMessagesFilterRoundVoice", []) + case .inputMessagesFilterUrl: + return ("inputMessagesFilterUrl", []) + case .inputMessagesFilterVideo: + return ("inputMessagesFilterVideo", []) + case .inputMessagesFilterVoice: + return ("inputMessagesFilterVoice", []) + } + } + + public static func parse_inputMessagesFilterChatPhotos(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterChatPhotos + } + public static func parse_inputMessagesFilterContacts(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterContacts + } + public static func parse_inputMessagesFilterDocument(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterDocument + } + public static func parse_inputMessagesFilterEmpty(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterEmpty + } + public static func parse_inputMessagesFilterGeo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterGeo + } + public static func parse_inputMessagesFilterGif(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterGif + } + public static func parse_inputMessagesFilterMusic(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterMusic + } + public static func parse_inputMessagesFilterMyMentions(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterMyMentions + } + public static func parse_inputMessagesFilterPhoneCalls(_ reader: BufferReader) -> MessagesFilter? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessagesFilter.inputMessagesFilterPhoneCalls(Cons_inputMessagesFilterPhoneCalls(flags: _1!)) + } + else { + return nil + } + } + public static func parse_inputMessagesFilterPhotoVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPhotoVideo + } + public static func parse_inputMessagesFilterPhotos(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPhotos + } + public static func parse_inputMessagesFilterPinned(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPinned + } + public static func parse_inputMessagesFilterPoll(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterPoll + } + public static func parse_inputMessagesFilterRoundVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterRoundVideo + } + public static func parse_inputMessagesFilterRoundVoice(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterRoundVoice + } + public static func parse_inputMessagesFilterUrl(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterUrl + } + public static func parse_inputMessagesFilterVideo(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterVideo + } + public static func parse_inputMessagesFilterVoice(_ reader: BufferReader) -> MessagesFilter? { + return Api.MessagesFilter.inputMessagesFilterVoice + } + } +} +public extension Api { + enum MissingInvitee: TypeConstructorDescription { + public class Cons_missingInvitee: TypeConstructorDescription { + public var flags: Int32 + public var userId: Int64 + public init(flags: Int32, userId: Int64) { + self.flags = flags + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("missingInvitee", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + case missingInvitee(Cons_missingInvitee) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .missingInvitee(let _data): + if boxed { + buffer.appendInt32(1653379620) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .missingInvitee(let _data): + return ("missingInvitee", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId))]) + } + } + + public static func parse_missingInvitee(_ reader: BufferReader) -> MissingInvitee? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.MissingInvitee.missingInvitee(Cons_missingInvitee(flags: _1!, userId: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum MyBoost: TypeConstructorDescription { + public class Cons_myBoost: TypeConstructorDescription { + public var flags: Int32 + public var slot: Int32 + public var peer: Api.Peer? + public var date: Int32 + public var expires: Int32 + public var cooldownUntilDate: Int32? + public init(flags: Int32, slot: Int32, peer: Api.Peer?, date: Int32, expires: Int32, cooldownUntilDate: Int32?) { + self.flags = flags + self.slot = slot + self.peer = peer + self.date = date + self.expires = expires + self.cooldownUntilDate = cooldownUntilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("myBoost", [("flags", ConstructorParameterDescription(self.flags)), ("slot", ConstructorParameterDescription(self.slot)), ("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("expires", ConstructorParameterDescription(self.expires)), ("cooldownUntilDate", ConstructorParameterDescription(self.cooldownUntilDate))]) + } + } + case myBoost(Cons_myBoost) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .myBoost(let _data): + if boxed { + buffer.appendInt32(-1001897636) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.slot, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.peer!.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.expires, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.cooldownUntilDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .myBoost(let _data): + return ("myBoost", [("flags", ConstructorParameterDescription(_data.flags)), ("slot", ConstructorParameterDescription(_data.slot)), ("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("expires", ConstructorParameterDescription(_data.expires)), ("cooldownUntilDate", ConstructorParameterDescription(_data.cooldownUntilDate))]) + } + } + + public static func parse_myBoost(_ reader: BufferReader) -> MyBoost? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MyBoost.myBoost(Cons_myBoost(flags: _1!, slot: _2!, peer: _3, date: _4!, expires: _5!, cooldownUntilDate: _6)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index dad55093d8..9134cda518 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -1,330 +1,52 @@ public extension Api { - indirect enum PageCaption: TypeConstructorDescription { - public class Cons_pageCaption: TypeConstructorDescription { - public var text: Api.RichText - public var credit: Api.RichText - public init(text: Api.RichText, credit: Api.RichText) { - self.text = text - self.credit = credit + enum NearestDc: TypeConstructorDescription { + public class Cons_nearestDc: TypeConstructorDescription { + public var country: String + public var thisDc: Int32 + public var nearestDc: Int32 + public init(country: String, thisDc: Int32, nearestDc: Int32) { + self.country = country + self.thisDc = thisDc + self.nearestDc = nearestDc } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageCaption", [("text", ConstructorParameterDescription(self.text)), ("credit", ConstructorParameterDescription(self.credit))]) + return ("nearestDc", [("country", ConstructorParameterDescription(self.country)), ("thisDc", ConstructorParameterDescription(self.thisDc)), ("nearestDc", ConstructorParameterDescription(self.nearestDc))]) } } - case pageCaption(Cons_pageCaption) + case nearestDc(Cons_nearestDc) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageCaption(let _data): + case .nearestDc(let _data): if boxed { - buffer.appendInt32(1869903447) + buffer.appendInt32(-1910892683) } - _data.text.serialize(buffer, true) - _data.credit.serialize(buffer, true) + serializeString(_data.country, buffer: buffer, boxed: false) + serializeInt32(_data.thisDc, buffer: buffer, boxed: false) + serializeInt32(_data.nearestDc, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .pageCaption(let _data): - return ("pageCaption", [("text", ConstructorParameterDescription(_data.text)), ("credit", ConstructorParameterDescription(_data.credit))]) + case .nearestDc(let _data): + return ("nearestDc", [("country", ConstructorParameterDescription(_data.country)), ("thisDc", ConstructorParameterDescription(_data.thisDc)), ("nearestDc", ConstructorParameterDescription(_data.nearestDc))]) } } - public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageCaption.pageCaption(Cons_pageCaption(text: _1!, credit: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum PageListItem: TypeConstructorDescription { - public class Cons_pageListItemBlocks: TypeConstructorDescription { - public var blocks: [Api.PageBlock] - public init(blocks: [Api.PageBlock]) { - self.blocks = blocks - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageListItemBlocks", [("blocks", ConstructorParameterDescription(self.blocks))]) - } - } - public class Cons_pageListItemText: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageListItemText", [("text", ConstructorParameterDescription(self.text))]) - } - } - case pageListItemBlocks(Cons_pageListItemBlocks) - case pageListItemText(Cons_pageListItemText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageListItemBlocks(let _data): - if boxed { - buffer.appendInt32(635466748) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { - item.serialize(buffer, true) - } - break - case .pageListItemText(let _data): - if boxed { - buffer.appendInt32(-1188055347) - } - _data.text.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pageListItemBlocks(let _data): - return ("pageListItemBlocks", [("blocks", ConstructorParameterDescription(_data.blocks))]) - case .pageListItemText(let _data): - return ("pageListItemText", [("text", ConstructorParameterDescription(_data.text))]) - } - } - - public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { - var _1: [Api.PageBlock]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemBlocks(Cons_pageListItemBlocks(blocks: _1!)) - } - else { - return nil - } - } - public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.PageListItem.pageListItemText(Cons_pageListItemText(text: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum PageListOrderedItem: TypeConstructorDescription { - public class Cons_pageListOrderedItemBlocks: TypeConstructorDescription { - public var num: String - public var blocks: [Api.PageBlock] - public init(num: String, blocks: [Api.PageBlock]) { - self.num = num - self.blocks = blocks - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageListOrderedItemBlocks", [("num", ConstructorParameterDescription(self.num)), ("blocks", ConstructorParameterDescription(self.blocks))]) - } - } - public class Cons_pageListOrderedItemText: TypeConstructorDescription { - public var num: String - public var text: Api.RichText - public init(num: String, text: Api.RichText) { - self.num = num - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageListOrderedItemText", [("num", ConstructorParameterDescription(self.num)), ("text", ConstructorParameterDescription(self.text))]) - } - } - case pageListOrderedItemBlocks(Cons_pageListOrderedItemBlocks) - case pageListOrderedItemText(Cons_pageListOrderedItemText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageListOrderedItemBlocks(let _data): - if boxed { - buffer.appendInt32(-1730311882) - } - serializeString(_data.num, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { - item.serialize(buffer, true) - } - break - case .pageListOrderedItemText(let _data): - if boxed { - buffer.appendInt32(1577484359) - } - serializeString(_data.num, buffer: buffer, boxed: false) - _data.text.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pageListOrderedItemBlocks(let _data): - return ("pageListOrderedItemBlocks", [("num", ConstructorParameterDescription(_data.num)), ("blocks", ConstructorParameterDescription(_data.blocks))]) - case .pageListOrderedItemText(let _data): - return ("pageListOrderedItemText", [("num", ConstructorParameterDescription(_data.num)), ("text", ConstructorParameterDescription(_data.text))]) - } - } - - public static func parse_pageListOrderedItemBlocks(_ reader: BufferReader) -> PageListOrderedItem? { + public static func parse_nearestDc(_ reader: BufferReader) -> NearestDc? { var _1: String? _1 = parseString(reader) - var _2: [Api.PageBlock]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemBlocks(Cons_pageListOrderedItemBlocks(num: _1!, blocks: _2!)) - } - else { - return nil - } - } - public static func parse_pageListOrderedItemText(_ reader: BufferReader) -> PageListOrderedItem? { - var _1: String? - _1 = parseString(reader) - var _2: Api.RichText? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PageListOrderedItem.pageListOrderedItemText(Cons_pageListOrderedItemText(num: _1!, text: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PageRelatedArticle: TypeConstructorDescription { - public class Cons_pageRelatedArticle: TypeConstructorDescription { - public var flags: Int32 - public var url: String - public var webpageId: Int64 - public var title: String? - public var description: String? - public var photoId: Int64? - public var author: String? - public var publishedDate: Int32? - public init(flags: Int32, url: String, webpageId: Int64, title: String?, description: String?, photoId: Int64?, author: String?, publishedDate: Int32?) { - self.flags = flags - self.url = url - self.webpageId = webpageId - self.title = title - self.description = description - self.photoId = photoId - self.author = author - self.publishedDate = publishedDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageRelatedArticle", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photoId", ConstructorParameterDescription(self.photoId)), ("author", ConstructorParameterDescription(self.author)), ("publishedDate", ConstructorParameterDescription(self.publishedDate))]) - } - } - case pageRelatedArticle(Cons_pageRelatedArticle) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageRelatedArticle(let _data): - if boxed { - buffer.appendInt32(-1282352120) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.webpageId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.description!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.photoId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.author!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.publishedDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pageRelatedArticle(let _data): - return ("pageRelatedArticle", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("author", ConstructorParameterDescription(_data.author)), ("publishedDate", ConstructorParameterDescription(_data.publishedDate))]) - } - } - - public static func parse_pageRelatedArticle(_ reader: BufferReader) -> PageRelatedArticle? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - var _6: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = reader.readInt64() - } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 { - _7 = parseString(reader) - } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt32() - } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PageRelatedArticle.pageRelatedArticle(Cons_pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8)) + if _c1 && _c2 && _c3 { + return Api.NearestDc.nearestDc(Cons_nearestDc(country: _1!, thisDc: _2!, nearestDc: _3!)) } else { return nil @@ -333,124 +55,99 @@ public extension Api { } } public extension Api { - indirect enum PageTableCell: TypeConstructorDescription { - public class Cons_pageTableCell: TypeConstructorDescription { - public var flags: Int32 - public var text: Api.RichText? - public var colspan: Int32? - public var rowspan: Int32? - public init(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?) { - self.flags = flags - self.text = text - self.colspan = colspan - self.rowspan = rowspan + enum NotificationSound: TypeConstructorDescription { + public class Cons_notificationSoundLocal: TypeConstructorDescription { + public var title: String + public var data: String + public init(title: String, data: String) { + self.title = title + self.data = data } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageTableCell", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("colspan", ConstructorParameterDescription(self.colspan)), ("rowspan", ConstructorParameterDescription(self.rowspan))]) + return ("notificationSoundLocal", [("title", ConstructorParameterDescription(self.title)), ("data", ConstructorParameterDescription(self.data))]) } } - case pageTableCell(Cons_pageTableCell) + public class Cons_notificationSoundRingtone: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("notificationSoundRingtone", [("id", ConstructorParameterDescription(self.id))]) + } + } + case notificationSoundDefault + case notificationSoundLocal(Cons_notificationSoundLocal) + case notificationSoundNone + case notificationSoundRingtone(Cons_notificationSoundRingtone) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .pageTableCell(let _data): + case .notificationSoundDefault: if boxed { - buffer.appendInt32(878078826) + buffer.appendInt32(-1746354498) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 7) != 0 { - _data.text!.serialize(buffer, true) + break + case .notificationSoundLocal(let _data): + if boxed { + buffer.appendInt32(-2096391452) } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.colspan!, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.data, buffer: buffer, boxed: false) + break + case .notificationSoundNone: + if boxed { + buffer.appendInt32(1863070943) } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.rowspan!, buffer: buffer, boxed: false) + break + case .notificationSoundRingtone(let _data): + if boxed { + buffer.appendInt32(-9666487) } + serializeInt64(_data.id, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .pageTableCell(let _data): - return ("pageTableCell", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("colspan", ConstructorParameterDescription(_data.colspan)), ("rowspan", ConstructorParameterDescription(_data.rowspan))]) + case .notificationSoundDefault: + return ("notificationSoundDefault", []) + case .notificationSoundLocal(let _data): + return ("notificationSoundLocal", [("title", ConstructorParameterDescription(_data.title)), ("data", ConstructorParameterDescription(_data.data))]) + case .notificationSoundNone: + return ("notificationSoundNone", []) + case .notificationSoundRingtone(let _data): + return ("notificationSoundRingtone", [("id", ConstructorParameterDescription(_data.id))]) } } - public static func parse_pageTableCell(_ reader: BufferReader) -> PageTableCell? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.RichText? - if Int(_1!) & Int(1 << 7) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.RichText - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } + public static func parse_notificationSoundDefault(_ reader: BufferReader) -> NotificationSound? { + return Api.NotificationSound.notificationSoundDefault + } + public static func parse_notificationSoundLocal(_ reader: BufferReader) -> NotificationSound? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 7) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PageTableCell.pageTableCell(Cons_pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4)) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.NotificationSound.notificationSoundLocal(Cons_notificationSoundLocal(title: _1!, data: _2!)) } else { return nil } } - } -} -public extension Api { - enum PageTableRow: TypeConstructorDescription { - public class Cons_pageTableRow: TypeConstructorDescription { - public var cells: [Api.PageTableCell] - public init(cells: [Api.PageTableCell]) { - self.cells = cells - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pageTableRow", [("cells", ConstructorParameterDescription(self.cells))]) - } + public static func parse_notificationSoundNone(_ reader: BufferReader) -> NotificationSound? { + return Api.NotificationSound.notificationSoundNone } - case pageTableRow(Cons_pageTableRow) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pageTableRow(let _data): - if boxed { - buffer.appendInt32(-524237339) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.cells.count)) - for item in _data.cells { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pageTableRow(let _data): - return ("pageTableRow", [("cells", ConstructorParameterDescription(_data.cells))]) - } - } - - public static func parse_pageTableRow(_ reader: BufferReader) -> PageTableRow? { - var _1: [Api.PageTableCell]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableCell.self) - } + public static func parse_notificationSoundRingtone(_ reader: BufferReader) -> NotificationSound? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.PageTableRow.pageTableRow(Cons_pageTableRow(cells: _1!)) + return Api.NotificationSound.notificationSoundRingtone(Cons_notificationSoundRingtone(id: _1!)) } else { return nil @@ -459,66 +156,158 @@ public extension Api { } } public extension Api { - indirect enum PaidReactionPrivacy: TypeConstructorDescription { - public class Cons_paidReactionPrivacyPeer: TypeConstructorDescription { - public var peer: Api.InputPeer - public init(peer: Api.InputPeer) { + enum NotifyPeer: TypeConstructorDescription { + public class Cons_notifyForumTopic: TypeConstructorDescription { + public var peer: Api.Peer + public var topMsgId: Int32 + public init(peer: Api.Peer, topMsgId: Int32) { + self.peer = peer + self.topMsgId = topMsgId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("notifyForumTopic", [("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId))]) + } + } + public class Cons_notifyPeer: TypeConstructorDescription { + public var peer: Api.Peer + public init(peer: Api.Peer) { self.peer = peer } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paidReactionPrivacyPeer", [("peer", ConstructorParameterDescription(self.peer))]) + return ("notifyPeer", [("peer", ConstructorParameterDescription(self.peer))]) } } - case paidReactionPrivacyAnonymous - case paidReactionPrivacyDefault - case paidReactionPrivacyPeer(Cons_paidReactionPrivacyPeer) + case notifyBroadcasts + case notifyChats + case notifyForumTopic(Cons_notifyForumTopic) + case notifyPeer(Cons_notifyPeer) + case notifyUsers public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .paidReactionPrivacyAnonymous: + case .notifyBroadcasts: if boxed { - buffer.appendInt32(520887001) + buffer.appendInt32(-703403793) } break - case .paidReactionPrivacyDefault: + case .notifyChats: if boxed { - buffer.appendInt32(543872158) + buffer.appendInt32(-1073230141) } break - case .paidReactionPrivacyPeer(let _data): + case .notifyForumTopic(let _data): if boxed { - buffer.appendInt32(-596837136) + buffer.appendInt32(577659656) } _data.peer.serialize(buffer, true) + serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) + break + case .notifyPeer(let _data): + if boxed { + buffer.appendInt32(-1613493288) + } + _data.peer.serialize(buffer, true) + break + case .notifyUsers: + if boxed { + buffer.appendInt32(-1261946036) + } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .paidReactionPrivacyAnonymous: - return ("paidReactionPrivacyAnonymous", []) - case .paidReactionPrivacyDefault: - return ("paidReactionPrivacyDefault", []) - case .paidReactionPrivacyPeer(let _data): - return ("paidReactionPrivacyPeer", [("peer", ConstructorParameterDescription(_data.peer))]) + case .notifyBroadcasts: + return ("notifyBroadcasts", []) + case .notifyChats: + return ("notifyChats", []) + case .notifyForumTopic(let _data): + return ("notifyForumTopic", [("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId))]) + case .notifyPeer(let _data): + return ("notifyPeer", [("peer", ConstructorParameterDescription(_data.peer))]) + case .notifyUsers: + return ("notifyUsers", []) } } - public static func parse_paidReactionPrivacyAnonymous(_ reader: BufferReader) -> PaidReactionPrivacy? { - return Api.PaidReactionPrivacy.paidReactionPrivacyAnonymous + public static func parse_notifyBroadcasts(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyBroadcasts } - public static func parse_paidReactionPrivacyDefault(_ reader: BufferReader) -> PaidReactionPrivacy? { - return Api.PaidReactionPrivacy.paidReactionPrivacyDefault + public static func parse_notifyChats(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyChats } - public static func parse_paidReactionPrivacyPeer(_ reader: BufferReader) -> PaidReactionPrivacy? { - var _1: Api.InputPeer? + public static func parse_notifyForumTopic(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.NotifyPeer.notifyForumTopic(Cons_notifyForumTopic(peer: _1!, topMsgId: _2!)) + } + else { + return nil + } + } + public static func parse_notifyPeer(_ reader: BufferReader) -> NotifyPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer } let _c1 = _1 != nil if _c1 { - return Api.PaidReactionPrivacy.paidReactionPrivacyPeer(Cons_paidReactionPrivacyPeer(peer: _1!)) + return Api.NotifyPeer.notifyPeer(Cons_notifyPeer(peer: _1!)) + } + else { + return nil + } + } + public static func parse_notifyUsers(_ reader: BufferReader) -> NotifyPeer? { + return Api.NotifyPeer.notifyUsers + } + } +} +public extension Api { + enum OutboxReadDate: TypeConstructorDescription { + public class Cons_outboxReadDate: TypeConstructorDescription { + public var date: Int32 + public init(date: Int32) { + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("outboxReadDate", [("date", ConstructorParameterDescription(self.date))]) + } + } + case outboxReadDate(Cons_outboxReadDate) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .outboxReadDate(let _data): + if boxed { + buffer.appendInt32(1001931436) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .outboxReadDate(let _data): + return ("outboxReadDate", [("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_outboxReadDate(_ reader: BufferReader) -> OutboxReadDate? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.OutboxReadDate.outboxReadDate(Cons_outboxReadDate(date: _1!)) } else { return nil @@ -527,43 +316,53 @@ public extension Api { } } public extension Api { - enum Passkey: TypeConstructorDescription { - public class Cons_passkey: TypeConstructorDescription { + enum Page: TypeConstructorDescription { + public class Cons_page: TypeConstructorDescription { public var flags: Int32 - public var id: String - public var name: String - public var date: Int32 - public var softwareEmojiId: Int64? - public var lastUsageDate: Int32? - public init(flags: Int32, id: String, name: String, date: Int32, softwareEmojiId: Int64?, lastUsageDate: Int32?) { + public var url: String + public var blocks: [Api.PageBlock] + public var photos: [Api.Photo] + public var documents: [Api.Document] + public var views: Int32? + public init(flags: Int32, url: String, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document], views: Int32?) { self.flags = flags - self.id = id - self.name = name - self.date = date - self.softwareEmojiId = softwareEmojiId - self.lastUsageDate = lastUsageDate + self.url = url + self.blocks = blocks + self.photos = photos + self.documents = documents + self.views = views } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passkey", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("name", ConstructorParameterDescription(self.name)), ("date", ConstructorParameterDescription(self.date)), ("softwareEmojiId", ConstructorParameterDescription(self.softwareEmojiId)), ("lastUsageDate", ConstructorParameterDescription(self.lastUsageDate))]) + return ("page", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("blocks", ConstructorParameterDescription(self.blocks)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents)), ("views", ConstructorParameterDescription(self.views))]) } } - case passkey(Cons_passkey) + case page(Cons_page) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passkey(let _data): + case .page(let _data): if boxed { - buffer.appendInt32(-1738457409) + buffer.appendInt32(-1738178803) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.id, buffer: buffer, boxed: false) - serializeString(_data.name, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.softwareEmojiId!, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.lastUsageDate!, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos.count)) + for item in _data.photos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.views!, buffer: buffer, boxed: false) } break } @@ -571,36 +370,40 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .passkey(let _data): - return ("passkey", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("name", ConstructorParameterDescription(_data.name)), ("date", ConstructorParameterDescription(_data.date)), ("softwareEmojiId", ConstructorParameterDescription(_data.softwareEmojiId)), ("lastUsageDate", ConstructorParameterDescription(_data.lastUsageDate))]) + case .page(let _data): + return ("page", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents)), ("views", ConstructorParameterDescription(_data.views))]) } } - public static func parse_passkey(_ reader: BufferReader) -> Passkey? { + public static func parse_page(_ reader: BufferReader) -> Page? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt64() + var _3: [Api.PageBlock]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _4: [Api.Photo]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) + } + var _5: [Api.Document]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Passkey.passkey(Cons_passkey(flags: _1!, id: _2!, name: _3!, date: _4!, softwareEmojiId: _5, lastUsageDate: _6)) + return Api.Page.page(Cons_page(flags: _1!, url: _2!, blocks: _3!, photos: _4!, documents: _5!, views: _6)) } else { return nil @@ -609,634 +412,1113 @@ public extension Api { } } public extension Api { - enum PasswordKdfAlgo: TypeConstructorDescription { - public class Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow: TypeConstructorDescription { - public var salt1: Buffer - public var salt2: Buffer - public var g: Int32 - public var p: Buffer - public init(salt1: Buffer, salt2: Buffer, g: Int32, p: Buffer) { - self.salt1 = salt1 - self.salt2 = salt2 - self.g = g - self.p = p + indirect enum PageBlock: TypeConstructorDescription { + public class Cons_inputPageBlockMap: TypeConstructorDescription { + public var geo: Api.InputGeoPoint + public var zoom: Int32 + public var w: Int32 + public var h: Int32 + public var caption: Api.PageCaption + public init(geo: Api.InputGeoPoint, zoom: Int32, w: Int32, h: Int32, caption: Api.PageCaption) { + self.geo = geo + self.zoom = zoom + self.w = w + self.h = h + self.caption = caption } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", ConstructorParameterDescription(self.salt1)), ("salt2", ConstructorParameterDescription(self.salt2)), ("g", ConstructorParameterDescription(self.g)), ("p", ConstructorParameterDescription(self.p))]) + return ("inputPageBlockMap", [("geo", ConstructorParameterDescription(self.geo)), ("zoom", ConstructorParameterDescription(self.zoom)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("caption", ConstructorParameterDescription(self.caption))]) } } - case passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) - case passwordKdfAlgoUnknown + public class Cons_pageBlockAnchor: TypeConstructorDescription { + public var name: String + public init(name: String) { + self.name = name + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockAnchor", [("name", ConstructorParameterDescription(self.name))]) + } + } + public class Cons_pageBlockAudio: TypeConstructorDescription { + public var audioId: Int64 + public var caption: Api.PageCaption + public init(audioId: Int64, caption: Api.PageCaption) { + self.audioId = audioId + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockAudio", [("audioId", ConstructorParameterDescription(self.audioId)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockAuthorDate: TypeConstructorDescription { + public var author: Api.RichText + public var publishedDate: Int32 + public init(author: Api.RichText, publishedDate: Int32) { + self.author = author + self.publishedDate = publishedDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockAuthorDate", [("author", ConstructorParameterDescription(self.author)), ("publishedDate", ConstructorParameterDescription(self.publishedDate))]) + } + } + public class Cons_pageBlockBlockquote: TypeConstructorDescription { + public var text: Api.RichText + public var caption: Api.RichText + public init(text: Api.RichText, caption: Api.RichText) { + self.text = text + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockBlockquote", [("text", ConstructorParameterDescription(self.text)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockBlockquoteBlocks: TypeConstructorDescription { + public var blocks: [Api.PageBlock] + public var caption: Api.RichText + public init(blocks: [Api.PageBlock], caption: Api.RichText) { + self.blocks = blocks + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockBlockquoteBlocks", [("blocks", ConstructorParameterDescription(self.blocks)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockChannel: TypeConstructorDescription { + public var channel: Api.Chat + public init(channel: Api.Chat) { + self.channel = channel + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockChannel", [("channel", ConstructorParameterDescription(self.channel))]) + } + } + public class Cons_pageBlockCollage: TypeConstructorDescription { + public var items: [Api.PageBlock] + public var caption: Api.PageCaption + public init(items: [Api.PageBlock], caption: Api.PageCaption) { + self.items = items + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockCollage", [("items", ConstructorParameterDescription(self.items)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockCover: TypeConstructorDescription { + public var cover: Api.PageBlock + public init(cover: Api.PageBlock) { + self.cover = cover + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockCover", [("cover", ConstructorParameterDescription(self.cover))]) + } + } + public class Cons_pageBlockDetails: TypeConstructorDescription { + public var flags: Int32 + public var blocks: [Api.PageBlock] + public var title: Api.RichText + public init(flags: Int32, blocks: [Api.PageBlock], title: Api.RichText) { + self.flags = flags + self.blocks = blocks + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockDetails", [("flags", ConstructorParameterDescription(self.flags)), ("blocks", ConstructorParameterDescription(self.blocks)), ("title", ConstructorParameterDescription(self.title))]) + } + } + public class Cons_pageBlockEmbed: TypeConstructorDescription { + public var flags: Int32 + public var url: String? + public var html: String? + public var posterPhotoId: Int64? + public var w: Int32? + public var h: Int32? + public var caption: Api.PageCaption + public init(flags: Int32, url: String?, html: String?, posterPhotoId: Int64?, w: Int32?, h: Int32?, caption: Api.PageCaption) { + self.flags = flags + self.url = url + self.html = html + self.posterPhotoId = posterPhotoId + self.w = w + self.h = h + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockEmbed", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("html", ConstructorParameterDescription(self.html)), ("posterPhotoId", ConstructorParameterDescription(self.posterPhotoId)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockEmbedPost: TypeConstructorDescription { + public var url: String + public var webpageId: Int64 + public var authorPhotoId: Int64 + public var author: String + public var date: Int32 + public var blocks: [Api.PageBlock] + public var caption: Api.PageCaption + public init(url: String, webpageId: Int64, authorPhotoId: Int64, author: String, date: Int32, blocks: [Api.PageBlock], caption: Api.PageCaption) { + self.url = url + self.webpageId = webpageId + self.authorPhotoId = authorPhotoId + self.author = author + self.date = date + self.blocks = blocks + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockEmbedPost", [("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId)), ("authorPhotoId", ConstructorParameterDescription(self.authorPhotoId)), ("author", ConstructorParameterDescription(self.author)), ("date", ConstructorParameterDescription(self.date)), ("blocks", ConstructorParameterDescription(self.blocks)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockFooter: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockFooter", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeader: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeader", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading1: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading1", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading2: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading2", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading3: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading3", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading4: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading4", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading5: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading5", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockHeading6: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockHeading6", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockKicker: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockKicker", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockList: TypeConstructorDescription { + public var items: [Api.PageListItem] + public init(items: [Api.PageListItem]) { + self.items = items + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockList", [("items", ConstructorParameterDescription(self.items))]) + } + } + public class Cons_pageBlockMap: TypeConstructorDescription { + public var geo: Api.GeoPoint + public var zoom: Int32 + public var w: Int32 + public var h: Int32 + public var caption: Api.PageCaption + public init(geo: Api.GeoPoint, zoom: Int32, w: Int32, h: Int32, caption: Api.PageCaption) { + self.geo = geo + self.zoom = zoom + self.w = w + self.h = h + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockMap", [("geo", ConstructorParameterDescription(self.geo)), ("zoom", ConstructorParameterDescription(self.zoom)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockMath: TypeConstructorDescription { + public var source: String + public init(source: String) { + self.source = source + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockMath", [("source", ConstructorParameterDescription(self.source))]) + } + } + public class Cons_pageBlockOrderedList: TypeConstructorDescription { + public var flags: Int32 + public var items: [Api.PageListOrderedItem] + public var start: Int32? + public var type: String? + public init(flags: Int32, items: [Api.PageListOrderedItem], start: Int32?, type: String?) { + self.flags = flags + self.items = items + self.start = start + self.type = type + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockOrderedList", [("flags", ConstructorParameterDescription(self.flags)), ("items", ConstructorParameterDescription(self.items)), ("start", ConstructorParameterDescription(self.start)), ("type", ConstructorParameterDescription(self.type))]) + } + } + public class Cons_pageBlockParagraph: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockParagraph", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockPhoto: TypeConstructorDescription { + public var flags: Int32 + public var photoId: Int64 + public var caption: Api.PageCaption + public var url: String? + public var webpageId: Int64? + public init(flags: Int32, photoId: Int64, caption: Api.PageCaption, url: String?, webpageId: Int64?) { + self.flags = flags + self.photoId = photoId + self.caption = caption + self.url = url + self.webpageId = webpageId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockPhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photoId", ConstructorParameterDescription(self.photoId)), ("caption", ConstructorParameterDescription(self.caption)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId))]) + } + } + public class Cons_pageBlockPreformatted: TypeConstructorDescription { + public var text: Api.RichText + public var language: String + public init(text: Api.RichText, language: String) { + self.text = text + self.language = language + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockPreformatted", [("text", ConstructorParameterDescription(self.text)), ("language", ConstructorParameterDescription(self.language))]) + } + } + public class Cons_pageBlockPullquote: TypeConstructorDescription { + public var text: Api.RichText + public var caption: Api.RichText + public init(text: Api.RichText, caption: Api.RichText) { + self.text = text + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockPullquote", [("text", ConstructorParameterDescription(self.text)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockRelatedArticles: TypeConstructorDescription { + public var title: Api.RichText + public var articles: [Api.PageRelatedArticle] + public init(title: Api.RichText, articles: [Api.PageRelatedArticle]) { + self.title = title + self.articles = articles + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockRelatedArticles", [("title", ConstructorParameterDescription(self.title)), ("articles", ConstructorParameterDescription(self.articles))]) + } + } + public class Cons_pageBlockSlideshow: TypeConstructorDescription { + public var items: [Api.PageBlock] + public var caption: Api.PageCaption + public init(items: [Api.PageBlock], caption: Api.PageCaption) { + self.items = items + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockSlideshow", [("items", ConstructorParameterDescription(self.items)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + public class Cons_pageBlockSubheader: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockSubheader", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockSubtitle: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockSubtitle", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockTable: TypeConstructorDescription { + public var flags: Int32 + public var title: Api.RichText + public var rows: [Api.PageTableRow] + public init(flags: Int32, title: Api.RichText, rows: [Api.PageTableRow]) { + self.flags = flags + self.title = title + self.rows = rows + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockTable", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("rows", ConstructorParameterDescription(self.rows))]) + } + } + public class Cons_pageBlockThinking: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockThinking", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockTitle: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockTitle", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_pageBlockVideo: TypeConstructorDescription { + public var flags: Int32 + public var videoId: Int64 + public var caption: Api.PageCaption + public init(flags: Int32, videoId: Int64, caption: Api.PageCaption) { + self.flags = flags + self.videoId = videoId + self.caption = caption + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageBlockVideo", [("flags", ConstructorParameterDescription(self.flags)), ("videoId", ConstructorParameterDescription(self.videoId)), ("caption", ConstructorParameterDescription(self.caption))]) + } + } + case inputPageBlockMap(Cons_inputPageBlockMap) + case pageBlockAnchor(Cons_pageBlockAnchor) + case pageBlockAudio(Cons_pageBlockAudio) + case pageBlockAuthorDate(Cons_pageBlockAuthorDate) + case pageBlockBlockquote(Cons_pageBlockBlockquote) + case pageBlockBlockquoteBlocks(Cons_pageBlockBlockquoteBlocks) + case pageBlockChannel(Cons_pageBlockChannel) + case pageBlockCollage(Cons_pageBlockCollage) + case pageBlockCover(Cons_pageBlockCover) + case pageBlockDetails(Cons_pageBlockDetails) + case pageBlockDivider + case pageBlockEmbed(Cons_pageBlockEmbed) + case pageBlockEmbedPost(Cons_pageBlockEmbedPost) + case pageBlockFooter(Cons_pageBlockFooter) + case pageBlockHeader(Cons_pageBlockHeader) + case pageBlockHeading1(Cons_pageBlockHeading1) + case pageBlockHeading2(Cons_pageBlockHeading2) + case pageBlockHeading3(Cons_pageBlockHeading3) + case pageBlockHeading4(Cons_pageBlockHeading4) + case pageBlockHeading5(Cons_pageBlockHeading5) + case pageBlockHeading6(Cons_pageBlockHeading6) + case pageBlockKicker(Cons_pageBlockKicker) + case pageBlockList(Cons_pageBlockList) + case pageBlockMap(Cons_pageBlockMap) + case pageBlockMath(Cons_pageBlockMath) + case pageBlockOrderedList(Cons_pageBlockOrderedList) + case pageBlockParagraph(Cons_pageBlockParagraph) + case pageBlockPhoto(Cons_pageBlockPhoto) + case pageBlockPreformatted(Cons_pageBlockPreformatted) + case pageBlockPullquote(Cons_pageBlockPullquote) + case pageBlockRelatedArticles(Cons_pageBlockRelatedArticles) + case pageBlockSlideshow(Cons_pageBlockSlideshow) + case pageBlockSubheader(Cons_pageBlockSubheader) + case pageBlockSubtitle(Cons_pageBlockSubtitle) + case pageBlockTable(Cons_pageBlockTable) + case pageBlockThinking(Cons_pageBlockThinking) + case pageBlockTitle(Cons_pageBlockTitle) + case pageBlockUnsupported + case pageBlockVideo(Cons_pageBlockVideo) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let _data): + case .inputPageBlockMap(let _data): if boxed { - buffer.appendInt32(982592842) + buffer.appendInt32(1464557951) } - serializeBytes(_data.salt1, buffer: buffer, boxed: false) - serializeBytes(_data.salt2, buffer: buffer, boxed: false) - serializeInt32(_data.g, buffer: buffer, boxed: false) - serializeBytes(_data.p, buffer: buffer, boxed: false) + _data.geo.serialize(buffer, true) + serializeInt32(_data.zoom, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + _data.caption.serialize(buffer, true) break - case .passwordKdfAlgoUnknown: + case .pageBlockAnchor(let _data): if boxed { - buffer.appendInt32(-732254058) + buffer.appendInt32(-837994576) } + serializeString(_data.name, buffer: buffer, boxed: false) + break + case .pageBlockAudio(let _data): + if boxed { + buffer.appendInt32(-2143067670) + } + serializeInt64(_data.audioId, buffer: buffer, boxed: false) + _data.caption.serialize(buffer, true) + break + case .pageBlockAuthorDate(let _data): + if boxed { + buffer.appendInt32(-1162877472) + } + _data.author.serialize(buffer, true) + serializeInt32(_data.publishedDate, buffer: buffer, boxed: false) + break + case .pageBlockBlockquote(let _data): + if boxed { + buffer.appendInt32(641563686) + } + _data.text.serialize(buffer, true) + _data.caption.serialize(buffer, true) + break + case .pageBlockBlockquoteBlocks(let _data): + if boxed { + buffer.appendInt32(242108356) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + _data.caption.serialize(buffer, true) + break + case .pageBlockChannel(let _data): + if boxed { + buffer.appendInt32(-283684427) + } + _data.channel.serialize(buffer, true) + break + case .pageBlockCollage(let _data): + if boxed { + buffer.appendInt32(1705048653) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.items.count)) + for item in _data.items { + item.serialize(buffer, true) + } + _data.caption.serialize(buffer, true) + break + case .pageBlockCover(let _data): + if boxed { + buffer.appendInt32(972174080) + } + _data.cover.serialize(buffer, true) + break + case .pageBlockDetails(let _data): + if boxed { + buffer.appendInt32(1987480557) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + _data.title.serialize(buffer, true) + break + case .pageBlockDivider: + if boxed { + buffer.appendInt32(-618614392) + } + break + case .pageBlockEmbed(let _data): + if boxed { + buffer.appendInt32(-1468953147) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.html!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.posterPhotoId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.w!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.h!, buffer: buffer, boxed: false) + } + _data.caption.serialize(buffer, true) + break + case .pageBlockEmbedPost(let _data): + if boxed { + buffer.appendInt32(-229005301) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.webpageId, buffer: buffer, boxed: false) + serializeInt64(_data.authorPhotoId, buffer: buffer, boxed: false) + serializeString(_data.author, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + _data.caption.serialize(buffer, true) + break + case .pageBlockFooter(let _data): + if boxed { + buffer.appendInt32(1216809369) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeader(let _data): + if boxed { + buffer.appendInt32(-1076861716) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading1(let _data): + if boxed { + buffer.appendInt32(-1157691601) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading2(let _data): + if boxed { + buffer.appendInt32(158018284) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading3(let _data): + if boxed { + buffer.appendInt32(1743204781) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading4(let _data): + if boxed { + buffer.appendInt32(-1254983893) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading5(let _data): + if boxed { + buffer.appendInt32(-608277398) + } + _data.text.serialize(buffer, true) + break + case .pageBlockHeading6(let _data): + if boxed { + buffer.appendInt32(1747599785) + } + _data.text.serialize(buffer, true) + break + case .pageBlockKicker(let _data): + if boxed { + buffer.appendInt32(504660880) + } + _data.text.serialize(buffer, true) + break + case .pageBlockList(let _data): + if boxed { + buffer.appendInt32(-454524911) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.items.count)) + for item in _data.items { + item.serialize(buffer, true) + } + break + case .pageBlockMap(let _data): + if boxed { + buffer.appendInt32(-1538310410) + } + _data.geo.serialize(buffer, true) + serializeInt32(_data.zoom, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + _data.caption.serialize(buffer, true) + break + case .pageBlockMath(let _data): + if boxed { + buffer.appendInt32(1493699616) + } + serializeString(_data.source, buffer: buffer, boxed: false) + break + case .pageBlockOrderedList(let _data): + if boxed { + buffer.appendInt32(534181569) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.items.count)) + for item in _data.items { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.start!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.type!, buffer: buffer, boxed: false) + } + break + case .pageBlockParagraph(let _data): + if boxed { + buffer.appendInt32(1182402406) + } + _data.text.serialize(buffer, true) + break + case .pageBlockPhoto(let _data): + if boxed { + buffer.appendInt32(391759200) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.photoId, buffer: buffer, boxed: false) + _data.caption.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.webpageId!, buffer: buffer, boxed: false) + } + break + case .pageBlockPreformatted(let _data): + if boxed { + buffer.appendInt32(-1066346178) + } + _data.text.serialize(buffer, true) + serializeString(_data.language, buffer: buffer, boxed: false) + break + case .pageBlockPullquote(let _data): + if boxed { + buffer.appendInt32(1329878739) + } + _data.text.serialize(buffer, true) + _data.caption.serialize(buffer, true) + break + case .pageBlockRelatedArticles(let _data): + if boxed { + buffer.appendInt32(370236054) + } + _data.title.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.articles.count)) + for item in _data.articles { + item.serialize(buffer, true) + } + break + case .pageBlockSlideshow(let _data): + if boxed { + buffer.appendInt32(52401552) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.items.count)) + for item in _data.items { + item.serialize(buffer, true) + } + _data.caption.serialize(buffer, true) + break + case .pageBlockSubheader(let _data): + if boxed { + buffer.appendInt32(-248793375) + } + _data.text.serialize(buffer, true) + break + case .pageBlockSubtitle(let _data): + if boxed { + buffer.appendInt32(-1879401953) + } + _data.text.serialize(buffer, true) + break + case .pageBlockTable(let _data): + if boxed { + buffer.appendInt32(-1085412734) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.title.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.rows.count)) + for item in _data.rows { + item.serialize(buffer, true) + } + break + case .pageBlockThinking(let _data): + if boxed { + buffer.appendInt32(1009361890) + } + _data.text.serialize(buffer, true) + break + case .pageBlockTitle(let _data): + if boxed { + buffer.appendInt32(1890305021) + } + _data.text.serialize(buffer, true) + break + case .pageBlockUnsupported: + if boxed { + buffer.appendInt32(324435594) + } + break + case .pageBlockVideo(let _data): + if boxed { + buffer.appendInt32(2089805750) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.videoId, buffer: buffer, boxed: false) + _data.caption.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let _data): - return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", ConstructorParameterDescription(_data.salt1)), ("salt2", ConstructorParameterDescription(_data.salt2)), ("g", ConstructorParameterDescription(_data.g)), ("p", ConstructorParameterDescription(_data.p))]) - case .passwordKdfAlgoUnknown: - return ("passwordKdfAlgoUnknown", []) + case .inputPageBlockMap(let _data): + return ("inputPageBlockMap", [("geo", ConstructorParameterDescription(_data.geo)), ("zoom", ConstructorParameterDescription(_data.zoom)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockAnchor(let _data): + return ("pageBlockAnchor", [("name", ConstructorParameterDescription(_data.name))]) + case .pageBlockAudio(let _data): + return ("pageBlockAudio", [("audioId", ConstructorParameterDescription(_data.audioId)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockAuthorDate(let _data): + return ("pageBlockAuthorDate", [("author", ConstructorParameterDescription(_data.author)), ("publishedDate", ConstructorParameterDescription(_data.publishedDate))]) + case .pageBlockBlockquote(let _data): + return ("pageBlockBlockquote", [("text", ConstructorParameterDescription(_data.text)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockBlockquoteBlocks(let _data): + return ("pageBlockBlockquoteBlocks", [("blocks", ConstructorParameterDescription(_data.blocks)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockChannel(let _data): + return ("pageBlockChannel", [("channel", ConstructorParameterDescription(_data.channel))]) + case .pageBlockCollage(let _data): + return ("pageBlockCollage", [("items", ConstructorParameterDescription(_data.items)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockCover(let _data): + return ("pageBlockCover", [("cover", ConstructorParameterDescription(_data.cover))]) + case .pageBlockDetails(let _data): + return ("pageBlockDetails", [("flags", ConstructorParameterDescription(_data.flags)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("title", ConstructorParameterDescription(_data.title))]) + case .pageBlockDivider: + return ("pageBlockDivider", []) + case .pageBlockEmbed(let _data): + return ("pageBlockEmbed", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("html", ConstructorParameterDescription(_data.html)), ("posterPhotoId", ConstructorParameterDescription(_data.posterPhotoId)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockEmbedPost(let _data): + return ("pageBlockEmbedPost", [("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId)), ("authorPhotoId", ConstructorParameterDescription(_data.authorPhotoId)), ("author", ConstructorParameterDescription(_data.author)), ("date", ConstructorParameterDescription(_data.date)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockFooter(let _data): + return ("pageBlockFooter", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeader(let _data): + return ("pageBlockHeader", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading1(let _data): + return ("pageBlockHeading1", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading2(let _data): + return ("pageBlockHeading2", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading3(let _data): + return ("pageBlockHeading3", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading4(let _data): + return ("pageBlockHeading4", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading5(let _data): + return ("pageBlockHeading5", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockHeading6(let _data): + return ("pageBlockHeading6", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockKicker(let _data): + return ("pageBlockKicker", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockList(let _data): + return ("pageBlockList", [("items", ConstructorParameterDescription(_data.items))]) + case .pageBlockMap(let _data): + return ("pageBlockMap", [("geo", ConstructorParameterDescription(_data.geo)), ("zoom", ConstructorParameterDescription(_data.zoom)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockMath(let _data): + return ("pageBlockMath", [("source", ConstructorParameterDescription(_data.source))]) + case .pageBlockOrderedList(let _data): + return ("pageBlockOrderedList", [("flags", ConstructorParameterDescription(_data.flags)), ("items", ConstructorParameterDescription(_data.items)), ("start", ConstructorParameterDescription(_data.start)), ("type", ConstructorParameterDescription(_data.type))]) + case .pageBlockParagraph(let _data): + return ("pageBlockParagraph", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockPhoto(let _data): + return ("pageBlockPhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("caption", ConstructorParameterDescription(_data.caption)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId))]) + case .pageBlockPreformatted(let _data): + return ("pageBlockPreformatted", [("text", ConstructorParameterDescription(_data.text)), ("language", ConstructorParameterDescription(_data.language))]) + case .pageBlockPullquote(let _data): + return ("pageBlockPullquote", [("text", ConstructorParameterDescription(_data.text)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockRelatedArticles(let _data): + return ("pageBlockRelatedArticles", [("title", ConstructorParameterDescription(_data.title)), ("articles", ConstructorParameterDescription(_data.articles))]) + case .pageBlockSlideshow(let _data): + return ("pageBlockSlideshow", [("items", ConstructorParameterDescription(_data.items)), ("caption", ConstructorParameterDescription(_data.caption))]) + case .pageBlockSubheader(let _data): + return ("pageBlockSubheader", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockSubtitle(let _data): + return ("pageBlockSubtitle", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockTable(let _data): + return ("pageBlockTable", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("rows", ConstructorParameterDescription(_data.rows))]) + case .pageBlockThinking(let _data): + return ("pageBlockThinking", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockTitle(let _data): + return ("pageBlockTitle", [("text", ConstructorParameterDescription(_data.text))]) + case .pageBlockUnsupported: + return ("pageBlockUnsupported", []) + case .pageBlockVideo(let _data): + return ("pageBlockVideo", [("flags", ConstructorParameterDescription(_data.flags)), ("videoId", ConstructorParameterDescription(_data.videoId)), ("caption", ConstructorParameterDescription(_data.caption))]) } } - public static func parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(_ reader: BufferReader) -> PasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) + public static func parse_inputPageBlockMap(_ reader: BufferReader) -> PageBlock? { + var _1: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.PageCaption? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!)) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageBlock.inputPageBlockMap(Cons_inputPageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!)) } else { return nil } } - public static func parse_passwordKdfAlgoUnknown(_ reader: BufferReader) -> PasswordKdfAlgo? { - return Api.PasswordKdfAlgo.passwordKdfAlgoUnknown - } - } -} -public extension Api { - enum PaymentCharge: TypeConstructorDescription { - public class Cons_paymentCharge: TypeConstructorDescription { - public var id: String - public var providerChargeId: String - public init(id: String, providerChargeId: String) { - self.id = id - self.providerChargeId = providerChargeId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentCharge", [("id", ConstructorParameterDescription(self.id)), ("providerChargeId", ConstructorParameterDescription(self.providerChargeId))]) - } - } - case paymentCharge(Cons_paymentCharge) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentCharge(let _data): - if boxed { - buffer.appendInt32(-368917890) - } - serializeString(_data.id, buffer: buffer, boxed: false) - serializeString(_data.providerChargeId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentCharge(let _data): - return ("paymentCharge", [("id", ConstructorParameterDescription(_data.id)), ("providerChargeId", ConstructorParameterDescription(_data.providerChargeId))]) - } - } - - public static func parse_paymentCharge(_ reader: BufferReader) -> PaymentCharge? { + public static func parse_pageBlockAnchor(_ reader: BufferReader) -> PageBlock? { var _1: String? _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockAnchor(Cons_pageBlockAnchor(name: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.PageCaption? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PaymentCharge.paymentCharge(Cons_paymentCharge(id: _1!, providerChargeId: _2!)) + return Api.PageBlock.pageBlockAudio(Cons_pageBlockAudio(audioId: _1!, caption: _2!)) } else { return nil } } - } -} -public extension Api { - enum PaymentFormMethod: TypeConstructorDescription { - public class Cons_paymentFormMethod: TypeConstructorDescription { - public var url: String - public var title: String - public init(url: String, title: String) { - self.url = url - self.title = title + public static func parse_pageBlockAuthorDate(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentFormMethod", [("url", ConstructorParameterDescription(self.url)), ("title", ConstructorParameterDescription(self.title))]) - } - } - case paymentFormMethod(Cons_paymentFormMethod) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentFormMethod(let _data): - if boxed { - buffer.appendInt32(-1996951013) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentFormMethod(let _data): - return ("paymentFormMethod", [("url", ConstructorParameterDescription(_data.url)), ("title", ConstructorParameterDescription(_data.title))]) - } - } - - public static func parse_paymentFormMethod(_ reader: BufferReader) -> PaymentFormMethod? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.PaymentFormMethod.paymentFormMethod(Cons_paymentFormMethod(url: _1!, title: _2!)) + return Api.PageBlock.pageBlockAuthorDate(Cons_pageBlockAuthorDate(author: _1!, publishedDate: _2!)) } else { return nil } } - } -} -public extension Api { - enum PaymentRequestedInfo: TypeConstructorDescription { - public class Cons_paymentRequestedInfo: TypeConstructorDescription { - public var flags: Int32 - public var name: String? - public var phone: String? - public var email: String? - public var shippingAddress: Api.PostAddress? - public init(flags: Int32, name: String?, phone: String?, email: String?, shippingAddress: Api.PostAddress?) { - self.flags = flags - self.name = name - self.phone = phone - self.email = email - self.shippingAddress = shippingAddress + public static func parse_pageBlockBlockquote(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentRequestedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("name", ConstructorParameterDescription(self.name)), ("phone", ConstructorParameterDescription(self.phone)), ("email", ConstructorParameterDescription(self.email)), ("shippingAddress", ConstructorParameterDescription(self.shippingAddress))]) + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockBlockquote(Cons_pageBlockBlockquote(text: _1!, caption: _2!)) + } + else { + return nil } } - case paymentRequestedInfo(Cons_paymentRequestedInfo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentRequestedInfo(let _data): - if boxed { - buffer.appendInt32(-1868808300) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.name!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.phone!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.email!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.shippingAddress!.serialize(buffer, true) - } - break + public static func parse_pageBlockBlockquoteBlocks(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockBlockquoteBlocks(Cons_pageBlockBlockquoteBlocks(blocks: _1!, caption: _2!)) + } + else { + return nil } } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentRequestedInfo(let _data): - return ("paymentRequestedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("name", ConstructorParameterDescription(_data.name)), ("phone", ConstructorParameterDescription(_data.phone)), ("email", ConstructorParameterDescription(_data.email)), ("shippingAddress", ConstructorParameterDescription(_data.shippingAddress))]) + public static func parse_pageBlockChannel(_ reader: BufferReader) -> PageBlock? { + var _1: Api.Chat? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Chat + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockChannel(Cons_pageBlockChannel(channel: _1!)) + } + else { + return nil } } - - public static func parse_paymentRequestedInfo(_ reader: BufferReader) -> PaymentRequestedInfo? { + public static func parse_pageBlockCollage(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _2: Api.PageCaption? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockCollage(Cons_pageBlockCollage(items: _1!, caption: _2!)) + } + else { + return nil + } + } + public static func parse_pageBlockCover(_ reader: BufferReader) -> PageBlock? { + var _1: Api.PageBlock? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PageBlock + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockCover(Cons_pageBlockCover(cover: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockDetails(_ reader: BufferReader) -> PageBlock? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PageBlock]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _3: Api.RichText? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.PageBlock.pageBlockDetails(Cons_pageBlockDetails(flags: _1!, blocks: _2!, title: _3!)) + } + else { + return nil + } + } + public static func parse_pageBlockDivider(_ reader: BufferReader) -> PageBlock? { + return Api.PageBlock.pageBlockDivider + } + public static func parse_pageBlockEmbed(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _2 = parseString(reader) } var _3: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseString(reader) + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _4 = reader.readInt64() } - var _5: Api.PostAddress? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PostAddress - } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _6 = reader.readInt32() + } + var _7: Api.PageCaption? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PageCaption } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PaymentRequestedInfo.paymentRequestedInfo(Cons_paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PageBlock.pageBlockEmbed(Cons_pageBlockEmbed(flags: _1!, url: _2, html: _3, posterPhotoId: _4, w: _5, h: _6, caption: _7!)) } else { return nil } } - } -} -public extension Api { - enum PaymentSavedCredentials: TypeConstructorDescription { - public class Cons_paymentSavedCredentialsCard: TypeConstructorDescription { - public var id: String - public var title: String - public init(id: String, title: String) { - self.id = id - self.title = title - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentSavedCredentialsCard", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title))]) - } - } - case paymentSavedCredentialsCard(Cons_paymentSavedCredentialsCard) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentSavedCredentialsCard(let _data): - if boxed { - buffer.appendInt32(-842892769) - } - serializeString(_data.id, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentSavedCredentialsCard(let _data): - return ("paymentSavedCredentialsCard", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title))]) - } - } - - public static func parse_paymentSavedCredentialsCard(_ reader: BufferReader) -> PaymentSavedCredentials? { + public static func parse_pageBlockEmbedPost(_ reader: BufferReader) -> PageBlock? { var _1: String? _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(Cons_paymentSavedCredentialsCard(id: _1!, title: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum Peer: TypeConstructorDescription { - public class Cons_peerChannel: TypeConstructorDescription { - public var channelId: Int64 - public init(channelId: Int64) { - self.channelId = channelId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerChannel", [("channelId", ConstructorParameterDescription(self.channelId))]) - } - } - public class Cons_peerChat: TypeConstructorDescription { - public var chatId: Int64 - public init(chatId: Int64) { - self.chatId = chatId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerChat", [("chatId", ConstructorParameterDescription(self.chatId))]) - } - } - public class Cons_peerUser: TypeConstructorDescription { - public var userId: Int64 - public init(userId: Int64) { - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerUser", [("userId", ConstructorParameterDescription(self.userId))]) - } - } - case peerChannel(Cons_peerChannel) - case peerChat(Cons_peerChat) - case peerUser(Cons_peerUser) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerChannel(let _data): - if boxed { - buffer.appendInt32(-1566230754) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - break - case .peerChat(let _data): - if boxed { - buffer.appendInt32(918946202) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - break - case .peerUser(let _data): - if boxed { - buffer.appendInt32(1498486562) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerChannel(let _data): - return ("peerChannel", [("channelId", ConstructorParameterDescription(_data.channelId))]) - case .peerChat(let _data): - return ("peerChat", [("chatId", ConstructorParameterDescription(_data.chatId))]) - case .peerUser(let _data): - return ("peerUser", [("userId", ConstructorParameterDescription(_data.userId))]) - } - } - - public static func parse_peerChannel(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerChannel(Cons_peerChannel(channelId: _1!)) - } - else { - return nil - } - } - public static func parse_peerChat(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerChat(Cons_peerChat(chatId: _1!)) - } - else { - return nil - } - } - public static func parse_peerUser(_ reader: BufferReader) -> Peer? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Peer.peerUser(Cons_peerUser(userId: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PeerBlocked: TypeConstructorDescription { - public class Cons_peerBlocked: TypeConstructorDescription { - public var peerId: Api.Peer - public var date: Int32 - public init(peerId: Api.Peer, date: Int32) { - self.peerId = peerId - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerBlocked", [("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case peerBlocked(Cons_peerBlocked) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerBlocked(let _data): - if boxed { - buffer.appendInt32(-386039788) - } - _data.peerId.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerBlocked(let _data): - return ("peerBlocked", [("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_peerBlocked(_ reader: BufferReader) -> PeerBlocked? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PeerBlocked.peerBlocked(Cons_peerBlocked(peerId: _1!, date: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PeerColor: TypeConstructorDescription { - public class Cons_inputPeerColorCollectible: TypeConstructorDescription { - public var collectibleId: Int64 - public init(collectibleId: Int64) { - self.collectibleId = collectibleId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputPeerColorCollectible", [("collectibleId", ConstructorParameterDescription(self.collectibleId))]) - } - } - public class Cons_peerColor: TypeConstructorDescription { - public var flags: Int32 - public var color: Int32? - public var backgroundEmojiId: Int64? - public init(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) { - self.flags = flags - self.color = color - self.backgroundEmojiId = backgroundEmojiId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColor", [("flags", ConstructorParameterDescription(self.flags)), ("color", ConstructorParameterDescription(self.color)), ("backgroundEmojiId", ConstructorParameterDescription(self.backgroundEmojiId))]) - } - } - public class Cons_peerColorCollectible: TypeConstructorDescription { - public var flags: Int32 - public var collectibleId: Int64 - public var giftEmojiId: Int64 - public var backgroundEmojiId: Int64 - public var accentColor: Int32 - public var colors: [Int32] - public var darkAccentColor: Int32? - public var darkColors: [Int32]? - public init(flags: Int32, collectibleId: Int64, giftEmojiId: Int64, backgroundEmojiId: Int64, accentColor: Int32, colors: [Int32], darkAccentColor: Int32?, darkColors: [Int32]?) { - self.flags = flags - self.collectibleId = collectibleId - self.giftEmojiId = giftEmojiId - self.backgroundEmojiId = backgroundEmojiId - self.accentColor = accentColor - self.colors = colors - self.darkAccentColor = darkAccentColor - self.darkColors = darkColors - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColorCollectible", [("flags", ConstructorParameterDescription(self.flags)), ("collectibleId", ConstructorParameterDescription(self.collectibleId)), ("giftEmojiId", ConstructorParameterDescription(self.giftEmojiId)), ("backgroundEmojiId", ConstructorParameterDescription(self.backgroundEmojiId)), ("accentColor", ConstructorParameterDescription(self.accentColor)), ("colors", ConstructorParameterDescription(self.colors)), ("darkAccentColor", ConstructorParameterDescription(self.darkAccentColor)), ("darkColors", ConstructorParameterDescription(self.darkColors))]) - } - } - case inputPeerColorCollectible(Cons_inputPeerColorCollectible) - case peerColor(Cons_peerColor) - case peerColorCollectible(Cons_peerColorCollectible) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPeerColorCollectible(let _data): - if boxed { - buffer.appendInt32(-1192589655) - } - serializeInt64(_data.collectibleId, buffer: buffer, boxed: false) - break - case .peerColor(let _data): - if boxed { - buffer.appendInt32(-1253352753) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.color!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt64(_data.backgroundEmojiId!, buffer: buffer, boxed: false) - } - break - case .peerColorCollectible(let _data): - if boxed { - buffer.appendInt32(-1178573926) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.collectibleId, buffer: buffer, boxed: false) - serializeInt64(_data.giftEmojiId, buffer: buffer, boxed: false) - serializeInt64(_data.backgroundEmojiId, buffer: buffer, boxed: false) - serializeInt32(_data.accentColor, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.colors.count)) - for item in _data.colors { - serializeInt32(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.darkAccentColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.darkColors!.count)) - for item in _data.darkColors! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputPeerColorCollectible(let _data): - return ("inputPeerColorCollectible", [("collectibleId", ConstructorParameterDescription(_data.collectibleId))]) - case .peerColor(let _data): - return ("peerColor", [("flags", ConstructorParameterDescription(_data.flags)), ("color", ConstructorParameterDescription(_data.color)), ("backgroundEmojiId", ConstructorParameterDescription(_data.backgroundEmojiId))]) - case .peerColorCollectible(let _data): - return ("peerColorCollectible", [("flags", ConstructorParameterDescription(_data.flags)), ("collectibleId", ConstructorParameterDescription(_data.collectibleId)), ("giftEmojiId", ConstructorParameterDescription(_data.giftEmojiId)), ("backgroundEmojiId", ConstructorParameterDescription(_data.backgroundEmojiId)), ("accentColor", ConstructorParameterDescription(_data.accentColor)), ("colors", ConstructorParameterDescription(_data.colors)), ("darkAccentColor", ConstructorParameterDescription(_data.darkAccentColor)), ("darkColors", ConstructorParameterDescription(_data.darkColors))]) - } - } - - public static func parse_inputPeerColorCollectible(_ reader: BufferReader) -> PeerColor? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.PeerColor.inputPeerColorCollectible(Cons_inputPeerColorCollectible(collectibleId: _1!)) - } - else { - return nil - } - } - public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.PeerColor.peerColor(Cons_peerColor(flags: _1!, color: _2, backgroundEmojiId: _3)) - } - else { - return nil - } - } - public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? { - var _1: Int32? - _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() var _3: Int64? _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() + var _4: String? + _4 = parseString(reader) var _5: Int32? _5 = reader.readInt32() - var _6: [Int32]? + var _6: [Api.PageBlock]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) } - var _7: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = reader.readInt32() - } - var _8: [Int32]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } + var _7: Api.PageCaption? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PageCaption } let _c1 = _1 != nil let _c2 = _2 != nil @@ -1244,988 +1526,410 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PeerColor.peerColorCollectible(Cons_peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8)) + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PageBlock.pageBlockEmbedPost(Cons_pageBlockEmbedPost(url: _1!, webpageId: _2!, authorPhotoId: _3!, author: _4!, date: _5!, blocks: _6!, caption: _7!)) } else { return nil } } - } -} -public extension Api { - enum PeerLocated: TypeConstructorDescription { - public class Cons_peerLocated: TypeConstructorDescription { - public var peer: Api.Peer - public var expires: Int32 - public var distance: Int32 - public init(peer: Api.Peer, expires: Int32, distance: Int32) { - self.peer = peer - self.expires = expires - self.distance = distance - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerLocated", [("peer", ConstructorParameterDescription(self.peer)), ("expires", ConstructorParameterDescription(self.expires)), ("distance", ConstructorParameterDescription(self.distance))]) - } - } - public class Cons_peerSelfLocated: TypeConstructorDescription { - public var expires: Int32 - public init(expires: Int32) { - self.expires = expires - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerSelfLocated", [("expires", ConstructorParameterDescription(self.expires))]) - } - } - case peerLocated(Cons_peerLocated) - case peerSelfLocated(Cons_peerSelfLocated) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerLocated(let _data): - if boxed { - buffer.appendInt32(-901375139) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.expires, buffer: buffer, boxed: false) - serializeInt32(_data.distance, buffer: buffer, boxed: false) - break - case .peerSelfLocated(let _data): - if boxed { - buffer.appendInt32(-118740917) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerLocated(let _data): - return ("peerLocated", [("peer", ConstructorParameterDescription(_data.peer)), ("expires", ConstructorParameterDescription(_data.expires)), ("distance", ConstructorParameterDescription(_data.distance))]) - case .peerSelfLocated(let _data): - return ("peerSelfLocated", [("expires", ConstructorParameterDescription(_data.expires))]) - } - } - - public static func parse_peerLocated(_ reader: BufferReader) -> PeerLocated? { - var _1: Api.Peer? + public static func parse_pageBlockFooter(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockFooter(Cons_pageBlockFooter(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeader(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeader(Cons_pageBlockHeader(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading1(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading1(Cons_pageBlockHeading1(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading2(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading2(Cons_pageBlockHeading2(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading3(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading3(Cons_pageBlockHeading3(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading4(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading4(Cons_pageBlockHeading4(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading5(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading5(Cons_pageBlockHeading5(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockHeading6(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockHeading6(Cons_pageBlockHeading6(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockKicker(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockKicker(Cons_pageBlockKicker(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockList(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageListItem]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListItem.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockList(Cons_pageBlockList(items: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockMap(_ reader: BufferReader) -> PageBlock? { + var _1: Api.GeoPoint? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GeoPoint } var _2: Int32? _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.PageCaption? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageBlock.pageBlockMap(Cons_pageBlockMap(geo: _1!, zoom: _2!, w: _3!, h: _4!, caption: _5!)) + } + else { + return nil + } + } + public static func parse_pageBlockMath(_ reader: BufferReader) -> PageBlock? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockMath(Cons_pageBlockMath(source: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockOrderedList(_ reader: BufferReader) -> PageBlock? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PageListOrderedItem]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageListOrderedItem.self) + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PageBlock.pageBlockOrderedList(Cons_pageBlockOrderedList(flags: _1!, items: _2!, start: _3, type: _4)) + } + else { + return nil + } + } + public static func parse_pageBlockParagraph(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockParagraph(Cons_pageBlockParagraph(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockPhoto(_ reader: BufferReader) -> PageBlock? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.PageCaption? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageBlock.pageBlockPhoto(Cons_pageBlockPhoto(flags: _1!, photoId: _2!, caption: _3!, url: _4, webpageId: _5)) + } + else { + return nil + } + } + public static func parse_pageBlockPreformatted(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockPreformatted(Cons_pageBlockPreformatted(text: _1!, language: _2!)) + } + else { + return nil + } + } + public static func parse_pageBlockPullquote(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockPullquote(Cons_pageBlockPullquote(text: _1!, caption: _2!)) + } + else { + return nil + } + } + public static func parse_pageBlockRelatedArticles(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: [Api.PageRelatedArticle]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageRelatedArticle.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockRelatedArticles(Cons_pageBlockRelatedArticles(title: _1!, articles: _2!)) + } + else { + return nil + } + } + public static func parse_pageBlockSlideshow(_ reader: BufferReader) -> PageBlock? { + var _1: [Api.PageBlock]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _2: Api.PageCaption? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PageCaption + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageBlock.pageBlockSlideshow(Cons_pageBlockSlideshow(items: _1!, caption: _2!)) + } + else { + return nil + } + } + public static func parse_pageBlockSubheader(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockSubheader(Cons_pageBlockSubheader(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockSubtitle(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.PageBlock.pageBlockSubtitle(Cons_pageBlockSubtitle(text: _1!)) + } + else { + return nil + } + } + public static func parse_pageBlockTable(_ reader: BufferReader) -> PageBlock? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _3: [Api.PageTableRow]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableRow.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.PeerLocated.peerLocated(Cons_peerLocated(peer: _1!, expires: _2!, distance: _3!)) + return Api.PageBlock.pageBlockTable(Cons_pageBlockTable(flags: _1!, title: _2!, rows: _3!)) } else { return nil } } - public static func parse_peerSelfLocated(_ reader: BufferReader) -> PeerLocated? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_pageBlockThinking(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil if _c1 { - return Api.PeerLocated.peerSelfLocated(Cons_peerSelfLocated(expires: _1!)) + return Api.PageBlock.pageBlockThinking(Cons_pageBlockThinking(text: _1!)) } else { return nil } } - } -} -public extension Api { - enum PeerNotifySettings: TypeConstructorDescription { - public class Cons_peerNotifySettings: TypeConstructorDescription { - public var flags: Int32 - public var showPreviews: Api.Bool? - public var silent: Api.Bool? - public var muteUntil: Int32? - public var iosSound: Api.NotificationSound? - public var androidSound: Api.NotificationSound? - public var otherSound: Api.NotificationSound? - public var storiesMuted: Api.Bool? - public var storiesHideSender: Api.Bool? - public var storiesIosSound: Api.NotificationSound? - public var storiesAndroidSound: Api.NotificationSound? - public var storiesOtherSound: Api.NotificationSound? - public init(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesIosSound: Api.NotificationSound?, storiesAndroidSound: Api.NotificationSound?, storiesOtherSound: Api.NotificationSound?) { - self.flags = flags - self.showPreviews = showPreviews - self.silent = silent - self.muteUntil = muteUntil - self.iosSound = iosSound - self.androidSound = androidSound - self.otherSound = otherSound - self.storiesMuted = storiesMuted - self.storiesHideSender = storiesHideSender - self.storiesIosSound = storiesIosSound - self.storiesAndroidSound = storiesAndroidSound - self.storiesOtherSound = storiesOtherSound - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerNotifySettings", [("flags", ConstructorParameterDescription(self.flags)), ("showPreviews", ConstructorParameterDescription(self.showPreviews)), ("silent", ConstructorParameterDescription(self.silent)), ("muteUntil", ConstructorParameterDescription(self.muteUntil)), ("iosSound", ConstructorParameterDescription(self.iosSound)), ("androidSound", ConstructorParameterDescription(self.androidSound)), ("otherSound", ConstructorParameterDescription(self.otherSound)), ("storiesMuted", ConstructorParameterDescription(self.storiesMuted)), ("storiesHideSender", ConstructorParameterDescription(self.storiesHideSender)), ("storiesIosSound", ConstructorParameterDescription(self.storiesIosSound)), ("storiesAndroidSound", ConstructorParameterDescription(self.storiesAndroidSound)), ("storiesOtherSound", ConstructorParameterDescription(self.storiesOtherSound))]) - } - } - case peerNotifySettings(Cons_peerNotifySettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerNotifySettings(let _data): - if boxed { - buffer.appendInt32(-1721619444) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.showPreviews!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.silent!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.muteUntil!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.iosSound!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.androidSound!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.otherSound!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.storiesMuted!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - _data.storiesHideSender!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - _data.storiesIosSound!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - _data.storiesAndroidSound!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.storiesOtherSound!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerNotifySettings(let _data): - return ("peerNotifySettings", [("flags", ConstructorParameterDescription(_data.flags)), ("showPreviews", ConstructorParameterDescription(_data.showPreviews)), ("silent", ConstructorParameterDescription(_data.silent)), ("muteUntil", ConstructorParameterDescription(_data.muteUntil)), ("iosSound", ConstructorParameterDescription(_data.iosSound)), ("androidSound", ConstructorParameterDescription(_data.androidSound)), ("otherSound", ConstructorParameterDescription(_data.otherSound)), ("storiesMuted", ConstructorParameterDescription(_data.storiesMuted)), ("storiesHideSender", ConstructorParameterDescription(_data.storiesHideSender)), ("storiesIosSound", ConstructorParameterDescription(_data.storiesIosSound)), ("storiesAndroidSound", ConstructorParameterDescription(_data.storiesAndroidSound)), ("storiesOtherSound", ConstructorParameterDescription(_data.storiesOtherSound))]) - } - } - - public static func parse_peerNotifySettings(_ reader: BufferReader) -> PeerNotifySettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: Api.NotificationSound? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - var _6: Api.NotificationSound? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - var _7: Api.NotificationSound? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - var _8: Api.Bool? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _9: Api.Bool? - if Int(_1!) & Int(1 << 7) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _10: Api.NotificationSound? - if Int(_1!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - var _11: Api.NotificationSound? - if Int(_1!) & Int(1 << 9) != 0 { - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - var _12: Api.NotificationSound? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.NotificationSound - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 9) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 10) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PeerNotifySettings.peerNotifySettings(Cons_peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PeerSettings: TypeConstructorDescription { - public class Cons_peerSettings: TypeConstructorDescription { - public var flags: Int32 - public var geoDistance: Int32? - public var requestChatTitle: String? - public var requestChatDate: Int32? - public var businessBotId: Int64? - public var businessBotManageUrl: String? - public var chargePaidMessageStars: Int64? - public var registrationMonth: String? - public var phoneCountry: String? - public var nameChangeDate: Int32? - public var photoChangeDate: Int32? - public init(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?, chargePaidMessageStars: Int64?, registrationMonth: String?, phoneCountry: String?, nameChangeDate: Int32?, photoChangeDate: Int32?) { - self.flags = flags - self.geoDistance = geoDistance - self.requestChatTitle = requestChatTitle - self.requestChatDate = requestChatDate - self.businessBotId = businessBotId - self.businessBotManageUrl = businessBotManageUrl - self.chargePaidMessageStars = chargePaidMessageStars - self.registrationMonth = registrationMonth - self.phoneCountry = phoneCountry - self.nameChangeDate = nameChangeDate - self.photoChangeDate = photoChangeDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerSettings", [("flags", ConstructorParameterDescription(self.flags)), ("geoDistance", ConstructorParameterDescription(self.geoDistance)), ("requestChatTitle", ConstructorParameterDescription(self.requestChatTitle)), ("requestChatDate", ConstructorParameterDescription(self.requestChatDate)), ("businessBotId", ConstructorParameterDescription(self.businessBotId)), ("businessBotManageUrl", ConstructorParameterDescription(self.businessBotManageUrl)), ("chargePaidMessageStars", ConstructorParameterDescription(self.chargePaidMessageStars)), ("registrationMonth", ConstructorParameterDescription(self.registrationMonth)), ("phoneCountry", ConstructorParameterDescription(self.phoneCountry)), ("nameChangeDate", ConstructorParameterDescription(self.nameChangeDate)), ("photoChangeDate", ConstructorParameterDescription(self.photoChangeDate))]) - } - } - case peerSettings(Cons_peerSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerSettings(let _data): - if boxed { - buffer.appendInt32(-193510921) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt32(_data.geoDistance!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - serializeString(_data.requestChatTitle!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - serializeInt32(_data.requestChatDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - serializeInt64(_data.businessBotId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - serializeString(_data.businessBotManageUrl!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - serializeInt64(_data.chargePaidMessageStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeString(_data.registrationMonth!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeString(_data.phoneCountry!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - serializeInt32(_data.nameChangeDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 18) != 0 { - serializeInt32(_data.photoChangeDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerSettings(let _data): - return ("peerSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("geoDistance", ConstructorParameterDescription(_data.geoDistance)), ("requestChatTitle", ConstructorParameterDescription(_data.requestChatTitle)), ("requestChatDate", ConstructorParameterDescription(_data.requestChatDate)), ("businessBotId", ConstructorParameterDescription(_data.businessBotId)), ("businessBotManageUrl", ConstructorParameterDescription(_data.businessBotManageUrl)), ("chargePaidMessageStars", ConstructorParameterDescription(_data.chargePaidMessageStars)), ("registrationMonth", ConstructorParameterDescription(_data.registrationMonth)), ("phoneCountry", ConstructorParameterDescription(_data.phoneCountry)), ("nameChangeDate", ConstructorParameterDescription(_data.nameChangeDate)), ("photoChangeDate", ConstructorParameterDescription(_data.photoChangeDate))]) - } - } - - public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 6) != 0 { - _2 = reader.readInt32() - } - var _3: String? - if Int(_1!) & Int(1 << 9) != 0 { - _3 = parseString(reader) - } - var _4: Int32? - if Int(_1!) & Int(1 << 9) != 0 { - _4 = reader.readInt32() - } - var _5: Int64? - if Int(_1!) & Int(1 << 13) != 0 { - _5 = reader.readInt64() - } - var _6: String? - if Int(_1!) & Int(1 << 13) != 0 { - _6 = parseString(reader) - } - var _7: Int64? - if Int(_1!) & Int(1 << 14) != 0 { - _7 = reader.readInt64() - } - var _8: String? - if Int(_1!) & Int(1 << 15) != 0 { - _8 = parseString(reader) - } - var _9: String? - if Int(_1!) & Int(1 << 16) != 0 { - _9 = parseString(reader) - } - var _10: Int32? - if Int(_1!) & Int(1 << 17) != 0 { - _10 = reader.readInt32() - } - var _11: Int32? - if Int(_1!) & Int(1 << 18) != 0 { - _11 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 14) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 15) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 16) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 17) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 18) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.PeerSettings.peerSettings(Cons_peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, nameChangeDate: _10, photoChangeDate: _11)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PeerStories: TypeConstructorDescription { - public class Cons_peerStories: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var maxReadId: Int32? - public var stories: [Api.StoryItem] - public init(flags: Int32, peer: Api.Peer, maxReadId: Int32?, stories: [Api.StoryItem]) { - self.flags = flags - self.peer = peer - self.maxReadId = maxReadId - self.stories = stories - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerStories", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("maxReadId", ConstructorParameterDescription(self.maxReadId)), ("stories", ConstructorParameterDescription(self.stories))]) - } - } - case peerStories(Cons_peerStories) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerStories(let _data): - if boxed { - buffer.appendInt32(-1707742823) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.maxReadId!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stories.count)) - for item in _data.stories { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerStories(let _data): - return ("peerStories", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("maxReadId", ConstructorParameterDescription(_data.maxReadId)), ("stories", ConstructorParameterDescription(_data.stories))]) - } - } - - public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? + public static func parse_pageBlockTitle(_ reader: BufferReader) -> PageBlock? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.StoryItem]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PeerStories.peerStories(Cons_peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PendingSuggestion: TypeConstructorDescription { - public class Cons_pendingSuggestion: TypeConstructorDescription { - public var suggestion: String - public var title: Api.TextWithEntities - public var description: Api.TextWithEntities - public var url: String - public init(suggestion: String, title: Api.TextWithEntities, description: Api.TextWithEntities, url: String) { - self.suggestion = suggestion - self.title = title - self.description = description - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pendingSuggestion", [("suggestion", ConstructorParameterDescription(self.suggestion)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("url", ConstructorParameterDescription(self.url))]) - } - } - case pendingSuggestion(Cons_pendingSuggestion) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pendingSuggestion(let _data): - if boxed { - buffer.appendInt32(-404214254) - } - serializeString(_data.suggestion, buffer: buffer, boxed: false) - _data.title.serialize(buffer, true) - _data.description.serialize(buffer, true) - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pendingSuggestion(let _data): - return ("pendingSuggestion", [("suggestion", ConstructorParameterDescription(_data.suggestion)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_pendingSuggestion(_ reader: BufferReader) -> PendingSuggestion? { - var _1: String? - _1 = parseString(reader) - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _3: Api.TextWithEntities? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PendingSuggestion.pendingSuggestion(Cons_pendingSuggestion(suggestion: _1!, title: _2!, description: _3!, url: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PhoneCall: TypeConstructorDescription { - public class Cons_phoneCall: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var date: Int32 - public var adminId: Int64 - public var participantId: Int64 - public var gAOrB: Buffer - public var keyFingerprint: Int64 - public var `protocol`: Api.PhoneCallProtocol - public var connections: [Api.PhoneConnection] - public var startDate: Int32 - public var customParameters: Api.DataJSON? - public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.date = date - self.adminId = adminId - self.participantId = participantId - self.gAOrB = gAOrB - self.keyFingerprint = keyFingerprint - self.`protocol` = `protocol` - self.connections = connections - self.startDate = startDate - self.customParameters = customParameters - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCall", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gAOrB", ConstructorParameterDescription(self.gAOrB)), ("keyFingerprint", ConstructorParameterDescription(self.keyFingerprint)), ("`protocol`", ConstructorParameterDescription(self.`protocol`)), ("connections", ConstructorParameterDescription(self.connections)), ("startDate", ConstructorParameterDescription(self.startDate)), ("customParameters", ConstructorParameterDescription(self.customParameters))]) - } - } - public class Cons_phoneCallAccepted: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var date: Int32 - public var adminId: Int64 - public var participantId: Int64 - public var gB: Buffer - public var `protocol`: Api.PhoneCallProtocol - public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, `protocol`: Api.PhoneCallProtocol) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.date = date - self.adminId = adminId - self.participantId = participantId - self.gB = gB - self.`protocol` = `protocol` - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallAccepted", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gB", ConstructorParameterDescription(self.gB)), ("`protocol`", ConstructorParameterDescription(self.`protocol`))]) - } - } - public class Cons_phoneCallDiscarded: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var reason: Api.PhoneCallDiscardReason? - public var duration: Int32? - public init(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) { - self.flags = flags - self.id = id - self.reason = reason - self.duration = duration - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallDiscarded", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("reason", ConstructorParameterDescription(self.reason)), ("duration", ConstructorParameterDescription(self.duration))]) - } - } - public class Cons_phoneCallEmpty: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallEmpty", [("id", ConstructorParameterDescription(self.id))]) - } - } - public class Cons_phoneCallRequested: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var date: Int32 - public var adminId: Int64 - public var participantId: Int64 - public var gAHash: Buffer - public var `protocol`: Api.PhoneCallProtocol - public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.date = date - self.adminId = adminId - self.participantId = participantId - self.gAHash = gAHash - self.`protocol` = `protocol` - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallRequested", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gAHash", ConstructorParameterDescription(self.gAHash)), ("`protocol`", ConstructorParameterDescription(self.`protocol`))]) - } - } - public class Cons_phoneCallWaiting: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var date: Int32 - public var adminId: Int64 - public var participantId: Int64 - public var `protocol`: Api.PhoneCallProtocol - public var receiveDate: Int32? - public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, `protocol`: Api.PhoneCallProtocol, receiveDate: Int32?) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.date = date - self.adminId = adminId - self.participantId = participantId - self.`protocol` = `protocol` - self.receiveDate = receiveDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallWaiting", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("`protocol`", ConstructorParameterDescription(self.`protocol`)), ("receiveDate", ConstructorParameterDescription(self.receiveDate))]) - } - } - case phoneCall(Cons_phoneCall) - case phoneCallAccepted(Cons_phoneCallAccepted) - case phoneCallDiscarded(Cons_phoneCallDiscarded) - case phoneCallEmpty(Cons_phoneCallEmpty) - case phoneCallRequested(Cons_phoneCallRequested) - case phoneCallWaiting(Cons_phoneCallWaiting) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCall(let _data): - if boxed { - buffer.appendInt32(810769141) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.adminId, buffer: buffer, boxed: false) - serializeInt64(_data.participantId, buffer: buffer, boxed: false) - serializeBytes(_data.gAOrB, buffer: buffer, boxed: false) - serializeInt64(_data.keyFingerprint, buffer: buffer, boxed: false) - _data.`protocol`.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.connections.count)) - for item in _data.connections { - item.serialize(buffer, true) - } - serializeInt32(_data.startDate, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 7) != 0 { - _data.customParameters!.serialize(buffer, true) - } - break - case .phoneCallAccepted(let _data): - if boxed { - buffer.appendInt32(912311057) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.adminId, buffer: buffer, boxed: false) - serializeInt64(_data.participantId, buffer: buffer, boxed: false) - serializeBytes(_data.gB, buffer: buffer, boxed: false) - _data.`protocol`.serialize(buffer, true) - break - case .phoneCallDiscarded(let _data): - if boxed { - buffer.appendInt32(1355435489) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.reason!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.duration!, buffer: buffer, boxed: false) - } - break - case .phoneCallEmpty(let _data): - if boxed { - buffer.appendInt32(1399245077) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - break - case .phoneCallRequested(let _data): - if boxed { - buffer.appendInt32(347139340) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.adminId, buffer: buffer, boxed: false) - serializeInt64(_data.participantId, buffer: buffer, boxed: false) - serializeBytes(_data.gAHash, buffer: buffer, boxed: false) - _data.`protocol`.serialize(buffer, true) - break - case .phoneCallWaiting(let _data): - if boxed { - buffer.appendInt32(-987599081) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.adminId, buffer: buffer, boxed: false) - serializeInt64(_data.participantId, buffer: buffer, boxed: false) - _data.`protocol`.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.receiveDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .phoneCall(let _data): - return ("phoneCall", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gAOrB", ConstructorParameterDescription(_data.gAOrB)), ("keyFingerprint", ConstructorParameterDescription(_data.keyFingerprint)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`)), ("connections", ConstructorParameterDescription(_data.connections)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("customParameters", ConstructorParameterDescription(_data.customParameters))]) - case .phoneCallAccepted(let _data): - return ("phoneCallAccepted", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gB", ConstructorParameterDescription(_data.gB)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`))]) - case .phoneCallDiscarded(let _data): - return ("phoneCallDiscarded", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("reason", ConstructorParameterDescription(_data.reason)), ("duration", ConstructorParameterDescription(_data.duration))]) - case .phoneCallEmpty(let _data): - return ("phoneCallEmpty", [("id", ConstructorParameterDescription(_data.id))]) - case .phoneCallRequested(let _data): - return ("phoneCallRequested", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gAHash", ConstructorParameterDescription(_data.gAHash)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`))]) - case .phoneCallWaiting(let _data): - return ("phoneCallWaiting", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`)), ("receiveDate", ConstructorParameterDescription(_data.receiveDate))]) - } - } - - public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Int64? - _8 = reader.readInt64() - var _9: Api.PhoneCallProtocol? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol - } - var _10: [Api.PhoneConnection]? - if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhoneConnection.self) - } - var _11: Int32? - _11 = reader.readInt32() - var _12: Api.DataJSON? - if Int(_1!) & Int(1 << 7) != 0 { - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.PhoneCall.phoneCall(Cons_phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12)) - } - else { - return nil - } - } - public static func parse_phoneCallAccepted(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Api.PhoneCallProtocol? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallAccepted(Cons_phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!)) - } - else { - return nil - } - } - public static func parse_phoneCallDiscarded(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.PhoneCallDiscardReason? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason - } - } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCall.phoneCallDiscarded(Cons_phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4)) - } - else { - return nil - } - } - public static func parse_phoneCallEmpty(_ reader: BufferReader) -> PhoneCall? { - var _1: Int64? - _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.PhoneCall.phoneCallEmpty(Cons_phoneCallEmpty(id: _1!)) + return Api.PageBlock.pageBlockTitle(Cons_pageBlockTitle(text: _1!)) } else { return nil } } - public static func parse_phoneCallRequested(_ reader: BufferReader) -> PhoneCall? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Api.PhoneCallProtocol? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallRequested(Cons_phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!)) - } - else { - return nil - } + public static func parse_pageBlockUnsupported(_ reader: BufferReader) -> PageBlock? { + return Api.PageBlock.pageBlockUnsupported } - public static func parse_phoneCallWaiting(_ reader: BufferReader) -> PhoneCall? { + public static func parse_pageBlockVideo(_ reader: BufferReader) -> PageBlock? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Api.PhoneCallProtocol? + var _3: Api.PageCaption? if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol - } - var _8: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _8 = reader.readInt32() + _3 = Api.parse(reader, signature: signature) as? Api.PageCaption } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallWaiting(Cons_phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8)) + if _c1 && _c2 && _c3 { + return Api.PageBlock.pageBlockVideo(Cons_pageBlockVideo(flags: _1!, videoId: _2!, caption: _3!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index f9f45b3af7..2bec29fc7d 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -1,3 +1,208 @@ +public extension Api { + enum BotApp: TypeConstructorDescription { + public class Cons_botApp: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var shortName: String + public var title: String + public var description: String + public var photo: Api.Photo + public var document: Api.Document? + public var hash: Int64 + public init(flags: Int32, id: Int64, accessHash: Int64, shortName: String, title: String, description: String, photo: Api.Photo, document: Api.Document?, hash: Int64) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.shortName = shortName + self.title = title + self.description = description + self.photo = photo + self.document = document + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("botApp", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("shortName", ConstructorParameterDescription(self.shortName)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("document", ConstructorParameterDescription(self.document)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case botApp(Cons_botApp) + case botAppNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botApp(let _data): + if boxed { + buffer.appendInt32(-1778593322) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeString(_data.shortName, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + _data.photo.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.document!.serialize(buffer, true) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + break + case .botAppNotModified: + if boxed { + buffer.appendInt32(1571189943) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .botApp(let _data): + return ("botApp", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("shortName", ConstructorParameterDescription(_data.shortName)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("document", ConstructorParameterDescription(_data.document)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .botAppNotModified: + return ("botAppNotModified", []) + } + } + + public static func parse_botApp(_ reader: BufferReader) -> BotApp? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.Photo? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo + } + var _8: Api.Document? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _9: Int64? + _9 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.BotApp.botApp(Cons_botApp(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8, hash: _9!)) + } + else { + return nil + } + } + public static func parse_botAppNotModified(_ reader: BufferReader) -> BotApp? { + return Api.BotApp.botAppNotModified + } + } +} +public extension Api { + enum BotAppSettings: TypeConstructorDescription { + public class Cons_botAppSettings: TypeConstructorDescription { + public var flags: Int32 + public var placeholderPath: Buffer? + public var backgroundColor: Int32? + public var backgroundDarkColor: Int32? + public var headerColor: Int32? + public var headerDarkColor: Int32? + public init(flags: Int32, placeholderPath: Buffer?, backgroundColor: Int32?, backgroundDarkColor: Int32?, headerColor: Int32?, headerDarkColor: Int32?) { + self.flags = flags + self.placeholderPath = placeholderPath + self.backgroundColor = backgroundColor + self.backgroundDarkColor = backgroundDarkColor + self.headerColor = headerColor + self.headerDarkColor = headerDarkColor + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("botAppSettings", [("flags", ConstructorParameterDescription(self.flags)), ("placeholderPath", ConstructorParameterDescription(self.placeholderPath)), ("backgroundColor", ConstructorParameterDescription(self.backgroundColor)), ("backgroundDarkColor", ConstructorParameterDescription(self.backgroundDarkColor)), ("headerColor", ConstructorParameterDescription(self.headerColor)), ("headerDarkColor", ConstructorParameterDescription(self.headerDarkColor))]) + } + } + case botAppSettings(Cons_botAppSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botAppSettings(let _data): + if boxed { + buffer.appendInt32(-912582320) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.placeholderPath!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.backgroundColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.backgroundDarkColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.headerColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.headerDarkColor!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .botAppSettings(let _data): + return ("botAppSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("placeholderPath", ConstructorParameterDescription(_data.placeholderPath)), ("backgroundColor", ConstructorParameterDescription(_data.backgroundColor)), ("backgroundDarkColor", ConstructorParameterDescription(_data.backgroundDarkColor)), ("headerColor", ConstructorParameterDescription(_data.headerColor)), ("headerDarkColor", ConstructorParameterDescription(_data.headerDarkColor))]) + } + } + + public static func parse_botAppSettings(_ reader: BufferReader) -> BotAppSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseBytes(reader) + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.BotAppSettings.botAppSettings(Cons_botAppSettings(flags: _1!, placeholderPath: _2, backgroundColor: _3, backgroundDarkColor: _4, headerColor: _5, headerDarkColor: _6)) + } + else { + return nil + } + } + } +} public extension Api { enum BotBusinessConnection: TypeConstructorDescription { public class Cons_botBusinessConnection: TypeConstructorDescription { @@ -58,7 +263,7 @@ public extension Api { var _5: Int32? _5 = reader.readInt32() var _6: Api.BusinessBotRights? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights } @@ -68,7 +273,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.BotBusinessConnection.botBusinessConnection(Cons_botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6)) } @@ -372,63 +577,63 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int64? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _2 = reader.readInt64() } var _3: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _3 = parseString(reader) } var _4: Api.Photo? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Photo } } var _5: Api.Document? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.Document } } var _6: [Api.BotCommand]? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) } } var _7: Api.BotMenuButton? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.BotMenuButton } } var _8: String? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _8 = parseString(reader) } var _9: Api.BotAppSettings? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.BotAppSettings } } var _10: Api.BotVerifierSettings? - if Int(_1!) & Int(1 << 9) != 0 { + if Int(_1 ?? 0) & Int(1 << 9) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.BotVerifierSettings } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 9) == 0) || _10 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.BotInfo.botInfo(Cons_botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7, privacyPolicyUrl: _8, appSettings: _9, verifierSettings: _10)) } @@ -554,6 +759,19 @@ public extension Api { return ("botInlineMessageMediaWebPage", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("url", ConstructorParameterDescription(self.url)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup))]) } } + public class Cons_botInlineMessageRichMessage: TypeConstructorDescription { + public var flags: Int32 + public var replyMarkup: Api.ReplyMarkup? + public var richMessage: Api.RichMessage + public init(flags: Int32, replyMarkup: Api.ReplyMarkup?, richMessage: Api.RichMessage) { + self.flags = flags + self.replyMarkup = replyMarkup + self.richMessage = richMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("botInlineMessageRichMessage", [("flags", ConstructorParameterDescription(self.flags)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) + } + } public class Cons_botInlineMessageText: TypeConstructorDescription { public var flags: Int32 public var message: String @@ -575,6 +793,7 @@ public extension Api { case botInlineMessageMediaInvoice(Cons_botInlineMessageMediaInvoice) case botInlineMessageMediaVenue(Cons_botInlineMessageMediaVenue) case botInlineMessageMediaWebPage(Cons_botInlineMessageMediaWebPage) + case botInlineMessageRichMessage(Cons_botInlineMessageRichMessage) case botInlineMessageText(Cons_botInlineMessageText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -677,6 +896,16 @@ public extension Api { _data.replyMarkup!.serialize(buffer, true) } break + case .botInlineMessageRichMessage(let _data): + if boxed { + buffer.appendInt32(174161531) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.replyMarkup!.serialize(buffer, true) + } + _data.richMessage.serialize(buffer, true) + break case .botInlineMessageText(let _data): if boxed { buffer.appendInt32(-1937807902) @@ -711,6 +940,8 @@ public extension Api { return ("botInlineMessageMediaVenue", [("flags", ConstructorParameterDescription(_data.flags)), ("geo", ConstructorParameterDescription(_data.geo)), ("title", ConstructorParameterDescription(_data.title)), ("address", ConstructorParameterDescription(_data.address)), ("provider", ConstructorParameterDescription(_data.provider)), ("venueId", ConstructorParameterDescription(_data.venueId)), ("venueType", ConstructorParameterDescription(_data.venueType)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) case .botInlineMessageMediaWebPage(let _data): return ("botInlineMessageMediaWebPage", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("url", ConstructorParameterDescription(_data.url)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) + case .botInlineMessageRichMessage(let _data): + return ("botInlineMessageRichMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) case .botInlineMessageText(let _data): return ("botInlineMessageText", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) } @@ -722,21 +953,21 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _4: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.BotInlineMessage.botInlineMessageMediaAuto(Cons_botInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4)) } @@ -756,7 +987,7 @@ public extension Api { var _5: String? _5 = parseString(reader) var _6: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -766,7 +997,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.BotInlineMessage.botInlineMessageMediaContact(Cons_botInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6)) } @@ -782,29 +1013,29 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint } var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = reader.readInt32() } var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = reader.readInt32() } var _6: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.BotInlineMessage.botInlineMessageMediaGeo(Cons_botInlineMessageMediaGeo(flags: _1!, geo: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6)) } @@ -820,7 +1051,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.WebDocument? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.WebDocument } @@ -830,7 +1061,7 @@ public extension Api { var _6: Int64? _6 = reader.readInt64() var _7: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -838,10 +1069,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.BotInlineMessage.botInlineMessageMediaInvoice(Cons_botInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, currency: _5!, totalAmount: _6!, replyMarkup: _7)) } @@ -867,7 +1098,7 @@ public extension Api { var _7: String? _7 = parseString(reader) var _8: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -879,7 +1110,7 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.BotInlineMessage.botInlineMessageMediaVenue(Cons_botInlineMessageMediaVenue(flags: _1!, geo: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8)) } @@ -893,7 +1124,7 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } @@ -901,16 +1132,16 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.BotInlineMessage.botInlineMessageMediaWebPage(Cons_botInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5)) } @@ -918,27 +1149,50 @@ public extension Api { return nil } } + public static func parse_botInlineMessageRichMessage(_ reader: BufferReader) -> BotInlineMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.ReplyMarkup? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } + } + var _3: Api.RichMessage? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.RichMessage + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.BotInlineMessage.botInlineMessageRichMessage(Cons_botInlineMessageRichMessage(flags: _1!, replyMarkup: _2, richMessage: _3!)) + } + else { + return nil + } + } public static func parse_botInlineMessageText(_ reader: BufferReader) -> BotInlineMessage? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _4: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.BotInlineMessage.botInlineMessageText(Cons_botInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4)) } @@ -1068,23 +1322,23 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.Photo? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Photo } } var _5: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.Document } } var _6: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _6 = parseString(reader) } var _7: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _7 = parseString(reader) } var _8: Api.BotInlineMessage? @@ -1094,10 +1348,10 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil let _c8 = _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.BotInlineResult.botInlineMediaResult(Cons_botInlineMediaResult(flags: _1!, id: _2!, type: _3!, photo: _4, document: _5, title: _6, description: _7, sendMessage: _8!)) @@ -1114,25 +1368,25 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } var _6: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _6 = parseString(reader) } var _7: Api.WebDocument? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.WebDocument } } var _8: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.WebDocument } @@ -1144,11 +1398,11 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _8 != nil let _c9 = _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.BotInlineResult.botInlineResult(Cons_botInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!)) @@ -1389,13 +1643,13 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.BotVerifierSettings.botVerifierSettings(Cons_botVerifierSettings(flags: _1!, icon: _2!, company: _3!, customDescription: _4)) } @@ -1543,211 +1797,3 @@ public extension Api { } } } -public extension Api { - enum BusinessBotRecipients: TypeConstructorDescription { - public class Cons_businessBotRecipients: TypeConstructorDescription { - public var flags: Int32 - public var users: [Int64]? - public var excludeUsers: [Int64]? - public init(flags: Int32, users: [Int64]?, excludeUsers: [Int64]?) { - self.flags = flags - self.users = users - self.excludeUsers = excludeUsers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("businessBotRecipients", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("excludeUsers", ConstructorParameterDescription(self.excludeUsers))]) - } - } - case businessBotRecipients(Cons_businessBotRecipients) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessBotRecipients(let _data): - if boxed { - buffer.appendInt32(-1198722189) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users!.count)) - for item in _data.users! { - serializeInt64(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 6) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.excludeUsers!.count)) - for item in _data.excludeUsers! { - serializeInt64(item, buffer: buffer, boxed: false) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .businessBotRecipients(let _data): - return ("businessBotRecipients", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("excludeUsers", ConstructorParameterDescription(_data.excludeUsers))]) - } - } - - public static func parse_businessBotRecipients(_ reader: BufferReader) -> BusinessBotRecipients? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int64]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - } - var _3: [Int64]? - if Int(_1!) & Int(1 << 6) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessBotRecipients.businessBotRecipients(Cons_businessBotRecipients(flags: _1!, users: _2, excludeUsers: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum BusinessBotRights: TypeConstructorDescription { - public class Cons_businessBotRights: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("businessBotRights", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - case businessBotRights(Cons_businessBotRights) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessBotRights(let _data): - if boxed { - buffer.appendInt32(-1604170505) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .businessBotRights(let _data): - return ("businessBotRights", [("flags", ConstructorParameterDescription(_data.flags))]) - } - } - - public static func parse_businessBotRights(_ reader: BufferReader) -> BusinessBotRights? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.BusinessBotRights.businessBotRights(Cons_businessBotRights(flags: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum BusinessChatLink: TypeConstructorDescription { - public class Cons_businessChatLink: TypeConstructorDescription { - public var flags: Int32 - public var link: String - public var message: String - public var entities: [Api.MessageEntity]? - public var title: String? - public var views: Int32 - public init(flags: Int32, link: String, message: String, entities: [Api.MessageEntity]?, title: String?, views: Int32) { - self.flags = flags - self.link = link - self.message = message - self.entities = entities - self.title = title - self.views = views - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("businessChatLink", [("flags", ConstructorParameterDescription(self.flags)), ("link", ConstructorParameterDescription(self.link)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("title", ConstructorParameterDescription(self.title)), ("views", ConstructorParameterDescription(self.views))]) - } - } - case businessChatLink(Cons_businessChatLink) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessChatLink(let _data): - if boxed { - buffer.appendInt32(-1263638929) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.link, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - serializeInt32(_data.views, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .businessChatLink(let _data): - return ("businessChatLink", [("flags", ConstructorParameterDescription(_data.flags)), ("link", ConstructorParameterDescription(_data.link)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("title", ConstructorParameterDescription(_data.title)), ("views", ConstructorParameterDescription(_data.views))]) - } - } - - public static func parse_businessChatLink(_ reader: BufferReader) -> BusinessChatLink? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.BusinessChatLink.businessChatLink(Cons_businessChatLink(flags: _1!, link: _2!, message: _3!, entities: _4, title: _5, views: _6!)) - } - else { - return nil - } - } - } -} diff --git a/submodules/TelegramApi/Sources/Api20.swift b/submodules/TelegramApi/Sources/Api20.swift index 5b522e41e1..15df72e4d0 100644 --- a/submodules/TelegramApi/Sources/Api20.swift +++ b/submodules/TelegramApi/Sources/Api20.swift @@ -1,123 +1,225 @@ public extension Api { - enum PhoneCallDiscardReason: TypeConstructorDescription { - public class Cons_phoneCallDiscardReasonMigrateConferenceCall: TypeConstructorDescription { - public var slug: String - public init(slug: String) { - self.slug = slug + indirect enum PageCaption: TypeConstructorDescription { + public class Cons_pageCaption: TypeConstructorDescription { + public var text: Api.RichText + public var credit: Api.RichText + public init(text: Api.RichText, credit: Api.RichText) { + self.text = text + self.credit = credit } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", ConstructorParameterDescription(self.slug))]) + return ("pageCaption", [("text", ConstructorParameterDescription(self.text)), ("credit", ConstructorParameterDescription(self.credit))]) } } - case phoneCallDiscardReasonBusy - case phoneCallDiscardReasonDisconnect - case phoneCallDiscardReasonHangup - case phoneCallDiscardReasonMigrateConferenceCall(Cons_phoneCallDiscardReasonMigrateConferenceCall) - case phoneCallDiscardReasonMissed + case pageCaption(Cons_pageCaption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .phoneCallDiscardReasonBusy: + case .pageCaption(let _data): if boxed { - buffer.appendInt32(-84416311) - } - break - case .phoneCallDiscardReasonDisconnect: - if boxed { - buffer.appendInt32(-527056480) - } - break - case .phoneCallDiscardReasonHangup: - if boxed { - buffer.appendInt32(1471006352) - } - break - case .phoneCallDiscardReasonMigrateConferenceCall(let _data): - if boxed { - buffer.appendInt32(-1615072777) - } - serializeString(_data.slug, buffer: buffer, boxed: false) - break - case .phoneCallDiscardReasonMissed: - if boxed { - buffer.appendInt32(-2048646399) + buffer.appendInt32(1869903447) } + _data.text.serialize(buffer, true) + _data.credit.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .phoneCallDiscardReasonBusy: - return ("phoneCallDiscardReasonBusy", []) - case .phoneCallDiscardReasonDisconnect: - return ("phoneCallDiscardReasonDisconnect", []) - case .phoneCallDiscardReasonHangup: - return ("phoneCallDiscardReasonHangup", []) - case .phoneCallDiscardReasonMigrateConferenceCall(let _data): - return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", ConstructorParameterDescription(_data.slug))]) - case .phoneCallDiscardReasonMissed: - return ("phoneCallDiscardReasonMissed", []) + case .pageCaption(let _data): + return ("pageCaption", [("text", ConstructorParameterDescription(_data.text)), ("credit", ConstructorParameterDescription(_data.credit))]) } } - public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy - } - public static func parse_phoneCallDiscardReasonDisconnect(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonDisconnect - } - public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup - } - public static func parse_phoneCallDiscardReasonMigrateConferenceCall(_ reader: BufferReader) -> PhoneCallDiscardReason? { - var _1: String? - _1 = parseString(reader) + public static func parse_pageCaption(_ reader: BufferReader) -> PageCaption? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } let _c1 = _1 != nil - if _c1 { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMigrateConferenceCall(Cons_phoneCallDiscardReasonMigrateConferenceCall(slug: _1!)) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageCaption.pageCaption(Cons_pageCaption(text: _1!, credit: _2!)) } else { return nil } } - public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { - return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed + } +} +public extension Api { + indirect enum PageListItem: TypeConstructorDescription { + public class Cons_pageListItemBlocks: TypeConstructorDescription { + public var flags: Int32 + public var blocks: [Api.PageBlock] + public init(flags: Int32, blocks: [Api.PageBlock]) { + self.flags = flags + self.blocks = blocks + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageListItemBlocks", [("flags", ConstructorParameterDescription(self.flags)), ("blocks", ConstructorParameterDescription(self.blocks))]) + } + } + public class Cons_pageListItemText: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.RichText + public init(flags: Int32, text: Api.RichText) { + self.flags = flags + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageListItemText", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text))]) + } + } + case pageListItemBlocks(Cons_pageListItemBlocks) + case pageListItemText(Cons_pageListItemText) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageListItemBlocks(let _data): + if boxed { + buffer.appendInt32(1674209194) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + break + case .pageListItemText(let _data): + if boxed { + buffer.appendInt32(794323004) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pageListItemBlocks(let _data): + return ("pageListItemBlocks", [("flags", ConstructorParameterDescription(_data.flags)), ("blocks", ConstructorParameterDescription(_data.blocks))]) + case .pageListItemText(let _data): + return ("pageListItemText", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text))]) + } + } + + public static func parse_pageListItemBlocks(_ reader: BufferReader) -> PageListItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PageBlock]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageListItem.pageListItemBlocks(Cons_pageListItemBlocks(flags: _1!, blocks: _2!)) + } + else { + return nil + } + } + public static func parse_pageListItemText(_ reader: BufferReader) -> PageListItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PageListItem.pageListItemText(Cons_pageListItemText(flags: _1!, text: _2!)) + } + else { + return nil + } } } } public extension Api { - enum PhoneCallProtocol: TypeConstructorDescription { - public class Cons_phoneCallProtocol: TypeConstructorDescription { + indirect enum PageListOrderedItem: TypeConstructorDescription { + public class Cons_pageListOrderedItemBlocks: TypeConstructorDescription { public var flags: Int32 - public var minLayer: Int32 - public var maxLayer: Int32 - public var libraryVersions: [String] - public init(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) { + public var num: String? + public var blocks: [Api.PageBlock] + public var value: Int32? + public var type: String? + public init(flags: Int32, num: String?, blocks: [Api.PageBlock], value: Int32?, type: String?) { self.flags = flags - self.minLayer = minLayer - self.maxLayer = maxLayer - self.libraryVersions = libraryVersions + self.num = num + self.blocks = blocks + self.value = value + self.type = type } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCallProtocol", [("flags", ConstructorParameterDescription(self.flags)), ("minLayer", ConstructorParameterDescription(self.minLayer)), ("maxLayer", ConstructorParameterDescription(self.maxLayer)), ("libraryVersions", ConstructorParameterDescription(self.libraryVersions))]) + return ("pageListOrderedItemBlocks", [("flags", ConstructorParameterDescription(self.flags)), ("num", ConstructorParameterDescription(self.num)), ("blocks", ConstructorParameterDescription(self.blocks)), ("value", ConstructorParameterDescription(self.value)), ("type", ConstructorParameterDescription(self.type))]) } } - case phoneCallProtocol(Cons_phoneCallProtocol) + public class Cons_pageListOrderedItemText: TypeConstructorDescription { + public var flags: Int32 + public var num: String? + public var text: Api.RichText + public var value: Int32? + public var type: String? + public init(flags: Int32, num: String?, text: Api.RichText, value: Int32?, type: String?) { + self.flags = flags + self.num = num + self.text = text + self.value = value + self.type = type + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageListOrderedItemText", [("flags", ConstructorParameterDescription(self.flags)), ("num", ConstructorParameterDescription(self.num)), ("text", ConstructorParameterDescription(self.text)), ("value", ConstructorParameterDescription(self.value)), ("type", ConstructorParameterDescription(self.type))]) + } + } + case pageListOrderedItemBlocks(Cons_pageListOrderedItemBlocks) + case pageListOrderedItemText(Cons_pageListOrderedItemText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .phoneCallProtocol(let _data): + case .pageListOrderedItemBlocks(let _data): if boxed { - buffer.appendInt32(-58224696) + buffer.appendInt32(-1879910928) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.minLayer, buffer: buffer, boxed: false) - serializeInt32(_data.maxLayer, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.num!, buffer: buffer, boxed: false) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.libraryVersions.count)) - for item in _data.libraryVersions { - serializeString(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.value!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.type!, buffer: buffer, boxed: false) + } + break + case .pageListOrderedItemText(let _data): + if boxed { + buffer.appendInt32(352522633) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.num!, buffer: buffer, boxed: false) + } + _data.text.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.value!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.type!, buffer: buffer, boxed: false) } break } @@ -125,167 +227,575 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .phoneCallProtocol(let _data): - return ("phoneCallProtocol", [("flags", ConstructorParameterDescription(_data.flags)), ("minLayer", ConstructorParameterDescription(_data.minLayer)), ("maxLayer", ConstructorParameterDescription(_data.maxLayer)), ("libraryVersions", ConstructorParameterDescription(_data.libraryVersions))]) + case .pageListOrderedItemBlocks(let _data): + return ("pageListOrderedItemBlocks", [("flags", ConstructorParameterDescription(_data.flags)), ("num", ConstructorParameterDescription(_data.num)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("value", ConstructorParameterDescription(_data.value)), ("type", ConstructorParameterDescription(_data.type))]) + case .pageListOrderedItemText(let _data): + return ("pageListOrderedItemText", [("flags", ConstructorParameterDescription(_data.flags)), ("num", ConstructorParameterDescription(_data.num)), ("text", ConstructorParameterDescription(_data.text)), ("value", ConstructorParameterDescription(_data.value)), ("type", ConstructorParameterDescription(_data.type))]) } } - public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { + public static func parse_pageListOrderedItemBlocks(_ reader: BufferReader) -> PageListOrderedItem? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _2 = parseString(reader) + } + var _3: [Api.PageBlock]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _4 = reader.readInt32() + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageListOrderedItem.pageListOrderedItemBlocks(Cons_pageListOrderedItemBlocks(flags: _1!, num: _2, blocks: _3!, value: _4, type: _5)) + } + else { + return nil + } + } + public static func parse_pageListOrderedItemText(_ reader: BufferReader) -> PageListOrderedItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _2 = parseString(reader) + } + var _3: Api.RichText? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _4 = reader.readInt32() + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PageListOrderedItem.pageListOrderedItemText(Cons_pageListOrderedItemText(flags: _1!, num: _2, text: _3!, value: _4, type: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PageRelatedArticle: TypeConstructorDescription { + public class Cons_pageRelatedArticle: TypeConstructorDescription { + public var flags: Int32 + public var url: String + public var webpageId: Int64 + public var title: String? + public var description: String? + public var photoId: Int64? + public var author: String? + public var publishedDate: Int32? + public init(flags: Int32, url: String, webpageId: Int64, title: String?, description: String?, photoId: Int64?, author: String?, publishedDate: Int32?) { + self.flags = flags + self.url = url + self.webpageId = webpageId + self.title = title + self.description = description + self.photoId = photoId + self.author = author + self.publishedDate = publishedDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageRelatedArticle", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photoId", ConstructorParameterDescription(self.photoId)), ("author", ConstructorParameterDescription(self.author)), ("publishedDate", ConstructorParameterDescription(self.publishedDate))]) + } + } + case pageRelatedArticle(Cons_pageRelatedArticle) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageRelatedArticle(let _data): + if boxed { + buffer.appendInt32(-1282352120) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.webpageId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.description!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.photoId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.author!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.publishedDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pageRelatedArticle(let _data): + return ("pageRelatedArticle", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("author", ConstructorParameterDescription(_data.author)), ("publishedDate", ConstructorParameterDescription(_data.publishedDate))]) + } + } + + public static func parse_pageRelatedArticle(_ reader: BufferReader) -> PageRelatedArticle? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = reader.readInt64() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _7 = parseString(reader) + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PageRelatedArticle.pageRelatedArticle(Cons_pageRelatedArticle(flags: _1!, url: _2!, webpageId: _3!, title: _4, description: _5, photoId: _6, author: _7, publishedDate: _8)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum PageTableCell: TypeConstructorDescription { + public class Cons_pageTableCell: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.RichText? + public var colspan: Int32? + public var rowspan: Int32? + public init(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?) { + self.flags = flags + self.text = text + self.colspan = colspan + self.rowspan = rowspan + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageTableCell", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("colspan", ConstructorParameterDescription(self.colspan)), ("rowspan", ConstructorParameterDescription(self.rowspan))]) + } + } + case pageTableCell(Cons_pageTableCell) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageTableCell(let _data): + if boxed { + buffer.appendInt32(878078826) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 7) != 0 { + _data.text!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.colspan!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.rowspan!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pageTableCell(let _data): + return ("pageTableCell", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("colspan", ConstructorParameterDescription(_data.colspan)), ("rowspan", ConstructorParameterDescription(_data.rowspan))]) + } + } + + public static func parse_pageTableCell(_ reader: BufferReader) -> PageTableCell? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.RichText? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PageTableCell.pageTableCell(Cons_pageTableCell(flags: _1!, text: _2, colspan: _3, rowspan: _4)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PageTableRow: TypeConstructorDescription { + public class Cons_pageTableRow: TypeConstructorDescription { + public var cells: [Api.PageTableCell] + public init(cells: [Api.PageTableCell]) { + self.cells = cells + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pageTableRow", [("cells", ConstructorParameterDescription(self.cells))]) + } + } + case pageTableRow(Cons_pageTableRow) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pageTableRow(let _data): + if boxed { + buffer.appendInt32(-524237339) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.cells.count)) + for item in _data.cells { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pageTableRow(let _data): + return ("pageTableRow", [("cells", ConstructorParameterDescription(_data.cells))]) + } + } + + public static func parse_pageTableRow(_ reader: BufferReader) -> PageTableRow? { + var _1: [Api.PageTableCell]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageTableCell.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PageTableRow.pageTableRow(Cons_pageTableRow(cells: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum PaidReactionPrivacy: TypeConstructorDescription { + public class Cons_paidReactionPrivacyPeer: TypeConstructorDescription { + public var peer: Api.InputPeer + public init(peer: Api.InputPeer) { + self.peer = peer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paidReactionPrivacyPeer", [("peer", ConstructorParameterDescription(self.peer))]) + } + } + case paidReactionPrivacyAnonymous + case paidReactionPrivacyDefault + case paidReactionPrivacyPeer(Cons_paidReactionPrivacyPeer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paidReactionPrivacyAnonymous: + if boxed { + buffer.appendInt32(520887001) + } + break + case .paidReactionPrivacyDefault: + if boxed { + buffer.appendInt32(543872158) + } + break + case .paidReactionPrivacyPeer(let _data): + if boxed { + buffer.appendInt32(-596837136) + } + _data.peer.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paidReactionPrivacyAnonymous: + return ("paidReactionPrivacyAnonymous", []) + case .paidReactionPrivacyDefault: + return ("paidReactionPrivacyDefault", []) + case .paidReactionPrivacyPeer(let _data): + return ("paidReactionPrivacyPeer", [("peer", ConstructorParameterDescription(_data.peer))]) + } + } + + public static func parse_paidReactionPrivacyAnonymous(_ reader: BufferReader) -> PaidReactionPrivacy? { + return Api.PaidReactionPrivacy.paidReactionPrivacyAnonymous + } + public static func parse_paidReactionPrivacyDefault(_ reader: BufferReader) -> PaidReactionPrivacy? { + return Api.PaidReactionPrivacy.paidReactionPrivacyDefault + } + public static func parse_paidReactionPrivacyPeer(_ reader: BufferReader) -> PaidReactionPrivacy? { + var _1: Api.InputPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputPeer + } + let _c1 = _1 != nil + if _c1 { + return Api.PaidReactionPrivacy.paidReactionPrivacyPeer(Cons_paidReactionPrivacyPeer(peer: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Passkey: TypeConstructorDescription { + public class Cons_passkey: TypeConstructorDescription { + public var flags: Int32 + public var id: String + public var name: String + public var date: Int32 + public var softwareEmojiId: Int64? + public var lastUsageDate: Int32? + public init(flags: Int32, id: String, name: String, date: Int32, softwareEmojiId: Int64?, lastUsageDate: Int32?) { + self.flags = flags + self.id = id + self.name = name + self.date = date + self.softwareEmojiId = softwareEmojiId + self.lastUsageDate = lastUsageDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passkey", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("name", ConstructorParameterDescription(self.name)), ("date", ConstructorParameterDescription(self.date)), ("softwareEmojiId", ConstructorParameterDescription(self.softwareEmojiId)), ("lastUsageDate", ConstructorParameterDescription(self.lastUsageDate))]) + } + } + case passkey(Cons_passkey) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passkey(let _data): + if boxed { + buffer.appendInt32(-1738457409) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.id, buffer: buffer, boxed: false) + serializeString(_data.name, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.softwareEmojiId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.lastUsageDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passkey(let _data): + return ("passkey", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("name", ConstructorParameterDescription(_data.name)), ("date", ConstructorParameterDescription(_data.date)), ("softwareEmojiId", ConstructorParameterDescription(_data.softwareEmojiId)), ("lastUsageDate", ConstructorParameterDescription(_data.lastUsageDate))]) + } + } + + public static func parse_passkey(_ reader: BufferReader) -> Passkey? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Passkey.passkey(Cons_passkey(flags: _1!, id: _2!, name: _3!, date: _4!, softwareEmojiId: _5, lastUsageDate: _6)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PasswordKdfAlgo: TypeConstructorDescription { + public class Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow: TypeConstructorDescription { + public var salt1: Buffer + public var salt2: Buffer + public var g: Int32 + public var p: Buffer + public init(salt1: Buffer, salt2: Buffer, g: Int32, p: Buffer) { + self.salt1 = salt1 + self.salt2 = salt2 + self.g = g + self.p = p + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", ConstructorParameterDescription(self.salt1)), ("salt2", ConstructorParameterDescription(self.salt2)), ("g", ConstructorParameterDescription(self.g)), ("p", ConstructorParameterDescription(self.p))]) + } + } + case passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow) + case passwordKdfAlgoUnknown + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let _data): + if boxed { + buffer.appendInt32(982592842) + } + serializeBytes(_data.salt1, buffer: buffer, boxed: false) + serializeBytes(_data.salt2, buffer: buffer, boxed: false) + serializeInt32(_data.g, buffer: buffer, boxed: false) + serializeBytes(_data.p, buffer: buffer, boxed: false) + break + case .passwordKdfAlgoUnknown: + if boxed { + buffer.appendInt32(-732254058) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(let _data): + return ("passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow", [("salt1", ConstructorParameterDescription(_data.salt1)), ("salt2", ConstructorParameterDescription(_data.salt2)), ("g", ConstructorParameterDescription(_data.g)), ("p", ConstructorParameterDescription(_data.p))]) + case .passwordKdfAlgoUnknown: + return ("passwordKdfAlgoUnknown", []) + } + } + + public static func parse_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(_ reader: BufferReader) -> PasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Buffer? + _2 = parseBytes(reader) var _3: Int32? _3 = reader.readInt32() - var _4: [String]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } + var _4: Buffer? + _4 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCallProtocol.phoneCallProtocol(Cons_phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!)) + return Api.PasswordKdfAlgo.passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(Cons_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow(salt1: _1!, salt2: _2!, g: _3!, p: _4!)) } else { return nil } } + public static func parse_passwordKdfAlgoUnknown(_ reader: BufferReader) -> PasswordKdfAlgo? { + return Api.PasswordKdfAlgo.passwordKdfAlgoUnknown + } } } public extension Api { - enum PhoneConnection: TypeConstructorDescription { - public class Cons_phoneConnection: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var ip: String - public var ipv6: String - public var port: Int32 - public var peerTag: Buffer - public init(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) { - self.flags = flags + enum PaymentCharge: TypeConstructorDescription { + public class Cons_paymentCharge: TypeConstructorDescription { + public var id: String + public var providerChargeId: String + public init(id: String, providerChargeId: String) { self.id = id - self.ip = ip - self.ipv6 = ipv6 - self.port = port - self.peerTag = peerTag + self.providerChargeId = providerChargeId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneConnection", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("ip", ConstructorParameterDescription(self.ip)), ("ipv6", ConstructorParameterDescription(self.ipv6)), ("port", ConstructorParameterDescription(self.port)), ("peerTag", ConstructorParameterDescription(self.peerTag))]) + return ("paymentCharge", [("id", ConstructorParameterDescription(self.id)), ("providerChargeId", ConstructorParameterDescription(self.providerChargeId))]) } } - public class Cons_phoneConnectionWebrtc: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var ip: String - public var ipv6: String - public var port: Int32 - public var username: String - public var password: String - public init(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) { - self.flags = flags - self.id = id - self.ip = ip - self.ipv6 = ipv6 - self.port = port - self.username = username - self.password = password - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneConnectionWebrtc", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("ip", ConstructorParameterDescription(self.ip)), ("ipv6", ConstructorParameterDescription(self.ipv6)), ("port", ConstructorParameterDescription(self.port)), ("username", ConstructorParameterDescription(self.username)), ("password", ConstructorParameterDescription(self.password))]) - } - } - case phoneConnection(Cons_phoneConnection) - case phoneConnectionWebrtc(Cons_phoneConnectionWebrtc) + case paymentCharge(Cons_paymentCharge) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .phoneConnection(let _data): + case .paymentCharge(let _data): if boxed { - buffer.appendInt32(-1665063993) + buffer.appendInt32(-368917890) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeString(_data.ip, buffer: buffer, boxed: false) - serializeString(_data.ipv6, buffer: buffer, boxed: false) - serializeInt32(_data.port, buffer: buffer, boxed: false) - serializeBytes(_data.peerTag, buffer: buffer, boxed: false) - break - case .phoneConnectionWebrtc(let _data): - if boxed { - buffer.appendInt32(1667228533) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeString(_data.ip, buffer: buffer, boxed: false) - serializeString(_data.ipv6, buffer: buffer, boxed: false) - serializeInt32(_data.port, buffer: buffer, boxed: false) - serializeString(_data.username, buffer: buffer, boxed: false) - serializeString(_data.password, buffer: buffer, boxed: false) + serializeString(_data.id, buffer: buffer, boxed: false) + serializeString(_data.providerChargeId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .phoneConnection(let _data): - return ("phoneConnection", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("ip", ConstructorParameterDescription(_data.ip)), ("ipv6", ConstructorParameterDescription(_data.ipv6)), ("port", ConstructorParameterDescription(_data.port)), ("peerTag", ConstructorParameterDescription(_data.peerTag))]) - case .phoneConnectionWebrtc(let _data): - return ("phoneConnectionWebrtc", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("ip", ConstructorParameterDescription(_data.ip)), ("ipv6", ConstructorParameterDescription(_data.ipv6)), ("port", ConstructorParameterDescription(_data.port)), ("username", ConstructorParameterDescription(_data.username)), ("password", ConstructorParameterDescription(_data.password))]) + case .paymentCharge(let _data): + return ("paymentCharge", [("id", ConstructorParameterDescription(_data.id)), ("providerChargeId", ConstructorParameterDescription(_data.providerChargeId))]) } } - public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) + public static func parse_paymentCharge(_ reader: BufferReader) -> PaymentCharge? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PhoneConnection.phoneConnection(Cons_phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!)) - } - else { - return nil - } - } - public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: String? - _6 = parseString(reader) - var _7: String? - _7 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PhoneConnection.phoneConnectionWebrtc(Cons_phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!)) + if _c1 && _c2 { + return Api.PaymentCharge.paymentCharge(Cons_paymentCharge(id: _1!, providerChargeId: _2!)) } else { return nil @@ -294,129 +804,522 @@ public extension Api { } } public extension Api { - enum Photo: TypeConstructorDescription { - public class Cons_photo: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var fileReference: Buffer - public var date: Int32 - public var sizes: [Api.PhotoSize] - public var videoSizes: [Api.VideoSize]? - public var dcId: Int32 - public init(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.fileReference = fileReference - self.date = date - self.sizes = sizes - self.videoSizes = videoSizes - self.dcId = dcId + enum PaymentFormMethod: TypeConstructorDescription { + public class Cons_paymentFormMethod: TypeConstructorDescription { + public var url: String + public var title: String + public init(url: String, title: String) { + self.url = url + self.title = title } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photo", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("fileReference", ConstructorParameterDescription(self.fileReference)), ("date", ConstructorParameterDescription(self.date)), ("sizes", ConstructorParameterDescription(self.sizes)), ("videoSizes", ConstructorParameterDescription(self.videoSizes)), ("dcId", ConstructorParameterDescription(self.dcId))]) + return ("paymentFormMethod", [("url", ConstructorParameterDescription(self.url)), ("title", ConstructorParameterDescription(self.title))]) } } - public class Cons_photoEmpty: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoEmpty", [("id", ConstructorParameterDescription(self.id))]) - } - } - case photo(Cons_photo) - case photoEmpty(Cons_photoEmpty) + case paymentFormMethod(Cons_paymentFormMethod) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photo(let _data): + case .paymentFormMethod(let _data): if boxed { - buffer.appendInt32(-82216347) + buffer.appendInt32(-1996951013) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paymentFormMethod(let _data): + return ("paymentFormMethod", [("url", ConstructorParameterDescription(_data.url)), ("title", ConstructorParameterDescription(_data.title))]) + } + } + + public static func parse_paymentFormMethod(_ reader: BufferReader) -> PaymentFormMethod? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PaymentFormMethod.paymentFormMethod(Cons_paymentFormMethod(url: _1!, title: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PaymentRequestedInfo: TypeConstructorDescription { + public class Cons_paymentRequestedInfo: TypeConstructorDescription { + public var flags: Int32 + public var name: String? + public var phone: String? + public var email: String? + public var shippingAddress: Api.PostAddress? + public init(flags: Int32, name: String?, phone: String?, email: String?, shippingAddress: Api.PostAddress?) { + self.flags = flags + self.name = name + self.phone = phone + self.email = email + self.shippingAddress = shippingAddress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentRequestedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("name", ConstructorParameterDescription(self.name)), ("phone", ConstructorParameterDescription(self.phone)), ("email", ConstructorParameterDescription(self.email)), ("shippingAddress", ConstructorParameterDescription(self.shippingAddress))]) + } + } + case paymentRequestedInfo(Cons_paymentRequestedInfo) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentRequestedInfo(let _data): + if boxed { + buffer.appendInt32(-1868808300) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeBytes(_data.fileReference, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.name!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.phone!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.email!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.shippingAddress!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paymentRequestedInfo(let _data): + return ("paymentRequestedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("name", ConstructorParameterDescription(_data.name)), ("phone", ConstructorParameterDescription(_data.phone)), ("email", ConstructorParameterDescription(_data.email)), ("shippingAddress", ConstructorParameterDescription(_data.shippingAddress))]) + } + } + + public static func parse_paymentRequestedInfo(_ reader: BufferReader) -> PaymentRequestedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseString(reader) + } + var _5: Api.PostAddress? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PostAddress + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PaymentRequestedInfo.paymentRequestedInfo(Cons_paymentRequestedInfo(flags: _1!, name: _2, phone: _3, email: _4, shippingAddress: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PaymentSavedCredentials: TypeConstructorDescription { + public class Cons_paymentSavedCredentialsCard: TypeConstructorDescription { + public var id: String + public var title: String + public init(id: String, title: String) { + self.id = id + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentSavedCredentialsCard", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title))]) + } + } + case paymentSavedCredentialsCard(Cons_paymentSavedCredentialsCard) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentSavedCredentialsCard(let _data): + if boxed { + buffer.appendInt32(-842892769) + } + serializeString(_data.id, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paymentSavedCredentialsCard(let _data): + return ("paymentSavedCredentialsCard", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title))]) + } + } + + public static func parse_paymentSavedCredentialsCard(_ reader: BufferReader) -> PaymentSavedCredentials? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PaymentSavedCredentials.paymentSavedCredentialsCard(Cons_paymentSavedCredentialsCard(id: _1!, title: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Peer: TypeConstructorDescription { + public class Cons_peerChannel: TypeConstructorDescription { + public var channelId: Int64 + public init(channelId: Int64) { + self.channelId = channelId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerChannel", [("channelId", ConstructorParameterDescription(self.channelId))]) + } + } + public class Cons_peerChat: TypeConstructorDescription { + public var chatId: Int64 + public init(chatId: Int64) { + self.chatId = chatId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerChat", [("chatId", ConstructorParameterDescription(self.chatId))]) + } + } + public class Cons_peerUser: TypeConstructorDescription { + public var userId: Int64 + public init(userId: Int64) { + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerUser", [("userId", ConstructorParameterDescription(self.userId))]) + } + } + case peerChannel(Cons_peerChannel) + case peerChat(Cons_peerChat) + case peerUser(Cons_peerUser) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerChannel(let _data): + if boxed { + buffer.appendInt32(-1566230754) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + break + case .peerChat(let _data): + if boxed { + buffer.appendInt32(918946202) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + break + case .peerUser(let _data): + if boxed { + buffer.appendInt32(1498486562) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerChannel(let _data): + return ("peerChannel", [("channelId", ConstructorParameterDescription(_data.channelId))]) + case .peerChat(let _data): + return ("peerChat", [("chatId", ConstructorParameterDescription(_data.chatId))]) + case .peerUser(let _data): + return ("peerUser", [("userId", ConstructorParameterDescription(_data.userId))]) + } + } + + public static func parse_peerChannel(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Peer.peerChannel(Cons_peerChannel(channelId: _1!)) + } + else { + return nil + } + } + public static func parse_peerChat(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Peer.peerChat(Cons_peerChat(chatId: _1!)) + } + else { + return nil + } + } + public static func parse_peerUser(_ reader: BufferReader) -> Peer? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Peer.peerUser(Cons_peerUser(userId: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PeerBlocked: TypeConstructorDescription { + public class Cons_peerBlocked: TypeConstructorDescription { + public var peerId: Api.Peer + public var date: Int32 + public init(peerId: Api.Peer, date: Int32) { + self.peerId = peerId + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerBlocked", [("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case peerBlocked(Cons_peerBlocked) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerBlocked(let _data): + if boxed { + buffer.appendInt32(-386039788) + } + _data.peerId.serialize(buffer, true) serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerBlocked(let _data): + return ("peerBlocked", [("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_peerBlocked(_ reader: BufferReader) -> PeerBlocked? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PeerBlocked.peerBlocked(Cons_peerBlocked(peerId: _1!, date: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PeerColor: TypeConstructorDescription { + public class Cons_inputPeerColorCollectible: TypeConstructorDescription { + public var collectibleId: Int64 + public init(collectibleId: Int64) { + self.collectibleId = collectibleId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputPeerColorCollectible", [("collectibleId", ConstructorParameterDescription(self.collectibleId))]) + } + } + public class Cons_peerColor: TypeConstructorDescription { + public var flags: Int32 + public var color: Int32? + public var backgroundEmojiId: Int64? + public init(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) { + self.flags = flags + self.color = color + self.backgroundEmojiId = backgroundEmojiId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerColor", [("flags", ConstructorParameterDescription(self.flags)), ("color", ConstructorParameterDescription(self.color)), ("backgroundEmojiId", ConstructorParameterDescription(self.backgroundEmojiId))]) + } + } + public class Cons_peerColorCollectible: TypeConstructorDescription { + public var flags: Int32 + public var collectibleId: Int64 + public var giftEmojiId: Int64 + public var backgroundEmojiId: Int64 + public var accentColor: Int32 + public var colors: [Int32] + public var darkAccentColor: Int32? + public var darkColors: [Int32]? + public init(flags: Int32, collectibleId: Int64, giftEmojiId: Int64, backgroundEmojiId: Int64, accentColor: Int32, colors: [Int32], darkAccentColor: Int32?, darkColors: [Int32]?) { + self.flags = flags + self.collectibleId = collectibleId + self.giftEmojiId = giftEmojiId + self.backgroundEmojiId = backgroundEmojiId + self.accentColor = accentColor + self.colors = colors + self.darkAccentColor = darkAccentColor + self.darkColors = darkColors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerColorCollectible", [("flags", ConstructorParameterDescription(self.flags)), ("collectibleId", ConstructorParameterDescription(self.collectibleId)), ("giftEmojiId", ConstructorParameterDescription(self.giftEmojiId)), ("backgroundEmojiId", ConstructorParameterDescription(self.backgroundEmojiId)), ("accentColor", ConstructorParameterDescription(self.accentColor)), ("colors", ConstructorParameterDescription(self.colors)), ("darkAccentColor", ConstructorParameterDescription(self.darkAccentColor)), ("darkColors", ConstructorParameterDescription(self.darkColors))]) + } + } + case inputPeerColorCollectible(Cons_inputPeerColorCollectible) + case peerColor(Cons_peerColor) + case peerColorCollectible(Cons_peerColorCollectible) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputPeerColorCollectible(let _data): + if boxed { + buffer.appendInt32(-1192589655) + } + serializeInt64(_data.collectibleId, buffer: buffer, boxed: false) + break + case .peerColor(let _data): + if boxed { + buffer.appendInt32(-1253352753) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.color!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt64(_data.backgroundEmojiId!, buffer: buffer, boxed: false) + } + break + case .peerColorCollectible(let _data): + if boxed { + buffer.appendInt32(-1178573926) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.collectibleId, buffer: buffer, boxed: false) + serializeInt64(_data.giftEmojiId, buffer: buffer, boxed: false) + serializeInt64(_data.backgroundEmojiId, buffer: buffer, boxed: false) + serializeInt32(_data.accentColor, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sizes.count)) - for item in _data.sizes { - item.serialize(buffer, true) + buffer.appendInt32(Int32(_data.colors.count)) + for item in _data.colors { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.darkAccentColor!, buffer: buffer, boxed: false) } if Int(_data.flags) & Int(1 << 1) != 0 { buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.videoSizes!.count)) - for item in _data.videoSizes! { - item.serialize(buffer, true) + buffer.appendInt32(Int32(_data.darkColors!.count)) + for item in _data.darkColors! { + serializeInt32(item, buffer: buffer, boxed: false) } } - serializeInt32(_data.dcId, buffer: buffer, boxed: false) - break - case .photoEmpty(let _data): - if boxed { - buffer.appendInt32(590459437) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .photo(let _data): - return ("photo", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("fileReference", ConstructorParameterDescription(_data.fileReference)), ("date", ConstructorParameterDescription(_data.date)), ("sizes", ConstructorParameterDescription(_data.sizes)), ("videoSizes", ConstructorParameterDescription(_data.videoSizes)), ("dcId", ConstructorParameterDescription(_data.dcId))]) - case .photoEmpty(let _data): - return ("photoEmpty", [("id", ConstructorParameterDescription(_data.id))]) + case .inputPeerColorCollectible(let _data): + return ("inputPeerColorCollectible", [("collectibleId", ConstructorParameterDescription(_data.collectibleId))]) + case .peerColor(let _data): + return ("peerColor", [("flags", ConstructorParameterDescription(_data.flags)), ("color", ConstructorParameterDescription(_data.color)), ("backgroundEmojiId", ConstructorParameterDescription(_data.backgroundEmojiId))]) + case .peerColorCollectible(let _data): + return ("peerColorCollectible", [("flags", ConstructorParameterDescription(_data.flags)), ("collectibleId", ConstructorParameterDescription(_data.collectibleId)), ("giftEmojiId", ConstructorParameterDescription(_data.giftEmojiId)), ("backgroundEmojiId", ConstructorParameterDescription(_data.backgroundEmojiId)), ("accentColor", ConstructorParameterDescription(_data.accentColor)), ("colors", ConstructorParameterDescription(_data.colors)), ("darkAccentColor", ConstructorParameterDescription(_data.darkAccentColor)), ("darkColors", ConstructorParameterDescription(_data.darkColors))]) } } - public static func parse_photo(_ reader: BufferReader) -> Photo? { + public static func parse_inputPeerColorCollectible(_ reader: BufferReader) -> PeerColor? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.PeerColor.inputPeerColorCollectible(Cons_inputPeerColorCollectible(collectibleId: _1!)) + } + else { + return nil + } + } + public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int64? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.PeerColor.peerColor(Cons_peerColor(flags: _1!, color: _2, backgroundEmojiId: _3)) + } + else { + return nil + } + } + public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() var _3: Int64? _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) + var _4: Int64? + _4 = reader.readInt64() var _5: Int32? _5 = reader.readInt32() - var _6: [Api.PhotoSize]? + var _6: [Int32]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - var _7: [Api.VideoSize]? - if Int(_1!) & Int(1 << 1) != 0 { + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = reader.readInt32() + } + var _8: [Int32]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self) + _8 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } - var _8: Int32? - _8 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Photo.photo(Cons_photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!)) - } - else { - return nil - } - } - public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Photo.photoEmpty(Cons_photoEmpty(id: _1!)) + return Api.PeerColor.peerColorCollectible(Cons_peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8)) } else { return nil @@ -425,259 +1328,85 @@ public extension Api { } } public extension Api { - enum PhotoSize: TypeConstructorDescription { - public class Cons_photoCachedSize: TypeConstructorDescription { - public var type: String - public var w: Int32 - public var h: Int32 - public var bytes: Buffer - public init(type: String, w: Int32, h: Int32, bytes: Buffer) { - self.type = type - self.w = w - self.h = h - self.bytes = bytes + enum PeerLocated: TypeConstructorDescription { + public class Cons_peerLocated: TypeConstructorDescription { + public var peer: Api.Peer + public var expires: Int32 + public var distance: Int32 + public init(peer: Api.Peer, expires: Int32, distance: Int32) { + self.peer = peer + self.expires = expires + self.distance = distance } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoCachedSize", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("bytes", ConstructorParameterDescription(self.bytes))]) + return ("peerLocated", [("peer", ConstructorParameterDescription(self.peer)), ("expires", ConstructorParameterDescription(self.expires)), ("distance", ConstructorParameterDescription(self.distance))]) } } - public class Cons_photoPathSize: TypeConstructorDescription { - public var type: String - public var bytes: Buffer - public init(type: String, bytes: Buffer) { - self.type = type - self.bytes = bytes + public class Cons_peerSelfLocated: TypeConstructorDescription { + public var expires: Int32 + public init(expires: Int32) { + self.expires = expires } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoPathSize", [("type", ConstructorParameterDescription(self.type)), ("bytes", ConstructorParameterDescription(self.bytes))]) + return ("peerSelfLocated", [("expires", ConstructorParameterDescription(self.expires))]) } } - public class Cons_photoSize: TypeConstructorDescription { - public var type: String - public var w: Int32 - public var h: Int32 - public var size: Int32 - public init(type: String, w: Int32, h: Int32, size: Int32) { - self.type = type - self.w = w - self.h = h - self.size = size - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoSize", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("size", ConstructorParameterDescription(self.size))]) - } - } - public class Cons_photoSizeEmpty: TypeConstructorDescription { - public var type: String - public init(type: String) { - self.type = type - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoSizeEmpty", [("type", ConstructorParameterDescription(self.type))]) - } - } - public class Cons_photoSizeProgressive: TypeConstructorDescription { - public var type: String - public var w: Int32 - public var h: Int32 - public var sizes: [Int32] - public init(type: String, w: Int32, h: Int32, sizes: [Int32]) { - self.type = type - self.w = w - self.h = h - self.sizes = sizes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoSizeProgressive", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("sizes", ConstructorParameterDescription(self.sizes))]) - } - } - public class Cons_photoStrippedSize: TypeConstructorDescription { - public var type: String - public var bytes: Buffer - public init(type: String, bytes: Buffer) { - self.type = type - self.bytes = bytes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photoStrippedSize", [("type", ConstructorParameterDescription(self.type)), ("bytes", ConstructorParameterDescription(self.bytes))]) - } - } - case photoCachedSize(Cons_photoCachedSize) - case photoPathSize(Cons_photoPathSize) - case photoSize(Cons_photoSize) - case photoSizeEmpty(Cons_photoSizeEmpty) - case photoSizeProgressive(Cons_photoSizeProgressive) - case photoStrippedSize(Cons_photoStrippedSize) + case peerLocated(Cons_peerLocated) + case peerSelfLocated(Cons_peerSelfLocated) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .photoCachedSize(let _data): + case .peerLocated(let _data): if boxed { - buffer.appendInt32(35527382) + buffer.appendInt32(-901375139) } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.expires, buffer: buffer, boxed: false) + serializeInt32(_data.distance, buffer: buffer, boxed: false) break - case .photoPathSize(let _data): + case .peerSelfLocated(let _data): if boxed { - buffer.appendInt32(-668906175) + buffer.appendInt32(-118740917) } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) - break - case .photoSize(let _data): - if boxed { - buffer.appendInt32(1976012384) - } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - serializeInt32(_data.size, buffer: buffer, boxed: false) - break - case .photoSizeEmpty(let _data): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(_data.type, buffer: buffer, boxed: false) - break - case .photoSizeProgressive(let _data): - if boxed { - buffer.appendInt32(-96535659) - } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sizes.count)) - for item in _data.sizes { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .photoStrippedSize(let _data): - if boxed { - buffer.appendInt32(-525288402) - } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) + serializeInt32(_data.expires, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .photoCachedSize(let _data): - return ("photoCachedSize", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("bytes", ConstructorParameterDescription(_data.bytes))]) - case .photoPathSize(let _data): - return ("photoPathSize", [("type", ConstructorParameterDescription(_data.type)), ("bytes", ConstructorParameterDescription(_data.bytes))]) - case .photoSize(let _data): - return ("photoSize", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("size", ConstructorParameterDescription(_data.size))]) - case .photoSizeEmpty(let _data): - return ("photoSizeEmpty", [("type", ConstructorParameterDescription(_data.type))]) - case .photoSizeProgressive(let _data): - return ("photoSizeProgressive", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("sizes", ConstructorParameterDescription(_data.sizes))]) - case .photoStrippedSize(let _data): - return ("photoStrippedSize", [("type", ConstructorParameterDescription(_data.type)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + case .peerLocated(let _data): + return ("peerLocated", [("peer", ConstructorParameterDescription(_data.peer)), ("expires", ConstructorParameterDescription(_data.expires)), ("distance", ConstructorParameterDescription(_data.distance))]) + case .peerSelfLocated(let _data): + return ("peerSelfLocated", [("expires", ConstructorParameterDescription(_data.expires))]) } } - public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) + public static func parse_peerLocated(_ reader: BufferReader) -> PeerLocated? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } var _2: Int32? _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoCachedSize(Cons_photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!)) + if _c1 && _c2 && _c3 { + return Api.PeerLocated.peerLocated(Cons_peerLocated(peer: _1!, expires: _2!, distance: _3!)) } else { return nil } } - public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoPathSize(Cons_photoPathSize(type: _1!, bytes: _2!)) - } - else { - return nil - } - } - public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSize(Cons_photoSize(type: _1!, w: _2!, h: _3!, size: _4!)) - } - else { - return nil - } - } - public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) + public static func parse_peerSelfLocated(_ reader: BufferReader) -> PeerLocated? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.PhotoSize.photoSizeEmpty(Cons_photoSizeEmpty(type: _1!)) - } - else { - return nil - } - } - public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhotoSize.photoSizeProgressive(Cons_photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!)) - } - else { - return nil - } - } - public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PhotoSize.photoStrippedSize(Cons_photoStrippedSize(type: _1!, bytes: _2!)) + return Api.PeerLocated.peerSelfLocated(Cons_peerSelfLocated(expires: _1!)) } else { return nil @@ -686,559 +1415,456 @@ public extension Api { } } public extension Api { - enum Poll: TypeConstructorDescription { - public class Cons_poll: TypeConstructorDescription { - public var id: Int64 + enum PeerNotifySettings: TypeConstructorDescription { + public class Cons_peerNotifySettings: TypeConstructorDescription { public var flags: Int32 - public var question: Api.TextWithEntities - public var answers: [Api.PollAnswer] - public var closePeriod: Int32? - public var closeDate: Int32? - public var hash: Int64 - public init(id: Int64, flags: Int32, question: Api.TextWithEntities, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?, hash: Int64) { - self.id = id + public var showPreviews: Api.Bool? + public var silent: Api.Bool? + public var muteUntil: Int32? + public var iosSound: Api.NotificationSound? + public var androidSound: Api.NotificationSound? + public var otherSound: Api.NotificationSound? + public var storiesMuted: Api.Bool? + public var storiesHideSender: Api.Bool? + public var storiesIosSound: Api.NotificationSound? + public var storiesAndroidSound: Api.NotificationSound? + public var storiesOtherSound: Api.NotificationSound? + public init(flags: Int32, showPreviews: Api.Bool?, silent: Api.Bool?, muteUntil: Int32?, iosSound: Api.NotificationSound?, androidSound: Api.NotificationSound?, otherSound: Api.NotificationSound?, storiesMuted: Api.Bool?, storiesHideSender: Api.Bool?, storiesIosSound: Api.NotificationSound?, storiesAndroidSound: Api.NotificationSound?, storiesOtherSound: Api.NotificationSound?) { self.flags = flags - self.question = question - self.answers = answers - self.closePeriod = closePeriod - self.closeDate = closeDate - self.hash = hash + self.showPreviews = showPreviews + self.silent = silent + self.muteUntil = muteUntil + self.iosSound = iosSound + self.androidSound = androidSound + self.otherSound = otherSound + self.storiesMuted = storiesMuted + self.storiesHideSender = storiesHideSender + self.storiesIosSound = storiesIosSound + self.storiesAndroidSound = storiesAndroidSound + self.storiesOtherSound = storiesOtherSound } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("poll", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("question", ConstructorParameterDescription(self.question)), ("answers", ConstructorParameterDescription(self.answers)), ("closePeriod", ConstructorParameterDescription(self.closePeriod)), ("closeDate", ConstructorParameterDescription(self.closeDate)), ("hash", ConstructorParameterDescription(self.hash))]) + return ("peerNotifySettings", [("flags", ConstructorParameterDescription(self.flags)), ("showPreviews", ConstructorParameterDescription(self.showPreviews)), ("silent", ConstructorParameterDescription(self.silent)), ("muteUntil", ConstructorParameterDescription(self.muteUntil)), ("iosSound", ConstructorParameterDescription(self.iosSound)), ("androidSound", ConstructorParameterDescription(self.androidSound)), ("otherSound", ConstructorParameterDescription(self.otherSound)), ("storiesMuted", ConstructorParameterDescription(self.storiesMuted)), ("storiesHideSender", ConstructorParameterDescription(self.storiesHideSender)), ("storiesIosSound", ConstructorParameterDescription(self.storiesIosSound)), ("storiesAndroidSound", ConstructorParameterDescription(self.storiesAndroidSound)), ("storiesOtherSound", ConstructorParameterDescription(self.storiesOtherSound))]) } } - case poll(Cons_poll) + case peerNotifySettings(Cons_peerNotifySettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .poll(let _data): + case .peerNotifySettings(let _data): if boxed { - buffer.appendInt32(-1203610647) + buffer.appendInt32(-1721619444) } - serializeInt64(_data.id, buffer: buffer, boxed: false) serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.question.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.answers.count)) - for item in _data.answers { - item.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.showPreviews!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.silent!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.muteUntil!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.iosSound!.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.closePeriod!, buffer: buffer, boxed: false) + _data.androidSound!.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.closeDate!, buffer: buffer, boxed: false) + _data.otherSound!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + _data.storiesMuted!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + _data.storiesHideSender!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + _data.storiesIosSound!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.storiesAndroidSound!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.storiesOtherSound!.serialize(buffer, true) } - serializeInt64(_data.hash, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .poll(let _data): - return ("poll", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("question", ConstructorParameterDescription(_data.question)), ("answers", ConstructorParameterDescription(_data.answers)), ("closePeriod", ConstructorParameterDescription(_data.closePeriod)), ("closeDate", ConstructorParameterDescription(_data.closeDate)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .peerNotifySettings(let _data): + return ("peerNotifySettings", [("flags", ConstructorParameterDescription(_data.flags)), ("showPreviews", ConstructorParameterDescription(_data.showPreviews)), ("silent", ConstructorParameterDescription(_data.silent)), ("muteUntil", ConstructorParameterDescription(_data.muteUntil)), ("iosSound", ConstructorParameterDescription(_data.iosSound)), ("androidSound", ConstructorParameterDescription(_data.androidSound)), ("otherSound", ConstructorParameterDescription(_data.otherSound)), ("storiesMuted", ConstructorParameterDescription(_data.storiesMuted)), ("storiesHideSender", ConstructorParameterDescription(_data.storiesHideSender)), ("storiesIosSound", ConstructorParameterDescription(_data.storiesIosSound)), ("storiesAndroidSound", ConstructorParameterDescription(_data.storiesAndroidSound)), ("storiesOtherSound", ConstructorParameterDescription(_data.storiesOtherSound))]) } } - public static func parse_poll(_ reader: BufferReader) -> Poll? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_peerNotifySettings(_ reader: BufferReader) -> PeerNotifySettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _3: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + var _5: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + var _6: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + var _7: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + var _8: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _9: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _10: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + var _11: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + var _12: Api.NotificationSound? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.PeerNotifySettings.peerNotifySettings(Cons_peerNotifySettings(flags: _1!, showPreviews: _2, silent: _3, muteUntil: _4, iosSound: _5, androidSound: _6, otherSound: _7, storiesMuted: _8, storiesHideSender: _9, storiesIosSound: _10, storiesAndroidSound: _11, storiesOtherSound: _12)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PeerSettings: TypeConstructorDescription { + public class Cons_peerSettings: TypeConstructorDescription { + public var flags: Int32 + public var geoDistance: Int32? + public var requestChatTitle: String? + public var requestChatDate: Int32? + public var businessBotId: Int64? + public var businessBotManageUrl: String? + public var chargePaidMessageStars: Int64? + public var registrationMonth: String? + public var phoneCountry: String? + public var nameChangeDate: Int32? + public var photoChangeDate: Int32? + public init(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?, chargePaidMessageStars: Int64?, registrationMonth: String?, phoneCountry: String?, nameChangeDate: Int32?, photoChangeDate: Int32?) { + self.flags = flags + self.geoDistance = geoDistance + self.requestChatTitle = requestChatTitle + self.requestChatDate = requestChatDate + self.businessBotId = businessBotId + self.businessBotManageUrl = businessBotManageUrl + self.chargePaidMessageStars = chargePaidMessageStars + self.registrationMonth = registrationMonth + self.phoneCountry = phoneCountry + self.nameChangeDate = nameChangeDate + self.photoChangeDate = photoChangeDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerSettings", [("flags", ConstructorParameterDescription(self.flags)), ("geoDistance", ConstructorParameterDescription(self.geoDistance)), ("requestChatTitle", ConstructorParameterDescription(self.requestChatTitle)), ("requestChatDate", ConstructorParameterDescription(self.requestChatDate)), ("businessBotId", ConstructorParameterDescription(self.businessBotId)), ("businessBotManageUrl", ConstructorParameterDescription(self.businessBotManageUrl)), ("chargePaidMessageStars", ConstructorParameterDescription(self.chargePaidMessageStars)), ("registrationMonth", ConstructorParameterDescription(self.registrationMonth)), ("phoneCountry", ConstructorParameterDescription(self.phoneCountry)), ("nameChangeDate", ConstructorParameterDescription(self.nameChangeDate)), ("photoChangeDate", ConstructorParameterDescription(self.photoChangeDate))]) + } + } + case peerSettings(Cons_peerSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerSettings(let _data): + if boxed { + buffer.appendInt32(-193510921) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt32(_data.geoDistance!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + serializeString(_data.requestChatTitle!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + serializeInt32(_data.requestChatDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + serializeInt64(_data.businessBotId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + serializeString(_data.businessBotManageUrl!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + serializeInt64(_data.chargePaidMessageStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeString(_data.registrationMonth!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeString(_data.phoneCountry!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + serializeInt32(_data.nameChangeDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 18) != 0 { + serializeInt32(_data.photoChangeDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerSettings(let _data): + return ("peerSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("geoDistance", ConstructorParameterDescription(_data.geoDistance)), ("requestChatTitle", ConstructorParameterDescription(_data.requestChatTitle)), ("requestChatDate", ConstructorParameterDescription(_data.requestChatDate)), ("businessBotId", ConstructorParameterDescription(_data.businessBotId)), ("businessBotManageUrl", ConstructorParameterDescription(_data.businessBotManageUrl)), ("chargePaidMessageStars", ConstructorParameterDescription(_data.chargePaidMessageStars)), ("registrationMonth", ConstructorParameterDescription(_data.registrationMonth)), ("phoneCountry", ConstructorParameterDescription(_data.phoneCountry)), ("nameChangeDate", ConstructorParameterDescription(_data.nameChangeDate)), ("photoChangeDate", ConstructorParameterDescription(_data.photoChangeDate))]) + } + } + + public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _2 = reader.readInt32() + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + _3 = parseString(reader) + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + _4 = reader.readInt32() + } + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + _5 = reader.readInt64() + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + _6 = parseString(reader) + } + var _7: Int64? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + _7 = reader.readInt64() + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _8 = parseString(reader) + } + var _9: String? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _9 = parseString(reader) + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _10 = reader.readInt32() + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + _11 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.PeerSettings.peerSettings(Cons_peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6, chargePaidMessageStars: _7, registrationMonth: _8, phoneCountry: _9, nameChangeDate: _10, photoChangeDate: _11)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PeerStories: TypeConstructorDescription { + public class Cons_peerStories: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var maxReadId: Int32? + public var stories: [Api.StoryItem] + public init(flags: Int32, peer: Api.Peer, maxReadId: Int32?, stories: [Api.StoryItem]) { + self.flags = flags + self.peer = peer + self.maxReadId = maxReadId + self.stories = stories + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerStories", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("maxReadId", ConstructorParameterDescription(self.maxReadId)), ("stories", ConstructorParameterDescription(self.stories))]) + } + } + case peerStories(Cons_peerStories) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerStories(let _data): + if boxed { + buffer.appendInt32(-1707742823) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.maxReadId!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stories.count)) + for item in _data.stories { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerStories(let _data): + return ("peerStories", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("maxReadId", ConstructorParameterDescription(_data.maxReadId)), ("stories", ConstructorParameterDescription(_data.stories))]) + } + } + + public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.StoryItem]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PeerStories.peerStories(Cons_peerStories(flags: _1!, peer: _2!, maxReadId: _3, stories: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PendingSuggestion: TypeConstructorDescription { + public class Cons_pendingSuggestion: TypeConstructorDescription { + public var suggestion: String + public var title: Api.TextWithEntities + public var description: Api.TextWithEntities + public var url: String + public init(suggestion: String, title: Api.TextWithEntities, description: Api.TextWithEntities, url: String) { + self.suggestion = suggestion + self.title = title + self.description = description + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pendingSuggestion", [("suggestion", ConstructorParameterDescription(self.suggestion)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("url", ConstructorParameterDescription(self.url))]) + } + } + case pendingSuggestion(Cons_pendingSuggestion) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pendingSuggestion(let _data): + if boxed { + buffer.appendInt32(-404214254) + } + serializeString(_data.suggestion, buffer: buffer, boxed: false) + _data.title.serialize(buffer, true) + _data.description.serialize(buffer, true) + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pendingSuggestion(let _data): + return ("pendingSuggestion", [("suggestion", ConstructorParameterDescription(_data.suggestion)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_pendingSuggestion(_ reader: BufferReader) -> PendingSuggestion? { + var _1: String? + _1 = parseString(reader) + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } var _3: Api.TextWithEntities? if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } - var _4: [Api.PollAnswer]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswer.self) - } - var _5: Int32? - if Int(_2!) & Int(1 << 4) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_2!) & Int(1 << 5) != 0 { - _6 = reader.readInt32() - } - var _7: Int64? - _7 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_2!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_2!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Poll.poll(Cons_poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6, hash: _7!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum PollAnswer: TypeConstructorDescription { - public class Cons_inputPollAnswer: TypeConstructorDescription { - public var flags: Int32 - public var text: Api.TextWithEntities - public var media: Api.InputMedia? - public init(flags: Int32, text: Api.TextWithEntities, media: Api.InputMedia?) { - self.flags = flags - self.text = text - self.media = media - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputPollAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("media", ConstructorParameterDescription(self.media))]) - } - } - public class Cons_pollAnswer: TypeConstructorDescription { - public var flags: Int32 - public var text: Api.TextWithEntities - public var option: Buffer - public var media: Api.MessageMedia? - public var addedBy: Api.Peer? - public var date: Int32? - public init(flags: Int32, text: Api.TextWithEntities, option: Buffer, media: Api.MessageMedia?, addedBy: Api.Peer?, date: Int32?) { - self.flags = flags - self.text = text - self.option = option - self.media = media - self.addedBy = addedBy - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pollAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option)), ("media", ConstructorParameterDescription(self.media)), ("addedBy", ConstructorParameterDescription(self.addedBy)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case inputPollAnswer(Cons_inputPollAnswer) - case pollAnswer(Cons_pollAnswer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputPollAnswer(let _data): - if boxed { - buffer.appendInt32(429911446) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.text.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.media!.serialize(buffer, true) - } - break - case .pollAnswer(let _data): - if boxed { - buffer.appendInt32(1266514026) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.text.serialize(buffer, true) - serializeBytes(_data.option, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.media!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.addedBy!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.date!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputPollAnswer(let _data): - return ("inputPollAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("media", ConstructorParameterDescription(_data.media))]) - case .pollAnswer(let _data): - return ("pollAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option)), ("media", ConstructorParameterDescription(_data.media)), ("addedBy", ConstructorParameterDescription(_data.addedBy)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_inputPollAnswer(_ reader: BufferReader) -> PollAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _3: Api.InputMedia? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputMedia - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.PollAnswer.inputPollAnswer(Cons_inputPollAnswer(flags: _1!, text: _2!, media: _3)) - } - else { - return nil - } - } - public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Api.MessageMedia? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - var _5: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PollAnswer.pollAnswer(Cons_pollAnswer(flags: _1!, text: _2!, option: _3!, media: _4, addedBy: _5, date: _6)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PollAnswerVoters: TypeConstructorDescription { - public class Cons_pollAnswerVoters: TypeConstructorDescription { - public var flags: Int32 - public var option: Buffer - public var voters: Int32? - public var recentVoters: [Api.Peer]? - public init(flags: Int32, option: Buffer, voters: Int32?, recentVoters: [Api.Peer]?) { - self.flags = flags - self.option = option - self.voters = voters - self.recentVoters = recentVoters - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pollAnswerVoters", [("flags", ConstructorParameterDescription(self.flags)), ("option", ConstructorParameterDescription(self.option)), ("voters", ConstructorParameterDescription(self.voters)), ("recentVoters", ConstructorParameterDescription(self.recentVoters))]) - } - } - case pollAnswerVoters(Cons_pollAnswerVoters) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pollAnswerVoters(let _data): - if boxed { - buffer.appendInt32(910500618) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeBytes(_data.option, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.voters!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentVoters!.count)) - for item in _data.recentVoters! { - item.serialize(buffer, true) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pollAnswerVoters(let _data): - return ("pollAnswerVoters", [("flags", ConstructorParameterDescription(_data.flags)), ("option", ConstructorParameterDescription(_data.option)), ("voters", ConstructorParameterDescription(_data.voters)), ("recentVoters", ConstructorParameterDescription(_data.recentVoters))]) - } - } - - public static func parse_pollAnswerVoters(_ reader: BufferReader) -> PollAnswerVoters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PollAnswerVoters.pollAnswerVoters(Cons_pollAnswerVoters(flags: _1!, option: _2!, voters: _3, recentVoters: _4)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum PollResults: TypeConstructorDescription { - public class Cons_pollResults: TypeConstructorDescription { - public var flags: Int32 - public var results: [Api.PollAnswerVoters]? - public var totalVoters: Int32? - public var recentVoters: [Api.Peer]? - public var solution: String? - public var solutionEntities: [Api.MessageEntity]? - public var solutionMedia: Api.MessageMedia? - public init(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?, solutionMedia: Api.MessageMedia?) { - self.flags = flags - self.results = results - self.totalVoters = totalVoters - self.recentVoters = recentVoters - self.solution = solution - self.solutionEntities = solutionEntities - self.solutionMedia = solutionMedia - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("pollResults", [("flags", ConstructorParameterDescription(self.flags)), ("results", ConstructorParameterDescription(self.results)), ("totalVoters", ConstructorParameterDescription(self.totalVoters)), ("recentVoters", ConstructorParameterDescription(self.recentVoters)), ("solution", ConstructorParameterDescription(self.solution)), ("solutionEntities", ConstructorParameterDescription(self.solutionEntities)), ("solutionMedia", ConstructorParameterDescription(self.solutionMedia))]) - } - } - case pollResults(Cons_pollResults) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .pollResults(let _data): - if boxed { - buffer.appendInt32(-1166298786) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.results!.count)) - for item in _data.results! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.totalVoters!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentVoters!.count)) - for item in _data.recentVoters! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.solution!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.solutionEntities!.count)) - for item in _data.solutionEntities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.solutionMedia!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .pollResults(let _data): - return ("pollResults", [("flags", ConstructorParameterDescription(_data.flags)), ("results", ConstructorParameterDescription(_data.results)), ("totalVoters", ConstructorParameterDescription(_data.totalVoters)), ("recentVoters", ConstructorParameterDescription(_data.recentVoters)), ("solution", ConstructorParameterDescription(_data.solution)), ("solutionEntities", ConstructorParameterDescription(_data.solutionEntities)), ("solutionMedia", ConstructorParameterDescription(_data.solutionMedia))]) - } - } - - public static func parse_pollResults(_ reader: BufferReader) -> PollResults? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.PollAnswerVoters]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswerVoters.self) - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.Peer]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - } - var _5: String? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = parseString(reader) - } - var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _7: Api.MessageMedia? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PollResults.pollResults(Cons_pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6, solutionMedia: _7)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PopularContact: TypeConstructorDescription { - public class Cons_popularContact: TypeConstructorDescription { - public var clientId: Int64 - public var importers: Int32 - public init(clientId: Int64, importers: Int32) { - self.clientId = clientId - self.importers = importers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("popularContact", [("clientId", ConstructorParameterDescription(self.clientId)), ("importers", ConstructorParameterDescription(self.importers))]) - } - } - case popularContact(Cons_popularContact) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .popularContact(let _data): - if boxed { - buffer.appendInt32(1558266229) - } - serializeInt64(_data.clientId, buffer: buffer, boxed: false) - serializeInt32(_data.importers, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .popularContact(let _data): - return ("popularContact", [("clientId", ConstructorParameterDescription(_data.clientId)), ("importers", ConstructorParameterDescription(_data.importers))]) - } - } - - public static func parse_popularContact(_ reader: BufferReader) -> PopularContact? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PopularContact.popularContact(Cons_popularContact(clientId: _1!, importers: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PostAddress: TypeConstructorDescription { - public class Cons_postAddress: TypeConstructorDescription { - public var streetLine1: String - public var streetLine2: String - public var city: String - public var state: String - public var countryIso2: String - public var postCode: String - public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) { - self.streetLine1 = streetLine1 - self.streetLine2 = streetLine2 - self.city = city - self.state = state - self.countryIso2 = countryIso2 - self.postCode = postCode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("postAddress", [("streetLine1", ConstructorParameterDescription(self.streetLine1)), ("streetLine2", ConstructorParameterDescription(self.streetLine2)), ("city", ConstructorParameterDescription(self.city)), ("state", ConstructorParameterDescription(self.state)), ("countryIso2", ConstructorParameterDescription(self.countryIso2)), ("postCode", ConstructorParameterDescription(self.postCode))]) - } - } - case postAddress(Cons_postAddress) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .postAddress(let _data): - if boxed { - buffer.appendInt32(512535275) - } - serializeString(_data.streetLine1, buffer: buffer, boxed: false) - serializeString(_data.streetLine2, buffer: buffer, boxed: false) - serializeString(_data.city, buffer: buffer, boxed: false) - serializeString(_data.state, buffer: buffer, boxed: false) - serializeString(_data.countryIso2, buffer: buffer, boxed: false) - serializeString(_data.postCode, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .postAddress(let _data): - return ("postAddress", [("streetLine1", ConstructorParameterDescription(_data.streetLine1)), ("streetLine2", ConstructorParameterDescription(_data.streetLine2)), ("city", ConstructorParameterDescription(_data.city)), ("state", ConstructorParameterDescription(_data.state)), ("countryIso2", ConstructorParameterDescription(_data.countryIso2)), ("postCode", ConstructorParameterDescription(_data.postCode))]) - } - } - - public static func parse_postAddress(_ reader: BufferReader) -> PostAddress? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.PostAddress.postAddress(Cons_postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!)) + if _c1 && _c2 && _c3 && _c4 { + return Api.PendingSuggestion.pendingSuggestion(Cons_pendingSuggestion(suggestion: _1!, title: _2!, description: _3!, url: _4!)) } else { return nil @@ -1247,243 +1873,221 @@ public extension Api { } } public extension Api { - enum PostInteractionCounters: TypeConstructorDescription { - public class Cons_postInteractionCountersMessage: TypeConstructorDescription { - public var msgId: Int32 - public var views: Int32 - public var forwards: Int32 - public var reactions: Int32 - public init(msgId: Int32, views: Int32, forwards: Int32, reactions: Int32) { - self.msgId = msgId - self.views = views - self.forwards = forwards - self.reactions = reactions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("postInteractionCountersMessage", [("msgId", ConstructorParameterDescription(self.msgId)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("reactions", ConstructorParameterDescription(self.reactions))]) - } - } - public class Cons_postInteractionCountersStory: TypeConstructorDescription { - public var storyId: Int32 - public var views: Int32 - public var forwards: Int32 - public var reactions: Int32 - public init(storyId: Int32, views: Int32, forwards: Int32, reactions: Int32) { - self.storyId = storyId - self.views = views - self.forwards = forwards - self.reactions = reactions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("postInteractionCountersStory", [("storyId", ConstructorParameterDescription(self.storyId)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("reactions", ConstructorParameterDescription(self.reactions))]) - } - } - case postInteractionCountersMessage(Cons_postInteractionCountersMessage) - case postInteractionCountersStory(Cons_postInteractionCountersStory) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .postInteractionCountersMessage(let _data): - if boxed { - buffer.appendInt32(-419066241) - } - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt32(_data.views, buffer: buffer, boxed: false) - serializeInt32(_data.forwards, buffer: buffer, boxed: false) - serializeInt32(_data.reactions, buffer: buffer, boxed: false) - break - case .postInteractionCountersStory(let _data): - if boxed { - buffer.appendInt32(-1974989273) - } - serializeInt32(_data.storyId, buffer: buffer, boxed: false) - serializeInt32(_data.views, buffer: buffer, boxed: false) - serializeInt32(_data.forwards, buffer: buffer, boxed: false) - serializeInt32(_data.reactions, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .postInteractionCountersMessage(let _data): - return ("postInteractionCountersMessage", [("msgId", ConstructorParameterDescription(_data.msgId)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("reactions", ConstructorParameterDescription(_data.reactions))]) - case .postInteractionCountersStory(let _data): - return ("postInteractionCountersStory", [("storyId", ConstructorParameterDescription(_data.storyId)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("reactions", ConstructorParameterDescription(_data.reactions))]) - } - } - - public static func parse_postInteractionCountersMessage(_ reader: BufferReader) -> PostInteractionCounters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersMessage(Cons_postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!)) - } - else { - return nil - } - } - public static func parse_postInteractionCountersStory(_ reader: BufferReader) -> PostInteractionCounters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PostInteractionCounters.postInteractionCountersStory(Cons_postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PremiumGiftCodeOption: TypeConstructorDescription { - public class Cons_premiumGiftCodeOption: TypeConstructorDescription { + enum PhoneCall: TypeConstructorDescription { + public class Cons_phoneCall: TypeConstructorDescription { public var flags: Int32 - public var users: Int32 - public var months: Int32 - public var storeProduct: String? - public var storeQuantity: Int32? - public var currency: String - public var amount: Int64 - public init(flags: Int32, users: Int32, months: Int32, storeProduct: String?, storeQuantity: Int32?, currency: String, amount: Int64) { + public var id: Int64 + public var accessHash: Int64 + public var date: Int32 + public var adminId: Int64 + public var participantId: Int64 + public var gAOrB: Buffer + public var keyFingerprint: Int64 + public var `protocol`: Api.PhoneCallProtocol + public var connections: [Api.PhoneConnection] + public var startDate: Int32 + public var customParameters: Api.DataJSON? + public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?) { self.flags = flags - self.users = users - self.months = months - self.storeProduct = storeProduct - self.storeQuantity = storeQuantity - self.currency = currency - self.amount = amount + self.id = id + self.accessHash = accessHash + self.date = date + self.adminId = adminId + self.participantId = participantId + self.gAOrB = gAOrB + self.keyFingerprint = keyFingerprint + self.`protocol` = `protocol` + self.connections = connections + self.startDate = startDate + self.customParameters = customParameters } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("premiumGiftCodeOption", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("months", ConstructorParameterDescription(self.months)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("storeQuantity", ConstructorParameterDescription(self.storeQuantity)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + return ("phoneCall", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gAOrB", ConstructorParameterDescription(self.gAOrB)), ("keyFingerprint", ConstructorParameterDescription(self.keyFingerprint)), ("`protocol`", ConstructorParameterDescription(self.`protocol`)), ("connections", ConstructorParameterDescription(self.connections)), ("startDate", ConstructorParameterDescription(self.startDate)), ("customParameters", ConstructorParameterDescription(self.customParameters))]) } } - case premiumGiftCodeOption(Cons_premiumGiftCodeOption) + public class Cons_phoneCallAccepted: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var date: Int32 + public var adminId: Int64 + public var participantId: Int64 + public var gB: Buffer + public var `protocol`: Api.PhoneCallProtocol + public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, `protocol`: Api.PhoneCallProtocol) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.date = date + self.adminId = adminId + self.participantId = participantId + self.gB = gB + self.`protocol` = `protocol` + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallAccepted", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gB", ConstructorParameterDescription(self.gB)), ("`protocol`", ConstructorParameterDescription(self.`protocol`))]) + } + } + public class Cons_phoneCallDiscarded: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var reason: Api.PhoneCallDiscardReason? + public var duration: Int32? + public init(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) { + self.flags = flags + self.id = id + self.reason = reason + self.duration = duration + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallDiscarded", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("reason", ConstructorParameterDescription(self.reason)), ("duration", ConstructorParameterDescription(self.duration))]) + } + } + public class Cons_phoneCallEmpty: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallEmpty", [("id", ConstructorParameterDescription(self.id))]) + } + } + public class Cons_phoneCallRequested: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var date: Int32 + public var adminId: Int64 + public var participantId: Int64 + public var gAHash: Buffer + public var `protocol`: Api.PhoneCallProtocol + public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.date = date + self.adminId = adminId + self.participantId = participantId + self.gAHash = gAHash + self.`protocol` = `protocol` + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallRequested", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("gAHash", ConstructorParameterDescription(self.gAHash)), ("`protocol`", ConstructorParameterDescription(self.`protocol`))]) + } + } + public class Cons_phoneCallWaiting: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var date: Int32 + public var adminId: Int64 + public var participantId: Int64 + public var `protocol`: Api.PhoneCallProtocol + public var receiveDate: Int32? + public init(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, `protocol`: Api.PhoneCallProtocol, receiveDate: Int32?) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.date = date + self.adminId = adminId + self.participantId = participantId + self.`protocol` = `protocol` + self.receiveDate = receiveDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallWaiting", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("date", ConstructorParameterDescription(self.date)), ("adminId", ConstructorParameterDescription(self.adminId)), ("participantId", ConstructorParameterDescription(self.participantId)), ("`protocol`", ConstructorParameterDescription(self.`protocol`)), ("receiveDate", ConstructorParameterDescription(self.receiveDate))]) + } + } + case phoneCall(Cons_phoneCall) + case phoneCallAccepted(Cons_phoneCallAccepted) + case phoneCallDiscarded(Cons_phoneCallDiscarded) + case phoneCallEmpty(Cons_phoneCallEmpty) + case phoneCallRequested(Cons_phoneCallRequested) + case phoneCallWaiting(Cons_phoneCallWaiting) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumGiftCodeOption(let _data): + case .phoneCall(let _data): if boxed { - buffer.appendInt32(629052971) + buffer.appendInt32(810769141) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.users, buffer: buffer, boxed: false) - serializeInt32(_data.months, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.adminId, buffer: buffer, boxed: false) + serializeInt64(_data.participantId, buffer: buffer, boxed: false) + serializeBytes(_data.gAOrB, buffer: buffer, boxed: false) + serializeInt64(_data.keyFingerprint, buffer: buffer, boxed: false) + _data.`protocol`.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.connections.count)) + for item in _data.connections { + item.serialize(buffer, true) + } + serializeInt32(_data.startDate, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 7) != 0 { + _data.customParameters!.serialize(buffer, true) + } + break + case .phoneCallAccepted(let _data): + if boxed { + buffer.appendInt32(912311057) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.adminId, buffer: buffer, boxed: false) + serializeInt64(_data.participantId, buffer: buffer, boxed: false) + serializeBytes(_data.gB, buffer: buffer, boxed: false) + _data.`protocol`.serialize(buffer, true) + break + case .phoneCallDiscarded(let _data): + if boxed { + buffer.appendInt32(1355435489) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + _data.reason!.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.storeQuantity!, buffer: buffer, boxed: false) + serializeInt32(_data.duration!, buffer: buffer, boxed: false) } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .premiumGiftCodeOption(let _data): - return ("premiumGiftCodeOption", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("months", ConstructorParameterDescription(_data.months)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("storeQuantity", ConstructorParameterDescription(_data.storeQuantity)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - } - } - - public static func parse_premiumGiftCodeOption(_ reader: BufferReader) -> PremiumGiftCodeOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() - } - var _6: String? - _6 = parseString(reader) - var _7: Int64? - _7 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumGiftCodeOption.premiumGiftCodeOption(Cons_premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PremiumSubscriptionOption: TypeConstructorDescription { - public class Cons_premiumSubscriptionOption: TypeConstructorDescription { - public var flags: Int32 - public var transaction: String? - public var months: Int32 - public var currency: String - public var amount: Int64 - public var botUrl: String - public var storeProduct: String? - public init(flags: Int32, transaction: String?, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) { - self.flags = flags - self.transaction = transaction - self.months = months - self.currency = currency - self.amount = amount - self.botUrl = botUrl - self.storeProduct = storeProduct - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("premiumSubscriptionOption", [("flags", ConstructorParameterDescription(self.flags)), ("transaction", ConstructorParameterDescription(self.transaction)), ("months", ConstructorParameterDescription(self.months)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("botUrl", ConstructorParameterDescription(self.botUrl)), ("storeProduct", ConstructorParameterDescription(self.storeProduct))]) - } - } - case premiumSubscriptionOption(Cons_premiumSubscriptionOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .premiumSubscriptionOption(let _data): + case .phoneCallEmpty(let _data): if boxed { - buffer.appendInt32(1596792306) + buffer.appendInt32(1399245077) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + break + case .phoneCallRequested(let _data): + if boxed { + buffer.appendInt32(347139340) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.transaction!, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.adminId, buffer: buffer, boxed: false) + serializeInt64(_data.participantId, buffer: buffer, boxed: false) + serializeBytes(_data.gAHash, buffer: buffer, boxed: false) + _data.`protocol`.serialize(buffer, true) + break + case .phoneCallWaiting(let _data): + if boxed { + buffer.appendInt32(-987599081) } - serializeInt32(_data.months, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeString(_data.botUrl, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.adminId, buffer: buffer, boxed: false) + serializeInt64(_data.participantId, buffer: buffer, boxed: false) + _data.`protocol`.serialize(buffer, true) if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + serializeInt32(_data.receiveDate!, buffer: buffer, boxed: false) } break } @@ -1491,154 +2095,209 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .premiumSubscriptionOption(let _data): - return ("premiumSubscriptionOption", [("flags", ConstructorParameterDescription(_data.flags)), ("transaction", ConstructorParameterDescription(_data.transaction)), ("months", ConstructorParameterDescription(_data.months)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("botUrl", ConstructorParameterDescription(_data.botUrl)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct))]) + case .phoneCall(let _data): + return ("phoneCall", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gAOrB", ConstructorParameterDescription(_data.gAOrB)), ("keyFingerprint", ConstructorParameterDescription(_data.keyFingerprint)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`)), ("connections", ConstructorParameterDescription(_data.connections)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("customParameters", ConstructorParameterDescription(_data.customParameters))]) + case .phoneCallAccepted(let _data): + return ("phoneCallAccepted", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gB", ConstructorParameterDescription(_data.gB)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`))]) + case .phoneCallDiscarded(let _data): + return ("phoneCallDiscarded", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("reason", ConstructorParameterDescription(_data.reason)), ("duration", ConstructorParameterDescription(_data.duration))]) + case .phoneCallEmpty(let _data): + return ("phoneCallEmpty", [("id", ConstructorParameterDescription(_data.id))]) + case .phoneCallRequested(let _data): + return ("phoneCallRequested", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("gAHash", ConstructorParameterDescription(_data.gAHash)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`))]) + case .phoneCallWaiting(let _data): + return ("phoneCallWaiting", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("date", ConstructorParameterDescription(_data.date)), ("adminId", ConstructorParameterDescription(_data.adminId)), ("participantId", ConstructorParameterDescription(_data.participantId)), ("`protocol`", ConstructorParameterDescription(_data.`protocol`)), ("receiveDate", ConstructorParameterDescription(_data.receiveDate))]) } } - public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? { + public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 3) != 0 { - _2 = parseString(reader) - } - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: String? - _6 = parseString(reader) - var _7: String? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.PremiumSubscriptionOption.premiumSubscriptionOption(Cons_premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7)) - } - else { - return nil - } - } - } -} -public extension Api { - enum PrepaidGiveaway: TypeConstructorDescription { - public class Cons_prepaidGiveaway: TypeConstructorDescription { - public var id: Int64 - public var months: Int32 - public var quantity: Int32 - public var date: Int32 - public init(id: Int64, months: Int32, quantity: Int32, date: Int32) { - self.id = id - self.months = months - self.quantity = quantity - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("prepaidGiveaway", [("id", ConstructorParameterDescription(self.id)), ("months", ConstructorParameterDescription(self.months)), ("quantity", ConstructorParameterDescription(self.quantity)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_prepaidStarsGiveaway: TypeConstructorDescription { - public var id: Int64 - public var stars: Int64 - public var quantity: Int32 - public var boosts: Int32 - public var date: Int32 - public init(id: Int64, stars: Int64, quantity: Int32, boosts: Int32, date: Int32) { - self.id = id - self.stars = stars - self.quantity = quantity - self.boosts = boosts - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("prepaidStarsGiveaway", [("id", ConstructorParameterDescription(self.id)), ("stars", ConstructorParameterDescription(self.stars)), ("quantity", ConstructorParameterDescription(self.quantity)), ("boosts", ConstructorParameterDescription(self.boosts)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case prepaidGiveaway(Cons_prepaidGiveaway) - case prepaidStarsGiveaway(Cons_prepaidStarsGiveaway) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .prepaidGiveaway(let _data): - if boxed { - buffer.appendInt32(-1303143084) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.months, buffer: buffer, boxed: false) - serializeInt32(_data.quantity, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .prepaidStarsGiveaway(let _data): - if boxed { - buffer.appendInt32(-1700956192) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - serializeInt32(_data.quantity, buffer: buffer, boxed: false) - serializeInt32(_data.boosts, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .prepaidGiveaway(let _data): - return ("prepaidGiveaway", [("id", ConstructorParameterDescription(_data.id)), ("months", ConstructorParameterDescription(_data.months)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("date", ConstructorParameterDescription(_data.date))]) - case .prepaidStarsGiveaway(let _data): - return ("prepaidStarsGiveaway", [("id", ConstructorParameterDescription(_data.id)), ("stars", ConstructorParameterDescription(_data.stars)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_prepaidGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PrepaidGiveaway.prepaidGiveaway(Cons_prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!)) - } - else { - return nil - } - } - public static func parse_prepaidStarsGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { - var _1: Int64? - _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Int64? + _8 = reader.readInt64() + var _9: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + var _10: [Api.PhoneConnection]? + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhoneConnection.self) + } + var _11: Int32? + _11 = reader.readInt32() + var _12: Api.DataJSON? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.PrepaidGiveaway.prepaidStarsGiveaway(Cons_prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, boosts: _4!, date: _5!)) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.PhoneCall.phoneCall(Cons_phoneCall(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAOrB: _7!, keyFingerprint: _8!, protocol: _9!, connections: _10!, startDate: _11!, customParameters: _12)) + } + else { + return nil + } + } + public static func parse_phoneCallAccepted(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallAccepted(Cons_phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!)) + } + else { + return nil + } + } + public static func parse_phoneCallDiscarded(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.PhoneCallDiscardReason? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.PhoneCallDiscardReason + } + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhoneCall.phoneCallDiscarded(Cons_phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4)) + } + else { + return nil + } + } + public static func parse_phoneCallEmpty(_ reader: BufferReader) -> PhoneCall? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.PhoneCall.phoneCallEmpty(Cons_phoneCallEmpty(id: _1!)) + } + else { + return nil + } + } + public static func parse_phoneCallRequested(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallRequested(Cons_phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!)) + } + else { + return nil + } + } + public static func parse_phoneCallWaiting(_ reader: BufferReader) -> PhoneCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Api.PhoneCallProtocol? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _8 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PhoneCall.phoneCallWaiting(Cons_phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8)) } else { return nil @@ -1646,172 +2305,3 @@ public extension Api { } } } -public extension Api { - enum PrivacyKey: TypeConstructorDescription { - case privacyKeyAbout - case privacyKeyAddedByPhone - case privacyKeyBirthday - case privacyKeyChatInvite - case privacyKeyForwards - case privacyKeyNoPaidMessages - case privacyKeyPhoneCall - case privacyKeyPhoneNumber - case privacyKeyPhoneP2P - case privacyKeyProfilePhoto - case privacyKeySavedMusic - case privacyKeyStarGiftsAutoSave - case privacyKeyStatusTimestamp - case privacyKeyVoiceMessages - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .privacyKeyAbout: - if boxed { - buffer.appendInt32(-1534675103) - } - break - case .privacyKeyAddedByPhone: - if boxed { - buffer.appendInt32(1124062251) - } - break - case .privacyKeyBirthday: - if boxed { - buffer.appendInt32(536913176) - } - break - case .privacyKeyChatInvite: - if boxed { - buffer.appendInt32(1343122938) - } - break - case .privacyKeyForwards: - if boxed { - buffer.appendInt32(1777096355) - } - break - case .privacyKeyNoPaidMessages: - if boxed { - buffer.appendInt32(399722706) - } - break - case .privacyKeyPhoneCall: - if boxed { - buffer.appendInt32(1030105979) - } - break - case .privacyKeyPhoneNumber: - if boxed { - buffer.appendInt32(-778378131) - } - break - case .privacyKeyPhoneP2P: - if boxed { - buffer.appendInt32(961092808) - } - break - case .privacyKeyProfilePhoto: - if boxed { - buffer.appendInt32(-1777000467) - } - break - case .privacyKeySavedMusic: - if boxed { - buffer.appendInt32(-8759525) - } - break - case .privacyKeyStarGiftsAutoSave: - if boxed { - buffer.appendInt32(749010424) - } - break - case .privacyKeyStatusTimestamp: - if boxed { - buffer.appendInt32(-1137792208) - } - break - case .privacyKeyVoiceMessages: - if boxed { - buffer.appendInt32(110621716) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .privacyKeyAbout: - return ("privacyKeyAbout", []) - case .privacyKeyAddedByPhone: - return ("privacyKeyAddedByPhone", []) - case .privacyKeyBirthday: - return ("privacyKeyBirthday", []) - case .privacyKeyChatInvite: - return ("privacyKeyChatInvite", []) - case .privacyKeyForwards: - return ("privacyKeyForwards", []) - case .privacyKeyNoPaidMessages: - return ("privacyKeyNoPaidMessages", []) - case .privacyKeyPhoneCall: - return ("privacyKeyPhoneCall", []) - case .privacyKeyPhoneNumber: - return ("privacyKeyPhoneNumber", []) - case .privacyKeyPhoneP2P: - return ("privacyKeyPhoneP2P", []) - case .privacyKeyProfilePhoto: - return ("privacyKeyProfilePhoto", []) - case .privacyKeySavedMusic: - return ("privacyKeySavedMusic", []) - case .privacyKeyStarGiftsAutoSave: - return ("privacyKeyStarGiftsAutoSave", []) - case .privacyKeyStatusTimestamp: - return ("privacyKeyStatusTimestamp", []) - case .privacyKeyVoiceMessages: - return ("privacyKeyVoiceMessages", []) - } - } - - public static func parse_privacyKeyAbout(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyAbout - } - public static func parse_privacyKeyAddedByPhone(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyAddedByPhone - } - public static func parse_privacyKeyBirthday(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyBirthday - } - public static func parse_privacyKeyChatInvite(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyChatInvite - } - public static func parse_privacyKeyForwards(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyForwards - } - public static func parse_privacyKeyNoPaidMessages(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyNoPaidMessages - } - public static func parse_privacyKeyPhoneCall(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneCall - } - public static func parse_privacyKeyPhoneNumber(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneNumber - } - public static func parse_privacyKeyPhoneP2P(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyPhoneP2P - } - public static func parse_privacyKeyProfilePhoto(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyProfilePhoto - } - public static func parse_privacyKeySavedMusic(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeySavedMusic - } - public static func parse_privacyKeyStarGiftsAutoSave(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyStarGiftsAutoSave - } - public static func parse_privacyKeyStatusTimestamp(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyStatusTimestamp - } - public static func parse_privacyKeyVoiceMessages(_ reader: BufferReader) -> PrivacyKey? { - return Api.PrivacyKey.privacyKeyVoiceMessages - } - } -} diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index a14d943b32..a630173cb1 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -1,134 +1,46 @@ public extension Api { - enum PrivacyRule: TypeConstructorDescription { - public class Cons_privacyValueAllowChatParticipants: TypeConstructorDescription { - public var chats: [Int64] - public init(chats: [Int64]) { - self.chats = chats + enum PhoneCallDiscardReason: TypeConstructorDescription { + public class Cons_phoneCallDiscardReasonMigrateConferenceCall: TypeConstructorDescription { + public var slug: String + public init(slug: String) { + self.slug = slug } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("privacyValueAllowChatParticipants", [("chats", ConstructorParameterDescription(self.chats))]) + return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", ConstructorParameterDescription(self.slug))]) } } - public class Cons_privacyValueAllowUsers: TypeConstructorDescription { - public var users: [Int64] - public init(users: [Int64]) { - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("privacyValueAllowUsers", [("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_privacyValueDisallowChatParticipants: TypeConstructorDescription { - public var chats: [Int64] - public init(chats: [Int64]) { - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("privacyValueDisallowChatParticipants", [("chats", ConstructorParameterDescription(self.chats))]) - } - } - public class Cons_privacyValueDisallowUsers: TypeConstructorDescription { - public var users: [Int64] - public init(users: [Int64]) { - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("privacyValueDisallowUsers", [("users", ConstructorParameterDescription(self.users))]) - } - } - case privacyValueAllowAll - case privacyValueAllowBots - case privacyValueAllowChatParticipants(Cons_privacyValueAllowChatParticipants) - case privacyValueAllowCloseFriends - case privacyValueAllowContacts - case privacyValueAllowPremium - case privacyValueAllowUsers(Cons_privacyValueAllowUsers) - case privacyValueDisallowAll - case privacyValueDisallowBots - case privacyValueDisallowChatParticipants(Cons_privacyValueDisallowChatParticipants) - case privacyValueDisallowContacts - case privacyValueDisallowUsers(Cons_privacyValueDisallowUsers) + case phoneCallDiscardReasonBusy + case phoneCallDiscardReasonDisconnect + case phoneCallDiscardReasonHangup + case phoneCallDiscardReasonMigrateConferenceCall(Cons_phoneCallDiscardReasonMigrateConferenceCall) + case phoneCallDiscardReasonMissed public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .privacyValueAllowAll: + case .phoneCallDiscardReasonBusy: if boxed { - buffer.appendInt32(1698855810) + buffer.appendInt32(-84416311) } break - case .privacyValueAllowBots: + case .phoneCallDiscardReasonDisconnect: if boxed { - buffer.appendInt32(558242653) + buffer.appendInt32(-527056480) } break - case .privacyValueAllowChatParticipants(let _data): + case .phoneCallDiscardReasonHangup: if boxed { - buffer.appendInt32(1796427406) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - serializeInt64(item, buffer: buffer, boxed: false) + buffer.appendInt32(1471006352) } break - case .privacyValueAllowCloseFriends: + case .phoneCallDiscardReasonMigrateConferenceCall(let _data): if boxed { - buffer.appendInt32(-135735141) + buffer.appendInt32(-1615072777) } + serializeString(_data.slug, buffer: buffer, boxed: false) break - case .privacyValueAllowContacts: + case .phoneCallDiscardReasonMissed: if boxed { - buffer.appendInt32(-123988) - } - break - case .privacyValueAllowPremium: - if boxed { - buffer.appendInt32(-320241333) - } - break - case .privacyValueAllowUsers(let _data): - if boxed { - buffer.appendInt32(-1198497870) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .privacyValueDisallowAll: - if boxed { - buffer.appendInt32(-1955338397) - } - break - case .privacyValueDisallowBots: - if boxed { - buffer.appendInt32(-156895185) - } - break - case .privacyValueDisallowChatParticipants(let _data): - if boxed { - buffer.appendInt32(1103656293) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .privacyValueDisallowContacts: - if boxed { - buffer.appendInt32(-125240806) - } - break - case .privacyValueDisallowUsers(let _data): - if boxed { - buffer.appendInt32(-463335103) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - serializeInt64(item, buffer: buffer, boxed: false) + buffer.appendInt32(-2048646399) } break } @@ -136,162 +48,76 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .privacyValueAllowAll: - return ("privacyValueAllowAll", []) - case .privacyValueAllowBots: - return ("privacyValueAllowBots", []) - case .privacyValueAllowChatParticipants(let _data): - return ("privacyValueAllowChatParticipants", [("chats", ConstructorParameterDescription(_data.chats))]) - case .privacyValueAllowCloseFriends: - return ("privacyValueAllowCloseFriends", []) - case .privacyValueAllowContacts: - return ("privacyValueAllowContacts", []) - case .privacyValueAllowPremium: - return ("privacyValueAllowPremium", []) - case .privacyValueAllowUsers(let _data): - return ("privacyValueAllowUsers", [("users", ConstructorParameterDescription(_data.users))]) - case .privacyValueDisallowAll: - return ("privacyValueDisallowAll", []) - case .privacyValueDisallowBots: - return ("privacyValueDisallowBots", []) - case .privacyValueDisallowChatParticipants(let _data): - return ("privacyValueDisallowChatParticipants", [("chats", ConstructorParameterDescription(_data.chats))]) - case .privacyValueDisallowContacts: - return ("privacyValueDisallowContacts", []) - case .privacyValueDisallowUsers(let _data): - return ("privacyValueDisallowUsers", [("users", ConstructorParameterDescription(_data.users))]) + case .phoneCallDiscardReasonBusy: + return ("phoneCallDiscardReasonBusy", []) + case .phoneCallDiscardReasonDisconnect: + return ("phoneCallDiscardReasonDisconnect", []) + case .phoneCallDiscardReasonHangup: + return ("phoneCallDiscardReasonHangup", []) + case .phoneCallDiscardReasonMigrateConferenceCall(let _data): + return ("phoneCallDiscardReasonMigrateConferenceCall", [("slug", ConstructorParameterDescription(_data.slug))]) + case .phoneCallDiscardReasonMissed: + return ("phoneCallDiscardReasonMissed", []) } } - public static func parse_privacyValueAllowAll(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowAll + public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy } - public static func parse_privacyValueAllowBots(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowBots + public static func parse_phoneCallDiscardReasonDisconnect(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonDisconnect } - public static func parse_privacyValueAllowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } + public static func parse_phoneCallDiscardReasonHangup(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonHangup + } + public static func parse_phoneCallDiscardReasonMigrateConferenceCall(_ reader: BufferReader) -> PhoneCallDiscardReason? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.PrivacyRule.privacyValueAllowChatParticipants(Cons_privacyValueAllowChatParticipants(chats: _1!)) + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMigrateConferenceCall(Cons_phoneCallDiscardReasonMigrateConferenceCall(slug: _1!)) } else { return nil } } - public static func parse_privacyValueAllowCloseFriends(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowCloseFriends - } - public static func parse_privacyValueAllowContacts(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowContacts - } - public static func parse_privacyValueAllowPremium(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueAllowPremium - } - public static func parse_privacyValueAllowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueAllowUsers(Cons_privacyValueAllowUsers(users: _1!)) - } - else { - return nil - } - } - public static func parse_privacyValueDisallowAll(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueDisallowAll - } - public static func parse_privacyValueDisallowBots(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueDisallowBots - } - public static func parse_privacyValueDisallowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowChatParticipants(Cons_privacyValueDisallowChatParticipants(chats: _1!)) - } - else { - return nil - } - } - public static func parse_privacyValueDisallowContacts(_ reader: BufferReader) -> PrivacyRule? { - return Api.PrivacyRule.privacyValueDisallowContacts - } - public static func parse_privacyValueDisallowUsers(_ reader: BufferReader) -> PrivacyRule? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.PrivacyRule.privacyValueDisallowUsers(Cons_privacyValueDisallowUsers(users: _1!)) - } - else { - return nil - } + public static func parse_phoneCallDiscardReasonMissed(_ reader: BufferReader) -> PhoneCallDiscardReason? { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonMissed } } } public extension Api { - enum ProfileTab: TypeConstructorDescription { - case profileTabFiles - case profileTabGifs - case profileTabGifts - case profileTabLinks - case profileTabMedia - case profileTabMusic - case profileTabPosts - case profileTabVoice + enum PhoneCallProtocol: TypeConstructorDescription { + public class Cons_phoneCallProtocol: TypeConstructorDescription { + public var flags: Int32 + public var minLayer: Int32 + public var maxLayer: Int32 + public var libraryVersions: [String] + public init(flags: Int32, minLayer: Int32, maxLayer: Int32, libraryVersions: [String]) { + self.flags = flags + self.minLayer = minLayer + self.maxLayer = maxLayer + self.libraryVersions = libraryVersions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCallProtocol", [("flags", ConstructorParameterDescription(self.flags)), ("minLayer", ConstructorParameterDescription(self.minLayer)), ("maxLayer", ConstructorParameterDescription(self.maxLayer)), ("libraryVersions", ConstructorParameterDescription(self.libraryVersions))]) + } + } + case phoneCallProtocol(Cons_phoneCallProtocol) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .profileTabFiles: + case .phoneCallProtocol(let _data): if boxed { - buffer.appendInt32(-1422681088) + buffer.appendInt32(-58224696) } - break - case .profileTabGifs: - if boxed { - buffer.appendInt32(-1564412267) - } - break - case .profileTabGifts: - if boxed { - buffer.appendInt32(1296815210) - } - break - case .profileTabLinks: - if boxed { - buffer.appendInt32(-748329831) - } - break - case .profileTabMedia: - if boxed { - buffer.appendInt32(1925597525) - } - break - case .profileTabMusic: - if boxed { - buffer.appendInt32(-1624780178) - } - break - case .profileTabPosts: - if boxed { - buffer.appendInt32(-1181952362) - } - break - case .profileTabVoice: - if boxed { - buffer.appendInt32(-461960914) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.minLayer, buffer: buffer, boxed: false) + serializeInt32(_data.maxLayer, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.libraryVersions.count)) + for item in _data.libraryVersions { + serializeString(item, buffer: buffer, boxed: false) } break } @@ -299,129 +125,28 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .profileTabFiles: - return ("profileTabFiles", []) - case .profileTabGifs: - return ("profileTabGifs", []) - case .profileTabGifts: - return ("profileTabGifts", []) - case .profileTabLinks: - return ("profileTabLinks", []) - case .profileTabMedia: - return ("profileTabMedia", []) - case .profileTabMusic: - return ("profileTabMusic", []) - case .profileTabPosts: - return ("profileTabPosts", []) - case .profileTabVoice: - return ("profileTabVoice", []) + case .phoneCallProtocol(let _data): + return ("phoneCallProtocol", [("flags", ConstructorParameterDescription(_data.flags)), ("minLayer", ConstructorParameterDescription(_data.minLayer)), ("maxLayer", ConstructorParameterDescription(_data.maxLayer)), ("libraryVersions", ConstructorParameterDescription(_data.libraryVersions))]) } } - public static func parse_profileTabFiles(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabFiles - } - public static func parse_profileTabGifs(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabGifs - } - public static func parse_profileTabGifts(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabGifts - } - public static func parse_profileTabLinks(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabLinks - } - public static func parse_profileTabMedia(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabMedia - } - public static func parse_profileTabMusic(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabMusic - } - public static func parse_profileTabPosts(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabPosts - } - public static func parse_profileTabVoice(_ reader: BufferReader) -> ProfileTab? { - return Api.ProfileTab.profileTabVoice - } - } -} -public extension Api { - indirect enum PublicForward: TypeConstructorDescription { - public class Cons_publicForwardMessage: TypeConstructorDescription { - public var message: Api.Message - public init(message: Api.Message) { - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("publicForwardMessage", [("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_publicForwardStory: TypeConstructorDescription { - public var peer: Api.Peer - public var story: Api.StoryItem - public init(peer: Api.Peer, story: Api.StoryItem) { - self.peer = peer - self.story = story - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("publicForwardStory", [("peer", ConstructorParameterDescription(self.peer)), ("story", ConstructorParameterDescription(self.story))]) - } - } - case publicForwardMessage(Cons_publicForwardMessage) - case publicForwardStory(Cons_publicForwardStory) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .publicForwardMessage(let _data): - if boxed { - buffer.appendInt32(32685898) - } - _data.message.serialize(buffer, true) - break - case .publicForwardStory(let _data): - if boxed { - buffer.appendInt32(-302797360) - } - _data.peer.serialize(buffer, true) - _data.story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .publicForwardMessage(let _data): - return ("publicForwardMessage", [("message", ConstructorParameterDescription(_data.message))]) - case .publicForwardStory(let _data): - return ("publicForwardStory", [("peer", ConstructorParameterDescription(_data.peer)), ("story", ConstructorParameterDescription(_data.story))]) - } - } - - public static func parse_publicForwardMessage(_ reader: BufferReader) -> PublicForward? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.PublicForward.publicForwardMessage(Cons_publicForwardMessage(message: _1!)) - } - else { - return nil - } - } - public static func parse_publicForwardStory(_ reader: BufferReader) -> PublicForward? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.StoryItem? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StoryItem + public static func parse_phoneCallProtocol(_ reader: BufferReader) -> PhoneCallProtocol? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [String]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.PublicForward.publicForwardStory(Cons_publicForwardStory(peer: _1!, story: _2!)) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhoneCallProtocol.phoneCallProtocol(Cons_phoneCallProtocol(flags: _1!, minLayer: _2!, maxLayer: _3!, libraryVersions: _4!)) } else { return nil @@ -430,50 +155,472 @@ public extension Api { } } public extension Api { - enum QuickReply: TypeConstructorDescription { - public class Cons_quickReply: TypeConstructorDescription { - public var shortcutId: Int32 - public var shortcut: String - public var topMessage: Int32 - public var count: Int32 - public init(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) { - self.shortcutId = shortcutId - self.shortcut = shortcut - self.topMessage = topMessage - self.count = count + enum PhoneConnection: TypeConstructorDescription { + public class Cons_phoneConnection: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var ip: String + public var ipv6: String + public var port: Int32 + public var peerTag: Buffer + public init(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, peerTag: Buffer) { + self.flags = flags + self.id = id + self.ip = ip + self.ipv6 = ipv6 + self.port = port + self.peerTag = peerTag } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("quickReply", [("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("shortcut", ConstructorParameterDescription(self.shortcut)), ("topMessage", ConstructorParameterDescription(self.topMessage)), ("count", ConstructorParameterDescription(self.count))]) + return ("phoneConnection", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("ip", ConstructorParameterDescription(self.ip)), ("ipv6", ConstructorParameterDescription(self.ipv6)), ("port", ConstructorParameterDescription(self.port)), ("peerTag", ConstructorParameterDescription(self.peerTag))]) } } - case quickReply(Cons_quickReply) + public class Cons_phoneConnectionWebrtc: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var ip: String + public var ipv6: String + public var port: Int32 + public var username: String + public var password: String + public init(flags: Int32, id: Int64, ip: String, ipv6: String, port: Int32, username: String, password: String) { + self.flags = flags + self.id = id + self.ip = ip + self.ipv6 = ipv6 + self.port = port + self.username = username + self.password = password + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneConnectionWebrtc", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("ip", ConstructorParameterDescription(self.ip)), ("ipv6", ConstructorParameterDescription(self.ipv6)), ("port", ConstructorParameterDescription(self.port)), ("username", ConstructorParameterDescription(self.username)), ("password", ConstructorParameterDescription(self.password))]) + } + } + case phoneConnection(Cons_phoneConnection) + case phoneConnectionWebrtc(Cons_phoneConnectionWebrtc) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .quickReply(let _data): + case .phoneConnection(let _data): if boxed { - buffer.appendInt32(110563371) + buffer.appendInt32(-1665063993) } - serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) - serializeString(_data.shortcut, buffer: buffer, boxed: false) - serializeInt32(_data.topMessage, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeString(_data.ip, buffer: buffer, boxed: false) + serializeString(_data.ipv6, buffer: buffer, boxed: false) + serializeInt32(_data.port, buffer: buffer, boxed: false) + serializeBytes(_data.peerTag, buffer: buffer, boxed: false) + break + case .phoneConnectionWebrtc(let _data): + if boxed { + buffer.appendInt32(1667228533) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeString(_data.ip, buffer: buffer, boxed: false) + serializeString(_data.ipv6, buffer: buffer, boxed: false) + serializeInt32(_data.port, buffer: buffer, boxed: false) + serializeString(_data.username, buffer: buffer, boxed: false) + serializeString(_data.password, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .quickReply(let _data): - return ("quickReply", [("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("shortcut", ConstructorParameterDescription(_data.shortcut)), ("topMessage", ConstructorParameterDescription(_data.topMessage)), ("count", ConstructorParameterDescription(_data.count))]) + case .phoneConnection(let _data): + return ("phoneConnection", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("ip", ConstructorParameterDescription(_data.ip)), ("ipv6", ConstructorParameterDescription(_data.ipv6)), ("port", ConstructorParameterDescription(_data.port)), ("peerTag", ConstructorParameterDescription(_data.peerTag))]) + case .phoneConnectionWebrtc(let _data): + return ("phoneConnectionWebrtc", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("ip", ConstructorParameterDescription(_data.ip)), ("ipv6", ConstructorParameterDescription(_data.ipv6)), ("port", ConstructorParameterDescription(_data.port)), ("username", ConstructorParameterDescription(_data.username)), ("password", ConstructorParameterDescription(_data.password))]) } } - public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { + public static func parse_phoneConnection(_ reader: BufferReader) -> PhoneConnection? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PhoneConnection.phoneConnection(Cons_phoneConnection(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, peerTag: _6!)) + } + else { + return nil + } + } + public static func parse_phoneConnectionWebrtc(_ reader: BufferReader) -> PhoneConnection? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + _6 = parseString(reader) + var _7: String? + _7 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PhoneConnection.phoneConnectionWebrtc(Cons_phoneConnectionWebrtc(flags: _1!, id: _2!, ip: _3!, ipv6: _4!, port: _5!, username: _6!, password: _7!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Photo: TypeConstructorDescription { + public class Cons_photo: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var accessHash: Int64 + public var fileReference: Buffer + public var date: Int32 + public var sizes: [Api.PhotoSize] + public var videoSizes: [Api.VideoSize]? + public var dcId: Int32 + public init(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], videoSizes: [Api.VideoSize]?, dcId: Int32) { + self.flags = flags + self.id = id + self.accessHash = accessHash + self.fileReference = fileReference + self.date = date + self.sizes = sizes + self.videoSizes = videoSizes + self.dcId = dcId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photo", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("fileReference", ConstructorParameterDescription(self.fileReference)), ("date", ConstructorParameterDescription(self.date)), ("sizes", ConstructorParameterDescription(self.sizes)), ("videoSizes", ConstructorParameterDescription(self.videoSizes)), ("dcId", ConstructorParameterDescription(self.dcId))]) + } + } + public class Cons_photoEmpty: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoEmpty", [("id", ConstructorParameterDescription(self.id))]) + } + } + case photo(Cons_photo) + case photoEmpty(Cons_photoEmpty) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photo(let _data): + if boxed { + buffer.appendInt32(-82216347) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeBytes(_data.fileReference, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sizes.count)) + for item in _data.sizes { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.videoSizes!.count)) + for item in _data.videoSizes! { + item.serialize(buffer, true) + } + } + serializeInt32(_data.dcId, buffer: buffer, boxed: false) + break + case .photoEmpty(let _data): + if boxed { + buffer.appendInt32(590459437) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .photo(let _data): + return ("photo", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("fileReference", ConstructorParameterDescription(_data.fileReference)), ("date", ConstructorParameterDescription(_data.date)), ("sizes", ConstructorParameterDescription(_data.sizes)), ("videoSizes", ConstructorParameterDescription(_data.videoSizes)), ("dcId", ConstructorParameterDescription(_data.dcId))]) + case .photoEmpty(let _data): + return ("photoEmpty", [("id", ConstructorParameterDescription(_data.id))]) + } + } + + public static func parse_photo(_ reader: BufferReader) -> Photo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.PhotoSize]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + } + var _7: [Api.VideoSize]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self) + } + } + var _8: Int32? + _8 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Photo.photo(Cons_photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!, videoSizes: _7, dcId: _8!)) + } + else { + return nil + } + } + public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Photo.photoEmpty(Cons_photoEmpty(id: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PhotoSize: TypeConstructorDescription { + public class Cons_photoCachedSize: TypeConstructorDescription { + public var type: String + public var w: Int32 + public var h: Int32 + public var bytes: Buffer + public init(type: String, w: Int32, h: Int32, bytes: Buffer) { + self.type = type + self.w = w + self.h = h + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoCachedSize", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + public class Cons_photoPathSize: TypeConstructorDescription { + public var type: String + public var bytes: Buffer + public init(type: String, bytes: Buffer) { + self.type = type + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoPathSize", [("type", ConstructorParameterDescription(self.type)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + public class Cons_photoSize: TypeConstructorDescription { + public var type: String + public var w: Int32 + public var h: Int32 + public var size: Int32 + public init(type: String, w: Int32, h: Int32, size: Int32) { + self.type = type + self.w = w + self.h = h + self.size = size + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoSize", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("size", ConstructorParameterDescription(self.size))]) + } + } + public class Cons_photoSizeEmpty: TypeConstructorDescription { + public var type: String + public init(type: String) { + self.type = type + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoSizeEmpty", [("type", ConstructorParameterDescription(self.type))]) + } + } + public class Cons_photoSizeProgressive: TypeConstructorDescription { + public var type: String + public var w: Int32 + public var h: Int32 + public var sizes: [Int32] + public init(type: String, w: Int32, h: Int32, sizes: [Int32]) { + self.type = type + self.w = w + self.h = h + self.sizes = sizes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoSizeProgressive", [("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("sizes", ConstructorParameterDescription(self.sizes))]) + } + } + public class Cons_photoStrippedSize: TypeConstructorDescription { + public var type: String + public var bytes: Buffer + public init(type: String, bytes: Buffer) { + self.type = type + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photoStrippedSize", [("type", ConstructorParameterDescription(self.type)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + case photoCachedSize(Cons_photoCachedSize) + case photoPathSize(Cons_photoPathSize) + case photoSize(Cons_photoSize) + case photoSizeEmpty(Cons_photoSizeEmpty) + case photoSizeProgressive(Cons_photoSizeProgressive) + case photoStrippedSize(Cons_photoStrippedSize) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let _data): + if boxed { + buffer.appendInt32(35527382) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + case .photoPathSize(let _data): + if boxed { + buffer.appendInt32(-668906175) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + case .photoSize(let _data): + if boxed { + buffer.appendInt32(1976012384) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + serializeInt32(_data.size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let _data): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(_data.type, buffer: buffer, boxed: false) + break + case .photoSizeProgressive(let _data): + if boxed { + buffer.appendInt32(-96535659) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sizes.count)) + for item in _data.sizes { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .photoStrippedSize(let _data): + if boxed { + buffer.appendInt32(-525288402) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .photoCachedSize(let _data): + return ("photoCachedSize", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + case .photoPathSize(let _data): + return ("photoPathSize", [("type", ConstructorParameterDescription(_data.type)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + case .photoSize(let _data): + return ("photoSize", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("size", ConstructorParameterDescription(_data.size))]) + case .photoSizeEmpty(let _data): + return ("photoSizeEmpty", [("type", ConstructorParameterDescription(_data.type))]) + case .photoSizeProgressive(let _data): + return ("photoSizeProgressive", [("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("sizes", ConstructorParameterDescription(_data.sizes))]) + case .photoStrippedSize(let _data): + return ("photoStrippedSize", [("type", ConstructorParameterDescription(_data.type)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + } + } + + public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PhotoSize.photoCachedSize(Cons_photoCachedSize(type: _1!, w: _2!, h: _3!, bytes: _4!)) + } + else { + return nil + } + } + public static func parse_photoPathSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PhotoSize.photoPathSize(Cons_photoPathSize(type: _1!, bytes: _2!)) + } + else { + return nil + } + } + public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() var _4: Int32? @@ -483,170 +630,54 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.QuickReply.quickReply(Cons_quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!)) + return Api.PhotoSize.photoSize(Cons_photoSize(type: _1!, w: _2!, h: _3!, size: _4!)) } else { return nil } } - } -} -public extension Api { - enum Reaction: TypeConstructorDescription { - public class Cons_reactionCustomEmoji: TypeConstructorDescription { - public var documentId: Int64 - public init(documentId: Int64) { - self.documentId = documentId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reactionCustomEmoji", [("documentId", ConstructorParameterDescription(self.documentId))]) - } - } - public class Cons_reactionEmoji: TypeConstructorDescription { - public var emoticon: String - public init(emoticon: String) { - self.emoticon = emoticon - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reactionEmoji", [("emoticon", ConstructorParameterDescription(self.emoticon))]) - } - } - case reactionCustomEmoji(Cons_reactionCustomEmoji) - case reactionEmoji(Cons_reactionEmoji) - case reactionEmpty - case reactionPaid - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reactionCustomEmoji(let _data): - if boxed { - buffer.appendInt32(-1992950669) - } - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - break - case .reactionEmoji(let _data): - if boxed { - buffer.appendInt32(455247544) - } - serializeString(_data.emoticon, buffer: buffer, boxed: false) - break - case .reactionEmpty: - if boxed { - buffer.appendInt32(2046153753) - } - break - case .reactionPaid: - if boxed { - buffer.appendInt32(1379771627) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .reactionCustomEmoji(let _data): - return ("reactionCustomEmoji", [("documentId", ConstructorParameterDescription(_data.documentId))]) - case .reactionEmoji(let _data): - return ("reactionEmoji", [("emoticon", ConstructorParameterDescription(_data.emoticon))]) - case .reactionEmpty: - return ("reactionEmpty", []) - case .reactionPaid: - return ("reactionPaid", []) - } - } - - public static func parse_reactionCustomEmoji(_ reader: BufferReader) -> Reaction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Reaction.reactionCustomEmoji(Cons_reactionCustomEmoji(documentId: _1!)) - } - else { - return nil - } - } - public static func parse_reactionEmoji(_ reader: BufferReader) -> Reaction? { + public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.Reaction.reactionEmoji(Cons_reactionEmoji(emoticon: _1!)) + return Api.PhotoSize.photoSizeEmpty(Cons_photoSizeEmpty(type: _1!)) } else { return nil } } - public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? { - return Api.Reaction.reactionEmpty - } - public static func parse_reactionPaid(_ reader: BufferReader) -> Reaction? { - return Api.Reaction.reactionPaid - } - } -} -public extension Api { - enum ReactionCount: TypeConstructorDescription { - public class Cons_reactionCount: TypeConstructorDescription { - public var flags: Int32 - public var chosenOrder: Int32? - public var reaction: Api.Reaction - public var count: Int32 - public init(flags: Int32, chosenOrder: Int32?, reaction: Api.Reaction, count: Int32) { - self.flags = flags - self.chosenOrder = chosenOrder - self.reaction = reaction - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reactionCount", [("flags", ConstructorParameterDescription(self.flags)), ("chosenOrder", ConstructorParameterDescription(self.chosenOrder)), ("reaction", ConstructorParameterDescription(self.reaction)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case reactionCount(Cons_reactionCount) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reactionCount(let _data): - if boxed { - buffer.appendInt32(-1546531968) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.chosenOrder!, buffer: buffer, boxed: false) - } - _data.reaction.serialize(buffer, true) - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .reactionCount(let _data): - return ("reactionCount", [("flags", ConstructorParameterDescription(_data.flags)), ("chosenOrder", ConstructorParameterDescription(_data.chosenOrder)), ("reaction", ConstructorParameterDescription(_data.reaction)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_photoSizeProgressive(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } - var _4: Int32? - _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.ReactionCount.reactionCount(Cons_reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!)) + return Api.PhotoSize.photoSizeProgressive(Cons_photoSizeProgressive(type: _1!, w: _2!, h: _3!, sizes: _4!)) + } + else { + return nil + } + } + public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PhotoSize.photoStrippedSize(Cons_photoStrippedSize(type: _1!, bytes: _2!)) } else { return nil @@ -655,131 +686,110 @@ public extension Api { } } public extension Api { - enum ReactionNotificationsFrom: TypeConstructorDescription { - case reactionNotificationsFromAll - case reactionNotificationsFromContacts - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reactionNotificationsFromAll: - if boxed { - buffer.appendInt32(1268654752) - } - break - case .reactionNotificationsFromContacts: - if boxed { - buffer.appendInt32(-1161583078) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .reactionNotificationsFromAll: - return ("reactionNotificationsFromAll", []) - case .reactionNotificationsFromContacts: - return ("reactionNotificationsFromContacts", []) - } - } - - public static func parse_reactionNotificationsFromAll(_ reader: BufferReader) -> ReactionNotificationsFrom? { - return Api.ReactionNotificationsFrom.reactionNotificationsFromAll - } - public static func parse_reactionNotificationsFromContacts(_ reader: BufferReader) -> ReactionNotificationsFrom? { - return Api.ReactionNotificationsFrom.reactionNotificationsFromContacts - } - } -} -public extension Api { - enum ReactionsNotifySettings: TypeConstructorDescription { - public class Cons_reactionsNotifySettings: TypeConstructorDescription { + enum Poll: TypeConstructorDescription { + public class Cons_poll: TypeConstructorDescription { + public var id: Int64 public var flags: Int32 - public var messagesNotifyFrom: Api.ReactionNotificationsFrom? - public var storiesNotifyFrom: Api.ReactionNotificationsFrom? - public var pollVotesNotifyFrom: Api.ReactionNotificationsFrom? - public var sound: Api.NotificationSound - public var showPreviews: Api.Bool - public init(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, pollVotesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) { + public var question: Api.TextWithEntities + public var answers: [Api.PollAnswer] + public var closePeriod: Int32? + public var closeDate: Int32? + public var countriesIso2: [String]? + public var hash: Int64 + public init(id: Int64, flags: Int32, question: Api.TextWithEntities, answers: [Api.PollAnswer], closePeriod: Int32?, closeDate: Int32?, countriesIso2: [String]?, hash: Int64) { + self.id = id self.flags = flags - self.messagesNotifyFrom = messagesNotifyFrom - self.storiesNotifyFrom = storiesNotifyFrom - self.pollVotesNotifyFrom = pollVotesNotifyFrom - self.sound = sound - self.showPreviews = showPreviews + self.question = question + self.answers = answers + self.closePeriod = closePeriod + self.closeDate = closeDate + self.countriesIso2 = countriesIso2 + self.hash = hash } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reactionsNotifySettings", [("flags", ConstructorParameterDescription(self.flags)), ("messagesNotifyFrom", ConstructorParameterDescription(self.messagesNotifyFrom)), ("storiesNotifyFrom", ConstructorParameterDescription(self.storiesNotifyFrom)), ("pollVotesNotifyFrom", ConstructorParameterDescription(self.pollVotesNotifyFrom)), ("sound", ConstructorParameterDescription(self.sound)), ("showPreviews", ConstructorParameterDescription(self.showPreviews))]) + return ("poll", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("question", ConstructorParameterDescription(self.question)), ("answers", ConstructorParameterDescription(self.answers)), ("closePeriod", ConstructorParameterDescription(self.closePeriod)), ("closeDate", ConstructorParameterDescription(self.closeDate)), ("countriesIso2", ConstructorParameterDescription(self.countriesIso2)), ("hash", ConstructorParameterDescription(self.hash))]) } } - case reactionsNotifySettings(Cons_reactionsNotifySettings) + case poll(Cons_poll) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .reactionsNotifySettings(let _data): + case .poll(let _data): if boxed { - buffer.appendInt32(1910827608) + buffer.appendInt32(-1771164225) } + serializeInt64(_data.id, buffer: buffer, boxed: false) serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.messagesNotifyFrom!.serialize(buffer, true) + _data.question.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.answers.count)) + for item in _data.answers { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.storiesNotifyFrom!.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.closePeriod!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.pollVotesNotifyFrom!.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.closeDate!, buffer: buffer, boxed: false) } - _data.sound.serialize(buffer, true) - _data.showPreviews.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 12) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.countriesIso2!.count)) + for item in _data.countriesIso2! { + serializeString(item, buffer: buffer, boxed: false) + } + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .reactionsNotifySettings(let _data): - return ("reactionsNotifySettings", [("flags", ConstructorParameterDescription(_data.flags)), ("messagesNotifyFrom", ConstructorParameterDescription(_data.messagesNotifyFrom)), ("storiesNotifyFrom", ConstructorParameterDescription(_data.storiesNotifyFrom)), ("pollVotesNotifyFrom", ConstructorParameterDescription(_data.pollVotesNotifyFrom)), ("sound", ConstructorParameterDescription(_data.sound)), ("showPreviews", ConstructorParameterDescription(_data.showPreviews))]) + case .poll(let _data): + return ("poll", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("question", ConstructorParameterDescription(_data.question)), ("answers", ConstructorParameterDescription(_data.answers)), ("closePeriod", ConstructorParameterDescription(_data.closePeriod)), ("closeDate", ConstructorParameterDescription(_data.closeDate)), ("countriesIso2", ConstructorParameterDescription(_data.countriesIso2)), ("hash", ConstructorParameterDescription(_data.hash))]) } } - public static func parse_reactionsNotifySettings(_ reader: BufferReader) -> ReactionsNotifySettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.ReactionNotificationsFrom? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom - } - } - var _3: Api.ReactionNotificationsFrom? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom - } - } - var _4: Api.ReactionNotificationsFrom? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom - } - } - var _5: Api.NotificationSound? + public static func parse_poll(_ reader: BufferReader) -> Poll? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.TextWithEntities? if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } - var _6: Api.Bool? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Bool + var _4: [Api.PollAnswer]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswer.self) } + var _5: Int32? + if Int(_2 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_2 ?? 0) & Int(1 << 5) != 0 { + _6 = reader.readInt32() + } + var _7: [String]? + if Int(_2 ?? 0) & Int(1 << 12) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _8: Int64? + _8 = reader.readInt64() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ReactionsNotifySettings.reactionsNotifySettings(Cons_reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, pollVotesNotifyFrom: _4, sound: _5!, showPreviews: _6!)) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_2 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_2 ?? 0) & Int(1 << 5) == 0) || _6 != nil + let _c7 = (Int(_2 ?? 0) & Int(1 << 12) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Poll.poll(Cons_poll(id: _1!, flags: _2!, question: _3!, answers: _4!, closePeriod: _5, closeDate: _6, countriesIso2: _7, hash: _8!)) } else { return nil @@ -788,40 +798,381 @@ public extension Api { } } public extension Api { - enum ReadParticipantDate: TypeConstructorDescription { - public class Cons_readParticipantDate: TypeConstructorDescription { - public var userId: Int64 - public var date: Int32 - public init(userId: Int64, date: Int32) { - self.userId = userId + indirect enum PollAnswer: TypeConstructorDescription { + public class Cons_inputPollAnswer: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.TextWithEntities + public var media: Api.InputMedia? + public init(flags: Int32, text: Api.TextWithEntities, media: Api.InputMedia?) { + self.flags = flags + self.text = text + self.media = media + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputPollAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("media", ConstructorParameterDescription(self.media))]) + } + } + public class Cons_pollAnswer: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.TextWithEntities + public var option: Buffer + public var media: Api.MessageMedia? + public var addedBy: Api.Peer? + public var date: Int32? + public init(flags: Int32, text: Api.TextWithEntities, option: Buffer, media: Api.MessageMedia?, addedBy: Api.Peer?, date: Int32?) { + self.flags = flags + self.text = text + self.option = option + self.media = media + self.addedBy = addedBy self.date = date } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("readParticipantDate", [("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date))]) + return ("pollAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option)), ("media", ConstructorParameterDescription(self.media)), ("addedBy", ConstructorParameterDescription(self.addedBy)), ("date", ConstructorParameterDescription(self.date))]) } } - case readParticipantDate(Cons_readParticipantDate) + case inputPollAnswer(Cons_inputPollAnswer) + case pollAnswer(Cons_pollAnswer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .readParticipantDate(let _data): + case .inputPollAnswer(let _data): if boxed { - buffer.appendInt32(1246753138) + buffer.appendInt32(429911446) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.media!.serialize(buffer, true) + } + break + case .pollAnswer(let _data): + if boxed { + buffer.appendInt32(1266514026) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + serializeBytes(_data.option, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.media!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.addedBy!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.date!, buffer: buffer, boxed: false) } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .readParticipantDate(let _data): - return ("readParticipantDate", [("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date))]) + case .inputPollAnswer(let _data): + return ("inputPollAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("media", ConstructorParameterDescription(_data.media))]) + case .pollAnswer(let _data): + return ("pollAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option)), ("media", ConstructorParameterDescription(_data.media)), ("addedBy", ConstructorParameterDescription(_data.addedBy)), ("date", ConstructorParameterDescription(_data.date))]) } } - public static func parse_readParticipantDate(_ reader: BufferReader) -> ReadParticipantDate? { + public static func parse_inputPollAnswer(_ reader: BufferReader) -> PollAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: Api.InputMedia? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputMedia + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.PollAnswer.inputPollAnswer(Cons_inputPollAnswer(flags: _1!, text: _2!, media: _3)) + } + else { + return nil + } + } + public static func parse_pollAnswer(_ reader: BufferReader) -> PollAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + var _5: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PollAnswer.pollAnswer(Cons_pollAnswer(flags: _1!, text: _2!, option: _3!, media: _4, addedBy: _5, date: _6)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PollAnswerVoters: TypeConstructorDescription { + public class Cons_pollAnswerVoters: TypeConstructorDescription { + public var flags: Int32 + public var option: Buffer + public var voters: Int32? + public var recentVoters: [Api.Peer]? + public init(flags: Int32, option: Buffer, voters: Int32?, recentVoters: [Api.Peer]?) { + self.flags = flags + self.option = option + self.voters = voters + self.recentVoters = recentVoters + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pollAnswerVoters", [("flags", ConstructorParameterDescription(self.flags)), ("option", ConstructorParameterDescription(self.option)), ("voters", ConstructorParameterDescription(self.voters)), ("recentVoters", ConstructorParameterDescription(self.recentVoters))]) + } + } + case pollAnswerVoters(Cons_pollAnswerVoters) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pollAnswerVoters(let _data): + if boxed { + buffer.appendInt32(910500618) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeBytes(_data.option, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.voters!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentVoters!.count)) + for item in _data.recentVoters! { + item.serialize(buffer, true) + } + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pollAnswerVoters(let _data): + return ("pollAnswerVoters", [("flags", ConstructorParameterDescription(_data.flags)), ("option", ConstructorParameterDescription(_data.option)), ("voters", ConstructorParameterDescription(_data.voters)), ("recentVoters", ConstructorParameterDescription(_data.recentVoters))]) + } + } + + public static func parse_pollAnswerVoters(_ reader: BufferReader) -> PollAnswerVoters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.Peer]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PollAnswerVoters.pollAnswerVoters(Cons_pollAnswerVoters(flags: _1!, option: _2!, voters: _3, recentVoters: _4)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum PollResults: TypeConstructorDescription { + public class Cons_pollResults: TypeConstructorDescription { + public var flags: Int32 + public var results: [Api.PollAnswerVoters]? + public var totalVoters: Int32? + public var recentVoters: [Api.Peer]? + public var solution: String? + public var solutionEntities: [Api.MessageEntity]? + public var solutionMedia: Api.MessageMedia? + public init(flags: Int32, results: [Api.PollAnswerVoters]?, totalVoters: Int32?, recentVoters: [Api.Peer]?, solution: String?, solutionEntities: [Api.MessageEntity]?, solutionMedia: Api.MessageMedia?) { + self.flags = flags + self.results = results + self.totalVoters = totalVoters + self.recentVoters = recentVoters + self.solution = solution + self.solutionEntities = solutionEntities + self.solutionMedia = solutionMedia + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pollResults", [("flags", ConstructorParameterDescription(self.flags)), ("results", ConstructorParameterDescription(self.results)), ("totalVoters", ConstructorParameterDescription(self.totalVoters)), ("recentVoters", ConstructorParameterDescription(self.recentVoters)), ("solution", ConstructorParameterDescription(self.solution)), ("solutionEntities", ConstructorParameterDescription(self.solutionEntities)), ("solutionMedia", ConstructorParameterDescription(self.solutionMedia))]) + } + } + case pollResults(Cons_pollResults) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pollResults(let _data): + if boxed { + buffer.appendInt32(-1166298786) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.results!.count)) + for item in _data.results! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.totalVoters!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentVoters!.count)) + for item in _data.recentVoters! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.solution!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.solutionEntities!.count)) + for item in _data.solutionEntities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.solutionMedia!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pollResults(let _data): + return ("pollResults", [("flags", ConstructorParameterDescription(_data.flags)), ("results", ConstructorParameterDescription(_data.results)), ("totalVoters", ConstructorParameterDescription(_data.totalVoters)), ("recentVoters", ConstructorParameterDescription(_data.recentVoters)), ("solution", ConstructorParameterDescription(_data.solution)), ("solutionEntities", ConstructorParameterDescription(_data.solutionEntities)), ("solutionMedia", ConstructorParameterDescription(_data.solutionMedia))]) + } + } + + public static func parse_pollResults(_ reader: BufferReader) -> PollResults? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PollAnswerVoters]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PollAnswerVoters.self) + } + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.Peer]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = parseString(reader) + } + var _6: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _7: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PollResults.pollResults(Cons_pollResults(flags: _1!, results: _2, totalVoters: _3, recentVoters: _4, solution: _5, solutionEntities: _6, solutionMedia: _7)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PopularContact: TypeConstructorDescription { + public class Cons_popularContact: TypeConstructorDescription { + public var clientId: Int64 + public var importers: Int32 + public init(clientId: Int64, importers: Int32) { + self.clientId = clientId + self.importers = importers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("popularContact", [("clientId", ConstructorParameterDescription(self.clientId)), ("importers", ConstructorParameterDescription(self.importers))]) + } + } + case popularContact(Cons_popularContact) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .popularContact(let _data): + if boxed { + buffer.appendInt32(1558266229) + } + serializeInt64(_data.clientId, buffer: buffer, boxed: false) + serializeInt32(_data.importers, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .popularContact(let _data): + return ("popularContact", [("clientId", ConstructorParameterDescription(_data.clientId)), ("importers", ConstructorParameterDescription(_data.importers))]) + } + } + + public static func parse_popularContact(_ reader: BufferReader) -> PopularContact? { var _1: Int64? _1 = reader.readInt64() var _2: Int32? @@ -829,7 +1180,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.ReadParticipantDate.readParticipantDate(Cons_readParticipantDate(userId: _1!, date: _2!)) + return Api.PopularContact.popularContact(Cons_popularContact(clientId: _1!, importers: _2!)) } else { return nil @@ -837,3 +1188,646 @@ public extension Api { } } } +public extension Api { + enum PostAddress: TypeConstructorDescription { + public class Cons_postAddress: TypeConstructorDescription { + public var streetLine1: String + public var streetLine2: String + public var city: String + public var state: String + public var countryIso2: String + public var postCode: String + public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) { + self.streetLine1 = streetLine1 + self.streetLine2 = streetLine2 + self.city = city + self.state = state + self.countryIso2 = countryIso2 + self.postCode = postCode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("postAddress", [("streetLine1", ConstructorParameterDescription(self.streetLine1)), ("streetLine2", ConstructorParameterDescription(self.streetLine2)), ("city", ConstructorParameterDescription(self.city)), ("state", ConstructorParameterDescription(self.state)), ("countryIso2", ConstructorParameterDescription(self.countryIso2)), ("postCode", ConstructorParameterDescription(self.postCode))]) + } + } + case postAddress(Cons_postAddress) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .postAddress(let _data): + if boxed { + buffer.appendInt32(512535275) + } + serializeString(_data.streetLine1, buffer: buffer, boxed: false) + serializeString(_data.streetLine2, buffer: buffer, boxed: false) + serializeString(_data.city, buffer: buffer, boxed: false) + serializeString(_data.state, buffer: buffer, boxed: false) + serializeString(_data.countryIso2, buffer: buffer, boxed: false) + serializeString(_data.postCode, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .postAddress(let _data): + return ("postAddress", [("streetLine1", ConstructorParameterDescription(_data.streetLine1)), ("streetLine2", ConstructorParameterDescription(_data.streetLine2)), ("city", ConstructorParameterDescription(_data.city)), ("state", ConstructorParameterDescription(_data.state)), ("countryIso2", ConstructorParameterDescription(_data.countryIso2)), ("postCode", ConstructorParameterDescription(_data.postCode))]) + } + } + + public static func parse_postAddress(_ reader: BufferReader) -> PostAddress? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PostAddress.postAddress(Cons_postAddress(streetLine1: _1!, streetLine2: _2!, city: _3!, state: _4!, countryIso2: _5!, postCode: _6!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PostInteractionCounters: TypeConstructorDescription { + public class Cons_postInteractionCountersMessage: TypeConstructorDescription { + public var msgId: Int32 + public var views: Int32 + public var forwards: Int32 + public var reactions: Int32 + public init(msgId: Int32, views: Int32, forwards: Int32, reactions: Int32) { + self.msgId = msgId + self.views = views + self.forwards = forwards + self.reactions = reactions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("postInteractionCountersMessage", [("msgId", ConstructorParameterDescription(self.msgId)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("reactions", ConstructorParameterDescription(self.reactions))]) + } + } + public class Cons_postInteractionCountersStory: TypeConstructorDescription { + public var storyId: Int32 + public var views: Int32 + public var forwards: Int32 + public var reactions: Int32 + public init(storyId: Int32, views: Int32, forwards: Int32, reactions: Int32) { + self.storyId = storyId + self.views = views + self.forwards = forwards + self.reactions = reactions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("postInteractionCountersStory", [("storyId", ConstructorParameterDescription(self.storyId)), ("views", ConstructorParameterDescription(self.views)), ("forwards", ConstructorParameterDescription(self.forwards)), ("reactions", ConstructorParameterDescription(self.reactions))]) + } + } + case postInteractionCountersMessage(Cons_postInteractionCountersMessage) + case postInteractionCountersStory(Cons_postInteractionCountersStory) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .postInteractionCountersMessage(let _data): + if boxed { + buffer.appendInt32(-419066241) + } + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt32(_data.views, buffer: buffer, boxed: false) + serializeInt32(_data.forwards, buffer: buffer, boxed: false) + serializeInt32(_data.reactions, buffer: buffer, boxed: false) + break + case .postInteractionCountersStory(let _data): + if boxed { + buffer.appendInt32(-1974989273) + } + serializeInt32(_data.storyId, buffer: buffer, boxed: false) + serializeInt32(_data.views, buffer: buffer, boxed: false) + serializeInt32(_data.forwards, buffer: buffer, boxed: false) + serializeInt32(_data.reactions, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .postInteractionCountersMessage(let _data): + return ("postInteractionCountersMessage", [("msgId", ConstructorParameterDescription(_data.msgId)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("reactions", ConstructorParameterDescription(_data.reactions))]) + case .postInteractionCountersStory(let _data): + return ("postInteractionCountersStory", [("storyId", ConstructorParameterDescription(_data.storyId)), ("views", ConstructorParameterDescription(_data.views)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("reactions", ConstructorParameterDescription(_data.reactions))]) + } + } + + public static func parse_postInteractionCountersMessage(_ reader: BufferReader) -> PostInteractionCounters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PostInteractionCounters.postInteractionCountersMessage(Cons_postInteractionCountersMessage(msgId: _1!, views: _2!, forwards: _3!, reactions: _4!)) + } + else { + return nil + } + } + public static func parse_postInteractionCountersStory(_ reader: BufferReader) -> PostInteractionCounters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PostInteractionCounters.postInteractionCountersStory(Cons_postInteractionCountersStory(storyId: _1!, views: _2!, forwards: _3!, reactions: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PremiumGiftCodeOption: TypeConstructorDescription { + public class Cons_premiumGiftCodeOption: TypeConstructorDescription { + public var flags: Int32 + public var users: Int32 + public var months: Int32 + public var storeProduct: String? + public var storeQuantity: Int32? + public var currency: String + public var amount: Int64 + public init(flags: Int32, users: Int32, months: Int32, storeProduct: String?, storeQuantity: Int32?, currency: String, amount: Int64) { + self.flags = flags + self.users = users + self.months = months + self.storeProduct = storeProduct + self.storeQuantity = storeQuantity + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("premiumGiftCodeOption", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("months", ConstructorParameterDescription(self.months)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("storeQuantity", ConstructorParameterDescription(self.storeQuantity)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + case premiumGiftCodeOption(Cons_premiumGiftCodeOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumGiftCodeOption(let _data): + if boxed { + buffer.appendInt32(629052971) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.users, buffer: buffer, boxed: false) + serializeInt32(_data.months, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.storeQuantity!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .premiumGiftCodeOption(let _data): + return ("premiumGiftCodeOption", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("months", ConstructorParameterDescription(_data.months)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("storeQuantity", ConstructorParameterDescription(_data.storeQuantity)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + } + } + + public static func parse_premiumGiftCodeOption(_ reader: BufferReader) -> PremiumGiftCodeOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + var _6: String? + _6 = parseString(reader) + var _7: Int64? + _7 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PremiumGiftCodeOption.premiumGiftCodeOption(Cons_premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PremiumSubscriptionOption: TypeConstructorDescription { + public class Cons_premiumSubscriptionOption: TypeConstructorDescription { + public var flags: Int32 + public var transaction: String? + public var months: Int32 + public var currency: String + public var amount: Int64 + public var botUrl: String + public var storeProduct: String? + public init(flags: Int32, transaction: String?, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) { + self.flags = flags + self.transaction = transaction + self.months = months + self.currency = currency + self.amount = amount + self.botUrl = botUrl + self.storeProduct = storeProduct + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("premiumSubscriptionOption", [("flags", ConstructorParameterDescription(self.flags)), ("transaction", ConstructorParameterDescription(self.transaction)), ("months", ConstructorParameterDescription(self.months)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("botUrl", ConstructorParameterDescription(self.botUrl)), ("storeProduct", ConstructorParameterDescription(self.storeProduct))]) + } + } + case premiumSubscriptionOption(Cons_premiumSubscriptionOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumSubscriptionOption(let _data): + if boxed { + buffer.appendInt32(1596792306) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.transaction!, buffer: buffer, boxed: false) + } + serializeInt32(_data.months, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeString(_data.botUrl, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .premiumSubscriptionOption(let _data): + return ("premiumSubscriptionOption", [("flags", ConstructorParameterDescription(_data.flags)), ("transaction", ConstructorParameterDescription(_data.transaction)), ("months", ConstructorParameterDescription(_data.months)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("botUrl", ConstructorParameterDescription(_data.botUrl)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct))]) + } + } + + public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _2 = parseString(reader) + } + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: String? + _6 = parseString(reader) + var _7: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.PremiumSubscriptionOption.premiumSubscriptionOption(Cons_premiumSubscriptionOption(flags: _1!, transaction: _2, months: _3!, currency: _4!, amount: _5!, botUrl: _6!, storeProduct: _7)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PrepaidGiveaway: TypeConstructorDescription { + public class Cons_prepaidGiveaway: TypeConstructorDescription { + public var id: Int64 + public var months: Int32 + public var quantity: Int32 + public var date: Int32 + public init(id: Int64, months: Int32, quantity: Int32, date: Int32) { + self.id = id + self.months = months + self.quantity = quantity + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("prepaidGiveaway", [("id", ConstructorParameterDescription(self.id)), ("months", ConstructorParameterDescription(self.months)), ("quantity", ConstructorParameterDescription(self.quantity)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_prepaidStarsGiveaway: TypeConstructorDescription { + public var id: Int64 + public var stars: Int64 + public var quantity: Int32 + public var boosts: Int32 + public var date: Int32 + public init(id: Int64, stars: Int64, quantity: Int32, boosts: Int32, date: Int32) { + self.id = id + self.stars = stars + self.quantity = quantity + self.boosts = boosts + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("prepaidStarsGiveaway", [("id", ConstructorParameterDescription(self.id)), ("stars", ConstructorParameterDescription(self.stars)), ("quantity", ConstructorParameterDescription(self.quantity)), ("boosts", ConstructorParameterDescription(self.boosts)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case prepaidGiveaway(Cons_prepaidGiveaway) + case prepaidStarsGiveaway(Cons_prepaidStarsGiveaway) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .prepaidGiveaway(let _data): + if boxed { + buffer.appendInt32(-1303143084) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.months, buffer: buffer, boxed: false) + serializeInt32(_data.quantity, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .prepaidStarsGiveaway(let _data): + if boxed { + buffer.appendInt32(-1700956192) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + serializeInt32(_data.quantity, buffer: buffer, boxed: false) + serializeInt32(_data.boosts, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .prepaidGiveaway(let _data): + return ("prepaidGiveaway", [("id", ConstructorParameterDescription(_data.id)), ("months", ConstructorParameterDescription(_data.months)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("date", ConstructorParameterDescription(_data.date))]) + case .prepaidStarsGiveaway(let _data): + return ("prepaidStarsGiveaway", [("id", ConstructorParameterDescription(_data.id)), ("stars", ConstructorParameterDescription(_data.stars)), ("quantity", ConstructorParameterDescription(_data.quantity)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_prepaidGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.PrepaidGiveaway.prepaidGiveaway(Cons_prepaidGiveaway(id: _1!, months: _2!, quantity: _3!, date: _4!)) + } + else { + return nil + } + } + public static func parse_prepaidStarsGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PrepaidGiveaway.prepaidStarsGiveaway(Cons_prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, boosts: _4!, date: _5!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum PrivacyKey: TypeConstructorDescription { + case privacyKeyAbout + case privacyKeyAddedByPhone + case privacyKeyBirthday + case privacyKeyChatInvite + case privacyKeyForwards + case privacyKeyNoPaidMessages + case privacyKeyPhoneCall + case privacyKeyPhoneNumber + case privacyKeyPhoneP2P + case privacyKeyProfilePhoto + case privacyKeySavedMusic + case privacyKeyStarGiftsAutoSave + case privacyKeyStatusTimestamp + case privacyKeyVoiceMessages + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .privacyKeyAbout: + if boxed { + buffer.appendInt32(-1534675103) + } + break + case .privacyKeyAddedByPhone: + if boxed { + buffer.appendInt32(1124062251) + } + break + case .privacyKeyBirthday: + if boxed { + buffer.appendInt32(536913176) + } + break + case .privacyKeyChatInvite: + if boxed { + buffer.appendInt32(1343122938) + } + break + case .privacyKeyForwards: + if boxed { + buffer.appendInt32(1777096355) + } + break + case .privacyKeyNoPaidMessages: + if boxed { + buffer.appendInt32(399722706) + } + break + case .privacyKeyPhoneCall: + if boxed { + buffer.appendInt32(1030105979) + } + break + case .privacyKeyPhoneNumber: + if boxed { + buffer.appendInt32(-778378131) + } + break + case .privacyKeyPhoneP2P: + if boxed { + buffer.appendInt32(961092808) + } + break + case .privacyKeyProfilePhoto: + if boxed { + buffer.appendInt32(-1777000467) + } + break + case .privacyKeySavedMusic: + if boxed { + buffer.appendInt32(-8759525) + } + break + case .privacyKeyStarGiftsAutoSave: + if boxed { + buffer.appendInt32(749010424) + } + break + case .privacyKeyStatusTimestamp: + if boxed { + buffer.appendInt32(-1137792208) + } + break + case .privacyKeyVoiceMessages: + if boxed { + buffer.appendInt32(110621716) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .privacyKeyAbout: + return ("privacyKeyAbout", []) + case .privacyKeyAddedByPhone: + return ("privacyKeyAddedByPhone", []) + case .privacyKeyBirthday: + return ("privacyKeyBirthday", []) + case .privacyKeyChatInvite: + return ("privacyKeyChatInvite", []) + case .privacyKeyForwards: + return ("privacyKeyForwards", []) + case .privacyKeyNoPaidMessages: + return ("privacyKeyNoPaidMessages", []) + case .privacyKeyPhoneCall: + return ("privacyKeyPhoneCall", []) + case .privacyKeyPhoneNumber: + return ("privacyKeyPhoneNumber", []) + case .privacyKeyPhoneP2P: + return ("privacyKeyPhoneP2P", []) + case .privacyKeyProfilePhoto: + return ("privacyKeyProfilePhoto", []) + case .privacyKeySavedMusic: + return ("privacyKeySavedMusic", []) + case .privacyKeyStarGiftsAutoSave: + return ("privacyKeyStarGiftsAutoSave", []) + case .privacyKeyStatusTimestamp: + return ("privacyKeyStatusTimestamp", []) + case .privacyKeyVoiceMessages: + return ("privacyKeyVoiceMessages", []) + } + } + + public static func parse_privacyKeyAbout(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyAbout + } + public static func parse_privacyKeyAddedByPhone(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyAddedByPhone + } + public static func parse_privacyKeyBirthday(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyBirthday + } + public static func parse_privacyKeyChatInvite(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyChatInvite + } + public static func parse_privacyKeyForwards(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyForwards + } + public static func parse_privacyKeyNoPaidMessages(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyNoPaidMessages + } + public static func parse_privacyKeyPhoneCall(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneCall + } + public static func parse_privacyKeyPhoneNumber(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneNumber + } + public static func parse_privacyKeyPhoneP2P(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyPhoneP2P + } + public static func parse_privacyKeyProfilePhoto(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyProfilePhoto + } + public static func parse_privacyKeySavedMusic(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeySavedMusic + } + public static func parse_privacyKeyStarGiftsAutoSave(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyStarGiftsAutoSave + } + public static func parse_privacyKeyStatusTimestamp(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyStatusTimestamp + } + public static func parse_privacyKeyVoiceMessages(_ reader: BufferReader) -> PrivacyKey? { + return Api.PrivacyKey.privacyKeyVoiceMessages + } + } +} diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index 200a882362..102550071d 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -1,46 +1,835 @@ public extension Api { - enum ReceivedNotifyMessage: TypeConstructorDescription { - public class Cons_receivedNotifyMessage: TypeConstructorDescription { - public var id: Int32 - public var flags: Int32 - public init(id: Int32, flags: Int32) { - self.id = id - self.flags = flags + enum PrivacyRule: TypeConstructorDescription { + public class Cons_privacyValueAllowChatParticipants: TypeConstructorDescription { + public var chats: [Int64] + public init(chats: [Int64]) { + self.chats = chats } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("receivedNotifyMessage", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags))]) + return ("privacyValueAllowChatParticipants", [("chats", ConstructorParameterDescription(self.chats))]) } } - case receivedNotifyMessage(Cons_receivedNotifyMessage) + public class Cons_privacyValueAllowUsers: TypeConstructorDescription { + public var users: [Int64] + public init(users: [Int64]) { + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("privacyValueAllowUsers", [("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_privacyValueDisallowChatParticipants: TypeConstructorDescription { + public var chats: [Int64] + public init(chats: [Int64]) { + self.chats = chats + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("privacyValueDisallowChatParticipants", [("chats", ConstructorParameterDescription(self.chats))]) + } + } + public class Cons_privacyValueDisallowUsers: TypeConstructorDescription { + public var users: [Int64] + public init(users: [Int64]) { + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("privacyValueDisallowUsers", [("users", ConstructorParameterDescription(self.users))]) + } + } + case privacyValueAllowAll + case privacyValueAllowBots + case privacyValueAllowChatParticipants(Cons_privacyValueAllowChatParticipants) + case privacyValueAllowCloseFriends + case privacyValueAllowContacts + case privacyValueAllowPremium + case privacyValueAllowUsers(Cons_privacyValueAllowUsers) + case privacyValueDisallowAll + case privacyValueDisallowBots + case privacyValueDisallowChatParticipants(Cons_privacyValueDisallowChatParticipants) + case privacyValueDisallowContacts + case privacyValueDisallowUsers(Cons_privacyValueDisallowUsers) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .receivedNotifyMessage(let _data): + case .privacyValueAllowAll: if boxed { - buffer.appendInt32(-1551583367) + buffer.appendInt32(1698855810) + } + break + case .privacyValueAllowBots: + if boxed { + buffer.appendInt32(558242653) + } + break + case .privacyValueAllowChatParticipants(let _data): + if boxed { + buffer.appendInt32(1796427406) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .privacyValueAllowCloseFriends: + if boxed { + buffer.appendInt32(-135735141) + } + break + case .privacyValueAllowContacts: + if boxed { + buffer.appendInt32(-123988) + } + break + case .privacyValueAllowPremium: + if boxed { + buffer.appendInt32(-320241333) + } + break + case .privacyValueAllowUsers(let _data): + if boxed { + buffer.appendInt32(-1198497870) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .privacyValueDisallowAll: + if boxed { + buffer.appendInt32(-1955338397) + } + break + case .privacyValueDisallowBots: + if boxed { + buffer.appendInt32(-156895185) + } + break + case .privacyValueDisallowChatParticipants(let _data): + if boxed { + buffer.appendInt32(1103656293) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .privacyValueDisallowContacts: + if boxed { + buffer.appendInt32(-125240806) + } + break + case .privacyValueDisallowUsers(let _data): + if boxed { + buffer.appendInt32(-463335103) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + serializeInt64(item, buffer: buffer, boxed: false) } - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.flags, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .receivedNotifyMessage(let _data): - return ("receivedNotifyMessage", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags))]) + case .privacyValueAllowAll: + return ("privacyValueAllowAll", []) + case .privacyValueAllowBots: + return ("privacyValueAllowBots", []) + case .privacyValueAllowChatParticipants(let _data): + return ("privacyValueAllowChatParticipants", [("chats", ConstructorParameterDescription(_data.chats))]) + case .privacyValueAllowCloseFriends: + return ("privacyValueAllowCloseFriends", []) + case .privacyValueAllowContacts: + return ("privacyValueAllowContacts", []) + case .privacyValueAllowPremium: + return ("privacyValueAllowPremium", []) + case .privacyValueAllowUsers(let _data): + return ("privacyValueAllowUsers", [("users", ConstructorParameterDescription(_data.users))]) + case .privacyValueDisallowAll: + return ("privacyValueDisallowAll", []) + case .privacyValueDisallowBots: + return ("privacyValueDisallowBots", []) + case .privacyValueDisallowChatParticipants(let _data): + return ("privacyValueDisallowChatParticipants", [("chats", ConstructorParameterDescription(_data.chats))]) + case .privacyValueDisallowContacts: + return ("privacyValueDisallowContacts", []) + case .privacyValueDisallowUsers(let _data): + return ("privacyValueDisallowUsers", [("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? { + public static func parse_privacyValueAllowAll(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowAll + } + public static func parse_privacyValueAllowBots(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowBots + } + public static func parse_privacyValueAllowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PrivacyRule.privacyValueAllowChatParticipants(Cons_privacyValueAllowChatParticipants(chats: _1!)) + } + else { + return nil + } + } + public static func parse_privacyValueAllowCloseFriends(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowCloseFriends + } + public static func parse_privacyValueAllowContacts(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowContacts + } + public static func parse_privacyValueAllowPremium(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueAllowPremium + } + public static func parse_privacyValueAllowUsers(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PrivacyRule.privacyValueAllowUsers(Cons_privacyValueAllowUsers(users: _1!)) + } + else { + return nil + } + } + public static func parse_privacyValueDisallowAll(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueDisallowAll + } + public static func parse_privacyValueDisallowBots(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueDisallowBots + } + public static func parse_privacyValueDisallowChatParticipants(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PrivacyRule.privacyValueDisallowChatParticipants(Cons_privacyValueDisallowChatParticipants(chats: _1!)) + } + else { + return nil + } + } + public static func parse_privacyValueDisallowContacts(_ reader: BufferReader) -> PrivacyRule? { + return Api.PrivacyRule.privacyValueDisallowContacts + } + public static func parse_privacyValueDisallowUsers(_ reader: BufferReader) -> PrivacyRule? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.PrivacyRule.privacyValueDisallowUsers(Cons_privacyValueDisallowUsers(users: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum ProfileTab: TypeConstructorDescription { + case profileTabFiles + case profileTabGifs + case profileTabGifts + case profileTabLinks + case profileTabMedia + case profileTabMusic + case profileTabPosts + case profileTabVoice + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .profileTabFiles: + if boxed { + buffer.appendInt32(-1422681088) + } + break + case .profileTabGifs: + if boxed { + buffer.appendInt32(-1564412267) + } + break + case .profileTabGifts: + if boxed { + buffer.appendInt32(1296815210) + } + break + case .profileTabLinks: + if boxed { + buffer.appendInt32(-748329831) + } + break + case .profileTabMedia: + if boxed { + buffer.appendInt32(1925597525) + } + break + case .profileTabMusic: + if boxed { + buffer.appendInt32(-1624780178) + } + break + case .profileTabPosts: + if boxed { + buffer.appendInt32(-1181952362) + } + break + case .profileTabVoice: + if boxed { + buffer.appendInt32(-461960914) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .profileTabFiles: + return ("profileTabFiles", []) + case .profileTabGifs: + return ("profileTabGifs", []) + case .profileTabGifts: + return ("profileTabGifts", []) + case .profileTabLinks: + return ("profileTabLinks", []) + case .profileTabMedia: + return ("profileTabMedia", []) + case .profileTabMusic: + return ("profileTabMusic", []) + case .profileTabPosts: + return ("profileTabPosts", []) + case .profileTabVoice: + return ("profileTabVoice", []) + } + } + + public static func parse_profileTabFiles(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabFiles + } + public static func parse_profileTabGifs(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabGifs + } + public static func parse_profileTabGifts(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabGifts + } + public static func parse_profileTabLinks(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabLinks + } + public static func parse_profileTabMedia(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabMedia + } + public static func parse_profileTabMusic(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabMusic + } + public static func parse_profileTabPosts(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabPosts + } + public static func parse_profileTabVoice(_ reader: BufferReader) -> ProfileTab? { + return Api.ProfileTab.profileTabVoice + } + } +} +public extension Api { + indirect enum PublicForward: TypeConstructorDescription { + public class Cons_publicForwardMessage: TypeConstructorDescription { + public var message: Api.Message + public init(message: Api.Message) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("publicForwardMessage", [("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_publicForwardStory: TypeConstructorDescription { + public var peer: Api.Peer + public var story: Api.StoryItem + public init(peer: Api.Peer, story: Api.StoryItem) { + self.peer = peer + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("publicForwardStory", [("peer", ConstructorParameterDescription(self.peer)), ("story", ConstructorParameterDescription(self.story))]) + } + } + case publicForwardMessage(Cons_publicForwardMessage) + case publicForwardStory(Cons_publicForwardStory) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .publicForwardMessage(let _data): + if boxed { + buffer.appendInt32(32685898) + } + _data.message.serialize(buffer, true) + break + case .publicForwardStory(let _data): + if boxed { + buffer.appendInt32(-302797360) + } + _data.peer.serialize(buffer, true) + _data.story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .publicForwardMessage(let _data): + return ("publicForwardMessage", [("message", ConstructorParameterDescription(_data.message))]) + case .publicForwardStory(let _data): + return ("publicForwardStory", [("peer", ConstructorParameterDescription(_data.peer)), ("story", ConstructorParameterDescription(_data.story))]) + } + } + + public static func parse_publicForwardMessage(_ reader: BufferReader) -> PublicForward? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.PublicForward.publicForwardMessage(Cons_publicForwardMessage(message: _1!)) + } + else { + return nil + } + } + public static func parse_publicForwardStory(_ reader: BufferReader) -> PublicForward? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.StoryItem? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.PublicForward.publicForwardStory(Cons_publicForwardStory(peer: _1!, story: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum QuickReply: TypeConstructorDescription { + public class Cons_quickReply: TypeConstructorDescription { + public var shortcutId: Int32 + public var shortcut: String + public var topMessage: Int32 + public var count: Int32 + public init(shortcutId: Int32, shortcut: String, topMessage: Int32, count: Int32) { + self.shortcutId = shortcutId + self.shortcut = shortcut + self.topMessage = topMessage + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("quickReply", [("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("shortcut", ConstructorParameterDescription(self.shortcut)), ("topMessage", ConstructorParameterDescription(self.topMessage)), ("count", ConstructorParameterDescription(self.count))]) + } + } + case quickReply(Cons_quickReply) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .quickReply(let _data): + if boxed { + buffer.appendInt32(110563371) + } + serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) + serializeString(_data.shortcut, buffer: buffer, boxed: false) + serializeInt32(_data.topMessage, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .quickReply(let _data): + return ("quickReply", [("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("shortcut", ConstructorParameterDescription(_data.shortcut)), ("topMessage", ConstructorParameterDescription(_data.topMessage)), ("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_quickReply(_ reader: BufferReader) -> QuickReply? { var _1: Int32? _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.QuickReply.quickReply(Cons_quickReply(shortcutId: _1!, shortcut: _2!, topMessage: _3!, count: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Reaction: TypeConstructorDescription { + public class Cons_reactionCustomEmoji: TypeConstructorDescription { + public var documentId: Int64 + public init(documentId: Int64) { + self.documentId = documentId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reactionCustomEmoji", [("documentId", ConstructorParameterDescription(self.documentId))]) + } + } + public class Cons_reactionEmoji: TypeConstructorDescription { + public var emoticon: String + public init(emoticon: String) { + self.emoticon = emoticon + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reactionEmoji", [("emoticon", ConstructorParameterDescription(self.emoticon))]) + } + } + case reactionCustomEmoji(Cons_reactionCustomEmoji) + case reactionEmoji(Cons_reactionEmoji) + case reactionEmpty + case reactionPaid + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactionCustomEmoji(let _data): + if boxed { + buffer.appendInt32(-1992950669) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + break + case .reactionEmoji(let _data): + if boxed { + buffer.appendInt32(455247544) + } + serializeString(_data.emoticon, buffer: buffer, boxed: false) + break + case .reactionEmpty: + if boxed { + buffer.appendInt32(2046153753) + } + break + case .reactionPaid: + if boxed { + buffer.appendInt32(1379771627) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reactionCustomEmoji(let _data): + return ("reactionCustomEmoji", [("documentId", ConstructorParameterDescription(_data.documentId))]) + case .reactionEmoji(let _data): + return ("reactionEmoji", [("emoticon", ConstructorParameterDescription(_data.emoticon))]) + case .reactionEmpty: + return ("reactionEmpty", []) + case .reactionPaid: + return ("reactionPaid", []) + } + } + + public static func parse_reactionCustomEmoji(_ reader: BufferReader) -> Reaction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Reaction.reactionCustomEmoji(Cons_reactionCustomEmoji(documentId: _1!)) + } + else { + return nil + } + } + public static func parse_reactionEmoji(_ reader: BufferReader) -> Reaction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.Reaction.reactionEmoji(Cons_reactionEmoji(emoticon: _1!)) + } + else { + return nil + } + } + public static func parse_reactionEmpty(_ reader: BufferReader) -> Reaction? { + return Api.Reaction.reactionEmpty + } + public static func parse_reactionPaid(_ reader: BufferReader) -> Reaction? { + return Api.Reaction.reactionPaid + } + } +} +public extension Api { + enum ReactionCount: TypeConstructorDescription { + public class Cons_reactionCount: TypeConstructorDescription { + public var flags: Int32 + public var chosenOrder: Int32? + public var reaction: Api.Reaction + public var count: Int32 + public init(flags: Int32, chosenOrder: Int32?, reaction: Api.Reaction, count: Int32) { + self.flags = flags + self.chosenOrder = chosenOrder + self.reaction = reaction + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reactionCount", [("flags", ConstructorParameterDescription(self.flags)), ("chosenOrder", ConstructorParameterDescription(self.chosenOrder)), ("reaction", ConstructorParameterDescription(self.reaction)), ("count", ConstructorParameterDescription(self.count))]) + } + } + case reactionCount(Cons_reactionCount) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactionCount(let _data): + if boxed { + buffer.appendInt32(-1546531968) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.chosenOrder!, buffer: buffer, boxed: false) + } + _data.reaction.serialize(buffer, true) + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reactionCount(let _data): + return ("reactionCount", [("flags", ConstructorParameterDescription(_data.flags)), ("chosenOrder", ConstructorParameterDescription(_data.chosenOrder)), ("reaction", ConstructorParameterDescription(_data.reaction)), ("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_reactionCount(_ reader: BufferReader) -> ReactionCount? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.ReactionCount.reactionCount(Cons_reactionCount(flags: _1!, chosenOrder: _2, reaction: _3!, count: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum ReactionNotificationsFrom: TypeConstructorDescription { + case reactionNotificationsFromAll + case reactionNotificationsFromContacts + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactionNotificationsFromAll: + if boxed { + buffer.appendInt32(1268654752) + } + break + case .reactionNotificationsFromContacts: + if boxed { + buffer.appendInt32(-1161583078) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reactionNotificationsFromAll: + return ("reactionNotificationsFromAll", []) + case .reactionNotificationsFromContacts: + return ("reactionNotificationsFromContacts", []) + } + } + + public static func parse_reactionNotificationsFromAll(_ reader: BufferReader) -> ReactionNotificationsFrom? { + return Api.ReactionNotificationsFrom.reactionNotificationsFromAll + } + public static func parse_reactionNotificationsFromContacts(_ reader: BufferReader) -> ReactionNotificationsFrom? { + return Api.ReactionNotificationsFrom.reactionNotificationsFromContacts + } + } +} +public extension Api { + enum ReactionsNotifySettings: TypeConstructorDescription { + public class Cons_reactionsNotifySettings: TypeConstructorDescription { + public var flags: Int32 + public var messagesNotifyFrom: Api.ReactionNotificationsFrom? + public var storiesNotifyFrom: Api.ReactionNotificationsFrom? + public var pollVotesNotifyFrom: Api.ReactionNotificationsFrom? + public var sound: Api.NotificationSound + public var showPreviews: Api.Bool + public init(flags: Int32, messagesNotifyFrom: Api.ReactionNotificationsFrom?, storiesNotifyFrom: Api.ReactionNotificationsFrom?, pollVotesNotifyFrom: Api.ReactionNotificationsFrom?, sound: Api.NotificationSound, showPreviews: Api.Bool) { + self.flags = flags + self.messagesNotifyFrom = messagesNotifyFrom + self.storiesNotifyFrom = storiesNotifyFrom + self.pollVotesNotifyFrom = pollVotesNotifyFrom + self.sound = sound + self.showPreviews = showPreviews + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reactionsNotifySettings", [("flags", ConstructorParameterDescription(self.flags)), ("messagesNotifyFrom", ConstructorParameterDescription(self.messagesNotifyFrom)), ("storiesNotifyFrom", ConstructorParameterDescription(self.storiesNotifyFrom)), ("pollVotesNotifyFrom", ConstructorParameterDescription(self.pollVotesNotifyFrom)), ("sound", ConstructorParameterDescription(self.sound)), ("showPreviews", ConstructorParameterDescription(self.showPreviews))]) + } + } + case reactionsNotifySettings(Cons_reactionsNotifySettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactionsNotifySettings(let _data): + if boxed { + buffer.appendInt32(1910827608) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.messagesNotifyFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.storiesNotifyFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.pollVotesNotifyFrom!.serialize(buffer, true) + } + _data.sound.serialize(buffer, true) + _data.showPreviews.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reactionsNotifySettings(let _data): + return ("reactionsNotifySettings", [("flags", ConstructorParameterDescription(_data.flags)), ("messagesNotifyFrom", ConstructorParameterDescription(_data.messagesNotifyFrom)), ("storiesNotifyFrom", ConstructorParameterDescription(_data.storiesNotifyFrom)), ("pollVotesNotifyFrom", ConstructorParameterDescription(_data.pollVotesNotifyFrom)), ("sound", ConstructorParameterDescription(_data.sound)), ("showPreviews", ConstructorParameterDescription(_data.showPreviews))]) + } + } + + public static func parse_reactionsNotifySettings(_ reader: BufferReader) -> ReactionsNotifySettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.ReactionNotificationsFrom? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + } + } + var _3: Api.ReactionNotificationsFrom? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + } + } + var _4: Api.ReactionNotificationsFrom? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.ReactionNotificationsFrom + } + } + var _5: Api.NotificationSound? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.NotificationSound + } + var _6: Api.Bool? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.ReactionsNotifySettings.reactionsNotifySettings(Cons_reactionsNotifySettings(flags: _1!, messagesNotifyFrom: _2, storiesNotifyFrom: _3, pollVotesNotifyFrom: _4, sound: _5!, showPreviews: _6!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum ReadParticipantDate: TypeConstructorDescription { + public class Cons_readParticipantDate: TypeConstructorDescription { + public var userId: Int64 + public var date: Int32 + public init(userId: Int64, date: Int32) { + self.userId = userId + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("readParticipantDate", [("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case readParticipantDate(Cons_readParticipantDate) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .readParticipantDate(let _data): + if boxed { + buffer.appendInt32(1246753138) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .readParticipantDate(let _data): + return ("readParticipantDate", [("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_readParticipantDate(_ reader: BufferReader) -> ReadParticipantDate? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.ReceivedNotifyMessage.receivedNotifyMessage(Cons_receivedNotifyMessage(id: _1!, flags: _2!)) + return Api.ReadParticipantDate.readParticipantDate(Cons_readParticipantDate(userId: _1!, date: _2!)) } else { return nil @@ -48,1182 +837,3 @@ public extension Api { } } } -public extension Api { - indirect enum RecentMeUrl: TypeConstructorDescription { - public class Cons_recentMeUrlChat: TypeConstructorDescription { - public var url: String - public var chatId: Int64 - public init(url: String, chatId: Int64) { - self.url = url - self.chatId = chatId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrlChat", [("url", ConstructorParameterDescription(self.url)), ("chatId", ConstructorParameterDescription(self.chatId))]) - } - } - public class Cons_recentMeUrlChatInvite: TypeConstructorDescription { - public var url: String - public var chatInvite: Api.ChatInvite - public init(url: String, chatInvite: Api.ChatInvite) { - self.url = url - self.chatInvite = chatInvite - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrlChatInvite", [("url", ConstructorParameterDescription(self.url)), ("chatInvite", ConstructorParameterDescription(self.chatInvite))]) - } - } - public class Cons_recentMeUrlStickerSet: TypeConstructorDescription { - public var url: String - public var set: Api.StickerSetCovered - public init(url: String, set: Api.StickerSetCovered) { - self.url = url - self.set = set - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrlStickerSet", [("url", ConstructorParameterDescription(self.url)), ("set", ConstructorParameterDescription(self.set))]) - } - } - public class Cons_recentMeUrlUnknown: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrlUnknown", [("url", ConstructorParameterDescription(self.url))]) - } - } - public class Cons_recentMeUrlUser: TypeConstructorDescription { - public var url: String - public var userId: Int64 - public init(url: String, userId: Int64) { - self.url = url - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrlUser", [("url", ConstructorParameterDescription(self.url)), ("userId", ConstructorParameterDescription(self.userId))]) - } - } - case recentMeUrlChat(Cons_recentMeUrlChat) - case recentMeUrlChatInvite(Cons_recentMeUrlChatInvite) - case recentMeUrlStickerSet(Cons_recentMeUrlStickerSet) - case recentMeUrlUnknown(Cons_recentMeUrlUnknown) - case recentMeUrlUser(Cons_recentMeUrlUser) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .recentMeUrlChat(let _data): - if boxed { - buffer.appendInt32(-1294306862) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - break - case .recentMeUrlChatInvite(let _data): - if boxed { - buffer.appendInt32(-347535331) - } - serializeString(_data.url, buffer: buffer, boxed: false) - _data.chatInvite.serialize(buffer, true) - break - case .recentMeUrlStickerSet(let _data): - if boxed { - buffer.appendInt32(-1140172836) - } - serializeString(_data.url, buffer: buffer, boxed: false) - _data.set.serialize(buffer, true) - break - case .recentMeUrlUnknown(let _data): - if boxed { - buffer.appendInt32(1189204285) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - case .recentMeUrlUser(let _data): - if boxed { - buffer.appendInt32(-1188296222) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .recentMeUrlChat(let _data): - return ("recentMeUrlChat", [("url", ConstructorParameterDescription(_data.url)), ("chatId", ConstructorParameterDescription(_data.chatId))]) - case .recentMeUrlChatInvite(let _data): - return ("recentMeUrlChatInvite", [("url", ConstructorParameterDescription(_data.url)), ("chatInvite", ConstructorParameterDescription(_data.chatInvite))]) - case .recentMeUrlStickerSet(let _data): - return ("recentMeUrlStickerSet", [("url", ConstructorParameterDescription(_data.url)), ("set", ConstructorParameterDescription(_data.set))]) - case .recentMeUrlUnknown(let _data): - return ("recentMeUrlUnknown", [("url", ConstructorParameterDescription(_data.url))]) - case .recentMeUrlUser(let _data): - return ("recentMeUrlUser", [("url", ConstructorParameterDescription(_data.url)), ("userId", ConstructorParameterDescription(_data.userId))]) - } - } - - public static func parse_recentMeUrlChat(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChat(Cons_recentMeUrlChat(url: _1!, chatId: _2!)) - } - else { - return nil - } - } - public static func parse_recentMeUrlChatInvite(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Api.ChatInvite? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ChatInvite - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlChatInvite(Cons_recentMeUrlChatInvite(url: _1!, chatInvite: _2!)) - } - else { - return nil - } - } - public static func parse_recentMeUrlStickerSet(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Api.StickerSetCovered? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StickerSetCovered - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlStickerSet(Cons_recentMeUrlStickerSet(url: _1!, set: _2!)) - } - else { - return nil - } - } - public static func parse_recentMeUrlUnknown(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.RecentMeUrl.recentMeUrlUnknown(Cons_recentMeUrlUnknown(url: _1!)) - } - else { - return nil - } - } - public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { - var _1: String? - _1 = parseString(reader) - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RecentMeUrl.recentMeUrlUser(Cons_recentMeUrlUser(url: _1!, userId: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum RecentStory: TypeConstructorDescription { - public class Cons_recentStory: TypeConstructorDescription { - public var flags: Int32 - public var maxId: Int32? - public init(flags: Int32, maxId: Int32?) { - self.flags = flags - self.maxId = maxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentStory", [("flags", ConstructorParameterDescription(self.flags)), ("maxId", ConstructorParameterDescription(self.maxId))]) - } - } - case recentStory(Cons_recentStory) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .recentStory(let _data): - if boxed { - buffer.appendInt32(1897752877) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.maxId!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .recentStory(let _data): - return ("recentStory", [("flags", ConstructorParameterDescription(_data.flags)), ("maxId", ConstructorParameterDescription(_data.maxId))]) - } - } - - public static func parse_recentStory(_ reader: BufferReader) -> RecentStory? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - if _c1 && _c2 { - return Api.RecentStory.recentStory(Cons_recentStory(flags: _1!, maxId: _2)) - } - else { - return nil - } - } - } -} -public extension Api { - enum ReplyMarkup: TypeConstructorDescription { - public class Cons_replyInlineMarkup: TypeConstructorDescription { - public var rows: [Api.KeyboardButtonRow] - public init(rows: [Api.KeyboardButtonRow]) { - self.rows = rows - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("replyInlineMarkup", [("rows", ConstructorParameterDescription(self.rows))]) - } - } - public class Cons_replyKeyboardForceReply: TypeConstructorDescription { - public var flags: Int32 - public var placeholder: String? - public init(flags: Int32, placeholder: String?) { - self.flags = flags - self.placeholder = placeholder - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("replyKeyboardForceReply", [("flags", ConstructorParameterDescription(self.flags)), ("placeholder", ConstructorParameterDescription(self.placeholder))]) - } - } - public class Cons_replyKeyboardHide: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("replyKeyboardHide", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_replyKeyboardMarkup: TypeConstructorDescription { - public var flags: Int32 - public var rows: [Api.KeyboardButtonRow] - public var placeholder: String? - public init(flags: Int32, rows: [Api.KeyboardButtonRow], placeholder: String?) { - self.flags = flags - self.rows = rows - self.placeholder = placeholder - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("replyKeyboardMarkup", [("flags", ConstructorParameterDescription(self.flags)), ("rows", ConstructorParameterDescription(self.rows)), ("placeholder", ConstructorParameterDescription(self.placeholder))]) - } - } - case replyInlineMarkup(Cons_replyInlineMarkup) - case replyKeyboardForceReply(Cons_replyKeyboardForceReply) - case replyKeyboardHide(Cons_replyKeyboardHide) - case replyKeyboardMarkup(Cons_replyKeyboardMarkup) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .replyInlineMarkup(let _data): - if boxed { - buffer.appendInt32(1218642516) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rows.count)) - for item in _data.rows { - item.serialize(buffer, true) - } - break - case .replyKeyboardForceReply(let _data): - if boxed { - buffer.appendInt32(-2035021048) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.placeholder!, buffer: buffer, boxed: false) - } - break - case .replyKeyboardHide(let _data): - if boxed { - buffer.appendInt32(-1606526075) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .replyKeyboardMarkup(let _data): - if boxed { - buffer.appendInt32(-2049074735) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rows.count)) - for item in _data.rows { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.placeholder!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .replyInlineMarkup(let _data): - return ("replyInlineMarkup", [("rows", ConstructorParameterDescription(_data.rows))]) - case .replyKeyboardForceReply(let _data): - return ("replyKeyboardForceReply", [("flags", ConstructorParameterDescription(_data.flags)), ("placeholder", ConstructorParameterDescription(_data.placeholder))]) - case .replyKeyboardHide(let _data): - return ("replyKeyboardHide", [("flags", ConstructorParameterDescription(_data.flags))]) - case .replyKeyboardMarkup(let _data): - return ("replyKeyboardMarkup", [("flags", ConstructorParameterDescription(_data.flags)), ("rows", ConstructorParameterDescription(_data.rows)), ("placeholder", ConstructorParameterDescription(_data.placeholder))]) - } - } - - public static func parse_replyInlineMarkup(_ reader: BufferReader) -> ReplyMarkup? { - var _1: [Api.KeyboardButtonRow]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyInlineMarkup(Cons_replyInlineMarkup(rows: _1!)) - } - else { - return nil - } - } - public static func parse_replyKeyboardForceReply(_ reader: BufferReader) -> ReplyMarkup? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 3) != 0 { - _2 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - if _c1 && _c2 { - return Api.ReplyMarkup.replyKeyboardForceReply(Cons_replyKeyboardForceReply(flags: _1!, placeholder: _2)) - } - else { - return nil - } - } - public static func parse_replyKeyboardHide(_ reader: BufferReader) -> ReplyMarkup? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.ReplyMarkup.replyKeyboardHide(Cons_replyKeyboardHide(flags: _1!)) - } - else { - return nil - } - } - public static func parse_replyKeyboardMarkup(_ reader: BufferReader) -> ReplyMarkup? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.KeyboardButtonRow]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) - } - var _3: String? - if Int(_1!) & Int(1 << 3) != 0 { - _3 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.ReplyMarkup.replyKeyboardMarkup(Cons_replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum ReportReason: TypeConstructorDescription { - case inputReportReasonChildAbuse - case inputReportReasonCopyright - case inputReportReasonFake - case inputReportReasonGeoIrrelevant - case inputReportReasonIllegalDrugs - case inputReportReasonOther - case inputReportReasonPersonalDetails - case inputReportReasonPornography - case inputReportReasonSpam - case inputReportReasonViolence - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputReportReasonChildAbuse: - if boxed { - buffer.appendInt32(-1376497949) - } - break - case .inputReportReasonCopyright: - if boxed { - buffer.appendInt32(-1685456582) - } - break - case .inputReportReasonFake: - if boxed { - buffer.appendInt32(-170010905) - } - break - case .inputReportReasonGeoIrrelevant: - if boxed { - buffer.appendInt32(-606798099) - } - break - case .inputReportReasonIllegalDrugs: - if boxed { - buffer.appendInt32(177124030) - } - break - case .inputReportReasonOther: - if boxed { - buffer.appendInt32(-1041980751) - } - break - case .inputReportReasonPersonalDetails: - if boxed { - buffer.appendInt32(-1631091139) - } - break - case .inputReportReasonPornography: - if boxed { - buffer.appendInt32(777640226) - } - break - case .inputReportReasonSpam: - if boxed { - buffer.appendInt32(1490799288) - } - break - case .inputReportReasonViolence: - if boxed { - buffer.appendInt32(505595789) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputReportReasonChildAbuse: - return ("inputReportReasonChildAbuse", []) - case .inputReportReasonCopyright: - return ("inputReportReasonCopyright", []) - case .inputReportReasonFake: - return ("inputReportReasonFake", []) - case .inputReportReasonGeoIrrelevant: - return ("inputReportReasonGeoIrrelevant", []) - case .inputReportReasonIllegalDrugs: - return ("inputReportReasonIllegalDrugs", []) - case .inputReportReasonOther: - return ("inputReportReasonOther", []) - case .inputReportReasonPersonalDetails: - return ("inputReportReasonPersonalDetails", []) - case .inputReportReasonPornography: - return ("inputReportReasonPornography", []) - case .inputReportReasonSpam: - return ("inputReportReasonSpam", []) - case .inputReportReasonViolence: - return ("inputReportReasonViolence", []) - } - } - - public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonChildAbuse - } - public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonCopyright - } - public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonFake - } - public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonGeoIrrelevant - } - public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonIllegalDrugs - } - public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonOther - } - public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonPersonalDetails - } - public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonPornography - } - public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonSpam - } - public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? { - return Api.ReportReason.inputReportReasonViolence - } - } -} -public extension Api { - enum ReportResult: TypeConstructorDescription { - public class Cons_reportResultAddComment: TypeConstructorDescription { - public var flags: Int32 - public var option: Buffer - public init(flags: Int32, option: Buffer) { - self.flags = flags - self.option = option - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reportResultAddComment", [("flags", ConstructorParameterDescription(self.flags)), ("option", ConstructorParameterDescription(self.option))]) - } - } - public class Cons_reportResultChooseOption: TypeConstructorDescription { - public var title: String - public var options: [Api.MessageReportOption] - public init(title: String, options: [Api.MessageReportOption]) { - self.title = title - self.options = options - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reportResultChooseOption", [("title", ConstructorParameterDescription(self.title)), ("options", ConstructorParameterDescription(self.options))]) - } - } - case reportResultAddComment(Cons_reportResultAddComment) - case reportResultChooseOption(Cons_reportResultChooseOption) - case reportResultReported - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reportResultAddComment(let _data): - if boxed { - buffer.appendInt32(1862904881) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeBytes(_data.option, buffer: buffer, boxed: false) - break - case .reportResultChooseOption(let _data): - if boxed { - buffer.appendInt32(-253435722) - } - serializeString(_data.title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.options.count)) - for item in _data.options { - item.serialize(buffer, true) - } - break - case .reportResultReported: - if boxed { - buffer.appendInt32(-1917633461) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .reportResultAddComment(let _data): - return ("reportResultAddComment", [("flags", ConstructorParameterDescription(_data.flags)), ("option", ConstructorParameterDescription(_data.option))]) - case .reportResultChooseOption(let _data): - return ("reportResultChooseOption", [("title", ConstructorParameterDescription(_data.title)), ("options", ConstructorParameterDescription(_data.options))]) - case .reportResultReported: - return ("reportResultReported", []) - } - } - - public static func parse_reportResultAddComment(_ reader: BufferReader) -> ReportResult? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReportResult.reportResultAddComment(Cons_reportResultAddComment(flags: _1!, option: _2!)) - } - else { - return nil - } - } - public static func parse_reportResultChooseOption(_ reader: BufferReader) -> ReportResult? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageReportOption]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageReportOption.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ReportResult.reportResultChooseOption(Cons_reportResultChooseOption(title: _1!, options: _2!)) - } - else { - return nil - } - } - public static func parse_reportResultReported(_ reader: BufferReader) -> ReportResult? { - return Api.ReportResult.reportResultReported - } - } -} -public extension Api { - enum RequestPeerType: TypeConstructorDescription { - public class Cons_requestPeerTypeBroadcast: TypeConstructorDescription { - public var flags: Int32 - public var hasUsername: Api.Bool? - public var userAdminRights: Api.ChatAdminRights? - public var botAdminRights: Api.ChatAdminRights? - public init(flags: Int32, hasUsername: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) { - self.flags = flags - self.hasUsername = hasUsername - self.userAdminRights = userAdminRights - self.botAdminRights = botAdminRights - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestPeerTypeBroadcast", [("flags", ConstructorParameterDescription(self.flags)), ("hasUsername", ConstructorParameterDescription(self.hasUsername)), ("userAdminRights", ConstructorParameterDescription(self.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(self.botAdminRights))]) - } - } - public class Cons_requestPeerTypeChat: TypeConstructorDescription { - public var flags: Int32 - public var hasUsername: Api.Bool? - public var forum: Api.Bool? - public var userAdminRights: Api.ChatAdminRights? - public var botAdminRights: Api.ChatAdminRights? - public init(flags: Int32, hasUsername: Api.Bool?, forum: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) { - self.flags = flags - self.hasUsername = hasUsername - self.forum = forum - self.userAdminRights = userAdminRights - self.botAdminRights = botAdminRights - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestPeerTypeChat", [("flags", ConstructorParameterDescription(self.flags)), ("hasUsername", ConstructorParameterDescription(self.hasUsername)), ("forum", ConstructorParameterDescription(self.forum)), ("userAdminRights", ConstructorParameterDescription(self.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(self.botAdminRights))]) - } - } - public class Cons_requestPeerTypeCreateBot: TypeConstructorDescription { - public var flags: Int32 - public var suggestedName: String? - public var suggestedUsername: String? - public init(flags: Int32, suggestedName: String?, suggestedUsername: String?) { - self.flags = flags - self.suggestedName = suggestedName - self.suggestedUsername = suggestedUsername - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestPeerTypeCreateBot", [("flags", ConstructorParameterDescription(self.flags)), ("suggestedName", ConstructorParameterDescription(self.suggestedName)), ("suggestedUsername", ConstructorParameterDescription(self.suggestedUsername))]) - } - } - public class Cons_requestPeerTypeUser: TypeConstructorDescription { - public var flags: Int32 - public var bot: Api.Bool? - public var premium: Api.Bool? - public init(flags: Int32, bot: Api.Bool?, premium: Api.Bool?) { - self.flags = flags - self.bot = bot - self.premium = premium - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestPeerTypeUser", [("flags", ConstructorParameterDescription(self.flags)), ("bot", ConstructorParameterDescription(self.bot)), ("premium", ConstructorParameterDescription(self.premium))]) - } - } - case requestPeerTypeBroadcast(Cons_requestPeerTypeBroadcast) - case requestPeerTypeChat(Cons_requestPeerTypeChat) - case requestPeerTypeCreateBot(Cons_requestPeerTypeCreateBot) - case requestPeerTypeUser(Cons_requestPeerTypeUser) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .requestPeerTypeBroadcast(let _data): - if boxed { - buffer.appendInt32(865857388) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.hasUsername!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.userAdminRights!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.botAdminRights!.serialize(buffer, true) - } - break - case .requestPeerTypeChat(let _data): - if boxed { - buffer.appendInt32(-906990053) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.hasUsername!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.forum!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.userAdminRights!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.botAdminRights!.serialize(buffer, true) - } - break - case .requestPeerTypeCreateBot(let _data): - if boxed { - buffer.appendInt32(1048699000) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.suggestedName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.suggestedUsername!, buffer: buffer, boxed: false) - } - break - case .requestPeerTypeUser(let _data): - if boxed { - buffer.appendInt32(1597737472) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.bot!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.premium!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .requestPeerTypeBroadcast(let _data): - return ("requestPeerTypeBroadcast", [("flags", ConstructorParameterDescription(_data.flags)), ("hasUsername", ConstructorParameterDescription(_data.hasUsername)), ("userAdminRights", ConstructorParameterDescription(_data.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(_data.botAdminRights))]) - case .requestPeerTypeChat(let _data): - return ("requestPeerTypeChat", [("flags", ConstructorParameterDescription(_data.flags)), ("hasUsername", ConstructorParameterDescription(_data.hasUsername)), ("forum", ConstructorParameterDescription(_data.forum)), ("userAdminRights", ConstructorParameterDescription(_data.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(_data.botAdminRights))]) - case .requestPeerTypeCreateBot(let _data): - return ("requestPeerTypeCreateBot", [("flags", ConstructorParameterDescription(_data.flags)), ("suggestedName", ConstructorParameterDescription(_data.suggestedName)), ("suggestedUsername", ConstructorParameterDescription(_data.suggestedUsername))]) - case .requestPeerTypeUser(let _data): - return ("requestPeerTypeUser", [("flags", ConstructorParameterDescription(_data.flags)), ("bot", ConstructorParameterDescription(_data.bot)), ("premium", ConstructorParameterDescription(_data.premium))]) - } - } - - public static func parse_requestPeerTypeBroadcast(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _3: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - var _4: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestPeerType.requestPeerTypeBroadcast(Cons_requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4)) - } - else { - return nil - } - } - public static func parse_requestPeerTypeChat(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _4: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - var _5: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestPeerType.requestPeerTypeChat(Cons_requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5)) - } - else { - return nil - } - } - public static func parse_requestPeerTypeCreateBot(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = parseString(reader) - } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.RequestPeerType.requestPeerTypeCreateBot(Cons_requestPeerTypeCreateBot(flags: _1!, suggestedName: _2, suggestedUsername: _3)) - } - else { - return nil - } - } - public static func parse_requestPeerTypeUser(_ reader: BufferReader) -> RequestPeerType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Bool? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.RequestPeerType.requestPeerTypeUser(Cons_requestPeerTypeUser(flags: _1!, bot: _2, premium: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum RequestedPeer: TypeConstructorDescription { - public class Cons_requestedPeerChannel: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var title: String? - public var username: String? - public var photo: Api.Photo? - public init(flags: Int32, channelId: Int64, title: String?, username: String?, photo: Api.Photo?) { - self.flags = flags - self.channelId = channelId - self.title = title - self.username = username - self.photo = photo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestedPeerChannel", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("title", ConstructorParameterDescription(self.title)), ("username", ConstructorParameterDescription(self.username)), ("photo", ConstructorParameterDescription(self.photo))]) - } - } - public class Cons_requestedPeerChat: TypeConstructorDescription { - public var flags: Int32 - public var chatId: Int64 - public var title: String? - public var photo: Api.Photo? - public init(flags: Int32, chatId: Int64, title: String?, photo: Api.Photo?) { - self.flags = flags - self.chatId = chatId - self.title = title - self.photo = photo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestedPeerChat", [("flags", ConstructorParameterDescription(self.flags)), ("chatId", ConstructorParameterDescription(self.chatId)), ("title", ConstructorParameterDescription(self.title)), ("photo", ConstructorParameterDescription(self.photo))]) - } - } - public class Cons_requestedPeerUser: TypeConstructorDescription { - public var flags: Int32 - public var userId: Int64 - public var firstName: String? - public var lastName: String? - public var username: String? - public var photo: Api.Photo? - public init(flags: Int32, userId: Int64, firstName: String?, lastName: String?, username: String?, photo: Api.Photo?) { - self.flags = flags - self.userId = userId - self.firstName = firstName - self.lastName = lastName - self.username = username - self.photo = photo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestedPeerUser", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("username", ConstructorParameterDescription(self.username)), ("photo", ConstructorParameterDescription(self.photo))]) - } - } - case requestedPeerChannel(Cons_requestedPeerChannel) - case requestedPeerChat(Cons_requestedPeerChat) - case requestedPeerUser(Cons_requestedPeerUser) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .requestedPeerChannel(let _data): - if boxed { - buffer.appendInt32(-1952185372) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.username!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - break - case .requestedPeerChat(let _data): - if boxed { - buffer.appendInt32(1929860175) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - break - case .requestedPeerUser(let _data): - if boxed { - buffer.appendInt32(-701500310) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.firstName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.lastName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.username!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .requestedPeerChannel(let _data): - return ("requestedPeerChannel", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("title", ConstructorParameterDescription(_data.title)), ("username", ConstructorParameterDescription(_data.username)), ("photo", ConstructorParameterDescription(_data.photo))]) - case .requestedPeerChat(let _data): - return ("requestedPeerChat", [("flags", ConstructorParameterDescription(_data.flags)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("title", ConstructorParameterDescription(_data.title)), ("photo", ConstructorParameterDescription(_data.photo))]) - case .requestedPeerUser(let _data): - return ("requestedPeerUser", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("username", ConstructorParameterDescription(_data.username)), ("photo", ConstructorParameterDescription(_data.photo))]) - } - } - - public static func parse_requestedPeerChannel(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) - } - var _5: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.RequestedPeer.requestedPeerChannel(Cons_requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5)) - } - else { - return nil - } - } - public static func parse_requestedPeerChat(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.RequestedPeer.requestedPeerChat(Cons_requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4)) - } - else { - return nil - } - } - public static func parse_requestedPeerUser(_ reader: BufferReader) -> RequestedPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - var _6: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.RequestedPeer.requestedPeerUser(Cons_requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6)) - } - else { - return nil - } - } - } -} -public extension Api { - enum RequirementToContact: TypeConstructorDescription { - public class Cons_requirementToContactPaidMessages: TypeConstructorDescription { - public var starsAmount: Int64 - public init(starsAmount: Int64) { - self.starsAmount = starsAmount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requirementToContactPaidMessages", [("starsAmount", ConstructorParameterDescription(self.starsAmount))]) - } - } - case requirementToContactEmpty - case requirementToContactPaidMessages(Cons_requirementToContactPaidMessages) - case requirementToContactPremium - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .requirementToContactEmpty: - if boxed { - buffer.appendInt32(84580409) - } - break - case .requirementToContactPaidMessages(let _data): - if boxed { - buffer.appendInt32(-1258914157) - } - serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) - break - case .requirementToContactPremium: - if boxed { - buffer.appendInt32(-444472087) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .requirementToContactEmpty: - return ("requirementToContactEmpty", []) - case .requirementToContactPaidMessages(let _data): - return ("requirementToContactPaidMessages", [("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) - case .requirementToContactPremium: - return ("requirementToContactPremium", []) - } - } - - public static func parse_requirementToContactEmpty(_ reader: BufferReader) -> RequirementToContact? { - return Api.RequirementToContact.requirementToContactEmpty - } - public static func parse_requirementToContactPaidMessages(_ reader: BufferReader) -> RequirementToContact? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.RequirementToContact.requirementToContactPaidMessages(Cons_requirementToContactPaidMessages(starsAmount: _1!)) - } - else { - return nil - } - } - public static func parse_requirementToContactPremium(_ reader: BufferReader) -> RequirementToContact? { - return Api.RequirementToContact.requirementToContactPremium - } - } -} diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 9e9f2b2b5c..798549678a 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -1,52 +1,46 @@ public extension Api { - enum RestrictionReason: TypeConstructorDescription { - public class Cons_restrictionReason: TypeConstructorDescription { - public var platform: String - public var reason: String - public var text: String - public init(platform: String, reason: String, text: String) { - self.platform = platform - self.reason = reason - self.text = text + enum ReceivedNotifyMessage: TypeConstructorDescription { + public class Cons_receivedNotifyMessage: TypeConstructorDescription { + public var id: Int32 + public var flags: Int32 + public init(id: Int32, flags: Int32) { + self.id = id + self.flags = flags } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("restrictionReason", [("platform", ConstructorParameterDescription(self.platform)), ("reason", ConstructorParameterDescription(self.reason)), ("text", ConstructorParameterDescription(self.text))]) + return ("receivedNotifyMessage", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags))]) } } - case restrictionReason(Cons_restrictionReason) + case receivedNotifyMessage(Cons_receivedNotifyMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .restrictionReason(let _data): + case .receivedNotifyMessage(let _data): if boxed { - buffer.appendInt32(-797791052) + buffer.appendInt32(-1551583367) } - serializeString(_data.platform, buffer: buffer, boxed: false) - serializeString(_data.reason, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .restrictionReason(let _data): - return ("restrictionReason", [("platform", ConstructorParameterDescription(_data.platform)), ("reason", ConstructorParameterDescription(_data.reason)), ("text", ConstructorParameterDescription(_data.text))]) + case .receivedNotifyMessage(let _data): + return ("receivedNotifyMessage", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags))]) } } - public static func parse_restrictionReason(_ reader: BufferReader) -> RestrictionReason? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) + public static func parse_receivedNotifyMessage(_ reader: BufferReader) -> ReceivedNotifyMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RestrictionReason.restrictionReason(Cons_restrictionReason(platform: _1!, reason: _2!, text: _3!)) + if _c1 && _c2 { + return Api.ReceivedNotifyMessage.receivedNotifyMessage(Cons_receivedNotifyMessage(id: _1!, flags: _2!)) } else { return nil @@ -55,531 +49,409 @@ public extension Api { } } public extension Api { - indirect enum RichText: TypeConstructorDescription { - public class Cons_textAnchor: TypeConstructorDescription { - public var text: Api.RichText - public var name: String - public init(text: Api.RichText, name: String) { - self.text = text - self.name = name - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textAnchor", [("text", ConstructorParameterDescription(self.text)), ("name", ConstructorParameterDescription(self.name))]) - } - } - public class Cons_textBold: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textBold", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textConcat: TypeConstructorDescription { - public var texts: [Api.RichText] - public init(texts: [Api.RichText]) { - self.texts = texts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textConcat", [("texts", ConstructorParameterDescription(self.texts))]) - } - } - public class Cons_textEmail: TypeConstructorDescription { - public var text: Api.RichText - public var email: String - public init(text: Api.RichText, email: String) { - self.text = text - self.email = email - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textEmail", [("text", ConstructorParameterDescription(self.text)), ("email", ConstructorParameterDescription(self.email))]) - } - } - public class Cons_textFixed: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textFixed", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textImage: TypeConstructorDescription { - public var documentId: Int64 - public var w: Int32 - public var h: Int32 - public init(documentId: Int64, w: Int32, h: Int32) { - self.documentId = documentId - self.w = w - self.h = h - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textImage", [("documentId", ConstructorParameterDescription(self.documentId)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h))]) - } - } - public class Cons_textItalic: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textItalic", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textMarked: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textMarked", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textPhone: TypeConstructorDescription { - public var text: Api.RichText - public var phone: String - public init(text: Api.RichText, phone: String) { - self.text = text - self.phone = phone - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textPhone", [("text", ConstructorParameterDescription(self.text)), ("phone", ConstructorParameterDescription(self.phone))]) - } - } - public class Cons_textPlain: TypeConstructorDescription { - public var text: String - public init(text: String) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textPlain", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textStrike: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textStrike", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textSubscript: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textSubscript", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textSuperscript: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textSuperscript", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textUnderline: TypeConstructorDescription { - public var text: Api.RichText - public init(text: Api.RichText) { - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textUnderline", [("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_textUrl: TypeConstructorDescription { - public var text: Api.RichText + indirect enum RecentMeUrl: TypeConstructorDescription { + public class Cons_recentMeUrlChat: TypeConstructorDescription { public var url: String - public var webpageId: Int64 - public init(text: Api.RichText, url: String, webpageId: Int64) { - self.text = text + public var chatId: Int64 + public init(url: String, chatId: Int64) { self.url = url - self.webpageId = webpageId + self.chatId = chatId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textUrl", [("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId))]) + return ("recentMeUrlChat", [("url", ConstructorParameterDescription(self.url)), ("chatId", ConstructorParameterDescription(self.chatId))]) } } - case textAnchor(Cons_textAnchor) - case textBold(Cons_textBold) - case textConcat(Cons_textConcat) - case textEmail(Cons_textEmail) - case textEmpty - case textFixed(Cons_textFixed) - case textImage(Cons_textImage) - case textItalic(Cons_textItalic) - case textMarked(Cons_textMarked) - case textPhone(Cons_textPhone) - case textPlain(Cons_textPlain) - case textStrike(Cons_textStrike) - case textSubscript(Cons_textSubscript) - case textSuperscript(Cons_textSuperscript) - case textUnderline(Cons_textUnderline) - case textUrl(Cons_textUrl) + public class Cons_recentMeUrlChatInvite: TypeConstructorDescription { + public var url: String + public var chatInvite: Api.ChatInvite + public init(url: String, chatInvite: Api.ChatInvite) { + self.url = url + self.chatInvite = chatInvite + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentMeUrlChatInvite", [("url", ConstructorParameterDescription(self.url)), ("chatInvite", ConstructorParameterDescription(self.chatInvite))]) + } + } + public class Cons_recentMeUrlStickerSet: TypeConstructorDescription { + public var url: String + public var set: Api.StickerSetCovered + public init(url: String, set: Api.StickerSetCovered) { + self.url = url + self.set = set + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentMeUrlStickerSet", [("url", ConstructorParameterDescription(self.url)), ("set", ConstructorParameterDescription(self.set))]) + } + } + public class Cons_recentMeUrlUnknown: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentMeUrlUnknown", [("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_recentMeUrlUser: TypeConstructorDescription { + public var url: String + public var userId: Int64 + public init(url: String, userId: Int64) { + self.url = url + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentMeUrlUser", [("url", ConstructorParameterDescription(self.url)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + case recentMeUrlChat(Cons_recentMeUrlChat) + case recentMeUrlChatInvite(Cons_recentMeUrlChatInvite) + case recentMeUrlStickerSet(Cons_recentMeUrlStickerSet) + case recentMeUrlUnknown(Cons_recentMeUrlUnknown) + case recentMeUrlUser(Cons_recentMeUrlUser) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .textAnchor(let _data): + case .recentMeUrlChat(let _data): if boxed { - buffer.appendInt32(894777186) + buffer.appendInt32(-1294306862) } - _data.text.serialize(buffer, true) - serializeString(_data.name, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.chatId, buffer: buffer, boxed: false) break - case .textBold(let _data): + case .recentMeUrlChatInvite(let _data): if boxed { - buffer.appendInt32(1730456516) + buffer.appendInt32(-347535331) } - _data.text.serialize(buffer, true) + serializeString(_data.url, buffer: buffer, boxed: false) + _data.chatInvite.serialize(buffer, true) break - case .textConcat(let _data): + case .recentMeUrlStickerSet(let _data): if boxed { - buffer.appendInt32(2120376535) + buffer.appendInt32(-1140172836) + } + serializeString(_data.url, buffer: buffer, boxed: false) + _data.set.serialize(buffer, true) + break + case .recentMeUrlUnknown(let _data): + if boxed { + buffer.appendInt32(1189204285) + } + serializeString(_data.url, buffer: buffer, boxed: false) + break + case .recentMeUrlUser(let _data): + if boxed { + buffer.appendInt32(-1188296222) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .recentMeUrlChat(let _data): + return ("recentMeUrlChat", [("url", ConstructorParameterDescription(_data.url)), ("chatId", ConstructorParameterDescription(_data.chatId))]) + case .recentMeUrlChatInvite(let _data): + return ("recentMeUrlChatInvite", [("url", ConstructorParameterDescription(_data.url)), ("chatInvite", ConstructorParameterDescription(_data.chatInvite))]) + case .recentMeUrlStickerSet(let _data): + return ("recentMeUrlStickerSet", [("url", ConstructorParameterDescription(_data.url)), ("set", ConstructorParameterDescription(_data.set))]) + case .recentMeUrlUnknown(let _data): + return ("recentMeUrlUnknown", [("url", ConstructorParameterDescription(_data.url))]) + case .recentMeUrlUser(let _data): + return ("recentMeUrlUser", [("url", ConstructorParameterDescription(_data.url)), ("userId", ConstructorParameterDescription(_data.userId))]) + } + } + + public static func parse_recentMeUrlChat(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlChat(Cons_recentMeUrlChat(url: _1!, chatId: _2!)) + } + else { + return nil + } + } + public static func parse_recentMeUrlChatInvite(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Api.ChatInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ChatInvite + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlChatInvite(Cons_recentMeUrlChatInvite(url: _1!, chatInvite: _2!)) + } + else { + return nil + } + } + public static func parse_recentMeUrlStickerSet(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Api.StickerSetCovered? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StickerSetCovered + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlStickerSet(Cons_recentMeUrlStickerSet(url: _1!, set: _2!)) + } + else { + return nil + } + } + public static func parse_recentMeUrlUnknown(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RecentMeUrl.recentMeUrlUnknown(Cons_recentMeUrlUnknown(url: _1!)) + } + else { + return nil + } + } + public static func parse_recentMeUrlUser(_ reader: BufferReader) -> RecentMeUrl? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RecentMeUrl.recentMeUrlUser(Cons_recentMeUrlUser(url: _1!, userId: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum RecentStory: TypeConstructorDescription { + public class Cons_recentStory: TypeConstructorDescription { + public var flags: Int32 + public var maxId: Int32? + public init(flags: Int32, maxId: Int32?) { + self.flags = flags + self.maxId = maxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentStory", [("flags", ConstructorParameterDescription(self.flags)), ("maxId", ConstructorParameterDescription(self.maxId))]) + } + } + case recentStory(Cons_recentStory) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .recentStory(let _data): + if boxed { + buffer.appendInt32(1897752877) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.maxId!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .recentStory(let _data): + return ("recentStory", [("flags", ConstructorParameterDescription(_data.flags)), ("maxId", ConstructorParameterDescription(_data.maxId))]) + } + } + + public static func parse_recentStory(_ reader: BufferReader) -> RecentStory? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + if _c1 && _c2 { + return Api.RecentStory.recentStory(Cons_recentStory(flags: _1!, maxId: _2)) + } + else { + return nil + } + } + } +} +public extension Api { + enum ReplyMarkup: TypeConstructorDescription { + public class Cons_replyInlineMarkup: TypeConstructorDescription { + public var rows: [Api.KeyboardButtonRow] + public init(rows: [Api.KeyboardButtonRow]) { + self.rows = rows + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("replyInlineMarkup", [("rows", ConstructorParameterDescription(self.rows))]) + } + } + public class Cons_replyKeyboardForceReply: TypeConstructorDescription { + public var flags: Int32 + public var placeholder: String? + public init(flags: Int32, placeholder: String?) { + self.flags = flags + self.placeholder = placeholder + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("replyKeyboardForceReply", [("flags", ConstructorParameterDescription(self.flags)), ("placeholder", ConstructorParameterDescription(self.placeholder))]) + } + } + public class Cons_replyKeyboardHide: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("replyKeyboardHide", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_replyKeyboardMarkup: TypeConstructorDescription { + public var flags: Int32 + public var rows: [Api.KeyboardButtonRow] + public var placeholder: String? + public init(flags: Int32, rows: [Api.KeyboardButtonRow], placeholder: String?) { + self.flags = flags + self.rows = rows + self.placeholder = placeholder + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("replyKeyboardMarkup", [("flags", ConstructorParameterDescription(self.flags)), ("rows", ConstructorParameterDescription(self.rows)), ("placeholder", ConstructorParameterDescription(self.placeholder))]) + } + } + case replyInlineMarkup(Cons_replyInlineMarkup) + case replyKeyboardForceReply(Cons_replyKeyboardForceReply) + case replyKeyboardHide(Cons_replyKeyboardHide) + case replyKeyboardMarkup(Cons_replyKeyboardMarkup) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .replyInlineMarkup(let _data): + if boxed { + buffer.appendInt32(1218642516) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.texts.count)) - for item in _data.texts { + buffer.appendInt32(Int32(_data.rows.count)) + for item in _data.rows { item.serialize(buffer, true) } break - case .textEmail(let _data): + case .replyKeyboardForceReply(let _data): if boxed { - buffer.appendInt32(-564523562) + buffer.appendInt32(-2035021048) } - _data.text.serialize(buffer, true) - serializeString(_data.email, buffer: buffer, boxed: false) - break - case .textEmpty: - if boxed { - buffer.appendInt32(-599948721) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.placeholder!, buffer: buffer, boxed: false) } break - case .textFixed(let _data): + case .replyKeyboardHide(let _data): if boxed { - buffer.appendInt32(1816074681) + buffer.appendInt32(-1606526075) } - _data.text.serialize(buffer, true) + serializeInt32(_data.flags, buffer: buffer, boxed: false) break - case .textImage(let _data): + case .replyKeyboardMarkup(let _data): if boxed { - buffer.appendInt32(136105807) + buffer.appendInt32(-2049074735) } - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - break - case .textItalic(let _data): - if boxed { - buffer.appendInt32(-653089380) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.rows.count)) + for item in _data.rows { + item.serialize(buffer, true) } - _data.text.serialize(buffer, true) - break - case .textMarked(let _data): - if boxed { - buffer.appendInt32(55281185) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.placeholder!, buffer: buffer, boxed: false) } - _data.text.serialize(buffer, true) - break - case .textPhone(let _data): - if boxed { - buffer.appendInt32(483104362) - } - _data.text.serialize(buffer, true) - serializeString(_data.phone, buffer: buffer, boxed: false) - break - case .textPlain(let _data): - if boxed { - buffer.appendInt32(1950782688) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .textStrike(let _data): - if boxed { - buffer.appendInt32(-1678197867) - } - _data.text.serialize(buffer, true) - break - case .textSubscript(let _data): - if boxed { - buffer.appendInt32(-311786236) - } - _data.text.serialize(buffer, true) - break - case .textSuperscript(let _data): - if boxed { - buffer.appendInt32(-939827711) - } - _data.text.serialize(buffer, true) - break - case .textUnderline(let _data): - if boxed { - buffer.appendInt32(-1054465340) - } - _data.text.serialize(buffer, true) - break - case .textUrl(let _data): - if boxed { - buffer.appendInt32(1009288385) - } - _data.text.serialize(buffer, true) - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.webpageId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .textAnchor(let _data): - return ("textAnchor", [("text", ConstructorParameterDescription(_data.text)), ("name", ConstructorParameterDescription(_data.name))]) - case .textBold(let _data): - return ("textBold", [("text", ConstructorParameterDescription(_data.text))]) - case .textConcat(let _data): - return ("textConcat", [("texts", ConstructorParameterDescription(_data.texts))]) - case .textEmail(let _data): - return ("textEmail", [("text", ConstructorParameterDescription(_data.text)), ("email", ConstructorParameterDescription(_data.email))]) - case .textEmpty: - return ("textEmpty", []) - case .textFixed(let _data): - return ("textFixed", [("text", ConstructorParameterDescription(_data.text))]) - case .textImage(let _data): - return ("textImage", [("documentId", ConstructorParameterDescription(_data.documentId)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h))]) - case .textItalic(let _data): - return ("textItalic", [("text", ConstructorParameterDescription(_data.text))]) - case .textMarked(let _data): - return ("textMarked", [("text", ConstructorParameterDescription(_data.text))]) - case .textPhone(let _data): - return ("textPhone", [("text", ConstructorParameterDescription(_data.text)), ("phone", ConstructorParameterDescription(_data.phone))]) - case .textPlain(let _data): - return ("textPlain", [("text", ConstructorParameterDescription(_data.text))]) - case .textStrike(let _data): - return ("textStrike", [("text", ConstructorParameterDescription(_data.text))]) - case .textSubscript(let _data): - return ("textSubscript", [("text", ConstructorParameterDescription(_data.text))]) - case .textSuperscript(let _data): - return ("textSuperscript", [("text", ConstructorParameterDescription(_data.text))]) - case .textUnderline(let _data): - return ("textUnderline", [("text", ConstructorParameterDescription(_data.text))]) - case .textUrl(let _data): - return ("textUrl", [("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId))]) + case .replyInlineMarkup(let _data): + return ("replyInlineMarkup", [("rows", ConstructorParameterDescription(_data.rows))]) + case .replyKeyboardForceReply(let _data): + return ("replyKeyboardForceReply", [("flags", ConstructorParameterDescription(_data.flags)), ("placeholder", ConstructorParameterDescription(_data.placeholder))]) + case .replyKeyboardHide(let _data): + return ("replyKeyboardHide", [("flags", ConstructorParameterDescription(_data.flags))]) + case .replyKeyboardMarkup(let _data): + return ("replyKeyboardMarkup", [("flags", ConstructorParameterDescription(_data.flags)), ("rows", ConstructorParameterDescription(_data.rows)), ("placeholder", ConstructorParameterDescription(_data.placeholder))]) } } - public static func parse_textAnchor(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textAnchor(Cons_textAnchor(text: _1!, name: _2!)) - } - else { - return nil - } - } - public static func parse_textBold(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textBold(Cons_textBold(text: _1!)) - } - else { - return nil - } - } - public static func parse_textConcat(_ reader: BufferReader) -> RichText? { - var _1: [Api.RichText]? + public static func parse_replyInlineMarkup(_ reader: BufferReader) -> ReplyMarkup? { + var _1: [Api.KeyboardButtonRow]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RichText.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) } let _c1 = _1 != nil if _c1 { - return Api.RichText.textConcat(Cons_textConcat(texts: _1!)) + return Api.ReplyMarkup.replyInlineMarkup(Cons_replyInlineMarkup(rows: _1!)) } else { return nil } } - public static func parse_textEmail(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } + public static func parse_replyKeyboardForceReply(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() var _2: String? - _2 = parseString(reader) + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _2 = parseString(reader) + } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil if _c1 && _c2 { - return Api.RichText.textEmail(Cons_textEmail(text: _1!, email: _2!)) + return Api.ReplyMarkup.replyKeyboardForceReply(Cons_replyKeyboardForceReply(flags: _1!, placeholder: _2)) } else { return nil } } - public static func parse_textEmpty(_ reader: BufferReader) -> RichText? { - return Api.RichText.textEmpty - } - public static func parse_textFixed(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } + public static func parse_replyKeyboardHide(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.RichText.textFixed(Cons_textFixed(text: _1!)) + return Api.ReplyMarkup.replyKeyboardHide(Cons_replyKeyboardHide(flags: _1!)) } else { return nil } } - public static func parse_textImage(_ reader: BufferReader) -> RichText? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + public static func parse_replyKeyboardMarkup(_ reader: BufferReader) -> ReplyMarkup? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.KeyboardButtonRow]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.KeyboardButtonRow.self) + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.RichText.textImage(Cons_textImage(documentId: _1!, w: _2!, h: _3!)) - } - else { - return nil - } - } - public static func parse_textItalic(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textItalic(Cons_textItalic(text: _1!)) - } - else { - return nil - } - } - public static func parse_textMarked(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textMarked(Cons_textMarked(text: _1!)) - } - else { - return nil - } - } - public static func parse_textPhone(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.RichText.textPhone(Cons_textPhone(text: _1!, phone: _2!)) - } - else { - return nil - } - } - public static func parse_textPlain(_ reader: BufferReader) -> RichText? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textPlain(Cons_textPlain(text: _1!)) - } - else { - return nil - } - } - public static func parse_textStrike(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textStrike(Cons_textStrike(text: _1!)) - } - else { - return nil - } - } - public static func parse_textSubscript(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSubscript(Cons_textSubscript(text: _1!)) - } - else { - return nil - } - } - public static func parse_textSuperscript(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textSuperscript(Cons_textSuperscript(text: _1!)) - } - else { - return nil - } - } - public static func parse_textUnderline(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - let _c1 = _1 != nil - if _c1 { - return Api.RichText.textUnderline(Cons_textUnderline(text: _1!)) - } - else { - return nil - } - } - public static func parse_textUrl(_ reader: BufferReader) -> RichText? { - var _1: Api.RichText? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.RichText - } - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.RichText.textUrl(Cons_textUrl(text: _1!, url: _2!, webpageId: _3!)) + return Api.ReplyMarkup.replyKeyboardMarkup(Cons_replyKeyboardMarkup(flags: _1!, rows: _2!, placeholder: _3)) } else { return nil @@ -588,376 +460,596 @@ public extension Api { } } public extension Api { - enum SavedContact: TypeConstructorDescription { - public class Cons_savedPhoneContact: TypeConstructorDescription { - public var phone: String - public var firstName: String - public var lastName: String - public var date: Int32 - public init(phone: String, firstName: String, lastName: String, date: Int32) { - self.phone = phone + enum ReportReason: TypeConstructorDescription { + case inputReportReasonChildAbuse + case inputReportReasonCopyright + case inputReportReasonFake + case inputReportReasonGeoIrrelevant + case inputReportReasonIllegalDrugs + case inputReportReasonOther + case inputReportReasonPersonalDetails + case inputReportReasonPornography + case inputReportReasonSpam + case inputReportReasonViolence + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputReportReasonChildAbuse: + if boxed { + buffer.appendInt32(-1376497949) + } + break + case .inputReportReasonCopyright: + if boxed { + buffer.appendInt32(-1685456582) + } + break + case .inputReportReasonFake: + if boxed { + buffer.appendInt32(-170010905) + } + break + case .inputReportReasonGeoIrrelevant: + if boxed { + buffer.appendInt32(-606798099) + } + break + case .inputReportReasonIllegalDrugs: + if boxed { + buffer.appendInt32(177124030) + } + break + case .inputReportReasonOther: + if boxed { + buffer.appendInt32(-1041980751) + } + break + case .inputReportReasonPersonalDetails: + if boxed { + buffer.appendInt32(-1631091139) + } + break + case .inputReportReasonPornography: + if boxed { + buffer.appendInt32(777640226) + } + break + case .inputReportReasonSpam: + if boxed { + buffer.appendInt32(1490799288) + } + break + case .inputReportReasonViolence: + if boxed { + buffer.appendInt32(505595789) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputReportReasonChildAbuse: + return ("inputReportReasonChildAbuse", []) + case .inputReportReasonCopyright: + return ("inputReportReasonCopyright", []) + case .inputReportReasonFake: + return ("inputReportReasonFake", []) + case .inputReportReasonGeoIrrelevant: + return ("inputReportReasonGeoIrrelevant", []) + case .inputReportReasonIllegalDrugs: + return ("inputReportReasonIllegalDrugs", []) + case .inputReportReasonOther: + return ("inputReportReasonOther", []) + case .inputReportReasonPersonalDetails: + return ("inputReportReasonPersonalDetails", []) + case .inputReportReasonPornography: + return ("inputReportReasonPornography", []) + case .inputReportReasonSpam: + return ("inputReportReasonSpam", []) + case .inputReportReasonViolence: + return ("inputReportReasonViolence", []) + } + } + + public static func parse_inputReportReasonChildAbuse(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonChildAbuse + } + public static func parse_inputReportReasonCopyright(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonCopyright + } + public static func parse_inputReportReasonFake(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonFake + } + public static func parse_inputReportReasonGeoIrrelevant(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonGeoIrrelevant + } + public static func parse_inputReportReasonIllegalDrugs(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonIllegalDrugs + } + public static func parse_inputReportReasonOther(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonOther + } + public static func parse_inputReportReasonPersonalDetails(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonPersonalDetails + } + public static func parse_inputReportReasonPornography(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonPornography + } + public static func parse_inputReportReasonSpam(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonSpam + } + public static func parse_inputReportReasonViolence(_ reader: BufferReader) -> ReportReason? { + return Api.ReportReason.inputReportReasonViolence + } + } +} +public extension Api { + enum ReportResult: TypeConstructorDescription { + public class Cons_reportResultAddComment: TypeConstructorDescription { + public var flags: Int32 + public var option: Buffer + public init(flags: Int32, option: Buffer) { + self.flags = flags + self.option = option + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reportResultAddComment", [("flags", ConstructorParameterDescription(self.flags)), ("option", ConstructorParameterDescription(self.option))]) + } + } + public class Cons_reportResultChooseOption: TypeConstructorDescription { + public var title: String + public var options: [Api.MessageReportOption] + public init(title: String, options: [Api.MessageReportOption]) { + self.title = title + self.options = options + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reportResultChooseOption", [("title", ConstructorParameterDescription(self.title)), ("options", ConstructorParameterDescription(self.options))]) + } + } + case reportResultAddComment(Cons_reportResultAddComment) + case reportResultChooseOption(Cons_reportResultChooseOption) + case reportResultReported + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reportResultAddComment(let _data): + if boxed { + buffer.appendInt32(1862904881) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeBytes(_data.option, buffer: buffer, boxed: false) + break + case .reportResultChooseOption(let _data): + if boxed { + buffer.appendInt32(-253435722) + } + serializeString(_data.title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.options.count)) + for item in _data.options { + item.serialize(buffer, true) + } + break + case .reportResultReported: + if boxed { + buffer.appendInt32(-1917633461) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reportResultAddComment(let _data): + return ("reportResultAddComment", [("flags", ConstructorParameterDescription(_data.flags)), ("option", ConstructorParameterDescription(_data.option))]) + case .reportResultChooseOption(let _data): + return ("reportResultChooseOption", [("title", ConstructorParameterDescription(_data.title)), ("options", ConstructorParameterDescription(_data.options))]) + case .reportResultReported: + return ("reportResultReported", []) + } + } + + public static func parse_reportResultAddComment(_ reader: BufferReader) -> ReportResult? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ReportResult.reportResultAddComment(Cons_reportResultAddComment(flags: _1!, option: _2!)) + } + else { + return nil + } + } + public static func parse_reportResultChooseOption(_ reader: BufferReader) -> ReportResult? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageReportOption]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageReportOption.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ReportResult.reportResultChooseOption(Cons_reportResultChooseOption(title: _1!, options: _2!)) + } + else { + return nil + } + } + public static func parse_reportResultReported(_ reader: BufferReader) -> ReportResult? { + return Api.ReportResult.reportResultReported + } + } +} +public extension Api { + enum RequestPeerType: TypeConstructorDescription { + public class Cons_requestPeerTypeBroadcast: TypeConstructorDescription { + public var flags: Int32 + public var hasUsername: Api.Bool? + public var userAdminRights: Api.ChatAdminRights? + public var botAdminRights: Api.ChatAdminRights? + public init(flags: Int32, hasUsername: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) { + self.flags = flags + self.hasUsername = hasUsername + self.userAdminRights = userAdminRights + self.botAdminRights = botAdminRights + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestPeerTypeBroadcast", [("flags", ConstructorParameterDescription(self.flags)), ("hasUsername", ConstructorParameterDescription(self.hasUsername)), ("userAdminRights", ConstructorParameterDescription(self.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(self.botAdminRights))]) + } + } + public class Cons_requestPeerTypeChat: TypeConstructorDescription { + public var flags: Int32 + public var hasUsername: Api.Bool? + public var forum: Api.Bool? + public var userAdminRights: Api.ChatAdminRights? + public var botAdminRights: Api.ChatAdminRights? + public init(flags: Int32, hasUsername: Api.Bool?, forum: Api.Bool?, userAdminRights: Api.ChatAdminRights?, botAdminRights: Api.ChatAdminRights?) { + self.flags = flags + self.hasUsername = hasUsername + self.forum = forum + self.userAdminRights = userAdminRights + self.botAdminRights = botAdminRights + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestPeerTypeChat", [("flags", ConstructorParameterDescription(self.flags)), ("hasUsername", ConstructorParameterDescription(self.hasUsername)), ("forum", ConstructorParameterDescription(self.forum)), ("userAdminRights", ConstructorParameterDescription(self.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(self.botAdminRights))]) + } + } + public class Cons_requestPeerTypeCreateBot: TypeConstructorDescription { + public var flags: Int32 + public var suggestedName: String? + public var suggestedUsername: String? + public init(flags: Int32, suggestedName: String?, suggestedUsername: String?) { + self.flags = flags + self.suggestedName = suggestedName + self.suggestedUsername = suggestedUsername + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestPeerTypeCreateBot", [("flags", ConstructorParameterDescription(self.flags)), ("suggestedName", ConstructorParameterDescription(self.suggestedName)), ("suggestedUsername", ConstructorParameterDescription(self.suggestedUsername))]) + } + } + public class Cons_requestPeerTypeUser: TypeConstructorDescription { + public var flags: Int32 + public var bot: Api.Bool? + public var premium: Api.Bool? + public init(flags: Int32, bot: Api.Bool?, premium: Api.Bool?) { + self.flags = flags + self.bot = bot + self.premium = premium + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestPeerTypeUser", [("flags", ConstructorParameterDescription(self.flags)), ("bot", ConstructorParameterDescription(self.bot)), ("premium", ConstructorParameterDescription(self.premium))]) + } + } + case requestPeerTypeBroadcast(Cons_requestPeerTypeBroadcast) + case requestPeerTypeChat(Cons_requestPeerTypeChat) + case requestPeerTypeCreateBot(Cons_requestPeerTypeCreateBot) + case requestPeerTypeUser(Cons_requestPeerTypeUser) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .requestPeerTypeBroadcast(let _data): + if boxed { + buffer.appendInt32(865857388) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.hasUsername!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.userAdminRights!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.botAdminRights!.serialize(buffer, true) + } + break + case .requestPeerTypeChat(let _data): + if boxed { + buffer.appendInt32(-906990053) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.hasUsername!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.forum!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.userAdminRights!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.botAdminRights!.serialize(buffer, true) + } + break + case .requestPeerTypeCreateBot(let _data): + if boxed { + buffer.appendInt32(1048699000) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.suggestedName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.suggestedUsername!, buffer: buffer, boxed: false) + } + break + case .requestPeerTypeUser(let _data): + if boxed { + buffer.appendInt32(1597737472) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.bot!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.premium!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .requestPeerTypeBroadcast(let _data): + return ("requestPeerTypeBroadcast", [("flags", ConstructorParameterDescription(_data.flags)), ("hasUsername", ConstructorParameterDescription(_data.hasUsername)), ("userAdminRights", ConstructorParameterDescription(_data.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(_data.botAdminRights))]) + case .requestPeerTypeChat(let _data): + return ("requestPeerTypeChat", [("flags", ConstructorParameterDescription(_data.flags)), ("hasUsername", ConstructorParameterDescription(_data.hasUsername)), ("forum", ConstructorParameterDescription(_data.forum)), ("userAdminRights", ConstructorParameterDescription(_data.userAdminRights)), ("botAdminRights", ConstructorParameterDescription(_data.botAdminRights))]) + case .requestPeerTypeCreateBot(let _data): + return ("requestPeerTypeCreateBot", [("flags", ConstructorParameterDescription(_data.flags)), ("suggestedName", ConstructorParameterDescription(_data.suggestedName)), ("suggestedUsername", ConstructorParameterDescription(_data.suggestedUsername))]) + case .requestPeerTypeUser(let _data): + return ("requestPeerTypeUser", [("flags", ConstructorParameterDescription(_data.flags)), ("bot", ConstructorParameterDescription(_data.bot)), ("premium", ConstructorParameterDescription(_data.premium))]) + } + } + + public static func parse_requestPeerTypeBroadcast(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _3: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + var _4: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.RequestPeerType.requestPeerTypeBroadcast(Cons_requestPeerTypeBroadcast(flags: _1!, hasUsername: _2, userAdminRights: _3, botAdminRights: _4)) + } + else { + return nil + } + } + public static func parse_requestPeerTypeChat(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _3: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _4: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + var _5: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.RequestPeerType.requestPeerTypeChat(Cons_requestPeerTypeChat(flags: _1!, hasUsername: _2, forum: _3, userAdminRights: _4, botAdminRights: _5)) + } + else { + return nil + } + } + public static func parse_requestPeerTypeCreateBot(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = parseString(reader) + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.RequestPeerType.requestPeerTypeCreateBot(Cons_requestPeerTypeCreateBot(flags: _1!, suggestedName: _2, suggestedUsername: _3)) + } + else { + return nil + } + } + public static func parse_requestPeerTypeUser(_ reader: BufferReader) -> RequestPeerType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _3: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.RequestPeerType.requestPeerTypeUser(Cons_requestPeerTypeUser(flags: _1!, bot: _2, premium: _3)) + } + else { + return nil + } + } + } +} +public extension Api { + enum RequestedPeer: TypeConstructorDescription { + public class Cons_requestedPeerChannel: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var title: String? + public var username: String? + public var photo: Api.Photo? + public init(flags: Int32, channelId: Int64, title: String?, username: String?, photo: Api.Photo?) { + self.flags = flags + self.channelId = channelId + self.title = title + self.username = username + self.photo = photo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestedPeerChannel", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("title", ConstructorParameterDescription(self.title)), ("username", ConstructorParameterDescription(self.username)), ("photo", ConstructorParameterDescription(self.photo))]) + } + } + public class Cons_requestedPeerChat: TypeConstructorDescription { + public var flags: Int32 + public var chatId: Int64 + public var title: String? + public var photo: Api.Photo? + public init(flags: Int32, chatId: Int64, title: String?, photo: Api.Photo?) { + self.flags = flags + self.chatId = chatId + self.title = title + self.photo = photo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("requestedPeerChat", [("flags", ConstructorParameterDescription(self.flags)), ("chatId", ConstructorParameterDescription(self.chatId)), ("title", ConstructorParameterDescription(self.title)), ("photo", ConstructorParameterDescription(self.photo))]) + } + } + public class Cons_requestedPeerUser: TypeConstructorDescription { + public var flags: Int32 + public var userId: Int64 + public var firstName: String? + public var lastName: String? + public var username: String? + public var photo: Api.Photo? + public init(flags: Int32, userId: Int64, firstName: String?, lastName: String?, username: String?, photo: Api.Photo?) { + self.flags = flags + self.userId = userId self.firstName = firstName self.lastName = lastName - self.date = date + self.username = username + self.photo = photo } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedPhoneContact", [("phone", ConstructorParameterDescription(self.phone)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("date", ConstructorParameterDescription(self.date))]) + return ("requestedPeerUser", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("username", ConstructorParameterDescription(self.username)), ("photo", ConstructorParameterDescription(self.photo))]) } } - case savedPhoneContact(Cons_savedPhoneContact) + case requestedPeerChannel(Cons_requestedPeerChannel) + case requestedPeerChat(Cons_requestedPeerChat) + case requestedPeerUser(Cons_requestedPeerUser) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedPhoneContact(let _data): + case .requestedPeerChannel(let _data): if boxed { - buffer.appendInt32(289586518) - } - serializeString(_data.phone, buffer: buffer, boxed: false) - serializeString(_data.firstName, buffer: buffer, boxed: false) - serializeString(_data.lastName, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedPhoneContact(let _data): - return ("savedPhoneContact", [("phone", ConstructorParameterDescription(_data.phone)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SavedContact.savedPhoneContact(Cons_savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum SavedDialog: TypeConstructorDescription { - public class Cons_monoForumDialog: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var topMessage: Int32 - public var readInboxMaxId: Int32 - public var readOutboxMaxId: Int32 - public var unreadCount: Int32 - public var unreadReactionsCount: Int32 - public var draft: Api.DraftMessage? - public init(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadReactionsCount: Int32, draft: Api.DraftMessage?) { - self.flags = flags - self.peer = peer - self.topMessage = topMessage - self.readInboxMaxId = readInboxMaxId - self.readOutboxMaxId = readOutboxMaxId - self.unreadCount = unreadCount - self.unreadReactionsCount = unreadReactionsCount - self.draft = draft - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("monoForumDialog", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMessage", ConstructorParameterDescription(self.topMessage)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("unreadReactionsCount", ConstructorParameterDescription(self.unreadReactionsCount)), ("draft", ConstructorParameterDescription(self.draft))]) - } - } - public class Cons_savedDialog: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var topMessage: Int32 - public init(flags: Int32, peer: Api.Peer, topMessage: Int32) { - self.flags = flags - self.peer = peer - self.topMessage = topMessage - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedDialog", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMessage", ConstructorParameterDescription(self.topMessage))]) - } - } - case monoForumDialog(Cons_monoForumDialog) - case savedDialog(Cons_savedDialog) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .monoForumDialog(let _data): - if boxed { - buffer.appendInt32(1681948327) + buffer.appendInt32(-1952185372) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.topMessage, buffer: buffer, boxed: false) - serializeInt32(_data.readInboxMaxId, buffer: buffer, boxed: false) - serializeInt32(_data.readOutboxMaxId, buffer: buffer, boxed: false) - serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) - serializeInt32(_data.unreadReactionsCount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.draft!.serialize(buffer, true) - } - break - case .savedDialog(let _data): - if boxed { - buffer.appendInt32(-1115174036) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.topMessage, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .monoForumDialog(let _data): - return ("monoForumDialog", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMessage", ConstructorParameterDescription(_data.topMessage)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("unreadReactionsCount", ConstructorParameterDescription(_data.unreadReactionsCount)), ("draft", ConstructorParameterDescription(_data.draft))]) - case .savedDialog(let _data): - return ("savedDialog", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMessage", ConstructorParameterDescription(_data.topMessage))]) - } - } - - public static func parse_monoForumDialog(_ reader: BufferReader) -> SavedDialog? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Api.DraftMessage? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.DraftMessage - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.SavedDialog.monoForumDialog(Cons_monoForumDialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadReactionsCount: _7!, draft: _8)) - } - else { - return nil - } - } - public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SavedDialog.savedDialog(Cons_savedDialog(flags: _1!, peer: _2!, topMessage: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SavedReactionTag: TypeConstructorDescription { - public class Cons_savedReactionTag: TypeConstructorDescription { - public var flags: Int32 - public var reaction: Api.Reaction - public var title: String? - public var count: Int32 - public init(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32) { - self.flags = flags - self.reaction = reaction - self.title = title - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedReactionTag", [("flags", ConstructorParameterDescription(self.flags)), ("reaction", ConstructorParameterDescription(self.reaction)), ("title", ConstructorParameterDescription(self.title)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case savedReactionTag(Cons_savedReactionTag) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedReactionTag(let _data): - if boxed { - buffer.appendInt32(-881854424) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.reaction.serialize(buffer, true) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 0) != 0 { serializeString(_data.title!, buffer: buffer, boxed: false) } - serializeInt32(_data.count, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.username!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.photo!.serialize(buffer, true) + } break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedReactionTag(let _data): - return ("savedReactionTag", [("flags", ConstructorParameterDescription(_data.flags)), ("reaction", ConstructorParameterDescription(_data.reaction)), ("title", ConstructorParameterDescription(_data.title)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Reaction? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Reaction - } - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SavedReactionTag.savedReactionTag(Cons_savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SavedStarGift: TypeConstructorDescription { - public class Cons_savedStarGift: TypeConstructorDescription { - public var flags: Int32 - public var fromId: Api.Peer? - public var date: Int32 - public var gift: Api.StarGift - public var message: Api.TextWithEntities? - public var msgId: Int32? - public var savedId: Int64? - public var convertStars: Int64? - public var upgradeStars: Int64? - public var canExportAt: Int32? - public var transferStars: Int64? - public var canTransferAt: Int32? - public var canResellAt: Int32? - public var collectionId: [Int32]? - public var prepaidUpgradeHash: String? - public var dropOriginalDetailsStars: Int64? - public var giftNum: Int32? - public var canCraftAt: Int32? - public init(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?, dropOriginalDetailsStars: Int64?, giftNum: Int32?, canCraftAt: Int32?) { - self.flags = flags - self.fromId = fromId - self.date = date - self.gift = gift - self.message = message - self.msgId = msgId - self.savedId = savedId - self.convertStars = convertStars - self.upgradeStars = upgradeStars - self.canExportAt = canExportAt - self.transferStars = transferStars - self.canTransferAt = canTransferAt - self.canResellAt = canResellAt - self.collectionId = collectionId - self.prepaidUpgradeHash = prepaidUpgradeHash - self.dropOriginalDetailsStars = dropOriginalDetailsStars - self.giftNum = giftNum - self.canCraftAt = canCraftAt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedStarGift", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("date", ConstructorParameterDescription(self.date)), ("gift", ConstructorParameterDescription(self.gift)), ("message", ConstructorParameterDescription(self.message)), ("msgId", ConstructorParameterDescription(self.msgId)), ("savedId", ConstructorParameterDescription(self.savedId)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("canExportAt", ConstructorParameterDescription(self.canExportAt)), ("transferStars", ConstructorParameterDescription(self.transferStars)), ("canTransferAt", ConstructorParameterDescription(self.canTransferAt)), ("canResellAt", ConstructorParameterDescription(self.canResellAt)), ("collectionId", ConstructorParameterDescription(self.collectionId)), ("prepaidUpgradeHash", ConstructorParameterDescription(self.prepaidUpgradeHash)), ("dropOriginalDetailsStars", ConstructorParameterDescription(self.dropOriginalDetailsStars)), ("giftNum", ConstructorParameterDescription(self.giftNum)), ("canCraftAt", ConstructorParameterDescription(self.canCraftAt))]) - } - } - case savedStarGift(Cons_savedStarGift) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedStarGift(let _data): + case .requestedPeerChat(let _data): if boxed { - buffer.appendInt32(1105150972) + buffer.appendInt32(1929860175) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.fromId!.serialize(buffer, true) + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) } - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.gift.serialize(buffer, true) if Int(_data.flags) & Int(1 << 2) != 0 { - _data.message!.serialize(buffer, true) + _data.photo!.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.msgId!, buffer: buffer, boxed: false) + break + case .requestedPeerUser(let _data): + if boxed { + buffer.appendInt32(-701500310) } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt64(_data.savedId!, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.firstName!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.convertStars!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.lastName!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.username!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeInt32(_data.canExportAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt64(_data.transferStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - serializeInt32(_data.canTransferAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - serializeInt32(_data.canResellAt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.collectionId!.count)) - for item in _data.collectionId! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeString(_data.prepaidUpgradeHash!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 18) != 0 { - serializeInt64(_data.dropOriginalDetailsStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 19) != 0 { - serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 20) != 0 { - serializeInt32(_data.canCraftAt!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.photo!.serialize(buffer, true) } break } @@ -965,106 +1057,103 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .savedStarGift(let _data): - return ("savedStarGift", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("date", ConstructorParameterDescription(_data.date)), ("gift", ConstructorParameterDescription(_data.gift)), ("message", ConstructorParameterDescription(_data.message)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("canExportAt", ConstructorParameterDescription(_data.canExportAt)), ("transferStars", ConstructorParameterDescription(_data.transferStars)), ("canTransferAt", ConstructorParameterDescription(_data.canTransferAt)), ("canResellAt", ConstructorParameterDescription(_data.canResellAt)), ("collectionId", ConstructorParameterDescription(_data.collectionId)), ("prepaidUpgradeHash", ConstructorParameterDescription(_data.prepaidUpgradeHash)), ("dropOriginalDetailsStars", ConstructorParameterDescription(_data.dropOriginalDetailsStars)), ("giftNum", ConstructorParameterDescription(_data.giftNum)), ("canCraftAt", ConstructorParameterDescription(_data.canCraftAt))]) + case .requestedPeerChannel(let _data): + return ("requestedPeerChannel", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("title", ConstructorParameterDescription(_data.title)), ("username", ConstructorParameterDescription(_data.username)), ("photo", ConstructorParameterDescription(_data.photo))]) + case .requestedPeerChat(let _data): + return ("requestedPeerChat", [("flags", ConstructorParameterDescription(_data.flags)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("title", ConstructorParameterDescription(_data.title)), ("photo", ConstructorParameterDescription(_data.photo))]) + case .requestedPeerUser(let _data): + return ("requestedPeerUser", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("username", ConstructorParameterDescription(_data.username)), ("photo", ConstructorParameterDescription(_data.photo))]) } } - public static func parse_savedStarGift(_ reader: BufferReader) -> SavedStarGift? { + public static func parse_requestedPeerChannel(_ reader: BufferReader) -> RequestedPeer? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer + _5 = Api.parse(reader, signature: signature) as? Api.Photo } } - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.StarGift? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _5: Api.TextWithEntities? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - var _6: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _6 = reader.readInt32() - } - var _7: Int64? - if Int(_1!) & Int(1 << 11) != 0 { - _7 = reader.readInt64() - } - var _8: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt64() - } - var _9: Int64? - if Int(_1!) & Int(1 << 6) != 0 { - _9 = reader.readInt64() - } - var _10: Int32? - if Int(_1!) & Int(1 << 7) != 0 { - _10 = reader.readInt32() - } - var _11: Int64? - if Int(_1!) & Int(1 << 8) != 0 { - _11 = reader.readInt64() - } - var _12: Int32? - if Int(_1!) & Int(1 << 13) != 0 { - _12 = reader.readInt32() - } - var _13: Int32? - if Int(_1!) & Int(1 << 14) != 0 { - _13 = reader.readInt32() - } - var _14: [Int32]? - if Int(_1!) & Int(1 << 15) != 0 { - if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - var _15: String? - if Int(_1!) & Int(1 << 16) != 0 { - _15 = parseString(reader) - } - var _16: Int64? - if Int(_1!) & Int(1 << 18) != 0 { - _16 = reader.readInt64() - } - var _17: Int32? - if Int(_1!) & Int(1 << 19) != 0 { - _17 = reader.readInt32() - } - var _18: Int32? - if Int(_1!) & Int(1 << 20) != 0 { - _18 = reader.readInt32() - } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 13) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 14) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 19) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 20) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.SavedStarGift.savedStarGift(Cons_savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16, giftNum: _17, canCraftAt: _18)) + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.RequestedPeer.requestedPeerChannel(Cons_requestedPeerChannel(flags: _1!, channelId: _2!, title: _3, username: _4, photo: _5)) + } + else { + return nil + } + } + public static func parse_requestedPeerChat(_ reader: BufferReader) -> RequestedPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.RequestedPeer.requestedPeerChat(Cons_requestedPeerChat(flags: _1!, chatId: _2!, title: _3, photo: _4)) + } + else { + return nil + } + } + public static func parse_requestedPeerUser(_ reader: BufferReader) -> RequestedPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.RequestedPeer.requestedPeerUser(Cons_requestedPeerUser(flags: _1!, userId: _2!, firstName: _3, lastName: _4, username: _5, photo: _6)) } else { return nil @@ -1073,350 +1162,36 @@ public extension Api { } } public extension Api { - enum SearchPostsFlood: TypeConstructorDescription { - public class Cons_searchPostsFlood: TypeConstructorDescription { - public var flags: Int32 - public var totalDaily: Int32 - public var remains: Int32 - public var waitTill: Int32? + enum RequirementToContact: TypeConstructorDescription { + public class Cons_requirementToContactPaidMessages: TypeConstructorDescription { public var starsAmount: Int64 - public init(flags: Int32, totalDaily: Int32, remains: Int32, waitTill: Int32?, starsAmount: Int64) { - self.flags = flags - self.totalDaily = totalDaily - self.remains = remains - self.waitTill = waitTill + public init(starsAmount: Int64) { self.starsAmount = starsAmount } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchPostsFlood", [("flags", ConstructorParameterDescription(self.flags)), ("totalDaily", ConstructorParameterDescription(self.totalDaily)), ("remains", ConstructorParameterDescription(self.remains)), ("waitTill", ConstructorParameterDescription(self.waitTill)), ("starsAmount", ConstructorParameterDescription(self.starsAmount))]) + return ("requirementToContactPaidMessages", [("starsAmount", ConstructorParameterDescription(self.starsAmount))]) } } - case searchPostsFlood(Cons_searchPostsFlood) + case requirementToContactEmpty + case requirementToContactPaidMessages(Cons_requirementToContactPaidMessages) + case requirementToContactPremium public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchPostsFlood(let _data): + case .requirementToContactEmpty: if boxed { - buffer.appendInt32(1040931690) + buffer.appendInt32(84580409) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.totalDaily, buffer: buffer, boxed: false) - serializeInt32(_data.remains, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.waitTill!, buffer: buffer, boxed: false) + break + case .requirementToContactPaidMessages(let _data): + if boxed { + buffer.appendInt32(-1258914157) } serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .searchPostsFlood(let _data): - return ("searchPostsFlood", [("flags", ConstructorParameterDescription(_data.flags)), ("totalDaily", ConstructorParameterDescription(_data.totalDaily)), ("remains", ConstructorParameterDescription(_data.remains)), ("waitTill", ConstructorParameterDescription(_data.waitTill)), ("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) - } - } - - public static func parse_searchPostsFlood(_ reader: BufferReader) -> SearchPostsFlood? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - var _5: Int64? - _5 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.SearchPostsFlood.searchPostsFlood(Cons_searchPostsFlood(flags: _1!, totalDaily: _2!, remains: _3!, waitTill: _4, starsAmount: _5!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SearchResultsCalendarPeriod: TypeConstructorDescription { - public class Cons_searchResultsCalendarPeriod: TypeConstructorDescription { - public var date: Int32 - public var minMsgId: Int32 - public var maxMsgId: Int32 - public var count: Int32 - public init(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) { - self.date = date - self.minMsgId = minMsgId - self.maxMsgId = maxMsgId - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchResultsCalendarPeriod", [("date", ConstructorParameterDescription(self.date)), ("minMsgId", ConstructorParameterDescription(self.minMsgId)), ("maxMsgId", ConstructorParameterDescription(self.maxMsgId)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case searchResultsCalendarPeriod(Cons_searchResultsCalendarPeriod) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .searchResultsCalendarPeriod(let _data): + case .requirementToContactPremium: if boxed { - buffer.appendInt32(-911191137) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.minMsgId, buffer: buffer, boxed: false) - serializeInt32(_data.maxMsgId, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .searchResultsCalendarPeriod(let _data): - return ("searchResultsCalendarPeriod", [("date", ConstructorParameterDescription(_data.date)), ("minMsgId", ConstructorParameterDescription(_data.minMsgId)), ("maxMsgId", ConstructorParameterDescription(_data.maxMsgId)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(Cons_searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SearchResultsPosition: TypeConstructorDescription { - public class Cons_searchResultPosition: TypeConstructorDescription { - public var msgId: Int32 - public var date: Int32 - public var offset: Int32 - public init(msgId: Int32, date: Int32, offset: Int32) { - self.msgId = msgId - self.date = date - self.offset = offset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchResultPosition", [("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("offset", ConstructorParameterDescription(self.offset))]) - } - } - case searchResultPosition(Cons_searchResultPosition) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .searchResultPosition(let _data): - if boxed { - buffer.appendInt32(2137295719) - } - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.offset, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .searchResultPosition(let _data): - return ("searchResultPosition", [("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("offset", ConstructorParameterDescription(_data.offset))]) - } - } - - public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SearchResultsPosition.searchResultPosition(Cons_searchResultPosition(msgId: _1!, date: _2!, offset: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SecureCredentialsEncrypted: TypeConstructorDescription { - public class Cons_secureCredentialsEncrypted: TypeConstructorDescription { - public var data: Buffer - public var hash: Buffer - public var secret: Buffer - public init(data: Buffer, hash: Buffer, secret: Buffer) { - self.data = data - self.hash = hash - self.secret = secret - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureCredentialsEncrypted", [("data", ConstructorParameterDescription(self.data)), ("hash", ConstructorParameterDescription(self.hash)), ("secret", ConstructorParameterDescription(self.secret))]) - } - } - case secureCredentialsEncrypted(Cons_secureCredentialsEncrypted) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureCredentialsEncrypted(let _data): - if boxed { - buffer.appendInt32(871426631) - } - serializeBytes(_data.data, buffer: buffer, boxed: false) - serializeBytes(_data.hash, buffer: buffer, boxed: false) - serializeBytes(_data.secret, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .secureCredentialsEncrypted(let _data): - return ("secureCredentialsEncrypted", [("data", ConstructorParameterDescription(_data.data)), ("hash", ConstructorParameterDescription(_data.hash)), ("secret", ConstructorParameterDescription(_data.secret))]) - } - } - - public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(Cons_secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SecureData: TypeConstructorDescription { - public class Cons_secureData: TypeConstructorDescription { - public var data: Buffer - public var dataHash: Buffer - public var secret: Buffer - public init(data: Buffer, dataHash: Buffer, secret: Buffer) { - self.data = data - self.dataHash = dataHash - self.secret = secret - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureData", [("data", ConstructorParameterDescription(self.data)), ("dataHash", ConstructorParameterDescription(self.dataHash)), ("secret", ConstructorParameterDescription(self.secret))]) - } - } - case secureData(Cons_secureData) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureData(let _data): - if boxed { - buffer.appendInt32(-1964327229) - } - serializeBytes(_data.data, buffer: buffer, boxed: false) - serializeBytes(_data.dataHash, buffer: buffer, boxed: false) - serializeBytes(_data.secret, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .secureData(let _data): - return ("secureData", [("data", ConstructorParameterDescription(_data.data)), ("dataHash", ConstructorParameterDescription(_data.dataHash)), ("secret", ConstructorParameterDescription(_data.secret))]) - } - } - - public static func parse_secureData(_ reader: BufferReader) -> SecureData? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureData.secureData(Cons_secureData(data: _1!, dataHash: _2!, secret: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SecureFile: TypeConstructorDescription { - public class Cons_secureFile: TypeConstructorDescription { - public var id: Int64 - public var accessHash: Int64 - public var size: Int64 - public var dcId: Int32 - public var date: Int32 - public var fileHash: Buffer - public var secret: Buffer - public init(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer) { - self.id = id - self.accessHash = accessHash - self.size = size - self.dcId = dcId - self.date = date - self.fileHash = fileHash - self.secret = secret - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureFile", [("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("size", ConstructorParameterDescription(self.size)), ("dcId", ConstructorParameterDescription(self.dcId)), ("date", ConstructorParameterDescription(self.date)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("secret", ConstructorParameterDescription(self.secret))]) - } - } - case secureFile(Cons_secureFile) - case secureFileEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureFile(let _data): - if boxed { - buffer.appendInt32(2097791614) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt64(_data.size, buffer: buffer, boxed: false) - serializeInt32(_data.dcId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeBytes(_data.secret, buffer: buffer, boxed: false) - break - case .secureFileEmpty: - if boxed { - buffer.appendInt32(1679398724) + buffer.appendInt32(-444472087) } break } @@ -1424,128 +1199,31 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .secureFile(let _data): - return ("secureFile", [("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("size", ConstructorParameterDescription(_data.size)), ("dcId", ConstructorParameterDescription(_data.dcId)), ("date", ConstructorParameterDescription(_data.date)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("secret", ConstructorParameterDescription(_data.secret))]) - case .secureFileEmpty: - return ("secureFileEmpty", []) + case .requirementToContactEmpty: + return ("requirementToContactEmpty", []) + case .requirementToContactPaidMessages(let _data): + return ("requirementToContactPaidMessages", [("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) + case .requirementToContactPremium: + return ("requirementToContactPremium", []) } } - public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? { + public static func parse_requirementToContactEmpty(_ reader: BufferReader) -> RequirementToContact? { + return Api.RequirementToContact.requirementToContactEmpty + } + public static func parse_requirementToContactPaidMessages(_ reader: BufferReader) -> RequirementToContact? { var _1: Int64? _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.SecureFile.secureFile(Cons_secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!)) + if _c1 { + return Api.RequirementToContact.requirementToContactPaidMessages(Cons_requirementToContactPaidMessages(starsAmount: _1!)) } else { return nil } } - public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? { - return Api.SecureFile.secureFileEmpty - } - } -} -public extension Api { - enum SecurePasswordKdfAlgo: TypeConstructorDescription { - public class Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000: TypeConstructorDescription { - public var salt: Buffer - public init(salt: Buffer) { - self.salt = salt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", ConstructorParameterDescription(self.salt))]) - } - } - public class Cons_securePasswordKdfAlgoSHA512: TypeConstructorDescription { - public var salt: Buffer - public init(salt: Buffer) { - self.salt = salt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("securePasswordKdfAlgoSHA512", [("salt", ConstructorParameterDescription(self.salt))]) - } - } - case securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000) - case securePasswordKdfAlgoSHA512(Cons_securePasswordKdfAlgoSHA512) - case securePasswordKdfAlgoUnknown - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let _data): - if boxed { - buffer.appendInt32(-1141711456) - } - serializeBytes(_data.salt, buffer: buffer, boxed: false) - break - case .securePasswordKdfAlgoSHA512(let _data): - if boxed { - buffer.appendInt32(-2042159726) - } - serializeBytes(_data.salt, buffer: buffer, boxed: false) - break - case .securePasswordKdfAlgoUnknown: - if boxed { - buffer.appendInt32(4883767) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let _data): - return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", ConstructorParameterDescription(_data.salt))]) - case .securePasswordKdfAlgoSHA512(let _data): - return ("securePasswordKdfAlgoSHA512", [("salt", ConstructorParameterDescription(_data.salt))]) - case .securePasswordKdfAlgoUnknown: - return ("securePasswordKdfAlgoUnknown", []) - } - } - - public static func parse_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!)) - } - else { - return nil - } - } - public static func parse_securePasswordKdfAlgoSHA512(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(Cons_securePasswordKdfAlgoSHA512(salt: _1!)) - } - else { - return nil - } - } - public static func parse_securePasswordKdfAlgoUnknown(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { - return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoUnknown + public static func parse_requirementToContactPremium(_ reader: BufferReader) -> RequirementToContact? { + return Api.RequirementToContact.requirementToContactPremium } } } diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index a72b6cde79..7aafb67abf 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,69 +1,52 @@ public extension Api { - enum SecurePlainData: TypeConstructorDescription { - public class Cons_securePlainEmail: TypeConstructorDescription { - public var email: String - public init(email: String) { - self.email = email + enum RestrictionReason: TypeConstructorDescription { + public class Cons_restrictionReason: TypeConstructorDescription { + public var platform: String + public var reason: String + public var text: String + public init(platform: String, reason: String, text: String) { + self.platform = platform + self.reason = reason + self.text = text } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("securePlainEmail", [("email", ConstructorParameterDescription(self.email))]) + return ("restrictionReason", [("platform", ConstructorParameterDescription(self.platform)), ("reason", ConstructorParameterDescription(self.reason)), ("text", ConstructorParameterDescription(self.text))]) } } - public class Cons_securePlainPhone: TypeConstructorDescription { - public var phone: String - public init(phone: String) { - self.phone = phone - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("securePlainPhone", [("phone", ConstructorParameterDescription(self.phone))]) - } - } - case securePlainEmail(Cons_securePlainEmail) - case securePlainPhone(Cons_securePlainPhone) + case restrictionReason(Cons_restrictionReason) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .securePlainEmail(let _data): + case .restrictionReason(let _data): if boxed { - buffer.appendInt32(569137759) + buffer.appendInt32(-797791052) } - serializeString(_data.email, buffer: buffer, boxed: false) - break - case .securePlainPhone(let _data): - if boxed { - buffer.appendInt32(2103482845) - } - serializeString(_data.phone, buffer: buffer, boxed: false) + serializeString(_data.platform, buffer: buffer, boxed: false) + serializeString(_data.reason, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .securePlainEmail(let _data): - return ("securePlainEmail", [("email", ConstructorParameterDescription(_data.email))]) - case .securePlainPhone(let _data): - return ("securePlainPhone", [("phone", ConstructorParameterDescription(_data.phone))]) + case .restrictionReason(let _data): + return ("restrictionReason", [("platform", ConstructorParameterDescription(_data.platform)), ("reason", ConstructorParameterDescription(_data.reason)), ("text", ConstructorParameterDescription(_data.text))]) } } - public static func parse_securePlainEmail(_ reader: BufferReader) -> SecurePlainData? { + public static func parse_restrictionReason(_ reader: BufferReader) -> RestrictionReason? { var _1: String? _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainEmail(Cons_securePlainEmail(email: _1!)) - } - else { - return nil - } - } - public static func parse_securePlainPhone(_ reader: BufferReader) -> SecurePlainData? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.SecurePlainData.securePlainPhone(Cons_securePlainPhone(phone: _1!)) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.RestrictionReason.restrictionReason(Cons_restrictionReason(platform: _1!, reason: _2!, text: _3!)) } else { return nil @@ -72,46 +55,44 @@ public extension Api { } } public extension Api { - enum SecureRequiredType: TypeConstructorDescription { - public class Cons_secureRequiredType: TypeConstructorDescription { + enum RichMessage: TypeConstructorDescription { + public class Cons_richMessage: TypeConstructorDescription { public var flags: Int32 - public var type: Api.SecureValueType - public init(flags: Int32, type: Api.SecureValueType) { + public var blocks: [Api.PageBlock] + public var photos: [Api.Photo] + public var documents: [Api.Document] + public init(flags: Int32, blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document]) { self.flags = flags - self.type = type + self.blocks = blocks + self.photos = photos + self.documents = documents } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureRequiredType", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type))]) + return ("richMessage", [("flags", ConstructorParameterDescription(self.flags)), ("blocks", ConstructorParameterDescription(self.blocks)), ("photos", ConstructorParameterDescription(self.photos)), ("documents", ConstructorParameterDescription(self.documents))]) } } - public class Cons_secureRequiredTypeOneOf: TypeConstructorDescription { - public var types: [Api.SecureRequiredType] - public init(types: [Api.SecureRequiredType]) { - self.types = types - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureRequiredTypeOneOf", [("types", ConstructorParameterDescription(self.types))]) - } - } - case secureRequiredType(Cons_secureRequiredType) - case secureRequiredTypeOneOf(Cons_secureRequiredTypeOneOf) + case richMessage(Cons_richMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureRequiredType(let _data): + case .richMessage(let _data): if boxed { - buffer.appendInt32(-2103600678) + buffer.appendInt32(-1158439541) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.type.serialize(buffer, true) - break - case .secureRequiredTypeOneOf(let _data): - if boxed { - buffer.appendInt32(41187252) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.types.count)) - for item in _data.types { + buffer.appendInt32(Int32(_data.photos.count)) + for item in _data.photos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { item.serialize(buffer, true) } break @@ -120,37 +101,32 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .secureRequiredType(let _data): - return ("secureRequiredType", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type))]) - case .secureRequiredTypeOneOf(let _data): - return ("secureRequiredTypeOneOf", [("types", ConstructorParameterDescription(_data.types))]) + case .richMessage(let _data): + return ("richMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("photos", ConstructorParameterDescription(_data.photos)), ("documents", ConstructorParameterDescription(_data.documents))]) } } - public static func parse_secureRequiredType(_ reader: BufferReader) -> SecureRequiredType? { + public static func parse_richMessage(_ reader: BufferReader) -> RichMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.SecureValueType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType + var _2: [Api.PageBlock]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PageBlock.self) + } + var _3: [Api.Photo]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureRequiredType.secureRequiredType(Cons_secureRequiredType(flags: _1!, type: _2!)) - } - else { - return nil - } - } - public static func parse_secureRequiredTypeOneOf(_ reader: BufferReader) -> SecureRequiredType? { - var _1: [Api.SecureRequiredType]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.SecureRequiredType.secureRequiredTypeOneOf(Cons_secureRequiredTypeOneOf(types: _1!)) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.RichMessage.richMessage(Cons_richMessage(flags: _1!, blocks: _2!, photos: _3!, documents: _4!)) } else { return nil @@ -159,56 +135,954 @@ public extension Api { } } public extension Api { - enum SecureSecretSettings: TypeConstructorDescription { - public class Cons_secureSecretSettings: TypeConstructorDescription { - public var secureAlgo: Api.SecurePasswordKdfAlgo - public var secureSecret: Buffer - public var secureSecretId: Int64 - public init(secureAlgo: Api.SecurePasswordKdfAlgo, secureSecret: Buffer, secureSecretId: Int64) { - self.secureAlgo = secureAlgo - self.secureSecret = secureSecret - self.secureSecretId = secureSecretId + indirect enum RichText: TypeConstructorDescription { + public class Cons_textAnchor: TypeConstructorDescription { + public var text: Api.RichText + public var name: String + public init(text: Api.RichText, name: String) { + self.text = text + self.name = name } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureSecretSettings", [("secureAlgo", ConstructorParameterDescription(self.secureAlgo)), ("secureSecret", ConstructorParameterDescription(self.secureSecret)), ("secureSecretId", ConstructorParameterDescription(self.secureSecretId))]) + return ("textAnchor", [("text", ConstructorParameterDescription(self.text)), ("name", ConstructorParameterDescription(self.name))]) } } - case secureSecretSettings(Cons_secureSecretSettings) + public class Cons_textAutoEmail: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textAutoEmail", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textAutoPhone: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textAutoPhone", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textAutoUrl: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textAutoUrl", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textBankCard: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textBankCard", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textBold: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textBold", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textBotCommand: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textBotCommand", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textCashtag: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textCashtag", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textConcat: TypeConstructorDescription { + public var texts: [Api.RichText] + public init(texts: [Api.RichText]) { + self.texts = texts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textConcat", [("texts", ConstructorParameterDescription(self.texts))]) + } + } + public class Cons_textCustomEmoji: TypeConstructorDescription { + public var documentId: Int64 + public var alt: String + public init(documentId: Int64, alt: String) { + self.documentId = documentId + self.alt = alt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textCustomEmoji", [("documentId", ConstructorParameterDescription(self.documentId)), ("alt", ConstructorParameterDescription(self.alt))]) + } + } + public class Cons_textDate: TypeConstructorDescription { + public var flags: Int32 + public var text: Api.RichText + public var date: Int32 + public init(flags: Int32, text: Api.RichText, date: Int32) { + self.flags = flags + self.text = text + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textDate", [("flags", ConstructorParameterDescription(self.flags)), ("text", ConstructorParameterDescription(self.text)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_textEmail: TypeConstructorDescription { + public var text: Api.RichText + public var email: String + public init(text: Api.RichText, email: String) { + self.text = text + self.email = email + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textEmail", [("text", ConstructorParameterDescription(self.text)), ("email", ConstructorParameterDescription(self.email))]) + } + } + public class Cons_textFixed: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textFixed", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textHashtag: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textHashtag", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textImage: TypeConstructorDescription { + public var documentId: Int64 + public var w: Int32 + public var h: Int32 + public init(documentId: Int64, w: Int32, h: Int32) { + self.documentId = documentId + self.w = w + self.h = h + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textImage", [("documentId", ConstructorParameterDescription(self.documentId)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h))]) + } + } + public class Cons_textItalic: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textItalic", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textMarked: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textMarked", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textMath: TypeConstructorDescription { + public var source: String + public init(source: String) { + self.source = source + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textMath", [("source", ConstructorParameterDescription(self.source))]) + } + } + public class Cons_textMention: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textMention", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textMentionName: TypeConstructorDescription { + public var text: Api.RichText + public var userId: Int64 + public init(text: Api.RichText, userId: Int64) { + self.text = text + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textMentionName", [("text", ConstructorParameterDescription(self.text)), ("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_textPhone: TypeConstructorDescription { + public var text: Api.RichText + public var phone: String + public init(text: Api.RichText, phone: String) { + self.text = text + self.phone = phone + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textPhone", [("text", ConstructorParameterDescription(self.text)), ("phone", ConstructorParameterDescription(self.phone))]) + } + } + public class Cons_textPlain: TypeConstructorDescription { + public var text: String + public init(text: String) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textPlain", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textSpoiler: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textSpoiler", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textStrike: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textStrike", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textSubscript: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textSubscript", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textSuperscript: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textSuperscript", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textUnderline: TypeConstructorDescription { + public var text: Api.RichText + public init(text: Api.RichText) { + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textUnderline", [("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_textUrl: TypeConstructorDescription { + public var text: Api.RichText + public var url: String + public var webpageId: Int64 + public init(text: Api.RichText, url: String, webpageId: Int64) { + self.text = text + self.url = url + self.webpageId = webpageId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textUrl", [("text", ConstructorParameterDescription(self.text)), ("url", ConstructorParameterDescription(self.url)), ("webpageId", ConstructorParameterDescription(self.webpageId))]) + } + } + case textAnchor(Cons_textAnchor) + case textAutoEmail(Cons_textAutoEmail) + case textAutoPhone(Cons_textAutoPhone) + case textAutoUrl(Cons_textAutoUrl) + case textBankCard(Cons_textBankCard) + case textBold(Cons_textBold) + case textBotCommand(Cons_textBotCommand) + case textCashtag(Cons_textCashtag) + case textConcat(Cons_textConcat) + case textCustomEmoji(Cons_textCustomEmoji) + case textDate(Cons_textDate) + case textEmail(Cons_textEmail) + case textEmpty + case textFixed(Cons_textFixed) + case textHashtag(Cons_textHashtag) + case textImage(Cons_textImage) + case textItalic(Cons_textItalic) + case textMarked(Cons_textMarked) + case textMath(Cons_textMath) + case textMention(Cons_textMention) + case textMentionName(Cons_textMentionName) + case textPhone(Cons_textPhone) + case textPlain(Cons_textPlain) + case textSpoiler(Cons_textSpoiler) + case textStrike(Cons_textStrike) + case textSubscript(Cons_textSubscript) + case textSuperscript(Cons_textSuperscript) + case textUnderline(Cons_textUnderline) + case textUrl(Cons_textUrl) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureSecretSettings(let _data): + case .textAnchor(let _data): if boxed { - buffer.appendInt32(354925740) + buffer.appendInt32(894777186) } - _data.secureAlgo.serialize(buffer, true) - serializeBytes(_data.secureSecret, buffer: buffer, boxed: false) - serializeInt64(_data.secureSecretId, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + serializeString(_data.name, buffer: buffer, boxed: false) + break + case .textAutoEmail(let _data): + if boxed { + buffer.appendInt32(-984177571) + } + _data.text.serialize(buffer, true) + break + case .textAutoPhone(let _data): + if boxed { + buffer.appendInt32(616720265) + } + _data.text.serialize(buffer, true) + break + case .textAutoUrl(let _data): + if boxed { + buffer.appendInt32(-1402305622) + } + _data.text.serialize(buffer, true) + break + case .textBankCard(let _data): + if boxed { + buffer.appendInt32(-1185513171) + } + _data.text.serialize(buffer, true) + break + case .textBold(let _data): + if boxed { + buffer.appendInt32(1730456516) + } + _data.text.serialize(buffer, true) + break + case .textBotCommand(let _data): + if boxed { + buffer.appendInt32(50276819) + } + _data.text.serialize(buffer, true) + break + case .textCashtag(let _data): + if boxed { + buffer.appendInt32(2073958401) + } + _data.text.serialize(buffer, true) + break + case .textConcat(let _data): + if boxed { + buffer.appendInt32(2120376535) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.texts.count)) + for item in _data.texts { + item.serialize(buffer, true) + } + break + case .textCustomEmoji(let _data): + if boxed { + buffer.appendInt32(-1570679104) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + serializeString(_data.alt, buffer: buffer, boxed: false) + break + case .textDate(let _data): + if boxed { + buffer.appendInt32(-1514906069) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .textEmail(let _data): + if boxed { + buffer.appendInt32(-564523562) + } + _data.text.serialize(buffer, true) + serializeString(_data.email, buffer: buffer, boxed: false) + break + case .textEmpty: + if boxed { + buffer.appendInt32(-599948721) + } + break + case .textFixed(let _data): + if boxed { + buffer.appendInt32(1816074681) + } + _data.text.serialize(buffer, true) + break + case .textHashtag(let _data): + if boxed { + buffer.appendInt32(1368728810) + } + _data.text.serialize(buffer, true) + break + case .textImage(let _data): + if boxed { + buffer.appendInt32(136105807) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + break + case .textItalic(let _data): + if boxed { + buffer.appendInt32(-653089380) + } + _data.text.serialize(buffer, true) + break + case .textMarked(let _data): + if boxed { + buffer.appendInt32(55281185) + } + _data.text.serialize(buffer, true) + break + case .textMath(let _data): + if boxed { + buffer.appendInt32(-1657885545) + } + serializeString(_data.source, buffer: buffer, boxed: false) + break + case .textMention(let _data): + if boxed { + buffer.appendInt32(-853225660) + } + _data.text.serialize(buffer, true) + break + case .textMentionName(let _data): + if boxed { + buffer.appendInt32(27917308) + } + _data.text.serialize(buffer, true) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .textPhone(let _data): + if boxed { + buffer.appendInt32(483104362) + } + _data.text.serialize(buffer, true) + serializeString(_data.phone, buffer: buffer, boxed: false) + break + case .textPlain(let _data): + if boxed { + buffer.appendInt32(1950782688) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .textSpoiler(let _data): + if boxed { + buffer.appendInt32(1277844834) + } + _data.text.serialize(buffer, true) + break + case .textStrike(let _data): + if boxed { + buffer.appendInt32(-1678197867) + } + _data.text.serialize(buffer, true) + break + case .textSubscript(let _data): + if boxed { + buffer.appendInt32(-311786236) + } + _data.text.serialize(buffer, true) + break + case .textSuperscript(let _data): + if boxed { + buffer.appendInt32(-939827711) + } + _data.text.serialize(buffer, true) + break + case .textUnderline(let _data): + if boxed { + buffer.appendInt32(-1054465340) + } + _data.text.serialize(buffer, true) + break + case .textUrl(let _data): + if boxed { + buffer.appendInt32(1009288385) + } + _data.text.serialize(buffer, true) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.webpageId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .secureSecretSettings(let _data): - return ("secureSecretSettings", [("secureAlgo", ConstructorParameterDescription(_data.secureAlgo)), ("secureSecret", ConstructorParameterDescription(_data.secureSecret)), ("secureSecretId", ConstructorParameterDescription(_data.secureSecretId))]) + case .textAnchor(let _data): + return ("textAnchor", [("text", ConstructorParameterDescription(_data.text)), ("name", ConstructorParameterDescription(_data.name))]) + case .textAutoEmail(let _data): + return ("textAutoEmail", [("text", ConstructorParameterDescription(_data.text))]) + case .textAutoPhone(let _data): + return ("textAutoPhone", [("text", ConstructorParameterDescription(_data.text))]) + case .textAutoUrl(let _data): + return ("textAutoUrl", [("text", ConstructorParameterDescription(_data.text))]) + case .textBankCard(let _data): + return ("textBankCard", [("text", ConstructorParameterDescription(_data.text))]) + case .textBold(let _data): + return ("textBold", [("text", ConstructorParameterDescription(_data.text))]) + case .textBotCommand(let _data): + return ("textBotCommand", [("text", ConstructorParameterDescription(_data.text))]) + case .textCashtag(let _data): + return ("textCashtag", [("text", ConstructorParameterDescription(_data.text))]) + case .textConcat(let _data): + return ("textConcat", [("texts", ConstructorParameterDescription(_data.texts))]) + case .textCustomEmoji(let _data): + return ("textCustomEmoji", [("documentId", ConstructorParameterDescription(_data.documentId)), ("alt", ConstructorParameterDescription(_data.alt))]) + case .textDate(let _data): + return ("textDate", [("flags", ConstructorParameterDescription(_data.flags)), ("text", ConstructorParameterDescription(_data.text)), ("date", ConstructorParameterDescription(_data.date))]) + case .textEmail(let _data): + return ("textEmail", [("text", ConstructorParameterDescription(_data.text)), ("email", ConstructorParameterDescription(_data.email))]) + case .textEmpty: + return ("textEmpty", []) + case .textFixed(let _data): + return ("textFixed", [("text", ConstructorParameterDescription(_data.text))]) + case .textHashtag(let _data): + return ("textHashtag", [("text", ConstructorParameterDescription(_data.text))]) + case .textImage(let _data): + return ("textImage", [("documentId", ConstructorParameterDescription(_data.documentId)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h))]) + case .textItalic(let _data): + return ("textItalic", [("text", ConstructorParameterDescription(_data.text))]) + case .textMarked(let _data): + return ("textMarked", [("text", ConstructorParameterDescription(_data.text))]) + case .textMath(let _data): + return ("textMath", [("source", ConstructorParameterDescription(_data.source))]) + case .textMention(let _data): + return ("textMention", [("text", ConstructorParameterDescription(_data.text))]) + case .textMentionName(let _data): + return ("textMentionName", [("text", ConstructorParameterDescription(_data.text)), ("userId", ConstructorParameterDescription(_data.userId))]) + case .textPhone(let _data): + return ("textPhone", [("text", ConstructorParameterDescription(_data.text)), ("phone", ConstructorParameterDescription(_data.phone))]) + case .textPlain(let _data): + return ("textPlain", [("text", ConstructorParameterDescription(_data.text))]) + case .textSpoiler(let _data): + return ("textSpoiler", [("text", ConstructorParameterDescription(_data.text))]) + case .textStrike(let _data): + return ("textStrike", [("text", ConstructorParameterDescription(_data.text))]) + case .textSubscript(let _data): + return ("textSubscript", [("text", ConstructorParameterDescription(_data.text))]) + case .textSuperscript(let _data): + return ("textSuperscript", [("text", ConstructorParameterDescription(_data.text))]) + case .textUnderline(let _data): + return ("textUnderline", [("text", ConstructorParameterDescription(_data.text))]) + case .textUrl(let _data): + return ("textUrl", [("text", ConstructorParameterDescription(_data.text)), ("url", ConstructorParameterDescription(_data.url)), ("webpageId", ConstructorParameterDescription(_data.webpageId))]) } } - public static func parse_secureSecretSettings(_ reader: BufferReader) -> SecureSecretSettings? { - var _1: Api.SecurePasswordKdfAlgo? + public static func parse_textAnchor(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo + _1 = Api.parse(reader, signature: signature) as? Api.RichText } - var _2: Buffer? - _2 = parseBytes(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RichText.textAnchor(Cons_textAnchor(text: _1!, name: _2!)) + } + else { + return nil + } + } + public static func parse_textAutoEmail(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textAutoEmail(Cons_textAutoEmail(text: _1!)) + } + else { + return nil + } + } + public static func parse_textAutoPhone(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textAutoPhone(Cons_textAutoPhone(text: _1!)) + } + else { + return nil + } + } + public static func parse_textAutoUrl(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textAutoUrl(Cons_textAutoUrl(text: _1!)) + } + else { + return nil + } + } + public static func parse_textBankCard(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textBankCard(Cons_textBankCard(text: _1!)) + } + else { + return nil + } + } + public static func parse_textBold(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textBold(Cons_textBold(text: _1!)) + } + else { + return nil + } + } + public static func parse_textBotCommand(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textBotCommand(Cons_textBotCommand(text: _1!)) + } + else { + return nil + } + } + public static func parse_textCashtag(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textCashtag(Cons_textCashtag(text: _1!)) + } + else { + return nil + } + } + public static func parse_textConcat(_ reader: BufferReader) -> RichText? { + var _1: [Api.RichText]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RichText.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textConcat(Cons_textConcat(texts: _1!)) + } + else { + return nil + } + } + public static func parse_textCustomEmoji(_ reader: BufferReader) -> RichText? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RichText.textCustomEmoji(Cons_textCustomEmoji(documentId: _1!, alt: _2!)) + } + else { + return nil + } + } + public static func parse_textDate(_ reader: BufferReader) -> RichText? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.RichText? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.RichText.textDate(Cons_textDate(flags: _1!, text: _2!, date: _3!)) + } + else { + return nil + } + } + public static func parse_textEmail(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RichText.textEmail(Cons_textEmail(text: _1!, email: _2!)) + } + else { + return nil + } + } + public static func parse_textEmpty(_ reader: BufferReader) -> RichText? { + return Api.RichText.textEmpty + } + public static func parse_textFixed(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textFixed(Cons_textFixed(text: _1!)) + } + else { + return nil + } + } + public static func parse_textHashtag(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textHashtag(Cons_textHashtag(text: _1!)) + } + else { + return nil + } + } + public static func parse_textImage(_ reader: BufferReader) -> RichText? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.RichText.textImage(Cons_textImage(documentId: _1!, w: _2!, h: _3!)) + } + else { + return nil + } + } + public static func parse_textItalic(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textItalic(Cons_textItalic(text: _1!)) + } + else { + return nil + } + } + public static func parse_textMarked(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textMarked(Cons_textMarked(text: _1!)) + } + else { + return nil + } + } + public static func parse_textMath(_ reader: BufferReader) -> RichText? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textMath(Cons_textMath(source: _1!)) + } + else { + return nil + } + } + public static func parse_textMention(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textMention(Cons_textMention(text: _1!)) + } + else { + return nil + } + } + public static func parse_textMentionName(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RichText.textMentionName(Cons_textMentionName(text: _1!, userId: _2!)) + } + else { + return nil + } + } + public static func parse_textPhone(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.RichText.textPhone(Cons_textPhone(text: _1!, phone: _2!)) + } + else { + return nil + } + } + public static func parse_textPlain(_ reader: BufferReader) -> RichText? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textPlain(Cons_textPlain(text: _1!)) + } + else { + return nil + } + } + public static func parse_textSpoiler(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textSpoiler(Cons_textSpoiler(text: _1!)) + } + else { + return nil + } + } + public static func parse_textStrike(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textStrike(Cons_textStrike(text: _1!)) + } + else { + return nil + } + } + public static func parse_textSubscript(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textSubscript(Cons_textSubscript(text: _1!)) + } + else { + return nil + } + } + public static func parse_textSuperscript(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textSuperscript(Cons_textSuperscript(text: _1!)) + } + else { + return nil + } + } + public static func parse_textUnderline(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + let _c1 = _1 != nil + if _c1 { + return Api.RichText.textUnderline(Cons_textUnderline(text: _1!)) + } + else { + return nil + } + } + public static func parse_textUrl(_ reader: BufferReader) -> RichText? { + var _1: Api.RichText? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.RichText + } + var _2: String? + _2 = parseString(reader) var _3: Int64? _3 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.SecureSecretSettings.secureSecretSettings(Cons_secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!)) + return Api.RichText.textUrl(Cons_textUrl(text: _1!, url: _2!, webpageId: _3!)) } else { return nil @@ -217,568 +1091,60 @@ public extension Api { } } public extension Api { - enum SecureValue: TypeConstructorDescription { - public class Cons_secureValue: TypeConstructorDescription { - public var flags: Int32 - public var type: Api.SecureValueType - public var data: Api.SecureData? - public var frontSide: Api.SecureFile? - public var reverseSide: Api.SecureFile? - public var selfie: Api.SecureFile? - public var translation: [Api.SecureFile]? - public var files: [Api.SecureFile]? - public var plainData: Api.SecurePlainData? - public var hash: Buffer - public init(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) { - self.flags = flags - self.type = type - self.data = data - self.frontSide = frontSide - self.reverseSide = reverseSide - self.selfie = selfie - self.translation = translation - self.files = files - self.plainData = plainData - self.hash = hash + enum SavedContact: TypeConstructorDescription { + public class Cons_savedPhoneContact: TypeConstructorDescription { + public var phone: String + public var firstName: String + public var lastName: String + public var date: Int32 + public init(phone: String, firstName: String, lastName: String, date: Int32) { + self.phone = phone + self.firstName = firstName + self.lastName = lastName + self.date = date } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValue", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("data", ConstructorParameterDescription(self.data)), ("frontSide", ConstructorParameterDescription(self.frontSide)), ("reverseSide", ConstructorParameterDescription(self.reverseSide)), ("selfie", ConstructorParameterDescription(self.selfie)), ("translation", ConstructorParameterDescription(self.translation)), ("files", ConstructorParameterDescription(self.files)), ("plainData", ConstructorParameterDescription(self.plainData)), ("hash", ConstructorParameterDescription(self.hash))]) + return ("savedPhoneContact", [("phone", ConstructorParameterDescription(self.phone)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("date", ConstructorParameterDescription(self.date))]) } } - case secureValue(Cons_secureValue) + case savedPhoneContact(Cons_savedPhoneContact) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .secureValue(let _data): + case .savedPhoneContact(let _data): if boxed { - buffer.appendInt32(411017418) + buffer.appendInt32(289586518) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.type.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.data!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.frontSide!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.reverseSide!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.selfie!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.translation!.count)) - for item in _data.translation! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.files!.count)) - for item in _data.files! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.plainData!.serialize(buffer, true) - } - serializeBytes(_data.hash, buffer: buffer, boxed: false) + serializeString(_data.phone, buffer: buffer, boxed: false) + serializeString(_data.firstName, buffer: buffer, boxed: false) + serializeString(_data.lastName, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .secureValue(let _data): - return ("secureValue", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("data", ConstructorParameterDescription(_data.data)), ("frontSide", ConstructorParameterDescription(_data.frontSide)), ("reverseSide", ConstructorParameterDescription(_data.reverseSide)), ("selfie", ConstructorParameterDescription(_data.selfie)), ("translation", ConstructorParameterDescription(_data.translation)), ("files", ConstructorParameterDescription(_data.files)), ("plainData", ConstructorParameterDescription(_data.plainData)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .savedPhoneContact(let _data): + return ("savedPhoneContact", [("phone", ConstructorParameterDescription(_data.phone)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("date", ConstructorParameterDescription(_data.date))]) } } - public static func parse_secureValue(_ reader: BufferReader) -> SecureValue? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.SecureValueType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _3: Api.SecureData? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SecureData - } - } - var _4: Api.SecureFile? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.SecureFile - } - } - var _5: Api.SecureFile? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.SecureFile - } - } - var _6: Api.SecureFile? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.SecureFile - } - } - var _7: [Api.SecureFile]? - if Int(_1!) & Int(1 << 6) != 0 { - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) - } - } - var _8: [Api.SecureFile]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) - } - } - var _9: Api.SecurePlainData? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData - } - } - var _10: Buffer? - _10 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.SecureValue.secureValue(Cons_secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SecureValueError: TypeConstructorDescription { - public class Cons_secureValueError: TypeConstructorDescription { - public var type: Api.SecureValueType - public var hash: Buffer - public var text: String - public init(type: Api.SecureValueType, hash: Buffer, text: String) { - self.type = type - self.hash = hash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueError", [("type", ConstructorParameterDescription(self.type)), ("hash", ConstructorParameterDescription(self.hash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorData: TypeConstructorDescription { - public var type: Api.SecureValueType - public var dataHash: Buffer - public var field: String - public var text: String - public init(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) { - self.type = type - self.dataHash = dataHash - self.field = field - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorData", [("type", ConstructorParameterDescription(self.type)), ("dataHash", ConstructorParameterDescription(self.dataHash)), ("field", ConstructorParameterDescription(self.field)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorFile: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: Buffer - public var text: String - public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorFile", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorFiles: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: [Buffer] - public var text: String - public init(type: Api.SecureValueType, fileHash: [Buffer], text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorFiles", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorFrontSide: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: Buffer - public var text: String - public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorFrontSide", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorReverseSide: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: Buffer - public var text: String - public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorReverseSide", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorSelfie: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: Buffer - public var text: String - public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorSelfie", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorTranslationFile: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: Buffer - public var text: String - public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorTranslationFile", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_secureValueErrorTranslationFiles: TypeConstructorDescription { - public var type: Api.SecureValueType - public var fileHash: [Buffer] - public var text: String - public init(type: Api.SecureValueType, fileHash: [Buffer], text: String) { - self.type = type - self.fileHash = fileHash - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueErrorTranslationFiles", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) - } - } - case secureValueError(Cons_secureValueError) - case secureValueErrorData(Cons_secureValueErrorData) - case secureValueErrorFile(Cons_secureValueErrorFile) - case secureValueErrorFiles(Cons_secureValueErrorFiles) - case secureValueErrorFrontSide(Cons_secureValueErrorFrontSide) - case secureValueErrorReverseSide(Cons_secureValueErrorReverseSide) - case secureValueErrorSelfie(Cons_secureValueErrorSelfie) - case secureValueErrorTranslationFile(Cons_secureValueErrorTranslationFile) - case secureValueErrorTranslationFiles(Cons_secureValueErrorTranslationFiles) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureValueError(let _data): - if boxed { - buffer.appendInt32(-2036501105) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.hash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorData(let _data): - if boxed { - buffer.appendInt32(-391902247) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.dataHash, buffer: buffer, boxed: false) - serializeString(_data.field, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorFile(let _data): - if boxed { - buffer.appendInt32(2054162547) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorFiles(let _data): - if boxed { - buffer.appendInt32(1717706985) - } - _data.type.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.fileHash.count)) - for item in _data.fileHash { - serializeBytes(item, buffer: buffer, boxed: false) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorFrontSide(let _data): - if boxed { - buffer.appendInt32(12467706) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorReverseSide(let _data): - if boxed { - buffer.appendInt32(-2037765467) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorSelfie(let _data): - if boxed { - buffer.appendInt32(-449327402) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorTranslationFile(let _data): - if boxed { - buffer.appendInt32(-1592506512) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.fileHash, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .secureValueErrorTranslationFiles(let _data): - if boxed { - buffer.appendInt32(878931416) - } - _data.type.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.fileHash.count)) - for item in _data.fileHash { - serializeBytes(item, buffer: buffer, boxed: false) - } - serializeString(_data.text, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .secureValueError(let _data): - return ("secureValueError", [("type", ConstructorParameterDescription(_data.type)), ("hash", ConstructorParameterDescription(_data.hash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorData(let _data): - return ("secureValueErrorData", [("type", ConstructorParameterDescription(_data.type)), ("dataHash", ConstructorParameterDescription(_data.dataHash)), ("field", ConstructorParameterDescription(_data.field)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorFile(let _data): - return ("secureValueErrorFile", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorFiles(let _data): - return ("secureValueErrorFiles", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorFrontSide(let _data): - return ("secureValueErrorFrontSide", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorReverseSide(let _data): - return ("secureValueErrorReverseSide", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorSelfie(let _data): - return ("secureValueErrorSelfie", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorTranslationFile(let _data): - return ("secureValueErrorTranslationFile", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - case .secureValueErrorTranslationFiles(let _data): - return ("secureValueErrorTranslationFiles", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) - } - } - - public static func parse_secureValueError(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) + public static func parse_savedPhoneContact(_ reader: BufferReader) -> SavedContact? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) var _3: String? _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueError(Cons_secureValueError(type: _1!, hash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorData(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.SecureValueError.secureValueErrorData(Cons_secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorFile(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFile(Cons_secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorFiles(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFiles(Cons_secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorFrontSide(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorFrontSide(Cons_secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorReverseSide(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorReverseSide(Cons_secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorSelfie(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorSelfie(Cons_secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorTranslationFile(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFile(Cons_secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!)) - } - else { - return nil - } - } - public static func parse_secureValueErrorTranslationFiles(_ reader: BufferReader) -> SecureValueError? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: [Buffer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SecureValueError.secureValueErrorTranslationFiles(Cons_secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!)) + return Api.SavedContact.savedPhoneContact(Cons_savedPhoneContact(phone: _1!, firstName: _2!, lastName: _3!, date: _4!)) } else { return nil @@ -786,673 +1152,3 @@ public extension Api { } } } -public extension Api { - enum SecureValueHash: TypeConstructorDescription { - public class Cons_secureValueHash: TypeConstructorDescription { - public var type: Api.SecureValueType - public var hash: Buffer - public init(type: Api.SecureValueType, hash: Buffer) { - self.type = type - self.hash = hash - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("secureValueHash", [("type", ConstructorParameterDescription(self.type)), ("hash", ConstructorParameterDescription(self.hash))]) - } - } - case secureValueHash(Cons_secureValueHash) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureValueHash(let _data): - if boxed { - buffer.appendInt32(-316748368) - } - _data.type.serialize(buffer, true) - serializeBytes(_data.hash, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .secureValueHash(let _data): - return ("secureValueHash", [("type", ConstructorParameterDescription(_data.type)), ("hash", ConstructorParameterDescription(_data.hash))]) - } - } - - public static func parse_secureValueHash(_ reader: BufferReader) -> SecureValueHash? { - var _1: Api.SecureValueType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType - } - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SecureValueHash.secureValueHash(Cons_secureValueHash(type: _1!, hash: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SecureValueType: TypeConstructorDescription { - case secureValueTypeAddress - case secureValueTypeBankStatement - case secureValueTypeDriverLicense - case secureValueTypeEmail - case secureValueTypeIdentityCard - case secureValueTypeInternalPassport - case secureValueTypePassport - case secureValueTypePassportRegistration - case secureValueTypePersonalDetails - case secureValueTypePhone - case secureValueTypeRentalAgreement - case secureValueTypeTemporaryRegistration - case secureValueTypeUtilityBill - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .secureValueTypeAddress: - if boxed { - buffer.appendInt32(-874308058) - } - break - case .secureValueTypeBankStatement: - if boxed { - buffer.appendInt32(-1995211763) - } - break - case .secureValueTypeDriverLicense: - if boxed { - buffer.appendInt32(115615172) - } - break - case .secureValueTypeEmail: - if boxed { - buffer.appendInt32(-1908627474) - } - break - case .secureValueTypeIdentityCard: - if boxed { - buffer.appendInt32(-1596951477) - } - break - case .secureValueTypeInternalPassport: - if boxed { - buffer.appendInt32(-1717268701) - } - break - case .secureValueTypePassport: - if boxed { - buffer.appendInt32(1034709504) - } - break - case .secureValueTypePassportRegistration: - if boxed { - buffer.appendInt32(-1713143702) - } - break - case .secureValueTypePersonalDetails: - if boxed { - buffer.appendInt32(-1658158621) - } - break - case .secureValueTypePhone: - if boxed { - buffer.appendInt32(-1289704741) - } - break - case .secureValueTypeRentalAgreement: - if boxed { - buffer.appendInt32(-1954007928) - } - break - case .secureValueTypeTemporaryRegistration: - if boxed { - buffer.appendInt32(-368907213) - } - break - case .secureValueTypeUtilityBill: - if boxed { - buffer.appendInt32(-63531698) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .secureValueTypeAddress: - return ("secureValueTypeAddress", []) - case .secureValueTypeBankStatement: - return ("secureValueTypeBankStatement", []) - case .secureValueTypeDriverLicense: - return ("secureValueTypeDriverLicense", []) - case .secureValueTypeEmail: - return ("secureValueTypeEmail", []) - case .secureValueTypeIdentityCard: - return ("secureValueTypeIdentityCard", []) - case .secureValueTypeInternalPassport: - return ("secureValueTypeInternalPassport", []) - case .secureValueTypePassport: - return ("secureValueTypePassport", []) - case .secureValueTypePassportRegistration: - return ("secureValueTypePassportRegistration", []) - case .secureValueTypePersonalDetails: - return ("secureValueTypePersonalDetails", []) - case .secureValueTypePhone: - return ("secureValueTypePhone", []) - case .secureValueTypeRentalAgreement: - return ("secureValueTypeRentalAgreement", []) - case .secureValueTypeTemporaryRegistration: - return ("secureValueTypeTemporaryRegistration", []) - case .secureValueTypeUtilityBill: - return ("secureValueTypeUtilityBill", []) - } - } - - public static func parse_secureValueTypeAddress(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeAddress - } - public static func parse_secureValueTypeBankStatement(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeBankStatement - } - public static func parse_secureValueTypeDriverLicense(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeDriverLicense - } - public static func parse_secureValueTypeEmail(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeEmail - } - public static func parse_secureValueTypeIdentityCard(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeIdentityCard - } - public static func parse_secureValueTypeInternalPassport(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeInternalPassport - } - public static func parse_secureValueTypePassport(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePassport - } - public static func parse_secureValueTypePassportRegistration(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePassportRegistration - } - public static func parse_secureValueTypePersonalDetails(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePersonalDetails - } - public static func parse_secureValueTypePhone(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypePhone - } - public static func parse_secureValueTypeRentalAgreement(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeRentalAgreement - } - public static func parse_secureValueTypeTemporaryRegistration(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeTemporaryRegistration - } - public static func parse_secureValueTypeUtilityBill(_ reader: BufferReader) -> SecureValueType? { - return Api.SecureValueType.secureValueTypeUtilityBill - } - } -} -public extension Api { - enum SendAsPeer: TypeConstructorDescription { - public class Cons_sendAsPeer: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public init(flags: Int32, peer: Api.Peer) { - self.flags = flags - self.peer = peer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendAsPeer", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer))]) - } - } - case sendAsPeer(Cons_sendAsPeer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sendAsPeer(let _data): - if boxed { - buffer.appendInt32(-1206095820) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sendAsPeer(let _data): - return ("sendAsPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer))]) - } - } - - public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SendAsPeer.sendAsPeer(Cons_sendAsPeer(flags: _1!, peer: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SendMessageAction: TypeConstructorDescription { - public class Cons_sendMessageEmojiInteraction: TypeConstructorDescription { - public var emoticon: String - public var msgId: Int32 - public var interaction: Api.DataJSON - public init(emoticon: String, msgId: Int32, interaction: Api.DataJSON) { - self.emoticon = emoticon - self.msgId = msgId - self.interaction = interaction - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageEmojiInteraction", [("emoticon", ConstructorParameterDescription(self.emoticon)), ("msgId", ConstructorParameterDescription(self.msgId)), ("interaction", ConstructorParameterDescription(self.interaction))]) - } - } - public class Cons_sendMessageEmojiInteractionSeen: TypeConstructorDescription { - public var emoticon: String - public init(emoticon: String) { - self.emoticon = emoticon - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageEmojiInteractionSeen", [("emoticon", ConstructorParameterDescription(self.emoticon))]) - } - } - public class Cons_sendMessageHistoryImportAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageHistoryImportAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - public class Cons_sendMessageTextDraftAction: TypeConstructorDescription { - public var randomId: Int64 - public var text: Api.TextWithEntities - public init(randomId: Int64, text: Api.TextWithEntities) { - self.randomId = randomId - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageTextDraftAction", [("randomId", ConstructorParameterDescription(self.randomId)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_sendMessageUploadAudioAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageUploadAudioAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - public class Cons_sendMessageUploadDocumentAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageUploadDocumentAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - public class Cons_sendMessageUploadPhotoAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageUploadPhotoAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - public class Cons_sendMessageUploadRoundAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageUploadRoundAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - public class Cons_sendMessageUploadVideoAction: TypeConstructorDescription { - public var progress: Int32 - public init(progress: Int32) { - self.progress = progress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendMessageUploadVideoAction", [("progress", ConstructorParameterDescription(self.progress))]) - } - } - case sendMessageCancelAction - case sendMessageChooseContactAction - case sendMessageChooseStickerAction - case sendMessageEmojiInteraction(Cons_sendMessageEmojiInteraction) - case sendMessageEmojiInteractionSeen(Cons_sendMessageEmojiInteractionSeen) - case sendMessageGamePlayAction - case sendMessageGeoLocationAction - case sendMessageHistoryImportAction(Cons_sendMessageHistoryImportAction) - case sendMessageRecordAudioAction - case sendMessageRecordRoundAction - case sendMessageRecordVideoAction - case sendMessageTextDraftAction(Cons_sendMessageTextDraftAction) - case sendMessageTypingAction - case sendMessageUploadAudioAction(Cons_sendMessageUploadAudioAction) - case sendMessageUploadDocumentAction(Cons_sendMessageUploadDocumentAction) - case sendMessageUploadPhotoAction(Cons_sendMessageUploadPhotoAction) - case sendMessageUploadRoundAction(Cons_sendMessageUploadRoundAction) - case sendMessageUploadVideoAction(Cons_sendMessageUploadVideoAction) - case speakingInGroupCallAction - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sendMessageCancelAction: - if boxed { - buffer.appendInt32(-44119819) - } - break - case .sendMessageChooseContactAction: - if boxed { - buffer.appendInt32(1653390447) - } - break - case .sendMessageChooseStickerAction: - if boxed { - buffer.appendInt32(-1336228175) - } - break - case .sendMessageEmojiInteraction(let _data): - if boxed { - buffer.appendInt32(630664139) - } - serializeString(_data.emoticon, buffer: buffer, boxed: false) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - _data.interaction.serialize(buffer, true) - break - case .sendMessageEmojiInteractionSeen(let _data): - if boxed { - buffer.appendInt32(-1234857938) - } - serializeString(_data.emoticon, buffer: buffer, boxed: false) - break - case .sendMessageGamePlayAction: - if boxed { - buffer.appendInt32(-580219064) - } - break - case .sendMessageGeoLocationAction: - if boxed { - buffer.appendInt32(393186209) - } - break - case .sendMessageHistoryImportAction(let _data): - if boxed { - buffer.appendInt32(-606432698) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .sendMessageRecordAudioAction: - if boxed { - buffer.appendInt32(-718310409) - } - break - case .sendMessageRecordRoundAction: - if boxed { - buffer.appendInt32(-1997373508) - } - break - case .sendMessageRecordVideoAction: - if boxed { - buffer.appendInt32(-1584933265) - } - break - case .sendMessageTextDraftAction(let _data): - if boxed { - buffer.appendInt32(929929052) - } - serializeInt64(_data.randomId, buffer: buffer, boxed: false) - _data.text.serialize(buffer, true) - break - case .sendMessageTypingAction: - if boxed { - buffer.appendInt32(381645902) - } - break - case .sendMessageUploadAudioAction(let _data): - if boxed { - buffer.appendInt32(-212740181) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadDocumentAction(let _data): - if boxed { - buffer.appendInt32(-1441998364) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadPhotoAction(let _data): - if boxed { - buffer.appendInt32(-774682074) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadRoundAction(let _data): - if boxed { - buffer.appendInt32(608050278) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .sendMessageUploadVideoAction(let _data): - if boxed { - buffer.appendInt32(-378127636) - } - serializeInt32(_data.progress, buffer: buffer, boxed: false) - break - case .speakingInGroupCallAction: - if boxed { - buffer.appendInt32(-651419003) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sendMessageCancelAction: - return ("sendMessageCancelAction", []) - case .sendMessageChooseContactAction: - return ("sendMessageChooseContactAction", []) - case .sendMessageChooseStickerAction: - return ("sendMessageChooseStickerAction", []) - case .sendMessageEmojiInteraction(let _data): - return ("sendMessageEmojiInteraction", [("emoticon", ConstructorParameterDescription(_data.emoticon)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("interaction", ConstructorParameterDescription(_data.interaction))]) - case .sendMessageEmojiInteractionSeen(let _data): - return ("sendMessageEmojiInteractionSeen", [("emoticon", ConstructorParameterDescription(_data.emoticon))]) - case .sendMessageGamePlayAction: - return ("sendMessageGamePlayAction", []) - case .sendMessageGeoLocationAction: - return ("sendMessageGeoLocationAction", []) - case .sendMessageHistoryImportAction(let _data): - return ("sendMessageHistoryImportAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .sendMessageRecordAudioAction: - return ("sendMessageRecordAudioAction", []) - case .sendMessageRecordRoundAction: - return ("sendMessageRecordRoundAction", []) - case .sendMessageRecordVideoAction: - return ("sendMessageRecordVideoAction", []) - case .sendMessageTextDraftAction(let _data): - return ("sendMessageTextDraftAction", [("randomId", ConstructorParameterDescription(_data.randomId)), ("text", ConstructorParameterDescription(_data.text))]) - case .sendMessageTypingAction: - return ("sendMessageTypingAction", []) - case .sendMessageUploadAudioAction(let _data): - return ("sendMessageUploadAudioAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .sendMessageUploadDocumentAction(let _data): - return ("sendMessageUploadDocumentAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .sendMessageUploadPhotoAction(let _data): - return ("sendMessageUploadPhotoAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .sendMessageUploadRoundAction(let _data): - return ("sendMessageUploadRoundAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .sendMessageUploadVideoAction(let _data): - return ("sendMessageUploadVideoAction", [("progress", ConstructorParameterDescription(_data.progress))]) - case .speakingInGroupCallAction: - return ("speakingInGroupCallAction", []) - } - } - - public static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageCancelAction - } - public static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageChooseContactAction - } - public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageChooseStickerAction - } - public static func parse_sendMessageEmojiInteraction(_ reader: BufferReader) -> SendMessageAction? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.DataJSON? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SendMessageAction.sendMessageEmojiInteraction(Cons_sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!)) - } - else { - return nil - } - } - public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageEmojiInteractionSeen(Cons_sendMessageEmojiInteractionSeen(emoticon: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageGamePlayAction - } - public static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageGeoLocationAction - } - public static func parse_sendMessageHistoryImportAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageHistoryImportAction(Cons_sendMessageHistoryImportAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordAudioAction - } - public static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordRoundAction - } - public static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageRecordVideoAction - } - public static func parse_sendMessageTextDraftAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SendMessageAction.sendMessageTextDraftAction(Cons_sendMessageTextDraftAction(randomId: _1!, text: _2!)) - } - else { - return nil - } - } - public static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.sendMessageTypingAction - } - public static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadAudioAction(Cons_sendMessageUploadAudioAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadDocumentAction(Cons_sendMessageUploadDocumentAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadPhotoAction(Cons_sendMessageUploadPhotoAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadRoundAction(Cons_sendMessageUploadRoundAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.SendMessageAction.sendMessageUploadVideoAction(Cons_sendMessageUploadVideoAction(progress: _1!)) - } - else { - return nil - } - } - public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { - return Api.SendMessageAction.speakingInGroupCallAction - } - } -} diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 36ee647b44..65ccfe0aa1 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -1,1504 +1,82 @@ public extension Api { - enum ShippingOption: TypeConstructorDescription { - public class Cons_shippingOption: TypeConstructorDescription { - public var id: String - public var title: String - public var prices: [Api.LabeledPrice] - public init(id: String, title: String, prices: [Api.LabeledPrice]) { - self.id = id - self.title = title - self.prices = prices - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("shippingOption", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title)), ("prices", ConstructorParameterDescription(self.prices))]) - } - } - case shippingOption(Cons_shippingOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .shippingOption(let _data): - if boxed { - buffer.appendInt32(-1239335713) - } - serializeString(_data.id, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.prices.count)) - for item in _data.prices { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .shippingOption(let _data): - return ("shippingOption", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title)), ("prices", ConstructorParameterDescription(_data.prices))]) - } - } - - public static func parse_shippingOption(_ reader: BufferReader) -> ShippingOption? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: [Api.LabeledPrice]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.ShippingOption.shippingOption(Cons_shippingOption(id: _1!, title: _2!, prices: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SmsJob: TypeConstructorDescription { - public class Cons_smsJob: TypeConstructorDescription { - public var jobId: String - public var phoneNumber: String - public var text: String - public init(jobId: String, phoneNumber: String, text: String) { - self.jobId = jobId - self.phoneNumber = phoneNumber - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("smsJob", [("jobId", ConstructorParameterDescription(self.jobId)), ("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("text", ConstructorParameterDescription(self.text))]) - } - } - case smsJob(Cons_smsJob) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .smsJob(let _data): - if boxed { - buffer.appendInt32(-425595208) - } - serializeString(_data.jobId, buffer: buffer, boxed: false) - serializeString(_data.phoneNumber, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .smsJob(let _data): - return ("smsJob", [("jobId", ConstructorParameterDescription(_data.jobId)), ("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("text", ConstructorParameterDescription(_data.text))]) - } - } - - public static func parse_smsJob(_ reader: BufferReader) -> SmsJob? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.SmsJob.smsJob(Cons_smsJob(jobId: _1!, phoneNumber: _2!, text: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum SponsoredMessage: TypeConstructorDescription { - public class Cons_sponsoredMessage: TypeConstructorDescription { - public var flags: Int32 - public var randomId: Buffer - public var url: String - public var title: String - public var message: String - public var entities: [Api.MessageEntity]? - public var photo: Api.Photo? - public var media: Api.MessageMedia? - public var color: Api.PeerColor? - public var buttonText: String - public var sponsorInfo: String? - public var additionalInfo: String? - public var minDisplayDuration: Int32? - public var maxDisplayDuration: Int32? - public init(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, media: Api.MessageMedia?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?, minDisplayDuration: Int32?, maxDisplayDuration: Int32?) { - self.flags = flags - self.randomId = randomId - self.url = url - self.title = title - self.message = message - self.entities = entities - self.photo = photo - self.media = media - self.color = color - self.buttonText = buttonText - self.sponsorInfo = sponsorInfo - self.additionalInfo = additionalInfo - self.minDisplayDuration = minDisplayDuration - self.maxDisplayDuration = maxDisplayDuration - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredMessage", [("flags", ConstructorParameterDescription(self.flags)), ("randomId", ConstructorParameterDescription(self.randomId)), ("url", ConstructorParameterDescription(self.url)), ("title", ConstructorParameterDescription(self.title)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("photo", ConstructorParameterDescription(self.photo)), ("media", ConstructorParameterDescription(self.media)), ("color", ConstructorParameterDescription(self.color)), ("buttonText", ConstructorParameterDescription(self.buttonText)), ("sponsorInfo", ConstructorParameterDescription(self.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(self.additionalInfo)), ("minDisplayDuration", ConstructorParameterDescription(self.minDisplayDuration)), ("maxDisplayDuration", ConstructorParameterDescription(self.maxDisplayDuration))]) - } - } - case sponsoredMessage(Cons_sponsoredMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredMessage(let _data): - if boxed { - buffer.appendInt32(2109703795) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeBytes(_data.randomId, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - _data.media!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - _data.color!.serialize(buffer, true) - } - serializeString(_data.buttonText, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeString(_data.sponsorInfo!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeString(_data.additionalInfo!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.minDisplayDuration!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.maxDisplayDuration!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sponsoredMessage(let _data): - return ("sponsoredMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("url", ConstructorParameterDescription(_data.url)), ("title", ConstructorParameterDescription(_data.title)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("photo", ConstructorParameterDescription(_data.photo)), ("media", ConstructorParameterDescription(_data.media)), ("color", ConstructorParameterDescription(_data.color)), ("buttonText", ConstructorParameterDescription(_data.buttonText)), ("sponsorInfo", ConstructorParameterDescription(_data.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(_data.additionalInfo)), ("minDisplayDuration", ConstructorParameterDescription(_data.minDisplayDuration)), ("maxDisplayDuration", ConstructorParameterDescription(_data.maxDisplayDuration))]) - } - } - - public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _7: Api.Photo? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _8: Api.MessageMedia? - if Int(_1!) & Int(1 << 14) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - } - var _9: Api.PeerColor? - if Int(_1!) & Int(1 << 13) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PeerColor - } - } - var _10: String? - _10 = parseString(reader) - var _11: String? - if Int(_1!) & Int(1 << 7) != 0 { - _11 = parseString(reader) - } - var _12: String? - if Int(_1!) & Int(1 << 8) != 0 { - _12 = parseString(reader) - } - var _13: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _13 = reader.readInt32() - } - var _14: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _14 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 14) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 13) == 0) || _9 != nil - let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { - return Api.SponsoredMessage.sponsoredMessage(Cons_sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12, minDisplayDuration: _13, maxDisplayDuration: _14)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SponsoredMessageReportOption: TypeConstructorDescription { - public class Cons_sponsoredMessageReportOption: TypeConstructorDescription { - public var text: String - public var option: Buffer - public init(text: String, option: Buffer) { - self.text = text - self.option = option - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredMessageReportOption", [("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option))]) - } - } - case sponsoredMessageReportOption(Cons_sponsoredMessageReportOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredMessageReportOption(let _data): - if boxed { - buffer.appendInt32(1124938064) - } - serializeString(_data.text, buffer: buffer, boxed: false) - serializeBytes(_data.option, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sponsoredMessageReportOption(let _data): - return ("sponsoredMessageReportOption", [("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option))]) - } - } - - public static func parse_sponsoredMessageReportOption(_ reader: BufferReader) -> SponsoredMessageReportOption? { - var _1: String? - _1 = parseString(reader) - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(Cons_sponsoredMessageReportOption(text: _1!, option: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SponsoredPeer: TypeConstructorDescription { - public class Cons_sponsoredPeer: TypeConstructorDescription { - public var flags: Int32 - public var randomId: Buffer - public var peer: Api.Peer - public var sponsorInfo: String? - public var additionalInfo: String? - public init(flags: Int32, randomId: Buffer, peer: Api.Peer, sponsorInfo: String?, additionalInfo: String?) { - self.flags = flags - self.randomId = randomId - self.peer = peer - self.sponsorInfo = sponsorInfo - self.additionalInfo = additionalInfo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredPeer", [("flags", ConstructorParameterDescription(self.flags)), ("randomId", ConstructorParameterDescription(self.randomId)), ("peer", ConstructorParameterDescription(self.peer)), ("sponsorInfo", ConstructorParameterDescription(self.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(self.additionalInfo))]) - } - } - case sponsoredPeer(Cons_sponsoredPeer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredPeer(let _data): - if boxed { - buffer.appendInt32(-963180333) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeBytes(_data.randomId, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.sponsorInfo!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.additionalInfo!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sponsoredPeer(let _data): - return ("sponsoredPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("peer", ConstructorParameterDescription(_data.peer)), ("sponsorInfo", ConstructorParameterDescription(_data.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(_data.additionalInfo))]) - } - } - - public static func parse_sponsoredPeer(_ reader: BufferReader) -> SponsoredPeer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Api.Peer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.SponsoredPeer.sponsoredPeer(Cons_sponsoredPeer(flags: _1!, randomId: _2!, peer: _3!, sponsorInfo: _4, additionalInfo: _5)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGift: TypeConstructorDescription { - public class Cons_starGift: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var sticker: Api.Document - public var stars: Int64 - public var availabilityRemains: Int32? - public var availabilityTotal: Int32? - public var availabilityResale: Int64? - public var convertStars: Int64 - public var firstSaleDate: Int32? - public var lastSaleDate: Int32? - public var upgradeStars: Int64? - public var resellMinStars: Int64? - public var title: String? - public var releasedBy: Api.Peer? - public var perUserTotal: Int32? - public var perUserRemains: Int32? - public var lockedUntilDate: Int32? - public var auctionSlug: String? - public var giftsPerRound: Int32? - public var auctionStartDate: Int32? - public var upgradeVariants: Int32? - public var background: Api.StarGiftBackground? - public init(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?, auctionSlug: String?, giftsPerRound: Int32?, auctionStartDate: Int32?, upgradeVariants: Int32?, background: Api.StarGiftBackground?) { - self.flags = flags - self.id = id - self.sticker = sticker - self.stars = stars - self.availabilityRemains = availabilityRemains - self.availabilityTotal = availabilityTotal - self.availabilityResale = availabilityResale - self.convertStars = convertStars - self.firstSaleDate = firstSaleDate - self.lastSaleDate = lastSaleDate - self.upgradeStars = upgradeStars - self.resellMinStars = resellMinStars - self.title = title - self.releasedBy = releasedBy - self.perUserTotal = perUserTotal - self.perUserRemains = perUserRemains - self.lockedUntilDate = lockedUntilDate - self.auctionSlug = auctionSlug - self.giftsPerRound = giftsPerRound - self.auctionStartDate = auctionStartDate - self.upgradeVariants = upgradeVariants - self.background = background - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGift", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("sticker", ConstructorParameterDescription(self.sticker)), ("stars", ConstructorParameterDescription(self.stars)), ("availabilityRemains", ConstructorParameterDescription(self.availabilityRemains)), ("availabilityTotal", ConstructorParameterDescription(self.availabilityTotal)), ("availabilityResale", ConstructorParameterDescription(self.availabilityResale)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("firstSaleDate", ConstructorParameterDescription(self.firstSaleDate)), ("lastSaleDate", ConstructorParameterDescription(self.lastSaleDate)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("resellMinStars", ConstructorParameterDescription(self.resellMinStars)), ("title", ConstructorParameterDescription(self.title)), ("releasedBy", ConstructorParameterDescription(self.releasedBy)), ("perUserTotal", ConstructorParameterDescription(self.perUserTotal)), ("perUserRemains", ConstructorParameterDescription(self.perUserRemains)), ("lockedUntilDate", ConstructorParameterDescription(self.lockedUntilDate)), ("auctionSlug", ConstructorParameterDescription(self.auctionSlug)), ("giftsPerRound", ConstructorParameterDescription(self.giftsPerRound)), ("auctionStartDate", ConstructorParameterDescription(self.auctionStartDate)), ("upgradeVariants", ConstructorParameterDescription(self.upgradeVariants)), ("background", ConstructorParameterDescription(self.background))]) - } - } - public class Cons_starGiftUnique: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var giftId: Int64 - public var title: String - public var slug: String - public var num: Int32 - public var ownerId: Api.Peer? - public var ownerName: String? - public var ownerAddress: String? - public var attributes: [Api.StarGiftAttribute] - public var availabilityIssued: Int32 - public var availabilityTotal: Int32 - public var giftAddress: String? - public var resellAmount: [Api.StarsAmount]? - public var releasedBy: Api.Peer? - public var valueAmount: Int64? - public var valueCurrency: String? - public var valueUsdAmount: Int64? - public var themePeer: Api.Peer? - public var peerColor: Api.PeerColor? - public var hostId: Api.Peer? - public var offerMinStars: Int32? - public var craftChancePermille: Int32? - public init(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, valueUsdAmount: Int64?, themePeer: Api.Peer?, peerColor: Api.PeerColor?, hostId: Api.Peer?, offerMinStars: Int32?, craftChancePermille: Int32?) { - self.flags = flags - self.id = id - self.giftId = giftId - self.title = title - self.slug = slug - self.num = num - self.ownerId = ownerId - self.ownerName = ownerName - self.ownerAddress = ownerAddress - self.attributes = attributes - self.availabilityIssued = availabilityIssued - self.availabilityTotal = availabilityTotal - self.giftAddress = giftAddress - self.resellAmount = resellAmount - self.releasedBy = releasedBy - self.valueAmount = valueAmount - self.valueCurrency = valueCurrency - self.valueUsdAmount = valueUsdAmount - self.themePeer = themePeer - self.peerColor = peerColor - self.hostId = hostId - self.offerMinStars = offerMinStars - self.craftChancePermille = craftChancePermille - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftUnique", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("giftId", ConstructorParameterDescription(self.giftId)), ("title", ConstructorParameterDescription(self.title)), ("slug", ConstructorParameterDescription(self.slug)), ("num", ConstructorParameterDescription(self.num)), ("ownerId", ConstructorParameterDescription(self.ownerId)), ("ownerName", ConstructorParameterDescription(self.ownerName)), ("ownerAddress", ConstructorParameterDescription(self.ownerAddress)), ("attributes", ConstructorParameterDescription(self.attributes)), ("availabilityIssued", ConstructorParameterDescription(self.availabilityIssued)), ("availabilityTotal", ConstructorParameterDescription(self.availabilityTotal)), ("giftAddress", ConstructorParameterDescription(self.giftAddress)), ("resellAmount", ConstructorParameterDescription(self.resellAmount)), ("releasedBy", ConstructorParameterDescription(self.releasedBy)), ("valueAmount", ConstructorParameterDescription(self.valueAmount)), ("valueCurrency", ConstructorParameterDescription(self.valueCurrency)), ("valueUsdAmount", ConstructorParameterDescription(self.valueUsdAmount)), ("themePeer", ConstructorParameterDescription(self.themePeer)), ("peerColor", ConstructorParameterDescription(self.peerColor)), ("hostId", ConstructorParameterDescription(self.hostId)), ("offerMinStars", ConstructorParameterDescription(self.offerMinStars)), ("craftChancePermille", ConstructorParameterDescription(self.craftChancePermille))]) - } - } - case starGift(Cons_starGift) - case starGiftUnique(Cons_starGiftUnique) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGift(let _data): - if boxed { - buffer.appendInt32(825922887) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - _data.sticker.serialize(buffer, true) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.availabilityRemains!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.availabilityTotal!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.availabilityResale!, buffer: buffer, boxed: false) - } - serializeInt64(_data.convertStars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.firstSaleDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.lastSaleDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.resellMinStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.releasedBy!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt32(_data.perUserTotal!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt32(_data.perUserRemains!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - serializeInt32(_data.lockedUntilDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeString(_data.auctionSlug!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt32(_data.giftsPerRound!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt32(_data.auctionStartDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - serializeInt32(_data.upgradeVariants!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - _data.background!.serialize(buffer, true) - } - break - case .starGiftUnique(let _data): - if boxed { - buffer.appendInt32(-2047825459) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.giftId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.slug, buffer: buffer, boxed: false) - serializeInt32(_data.num, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.ownerId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.ownerName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.ownerAddress!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes.count)) - for item in _data.attributes { - item.serialize(buffer, true) - } - serializeInt32(_data.availabilityIssued, buffer: buffer, boxed: false) - serializeInt32(_data.availabilityTotal, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.giftAddress!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.resellAmount!.count)) - for item in _data.resellAmount! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.releasedBy!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt64(_data.valueAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeString(_data.valueCurrency!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt64(_data.valueUsdAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.themePeer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - _data.peerColor!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - _data.hostId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - serializeInt32(_data.offerMinStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeInt32(_data.craftChancePermille!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGift(let _data): - return ("starGift", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("sticker", ConstructorParameterDescription(_data.sticker)), ("stars", ConstructorParameterDescription(_data.stars)), ("availabilityRemains", ConstructorParameterDescription(_data.availabilityRemains)), ("availabilityTotal", ConstructorParameterDescription(_data.availabilityTotal)), ("availabilityResale", ConstructorParameterDescription(_data.availabilityResale)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("firstSaleDate", ConstructorParameterDescription(_data.firstSaleDate)), ("lastSaleDate", ConstructorParameterDescription(_data.lastSaleDate)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("resellMinStars", ConstructorParameterDescription(_data.resellMinStars)), ("title", ConstructorParameterDescription(_data.title)), ("releasedBy", ConstructorParameterDescription(_data.releasedBy)), ("perUserTotal", ConstructorParameterDescription(_data.perUserTotal)), ("perUserRemains", ConstructorParameterDescription(_data.perUserRemains)), ("lockedUntilDate", ConstructorParameterDescription(_data.lockedUntilDate)), ("auctionSlug", ConstructorParameterDescription(_data.auctionSlug)), ("giftsPerRound", ConstructorParameterDescription(_data.giftsPerRound)), ("auctionStartDate", ConstructorParameterDescription(_data.auctionStartDate)), ("upgradeVariants", ConstructorParameterDescription(_data.upgradeVariants)), ("background", ConstructorParameterDescription(_data.background))]) - case .starGiftUnique(let _data): - return ("starGiftUnique", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("giftId", ConstructorParameterDescription(_data.giftId)), ("title", ConstructorParameterDescription(_data.title)), ("slug", ConstructorParameterDescription(_data.slug)), ("num", ConstructorParameterDescription(_data.num)), ("ownerId", ConstructorParameterDescription(_data.ownerId)), ("ownerName", ConstructorParameterDescription(_data.ownerName)), ("ownerAddress", ConstructorParameterDescription(_data.ownerAddress)), ("attributes", ConstructorParameterDescription(_data.attributes)), ("availabilityIssued", ConstructorParameterDescription(_data.availabilityIssued)), ("availabilityTotal", ConstructorParameterDescription(_data.availabilityTotal)), ("giftAddress", ConstructorParameterDescription(_data.giftAddress)), ("resellAmount", ConstructorParameterDescription(_data.resellAmount)), ("releasedBy", ConstructorParameterDescription(_data.releasedBy)), ("valueAmount", ConstructorParameterDescription(_data.valueAmount)), ("valueCurrency", ConstructorParameterDescription(_data.valueCurrency)), ("valueUsdAmount", ConstructorParameterDescription(_data.valueUsdAmount)), ("themePeer", ConstructorParameterDescription(_data.themePeer)), ("peerColor", ConstructorParameterDescription(_data.peerColor)), ("hostId", ConstructorParameterDescription(_data.hostId)), ("offerMinStars", ConstructorParameterDescription(_data.offerMinStars)), ("craftChancePermille", ConstructorParameterDescription(_data.craftChancePermille))]) - } - } - - public static func parse_starGift(_ reader: BufferReader) -> StarGift? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.Document? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Document - } - var _4: Int64? - _4 = reader.readInt64() - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt32() - } - var _7: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _7 = reader.readInt64() - } - var _8: Int64? - _8 = reader.readInt64() - var _9: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _9 = reader.readInt32() - } - var _10: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _10 = reader.readInt32() - } - var _11: Int64? - if Int(_1!) & Int(1 << 3) != 0 { - _11 = reader.readInt64() - } - var _12: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _12 = reader.readInt64() - } - var _13: String? - if Int(_1!) & Int(1 << 5) != 0 { - _13 = parseString(reader) - } - var _14: Api.Peer? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _14 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _15: Int32? - if Int(_1!) & Int(1 << 8) != 0 { - _15 = reader.readInt32() - } - var _16: Int32? - if Int(_1!) & Int(1 << 8) != 0 { - _16 = reader.readInt32() - } - var _17: Int32? - if Int(_1!) & Int(1 << 9) != 0 { - _17 = reader.readInt32() - } - var _18: String? - if Int(_1!) & Int(1 << 11) != 0 { - _18 = parseString(reader) - } - var _19: Int32? - if Int(_1!) & Int(1 << 11) != 0 { - _19 = reader.readInt32() - } - var _20: Int32? - if Int(_1!) & Int(1 << 11) != 0 { - _20 = reader.readInt32() - } - var _21: Int32? - if Int(_1!) & Int(1 << 12) != 0 { - _21 = reader.readInt32() - } - var _22: Api.StarGiftBackground? - if Int(_1!) & Int(1 << 13) != 0 { - if let signature = reader.readInt32() { - _22 = Api.parse(reader, signature: signature) as? Api.StarGiftBackground - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 8) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 11) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 11) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 11) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 12) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 13) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.StarGift.starGift(Cons_starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16, lockedUntilDate: _17, auctionSlug: _18, giftsPerRound: _19, auctionStartDate: _20, upgradeVariants: _21, background: _22)) - } - else { - return nil - } - } - public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _8: String? - if Int(_1!) & Int(1 << 1) != 0 { - _8 = parseString(reader) - } - var _9: String? - if Int(_1!) & Int(1 << 2) != 0 { - _9 = parseString(reader) - } - var _10: [Api.StarGiftAttribute]? - if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) - } - var _11: Int32? - _11 = reader.readInt32() - var _12: Int32? - _12 = reader.readInt32() - var _13: String? - if Int(_1!) & Int(1 << 3) != 0 { - _13 = parseString(reader) - } - var _14: [Api.StarsAmount]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsAmount.self) - } - } - var _15: Api.Peer? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _16: Int64? - if Int(_1!) & Int(1 << 8) != 0 { - _16 = reader.readInt64() - } - var _17: String? - if Int(_1!) & Int(1 << 8) != 0 { - _17 = parseString(reader) - } - var _18: Int64? - if Int(_1!) & Int(1 << 8) != 0 { - _18 = reader.readInt64() - } - var _19: Api.Peer? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _20: Api.PeerColor? - if Int(_1!) & Int(1 << 11) != 0 { - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.PeerColor - } - } - var _21: Api.Peer? - if Int(_1!) & Int(1 << 12) != 0 { - if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _22: Int32? - if Int(_1!) & Int(1 << 13) != 0 { - _22 = reader.readInt32() - } - var _23: Int32? - if Int(_1!) & Int(1 << 16) != 0 { - _23 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = (Int(_1!) & Int(1 << 3) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 4) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 5) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 8) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 10) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 11) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 12) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 13) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 16) == 0) || _23 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 { - return Api.StarGift.starGiftUnique(Cons_starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, valueUsdAmount: _18, themePeer: _19, peerColor: _20, hostId: _21, offerMinStars: _22, craftChancePermille: _23)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftActiveAuctionState: TypeConstructorDescription { - public class Cons_starGiftActiveAuctionState: TypeConstructorDescription { - public var gift: Api.StarGift - public var state: Api.StarGiftAuctionState - public var userState: Api.StarGiftAuctionUserState - public init(gift: Api.StarGift, state: Api.StarGiftAuctionState, userState: Api.StarGiftAuctionUserState) { - self.gift = gift - self.state = state - self.userState = userState - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftActiveAuctionState", [("gift", ConstructorParameterDescription(self.gift)), ("state", ConstructorParameterDescription(self.state)), ("userState", ConstructorParameterDescription(self.userState))]) - } - } - case starGiftActiveAuctionState(Cons_starGiftActiveAuctionState) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftActiveAuctionState(let _data): - if boxed { - buffer.appendInt32(-753154979) - } - _data.gift.serialize(buffer, true) - _data.state.serialize(buffer, true) - _data.userState.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftActiveAuctionState(let _data): - return ("starGiftActiveAuctionState", [("gift", ConstructorParameterDescription(_data.gift)), ("state", ConstructorParameterDescription(_data.state)), ("userState", ConstructorParameterDescription(_data.userState))]) - } - } - - public static func parse_starGiftActiveAuctionState(_ reader: BufferReader) -> StarGiftActiveAuctionState? { - var _1: Api.StarGift? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _2: Api.StarGiftAuctionState? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState - } - var _3: Api.StarGiftAuctionUserState? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftActiveAuctionState.starGiftActiveAuctionState(Cons_starGiftActiveAuctionState(gift: _1!, state: _2!, userState: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftAttribute: TypeConstructorDescription { - public class Cons_starGiftAttributeBackdrop: TypeConstructorDescription { - public var name: String - public var backdropId: Int32 - public var centerColor: Int32 - public var edgeColor: Int32 - public var patternColor: Int32 - public var textColor: Int32 - public var rarity: Api.StarGiftAttributeRarity - public init(name: String, backdropId: Int32, centerColor: Int32, edgeColor: Int32, patternColor: Int32, textColor: Int32, rarity: Api.StarGiftAttributeRarity) { - self.name = name - self.backdropId = backdropId - self.centerColor = centerColor - self.edgeColor = edgeColor - self.patternColor = patternColor - self.textColor = textColor - self.rarity = rarity - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeBackdrop", [("name", ConstructorParameterDescription(self.name)), ("backdropId", ConstructorParameterDescription(self.backdropId)), ("centerColor", ConstructorParameterDescription(self.centerColor)), ("edgeColor", ConstructorParameterDescription(self.edgeColor)), ("patternColor", ConstructorParameterDescription(self.patternColor)), ("textColor", ConstructorParameterDescription(self.textColor)), ("rarity", ConstructorParameterDescription(self.rarity))]) - } - } - public class Cons_starGiftAttributeModel: TypeConstructorDescription { - public var flags: Int32 - public var name: String - public var document: Api.Document - public var rarity: Api.StarGiftAttributeRarity - public init(flags: Int32, name: String, document: Api.Document, rarity: Api.StarGiftAttributeRarity) { - self.flags = flags - self.name = name - self.document = document - self.rarity = rarity - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeModel", [("flags", ConstructorParameterDescription(self.flags)), ("name", ConstructorParameterDescription(self.name)), ("document", ConstructorParameterDescription(self.document)), ("rarity", ConstructorParameterDescription(self.rarity))]) - } - } - public class Cons_starGiftAttributeOriginalDetails: TypeConstructorDescription { - public var flags: Int32 - public var senderId: Api.Peer? - public var recipientId: Api.Peer - public var date: Int32 - public var message: Api.TextWithEntities? - public init(flags: Int32, senderId: Api.Peer?, recipientId: Api.Peer, date: Int32, message: Api.TextWithEntities?) { - self.flags = flags - self.senderId = senderId - self.recipientId = recipientId - self.date = date - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeOriginalDetails", [("flags", ConstructorParameterDescription(self.flags)), ("senderId", ConstructorParameterDescription(self.senderId)), ("recipientId", ConstructorParameterDescription(self.recipientId)), ("date", ConstructorParameterDescription(self.date)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_starGiftAttributePattern: TypeConstructorDescription { - public var name: String - public var document: Api.Document - public var rarity: Api.StarGiftAttributeRarity - public init(name: String, document: Api.Document, rarity: Api.StarGiftAttributeRarity) { - self.name = name - self.document = document - self.rarity = rarity - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributePattern", [("name", ConstructorParameterDescription(self.name)), ("document", ConstructorParameterDescription(self.document)), ("rarity", ConstructorParameterDescription(self.rarity))]) - } - } - case starGiftAttributeBackdrop(Cons_starGiftAttributeBackdrop) - case starGiftAttributeModel(Cons_starGiftAttributeModel) - case starGiftAttributeOriginalDetails(Cons_starGiftAttributeOriginalDetails) - case starGiftAttributePattern(Cons_starGiftAttributePattern) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAttributeBackdrop(let _data): - if boxed { - buffer.appendInt32(-1624963868) - } - serializeString(_data.name, buffer: buffer, boxed: false) - serializeInt32(_data.backdropId, buffer: buffer, boxed: false) - serializeInt32(_data.centerColor, buffer: buffer, boxed: false) - serializeInt32(_data.edgeColor, buffer: buffer, boxed: false) - serializeInt32(_data.patternColor, buffer: buffer, boxed: false) - serializeInt32(_data.textColor, buffer: buffer, boxed: false) - _data.rarity.serialize(buffer, true) - break - case .starGiftAttributeModel(let _data): - if boxed { - buffer.appendInt32(1448235490) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.name, buffer: buffer, boxed: false) - _data.document.serialize(buffer, true) - _data.rarity.serialize(buffer, true) - break - case .starGiftAttributeOriginalDetails(let _data): - if boxed { - buffer.appendInt32(-524291476) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.senderId!.serialize(buffer, true) - } - _data.recipientId.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.message!.serialize(buffer, true) - } - break - case .starGiftAttributePattern(let _data): - if boxed { - buffer.appendInt32(1315997162) - } - serializeString(_data.name, buffer: buffer, boxed: false) - _data.document.serialize(buffer, true) - _data.rarity.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAttributeBackdrop(let _data): - return ("starGiftAttributeBackdrop", [("name", ConstructorParameterDescription(_data.name)), ("backdropId", ConstructorParameterDescription(_data.backdropId)), ("centerColor", ConstructorParameterDescription(_data.centerColor)), ("edgeColor", ConstructorParameterDescription(_data.edgeColor)), ("patternColor", ConstructorParameterDescription(_data.patternColor)), ("textColor", ConstructorParameterDescription(_data.textColor)), ("rarity", ConstructorParameterDescription(_data.rarity))]) - case .starGiftAttributeModel(let _data): - return ("starGiftAttributeModel", [("flags", ConstructorParameterDescription(_data.flags)), ("name", ConstructorParameterDescription(_data.name)), ("document", ConstructorParameterDescription(_data.document)), ("rarity", ConstructorParameterDescription(_data.rarity))]) - case .starGiftAttributeOriginalDetails(let _data): - return ("starGiftAttributeOriginalDetails", [("flags", ConstructorParameterDescription(_data.flags)), ("senderId", ConstructorParameterDescription(_data.senderId)), ("recipientId", ConstructorParameterDescription(_data.recipientId)), ("date", ConstructorParameterDescription(_data.date)), ("message", ConstructorParameterDescription(_data.message))]) - case .starGiftAttributePattern(let _data): - return ("starGiftAttributePattern", [("name", ConstructorParameterDescription(_data.name)), ("document", ConstructorParameterDescription(_data.document)), ("rarity", ConstructorParameterDescription(_data.rarity))]) - } - } - - public static func parse_starGiftAttributeBackdrop(_ reader: BufferReader) -> StarGiftAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Api.StarGiftAttributeRarity? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarGiftAttribute.starGiftAttributeBackdrop(Cons_starGiftAttributeBackdrop(name: _1!, backdropId: _2!, centerColor: _3!, edgeColor: _4!, patternColor: _5!, textColor: _6!, rarity: _7!)) - } - else { - return nil - } - } - public static func parse_starGiftAttributeModel(_ reader: BufferReader) -> StarGiftAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.Document? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Document - } - var _4: Api.StarGiftAttributeRarity? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StarGiftAttribute.starGiftAttributeModel(Cons_starGiftAttributeModel(flags: _1!, name: _2!, document: _3!, rarity: _4!)) - } - else { - return nil - } - } - public static func parse_starGiftAttributeOriginalDetails(_ reader: BufferReader) -> StarGiftAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Api.Peer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarGiftAttribute.starGiftAttributeOriginalDetails(Cons_starGiftAttributeOriginalDetails(flags: _1!, senderId: _2, recipientId: _3!, date: _4!, message: _5)) - } - else { - return nil - } - } - public static func parse_starGiftAttributePattern(_ reader: BufferReader) -> StarGiftAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: Api.Document? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document - } - var _3: Api.StarGiftAttributeRarity? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StarGiftAttribute.starGiftAttributePattern(Cons_starGiftAttributePattern(name: _1!, document: _2!, rarity: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftAttributeCounter: TypeConstructorDescription { - public class Cons_starGiftAttributeCounter: TypeConstructorDescription { - public var attribute: Api.StarGiftAttributeId - public var count: Int32 - public init(attribute: Api.StarGiftAttributeId, count: Int32) { - self.attribute = attribute - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeCounter", [("attribute", ConstructorParameterDescription(self.attribute)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case starGiftAttributeCounter(Cons_starGiftAttributeCounter) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAttributeCounter(let _data): - if boxed { - buffer.appendInt32(783398488) - } - _data.attribute.serialize(buffer, true) - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAttributeCounter(let _data): - return ("starGiftAttributeCounter", [("attribute", ConstructorParameterDescription(_data.attribute)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_starGiftAttributeCounter(_ reader: BufferReader) -> StarGiftAttributeCounter? { - var _1: Api.StarGiftAttributeId? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeId - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarGiftAttributeCounter.starGiftAttributeCounter(Cons_starGiftAttributeCounter(attribute: _1!, count: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftAttributeId: TypeConstructorDescription { - public class Cons_starGiftAttributeIdBackdrop: TypeConstructorDescription { - public var backdropId: Int32 - public init(backdropId: Int32) { - self.backdropId = backdropId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeIdBackdrop", [("backdropId", ConstructorParameterDescription(self.backdropId))]) - } - } - public class Cons_starGiftAttributeIdModel: TypeConstructorDescription { - public var documentId: Int64 - public init(documentId: Int64) { - self.documentId = documentId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeIdModel", [("documentId", ConstructorParameterDescription(self.documentId))]) - } - } - public class Cons_starGiftAttributeIdPattern: TypeConstructorDescription { - public var documentId: Int64 - public init(documentId: Int64) { - self.documentId = documentId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeIdPattern", [("documentId", ConstructorParameterDescription(self.documentId))]) - } - } - case starGiftAttributeIdBackdrop(Cons_starGiftAttributeIdBackdrop) - case starGiftAttributeIdModel(Cons_starGiftAttributeIdModel) - case starGiftAttributeIdPattern(Cons_starGiftAttributeIdPattern) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAttributeIdBackdrop(let _data): - if boxed { - buffer.appendInt32(520210263) - } - serializeInt32(_data.backdropId, buffer: buffer, boxed: false) - break - case .starGiftAttributeIdModel(let _data): - if boxed { - buffer.appendInt32(1219145276) - } - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - break - case .starGiftAttributeIdPattern(let _data): - if boxed { - buffer.appendInt32(1242965043) - } - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAttributeIdBackdrop(let _data): - return ("starGiftAttributeIdBackdrop", [("backdropId", ConstructorParameterDescription(_data.backdropId))]) - case .starGiftAttributeIdModel(let _data): - return ("starGiftAttributeIdModel", [("documentId", ConstructorParameterDescription(_data.documentId))]) - case .starGiftAttributeIdPattern(let _data): - return ("starGiftAttributeIdPattern", [("documentId", ConstructorParameterDescription(_data.documentId))]) - } - } - - public static func parse_starGiftAttributeIdBackdrop(_ reader: BufferReader) -> StarGiftAttributeId? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdBackdrop(Cons_starGiftAttributeIdBackdrop(backdropId: _1!)) - } - else { - return nil - } - } - public static func parse_starGiftAttributeIdModel(_ reader: BufferReader) -> StarGiftAttributeId? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdModel(Cons_starGiftAttributeIdModel(documentId: _1!)) - } - else { - return nil - } - } - public static func parse_starGiftAttributeIdPattern(_ reader: BufferReader) -> StarGiftAttributeId? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeId.starGiftAttributeIdPattern(Cons_starGiftAttributeIdPattern(documentId: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftAttributeRarity: TypeConstructorDescription { - public class Cons_starGiftAttributeRarity: TypeConstructorDescription { - public var permille: Int32 - public init(permille: Int32) { - self.permille = permille - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAttributeRarity", [("permille", ConstructorParameterDescription(self.permille))]) - } - } - case starGiftAttributeRarity(Cons_starGiftAttributeRarity) - case starGiftAttributeRarityEpic - case starGiftAttributeRarityLegendary - case starGiftAttributeRarityRare - case starGiftAttributeRarityUncommon - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAttributeRarity(let _data): - if boxed { - buffer.appendInt32(910391095) - } - serializeInt32(_data.permille, buffer: buffer, boxed: false) - break - case .starGiftAttributeRarityEpic: - if boxed { - buffer.appendInt32(2029777832) - } - break - case .starGiftAttributeRarityLegendary: - if boxed { - buffer.appendInt32(-822614104) - } - break - case .starGiftAttributeRarityRare: - if boxed { - buffer.appendInt32(-259174037) - } - break - case .starGiftAttributeRarityUncommon: - if boxed { - buffer.appendInt32(-607231095) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAttributeRarity(let _data): - return ("starGiftAttributeRarity", [("permille", ConstructorParameterDescription(_data.permille))]) - case .starGiftAttributeRarityEpic: - return ("starGiftAttributeRarityEpic", []) - case .starGiftAttributeRarityLegendary: - return ("starGiftAttributeRarityLegendary", []) - case .starGiftAttributeRarityRare: - return ("starGiftAttributeRarityRare", []) - case .starGiftAttributeRarityUncommon: - return ("starGiftAttributeRarityUncommon", []) - } - } - - public static func parse_starGiftAttributeRarity(_ reader: BufferReader) -> StarGiftAttributeRarity? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.StarGiftAttributeRarity.starGiftAttributeRarity(Cons_starGiftAttributeRarity(permille: _1!)) - } - else { - return nil - } - } - public static func parse_starGiftAttributeRarityEpic(_ reader: BufferReader) -> StarGiftAttributeRarity? { - return Api.StarGiftAttributeRarity.starGiftAttributeRarityEpic - } - public static func parse_starGiftAttributeRarityLegendary(_ reader: BufferReader) -> StarGiftAttributeRarity? { - return Api.StarGiftAttributeRarity.starGiftAttributeRarityLegendary - } - public static func parse_starGiftAttributeRarityRare(_ reader: BufferReader) -> StarGiftAttributeRarity? { - return Api.StarGiftAttributeRarity.starGiftAttributeRarityRare - } - public static func parse_starGiftAttributeRarityUncommon(_ reader: BufferReader) -> StarGiftAttributeRarity? { - return Api.StarGiftAttributeRarity.starGiftAttributeRarityUncommon - } - } -} -public extension Api { - enum StarGiftAuctionAcquiredGift: TypeConstructorDescription { - public class Cons_starGiftAuctionAcquiredGift: TypeConstructorDescription { + indirect enum SavedDialog: TypeConstructorDescription { + public class Cons_monoForumDialog: TypeConstructorDescription { public var flags: Int32 public var peer: Api.Peer - public var date: Int32 - public var bidAmount: Int64 - public var round: Int32 - public var pos: Int32 - public var message: Api.TextWithEntities? - public var giftNum: Int32? - public init(flags: Int32, peer: Api.Peer, date: Int32, bidAmount: Int64, round: Int32, pos: Int32, message: Api.TextWithEntities?, giftNum: Int32?) { + public var topMessage: Int32 + public var readInboxMaxId: Int32 + public var readOutboxMaxId: Int32 + public var unreadCount: Int32 + public var unreadReactionsCount: Int32 + public var draft: Api.DraftMessage? + public init(flags: Int32, peer: Api.Peer, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadReactionsCount: Int32, draft: Api.DraftMessage?) { self.flags = flags self.peer = peer - self.date = date - self.bidAmount = bidAmount - self.round = round - self.pos = pos - self.message = message - self.giftNum = giftNum + self.topMessage = topMessage + self.readInboxMaxId = readInboxMaxId + self.readOutboxMaxId = readOutboxMaxId + self.unreadCount = unreadCount + self.unreadReactionsCount = unreadReactionsCount + self.draft = draft } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionAcquiredGift", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("bidAmount", ConstructorParameterDescription(self.bidAmount)), ("round", ConstructorParameterDescription(self.round)), ("pos", ConstructorParameterDescription(self.pos)), ("message", ConstructorParameterDescription(self.message)), ("giftNum", ConstructorParameterDescription(self.giftNum))]) + return ("monoForumDialog", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMessage", ConstructorParameterDescription(self.topMessage)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("unreadReactionsCount", ConstructorParameterDescription(self.unreadReactionsCount)), ("draft", ConstructorParameterDescription(self.draft))]) } } - case starGiftAuctionAcquiredGift(Cons_starGiftAuctionAcquiredGift) + public class Cons_savedDialog: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var topMessage: Int32 + public init(flags: Int32, peer: Api.Peer, topMessage: Int32) { + self.flags = flags + self.peer = peer + self.topMessage = topMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedDialog", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMessage", ConstructorParameterDescription(self.topMessage))]) + } + } + case monoForumDialog(Cons_monoForumDialog) + case savedDialog(Cons_savedDialog) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGiftAuctionAcquiredGift(let _data): + case .monoForumDialog(let _data): if boxed { - buffer.appendInt32(1118831432) + buffer.appendInt32(1681948327) } serializeInt32(_data.flags, buffer: buffer, boxed: false) _data.peer.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.bidAmount, buffer: buffer, boxed: false) - serializeInt32(_data.round, buffer: buffer, boxed: false) - serializeInt32(_data.pos, buffer: buffer, boxed: false) + serializeInt32(_data.topMessage, buffer: buffer, boxed: false) + serializeInt32(_data.readInboxMaxId, buffer: buffer, boxed: false) + serializeInt32(_data.readOutboxMaxId, buffer: buffer, boxed: false) + serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) + serializeInt32(_data.unreadReactionsCount, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 1) != 0 { - _data.message!.serialize(buffer, true) + _data.draft!.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) + break + case .savedDialog(let _data): + if boxed { + buffer.appendInt32(-1115174036) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.topMessage, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starGiftAuctionAcquiredGift(let _data): - return ("starGiftAuctionAcquiredGift", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("bidAmount", ConstructorParameterDescription(_data.bidAmount)), ("round", ConstructorParameterDescription(_data.round)), ("pos", ConstructorParameterDescription(_data.pos)), ("message", ConstructorParameterDescription(_data.message)), ("giftNum", ConstructorParameterDescription(_data.giftNum))]) + case .monoForumDialog(let _data): + return ("monoForumDialog", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMessage", ConstructorParameterDescription(_data.topMessage)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("unreadReactionsCount", ConstructorParameterDescription(_data.unreadReactionsCount)), ("draft", ConstructorParameterDescription(_data.draft))]) + case .savedDialog(let _data): + return ("savedDialog", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMessage", ConstructorParameterDescription(_data.topMessage))]) } } - public static func parse_starGiftAuctionAcquiredGift(_ reader: BufferReader) -> StarGiftAuctionAcquiredGift? { + public static func parse_monoForumDialog(_ reader: BufferReader) -> SavedDialog? { var _1: Int32? _1 = reader.readInt32() var _2: Api.Peer? @@ -1507,32 +85,49 @@ public extension Api { } var _3: Int32? _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() var _5: Int32? _5 = reader.readInt32() var _6: Int32? _6 = reader.readInt32() - var _7: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.DraftMessage? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + _8 = Api.parse(reader, signature: signature) as? Api.DraftMessage } } - var _8: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _8 = reader.readInt32() - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.StarGiftAuctionAcquiredGift.starGiftAuctionAcquiredGift(Cons_starGiftAuctionAcquiredGift(flags: _1!, peer: _2!, date: _3!, bidAmount: _4!, round: _5!, pos: _6!, message: _7, giftNum: _8)) + return Api.SavedDialog.monoForumDialog(Cons_monoForumDialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadReactionsCount: _7!, draft: _8)) + } + else { + return nil + } + } + public static func parse_savedDialog(_ reader: BufferReader) -> SavedDialog? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SavedDialog.savedDialog(Cons_savedDialog(flags: _1!, peer: _2!, topMessage: _3!)) } else { return nil @@ -1541,81 +136,404 @@ public extension Api { } } public extension Api { - enum StarGiftAuctionRound: TypeConstructorDescription { - public class Cons_starGiftAuctionRound: TypeConstructorDescription { - public var num: Int32 - public var duration: Int32 - public init(num: Int32, duration: Int32) { - self.num = num - self.duration = duration + enum SavedReactionTag: TypeConstructorDescription { + public class Cons_savedReactionTag: TypeConstructorDescription { + public var flags: Int32 + public var reaction: Api.Reaction + public var title: String? + public var count: Int32 + public init(flags: Int32, reaction: Api.Reaction, title: String?, count: Int32) { + self.flags = flags + self.reaction = reaction + self.title = title + self.count = count } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionRound", [("num", ConstructorParameterDescription(self.num)), ("duration", ConstructorParameterDescription(self.duration))]) + return ("savedReactionTag", [("flags", ConstructorParameterDescription(self.flags)), ("reaction", ConstructorParameterDescription(self.reaction)), ("title", ConstructorParameterDescription(self.title)), ("count", ConstructorParameterDescription(self.count))]) } } - public class Cons_starGiftAuctionRoundExtendable: TypeConstructorDescription { - public var num: Int32 - public var duration: Int32 - public var extendTop: Int32 - public var extendWindow: Int32 - public init(num: Int32, duration: Int32, extendTop: Int32, extendWindow: Int32) { - self.num = num - self.duration = duration - self.extendTop = extendTop - self.extendWindow = extendWindow - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionRoundExtendable", [("num", ConstructorParameterDescription(self.num)), ("duration", ConstructorParameterDescription(self.duration)), ("extendTop", ConstructorParameterDescription(self.extendTop)), ("extendWindow", ConstructorParameterDescription(self.extendWindow))]) - } - } - case starGiftAuctionRound(Cons_starGiftAuctionRound) - case starGiftAuctionRoundExtendable(Cons_starGiftAuctionRoundExtendable) + case savedReactionTag(Cons_savedReactionTag) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGiftAuctionRound(let _data): + case .savedReactionTag(let _data): if boxed { - buffer.appendInt32(984483112) + buffer.appendInt32(-881854424) } - serializeInt32(_data.num, buffer: buffer, boxed: false) - serializeInt32(_data.duration, buffer: buffer, boxed: false) - break - case .starGiftAuctionRoundExtendable(let _data): - if boxed { - buffer.appendInt32(178266597) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.reaction.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) } - serializeInt32(_data.num, buffer: buffer, boxed: false) - serializeInt32(_data.duration, buffer: buffer, boxed: false) - serializeInt32(_data.extendTop, buffer: buffer, boxed: false) - serializeInt32(_data.extendWindow, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starGiftAuctionRound(let _data): - return ("starGiftAuctionRound", [("num", ConstructorParameterDescription(_data.num)), ("duration", ConstructorParameterDescription(_data.duration))]) - case .starGiftAuctionRoundExtendable(let _data): - return ("starGiftAuctionRoundExtendable", [("num", ConstructorParameterDescription(_data.num)), ("duration", ConstructorParameterDescription(_data.duration)), ("extendTop", ConstructorParameterDescription(_data.extendTop)), ("extendWindow", ConstructorParameterDescription(_data.extendWindow))]) + case .savedReactionTag(let _data): + return ("savedReactionTag", [("flags", ConstructorParameterDescription(_data.flags)), ("reaction", ConstructorParameterDescription(_data.reaction)), ("title", ConstructorParameterDescription(_data.title)), ("count", ConstructorParameterDescription(_data.count))]) } } - public static func parse_starGiftAuctionRound(_ reader: BufferReader) -> StarGiftAuctionRound? { + public static func parse_savedReactionTag(_ reader: BufferReader) -> SavedReactionTag? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Api.Reaction? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Reaction + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarGiftAuctionRound.starGiftAuctionRound(Cons_starGiftAuctionRound(num: _1!, duration: _2!)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SavedReactionTag.savedReactionTag(Cons_savedReactionTag(flags: _1!, reaction: _2!, title: _3, count: _4!)) } else { return nil } } - public static func parse_starGiftAuctionRoundExtendable(_ reader: BufferReader) -> StarGiftAuctionRound? { + } +} +public extension Api { + enum SavedStarGift: TypeConstructorDescription { + public class Cons_savedStarGift: TypeConstructorDescription { + public var flags: Int32 + public var fromId: Api.Peer? + public var date: Int32 + public var gift: Api.StarGift + public var message: Api.TextWithEntities? + public var msgId: Int32? + public var savedId: Int64? + public var convertStars: Int64? + public var upgradeStars: Int64? + public var canExportAt: Int32? + public var transferStars: Int64? + public var canTransferAt: Int32? + public var canResellAt: Int32? + public var collectionId: [Int32]? + public var prepaidUpgradeHash: String? + public var dropOriginalDetailsStars: Int64? + public var giftNum: Int32? + public var canCraftAt: Int32? + public init(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?, dropOriginalDetailsStars: Int64?, giftNum: Int32?, canCraftAt: Int32?) { + self.flags = flags + self.fromId = fromId + self.date = date + self.gift = gift + self.message = message + self.msgId = msgId + self.savedId = savedId + self.convertStars = convertStars + self.upgradeStars = upgradeStars + self.canExportAt = canExportAt + self.transferStars = transferStars + self.canTransferAt = canTransferAt + self.canResellAt = canResellAt + self.collectionId = collectionId + self.prepaidUpgradeHash = prepaidUpgradeHash + self.dropOriginalDetailsStars = dropOriginalDetailsStars + self.giftNum = giftNum + self.canCraftAt = canCraftAt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedStarGift", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("date", ConstructorParameterDescription(self.date)), ("gift", ConstructorParameterDescription(self.gift)), ("message", ConstructorParameterDescription(self.message)), ("msgId", ConstructorParameterDescription(self.msgId)), ("savedId", ConstructorParameterDescription(self.savedId)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("canExportAt", ConstructorParameterDescription(self.canExportAt)), ("transferStars", ConstructorParameterDescription(self.transferStars)), ("canTransferAt", ConstructorParameterDescription(self.canTransferAt)), ("canResellAt", ConstructorParameterDescription(self.canResellAt)), ("collectionId", ConstructorParameterDescription(self.collectionId)), ("prepaidUpgradeHash", ConstructorParameterDescription(self.prepaidUpgradeHash)), ("dropOriginalDetailsStars", ConstructorParameterDescription(self.dropOriginalDetailsStars)), ("giftNum", ConstructorParameterDescription(self.giftNum)), ("canCraftAt", ConstructorParameterDescription(self.canCraftAt))]) + } + } + case savedStarGift(Cons_savedStarGift) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedStarGift(let _data): + if boxed { + buffer.appendInt32(1105150972) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.fromId!.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.gift.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.message!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.msgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt64(_data.savedId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.convertStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeInt32(_data.canExportAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt64(_data.transferStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + serializeInt32(_data.canTransferAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + serializeInt32(_data.canResellAt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.collectionId!.count)) + for item in _data.collectionId! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeString(_data.prepaidUpgradeHash!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 18) != 0 { + serializeInt64(_data.dropOriginalDetailsStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 19) != 0 { + serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 20) != 0 { + serializeInt32(_data.canCraftAt!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedStarGift(let _data): + return ("savedStarGift", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("date", ConstructorParameterDescription(_data.date)), ("gift", ConstructorParameterDescription(_data.gift)), ("message", ConstructorParameterDescription(_data.message)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("savedId", ConstructorParameterDescription(_data.savedId)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("canExportAt", ConstructorParameterDescription(_data.canExportAt)), ("transferStars", ConstructorParameterDescription(_data.transferStars)), ("canTransferAt", ConstructorParameterDescription(_data.canTransferAt)), ("canResellAt", ConstructorParameterDescription(_data.canResellAt)), ("collectionId", ConstructorParameterDescription(_data.collectionId)), ("prepaidUpgradeHash", ConstructorParameterDescription(_data.prepaidUpgradeHash)), ("dropOriginalDetailsStars", ConstructorParameterDescription(_data.dropOriginalDetailsStars)), ("giftNum", ConstructorParameterDescription(_data.giftNum)), ("canCraftAt", ConstructorParameterDescription(_data.canCraftAt))]) + } + } + + public static func parse_savedStarGift(_ reader: BufferReader) -> SavedStarGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.StarGift? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _5: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _6 = reader.readInt32() + } + var _7: Int64? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = reader.readInt64() + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _9 = reader.readInt64() + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _10 = reader.readInt32() + } + var _11: Int64? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _11 = reader.readInt64() + } + var _12: Int32? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + _12 = reader.readInt32() + } + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + _13 = reader.readInt32() + } + var _14: [Int32]? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + } + var _15: String? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _15 = parseString(reader) + } + var _16: Int64? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + _16 = reader.readInt64() + } + var _17: Int32? + if Int(_1 ?? 0) & Int(1 << 19) != 0 { + _17 = reader.readInt32() + } + var _18: Int32? + if Int(_1 ?? 0) & Int(1 << 20) != 0 { + _18 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 19) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _18 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { + return Api.SavedStarGift.savedStarGift(Cons_savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16, giftNum: _17, canCraftAt: _18)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SearchPostsFlood: TypeConstructorDescription { + public class Cons_searchPostsFlood: TypeConstructorDescription { + public var flags: Int32 + public var totalDaily: Int32 + public var remains: Int32 + public var waitTill: Int32? + public var starsAmount: Int64 + public init(flags: Int32, totalDaily: Int32, remains: Int32, waitTill: Int32?, starsAmount: Int64) { + self.flags = flags + self.totalDaily = totalDaily + self.remains = remains + self.waitTill = waitTill + self.starsAmount = starsAmount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("searchPostsFlood", [("flags", ConstructorParameterDescription(self.flags)), ("totalDaily", ConstructorParameterDescription(self.totalDaily)), ("remains", ConstructorParameterDescription(self.remains)), ("waitTill", ConstructorParameterDescription(self.waitTill)), ("starsAmount", ConstructorParameterDescription(self.starsAmount))]) + } + } + case searchPostsFlood(Cons_searchPostsFlood) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchPostsFlood(let _data): + if boxed { + buffer.appendInt32(1040931690) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.totalDaily, buffer: buffer, boxed: false) + serializeInt32(_data.remains, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.waitTill!, buffer: buffer, boxed: false) + } + serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .searchPostsFlood(let _data): + return ("searchPostsFlood", [("flags", ConstructorParameterDescription(_data.flags)), ("totalDaily", ConstructorParameterDescription(_data.totalDaily)), ("remains", ConstructorParameterDescription(_data.remains)), ("waitTill", ConstructorParameterDescription(_data.waitTill)), ("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) + } + } + + public static func parse_searchPostsFlood(_ reader: BufferReader) -> SearchPostsFlood? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } + var _5: Int64? + _5 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.SearchPostsFlood.searchPostsFlood(Cons_searchPostsFlood(flags: _1!, totalDaily: _2!, remains: _3!, waitTill: _4, starsAmount: _5!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SearchResultsCalendarPeriod: TypeConstructorDescription { + public class Cons_searchResultsCalendarPeriod: TypeConstructorDescription { + public var date: Int32 + public var minMsgId: Int32 + public var maxMsgId: Int32 + public var count: Int32 + public init(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) { + self.date = date + self.minMsgId = minMsgId + self.maxMsgId = maxMsgId + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("searchResultsCalendarPeriod", [("date", ConstructorParameterDescription(self.date)), ("minMsgId", ConstructorParameterDescription(self.minMsgId)), ("maxMsgId", ConstructorParameterDescription(self.maxMsgId)), ("count", ConstructorParameterDescription(self.count))]) + } + } + case searchResultsCalendarPeriod(Cons_searchResultsCalendarPeriod) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsCalendarPeriod(let _data): + if boxed { + buffer.appendInt32(-911191137) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.minMsgId, buffer: buffer, boxed: false) + serializeInt32(_data.maxMsgId, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .searchResultsCalendarPeriod(let _data): + return ("searchResultsCalendarPeriod", [("date", ConstructorParameterDescription(_data.date)), ("minMsgId", ConstructorParameterDescription(_data.minMsgId)), ("maxMsgId", ConstructorParameterDescription(_data.maxMsgId)), ("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -1629,7 +547,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.StarGiftAuctionRound.starGiftAuctionRoundExtendable(Cons_starGiftAuctionRoundExtendable(num: _1!, duration: _2!, extendTop: _3!, extendWindow: _4!)) + return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(Cons_searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!)) } else { return nil @@ -1638,351 +556,43 @@ public extension Api { } } public extension Api { - enum StarGiftAuctionState: TypeConstructorDescription { - public class Cons_starGiftAuctionState: TypeConstructorDescription { - public var version: Int32 - public var startDate: Int32 - public var endDate: Int32 - public var minBidAmount: Int64 - public var bidLevels: [Api.AuctionBidLevel] - public var topBidders: [Int64] - public var nextRoundAt: Int32 - public var lastGiftNum: Int32 - public var giftsLeft: Int32 - public var currentRound: Int32 - public var totalRounds: Int32 - public var rounds: [Api.StarGiftAuctionRound] - public init(version: Int32, startDate: Int32, endDate: Int32, minBidAmount: Int64, bidLevels: [Api.AuctionBidLevel], topBidders: [Int64], nextRoundAt: Int32, lastGiftNum: Int32, giftsLeft: Int32, currentRound: Int32, totalRounds: Int32, rounds: [Api.StarGiftAuctionRound]) { - self.version = version - self.startDate = startDate - self.endDate = endDate - self.minBidAmount = minBidAmount - self.bidLevels = bidLevels - self.topBidders = topBidders - self.nextRoundAt = nextRoundAt - self.lastGiftNum = lastGiftNum - self.giftsLeft = giftsLeft - self.currentRound = currentRound - self.totalRounds = totalRounds - self.rounds = rounds + enum SearchResultsPosition: TypeConstructorDescription { + public class Cons_searchResultPosition: TypeConstructorDescription { + public var msgId: Int32 + public var date: Int32 + public var offset: Int32 + public init(msgId: Int32, date: Int32, offset: Int32) { + self.msgId = msgId + self.date = date + self.offset = offset } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionState", [("version", ConstructorParameterDescription(self.version)), ("startDate", ConstructorParameterDescription(self.startDate)), ("endDate", ConstructorParameterDescription(self.endDate)), ("minBidAmount", ConstructorParameterDescription(self.minBidAmount)), ("bidLevels", ConstructorParameterDescription(self.bidLevels)), ("topBidders", ConstructorParameterDescription(self.topBidders)), ("nextRoundAt", ConstructorParameterDescription(self.nextRoundAt)), ("lastGiftNum", ConstructorParameterDescription(self.lastGiftNum)), ("giftsLeft", ConstructorParameterDescription(self.giftsLeft)), ("currentRound", ConstructorParameterDescription(self.currentRound)), ("totalRounds", ConstructorParameterDescription(self.totalRounds)), ("rounds", ConstructorParameterDescription(self.rounds))]) + return ("searchResultPosition", [("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("offset", ConstructorParameterDescription(self.offset))]) } } - public class Cons_starGiftAuctionStateFinished: TypeConstructorDescription { - public var flags: Int32 - public var startDate: Int32 - public var endDate: Int32 - public var averagePrice: Int64 - public var listedCount: Int32? - public var fragmentListedCount: Int32? - public var fragmentListedUrl: String? - public init(flags: Int32, startDate: Int32, endDate: Int32, averagePrice: Int64, listedCount: Int32?, fragmentListedCount: Int32?, fragmentListedUrl: String?) { - self.flags = flags - self.startDate = startDate - self.endDate = endDate - self.averagePrice = averagePrice - self.listedCount = listedCount - self.fragmentListedCount = fragmentListedCount - self.fragmentListedUrl = fragmentListedUrl - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionStateFinished", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("endDate", ConstructorParameterDescription(self.endDate)), ("averagePrice", ConstructorParameterDescription(self.averagePrice)), ("listedCount", ConstructorParameterDescription(self.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(self.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(self.fragmentListedUrl))]) - } - } - case starGiftAuctionState(Cons_starGiftAuctionState) - case starGiftAuctionStateFinished(Cons_starGiftAuctionStateFinished) - case starGiftAuctionStateNotModified + case searchResultPosition(Cons_searchResultPosition) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGiftAuctionState(let _data): + case .searchResultPosition(let _data): if boxed { - buffer.appendInt32(1998212710) - } - serializeInt32(_data.version, buffer: buffer, boxed: false) - serializeInt32(_data.startDate, buffer: buffer, boxed: false) - serializeInt32(_data.endDate, buffer: buffer, boxed: false) - serializeInt64(_data.minBidAmount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.bidLevels.count)) - for item in _data.bidLevels { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topBidders.count)) - for item in _data.topBidders { - serializeInt64(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.nextRoundAt, buffer: buffer, boxed: false) - serializeInt32(_data.lastGiftNum, buffer: buffer, boxed: false) - serializeInt32(_data.giftsLeft, buffer: buffer, boxed: false) - serializeInt32(_data.currentRound, buffer: buffer, boxed: false) - serializeInt32(_data.totalRounds, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rounds.count)) - for item in _data.rounds { - item.serialize(buffer, true) - } - break - case .starGiftAuctionStateFinished(let _data): - if boxed { - buffer.appendInt32(-1758614593) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.startDate, buffer: buffer, boxed: false) - serializeInt32(_data.endDate, buffer: buffer, boxed: false) - serializeInt64(_data.averagePrice, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.listedCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.fragmentListedCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.fragmentListedUrl!, buffer: buffer, boxed: false) - } - break - case .starGiftAuctionStateNotModified: - if boxed { - buffer.appendInt32(-30197422) + buffer.appendInt32(2137295719) } + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.offset, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starGiftAuctionState(let _data): - return ("starGiftAuctionState", [("version", ConstructorParameterDescription(_data.version)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("minBidAmount", ConstructorParameterDescription(_data.minBidAmount)), ("bidLevels", ConstructorParameterDescription(_data.bidLevels)), ("topBidders", ConstructorParameterDescription(_data.topBidders)), ("nextRoundAt", ConstructorParameterDescription(_data.nextRoundAt)), ("lastGiftNum", ConstructorParameterDescription(_data.lastGiftNum)), ("giftsLeft", ConstructorParameterDescription(_data.giftsLeft)), ("currentRound", ConstructorParameterDescription(_data.currentRound)), ("totalRounds", ConstructorParameterDescription(_data.totalRounds)), ("rounds", ConstructorParameterDescription(_data.rounds))]) - case .starGiftAuctionStateFinished(let _data): - return ("starGiftAuctionStateFinished", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("averagePrice", ConstructorParameterDescription(_data.averagePrice)), ("listedCount", ConstructorParameterDescription(_data.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(_data.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(_data.fragmentListedUrl))]) - case .starGiftAuctionStateNotModified: - return ("starGiftAuctionStateNotModified", []) + case .searchResultPosition(let _data): + return ("searchResultPosition", [("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("offset", ConstructorParameterDescription(_data.offset))]) } } - public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: [Api.AuctionBidLevel]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AuctionBidLevel.self) - } - var _6: [Int64]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Int32? - _9 = reader.readInt32() - var _10: Int32? - _10 = reader.readInt32() - var _11: Int32? - _11 = reader.readInt32() - var _12: [Api.StarGiftAuctionRound]? - if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAuctionRound.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StarGiftAuctionState.starGiftAuctionState(Cons_starGiftAuctionState(version: _1!, startDate: _2!, endDate: _3!, minBidAmount: _4!, bidLevels: _5!, topBidders: _6!, nextRoundAt: _7!, lastGiftNum: _8!, giftsLeft: _9!, currentRound: _10!, totalRounds: _11!, rounds: _12!)) - } - else { - return nil - } - } - public static func parse_starGiftAuctionStateFinished(_ reader: BufferReader) -> StarGiftAuctionState? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt32() - } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarGiftAuctionState.starGiftAuctionStateFinished(Cons_starGiftAuctionStateFinished(flags: _1!, startDate: _2!, endDate: _3!, averagePrice: _4!, listedCount: _5, fragmentListedCount: _6, fragmentListedUrl: _7)) - } - else { - return nil - } - } - public static func parse_starGiftAuctionStateNotModified(_ reader: BufferReader) -> StarGiftAuctionState? { - return Api.StarGiftAuctionState.starGiftAuctionStateNotModified - } - } -} -public extension Api { - enum StarGiftAuctionUserState: TypeConstructorDescription { - public class Cons_starGiftAuctionUserState: TypeConstructorDescription { - public var flags: Int32 - public var bidAmount: Int64? - public var bidDate: Int32? - public var minBidAmount: Int64? - public var bidPeer: Api.Peer? - public var acquiredCount: Int32 - public init(flags: Int32, bidAmount: Int64?, bidDate: Int32?, minBidAmount: Int64?, bidPeer: Api.Peer?, acquiredCount: Int32) { - self.flags = flags - self.bidAmount = bidAmount - self.bidDate = bidDate - self.minBidAmount = minBidAmount - self.bidPeer = bidPeer - self.acquiredCount = acquiredCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionUserState", [("flags", ConstructorParameterDescription(self.flags)), ("bidAmount", ConstructorParameterDescription(self.bidAmount)), ("bidDate", ConstructorParameterDescription(self.bidDate)), ("minBidAmount", ConstructorParameterDescription(self.minBidAmount)), ("bidPeer", ConstructorParameterDescription(self.bidPeer)), ("acquiredCount", ConstructorParameterDescription(self.acquiredCount))]) - } - } - case starGiftAuctionUserState(Cons_starGiftAuctionUserState) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAuctionUserState(let _data): - if boxed { - buffer.appendInt32(787403204) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.bidAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.bidDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.minBidAmount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.bidPeer!.serialize(buffer, true) - } - serializeInt32(_data.acquiredCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAuctionUserState(let _data): - return ("starGiftAuctionUserState", [("flags", ConstructorParameterDescription(_data.flags)), ("bidAmount", ConstructorParameterDescription(_data.bidAmount)), ("bidDate", ConstructorParameterDescription(_data.bidDate)), ("minBidAmount", ConstructorParameterDescription(_data.minBidAmount)), ("bidPeer", ConstructorParameterDescription(_data.bidPeer)), ("acquiredCount", ConstructorParameterDescription(_data.acquiredCount))]) - } - } - - public static func parse_starGiftAuctionUserState(_ reader: BufferReader) -> StarGiftAuctionUserState? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt64() - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt64() - } - var _5: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGiftAuctionUserState.starGiftAuctionUserState(Cons_starGiftAuctionUserState(flags: _1!, bidAmount: _2, bidDate: _3, minBidAmount: _4, bidPeer: _5, acquiredCount: _6!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarGiftBackground: TypeConstructorDescription { - public class Cons_starGiftBackground: TypeConstructorDescription { - public var centerColor: Int32 - public var edgeColor: Int32 - public var textColor: Int32 - public init(centerColor: Int32, edgeColor: Int32, textColor: Int32) { - self.centerColor = centerColor - self.edgeColor = edgeColor - self.textColor = textColor - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftBackground", [("centerColor", ConstructorParameterDescription(self.centerColor)), ("edgeColor", ConstructorParameterDescription(self.edgeColor)), ("textColor", ConstructorParameterDescription(self.textColor))]) - } - } - case starGiftBackground(Cons_starGiftBackground) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftBackground(let _data): - if boxed { - buffer.appendInt32(-1342872680) - } - serializeInt32(_data.centerColor, buffer: buffer, boxed: false) - serializeInt32(_data.edgeColor, buffer: buffer, boxed: false) - serializeInt32(_data.textColor, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftBackground(let _data): - return ("starGiftBackground", [("centerColor", ConstructorParameterDescription(_data.centerColor)), ("edgeColor", ConstructorParameterDescription(_data.edgeColor)), ("textColor", ConstructorParameterDescription(_data.textColor))]) - } - } - - public static func parse_starGiftBackground(_ reader: BufferReader) -> StarGiftBackground? { + public static func parse_searchResultPosition(_ reader: BufferReader) -> SearchResultsPosition? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -1993,7 +603,7 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.StarGiftBackground.starGiftBackground(Cons_starGiftBackground(centerColor: _1!, edgeColor: _2!, textColor: _3!)) + return Api.SearchResultsPosition.searchResultPosition(Cons_searchResultPosition(msgId: _1!, date: _2!, offset: _3!)) } else { return nil @@ -2002,78 +612,54 @@ public extension Api { } } public extension Api { - enum StarGiftCollection: TypeConstructorDescription { - public class Cons_starGiftCollection: TypeConstructorDescription { - public var flags: Int32 - public var collectionId: Int32 - public var title: String - public var icon: Api.Document? - public var giftsCount: Int32 - public var hash: Int64 - public init(flags: Int32, collectionId: Int32, title: String, icon: Api.Document?, giftsCount: Int32, hash: Int64) { - self.flags = flags - self.collectionId = collectionId - self.title = title - self.icon = icon - self.giftsCount = giftsCount + enum SecureCredentialsEncrypted: TypeConstructorDescription { + public class Cons_secureCredentialsEncrypted: TypeConstructorDescription { + public var data: Buffer + public var hash: Buffer + public var secret: Buffer + public init(data: Buffer, hash: Buffer, secret: Buffer) { + self.data = data self.hash = hash + self.secret = secret } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftCollection", [("flags", ConstructorParameterDescription(self.flags)), ("collectionId", ConstructorParameterDescription(self.collectionId)), ("title", ConstructorParameterDescription(self.title)), ("icon", ConstructorParameterDescription(self.icon)), ("giftsCount", ConstructorParameterDescription(self.giftsCount)), ("hash", ConstructorParameterDescription(self.hash))]) + return ("secureCredentialsEncrypted", [("data", ConstructorParameterDescription(self.data)), ("hash", ConstructorParameterDescription(self.hash)), ("secret", ConstructorParameterDescription(self.secret))]) } } - case starGiftCollection(Cons_starGiftCollection) + case secureCredentialsEncrypted(Cons_secureCredentialsEncrypted) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGiftCollection(let _data): + case .secureCredentialsEncrypted(let _data): if boxed { - buffer.appendInt32(-1653926992) + buffer.appendInt32(871426631) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.collectionId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.icon!.serialize(buffer, true) - } - serializeInt32(_data.giftsCount, buffer: buffer, boxed: false) - serializeInt64(_data.hash, buffer: buffer, boxed: false) + serializeBytes(_data.data, buffer: buffer, boxed: false) + serializeBytes(_data.hash, buffer: buffer, boxed: false) + serializeBytes(_data.secret, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starGiftCollection(let _data): - return ("starGiftCollection", [("flags", ConstructorParameterDescription(_data.flags)), ("collectionId", ConstructorParameterDescription(_data.collectionId)), ("title", ConstructorParameterDescription(_data.title)), ("icon", ConstructorParameterDescription(_data.icon)), ("giftsCount", ConstructorParameterDescription(_data.giftsCount)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .secureCredentialsEncrypted(let _data): + return ("secureCredentialsEncrypted", [("data", ConstructorParameterDescription(_data.data)), ("hash", ConstructorParameterDescription(_data.hash)), ("secret", ConstructorParameterDescription(_data.secret))]) } } - public static func parse_starGiftCollection(_ reader: BufferReader) -> StarGiftCollection? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Document - } - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int64? - _6 = reader.readInt64() + public static func parse_secureCredentialsEncrypted(_ reader: BufferReader) -> SecureCredentialsEncrypted? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarGiftCollection.starGiftCollection(Cons_starGiftCollection(flags: _1!, collectionId: _2!, title: _3!, icon: _4, giftsCount: _5!, hash: _6!)) + if _c1 && _c2 && _c3 { + return Api.SecureCredentialsEncrypted.secureCredentialsEncrypted(Cons_secureCredentialsEncrypted(data: _1!, hash: _2!, secret: _3!)) } else { return nil @@ -2082,48 +668,389 @@ public extension Api { } } public extension Api { - enum StarGiftUpgradePrice: TypeConstructorDescription { - public class Cons_starGiftUpgradePrice: TypeConstructorDescription { - public var date: Int32 - public var upgradeStars: Int64 - public init(date: Int32, upgradeStars: Int64) { - self.date = date - self.upgradeStars = upgradeStars + enum SecureData: TypeConstructorDescription { + public class Cons_secureData: TypeConstructorDescription { + public var data: Buffer + public var dataHash: Buffer + public var secret: Buffer + public init(data: Buffer, dataHash: Buffer, secret: Buffer) { + self.data = data + self.dataHash = dataHash + self.secret = secret } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftUpgradePrice", [("date", ConstructorParameterDescription(self.date)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars))]) + return ("secureData", [("data", ConstructorParameterDescription(self.data)), ("dataHash", ConstructorParameterDescription(self.dataHash)), ("secret", ConstructorParameterDescription(self.secret))]) } } - case starGiftUpgradePrice(Cons_starGiftUpgradePrice) + case secureData(Cons_secureData) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGiftUpgradePrice(let _data): + case .secureData(let _data): if boxed { - buffer.appendInt32(-1712704739) + buffer.appendInt32(-1964327229) } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.upgradeStars, buffer: buffer, boxed: false) + serializeBytes(_data.data, buffer: buffer, boxed: false) + serializeBytes(_data.dataHash, buffer: buffer, boxed: false) + serializeBytes(_data.secret, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starGiftUpgradePrice(let _data): - return ("starGiftUpgradePrice", [("date", ConstructorParameterDescription(_data.date)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars))]) + case .secureData(let _data): + return ("secureData", [("data", ConstructorParameterDescription(_data.data)), ("dataHash", ConstructorParameterDescription(_data.dataHash)), ("secret", ConstructorParameterDescription(_data.secret))]) } } - public static func parse_starGiftUpgradePrice(_ reader: BufferReader) -> StarGiftUpgradePrice? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_secureData(_ reader: BufferReader) -> SecureData? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureData.secureData(Cons_secureData(data: _1!, dataHash: _2!, secret: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SecureFile: TypeConstructorDescription { + public class Cons_secureFile: TypeConstructorDescription { + public var id: Int64 + public var accessHash: Int64 + public var size: Int64 + public var dcId: Int32 + public var date: Int32 + public var fileHash: Buffer + public var secret: Buffer + public init(id: Int64, accessHash: Int64, size: Int64, dcId: Int32, date: Int32, fileHash: Buffer, secret: Buffer) { + self.id = id + self.accessHash = accessHash + self.size = size + self.dcId = dcId + self.date = date + self.fileHash = fileHash + self.secret = secret + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureFile", [("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("size", ConstructorParameterDescription(self.size)), ("dcId", ConstructorParameterDescription(self.dcId)), ("date", ConstructorParameterDescription(self.date)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("secret", ConstructorParameterDescription(self.secret))]) + } + } + case secureFile(Cons_secureFile) + case secureFileEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureFile(let _data): + if boxed { + buffer.appendInt32(2097791614) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt64(_data.size, buffer: buffer, boxed: false) + serializeInt32(_data.dcId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeBytes(_data.secret, buffer: buffer, boxed: false) + break + case .secureFileEmpty: + if boxed { + buffer.appendInt32(1679398724) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureFile(let _data): + return ("secureFile", [("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("size", ConstructorParameterDescription(_data.size)), ("dcId", ConstructorParameterDescription(_data.dcId)), ("date", ConstructorParameterDescription(_data.date)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("secret", ConstructorParameterDescription(_data.secret))]) + case .secureFileEmpty: + return ("secureFileEmpty", []) + } + } + + public static func parse_secureFile(_ reader: BufferReader) -> SecureFile? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.SecureFile.secureFile(Cons_secureFile(id: _1!, accessHash: _2!, size: _3!, dcId: _4!, date: _5!, fileHash: _6!, secret: _7!)) + } + else { + return nil + } + } + public static func parse_secureFileEmpty(_ reader: BufferReader) -> SecureFile? { + return Api.SecureFile.secureFileEmpty + } + } +} +public extension Api { + enum SecurePasswordKdfAlgo: TypeConstructorDescription { + public class Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000: TypeConstructorDescription { + public var salt: Buffer + public init(salt: Buffer) { + self.salt = salt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", ConstructorParameterDescription(self.salt))]) + } + } + public class Cons_securePasswordKdfAlgoSHA512: TypeConstructorDescription { + public var salt: Buffer + public init(salt: Buffer) { + self.salt = salt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("securePasswordKdfAlgoSHA512", [("salt", ConstructorParameterDescription(self.salt))]) + } + } + case securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000) + case securePasswordKdfAlgoSHA512(Cons_securePasswordKdfAlgoSHA512) + case securePasswordKdfAlgoUnknown + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let _data): + if boxed { + buffer.appendInt32(-1141711456) + } + serializeBytes(_data.salt, buffer: buffer, boxed: false) + break + case .securePasswordKdfAlgoSHA512(let _data): + if boxed { + buffer.appendInt32(-2042159726) + } + serializeBytes(_data.salt, buffer: buffer, boxed: false) + break + case .securePasswordKdfAlgoUnknown: + if boxed { + buffer.appendInt32(4883767) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(let _data): + return ("securePasswordKdfAlgoPBKDF2HMACSHA512iter100000", [("salt", ConstructorParameterDescription(_data.salt))]) + case .securePasswordKdfAlgoSHA512(let _data): + return ("securePasswordKdfAlgoSHA512", [("salt", ConstructorParameterDescription(_data.salt))]) + case .securePasswordKdfAlgoUnknown: + return ("securePasswordKdfAlgoUnknown", []) + } + } + + public static func parse_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(Cons_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(salt: _1!)) + } + else { + return nil + } + } + public static func parse_securePasswordKdfAlgoSHA512(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoSHA512(Cons_securePasswordKdfAlgoSHA512(salt: _1!)) + } + else { + return nil + } + } + public static func parse_securePasswordKdfAlgoUnknown(_ reader: BufferReader) -> SecurePasswordKdfAlgo? { + return Api.SecurePasswordKdfAlgo.securePasswordKdfAlgoUnknown + } + } +} +public extension Api { + enum SecurePlainData: TypeConstructorDescription { + public class Cons_securePlainEmail: TypeConstructorDescription { + public var email: String + public init(email: String) { + self.email = email + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("securePlainEmail", [("email", ConstructorParameterDescription(self.email))]) + } + } + public class Cons_securePlainPhone: TypeConstructorDescription { + public var phone: String + public init(phone: String) { + self.phone = phone + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("securePlainPhone", [("phone", ConstructorParameterDescription(self.phone))]) + } + } + case securePlainEmail(Cons_securePlainEmail) + case securePlainPhone(Cons_securePlainPhone) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .securePlainEmail(let _data): + if boxed { + buffer.appendInt32(569137759) + } + serializeString(_data.email, buffer: buffer, boxed: false) + break + case .securePlainPhone(let _data): + if boxed { + buffer.appendInt32(2103482845) + } + serializeString(_data.phone, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .securePlainEmail(let _data): + return ("securePlainEmail", [("email", ConstructorParameterDescription(_data.email))]) + case .securePlainPhone(let _data): + return ("securePlainPhone", [("phone", ConstructorParameterDescription(_data.phone))]) + } + } + + public static func parse_securePlainEmail(_ reader: BufferReader) -> SecurePlainData? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SecurePlainData.securePlainEmail(Cons_securePlainEmail(email: _1!)) + } + else { + return nil + } + } + public static func parse_securePlainPhone(_ reader: BufferReader) -> SecurePlainData? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SecurePlainData.securePlainPhone(Cons_securePlainPhone(phone: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SecureRequiredType: TypeConstructorDescription { + public class Cons_secureRequiredType: TypeConstructorDescription { + public var flags: Int32 + public var type: Api.SecureValueType + public init(flags: Int32, type: Api.SecureValueType) { + self.flags = flags + self.type = type + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureRequiredType", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type))]) + } + } + public class Cons_secureRequiredTypeOneOf: TypeConstructorDescription { + public var types: [Api.SecureRequiredType] + public init(types: [Api.SecureRequiredType]) { + self.types = types + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureRequiredTypeOneOf", [("types", ConstructorParameterDescription(self.types))]) + } + } + case secureRequiredType(Cons_secureRequiredType) + case secureRequiredTypeOneOf(Cons_secureRequiredTypeOneOf) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureRequiredType(let _data): + if boxed { + buffer.appendInt32(-2103600678) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.type.serialize(buffer, true) + break + case .secureRequiredTypeOneOf(let _data): + if boxed { + buffer.appendInt32(41187252) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.types.count)) + for item in _data.types { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureRequiredType(let _data): + return ("secureRequiredType", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type))]) + case .secureRequiredTypeOneOf(let _data): + return ("secureRequiredTypeOneOf", [("types", ConstructorParameterDescription(_data.types))]) + } + } + + public static func parse_secureRequiredType(_ reader: BufferReader) -> SecureRequiredType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.SecureValueType? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StarGiftUpgradePrice.starGiftUpgradePrice(Cons_starGiftUpgradePrice(date: _1!, upgradeStars: _2!)) + return Api.SecureRequiredType.secureRequiredType(Cons_secureRequiredType(flags: _1!, type: _2!)) + } + else { + return nil + } + } + public static func parse_secureRequiredTypeOneOf(_ reader: BufferReader) -> SecureRequiredType? { + var _1: [Api.SecureRequiredType]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.SecureRequiredType.secureRequiredTypeOneOf(Cons_secureRequiredTypeOneOf(types: _1!)) } else { return nil @@ -2132,86 +1059,56 @@ public extension Api { } } public extension Api { - enum StarRefProgram: TypeConstructorDescription { - public class Cons_starRefProgram: TypeConstructorDescription { - public var flags: Int32 - public var botId: Int64 - public var commissionPermille: Int32 - public var durationMonths: Int32? - public var endDate: Int32? - public var dailyRevenuePerUser: Api.StarsAmount? - public init(flags: Int32, botId: Int64, commissionPermille: Int32, durationMonths: Int32?, endDate: Int32?, dailyRevenuePerUser: Api.StarsAmount?) { - self.flags = flags - self.botId = botId - self.commissionPermille = commissionPermille - self.durationMonths = durationMonths - self.endDate = endDate - self.dailyRevenuePerUser = dailyRevenuePerUser + enum SecureSecretSettings: TypeConstructorDescription { + public class Cons_secureSecretSettings: TypeConstructorDescription { + public var secureAlgo: Api.SecurePasswordKdfAlgo + public var secureSecret: Buffer + public var secureSecretId: Int64 + public init(secureAlgo: Api.SecurePasswordKdfAlgo, secureSecret: Buffer, secureSecretId: Int64) { + self.secureAlgo = secureAlgo + self.secureSecret = secureSecret + self.secureSecretId = secureSecretId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starRefProgram", [("flags", ConstructorParameterDescription(self.flags)), ("botId", ConstructorParameterDescription(self.botId)), ("commissionPermille", ConstructorParameterDescription(self.commissionPermille)), ("durationMonths", ConstructorParameterDescription(self.durationMonths)), ("endDate", ConstructorParameterDescription(self.endDate)), ("dailyRevenuePerUser", ConstructorParameterDescription(self.dailyRevenuePerUser))]) + return ("secureSecretSettings", [("secureAlgo", ConstructorParameterDescription(self.secureAlgo)), ("secureSecret", ConstructorParameterDescription(self.secureSecret)), ("secureSecretId", ConstructorParameterDescription(self.secureSecretId))]) } } - case starRefProgram(Cons_starRefProgram) + case secureSecretSettings(Cons_secureSecretSettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starRefProgram(let _data): + case .secureSecretSettings(let _data): if boxed { - buffer.appendInt32(-586389774) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeInt32(_data.commissionPermille, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.durationMonths!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.endDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.dailyRevenuePerUser!.serialize(buffer, true) + buffer.appendInt32(354925740) } + _data.secureAlgo.serialize(buffer, true) + serializeBytes(_data.secureSecret, buffer: buffer, boxed: false) + serializeInt64(_data.secureSecretId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starRefProgram(let _data): - return ("starRefProgram", [("flags", ConstructorParameterDescription(_data.flags)), ("botId", ConstructorParameterDescription(_data.botId)), ("commissionPermille", ConstructorParameterDescription(_data.commissionPermille)), ("durationMonths", ConstructorParameterDescription(_data.durationMonths)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("dailyRevenuePerUser", ConstructorParameterDescription(_data.dailyRevenuePerUser))]) + case .secureSecretSettings(let _data): + return ("secureSecretSettings", [("secureAlgo", ConstructorParameterDescription(_data.secureAlgo)), ("secureSecret", ConstructorParameterDescription(_data.secureSecret)), ("secureSecretId", ConstructorParameterDescription(_data.secureSecretId))]) } } - public static func parse_starRefProgram(_ reader: BufferReader) -> StarRefProgram? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() - } - var _6: Api.StarsAmount? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } + public static func parse_secureSecretSettings(_ reader: BufferReader) -> SecureSecretSettings? { + var _1: Api.SecurePasswordKdfAlgo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StarRefProgram.starRefProgram(Cons_starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6)) + if _c1 && _c2 && _c3 { + return Api.SecureSecretSettings.secureSecretSettings(Cons_secureSecretSettings(secureAlgo: _1!, secureSecret: _2!, secureSecretId: _3!)) } else { return nil @@ -2219,3 +1116,783 @@ public extension Api { } } } +public extension Api { + enum SecureValue: TypeConstructorDescription { + public class Cons_secureValue: TypeConstructorDescription { + public var flags: Int32 + public var type: Api.SecureValueType + public var data: Api.SecureData? + public var frontSide: Api.SecureFile? + public var reverseSide: Api.SecureFile? + public var selfie: Api.SecureFile? + public var translation: [Api.SecureFile]? + public var files: [Api.SecureFile]? + public var plainData: Api.SecurePlainData? + public var hash: Buffer + public init(flags: Int32, type: Api.SecureValueType, data: Api.SecureData?, frontSide: Api.SecureFile?, reverseSide: Api.SecureFile?, selfie: Api.SecureFile?, translation: [Api.SecureFile]?, files: [Api.SecureFile]?, plainData: Api.SecurePlainData?, hash: Buffer) { + self.flags = flags + self.type = type + self.data = data + self.frontSide = frontSide + self.reverseSide = reverseSide + self.selfie = selfie + self.translation = translation + self.files = files + self.plainData = plainData + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValue", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("data", ConstructorParameterDescription(self.data)), ("frontSide", ConstructorParameterDescription(self.frontSide)), ("reverseSide", ConstructorParameterDescription(self.reverseSide)), ("selfie", ConstructorParameterDescription(self.selfie)), ("translation", ConstructorParameterDescription(self.translation)), ("files", ConstructorParameterDescription(self.files)), ("plainData", ConstructorParameterDescription(self.plainData)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case secureValue(Cons_secureValue) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureValue(let _data): + if boxed { + buffer.appendInt32(411017418) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.type.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.data!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.frontSide!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.reverseSide!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.selfie!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.translation!.count)) + for item in _data.translation! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.files!.count)) + for item in _data.files! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.plainData!.serialize(buffer, true) + } + serializeBytes(_data.hash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureValue(let _data): + return ("secureValue", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("data", ConstructorParameterDescription(_data.data)), ("frontSide", ConstructorParameterDescription(_data.frontSide)), ("reverseSide", ConstructorParameterDescription(_data.reverseSide)), ("selfie", ConstructorParameterDescription(_data.selfie)), ("translation", ConstructorParameterDescription(_data.translation)), ("files", ConstructorParameterDescription(_data.files)), ("plainData", ConstructorParameterDescription(_data.plainData)), ("hash", ConstructorParameterDescription(_data.hash))]) + } + } + + public static func parse_secureValue(_ reader: BufferReader) -> SecureValue? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.SecureValueType? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _3: Api.SecureData? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SecureData + } + } + var _4: Api.SecureFile? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.SecureFile + } + } + var _5: Api.SecureFile? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.SecureFile + } + } + var _6: Api.SecureFile? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.SecureFile + } + } + var _7: [Api.SecureFile]? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) + } + } + var _8: [Api.SecureFile]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureFile.self) + } + } + var _9: Api.SecurePlainData? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.SecurePlainData + } + } + var _10: Buffer? + _10 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.SecureValue.secureValue(Cons_secureValue(flags: _1!, type: _2!, data: _3, frontSide: _4, reverseSide: _5, selfie: _6, translation: _7, files: _8, plainData: _9, hash: _10!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SecureValueError: TypeConstructorDescription { + public class Cons_secureValueError: TypeConstructorDescription { + public var type: Api.SecureValueType + public var hash: Buffer + public var text: String + public init(type: Api.SecureValueType, hash: Buffer, text: String) { + self.type = type + self.hash = hash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueError", [("type", ConstructorParameterDescription(self.type)), ("hash", ConstructorParameterDescription(self.hash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorData: TypeConstructorDescription { + public var type: Api.SecureValueType + public var dataHash: Buffer + public var field: String + public var text: String + public init(type: Api.SecureValueType, dataHash: Buffer, field: String, text: String) { + self.type = type + self.dataHash = dataHash + self.field = field + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorData", [("type", ConstructorParameterDescription(self.type)), ("dataHash", ConstructorParameterDescription(self.dataHash)), ("field", ConstructorParameterDescription(self.field)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorFile: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: Buffer + public var text: String + public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorFile", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorFiles: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: [Buffer] + public var text: String + public init(type: Api.SecureValueType, fileHash: [Buffer], text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorFiles", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorFrontSide: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: Buffer + public var text: String + public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorFrontSide", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorReverseSide: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: Buffer + public var text: String + public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorReverseSide", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorSelfie: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: Buffer + public var text: String + public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorSelfie", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorTranslationFile: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: Buffer + public var text: String + public init(type: Api.SecureValueType, fileHash: Buffer, text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorTranslationFile", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_secureValueErrorTranslationFiles: TypeConstructorDescription { + public var type: Api.SecureValueType + public var fileHash: [Buffer] + public var text: String + public init(type: Api.SecureValueType, fileHash: [Buffer], text: String) { + self.type = type + self.fileHash = fileHash + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueErrorTranslationFiles", [("type", ConstructorParameterDescription(self.type)), ("fileHash", ConstructorParameterDescription(self.fileHash)), ("text", ConstructorParameterDescription(self.text))]) + } + } + case secureValueError(Cons_secureValueError) + case secureValueErrorData(Cons_secureValueErrorData) + case secureValueErrorFile(Cons_secureValueErrorFile) + case secureValueErrorFiles(Cons_secureValueErrorFiles) + case secureValueErrorFrontSide(Cons_secureValueErrorFrontSide) + case secureValueErrorReverseSide(Cons_secureValueErrorReverseSide) + case secureValueErrorSelfie(Cons_secureValueErrorSelfie) + case secureValueErrorTranslationFile(Cons_secureValueErrorTranslationFile) + case secureValueErrorTranslationFiles(Cons_secureValueErrorTranslationFiles) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureValueError(let _data): + if boxed { + buffer.appendInt32(-2036501105) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.hash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorData(let _data): + if boxed { + buffer.appendInt32(-391902247) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.dataHash, buffer: buffer, boxed: false) + serializeString(_data.field, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorFile(let _data): + if boxed { + buffer.appendInt32(2054162547) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorFiles(let _data): + if boxed { + buffer.appendInt32(1717706985) + } + _data.type.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.fileHash.count)) + for item in _data.fileHash { + serializeBytes(item, buffer: buffer, boxed: false) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorFrontSide(let _data): + if boxed { + buffer.appendInt32(12467706) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorReverseSide(let _data): + if boxed { + buffer.appendInt32(-2037765467) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorSelfie(let _data): + if boxed { + buffer.appendInt32(-449327402) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorTranslationFile(let _data): + if boxed { + buffer.appendInt32(-1592506512) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.fileHash, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .secureValueErrorTranslationFiles(let _data): + if boxed { + buffer.appendInt32(878931416) + } + _data.type.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.fileHash.count)) + for item in _data.fileHash { + serializeBytes(item, buffer: buffer, boxed: false) + } + serializeString(_data.text, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureValueError(let _data): + return ("secureValueError", [("type", ConstructorParameterDescription(_data.type)), ("hash", ConstructorParameterDescription(_data.hash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorData(let _data): + return ("secureValueErrorData", [("type", ConstructorParameterDescription(_data.type)), ("dataHash", ConstructorParameterDescription(_data.dataHash)), ("field", ConstructorParameterDescription(_data.field)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorFile(let _data): + return ("secureValueErrorFile", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorFiles(let _data): + return ("secureValueErrorFiles", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorFrontSide(let _data): + return ("secureValueErrorFrontSide", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorReverseSide(let _data): + return ("secureValueErrorReverseSide", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorSelfie(let _data): + return ("secureValueErrorSelfie", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorTranslationFile(let _data): + return ("secureValueErrorTranslationFile", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + case .secureValueErrorTranslationFiles(let _data): + return ("secureValueErrorTranslationFiles", [("type", ConstructorParameterDescription(_data.type)), ("fileHash", ConstructorParameterDescription(_data.fileHash)), ("text", ConstructorParameterDescription(_data.text))]) + } + } + + public static func parse_secureValueError(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueError(Cons_secureValueError(type: _1!, hash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorData(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SecureValueError.secureValueErrorData(Cons_secureValueErrorData(type: _1!, dataHash: _2!, field: _3!, text: _4!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorFile(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorFile(Cons_secureValueErrorFile(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorFiles(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorFiles(Cons_secureValueErrorFiles(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorFrontSide(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorFrontSide(Cons_secureValueErrorFrontSide(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorReverseSide(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorReverseSide(Cons_secureValueErrorReverseSide(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorSelfie(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorSelfie(Cons_secureValueErrorSelfie(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorTranslationFile(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorTranslationFile(Cons_secureValueErrorTranslationFile(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + public static func parse_secureValueErrorTranslationFiles(_ reader: BufferReader) -> SecureValueError? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: [Buffer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SecureValueError.secureValueErrorTranslationFiles(Cons_secureValueErrorTranslationFiles(type: _1!, fileHash: _2!, text: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SecureValueHash: TypeConstructorDescription { + public class Cons_secureValueHash: TypeConstructorDescription { + public var type: Api.SecureValueType + public var hash: Buffer + public init(type: Api.SecureValueType, hash: Buffer) { + self.type = type + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("secureValueHash", [("type", ConstructorParameterDescription(self.type)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case secureValueHash(Cons_secureValueHash) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureValueHash(let _data): + if boxed { + buffer.appendInt32(-316748368) + } + _data.type.serialize(buffer, true) + serializeBytes(_data.hash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureValueHash(let _data): + return ("secureValueHash", [("type", ConstructorParameterDescription(_data.type)), ("hash", ConstructorParameterDescription(_data.hash))]) + } + } + + public static func parse_secureValueHash(_ reader: BufferReader) -> SecureValueHash? { + var _1: Api.SecureValueType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.SecureValueType + } + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.SecureValueHash.secureValueHash(Cons_secureValueHash(type: _1!, hash: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SecureValueType: TypeConstructorDescription { + case secureValueTypeAddress + case secureValueTypeBankStatement + case secureValueTypeDriverLicense + case secureValueTypeEmail + case secureValueTypeIdentityCard + case secureValueTypeInternalPassport + case secureValueTypePassport + case secureValueTypePassportRegistration + case secureValueTypePersonalDetails + case secureValueTypePhone + case secureValueTypeRentalAgreement + case secureValueTypeTemporaryRegistration + case secureValueTypeUtilityBill + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .secureValueTypeAddress: + if boxed { + buffer.appendInt32(-874308058) + } + break + case .secureValueTypeBankStatement: + if boxed { + buffer.appendInt32(-1995211763) + } + break + case .secureValueTypeDriverLicense: + if boxed { + buffer.appendInt32(115615172) + } + break + case .secureValueTypeEmail: + if boxed { + buffer.appendInt32(-1908627474) + } + break + case .secureValueTypeIdentityCard: + if boxed { + buffer.appendInt32(-1596951477) + } + break + case .secureValueTypeInternalPassport: + if boxed { + buffer.appendInt32(-1717268701) + } + break + case .secureValueTypePassport: + if boxed { + buffer.appendInt32(1034709504) + } + break + case .secureValueTypePassportRegistration: + if boxed { + buffer.appendInt32(-1713143702) + } + break + case .secureValueTypePersonalDetails: + if boxed { + buffer.appendInt32(-1658158621) + } + break + case .secureValueTypePhone: + if boxed { + buffer.appendInt32(-1289704741) + } + break + case .secureValueTypeRentalAgreement: + if boxed { + buffer.appendInt32(-1954007928) + } + break + case .secureValueTypeTemporaryRegistration: + if boxed { + buffer.appendInt32(-368907213) + } + break + case .secureValueTypeUtilityBill: + if boxed { + buffer.appendInt32(-63531698) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .secureValueTypeAddress: + return ("secureValueTypeAddress", []) + case .secureValueTypeBankStatement: + return ("secureValueTypeBankStatement", []) + case .secureValueTypeDriverLicense: + return ("secureValueTypeDriverLicense", []) + case .secureValueTypeEmail: + return ("secureValueTypeEmail", []) + case .secureValueTypeIdentityCard: + return ("secureValueTypeIdentityCard", []) + case .secureValueTypeInternalPassport: + return ("secureValueTypeInternalPassport", []) + case .secureValueTypePassport: + return ("secureValueTypePassport", []) + case .secureValueTypePassportRegistration: + return ("secureValueTypePassportRegistration", []) + case .secureValueTypePersonalDetails: + return ("secureValueTypePersonalDetails", []) + case .secureValueTypePhone: + return ("secureValueTypePhone", []) + case .secureValueTypeRentalAgreement: + return ("secureValueTypeRentalAgreement", []) + case .secureValueTypeTemporaryRegistration: + return ("secureValueTypeTemporaryRegistration", []) + case .secureValueTypeUtilityBill: + return ("secureValueTypeUtilityBill", []) + } + } + + public static func parse_secureValueTypeAddress(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeAddress + } + public static func parse_secureValueTypeBankStatement(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeBankStatement + } + public static func parse_secureValueTypeDriverLicense(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeDriverLicense + } + public static func parse_secureValueTypeEmail(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeEmail + } + public static func parse_secureValueTypeIdentityCard(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeIdentityCard + } + public static func parse_secureValueTypeInternalPassport(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeInternalPassport + } + public static func parse_secureValueTypePassport(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePassport + } + public static func parse_secureValueTypePassportRegistration(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePassportRegistration + } + public static func parse_secureValueTypePersonalDetails(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePersonalDetails + } + public static func parse_secureValueTypePhone(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypePhone + } + public static func parse_secureValueTypeRentalAgreement(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeRentalAgreement + } + public static func parse_secureValueTypeTemporaryRegistration(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeTemporaryRegistration + } + public static func parse_secureValueTypeUtilityBill(_ reader: BufferReader) -> SecureValueType? { + return Api.SecureValueType.secureValueTypeUtilityBill + } + } +} diff --git a/submodules/TelegramApi/Sources/Api26.swift b/submodules/TelegramApi/Sources/Api26.swift index 8bc5c73f45..fad4adecdb 100644 --- a/submodules/TelegramApi/Sources/Api26.swift +++ b/submodules/TelegramApi/Sources/Api26.swift @@ -1,75 +1,48 @@ public extension Api { - enum StarsAmount: TypeConstructorDescription { - public class Cons_starsAmount: TypeConstructorDescription { - public var amount: Int64 - public var nanos: Int32 - public init(amount: Int64, nanos: Int32) { - self.amount = amount - self.nanos = nanos + enum SendAsPeer: TypeConstructorDescription { + public class Cons_sendAsPeer: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public init(flags: Int32, peer: Api.Peer) { + self.flags = flags + self.peer = peer } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsAmount", [("amount", ConstructorParameterDescription(self.amount)), ("nanos", ConstructorParameterDescription(self.nanos))]) + return ("sendAsPeer", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer))]) } } - public class Cons_starsTonAmount: TypeConstructorDescription { - public var amount: Int64 - public init(amount: Int64) { - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsTonAmount", [("amount", ConstructorParameterDescription(self.amount))]) - } - } - case starsAmount(Cons_starsAmount) - case starsTonAmount(Cons_starsTonAmount) + case sendAsPeer(Cons_sendAsPeer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsAmount(let _data): + case .sendAsPeer(let _data): if boxed { - buffer.appendInt32(-1145654109) + buffer.appendInt32(-1206095820) } - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeInt32(_data.nanos, buffer: buffer, boxed: false) - break - case .starsTonAmount(let _data): - if boxed { - buffer.appendInt32(1957618656) - } - serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starsAmount(let _data): - return ("starsAmount", [("amount", ConstructorParameterDescription(_data.amount)), ("nanos", ConstructorParameterDescription(_data.nanos))]) - case .starsTonAmount(let _data): - return ("starsTonAmount", [("amount", ConstructorParameterDescription(_data.amount))]) + case .sendAsPeer(let _data): + return ("sendAsPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer))]) } } - public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + public static func parse_sendAsPeer(_ reader: BufferReader) -> SendAsPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!)) - } - else { - return nil - } - } - public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!)) + return Api.SendAsPeer.sendAsPeer(Cons_sendAsPeer(flags: _1!, peer: _2!)) } else { return nil @@ -78,119 +51,515 @@ public extension Api { } } public extension Api { - enum StarsGiftOption: TypeConstructorDescription { - public class Cons_starsGiftOption: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var storeProduct: String? - public var currency: String - public var amount: Int64 - public init(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) { - self.flags = flags - self.stars = stars - self.storeProduct = storeProduct - self.currency = currency - self.amount = amount + enum SendMessageAction: TypeConstructorDescription { + public class Cons_inputSendMessageRichMessageDraftAction: TypeConstructorDescription { + public var randomId: Int64 + public var richMessage: Api.InputRichMessage + public init(randomId: Int64, richMessage: Api.InputRichMessage) { + self.randomId = randomId + self.richMessage = richMessage } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsGiftOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + return ("inputSendMessageRichMessageDraftAction", [("randomId", ConstructorParameterDescription(self.randomId)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) } } - case starsGiftOption(Cons_starsGiftOption) + public class Cons_sendMessageEmojiInteraction: TypeConstructorDescription { + public var emoticon: String + public var msgId: Int32 + public var interaction: Api.DataJSON + public init(emoticon: String, msgId: Int32, interaction: Api.DataJSON) { + self.emoticon = emoticon + self.msgId = msgId + self.interaction = interaction + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageEmojiInteraction", [("emoticon", ConstructorParameterDescription(self.emoticon)), ("msgId", ConstructorParameterDescription(self.msgId)), ("interaction", ConstructorParameterDescription(self.interaction))]) + } + } + public class Cons_sendMessageEmojiInteractionSeen: TypeConstructorDescription { + public var emoticon: String + public init(emoticon: String) { + self.emoticon = emoticon + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageEmojiInteractionSeen", [("emoticon", ConstructorParameterDescription(self.emoticon))]) + } + } + public class Cons_sendMessageHistoryImportAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageHistoryImportAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + public class Cons_sendMessageRichMessageDraftAction: TypeConstructorDescription { + public var randomId: Int64 + public var richMessage: Api.RichMessage + public init(randomId: Int64, richMessage: Api.RichMessage) { + self.randomId = randomId + self.richMessage = richMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageRichMessageDraftAction", [("randomId", ConstructorParameterDescription(self.randomId)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) + } + } + public class Cons_sendMessageTextDraftAction: TypeConstructorDescription { + public var randomId: Int64 + public var text: Api.TextWithEntities + public init(randomId: Int64, text: Api.TextWithEntities) { + self.randomId = randomId + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageTextDraftAction", [("randomId", ConstructorParameterDescription(self.randomId)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_sendMessageUploadAudioAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageUploadAudioAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + public class Cons_sendMessageUploadDocumentAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageUploadDocumentAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + public class Cons_sendMessageUploadPhotoAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageUploadPhotoAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + public class Cons_sendMessageUploadRoundAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageUploadRoundAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + public class Cons_sendMessageUploadVideoAction: TypeConstructorDescription { + public var progress: Int32 + public init(progress: Int32) { + self.progress = progress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendMessageUploadVideoAction", [("progress", ConstructorParameterDescription(self.progress))]) + } + } + case inputSendMessageRichMessageDraftAction(Cons_inputSendMessageRichMessageDraftAction) + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageChooseStickerAction + case sendMessageEmojiInteraction(Cons_sendMessageEmojiInteraction) + case sendMessageEmojiInteractionSeen(Cons_sendMessageEmojiInteractionSeen) + case sendMessageGamePlayAction + case sendMessageGeoLocationAction + case sendMessageHistoryImportAction(Cons_sendMessageHistoryImportAction) + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageRichMessageDraftAction(Cons_sendMessageRichMessageDraftAction) + case sendMessageTextDraftAction(Cons_sendMessageTextDraftAction) + case sendMessageTypingAction + case sendMessageUploadAudioAction(Cons_sendMessageUploadAudioAction) + case sendMessageUploadDocumentAction(Cons_sendMessageUploadDocumentAction) + case sendMessageUploadPhotoAction(Cons_sendMessageUploadPhotoAction) + case sendMessageUploadRoundAction(Cons_sendMessageUploadRoundAction) + case sendMessageUploadVideoAction(Cons_sendMessageUploadVideoAction) + case speakingInGroupCallAction public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsGiftOption(let _data): + case .inputSendMessageRichMessageDraftAction(let _data): if boxed { - buffer.appendInt32(1577421297) + buffer.appendInt32(-491635887) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + _data.richMessage.serialize(buffer, true) + break + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageChooseStickerAction: + if boxed { + buffer.appendInt32(-1336228175) + } + break + case .sendMessageEmojiInteraction(let _data): + if boxed { + buffer.appendInt32(630664139) + } + serializeString(_data.emoticon, buffer: buffer, boxed: false) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + _data.interaction.serialize(buffer, true) + break + case .sendMessageEmojiInteractionSeen(let _data): + if boxed { + buffer.appendInt32(-1234857938) + } + serializeString(_data.emoticon, buffer: buffer, boxed: false) + break + case .sendMessageGamePlayAction: + if boxed { + buffer.appendInt32(-580219064) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageHistoryImportAction(let _data): + if boxed { + buffer.appendInt32(-606432698) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageRichMessageDraftAction(let _data): + if boxed { + buffer.appendInt32(-1563745031) + } + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + _data.richMessage.serialize(buffer, true) + break + case .sendMessageTextDraftAction(let _data): + if boxed { + buffer.appendInt32(929929052) + } + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + _data.text.serialize(buffer, true) + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction(let _data): + if boxed { + buffer.appendInt32(-212740181) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .sendMessageUploadDocumentAction(let _data): + if boxed { + buffer.appendInt32(-1441998364) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .sendMessageUploadPhotoAction(let _data): + if boxed { + buffer.appendInt32(-774682074) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .sendMessageUploadRoundAction(let _data): + if boxed { + buffer.appendInt32(608050278) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .sendMessageUploadVideoAction(let _data): + if boxed { + buffer.appendInt32(-378127636) + } + serializeInt32(_data.progress, buffer: buffer, boxed: false) + break + case .speakingInGroupCallAction: + if boxed { + buffer.appendInt32(-651419003) } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starsGiftOption(let _data): - return ("starsGiftOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .inputSendMessageRichMessageDraftAction(let _data): + return ("inputSendMessageRichMessageDraftAction", [("randomId", ConstructorParameterDescription(_data.randomId)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) + case .sendMessageCancelAction: + return ("sendMessageCancelAction", []) + case .sendMessageChooseContactAction: + return ("sendMessageChooseContactAction", []) + case .sendMessageChooseStickerAction: + return ("sendMessageChooseStickerAction", []) + case .sendMessageEmojiInteraction(let _data): + return ("sendMessageEmojiInteraction", [("emoticon", ConstructorParameterDescription(_data.emoticon)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("interaction", ConstructorParameterDescription(_data.interaction))]) + case .sendMessageEmojiInteractionSeen(let _data): + return ("sendMessageEmojiInteractionSeen", [("emoticon", ConstructorParameterDescription(_data.emoticon))]) + case .sendMessageGamePlayAction: + return ("sendMessageGamePlayAction", []) + case .sendMessageGeoLocationAction: + return ("sendMessageGeoLocationAction", []) + case .sendMessageHistoryImportAction(let _data): + return ("sendMessageHistoryImportAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .sendMessageRecordAudioAction: + return ("sendMessageRecordAudioAction", []) + case .sendMessageRecordRoundAction: + return ("sendMessageRecordRoundAction", []) + case .sendMessageRecordVideoAction: + return ("sendMessageRecordVideoAction", []) + case .sendMessageRichMessageDraftAction(let _data): + return ("sendMessageRichMessageDraftAction", [("randomId", ConstructorParameterDescription(_data.randomId)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) + case .sendMessageTextDraftAction(let _data): + return ("sendMessageTextDraftAction", [("randomId", ConstructorParameterDescription(_data.randomId)), ("text", ConstructorParameterDescription(_data.text))]) + case .sendMessageTypingAction: + return ("sendMessageTypingAction", []) + case .sendMessageUploadAudioAction(let _data): + return ("sendMessageUploadAudioAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .sendMessageUploadDocumentAction(let _data): + return ("sendMessageUploadDocumentAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .sendMessageUploadPhotoAction(let _data): + return ("sendMessageUploadPhotoAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .sendMessageUploadRoundAction(let _data): + return ("sendMessageUploadRoundAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .sendMessageUploadVideoAction(let _data): + return ("sendMessageUploadVideoAction", [("progress", ConstructorParameterDescription(_data.progress))]) + case .speakingInGroupCallAction: + return ("speakingInGroupCallAction", []) } } - public static func parse_starsGiftOption(_ reader: BufferReader) -> StarsGiftOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) + public static func parse_inputSendMessageRichMessageDraftAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.InputRichMessage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputRichMessage } - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsGiftOption.starsGiftOption(Cons_starsGiftOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)) + if _c1 && _c2 { + return Api.SendMessageAction.inputSendMessageRichMessageDraftAction(Cons_inputSendMessageRichMessageDraftAction(randomId: _1!, richMessage: _2!)) } else { return nil } } + public static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageCancelAction + } + public static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseContactAction + } + public static func parse_sendMessageChooseStickerAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageChooseStickerAction + } + public static func parse_sendMessageEmojiInteraction(_ reader: BufferReader) -> SendMessageAction? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.DataJSON? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.SendMessageAction.sendMessageEmojiInteraction(Cons_sendMessageEmojiInteraction(emoticon: _1!, msgId: _2!, interaction: _3!)) + } + else { + return nil + } + } + public static func parse_sendMessageEmojiInteractionSeen(_ reader: BufferReader) -> SendMessageAction? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageEmojiInteractionSeen(Cons_sendMessageEmojiInteractionSeen(emoticon: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageGamePlayAction + } + public static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageGeoLocationAction + } + public static func parse_sendMessageHistoryImportAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageHistoryImportAction(Cons_sendMessageHistoryImportAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordAudioAction + } + public static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordRoundAction + } + public static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordVideoAction + } + public static func parse_sendMessageRichMessageDraftAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.RichMessage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.RichMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.SendMessageAction.sendMessageRichMessageDraftAction(Cons_sendMessageRichMessageDraftAction(randomId: _1!, richMessage: _2!)) + } + else { + return nil + } + } + public static func parse_sendMessageTextDraftAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.SendMessageAction.sendMessageTextDraftAction(Cons_sendMessageTextDraftAction(randomId: _1!, text: _2!)) + } + else { + return nil + } + } + public static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageTypingAction + } + public static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadAudioAction(Cons_sendMessageUploadAudioAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadDocumentAction(Cons_sendMessageUploadDocumentAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadPhotoAction(Cons_sendMessageUploadPhotoAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadRoundAction(Cons_sendMessageUploadRoundAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.SendMessageAction.sendMessageUploadVideoAction(Cons_sendMessageUploadVideoAction(progress: _1!)) + } + else { + return nil + } + } + public static func parse_speakingInGroupCallAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.speakingInGroupCallAction + } } } public extension Api { - enum StarsGiveawayOption: TypeConstructorDescription { - public class Cons_starsGiveawayOption: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var yearlyBoosts: Int32 - public var storeProduct: String? - public var currency: String - public var amount: Int64 - public var winners: [Api.StarsGiveawayWinnersOption] - public init(flags: Int32, stars: Int64, yearlyBoosts: Int32, storeProduct: String?, currency: String, amount: Int64, winners: [Api.StarsGiveawayWinnersOption]) { - self.flags = flags - self.stars = stars - self.yearlyBoosts = yearlyBoosts - self.storeProduct = storeProduct - self.currency = currency - self.amount = amount - self.winners = winners + enum ShippingOption: TypeConstructorDescription { + public class Cons_shippingOption: TypeConstructorDescription { + public var id: String + public var title: String + public var prices: [Api.LabeledPrice] + public init(id: String, title: String, prices: [Api.LabeledPrice]) { + self.id = id + self.title = title + self.prices = prices } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsGiveawayOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("yearlyBoosts", ConstructorParameterDescription(self.yearlyBoosts)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("winners", ConstructorParameterDescription(self.winners))]) + return ("shippingOption", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title)), ("prices", ConstructorParameterDescription(self.prices))]) } } - case starsGiveawayOption(Cons_starsGiveawayOption) + case shippingOption(Cons_shippingOption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsGiveawayOption(let _data): + case .shippingOption(let _data): if boxed { - buffer.appendInt32(-1798404822) + buffer.appendInt32(-1239335713) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - serializeInt32(_data.yearlyBoosts, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.storeProduct!, buffer: buffer, boxed: false) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeString(_data.id, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.winners.count)) - for item in _data.winners { + buffer.appendInt32(Int32(_data.prices.count)) + for item in _data.prices { item.serialize(buffer, true) } break @@ -199,95 +568,25 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starsGiveawayOption(let _data): - return ("starsGiveawayOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("yearlyBoosts", ConstructorParameterDescription(_data.yearlyBoosts)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("winners", ConstructorParameterDescription(_data.winners))]) + case .shippingOption(let _data): + return ("shippingOption", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title)), ("prices", ConstructorParameterDescription(_data.prices))]) } } - public static func parse_starsGiveawayOption(_ reader: BufferReader) -> StarsGiveawayOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseString(reader) - } - var _5: String? - _5 = parseString(reader) - var _6: Int64? - _6 = reader.readInt64() - var _7: [Api.StarsGiveawayWinnersOption]? + public static func parse_shippingOption(_ reader: BufferReader) -> ShippingOption? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: [Api.LabeledPrice]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiveawayWinnersOption.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.LabeledPrice.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.StarsGiveawayOption.starsGiveawayOption(Cons_starsGiveawayOption(flags: _1!, stars: _2!, yearlyBoosts: _3!, storeProduct: _4, currency: _5!, amount: _6!, winners: _7!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarsGiveawayWinnersOption: TypeConstructorDescription { - public class Cons_starsGiveawayWinnersOption: TypeConstructorDescription { - public var flags: Int32 - public var users: Int32 - public var perUserStars: Int64 - public init(flags: Int32, users: Int32, perUserStars: Int64) { - self.flags = flags - self.users = users - self.perUserStars = perUserStars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsGiveawayWinnersOption", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("perUserStars", ConstructorParameterDescription(self.perUserStars))]) - } - } - case starsGiveawayWinnersOption(Cons_starsGiveawayWinnersOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsGiveawayWinnersOption(let _data): - if boxed { - buffer.appendInt32(1411605001) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.users, buffer: buffer, boxed: false) - serializeInt64(_data.perUserStars, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsGiveawayWinnersOption(let _data): - return ("starsGiveawayWinnersOption", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("perUserStars", ConstructorParameterDescription(_data.perUserStars))]) - } - } - - public static func parse_starsGiveawayWinnersOption(_ reader: BufferReader) -> StarsGiveawayWinnersOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.StarsGiveawayWinnersOption.starsGiveawayWinnersOption(Cons_starsGiveawayWinnersOption(flags: _1!, users: _2!, perUserStars: _3!)) + return Api.ShippingOption.shippingOption(Cons_shippingOption(id: _1!, title: _2!, prices: _3!)) } else { return nil @@ -296,70 +595,54 @@ public extension Api { } } public extension Api { - enum StarsRating: TypeConstructorDescription { - public class Cons_starsRating: TypeConstructorDescription { - public var flags: Int32 - public var level: Int32 - public var currentLevelStars: Int64 - public var stars: Int64 - public var nextLevelStars: Int64? - public init(flags: Int32, level: Int32, currentLevelStars: Int64, stars: Int64, nextLevelStars: Int64?) { - self.flags = flags - self.level = level - self.currentLevelStars = currentLevelStars - self.stars = stars - self.nextLevelStars = nextLevelStars + enum SmsJob: TypeConstructorDescription { + public class Cons_smsJob: TypeConstructorDescription { + public var jobId: String + public var phoneNumber: String + public var text: String + public init(jobId: String, phoneNumber: String, text: String) { + self.jobId = jobId + self.phoneNumber = phoneNumber + self.text = text } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsRating", [("flags", ConstructorParameterDescription(self.flags)), ("level", ConstructorParameterDescription(self.level)), ("currentLevelStars", ConstructorParameterDescription(self.currentLevelStars)), ("stars", ConstructorParameterDescription(self.stars)), ("nextLevelStars", ConstructorParameterDescription(self.nextLevelStars))]) + return ("smsJob", [("jobId", ConstructorParameterDescription(self.jobId)), ("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("text", ConstructorParameterDescription(self.text))]) } } - case starsRating(Cons_starsRating) + case smsJob(Cons_smsJob) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsRating(let _data): + case .smsJob(let _data): if boxed { - buffer.appendInt32(453922567) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.level, buffer: buffer, boxed: false) - serializeInt64(_data.currentLevelStars, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.nextLevelStars!, buffer: buffer, boxed: false) + buffer.appendInt32(-425595208) } + serializeString(_data.jobId, buffer: buffer, boxed: false) + serializeString(_data.phoneNumber, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starsRating(let _data): - return ("starsRating", [("flags", ConstructorParameterDescription(_data.flags)), ("level", ConstructorParameterDescription(_data.level)), ("currentLevelStars", ConstructorParameterDescription(_data.currentLevelStars)), ("stars", ConstructorParameterDescription(_data.stars)), ("nextLevelStars", ConstructorParameterDescription(_data.nextLevelStars))]) + case .smsJob(let _data): + return ("smsJob", [("jobId", ConstructorParameterDescription(_data.jobId)), ("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("text", ConstructorParameterDescription(_data.text))]) } } - public static func parse_starsRating(_ reader: BufferReader) -> StarsRating? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt64() - } + public static func parse_smsJob(_ reader: BufferReader) -> SmsJob? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsRating.starsRating(Cons_starsRating(flags: _1!, level: _2!, currentLevelStars: _3!, stars: _4!, nextLevelStars: _5)) + if _c1 && _c2 && _c3 { + return Api.SmsJob.smsJob(Cons_smsJob(jobId: _1!, phoneNumber: _2!, text: _3!)) } else { return nil @@ -368,134 +651,83 @@ public extension Api { } } public extension Api { - enum StarsRevenueStatus: TypeConstructorDescription { - public class Cons_starsRevenueStatus: TypeConstructorDescription { + indirect enum SponsoredMessage: TypeConstructorDescription { + public class Cons_sponsoredMessage: TypeConstructorDescription { public var flags: Int32 - public var currentBalance: Api.StarsAmount - public var availableBalance: Api.StarsAmount - public var overallRevenue: Api.StarsAmount - public var nextWithdrawalAt: Int32? - public init(flags: Int32, currentBalance: Api.StarsAmount, availableBalance: Api.StarsAmount, overallRevenue: Api.StarsAmount, nextWithdrawalAt: Int32?) { + public var randomId: Buffer + public var url: String + public var title: String + public var message: String + public var entities: [Api.MessageEntity]? + public var photo: Api.Photo? + public var media: Api.MessageMedia? + public var color: Api.PeerColor? + public var buttonText: String + public var sponsorInfo: String? + public var additionalInfo: String? + public var minDisplayDuration: Int32? + public var maxDisplayDuration: Int32? + public init(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, media: Api.MessageMedia?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?, minDisplayDuration: Int32?, maxDisplayDuration: Int32?) { self.flags = flags - self.currentBalance = currentBalance - self.availableBalance = availableBalance - self.overallRevenue = overallRevenue - self.nextWithdrawalAt = nextWithdrawalAt - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsRevenueStatus", [("flags", ConstructorParameterDescription(self.flags)), ("currentBalance", ConstructorParameterDescription(self.currentBalance)), ("availableBalance", ConstructorParameterDescription(self.availableBalance)), ("overallRevenue", ConstructorParameterDescription(self.overallRevenue)), ("nextWithdrawalAt", ConstructorParameterDescription(self.nextWithdrawalAt))]) - } - } - case starsRevenueStatus(Cons_starsRevenueStatus) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsRevenueStatus(let _data): - if boxed { - buffer.appendInt32(-21080943) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.currentBalance.serialize(buffer, true) - _data.availableBalance.serialize(buffer, true) - _data.overallRevenue.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.nextWithdrawalAt!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsRevenueStatus(let _data): - return ("starsRevenueStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("currentBalance", ConstructorParameterDescription(_data.currentBalance)), ("availableBalance", ConstructorParameterDescription(_data.availableBalance)), ("overallRevenue", ConstructorParameterDescription(_data.overallRevenue)), ("nextWithdrawalAt", ConstructorParameterDescription(_data.nextWithdrawalAt))]) - } - } - - public static func parse_starsRevenueStatus(_ reader: BufferReader) -> StarsRevenueStatus? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarsAmount? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _3: Api.StarsAmount? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _4: Api.StarsAmount? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsRevenueStatus.starsRevenueStatus(Cons_starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!, nextWithdrawalAt: _5)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarsSubscription: TypeConstructorDescription { - public class Cons_starsSubscription: TypeConstructorDescription { - public var flags: Int32 - public var id: String - public var peer: Api.Peer - public var untilDate: Int32 - public var pricing: Api.StarsSubscriptionPricing - public var chatInviteHash: String? - public var title: String? - public var photo: Api.WebDocument? - public var invoiceSlug: String? - public init(flags: Int32, id: String, peer: Api.Peer, untilDate: Int32, pricing: Api.StarsSubscriptionPricing, chatInviteHash: String?, title: String?, photo: Api.WebDocument?, invoiceSlug: String?) { - self.flags = flags - self.id = id - self.peer = peer - self.untilDate = untilDate - self.pricing = pricing - self.chatInviteHash = chatInviteHash + self.randomId = randomId + self.url = url self.title = title + self.message = message + self.entities = entities self.photo = photo - self.invoiceSlug = invoiceSlug + self.media = media + self.color = color + self.buttonText = buttonText + self.sponsorInfo = sponsorInfo + self.additionalInfo = additionalInfo + self.minDisplayDuration = minDisplayDuration + self.maxDisplayDuration = maxDisplayDuration } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsSubscription", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("peer", ConstructorParameterDescription(self.peer)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("pricing", ConstructorParameterDescription(self.pricing)), ("chatInviteHash", ConstructorParameterDescription(self.chatInviteHash)), ("title", ConstructorParameterDescription(self.title)), ("photo", ConstructorParameterDescription(self.photo)), ("invoiceSlug", ConstructorParameterDescription(self.invoiceSlug))]) + return ("sponsoredMessage", [("flags", ConstructorParameterDescription(self.flags)), ("randomId", ConstructorParameterDescription(self.randomId)), ("url", ConstructorParameterDescription(self.url)), ("title", ConstructorParameterDescription(self.title)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("photo", ConstructorParameterDescription(self.photo)), ("media", ConstructorParameterDescription(self.media)), ("color", ConstructorParameterDescription(self.color)), ("buttonText", ConstructorParameterDescription(self.buttonText)), ("sponsorInfo", ConstructorParameterDescription(self.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(self.additionalInfo)), ("minDisplayDuration", ConstructorParameterDescription(self.minDisplayDuration)), ("maxDisplayDuration", ConstructorParameterDescription(self.maxDisplayDuration))]) } } - case starsSubscription(Cons_starsSubscription) + case sponsoredMessage(Cons_sponsoredMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsSubscription(let _data): + case .sponsoredMessage(let _data): if boxed { - buffer.appendInt32(779004698) + buffer.appendInt32(2109703795) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.id, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - _data.pricing.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.chatInviteHash!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.photo!.serialize(buffer, true) + serializeBytes(_data.randomId, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } } if Int(_data.flags) & Int(1 << 6) != 0 { - serializeString(_data.invoiceSlug!, buffer: buffer, boxed: false) + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + _data.media!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + _data.color!.serialize(buffer, true) + } + serializeString(_data.buttonText, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeString(_data.sponsorInfo!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeString(_data.additionalInfo!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.minDisplayDuration!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.maxDisplayDuration!, buffer: buffer, boxed: false) } break } @@ -503,1306 +735,998 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .starsSubscription(let _data): - return ("starsSubscription", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("peer", ConstructorParameterDescription(_data.peer)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("pricing", ConstructorParameterDescription(_data.pricing)), ("chatInviteHash", ConstructorParameterDescription(_data.chatInviteHash)), ("title", ConstructorParameterDescription(_data.title)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoiceSlug", ConstructorParameterDescription(_data.invoiceSlug))]) + case .sponsoredMessage(let _data): + return ("sponsoredMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("url", ConstructorParameterDescription(_data.url)), ("title", ConstructorParameterDescription(_data.title)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("photo", ConstructorParameterDescription(_data.photo)), ("media", ConstructorParameterDescription(_data.media)), ("color", ConstructorParameterDescription(_data.color)), ("buttonText", ConstructorParameterDescription(_data.buttonText)), ("sponsorInfo", ConstructorParameterDescription(_data.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(_data.additionalInfo)), ("minDisplayDuration", ConstructorParameterDescription(_data.minDisplayDuration)), ("maxDisplayDuration", ConstructorParameterDescription(_data.maxDisplayDuration))]) } } - public static func parse_starsSubscription(_ reader: BufferReader) -> StarsSubscription? { + public static func parse_sponsoredMessage(_ reader: BufferReader) -> SponsoredMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _7: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _8: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + var _9: Api.PeerColor? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PeerColor + } + } + var _10: String? + _10 = parseString(reader) + var _11: String? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _11 = parseString(reader) + } + var _12: String? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _12 = parseString(reader) + } + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _13 = reader.readInt32() + } + var _14: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _14 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _9 != nil + let _c10 = _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.SponsoredMessage.sponsoredMessage(Cons_sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12, minDisplayDuration: _13, maxDisplayDuration: _14)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SponsoredMessageReportOption: TypeConstructorDescription { + public class Cons_sponsoredMessageReportOption: TypeConstructorDescription { + public var text: String + public var option: Buffer + public init(text: String, option: Buffer) { + self.text = text + self.option = option + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sponsoredMessageReportOption", [("text", ConstructorParameterDescription(self.text)), ("option", ConstructorParameterDescription(self.option))]) + } + } + case sponsoredMessageReportOption(Cons_sponsoredMessageReportOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredMessageReportOption(let _data): + if boxed { + buffer.appendInt32(1124938064) + } + serializeString(_data.text, buffer: buffer, boxed: false) + serializeBytes(_data.option, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sponsoredMessageReportOption(let _data): + return ("sponsoredMessageReportOption", [("text", ConstructorParameterDescription(_data.text)), ("option", ConstructorParameterDescription(_data.option))]) + } + } + + public static func parse_sponsoredMessageReportOption(_ reader: BufferReader) -> SponsoredMessageReportOption? { + var _1: String? + _1 = parseString(reader) + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.SponsoredMessageReportOption.sponsoredMessageReportOption(Cons_sponsoredMessageReportOption(text: _1!, option: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SponsoredPeer: TypeConstructorDescription { + public class Cons_sponsoredPeer: TypeConstructorDescription { + public var flags: Int32 + public var randomId: Buffer + public var peer: Api.Peer + public var sponsorInfo: String? + public var additionalInfo: String? + public init(flags: Int32, randomId: Buffer, peer: Api.Peer, sponsorInfo: String?, additionalInfo: String?) { + self.flags = flags + self.randomId = randomId + self.peer = peer + self.sponsorInfo = sponsorInfo + self.additionalInfo = additionalInfo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sponsoredPeer", [("flags", ConstructorParameterDescription(self.flags)), ("randomId", ConstructorParameterDescription(self.randomId)), ("peer", ConstructorParameterDescription(self.peer)), ("sponsorInfo", ConstructorParameterDescription(self.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(self.additionalInfo))]) + } + } + case sponsoredPeer(Cons_sponsoredPeer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredPeer(let _data): + if boxed { + buffer.appendInt32(-963180333) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeBytes(_data.randomId, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.sponsorInfo!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.additionalInfo!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sponsoredPeer(let _data): + return ("sponsoredPeer", [("flags", ConstructorParameterDescription(_data.flags)), ("randomId", ConstructorParameterDescription(_data.randomId)), ("peer", ConstructorParameterDescription(_data.peer)), ("sponsorInfo", ConstructorParameterDescription(_data.sponsorInfo)), ("additionalInfo", ConstructorParameterDescription(_data.additionalInfo))]) + } + } + + public static func parse_sponsoredPeer(_ reader: BufferReader) -> SponsoredPeer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.SponsoredPeer.sponsoredPeer(Cons_sponsoredPeer(flags: _1!, randomId: _2!, peer: _3!, sponsorInfo: _4, additionalInfo: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarGift: TypeConstructorDescription { + public class Cons_starGift: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var sticker: Api.Document + public var stars: Int64 + public var availabilityRemains: Int32? + public var availabilityTotal: Int32? + public var availabilityResale: Int64? + public var convertStars: Int64 + public var firstSaleDate: Int32? + public var lastSaleDate: Int32? + public var upgradeStars: Int64? + public var resellMinStars: Int64? + public var title: String? + public var releasedBy: Api.Peer? + public var perUserTotal: Int32? + public var perUserRemains: Int32? + public var lockedUntilDate: Int32? + public var auctionSlug: String? + public var giftsPerRound: Int32? + public var auctionStartDate: Int32? + public var upgradeVariants: Int32? + public var background: Api.StarGiftBackground? + public init(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?, auctionSlug: String?, giftsPerRound: Int32?, auctionStartDate: Int32?, upgradeVariants: Int32?, background: Api.StarGiftBackground?) { + self.flags = flags + self.id = id + self.sticker = sticker + self.stars = stars + self.availabilityRemains = availabilityRemains + self.availabilityTotal = availabilityTotal + self.availabilityResale = availabilityResale + self.convertStars = convertStars + self.firstSaleDate = firstSaleDate + self.lastSaleDate = lastSaleDate + self.upgradeStars = upgradeStars + self.resellMinStars = resellMinStars + self.title = title + self.releasedBy = releasedBy + self.perUserTotal = perUserTotal + self.perUserRemains = perUserRemains + self.lockedUntilDate = lockedUntilDate + self.auctionSlug = auctionSlug + self.giftsPerRound = giftsPerRound + self.auctionStartDate = auctionStartDate + self.upgradeVariants = upgradeVariants + self.background = background + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGift", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("sticker", ConstructorParameterDescription(self.sticker)), ("stars", ConstructorParameterDescription(self.stars)), ("availabilityRemains", ConstructorParameterDescription(self.availabilityRemains)), ("availabilityTotal", ConstructorParameterDescription(self.availabilityTotal)), ("availabilityResale", ConstructorParameterDescription(self.availabilityResale)), ("convertStars", ConstructorParameterDescription(self.convertStars)), ("firstSaleDate", ConstructorParameterDescription(self.firstSaleDate)), ("lastSaleDate", ConstructorParameterDescription(self.lastSaleDate)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars)), ("resellMinStars", ConstructorParameterDescription(self.resellMinStars)), ("title", ConstructorParameterDescription(self.title)), ("releasedBy", ConstructorParameterDescription(self.releasedBy)), ("perUserTotal", ConstructorParameterDescription(self.perUserTotal)), ("perUserRemains", ConstructorParameterDescription(self.perUserRemains)), ("lockedUntilDate", ConstructorParameterDescription(self.lockedUntilDate)), ("auctionSlug", ConstructorParameterDescription(self.auctionSlug)), ("giftsPerRound", ConstructorParameterDescription(self.giftsPerRound)), ("auctionStartDate", ConstructorParameterDescription(self.auctionStartDate)), ("upgradeVariants", ConstructorParameterDescription(self.upgradeVariants)), ("background", ConstructorParameterDescription(self.background))]) + } + } + public class Cons_starGiftUnique: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var giftId: Int64 + public var title: String + public var slug: String + public var num: Int32 + public var ownerId: Api.Peer? + public var ownerName: String? + public var ownerAddress: String? + public var attributes: [Api.StarGiftAttribute] + public var availabilityIssued: Int32 + public var availabilityTotal: Int32 + public var giftAddress: String? + public var resellAmount: [Api.StarsAmount]? + public var releasedBy: Api.Peer? + public var valueAmount: Int64? + public var valueCurrency: String? + public var valueUsdAmount: Int64? + public var themePeer: Api.Peer? + public var peerColor: Api.PeerColor? + public var hostId: Api.Peer? + public var offerMinStars: Int32? + public var craftChancePermille: Int32? + public init(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, valueUsdAmount: Int64?, themePeer: Api.Peer?, peerColor: Api.PeerColor?, hostId: Api.Peer?, offerMinStars: Int32?, craftChancePermille: Int32?) { + self.flags = flags + self.id = id + self.giftId = giftId + self.title = title + self.slug = slug + self.num = num + self.ownerId = ownerId + self.ownerName = ownerName + self.ownerAddress = ownerAddress + self.attributes = attributes + self.availabilityIssued = availabilityIssued + self.availabilityTotal = availabilityTotal + self.giftAddress = giftAddress + self.resellAmount = resellAmount + self.releasedBy = releasedBy + self.valueAmount = valueAmount + self.valueCurrency = valueCurrency + self.valueUsdAmount = valueUsdAmount + self.themePeer = themePeer + self.peerColor = peerColor + self.hostId = hostId + self.offerMinStars = offerMinStars + self.craftChancePermille = craftChancePermille + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftUnique", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("giftId", ConstructorParameterDescription(self.giftId)), ("title", ConstructorParameterDescription(self.title)), ("slug", ConstructorParameterDescription(self.slug)), ("num", ConstructorParameterDescription(self.num)), ("ownerId", ConstructorParameterDescription(self.ownerId)), ("ownerName", ConstructorParameterDescription(self.ownerName)), ("ownerAddress", ConstructorParameterDescription(self.ownerAddress)), ("attributes", ConstructorParameterDescription(self.attributes)), ("availabilityIssued", ConstructorParameterDescription(self.availabilityIssued)), ("availabilityTotal", ConstructorParameterDescription(self.availabilityTotal)), ("giftAddress", ConstructorParameterDescription(self.giftAddress)), ("resellAmount", ConstructorParameterDescription(self.resellAmount)), ("releasedBy", ConstructorParameterDescription(self.releasedBy)), ("valueAmount", ConstructorParameterDescription(self.valueAmount)), ("valueCurrency", ConstructorParameterDescription(self.valueCurrency)), ("valueUsdAmount", ConstructorParameterDescription(self.valueUsdAmount)), ("themePeer", ConstructorParameterDescription(self.themePeer)), ("peerColor", ConstructorParameterDescription(self.peerColor)), ("hostId", ConstructorParameterDescription(self.hostId)), ("offerMinStars", ConstructorParameterDescription(self.offerMinStars)), ("craftChancePermille", ConstructorParameterDescription(self.craftChancePermille))]) + } + } + case starGift(Cons_starGift) + case starGiftUnique(Cons_starGiftUnique) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGift(let _data): + if boxed { + buffer.appendInt32(825922887) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + _data.sticker.serialize(buffer, true) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.availabilityRemains!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.availabilityTotal!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.availabilityResale!, buffer: buffer, boxed: false) + } + serializeInt64(_data.convertStars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.firstSaleDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.lastSaleDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt64(_data.upgradeStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.resellMinStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + _data.releasedBy!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt32(_data.perUserTotal!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt32(_data.perUserRemains!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + serializeInt32(_data.lockedUntilDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeString(_data.auctionSlug!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt32(_data.giftsPerRound!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt32(_data.auctionStartDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + serializeInt32(_data.upgradeVariants!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + _data.background!.serialize(buffer, true) + } + break + case .starGiftUnique(let _data): + if boxed { + buffer.appendInt32(-2047825459) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.giftId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.slug, buffer: buffer, boxed: false) + serializeInt32(_data.num, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.ownerId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.ownerName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.ownerAddress!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.attributes.count)) + for item in _data.attributes { + item.serialize(buffer, true) + } + serializeInt32(_data.availabilityIssued, buffer: buffer, boxed: false) + serializeInt32(_data.availabilityTotal, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.giftAddress!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.resellAmount!.count)) + for item in _data.resellAmount! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.releasedBy!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt64(_data.valueAmount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeString(_data.valueCurrency!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt64(_data.valueUsdAmount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.themePeer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + _data.peerColor!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + _data.hostId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + serializeInt32(_data.offerMinStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeInt32(_data.craftChancePermille!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGift(let _data): + return ("starGift", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("sticker", ConstructorParameterDescription(_data.sticker)), ("stars", ConstructorParameterDescription(_data.stars)), ("availabilityRemains", ConstructorParameterDescription(_data.availabilityRemains)), ("availabilityTotal", ConstructorParameterDescription(_data.availabilityTotal)), ("availabilityResale", ConstructorParameterDescription(_data.availabilityResale)), ("convertStars", ConstructorParameterDescription(_data.convertStars)), ("firstSaleDate", ConstructorParameterDescription(_data.firstSaleDate)), ("lastSaleDate", ConstructorParameterDescription(_data.lastSaleDate)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars)), ("resellMinStars", ConstructorParameterDescription(_data.resellMinStars)), ("title", ConstructorParameterDescription(_data.title)), ("releasedBy", ConstructorParameterDescription(_data.releasedBy)), ("perUserTotal", ConstructorParameterDescription(_data.perUserTotal)), ("perUserRemains", ConstructorParameterDescription(_data.perUserRemains)), ("lockedUntilDate", ConstructorParameterDescription(_data.lockedUntilDate)), ("auctionSlug", ConstructorParameterDescription(_data.auctionSlug)), ("giftsPerRound", ConstructorParameterDescription(_data.giftsPerRound)), ("auctionStartDate", ConstructorParameterDescription(_data.auctionStartDate)), ("upgradeVariants", ConstructorParameterDescription(_data.upgradeVariants)), ("background", ConstructorParameterDescription(_data.background))]) + case .starGiftUnique(let _data): + return ("starGiftUnique", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("giftId", ConstructorParameterDescription(_data.giftId)), ("title", ConstructorParameterDescription(_data.title)), ("slug", ConstructorParameterDescription(_data.slug)), ("num", ConstructorParameterDescription(_data.num)), ("ownerId", ConstructorParameterDescription(_data.ownerId)), ("ownerName", ConstructorParameterDescription(_data.ownerName)), ("ownerAddress", ConstructorParameterDescription(_data.ownerAddress)), ("attributes", ConstructorParameterDescription(_data.attributes)), ("availabilityIssued", ConstructorParameterDescription(_data.availabilityIssued)), ("availabilityTotal", ConstructorParameterDescription(_data.availabilityTotal)), ("giftAddress", ConstructorParameterDescription(_data.giftAddress)), ("resellAmount", ConstructorParameterDescription(_data.resellAmount)), ("releasedBy", ConstructorParameterDescription(_data.releasedBy)), ("valueAmount", ConstructorParameterDescription(_data.valueAmount)), ("valueCurrency", ConstructorParameterDescription(_data.valueCurrency)), ("valueUsdAmount", ConstructorParameterDescription(_data.valueUsdAmount)), ("themePeer", ConstructorParameterDescription(_data.themePeer)), ("peerColor", ConstructorParameterDescription(_data.peerColor)), ("hostId", ConstructorParameterDescription(_data.hostId)), ("offerMinStars", ConstructorParameterDescription(_data.offerMinStars)), ("craftChancePermille", ConstructorParameterDescription(_data.craftChancePermille))]) + } + } + + public static func parse_starGift(_ reader: BufferReader) -> StarGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Document? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Document + } + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt32() + } + var _7: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _7 = reader.readInt64() + } + var _8: Int64? + _8 = reader.readInt64() + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _9 = reader.readInt32() + } + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _10 = reader.readInt32() + } + var _11: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _11 = reader.readInt64() + } + var _12: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _12 = reader.readInt64() + } + var _13: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _13 = parseString(reader) + } + var _14: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _15: Int32? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _15 = reader.readInt32() + } + var _16: Int32? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _16 = reader.readInt32() + } + var _17: Int32? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + _17 = reader.readInt32() + } + var _18: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _18 = parseString(reader) + } + var _19: Int32? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _19 = reader.readInt32() + } + var _20: Int32? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _20 = reader.readInt32() + } + var _21: Int32? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + _21 = reader.readInt32() + } + var _22: Api.StarGiftBackground? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + if let signature = reader.readInt32() { + _22 = Api.parse(reader, signature: signature) as? Api.StarGiftBackground + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _20 != nil + let _c21 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _21 != nil + let _c22 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _22 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { + return Api.StarGift.starGift(Cons_starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14, perUserTotal: _15, perUserRemains: _16, lockedUntilDate: _17, auctionSlug: _18, giftsPerRound: _19, auctionStartDate: _20, upgradeVariants: _21, background: _22)) + } + else { + return nil + } + } + public static func parse_starGiftUnique(_ reader: BufferReader) -> StarGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _8 = parseString(reader) + } + var _9: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _9 = parseString(reader) + } + var _10: [Api.StarGiftAttribute]? + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + } + var _11: Int32? + _11 = reader.readInt32() + var _12: Int32? + _12 = reader.readInt32() + var _13: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _13 = parseString(reader) + } + var _14: [Api.StarsAmount]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsAmount.self) + } + } + var _15: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _16: Int64? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _16 = reader.readInt64() + } + var _17: String? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _17 = parseString(reader) + } + var _18: Int64? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _18 = reader.readInt64() + } + var _19: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _20: Api.PeerColor? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.PeerColor + } + } + var _21: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _22: Int32? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + _22 = reader.readInt32() + } + var _23: Int32? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _23 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _20 != nil + let _c21 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _21 != nil + let _c22 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _22 != nil + let _c23 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _23 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 { + return Api.StarGift.starGiftUnique(Cons_starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, valueUsdAmount: _18, themePeer: _19, peerColor: _20, hostId: _21, offerMinStars: _22, craftChancePermille: _23)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarGiftActiveAuctionState: TypeConstructorDescription { + public class Cons_starGiftActiveAuctionState: TypeConstructorDescription { + public var gift: Api.StarGift + public var state: Api.StarGiftAuctionState + public var userState: Api.StarGiftAuctionUserState + public init(gift: Api.StarGift, state: Api.StarGiftAuctionState, userState: Api.StarGiftAuctionUserState) { + self.gift = gift + self.state = state + self.userState = userState + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftActiveAuctionState", [("gift", ConstructorParameterDescription(self.gift)), ("state", ConstructorParameterDescription(self.state)), ("userState", ConstructorParameterDescription(self.userState))]) + } + } + case starGiftActiveAuctionState(Cons_starGiftActiveAuctionState) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftActiveAuctionState(let _data): + if boxed { + buffer.appendInt32(-753154979) + } + _data.gift.serialize(buffer, true) + _data.state.serialize(buffer, true) + _data.userState.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftActiveAuctionState(let _data): + return ("starGiftActiveAuctionState", [("gift", ConstructorParameterDescription(_data.gift)), ("state", ConstructorParameterDescription(_data.state)), ("userState", ConstructorParameterDescription(_data.userState))]) + } + } + + public static func parse_starGiftActiveAuctionState(_ reader: BufferReader) -> StarGiftActiveAuctionState? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _2: Api.StarGiftAuctionState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState + } + var _3: Api.StarGiftAuctionUserState? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StarGiftActiveAuctionState.starGiftActiveAuctionState(Cons_starGiftActiveAuctionState(gift: _1!, state: _2!, userState: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarGiftAttribute: TypeConstructorDescription { + public class Cons_starGiftAttributeBackdrop: TypeConstructorDescription { + public var name: String + public var backdropId: Int32 + public var centerColor: Int32 + public var edgeColor: Int32 + public var patternColor: Int32 + public var textColor: Int32 + public var rarity: Api.StarGiftAttributeRarity + public init(name: String, backdropId: Int32, centerColor: Int32, edgeColor: Int32, patternColor: Int32, textColor: Int32, rarity: Api.StarGiftAttributeRarity) { + self.name = name + self.backdropId = backdropId + self.centerColor = centerColor + self.edgeColor = edgeColor + self.patternColor = patternColor + self.textColor = textColor + self.rarity = rarity + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeBackdrop", [("name", ConstructorParameterDescription(self.name)), ("backdropId", ConstructorParameterDescription(self.backdropId)), ("centerColor", ConstructorParameterDescription(self.centerColor)), ("edgeColor", ConstructorParameterDescription(self.edgeColor)), ("patternColor", ConstructorParameterDescription(self.patternColor)), ("textColor", ConstructorParameterDescription(self.textColor)), ("rarity", ConstructorParameterDescription(self.rarity))]) + } + } + public class Cons_starGiftAttributeModel: TypeConstructorDescription { + public var flags: Int32 + public var name: String + public var document: Api.Document + public var rarity: Api.StarGiftAttributeRarity + public init(flags: Int32, name: String, document: Api.Document, rarity: Api.StarGiftAttributeRarity) { + self.flags = flags + self.name = name + self.document = document + self.rarity = rarity + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeModel", [("flags", ConstructorParameterDescription(self.flags)), ("name", ConstructorParameterDescription(self.name)), ("document", ConstructorParameterDescription(self.document)), ("rarity", ConstructorParameterDescription(self.rarity))]) + } + } + public class Cons_starGiftAttributeOriginalDetails: TypeConstructorDescription { + public var flags: Int32 + public var senderId: Api.Peer? + public var recipientId: Api.Peer + public var date: Int32 + public var message: Api.TextWithEntities? + public init(flags: Int32, senderId: Api.Peer?, recipientId: Api.Peer, date: Int32, message: Api.TextWithEntities?) { + self.flags = flags + self.senderId = senderId + self.recipientId = recipientId + self.date = date + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeOriginalDetails", [("flags", ConstructorParameterDescription(self.flags)), ("senderId", ConstructorParameterDescription(self.senderId)), ("recipientId", ConstructorParameterDescription(self.recipientId)), ("date", ConstructorParameterDescription(self.date)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_starGiftAttributePattern: TypeConstructorDescription { + public var name: String + public var document: Api.Document + public var rarity: Api.StarGiftAttributeRarity + public init(name: String, document: Api.Document, rarity: Api.StarGiftAttributeRarity) { + self.name = name + self.document = document + self.rarity = rarity + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributePattern", [("name", ConstructorParameterDescription(self.name)), ("document", ConstructorParameterDescription(self.document)), ("rarity", ConstructorParameterDescription(self.rarity))]) + } + } + case starGiftAttributeBackdrop(Cons_starGiftAttributeBackdrop) + case starGiftAttributeModel(Cons_starGiftAttributeModel) + case starGiftAttributeOriginalDetails(Cons_starGiftAttributeOriginalDetails) + case starGiftAttributePattern(Cons_starGiftAttributePattern) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAttributeBackdrop(let _data): + if boxed { + buffer.appendInt32(-1624963868) + } + serializeString(_data.name, buffer: buffer, boxed: false) + serializeInt32(_data.backdropId, buffer: buffer, boxed: false) + serializeInt32(_data.centerColor, buffer: buffer, boxed: false) + serializeInt32(_data.edgeColor, buffer: buffer, boxed: false) + serializeInt32(_data.patternColor, buffer: buffer, boxed: false) + serializeInt32(_data.textColor, buffer: buffer, boxed: false) + _data.rarity.serialize(buffer, true) + break + case .starGiftAttributeModel(let _data): + if boxed { + buffer.appendInt32(1448235490) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.name, buffer: buffer, boxed: false) + _data.document.serialize(buffer, true) + _data.rarity.serialize(buffer, true) + break + case .starGiftAttributeOriginalDetails(let _data): + if boxed { + buffer.appendInt32(-524291476) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.senderId!.serialize(buffer, true) + } + _data.recipientId.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.message!.serialize(buffer, true) + } + break + case .starGiftAttributePattern(let _data): + if boxed { + buffer.appendInt32(1315997162) + } + serializeString(_data.name, buffer: buffer, boxed: false) + _data.document.serialize(buffer, true) + _data.rarity.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAttributeBackdrop(let _data): + return ("starGiftAttributeBackdrop", [("name", ConstructorParameterDescription(_data.name)), ("backdropId", ConstructorParameterDescription(_data.backdropId)), ("centerColor", ConstructorParameterDescription(_data.centerColor)), ("edgeColor", ConstructorParameterDescription(_data.edgeColor)), ("patternColor", ConstructorParameterDescription(_data.patternColor)), ("textColor", ConstructorParameterDescription(_data.textColor)), ("rarity", ConstructorParameterDescription(_data.rarity))]) + case .starGiftAttributeModel(let _data): + return ("starGiftAttributeModel", [("flags", ConstructorParameterDescription(_data.flags)), ("name", ConstructorParameterDescription(_data.name)), ("document", ConstructorParameterDescription(_data.document)), ("rarity", ConstructorParameterDescription(_data.rarity))]) + case .starGiftAttributeOriginalDetails(let _data): + return ("starGiftAttributeOriginalDetails", [("flags", ConstructorParameterDescription(_data.flags)), ("senderId", ConstructorParameterDescription(_data.senderId)), ("recipientId", ConstructorParameterDescription(_data.recipientId)), ("date", ConstructorParameterDescription(_data.date)), ("message", ConstructorParameterDescription(_data.message))]) + case .starGiftAttributePattern(let _data): + return ("starGiftAttributePattern", [("name", ConstructorParameterDescription(_data.name)), ("document", ConstructorParameterDescription(_data.document)), ("rarity", ConstructorParameterDescription(_data.rarity))]) + } + } + + public static func parse_starGiftAttributeBackdrop(_ reader: BufferReader) -> StarGiftAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.StarGiftAttributeRarity? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.StarGiftAttribute.starGiftAttributeBackdrop(Cons_starGiftAttributeBackdrop(name: _1!, backdropId: _2!, centerColor: _3!, edgeColor: _4!, patternColor: _5!, textColor: _6!, rarity: _7!)) + } + else { + return nil + } + } + public static func parse_starGiftAttributeModel(_ reader: BufferReader) -> StarGiftAttribute? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) + var _3: Api.Document? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Document + } + var _4: Api.StarGiftAttributeRarity? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StarGiftAttribute.starGiftAttributeModel(Cons_starGiftAttributeModel(flags: _1!, name: _2!, document: _3!, rarity: _4!)) + } + else { + return nil + } + } + public static func parse_starGiftAttributeOriginalDetails(_ reader: BufferReader) -> StarGiftAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } var _3: Api.Peer? if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.Peer } var _4: Int32? _4 = reader.readInt32() - var _5: Api.StarsSubscriptionPricing? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing - } - var _6: String? - if Int(_1!) & Int(1 << 3) != 0 { - _6 = parseString(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 4) != 0 { - _7 = parseString(reader) - } - var _8: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 { + var _5: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.WebDocument + _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } } - var _9: String? - if Int(_1!) & Int(1 << 6) != 0 { - _9 = parseString(reader) - } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.StarsSubscription.starsSubscription(Cons_starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6, title: _7, photo: _8, invoiceSlug: _9)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarsSubscriptionPricing: TypeConstructorDescription { - public class Cons_starsSubscriptionPricing: TypeConstructorDescription { - public var period: Int32 - public var amount: Int64 - public init(period: Int32, amount: Int64) { - self.period = period - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsSubscriptionPricing", [("period", ConstructorParameterDescription(self.period)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - case starsSubscriptionPricing(Cons_starsSubscriptionPricing) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsSubscriptionPricing(let _data): - if boxed { - buffer.appendInt32(88173912) - } - serializeInt32(_data.period, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsSubscriptionPricing(let _data): - return ("starsSubscriptionPricing", [("period", ConstructorParameterDescription(_data.period)), ("amount", ConstructorParameterDescription(_data.amount))]) - } - } - - public static func parse_starsSubscriptionPricing(_ reader: BufferReader) -> StarsSubscriptionPricing? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StarsSubscriptionPricing.starsSubscriptionPricing(Cons_starsSubscriptionPricing(period: _1!, amount: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarsTopupOption: TypeConstructorDescription { - public class Cons_starsTopupOption: TypeConstructorDescription { - public var flags: Int32 - public var stars: Int64 - public var storeProduct: String? - public var currency: String - public var amount: Int64 - public init(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) { - self.flags = flags - self.stars = stars - self.storeProduct = storeProduct - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsTopupOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - case starsTopupOption(Cons_starsTopupOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTopupOption(let _data): - if boxed { - buffer.appendInt32(198776256) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stars, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.storeProduct!, buffer: buffer, boxed: false) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsTopupOption(let _data): - return ("starsTopupOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - } - } - - public static func parse_starsTopupOption(_ reader: BufferReader) -> StarsTopupOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StarsTopupOption.starsTopupOption(Cons_starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)) + return Api.StarGiftAttribute.starGiftAttributeOriginalDetails(Cons_starGiftAttributeOriginalDetails(flags: _1!, senderId: _2, recipientId: _3!, date: _4!, message: _5)) } else { return nil } } - } -} -public extension Api { - enum StarsTransaction: TypeConstructorDescription { - public class Cons_starsTransaction: TypeConstructorDescription { - public var flags: Int32 - public var id: String - public var amount: Api.StarsAmount - public var date: Int32 - public var peer: Api.StarsTransactionPeer - public var title: String? - public var description: String? - public var photo: Api.WebDocument? - public var transactionDate: Int32? - public var transactionUrl: String? - public var botPayload: Buffer? - public var msgId: Int32? - public var extendedMedia: [Api.MessageMedia]? - public var subscriptionPeriod: Int32? - public var giveawayPostId: Int32? - public var stargift: Api.StarGift? - public var floodskipNumber: Int32? - public var starrefCommissionPermille: Int32? - public var starrefPeer: Api.Peer? - public var starrefAmount: Api.StarsAmount? - public var paidMessages: Int32? - public var premiumGiftMonths: Int32? - public var adsProceedsFromDate: Int32? - public var adsProceedsToDate: Int32? - public init(flags: Int32, id: String, amount: Api.StarsAmount, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?, starrefCommissionPermille: Int32?, starrefPeer: Api.Peer?, starrefAmount: Api.StarsAmount?, paidMessages: Int32?, premiumGiftMonths: Int32?, adsProceedsFromDate: Int32?, adsProceedsToDate: Int32?) { - self.flags = flags - self.id = id - self.amount = amount - self.date = date - self.peer = peer - self.title = title - self.description = description - self.photo = photo - self.transactionDate = transactionDate - self.transactionUrl = transactionUrl - self.botPayload = botPayload - self.msgId = msgId - self.extendedMedia = extendedMedia - self.subscriptionPeriod = subscriptionPeriod - self.giveawayPostId = giveawayPostId - self.stargift = stargift - self.floodskipNumber = floodskipNumber - self.starrefCommissionPermille = starrefCommissionPermille - self.starrefPeer = starrefPeer - self.starrefAmount = starrefAmount - self.paidMessages = paidMessages - self.premiumGiftMonths = premiumGiftMonths - self.adsProceedsFromDate = adsProceedsFromDate - self.adsProceedsToDate = adsProceedsToDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsTransaction", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("amount", ConstructorParameterDescription(self.amount)), ("date", ConstructorParameterDescription(self.date)), ("peer", ConstructorParameterDescription(self.peer)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("transactionDate", ConstructorParameterDescription(self.transactionDate)), ("transactionUrl", ConstructorParameterDescription(self.transactionUrl)), ("botPayload", ConstructorParameterDescription(self.botPayload)), ("msgId", ConstructorParameterDescription(self.msgId)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia)), ("subscriptionPeriod", ConstructorParameterDescription(self.subscriptionPeriod)), ("giveawayPostId", ConstructorParameterDescription(self.giveawayPostId)), ("stargift", ConstructorParameterDescription(self.stargift)), ("floodskipNumber", ConstructorParameterDescription(self.floodskipNumber)), ("starrefCommissionPermille", ConstructorParameterDescription(self.starrefCommissionPermille)), ("starrefPeer", ConstructorParameterDescription(self.starrefPeer)), ("starrefAmount", ConstructorParameterDescription(self.starrefAmount)), ("paidMessages", ConstructorParameterDescription(self.paidMessages)), ("premiumGiftMonths", ConstructorParameterDescription(self.premiumGiftMonths)), ("adsProceedsFromDate", ConstructorParameterDescription(self.adsProceedsFromDate)), ("adsProceedsToDate", ConstructorParameterDescription(self.adsProceedsToDate))]) - } - } - case starsTransaction(Cons_starsTransaction) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTransaction(let _data): - if boxed { - buffer.appendInt32(325426864) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.id, buffer: buffer, boxed: false) - _data.amount.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.description!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.transactionDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.transactionUrl!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeBytes(_data.botPayload!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt32(_data.msgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.extendedMedia!.count)) - for item in _data.extendedMedia! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 12) != 0 { - serializeInt32(_data.subscriptionPeriod!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 13) != 0 { - serializeInt32(_data.giveawayPostId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - _data.stargift!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - serializeInt32(_data.floodskipNumber!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeInt32(_data.starrefCommissionPermille!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - _data.starrefPeer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - _data.starrefAmount!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 19) != 0 { - serializeInt32(_data.paidMessages!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 20) != 0 { - serializeInt32(_data.premiumGiftMonths!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 23) != 0 { - serializeInt32(_data.adsProceedsFromDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 23) != 0 { - serializeInt32(_data.adsProceedsToDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsTransaction(let _data): - return ("starsTransaction", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("amount", ConstructorParameterDescription(_data.amount)), ("date", ConstructorParameterDescription(_data.date)), ("peer", ConstructorParameterDescription(_data.peer)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("transactionDate", ConstructorParameterDescription(_data.transactionDate)), ("transactionUrl", ConstructorParameterDescription(_data.transactionUrl)), ("botPayload", ConstructorParameterDescription(_data.botPayload)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia)), ("subscriptionPeriod", ConstructorParameterDescription(_data.subscriptionPeriod)), ("giveawayPostId", ConstructorParameterDescription(_data.giveawayPostId)), ("stargift", ConstructorParameterDescription(_data.stargift)), ("floodskipNumber", ConstructorParameterDescription(_data.floodskipNumber)), ("starrefCommissionPermille", ConstructorParameterDescription(_data.starrefCommissionPermille)), ("starrefPeer", ConstructorParameterDescription(_data.starrefPeer)), ("starrefAmount", ConstructorParameterDescription(_data.starrefAmount)), ("paidMessages", ConstructorParameterDescription(_data.paidMessages)), ("premiumGiftMonths", ConstructorParameterDescription(_data.premiumGiftMonths)), ("adsProceedsFromDate", ConstructorParameterDescription(_data.adsProceedsFromDate)), ("adsProceedsToDate", ConstructorParameterDescription(_data.adsProceedsToDate))]) - } - } - - public static func parse_starsTransaction(_ reader: BufferReader) -> StarsTransaction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.StarsAmount? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Api.StarsTransactionPeer? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) - } - var _8: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.WebDocument - } - } - var _9: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _9 = reader.readInt32() - } - var _10: String? - if Int(_1!) & Int(1 << 5) != 0 { - _10 = parseString(reader) - } - var _11: Buffer? - if Int(_1!) & Int(1 << 7) != 0 { - _11 = parseBytes(reader) - } - var _12: Int32? - if Int(_1!) & Int(1 << 8) != 0 { - _12 = reader.readInt32() - } - var _13: [Api.MessageMedia]? - if Int(_1!) & Int(1 << 9) != 0 { - if let _ = reader.readInt32() { - _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self) - } - } - var _14: Int32? - if Int(_1!) & Int(1 << 12) != 0 { - _14 = reader.readInt32() - } - var _15: Int32? - if Int(_1!) & Int(1 << 13) != 0 { - _15 = reader.readInt32() - } - var _16: Api.StarGift? - if Int(_1!) & Int(1 << 14) != 0 { - if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.StarGift - } - } - var _17: Int32? - if Int(_1!) & Int(1 << 15) != 0 { - _17 = reader.readInt32() - } - var _18: Int32? - if Int(_1!) & Int(1 << 16) != 0 { - _18 = reader.readInt32() - } - var _19: Api.Peer? - if Int(_1!) & Int(1 << 17) != 0 { - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _20: Api.StarsAmount? - if Int(_1!) & Int(1 << 17) != 0 { - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - } - var _21: Int32? - if Int(_1!) & Int(1 << 19) != 0 { - _21 = reader.readInt32() - } - var _22: Int32? - if Int(_1!) & Int(1 << 20) != 0 { - _22 = reader.readInt32() - } - var _23: Int32? - if Int(_1!) & Int(1 << 23) != 0 { - _23 = reader.readInt32() - } - var _24: Int32? - if Int(_1!) & Int(1 << 23) != 0 { - _24 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 19) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 20) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 23) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 23) == 0) || _24 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { - return Api.StarsTransaction.starsTransaction(Cons_starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StarsTransactionPeer: TypeConstructorDescription { - public class Cons_starsTransactionPeer: TypeConstructorDescription { - public var peer: Api.Peer - public init(peer: Api.Peer) { - self.peer = peer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsTransactionPeer", [("peer", ConstructorParameterDescription(self.peer))]) - } - } - case starsTransactionPeer(Cons_starsTransactionPeer) - case starsTransactionPeerAPI - case starsTransactionPeerAds - case starsTransactionPeerAppStore - case starsTransactionPeerFragment - case starsTransactionPeerPlayMarket - case starsTransactionPeerPremiumBot - case starsTransactionPeerUnsupported - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsTransactionPeer(let _data): - if boxed { - buffer.appendInt32(-670195363) - } - _data.peer.serialize(buffer, true) - break - case .starsTransactionPeerAPI: - if boxed { - buffer.appendInt32(-110658899) - } - break - case .starsTransactionPeerAds: - if boxed { - buffer.appendInt32(1617438738) - } - break - case .starsTransactionPeerAppStore: - if boxed { - buffer.appendInt32(-1269320843) - } - break - case .starsTransactionPeerFragment: - if boxed { - buffer.appendInt32(-382740222) - } - break - case .starsTransactionPeerPlayMarket: - if boxed { - buffer.appendInt32(2069236235) - } - break - case .starsTransactionPeerPremiumBot: - if boxed { - buffer.appendInt32(621656824) - } - break - case .starsTransactionPeerUnsupported: - if boxed { - buffer.appendInt32(-1779253276) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsTransactionPeer(let _data): - return ("starsTransactionPeer", [("peer", ConstructorParameterDescription(_data.peer))]) - case .starsTransactionPeerAPI: - return ("starsTransactionPeerAPI", []) - case .starsTransactionPeerAds: - return ("starsTransactionPeerAds", []) - case .starsTransactionPeerAppStore: - return ("starsTransactionPeerAppStore", []) - case .starsTransactionPeerFragment: - return ("starsTransactionPeerFragment", []) - case .starsTransactionPeerPlayMarket: - return ("starsTransactionPeerPlayMarket", []) - case .starsTransactionPeerPremiumBot: - return ("starsTransactionPeerPremiumBot", []) - case .starsTransactionPeerUnsupported: - return ("starsTransactionPeerUnsupported", []) - } - } - - public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - if _c1 { - return Api.StarsTransactionPeer.starsTransactionPeer(Cons_starsTransactionPeer(peer: _1!)) - } - else { - return nil - } - } - public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAPI - } - public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAds - } - public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerAppStore - } - public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerFragment - } - public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket - } - public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot - } - public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { - return Api.StarsTransactionPeer.starsTransactionPeerUnsupported - } - } -} -public extension Api { - enum StatsAbsValueAndPrev: TypeConstructorDescription { - public class Cons_statsAbsValueAndPrev: TypeConstructorDescription { - public var current: Double - public var previous: Double - public init(current: Double, previous: Double) { - self.current = current - self.previous = previous - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsAbsValueAndPrev", [("current", ConstructorParameterDescription(self.current)), ("previous", ConstructorParameterDescription(self.previous))]) - } - } - case statsAbsValueAndPrev(Cons_statsAbsValueAndPrev) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsAbsValueAndPrev(let _data): - if boxed { - buffer.appendInt32(-884757282) - } - serializeDouble(_data.current, buffer: buffer, boxed: false) - serializeDouble(_data.previous, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsAbsValueAndPrev(let _data): - return ("statsAbsValueAndPrev", [("current", ConstructorParameterDescription(_data.current)), ("previous", ConstructorParameterDescription(_data.previous))]) - } - } - - public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(Cons_statsAbsValueAndPrev(current: _1!, previous: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsDateRangeDays: TypeConstructorDescription { - public class Cons_statsDateRangeDays: TypeConstructorDescription { - public var minDate: Int32 - public var maxDate: Int32 - public init(minDate: Int32, maxDate: Int32) { - self.minDate = minDate - self.maxDate = maxDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsDateRangeDays", [("minDate", ConstructorParameterDescription(self.minDate)), ("maxDate", ConstructorParameterDescription(self.maxDate))]) - } - } - case statsDateRangeDays(Cons_statsDateRangeDays) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsDateRangeDays(let _data): - if boxed { - buffer.appendInt32(-1237848657) - } - serializeInt32(_data.minDate, buffer: buffer, boxed: false) - serializeInt32(_data.maxDate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsDateRangeDays(let _data): - return ("statsDateRangeDays", [("minDate", ConstructorParameterDescription(_data.minDate)), ("maxDate", ConstructorParameterDescription(_data.maxDate))]) - } - } - - public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsDateRangeDays.statsDateRangeDays(Cons_statsDateRangeDays(minDate: _1!, maxDate: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsGraph: TypeConstructorDescription { - public class Cons_statsGraph: TypeConstructorDescription { - public var flags: Int32 - public var json: Api.DataJSON - public var zoomToken: String? - public init(flags: Int32, json: Api.DataJSON, zoomToken: String?) { - self.flags = flags - self.json = json - self.zoomToken = zoomToken - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGraph", [("flags", ConstructorParameterDescription(self.flags)), ("json", ConstructorParameterDescription(self.json)), ("zoomToken", ConstructorParameterDescription(self.zoomToken))]) - } - } - public class Cons_statsGraphAsync: TypeConstructorDescription { - public var token: String - public init(token: String) { - self.token = token - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGraphAsync", [("token", ConstructorParameterDescription(self.token))]) - } - } - public class Cons_statsGraphError: TypeConstructorDescription { - public var error: String - public init(error: String) { - self.error = error - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGraphError", [("error", ConstructorParameterDescription(self.error))]) - } - } - case statsGraph(Cons_statsGraph) - case statsGraphAsync(Cons_statsGraphAsync) - case statsGraphError(Cons_statsGraphError) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGraph(let _data): - if boxed { - buffer.appendInt32(-1901828938) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.json.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.zoomToken!, buffer: buffer, boxed: false) - } - break - case .statsGraphAsync(let _data): - if boxed { - buffer.appendInt32(1244130093) - } - serializeString(_data.token, buffer: buffer, boxed: false) - break - case .statsGraphError(let _data): - if boxed { - buffer.appendInt32(-1092839390) - } - serializeString(_data.error, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsGraph(let _data): - return ("statsGraph", [("flags", ConstructorParameterDescription(_data.flags)), ("json", ConstructorParameterDescription(_data.json)), ("zoomToken", ConstructorParameterDescription(_data.zoomToken))]) - case .statsGraphAsync(let _data): - return ("statsGraphAsync", [("token", ConstructorParameterDescription(_data.token))]) - case .statsGraphError(let _data): - return ("statsGraphError", [("error", ConstructorParameterDescription(_data.error))]) - } - } - - public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.StatsGraph.statsGraph(Cons_statsGraph(flags: _1!, json: _2!, zoomToken: _3)) - } - else { - return nil - } - } - public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? { + public static func parse_starGiftAttributePattern(_ reader: BufferReader) -> StarGiftAttribute? { var _1: String? _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphAsync(Cons_statsGraphAsync(token: _1!)) + var _2: Api.Document? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Document } - else { - return nil + var _3: Api.StarGiftAttributeRarity? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeRarity } - } - public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.StatsGraph.statsGraphError(Cons_statsGraphError(error: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsGroupTopAdmin: TypeConstructorDescription { - public class Cons_statsGroupTopAdmin: TypeConstructorDescription { - public var userId: Int64 - public var deleted: Int32 - public var kicked: Int32 - public var banned: Int32 - public init(userId: Int64, deleted: Int32, kicked: Int32, banned: Int32) { - self.userId = userId - self.deleted = deleted - self.kicked = kicked - self.banned = banned - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGroupTopAdmin", [("userId", ConstructorParameterDescription(self.userId)), ("deleted", ConstructorParameterDescription(self.deleted)), ("kicked", ConstructorParameterDescription(self.kicked)), ("banned", ConstructorParameterDescription(self.banned))]) - } - } - case statsGroupTopAdmin(Cons_statsGroupTopAdmin) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGroupTopAdmin(let _data): - if boxed { - buffer.appendInt32(-682079097) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.deleted, buffer: buffer, boxed: false) - serializeInt32(_data.kicked, buffer: buffer, boxed: false) - serializeInt32(_data.banned, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsGroupTopAdmin(let _data): - return ("statsGroupTopAdmin", [("userId", ConstructorParameterDescription(_data.userId)), ("deleted", ConstructorParameterDescription(_data.deleted)), ("kicked", ConstructorParameterDescription(_data.kicked)), ("banned", ConstructorParameterDescription(_data.banned))]) - } - } - - public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StatsGroupTopAdmin.statsGroupTopAdmin(Cons_statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsGroupTopInviter: TypeConstructorDescription { - public class Cons_statsGroupTopInviter: TypeConstructorDescription { - public var userId: Int64 - public var invitations: Int32 - public init(userId: Int64, invitations: Int32) { - self.userId = userId - self.invitations = invitations - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGroupTopInviter", [("userId", ConstructorParameterDescription(self.userId)), ("invitations", ConstructorParameterDescription(self.invitations))]) - } - } - case statsGroupTopInviter(Cons_statsGroupTopInviter) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGroupTopInviter(let _data): - if boxed { - buffer.appendInt32(1398765469) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.invitations, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsGroupTopInviter(let _data): - return ("statsGroupTopInviter", [("userId", ConstructorParameterDescription(_data.userId)), ("invitations", ConstructorParameterDescription(_data.invitations))]) - } - } - - public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsGroupTopInviter.statsGroupTopInviter(Cons_statsGroupTopInviter(userId: _1!, invitations: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsGroupTopPoster: TypeConstructorDescription { - public class Cons_statsGroupTopPoster: TypeConstructorDescription { - public var userId: Int64 - public var messages: Int32 - public var avgChars: Int32 - public init(userId: Int64, messages: Int32, avgChars: Int32) { - self.userId = userId - self.messages = messages - self.avgChars = avgChars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsGroupTopPoster", [("userId", ConstructorParameterDescription(self.userId)), ("messages", ConstructorParameterDescription(self.messages)), ("avgChars", ConstructorParameterDescription(self.avgChars))]) - } - } - case statsGroupTopPoster(Cons_statsGroupTopPoster) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsGroupTopPoster(let _data): - if boxed { - buffer.appendInt32(-1660637285) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.messages, buffer: buffer, boxed: false) - serializeInt32(_data.avgChars, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsGroupTopPoster(let _data): - return ("statsGroupTopPoster", [("userId", ConstructorParameterDescription(_data.userId)), ("messages", ConstructorParameterDescription(_data.messages)), ("avgChars", ConstructorParameterDescription(_data.avgChars))]) - } - } - - public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.StatsGroupTopPoster.statsGroupTopPoster(Cons_statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsPercentValue: TypeConstructorDescription { - public class Cons_statsPercentValue: TypeConstructorDescription { - public var part: Double - public var total: Double - public init(part: Double, total: Double) { - self.part = part - self.total = total - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsPercentValue", [("part", ConstructorParameterDescription(self.part)), ("total", ConstructorParameterDescription(self.total))]) - } - } - case statsPercentValue(Cons_statsPercentValue) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsPercentValue(let _data): - if boxed { - buffer.appendInt32(-875679776) - } - serializeDouble(_data.part, buffer: buffer, boxed: false) - serializeDouble(_data.total, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsPercentValue(let _data): - return ("statsPercentValue", [("part", ConstructorParameterDescription(_data.part)), ("total", ConstructorParameterDescription(_data.total))]) - } - } - - public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StatsPercentValue.statsPercentValue(Cons_statsPercentValue(part: _1!, total: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StatsURL: TypeConstructorDescription { - public class Cons_statsURL: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("statsURL", [("url", ConstructorParameterDescription(self.url))]) - } - } - case statsURL(Cons_statsURL) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .statsURL(let _data): - if boxed { - buffer.appendInt32(1202287072) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .statsURL(let _data): - return ("statsURL", [("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_statsURL(_ reader: BufferReader) -> StatsURL? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.StatsURL.statsURL(Cons_statsURL(url: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StickerKeyword: TypeConstructorDescription { - public class Cons_stickerKeyword: TypeConstructorDescription { - public var documentId: Int64 - public var keyword: [String] - public init(documentId: Int64, keyword: [String]) { - self.documentId = documentId - self.keyword = keyword - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerKeyword", [("documentId", ConstructorParameterDescription(self.documentId)), ("keyword", ConstructorParameterDescription(self.keyword))]) - } - } - case stickerKeyword(Cons_stickerKeyword) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerKeyword(let _data): - if boxed { - buffer.appendInt32(-50416996) - } - serializeInt64(_data.documentId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.keyword.count)) - for item in _data.keyword { - serializeString(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .stickerKeyword(let _data): - return ("stickerKeyword", [("documentId", ConstructorParameterDescription(_data.documentId)), ("keyword", ConstructorParameterDescription(_data.keyword))]) - } - } - - public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [String]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerKeyword.stickerKeyword(Cons_stickerKeyword(documentId: _1!, keyword: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StickerPack: TypeConstructorDescription { - public class Cons_stickerPack: TypeConstructorDescription { - public var emoticon: String - public var documents: [Int64] - public init(emoticon: String, documents: [Int64]) { - self.emoticon = emoticon - self.documents = documents - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerPack", [("emoticon", ConstructorParameterDescription(self.emoticon)), ("documents", ConstructorParameterDescription(self.documents))]) - } - } - case stickerPack(Cons_stickerPack) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerPack(let _data): - if boxed { - buffer.appendInt32(313694676) - } - serializeString(_data.emoticon, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .stickerPack(let _data): - return ("stickerPack", [("emoticon", ConstructorParameterDescription(_data.emoticon)), ("documents", ConstructorParameterDescription(_data.documents))]) - } - } - - public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { - var _1: String? - _1 = parseString(reader) - var _2: [Int64]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StickerPack.stickerPack(Cons_stickerPack(emoticon: _1!, documents: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StickerSet: TypeConstructorDescription { - public class Cons_stickerSet: TypeConstructorDescription { - public var flags: Int32 - public var installedDate: Int32? - public var id: Int64 - public var accessHash: Int64 - public var title: String - public var shortName: String - public var thumbs: [Api.PhotoSize]? - public var thumbDcId: Int32? - public var thumbVersion: Int32? - public var thumbDocumentId: Int64? - public var count: Int32 - public var hash: Int32 - public init(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32) { - self.flags = flags - self.installedDate = installedDate - self.id = id - self.accessHash = accessHash - self.title = title - self.shortName = shortName - self.thumbs = thumbs - self.thumbDcId = thumbDcId - self.thumbVersion = thumbVersion - self.thumbDocumentId = thumbDocumentId - self.count = count - self.hash = hash - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSet", [("flags", ConstructorParameterDescription(self.flags)), ("installedDate", ConstructorParameterDescription(self.installedDate)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("title", ConstructorParameterDescription(self.title)), ("shortName", ConstructorParameterDescription(self.shortName)), ("thumbs", ConstructorParameterDescription(self.thumbs)), ("thumbDcId", ConstructorParameterDescription(self.thumbDcId)), ("thumbVersion", ConstructorParameterDescription(self.thumbVersion)), ("thumbDocumentId", ConstructorParameterDescription(self.thumbDocumentId)), ("count", ConstructorParameterDescription(self.count)), ("hash", ConstructorParameterDescription(self.hash))]) - } - } - case stickerSet(Cons_stickerSet) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stickerSet(let _data): - if boxed { - buffer.appendInt32(768691932) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.installedDate!, buffer: buffer, boxed: false) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.shortName, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.thumbs!.count)) - for item in _data.thumbs! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.thumbDcId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.thumbVersion!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeInt64(_data.thumbDocumentId!, buffer: buffer, boxed: false) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - serializeInt32(_data.hash, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .stickerSet(let _data): - return ("stickerSet", [("flags", ConstructorParameterDescription(_data.flags)), ("installedDate", ConstructorParameterDescription(_data.installedDate)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("title", ConstructorParameterDescription(_data.title)), ("shortName", ConstructorParameterDescription(_data.shortName)), ("thumbs", ConstructorParameterDescription(_data.thumbs)), ("thumbDcId", ConstructorParameterDescription(_data.thumbDcId)), ("thumbVersion", ConstructorParameterDescription(_data.thumbVersion)), ("thumbDocumentId", ConstructorParameterDescription(_data.thumbDocumentId)), ("count", ConstructorParameterDescription(_data.count)), ("hash", ConstructorParameterDescription(_data.hash))]) - } - } - - public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: [Api.PhotoSize]? - if Int(_1!) & Int(1 << 4) != 0 { - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) - } - } - var _8: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = reader.readInt32() - } - var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _9 = reader.readInt32() - } - var _10: Int64? - if Int(_1!) & Int(1 << 8) != 0 { - _10 = reader.readInt64() - } - var _11: Int32? - _11 = reader.readInt32() - var _12: Int32? - _12 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 8) == 0) || _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.StickerSet.stickerSet(Cons_stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!)) + return Api.StarGiftAttribute.starGiftAttributePattern(Cons_starGiftAttributePattern(name: _1!, document: _2!, rarity: _3!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index eb5b139182..ac9bb8ab2a 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1,677 +1,419 @@ public extension Api { - enum StickerSetCovered: TypeConstructorDescription { - public class Cons_stickerSetCovered: TypeConstructorDescription { - public var set: Api.StickerSet - public var cover: Api.Document - public init(set: Api.StickerSet, cover: Api.Document) { - self.set = set - self.cover = cover + enum StarGiftAttributeCounter: TypeConstructorDescription { + public class Cons_starGiftAttributeCounter: TypeConstructorDescription { + public var attribute: Api.StarGiftAttributeId + public var count: Int32 + public init(attribute: Api.StarGiftAttributeId, count: Int32) { + self.attribute = attribute + self.count = count } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSetCovered", [("set", ConstructorParameterDescription(self.set)), ("cover", ConstructorParameterDescription(self.cover))]) + return ("starGiftAttributeCounter", [("attribute", ConstructorParameterDescription(self.attribute)), ("count", ConstructorParameterDescription(self.count))]) } } - public class Cons_stickerSetFullCovered: TypeConstructorDescription { - public var set: Api.StickerSet - public var packs: [Api.StickerPack] - public var keywords: [Api.StickerKeyword] - public var documents: [Api.Document] - public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) { - self.set = set - self.packs = packs - self.keywords = keywords - self.documents = documents - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSetFullCovered", [("set", ConstructorParameterDescription(self.set)), ("packs", ConstructorParameterDescription(self.packs)), ("keywords", ConstructorParameterDescription(self.keywords)), ("documents", ConstructorParameterDescription(self.documents))]) - } - } - public class Cons_stickerSetMultiCovered: TypeConstructorDescription { - public var set: Api.StickerSet - public var covers: [Api.Document] - public init(set: Api.StickerSet, covers: [Api.Document]) { - self.set = set - self.covers = covers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSetMultiCovered", [("set", ConstructorParameterDescription(self.set)), ("covers", ConstructorParameterDescription(self.covers))]) - } - } - public class Cons_stickerSetNoCovered: TypeConstructorDescription { - public var set: Api.StickerSet - public init(set: Api.StickerSet) { - self.set = set - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSetNoCovered", [("set", ConstructorParameterDescription(self.set))]) - } - } - case stickerSetCovered(Cons_stickerSetCovered) - case stickerSetFullCovered(Cons_stickerSetFullCovered) - case stickerSetMultiCovered(Cons_stickerSetMultiCovered) - case stickerSetNoCovered(Cons_stickerSetNoCovered) + case starGiftAttributeCounter(Cons_starGiftAttributeCounter) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSetCovered(let _data): + case .starGiftAttributeCounter(let _data): if boxed { - buffer.appendInt32(1678812626) + buffer.appendInt32(783398488) } - _data.set.serialize(buffer, true) - _data.cover.serialize(buffer, true) - break - case .stickerSetFullCovered(let _data): - if boxed { - buffer.appendInt32(1087454222) - } - _data.set.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.packs.count)) - for item in _data.packs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.keywords.count)) - for item in _data.keywords { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { - item.serialize(buffer, true) - } - break - case .stickerSetMultiCovered(let _data): - if boxed { - buffer.appendInt32(872932635) - } - _data.set.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.covers.count)) - for item in _data.covers { - item.serialize(buffer, true) - } - break - case .stickerSetNoCovered(let _data): - if boxed { - buffer.appendInt32(2008112412) - } - _data.set.serialize(buffer, true) + _data.attribute.serialize(buffer, true) + serializeInt32(_data.count, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .stickerSetCovered(let _data): - return ("stickerSetCovered", [("set", ConstructorParameterDescription(_data.set)), ("cover", ConstructorParameterDescription(_data.cover))]) - case .stickerSetFullCovered(let _data): - return ("stickerSetFullCovered", [("set", ConstructorParameterDescription(_data.set)), ("packs", ConstructorParameterDescription(_data.packs)), ("keywords", ConstructorParameterDescription(_data.keywords)), ("documents", ConstructorParameterDescription(_data.documents))]) - case .stickerSetMultiCovered(let _data): - return ("stickerSetMultiCovered", [("set", ConstructorParameterDescription(_data.set)), ("covers", ConstructorParameterDescription(_data.covers))]) - case .stickerSetNoCovered(let _data): - return ("stickerSetNoCovered", [("set", ConstructorParameterDescription(_data.set))]) + case .starGiftAttributeCounter(let _data): + return ("starGiftAttributeCounter", [("attribute", ConstructorParameterDescription(_data.attribute)), ("count", ConstructorParameterDescription(_data.count))]) } } - public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? + public static func parse_starGiftAttributeCounter(_ reader: BufferReader) -> StarGiftAttributeCounter? { + var _1: Api.StarGiftAttributeId? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - var _2: Api.Document? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Document + _1 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeId } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!)) + return Api.StarGiftAttributeCounter.starGiftAttributeCounter(Cons_starGiftAttributeCounter(attribute: _1!, count: _2!)) } else { return nil } } - public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? + } +} +public extension Api { + enum StarGiftAttributeId: TypeConstructorDescription { + public class Cons_starGiftAttributeIdBackdrop: TypeConstructorDescription { + public var backdropId: Int32 + public init(backdropId: Int32) { + self.backdropId = backdropId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeIdBackdrop", [("backdropId", ConstructorParameterDescription(self.backdropId))]) + } + } + public class Cons_starGiftAttributeIdModel: TypeConstructorDescription { + public var documentId: Int64 + public init(documentId: Int64) { + self.documentId = documentId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeIdModel", [("documentId", ConstructorParameterDescription(self.documentId))]) + } + } + public class Cons_starGiftAttributeIdPattern: TypeConstructorDescription { + public var documentId: Int64 + public init(documentId: Int64) { + self.documentId = documentId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeIdPattern", [("documentId", ConstructorParameterDescription(self.documentId))]) + } + } + case starGiftAttributeIdBackdrop(Cons_starGiftAttributeIdBackdrop) + case starGiftAttributeIdModel(Cons_starGiftAttributeIdModel) + case starGiftAttributeIdPattern(Cons_starGiftAttributeIdPattern) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAttributeIdBackdrop(let _data): + if boxed { + buffer.appendInt32(520210263) + } + serializeInt32(_data.backdropId, buffer: buffer, boxed: false) + break + case .starGiftAttributeIdModel(let _data): + if boxed { + buffer.appendInt32(1219145276) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + break + case .starGiftAttributeIdPattern(let _data): + if boxed { + buffer.appendInt32(1242965043) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAttributeIdBackdrop(let _data): + return ("starGiftAttributeIdBackdrop", [("backdropId", ConstructorParameterDescription(_data.backdropId))]) + case .starGiftAttributeIdModel(let _data): + return ("starGiftAttributeIdModel", [("documentId", ConstructorParameterDescription(_data.documentId))]) + case .starGiftAttributeIdPattern(let _data): + return ("starGiftAttributeIdPattern", [("documentId", ConstructorParameterDescription(_data.documentId))]) + } + } + + public static func parse_starGiftAttributeIdBackdrop(_ reader: BufferReader) -> StarGiftAttributeId? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.StarGiftAttributeId.starGiftAttributeIdBackdrop(Cons_starGiftAttributeIdBackdrop(backdropId: _1!)) + } + else { + return nil + } + } + public static func parse_starGiftAttributeIdModel(_ reader: BufferReader) -> StarGiftAttributeId? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.StarGiftAttributeId.starGiftAttributeIdModel(Cons_starGiftAttributeIdModel(documentId: _1!)) + } + else { + return nil + } + } + public static func parse_starGiftAttributeIdPattern(_ reader: BufferReader) -> StarGiftAttributeId? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.StarGiftAttributeId.starGiftAttributeIdPattern(Cons_starGiftAttributeIdPattern(documentId: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarGiftAttributeRarity: TypeConstructorDescription { + public class Cons_starGiftAttributeRarity: TypeConstructorDescription { + public var permille: Int32 + public init(permille: Int32) { + self.permille = permille + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAttributeRarity", [("permille", ConstructorParameterDescription(self.permille))]) + } + } + case starGiftAttributeRarity(Cons_starGiftAttributeRarity) + case starGiftAttributeRarityEpic + case starGiftAttributeRarityLegendary + case starGiftAttributeRarityRare + case starGiftAttributeRarityUncommon + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAttributeRarity(let _data): + if boxed { + buffer.appendInt32(910391095) + } + serializeInt32(_data.permille, buffer: buffer, boxed: false) + break + case .starGiftAttributeRarityEpic: + if boxed { + buffer.appendInt32(2029777832) + } + break + case .starGiftAttributeRarityLegendary: + if boxed { + buffer.appendInt32(-822614104) + } + break + case .starGiftAttributeRarityRare: + if boxed { + buffer.appendInt32(-259174037) + } + break + case .starGiftAttributeRarityUncommon: + if boxed { + buffer.appendInt32(-607231095) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAttributeRarity(let _data): + return ("starGiftAttributeRarity", [("permille", ConstructorParameterDescription(_data.permille))]) + case .starGiftAttributeRarityEpic: + return ("starGiftAttributeRarityEpic", []) + case .starGiftAttributeRarityLegendary: + return ("starGiftAttributeRarityLegendary", []) + case .starGiftAttributeRarityRare: + return ("starGiftAttributeRarityRare", []) + case .starGiftAttributeRarityUncommon: + return ("starGiftAttributeRarityUncommon", []) + } + } + + public static func parse_starGiftAttributeRarity(_ reader: BufferReader) -> StarGiftAttributeRarity? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.StarGiftAttributeRarity.starGiftAttributeRarity(Cons_starGiftAttributeRarity(permille: _1!)) + } + else { + return nil + } + } + public static func parse_starGiftAttributeRarityEpic(_ reader: BufferReader) -> StarGiftAttributeRarity? { + return Api.StarGiftAttributeRarity.starGiftAttributeRarityEpic + } + public static func parse_starGiftAttributeRarityLegendary(_ reader: BufferReader) -> StarGiftAttributeRarity? { + return Api.StarGiftAttributeRarity.starGiftAttributeRarityLegendary + } + public static func parse_starGiftAttributeRarityRare(_ reader: BufferReader) -> StarGiftAttributeRarity? { + return Api.StarGiftAttributeRarity.starGiftAttributeRarityRare + } + public static func parse_starGiftAttributeRarityUncommon(_ reader: BufferReader) -> StarGiftAttributeRarity? { + return Api.StarGiftAttributeRarity.starGiftAttributeRarityUncommon + } + } +} +public extension Api { + enum StarGiftAuctionAcquiredGift: TypeConstructorDescription { + public class Cons_starGiftAuctionAcquiredGift: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var date: Int32 + public var bidAmount: Int64 + public var round: Int32 + public var pos: Int32 + public var message: Api.TextWithEntities? + public var giftNum: Int32? + public init(flags: Int32, peer: Api.Peer, date: Int32, bidAmount: Int64, round: Int32, pos: Int32, message: Api.TextWithEntities?, giftNum: Int32?) { + self.flags = flags + self.peer = peer + self.date = date + self.bidAmount = bidAmount + self.round = round + self.pos = pos + self.message = message + self.giftNum = giftNum + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAuctionAcquiredGift", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("bidAmount", ConstructorParameterDescription(self.bidAmount)), ("round", ConstructorParameterDescription(self.round)), ("pos", ConstructorParameterDescription(self.pos)), ("message", ConstructorParameterDescription(self.message)), ("giftNum", ConstructorParameterDescription(self.giftNum))]) + } + } + case starGiftAuctionAcquiredGift(Cons_starGiftAuctionAcquiredGift) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionAcquiredGift(let _data): + if boxed { + buffer.appendInt32(1118831432) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.bidAmount, buffer: buffer, boxed: false) + serializeInt32(_data.round, buffer: buffer, boxed: false) + serializeInt32(_data.pos, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.message!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.giftNum!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAuctionAcquiredGift(let _data): + return ("starGiftAuctionAcquiredGift", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("bidAmount", ConstructorParameterDescription(_data.bidAmount)), ("round", ConstructorParameterDescription(_data.round)), ("pos", ConstructorParameterDescription(_data.pos)), ("message", ConstructorParameterDescription(_data.message)), ("giftNum", ConstructorParameterDescription(_data.giftNum))]) + } + } + + public static func parse_starGiftAuctionAcquiredGift(_ reader: BufferReader) -> StarGiftAuctionAcquiredGift? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } - var _3: [Api.StickerKeyword]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) - } - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _8 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!)) + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.StarGiftAuctionAcquiredGift.starGiftAuctionAcquiredGift(Cons_starGiftAuctionAcquiredGift(flags: _1!, peer: _2!, date: _3!, bidAmount: _4!, round: _5!, pos: _6!, message: _7, giftNum: _8)) } else { return nil } } - public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } +} +public extension Api { + enum StarGiftAuctionRound: TypeConstructorDescription { + public class Cons_starGiftAuctionRound: TypeConstructorDescription { + public var num: Int32 + public var duration: Int32 + public init(num: Int32, duration: Int32) { + self.num = num + self.duration = duration } - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAuctionRound", [("num", ConstructorParameterDescription(self.num)), ("duration", ConstructorParameterDescription(self.duration))]) } + } + public class Cons_starGiftAuctionRoundExtendable: TypeConstructorDescription { + public var num: Int32 + public var duration: Int32 + public var extendTop: Int32 + public var extendWindow: Int32 + public init(num: Int32, duration: Int32, extendTop: Int32, extendWindow: Int32) { + self.num = num + self.duration = duration + self.extendTop = extendTop + self.extendWindow = extendWindow + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAuctionRoundExtendable", [("num", ConstructorParameterDescription(self.num)), ("duration", ConstructorParameterDescription(self.duration)), ("extendTop", ConstructorParameterDescription(self.extendTop)), ("extendWindow", ConstructorParameterDescription(self.extendWindow))]) + } + } + case starGiftAuctionRound(Cons_starGiftAuctionRound) + case starGiftAuctionRoundExtendable(Cons_starGiftAuctionRoundExtendable) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionRound(let _data): + if boxed { + buffer.appendInt32(984483112) + } + serializeInt32(_data.num, buffer: buffer, boxed: false) + serializeInt32(_data.duration, buffer: buffer, boxed: false) + break + case .starGiftAuctionRoundExtendable(let _data): + if boxed { + buffer.appendInt32(178266597) + } + serializeInt32(_data.num, buffer: buffer, boxed: false) + serializeInt32(_data.duration, buffer: buffer, boxed: false) + serializeInt32(_data.extendTop, buffer: buffer, boxed: false) + serializeInt32(_data.extendWindow, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAuctionRound(let _data): + return ("starGiftAuctionRound", [("num", ConstructorParameterDescription(_data.num)), ("duration", ConstructorParameterDescription(_data.duration))]) + case .starGiftAuctionRoundExtendable(let _data): + return ("starGiftAuctionRoundExtendable", [("num", ConstructorParameterDescription(_data.num)), ("duration", ConstructorParameterDescription(_data.duration)), ("extendTop", ConstructorParameterDescription(_data.extendTop)), ("extendWindow", ConstructorParameterDescription(_data.extendWindow))]) + } + } + + public static func parse_starGiftAuctionRound(_ reader: BufferReader) -> StarGiftAuctionRound? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!)) + return Api.StarGiftAuctionRound.starGiftAuctionRound(Cons_starGiftAuctionRound(num: _1!, duration: _2!)) } else { return nil } } - public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } - let _c1 = _1 != nil - if _c1 { - return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StoriesStealthMode: TypeConstructorDescription { - public class Cons_storiesStealthMode: TypeConstructorDescription { - public var flags: Int32 - public var activeUntilDate: Int32? - public var cooldownUntilDate: Int32? - public init(flags: Int32, activeUntilDate: Int32?, cooldownUntilDate: Int32?) { - self.flags = flags - self.activeUntilDate = activeUntilDate - self.cooldownUntilDate = cooldownUntilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storiesStealthMode", [("flags", ConstructorParameterDescription(self.flags)), ("activeUntilDate", ConstructorParameterDescription(self.activeUntilDate)), ("cooldownUntilDate", ConstructorParameterDescription(self.cooldownUntilDate))]) - } - } - case storiesStealthMode(Cons_storiesStealthMode) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storiesStealthMode(let _data): - if boxed { - buffer.appendInt32(1898850301) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.activeUntilDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.cooldownUntilDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storiesStealthMode(let _data): - return ("storiesStealthMode", [("flags", ConstructorParameterDescription(_data.flags)), ("activeUntilDate", ConstructorParameterDescription(_data.activeUntilDate)), ("cooldownUntilDate", ConstructorParameterDescription(_data.cooldownUntilDate))]) - } - } - - public static func parse_storiesStealthMode(_ reader: BufferReader) -> StoriesStealthMode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoriesStealthMode.storiesStealthMode(Cons_storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StoryAlbum: TypeConstructorDescription { - public class Cons_storyAlbum: TypeConstructorDescription { - public var flags: Int32 - public var albumId: Int32 - public var title: String - public var iconPhoto: Api.Photo? - public var iconVideo: Api.Document? - public init(flags: Int32, albumId: Int32, title: String, iconPhoto: Api.Photo?, iconVideo: Api.Document?) { - self.flags = flags - self.albumId = albumId - self.title = title - self.iconPhoto = iconPhoto - self.iconVideo = iconVideo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyAlbum", [("flags", ConstructorParameterDescription(self.flags)), ("albumId", ConstructorParameterDescription(self.albumId)), ("title", ConstructorParameterDescription(self.title)), ("iconPhoto", ConstructorParameterDescription(self.iconPhoto)), ("iconVideo", ConstructorParameterDescription(self.iconVideo))]) - } - } - case storyAlbum(Cons_storyAlbum) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyAlbum(let _data): - if boxed { - buffer.appendInt32(-1826262950) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.albumId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.iconPhoto!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.iconVideo!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyAlbum(let _data): - return ("storyAlbum", [("flags", ConstructorParameterDescription(_data.flags)), ("albumId", ConstructorParameterDescription(_data.albumId)), ("title", ConstructorParameterDescription(_data.title)), ("iconPhoto", ConstructorParameterDescription(_data.iconPhoto)), ("iconVideo", ConstructorParameterDescription(_data.iconVideo))]) - } - } - - public static func parse_storyAlbum(_ reader: BufferReader) -> StoryAlbum? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: Api.Photo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _5: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Document - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.StoryAlbum.storyAlbum(Cons_storyAlbum(flags: _1!, albumId: _2!, title: _3!, iconPhoto: _4, iconVideo: _5)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StoryFwdHeader: TypeConstructorDescription { - public class Cons_storyFwdHeader: TypeConstructorDescription { - public var flags: Int32 - public var from: Api.Peer? - public var fromName: String? - public var storyId: Int32? - public init(flags: Int32, from: Api.Peer?, fromName: String?, storyId: Int32?) { - self.flags = flags - self.from = from - self.fromName = fromName - self.storyId = storyId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyFwdHeader", [("flags", ConstructorParameterDescription(self.flags)), ("from", ConstructorParameterDescription(self.from)), ("fromName", ConstructorParameterDescription(self.fromName)), ("storyId", ConstructorParameterDescription(self.storyId))]) - } - } - case storyFwdHeader(Cons_storyFwdHeader) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyFwdHeader(let _data): - if boxed { - buffer.appendInt32(-1205411504) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.from!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.fromName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.storyId!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyFwdHeader(let _data): - return ("storyFwdHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("from", ConstructorParameterDescription(_data.from)), ("fromName", ConstructorParameterDescription(_data.fromName)), ("storyId", ConstructorParameterDescription(_data.storyId))]) - } - } - - public static func parse_storyFwdHeader(_ reader: BufferReader) -> StoryFwdHeader? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = parseString(reader) - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryFwdHeader.storyFwdHeader(Cons_storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum StoryItem: TypeConstructorDescription { - public class Cons_storyItem: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var date: Int32 - public var fromId: Api.Peer? - public var fwdFrom: Api.StoryFwdHeader? - public var expireDate: Int32 - public var caption: String? - public var entities: [Api.MessageEntity]? - public var media: Api.MessageMedia - public var mediaAreas: [Api.MediaArea]? - public var privacy: [Api.PrivacyRule]? - public var views: Api.StoryViews? - public var sentReaction: Api.Reaction? - public var albums: [Int32]? - public var music: Api.Document? - public init(flags: Int32, id: Int32, date: Int32, fromId: Api.Peer?, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?, albums: [Int32]?, music: Api.Document?) { - self.flags = flags - self.id = id - self.date = date - self.fromId = fromId - self.fwdFrom = fwdFrom - self.expireDate = expireDate - self.caption = caption - self.entities = entities - self.media = media - self.mediaAreas = mediaAreas - self.privacy = privacy - self.views = views - self.sentReaction = sentReaction - self.albums = albums - self.music = music - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyItem", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("date", ConstructorParameterDescription(self.date)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("expireDate", ConstructorParameterDescription(self.expireDate)), ("caption", ConstructorParameterDescription(self.caption)), ("entities", ConstructorParameterDescription(self.entities)), ("media", ConstructorParameterDescription(self.media)), ("mediaAreas", ConstructorParameterDescription(self.mediaAreas)), ("privacy", ConstructorParameterDescription(self.privacy)), ("views", ConstructorParameterDescription(self.views)), ("sentReaction", ConstructorParameterDescription(self.sentReaction)), ("albums", ConstructorParameterDescription(self.albums)), ("music", ConstructorParameterDescription(self.music))]) - } - } - public class Cons_storyItemDeleted: TypeConstructorDescription { - public var id: Int32 - public init(id: Int32) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyItemDeleted", [("id", ConstructorParameterDescription(self.id))]) - } - } - public class Cons_storyItemSkipped: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var date: Int32 - public var expireDate: Int32 - public init(flags: Int32, id: Int32, date: Int32, expireDate: Int32) { - self.flags = flags - self.id = id - self.date = date - self.expireDate = expireDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyItemSkipped", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("date", ConstructorParameterDescription(self.date)), ("expireDate", ConstructorParameterDescription(self.expireDate))]) - } - } - case storyItem(Cons_storyItem) - case storyItemDeleted(Cons_storyItemDeleted) - case storyItemSkipped(Cons_storyItemSkipped) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyItem(let _data): - if boxed { - buffer.appendInt32(379894076) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 18) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - _data.fwdFrom!.serialize(buffer, true) - } - serializeInt32(_data.expireDate, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.caption!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - _data.media.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 14) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.mediaAreas!.count)) - for item in _data.mediaAreas! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.privacy!.count)) - for item in _data.privacy! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.views!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - _data.sentReaction!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 19) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.albums!.count)) - for item in _data.albums! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 20) != 0 { - _data.music!.serialize(buffer, true) - } - break - case .storyItemDeleted(let _data): - if boxed { - buffer.appendInt32(1374088783) - } - serializeInt32(_data.id, buffer: buffer, boxed: false) - break - case .storyItemSkipped(let _data): - if boxed { - buffer.appendInt32(-5388013) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.expireDate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyItem(let _data): - return ("storyItem", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("date", ConstructorParameterDescription(_data.date)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("expireDate", ConstructorParameterDescription(_data.expireDate)), ("caption", ConstructorParameterDescription(_data.caption)), ("entities", ConstructorParameterDescription(_data.entities)), ("media", ConstructorParameterDescription(_data.media)), ("mediaAreas", ConstructorParameterDescription(_data.mediaAreas)), ("privacy", ConstructorParameterDescription(_data.privacy)), ("views", ConstructorParameterDescription(_data.views)), ("sentReaction", ConstructorParameterDescription(_data.sentReaction)), ("albums", ConstructorParameterDescription(_data.albums)), ("music", ConstructorParameterDescription(_data.music))]) - case .storyItemDeleted(let _data): - return ("storyItemDeleted", [("id", ConstructorParameterDescription(_data.id))]) - case .storyItemSkipped(let _data): - return ("storyItemSkipped", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("date", ConstructorParameterDescription(_data.date)), ("expireDate", ConstructorParameterDescription(_data.expireDate))]) - } - } - - public static func parse_storyItem(_ reader: BufferReader) -> StoryItem? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if Int(_1!) & Int(1 << 18) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _5: Api.StoryFwdHeader? - if Int(_1!) & Int(1 << 17) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader - } - } - var _6: Int32? - _6 = reader.readInt32() - var _7: String? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = parseString(reader) - } - var _8: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _9: Api.MessageMedia? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - var _10: [Api.MediaArea]? - if Int(_1!) & Int(1 << 14) != 0 { - if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) - } - } - var _11: [Api.PrivacyRule]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) - } - } - var _12: Api.StoryViews? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StoryViews - } - } - var _13: Api.Reaction? - if Int(_1!) & Int(1 << 15) != 0 { - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.Reaction - } - } - var _14: [Int32]? - if Int(_1!) & Int(1 << 19) != 0 { - if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - var _15: Api.Document? - if Int(_1!) & Int(1 << 20) != 0 { - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.Document - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 18) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 17) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 2) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 19) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 20) == 0) || _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.StoryItem.storyItem(Cons_storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13, albums: _14, music: _15)) - } - else { - return nil - } - } - public static func parse_storyItemDeleted(_ reader: BufferReader) -> StoryItem? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.StoryItem.storyItemDeleted(Cons_storyItemDeleted(id: _1!)) - } - else { - return nil - } - } - public static func parse_storyItemSkipped(_ reader: BufferReader) -> StoryItem? { + public static func parse_starGiftAuctionRoundExtendable(_ reader: BufferReader) -> StarGiftAuctionRound? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -685,7 +427,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.StoryItem.storyItemSkipped(Cons_storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!)) + return Api.StarGiftAuctionRound.starGiftAuctionRoundExtendable(Cons_starGiftAuctionRoundExtendable(num: _1!, duration: _2!, extendTop: _3!, extendWindow: _4!)) } else { return nil @@ -694,564 +436,115 @@ public extension Api { } } public extension Api { - indirect enum StoryReaction: TypeConstructorDescription { - public class Cons_storyReaction: TypeConstructorDescription { - public var peerId: Api.Peer - public var date: Int32 - public var reaction: Api.Reaction - public init(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) { - self.peerId = peerId - self.date = date - self.reaction = reaction + enum StarGiftAuctionState: TypeConstructorDescription { + public class Cons_starGiftAuctionState: TypeConstructorDescription { + public var version: Int32 + public var startDate: Int32 + public var endDate: Int32 + public var minBidAmount: Int64 + public var bidLevels: [Api.AuctionBidLevel] + public var topBidders: [Int64] + public var nextRoundAt: Int32 + public var lastGiftNum: Int32 + public var giftsLeft: Int32 + public var currentRound: Int32 + public var totalRounds: Int32 + public var rounds: [Api.StarGiftAuctionRound] + public init(version: Int32, startDate: Int32, endDate: Int32, minBidAmount: Int64, bidLevels: [Api.AuctionBidLevel], topBidders: [Int64], nextRoundAt: Int32, lastGiftNum: Int32, giftsLeft: Int32, currentRound: Int32, totalRounds: Int32, rounds: [Api.StarGiftAuctionRound]) { + self.version = version + self.startDate = startDate + self.endDate = endDate + self.minBidAmount = minBidAmount + self.bidLevels = bidLevels + self.topBidders = topBidders + self.nextRoundAt = nextRoundAt + self.lastGiftNum = lastGiftNum + self.giftsLeft = giftsLeft + self.currentRound = currentRound + self.totalRounds = totalRounds + self.rounds = rounds } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyReaction", [("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) + return ("starGiftAuctionState", [("version", ConstructorParameterDescription(self.version)), ("startDate", ConstructorParameterDescription(self.startDate)), ("endDate", ConstructorParameterDescription(self.endDate)), ("minBidAmount", ConstructorParameterDescription(self.minBidAmount)), ("bidLevels", ConstructorParameterDescription(self.bidLevels)), ("topBidders", ConstructorParameterDescription(self.topBidders)), ("nextRoundAt", ConstructorParameterDescription(self.nextRoundAt)), ("lastGiftNum", ConstructorParameterDescription(self.lastGiftNum)), ("giftsLeft", ConstructorParameterDescription(self.giftsLeft)), ("currentRound", ConstructorParameterDescription(self.currentRound)), ("totalRounds", ConstructorParameterDescription(self.totalRounds)), ("rounds", ConstructorParameterDescription(self.rounds))]) } } - public class Cons_storyReactionPublicForward: TypeConstructorDescription { - public var message: Api.Message - public init(message: Api.Message) { - self.message = message + public class Cons_starGiftAuctionStateFinished: TypeConstructorDescription { + public var flags: Int32 + public var startDate: Int32 + public var endDate: Int32 + public var averagePrice: Int64 + public var listedCount: Int32? + public var fragmentListedCount: Int32? + public var fragmentListedUrl: String? + public init(flags: Int32, startDate: Int32, endDate: Int32, averagePrice: Int64, listedCount: Int32?, fragmentListedCount: Int32?, fragmentListedUrl: String?) { + self.flags = flags + self.startDate = startDate + self.endDate = endDate + self.averagePrice = averagePrice + self.listedCount = listedCount + self.fragmentListedCount = fragmentListedCount + self.fragmentListedUrl = fragmentListedUrl } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyReactionPublicForward", [("message", ConstructorParameterDescription(self.message))]) + return ("starGiftAuctionStateFinished", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("endDate", ConstructorParameterDescription(self.endDate)), ("averagePrice", ConstructorParameterDescription(self.averagePrice)), ("listedCount", ConstructorParameterDescription(self.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(self.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(self.fragmentListedUrl))]) } } - public class Cons_storyReactionPublicRepost: TypeConstructorDescription { - public var peerId: Api.Peer - public var story: Api.StoryItem - public init(peerId: Api.Peer, story: Api.StoryItem) { - self.peerId = peerId - self.story = story - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyReactionPublicRepost", [("peerId", ConstructorParameterDescription(self.peerId)), ("story", ConstructorParameterDescription(self.story))]) - } - } - case storyReaction(Cons_storyReaction) - case storyReactionPublicForward(Cons_storyReactionPublicForward) - case storyReactionPublicRepost(Cons_storyReactionPublicRepost) + case starGiftAuctionState(Cons_starGiftAuctionState) + case starGiftAuctionStateFinished(Cons_starGiftAuctionStateFinished) + case starGiftAuctionStateNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyReaction(let _data): + case .starGiftAuctionState(let _data): if boxed { - buffer.appendInt32(1620104917) + buffer.appendInt32(1998212710) } - _data.peerId.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.reaction.serialize(buffer, true) - break - case .storyReactionPublicForward(let _data): - if boxed { - buffer.appendInt32(-1146411453) - } - _data.message.serialize(buffer, true) - break - case .storyReactionPublicRepost(let _data): - if boxed { - buffer.appendInt32(-808644845) - } - _data.peerId.serialize(buffer, true) - _data.story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyReaction(let _data): - return ("storyReaction", [("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) - case .storyReactionPublicForward(let _data): - return ("storyReactionPublicForward", [("message", ConstructorParameterDescription(_data.message))]) - case .storyReactionPublicRepost(let _data): - return ("storyReactionPublicRepost", [("peerId", ConstructorParameterDescription(_data.peerId)), ("story", ConstructorParameterDescription(_data.story))]) - } - } - - public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryReaction.storyReaction(Cons_storyReaction(peerId: _1!, date: _2!, reaction: _3!)) - } - else { - return nil - } - } - public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.StoryReaction.storyReactionPublicForward(Cons_storyReactionPublicForward(message: _1!)) - } - else { - return nil - } - } - public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.StoryItem? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryReaction.storyReactionPublicRepost(Cons_storyReactionPublicRepost(peerId: _1!, story: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - indirect enum StoryView: TypeConstructorDescription { - public class Cons_storyView: TypeConstructorDescription { - public var flags: Int32 - public var userId: Int64 - public var date: Int32 - public var reaction: Api.Reaction? - public init(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) { - self.flags = flags - self.userId = userId - self.date = date - self.reaction = reaction - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyView", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) - } - } - public class Cons_storyViewPublicForward: TypeConstructorDescription { - public var flags: Int32 - public var message: Api.Message - public init(flags: Int32, message: Api.Message) { - self.flags = flags - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyViewPublicForward", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_storyViewPublicRepost: TypeConstructorDescription { - public var flags: Int32 - public var peerId: Api.Peer - public var story: Api.StoryItem - public init(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) { - self.flags = flags - self.peerId = peerId - self.story = story - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyViewPublicRepost", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("story", ConstructorParameterDescription(self.story))]) - } - } - case storyView(Cons_storyView) - case storyViewPublicForward(Cons_storyViewPublicForward) - case storyViewPublicRepost(Cons_storyViewPublicRepost) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyView(let _data): - if boxed { - buffer.appendInt32(-1329730875) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.reaction!.serialize(buffer, true) - } - break - case .storyViewPublicForward(let _data): - if boxed { - buffer.appendInt32(-1870436597) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.message.serialize(buffer, true) - break - case .storyViewPublicRepost(let _data): - if boxed { - buffer.appendInt32(-1116418231) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peerId.serialize(buffer, true) - _data.story.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyView(let _data): - return ("storyView", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) - case .storyViewPublicForward(let _data): - return ("storyViewPublicForward", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message))]) - case .storyViewPublicRepost(let _data): - return ("storyViewPublicRepost", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("story", ConstructorParameterDescription(_data.story))]) - } - } - - public static func parse_storyView(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Reaction? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Reaction - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.StoryView.storyView(Cons_storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4)) - } - else { - return nil - } - } - public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Message? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.StoryView.storyViewPublicForward(Cons_storyViewPublicForward(flags: _1!, message: _2!)) - } - else { - return nil - } - } - public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.StoryItem? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.StoryView.storyViewPublicRepost(Cons_storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum StoryViews: TypeConstructorDescription { - public class Cons_storyViews: TypeConstructorDescription { - public var flags: Int32 - public var viewsCount: Int32 - public var forwardsCount: Int32? - public var reactions: [Api.ReactionCount]? - public var reactionsCount: Int32? - public var recentViewers: [Int64]? - public init(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) { - self.flags = flags - self.viewsCount = viewsCount - self.forwardsCount = forwardsCount - self.reactions = reactions - self.reactionsCount = reactionsCount - self.recentViewers = recentViewers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyViews", [("flags", ConstructorParameterDescription(self.flags)), ("viewsCount", ConstructorParameterDescription(self.viewsCount)), ("forwardsCount", ConstructorParameterDescription(self.forwardsCount)), ("reactions", ConstructorParameterDescription(self.reactions)), ("reactionsCount", ConstructorParameterDescription(self.reactionsCount)), ("recentViewers", ConstructorParameterDescription(self.recentViewers))]) - } - } - case storyViews(Cons_storyViews) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyViews(let _data): - if boxed { - buffer.appendInt32(-1923523370) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.viewsCount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.forwardsCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.reactions!.count)) - for item in _data.reactions! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.reactionsCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentViewers!.count)) - for item in _data.recentViewers! { - serializeInt64(item, buffer: buffer, boxed: false) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyViews(let _data): - return ("storyViews", [("flags", ConstructorParameterDescription(_data.flags)), ("viewsCount", ConstructorParameterDescription(_data.viewsCount)), ("forwardsCount", ConstructorParameterDescription(_data.forwardsCount)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("reactionsCount", ConstructorParameterDescription(_data.reactionsCount)), ("recentViewers", ConstructorParameterDescription(_data.recentViewers))]) - } - } - - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.ReactionCount]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = reader.readInt32() - } - var _6: [Int64]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.StoryViews.storyViews(Cons_storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6)) - } - else { - return nil - } - } - } -} -public extension Api { - enum SuggestedPost: TypeConstructorDescription { - public class Cons_suggestedPost: TypeConstructorDescription { - public var flags: Int32 - public var price: Api.StarsAmount? - public var scheduleDate: Int32? - public init(flags: Int32, price: Api.StarsAmount?, scheduleDate: Int32?) { - self.flags = flags - self.price = price - self.scheduleDate = scheduleDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("suggestedPost", [("flags", ConstructorParameterDescription(self.flags)), ("price", ConstructorParameterDescription(self.price)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate))]) - } - } - case suggestedPost(Cons_suggestedPost) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .suggestedPost(let _data): - if boxed { - buffer.appendInt32(244201445) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.price!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.scheduleDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .suggestedPost(let _data): - return ("suggestedPost", [("flags", ConstructorParameterDescription(_data.flags)), ("price", ConstructorParameterDescription(_data.price)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate))]) - } - } - - public static func parse_suggestedPost(_ reader: BufferReader) -> SuggestedPost? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarsAmount? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.SuggestedPost.suggestedPost(Cons_suggestedPost(flags: _1!, price: _2, scheduleDate: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum TextWithEntities: TypeConstructorDescription { - public class Cons_textWithEntities: TypeConstructorDescription { - public var text: String - public var entities: [Api.MessageEntity] - public init(text: String, entities: [Api.MessageEntity]) { - self.text = text - self.entities = entities - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("textWithEntities", [("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities))]) - } - } - case textWithEntities(Cons_textWithEntities) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .textWithEntities(let _data): - if boxed { - buffer.appendInt32(1964978502) - } - serializeString(_data.text, buffer: buffer, boxed: false) + serializeInt32(_data.version, buffer: buffer, boxed: false) + serializeInt32(_data.startDate, buffer: buffer, boxed: false) + serializeInt32(_data.endDate, buffer: buffer, boxed: false) + serializeInt64(_data.minBidAmount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities.count)) - for item in _data.entities { + buffer.appendInt32(Int32(_data.bidLevels.count)) + for item in _data.bidLevels { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topBidders.count)) + for item in _data.topBidders { + serializeInt64(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.nextRoundAt, buffer: buffer, boxed: false) + serializeInt32(_data.lastGiftNum, buffer: buffer, boxed: false) + serializeInt32(_data.giftsLeft, buffer: buffer, boxed: false) + serializeInt32(_data.currentRound, buffer: buffer, boxed: false) + serializeInt32(_data.totalRounds, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.rounds.count)) + for item in _data.rounds { item.serialize(buffer, true) } break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .textWithEntities(let _data): - return ("textWithEntities", [("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities))]) - } - } - - public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TextWithEntities.textWithEntities(Cons_textWithEntities(text: _1!, entities: _2!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum Theme: TypeConstructorDescription { - public class Cons_theme: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var accessHash: Int64 - public var slug: String - public var title: String - public var document: Api.Document? - public var settings: [Api.ThemeSettings]? - public var emoticon: String? - public var installsCount: Int32? - public init(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) { - self.flags = flags - self.id = id - self.accessHash = accessHash - self.slug = slug - self.title = title - self.document = document - self.settings = settings - self.emoticon = emoticon - self.installsCount = installsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("theme", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("slug", ConstructorParameterDescription(self.slug)), ("title", ConstructorParameterDescription(self.title)), ("document", ConstructorParameterDescription(self.document)), ("settings", ConstructorParameterDescription(self.settings)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("installsCount", ConstructorParameterDescription(self.installsCount))]) - } - } - case theme(Cons_theme) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .theme(let _data): + case .starGiftAuctionStateFinished(let _data): if boxed { - buffer.appendInt32(-1609668650) + buffer.appendInt32(-1758614593) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeString(_data.slug, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.document!.serialize(buffer, true) + serializeInt32(_data.startDate, buffer: buffer, boxed: false) + serializeInt32(_data.endDate, buffer: buffer, boxed: false) + serializeInt64(_data.averagePrice, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.listedCount!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.settings!.count)) - for item in _data.settings! { - item.serialize(buffer, true) - } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.fragmentListedCount!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeString(_data.emoticon!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.fragmentListedUrl!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.installsCount!, buffer: buffer, boxed: false) + break + case .starGiftAuctionStateNotModified: + if boxed { + buffer.appendInt32(-30197422) } break } @@ -1259,149 +552,190 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .theme(let _data): - return ("theme", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("slug", ConstructorParameterDescription(_data.slug)), ("title", ConstructorParameterDescription(_data.title)), ("document", ConstructorParameterDescription(_data.document)), ("settings", ConstructorParameterDescription(_data.settings)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("installsCount", ConstructorParameterDescription(_data.installsCount))]) + case .starGiftAuctionState(let _data): + return ("starGiftAuctionState", [("version", ConstructorParameterDescription(_data.version)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("minBidAmount", ConstructorParameterDescription(_data.minBidAmount)), ("bidLevels", ConstructorParameterDescription(_data.bidLevels)), ("topBidders", ConstructorParameterDescription(_data.topBidders)), ("nextRoundAt", ConstructorParameterDescription(_data.nextRoundAt)), ("lastGiftNum", ConstructorParameterDescription(_data.lastGiftNum)), ("giftsLeft", ConstructorParameterDescription(_data.giftsLeft)), ("currentRound", ConstructorParameterDescription(_data.currentRound)), ("totalRounds", ConstructorParameterDescription(_data.totalRounds)), ("rounds", ConstructorParameterDescription(_data.rounds))]) + case .starGiftAuctionStateFinished(let _data): + return ("starGiftAuctionStateFinished", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("averagePrice", ConstructorParameterDescription(_data.averagePrice)), ("listedCount", ConstructorParameterDescription(_data.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(_data.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(_data.fragmentListedUrl))]) + case .starGiftAuctionStateNotModified: + return ("starGiftAuctionStateNotModified", []) } } - public static func parse_theme(_ reader: BufferReader) -> Theme? { + public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.Document? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Document - } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: [Api.AuctionBidLevel]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AuctionBidLevel.self) } - var _7: [Api.ThemeSettings]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) - } - } - var _8: String? - if Int(_1!) & Int(1 << 6) != 0 { - _8 = parseString(reader) + var _6: [Int64]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() var _9: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _9 = reader.readInt32() + _9 = reader.readInt32() + var _10: Int32? + _10 = reader.readInt32() + var _11: Int32? + _11 = reader.readInt32() + var _12: [Api.StarGiftAuctionRound]? + if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAuctionRound.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Theme.theme(Cons_theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9)) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.StarGiftAuctionState.starGiftAuctionState(Cons_starGiftAuctionState(version: _1!, startDate: _2!, endDate: _3!, minBidAmount: _4!, bidLevels: _5!, topBidders: _6!, nextRoundAt: _7!, lastGiftNum: _8!, giftsLeft: _9!, currentRound: _10!, totalRounds: _11!, rounds: _12!)) } else { return nil } } + public static func parse_starGiftAuctionStateFinished(_ reader: BufferReader) -> StarGiftAuctionState? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.StarGiftAuctionState.starGiftAuctionStateFinished(Cons_starGiftAuctionStateFinished(flags: _1!, startDate: _2!, endDate: _3!, averagePrice: _4!, listedCount: _5, fragmentListedCount: _6, fragmentListedUrl: _7)) + } + else { + return nil + } + } + public static func parse_starGiftAuctionStateNotModified(_ reader: BufferReader) -> StarGiftAuctionState? { + return Api.StarGiftAuctionState.starGiftAuctionStateNotModified + } } } public extension Api { - enum ThemeSettings: TypeConstructorDescription { - public class Cons_themeSettings: TypeConstructorDescription { + enum StarGiftAuctionUserState: TypeConstructorDescription { + public class Cons_starGiftAuctionUserState: TypeConstructorDescription { public var flags: Int32 - public var baseTheme: Api.BaseTheme - public var accentColor: Int32 - public var outboxAccentColor: Int32? - public var messageColors: [Int32]? - public var wallpaper: Api.WallPaper? - public init(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) { + public var bidAmount: Int64? + public var bidDate: Int32? + public var minBidAmount: Int64? + public var bidPeer: Api.Peer? + public var acquiredCount: Int32 + public init(flags: Int32, bidAmount: Int64?, bidDate: Int32?, minBidAmount: Int64?, bidPeer: Api.Peer?, acquiredCount: Int32) { self.flags = flags - self.baseTheme = baseTheme - self.accentColor = accentColor - self.outboxAccentColor = outboxAccentColor - self.messageColors = messageColors - self.wallpaper = wallpaper + self.bidAmount = bidAmount + self.bidDate = bidDate + self.minBidAmount = minBidAmount + self.bidPeer = bidPeer + self.acquiredCount = acquiredCount } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("themeSettings", [("flags", ConstructorParameterDescription(self.flags)), ("baseTheme", ConstructorParameterDescription(self.baseTheme)), ("accentColor", ConstructorParameterDescription(self.accentColor)), ("outboxAccentColor", ConstructorParameterDescription(self.outboxAccentColor)), ("messageColors", ConstructorParameterDescription(self.messageColors)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) + return ("starGiftAuctionUserState", [("flags", ConstructorParameterDescription(self.flags)), ("bidAmount", ConstructorParameterDescription(self.bidAmount)), ("bidDate", ConstructorParameterDescription(self.bidDate)), ("minBidAmount", ConstructorParameterDescription(self.minBidAmount)), ("bidPeer", ConstructorParameterDescription(self.bidPeer)), ("acquiredCount", ConstructorParameterDescription(self.acquiredCount))]) } } - case themeSettings(Cons_themeSettings) + case starGiftAuctionUserState(Cons_starGiftAuctionUserState) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .themeSettings(let _data): + case .starGiftAuctionUserState(let _data): if boxed { - buffer.appendInt32(-94849324) + buffer.appendInt32(787403204) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.baseTheme.serialize(buffer, true) - serializeInt32(_data.accentColor, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.outboxAccentColor!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.bidAmount!, buffer: buffer, boxed: false) } if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messageColors!.count)) - for item in _data.messageColors! { - serializeInt32(item, buffer: buffer, boxed: false) - } + serializeInt32(_data.bidDate!, buffer: buffer, boxed: false) } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.wallpaper!.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.minBidAmount!, buffer: buffer, boxed: false) } + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.bidPeer!.serialize(buffer, true) + } + serializeInt32(_data.acquiredCount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .themeSettings(let _data): - return ("themeSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("baseTheme", ConstructorParameterDescription(_data.baseTheme)), ("accentColor", ConstructorParameterDescription(_data.accentColor)), ("outboxAccentColor", ConstructorParameterDescription(_data.outboxAccentColor)), ("messageColors", ConstructorParameterDescription(_data.messageColors)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) + case .starGiftAuctionUserState(let _data): + return ("starGiftAuctionUserState", [("flags", ConstructorParameterDescription(_data.flags)), ("bidAmount", ConstructorParameterDescription(_data.bidAmount)), ("bidDate", ConstructorParameterDescription(_data.bidDate)), ("minBidAmount", ConstructorParameterDescription(_data.minBidAmount)), ("bidPeer", ConstructorParameterDescription(_data.bidPeer)), ("acquiredCount", ConstructorParameterDescription(_data.acquiredCount))]) } } - public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { + public static func parse_starGiftAuctionUserState(_ reader: BufferReader) -> StarGiftAuctionUserState? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.BaseTheme? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme + var _2: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt64() } var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _4 = reader.readInt32() + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() } - var _5: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt64() } - var _6: Api.WallPaper? - if Int(_1!) & Int(1 << 1) != 0 { + var _5: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaper + _5 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _6: Int32? + _6 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.ThemeSettings.themeSettings(Cons_themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6)) + return Api.StarGiftAuctionUserState.starGiftAuctionUserState(Cons_starGiftAuctionUserState(flags: _1!, bidAmount: _2, bidDate: _3, minBidAmount: _4, bidPeer: _5, acquiredCount: _6!)) } else { return nil @@ -1410,112 +744,54 @@ public extension Api { } } public extension Api { - enum Timezone: TypeConstructorDescription { - public class Cons_timezone: TypeConstructorDescription { - public var id: String - public var name: String - public var utcOffset: Int32 - public init(id: String, name: String, utcOffset: Int32) { - self.id = id - self.name = name - self.utcOffset = utcOffset + enum StarGiftBackground: TypeConstructorDescription { + public class Cons_starGiftBackground: TypeConstructorDescription { + public var centerColor: Int32 + public var edgeColor: Int32 + public var textColor: Int32 + public init(centerColor: Int32, edgeColor: Int32, textColor: Int32) { + self.centerColor = centerColor + self.edgeColor = edgeColor + self.textColor = textColor } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("timezone", [("id", ConstructorParameterDescription(self.id)), ("name", ConstructorParameterDescription(self.name)), ("utcOffset", ConstructorParameterDescription(self.utcOffset))]) + return ("starGiftBackground", [("centerColor", ConstructorParameterDescription(self.centerColor)), ("edgeColor", ConstructorParameterDescription(self.edgeColor)), ("textColor", ConstructorParameterDescription(self.textColor))]) } } - case timezone(Cons_timezone) + case starGiftBackground(Cons_starGiftBackground) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .timezone(let _data): + case .starGiftBackground(let _data): if boxed { - buffer.appendInt32(-7173643) + buffer.appendInt32(-1342872680) } - serializeString(_data.id, buffer: buffer, boxed: false) - serializeString(_data.name, buffer: buffer, boxed: false) - serializeInt32(_data.utcOffset, buffer: buffer, boxed: false) + serializeInt32(_data.centerColor, buffer: buffer, boxed: false) + serializeInt32(_data.edgeColor, buffer: buffer, boxed: false) + serializeInt32(_data.textColor, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .timezone(let _data): - return ("timezone", [("id", ConstructorParameterDescription(_data.id)), ("name", ConstructorParameterDescription(_data.name)), ("utcOffset", ConstructorParameterDescription(_data.utcOffset))]) + case .starGiftBackground(let _data): + return ("starGiftBackground", [("centerColor", ConstructorParameterDescription(_data.centerColor)), ("edgeColor", ConstructorParameterDescription(_data.edgeColor)), ("textColor", ConstructorParameterDescription(_data.textColor))]) } } - public static func parse_timezone(_ reader: BufferReader) -> Timezone? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Timezone.timezone(Cons_timezone(id: _1!, name: _2!, utcOffset: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum TodoCompletion: TypeConstructorDescription { - public class Cons_todoCompletion: TypeConstructorDescription { - public var id: Int32 - public var completedBy: Api.Peer - public var date: Int32 - public init(id: Int32, completedBy: Api.Peer, date: Int32) { - self.id = id - self.completedBy = completedBy - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("todoCompletion", [("id", ConstructorParameterDescription(self.id)), ("completedBy", ConstructorParameterDescription(self.completedBy)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case todoCompletion(Cons_todoCompletion) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .todoCompletion(let _data): - if boxed { - buffer.appendInt32(572241380) - } - serializeInt32(_data.id, buffer: buffer, boxed: false) - _data.completedBy.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .todoCompletion(let _data): - return ("todoCompletion", [("id", ConstructorParameterDescription(_data.id)), ("completedBy", ConstructorParameterDescription(_data.completedBy)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_todoCompletion(_ reader: BufferReader) -> TodoCompletion? { + public static func parse_starGiftBackground(_ reader: BufferReader) -> StarGiftBackground? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.TodoCompletion.todoCompletion(Cons_todoCompletion(id: _1!, completedBy: _2!, date: _3!)) + return Api.StarGiftBackground.starGiftBackground(Cons_starGiftBackground(centerColor: _1!, edgeColor: _2!, textColor: _3!)) } else { return nil @@ -1524,50 +800,128 @@ public extension Api { } } public extension Api { - enum TodoItem: TypeConstructorDescription { - public class Cons_todoItem: TypeConstructorDescription { - public var id: Int32 - public var title: Api.TextWithEntities - public init(id: Int32, title: Api.TextWithEntities) { - self.id = id + enum StarGiftCollection: TypeConstructorDescription { + public class Cons_starGiftCollection: TypeConstructorDescription { + public var flags: Int32 + public var collectionId: Int32 + public var title: String + public var icon: Api.Document? + public var giftsCount: Int32 + public var hash: Int64 + public init(flags: Int32, collectionId: Int32, title: String, icon: Api.Document?, giftsCount: Int32, hash: Int64) { + self.flags = flags + self.collectionId = collectionId self.title = title + self.icon = icon + self.giftsCount = giftsCount + self.hash = hash } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("todoItem", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title))]) + return ("starGiftCollection", [("flags", ConstructorParameterDescription(self.flags)), ("collectionId", ConstructorParameterDescription(self.collectionId)), ("title", ConstructorParameterDescription(self.title)), ("icon", ConstructorParameterDescription(self.icon)), ("giftsCount", ConstructorParameterDescription(self.giftsCount)), ("hash", ConstructorParameterDescription(self.hash))]) } } - case todoItem(Cons_todoItem) + case starGiftCollection(Cons_starGiftCollection) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .todoItem(let _data): + case .starGiftCollection(let _data): if boxed { - buffer.appendInt32(-878074577) + buffer.appendInt32(-1653926992) } - serializeInt32(_data.id, buffer: buffer, boxed: false) - _data.title.serialize(buffer, true) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.collectionId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.icon!.serialize(buffer, true) + } + serializeInt32(_data.giftsCount, buffer: buffer, boxed: false) + serializeInt64(_data.hash, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .todoItem(let _data): - return ("todoItem", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title))]) + case .starGiftCollection(let _data): + return ("starGiftCollection", [("flags", ConstructorParameterDescription(_data.flags)), ("collectionId", ConstructorParameterDescription(_data.collectionId)), ("title", ConstructorParameterDescription(_data.title)), ("icon", ConstructorParameterDescription(_data.icon)), ("giftsCount", ConstructorParameterDescription(_data.giftsCount)), ("hash", ConstructorParameterDescription(_data.hash))]) } } - public static func parse_todoItem(_ reader: BufferReader) -> TodoItem? { + public static func parse_starGiftCollection(_ reader: BufferReader) -> StarGiftCollection? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Api.Document? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Document + } } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + _6 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StarGiftCollection.starGiftCollection(Cons_starGiftCollection(flags: _1!, collectionId: _2!, title: _3!, icon: _4, giftsCount: _5!, hash: _6!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarGiftUpgradePrice: TypeConstructorDescription { + public class Cons_starGiftUpgradePrice: TypeConstructorDescription { + public var date: Int32 + public var upgradeStars: Int64 + public init(date: Int32, upgradeStars: Int64) { + self.date = date + self.upgradeStars = upgradeStars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftUpgradePrice", [("date", ConstructorParameterDescription(self.date)), ("upgradeStars", ConstructorParameterDescription(self.upgradeStars))]) + } + } + case starGiftUpgradePrice(Cons_starGiftUpgradePrice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftUpgradePrice(let _data): + if boxed { + buffer.appendInt32(-1712704739) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.upgradeStars, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftUpgradePrice(let _data): + return ("starGiftUpgradePrice", [("date", ConstructorParameterDescription(_data.date)), ("upgradeStars", ConstructorParameterDescription(_data.upgradeStars))]) + } + } + + public static func parse_starGiftUpgradePrice(_ reader: BufferReader) -> StarGiftUpgradePrice? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.TodoItem.todoItem(Cons_todoItem(id: _1!, title: _2!)) + return Api.StarGiftUpgradePrice.starGiftUpgradePrice(Cons_starGiftUpgradePrice(date: _1!, upgradeStars: _2!)) } else { return nil @@ -1576,33 +930,286 @@ public extension Api { } } public extension Api { - enum TodoList: TypeConstructorDescription { - public class Cons_todoList: TypeConstructorDescription { + enum StarRefProgram: TypeConstructorDescription { + public class Cons_starRefProgram: TypeConstructorDescription { public var flags: Int32 - public var title: Api.TextWithEntities - public var list: [Api.TodoItem] - public init(flags: Int32, title: Api.TextWithEntities, list: [Api.TodoItem]) { + public var botId: Int64 + public var commissionPermille: Int32 + public var durationMonths: Int32? + public var endDate: Int32? + public var dailyRevenuePerUser: Api.StarsAmount? + public init(flags: Int32, botId: Int64, commissionPermille: Int32, durationMonths: Int32?, endDate: Int32?, dailyRevenuePerUser: Api.StarsAmount?) { self.flags = flags - self.title = title - self.list = list + self.botId = botId + self.commissionPermille = commissionPermille + self.durationMonths = durationMonths + self.endDate = endDate + self.dailyRevenuePerUser = dailyRevenuePerUser } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("todoList", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("list", ConstructorParameterDescription(self.list))]) + return ("starRefProgram", [("flags", ConstructorParameterDescription(self.flags)), ("botId", ConstructorParameterDescription(self.botId)), ("commissionPermille", ConstructorParameterDescription(self.commissionPermille)), ("durationMonths", ConstructorParameterDescription(self.durationMonths)), ("endDate", ConstructorParameterDescription(self.endDate)), ("dailyRevenuePerUser", ConstructorParameterDescription(self.dailyRevenuePerUser))]) } } - case todoList(Cons_todoList) + case starRefProgram(Cons_starRefProgram) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .todoList(let _data): + case .starRefProgram(let _data): if boxed { - buffer.appendInt32(1236871718) + buffer.appendInt32(-586389774) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.title.serialize(buffer, true) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeInt32(_data.commissionPermille, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.durationMonths!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.endDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.dailyRevenuePerUser!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starRefProgram(let _data): + return ("starRefProgram", [("flags", ConstructorParameterDescription(_data.flags)), ("botId", ConstructorParameterDescription(_data.botId)), ("commissionPermille", ConstructorParameterDescription(_data.commissionPermille)), ("durationMonths", ConstructorParameterDescription(_data.durationMonths)), ("endDate", ConstructorParameterDescription(_data.endDate)), ("dailyRevenuePerUser", ConstructorParameterDescription(_data.dailyRevenuePerUser))]) + } + } + + public static func parse_starRefProgram(_ reader: BufferReader) -> StarRefProgram? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + var _6: Api.StarsAmount? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StarRefProgram.starRefProgram(Cons_starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsAmount: TypeConstructorDescription { + public class Cons_starsAmount: TypeConstructorDescription { + public var amount: Int64 + public var nanos: Int32 + public init(amount: Int64, nanos: Int32) { + self.amount = amount + self.nanos = nanos + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsAmount", [("amount", ConstructorParameterDescription(self.amount)), ("nanos", ConstructorParameterDescription(self.nanos))]) + } + } + public class Cons_starsTonAmount: TypeConstructorDescription { + public var amount: Int64 + public init(amount: Int64) { + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsTonAmount", [("amount", ConstructorParameterDescription(self.amount))]) + } + } + case starsAmount(Cons_starsAmount) + case starsTonAmount(Cons_starsTonAmount) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsAmount(let _data): + if boxed { + buffer.appendInt32(-1145654109) + } + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeInt32(_data.nanos, buffer: buffer, boxed: false) + break + case .starsTonAmount(let _data): + if boxed { + buffer.appendInt32(1957618656) + } + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsAmount(let _data): + return ("starsAmount", [("amount", ConstructorParameterDescription(_data.amount)), ("nanos", ConstructorParameterDescription(_data.nanos))]) + case .starsTonAmount(let _data): + return ("starsTonAmount", [("amount", ConstructorParameterDescription(_data.amount))]) + } + } + + public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!)) + } + else { + return nil + } + } + public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsGiftOption: TypeConstructorDescription { + public class Cons_starsGiftOption: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var storeProduct: String? + public var currency: String + public var amount: Int64 + public init(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) { + self.flags = flags + self.stars = stars + self.storeProduct = storeProduct + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsGiftOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + case starsGiftOption(Cons_starsGiftOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsGiftOption(let _data): + if boxed { + buffer.appendInt32(1577421297) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsGiftOption(let _data): + return ("starsGiftOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + } + } + + public static func parse_starsGiftOption(_ reader: BufferReader) -> StarsGiftOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarsGiftOption.starsGiftOption(Cons_starsGiftOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsGiveawayOption: TypeConstructorDescription { + public class Cons_starsGiveawayOption: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var yearlyBoosts: Int32 + public var storeProduct: String? + public var currency: String + public var amount: Int64 + public var winners: [Api.StarsGiveawayWinnersOption] + public init(flags: Int32, stars: Int64, yearlyBoosts: Int32, storeProduct: String?, currency: String, amount: Int64, winners: [Api.StarsGiveawayWinnersOption]) { + self.flags = flags + self.stars = stars + self.yearlyBoosts = yearlyBoosts + self.storeProduct = storeProduct + self.currency = currency + self.amount = amount + self.winners = winners + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsGiveawayOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("yearlyBoosts", ConstructorParameterDescription(self.yearlyBoosts)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("winners", ConstructorParameterDescription(self.winners))]) + } + } + case starsGiveawayOption(Cons_starsGiveawayOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsGiveawayOption(let _data): + if boxed { + buffer.appendInt32(-1798404822) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + serializeInt32(_data.yearlyBoosts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.list.count)) - for item in _data.list { + buffer.appendInt32(Int32(_data.winners.count)) + for item in _data.winners { item.serialize(buffer, true) } break @@ -1611,27 +1218,39 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .todoList(let _data): - return ("todoList", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("list", ConstructorParameterDescription(_data.list))]) + case .starsGiveawayOption(let _data): + return ("starsGiveawayOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("yearlyBoosts", ConstructorParameterDescription(_data.yearlyBoosts)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("winners", ConstructorParameterDescription(_data.winners))]) } } - public static func parse_todoList(_ reader: BufferReader) -> TodoList? { + public static func parse_starsGiveawayOption(_ reader: BufferReader) -> StarsGiveawayOption? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseString(reader) } - var _3: [Api.TodoItem]? + var _5: String? + _5 = parseString(reader) + var _6: Int64? + _6 = reader.readInt64() + var _7: [Api.StarsGiveawayWinnersOption]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiveawayWinnersOption.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TodoList.todoList(Cons_todoList(flags: _1!, title: _2!, list: _3!)) + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.StarsGiveawayOption.starsGiveawayOption(Cons_starsGiveawayOption(flags: _1!, stars: _2!, yearlyBoosts: _3!, storeProduct: _4, currency: _5!, amount: _6!, winners: _7!)) } else { return nil @@ -1640,168 +1259,843 @@ public extension Api { } } public extension Api { - enum TopPeer: TypeConstructorDescription { - public class Cons_topPeer: TypeConstructorDescription { - public var peer: Api.Peer - public var rating: Double - public init(peer: Api.Peer, rating: Double) { - self.peer = peer - self.rating = rating + enum StarsGiveawayWinnersOption: TypeConstructorDescription { + public class Cons_starsGiveawayWinnersOption: TypeConstructorDescription { + public var flags: Int32 + public var users: Int32 + public var perUserStars: Int64 + public init(flags: Int32, users: Int32, perUserStars: Int64) { + self.flags = flags + self.users = users + self.perUserStars = perUserStars } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("topPeer", [("peer", ConstructorParameterDescription(self.peer)), ("rating", ConstructorParameterDescription(self.rating))]) + return ("starsGiveawayWinnersOption", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("perUserStars", ConstructorParameterDescription(self.perUserStars))]) } } - case topPeer(Cons_topPeer) + case starsGiveawayWinnersOption(Cons_starsGiveawayWinnersOption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .topPeer(let _data): + case .starsGiveawayWinnersOption(let _data): if boxed { - buffer.appendInt32(-305282981) + buffer.appendInt32(1411605001) } - _data.peer.serialize(buffer, true) - serializeDouble(_data.rating, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.users, buffer: buffer, boxed: false) + serializeInt64(_data.perUserStars, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .topPeer(let _data): - return ("topPeer", [("peer", ConstructorParameterDescription(_data.peer)), ("rating", ConstructorParameterDescription(_data.rating))]) + case .starsGiveawayWinnersOption(let _data): + return ("starsGiveawayWinnersOption", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("perUserStars", ConstructorParameterDescription(_data.perUserStars))]) } } - public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? { + public static func parse_starsGiveawayWinnersOption(_ reader: BufferReader) -> StarsGiveawayWinnersOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StarsGiveawayWinnersOption.starsGiveawayWinnersOption(Cons_starsGiveawayWinnersOption(flags: _1!, users: _2!, perUserStars: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsRating: TypeConstructorDescription { + public class Cons_starsRating: TypeConstructorDescription { + public var flags: Int32 + public var level: Int32 + public var currentLevelStars: Int64 + public var stars: Int64 + public var nextLevelStars: Int64? + public init(flags: Int32, level: Int32, currentLevelStars: Int64, stars: Int64, nextLevelStars: Int64?) { + self.flags = flags + self.level = level + self.currentLevelStars = currentLevelStars + self.stars = stars + self.nextLevelStars = nextLevelStars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsRating", [("flags", ConstructorParameterDescription(self.flags)), ("level", ConstructorParameterDescription(self.level)), ("currentLevelStars", ConstructorParameterDescription(self.currentLevelStars)), ("stars", ConstructorParameterDescription(self.stars)), ("nextLevelStars", ConstructorParameterDescription(self.nextLevelStars))]) + } + } + case starsRating(Cons_starsRating) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsRating(let _data): + if boxed { + buffer.appendInt32(453922567) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.level, buffer: buffer, boxed: false) + serializeInt64(_data.currentLevelStars, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.nextLevelStars!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsRating(let _data): + return ("starsRating", [("flags", ConstructorParameterDescription(_data.flags)), ("level", ConstructorParameterDescription(_data.level)), ("currentLevelStars", ConstructorParameterDescription(_data.currentLevelStars)), ("stars", ConstructorParameterDescription(_data.stars)), ("nextLevelStars", ConstructorParameterDescription(_data.nextLevelStars))]) + } + } + + public static func parse_starsRating(_ reader: BufferReader) -> StarsRating? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarsRating.starsRating(Cons_starsRating(flags: _1!, level: _2!, currentLevelStars: _3!, stars: _4!, nextLevelStars: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsRevenueStatus: TypeConstructorDescription { + public class Cons_starsRevenueStatus: TypeConstructorDescription { + public var flags: Int32 + public var currentBalance: Api.StarsAmount + public var availableBalance: Api.StarsAmount + public var overallRevenue: Api.StarsAmount + public var nextWithdrawalAt: Int32? + public init(flags: Int32, currentBalance: Api.StarsAmount, availableBalance: Api.StarsAmount, overallRevenue: Api.StarsAmount, nextWithdrawalAt: Int32?) { + self.flags = flags + self.currentBalance = currentBalance + self.availableBalance = availableBalance + self.overallRevenue = overallRevenue + self.nextWithdrawalAt = nextWithdrawalAt + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsRevenueStatus", [("flags", ConstructorParameterDescription(self.flags)), ("currentBalance", ConstructorParameterDescription(self.currentBalance)), ("availableBalance", ConstructorParameterDescription(self.availableBalance)), ("overallRevenue", ConstructorParameterDescription(self.overallRevenue)), ("nextWithdrawalAt", ConstructorParameterDescription(self.nextWithdrawalAt))]) + } + } + case starsRevenueStatus(Cons_starsRevenueStatus) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsRevenueStatus(let _data): + if boxed { + buffer.appendInt32(-21080943) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.currentBalance.serialize(buffer, true) + _data.availableBalance.serialize(buffer, true) + _data.overallRevenue.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.nextWithdrawalAt!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsRevenueStatus(let _data): + return ("starsRevenueStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("currentBalance", ConstructorParameterDescription(_data.currentBalance)), ("availableBalance", ConstructorParameterDescription(_data.availableBalance)), ("overallRevenue", ConstructorParameterDescription(_data.overallRevenue)), ("nextWithdrawalAt", ConstructorParameterDescription(_data.nextWithdrawalAt))]) + } + } + + public static func parse_starsRevenueStatus(_ reader: BufferReader) -> StarsRevenueStatus? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarsAmount? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _4: Api.StarsAmount? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarsRevenueStatus.starsRevenueStatus(Cons_starsRevenueStatus(flags: _1!, currentBalance: _2!, availableBalance: _3!, overallRevenue: _4!, nextWithdrawalAt: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsSubscription: TypeConstructorDescription { + public class Cons_starsSubscription: TypeConstructorDescription { + public var flags: Int32 + public var id: String + public var peer: Api.Peer + public var untilDate: Int32 + public var pricing: Api.StarsSubscriptionPricing + public var chatInviteHash: String? + public var title: String? + public var photo: Api.WebDocument? + public var invoiceSlug: String? + public init(flags: Int32, id: String, peer: Api.Peer, untilDate: Int32, pricing: Api.StarsSubscriptionPricing, chatInviteHash: String?, title: String?, photo: Api.WebDocument?, invoiceSlug: String?) { + self.flags = flags + self.id = id + self.peer = peer + self.untilDate = untilDate + self.pricing = pricing + self.chatInviteHash = chatInviteHash + self.title = title + self.photo = photo + self.invoiceSlug = invoiceSlug + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsSubscription", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("peer", ConstructorParameterDescription(self.peer)), ("untilDate", ConstructorParameterDescription(self.untilDate)), ("pricing", ConstructorParameterDescription(self.pricing)), ("chatInviteHash", ConstructorParameterDescription(self.chatInviteHash)), ("title", ConstructorParameterDescription(self.title)), ("photo", ConstructorParameterDescription(self.photo)), ("invoiceSlug", ConstructorParameterDescription(self.invoiceSlug))]) + } + } + case starsSubscription(Cons_starsSubscription) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsSubscription(let _data): + if boxed { + buffer.appendInt32(779004698) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.id, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + _data.pricing.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.chatInviteHash!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeString(_data.invoiceSlug!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsSubscription(let _data): + return ("starsSubscription", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("peer", ConstructorParameterDescription(_data.peer)), ("untilDate", ConstructorParameterDescription(_data.untilDate)), ("pricing", ConstructorParameterDescription(_data.pricing)), ("chatInviteHash", ConstructorParameterDescription(_data.chatInviteHash)), ("title", ConstructorParameterDescription(_data.title)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoiceSlug", ConstructorParameterDescription(_data.invoiceSlug))]) + } + } + + public static func parse_starsSubscription(_ reader: BufferReader) -> StarsSubscription? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.StarsSubscriptionPricing? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _6 = parseString(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _7 = parseString(reader) + } + var _8: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _9: String? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _9 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.StarsSubscription.starsSubscription(Cons_starsSubscription(flags: _1!, id: _2!, peer: _3!, untilDate: _4!, pricing: _5!, chatInviteHash: _6, title: _7, photo: _8, invoiceSlug: _9)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsSubscriptionPricing: TypeConstructorDescription { + public class Cons_starsSubscriptionPricing: TypeConstructorDescription { + public var period: Int32 + public var amount: Int64 + public init(period: Int32, amount: Int64) { + self.period = period + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsSubscriptionPricing", [("period", ConstructorParameterDescription(self.period)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + case starsSubscriptionPricing(Cons_starsSubscriptionPricing) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsSubscriptionPricing(let _data): + if boxed { + buffer.appendInt32(88173912) + } + serializeInt32(_data.period, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsSubscriptionPricing(let _data): + return ("starsSubscriptionPricing", [("period", ConstructorParameterDescription(_data.period)), ("amount", ConstructorParameterDescription(_data.amount))]) + } + } + + public static func parse_starsSubscriptionPricing(_ reader: BufferReader) -> StarsSubscriptionPricing? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StarsSubscriptionPricing.starsSubscriptionPricing(Cons_starsSubscriptionPricing(period: _1!, amount: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsTopupOption: TypeConstructorDescription { + public class Cons_starsTopupOption: TypeConstructorDescription { + public var flags: Int32 + public var stars: Int64 + public var storeProduct: String? + public var currency: String + public var amount: Int64 + public init(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64) { + self.flags = flags + self.stars = stars + self.storeProduct = storeProduct + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsTopupOption", [("flags", ConstructorParameterDescription(self.flags)), ("stars", ConstructorParameterDescription(self.stars)), ("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + case starsTopupOption(Cons_starsTopupOption) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTopupOption(let _data): + if boxed { + buffer.appendInt32(198776256) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stars, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.storeProduct!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsTopupOption(let _data): + return ("starsTopupOption", [("flags", ConstructorParameterDescription(_data.flags)), ("stars", ConstructorParameterDescription(_data.stars)), ("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + } + } + + public static func parse_starsTopupOption(_ reader: BufferReader) -> StarsTopupOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StarsTopupOption.starsTopupOption(Cons_starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsTransaction: TypeConstructorDescription { + public class Cons_starsTransaction: TypeConstructorDescription { + public var flags: Int32 + public var id: String + public var amount: Api.StarsAmount + public var date: Int32 + public var peer: Api.StarsTransactionPeer + public var title: String? + public var description: String? + public var photo: Api.WebDocument? + public var transactionDate: Int32? + public var transactionUrl: String? + public var botPayload: Buffer? + public var msgId: Int32? + public var extendedMedia: [Api.MessageMedia]? + public var subscriptionPeriod: Int32? + public var giveawayPostId: Int32? + public var stargift: Api.StarGift? + public var floodskipNumber: Int32? + public var starrefCommissionPermille: Int32? + public var starrefPeer: Api.Peer? + public var starrefAmount: Api.StarsAmount? + public var paidMessages: Int32? + public var premiumGiftMonths: Int32? + public var adsProceedsFromDate: Int32? + public var adsProceedsToDate: Int32? + public init(flags: Int32, id: String, amount: Api.StarsAmount, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?, starrefCommissionPermille: Int32?, starrefPeer: Api.Peer?, starrefAmount: Api.StarsAmount?, paidMessages: Int32?, premiumGiftMonths: Int32?, adsProceedsFromDate: Int32?, adsProceedsToDate: Int32?) { + self.flags = flags + self.id = id + self.amount = amount + self.date = date + self.peer = peer + self.title = title + self.description = description + self.photo = photo + self.transactionDate = transactionDate + self.transactionUrl = transactionUrl + self.botPayload = botPayload + self.msgId = msgId + self.extendedMedia = extendedMedia + self.subscriptionPeriod = subscriptionPeriod + self.giveawayPostId = giveawayPostId + self.stargift = stargift + self.floodskipNumber = floodskipNumber + self.starrefCommissionPermille = starrefCommissionPermille + self.starrefPeer = starrefPeer + self.starrefAmount = starrefAmount + self.paidMessages = paidMessages + self.premiumGiftMonths = premiumGiftMonths + self.adsProceedsFromDate = adsProceedsFromDate + self.adsProceedsToDate = adsProceedsToDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsTransaction", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("amount", ConstructorParameterDescription(self.amount)), ("date", ConstructorParameterDescription(self.date)), ("peer", ConstructorParameterDescription(self.peer)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("transactionDate", ConstructorParameterDescription(self.transactionDate)), ("transactionUrl", ConstructorParameterDescription(self.transactionUrl)), ("botPayload", ConstructorParameterDescription(self.botPayload)), ("msgId", ConstructorParameterDescription(self.msgId)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia)), ("subscriptionPeriod", ConstructorParameterDescription(self.subscriptionPeriod)), ("giveawayPostId", ConstructorParameterDescription(self.giveawayPostId)), ("stargift", ConstructorParameterDescription(self.stargift)), ("floodskipNumber", ConstructorParameterDescription(self.floodskipNumber)), ("starrefCommissionPermille", ConstructorParameterDescription(self.starrefCommissionPermille)), ("starrefPeer", ConstructorParameterDescription(self.starrefPeer)), ("starrefAmount", ConstructorParameterDescription(self.starrefAmount)), ("paidMessages", ConstructorParameterDescription(self.paidMessages)), ("premiumGiftMonths", ConstructorParameterDescription(self.premiumGiftMonths)), ("adsProceedsFromDate", ConstructorParameterDescription(self.adsProceedsFromDate)), ("adsProceedsToDate", ConstructorParameterDescription(self.adsProceedsToDate))]) + } + } + case starsTransaction(Cons_starsTransaction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTransaction(let _data): + if boxed { + buffer.appendInt32(325426864) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.id, buffer: buffer, boxed: false) + _data.amount.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.description!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.transactionDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.transactionUrl!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeBytes(_data.botPayload!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt32(_data.msgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.extendedMedia!.count)) + for item in _data.extendedMedia! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 12) != 0 { + serializeInt32(_data.subscriptionPeriod!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 13) != 0 { + serializeInt32(_data.giveawayPostId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + _data.stargift!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + serializeInt32(_data.floodskipNumber!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeInt32(_data.starrefCommissionPermille!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + _data.starrefPeer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + _data.starrefAmount!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 19) != 0 { + serializeInt32(_data.paidMessages!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 20) != 0 { + serializeInt32(_data.premiumGiftMonths!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 23) != 0 { + serializeInt32(_data.adsProceedsFromDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 23) != 0 { + serializeInt32(_data.adsProceedsToDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsTransaction(let _data): + return ("starsTransaction", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("amount", ConstructorParameterDescription(_data.amount)), ("date", ConstructorParameterDescription(_data.date)), ("peer", ConstructorParameterDescription(_data.peer)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("transactionDate", ConstructorParameterDescription(_data.transactionDate)), ("transactionUrl", ConstructorParameterDescription(_data.transactionUrl)), ("botPayload", ConstructorParameterDescription(_data.botPayload)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia)), ("subscriptionPeriod", ConstructorParameterDescription(_data.subscriptionPeriod)), ("giveawayPostId", ConstructorParameterDescription(_data.giveawayPostId)), ("stargift", ConstructorParameterDescription(_data.stargift)), ("floodskipNumber", ConstructorParameterDescription(_data.floodskipNumber)), ("starrefCommissionPermille", ConstructorParameterDescription(_data.starrefCommissionPermille)), ("starrefPeer", ConstructorParameterDescription(_data.starrefPeer)), ("starrefAmount", ConstructorParameterDescription(_data.starrefAmount)), ("paidMessages", ConstructorParameterDescription(_data.paidMessages)), ("premiumGiftMonths", ConstructorParameterDescription(_data.premiumGiftMonths)), ("adsProceedsFromDate", ConstructorParameterDescription(_data.adsProceedsFromDate)), ("adsProceedsToDate", ConstructorParameterDescription(_data.adsProceedsToDate))]) + } + } + + public static func parse_starsTransaction(_ reader: BufferReader) -> StarsTransaction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.StarsAmount? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Api.StarsTransactionPeer? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) + } + var _8: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _9 = reader.readInt32() + } + var _10: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _10 = parseString(reader) + } + var _11: Buffer? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _11 = parseBytes(reader) + } + var _12: Int32? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _12 = reader.readInt32() + } + var _13: [Api.MessageMedia]? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let _ = reader.readInt32() { + _13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self) + } + } + var _14: Int32? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + _14 = reader.readInt32() + } + var _15: Int32? + if Int(_1 ?? 0) & Int(1 << 13) != 0 { + _15 = reader.readInt32() + } + var _16: Api.StarGift? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.StarGift + } + } + var _17: Int32? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + _17 = reader.readInt32() + } + var _18: Int32? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _18 = reader.readInt32() + } + var _19: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _20: Api.StarsAmount? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + } + var _21: Int32? + if Int(_1 ?? 0) & Int(1 << 19) != 0 { + _21 = reader.readInt32() + } + var _22: Int32? + if Int(_1 ?? 0) & Int(1 << 20) != 0 { + _22 = reader.readInt32() + } + var _23: Int32? + if Int(_1 ?? 0) & Int(1 << 23) != 0 { + _23 = reader.readInt32() + } + var _24: Int32? + if Int(_1 ?? 0) & Int(1 << 23) != 0 { + _24 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _20 != nil + let _c21 = (Int(_1 ?? 0) & Int(1 << 19) == 0) || _21 != nil + let _c22 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _22 != nil + let _c23 = (Int(_1 ?? 0) & Int(1 << 23) == 0) || _23 != nil + let _c24 = (Int(_1 ?? 0) & Int(1 << 23) == 0) || _24 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 { + return Api.StarsTransaction.starsTransaction(Cons_starsTransaction(flags: _1!, id: _2!, amount: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20, paidMessages: _21, premiumGiftMonths: _22, adsProceedsFromDate: _23, adsProceedsToDate: _24)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StarsTransactionPeer: TypeConstructorDescription { + public class Cons_starsTransactionPeer: TypeConstructorDescription { + public var peer: Api.Peer + public init(peer: Api.Peer) { + self.peer = peer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsTransactionPeer", [("peer", ConstructorParameterDescription(self.peer))]) + } + } + case starsTransactionPeer(Cons_starsTransactionPeer) + case starsTransactionPeerAPI + case starsTransactionPeerAds + case starsTransactionPeerAppStore + case starsTransactionPeerFragment + case starsTransactionPeerPlayMarket + case starsTransactionPeerPremiumBot + case starsTransactionPeerUnsupported + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsTransactionPeer(let _data): + if boxed { + buffer.appendInt32(-670195363) + } + _data.peer.serialize(buffer, true) + break + case .starsTransactionPeerAPI: + if boxed { + buffer.appendInt32(-110658899) + } + break + case .starsTransactionPeerAds: + if boxed { + buffer.appendInt32(1617438738) + } + break + case .starsTransactionPeerAppStore: + if boxed { + buffer.appendInt32(-1269320843) + } + break + case .starsTransactionPeerFragment: + if boxed { + buffer.appendInt32(-382740222) + } + break + case .starsTransactionPeerPlayMarket: + if boxed { + buffer.appendInt32(2069236235) + } + break + case .starsTransactionPeerPremiumBot: + if boxed { + buffer.appendInt32(621656824) + } + break + case .starsTransactionPeerUnsupported: + if boxed { + buffer.appendInt32(-1779253276) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsTransactionPeer(let _data): + return ("starsTransactionPeer", [("peer", ConstructorParameterDescription(_data.peer))]) + case .starsTransactionPeerAPI: + return ("starsTransactionPeerAPI", []) + case .starsTransactionPeerAds: + return ("starsTransactionPeerAds", []) + case .starsTransactionPeerAppStore: + return ("starsTransactionPeerAppStore", []) + case .starsTransactionPeerFragment: + return ("starsTransactionPeerFragment", []) + case .starsTransactionPeerPlayMarket: + return ("starsTransactionPeerPlayMarket", []) + case .starsTransactionPeerPremiumBot: + return ("starsTransactionPeerPremiumBot", []) + case .starsTransactionPeerUnsupported: + return ("starsTransactionPeerUnsupported", []) + } + } + + public static func parse_starsTransactionPeer(_ reader: BufferReader) -> StarsTransactionPeer? { var _1: Api.Peer? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer } - var _2: Double? - _2 = reader.readDouble() let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.TopPeer.topPeer(Cons_topPeer(peer: _1!, rating: _2!)) + if _c1 { + return Api.StarsTransactionPeer.starsTransactionPeer(Cons_starsTransactionPeer(peer: _1!)) } else { return nil } } - } -} -public extension Api { - enum TopPeerCategory: TypeConstructorDescription { - case topPeerCategoryBotsApp - case topPeerCategoryBotsInline - case topPeerCategoryBotsPM - case topPeerCategoryChannels - case topPeerCategoryCorrespondents - case topPeerCategoryForwardChats - case topPeerCategoryForwardUsers - case topPeerCategoryGroups - case topPeerCategoryPhoneCalls - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .topPeerCategoryBotsApp: - if boxed { - buffer.appendInt32(-39945236) - } - break - case .topPeerCategoryBotsInline: - if boxed { - buffer.appendInt32(344356834) - } - break - case .topPeerCategoryBotsPM: - if boxed { - buffer.appendInt32(-1419371685) - } - break - case .topPeerCategoryChannels: - if boxed { - buffer.appendInt32(371037736) - } - break - case .topPeerCategoryCorrespondents: - if boxed { - buffer.appendInt32(104314861) - } - break - case .topPeerCategoryForwardChats: - if boxed { - buffer.appendInt32(-68239120) - } - break - case .topPeerCategoryForwardUsers: - if boxed { - buffer.appendInt32(-1472172887) - } - break - case .topPeerCategoryGroups: - if boxed { - buffer.appendInt32(-1122524854) - } - break - case .topPeerCategoryPhoneCalls: - if boxed { - buffer.appendInt32(511092620) - } - break - } + public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAPI } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .topPeerCategoryBotsApp: - return ("topPeerCategoryBotsApp", []) - case .topPeerCategoryBotsInline: - return ("topPeerCategoryBotsInline", []) - case .topPeerCategoryBotsPM: - return ("topPeerCategoryBotsPM", []) - case .topPeerCategoryChannels: - return ("topPeerCategoryChannels", []) - case .topPeerCategoryCorrespondents: - return ("topPeerCategoryCorrespondents", []) - case .topPeerCategoryForwardChats: - return ("topPeerCategoryForwardChats", []) - case .topPeerCategoryForwardUsers: - return ("topPeerCategoryForwardUsers", []) - case .topPeerCategoryGroups: - return ("topPeerCategoryGroups", []) - case .topPeerCategoryPhoneCalls: - return ("topPeerCategoryPhoneCalls", []) - } + public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAds } - - public static func parse_topPeerCategoryBotsApp(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryBotsApp + public static func parse_starsTransactionPeerAppStore(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAppStore } - public static func parse_topPeerCategoryBotsInline(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryBotsInline + public static func parse_starsTransactionPeerFragment(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerFragment } - public static func parse_topPeerCategoryBotsPM(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryBotsPM + public static func parse_starsTransactionPeerPlayMarket(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPlayMarket } - public static func parse_topPeerCategoryChannels(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryChannels + public static func parse_starsTransactionPeerPremiumBot(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerPremiumBot } - public static func parse_topPeerCategoryCorrespondents(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryCorrespondents - } - public static func parse_topPeerCategoryForwardChats(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryForwardChats - } - public static func parse_topPeerCategoryForwardUsers(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryForwardUsers - } - public static func parse_topPeerCategoryGroups(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryGroups - } - public static func parse_topPeerCategoryPhoneCalls(_ reader: BufferReader) -> TopPeerCategory? { - return Api.TopPeerCategory.topPeerCategoryPhoneCalls + public static func parse_starsTransactionPeerUnsupported(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerUnsupported } } } diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index e3507ba121..cb92c9896d 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,60 +1,46 @@ public extension Api { - enum TopPeerCategoryPeers: TypeConstructorDescription { - public class Cons_topPeerCategoryPeers: TypeConstructorDescription { - public var category: Api.TopPeerCategory - public var count: Int32 - public var peers: [Api.TopPeer] - public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) { - self.category = category - self.count = count - self.peers = peers + enum StatsAbsValueAndPrev: TypeConstructorDescription { + public class Cons_statsAbsValueAndPrev: TypeConstructorDescription { + public var current: Double + public var previous: Double + public init(current: Double, previous: Double) { + self.current = current + self.previous = previous } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("topPeerCategoryPeers", [("category", ConstructorParameterDescription(self.category)), ("count", ConstructorParameterDescription(self.count)), ("peers", ConstructorParameterDescription(self.peers))]) + return ("statsAbsValueAndPrev", [("current", ConstructorParameterDescription(self.current)), ("previous", ConstructorParameterDescription(self.previous))]) } } - case topPeerCategoryPeers(Cons_topPeerCategoryPeers) + case statsAbsValueAndPrev(Cons_statsAbsValueAndPrev) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .topPeerCategoryPeers(let _data): + case .statsAbsValueAndPrev(let _data): if boxed { - buffer.appendInt32(-75283823) - } - _data.category.serialize(buffer, true) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) + buffer.appendInt32(-884757282) } + serializeDouble(_data.current, buffer: buffer, boxed: false) + serializeDouble(_data.previous, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .topPeerCategoryPeers(let _data): - return ("topPeerCategoryPeers", [("category", ConstructorParameterDescription(_data.category)), ("count", ConstructorParameterDescription(_data.count)), ("peers", ConstructorParameterDescription(_data.peers))]) + case .statsAbsValueAndPrev(let _data): + return ("statsAbsValueAndPrev", [("current", ConstructorParameterDescription(_data.current)), ("previous", ConstructorParameterDescription(_data.previous))]) } } - public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? { - var _1: Api.TopPeerCategory? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.TopPeer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self) - } + public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!)) + if _c1 && _c2 { + return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(Cons_statsAbsValueAndPrev(current: _1!, previous: _2!)) } else { return nil @@ -63,6084 +49,218 @@ public extension Api { } } public extension Api { - indirect enum Update: TypeConstructorDescription { - public class Cons_updateBotBusinessConnect: TypeConstructorDescription { - public var connection: Api.BotBusinessConnection - public var qts: Int32 - public init(connection: Api.BotBusinessConnection, qts: Int32) { - self.connection = connection - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotBusinessConnect", [("connection", ConstructorParameterDescription(self.connection)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotCallbackQuery: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var userId: Int64 - public var peer: Api.Peer - public var msgId: Int32 - public var chatInstance: Int64 - public var data: Buffer? - public var gameShortName: String? - public init(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) { - self.flags = flags - self.queryId = queryId - self.userId = userId - self.peer = peer - self.msgId = msgId - self.chatInstance = chatInstance - self.data = data - self.gameShortName = gameShortName - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data)), ("gameShortName", ConstructorParameterDescription(self.gameShortName))]) - } - } - public class Cons_updateBotChatBoost: TypeConstructorDescription { - public var peer: Api.Peer - public var boost: Api.Boost - public var qts: Int32 - public init(peer: Api.Peer, boost: Api.Boost, qts: Int32) { - self.peer = peer - self.boost = boost - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotChatBoost", [("peer", ConstructorParameterDescription(self.peer)), ("boost", ConstructorParameterDescription(self.boost)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotChatInviteRequester: TypeConstructorDescription { - public var peer: Api.Peer - public var date: Int32 - public var userId: Int64 - public var about: String - public var invite: Api.ExportedChatInvite - public var qts: Int32 - public init(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32) { - self.peer = peer - self.date = date - self.userId = userId - self.about = about - self.invite = invite - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotChatInviteRequester", [("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("userId", ConstructorParameterDescription(self.userId)), ("about", ConstructorParameterDescription(self.about)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotCommands: TypeConstructorDescription { - public var peer: Api.Peer - public var botId: Int64 - public var commands: [Api.BotCommand] - public init(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) { - self.peer = peer - self.botId = botId - self.commands = commands - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotCommands", [("peer", ConstructorParameterDescription(self.peer)), ("botId", ConstructorParameterDescription(self.botId)), ("commands", ConstructorParameterDescription(self.commands))]) - } - } - public class Cons_updateBotDeleteBusinessMessage: TypeConstructorDescription { - public var connectionId: String - public var peer: Api.Peer - public var messages: [Int32] - public var qts: Int32 - public init(connectionId: String, peer: Api.Peer, messages: [Int32], qts: Int32) { - self.connectionId = connectionId - self.peer = peer - self.messages = messages - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotDeleteBusinessMessage", [("connectionId", ConstructorParameterDescription(self.connectionId)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotEditBusinessMessage: TypeConstructorDescription { - public var flags: Int32 - public var connectionId: String - public var message: Api.Message - public var replyToMessage: Api.Message? - public var qts: Int32 - public init(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) { - self.flags = flags - self.connectionId = connectionId - self.message = message - self.replyToMessage = replyToMessage - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotEditBusinessMessage", [("flags", ConstructorParameterDescription(self.flags)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotInlineQuery: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var userId: Int64 - public var query: String - public var geo: Api.GeoPoint? - public var peerType: Api.InlineQueryPeerType? - public var offset: String - public init(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) { - self.flags = flags - self.queryId = queryId - self.userId = userId - self.query = query - self.geo = geo - self.peerType = peerType - self.offset = offset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotInlineQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("query", ConstructorParameterDescription(self.query)), ("geo", ConstructorParameterDescription(self.geo)), ("peerType", ConstructorParameterDescription(self.peerType)), ("offset", ConstructorParameterDescription(self.offset))]) - } - } - public class Cons_updateBotInlineSend: TypeConstructorDescription { - public var flags: Int32 - public var userId: Int64 - public var query: String - public var geo: Api.GeoPoint? - public var id: String - public var msgId: Api.InputBotInlineMessageID? - public init(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) { - self.flags = flags - self.userId = userId - self.query = query - self.geo = geo - self.id = id - self.msgId = msgId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotInlineSend", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("query", ConstructorParameterDescription(self.query)), ("geo", ConstructorParameterDescription(self.geo)), ("id", ConstructorParameterDescription(self.id)), ("msgId", ConstructorParameterDescription(self.msgId))]) - } - } - public class Cons_updateBotMenuButton: TypeConstructorDescription { - public var botId: Int64 - public var button: Api.BotMenuButton - public init(botId: Int64, button: Api.BotMenuButton) { - self.botId = botId - self.button = button - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotMenuButton", [("botId", ConstructorParameterDescription(self.botId)), ("button", ConstructorParameterDescription(self.button))]) - } - } - public class Cons_updateBotMessageReaction: TypeConstructorDescription { - public var peer: Api.Peer - public var msgId: Int32 - public var date: Int32 - public var actor: Api.Peer - public var oldReactions: [Api.Reaction] - public var newReactions: [Api.Reaction] - public var qts: Int32 - public init(peer: Api.Peer, msgId: Int32, date: Int32, actor: Api.Peer, oldReactions: [Api.Reaction], newReactions: [Api.Reaction], qts: Int32) { - self.peer = peer - self.msgId = msgId - self.date = date - self.actor = actor - self.oldReactions = oldReactions - self.newReactions = newReactions - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotMessageReaction", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("actor", ConstructorParameterDescription(self.actor)), ("oldReactions", ConstructorParameterDescription(self.oldReactions)), ("newReactions", ConstructorParameterDescription(self.newReactions)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotMessageReactions: TypeConstructorDescription { - public var peer: Api.Peer - public var msgId: Int32 - public var date: Int32 - public var reactions: [Api.ReactionCount] - public var qts: Int32 - public init(peer: Api.Peer, msgId: Int32, date: Int32, reactions: [Api.ReactionCount], qts: Int32) { - self.peer = peer - self.msgId = msgId - self.date = date - self.reactions = reactions - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotMessageReactions", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("reactions", ConstructorParameterDescription(self.reactions)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotNewBusinessMessage: TypeConstructorDescription { - public var flags: Int32 - public var connectionId: String - public var message: Api.Message - public var replyToMessage: Api.Message? - public var qts: Int32 - public init(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) { - self.flags = flags - self.connectionId = connectionId - self.message = message - self.replyToMessage = replyToMessage - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotNewBusinessMessage", [("flags", ConstructorParameterDescription(self.flags)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotPrecheckoutQuery: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var userId: Int64 - public var payload: Buffer - public var info: Api.PaymentRequestedInfo? - public var shippingOptionId: String? - public var currency: String - public var totalAmount: Int64 - public init(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) { - self.flags = flags - self.queryId = queryId - self.userId = userId - self.payload = payload - self.info = info - self.shippingOptionId = shippingOptionId - self.currency = currency - self.totalAmount = totalAmount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotPrecheckoutQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("info", ConstructorParameterDescription(self.info)), ("shippingOptionId", ConstructorParameterDescription(self.shippingOptionId)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount))]) - } - } - public class Cons_updateBotPurchasedPaidMedia: TypeConstructorDescription { - public var userId: Int64 - public var payload: String - public var qts: Int32 - public init(userId: Int64, payload: String, qts: Int32) { - self.userId = userId - self.payload = payload - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotPurchasedPaidMedia", [("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotShippingQuery: TypeConstructorDescription { - public var queryId: Int64 - public var userId: Int64 - public var payload: Buffer - public var shippingAddress: Api.PostAddress - public init(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) { - self.queryId = queryId - self.userId = userId - self.payload = payload - self.shippingAddress = shippingAddress - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotShippingQuery", [("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("shippingAddress", ConstructorParameterDescription(self.shippingAddress))]) - } - } - public class Cons_updateBotStopped: TypeConstructorDescription { - public var userId: Int64 - public var date: Int32 - public var stopped: Api.Bool - public var qts: Int32 - public init(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) { - self.userId = userId - self.date = date - self.stopped = stopped - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotStopped", [("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date)), ("stopped", ConstructorParameterDescription(self.stopped)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateBotWebhookJSON: TypeConstructorDescription { - public var data: Api.DataJSON - public init(data: Api.DataJSON) { - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotWebhookJSON", [("data", ConstructorParameterDescription(self.data))]) - } - } - public class Cons_updateBotWebhookJSONQuery: TypeConstructorDescription { - public var queryId: Int64 - public var data: Api.DataJSON - public var timeout: Int32 - public init(queryId: Int64, data: Api.DataJSON, timeout: Int32) { - self.queryId = queryId - self.data = data - self.timeout = timeout - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBotWebhookJSONQuery", [("queryId", ConstructorParameterDescription(self.queryId)), ("data", ConstructorParameterDescription(self.data)), ("timeout", ConstructorParameterDescription(self.timeout))]) - } - } - public class Cons_updateBusinessBotCallbackQuery: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var userId: Int64 - public var connectionId: String - public var message: Api.Message - public var replyToMessage: Api.Message? - public var chatInstance: Int64 - public var data: Buffer? - public init(flags: Int32, queryId: Int64, userId: Int64, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, chatInstance: Int64, data: Buffer?) { - self.flags = flags - self.queryId = queryId - self.userId = userId - self.connectionId = connectionId - self.message = message - self.replyToMessage = replyToMessage - self.chatInstance = chatInstance - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateBusinessBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data))]) - } - } - public class Cons_updateChannel: TypeConstructorDescription { - public var channelId: Int64 - public init(channelId: Int64) { - self.channelId = channelId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannel", [("channelId", ConstructorParameterDescription(self.channelId))]) - } - } - public class Cons_updateChannelAvailableMessages: TypeConstructorDescription { - public var channelId: Int64 - public var availableMinId: Int32 - public init(channelId: Int64, availableMinId: Int32) { - self.channelId = channelId - self.availableMinId = availableMinId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelAvailableMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("availableMinId", ConstructorParameterDescription(self.availableMinId))]) - } - } - public class Cons_updateChannelMessageForwards: TypeConstructorDescription { - public var channelId: Int64 - public var id: Int32 - public var forwards: Int32 - public init(channelId: Int64, id: Int32, forwards: Int32) { - self.channelId = channelId - self.id = id - self.forwards = forwards - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelMessageForwards", [("channelId", ConstructorParameterDescription(self.channelId)), ("id", ConstructorParameterDescription(self.id)), ("forwards", ConstructorParameterDescription(self.forwards))]) - } - } - public class Cons_updateChannelMessageViews: TypeConstructorDescription { - public var channelId: Int64 - public var id: Int32 - public var views: Int32 - public init(channelId: Int64, id: Int32, views: Int32) { - self.channelId = channelId - self.id = id - self.views = views - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelMessageViews", [("channelId", ConstructorParameterDescription(self.channelId)), ("id", ConstructorParameterDescription(self.id)), ("views", ConstructorParameterDescription(self.views))]) - } - } - public class Cons_updateChannelParticipant: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var date: Int32 - public var actorId: Int64 - public var userId: Int64 - public var prevParticipant: Api.ChannelParticipant? - public var newParticipant: Api.ChannelParticipant? - public var invite: Api.ExportedChatInvite? - public var qts: Int32 - public init(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) { - self.flags = flags - self.channelId = channelId - self.date = date - self.actorId = actorId - self.userId = userId - self.prevParticipant = prevParticipant - self.newParticipant = newParticipant - self.invite = invite - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelParticipant", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("date", ConstructorParameterDescription(self.date)), ("actorId", ConstructorParameterDescription(self.actorId)), ("userId", ConstructorParameterDescription(self.userId)), ("prevParticipant", ConstructorParameterDescription(self.prevParticipant)), ("newParticipant", ConstructorParameterDescription(self.newParticipant)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateChannelReadMessagesContents: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var topMsgId: Int32? - public var savedPeerId: Api.Peer? - public var messages: [Int32] - public init(flags: Int32, channelId: Int64, topMsgId: Int32?, savedPeerId: Api.Peer?, messages: [Int32]) { - self.flags = flags - self.channelId = channelId - self.topMsgId = topMsgId - self.savedPeerId = savedPeerId - self.messages = messages - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelReadMessagesContents", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("messages", ConstructorParameterDescription(self.messages))]) - } - } - public class Cons_updateChannelTooLong: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var pts: Int32? - public init(flags: Int32, channelId: Int64, pts: Int32?) { - self.flags = flags - self.channelId = channelId - self.pts = pts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelTooLong", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("pts", ConstructorParameterDescription(self.pts))]) - } - } - public class Cons_updateChannelUserTyping: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var topMsgId: Int32? - public var fromId: Api.Peer - public var action: Api.SendMessageAction - public init(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) { - self.flags = flags - self.channelId = channelId - self.topMsgId = topMsgId - self.fromId = fromId - self.action = action - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelUserTyping", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("fromId", ConstructorParameterDescription(self.fromId)), ("action", ConstructorParameterDescription(self.action))]) - } - } - public class Cons_updateChannelViewForumAsMessages: TypeConstructorDescription { - public var channelId: Int64 - public var enabled: Api.Bool - public init(channelId: Int64, enabled: Api.Bool) { - self.channelId = channelId - self.enabled = enabled - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelViewForumAsMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("enabled", ConstructorParameterDescription(self.enabled))]) - } - } - public class Cons_updateChannelWebPage: TypeConstructorDescription { - public var channelId: Int64 - public var webpage: Api.WebPage - public var pts: Int32 - public var ptsCount: Int32 - public init(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) { - self.channelId = channelId - self.webpage = webpage - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChannelWebPage", [("channelId", ConstructorParameterDescription(self.channelId)), ("webpage", ConstructorParameterDescription(self.webpage)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateChat: TypeConstructorDescription { - public var chatId: Int64 - public init(chatId: Int64) { - self.chatId = chatId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChat", [("chatId", ConstructorParameterDescription(self.chatId))]) - } - } - public class Cons_updateChatDefaultBannedRights: TypeConstructorDescription { - public var peer: Api.Peer - public var defaultBannedRights: Api.ChatBannedRights - public var version: Int32 - public init(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) { - self.peer = peer - self.defaultBannedRights = defaultBannedRights - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatDefaultBannedRights", [("peer", ConstructorParameterDescription(self.peer)), ("defaultBannedRights", ConstructorParameterDescription(self.defaultBannedRights)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateChatParticipant: TypeConstructorDescription { - public var flags: Int32 - public var chatId: Int64 - public var date: Int32 - public var actorId: Int64 - public var userId: Int64 - public var prevParticipant: Api.ChatParticipant? - public var newParticipant: Api.ChatParticipant? - public var invite: Api.ExportedChatInvite? - public var qts: Int32 - public init(flags: Int32, chatId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) { - self.flags = flags - self.chatId = chatId - self.date = date - self.actorId = actorId - self.userId = userId - self.prevParticipant = prevParticipant - self.newParticipant = newParticipant - self.invite = invite - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipant", [("flags", ConstructorParameterDescription(self.flags)), ("chatId", ConstructorParameterDescription(self.chatId)), ("date", ConstructorParameterDescription(self.date)), ("actorId", ConstructorParameterDescription(self.actorId)), ("userId", ConstructorParameterDescription(self.userId)), ("prevParticipant", ConstructorParameterDescription(self.prevParticipant)), ("newParticipant", ConstructorParameterDescription(self.newParticipant)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateChatParticipantAdd: TypeConstructorDescription { - public var chatId: Int64 - public var userId: Int64 - public var inviterId: Int64 - public var date: Int32 - public var version: Int32 - public init(chatId: Int64, userId: Int64, inviterId: Int64, date: Int32, version: Int32) { - self.chatId = chatId - self.userId = userId - self.inviterId = inviterId - self.date = date - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipantAdd", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("inviterId", ConstructorParameterDescription(self.inviterId)), ("date", ConstructorParameterDescription(self.date)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateChatParticipantAdmin: TypeConstructorDescription { - public var chatId: Int64 - public var userId: Int64 - public var isAdmin: Api.Bool - public var version: Int32 - public init(chatId: Int64, userId: Int64, isAdmin: Api.Bool, version: Int32) { - self.chatId = chatId - self.userId = userId - self.isAdmin = isAdmin - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipantAdmin", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("isAdmin", ConstructorParameterDescription(self.isAdmin)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateChatParticipantDelete: TypeConstructorDescription { - public var chatId: Int64 - public var userId: Int64 - public var version: Int32 - public init(chatId: Int64, userId: Int64, version: Int32) { - self.chatId = chatId - self.userId = userId - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipantDelete", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateChatParticipantRank: TypeConstructorDescription { - public var chatId: Int64 - public var userId: Int64 - public var rank: String - public var version: Int32 - public init(chatId: Int64, userId: Int64, rank: String, version: Int32) { - self.chatId = chatId - self.userId = userId - self.rank = rank - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipantRank", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("rank", ConstructorParameterDescription(self.rank)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateChatParticipants: TypeConstructorDescription { - public var participants: Api.ChatParticipants - public init(participants: Api.ChatParticipants) { - self.participants = participants - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatParticipants", [("participants", ConstructorParameterDescription(self.participants))]) - } - } - public class Cons_updateChatUserTyping: TypeConstructorDescription { - public var chatId: Int64 - public var fromId: Api.Peer - public var action: Api.SendMessageAction - public init(chatId: Int64, fromId: Api.Peer, action: Api.SendMessageAction) { - self.chatId = chatId - self.fromId = fromId - self.action = action - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateChatUserTyping", [("chatId", ConstructorParameterDescription(self.chatId)), ("fromId", ConstructorParameterDescription(self.fromId)), ("action", ConstructorParameterDescription(self.action))]) - } - } - public class Cons_updateDcOptions: TypeConstructorDescription { - public var dcOptions: [Api.DcOption] - public init(dcOptions: [Api.DcOption]) { - self.dcOptions = dcOptions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDcOptions", [("dcOptions", ConstructorParameterDescription(self.dcOptions))]) - } - } - public class Cons_updateDeleteChannelMessages: TypeConstructorDescription { - public var channelId: Int64 - public var messages: [Int32] - public var pts: Int32 - public var ptsCount: Int32 - public init(channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) { - self.channelId = channelId - self.messages = messages - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteChannelMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateDeleteGroupCallMessages: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var messages: [Int32] - public init(call: Api.InputGroupCall, messages: [Int32]) { - self.call = call - self.messages = messages - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteGroupCallMessages", [("call", ConstructorParameterDescription(self.call)), ("messages", ConstructorParameterDescription(self.messages))]) - } - } - public class Cons_updateDeleteMessages: TypeConstructorDescription { - public var messages: [Int32] - public var pts: Int32 - public var ptsCount: Int32 - public init(messages: [Int32], pts: Int32, ptsCount: Int32) { - self.messages = messages - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteMessages", [("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateDeleteQuickReply: TypeConstructorDescription { - public var shortcutId: Int32 - public init(shortcutId: Int32) { - self.shortcutId = shortcutId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteQuickReply", [("shortcutId", ConstructorParameterDescription(self.shortcutId))]) - } - } - public class Cons_updateDeleteQuickReplyMessages: TypeConstructorDescription { - public var shortcutId: Int32 - public var messages: [Int32] - public init(shortcutId: Int32, messages: [Int32]) { - self.shortcutId = shortcutId - self.messages = messages - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteQuickReplyMessages", [("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("messages", ConstructorParameterDescription(self.messages))]) - } - } - public class Cons_updateDeleteScheduledMessages: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var messages: [Int32] - public var sentMessages: [Int32]? - public init(flags: Int32, peer: Api.Peer, messages: [Int32], sentMessages: [Int32]?) { - self.flags = flags - self.peer = peer - self.messages = messages - self.sentMessages = sentMessages - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDeleteScheduledMessages", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("sentMessages", ConstructorParameterDescription(self.sentMessages))]) - } - } - public class Cons_updateDialogFilter: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var filter: Api.DialogFilter? - public init(flags: Int32, id: Int32, filter: Api.DialogFilter?) { - self.flags = flags - self.id = id - self.filter = filter - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDialogFilter", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("filter", ConstructorParameterDescription(self.filter))]) - } - } - public class Cons_updateDialogFilterOrder: TypeConstructorDescription { - public var order: [Int32] - public init(order: [Int32]) { - self.order = order - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDialogFilterOrder", [("order", ConstructorParameterDescription(self.order))]) - } - } - public class Cons_updateDialogPinned: TypeConstructorDescription { - public var flags: Int32 - public var folderId: Int32? - public var peer: Api.DialogPeer - public init(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) { - self.flags = flags - self.folderId = folderId - self.peer = peer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDialogPinned", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("peer", ConstructorParameterDescription(self.peer))]) - } - } - public class Cons_updateDialogUnreadMark: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.DialogPeer - public var savedPeerId: Api.Peer? - public init(flags: Int32, peer: Api.DialogPeer, savedPeerId: Api.Peer?) { - self.flags = flags - self.peer = peer - self.savedPeerId = savedPeerId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDialogUnreadMark", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId))]) - } - } - public class Cons_updateDraftMessage: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var topMsgId: Int32? - public var savedPeerId: Api.Peer? - public var draft: Api.DraftMessage - public init(flags: Int32, peer: Api.Peer, topMsgId: Int32?, savedPeerId: Api.Peer?, draft: Api.DraftMessage) { - self.flags = flags - self.peer = peer - self.topMsgId = topMsgId - self.savedPeerId = savedPeerId - self.draft = draft - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateDraftMessage", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("draft", ConstructorParameterDescription(self.draft))]) - } - } - public class Cons_updateEditChannelMessage: TypeConstructorDescription { - public var message: Api.Message - public var pts: Int32 - public var ptsCount: Int32 - public init(message: Api.Message, pts: Int32, ptsCount: Int32) { - self.message = message - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEditChannelMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateEditMessage: TypeConstructorDescription { - public var message: Api.Message - public var pts: Int32 - public var ptsCount: Int32 - public init(message: Api.Message, pts: Int32, ptsCount: Int32) { - self.message = message - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEditMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateEmojiGameInfo: TypeConstructorDescription { - public var info: Api.messages.EmojiGameInfo - public init(info: Api.messages.EmojiGameInfo) { - self.info = info - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEmojiGameInfo", [("info", ConstructorParameterDescription(self.info))]) - } - } - public class Cons_updateEncryptedChatTyping: TypeConstructorDescription { - public var chatId: Int32 - public init(chatId: Int32) { - self.chatId = chatId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEncryptedChatTyping", [("chatId", ConstructorParameterDescription(self.chatId))]) - } - } - public class Cons_updateEncryptedMessagesRead: TypeConstructorDescription { - public var chatId: Int32 + enum StatsDateRangeDays: TypeConstructorDescription { + public class Cons_statsDateRangeDays: TypeConstructorDescription { + public var minDate: Int32 public var maxDate: Int32 - public var date: Int32 - public init(chatId: Int32, maxDate: Int32, date: Int32) { - self.chatId = chatId + public init(minDate: Int32, maxDate: Int32) { + self.minDate = minDate self.maxDate = maxDate - self.date = date } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEncryptedMessagesRead", [("chatId", ConstructorParameterDescription(self.chatId)), ("maxDate", ConstructorParameterDescription(self.maxDate)), ("date", ConstructorParameterDescription(self.date))]) + return ("statsDateRangeDays", [("minDate", ConstructorParameterDescription(self.minDate)), ("maxDate", ConstructorParameterDescription(self.maxDate))]) } } - public class Cons_updateEncryption: TypeConstructorDescription { - public var chat: Api.EncryptedChat - public var date: Int32 - public init(chat: Api.EncryptedChat, date: Int32) { - self.chat = chat - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateEncryption", [("chat", ConstructorParameterDescription(self.chat)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_updateFolderPeers: TypeConstructorDescription { - public var folderPeers: [Api.FolderPeer] - public var pts: Int32 - public var ptsCount: Int32 - public init(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) { - self.folderPeers = folderPeers - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateFolderPeers", [("folderPeers", ConstructorParameterDescription(self.folderPeers)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateGeoLiveViewed: TypeConstructorDescription { - public var peer: Api.Peer - public var msgId: Int32 - public init(peer: Api.Peer, msgId: Int32) { - self.peer = peer - self.msgId = msgId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGeoLiveViewed", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId))]) - } - } - public class Cons_updateGroupCall: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer? - public var call: Api.GroupCall - public init(flags: Int32, peer: Api.Peer?, call: Api.GroupCall) { - self.flags = flags - self.peer = peer - self.call = call - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCall", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("call", ConstructorParameterDescription(self.call))]) - } - } - public class Cons_updateGroupCallChainBlocks: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var subChainId: Int32 - public var blocks: [Buffer] - public var nextOffset: Int32 - public init(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32) { - self.call = call - self.subChainId = subChainId - self.blocks = blocks - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCallChainBlocks", [("call", ConstructorParameterDescription(self.call)), ("subChainId", ConstructorParameterDescription(self.subChainId)), ("blocks", ConstructorParameterDescription(self.blocks)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - public class Cons_updateGroupCallConnection: TypeConstructorDescription { - public var flags: Int32 - public var params: Api.DataJSON - public init(flags: Int32, params: Api.DataJSON) { - self.flags = flags - self.params = params - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCallConnection", [("flags", ConstructorParameterDescription(self.flags)), ("params", ConstructorParameterDescription(self.params))]) - } - } - public class Cons_updateGroupCallEncryptedMessage: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var fromId: Api.Peer - public var encryptedMessage: Buffer - public init(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer) { - self.call = call - self.fromId = fromId - self.encryptedMessage = encryptedMessage - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCallEncryptedMessage", [("call", ConstructorParameterDescription(self.call)), ("fromId", ConstructorParameterDescription(self.fromId)), ("encryptedMessage", ConstructorParameterDescription(self.encryptedMessage))]) - } - } - public class Cons_updateGroupCallMessage: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var message: Api.GroupCallMessage - public init(call: Api.InputGroupCall, message: Api.GroupCallMessage) { - self.call = call - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCallMessage", [("call", ConstructorParameterDescription(self.call)), ("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_updateGroupCallParticipants: TypeConstructorDescription { - public var call: Api.InputGroupCall - public var participants: [Api.GroupCallParticipant] - public var version: Int32 - public init(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) { - self.call = call - self.participants = participants - self.version = version - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateGroupCallParticipants", [("call", ConstructorParameterDescription(self.call)), ("participants", ConstructorParameterDescription(self.participants)), ("version", ConstructorParameterDescription(self.version))]) - } - } - public class Cons_updateInlineBotCallbackQuery: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var userId: Int64 - public var msgId: Api.InputBotInlineMessageID - public var chatInstance: Int64 - public var data: Buffer? - public var gameShortName: String? - public init(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) { - self.flags = flags - self.queryId = queryId - self.userId = userId - self.msgId = msgId - self.chatInstance = chatInstance - self.data = data - self.gameShortName = gameShortName - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateInlineBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("msgId", ConstructorParameterDescription(self.msgId)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data)), ("gameShortName", ConstructorParameterDescription(self.gameShortName))]) - } - } - public class Cons_updateLangPack: TypeConstructorDescription { - public var difference: Api.LangPackDifference - public init(difference: Api.LangPackDifference) { - self.difference = difference - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateLangPack", [("difference", ConstructorParameterDescription(self.difference))]) - } - } - public class Cons_updateLangPackTooLong: TypeConstructorDescription { - public var langCode: String - public init(langCode: String) { - self.langCode = langCode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateLangPackTooLong", [("langCode", ConstructorParameterDescription(self.langCode))]) - } - } - public class Cons_updateManagedBot: TypeConstructorDescription { - public var userId: Int64 - public var botId: Int64 - public var qts: Int32 - public init(userId: Int64, botId: Int64, qts: Int32) { - self.userId = userId - self.botId = botId - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateManagedBot", [("userId", ConstructorParameterDescription(self.userId)), ("botId", ConstructorParameterDescription(self.botId)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateMessageExtendedMedia: TypeConstructorDescription { - public var peer: Api.Peer - public var msgId: Int32 - public var extendedMedia: [Api.MessageExtendedMedia] - public init(peer: Api.Peer, msgId: Int32, extendedMedia: [Api.MessageExtendedMedia]) { - self.peer = peer - self.msgId = msgId - self.extendedMedia = extendedMedia - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMessageExtendedMedia", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) - } - } - public class Cons_updateMessageID: TypeConstructorDescription { - public var id: Int32 - public var randomId: Int64 - public init(id: Int32, randomId: Int64) { - self.id = id - self.randomId = randomId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMessageID", [("id", ConstructorParameterDescription(self.id)), ("randomId", ConstructorParameterDescription(self.randomId))]) - } - } - public class Cons_updateMessagePoll: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer? - public var msgId: Int32? - public var topMsgId: Int32? - public var pollId: Int64 - public var poll: Api.Poll? - public var results: Api.PollResults - public init(flags: Int32, peer: Api.Peer?, msgId: Int32?, topMsgId: Int32?, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) { - self.flags = flags - self.peer = peer - self.msgId = msgId - self.topMsgId = topMsgId - self.pollId = pollId - self.poll = poll - self.results = results - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMessagePoll", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("pollId", ConstructorParameterDescription(self.pollId)), ("poll", ConstructorParameterDescription(self.poll)), ("results", ConstructorParameterDescription(self.results))]) - } - } - public class Cons_updateMessagePollVote: TypeConstructorDescription { - public var pollId: Int64 - public var peer: Api.Peer - public var options: [Buffer] - public var positions: [Int32] - public var qts: Int32 - public init(pollId: Int64, peer: Api.Peer, options: [Buffer], positions: [Int32], qts: Int32) { - self.pollId = pollId - self.peer = peer - self.options = options - self.positions = positions - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMessagePollVote", [("pollId", ConstructorParameterDescription(self.pollId)), ("peer", ConstructorParameterDescription(self.peer)), ("options", ConstructorParameterDescription(self.options)), ("positions", ConstructorParameterDescription(self.positions)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateMessageReactions: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var msgId: Int32 - public var topMsgId: Int32? - public var savedPeerId: Api.Peer? - public var reactions: Api.MessageReactions - public init(flags: Int32, peer: Api.Peer, msgId: Int32, topMsgId: Int32?, savedPeerId: Api.Peer?, reactions: Api.MessageReactions) { - self.flags = flags - self.peer = peer - self.msgId = msgId - self.topMsgId = topMsgId - self.savedPeerId = savedPeerId - self.reactions = reactions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMessageReactions", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("reactions", ConstructorParameterDescription(self.reactions))]) - } - } - public class Cons_updateMonoForumNoPaidException: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var savedPeerId: Api.Peer - public init(flags: Int32, channelId: Int64, savedPeerId: Api.Peer) { - self.flags = flags - self.channelId = channelId - self.savedPeerId = savedPeerId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMonoForumNoPaidException", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId))]) - } - } - public class Cons_updateMoveStickerSetToTop: TypeConstructorDescription { - public var flags: Int32 - public var stickerset: Int64 - public init(flags: Int32, stickerset: Int64) { - self.flags = flags - self.stickerset = stickerset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateMoveStickerSetToTop", [("flags", ConstructorParameterDescription(self.flags)), ("stickerset", ConstructorParameterDescription(self.stickerset))]) - } - } - public class Cons_updateNewAuthorization: TypeConstructorDescription { - public var flags: Int32 - public var hash: Int64 - public var date: Int32? - public var device: String? - public var location: String? - public init(flags: Int32, hash: Int64, date: Int32?, device: String?, location: String?) { - self.flags = flags - self.hash = hash - self.date = date - self.device = device - self.location = location - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewAuthorization", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("date", ConstructorParameterDescription(self.date)), ("device", ConstructorParameterDescription(self.device)), ("location", ConstructorParameterDescription(self.location))]) - } - } - public class Cons_updateNewChannelMessage: TypeConstructorDescription { - public var message: Api.Message - public var pts: Int32 - public var ptsCount: Int32 - public init(message: Api.Message, pts: Int32, ptsCount: Int32) { - self.message = message - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewChannelMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateNewEncryptedMessage: TypeConstructorDescription { - public var message: Api.EncryptedMessage - public var qts: Int32 - public init(message: Api.EncryptedMessage, qts: Int32) { - self.message = message - self.qts = qts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewEncryptedMessage", [("message", ConstructorParameterDescription(self.message)), ("qts", ConstructorParameterDescription(self.qts))]) - } - } - public class Cons_updateNewMessage: TypeConstructorDescription { - public var message: Api.Message - public var pts: Int32 - public var ptsCount: Int32 - public init(message: Api.Message, pts: Int32, ptsCount: Int32) { - self.message = message - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateNewQuickReply: TypeConstructorDescription { - public var quickReply: Api.QuickReply - public init(quickReply: Api.QuickReply) { - self.quickReply = quickReply - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewQuickReply", [("quickReply", ConstructorParameterDescription(self.quickReply))]) - } - } - public class Cons_updateNewScheduledMessage: TypeConstructorDescription { - public var message: Api.Message - public init(message: Api.Message) { - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewScheduledMessage", [("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_updateNewStickerSet: TypeConstructorDescription { - public var stickerset: Api.messages.StickerSet - public init(stickerset: Api.messages.StickerSet) { - self.stickerset = stickerset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewStickerSet", [("stickerset", ConstructorParameterDescription(self.stickerset))]) - } - } - public class Cons_updateNewStoryReaction: TypeConstructorDescription { - public var storyId: Int32 - public var peer: Api.Peer - public var reaction: Api.Reaction - public init(storyId: Int32, peer: Api.Peer, reaction: Api.Reaction) { - self.storyId = storyId - self.peer = peer - self.reaction = reaction - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNewStoryReaction", [("storyId", ConstructorParameterDescription(self.storyId)), ("peer", ConstructorParameterDescription(self.peer)), ("reaction", ConstructorParameterDescription(self.reaction))]) - } - } - public class Cons_updateNotifySettings: TypeConstructorDescription { - public var peer: Api.NotifyPeer - public var notifySettings: Api.PeerNotifySettings - public init(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) { - self.peer = peer - self.notifySettings = notifySettings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateNotifySettings", [("peer", ConstructorParameterDescription(self.peer)), ("notifySettings", ConstructorParameterDescription(self.notifySettings))]) - } - } - public class Cons_updatePaidReactionPrivacy: TypeConstructorDescription { - public var `private`: Api.PaidReactionPrivacy - public init(`private`: Api.PaidReactionPrivacy) { - self.`private` = `private` - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePaidReactionPrivacy", [("`private`", ConstructorParameterDescription(self.`private`))]) - } - } - public class Cons_updatePeerBlocked: TypeConstructorDescription { - public var flags: Int32 - public var peerId: Api.Peer - public init(flags: Int32, peerId: Api.Peer) { - self.flags = flags - self.peerId = peerId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePeerBlocked", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId))]) - } - } - public class Cons_updatePeerHistoryTTL: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var ttlPeriod: Int32? - public init(flags: Int32, peer: Api.Peer, ttlPeriod: Int32?) { - self.flags = flags - self.peer = peer - self.ttlPeriod = ttlPeriod - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePeerHistoryTTL", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) - } - } - public class Cons_updatePeerLocated: TypeConstructorDescription { - public var peers: [Api.PeerLocated] - public init(peers: [Api.PeerLocated]) { - self.peers = peers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePeerLocated", [("peers", ConstructorParameterDescription(self.peers))]) - } - } - public class Cons_updatePeerSettings: TypeConstructorDescription { - public var peer: Api.Peer - public var settings: Api.PeerSettings - public init(peer: Api.Peer, settings: Api.PeerSettings) { - self.peer = peer - self.settings = settings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePeerSettings", [("peer", ConstructorParameterDescription(self.peer)), ("settings", ConstructorParameterDescription(self.settings))]) - } - } - public class Cons_updatePeerWallpaper: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var wallpaper: Api.WallPaper? - public init(flags: Int32, peer: Api.Peer, wallpaper: Api.WallPaper?) { - self.flags = flags - self.peer = peer - self.wallpaper = wallpaper - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePeerWallpaper", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) - } - } - public class Cons_updatePendingJoinRequests: TypeConstructorDescription { - public var peer: Api.Peer - public var requestsPending: Int32 - public var recentRequesters: [Int64] - public init(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64]) { - self.peer = peer - self.requestsPending = requestsPending - self.recentRequesters = recentRequesters - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePendingJoinRequests", [("peer", ConstructorParameterDescription(self.peer)), ("requestsPending", ConstructorParameterDescription(self.requestsPending)), ("recentRequesters", ConstructorParameterDescription(self.recentRequesters))]) - } - } - public class Cons_updatePhoneCall: TypeConstructorDescription { - public var phoneCall: Api.PhoneCall - public init(phoneCall: Api.PhoneCall) { - self.phoneCall = phoneCall - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePhoneCall", [("phoneCall", ConstructorParameterDescription(self.phoneCall))]) - } - } - public class Cons_updatePhoneCallSignalingData: TypeConstructorDescription { - public var phoneCallId: Int64 - public var data: Buffer - public init(phoneCallId: Int64, data: Buffer) { - self.phoneCallId = phoneCallId - self.data = data - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePhoneCallSignalingData", [("phoneCallId", ConstructorParameterDescription(self.phoneCallId)), ("data", ConstructorParameterDescription(self.data))]) - } - } - public class Cons_updatePinnedChannelMessages: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var messages: [Int32] - public var pts: Int32 - public var ptsCount: Int32 - public init(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) { - self.flags = flags - self.channelId = channelId - self.messages = messages - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedChannelMessages", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updatePinnedDialogs: TypeConstructorDescription { - public var flags: Int32 - public var folderId: Int32? - public var order: [Api.DialogPeer]? - public init(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) { - self.flags = flags - self.folderId = folderId - self.order = order - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedDialogs", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("order", ConstructorParameterDescription(self.order))]) - } - } - public class Cons_updatePinnedForumTopic: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var topicId: Int32 - public init(flags: Int32, peer: Api.Peer, topicId: Int32) { - self.flags = flags - self.peer = peer - self.topicId = topicId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedForumTopic", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topicId", ConstructorParameterDescription(self.topicId))]) - } - } - public class Cons_updatePinnedForumTopics: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var order: [Int32]? - public init(flags: Int32, peer: Api.Peer, order: [Int32]?) { - self.flags = flags - self.peer = peer - self.order = order - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedForumTopics", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("order", ConstructorParameterDescription(self.order))]) - } - } - public class Cons_updatePinnedMessages: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var messages: [Int32] - public var pts: Int32 - public var ptsCount: Int32 - public init(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) { - self.flags = flags - self.peer = peer - self.messages = messages - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedMessages", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updatePinnedSavedDialogs: TypeConstructorDescription { - public var flags: Int32 - public var order: [Api.DialogPeer]? - public init(flags: Int32, order: [Api.DialogPeer]?) { - self.flags = flags - self.order = order - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePinnedSavedDialogs", [("flags", ConstructorParameterDescription(self.flags)), ("order", ConstructorParameterDescription(self.order))]) - } - } - public class Cons_updatePrivacy: TypeConstructorDescription { - public var key: Api.PrivacyKey - public var rules: [Api.PrivacyRule] - public init(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) { - self.key = key - self.rules = rules - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatePrivacy", [("key", ConstructorParameterDescription(self.key)), ("rules", ConstructorParameterDescription(self.rules))]) - } - } - public class Cons_updateQuickReplies: TypeConstructorDescription { - public var quickReplies: [Api.QuickReply] - public init(quickReplies: [Api.QuickReply]) { - self.quickReplies = quickReplies - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateQuickReplies", [("quickReplies", ConstructorParameterDescription(self.quickReplies))]) - } - } - public class Cons_updateQuickReplyMessage: TypeConstructorDescription { - public var message: Api.Message - public init(message: Api.Message) { - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateQuickReplyMessage", [("message", ConstructorParameterDescription(self.message))]) - } - } - public class Cons_updateReadChannelDiscussionInbox: TypeConstructorDescription { - public var flags: Int32 - public var channelId: Int64 - public var topMsgId: Int32 - public var readMaxId: Int32 - public var broadcastId: Int64? - public var broadcastPost: Int32? - public init(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) { - self.flags = flags - self.channelId = channelId - self.topMsgId = topMsgId - self.readMaxId = readMaxId - self.broadcastId = broadcastId - self.broadcastPost = broadcastPost - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadChannelDiscussionInbox", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId)), ("broadcastId", ConstructorParameterDescription(self.broadcastId)), ("broadcastPost", ConstructorParameterDescription(self.broadcastPost))]) - } - } - public class Cons_updateReadChannelDiscussionOutbox: TypeConstructorDescription { - public var channelId: Int64 - public var topMsgId: Int32 - public var readMaxId: Int32 - public init(channelId: Int64, topMsgId: Int32, readMaxId: Int32) { - self.channelId = channelId - self.topMsgId = topMsgId - self.readMaxId = readMaxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadChannelDiscussionOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) - } - } - public class Cons_updateReadChannelInbox: TypeConstructorDescription { - public var flags: Int32 - public var folderId: Int32? - public var channelId: Int64 - public var maxId: Int32 - public var stillUnreadCount: Int32 - public var pts: Int32 - public init(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32) { - self.flags = flags - self.folderId = folderId - self.channelId = channelId - self.maxId = maxId - self.stillUnreadCount = stillUnreadCount - self.pts = pts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadChannelInbox", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("stillUnreadCount", ConstructorParameterDescription(self.stillUnreadCount)), ("pts", ConstructorParameterDescription(self.pts))]) - } - } - public class Cons_updateReadChannelOutbox: TypeConstructorDescription { - public var channelId: Int64 - public var maxId: Int32 - public init(channelId: Int64, maxId: Int32) { - self.channelId = channelId - self.maxId = maxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadChannelOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId))]) - } - } - public class Cons_updateReadHistoryInbox: TypeConstructorDescription { - public var flags: Int32 - public var folderId: Int32? - public var peer: Api.Peer - public var topMsgId: Int32? - public var maxId: Int32 - public var stillUnreadCount: Int32 - public var pts: Int32 - public var ptsCount: Int32 - public init(flags: Int32, folderId: Int32?, peer: Api.Peer, topMsgId: Int32?, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) { - self.flags = flags - self.folderId = folderId - self.peer = peer - self.topMsgId = topMsgId - self.maxId = maxId - self.stillUnreadCount = stillUnreadCount - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadHistoryInbox", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("stillUnreadCount", ConstructorParameterDescription(self.stillUnreadCount)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateReadHistoryOutbox: TypeConstructorDescription { - public var peer: Api.Peer - public var maxId: Int32 - public var pts: Int32 - public var ptsCount: Int32 - public init(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) { - self.peer = peer - self.maxId = maxId - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadHistoryOutbox", [("peer", ConstructorParameterDescription(self.peer)), ("maxId", ConstructorParameterDescription(self.maxId)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateReadMessagesContents: TypeConstructorDescription { - public var flags: Int32 - public var messages: [Int32] - public var pts: Int32 - public var ptsCount: Int32 - public var date: Int32? - public init(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?) { - self.flags = flags - self.messages = messages - self.pts = pts - self.ptsCount = ptsCount - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadMessagesContents", [("flags", ConstructorParameterDescription(self.flags)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_updateReadMonoForumInbox: TypeConstructorDescription { - public var channelId: Int64 - public var savedPeerId: Api.Peer - public var readMaxId: Int32 - public init(channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) { - self.channelId = channelId - self.savedPeerId = savedPeerId - self.readMaxId = readMaxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadMonoForumInbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) - } - } - public class Cons_updateReadMonoForumOutbox: TypeConstructorDescription { - public var channelId: Int64 - public var savedPeerId: Api.Peer - public var readMaxId: Int32 - public init(channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) { - self.channelId = channelId - self.savedPeerId = savedPeerId - self.readMaxId = readMaxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadMonoForumOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) - } - } - public class Cons_updateReadStories: TypeConstructorDescription { - public var peer: Api.Peer - public var maxId: Int32 - public init(peer: Api.Peer, maxId: Int32) { - self.peer = peer - self.maxId = maxId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateReadStories", [("peer", ConstructorParameterDescription(self.peer)), ("maxId", ConstructorParameterDescription(self.maxId))]) - } - } - public class Cons_updateSavedDialogPinned: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.DialogPeer - public init(flags: Int32, peer: Api.DialogPeer) { - self.flags = flags - self.peer = peer - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateSavedDialogPinned", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer))]) - } - } - public class Cons_updateSentPhoneCode: TypeConstructorDescription { - public var sentCode: Api.auth.SentCode - public init(sentCode: Api.auth.SentCode) { - self.sentCode = sentCode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateSentPhoneCode", [("sentCode", ConstructorParameterDescription(self.sentCode))]) - } - } - public class Cons_updateSentStoryReaction: TypeConstructorDescription { - public var peer: Api.Peer - public var storyId: Int32 - public var reaction: Api.Reaction - public init(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) { - self.peer = peer - self.storyId = storyId - self.reaction = reaction - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateSentStoryReaction", [("peer", ConstructorParameterDescription(self.peer)), ("storyId", ConstructorParameterDescription(self.storyId)), ("reaction", ConstructorParameterDescription(self.reaction))]) - } - } - public class Cons_updateServiceNotification: TypeConstructorDescription { - public var flags: Int32 - public var inboxDate: Int32? - public var type: String - public var message: String - public var media: Api.MessageMedia - public var entities: [Api.MessageEntity] - public init(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) { - self.flags = flags - self.inboxDate = inboxDate - self.type = type - self.message = message - self.media = media - self.entities = entities - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateServiceNotification", [("flags", ConstructorParameterDescription(self.flags)), ("inboxDate", ConstructorParameterDescription(self.inboxDate)), ("type", ConstructorParameterDescription(self.type)), ("message", ConstructorParameterDescription(self.message)), ("media", ConstructorParameterDescription(self.media)), ("entities", ConstructorParameterDescription(self.entities))]) - } - } - public class Cons_updateSmsJob: TypeConstructorDescription { - public var jobId: String - public init(jobId: String) { - self.jobId = jobId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateSmsJob", [("jobId", ConstructorParameterDescription(self.jobId))]) - } - } - public class Cons_updateStarGiftAuctionState: TypeConstructorDescription { - public var giftId: Int64 - public var state: Api.StarGiftAuctionState - public init(giftId: Int64, state: Api.StarGiftAuctionState) { - self.giftId = giftId - self.state = state - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStarGiftAuctionState", [("giftId", ConstructorParameterDescription(self.giftId)), ("state", ConstructorParameterDescription(self.state))]) - } - } - public class Cons_updateStarGiftAuctionUserState: TypeConstructorDescription { - public var giftId: Int64 - public var userState: Api.StarGiftAuctionUserState - public init(giftId: Int64, userState: Api.StarGiftAuctionUserState) { - self.giftId = giftId - self.userState = userState - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStarGiftAuctionUserState", [("giftId", ConstructorParameterDescription(self.giftId)), ("userState", ConstructorParameterDescription(self.userState))]) - } - } - public class Cons_updateStarsBalance: TypeConstructorDescription { - public var balance: Api.StarsAmount - public init(balance: Api.StarsAmount) { - self.balance = balance - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStarsBalance", [("balance", ConstructorParameterDescription(self.balance))]) - } - } - public class Cons_updateStarsRevenueStatus: TypeConstructorDescription { - public var peer: Api.Peer - public var status: Api.StarsRevenueStatus - public init(peer: Api.Peer, status: Api.StarsRevenueStatus) { - self.peer = peer - self.status = status - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStarsRevenueStatus", [("peer", ConstructorParameterDescription(self.peer)), ("status", ConstructorParameterDescription(self.status))]) - } - } - public class Cons_updateStickerSets: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStickerSets", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_updateStickerSetsOrder: TypeConstructorDescription { - public var flags: Int32 - public var order: [Int64] - public init(flags: Int32, order: [Int64]) { - self.flags = flags - self.order = order - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStickerSetsOrder", [("flags", ConstructorParameterDescription(self.flags)), ("order", ConstructorParameterDescription(self.order))]) - } - } - public class Cons_updateStoriesStealthMode: TypeConstructorDescription { - public var stealthMode: Api.StoriesStealthMode - public init(stealthMode: Api.StoriesStealthMode) { - self.stealthMode = stealthMode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStoriesStealthMode", [("stealthMode", ConstructorParameterDescription(self.stealthMode))]) - } - } - public class Cons_updateStory: TypeConstructorDescription { - public var peer: Api.Peer - public var story: Api.StoryItem - public init(peer: Api.Peer, story: Api.StoryItem) { - self.peer = peer - self.story = story - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStory", [("peer", ConstructorParameterDescription(self.peer)), ("story", ConstructorParameterDescription(self.story))]) - } - } - public class Cons_updateStoryID: TypeConstructorDescription { - public var id: Int32 - public var randomId: Int64 - public init(id: Int32, randomId: Int64) { - self.id = id - self.randomId = randomId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateStoryID", [("id", ConstructorParameterDescription(self.id)), ("randomId", ConstructorParameterDescription(self.randomId))]) - } - } - public class Cons_updateTheme: TypeConstructorDescription { - public var theme: Api.Theme - public init(theme: Api.Theme) { - self.theme = theme - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateTheme", [("theme", ConstructorParameterDescription(self.theme))]) - } - } - public class Cons_updateTranscribedAudio: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var msgId: Int32 - public var transcriptionId: Int64 - public var text: String - public init(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String) { - self.flags = flags - self.peer = peer - self.msgId = msgId - self.transcriptionId = transcriptionId - self.text = text - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateTranscribedAudio", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("transcriptionId", ConstructorParameterDescription(self.transcriptionId)), ("text", ConstructorParameterDescription(self.text))]) - } - } - public class Cons_updateUser: TypeConstructorDescription { - public var userId: Int64 - public init(userId: Int64) { - self.userId = userId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUser", [("userId", ConstructorParameterDescription(self.userId))]) - } - } - public class Cons_updateUserEmojiStatus: TypeConstructorDescription { - public var userId: Int64 - public var emojiStatus: Api.EmojiStatus - public init(userId: Int64, emojiStatus: Api.EmojiStatus) { - self.userId = userId - self.emojiStatus = emojiStatus - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUserEmojiStatus", [("userId", ConstructorParameterDescription(self.userId)), ("emojiStatus", ConstructorParameterDescription(self.emojiStatus))]) - } - } - public class Cons_updateUserName: TypeConstructorDescription { - public var userId: Int64 - public var firstName: String - public var lastName: String - public var usernames: [Api.Username] - public init(userId: Int64, firstName: String, lastName: String, usernames: [Api.Username]) { - self.userId = userId - self.firstName = firstName - self.lastName = lastName - self.usernames = usernames - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUserName", [("userId", ConstructorParameterDescription(self.userId)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("usernames", ConstructorParameterDescription(self.usernames))]) - } - } - public class Cons_updateUserPhone: TypeConstructorDescription { - public var userId: Int64 - public var phone: String - public init(userId: Int64, phone: String) { - self.userId = userId - self.phone = phone - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUserPhone", [("userId", ConstructorParameterDescription(self.userId)), ("phone", ConstructorParameterDescription(self.phone))]) - } - } - public class Cons_updateUserStatus: TypeConstructorDescription { - public var userId: Int64 - public var status: Api.UserStatus - public init(userId: Int64, status: Api.UserStatus) { - self.userId = userId - self.status = status - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUserStatus", [("userId", ConstructorParameterDescription(self.userId)), ("status", ConstructorParameterDescription(self.status))]) - } - } - public class Cons_updateUserTyping: TypeConstructorDescription { - public var flags: Int32 - public var userId: Int64 - public var topMsgId: Int32? - public var action: Api.SendMessageAction - public init(flags: Int32, userId: Int64, topMsgId: Int32?, action: Api.SendMessageAction) { - self.flags = flags - self.userId = userId - self.topMsgId = topMsgId - self.action = action - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateUserTyping", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("action", ConstructorParameterDescription(self.action))]) - } - } - public class Cons_updateWebPage: TypeConstructorDescription { - public var webpage: Api.WebPage - public var pts: Int32 - public var ptsCount: Int32 - public init(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) { - self.webpage = webpage - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateWebPage", [("webpage", ConstructorParameterDescription(self.webpage)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - public class Cons_updateWebViewResultSent: TypeConstructorDescription { - public var queryId: Int64 - public init(queryId: Int64) { - self.queryId = queryId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateWebViewResultSent", [("queryId", ConstructorParameterDescription(self.queryId))]) - } - } - case updateAttachMenuBots - case updateAutoSaveSettings - case updateBotBusinessConnect(Cons_updateBotBusinessConnect) - case updateBotCallbackQuery(Cons_updateBotCallbackQuery) - case updateBotChatBoost(Cons_updateBotChatBoost) - case updateBotChatInviteRequester(Cons_updateBotChatInviteRequester) - case updateBotCommands(Cons_updateBotCommands) - case updateBotDeleteBusinessMessage(Cons_updateBotDeleteBusinessMessage) - case updateBotEditBusinessMessage(Cons_updateBotEditBusinessMessage) - case updateBotInlineQuery(Cons_updateBotInlineQuery) - case updateBotInlineSend(Cons_updateBotInlineSend) - case updateBotMenuButton(Cons_updateBotMenuButton) - case updateBotMessageReaction(Cons_updateBotMessageReaction) - case updateBotMessageReactions(Cons_updateBotMessageReactions) - case updateBotNewBusinessMessage(Cons_updateBotNewBusinessMessage) - case updateBotPrecheckoutQuery(Cons_updateBotPrecheckoutQuery) - case updateBotPurchasedPaidMedia(Cons_updateBotPurchasedPaidMedia) - case updateBotShippingQuery(Cons_updateBotShippingQuery) - case updateBotStopped(Cons_updateBotStopped) - case updateBotWebhookJSON(Cons_updateBotWebhookJSON) - case updateBotWebhookJSONQuery(Cons_updateBotWebhookJSONQuery) - case updateBusinessBotCallbackQuery(Cons_updateBusinessBotCallbackQuery) - case updateChannel(Cons_updateChannel) - case updateChannelAvailableMessages(Cons_updateChannelAvailableMessages) - case updateChannelMessageForwards(Cons_updateChannelMessageForwards) - case updateChannelMessageViews(Cons_updateChannelMessageViews) - case updateChannelParticipant(Cons_updateChannelParticipant) - case updateChannelReadMessagesContents(Cons_updateChannelReadMessagesContents) - case updateChannelTooLong(Cons_updateChannelTooLong) - case updateChannelUserTyping(Cons_updateChannelUserTyping) - case updateChannelViewForumAsMessages(Cons_updateChannelViewForumAsMessages) - case updateChannelWebPage(Cons_updateChannelWebPage) - case updateChat(Cons_updateChat) - case updateChatDefaultBannedRights(Cons_updateChatDefaultBannedRights) - case updateChatParticipant(Cons_updateChatParticipant) - case updateChatParticipantAdd(Cons_updateChatParticipantAdd) - case updateChatParticipantAdmin(Cons_updateChatParticipantAdmin) - case updateChatParticipantDelete(Cons_updateChatParticipantDelete) - case updateChatParticipantRank(Cons_updateChatParticipantRank) - case updateChatParticipants(Cons_updateChatParticipants) - case updateChatUserTyping(Cons_updateChatUserTyping) - case updateConfig - case updateContactsReset - case updateDcOptions(Cons_updateDcOptions) - case updateDeleteChannelMessages(Cons_updateDeleteChannelMessages) - case updateDeleteGroupCallMessages(Cons_updateDeleteGroupCallMessages) - case updateDeleteMessages(Cons_updateDeleteMessages) - case updateDeleteQuickReply(Cons_updateDeleteQuickReply) - case updateDeleteQuickReplyMessages(Cons_updateDeleteQuickReplyMessages) - case updateDeleteScheduledMessages(Cons_updateDeleteScheduledMessages) - case updateDialogFilter(Cons_updateDialogFilter) - case updateDialogFilterOrder(Cons_updateDialogFilterOrder) - case updateDialogFilters - case updateDialogPinned(Cons_updateDialogPinned) - case updateDialogUnreadMark(Cons_updateDialogUnreadMark) - case updateDraftMessage(Cons_updateDraftMessage) - case updateEditChannelMessage(Cons_updateEditChannelMessage) - case updateEditMessage(Cons_updateEditMessage) - case updateEmojiGameInfo(Cons_updateEmojiGameInfo) - case updateEncryptedChatTyping(Cons_updateEncryptedChatTyping) - case updateEncryptedMessagesRead(Cons_updateEncryptedMessagesRead) - case updateEncryption(Cons_updateEncryption) - case updateFavedStickers - case updateFolderPeers(Cons_updateFolderPeers) - case updateGeoLiveViewed(Cons_updateGeoLiveViewed) - case updateGroupCall(Cons_updateGroupCall) - case updateGroupCallChainBlocks(Cons_updateGroupCallChainBlocks) - case updateGroupCallConnection(Cons_updateGroupCallConnection) - case updateGroupCallEncryptedMessage(Cons_updateGroupCallEncryptedMessage) - case updateGroupCallMessage(Cons_updateGroupCallMessage) - case updateGroupCallParticipants(Cons_updateGroupCallParticipants) - case updateInlineBotCallbackQuery(Cons_updateInlineBotCallbackQuery) - case updateLangPack(Cons_updateLangPack) - case updateLangPackTooLong(Cons_updateLangPackTooLong) - case updateLoginToken - case updateManagedBot(Cons_updateManagedBot) - case updateMessageExtendedMedia(Cons_updateMessageExtendedMedia) - case updateMessageID(Cons_updateMessageID) - case updateMessagePoll(Cons_updateMessagePoll) - case updateMessagePollVote(Cons_updateMessagePollVote) - case updateMessageReactions(Cons_updateMessageReactions) - case updateMonoForumNoPaidException(Cons_updateMonoForumNoPaidException) - case updateMoveStickerSetToTop(Cons_updateMoveStickerSetToTop) - case updateNewAuthorization(Cons_updateNewAuthorization) - case updateNewChannelMessage(Cons_updateNewChannelMessage) - case updateNewEncryptedMessage(Cons_updateNewEncryptedMessage) - case updateNewMessage(Cons_updateNewMessage) - case updateNewQuickReply(Cons_updateNewQuickReply) - case updateNewScheduledMessage(Cons_updateNewScheduledMessage) - case updateNewStickerSet(Cons_updateNewStickerSet) - case updateNewStoryReaction(Cons_updateNewStoryReaction) - case updateNotifySettings(Cons_updateNotifySettings) - case updatePaidReactionPrivacy(Cons_updatePaidReactionPrivacy) - case updatePeerBlocked(Cons_updatePeerBlocked) - case updatePeerHistoryTTL(Cons_updatePeerHistoryTTL) - case updatePeerLocated(Cons_updatePeerLocated) - case updatePeerSettings(Cons_updatePeerSettings) - case updatePeerWallpaper(Cons_updatePeerWallpaper) - case updatePendingJoinRequests(Cons_updatePendingJoinRequests) - case updatePhoneCall(Cons_updatePhoneCall) - case updatePhoneCallSignalingData(Cons_updatePhoneCallSignalingData) - case updatePinnedChannelMessages(Cons_updatePinnedChannelMessages) - case updatePinnedDialogs(Cons_updatePinnedDialogs) - case updatePinnedForumTopic(Cons_updatePinnedForumTopic) - case updatePinnedForumTopics(Cons_updatePinnedForumTopics) - case updatePinnedMessages(Cons_updatePinnedMessages) - case updatePinnedSavedDialogs(Cons_updatePinnedSavedDialogs) - case updatePrivacy(Cons_updatePrivacy) - case updatePtsChanged - case updateQuickReplies(Cons_updateQuickReplies) - case updateQuickReplyMessage(Cons_updateQuickReplyMessage) - case updateReadChannelDiscussionInbox(Cons_updateReadChannelDiscussionInbox) - case updateReadChannelDiscussionOutbox(Cons_updateReadChannelDiscussionOutbox) - case updateReadChannelInbox(Cons_updateReadChannelInbox) - case updateReadChannelOutbox(Cons_updateReadChannelOutbox) - case updateReadFeaturedEmojiStickers - case updateReadFeaturedStickers - case updateReadHistoryInbox(Cons_updateReadHistoryInbox) - case updateReadHistoryOutbox(Cons_updateReadHistoryOutbox) - case updateReadMessagesContents(Cons_updateReadMessagesContents) - case updateReadMonoForumInbox(Cons_updateReadMonoForumInbox) - case updateReadMonoForumOutbox(Cons_updateReadMonoForumOutbox) - case updateReadStories(Cons_updateReadStories) - case updateRecentEmojiStatuses - case updateRecentReactions - case updateRecentStickers - case updateSavedDialogPinned(Cons_updateSavedDialogPinned) - case updateSavedGifs - case updateSavedReactionTags - case updateSavedRingtones - case updateSentPhoneCode(Cons_updateSentPhoneCode) - case updateSentStoryReaction(Cons_updateSentStoryReaction) - case updateServiceNotification(Cons_updateServiceNotification) - case updateSmsJob(Cons_updateSmsJob) - case updateStarGiftAuctionState(Cons_updateStarGiftAuctionState) - case updateStarGiftAuctionUserState(Cons_updateStarGiftAuctionUserState) - case updateStarGiftCraftFail - case updateStarsBalance(Cons_updateStarsBalance) - case updateStarsRevenueStatus(Cons_updateStarsRevenueStatus) - case updateStickerSets(Cons_updateStickerSets) - case updateStickerSetsOrder(Cons_updateStickerSetsOrder) - case updateStoriesStealthMode(Cons_updateStoriesStealthMode) - case updateStory(Cons_updateStory) - case updateStoryID(Cons_updateStoryID) - case updateTheme(Cons_updateTheme) - case updateTranscribedAudio(Cons_updateTranscribedAudio) - case updateUser(Cons_updateUser) - case updateUserEmojiStatus(Cons_updateUserEmojiStatus) - case updateUserName(Cons_updateUserName) - case updateUserPhone(Cons_updateUserPhone) - case updateUserStatus(Cons_updateUserStatus) - case updateUserTyping(Cons_updateUserTyping) - case updateWebPage(Cons_updateWebPage) - case updateWebViewResultSent(Cons_updateWebViewResultSent) + case statsDateRangeDays(Cons_statsDateRangeDays) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .updateAttachMenuBots: + case .statsDateRangeDays(let _data): if boxed { - buffer.appendInt32(397910539) + buffer.appendInt32(-1237848657) } - break - case .updateAutoSaveSettings: - if boxed { - buffer.appendInt32(-335171433) - } - break - case .updateBotBusinessConnect(let _data): - if boxed { - buffer.appendInt32(-1964652166) - } - _data.connection.serialize(buffer, true) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotCallbackQuery(let _data): - if boxed { - buffer.appendInt32(-1177566067) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.data!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.gameShortName!, buffer: buffer, boxed: false) - } - break - case .updateBotChatBoost(let _data): - if boxed { - buffer.appendInt32(-1873947492) - } - _data.peer.serialize(buffer, true) - _data.boost.serialize(buffer, true) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotChatInviteRequester(let _data): - if boxed { - buffer.appendInt32(299870598) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.about, buffer: buffer, boxed: false) - _data.invite.serialize(buffer, true) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotCommands(let _data): - if boxed { - buffer.appendInt32(1299263278) - } - _data.peer.serialize(buffer, true) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.commands.count)) - for item in _data.commands { - item.serialize(buffer, true) - } - break - case .updateBotDeleteBusinessMessage(let _data): - if boxed { - buffer.appendInt32(-1607821266) - } - serializeString(_data.connectionId, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotEditBusinessMessage(let _data): - if boxed { - buffer.appendInt32(132077692) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.connectionId, buffer: buffer, boxed: false) - _data.message.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.replyToMessage!.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotInlineQuery(let _data): - if boxed { - buffer.appendInt32(1232025500) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.query, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.geo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.peerType!.serialize(buffer, true) - } - serializeString(_data.offset, buffer: buffer, boxed: false) - break - case .updateBotInlineSend(let _data): - if boxed { - buffer.appendInt32(317794823) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.query, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.geo!.serialize(buffer, true) - } - serializeString(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.msgId!.serialize(buffer, true) - } - break - case .updateBotMenuButton(let _data): - if boxed { - buffer.appendInt32(347625491) - } - serializeInt64(_data.botId, buffer: buffer, boxed: false) - _data.button.serialize(buffer, true) - break - case .updateBotMessageReaction(let _data): - if boxed { - buffer.appendInt32(-1407069234) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.actor.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.oldReactions.count)) - for item in _data.oldReactions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newReactions.count)) - for item in _data.newReactions { - item.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotMessageReactions(let _data): - if boxed { - buffer.appendInt32(164329305) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.reactions.count)) - for item in _data.reactions { - item.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotNewBusinessMessage(let _data): - if boxed { - buffer.appendInt32(-1646578564) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.connectionId, buffer: buffer, boxed: false) - _data.message.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.replyToMessage!.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotPrecheckoutQuery(let _data): - if boxed { - buffer.appendInt32(-1934976362) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeBytes(_data.payload, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.info!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.shippingOptionId!, buffer: buffer, boxed: false) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - break - case .updateBotPurchasedPaidMedia(let _data): - if boxed { - buffer.appendInt32(675009298) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.payload, buffer: buffer, boxed: false) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotShippingQuery(let _data): - if boxed { - buffer.appendInt32(-1246823043) - } - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeBytes(_data.payload, buffer: buffer, boxed: false) - _data.shippingAddress.serialize(buffer, true) - break - case .updateBotStopped(let _data): - if boxed { - buffer.appendInt32(-997782967) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.stopped.serialize(buffer, true) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateBotWebhookJSON(let _data): - if boxed { - buffer.appendInt32(-2095595325) - } - _data.data.serialize(buffer, true) - break - case .updateBotWebhookJSONQuery(let _data): - if boxed { - buffer.appendInt32(-1684914010) - } - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - _data.data.serialize(buffer, true) - serializeInt32(_data.timeout, buffer: buffer, boxed: false) - break - case .updateBusinessBotCallbackQuery(let _data): - if boxed { - buffer.appendInt32(513998247) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.connectionId, buffer: buffer, boxed: false) - _data.message.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.replyToMessage!.serialize(buffer, true) - } - serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.data!, buffer: buffer, boxed: false) - } - break - case .updateChannel(let _data): - if boxed { - buffer.appendInt32(1666927625) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - break - case .updateChannelAvailableMessages(let _data): - if boxed { - buffer.appendInt32(-1304443240) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.availableMinId, buffer: buffer, boxed: false) - break - case .updateChannelMessageForwards(let _data): - if boxed { - buffer.appendInt32(-761649164) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.forwards, buffer: buffer, boxed: false) - break - case .updateChannelMessageViews(let _data): - if boxed { - buffer.appendInt32(-232346616) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.views, buffer: buffer, boxed: false) - break - case .updateChannelParticipant(let _data): - if boxed { - buffer.appendInt32(-1738720581) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.actorId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.prevParticipant!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.newParticipant!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.invite!.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateChannelReadMessagesContents(let _data): - if boxed { - buffer.appendInt32(636691703) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateChannelTooLong(let _data): - if boxed { - buffer.appendInt32(277713951) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.pts!, buffer: buffer, boxed: false) - } - break - case .updateChannelUserTyping(let _data): - if boxed { - buffer.appendInt32(-1937192669) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - _data.fromId.serialize(buffer, true) - _data.action.serialize(buffer, true) - break - case .updateChannelViewForumAsMessages(let _data): - if boxed { - buffer.appendInt32(129403168) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - _data.enabled.serialize(buffer, true) - break - case .updateChannelWebPage(let _data): - if boxed { - buffer.appendInt32(791390623) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - _data.webpage.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateChat(let _data): - if boxed { - buffer.appendInt32(-124097970) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - break - case .updateChatDefaultBannedRights(let _data): - if boxed { - buffer.appendInt32(1421875280) - } - _data.peer.serialize(buffer, true) - _data.defaultBannedRights.serialize(buffer, true) - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateChatParticipant(let _data): - if boxed { - buffer.appendInt32(-796432838) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.actorId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.prevParticipant!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.newParticipant!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.invite!.serialize(buffer, true) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateChatParticipantAdd(let _data): - if boxed { - buffer.appendInt32(1037718609) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt64(_data.inviterId, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateChatParticipantAdmin(let _data): - if boxed { - buffer.appendInt32(-674602590) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - _data.isAdmin.serialize(buffer, true) - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateChatParticipantDelete(let _data): - if boxed { - buffer.appendInt32(-483443337) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateChatParticipantRank(let _data): - if boxed { - buffer.appendInt32(-1115461703) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.rank, buffer: buffer, boxed: false) - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateChatParticipants(let _data): - if boxed { - buffer.appendInt32(125178264) - } - _data.participants.serialize(buffer, true) - break - case .updateChatUserTyping(let _data): - if boxed { - buffer.appendInt32(-2092401936) - } - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - _data.fromId.serialize(buffer, true) - _data.action.serialize(buffer, true) - break - case .updateConfig: - if boxed { - buffer.appendInt32(-1574314746) - } - break - case .updateContactsReset: - if boxed { - buffer.appendInt32(1887741886) - } - break - case .updateDcOptions(let _data): - if boxed { - buffer.appendInt32(-1906403213) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dcOptions.count)) - for item in _data.dcOptions { - item.serialize(buffer, true) - } - break - case .updateDeleteChannelMessages(let _data): - if boxed { - buffer.appendInt32(-1020437742) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateDeleteGroupCallMessages(let _data): - if boxed { - buffer.appendInt32(1048963372) - } - _data.call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDeleteMessages(let _data): - if boxed { - buffer.appendInt32(-1576161051) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateDeleteQuickReply(let _data): - if boxed { - buffer.appendInt32(1407644140) - } - serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) - break - case .updateDeleteQuickReplyMessages(let _data): - if boxed { - buffer.appendInt32(1450174413) - } - serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDeleteScheduledMessages(let _data): - if boxed { - buffer.appendInt32(-223929981) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sentMessages!.count)) - for item in _data.sentMessages! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - break - case .updateDialogFilter(let _data): - if boxed { - buffer.appendInt32(654302845) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.filter!.serialize(buffer, true) - } - break - case .updateDialogFilterOrder(let _data): - if boxed { - buffer.appendInt32(-1512627963) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.order.count)) - for item in _data.order { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .updateDialogFilters: - if boxed { - buffer.appendInt32(889491791) - } - break - case .updateDialogPinned(let _data): - if boxed { - buffer.appendInt32(1852826908) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.folderId!, buffer: buffer, boxed: false) - } - _data.peer.serialize(buffer, true) - break - case .updateDialogUnreadMark(let _data): - if boxed { - buffer.appendInt32(-1235684802) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - break - case .updateDraftMessage(let _data): - if boxed { - buffer.appendInt32(-302247650) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - _data.draft.serialize(buffer, true) - break - case .updateEditChannelMessage(let _data): - if boxed { - buffer.appendInt32(457133559) - } - _data.message.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateEditMessage(let _data): - if boxed { - buffer.appendInt32(-469536605) - } - _data.message.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateEmojiGameInfo(let _data): - if boxed { - buffer.appendInt32(-73640838) - } - _data.info.serialize(buffer, true) - break - case .updateEncryptedChatTyping(let _data): - if boxed { - buffer.appendInt32(386986326) - } - serializeInt32(_data.chatId, buffer: buffer, boxed: false) - break - case .updateEncryptedMessagesRead(let _data): - if boxed { - buffer.appendInt32(956179895) - } - serializeInt32(_data.chatId, buffer: buffer, boxed: false) + serializeInt32(_data.minDate, buffer: buffer, boxed: false) serializeInt32(_data.maxDate, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .updateEncryption(let _data): - if boxed { - buffer.appendInt32(-1264392051) - } - _data.chat.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .updateFavedStickers: - if boxed { - buffer.appendInt32(-451831443) - } - break - case .updateFolderPeers(let _data): - if boxed { - buffer.appendInt32(422972864) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.folderPeers.count)) - for item in _data.folderPeers { - item.serialize(buffer, true) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateGeoLiveViewed(let _data): - if boxed { - buffer.appendInt32(-2027964103) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - break - case .updateGroupCall(let _data): - if boxed { - buffer.appendInt32(-1658710304) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.peer!.serialize(buffer, true) - } - _data.call.serialize(buffer, true) - break - case .updateGroupCallChainBlocks(let _data): - if boxed { - buffer.appendInt32(-1535694705) - } - _data.call.serialize(buffer, true) - serializeInt32(_data.subChainId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocks.count)) - for item in _data.blocks { - serializeBytes(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.nextOffset, buffer: buffer, boxed: false) - break - case .updateGroupCallConnection(let _data): - if boxed { - buffer.appendInt32(192428418) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.params.serialize(buffer, true) - break - case .updateGroupCallEncryptedMessage(let _data): - if boxed { - buffer.appendInt32(-917002394) - } - _data.call.serialize(buffer, true) - _data.fromId.serialize(buffer, true) - serializeBytes(_data.encryptedMessage, buffer: buffer, boxed: false) - break - case .updateGroupCallMessage(let _data): - if boxed { - buffer.appendInt32(-667783411) - } - _data.call.serialize(buffer, true) - _data.message.serialize(buffer, true) - break - case .updateGroupCallParticipants(let _data): - if boxed { - buffer.appendInt32(-219423922) - } - _data.call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.participants.count)) - for item in _data.participants { - item.serialize(buffer, true) - } - serializeInt32(_data.version, buffer: buffer, boxed: false) - break - case .updateInlineBotCallbackQuery(let _data): - if boxed { - buffer.appendInt32(1763610706) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - _data.msgId.serialize(buffer, true) - serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.data!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.gameShortName!, buffer: buffer, boxed: false) - } - break - case .updateLangPack(let _data): - if boxed { - buffer.appendInt32(1442983757) - } - _data.difference.serialize(buffer, true) - break - case .updateLangPackTooLong(let _data): - if boxed { - buffer.appendInt32(1180041828) - } - serializeString(_data.langCode, buffer: buffer, boxed: false) - break - case .updateLoginToken: - if boxed { - buffer.appendInt32(1448076945) - } - break - case .updateManagedBot(let _data): - if boxed { - buffer.appendInt32(1216408986) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateMessageExtendedMedia(let _data): - if boxed { - buffer.appendInt32(-710666460) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.extendedMedia.count)) - for item in _data.extendedMedia { - item.serialize(buffer, true) - } - break - case .updateMessageID(let _data): - if boxed { - buffer.appendInt32(1318109142) - } - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.randomId, buffer: buffer, boxed: false) - break - case .updateMessagePoll(let _data): - if boxed { - buffer.appendInt32(-699641301) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.peer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.msgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - serializeInt64(_data.pollId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.poll!.serialize(buffer, true) - } - _data.results.serialize(buffer, true) - break - case .updateMessagePollVote(let _data): - if boxed { - buffer.appendInt32(1989799956) - } - serializeInt64(_data.pollId, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.options.count)) - for item in _data.options { - serializeBytes(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.positions.count)) - for item in _data.positions { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateMessageReactions(let _data): - if boxed { - buffer.appendInt32(506035194) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.savedPeerId!.serialize(buffer, true) - } - _data.reactions.serialize(buffer, true) - break - case .updateMonoForumNoPaidException(let _data): - if boxed { - buffer.appendInt32(-1618924792) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - _data.savedPeerId.serialize(buffer, true) - break - case .updateMoveStickerSetToTop(let _data): - if boxed { - buffer.appendInt32(-2030252155) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.stickerset, buffer: buffer, boxed: false) - break - case .updateNewAuthorization(let _data): - if boxed { - buffer.appendInt32(-1991136273) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.hash, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.date!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.device!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.location!, buffer: buffer, boxed: false) - } - break - case .updateNewChannelMessage(let _data): - if boxed { - buffer.appendInt32(1656358105) - } - _data.message.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateNewEncryptedMessage(let _data): - if boxed { - buffer.appendInt32(314359194) - } - _data.message.serialize(buffer, true) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - break - case .updateNewMessage(let _data): - if boxed { - buffer.appendInt32(522914557) - } - _data.message.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateNewQuickReply(let _data): - if boxed { - buffer.appendInt32(-180508905) - } - _data.quickReply.serialize(buffer, true) - break - case .updateNewScheduledMessage(let _data): - if boxed { - buffer.appendInt32(967122427) - } - _data.message.serialize(buffer, true) - break - case .updateNewStickerSet(let _data): - if boxed { - buffer.appendInt32(1753886890) - } - _data.stickerset.serialize(buffer, true) - break - case .updateNewStoryReaction(let _data): - if boxed { - buffer.appendInt32(405070859) - } - serializeInt32(_data.storyId, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - _data.reaction.serialize(buffer, true) - break - case .updateNotifySettings(let _data): - if boxed { - buffer.appendInt32(-1094555409) - } - _data.peer.serialize(buffer, true) - _data.notifySettings.serialize(buffer, true) - break - case .updatePaidReactionPrivacy(let _data): - if boxed { - buffer.appendInt32(-1955438642) - } - _data.`private`.serialize(buffer, true) - break - case .updatePeerBlocked(let _data): - if boxed { - buffer.appendInt32(-337610926) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peerId.serialize(buffer, true) - break - case .updatePeerHistoryTTL(let _data): - if boxed { - buffer.appendInt32(-1147422299) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - break - case .updatePeerLocated(let _data): - if boxed { - buffer.appendInt32(-1263546448) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - break - case .updatePeerSettings(let _data): - if boxed { - buffer.appendInt32(1786671974) - } - _data.peer.serialize(buffer, true) - _data.settings.serialize(buffer, true) - break - case .updatePeerWallpaper(let _data): - if boxed { - buffer.appendInt32(-1371598819) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.wallpaper!.serialize(buffer, true) - } - break - case .updatePendingJoinRequests(let _data): - if boxed { - buffer.appendInt32(1885586395) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.requestsPending, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentRequesters.count)) - for item in _data.recentRequesters { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .updatePhoneCall(let _data): - if boxed { - buffer.appendInt32(-1425052898) - } - _data.phoneCall.serialize(buffer, true) - break - case .updatePhoneCallSignalingData(let _data): - if boxed { - buffer.appendInt32(643940105) - } - serializeInt64(_data.phoneCallId, buffer: buffer, boxed: false) - serializeBytes(_data.data, buffer: buffer, boxed: false) - break - case .updatePinnedChannelMessages(let _data): - if boxed { - buffer.appendInt32(1538885128) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updatePinnedDialogs(let _data): - if boxed { - buffer.appendInt32(-99664734) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.folderId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.order!.count)) - for item in _data.order! { - item.serialize(buffer, true) - } - } - break - case .updatePinnedForumTopic(let _data): - if boxed { - buffer.appendInt32(1748708434) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.topicId, buffer: buffer, boxed: false) - break - case .updatePinnedForumTopics(let _data): - if boxed { - buffer.appendInt32(-554613808) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.order!.count)) - for item in _data.order! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - break - case .updatePinnedMessages(let _data): - if boxed { - buffer.appendInt32(-309990731) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updatePinnedSavedDialogs(let _data): - if boxed { - buffer.appendInt32(1751942566) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.order!.count)) - for item in _data.order! { - item.serialize(buffer, true) - } - } - break - case .updatePrivacy(let _data): - if boxed { - buffer.appendInt32(-298113238) - } - _data.key.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rules.count)) - for item in _data.rules { - item.serialize(buffer, true) - } - break - case .updatePtsChanged: - if boxed { - buffer.appendInt32(861169551) - } - break - case .updateQuickReplies(let _data): - if boxed { - buffer.appendInt32(-112784718) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.quickReplies.count)) - for item in _data.quickReplies { - item.serialize(buffer, true) - } - break - case .updateQuickReplyMessage(let _data): - if boxed { - buffer.appendInt32(1040518415) - } - _data.message.serialize(buffer, true) - break - case .updateReadChannelDiscussionInbox(let _data): - if boxed { - buffer.appendInt32(-693004986) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) - serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.broadcastId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.broadcastPost!, buffer: buffer, boxed: false) - } - break - case .updateReadChannelDiscussionOutbox(let _data): - if boxed { - buffer.appendInt32(1767677564) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) - serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) - break - case .updateReadChannelInbox(let _data): - if boxed { - buffer.appendInt32(-1842450928) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.folderId!, buffer: buffer, boxed: false) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - serializeInt32(_data.stillUnreadCount, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - break - case .updateReadChannelOutbox(let _data): - if boxed { - buffer.appendInt32(-1218471511) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - break - case .updateReadFeaturedEmojiStickers: - if boxed { - buffer.appendInt32(-78886548) - } - break - case .updateReadFeaturedStickers: - if boxed { - buffer.appendInt32(1461528386) - } - break - case .updateReadHistoryInbox(let _data): - if boxed { - buffer.appendInt32(-1635468135) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.folderId!, buffer: buffer, boxed: false) - } - _data.peer.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - serializeInt32(_data.stillUnreadCount, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateReadHistoryOutbox(let _data): - if boxed { - buffer.appendInt32(791617983) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateReadMessagesContents(let _data): - if boxed { - buffer.appendInt32(-131960447) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.date!, buffer: buffer, boxed: false) - } - break - case .updateReadMonoForumInbox(let _data): - if boxed { - buffer.appendInt32(2008081266) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - _data.savedPeerId.serialize(buffer, true) - serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) - break - case .updateReadMonoForumOutbox(let _data): - if boxed { - buffer.appendInt32(-1532521610) - } - serializeInt64(_data.channelId, buffer: buffer, boxed: false) - _data.savedPeerId.serialize(buffer, true) - serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) - break - case .updateReadStories(let _data): - if boxed { - buffer.appendInt32(-145845461) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.maxId, buffer: buffer, boxed: false) - break - case .updateRecentEmojiStatuses: - if boxed { - buffer.appendInt32(821314523) - } - break - case .updateRecentReactions: - if boxed { - buffer.appendInt32(1870160884) - } - break - case .updateRecentStickers: - if boxed { - buffer.appendInt32(-1706939360) - } - break - case .updateSavedDialogPinned(let _data): - if boxed { - buffer.appendInt32(-1364222348) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - break - case .updateSavedGifs: - if boxed { - buffer.appendInt32(-1821035490) - } - break - case .updateSavedReactionTags: - if boxed { - buffer.appendInt32(969307186) - } - break - case .updateSavedRingtones: - if boxed { - buffer.appendInt32(1960361625) - } - break - case .updateSentPhoneCode(let _data): - if boxed { - buffer.appendInt32(1347068303) - } - _data.sentCode.serialize(buffer, true) - break - case .updateSentStoryReaction(let _data): - if boxed { - buffer.appendInt32(2103604867) - } - _data.peer.serialize(buffer, true) - serializeInt32(_data.storyId, buffer: buffer, boxed: false) - _data.reaction.serialize(buffer, true) - break - case .updateServiceNotification(let _data): - if boxed { - buffer.appendInt32(-337352679) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.inboxDate!, buffer: buffer, boxed: false) - } - serializeString(_data.type, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - _data.media.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities.count)) - for item in _data.entities { - item.serialize(buffer, true) - } - break - case .updateSmsJob(let _data): - if boxed { - buffer.appendInt32(-245208620) - } - serializeString(_data.jobId, buffer: buffer, boxed: false) - break - case .updateStarGiftAuctionState(let _data): - if boxed { - buffer.appendInt32(1222788802) - } - serializeInt64(_data.giftId, buffer: buffer, boxed: false) - _data.state.serialize(buffer, true) - break - case .updateStarGiftAuctionUserState(let _data): - if boxed { - buffer.appendInt32(-598150370) - } - serializeInt64(_data.giftId, buffer: buffer, boxed: false) - _data.userState.serialize(buffer, true) - break - case .updateStarGiftCraftFail: - if boxed { - buffer.appendInt32(-1408818108) - } - break - case .updateStarsBalance(let _data): - if boxed { - buffer.appendInt32(1317053305) - } - _data.balance.serialize(buffer, true) - break - case .updateStarsRevenueStatus(let _data): - if boxed { - buffer.appendInt32(-1518030823) - } - _data.peer.serialize(buffer, true) - _data.status.serialize(buffer, true) - break - case .updateStickerSets(let _data): - if boxed { - buffer.appendInt32(834816008) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .updateStickerSetsOrder(let _data): - if boxed { - buffer.appendInt32(196268545) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.order.count)) - for item in _data.order { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .updateStoriesStealthMode(let _data): - if boxed { - buffer.appendInt32(738741697) - } - _data.stealthMode.serialize(buffer, true) - break - case .updateStory(let _data): - if boxed { - buffer.appendInt32(1974712216) - } - _data.peer.serialize(buffer, true) - _data.story.serialize(buffer, true) - break - case .updateStoryID(let _data): - if boxed { - buffer.appendInt32(468923833) - } - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.randomId, buffer: buffer, boxed: false) - break - case .updateTheme(let _data): - if boxed { - buffer.appendInt32(-2112423005) - } - _data.theme.serialize(buffer, true) - break - case .updateTranscribedAudio(let _data): - if boxed { - buffer.appendInt32(8703322) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeInt32(_data.msgId, buffer: buffer, boxed: false) - serializeInt64(_data.transcriptionId, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - break - case .updateUser(let _data): - if boxed { - buffer.appendInt32(542282808) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - break - case .updateUserEmojiStatus(let _data): - if boxed { - buffer.appendInt32(674706841) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - _data.emojiStatus.serialize(buffer, true) - break - case .updateUserName(let _data): - if boxed { - buffer.appendInt32(-1484486364) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.firstName, buffer: buffer, boxed: false) - serializeString(_data.lastName, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.usernames.count)) - for item in _data.usernames { - item.serialize(buffer, true) - } - break - case .updateUserPhone(let _data): - if boxed { - buffer.appendInt32(88680979) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.phone, buffer: buffer, boxed: false) - break - case .updateUserStatus(let _data): - if boxed { - buffer.appendInt32(-440534818) - } - serializeInt64(_data.userId, buffer: buffer, boxed: false) - _data.status.serialize(buffer, true) - break - case .updateUserTyping(let _data): - if boxed { - buffer.appendInt32(706199388) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) - } - _data.action.serialize(buffer, true) - break - case .updateWebPage(let _data): - if boxed { - buffer.appendInt32(2139689491) - } - _data.webpage.serialize(buffer, true) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - case .updateWebViewResultSent(let _data): - if boxed { - buffer.appendInt32(361936797) - } - serializeInt64(_data.queryId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .updateAttachMenuBots: - return ("updateAttachMenuBots", []) - case .updateAutoSaveSettings: - return ("updateAutoSaveSettings", []) - case .updateBotBusinessConnect(let _data): - return ("updateBotBusinessConnect", [("connection", ConstructorParameterDescription(_data.connection)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotCallbackQuery(let _data): - return ("updateBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data)), ("gameShortName", ConstructorParameterDescription(_data.gameShortName))]) - case .updateBotChatBoost(let _data): - return ("updateBotChatBoost", [("peer", ConstructorParameterDescription(_data.peer)), ("boost", ConstructorParameterDescription(_data.boost)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotChatInviteRequester(let _data): - return ("updateBotChatInviteRequester", [("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("userId", ConstructorParameterDescription(_data.userId)), ("about", ConstructorParameterDescription(_data.about)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotCommands(let _data): - return ("updateBotCommands", [("peer", ConstructorParameterDescription(_data.peer)), ("botId", ConstructorParameterDescription(_data.botId)), ("commands", ConstructorParameterDescription(_data.commands))]) - case .updateBotDeleteBusinessMessage(let _data): - return ("updateBotDeleteBusinessMessage", [("connectionId", ConstructorParameterDescription(_data.connectionId)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotEditBusinessMessage(let _data): - return ("updateBotEditBusinessMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotInlineQuery(let _data): - return ("updateBotInlineQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("query", ConstructorParameterDescription(_data.query)), ("geo", ConstructorParameterDescription(_data.geo)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("offset", ConstructorParameterDescription(_data.offset))]) - case .updateBotInlineSend(let _data): - return ("updateBotInlineSend", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("query", ConstructorParameterDescription(_data.query)), ("geo", ConstructorParameterDescription(_data.geo)), ("id", ConstructorParameterDescription(_data.id)), ("msgId", ConstructorParameterDescription(_data.msgId))]) - case .updateBotMenuButton(let _data): - return ("updateBotMenuButton", [("botId", ConstructorParameterDescription(_data.botId)), ("button", ConstructorParameterDescription(_data.button))]) - case .updateBotMessageReaction(let _data): - return ("updateBotMessageReaction", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("actor", ConstructorParameterDescription(_data.actor)), ("oldReactions", ConstructorParameterDescription(_data.oldReactions)), ("newReactions", ConstructorParameterDescription(_data.newReactions)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotMessageReactions(let _data): - return ("updateBotMessageReactions", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotNewBusinessMessage(let _data): - return ("updateBotNewBusinessMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotPrecheckoutQuery(let _data): - return ("updateBotPrecheckoutQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("info", ConstructorParameterDescription(_data.info)), ("shippingOptionId", ConstructorParameterDescription(_data.shippingOptionId)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount))]) - case .updateBotPurchasedPaidMedia(let _data): - return ("updateBotPurchasedPaidMedia", [("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotShippingQuery(let _data): - return ("updateBotShippingQuery", [("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("shippingAddress", ConstructorParameterDescription(_data.shippingAddress))]) - case .updateBotStopped(let _data): - return ("updateBotStopped", [("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date)), ("stopped", ConstructorParameterDescription(_data.stopped)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateBotWebhookJSON(let _data): - return ("updateBotWebhookJSON", [("data", ConstructorParameterDescription(_data.data))]) - case .updateBotWebhookJSONQuery(let _data): - return ("updateBotWebhookJSONQuery", [("queryId", ConstructorParameterDescription(_data.queryId)), ("data", ConstructorParameterDescription(_data.data)), ("timeout", ConstructorParameterDescription(_data.timeout))]) - case .updateBusinessBotCallbackQuery(let _data): - return ("updateBusinessBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data))]) - case .updateChannel(let _data): - return ("updateChannel", [("channelId", ConstructorParameterDescription(_data.channelId))]) - case .updateChannelAvailableMessages(let _data): - return ("updateChannelAvailableMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("availableMinId", ConstructorParameterDescription(_data.availableMinId))]) - case .updateChannelMessageForwards(let _data): - return ("updateChannelMessageForwards", [("channelId", ConstructorParameterDescription(_data.channelId)), ("id", ConstructorParameterDescription(_data.id)), ("forwards", ConstructorParameterDescription(_data.forwards))]) - case .updateChannelMessageViews(let _data): - return ("updateChannelMessageViews", [("channelId", ConstructorParameterDescription(_data.channelId)), ("id", ConstructorParameterDescription(_data.id)), ("views", ConstructorParameterDescription(_data.views))]) - case .updateChannelParticipant(let _data): - return ("updateChannelParticipant", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("date", ConstructorParameterDescription(_data.date)), ("actorId", ConstructorParameterDescription(_data.actorId)), ("userId", ConstructorParameterDescription(_data.userId)), ("prevParticipant", ConstructorParameterDescription(_data.prevParticipant)), ("newParticipant", ConstructorParameterDescription(_data.newParticipant)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateChannelReadMessagesContents(let _data): - return ("updateChannelReadMessagesContents", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("messages", ConstructorParameterDescription(_data.messages))]) - case .updateChannelTooLong(let _data): - return ("updateChannelTooLong", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("pts", ConstructorParameterDescription(_data.pts))]) - case .updateChannelUserTyping(let _data): - return ("updateChannelUserTyping", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("action", ConstructorParameterDescription(_data.action))]) - case .updateChannelViewForumAsMessages(let _data): - return ("updateChannelViewForumAsMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("enabled", ConstructorParameterDescription(_data.enabled))]) - case .updateChannelWebPage(let _data): - return ("updateChannelWebPage", [("channelId", ConstructorParameterDescription(_data.channelId)), ("webpage", ConstructorParameterDescription(_data.webpage)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateChat(let _data): - return ("updateChat", [("chatId", ConstructorParameterDescription(_data.chatId))]) - case .updateChatDefaultBannedRights(let _data): - return ("updateChatDefaultBannedRights", [("peer", ConstructorParameterDescription(_data.peer)), ("defaultBannedRights", ConstructorParameterDescription(_data.defaultBannedRights)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateChatParticipant(let _data): - return ("updateChatParticipant", [("flags", ConstructorParameterDescription(_data.flags)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("date", ConstructorParameterDescription(_data.date)), ("actorId", ConstructorParameterDescription(_data.actorId)), ("userId", ConstructorParameterDescription(_data.userId)), ("prevParticipant", ConstructorParameterDescription(_data.prevParticipant)), ("newParticipant", ConstructorParameterDescription(_data.newParticipant)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateChatParticipantAdd(let _data): - return ("updateChatParticipantAdd", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("inviterId", ConstructorParameterDescription(_data.inviterId)), ("date", ConstructorParameterDescription(_data.date)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateChatParticipantAdmin(let _data): - return ("updateChatParticipantAdmin", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("isAdmin", ConstructorParameterDescription(_data.isAdmin)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateChatParticipantDelete(let _data): - return ("updateChatParticipantDelete", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateChatParticipantRank(let _data): - return ("updateChatParticipantRank", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("rank", ConstructorParameterDescription(_data.rank)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateChatParticipants(let _data): - return ("updateChatParticipants", [("participants", ConstructorParameterDescription(_data.participants))]) - case .updateChatUserTyping(let _data): - return ("updateChatUserTyping", [("chatId", ConstructorParameterDescription(_data.chatId)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("action", ConstructorParameterDescription(_data.action))]) - case .updateConfig: - return ("updateConfig", []) - case .updateContactsReset: - return ("updateContactsReset", []) - case .updateDcOptions(let _data): - return ("updateDcOptions", [("dcOptions", ConstructorParameterDescription(_data.dcOptions))]) - case .updateDeleteChannelMessages(let _data): - return ("updateDeleteChannelMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateDeleteGroupCallMessages(let _data): - return ("updateDeleteGroupCallMessages", [("call", ConstructorParameterDescription(_data.call)), ("messages", ConstructorParameterDescription(_data.messages))]) - case .updateDeleteMessages(let _data): - return ("updateDeleteMessages", [("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateDeleteQuickReply(let _data): - return ("updateDeleteQuickReply", [("shortcutId", ConstructorParameterDescription(_data.shortcutId))]) - case .updateDeleteQuickReplyMessages(let _data): - return ("updateDeleteQuickReplyMessages", [("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("messages", ConstructorParameterDescription(_data.messages))]) - case .updateDeleteScheduledMessages(let _data): - return ("updateDeleteScheduledMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("sentMessages", ConstructorParameterDescription(_data.sentMessages))]) - case .updateDialogFilter(let _data): - return ("updateDialogFilter", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("filter", ConstructorParameterDescription(_data.filter))]) - case .updateDialogFilterOrder(let _data): - return ("updateDialogFilterOrder", [("order", ConstructorParameterDescription(_data.order))]) - case .updateDialogFilters: - return ("updateDialogFilters", []) - case .updateDialogPinned(let _data): - return ("updateDialogPinned", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("peer", ConstructorParameterDescription(_data.peer))]) - case .updateDialogUnreadMark(let _data): - return ("updateDialogUnreadMark", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId))]) - case .updateDraftMessage(let _data): - return ("updateDraftMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("draft", ConstructorParameterDescription(_data.draft))]) - case .updateEditChannelMessage(let _data): - return ("updateEditChannelMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateEditMessage(let _data): - return ("updateEditMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateEmojiGameInfo(let _data): - return ("updateEmojiGameInfo", [("info", ConstructorParameterDescription(_data.info))]) - case .updateEncryptedChatTyping(let _data): - return ("updateEncryptedChatTyping", [("chatId", ConstructorParameterDescription(_data.chatId))]) - case .updateEncryptedMessagesRead(let _data): - return ("updateEncryptedMessagesRead", [("chatId", ConstructorParameterDescription(_data.chatId)), ("maxDate", ConstructorParameterDescription(_data.maxDate)), ("date", ConstructorParameterDescription(_data.date))]) - case .updateEncryption(let _data): - return ("updateEncryption", [("chat", ConstructorParameterDescription(_data.chat)), ("date", ConstructorParameterDescription(_data.date))]) - case .updateFavedStickers: - return ("updateFavedStickers", []) - case .updateFolderPeers(let _data): - return ("updateFolderPeers", [("folderPeers", ConstructorParameterDescription(_data.folderPeers)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateGeoLiveViewed(let _data): - return ("updateGeoLiveViewed", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId))]) - case .updateGroupCall(let _data): - return ("updateGroupCall", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("call", ConstructorParameterDescription(_data.call))]) - case .updateGroupCallChainBlocks(let _data): - return ("updateGroupCallChainBlocks", [("call", ConstructorParameterDescription(_data.call)), ("subChainId", ConstructorParameterDescription(_data.subChainId)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) - case .updateGroupCallConnection(let _data): - return ("updateGroupCallConnection", [("flags", ConstructorParameterDescription(_data.flags)), ("params", ConstructorParameterDescription(_data.params))]) - case .updateGroupCallEncryptedMessage(let _data): - return ("updateGroupCallEncryptedMessage", [("call", ConstructorParameterDescription(_data.call)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("encryptedMessage", ConstructorParameterDescription(_data.encryptedMessage))]) - case .updateGroupCallMessage(let _data): - return ("updateGroupCallMessage", [("call", ConstructorParameterDescription(_data.call)), ("message", ConstructorParameterDescription(_data.message))]) - case .updateGroupCallParticipants(let _data): - return ("updateGroupCallParticipants", [("call", ConstructorParameterDescription(_data.call)), ("participants", ConstructorParameterDescription(_data.participants)), ("version", ConstructorParameterDescription(_data.version))]) - case .updateInlineBotCallbackQuery(let _data): - return ("updateInlineBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data)), ("gameShortName", ConstructorParameterDescription(_data.gameShortName))]) - case .updateLangPack(let _data): - return ("updateLangPack", [("difference", ConstructorParameterDescription(_data.difference))]) - case .updateLangPackTooLong(let _data): - return ("updateLangPackTooLong", [("langCode", ConstructorParameterDescription(_data.langCode))]) - case .updateLoginToken: - return ("updateLoginToken", []) - case .updateManagedBot(let _data): - return ("updateManagedBot", [("userId", ConstructorParameterDescription(_data.userId)), ("botId", ConstructorParameterDescription(_data.botId)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateMessageExtendedMedia(let _data): - return ("updateMessageExtendedMedia", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) - case .updateMessageID(let _data): - return ("updateMessageID", [("id", ConstructorParameterDescription(_data.id)), ("randomId", ConstructorParameterDescription(_data.randomId))]) - case .updateMessagePoll(let _data): - return ("updateMessagePoll", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("pollId", ConstructorParameterDescription(_data.pollId)), ("poll", ConstructorParameterDescription(_data.poll)), ("results", ConstructorParameterDescription(_data.results))]) - case .updateMessagePollVote(let _data): - return ("updateMessagePollVote", [("pollId", ConstructorParameterDescription(_data.pollId)), ("peer", ConstructorParameterDescription(_data.peer)), ("options", ConstructorParameterDescription(_data.options)), ("positions", ConstructorParameterDescription(_data.positions)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateMessageReactions(let _data): - return ("updateMessageReactions", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("reactions", ConstructorParameterDescription(_data.reactions))]) - case .updateMonoForumNoPaidException(let _data): - return ("updateMonoForumNoPaidException", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId))]) - case .updateMoveStickerSetToTop(let _data): - return ("updateMoveStickerSetToTop", [("flags", ConstructorParameterDescription(_data.flags)), ("stickerset", ConstructorParameterDescription(_data.stickerset))]) - case .updateNewAuthorization(let _data): - return ("updateNewAuthorization", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("date", ConstructorParameterDescription(_data.date)), ("device", ConstructorParameterDescription(_data.device)), ("location", ConstructorParameterDescription(_data.location))]) - case .updateNewChannelMessage(let _data): - return ("updateNewChannelMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateNewEncryptedMessage(let _data): - return ("updateNewEncryptedMessage", [("message", ConstructorParameterDescription(_data.message)), ("qts", ConstructorParameterDescription(_data.qts))]) - case .updateNewMessage(let _data): - return ("updateNewMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateNewQuickReply(let _data): - return ("updateNewQuickReply", [("quickReply", ConstructorParameterDescription(_data.quickReply))]) - case .updateNewScheduledMessage(let _data): - return ("updateNewScheduledMessage", [("message", ConstructorParameterDescription(_data.message))]) - case .updateNewStickerSet(let _data): - return ("updateNewStickerSet", [("stickerset", ConstructorParameterDescription(_data.stickerset))]) - case .updateNewStoryReaction(let _data): - return ("updateNewStoryReaction", [("storyId", ConstructorParameterDescription(_data.storyId)), ("peer", ConstructorParameterDescription(_data.peer)), ("reaction", ConstructorParameterDescription(_data.reaction))]) - case .updateNotifySettings(let _data): - return ("updateNotifySettings", [("peer", ConstructorParameterDescription(_data.peer)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings))]) - case .updatePaidReactionPrivacy(let _data): - return ("updatePaidReactionPrivacy", [("`private`", ConstructorParameterDescription(_data.`private`))]) - case .updatePeerBlocked(let _data): - return ("updatePeerBlocked", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId))]) - case .updatePeerHistoryTTL(let _data): - return ("updatePeerHistoryTTL", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) - case .updatePeerLocated(let _data): - return ("updatePeerLocated", [("peers", ConstructorParameterDescription(_data.peers))]) - case .updatePeerSettings(let _data): - return ("updatePeerSettings", [("peer", ConstructorParameterDescription(_data.peer)), ("settings", ConstructorParameterDescription(_data.settings))]) - case .updatePeerWallpaper(let _data): - return ("updatePeerWallpaper", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) - case .updatePendingJoinRequests(let _data): - return ("updatePendingJoinRequests", [("peer", ConstructorParameterDescription(_data.peer)), ("requestsPending", ConstructorParameterDescription(_data.requestsPending)), ("recentRequesters", ConstructorParameterDescription(_data.recentRequesters))]) - case .updatePhoneCall(let _data): - return ("updatePhoneCall", [("phoneCall", ConstructorParameterDescription(_data.phoneCall))]) - case .updatePhoneCallSignalingData(let _data): - return ("updatePhoneCallSignalingData", [("phoneCallId", ConstructorParameterDescription(_data.phoneCallId)), ("data", ConstructorParameterDescription(_data.data))]) - case .updatePinnedChannelMessages(let _data): - return ("updatePinnedChannelMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updatePinnedDialogs(let _data): - return ("updatePinnedDialogs", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("order", ConstructorParameterDescription(_data.order))]) - case .updatePinnedForumTopic(let _data): - return ("updatePinnedForumTopic", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topicId", ConstructorParameterDescription(_data.topicId))]) - case .updatePinnedForumTopics(let _data): - return ("updatePinnedForumTopics", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("order", ConstructorParameterDescription(_data.order))]) - case .updatePinnedMessages(let _data): - return ("updatePinnedMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updatePinnedSavedDialogs(let _data): - return ("updatePinnedSavedDialogs", [("flags", ConstructorParameterDescription(_data.flags)), ("order", ConstructorParameterDescription(_data.order))]) - case .updatePrivacy(let _data): - return ("updatePrivacy", [("key", ConstructorParameterDescription(_data.key)), ("rules", ConstructorParameterDescription(_data.rules))]) - case .updatePtsChanged: - return ("updatePtsChanged", []) - case .updateQuickReplies(let _data): - return ("updateQuickReplies", [("quickReplies", ConstructorParameterDescription(_data.quickReplies))]) - case .updateQuickReplyMessage(let _data): - return ("updateQuickReplyMessage", [("message", ConstructorParameterDescription(_data.message))]) - case .updateReadChannelDiscussionInbox(let _data): - return ("updateReadChannelDiscussionInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId)), ("broadcastId", ConstructorParameterDescription(_data.broadcastId)), ("broadcastPost", ConstructorParameterDescription(_data.broadcastPost))]) - case .updateReadChannelDiscussionOutbox(let _data): - return ("updateReadChannelDiscussionOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) - case .updateReadChannelInbox(let _data): - return ("updateReadChannelInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("stillUnreadCount", ConstructorParameterDescription(_data.stillUnreadCount)), ("pts", ConstructorParameterDescription(_data.pts))]) - case .updateReadChannelOutbox(let _data): - return ("updateReadChannelOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId))]) - case .updateReadFeaturedEmojiStickers: - return ("updateReadFeaturedEmojiStickers", []) - case .updateReadFeaturedStickers: - return ("updateReadFeaturedStickers", []) - case .updateReadHistoryInbox(let _data): - return ("updateReadHistoryInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("stillUnreadCount", ConstructorParameterDescription(_data.stillUnreadCount)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateReadHistoryOutbox(let _data): - return ("updateReadHistoryOutbox", [("peer", ConstructorParameterDescription(_data.peer)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateReadMessagesContents(let _data): - return ("updateReadMessagesContents", [("flags", ConstructorParameterDescription(_data.flags)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date))]) - case .updateReadMonoForumInbox(let _data): - return ("updateReadMonoForumInbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) - case .updateReadMonoForumOutbox(let _data): - return ("updateReadMonoForumOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) - case .updateReadStories(let _data): - return ("updateReadStories", [("peer", ConstructorParameterDescription(_data.peer)), ("maxId", ConstructorParameterDescription(_data.maxId))]) - case .updateRecentEmojiStatuses: - return ("updateRecentEmojiStatuses", []) - case .updateRecentReactions: - return ("updateRecentReactions", []) - case .updateRecentStickers: - return ("updateRecentStickers", []) - case .updateSavedDialogPinned(let _data): - return ("updateSavedDialogPinned", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer))]) - case .updateSavedGifs: - return ("updateSavedGifs", []) - case .updateSavedReactionTags: - return ("updateSavedReactionTags", []) - case .updateSavedRingtones: - return ("updateSavedRingtones", []) - case .updateSentPhoneCode(let _data): - return ("updateSentPhoneCode", [("sentCode", ConstructorParameterDescription(_data.sentCode))]) - case .updateSentStoryReaction(let _data): - return ("updateSentStoryReaction", [("peer", ConstructorParameterDescription(_data.peer)), ("storyId", ConstructorParameterDescription(_data.storyId)), ("reaction", ConstructorParameterDescription(_data.reaction))]) - case .updateServiceNotification(let _data): - return ("updateServiceNotification", [("flags", ConstructorParameterDescription(_data.flags)), ("inboxDate", ConstructorParameterDescription(_data.inboxDate)), ("type", ConstructorParameterDescription(_data.type)), ("message", ConstructorParameterDescription(_data.message)), ("media", ConstructorParameterDescription(_data.media)), ("entities", ConstructorParameterDescription(_data.entities))]) - case .updateSmsJob(let _data): - return ("updateSmsJob", [("jobId", ConstructorParameterDescription(_data.jobId))]) - case .updateStarGiftAuctionState(let _data): - return ("updateStarGiftAuctionState", [("giftId", ConstructorParameterDescription(_data.giftId)), ("state", ConstructorParameterDescription(_data.state))]) - case .updateStarGiftAuctionUserState(let _data): - return ("updateStarGiftAuctionUserState", [("giftId", ConstructorParameterDescription(_data.giftId)), ("userState", ConstructorParameterDescription(_data.userState))]) - case .updateStarGiftCraftFail: - return ("updateStarGiftCraftFail", []) - case .updateStarsBalance(let _data): - return ("updateStarsBalance", [("balance", ConstructorParameterDescription(_data.balance))]) - case .updateStarsRevenueStatus(let _data): - return ("updateStarsRevenueStatus", [("peer", ConstructorParameterDescription(_data.peer)), ("status", ConstructorParameterDescription(_data.status))]) - case .updateStickerSets(let _data): - return ("updateStickerSets", [("flags", ConstructorParameterDescription(_data.flags))]) - case .updateStickerSetsOrder(let _data): - return ("updateStickerSetsOrder", [("flags", ConstructorParameterDescription(_data.flags)), ("order", ConstructorParameterDescription(_data.order))]) - case .updateStoriesStealthMode(let _data): - return ("updateStoriesStealthMode", [("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) - case .updateStory(let _data): - return ("updateStory", [("peer", ConstructorParameterDescription(_data.peer)), ("story", ConstructorParameterDescription(_data.story))]) - case .updateStoryID(let _data): - return ("updateStoryID", [("id", ConstructorParameterDescription(_data.id)), ("randomId", ConstructorParameterDescription(_data.randomId))]) - case .updateTheme(let _data): - return ("updateTheme", [("theme", ConstructorParameterDescription(_data.theme))]) - case .updateTranscribedAudio(let _data): - return ("updateTranscribedAudio", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("transcriptionId", ConstructorParameterDescription(_data.transcriptionId)), ("text", ConstructorParameterDescription(_data.text))]) - case .updateUser(let _data): - return ("updateUser", [("userId", ConstructorParameterDescription(_data.userId))]) - case .updateUserEmojiStatus(let _data): - return ("updateUserEmojiStatus", [("userId", ConstructorParameterDescription(_data.userId)), ("emojiStatus", ConstructorParameterDescription(_data.emojiStatus))]) - case .updateUserName(let _data): - return ("updateUserName", [("userId", ConstructorParameterDescription(_data.userId)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("usernames", ConstructorParameterDescription(_data.usernames))]) - case .updateUserPhone(let _data): - return ("updateUserPhone", [("userId", ConstructorParameterDescription(_data.userId)), ("phone", ConstructorParameterDescription(_data.phone))]) - case .updateUserStatus(let _data): - return ("updateUserStatus", [("userId", ConstructorParameterDescription(_data.userId)), ("status", ConstructorParameterDescription(_data.status))]) - case .updateUserTyping(let _data): - return ("updateUserTyping", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("action", ConstructorParameterDescription(_data.action))]) - case .updateWebPage(let _data): - return ("updateWebPage", [("webpage", ConstructorParameterDescription(_data.webpage)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - case .updateWebViewResultSent(let _data): - return ("updateWebViewResultSent", [("queryId", ConstructorParameterDescription(_data.queryId))]) + case .statsDateRangeDays(let _data): + return ("statsDateRangeDays", [("minDate", ConstructorParameterDescription(_data.minDate)), ("maxDate", ConstructorParameterDescription(_data.maxDate))]) } } - public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? { - return Api.Update.updateAttachMenuBots - } - public static func parse_updateAutoSaveSettings(_ reader: BufferReader) -> Update? { - return Api.Update.updateAutoSaveSettings - } - public static func parse_updateBotBusinessConnect(_ reader: BufferReader) -> Update? { - var _1: Api.BotBusinessConnection? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.BotBusinessConnection - } + public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateBotBusinessConnect(Cons_updateBotBusinessConnect(connection: _1!, qts: _2!)) + return Api.StatsDateRangeDays.statsDateRangeDays(Cons_statsDateRangeDays(minDate: _1!, maxDate: _2!)) } else { return nil } } - public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer + } +} +public extension Api { + enum StatsGraph: TypeConstructorDescription { + public class Cons_statsGraph: TypeConstructorDescription { + public var flags: Int32 + public var json: Api.DataJSON + public var zoomToken: String? + public init(flags: Int32, json: Api.DataJSON, zoomToken: String?) { + self.flags = flags + self.json = json + self.zoomToken = zoomToken } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int64? - _6 = reader.readInt64() - var _7: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = parseBytes(reader) - } - var _8: String? - if Int(_1!) & Int(1 << 1) != 0 { - _8 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotCallbackQuery(Cons_updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGraph", [("flags", ConstructorParameterDescription(self.flags)), ("json", ConstructorParameterDescription(self.json)), ("zoomToken", ConstructorParameterDescription(self.zoomToken))]) } } - public static func parse_updateBotChatBoost(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + public class Cons_statsGraphAsync: TypeConstructorDescription { + public var token: String + public init(token: String) { + self.token = token } - var _2: Api.Boost? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Boost - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotChatBoost(Cons_updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGraphAsync", [("token", ConstructorParameterDescription(self.token))]) } } - public static func parse_updateBotChatInviteRequester(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + public class Cons_statsGraphError: TypeConstructorDescription { + public var error: String + public init(error: String) { + self.error = error } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotChatInviteRequester(Cons_updateBotChatInviteRequester(peer: _1!, date: _2!, userId: _3!, about: _4!, invite: _5!, qts: _6!)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGraphError", [("error", ConstructorParameterDescription(self.error))]) } } - public static func parse_updateBotCommands(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int64? - _2 = reader.readInt64() - var _3: [Api.BotCommand]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotCommands(Cons_updateBotCommands(peer: _1!, botId: _2!, commands: _3!)) - } - else { - return nil - } - } - public static func parse_updateBotDeleteBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotDeleteBusinessMessage(Cons_updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!)) - } - else { - return nil - } - } - public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.Message? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Message - } - var _4: Api.Message? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Message + case statsGraph(Cons_statsGraph) + case statsGraphAsync(Cons_statsGraphAsync) + case statsGraphError(Cons_statsGraphError) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGraph(let _data): + if boxed { + buffer.appendInt32(-1901828938) } - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotEditBusinessMessage(Cons_updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!)) - } - else { - return nil - } - } - public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.GeoPoint? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.GeoPoint + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.json.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.zoomToken!, buffer: buffer, boxed: false) } - } - var _6: Api.InlineQueryPeerType? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InlineQueryPeerType + break + case .statsGraphAsync(let _data): + if boxed { + buffer.appendInt32(1244130093) } - } - var _7: String? - _7 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotInlineQuery(Cons_updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!)) - } - else { - return nil - } - } - public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Api.GeoPoint? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.GeoPoint + serializeString(_data.token, buffer: buffer, boxed: false) + break + case .statsGraphError(let _data): + if boxed { + buffer.appendInt32(-1092839390) } - } - var _5: String? - _5 = parseString(reader) - var _6: Api.InputBotInlineMessageID? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateBotInlineSend(Cons_updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6)) - } - else { - return nil + serializeString(_data.error, buffer: buffer, boxed: false) + break } } - public static func parse_updateBotMenuButton(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.BotMenuButton? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BotMenuButton - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateBotMenuButton(Cons_updateBotMenuButton(botId: _1!, button: _2!)) - } - else { - return nil + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsGraph(let _data): + return ("statsGraph", [("flags", ConstructorParameterDescription(_data.flags)), ("json", ConstructorParameterDescription(_data.json)), ("zoomToken", ConstructorParameterDescription(_data.zoomToken))]) + case .statsGraphAsync(let _data): + return ("statsGraphAsync", [("token", ConstructorParameterDescription(_data.token))]) + case .statsGraphError(let _data): + return ("statsGraphError", [("error", ConstructorParameterDescription(_data.error))]) } } - public static func parse_updateBotMessageReaction(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: [Api.Reaction]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - var _6: [Api.Reaction]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - var _7: Int32? - _7 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateBotMessageReaction(Cons_updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!)) - } - else { - return nil - } - } - public static func parse_updateBotMessageReactions(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.ReactionCount]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotMessageReactions(Cons_updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!)) - } - else { - return nil - } - } - public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.Message? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Message - } - var _4: Api.Message? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Message - } - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateBotNewBusinessMessage(Cons_updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!)) - } - else { - return nil - } - } - public static func parse_updateBotPrecheckoutQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } - } - var _6: String? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = parseString(reader) - } - var _7: String? - _7 = parseString(reader) - var _8: Int64? - _8 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBotPrecheckoutQuery(Cons_updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!)) - } - else { - return nil - } - } - public static func parse_updateBotPurchasedPaidMedia(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotPurchasedPaidMedia(Cons_updateBotPurchasedPaidMedia(userId: _1!, payload: _2!, qts: _3!)) - } - else { - return nil - } - } - public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Api.PostAddress? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.PostAddress - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotShippingQuery(Cons_updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!)) - } - else { - return nil - } - } - public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Bool? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateBotStopped(Cons_updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!)) - } - else { - return nil - } - } - public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? { - var _1: Api.DataJSON? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateBotWebhookJSON(Cons_updateBotWebhookJSON(data: _1!)) - } - else { - return nil - } - } - public static func parse_updateBotWebhookJSONQuery(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotWebhookJSONQuery(Cons_updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!)) - } - else { - return nil - } - } - public static func parse_updateBusinessBotCallbackQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.Message? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Message - } - var _6: Api.Message? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Message - } - } - var _7: Int64? - _7 = reader.readInt64() - var _8: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _8 = parseBytes(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateBusinessBotCallbackQuery(Cons_updateBusinessBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, connectionId: _4!, message: _5!, replyToMessage: _6, chatInstance: _7!, data: _8)) - } - else { - return nil - } - } - public static func parse_updateChannel(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChannel(Cons_updateChannel(channelId: _1!)) - } - else { - return nil - } - } - public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelAvailableMessages(Cons_updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!)) - } - else { - return nil - } - } - public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageForwards(Cons_updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!)) - } - else { - return nil - } - } - public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelMessageViews(Cons_updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!)) - } - else { - return nil - } - } - public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.ChannelParticipant? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } - } - var _7: Api.ChannelParticipant? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } - } - var _8: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - } - var _9: Int32? - _9 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChannelParticipant(Cons_updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!)) - } - else { - return nil - } - } - public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _5: [Int32]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChannelReadMessagesContents(Cons_updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, savedPeerId: _4, messages: _5!)) - } - else { - return nil - } - } - public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelTooLong(Cons_updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3)) - } - else { - return nil - } - } - public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Api.Peer? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _5: Api.SendMessageAction? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChannelUserTyping(Cons_updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!)) - } - else { - return nil - } - } - public static func parse_updateChannelViewForumAsMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Bool? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Bool - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateChannelViewForumAsMessages(Cons_updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!)) - } - else { - return nil - } - } - public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.WebPage? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.WebPage - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChannelWebPage(Cons_updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!)) - } - else { - return nil - } - } - public static func parse_updateChat(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChat(Cons_updateChat(chatId: _1!)) - } - else { - return nil - } - } - public static func parse_updateChatDefaultBannedRights(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.ChatBannedRights? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatDefaultBannedRights(Cons_updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.ChatParticipant? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.ChatParticipant - } - } - var _7: Api.ChatParticipant? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.ChatParticipant - } - } - var _8: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - } - var _9: Int32? - _9 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Update.updateChatParticipant(Cons_updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateChatParticipantAdd(Cons_updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.Bool? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChatParticipantAdmin(Cons_updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatParticipantDelete(Cons_updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipantRank(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateChatParticipantRank(Cons_updateChatParticipantRank(chatId: _1!, userId: _2!, rank: _3!, version: _4!)) - } - else { - return nil - } - } - public static func parse_updateChatParticipants(_ reader: BufferReader) -> Update? { - var _1: Api.ChatParticipants? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChatParticipants - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateChatParticipants(Cons_updateChatParticipants(participants: _1!)) - } - else { - return nil - } - } - public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.SendMessageAction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChatUserTyping(Cons_updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!)) - } - else { - return nil - } - } - public static func parse_updateConfig(_ reader: BufferReader) -> Update? { - return Api.Update.updateConfig - } - public static func parse_updateContactsReset(_ reader: BufferReader) -> Update? { - return Api.Update.updateContactsReset - } - public static func parse_updateDcOptions(_ reader: BufferReader) -> Update? { - var _1: [Api.DcOption]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DcOption.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDcOptions(Cons_updateDcOptions(dcOptions: _1!)) - } - else { - return nil - } - } - public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDeleteChannelMessages(Cons_updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!)) - } - else { - return nil - } - } - public static func parse_updateDeleteGroupCallMessages(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteGroupCallMessages(Cons_updateDeleteGroupCallMessages(call: _1!, messages: _2!)) - } - else { - return nil - } - } - public static func parse_updateDeleteMessages(_ reader: BufferReader) -> Update? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDeleteMessages(Cons_updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateDeleteQuickReply(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDeleteQuickReply(Cons_updateDeleteQuickReply(shortcutId: _1!)) - } - else { - return nil - } - } - public static func parse_updateDeleteQuickReplyMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateDeleteQuickReplyMessages(Cons_updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!)) - } - else { - return nil - } - } - public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateDeleteScheduledMessages(Cons_updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4)) - } - else { - return nil - } - } - public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.DialogFilter? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DialogFilter - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogFilter(Cons_updateDialogFilter(flags: _1!, id: _2!, filter: _3)) - } - else { - return nil - } - } - public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateDialogFilterOrder(Cons_updateDialogFilterOrder(order: _1!)) - } - else { - return nil - } - } - public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? { - return Api.Update.updateDialogFilters - } - public static func parse_updateDialogPinned(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() - } - var _3: Api.DialogPeer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.DialogPeer - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogPinned(Cons_updateDialogPinned(flags: _1!, folderId: _2, peer: _3!)) - } - else { - return nil - } - } - public static func parse_updateDialogUnreadMark(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DialogPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer - } - var _3: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateDialogUnreadMark(Cons_updateDialogUnreadMark(flags: _1!, peer: _2!, savedPeerId: _3)) - } - else { - return nil - } - } - public static func parse_updateDraftMessage(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _5: Api.DraftMessage? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.DraftMessage - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateDraftMessage(Cons_updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, savedPeerId: _4, draft: _5!)) - } - else { - return nil - } - } - public static func parse_updateEditChannelMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditChannelMessage(Cons_updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateEditMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEditMessage(Cons_updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateEmojiGameInfo(_ reader: BufferReader) -> Update? { - var _1: Api.messages.EmojiGameInfo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateEmojiGameInfo(Cons_updateEmojiGameInfo(info: _1!)) - } - else { - return nil - } - } - public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateEncryptedChatTyping(Cons_updateEncryptedChatTyping(chatId: _1!)) - } - else { - return nil - } - } - public static func parse_updateEncryptedMessagesRead(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateEncryptedMessagesRead(Cons_updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!)) - } - else { - return nil - } - } - public static func parse_updateEncryption(_ reader: BufferReader) -> Update? { - var _1: Api.EncryptedChat? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.EncryptedChat - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateEncryption(Cons_updateEncryption(chat: _1!, date: _2!)) - } - else { - return nil - } - } - public static func parse_updateFavedStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateFavedStickers - } - public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? { - var _1: [Api.FolderPeer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FolderPeer.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateFolderPeers(Cons_updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGeoLiveViewed(Cons_updateGeoLiveViewed(peer: _1!, msgId: _2!)) - } - else { - return nil - } - } - public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Api.GroupCall? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.GroupCall - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCall(Cons_updateGroupCall(flags: _1!, peer: _2, call: _3!)) - } - else { - return nil - } - } - public static func parse_updateGroupCallChainBlocks(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Buffer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateGroupCallChainBlocks(Cons_updateGroupCallChainBlocks(call: _1!, subChainId: _2!, blocks: _3!, nextOffset: _4!)) - } - else { - return nil - } - } - public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { + + public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? { var _1: Int32? _1 = reader.readInt32() var _2: Api.DataJSON? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.DataJSON } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCallConnection(Cons_updateGroupCallConnection(flags: _1!, params: _2!)) - } - else { - return nil - } - } - public static func parse_updateGroupCallEncryptedMessage(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Buffer? - _3 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCallEncryptedMessage(Cons_updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!)) + return Api.StatsGraph.statsGraph(Cons_statsGraph(flags: _1!, json: _2!, zoomToken: _3)) } else { return nil } } - public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: Api.GroupCallMessage? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.GroupCallMessage - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateGroupCallMessage(Cons_updateGroupCallMessage(call: _1!, message: _2!)) - } - else { - return nil - } - } - public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { - var _1: Api.InputGroupCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall - } - var _2: [Api.GroupCallParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateGroupCallParticipants(Cons_updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!)) - } - else { - return nil - } - } - public static func parse_updateInlineBotCallbackQuery(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: Api.InputBotInlineMessageID? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } - var _5: Int64? - _5 = reader.readInt64() - var _6: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseBytes(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateInlineBotCallbackQuery(Cons_updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7)) - } - else { - return nil - } - } - public static func parse_updateLangPack(_ reader: BufferReader) -> Update? { - var _1: Api.LangPackDifference? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateLangPack(Cons_updateLangPack(difference: _1!)) - } - else { - return nil - } - } - public static func parse_updateLangPackTooLong(_ reader: BufferReader) -> Update? { + public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.Update.updateLangPackTooLong(Cons_updateLangPackTooLong(langCode: _1!)) + return Api.StatsGraph.statsGraphAsync(Cons_statsGraphAsync(token: _1!)) } else { return nil } } - public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { - return Api.Update.updateLoginToken + public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.StatsGraph.statsGraphError(Cons_statsGraphError(error: _1!)) + } + else { + return nil + } } - public static func parse_updateManagedBot(_ reader: BufferReader) -> Update? { + } +} +public extension Api { + enum StatsGroupTopAdmin: TypeConstructorDescription { + public class Cons_statsGroupTopAdmin: TypeConstructorDescription { + public var userId: Int64 + public var deleted: Int32 + public var kicked: Int32 + public var banned: Int32 + public init(userId: Int64, deleted: Int32, kicked: Int32, banned: Int32) { + self.userId = userId + self.deleted = deleted + self.kicked = kicked + self.banned = banned + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGroupTopAdmin", [("userId", ConstructorParameterDescription(self.userId)), ("deleted", ConstructorParameterDescription(self.deleted)), ("kicked", ConstructorParameterDescription(self.kicked)), ("banned", ConstructorParameterDescription(self.banned))]) + } + } + case statsGroupTopAdmin(Cons_statsGroupTopAdmin) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopAdmin(let _data): + if boxed { + buffer.appendInt32(-682079097) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.deleted, buffer: buffer, boxed: false) + serializeInt32(_data.kicked, buffer: buffer, boxed: false) + serializeInt32(_data.banned, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsGroupTopAdmin(let _data): + return ("statsGroupTopAdmin", [("userId", ConstructorParameterDescription(_data.userId)), ("deleted", ConstructorParameterDescription(_data.deleted)), ("kicked", ConstructorParameterDescription(_data.kicked)), ("banned", ConstructorParameterDescription(_data.banned))]) + } + } + + public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? { var _1: Int64? _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateManagedBot(Cons_updateManagedBot(userId: _1!, botId: _2!, qts: _3!)) - } - else { - return nil - } - } - public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.MessageExtendedMedia]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateMessageExtendedMedia(Cons_updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!)) - } - else { - return nil - } - } - public static func parse_updateMessageID(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMessageID(Cons_updateMessageID(id: _1!, randomId: _2!)) - } - else { - return nil - } - } - public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: Int64? - _5 = reader.readInt64() - var _6: Api.Poll? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Poll - } - } - var _7: Api.PollResults? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PollResults - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.Update.updateMessagePoll(Cons_updateMessagePoll(flags: _1!, peer: _2, msgId: _3, topMsgId: _4, pollId: _5!, poll: _6, results: _7!)) - } - else { - return nil - } - } - public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Buffer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) - } - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateMessagePollVote(Cons_updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, positions: _4!, qts: _5!)) - } - else { - return nil - } - } - public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt32() - } - var _5: Api.Peer? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _6: Api.MessageReactions? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageReactions - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateMessageReactions(Cons_updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, savedPeerId: _5, reactions: _6!)) - } - else { - return nil - } - } - public static func parse_updateMonoForumNoPaidException(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Api.Peer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateMonoForumNoPaidException(Cons_updateMonoForumNoPaidException(flags: _1!, channelId: _2!, savedPeerId: _3!)) - } - else { - return nil - } - } - public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateMoveStickerSetToTop(Cons_updateMoveStickerSetToTop(flags: _1!, stickerset: _2!)) - } - else { - return nil - } - } - public static func parse_updateNewAuthorization(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateNewAuthorization(Cons_updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5)) - } - else { - return nil - } - } - public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewChannelMessage(Cons_updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { - var _1: Api.EncryptedMessage? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.EncryptedMessage - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNewEncryptedMessage(Cons_updateNewEncryptedMessage(message: _1!, qts: _2!)) - } - else { - return nil - } - } - public static func parse_updateNewMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewMessage(Cons_updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { - var _1: Api.QuickReply? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.QuickReply - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewQuickReply(Cons_updateNewQuickReply(quickReply: _1!)) - } - else { - return nil - } - } - public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewScheduledMessage(Cons_updateNewScheduledMessage(message: _1!)) - } - else { - return nil - } - } - public static func parse_updateNewStickerSet(_ reader: BufferReader) -> Update? { - var _1: Api.messages.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateNewStickerSet(Cons_updateNewStickerSet(stickerset: _1!)) - } - else { - return nil - } - } - public static func parse_updateNewStoryReaction(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateNewStoryReaction(Cons_updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!)) - } - else { - return nil - } - } - public static func parse_updateNotifySettings(_ reader: BufferReader) -> Update? { - var _1: Api.NotifyPeer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.NotifyPeer - } - var _2: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateNotifySettings(Cons_updateNotifySettings(peer: _1!, notifySettings: _2!)) - } - else { - return nil - } - } - public static func parse_updatePaidReactionPrivacy(_ reader: BufferReader) -> Update? { - var _1: Api.PaidReactionPrivacy? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PaidReactionPrivacy - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePaidReactionPrivacy(Cons_updatePaidReactionPrivacy(private: _1!)) - } - else { - return nil - } - } - public static func parse_updatePeerBlocked(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerBlocked(Cons_updatePeerBlocked(flags: _1!, peerId: _2!)) - } - else { - return nil - } - } - public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerHistoryTTL(Cons_updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3)) - } - else { - return nil - } - } - public static func parse_updatePeerLocated(_ reader: BufferReader) -> Update? { - var _1: [Api.PeerLocated]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerLocated.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePeerLocated(Cons_updatePeerLocated(peers: _1!)) - } - else { - return nil - } - } - public static func parse_updatePeerSettings(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.PeerSettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePeerSettings(Cons_updatePeerSettings(peer: _1!, settings: _2!)) - } - else { - return nil - } - } - public static func parse_updatePeerWallpaper(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Api.WallPaper? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.WallPaper - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePeerWallpaper(Cons_updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3)) - } - else { - return nil - } - } - public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Int64]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePendingJoinRequests(Cons_updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!)) - } - else { - return nil - } - } - public static func parse_updatePhoneCall(_ reader: BufferReader) -> Update? { - var _1: Api.PhoneCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updatePhoneCall(Cons_updatePhoneCall(phoneCall: _1!)) - } - else { - return nil - } - } - public static func parse_updatePhoneCallSignalingData(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePhoneCallSignalingData(Cons_updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!)) - } - else { - return nil - } - } - public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedChannelMessages(Cons_updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!)) - } - else { - return nil - } - } - public static func parse_updatePinnedDialogs(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() - } - var _3: [Api.DialogPeer]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedDialogs(Cons_updatePinnedDialogs(flags: _1!, folderId: _2, order: _3)) - } - else { - return nil - } - } - public static func parse_updatePinnedForumTopic(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedForumTopic(Cons_updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!)) - } - else { - return nil - } - } - public static func parse_updatePinnedForumTopics(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updatePinnedForumTopics(Cons_updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3)) - } - else { - return nil - } - } - public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updatePinnedMessages(Cons_updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!)) - } - else { - return nil - } - } - public static func parse_updatePinnedSavedDialogs(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.DialogPeer]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.Update.updatePinnedSavedDialogs(Cons_updatePinnedSavedDialogs(flags: _1!, order: _2)) - } - else { - return nil - } - } - public static func parse_updatePrivacy(_ reader: BufferReader) -> Update? { - var _1: Api.PrivacyKey? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PrivacyKey - } - var _2: [Api.PrivacyRule]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updatePrivacy(Cons_updatePrivacy(key: _1!, rules: _2!)) - } - else { - return nil - } - } - public static func parse_updatePtsChanged(_ reader: BufferReader) -> Update? { - return Api.Update.updatePtsChanged - } - public static func parse_updateQuickReplies(_ reader: BufferReader) -> Update? { - var _1: [Api.QuickReply]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplies(Cons_updateQuickReplies(quickReplies: _1!)) - } - else { - return nil - } - } - public static func parse_updateQuickReplyMessage(_ reader: BufferReader) -> Update? { - var _1: Api.Message? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Message - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateQuickReplyMessage(Cons_updateQuickReplyMessage(message: _1!)) - } - else { - return nil - } - } - public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt64() - } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelDiscussionInbox(Cons_updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6)) - } - else { - return nil - } - } - public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadChannelDiscussionOutbox(Cons_updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!)) - } - else { - return nil - } - } - public static func parse_updateReadChannelInbox(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int64? - _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateReadChannelInbox(Cons_updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!)) - } - else { - return nil - } - } - public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateReadChannelOutbox(Cons_updateReadChannelOutbox(channelId: _1!, maxId: _2!)) - } - else { - return nil - } - } - public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateReadFeaturedEmojiStickers - } - public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateReadFeaturedStickers - } - public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Api.Peer? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Update.updateReadHistoryInbox(Cons_updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, topMsgId: _4, maxId: _5!, stillUnreadCount: _6!, pts: _7!, ptsCount: _8!)) - } - else { - return nil - } - } - public static func parse_updateReadHistoryOutbox(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } var _2: Int32? _2 = reader.readInt32() var _3: Int32? @@ -6152,284 +272,311 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateReadHistoryOutbox(Cons_updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!)) + return Api.StatsGroupTopAdmin.statsGroupTopAdmin(Cons_statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!)) } else { return nil } } - public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } +} +public extension Api { + enum StatsGroupTopInviter: TypeConstructorDescription { + public class Cons_statsGroupTopInviter: TypeConstructorDescription { + public var userId: Int64 + public var invitations: Int32 + public init(userId: Int64, invitations: Int32) { + self.userId = userId + self.invitations = invitations } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateReadMessagesContents(Cons_updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGroupTopInviter", [("userId", ConstructorParameterDescription(self.userId)), ("invitations", ConstructorParameterDescription(self.invitations))]) } } - public static func parse_updateReadMonoForumInbox(_ reader: BufferReader) -> Update? { + case statsGroupTopInviter(Cons_statsGroupTopInviter) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopInviter(let _data): + if boxed { + buffer.appendInt32(1398765469) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.invitations, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsGroupTopInviter(let _data): + return ("statsGroupTopInviter", [("userId", ConstructorParameterDescription(_data.userId)), ("invitations", ConstructorParameterDescription(_data.invitations))]) + } + } + + public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? { var _1: Int64? _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadMonoForumInbox(Cons_updateReadMonoForumInbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!)) - } - else { - return nil - } - } - public static func parse_updateReadMonoForumOutbox(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateReadMonoForumOutbox(Cons_updateReadMonoForumOutbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!)) - } - else { - return nil - } - } - public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateReadStories(Cons_updateReadStories(peer: _1!, maxId: _2!)) + return Api.StatsGroupTopInviter.statsGroupTopInviter(Cons_statsGroupTopInviter(userId: _1!, invitations: _2!)) } else { return nil } } - public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentEmojiStatuses - } - public static func parse_updateRecentReactions(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentReactions - } - public static func parse_updateRecentStickers(_ reader: BufferReader) -> Update? { - return Api.Update.updateRecentStickers - } - public static func parse_updateSavedDialogPinned(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DialogPeer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } +} +public extension Api { + enum StatsGroupTopPoster: TypeConstructorDescription { + public class Cons_statsGroupTopPoster: TypeConstructorDescription { + public var userId: Int64 + public var messages: Int32 + public var avgChars: Int32 + public init(userId: Int64, messages: Int32, avgChars: Int32) { + self.userId = userId + self.messages = messages + self.avgChars = avgChars } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateSavedDialogPinned(Cons_updateSavedDialogPinned(flags: _1!, peer: _2!)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsGroupTopPoster", [("userId", ConstructorParameterDescription(self.userId)), ("messages", ConstructorParameterDescription(self.messages)), ("avgChars", ConstructorParameterDescription(self.avgChars))]) } } - public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedGifs - } - public static func parse_updateSavedReactionTags(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedReactionTags - } - public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? { - return Api.Update.updateSavedRingtones - } - public static func parse_updateSentPhoneCode(_ reader: BufferReader) -> Update? { - var _1: Api.auth.SentCode? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateSentPhoneCode(Cons_updateSentPhoneCode(sentCode: _1!)) - } - else { - return nil + case statsGroupTopPoster(Cons_statsGroupTopPoster) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsGroupTopPoster(let _data): + if boxed { + buffer.appendInt32(-1660637285) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.messages, buffer: buffer, boxed: false) + serializeInt32(_data.avgChars, buffer: buffer, boxed: false) + break } } - public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsGroupTopPoster(let _data): + return ("statsGroupTopPoster", [("userId", ConstructorParameterDescription(_data.userId)), ("messages", ConstructorParameterDescription(_data.messages)), ("avgChars", ConstructorParameterDescription(_data.avgChars))]) } + } + + public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int32? _2 = reader.readInt32() - var _3: Api.Reaction? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Reaction - } + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.Update.updateSentStoryReaction(Cons_updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!)) + return Api.StatsGroupTopPoster.statsGroupTopPoster(Cons_statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!)) } else { return nil } } - public static func parse_updateServiceNotification(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() + } +} +public extension Api { + enum StatsPercentValue: TypeConstructorDescription { + public class Cons_statsPercentValue: TypeConstructorDescription { + public var part: Double + public var total: Double + public init(part: Double, total: Double) { + self.part = part + self.total = total } - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: Api.MessageMedia? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsPercentValue", [("part", ConstructorParameterDescription(self.part)), ("total", ConstructorParameterDescription(self.total))]) } - var _6: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + case statsPercentValue(Cons_statsPercentValue) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsPercentValue(let _data): + if boxed { + buffer.appendInt32(-875679776) + } + serializeDouble(_data.part, buffer: buffer, boxed: false) + serializeDouble(_data.total, buffer: buffer, boxed: false) + break } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsPercentValue(let _data): + return ("statsPercentValue", [("part", ConstructorParameterDescription(_data.part)), ("total", ConstructorParameterDescription(_data.total))]) + } + } + + public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Update.updateServiceNotification(Cons_updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!)) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StatsPercentValue.statsPercentValue(Cons_statsPercentValue(part: _1!, total: _2!)) } else { return nil } } - public static func parse_updateSmsJob(_ reader: BufferReader) -> Update? { + } +} +public extension Api { + enum StatsURL: TypeConstructorDescription { + public class Cons_statsURL: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("statsURL", [("url", ConstructorParameterDescription(self.url))]) + } + } + case statsURL(Cons_statsURL) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .statsURL(let _data): + if boxed { + buffer.appendInt32(1202287072) + } + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .statsURL(let _data): + return ("statsURL", [("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_statsURL(_ reader: BufferReader) -> StatsURL? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.Update.updateSmsJob(Cons_updateSmsJob(jobId: _1!)) + return Api.StatsURL.statsURL(Cons_statsURL(url: _1!)) } else { return nil } } - public static func parse_updateStarGiftAuctionState(_ reader: BufferReader) -> Update? { + } +} +public extension Api { + enum StickerKeyword: TypeConstructorDescription { + public class Cons_stickerKeyword: TypeConstructorDescription { + public var documentId: Int64 + public var keyword: [String] + public init(documentId: Int64, keyword: [String]) { + self.documentId = documentId + self.keyword = keyword + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerKeyword", [("documentId", ConstructorParameterDescription(self.documentId)), ("keyword", ConstructorParameterDescription(self.keyword))]) + } + } + case stickerKeyword(Cons_stickerKeyword) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerKeyword(let _data): + if boxed { + buffer.appendInt32(-50416996) + } + serializeInt64(_data.documentId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.keyword.count)) + for item in _data.keyword { + serializeString(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stickerKeyword(let _data): + return ("stickerKeyword", [("documentId", ConstructorParameterDescription(_data.documentId)), ("keyword", ConstructorParameterDescription(_data.keyword))]) + } + } + + public static func parse_stickerKeyword(_ reader: BufferReader) -> StickerKeyword? { var _1: Int64? _1 = reader.readInt64() - var _2: Api.StarGiftAuctionState? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState + var _2: [String]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateStarGiftAuctionState(Cons_updateStarGiftAuctionState(giftId: _1!, state: _2!)) + return Api.StickerKeyword.stickerKeyword(Cons_stickerKeyword(documentId: _1!, keyword: _2!)) } else { return nil } } - public static func parse_updateStarGiftAuctionUserState(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.StarGiftAuctionUserState? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState + } +} +public extension Api { + enum StickerPack: TypeConstructorDescription { + public class Cons_stickerPack: TypeConstructorDescription { + public var emoticon: String + public var documents: [Int64] + public init(emoticon: String, documents: [Int64]) { + self.emoticon = emoticon + self.documents = documents } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStarGiftAuctionUserState(Cons_updateStarGiftAuctionUserState(giftId: _1!, userState: _2!)) - } - else { - return nil + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerPack", [("emoticon", ConstructorParameterDescription(self.emoticon)), ("documents", ConstructorParameterDescription(self.documents))]) } } - public static func parse_updateStarGiftCraftFail(_ reader: BufferReader) -> Update? { - return Api.Update.updateStarGiftCraftFail - } - public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? { - var _1: Api.StarsAmount? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStarsBalance(Cons_updateStarsBalance(balance: _1!)) - } - else { - return nil + case stickerPack(Cons_stickerPack) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerPack(let _data): + if boxed { + buffer.appendInt32(313694676) + } + serializeString(_data.emoticon, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + serializeInt64(item, buffer: buffer, boxed: false) + } + break } } - public static func parse_updateStarsRevenueStatus(_ reader: BufferReader) -> Update? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: Api.StarsRevenueStatus? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStarsRevenueStatus(Cons_updateStarsRevenueStatus(peer: _1!, status: _2!)) - } - else { - return nil + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stickerPack(let _data): + return ("stickerPack", [("emoticon", ConstructorParameterDescription(_data.emoticon)), ("documents", ConstructorParameterDescription(_data.documents))]) } } - public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateStickerSets(Cons_updateStickerSets(flags: _1!)) - } - else { - return nil - } - } - public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() + + public static func parse_stickerPack(_ reader: BufferReader) -> StickerPack? { + var _1: String? + _1 = parseString(reader) var _2: [Int64]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) @@ -6437,26 +584,956 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateStickerSetsOrder(Cons_updateStickerSetsOrder(flags: _1!, order: _2!)) + return Api.StickerPack.stickerPack(Cons_stickerPack(emoticon: _1!, documents: _2!)) } else { return nil } } - public static func parse_updateStoriesStealthMode(_ reader: BufferReader) -> Update? { - var _1: Api.StoriesStealthMode? + } +} +public extension Api { + enum StickerSet: TypeConstructorDescription { + public class Cons_stickerSet: TypeConstructorDescription { + public var flags: Int32 + public var installedDate: Int32? + public var id: Int64 + public var accessHash: Int64 + public var title: String + public var shortName: String + public var thumbs: [Api.PhotoSize]? + public var thumbDcId: Int32? + public var thumbVersion: Int32? + public var thumbDocumentId: Int64? + public var count: Int32 + public var hash: Int32 + public init(flags: Int32, installedDate: Int32?, id: Int64, accessHash: Int64, title: String, shortName: String, thumbs: [Api.PhotoSize]?, thumbDcId: Int32?, thumbVersion: Int32?, thumbDocumentId: Int64?, count: Int32, hash: Int32) { + self.flags = flags + self.installedDate = installedDate + self.id = id + self.accessHash = accessHash + self.title = title + self.shortName = shortName + self.thumbs = thumbs + self.thumbDcId = thumbDcId + self.thumbVersion = thumbVersion + self.thumbDocumentId = thumbDocumentId + self.count = count + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSet", [("flags", ConstructorParameterDescription(self.flags)), ("installedDate", ConstructorParameterDescription(self.installedDate)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("title", ConstructorParameterDescription(self.title)), ("shortName", ConstructorParameterDescription(self.shortName)), ("thumbs", ConstructorParameterDescription(self.thumbs)), ("thumbDcId", ConstructorParameterDescription(self.thumbDcId)), ("thumbVersion", ConstructorParameterDescription(self.thumbVersion)), ("thumbDocumentId", ConstructorParameterDescription(self.thumbDocumentId)), ("count", ConstructorParameterDescription(self.count)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case stickerSet(Cons_stickerSet) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerSet(let _data): + if boxed { + buffer.appendInt32(768691932) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.installedDate!, buffer: buffer, boxed: false) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.shortName, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.thumbs!.count)) + for item in _data.thumbs! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.thumbDcId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.thumbVersion!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeInt64(_data.thumbDocumentId!, buffer: buffer, boxed: false) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.hash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stickerSet(let _data): + return ("stickerSet", [("flags", ConstructorParameterDescription(_data.flags)), ("installedDate", ConstructorParameterDescription(_data.installedDate)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("title", ConstructorParameterDescription(_data.title)), ("shortName", ConstructorParameterDescription(_data.shortName)), ("thumbs", ConstructorParameterDescription(_data.thumbs)), ("thumbDcId", ConstructorParameterDescription(_data.thumbDcId)), ("thumbVersion", ConstructorParameterDescription(_data.thumbVersion)), ("thumbDocumentId", ConstructorParameterDescription(_data.thumbDocumentId)), ("count", ConstructorParameterDescription(_data.count)), ("hash", ConstructorParameterDescription(_data.hash))]) + } + } + + public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: [Api.PhotoSize]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + } + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = reader.readInt32() + } + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _9 = reader.readInt32() + } + var _10: Int64? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _10 = reader.readInt64() + } + var _11: Int32? + _11 = reader.readInt32() + var _12: Int32? + _12 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.StickerSet.stickerSet(Cons_stickerSet(flags: _1!, installedDate: _2, id: _3!, accessHash: _4!, title: _5!, shortName: _6!, thumbs: _7, thumbDcId: _8, thumbVersion: _9, thumbDocumentId: _10, count: _11!, hash: _12!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StickerSetCovered: TypeConstructorDescription { + public class Cons_stickerSetCovered: TypeConstructorDescription { + public var set: Api.StickerSet + public var cover: Api.Document + public init(set: Api.StickerSet, cover: Api.Document) { + self.set = set + self.cover = cover + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSetCovered", [("set", ConstructorParameterDescription(self.set)), ("cover", ConstructorParameterDescription(self.cover))]) + } + } + public class Cons_stickerSetFullCovered: TypeConstructorDescription { + public var set: Api.StickerSet + public var packs: [Api.StickerPack] + public var keywords: [Api.StickerKeyword] + public var documents: [Api.Document] + public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) { + self.set = set + self.packs = packs + self.keywords = keywords + self.documents = documents + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSetFullCovered", [("set", ConstructorParameterDescription(self.set)), ("packs", ConstructorParameterDescription(self.packs)), ("keywords", ConstructorParameterDescription(self.keywords)), ("documents", ConstructorParameterDescription(self.documents))]) + } + } + public class Cons_stickerSetMultiCovered: TypeConstructorDescription { + public var set: Api.StickerSet + public var covers: [Api.Document] + public init(set: Api.StickerSet, covers: [Api.Document]) { + self.set = set + self.covers = covers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSetMultiCovered", [("set", ConstructorParameterDescription(self.set)), ("covers", ConstructorParameterDescription(self.covers))]) + } + } + public class Cons_stickerSetNoCovered: TypeConstructorDescription { + public var set: Api.StickerSet + public init(set: Api.StickerSet) { + self.set = set + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSetNoCovered", [("set", ConstructorParameterDescription(self.set))]) + } + } + case stickerSetCovered(Cons_stickerSetCovered) + case stickerSetFullCovered(Cons_stickerSetFullCovered) + case stickerSetMultiCovered(Cons_stickerSetMultiCovered) + case stickerSetNoCovered(Cons_stickerSetNoCovered) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerSetCovered(let _data): + if boxed { + buffer.appendInt32(1678812626) + } + _data.set.serialize(buffer, true) + _data.cover.serialize(buffer, true) + break + case .stickerSetFullCovered(let _data): + if boxed { + buffer.appendInt32(1087454222) + } + _data.set.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.packs.count)) + for item in _data.packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.keywords.count)) + for item in _data.keywords { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + item.serialize(buffer, true) + } + break + case .stickerSetMultiCovered(let _data): + if boxed { + buffer.appendInt32(872932635) + } + _data.set.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.covers.count)) + for item in _data.covers { + item.serialize(buffer, true) + } + break + case .stickerSetNoCovered(let _data): + if boxed { + buffer.appendInt32(2008112412) + } + _data.set.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stickerSetCovered(let _data): + return ("stickerSetCovered", [("set", ConstructorParameterDescription(_data.set)), ("cover", ConstructorParameterDescription(_data.cover))]) + case .stickerSetFullCovered(let _data): + return ("stickerSetFullCovered", [("set", ConstructorParameterDescription(_data.set)), ("packs", ConstructorParameterDescription(_data.packs)), ("keywords", ConstructorParameterDescription(_data.keywords)), ("documents", ConstructorParameterDescription(_data.documents))]) + case .stickerSetMultiCovered(let _data): + return ("stickerSetMultiCovered", [("set", ConstructorParameterDescription(_data.set)), ("covers", ConstructorParameterDescription(_data.covers))]) + case .stickerSetNoCovered(let _data): + return ("stickerSetNoCovered", [("set", ConstructorParameterDescription(_data.set))]) + } + } + + public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: Api.Document? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Document + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!)) + } + else { + return nil + } + } + public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.StickerKeyword]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!)) + } + else { + return nil + } + } + public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!)) + } + else { + return nil + } + } + public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet } let _c1 = _1 != nil if _c1 { - return Api.Update.updateStoriesStealthMode(Cons_updateStoriesStealthMode(stealthMode: _1!)) + return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!)) } else { return nil } } - public static func parse_updateStory(_ reader: BufferReader) -> Update? { + } +} +public extension Api { + enum StoriesStealthMode: TypeConstructorDescription { + public class Cons_storiesStealthMode: TypeConstructorDescription { + public var flags: Int32 + public var activeUntilDate: Int32? + public var cooldownUntilDate: Int32? + public init(flags: Int32, activeUntilDate: Int32?, cooldownUntilDate: Int32?) { + self.flags = flags + self.activeUntilDate = activeUntilDate + self.cooldownUntilDate = cooldownUntilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storiesStealthMode", [("flags", ConstructorParameterDescription(self.flags)), ("activeUntilDate", ConstructorParameterDescription(self.activeUntilDate)), ("cooldownUntilDate", ConstructorParameterDescription(self.cooldownUntilDate))]) + } + } + case storiesStealthMode(Cons_storiesStealthMode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storiesStealthMode(let _data): + if boxed { + buffer.appendInt32(1898850301) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.activeUntilDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.cooldownUntilDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storiesStealthMode(let _data): + return ("storiesStealthMode", [("flags", ConstructorParameterDescription(_data.flags)), ("activeUntilDate", ConstructorParameterDescription(_data.activeUntilDate)), ("cooldownUntilDate", ConstructorParameterDescription(_data.cooldownUntilDate))]) + } + } + + public static func parse_storiesStealthMode(_ reader: BufferReader) -> StoriesStealthMode? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoriesStealthMode.storiesStealthMode(Cons_storiesStealthMode(flags: _1!, activeUntilDate: _2, cooldownUntilDate: _3)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StoryAlbum: TypeConstructorDescription { + public class Cons_storyAlbum: TypeConstructorDescription { + public var flags: Int32 + public var albumId: Int32 + public var title: String + public var iconPhoto: Api.Photo? + public var iconVideo: Api.Document? + public init(flags: Int32, albumId: Int32, title: String, iconPhoto: Api.Photo?, iconVideo: Api.Document?) { + self.flags = flags + self.albumId = albumId + self.title = title + self.iconPhoto = iconPhoto + self.iconVideo = iconVideo + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyAlbum", [("flags", ConstructorParameterDescription(self.flags)), ("albumId", ConstructorParameterDescription(self.albumId)), ("title", ConstructorParameterDescription(self.title)), ("iconPhoto", ConstructorParameterDescription(self.iconPhoto)), ("iconVideo", ConstructorParameterDescription(self.iconVideo))]) + } + } + case storyAlbum(Cons_storyAlbum) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyAlbum(let _data): + if boxed { + buffer.appendInt32(-1826262950) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.albumId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.iconPhoto!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.iconVideo!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyAlbum(let _data): + return ("storyAlbum", [("flags", ConstructorParameterDescription(_data.flags)), ("albumId", ConstructorParameterDescription(_data.albumId)), ("title", ConstructorParameterDescription(_data.title)), ("iconPhoto", ConstructorParameterDescription(_data.iconPhoto)), ("iconVideo", ConstructorParameterDescription(_data.iconVideo))]) + } + } + + public static func parse_storyAlbum(_ reader: BufferReader) -> StoryAlbum? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _5: Api.Document? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Document + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.StoryAlbum.storyAlbum(Cons_storyAlbum(flags: _1!, albumId: _2!, title: _3!, iconPhoto: _4, iconVideo: _5)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StoryFwdHeader: TypeConstructorDescription { + public class Cons_storyFwdHeader: TypeConstructorDescription { + public var flags: Int32 + public var from: Api.Peer? + public var fromName: String? + public var storyId: Int32? + public init(flags: Int32, from: Api.Peer?, fromName: String?, storyId: Int32?) { + self.flags = flags + self.from = from + self.fromName = fromName + self.storyId = storyId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyFwdHeader", [("flags", ConstructorParameterDescription(self.flags)), ("from", ConstructorParameterDescription(self.from)), ("fromName", ConstructorParameterDescription(self.fromName)), ("storyId", ConstructorParameterDescription(self.storyId))]) + } + } + case storyFwdHeader(Cons_storyFwdHeader) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyFwdHeader(let _data): + if boxed { + buffer.appendInt32(-1205411504) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.from!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.fromName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.storyId!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyFwdHeader(let _data): + return ("storyFwdHeader", [("flags", ConstructorParameterDescription(_data.flags)), ("from", ConstructorParameterDescription(_data.from)), ("fromName", ConstructorParameterDescription(_data.fromName)), ("storyId", ConstructorParameterDescription(_data.storyId))]) + } + } + + public static func parse_storyFwdHeader(_ reader: BufferReader) -> StoryFwdHeader? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = parseString(reader) + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryFwdHeader.storyFwdHeader(Cons_storyFwdHeader(flags: _1!, from: _2, fromName: _3, storyId: _4)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum StoryItem: TypeConstructorDescription { + public class Cons_storyItem: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var date: Int32 + public var fromId: Api.Peer? + public var fwdFrom: Api.StoryFwdHeader? + public var expireDate: Int32 + public var caption: String? + public var entities: [Api.MessageEntity]? + public var media: Api.MessageMedia + public var mediaAreas: [Api.MediaArea]? + public var privacy: [Api.PrivacyRule]? + public var views: Api.StoryViews? + public var sentReaction: Api.Reaction? + public var albums: [Int32]? + public var music: Api.Document? + public init(flags: Int32, id: Int32, date: Int32, fromId: Api.Peer?, fwdFrom: Api.StoryFwdHeader?, expireDate: Int32, caption: String?, entities: [Api.MessageEntity]?, media: Api.MessageMedia, mediaAreas: [Api.MediaArea]?, privacy: [Api.PrivacyRule]?, views: Api.StoryViews?, sentReaction: Api.Reaction?, albums: [Int32]?, music: Api.Document?) { + self.flags = flags + self.id = id + self.date = date + self.fromId = fromId + self.fwdFrom = fwdFrom + self.expireDate = expireDate + self.caption = caption + self.entities = entities + self.media = media + self.mediaAreas = mediaAreas + self.privacy = privacy + self.views = views + self.sentReaction = sentReaction + self.albums = albums + self.music = music + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyItem", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("date", ConstructorParameterDescription(self.date)), ("fromId", ConstructorParameterDescription(self.fromId)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("expireDate", ConstructorParameterDescription(self.expireDate)), ("caption", ConstructorParameterDescription(self.caption)), ("entities", ConstructorParameterDescription(self.entities)), ("media", ConstructorParameterDescription(self.media)), ("mediaAreas", ConstructorParameterDescription(self.mediaAreas)), ("privacy", ConstructorParameterDescription(self.privacy)), ("views", ConstructorParameterDescription(self.views)), ("sentReaction", ConstructorParameterDescription(self.sentReaction)), ("albums", ConstructorParameterDescription(self.albums)), ("music", ConstructorParameterDescription(self.music))]) + } + } + public class Cons_storyItemDeleted: TypeConstructorDescription { + public var id: Int32 + public init(id: Int32) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyItemDeleted", [("id", ConstructorParameterDescription(self.id))]) + } + } + public class Cons_storyItemSkipped: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var date: Int32 + public var expireDate: Int32 + public init(flags: Int32, id: Int32, date: Int32, expireDate: Int32) { + self.flags = flags + self.id = id + self.date = date + self.expireDate = expireDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyItemSkipped", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("date", ConstructorParameterDescription(self.date)), ("expireDate", ConstructorParameterDescription(self.expireDate))]) + } + } + case storyItem(Cons_storyItem) + case storyItemDeleted(Cons_storyItemDeleted) + case storyItemSkipped(Cons_storyItemSkipped) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyItem(let _data): + if boxed { + buffer.appendInt32(379894076) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 18) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + _data.fwdFrom!.serialize(buffer, true) + } + serializeInt32(_data.expireDate, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.caption!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + _data.media.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 14) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.mediaAreas!.count)) + for item in _data.mediaAreas! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.privacy!.count)) + for item in _data.privacy! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.views!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + _data.sentReaction!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 19) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.albums!.count)) + for item in _data.albums! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 20) != 0 { + _data.music!.serialize(buffer, true) + } + break + case .storyItemDeleted(let _data): + if boxed { + buffer.appendInt32(1374088783) + } + serializeInt32(_data.id, buffer: buffer, boxed: false) + break + case .storyItemSkipped(let _data): + if boxed { + buffer.appendInt32(-5388013) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.expireDate, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyItem(let _data): + return ("storyItem", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("date", ConstructorParameterDescription(_data.date)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("expireDate", ConstructorParameterDescription(_data.expireDate)), ("caption", ConstructorParameterDescription(_data.caption)), ("entities", ConstructorParameterDescription(_data.entities)), ("media", ConstructorParameterDescription(_data.media)), ("mediaAreas", ConstructorParameterDescription(_data.mediaAreas)), ("privacy", ConstructorParameterDescription(_data.privacy)), ("views", ConstructorParameterDescription(_data.views)), ("sentReaction", ConstructorParameterDescription(_data.sentReaction)), ("albums", ConstructorParameterDescription(_data.albums)), ("music", ConstructorParameterDescription(_data.music))]) + case .storyItemDeleted(let _data): + return ("storyItemDeleted", [("id", ConstructorParameterDescription(_data.id))]) + case .storyItemSkipped(let _data): + return ("storyItemSkipped", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("date", ConstructorParameterDescription(_data.date)), ("expireDate", ConstructorParameterDescription(_data.expireDate))]) + } + } + + public static func parse_storyItem(_ reader: BufferReader) -> StoryItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _5: Api.StoryFwdHeader? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StoryFwdHeader + } + } + var _6: Int32? + _6 = reader.readInt32() + var _7: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = parseString(reader) + } + var _8: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _9: Api.MessageMedia? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + var _10: [Api.MediaArea]? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MediaArea.self) + } + } + var _11: [Api.PrivacyRule]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + } + } + var _12: Api.StoryViews? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.StoryViews + } + } + var _13: Api.Reaction? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.Reaction + } + } + var _14: [Int32]? + if Int(_1 ?? 0) & Int(1 << 19) != 0 { + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + } + var _15: Api.Document? + if Int(_1 ?? 0) & Int(1 << 20) != 0 { + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.Document + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 19) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.StoryItem.storyItem(Cons_storyItem(flags: _1!, id: _2!, date: _3!, fromId: _4, fwdFrom: _5, expireDate: _6!, caption: _7, entities: _8, media: _9!, mediaAreas: _10, privacy: _11, views: _12, sentReaction: _13, albums: _14, music: _15)) + } + else { + return nil + } + } + public static func parse_storyItemDeleted(_ reader: BufferReader) -> StoryItem? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.StoryItem.storyItemDeleted(Cons_storyItemDeleted(id: _1!)) + } + else { + return nil + } + } + public static func parse_storyItemSkipped(_ reader: BufferReader) -> StoryItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryItem.storyItemSkipped(Cons_storyItemSkipped(flags: _1!, id: _2!, date: _3!, expireDate: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum StoryReaction: TypeConstructorDescription { + public class Cons_storyReaction: TypeConstructorDescription { + public var peerId: Api.Peer + public var date: Int32 + public var reaction: Api.Reaction + public init(peerId: Api.Peer, date: Int32, reaction: Api.Reaction) { + self.peerId = peerId + self.date = date + self.reaction = reaction + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyReaction", [("peerId", ConstructorParameterDescription(self.peerId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) + } + } + public class Cons_storyReactionPublicForward: TypeConstructorDescription { + public var message: Api.Message + public init(message: Api.Message) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyReactionPublicForward", [("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_storyReactionPublicRepost: TypeConstructorDescription { + public var peerId: Api.Peer + public var story: Api.StoryItem + public init(peerId: Api.Peer, story: Api.StoryItem) { + self.peerId = peerId + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyReactionPublicRepost", [("peerId", ConstructorParameterDescription(self.peerId)), ("story", ConstructorParameterDescription(self.story))]) + } + } + case storyReaction(Cons_storyReaction) + case storyReactionPublicForward(Cons_storyReactionPublicForward) + case storyReactionPublicRepost(Cons_storyReactionPublicRepost) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyReaction(let _data): + if boxed { + buffer.appendInt32(1620104917) + } + _data.peerId.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.reaction.serialize(buffer, true) + break + case .storyReactionPublicForward(let _data): + if boxed { + buffer.appendInt32(-1146411453) + } + _data.message.serialize(buffer, true) + break + case .storyReactionPublicRepost(let _data): + if boxed { + buffer.appendInt32(-808644845) + } + _data.peerId.serialize(buffer, true) + _data.story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyReaction(let _data): + return ("storyReaction", [("peerId", ConstructorParameterDescription(_data.peerId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) + case .storyReactionPublicForward(let _data): + return ("storyReactionPublicForward", [("message", ConstructorParameterDescription(_data.message))]) + case .storyReactionPublicRepost(let _data): + return ("storyReactionPublicRepost", [("peerId", ConstructorParameterDescription(_data.peerId)), ("story", ConstructorParameterDescription(_data.story))]) + } + } + + public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryReaction.storyReaction(Cons_storyReaction(peerId: _1!, date: _2!, reaction: _3!)) + } + else { + return nil + } + } + public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.StoryReaction.storyReactionPublicForward(Cons_storyReactionPublicForward(message: _1!)) + } + else { + return nil + } + } + public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? { var _1: Api.Peer? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.Peer @@ -6468,192 +1545,381 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Update.updateStory(Cons_updateStory(peer: _1!, story: _2!)) - } - else { - return nil - } - } - public static func parse_updateStoryID(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateStoryID(Cons_updateStoryID(id: _1!, randomId: _2!)) - } - else { - return nil - } - } - public static func parse_updateTheme(_ reader: BufferReader) -> Update? { - var _1: Api.Theme? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Theme - } - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateTheme(Cons_updateTheme(theme: _1!)) - } - else { - return nil - } - } - public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Update.updateTranscribedAudio(Cons_updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!)) - } - else { - return nil - } - } - public static func parse_updateUser(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateUser(Cons_updateUser(userId: _1!)) - } - else { - return nil - } - } - public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.EmojiStatus? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.EmojiStatus - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserEmojiStatus(Cons_updateUserEmojiStatus(userId: _1!, emojiStatus: _2!)) - } - else { - return nil - } - } - public static func parse_updateUserName(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: [Api.Username]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateUserName(Cons_updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!)) - } - else { - return nil - } - } - public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserPhone(Cons_updateUserPhone(userId: _1!, phone: _2!)) - } - else { - return nil - } - } - public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.UserStatus? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.UserStatus - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Update.updateUserStatus(Cons_updateUserStatus(userId: _1!, status: _2!)) - } - else { - return nil - } - } - public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Api.SendMessageAction? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.SendMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.Update.updateUserTyping(Cons_updateUserTyping(flags: _1!, userId: _2!, topMsgId: _3, action: _4!)) - } - else { - return nil - } - } - public static func parse_updateWebPage(_ reader: BufferReader) -> Update? { - var _1: Api.WebPage? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.WebPage - } - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateWebPage(Cons_updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!)) - } - else { - return nil - } - } - public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.Update.updateWebViewResultSent(Cons_updateWebViewResultSent(queryId: _1!)) + return Api.StoryReaction.storyReactionPublicRepost(Cons_storyReactionPublicRepost(peerId: _1!, story: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum StoryView: TypeConstructorDescription { + public class Cons_storyView: TypeConstructorDescription { + public var flags: Int32 + public var userId: Int64 + public var date: Int32 + public var reaction: Api.Reaction? + public init(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?) { + self.flags = flags + self.userId = userId + self.date = date + self.reaction = reaction + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyView", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date)), ("reaction", ConstructorParameterDescription(self.reaction))]) + } + } + public class Cons_storyViewPublicForward: TypeConstructorDescription { + public var flags: Int32 + public var message: Api.Message + public init(flags: Int32, message: Api.Message) { + self.flags = flags + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyViewPublicForward", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_storyViewPublicRepost: TypeConstructorDescription { + public var flags: Int32 + public var peerId: Api.Peer + public var story: Api.StoryItem + public init(flags: Int32, peerId: Api.Peer, story: Api.StoryItem) { + self.flags = flags + self.peerId = peerId + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyViewPublicRepost", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId)), ("story", ConstructorParameterDescription(self.story))]) + } + } + case storyView(Cons_storyView) + case storyViewPublicForward(Cons_storyViewPublicForward) + case storyViewPublicRepost(Cons_storyViewPublicRepost) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyView(let _data): + if boxed { + buffer.appendInt32(-1329730875) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.reaction!.serialize(buffer, true) + } + break + case .storyViewPublicForward(let _data): + if boxed { + buffer.appendInt32(-1870436597) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.message.serialize(buffer, true) + break + case .storyViewPublicRepost(let _data): + if boxed { + buffer.appendInt32(-1116418231) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peerId.serialize(buffer, true) + _data.story.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyView(let _data): + return ("storyView", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date)), ("reaction", ConstructorParameterDescription(_data.reaction))]) + case .storyViewPublicForward(let _data): + return ("storyViewPublicForward", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message))]) + case .storyViewPublicRepost(let _data): + return ("storyViewPublicRepost", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId)), ("story", ConstructorParameterDescription(_data.story))]) + } + } + + public static func parse_storyView(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Reaction? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Reaction + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.StoryView.storyView(Cons_storyView(flags: _1!, userId: _2!, date: _3!, reaction: _4)) + } + else { + return nil + } + } + public static func parse_storyViewPublicForward(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Message? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.StoryView.storyViewPublicForward(Cons_storyViewPublicForward(flags: _1!, message: _2!)) + } + else { + return nil + } + } + public static func parse_storyViewPublicRepost(_ reader: BufferReader) -> StoryView? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.StoryItem? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.StoryView.storyViewPublicRepost(Cons_storyViewPublicRepost(flags: _1!, peerId: _2!, story: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum StoryViews: TypeConstructorDescription { + public class Cons_storyViews: TypeConstructorDescription { + public var flags: Int32 + public var viewsCount: Int32 + public var forwardsCount: Int32? + public var reactions: [Api.ReactionCount]? + public var reactionsCount: Int32? + public var recentViewers: [Int64]? + public init(flags: Int32, viewsCount: Int32, forwardsCount: Int32?, reactions: [Api.ReactionCount]?, reactionsCount: Int32?, recentViewers: [Int64]?) { + self.flags = flags + self.viewsCount = viewsCount + self.forwardsCount = forwardsCount + self.reactions = reactions + self.reactionsCount = reactionsCount + self.recentViewers = recentViewers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyViews", [("flags", ConstructorParameterDescription(self.flags)), ("viewsCount", ConstructorParameterDescription(self.viewsCount)), ("forwardsCount", ConstructorParameterDescription(self.forwardsCount)), ("reactions", ConstructorParameterDescription(self.reactions)), ("reactionsCount", ConstructorParameterDescription(self.reactionsCount)), ("recentViewers", ConstructorParameterDescription(self.recentViewers))]) + } + } + case storyViews(Cons_storyViews) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let _data): + if boxed { + buffer.appendInt32(-1923523370) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.viewsCount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.forwardsCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.reactions!.count)) + for item in _data.reactions! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.reactionsCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentViewers!.count)) + for item in _data.recentViewers! { + serializeInt64(item, buffer: buffer, boxed: false) + } + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyViews(let _data): + return ("storyViews", [("flags", ConstructorParameterDescription(_data.flags)), ("viewsCount", ConstructorParameterDescription(_data.viewsCount)), ("forwardsCount", ConstructorParameterDescription(_data.forwardsCount)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("reactionsCount", ConstructorParameterDescription(_data.reactionsCount)), ("recentViewers", ConstructorParameterDescription(_data.recentViewers))]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.ReactionCount]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt32() + } + var _6: [Int64]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.StoryViews.storyViews(Cons_storyViews(flags: _1!, viewsCount: _2!, forwardsCount: _3, reactions: _4, reactionsCount: _5, recentViewers: _6)) + } + else { + return nil + } + } + } +} +public extension Api { + enum SuggestedPost: TypeConstructorDescription { + public class Cons_suggestedPost: TypeConstructorDescription { + public var flags: Int32 + public var price: Api.StarsAmount? + public var scheduleDate: Int32? + public init(flags: Int32, price: Api.StarsAmount?, scheduleDate: Int32?) { + self.flags = flags + self.price = price + self.scheduleDate = scheduleDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("suggestedPost", [("flags", ConstructorParameterDescription(self.flags)), ("price", ConstructorParameterDescription(self.price)), ("scheduleDate", ConstructorParameterDescription(self.scheduleDate))]) + } + } + case suggestedPost(Cons_suggestedPost) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedPost(let _data): + if boxed { + buffer.appendInt32(244201445) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.price!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.scheduleDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .suggestedPost(let _data): + return ("suggestedPost", [("flags", ConstructorParameterDescription(_data.flags)), ("price", ConstructorParameterDescription(_data.price)), ("scheduleDate", ConstructorParameterDescription(_data.scheduleDate))]) + } + } + + public static func parse_suggestedPost(_ reader: BufferReader) -> SuggestedPost? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarsAmount? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.SuggestedPost.suggestedPost(Cons_suggestedPost(flags: _1!, price: _2, scheduleDate: _3)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TextWithEntities: TypeConstructorDescription { + public class Cons_textWithEntities: TypeConstructorDescription { + public var text: String + public var entities: [Api.MessageEntity] + public init(text: String, entities: [Api.MessageEntity]) { + self.text = text + self.entities = entities + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("textWithEntities", [("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities))]) + } + } + case textWithEntities(Cons_textWithEntities) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .textWithEntities(let _data): + if boxed { + buffer.appendInt32(1964978502) + } + serializeString(_data.text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities.count)) + for item in _data.entities { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .textWithEntities(let _data): + return ("textWithEntities", [("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities))]) + } + } + + public static func parse_textWithEntities(_ reader: BufferReader) -> TextWithEntities? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TextWithEntities.textWithEntities(Cons_textWithEntities(text: _1!, entities: _2!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index c9cb55d312..8325a5c29c 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1,290 +1,58 @@ public extension Api { - indirect enum Updates: TypeConstructorDescription { - public class Cons_updateShort: TypeConstructorDescription { - public var update: Api.Update - public var date: Int32 - public init(update: Api.Update, date: Int32) { - self.update = update - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateShort", [("update", ConstructorParameterDescription(self.update)), ("date", ConstructorParameterDescription(self.date))]) - } - } - public class Cons_updateShortChatMessage: TypeConstructorDescription { + enum Theme: TypeConstructorDescription { + public class Cons_theme: TypeConstructorDescription { public var flags: Int32 - public var id: Int32 - public var fromId: Int64 - public var chatId: Int64 - public var message: String - public var pts: Int32 - public var ptsCount: Int32 - public var date: Int32 - public var fwdFrom: Api.MessageFwdHeader? - public var viaBotId: Int64? - public var replyTo: Api.MessageReplyHeader? - public var entities: [Api.MessageEntity]? - public var ttlPeriod: Int32? - public init(flags: Int32, id: Int32, fromId: Int64, chatId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { + public var id: Int64 + public var accessHash: Int64 + public var slug: String + public var title: String + public var document: Api.Document? + public var settings: [Api.ThemeSettings]? + public var emoticon: String? + public var installsCount: Int32? + public init(flags: Int32, id: Int64, accessHash: Int64, slug: String, title: String, document: Api.Document?, settings: [Api.ThemeSettings]?, emoticon: String?, installsCount: Int32?) { self.flags = flags self.id = id - self.fromId = fromId - self.chatId = chatId - self.message = message - self.pts = pts - self.ptsCount = ptsCount - self.date = date - self.fwdFrom = fwdFrom - self.viaBotId = viaBotId - self.replyTo = replyTo - self.entities = entities - self.ttlPeriod = ttlPeriod + self.accessHash = accessHash + self.slug = slug + self.title = title + self.document = document + self.settings = settings + self.emoticon = emoticon + self.installsCount = installsCount } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateShortChatMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("chatId", ConstructorParameterDescription(self.chatId)), ("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) + return ("theme", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("slug", ConstructorParameterDescription(self.slug)), ("title", ConstructorParameterDescription(self.title)), ("document", ConstructorParameterDescription(self.document)), ("settings", ConstructorParameterDescription(self.settings)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("installsCount", ConstructorParameterDescription(self.installsCount))]) } } - public class Cons_updateShortMessage: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var userId: Int64 - public var message: String - public var pts: Int32 - public var ptsCount: Int32 - public var date: Int32 - public var fwdFrom: Api.MessageFwdHeader? - public var viaBotId: Int64? - public var replyTo: Api.MessageReplyHeader? - public var entities: [Api.MessageEntity]? - public var ttlPeriod: Int32? - public init(flags: Int32, id: Int32, userId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { - self.flags = flags - self.id = id - self.userId = userId - self.message = message - self.pts = pts - self.ptsCount = ptsCount - self.date = date - self.fwdFrom = fwdFrom - self.viaBotId = viaBotId - self.replyTo = replyTo - self.entities = entities - self.ttlPeriod = ttlPeriod - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateShortMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("userId", ConstructorParameterDescription(self.userId)), ("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) - } - } - public class Cons_updateShortSentMessage: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var pts: Int32 - public var ptsCount: Int32 - public var date: Int32 - public var media: Api.MessageMedia? - public var entities: [Api.MessageEntity]? - public var ttlPeriod: Int32? - public init(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { - self.flags = flags - self.id = id - self.pts = pts - self.ptsCount = ptsCount - self.date = date - self.media = media - self.entities = entities - self.ttlPeriod = ttlPeriod - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updateShortSentMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("media", ConstructorParameterDescription(self.media)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) - } - } - public class Cons_updates: TypeConstructorDescription { - public var updates: [Api.Update] - public var users: [Api.User] - public var chats: [Api.Chat] - public var date: Int32 - public var seq: Int32 - public init(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) { - self.updates = updates - self.users = users - self.chats = chats - self.date = date - self.seq = seq - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updates", [("updates", ConstructorParameterDescription(self.updates)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats)), ("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq))]) - } - } - public class Cons_updatesCombined: TypeConstructorDescription { - public var updates: [Api.Update] - public var users: [Api.User] - public var chats: [Api.Chat] - public var date: Int32 - public var seqStart: Int32 - public var seq: Int32 - public init(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) { - self.updates = updates - self.users = users - self.chats = chats - self.date = date - self.seqStart = seqStart - self.seq = seq - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("updatesCombined", [("updates", ConstructorParameterDescription(self.updates)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats)), ("date", ConstructorParameterDescription(self.date)), ("seqStart", ConstructorParameterDescription(self.seqStart)), ("seq", ConstructorParameterDescription(self.seq))]) - } - } - case updateShort(Cons_updateShort) - case updateShortChatMessage(Cons_updateShortChatMessage) - case updateShortMessage(Cons_updateShortMessage) - case updateShortSentMessage(Cons_updateShortSentMessage) - case updates(Cons_updates) - case updatesCombined(Cons_updatesCombined) - case updatesTooLong + case theme(Cons_theme) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .updateShort(let _data): + case .theme(let _data): if boxed { - buffer.appendInt32(2027216577) - } - _data.update.serialize(buffer, true) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .updateShortChatMessage(let _data): - if boxed { - buffer.appendInt32(1299050149) + buffer.appendInt32(-1609668650) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.fromId, buffer: buffer, boxed: false) - serializeInt64(_data.chatId, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeString(_data.slug, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 2) != 0 { - _data.fwdFrom!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) + _data.document!.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 3) != 0 { - _data.replyTo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { + buffer.appendInt32(Int32(_data.settings!.count)) + for item in _data.settings! { item.serialize(buffer, true) } } - if Int(_data.flags) & Int(1 << 25) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeString(_data.emoticon!, buffer: buffer, boxed: false) } - break - case .updateShortMessage(let _data): - if boxed { - buffer.appendInt32(826001400) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt64(_data.userId, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.fwdFrom!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.replyTo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 25) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - break - case .updateShortSentMessage(let _data): - if boxed { - buffer.appendInt32(-1877614335) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 9) != 0 { - _data.media!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 25) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - break - case .updates(let _data): - if boxed { - buffer.appendInt32(1957577280) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.updates.count)) - for item in _data.updates { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.seq, buffer: buffer, boxed: false) - break - case .updatesCombined(let _data): - if boxed { - buffer.appendInt32(1918567619) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.updates.count)) - for item in _data.updates { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.seqStart, buffer: buffer, boxed: false) - serializeInt32(_data.seq, buffer: buffer, boxed: false) - break - case .updatesTooLong: - if boxed { - buffer.appendInt32(-484987010) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.installsCount!, buffer: buffer, boxed: false) } break } @@ -292,81 +60,4623 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .updateShort(let _data): - return ("updateShort", [("update", ConstructorParameterDescription(_data.update)), ("date", ConstructorParameterDescription(_data.date))]) - case .updateShortChatMessage(let _data): - return ("updateShortChatMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) - case .updateShortMessage(let _data): - return ("updateShortMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("userId", ConstructorParameterDescription(_data.userId)), ("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) - case .updateShortSentMessage(let _data): - return ("updateShortSentMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("media", ConstructorParameterDescription(_data.media)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) - case .updates(let _data): - return ("updates", [("updates", ConstructorParameterDescription(_data.updates)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats)), ("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq))]) - case .updatesCombined(let _data): - return ("updatesCombined", [("updates", ConstructorParameterDescription(_data.updates)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats)), ("date", ConstructorParameterDescription(_data.date)), ("seqStart", ConstructorParameterDescription(_data.seqStart)), ("seq", ConstructorParameterDescription(_data.seq))]) - case .updatesTooLong: - return ("updatesTooLong", []) + case .theme(let _data): + return ("theme", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("slug", ConstructorParameterDescription(_data.slug)), ("title", ConstructorParameterDescription(_data.title)), ("document", ConstructorParameterDescription(_data.document)), ("settings", ConstructorParameterDescription(_data.settings)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("installsCount", ConstructorParameterDescription(_data.installsCount))]) } } - public static func parse_updateShort(_ reader: BufferReader) -> Updates? { - var _1: Api.Update? + public static func parse_theme(_ reader: BufferReader) -> Theme? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.Document? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _7: [Api.ThemeSettings]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ThemeSettings.self) + } + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _8 = parseString(reader) + } + var _9: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _9 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Theme.theme(Cons_theme(flags: _1!, id: _2!, accessHash: _3!, slug: _4!, title: _5!, document: _6, settings: _7, emoticon: _8, installsCount: _9)) + } + else { + return nil + } + } + } +} +public extension Api { + enum ThemeSettings: TypeConstructorDescription { + public class Cons_themeSettings: TypeConstructorDescription { + public var flags: Int32 + public var baseTheme: Api.BaseTheme + public var accentColor: Int32 + public var outboxAccentColor: Int32? + public var messageColors: [Int32]? + public var wallpaper: Api.WallPaper? + public init(flags: Int32, baseTheme: Api.BaseTheme, accentColor: Int32, outboxAccentColor: Int32?, messageColors: [Int32]?, wallpaper: Api.WallPaper?) { + self.flags = flags + self.baseTheme = baseTheme + self.accentColor = accentColor + self.outboxAccentColor = outboxAccentColor + self.messageColors = messageColors + self.wallpaper = wallpaper + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("themeSettings", [("flags", ConstructorParameterDescription(self.flags)), ("baseTheme", ConstructorParameterDescription(self.baseTheme)), ("accentColor", ConstructorParameterDescription(self.accentColor)), ("outboxAccentColor", ConstructorParameterDescription(self.outboxAccentColor)), ("messageColors", ConstructorParameterDescription(self.messageColors)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) + } + } + case themeSettings(Cons_themeSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themeSettings(let _data): + if boxed { + buffer.appendInt32(-94849324) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.baseTheme.serialize(buffer, true) + serializeInt32(_data.accentColor, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.outboxAccentColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messageColors!.count)) + for item in _data.messageColors! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.wallpaper!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .themeSettings(let _data): + return ("themeSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("baseTheme", ConstructorParameterDescription(_data.baseTheme)), ("accentColor", ConstructorParameterDescription(_data.accentColor)), ("outboxAccentColor", ConstructorParameterDescription(_data.outboxAccentColor)), ("messageColors", ConstructorParameterDescription(_data.messageColors)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) + } + } + + public static func parse_themeSettings(_ reader: BufferReader) -> ThemeSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BaseTheme? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Update + _2 = Api.parse(reader, signature: signature) as? Api.BaseTheme + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _4 = reader.readInt32() + } + var _5: [Int32]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + } + var _6: Api.WallPaper? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.ThemeSettings.themeSettings(Cons_themeSettings(flags: _1!, baseTheme: _2!, accentColor: _3!, outboxAccentColor: _4, messageColors: _5, wallpaper: _6)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Timezone: TypeConstructorDescription { + public class Cons_timezone: TypeConstructorDescription { + public var id: String + public var name: String + public var utcOffset: Int32 + public init(id: String, name: String, utcOffset: Int32) { + self.id = id + self.name = name + self.utcOffset = utcOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("timezone", [("id", ConstructorParameterDescription(self.id)), ("name", ConstructorParameterDescription(self.name)), ("utcOffset", ConstructorParameterDescription(self.utcOffset))]) + } + } + case timezone(Cons_timezone) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .timezone(let _data): + if boxed { + buffer.appendInt32(-7173643) + } + serializeString(_data.id, buffer: buffer, boxed: false) + serializeString(_data.name, buffer: buffer, boxed: false) + serializeInt32(_data.utcOffset, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .timezone(let _data): + return ("timezone", [("id", ConstructorParameterDescription(_data.id)), ("name", ConstructorParameterDescription(_data.name)), ("utcOffset", ConstructorParameterDescription(_data.utcOffset))]) + } + } + + public static func parse_timezone(_ reader: BufferReader) -> Timezone? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Timezone.timezone(Cons_timezone(id: _1!, name: _2!, utcOffset: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TodoCompletion: TypeConstructorDescription { + public class Cons_todoCompletion: TypeConstructorDescription { + public var id: Int32 + public var completedBy: Api.Peer + public var date: Int32 + public init(id: Int32, completedBy: Api.Peer, date: Int32) { + self.id = id + self.completedBy = completedBy + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("todoCompletion", [("id", ConstructorParameterDescription(self.id)), ("completedBy", ConstructorParameterDescription(self.completedBy)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case todoCompletion(Cons_todoCompletion) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .todoCompletion(let _data): + if boxed { + buffer.appendInt32(572241380) + } + serializeInt32(_data.id, buffer: buffer, boxed: false) + _data.completedBy.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .todoCompletion(let _data): + return ("todoCompletion", [("id", ConstructorParameterDescription(_data.id)), ("completedBy", ConstructorParameterDescription(_data.completedBy)), ("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_todoCompletion(_ reader: BufferReader) -> TodoCompletion? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.TodoCompletion.todoCompletion(Cons_todoCompletion(id: _1!, completedBy: _2!, date: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TodoItem: TypeConstructorDescription { + public class Cons_todoItem: TypeConstructorDescription { + public var id: Int32 + public var title: Api.TextWithEntities + public init(id: Int32, title: Api.TextWithEntities) { + self.id = id + self.title = title + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("todoItem", [("id", ConstructorParameterDescription(self.id)), ("title", ConstructorParameterDescription(self.title))]) + } + } + case todoItem(Cons_todoItem) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .todoItem(let _data): + if boxed { + buffer.appendInt32(-878074577) + } + serializeInt32(_data.id, buffer: buffer, boxed: false) + _data.title.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .todoItem(let _data): + return ("todoItem", [("id", ConstructorParameterDescription(_data.id)), ("title", ConstructorParameterDescription(_data.title))]) + } + } + + public static func parse_todoItem(_ reader: BufferReader) -> TodoItem? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TodoItem.todoItem(Cons_todoItem(id: _1!, title: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TodoList: TypeConstructorDescription { + public class Cons_todoList: TypeConstructorDescription { + public var flags: Int32 + public var title: Api.TextWithEntities + public var list: [Api.TodoItem] + public init(flags: Int32, title: Api.TextWithEntities, list: [Api.TodoItem]) { + self.flags = flags + self.title = title + self.list = list + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("todoList", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("list", ConstructorParameterDescription(self.list))]) + } + } + case todoList(Cons_todoList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .todoList(let _data): + if boxed { + buffer.appendInt32(1236871718) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.title.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.list.count)) + for item in _data.list { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .todoList(let _data): + return ("todoList", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("list", ConstructorParameterDescription(_data.list))]) + } + } + + public static func parse_todoList(_ reader: BufferReader) -> TodoList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: [Api.TodoItem]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TodoItem.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.TodoList.todoList(Cons_todoList(flags: _1!, title: _2!, list: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TopPeer: TypeConstructorDescription { + public class Cons_topPeer: TypeConstructorDescription { + public var peer: Api.Peer + public var rating: Double + public init(peer: Api.Peer, rating: Double) { + self.peer = peer + self.rating = rating + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("topPeer", [("peer", ConstructorParameterDescription(self.peer)), ("rating", ConstructorParameterDescription(self.rating))]) + } + } + case topPeer(Cons_topPeer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeer(let _data): + if boxed { + buffer.appendInt32(-305282981) + } + _data.peer.serialize(buffer, true) + serializeDouble(_data.rating, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .topPeer(let _data): + return ("topPeer", [("peer", ConstructorParameterDescription(_data.peer)), ("rating", ConstructorParameterDescription(_data.rating))]) + } + } + + public static func parse_topPeer(_ reader: BufferReader) -> TopPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.TopPeer.topPeer(Cons_topPeer(peer: _1!, rating: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum TopPeerCategory: TypeConstructorDescription { + case topPeerCategoryBotsApp + case topPeerCategoryBotsGuestChat + case topPeerCategoryBotsInline + case topPeerCategoryBotsPM + case topPeerCategoryChannels + case topPeerCategoryCorrespondents + case topPeerCategoryForwardChats + case topPeerCategoryForwardUsers + case topPeerCategoryGroups + case topPeerCategoryPhoneCalls + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeerCategoryBotsApp: + if boxed { + buffer.appendInt32(-39945236) + } + break + case .topPeerCategoryBotsGuestChat: + if boxed { + buffer.appendInt32(1814361053) + } + break + case .topPeerCategoryBotsInline: + if boxed { + buffer.appendInt32(344356834) + } + break + case .topPeerCategoryBotsPM: + if boxed { + buffer.appendInt32(-1419371685) + } + break + case .topPeerCategoryChannels: + if boxed { + buffer.appendInt32(371037736) + } + break + case .topPeerCategoryCorrespondents: + if boxed { + buffer.appendInt32(104314861) + } + break + case .topPeerCategoryForwardChats: + if boxed { + buffer.appendInt32(-68239120) + } + break + case .topPeerCategoryForwardUsers: + if boxed { + buffer.appendInt32(-1472172887) + } + break + case .topPeerCategoryGroups: + if boxed { + buffer.appendInt32(-1122524854) + } + break + case .topPeerCategoryPhoneCalls: + if boxed { + buffer.appendInt32(511092620) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .topPeerCategoryBotsApp: + return ("topPeerCategoryBotsApp", []) + case .topPeerCategoryBotsGuestChat: + return ("topPeerCategoryBotsGuestChat", []) + case .topPeerCategoryBotsInline: + return ("topPeerCategoryBotsInline", []) + case .topPeerCategoryBotsPM: + return ("topPeerCategoryBotsPM", []) + case .topPeerCategoryChannels: + return ("topPeerCategoryChannels", []) + case .topPeerCategoryCorrespondents: + return ("topPeerCategoryCorrespondents", []) + case .topPeerCategoryForwardChats: + return ("topPeerCategoryForwardChats", []) + case .topPeerCategoryForwardUsers: + return ("topPeerCategoryForwardUsers", []) + case .topPeerCategoryGroups: + return ("topPeerCategoryGroups", []) + case .topPeerCategoryPhoneCalls: + return ("topPeerCategoryPhoneCalls", []) + } + } + + public static func parse_topPeerCategoryBotsApp(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsApp + } + public static func parse_topPeerCategoryBotsGuestChat(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsGuestChat + } + public static func parse_topPeerCategoryBotsInline(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsInline + } + public static func parse_topPeerCategoryBotsPM(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryBotsPM + } + public static func parse_topPeerCategoryChannels(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryChannels + } + public static func parse_topPeerCategoryCorrespondents(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryCorrespondents + } + public static func parse_topPeerCategoryForwardChats(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryForwardChats + } + public static func parse_topPeerCategoryForwardUsers(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryForwardUsers + } + public static func parse_topPeerCategoryGroups(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryGroups + } + public static func parse_topPeerCategoryPhoneCalls(_ reader: BufferReader) -> TopPeerCategory? { + return Api.TopPeerCategory.topPeerCategoryPhoneCalls + } + } +} +public extension Api { + enum TopPeerCategoryPeers: TypeConstructorDescription { + public class Cons_topPeerCategoryPeers: TypeConstructorDescription { + public var category: Api.TopPeerCategory + public var count: Int32 + public var peers: [Api.TopPeer] + public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) { + self.category = category + self.count = count + self.peers = peers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("topPeerCategoryPeers", [("category", ConstructorParameterDescription(self.category)), ("count", ConstructorParameterDescription(self.count)), ("peers", ConstructorParameterDescription(self.peers))]) + } + } + case topPeerCategoryPeers(Cons_topPeerCategoryPeers) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeerCategoryPeers(let _data): + if boxed { + buffer.appendInt32(-75283823) + } + _data.category.serialize(buffer, true) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .topPeerCategoryPeers(let _data): + return ("topPeerCategoryPeers", [("category", ConstructorParameterDescription(_data.category)), ("count", ConstructorParameterDescription(_data.count)), ("peers", ConstructorParameterDescription(_data.peers))]) + } + } + + public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? { + var _1: Api.TopPeerCategory? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.TopPeer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum Update: TypeConstructorDescription { + public class Cons_updateBotBusinessConnect: TypeConstructorDescription { + public var connection: Api.BotBusinessConnection + public var qts: Int32 + public init(connection: Api.BotBusinessConnection, qts: Int32) { + self.connection = connection + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotBusinessConnect", [("connection", ConstructorParameterDescription(self.connection)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotCallbackQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var userId: Int64 + public var peer: Api.Peer + public var msgId: Int32 + public var chatInstance: Int64 + public var data: Buffer? + public var gameShortName: String? + public init(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?) { + self.flags = flags + self.queryId = queryId + self.userId = userId + self.peer = peer + self.msgId = msgId + self.chatInstance = chatInstance + self.data = data + self.gameShortName = gameShortName + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data)), ("gameShortName", ConstructorParameterDescription(self.gameShortName))]) + } + } + public class Cons_updateBotChatBoost: TypeConstructorDescription { + public var peer: Api.Peer + public var boost: Api.Boost + public var qts: Int32 + public init(peer: Api.Peer, boost: Api.Boost, qts: Int32) { + self.peer = peer + self.boost = boost + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotChatBoost", [("peer", ConstructorParameterDescription(self.peer)), ("boost", ConstructorParameterDescription(self.boost)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotChatInviteRequester: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var date: Int32 + public var userId: Int64 + public var about: String + public var invite: Api.ExportedChatInvite + public var qts: Int32 + public var queryId: Int64? + public init(flags: Int32, peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32, queryId: Int64?) { + self.flags = flags + self.peer = peer + self.date = date + self.userId = userId + self.about = about + self.invite = invite + self.qts = qts + self.queryId = queryId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotChatInviteRequester", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("date", ConstructorParameterDescription(self.date)), ("userId", ConstructorParameterDescription(self.userId)), ("about", ConstructorParameterDescription(self.about)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts)), ("queryId", ConstructorParameterDescription(self.queryId))]) + } + } + public class Cons_updateBotCommands: TypeConstructorDescription { + public var peer: Api.Peer + public var botId: Int64 + public var commands: [Api.BotCommand] + public init(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) { + self.peer = peer + self.botId = botId + self.commands = commands + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotCommands", [("peer", ConstructorParameterDescription(self.peer)), ("botId", ConstructorParameterDescription(self.botId)), ("commands", ConstructorParameterDescription(self.commands))]) + } + } + public class Cons_updateBotDeleteBusinessMessage: TypeConstructorDescription { + public var connectionId: String + public var peer: Api.Peer + public var messages: [Int32] + public var qts: Int32 + public init(connectionId: String, peer: Api.Peer, messages: [Int32], qts: Int32) { + self.connectionId = connectionId + self.peer = peer + self.messages = messages + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotDeleteBusinessMessage", [("connectionId", ConstructorParameterDescription(self.connectionId)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotEditBusinessMessage: TypeConstructorDescription { + public var flags: Int32 + public var connectionId: String + public var message: Api.Message + public var replyToMessage: Api.Message? + public var qts: Int32 + public init(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) { + self.flags = flags + self.connectionId = connectionId + self.message = message + self.replyToMessage = replyToMessage + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotEditBusinessMessage", [("flags", ConstructorParameterDescription(self.flags)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotGuestChatQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var message: Api.Message + public var referenceMessages: [Api.Message]? + public var qts: Int32 + public init(flags: Int32, queryId: Int64, message: Api.Message, referenceMessages: [Api.Message]?, qts: Int32) { + self.flags = flags + self.queryId = queryId + self.message = message + self.referenceMessages = referenceMessages + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotGuestChatQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("message", ConstructorParameterDescription(self.message)), ("referenceMessages", ConstructorParameterDescription(self.referenceMessages)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotInlineQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var userId: Int64 + public var query: String + public var geo: Api.GeoPoint? + public var peerType: Api.InlineQueryPeerType? + public var offset: String + public init(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) { + self.flags = flags + self.queryId = queryId + self.userId = userId + self.query = query + self.geo = geo + self.peerType = peerType + self.offset = offset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotInlineQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("query", ConstructorParameterDescription(self.query)), ("geo", ConstructorParameterDescription(self.geo)), ("peerType", ConstructorParameterDescription(self.peerType)), ("offset", ConstructorParameterDescription(self.offset))]) + } + } + public class Cons_updateBotInlineSend: TypeConstructorDescription { + public var flags: Int32 + public var userId: Int64 + public var query: String + public var geo: Api.GeoPoint? + public var id: String + public var msgId: Api.InputBotInlineMessageID? + public init(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) { + self.flags = flags + self.userId = userId + self.query = query + self.geo = geo + self.id = id + self.msgId = msgId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotInlineSend", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("query", ConstructorParameterDescription(self.query)), ("geo", ConstructorParameterDescription(self.geo)), ("id", ConstructorParameterDescription(self.id)), ("msgId", ConstructorParameterDescription(self.msgId))]) + } + } + public class Cons_updateBotMenuButton: TypeConstructorDescription { + public var botId: Int64 + public var button: Api.BotMenuButton + public init(botId: Int64, button: Api.BotMenuButton) { + self.botId = botId + self.button = button + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotMenuButton", [("botId", ConstructorParameterDescription(self.botId)), ("button", ConstructorParameterDescription(self.button))]) + } + } + public class Cons_updateBotMessageReaction: TypeConstructorDescription { + public var peer: Api.Peer + public var msgId: Int32 + public var date: Int32 + public var actor: Api.Peer + public var oldReactions: [Api.Reaction] + public var newReactions: [Api.Reaction] + public var qts: Int32 + public init(peer: Api.Peer, msgId: Int32, date: Int32, actor: Api.Peer, oldReactions: [Api.Reaction], newReactions: [Api.Reaction], qts: Int32) { + self.peer = peer + self.msgId = msgId + self.date = date + self.actor = actor + self.oldReactions = oldReactions + self.newReactions = newReactions + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotMessageReaction", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("actor", ConstructorParameterDescription(self.actor)), ("oldReactions", ConstructorParameterDescription(self.oldReactions)), ("newReactions", ConstructorParameterDescription(self.newReactions)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotMessageReactions: TypeConstructorDescription { + public var peer: Api.Peer + public var msgId: Int32 + public var date: Int32 + public var reactions: [Api.ReactionCount] + public var qts: Int32 + public init(peer: Api.Peer, msgId: Int32, date: Int32, reactions: [Api.ReactionCount], qts: Int32) { + self.peer = peer + self.msgId = msgId + self.date = date + self.reactions = reactions + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotMessageReactions", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("date", ConstructorParameterDescription(self.date)), ("reactions", ConstructorParameterDescription(self.reactions)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotNewBusinessMessage: TypeConstructorDescription { + public var flags: Int32 + public var connectionId: String + public var message: Api.Message + public var replyToMessage: Api.Message? + public var qts: Int32 + public init(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) { + self.flags = flags + self.connectionId = connectionId + self.message = message + self.replyToMessage = replyToMessage + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotNewBusinessMessage", [("flags", ConstructorParameterDescription(self.flags)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotPrecheckoutQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var userId: Int64 + public var payload: Buffer + public var info: Api.PaymentRequestedInfo? + public var shippingOptionId: String? + public var currency: String + public var totalAmount: Int64 + public init(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) { + self.flags = flags + self.queryId = queryId + self.userId = userId + self.payload = payload + self.info = info + self.shippingOptionId = shippingOptionId + self.currency = currency + self.totalAmount = totalAmount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotPrecheckoutQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("info", ConstructorParameterDescription(self.info)), ("shippingOptionId", ConstructorParameterDescription(self.shippingOptionId)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount))]) + } + } + public class Cons_updateBotPurchasedPaidMedia: TypeConstructorDescription { + public var userId: Int64 + public var payload: String + public var qts: Int32 + public init(userId: Int64, payload: String, qts: Int32) { + self.userId = userId + self.payload = payload + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotPurchasedPaidMedia", [("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotShippingQuery: TypeConstructorDescription { + public var queryId: Int64 + public var userId: Int64 + public var payload: Buffer + public var shippingAddress: Api.PostAddress + public init(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) { + self.queryId = queryId + self.userId = userId + self.payload = payload + self.shippingAddress = shippingAddress + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotShippingQuery", [("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("payload", ConstructorParameterDescription(self.payload)), ("shippingAddress", ConstructorParameterDescription(self.shippingAddress))]) + } + } + public class Cons_updateBotStopped: TypeConstructorDescription { + public var userId: Int64 + public var date: Int32 + public var stopped: Api.Bool + public var qts: Int32 + public init(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) { + self.userId = userId + self.date = date + self.stopped = stopped + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotStopped", [("userId", ConstructorParameterDescription(self.userId)), ("date", ConstructorParameterDescription(self.date)), ("stopped", ConstructorParameterDescription(self.stopped)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateBotWebhookJSON: TypeConstructorDescription { + public var data: Api.DataJSON + public init(data: Api.DataJSON) { + self.data = data + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotWebhookJSON", [("data", ConstructorParameterDescription(self.data))]) + } + } + public class Cons_updateBotWebhookJSONQuery: TypeConstructorDescription { + public var queryId: Int64 + public var data: Api.DataJSON + public var timeout: Int32 + public init(queryId: Int64, data: Api.DataJSON, timeout: Int32) { + self.queryId = queryId + self.data = data + self.timeout = timeout + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBotWebhookJSONQuery", [("queryId", ConstructorParameterDescription(self.queryId)), ("data", ConstructorParameterDescription(self.data)), ("timeout", ConstructorParameterDescription(self.timeout))]) + } + } + public class Cons_updateBusinessBotCallbackQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var userId: Int64 + public var connectionId: String + public var message: Api.Message + public var replyToMessage: Api.Message? + public var chatInstance: Int64 + public var data: Buffer? + public init(flags: Int32, queryId: Int64, userId: Int64, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, chatInstance: Int64, data: Buffer?) { + self.flags = flags + self.queryId = queryId + self.userId = userId + self.connectionId = connectionId + self.message = message + self.replyToMessage = replyToMessage + self.chatInstance = chatInstance + self.data = data + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateBusinessBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("connectionId", ConstructorParameterDescription(self.connectionId)), ("message", ConstructorParameterDescription(self.message)), ("replyToMessage", ConstructorParameterDescription(self.replyToMessage)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data))]) + } + } + public class Cons_updateChannel: TypeConstructorDescription { + public var channelId: Int64 + public init(channelId: Int64) { + self.channelId = channelId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannel", [("channelId", ConstructorParameterDescription(self.channelId))]) + } + } + public class Cons_updateChannelAvailableMessages: TypeConstructorDescription { + public var channelId: Int64 + public var availableMinId: Int32 + public init(channelId: Int64, availableMinId: Int32) { + self.channelId = channelId + self.availableMinId = availableMinId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelAvailableMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("availableMinId", ConstructorParameterDescription(self.availableMinId))]) + } + } + public class Cons_updateChannelMessageForwards: TypeConstructorDescription { + public var channelId: Int64 + public var id: Int32 + public var forwards: Int32 + public init(channelId: Int64, id: Int32, forwards: Int32) { + self.channelId = channelId + self.id = id + self.forwards = forwards + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelMessageForwards", [("channelId", ConstructorParameterDescription(self.channelId)), ("id", ConstructorParameterDescription(self.id)), ("forwards", ConstructorParameterDescription(self.forwards))]) + } + } + public class Cons_updateChannelMessageViews: TypeConstructorDescription { + public var channelId: Int64 + public var id: Int32 + public var views: Int32 + public init(channelId: Int64, id: Int32, views: Int32) { + self.channelId = channelId + self.id = id + self.views = views + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelMessageViews", [("channelId", ConstructorParameterDescription(self.channelId)), ("id", ConstructorParameterDescription(self.id)), ("views", ConstructorParameterDescription(self.views))]) + } + } + public class Cons_updateChannelParticipant: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var date: Int32 + public var actorId: Int64 + public var userId: Int64 + public var prevParticipant: Api.ChannelParticipant? + public var newParticipant: Api.ChannelParticipant? + public var invite: Api.ExportedChatInvite? + public var qts: Int32 + public init(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) { + self.flags = flags + self.channelId = channelId + self.date = date + self.actorId = actorId + self.userId = userId + self.prevParticipant = prevParticipant + self.newParticipant = newParticipant + self.invite = invite + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelParticipant", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("date", ConstructorParameterDescription(self.date)), ("actorId", ConstructorParameterDescription(self.actorId)), ("userId", ConstructorParameterDescription(self.userId)), ("prevParticipant", ConstructorParameterDescription(self.prevParticipant)), ("newParticipant", ConstructorParameterDescription(self.newParticipant)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateChannelReadMessagesContents: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var topMsgId: Int32? + public var savedPeerId: Api.Peer? + public var messages: [Int32] + public init(flags: Int32, channelId: Int64, topMsgId: Int32?, savedPeerId: Api.Peer?, messages: [Int32]) { + self.flags = flags + self.channelId = channelId + self.topMsgId = topMsgId + self.savedPeerId = savedPeerId + self.messages = messages + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelReadMessagesContents", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("messages", ConstructorParameterDescription(self.messages))]) + } + } + public class Cons_updateChannelTooLong: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var pts: Int32? + public init(flags: Int32, channelId: Int64, pts: Int32?) { + self.flags = flags + self.channelId = channelId + self.pts = pts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelTooLong", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("pts", ConstructorParameterDescription(self.pts))]) + } + } + public class Cons_updateChannelUserTyping: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var topMsgId: Int32? + public var fromId: Api.Peer + public var action: Api.SendMessageAction + public init(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) { + self.flags = flags + self.channelId = channelId + self.topMsgId = topMsgId + self.fromId = fromId + self.action = action + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelUserTyping", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("fromId", ConstructorParameterDescription(self.fromId)), ("action", ConstructorParameterDescription(self.action))]) + } + } + public class Cons_updateChannelViewForumAsMessages: TypeConstructorDescription { + public var channelId: Int64 + public var enabled: Api.Bool + public init(channelId: Int64, enabled: Api.Bool) { + self.channelId = channelId + self.enabled = enabled + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelViewForumAsMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("enabled", ConstructorParameterDescription(self.enabled))]) + } + } + public class Cons_updateChannelWebPage: TypeConstructorDescription { + public var channelId: Int64 + public var webpage: Api.WebPage + public var pts: Int32 + public var ptsCount: Int32 + public init(channelId: Int64, webpage: Api.WebPage, pts: Int32, ptsCount: Int32) { + self.channelId = channelId + self.webpage = webpage + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChannelWebPage", [("channelId", ConstructorParameterDescription(self.channelId)), ("webpage", ConstructorParameterDescription(self.webpage)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateChat: TypeConstructorDescription { + public var chatId: Int64 + public init(chatId: Int64) { + self.chatId = chatId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChat", [("chatId", ConstructorParameterDescription(self.chatId))]) + } + } + public class Cons_updateChatDefaultBannedRights: TypeConstructorDescription { + public var peer: Api.Peer + public var defaultBannedRights: Api.ChatBannedRights + public var version: Int32 + public init(peer: Api.Peer, defaultBannedRights: Api.ChatBannedRights, version: Int32) { + self.peer = peer + self.defaultBannedRights = defaultBannedRights + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatDefaultBannedRights", [("peer", ConstructorParameterDescription(self.peer)), ("defaultBannedRights", ConstructorParameterDescription(self.defaultBannedRights)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateChatParticipant: TypeConstructorDescription { + public var flags: Int32 + public var chatId: Int64 + public var date: Int32 + public var actorId: Int64 + public var userId: Int64 + public var prevParticipant: Api.ChatParticipant? + public var newParticipant: Api.ChatParticipant? + public var invite: Api.ExportedChatInvite? + public var qts: Int32 + public init(flags: Int32, chatId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChatParticipant?, newParticipant: Api.ChatParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) { + self.flags = flags + self.chatId = chatId + self.date = date + self.actorId = actorId + self.userId = userId + self.prevParticipant = prevParticipant + self.newParticipant = newParticipant + self.invite = invite + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipant", [("flags", ConstructorParameterDescription(self.flags)), ("chatId", ConstructorParameterDescription(self.chatId)), ("date", ConstructorParameterDescription(self.date)), ("actorId", ConstructorParameterDescription(self.actorId)), ("userId", ConstructorParameterDescription(self.userId)), ("prevParticipant", ConstructorParameterDescription(self.prevParticipant)), ("newParticipant", ConstructorParameterDescription(self.newParticipant)), ("invite", ConstructorParameterDescription(self.invite)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateChatParticipantAdd: TypeConstructorDescription { + public var chatId: Int64 + public var userId: Int64 + public var inviterId: Int64 + public var date: Int32 + public var version: Int32 + public init(chatId: Int64, userId: Int64, inviterId: Int64, date: Int32, version: Int32) { + self.chatId = chatId + self.userId = userId + self.inviterId = inviterId + self.date = date + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipantAdd", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("inviterId", ConstructorParameterDescription(self.inviterId)), ("date", ConstructorParameterDescription(self.date)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateChatParticipantAdmin: TypeConstructorDescription { + public var chatId: Int64 + public var userId: Int64 + public var isAdmin: Api.Bool + public var version: Int32 + public init(chatId: Int64, userId: Int64, isAdmin: Api.Bool, version: Int32) { + self.chatId = chatId + self.userId = userId + self.isAdmin = isAdmin + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipantAdmin", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("isAdmin", ConstructorParameterDescription(self.isAdmin)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateChatParticipantDelete: TypeConstructorDescription { + public var chatId: Int64 + public var userId: Int64 + public var version: Int32 + public init(chatId: Int64, userId: Int64, version: Int32) { + self.chatId = chatId + self.userId = userId + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipantDelete", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateChatParticipantRank: TypeConstructorDescription { + public var chatId: Int64 + public var userId: Int64 + public var rank: String + public var version: Int32 + public init(chatId: Int64, userId: Int64, rank: String, version: Int32) { + self.chatId = chatId + self.userId = userId + self.rank = rank + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipantRank", [("chatId", ConstructorParameterDescription(self.chatId)), ("userId", ConstructorParameterDescription(self.userId)), ("rank", ConstructorParameterDescription(self.rank)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateChatParticipants: TypeConstructorDescription { + public var participants: Api.ChatParticipants + public init(participants: Api.ChatParticipants) { + self.participants = participants + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatParticipants", [("participants", ConstructorParameterDescription(self.participants))]) + } + } + public class Cons_updateChatUserTyping: TypeConstructorDescription { + public var chatId: Int64 + public var fromId: Api.Peer + public var action: Api.SendMessageAction + public init(chatId: Int64, fromId: Api.Peer, action: Api.SendMessageAction) { + self.chatId = chatId + self.fromId = fromId + self.action = action + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateChatUserTyping", [("chatId", ConstructorParameterDescription(self.chatId)), ("fromId", ConstructorParameterDescription(self.fromId)), ("action", ConstructorParameterDescription(self.action))]) + } + } + public class Cons_updateDcOptions: TypeConstructorDescription { + public var dcOptions: [Api.DcOption] + public init(dcOptions: [Api.DcOption]) { + self.dcOptions = dcOptions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDcOptions", [("dcOptions", ConstructorParameterDescription(self.dcOptions))]) + } + } + public class Cons_updateDeleteChannelMessages: TypeConstructorDescription { + public var channelId: Int64 + public var messages: [Int32] + public var pts: Int32 + public var ptsCount: Int32 + public init(channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) { + self.channelId = channelId + self.messages = messages + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteChannelMessages", [("channelId", ConstructorParameterDescription(self.channelId)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateDeleteGroupCallMessages: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var messages: [Int32] + public init(call: Api.InputGroupCall, messages: [Int32]) { + self.call = call + self.messages = messages + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteGroupCallMessages", [("call", ConstructorParameterDescription(self.call)), ("messages", ConstructorParameterDescription(self.messages))]) + } + } + public class Cons_updateDeleteMessages: TypeConstructorDescription { + public var messages: [Int32] + public var pts: Int32 + public var ptsCount: Int32 + public init(messages: [Int32], pts: Int32, ptsCount: Int32) { + self.messages = messages + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteMessages", [("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateDeleteQuickReply: TypeConstructorDescription { + public var shortcutId: Int32 + public init(shortcutId: Int32) { + self.shortcutId = shortcutId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteQuickReply", [("shortcutId", ConstructorParameterDescription(self.shortcutId))]) + } + } + public class Cons_updateDeleteQuickReplyMessages: TypeConstructorDescription { + public var shortcutId: Int32 + public var messages: [Int32] + public init(shortcutId: Int32, messages: [Int32]) { + self.shortcutId = shortcutId + self.messages = messages + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteQuickReplyMessages", [("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("messages", ConstructorParameterDescription(self.messages))]) + } + } + public class Cons_updateDeleteScheduledMessages: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var messages: [Int32] + public var sentMessages: [Int32]? + public init(flags: Int32, peer: Api.Peer, messages: [Int32], sentMessages: [Int32]?) { + self.flags = flags + self.peer = peer + self.messages = messages + self.sentMessages = sentMessages + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDeleteScheduledMessages", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("sentMessages", ConstructorParameterDescription(self.sentMessages))]) + } + } + public class Cons_updateDialogFilter: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var filter: Api.DialogFilter? + public init(flags: Int32, id: Int32, filter: Api.DialogFilter?) { + self.flags = flags + self.id = id + self.filter = filter + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDialogFilter", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("filter", ConstructorParameterDescription(self.filter))]) + } + } + public class Cons_updateDialogFilterOrder: TypeConstructorDescription { + public var order: [Int32] + public init(order: [Int32]) { + self.order = order + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDialogFilterOrder", [("order", ConstructorParameterDescription(self.order))]) + } + } + public class Cons_updateDialogPinned: TypeConstructorDescription { + public var flags: Int32 + public var folderId: Int32? + public var peer: Api.DialogPeer + public init(flags: Int32, folderId: Int32?, peer: Api.DialogPeer) { + self.flags = flags + self.folderId = folderId + self.peer = peer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDialogPinned", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("peer", ConstructorParameterDescription(self.peer))]) + } + } + public class Cons_updateDialogUnreadMark: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.DialogPeer + public var savedPeerId: Api.Peer? + public init(flags: Int32, peer: Api.DialogPeer, savedPeerId: Api.Peer?) { + self.flags = flags + self.peer = peer + self.savedPeerId = savedPeerId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDialogUnreadMark", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId))]) + } + } + public class Cons_updateDraftMessage: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var topMsgId: Int32? + public var savedPeerId: Api.Peer? + public var draft: Api.DraftMessage + public init(flags: Int32, peer: Api.Peer, topMsgId: Int32?, savedPeerId: Api.Peer?, draft: Api.DraftMessage) { + self.flags = flags + self.peer = peer + self.topMsgId = topMsgId + self.savedPeerId = savedPeerId + self.draft = draft + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateDraftMessage", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("draft", ConstructorParameterDescription(self.draft))]) + } + } + public class Cons_updateEditChannelMessage: TypeConstructorDescription { + public var message: Api.Message + public var pts: Int32 + public var ptsCount: Int32 + public init(message: Api.Message, pts: Int32, ptsCount: Int32) { + self.message = message + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEditChannelMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateEditMessage: TypeConstructorDescription { + public var message: Api.Message + public var pts: Int32 + public var ptsCount: Int32 + public init(message: Api.Message, pts: Int32, ptsCount: Int32) { + self.message = message + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEditMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateEmojiGameInfo: TypeConstructorDescription { + public var info: Api.messages.EmojiGameInfo + public init(info: Api.messages.EmojiGameInfo) { + self.info = info + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEmojiGameInfo", [("info", ConstructorParameterDescription(self.info))]) + } + } + public class Cons_updateEncryptedChatTyping: TypeConstructorDescription { + public var chatId: Int32 + public init(chatId: Int32) { + self.chatId = chatId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEncryptedChatTyping", [("chatId", ConstructorParameterDescription(self.chatId))]) + } + } + public class Cons_updateEncryptedMessagesRead: TypeConstructorDescription { + public var chatId: Int32 + public var maxDate: Int32 + public var date: Int32 + public init(chatId: Int32, maxDate: Int32, date: Int32) { + self.chatId = chatId + self.maxDate = maxDate + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEncryptedMessagesRead", [("chatId", ConstructorParameterDescription(self.chatId)), ("maxDate", ConstructorParameterDescription(self.maxDate)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_updateEncryption: TypeConstructorDescription { + public var chat: Api.EncryptedChat + public var date: Int32 + public init(chat: Api.EncryptedChat, date: Int32) { + self.chat = chat + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateEncryption", [("chat", ConstructorParameterDescription(self.chat)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_updateFolderPeers: TypeConstructorDescription { + public var folderPeers: [Api.FolderPeer] + public var pts: Int32 + public var ptsCount: Int32 + public init(folderPeers: [Api.FolderPeer], pts: Int32, ptsCount: Int32) { + self.folderPeers = folderPeers + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateFolderPeers", [("folderPeers", ConstructorParameterDescription(self.folderPeers)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateGeoLiveViewed: TypeConstructorDescription { + public var peer: Api.Peer + public var msgId: Int32 + public init(peer: Api.Peer, msgId: Int32) { + self.peer = peer + self.msgId = msgId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGeoLiveViewed", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId))]) + } + } + public class Cons_updateGroupCall: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer? + public var call: Api.GroupCall + public init(flags: Int32, peer: Api.Peer?, call: Api.GroupCall) { + self.flags = flags + self.peer = peer + self.call = call + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCall", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("call", ConstructorParameterDescription(self.call))]) + } + } + public class Cons_updateGroupCallChainBlocks: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var subChainId: Int32 + public var blocks: [Buffer] + public var nextOffset: Int32 + public init(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32) { + self.call = call + self.subChainId = subChainId + self.blocks = blocks + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCallChainBlocks", [("call", ConstructorParameterDescription(self.call)), ("subChainId", ConstructorParameterDescription(self.subChainId)), ("blocks", ConstructorParameterDescription(self.blocks)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + public class Cons_updateGroupCallConnection: TypeConstructorDescription { + public var flags: Int32 + public var params: Api.DataJSON + public init(flags: Int32, params: Api.DataJSON) { + self.flags = flags + self.params = params + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCallConnection", [("flags", ConstructorParameterDescription(self.flags)), ("params", ConstructorParameterDescription(self.params))]) + } + } + public class Cons_updateGroupCallEncryptedMessage: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var fromId: Api.Peer + public var encryptedMessage: Buffer + public init(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer) { + self.call = call + self.fromId = fromId + self.encryptedMessage = encryptedMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCallEncryptedMessage", [("call", ConstructorParameterDescription(self.call)), ("fromId", ConstructorParameterDescription(self.fromId)), ("encryptedMessage", ConstructorParameterDescription(self.encryptedMessage))]) + } + } + public class Cons_updateGroupCallMessage: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var message: Api.GroupCallMessage + public init(call: Api.InputGroupCall, message: Api.GroupCallMessage) { + self.call = call + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCallMessage", [("call", ConstructorParameterDescription(self.call)), ("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_updateGroupCallParticipants: TypeConstructorDescription { + public var call: Api.InputGroupCall + public var participants: [Api.GroupCallParticipant] + public var version: Int32 + public init(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) { + self.call = call + self.participants = participants + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateGroupCallParticipants", [("call", ConstructorParameterDescription(self.call)), ("participants", ConstructorParameterDescription(self.participants)), ("version", ConstructorParameterDescription(self.version))]) + } + } + public class Cons_updateInlineBotCallbackQuery: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64 + public var userId: Int64 + public var msgId: Api.InputBotInlineMessageID + public var chatInstance: Int64 + public var data: Buffer? + public var gameShortName: String? + public init(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) { + self.flags = flags + self.queryId = queryId + self.userId = userId + self.msgId = msgId + self.chatInstance = chatInstance + self.data = data + self.gameShortName = gameShortName + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateInlineBotCallbackQuery", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("userId", ConstructorParameterDescription(self.userId)), ("msgId", ConstructorParameterDescription(self.msgId)), ("chatInstance", ConstructorParameterDescription(self.chatInstance)), ("data", ConstructorParameterDescription(self.data)), ("gameShortName", ConstructorParameterDescription(self.gameShortName))]) + } + } + public class Cons_updateJoinChatWebViewDecision: TypeConstructorDescription { + public var peer: Api.Peer + public var queryId: Int64 + public var result: Api.JoinChatBotResult + public init(peer: Api.Peer, queryId: Int64, result: Api.JoinChatBotResult) { + self.peer = peer + self.queryId = queryId + self.result = result + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateJoinChatWebViewDecision", [("peer", ConstructorParameterDescription(self.peer)), ("queryId", ConstructorParameterDescription(self.queryId)), ("result", ConstructorParameterDescription(self.result))]) + } + } + public class Cons_updateLangPack: TypeConstructorDescription { + public var difference: Api.LangPackDifference + public init(difference: Api.LangPackDifference) { + self.difference = difference + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateLangPack", [("difference", ConstructorParameterDescription(self.difference))]) + } + } + public class Cons_updateLangPackTooLong: TypeConstructorDescription { + public var langCode: String + public init(langCode: String) { + self.langCode = langCode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateLangPackTooLong", [("langCode", ConstructorParameterDescription(self.langCode))]) + } + } + public class Cons_updateManagedBot: TypeConstructorDescription { + public var userId: Int64 + public var botId: Int64 + public var qts: Int32 + public init(userId: Int64, botId: Int64, qts: Int32) { + self.userId = userId + self.botId = botId + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateManagedBot", [("userId", ConstructorParameterDescription(self.userId)), ("botId", ConstructorParameterDescription(self.botId)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateMessageExtendedMedia: TypeConstructorDescription { + public var peer: Api.Peer + public var msgId: Int32 + public var extendedMedia: [Api.MessageExtendedMedia] + public init(peer: Api.Peer, msgId: Int32, extendedMedia: [Api.MessageExtendedMedia]) { + self.peer = peer + self.msgId = msgId + self.extendedMedia = extendedMedia + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMessageExtendedMedia", [("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("extendedMedia", ConstructorParameterDescription(self.extendedMedia))]) + } + } + public class Cons_updateMessageID: TypeConstructorDescription { + public var id: Int32 + public var randomId: Int64 + public init(id: Int32, randomId: Int64) { + self.id = id + self.randomId = randomId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMessageID", [("id", ConstructorParameterDescription(self.id)), ("randomId", ConstructorParameterDescription(self.randomId))]) + } + } + public class Cons_updateMessagePoll: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer? + public var msgId: Int32? + public var topMsgId: Int32? + public var pollId: Int64 + public var poll: Api.Poll? + public var results: Api.PollResults + public init(flags: Int32, peer: Api.Peer?, msgId: Int32?, topMsgId: Int32?, pollId: Int64, poll: Api.Poll?, results: Api.PollResults) { + self.flags = flags + self.peer = peer + self.msgId = msgId + self.topMsgId = topMsgId + self.pollId = pollId + self.poll = poll + self.results = results + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMessagePoll", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("pollId", ConstructorParameterDescription(self.pollId)), ("poll", ConstructorParameterDescription(self.poll)), ("results", ConstructorParameterDescription(self.results))]) + } + } + public class Cons_updateMessagePollVote: TypeConstructorDescription { + public var pollId: Int64 + public var peer: Api.Peer + public var options: [Buffer] + public var positions: [Int32] + public var qts: Int32 + public init(pollId: Int64, peer: Api.Peer, options: [Buffer], positions: [Int32], qts: Int32) { + self.pollId = pollId + self.peer = peer + self.options = options + self.positions = positions + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMessagePollVote", [("pollId", ConstructorParameterDescription(self.pollId)), ("peer", ConstructorParameterDescription(self.peer)), ("options", ConstructorParameterDescription(self.options)), ("positions", ConstructorParameterDescription(self.positions)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateMessageReactions: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var msgId: Int32 + public var topMsgId: Int32? + public var savedPeerId: Api.Peer? + public var reactions: Api.MessageReactions + public init(flags: Int32, peer: Api.Peer, msgId: Int32, topMsgId: Int32?, savedPeerId: Api.Peer?, reactions: Api.MessageReactions) { + self.flags = flags + self.peer = peer + self.msgId = msgId + self.topMsgId = topMsgId + self.savedPeerId = savedPeerId + self.reactions = reactions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMessageReactions", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("reactions", ConstructorParameterDescription(self.reactions))]) + } + } + public class Cons_updateMonoForumNoPaidException: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var savedPeerId: Api.Peer + public init(flags: Int32, channelId: Int64, savedPeerId: Api.Peer) { + self.flags = flags + self.channelId = channelId + self.savedPeerId = savedPeerId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMonoForumNoPaidException", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId))]) + } + } + public class Cons_updateMoveStickerSetToTop: TypeConstructorDescription { + public var flags: Int32 + public var stickerset: Int64 + public init(flags: Int32, stickerset: Int64) { + self.flags = flags + self.stickerset = stickerset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateMoveStickerSetToTop", [("flags", ConstructorParameterDescription(self.flags)), ("stickerset", ConstructorParameterDescription(self.stickerset))]) + } + } + public class Cons_updateNewAuthorization: TypeConstructorDescription { + public var flags: Int32 + public var hash: Int64 + public var date: Int32? + public var device: String? + public var location: String? + public init(flags: Int32, hash: Int64, date: Int32?, device: String?, location: String?) { + self.flags = flags + self.hash = hash + self.date = date + self.device = device + self.location = location + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewAuthorization", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("date", ConstructorParameterDescription(self.date)), ("device", ConstructorParameterDescription(self.device)), ("location", ConstructorParameterDescription(self.location))]) + } + } + public class Cons_updateNewBotConnection: TypeConstructorDescription { + public var flags: Int32 + public var botId: Int64 + public var date: Int32? + public var device: String? + public var location: String? + public init(flags: Int32, botId: Int64, date: Int32?, device: String?, location: String?) { + self.flags = flags + self.botId = botId + self.date = date + self.device = device + self.location = location + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewBotConnection", [("flags", ConstructorParameterDescription(self.flags)), ("botId", ConstructorParameterDescription(self.botId)), ("date", ConstructorParameterDescription(self.date)), ("device", ConstructorParameterDescription(self.device)), ("location", ConstructorParameterDescription(self.location))]) + } + } + public class Cons_updateNewChannelMessage: TypeConstructorDescription { + public var message: Api.Message + public var pts: Int32 + public var ptsCount: Int32 + public init(message: Api.Message, pts: Int32, ptsCount: Int32) { + self.message = message + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewChannelMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateNewEncryptedMessage: TypeConstructorDescription { + public var message: Api.EncryptedMessage + public var qts: Int32 + public init(message: Api.EncryptedMessage, qts: Int32) { + self.message = message + self.qts = qts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewEncryptedMessage", [("message", ConstructorParameterDescription(self.message)), ("qts", ConstructorParameterDescription(self.qts))]) + } + } + public class Cons_updateNewMessage: TypeConstructorDescription { + public var message: Api.Message + public var pts: Int32 + public var ptsCount: Int32 + public init(message: Api.Message, pts: Int32, ptsCount: Int32) { + self.message = message + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewMessage", [("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateNewQuickReply: TypeConstructorDescription { + public var quickReply: Api.QuickReply + public init(quickReply: Api.QuickReply) { + self.quickReply = quickReply + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewQuickReply", [("quickReply", ConstructorParameterDescription(self.quickReply))]) + } + } + public class Cons_updateNewScheduledMessage: TypeConstructorDescription { + public var message: Api.Message + public init(message: Api.Message) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewScheduledMessage", [("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_updateNewStickerSet: TypeConstructorDescription { + public var stickerset: Api.messages.StickerSet + public init(stickerset: Api.messages.StickerSet) { + self.stickerset = stickerset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewStickerSet", [("stickerset", ConstructorParameterDescription(self.stickerset))]) + } + } + public class Cons_updateNewStoryReaction: TypeConstructorDescription { + public var storyId: Int32 + public var peer: Api.Peer + public var reaction: Api.Reaction + public init(storyId: Int32, peer: Api.Peer, reaction: Api.Reaction) { + self.storyId = storyId + self.peer = peer + self.reaction = reaction + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNewStoryReaction", [("storyId", ConstructorParameterDescription(self.storyId)), ("peer", ConstructorParameterDescription(self.peer)), ("reaction", ConstructorParameterDescription(self.reaction))]) + } + } + public class Cons_updateNotifySettings: TypeConstructorDescription { + public var peer: Api.NotifyPeer + public var notifySettings: Api.PeerNotifySettings + public init(peer: Api.NotifyPeer, notifySettings: Api.PeerNotifySettings) { + self.peer = peer + self.notifySettings = notifySettings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateNotifySettings", [("peer", ConstructorParameterDescription(self.peer)), ("notifySettings", ConstructorParameterDescription(self.notifySettings))]) + } + } + public class Cons_updatePaidReactionPrivacy: TypeConstructorDescription { + public var `private`: Api.PaidReactionPrivacy + public init(`private`: Api.PaidReactionPrivacy) { + self.`private` = `private` + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePaidReactionPrivacy", [("`private`", ConstructorParameterDescription(self.`private`))]) + } + } + public class Cons_updatePeerBlocked: TypeConstructorDescription { + public var flags: Int32 + public var peerId: Api.Peer + public init(flags: Int32, peerId: Api.Peer) { + self.flags = flags + self.peerId = peerId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePeerBlocked", [("flags", ConstructorParameterDescription(self.flags)), ("peerId", ConstructorParameterDescription(self.peerId))]) + } + } + public class Cons_updatePeerHistoryTTL: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var ttlPeriod: Int32? + public init(flags: Int32, peer: Api.Peer, ttlPeriod: Int32?) { + self.flags = flags + self.peer = peer + self.ttlPeriod = ttlPeriod + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePeerHistoryTTL", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) + } + } + public class Cons_updatePeerLocated: TypeConstructorDescription { + public var peers: [Api.PeerLocated] + public init(peers: [Api.PeerLocated]) { + self.peers = peers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePeerLocated", [("peers", ConstructorParameterDescription(self.peers))]) + } + } + public class Cons_updatePeerSettings: TypeConstructorDescription { + public var peer: Api.Peer + public var settings: Api.PeerSettings + public init(peer: Api.Peer, settings: Api.PeerSettings) { + self.peer = peer + self.settings = settings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePeerSettings", [("peer", ConstructorParameterDescription(self.peer)), ("settings", ConstructorParameterDescription(self.settings))]) + } + } + public class Cons_updatePeerWallpaper: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var wallpaper: Api.WallPaper? + public init(flags: Int32, peer: Api.Peer, wallpaper: Api.WallPaper?) { + self.flags = flags + self.peer = peer + self.wallpaper = wallpaper + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePeerWallpaper", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("wallpaper", ConstructorParameterDescription(self.wallpaper))]) + } + } + public class Cons_updatePendingJoinRequests: TypeConstructorDescription { + public var peer: Api.Peer + public var requestsPending: Int32 + public var recentRequesters: [Int64] + public init(peer: Api.Peer, requestsPending: Int32, recentRequesters: [Int64]) { + self.peer = peer + self.requestsPending = requestsPending + self.recentRequesters = recentRequesters + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePendingJoinRequests", [("peer", ConstructorParameterDescription(self.peer)), ("requestsPending", ConstructorParameterDescription(self.requestsPending)), ("recentRequesters", ConstructorParameterDescription(self.recentRequesters))]) + } + } + public class Cons_updatePhoneCall: TypeConstructorDescription { + public var phoneCall: Api.PhoneCall + public init(phoneCall: Api.PhoneCall) { + self.phoneCall = phoneCall + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePhoneCall", [("phoneCall", ConstructorParameterDescription(self.phoneCall))]) + } + } + public class Cons_updatePhoneCallSignalingData: TypeConstructorDescription { + public var phoneCallId: Int64 + public var data: Buffer + public init(phoneCallId: Int64, data: Buffer) { + self.phoneCallId = phoneCallId + self.data = data + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePhoneCallSignalingData", [("phoneCallId", ConstructorParameterDescription(self.phoneCallId)), ("data", ConstructorParameterDescription(self.data))]) + } + } + public class Cons_updatePinnedChannelMessages: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var messages: [Int32] + public var pts: Int32 + public var ptsCount: Int32 + public init(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) { + self.flags = flags + self.channelId = channelId + self.messages = messages + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedChannelMessages", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updatePinnedDialogs: TypeConstructorDescription { + public var flags: Int32 + public var folderId: Int32? + public var order: [Api.DialogPeer]? + public init(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) { + self.flags = flags + self.folderId = folderId + self.order = order + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedDialogs", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("order", ConstructorParameterDescription(self.order))]) + } + } + public class Cons_updatePinnedForumTopic: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var topicId: Int32 + public init(flags: Int32, peer: Api.Peer, topicId: Int32) { + self.flags = flags + self.peer = peer + self.topicId = topicId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedForumTopic", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("topicId", ConstructorParameterDescription(self.topicId))]) + } + } + public class Cons_updatePinnedForumTopics: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var order: [Int32]? + public init(flags: Int32, peer: Api.Peer, order: [Int32]?) { + self.flags = flags + self.peer = peer + self.order = order + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedForumTopics", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("order", ConstructorParameterDescription(self.order))]) + } + } + public class Cons_updatePinnedMessages: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var messages: [Int32] + public var pts: Int32 + public var ptsCount: Int32 + public init(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) { + self.flags = flags + self.peer = peer + self.messages = messages + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedMessages", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updatePinnedSavedDialogs: TypeConstructorDescription { + public var flags: Int32 + public var order: [Api.DialogPeer]? + public init(flags: Int32, order: [Api.DialogPeer]?) { + self.flags = flags + self.order = order + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePinnedSavedDialogs", [("flags", ConstructorParameterDescription(self.flags)), ("order", ConstructorParameterDescription(self.order))]) + } + } + public class Cons_updatePrivacy: TypeConstructorDescription { + public var key: Api.PrivacyKey + public var rules: [Api.PrivacyRule] + public init(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) { + self.key = key + self.rules = rules + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatePrivacy", [("key", ConstructorParameterDescription(self.key)), ("rules", ConstructorParameterDescription(self.rules))]) + } + } + public class Cons_updateQuickReplies: TypeConstructorDescription { + public var quickReplies: [Api.QuickReply] + public init(quickReplies: [Api.QuickReply]) { + self.quickReplies = quickReplies + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateQuickReplies", [("quickReplies", ConstructorParameterDescription(self.quickReplies))]) + } + } + public class Cons_updateQuickReplyMessage: TypeConstructorDescription { + public var message: Api.Message + public init(message: Api.Message) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateQuickReplyMessage", [("message", ConstructorParameterDescription(self.message))]) + } + } + public class Cons_updateReadChannelDiscussionInbox: TypeConstructorDescription { + public var flags: Int32 + public var channelId: Int64 + public var topMsgId: Int32 + public var readMaxId: Int32 + public var broadcastId: Int64? + public var broadcastPost: Int32? + public init(flags: Int32, channelId: Int64, topMsgId: Int32, readMaxId: Int32, broadcastId: Int64?, broadcastPost: Int32?) { + self.flags = flags + self.channelId = channelId + self.topMsgId = topMsgId + self.readMaxId = readMaxId + self.broadcastId = broadcastId + self.broadcastPost = broadcastPost + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadChannelDiscussionInbox", [("flags", ConstructorParameterDescription(self.flags)), ("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId)), ("broadcastId", ConstructorParameterDescription(self.broadcastId)), ("broadcastPost", ConstructorParameterDescription(self.broadcastPost))]) + } + } + public class Cons_updateReadChannelDiscussionOutbox: TypeConstructorDescription { + public var channelId: Int64 + public var topMsgId: Int32 + public var readMaxId: Int32 + public init(channelId: Int64, topMsgId: Int32, readMaxId: Int32) { + self.channelId = channelId + self.topMsgId = topMsgId + self.readMaxId = readMaxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadChannelDiscussionOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) + } + } + public class Cons_updateReadChannelInbox: TypeConstructorDescription { + public var flags: Int32 + public var folderId: Int32? + public var channelId: Int64 + public var maxId: Int32 + public var stillUnreadCount: Int32 + public var pts: Int32 + public init(flags: Int32, folderId: Int32?, channelId: Int64, maxId: Int32, stillUnreadCount: Int32, pts: Int32) { + self.flags = flags + self.folderId = folderId + self.channelId = channelId + self.maxId = maxId + self.stillUnreadCount = stillUnreadCount + self.pts = pts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadChannelInbox", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("stillUnreadCount", ConstructorParameterDescription(self.stillUnreadCount)), ("pts", ConstructorParameterDescription(self.pts))]) + } + } + public class Cons_updateReadChannelOutbox: TypeConstructorDescription { + public var channelId: Int64 + public var maxId: Int32 + public init(channelId: Int64, maxId: Int32) { + self.channelId = channelId + self.maxId = maxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadChannelOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("maxId", ConstructorParameterDescription(self.maxId))]) + } + } + public class Cons_updateReadHistoryInbox: TypeConstructorDescription { + public var flags: Int32 + public var folderId: Int32? + public var peer: Api.Peer + public var topMsgId: Int32? + public var maxId: Int32 + public var stillUnreadCount: Int32 + public var pts: Int32 + public var ptsCount: Int32 + public init(flags: Int32, folderId: Int32?, peer: Api.Peer, topMsgId: Int32?, maxId: Int32, stillUnreadCount: Int32, pts: Int32, ptsCount: Int32) { + self.flags = flags + self.folderId = folderId + self.peer = peer + self.topMsgId = topMsgId + self.maxId = maxId + self.stillUnreadCount = stillUnreadCount + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadHistoryInbox", [("flags", ConstructorParameterDescription(self.flags)), ("folderId", ConstructorParameterDescription(self.folderId)), ("peer", ConstructorParameterDescription(self.peer)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("maxId", ConstructorParameterDescription(self.maxId)), ("stillUnreadCount", ConstructorParameterDescription(self.stillUnreadCount)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateReadHistoryOutbox: TypeConstructorDescription { + public var peer: Api.Peer + public var maxId: Int32 + public var pts: Int32 + public var ptsCount: Int32 + public init(peer: Api.Peer, maxId: Int32, pts: Int32, ptsCount: Int32) { + self.peer = peer + self.maxId = maxId + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadHistoryOutbox", [("peer", ConstructorParameterDescription(self.peer)), ("maxId", ConstructorParameterDescription(self.maxId)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateReadMessagesContents: TypeConstructorDescription { + public var flags: Int32 + public var messages: [Int32] + public var pts: Int32 + public var ptsCount: Int32 + public var date: Int32? + public init(flags: Int32, messages: [Int32], pts: Int32, ptsCount: Int32, date: Int32?) { + self.flags = flags + self.messages = messages + self.pts = pts + self.ptsCount = ptsCount + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadMessagesContents", [("flags", ConstructorParameterDescription(self.flags)), ("messages", ConstructorParameterDescription(self.messages)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date))]) + } + } + public class Cons_updateReadMonoForumInbox: TypeConstructorDescription { + public var channelId: Int64 + public var savedPeerId: Api.Peer + public var readMaxId: Int32 + public init(channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) { + self.channelId = channelId + self.savedPeerId = savedPeerId + self.readMaxId = readMaxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadMonoForumInbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) + } + } + public class Cons_updateReadMonoForumOutbox: TypeConstructorDescription { + public var channelId: Int64 + public var savedPeerId: Api.Peer + public var readMaxId: Int32 + public init(channelId: Int64, savedPeerId: Api.Peer, readMaxId: Int32) { + self.channelId = channelId + self.savedPeerId = savedPeerId + self.readMaxId = readMaxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadMonoForumOutbox", [("channelId", ConstructorParameterDescription(self.channelId)), ("savedPeerId", ConstructorParameterDescription(self.savedPeerId)), ("readMaxId", ConstructorParameterDescription(self.readMaxId))]) + } + } + public class Cons_updateReadStories: TypeConstructorDescription { + public var peer: Api.Peer + public var maxId: Int32 + public init(peer: Api.Peer, maxId: Int32) { + self.peer = peer + self.maxId = maxId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateReadStories", [("peer", ConstructorParameterDescription(self.peer)), ("maxId", ConstructorParameterDescription(self.maxId))]) + } + } + public class Cons_updateSavedDialogPinned: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.DialogPeer + public init(flags: Int32, peer: Api.DialogPeer) { + self.flags = flags + self.peer = peer + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateSavedDialogPinned", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer))]) + } + } + public class Cons_updateSentPhoneCode: TypeConstructorDescription { + public var sentCode: Api.auth.SentCode + public init(sentCode: Api.auth.SentCode) { + self.sentCode = sentCode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateSentPhoneCode", [("sentCode", ConstructorParameterDescription(self.sentCode))]) + } + } + public class Cons_updateSentStoryReaction: TypeConstructorDescription { + public var peer: Api.Peer + public var storyId: Int32 + public var reaction: Api.Reaction + public init(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction) { + self.peer = peer + self.storyId = storyId + self.reaction = reaction + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateSentStoryReaction", [("peer", ConstructorParameterDescription(self.peer)), ("storyId", ConstructorParameterDescription(self.storyId)), ("reaction", ConstructorParameterDescription(self.reaction))]) + } + } + public class Cons_updateServiceNotification: TypeConstructorDescription { + public var flags: Int32 + public var inboxDate: Int32? + public var type: String + public var message: String + public var media: Api.MessageMedia + public var entities: [Api.MessageEntity] + public init(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity]) { + self.flags = flags + self.inboxDate = inboxDate + self.type = type + self.message = message + self.media = media + self.entities = entities + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateServiceNotification", [("flags", ConstructorParameterDescription(self.flags)), ("inboxDate", ConstructorParameterDescription(self.inboxDate)), ("type", ConstructorParameterDescription(self.type)), ("message", ConstructorParameterDescription(self.message)), ("media", ConstructorParameterDescription(self.media)), ("entities", ConstructorParameterDescription(self.entities))]) + } + } + public class Cons_updateSmsJob: TypeConstructorDescription { + public var jobId: String + public init(jobId: String) { + self.jobId = jobId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateSmsJob", [("jobId", ConstructorParameterDescription(self.jobId))]) + } + } + public class Cons_updateStarGiftAuctionState: TypeConstructorDescription { + public var giftId: Int64 + public var state: Api.StarGiftAuctionState + public init(giftId: Int64, state: Api.StarGiftAuctionState) { + self.giftId = giftId + self.state = state + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStarGiftAuctionState", [("giftId", ConstructorParameterDescription(self.giftId)), ("state", ConstructorParameterDescription(self.state))]) + } + } + public class Cons_updateStarGiftAuctionUserState: TypeConstructorDescription { + public var giftId: Int64 + public var userState: Api.StarGiftAuctionUserState + public init(giftId: Int64, userState: Api.StarGiftAuctionUserState) { + self.giftId = giftId + self.userState = userState + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStarGiftAuctionUserState", [("giftId", ConstructorParameterDescription(self.giftId)), ("userState", ConstructorParameterDescription(self.userState))]) + } + } + public class Cons_updateStarsBalance: TypeConstructorDescription { + public var balance: Api.StarsAmount + public init(balance: Api.StarsAmount) { + self.balance = balance + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStarsBalance", [("balance", ConstructorParameterDescription(self.balance))]) + } + } + public class Cons_updateStarsRevenueStatus: TypeConstructorDescription { + public var peer: Api.Peer + public var status: Api.StarsRevenueStatus + public init(peer: Api.Peer, status: Api.StarsRevenueStatus) { + self.peer = peer + self.status = status + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStarsRevenueStatus", [("peer", ConstructorParameterDescription(self.peer)), ("status", ConstructorParameterDescription(self.status))]) + } + } + public class Cons_updateStickerSets: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStickerSets", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_updateStickerSetsOrder: TypeConstructorDescription { + public var flags: Int32 + public var order: [Int64] + public init(flags: Int32, order: [Int64]) { + self.flags = flags + self.order = order + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStickerSetsOrder", [("flags", ConstructorParameterDescription(self.flags)), ("order", ConstructorParameterDescription(self.order))]) + } + } + public class Cons_updateStoriesStealthMode: TypeConstructorDescription { + public var stealthMode: Api.StoriesStealthMode + public init(stealthMode: Api.StoriesStealthMode) { + self.stealthMode = stealthMode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStoriesStealthMode", [("stealthMode", ConstructorParameterDescription(self.stealthMode))]) + } + } + public class Cons_updateStory: TypeConstructorDescription { + public var peer: Api.Peer + public var story: Api.StoryItem + public init(peer: Api.Peer, story: Api.StoryItem) { + self.peer = peer + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStory", [("peer", ConstructorParameterDescription(self.peer)), ("story", ConstructorParameterDescription(self.story))]) + } + } + public class Cons_updateStoryID: TypeConstructorDescription { + public var id: Int32 + public var randomId: Int64 + public init(id: Int32, randomId: Int64) { + self.id = id + self.randomId = randomId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateStoryID", [("id", ConstructorParameterDescription(self.id)), ("randomId", ConstructorParameterDescription(self.randomId))]) + } + } + public class Cons_updateTheme: TypeConstructorDescription { + public var theme: Api.Theme + public init(theme: Api.Theme) { + self.theme = theme + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateTheme", [("theme", ConstructorParameterDescription(self.theme))]) + } + } + public class Cons_updateTranscribedAudio: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var msgId: Int32 + public var transcriptionId: Int64 + public var text: String + public init(flags: Int32, peer: Api.Peer, msgId: Int32, transcriptionId: Int64, text: String) { + self.flags = flags + self.peer = peer + self.msgId = msgId + self.transcriptionId = transcriptionId + self.text = text + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateTranscribedAudio", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("msgId", ConstructorParameterDescription(self.msgId)), ("transcriptionId", ConstructorParameterDescription(self.transcriptionId)), ("text", ConstructorParameterDescription(self.text))]) + } + } + public class Cons_updateUser: TypeConstructorDescription { + public var userId: Int64 + public init(userId: Int64) { + self.userId = userId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUser", [("userId", ConstructorParameterDescription(self.userId))]) + } + } + public class Cons_updateUserEmojiStatus: TypeConstructorDescription { + public var userId: Int64 + public var emojiStatus: Api.EmojiStatus + public init(userId: Int64, emojiStatus: Api.EmojiStatus) { + self.userId = userId + self.emojiStatus = emojiStatus + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUserEmojiStatus", [("userId", ConstructorParameterDescription(self.userId)), ("emojiStatus", ConstructorParameterDescription(self.emojiStatus))]) + } + } + public class Cons_updateUserName: TypeConstructorDescription { + public var userId: Int64 + public var firstName: String + public var lastName: String + public var usernames: [Api.Username] + public init(userId: Int64, firstName: String, lastName: String, usernames: [Api.Username]) { + self.userId = userId + self.firstName = firstName + self.lastName = lastName + self.usernames = usernames + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUserName", [("userId", ConstructorParameterDescription(self.userId)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("usernames", ConstructorParameterDescription(self.usernames))]) + } + } + public class Cons_updateUserPhone: TypeConstructorDescription { + public var userId: Int64 + public var phone: String + public init(userId: Int64, phone: String) { + self.userId = userId + self.phone = phone + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUserPhone", [("userId", ConstructorParameterDescription(self.userId)), ("phone", ConstructorParameterDescription(self.phone))]) + } + } + public class Cons_updateUserStatus: TypeConstructorDescription { + public var userId: Int64 + public var status: Api.UserStatus + public init(userId: Int64, status: Api.UserStatus) { + self.userId = userId + self.status = status + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUserStatus", [("userId", ConstructorParameterDescription(self.userId)), ("status", ConstructorParameterDescription(self.status))]) + } + } + public class Cons_updateUserTyping: TypeConstructorDescription { + public var flags: Int32 + public var userId: Int64 + public var topMsgId: Int32? + public var action: Api.SendMessageAction + public init(flags: Int32, userId: Int64, topMsgId: Int32?, action: Api.SendMessageAction) { + self.flags = flags + self.userId = userId + self.topMsgId = topMsgId + self.action = action + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateUserTyping", [("flags", ConstructorParameterDescription(self.flags)), ("userId", ConstructorParameterDescription(self.userId)), ("topMsgId", ConstructorParameterDescription(self.topMsgId)), ("action", ConstructorParameterDescription(self.action))]) + } + } + public class Cons_updateWebBrowserException: TypeConstructorDescription { + public var flags: Int32 + public var openExternalBrowser: Api.Bool? + public var exception: Api.WebDomainException + public init(flags: Int32, openExternalBrowser: Api.Bool?, exception: Api.WebDomainException) { + self.flags = flags + self.openExternalBrowser = openExternalBrowser + self.exception = exception + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateWebBrowserException", [("flags", ConstructorParameterDescription(self.flags)), ("openExternalBrowser", ConstructorParameterDescription(self.openExternalBrowser)), ("exception", ConstructorParameterDescription(self.exception))]) + } + } + public class Cons_updateWebBrowserSettings: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateWebBrowserSettings", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_updateWebPage: TypeConstructorDescription { + public var webpage: Api.WebPage + public var pts: Int32 + public var ptsCount: Int32 + public init(webpage: Api.WebPage, pts: Int32, ptsCount: Int32) { + self.webpage = webpage + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateWebPage", [("webpage", ConstructorParameterDescription(self.webpage)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + public class Cons_updateWebViewResultSent: TypeConstructorDescription { + public var queryId: Int64 + public init(queryId: Int64) { + self.queryId = queryId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updateWebViewResultSent", [("queryId", ConstructorParameterDescription(self.queryId))]) + } + } + case updateAiComposeTones + case updateAttachMenuBots + case updateAutoSaveSettings + case updateBotBusinessConnect(Cons_updateBotBusinessConnect) + case updateBotCallbackQuery(Cons_updateBotCallbackQuery) + case updateBotChatBoost(Cons_updateBotChatBoost) + case updateBotChatInviteRequester(Cons_updateBotChatInviteRequester) + case updateBotCommands(Cons_updateBotCommands) + case updateBotDeleteBusinessMessage(Cons_updateBotDeleteBusinessMessage) + case updateBotEditBusinessMessage(Cons_updateBotEditBusinessMessage) + case updateBotGuestChatQuery(Cons_updateBotGuestChatQuery) + case updateBotInlineQuery(Cons_updateBotInlineQuery) + case updateBotInlineSend(Cons_updateBotInlineSend) + case updateBotMenuButton(Cons_updateBotMenuButton) + case updateBotMessageReaction(Cons_updateBotMessageReaction) + case updateBotMessageReactions(Cons_updateBotMessageReactions) + case updateBotNewBusinessMessage(Cons_updateBotNewBusinessMessage) + case updateBotPrecheckoutQuery(Cons_updateBotPrecheckoutQuery) + case updateBotPurchasedPaidMedia(Cons_updateBotPurchasedPaidMedia) + case updateBotShippingQuery(Cons_updateBotShippingQuery) + case updateBotStopped(Cons_updateBotStopped) + case updateBotWebhookJSON(Cons_updateBotWebhookJSON) + case updateBotWebhookJSONQuery(Cons_updateBotWebhookJSONQuery) + case updateBusinessBotCallbackQuery(Cons_updateBusinessBotCallbackQuery) + case updateChannel(Cons_updateChannel) + case updateChannelAvailableMessages(Cons_updateChannelAvailableMessages) + case updateChannelMessageForwards(Cons_updateChannelMessageForwards) + case updateChannelMessageViews(Cons_updateChannelMessageViews) + case updateChannelParticipant(Cons_updateChannelParticipant) + case updateChannelReadMessagesContents(Cons_updateChannelReadMessagesContents) + case updateChannelTooLong(Cons_updateChannelTooLong) + case updateChannelUserTyping(Cons_updateChannelUserTyping) + case updateChannelViewForumAsMessages(Cons_updateChannelViewForumAsMessages) + case updateChannelWebPage(Cons_updateChannelWebPage) + case updateChat(Cons_updateChat) + case updateChatDefaultBannedRights(Cons_updateChatDefaultBannedRights) + case updateChatParticipant(Cons_updateChatParticipant) + case updateChatParticipantAdd(Cons_updateChatParticipantAdd) + case updateChatParticipantAdmin(Cons_updateChatParticipantAdmin) + case updateChatParticipantDelete(Cons_updateChatParticipantDelete) + case updateChatParticipantRank(Cons_updateChatParticipantRank) + case updateChatParticipants(Cons_updateChatParticipants) + case updateChatUserTyping(Cons_updateChatUserTyping) + case updateConfig + case updateContactsReset + case updateDcOptions(Cons_updateDcOptions) + case updateDeleteChannelMessages(Cons_updateDeleteChannelMessages) + case updateDeleteGroupCallMessages(Cons_updateDeleteGroupCallMessages) + case updateDeleteMessages(Cons_updateDeleteMessages) + case updateDeleteQuickReply(Cons_updateDeleteQuickReply) + case updateDeleteQuickReplyMessages(Cons_updateDeleteQuickReplyMessages) + case updateDeleteScheduledMessages(Cons_updateDeleteScheduledMessages) + case updateDialogFilter(Cons_updateDialogFilter) + case updateDialogFilterOrder(Cons_updateDialogFilterOrder) + case updateDialogFilters + case updateDialogPinned(Cons_updateDialogPinned) + case updateDialogUnreadMark(Cons_updateDialogUnreadMark) + case updateDraftMessage(Cons_updateDraftMessage) + case updateEditChannelMessage(Cons_updateEditChannelMessage) + case updateEditMessage(Cons_updateEditMessage) + case updateEmojiGameInfo(Cons_updateEmojiGameInfo) + case updateEncryptedChatTyping(Cons_updateEncryptedChatTyping) + case updateEncryptedMessagesRead(Cons_updateEncryptedMessagesRead) + case updateEncryption(Cons_updateEncryption) + case updateFavedStickers + case updateFolderPeers(Cons_updateFolderPeers) + case updateGeoLiveViewed(Cons_updateGeoLiveViewed) + case updateGroupCall(Cons_updateGroupCall) + case updateGroupCallChainBlocks(Cons_updateGroupCallChainBlocks) + case updateGroupCallConnection(Cons_updateGroupCallConnection) + case updateGroupCallEncryptedMessage(Cons_updateGroupCallEncryptedMessage) + case updateGroupCallMessage(Cons_updateGroupCallMessage) + case updateGroupCallParticipants(Cons_updateGroupCallParticipants) + case updateInlineBotCallbackQuery(Cons_updateInlineBotCallbackQuery) + case updateJoinChatWebViewDecision(Cons_updateJoinChatWebViewDecision) + case updateLangPack(Cons_updateLangPack) + case updateLangPackTooLong(Cons_updateLangPackTooLong) + case updateLoginToken + case updateManagedBot(Cons_updateManagedBot) + case updateMessageExtendedMedia(Cons_updateMessageExtendedMedia) + case updateMessageID(Cons_updateMessageID) + case updateMessagePoll(Cons_updateMessagePoll) + case updateMessagePollVote(Cons_updateMessagePollVote) + case updateMessageReactions(Cons_updateMessageReactions) + case updateMonoForumNoPaidException(Cons_updateMonoForumNoPaidException) + case updateMoveStickerSetToTop(Cons_updateMoveStickerSetToTop) + case updateNewAuthorization(Cons_updateNewAuthorization) + case updateNewBotConnection(Cons_updateNewBotConnection) + case updateNewChannelMessage(Cons_updateNewChannelMessage) + case updateNewEncryptedMessage(Cons_updateNewEncryptedMessage) + case updateNewMessage(Cons_updateNewMessage) + case updateNewQuickReply(Cons_updateNewQuickReply) + case updateNewScheduledMessage(Cons_updateNewScheduledMessage) + case updateNewStickerSet(Cons_updateNewStickerSet) + case updateNewStoryReaction(Cons_updateNewStoryReaction) + case updateNotifySettings(Cons_updateNotifySettings) + case updatePaidReactionPrivacy(Cons_updatePaidReactionPrivacy) + case updatePeerBlocked(Cons_updatePeerBlocked) + case updatePeerHistoryTTL(Cons_updatePeerHistoryTTL) + case updatePeerLocated(Cons_updatePeerLocated) + case updatePeerSettings(Cons_updatePeerSettings) + case updatePeerWallpaper(Cons_updatePeerWallpaper) + case updatePendingJoinRequests(Cons_updatePendingJoinRequests) + case updatePhoneCall(Cons_updatePhoneCall) + case updatePhoneCallSignalingData(Cons_updatePhoneCallSignalingData) + case updatePinnedChannelMessages(Cons_updatePinnedChannelMessages) + case updatePinnedDialogs(Cons_updatePinnedDialogs) + case updatePinnedForumTopic(Cons_updatePinnedForumTopic) + case updatePinnedForumTopics(Cons_updatePinnedForumTopics) + case updatePinnedMessages(Cons_updatePinnedMessages) + case updatePinnedSavedDialogs(Cons_updatePinnedSavedDialogs) + case updatePrivacy(Cons_updatePrivacy) + case updatePtsChanged + case updateQuickReplies(Cons_updateQuickReplies) + case updateQuickReplyMessage(Cons_updateQuickReplyMessage) + case updateReadChannelDiscussionInbox(Cons_updateReadChannelDiscussionInbox) + case updateReadChannelDiscussionOutbox(Cons_updateReadChannelDiscussionOutbox) + case updateReadChannelInbox(Cons_updateReadChannelInbox) + case updateReadChannelOutbox(Cons_updateReadChannelOutbox) + case updateReadFeaturedEmojiStickers + case updateReadFeaturedStickers + case updateReadHistoryInbox(Cons_updateReadHistoryInbox) + case updateReadHistoryOutbox(Cons_updateReadHistoryOutbox) + case updateReadMessagesContents(Cons_updateReadMessagesContents) + case updateReadMonoForumInbox(Cons_updateReadMonoForumInbox) + case updateReadMonoForumOutbox(Cons_updateReadMonoForumOutbox) + case updateReadStories(Cons_updateReadStories) + case updateRecentEmojiStatuses + case updateRecentReactions + case updateRecentStickers + case updateSavedDialogPinned(Cons_updateSavedDialogPinned) + case updateSavedGifs + case updateSavedReactionTags + case updateSavedRingtones + case updateSentPhoneCode(Cons_updateSentPhoneCode) + case updateSentStoryReaction(Cons_updateSentStoryReaction) + case updateServiceNotification(Cons_updateServiceNotification) + case updateSmsJob(Cons_updateSmsJob) + case updateStarGiftAuctionState(Cons_updateStarGiftAuctionState) + case updateStarGiftAuctionUserState(Cons_updateStarGiftAuctionUserState) + case updateStarGiftCraftFail + case updateStarsBalance(Cons_updateStarsBalance) + case updateStarsRevenueStatus(Cons_updateStarsRevenueStatus) + case updateStickerSets(Cons_updateStickerSets) + case updateStickerSetsOrder(Cons_updateStickerSetsOrder) + case updateStoriesStealthMode(Cons_updateStoriesStealthMode) + case updateStory(Cons_updateStory) + case updateStoryID(Cons_updateStoryID) + case updateTheme(Cons_updateTheme) + case updateTranscribedAudio(Cons_updateTranscribedAudio) + case updateUser(Cons_updateUser) + case updateUserEmojiStatus(Cons_updateUserEmojiStatus) + case updateUserName(Cons_updateUserName) + case updateUserPhone(Cons_updateUserPhone) + case updateUserStatus(Cons_updateUserStatus) + case updateUserTyping(Cons_updateUserTyping) + case updateWebBrowserException(Cons_updateWebBrowserException) + case updateWebBrowserSettings(Cons_updateWebBrowserSettings) + case updateWebPage(Cons_updateWebPage) + case updateWebViewResultSent(Cons_updateWebViewResultSent) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .updateAiComposeTones: + if boxed { + buffer.appendInt32(-1945136645) + } + break + case .updateAttachMenuBots: + if boxed { + buffer.appendInt32(397910539) + } + break + case .updateAutoSaveSettings: + if boxed { + buffer.appendInt32(-335171433) + } + break + case .updateBotBusinessConnect(let _data): + if boxed { + buffer.appendInt32(-1964652166) + } + _data.connection.serialize(buffer, true) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotCallbackQuery(let _data): + if boxed { + buffer.appendInt32(-1177566067) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.data!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.gameShortName!, buffer: buffer, boxed: false) + } + break + case .updateBotChatBoost(let _data): + if boxed { + buffer.appendInt32(-1873947492) + } + _data.peer.serialize(buffer, true) + _data.boost.serialize(buffer, true) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotChatInviteRequester(let _data): + if boxed { + buffer.appendInt32(2092125561) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.about, buffer: buffer, boxed: false) + _data.invite.serialize(buffer, true) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.queryId!, buffer: buffer, boxed: false) + } + break + case .updateBotCommands(let _data): + if boxed { + buffer.appendInt32(1299263278) + } + _data.peer.serialize(buffer, true) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.commands.count)) + for item in _data.commands { + item.serialize(buffer, true) + } + break + case .updateBotDeleteBusinessMessage(let _data): + if boxed { + buffer.appendInt32(-1607821266) + } + serializeString(_data.connectionId, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotEditBusinessMessage(let _data): + if boxed { + buffer.appendInt32(132077692) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.connectionId, buffer: buffer, boxed: false) + _data.message.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.replyToMessage!.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotGuestChatQuery(let _data): + if boxed { + buffer.appendInt32(-841742019) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + _data.message.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.referenceMessages!.count)) + for item in _data.referenceMessages! { + item.serialize(buffer, true) + } + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotInlineQuery(let _data): + if boxed { + buffer.appendInt32(1232025500) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.query, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.geo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.peerType!.serialize(buffer, true) + } + serializeString(_data.offset, buffer: buffer, boxed: false) + break + case .updateBotInlineSend(let _data): + if boxed { + buffer.appendInt32(317794823) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.query, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.geo!.serialize(buffer, true) + } + serializeString(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.msgId!.serialize(buffer, true) + } + break + case .updateBotMenuButton(let _data): + if boxed { + buffer.appendInt32(347625491) + } + serializeInt64(_data.botId, buffer: buffer, boxed: false) + _data.button.serialize(buffer, true) + break + case .updateBotMessageReaction(let _data): + if boxed { + buffer.appendInt32(-1407069234) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.actor.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.oldReactions.count)) + for item in _data.oldReactions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newReactions.count)) + for item in _data.newReactions { + item.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotMessageReactions(let _data): + if boxed { + buffer.appendInt32(164329305) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.reactions.count)) + for item in _data.reactions { + item.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotNewBusinessMessage(let _data): + if boxed { + buffer.appendInt32(-1646578564) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.connectionId, buffer: buffer, boxed: false) + _data.message.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.replyToMessage!.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotPrecheckoutQuery(let _data): + if boxed { + buffer.appendInt32(-1934976362) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeBytes(_data.payload, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.info!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.shippingOptionId!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + break + case .updateBotPurchasedPaidMedia(let _data): + if boxed { + buffer.appendInt32(675009298) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.payload, buffer: buffer, boxed: false) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotShippingQuery(let _data): + if boxed { + buffer.appendInt32(-1246823043) + } + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeBytes(_data.payload, buffer: buffer, boxed: false) + _data.shippingAddress.serialize(buffer, true) + break + case .updateBotStopped(let _data): + if boxed { + buffer.appendInt32(-997782967) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.stopped.serialize(buffer, true) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateBotWebhookJSON(let _data): + if boxed { + buffer.appendInt32(-2095595325) + } + _data.data.serialize(buffer, true) + break + case .updateBotWebhookJSONQuery(let _data): + if boxed { + buffer.appendInt32(-1684914010) + } + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + _data.data.serialize(buffer, true) + serializeInt32(_data.timeout, buffer: buffer, boxed: false) + break + case .updateBusinessBotCallbackQuery(let _data): + if boxed { + buffer.appendInt32(513998247) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.connectionId, buffer: buffer, boxed: false) + _data.message.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.replyToMessage!.serialize(buffer, true) + } + serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.data!, buffer: buffer, boxed: false) + } + break + case .updateChannel(let _data): + if boxed { + buffer.appendInt32(1666927625) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + break + case .updateChannelAvailableMessages(let _data): + if boxed { + buffer.appendInt32(-1304443240) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.availableMinId, buffer: buffer, boxed: false) + break + case .updateChannelMessageForwards(let _data): + if boxed { + buffer.appendInt32(-761649164) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.forwards, buffer: buffer, boxed: false) + break + case .updateChannelMessageViews(let _data): + if boxed { + buffer.appendInt32(-232346616) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.views, buffer: buffer, boxed: false) + break + case .updateChannelParticipant(let _data): + if boxed { + buffer.appendInt32(-1738720581) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.actorId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.prevParticipant!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.newParticipant!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.invite!.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateChannelReadMessagesContents(let _data): + if boxed { + buffer.appendInt32(636691703) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateChannelTooLong(let _data): + if boxed { + buffer.appendInt32(277713951) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.pts!, buffer: buffer, boxed: false) + } + break + case .updateChannelUserTyping(let _data): + if boxed { + buffer.appendInt32(-1937192669) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + _data.fromId.serialize(buffer, true) + _data.action.serialize(buffer, true) + break + case .updateChannelViewForumAsMessages(let _data): + if boxed { + buffer.appendInt32(129403168) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + _data.enabled.serialize(buffer, true) + break + case .updateChannelWebPage(let _data): + if boxed { + buffer.appendInt32(791390623) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + _data.webpage.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateChat(let _data): + if boxed { + buffer.appendInt32(-124097970) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + break + case .updateChatDefaultBannedRights(let _data): + if boxed { + buffer.appendInt32(1421875280) + } + _data.peer.serialize(buffer, true) + _data.defaultBannedRights.serialize(buffer, true) + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateChatParticipant(let _data): + if boxed { + buffer.appendInt32(-796432838) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.actorId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.prevParticipant!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.newParticipant!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.invite!.serialize(buffer, true) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateChatParticipantAdd(let _data): + if boxed { + buffer.appendInt32(1037718609) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt64(_data.inviterId, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateChatParticipantAdmin(let _data): + if boxed { + buffer.appendInt32(-674602590) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + _data.isAdmin.serialize(buffer, true) + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateChatParticipantDelete(let _data): + if boxed { + buffer.appendInt32(-483443337) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateChatParticipantRank(let _data): + if boxed { + buffer.appendInt32(-1115461703) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.rank, buffer: buffer, boxed: false) + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateChatParticipants(let _data): + if boxed { + buffer.appendInt32(125178264) + } + _data.participants.serialize(buffer, true) + break + case .updateChatUserTyping(let _data): + if boxed { + buffer.appendInt32(-2092401936) + } + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + _data.fromId.serialize(buffer, true) + _data.action.serialize(buffer, true) + break + case .updateConfig: + if boxed { + buffer.appendInt32(-1574314746) + } + break + case .updateContactsReset: + if boxed { + buffer.appendInt32(1887741886) + } + break + case .updateDcOptions(let _data): + if boxed { + buffer.appendInt32(-1906403213) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dcOptions.count)) + for item in _data.dcOptions { + item.serialize(buffer, true) + } + break + case .updateDeleteChannelMessages(let _data): + if boxed { + buffer.appendInt32(-1020437742) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateDeleteGroupCallMessages(let _data): + if boxed { + buffer.appendInt32(1048963372) + } + _data.call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDeleteMessages(let _data): + if boxed { + buffer.appendInt32(-1576161051) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateDeleteQuickReply(let _data): + if boxed { + buffer.appendInt32(1407644140) + } + serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) + break + case .updateDeleteQuickReplyMessages(let _data): + if boxed { + buffer.appendInt32(1450174413) + } + serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDeleteScheduledMessages(let _data): + if boxed { + buffer.appendInt32(-223929981) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sentMessages!.count)) + for item in _data.sentMessages! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + break + case .updateDialogFilter(let _data): + if boxed { + buffer.appendInt32(654302845) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.filter!.serialize(buffer, true) + } + break + case .updateDialogFilterOrder(let _data): + if boxed { + buffer.appendInt32(-1512627963) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.order.count)) + for item in _data.order { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .updateDialogFilters: + if boxed { + buffer.appendInt32(889491791) + } + break + case .updateDialogPinned(let _data): + if boxed { + buffer.appendInt32(1852826908) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.folderId!, buffer: buffer, boxed: false) + } + _data.peer.serialize(buffer, true) + break + case .updateDialogUnreadMark(let _data): + if boxed { + buffer.appendInt32(-1235684802) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + break + case .updateDraftMessage(let _data): + if boxed { + buffer.appendInt32(-302247650) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + _data.draft.serialize(buffer, true) + break + case .updateEditChannelMessage(let _data): + if boxed { + buffer.appendInt32(457133559) + } + _data.message.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateEditMessage(let _data): + if boxed { + buffer.appendInt32(-469536605) + } + _data.message.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateEmojiGameInfo(let _data): + if boxed { + buffer.appendInt32(-73640838) + } + _data.info.serialize(buffer, true) + break + case .updateEncryptedChatTyping(let _data): + if boxed { + buffer.appendInt32(386986326) + } + serializeInt32(_data.chatId, buffer: buffer, boxed: false) + break + case .updateEncryptedMessagesRead(let _data): + if boxed { + buffer.appendInt32(956179895) + } + serializeInt32(_data.chatId, buffer: buffer, boxed: false) + serializeInt32(_data.maxDate, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .updateEncryption(let _data): + if boxed { + buffer.appendInt32(-1264392051) + } + _data.chat.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .updateFavedStickers: + if boxed { + buffer.appendInt32(-451831443) + } + break + case .updateFolderPeers(let _data): + if boxed { + buffer.appendInt32(422972864) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.folderPeers.count)) + for item in _data.folderPeers { + item.serialize(buffer, true) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateGeoLiveViewed(let _data): + if boxed { + buffer.appendInt32(-2027964103) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + break + case .updateGroupCall(let _data): + if boxed { + buffer.appendInt32(-1658710304) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.peer!.serialize(buffer, true) + } + _data.call.serialize(buffer, true) + break + case .updateGroupCallChainBlocks(let _data): + if boxed { + buffer.appendInt32(-1535694705) + } + _data.call.serialize(buffer, true) + serializeInt32(_data.subChainId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocks.count)) + for item in _data.blocks { + serializeBytes(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.nextOffset, buffer: buffer, boxed: false) + break + case .updateGroupCallConnection(let _data): + if boxed { + buffer.appendInt32(192428418) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.params.serialize(buffer, true) + break + case .updateGroupCallEncryptedMessage(let _data): + if boxed { + buffer.appendInt32(-917002394) + } + _data.call.serialize(buffer, true) + _data.fromId.serialize(buffer, true) + serializeBytes(_data.encryptedMessage, buffer: buffer, boxed: false) + break + case .updateGroupCallMessage(let _data): + if boxed { + buffer.appendInt32(-667783411) + } + _data.call.serialize(buffer, true) + _data.message.serialize(buffer, true) + break + case .updateGroupCallParticipants(let _data): + if boxed { + buffer.appendInt32(-219423922) + } + _data.call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.participants.count)) + for item in _data.participants { + item.serialize(buffer, true) + } + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + case .updateInlineBotCallbackQuery(let _data): + if boxed { + buffer.appendInt32(1763610706) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + _data.msgId.serialize(buffer, true) + serializeInt64(_data.chatInstance, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.data!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.gameShortName!, buffer: buffer, boxed: false) + } + break + case .updateJoinChatWebViewDecision(let _data): + if boxed { + buffer.appendInt32(-1112768912) + } + _data.peer.serialize(buffer, true) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + _data.result.serialize(buffer, true) + break + case .updateLangPack(let _data): + if boxed { + buffer.appendInt32(1442983757) + } + _data.difference.serialize(buffer, true) + break + case .updateLangPackTooLong(let _data): + if boxed { + buffer.appendInt32(1180041828) + } + serializeString(_data.langCode, buffer: buffer, boxed: false) + break + case .updateLoginToken: + if boxed { + buffer.appendInt32(1448076945) + } + break + case .updateManagedBot(let _data): + if boxed { + buffer.appendInt32(1216408986) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateMessageExtendedMedia(let _data): + if boxed { + buffer.appendInt32(-710666460) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.extendedMedia.count)) + for item in _data.extendedMedia { + item.serialize(buffer, true) + } + break + case .updateMessageID(let _data): + if boxed { + buffer.appendInt32(1318109142) + } + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + break + case .updateMessagePoll(let _data): + if boxed { + buffer.appendInt32(-699641301) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.peer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.msgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + serializeInt64(_data.pollId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.poll!.serialize(buffer, true) + } + _data.results.serialize(buffer, true) + break + case .updateMessagePollVote(let _data): + if boxed { + buffer.appendInt32(1989799956) + } + serializeInt64(_data.pollId, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.options.count)) + for item in _data.options { + serializeBytes(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.positions.count)) + for item in _data.positions { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateMessageReactions(let _data): + if boxed { + buffer.appendInt32(506035194) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.savedPeerId!.serialize(buffer, true) + } + _data.reactions.serialize(buffer, true) + break + case .updateMonoForumNoPaidException(let _data): + if boxed { + buffer.appendInt32(-1618924792) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + _data.savedPeerId.serialize(buffer, true) + break + case .updateMoveStickerSetToTop(let _data): + if boxed { + buffer.appendInt32(-2030252155) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.stickerset, buffer: buffer, boxed: false) + break + case .updateNewAuthorization(let _data): + if boxed { + buffer.appendInt32(-1991136273) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.hash, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.date!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.device!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.location!, buffer: buffer, boxed: false) + } + break + case .updateNewBotConnection(let _data): + if boxed { + buffer.appendInt32(-1306491994) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.date!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.device!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.location!, buffer: buffer, boxed: false) + } + break + case .updateNewChannelMessage(let _data): + if boxed { + buffer.appendInt32(1656358105) + } + _data.message.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateNewEncryptedMessage(let _data): + if boxed { + buffer.appendInt32(314359194) + } + _data.message.serialize(buffer, true) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + break + case .updateNewMessage(let _data): + if boxed { + buffer.appendInt32(522914557) + } + _data.message.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateNewQuickReply(let _data): + if boxed { + buffer.appendInt32(-180508905) + } + _data.quickReply.serialize(buffer, true) + break + case .updateNewScheduledMessage(let _data): + if boxed { + buffer.appendInt32(967122427) + } + _data.message.serialize(buffer, true) + break + case .updateNewStickerSet(let _data): + if boxed { + buffer.appendInt32(1753886890) + } + _data.stickerset.serialize(buffer, true) + break + case .updateNewStoryReaction(let _data): + if boxed { + buffer.appendInt32(405070859) + } + serializeInt32(_data.storyId, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + _data.reaction.serialize(buffer, true) + break + case .updateNotifySettings(let _data): + if boxed { + buffer.appendInt32(-1094555409) + } + _data.peer.serialize(buffer, true) + _data.notifySettings.serialize(buffer, true) + break + case .updatePaidReactionPrivacy(let _data): + if boxed { + buffer.appendInt32(-1955438642) + } + _data.`private`.serialize(buffer, true) + break + case .updatePeerBlocked(let _data): + if boxed { + buffer.appendInt32(-337610926) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peerId.serialize(buffer, true) + break + case .updatePeerHistoryTTL(let _data): + if boxed { + buffer.appendInt32(-1147422299) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + break + case .updatePeerLocated(let _data): + if boxed { + buffer.appendInt32(-1263546448) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + break + case .updatePeerSettings(let _data): + if boxed { + buffer.appendInt32(1786671974) + } + _data.peer.serialize(buffer, true) + _data.settings.serialize(buffer, true) + break + case .updatePeerWallpaper(let _data): + if boxed { + buffer.appendInt32(-1371598819) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.wallpaper!.serialize(buffer, true) + } + break + case .updatePendingJoinRequests(let _data): + if boxed { + buffer.appendInt32(1885586395) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.requestsPending, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentRequesters.count)) + for item in _data.recentRequesters { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .updatePhoneCall(let _data): + if boxed { + buffer.appendInt32(-1425052898) + } + _data.phoneCall.serialize(buffer, true) + break + case .updatePhoneCallSignalingData(let _data): + if boxed { + buffer.appendInt32(643940105) + } + serializeInt64(_data.phoneCallId, buffer: buffer, boxed: false) + serializeBytes(_data.data, buffer: buffer, boxed: false) + break + case .updatePinnedChannelMessages(let _data): + if boxed { + buffer.appendInt32(1538885128) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updatePinnedDialogs(let _data): + if boxed { + buffer.appendInt32(-99664734) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.folderId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.order!.count)) + for item in _data.order! { + item.serialize(buffer, true) + } + } + break + case .updatePinnedForumTopic(let _data): + if boxed { + buffer.appendInt32(1748708434) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.topicId, buffer: buffer, boxed: false) + break + case .updatePinnedForumTopics(let _data): + if boxed { + buffer.appendInt32(-554613808) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.order!.count)) + for item in _data.order! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + break + case .updatePinnedMessages(let _data): + if boxed { + buffer.appendInt32(-309990731) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updatePinnedSavedDialogs(let _data): + if boxed { + buffer.appendInt32(1751942566) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.order!.count)) + for item in _data.order! { + item.serialize(buffer, true) + } + } + break + case .updatePrivacy(let _data): + if boxed { + buffer.appendInt32(-298113238) + } + _data.key.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.rules.count)) + for item in _data.rules { + item.serialize(buffer, true) + } + break + case .updatePtsChanged: + if boxed { + buffer.appendInt32(861169551) + } + break + case .updateQuickReplies(let _data): + if boxed { + buffer.appendInt32(-112784718) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.quickReplies.count)) + for item in _data.quickReplies { + item.serialize(buffer, true) + } + break + case .updateQuickReplyMessage(let _data): + if boxed { + buffer.appendInt32(1040518415) + } + _data.message.serialize(buffer, true) + break + case .updateReadChannelDiscussionInbox(let _data): + if boxed { + buffer.appendInt32(-693004986) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) + serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.broadcastId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.broadcastPost!, buffer: buffer, boxed: false) + } + break + case .updateReadChannelDiscussionOutbox(let _data): + if boxed { + buffer.appendInt32(1767677564) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.topMsgId, buffer: buffer, boxed: false) + serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) + break + case .updateReadChannelInbox(let _data): + if boxed { + buffer.appendInt32(-1842450928) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.folderId!, buffer: buffer, boxed: false) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.maxId, buffer: buffer, boxed: false) + serializeInt32(_data.stillUnreadCount, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + break + case .updateReadChannelOutbox(let _data): + if boxed { + buffer.appendInt32(-1218471511) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + serializeInt32(_data.maxId, buffer: buffer, boxed: false) + break + case .updateReadFeaturedEmojiStickers: + if boxed { + buffer.appendInt32(-78886548) + } + break + case .updateReadFeaturedStickers: + if boxed { + buffer.appendInt32(1461528386) + } + break + case .updateReadHistoryInbox(let _data): + if boxed { + buffer.appendInt32(-1635468135) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.folderId!, buffer: buffer, boxed: false) + } + _data.peer.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + serializeInt32(_data.maxId, buffer: buffer, boxed: false) + serializeInt32(_data.stillUnreadCount, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateReadHistoryOutbox(let _data): + if boxed { + buffer.appendInt32(791617983) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.maxId, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateReadMessagesContents(let _data): + if boxed { + buffer.appendInt32(-131960447) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.date!, buffer: buffer, boxed: false) + } + break + case .updateReadMonoForumInbox(let _data): + if boxed { + buffer.appendInt32(2008081266) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + _data.savedPeerId.serialize(buffer, true) + serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) + break + case .updateReadMonoForumOutbox(let _data): + if boxed { + buffer.appendInt32(-1532521610) + } + serializeInt64(_data.channelId, buffer: buffer, boxed: false) + _data.savedPeerId.serialize(buffer, true) + serializeInt32(_data.readMaxId, buffer: buffer, boxed: false) + break + case .updateReadStories(let _data): + if boxed { + buffer.appendInt32(-145845461) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.maxId, buffer: buffer, boxed: false) + break + case .updateRecentEmojiStatuses: + if boxed { + buffer.appendInt32(821314523) + } + break + case .updateRecentReactions: + if boxed { + buffer.appendInt32(1870160884) + } + break + case .updateRecentStickers: + if boxed { + buffer.appendInt32(-1706939360) + } + break + case .updateSavedDialogPinned(let _data): + if boxed { + buffer.appendInt32(-1364222348) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + break + case .updateSavedGifs: + if boxed { + buffer.appendInt32(-1821035490) + } + break + case .updateSavedReactionTags: + if boxed { + buffer.appendInt32(969307186) + } + break + case .updateSavedRingtones: + if boxed { + buffer.appendInt32(1960361625) + } + break + case .updateSentPhoneCode(let _data): + if boxed { + buffer.appendInt32(1347068303) + } + _data.sentCode.serialize(buffer, true) + break + case .updateSentStoryReaction(let _data): + if boxed { + buffer.appendInt32(2103604867) + } + _data.peer.serialize(buffer, true) + serializeInt32(_data.storyId, buffer: buffer, boxed: false) + _data.reaction.serialize(buffer, true) + break + case .updateServiceNotification(let _data): + if boxed { + buffer.appendInt32(-337352679) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.inboxDate!, buffer: buffer, boxed: false) + } + serializeString(_data.type, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + _data.media.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities.count)) + for item in _data.entities { + item.serialize(buffer, true) + } + break + case .updateSmsJob(let _data): + if boxed { + buffer.appendInt32(-245208620) + } + serializeString(_data.jobId, buffer: buffer, boxed: false) + break + case .updateStarGiftAuctionState(let _data): + if boxed { + buffer.appendInt32(1222788802) + } + serializeInt64(_data.giftId, buffer: buffer, boxed: false) + _data.state.serialize(buffer, true) + break + case .updateStarGiftAuctionUserState(let _data): + if boxed { + buffer.appendInt32(-598150370) + } + serializeInt64(_data.giftId, buffer: buffer, boxed: false) + _data.userState.serialize(buffer, true) + break + case .updateStarGiftCraftFail: + if boxed { + buffer.appendInt32(-1408818108) + } + break + case .updateStarsBalance(let _data): + if boxed { + buffer.appendInt32(1317053305) + } + _data.balance.serialize(buffer, true) + break + case .updateStarsRevenueStatus(let _data): + if boxed { + buffer.appendInt32(-1518030823) + } + _data.peer.serialize(buffer, true) + _data.status.serialize(buffer, true) + break + case .updateStickerSets(let _data): + if boxed { + buffer.appendInt32(834816008) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .updateStickerSetsOrder(let _data): + if boxed { + buffer.appendInt32(196268545) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.order.count)) + for item in _data.order { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .updateStoriesStealthMode(let _data): + if boxed { + buffer.appendInt32(738741697) + } + _data.stealthMode.serialize(buffer, true) + break + case .updateStory(let _data): + if boxed { + buffer.appendInt32(1974712216) + } + _data.peer.serialize(buffer, true) + _data.story.serialize(buffer, true) + break + case .updateStoryID(let _data): + if boxed { + buffer.appendInt32(468923833) + } + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.randomId, buffer: buffer, boxed: false) + break + case .updateTheme(let _data): + if boxed { + buffer.appendInt32(-2112423005) + } + _data.theme.serialize(buffer, true) + break + case .updateTranscribedAudio(let _data): + if boxed { + buffer.appendInt32(8703322) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.msgId, buffer: buffer, boxed: false) + serializeInt64(_data.transcriptionId, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + break + case .updateUser(let _data): + if boxed { + buffer.appendInt32(542282808) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + break + case .updateUserEmojiStatus(let _data): + if boxed { + buffer.appendInt32(674706841) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + _data.emojiStatus.serialize(buffer, true) + break + case .updateUserName(let _data): + if boxed { + buffer.appendInt32(-1484486364) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.firstName, buffer: buffer, boxed: false) + serializeString(_data.lastName, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.usernames.count)) + for item in _data.usernames { + item.serialize(buffer, true) + } + break + case .updateUserPhone(let _data): + if boxed { + buffer.appendInt32(88680979) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.phone, buffer: buffer, boxed: false) + break + case .updateUserStatus(let _data): + if boxed { + buffer.appendInt32(-440534818) + } + serializeInt64(_data.userId, buffer: buffer, boxed: false) + _data.status.serialize(buffer, true) + break + case .updateUserTyping(let _data): + if boxed { + buffer.appendInt32(706199388) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.topMsgId!, buffer: buffer, boxed: false) + } + _data.action.serialize(buffer, true) + break + case .updateWebBrowserException(let _data): + if boxed { + buffer.appendInt32(335872721) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.openExternalBrowser!.serialize(buffer, true) + } + _data.exception.serialize(buffer, true) + break + case .updateWebBrowserSettings(let _data): + if boxed { + buffer.appendInt32(-1013306658) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .updateWebPage(let _data): + if boxed { + buffer.appendInt32(2139689491) + } + _data.webpage.serialize(buffer, true) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + case .updateWebViewResultSent(let _data): + if boxed { + buffer.appendInt32(361936797) + } + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .updateAiComposeTones: + return ("updateAiComposeTones", []) + case .updateAttachMenuBots: + return ("updateAttachMenuBots", []) + case .updateAutoSaveSettings: + return ("updateAutoSaveSettings", []) + case .updateBotBusinessConnect(let _data): + return ("updateBotBusinessConnect", [("connection", ConstructorParameterDescription(_data.connection)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotCallbackQuery(let _data): + return ("updateBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data)), ("gameShortName", ConstructorParameterDescription(_data.gameShortName))]) + case .updateBotChatBoost(let _data): + return ("updateBotChatBoost", [("peer", ConstructorParameterDescription(_data.peer)), ("boost", ConstructorParameterDescription(_data.boost)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotChatInviteRequester(let _data): + return ("updateBotChatInviteRequester", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("date", ConstructorParameterDescription(_data.date)), ("userId", ConstructorParameterDescription(_data.userId)), ("about", ConstructorParameterDescription(_data.about)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts)), ("queryId", ConstructorParameterDescription(_data.queryId))]) + case .updateBotCommands(let _data): + return ("updateBotCommands", [("peer", ConstructorParameterDescription(_data.peer)), ("botId", ConstructorParameterDescription(_data.botId)), ("commands", ConstructorParameterDescription(_data.commands))]) + case .updateBotDeleteBusinessMessage(let _data): + return ("updateBotDeleteBusinessMessage", [("connectionId", ConstructorParameterDescription(_data.connectionId)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotEditBusinessMessage(let _data): + return ("updateBotEditBusinessMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotGuestChatQuery(let _data): + return ("updateBotGuestChatQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("message", ConstructorParameterDescription(_data.message)), ("referenceMessages", ConstructorParameterDescription(_data.referenceMessages)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotInlineQuery(let _data): + return ("updateBotInlineQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("query", ConstructorParameterDescription(_data.query)), ("geo", ConstructorParameterDescription(_data.geo)), ("peerType", ConstructorParameterDescription(_data.peerType)), ("offset", ConstructorParameterDescription(_data.offset))]) + case .updateBotInlineSend(let _data): + return ("updateBotInlineSend", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("query", ConstructorParameterDescription(_data.query)), ("geo", ConstructorParameterDescription(_data.geo)), ("id", ConstructorParameterDescription(_data.id)), ("msgId", ConstructorParameterDescription(_data.msgId))]) + case .updateBotMenuButton(let _data): + return ("updateBotMenuButton", [("botId", ConstructorParameterDescription(_data.botId)), ("button", ConstructorParameterDescription(_data.button))]) + case .updateBotMessageReaction(let _data): + return ("updateBotMessageReaction", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("actor", ConstructorParameterDescription(_data.actor)), ("oldReactions", ConstructorParameterDescription(_data.oldReactions)), ("newReactions", ConstructorParameterDescription(_data.newReactions)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotMessageReactions(let _data): + return ("updateBotMessageReactions", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("date", ConstructorParameterDescription(_data.date)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotNewBusinessMessage(let _data): + return ("updateBotNewBusinessMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotPrecheckoutQuery(let _data): + return ("updateBotPrecheckoutQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("info", ConstructorParameterDescription(_data.info)), ("shippingOptionId", ConstructorParameterDescription(_data.shippingOptionId)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount))]) + case .updateBotPurchasedPaidMedia(let _data): + return ("updateBotPurchasedPaidMedia", [("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotShippingQuery(let _data): + return ("updateBotShippingQuery", [("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("payload", ConstructorParameterDescription(_data.payload)), ("shippingAddress", ConstructorParameterDescription(_data.shippingAddress))]) + case .updateBotStopped(let _data): + return ("updateBotStopped", [("userId", ConstructorParameterDescription(_data.userId)), ("date", ConstructorParameterDescription(_data.date)), ("stopped", ConstructorParameterDescription(_data.stopped)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateBotWebhookJSON(let _data): + return ("updateBotWebhookJSON", [("data", ConstructorParameterDescription(_data.data))]) + case .updateBotWebhookJSONQuery(let _data): + return ("updateBotWebhookJSONQuery", [("queryId", ConstructorParameterDescription(_data.queryId)), ("data", ConstructorParameterDescription(_data.data)), ("timeout", ConstructorParameterDescription(_data.timeout))]) + case .updateBusinessBotCallbackQuery(let _data): + return ("updateBusinessBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("connectionId", ConstructorParameterDescription(_data.connectionId)), ("message", ConstructorParameterDescription(_data.message)), ("replyToMessage", ConstructorParameterDescription(_data.replyToMessage)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data))]) + case .updateChannel(let _data): + return ("updateChannel", [("channelId", ConstructorParameterDescription(_data.channelId))]) + case .updateChannelAvailableMessages(let _data): + return ("updateChannelAvailableMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("availableMinId", ConstructorParameterDescription(_data.availableMinId))]) + case .updateChannelMessageForwards(let _data): + return ("updateChannelMessageForwards", [("channelId", ConstructorParameterDescription(_data.channelId)), ("id", ConstructorParameterDescription(_data.id)), ("forwards", ConstructorParameterDescription(_data.forwards))]) + case .updateChannelMessageViews(let _data): + return ("updateChannelMessageViews", [("channelId", ConstructorParameterDescription(_data.channelId)), ("id", ConstructorParameterDescription(_data.id)), ("views", ConstructorParameterDescription(_data.views))]) + case .updateChannelParticipant(let _data): + return ("updateChannelParticipant", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("date", ConstructorParameterDescription(_data.date)), ("actorId", ConstructorParameterDescription(_data.actorId)), ("userId", ConstructorParameterDescription(_data.userId)), ("prevParticipant", ConstructorParameterDescription(_data.prevParticipant)), ("newParticipant", ConstructorParameterDescription(_data.newParticipant)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateChannelReadMessagesContents(let _data): + return ("updateChannelReadMessagesContents", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("messages", ConstructorParameterDescription(_data.messages))]) + case .updateChannelTooLong(let _data): + return ("updateChannelTooLong", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("pts", ConstructorParameterDescription(_data.pts))]) + case .updateChannelUserTyping(let _data): + return ("updateChannelUserTyping", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("action", ConstructorParameterDescription(_data.action))]) + case .updateChannelViewForumAsMessages(let _data): + return ("updateChannelViewForumAsMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("enabled", ConstructorParameterDescription(_data.enabled))]) + case .updateChannelWebPage(let _data): + return ("updateChannelWebPage", [("channelId", ConstructorParameterDescription(_data.channelId)), ("webpage", ConstructorParameterDescription(_data.webpage)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateChat(let _data): + return ("updateChat", [("chatId", ConstructorParameterDescription(_data.chatId))]) + case .updateChatDefaultBannedRights(let _data): + return ("updateChatDefaultBannedRights", [("peer", ConstructorParameterDescription(_data.peer)), ("defaultBannedRights", ConstructorParameterDescription(_data.defaultBannedRights)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateChatParticipant(let _data): + return ("updateChatParticipant", [("flags", ConstructorParameterDescription(_data.flags)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("date", ConstructorParameterDescription(_data.date)), ("actorId", ConstructorParameterDescription(_data.actorId)), ("userId", ConstructorParameterDescription(_data.userId)), ("prevParticipant", ConstructorParameterDescription(_data.prevParticipant)), ("newParticipant", ConstructorParameterDescription(_data.newParticipant)), ("invite", ConstructorParameterDescription(_data.invite)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateChatParticipantAdd(let _data): + return ("updateChatParticipantAdd", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("inviterId", ConstructorParameterDescription(_data.inviterId)), ("date", ConstructorParameterDescription(_data.date)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateChatParticipantAdmin(let _data): + return ("updateChatParticipantAdmin", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("isAdmin", ConstructorParameterDescription(_data.isAdmin)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateChatParticipantDelete(let _data): + return ("updateChatParticipantDelete", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateChatParticipantRank(let _data): + return ("updateChatParticipantRank", [("chatId", ConstructorParameterDescription(_data.chatId)), ("userId", ConstructorParameterDescription(_data.userId)), ("rank", ConstructorParameterDescription(_data.rank)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateChatParticipants(let _data): + return ("updateChatParticipants", [("participants", ConstructorParameterDescription(_data.participants))]) + case .updateChatUserTyping(let _data): + return ("updateChatUserTyping", [("chatId", ConstructorParameterDescription(_data.chatId)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("action", ConstructorParameterDescription(_data.action))]) + case .updateConfig: + return ("updateConfig", []) + case .updateContactsReset: + return ("updateContactsReset", []) + case .updateDcOptions(let _data): + return ("updateDcOptions", [("dcOptions", ConstructorParameterDescription(_data.dcOptions))]) + case .updateDeleteChannelMessages(let _data): + return ("updateDeleteChannelMessages", [("channelId", ConstructorParameterDescription(_data.channelId)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateDeleteGroupCallMessages(let _data): + return ("updateDeleteGroupCallMessages", [("call", ConstructorParameterDescription(_data.call)), ("messages", ConstructorParameterDescription(_data.messages))]) + case .updateDeleteMessages(let _data): + return ("updateDeleteMessages", [("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateDeleteQuickReply(let _data): + return ("updateDeleteQuickReply", [("shortcutId", ConstructorParameterDescription(_data.shortcutId))]) + case .updateDeleteQuickReplyMessages(let _data): + return ("updateDeleteQuickReplyMessages", [("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("messages", ConstructorParameterDescription(_data.messages))]) + case .updateDeleteScheduledMessages(let _data): + return ("updateDeleteScheduledMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("sentMessages", ConstructorParameterDescription(_data.sentMessages))]) + case .updateDialogFilter(let _data): + return ("updateDialogFilter", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("filter", ConstructorParameterDescription(_data.filter))]) + case .updateDialogFilterOrder(let _data): + return ("updateDialogFilterOrder", [("order", ConstructorParameterDescription(_data.order))]) + case .updateDialogFilters: + return ("updateDialogFilters", []) + case .updateDialogPinned(let _data): + return ("updateDialogPinned", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("peer", ConstructorParameterDescription(_data.peer))]) + case .updateDialogUnreadMark(let _data): + return ("updateDialogUnreadMark", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId))]) + case .updateDraftMessage(let _data): + return ("updateDraftMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("draft", ConstructorParameterDescription(_data.draft))]) + case .updateEditChannelMessage(let _data): + return ("updateEditChannelMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateEditMessage(let _data): + return ("updateEditMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateEmojiGameInfo(let _data): + return ("updateEmojiGameInfo", [("info", ConstructorParameterDescription(_data.info))]) + case .updateEncryptedChatTyping(let _data): + return ("updateEncryptedChatTyping", [("chatId", ConstructorParameterDescription(_data.chatId))]) + case .updateEncryptedMessagesRead(let _data): + return ("updateEncryptedMessagesRead", [("chatId", ConstructorParameterDescription(_data.chatId)), ("maxDate", ConstructorParameterDescription(_data.maxDate)), ("date", ConstructorParameterDescription(_data.date))]) + case .updateEncryption(let _data): + return ("updateEncryption", [("chat", ConstructorParameterDescription(_data.chat)), ("date", ConstructorParameterDescription(_data.date))]) + case .updateFavedStickers: + return ("updateFavedStickers", []) + case .updateFolderPeers(let _data): + return ("updateFolderPeers", [("folderPeers", ConstructorParameterDescription(_data.folderPeers)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateGeoLiveViewed(let _data): + return ("updateGeoLiveViewed", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId))]) + case .updateGroupCall(let _data): + return ("updateGroupCall", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("call", ConstructorParameterDescription(_data.call))]) + case .updateGroupCallChainBlocks(let _data): + return ("updateGroupCallChainBlocks", [("call", ConstructorParameterDescription(_data.call)), ("subChainId", ConstructorParameterDescription(_data.subChainId)), ("blocks", ConstructorParameterDescription(_data.blocks)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + case .updateGroupCallConnection(let _data): + return ("updateGroupCallConnection", [("flags", ConstructorParameterDescription(_data.flags)), ("params", ConstructorParameterDescription(_data.params))]) + case .updateGroupCallEncryptedMessage(let _data): + return ("updateGroupCallEncryptedMessage", [("call", ConstructorParameterDescription(_data.call)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("encryptedMessage", ConstructorParameterDescription(_data.encryptedMessage))]) + case .updateGroupCallMessage(let _data): + return ("updateGroupCallMessage", [("call", ConstructorParameterDescription(_data.call)), ("message", ConstructorParameterDescription(_data.message))]) + case .updateGroupCallParticipants(let _data): + return ("updateGroupCallParticipants", [("call", ConstructorParameterDescription(_data.call)), ("participants", ConstructorParameterDescription(_data.participants)), ("version", ConstructorParameterDescription(_data.version))]) + case .updateInlineBotCallbackQuery(let _data): + return ("updateInlineBotCallbackQuery", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("userId", ConstructorParameterDescription(_data.userId)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("chatInstance", ConstructorParameterDescription(_data.chatInstance)), ("data", ConstructorParameterDescription(_data.data)), ("gameShortName", ConstructorParameterDescription(_data.gameShortName))]) + case .updateJoinChatWebViewDecision(let _data): + return ("updateJoinChatWebViewDecision", [("peer", ConstructorParameterDescription(_data.peer)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("result", ConstructorParameterDescription(_data.result))]) + case .updateLangPack(let _data): + return ("updateLangPack", [("difference", ConstructorParameterDescription(_data.difference))]) + case .updateLangPackTooLong(let _data): + return ("updateLangPackTooLong", [("langCode", ConstructorParameterDescription(_data.langCode))]) + case .updateLoginToken: + return ("updateLoginToken", []) + case .updateManagedBot(let _data): + return ("updateManagedBot", [("userId", ConstructorParameterDescription(_data.userId)), ("botId", ConstructorParameterDescription(_data.botId)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateMessageExtendedMedia(let _data): + return ("updateMessageExtendedMedia", [("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("extendedMedia", ConstructorParameterDescription(_data.extendedMedia))]) + case .updateMessageID(let _data): + return ("updateMessageID", [("id", ConstructorParameterDescription(_data.id)), ("randomId", ConstructorParameterDescription(_data.randomId))]) + case .updateMessagePoll(let _data): + return ("updateMessagePoll", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("pollId", ConstructorParameterDescription(_data.pollId)), ("poll", ConstructorParameterDescription(_data.poll)), ("results", ConstructorParameterDescription(_data.results))]) + case .updateMessagePollVote(let _data): + return ("updateMessagePollVote", [("pollId", ConstructorParameterDescription(_data.pollId)), ("peer", ConstructorParameterDescription(_data.peer)), ("options", ConstructorParameterDescription(_data.options)), ("positions", ConstructorParameterDescription(_data.positions)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateMessageReactions(let _data): + return ("updateMessageReactions", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("reactions", ConstructorParameterDescription(_data.reactions))]) + case .updateMonoForumNoPaidException(let _data): + return ("updateMonoForumNoPaidException", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId))]) + case .updateMoveStickerSetToTop(let _data): + return ("updateMoveStickerSetToTop", [("flags", ConstructorParameterDescription(_data.flags)), ("stickerset", ConstructorParameterDescription(_data.stickerset))]) + case .updateNewAuthorization(let _data): + return ("updateNewAuthorization", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("date", ConstructorParameterDescription(_data.date)), ("device", ConstructorParameterDescription(_data.device)), ("location", ConstructorParameterDescription(_data.location))]) + case .updateNewBotConnection(let _data): + return ("updateNewBotConnection", [("flags", ConstructorParameterDescription(_data.flags)), ("botId", ConstructorParameterDescription(_data.botId)), ("date", ConstructorParameterDescription(_data.date)), ("device", ConstructorParameterDescription(_data.device)), ("location", ConstructorParameterDescription(_data.location))]) + case .updateNewChannelMessage(let _data): + return ("updateNewChannelMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateNewEncryptedMessage(let _data): + return ("updateNewEncryptedMessage", [("message", ConstructorParameterDescription(_data.message)), ("qts", ConstructorParameterDescription(_data.qts))]) + case .updateNewMessage(let _data): + return ("updateNewMessage", [("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateNewQuickReply(let _data): + return ("updateNewQuickReply", [("quickReply", ConstructorParameterDescription(_data.quickReply))]) + case .updateNewScheduledMessage(let _data): + return ("updateNewScheduledMessage", [("message", ConstructorParameterDescription(_data.message))]) + case .updateNewStickerSet(let _data): + return ("updateNewStickerSet", [("stickerset", ConstructorParameterDescription(_data.stickerset))]) + case .updateNewStoryReaction(let _data): + return ("updateNewStoryReaction", [("storyId", ConstructorParameterDescription(_data.storyId)), ("peer", ConstructorParameterDescription(_data.peer)), ("reaction", ConstructorParameterDescription(_data.reaction))]) + case .updateNotifySettings(let _data): + return ("updateNotifySettings", [("peer", ConstructorParameterDescription(_data.peer)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings))]) + case .updatePaidReactionPrivacy(let _data): + return ("updatePaidReactionPrivacy", [("`private`", ConstructorParameterDescription(_data.`private`))]) + case .updatePeerBlocked(let _data): + return ("updatePeerBlocked", [("flags", ConstructorParameterDescription(_data.flags)), ("peerId", ConstructorParameterDescription(_data.peerId))]) + case .updatePeerHistoryTTL(let _data): + return ("updatePeerHistoryTTL", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) + case .updatePeerLocated(let _data): + return ("updatePeerLocated", [("peers", ConstructorParameterDescription(_data.peers))]) + case .updatePeerSettings(let _data): + return ("updatePeerSettings", [("peer", ConstructorParameterDescription(_data.peer)), ("settings", ConstructorParameterDescription(_data.settings))]) + case .updatePeerWallpaper(let _data): + return ("updatePeerWallpaper", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper))]) + case .updatePendingJoinRequests(let _data): + return ("updatePendingJoinRequests", [("peer", ConstructorParameterDescription(_data.peer)), ("requestsPending", ConstructorParameterDescription(_data.requestsPending)), ("recentRequesters", ConstructorParameterDescription(_data.recentRequesters))]) + case .updatePhoneCall(let _data): + return ("updatePhoneCall", [("phoneCall", ConstructorParameterDescription(_data.phoneCall))]) + case .updatePhoneCallSignalingData(let _data): + return ("updatePhoneCallSignalingData", [("phoneCallId", ConstructorParameterDescription(_data.phoneCallId)), ("data", ConstructorParameterDescription(_data.data))]) + case .updatePinnedChannelMessages(let _data): + return ("updatePinnedChannelMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updatePinnedDialogs(let _data): + return ("updatePinnedDialogs", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("order", ConstructorParameterDescription(_data.order))]) + case .updatePinnedForumTopic(let _data): + return ("updatePinnedForumTopic", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("topicId", ConstructorParameterDescription(_data.topicId))]) + case .updatePinnedForumTopics(let _data): + return ("updatePinnedForumTopics", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("order", ConstructorParameterDescription(_data.order))]) + case .updatePinnedMessages(let _data): + return ("updatePinnedMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updatePinnedSavedDialogs(let _data): + return ("updatePinnedSavedDialogs", [("flags", ConstructorParameterDescription(_data.flags)), ("order", ConstructorParameterDescription(_data.order))]) + case .updatePrivacy(let _data): + return ("updatePrivacy", [("key", ConstructorParameterDescription(_data.key)), ("rules", ConstructorParameterDescription(_data.rules))]) + case .updatePtsChanged: + return ("updatePtsChanged", []) + case .updateQuickReplies(let _data): + return ("updateQuickReplies", [("quickReplies", ConstructorParameterDescription(_data.quickReplies))]) + case .updateQuickReplyMessage(let _data): + return ("updateQuickReplyMessage", [("message", ConstructorParameterDescription(_data.message))]) + case .updateReadChannelDiscussionInbox(let _data): + return ("updateReadChannelDiscussionInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId)), ("broadcastId", ConstructorParameterDescription(_data.broadcastId)), ("broadcastPost", ConstructorParameterDescription(_data.broadcastPost))]) + case .updateReadChannelDiscussionOutbox(let _data): + return ("updateReadChannelDiscussionOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) + case .updateReadChannelInbox(let _data): + return ("updateReadChannelInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("stillUnreadCount", ConstructorParameterDescription(_data.stillUnreadCount)), ("pts", ConstructorParameterDescription(_data.pts))]) + case .updateReadChannelOutbox(let _data): + return ("updateReadChannelOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("maxId", ConstructorParameterDescription(_data.maxId))]) + case .updateReadFeaturedEmojiStickers: + return ("updateReadFeaturedEmojiStickers", []) + case .updateReadFeaturedStickers: + return ("updateReadFeaturedStickers", []) + case .updateReadHistoryInbox(let _data): + return ("updateReadHistoryInbox", [("flags", ConstructorParameterDescription(_data.flags)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("peer", ConstructorParameterDescription(_data.peer)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("stillUnreadCount", ConstructorParameterDescription(_data.stillUnreadCount)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateReadHistoryOutbox(let _data): + return ("updateReadHistoryOutbox", [("peer", ConstructorParameterDescription(_data.peer)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateReadMessagesContents(let _data): + return ("updateReadMessagesContents", [("flags", ConstructorParameterDescription(_data.flags)), ("messages", ConstructorParameterDescription(_data.messages)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date))]) + case .updateReadMonoForumInbox(let _data): + return ("updateReadMonoForumInbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) + case .updateReadMonoForumOutbox(let _data): + return ("updateReadMonoForumOutbox", [("channelId", ConstructorParameterDescription(_data.channelId)), ("savedPeerId", ConstructorParameterDescription(_data.savedPeerId)), ("readMaxId", ConstructorParameterDescription(_data.readMaxId))]) + case .updateReadStories(let _data): + return ("updateReadStories", [("peer", ConstructorParameterDescription(_data.peer)), ("maxId", ConstructorParameterDescription(_data.maxId))]) + case .updateRecentEmojiStatuses: + return ("updateRecentEmojiStatuses", []) + case .updateRecentReactions: + return ("updateRecentReactions", []) + case .updateRecentStickers: + return ("updateRecentStickers", []) + case .updateSavedDialogPinned(let _data): + return ("updateSavedDialogPinned", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer))]) + case .updateSavedGifs: + return ("updateSavedGifs", []) + case .updateSavedReactionTags: + return ("updateSavedReactionTags", []) + case .updateSavedRingtones: + return ("updateSavedRingtones", []) + case .updateSentPhoneCode(let _data): + return ("updateSentPhoneCode", [("sentCode", ConstructorParameterDescription(_data.sentCode))]) + case .updateSentStoryReaction(let _data): + return ("updateSentStoryReaction", [("peer", ConstructorParameterDescription(_data.peer)), ("storyId", ConstructorParameterDescription(_data.storyId)), ("reaction", ConstructorParameterDescription(_data.reaction))]) + case .updateServiceNotification(let _data): + return ("updateServiceNotification", [("flags", ConstructorParameterDescription(_data.flags)), ("inboxDate", ConstructorParameterDescription(_data.inboxDate)), ("type", ConstructorParameterDescription(_data.type)), ("message", ConstructorParameterDescription(_data.message)), ("media", ConstructorParameterDescription(_data.media)), ("entities", ConstructorParameterDescription(_data.entities))]) + case .updateSmsJob(let _data): + return ("updateSmsJob", [("jobId", ConstructorParameterDescription(_data.jobId))]) + case .updateStarGiftAuctionState(let _data): + return ("updateStarGiftAuctionState", [("giftId", ConstructorParameterDescription(_data.giftId)), ("state", ConstructorParameterDescription(_data.state))]) + case .updateStarGiftAuctionUserState(let _data): + return ("updateStarGiftAuctionUserState", [("giftId", ConstructorParameterDescription(_data.giftId)), ("userState", ConstructorParameterDescription(_data.userState))]) + case .updateStarGiftCraftFail: + return ("updateStarGiftCraftFail", []) + case .updateStarsBalance(let _data): + return ("updateStarsBalance", [("balance", ConstructorParameterDescription(_data.balance))]) + case .updateStarsRevenueStatus(let _data): + return ("updateStarsRevenueStatus", [("peer", ConstructorParameterDescription(_data.peer)), ("status", ConstructorParameterDescription(_data.status))]) + case .updateStickerSets(let _data): + return ("updateStickerSets", [("flags", ConstructorParameterDescription(_data.flags))]) + case .updateStickerSetsOrder(let _data): + return ("updateStickerSetsOrder", [("flags", ConstructorParameterDescription(_data.flags)), ("order", ConstructorParameterDescription(_data.order))]) + case .updateStoriesStealthMode(let _data): + return ("updateStoriesStealthMode", [("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) + case .updateStory(let _data): + return ("updateStory", [("peer", ConstructorParameterDescription(_data.peer)), ("story", ConstructorParameterDescription(_data.story))]) + case .updateStoryID(let _data): + return ("updateStoryID", [("id", ConstructorParameterDescription(_data.id)), ("randomId", ConstructorParameterDescription(_data.randomId))]) + case .updateTheme(let _data): + return ("updateTheme", [("theme", ConstructorParameterDescription(_data.theme))]) + case .updateTranscribedAudio(let _data): + return ("updateTranscribedAudio", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("msgId", ConstructorParameterDescription(_data.msgId)), ("transcriptionId", ConstructorParameterDescription(_data.transcriptionId)), ("text", ConstructorParameterDescription(_data.text))]) + case .updateUser(let _data): + return ("updateUser", [("userId", ConstructorParameterDescription(_data.userId))]) + case .updateUserEmojiStatus(let _data): + return ("updateUserEmojiStatus", [("userId", ConstructorParameterDescription(_data.userId)), ("emojiStatus", ConstructorParameterDescription(_data.emojiStatus))]) + case .updateUserName(let _data): + return ("updateUserName", [("userId", ConstructorParameterDescription(_data.userId)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("usernames", ConstructorParameterDescription(_data.usernames))]) + case .updateUserPhone(let _data): + return ("updateUserPhone", [("userId", ConstructorParameterDescription(_data.userId)), ("phone", ConstructorParameterDescription(_data.phone))]) + case .updateUserStatus(let _data): + return ("updateUserStatus", [("userId", ConstructorParameterDescription(_data.userId)), ("status", ConstructorParameterDescription(_data.status))]) + case .updateUserTyping(let _data): + return ("updateUserTyping", [("flags", ConstructorParameterDescription(_data.flags)), ("userId", ConstructorParameterDescription(_data.userId)), ("topMsgId", ConstructorParameterDescription(_data.topMsgId)), ("action", ConstructorParameterDescription(_data.action))]) + case .updateWebBrowserException(let _data): + return ("updateWebBrowserException", [("flags", ConstructorParameterDescription(_data.flags)), ("openExternalBrowser", ConstructorParameterDescription(_data.openExternalBrowser)), ("exception", ConstructorParameterDescription(_data.exception))]) + case .updateWebBrowserSettings(let _data): + return ("updateWebBrowserSettings", [("flags", ConstructorParameterDescription(_data.flags))]) + case .updateWebPage(let _data): + return ("updateWebPage", [("webpage", ConstructorParameterDescription(_data.webpage)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + case .updateWebViewResultSent(let _data): + return ("updateWebViewResultSent", [("queryId", ConstructorParameterDescription(_data.queryId))]) + } + } + + public static func parse_updateAiComposeTones(_ reader: BufferReader) -> Update? { + return Api.Update.updateAiComposeTones + } + public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? { + return Api.Update.updateAttachMenuBots + } + public static func parse_updateAutoSaveSettings(_ reader: BufferReader) -> Update? { + return Api.Update.updateAutoSaveSettings + } + public static func parse_updateBotBusinessConnect(_ reader: BufferReader) -> Update? { + var _1: Api.BotBusinessConnection? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.BotBusinessConnection } var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.Updates.updateShort(Cons_updateShort(update: _1!, date: _2!)) + return Api.Update.updateBotBusinessConnect(Cons_updateBotBusinessConnect(connection: _1!, qts: _2!)) } else { return nil } } - public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { + public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() var _3: Int64? _3 = reader.readInt64() + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + _6 = reader.readInt64() + var _7: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = parseBytes(reader) + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _8 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateBotCallbackQuery(Cons_updateBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, peer: _4!, msgId: _5!, chatInstance: _6!, data: _7, gameShortName: _8)) + } + else { + return nil + } + } + public static func parse_updateBotChatBoost(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.Boost? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Boost + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotChatBoost(Cons_updateBotChatBoost(peer: _1!, boost: _2!, qts: _3!)) + } + else { + return nil + } + } + public static func parse_updateBotChatInviteRequester(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() var _4: Int64? _4 = reader.readInt64() var _5: String? _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() + var _6: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } var _7: Int32? _7 = reader.readInt32() - var _8: Int32? - _8 = reader.readInt32() - var _9: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } - } - var _10: Int64? - if Int(_1!) & Int(1 << 11) != 0 { - _10 = reader.readInt64() - } - var _11: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } - } - var _12: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 { - if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _13: Int32? - if Int(_1!) & Int(1 << 25) != 0 { - _13 = reader.readInt32() + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _8 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil @@ -375,60 +4685,227 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 25) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.Updates.updateShortChatMessage(Cons_updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13)) + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateBotChatInviteRequester(Cons_updateBotChatInviteRequester(flags: _1!, peer: _2!, date: _3!, userId: _4!, about: _5!, invite: _6!, qts: _7!, queryId: _8)) } else { return nil } } - public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { + public static func parse_updateBotCommands(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int64? + _2 = reader.readInt64() + var _3: [Api.BotCommand]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotCommands(Cons_updateBotCommands(peer: _1!, botId: _2!, commands: _3!)) + } + else { + return nil + } + } + public static func parse_updateBotDeleteBusinessMessage(_ reader: BufferReader) -> Update? { + var _1: String? + _1 = parseString(reader) + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotDeleteBusinessMessage(Cons_updateBotDeleteBusinessMessage(connectionId: _1!, peer: _2!, messages: _3!, qts: _4!)) + } + else { + return nil + } + } + public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Message + } + var _4: Api.Message? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Message + } + } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotEditBusinessMessage(Cons_updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!)) + } + else { + return nil + } + } + public static func parse_updateBotGuestChatQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.Message? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Message + } + var _4: [Api.Message]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + } + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotGuestChatQuery(Cons_updateBotGuestChatQuery(flags: _1!, queryId: _2!, message: _3!, referenceMessages: _4, qts: _5!)) + } + else { + return nil + } + } + public static func parse_updateBotInlineQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() var _3: Int64? _3 = reader.readInt64() var _4: String? _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() + var _5: Api.GeoPoint? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + } + var _6: Api.InlineQueryPeerType? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InlineQueryPeerType + } + } + var _7: String? + _7 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateBotInlineQuery(Cons_updateBotInlineQuery(flags: _1!, queryId: _2!, userId: _3!, query: _4!, geo: _5, peerType: _6, offset: _7!)) + } + else { + return nil + } + } + public static func parse_updateBotInlineSend(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Api.GeoPoint? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.GeoPoint + } + } + var _5: String? + _5 = parseString(reader) + var _6: Api.InputBotInlineMessageID? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateBotInlineSend(Cons_updateBotInlineSend(flags: _1!, userId: _2!, query: _3!, geo: _4, id: _5!, msgId: _6)) + } + else { + return nil + } + } + public static func parse_updateBotMenuButton(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.BotMenuButton? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BotMenuButton + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateBotMenuButton(Cons_updateBotMenuButton(botId: _1!, button: _2!)) + } + else { + return nil + } + } + public static func parse_updateBotMessageReaction(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: [Api.Reaction]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } + var _6: [Api.Reaction]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } var _7: Int32? _7 = reader.readInt32() - var _8: Api.MessageFwdHeader? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader - } - } - var _9: Int64? - if Int(_1!) & Int(1 << 11) != 0 { - _9 = reader.readInt64() - } - var _10: Api.MessageReplyHeader? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader - } - } - var _11: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 { - if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _12: Int32? - if Int(_1!) & Int(1 << 25) != 0 { - _12 = reader.readInt32() - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -436,73 +4913,549 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 11) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 25) == 0) || _12 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { - return Api.Updates.updateShortMessage(Cons_updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12)) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateBotMessageReaction(Cons_updateBotMessageReaction(peer: _1!, msgId: _2!, date: _3!, actor: _4!, oldReactions: _5!, newReactions: _6!, qts: _7!)) } else { return nil } } - public static func parse_updateShortSentMessage(_ reader: BufferReader) -> Updates? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_updateBotMessageReactions(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } var _2: Int32? _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + var _4: [Api.ReactionCount]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReactionCount.self) + } var _5: Int32? _5 = reader.readInt32() - var _6: Api.MessageMedia? - if Int(_1!) & Int(1 << 9) != 0 { + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotMessageReactions(Cons_updateBotMessageReactions(peer: _1!, msgId: _2!, date: _3!, reactions: _4!, qts: _5!)) + } + else { + return nil + } + } + public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Message + } + var _4: Api.Message? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.MessageMedia + _4 = Api.parse(reader, signature: signature) as? Api.Message } } - var _7: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 { - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotNewBusinessMessage(Cons_updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!)) + } + else { + return nil + } + } + public static func parse_updateBotPrecheckoutQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Api.PaymentRequestedInfo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo } } - var _8: Int32? - if Int(_1!) & Int(1 << 25) != 0 { - _8 = reader.readInt32() + var _6: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = parseString(reader) + } + var _7: String? + _7 = parseString(reader) + var _8: Int64? + _8 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateBotPrecheckoutQuery(Cons_updateBotPrecheckoutQuery(flags: _1!, queryId: _2!, userId: _3!, payload: _4!, info: _5, shippingOptionId: _6, currency: _7!, totalAmount: _8!)) + } + else { + return nil + } + } + public static func parse_updateBotPurchasedPaidMedia(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotPurchasedPaidMedia(Cons_updateBotPurchasedPaidMedia(userId: _1!, payload: _2!, qts: _3!)) + } + else { + return nil + } + } + public static func parse_updateBotShippingQuery(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Api.PostAddress? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.PostAddress + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotShippingQuery(Cons_updateBotShippingQuery(queryId: _1!, userId: _2!, payload: _3!, shippingAddress: _4!)) + } + else { + return nil + } + } + public static func parse_updateBotStopped(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Bool? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateBotStopped(Cons_updateBotStopped(userId: _1!, date: _2!, stopped: _3!, qts: _4!)) + } + else { + return nil + } + } + public static func parse_updateBotWebhookJSON(_ reader: BufferReader) -> Update? { + var _1: Api.DataJSON? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateBotWebhookJSON(Cons_updateBotWebhookJSON(data: _1!)) + } + else { + return nil + } + } + public static func parse_updateBotWebhookJSONQuery(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateBotWebhookJSONQuery(Cons_updateBotWebhookJSONQuery(queryId: _1!, data: _2!, timeout: _3!)) + } + else { + return nil + } + } + public static func parse_updateBusinessBotCallbackQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Api.Message? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Message + } + var _6: Api.Message? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Message + } + } + var _7: Int64? + _7 = reader.readInt64() + var _8: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _8 = parseBytes(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 9) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.Updates.updateShortSentMessage(Cons_updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8)) + return Api.Update.updateBusinessBotCallbackQuery(Cons_updateBusinessBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, connectionId: _4!, message: _5!, replyToMessage: _6, chatInstance: _7!, data: _8)) } else { return nil } } - public static func parse_updates(_ reader: BufferReader) -> Updates? { - var _1: [Api.Update]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + public static func parse_updateChannel(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChannel(Cons_updateChannel(channelId: _1!)) } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + else { + return nil } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + public static func parse_updateChannelAvailableMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateChannelAvailableMessages(Cons_updateChannelAvailableMessages(channelId: _1!, availableMinId: _2!)) } + else { + return nil + } + } + public static func parse_updateChannelMessageForwards(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelMessageForwards(Cons_updateChannelMessageForwards(channelId: _1!, id: _2!, forwards: _3!)) + } + else { + return nil + } + } + public static func parse_updateChannelMessageViews(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelMessageViews(Cons_updateChannelMessageViews(channelId: _1!, id: _2!, views: _3!)) + } + else { + return nil + } + } + public static func parse_updateChannelParticipant(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.ChannelParticipant? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant + } + } + var _7: Api.ChannelParticipant? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant + } + } + var _8: Api.ExportedChatInvite? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + } + var _9: Int32? + _9 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Update.updateChannelParticipant(Cons_updateChannelParticipant(flags: _1!, channelId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!)) + } + else { + return nil + } + } + public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _5: [Int32]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateChannelReadMessagesContents(Cons_updateChannelReadMessagesContents(flags: _1!, channelId: _2!, topMsgId: _3, savedPeerId: _4, messages: _5!)) + } + else { + return nil + } + } + public static func parse_updateChannelTooLong(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChannelTooLong(Cons_updateChannelTooLong(flags: _1!, channelId: _2!, pts: _3)) + } + else { + return nil + } + } + public static func parse_updateChannelUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Api.Peer? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _5: Api.SendMessageAction? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateChannelUserTyping(Cons_updateChannelUserTyping(flags: _1!, channelId: _2!, topMsgId: _3, fromId: _4!, action: _5!)) + } + else { + return nil + } + } + public static func parse_updateChannelViewForumAsMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Bool? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateChannelViewForumAsMessages(Cons_updateChannelViewForumAsMessages(channelId: _1!, enabled: _2!)) + } + else { + return nil + } + } + public static func parse_updateChannelWebPage(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.WebPage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WebPage + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChannelWebPage(Cons_updateChannelWebPage(channelId: _1!, webpage: _2!, pts: _3!, ptsCount: _4!)) + } + else { + return nil + } + } + public static func parse_updateChat(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChat(Cons_updateChat(chatId: _1!)) + } + else { + return nil + } + } + public static func parse_updateChatDefaultBannedRights(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.ChatBannedRights? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatDefaultBannedRights(Cons_updateChatDefaultBannedRights(peer: _1!, defaultBannedRights: _2!, version: _3!)) + } + else { + return nil + } + } + public static func parse_updateChatParticipant(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.ChatParticipant? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ChatParticipant + } + } + var _7: Api.ChatParticipant? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.ChatParticipant + } + } + var _8: Api.ExportedChatInvite? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + } + var _9: Int32? + _9 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.Update.updateChatParticipant(Cons_updateChatParticipant(flags: _1!, chatId: _2!, date: _3!, actorId: _4!, userId: _5!, prevParticipant: _6, newParticipant: _7, invite: _8, qts: _9!)) + } + else { + return nil + } + } + public static func parse_updateChatParticipantAdd(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() var _4: Int32? _4 = reader.readInt32() var _5: Int32? @@ -513,1426 +5466,151 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Updates.updates(Cons_updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!)) + return Api.Update.updateChatParticipantAdd(Cons_updateChatParticipantAdd(chatId: _1!, userId: _2!, inviterId: _3!, date: _4!, version: _5!)) } else { return nil } } - public static func parse_updatesCombined(_ reader: BufferReader) -> Updates? { - var _1: [Api.Update]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.Updates.updatesCombined(Cons_updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!)) - } - else { - return nil - } - } - public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { - return Api.Updates.updatesTooLong - } - } -} -public extension Api { - enum UrlAuthResult: TypeConstructorDescription { - public class Cons_urlAuthResultAccepted: TypeConstructorDescription { - public var flags: Int32 - public var url: String? - public init(flags: Int32, url: String?) { - self.flags = flags - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("urlAuthResultAccepted", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url))]) - } - } - public class Cons_urlAuthResultRequest: TypeConstructorDescription { - public var flags: Int32 - public var bot: Api.User - public var domain: String - public var browser: String? - public var platform: String? - public var ip: String? - public var region: String? - public var matchCodes: [String]? - public var userIdHint: Int64? - public var verifiedAppName: String? - public init(flags: Int32, bot: Api.User, domain: String, browser: String?, platform: String?, ip: String?, region: String?, matchCodes: [String]?, userIdHint: Int64?, verifiedAppName: String?) { - self.flags = flags - self.bot = bot - self.domain = domain - self.browser = browser - self.platform = platform - self.ip = ip - self.region = region - self.matchCodes = matchCodes - self.userIdHint = userIdHint - self.verifiedAppName = verifiedAppName - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("urlAuthResultRequest", [("flags", ConstructorParameterDescription(self.flags)), ("bot", ConstructorParameterDescription(self.bot)), ("domain", ConstructorParameterDescription(self.domain)), ("browser", ConstructorParameterDescription(self.browser)), ("platform", ConstructorParameterDescription(self.platform)), ("ip", ConstructorParameterDescription(self.ip)), ("region", ConstructorParameterDescription(self.region)), ("matchCodes", ConstructorParameterDescription(self.matchCodes)), ("userIdHint", ConstructorParameterDescription(self.userIdHint)), ("verifiedAppName", ConstructorParameterDescription(self.verifiedAppName))]) - } - } - case urlAuthResultAccepted(Cons_urlAuthResultAccepted) - case urlAuthResultDefault - case urlAuthResultRequest(Cons_urlAuthResultRequest) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .urlAuthResultAccepted(let _data): - if boxed { - buffer.appendInt32(1648005024) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - break - case .urlAuthResultDefault: - if boxed { - buffer.appendInt32(-1445536993) - } - break - case .urlAuthResultRequest(let _data): - if boxed { - buffer.appendInt32(1020666860) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.bot.serialize(buffer, true) - serializeString(_data.domain, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.browser!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.platform!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.ip!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.region!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.matchCodes!.count)) - for item in _data.matchCodes! { - serializeString(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.userIdHint!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeString(_data.verifiedAppName!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .urlAuthResultAccepted(let _data): - return ("urlAuthResultAccepted", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url))]) - case .urlAuthResultDefault: - return ("urlAuthResultDefault", []) - case .urlAuthResultRequest(let _data): - return ("urlAuthResultRequest", [("flags", ConstructorParameterDescription(_data.flags)), ("bot", ConstructorParameterDescription(_data.bot)), ("domain", ConstructorParameterDescription(_data.domain)), ("browser", ConstructorParameterDescription(_data.browser)), ("platform", ConstructorParameterDescription(_data.platform)), ("ip", ConstructorParameterDescription(_data.ip)), ("region", ConstructorParameterDescription(_data.region)), ("matchCodes", ConstructorParameterDescription(_data.matchCodes)), ("userIdHint", ConstructorParameterDescription(_data.userIdHint)), ("verifiedAppName", ConstructorParameterDescription(_data.verifiedAppName))]) - } - } - - public static func parse_urlAuthResultAccepted(_ reader: BufferReader) -> UrlAuthResult? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.UrlAuthResult.urlAuthResultAccepted(Cons_urlAuthResultAccepted(flags: _1!, url: _2)) - } - else { - return nil - } - } - public static func parse_urlAuthResultDefault(_ reader: BufferReader) -> UrlAuthResult? { - return Api.UrlAuthResult.urlAuthResultDefault - } - public static func parse_urlAuthResultRequest(_ reader: BufferReader) -> UrlAuthResult? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.User? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.User - } - var _3: String? - _3 = parseString(reader) - var _4: String? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = parseString(reader) - } - var _6: String? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = parseString(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 2) != 0 { - _7 = parseString(reader) - } - var _8: [String]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - } - var _9: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _9 = reader.readInt64() - } - var _10: String? - if Int(_1!) & Int(1 << 7) != 0 { - _10 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.UrlAuthResult.urlAuthResultRequest(Cons_urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!, browser: _4, platform: _5, ip: _6, region: _7, matchCodes: _8, userIdHint: _9, verifiedAppName: _10)) - } - else { - return nil - } - } - } -} -public extension Api { - enum User: TypeConstructorDescription { - public class Cons_user: TypeConstructorDescription { - public var flags: Int32 - public var flags2: Int32 - public var id: Int64 - public var accessHash: Int64? - public var firstName: String? - public var lastName: String? - public var username: String? - public var phone: String? - public var photo: Api.UserProfilePhoto? - public var status: Api.UserStatus? - public var botInfoVersion: Int32? - public var restrictionReason: [Api.RestrictionReason]? - public var botInlinePlaceholder: String? - public var langCode: String? - public var emojiStatus: Api.EmojiStatus? - public var usernames: [Api.Username]? - public var storiesMaxId: Api.RecentStory? - public var color: Api.PeerColor? - public var profileColor: Api.PeerColor? - public var botActiveUsers: Int32? - public var botVerificationIcon: Int64? - public var sendPaidMessagesStars: Int64? - public init(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?, storiesMaxId: Api.RecentStory?, color: Api.PeerColor?, profileColor: Api.PeerColor?, botActiveUsers: Int32?, botVerificationIcon: Int64?, sendPaidMessagesStars: Int64?) { - self.flags = flags - self.flags2 = flags2 - self.id = id - self.accessHash = accessHash - self.firstName = firstName - self.lastName = lastName - self.username = username - self.phone = phone - self.photo = photo - self.status = status - self.botInfoVersion = botInfoVersion - self.restrictionReason = restrictionReason - self.botInlinePlaceholder = botInlinePlaceholder - self.langCode = langCode - self.emojiStatus = emojiStatus - self.usernames = usernames - self.storiesMaxId = storiesMaxId - self.color = color - self.profileColor = profileColor - self.botActiveUsers = botActiveUsers - self.botVerificationIcon = botVerificationIcon - self.sendPaidMessagesStars = sendPaidMessagesStars - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("user", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("username", ConstructorParameterDescription(self.username)), ("phone", ConstructorParameterDescription(self.phone)), ("photo", ConstructorParameterDescription(self.photo)), ("status", ConstructorParameterDescription(self.status)), ("botInfoVersion", ConstructorParameterDescription(self.botInfoVersion)), ("restrictionReason", ConstructorParameterDescription(self.restrictionReason)), ("botInlinePlaceholder", ConstructorParameterDescription(self.botInlinePlaceholder)), ("langCode", ConstructorParameterDescription(self.langCode)), ("emojiStatus", ConstructorParameterDescription(self.emojiStatus)), ("usernames", ConstructorParameterDescription(self.usernames)), ("storiesMaxId", ConstructorParameterDescription(self.storiesMaxId)), ("color", ConstructorParameterDescription(self.color)), ("profileColor", ConstructorParameterDescription(self.profileColor)), ("botActiveUsers", ConstructorParameterDescription(self.botActiveUsers)), ("botVerificationIcon", ConstructorParameterDescription(self.botVerificationIcon)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars))]) - } - } - public class Cons_userEmpty: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userEmpty", [("id", ConstructorParameterDescription(self.id))]) - } - } - case user(Cons_user) - case userEmpty(Cons_userEmpty) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .user(let _data): - if boxed { - buffer.appendInt32(829899656) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.flags2, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.accessHash!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.firstName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.lastName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.username!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.phone!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - _data.status!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - serializeInt32(_data.botInfoVersion!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 18) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.restrictionReason!.count)) - for item in _data.restrictionReason! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 19) != 0 { - serializeString(_data.botInlinePlaceholder!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 22) != 0 { - serializeString(_data.langCode!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 30) != 0 { - _data.emojiStatus!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.usernames!.count)) - for item in _data.usernames! { - item.serialize(buffer, true) - } - } - if Int(_data.flags2) & Int(1 << 5) != 0 { - _data.storiesMaxId!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 8) != 0 { - _data.color!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 9) != 0 { - _data.profileColor!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 12) != 0 { - serializeInt32(_data.botActiveUsers!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 14) != 0 { - serializeInt64(_data.botVerificationIcon!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 15) != 0 { - serializeInt64(_data.sendPaidMessagesStars!, buffer: buffer, boxed: false) - } - break - case .userEmpty(let _data): - if boxed { - buffer.appendInt32(-742634630) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .user(let _data): - return ("user", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("username", ConstructorParameterDescription(_data.username)), ("phone", ConstructorParameterDescription(_data.phone)), ("photo", ConstructorParameterDescription(_data.photo)), ("status", ConstructorParameterDescription(_data.status)), ("botInfoVersion", ConstructorParameterDescription(_data.botInfoVersion)), ("restrictionReason", ConstructorParameterDescription(_data.restrictionReason)), ("botInlinePlaceholder", ConstructorParameterDescription(_data.botInlinePlaceholder)), ("langCode", ConstructorParameterDescription(_data.langCode)), ("emojiStatus", ConstructorParameterDescription(_data.emojiStatus)), ("usernames", ConstructorParameterDescription(_data.usernames)), ("storiesMaxId", ConstructorParameterDescription(_data.storiesMaxId)), ("color", ConstructorParameterDescription(_data.color)), ("profileColor", ConstructorParameterDescription(_data.profileColor)), ("botActiveUsers", ConstructorParameterDescription(_data.botActiveUsers)), ("botVerificationIcon", ConstructorParameterDescription(_data.botVerificationIcon)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars))]) - case .userEmpty(let _data): - return ("userEmpty", [("id", ConstructorParameterDescription(_data.id))]) - } - } - - public static func parse_user(_ reader: BufferReader) -> User? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt64() - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - var _6: String? - if Int(_1!) & Int(1 << 2) != 0 { - _6 = parseString(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 3) != 0 { - _7 = parseString(reader) - } - var _8: String? - if Int(_1!) & Int(1 << 4) != 0 { - _8 = parseString(reader) - } - var _9: Api.UserProfilePhoto? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.UserProfilePhoto - } - } - var _10: Api.UserStatus? - if Int(_1!) & Int(1 << 6) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.UserStatus - } - } - var _11: Int32? - if Int(_1!) & Int(1 << 14) != 0 { - _11 = reader.readInt32() - } - var _12: [Api.RestrictionReason]? - if Int(_1!) & Int(1 << 18) != 0 { - if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) - } - } - var _13: String? - if Int(_1!) & Int(1 << 19) != 0 { - _13 = parseString(reader) - } - var _14: String? - if Int(_1!) & Int(1 << 22) != 0 { - _14 = parseString(reader) - } - var _15: Api.EmojiStatus? - if Int(_1!) & Int(1 << 30) != 0 { - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.EmojiStatus - } - } - var _16: [Api.Username]? - if Int(_2!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) - } - } - var _17: Api.RecentStory? - if Int(_2!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.RecentStory - } - } - var _18: Api.PeerColor? - if Int(_2!) & Int(1 << 8) != 0 { - if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.PeerColor - } - } - var _19: Api.PeerColor? - if Int(_2!) & Int(1 << 9) != 0 { - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.PeerColor - } - } - var _20: Int32? - if Int(_2!) & Int(1 << 12) != 0 { - _20 = reader.readInt32() - } - var _21: Int64? - if Int(_2!) & Int(1 << 14) != 0 { - _21 = reader.readInt64() - } - var _22: Int64? - if Int(_2!) & Int(1 << 15) != 0 { - _22 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 14) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 19) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 22) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 30) == 0) || _15 != nil - let _c16 = (Int(_2!) & Int(1 << 0) == 0) || _16 != nil - let _c17 = (Int(_2!) & Int(1 << 5) == 0) || _17 != nil - let _c18 = (Int(_2!) & Int(1 << 8) == 0) || _18 != nil - let _c19 = (Int(_2!) & Int(1 << 9) == 0) || _19 != nil - let _c20 = (Int(_2!) & Int(1 << 12) == 0) || _20 != nil - let _c21 = (Int(_2!) & Int(1 << 14) == 0) || _21 != nil - let _c22 = (Int(_2!) & Int(1 << 15) == 0) || _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.User.user(Cons_user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19, botActiveUsers: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22)) - } - else { - return nil - } - } - public static func parse_userEmpty(_ reader: BufferReader) -> User? { + public static func parse_updateChatParticipantAdmin(_ reader: BufferReader) -> Update? { var _1: Int64? _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.User.userEmpty(Cons_userEmpty(id: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum UserFull: TypeConstructorDescription { - public class Cons_userFull: TypeConstructorDescription { - public var flags: Int32 - public var flags2: Int32 - public var id: Int64 - public var about: String? - public var settings: Api.PeerSettings - public var personalPhoto: Api.Photo? - public var profilePhoto: Api.Photo? - public var fallbackPhoto: Api.Photo? - public var notifySettings: Api.PeerNotifySettings - public var botInfo: Api.BotInfo? - public var pinnedMsgId: Int32? - public var commonChatsCount: Int32 - public var folderId: Int32? - public var ttlPeriod: Int32? - public var theme: Api.ChatTheme? - public var privateForwardName: String? - public var botGroupAdminRights: Api.ChatAdminRights? - public var botBroadcastAdminRights: Api.ChatAdminRights? - public var wallpaper: Api.WallPaper? - public var stories: Api.PeerStories? - public var businessWorkHours: Api.BusinessWorkHours? - public var businessLocation: Api.BusinessLocation? - public var businessGreetingMessage: Api.BusinessGreetingMessage? - public var businessAwayMessage: Api.BusinessAwayMessage? - public var businessIntro: Api.BusinessIntro? - public var birthday: Api.Birthday? - public var personalChannelId: Int64? - public var personalChannelMessage: Int32? - public var stargiftsCount: Int32? - public var starrefProgram: Api.StarRefProgram? - public var botVerification: Api.BotVerification? - public var sendPaidMessagesStars: Int64? - public var disallowedGifts: Api.DisallowedGiftsSettings? - public var starsRating: Api.StarsRating? - public var starsMyPendingRating: Api.StarsRating? - public var starsMyPendingRatingDate: Int32? - public var mainTab: Api.ProfileTab? - public var savedMusic: Api.Document? - public var note: Api.TextWithEntities? - public var botManagerId: Int64? - public init(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?, botManagerId: Int64?) { - self.flags = flags - self.flags2 = flags2 - self.id = id - self.about = about - self.settings = settings - self.personalPhoto = personalPhoto - self.profilePhoto = profilePhoto - self.fallbackPhoto = fallbackPhoto - self.notifySettings = notifySettings - self.botInfo = botInfo - self.pinnedMsgId = pinnedMsgId - self.commonChatsCount = commonChatsCount - self.folderId = folderId - self.ttlPeriod = ttlPeriod - self.theme = theme - self.privateForwardName = privateForwardName - self.botGroupAdminRights = botGroupAdminRights - self.botBroadcastAdminRights = botBroadcastAdminRights - self.wallpaper = wallpaper - self.stories = stories - self.businessWorkHours = businessWorkHours - self.businessLocation = businessLocation - self.businessGreetingMessage = businessGreetingMessage - self.businessAwayMessage = businessAwayMessage - self.businessIntro = businessIntro - self.birthday = birthday - self.personalChannelId = personalChannelId - self.personalChannelMessage = personalChannelMessage - self.stargiftsCount = stargiftsCount - self.starrefProgram = starrefProgram - self.botVerification = botVerification - self.sendPaidMessagesStars = sendPaidMessagesStars - self.disallowedGifts = disallowedGifts - self.starsRating = starsRating - self.starsMyPendingRating = starsMyPendingRating - self.starsMyPendingRatingDate = starsMyPendingRatingDate - self.mainTab = mainTab - self.savedMusic = savedMusic - self.note = note - self.botManagerId = botManagerId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userFull", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("about", ConstructorParameterDescription(self.about)), ("settings", ConstructorParameterDescription(self.settings)), ("personalPhoto", ConstructorParameterDescription(self.personalPhoto)), ("profilePhoto", ConstructorParameterDescription(self.profilePhoto)), ("fallbackPhoto", ConstructorParameterDescription(self.fallbackPhoto)), ("notifySettings", ConstructorParameterDescription(self.notifySettings)), ("botInfo", ConstructorParameterDescription(self.botInfo)), ("pinnedMsgId", ConstructorParameterDescription(self.pinnedMsgId)), ("commonChatsCount", ConstructorParameterDescription(self.commonChatsCount)), ("folderId", ConstructorParameterDescription(self.folderId)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("theme", ConstructorParameterDescription(self.theme)), ("privateForwardName", ConstructorParameterDescription(self.privateForwardName)), ("botGroupAdminRights", ConstructorParameterDescription(self.botGroupAdminRights)), ("botBroadcastAdminRights", ConstructorParameterDescription(self.botBroadcastAdminRights)), ("wallpaper", ConstructorParameterDescription(self.wallpaper)), ("stories", ConstructorParameterDescription(self.stories)), ("businessWorkHours", ConstructorParameterDescription(self.businessWorkHours)), ("businessLocation", ConstructorParameterDescription(self.businessLocation)), ("businessGreetingMessage", ConstructorParameterDescription(self.businessGreetingMessage)), ("businessAwayMessage", ConstructorParameterDescription(self.businessAwayMessage)), ("businessIntro", ConstructorParameterDescription(self.businessIntro)), ("birthday", ConstructorParameterDescription(self.birthday)), ("personalChannelId", ConstructorParameterDescription(self.personalChannelId)), ("personalChannelMessage", ConstructorParameterDescription(self.personalChannelMessage)), ("stargiftsCount", ConstructorParameterDescription(self.stargiftsCount)), ("starrefProgram", ConstructorParameterDescription(self.starrefProgram)), ("botVerification", ConstructorParameterDescription(self.botVerification)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars)), ("disallowedGifts", ConstructorParameterDescription(self.disallowedGifts)), ("starsRating", ConstructorParameterDescription(self.starsRating)), ("starsMyPendingRating", ConstructorParameterDescription(self.starsMyPendingRating)), ("starsMyPendingRatingDate", ConstructorParameterDescription(self.starsMyPendingRatingDate)), ("mainTab", ConstructorParameterDescription(self.mainTab)), ("savedMusic", ConstructorParameterDescription(self.savedMusic)), ("note", ConstructorParameterDescription(self.note)), ("botManagerId", ConstructorParameterDescription(self.botManagerId))]) - } - } - case userFull(Cons_userFull) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userFull(let _data): - if boxed { - buffer.appendInt32(114026053) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.flags2, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.about!, buffer: buffer, boxed: false) - } - _data.settings.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 21) != 0 { - _data.personalPhoto!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.profilePhoto!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 22) != 0 { - _data.fallbackPhoto!.serialize(buffer, true) - } - _data.notifySettings.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.botInfo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt32(_data.pinnedMsgId!, buffer: buffer, boxed: false) - } - serializeInt32(_data.commonChatsCount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 11) != 0 { - serializeInt32(_data.folderId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 14) != 0 { - serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 15) != 0 { - _data.theme!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 16) != 0 { - serializeString(_data.privateForwardName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 17) != 0 { - _data.botGroupAdminRights!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 18) != 0 { - _data.botBroadcastAdminRights!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 24) != 0 { - _data.wallpaper!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 25) != 0 { - _data.stories!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 0) != 0 { - _data.businessWorkHours!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 1) != 0 { - _data.businessLocation!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 2) != 0 { - _data.businessGreetingMessage!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 3) != 0 { - _data.businessAwayMessage!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 4) != 0 { - _data.businessIntro!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 5) != 0 { - _data.birthday!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 6) != 0 { - serializeInt64(_data.personalChannelId!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 6) != 0 { - serializeInt32(_data.personalChannelMessage!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 8) != 0 { - serializeInt32(_data.stargiftsCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 11) != 0 { - _data.starrefProgram!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 12) != 0 { - _data.botVerification!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 14) != 0 { - serializeInt64(_data.sendPaidMessagesStars!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 15) != 0 { - _data.disallowedGifts!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 17) != 0 { - _data.starsRating!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 18) != 0 { - _data.starsMyPendingRating!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 18) != 0 { - serializeInt32(_data.starsMyPendingRatingDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags2) & Int(1 << 20) != 0 { - _data.mainTab!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 21) != 0 { - _data.savedMusic!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 22) != 0 { - _data.note!.serialize(buffer, true) - } - if Int(_data.flags2) & Int(1 << 25) != 0 { - serializeInt64(_data.botManagerId!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .userFull(let _data): - return ("userFull", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("about", ConstructorParameterDescription(_data.about)), ("settings", ConstructorParameterDescription(_data.settings)), ("personalPhoto", ConstructorParameterDescription(_data.personalPhoto)), ("profilePhoto", ConstructorParameterDescription(_data.profilePhoto)), ("fallbackPhoto", ConstructorParameterDescription(_data.fallbackPhoto)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings)), ("botInfo", ConstructorParameterDescription(_data.botInfo)), ("pinnedMsgId", ConstructorParameterDescription(_data.pinnedMsgId)), ("commonChatsCount", ConstructorParameterDescription(_data.commonChatsCount)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("theme", ConstructorParameterDescription(_data.theme)), ("privateForwardName", ConstructorParameterDescription(_data.privateForwardName)), ("botGroupAdminRights", ConstructorParameterDescription(_data.botGroupAdminRights)), ("botBroadcastAdminRights", ConstructorParameterDescription(_data.botBroadcastAdminRights)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper)), ("stories", ConstructorParameterDescription(_data.stories)), ("businessWorkHours", ConstructorParameterDescription(_data.businessWorkHours)), ("businessLocation", ConstructorParameterDescription(_data.businessLocation)), ("businessGreetingMessage", ConstructorParameterDescription(_data.businessGreetingMessage)), ("businessAwayMessage", ConstructorParameterDescription(_data.businessAwayMessage)), ("businessIntro", ConstructorParameterDescription(_data.businessIntro)), ("birthday", ConstructorParameterDescription(_data.birthday)), ("personalChannelId", ConstructorParameterDescription(_data.personalChannelId)), ("personalChannelMessage", ConstructorParameterDescription(_data.personalChannelMessage)), ("stargiftsCount", ConstructorParameterDescription(_data.stargiftsCount)), ("starrefProgram", ConstructorParameterDescription(_data.starrefProgram)), ("botVerification", ConstructorParameterDescription(_data.botVerification)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars)), ("disallowedGifts", ConstructorParameterDescription(_data.disallowedGifts)), ("starsRating", ConstructorParameterDescription(_data.starsRating)), ("starsMyPendingRating", ConstructorParameterDescription(_data.starsMyPendingRating)), ("starsMyPendingRatingDate", ConstructorParameterDescription(_data.starsMyPendingRatingDate)), ("mainTab", ConstructorParameterDescription(_data.mainTab)), ("savedMusic", ConstructorParameterDescription(_data.savedMusic)), ("note", ConstructorParameterDescription(_data.note)), ("botManagerId", ConstructorParameterDescription(_data.botManagerId))]) - } - } - - public static func parse_userFull(_ reader: BufferReader) -> UserFull? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) - } - var _5: Api.PeerSettings? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - var _6: Api.Photo? - if Int(_1!) & Int(1 << 21) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _7: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _8: Api.Photo? - if Int(_1!) & Int(1 << 22) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _9: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - var _10: Api.BotInfo? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.BotInfo - } - } - var _11: Int32? - if Int(_1!) & Int(1 << 6) != 0 { - _11 = reader.readInt32() - } - var _12: Int32? - _12 = reader.readInt32() - var _13: Int32? - if Int(_1!) & Int(1 << 11) != 0 { - _13 = reader.readInt32() - } - var _14: Int32? - if Int(_1!) & Int(1 << 14) != 0 { - _14 = reader.readInt32() - } - var _15: Api.ChatTheme? - if Int(_1!) & Int(1 << 15) != 0 { - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.ChatTheme - } - } - var _16: String? - if Int(_1!) & Int(1 << 16) != 0 { - _16 = parseString(reader) - } - var _17: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 17) != 0 { - if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - var _18: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 18) != 0 { - if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } - } - var _19: Api.WallPaper? - if Int(_1!) & Int(1 << 24) != 0 { - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.WallPaper - } - } - var _20: Api.PeerStories? - if Int(_1!) & Int(1 << 25) != 0 { - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.PeerStories - } - } - var _21: Api.BusinessWorkHours? - if Int(_2!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.BusinessWorkHours - } - } - var _22: Api.BusinessLocation? - if Int(_2!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _22 = Api.parse(reader, signature: signature) as? Api.BusinessLocation - } - } - var _23: Api.BusinessGreetingMessage? - if Int(_2!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _23 = Api.parse(reader, signature: signature) as? Api.BusinessGreetingMessage - } - } - var _24: Api.BusinessAwayMessage? - if Int(_2!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _24 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage - } - } - var _25: Api.BusinessIntro? - if Int(_2!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _25 = Api.parse(reader, signature: signature) as? Api.BusinessIntro - } - } - var _26: Api.Birthday? - if Int(_2!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _26 = Api.parse(reader, signature: signature) as? Api.Birthday - } - } - var _27: Int64? - if Int(_2!) & Int(1 << 6) != 0 { - _27 = reader.readInt64() - } - var _28: Int32? - if Int(_2!) & Int(1 << 6) != 0 { - _28 = reader.readInt32() - } - var _29: Int32? - if Int(_2!) & Int(1 << 8) != 0 { - _29 = reader.readInt32() - } - var _30: Api.StarRefProgram? - if Int(_2!) & Int(1 << 11) != 0 { - if let signature = reader.readInt32() { - _30 = Api.parse(reader, signature: signature) as? Api.StarRefProgram - } - } - var _31: Api.BotVerification? - if Int(_2!) & Int(1 << 12) != 0 { - if let signature = reader.readInt32() { - _31 = Api.parse(reader, signature: signature) as? Api.BotVerification - } - } - var _32: Int64? - if Int(_2!) & Int(1 << 14) != 0 { - _32 = reader.readInt64() - } - var _33: Api.DisallowedGiftsSettings? - if Int(_2!) & Int(1 << 15) != 0 { - if let signature = reader.readInt32() { - _33 = Api.parse(reader, signature: signature) as? Api.DisallowedGiftsSettings - } - } - var _34: Api.StarsRating? - if Int(_2!) & Int(1 << 17) != 0 { - if let signature = reader.readInt32() { - _34 = Api.parse(reader, signature: signature) as? Api.StarsRating - } - } - var _35: Api.StarsRating? - if Int(_2!) & Int(1 << 18) != 0 { - if let signature = reader.readInt32() { - _35 = Api.parse(reader, signature: signature) as? Api.StarsRating - } - } - var _36: Int32? - if Int(_2!) & Int(1 << 18) != 0 { - _36 = reader.readInt32() - } - var _37: Api.ProfileTab? - if Int(_2!) & Int(1 << 20) != 0 { - if let signature = reader.readInt32() { - _37 = Api.parse(reader, signature: signature) as? Api.ProfileTab - } - } - var _38: Api.Document? - if Int(_2!) & Int(1 << 21) != 0 { - if let signature = reader.readInt32() { - _38 = Api.parse(reader, signature: signature) as? Api.Document - } - } - var _39: Api.TextWithEntities? - if Int(_2!) & Int(1 << 22) != 0 { - if let signature = reader.readInt32() { - _39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - } - var _40: Int64? - if Int(_2!) & Int(1 << 25) != 0 { - _40 = reader.readInt64() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 21) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 22) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = (Int(_1!) & Int(1 << 11) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 14) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 15) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 16) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 17) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 18) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 24) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 25) == 0) || _20 != nil - let _c21 = (Int(_2!) & Int(1 << 0) == 0) || _21 != nil - let _c22 = (Int(_2!) & Int(1 << 1) == 0) || _22 != nil - let _c23 = (Int(_2!) & Int(1 << 2) == 0) || _23 != nil - let _c24 = (Int(_2!) & Int(1 << 3) == 0) || _24 != nil - let _c25 = (Int(_2!) & Int(1 << 4) == 0) || _25 != nil - let _c26 = (Int(_2!) & Int(1 << 5) == 0) || _26 != nil - let _c27 = (Int(_2!) & Int(1 << 6) == 0) || _27 != nil - let _c28 = (Int(_2!) & Int(1 << 6) == 0) || _28 != nil - let _c29 = (Int(_2!) & Int(1 << 8) == 0) || _29 != nil - let _c30 = (Int(_2!) & Int(1 << 11) == 0) || _30 != nil - let _c31 = (Int(_2!) & Int(1 << 12) == 0) || _31 != nil - let _c32 = (Int(_2!) & Int(1 << 14) == 0) || _32 != nil - let _c33 = (Int(_2!) & Int(1 << 15) == 0) || _33 != nil - let _c34 = (Int(_2!) & Int(1 << 17) == 0) || _34 != nil - let _c35 = (Int(_2!) & Int(1 << 18) == 0) || _35 != nil - let _c36 = (Int(_2!) & Int(1 << 18) == 0) || _36 != nil - let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil - let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil - let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil - let _c40 = (Int(_2!) & Int(1 << 25) == 0) || _40 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 { - return Api.UserFull.userFull(Cons_userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39, botManagerId: _40)) - } - else { - return nil - } - } - } -} -public extension Api { - enum UserProfilePhoto: TypeConstructorDescription { - public class Cons_userProfilePhoto: TypeConstructorDescription { - public var flags: Int32 - public var photoId: Int64 - public var strippedThumb: Buffer? - public var dcId: Int32 - public init(flags: Int32, photoId: Int64, strippedThumb: Buffer?, dcId: Int32) { - self.flags = flags - self.photoId = photoId - self.strippedThumb = strippedThumb - self.dcId = dcId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userProfilePhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photoId", ConstructorParameterDescription(self.photoId)), ("strippedThumb", ConstructorParameterDescription(self.strippedThumb)), ("dcId", ConstructorParameterDescription(self.dcId))]) - } - } - case userProfilePhoto(Cons_userProfilePhoto) - case userProfilePhotoEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userProfilePhoto(let _data): - if boxed { - buffer.appendInt32(-2100168954) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.photoId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeBytes(_data.strippedThumb!, buffer: buffer, boxed: false) - } - serializeInt32(_data.dcId, buffer: buffer, boxed: false) - break - case .userProfilePhotoEmpty: - if boxed { - buffer.appendInt32(1326562017) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .userProfilePhoto(let _data): - return ("userProfilePhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("strippedThumb", ConstructorParameterDescription(_data.strippedThumb)), ("dcId", ConstructorParameterDescription(_data.dcId))]) - case .userProfilePhotoEmpty: - return ("userProfilePhotoEmpty", []) - } - } - - public static func parse_userProfilePhoto(_ reader: BufferReader) -> UserProfilePhoto? { - var _1: Int32? - _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: Buffer? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = parseBytes(reader) + var _3: Api.Bool? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool } var _4: Int32? _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.UserProfilePhoto.userProfilePhoto(Cons_userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!)) + return Api.Update.updateChatParticipantAdmin(Cons_updateChatParticipantAdmin(chatId: _1!, userId: _2!, isAdmin: _3!, version: _4!)) } else { return nil } } - public static func parse_userProfilePhotoEmpty(_ reader: BufferReader) -> UserProfilePhoto? { - return Api.UserProfilePhoto.userProfilePhotoEmpty - } - } -} -public extension Api { - enum UserStatus: TypeConstructorDescription { - public class Cons_userStatusLastMonth: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userStatusLastMonth", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_userStatusLastWeek: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userStatusLastWeek", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_userStatusOffline: TypeConstructorDescription { - public var wasOnline: Int32 - public init(wasOnline: Int32) { - self.wasOnline = wasOnline - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userStatusOffline", [("wasOnline", ConstructorParameterDescription(self.wasOnline))]) - } - } - public class Cons_userStatusOnline: TypeConstructorDescription { - public var expires: Int32 - public init(expires: Int32) { - self.expires = expires - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userStatusOnline", [("expires", ConstructorParameterDescription(self.expires))]) - } - } - public class Cons_userStatusRecently: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userStatusRecently", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - case userStatusEmpty - case userStatusLastMonth(Cons_userStatusLastMonth) - case userStatusLastWeek(Cons_userStatusLastWeek) - case userStatusOffline(Cons_userStatusOffline) - case userStatusOnline(Cons_userStatusOnline) - case userStatusRecently(Cons_userStatusRecently) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userStatusEmpty: - if boxed { - buffer.appendInt32(164646985) - } - break - case .userStatusLastMonth(let _data): - if boxed { - buffer.appendInt32(1703516023) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .userStatusLastWeek(let _data): - if boxed { - buffer.appendInt32(1410997530) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .userStatusOffline(let _data): - if boxed { - buffer.appendInt32(9203775) - } - serializeInt32(_data.wasOnline, buffer: buffer, boxed: false) - break - case .userStatusOnline(let _data): - if boxed { - buffer.appendInt32(-306628279) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - break - case .userStatusRecently(let _data): - if boxed { - buffer.appendInt32(2065268168) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .userStatusEmpty: - return ("userStatusEmpty", []) - case .userStatusLastMonth(let _data): - return ("userStatusLastMonth", [("flags", ConstructorParameterDescription(_data.flags))]) - case .userStatusLastWeek(let _data): - return ("userStatusLastWeek", [("flags", ConstructorParameterDescription(_data.flags))]) - case .userStatusOffline(let _data): - return ("userStatusOffline", [("wasOnline", ConstructorParameterDescription(_data.wasOnline))]) - case .userStatusOnline(let _data): - return ("userStatusOnline", [("expires", ConstructorParameterDescription(_data.expires))]) - case .userStatusRecently(let _data): - return ("userStatusRecently", [("flags", ConstructorParameterDescription(_data.flags))]) - } - } - - public static func parse_userStatusEmpty(_ reader: BufferReader) -> UserStatus? { - return Api.UserStatus.userStatusEmpty - } - public static func parse_userStatusLastMonth(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastMonth(Cons_userStatusLastMonth(flags: _1!)) - } - else { - return nil - } - } - public static func parse_userStatusLastWeek(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusLastWeek(Cons_userStatusLastWeek(flags: _1!)) - } - else { - return nil - } - } - public static func parse_userStatusOffline(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOffline(Cons_userStatusOffline(wasOnline: _1!)) - } - else { - return nil - } - } - public static func parse_userStatusOnline(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusOnline(Cons_userStatusOnline(expires: _1!)) - } - else { - return nil - } - } - public static func parse_userStatusRecently(_ reader: BufferReader) -> UserStatus? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.UserStatus.userStatusRecently(Cons_userStatusRecently(flags: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum Username: TypeConstructorDescription { - public class Cons_username: TypeConstructorDescription { - public var flags: Int32 - public var username: String - public init(flags: Int32, username: String) { - self.flags = flags - self.username = username - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("username", [("flags", ConstructorParameterDescription(self.flags)), ("username", ConstructorParameterDescription(self.username))]) - } - } - case username(Cons_username) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .username(let _data): - if boxed { - buffer.appendInt32(-1274595769) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.username, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .username(let _data): - return ("username", [("flags", ConstructorParameterDescription(_data.flags)), ("username", ConstructorParameterDescription(_data.username))]) - } - } - - public static func parse_username(_ reader: BufferReader) -> Username? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + public static func parse_updateChatParticipantDelete(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.Username.username(Cons_username(flags: _1!, username: _2!)) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatParticipantDelete(Cons_updateChatParticipantDelete(chatId: _1!, userId: _2!, version: _3!)) } else { return nil } } - } -} -public extension Api { - enum VideoSize: TypeConstructorDescription { - public class Cons_videoSize: TypeConstructorDescription { - public var flags: Int32 - public var type: String - public var w: Int32 - public var h: Int32 - public var size: Int32 - public var videoStartTs: Double? - public init(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?) { - self.flags = flags - self.type = type - self.w = w - self.h = h - self.size = size - self.videoStartTs = videoStartTs + public static func parse_updateChatParticipantRank(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateChatParticipantRank(Cons_updateChatParticipantRank(chatId: _1!, userId: _2!, rank: _3!, version: _4!)) } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("videoSize", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("size", ConstructorParameterDescription(self.size)), ("videoStartTs", ConstructorParameterDescription(self.videoStartTs))]) + else { + return nil } } - public class Cons_videoSizeEmojiMarkup: TypeConstructorDescription { - public var emojiId: Int64 - public var backgroundColors: [Int32] - public init(emojiId: Int64, backgroundColors: [Int32]) { - self.emojiId = emojiId - self.backgroundColors = backgroundColors + public static func parse_updateChatParticipants(_ reader: BufferReader) -> Update? { + var _1: Api.ChatParticipants? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChatParticipants } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("videoSizeEmojiMarkup", [("emojiId", ConstructorParameterDescription(self.emojiId)), ("backgroundColors", ConstructorParameterDescription(self.backgroundColors))]) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateChatParticipants(Cons_updateChatParticipants(participants: _1!)) + } + else { + return nil } } - public class Cons_videoSizeStickerMarkup: TypeConstructorDescription { - public var stickerset: Api.InputStickerSet - public var stickerId: Int64 - public var backgroundColors: [Int32] - public init(stickerset: Api.InputStickerSet, stickerId: Int64, backgroundColors: [Int32]) { - self.stickerset = stickerset - self.stickerId = stickerId - self.backgroundColors = backgroundColors + public static func parse_updateChatUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("videoSizeStickerMarkup", [("stickerset", ConstructorParameterDescription(self.stickerset)), ("stickerId", ConstructorParameterDescription(self.stickerId)), ("backgroundColors", ConstructorParameterDescription(self.backgroundColors))]) + var _3: Api.SendMessageAction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateChatUserTyping(Cons_updateChatUserTyping(chatId: _1!, fromId: _2!, action: _3!)) + } + else { + return nil } } - case videoSize(Cons_videoSize) - case videoSizeEmojiMarkup(Cons_videoSizeEmojiMarkup) - case videoSizeStickerMarkup(Cons_videoSizeStickerMarkup) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .videoSize(let _data): - if boxed { - buffer.appendInt32(-567037804) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.type, buffer: buffer, boxed: false) - serializeInt32(_data.w, buffer: buffer, boxed: false) - serializeInt32(_data.h, buffer: buffer, boxed: false) - serializeInt32(_data.size, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeDouble(_data.videoStartTs!, buffer: buffer, boxed: false) - } - break - case .videoSizeEmojiMarkup(let _data): - if boxed { - buffer.appendInt32(-128171716) - } - serializeInt64(_data.emojiId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.backgroundColors.count)) - for item in _data.backgroundColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .videoSizeStickerMarkup(let _data): - if boxed { - buffer.appendInt32(228623102) - } - _data.stickerset.serialize(buffer, true) - serializeInt64(_data.stickerId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.backgroundColors.count)) - for item in _data.backgroundColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break + public static func parse_updateConfig(_ reader: BufferReader) -> Update? { + return Api.Update.updateConfig + } + public static func parse_updateContactsReset(_ reader: BufferReader) -> Update? { + return Api.Update.updateContactsReset + } + public static func parse_updateDcOptions(_ reader: BufferReader) -> Update? { + var _1: [Api.DcOption]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DcOption.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDcOptions(Cons_updateDcOptions(dcOptions: _1!)) + } + else { + return nil } } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .videoSize(let _data): - return ("videoSize", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("size", ConstructorParameterDescription(_data.size)), ("videoStartTs", ConstructorParameterDescription(_data.videoStartTs))]) - case .videoSizeEmojiMarkup(let _data): - return ("videoSizeEmojiMarkup", [("emojiId", ConstructorParameterDescription(_data.emojiId)), ("backgroundColors", ConstructorParameterDescription(_data.backgroundColors))]) - case .videoSizeStickerMarkup(let _data): - return ("videoSizeStickerMarkup", [("stickerset", ConstructorParameterDescription(_data.stickerset)), ("stickerId", ConstructorParameterDescription(_data.stickerId)), ("backgroundColors", ConstructorParameterDescription(_data.backgroundColors))]) + public static func parse_updateDeleteChannelMessages(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - } - - public static func parse_videoSize(_ reader: BufferReader) -> VideoSize? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) var _3: Int32? _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: Double? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readDouble() - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.VideoSize.videoSize(Cons_videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6)) + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateDeleteChannelMessages(Cons_updateDeleteChannelMessages(channelId: _1!, messages: _2!, pts: _3!, ptsCount: _4!)) } else { return nil } } - public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_updateDeleteGroupCallMessages(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } var _2: [Int32]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) @@ -1940,845 +5618,1941 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.VideoSize.videoSizeEmojiMarkup(Cons_videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!)) + return Api.Update.updateDeleteGroupCallMessages(Cons_updateDeleteGroupCallMessages(call: _1!, messages: _2!)) } else { return nil } } - public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? { - var _1: Api.InputStickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + public static func parse_updateDeleteMessages(_ reader: BufferReader) -> Update? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDeleteMessages(Cons_updateDeleteMessages(messages: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateDeleteQuickReply(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDeleteQuickReply(Cons_updateDeleteQuickReply(shortcutId: _1!)) + } + else { + return nil + } + } + public static func parse_updateDeleteQuickReplyMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateDeleteQuickReplyMessages(Cons_updateDeleteQuickReplyMessages(shortcutId: _1!, messages: _2!)) + } + else { + return nil + } + } + public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - var _2: Int64? - _2 = reader.readInt64() var _3: [Int32]? if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.VideoSize.videoSizeStickerMarkup(Cons_videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum WallPaper: TypeConstructorDescription { - public class Cons_wallPaper: TypeConstructorDescription { - public var id: Int64 - public var flags: Int32 - public var accessHash: Int64 - public var slug: String - public var document: Api.Document - public var settings: Api.WallPaperSettings? - public init(id: Int64, flags: Int32, accessHash: Int64, slug: String, document: Api.Document, settings: Api.WallPaperSettings?) { - self.id = id - self.flags = flags - self.accessHash = accessHash - self.slug = slug - self.document = document - self.settings = settings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("wallPaper", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("slug", ConstructorParameterDescription(self.slug)), ("document", ConstructorParameterDescription(self.document)), ("settings", ConstructorParameterDescription(self.settings))]) - } - } - public class Cons_wallPaperNoFile: TypeConstructorDescription { - public var id: Int64 - public var flags: Int32 - public var settings: Api.WallPaperSettings? - public init(id: Int64, flags: Int32, settings: Api.WallPaperSettings?) { - self.id = id - self.flags = flags - self.settings = settings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("wallPaperNoFile", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("settings", ConstructorParameterDescription(self.settings))]) - } - } - case wallPaper(Cons_wallPaper) - case wallPaperNoFile(Cons_wallPaperNoFile) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .wallPaper(let _data): - if boxed { - buffer.appendInt32(-1539849235) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeString(_data.slug, buffer: buffer, boxed: false) - _data.document.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.settings!.serialize(buffer, true) - } - break - case .wallPaperNoFile(let _data): - if boxed { - buffer.appendInt32(-528465642) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.settings!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .wallPaper(let _data): - return ("wallPaper", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("slug", ConstructorParameterDescription(_data.slug)), ("document", ConstructorParameterDescription(_data.document)), ("settings", ConstructorParameterDescription(_data.settings))]) - case .wallPaperNoFile(let _data): - return ("wallPaperNoFile", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("settings", ConstructorParameterDescription(_data.settings))]) - } - } - - public static func parse_wallPaper(_ reader: BufferReader) -> WallPaper? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Api.Document? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.Document - } - var _6: Api.WallPaperSettings? - if Int(_2!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + var _4: [Int32]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_2!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.WallPaper.wallPaper(Cons_wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6)) + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateDeleteScheduledMessages(Cons_updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4)) } else { return nil } } - public static func parse_wallPaperNoFile(_ reader: BufferReader) -> WallPaper? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.WallPaperSettings? - if Int(_2!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_2!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WallPaper.wallPaperNoFile(Cons_wallPaperNoFile(id: _1!, flags: _2!, settings: _3)) - } - else { - return nil - } - } - } -} -public extension Api { - enum WallPaperSettings: TypeConstructorDescription { - public class Cons_wallPaperSettings: TypeConstructorDescription { - public var flags: Int32 - public var backgroundColor: Int32? - public var secondBackgroundColor: Int32? - public var thirdBackgroundColor: Int32? - public var fourthBackgroundColor: Int32? - public var intensity: Int32? - public var rotation: Int32? - public var emoticon: String? - public init(flags: Int32, backgroundColor: Int32?, secondBackgroundColor: Int32?, thirdBackgroundColor: Int32?, fourthBackgroundColor: Int32?, intensity: Int32?, rotation: Int32?, emoticon: String?) { - self.flags = flags - self.backgroundColor = backgroundColor - self.secondBackgroundColor = secondBackgroundColor - self.thirdBackgroundColor = thirdBackgroundColor - self.fourthBackgroundColor = fourthBackgroundColor - self.intensity = intensity - self.rotation = rotation - self.emoticon = emoticon - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("wallPaperSettings", [("flags", ConstructorParameterDescription(self.flags)), ("backgroundColor", ConstructorParameterDescription(self.backgroundColor)), ("secondBackgroundColor", ConstructorParameterDescription(self.secondBackgroundColor)), ("thirdBackgroundColor", ConstructorParameterDescription(self.thirdBackgroundColor)), ("fourthBackgroundColor", ConstructorParameterDescription(self.fourthBackgroundColor)), ("intensity", ConstructorParameterDescription(self.intensity)), ("rotation", ConstructorParameterDescription(self.rotation)), ("emoticon", ConstructorParameterDescription(self.emoticon))]) - } - } - case wallPaperSettings(Cons_wallPaperSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .wallPaperSettings(let _data): - if boxed { - buffer.appendInt32(925826256) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.backgroundColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.secondBackgroundColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.thirdBackgroundColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt32(_data.fourthBackgroundColor!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.intensity!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.rotation!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeString(_data.emoticon!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .wallPaperSettings(let _data): - return ("wallPaperSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("backgroundColor", ConstructorParameterDescription(_data.backgroundColor)), ("secondBackgroundColor", ConstructorParameterDescription(_data.secondBackgroundColor)), ("thirdBackgroundColor", ConstructorParameterDescription(_data.thirdBackgroundColor)), ("fourthBackgroundColor", ConstructorParameterDescription(_data.fourthBackgroundColor)), ("intensity", ConstructorParameterDescription(_data.intensity)), ("rotation", ConstructorParameterDescription(_data.rotation)), ("emoticon", ConstructorParameterDescription(_data.emoticon))]) - } - } - - public static func parse_wallPaperSettings(_ reader: BufferReader) -> WallPaperSettings? { + public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 6) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _6 = reader.readInt32() - } - var _7: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _7 = reader.readInt32() - } - var _8: String? - if Int(_1!) & Int(1 << 7) != 0 { - _8 = parseString(reader) + _2 = reader.readInt32() + var _3: Api.DialogFilter? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.DialogFilter + } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 4) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.WallPaperSettings.wallPaperSettings(Cons_wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8)) + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogFilter(Cons_updateDialogFilter(flags: _1!, id: _2!, filter: _3)) } else { return nil } } - } -} -public extension Api { - enum WebAuthorization: TypeConstructorDescription { - public class Cons_webAuthorization: TypeConstructorDescription { - public var hash: Int64 - public var botId: Int64 - public var domain: String - public var browser: String - public var platform: String - public var dateCreated: Int32 - public var dateActive: Int32 - public var ip: String - public var region: String - public init(hash: Int64, botId: Int64, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) { - self.hash = hash - self.botId = botId - self.domain = domain - self.browser = browser - self.platform = platform - self.dateCreated = dateCreated - self.dateActive = dateActive - self.ip = ip - self.region = region + public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webAuthorization", [("hash", ConstructorParameterDescription(self.hash)), ("botId", ConstructorParameterDescription(self.botId)), ("domain", ConstructorParameterDescription(self.domain)), ("browser", ConstructorParameterDescription(self.browser)), ("platform", ConstructorParameterDescription(self.platform)), ("dateCreated", ConstructorParameterDescription(self.dateCreated)), ("dateActive", ConstructorParameterDescription(self.dateActive)), ("ip", ConstructorParameterDescription(self.ip)), ("region", ConstructorParameterDescription(self.region))]) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateDialogFilterOrder(Cons_updateDialogFilterOrder(order: _1!)) + } + else { + return nil } } - case webAuthorization(Cons_webAuthorization) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webAuthorization(let _data): - if boxed { - buffer.appendInt32(-1493633966) + public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? { + return Api.Update.updateDialogFilters + } + public static func parse_updateDialogPinned(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = reader.readInt32() + } + var _3: Api.DialogPeer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogPinned(Cons_updateDialogPinned(flags: _1!, folderId: _2, peer: _3!)) + } + else { + return nil + } + } + public static func parse_updateDialogUnreadMark(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DialogPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeString(_data.domain, buffer: buffer, boxed: false) - serializeString(_data.browser, buffer: buffer, boxed: false) - serializeString(_data.platform, buffer: buffer, boxed: false) - serializeInt32(_data.dateCreated, buffer: buffer, boxed: false) - serializeInt32(_data.dateActive, buffer: buffer, boxed: false) - serializeString(_data.ip, buffer: buffer, boxed: false) - serializeString(_data.region, buffer: buffer, boxed: false) - break + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateDialogUnreadMark(Cons_updateDialogUnreadMark(flags: _1!, peer: _2!, savedPeerId: _3)) + } + else { + return nil } } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webAuthorization(let _data): - return ("webAuthorization", [("hash", ConstructorParameterDescription(_data.hash)), ("botId", ConstructorParameterDescription(_data.botId)), ("domain", ConstructorParameterDescription(_data.domain)), ("browser", ConstructorParameterDescription(_data.browser)), ("platform", ConstructorParameterDescription(_data.platform)), ("dateCreated", ConstructorParameterDescription(_data.dateCreated)), ("dateActive", ConstructorParameterDescription(_data.dateActive)), ("ip", ConstructorParameterDescription(_data.ip)), ("region", ConstructorParameterDescription(_data.region))]) + public static func parse_updateDraftMessage(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _5: Api.DraftMessage? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.DraftMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateDraftMessage(Cons_updateDraftMessage(flags: _1!, peer: _2!, topMsgId: _3, savedPeerId: _4, draft: _5!)) + } + else { + return nil } } - - public static func parse_webAuthorization(_ reader: BufferReader) -> WebAuthorization? { - var _1: Int64? - _1 = reader.readInt64() + public static func parse_updateEditChannelMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEditChannelMessage(Cons_updateEditChannelMessage(message: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateEditMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEditMessage(Cons_updateEditMessage(message: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateEmojiGameInfo(_ reader: BufferReader) -> Update? { + var _1: Api.messages.EmojiGameInfo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateEmojiGameInfo(Cons_updateEmojiGameInfo(info: _1!)) + } + else { + return nil + } + } + public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateEncryptedChatTyping(Cons_updateEncryptedChatTyping(chatId: _1!)) + } + else { + return nil + } + } + public static func parse_updateEncryptedMessagesRead(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateEncryptedMessagesRead(Cons_updateEncryptedMessagesRead(chatId: _1!, maxDate: _2!, date: _3!)) + } + else { + return nil + } + } + public static func parse_updateEncryption(_ reader: BufferReader) -> Update? { + var _1: Api.EncryptedChat? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateEncryption(Cons_updateEncryption(chat: _1!, date: _2!)) + } + else { + return nil + } + } + public static func parse_updateFavedStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateFavedStickers + } + public static func parse_updateFolderPeers(_ reader: BufferReader) -> Update? { + var _1: [Api.FolderPeer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FolderPeer.self) + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateFolderPeers(Cons_updateFolderPeers(folderPeers: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGeoLiveViewed(Cons_updateGeoLiveViewed(peer: _1!, msgId: _2!)) + } + else { + return nil + } + } + public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: Api.GroupCall? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCall(Cons_updateGroupCall(flags: _1!, peer: _2, call: _3!)) + } + else { + return nil + } + } + public static func parse_updateGroupCallChainBlocks(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Buffer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateGroupCallChainBlocks(Cons_updateGroupCallChainBlocks(call: _1!, subChainId: _2!, blocks: _3!, nextOffset: _4!)) + } + else { + return nil + } + } + public static func parse_updateGroupCallConnection(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCallConnection(Cons_updateGroupCallConnection(flags: _1!, params: _2!)) + } + else { + return nil + } + } + public static func parse_updateGroupCallEncryptedMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallEncryptedMessage(Cons_updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!)) + } + else { + return nil + } + } + public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.GroupCallMessage? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GroupCallMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCallMessage(Cons_updateGroupCallMessage(call: _1!, message: _2!)) + } + else { + return nil + } + } + public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallParticipants(Cons_updateGroupCallParticipants(call: _1!, participants: _2!, version: _3!)) + } + else { + return nil + } + } + public static func parse_updateInlineBotCallbackQuery(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - _7 = reader.readInt32() - var _8: String? - _8 = parseString(reader) - var _9: String? - _9 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Api.InputBotInlineMessageID? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } + var _5: Int64? + _5 = reader.readInt64() + var _6: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseBytes(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.WebAuthorization.webAuthorization(Cons_webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!)) + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateInlineBotCallbackQuery(Cons_updateInlineBotCallbackQuery(flags: _1!, queryId: _2!, userId: _3!, msgId: _4!, chatInstance: _5!, data: _6, gameShortName: _7)) } else { return nil } } - } -} -public extension Api { - enum WebDocument: TypeConstructorDescription { - public class Cons_webDocument: TypeConstructorDescription { - public var url: String - public var accessHash: Int64 - public var size: Int32 - public var mimeType: String - public var attributes: [Api.DocumentAttribute] - public init(url: String, accessHash: Int64, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) { - self.url = url - self.accessHash = accessHash - self.size = size - self.mimeType = mimeType - self.attributes = attributes + public static func parse_updateJoinChatWebViewDecision(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webDocument", [("url", ConstructorParameterDescription(self.url)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("attributes", ConstructorParameterDescription(self.attributes))]) + var _2: Int64? + _2 = reader.readInt64() + var _3: Api.JoinChatBotResult? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.JoinChatBotResult + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateJoinChatWebViewDecision(Cons_updateJoinChatWebViewDecision(peer: _1!, queryId: _2!, result: _3!)) + } + else { + return nil } } - public class Cons_webDocumentNoProxy: TypeConstructorDescription { - public var url: String - public var size: Int32 - public var mimeType: String - public var attributes: [Api.DocumentAttribute] - public init(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) { - self.url = url - self.size = size - self.mimeType = mimeType - self.attributes = attributes + public static func parse_updateLangPack(_ reader: BufferReader) -> Update? { + var _1: Api.LangPackDifference? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.LangPackDifference } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webDocumentNoProxy", [("url", ConstructorParameterDescription(self.url)), ("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("attributes", ConstructorParameterDescription(self.attributes))]) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateLangPack(Cons_updateLangPack(difference: _1!)) + } + else { + return nil } } - case webDocument(Cons_webDocument) - case webDocumentNoProxy(Cons_webDocumentNoProxy) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webDocument(let _data): - if boxed { - buffer.appendInt32(475467473) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt64(_data.accessHash, buffer: buffer, boxed: false) - serializeInt32(_data.size, buffer: buffer, boxed: false) - serializeString(_data.mimeType, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes.count)) - for item in _data.attributes { - item.serialize(buffer, true) - } - break - case .webDocumentNoProxy(let _data): - if boxed { - buffer.appendInt32(-104284986) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt32(_data.size, buffer: buffer, boxed: false) - serializeString(_data.mimeType, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes.count)) - for item in _data.attributes { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webDocument(let _data): - return ("webDocument", [("url", ConstructorParameterDescription(_data.url)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("attributes", ConstructorParameterDescription(_data.attributes))]) - case .webDocumentNoProxy(let _data): - return ("webDocumentNoProxy", [("url", ConstructorParameterDescription(_data.url)), ("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("attributes", ConstructorParameterDescription(_data.attributes))]) - } - } - - public static func parse_webDocument(_ reader: BufferReader) -> WebDocument? { + public static func parse_updateLangPackTooLong(_ reader: BufferReader) -> Update? { var _1: String? _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateLangPackTooLong(Cons_updateLangPackTooLong(langCode: _1!)) + } + else { + return nil + } + } + public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? { + return Api.Update.updateLoginToken + } + public static func parse_updateManagedBot(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() var _3: Int32? _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: [Api.DocumentAttribute]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateManagedBot(Cons_updateManagedBot(userId: _1!, botId: _2!, qts: _3!)) } + else { + return nil + } + } + public static func parse_updateMessageExtendedMedia(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.MessageExtendedMedia]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageExtendedMedia.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateMessageExtendedMedia(Cons_updateMessageExtendedMedia(peer: _1!, msgId: _2!, extendedMedia: _3!)) + } + else { + return nil + } + } + public static func parse_updateMessageID(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateMessageID(Cons_updateMessageID(id: _1!, randomId: _2!)) + } + else { + return nil + } + } + public static func parse_updateMessagePoll(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + var _5: Int64? + _5 = reader.readInt64() + var _6: Api.Poll? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Poll + } + } + var _7: Api.PollResults? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PollResults + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.Update.updateMessagePoll(Cons_updateMessagePoll(flags: _1!, peer: _2, msgId: _3, topMsgId: _4, pollId: _5!, poll: _6, results: _7!)) + } + else { + return nil + } + } + public static func parse_updateMessagePollVote(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Buffer]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) + } + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.WebDocument.webDocument(Cons_webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!)) + return Api.Update.updateMessagePollVote(Cons_updateMessagePollVote(pollId: _1!, peer: _2!, options: _3!, positions: _4!, qts: _5!)) } else { return nil } } - public static func parse_webDocumentNoProxy(_ reader: BufferReader) -> WebDocument? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: [Api.DocumentAttribute]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + public static func parse_updateMessageReactions(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt32() + } + var _5: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _6: Api.MessageReactions? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.MessageReactions } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebDocument.webDocumentNoProxy(Cons_webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!)) + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateMessageReactions(Cons_updateMessageReactions(flags: _1!, peer: _2!, msgId: _3!, topMsgId: _4, savedPeerId: _5, reactions: _6!)) } else { return nil } } - } -} -public extension Api { - enum WebPage: TypeConstructorDescription { - public class Cons_webPage: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var url: String - public var displayUrl: String - public var hash: Int32 - public var type: String? - public var siteName: String? - public var title: String? - public var description: String? - public var photo: Api.Photo? - public var embedUrl: String? - public var embedType: String? - public var embedWidth: Int32? - public var embedHeight: Int32? - public var duration: Int32? - public var author: String? - public var document: Api.Document? - public var cachedPage: Api.Page? - public var attributes: [Api.WebPageAttribute]? - public init(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) { - self.flags = flags - self.id = id - self.url = url - self.displayUrl = displayUrl - self.hash = hash - self.type = type - self.siteName = siteName - self.title = title - self.description = description - self.photo = photo - self.embedUrl = embedUrl - self.embedType = embedType - self.embedWidth = embedWidth - self.embedHeight = embedHeight - self.duration = duration - self.author = author - self.document = document - self.cachedPage = cachedPage - self.attributes = attributes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url)), ("displayUrl", ConstructorParameterDescription(self.displayUrl)), ("hash", ConstructorParameterDescription(self.hash)), ("type", ConstructorParameterDescription(self.type)), ("siteName", ConstructorParameterDescription(self.siteName)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("embedUrl", ConstructorParameterDescription(self.embedUrl)), ("embedType", ConstructorParameterDescription(self.embedType)), ("embedWidth", ConstructorParameterDescription(self.embedWidth)), ("embedHeight", ConstructorParameterDescription(self.embedHeight)), ("duration", ConstructorParameterDescription(self.duration)), ("author", ConstructorParameterDescription(self.author)), ("document", ConstructorParameterDescription(self.document)), ("cachedPage", ConstructorParameterDescription(self.cachedPage)), ("attributes", ConstructorParameterDescription(self.attributes))]) - } - } - public class Cons_webPageEmpty: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var url: String? - public init(flags: Int32, id: Int64, url: String?) { - self.flags = flags - self.id = id - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url))]) - } - } - public class Cons_webPageNotModified: TypeConstructorDescription { - public var flags: Int32 - public var cachedPageViews: Int32? - public init(flags: Int32, cachedPageViews: Int32?) { - self.flags = flags - self.cachedPageViews = cachedPageViews - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("cachedPageViews", ConstructorParameterDescription(self.cachedPageViews))]) - } - } - public class Cons_webPagePending: TypeConstructorDescription { - public var flags: Int32 - public var id: Int64 - public var url: String? - public var date: Int32 - public init(flags: Int32, id: Int64, url: String?, date: Int32) { - self.flags = flags - self.id = id - self.url = url - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPagePending", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case webPage(Cons_webPage) - case webPageEmpty(Cons_webPageEmpty) - case webPageNotModified(Cons_webPageNotModified) - case webPagePending(Cons_webPagePending) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webPage(let _data): - if boxed { - buffer.appendInt32(-392411726) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - serializeString(_data.displayUrl, buffer: buffer, boxed: false) - serializeInt32(_data.hash, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.type!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.siteName!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.description!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.photo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.embedUrl!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.embedType!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt32(_data.embedWidth!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeInt32(_data.embedHeight!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 7) != 0 { - serializeInt32(_data.duration!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 8) != 0 { - serializeString(_data.author!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 9) != 0 { - _data.document!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 10) != 0 { - _data.cachedPage!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 12) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes!.count)) - for item in _data.attributes! { - item.serialize(buffer, true) - } - } - break - case .webPageEmpty(let _data): - if boxed { - buffer.appendInt32(555358088) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - break - case .webPageNotModified(let _data): - if boxed { - buffer.appendInt32(1930545681) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.cachedPageViews!, buffer: buffer, boxed: false) - } - break - case .webPagePending(let _data): - if boxed { - buffer.appendInt32(-1328464313) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webPage(let _data): - return ("webPage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url)), ("displayUrl", ConstructorParameterDescription(_data.displayUrl)), ("hash", ConstructorParameterDescription(_data.hash)), ("type", ConstructorParameterDescription(_data.type)), ("siteName", ConstructorParameterDescription(_data.siteName)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("embedUrl", ConstructorParameterDescription(_data.embedUrl)), ("embedType", ConstructorParameterDescription(_data.embedType)), ("embedWidth", ConstructorParameterDescription(_data.embedWidth)), ("embedHeight", ConstructorParameterDescription(_data.embedHeight)), ("duration", ConstructorParameterDescription(_data.duration)), ("author", ConstructorParameterDescription(_data.author)), ("document", ConstructorParameterDescription(_data.document)), ("cachedPage", ConstructorParameterDescription(_data.cachedPage)), ("attributes", ConstructorParameterDescription(_data.attributes))]) - case .webPageEmpty(let _data): - return ("webPageEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url))]) - case .webPageNotModified(let _data): - return ("webPageNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("cachedPageViews", ConstructorParameterDescription(_data.cachedPageViews))]) - case .webPagePending(let _data): - return ("webPagePending", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url)), ("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + public static func parse_updateMonoForumNoPaidException(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateMonoForumNoPaidException(Cons_updateMonoForumNoPaidException(flags: _1!, channelId: _2!, savedPeerId: _3!)) + } + else { + return nil + } + } + public static func parse_updateMoveStickerSetToTop(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateMoveStickerSetToTop(Cons_updateMoveStickerSetToTop(flags: _1!, stickerset: _2!)) + } + else { + return nil + } + } + public static func parse_updateNewAuthorization(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } var _4: String? - _4 = parseString(reader) + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateNewAuthorization(Cons_updateNewAuthorization(flags: _1!, hash: _2!, date: _3, device: _4, location: _5)) + } + else { + return nil + } + } + public static func parse_updateNewBotConnection(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateNewBotConnection(Cons_updateNewBotConnection(flags: _1!, botId: _2!, date: _3, device: _4, location: _5)) + } + else { + return nil + } + } + public static func parse_updateNewChannelMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewChannelMessage(Cons_updateNewChannelMessage(message: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateNewEncryptedMessage(_ reader: BufferReader) -> Update? { + var _1: Api.EncryptedMessage? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.EncryptedMessage + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateNewEncryptedMessage(Cons_updateNewEncryptedMessage(message: _1!, qts: _2!)) + } + else { + return nil + } + } + public static func parse_updateNewMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewMessage(Cons_updateNewMessage(message: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateNewQuickReply(_ reader: BufferReader) -> Update? { + var _1: Api.QuickReply? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.QuickReply + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewQuickReply(Cons_updateNewQuickReply(quickReply: _1!)) + } + else { + return nil + } + } + public static func parse_updateNewScheduledMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewScheduledMessage(Cons_updateNewScheduledMessage(message: _1!)) + } + else { + return nil + } + } + public static func parse_updateNewStickerSet(_ reader: BufferReader) -> Update? { + var _1: Api.messages.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateNewStickerSet(Cons_updateNewStickerSet(stickerset: _1!)) + } + else { + return nil + } + } + public static func parse_updateNewStoryReaction(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateNewStoryReaction(Cons_updateNewStoryReaction(storyId: _1!, peer: _2!, reaction: _3!)) + } + else { + return nil + } + } + public static func parse_updateNotifySettings(_ reader: BufferReader) -> Update? { + var _1: Api.NotifyPeer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.NotifyPeer + } + var _2: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateNotifySettings(Cons_updateNotifySettings(peer: _1!, notifySettings: _2!)) + } + else { + return nil + } + } + public static func parse_updatePaidReactionPrivacy(_ reader: BufferReader) -> Update? { + var _1: Api.PaidReactionPrivacy? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PaidReactionPrivacy + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updatePaidReactionPrivacy(Cons_updatePaidReactionPrivacy(private: _1!)) + } + else { + return nil + } + } + public static func parse_updatePeerBlocked(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePeerBlocked(Cons_updatePeerBlocked(flags: _1!, peerId: _2!)) + } + else { + return nil + } + } + public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePeerHistoryTTL(Cons_updatePeerHistoryTTL(flags: _1!, peer: _2!, ttlPeriod: _3)) + } + else { + return nil + } + } + public static func parse_updatePeerLocated(_ reader: BufferReader) -> Update? { + var _1: [Api.PeerLocated]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerLocated.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updatePeerLocated(Cons_updatePeerLocated(peers: _1!)) + } + else { + return nil + } + } + public static func parse_updatePeerSettings(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.PeerSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PeerSettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePeerSettings(Cons_updatePeerSettings(peer: _1!, settings: _2!)) + } + else { + return nil + } + } + public static func parse_updatePeerWallpaper(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.WallPaper? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePeerWallpaper(Cons_updatePeerWallpaper(flags: _1!, peer: _2!, wallpaper: _3)) + } + else { + return nil + } + } + public static func parse_updatePendingJoinRequests(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Int64]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePendingJoinRequests(Cons_updatePendingJoinRequests(peer: _1!, requestsPending: _2!, recentRequesters: _3!)) + } + else { + return nil + } + } + public static func parse_updatePhoneCall(_ reader: BufferReader) -> Update? { + var _1: Api.PhoneCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updatePhoneCall(Cons_updatePhoneCall(phoneCall: _1!)) + } + else { + return nil + } + } + public static func parse_updatePhoneCallSignalingData(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePhoneCallSignalingData(Cons_updatePhoneCallSignalingData(phoneCallId: _1!, data: _2!)) + } + else { + return nil + } + } + public static func parse_updatePinnedChannelMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _4: Int32? + _4 = reader.readInt32() var _5: Int32? _5 = reader.readInt32() - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) - } - var _8: String? - if Int(_1!) & Int(1 << 2) != 0 { - _8 = parseString(reader) - } - var _9: String? - if Int(_1!) & Int(1 << 3) != 0 { - _9 = parseString(reader) - } - var _10: Api.Photo? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.Photo - } - } - var _11: String? - if Int(_1!) & Int(1 << 5) != 0 { - _11 = parseString(reader) - } - var _12: String? - if Int(_1!) & Int(1 << 5) != 0 { - _12 = parseString(reader) - } - var _13: Int32? - if Int(_1!) & Int(1 << 6) != 0 { - _13 = reader.readInt32() - } - var _14: Int32? - if Int(_1!) & Int(1 << 6) != 0 { - _14 = reader.readInt32() - } - var _15: Int32? - if Int(_1!) & Int(1 << 7) != 0 { - _15 = reader.readInt32() - } - var _16: String? - if Int(_1!) & Int(1 << 8) != 0 { - _16 = parseString(reader) - } - var _17: Api.Document? - if Int(_1!) & Int(1 << 9) != 0 { - if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.Document - } - } - var _18: Api.Page? - if Int(_1!) & Int(1 << 10) != 0 { - if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.Page - } - } - var _19: [Api.WebPageAttribute]? - if Int(_1!) & Int(1 << 12) != 0 { - if let _ = reader.readInt32() { - _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) - } - } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 6) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 7) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 9) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 12) == 0) || _19 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { - return Api.WebPage.webPage(Cons_webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19)) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updatePinnedChannelMessages(Cons_updatePinnedChannelMessages(flags: _1!, channelId: _2!, messages: _3!, pts: _4!, ptsCount: _5!)) } else { return nil } } - public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPage.webPageEmpty(Cons_webPageEmpty(flags: _1!, id: _2!, url: _3)) - } - else { - return nil - } - } - public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { + public static func parse_updatePinnedDialogs(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _2 = reader.readInt32() } + var _3: [Api.DialogPeer]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + } + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebPage.webPageNotModified(Cons_webPageNotModified(flags: _1!, cachedPageViews: _2)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedDialogs(Cons_updatePinnedDialogs(flags: _1!, folderId: _2, order: _3)) } else { return nil } } - public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { + public static func parse_updatePinnedForumTopic(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopic(Cons_updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!)) + } + else { + return nil + } + } + public static func parse_updatePinnedForumTopics(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Int32]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopics(Cons_updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3)) + } + else { + return nil + } + } + public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updatePinnedMessages(Cons_updatePinnedMessages(flags: _1!, peer: _2!, messages: _3!, pts: _4!, ptsCount: _5!)) + } + else { + return nil + } + } + public static func parse_updatePinnedSavedDialogs(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogPeer]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.Update.updatePinnedSavedDialogs(Cons_updatePinnedSavedDialogs(flags: _1!, order: _2)) + } + else { + return nil + } + } + public static func parse_updatePrivacy(_ reader: BufferReader) -> Update? { + var _1: Api.PrivacyKey? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PrivacyKey + } + var _2: [Api.PrivacyRule]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updatePrivacy(Cons_updatePrivacy(key: _1!, rules: _2!)) + } + else { + return nil + } + } + public static func parse_updatePtsChanged(_ reader: BufferReader) -> Update? { + return Api.Update.updatePtsChanged + } + public static func parse_updateQuickReplies(_ reader: BufferReader) -> Update? { + var _1: [Api.QuickReply]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateQuickReplies(Cons_updateQuickReplies(quickReplies: _1!)) + } + else { + return nil + } + } + public static func parse_updateQuickReplyMessage(_ reader: BufferReader) -> Update? { + var _1: Api.Message? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Message + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateQuickReplyMessage(Cons_updateQuickReplyMessage(message: _1!)) + } + else { + return nil + } + } + public static func parse_updateReadChannelDiscussionInbox(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateReadChannelDiscussionInbox(Cons_updateReadChannelDiscussionInbox(flags: _1!, channelId: _2!, topMsgId: _3!, readMaxId: _4!, broadcastId: _5, broadcastPost: _6)) + } + else { + return nil + } + } + public static func parse_updateReadChannelDiscussionOutbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadChannelDiscussionOutbox(Cons_updateReadChannelDiscussionOutbox(channelId: _1!, topMsgId: _2!, readMaxId: _3!)) + } + else { + return nil + } + } + public static func parse_updateReadChannelInbox(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateReadChannelInbox(Cons_updateReadChannelInbox(flags: _1!, folderId: _2, channelId: _3!, maxId: _4!, stillUnreadCount: _5!, pts: _6!)) + } + else { + return nil + } + } + public static func parse_updateReadChannelOutbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateReadChannelOutbox(Cons_updateReadChannelOutbox(channelId: _1!, maxId: _2!)) + } + else { + return nil + } + } + public static func parse_updateReadFeaturedEmojiStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateReadFeaturedEmojiStickers + } + public static func parse_updateReadFeaturedStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateReadFeaturedStickers + } + public static func parse_updateReadHistoryInbox(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Api.Peer? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Update.updateReadHistoryInbox(Cons_updateReadHistoryInbox(flags: _1!, folderId: _2, peer: _3!, topMsgId: _4, maxId: _5!, stillUnreadCount: _6!, pts: _7!, ptsCount: _8!)) + } + else { + return nil + } + } + public static func parse_updateReadHistoryOutbox(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.WebPage.webPagePending(Cons_webPagePending(flags: _1!, id: _2!, url: _3, date: _4!)) + return Api.Update.updateReadHistoryOutbox(Cons_updateReadHistoryOutbox(peer: _1!, maxId: _2!, pts: _3!, ptsCount: _4!)) + } + else { + return nil + } + } + public static func parse_updateReadMessagesContents(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateReadMessagesContents(Cons_updateReadMessagesContents(flags: _1!, messages: _2!, pts: _3!, ptsCount: _4!, date: _5)) + } + else { + return nil + } + } + public static func parse_updateReadMonoForumInbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadMonoForumInbox(Cons_updateReadMonoForumInbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!)) + } + else { + return nil + } + } + public static func parse_updateReadMonoForumOutbox(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateReadMonoForumOutbox(Cons_updateReadMonoForumOutbox(channelId: _1!, savedPeerId: _2!, readMaxId: _3!)) + } + else { + return nil + } + } + public static func parse_updateReadStories(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateReadStories(Cons_updateReadStories(peer: _1!, maxId: _2!)) + } + else { + return nil + } + } + public static func parse_updateRecentEmojiStatuses(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentEmojiStatuses + } + public static func parse_updateRecentReactions(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentReactions + } + public static func parse_updateRecentStickers(_ reader: BufferReader) -> Update? { + return Api.Update.updateRecentStickers + } + public static func parse_updateSavedDialogPinned(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DialogPeer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DialogPeer + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateSavedDialogPinned(Cons_updateSavedDialogPinned(flags: _1!, peer: _2!)) + } + else { + return nil + } + } + public static func parse_updateSavedGifs(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedGifs + } + public static func parse_updateSavedReactionTags(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedReactionTags + } + public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? { + return Api.Update.updateSavedRingtones + } + public static func parse_updateSentPhoneCode(_ reader: BufferReader) -> Update? { + var _1: Api.auth.SentCode? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateSentPhoneCode(Cons_updateSentPhoneCode(sentCode: _1!)) + } + else { + return nil + } + } + public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Reaction? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Reaction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateSentStoryReaction(Cons_updateSentStoryReaction(peer: _1!, storyId: _2!, reaction: _3!)) + } + else { + return nil + } + } + public static func parse_updateServiceNotification(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = reader.readInt32() + } + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Api.MessageMedia? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + var _6: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Update.updateServiceNotification(Cons_updateServiceNotification(flags: _1!, inboxDate: _2, type: _3!, message: _4!, media: _5!, entities: _6!)) + } + else { + return nil + } + } + public static func parse_updateSmsJob(_ reader: BufferReader) -> Update? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateSmsJob(Cons_updateSmsJob(jobId: _1!)) + } + else { + return nil + } + } + public static func parse_updateStarGiftAuctionState(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.StarGiftAuctionState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStarGiftAuctionState(Cons_updateStarGiftAuctionState(giftId: _1!, state: _2!)) + } + else { + return nil + } + } + public static func parse_updateStarGiftAuctionUserState(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.StarGiftAuctionUserState? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStarGiftAuctionUserState(Cons_updateStarGiftAuctionUserState(giftId: _1!, userState: _2!)) + } + else { + return nil + } + } + public static func parse_updateStarGiftCraftFail(_ reader: BufferReader) -> Update? { + return Api.Update.updateStarGiftCraftFail + } + public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? { + var _1: Api.StarsAmount? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateStarsBalance(Cons_updateStarsBalance(balance: _1!)) + } + else { + return nil + } + } + public static func parse_updateStarsRevenueStatus(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.StarsRevenueStatus? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStarsRevenueStatus(Cons_updateStarsRevenueStatus(peer: _1!, status: _2!)) + } + else { + return nil + } + } + public static func parse_updateStickerSets(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateStickerSets(Cons_updateStickerSets(flags: _1!)) + } + else { + return nil + } + } + public static func parse_updateStickerSetsOrder(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int64]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStickerSetsOrder(Cons_updateStickerSetsOrder(flags: _1!, order: _2!)) + } + else { + return nil + } + } + public static func parse_updateStoriesStealthMode(_ reader: BufferReader) -> Update? { + var _1: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateStoriesStealthMode(Cons_updateStoriesStealthMode(stealthMode: _1!)) + } + else { + return nil + } + } + public static func parse_updateStory(_ reader: BufferReader) -> Update? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: Api.StoryItem? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StoryItem + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStory(Cons_updateStory(peer: _1!, story: _2!)) + } + else { + return nil + } + } + public static func parse_updateStoryID(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateStoryID(Cons_updateStoryID(id: _1!, randomId: _2!)) + } + else { + return nil + } + } + public static func parse_updateTheme(_ reader: BufferReader) -> Update? { + var _1: Api.Theme? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Theme + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateTheme(Cons_updateTheme(theme: _1!)) + } + else { + return nil + } + } + public static func parse_updateTranscribedAudio(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateTranscribedAudio(Cons_updateTranscribedAudio(flags: _1!, peer: _2!, msgId: _3!, transcriptionId: _4!, text: _5!)) + } + else { + return nil + } + } + public static func parse_updateUser(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateUser(Cons_updateUser(userId: _1!)) + } + else { + return nil + } + } + public static func parse_updateUserEmojiStatus(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.EmojiStatus? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.EmojiStatus + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateUserEmojiStatus(Cons_updateUserEmojiStatus(userId: _1!, emojiStatus: _2!)) + } + else { + return nil + } + } + public static func parse_updateUserName(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: [Api.Username]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateUserName(Cons_updateUserName(userId: _1!, firstName: _2!, lastName: _3!, usernames: _4!)) + } + else { + return nil + } + } + public static func parse_updateUserPhone(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateUserPhone(Cons_updateUserPhone(userId: _1!, phone: _2!)) + } + else { + return nil + } + } + public static func parse_updateUserStatus(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.UserStatus? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.UserStatus + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateUserStatus(Cons_updateUserStatus(userId: _1!, status: _2!)) + } + else { + return nil + } + } + public static func parse_updateUserTyping(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Api.SendMessageAction? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.SendMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.Update.updateUserTyping(Cons_updateUserTyping(flags: _1!, userId: _2!, topMsgId: _3, action: _4!)) + } + else { + return nil + } + } + public static func parse_updateWebBrowserException(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _3: Api.WebDomainException? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.WebDomainException + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateWebBrowserException(Cons_updateWebBrowserException(flags: _1!, openExternalBrowser: _2, exception: _3!)) + } + else { + return nil + } + } + public static func parse_updateWebBrowserSettings(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateWebBrowserSettings(Cons_updateWebBrowserSettings(flags: _1!)) + } + else { + return nil + } + } + public static func parse_updateWebPage(_ reader: BufferReader) -> Update? { + var _1: Api.WebPage? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.WebPage + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateWebPage(Cons_updateWebPage(webpage: _1!, pts: _2!, ptsCount: _3!)) + } + else { + return nil + } + } + public static func parse_updateWebViewResultSent(_ reader: BufferReader) -> Update? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateWebViewResultSent(Cons_updateWebViewResultSent(queryId: _1!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 36621d885d..e61edad873 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1,3 +1,211 @@ +public extension Api { + enum BusinessBotRecipients: TypeConstructorDescription { + public class Cons_businessBotRecipients: TypeConstructorDescription { + public var flags: Int32 + public var users: [Int64]? + public var excludeUsers: [Int64]? + public init(flags: Int32, users: [Int64]?, excludeUsers: [Int64]?) { + self.flags = flags + self.users = users + self.excludeUsers = excludeUsers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("businessBotRecipients", [("flags", ConstructorParameterDescription(self.flags)), ("users", ConstructorParameterDescription(self.users)), ("excludeUsers", ConstructorParameterDescription(self.excludeUsers))]) + } + } + case businessBotRecipients(Cons_businessBotRecipients) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessBotRecipients(let _data): + if boxed { + buffer.appendInt32(-1198722189) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users!.count)) + for item in _data.users! { + serializeInt64(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 6) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.excludeUsers!.count)) + for item in _data.excludeUsers! { + serializeInt64(item, buffer: buffer, boxed: false) + } + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .businessBotRecipients(let _data): + return ("businessBotRecipients", [("flags", ConstructorParameterDescription(_data.flags)), ("users", ConstructorParameterDescription(_data.users)), ("excludeUsers", ConstructorParameterDescription(_data.excludeUsers))]) + } + } + + public static func parse_businessBotRecipients(_ reader: BufferReader) -> BusinessBotRecipients? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Int64]? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + } + var _3: [Int64]? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.BusinessBotRecipients.businessBotRecipients(Cons_businessBotRecipients(flags: _1!, users: _2, excludeUsers: _3)) + } + else { + return nil + } + } + } +} +public extension Api { + enum BusinessBotRights: TypeConstructorDescription { + public class Cons_businessBotRights: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("businessBotRights", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + case businessBotRights(Cons_businessBotRights) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessBotRights(let _data): + if boxed { + buffer.appendInt32(-1604170505) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .businessBotRights(let _data): + return ("businessBotRights", [("flags", ConstructorParameterDescription(_data.flags))]) + } + } + + public static func parse_businessBotRights(_ reader: BufferReader) -> BusinessBotRights? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.BusinessBotRights.businessBotRights(Cons_businessBotRights(flags: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum BusinessChatLink: TypeConstructorDescription { + public class Cons_businessChatLink: TypeConstructorDescription { + public var flags: Int32 + public var link: String + public var message: String + public var entities: [Api.MessageEntity]? + public var title: String? + public var views: Int32 + public init(flags: Int32, link: String, message: String, entities: [Api.MessageEntity]?, title: String?, views: Int32) { + self.flags = flags + self.link = link + self.message = message + self.entities = entities + self.title = title + self.views = views + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("businessChatLink", [("flags", ConstructorParameterDescription(self.flags)), ("link", ConstructorParameterDescription(self.link)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("title", ConstructorParameterDescription(self.title)), ("views", ConstructorParameterDescription(self.views))]) + } + } + case businessChatLink(Cons_businessChatLink) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessChatLink(let _data): + if boxed { + buffer.appendInt32(-1263638929) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.link, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + serializeInt32(_data.views, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .businessChatLink(let _data): + return ("businessChatLink", [("flags", ConstructorParameterDescription(_data.flags)), ("link", ConstructorParameterDescription(_data.link)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("title", ConstructorParameterDescription(_data.title)), ("views", ConstructorParameterDescription(_data.views))]) + } + } + + public static func parse_businessChatLink(_ reader: BufferReader) -> BusinessChatLink? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.BusinessChatLink.businessChatLink(Cons_businessChatLink(flags: _1!, link: _2!, message: _3!, entities: _4, title: _5, views: _6!)) + } + else { + return nil + } + } + } +} public extension Api { enum BusinessGreetingMessage: TypeConstructorDescription { public class Cons_businessGreetingMessage: TypeConstructorDescription { @@ -106,7 +314,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Document } @@ -114,7 +322,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.BusinessIntro.businessIntro(Cons_businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4)) } @@ -167,7 +375,7 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.GeoPoint? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.GeoPoint } @@ -175,7 +383,7 @@ public extension Api { var _3: String? _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { return Api.BusinessLocation.businessLocation(Cons_businessLocation(flags: _1!, geoPoint: _2, address: _3!)) @@ -230,13 +438,13 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: [Int64]? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil if _c1 && _c2 { return Api.BusinessRecipients.businessRecipients(Cons_businessRecipients(flags: _1!, users: _2)) } @@ -2115,20 +2323,20 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.ForumTopic? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.ForumTopic } } var _3: Api.ForumTopic? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.ForumTopic } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.ChannelAdminLogEventAction.channelAdminLogEventActionPinTopic(Cons_channelAdminLogEventActionPinTopic(flags: _1!, prevTopic: _2, newTopic: _3)) } diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 321e789202..638cfb804c 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -1,1785 +1,249 @@ public extension Api { - indirect enum WebPageAttribute: TypeConstructorDescription { - public class Cons_webPageAttributeStarGiftAuction: TypeConstructorDescription { - public var gift: Api.StarGift - public var endDate: Int32 - public init(gift: Api.StarGift, endDate: Int32) { - self.gift = gift - self.endDate = endDate + indirect enum Updates: TypeConstructorDescription { + public class Cons_updateShort: TypeConstructorDescription { + public var update: Api.Update + public var date: Int32 + public init(update: Api.Update, date: Int32) { + self.update = update + self.date = date } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeStarGiftAuction", [("gift", ConstructorParameterDescription(self.gift)), ("endDate", ConstructorParameterDescription(self.endDate))]) + return ("updateShort", [("update", ConstructorParameterDescription(self.update)), ("date", ConstructorParameterDescription(self.date))]) } } - public class Cons_webPageAttributeStarGiftCollection: TypeConstructorDescription { - public var icons: [Api.Document] - public init(icons: [Api.Document]) { - self.icons = icons - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeStarGiftCollection", [("icons", ConstructorParameterDescription(self.icons))]) - } - } - public class Cons_webPageAttributeStickerSet: TypeConstructorDescription { + public class Cons_updateShortChatMessage: TypeConstructorDescription { public var flags: Int32 - public var stickers: [Api.Document] - public init(flags: Int32, stickers: [Api.Document]) { - self.flags = flags - self.stickers = stickers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeStickerSet", [("flags", ConstructorParameterDescription(self.flags)), ("stickers", ConstructorParameterDescription(self.stickers))]) - } - } - public class Cons_webPageAttributeStory: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer public var id: Int32 - public var story: Api.StoryItem? - public init(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) { + public var fromId: Int64 + public var chatId: Int64 + public var message: String + public var pts: Int32 + public var ptsCount: Int32 + public var date: Int32 + public var fwdFrom: Api.MessageFwdHeader? + public var viaBotId: Int64? + public var replyTo: Api.MessageReplyHeader? + public var entities: [Api.MessageEntity]? + public var ttlPeriod: Int32? + public init(flags: Int32, id: Int32, fromId: Int64, chatId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { self.flags = flags - self.peer = peer self.id = id - self.story = story + self.fromId = fromId + self.chatId = chatId + self.message = message + self.pts = pts + self.ptsCount = ptsCount + self.date = date + self.fwdFrom = fwdFrom + self.viaBotId = viaBotId + self.replyTo = replyTo + self.entities = entities + self.ttlPeriod = ttlPeriod } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeStory", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("id", ConstructorParameterDescription(self.id)), ("story", ConstructorParameterDescription(self.story))]) + return ("updateShortChatMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("fromId", ConstructorParameterDescription(self.fromId)), ("chatId", ConstructorParameterDescription(self.chatId)), ("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) } } - public class Cons_webPageAttributeTheme: TypeConstructorDescription { + public class Cons_updateShortMessage: TypeConstructorDescription { public var flags: Int32 - public var documents: [Api.Document]? - public var settings: Api.ThemeSettings? - public init(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) { + public var id: Int32 + public var userId: Int64 + public var message: String + public var pts: Int32 + public var ptsCount: Int32 + public var date: Int32 + public var fwdFrom: Api.MessageFwdHeader? + public var viaBotId: Int64? + public var replyTo: Api.MessageReplyHeader? + public var entities: [Api.MessageEntity]? + public var ttlPeriod: Int32? + public init(flags: Int32, id: Int32, userId: Int64, message: String, pts: Int32, ptsCount: Int32, date: Int32, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, replyTo: Api.MessageReplyHeader?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { self.flags = flags - self.documents = documents - self.settings = settings + self.id = id + self.userId = userId + self.message = message + self.pts = pts + self.ptsCount = ptsCount + self.date = date + self.fwdFrom = fwdFrom + self.viaBotId = viaBotId + self.replyTo = replyTo + self.entities = entities + self.ttlPeriod = ttlPeriod } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeTheme", [("flags", ConstructorParameterDescription(self.flags)), ("documents", ConstructorParameterDescription(self.documents)), ("settings", ConstructorParameterDescription(self.settings))]) + return ("updateShortMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("userId", ConstructorParameterDescription(self.userId)), ("message", ConstructorParameterDescription(self.message)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("fwdFrom", ConstructorParameterDescription(self.fwdFrom)), ("viaBotId", ConstructorParameterDescription(self.viaBotId)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) } } - public class Cons_webPageAttributeUniqueStarGift: TypeConstructorDescription { - public var gift: Api.StarGift - public init(gift: Api.StarGift) { - self.gift = gift + public class Cons_updateShortSentMessage: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var pts: Int32 + public var ptsCount: Int32 + public var date: Int32 + public var media: Api.MessageMedia? + public var entities: [Api.MessageEntity]? + public var ttlPeriod: Int32? + public init(flags: Int32, id: Int32, pts: Int32, ptsCount: Int32, date: Int32, media: Api.MessageMedia?, entities: [Api.MessageEntity]?, ttlPeriod: Int32?) { + self.flags = flags + self.id = id + self.pts = pts + self.ptsCount = ptsCount + self.date = date + self.media = media + self.entities = entities + self.ttlPeriod = ttlPeriod } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPageAttributeUniqueStarGift", [("gift", ConstructorParameterDescription(self.gift))]) + return ("updateShortSentMessage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("date", ConstructorParameterDescription(self.date)), ("media", ConstructorParameterDescription(self.media)), ("entities", ConstructorParameterDescription(self.entities)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod))]) } } - case webPageAttributeStarGiftAuction(Cons_webPageAttributeStarGiftAuction) - case webPageAttributeStarGiftCollection(Cons_webPageAttributeStarGiftCollection) - case webPageAttributeStickerSet(Cons_webPageAttributeStickerSet) - case webPageAttributeStory(Cons_webPageAttributeStory) - case webPageAttributeTheme(Cons_webPageAttributeTheme) - case webPageAttributeUniqueStarGift(Cons_webPageAttributeUniqueStarGift) + public class Cons_updates: TypeConstructorDescription { + public var updates: [Api.Update] + public var users: [Api.User] + public var chats: [Api.Chat] + public var date: Int32 + public var seq: Int32 + public init(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seq: Int32) { + self.updates = updates + self.users = users + self.chats = chats + self.date = date + self.seq = seq + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updates", [("updates", ConstructorParameterDescription(self.updates)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats)), ("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq))]) + } + } + public class Cons_updatesCombined: TypeConstructorDescription { + public var updates: [Api.Update] + public var users: [Api.User] + public var chats: [Api.Chat] + public var date: Int32 + public var seqStart: Int32 + public var seq: Int32 + public init(updates: [Api.Update], users: [Api.User], chats: [Api.Chat], date: Int32, seqStart: Int32, seq: Int32) { + self.updates = updates + self.users = users + self.chats = chats + self.date = date + self.seqStart = seqStart + self.seq = seq + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("updatesCombined", [("updates", ConstructorParameterDescription(self.updates)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats)), ("date", ConstructorParameterDescription(self.date)), ("seqStart", ConstructorParameterDescription(self.seqStart)), ("seq", ConstructorParameterDescription(self.seq))]) + } + } + case updateShort(Cons_updateShort) + case updateShortChatMessage(Cons_updateShortChatMessage) + case updateShortMessage(Cons_updateShortMessage) + case updateShortSentMessage(Cons_updateShortSentMessage) + case updates(Cons_updates) + case updatesCombined(Cons_updatesCombined) + case updatesTooLong public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webPageAttributeStarGiftAuction(let _data): + case .updateShort(let _data): if boxed { - buffer.appendInt32(29770178) + buffer.appendInt32(2027216577) } - _data.gift.serialize(buffer, true) - serializeInt32(_data.endDate, buffer: buffer, boxed: false) + _data.update.serialize(buffer, true) + serializeInt32(_data.date, buffer: buffer, boxed: false) break - case .webPageAttributeStarGiftCollection(let _data): + case .updateShortChatMessage(let _data): if boxed { - buffer.appendInt32(835375875) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.icons.count)) - for item in _data.icons { - item.serialize(buffer, true) - } - break - case .webPageAttributeStickerSet(let _data): - if boxed { - buffer.appendInt32(1355547603) + buffer.appendInt32(1299050149) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stickers.count)) - for item in _data.stickers { - item.serialize(buffer, true) - } - break - case .webPageAttributeStory(let _data): - if boxed { - buffer.appendInt32(781501415) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) serializeInt32(_data.id, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.story!.serialize(buffer, true) - } - break - case .webPageAttributeTheme(let _data): - if boxed { - buffer.appendInt32(1421174295) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents!.count)) - for item in _data.documents! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.settings!.serialize(buffer, true) - } - break - case .webPageAttributeUniqueStarGift(let _data): - if boxed { - buffer.appendInt32(-814781000) - } - _data.gift.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webPageAttributeStarGiftAuction(let _data): - return ("webPageAttributeStarGiftAuction", [("gift", ConstructorParameterDescription(_data.gift)), ("endDate", ConstructorParameterDescription(_data.endDate))]) - case .webPageAttributeStarGiftCollection(let _data): - return ("webPageAttributeStarGiftCollection", [("icons", ConstructorParameterDescription(_data.icons))]) - case .webPageAttributeStickerSet(let _data): - return ("webPageAttributeStickerSet", [("flags", ConstructorParameterDescription(_data.flags)), ("stickers", ConstructorParameterDescription(_data.stickers))]) - case .webPageAttributeStory(let _data): - return ("webPageAttributeStory", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("id", ConstructorParameterDescription(_data.id)), ("story", ConstructorParameterDescription(_data.story))]) - case .webPageAttributeTheme(let _data): - return ("webPageAttributeTheme", [("flags", ConstructorParameterDescription(_data.flags)), ("documents", ConstructorParameterDescription(_data.documents)), ("settings", ConstructorParameterDescription(_data.settings))]) - case .webPageAttributeUniqueStarGift(let _data): - return ("webPageAttributeUniqueStarGift", [("gift", ConstructorParameterDescription(_data.gift))]) - } - } - - public static func parse_webPageAttributeStarGiftAuction(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Api.StarGift? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPageAttribute.webPageAttributeStarGiftAuction(Cons_webPageAttributeStarGiftAuction(gift: _1!, endDate: _2!)) - } - else { - return nil - } - } - public static func parse_webPageAttributeStarGiftCollection(_ reader: BufferReader) -> WebPageAttribute? { - var _1: [Api.Document]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.WebPageAttribute.webPageAttributeStarGiftCollection(Cons_webPageAttributeStarGiftCollection(icons: _1!)) - } - else { - return nil - } - } - public static func parse_webPageAttributeStickerSet(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.WebPageAttribute.webPageAttributeStickerSet(Cons_webPageAttributeStickerSet(flags: _1!, stickers: _2!)) - } - else { - return nil - } - } - public static func parse_webPageAttributeStory(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Api.StoryItem? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StoryItem - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.WebPageAttribute.webPageAttributeStory(Cons_webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4)) - } - else { - return nil - } - } - public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Document]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - } - var _3: Api.ThemeSettings? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ThemeSettings - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebPageAttribute.webPageAttributeTheme(Cons_webPageAttributeTheme(flags: _1!, documents: _2, settings: _3)) - } - else { - return nil - } - } - public static func parse_webPageAttributeUniqueStarGift(_ reader: BufferReader) -> WebPageAttribute? { - var _1: Api.StarGift? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGift - } - let _c1 = _1 != nil - if _c1 { - return Api.WebPageAttribute.webPageAttributeUniqueStarGift(Cons_webPageAttributeUniqueStarGift(gift: _1!)) - } - else { - return nil - } - } - } -} -public extension Api { - enum WebViewMessageSent: TypeConstructorDescription { - public class Cons_webViewMessageSent: TypeConstructorDescription { - public var flags: Int32 - public var msgId: Api.InputBotInlineMessageID? - public init(flags: Int32, msgId: Api.InputBotInlineMessageID?) { - self.flags = flags - self.msgId = msgId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webViewMessageSent", [("flags", ConstructorParameterDescription(self.flags)), ("msgId", ConstructorParameterDescription(self.msgId))]) - } - } - case webViewMessageSent(Cons_webViewMessageSent) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webViewMessageSent(let _data): - if boxed { - buffer.appendInt32(211046684) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.msgId!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webViewMessageSent(let _data): - return ("webViewMessageSent", [("flags", ConstructorParameterDescription(_data.flags)), ("msgId", ConstructorParameterDescription(_data.msgId))]) - } - } - - public static func parse_webViewMessageSent(_ reader: BufferReader) -> WebViewMessageSent? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputBotInlineMessageID? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.WebViewMessageSent.webViewMessageSent(Cons_webViewMessageSent(flags: _1!, msgId: _2)) - } - else { - return nil - } - } - } -} -public extension Api { - enum WebViewResult: TypeConstructorDescription { - public class Cons_webViewResultUrl: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64? - public var url: String - public init(flags: Int32, queryId: Int64?, url: String) { - self.flags = flags - self.queryId = queryId - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webViewResultUrl", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("url", ConstructorParameterDescription(self.url))]) - } - } - case webViewResultUrl(Cons_webViewResultUrl) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webViewResultUrl(let _data): - if boxed { - buffer.appendInt32(1294139288) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.queryId!, buffer: buffer, boxed: false) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webViewResultUrl(let _data): - return ("webViewResultUrl", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_webViewResultUrl(_ reader: BufferReader) -> WebViewResult? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt64() - } - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.WebViewResult.webViewResultUrl(Cons_webViewResultUrl(flags: _1!, queryId: _2, url: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum AuthorizationForm: TypeConstructorDescription { - public class Cons_authorizationForm: TypeConstructorDescription { - public var flags: Int32 - public var requiredTypes: [Api.SecureRequiredType] - public var values: [Api.SecureValue] - public var errors: [Api.SecureValueError] - public var users: [Api.User] - public var privacyPolicyUrl: String? - public init(flags: Int32, requiredTypes: [Api.SecureRequiredType], values: [Api.SecureValue], errors: [Api.SecureValueError], users: [Api.User], privacyPolicyUrl: String?) { - self.flags = flags - self.requiredTypes = requiredTypes - self.values = values - self.errors = errors - self.users = users - self.privacyPolicyUrl = privacyPolicyUrl - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("authorizationForm", [("flags", ConstructorParameterDescription(self.flags)), ("requiredTypes", ConstructorParameterDescription(self.requiredTypes)), ("values", ConstructorParameterDescription(self.values)), ("errors", ConstructorParameterDescription(self.errors)), ("users", ConstructorParameterDescription(self.users)), ("privacyPolicyUrl", ConstructorParameterDescription(self.privacyPolicyUrl))]) - } - } - case authorizationForm(Cons_authorizationForm) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .authorizationForm(let _data): - if boxed { - buffer.appendInt32(-1389486888) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.requiredTypes.count)) - for item in _data.requiredTypes { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.values.count)) - for item in _data.values { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.errors.count)) - for item in _data.errors { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.privacyPolicyUrl!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .authorizationForm(let _data): - return ("authorizationForm", [("flags", ConstructorParameterDescription(_data.flags)), ("requiredTypes", ConstructorParameterDescription(_data.requiredTypes)), ("values", ConstructorParameterDescription(_data.values)), ("errors", ConstructorParameterDescription(_data.errors)), ("users", ConstructorParameterDescription(_data.users)), ("privacyPolicyUrl", ConstructorParameterDescription(_data.privacyPolicyUrl))]) - } - } - - public static func parse_authorizationForm(_ reader: BufferReader) -> AuthorizationForm? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.SecureRequiredType]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) - } - var _3: [Api.SecureValue]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) - } - var _4: [Api.SecureValueError]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueError.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AuthorizationForm.authorizationForm(Cons_authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum Authorizations: TypeConstructorDescription { - public class Cons_authorizations: TypeConstructorDescription { - public var authorizationTtlDays: Int32 - public var authorizations: [Api.Authorization] - public init(authorizationTtlDays: Int32, authorizations: [Api.Authorization]) { - self.authorizationTtlDays = authorizationTtlDays - self.authorizations = authorizations - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("authorizations", [("authorizationTtlDays", ConstructorParameterDescription(self.authorizationTtlDays)), ("authorizations", ConstructorParameterDescription(self.authorizations))]) - } - } - case authorizations(Cons_authorizations) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .authorizations(let _data): - if boxed { - buffer.appendInt32(1275039392) - } - serializeInt32(_data.authorizationTtlDays, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.authorizations.count)) - for item in _data.authorizations { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .authorizations(let _data): - return ("authorizations", [("authorizationTtlDays", ConstructorParameterDescription(_data.authorizationTtlDays)), ("authorizations", ConstructorParameterDescription(_data.authorizations))]) - } - } - - public static func parse_authorizations(_ reader: BufferReader) -> Authorizations? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Authorization]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Authorization.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.Authorizations.authorizations(Cons_authorizations(authorizationTtlDays: _1!, authorizations: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum AutoDownloadSettings: TypeConstructorDescription { - public class Cons_autoDownloadSettings: TypeConstructorDescription { - public var low: Api.AutoDownloadSettings - public var medium: Api.AutoDownloadSettings - public var high: Api.AutoDownloadSettings - public init(low: Api.AutoDownloadSettings, medium: Api.AutoDownloadSettings, high: Api.AutoDownloadSettings) { - self.low = low - self.medium = medium - self.high = high - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("autoDownloadSettings", [("low", ConstructorParameterDescription(self.low)), ("medium", ConstructorParameterDescription(self.medium)), ("high", ConstructorParameterDescription(self.high))]) - } - } - case autoDownloadSettings(Cons_autoDownloadSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .autoDownloadSettings(let _data): - if boxed { - buffer.appendInt32(1674235686) - } - _data.low.serialize(buffer, true) - _data.medium.serialize(buffer, true) - _data.high.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .autoDownloadSettings(let _data): - return ("autoDownloadSettings", [("low", ConstructorParameterDescription(_data.low)), ("medium", ConstructorParameterDescription(_data.medium)), ("high", ConstructorParameterDescription(_data.high))]) - } - } - - public static func parse_autoDownloadSettings(_ reader: BufferReader) -> AutoDownloadSettings? { - var _1: Api.AutoDownloadSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings - } - var _2: Api.AutoDownloadSettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings - } - var _3: Api.AutoDownloadSettings? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.AutoDownloadSettings.autoDownloadSettings(Cons_autoDownloadSettings(low: _1!, medium: _2!, high: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum AutoSaveSettings: TypeConstructorDescription { - public class Cons_autoSaveSettings: TypeConstructorDescription { - public var usersSettings: Api.AutoSaveSettings - public var chatsSettings: Api.AutoSaveSettings - public var broadcastsSettings: Api.AutoSaveSettings - public var exceptions: [Api.AutoSaveException] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(usersSettings: Api.AutoSaveSettings, chatsSettings: Api.AutoSaveSettings, broadcastsSettings: Api.AutoSaveSettings, exceptions: [Api.AutoSaveException], chats: [Api.Chat], users: [Api.User]) { - self.usersSettings = usersSettings - self.chatsSettings = chatsSettings - self.broadcastsSettings = broadcastsSettings - self.exceptions = exceptions - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("autoSaveSettings", [("usersSettings", ConstructorParameterDescription(self.usersSettings)), ("chatsSettings", ConstructorParameterDescription(self.chatsSettings)), ("broadcastsSettings", ConstructorParameterDescription(self.broadcastsSettings)), ("exceptions", ConstructorParameterDescription(self.exceptions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case autoSaveSettings(Cons_autoSaveSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .autoSaveSettings(let _data): - if boxed { - buffer.appendInt32(1279133341) - } - _data.usersSettings.serialize(buffer, true) - _data.chatsSettings.serialize(buffer, true) - _data.broadcastsSettings.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.exceptions.count)) - for item in _data.exceptions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .autoSaveSettings(let _data): - return ("autoSaveSettings", [("usersSettings", ConstructorParameterDescription(_data.usersSettings)), ("chatsSettings", ConstructorParameterDescription(_data.chatsSettings)), ("broadcastsSettings", ConstructorParameterDescription(_data.broadcastsSettings)), ("exceptions", ConstructorParameterDescription(_data.exceptions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? { - var _1: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _2: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _3: Api.AutoSaveSettings? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings - } - var _4: [Api.AutoSaveException]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AutoSaveException.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.AutoSaveSettings.autoSaveSettings(Cons_autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum BusinessChatLinks: TypeConstructorDescription { - public class Cons_businessChatLinks: TypeConstructorDescription { - public var links: [Api.BusinessChatLink] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(links: [Api.BusinessChatLink], chats: [Api.Chat], users: [Api.User]) { - self.links = links - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("businessChatLinks", [("links", ConstructorParameterDescription(self.links)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case businessChatLinks(Cons_businessChatLinks) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .businessChatLinks(let _data): - if boxed { - buffer.appendInt32(-331111727) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.links.count)) - for item in _data.links { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .businessChatLinks(let _data): - return ("businessChatLinks", [("links", ConstructorParameterDescription(_data.links)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_businessChatLinks(_ reader: BufferReader) -> BusinessChatLinks? { - var _1: [Api.BusinessChatLink]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BusinessChatLink.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.BusinessChatLinks.businessChatLinks(Cons_businessChatLinks(links: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum ChatThemes: TypeConstructorDescription { - public class Cons_chatThemes: TypeConstructorDescription { - public var flags: Int32 - public var hash: Int64 - public var themes: [Api.ChatTheme] - public var chats: [Api.Chat] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, hash: Int64, themes: [Api.ChatTheme], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { - self.flags = flags - self.hash = hash - self.themes = themes - self.chats = chats - self.users = users - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatThemes", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("themes", ConstructorParameterDescription(self.themes)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - case chatThemes(Cons_chatThemes) - case chatThemesNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chatThemes(let _data): - if boxed { - buffer.appendInt32(-1106673293) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.themes.count)) - for item in _data.themes { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - break - case .chatThemesNotModified: - if boxed { - buffer.appendInt32(-535699004) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .chatThemes(let _data): - return ("chatThemes", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("themes", ConstructorParameterDescription(_data.themes)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) - case .chatThemesNotModified: - return ("chatThemesNotModified", []) - } - } - - public static func parse_chatThemes(_ reader: BufferReader) -> ChatThemes? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Api.ChatTheme]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.ChatThemes.chatThemes(Cons_chatThemes(flags: _1!, hash: _2!, themes: _3!, chats: _4!, users: _5!, nextOffset: _6)) - } - else { - return nil - } - } - public static func parse_chatThemesNotModified(_ reader: BufferReader) -> ChatThemes? { - return Api.account.ChatThemes.chatThemesNotModified - } - } -} -public extension Api.account { - enum ConnectedBots: TypeConstructorDescription { - public class Cons_connectedBots: TypeConstructorDescription { - public var connectedBots: [Api.ConnectedBot] - public var users: [Api.User] - public init(connectedBots: [Api.ConnectedBot], users: [Api.User]) { - self.connectedBots = connectedBots - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("connectedBots", [("connectedBots", ConstructorParameterDescription(self.connectedBots)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case connectedBots(Cons_connectedBots) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .connectedBots(let _data): - if boxed { - buffer.appendInt32(400029819) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.connectedBots.count)) - for item in _data.connectedBots { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .connectedBots(let _data): - return ("connectedBots", [("connectedBots", ConstructorParameterDescription(_data.connectedBots)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_connectedBots(_ reader: BufferReader) -> ConnectedBots? { - var _1: [Api.ConnectedBot]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBot.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.ConnectedBots.connectedBots(Cons_connectedBots(connectedBots: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum ContentSettings: TypeConstructorDescription { - public class Cons_contentSettings: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("contentSettings", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - case contentSettings(Cons_contentSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contentSettings(let _data): - if boxed { - buffer.appendInt32(1474462241) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .contentSettings(let _data): - return ("contentSettings", [("flags", ConstructorParameterDescription(_data.flags))]) - } - } - - public static func parse_contentSettings(_ reader: BufferReader) -> ContentSettings? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.account.ContentSettings.contentSettings(Cons_contentSettings(flags: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum EmailVerified: TypeConstructorDescription { - public class Cons_emailVerified: TypeConstructorDescription { - public var email: String - public init(email: String) { - self.email = email - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emailVerified", [("email", ConstructorParameterDescription(self.email))]) - } - } - public class Cons_emailVerifiedLogin: TypeConstructorDescription { - public var email: String - public var sentCode: Api.auth.SentCode - public init(email: String, sentCode: Api.auth.SentCode) { - self.email = email - self.sentCode = sentCode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emailVerifiedLogin", [("email", ConstructorParameterDescription(self.email)), ("sentCode", ConstructorParameterDescription(self.sentCode))]) - } - } - case emailVerified(Cons_emailVerified) - case emailVerifiedLogin(Cons_emailVerifiedLogin) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .emailVerified(let _data): - if boxed { - buffer.appendInt32(731303195) - } - serializeString(_data.email, buffer: buffer, boxed: false) - break - case .emailVerifiedLogin(let _data): - if boxed { - buffer.appendInt32(-507835039) - } - serializeString(_data.email, buffer: buffer, boxed: false) - _data.sentCode.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .emailVerified(let _data): - return ("emailVerified", [("email", ConstructorParameterDescription(_data.email))]) - case .emailVerifiedLogin(let _data): - return ("emailVerifiedLogin", [("email", ConstructorParameterDescription(_data.email)), ("sentCode", ConstructorParameterDescription(_data.sentCode))]) - } - } - - public static func parse_emailVerified(_ reader: BufferReader) -> EmailVerified? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.account.EmailVerified.emailVerified(Cons_emailVerified(email: _1!)) - } - else { - return nil - } - } - public static func parse_emailVerifiedLogin(_ reader: BufferReader) -> EmailVerified? { - var _1: String? - _1 = parseString(reader) - var _2: Api.auth.SentCode? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmailVerified.emailVerifiedLogin(Cons_emailVerifiedLogin(email: _1!, sentCode: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum EmojiStatuses: TypeConstructorDescription { - public class Cons_emojiStatuses: TypeConstructorDescription { - public var hash: Int64 - public var statuses: [Api.EmojiStatus] - public init(hash: Int64, statuses: [Api.EmojiStatus]) { - self.hash = hash - self.statuses = statuses - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emojiStatuses", [("hash", ConstructorParameterDescription(self.hash)), ("statuses", ConstructorParameterDescription(self.statuses))]) - } - } - case emojiStatuses(Cons_emojiStatuses) - case emojiStatusesNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .emojiStatuses(let _data): - if boxed { - buffer.appendInt32(-1866176559) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.statuses.count)) - for item in _data.statuses { - item.serialize(buffer, true) - } - break - case .emojiStatusesNotModified: - if boxed { - buffer.appendInt32(-796072379) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .emojiStatuses(let _data): - return ("emojiStatuses", [("hash", ConstructorParameterDescription(_data.hash)), ("statuses", ConstructorParameterDescription(_data.statuses))]) - case .emojiStatusesNotModified: - return ("emojiStatusesNotModified", []) - } - } - - public static func parse_emojiStatuses(_ reader: BufferReader) -> EmojiStatuses? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.EmojiStatus]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiStatus.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.EmojiStatuses.emojiStatuses(Cons_emojiStatuses(hash: _1!, statuses: _2!)) - } - else { - return nil - } - } - public static func parse_emojiStatusesNotModified(_ reader: BufferReader) -> EmojiStatuses? { - return Api.account.EmojiStatuses.emojiStatusesNotModified - } - } -} -public extension Api.account { - enum PaidMessagesRevenue: TypeConstructorDescription { - public class Cons_paidMessagesRevenue: TypeConstructorDescription { - public var starsAmount: Int64 - public init(starsAmount: Int64) { - self.starsAmount = starsAmount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paidMessagesRevenue", [("starsAmount", ConstructorParameterDescription(self.starsAmount))]) - } - } - case paidMessagesRevenue(Cons_paidMessagesRevenue) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paidMessagesRevenue(let _data): - if boxed { - buffer.appendInt32(504403720) - } - serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paidMessagesRevenue(let _data): - return ("paidMessagesRevenue", [("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) - } - } - - public static func parse_paidMessagesRevenue(_ reader: BufferReader) -> PaidMessagesRevenue? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.account.PaidMessagesRevenue.paidMessagesRevenue(Cons_paidMessagesRevenue(starsAmount: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum PasskeyRegistrationOptions: TypeConstructorDescription { - public class Cons_passkeyRegistrationOptions: TypeConstructorDescription { - public var options: Api.DataJSON - public init(options: Api.DataJSON) { - self.options = options - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passkeyRegistrationOptions", [("options", ConstructorParameterDescription(self.options))]) - } - } - case passkeyRegistrationOptions(Cons_passkeyRegistrationOptions) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passkeyRegistrationOptions(let _data): - if boxed { - buffer.appendInt32(-513057567) - } - _data.options.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .passkeyRegistrationOptions(let _data): - return ("passkeyRegistrationOptions", [("options", ConstructorParameterDescription(_data.options))]) - } - } - - public static func parse_passkeyRegistrationOptions(_ reader: BufferReader) -> PasskeyRegistrationOptions? { - var _1: Api.DataJSON? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - let _c1 = _1 != nil - if _c1 { - return Api.account.PasskeyRegistrationOptions.passkeyRegistrationOptions(Cons_passkeyRegistrationOptions(options: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum Passkeys: TypeConstructorDescription { - public class Cons_passkeys: TypeConstructorDescription { - public var passkeys: [Api.Passkey] - public init(passkeys: [Api.Passkey]) { - self.passkeys = passkeys - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passkeys", [("passkeys", ConstructorParameterDescription(self.passkeys))]) - } - } - case passkeys(Cons_passkeys) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passkeys(let _data): - if boxed { - buffer.appendInt32(-119494116) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.passkeys.count)) - for item in _data.passkeys { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .passkeys(let _data): - return ("passkeys", [("passkeys", ConstructorParameterDescription(_data.passkeys))]) - } - } - - public static func parse_passkeys(_ reader: BufferReader) -> Passkeys? { - var _1: [Api.Passkey]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Passkey.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.account.Passkeys.passkeys(Cons_passkeys(passkeys: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum Password: TypeConstructorDescription { - public class Cons_password: TypeConstructorDescription { - public var flags: Int32 - public var currentAlgo: Api.PasswordKdfAlgo? - public var srpB: Buffer? - public var srpId: Int64? - public var hint: String? - public var emailUnconfirmedPattern: String? - public var newAlgo: Api.PasswordKdfAlgo - public var newSecureAlgo: Api.SecurePasswordKdfAlgo - public var secureRandom: Buffer - public var pendingResetDate: Int32? - public var loginEmailPattern: String? - public init(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer, pendingResetDate: Int32?, loginEmailPattern: String?) { - self.flags = flags - self.currentAlgo = currentAlgo - self.srpB = srpB - self.srpId = srpId - self.hint = hint - self.emailUnconfirmedPattern = emailUnconfirmedPattern - self.newAlgo = newAlgo - self.newSecureAlgo = newSecureAlgo - self.secureRandom = secureRandom - self.pendingResetDate = pendingResetDate - self.loginEmailPattern = loginEmailPattern - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("password", [("flags", ConstructorParameterDescription(self.flags)), ("currentAlgo", ConstructorParameterDescription(self.currentAlgo)), ("srpB", ConstructorParameterDescription(self.srpB)), ("srpId", ConstructorParameterDescription(self.srpId)), ("hint", ConstructorParameterDescription(self.hint)), ("emailUnconfirmedPattern", ConstructorParameterDescription(self.emailUnconfirmedPattern)), ("newAlgo", ConstructorParameterDescription(self.newAlgo)), ("newSecureAlgo", ConstructorParameterDescription(self.newSecureAlgo)), ("secureRandom", ConstructorParameterDescription(self.secureRandom)), ("pendingResetDate", ConstructorParameterDescription(self.pendingResetDate)), ("loginEmailPattern", ConstructorParameterDescription(self.loginEmailPattern))]) - } - } - case password(Cons_password) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .password(let _data): - if boxed { - buffer.appendInt32(-1787080453) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.fromId, buffer: buffer, boxed: false) + serializeInt64(_data.chatId, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 2) != 0 { - _data.currentAlgo!.serialize(buffer, true) + _data.fwdFrom!.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeBytes(_data.srpB!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.srpId!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) } if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.hint!, buffer: buffer, boxed: false) + _data.replyTo!.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.emailUnconfirmedPattern!, buffer: buffer, boxed: false) - } - _data.newAlgo.serialize(buffer, true) - _data.newSecureAlgo.serialize(buffer, true) - serializeBytes(_data.secureRandom, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.pendingResetDate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 6) != 0 { - serializeString(_data.loginEmailPattern!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .password(let _data): - return ("password", [("flags", ConstructorParameterDescription(_data.flags)), ("currentAlgo", ConstructorParameterDescription(_data.currentAlgo)), ("srpB", ConstructorParameterDescription(_data.srpB)), ("srpId", ConstructorParameterDescription(_data.srpId)), ("hint", ConstructorParameterDescription(_data.hint)), ("emailUnconfirmedPattern", ConstructorParameterDescription(_data.emailUnconfirmedPattern)), ("newAlgo", ConstructorParameterDescription(_data.newAlgo)), ("newSecureAlgo", ConstructorParameterDescription(_data.newSecureAlgo)), ("secureRandom", ConstructorParameterDescription(_data.secureRandom)), ("pendingResetDate", ConstructorParameterDescription(_data.pendingResetDate)), ("loginEmailPattern", ConstructorParameterDescription(_data.loginEmailPattern))]) - } - } - - public static func parse_password(_ reader: BufferReader) -> Password? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.PasswordKdfAlgo? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo - } - } - var _3: Buffer? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = parseBytes(reader) - } - var _4: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt64() - } - var _5: String? - if Int(_1!) & Int(1 << 3) != 0 { - _5 = parseString(reader) - } - var _6: String? - if Int(_1!) & Int(1 << 4) != 0 { - _6 = parseString(reader) - } - var _7: Api.PasswordKdfAlgo? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo - } - var _8: Api.SecurePasswordKdfAlgo? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo - } - var _9: Buffer? - _9 = parseBytes(reader) - var _10: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _10 = reader.readInt32() - } - var _11: String? - if Int(_1!) & Int(1 << 6) != 0 { - _11 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 6) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.account.Password.password(Cons_password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum PasswordInputSettings: TypeConstructorDescription { - public class Cons_passwordInputSettings: TypeConstructorDescription { - public var flags: Int32 - public var newAlgo: Api.PasswordKdfAlgo? - public var newPasswordHash: Buffer? - public var hint: String? - public var email: String? - public var newSecureSettings: Api.SecureSecretSettings? - public init(flags: Int32, newAlgo: Api.PasswordKdfAlgo?, newPasswordHash: Buffer?, hint: String?, email: String?, newSecureSettings: Api.SecureSecretSettings?) { - self.flags = flags - self.newAlgo = newAlgo - self.newPasswordHash = newPasswordHash - self.hint = hint - self.email = email - self.newSecureSettings = newSecureSettings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passwordInputSettings", [("flags", ConstructorParameterDescription(self.flags)), ("newAlgo", ConstructorParameterDescription(self.newAlgo)), ("newPasswordHash", ConstructorParameterDescription(self.newPasswordHash)), ("hint", ConstructorParameterDescription(self.hint)), ("email", ConstructorParameterDescription(self.email)), ("newSecureSettings", ConstructorParameterDescription(self.newSecureSettings))]) - } - } - case passwordInputSettings(Cons_passwordInputSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passwordInputSettings(let _data): - if boxed { - buffer.appendInt32(-1036572727) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.newAlgo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.newPasswordHash!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.hint!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.email!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.newSecureSettings!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .passwordInputSettings(let _data): - return ("passwordInputSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("newAlgo", ConstructorParameterDescription(_data.newAlgo)), ("newPasswordHash", ConstructorParameterDescription(_data.newPasswordHash)), ("hint", ConstructorParameterDescription(_data.hint)), ("email", ConstructorParameterDescription(_data.email)), ("newSecureSettings", ConstructorParameterDescription(_data.newSecureSettings))]) - } - } - - public static func parse_passwordInputSettings(_ reader: BufferReader) -> PasswordInputSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.PasswordKdfAlgo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo - } - } - var _3: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseBytes(reader) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = parseString(reader) - } - var _6: Api.SecureSecretSettings? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.PasswordInputSettings.passwordInputSettings(Cons_passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum PasswordSettings: TypeConstructorDescription { - public class Cons_passwordSettings: TypeConstructorDescription { - public var flags: Int32 - public var email: String? - public var secureSettings: Api.SecureSecretSettings? - public init(flags: Int32, email: String?, secureSettings: Api.SecureSecretSettings?) { - self.flags = flags - self.email = email - self.secureSettings = secureSettings - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passwordSettings", [("flags", ConstructorParameterDescription(self.flags)), ("email", ConstructorParameterDescription(self.email)), ("secureSettings", ConstructorParameterDescription(self.secureSettings))]) - } - } - case passwordSettings(Cons_passwordSettings) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .passwordSettings(let _data): - if boxed { - buffer.appendInt32(-1705233435) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.email!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.secureSettings!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .passwordSettings(let _data): - return ("passwordSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("email", ConstructorParameterDescription(_data.email)), ("secureSettings", ConstructorParameterDescription(_data.secureSettings))]) - } - } - - public static func parse_passwordSettings(_ reader: BufferReader) -> PasswordSettings? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - var _3: Api.SecureSecretSettings? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PasswordSettings.passwordSettings(Cons_passwordSettings(flags: _1!, email: _2, secureSettings: _3)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum PrivacyRules: TypeConstructorDescription { - public class Cons_privacyRules: TypeConstructorDescription { - public var rules: [Api.PrivacyRule] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(rules: [Api.PrivacyRule], chats: [Api.Chat], users: [Api.User]) { - self.rules = rules - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("privacyRules", [("rules", ConstructorParameterDescription(self.rules)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case privacyRules(Cons_privacyRules) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .privacyRules(let _data): - if boxed { - buffer.appendInt32(1352683077) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.rules.count)) - for item in _data.rules { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .privacyRules(let _data): - return ("privacyRules", [("rules", ConstructorParameterDescription(_data.rules)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_privacyRules(_ reader: BufferReader) -> PrivacyRules? { - var _1: [Api.PrivacyRule]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.account.PrivacyRules.privacyRules(Cons_privacyRules(rules: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum ResetPasswordResult: TypeConstructorDescription { - public class Cons_resetPasswordFailedWait: TypeConstructorDescription { - public var retryDate: Int32 - public init(retryDate: Int32) { - self.retryDate = retryDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("resetPasswordFailedWait", [("retryDate", ConstructorParameterDescription(self.retryDate))]) - } - } - public class Cons_resetPasswordRequestedWait: TypeConstructorDescription { - public var untilDate: Int32 - public init(untilDate: Int32) { - self.untilDate = untilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("resetPasswordRequestedWait", [("untilDate", ConstructorParameterDescription(self.untilDate))]) - } - } - case resetPasswordFailedWait(Cons_resetPasswordFailedWait) - case resetPasswordOk - case resetPasswordRequestedWait(Cons_resetPasswordRequestedWait) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .resetPasswordFailedWait(let _data): - if boxed { - buffer.appendInt32(-478701471) - } - serializeInt32(_data.retryDate, buffer: buffer, boxed: false) - break - case .resetPasswordOk: - if boxed { - buffer.appendInt32(-383330754) - } - break - case .resetPasswordRequestedWait(let _data): - if boxed { - buffer.appendInt32(-370148227) - } - serializeInt32(_data.untilDate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .resetPasswordFailedWait(let _data): - return ("resetPasswordFailedWait", [("retryDate", ConstructorParameterDescription(_data.retryDate))]) - case .resetPasswordOk: - return ("resetPasswordOk", []) - case .resetPasswordRequestedWait(let _data): - return ("resetPasswordRequestedWait", [("untilDate", ConstructorParameterDescription(_data.untilDate))]) - } - } - - public static func parse_resetPasswordFailedWait(_ reader: BufferReader) -> ResetPasswordResult? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordFailedWait(Cons_resetPasswordFailedWait(retryDate: _1!)) - } - else { - return nil - } - } - public static func parse_resetPasswordOk(_ reader: BufferReader) -> ResetPasswordResult? { - return Api.account.ResetPasswordResult.resetPasswordOk - } - public static func parse_resetPasswordRequestedWait(_ reader: BufferReader) -> ResetPasswordResult? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.account.ResetPasswordResult.resetPasswordRequestedWait(Cons_resetPasswordRequestedWait(untilDate: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum ResolvedBusinessChatLinks: TypeConstructorDescription { - public class Cons_resolvedBusinessChatLinks: TypeConstructorDescription { - public var flags: Int32 - public var peer: Api.Peer - public var message: String - public var entities: [Api.MessageEntity]? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, peer: Api.Peer, message: String, entities: [Api.MessageEntity]?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.peer = peer - self.message = message - self.entities = entities - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("resolvedBusinessChatLinks", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case resolvedBusinessChatLinks(Cons_resolvedBusinessChatLinks) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .resolvedBusinessChatLinks(let _data): - if boxed { - buffer.appendInt32(-1708937439) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.peer.serialize(buffer, true) - serializeString(_data.message, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { + if Int(_data.flags) & Int(1 << 7) != 0 { buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.entities!.count)) for item in _data.entities! { item.serialize(buffer, true) } } + if Int(_data.flags) & Int(1 << 25) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + break + case .updateShortMessage(let _data): + if boxed { + buffer.appendInt32(826001400) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.userId, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.fwdFrom!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt64(_data.viaBotId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.replyTo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 25) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + break + case .updateShortSentMessage(let _data): + if boxed { + buffer.appendInt32(-1877614335) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.media!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 25) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + break + case .updates(let _data): + if boxed { + buffer.appendInt32(1957577280) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { + buffer.appendInt32(Int32(_data.updates.count)) + for item in _data.updates { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1787,48 +251,2251 @@ public extension Api.account { for item in _data.users { item.serialize(buffer, true) } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.seq, buffer: buffer, boxed: false) + break + case .updatesCombined(let _data): + if boxed { + buffer.appendInt32(1918567619) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.updates.count)) + for item in _data.updates { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.seqStart, buffer: buffer, boxed: false) + serializeInt32(_data.seq, buffer: buffer, boxed: false) + break + case .updatesTooLong: + if boxed { + buffer.appendInt32(-484987010) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .updateShort(let _data): + return ("updateShort", [("update", ConstructorParameterDescription(_data.update)), ("date", ConstructorParameterDescription(_data.date))]) + case .updateShortChatMessage(let _data): + return ("updateShortChatMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("chatId", ConstructorParameterDescription(_data.chatId)), ("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) + case .updateShortMessage(let _data): + return ("updateShortMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("userId", ConstructorParameterDescription(_data.userId)), ("message", ConstructorParameterDescription(_data.message)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("fwdFrom", ConstructorParameterDescription(_data.fwdFrom)), ("viaBotId", ConstructorParameterDescription(_data.viaBotId)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) + case .updateShortSentMessage(let _data): + return ("updateShortSentMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("date", ConstructorParameterDescription(_data.date)), ("media", ConstructorParameterDescription(_data.media)), ("entities", ConstructorParameterDescription(_data.entities)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod))]) + case .updates(let _data): + return ("updates", [("updates", ConstructorParameterDescription(_data.updates)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats)), ("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq))]) + case .updatesCombined(let _data): + return ("updatesCombined", [("updates", ConstructorParameterDescription(_data.updates)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats)), ("date", ConstructorParameterDescription(_data.date)), ("seqStart", ConstructorParameterDescription(_data.seqStart)), ("seq", ConstructorParameterDescription(_data.seq))]) + case .updatesTooLong: + return ("updatesTooLong", []) + } + } + + public static func parse_updateShort(_ reader: BufferReader) -> Updates? { + var _1: Api.Update? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Update + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Updates.updateShort(Cons_updateShort(update: _1!, date: _2!)) + } + else { + return nil + } + } + public static func parse_updateShortChatMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Api.MessageFwdHeader? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } + } + var _10: Int64? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _10 = reader.readInt64() + } + var _11: Api.MessageReplyHeader? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } + } + var _12: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + _13 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.Updates.updateShortChatMessage(Cons_updateShortChatMessage(flags: _1!, id: _2!, fromId: _3!, chatId: _4!, message: _5!, pts: _6!, ptsCount: _7!, date: _8!, fwdFrom: _9, viaBotId: _10, replyTo: _11, entities: _12, ttlPeriod: _13)) + } + else { + return nil + } + } + public static func parse_updateShortMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Api.MessageFwdHeader? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader + } + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _9 = reader.readInt64() + } + var _10: Api.MessageReplyHeader? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader + } + } + var _11: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _12: Int32? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + _12 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.Updates.updateShortMessage(Cons_updateShortMessage(flags: _1!, id: _2!, userId: _3!, message: _4!, pts: _5!, ptsCount: _6!, date: _7!, fwdFrom: _8, viaBotId: _9, replyTo: _10, entities: _11, ttlPeriod: _12)) + } + else { + return nil + } + } + public static func parse_updateShortSentMessage(_ reader: BufferReader) -> Updates? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Api.MessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + } + var _7: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + var _8: Int32? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + _8 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.Updates.updateShortSentMessage(Cons_updateShortSentMessage(flags: _1!, id: _2!, pts: _3!, ptsCount: _4!, date: _5!, media: _6, entities: _7, ttlPeriod: _8)) + } + else { + return nil + } + } + public static func parse_updates(_ reader: BufferReader) -> Updates? { + var _1: [Api.Update]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Updates.updates(Cons_updates(updates: _1!, users: _2!, chats: _3!, date: _4!, seq: _5!)) + } + else { + return nil + } + } + public static func parse_updatesCombined(_ reader: BufferReader) -> Updates? { + var _1: [Api.Update]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Updates.updatesCombined(Cons_updatesCombined(updates: _1!, users: _2!, chats: _3!, date: _4!, seqStart: _5!, seq: _6!)) + } + else { + return nil + } + } + public static func parse_updatesTooLong(_ reader: BufferReader) -> Updates? { + return Api.Updates.updatesTooLong + } + } +} +public extension Api { + enum UrlAuthResult: TypeConstructorDescription { + public class Cons_urlAuthResultAccepted: TypeConstructorDescription { + public var flags: Int32 + public var url: String? + public init(flags: Int32, url: String?) { + self.flags = flags + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("urlAuthResultAccepted", [("flags", ConstructorParameterDescription(self.flags)), ("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_urlAuthResultRequest: TypeConstructorDescription { + public var flags: Int32 + public var bot: Api.User + public var domain: String + public var browser: String? + public var platform: String? + public var ip: String? + public var region: String? + public var matchCodes: [String]? + public var userIdHint: Int64? + public var verifiedAppName: String? + public init(flags: Int32, bot: Api.User, domain: String, browser: String?, platform: String?, ip: String?, region: String?, matchCodes: [String]?, userIdHint: Int64?, verifiedAppName: String?) { + self.flags = flags + self.bot = bot + self.domain = domain + self.browser = browser + self.platform = platform + self.ip = ip + self.region = region + self.matchCodes = matchCodes + self.userIdHint = userIdHint + self.verifiedAppName = verifiedAppName + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("urlAuthResultRequest", [("flags", ConstructorParameterDescription(self.flags)), ("bot", ConstructorParameterDescription(self.bot)), ("domain", ConstructorParameterDescription(self.domain)), ("browser", ConstructorParameterDescription(self.browser)), ("platform", ConstructorParameterDescription(self.platform)), ("ip", ConstructorParameterDescription(self.ip)), ("region", ConstructorParameterDescription(self.region)), ("matchCodes", ConstructorParameterDescription(self.matchCodes)), ("userIdHint", ConstructorParameterDescription(self.userIdHint)), ("verifiedAppName", ConstructorParameterDescription(self.verifiedAppName))]) + } + } + case urlAuthResultAccepted(Cons_urlAuthResultAccepted) + case urlAuthResultDefault + case urlAuthResultRequest(Cons_urlAuthResultRequest) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .urlAuthResultAccepted(let _data): + if boxed { + buffer.appendInt32(1648005024) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + break + case .urlAuthResultDefault: + if boxed { + buffer.appendInt32(-1445536993) + } + break + case .urlAuthResultRequest(let _data): + if boxed { + buffer.appendInt32(1020666860) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.bot.serialize(buffer, true) + serializeString(_data.domain, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.browser!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.platform!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.ip!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.region!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.matchCodes!.count)) + for item in _data.matchCodes! { + serializeString(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.userIdHint!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeString(_data.verifiedAppName!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .urlAuthResultAccepted(let _data): + return ("urlAuthResultAccepted", [("flags", ConstructorParameterDescription(_data.flags)), ("url", ConstructorParameterDescription(_data.url))]) + case .urlAuthResultDefault: + return ("urlAuthResultDefault", []) + case .urlAuthResultRequest(let _data): + return ("urlAuthResultRequest", [("flags", ConstructorParameterDescription(_data.flags)), ("bot", ConstructorParameterDescription(_data.bot)), ("domain", ConstructorParameterDescription(_data.domain)), ("browser", ConstructorParameterDescription(_data.browser)), ("platform", ConstructorParameterDescription(_data.platform)), ("ip", ConstructorParameterDescription(_data.ip)), ("region", ConstructorParameterDescription(_data.region)), ("matchCodes", ConstructorParameterDescription(_data.matchCodes)), ("userIdHint", ConstructorParameterDescription(_data.userIdHint)), ("verifiedAppName", ConstructorParameterDescription(_data.verifiedAppName))]) + } + } + + public static func parse_urlAuthResultAccepted(_ reader: BufferReader) -> UrlAuthResult? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.UrlAuthResult.urlAuthResultAccepted(Cons_urlAuthResultAccepted(flags: _1!, url: _2)) + } + else { + return nil + } + } + public static func parse_urlAuthResultDefault(_ reader: BufferReader) -> UrlAuthResult? { + return Api.UrlAuthResult.urlAuthResultDefault + } + public static func parse_urlAuthResultRequest(_ reader: BufferReader) -> UrlAuthResult? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.User? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.User + } + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseString(reader) + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = parseString(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _7 = parseString(reader) + } + var _8: [String]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _9 = reader.readInt64() + } + var _10: String? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _10 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.UrlAuthResult.urlAuthResultRequest(Cons_urlAuthResultRequest(flags: _1!, bot: _2!, domain: _3!, browser: _4, platform: _5, ip: _6, region: _7, matchCodes: _8, userIdHint: _9, verifiedAppName: _10)) + } + else { + return nil + } + } + } +} +public extension Api { + enum User: TypeConstructorDescription { + public class Cons_user: TypeConstructorDescription { + public var flags: Int32 + public var flags2: Int32 + public var id: Int64 + public var accessHash: Int64? + public var firstName: String? + public var lastName: String? + public var username: String? + public var phone: String? + public var photo: Api.UserProfilePhoto? + public var status: Api.UserStatus? + public var botInfoVersion: Int32? + public var restrictionReason: [Api.RestrictionReason]? + public var botInlinePlaceholder: String? + public var langCode: String? + public var emojiStatus: Api.EmojiStatus? + public var usernames: [Api.Username]? + public var storiesMaxId: Api.RecentStory? + public var color: Api.PeerColor? + public var profileColor: Api.PeerColor? + public var botActiveUsers: Int32? + public var botVerificationIcon: Int64? + public var sendPaidMessagesStars: Int64? + public init(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?, storiesMaxId: Api.RecentStory?, color: Api.PeerColor?, profileColor: Api.PeerColor?, botActiveUsers: Int32?, botVerificationIcon: Int64?, sendPaidMessagesStars: Int64?) { + self.flags = flags + self.flags2 = flags2 + self.id = id + self.accessHash = accessHash + self.firstName = firstName + self.lastName = lastName + self.username = username + self.phone = phone + self.photo = photo + self.status = status + self.botInfoVersion = botInfoVersion + self.restrictionReason = restrictionReason + self.botInlinePlaceholder = botInlinePlaceholder + self.langCode = langCode + self.emojiStatus = emojiStatus + self.usernames = usernames + self.storiesMaxId = storiesMaxId + self.color = color + self.profileColor = profileColor + self.botActiveUsers = botActiveUsers + self.botVerificationIcon = botVerificationIcon + self.sendPaidMessagesStars = sendPaidMessagesStars + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("user", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("firstName", ConstructorParameterDescription(self.firstName)), ("lastName", ConstructorParameterDescription(self.lastName)), ("username", ConstructorParameterDescription(self.username)), ("phone", ConstructorParameterDescription(self.phone)), ("photo", ConstructorParameterDescription(self.photo)), ("status", ConstructorParameterDescription(self.status)), ("botInfoVersion", ConstructorParameterDescription(self.botInfoVersion)), ("restrictionReason", ConstructorParameterDescription(self.restrictionReason)), ("botInlinePlaceholder", ConstructorParameterDescription(self.botInlinePlaceholder)), ("langCode", ConstructorParameterDescription(self.langCode)), ("emojiStatus", ConstructorParameterDescription(self.emojiStatus)), ("usernames", ConstructorParameterDescription(self.usernames)), ("storiesMaxId", ConstructorParameterDescription(self.storiesMaxId)), ("color", ConstructorParameterDescription(self.color)), ("profileColor", ConstructorParameterDescription(self.profileColor)), ("botActiveUsers", ConstructorParameterDescription(self.botActiveUsers)), ("botVerificationIcon", ConstructorParameterDescription(self.botVerificationIcon)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars))]) + } + } + public class Cons_userEmpty: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userEmpty", [("id", ConstructorParameterDescription(self.id))]) + } + } + case user(Cons_user) + case userEmpty(Cons_userEmpty) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .user(let _data): + if boxed { + buffer.appendInt32(829899656) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.flags2, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.accessHash!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.firstName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.lastName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.username!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.phone!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + _data.status!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + serializeInt32(_data.botInfoVersion!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 18) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.restrictionReason!.count)) + for item in _data.restrictionReason! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 19) != 0 { + serializeString(_data.botInlinePlaceholder!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 22) != 0 { + serializeString(_data.langCode!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 30) != 0 { + _data.emojiStatus!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.usernames!.count)) + for item in _data.usernames! { + item.serialize(buffer, true) + } + } + if Int(_data.flags2) & Int(1 << 5) != 0 { + _data.storiesMaxId!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 8) != 0 { + _data.color!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 9) != 0 { + _data.profileColor!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 12) != 0 { + serializeInt32(_data.botActiveUsers!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 14) != 0 { + serializeInt64(_data.botVerificationIcon!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 15) != 0 { + serializeInt64(_data.sendPaidMessagesStars!, buffer: buffer, boxed: false) + } + break + case .userEmpty(let _data): + if boxed { + buffer.appendInt32(-742634630) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .user(let _data): + return ("user", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("firstName", ConstructorParameterDescription(_data.firstName)), ("lastName", ConstructorParameterDescription(_data.lastName)), ("username", ConstructorParameterDescription(_data.username)), ("phone", ConstructorParameterDescription(_data.phone)), ("photo", ConstructorParameterDescription(_data.photo)), ("status", ConstructorParameterDescription(_data.status)), ("botInfoVersion", ConstructorParameterDescription(_data.botInfoVersion)), ("restrictionReason", ConstructorParameterDescription(_data.restrictionReason)), ("botInlinePlaceholder", ConstructorParameterDescription(_data.botInlinePlaceholder)), ("langCode", ConstructorParameterDescription(_data.langCode)), ("emojiStatus", ConstructorParameterDescription(_data.emojiStatus)), ("usernames", ConstructorParameterDescription(_data.usernames)), ("storiesMaxId", ConstructorParameterDescription(_data.storiesMaxId)), ("color", ConstructorParameterDescription(_data.color)), ("profileColor", ConstructorParameterDescription(_data.profileColor)), ("botActiveUsers", ConstructorParameterDescription(_data.botActiveUsers)), ("botVerificationIcon", ConstructorParameterDescription(_data.botVerificationIcon)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars))]) + case .userEmpty(let _data): + return ("userEmpty", [("id", ConstructorParameterDescription(_data.id))]) + } + } + + public static func parse_user(_ reader: BufferReader) -> User? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt64() + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _6 = parseString(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _7 = parseString(reader) + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _8 = parseString(reader) + } + var _9: Api.UserProfilePhoto? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.UserProfilePhoto + } + } + var _10: Api.UserStatus? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.UserStatus + } + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + _11 = reader.readInt32() + } + var _12: [Api.RestrictionReason]? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) + } + } + var _13: String? + if Int(_1 ?? 0) & Int(1 << 19) != 0 { + _13 = parseString(reader) + } + var _14: String? + if Int(_1 ?? 0) & Int(1 << 22) != 0 { + _14 = parseString(reader) + } + var _15: Api.EmojiStatus? + if Int(_1 ?? 0) & Int(1 << 30) != 0 { + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.EmojiStatus + } + } + var _16: [Api.Username]? + if Int(_2 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) + } + } + var _17: Api.RecentStory? + if Int(_2 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.RecentStory + } + } + var _18: Api.PeerColor? + if Int(_2 ?? 0) & Int(1 << 8) != 0 { + if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.PeerColor + } + } + var _19: Api.PeerColor? + if Int(_2 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.PeerColor + } + } + var _20: Int32? + if Int(_2 ?? 0) & Int(1 << 12) != 0 { + _20 = reader.readInt32() + } + var _21: Int64? + if Int(_2 ?? 0) & Int(1 << 14) != 0 { + _21 = reader.readInt64() + } + var _22: Int64? + if Int(_2 ?? 0) & Int(1 << 15) != 0 { + _22 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 19) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 22) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 30) == 0) || _15 != nil + let _c16 = (Int(_2 ?? 0) & Int(1 << 0) == 0) || _16 != nil + let _c17 = (Int(_2 ?? 0) & Int(1 << 5) == 0) || _17 != nil + let _c18 = (Int(_2 ?? 0) & Int(1 << 8) == 0) || _18 != nil + let _c19 = (Int(_2 ?? 0) & Int(1 << 9) == 0) || _19 != nil + let _c20 = (Int(_2 ?? 0) & Int(1 << 12) == 0) || _20 != nil + let _c21 = (Int(_2 ?? 0) & Int(1 << 14) == 0) || _21 != nil + let _c22 = (Int(_2 ?? 0) & Int(1 << 15) == 0) || _22 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { + return Api.User.user(Cons_user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17, color: _18, profileColor: _19, botActiveUsers: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22)) + } + else { + return nil + } + } + public static func parse_userEmpty(_ reader: BufferReader) -> User? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.User.userEmpty(Cons_userEmpty(id: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum UserFull: TypeConstructorDescription { + public class Cons_userFull: TypeConstructorDescription { + public var flags: Int32 + public var flags2: Int32 + public var id: Int64 + public var about: String? + public var settings: Api.PeerSettings + public var personalPhoto: Api.Photo? + public var profilePhoto: Api.Photo? + public var fallbackPhoto: Api.Photo? + public var notifySettings: Api.PeerNotifySettings + public var botInfo: Api.BotInfo? + public var pinnedMsgId: Int32? + public var commonChatsCount: Int32 + public var folderId: Int32? + public var ttlPeriod: Int32? + public var theme: Api.ChatTheme? + public var privateForwardName: String? + public var botGroupAdminRights: Api.ChatAdminRights? + public var botBroadcastAdminRights: Api.ChatAdminRights? + public var wallpaper: Api.WallPaper? + public var stories: Api.PeerStories? + public var businessWorkHours: Api.BusinessWorkHours? + public var businessLocation: Api.BusinessLocation? + public var businessGreetingMessage: Api.BusinessGreetingMessage? + public var businessAwayMessage: Api.BusinessAwayMessage? + public var businessIntro: Api.BusinessIntro? + public var birthday: Api.Birthday? + public var personalChannelId: Int64? + public var personalChannelMessage: Int32? + public var stargiftsCount: Int32? + public var starrefProgram: Api.StarRefProgram? + public var botVerification: Api.BotVerification? + public var sendPaidMessagesStars: Int64? + public var disallowedGifts: Api.DisallowedGiftsSettings? + public var starsRating: Api.StarsRating? + public var starsMyPendingRating: Api.StarsRating? + public var starsMyPendingRatingDate: Int32? + public var mainTab: Api.ProfileTab? + public var savedMusic: Api.Document? + public var note: Api.TextWithEntities? + public var botManagerId: Int64? + public init(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?, botManagerId: Int64?) { + self.flags = flags + self.flags2 = flags2 + self.id = id + self.about = about + self.settings = settings + self.personalPhoto = personalPhoto + self.profilePhoto = profilePhoto + self.fallbackPhoto = fallbackPhoto + self.notifySettings = notifySettings + self.botInfo = botInfo + self.pinnedMsgId = pinnedMsgId + self.commonChatsCount = commonChatsCount + self.folderId = folderId + self.ttlPeriod = ttlPeriod + self.theme = theme + self.privateForwardName = privateForwardName + self.botGroupAdminRights = botGroupAdminRights + self.botBroadcastAdminRights = botBroadcastAdminRights + self.wallpaper = wallpaper + self.stories = stories + self.businessWorkHours = businessWorkHours + self.businessLocation = businessLocation + self.businessGreetingMessage = businessGreetingMessage + self.businessAwayMessage = businessAwayMessage + self.businessIntro = businessIntro + self.birthday = birthday + self.personalChannelId = personalChannelId + self.personalChannelMessage = personalChannelMessage + self.stargiftsCount = stargiftsCount + self.starrefProgram = starrefProgram + self.botVerification = botVerification + self.sendPaidMessagesStars = sendPaidMessagesStars + self.disallowedGifts = disallowedGifts + self.starsRating = starsRating + self.starsMyPendingRating = starsMyPendingRating + self.starsMyPendingRatingDate = starsMyPendingRatingDate + self.mainTab = mainTab + self.savedMusic = savedMusic + self.note = note + self.botManagerId = botManagerId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userFull", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("about", ConstructorParameterDescription(self.about)), ("settings", ConstructorParameterDescription(self.settings)), ("personalPhoto", ConstructorParameterDescription(self.personalPhoto)), ("profilePhoto", ConstructorParameterDescription(self.profilePhoto)), ("fallbackPhoto", ConstructorParameterDescription(self.fallbackPhoto)), ("notifySettings", ConstructorParameterDescription(self.notifySettings)), ("botInfo", ConstructorParameterDescription(self.botInfo)), ("pinnedMsgId", ConstructorParameterDescription(self.pinnedMsgId)), ("commonChatsCount", ConstructorParameterDescription(self.commonChatsCount)), ("folderId", ConstructorParameterDescription(self.folderId)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("theme", ConstructorParameterDescription(self.theme)), ("privateForwardName", ConstructorParameterDescription(self.privateForwardName)), ("botGroupAdminRights", ConstructorParameterDescription(self.botGroupAdminRights)), ("botBroadcastAdminRights", ConstructorParameterDescription(self.botBroadcastAdminRights)), ("wallpaper", ConstructorParameterDescription(self.wallpaper)), ("stories", ConstructorParameterDescription(self.stories)), ("businessWorkHours", ConstructorParameterDescription(self.businessWorkHours)), ("businessLocation", ConstructorParameterDescription(self.businessLocation)), ("businessGreetingMessage", ConstructorParameterDescription(self.businessGreetingMessage)), ("businessAwayMessage", ConstructorParameterDescription(self.businessAwayMessage)), ("businessIntro", ConstructorParameterDescription(self.businessIntro)), ("birthday", ConstructorParameterDescription(self.birthday)), ("personalChannelId", ConstructorParameterDescription(self.personalChannelId)), ("personalChannelMessage", ConstructorParameterDescription(self.personalChannelMessage)), ("stargiftsCount", ConstructorParameterDescription(self.stargiftsCount)), ("starrefProgram", ConstructorParameterDescription(self.starrefProgram)), ("botVerification", ConstructorParameterDescription(self.botVerification)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars)), ("disallowedGifts", ConstructorParameterDescription(self.disallowedGifts)), ("starsRating", ConstructorParameterDescription(self.starsRating)), ("starsMyPendingRating", ConstructorParameterDescription(self.starsMyPendingRating)), ("starsMyPendingRatingDate", ConstructorParameterDescription(self.starsMyPendingRatingDate)), ("mainTab", ConstructorParameterDescription(self.mainTab)), ("savedMusic", ConstructorParameterDescription(self.savedMusic)), ("note", ConstructorParameterDescription(self.note)), ("botManagerId", ConstructorParameterDescription(self.botManagerId))]) + } + } + case userFull(Cons_userFull) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userFull(let _data): + if boxed { + buffer.appendInt32(114026053) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.flags2, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.about!, buffer: buffer, boxed: false) + } + _data.settings.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 21) != 0 { + _data.personalPhoto!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.profilePhoto!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 22) != 0 { + _data.fallbackPhoto!.serialize(buffer, true) + } + _data.notifySettings.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.botInfo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt32(_data.pinnedMsgId!, buffer: buffer, boxed: false) + } + serializeInt32(_data.commonChatsCount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 11) != 0 { + serializeInt32(_data.folderId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 14) != 0 { + serializeInt32(_data.ttlPeriod!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 15) != 0 { + _data.theme!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 16) != 0 { + serializeString(_data.privateForwardName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 17) != 0 { + _data.botGroupAdminRights!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 18) != 0 { + _data.botBroadcastAdminRights!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 24) != 0 { + _data.wallpaper!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 25) != 0 { + _data.stories!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 0) != 0 { + _data.businessWorkHours!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 1) != 0 { + _data.businessLocation!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 2) != 0 { + _data.businessGreetingMessage!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 3) != 0 { + _data.businessAwayMessage!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 4) != 0 { + _data.businessIntro!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 5) != 0 { + _data.birthday!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 6) != 0 { + serializeInt64(_data.personalChannelId!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 6) != 0 { + serializeInt32(_data.personalChannelMessage!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 8) != 0 { + serializeInt32(_data.stargiftsCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 11) != 0 { + _data.starrefProgram!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 12) != 0 { + _data.botVerification!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 14) != 0 { + serializeInt64(_data.sendPaidMessagesStars!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 15) != 0 { + _data.disallowedGifts!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 17) != 0 { + _data.starsRating!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 18) != 0 { + _data.starsMyPendingRating!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 18) != 0 { + serializeInt32(_data.starsMyPendingRatingDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags2) & Int(1 << 20) != 0 { + _data.mainTab!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 21) != 0 { + _data.savedMusic!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 22) != 0 { + _data.note!.serialize(buffer, true) + } + if Int(_data.flags2) & Int(1 << 25) != 0 { + serializeInt64(_data.botManagerId!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .userFull(let _data): + return ("userFull", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("about", ConstructorParameterDescription(_data.about)), ("settings", ConstructorParameterDescription(_data.settings)), ("personalPhoto", ConstructorParameterDescription(_data.personalPhoto)), ("profilePhoto", ConstructorParameterDescription(_data.profilePhoto)), ("fallbackPhoto", ConstructorParameterDescription(_data.fallbackPhoto)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings)), ("botInfo", ConstructorParameterDescription(_data.botInfo)), ("pinnedMsgId", ConstructorParameterDescription(_data.pinnedMsgId)), ("commonChatsCount", ConstructorParameterDescription(_data.commonChatsCount)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("theme", ConstructorParameterDescription(_data.theme)), ("privateForwardName", ConstructorParameterDescription(_data.privateForwardName)), ("botGroupAdminRights", ConstructorParameterDescription(_data.botGroupAdminRights)), ("botBroadcastAdminRights", ConstructorParameterDescription(_data.botBroadcastAdminRights)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper)), ("stories", ConstructorParameterDescription(_data.stories)), ("businessWorkHours", ConstructorParameterDescription(_data.businessWorkHours)), ("businessLocation", ConstructorParameterDescription(_data.businessLocation)), ("businessGreetingMessage", ConstructorParameterDescription(_data.businessGreetingMessage)), ("businessAwayMessage", ConstructorParameterDescription(_data.businessAwayMessage)), ("businessIntro", ConstructorParameterDescription(_data.businessIntro)), ("birthday", ConstructorParameterDescription(_data.birthday)), ("personalChannelId", ConstructorParameterDescription(_data.personalChannelId)), ("personalChannelMessage", ConstructorParameterDescription(_data.personalChannelMessage)), ("stargiftsCount", ConstructorParameterDescription(_data.stargiftsCount)), ("starrefProgram", ConstructorParameterDescription(_data.starrefProgram)), ("botVerification", ConstructorParameterDescription(_data.botVerification)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars)), ("disallowedGifts", ConstructorParameterDescription(_data.disallowedGifts)), ("starsRating", ConstructorParameterDescription(_data.starsRating)), ("starsMyPendingRating", ConstructorParameterDescription(_data.starsMyPendingRating)), ("starsMyPendingRatingDate", ConstructorParameterDescription(_data.starsMyPendingRatingDate)), ("mainTab", ConstructorParameterDescription(_data.mainTab)), ("savedMusic", ConstructorParameterDescription(_data.savedMusic)), ("note", ConstructorParameterDescription(_data.note)), ("botManagerId", ConstructorParameterDescription(_data.botManagerId))]) + } + } + + public static func parse_userFull(_ reader: BufferReader) -> UserFull? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Api.PeerSettings? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.PeerSettings + } + var _6: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 21) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _7: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _8: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 22) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _9: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + var _10: Api.BotInfo? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.BotInfo + } + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _11 = reader.readInt32() + } + var _12: Int32? + _12 = reader.readInt32() + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _13 = reader.readInt32() + } + var _14: Int32? + if Int(_1 ?? 0) & Int(1 << 14) != 0 { + _14 = reader.readInt32() + } + var _15: Api.ChatTheme? + if Int(_1 ?? 0) & Int(1 << 15) != 0 { + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.ChatTheme + } + } + var _16: String? + if Int(_1 ?? 0) & Int(1 << 16) != 0 { + _16 = parseString(reader) + } + var _17: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + var _18: Api.ChatAdminRights? + if Int(_1 ?? 0) & Int(1 << 18) != 0 { + if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights + } + } + var _19: Api.WallPaper? + if Int(_1 ?? 0) & Int(1 << 24) != 0 { + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + } + var _20: Api.PeerStories? + if Int(_1 ?? 0) & Int(1 << 25) != 0 { + if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.PeerStories + } + } + var _21: Api.BusinessWorkHours? + if Int(_2 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.BusinessWorkHours + } + } + var _22: Api.BusinessLocation? + if Int(_2 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _22 = Api.parse(reader, signature: signature) as? Api.BusinessLocation + } + } + var _23: Api.BusinessGreetingMessage? + if Int(_2 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _23 = Api.parse(reader, signature: signature) as? Api.BusinessGreetingMessage + } + } + var _24: Api.BusinessAwayMessage? + if Int(_2 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _24 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage + } + } + var _25: Api.BusinessIntro? + if Int(_2 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _25 = Api.parse(reader, signature: signature) as? Api.BusinessIntro + } + } + var _26: Api.Birthday? + if Int(_2 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _26 = Api.parse(reader, signature: signature) as? Api.Birthday + } + } + var _27: Int64? + if Int(_2 ?? 0) & Int(1 << 6) != 0 { + _27 = reader.readInt64() + } + var _28: Int32? + if Int(_2 ?? 0) & Int(1 << 6) != 0 { + _28 = reader.readInt32() + } + var _29: Int32? + if Int(_2 ?? 0) & Int(1 << 8) != 0 { + _29 = reader.readInt32() + } + var _30: Api.StarRefProgram? + if Int(_2 ?? 0) & Int(1 << 11) != 0 { + if let signature = reader.readInt32() { + _30 = Api.parse(reader, signature: signature) as? Api.StarRefProgram + } + } + var _31: Api.BotVerification? + if Int(_2 ?? 0) & Int(1 << 12) != 0 { + if let signature = reader.readInt32() { + _31 = Api.parse(reader, signature: signature) as? Api.BotVerification + } + } + var _32: Int64? + if Int(_2 ?? 0) & Int(1 << 14) != 0 { + _32 = reader.readInt64() + } + var _33: Api.DisallowedGiftsSettings? + if Int(_2 ?? 0) & Int(1 << 15) != 0 { + if let signature = reader.readInt32() { + _33 = Api.parse(reader, signature: signature) as? Api.DisallowedGiftsSettings + } + } + var _34: Api.StarsRating? + if Int(_2 ?? 0) & Int(1 << 17) != 0 { + if let signature = reader.readInt32() { + _34 = Api.parse(reader, signature: signature) as? Api.StarsRating + } + } + var _35: Api.StarsRating? + if Int(_2 ?? 0) & Int(1 << 18) != 0 { + if let signature = reader.readInt32() { + _35 = Api.parse(reader, signature: signature) as? Api.StarsRating + } + } + var _36: Int32? + if Int(_2 ?? 0) & Int(1 << 18) != 0 { + _36 = reader.readInt32() + } + var _37: Api.ProfileTab? + if Int(_2 ?? 0) & Int(1 << 20) != 0 { + if let signature = reader.readInt32() { + _37 = Api.parse(reader, signature: signature) as? Api.ProfileTab + } + } + var _38: Api.Document? + if Int(_2 ?? 0) & Int(1 << 21) != 0 { + if let signature = reader.readInt32() { + _38 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _39: Api.TextWithEntities? + if Int(_2 ?? 0) & Int(1 << 22) != 0 { + if let signature = reader.readInt32() { + _39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + var _40: Int64? + if Int(_2 ?? 0) & Int(1 << 25) != 0 { + _40 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 21) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 22) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 24) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _20 != nil + let _c21 = (Int(_2 ?? 0) & Int(1 << 0) == 0) || _21 != nil + let _c22 = (Int(_2 ?? 0) & Int(1 << 1) == 0) || _22 != nil + let _c23 = (Int(_2 ?? 0) & Int(1 << 2) == 0) || _23 != nil + let _c24 = (Int(_2 ?? 0) & Int(1 << 3) == 0) || _24 != nil + let _c25 = (Int(_2 ?? 0) & Int(1 << 4) == 0) || _25 != nil + let _c26 = (Int(_2 ?? 0) & Int(1 << 5) == 0) || _26 != nil + let _c27 = (Int(_2 ?? 0) & Int(1 << 6) == 0) || _27 != nil + let _c28 = (Int(_2 ?? 0) & Int(1 << 6) == 0) || _28 != nil + let _c29 = (Int(_2 ?? 0) & Int(1 << 8) == 0) || _29 != nil + let _c30 = (Int(_2 ?? 0) & Int(1 << 11) == 0) || _30 != nil + let _c31 = (Int(_2 ?? 0) & Int(1 << 12) == 0) || _31 != nil + let _c32 = (Int(_2 ?? 0) & Int(1 << 14) == 0) || _32 != nil + let _c33 = (Int(_2 ?? 0) & Int(1 << 15) == 0) || _33 != nil + let _c34 = (Int(_2 ?? 0) & Int(1 << 17) == 0) || _34 != nil + let _c35 = (Int(_2 ?? 0) & Int(1 << 18) == 0) || _35 != nil + let _c36 = (Int(_2 ?? 0) & Int(1 << 18) == 0) || _36 != nil + let _c37 = (Int(_2 ?? 0) & Int(1 << 20) == 0) || _37 != nil + let _c38 = (Int(_2 ?? 0) & Int(1 << 21) == 0) || _38 != nil + let _c39 = (Int(_2 ?? 0) & Int(1 << 22) == 0) || _39 != nil + let _c40 = (Int(_2 ?? 0) & Int(1 << 25) == 0) || _40 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 { + return Api.UserFull.userFull(Cons_userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39, botManagerId: _40)) + } + else { + return nil + } + } + } +} +public extension Api { + enum UserProfilePhoto: TypeConstructorDescription { + public class Cons_userProfilePhoto: TypeConstructorDescription { + public var flags: Int32 + public var photoId: Int64 + public var strippedThumb: Buffer? + public var dcId: Int32 + public init(flags: Int32, photoId: Int64, strippedThumb: Buffer?, dcId: Int32) { + self.flags = flags + self.photoId = photoId + self.strippedThumb = strippedThumb + self.dcId = dcId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userProfilePhoto", [("flags", ConstructorParameterDescription(self.flags)), ("photoId", ConstructorParameterDescription(self.photoId)), ("strippedThumb", ConstructorParameterDescription(self.strippedThumb)), ("dcId", ConstructorParameterDescription(self.dcId))]) + } + } + case userProfilePhoto(Cons_userProfilePhoto) + case userProfilePhotoEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userProfilePhoto(let _data): + if boxed { + buffer.appendInt32(-2100168954) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.photoId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeBytes(_data.strippedThumb!, buffer: buffer, boxed: false) + } + serializeInt32(_data.dcId, buffer: buffer, boxed: false) + break + case .userProfilePhotoEmpty: + if boxed { + buffer.appendInt32(1326562017) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .userProfilePhoto(let _data): + return ("userProfilePhoto", [("flags", ConstructorParameterDescription(_data.flags)), ("photoId", ConstructorParameterDescription(_data.photoId)), ("strippedThumb", ConstructorParameterDescription(_data.strippedThumb)), ("dcId", ConstructorParameterDescription(_data.dcId))]) + case .userProfilePhotoEmpty: + return ("userProfilePhotoEmpty", []) + } + } + + public static func parse_userProfilePhoto(_ reader: BufferReader) -> UserProfilePhoto? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = parseBytes(reader) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.UserProfilePhoto.userProfilePhoto(Cons_userProfilePhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!)) + } + else { + return nil + } + } + public static func parse_userProfilePhotoEmpty(_ reader: BufferReader) -> UserProfilePhoto? { + return Api.UserProfilePhoto.userProfilePhotoEmpty + } + } +} +public extension Api { + enum UserStatus: TypeConstructorDescription { + public class Cons_userStatusLastMonth: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userStatusLastMonth", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_userStatusLastWeek: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userStatusLastWeek", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_userStatusOffline: TypeConstructorDescription { + public var wasOnline: Int32 + public init(wasOnline: Int32) { + self.wasOnline = wasOnline + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userStatusOffline", [("wasOnline", ConstructorParameterDescription(self.wasOnline))]) + } + } + public class Cons_userStatusOnline: TypeConstructorDescription { + public var expires: Int32 + public init(expires: Int32) { + self.expires = expires + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userStatusOnline", [("expires", ConstructorParameterDescription(self.expires))]) + } + } + public class Cons_userStatusRecently: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userStatusRecently", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + case userStatusEmpty + case userStatusLastMonth(Cons_userStatusLastMonth) + case userStatusLastWeek(Cons_userStatusLastWeek) + case userStatusOffline(Cons_userStatusOffline) + case userStatusOnline(Cons_userStatusOnline) + case userStatusRecently(Cons_userStatusRecently) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userStatusEmpty: + if boxed { + buffer.appendInt32(164646985) + } + break + case .userStatusLastMonth(let _data): + if boxed { + buffer.appendInt32(1703516023) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .userStatusLastWeek(let _data): + if boxed { + buffer.appendInt32(1410997530) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .userStatusOffline(let _data): + if boxed { + buffer.appendInt32(9203775) + } + serializeInt32(_data.wasOnline, buffer: buffer, boxed: false) + break + case .userStatusOnline(let _data): + if boxed { + buffer.appendInt32(-306628279) + } + serializeInt32(_data.expires, buffer: buffer, boxed: false) + break + case .userStatusRecently(let _data): + if boxed { + buffer.appendInt32(2065268168) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .userStatusEmpty: + return ("userStatusEmpty", []) + case .userStatusLastMonth(let _data): + return ("userStatusLastMonth", [("flags", ConstructorParameterDescription(_data.flags))]) + case .userStatusLastWeek(let _data): + return ("userStatusLastWeek", [("flags", ConstructorParameterDescription(_data.flags))]) + case .userStatusOffline(let _data): + return ("userStatusOffline", [("wasOnline", ConstructorParameterDescription(_data.wasOnline))]) + case .userStatusOnline(let _data): + return ("userStatusOnline", [("expires", ConstructorParameterDescription(_data.expires))]) + case .userStatusRecently(let _data): + return ("userStatusRecently", [("flags", ConstructorParameterDescription(_data.flags))]) + } + } + + public static func parse_userStatusEmpty(_ reader: BufferReader) -> UserStatus? { + return Api.UserStatus.userStatusEmpty + } + public static func parse_userStatusLastMonth(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusLastMonth(Cons_userStatusLastMonth(flags: _1!)) + } + else { + return nil + } + } + public static func parse_userStatusLastWeek(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusLastWeek(Cons_userStatusLastWeek(flags: _1!)) + } + else { + return nil + } + } + public static func parse_userStatusOffline(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusOffline(Cons_userStatusOffline(wasOnline: _1!)) + } + else { + return nil + } + } + public static func parse_userStatusOnline(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusOnline(Cons_userStatusOnline(expires: _1!)) + } + else { + return nil + } + } + public static func parse_userStatusRecently(_ reader: BufferReader) -> UserStatus? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.UserStatus.userStatusRecently(Cons_userStatusRecently(flags: _1!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum Username: TypeConstructorDescription { + public class Cons_username: TypeConstructorDescription { + public var flags: Int32 + public var username: String + public init(flags: Int32, username: String) { + self.flags = flags + self.username = username + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("username", [("flags", ConstructorParameterDescription(self.flags)), ("username", ConstructorParameterDescription(self.username))]) + } + } + case username(Cons_username) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .username(let _data): + if boxed { + buffer.appendInt32(-1274595769) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.username, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .username(let _data): + return ("username", [("flags", ConstructorParameterDescription(_data.flags)), ("username", ConstructorParameterDescription(_data.username))]) + } + } + + public static func parse_username(_ reader: BufferReader) -> Username? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Username.username(Cons_username(flags: _1!, username: _2!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum VideoSize: TypeConstructorDescription { + public class Cons_videoSize: TypeConstructorDescription { + public var flags: Int32 + public var type: String + public var w: Int32 + public var h: Int32 + public var size: Int32 + public var videoStartTs: Double? + public init(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?) { + self.flags = flags + self.type = type + self.w = w + self.h = h + self.size = size + self.videoStartTs = videoStartTs + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("videoSize", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("w", ConstructorParameterDescription(self.w)), ("h", ConstructorParameterDescription(self.h)), ("size", ConstructorParameterDescription(self.size)), ("videoStartTs", ConstructorParameterDescription(self.videoStartTs))]) + } + } + public class Cons_videoSizeEmojiMarkup: TypeConstructorDescription { + public var emojiId: Int64 + public var backgroundColors: [Int32] + public init(emojiId: Int64, backgroundColors: [Int32]) { + self.emojiId = emojiId + self.backgroundColors = backgroundColors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("videoSizeEmojiMarkup", [("emojiId", ConstructorParameterDescription(self.emojiId)), ("backgroundColors", ConstructorParameterDescription(self.backgroundColors))]) + } + } + public class Cons_videoSizeStickerMarkup: TypeConstructorDescription { + public var stickerset: Api.InputStickerSet + public var stickerId: Int64 + public var backgroundColors: [Int32] + public init(stickerset: Api.InputStickerSet, stickerId: Int64, backgroundColors: [Int32]) { + self.stickerset = stickerset + self.stickerId = stickerId + self.backgroundColors = backgroundColors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("videoSizeStickerMarkup", [("stickerset", ConstructorParameterDescription(self.stickerset)), ("stickerId", ConstructorParameterDescription(self.stickerId)), ("backgroundColors", ConstructorParameterDescription(self.backgroundColors))]) + } + } + case videoSize(Cons_videoSize) + case videoSizeEmojiMarkup(Cons_videoSizeEmojiMarkup) + case videoSizeStickerMarkup(Cons_videoSizeStickerMarkup) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .videoSize(let _data): + if boxed { + buffer.appendInt32(-567037804) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.type, buffer: buffer, boxed: false) + serializeInt32(_data.w, buffer: buffer, boxed: false) + serializeInt32(_data.h, buffer: buffer, boxed: false) + serializeInt32(_data.size, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeDouble(_data.videoStartTs!, buffer: buffer, boxed: false) + } + break + case .videoSizeEmojiMarkup(let _data): + if boxed { + buffer.appendInt32(-128171716) + } + serializeInt64(_data.emojiId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.backgroundColors.count)) + for item in _data.backgroundColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .videoSizeStickerMarkup(let _data): + if boxed { + buffer.appendInt32(228623102) + } + _data.stickerset.serialize(buffer, true) + serializeInt64(_data.stickerId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.backgroundColors.count)) + for item in _data.backgroundColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .videoSize(let _data): + return ("videoSize", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("w", ConstructorParameterDescription(_data.w)), ("h", ConstructorParameterDescription(_data.h)), ("size", ConstructorParameterDescription(_data.size)), ("videoStartTs", ConstructorParameterDescription(_data.videoStartTs))]) + case .videoSizeEmojiMarkup(let _data): + return ("videoSizeEmojiMarkup", [("emojiId", ConstructorParameterDescription(_data.emojiId)), ("backgroundColors", ConstructorParameterDescription(_data.backgroundColors))]) + case .videoSizeStickerMarkup(let _data): + return ("videoSizeStickerMarkup", [("stickerset", ConstructorParameterDescription(_data.stickerset)), ("stickerId", ConstructorParameterDescription(_data.stickerId)), ("backgroundColors", ConstructorParameterDescription(_data.backgroundColors))]) + } + } + + public static func parse_videoSize(_ reader: BufferReader) -> VideoSize? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Double? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readDouble() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.VideoSize.videoSize(Cons_videoSize(flags: _1!, type: _2!, w: _3!, h: _4!, size: _5!, videoStartTs: _6)) + } + else { + return nil + } + } + public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.VideoSize.videoSizeEmojiMarkup(Cons_videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!)) + } + else { + return nil + } + } + public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? { + var _1: Api.InputStickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet + } + var _2: Int64? + _2 = reader.readInt64() + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.VideoSize.videoSizeStickerMarkup(Cons_videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum WallPaper: TypeConstructorDescription { + public class Cons_wallPaper: TypeConstructorDescription { + public var id: Int64 + public var flags: Int32 + public var accessHash: Int64 + public var slug: String + public var document: Api.Document + public var settings: Api.WallPaperSettings? + public init(id: Int64, flags: Int32, accessHash: Int64, slug: String, document: Api.Document, settings: Api.WallPaperSettings?) { + self.id = id + self.flags = flags + self.accessHash = accessHash + self.slug = slug + self.document = document + self.settings = settings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("wallPaper", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("slug", ConstructorParameterDescription(self.slug)), ("document", ConstructorParameterDescription(self.document)), ("settings", ConstructorParameterDescription(self.settings))]) + } + } + public class Cons_wallPaperNoFile: TypeConstructorDescription { + public var id: Int64 + public var flags: Int32 + public var settings: Api.WallPaperSettings? + public init(id: Int64, flags: Int32, settings: Api.WallPaperSettings?) { + self.id = id + self.flags = flags + self.settings = settings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("wallPaperNoFile", [("id", ConstructorParameterDescription(self.id)), ("flags", ConstructorParameterDescription(self.flags)), ("settings", ConstructorParameterDescription(self.settings))]) + } + } + case wallPaper(Cons_wallPaper) + case wallPaperNoFile(Cons_wallPaperNoFile) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .wallPaper(let _data): + if boxed { + buffer.appendInt32(-1539849235) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeString(_data.slug, buffer: buffer, boxed: false) + _data.document.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.settings!.serialize(buffer, true) + } + break + case .wallPaperNoFile(let _data): + if boxed { + buffer.appendInt32(-528465642) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.settings!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .wallPaper(let _data): + return ("wallPaper", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("slug", ConstructorParameterDescription(_data.slug)), ("document", ConstructorParameterDescription(_data.document)), ("settings", ConstructorParameterDescription(_data.settings))]) + case .wallPaperNoFile(let _data): + return ("wallPaperNoFile", [("id", ConstructorParameterDescription(_data.id)), ("flags", ConstructorParameterDescription(_data.flags)), ("settings", ConstructorParameterDescription(_data.settings))]) + } + } + + public static func parse_wallPaper(_ reader: BufferReader) -> WallPaper? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: Api.Document? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Document + } + var _6: Api.WallPaperSettings? + if Int(_2 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_2 ?? 0) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.WallPaper.wallPaper(Cons_wallPaper(id: _1!, flags: _2!, accessHash: _3!, slug: _4!, document: _5!, settings: _6)) + } + else { + return nil + } + } + public static func parse_wallPaperNoFile(_ reader: BufferReader) -> WallPaper? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.WallPaperSettings? + if Int(_2 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.WallPaperSettings + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_2 ?? 0) & Int(1 << 2) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WallPaper.wallPaperNoFile(Cons_wallPaperNoFile(id: _1!, flags: _2!, settings: _3)) + } + else { + return nil + } + } + } +} +public extension Api { + enum WallPaperSettings: TypeConstructorDescription { + public class Cons_wallPaperSettings: TypeConstructorDescription { + public var flags: Int32 + public var backgroundColor: Int32? + public var secondBackgroundColor: Int32? + public var thirdBackgroundColor: Int32? + public var fourthBackgroundColor: Int32? + public var intensity: Int32? + public var rotation: Int32? + public var emoticon: String? + public init(flags: Int32, backgroundColor: Int32?, secondBackgroundColor: Int32?, thirdBackgroundColor: Int32?, fourthBackgroundColor: Int32?, intensity: Int32?, rotation: Int32?, emoticon: String?) { + self.flags = flags + self.backgroundColor = backgroundColor + self.secondBackgroundColor = secondBackgroundColor + self.thirdBackgroundColor = thirdBackgroundColor + self.fourthBackgroundColor = fourthBackgroundColor + self.intensity = intensity + self.rotation = rotation + self.emoticon = emoticon + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("wallPaperSettings", [("flags", ConstructorParameterDescription(self.flags)), ("backgroundColor", ConstructorParameterDescription(self.backgroundColor)), ("secondBackgroundColor", ConstructorParameterDescription(self.secondBackgroundColor)), ("thirdBackgroundColor", ConstructorParameterDescription(self.thirdBackgroundColor)), ("fourthBackgroundColor", ConstructorParameterDescription(self.fourthBackgroundColor)), ("intensity", ConstructorParameterDescription(self.intensity)), ("rotation", ConstructorParameterDescription(self.rotation)), ("emoticon", ConstructorParameterDescription(self.emoticon))]) + } + } + case wallPaperSettings(Cons_wallPaperSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .wallPaperSettings(let _data): + if boxed { + buffer.appendInt32(925826256) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.backgroundColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.secondBackgroundColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.thirdBackgroundColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt32(_data.fourthBackgroundColor!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.intensity!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.rotation!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeString(_data.emoticon!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .wallPaperSettings(let _data): + return ("wallPaperSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("backgroundColor", ConstructorParameterDescription(_data.backgroundColor)), ("secondBackgroundColor", ConstructorParameterDescription(_data.secondBackgroundColor)), ("thirdBackgroundColor", ConstructorParameterDescription(_data.thirdBackgroundColor)), ("fourthBackgroundColor", ConstructorParameterDescription(_data.fourthBackgroundColor)), ("intensity", ConstructorParameterDescription(_data.intensity)), ("rotation", ConstructorParameterDescription(_data.rotation)), ("emoticon", ConstructorParameterDescription(_data.emoticon))]) + } + } + + public static func parse_wallPaperSettings(_ reader: BufferReader) -> WallPaperSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _6 = reader.readInt32() + } + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _7 = reader.readInt32() + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _8 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.WallPaperSettings.wallPaperSettings(Cons_wallPaperSettings(flags: _1!, backgroundColor: _2, secondBackgroundColor: _3, thirdBackgroundColor: _4, fourthBackgroundColor: _5, intensity: _6, rotation: _7, emoticon: _8)) + } + else { + return nil + } + } + } +} +public extension Api { + enum WebAuthorization: TypeConstructorDescription { + public class Cons_webAuthorization: TypeConstructorDescription { + public var hash: Int64 + public var botId: Int64 + public var domain: String + public var browser: String + public var platform: String + public var dateCreated: Int32 + public var dateActive: Int32 + public var ip: String + public var region: String + public init(hash: Int64, botId: Int64, domain: String, browser: String, platform: String, dateCreated: Int32, dateActive: Int32, ip: String, region: String) { + self.hash = hash + self.botId = botId + self.domain = domain + self.browser = browser + self.platform = platform + self.dateCreated = dateCreated + self.dateActive = dateActive + self.ip = ip + self.region = region + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webAuthorization", [("hash", ConstructorParameterDescription(self.hash)), ("botId", ConstructorParameterDescription(self.botId)), ("domain", ConstructorParameterDescription(self.domain)), ("browser", ConstructorParameterDescription(self.browser)), ("platform", ConstructorParameterDescription(self.platform)), ("dateCreated", ConstructorParameterDescription(self.dateCreated)), ("dateActive", ConstructorParameterDescription(self.dateActive)), ("ip", ConstructorParameterDescription(self.ip)), ("region", ConstructorParameterDescription(self.region))]) + } + } + case webAuthorization(Cons_webAuthorization) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webAuthorization(let _data): + if boxed { + buffer.appendInt32(-1493633966) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeString(_data.domain, buffer: buffer, boxed: false) + serializeString(_data.browser, buffer: buffer, boxed: false) + serializeString(_data.platform, buffer: buffer, boxed: false) + serializeInt32(_data.dateCreated, buffer: buffer, boxed: false) + serializeInt32(_data.dateActive, buffer: buffer, boxed: false) + serializeString(_data.ip, buffer: buffer, boxed: false) + serializeString(_data.region, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .webAuthorization(let _data): + return ("webAuthorization", [("hash", ConstructorParameterDescription(_data.hash)), ("botId", ConstructorParameterDescription(_data.botId)), ("domain", ConstructorParameterDescription(_data.domain)), ("browser", ConstructorParameterDescription(_data.browser)), ("platform", ConstructorParameterDescription(_data.platform)), ("dateCreated", ConstructorParameterDescription(_data.dateCreated)), ("dateActive", ConstructorParameterDescription(_data.dateActive)), ("ip", ConstructorParameterDescription(_data.ip)), ("region", ConstructorParameterDescription(_data.region))]) + } + } + + public static func parse_webAuthorization(_ reader: BufferReader) -> WebAuthorization? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: String? + _8 = parseString(reader) + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.WebAuthorization.webAuthorization(Cons_webAuthorization(hash: _1!, botId: _2!, domain: _3!, browser: _4!, platform: _5!, dateCreated: _6!, dateActive: _7!, ip: _8!, region: _9!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum WebDocument: TypeConstructorDescription { + public class Cons_webDocument: TypeConstructorDescription { + public var url: String + public var accessHash: Int64 + public var size: Int32 + public var mimeType: String + public var attributes: [Api.DocumentAttribute] + public init(url: String, accessHash: Int64, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) { + self.url = url + self.accessHash = accessHash + self.size = size + self.mimeType = mimeType + self.attributes = attributes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webDocument", [("url", ConstructorParameterDescription(self.url)), ("accessHash", ConstructorParameterDescription(self.accessHash)), ("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("attributes", ConstructorParameterDescription(self.attributes))]) + } + } + public class Cons_webDocumentNoProxy: TypeConstructorDescription { + public var url: String + public var size: Int32 + public var mimeType: String + public var attributes: [Api.DocumentAttribute] + public init(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) { + self.url = url + self.size = size + self.mimeType = mimeType + self.attributes = attributes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webDocumentNoProxy", [("url", ConstructorParameterDescription(self.url)), ("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("attributes", ConstructorParameterDescription(self.attributes))]) + } + } + case webDocument(Cons_webDocument) + case webDocumentNoProxy(Cons_webDocumentNoProxy) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webDocument(let _data): + if boxed { + buffer.appendInt32(475467473) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + serializeInt32(_data.size, buffer: buffer, boxed: false) + serializeString(_data.mimeType, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.attributes.count)) + for item in _data.attributes { + item.serialize(buffer, true) + } + break + case .webDocumentNoProxy(let _data): + if boxed { + buffer.appendInt32(-104284986) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt32(_data.size, buffer: buffer, boxed: false) + serializeString(_data.mimeType, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.attributes.count)) + for item in _data.attributes { + item.serialize(buffer, true) + } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .resolvedBusinessChatLinks(let _data): - return ("resolvedBusinessChatLinks", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .webDocument(let _data): + return ("webDocument", [("url", ConstructorParameterDescription(_data.url)), ("accessHash", ConstructorParameterDescription(_data.accessHash)), ("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("attributes", ConstructorParameterDescription(_data.attributes))]) + case .webDocumentNoProxy(let _data): + return ("webDocumentNoProxy", [("url", ConstructorParameterDescription(_data.url)), ("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("attributes", ConstructorParameterDescription(_data.attributes))]) } } - public static func parse_resolvedBusinessChatLinks(_ reader: BufferReader) -> ResolvedBusinessChatLinks? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _3: String? - _3 = parseString(reader) - var _4: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - } - var _5: [Api.Chat]? + public static func parse_webDocument(_ reader: BufferReader) -> WebDocument? { + var _1: String? + _1 = parseString(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: [Api.DocumentAttribute]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(Cons_resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!)) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.WebDocument.webDocument(Cons_webDocument(url: _1!, accessHash: _2!, size: _3!, mimeType: _4!, attributes: _5!)) + } + else { + return nil + } + } + public static func parse_webDocumentNoProxy(_ reader: BufferReader) -> WebDocument? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.DocumentAttribute]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DocumentAttribute.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebDocument.webDocumentNoProxy(Cons_webDocumentNoProxy(url: _1!, size: _2!, mimeType: _3!, attributes: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + enum WebDomainException: TypeConstructorDescription { + public class Cons_webDomainException: TypeConstructorDescription { + public var flags: Int32 + public var domain: String + public var url: String + public var title: String + public var favicon: Int64? + public init(flags: Int32, domain: String, url: String, title: String, favicon: Int64?) { + self.flags = flags + self.domain = domain + self.url = url + self.title = title + self.favicon = favicon + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webDomainException", [("flags", ConstructorParameterDescription(self.flags)), ("domain", ConstructorParameterDescription(self.domain)), ("url", ConstructorParameterDescription(self.url)), ("title", ConstructorParameterDescription(self.title)), ("favicon", ConstructorParameterDescription(self.favicon))]) + } + } + case webDomainException(Cons_webDomainException) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webDomainException(let _data): + if boxed { + buffer.appendInt32(-1824741993) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.domain, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.favicon!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .webDomainException(let _data): + return ("webDomainException", [("flags", ConstructorParameterDescription(_data.flags)), ("domain", ConstructorParameterDescription(_data.domain)), ("url", ConstructorParameterDescription(_data.url)), ("title", ConstructorParameterDescription(_data.title)), ("favicon", ConstructorParameterDescription(_data.favicon))]) + } + } + + public static func parse_webDomainException(_ reader: BufferReader) -> WebDomainException? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.WebDomainException.webDomainException(Cons_webDomainException(flags: _1!, domain: _2!, url: _3!, title: _4!, favicon: _5)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index 46d8b246c9..9f01fe1ae2 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -1,170 +1,579 @@ -public extension Api.account { - enum SavedMusicIds: TypeConstructorDescription { - public class Cons_savedMusicIds: TypeConstructorDescription { - public var ids: [Int64] - public init(ids: [Int64]) { - self.ids = ids - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedMusicIds", [("ids", ConstructorParameterDescription(self.ids))]) - } - } - case savedMusicIds(Cons_savedMusicIds) - case savedMusicIdsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedMusicIds(let _data): - if boxed { - buffer.appendInt32(-1718786506) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.ids.count)) - for item in _data.ids { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .savedMusicIdsNotModified: - if boxed { - buffer.appendInt32(1338514798) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedMusicIds(let _data): - return ("savedMusicIds", [("ids", ConstructorParameterDescription(_data.ids))]) - case .savedMusicIdsNotModified: - return ("savedMusicIdsNotModified", []) - } - } - - public static func parse_savedMusicIds(_ reader: BufferReader) -> SavedMusicIds? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.account.SavedMusicIds.savedMusicIds(Cons_savedMusicIds(ids: _1!)) - } - else { - return nil - } - } - public static func parse_savedMusicIdsNotModified(_ reader: BufferReader) -> SavedMusicIds? { - return Api.account.SavedMusicIds.savedMusicIdsNotModified - } - } -} -public extension Api.account { - enum SavedRingtone: TypeConstructorDescription { - public class Cons_savedRingtoneConverted: TypeConstructorDescription { - public var document: Api.Document - public init(document: Api.Document) { - self.document = document - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedRingtoneConverted", [("document", ConstructorParameterDescription(self.document))]) - } - } - case savedRingtone - case savedRingtoneConverted(Cons_savedRingtoneConverted) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedRingtone: - if boxed { - buffer.appendInt32(-1222230163) - } - break - case .savedRingtoneConverted(let _data): - if boxed { - buffer.appendInt32(523271863) - } - _data.document.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedRingtone: - return ("savedRingtone", []) - case .savedRingtoneConverted(let _data): - return ("savedRingtoneConverted", [("document", ConstructorParameterDescription(_data.document))]) - } - } - - public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? { - return Api.account.SavedRingtone.savedRingtone - } - public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? { - var _1: Api.Document? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Document - } - let _c1 = _1 != nil - if _c1 { - return Api.account.SavedRingtone.savedRingtoneConverted(Cons_savedRingtoneConverted(document: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.account { - enum SavedRingtones: TypeConstructorDescription { - public class Cons_savedRingtones: TypeConstructorDescription { - public var hash: Int64 - public var ringtones: [Api.Document] - public init(hash: Int64, ringtones: [Api.Document]) { +public extension Api { + enum WebPage: TypeConstructorDescription { + public class Cons_webPage: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var url: String + public var displayUrl: String + public var hash: Int32 + public var type: String? + public var siteName: String? + public var title: String? + public var description: String? + public var photo: Api.Photo? + public var embedUrl: String? + public var embedType: String? + public var embedWidth: Int32? + public var embedHeight: Int32? + public var duration: Int32? + public var author: String? + public var document: Api.Document? + public var cachedPage: Api.Page? + public var attributes: [Api.WebPageAttribute]? + public init(flags: Int32, id: Int64, url: String, displayUrl: String, hash: Int32, type: String?, siteName: String?, title: String?, description: String?, photo: Api.Photo?, embedUrl: String?, embedType: String?, embedWidth: Int32?, embedHeight: Int32?, duration: Int32?, author: String?, document: Api.Document?, cachedPage: Api.Page?, attributes: [Api.WebPageAttribute]?) { + self.flags = flags + self.id = id + self.url = url + self.displayUrl = displayUrl self.hash = hash - self.ringtones = ringtones + self.type = type + self.siteName = siteName + self.title = title + self.description = description + self.photo = photo + self.embedUrl = embedUrl + self.embedType = embedType + self.embedWidth = embedWidth + self.embedHeight = embedHeight + self.duration = duration + self.author = author + self.document = document + self.cachedPage = cachedPage + self.attributes = attributes } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedRingtones", [("hash", ConstructorParameterDescription(self.hash)), ("ringtones", ConstructorParameterDescription(self.ringtones))]) + return ("webPage", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url)), ("displayUrl", ConstructorParameterDescription(self.displayUrl)), ("hash", ConstructorParameterDescription(self.hash)), ("type", ConstructorParameterDescription(self.type)), ("siteName", ConstructorParameterDescription(self.siteName)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("embedUrl", ConstructorParameterDescription(self.embedUrl)), ("embedType", ConstructorParameterDescription(self.embedType)), ("embedWidth", ConstructorParameterDescription(self.embedWidth)), ("embedHeight", ConstructorParameterDescription(self.embedHeight)), ("duration", ConstructorParameterDescription(self.duration)), ("author", ConstructorParameterDescription(self.author)), ("document", ConstructorParameterDescription(self.document)), ("cachedPage", ConstructorParameterDescription(self.cachedPage)), ("attributes", ConstructorParameterDescription(self.attributes))]) } } - case savedRingtones(Cons_savedRingtones) - case savedRingtonesNotModified + public class Cons_webPageEmpty: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var url: String? + public init(flags: Int32, id: Int64, url: String?) { + self.flags = flags + self.id = id + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url))]) + } + } + public class Cons_webPageNotModified: TypeConstructorDescription { + public var flags: Int32 + public var cachedPageViews: Int32? + public init(flags: Int32, cachedPageViews: Int32?) { + self.flags = flags + self.cachedPageViews = cachedPageViews + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("cachedPageViews", ConstructorParameterDescription(self.cachedPageViews))]) + } + } + public class Cons_webPagePending: TypeConstructorDescription { + public var flags: Int32 + public var id: Int64 + public var url: String? + public var date: Int32 + public init(flags: Int32, id: Int64, url: String?, date: Int32) { + self.flags = flags + self.id = id + self.url = url + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPagePending", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("url", ConstructorParameterDescription(self.url)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case webPage(Cons_webPage) + case webPageEmpty(Cons_webPageEmpty) + case webPageNotModified(Cons_webPageNotModified) + case webPagePending(Cons_webPagePending) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedRingtones(let _data): + case .webPage(let _data): if boxed { - buffer.appendInt32(-1041683259) + buffer.appendInt32(-392411726) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + serializeString(_data.displayUrl, buffer: buffer, boxed: false) + serializeInt32(_data.hash, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.type!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.siteName!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.title!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.description!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.photo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.embedUrl!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.embedType!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt32(_data.embedWidth!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeInt32(_data.embedHeight!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 7) != 0 { + serializeInt32(_data.duration!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 8) != 0 { + serializeString(_data.author!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.document!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 10) != 0 { + _data.cachedPage!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 12) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.attributes!.count)) + for item in _data.attributes! { + item.serialize(buffer, true) + } + } + break + case .webPageEmpty(let _data): + if boxed { + buffer.appendInt32(555358088) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + break + case .webPageNotModified(let _data): + if boxed { + buffer.appendInt32(1930545681) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.cachedPageViews!, buffer: buffer, boxed: false) + } + break + case .webPagePending(let _data): + if boxed { + buffer.appendInt32(-1328464313) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .webPage(let _data): + return ("webPage", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url)), ("displayUrl", ConstructorParameterDescription(_data.displayUrl)), ("hash", ConstructorParameterDescription(_data.hash)), ("type", ConstructorParameterDescription(_data.type)), ("siteName", ConstructorParameterDescription(_data.siteName)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("embedUrl", ConstructorParameterDescription(_data.embedUrl)), ("embedType", ConstructorParameterDescription(_data.embedType)), ("embedWidth", ConstructorParameterDescription(_data.embedWidth)), ("embedHeight", ConstructorParameterDescription(_data.embedHeight)), ("duration", ConstructorParameterDescription(_data.duration)), ("author", ConstructorParameterDescription(_data.author)), ("document", ConstructorParameterDescription(_data.document)), ("cachedPage", ConstructorParameterDescription(_data.cachedPage)), ("attributes", ConstructorParameterDescription(_data.attributes))]) + case .webPageEmpty(let _data): + return ("webPageEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url))]) + case .webPageNotModified(let _data): + return ("webPageNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("cachedPageViews", ConstructorParameterDescription(_data.cachedPageViews))]) + case .webPagePending(let _data): + return ("webPagePending", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("url", ConstructorParameterDescription(_data.url)), ("date", ConstructorParameterDescription(_data.date))]) + } + } + + public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) + } + var _8: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _8 = parseString(reader) + } + var _9: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _9 = parseString(reader) + } + var _10: Api.Photo? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.Photo + } + } + var _11: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _11 = parseString(reader) + } + var _12: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _12 = parseString(reader) + } + var _13: Int32? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _13 = reader.readInt32() + } + var _14: Int32? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _14 = reader.readInt32() + } + var _15: Int32? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + _15 = reader.readInt32() + } + var _16: String? + if Int(_1 ?? 0) & Int(1 << 8) != 0 { + _16 = parseString(reader) + } + var _17: Api.Document? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _18: Api.Page? + if Int(_1 ?? 0) & Int(1 << 10) != 0 { + if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.Page + } + } + var _19: [Api.WebPageAttribute]? + if Int(_1 ?? 0) & Int(1 << 12) != 0 { + if let _ = reader.readInt32() { + _19 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebPageAttribute.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.WebPage.webPage(Cons_webPage(flags: _1!, id: _2!, url: _3!, displayUrl: _4!, hash: _5!, type: _6, siteName: _7, title: _8, description: _9, photo: _10, embedUrl: _11, embedType: _12, embedWidth: _13, embedHeight: _14, duration: _15, author: _16, document: _17, cachedPage: _18, attributes: _19)) + } + else { + return nil + } + } + public static func parse_webPageEmpty(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPage.webPageEmpty(Cons_webPageEmpty(flags: _1!, id: _2!, url: _3)) + } + else { + return nil + } + } + public static func parse_webPageNotModified(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.WebPage.webPageNotModified(Cons_webPageNotModified(flags: _1!, cachedPageViews: _2)) + } + else { + return nil + } + } + public static func parse_webPagePending(_ reader: BufferReader) -> WebPage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPage.webPagePending(Cons_webPagePending(flags: _1!, id: _2!, url: _3, date: _4!)) + } + else { + return nil + } + } + } +} +public extension Api { + indirect enum WebPageAttribute: TypeConstructorDescription { + public class Cons_webPageAttributeAiComposeTone: TypeConstructorDescription { + public var emojiId: Int64 + public init(emojiId: Int64) { + self.emojiId = emojiId + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeAiComposeTone", [("emojiId", ConstructorParameterDescription(self.emojiId))]) + } + } + public class Cons_webPageAttributeStarGiftAuction: TypeConstructorDescription { + public var gift: Api.StarGift + public var endDate: Int32 + public init(gift: Api.StarGift, endDate: Int32) { + self.gift = gift + self.endDate = endDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeStarGiftAuction", [("gift", ConstructorParameterDescription(self.gift)), ("endDate", ConstructorParameterDescription(self.endDate))]) + } + } + public class Cons_webPageAttributeStarGiftCollection: TypeConstructorDescription { + public var icons: [Api.Document] + public init(icons: [Api.Document]) { + self.icons = icons + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeStarGiftCollection", [("icons", ConstructorParameterDescription(self.icons))]) + } + } + public class Cons_webPageAttributeStickerSet: TypeConstructorDescription { + public var flags: Int32 + public var stickers: [Api.Document] + public init(flags: Int32, stickers: [Api.Document]) { + self.flags = flags + self.stickers = stickers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeStickerSet", [("flags", ConstructorParameterDescription(self.flags)), ("stickers", ConstructorParameterDescription(self.stickers))]) + } + } + public class Cons_webPageAttributeStory: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var id: Int32 + public var story: Api.StoryItem? + public init(flags: Int32, peer: Api.Peer, id: Int32, story: Api.StoryItem?) { + self.flags = flags + self.peer = peer + self.id = id + self.story = story + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeStory", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("id", ConstructorParameterDescription(self.id)), ("story", ConstructorParameterDescription(self.story))]) + } + } + public class Cons_webPageAttributeTheme: TypeConstructorDescription { + public var flags: Int32 + public var documents: [Api.Document]? + public var settings: Api.ThemeSettings? + public init(flags: Int32, documents: [Api.Document]?, settings: Api.ThemeSettings?) { + self.flags = flags + self.documents = documents + self.settings = settings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeTheme", [("flags", ConstructorParameterDescription(self.flags)), ("documents", ConstructorParameterDescription(self.documents)), ("settings", ConstructorParameterDescription(self.settings))]) + } + } + public class Cons_webPageAttributeUniqueStarGift: TypeConstructorDescription { + public var gift: Api.StarGift + public init(gift: Api.StarGift) { + self.gift = gift + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webPageAttributeUniqueStarGift", [("gift", ConstructorParameterDescription(self.gift))]) + } + } + case webPageAttributeAiComposeTone(Cons_webPageAttributeAiComposeTone) + case webPageAttributeStarGiftAuction(Cons_webPageAttributeStarGiftAuction) + case webPageAttributeStarGiftCollection(Cons_webPageAttributeStarGiftCollection) + case webPageAttributeStickerSet(Cons_webPageAttributeStickerSet) + case webPageAttributeStory(Cons_webPageAttributeStory) + case webPageAttributeTheme(Cons_webPageAttributeTheme) + case webPageAttributeUniqueStarGift(Cons_webPageAttributeUniqueStarGift) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webPageAttributeAiComposeTone(let _data): + if boxed { + buffer.appendInt32(2005007896) + } + serializeInt64(_data.emojiId, buffer: buffer, boxed: false) + break + case .webPageAttributeStarGiftAuction(let _data): + if boxed { + buffer.appendInt32(29770178) + } + _data.gift.serialize(buffer, true) + serializeInt32(_data.endDate, buffer: buffer, boxed: false) + break + case .webPageAttributeStarGiftCollection(let _data): + if boxed { + buffer.appendInt32(835375875) } - serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.ringtones.count)) - for item in _data.ringtones { + buffer.appendInt32(Int32(_data.icons.count)) + for item in _data.icons { item.serialize(buffer, true) } break - case .savedRingtonesNotModified: + case .webPageAttributeStickerSet(let _data): if boxed { - buffer.appendInt32(-67704655) + buffer.appendInt32(1355547603) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stickers.count)) + for item in _data.stickers { + item.serialize(buffer, true) + } + break + case .webPageAttributeStory(let _data): + if boxed { + buffer.appendInt32(781501415) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeInt32(_data.id, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.story!.serialize(buffer, true) + } + break + case .webPageAttributeTheme(let _data): + if boxed { + buffer.appendInt32(1421174295) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents!.count)) + for item in _data.documents! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.settings!.serialize(buffer, true) + } + break + case .webPageAttributeUniqueStarGift(let _data): + if boxed { + buffer.appendInt32(-814781000) + } + _data.gift.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .savedRingtones(let _data): - return ("savedRingtones", [("hash", ConstructorParameterDescription(_data.hash)), ("ringtones", ConstructorParameterDescription(_data.ringtones))]) - case .savedRingtonesNotModified: - return ("savedRingtonesNotModified", []) + case .webPageAttributeAiComposeTone(let _data): + return ("webPageAttributeAiComposeTone", [("emojiId", ConstructorParameterDescription(_data.emojiId))]) + case .webPageAttributeStarGiftAuction(let _data): + return ("webPageAttributeStarGiftAuction", [("gift", ConstructorParameterDescription(_data.gift)), ("endDate", ConstructorParameterDescription(_data.endDate))]) + case .webPageAttributeStarGiftCollection(let _data): + return ("webPageAttributeStarGiftCollection", [("icons", ConstructorParameterDescription(_data.icons))]) + case .webPageAttributeStickerSet(let _data): + return ("webPageAttributeStickerSet", [("flags", ConstructorParameterDescription(_data.flags)), ("stickers", ConstructorParameterDescription(_data.stickers))]) + case .webPageAttributeStory(let _data): + return ("webPageAttributeStory", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("id", ConstructorParameterDescription(_data.id)), ("story", ConstructorParameterDescription(_data.story))]) + case .webPageAttributeTheme(let _data): + return ("webPageAttributeTheme", [("flags", ConstructorParameterDescription(_data.flags)), ("documents", ConstructorParameterDescription(_data.documents)), ("settings", ConstructorParameterDescription(_data.settings))]) + case .webPageAttributeUniqueStarGift(let _data): + return ("webPageAttributeUniqueStarGift", [("gift", ConstructorParameterDescription(_data.gift))]) } } - public static func parse_savedRingtones(_ reader: BufferReader) -> SavedRingtones? { + public static func parse_webPageAttributeAiComposeTone(_ reader: BufferReader) -> WebPageAttribute? { var _1: Int64? _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.WebPageAttribute.webPageAttributeAiComposeTone(Cons_webPageAttributeAiComposeTone(emojiId: _1!)) + } + else { + return nil + } + } + public static func parse_webPageAttributeStarGiftAuction(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.WebPageAttribute.webPageAttributeStarGiftAuction(Cons_webPageAttributeStarGiftAuction(gift: _1!, endDate: _2!)) + } + else { + return nil + } + } + public static func parse_webPageAttributeStarGiftCollection(_ reader: BufferReader) -> WebPageAttribute? { + var _1: [Api.Document]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.WebPageAttribute.webPageAttributeStarGiftCollection(Cons_webPageAttributeStarGiftCollection(icons: _1!)) + } + else { + return nil + } + } + public static func parse_webPageAttributeStickerSet(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() var _2: [Api.Document]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) @@ -172,104 +581,71 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.account.SavedRingtones.savedRingtones(Cons_savedRingtones(hash: _1!, ringtones: _2!)) + return Api.WebPageAttribute.webPageAttributeStickerSet(Cons_webPageAttributeStickerSet(flags: _1!, stickers: _2!)) } else { return nil } } - public static func parse_savedRingtonesNotModified(_ reader: BufferReader) -> SavedRingtones? { - return Api.account.SavedRingtones.savedRingtonesNotModified - } - } -} -public extension Api.account { - enum SentEmailCode: TypeConstructorDescription { - public class Cons_sentEmailCode: TypeConstructorDescription { - public var emailPattern: String - public var length: Int32 - public init(emailPattern: String, length: Int32) { - self.emailPattern = emailPattern - self.length = length + public static func parse_webPageAttributeStory(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentEmailCode", [("emailPattern", ConstructorParameterDescription(self.emailPattern)), ("length", ConstructorParameterDescription(self.length))]) - } - } - case sentEmailCode(Cons_sentEmailCode) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sentEmailCode(let _data): - if boxed { - buffer.appendInt32(-2128640689) + var _3: Int32? + _3 = reader.readInt32() + var _4: Api.StoryItem? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StoryItem } - serializeString(_data.emailPattern, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sentEmailCode(let _data): - return ("sentEmailCode", [("emailPattern", ConstructorParameterDescription(_data.emailPattern)), ("length", ConstructorParameterDescription(_data.length))]) - } - } - - public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.SentEmailCode.sentEmailCode(Cons_sentEmailCode(emailPattern: _1!, length: _2!)) + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.WebPageAttribute.webPageAttributeStory(Cons_webPageAttributeStory(flags: _1!, peer: _2!, id: _3!, story: _4)) } else { return nil } } - } -} -public extension Api.account { - enum Takeout: TypeConstructorDescription { - public class Cons_takeout: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("takeout", [("id", ConstructorParameterDescription(self.id))]) - } - } - case takeout(Cons_takeout) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .takeout(let _data): - if boxed { - buffer.appendInt32(1304052993) + public static func parse_webPageAttributeTheme(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Document]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - serializeInt64(_data.id, buffer: buffer, boxed: false) - break + } + var _3: Api.ThemeSettings? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.ThemeSettings + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebPageAttribute.webPageAttributeTheme(Cons_webPageAttributeTheme(flags: _1!, documents: _2, settings: _3)) + } + else { + return nil } } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .takeout(let _data): - return ("takeout", [("id", ConstructorParameterDescription(_data.id))]) + public static func parse_webPageAttributeUniqueStarGift(_ reader: BufferReader) -> WebPageAttribute? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift } - } - - public static func parse_takeout(_ reader: BufferReader) -> Takeout? { - var _1: Int64? - _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.account.Takeout.takeout(Cons_takeout(id: _1!)) + return Api.WebPageAttribute.webPageAttributeUniqueStarGift(Cons_webPageAttributeUniqueStarGift(gift: _1!)) } else { return nil @@ -277,38 +653,30 @@ public extension Api.account { } } } -public extension Api.account { - enum Themes: TypeConstructorDescription { - public class Cons_themes: TypeConstructorDescription { - public var hash: Int64 - public var themes: [Api.Theme] - public init(hash: Int64, themes: [Api.Theme]) { - self.hash = hash - self.themes = themes +public extension Api { + enum WebViewMessageSent: TypeConstructorDescription { + public class Cons_webViewMessageSent: TypeConstructorDescription { + public var flags: Int32 + public var msgId: Api.InputBotInlineMessageID? + public init(flags: Int32, msgId: Api.InputBotInlineMessageID?) { + self.flags = flags + self.msgId = msgId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("themes", [("hash", ConstructorParameterDescription(self.hash)), ("themes", ConstructorParameterDescription(self.themes))]) + return ("webViewMessageSent", [("flags", ConstructorParameterDescription(self.flags)), ("msgId", ConstructorParameterDescription(self.msgId))]) } } - case themes(Cons_themes) - case themesNotModified + case webViewMessageSent(Cons_webViewMessageSent) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .themes(let _data): + case .webViewMessageSent(let _data): if boxed { - buffer.appendInt32(-1707242387) + buffer.appendInt32(211046684) } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.themes.count)) - for item in _data.themes { - item.serialize(buffer, true) - } - break - case .themesNotModified: - if boxed { - buffer.appendInt32(-199313886) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.msgId!.serialize(buffer, true) } break } @@ -316,77 +684,84 @@ public extension Api.account { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .themes(let _data): - return ("themes", [("hash", ConstructorParameterDescription(_data.hash)), ("themes", ConstructorParameterDescription(_data.themes))]) - case .themesNotModified: - return ("themesNotModified", []) + case .webViewMessageSent(let _data): + return ("webViewMessageSent", [("flags", ConstructorParameterDescription(_data.flags)), ("msgId", ConstructorParameterDescription(_data.msgId))]) } } - public static func parse_themes(_ reader: BufferReader) -> Themes? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Theme]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) + public static func parse_webViewMessageSent(_ reader: BufferReader) -> WebViewMessageSent? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputBotInlineMessageID? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.account.Themes.themes(Cons_themes(hash: _1!, themes: _2!)) + return Api.WebViewMessageSent.webViewMessageSent(Cons_webViewMessageSent(flags: _1!, msgId: _2)) } else { return nil } } - public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { - return Api.account.Themes.themesNotModified - } } } -public extension Api.account { - enum TmpPassword: TypeConstructorDescription { - public class Cons_tmpPassword: TypeConstructorDescription { - public var tmpPassword: Buffer - public var validUntil: Int32 - public init(tmpPassword: Buffer, validUntil: Int32) { - self.tmpPassword = tmpPassword - self.validUntil = validUntil +public extension Api { + enum WebViewResult: TypeConstructorDescription { + public class Cons_webViewResultUrl: TypeConstructorDescription { + public var flags: Int32 + public var queryId: Int64? + public var url: String + public init(flags: Int32, queryId: Int64?, url: String) { + self.flags = flags + self.queryId = queryId + self.url = url } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("tmpPassword", [("tmpPassword", ConstructorParameterDescription(self.tmpPassword)), ("validUntil", ConstructorParameterDescription(self.validUntil))]) + return ("webViewResultUrl", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("url", ConstructorParameterDescription(self.url))]) } } - case tmpPassword(Cons_tmpPassword) + case webViewResultUrl(Cons_webViewResultUrl) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .tmpPassword(let _data): + case .webViewResultUrl(let _data): if boxed { - buffer.appendInt32(-614138572) + buffer.appendInt32(1294139288) } - serializeBytes(_data.tmpPassword, buffer: buffer, boxed: false) - serializeInt32(_data.validUntil, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.queryId!, buffer: buffer, boxed: false) + } + serializeString(_data.url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .tmpPassword(let _data): - return ("tmpPassword", [("tmpPassword", ConstructorParameterDescription(_data.tmpPassword)), ("validUntil", ConstructorParameterDescription(_data.validUntil))]) + case .webViewResultUrl(let _data): + return ("webViewResultUrl", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("url", ConstructorParameterDescription(_data.url))]) } } - public static func parse_tmpPassword(_ reader: BufferReader) -> TmpPassword? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() + public static func parse_webViewResultUrl(_ reader: BufferReader) -> WebViewResult? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt64() + } + var _3: String? + _3 = parseString(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.TmpPassword.tmpPassword(Cons_tmpPassword(tmpPassword: _1!, validUntil: _2!)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.WebViewResult.webViewResultUrl(Cons_webViewResultUrl(flags: _1!, queryId: _2, url: _3!)) } else { return nil @@ -395,98 +770,267 @@ public extension Api.account { } } public extension Api.account { - enum WallPapers: TypeConstructorDescription { - public class Cons_wallPapers: TypeConstructorDescription { - public var hash: Int64 - public var wallpapers: [Api.WallPaper] - public init(hash: Int64, wallpapers: [Api.WallPaper]) { - self.hash = hash - self.wallpapers = wallpapers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("wallPapers", [("hash", ConstructorParameterDescription(self.hash)), ("wallpapers", ConstructorParameterDescription(self.wallpapers))]) - } - } - case wallPapers(Cons_wallPapers) - case wallPapersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .wallPapers(let _data): - if boxed { - buffer.appendInt32(-842824308) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.wallpapers.count)) - for item in _data.wallpapers { - item.serialize(buffer, true) - } - break - case .wallPapersNotModified: - if boxed { - buffer.appendInt32(471437699) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .wallPapers(let _data): - return ("wallPapers", [("hash", ConstructorParameterDescription(_data.hash)), ("wallpapers", ConstructorParameterDescription(_data.wallpapers))]) - case .wallPapersNotModified: - return ("wallPapersNotModified", []) - } - } - - public static func parse_wallPapers(_ reader: BufferReader) -> WallPapers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.WallPaper]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.account.WallPapers.wallPapers(Cons_wallPapers(hash: _1!, wallpapers: _2!)) - } - else { - return nil - } - } - public static func parse_wallPapersNotModified(_ reader: BufferReader) -> WallPapers? { - return Api.account.WallPapers.wallPapersNotModified - } - } -} -public extension Api.account { - enum WebAuthorizations: TypeConstructorDescription { - public class Cons_webAuthorizations: TypeConstructorDescription { - public var authorizations: [Api.WebAuthorization] + enum AuthorizationForm: TypeConstructorDescription { + public class Cons_authorizationForm: TypeConstructorDescription { + public var flags: Int32 + public var requiredTypes: [Api.SecureRequiredType] + public var values: [Api.SecureValue] + public var errors: [Api.SecureValueError] public var users: [Api.User] - public init(authorizations: [Api.WebAuthorization], users: [Api.User]) { - self.authorizations = authorizations + public var privacyPolicyUrl: String? + public init(flags: Int32, requiredTypes: [Api.SecureRequiredType], values: [Api.SecureValue], errors: [Api.SecureValueError], users: [Api.User], privacyPolicyUrl: String?) { + self.flags = flags + self.requiredTypes = requiredTypes + self.values = values + self.errors = errors self.users = users + self.privacyPolicyUrl = privacyPolicyUrl } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webAuthorizations", [("authorizations", ConstructorParameterDescription(self.authorizations)), ("users", ConstructorParameterDescription(self.users))]) + return ("authorizationForm", [("flags", ConstructorParameterDescription(self.flags)), ("requiredTypes", ConstructorParameterDescription(self.requiredTypes)), ("values", ConstructorParameterDescription(self.values)), ("errors", ConstructorParameterDescription(self.errors)), ("users", ConstructorParameterDescription(self.users)), ("privacyPolicyUrl", ConstructorParameterDescription(self.privacyPolicyUrl))]) } } - case webAuthorizations(Cons_webAuthorizations) + case authorizationForm(Cons_authorizationForm) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webAuthorizations(let _data): + case .authorizationForm(let _data): if boxed { - buffer.appendInt32(-313079300) + buffer.appendInt32(-1389486888) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.requiredTypes.count)) + for item in _data.requiredTypes { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.values.count)) + for item in _data.values { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.errors.count)) + for item in _data.errors { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.privacyPolicyUrl!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .authorizationForm(let _data): + return ("authorizationForm", [("flags", ConstructorParameterDescription(_data.flags)), ("requiredTypes", ConstructorParameterDescription(_data.requiredTypes)), ("values", ConstructorParameterDescription(_data.values)), ("errors", ConstructorParameterDescription(_data.errors)), ("users", ConstructorParameterDescription(_data.users)), ("privacyPolicyUrl", ConstructorParameterDescription(_data.privacyPolicyUrl))]) + } + } + + public static func parse_authorizationForm(_ reader: BufferReader) -> AuthorizationForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SecureRequiredType]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureRequiredType.self) + } + var _3: [Api.SecureValue]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + var _4: [Api.SecureValueError]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValueError.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.AuthorizationForm.authorizationForm(Cons_authorizationForm(flags: _1!, requiredTypes: _2!, values: _3!, errors: _4!, users: _5!, privacyPolicyUrl: _6)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum Authorizations: TypeConstructorDescription { + public class Cons_authorizations: TypeConstructorDescription { + public var authorizationTtlDays: Int32 + public var authorizations: [Api.Authorization] + public init(authorizationTtlDays: Int32, authorizations: [Api.Authorization]) { + self.authorizationTtlDays = authorizationTtlDays + self.authorizations = authorizations + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("authorizations", [("authorizationTtlDays", ConstructorParameterDescription(self.authorizationTtlDays)), ("authorizations", ConstructorParameterDescription(self.authorizations))]) + } + } + case authorizations(Cons_authorizations) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .authorizations(let _data): + if boxed { + buffer.appendInt32(1275039392) + } + serializeInt32(_data.authorizationTtlDays, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.authorizations.count)) for item in _data.authorizations { item.serialize(buffer, true) } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .authorizations(let _data): + return ("authorizations", [("authorizationTtlDays", ConstructorParameterDescription(_data.authorizationTtlDays)), ("authorizations", ConstructorParameterDescription(_data.authorizations))]) + } + } + + public static func parse_authorizations(_ reader: BufferReader) -> Authorizations? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Authorization]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Authorization.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.Authorizations.authorizations(Cons_authorizations(authorizationTtlDays: _1!, authorizations: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum AutoDownloadSettings: TypeConstructorDescription { + public class Cons_autoDownloadSettings: TypeConstructorDescription { + public var low: Api.AutoDownloadSettings + public var medium: Api.AutoDownloadSettings + public var high: Api.AutoDownloadSettings + public init(low: Api.AutoDownloadSettings, medium: Api.AutoDownloadSettings, high: Api.AutoDownloadSettings) { + self.low = low + self.medium = medium + self.high = high + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("autoDownloadSettings", [("low", ConstructorParameterDescription(self.low)), ("medium", ConstructorParameterDescription(self.medium)), ("high", ConstructorParameterDescription(self.high))]) + } + } + case autoDownloadSettings(Cons_autoDownloadSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .autoDownloadSettings(let _data): + if boxed { + buffer.appendInt32(1674235686) + } + _data.low.serialize(buffer, true) + _data.medium.serialize(buffer, true) + _data.high.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .autoDownloadSettings(let _data): + return ("autoDownloadSettings", [("low", ConstructorParameterDescription(_data.low)), ("medium", ConstructorParameterDescription(_data.medium)), ("high", ConstructorParameterDescription(_data.high))]) + } + } + + public static func parse_autoDownloadSettings(_ reader: BufferReader) -> AutoDownloadSettings? { + var _1: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + var _2: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + var _3: Api.AutoDownloadSettings? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.AutoDownloadSettings + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.AutoDownloadSettings.autoDownloadSettings(Cons_autoDownloadSettings(low: _1!, medium: _2!, high: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum AutoSaveSettings: TypeConstructorDescription { + public class Cons_autoSaveSettings: TypeConstructorDescription { + public var usersSettings: Api.AutoSaveSettings + public var chatsSettings: Api.AutoSaveSettings + public var broadcastsSettings: Api.AutoSaveSettings + public var exceptions: [Api.AutoSaveException] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(usersSettings: Api.AutoSaveSettings, chatsSettings: Api.AutoSaveSettings, broadcastsSettings: Api.AutoSaveSettings, exceptions: [Api.AutoSaveException], chats: [Api.Chat], users: [Api.User]) { + self.usersSettings = usersSettings + self.chatsSettings = chatsSettings + self.broadcastsSettings = broadcastsSettings + self.exceptions = exceptions + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("autoSaveSettings", [("usersSettings", ConstructorParameterDescription(self.usersSettings)), ("chatsSettings", ConstructorParameterDescription(self.chatsSettings)), ("broadcastsSettings", ConstructorParameterDescription(self.broadcastsSettings)), ("exceptions", ConstructorParameterDescription(self.exceptions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case autoSaveSettings(Cons_autoSaveSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .autoSaveSettings(let _data): + if boxed { + buffer.appendInt32(1279133341) + } + _data.usersSettings.serialize(buffer, true) + _data.chatsSettings.serialize(buffer, true) + _data.broadcastsSettings.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.exceptions.count)) + for item in _data.exceptions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.users.count)) for item in _data.users { @@ -498,15 +1042,278 @@ public extension Api.account { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .webAuthorizations(let _data): - return ("webAuthorizations", [("authorizations", ConstructorParameterDescription(_data.authorizations)), ("users", ConstructorParameterDescription(_data.users))]) + case .autoSaveSettings(let _data): + return ("autoSaveSettings", [("usersSettings", ConstructorParameterDescription(_data.usersSettings)), ("chatsSettings", ConstructorParameterDescription(_data.chatsSettings)), ("broadcastsSettings", ConstructorParameterDescription(_data.broadcastsSettings)), ("exceptions", ConstructorParameterDescription(_data.exceptions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_webAuthorizations(_ reader: BufferReader) -> WebAuthorizations? { - var _1: [Api.WebAuthorization]? + public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? { + var _1: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _2: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _3: Api.AutoSaveSettings? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings + } + var _4: [Api.AutoSaveException]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebAuthorization.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AutoSaveException.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.AutoSaveSettings.autoSaveSettings(Cons_autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum BusinessChatLinks: TypeConstructorDescription { + public class Cons_businessChatLinks: TypeConstructorDescription { + public var links: [Api.BusinessChatLink] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(links: [Api.BusinessChatLink], chats: [Api.Chat], users: [Api.User]) { + self.links = links + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("businessChatLinks", [("links", ConstructorParameterDescription(self.links)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case businessChatLinks(Cons_businessChatLinks) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessChatLinks(let _data): + if boxed { + buffer.appendInt32(-331111727) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.links.count)) + for item in _data.links { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .businessChatLinks(let _data): + return ("businessChatLinks", [("links", ConstructorParameterDescription(_data.links)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_businessChatLinks(_ reader: BufferReader) -> BusinessChatLinks? { + var _1: [Api.BusinessChatLink]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BusinessChatLink.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.BusinessChatLinks.businessChatLinks(Cons_businessChatLinks(links: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum ChatThemes: TypeConstructorDescription { + public class Cons_chatThemes: TypeConstructorDescription { + public var flags: Int32 + public var hash: Int64 + public var themes: [Api.ChatTheme] + public var chats: [Api.Chat] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, hash: Int64, themes: [Api.ChatTheme], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.hash = hash + self.themes = themes + self.chats = chats + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatThemes", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("themes", ConstructorParameterDescription(self.themes)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case chatThemes(Cons_chatThemes) + case chatThemesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatThemes(let _data): + if boxed { + buffer.appendInt32(-1106673293) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.themes.count)) + for item in _data.themes { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + break + case .chatThemesNotModified: + if boxed { + buffer.appendInt32(-535699004) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .chatThemes(let _data): + return ("chatThemes", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("themes", ConstructorParameterDescription(_data.themes)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + case .chatThemesNotModified: + return ("chatThemesNotModified", []) + } + } + + public static func parse_chatThemes(_ reader: BufferReader) -> ChatThemes? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: [Api.ChatTheme]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.ChatThemes.chatThemes(Cons_chatThemes(flags: _1!, hash: _2!, themes: _3!, chats: _4!, users: _5!, nextOffset: _6)) + } + else { + return nil + } + } + public static func parse_chatThemesNotModified(_ reader: BufferReader) -> ChatThemes? { + return Api.account.ChatThemes.chatThemesNotModified + } + } +} +public extension Api.account { + enum ConnectedBots: TypeConstructorDescription { + public class Cons_connectedBots: TypeConstructorDescription { + public var connectedBots: [Api.ConnectedBot] + public var users: [Api.User] + public init(connectedBots: [Api.ConnectedBot], users: [Api.User]) { + self.connectedBots = connectedBots + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("connectedBots", [("connectedBots", ConstructorParameterDescription(self.connectedBots)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case connectedBots(Cons_connectedBots) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .connectedBots(let _data): + if boxed { + buffer.appendInt32(400029819) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.connectedBots.count)) + for item in _data.connectedBots { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .connectedBots(let _data): + return ("connectedBots", [("connectedBots", ConstructorParameterDescription(_data.connectedBots)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_connectedBots(_ reader: BufferReader) -> ConnectedBots? { + var _1: [Api.ConnectedBot]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBot.self) } var _2: [Api.User]? if let _ = reader.readInt32() { @@ -515,7 +1322,7 @@ public extension Api.account { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.account.WebAuthorizations.webAuthorizations(Cons_webAuthorizations(authorizations: _1!, users: _2!)) + return Api.account.ConnectedBots.connectedBots(Cons_connectedBots(connectedBots: _1!, users: _2!)) } else { return nil @@ -523,412 +1330,43 @@ public extension Api.account { } } } -public extension Api.auth { - enum Authorization: TypeConstructorDescription { - public class Cons_authorization: TypeConstructorDescription { +public extension Api.account { + enum ContentSettings: TypeConstructorDescription { + public class Cons_contentSettings: TypeConstructorDescription { public var flags: Int32 - public var otherwiseReloginDays: Int32? - public var tmpSessions: Int32? - public var futureAuthToken: Buffer? - public var user: Api.User - public init(flags: Int32, otherwiseReloginDays: Int32?, tmpSessions: Int32?, futureAuthToken: Buffer?, user: Api.User) { + public init(flags: Int32) { self.flags = flags - self.otherwiseReloginDays = otherwiseReloginDays - self.tmpSessions = tmpSessions - self.futureAuthToken = futureAuthToken - self.user = user } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("authorization", [("flags", ConstructorParameterDescription(self.flags)), ("otherwiseReloginDays", ConstructorParameterDescription(self.otherwiseReloginDays)), ("tmpSessions", ConstructorParameterDescription(self.tmpSessions)), ("futureAuthToken", ConstructorParameterDescription(self.futureAuthToken)), ("user", ConstructorParameterDescription(self.user))]) + return ("contentSettings", [("flags", ConstructorParameterDescription(self.flags))]) } } - public class Cons_authorizationSignUpRequired: TypeConstructorDescription { - public var flags: Int32 - public var termsOfService: Api.help.TermsOfService? - public init(flags: Int32, termsOfService: Api.help.TermsOfService?) { - self.flags = flags - self.termsOfService = termsOfService - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("authorizationSignUpRequired", [("flags", ConstructorParameterDescription(self.flags)), ("termsOfService", ConstructorParameterDescription(self.termsOfService))]) - } - } - case authorization(Cons_authorization) - case authorizationSignUpRequired(Cons_authorizationSignUpRequired) + case contentSettings(Cons_contentSettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .authorization(let _data): + case .contentSettings(let _data): if boxed { - buffer.appendInt32(782418132) + buffer.appendInt32(1474462241) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.otherwiseReloginDays!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.tmpSessions!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeBytes(_data.futureAuthToken!, buffer: buffer, boxed: false) - } - _data.user.serialize(buffer, true) - break - case .authorizationSignUpRequired(let _data): - if boxed { - buffer.appendInt32(1148485274) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.termsOfService!.serialize(buffer, true) - } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .authorization(let _data): - return ("authorization", [("flags", ConstructorParameterDescription(_data.flags)), ("otherwiseReloginDays", ConstructorParameterDescription(_data.otherwiseReloginDays)), ("tmpSessions", ConstructorParameterDescription(_data.tmpSessions)), ("futureAuthToken", ConstructorParameterDescription(_data.futureAuthToken)), ("user", ConstructorParameterDescription(_data.user))]) - case .authorizationSignUpRequired(let _data): - return ("authorizationSignUpRequired", [("flags", ConstructorParameterDescription(_data.flags)), ("termsOfService", ConstructorParameterDescription(_data.termsOfService))]) + case .contentSettings(let _data): + return ("contentSettings", [("flags", ConstructorParameterDescription(_data.flags))]) } } - public static func parse_authorization(_ reader: BufferReader) -> Authorization? { + public static func parse_contentSettings(_ reader: BufferReader) -> ContentSettings? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() - } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Buffer? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseBytes(reader) - } - var _5: Api.User? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.User - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.Authorization.authorization(Cons_authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!)) - } - else { - return nil - } - } - public static func parse_authorizationSignUpRequired(_ reader: BufferReader) -> Authorization? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.help.TermsOfService? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.Authorization.authorizationSignUpRequired(Cons_authorizationSignUpRequired(flags: _1!, termsOfService: _2)) - } - else { - return nil - } - } - } -} -public extension Api.auth { - enum CodeType: TypeConstructorDescription { - case codeTypeCall - case codeTypeFlashCall - case codeTypeFragmentSms - case codeTypeMissedCall - case codeTypeSms - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .codeTypeCall: - if boxed { - buffer.appendInt32(1948046307) - } - break - case .codeTypeFlashCall: - if boxed { - buffer.appendInt32(577556219) - } - break - case .codeTypeFragmentSms: - if boxed { - buffer.appendInt32(116234636) - } - break - case .codeTypeMissedCall: - if boxed { - buffer.appendInt32(-702884114) - } - break - case .codeTypeSms: - if boxed { - buffer.appendInt32(1923290508) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .codeTypeCall: - return ("codeTypeCall", []) - case .codeTypeFlashCall: - return ("codeTypeFlashCall", []) - case .codeTypeFragmentSms: - return ("codeTypeFragmentSms", []) - case .codeTypeMissedCall: - return ("codeTypeMissedCall", []) - case .codeTypeSms: - return ("codeTypeSms", []) - } - } - - public static func parse_codeTypeCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeCall - } - public static func parse_codeTypeFlashCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeFlashCall - } - public static func parse_codeTypeFragmentSms(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeFragmentSms - } - public static func parse_codeTypeMissedCall(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeMissedCall - } - public static func parse_codeTypeSms(_ reader: BufferReader) -> CodeType? { - return Api.auth.CodeType.codeTypeSms - } - } -} -public extension Api.auth { - enum ExportedAuthorization: TypeConstructorDescription { - public class Cons_exportedAuthorization: TypeConstructorDescription { - public var id: Int64 - public var bytes: Buffer - public init(id: Int64, bytes: Buffer) { - self.id = id - self.bytes = bytes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedAuthorization", [("id", ConstructorParameterDescription(self.id)), ("bytes", ConstructorParameterDescription(self.bytes))]) - } - } - case exportedAuthorization(Cons_exportedAuthorization) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .exportedAuthorization(let _data): - if boxed { - buffer.appendInt32(-1271602504) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .exportedAuthorization(let _data): - return ("exportedAuthorization", [("id", ConstructorParameterDescription(_data.id)), ("bytes", ConstructorParameterDescription(_data.bytes))]) - } - } - - public static func parse_exportedAuthorization(_ reader: BufferReader) -> ExportedAuthorization? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.ExportedAuthorization.exportedAuthorization(Cons_exportedAuthorization(id: _1!, bytes: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.auth { - enum LoggedOut: TypeConstructorDescription { - public class Cons_loggedOut: TypeConstructorDescription { - public var flags: Int32 - public var futureAuthToken: Buffer? - public init(flags: Int32, futureAuthToken: Buffer?) { - self.flags = flags - self.futureAuthToken = futureAuthToken - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("loggedOut", [("flags", ConstructorParameterDescription(self.flags)), ("futureAuthToken", ConstructorParameterDescription(self.futureAuthToken))]) - } - } - case loggedOut(Cons_loggedOut) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .loggedOut(let _data): - if boxed { - buffer.appendInt32(-1012759713) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.futureAuthToken!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .loggedOut(let _data): - return ("loggedOut", [("flags", ConstructorParameterDescription(_data.flags)), ("futureAuthToken", ConstructorParameterDescription(_data.futureAuthToken))]) - } - } - - public static func parse_loggedOut(_ reader: BufferReader) -> LoggedOut? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseBytes(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.LoggedOut.loggedOut(Cons_loggedOut(flags: _1!, futureAuthToken: _2)) - } - else { - return nil - } - } - } -} -public extension Api.auth { - enum LoginToken: TypeConstructorDescription { - public class Cons_loginToken: TypeConstructorDescription { - public var expires: Int32 - public var token: Buffer - public init(expires: Int32, token: Buffer) { - self.expires = expires - self.token = token - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("loginToken", [("expires", ConstructorParameterDescription(self.expires)), ("token", ConstructorParameterDescription(self.token))]) - } - } - public class Cons_loginTokenMigrateTo: TypeConstructorDescription { - public var dcId: Int32 - public var token: Buffer - public init(dcId: Int32, token: Buffer) { - self.dcId = dcId - self.token = token - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("loginTokenMigrateTo", [("dcId", ConstructorParameterDescription(self.dcId)), ("token", ConstructorParameterDescription(self.token))]) - } - } - public class Cons_loginTokenSuccess: TypeConstructorDescription { - public var authorization: Api.auth.Authorization - public init(authorization: Api.auth.Authorization) { - self.authorization = authorization - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("loginTokenSuccess", [("authorization", ConstructorParameterDescription(self.authorization))]) - } - } - case loginToken(Cons_loginToken) - case loginTokenMigrateTo(Cons_loginTokenMigrateTo) - case loginTokenSuccess(Cons_loginTokenSuccess) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .loginToken(let _data): - if boxed { - buffer.appendInt32(1654593920) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - serializeBytes(_data.token, buffer: buffer, boxed: false) - break - case .loginTokenMigrateTo(let _data): - if boxed { - buffer.appendInt32(110008598) - } - serializeInt32(_data.dcId, buffer: buffer, boxed: false) - serializeBytes(_data.token, buffer: buffer, boxed: false) - break - case .loginTokenSuccess(let _data): - if boxed { - buffer.appendInt32(957176926) - } - _data.authorization.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .loginToken(let _data): - return ("loginToken", [("expires", ConstructorParameterDescription(_data.expires)), ("token", ConstructorParameterDescription(_data.token))]) - case .loginTokenMigrateTo(let _data): - return ("loginTokenMigrateTo", [("dcId", ConstructorParameterDescription(_data.dcId)), ("token", ConstructorParameterDescription(_data.token))]) - case .loginTokenSuccess(let _data): - return ("loginTokenSuccess", [("authorization", ConstructorParameterDescription(_data.authorization))]) - } - } - - public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.LoginToken.loginToken(Cons_loginToken(expires: _1!, token: _2!)) - } - else { - return nil - } - } - public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.LoginToken.loginTokenMigrateTo(Cons_loginTokenMigrateTo(dcId: _1!, token: _2!)) - } - else { - return nil - } - } - public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? { - var _1: Api.auth.Authorization? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } let _c1 = _1 != nil if _c1 { - return Api.auth.LoginToken.loginTokenSuccess(Cons_loginTokenSuccess(authorization: _1!)) + return Api.account.ContentSettings.contentSettings(Cons_contentSettings(flags: _1!)) } else { return nil @@ -936,24 +1374,216 @@ public extension Api.auth { } } } -public extension Api.auth { - enum PasskeyLoginOptions: TypeConstructorDescription { - public class Cons_passkeyLoginOptions: TypeConstructorDescription { +public extension Api.account { + enum EmailVerified: TypeConstructorDescription { + public class Cons_emailVerified: TypeConstructorDescription { + public var email: String + public init(email: String) { + self.email = email + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("emailVerified", [("email", ConstructorParameterDescription(self.email))]) + } + } + public class Cons_emailVerifiedLogin: TypeConstructorDescription { + public var email: String + public var sentCode: Api.auth.SentCode + public init(email: String, sentCode: Api.auth.SentCode) { + self.email = email + self.sentCode = sentCode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("emailVerifiedLogin", [("email", ConstructorParameterDescription(self.email)), ("sentCode", ConstructorParameterDescription(self.sentCode))]) + } + } + case emailVerified(Cons_emailVerified) + case emailVerifiedLogin(Cons_emailVerifiedLogin) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emailVerified(let _data): + if boxed { + buffer.appendInt32(731303195) + } + serializeString(_data.email, buffer: buffer, boxed: false) + break + case .emailVerifiedLogin(let _data): + if boxed { + buffer.appendInt32(-507835039) + } + serializeString(_data.email, buffer: buffer, boxed: false) + _data.sentCode.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .emailVerified(let _data): + return ("emailVerified", [("email", ConstructorParameterDescription(_data.email))]) + case .emailVerifiedLogin(let _data): + return ("emailVerifiedLogin", [("email", ConstructorParameterDescription(_data.email)), ("sentCode", ConstructorParameterDescription(_data.sentCode))]) + } + } + + public static func parse_emailVerified(_ reader: BufferReader) -> EmailVerified? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.account.EmailVerified.emailVerified(Cons_emailVerified(email: _1!)) + } + else { + return nil + } + } + public static func parse_emailVerifiedLogin(_ reader: BufferReader) -> EmailVerified? { + var _1: String? + _1 = parseString(reader) + var _2: Api.auth.SentCode? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.EmailVerified.emailVerifiedLogin(Cons_emailVerifiedLogin(email: _1!, sentCode: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum EmojiStatuses: TypeConstructorDescription { + public class Cons_emojiStatuses: TypeConstructorDescription { + public var hash: Int64 + public var statuses: [Api.EmojiStatus] + public init(hash: Int64, statuses: [Api.EmojiStatus]) { + self.hash = hash + self.statuses = statuses + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("emojiStatuses", [("hash", ConstructorParameterDescription(self.hash)), ("statuses", ConstructorParameterDescription(self.statuses))]) + } + } + case emojiStatuses(Cons_emojiStatuses) + case emojiStatusesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emojiStatuses(let _data): + if boxed { + buffer.appendInt32(-1866176559) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.statuses.count)) + for item in _data.statuses { + item.serialize(buffer, true) + } + break + case .emojiStatusesNotModified: + if boxed { + buffer.appendInt32(-796072379) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .emojiStatuses(let _data): + return ("emojiStatuses", [("hash", ConstructorParameterDescription(_data.hash)), ("statuses", ConstructorParameterDescription(_data.statuses))]) + case .emojiStatusesNotModified: + return ("emojiStatusesNotModified", []) + } + } + + public static func parse_emojiStatuses(_ reader: BufferReader) -> EmojiStatuses? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.EmojiStatus]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiStatus.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.EmojiStatuses.emojiStatuses(Cons_emojiStatuses(hash: _1!, statuses: _2!)) + } + else { + return nil + } + } + public static func parse_emojiStatusesNotModified(_ reader: BufferReader) -> EmojiStatuses? { + return Api.account.EmojiStatuses.emojiStatusesNotModified + } + } +} +public extension Api.account { + enum PaidMessagesRevenue: TypeConstructorDescription { + public class Cons_paidMessagesRevenue: TypeConstructorDescription { + public var starsAmount: Int64 + public init(starsAmount: Int64) { + self.starsAmount = starsAmount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paidMessagesRevenue", [("starsAmount", ConstructorParameterDescription(self.starsAmount))]) + } + } + case paidMessagesRevenue(Cons_paidMessagesRevenue) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paidMessagesRevenue(let _data): + if boxed { + buffer.appendInt32(504403720) + } + serializeInt64(_data.starsAmount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paidMessagesRevenue(let _data): + return ("paidMessagesRevenue", [("starsAmount", ConstructorParameterDescription(_data.starsAmount))]) + } + } + + public static func parse_paidMessagesRevenue(_ reader: BufferReader) -> PaidMessagesRevenue? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.account.PaidMessagesRevenue.paidMessagesRevenue(Cons_paidMessagesRevenue(starsAmount: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum PasskeyRegistrationOptions: TypeConstructorDescription { + public class Cons_passkeyRegistrationOptions: TypeConstructorDescription { public var options: Api.DataJSON public init(options: Api.DataJSON) { self.options = options } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passkeyLoginOptions", [("options", ConstructorParameterDescription(self.options))]) + return ("passkeyRegistrationOptions", [("options", ConstructorParameterDescription(self.options))]) } } - case passkeyLoginOptions(Cons_passkeyLoginOptions) + case passkeyRegistrationOptions(Cons_passkeyRegistrationOptions) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passkeyLoginOptions(let _data): + case .passkeyRegistrationOptions(let _data): if boxed { - buffer.appendInt32(-503089271) + buffer.appendInt32(-513057567) } _data.options.serialize(buffer, true) break @@ -962,19 +1592,19 @@ public extension Api.auth { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .passkeyLoginOptions(let _data): - return ("passkeyLoginOptions", [("options", ConstructorParameterDescription(_data.options))]) + case .passkeyRegistrationOptions(let _data): + return ("passkeyRegistrationOptions", [("options", ConstructorParameterDescription(_data.options))]) } } - public static func parse_passkeyLoginOptions(_ reader: BufferReader) -> PasskeyLoginOptions? { + public static func parse_passkeyRegistrationOptions(_ reader: BufferReader) -> PasskeyRegistrationOptions? { var _1: Api.DataJSON? if let signature = reader.readInt32() { _1 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil if _c1 { - return Api.auth.PasskeyLoginOptions.passkeyLoginOptions(Cons_passkeyLoginOptions(options: _1!)) + return Api.account.PasskeyRegistrationOptions.passkeyRegistrationOptions(Cons_passkeyRegistrationOptions(options: _1!)) } else { return nil @@ -982,43 +1612,49 @@ public extension Api.auth { } } } -public extension Api.auth { - enum PasswordRecovery: TypeConstructorDescription { - public class Cons_passwordRecovery: TypeConstructorDescription { - public var emailPattern: String - public init(emailPattern: String) { - self.emailPattern = emailPattern +public extension Api.account { + enum Passkeys: TypeConstructorDescription { + public class Cons_passkeys: TypeConstructorDescription { + public var passkeys: [Api.Passkey] + public init(passkeys: [Api.Passkey]) { + self.passkeys = passkeys } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passwordRecovery", [("emailPattern", ConstructorParameterDescription(self.emailPattern))]) + return ("passkeys", [("passkeys", ConstructorParameterDescription(self.passkeys))]) } } - case passwordRecovery(Cons_passwordRecovery) + case passkeys(Cons_passkeys) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passwordRecovery(let _data): + case .passkeys(let _data): if boxed { - buffer.appendInt32(326715557) + buffer.appendInt32(-119494116) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.passkeys.count)) + for item in _data.passkeys { + item.serialize(buffer, true) } - serializeString(_data.emailPattern, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .passwordRecovery(let _data): - return ("passwordRecovery", [("emailPattern", ConstructorParameterDescription(_data.emailPattern))]) + case .passkeys(let _data): + return ("passkeys", [("passkeys", ConstructorParameterDescription(_data.passkeys))]) } } - public static func parse_passwordRecovery(_ reader: BufferReader) -> PasswordRecovery? { - var _1: String? - _1 = parseString(reader) + public static func parse_passkeys(_ reader: BufferReader) -> Passkeys? { + var _1: [Api.Passkey]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Passkey.self) + } let _c1 = _1 != nil if _c1 { - return Api.auth.PasswordRecovery.passwordRecovery(Cons_passwordRecovery(emailPattern: _1!)) + return Api.account.Passkeys.passkeys(Cons_passkeys(passkeys: _1!)) } else { return nil @@ -1026,415 +1662,69 @@ public extension Api.auth { } } } -public extension Api.auth { - enum SentCode: TypeConstructorDescription { - public class Cons_sentCode: TypeConstructorDescription { +public extension Api.account { + enum Password: TypeConstructorDescription { + public class Cons_password: TypeConstructorDescription { public var flags: Int32 - public var type: Api.auth.SentCodeType - public var phoneCodeHash: String - public var nextType: Api.auth.CodeType? - public var timeout: Int32? - public init(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) { + public var currentAlgo: Api.PasswordKdfAlgo? + public var srpB: Buffer? + public var srpId: Int64? + public var hint: String? + public var emailUnconfirmedPattern: String? + public var newAlgo: Api.PasswordKdfAlgo + public var newSecureAlgo: Api.SecurePasswordKdfAlgo + public var secureRandom: Buffer + public var pendingResetDate: Int32? + public var loginEmailPattern: String? + public init(flags: Int32, currentAlgo: Api.PasswordKdfAlgo?, srpB: Buffer?, srpId: Int64?, hint: String?, emailUnconfirmedPattern: String?, newAlgo: Api.PasswordKdfAlgo, newSecureAlgo: Api.SecurePasswordKdfAlgo, secureRandom: Buffer, pendingResetDate: Int32?, loginEmailPattern: String?) { self.flags = flags - self.type = type - self.phoneCodeHash = phoneCodeHash - self.nextType = nextType - self.timeout = timeout + self.currentAlgo = currentAlgo + self.srpB = srpB + self.srpId = srpId + self.hint = hint + self.emailUnconfirmedPattern = emailUnconfirmedPattern + self.newAlgo = newAlgo + self.newSecureAlgo = newSecureAlgo + self.secureRandom = secureRandom + self.pendingResetDate = pendingResetDate + self.loginEmailPattern = loginEmailPattern } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCode", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("nextType", ConstructorParameterDescription(self.nextType)), ("timeout", ConstructorParameterDescription(self.timeout))]) + return ("password", [("flags", ConstructorParameterDescription(self.flags)), ("currentAlgo", ConstructorParameterDescription(self.currentAlgo)), ("srpB", ConstructorParameterDescription(self.srpB)), ("srpId", ConstructorParameterDescription(self.srpId)), ("hint", ConstructorParameterDescription(self.hint)), ("emailUnconfirmedPattern", ConstructorParameterDescription(self.emailUnconfirmedPattern)), ("newAlgo", ConstructorParameterDescription(self.newAlgo)), ("newSecureAlgo", ConstructorParameterDescription(self.newSecureAlgo)), ("secureRandom", ConstructorParameterDescription(self.secureRandom)), ("pendingResetDate", ConstructorParameterDescription(self.pendingResetDate)), ("loginEmailPattern", ConstructorParameterDescription(self.loginEmailPattern))]) } } - public class Cons_sentCodePaymentRequired: TypeConstructorDescription { - public var storeProduct: String - public var phoneCodeHash: String - public var supportEmailAddress: String - public var supportEmailSubject: String - public var currency: String - public var amount: Int64 - public init(storeProduct: String, phoneCodeHash: String, supportEmailAddress: String, supportEmailSubject: String, currency: String, amount: Int64) { - self.storeProduct = storeProduct - self.phoneCodeHash = phoneCodeHash - self.supportEmailAddress = supportEmailAddress - self.supportEmailSubject = supportEmailSubject - self.currency = currency - self.amount = amount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodePaymentRequired", [("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("supportEmailAddress", ConstructorParameterDescription(self.supportEmailAddress)), ("supportEmailSubject", ConstructorParameterDescription(self.supportEmailSubject)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) - } - } - public class Cons_sentCodeSuccess: TypeConstructorDescription { - public var authorization: Api.auth.Authorization - public init(authorization: Api.auth.Authorization) { - self.authorization = authorization - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeSuccess", [("authorization", ConstructorParameterDescription(self.authorization))]) - } - } - case sentCode(Cons_sentCode) - case sentCodePaymentRequired(Cons_sentCodePaymentRequired) - case sentCodeSuccess(Cons_sentCodeSuccess) + case password(Cons_password) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sentCode(let _data): + case .password(let _data): if boxed { - buffer.appendInt32(1577067778) + buffer.appendInt32(-1787080453) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.type.serialize(buffer, true) - serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.nextType!.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.currentAlgo!.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.timeout!, buffer: buffer, boxed: false) + serializeBytes(_data.srpB!, buffer: buffer, boxed: false) } - break - case .sentCodePaymentRequired(let _data): - if boxed { - buffer.appendInt32(-527082948) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.srpId!, buffer: buffer, boxed: false) } - serializeString(_data.storeProduct, buffer: buffer, boxed: false) - serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) - serializeString(_data.supportEmailAddress, buffer: buffer, boxed: false) - serializeString(_data.supportEmailSubject, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - break - case .sentCodeSuccess(let _data): - if boxed { - buffer.appendInt32(596704836) - } - _data.authorization.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sentCode(let _data): - return ("sentCode", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("nextType", ConstructorParameterDescription(_data.nextType)), ("timeout", ConstructorParameterDescription(_data.timeout))]) - case .sentCodePaymentRequired(let _data): - return ("sentCodePaymentRequired", [("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("supportEmailAddress", ConstructorParameterDescription(_data.supportEmailAddress)), ("supportEmailSubject", ConstructorParameterDescription(_data.supportEmailSubject)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) - case .sentCodeSuccess(let _data): - return ("sentCodeSuccess", [("authorization", ConstructorParameterDescription(_data.authorization))]) - } - } - - public static func parse_sentCode(_ reader: BufferReader) -> SentCode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.auth.SentCodeType? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCodeType - } - var _3: String? - _3 = parseString(reader) - var _4: Api.auth.CodeType? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.auth.CodeType - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCode.sentCode(Cons_sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5)) - } - else { - return nil - } - } - public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Int64? - _6 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.auth.SentCode.sentCodePaymentRequired(Cons_sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!, supportEmailAddress: _3!, supportEmailSubject: _4!, currency: _5!, amount: _6!)) - } - else { - return nil - } - } - public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { - var _1: Api.auth.Authorization? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCode.sentCodeSuccess(Cons_sentCodeSuccess(authorization: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.auth { - enum SentCodeType: TypeConstructorDescription { - public class Cons_sentCodeTypeApp: TypeConstructorDescription { - public var length: Int32 - public init(length: Int32) { - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeApp", [("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeCall: TypeConstructorDescription { - public var length: Int32 - public init(length: Int32) { - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeCall", [("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeEmailCode: TypeConstructorDescription { - public var flags: Int32 - public var emailPattern: String - public var length: Int32 - public var resetAvailablePeriod: Int32? - public var resetPendingDate: Int32? - public init(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?) { - self.flags = flags - self.emailPattern = emailPattern - self.length = length - self.resetAvailablePeriod = resetAvailablePeriod - self.resetPendingDate = resetPendingDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeEmailCode", [("flags", ConstructorParameterDescription(self.flags)), ("emailPattern", ConstructorParameterDescription(self.emailPattern)), ("length", ConstructorParameterDescription(self.length)), ("resetAvailablePeriod", ConstructorParameterDescription(self.resetAvailablePeriod)), ("resetPendingDate", ConstructorParameterDescription(self.resetPendingDate))]) - } - } - public class Cons_sentCodeTypeFirebaseSms: TypeConstructorDescription { - public var flags: Int32 - public var nonce: Buffer? - public var playIntegrityProjectId: Int64? - public var playIntegrityNonce: Buffer? - public var receipt: String? - public var pushTimeout: Int32? - public var length: Int32 - public init(flags: Int32, nonce: Buffer?, playIntegrityProjectId: Int64?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) { - self.flags = flags - self.nonce = nonce - self.playIntegrityProjectId = playIntegrityProjectId - self.playIntegrityNonce = playIntegrityNonce - self.receipt = receipt - self.pushTimeout = pushTimeout - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeFirebaseSms", [("flags", ConstructorParameterDescription(self.flags)), ("nonce", ConstructorParameterDescription(self.nonce)), ("playIntegrityProjectId", ConstructorParameterDescription(self.playIntegrityProjectId)), ("playIntegrityNonce", ConstructorParameterDescription(self.playIntegrityNonce)), ("receipt", ConstructorParameterDescription(self.receipt)), ("pushTimeout", ConstructorParameterDescription(self.pushTimeout)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeFlashCall: TypeConstructorDescription { - public var pattern: String - public init(pattern: String) { - self.pattern = pattern - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeFlashCall", [("pattern", ConstructorParameterDescription(self.pattern))]) - } - } - public class Cons_sentCodeTypeFragmentSms: TypeConstructorDescription { - public var url: String - public var length: Int32 - public init(url: String, length: Int32) { - self.url = url - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeFragmentSms", [("url", ConstructorParameterDescription(self.url)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeMissedCall: TypeConstructorDescription { - public var prefix: String - public var length: Int32 - public init(prefix: String, length: Int32) { - self.prefix = prefix - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeMissedCall", [("prefix", ConstructorParameterDescription(self.prefix)), ("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeSetUpEmailRequired: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeSetUpEmailRequired", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - public class Cons_sentCodeTypeSms: TypeConstructorDescription { - public var length: Int32 - public init(length: Int32) { - self.length = length - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeSms", [("length", ConstructorParameterDescription(self.length))]) - } - } - public class Cons_sentCodeTypeSmsPhrase: TypeConstructorDescription { - public var flags: Int32 - public var beginning: String? - public init(flags: Int32, beginning: String?) { - self.flags = flags - self.beginning = beginning - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeSmsPhrase", [("flags", ConstructorParameterDescription(self.flags)), ("beginning", ConstructorParameterDescription(self.beginning))]) - } - } - public class Cons_sentCodeTypeSmsWord: TypeConstructorDescription { - public var flags: Int32 - public var beginning: String? - public init(flags: Int32, beginning: String?) { - self.flags = flags - self.beginning = beginning - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentCodeTypeSmsWord", [("flags", ConstructorParameterDescription(self.flags)), ("beginning", ConstructorParameterDescription(self.beginning))]) - } - } - case sentCodeTypeApp(Cons_sentCodeTypeApp) - case sentCodeTypeCall(Cons_sentCodeTypeCall) - case sentCodeTypeEmailCode(Cons_sentCodeTypeEmailCode) - case sentCodeTypeFirebaseSms(Cons_sentCodeTypeFirebaseSms) - case sentCodeTypeFlashCall(Cons_sentCodeTypeFlashCall) - case sentCodeTypeFragmentSms(Cons_sentCodeTypeFragmentSms) - case sentCodeTypeMissedCall(Cons_sentCodeTypeMissedCall) - case sentCodeTypeSetUpEmailRequired(Cons_sentCodeTypeSetUpEmailRequired) - case sentCodeTypeSms(Cons_sentCodeTypeSms) - case sentCodeTypeSmsPhrase(Cons_sentCodeTypeSmsPhrase) - case sentCodeTypeSmsWord(Cons_sentCodeTypeSmsWord) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sentCodeTypeApp(let _data): - if boxed { - buffer.appendInt32(1035688326) - } - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeCall(let _data): - if boxed { - buffer.appendInt32(1398007207) - } - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeEmailCode(let _data): - if boxed { - buffer.appendInt32(-196020837) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.emailPattern, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.resetAvailablePeriod!, buffer: buffer, boxed: false) + serializeString(_data.hint!, buffer: buffer, boxed: false) } if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.resetPendingDate!, buffer: buffer, boxed: false) + serializeString(_data.emailUnconfirmedPattern!, buffer: buffer, boxed: false) } - break - case .sentCodeTypeFirebaseSms(let _data): - if boxed { - buffer.appendInt32(10475318) + _data.newAlgo.serialize(buffer, true) + _data.newSecureAlgo.serialize(buffer, true) + serializeBytes(_data.secureRandom, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.pendingResetDate!, buffer: buffer, boxed: false) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeBytes(_data.nonce!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.playIntegrityProjectId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeBytes(_data.playIntegrityNonce!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.receipt!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.pushTimeout!, buffer: buffer, boxed: false) - } - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeFlashCall(let _data): - if boxed { - buffer.appendInt32(-1425815847) - } - serializeString(_data.pattern, buffer: buffer, boxed: false) - break - case .sentCodeTypeFragmentSms(let _data): - if boxed { - buffer.appendInt32(-648651719) - } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeMissedCall(let _data): - if boxed { - buffer.appendInt32(-2113903484) - } - serializeString(_data.prefix, buffer: buffer, boxed: false) - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeSetUpEmailRequired(let _data): - if boxed { - buffer.appendInt32(-1521934870) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - case .sentCodeTypeSms(let _data): - if boxed { - buffer.appendInt32(-1073693790) - } - serializeInt32(_data.length, buffer: buffer, boxed: false) - break - case .sentCodeTypeSmsPhrase(let _data): - if boxed { - buffer.appendInt32(-1284008785) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.beginning!, buffer: buffer, boxed: false) - } - break - case .sentCodeTypeSmsWord(let _data): - if boxed { - buffer.appendInt32(-1542017919) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.beginning!, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 6) != 0 { + serializeString(_data.loginEmailPattern!, buffer: buffer, boxed: false) } break } @@ -1442,207 +1732,231 @@ public extension Api.auth { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .sentCodeTypeApp(let _data): - return ("sentCodeTypeApp", [("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeCall(let _data): - return ("sentCodeTypeCall", [("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeEmailCode(let _data): - return ("sentCodeTypeEmailCode", [("flags", ConstructorParameterDescription(_data.flags)), ("emailPattern", ConstructorParameterDescription(_data.emailPattern)), ("length", ConstructorParameterDescription(_data.length)), ("resetAvailablePeriod", ConstructorParameterDescription(_data.resetAvailablePeriod)), ("resetPendingDate", ConstructorParameterDescription(_data.resetPendingDate))]) - case .sentCodeTypeFirebaseSms(let _data): - return ("sentCodeTypeFirebaseSms", [("flags", ConstructorParameterDescription(_data.flags)), ("nonce", ConstructorParameterDescription(_data.nonce)), ("playIntegrityProjectId", ConstructorParameterDescription(_data.playIntegrityProjectId)), ("playIntegrityNonce", ConstructorParameterDescription(_data.playIntegrityNonce)), ("receipt", ConstructorParameterDescription(_data.receipt)), ("pushTimeout", ConstructorParameterDescription(_data.pushTimeout)), ("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeFlashCall(let _data): - return ("sentCodeTypeFlashCall", [("pattern", ConstructorParameterDescription(_data.pattern))]) - case .sentCodeTypeFragmentSms(let _data): - return ("sentCodeTypeFragmentSms", [("url", ConstructorParameterDescription(_data.url)), ("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeMissedCall(let _data): - return ("sentCodeTypeMissedCall", [("prefix", ConstructorParameterDescription(_data.prefix)), ("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeSetUpEmailRequired(let _data): - return ("sentCodeTypeSetUpEmailRequired", [("flags", ConstructorParameterDescription(_data.flags))]) - case .sentCodeTypeSms(let _data): - return ("sentCodeTypeSms", [("length", ConstructorParameterDescription(_data.length))]) - case .sentCodeTypeSmsPhrase(let _data): - return ("sentCodeTypeSmsPhrase", [("flags", ConstructorParameterDescription(_data.flags)), ("beginning", ConstructorParameterDescription(_data.beginning))]) - case .sentCodeTypeSmsWord(let _data): - return ("sentCodeTypeSmsWord", [("flags", ConstructorParameterDescription(_data.flags)), ("beginning", ConstructorParameterDescription(_data.beginning))]) + case .password(let _data): + return ("password", [("flags", ConstructorParameterDescription(_data.flags)), ("currentAlgo", ConstructorParameterDescription(_data.currentAlgo)), ("srpB", ConstructorParameterDescription(_data.srpB)), ("srpId", ConstructorParameterDescription(_data.srpId)), ("hint", ConstructorParameterDescription(_data.hint)), ("emailUnconfirmedPattern", ConstructorParameterDescription(_data.emailUnconfirmedPattern)), ("newAlgo", ConstructorParameterDescription(_data.newAlgo)), ("newSecureAlgo", ConstructorParameterDescription(_data.newSecureAlgo)), ("secureRandom", ConstructorParameterDescription(_data.secureRandom)), ("pendingResetDate", ConstructorParameterDescription(_data.pendingResetDate)), ("loginEmailPattern", ConstructorParameterDescription(_data.loginEmailPattern))]) } } - public static func parse_sentCodeTypeApp(_ reader: BufferReader) -> SentCodeType? { + public static func parse_password(_ reader: BufferReader) -> Password? { var _1: Int32? _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeApp(Cons_sentCodeTypeApp(length: _1!)) + var _2: Api.PasswordKdfAlgo? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + } } - else { - return nil + var _3: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = parseBytes(reader) } - } - public static func parse_sentCodeTypeCall(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeCall(Cons_sentCodeTypeCall(length: _1!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeEmailCode(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.auth.SentCodeType.sentCodeTypeEmailCode(Cons_sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeFirebaseSms(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseBytes(reader) - } - var _3: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = reader.readInt64() - } - var _4: Buffer? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseBytes(reader) + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt64() } var _5: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = parseString(reader) } - var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt32() + var _6: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _6 = parseString(reader) + } + var _7: Api.PasswordKdfAlgo? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + } + var _8: Api.SecurePasswordKdfAlgo? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.SecurePasswordKdfAlgo + } + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _10 = reader.readInt32() + } + var _11: String? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + _11 = parseString(reader) } - var _7: Int32? - _7 = reader.readInt32() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(Cons_sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityProjectId: _3, playIntegrityNonce: _4, receipt: _5, pushTimeout: _6, length: _7!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeFlashCall(_ reader: BufferReader) -> SentCodeType? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeFlashCall(Cons_sentCodeTypeFlashCall(pattern: _1!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeFragmentSms(Cons_sentCodeTypeFragmentSms(url: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeMissedCall(Cons_sentCodeTypeMissedCall(prefix: _1!, length: _2!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSetUpEmailRequired(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(Cons_sentCodeTypeSetUpEmailRequired(flags: _1!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSms(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.auth.SentCodeType.sentCodeTypeSms(Cons_sentCodeTypeSms(length: _1!)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSmsPhrase(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(Cons_sentCodeTypeSmsPhrase(flags: _1!, beginning: _2)) - } - else { - return nil - } - } - public static func parse_sentCodeTypeSmsWord(_ reader: BufferReader) -> SentCodeType? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.auth.SentCodeType.sentCodeTypeSmsWord(Cons_sentCodeTypeSmsWord(flags: _1!, beginning: _2)) + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.account.Password.password(Cons_password(flags: _1!, currentAlgo: _2, srpB: _3, srpId: _4, hint: _5, emailUnconfirmedPattern: _6, newAlgo: _7!, newSecureAlgo: _8!, secureRandom: _9!, pendingResetDate: _10, loginEmailPattern: _11)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum PasswordInputSettings: TypeConstructorDescription { + public class Cons_passwordInputSettings: TypeConstructorDescription { + public var flags: Int32 + public var newAlgo: Api.PasswordKdfAlgo? + public var newPasswordHash: Buffer? + public var hint: String? + public var email: String? + public var newSecureSettings: Api.SecureSecretSettings? + public init(flags: Int32, newAlgo: Api.PasswordKdfAlgo?, newPasswordHash: Buffer?, hint: String?, email: String?, newSecureSettings: Api.SecureSecretSettings?) { + self.flags = flags + self.newAlgo = newAlgo + self.newPasswordHash = newPasswordHash + self.hint = hint + self.email = email + self.newSecureSettings = newSecureSettings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passwordInputSettings", [("flags", ConstructorParameterDescription(self.flags)), ("newAlgo", ConstructorParameterDescription(self.newAlgo)), ("newPasswordHash", ConstructorParameterDescription(self.newPasswordHash)), ("hint", ConstructorParameterDescription(self.hint)), ("email", ConstructorParameterDescription(self.email)), ("newSecureSettings", ConstructorParameterDescription(self.newSecureSettings))]) + } + } + case passwordInputSettings(Cons_passwordInputSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passwordInputSettings(let _data): + if boxed { + buffer.appendInt32(-1036572727) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.newAlgo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.newPasswordHash!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.hint!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.email!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.newSecureSettings!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passwordInputSettings(let _data): + return ("passwordInputSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("newAlgo", ConstructorParameterDescription(_data.newAlgo)), ("newPasswordHash", ConstructorParameterDescription(_data.newPasswordHash)), ("hint", ConstructorParameterDescription(_data.hint)), ("email", ConstructorParameterDescription(_data.email)), ("newSecureSettings", ConstructorParameterDescription(_data.newSecureSettings))]) + } + } + + public static func parse_passwordInputSettings(_ reader: BufferReader) -> PasswordInputSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.PasswordKdfAlgo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PasswordKdfAlgo + } + } + var _3: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseBytes(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: Api.SecureSecretSettings? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.account.PasswordInputSettings.passwordInputSettings(Cons_passwordInputSettings(flags: _1!, newAlgo: _2, newPasswordHash: _3, hint: _4, email: _5, newSecureSettings: _6)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum PasswordSettings: TypeConstructorDescription { + public class Cons_passwordSettings: TypeConstructorDescription { + public var flags: Int32 + public var email: String? + public var secureSettings: Api.SecureSecretSettings? + public init(flags: Int32, email: String?, secureSettings: Api.SecureSecretSettings?) { + self.flags = flags + self.email = email + self.secureSettings = secureSettings + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passwordSettings", [("flags", ConstructorParameterDescription(self.flags)), ("email", ConstructorParameterDescription(self.email)), ("secureSettings", ConstructorParameterDescription(self.secureSettings))]) + } + } + case passwordSettings(Cons_passwordSettings) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passwordSettings(let _data): + if boxed { + buffer.appendInt32(-1705233435) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.email!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.secureSettings!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passwordSettings(let _data): + return ("passwordSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("email", ConstructorParameterDescription(_data.email)), ("secureSettings", ConstructorParameterDescription(_data.secureSettings))]) + } + } + + public static func parse_passwordSettings(_ reader: BufferReader) -> PasswordSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: Api.SecureSecretSettings? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.SecureSecretSettings + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.PasswordSettings.passwordSettings(Cons_passwordSettings(flags: _1!, email: _2, secureSettings: _3)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index c188d6b7db..490b0d6ae1 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1,782 +1,235 @@ -public extension Api.bots { - enum BotInfo: TypeConstructorDescription { - public class Cons_botInfo: TypeConstructorDescription { - public var name: String - public var about: String - public var description: String - public init(name: String, about: String, description: String) { - self.name = name - self.about = about - self.description = description +public extension Api.account { + enum PrivacyRules: TypeConstructorDescription { + public class Cons_privacyRules: TypeConstructorDescription { + public var rules: [Api.PrivacyRule] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(rules: [Api.PrivacyRule], chats: [Api.Chat], users: [Api.User]) { + self.rules = rules + self.chats = chats + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botInfo", [("name", ConstructorParameterDescription(self.name)), ("about", ConstructorParameterDescription(self.about)), ("description", ConstructorParameterDescription(self.description))]) + return ("privacyRules", [("rules", ConstructorParameterDescription(self.rules)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case botInfo(Cons_botInfo) + case privacyRules(Cons_privacyRules) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botInfo(let _data): + case .privacyRules(let _data): if boxed { - buffer.appendInt32(-391678544) + buffer.appendInt32(1352683077) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.rules.count)) + for item in _data.rules { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } - serializeString(_data.name, buffer: buffer, boxed: false) - serializeString(_data.about, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .botInfo(let _data): - return ("botInfo", [("name", ConstructorParameterDescription(_data.name)), ("about", ConstructorParameterDescription(_data.about)), ("description", ConstructorParameterDescription(_data.description))]) + case .privacyRules(let _data): + return ("privacyRules", [("rules", ConstructorParameterDescription(_data.rules)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + public static func parse_privacyRules(_ reader: BufferReader) -> PrivacyRules? { + var _1: [Api.PrivacyRule]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrivacyRule.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.account.PrivacyRules.privacyRules(Cons_privacyRules(rules: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum ResetPasswordResult: TypeConstructorDescription { + public class Cons_resetPasswordFailedWait: TypeConstructorDescription { + public var retryDate: Int32 + public init(retryDate: Int32) { + self.retryDate = retryDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("resetPasswordFailedWait", [("retryDate", ConstructorParameterDescription(self.retryDate))]) + } + } + public class Cons_resetPasswordRequestedWait: TypeConstructorDescription { + public var untilDate: Int32 + public init(untilDate: Int32) { + self.untilDate = untilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("resetPasswordRequestedWait", [("untilDate", ConstructorParameterDescription(self.untilDate))]) + } + } + case resetPasswordFailedWait(Cons_resetPasswordFailedWait) + case resetPasswordOk + case resetPasswordRequestedWait(Cons_resetPasswordRequestedWait) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .resetPasswordFailedWait(let _data): + if boxed { + buffer.appendInt32(-478701471) + } + serializeInt32(_data.retryDate, buffer: buffer, boxed: false) + break + case .resetPasswordOk: + if boxed { + buffer.appendInt32(-383330754) + } + break + case .resetPasswordRequestedWait(let _data): + if boxed { + buffer.appendInt32(-370148227) + } + serializeInt32(_data.untilDate, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .resetPasswordFailedWait(let _data): + return ("resetPasswordFailedWait", [("retryDate", ConstructorParameterDescription(_data.retryDate))]) + case .resetPasswordOk: + return ("resetPasswordOk", []) + case .resetPasswordRequestedWait(let _data): + return ("resetPasswordRequestedWait", [("untilDate", ConstructorParameterDescription(_data.untilDate))]) + } + } + + public static func parse_resetPasswordFailedWait(_ reader: BufferReader) -> ResetPasswordResult? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.account.ResetPasswordResult.resetPasswordFailedWait(Cons_resetPasswordFailedWait(retryDate: _1!)) + } + else { + return nil + } + } + public static func parse_resetPasswordOk(_ reader: BufferReader) -> ResetPasswordResult? { + return Api.account.ResetPasswordResult.resetPasswordOk + } + public static func parse_resetPasswordRequestedWait(_ reader: BufferReader) -> ResetPasswordResult? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.account.ResetPasswordResult.resetPasswordRequestedWait(Cons_resetPasswordRequestedWait(untilDate: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum ResolvedBusinessChatLinks: TypeConstructorDescription { + public class Cons_resolvedBusinessChatLinks: TypeConstructorDescription { + public var flags: Int32 + public var peer: Api.Peer + public var message: String + public var entities: [Api.MessageEntity]? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, peer: Api.Peer, message: String, entities: [Api.MessageEntity]?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.peer = peer + self.message = message + self.entities = entities + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("resolvedBusinessChatLinks", [("flags", ConstructorParameterDescription(self.flags)), ("peer", ConstructorParameterDescription(self.peer)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case resolvedBusinessChatLinks(Cons_resolvedBusinessChatLinks) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .resolvedBusinessChatLinks(let _data): + if boxed { + buffer.appendInt32(-1708937439) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.peer.serialize(buffer, true) + serializeString(_data.message, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .resolvedBusinessChatLinks(let _data): + return ("resolvedBusinessChatLinks", [("flags", ConstructorParameterDescription(_data.flags)), ("peer", ConstructorParameterDescription(_data.peer)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_resolvedBusinessChatLinks(_ reader: BufferReader) -> ResolvedBusinessChatLinks? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } var _3: String? _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.BotInfo.botInfo(Cons_botInfo(name: _1!, about: _2!, description: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.bots { - enum ExportedBotToken: TypeConstructorDescription { - public class Cons_exportedBotToken: TypeConstructorDescription { - public var token: String - public init(token: String) { - self.token = token - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedBotToken", [("token", ConstructorParameterDescription(self.token))]) - } - } - case exportedBotToken(Cons_exportedBotToken) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .exportedBotToken(let _data): - if boxed { - buffer.appendInt32(1012971041) + var _4: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } - serializeString(_data.token, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .exportedBotToken(let _data): - return ("exportedBotToken", [("token", ConstructorParameterDescription(_data.token))]) - } - } - - public static func parse_exportedBotToken(_ reader: BufferReader) -> ExportedBotToken? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.bots.ExportedBotToken.exportedBotToken(Cons_exportedBotToken(token: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.bots { - enum PopularAppBots: TypeConstructorDescription { - public class Cons_popularAppBots: TypeConstructorDescription { - public var flags: Int32 - public var nextOffset: String? - public var users: [Api.User] - public init(flags: Int32, nextOffset: String?, users: [Api.User]) { - self.flags = flags - self.nextOffset = nextOffset - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("popularAppBots", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case popularAppBots(Cons_popularAppBots) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .popularAppBots(let _data): - if boxed { - buffer.appendInt32(428978491) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .popularAppBots(let _data): - return ("popularAppBots", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_popularAppBots(_ reader: BufferReader) -> PopularAppBots? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.bots.PopularAppBots.popularAppBots(Cons_popularAppBots(flags: _1!, nextOffset: _2, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.bots { - enum PreviewInfo: TypeConstructorDescription { - public class Cons_previewInfo: TypeConstructorDescription { - public var media: [Api.BotPreviewMedia] - public var langCodes: [String] - public init(media: [Api.BotPreviewMedia], langCodes: [String]) { - self.media = media - self.langCodes = langCodes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("previewInfo", [("media", ConstructorParameterDescription(self.media)), ("langCodes", ConstructorParameterDescription(self.langCodes))]) - } - } - case previewInfo(Cons_previewInfo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .previewInfo(let _data): - if boxed { - buffer.appendInt32(212278628) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.media.count)) - for item in _data.media { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.langCodes.count)) - for item in _data.langCodes { - serializeString(item, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .previewInfo(let _data): - return ("previewInfo", [("media", ConstructorParameterDescription(_data.media)), ("langCodes", ConstructorParameterDescription(_data.langCodes))]) - } - } - - public static func parse_previewInfo(_ reader: BufferReader) -> PreviewInfo? { - var _1: [Api.BotPreviewMedia]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self) - } - var _2: [String]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.bots.PreviewInfo.previewInfo(Cons_previewInfo(media: _1!, langCodes: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.bots { - enum RequestedButton: TypeConstructorDescription { - public class Cons_requestedButton: TypeConstructorDescription { - public var webappReqId: String - public init(webappReqId: String) { - self.webappReqId = webappReqId - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("requestedButton", [("webappReqId", ConstructorParameterDescription(self.webappReqId))]) - } - } - case requestedButton(Cons_requestedButton) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .requestedButton(let _data): - if boxed { - buffer.appendInt32(-247743273) - } - serializeString(_data.webappReqId, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .requestedButton(let _data): - return ("requestedButton", [("webappReqId", ConstructorParameterDescription(_data.webappReqId))]) - } - } - - public static func parse_requestedButton(_ reader: BufferReader) -> RequestedButton? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.bots.RequestedButton.requestedButton(Cons_requestedButton(webappReqId: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.channels { - enum AdminLogResults: TypeConstructorDescription { - public class Cons_adminLogResults: TypeConstructorDescription { - public var events: [Api.ChannelAdminLogEvent] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(events: [Api.ChannelAdminLogEvent], chats: [Api.Chat], users: [Api.User]) { - self.events = events - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("adminLogResults", [("events", ConstructorParameterDescription(self.events)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case adminLogResults(Cons_adminLogResults) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .adminLogResults(let _data): - if boxed { - buffer.appendInt32(-309659827) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.events.count)) - for item in _data.events { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .adminLogResults(let _data): - return ("adminLogResults", [("events", ConstructorParameterDescription(_data.events)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_adminLogResults(_ reader: BufferReader) -> AdminLogResults? { - var _1: [Api.ChannelAdminLogEvent]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelAdminLogEvent.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.AdminLogResults.adminLogResults(Cons_adminLogResults(events: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.channels { - enum ChannelParticipant: TypeConstructorDescription { - public class Cons_channelParticipant: TypeConstructorDescription { - public var participant: Api.ChannelParticipant - public var chats: [Api.Chat] - public var users: [Api.User] - public init(participant: Api.ChannelParticipant, chats: [Api.Chat], users: [Api.User]) { - self.participant = participant - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelParticipant", [("participant", ConstructorParameterDescription(self.participant)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case channelParticipant(Cons_channelParticipant) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .channelParticipant(let _data): - if boxed { - buffer.appendInt32(-541588713) - } - _data.participant.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .channelParticipant(let _data): - return ("channelParticipant", [("participant", ConstructorParameterDescription(_data.participant)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? { - var _1: Api.ChannelParticipant? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.ChannelParticipant.channelParticipant(Cons_channelParticipant(participant: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.channels { - enum ChannelParticipants: TypeConstructorDescription { - public class Cons_channelParticipants: TypeConstructorDescription { - public var count: Int32 - public var participants: [Api.ChannelParticipant] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(count: Int32, participants: [Api.ChannelParticipant], chats: [Api.Chat], users: [Api.User]) { - self.count = count - self.participants = participants - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelParticipants", [("count", ConstructorParameterDescription(self.count)), ("participants", ConstructorParameterDescription(self.participants)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case channelParticipants(Cons_channelParticipants) - case channelParticipantsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .channelParticipants(let _data): - if boxed { - buffer.appendInt32(-1699676497) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.participants.count)) - for item in _data.participants { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .channelParticipantsNotModified: - if boxed { - buffer.appendInt32(-266911767) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .channelParticipants(let _data): - return ("channelParticipants", [("count", ConstructorParameterDescription(_data.count)), ("participants", ConstructorParameterDescription(_data.participants)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .channelParticipantsNotModified: - return ("channelParticipantsNotModified", []) - } - } - - public static func parse_channelParticipants(_ reader: BufferReader) -> ChannelParticipants? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.ChannelParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelParticipant.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.channels.ChannelParticipants.channelParticipants(Cons_channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - public static func parse_channelParticipantsNotModified(_ reader: BufferReader) -> ChannelParticipants? { - return Api.channels.ChannelParticipants.channelParticipantsNotModified - } - } -} -public extension Api.channels { - enum SendAsPeers: TypeConstructorDescription { - public class Cons_sendAsPeers: TypeConstructorDescription { - public var peers: [Api.SendAsPeer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User]) { - self.peers = peers - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sendAsPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case sendAsPeers(Cons_sendAsPeers) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sendAsPeers(let _data): - if boxed { - buffer.appendInt32(-191450938) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sendAsPeers(let _data): - return ("sendAsPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? { - var _1: [Api.SendAsPeer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.channels.SendAsPeers.sendAsPeers(Cons_sendAsPeers(peers: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.channels { - enum SponsoredMessageReportResult: TypeConstructorDescription { - public class Cons_sponsoredMessageReportResultChooseOption: TypeConstructorDescription { - public var title: String - public var options: [Api.SponsoredMessageReportOption] - public init(title: String, options: [Api.SponsoredMessageReportOption]) { - self.title = title - self.options = options - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredMessageReportResultChooseOption", [("title", ConstructorParameterDescription(self.title)), ("options", ConstructorParameterDescription(self.options))]) - } - } - case sponsoredMessageReportResultAdsHidden - case sponsoredMessageReportResultChooseOption(Cons_sponsoredMessageReportResultChooseOption) - case sponsoredMessageReportResultReported - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredMessageReportResultAdsHidden: - if boxed { - buffer.appendInt32(1044107055) - } - break - case .sponsoredMessageReportResultChooseOption(let _data): - if boxed { - buffer.appendInt32(-2073059774) - } - serializeString(_data.title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.options.count)) - for item in _data.options { - item.serialize(buffer, true) - } - break - case .sponsoredMessageReportResultReported: - if boxed { - buffer.appendInt32(-1384544183) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sponsoredMessageReportResultAdsHidden: - return ("sponsoredMessageReportResultAdsHidden", []) - case .sponsoredMessageReportResultChooseOption(let _data): - return ("sponsoredMessageReportResultChooseOption", [("title", ConstructorParameterDescription(_data.title)), ("options", ConstructorParameterDescription(_data.options))]) - case .sponsoredMessageReportResultReported: - return ("sponsoredMessageReportResultReported", []) - } - } - - public static func parse_sponsoredMessageReportResultAdsHidden(_ reader: BufferReader) -> SponsoredMessageReportResult? { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultAdsHidden - } - public static func parse_sponsoredMessageReportResultChooseOption(_ reader: BufferReader) -> SponsoredMessageReportResult? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.SponsoredMessageReportOption]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessageReportOption.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(Cons_sponsoredMessageReportResultChooseOption(title: _1!, options: _2!)) - } - else { - return nil - } - } - public static func parse_sponsoredMessageReportResultReported(_ reader: BufferReader) -> SponsoredMessageReportResult? { - return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultReported - } - } -} -public extension Api.chatlists { - enum ChatlistInvite: TypeConstructorDescription { - public class Cons_chatlistInvite: TypeConstructorDescription { - public var flags: Int32 - public var title: Api.TextWithEntities - public var emoticon: String? - public var peers: [Api.Peer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, title: Api.TextWithEntities, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.title = title - self.emoticon = emoticon - self.peers = peers - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatlistInvite", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_chatlistInviteAlready: TypeConstructorDescription { - public var filterId: Int32 - public var missingPeers: [Api.Peer] - public var alreadyPeers: [Api.Peer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { - self.filterId = filterId - self.missingPeers = missingPeers - self.alreadyPeers = alreadyPeers - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatlistInviteAlready", [("filterId", ConstructorParameterDescription(self.filterId)), ("missingPeers", ConstructorParameterDescription(self.missingPeers)), ("alreadyPeers", ConstructorParameterDescription(self.alreadyPeers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case chatlistInvite(Cons_chatlistInvite) - case chatlistInviteAlready(Cons_chatlistInviteAlready) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chatlistInvite(let _data): - if boxed { - buffer.appendInt32(-250687953) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.title.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.emoticon!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .chatlistInviteAlready(let _data): - if boxed { - buffer.appendInt32(-91752871) - } - serializeInt32(_data.filterId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.missingPeers.count)) - for item in _data.missingPeers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.alreadyPeers.count)) - for item in _data.alreadyPeers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .chatlistInvite(let _data): - return ("chatlistInvite", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .chatlistInviteAlready(let _data): - return ("chatlistInviteAlready", [("filterId", ConstructorParameterDescription(_data.filterId)), ("missingPeers", ConstructorParameterDescription(_data.missingPeers)), ("alreadyPeers", ConstructorParameterDescription(_data.alreadyPeers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.TextWithEntities? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = parseString(reader) - } - var _4: [Api.Peer]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } var _5: [Api.Chat]? if let _ = reader.readInt32() { @@ -788,43 +241,12 @@ public extension Api.chatlists { } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = _4 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.chatlists.ChatlistInvite.chatlistInvite(Cons_chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!)) - } - else { - return nil - } - } - public static func parse_chatlistInviteAlready(_ reader: BufferReader) -> ChatlistInvite? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _3: [Api.Peer]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.chatlists.ChatlistInvite.chatlistInviteAlready(Cons_chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!)) + return Api.account.ResolvedBusinessChatLinks.resolvedBusinessChatLinks(Cons_resolvedBusinessChatLinks(flags: _1!, peer: _2!, message: _3!, entities: _4, chats: _5!, users: _6!)) } else { return nil @@ -832,43 +254,35 @@ public extension Api.chatlists { } } } -public extension Api.chatlists { - enum ChatlistUpdates: TypeConstructorDescription { - public class Cons_chatlistUpdates: TypeConstructorDescription { - public var missingPeers: [Api.Peer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { - self.missingPeers = missingPeers - self.chats = chats - self.users = users +public extension Api.account { + enum SavedMusicIds: TypeConstructorDescription { + public class Cons_savedMusicIds: TypeConstructorDescription { + public var ids: [Int64] + public init(ids: [Int64]) { + self.ids = ids } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatlistUpdates", [("missingPeers", ConstructorParameterDescription(self.missingPeers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("savedMusicIds", [("ids", ConstructorParameterDescription(self.ids))]) } } - case chatlistUpdates(Cons_chatlistUpdates) + case savedMusicIds(Cons_savedMusicIds) + case savedMusicIdsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatlistUpdates(let _data): + case .savedMusicIds(let _data): if boxed { - buffer.appendInt32(-1816295539) + buffer.appendInt32(-1718786506) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.missingPeers.count)) - for item in _data.missingPeers { - item.serialize(buffer, true) + buffer.appendInt32(Int32(_data.ids.count)) + for item in _data.ids { + serializeInt64(item, buffer: buffer, boxed: false) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + break + case .savedMusicIdsNotModified: + if boxed { + buffer.appendInt32(1338514798) } break } @@ -876,29 +290,81 @@ public extension Api.chatlists { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .chatlistUpdates(let _data): - return ("chatlistUpdates", [("missingPeers", ConstructorParameterDescription(_data.missingPeers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .savedMusicIds(let _data): + return ("savedMusicIds", [("ids", ConstructorParameterDescription(_data.ids))]) + case .savedMusicIdsNotModified: + return ("savedMusicIdsNotModified", []) } } - public static func parse_chatlistUpdates(_ reader: BufferReader) -> ChatlistUpdates? { - var _1: [Api.Peer]? + public static func parse_savedMusicIds(_ reader: BufferReader) -> SavedMusicIds? { + var _1: [Int64]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ChatlistUpdates.chatlistUpdates(Cons_chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!)) + if _c1 { + return Api.account.SavedMusicIds.savedMusicIds(Cons_savedMusicIds(ids: _1!)) + } + else { + return nil + } + } + public static func parse_savedMusicIdsNotModified(_ reader: BufferReader) -> SavedMusicIds? { + return Api.account.SavedMusicIds.savedMusicIdsNotModified + } + } +} +public extension Api.account { + enum SavedRingtone: TypeConstructorDescription { + public class Cons_savedRingtoneConverted: TypeConstructorDescription { + public var document: Api.Document + public init(document: Api.Document) { + self.document = document + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedRingtoneConverted", [("document", ConstructorParameterDescription(self.document))]) + } + } + case savedRingtone + case savedRingtoneConverted(Cons_savedRingtoneConverted) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedRingtone: + if boxed { + buffer.appendInt32(-1222230163) + } + break + case .savedRingtoneConverted(let _data): + if boxed { + buffer.appendInt32(523271863) + } + _data.document.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedRingtone: + return ("savedRingtone", []) + case .savedRingtoneConverted(let _data): + return ("savedRingtoneConverted", [("document", ConstructorParameterDescription(_data.document))]) + } + } + + public static func parse_savedRingtone(_ reader: BufferReader) -> SavedRingtone? { + return Api.account.SavedRingtone.savedRingtone + } + public static func parse_savedRingtoneConverted(_ reader: BufferReader) -> SavedRingtone? { + var _1: Api.Document? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Document + } + let _c1 = _1 != nil + if _c1 { + return Api.account.SavedRingtone.savedRingtoneConverted(Cons_savedRingtoneConverted(document: _1!)) } else { return nil @@ -906,53 +372,116 @@ public extension Api.chatlists { } } } -public extension Api.chatlists { - enum ExportedChatlistInvite: TypeConstructorDescription { - public class Cons_exportedChatlistInvite: TypeConstructorDescription { - public var filter: Api.DialogFilter - public var invite: Api.ExportedChatlistInvite - public init(filter: Api.DialogFilter, invite: Api.ExportedChatlistInvite) { - self.filter = filter - self.invite = invite +public extension Api.account { + enum SavedRingtones: TypeConstructorDescription { + public class Cons_savedRingtones: TypeConstructorDescription { + public var hash: Int64 + public var ringtones: [Api.Document] + public init(hash: Int64, ringtones: [Api.Document]) { + self.hash = hash + self.ringtones = ringtones } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedChatlistInvite", [("filter", ConstructorParameterDescription(self.filter)), ("invite", ConstructorParameterDescription(self.invite))]) + return ("savedRingtones", [("hash", ConstructorParameterDescription(self.hash)), ("ringtones", ConstructorParameterDescription(self.ringtones))]) } } - case exportedChatlistInvite(Cons_exportedChatlistInvite) + case savedRingtones(Cons_savedRingtones) + case savedRingtonesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatlistInvite(let _data): + case .savedRingtones(let _data): if boxed { - buffer.appendInt32(283567014) + buffer.appendInt32(-1041683259) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.ringtones.count)) + for item in _data.ringtones { + item.serialize(buffer, true) + } + break + case .savedRingtonesNotModified: + if boxed { + buffer.appendInt32(-67704655) } - _data.filter.serialize(buffer, true) - _data.invite.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedChatlistInvite(let _data): - return ("exportedChatlistInvite", [("filter", ConstructorParameterDescription(_data.filter)), ("invite", ConstructorParameterDescription(_data.invite))]) + case .savedRingtones(let _data): + return ("savedRingtones", [("hash", ConstructorParameterDescription(_data.hash)), ("ringtones", ConstructorParameterDescription(_data.ringtones))]) + case .savedRingtonesNotModified: + return ("savedRingtonesNotModified", []) } } - public static func parse_exportedChatlistInvite(_ reader: BufferReader) -> ExportedChatlistInvite? { - var _1: Api.DialogFilter? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.DialogFilter - } - var _2: Api.ExportedChatlistInvite? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite + public static func parse_savedRingtones(_ reader: BufferReader) -> SavedRingtones? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(Cons_exportedChatlistInvite(filter: _1!, invite: _2!)) + return Api.account.SavedRingtones.savedRingtones(Cons_savedRingtones(hash: _1!, ringtones: _2!)) + } + else { + return nil + } + } + public static func parse_savedRingtonesNotModified(_ reader: BufferReader) -> SavedRingtones? { + return Api.account.SavedRingtones.savedRingtonesNotModified + } + } +} +public extension Api.account { + enum SentEmailCode: TypeConstructorDescription { + public class Cons_sentEmailCode: TypeConstructorDescription { + public var emailPattern: String + public var length: Int32 + public init(emailPattern: String, length: Int32) { + self.emailPattern = emailPattern + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentEmailCode", [("emailPattern", ConstructorParameterDescription(self.emailPattern)), ("length", ConstructorParameterDescription(self.length))]) + } + } + case sentEmailCode(Cons_sentEmailCode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sentEmailCode(let _data): + if boxed { + buffer.appendInt32(-2128640689) + } + serializeString(_data.emailPattern, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sentEmailCode(let _data): + return ("sentEmailCode", [("emailPattern", ConstructorParameterDescription(_data.emailPattern)), ("length", ConstructorParameterDescription(_data.length))]) + } + } + + public static func parse_sentEmailCode(_ reader: BufferReader) -> SentEmailCode? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.SentEmailCode.sentEmailCode(Cons_sentEmailCode(emailPattern: _1!, length: _2!)) } else { return nil @@ -960,37 +489,258 @@ public extension Api.chatlists { } } } -public extension Api.chatlists { - enum ExportedInvites: TypeConstructorDescription { - public class Cons_exportedInvites: TypeConstructorDescription { - public var invites: [Api.ExportedChatlistInvite] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(invites: [Api.ExportedChatlistInvite], chats: [Api.Chat], users: [Api.User]) { - self.invites = invites - self.chats = chats - self.users = users +public extension Api.account { + enum Takeout: TypeConstructorDescription { + public class Cons_takeout: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedInvites", [("invites", ConstructorParameterDescription(self.invites)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("takeout", [("id", ConstructorParameterDescription(self.id))]) } } - case exportedInvites(Cons_exportedInvites) + case takeout(Cons_takeout) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedInvites(let _data): + case .takeout(let _data): if boxed { - buffer.appendInt32(279670215) + buffer.appendInt32(1304052993) } + serializeInt64(_data.id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .takeout(let _data): + return ("takeout", [("id", ConstructorParameterDescription(_data.id))]) + } + } + + public static func parse_takeout(_ reader: BufferReader) -> Takeout? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.account.Takeout.takeout(Cons_takeout(id: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum Themes: TypeConstructorDescription { + public class Cons_themes: TypeConstructorDescription { + public var hash: Int64 + public var themes: [Api.Theme] + public init(hash: Int64, themes: [Api.Theme]) { + self.hash = hash + self.themes = themes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("themes", [("hash", ConstructorParameterDescription(self.hash)), ("themes", ConstructorParameterDescription(self.themes))]) + } + } + case themes(Cons_themes) + case themesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .themes(let _data): + if boxed { + buffer.appendInt32(-1707242387) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.invites.count)) - for item in _data.invites { + buffer.appendInt32(Int32(_data.themes.count)) + for item in _data.themes { item.serialize(buffer, true) } + break + case .themesNotModified: + if boxed { + buffer.appendInt32(-199313886) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .themes(let _data): + return ("themes", [("hash", ConstructorParameterDescription(_data.hash)), ("themes", ConstructorParameterDescription(_data.themes))]) + case .themesNotModified: + return ("themesNotModified", []) + } + } + + public static func parse_themes(_ reader: BufferReader) -> Themes? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Theme]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Theme.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.Themes.themes(Cons_themes(hash: _1!, themes: _2!)) + } + else { + return nil + } + } + public static func parse_themesNotModified(_ reader: BufferReader) -> Themes? { + return Api.account.Themes.themesNotModified + } + } +} +public extension Api.account { + enum TmpPassword: TypeConstructorDescription { + public class Cons_tmpPassword: TypeConstructorDescription { + public var tmpPassword: Buffer + public var validUntil: Int32 + public init(tmpPassword: Buffer, validUntil: Int32) { + self.tmpPassword = tmpPassword + self.validUntil = validUntil + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("tmpPassword", [("tmpPassword", ConstructorParameterDescription(self.tmpPassword)), ("validUntil", ConstructorParameterDescription(self.validUntil))]) + } + } + case tmpPassword(Cons_tmpPassword) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .tmpPassword(let _data): + if boxed { + buffer.appendInt32(-614138572) + } + serializeBytes(_data.tmpPassword, buffer: buffer, boxed: false) + serializeInt32(_data.validUntil, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .tmpPassword(let _data): + return ("tmpPassword", [("tmpPassword", ConstructorParameterDescription(_data.tmpPassword)), ("validUntil", ConstructorParameterDescription(_data.validUntil))]) + } + } + + public static func parse_tmpPassword(_ reader: BufferReader) -> TmpPassword? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.TmpPassword.tmpPassword(Cons_tmpPassword(tmpPassword: _1!, validUntil: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.account { + enum WallPapers: TypeConstructorDescription { + public class Cons_wallPapers: TypeConstructorDescription { + public var hash: Int64 + public var wallpapers: [Api.WallPaper] + public init(hash: Int64, wallpapers: [Api.WallPaper]) { + self.hash = hash + self.wallpapers = wallpapers + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("wallPapers", [("hash", ConstructorParameterDescription(self.hash)), ("wallpapers", ConstructorParameterDescription(self.wallpapers))]) + } + } + case wallPapers(Cons_wallPapers) + case wallPapersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .wallPapers(let _data): + if boxed { + buffer.appendInt32(-842824308) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { + buffer.appendInt32(Int32(_data.wallpapers.count)) + for item in _data.wallpapers { + item.serialize(buffer, true) + } + break + case .wallPapersNotModified: + if boxed { + buffer.appendInt32(471437699) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .wallPapers(let _data): + return ("wallPapers", [("hash", ConstructorParameterDescription(_data.hash)), ("wallpapers", ConstructorParameterDescription(_data.wallpapers))]) + case .wallPapersNotModified: + return ("wallPapersNotModified", []) + } + } + + public static func parse_wallPapers(_ reader: BufferReader) -> WallPapers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.WallPaper]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.account.WallPapers.wallPapers(Cons_wallPapers(hash: _1!, wallpapers: _2!)) + } + else { + return nil + } + } + public static func parse_wallPapersNotModified(_ reader: BufferReader) -> WallPapers? { + return Api.account.WallPapers.wallPapersNotModified + } + } +} +public extension Api.account { + enum WebAuthorizations: TypeConstructorDescription { + public class Cons_webAuthorizations: TypeConstructorDescription { + public var authorizations: [Api.WebAuthorization] + public var users: [Api.User] + public init(authorizations: [Api.WebAuthorization], users: [Api.User]) { + self.authorizations = authorizations + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webAuthorizations", [("authorizations", ConstructorParameterDescription(self.authorizations)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case webAuthorizations(Cons_webAuthorizations) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webAuthorizations(let _data): + if boxed { + buffer.appendInt32(-313079300) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.authorizations.count)) + for item in _data.authorizations { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1004,221 +754,15 @@ public extension Api.chatlists { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedInvites(let _data): - return ("exportedInvites", [("invites", ConstructorParameterDescription(_data.invites)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .webAuthorizations(let _data): + return ("webAuthorizations", [("authorizations", ConstructorParameterDescription(_data.authorizations)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? { - var _1: [Api.ExportedChatlistInvite]? + public static func parse_webAuthorizations(_ reader: BufferReader) -> WebAuthorizations? { + var _1: [Api.WebAuthorization]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatlistInvite.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.chatlists.ExportedInvites.exportedInvites(Cons_exportedInvites(invites: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.contacts { - enum Blocked: TypeConstructorDescription { - public class Cons_blocked: TypeConstructorDescription { - public var blocked: [Api.PeerBlocked] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) { - self.blocked = blocked - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("blocked", [("blocked", ConstructorParameterDescription(self.blocked)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_blockedSlice: TypeConstructorDescription { - public var count: Int32 - public var blocked: [Api.PeerBlocked] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) { - self.count = count - self.blocked = blocked - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("blockedSlice", [("count", ConstructorParameterDescription(self.count)), ("blocked", ConstructorParameterDescription(self.blocked)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case blocked(Cons_blocked) - case blockedSlice(Cons_blockedSlice) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .blocked(let _data): - if boxed { - buffer.appendInt32(182326673) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocked.count)) - for item in _data.blocked { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .blockedSlice(let _data): - if boxed { - buffer.appendInt32(-513392236) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.blocked.count)) - for item in _data.blocked { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .blocked(let _data): - return ("blocked", [("blocked", ConstructorParameterDescription(_data.blocked)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .blockedSlice(let _data): - return ("blockedSlice", [("count", ConstructorParameterDescription(_data.count)), ("blocked", ConstructorParameterDescription(_data.blocked)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_blocked(_ reader: BufferReader) -> Blocked? { - var _1: [Api.PeerBlocked]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Blocked.blocked(Cons_blocked(blocked: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.PeerBlocked]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Blocked.blockedSlice(Cons_blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - } -} -public extension Api.contacts { - enum ContactBirthdays: TypeConstructorDescription { - public class Cons_contactBirthdays: TypeConstructorDescription { - public var contacts: [Api.ContactBirthday] - public var users: [Api.User] - public init(contacts: [Api.ContactBirthday], users: [Api.User]) { - self.contacts = contacts - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("contactBirthdays", [("contacts", ConstructorParameterDescription(self.contacts)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case contactBirthdays(Cons_contactBirthdays) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .contactBirthdays(let _data): - if boxed { - buffer.appendInt32(290452237) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.contacts.count)) - for item in _data.contacts { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .contactBirthdays(let _data): - return ("contactBirthdays", [("contacts", ConstructorParameterDescription(_data.contacts)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_contactBirthdays(_ reader: BufferReader) -> ContactBirthdays? { - var _1: [Api.ContactBirthday]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactBirthday.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebAuthorization.self) } var _2: [Api.User]? if let _ = reader.readInt32() { @@ -1227,7 +771,7 @@ public extension Api.contacts { let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.contacts.ContactBirthdays.contactBirthdays(Cons_contactBirthdays(contacts: _1!, users: _2!)) + return Api.account.WebAuthorizations.webAuthorizations(Cons_webAuthorizations(authorizations: _1!, users: _2!)) } else { return nil @@ -1235,45 +779,48 @@ public extension Api.contacts { } } } -public extension Api.contacts { - enum Contacts: TypeConstructorDescription { - public class Cons_contacts: TypeConstructorDescription { - public var contacts: [Api.Contact] - public var savedCount: Int32 - public var users: [Api.User] - public init(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) { - self.contacts = contacts - self.savedCount = savedCount - self.users = users +public extension Api.account { + enum WebBrowserSettings: TypeConstructorDescription { + public class Cons_webBrowserSettings: TypeConstructorDescription { + public var flags: Int32 + public var externalExceptions: [Api.WebDomainException] + public var inappExceptions: [Api.WebDomainException] + public var hash: Int64 + public init(flags: Int32, externalExceptions: [Api.WebDomainException], inappExceptions: [Api.WebDomainException], hash: Int64) { + self.flags = flags + self.externalExceptions = externalExceptions + self.inappExceptions = inappExceptions + self.hash = hash } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("contacts", [("contacts", ConstructorParameterDescription(self.contacts)), ("savedCount", ConstructorParameterDescription(self.savedCount)), ("users", ConstructorParameterDescription(self.users))]) + return ("webBrowserSettings", [("flags", ConstructorParameterDescription(self.flags)), ("externalExceptions", ConstructorParameterDescription(self.externalExceptions)), ("inappExceptions", ConstructorParameterDescription(self.inappExceptions)), ("hash", ConstructorParameterDescription(self.hash))]) } } - case contacts(Cons_contacts) - case contactsNotModified + case webBrowserSettings(Cons_webBrowserSettings) + case webBrowserSettingsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .contacts(let _data): + case .webBrowserSettings(let _data): if boxed { - buffer.appendInt32(-353862078) + buffer.appendInt32(2045480115) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.contacts.count)) - for item in _data.contacts { + buffer.appendInt32(Int32(_data.externalExceptions.count)) + for item in _data.externalExceptions { item.serialize(buffer, true) } - serializeInt32(_data.savedCount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { + buffer.appendInt32(Int32(_data.inappExceptions.count)) + for item in _data.inappExceptions { item.serialize(buffer, true) } + serializeInt64(_data.hash, buffer: buffer, boxed: false) break - case .contactsNotModified: + case .webBrowserSettingsNotModified: if boxed { - buffer.appendInt32(-1219778094) + buffer.appendInt32(-1021538482) } break } @@ -1281,529 +828,411 @@ public extension Api.contacts { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .contacts(let _data): - return ("contacts", [("contacts", ConstructorParameterDescription(_data.contacts)), ("savedCount", ConstructorParameterDescription(_data.savedCount)), ("users", ConstructorParameterDescription(_data.users))]) - case .contactsNotModified: - return ("contactsNotModified", []) + case .webBrowserSettings(let _data): + return ("webBrowserSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("externalExceptions", ConstructorParameterDescription(_data.externalExceptions)), ("inappExceptions", ConstructorParameterDescription(_data.inappExceptions)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .webBrowserSettingsNotModified: + return ("webBrowserSettingsNotModified", []) } } - public static func parse_contacts(_ reader: BufferReader) -> Contacts? { - var _1: [Api.Contact]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) - } - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.Contacts.contacts(Cons_contacts(contacts: _1!, savedCount: _2!, users: _3!)) - } - else { - return nil - } - } - public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { - return Api.contacts.Contacts.contactsNotModified - } - } -} -public extension Api.contacts { - enum Found: TypeConstructorDescription { - public class Cons_found: TypeConstructorDescription { - public var myResults: [Api.Peer] - public var results: [Api.Peer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { - self.myResults = myResults - self.results = results - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("found", [("myResults", ConstructorParameterDescription(self.myResults)), ("results", ConstructorParameterDescription(self.results)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case found(Cons_found) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .found(let _data): - if boxed { - buffer.appendInt32(-1290580579) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.myResults.count)) - for item in _data.myResults { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.results.count)) - for item in _data.results { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .found(let _data): - return ("found", [("myResults", ConstructorParameterDescription(_data.myResults)), ("results", ConstructorParameterDescription(_data.results)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_found(_ reader: BufferReader) -> Found? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _2: [Api.Peer]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.Found.found(Cons_found(myResults: _1!, results: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - } -} -public extension Api.contacts { - enum ImportedContacts: TypeConstructorDescription { - public class Cons_importedContacts: TypeConstructorDescription { - public var imported: [Api.ImportedContact] - public var popularInvites: [Api.PopularContact] - public var retryContacts: [Int64] - public var users: [Api.User] - public init(imported: [Api.ImportedContact], popularInvites: [Api.PopularContact], retryContacts: [Int64], users: [Api.User]) { - self.imported = imported - self.popularInvites = popularInvites - self.retryContacts = retryContacts - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("importedContacts", [("imported", ConstructorParameterDescription(self.imported)), ("popularInvites", ConstructorParameterDescription(self.popularInvites)), ("retryContacts", ConstructorParameterDescription(self.retryContacts)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case importedContacts(Cons_importedContacts) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .importedContacts(let _data): - if boxed { - buffer.appendInt32(2010127419) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.imported.count)) - for item in _data.imported { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.popularInvites.count)) - for item in _data.popularInvites { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.retryContacts.count)) - for item in _data.retryContacts { - serializeInt64(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .importedContacts(let _data): - return ("importedContacts", [("imported", ConstructorParameterDescription(_data.imported)), ("popularInvites", ConstructorParameterDescription(_data.popularInvites)), ("retryContacts", ConstructorParameterDescription(_data.retryContacts)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_importedContacts(_ reader: BufferReader) -> ImportedContacts? { - var _1: [Api.ImportedContact]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ImportedContact.self) - } - var _2: [Api.PopularContact]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PopularContact.self) - } - var _3: [Int64]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.contacts.ImportedContacts.importedContacts(Cons_importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!)) - } - else { - return nil - } - } - } -} -public extension Api.contacts { - enum ResolvedPeer: TypeConstructorDescription { - public class Cons_resolvedPeer: TypeConstructorDescription { - public var peer: Api.Peer - public var chats: [Api.Chat] - public var users: [Api.User] - public init(peer: Api.Peer, chats: [Api.Chat], users: [Api.User]) { - self.peer = peer - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("resolvedPeer", [("peer", ConstructorParameterDescription(self.peer)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case resolvedPeer(Cons_resolvedPeer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .resolvedPeer(let _data): - if boxed { - buffer.appendInt32(2131196633) - } - _data.peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .resolvedPeer(let _data): - return ("resolvedPeer", [("peer", ConstructorParameterDescription(_data.peer)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_resolvedPeer(_ reader: BufferReader) -> ResolvedPeer? { - var _1: Api.Peer? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Peer - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.ResolvedPeer.resolvedPeer(Cons_resolvedPeer(peer: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.contacts { - enum SponsoredPeers: TypeConstructorDescription { - public class Cons_sponsoredPeers: TypeConstructorDescription { - public var peers: [Api.SponsoredPeer] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(peers: [Api.SponsoredPeer], chats: [Api.Chat], users: [Api.User]) { - self.peers = peers - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case sponsoredPeers(Cons_sponsoredPeers) - case sponsoredPeersEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sponsoredPeers(let _data): - if boxed { - buffer.appendInt32(-352114556) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .sponsoredPeersEmpty: - if boxed { - buffer.appendInt32(-365775695) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sponsoredPeers(let _data): - return ("sponsoredPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .sponsoredPeersEmpty: - return ("sponsoredPeersEmpty", []) - } - } - - public static func parse_sponsoredPeers(_ reader: BufferReader) -> SponsoredPeers? { - var _1: [Api.SponsoredPeer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredPeer.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.SponsoredPeers.sponsoredPeers(Cons_sponsoredPeers(peers: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - public static func parse_sponsoredPeersEmpty(_ reader: BufferReader) -> SponsoredPeers? { - return Api.contacts.SponsoredPeers.sponsoredPeersEmpty - } - } -} -public extension Api.contacts { - enum TopPeers: TypeConstructorDescription { - public class Cons_topPeers: TypeConstructorDescription { - public var categories: [Api.TopPeerCategoryPeers] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) { - self.categories = categories - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("topPeers", [("categories", ConstructorParameterDescription(self.categories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case topPeers(Cons_topPeers) - case topPeersDisabled - case topPeersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .topPeers(let _data): - if boxed { - buffer.appendInt32(1891070632) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.categories.count)) - for item in _data.categories { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .topPeersDisabled: - if boxed { - buffer.appendInt32(-1255369827) - } - break - case .topPeersNotModified: - if boxed { - buffer.appendInt32(-567906571) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .topPeers(let _data): - return ("topPeers", [("categories", ConstructorParameterDescription(_data.categories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .topPeersDisabled: - return ("topPeersDisabled", []) - case .topPeersNotModified: - return ("topPeersNotModified", []) - } - } - - public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { - var _1: [Api.TopPeerCategoryPeers]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.contacts.TopPeers.topPeers(Cons_topPeers(categories: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersDisabled - } - public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { - return Api.contacts.TopPeers.topPeersNotModified - } - } -} -public extension Api.fragment { - enum CollectibleInfo: TypeConstructorDescription { - public class Cons_collectibleInfo: TypeConstructorDescription { - public var purchaseDate: Int32 - public var currency: String - public var amount: Int64 - public var cryptoCurrency: String - public var cryptoAmount: Int64 - public var url: String - public init(purchaseDate: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, url: String) { - self.purchaseDate = purchaseDate - self.currency = currency - self.amount = amount - self.cryptoCurrency = cryptoCurrency - self.cryptoAmount = cryptoAmount - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("collectibleInfo", [("purchaseDate", ConstructorParameterDescription(self.purchaseDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("url", ConstructorParameterDescription(self.url))]) - } - } - case collectibleInfo(Cons_collectibleInfo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .collectibleInfo(let _data): - if boxed { - buffer.appendInt32(1857945489) - } - serializeInt32(_data.purchaseDate, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.amount, buffer: buffer, boxed: false) - serializeString(_data.cryptoCurrency, buffer: buffer, boxed: false) - serializeInt64(_data.cryptoAmount, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .collectibleInfo(let _data): - return ("collectibleInfo", [("purchaseDate", ConstructorParameterDescription(_data.purchaseDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_collectibleInfo(_ reader: BufferReader) -> CollectibleInfo? { + public static func parse_webBrowserSettings(_ reader: BufferReader) -> WebBrowserSettings? { var _1: Int32? _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: String? - _6 = parseString(reader) + var _2: [Api.WebDomainException]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebDomainException.self) + } + var _3: [Api.WebDomainException]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.WebDomainException.self) + } + var _4: Int64? + _4 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.account.WebBrowserSettings.webBrowserSettings(Cons_webBrowserSettings(flags: _1!, externalExceptions: _2!, inappExceptions: _3!, hash: _4!)) + } + else { + return nil + } + } + public static func parse_webBrowserSettingsNotModified(_ reader: BufferReader) -> WebBrowserSettings? { + return Api.account.WebBrowserSettings.webBrowserSettingsNotModified + } + } +} +public extension Api.aicompose { + enum Tones: TypeConstructorDescription { + public class Cons_tones: TypeConstructorDescription { + public var hash: Int64 + public var tones: [Api.AiComposeTone] + public var users: [Api.User] + public init(hash: Int64, tones: [Api.AiComposeTone], users: [Api.User]) { + self.hash = hash + self.tones = tones + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("tones", [("hash", ConstructorParameterDescription(self.hash)), ("tones", ConstructorParameterDescription(self.tones)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case tones(Cons_tones) + case tonesNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .tones(let _data): + if boxed { + buffer.appendInt32(1822232318) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.tones.count)) + for item in _data.tones { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .tonesNotModified: + if boxed { + buffer.appendInt32(-1040948989) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .tones(let _data): + return ("tones", [("hash", ConstructorParameterDescription(_data.hash)), ("tones", ConstructorParameterDescription(_data.tones)), ("users", ConstructorParameterDescription(_data.users))]) + case .tonesNotModified: + return ("tonesNotModified", []) + } + } + + public static func parse_tones(_ reader: BufferReader) -> Tones? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.AiComposeTone]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AiComposeTone.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.aicompose.Tones.tones(Cons_tones(hash: _1!, tones: _2!, users: _3!)) + } + else { + return nil + } + } + public static func parse_tonesNotModified(_ reader: BufferReader) -> Tones? { + return Api.aicompose.Tones.tonesNotModified + } + } +} +public extension Api.auth { + enum Authorization: TypeConstructorDescription { + public class Cons_authorization: TypeConstructorDescription { + public var flags: Int32 + public var otherwiseReloginDays: Int32? + public var tmpSessions: Int32? + public var futureAuthToken: Buffer? + public var user: Api.User + public init(flags: Int32, otherwiseReloginDays: Int32?, tmpSessions: Int32?, futureAuthToken: Buffer?, user: Api.User) { + self.flags = flags + self.otherwiseReloginDays = otherwiseReloginDays + self.tmpSessions = tmpSessions + self.futureAuthToken = futureAuthToken + self.user = user + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("authorization", [("flags", ConstructorParameterDescription(self.flags)), ("otherwiseReloginDays", ConstructorParameterDescription(self.otherwiseReloginDays)), ("tmpSessions", ConstructorParameterDescription(self.tmpSessions)), ("futureAuthToken", ConstructorParameterDescription(self.futureAuthToken)), ("user", ConstructorParameterDescription(self.user))]) + } + } + public class Cons_authorizationSignUpRequired: TypeConstructorDescription { + public var flags: Int32 + public var termsOfService: Api.help.TermsOfService? + public init(flags: Int32, termsOfService: Api.help.TermsOfService?) { + self.flags = flags + self.termsOfService = termsOfService + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("authorizationSignUpRequired", [("flags", ConstructorParameterDescription(self.flags)), ("termsOfService", ConstructorParameterDescription(self.termsOfService))]) + } + } + case authorization(Cons_authorization) + case authorizationSignUpRequired(Cons_authorizationSignUpRequired) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .authorization(let _data): + if boxed { + buffer.appendInt32(782418132) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.otherwiseReloginDays!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.tmpSessions!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeBytes(_data.futureAuthToken!, buffer: buffer, boxed: false) + } + _data.user.serialize(buffer, true) + break + case .authorizationSignUpRequired(let _data): + if boxed { + buffer.appendInt32(1148485274) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.termsOfService!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .authorization(let _data): + return ("authorization", [("flags", ConstructorParameterDescription(_data.flags)), ("otherwiseReloginDays", ConstructorParameterDescription(_data.otherwiseReloginDays)), ("tmpSessions", ConstructorParameterDescription(_data.tmpSessions)), ("futureAuthToken", ConstructorParameterDescription(_data.futureAuthToken)), ("user", ConstructorParameterDescription(_data.user))]) + case .authorizationSignUpRequired(let _data): + return ("authorizationSignUpRequired", [("flags", ConstructorParameterDescription(_data.flags)), ("termsOfService", ConstructorParameterDescription(_data.termsOfService))]) + } + } + + public static func parse_authorization(_ reader: BufferReader) -> Authorization? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = reader.readInt32() + } + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseBytes(reader) + } + var _5: Api.User? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.User + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.fragment.CollectibleInfo.collectibleInfo(Cons_collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!)) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.Authorization.authorization(Cons_authorization(flags: _1!, otherwiseReloginDays: _2, tmpSessions: _3, futureAuthToken: _4, user: _5!)) + } + else { + return nil + } + } + public static func parse_authorizationSignUpRequired(_ reader: BufferReader) -> Authorization? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.help.TermsOfService? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.Authorization.authorizationSignUpRequired(Cons_authorizationSignUpRequired(flags: _1!, termsOfService: _2)) + } + else { + return nil + } + } + } +} +public extension Api.auth { + enum CodeType: TypeConstructorDescription { + case codeTypeCall + case codeTypeFlashCall + case codeTypeFragmentSms + case codeTypeMissedCall + case codeTypeSms + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .codeTypeCall: + if boxed { + buffer.appendInt32(1948046307) + } + break + case .codeTypeFlashCall: + if boxed { + buffer.appendInt32(577556219) + } + break + case .codeTypeFragmentSms: + if boxed { + buffer.appendInt32(116234636) + } + break + case .codeTypeMissedCall: + if boxed { + buffer.appendInt32(-702884114) + } + break + case .codeTypeSms: + if boxed { + buffer.appendInt32(1923290508) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .codeTypeCall: + return ("codeTypeCall", []) + case .codeTypeFlashCall: + return ("codeTypeFlashCall", []) + case .codeTypeFragmentSms: + return ("codeTypeFragmentSms", []) + case .codeTypeMissedCall: + return ("codeTypeMissedCall", []) + case .codeTypeSms: + return ("codeTypeSms", []) + } + } + + public static func parse_codeTypeCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeCall + } + public static func parse_codeTypeFlashCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeFlashCall + } + public static func parse_codeTypeFragmentSms(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeFragmentSms + } + public static func parse_codeTypeMissedCall(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeMissedCall + } + public static func parse_codeTypeSms(_ reader: BufferReader) -> CodeType? { + return Api.auth.CodeType.codeTypeSms + } + } +} +public extension Api.auth { + enum ExportedAuthorization: TypeConstructorDescription { + public class Cons_exportedAuthorization: TypeConstructorDescription { + public var id: Int64 + public var bytes: Buffer + public init(id: Int64, bytes: Buffer) { + self.id = id + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedAuthorization", [("id", ConstructorParameterDescription(self.id)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + case exportedAuthorization(Cons_exportedAuthorization) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedAuthorization(let _data): + if boxed { + buffer.appendInt32(-1271602504) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedAuthorization(let _data): + return ("exportedAuthorization", [("id", ConstructorParameterDescription(_data.id)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + } + } + + public static func parse_exportedAuthorization(_ reader: BufferReader) -> ExportedAuthorization? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.ExportedAuthorization.exportedAuthorization(Cons_exportedAuthorization(id: _1!, bytes: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.auth { + enum LoggedOut: TypeConstructorDescription { + public class Cons_loggedOut: TypeConstructorDescription { + public var flags: Int32 + public var futureAuthToken: Buffer? + public init(flags: Int32, futureAuthToken: Buffer?) { + self.flags = flags + self.futureAuthToken = futureAuthToken + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("loggedOut", [("flags", ConstructorParameterDescription(self.flags)), ("futureAuthToken", ConstructorParameterDescription(self.futureAuthToken))]) + } + } + case loggedOut(Cons_loggedOut) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .loggedOut(let _data): + if boxed { + buffer.appendInt32(-1012759713) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.futureAuthToken!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .loggedOut(let _data): + return ("loggedOut", [("flags", ConstructorParameterDescription(_data.flags)), ("futureAuthToken", ConstructorParameterDescription(_data.futureAuthToken))]) + } + } + + public static func parse_loggedOut(_ reader: BufferReader) -> LoggedOut? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.LoggedOut.loggedOut(Cons_loggedOut(flags: _1!, futureAuthToken: _2)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api33.swift b/submodules/TelegramApi/Sources/Api33.swift index d3b185ee7f..aae52360c3 100644 --- a/submodules/TelegramApi/Sources/Api33.swift +++ b/submodules/TelegramApi/Sources/Api33.swift @@ -1,217 +1,624 @@ -public extension Api.help { - enum AppConfig: TypeConstructorDescription { - public class Cons_appConfig: TypeConstructorDescription { - public var hash: Int32 - public var config: Api.JSONValue - public init(hash: Int32, config: Api.JSONValue) { - self.hash = hash - self.config = config +public extension Api.auth { + enum LoginToken: TypeConstructorDescription { + public class Cons_loginToken: TypeConstructorDescription { + public var expires: Int32 + public var token: Buffer + public init(expires: Int32, token: Buffer) { + self.expires = expires + self.token = token } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("appConfig", [("hash", ConstructorParameterDescription(self.hash)), ("config", ConstructorParameterDescription(self.config))]) + return ("loginToken", [("expires", ConstructorParameterDescription(self.expires)), ("token", ConstructorParameterDescription(self.token))]) } } - case appConfig(Cons_appConfig) - case appConfigNotModified + public class Cons_loginTokenMigrateTo: TypeConstructorDescription { + public var dcId: Int32 + public var token: Buffer + public init(dcId: Int32, token: Buffer) { + self.dcId = dcId + self.token = token + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("loginTokenMigrateTo", [("dcId", ConstructorParameterDescription(self.dcId)), ("token", ConstructorParameterDescription(self.token))]) + } + } + public class Cons_loginTokenSuccess: TypeConstructorDescription { + public var authorization: Api.auth.Authorization + public init(authorization: Api.auth.Authorization) { + self.authorization = authorization + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("loginTokenSuccess", [("authorization", ConstructorParameterDescription(self.authorization))]) + } + } + case loginToken(Cons_loginToken) + case loginTokenMigrateTo(Cons_loginTokenMigrateTo) + case loginTokenSuccess(Cons_loginTokenSuccess) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .appConfig(let _data): + case .loginToken(let _data): if boxed { - buffer.appendInt32(-585598930) + buffer.appendInt32(1654593920) } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - _data.config.serialize(buffer, true) + serializeInt32(_data.expires, buffer: buffer, boxed: false) + serializeBytes(_data.token, buffer: buffer, boxed: false) break - case .appConfigNotModified: + case .loginTokenMigrateTo(let _data): if boxed { - buffer.appendInt32(2094949405) + buffer.appendInt32(110008598) } + serializeInt32(_data.dcId, buffer: buffer, boxed: false) + serializeBytes(_data.token, buffer: buffer, boxed: false) + break + case .loginTokenSuccess(let _data): + if boxed { + buffer.appendInt32(957176926) + } + _data.authorization.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .appConfig(let _data): - return ("appConfig", [("hash", ConstructorParameterDescription(_data.hash)), ("config", ConstructorParameterDescription(_data.config))]) - case .appConfigNotModified: - return ("appConfigNotModified", []) + case .loginToken(let _data): + return ("loginToken", [("expires", ConstructorParameterDescription(_data.expires)), ("token", ConstructorParameterDescription(_data.token))]) + case .loginTokenMigrateTo(let _data): + return ("loginTokenMigrateTo", [("dcId", ConstructorParameterDescription(_data.dcId)), ("token", ConstructorParameterDescription(_data.token))]) + case .loginTokenSuccess(let _data): + return ("loginTokenSuccess", [("authorization", ConstructorParameterDescription(_data.authorization))]) } } - public static func parse_appConfig(_ reader: BufferReader) -> AppConfig? { + public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.JSONValue? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.JSONValue - } + var _2: Buffer? + _2 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.help.AppConfig.appConfig(Cons_appConfig(hash: _1!, config: _2!)) + return Api.auth.LoginToken.loginToken(Cons_loginToken(expires: _1!, token: _2!)) } else { return nil } } - public static func parse_appConfigNotModified(_ reader: BufferReader) -> AppConfig? { - return Api.help.AppConfig.appConfigNotModified + public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.LoginToken.loginTokenMigrateTo(Cons_loginTokenMigrateTo(dcId: _1!, token: _2!)) + } + else { + return nil + } + } + public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? { + var _1: Api.auth.Authorization? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + let _c1 = _1 != nil + if _c1 { + return Api.auth.LoginToken.loginTokenSuccess(Cons_loginTokenSuccess(authorization: _1!)) + } + else { + return nil + } } } } -public extension Api.help { - enum AppUpdate: TypeConstructorDescription { - public class Cons_appUpdate: TypeConstructorDescription { - public var flags: Int32 - public var id: Int32 - public var version: String - public var text: String - public var entities: [Api.MessageEntity] - public var document: Api.Document? - public var url: String? - public var sticker: Api.Document? - public init(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) { - self.flags = flags - self.id = id - self.version = version - self.text = text - self.entities = entities - self.document = document - self.url = url - self.sticker = sticker +public extension Api.auth { + enum PasskeyLoginOptions: TypeConstructorDescription { + public class Cons_passkeyLoginOptions: TypeConstructorDescription { + public var options: Api.DataJSON + public init(options: Api.DataJSON) { + self.options = options } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("appUpdate", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("version", ConstructorParameterDescription(self.version)), ("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities)), ("document", ConstructorParameterDescription(self.document)), ("url", ConstructorParameterDescription(self.url)), ("sticker", ConstructorParameterDescription(self.sticker))]) + return ("passkeyLoginOptions", [("options", ConstructorParameterDescription(self.options))]) } } - case appUpdate(Cons_appUpdate) - case noAppUpdate + case passkeyLoginOptions(Cons_passkeyLoginOptions) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .appUpdate(let _data): + case .passkeyLoginOptions(let _data): if boxed { - buffer.appendInt32(-860107216) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.id, buffer: buffer, boxed: false) - serializeString(_data.version, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities.count)) - for item in _data.entities { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.document!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.sticker!.serialize(buffer, true) - } - break - case .noAppUpdate: - if boxed { - buffer.appendInt32(-1000708810) + buffer.appendInt32(-503089271) } + _data.options.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .appUpdate(let _data): - return ("appUpdate", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("version", ConstructorParameterDescription(_data.version)), ("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities)), ("document", ConstructorParameterDescription(_data.document)), ("url", ConstructorParameterDescription(_data.url)), ("sticker", ConstructorParameterDescription(_data.sticker))]) - case .noAppUpdate: - return ("noAppUpdate", []) + case .passkeyLoginOptions(let _data): + return ("passkeyLoginOptions", [("options", ConstructorParameterDescription(_data.options))]) } } - public static func parse_appUpdate(_ reader: BufferReader) -> AppUpdate? { + public static func parse_passkeyLoginOptions(_ reader: BufferReader) -> PasskeyLoginOptions? { + var _1: Api.DataJSON? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + let _c1 = _1 != nil + if _c1 { + return Api.auth.PasskeyLoginOptions.passkeyLoginOptions(Cons_passkeyLoginOptions(options: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.auth { + enum PasswordRecovery: TypeConstructorDescription { + public class Cons_passwordRecovery: TypeConstructorDescription { + public var emailPattern: String + public init(emailPattern: String) { + self.emailPattern = emailPattern + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passwordRecovery", [("emailPattern", ConstructorParameterDescription(self.emailPattern))]) + } + } + case passwordRecovery(Cons_passwordRecovery) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passwordRecovery(let _data): + if boxed { + buffer.appendInt32(326715557) + } + serializeString(_data.emailPattern, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passwordRecovery(let _data): + return ("passwordRecovery", [("emailPattern", ConstructorParameterDescription(_data.emailPattern))]) + } + } + + public static func parse_passwordRecovery(_ reader: BufferReader) -> PasswordRecovery? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.auth.PasswordRecovery.passwordRecovery(Cons_passwordRecovery(emailPattern: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.auth { + enum SentCode: TypeConstructorDescription { + public class Cons_sentCode: TypeConstructorDescription { + public var flags: Int32 + public var type: Api.auth.SentCodeType + public var phoneCodeHash: String + public var nextType: Api.auth.CodeType? + public var timeout: Int32? + public init(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?) { + self.flags = flags + self.type = type + self.phoneCodeHash = phoneCodeHash + self.nextType = nextType + self.timeout = timeout + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCode", [("flags", ConstructorParameterDescription(self.flags)), ("type", ConstructorParameterDescription(self.type)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("nextType", ConstructorParameterDescription(self.nextType)), ("timeout", ConstructorParameterDescription(self.timeout))]) + } + } + public class Cons_sentCodePaymentRequired: TypeConstructorDescription { + public var storeProduct: String + public var phoneCodeHash: String + public var supportEmailAddress: String + public var supportEmailSubject: String + public var premiumDays: Int32 + public var currency: String + public var amount: Int64 + public init(storeProduct: String, phoneCodeHash: String, supportEmailAddress: String, supportEmailSubject: String, premiumDays: Int32, currency: String, amount: Int64) { + self.storeProduct = storeProduct + self.phoneCodeHash = phoneCodeHash + self.supportEmailAddress = supportEmailAddress + self.supportEmailSubject = supportEmailSubject + self.premiumDays = premiumDays + self.currency = currency + self.amount = amount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodePaymentRequired", [("storeProduct", ConstructorParameterDescription(self.storeProduct)), ("phoneCodeHash", ConstructorParameterDescription(self.phoneCodeHash)), ("supportEmailAddress", ConstructorParameterDescription(self.supportEmailAddress)), ("supportEmailSubject", ConstructorParameterDescription(self.supportEmailSubject)), ("premiumDays", ConstructorParameterDescription(self.premiumDays)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount))]) + } + } + public class Cons_sentCodeSuccess: TypeConstructorDescription { + public var authorization: Api.auth.Authorization + public init(authorization: Api.auth.Authorization) { + self.authorization = authorization + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeSuccess", [("authorization", ConstructorParameterDescription(self.authorization))]) + } + } + case sentCode(Cons_sentCode) + case sentCodePaymentRequired(Cons_sentCodePaymentRequired) + case sentCodeSuccess(Cons_sentCodeSuccess) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sentCode(let _data): + if boxed { + buffer.appendInt32(1577067778) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.type.serialize(buffer, true) + serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.nextType!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.timeout!, buffer: buffer, boxed: false) + } + break + case .sentCodePaymentRequired(let _data): + if boxed { + buffer.appendInt32(-125665601) + } + serializeString(_data.storeProduct, buffer: buffer, boxed: false) + serializeString(_data.phoneCodeHash, buffer: buffer, boxed: false) + serializeString(_data.supportEmailAddress, buffer: buffer, boxed: false) + serializeString(_data.supportEmailSubject, buffer: buffer, boxed: false) + serializeInt32(_data.premiumDays, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + break + case .sentCodeSuccess(let _data): + if boxed { + buffer.appendInt32(596704836) + } + _data.authorization.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sentCode(let _data): + return ("sentCode", [("flags", ConstructorParameterDescription(_data.flags)), ("type", ConstructorParameterDescription(_data.type)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("nextType", ConstructorParameterDescription(_data.nextType)), ("timeout", ConstructorParameterDescription(_data.timeout))]) + case .sentCodePaymentRequired(let _data): + return ("sentCodePaymentRequired", [("storeProduct", ConstructorParameterDescription(_data.storeProduct)), ("phoneCodeHash", ConstructorParameterDescription(_data.phoneCodeHash)), ("supportEmailAddress", ConstructorParameterDescription(_data.supportEmailAddress)), ("supportEmailSubject", ConstructorParameterDescription(_data.supportEmailSubject)), ("premiumDays", ConstructorParameterDescription(_data.premiumDays)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount))]) + case .sentCodeSuccess(let _data): + return ("sentCodeSuccess", [("authorization", ConstructorParameterDescription(_data.authorization))]) + } + } + + public static func parse_sentCode(_ reader: BufferReader) -> SentCode? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Api.auth.SentCodeType? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.auth.SentCodeType + } + var _3: String? + _3 = parseString(reader) + var _4: Api.auth.CodeType? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.auth.CodeType + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.SentCode.sentCode(Cons_sentCode(flags: _1!, type: _2!, phoneCodeHash: _3!, nextType: _4, timeout: _5)) + } + else { + return nil + } + } + public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) var _3: String? _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - var _6: Api.Document? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.Document - } - } - var _7: String? - if Int(_1!) & Int(1 << 2) != 0 { - _7 = parseString(reader) - } - var _8: Api.Document? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Document - } - } + var _5: Int32? + _5 = reader.readInt32() + var _6: String? + _6 = parseString(reader) + var _7: Int64? + _7 = reader.readInt64() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.help.AppUpdate.appUpdate(Cons_appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8)) + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.auth.SentCode.sentCodePaymentRequired(Cons_sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!, supportEmailAddress: _3!, supportEmailSubject: _4!, premiumDays: _5!, currency: _6!, amount: _7!)) } else { return nil } } - public static func parse_noAppUpdate(_ reader: BufferReader) -> AppUpdate? { - return Api.help.AppUpdate.noAppUpdate + public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? { + var _1: Api.auth.Authorization? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCode.sentCodeSuccess(Cons_sentCodeSuccess(authorization: _1!)) + } + else { + return nil + } } } } -public extension Api.help { - enum CountriesList: TypeConstructorDescription { - public class Cons_countriesList: TypeConstructorDescription { - public var countries: [Api.help.Country] - public var hash: Int32 - public init(countries: [Api.help.Country], hash: Int32) { - self.countries = countries - self.hash = hash +public extension Api.auth { + enum SentCodeType: TypeConstructorDescription { + public class Cons_sentCodeTypeApp: TypeConstructorDescription { + public var length: Int32 + public init(length: Int32) { + self.length = length } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("countriesList", [("countries", ConstructorParameterDescription(self.countries)), ("hash", ConstructorParameterDescription(self.hash))]) + return ("sentCodeTypeApp", [("length", ConstructorParameterDescription(self.length))]) } } - case countriesList(Cons_countriesList) - case countriesListNotModified + public class Cons_sentCodeTypeCall: TypeConstructorDescription { + public var length: Int32 + public init(length: Int32) { + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeCall", [("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_sentCodeTypeEmailCode: TypeConstructorDescription { + public var flags: Int32 + public var emailPattern: String + public var length: Int32 + public var resetAvailablePeriod: Int32? + public var resetPendingDate: Int32? + public init(flags: Int32, emailPattern: String, length: Int32, resetAvailablePeriod: Int32?, resetPendingDate: Int32?) { + self.flags = flags + self.emailPattern = emailPattern + self.length = length + self.resetAvailablePeriod = resetAvailablePeriod + self.resetPendingDate = resetPendingDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeEmailCode", [("flags", ConstructorParameterDescription(self.flags)), ("emailPattern", ConstructorParameterDescription(self.emailPattern)), ("length", ConstructorParameterDescription(self.length)), ("resetAvailablePeriod", ConstructorParameterDescription(self.resetAvailablePeriod)), ("resetPendingDate", ConstructorParameterDescription(self.resetPendingDate))]) + } + } + public class Cons_sentCodeTypeFirebaseSms: TypeConstructorDescription { + public var flags: Int32 + public var nonce: Buffer? + public var playIntegrityProjectId: Int64? + public var playIntegrityNonce: Buffer? + public var receipt: String? + public var pushTimeout: Int32? + public var length: Int32 + public init(flags: Int32, nonce: Buffer?, playIntegrityProjectId: Int64?, playIntegrityNonce: Buffer?, receipt: String?, pushTimeout: Int32?, length: Int32) { + self.flags = flags + self.nonce = nonce + self.playIntegrityProjectId = playIntegrityProjectId + self.playIntegrityNonce = playIntegrityNonce + self.receipt = receipt + self.pushTimeout = pushTimeout + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeFirebaseSms", [("flags", ConstructorParameterDescription(self.flags)), ("nonce", ConstructorParameterDescription(self.nonce)), ("playIntegrityProjectId", ConstructorParameterDescription(self.playIntegrityProjectId)), ("playIntegrityNonce", ConstructorParameterDescription(self.playIntegrityNonce)), ("receipt", ConstructorParameterDescription(self.receipt)), ("pushTimeout", ConstructorParameterDescription(self.pushTimeout)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_sentCodeTypeFlashCall: TypeConstructorDescription { + public var pattern: String + public init(pattern: String) { + self.pattern = pattern + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeFlashCall", [("pattern", ConstructorParameterDescription(self.pattern))]) + } + } + public class Cons_sentCodeTypeFragmentSms: TypeConstructorDescription { + public var url: String + public var length: Int32 + public init(url: String, length: Int32) { + self.url = url + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeFragmentSms", [("url", ConstructorParameterDescription(self.url)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_sentCodeTypeMissedCall: TypeConstructorDescription { + public var prefix: String + public var length: Int32 + public init(prefix: String, length: Int32) { + self.prefix = prefix + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeMissedCall", [("prefix", ConstructorParameterDescription(self.prefix)), ("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_sentCodeTypeSetUpEmailRequired: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeSetUpEmailRequired", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + public class Cons_sentCodeTypeSms: TypeConstructorDescription { + public var length: Int32 + public init(length: Int32) { + self.length = length + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeSms", [("length", ConstructorParameterDescription(self.length))]) + } + } + public class Cons_sentCodeTypeSmsPhrase: TypeConstructorDescription { + public var flags: Int32 + public var beginning: String? + public init(flags: Int32, beginning: String?) { + self.flags = flags + self.beginning = beginning + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeSmsPhrase", [("flags", ConstructorParameterDescription(self.flags)), ("beginning", ConstructorParameterDescription(self.beginning))]) + } + } + public class Cons_sentCodeTypeSmsWord: TypeConstructorDescription { + public var flags: Int32 + public var beginning: String? + public init(flags: Int32, beginning: String?) { + self.flags = flags + self.beginning = beginning + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentCodeTypeSmsWord", [("flags", ConstructorParameterDescription(self.flags)), ("beginning", ConstructorParameterDescription(self.beginning))]) + } + } + case sentCodeTypeApp(Cons_sentCodeTypeApp) + case sentCodeTypeCall(Cons_sentCodeTypeCall) + case sentCodeTypeEmailCode(Cons_sentCodeTypeEmailCode) + case sentCodeTypeFirebaseSms(Cons_sentCodeTypeFirebaseSms) + case sentCodeTypeFlashCall(Cons_sentCodeTypeFlashCall) + case sentCodeTypeFragmentSms(Cons_sentCodeTypeFragmentSms) + case sentCodeTypeMissedCall(Cons_sentCodeTypeMissedCall) + case sentCodeTypeSetUpEmailRequired(Cons_sentCodeTypeSetUpEmailRequired) + case sentCodeTypeSms(Cons_sentCodeTypeSms) + case sentCodeTypeSmsPhrase(Cons_sentCodeTypeSmsPhrase) + case sentCodeTypeSmsWord(Cons_sentCodeTypeSmsWord) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .countriesList(let _data): + case .sentCodeTypeApp(let _data): if boxed { - buffer.appendInt32(-2016381538) + buffer.appendInt32(1035688326) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.countries.count)) - for item in _data.countries { - item.serialize(buffer, true) - } - serializeInt32(_data.hash, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) break - case .countriesListNotModified: + case .sentCodeTypeCall(let _data): if boxed { - buffer.appendInt32(-1815339214) + buffer.appendInt32(1398007207) + } + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .sentCodeTypeEmailCode(let _data): + if boxed { + buffer.appendInt32(-196020837) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.emailPattern, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.resetAvailablePeriod!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.resetPendingDate!, buffer: buffer, boxed: false) + } + break + case .sentCodeTypeFirebaseSms(let _data): + if boxed { + buffer.appendInt32(10475318) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeBytes(_data.nonce!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.playIntegrityProjectId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeBytes(_data.playIntegrityNonce!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.receipt!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.pushTimeout!, buffer: buffer, boxed: false) + } + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .sentCodeTypeFlashCall(let _data): + if boxed { + buffer.appendInt32(-1425815847) + } + serializeString(_data.pattern, buffer: buffer, boxed: false) + break + case .sentCodeTypeFragmentSms(let _data): + if boxed { + buffer.appendInt32(-648651719) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .sentCodeTypeMissedCall(let _data): + if boxed { + buffer.appendInt32(-2113903484) + } + serializeString(_data.prefix, buffer: buffer, boxed: false) + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .sentCodeTypeSetUpEmailRequired(let _data): + if boxed { + buffer.appendInt32(-1521934870) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + break + case .sentCodeTypeSms(let _data): + if boxed { + buffer.appendInt32(-1073693790) + } + serializeInt32(_data.length, buffer: buffer, boxed: false) + break + case .sentCodeTypeSmsPhrase(let _data): + if boxed { + buffer.appendInt32(-1284008785) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.beginning!, buffer: buffer, boxed: false) + } + break + case .sentCodeTypeSmsWord(let _data): + if boxed { + buffer.appendInt32(-1542017919) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.beginning!, buffer: buffer, boxed: false) } break } @@ -219,187 +626,207 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .countriesList(let _data): - return ("countriesList", [("countries", ConstructorParameterDescription(_data.countries)), ("hash", ConstructorParameterDescription(_data.hash))]) - case .countriesListNotModified: - return ("countriesListNotModified", []) + case .sentCodeTypeApp(let _data): + return ("sentCodeTypeApp", [("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeCall(let _data): + return ("sentCodeTypeCall", [("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeEmailCode(let _data): + return ("sentCodeTypeEmailCode", [("flags", ConstructorParameterDescription(_data.flags)), ("emailPattern", ConstructorParameterDescription(_data.emailPattern)), ("length", ConstructorParameterDescription(_data.length)), ("resetAvailablePeriod", ConstructorParameterDescription(_data.resetAvailablePeriod)), ("resetPendingDate", ConstructorParameterDescription(_data.resetPendingDate))]) + case .sentCodeTypeFirebaseSms(let _data): + return ("sentCodeTypeFirebaseSms", [("flags", ConstructorParameterDescription(_data.flags)), ("nonce", ConstructorParameterDescription(_data.nonce)), ("playIntegrityProjectId", ConstructorParameterDescription(_data.playIntegrityProjectId)), ("playIntegrityNonce", ConstructorParameterDescription(_data.playIntegrityNonce)), ("receipt", ConstructorParameterDescription(_data.receipt)), ("pushTimeout", ConstructorParameterDescription(_data.pushTimeout)), ("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeFlashCall(let _data): + return ("sentCodeTypeFlashCall", [("pattern", ConstructorParameterDescription(_data.pattern))]) + case .sentCodeTypeFragmentSms(let _data): + return ("sentCodeTypeFragmentSms", [("url", ConstructorParameterDescription(_data.url)), ("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeMissedCall(let _data): + return ("sentCodeTypeMissedCall", [("prefix", ConstructorParameterDescription(_data.prefix)), ("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeSetUpEmailRequired(let _data): + return ("sentCodeTypeSetUpEmailRequired", [("flags", ConstructorParameterDescription(_data.flags))]) + case .sentCodeTypeSms(let _data): + return ("sentCodeTypeSms", [("length", ConstructorParameterDescription(_data.length))]) + case .sentCodeTypeSmsPhrase(let _data): + return ("sentCodeTypeSmsPhrase", [("flags", ConstructorParameterDescription(_data.flags)), ("beginning", ConstructorParameterDescription(_data.beginning))]) + case .sentCodeTypeSmsWord(let _data): + return ("sentCodeTypeSmsWord", [("flags", ConstructorParameterDescription(_data.flags)), ("beginning", ConstructorParameterDescription(_data.beginning))]) } } - public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { - var _1: [Api.help.Country]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) + public static func parse_sentCodeTypeApp(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeApp(Cons_sentCodeTypeApp(length: _1!)) } + else { + return nil + } + } + public static func parse_sentCodeTypeCall(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeCall(Cons_sentCodeTypeCall(length: _1!)) + } + else { + return nil + } + } + public static func parse_sentCodeTypeEmailCode(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.auth.SentCodeType.sentCodeTypeEmailCode(Cons_sentCodeTypeEmailCode(flags: _1!, emailPattern: _2!, length: _3!, resetAvailablePeriod: _4, resetPendingDate: _5)) + } + else { + return nil + } + } + public static func parse_sentCodeTypeFirebaseSms(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseBytes(reader) + } + var _3: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = reader.readInt64() + } + var _4: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseBytes(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = parseString(reader) + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.auth.SentCodeType.sentCodeTypeFirebaseSms(Cons_sentCodeTypeFirebaseSms(flags: _1!, nonce: _2, playIntegrityProjectId: _3, playIntegrityNonce: _4, receipt: _5, pushTimeout: _6, length: _7!)) + } + else { + return nil + } + } + public static func parse_sentCodeTypeFlashCall(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeFlashCall(Cons_sentCodeTypeFlashCall(pattern: _1!)) + } + else { + return nil + } + } + public static func parse_sentCodeTypeFragmentSms(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) var _2: Int32? _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.help.CountriesList.countriesList(Cons_countriesList(countries: _1!, hash: _2!)) + return Api.auth.SentCodeType.sentCodeTypeFragmentSms(Cons_sentCodeTypeFragmentSms(url: _1!, length: _2!)) } else { return nil } } - public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { - return Api.help.CountriesList.countriesListNotModified - } - } -} -public extension Api.help { - enum Country: TypeConstructorDescription { - public class Cons_country: TypeConstructorDescription { - public var flags: Int32 - public var iso2: String - public var defaultName: String - public var name: String? - public var countryCodes: [Api.help.CountryCode] - public init(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode]) { - self.flags = flags - self.iso2 = iso2 - self.defaultName = defaultName - self.name = name - self.countryCodes = countryCodes + public static func parse_sentCodeTypeMissedCall(_ reader: BufferReader) -> SentCodeType? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeMissedCall(Cons_sentCodeTypeMissedCall(prefix: _1!, length: _2!)) } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("country", [("flags", ConstructorParameterDescription(self.flags)), ("iso2", ConstructorParameterDescription(self.iso2)), ("defaultName", ConstructorParameterDescription(self.defaultName)), ("name", ConstructorParameterDescription(self.name)), ("countryCodes", ConstructorParameterDescription(self.countryCodes))]) + else { + return nil } } - case country(Cons_country) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .country(let _data): - if boxed { - buffer.appendInt32(-1014526429) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.iso2, buffer: buffer, boxed: false) - serializeString(_data.defaultName, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.name!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.countryCodes.count)) - for item in _data.countryCodes { - item.serialize(buffer, true) - } - break + public static func parse_sentCodeTypeSetUpEmailRequired(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeSetUpEmailRequired(Cons_sentCodeTypeSetUpEmailRequired(flags: _1!)) + } + else { + return nil } } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .country(let _data): - return ("country", [("flags", ConstructorParameterDescription(_data.flags)), ("iso2", ConstructorParameterDescription(_data.iso2)), ("defaultName", ConstructorParameterDescription(_data.defaultName)), ("name", ConstructorParameterDescription(_data.name)), ("countryCodes", ConstructorParameterDescription(_data.countryCodes))]) + public static func parse_sentCodeTypeSms(_ reader: BufferReader) -> SentCodeType? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.auth.SentCodeType.sentCodeTypeSms(Cons_sentCodeTypeSms(length: _1!)) + } + else { + return nil } } - - public static func parse_country(_ reader: BufferReader) -> Country? { + public static func parse_sentCodeTypeSmsPhrase(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) - } - var _5: [Api.help.CountryCode]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.CountryCode.self) + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.Country.country(Cons_country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeSmsPhrase(Cons_sentCodeTypeSmsPhrase(flags: _1!, beginning: _2)) } else { return nil } } - } -} -public extension Api.help { - enum CountryCode: TypeConstructorDescription { - public class Cons_countryCode: TypeConstructorDescription { - public var flags: Int32 - public var countryCode: String - public var prefixes: [String]? - public var patterns: [String]? - public init(flags: Int32, countryCode: String, prefixes: [String]?, patterns: [String]?) { - self.flags = flags - self.countryCode = countryCode - self.prefixes = prefixes - self.patterns = patterns - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("countryCode", [("flags", ConstructorParameterDescription(self.flags)), ("countryCode", ConstructorParameterDescription(self.countryCode)), ("prefixes", ConstructorParameterDescription(self.prefixes)), ("patterns", ConstructorParameterDescription(self.patterns))]) - } - } - case countryCode(Cons_countryCode) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .countryCode(let _data): - if boxed { - buffer.appendInt32(1107543535) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.countryCode, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.prefixes!.count)) - for item in _data.prefixes! { - serializeString(item, buffer: buffer, boxed: false) - } - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.patterns!.count)) - for item in _data.patterns! { - serializeString(item, buffer: buffer, boxed: false) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .countryCode(let _data): - return ("countryCode", [("flags", ConstructorParameterDescription(_data.flags)), ("countryCode", ConstructorParameterDescription(_data.countryCode)), ("prefixes", ConstructorParameterDescription(_data.prefixes)), ("patterns", ConstructorParameterDescription(_data.patterns))]) - } - } - - public static func parse_countryCode(_ reader: BufferReader) -> CountryCode? { + public static func parse_sentCodeTypeSmsWord(_ reader: BufferReader) -> SentCodeType? { var _1: Int32? _1 = reader.readInt32() var _2: String? - _2 = parseString(reader) - var _3: [String]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - } - var _4: [String]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.CountryCode.countryCode(Cons_countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + if _c1 && _c2 { + return Api.auth.SentCodeType.sentCodeTypeSmsWord(Cons_sentCodeTypeSmsWord(flags: _1!, beginning: _2)) } else { return nil @@ -407,43 +834,197 @@ public extension Api.help { } } } -public extension Api.help { - enum DeepLinkInfo: TypeConstructorDescription { - public class Cons_deepLinkInfo: TypeConstructorDescription { +public extension Api.bots { + enum AccessSettings: TypeConstructorDescription { + public class Cons_accessSettings: TypeConstructorDescription { public var flags: Int32 - public var message: String - public var entities: [Api.MessageEntity]? - public init(flags: Int32, message: String, entities: [Api.MessageEntity]?) { + public var addUsers: [Api.User]? + public init(flags: Int32, addUsers: [Api.User]?) { self.flags = flags - self.message = message - self.entities = entities + self.addUsers = addUsers } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("deepLinkInfo", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities))]) + return ("accessSettings", [("flags", ConstructorParameterDescription(self.flags)), ("addUsers", ConstructorParameterDescription(self.addUsers))]) } } - case deepLinkInfo(Cons_deepLinkInfo) - case deepLinkInfoEmpty + case accessSettings(Cons_accessSettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .deepLinkInfo(let _data): + case .accessSettings(let _data): if boxed { - buffer.appendInt32(1783556146) + buffer.appendInt32(-585121901) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.message, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 1) != 0 { buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities!.count)) - for item in _data.entities! { + buffer.appendInt32(Int32(_data.addUsers!.count)) + for item in _data.addUsers! { item.serialize(buffer, true) } } break - case .deepLinkInfoEmpty: + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .accessSettings(let _data): + return ("accessSettings", [("flags", ConstructorParameterDescription(_data.flags)), ("addUsers", ConstructorParameterDescription(_data.addUsers))]) + } + } + + public static func parse_accessSettings(_ reader: BufferReader) -> AccessSettings? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.User]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + if _c1 && _c2 { + return Api.bots.AccessSettings.accessSettings(Cons_accessSettings(flags: _1!, addUsers: _2)) + } + else { + return nil + } + } + } +} +public extension Api.bots { + enum BotInfo: TypeConstructorDescription { + public class Cons_botInfo: TypeConstructorDescription { + public var name: String + public var about: String + public var description: String + public init(name: String, about: String, description: String) { + self.name = name + self.about = about + self.description = description + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("botInfo", [("name", ConstructorParameterDescription(self.name)), ("about", ConstructorParameterDescription(self.about)), ("description", ConstructorParameterDescription(self.description))]) + } + } + case botInfo(Cons_botInfo) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botInfo(let _data): if boxed { - buffer.appendInt32(1722786150) + buffer.appendInt32(-391678544) + } + serializeString(_data.name, buffer: buffer, boxed: false) + serializeString(_data.about, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .botInfo(let _data): + return ("botInfo", [("name", ConstructorParameterDescription(_data.name)), ("about", ConstructorParameterDescription(_data.about)), ("description", ConstructorParameterDescription(_data.description))]) + } + } + + public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.bots.BotInfo.botInfo(Cons_botInfo(name: _1!, about: _2!, description: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.bots { + enum ExportedBotToken: TypeConstructorDescription { + public class Cons_exportedBotToken: TypeConstructorDescription { + public var token: String + public init(token: String) { + self.token = token + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedBotToken", [("token", ConstructorParameterDescription(self.token))]) + } + } + case exportedBotToken(Cons_exportedBotToken) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedBotToken(let _data): + if boxed { + buffer.appendInt32(1012971041) + } + serializeString(_data.token, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedBotToken(let _data): + return ("exportedBotToken", [("token", ConstructorParameterDescription(_data.token))]) + } + } + + public static func parse_exportedBotToken(_ reader: BufferReader) -> ExportedBotToken? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.bots.ExportedBotToken.exportedBotToken(Cons_exportedBotToken(token: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.bots { + enum PopularAppBots: TypeConstructorDescription { + public class Cons_popularAppBots: TypeConstructorDescription { + public var flags: Int32 + public var nextOffset: String? + public var users: [Api.User] + public init(flags: Int32, nextOffset: String?, users: [Api.User]) { + self.flags = flags + self.nextOffset = nextOffset + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("popularAppBots", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case popularAppBots(Cons_popularAppBots) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .popularAppBots(let _data): + if boxed { + buffer.appendInt32(428978491) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } break } @@ -451,76 +1032,27 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .deepLinkInfo(let _data): - return ("deepLinkInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities))]) - case .deepLinkInfoEmpty: - return ("deepLinkInfoEmpty", []) + case .popularAppBots(let _data): + return ("popularAppBots", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_deepLinkInfo(_ reader: BufferReader) -> DeepLinkInfo? { + public static func parse_popularAppBots(_ reader: BufferReader) -> PopularAppBots? { var _1: Int32? _1 = reader.readInt32() var _2: String? - _2 = parseString(reader) - var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.help.DeepLinkInfo.deepLinkInfo(Cons_deepLinkInfo(flags: _1!, message: _2!, entities: _3)) - } - else { - return nil - } - } - public static func parse_deepLinkInfoEmpty(_ reader: BufferReader) -> DeepLinkInfo? { - return Api.help.DeepLinkInfo.deepLinkInfoEmpty - } - } -} -public extension Api.help { - enum InviteText: TypeConstructorDescription { - public class Cons_inviteText: TypeConstructorDescription { - public var message: String - public init(message: String) { - self.message = message - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inviteText", [("message", ConstructorParameterDescription(self.message))]) - } - } - case inviteText(Cons_inviteText) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inviteText(let _data): - if boxed { - buffer.appendInt32(415997816) - } - serializeString(_data.message, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inviteText(let _data): - return ("inviteText", [("message", ConstructorParameterDescription(_data.message))]) - } - } - - public static func parse_inviteText(_ reader: BufferReader) -> InviteText? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.help.InviteText.inviteText(Cons_inviteText(message: _1!)) + return Api.bots.PopularAppBots.popularAppBots(Cons_popularAppBots(flags: _1!, nextOffset: _2, users: _3!)) } else { return nil @@ -528,34 +1060,36 @@ public extension Api.help { } } } -public extension Api.help { - enum PassportConfig: TypeConstructorDescription { - public class Cons_passportConfig: TypeConstructorDescription { - public var hash: Int32 - public var countriesLangs: Api.DataJSON - public init(hash: Int32, countriesLangs: Api.DataJSON) { - self.hash = hash - self.countriesLangs = countriesLangs +public extension Api.bots { + enum PreviewInfo: TypeConstructorDescription { + public class Cons_previewInfo: TypeConstructorDescription { + public var media: [Api.BotPreviewMedia] + public var langCodes: [String] + public init(media: [Api.BotPreviewMedia], langCodes: [String]) { + self.media = media + self.langCodes = langCodes } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("passportConfig", [("hash", ConstructorParameterDescription(self.hash)), ("countriesLangs", ConstructorParameterDescription(self.countriesLangs))]) + return ("previewInfo", [("media", ConstructorParameterDescription(self.media)), ("langCodes", ConstructorParameterDescription(self.langCodes))]) } } - case passportConfig(Cons_passportConfig) - case passportConfigNotModified + case previewInfo(Cons_previewInfo) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .passportConfig(let _data): + case .previewInfo(let _data): if boxed { - buffer.appendInt32(-1600596305) + buffer.appendInt32(212278628) } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - _data.countriesLangs.serialize(buffer, true) - break - case .passportConfigNotModified: - if boxed { - buffer.appendInt32(-1078332329) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.media.count)) + for item in _data.media { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.langCodes.count)) + for item in _data.langCodes { + serializeString(item, buffer: buffer, boxed: false) } break } @@ -563,121 +1097,24 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .passportConfig(let _data): - return ("passportConfig", [("hash", ConstructorParameterDescription(_data.hash)), ("countriesLangs", ConstructorParameterDescription(_data.countriesLangs))]) - case .passportConfigNotModified: - return ("passportConfigNotModified", []) + case .previewInfo(let _data): + return ("previewInfo", [("media", ConstructorParameterDescription(_data.media)), ("langCodes", ConstructorParameterDescription(_data.langCodes))]) } } - public static func parse_passportConfig(_ reader: BufferReader) -> PassportConfig? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + public static func parse_previewInfo(_ reader: BufferReader) -> PreviewInfo? { + var _1: [Api.BotPreviewMedia]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self) + } + var _2: [String]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.help.PassportConfig.passportConfig(Cons_passportConfig(hash: _1!, countriesLangs: _2!)) - } - else { - return nil - } - } - public static func parse_passportConfigNotModified(_ reader: BufferReader) -> PassportConfig? { - return Api.help.PassportConfig.passportConfigNotModified - } - } -} -public extension Api.help { - enum PeerColorOption: TypeConstructorDescription { - public class Cons_peerColorOption: TypeConstructorDescription { - public var flags: Int32 - public var colorId: Int32 - public var colors: Api.help.PeerColorSet? - public var darkColors: Api.help.PeerColorSet? - public var channelMinLevel: Int32? - public var groupMinLevel: Int32? - public init(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) { - self.flags = flags - self.colorId = colorId - self.colors = colors - self.darkColors = darkColors - self.channelMinLevel = channelMinLevel - self.groupMinLevel = groupMinLevel - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColorOption", [("flags", ConstructorParameterDescription(self.flags)), ("colorId", ConstructorParameterDescription(self.colorId)), ("colors", ConstructorParameterDescription(self.colors)), ("darkColors", ConstructorParameterDescription(self.darkColors)), ("channelMinLevel", ConstructorParameterDescription(self.channelMinLevel)), ("groupMinLevel", ConstructorParameterDescription(self.groupMinLevel))]) - } - } - case peerColorOption(Cons_peerColorOption) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerColorOption(let _data): - if boxed { - buffer.appendInt32(-1377014082) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.colorId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.colors!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.darkColors!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.channelMinLevel!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.groupMinLevel!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerColorOption(let _data): - return ("peerColorOption", [("flags", ConstructorParameterDescription(_data.flags)), ("colorId", ConstructorParameterDescription(_data.colorId)), ("colors", ConstructorParameterDescription(_data.colors)), ("darkColors", ConstructorParameterDescription(_data.darkColors)), ("channelMinLevel", ConstructorParameterDescription(_data.channelMinLevel)), ("groupMinLevel", ConstructorParameterDescription(_data.groupMinLevel))]) - } - } - - public static func parse_peerColorOption(_ reader: BufferReader) -> PeerColorOption? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.help.PeerColorSet? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet - } - } - var _4: Api.help.PeerColorSet? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet - } - } - var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _6 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PeerColorOption.peerColorOption(Cons_peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6)) + return Api.bots.PreviewInfo.previewInfo(Cons_previewInfo(media: _1!, langCodes: _2!)) } else { return nil @@ -685,450 +1122,43 @@ public extension Api.help { } } } -public extension Api.help { - enum PeerColorSet: TypeConstructorDescription { - public class Cons_peerColorProfileSet: TypeConstructorDescription { - public var paletteColors: [Int32] - public var bgColors: [Int32] - public var storyColors: [Int32] - public init(paletteColors: [Int32], bgColors: [Int32], storyColors: [Int32]) { - self.paletteColors = paletteColors - self.bgColors = bgColors - self.storyColors = storyColors +public extension Api.bots { + enum RequestedButton: TypeConstructorDescription { + public class Cons_requestedButton: TypeConstructorDescription { + public var webappReqId: String + public init(webappReqId: String) { + self.webappReqId = webappReqId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColorProfileSet", [("paletteColors", ConstructorParameterDescription(self.paletteColors)), ("bgColors", ConstructorParameterDescription(self.bgColors)), ("storyColors", ConstructorParameterDescription(self.storyColors))]) + return ("requestedButton", [("webappReqId", ConstructorParameterDescription(self.webappReqId))]) } } - public class Cons_peerColorSet: TypeConstructorDescription { - public var colors: [Int32] - public init(colors: [Int32]) { - self.colors = colors - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColorSet", [("colors", ConstructorParameterDescription(self.colors))]) - } - } - case peerColorProfileSet(Cons_peerColorProfileSet) - case peerColorSet(Cons_peerColorSet) + case requestedButton(Cons_requestedButton) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerColorProfileSet(let _data): + case .requestedButton(let _data): if boxed { - buffer.appendInt32(1987928555) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.paletteColors.count)) - for item in _data.paletteColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.bgColors.count)) - for item in _data.bgColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.storyColors.count)) - for item in _data.storyColors { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .peerColorSet(let _data): - if boxed { - buffer.appendInt32(639736408) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.colors.count)) - for item in _data.colors { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(-247743273) } + serializeString(_data.webappReqId, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .peerColorProfileSet(let _data): - return ("peerColorProfileSet", [("paletteColors", ConstructorParameterDescription(_data.paletteColors)), ("bgColors", ConstructorParameterDescription(_data.bgColors)), ("storyColors", ConstructorParameterDescription(_data.storyColors))]) - case .peerColorSet(let _data): - return ("peerColorSet", [("colors", ConstructorParameterDescription(_data.colors))]) + case .requestedButton(let _data): + return ("requestedButton", [("webappReqId", ConstructorParameterDescription(_data.webappReqId))]) } } - public static func parse_peerColorProfileSet(_ reader: BufferReader) -> PeerColorSet? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: [Int32]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _3: [Int32]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.help.PeerColorSet.peerColorProfileSet(Cons_peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!)) - } - else { - return nil - } - } - public static func parse_peerColorSet(_ reader: BufferReader) -> PeerColorSet? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.help.PeerColorSet.peerColorSet(Cons_peerColorSet(colors: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.help { - enum PeerColors: TypeConstructorDescription { - public class Cons_peerColors: TypeConstructorDescription { - public var hash: Int32 - public var colors: [Api.help.PeerColorOption] - public init(hash: Int32, colors: [Api.help.PeerColorOption]) { - self.hash = hash - self.colors = colors - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerColors", [("hash", ConstructorParameterDescription(self.hash)), ("colors", ConstructorParameterDescription(self.colors))]) - } - } - case peerColors(Cons_peerColors) - case peerColorsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerColors(let _data): - if boxed { - buffer.appendInt32(16313608) - } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.colors.count)) - for item in _data.colors { - item.serialize(buffer, true) - } - break - case .peerColorsNotModified: - if boxed { - buffer.appendInt32(732034510) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerColors(let _data): - return ("peerColors", [("hash", ConstructorParameterDescription(_data.hash)), ("colors", ConstructorParameterDescription(_data.colors))]) - case .peerColorsNotModified: - return ("peerColorsNotModified", []) - } - } - - public static func parse_peerColors(_ reader: BufferReader) -> PeerColors? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.help.PeerColorOption]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.PeerColorOption.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.PeerColors.peerColors(Cons_peerColors(hash: _1!, colors: _2!)) - } - else { - return nil - } - } - public static func parse_peerColorsNotModified(_ reader: BufferReader) -> PeerColors? { - return Api.help.PeerColors.peerColorsNotModified - } - } -} -public extension Api.help { - enum PremiumPromo: TypeConstructorDescription { - public class Cons_premiumPromo: TypeConstructorDescription { - public var statusText: String - public var statusEntities: [Api.MessageEntity] - public var videoSections: [String] - public var videos: [Api.Document] - public var periodOptions: [Api.PremiumSubscriptionOption] - public var users: [Api.User] - public init(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) { - self.statusText = statusText - self.statusEntities = statusEntities - self.videoSections = videoSections - self.videos = videos - self.periodOptions = periodOptions - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("premiumPromo", [("statusText", ConstructorParameterDescription(self.statusText)), ("statusEntities", ConstructorParameterDescription(self.statusEntities)), ("videoSections", ConstructorParameterDescription(self.videoSections)), ("videos", ConstructorParameterDescription(self.videos)), ("periodOptions", ConstructorParameterDescription(self.periodOptions)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case premiumPromo(Cons_premiumPromo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .premiumPromo(let _data): - if boxed { - buffer.appendInt32(1395946908) - } - serializeString(_data.statusText, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.statusEntities.count)) - for item in _data.statusEntities { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.videoSections.count)) - for item in _data.videoSections { - serializeString(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.videos.count)) - for item in _data.videos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.periodOptions.count)) - for item in _data.periodOptions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .premiumPromo(let _data): - return ("premiumPromo", [("statusText", ConstructorParameterDescription(_data.statusText)), ("statusEntities", ConstructorParameterDescription(_data.statusEntities)), ("videoSections", ConstructorParameterDescription(_data.videoSections)), ("videos", ConstructorParameterDescription(_data.videos)), ("periodOptions", ConstructorParameterDescription(_data.periodOptions)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { + public static func parse_requestedButton(_ reader: BufferReader) -> RequestedButton? { var _1: String? _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - var _3: [String]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _5: [Api.PremiumSubscriptionOption]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.help.PremiumPromo.premiumPromo(Cons_premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!)) - } - else { - return nil - } - } - } -} -public extension Api.help { - enum PromoData: TypeConstructorDescription { - public class Cons_promoData: TypeConstructorDescription { - public var flags: Int32 - public var expires: Int32 - public var peer: Api.Peer? - public var psaType: String? - public var psaMessage: String? - public var pendingSuggestions: [String] - public var dismissedSuggestions: [String] - public var customPendingSuggestion: Api.PendingSuggestion? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, expires: Int32, peer: Api.Peer?, psaType: String?, psaMessage: String?, pendingSuggestions: [String], dismissedSuggestions: [String], customPendingSuggestion: Api.PendingSuggestion?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.expires = expires - self.peer = peer - self.psaType = psaType - self.psaMessage = psaMessage - self.pendingSuggestions = pendingSuggestions - self.dismissedSuggestions = dismissedSuggestions - self.customPendingSuggestion = customPendingSuggestion - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("promoData", [("flags", ConstructorParameterDescription(self.flags)), ("expires", ConstructorParameterDescription(self.expires)), ("peer", ConstructorParameterDescription(self.peer)), ("psaType", ConstructorParameterDescription(self.psaType)), ("psaMessage", ConstructorParameterDescription(self.psaMessage)), ("pendingSuggestions", ConstructorParameterDescription(self.pendingSuggestions)), ("dismissedSuggestions", ConstructorParameterDescription(self.dismissedSuggestions)), ("customPendingSuggestion", ConstructorParameterDescription(self.customPendingSuggestion)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_promoDataEmpty: TypeConstructorDescription { - public var expires: Int32 - public init(expires: Int32) { - self.expires = expires - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("promoDataEmpty", [("expires", ConstructorParameterDescription(self.expires))]) - } - } - case promoData(Cons_promoData) - case promoDataEmpty(Cons_promoDataEmpty) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .promoData(let _data): - if boxed { - buffer.appendInt32(145021050) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.expires, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.peer!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.psaType!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.psaMessage!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.pendingSuggestions.count)) - for item in _data.pendingSuggestions { - serializeString(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dismissedSuggestions.count)) - for item in _data.dismissedSuggestions { - serializeString(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.customPendingSuggestion!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .promoDataEmpty(let _data): - if boxed { - buffer.appendInt32(-1728664459) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .promoData(let _data): - return ("promoData", [("flags", ConstructorParameterDescription(_data.flags)), ("expires", ConstructorParameterDescription(_data.expires)), ("peer", ConstructorParameterDescription(_data.peer)), ("psaType", ConstructorParameterDescription(_data.psaType)), ("psaMessage", ConstructorParameterDescription(_data.psaMessage)), ("pendingSuggestions", ConstructorParameterDescription(_data.pendingSuggestions)), ("dismissedSuggestions", ConstructorParameterDescription(_data.dismissedSuggestions)), ("customPendingSuggestion", ConstructorParameterDescription(_data.customPendingSuggestion)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .promoDataEmpty(let _data): - return ("promoDataEmpty", [("expires", ConstructorParameterDescription(_data.expires))]) - } - } - - public static func parse_promoData(_ reader: BufferReader) -> PromoData? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Peer? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = parseString(reader) - } - var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = parseString(reader) - } - var _6: [String]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - var _7: [String]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) - } - var _8: Api.PendingSuggestion? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PendingSuggestion - } - } - var _9: [Api.Chat]? - if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _10: [Api.User]? - if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 4) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.help.PromoData.promoData(Cons_promoData(flags: _1!, expires: _2!, peer: _3, psaType: _4, psaMessage: _5, pendingSuggestions: _6!, dismissedSuggestions: _7!, customPendingSuggestion: _8, chats: _9!, users: _10!)) - } - else { - return nil - } - } - public static func parse_promoDataEmpty(_ reader: BufferReader) -> PromoData? { - var _1: Int32? - _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.help.PromoData.promoDataEmpty(Cons_promoDataEmpty(expires: _1!)) + return Api.bots.RequestedButton.requestedButton(Cons_requestedButton(webappReqId: _1!)) } else { return nil @@ -1136,32 +1166,32 @@ public extension Api.help { } } } -public extension Api.help { - enum RecentMeUrls: TypeConstructorDescription { - public class Cons_recentMeUrls: TypeConstructorDescription { - public var urls: [Api.RecentMeUrl] +public extension Api.channels { + enum AdminLogResults: TypeConstructorDescription { + public class Cons_adminLogResults: TypeConstructorDescription { + public var events: [Api.ChannelAdminLogEvent] public var chats: [Api.Chat] public var users: [Api.User] - public init(urls: [Api.RecentMeUrl], chats: [Api.Chat], users: [Api.User]) { - self.urls = urls + public init(events: [Api.ChannelAdminLogEvent], chats: [Api.Chat], users: [Api.User]) { + self.events = events self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentMeUrls", [("urls", ConstructorParameterDescription(self.urls)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("adminLogResults", [("events", ConstructorParameterDescription(self.events)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case recentMeUrls(Cons_recentMeUrls) + case adminLogResults(Cons_adminLogResults) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .recentMeUrls(let _data): + case .adminLogResults(let _data): if boxed { - buffer.appendInt32(235081943) + buffer.appendInt32(-309659827) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.urls.count)) - for item in _data.urls { + buffer.appendInt32(Int32(_data.events.count)) + for item in _data.events { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1180,15 +1210,15 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .recentMeUrls(let _data): - return ("recentMeUrls", [("urls", ConstructorParameterDescription(_data.urls)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .adminLogResults(let _data): + return ("adminLogResults", [("events", ConstructorParameterDescription(_data.events)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_recentMeUrls(_ reader: BufferReader) -> RecentMeUrls? { - var _1: [Api.RecentMeUrl]? + public static func parse_adminLogResults(_ reader: BufferReader) -> AdminLogResults? { + var _1: [Api.ChannelAdminLogEvent]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentMeUrl.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelAdminLogEvent.self) } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -1202,7 +1232,7 @@ public extension Api.help { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.help.RecentMeUrls.recentMeUrls(Cons_recentMeUrls(urls: _1!, chats: _2!, users: _3!)) + return Api.channels.AdminLogResults.adminLogResults(Cons_adminLogResults(events: _1!, chats: _2!, users: _3!)) } else { return nil @@ -1210,139 +1240,39 @@ public extension Api.help { } } } -public extension Api.help { - enum Support: TypeConstructorDescription { - public class Cons_support: TypeConstructorDescription { - public var phoneNumber: String - public var user: Api.User - public init(phoneNumber: String, user: Api.User) { - self.phoneNumber = phoneNumber - self.user = user +public extension Api.channels { + enum ChannelParticipant: TypeConstructorDescription { + public class Cons_channelParticipant: TypeConstructorDescription { + public var participant: Api.ChannelParticipant + public var chats: [Api.Chat] + public var users: [Api.User] + public init(participant: Api.ChannelParticipant, chats: [Api.Chat], users: [Api.User]) { + self.participant = participant + self.chats = chats + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("support", [("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("user", ConstructorParameterDescription(self.user))]) + return ("channelParticipant", [("participant", ConstructorParameterDescription(self.participant)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case support(Cons_support) + case channelParticipant(Cons_channelParticipant) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .support(let _data): + case .channelParticipant(let _data): if boxed { - buffer.appendInt32(398898678) + buffer.appendInt32(-541588713) } - serializeString(_data.phoneNumber, buffer: buffer, boxed: false) - _data.user.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .support(let _data): - return ("support", [("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("user", ConstructorParameterDescription(_data.user))]) - } - } - - public static func parse_support(_ reader: BufferReader) -> Support? { - var _1: String? - _1 = parseString(reader) - var _2: Api.User? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.User - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.Support.support(Cons_support(phoneNumber: _1!, user: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.help { - enum SupportName: TypeConstructorDescription { - public class Cons_supportName: TypeConstructorDescription { - public var name: String - public init(name: String) { - self.name = name - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("supportName", [("name", ConstructorParameterDescription(self.name))]) - } - } - case supportName(Cons_supportName) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .supportName(let _data): - if boxed { - buffer.appendInt32(-1945767479) - } - serializeString(_data.name, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .supportName(let _data): - return ("supportName", [("name", ConstructorParameterDescription(_data.name))]) - } - } - - public static func parse_supportName(_ reader: BufferReader) -> SupportName? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.help.SupportName.supportName(Cons_supportName(name: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.help { - enum TermsOfService: TypeConstructorDescription { - public class Cons_termsOfService: TypeConstructorDescription { - public var flags: Int32 - public var id: Api.DataJSON - public var text: String - public var entities: [Api.MessageEntity] - public var minAgeConfirm: Int32? - public init(flags: Int32, id: Api.DataJSON, text: String, entities: [Api.MessageEntity], minAgeConfirm: Int32?) { - self.flags = flags - self.id = id - self.text = text - self.entities = entities - self.minAgeConfirm = minAgeConfirm - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("termsOfService", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities)), ("minAgeConfirm", ConstructorParameterDescription(self.minAgeConfirm))]) - } - } - case termsOfService(Cons_termsOfService) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .termsOfService(let _data): - if boxed { - buffer.appendInt32(2013922064) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.id.serialize(buffer, true) - serializeString(_data.text, buffer: buffer, boxed: false) + _data.participant.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities.count)) - for item in _data.entities { + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.minAgeConfirm!, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } break } @@ -1350,35 +1280,29 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .termsOfService(let _data): - return ("termsOfService", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities)), ("minAgeConfirm", ConstructorParameterDescription(_data.minAgeConfirm))]) + case .channelParticipant(let _data): + return ("channelParticipant", [("participant", ConstructorParameterDescription(_data.participant)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_termsOfService(_ reader: BufferReader) -> TermsOfService? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.DataJSON? + public static func parse_channelParticipant(_ reader: BufferReader) -> ChannelParticipant? { + var _1: Api.ChannelParticipant? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + _1 = Api.parse(reader, signature: signature) as? Api.ChannelParticipant } - var _3: String? - _3 = parseString(reader) - var _4: [Api.MessageEntity]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.help.TermsOfService.termsOfService(Cons_termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5)) + if _c1 && _c2 && _c3 { + return Api.channels.ChannelParticipant.channelParticipant(Cons_channelParticipant(participant: _1!, chats: _2!, users: _3!)) } else { return nil @@ -1386,265 +1310,52 @@ public extension Api.help { } } } -public extension Api.help { - enum TermsOfServiceUpdate: TypeConstructorDescription { - public class Cons_termsOfServiceUpdate: TypeConstructorDescription { - public var expires: Int32 - public var termsOfService: Api.help.TermsOfService - public init(expires: Int32, termsOfService: Api.help.TermsOfService) { - self.expires = expires - self.termsOfService = termsOfService +public extension Api.channels { + enum ChannelParticipants: TypeConstructorDescription { + public class Cons_channelParticipants: TypeConstructorDescription { + public var count: Int32 + public var participants: [Api.ChannelParticipant] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(count: Int32, participants: [Api.ChannelParticipant], chats: [Api.Chat], users: [Api.User]) { + self.count = count + self.participants = participants + self.chats = chats + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("termsOfServiceUpdate", [("expires", ConstructorParameterDescription(self.expires)), ("termsOfService", ConstructorParameterDescription(self.termsOfService))]) + return ("channelParticipants", [("count", ConstructorParameterDescription(self.count)), ("participants", ConstructorParameterDescription(self.participants)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - public class Cons_termsOfServiceUpdateEmpty: TypeConstructorDescription { - public var expires: Int32 - public init(expires: Int32) { - self.expires = expires - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("termsOfServiceUpdateEmpty", [("expires", ConstructorParameterDescription(self.expires))]) - } - } - case termsOfServiceUpdate(Cons_termsOfServiceUpdate) - case termsOfServiceUpdateEmpty(Cons_termsOfServiceUpdateEmpty) + case channelParticipants(Cons_channelParticipants) + case channelParticipantsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .termsOfServiceUpdate(let _data): + case .channelParticipants(let _data): if boxed { - buffer.appendInt32(686618977) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - _data.termsOfService.serialize(buffer, true) - break - case .termsOfServiceUpdateEmpty(let _data): - if boxed { - buffer.appendInt32(-483352705) - } - serializeInt32(_data.expires, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .termsOfServiceUpdate(let _data): - return ("termsOfServiceUpdate", [("expires", ConstructorParameterDescription(_data.expires)), ("termsOfService", ConstructorParameterDescription(_data.termsOfService))]) - case .termsOfServiceUpdateEmpty(let _data): - return ("termsOfServiceUpdateEmpty", [("expires", ConstructorParameterDescription(_data.expires))]) - } - } - - public static func parse_termsOfServiceUpdate(_ reader: BufferReader) -> TermsOfServiceUpdate? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.help.TermsOfService? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(Cons_termsOfServiceUpdate(expires: _1!, termsOfService: _2!)) - } - else { - return nil - } - } - public static func parse_termsOfServiceUpdateEmpty(_ reader: BufferReader) -> TermsOfServiceUpdate? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(Cons_termsOfServiceUpdateEmpty(expires: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.help { - enum TimezonesList: TypeConstructorDescription { - public class Cons_timezonesList: TypeConstructorDescription { - public var timezones: [Api.Timezone] - public var hash: Int32 - public init(timezones: [Api.Timezone], hash: Int32) { - self.timezones = timezones - self.hash = hash - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("timezonesList", [("timezones", ConstructorParameterDescription(self.timezones)), ("hash", ConstructorParameterDescription(self.hash))]) - } - } - case timezonesList(Cons_timezonesList) - case timezonesListNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .timezonesList(let _data): - if boxed { - buffer.appendInt32(2071260529) + buffer.appendInt32(-1699676497) } + serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.timezones.count)) - for item in _data.timezones { + buffer.appendInt32(Int32(_data.participants.count)) + for item in _data.participants { item.serialize(buffer, true) } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - break - case .timezonesListNotModified: - if boxed { - buffer.appendInt32(-1761146676) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .timezonesList(let _data): - return ("timezonesList", [("timezones", ConstructorParameterDescription(_data.timezones)), ("hash", ConstructorParameterDescription(_data.hash))]) - case .timezonesListNotModified: - return ("timezonesListNotModified", []) - } - } - - public static func parse_timezonesList(_ reader: BufferReader) -> TimezonesList? { - var _1: [Api.Timezone]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Timezone.self) - } - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.help.TimezonesList.timezonesList(Cons_timezonesList(timezones: _1!, hash: _2!)) - } - else { - return nil - } - } - public static func parse_timezonesListNotModified(_ reader: BufferReader) -> TimezonesList? { - return Api.help.TimezonesList.timezonesListNotModified - } - } -} -public extension Api.help { - enum UserInfo: TypeConstructorDescription { - public class Cons_userInfo: TypeConstructorDescription { - public var message: String - public var entities: [Api.MessageEntity] - public var author: String - public var date: Int32 - public init(message: String, entities: [Api.MessageEntity], author: String, date: Int32) { - self.message = message - self.entities = entities - self.author = author - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userInfo", [("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("author", ConstructorParameterDescription(self.author)), ("date", ConstructorParameterDescription(self.date))]) - } - } - case userInfo(Cons_userInfo) - case userInfoEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .userInfo(let _data): - if boxed { - buffer.appendInt32(32192344) - } - serializeString(_data.message, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.entities.count)) - for item in _data.entities { + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { item.serialize(buffer, true) } - serializeString(_data.author, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - case .userInfoEmpty: - if boxed { - buffer.appendInt32(-206688531) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .userInfo(let _data): - return ("userInfo", [("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("author", ConstructorParameterDescription(_data.author)), ("date", ConstructorParameterDescription(_data.date))]) - case .userInfoEmpty: - return ("userInfoEmpty", []) - } - } - - public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.MessageEntity]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) - } - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.help.UserInfo.userInfo(Cons_userInfo(message: _1!, entities: _2!, author: _3!, date: _4!)) - } - else { - return nil - } - } - public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? { - return Api.help.UserInfo.userInfoEmpty - } - } -} -public extension Api.messages { - enum AffectedFoundMessages: TypeConstructorDescription { - public class Cons_affectedFoundMessages: TypeConstructorDescription { - public var pts: Int32 - public var ptsCount: Int32 - public var offset: Int32 - public var messages: [Int32] - public init(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) { - self.pts = pts - self.ptsCount = ptsCount - self.offset = offset - self.messages = messages - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("affectedFoundMessages", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("offset", ConstructorParameterDescription(self.offset)), ("messages", ConstructorParameterDescription(self.messages))]) - } - } - case affectedFoundMessages(Cons_affectedFoundMessages) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedFoundMessages(let _data): - if boxed { - buffer.appendInt32(-275956116) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - serializeInt32(_data.offset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - serializeInt32(item, buffer: buffer, boxed: false) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .channelParticipantsNotModified: + if boxed { + buffer.appendInt32(-266911767) } break } @@ -1652,28 +1363,111 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .affectedFoundMessages(let _data): - return ("affectedFoundMessages", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("offset", ConstructorParameterDescription(_data.offset)), ("messages", ConstructorParameterDescription(_data.messages))]) + case .channelParticipants(let _data): + return ("channelParticipants", [("count", ConstructorParameterDescription(_data.count)), ("participants", ConstructorParameterDescription(_data.participants)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .channelParticipantsNotModified: + return ("channelParticipantsNotModified", []) } } - public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { + public static func parse_channelParticipants(_ reader: BufferReader) -> ChannelParticipants? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Int32]? + var _2: [Api.ChannelParticipant]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChannelParticipant.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.messages.AffectedFoundMessages.affectedFoundMessages(Cons_affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!)) + return Api.channels.ChannelParticipants.channelParticipants(Cons_channelParticipants(count: _1!, participants: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + public static func parse_channelParticipantsNotModified(_ reader: BufferReader) -> ChannelParticipants? { + return Api.channels.ChannelParticipants.channelParticipantsNotModified + } + } +} +public extension Api.channels { + enum SendAsPeers: TypeConstructorDescription { + public class Cons_sendAsPeers: TypeConstructorDescription { + public var peers: [Api.SendAsPeer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(peers: [Api.SendAsPeer], chats: [Api.Chat], users: [Api.User]) { + self.peers = peers + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sendAsPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case sendAsPeers(Cons_sendAsPeers) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendAsPeers(let _data): + if boxed { + buffer.appendInt32(-191450938) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sendAsPeers(let _data): + return ("sendAsPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_sendAsPeers(_ reader: BufferReader) -> SendAsPeers? { + var _1: [Api.SendAsPeer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SendAsPeer.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.channels.SendAsPeers.sendAsPeers(Cons_sendAsPeers(peers: _1!, chats: _2!, users: _3!)) } else { return nil @@ -1681,3 +1475,81 @@ public extension Api.messages { } } } +public extension Api.channels { + enum SponsoredMessageReportResult: TypeConstructorDescription { + public class Cons_sponsoredMessageReportResultChooseOption: TypeConstructorDescription { + public var title: String + public var options: [Api.SponsoredMessageReportOption] + public init(title: String, options: [Api.SponsoredMessageReportOption]) { + self.title = title + self.options = options + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sponsoredMessageReportResultChooseOption", [("title", ConstructorParameterDescription(self.title)), ("options", ConstructorParameterDescription(self.options))]) + } + } + case sponsoredMessageReportResultAdsHidden + case sponsoredMessageReportResultChooseOption(Cons_sponsoredMessageReportResultChooseOption) + case sponsoredMessageReportResultReported + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredMessageReportResultAdsHidden: + if boxed { + buffer.appendInt32(1044107055) + } + break + case .sponsoredMessageReportResultChooseOption(let _data): + if boxed { + buffer.appendInt32(-2073059774) + } + serializeString(_data.title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.options.count)) + for item in _data.options { + item.serialize(buffer, true) + } + break + case .sponsoredMessageReportResultReported: + if boxed { + buffer.appendInt32(-1384544183) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sponsoredMessageReportResultAdsHidden: + return ("sponsoredMessageReportResultAdsHidden", []) + case .sponsoredMessageReportResultChooseOption(let _data): + return ("sponsoredMessageReportResultChooseOption", [("title", ConstructorParameterDescription(_data.title)), ("options", ConstructorParameterDescription(_data.options))]) + case .sponsoredMessageReportResultReported: + return ("sponsoredMessageReportResultReported", []) + } + } + + public static func parse_sponsoredMessageReportResultAdsHidden(_ reader: BufferReader) -> SponsoredMessageReportResult? { + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultAdsHidden + } + public static func parse_sponsoredMessageReportResultChooseOption(_ reader: BufferReader) -> SponsoredMessageReportResult? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.SponsoredMessageReportOption]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessageReportOption.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultChooseOption(Cons_sponsoredMessageReportResultChooseOption(title: _1!, options: _2!)) + } + else { + return nil + } + } + public static func parse_sponsoredMessageReportResultReported(_ reader: BufferReader) -> SponsoredMessageReportResult? { + return Api.channels.SponsoredMessageReportResult.sponsoredMessageReportResultReported + } + } +} diff --git a/submodules/TelegramApi/Sources/Api34.swift b/submodules/TelegramApi/Sources/Api34.swift index f03ad72730..8f8345bbcf 100644 --- a/submodules/TelegramApi/Sources/Api34.swift +++ b/submodules/TelegramApi/Sources/Api34.swift @@ -1,688 +1,63 @@ -public extension Api.messages { - enum AffectedHistory: TypeConstructorDescription { - public class Cons_affectedHistory: TypeConstructorDescription { - public var pts: Int32 - public var ptsCount: Int32 - public var offset: Int32 - public init(pts: Int32, ptsCount: Int32, offset: Int32) { - self.pts = pts - self.ptsCount = ptsCount - self.offset = offset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("affectedHistory", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("offset", ConstructorParameterDescription(self.offset))]) - } - } - case affectedHistory(Cons_affectedHistory) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedHistory(let _data): - if boxed { - buffer.appendInt32(-1269012015) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - serializeInt32(_data.offset, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .affectedHistory(let _data): - return ("affectedHistory", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("offset", ConstructorParameterDescription(_data.offset))]) - } - } - - public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AffectedHistory.affectedHistory(Cons_affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum AffectedMessages: TypeConstructorDescription { - public class Cons_affectedMessages: TypeConstructorDescription { - public var pts: Int32 - public var ptsCount: Int32 - public init(pts: Int32, ptsCount: Int32) { - self.pts = pts - self.ptsCount = ptsCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("affectedMessages", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) - } - } - case affectedMessages(Cons_affectedMessages) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .affectedMessages(let _data): - if boxed { - buffer.appendInt32(-2066640507) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .affectedMessages(let _data): - return ("affectedMessages", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) - } - } - - public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AffectedMessages.affectedMessages(Cons_affectedMessages(pts: _1!, ptsCount: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum AllStickers: TypeConstructorDescription { - public class Cons_allStickers: TypeConstructorDescription { - public var hash: Int64 - public var sets: [Api.StickerSet] - public init(hash: Int64, sets: [Api.StickerSet]) { - self.hash = hash - self.sets = sets - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("allStickers", [("hash", ConstructorParameterDescription(self.hash)), ("sets", ConstructorParameterDescription(self.sets))]) - } - } - case allStickers(Cons_allStickers) - case allStickersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .allStickers(let _data): - if boxed { - buffer.appendInt32(-843329861) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sets.count)) - for item in _data.sets { - item.serialize(buffer, true) - } - break - case .allStickersNotModified: - if boxed { - buffer.appendInt32(-395967805) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .allStickers(let _data): - return ("allStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("sets", ConstructorParameterDescription(_data.sets))]) - case .allStickersNotModified: - return ("allStickersNotModified", []) - } - } - - public static func parse_allStickers(_ reader: BufferReader) -> AllStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerSet]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSet.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AllStickers.allStickers(Cons_allStickers(hash: _1!, sets: _2!)) - } - else { - return nil - } - } - public static func parse_allStickersNotModified(_ reader: BufferReader) -> AllStickers? { - return Api.messages.AllStickers.allStickersNotModified - } - } -} -public extension Api.messages { - enum ArchivedStickers: TypeConstructorDescription { - public class Cons_archivedStickers: TypeConstructorDescription { - public var count: Int32 - public var sets: [Api.StickerSetCovered] - public init(count: Int32, sets: [Api.StickerSetCovered]) { - self.count = count - self.sets = sets - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("archivedStickers", [("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets))]) - } - } - case archivedStickers(Cons_archivedStickers) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .archivedStickers(let _data): - if boxed { - buffer.appendInt32(1338747336) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sets.count)) - for item in _data.sets { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .archivedStickers(let _data): - return ("archivedStickers", [("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets))]) - } - } - - public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ArchivedStickers.archivedStickers(Cons_archivedStickers(count: _1!, sets: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum AvailableEffects: TypeConstructorDescription { - public class Cons_availableEffects: TypeConstructorDescription { - public var hash: Int32 - public var effects: [Api.AvailableEffect] - public var documents: [Api.Document] - public init(hash: Int32, effects: [Api.AvailableEffect], documents: [Api.Document]) { - self.hash = hash - self.effects = effects - self.documents = documents - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("availableEffects", [("hash", ConstructorParameterDescription(self.hash)), ("effects", ConstructorParameterDescription(self.effects)), ("documents", ConstructorParameterDescription(self.documents))]) - } - } - case availableEffects(Cons_availableEffects) - case availableEffectsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .availableEffects(let _data): - if boxed { - buffer.appendInt32(-1109696146) - } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.effects.count)) - for item in _data.effects { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { - item.serialize(buffer, true) - } - break - case .availableEffectsNotModified: - if boxed { - buffer.appendInt32(-772957605) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .availableEffects(let _data): - return ("availableEffects", [("hash", ConstructorParameterDescription(_data.hash)), ("effects", ConstructorParameterDescription(_data.effects)), ("documents", ConstructorParameterDescription(_data.documents))]) - case .availableEffectsNotModified: - return ("availableEffectsNotModified", []) - } - } - - public static func parse_availableEffects(_ reader: BufferReader) -> AvailableEffects? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.AvailableEffect]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableEffect.self) - } - var _3: [Api.Document]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.AvailableEffects.availableEffects(Cons_availableEffects(hash: _1!, effects: _2!, documents: _3!)) - } - else { - return nil - } - } - public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { - return Api.messages.AvailableEffects.availableEffectsNotModified - } - } -} -public extension Api.messages { - enum AvailableReactions: TypeConstructorDescription { - public class Cons_availableReactions: TypeConstructorDescription { - public var hash: Int32 - public var reactions: [Api.AvailableReaction] - public init(hash: Int32, reactions: [Api.AvailableReaction]) { - self.hash = hash - self.reactions = reactions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("availableReactions", [("hash", ConstructorParameterDescription(self.hash)), ("reactions", ConstructorParameterDescription(self.reactions))]) - } - } - case availableReactions(Cons_availableReactions) - case availableReactionsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .availableReactions(let _data): - if boxed { - buffer.appendInt32(1989032621) - } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.reactions.count)) - for item in _data.reactions { - item.serialize(buffer, true) - } - break - case .availableReactionsNotModified: - if boxed { - buffer.appendInt32(-1626924713) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .availableReactions(let _data): - return ("availableReactions", [("hash", ConstructorParameterDescription(_data.hash)), ("reactions", ConstructorParameterDescription(_data.reactions))]) - case .availableReactionsNotModified: - return ("availableReactionsNotModified", []) - } - } - - public static func parse_availableReactions(_ reader: BufferReader) -> AvailableReactions? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.AvailableReaction]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableReaction.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.AvailableReactions.availableReactions(Cons_availableReactions(hash: _1!, reactions: _2!)) - } - else { - return nil - } - } - public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? { - return Api.messages.AvailableReactions.availableReactionsNotModified - } - } -} -public extension Api.messages { - enum BotApp: TypeConstructorDescription { - public class Cons_botApp: TypeConstructorDescription { +public extension Api.chatlists { + enum ChatlistInvite: TypeConstructorDescription { + public class Cons_chatlistInvite: TypeConstructorDescription { public var flags: Int32 - public var app: Api.BotApp - public init(flags: Int32, app: Api.BotApp) { + public var title: Api.TextWithEntities + public var emoticon: String? + public var peers: [Api.Peer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, title: Api.TextWithEntities, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { self.flags = flags - self.app = app + self.title = title + self.emoticon = emoticon + self.peers = peers + self.chats = chats + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botApp", [("flags", ConstructorParameterDescription(self.flags)), ("app", ConstructorParameterDescription(self.app))]) + return ("chatlistInvite", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title)), ("emoticon", ConstructorParameterDescription(self.emoticon)), ("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case botApp(Cons_botApp) + public class Cons_chatlistInviteAlready: TypeConstructorDescription { + public var filterId: Int32 + public var missingPeers: [Api.Peer] + public var alreadyPeers: [Api.Peer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { + self.filterId = filterId + self.missingPeers = missingPeers + self.alreadyPeers = alreadyPeers + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatlistInviteAlready", [("filterId", ConstructorParameterDescription(self.filterId)), ("missingPeers", ConstructorParameterDescription(self.missingPeers)), ("alreadyPeers", ConstructorParameterDescription(self.alreadyPeers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case chatlistInvite(Cons_chatlistInvite) + case chatlistInviteAlready(Cons_chatlistInviteAlready) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .botApp(let _data): + case .chatlistInvite(let _data): if boxed { - buffer.appendInt32(-347034123) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.app.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botApp(let _data): - return ("botApp", [("flags", ConstructorParameterDescription(_data.flags)), ("app", ConstructorParameterDescription(_data.app))]) - } - } - - public static func parse_botApp(_ reader: BufferReader) -> BotApp? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.BotApp? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BotApp - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.BotApp.botApp(Cons_botApp(flags: _1!, app: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum BotCallbackAnswer: TypeConstructorDescription { - public class Cons_botCallbackAnswer: TypeConstructorDescription { - public var flags: Int32 - public var message: String? - public var url: String? - public var cacheTime: Int32 - public init(flags: Int32, message: String?, url: String?, cacheTime: Int32) { - self.flags = flags - self.message = message - self.url = url - self.cacheTime = cacheTime - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botCallbackAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("url", ConstructorParameterDescription(self.url)), ("cacheTime", ConstructorParameterDescription(self.cacheTime))]) - } - } - case botCallbackAnswer(Cons_botCallbackAnswer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botCallbackAnswer(let _data): - if boxed { - buffer.appendInt32(911761060) + buffer.appendInt32(-250687953) } serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.title.serialize(buffer, true) if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.message!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.url!, buffer: buffer, boxed: false) - } - serializeInt32(_data.cacheTime, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botCallbackAnswer(let _data): - return ("botCallbackAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("url", ConstructorParameterDescription(_data.url)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime))]) - } - } - - public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) - } - var _3: String? - if Int(_1!) & Int(1 << 2) != 0 { - _3 = parseString(reader) - } - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.BotCallbackAnswer.botCallbackAnswer(Cons_botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum BotPreparedInlineMessage: TypeConstructorDescription { - public class Cons_botPreparedInlineMessage: TypeConstructorDescription { - public var id: String - public var expireDate: Int32 - public init(id: String, expireDate: Int32) { - self.id = id - self.expireDate = expireDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botPreparedInlineMessage", [("id", ConstructorParameterDescription(self.id)), ("expireDate", ConstructorParameterDescription(self.expireDate))]) - } - } - case botPreparedInlineMessage(Cons_botPreparedInlineMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botPreparedInlineMessage(let _data): - if boxed { - buffer.appendInt32(-1899035375) - } - serializeString(_data.id, buffer: buffer, boxed: false) - serializeInt32(_data.expireDate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botPreparedInlineMessage(let _data): - return ("botPreparedInlineMessage", [("id", ConstructorParameterDescription(_data.id)), ("expireDate", ConstructorParameterDescription(_data.expireDate))]) - } - } - - public static func parse_botPreparedInlineMessage(_ reader: BufferReader) -> BotPreparedInlineMessage? { - var _1: String? - _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.BotPreparedInlineMessage.botPreparedInlineMessage(Cons_botPreparedInlineMessage(id: _1!, expireDate: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum BotResults: TypeConstructorDescription { - public class Cons_botResults: TypeConstructorDescription { - public var flags: Int32 - public var queryId: Int64 - public var nextOffset: String? - public var switchPm: Api.InlineBotSwitchPM? - public var switchWebview: Api.InlineBotWebView? - public var results: [Api.BotInlineResult] - public var cacheTime: Int32 - public var users: [Api.User] - public init(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) { - self.flags = flags - self.queryId = queryId - self.nextOffset = nextOffset - self.switchPm = switchPm - self.switchWebview = switchWebview - self.results = results - self.cacheTime = cacheTime - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("botResults", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("switchPm", ConstructorParameterDescription(self.switchPm)), ("switchWebview", ConstructorParameterDescription(self.switchWebview)), ("results", ConstructorParameterDescription(self.results)), ("cacheTime", ConstructorParameterDescription(self.cacheTime)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case botResults(Cons_botResults) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .botResults(let _data): - if boxed { - buffer.appendInt32(-534646026) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.queryId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.switchPm!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.switchWebview!.serialize(buffer, true) + serializeString(_data.emoticon!, buffer: buffer, boxed: false) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.results.count)) - for item in _data.results { + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { item.serialize(buffer, true) } - serializeInt32(_data.cacheTime, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .botResults(let _data): - return ("botResults", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("switchPm", ConstructorParameterDescription(_data.switchPm)), ("switchWebview", ConstructorParameterDescription(_data.switchWebview)), ("results", ConstructorParameterDescription(_data.results)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_botResults(_ reader: BufferReader) -> BotResults? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = parseString(reader) - } - var _4: Api.InlineBotSwitchPM? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM - } - } - var _5: Api.InlineBotWebView? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.InlineBotWebView - } - } - var _6: [Api.BotInlineResult]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) - } - var _7: Int32? - _7 = reader.readInt32() - var _8: [Api.User]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.BotResults.botResults(Cons_botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum ChatAdminsWithInvites: TypeConstructorDescription { - public class Cons_chatAdminsWithInvites: TypeConstructorDescription { - public var admins: [Api.ChatAdminWithInvites] - public var users: [Api.User] - public init(admins: [Api.ChatAdminWithInvites], users: [Api.User]) { - self.admins = admins - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatAdminsWithInvites", [("admins", ConstructorParameterDescription(self.admins)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case chatAdminsWithInvites(Cons_chatAdminsWithInvites) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chatAdminsWithInvites(let _data): - if boxed { - buffer.appendInt32(-1231326505) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.admins.count)) - for item in _data.admins { + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -691,60 +66,21 @@ public extension Api.messages { item.serialize(buffer, true) } break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .chatAdminsWithInvites(let _data): - return ("chatAdminsWithInvites", [("admins", ConstructorParameterDescription(_data.admins)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_chatAdminsWithInvites(_ reader: BufferReader) -> ChatAdminsWithInvites? { - var _1: [Api.ChatAdminWithInvites]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatAdminWithInvites.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(Cons_chatAdminsWithInvites(admins: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum ChatFull: TypeConstructorDescription { - public class Cons_chatFull: TypeConstructorDescription { - public var fullChat: Api.ChatFull - public var chats: [Api.Chat] - public var users: [Api.User] - public init(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User]) { - self.fullChat = fullChat - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatFull", [("fullChat", ConstructorParameterDescription(self.fullChat)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case chatFull(Cons_chatFull) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chatFull(let _data): + case .chatlistInviteAlready(let _data): if boxed { - buffer.appendInt32(-438840932) + buffer.appendInt32(-91752871) + } + serializeInt32(_data.filterId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.missingPeers.count)) + for item in _data.missingPeers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.alreadyPeers.count)) + for item in _data.alreadyPeers { + item.serialize(buffer, true) } - _data.fullChat.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -761,619 +97,59 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .chatFull(let _data): - return ("chatFull", [("fullChat", ConstructorParameterDescription(_data.fullChat)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .chatlistInvite(let _data): + return ("chatlistInvite", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title)), ("emoticon", ConstructorParameterDescription(_data.emoticon)), ("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .chatlistInviteAlready(let _data): + return ("chatlistInviteAlready", [("filterId", ConstructorParameterDescription(_data.filterId)), ("missingPeers", ConstructorParameterDescription(_data.missingPeers)), ("alreadyPeers", ConstructorParameterDescription(_data.alreadyPeers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { - var _1: Api.ChatFull? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ChatFull - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatFull.chatFull(Cons_chatFull(fullChat: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum ChatInviteImporters: TypeConstructorDescription { - public class Cons_chatInviteImporters: TypeConstructorDescription { - public var count: Int32 - public var importers: [Api.ChatInviteImporter] - public var users: [Api.User] - public init(count: Int32, importers: [Api.ChatInviteImporter], users: [Api.User]) { - self.count = count - self.importers = importers - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatInviteImporters", [("count", ConstructorParameterDescription(self.count)), ("importers", ConstructorParameterDescription(self.importers)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case chatInviteImporters(Cons_chatInviteImporters) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chatInviteImporters(let _data): - if boxed { - buffer.appendInt32(-2118733814) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.importers.count)) - for item in _data.importers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .chatInviteImporters(let _data): - return ("chatInviteImporters", [("count", ConstructorParameterDescription(_data.count)), ("importers", ConstructorParameterDescription(_data.importers)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_chatInviteImporters(_ reader: BufferReader) -> ChatInviteImporters? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.ChatInviteImporter]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatInviteImporter.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ChatInviteImporters.chatInviteImporters(Cons_chatInviteImporters(count: _1!, importers: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum Chats: TypeConstructorDescription { - public class Cons_chats: TypeConstructorDescription { - public var chats: [Api.Chat] - public init(chats: [Api.Chat]) { - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chats", [("chats", ConstructorParameterDescription(self.chats))]) - } - } - public class Cons_chatsSlice: TypeConstructorDescription { - public var count: Int32 - public var chats: [Api.Chat] - public init(count: Int32, chats: [Api.Chat]) { - self.count = count - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("chatsSlice", [("count", ConstructorParameterDescription(self.count)), ("chats", ConstructorParameterDescription(self.chats))]) - } - } - case chats(Cons_chats) - case chatsSlice(Cons_chatsSlice) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .chats(let _data): - if boxed { - buffer.appendInt32(1694474197) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - break - case .chatsSlice(let _data): - if boxed { - buffer.appendInt32(-1663561404) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .chats(let _data): - return ("chats", [("chats", ConstructorParameterDescription(_data.chats))]) - case .chatsSlice(let _data): - return ("chatsSlice", [("count", ConstructorParameterDescription(_data.count)), ("chats", ConstructorParameterDescription(_data.chats))]) - } - } - - public static func parse_chats(_ reader: BufferReader) -> Chats? { - var _1: [Api.Chat]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.messages.Chats.chats(Cons_chats(chats: _1!)) - } - else { - return nil - } - } - public static func parse_chatsSlice(_ reader: BufferReader) -> Chats? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Chats.chatsSlice(Cons_chatsSlice(count: _1!, chats: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum CheckedHistoryImportPeer: TypeConstructorDescription { - public class Cons_checkedHistoryImportPeer: TypeConstructorDescription { - public var confirmText: String - public init(confirmText: String) { - self.confirmText = confirmText - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("checkedHistoryImportPeer", [("confirmText", ConstructorParameterDescription(self.confirmText))]) - } - } - case checkedHistoryImportPeer(Cons_checkedHistoryImportPeer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .checkedHistoryImportPeer(let _data): - if boxed { - buffer.appendInt32(-1571952873) - } - serializeString(_data.confirmText, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .checkedHistoryImportPeer(let _data): - return ("checkedHistoryImportPeer", [("confirmText", ConstructorParameterDescription(_data.confirmText))]) - } - } - - public static func parse_checkedHistoryImportPeer(_ reader: BufferReader) -> CheckedHistoryImportPeer? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(Cons_checkedHistoryImportPeer(confirmText: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum ComposedMessageWithAI: TypeConstructorDescription { - public class Cons_composedMessageWithAI: TypeConstructorDescription { - public var flags: Int32 - public var resultText: Api.TextWithEntities - public var diffText: Api.TextWithEntities? - public init(flags: Int32, resultText: Api.TextWithEntities, diffText: Api.TextWithEntities?) { - self.flags = flags - self.resultText = resultText - self.diffText = diffText - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("composedMessageWithAI", [("flags", ConstructorParameterDescription(self.flags)), ("resultText", ConstructorParameterDescription(self.resultText)), ("diffText", ConstructorParameterDescription(self.diffText))]) - } - } - case composedMessageWithAI(Cons_composedMessageWithAI) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .composedMessageWithAI(let _data): - if boxed { - buffer.appendInt32(-1864913414) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.resultText.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.diffText!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .composedMessageWithAI(let _data): - return ("composedMessageWithAI", [("flags", ConstructorParameterDescription(_data.flags)), ("resultText", ConstructorParameterDescription(_data.resultText)), ("diffText", ConstructorParameterDescription(_data.diffText))]) - } - } - - public static func parse_composedMessageWithAI(_ reader: BufferReader) -> ComposedMessageWithAI? { + public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? { var _1: Int32? _1 = reader.readInt32() var _2: Api.TextWithEntities? if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } - var _3: Api.TextWithEntities? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: [Api.Peer]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ComposedMessageWithAI.composedMessageWithAI(Cons_composedMessageWithAI(flags: _1!, resultText: _2!, diffText: _3)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum DhConfig: TypeConstructorDescription { - public class Cons_dhConfig: TypeConstructorDescription { - public var g: Int32 - public var p: Buffer - public var version: Int32 - public var random: Buffer - public init(g: Int32, p: Buffer, version: Int32, random: Buffer) { - self.g = g - self.p = p - self.version = version - self.random = random - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dhConfig", [("g", ConstructorParameterDescription(self.g)), ("p", ConstructorParameterDescription(self.p)), ("version", ConstructorParameterDescription(self.version)), ("random", ConstructorParameterDescription(self.random))]) - } - } - public class Cons_dhConfigNotModified: TypeConstructorDescription { - public var random: Buffer - public init(random: Buffer) { - self.random = random - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dhConfigNotModified", [("random", ConstructorParameterDescription(self.random))]) - } - } - case dhConfig(Cons_dhConfig) - case dhConfigNotModified(Cons_dhConfigNotModified) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .dhConfig(let _data): - if boxed { - buffer.appendInt32(740433629) - } - serializeInt32(_data.g, buffer: buffer, boxed: false) - serializeBytes(_data.p, buffer: buffer, boxed: false) - serializeInt32(_data.version, buffer: buffer, boxed: false) - serializeBytes(_data.random, buffer: buffer, boxed: false) - break - case .dhConfigNotModified(let _data): - if boxed { - buffer.appendInt32(-1058912715) - } - serializeBytes(_data.random, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .dhConfig(let _data): - return ("dhConfig", [("g", ConstructorParameterDescription(_data.g)), ("p", ConstructorParameterDescription(_data.p)), ("version", ConstructorParameterDescription(_data.version)), ("random", ConstructorParameterDescription(_data.random))]) - case .dhConfigNotModified(let _data): - return ("dhConfigNotModified", [("random", ConstructorParameterDescription(_data.random))]) - } - } - - public static func parse_dhConfig(_ reader: BufferReader) -> DhConfig? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Int32? - _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.DhConfig.dhConfig(Cons_dhConfig(g: _1!, p: _2!, version: _3!, random: _4!)) + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.chatlists.ChatlistInvite.chatlistInvite(Cons_chatlistInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!)) } else { return nil } } - public static func parse_dhConfigNotModified(_ reader: BufferReader) -> DhConfig? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.messages.DhConfig.dhConfigNotModified(Cons_dhConfigNotModified(random: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum DialogFilters: TypeConstructorDescription { - public class Cons_dialogFilters: TypeConstructorDescription { - public var flags: Int32 - public var filters: [Api.DialogFilter] - public init(flags: Int32, filters: [Api.DialogFilter]) { - self.flags = flags - self.filters = filters - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dialogFilters", [("flags", ConstructorParameterDescription(self.flags)), ("filters", ConstructorParameterDescription(self.filters))]) - } - } - case dialogFilters(Cons_dialogFilters) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .dialogFilters(let _data): - if boxed { - buffer.appendInt32(718878489) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.filters.count)) - for item in _data.filters { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .dialogFilters(let _data): - return ("dialogFilters", [("flags", ConstructorParameterDescription(_data.flags)), ("filters", ConstructorParameterDescription(_data.filters))]) - } - } - - public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { + public static func parse_chatlistInviteAlready(_ reader: BufferReader) -> ChatlistInvite? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.DialogFilter]? + var _2: [Api.Peer]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.DialogFilters.dialogFilters(Cons_dialogFilters(flags: _1!, filters: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum Dialogs: TypeConstructorDescription { - public class Cons_dialogs: TypeConstructorDescription { - public var dialogs: [Api.Dialog] - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { - self.dialogs = dialogs - self.messages = messages - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_dialogsNotModified: TypeConstructorDescription { - public var count: Int32 - public init(count: Int32) { - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dialogsNotModified", [("count", ConstructorParameterDescription(self.count))]) - } - } - public class Cons_dialogsSlice: TypeConstructorDescription { - public var count: Int32 - public var dialogs: [Api.Dialog] - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { - self.count = count - self.dialogs = dialogs - self.messages = messages - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("dialogsSlice", [("count", ConstructorParameterDescription(self.count)), ("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case dialogs(Cons_dialogs) - case dialogsNotModified(Cons_dialogsNotModified) - case dialogsSlice(Cons_dialogsSlice) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .dialogs(let _data): - if boxed { - buffer.appendInt32(364538944) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dialogs.count)) - for item in _data.dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .dialogsNotModified(let _data): - if boxed { - buffer.appendInt32(-253500010) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - case .dialogsSlice(let _data): - if boxed { - buffer.appendInt32(1910543603) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dialogs.count)) - for item in _data.dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .dialogs(let _data): - return ("dialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .dialogsNotModified(let _data): - return ("dialogsNotModified", [("count", ConstructorParameterDescription(_data.count))]) - case .dialogsSlice(let _data): - return ("dialogsSlice", [("count", ConstructorParameterDescription(_data.count)), ("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { - var _1: [Api.Dialog]? + var _3: [Api.Peer]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Dialogs.dialogs(Cons_dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.Dialogs.dialogsNotModified(Cons_dialogsNotModified(count: _1!)) - } - else { - return nil - } - } - public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Dialog]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _3: [Api.Message]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } var _4: [Api.Chat]? if let _ = reader.readInt32() { @@ -1389,7 +165,7 @@ public extension Api.messages { let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.Dialogs.dialogsSlice(Cons_dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)) + return Api.chatlists.ChatlistInvite.chatlistInviteAlready(Cons_chatlistInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!)) } else { return nil @@ -1397,55 +173,34 @@ public extension Api.messages { } } } -public extension Api.messages { - enum DiscussionMessage: TypeConstructorDescription { - public class Cons_discussionMessage: TypeConstructorDescription { - public var flags: Int32 - public var messages: [Api.Message] - public var maxId: Int32? - public var readInboxMaxId: Int32? - public var readOutboxMaxId: Int32? - public var unreadCount: Int32 +public extension Api.chatlists { + enum ChatlistUpdates: TypeConstructorDescription { + public class Cons_chatlistUpdates: TypeConstructorDescription { + public var missingPeers: [Api.Peer] public var chats: [Api.Chat] public var users: [Api.User] - public init(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.messages = messages - self.maxId = maxId - self.readInboxMaxId = readInboxMaxId - self.readOutboxMaxId = readOutboxMaxId - self.unreadCount = unreadCount + public init(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { + self.missingPeers = missingPeers self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("discussionMessage", [("flags", ConstructorParameterDescription(self.flags)), ("messages", ConstructorParameterDescription(self.messages)), ("maxId", ConstructorParameterDescription(self.maxId)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("chatlistUpdates", [("missingPeers", ConstructorParameterDescription(self.missingPeers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case discussionMessage(Cons_discussionMessage) + case chatlistUpdates(Cons_chatlistUpdates) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .discussionMessage(let _data): + case .chatlistUpdates(let _data): if boxed { - buffer.appendInt32(-1506535550) + buffer.appendInt32(-1816295539) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { + buffer.appendInt32(Int32(_data.missingPeers.count)) + for item in _data.missingPeers { item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.maxId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.readInboxMaxId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.readOutboxMaxId!, buffer: buffer, boxed: false) - } - serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -1462,50 +217,29 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .discussionMessage(let _data): - return ("discussionMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("messages", ConstructorParameterDescription(_data.messages)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .chatlistUpdates(let _data): + return ("chatlistUpdates", [("missingPeers", ConstructorParameterDescription(_data.missingPeers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Message]? + public static func parse_chatlistUpdates(_ reader: BufferReader) -> ChatlistUpdates? { + var _1: [Api.Peer]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) } - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - _6 = reader.readInt32() - var _7: [Api.Chat]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _8: [Api.User]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.DiscussionMessage.discussionMessage(Cons_discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!)) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.chatlists.ChatlistUpdates.chatlistUpdates(Cons_chatlistUpdates(missingPeers: _1!, chats: _2!, users: _3!)) } else { return nil @@ -1513,52 +247,97 @@ public extension Api.messages { } } } -public extension Api.messages { - enum EmojiGameInfo: TypeConstructorDescription { - public class Cons_emojiGameDiceInfo: TypeConstructorDescription { - public var flags: Int32 - public var gameHash: String - public var prevStake: Int64 - public var currentStreak: Int32 - public var params: [Int32] - public var playsLeft: Int32? - public init(flags: Int32, gameHash: String, prevStake: Int64, currentStreak: Int32, params: [Int32], playsLeft: Int32?) { - self.flags = flags - self.gameHash = gameHash - self.prevStake = prevStake - self.currentStreak = currentStreak - self.params = params - self.playsLeft = playsLeft +public extension Api.chatlists { + enum ExportedChatlistInvite: TypeConstructorDescription { + public class Cons_exportedChatlistInvite: TypeConstructorDescription { + public var filter: Api.DialogFilter + public var invite: Api.ExportedChatlistInvite + public init(filter: Api.DialogFilter, invite: Api.ExportedChatlistInvite) { + self.filter = filter + self.invite = invite } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emojiGameDiceInfo", [("flags", ConstructorParameterDescription(self.flags)), ("gameHash", ConstructorParameterDescription(self.gameHash)), ("prevStake", ConstructorParameterDescription(self.prevStake)), ("currentStreak", ConstructorParameterDescription(self.currentStreak)), ("params", ConstructorParameterDescription(self.params)), ("playsLeft", ConstructorParameterDescription(self.playsLeft))]) + return ("exportedChatlistInvite", [("filter", ConstructorParameterDescription(self.filter)), ("invite", ConstructorParameterDescription(self.invite))]) } } - case emojiGameDiceInfo(Cons_emojiGameDiceInfo) - case emojiGameUnavailable + case exportedChatlistInvite(Cons_exportedChatlistInvite) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiGameDiceInfo(let _data): + case .exportedChatlistInvite(let _data): if boxed { - buffer.appendInt32(1155883043) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.gameHash, buffer: buffer, boxed: false) - serializeInt64(_data.prevStake, buffer: buffer, boxed: false) - serializeInt32(_data.currentStreak, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.params.count)) - for item in _data.params { - serializeInt32(item, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.playsLeft!, buffer: buffer, boxed: false) + buffer.appendInt32(283567014) } + _data.filter.serialize(buffer, true) + _data.invite.serialize(buffer, true) break - case .emojiGameUnavailable: + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedChatlistInvite(let _data): + return ("exportedChatlistInvite", [("filter", ConstructorParameterDescription(_data.filter)), ("invite", ConstructorParameterDescription(_data.invite))]) + } + } + + public static func parse_exportedChatlistInvite(_ reader: BufferReader) -> ExportedChatlistInvite? { + var _1: Api.DialogFilter? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.DialogFilter + } + var _2: Api.ExportedChatlistInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.chatlists.ExportedChatlistInvite.exportedChatlistInvite(Cons_exportedChatlistInvite(filter: _1!, invite: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.chatlists { + enum ExportedInvites: TypeConstructorDescription { + public class Cons_exportedInvites: TypeConstructorDescription { + public var invites: [Api.ExportedChatlistInvite] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(invites: [Api.ExportedChatlistInvite], chats: [Api.Chat], users: [Api.User]) { + self.invites = invites + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedInvites", [("invites", ConstructorParameterDescription(self.invites)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case exportedInvites(Cons_exportedInvites) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedInvites(let _data): if boxed { - buffer.appendInt32(1508266805) + buffer.appendInt32(279670215) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.invites.count)) + for item in _data.invites { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } break } @@ -1566,136 +345,1180 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .emojiGameDiceInfo(let _data): - return ("emojiGameDiceInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("gameHash", ConstructorParameterDescription(_data.gameHash)), ("prevStake", ConstructorParameterDescription(_data.prevStake)), ("currentStreak", ConstructorParameterDescription(_data.currentStreak)), ("params", ConstructorParameterDescription(_data.params)), ("playsLeft", ConstructorParameterDescription(_data.playsLeft))]) - case .emojiGameUnavailable: - return ("emojiGameUnavailable", []) + case .exportedInvites(let _data): + return ("exportedInvites", [("invites", ConstructorParameterDescription(_data.invites)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_emojiGameDiceInfo(_ reader: BufferReader) -> EmojiGameInfo? { + public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? { + var _1: [Api.ExportedChatlistInvite]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatlistInvite.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.chatlists.ExportedInvites.exportedInvites(Cons_exportedInvites(invites: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum Blocked: TypeConstructorDescription { + public class Cons_blocked: TypeConstructorDescription { + public var blocked: [Api.PeerBlocked] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) { + self.blocked = blocked + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("blocked", [("blocked", ConstructorParameterDescription(self.blocked)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_blockedSlice: TypeConstructorDescription { + public var count: Int32 + public var blocked: [Api.PeerBlocked] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(count: Int32, blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User]) { + self.count = count + self.blocked = blocked + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("blockedSlice", [("count", ConstructorParameterDescription(self.count)), ("blocked", ConstructorParameterDescription(self.blocked)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case blocked(Cons_blocked) + case blockedSlice(Cons_blockedSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .blocked(let _data): + if boxed { + buffer.appendInt32(182326673) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocked.count)) + for item in _data.blocked { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .blockedSlice(let _data): + if boxed { + buffer.appendInt32(-513392236) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.blocked.count)) + for item in _data.blocked { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .blocked(let _data): + return ("blocked", [("blocked", ConstructorParameterDescription(_data.blocked)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .blockedSlice(let _data): + return ("blockedSlice", [("count", ConstructorParameterDescription(_data.count)), ("blocked", ConstructorParameterDescription(_data.blocked)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_blocked(_ reader: BufferReader) -> Blocked? { + var _1: [Api.PeerBlocked]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.Blocked.blocked(Cons_blocked(blocked: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + public static func parse_blockedSlice(_ reader: BufferReader) -> Blocked? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.PeerBlocked]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerBlocked.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.Blocked.blockedSlice(Cons_blockedSlice(count: _1!, blocked: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum ContactBirthdays: TypeConstructorDescription { + public class Cons_contactBirthdays: TypeConstructorDescription { + public var contacts: [Api.ContactBirthday] + public var users: [Api.User] + public init(contacts: [Api.ContactBirthday], users: [Api.User]) { + self.contacts = contacts + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("contactBirthdays", [("contacts", ConstructorParameterDescription(self.contacts)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case contactBirthdays(Cons_contactBirthdays) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .contactBirthdays(let _data): + if boxed { + buffer.appendInt32(290452237) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.contacts.count)) + for item in _data.contacts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .contactBirthdays(let _data): + return ("contactBirthdays", [("contacts", ConstructorParameterDescription(_data.contacts)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_contactBirthdays(_ reader: BufferReader) -> ContactBirthdays? { + var _1: [Api.ContactBirthday]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactBirthday.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.contacts.ContactBirthdays.contactBirthdays(Cons_contactBirthdays(contacts: _1!, users: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum Contacts: TypeConstructorDescription { + public class Cons_contacts: TypeConstructorDescription { + public var contacts: [Api.Contact] + public var savedCount: Int32 + public var users: [Api.User] + public init(contacts: [Api.Contact], savedCount: Int32, users: [Api.User]) { + self.contacts = contacts + self.savedCount = savedCount + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("contacts", [("contacts", ConstructorParameterDescription(self.contacts)), ("savedCount", ConstructorParameterDescription(self.savedCount)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case contacts(Cons_contacts) + case contactsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .contacts(let _data): + if boxed { + buffer.appendInt32(-353862078) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.contacts.count)) + for item in _data.contacts { + item.serialize(buffer, true) + } + serializeInt32(_data.savedCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .contactsNotModified: + if boxed { + buffer.appendInt32(-1219778094) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .contacts(let _data): + return ("contacts", [("contacts", ConstructorParameterDescription(_data.contacts)), ("savedCount", ConstructorParameterDescription(_data.savedCount)), ("users", ConstructorParameterDescription(_data.users))]) + case .contactsNotModified: + return ("contactsNotModified", []) + } + } + + public static func parse_contacts(_ reader: BufferReader) -> Contacts? { + var _1: [Api.Contact]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Contact.self) + } + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.Contacts.contacts(Cons_contacts(contacts: _1!, savedCount: _2!, users: _3!)) + } + else { + return nil + } + } + public static func parse_contactsNotModified(_ reader: BufferReader) -> Contacts? { + return Api.contacts.Contacts.contactsNotModified + } + } +} +public extension Api.contacts { + enum Found: TypeConstructorDescription { + public class Cons_found: TypeConstructorDescription { + public var myResults: [Api.Peer] + public var results: [Api.Peer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(myResults: [Api.Peer], results: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { + self.myResults = myResults + self.results = results + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("found", [("myResults", ConstructorParameterDescription(self.myResults)), ("results", ConstructorParameterDescription(self.results)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case found(Cons_found) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .found(let _data): + if boxed { + buffer.appendInt32(-1290580579) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.myResults.count)) + for item in _data.myResults { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.results.count)) + for item in _data.results { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .found(let _data): + return ("found", [("myResults", ConstructorParameterDescription(_data.myResults)), ("results", ConstructorParameterDescription(_data.results)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_found(_ reader: BufferReader) -> Found? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _2: [Api.Peer]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.Found.found(Cons_found(myResults: _1!, results: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum ImportedContacts: TypeConstructorDescription { + public class Cons_importedContacts: TypeConstructorDescription { + public var imported: [Api.ImportedContact] + public var popularInvites: [Api.PopularContact] + public var retryContacts: [Int64] + public var users: [Api.User] + public init(imported: [Api.ImportedContact], popularInvites: [Api.PopularContact], retryContacts: [Int64], users: [Api.User]) { + self.imported = imported + self.popularInvites = popularInvites + self.retryContacts = retryContacts + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("importedContacts", [("imported", ConstructorParameterDescription(self.imported)), ("popularInvites", ConstructorParameterDescription(self.popularInvites)), ("retryContacts", ConstructorParameterDescription(self.retryContacts)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case importedContacts(Cons_importedContacts) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .importedContacts(let _data): + if boxed { + buffer.appendInt32(2010127419) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.imported.count)) + for item in _data.imported { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.popularInvites.count)) + for item in _data.popularInvites { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.retryContacts.count)) + for item in _data.retryContacts { + serializeInt64(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .importedContacts(let _data): + return ("importedContacts", [("imported", ConstructorParameterDescription(_data.imported)), ("popularInvites", ConstructorParameterDescription(_data.popularInvites)), ("retryContacts", ConstructorParameterDescription(_data.retryContacts)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_importedContacts(_ reader: BufferReader) -> ImportedContacts? { + var _1: [Api.ImportedContact]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ImportedContact.self) + } + var _2: [Api.PopularContact]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PopularContact.self) + } + var _3: [Int64]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.contacts.ImportedContacts.importedContacts(Cons_importedContacts(imported: _1!, popularInvites: _2!, retryContacts: _3!, users: _4!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum ResolvedPeer: TypeConstructorDescription { + public class Cons_resolvedPeer: TypeConstructorDescription { + public var peer: Api.Peer + public var chats: [Api.Chat] + public var users: [Api.User] + public init(peer: Api.Peer, chats: [Api.Chat], users: [Api.User]) { + self.peer = peer + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("resolvedPeer", [("peer", ConstructorParameterDescription(self.peer)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case resolvedPeer(Cons_resolvedPeer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .resolvedPeer(let _data): + if boxed { + buffer.appendInt32(2131196633) + } + _data.peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .resolvedPeer(let _data): + return ("resolvedPeer", [("peer", ConstructorParameterDescription(_data.peer)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_resolvedPeer(_ reader: BufferReader) -> ResolvedPeer? { + var _1: Api.Peer? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.ResolvedPeer.resolvedPeer(Cons_resolvedPeer(peer: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.contacts { + enum SponsoredPeers: TypeConstructorDescription { + public class Cons_sponsoredPeers: TypeConstructorDescription { + public var peers: [Api.SponsoredPeer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(peers: [Api.SponsoredPeer], chats: [Api.Chat], users: [Api.User]) { + self.peers = peers + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sponsoredPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case sponsoredPeers(Cons_sponsoredPeers) + case sponsoredPeersEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sponsoredPeers(let _data): + if boxed { + buffer.appendInt32(-352114556) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .sponsoredPeersEmpty: + if boxed { + buffer.appendInt32(-365775695) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .sponsoredPeers(let _data): + return ("sponsoredPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .sponsoredPeersEmpty: + return ("sponsoredPeersEmpty", []) + } + } + + public static func parse_sponsoredPeers(_ reader: BufferReader) -> SponsoredPeers? { + var _1: [Api.SponsoredPeer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredPeer.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.SponsoredPeers.sponsoredPeers(Cons_sponsoredPeers(peers: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + public static func parse_sponsoredPeersEmpty(_ reader: BufferReader) -> SponsoredPeers? { + return Api.contacts.SponsoredPeers.sponsoredPeersEmpty + } + } +} +public extension Api.contacts { + enum TopPeers: TypeConstructorDescription { + public class Cons_topPeers: TypeConstructorDescription { + public var categories: [Api.TopPeerCategoryPeers] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(categories: [Api.TopPeerCategoryPeers], chats: [Api.Chat], users: [Api.User]) { + self.categories = categories + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("topPeers", [("categories", ConstructorParameterDescription(self.categories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case topPeers(Cons_topPeers) + case topPeersDisabled + case topPeersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .topPeers(let _data): + if boxed { + buffer.appendInt32(1891070632) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.categories.count)) + for item in _data.categories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .topPeersDisabled: + if boxed { + buffer.appendInt32(-1255369827) + } + break + case .topPeersNotModified: + if boxed { + buffer.appendInt32(-567906571) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .topPeers(let _data): + return ("topPeers", [("categories", ConstructorParameterDescription(_data.categories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .topPeersDisabled: + return ("topPeersDisabled", []) + case .topPeersNotModified: + return ("topPeersNotModified", []) + } + } + + public static func parse_topPeers(_ reader: BufferReader) -> TopPeers? { + var _1: [Api.TopPeerCategoryPeers]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeerCategoryPeers.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.contacts.TopPeers.topPeers(Cons_topPeers(categories: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + public static func parse_topPeersDisabled(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersDisabled + } + public static func parse_topPeersNotModified(_ reader: BufferReader) -> TopPeers? { + return Api.contacts.TopPeers.topPeersNotModified + } + } +} +public extension Api.fragment { + enum CollectibleInfo: TypeConstructorDescription { + public class Cons_collectibleInfo: TypeConstructorDescription { + public var purchaseDate: Int32 + public var currency: String + public var amount: Int64 + public var cryptoCurrency: String + public var cryptoAmount: Int64 + public var url: String + public init(purchaseDate: Int32, currency: String, amount: Int64, cryptoCurrency: String, cryptoAmount: Int64, url: String) { + self.purchaseDate = purchaseDate + self.currency = currency + self.amount = amount + self.cryptoCurrency = cryptoCurrency + self.cryptoAmount = cryptoAmount + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("collectibleInfo", [("purchaseDate", ConstructorParameterDescription(self.purchaseDate)), ("currency", ConstructorParameterDescription(self.currency)), ("amount", ConstructorParameterDescription(self.amount)), ("cryptoCurrency", ConstructorParameterDescription(self.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(self.cryptoAmount)), ("url", ConstructorParameterDescription(self.url))]) + } + } + case collectibleInfo(Cons_collectibleInfo) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .collectibleInfo(let _data): + if boxed { + buffer.appendInt32(1857945489) + } + serializeInt32(_data.purchaseDate, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.amount, buffer: buffer, boxed: false) + serializeString(_data.cryptoCurrency, buffer: buffer, boxed: false) + serializeInt64(_data.cryptoAmount, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .collectibleInfo(let _data): + return ("collectibleInfo", [("purchaseDate", ConstructorParameterDescription(_data.purchaseDate)), ("currency", ConstructorParameterDescription(_data.currency)), ("amount", ConstructorParameterDescription(_data.amount)), ("cryptoCurrency", ConstructorParameterDescription(_data.cryptoCurrency)), ("cryptoAmount", ConstructorParameterDescription(_data.cryptoAmount)), ("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_collectibleInfo(_ reader: BufferReader) -> CollectibleInfo? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) var _3: Int64? _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: [Int32]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.fragment.CollectibleInfo.collectibleInfo(Cons_collectibleInfo(purchaseDate: _1!, currency: _2!, amount: _3!, cryptoCurrency: _4!, cryptoAmount: _5!, url: _6!)) } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt32() + else { + return nil + } + } + } +} +public extension Api.help { + enum AppConfig: TypeConstructorDescription { + public class Cons_appConfig: TypeConstructorDescription { + public var hash: Int32 + public var config: Api.JSONValue + public init(hash: Int32, config: Api.JSONValue) { + self.hash = hash + self.config = config + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("appConfig", [("hash", ConstructorParameterDescription(self.hash)), ("config", ConstructorParameterDescription(self.config))]) + } + } + case appConfig(Cons_appConfig) + case appConfigNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .appConfig(let _data): + if boxed { + buffer.appendInt32(-585598930) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + _data.config.serialize(buffer, true) + break + case .appConfigNotModified: + if boxed { + buffer.appendInt32(2094949405) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .appConfig(let _data): + return ("appConfig", [("hash", ConstructorParameterDescription(_data.hash)), ("config", ConstructorParameterDescription(_data.config))]) + case .appConfigNotModified: + return ("appConfigNotModified", []) + } + } + + public static func parse_appConfig(_ reader: BufferReader) -> AppConfig? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.JSONValue? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.JSONValue + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.help.AppConfig.appConfig(Cons_appConfig(hash: _1!, config: _2!)) + } + else { + return nil + } + } + public static func parse_appConfigNotModified(_ reader: BufferReader) -> AppConfig? { + return Api.help.AppConfig.appConfigNotModified + } + } +} +public extension Api.help { + enum AppUpdate: TypeConstructorDescription { + public class Cons_appUpdate: TypeConstructorDescription { + public var flags: Int32 + public var id: Int32 + public var version: String + public var text: String + public var entities: [Api.MessageEntity] + public var document: Api.Document? + public var url: String? + public var sticker: Api.Document? + public init(flags: Int32, id: Int32, version: String, text: String, entities: [Api.MessageEntity], document: Api.Document?, url: String?, sticker: Api.Document?) { + self.flags = flags + self.id = id + self.version = version + self.text = text + self.entities = entities + self.document = document + self.url = url + self.sticker = sticker + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("appUpdate", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("version", ConstructorParameterDescription(self.version)), ("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities)), ("document", ConstructorParameterDescription(self.document)), ("url", ConstructorParameterDescription(self.url)), ("sticker", ConstructorParameterDescription(self.sticker))]) + } + } + case appUpdate(Cons_appUpdate) + case noAppUpdate + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .appUpdate(let _data): + if boxed { + buffer.appendInt32(-860107216) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.id, buffer: buffer, boxed: false) + serializeString(_data.version, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities.count)) + for item in _data.entities { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.document!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.sticker!.serialize(buffer, true) + } + break + case .noAppUpdate: + if boxed { + buffer.appendInt32(-1000708810) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .appUpdate(let _data): + return ("appUpdate", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("version", ConstructorParameterDescription(_data.version)), ("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities)), ("document", ConstructorParameterDescription(_data.document)), ("url", ConstructorParameterDescription(_data.url)), ("sticker", ConstructorParameterDescription(_data.sticker))]) + case .noAppUpdate: + return ("noAppUpdate", []) + } + } + + public static func parse_appUpdate(_ reader: BufferReader) -> AppUpdate? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _6: Api.Document? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.Document + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _7 = parseString(reader) + } + var _8: Api.Document? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Document + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.EmojiGameInfo.emojiGameDiceInfo(Cons_emojiGameDiceInfo(flags: _1!, gameHash: _2!, prevStake: _3!, currentStreak: _4!, params: _5!, playsLeft: _6)) + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.help.AppUpdate.appUpdate(Cons_appUpdate(flags: _1!, id: _2!, version: _3!, text: _4!, entities: _5!, document: _6, url: _7, sticker: _8)) } else { return nil } } - public static func parse_emojiGameUnavailable(_ reader: BufferReader) -> EmojiGameInfo? { - return Api.messages.EmojiGameInfo.emojiGameUnavailable + public static func parse_noAppUpdate(_ reader: BufferReader) -> AppUpdate? { + return Api.help.AppUpdate.noAppUpdate } } } -public extension Api.messages { - enum EmojiGameOutcome: TypeConstructorDescription { - public class Cons_emojiGameOutcome: TypeConstructorDescription { - public var seed: Buffer - public var stakeTonAmount: Int64 - public var tonAmount: Int64 - public init(seed: Buffer, stakeTonAmount: Int64, tonAmount: Int64) { - self.seed = seed - self.stakeTonAmount = stakeTonAmount - self.tonAmount = tonAmount +public extension Api.help { + enum CountriesList: TypeConstructorDescription { + public class Cons_countriesList: TypeConstructorDescription { + public var countries: [Api.help.Country] + public var hash: Int32 + public init(countries: [Api.help.Country], hash: Int32) { + self.countries = countries + self.hash = hash } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emojiGameOutcome", [("seed", ConstructorParameterDescription(self.seed)), ("stakeTonAmount", ConstructorParameterDescription(self.stakeTonAmount)), ("tonAmount", ConstructorParameterDescription(self.tonAmount))]) + return ("countriesList", [("countries", ConstructorParameterDescription(self.countries)), ("hash", ConstructorParameterDescription(self.hash))]) } } - case emojiGameOutcome(Cons_emojiGameOutcome) + case countriesList(Cons_countriesList) + case countriesListNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiGameOutcome(let _data): + case .countriesList(let _data): if boxed { - buffer.appendInt32(-634726841) + buffer.appendInt32(-2016381538) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.countries.count)) + for item in _data.countries { + item.serialize(buffer, true) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + break + case .countriesListNotModified: + if boxed { + buffer.appendInt32(-1815339214) } - serializeBytes(_data.seed, buffer: buffer, boxed: false) - serializeInt64(_data.stakeTonAmount, buffer: buffer, boxed: false) - serializeInt64(_data.tonAmount, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .emojiGameOutcome(let _data): - return ("emojiGameOutcome", [("seed", ConstructorParameterDescription(_data.seed)), ("stakeTonAmount", ConstructorParameterDescription(_data.stakeTonAmount)), ("tonAmount", ConstructorParameterDescription(_data.tonAmount))]) + case .countriesList(let _data): + return ("countriesList", [("countries", ConstructorParameterDescription(_data.countries)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .countriesListNotModified: + return ("countriesListNotModified", []) } } - public static func parse_emojiGameOutcome(_ reader: BufferReader) -> EmojiGameOutcome? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() + public static func parse_countriesList(_ reader: BufferReader) -> CountriesList? { + var _1: [Api.help.Country]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.Country.self) + } + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.EmojiGameOutcome.emojiGameOutcome(Cons_emojiGameOutcome(seed: _1!, stakeTonAmount: _2!, tonAmount: _3!)) + if _c1 && _c2 { + return Api.help.CountriesList.countriesList(Cons_countriesList(countries: _1!, hash: _2!)) } else { return nil } } + public static func parse_countriesListNotModified(_ reader: BufferReader) -> CountriesList? { + return Api.help.CountriesList.countriesListNotModified + } } } -public extension Api.messages { - enum EmojiGroups: TypeConstructorDescription { - public class Cons_emojiGroups: TypeConstructorDescription { - public var hash: Int32 - public var groups: [Api.EmojiGroup] - public init(hash: Int32, groups: [Api.EmojiGroup]) { - self.hash = hash - self.groups = groups +public extension Api.help { + enum Country: TypeConstructorDescription { + public class Cons_country: TypeConstructorDescription { + public var flags: Int32 + public var iso2: String + public var defaultName: String + public var name: String? + public var countryCodes: [Api.help.CountryCode] + public init(flags: Int32, iso2: String, defaultName: String, name: String?, countryCodes: [Api.help.CountryCode]) { + self.flags = flags + self.iso2 = iso2 + self.defaultName = defaultName + self.name = name + self.countryCodes = countryCodes } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("emojiGroups", [("hash", ConstructorParameterDescription(self.hash)), ("groups", ConstructorParameterDescription(self.groups))]) + return ("country", [("flags", ConstructorParameterDescription(self.flags)), ("iso2", ConstructorParameterDescription(self.iso2)), ("defaultName", ConstructorParameterDescription(self.defaultName)), ("name", ConstructorParameterDescription(self.name)), ("countryCodes", ConstructorParameterDescription(self.countryCodes))]) } } - case emojiGroups(Cons_emojiGroups) - case emojiGroupsNotModified + case country(Cons_country) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .emojiGroups(let _data): + case .country(let _data): if boxed { - buffer.appendInt32(-2011186869) + buffer.appendInt32(-1014526429) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.iso2, buffer: buffer, boxed: false) + serializeString(_data.defaultName, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.name!, buffer: buffer, boxed: false) } - serializeInt32(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.groups.count)) - for item in _data.groups { + buffer.appendInt32(Int32(_data.countryCodes.count)) + for item in _data.countryCodes { item.serialize(buffer, true) } break - case .emojiGroupsNotModified: + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .country(let _data): + return ("country", [("flags", ConstructorParameterDescription(_data.flags)), ("iso2", ConstructorParameterDescription(_data.iso2)), ("defaultName", ConstructorParameterDescription(_data.defaultName)), ("name", ConstructorParameterDescription(_data.name)), ("countryCodes", ConstructorParameterDescription(_data.countryCodes))]) + } + } + + public static func parse_country(_ reader: BufferReader) -> Country? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: [Api.help.CountryCode]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.CountryCode.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.help.Country.country(Cons_country(flags: _1!, iso2: _2!, defaultName: _3!, name: _4, countryCodes: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum CountryCode: TypeConstructorDescription { + public class Cons_countryCode: TypeConstructorDescription { + public var flags: Int32 + public var countryCode: String + public var prefixes: [String]? + public var patterns: [String]? + public init(flags: Int32, countryCode: String, prefixes: [String]?, patterns: [String]?) { + self.flags = flags + self.countryCode = countryCode + self.prefixes = prefixes + self.patterns = patterns + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("countryCode", [("flags", ConstructorParameterDescription(self.flags)), ("countryCode", ConstructorParameterDescription(self.countryCode)), ("prefixes", ConstructorParameterDescription(self.prefixes)), ("patterns", ConstructorParameterDescription(self.patterns))]) + } + } + case countryCode(Cons_countryCode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .countryCode(let _data): if boxed { - buffer.appendInt32(1874111879) + buffer.appendInt32(1107543535) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.countryCode, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.prefixes!.count)) + for item in _data.prefixes! { + serializeString(item, buffer: buffer, boxed: false) + } + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.patterns!.count)) + for item in _data.patterns! { + serializeString(item, buffer: buffer, boxed: false) + } } break } @@ -1703,31 +1526,222 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .emojiGroups(let _data): - return ("emojiGroups", [("hash", ConstructorParameterDescription(_data.hash)), ("groups", ConstructorParameterDescription(_data.groups))]) - case .emojiGroupsNotModified: - return ("emojiGroupsNotModified", []) + case .countryCode(let _data): + return ("countryCode", [("flags", ConstructorParameterDescription(_data.flags)), ("countryCode", ConstructorParameterDescription(_data.countryCode)), ("prefixes", ConstructorParameterDescription(_data.prefixes)), ("patterns", ConstructorParameterDescription(_data.patterns))]) } } - public static func parse_emojiGroups(_ reader: BufferReader) -> EmojiGroups? { + public static func parse_countryCode(_ reader: BufferReader) -> CountryCode? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.EmojiGroup]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiGroup.self) + var _2: String? + _2 = parseString(reader) + var _3: [String]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + var _4: [String]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.help.CountryCode.countryCode(Cons_countryCode(flags: _1!, countryCode: _2!, prefixes: _3, patterns: _4)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum DeepLinkInfo: TypeConstructorDescription { + public class Cons_deepLinkInfo: TypeConstructorDescription { + public var flags: Int32 + public var message: String + public var entities: [Api.MessageEntity]? + public init(flags: Int32, message: String, entities: [Api.MessageEntity]?) { + self.flags = flags + self.message = message + self.entities = entities + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("deepLinkInfo", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities))]) + } + } + case deepLinkInfo(Cons_deepLinkInfo) + case deepLinkInfoEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .deepLinkInfo(let _data): + if boxed { + buffer.appendInt32(1783556146) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.message, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities!.count)) + for item in _data.entities! { + item.serialize(buffer, true) + } + } + break + case .deepLinkInfoEmpty: + if boxed { + buffer.appendInt32(1722786150) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .deepLinkInfo(let _data): + return ("deepLinkInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities))]) + case .deepLinkInfoEmpty: + return ("deepLinkInfoEmpty", []) + } + } + + public static func parse_deepLinkInfo(_ reader: BufferReader) -> DeepLinkInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: [Api.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.help.DeepLinkInfo.deepLinkInfo(Cons_deepLinkInfo(flags: _1!, message: _2!, entities: _3)) + } + else { + return nil + } + } + public static func parse_deepLinkInfoEmpty(_ reader: BufferReader) -> DeepLinkInfo? { + return Api.help.DeepLinkInfo.deepLinkInfoEmpty + } + } +} +public extension Api.help { + enum InviteText: TypeConstructorDescription { + public class Cons_inviteText: TypeConstructorDescription { + public var message: String + public init(message: String) { + self.message = message + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inviteText", [("message", ConstructorParameterDescription(self.message))]) + } + } + case inviteText(Cons_inviteText) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inviteText(let _data): + if boxed { + buffer.appendInt32(415997816) + } + serializeString(_data.message, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inviteText(let _data): + return ("inviteText", [("message", ConstructorParameterDescription(_data.message))]) + } + } + + public static func parse_inviteText(_ reader: BufferReader) -> InviteText? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.help.InviteText.inviteText(Cons_inviteText(message: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum PassportConfig: TypeConstructorDescription { + public class Cons_passportConfig: TypeConstructorDescription { + public var hash: Int32 + public var countriesLangs: Api.DataJSON + public init(hash: Int32, countriesLangs: Api.DataJSON) { + self.hash = hash + self.countriesLangs = countriesLangs + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("passportConfig", [("hash", ConstructorParameterDescription(self.hash)), ("countriesLangs", ConstructorParameterDescription(self.countriesLangs))]) + } + } + case passportConfig(Cons_passportConfig) + case passportConfigNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .passportConfig(let _data): + if boxed { + buffer.appendInt32(-1600596305) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + _data.countriesLangs.serialize(buffer, true) + break + case .passportConfigNotModified: + if boxed { + buffer.appendInt32(-1078332329) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .passportConfig(let _data): + return ("passportConfig", [("hash", ConstructorParameterDescription(_data.hash)), ("countriesLangs", ConstructorParameterDescription(_data.countriesLangs))]) + case .passportConfigNotModified: + return ("passportConfigNotModified", []) + } + } + + public static func parse_passportConfig(_ reader: BufferReader) -> PassportConfig? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.EmojiGroups.emojiGroups(Cons_emojiGroups(hash: _1!, groups: _2!)) + return Api.help.PassportConfig.passportConfig(Cons_passportConfig(hash: _1!, countriesLangs: _2!)) } else { return nil } } - public static func parse_emojiGroupsNotModified(_ reader: BufferReader) -> EmojiGroups? { - return Api.messages.EmojiGroups.emojiGroupsNotModified + public static func parse_passportConfigNotModified(_ reader: BufferReader) -> PassportConfig? { + return Api.help.PassportConfig.passportConfigNotModified } } } diff --git a/submodules/TelegramApi/Sources/Api35.swift b/submodules/TelegramApi/Sources/Api35.swift index 15431344d4..1727a10853 100644 --- a/submodules/TelegramApi/Sources/Api35.swift +++ b/submodules/TelegramApi/Sources/Api35.swift @@ -1,55 +1,45 @@ -public extension Api.messages { - enum ExportedChatInvite: TypeConstructorDescription { - public class Cons_exportedChatInvite: TypeConstructorDescription { - public var invite: Api.ExportedChatInvite - public var users: [Api.User] - public init(invite: Api.ExportedChatInvite, users: [Api.User]) { - self.invite = invite - self.users = users +public extension Api.help { + enum PeerColorOption: TypeConstructorDescription { + public class Cons_peerColorOption: TypeConstructorDescription { + public var flags: Int32 + public var colorId: Int32 + public var colors: Api.help.PeerColorSet? + public var darkColors: Api.help.PeerColorSet? + public var channelMinLevel: Int32? + public var groupMinLevel: Int32? + public init(flags: Int32, colorId: Int32, colors: Api.help.PeerColorSet?, darkColors: Api.help.PeerColorSet?, channelMinLevel: Int32?, groupMinLevel: Int32?) { + self.flags = flags + self.colorId = colorId + self.colors = colors + self.darkColors = darkColors + self.channelMinLevel = channelMinLevel + self.groupMinLevel = groupMinLevel } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedChatInvite", [("invite", ConstructorParameterDescription(self.invite)), ("users", ConstructorParameterDescription(self.users))]) + return ("peerColorOption", [("flags", ConstructorParameterDescription(self.flags)), ("colorId", ConstructorParameterDescription(self.colorId)), ("colors", ConstructorParameterDescription(self.colors)), ("darkColors", ConstructorParameterDescription(self.darkColors)), ("channelMinLevel", ConstructorParameterDescription(self.channelMinLevel)), ("groupMinLevel", ConstructorParameterDescription(self.groupMinLevel))]) } } - public class Cons_exportedChatInviteReplaced: TypeConstructorDescription { - public var invite: Api.ExportedChatInvite - public var newInvite: Api.ExportedChatInvite - public var users: [Api.User] - public init(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) { - self.invite = invite - self.newInvite = newInvite - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedChatInviteReplaced", [("invite", ConstructorParameterDescription(self.invite)), ("newInvite", ConstructorParameterDescription(self.newInvite)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case exportedChatInvite(Cons_exportedChatInvite) - case exportedChatInviteReplaced(Cons_exportedChatInviteReplaced) + case peerColorOption(Cons_peerColorOption) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatInvite(let _data): + case .peerColorOption(let _data): if boxed { - buffer.appendInt32(410107472) + buffer.appendInt32(-1377014082) } - _data.invite.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.colorId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.colors!.serialize(buffer, true) } - break - case .exportedChatInviteReplaced(let _data): - if boxed { - buffer.appendInt32(572915951) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.darkColors!.serialize(buffer, true) } - _data.invite.serialize(buffer, true) - _data.newInvite.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.channelMinLevel!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.groupMinLevel!, buffer: buffer, boxed: false) } break } @@ -57,83 +47,275 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedChatInvite(let _data): - return ("exportedChatInvite", [("invite", ConstructorParameterDescription(_data.invite)), ("users", ConstructorParameterDescription(_data.users))]) - case .exportedChatInviteReplaced(let _data): - return ("exportedChatInviteReplaced", [("invite", ConstructorParameterDescription(_data.invite)), ("newInvite", ConstructorParameterDescription(_data.newInvite)), ("users", ConstructorParameterDescription(_data.users))]) + case .peerColorOption(let _data): + return ("peerColorOption", [("flags", ConstructorParameterDescription(_data.flags)), ("colorId", ConstructorParameterDescription(_data.colorId)), ("colors", ConstructorParameterDescription(_data.colors)), ("darkColors", ConstructorParameterDescription(_data.darkColors)), ("channelMinLevel", ConstructorParameterDescription(_data.channelMinLevel)), ("groupMinLevel", ConstructorParameterDescription(_data.groupMinLevel))]) } } - public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? { - var _1: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + public static func parse_peerColorOption(_ reader: BufferReader) -> PeerColorOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.help.PeerColorSet? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet + } } - var _2: [Api.User]? + var _4: Api.help.PeerColorSet? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.help.PeerColorSet + } + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _5 = reader.readInt32() + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _6 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PeerColorOption.peerColorOption(Cons_peerColorOption(flags: _1!, colorId: _2!, colors: _3, darkColors: _4, channelMinLevel: _5, groupMinLevel: _6)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum PeerColorSet: TypeConstructorDescription { + public class Cons_peerColorProfileSet: TypeConstructorDescription { + public var paletteColors: [Int32] + public var bgColors: [Int32] + public var storyColors: [Int32] + public init(paletteColors: [Int32], bgColors: [Int32], storyColors: [Int32]) { + self.paletteColors = paletteColors + self.bgColors = bgColors + self.storyColors = storyColors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerColorProfileSet", [("paletteColors", ConstructorParameterDescription(self.paletteColors)), ("bgColors", ConstructorParameterDescription(self.bgColors)), ("storyColors", ConstructorParameterDescription(self.storyColors))]) + } + } + public class Cons_peerColorSet: TypeConstructorDescription { + public var colors: [Int32] + public init(colors: [Int32]) { + self.colors = colors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerColorSet", [("colors", ConstructorParameterDescription(self.colors))]) + } + } + case peerColorProfileSet(Cons_peerColorProfileSet) + case peerColorSet(Cons_peerColorSet) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerColorProfileSet(let _data): + if boxed { + buffer.appendInt32(1987928555) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.paletteColors.count)) + for item in _data.paletteColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.bgColors.count)) + for item in _data.bgColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.storyColors.count)) + for item in _data.storyColors { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .peerColorSet(let _data): + if boxed { + buffer.appendInt32(639736408) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.colors.count)) + for item in _data.colors { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerColorProfileSet(let _data): + return ("peerColorProfileSet", [("paletteColors", ConstructorParameterDescription(_data.paletteColors)), ("bgColors", ConstructorParameterDescription(_data.bgColors)), ("storyColors", ConstructorParameterDescription(_data.storyColors))]) + case .peerColorSet(let _data): + return ("peerColorSet", [("colors", ConstructorParameterDescription(_data.colors))]) + } + } + + public static func parse_peerColorProfileSet(_ reader: BufferReader) -> PeerColorSet? { + var _1: [Int32]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _3: [Int32]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.help.PeerColorSet.peerColorProfileSet(Cons_peerColorProfileSet(paletteColors: _1!, bgColors: _2!, storyColors: _3!)) + } + else { + return nil + } + } + public static func parse_peerColorSet(_ reader: BufferReader) -> PeerColorSet? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.help.PeerColorSet.peerColorSet(Cons_peerColorSet(colors: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum PeerColors: TypeConstructorDescription { + public class Cons_peerColors: TypeConstructorDescription { + public var hash: Int32 + public var colors: [Api.help.PeerColorOption] + public init(hash: Int32, colors: [Api.help.PeerColorOption]) { + self.hash = hash + self.colors = colors + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerColors", [("hash", ConstructorParameterDescription(self.hash)), ("colors", ConstructorParameterDescription(self.colors))]) + } + } + case peerColors(Cons_peerColors) + case peerColorsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerColors(let _data): + if boxed { + buffer.appendInt32(16313608) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.colors.count)) + for item in _data.colors { + item.serialize(buffer, true) + } + break + case .peerColorsNotModified: + if boxed { + buffer.appendInt32(732034510) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerColors(let _data): + return ("peerColors", [("hash", ConstructorParameterDescription(_data.hash)), ("colors", ConstructorParameterDescription(_data.colors))]) + case .peerColorsNotModified: + return ("peerColorsNotModified", []) + } + } + + public static func parse_peerColors(_ reader: BufferReader) -> PeerColors? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.help.PeerColorOption]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.help.PeerColorOption.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.ExportedChatInvite.exportedChatInvite(Cons_exportedChatInvite(invite: _1!, users: _2!)) + return Api.help.PeerColors.peerColors(Cons_peerColors(hash: _1!, colors: _2!)) } else { return nil } } - public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? { - var _1: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _2: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(Cons_exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!)) - } - else { - return nil - } + public static func parse_peerColorsNotModified(_ reader: BufferReader) -> PeerColors? { + return Api.help.PeerColors.peerColorsNotModified } } } -public extension Api.messages { - enum ExportedChatInvites: TypeConstructorDescription { - public class Cons_exportedChatInvites: TypeConstructorDescription { - public var count: Int32 - public var invites: [Api.ExportedChatInvite] +public extension Api.help { + enum PremiumPromo: TypeConstructorDescription { + public class Cons_premiumPromo: TypeConstructorDescription { + public var statusText: String + public var statusEntities: [Api.MessageEntity] + public var videoSections: [String] + public var videos: [Api.Document] + public var periodOptions: [Api.PremiumSubscriptionOption] public var users: [Api.User] - public init(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User]) { - self.count = count - self.invites = invites + public init(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) { + self.statusText = statusText + self.statusEntities = statusEntities + self.videoSections = videoSections + self.videos = videos + self.periodOptions = periodOptions self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedChatInvites", [("count", ConstructorParameterDescription(self.count)), ("invites", ConstructorParameterDescription(self.invites)), ("users", ConstructorParameterDescription(self.users))]) + return ("premiumPromo", [("statusText", ConstructorParameterDescription(self.statusText)), ("statusEntities", ConstructorParameterDescription(self.statusEntities)), ("videoSections", ConstructorParameterDescription(self.videoSections)), ("videos", ConstructorParameterDescription(self.videos)), ("periodOptions", ConstructorParameterDescription(self.periodOptions)), ("users", ConstructorParameterDescription(self.users))]) } } - case exportedChatInvites(Cons_exportedChatInvites) + case premiumPromo(Cons_premiumPromo) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedChatInvites(let _data): + case .premiumPromo(let _data): if boxed { - buffer.appendInt32(-1111085620) + buffer.appendInt32(1395946908) } - serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeString(_data.statusText, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.invites.count)) - for item in _data.invites { + buffer.appendInt32(Int32(_data.statusEntities.count)) + for item in _data.statusEntities { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.videoSections.count)) + for item in _data.videoSections { + serializeString(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.videos.count)) + for item in _data.videos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.periodOptions.count)) + for item in _data.periodOptions { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -147,17 +329,277 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedChatInvites(let _data): - return ("exportedChatInvites", [("count", ConstructorParameterDescription(_data.count)), ("invites", ConstructorParameterDescription(_data.invites)), ("users", ConstructorParameterDescription(_data.users))]) + case .premiumPromo(let _data): + return ("premiumPromo", [("statusText", ConstructorParameterDescription(_data.statusText)), ("statusEntities", ConstructorParameterDescription(_data.statusEntities)), ("videoSections", ConstructorParameterDescription(_data.videoSections)), ("videos", ConstructorParameterDescription(_data.videos)), ("periodOptions", ConstructorParameterDescription(_data.periodOptions)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_exportedChatInvites(_ reader: BufferReader) -> ExportedChatInvites? { + public static func parse_premiumPromo(_ reader: BufferReader) -> PremiumPromo? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _3: [String]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _5: [Api.PremiumSubscriptionOption]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PremiumPromo.premiumPromo(Cons_premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum PromoData: TypeConstructorDescription { + public class Cons_promoData: TypeConstructorDescription { + public var flags: Int32 + public var expires: Int32 + public var peer: Api.Peer? + public var psaType: String? + public var psaMessage: String? + public var pendingSuggestions: [String] + public var dismissedSuggestions: [String] + public var customPendingSuggestion: Api.PendingSuggestion? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, expires: Int32, peer: Api.Peer?, psaType: String?, psaMessage: String?, pendingSuggestions: [String], dismissedSuggestions: [String], customPendingSuggestion: Api.PendingSuggestion?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.expires = expires + self.peer = peer + self.psaType = psaType + self.psaMessage = psaMessage + self.pendingSuggestions = pendingSuggestions + self.dismissedSuggestions = dismissedSuggestions + self.customPendingSuggestion = customPendingSuggestion + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("promoData", [("flags", ConstructorParameterDescription(self.flags)), ("expires", ConstructorParameterDescription(self.expires)), ("peer", ConstructorParameterDescription(self.peer)), ("psaType", ConstructorParameterDescription(self.psaType)), ("psaMessage", ConstructorParameterDescription(self.psaMessage)), ("pendingSuggestions", ConstructorParameterDescription(self.pendingSuggestions)), ("dismissedSuggestions", ConstructorParameterDescription(self.dismissedSuggestions)), ("customPendingSuggestion", ConstructorParameterDescription(self.customPendingSuggestion)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_promoDataEmpty: TypeConstructorDescription { + public var expires: Int32 + public init(expires: Int32) { + self.expires = expires + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("promoDataEmpty", [("expires", ConstructorParameterDescription(self.expires))]) + } + } + case promoData(Cons_promoData) + case promoDataEmpty(Cons_promoDataEmpty) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .promoData(let _data): + if boxed { + buffer.appendInt32(145021050) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.expires, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.peer!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.psaType!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.psaMessage!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.pendingSuggestions.count)) + for item in _data.pendingSuggestions { + serializeString(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dismissedSuggestions.count)) + for item in _data.dismissedSuggestions { + serializeString(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.customPendingSuggestion!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .promoDataEmpty(let _data): + if boxed { + buffer.appendInt32(-1728664459) + } + serializeInt32(_data.expires, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .promoData(let _data): + return ("promoData", [("flags", ConstructorParameterDescription(_data.flags)), ("expires", ConstructorParameterDescription(_data.expires)), ("peer", ConstructorParameterDescription(_data.peer)), ("psaType", ConstructorParameterDescription(_data.psaType)), ("psaMessage", ConstructorParameterDescription(_data.psaMessage)), ("pendingSuggestions", ConstructorParameterDescription(_data.pendingSuggestions)), ("dismissedSuggestions", ConstructorParameterDescription(_data.dismissedSuggestions)), ("customPendingSuggestion", ConstructorParameterDescription(_data.customPendingSuggestion)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .promoDataEmpty(let _data): + return ("promoDataEmpty", [("expires", ConstructorParameterDescription(_data.expires))]) + } + } + + public static func parse_promoData(_ reader: BufferReader) -> PromoData? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.ExportedChatInvite]? + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Peer + } + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseString(reader) + } + var _6: [String]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatInvite.self) + _6 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _7: [String]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _8: Api.PendingSuggestion? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.PendingSuggestion + } + } + var _9: [Api.Chat]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _10: [Api.User]? + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.help.PromoData.promoData(Cons_promoData(flags: _1!, expires: _2!, peer: _3, psaType: _4, psaMessage: _5, pendingSuggestions: _6!, dismissedSuggestions: _7!, customPendingSuggestion: _8, chats: _9!, users: _10!)) + } + else { + return nil + } + } + public static func parse_promoDataEmpty(_ reader: BufferReader) -> PromoData? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.help.PromoData.promoDataEmpty(Cons_promoDataEmpty(expires: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum RecentMeUrls: TypeConstructorDescription { + public class Cons_recentMeUrls: TypeConstructorDescription { + public var urls: [Api.RecentMeUrl] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(urls: [Api.RecentMeUrl], chats: [Api.Chat], users: [Api.User]) { + self.urls = urls + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentMeUrls", [("urls", ConstructorParameterDescription(self.urls)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case recentMeUrls(Cons_recentMeUrls) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .recentMeUrls(let _data): + if boxed { + buffer.appendInt32(235081943) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.urls.count)) + for item in _data.urls { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .recentMeUrls(let _data): + return ("recentMeUrls", [("urls", ConstructorParameterDescription(_data.urls)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_recentMeUrls(_ reader: BufferReader) -> RecentMeUrls? { + var _1: [Api.RecentMeUrl]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentMeUrl.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } var _3: [Api.User]? if let _ = reader.readInt32() { @@ -167,7 +609,478 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.messages.ExportedChatInvites.exportedChatInvites(Cons_exportedChatInvites(count: _1!, invites: _2!, users: _3!)) + return Api.help.RecentMeUrls.recentMeUrls(Cons_recentMeUrls(urls: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum Support: TypeConstructorDescription { + public class Cons_support: TypeConstructorDescription { + public var phoneNumber: String + public var user: Api.User + public init(phoneNumber: String, user: Api.User) { + self.phoneNumber = phoneNumber + self.user = user + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("support", [("phoneNumber", ConstructorParameterDescription(self.phoneNumber)), ("user", ConstructorParameterDescription(self.user))]) + } + } + case support(Cons_support) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .support(let _data): + if boxed { + buffer.appendInt32(398898678) + } + serializeString(_data.phoneNumber, buffer: buffer, boxed: false) + _data.user.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .support(let _data): + return ("support", [("phoneNumber", ConstructorParameterDescription(_data.phoneNumber)), ("user", ConstructorParameterDescription(_data.user))]) + } + } + + public static func parse_support(_ reader: BufferReader) -> Support? { + var _1: String? + _1 = parseString(reader) + var _2: Api.User? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.User + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.help.Support.support(Cons_support(phoneNumber: _1!, user: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum SupportName: TypeConstructorDescription { + public class Cons_supportName: TypeConstructorDescription { + public var name: String + public init(name: String) { + self.name = name + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("supportName", [("name", ConstructorParameterDescription(self.name))]) + } + } + case supportName(Cons_supportName) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .supportName(let _data): + if boxed { + buffer.appendInt32(-1945767479) + } + serializeString(_data.name, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .supportName(let _data): + return ("supportName", [("name", ConstructorParameterDescription(_data.name))]) + } + } + + public static func parse_supportName(_ reader: BufferReader) -> SupportName? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.help.SupportName.supportName(Cons_supportName(name: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum TermsOfService: TypeConstructorDescription { + public class Cons_termsOfService: TypeConstructorDescription { + public var flags: Int32 + public var id: Api.DataJSON + public var text: String + public var entities: [Api.MessageEntity] + public var minAgeConfirm: Int32? + public init(flags: Int32, id: Api.DataJSON, text: String, entities: [Api.MessageEntity], minAgeConfirm: Int32?) { + self.flags = flags + self.id = id + self.text = text + self.entities = entities + self.minAgeConfirm = minAgeConfirm + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("termsOfService", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("text", ConstructorParameterDescription(self.text)), ("entities", ConstructorParameterDescription(self.entities)), ("minAgeConfirm", ConstructorParameterDescription(self.minAgeConfirm))]) + } + } + case termsOfService(Cons_termsOfService) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .termsOfService(let _data): + if boxed { + buffer.appendInt32(2013922064) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.id.serialize(buffer, true) + serializeString(_data.text, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities.count)) + for item in _data.entities { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.minAgeConfirm!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .termsOfService(let _data): + return ("termsOfService", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("text", ConstructorParameterDescription(_data.text)), ("entities", ConstructorParameterDescription(_data.entities)), ("minAgeConfirm", ConstructorParameterDescription(_data.minAgeConfirm))]) + } + } + + public static func parse_termsOfService(_ reader: BufferReader) -> TermsOfService? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.DataJSON? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + var _3: String? + _3 = parseString(reader) + var _4: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.help.TermsOfService.termsOfService(Cons_termsOfService(flags: _1!, id: _2!, text: _3!, entities: _4!, minAgeConfirm: _5)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum TermsOfServiceUpdate: TypeConstructorDescription { + public class Cons_termsOfServiceUpdate: TypeConstructorDescription { + public var expires: Int32 + public var termsOfService: Api.help.TermsOfService + public init(expires: Int32, termsOfService: Api.help.TermsOfService) { + self.expires = expires + self.termsOfService = termsOfService + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("termsOfServiceUpdate", [("expires", ConstructorParameterDescription(self.expires)), ("termsOfService", ConstructorParameterDescription(self.termsOfService))]) + } + } + public class Cons_termsOfServiceUpdateEmpty: TypeConstructorDescription { + public var expires: Int32 + public init(expires: Int32) { + self.expires = expires + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("termsOfServiceUpdateEmpty", [("expires", ConstructorParameterDescription(self.expires))]) + } + } + case termsOfServiceUpdate(Cons_termsOfServiceUpdate) + case termsOfServiceUpdateEmpty(Cons_termsOfServiceUpdateEmpty) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .termsOfServiceUpdate(let _data): + if boxed { + buffer.appendInt32(686618977) + } + serializeInt32(_data.expires, buffer: buffer, boxed: false) + _data.termsOfService.serialize(buffer, true) + break + case .termsOfServiceUpdateEmpty(let _data): + if boxed { + buffer.appendInt32(-483352705) + } + serializeInt32(_data.expires, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .termsOfServiceUpdate(let _data): + return ("termsOfServiceUpdate", [("expires", ConstructorParameterDescription(_data.expires)), ("termsOfService", ConstructorParameterDescription(_data.termsOfService))]) + case .termsOfServiceUpdateEmpty(let _data): + return ("termsOfServiceUpdateEmpty", [("expires", ConstructorParameterDescription(_data.expires))]) + } + } + + public static func parse_termsOfServiceUpdate(_ reader: BufferReader) -> TermsOfServiceUpdate? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.help.TermsOfService? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.help.TermsOfService + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdate(Cons_termsOfServiceUpdate(expires: _1!, termsOfService: _2!)) + } + else { + return nil + } + } + public static func parse_termsOfServiceUpdateEmpty(_ reader: BufferReader) -> TermsOfServiceUpdate? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.help.TermsOfServiceUpdate.termsOfServiceUpdateEmpty(Cons_termsOfServiceUpdateEmpty(expires: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.help { + enum TimezonesList: TypeConstructorDescription { + public class Cons_timezonesList: TypeConstructorDescription { + public var timezones: [Api.Timezone] + public var hash: Int32 + public init(timezones: [Api.Timezone], hash: Int32) { + self.timezones = timezones + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("timezonesList", [("timezones", ConstructorParameterDescription(self.timezones)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case timezonesList(Cons_timezonesList) + case timezonesListNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .timezonesList(let _data): + if boxed { + buffer.appendInt32(2071260529) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.timezones.count)) + for item in _data.timezones { + item.serialize(buffer, true) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + break + case .timezonesListNotModified: + if boxed { + buffer.appendInt32(-1761146676) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .timezonesList(let _data): + return ("timezonesList", [("timezones", ConstructorParameterDescription(_data.timezones)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .timezonesListNotModified: + return ("timezonesListNotModified", []) + } + } + + public static func parse_timezonesList(_ reader: BufferReader) -> TimezonesList? { + var _1: [Api.Timezone]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Timezone.self) + } + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.help.TimezonesList.timezonesList(Cons_timezonesList(timezones: _1!, hash: _2!)) + } + else { + return nil + } + } + public static func parse_timezonesListNotModified(_ reader: BufferReader) -> TimezonesList? { + return Api.help.TimezonesList.timezonesListNotModified + } + } +} +public extension Api.help { + enum UserInfo: TypeConstructorDescription { + public class Cons_userInfo: TypeConstructorDescription { + public var message: String + public var entities: [Api.MessageEntity] + public var author: String + public var date: Int32 + public init(message: String, entities: [Api.MessageEntity], author: String, date: Int32) { + self.message = message + self.entities = entities + self.author = author + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userInfo", [("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("author", ConstructorParameterDescription(self.author)), ("date", ConstructorParameterDescription(self.date))]) + } + } + case userInfo(Cons_userInfo) + case userInfoEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userInfo(let _data): + if boxed { + buffer.appendInt32(32192344) + } + serializeString(_data.message, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.entities.count)) + for item in _data.entities { + item.serialize(buffer, true) + } + serializeString(_data.author, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + break + case .userInfoEmpty: + if boxed { + buffer.appendInt32(-206688531) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .userInfo(let _data): + return ("userInfo", [("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("author", ConstructorParameterDescription(_data.author)), ("date", ConstructorParameterDescription(_data.date))]) + case .userInfoEmpty: + return ("userInfoEmpty", []) + } + } + + public static func parse_userInfo(_ reader: BufferReader) -> UserInfo? { + var _1: String? + _1 = parseString(reader) + var _2: [Api.MessageEntity]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) + } + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.help.UserInfo.userInfo(Cons_userInfo(message: _1!, entities: _2!, author: _3!, date: _4!)) + } + else { + return nil + } + } + public static func parse_userInfoEmpty(_ reader: BufferReader) -> UserInfo? { + return Api.help.UserInfo.userInfoEmpty + } + } +} +public extension Api.messages { + enum AffectedFoundMessages: TypeConstructorDescription { + public class Cons_affectedFoundMessages: TypeConstructorDescription { + public var pts: Int32 + public var ptsCount: Int32 + public var offset: Int32 + public var messages: [Int32] + public init(pts: Int32, ptsCount: Int32, offset: Int32, messages: [Int32]) { + self.pts = pts + self.ptsCount = ptsCount + self.offset = offset + self.messages = messages + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("affectedFoundMessages", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("offset", ConstructorParameterDescription(self.offset)), ("messages", ConstructorParameterDescription(self.messages))]) + } + } + case affectedFoundMessages(Cons_affectedFoundMessages) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .affectedFoundMessages(let _data): + if boxed { + buffer.appendInt32(-275956116) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + serializeInt32(_data.offset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .affectedFoundMessages(let _data): + return ("affectedFoundMessages", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("offset", ConstructorParameterDescription(_data.offset)), ("messages", ConstructorParameterDescription(_data.messages))]) + } + } + + public static func parse_affectedFoundMessages(_ reader: BufferReader) -> AffectedFoundMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.AffectedFoundMessages.affectedFoundMessages(Cons_affectedFoundMessages(pts: _1!, ptsCount: _2!, offset: _3!, messages: _4!)) } else { return nil @@ -176,44 +1089,143 @@ public extension Api.messages { } } public extension Api.messages { - enum FavedStickers: TypeConstructorDescription { - public class Cons_favedStickers: TypeConstructorDescription { - public var hash: Int64 - public var packs: [Api.StickerPack] - public var stickers: [Api.Document] - public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) { - self.hash = hash - self.packs = packs - self.stickers = stickers + enum AffectedHistory: TypeConstructorDescription { + public class Cons_affectedHistory: TypeConstructorDescription { + public var pts: Int32 + public var ptsCount: Int32 + public var offset: Int32 + public init(pts: Int32, ptsCount: Int32, offset: Int32) { + self.pts = pts + self.ptsCount = ptsCount + self.offset = offset } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("favedStickers", [("hash", ConstructorParameterDescription(self.hash)), ("packs", ConstructorParameterDescription(self.packs)), ("stickers", ConstructorParameterDescription(self.stickers))]) + return ("affectedHistory", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount)), ("offset", ConstructorParameterDescription(self.offset))]) } } - case favedStickers(Cons_favedStickers) - case favedStickersNotModified + case affectedHistory(Cons_affectedHistory) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .favedStickers(let _data): + case .affectedHistory(let _data): if boxed { - buffer.appendInt32(750063767) + buffer.appendInt32(-1269012015) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + serializeInt32(_data.offset, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .affectedHistory(let _data): + return ("affectedHistory", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount)), ("offset", ConstructorParameterDescription(_data.offset))]) + } + } + + public static func parse_affectedHistory(_ reader: BufferReader) -> AffectedHistory? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.AffectedHistory.affectedHistory(Cons_affectedHistory(pts: _1!, ptsCount: _2!, offset: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum AffectedMessages: TypeConstructorDescription { + public class Cons_affectedMessages: TypeConstructorDescription { + public var pts: Int32 + public var ptsCount: Int32 + public init(pts: Int32, ptsCount: Int32) { + self.pts = pts + self.ptsCount = ptsCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("affectedMessages", [("pts", ConstructorParameterDescription(self.pts)), ("ptsCount", ConstructorParameterDescription(self.ptsCount))]) + } + } + case affectedMessages(Cons_affectedMessages) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .affectedMessages(let _data): + if boxed { + buffer.appendInt32(-2066640507) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.ptsCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .affectedMessages(let _data): + return ("affectedMessages", [("pts", ConstructorParameterDescription(_data.pts)), ("ptsCount", ConstructorParameterDescription(_data.ptsCount))]) + } + } + + public static func parse_affectedMessages(_ reader: BufferReader) -> AffectedMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.AffectedMessages.affectedMessages(Cons_affectedMessages(pts: _1!, ptsCount: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum AllStickers: TypeConstructorDescription { + public class Cons_allStickers: TypeConstructorDescription { + public var hash: Int64 + public var sets: [Api.StickerSet] + public init(hash: Int64, sets: [Api.StickerSet]) { + self.hash = hash + self.sets = sets + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("allStickers", [("hash", ConstructorParameterDescription(self.hash)), ("sets", ConstructorParameterDescription(self.sets))]) + } + } + case allStickers(Cons_allStickers) + case allStickersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .allStickers(let _data): + if boxed { + buffer.appendInt32(-843329861) } serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.packs.count)) - for item in _data.packs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stickers.count)) - for item in _data.stickers { + buffer.appendInt32(Int32(_data.sets.count)) + for item in _data.sets { item.serialize(buffer, true) } break - case .favedStickersNotModified: + case .allStickersNotModified: if boxed { - buffer.appendInt32(-1634752813) + buffer.appendInt32(-395967805) } break } @@ -221,19 +1233,149 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .favedStickers(let _data): - return ("favedStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("packs", ConstructorParameterDescription(_data.packs)), ("stickers", ConstructorParameterDescription(_data.stickers))]) - case .favedStickersNotModified: - return ("favedStickersNotModified", []) + case .allStickers(let _data): + return ("allStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("sets", ConstructorParameterDescription(_data.sets))]) + case .allStickersNotModified: + return ("allStickersNotModified", []) } } - public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { + public static func parse_allStickers(_ reader: BufferReader) -> AllStickers? { var _1: Int64? _1 = reader.readInt64() - var _2: [Api.StickerPack]? + var _2: [Api.StickerSet]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSet.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.AllStickers.allStickers(Cons_allStickers(hash: _1!, sets: _2!)) + } + else { + return nil + } + } + public static func parse_allStickersNotModified(_ reader: BufferReader) -> AllStickers? { + return Api.messages.AllStickers.allStickersNotModified + } + } +} +public extension Api.messages { + enum ArchivedStickers: TypeConstructorDescription { + public class Cons_archivedStickers: TypeConstructorDescription { + public var count: Int32 + public var sets: [Api.StickerSetCovered] + public init(count: Int32, sets: [Api.StickerSetCovered]) { + self.count = count + self.sets = sets + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("archivedStickers", [("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets))]) + } + } + case archivedStickers(Cons_archivedStickers) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .archivedStickers(let _data): + if boxed { + buffer.appendInt32(1338747336) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sets.count)) + for item in _data.sets { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .archivedStickers(let _data): + return ("archivedStickers", [("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets))]) + } + } + + public static func parse_archivedStickers(_ reader: BufferReader) -> ArchivedStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.ArchivedStickers.archivedStickers(Cons_archivedStickers(count: _1!, sets: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum AvailableEffects: TypeConstructorDescription { + public class Cons_availableEffects: TypeConstructorDescription { + public var hash: Int32 + public var effects: [Api.AvailableEffect] + public var documents: [Api.Document] + public init(hash: Int32, effects: [Api.AvailableEffect], documents: [Api.Document]) { + self.hash = hash + self.effects = effects + self.documents = documents + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("availableEffects", [("hash", ConstructorParameterDescription(self.hash)), ("effects", ConstructorParameterDescription(self.effects)), ("documents", ConstructorParameterDescription(self.documents))]) + } + } + case availableEffects(Cons_availableEffects) + case availableEffectsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .availableEffects(let _data): + if boxed { + buffer.appendInt32(-1109696146) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.effects.count)) + for item in _data.effects { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + item.serialize(buffer, true) + } + break + case .availableEffectsNotModified: + if boxed { + buffer.appendInt32(-772957605) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .availableEffects(let _data): + return ("availableEffects", [("hash", ConstructorParameterDescription(_data.hash)), ("effects", ConstructorParameterDescription(_data.effects)), ("documents", ConstructorParameterDescription(_data.documents))]) + case .availableEffectsNotModified: + return ("availableEffectsNotModified", []) + } + } + + public static func parse_availableEffects(_ reader: BufferReader) -> AvailableEffects? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.AvailableEffect]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableEffect.self) } var _3: [Api.Document]? if let _ = reader.readInt32() { @@ -243,792 +1385,49 @@ public extension Api.messages { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.messages.FavedStickers.favedStickers(Cons_favedStickers(hash: _1!, packs: _2!, stickers: _3!)) + return Api.messages.AvailableEffects.availableEffects(Cons_availableEffects(hash: _1!, effects: _2!, documents: _3!)) } else { return nil } } - public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { - return Api.messages.FavedStickers.favedStickersNotModified + public static func parse_availableEffectsNotModified(_ reader: BufferReader) -> AvailableEffects? { + return Api.messages.AvailableEffects.availableEffectsNotModified } } } public extension Api.messages { - enum FeaturedStickers: TypeConstructorDescription { - public class Cons_featuredStickers: TypeConstructorDescription { - public var flags: Int32 - public var hash: Int64 - public var count: Int32 - public var sets: [Api.StickerSetCovered] - public var unread: [Int64] - public init(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) { - self.flags = flags + enum AvailableReactions: TypeConstructorDescription { + public class Cons_availableReactions: TypeConstructorDescription { + public var hash: Int32 + public var reactions: [Api.AvailableReaction] + public init(hash: Int32, reactions: [Api.AvailableReaction]) { self.hash = hash - self.count = count - self.sets = sets - self.unread = unread - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("featuredStickers", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets)), ("unread", ConstructorParameterDescription(self.unread))]) - } - } - public class Cons_featuredStickersNotModified: TypeConstructorDescription { - public var count: Int32 - public init(count: Int32) { - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("featuredStickersNotModified", [("count", ConstructorParameterDescription(self.count))]) - } - } - case featuredStickers(Cons_featuredStickers) - case featuredStickersNotModified(Cons_featuredStickersNotModified) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .featuredStickers(let _data): - if boxed { - buffer.appendInt32(-1103615738) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.hash, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sets.count)) - for item in _data.sets { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.unread.count)) - for item in _data.unread { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .featuredStickersNotModified(let _data): - if boxed { - buffer.appendInt32(-958657434) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .featuredStickers(let _data): - return ("featuredStickers", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets)), ("unread", ConstructorParameterDescription(_data.unread))]) - case .featuredStickersNotModified(let _data): - return ("featuredStickersNotModified", [("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - var _5: [Int64]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.FeaturedStickers.featuredStickers(Cons_featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)) - } - else { - return nil - } - } - public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.FeaturedStickers.featuredStickersNotModified(Cons_featuredStickersNotModified(count: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum ForumTopics: TypeConstructorDescription { - public class Cons_forumTopics: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var topics: [Api.ForumTopic] - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public var pts: Int32 - public init(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) { - self.flags = flags - self.count = count - self.topics = topics - self.messages = messages - self.chats = chats - self.users = users - self.pts = pts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("forumTopics", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("topics", ConstructorParameterDescription(self.topics)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("pts", ConstructorParameterDescription(self.pts))]) - } - } - case forumTopics(Cons_forumTopics) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .forumTopics(let _data): - if boxed { - buffer.appendInt32(913709011) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topics.count)) - for item in _data.topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .forumTopics(let _data): - return ("forumTopics", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("topics", ConstructorParameterDescription(_data.topics)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("pts", ConstructorParameterDescription(_data.pts))]) - } - } - - public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _7: Int32? - _7 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.ForumTopics.forumTopics(Cons_forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum FoundStickerSets: TypeConstructorDescription { - public class Cons_foundStickerSets: TypeConstructorDescription { - public var hash: Int64 - public var sets: [Api.StickerSetCovered] - public init(hash: Int64, sets: [Api.StickerSetCovered]) { - self.hash = hash - self.sets = sets - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("foundStickerSets", [("hash", ConstructorParameterDescription(self.hash)), ("sets", ConstructorParameterDescription(self.sets))]) - } - } - case foundStickerSets(Cons_foundStickerSets) - case foundStickerSetsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .foundStickerSets(let _data): - if boxed { - buffer.appendInt32(-1963942446) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sets.count)) - for item in _data.sets { - item.serialize(buffer, true) - } - break - case .foundStickerSetsNotModified: - if boxed { - buffer.appendInt32(223655517) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .foundStickerSets(let _data): - return ("foundStickerSets", [("hash", ConstructorParameterDescription(_data.hash)), ("sets", ConstructorParameterDescription(_data.sets))]) - case .foundStickerSetsNotModified: - return ("foundStickerSetsNotModified", []) - } - } - - public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickerSets.foundStickerSets(Cons_foundStickerSets(hash: _1!, sets: _2!)) - } - else { - return nil - } - } - public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { - return Api.messages.FoundStickerSets.foundStickerSetsNotModified - } - } -} -public extension Api.messages { - enum FoundStickers: TypeConstructorDescription { - public class Cons_foundStickers: TypeConstructorDescription { - public var flags: Int32 - public var nextOffset: Int32? - public var hash: Int64 - public var stickers: [Api.Document] - public init(flags: Int32, nextOffset: Int32?, hash: Int64, stickers: [Api.Document]) { - self.flags = flags - self.nextOffset = nextOffset - self.hash = hash - self.stickers = stickers - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("foundStickers", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("hash", ConstructorParameterDescription(self.hash)), ("stickers", ConstructorParameterDescription(self.stickers))]) - } - } - public class Cons_foundStickersNotModified: TypeConstructorDescription { - public var flags: Int32 - public var nextOffset: Int32? - public init(flags: Int32, nextOffset: Int32?) { - self.flags = flags - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("foundStickersNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - case foundStickers(Cons_foundStickers) - case foundStickersNotModified(Cons_foundStickersNotModified) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .foundStickers(let _data): - if boxed { - buffer.appendInt32(-2100698480) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.nextOffset!, buffer: buffer, boxed: false) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stickers.count)) - for item in _data.stickers { - item.serialize(buffer, true) - } - break - case .foundStickersNotModified(let _data): - if boxed { - buffer.appendInt32(1611711796) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.nextOffset!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .foundStickers(let _data): - return ("foundStickers", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("hash", ConstructorParameterDescription(_data.hash)), ("stickers", ConstructorParameterDescription(_data.stickers))]) - case .foundStickersNotModified(let _data): - return ("foundStickersNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) - } - } - - public static func parse_foundStickers(_ reader: BufferReader) -> FoundStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int64? - _3 = reader.readInt64() - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.FoundStickers.foundStickers(Cons_foundStickers(flags: _1!, nextOffset: _2, hash: _3!, stickers: _4!)) - } - else { - return nil - } - } - public static func parse_foundStickersNotModified(_ reader: BufferReader) -> FoundStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.messages.FoundStickers.foundStickersNotModified(Cons_foundStickersNotModified(flags: _1!, nextOffset: _2)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum HighScores: TypeConstructorDescription { - public class Cons_highScores: TypeConstructorDescription { - public var scores: [Api.HighScore] - public var users: [Api.User] - public init(scores: [Api.HighScore], users: [Api.User]) { - self.scores = scores - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("highScores", [("scores", ConstructorParameterDescription(self.scores)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case highScores(Cons_highScores) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .highScores(let _data): - if boxed { - buffer.appendInt32(-1707344487) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.scores.count)) - for item in _data.scores { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .highScores(let _data): - return ("highScores", [("scores", ConstructorParameterDescription(_data.scores)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_highScores(_ reader: BufferReader) -> HighScores? { - var _1: [Api.HighScore]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.HighScores.highScores(Cons_highScores(scores: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum HistoryImport: TypeConstructorDescription { - public class Cons_historyImport: TypeConstructorDescription { - public var id: Int64 - public init(id: Int64) { - self.id = id - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("historyImport", [("id", ConstructorParameterDescription(self.id))]) - } - } - case historyImport(Cons_historyImport) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .historyImport(let _data): - if boxed { - buffer.appendInt32(375566091) - } - serializeInt64(_data.id, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .historyImport(let _data): - return ("historyImport", [("id", ConstructorParameterDescription(_data.id))]) - } - } - - public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return Api.messages.HistoryImport.historyImport(Cons_historyImport(id: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum HistoryImportParsed: TypeConstructorDescription { - public class Cons_historyImportParsed: TypeConstructorDescription { - public var flags: Int32 - public var title: String? - public init(flags: Int32, title: String?) { - self.flags = flags - self.title = title - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("historyImportParsed", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title))]) - } - } - case historyImportParsed(Cons_historyImportParsed) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .historyImportParsed(let _data): - if boxed { - buffer.appendInt32(1578088377) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.title!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .historyImportParsed(let _data): - return ("historyImportParsed", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title))]) - } - } - - public static func parse_historyImportParsed(_ reader: BufferReader) -> HistoryImportParsed? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 2) != 0 { - _2 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil - if _c1 && _c2 { - return Api.messages.HistoryImportParsed.historyImportParsed(Cons_historyImportParsed(flags: _1!, title: _2)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum InactiveChats: TypeConstructorDescription { - public class Cons_inactiveChats: TypeConstructorDescription { - public var dates: [Int32] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(dates: [Int32], chats: [Api.Chat], users: [Api.User]) { - self.dates = dates - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inactiveChats", [("dates", ConstructorParameterDescription(self.dates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case inactiveChats(Cons_inactiveChats) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inactiveChats(let _data): - if boxed { - buffer.appendInt32(-1456996667) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dates.count)) - for item in _data.dates { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inactiveChats(let _data): - return ("inactiveChats", [("dates", ConstructorParameterDescription(_data.dates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_inactiveChats(_ reader: BufferReader) -> InactiveChats? { - var _1: [Int32]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.InactiveChats.inactiveChats(Cons_inactiveChats(dates: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - indirect enum InvitedUsers: TypeConstructorDescription { - public class Cons_invitedUsers: TypeConstructorDescription { - public var updates: Api.Updates - public var missingInvitees: [Api.MissingInvitee] - public init(updates: Api.Updates, missingInvitees: [Api.MissingInvitee]) { - self.updates = updates - self.missingInvitees = missingInvitees - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("invitedUsers", [("updates", ConstructorParameterDescription(self.updates)), ("missingInvitees", ConstructorParameterDescription(self.missingInvitees))]) - } - } - case invitedUsers(Cons_invitedUsers) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .invitedUsers(let _data): - if boxed { - buffer.appendInt32(2136862630) - } - _data.updates.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.missingInvitees.count)) - for item in _data.missingInvitees { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .invitedUsers(let _data): - return ("invitedUsers", [("updates", ConstructorParameterDescription(_data.updates)), ("missingInvitees", ConstructorParameterDescription(_data.missingInvitees))]) - } - } - - public static func parse_invitedUsers(_ reader: BufferReader) -> InvitedUsers? { - var _1: Api.Updates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Updates - } - var _2: [Api.MissingInvitee]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MissingInvitee.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.InvitedUsers.invitedUsers(Cons_invitedUsers(updates: _1!, missingInvitees: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum MessageEditData: TypeConstructorDescription { - public class Cons_messageEditData: TypeConstructorDescription { - public var flags: Int32 - public init(flags: Int32) { - self.flags = flags - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageEditData", [("flags", ConstructorParameterDescription(self.flags))]) - } - } - case messageEditData(Cons_messageEditData) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageEditData(let _data): - if boxed { - buffer.appendInt32(649453030) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageEditData(let _data): - return ("messageEditData", [("flags", ConstructorParameterDescription(_data.flags))]) - } - } - - public static func parse_messageEditData(_ reader: BufferReader) -> MessageEditData? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.MessageEditData.messageEditData(Cons_messageEditData(flags: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum MessageReactionsList: TypeConstructorDescription { - public class Cons_messageReactionsList: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var reactions: [Api.MessagePeerReaction] - public var chats: [Api.Chat] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, count: Int32, reactions: [Api.MessagePeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { - self.flags = flags - self.count = count self.reactions = reactions - self.chats = chats - self.users = users - self.nextOffset = nextOffset } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageReactionsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("reactions", ConstructorParameterDescription(self.reactions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + return ("availableReactions", [("hash", ConstructorParameterDescription(self.hash)), ("reactions", ConstructorParameterDescription(self.reactions))]) } } - case messageReactionsList(Cons_messageReactionsList) + case availableReactions(Cons_availableReactions) + case availableReactionsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageReactionsList(let _data): + case .availableReactions(let _data): if boxed { - buffer.appendInt32(834488621) + buffer.appendInt32(1989032621) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.reactions.count)) for item in _data.reactions { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + break + case .availableReactionsNotModified: + if boxed { + buffer.appendInt32(-1626924713) } break } @@ -1036,499 +1435,79 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .messageReactionsList(let _data): - return ("messageReactionsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + case .availableReactions(let _data): + return ("availableReactions", [("hash", ConstructorParameterDescription(_data.hash)), ("reactions", ConstructorParameterDescription(_data.reactions))]) + case .availableReactionsNotModified: + return ("availableReactionsNotModified", []) } } - public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? { + public static func parse_availableReactions(_ reader: BufferReader) -> AvailableReactions? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.MessagePeerReaction]? + var _2: [Api.AvailableReaction]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.MessageReactionsList.messageReactionsList(Cons_messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum MessageViews: TypeConstructorDescription { - public class Cons_messageViews: TypeConstructorDescription { - public var views: [Api.MessageViews] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(views: [Api.MessageViews], chats: [Api.Chat], users: [Api.User]) { - self.views = views - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageViews", [("views", ConstructorParameterDescription(self.views)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case messageViews(Cons_messageViews) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageViews(let _data): - if boxed { - buffer.appendInt32(-1228606141) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.views.count)) - for item in _data.views { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .messageViews(let _data): - return ("messageViews", [("views", ConstructorParameterDescription(_data.views)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { - var _1: [Api.MessageViews]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.MessageViews.messageViews(Cons_messageViews(views: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum Messages: TypeConstructorDescription { - public class Cons_channelMessages: TypeConstructorDescription { - public var flags: Int32 - public var pts: Int32 - public var count: Int32 - public var offsetIdOffset: Int32? - public var messages: [Api.Message] - public var topics: [Api.ForumTopic] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.pts = pts - self.count = count - self.offsetIdOffset = offsetIdOffset - self.messages = messages - self.topics = topics - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelMessages", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("count", ConstructorParameterDescription(self.count)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_messages: TypeConstructorDescription { - public var messages: [Api.Message] - public var topics: [Api.ForumTopic] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { - self.messages = messages - self.topics = topics - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messages", [("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_messagesNotModified: TypeConstructorDescription { - public var count: Int32 - public init(count: Int32) { - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagesNotModified", [("count", ConstructorParameterDescription(self.count))]) - } - } - public class Cons_messagesSlice: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var nextRate: Int32? - public var offsetIdOffset: Int32? - public var searchFlood: Api.SearchPostsFlood? - public var messages: [Api.Message] - public var topics: [Api.ForumTopic] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, searchFlood: Api.SearchPostsFlood?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.count = count - self.nextRate = nextRate - self.offsetIdOffset = offsetIdOffset - self.searchFlood = searchFlood - self.messages = messages - self.topics = topics - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messagesSlice", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("nextRate", ConstructorParameterDescription(self.nextRate)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("searchFlood", ConstructorParameterDescription(self.searchFlood)), ("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case channelMessages(Cons_channelMessages) - case messages(Cons_messages) - case messagesNotModified(Cons_messagesNotModified) - case messagesSlice(Cons_messagesSlice) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .channelMessages(let _data): - if boxed { - buffer.appendInt32(-948520370) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topics.count)) - for item in _data.topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .messages(let _data): - if boxed { - buffer.appendInt32(494135274) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topics.count)) - for item in _data.topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .messagesNotModified(let _data): - if boxed { - buffer.appendInt32(1951620897) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - case .messagesSlice(let _data): - if boxed { - buffer.appendInt32(1595959062) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.nextRate!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - _data.searchFlood!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topics.count)) - for item in _data.topics { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .channelMessages(let _data): - return ("channelMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("count", ConstructorParameterDescription(_data.count)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .messages(let _data): - return ("messages", [("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .messagesNotModified(let _data): - return ("messagesNotModified", [("count", ConstructorParameterDescription(_data.count))]) - case .messagesSlice(let _data): - return ("messagesSlice", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("nextRate", ConstructorParameterDescription(_data.nextRate)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("searchFlood", ConstructorParameterDescription(_data.searchFlood)), ("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_channelMessages(_ reader: BufferReader) -> Messages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: [Api.Message]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _6: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _7: [Api.Chat]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _8: [Api.User]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.messages.Messages.channelMessages(Cons_channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!)) - } - else { - return nil - } - } - public static func parse_messages(_ reader: BufferReader) -> Messages? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Messages.messages(Cons_messages(messages: _1!, topics: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - public static func parse_messagesNotModified(_ reader: BufferReader) -> Messages? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.Messages.messagesNotModified(Cons_messagesNotModified(count: _1!)) - } - else { - return nil - } - } - public static func parse_messagesSlice(_ reader: BufferReader) -> Messages? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: Api.SearchPostsFlood? - if Int(_1!) & Int(1 << 3) != 0 { - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood - } - } - var _6: [Api.Message]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _7: [Api.ForumTopic]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) - } - var _8: [Api.Chat]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _9: [Api.User]? - if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.messages.Messages.messagesSlice(Cons_messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, searchFlood: _5, messages: _6!, topics: _7!, chats: _8!, users: _9!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum MyStickers: TypeConstructorDescription { - public class Cons_myStickers: TypeConstructorDescription { - public var count: Int32 - public var sets: [Api.StickerSetCovered] - public init(count: Int32, sets: [Api.StickerSetCovered]) { - self.count = count - self.sets = sets - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("myStickers", [("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets))]) - } - } - case myStickers(Cons_myStickers) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .myStickers(let _data): - if boxed { - buffer.appendInt32(-83926371) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sets.count)) - for item in _data.sets { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .myStickers(let _data): - return ("myStickers", [("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets))]) - } - } - - public static func parse_myStickers(_ reader: BufferReader) -> MyStickers? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StickerSetCovered]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AvailableReaction.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.MyStickers.myStickers(Cons_myStickers(count: _1!, sets: _2!)) + return Api.messages.AvailableReactions.availableReactions(Cons_availableReactions(hash: _1!, reactions: _2!)) + } + else { + return nil + } + } + public static func parse_availableReactionsNotModified(_ reader: BufferReader) -> AvailableReactions? { + return Api.messages.AvailableReactions.availableReactionsNotModified + } + } +} +public extension Api.messages { + enum BotApp: TypeConstructorDescription { + public class Cons_botApp: TypeConstructorDescription { + public var flags: Int32 + public var app: Api.BotApp + public init(flags: Int32, app: Api.BotApp) { + self.flags = flags + self.app = app + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("botApp", [("flags", ConstructorParameterDescription(self.flags)), ("app", ConstructorParameterDescription(self.app))]) + } + } + case botApp(Cons_botApp) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .botApp(let _data): + if boxed { + buffer.appendInt32(-347034123) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.app.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .botApp(let _data): + return ("botApp", [("flags", ConstructorParameterDescription(_data.flags)), ("app", ConstructorParameterDescription(_data.app))]) + } + } + + public static func parse_botApp(_ reader: BufferReader) -> BotApp? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.BotApp? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BotApp + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.BotApp.botApp(Cons_botApp(flags: _1!, app: _2!)) } else { return nil @@ -1537,92 +1516,68 @@ public extension Api.messages { } } public extension Api.messages { - enum PeerDialogs: TypeConstructorDescription { - public class Cons_peerDialogs: TypeConstructorDescription { - public var dialogs: [Api.Dialog] - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public var state: Api.updates.State - public init(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) { - self.dialogs = dialogs - self.messages = messages - self.chats = chats - self.users = users - self.state = state + enum BotCallbackAnswer: TypeConstructorDescription { + public class Cons_botCallbackAnswer: TypeConstructorDescription { + public var flags: Int32 + public var message: String? + public var url: String? + public var cacheTime: Int32 + public init(flags: Int32, message: String?, url: String?, cacheTime: Int32) { + self.flags = flags + self.message = message + self.url = url + self.cacheTime = cacheTime } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerDialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("state", ConstructorParameterDescription(self.state))]) + return ("botCallbackAnswer", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("url", ConstructorParameterDescription(self.url)), ("cacheTime", ConstructorParameterDescription(self.cacheTime))]) } } - case peerDialogs(Cons_peerDialogs) + case botCallbackAnswer(Cons_botCallbackAnswer) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerDialogs(let _data): + case .botCallbackAnswer(let _data): if boxed { - buffer.appendInt32(863093588) + buffer.appendInt32(911761060) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dialogs.count)) - for item in _data.dialogs { - item.serialize(buffer, true) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.message!, buffer: buffer, boxed: false) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.url!, buffer: buffer, boxed: false) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - _data.state.serialize(buffer, true) + serializeInt32(_data.cacheTime, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .peerDialogs(let _data): - return ("peerDialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("state", ConstructorParameterDescription(_data.state))]) + case .botCallbackAnswer(let _data): + return ("botCallbackAnswer", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("url", ConstructorParameterDescription(_data.url)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime))]) } } - public static func parse_peerDialogs(_ reader: BufferReader) -> PeerDialogs? { - var _1: [Api.Dialog]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + public static func parse_botCallbackAnswer(_ reader: BufferReader) -> BotCallbackAnswer? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _5: Api.updates.State? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.updates.State + var _3: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _3 = parseString(reader) } + var _4: Int32? + _4 = reader.readInt32() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.PeerDialogs.peerDialogs(Cons_peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!)) + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.BotCallbackAnswer.botCallbackAnswer(Cons_botCallbackAnswer(flags: _1!, message: _2, url: _3, cacheTime: _4!)) } else { return nil @@ -1631,68 +1586,48 @@ public extension Api.messages { } } public extension Api.messages { - enum PeerSettings: TypeConstructorDescription { - public class Cons_peerSettings: TypeConstructorDescription { - public var settings: Api.PeerSettings - public var chats: [Api.Chat] - public var users: [Api.User] - public init(settings: Api.PeerSettings, chats: [Api.Chat], users: [Api.User]) { - self.settings = settings - self.chats = chats - self.users = users + enum BotPreparedInlineMessage: TypeConstructorDescription { + public class Cons_botPreparedInlineMessage: TypeConstructorDescription { + public var id: String + public var expireDate: Int32 + public init(id: String, expireDate: Int32) { + self.id = id + self.expireDate = expireDate } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerSettings", [("settings", ConstructorParameterDescription(self.settings)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("botPreparedInlineMessage", [("id", ConstructorParameterDescription(self.id)), ("expireDate", ConstructorParameterDescription(self.expireDate))]) } } - case peerSettings(Cons_peerSettings) + case botPreparedInlineMessage(Cons_botPreparedInlineMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerSettings(let _data): + case .botPreparedInlineMessage(let _data): if boxed { - buffer.appendInt32(1753266509) - } - _data.settings.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + buffer.appendInt32(-1899035375) } + serializeString(_data.id, buffer: buffer, boxed: false) + serializeInt32(_data.expireDate, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .peerSettings(let _data): - return ("peerSettings", [("settings", ConstructorParameterDescription(_data.settings)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .botPreparedInlineMessage(let _data): + return ("botPreparedInlineMessage", [("id", ConstructorParameterDescription(_data.id)), ("expireDate", ConstructorParameterDescription(_data.expireDate))]) } } - public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { - var _1: Api.PeerSettings? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PeerSettings - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } + public static func parse_botPreparedInlineMessage(_ reader: BufferReader) -> BotPreparedInlineMessage? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.PeerSettings.peerSettings(Cons_peerSettings(settings: _1!, chats: _2!, users: _3!)) + if _c1 && _c2 { + return Api.messages.BotPreparedInlineMessage.botPreparedInlineMessage(Cons_botPreparedInlineMessage(id: _1!, expireDate: _2!)) } else { return nil @@ -1701,37 +1636,52 @@ public extension Api.messages { } } public extension Api.messages { - enum PreparedInlineMessage: TypeConstructorDescription { - public class Cons_preparedInlineMessage: TypeConstructorDescription { + enum BotResults: TypeConstructorDescription { + public class Cons_botResults: TypeConstructorDescription { + public var flags: Int32 public var queryId: Int64 - public var result: Api.BotInlineResult - public var peerTypes: [Api.InlineQueryPeerType] + public var nextOffset: String? + public var switchPm: Api.InlineBotSwitchPM? + public var switchWebview: Api.InlineBotWebView? + public var results: [Api.BotInlineResult] public var cacheTime: Int32 public var users: [Api.User] - public init(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], cacheTime: Int32, users: [Api.User]) { + public init(flags: Int32, queryId: Int64, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?, results: [Api.BotInlineResult], cacheTime: Int32, users: [Api.User]) { + self.flags = flags self.queryId = queryId - self.result = result - self.peerTypes = peerTypes + self.nextOffset = nextOffset + self.switchPm = switchPm + self.switchWebview = switchWebview + self.results = results self.cacheTime = cacheTime self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("preparedInlineMessage", [("queryId", ConstructorParameterDescription(self.queryId)), ("result", ConstructorParameterDescription(self.result)), ("peerTypes", ConstructorParameterDescription(self.peerTypes)), ("cacheTime", ConstructorParameterDescription(self.cacheTime)), ("users", ConstructorParameterDescription(self.users))]) + return ("botResults", [("flags", ConstructorParameterDescription(self.flags)), ("queryId", ConstructorParameterDescription(self.queryId)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("switchPm", ConstructorParameterDescription(self.switchPm)), ("switchWebview", ConstructorParameterDescription(self.switchWebview)), ("results", ConstructorParameterDescription(self.results)), ("cacheTime", ConstructorParameterDescription(self.cacheTime)), ("users", ConstructorParameterDescription(self.users))]) } } - case preparedInlineMessage(Cons_preparedInlineMessage) + case botResults(Cons_botResults) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .preparedInlineMessage(let _data): + case .botResults(let _data): if boxed { - buffer.appendInt32(-11046771) + buffer.appendInt32(-534646026) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt64(_data.queryId, buffer: buffer, boxed: false) - _data.result.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.switchPm!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.switchWebview!.serialize(buffer, true) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peerTypes.count)) - for item in _data.peerTypes { + buffer.appendInt32(Int32(_data.results.count)) + for item in _data.results { item.serialize(buffer, true) } serializeInt32(_data.cacheTime, buffer: buffer, boxed: false) @@ -1746,35 +1696,52 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .preparedInlineMessage(let _data): - return ("preparedInlineMessage", [("queryId", ConstructorParameterDescription(_data.queryId)), ("result", ConstructorParameterDescription(_data.result)), ("peerTypes", ConstructorParameterDescription(_data.peerTypes)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime)), ("users", ConstructorParameterDescription(_data.users))]) + case .botResults(let _data): + return ("botResults", [("flags", ConstructorParameterDescription(_data.flags)), ("queryId", ConstructorParameterDescription(_data.queryId)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("switchPm", ConstructorParameterDescription(_data.switchPm)), ("switchWebview", ConstructorParameterDescription(_data.switchWebview)), ("results", ConstructorParameterDescription(_data.results)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_preparedInlineMessage(_ reader: BufferReader) -> PreparedInlineMessage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.BotInlineResult? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BotInlineResult + public static func parse_botResults(_ reader: BufferReader) -> BotResults? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = parseString(reader) } - var _3: [Api.InlineQueryPeerType]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) + var _4: Api.InlineBotSwitchPM? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InlineBotSwitchPM + } } - var _4: Int32? - _4 = reader.readInt32() - var _5: [Api.User]? + var _5: Api.InlineBotWebView? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.InlineBotWebView + } + } + var _6: [Api.BotInlineResult]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInlineResult.self) + } + var _7: Int32? + _7 = reader.readInt32() + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.PreparedInlineMessage.preparedInlineMessage(Cons_preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!)) + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.BotResults.botResults(Cons_botResults(flags: _1!, queryId: _2!, nextOffset: _3, switchPm: _4, switchWebview: _5, results: _6!, cacheTime: _7!, users: _8!)) } else { return nil @@ -1782,167 +1749,3 @@ public extension Api.messages { } } } -public extension Api.messages { - enum QuickReplies: TypeConstructorDescription { - public class Cons_quickReplies: TypeConstructorDescription { - public var quickReplies: [Api.QuickReply] - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { - self.quickReplies = quickReplies - self.messages = messages - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("quickReplies", [("quickReplies", ConstructorParameterDescription(self.quickReplies)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case quickReplies(Cons_quickReplies) - case quickRepliesNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .quickReplies(let _data): - if boxed { - buffer.appendInt32(-963811691) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.quickReplies.count)) - for item in _data.quickReplies { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .quickRepliesNotModified: - if boxed { - buffer.appendInt32(1603398491) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .quickReplies(let _data): - return ("quickReplies", [("quickReplies", ConstructorParameterDescription(_data.quickReplies)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .quickRepliesNotModified: - return ("quickRepliesNotModified", []) - } - } - - public static func parse_quickReplies(_ reader: BufferReader) -> QuickReplies? { - var _1: [Api.QuickReply]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.QuickReplies.quickReplies(Cons_quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!)) - } - else { - return nil - } - } - public static func parse_quickRepliesNotModified(_ reader: BufferReader) -> QuickReplies? { - return Api.messages.QuickReplies.quickRepliesNotModified - } - } -} -public extension Api.messages { - enum Reactions: TypeConstructorDescription { - public class Cons_reactions: TypeConstructorDescription { - public var hash: Int64 - public var reactions: [Api.Reaction] - public init(hash: Int64, reactions: [Api.Reaction]) { - self.hash = hash - self.reactions = reactions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("reactions", [("hash", ConstructorParameterDescription(self.hash)), ("reactions", ConstructorParameterDescription(self.reactions))]) - } - } - case reactions(Cons_reactions) - case reactionsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .reactions(let _data): - if boxed { - buffer.appendInt32(-352454890) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.reactions.count)) - for item in _data.reactions { - item.serialize(buffer, true) - } - break - case .reactionsNotModified: - if boxed { - buffer.appendInt32(-1334846497) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .reactions(let _data): - return ("reactions", [("hash", ConstructorParameterDescription(_data.hash)), ("reactions", ConstructorParameterDescription(_data.reactions))]) - case .reactionsNotModified: - return ("reactionsNotModified", []) - } - } - - public static func parse_reactions(_ reader: BufferReader) -> Reactions? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Reaction]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Reactions.reactions(Cons_reactions(hash: _1!, reactions: _2!)) - } - else { - return nil - } - } - public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { - return Api.messages.Reactions.reactionsNotModified - } - } -} diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index 2ad050397d..ff6fd215ca 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -1,127 +1,677 @@ public extension Api.messages { - enum RecentStickers: TypeConstructorDescription { - public class Cons_recentStickers: TypeConstructorDescription { - public var hash: Int64 - public var packs: [Api.StickerPack] - public var stickers: [Api.Document] - public var dates: [Int32] - public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) { - self.hash = hash - self.packs = packs - self.stickers = stickers - self.dates = dates + enum ChatAdminsWithInvites: TypeConstructorDescription { + public class Cons_chatAdminsWithInvites: TypeConstructorDescription { + public var admins: [Api.ChatAdminWithInvites] + public var users: [Api.User] + public init(admins: [Api.ChatAdminWithInvites], users: [Api.User]) { + self.admins = admins + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("recentStickers", [("hash", ConstructorParameterDescription(self.hash)), ("packs", ConstructorParameterDescription(self.packs)), ("stickers", ConstructorParameterDescription(self.stickers)), ("dates", ConstructorParameterDescription(self.dates))]) + return ("chatAdminsWithInvites", [("admins", ConstructorParameterDescription(self.admins)), ("users", ConstructorParameterDescription(self.users))]) } } - case recentStickers(Cons_recentStickers) - case recentStickersNotModified + case chatAdminsWithInvites(Cons_chatAdminsWithInvites) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .recentStickers(let _data): + case .chatAdminsWithInvites(let _data): if boxed { - buffer.appendInt32(-1999405994) + buffer.appendInt32(-1231326505) } - serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.packs.count)) - for item in _data.packs { + buffer.appendInt32(Int32(_data.admins.count)) + for item in _data.admins { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stickers.count)) - for item in _data.stickers { + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.dates.count)) - for item in _data.dates { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .recentStickersNotModified: - if boxed { - buffer.appendInt32(186120336) - } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .recentStickers(let _data): - return ("recentStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("packs", ConstructorParameterDescription(_data.packs)), ("stickers", ConstructorParameterDescription(_data.stickers)), ("dates", ConstructorParameterDescription(_data.dates))]) - case .recentStickersNotModified: - return ("recentStickersNotModified", []) + case .chatAdminsWithInvites(let _data): + return ("chatAdminsWithInvites", [("admins", ConstructorParameterDescription(_data.admins)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerPack]? + public static func parse_chatAdminsWithInvites(_ reader: BufferReader) -> ChatAdminsWithInvites? { + var _1: [Api.ChatAdminWithInvites]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatAdminWithInvites.self) } - var _3: [Api.Document]? + var _2: [Api.User]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.RecentStickers.recentStickers(Cons_recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!)) + if _c1 && _c2 { + return Api.messages.ChatAdminsWithInvites.chatAdminsWithInvites(Cons_chatAdminsWithInvites(admins: _1!, users: _2!)) } else { return nil } } - public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { - return Api.messages.RecentStickers.recentStickersNotModified + } +} +public extension Api.messages { + enum ChatFull: TypeConstructorDescription { + public class Cons_chatFull: TypeConstructorDescription { + public var fullChat: Api.ChatFull + public var chats: [Api.Chat] + public var users: [Api.User] + public init(fullChat: Api.ChatFull, chats: [Api.Chat], users: [Api.User]) { + self.fullChat = fullChat + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatFull", [("fullChat", ConstructorParameterDescription(self.fullChat)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case chatFull(Cons_chatFull) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatFull(let _data): + if boxed { + buffer.appendInt32(-438840932) + } + _data.fullChat.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .chatFull(let _data): + return ("chatFull", [("fullChat", ConstructorParameterDescription(_data.fullChat)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? { + var _1: Api.ChatFull? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ChatFull + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ChatFull.chatFull(Cons_chatFull(fullChat: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } } } } public extension Api.messages { - enum SavedDialogs: TypeConstructorDescription { - public class Cons_savedDialogs: TypeConstructorDescription { - public var dialogs: [Api.SavedDialog] + enum ChatInviteImporters: TypeConstructorDescription { + public class Cons_chatInviteImporters: TypeConstructorDescription { + public var count: Int32 + public var importers: [Api.ChatInviteImporter] + public var users: [Api.User] + public init(count: Int32, importers: [Api.ChatInviteImporter], users: [Api.User]) { + self.count = count + self.importers = importers + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatInviteImporters", [("count", ConstructorParameterDescription(self.count)), ("importers", ConstructorParameterDescription(self.importers)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case chatInviteImporters(Cons_chatInviteImporters) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatInviteImporters(let _data): + if boxed { + buffer.appendInt32(-2118733814) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.importers.count)) + for item in _data.importers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .chatInviteImporters(let _data): + return ("chatInviteImporters", [("count", ConstructorParameterDescription(_data.count)), ("importers", ConstructorParameterDescription(_data.importers)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_chatInviteImporters(_ reader: BufferReader) -> ChatInviteImporters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ChatInviteImporter]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatInviteImporter.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ChatInviteImporters.chatInviteImporters(Cons_chatInviteImporters(count: _1!, importers: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + indirect enum ChatInviteJoinResult: TypeConstructorDescription { + public class Cons_chatInviteJoinResultOk: TypeConstructorDescription { + public var updates: Api.Updates + public init(updates: Api.Updates) { + self.updates = updates + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatInviteJoinResultOk", [("updates", ConstructorParameterDescription(self.updates))]) + } + } + public class Cons_chatInviteJoinResultWebView: TypeConstructorDescription { + public var botId: Int64 + public var webview: Api.WebViewResult + public var users: [Api.User] + public init(botId: Int64, webview: Api.WebViewResult, users: [Api.User]) { + self.botId = botId + self.webview = webview + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatInviteJoinResultWebView", [("botId", ConstructorParameterDescription(self.botId)), ("webview", ConstructorParameterDescription(self.webview)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case chatInviteJoinResultOk(Cons_chatInviteJoinResultOk) + case chatInviteJoinResultWebView(Cons_chatInviteJoinResultWebView) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chatInviteJoinResultOk(let _data): + if boxed { + buffer.appendInt32(1146512295) + } + _data.updates.serialize(buffer, true) + break + case .chatInviteJoinResultWebView(let _data): + if boxed { + buffer.appendInt32(793887543) + } + serializeInt64(_data.botId, buffer: buffer, boxed: false) + _data.webview.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .chatInviteJoinResultOk(let _data): + return ("chatInviteJoinResultOk", [("updates", ConstructorParameterDescription(_data.updates))]) + case .chatInviteJoinResultWebView(let _data): + return ("chatInviteJoinResultWebView", [("botId", ConstructorParameterDescription(_data.botId)), ("webview", ConstructorParameterDescription(_data.webview)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_chatInviteJoinResultOk(_ reader: BufferReader) -> ChatInviteJoinResult? { + var _1: Api.Updates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Updates + } + let _c1 = _1 != nil + if _c1 { + return Api.messages.ChatInviteJoinResult.chatInviteJoinResultOk(Cons_chatInviteJoinResultOk(updates: _1!)) + } + else { + return nil + } + } + public static func parse_chatInviteJoinResultWebView(_ reader: BufferReader) -> ChatInviteJoinResult? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.WebViewResult? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ChatInviteJoinResult.chatInviteJoinResultWebView(Cons_chatInviteJoinResultWebView(botId: _1!, webview: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum Chats: TypeConstructorDescription { + public class Cons_chats: TypeConstructorDescription { + public var chats: [Api.Chat] + public init(chats: [Api.Chat]) { + self.chats = chats + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chats", [("chats", ConstructorParameterDescription(self.chats))]) + } + } + public class Cons_chatsSlice: TypeConstructorDescription { + public var count: Int32 + public var chats: [Api.Chat] + public init(count: Int32, chats: [Api.Chat]) { + self.count = count + self.chats = chats + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("chatsSlice", [("count", ConstructorParameterDescription(self.count)), ("chats", ConstructorParameterDescription(self.chats))]) + } + } + case chats(Cons_chats) + case chatsSlice(Cons_chatsSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .chats(let _data): + if boxed { + buffer.appendInt32(1694474197) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + break + case .chatsSlice(let _data): + if boxed { + buffer.appendInt32(-1663561404) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .chats(let _data): + return ("chats", [("chats", ConstructorParameterDescription(_data.chats))]) + case .chatsSlice(let _data): + return ("chatsSlice", [("count", ConstructorParameterDescription(_data.count)), ("chats", ConstructorParameterDescription(_data.chats))]) + } + } + + public static func parse_chats(_ reader: BufferReader) -> Chats? { + var _1: [Api.Chat]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.messages.Chats.chats(Cons_chats(chats: _1!)) + } + else { + return nil + } + } + public static func parse_chatsSlice(_ reader: BufferReader) -> Chats? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.Chats.chatsSlice(Cons_chatsSlice(count: _1!, chats: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum CheckedHistoryImportPeer: TypeConstructorDescription { + public class Cons_checkedHistoryImportPeer: TypeConstructorDescription { + public var confirmText: String + public init(confirmText: String) { + self.confirmText = confirmText + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("checkedHistoryImportPeer", [("confirmText", ConstructorParameterDescription(self.confirmText))]) + } + } + case checkedHistoryImportPeer(Cons_checkedHistoryImportPeer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .checkedHistoryImportPeer(let _data): + if boxed { + buffer.appendInt32(-1571952873) + } + serializeString(_data.confirmText, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .checkedHistoryImportPeer(let _data): + return ("checkedHistoryImportPeer", [("confirmText", ConstructorParameterDescription(_data.confirmText))]) + } + } + + public static func parse_checkedHistoryImportPeer(_ reader: BufferReader) -> CheckedHistoryImportPeer? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.messages.CheckedHistoryImportPeer.checkedHistoryImportPeer(Cons_checkedHistoryImportPeer(confirmText: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum ComposedMessageWithAI: TypeConstructorDescription { + public class Cons_composedMessageWithAI: TypeConstructorDescription { + public var flags: Int32 + public var resultText: Api.TextWithEntities + public var diffText: Api.TextWithEntities? + public init(flags: Int32, resultText: Api.TextWithEntities, diffText: Api.TextWithEntities?) { + self.flags = flags + self.resultText = resultText + self.diffText = diffText + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("composedMessageWithAI", [("flags", ConstructorParameterDescription(self.flags)), ("resultText", ConstructorParameterDescription(self.resultText)), ("diffText", ConstructorParameterDescription(self.diffText))]) + } + } + case composedMessageWithAI(Cons_composedMessageWithAI) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .composedMessageWithAI(let _data): + if boxed { + buffer.appendInt32(-1864913414) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.resultText.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.diffText!.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .composedMessageWithAI(let _data): + return ("composedMessageWithAI", [("flags", ConstructorParameterDescription(_data.flags)), ("resultText", ConstructorParameterDescription(_data.resultText)), ("diffText", ConstructorParameterDescription(_data.diffText))]) + } + } + + public static func parse_composedMessageWithAI(_ reader: BufferReader) -> ComposedMessageWithAI? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.TextWithEntities? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + var _3: Api.TextWithEntities? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ComposedMessageWithAI.composedMessageWithAI(Cons_composedMessageWithAI(flags: _1!, resultText: _2!, diffText: _3)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum DhConfig: TypeConstructorDescription { + public class Cons_dhConfig: TypeConstructorDescription { + public var g: Int32 + public var p: Buffer + public var version: Int32 + public var random: Buffer + public init(g: Int32, p: Buffer, version: Int32, random: Buffer) { + self.g = g + self.p = p + self.version = version + self.random = random + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("dhConfig", [("g", ConstructorParameterDescription(self.g)), ("p", ConstructorParameterDescription(self.p)), ("version", ConstructorParameterDescription(self.version)), ("random", ConstructorParameterDescription(self.random))]) + } + } + public class Cons_dhConfigNotModified: TypeConstructorDescription { + public var random: Buffer + public init(random: Buffer) { + self.random = random + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("dhConfigNotModified", [("random", ConstructorParameterDescription(self.random))]) + } + } + case dhConfig(Cons_dhConfig) + case dhConfigNotModified(Cons_dhConfigNotModified) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .dhConfig(let _data): + if boxed { + buffer.appendInt32(740433629) + } + serializeInt32(_data.g, buffer: buffer, boxed: false) + serializeBytes(_data.p, buffer: buffer, boxed: false) + serializeInt32(_data.version, buffer: buffer, boxed: false) + serializeBytes(_data.random, buffer: buffer, boxed: false) + break + case .dhConfigNotModified(let _data): + if boxed { + buffer.appendInt32(-1058912715) + } + serializeBytes(_data.random, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .dhConfig(let _data): + return ("dhConfig", [("g", ConstructorParameterDescription(_data.g)), ("p", ConstructorParameterDescription(_data.p)), ("version", ConstructorParameterDescription(_data.version)), ("random", ConstructorParameterDescription(_data.random))]) + case .dhConfigNotModified(let _data): + return ("dhConfigNotModified", [("random", ConstructorParameterDescription(_data.random))]) + } + } + + public static func parse_dhConfig(_ reader: BufferReader) -> DhConfig? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.DhConfig.dhConfig(Cons_dhConfig(g: _1!, p: _2!, version: _3!, random: _4!)) + } + else { + return nil + } + } + public static func parse_dhConfigNotModified(_ reader: BufferReader) -> DhConfig? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.messages.DhConfig.dhConfigNotModified(Cons_dhConfigNotModified(random: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum DialogFilters: TypeConstructorDescription { + public class Cons_dialogFilters: TypeConstructorDescription { + public var flags: Int32 + public var filters: [Api.DialogFilter] + public init(flags: Int32, filters: [Api.DialogFilter]) { + self.flags = flags + self.filters = filters + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("dialogFilters", [("flags", ConstructorParameterDescription(self.flags)), ("filters", ConstructorParameterDescription(self.filters))]) + } + } + case dialogFilters(Cons_dialogFilters) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .dialogFilters(let _data): + if boxed { + buffer.appendInt32(718878489) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.filters.count)) + for item in _data.filters { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .dialogFilters(let _data): + return ("dialogFilters", [("flags", ConstructorParameterDescription(_data.flags)), ("filters", ConstructorParameterDescription(_data.filters))]) + } + } + + public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogFilter]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.DialogFilters.dialogFilters(Cons_dialogFilters(flags: _1!, filters: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum Dialogs: TypeConstructorDescription { + public class Cons_dialogs: TypeConstructorDescription { + public var dialogs: [Api.Dialog] public var messages: [Api.Message] public var chats: [Api.Chat] public var users: [Api.User] - public init(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + public init(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { self.dialogs = dialogs self.messages = messages self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedDialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("dialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - public class Cons_savedDialogsNotModified: TypeConstructorDescription { + public class Cons_dialogsNotModified: TypeConstructorDescription { public var count: Int32 public init(count: Int32) { self.count = count } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedDialogsNotModified", [("count", ConstructorParameterDescription(self.count))]) + return ("dialogsNotModified", [("count", ConstructorParameterDescription(self.count))]) } } - public class Cons_savedDialogsSlice: TypeConstructorDescription { + public class Cons_dialogsSlice: TypeConstructorDescription { public var count: Int32 - public var dialogs: [Api.SavedDialog] + public var dialogs: [Api.Dialog] public var messages: [Api.Message] public var chats: [Api.Chat] public var users: [Api.User] - public init(count: Int32, dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + public init(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { self.count = count self.dialogs = dialogs self.messages = messages @@ -129,18 +679,18 @@ public extension Api.messages { self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedDialogsSlice", [("count", ConstructorParameterDescription(self.count)), ("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("dialogsSlice", [("count", ConstructorParameterDescription(self.count)), ("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case savedDialogs(Cons_savedDialogs) - case savedDialogsNotModified(Cons_savedDialogsNotModified) - case savedDialogsSlice(Cons_savedDialogsSlice) + case dialogs(Cons_dialogs) + case dialogsNotModified(Cons_dialogsNotModified) + case dialogsSlice(Cons_dialogsSlice) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedDialogs(let _data): + case .dialogs(let _data): if boxed { - buffer.appendInt32(-130358751) + buffer.appendInt32(364538944) } buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.dialogs.count)) @@ -163,15 +713,15 @@ public extension Api.messages { item.serialize(buffer, true) } break - case .savedDialogsNotModified(let _data): + case .dialogsNotModified(let _data): if boxed { - buffer.appendInt32(-1071681560) + buffer.appendInt32(-253500010) } serializeInt32(_data.count, buffer: buffer, boxed: false) break - case .savedDialogsSlice(let _data): + case .dialogsSlice(let _data): if boxed { - buffer.appendInt32(1153080793) + buffer.appendInt32(1910543603) } serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -200,19 +750,19 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .savedDialogs(let _data): - return ("savedDialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .savedDialogsNotModified(let _data): - return ("savedDialogsNotModified", [("count", ConstructorParameterDescription(_data.count))]) - case .savedDialogsSlice(let _data): - return ("savedDialogsSlice", [("count", ConstructorParameterDescription(_data.count)), ("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .dialogs(let _data): + return ("dialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .dialogsNotModified(let _data): + return ("dialogsNotModified", [("count", ConstructorParameterDescription(_data.count))]) + case .dialogsSlice(let _data): + return ("dialogsSlice", [("count", ConstructorParameterDescription(_data.count)), ("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_savedDialogs(_ reader: BufferReader) -> SavedDialogs? { - var _1: [Api.SavedDialog]? + public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { + var _1: [Api.Dialog]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) } var _2: [Api.Message]? if let _ = reader.readInt32() { @@ -231,29 +781,29 @@ public extension Api.messages { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.messages.SavedDialogs.savedDialogs(Cons_savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)) + return Api.messages.Dialogs.dialogs(Cons_dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)) } else { return nil } } - public static func parse_savedDialogsNotModified(_ reader: BufferReader) -> SavedDialogs? { + public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { var _1: Int32? _1 = reader.readInt32() let _c1 = _1 != nil if _c1 { - return Api.messages.SavedDialogs.savedDialogsNotModified(Cons_savedDialogsNotModified(count: _1!)) + return Api.messages.Dialogs.dialogsNotModified(Cons_dialogsNotModified(count: _1!)) } else { return nil } } - public static func parse_savedDialogsSlice(_ reader: BufferReader) -> SavedDialogs? { + public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.SavedDialog]? + var _2: [Api.Dialog]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) } var _3: [Api.Message]? if let _ = reader.readInt32() { @@ -273,7 +823,7 @@ public extension Api.messages { let _c4 = _4 != nil let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.SavedDialogs.savedDialogsSlice(Cons_savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)) + return Api.messages.Dialogs.dialogsSlice(Cons_dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)) } else { return nil @@ -282,249 +832,54 @@ public extension Api.messages { } } public extension Api.messages { - enum SavedGifs: TypeConstructorDescription { - public class Cons_savedGifs: TypeConstructorDescription { - public var hash: Int64 - public var gifs: [Api.Document] - public init(hash: Int64, gifs: [Api.Document]) { - self.hash = hash - self.gifs = gifs - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedGifs", [("hash", ConstructorParameterDescription(self.hash)), ("gifs", ConstructorParameterDescription(self.gifs))]) - } - } - case savedGifs(Cons_savedGifs) - case savedGifsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedGifs(let _data): - if boxed { - buffer.appendInt32(-2069878259) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.gifs.count)) - for item in _data.gifs { - item.serialize(buffer, true) - } - break - case .savedGifsNotModified: - if boxed { - buffer.appendInt32(-402498398) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedGifs(let _data): - return ("savedGifs", [("hash", ConstructorParameterDescription(_data.hash)), ("gifs", ConstructorParameterDescription(_data.gifs))]) - case .savedGifsNotModified: - return ("savedGifsNotModified", []) - } - } - - public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedGifs.savedGifs(Cons_savedGifs(hash: _1!, gifs: _2!)) - } - else { - return nil - } - } - public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { - return Api.messages.SavedGifs.savedGifsNotModified - } - } -} -public extension Api.messages { - enum SavedReactionTags: TypeConstructorDescription { - public class Cons_savedReactionTags: TypeConstructorDescription { - public var tags: [Api.SavedReactionTag] - public var hash: Int64 - public init(tags: [Api.SavedReactionTag], hash: Int64) { - self.tags = tags - self.hash = hash - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedReactionTags", [("tags", ConstructorParameterDescription(self.tags)), ("hash", ConstructorParameterDescription(self.hash))]) - } - } - case savedReactionTags(Cons_savedReactionTags) - case savedReactionTagsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedReactionTags(let _data): - if boxed { - buffer.appendInt32(844731658) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.tags.count)) - for item in _data.tags { - item.serialize(buffer, true) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - break - case .savedReactionTagsNotModified: - if boxed { - buffer.appendInt32(-2003084817) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedReactionTags(let _data): - return ("savedReactionTags", [("tags", ConstructorParameterDescription(_data.tags)), ("hash", ConstructorParameterDescription(_data.hash))]) - case .savedReactionTagsNotModified: - return ("savedReactionTagsNotModified", []) - } - } - - public static func parse_savedReactionTags(_ reader: BufferReader) -> SavedReactionTags? { - var _1: [Api.SavedReactionTag]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedReactionTag.self) - } - var _2: Int64? - _2 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SavedReactionTags.savedReactionTags(Cons_savedReactionTags(tags: _1!, hash: _2!)) - } - else { - return nil - } - } - public static func parse_savedReactionTagsNotModified(_ reader: BufferReader) -> SavedReactionTags? { - return Api.messages.SavedReactionTags.savedReactionTagsNotModified - } - } -} -public extension Api.messages { - enum SearchCounter: TypeConstructorDescription { - public class Cons_searchCounter: TypeConstructorDescription { + enum DiscussionMessage: TypeConstructorDescription { + public class Cons_discussionMessage: TypeConstructorDescription { public var flags: Int32 - public var filter: Api.MessagesFilter - public var count: Int32 - public init(flags: Int32, filter: Api.MessagesFilter, count: Int32) { - self.flags = flags - self.filter = filter - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchCounter", [("flags", ConstructorParameterDescription(self.flags)), ("filter", ConstructorParameterDescription(self.filter)), ("count", ConstructorParameterDescription(self.count))]) - } - } - case searchCounter(Cons_searchCounter) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .searchCounter(let _data): - if boxed { - buffer.appendInt32(-398136321) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.filter.serialize(buffer, true) - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .searchCounter(let _data): - return ("searchCounter", [("flags", ConstructorParameterDescription(_data.flags)), ("filter", ConstructorParameterDescription(_data.filter)), ("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.MessagesFilter? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter - } - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.SearchCounter.searchCounter(Cons_searchCounter(flags: _1!, filter: _2!, count: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum SearchResultsCalendar: TypeConstructorDescription { - public class Cons_searchResultsCalendar: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var minDate: Int32 - public var minMsgId: Int32 - public var offsetIdOffset: Int32? - public var periods: [Api.SearchResultsCalendarPeriod] public var messages: [Api.Message] + public var maxId: Int32? + public var readInboxMaxId: Int32? + public var readOutboxMaxId: Int32? + public var unreadCount: Int32 public var chats: [Api.Chat] public var users: [Api.User] - public init(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + public init(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) { self.flags = flags - self.count = count - self.minDate = minDate - self.minMsgId = minMsgId - self.offsetIdOffset = offsetIdOffset - self.periods = periods self.messages = messages + self.maxId = maxId + self.readInboxMaxId = readInboxMaxId + self.readOutboxMaxId = readOutboxMaxId + self.unreadCount = unreadCount self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchResultsCalendar", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("minDate", ConstructorParameterDescription(self.minDate)), ("minMsgId", ConstructorParameterDescription(self.minMsgId)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("periods", ConstructorParameterDescription(self.periods)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("discussionMessage", [("flags", ConstructorParameterDescription(self.flags)), ("messages", ConstructorParameterDescription(self.messages)), ("maxId", ConstructorParameterDescription(self.maxId)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case searchResultsCalendar(Cons_searchResultsCalendar) + case discussionMessage(Cons_discussionMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultsCalendar(let _data): + case .discussionMessage(let _data): if boxed { - buffer.appendInt32(343859772) + buffer.appendInt32(-1506535550) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - serializeInt32(_data.minDate, buffer: buffer, boxed: false) - serializeInt32(_data.minMsgId, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.periods.count)) - for item in _data.periods { - item.serialize(buffer, true) - } buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.messages.count)) for item in _data.messages { item.serialize(buffer, true) } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.maxId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.readInboxMaxId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.readOutboxMaxId!, buffer: buffer, boxed: false) + } + serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -541,51 +896,201 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .searchResultsCalendar(let _data): - return ("searchResultsCalendar", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("minDate", ConstructorParameterDescription(_data.minDate)), ("minMsgId", ConstructorParameterDescription(_data.minMsgId)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("periods", ConstructorParameterDescription(_data.periods)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .discussionMessage(let _data): + return ("discussionMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("messages", ConstructorParameterDescription(_data.messages)), ("maxId", ConstructorParameterDescription(_data.maxId)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? { + public static func parse_discussionMessage(_ reader: BufferReader) -> DiscussionMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } var _3: Int32? - _3 = reader.readInt32() + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } var _4: Int32? - _4 = reader.readInt32() + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = reader.readInt32() } - var _6: [Api.SearchResultsCalendarPeriod]? + var _6: Int32? + _6 = reader.readInt32() + var _7: [Api.Chat]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - var _7: [Api.Message]? + var _8: [Api.User]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _8: [Api.Chat]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.messages.DiscussionMessage.discussionMessage(Cons_discussionMessage(flags: _1!, messages: _2!, maxId: _3, readInboxMaxId: _4, readOutboxMaxId: _5, unreadCount: _6!, chats: _7!, users: _8!)) } - var _9: [Api.User]? + else { + return nil + } + } + } +} +public extension Api.messages { + enum EmojiGameInfo: TypeConstructorDescription { + public class Cons_emojiGameDiceInfo: TypeConstructorDescription { + public var flags: Int32 + public var gameHash: String + public var prevStake: Int64 + public var currentStreak: Int32 + public var params: [Int32] + public var playsLeft: Int32? + public init(flags: Int32, gameHash: String, prevStake: Int64, currentStreak: Int32, params: [Int32], playsLeft: Int32?) { + self.flags = flags + self.gameHash = gameHash + self.prevStake = prevStake + self.currentStreak = currentStreak + self.params = params + self.playsLeft = playsLeft + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("emojiGameDiceInfo", [("flags", ConstructorParameterDescription(self.flags)), ("gameHash", ConstructorParameterDescription(self.gameHash)), ("prevStake", ConstructorParameterDescription(self.prevStake)), ("currentStreak", ConstructorParameterDescription(self.currentStreak)), ("params", ConstructorParameterDescription(self.params)), ("playsLeft", ConstructorParameterDescription(self.playsLeft))]) + } + } + case emojiGameDiceInfo(Cons_emojiGameDiceInfo) + case emojiGameUnavailable + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emojiGameDiceInfo(let _data): + if boxed { + buffer.appendInt32(1155883043) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.gameHash, buffer: buffer, boxed: false) + serializeInt64(_data.prevStake, buffer: buffer, boxed: false) + serializeInt32(_data.currentStreak, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.params.count)) + for item in _data.params { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.playsLeft!, buffer: buffer, boxed: false) + } + break + case .emojiGameUnavailable: + if boxed { + buffer.appendInt32(1508266805) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .emojiGameDiceInfo(let _data): + return ("emojiGameDiceInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("gameHash", ConstructorParameterDescription(_data.gameHash)), ("prevStake", ConstructorParameterDescription(_data.prevStake)), ("currentStreak", ConstructorParameterDescription(_data.currentStreak)), ("params", ConstructorParameterDescription(_data.params)), ("playsLeft", ConstructorParameterDescription(_data.playsLeft))]) + case .emojiGameUnavailable: + return ("emojiGameUnavailable", []) + } + } + + public static func parse_emojiGameDiceInfo(_ reader: BufferReader) -> EmojiGameInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: [Int32]? if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.messages.SearchResultsCalendar.searchResultsCalendar(Cons_searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!)) + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.messages.EmojiGameInfo.emojiGameDiceInfo(Cons_emojiGameDiceInfo(flags: _1!, gameHash: _2!, prevStake: _3!, currentStreak: _4!, params: _5!, playsLeft: _6)) + } + else { + return nil + } + } + public static func parse_emojiGameUnavailable(_ reader: BufferReader) -> EmojiGameInfo? { + return Api.messages.EmojiGameInfo.emojiGameUnavailable + } + } +} +public extension Api.messages { + enum EmojiGameOutcome: TypeConstructorDescription { + public class Cons_emojiGameOutcome: TypeConstructorDescription { + public var seed: Buffer + public var stakeTonAmount: Int64 + public var tonAmount: Int64 + public init(seed: Buffer, stakeTonAmount: Int64, tonAmount: Int64) { + self.seed = seed + self.stakeTonAmount = stakeTonAmount + self.tonAmount = tonAmount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("emojiGameOutcome", [("seed", ConstructorParameterDescription(self.seed)), ("stakeTonAmount", ConstructorParameterDescription(self.stakeTonAmount)), ("tonAmount", ConstructorParameterDescription(self.tonAmount))]) + } + } + case emojiGameOutcome(Cons_emojiGameOutcome) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .emojiGameOutcome(let _data): + if boxed { + buffer.appendInt32(-634726841) + } + serializeBytes(_data.seed, buffer: buffer, boxed: false) + serializeInt64(_data.stakeTonAmount, buffer: buffer, boxed: false) + serializeInt64(_data.tonAmount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .emojiGameOutcome(let _data): + return ("emojiGameOutcome", [("seed", ConstructorParameterDescription(_data.seed)), ("stakeTonAmount", ConstructorParameterDescription(_data.stakeTonAmount)), ("tonAmount", ConstructorParameterDescription(_data.tonAmount))]) + } + } + + public static func parse_emojiGameOutcome(_ reader: BufferReader) -> EmojiGameOutcome? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.EmojiGameOutcome.emojiGameOutcome(Cons_emojiGameOutcome(seed: _1!, stakeTonAmount: _2!, tonAmount: _3!)) } else { return nil @@ -594,192 +1099,208 @@ public extension Api.messages { } } public extension Api.messages { - enum SearchResultsPositions: TypeConstructorDescription { - public class Cons_searchResultsPositions: TypeConstructorDescription { - public var count: Int32 - public var positions: [Api.SearchResultsPosition] - public init(count: Int32, positions: [Api.SearchResultsPosition]) { - self.count = count - self.positions = positions + enum EmojiGroups: TypeConstructorDescription { + public class Cons_emojiGroups: TypeConstructorDescription { + public var hash: Int32 + public var groups: [Api.EmojiGroup] + public init(hash: Int32, groups: [Api.EmojiGroup]) { + self.hash = hash + self.groups = groups } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("searchResultsPositions", [("count", ConstructorParameterDescription(self.count)), ("positions", ConstructorParameterDescription(self.positions))]) + return ("emojiGroups", [("hash", ConstructorParameterDescription(self.hash)), ("groups", ConstructorParameterDescription(self.groups))]) } } - case searchResultsPositions(Cons_searchResultsPositions) + case emojiGroups(Cons_emojiGroups) + case emojiGroupsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .searchResultsPositions(let _data): + case .emojiGroups(let _data): if boxed { - buffer.appendInt32(1404185519) + buffer.appendInt32(-2011186869) } - serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.positions.count)) - for item in _data.positions { + buffer.appendInt32(Int32(_data.groups.count)) + for item in _data.groups { item.serialize(buffer, true) } break + case .emojiGroupsNotModified: + if boxed { + buffer.appendInt32(1874111879) + } + break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .searchResultsPositions(let _data): - return ("searchResultsPositions", [("count", ConstructorParameterDescription(_data.count)), ("positions", ConstructorParameterDescription(_data.positions))]) + case .emojiGroups(let _data): + return ("emojiGroups", [("hash", ConstructorParameterDescription(_data.hash)), ("groups", ConstructorParameterDescription(_data.groups))]) + case .emojiGroupsNotModified: + return ("emojiGroupsNotModified", []) } } - public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? { + public static func parse_emojiGroups(_ reader: BufferReader) -> EmojiGroups? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.SearchResultsPosition]? + var _2: [Api.EmojiGroup]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiGroup.self) } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.messages.SearchResultsPositions.searchResultsPositions(Cons_searchResultsPositions(count: _1!, positions: _2!)) + return Api.messages.EmojiGroups.emojiGroups(Cons_emojiGroups(hash: _1!, groups: _2!)) } else { return nil } } + public static func parse_emojiGroupsNotModified(_ reader: BufferReader) -> EmojiGroups? { + return Api.messages.EmojiGroups.emojiGroupsNotModified + } } } public extension Api.messages { - enum SentEncryptedMessage: TypeConstructorDescription { - public class Cons_sentEncryptedFile: TypeConstructorDescription { - public var date: Int32 - public var file: Api.EncryptedFile - public init(date: Int32, file: Api.EncryptedFile) { - self.date = date - self.file = file - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentEncryptedFile", [("date", ConstructorParameterDescription(self.date)), ("file", ConstructorParameterDescription(self.file))]) - } - } - public class Cons_sentEncryptedMessage: TypeConstructorDescription { - public var date: Int32 - public init(date: Int32) { - self.date = date - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sentEncryptedMessage", [("date", ConstructorParameterDescription(self.date))]) - } - } - case sentEncryptedFile(Cons_sentEncryptedFile) - case sentEncryptedMessage(Cons_sentEncryptedMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .sentEncryptedFile(let _data): - if boxed { - buffer.appendInt32(-1802240206) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - _data.file.serialize(buffer, true) - break - case .sentEncryptedMessage(let _data): - if boxed { - buffer.appendInt32(1443858741) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .sentEncryptedFile(let _data): - return ("sentEncryptedFile", [("date", ConstructorParameterDescription(_data.date)), ("file", ConstructorParameterDescription(_data.file))]) - case .sentEncryptedMessage(let _data): - return ("sentEncryptedMessage", [("date", ConstructorParameterDescription(_data.date))]) - } - } - - public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.EncryptedFile? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.SentEncryptedMessage.sentEncryptedFile(Cons_sentEncryptedFile(date: _1!, file: _2!)) - } - else { - return nil - } - } - public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.SentEncryptedMessage.sentEncryptedMessage(Cons_sentEncryptedMessage(date: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum SponsoredMessages: TypeConstructorDescription { - public class Cons_sponsoredMessages: TypeConstructorDescription { - public var flags: Int32 - public var postsBetween: Int32? - public var startDelay: Int32? - public var betweenDelay: Int32? - public var messages: [Api.SponsoredMessage] - public var chats: [Api.Chat] + enum ExportedChatInvite: TypeConstructorDescription { + public class Cons_exportedChatInvite: TypeConstructorDescription { + public var invite: Api.ExportedChatInvite public var users: [Api.User] - public init(flags: Int32, postsBetween: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.postsBetween = postsBetween - self.startDelay = startDelay - self.betweenDelay = betweenDelay - self.messages = messages - self.chats = chats + public init(invite: Api.ExportedChatInvite, users: [Api.User]) { + self.invite = invite self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("sponsoredMessages", [("flags", ConstructorParameterDescription(self.flags)), ("postsBetween", ConstructorParameterDescription(self.postsBetween)), ("startDelay", ConstructorParameterDescription(self.startDelay)), ("betweenDelay", ConstructorParameterDescription(self.betweenDelay)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("exportedChatInvite", [("invite", ConstructorParameterDescription(self.invite)), ("users", ConstructorParameterDescription(self.users))]) } } - case sponsoredMessages(Cons_sponsoredMessages) - case sponsoredMessagesEmpty + public class Cons_exportedChatInviteReplaced: TypeConstructorDescription { + public var invite: Api.ExportedChatInvite + public var newInvite: Api.ExportedChatInvite + public var users: [Api.User] + public init(invite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite, users: [Api.User]) { + self.invite = invite + self.newInvite = newInvite + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedChatInviteReplaced", [("invite", ConstructorParameterDescription(self.invite)), ("newInvite", ConstructorParameterDescription(self.newInvite)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case exportedChatInvite(Cons_exportedChatInvite) + case exportedChatInviteReplaced(Cons_exportedChatInviteReplaced) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessages(let _data): + case .exportedChatInvite(let _data): if boxed { - buffer.appendInt32(-2464403) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.postsBetween!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.startDelay!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.betweenDelay!, buffer: buffer, boxed: false) + buffer.appendInt32(410107472) } + _data.invite.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { item.serialize(buffer, true) } + break + case .exportedChatInviteReplaced(let _data): + if boxed { + buffer.appendInt32(572915951) + } + _data.invite.serialize(buffer, true) + _data.newInvite.serialize(buffer, true) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedChatInvite(let _data): + return ("exportedChatInvite", [("invite", ConstructorParameterDescription(_data.invite)), ("users", ConstructorParameterDescription(_data.users))]) + case .exportedChatInviteReplaced(let _data): + return ("exportedChatInviteReplaced", [("invite", ConstructorParameterDescription(_data.invite)), ("newInvite", ConstructorParameterDescription(_data.newInvite)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_exportedChatInvite(_ reader: BufferReader) -> ExportedChatInvite? { + var _1: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.ExportedChatInvite.exportedChatInvite(Cons_exportedChatInvite(invite: _1!, users: _2!)) + } + else { + return nil + } + } + public static func parse_exportedChatInviteReplaced(_ reader: BufferReader) -> ExportedChatInvite? { + var _1: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + var _2: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ExportedChatInvite.exportedChatInviteReplaced(Cons_exportedChatInviteReplaced(invite: _1!, newInvite: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum ExportedChatInvites: TypeConstructorDescription { + public class Cons_exportedChatInvites: TypeConstructorDescription { + public var count: Int32 + public var invites: [Api.ExportedChatInvite] + public var users: [Api.User] + public init(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User]) { + self.count = count + self.invites = invites + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedChatInvites", [("count", ConstructorParameterDescription(self.count)), ("invites", ConstructorParameterDescription(self.invites)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case exportedChatInvites(Cons_exportedChatInvites) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedChatInvites(let _data): + if boxed { + buffer.appendInt32(-1111085620) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.invites.count)) + for item in _data.invites { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -788,115 +1309,78 @@ public extension Api.messages { item.serialize(buffer, true) } break - case .sponsoredMessagesEmpty: - if boxed { - buffer.appendInt32(406407439) - } - break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .sponsoredMessages(let _data): - return ("sponsoredMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("postsBetween", ConstructorParameterDescription(_data.postsBetween)), ("startDelay", ConstructorParameterDescription(_data.startDelay)), ("betweenDelay", ConstructorParameterDescription(_data.betweenDelay)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .sponsoredMessagesEmpty: - return ("sponsoredMessagesEmpty", []) + case .exportedChatInvites(let _data): + return ("exportedChatInvites", [("count", ConstructorParameterDescription(_data.count)), ("invites", ConstructorParameterDescription(_data.invites)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { + public static func parse_exportedChatInvites(_ reader: BufferReader) -> ExportedChatInvites? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = reader.readInt32() - } - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt32() - } - var _5: [Api.SponsoredMessage]? + var _2: [Api.ExportedChatInvite]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedChatInvite.self) } - var _6: [Api.Chat]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.messages.SponsoredMessages.sponsoredMessages(Cons_sponsoredMessages(flags: _1!, postsBetween: _2, startDelay: _3, betweenDelay: _4, messages: _5!, chats: _6!, users: _7!)) + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.ExportedChatInvites.exportedChatInvites(Cons_exportedChatInvites(count: _1!, invites: _2!, users: _3!)) } else { return nil } } - public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { - return Api.messages.SponsoredMessages.sponsoredMessagesEmpty - } } } public extension Api.messages { - enum StickerSet: TypeConstructorDescription { - public class Cons_stickerSet: TypeConstructorDescription { - public var set: Api.StickerSet + enum FavedStickers: TypeConstructorDescription { + public class Cons_favedStickers: TypeConstructorDescription { + public var hash: Int64 public var packs: [Api.StickerPack] - public var keywords: [Api.StickerKeyword] - public var documents: [Api.Document] - public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) { - self.set = set + public var stickers: [Api.Document] + public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document]) { + self.hash = hash self.packs = packs - self.keywords = keywords - self.documents = documents + self.stickers = stickers } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSet", [("set", ConstructorParameterDescription(self.set)), ("packs", ConstructorParameterDescription(self.packs)), ("keywords", ConstructorParameterDescription(self.keywords)), ("documents", ConstructorParameterDescription(self.documents))]) + return ("favedStickers", [("hash", ConstructorParameterDescription(self.hash)), ("packs", ConstructorParameterDescription(self.packs)), ("stickers", ConstructorParameterDescription(self.stickers))]) } } - case stickerSet(Cons_stickerSet) - case stickerSetNotModified + case favedStickers(Cons_favedStickers) + case favedStickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSet(let _data): + case .favedStickers(let _data): if boxed { - buffer.appendInt32(1846886166) + buffer.appendInt32(750063767) } - _data.set.serialize(buffer, true) + serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.packs.count)) for item in _data.packs { item.serialize(buffer, true) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.keywords.count)) - for item in _data.keywords { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { + buffer.appendInt32(Int32(_data.stickers.count)) + for item in _data.stickers { item.serialize(buffer, true) } break - case .stickerSetNotModified: + case .favedStickersNotModified: if boxed { - buffer.appendInt32(-738646805) + buffer.appendInt32(-1634752813) } break } @@ -904,75 +1388,284 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .stickerSet(let _data): - return ("stickerSet", [("set", ConstructorParameterDescription(_data.set)), ("packs", ConstructorParameterDescription(_data.packs)), ("keywords", ConstructorParameterDescription(_data.keywords)), ("documents", ConstructorParameterDescription(_data.documents))]) - case .stickerSetNotModified: - return ("stickerSetNotModified", []) + case .favedStickers(let _data): + return ("favedStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("packs", ConstructorParameterDescription(_data.packs)), ("stickers", ConstructorParameterDescription(_data.stickers))]) + case .favedStickersNotModified: + return ("favedStickersNotModified", []) } } - public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { - var _1: Api.StickerSet? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StickerSet - } + public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? { + var _1: Int64? + _1 = reader.readInt64() var _2: [Api.StickerPack]? if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) } - var _3: [Api.StickerKeyword]? + var _3: [Api.Document]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) - } - var _4: [Api.Document]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.StickerSet.stickerSet(Cons_stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!)) + if _c1 && _c2 && _c3 { + return Api.messages.FavedStickers.favedStickers(Cons_favedStickers(hash: _1!, packs: _2!, stickers: _3!)) } else { return nil } } - public static func parse_stickerSetNotModified(_ reader: BufferReader) -> StickerSet? { - return Api.messages.StickerSet.stickerSetNotModified + public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? { + return Api.messages.FavedStickers.favedStickersNotModified } } } public extension Api.messages { - enum StickerSetInstallResult: TypeConstructorDescription { - public class Cons_stickerSetInstallResultArchive: TypeConstructorDescription { + enum FeaturedStickers: TypeConstructorDescription { + public class Cons_featuredStickers: TypeConstructorDescription { + public var flags: Int32 + public var hash: Int64 + public var count: Int32 public var sets: [Api.StickerSetCovered] - public init(sets: [Api.StickerSetCovered]) { + public var unread: [Int64] + public init(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64]) { + self.flags = flags + self.hash = hash + self.count = count self.sets = sets + self.unread = unread } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickerSetInstallResultArchive", [("sets", ConstructorParameterDescription(self.sets))]) + return ("featuredStickers", [("flags", ConstructorParameterDescription(self.flags)), ("hash", ConstructorParameterDescription(self.hash)), ("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets)), ("unread", ConstructorParameterDescription(self.unread))]) } } - case stickerSetInstallResultArchive(Cons_stickerSetInstallResultArchive) - case stickerSetInstallResultSuccess + public class Cons_featuredStickersNotModified: TypeConstructorDescription { + public var count: Int32 + public init(count: Int32) { + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("featuredStickersNotModified", [("count", ConstructorParameterDescription(self.count))]) + } + } + case featuredStickers(Cons_featuredStickers) + case featuredStickersNotModified(Cons_featuredStickersNotModified) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickerSetInstallResultArchive(let _data): + case .featuredStickers(let _data): if boxed { - buffer.appendInt32(904138920) + buffer.appendInt32(-1103615738) } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.hash, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sets.count)) + for item in _data.sets { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.unread.count)) + for item in _data.unread { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .featuredStickersNotModified(let _data): + if boxed { + buffer.appendInt32(-958657434) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .featuredStickers(let _data): + return ("featuredStickers", [("flags", ConstructorParameterDescription(_data.flags)), ("hash", ConstructorParameterDescription(_data.hash)), ("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets)), ("unread", ConstructorParameterDescription(_data.unread))]) + case .featuredStickersNotModified(let _data): + return ("featuredStickersNotModified", [("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + var _5: [Int64]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.FeaturedStickers.featuredStickers(Cons_featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)) + } + else { + return nil + } + } + public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.FeaturedStickers.featuredStickersNotModified(Cons_featuredStickersNotModified(count: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum ForumTopics: TypeConstructorDescription { + public class Cons_forumTopics: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var topics: [Api.ForumTopic] + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public var pts: Int32 + public init(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32) { + self.flags = flags + self.count = count + self.topics = topics + self.messages = messages + self.chats = chats + self.users = users + self.pts = pts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("forumTopics", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("topics", ConstructorParameterDescription(self.topics)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("pts", ConstructorParameterDescription(self.pts))]) + } + } + case forumTopics(Cons_forumTopics) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .forumTopics(let _data): + if boxed { + buffer.appendInt32(913709011) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topics.count)) + for item in _data.topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .forumTopics(let _data): + return ("forumTopics", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("topics", ConstructorParameterDescription(_data.topics)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("pts", ConstructorParameterDescription(_data.pts))]) + } + } + + public static func parse_forumTopics(_ reader: BufferReader) -> ForumTopics? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.ForumTopic]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Int32? + _7 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.ForumTopics.forumTopics(Cons_forumTopics(flags: _1!, count: _2!, topics: _3!, messages: _4!, chats: _5!, users: _6!, pts: _7!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum FoundStickerSets: TypeConstructorDescription { + public class Cons_foundStickerSets: TypeConstructorDescription { + public var hash: Int64 + public var sets: [Api.StickerSetCovered] + public init(hash: Int64, sets: [Api.StickerSetCovered]) { + self.hash = hash + self.sets = sets + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("foundStickerSets", [("hash", ConstructorParameterDescription(self.hash)), ("sets", ConstructorParameterDescription(self.sets))]) + } + } + case foundStickerSets(Cons_foundStickerSets) + case foundStickerSetsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .foundStickerSets(let _data): + if boxed { + buffer.appendInt32(-1963942446) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.sets.count)) for item in _data.sets { item.serialize(buffer, true) } break - case .stickerSetInstallResultSuccess: + case .foundStickerSetsNotModified: if boxed { - buffer.appendInt32(946083368) + buffer.appendInt32(223655517) } break } @@ -980,52 +1673,74 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .stickerSetInstallResultArchive(let _data): - return ("stickerSetInstallResultArchive", [("sets", ConstructorParameterDescription(_data.sets))]) - case .stickerSetInstallResultSuccess: - return ("stickerSetInstallResultSuccess", []) + case .foundStickerSets(let _data): + return ("foundStickerSets", [("hash", ConstructorParameterDescription(_data.hash)), ("sets", ConstructorParameterDescription(_data.sets))]) + case .foundStickerSetsNotModified: + return ("foundStickerSetsNotModified", []) } } - public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? { - var _1: [Api.StickerSetCovered]? + public static func parse_foundStickerSets(_ reader: BufferReader) -> FoundStickerSets? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerSetCovered]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil - if _c1 { - return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(Cons_stickerSetInstallResultArchive(sets: _1!)) + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.FoundStickerSets.foundStickerSets(Cons_foundStickerSets(hash: _1!, sets: _2!)) } else { return nil } } - public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? { - return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess + public static func parse_foundStickerSetsNotModified(_ reader: BufferReader) -> FoundStickerSets? { + return Api.messages.FoundStickerSets.foundStickerSetsNotModified } } } public extension Api.messages { - enum Stickers: TypeConstructorDescription { - public class Cons_stickers: TypeConstructorDescription { + enum FoundStickers: TypeConstructorDescription { + public class Cons_foundStickers: TypeConstructorDescription { + public var flags: Int32 + public var nextOffset: Int32? public var hash: Int64 public var stickers: [Api.Document] - public init(hash: Int64, stickers: [Api.Document]) { + public init(flags: Int32, nextOffset: Int32?, hash: Int64, stickers: [Api.Document]) { + self.flags = flags + self.nextOffset = nextOffset self.hash = hash self.stickers = stickers } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stickers", [("hash", ConstructorParameterDescription(self.hash)), ("stickers", ConstructorParameterDescription(self.stickers))]) + return ("foundStickers", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("hash", ConstructorParameterDescription(self.hash)), ("stickers", ConstructorParameterDescription(self.stickers))]) } } - case stickers(Cons_stickers) - case stickersNotModified + public class Cons_foundStickersNotModified: TypeConstructorDescription { + public var flags: Int32 + public var nextOffset: Int32? + public init(flags: Int32, nextOffset: Int32?) { + self.flags = flags + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("foundStickersNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case foundStickers(Cons_foundStickers) + case foundStickersNotModified(Cons_foundStickersNotModified) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .stickers(let _data): + case .foundStickers(let _data): if boxed { - buffer.appendInt32(816245886) + buffer.appendInt32(-2100698480) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.nextOffset!, buffer: buffer, boxed: false) } serializeInt64(_data.hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -1034,218 +1749,13 @@ public extension Api.messages { item.serialize(buffer, true) } break - case .stickersNotModified: + case .foundStickersNotModified(let _data): if boxed { - buffer.appendInt32(-244016606) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .stickers(let _data): - return ("stickers", [("hash", ConstructorParameterDescription(_data.hash)), ("stickers", ConstructorParameterDescription(_data.stickers))]) - case .stickersNotModified: - return ("stickersNotModified", []) - } - } - - public static func parse_stickers(_ reader: BufferReader) -> Stickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.messages.Stickers.stickers(Cons_stickers(hash: _1!, stickers: _2!)) - } - else { - return nil - } - } - public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { - return Api.messages.Stickers.stickersNotModified - } - } -} -public extension Api.messages { - enum TranscribedAudio: TypeConstructorDescription { - public class Cons_transcribedAudio: TypeConstructorDescription { - public var flags: Int32 - public var transcriptionId: Int64 - public var text: String - public var trialRemainsNum: Int32? - public var trialRemainsUntilDate: Int32? - public init(flags: Int32, transcriptionId: Int64, text: String, trialRemainsNum: Int32?, trialRemainsUntilDate: Int32?) { - self.flags = flags - self.transcriptionId = transcriptionId - self.text = text - self.trialRemainsNum = trialRemainsNum - self.trialRemainsUntilDate = trialRemainsUntilDate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("transcribedAudio", [("flags", ConstructorParameterDescription(self.flags)), ("transcriptionId", ConstructorParameterDescription(self.transcriptionId)), ("text", ConstructorParameterDescription(self.text)), ("trialRemainsNum", ConstructorParameterDescription(self.trialRemainsNum)), ("trialRemainsUntilDate", ConstructorParameterDescription(self.trialRemainsUntilDate))]) - } - } - case transcribedAudio(Cons_transcribedAudio) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .transcribedAudio(let _data): - if boxed { - buffer.appendInt32(-809903785) + buffer.appendInt32(1611711796) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.transcriptionId, buffer: buffer, boxed: false) - serializeString(_data.text, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.trialRemainsNum!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.trialRemainsUntilDate!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .transcribedAudio(let _data): - return ("transcribedAudio", [("flags", ConstructorParameterDescription(_data.flags)), ("transcriptionId", ConstructorParameterDescription(_data.transcriptionId)), ("text", ConstructorParameterDescription(_data.text)), ("trialRemainsNum", ConstructorParameterDescription(_data.trialRemainsNum)), ("trialRemainsUntilDate", ConstructorParameterDescription(_data.trialRemainsUntilDate))]) - } - } - - public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: String? - _3 = parseString(reader) - var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _4 = reader.readInt32() - } - var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _5 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.TranscribedAudio.transcribedAudio(Cons_transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum TranslatedText: TypeConstructorDescription { - public class Cons_translateResult: TypeConstructorDescription { - public var result: [Api.TextWithEntities] - public init(result: [Api.TextWithEntities]) { - self.result = result - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("translateResult", [("result", ConstructorParameterDescription(self.result))]) - } - } - case translateResult(Cons_translateResult) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .translateResult(let _data): - if boxed { - buffer.appendInt32(870003448) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.result.count)) - for item in _data.result { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .translateResult(let _data): - return ("translateResult", [("result", ConstructorParameterDescription(_data.result))]) - } - } - - public static func parse_translateResult(_ reader: BufferReader) -> TranslatedText? { - var _1: [Api.TextWithEntities]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TextWithEntities.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.messages.TranslatedText.translateResult(Cons_translateResult(result: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - enum VotesList: TypeConstructorDescription { - public class Cons_votesList: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var votes: [Api.MessagePeerVote] - public var chats: [Api.Chat] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, count: Int32, votes: [Api.MessagePeerVote], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { - self.flags = flags - self.count = count - self.votes = votes - self.chats = chats - self.users = users - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("votesList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("votes", ConstructorParameterDescription(self.votes)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - case votesList(Cons_votesList) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .votesList(let _data): - if boxed { - buffer.appendInt32(1218005070) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.votes.count)) - for item in _data.votes { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + serializeInt32(_data.nextOffset!, buffer: buffer, boxed: false) } break } @@ -1253,483 +1763,48 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .votesList(let _data): - return ("votesList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("votes", ConstructorParameterDescription(_data.votes)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + case .foundStickers(let _data): + return ("foundStickers", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("hash", ConstructorParameterDescription(_data.hash)), ("stickers", ConstructorParameterDescription(_data.stickers))]) + case .foundStickersNotModified(let _data): + return ("foundStickersNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) } } - public static func parse_votesList(_ reader: BufferReader) -> VotesList? { + public static func parse_foundStickers(_ reader: BufferReader) -> FoundStickers? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.MessagePeerVote]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerVote.self) + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() } - var _4: [Api.Chat]? + var _3: Int64? + _3 = reader.readInt64() + var _4: [Api.Document]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.messages.VotesList.votesList(Cons_votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6)) + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.FoundStickers.foundStickers(Cons_foundStickers(flags: _1!, nextOffset: _2, hash: _3!, stickers: _4!)) } else { return nil } } - } -} -public extension Api.messages { - enum WebPage: TypeConstructorDescription { - public class Cons_webPage: TypeConstructorDescription { - public var webpage: Api.WebPage - public var chats: [Api.Chat] - public var users: [Api.User] - public init(webpage: Api.WebPage, chats: [Api.Chat], users: [Api.User]) { - self.webpage = webpage - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPage", [("webpage", ConstructorParameterDescription(self.webpage)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case webPage(Cons_webPage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webPage(let _data): - if boxed { - buffer.appendInt32(-44166467) - } - _data.webpage.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webPage(let _data): - return ("webPage", [("webpage", ConstructorParameterDescription(_data.webpage)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_webPage(_ reader: BufferReader) -> WebPage? { - var _1: Api.WebPage? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.WebPage - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + public static func parse_foundStickersNotModified(_ reader: BufferReader) -> FoundStickers? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.WebPage.webPage(Cons_webPage(webpage: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.messages { - indirect enum WebPagePreview: TypeConstructorDescription { - public class Cons_webPagePreview: TypeConstructorDescription { - public var media: Api.MessageMedia - public var chats: [Api.Chat] - public var users: [Api.User] - public init(media: Api.MessageMedia, chats: [Api.Chat], users: [Api.User]) { - self.media = media - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webPagePreview", [("media", ConstructorParameterDescription(self.media)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case webPagePreview(Cons_webPagePreview) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .webPagePreview(let _data): - if boxed { - buffer.appendInt32(-1936029524) - } - _data.media.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .webPagePreview(let _data): - return ("webPagePreview", [("media", ConstructorParameterDescription(_data.media)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_webPagePreview(_ reader: BufferReader) -> WebPagePreview? { - var _1: Api.MessageMedia? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.messages.WebPagePreview.webPagePreview(Cons_webPagePreview(media: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum BankCardData: TypeConstructorDescription { - public class Cons_bankCardData: TypeConstructorDescription { - public var title: String - public var openUrls: [Api.BankCardOpenUrl] - public init(title: String, openUrls: [Api.BankCardOpenUrl]) { - self.title = title - self.openUrls = openUrls - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("bankCardData", [("title", ConstructorParameterDescription(self.title)), ("openUrls", ConstructorParameterDescription(self.openUrls))]) - } - } - case bankCardData(Cons_bankCardData) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .bankCardData(let _data): - if boxed { - buffer.appendInt32(1042605427) - } - serializeString(_data.title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.openUrls.count)) - for item in _data.openUrls { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .bankCardData(let _data): - return ("bankCardData", [("title", ConstructorParameterDescription(_data.title)), ("openUrls", ConstructorParameterDescription(_data.openUrls))]) - } - } - - public static func parse_bankCardData(_ reader: BufferReader) -> BankCardData? { - var _1: String? - _1 = parseString(reader) - var _2: [Api.BankCardOpenUrl]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BankCardOpenUrl.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.payments.BankCardData.bankCardData(Cons_bankCardData(title: _1!, openUrls: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum CheckCanSendGiftResult: TypeConstructorDescription { - public class Cons_checkCanSendGiftResultFail: TypeConstructorDescription { - public var reason: Api.TextWithEntities - public init(reason: Api.TextWithEntities) { - self.reason = reason - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("checkCanSendGiftResultFail", [("reason", ConstructorParameterDescription(self.reason))]) - } - } - case checkCanSendGiftResultFail(Cons_checkCanSendGiftResultFail) - case checkCanSendGiftResultOk - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .checkCanSendGiftResultFail(let _data): - if boxed { - buffer.appendInt32(-706379148) - } - _data.reason.serialize(buffer, true) - break - case .checkCanSendGiftResultOk: - if boxed { - buffer.appendInt32(927967149) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .checkCanSendGiftResultFail(let _data): - return ("checkCanSendGiftResultFail", [("reason", ConstructorParameterDescription(_data.reason))]) - case .checkCanSendGiftResultOk: - return ("checkCanSendGiftResultOk", []) - } - } - - public static func parse_checkCanSendGiftResultFail(_ reader: BufferReader) -> CheckCanSendGiftResult? { - var _1: Api.TextWithEntities? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultFail(Cons_checkCanSendGiftResultFail(reason: _1!)) - } - else { - return nil - } - } - public static func parse_checkCanSendGiftResultOk(_ reader: BufferReader) -> CheckCanSendGiftResult? { - return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultOk - } - } -} -public extension Api.payments { - enum CheckedGiftCode: TypeConstructorDescription { - public class Cons_checkedGiftCode: TypeConstructorDescription { - public var flags: Int32 - public var fromId: Api.Peer? - public var giveawayMsgId: Int32? - public var toId: Int64? - public var date: Int32 - public var days: Int32 - public var usedDate: Int32? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, fromId: Api.Peer?, giveawayMsgId: Int32?, toId: Int64?, date: Int32, days: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.fromId = fromId - self.giveawayMsgId = giveawayMsgId - self.toId = toId - self.date = date - self.days = days - self.usedDate = usedDate - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("checkedGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("giveawayMsgId", ConstructorParameterDescription(self.giveawayMsgId)), ("toId", ConstructorParameterDescription(self.toId)), ("date", ConstructorParameterDescription(self.date)), ("days", ConstructorParameterDescription(self.days)), ("usedDate", ConstructorParameterDescription(self.usedDate)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case checkedGiftCode(Cons_checkedGiftCode) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .checkedGiftCode(let _data): - if boxed { - buffer.appendInt32(-342343793) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.fromId!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt32(_data.giveawayMsgId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.toId!, buffer: buffer, boxed: false) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.days, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.usedDate!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .checkedGiftCode(let _data): - return ("checkedGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("giveawayMsgId", ConstructorParameterDescription(_data.giveawayMsgId)), ("toId", ConstructorParameterDescription(_data.toId)), ("date", ConstructorParameterDescription(_data.date)), ("days", ConstructorParameterDescription(_data.days)), ("usedDate", ConstructorParameterDescription(_data.usedDate)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_checkedGiftCode(_ reader: BufferReader) -> CheckedGiftCode? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.Peer? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Peer - } - } - var _3: Int32? - if Int(_1!) & Int(1 << 3) != 0 { - _3 = reader.readInt32() - } - var _4: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = reader.readInt64() - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = reader.readInt32() - } - var _8: [Api.Chat]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _9: [Api.User]? - if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.CheckedGiftCode.checkedGiftCode(Cons_checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, days: _6!, usedDate: _7, chats: _8!, users: _9!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum ConnectedStarRefBots: TypeConstructorDescription { - public class Cons_connectedStarRefBots: TypeConstructorDescription { - public var count: Int32 - public var connectedBots: [Api.ConnectedBotStarRef] - public var users: [Api.User] - public init(count: Int32, connectedBots: [Api.ConnectedBotStarRef], users: [Api.User]) { - self.count = count - self.connectedBots = connectedBots - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("connectedStarRefBots", [("count", ConstructorParameterDescription(self.count)), ("connectedBots", ConstructorParameterDescription(self.connectedBots)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case connectedStarRefBots(Cons_connectedStarRefBots) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .connectedStarRefBots(let _data): - if boxed { - buffer.appendInt32(-1730811363) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.connectedBots.count)) - for item in _data.connectedBots { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .connectedStarRefBots(let _data): - return ("connectedStarRefBots", [("count", ConstructorParameterDescription(_data.count)), ("connectedBots", ConstructorParameterDescription(_data.connectedBots)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_connectedStarRefBots(_ reader: BufferReader) -> ConnectedStarRefBots? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.ConnectedBotStarRef]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBotStarRef.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ConnectedStarRefBots.connectedStarRefBots(Cons_connectedStarRefBots(count: _1!, connectedBots: _2!, users: _3!)) + return Api.messages.FoundStickers.foundStickersNotModified(Cons_foundStickersNotModified(flags: _1!, nextOffset: _2)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index c4a2ad6d96..64c62ba496 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -1,40 +1,102 @@ -public extension Api.payments { - enum ExportedInvoice: TypeConstructorDescription { - public class Cons_exportedInvoice: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url +public extension Api.messages { + enum HighScores: TypeConstructorDescription { + public class Cons_highScores: TypeConstructorDescription { + public var scores: [Api.HighScore] + public var users: [Api.User] + public init(scores: [Api.HighScore], users: [Api.User]) { + self.scores = scores + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedInvoice", [("url", ConstructorParameterDescription(self.url))]) + return ("highScores", [("scores", ConstructorParameterDescription(self.scores)), ("users", ConstructorParameterDescription(self.users))]) } } - case exportedInvoice(Cons_exportedInvoice) + case highScores(Cons_highScores) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedInvoice(let _data): + case .highScores(let _data): if boxed { - buffer.appendInt32(-1362048039) + buffer.appendInt32(-1707344487) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.scores.count)) + for item in _data.scores { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } - serializeString(_data.url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedInvoice(let _data): - return ("exportedInvoice", [("url", ConstructorParameterDescription(_data.url))]) + case .highScores(let _data): + return ("highScores", [("scores", ConstructorParameterDescription(_data.scores)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? { - var _1: String? - _1 = parseString(reader) + public static func parse_highScores(_ reader: BufferReader) -> HighScores? { + var _1: [Api.HighScore]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.HighScore.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.HighScores.highScores(Cons_highScores(scores: _1!, users: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum HistoryImport: TypeConstructorDescription { + public class Cons_historyImport: TypeConstructorDescription { + public var id: Int64 + public init(id: Int64) { + self.id = id + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("historyImport", [("id", ConstructorParameterDescription(self.id))]) + } + } + case historyImport(Cons_historyImport) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .historyImport(let _data): + if boxed { + buffer.appendInt32(375566091) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .historyImport(let _data): + return ("historyImport", [("id", ConstructorParameterDescription(_data.id))]) + } + } + + public static func parse_historyImport(_ reader: BufferReader) -> HistoryImport? { + var _1: Int64? + _1 = reader.readInt64() let _c1 = _1 != nil if _c1 { - return Api.payments.ExportedInvoice.exportedInvoice(Cons_exportedInvoice(url: _1!)) + return Api.messages.HistoryImport.historyImport(Cons_historyImport(id: _1!)) } else { return nil @@ -42,83 +104,30 @@ public extension Api.payments { } } } -public extension Api.payments { - enum GiveawayInfo: TypeConstructorDescription { - public class Cons_giveawayInfo: TypeConstructorDescription { +public extension Api.messages { + enum HistoryImportParsed: TypeConstructorDescription { + public class Cons_historyImportParsed: TypeConstructorDescription { public var flags: Int32 - public var startDate: Int32 - public var joinedTooEarlyDate: Int32? - public var adminDisallowedChatId: Int64? - public var disallowedCountry: String? - public init(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) { + public var title: String? + public init(flags: Int32, title: String?) { self.flags = flags - self.startDate = startDate - self.joinedTooEarlyDate = joinedTooEarlyDate - self.adminDisallowedChatId = adminDisallowedChatId - self.disallowedCountry = disallowedCountry + self.title = title } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("giveawayInfo", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("joinedTooEarlyDate", ConstructorParameterDescription(self.joinedTooEarlyDate)), ("adminDisallowedChatId", ConstructorParameterDescription(self.adminDisallowedChatId)), ("disallowedCountry", ConstructorParameterDescription(self.disallowedCountry))]) + return ("historyImportParsed", [("flags", ConstructorParameterDescription(self.flags)), ("title", ConstructorParameterDescription(self.title))]) } } - public class Cons_giveawayInfoResults: TypeConstructorDescription { - public var flags: Int32 - public var startDate: Int32 - public var giftCodeSlug: String? - public var starsPrize: Int64? - public var finishDate: Int32 - public var winnersCount: Int32 - public var activatedCount: Int32? - public init(flags: Int32, startDate: Int32, giftCodeSlug: String?, starsPrize: Int64?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?) { - self.flags = flags - self.startDate = startDate - self.giftCodeSlug = giftCodeSlug - self.starsPrize = starsPrize - self.finishDate = finishDate - self.winnersCount = winnersCount - self.activatedCount = activatedCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("giveawayInfoResults", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("giftCodeSlug", ConstructorParameterDescription(self.giftCodeSlug)), ("starsPrize", ConstructorParameterDescription(self.starsPrize)), ("finishDate", ConstructorParameterDescription(self.finishDate)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("activatedCount", ConstructorParameterDescription(self.activatedCount))]) - } - } - case giveawayInfo(Cons_giveawayInfo) - case giveawayInfoResults(Cons_giveawayInfoResults) + case historyImportParsed(Cons_historyImportParsed) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .giveawayInfo(let _data): + case .historyImportParsed(let _data): if boxed { - buffer.appendInt32(1130879648) + buffer.appendInt32(1578088377) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.startDate, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.joinedTooEarlyDate!, buffer: buffer, boxed: false) - } if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.adminDisallowedChatId!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.disallowedCountry!, buffer: buffer, boxed: false) - } - break - case .giveawayInfoResults(let _data): - if boxed { - buffer.appendInt32(-512366993) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.startDate, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeString(_data.giftCodeSlug!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.starsPrize!, buffer: buffer, boxed: false) - } - serializeInt32(_data.finishDate, buffer: buffer, boxed: false) - serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt32(_data.activatedCount!, buffer: buffer, boxed: false) + serializeString(_data.title!, buffer: buffer, boxed: false) } break } @@ -126,72 +135,22 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .giveawayInfo(let _data): - return ("giveawayInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("joinedTooEarlyDate", ConstructorParameterDescription(_data.joinedTooEarlyDate)), ("adminDisallowedChatId", ConstructorParameterDescription(_data.adminDisallowedChatId)), ("disallowedCountry", ConstructorParameterDescription(_data.disallowedCountry))]) - case .giveawayInfoResults(let _data): - return ("giveawayInfoResults", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("giftCodeSlug", ConstructorParameterDescription(_data.giftCodeSlug)), ("starsPrize", ConstructorParameterDescription(_data.starsPrize)), ("finishDate", ConstructorParameterDescription(_data.finishDate)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("activatedCount", ConstructorParameterDescription(_data.activatedCount))]) + case .historyImportParsed(let _data): + return ("historyImportParsed", [("flags", ConstructorParameterDescription(_data.flags)), ("title", ConstructorParameterDescription(_data.title))]) } } - public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? { + public static func parse_historyImportParsed(_ reader: BufferReader) -> HistoryImportParsed? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = reader.readInt64() - } - var _5: String? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = parseString(reader) + var _2: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _2 = parseString(reader) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.GiveawayInfo.giveawayInfo(Cons_giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5)) - } - else { - return nil - } - } - public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 3) != 0 { - _3 = parseString(reader) - } - var _4: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _4 = reader.readInt64() - } - var _5: Int32? - _5 = reader.readInt32() - var _6: Int32? - _6 = reader.readInt32() - var _7: Int32? - if Int(_1!) & Int(1 << 2) != 0 { - _7 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.payments.GiveawayInfo.giveawayInfoResults(Cons_giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7)) + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + if _c1 && _c2 { + return Api.messages.HistoryImportParsed.historyImportParsed(Cons_historyImportParsed(flags: _1!, title: _2)) } else { return nil @@ -199,153 +158,230 @@ public extension Api.payments { } } } -public extension Api.payments { - enum PaymentForm: TypeConstructorDescription { - public class Cons_paymentForm: TypeConstructorDescription { - public var flags: Int32 - public var formId: Int64 - public var botId: Int64 - public var title: String - public var description: String - public var photo: Api.WebDocument? - public var invoice: Api.Invoice - public var providerId: Int64 - public var url: String - public var nativeProvider: String? - public var nativeParams: Api.DataJSON? - public var additionalMethods: [Api.PaymentFormMethod]? - public var savedInfo: Api.PaymentRequestedInfo? - public var savedCredentials: [Api.PaymentSavedCredentials]? +public extension Api.messages { + enum InactiveChats: TypeConstructorDescription { + public class Cons_inactiveChats: TypeConstructorDescription { + public var dates: [Int32] + public var chats: [Api.Chat] public var users: [Api.User] - public init(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) { - self.flags = flags - self.formId = formId - self.botId = botId - self.title = title - self.description = description - self.photo = photo - self.invoice = invoice - self.providerId = providerId - self.url = url - self.nativeProvider = nativeProvider - self.nativeParams = nativeParams - self.additionalMethods = additionalMethods - self.savedInfo = savedInfo - self.savedCredentials = savedCredentials + public init(dates: [Int32], chats: [Api.Chat], users: [Api.User]) { + self.dates = dates + self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentForm", [("flags", ConstructorParameterDescription(self.flags)), ("formId", ConstructorParameterDescription(self.formId)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("providerId", ConstructorParameterDescription(self.providerId)), ("url", ConstructorParameterDescription(self.url)), ("nativeProvider", ConstructorParameterDescription(self.nativeProvider)), ("nativeParams", ConstructorParameterDescription(self.nativeParams)), ("additionalMethods", ConstructorParameterDescription(self.additionalMethods)), ("savedInfo", ConstructorParameterDescription(self.savedInfo)), ("savedCredentials", ConstructorParameterDescription(self.savedCredentials)), ("users", ConstructorParameterDescription(self.users))]) + return ("inactiveChats", [("dates", ConstructorParameterDescription(self.dates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - public class Cons_paymentFormStarGift: TypeConstructorDescription { - public var formId: Int64 - public var invoice: Api.Invoice - public init(formId: Int64, invoice: Api.Invoice) { - self.formId = formId - self.invoice = invoice - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentFormStarGift", [("formId", ConstructorParameterDescription(self.formId)), ("invoice", ConstructorParameterDescription(self.invoice))]) - } - } - public class Cons_paymentFormStars: TypeConstructorDescription { - public var flags: Int32 - public var formId: Int64 - public var botId: Int64 - public var title: String - public var description: String - public var photo: Api.WebDocument? - public var invoice: Api.Invoice - public var users: [Api.User] - public init(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, users: [Api.User]) { - self.flags = flags - self.formId = formId - self.botId = botId - self.title = title - self.description = description - self.photo = photo - self.invoice = invoice - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentFormStars", [("flags", ConstructorParameterDescription(self.flags)), ("formId", ConstructorParameterDescription(self.formId)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case paymentForm(Cons_paymentForm) - case paymentFormStarGift(Cons_paymentFormStarGift) - case paymentFormStars(Cons_paymentFormStars) + case inactiveChats(Cons_inactiveChats) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .paymentForm(let _data): + case .inactiveChats(let _data): if boxed { - buffer.appendInt32(-1610250415) + buffer.appendInt32(-1456996667) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dates.count)) + for item in _data.dates { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inactiveChats(let _data): + return ("inactiveChats", [("dates", ConstructorParameterDescription(_data.dates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_inactiveChats(_ reader: BufferReader) -> InactiveChats? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.InactiveChats.inactiveChats(Cons_inactiveChats(dates: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + indirect enum InvitedUsers: TypeConstructorDescription { + public class Cons_invitedUsers: TypeConstructorDescription { + public var updates: Api.Updates + public var missingInvitees: [Api.MissingInvitee] + public init(updates: Api.Updates, missingInvitees: [Api.MissingInvitee]) { + self.updates = updates + self.missingInvitees = missingInvitees + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("invitedUsers", [("updates", ConstructorParameterDescription(self.updates)), ("missingInvitees", ConstructorParameterDescription(self.missingInvitees))]) + } + } + case invitedUsers(Cons_invitedUsers) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .invitedUsers(let _data): + if boxed { + buffer.appendInt32(2136862630) + } + _data.updates.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.missingInvitees.count)) + for item in _data.missingInvitees { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .invitedUsers(let _data): + return ("invitedUsers", [("updates", ConstructorParameterDescription(_data.updates)), ("missingInvitees", ConstructorParameterDescription(_data.missingInvitees))]) + } + } + + public static func parse_invitedUsers(_ reader: BufferReader) -> InvitedUsers? { + var _1: Api.Updates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Updates + } + var _2: [Api.MissingInvitee]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MissingInvitee.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.InvitedUsers.invitedUsers(Cons_invitedUsers(updates: _1!, missingInvitees: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum MessageEditData: TypeConstructorDescription { + public class Cons_messageEditData: TypeConstructorDescription { + public var flags: Int32 + public init(flags: Int32) { + self.flags = flags + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageEditData", [("flags", ConstructorParameterDescription(self.flags))]) + } + } + case messageEditData(Cons_messageEditData) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEditData(let _data): + if boxed { + buffer.appendInt32(649453030) } serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.formId, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.photo!.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageEditData(let _data): + return ("messageEditData", [("flags", ConstructorParameterDescription(_data.flags))]) + } + } + + public static func parse_messageEditData(_ reader: BufferReader) -> MessageEditData? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.MessageEditData.messageEditData(Cons_messageEditData(flags: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum MessageReactionsList: TypeConstructorDescription { + public class Cons_messageReactionsList: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var reactions: [Api.MessagePeerReaction] + public var chats: [Api.Chat] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, count: Int32, reactions: [Api.MessagePeerReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.count = count + self.reactions = reactions + self.chats = chats + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageReactionsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("reactions", ConstructorParameterDescription(self.reactions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case messageReactionsList(Cons_messageReactionsList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageReactionsList(let _data): + if boxed { + buffer.appendInt32(834488621) } - _data.invoice.serialize(buffer, true) - serializeInt64(_data.providerId, buffer: buffer, boxed: false) - serializeString(_data.url, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeString(_data.nativeProvider!, buffer: buffer, boxed: false) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.reactions.count)) + for item in _data.reactions { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 4) != 0 { - _data.nativeParams!.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 6) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.additionalMethods!.count)) - for item in _data.additionalMethods! { - item.serialize(buffer, true) - } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 0) != 0 { - _data.savedInfo!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.savedCredentials!.count)) - for item in _data.savedCredentials! { - item.serialize(buffer, true) - } - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .paymentFormStarGift(let _data): - if boxed { - buffer.appendInt32(-1272590367) - } - serializeInt64(_data.formId, buffer: buffer, boxed: false) - _data.invoice.serialize(buffer, true) - break - case .paymentFormStars(let _data): - if boxed { - buffer.appendInt32(2079764828) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt64(_data.formId, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 5) != 0 { - _data.photo!.serialize(buffer, true) - } - _data.invoice.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) } break } @@ -353,130 +389,338 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .paymentForm(let _data): - return ("paymentForm", [("flags", ConstructorParameterDescription(_data.flags)), ("formId", ConstructorParameterDescription(_data.formId)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("providerId", ConstructorParameterDescription(_data.providerId)), ("url", ConstructorParameterDescription(_data.url)), ("nativeProvider", ConstructorParameterDescription(_data.nativeProvider)), ("nativeParams", ConstructorParameterDescription(_data.nativeParams)), ("additionalMethods", ConstructorParameterDescription(_data.additionalMethods)), ("savedInfo", ConstructorParameterDescription(_data.savedInfo)), ("savedCredentials", ConstructorParameterDescription(_data.savedCredentials)), ("users", ConstructorParameterDescription(_data.users))]) - case .paymentFormStarGift(let _data): - return ("paymentFormStarGift", [("formId", ConstructorParameterDescription(_data.formId)), ("invoice", ConstructorParameterDescription(_data.invoice))]) - case .paymentFormStars(let _data): - return ("paymentFormStars", [("flags", ConstructorParameterDescription(_data.flags)), ("formId", ConstructorParameterDescription(_data.formId)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("users", ConstructorParameterDescription(_data.users))]) + case .messageReactionsList(let _data): + return ("messageReactionsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) } } - public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { + public static func parse_messageReactionsList(_ reader: BufferReader) -> MessageReactionsList? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } - } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _8: Int64? - _8 = reader.readInt64() - var _9: String? - _9 = parseString(reader) - var _10: String? - if Int(_1!) & Int(1 << 4) != 0 { - _10 = parseString(reader) - } - var _11: Api.DataJSON? - if Int(_1!) & Int(1 << 4) != 0 { - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.DataJSON - } - } - var _12: [Api.PaymentFormMethod]? - if Int(_1!) & Int(1 << 6) != 0 { - if let _ = reader.readInt32() { - _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) - } - } - var _13: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } - } - var _14: [Api.PaymentSavedCredentials]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) - } - } - var _15: [Api.User]? + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.MessagePeerReaction]? if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerReaction.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 4) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 6) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 0) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 1) == 0) || _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentForm.paymentForm(Cons_paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!)) + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.messages.MessageReactionsList.messageReactionsList(Cons_messageReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6)) } else { return nil } } - public static func parse_paymentFormStarGift(_ reader: BufferReader) -> PaymentForm? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Api.Invoice? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Invoice + } +} +public extension Api.messages { + enum MessageViews: TypeConstructorDescription { + public class Cons_messageViews: TypeConstructorDescription { + public var views: [Api.MessageViews] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(views: [Api.MessageViews], chats: [Api.Chat], users: [Api.User]) { + self.views = views + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageViews", [("views", ConstructorParameterDescription(self.views)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case messageViews(Cons_messageViews) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageViews(let _data): + if boxed { + buffer.appendInt32(-1228606141) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.views.count)) + for item in _data.views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageViews(let _data): + return ("messageViews", [("views", ConstructorParameterDescription(_data.views)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_messageViews(_ reader: BufferReader) -> MessageViews? { + var _1: [Api.MessageViews]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageViews.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.PaymentForm.paymentFormStarGift(Cons_paymentFormStarGift(formId: _1!, invoice: _2!)) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.MessageViews.messageViews(Cons_messageViews(views: _1!, chats: _2!, users: _3!)) } else { return nil } } - public static func parse_paymentFormStars(_ reader: BufferReader) -> PaymentForm? { + } +} +public extension Api.messages { + enum Messages: TypeConstructorDescription { + public class Cons_channelMessages: TypeConstructorDescription { + public var flags: Int32 + public var pts: Int32 + public var count: Int32 + public var offsetIdOffset: Int32? + public var messages: [Api.Message] + public var topics: [Api.ForumTopic] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, pts: Int32, count: Int32, offsetIdOffset: Int32?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.pts = pts + self.count = count + self.offsetIdOffset = offsetIdOffset + self.messages = messages + self.topics = topics + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("channelMessages", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("count", ConstructorParameterDescription(self.count)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_messages: TypeConstructorDescription { + public var messages: [Api.Message] + public var topics: [Api.ForumTopic] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { + self.messages = messages + self.topics = topics + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messages", [("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_messagesNotModified: TypeConstructorDescription { + public var count: Int32 + public init(count: Int32) { + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messagesNotModified", [("count", ConstructorParameterDescription(self.count))]) + } + } + public class Cons_messagesSlice: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var nextRate: Int32? + public var offsetIdOffset: Int32? + public var searchFlood: Api.SearchPostsFlood? + public var messages: [Api.Message] + public var topics: [Api.ForumTopic] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, count: Int32, nextRate: Int32?, offsetIdOffset: Int32?, searchFlood: Api.SearchPostsFlood?, messages: [Api.Message], topics: [Api.ForumTopic], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.count = count + self.nextRate = nextRate + self.offsetIdOffset = offsetIdOffset + self.searchFlood = searchFlood + self.messages = messages + self.topics = topics + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messagesSlice", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("nextRate", ConstructorParameterDescription(self.nextRate)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("searchFlood", ConstructorParameterDescription(self.searchFlood)), ("messages", ConstructorParameterDescription(self.messages)), ("topics", ConstructorParameterDescription(self.topics)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case channelMessages(Cons_channelMessages) + case messages(Cons_messages) + case messagesNotModified(Cons_messagesNotModified) + case messagesSlice(Cons_messagesSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .channelMessages(let _data): + if boxed { + buffer.appendInt32(-948520370) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topics.count)) + for item in _data.topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .messages(let _data): + if boxed { + buffer.appendInt32(494135274) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topics.count)) + for item in _data.topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .messagesNotModified(let _data): + if boxed { + buffer.appendInt32(1951620897) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + case .messagesSlice(let _data): + if boxed { + buffer.appendInt32(1595959062) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.nextRate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + _data.searchFlood!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topics.count)) + for item in _data.topics { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .channelMessages(let _data): + return ("channelMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("count", ConstructorParameterDescription(_data.count)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .messages(let _data): + return ("messages", [("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .messagesNotModified(let _data): + return ("messagesNotModified", [("count", ConstructorParameterDescription(_data.count))]) + case .messagesSlice(let _data): + return ("messagesSlice", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("nextRate", ConstructorParameterDescription(_data.nextRate)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("searchFlood", ConstructorParameterDescription(_data.searchFlood)), ("messages", ConstructorParameterDescription(_data.messages)), ("topics", ConstructorParameterDescription(_data.topics)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_channelMessages(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 5) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice + var _5: [Api.Message]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _6: [Api.ForumTopic]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) + } + var _7: [Api.Chat]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } var _8: [Api.User]? if let _ = reader.readInt32() { @@ -485,1204 +729,26 @@ public extension Api.payments { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 5) == 0) || _6 != nil + let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.payments.PaymentForm.paymentFormStars(Cons_paymentFormStars(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, users: _8!)) + return Api.messages.Messages.channelMessages(Cons_channelMessages(flags: _1!, pts: _2!, count: _3!, offsetIdOffset: _4, messages: _5!, topics: _6!, chats: _7!, users: _8!)) } else { return nil } } - } -} -public extension Api.payments { - enum PaymentReceipt: TypeConstructorDescription { - public class Cons_paymentReceipt: TypeConstructorDescription { - public var flags: Int32 - public var date: Int32 - public var botId: Int64 - public var providerId: Int64 - public var title: String - public var description: String - public var photo: Api.WebDocument? - public var invoice: Api.Invoice - public var info: Api.PaymentRequestedInfo? - public var shipping: Api.ShippingOption? - public var tipAmount: Int64? - public var currency: String - public var totalAmount: Int64 - public var credentialsTitle: String - public var users: [Api.User] - public init(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) { - self.flags = flags - self.date = date - self.botId = botId - self.providerId = providerId - self.title = title - self.description = description - self.photo = photo - self.invoice = invoice - self.info = info - self.shipping = shipping - self.tipAmount = tipAmount - self.currency = currency - self.totalAmount = totalAmount - self.credentialsTitle = credentialsTitle - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentReceipt", [("flags", ConstructorParameterDescription(self.flags)), ("date", ConstructorParameterDescription(self.date)), ("botId", ConstructorParameterDescription(self.botId)), ("providerId", ConstructorParameterDescription(self.providerId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("info", ConstructorParameterDescription(self.info)), ("shipping", ConstructorParameterDescription(self.shipping)), ("tipAmount", ConstructorParameterDescription(self.tipAmount)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("credentialsTitle", ConstructorParameterDescription(self.credentialsTitle)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_paymentReceiptStars: TypeConstructorDescription { - public var flags: Int32 - public var date: Int32 - public var botId: Int64 - public var title: String - public var description: String - public var photo: Api.WebDocument? - public var invoice: Api.Invoice - public var currency: String - public var totalAmount: Int64 - public var transactionId: String - public var users: [Api.User] - public init(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User]) { - self.flags = flags - self.date = date - self.botId = botId - self.title = title - self.description = description - self.photo = photo - self.invoice = invoice - self.currency = currency - self.totalAmount = totalAmount - self.transactionId = transactionId - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentReceiptStars", [("flags", ConstructorParameterDescription(self.flags)), ("date", ConstructorParameterDescription(self.date)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case paymentReceipt(Cons_paymentReceipt) - case paymentReceiptStars(Cons_paymentReceiptStars) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentReceipt(let _data): - if boxed { - buffer.appendInt32(1891958275) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeInt64(_data.providerId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - _data.invoice.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.info!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.shipping!.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt64(_data.tipAmount!, buffer: buffer, boxed: false) - } - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - serializeString(_data.credentialsTitle, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .paymentReceiptStars(let _data): - if boxed { - buffer.appendInt32(-625215430) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt64(_data.botId, buffer: buffer, boxed: false) - serializeString(_data.title, buffer: buffer, boxed: false) - serializeString(_data.description, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 2) != 0 { - _data.photo!.serialize(buffer, true) - } - _data.invoice.serialize(buffer, true) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) - serializeString(_data.transactionId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentReceipt(let _data): - return ("paymentReceipt", [("flags", ConstructorParameterDescription(_data.flags)), ("date", ConstructorParameterDescription(_data.date)), ("botId", ConstructorParameterDescription(_data.botId)), ("providerId", ConstructorParameterDescription(_data.providerId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("info", ConstructorParameterDescription(_data.info)), ("shipping", ConstructorParameterDescription(_data.shipping)), ("tipAmount", ConstructorParameterDescription(_data.tipAmount)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("credentialsTitle", ConstructorParameterDescription(_data.credentialsTitle)), ("users", ConstructorParameterDescription(_data.users))]) - case .paymentReceiptStars(let _data): - return ("paymentReceiptStars", [("flags", ConstructorParameterDescription(_data.flags)), ("date", ConstructorParameterDescription(_data.date)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WebDocument - } - } - var _8: Api.Invoice? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _9: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } - } - var _10: Api.ShippingOption? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption - } - } - var _11: Int64? - if Int(_1!) & Int(1 << 3) != 0 { - _11 = reader.readInt64() - } - var _12: String? - _12 = parseString(reader) - var _13: Int64? - _13 = reader.readInt64() - var _14: String? - _14 = parseString(reader) - var _15: [Api.User]? + public static func parse_messages(_ reader: BufferReader) -> Messages? { + var _1: [Api.Message]? if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentReceipt.paymentReceipt(Cons_paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)) - } - else { - return nil - } - } - public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 { - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.WebDocument - } - } - var _7: Api.Invoice? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _8: String? - _8 = parseString(reader) - var _9: Int64? - _9 = reader.readInt64() - var _10: String? - _10 = parseString(reader) - var _11: [Api.User]? + var _2: [Api.ForumTopic]? if let _ = reader.readInt32() { - _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.payments.PaymentReceipt.paymentReceiptStars(Cons_paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - indirect enum PaymentResult: TypeConstructorDescription { - public class Cons_paymentResult: TypeConstructorDescription { - public var updates: Api.Updates - public init(updates: Api.Updates) { - self.updates = updates - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentResult", [("updates", ConstructorParameterDescription(self.updates))]) - } - } - public class Cons_paymentVerificationNeeded: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("paymentVerificationNeeded", [("url", ConstructorParameterDescription(self.url))]) - } - } - case paymentResult(Cons_paymentResult) - case paymentVerificationNeeded(Cons_paymentVerificationNeeded) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentResult(let _data): - if boxed { - buffer.appendInt32(1314881805) - } - _data.updates.serialize(buffer, true) - break - case .paymentVerificationNeeded(let _data): - if boxed { - buffer.appendInt32(-666824391) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .paymentResult(let _data): - return ("paymentResult", [("updates", ConstructorParameterDescription(_data.updates))]) - case .paymentVerificationNeeded(let _data): - return ("paymentVerificationNeeded", [("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { - var _1: Api.Updates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Updates - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentResult(Cons_paymentResult(updates: _1!)) - } - else { - return nil - } - } - public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentVerificationNeeded(Cons_paymentVerificationNeeded(url: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum ResaleStarGifts: TypeConstructorDescription { - public class Cons_resaleStarGifts: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var gifts: [Api.StarGift] - public var nextOffset: String? - public var attributes: [Api.StarGiftAttribute]? - public var attributesHash: Int64? - public var chats: [Api.Chat] - public var counters: [Api.StarGiftAttributeCounter]? - public var users: [Api.User] - public init(flags: Int32, count: Int32, gifts: [Api.StarGift], nextOffset: String?, attributes: [Api.StarGiftAttribute]?, attributesHash: Int64?, chats: [Api.Chat], counters: [Api.StarGiftAttributeCounter]?, users: [Api.User]) { - self.flags = flags - self.count = count - self.gifts = gifts - self.nextOffset = nextOffset - self.attributes = attributes - self.attributesHash = attributesHash - self.chats = chats - self.counters = counters - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("resaleStarGifts", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("gifts", ConstructorParameterDescription(self.gifts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("attributes", ConstructorParameterDescription(self.attributes)), ("attributesHash", ConstructorParameterDescription(self.attributesHash)), ("chats", ConstructorParameterDescription(self.chats)), ("counters", ConstructorParameterDescription(self.counters)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case resaleStarGifts(Cons_resaleStarGifts) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .resaleStarGifts(let _data): - if boxed { - buffer.appendInt32(-1803939105) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.gifts.count)) - for item in _data.gifts { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes!.count)) - for item in _data.attributes! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt64(_data.attributesHash!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.counters!.count)) - for item in _data.counters! { - item.serialize(buffer, true) - } - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .resaleStarGifts(let _data): - return ("resaleStarGifts", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("attributes", ConstructorParameterDescription(_data.attributes)), ("attributesHash", ConstructorParameterDescription(_data.attributesHash)), ("chats", ConstructorParameterDescription(_data.chats)), ("counters", ConstructorParameterDescription(_data.counters)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_resaleStarGifts(_ reader: BufferReader) -> ResaleStarGifts? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StarGift]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: [Api.StarGiftAttribute]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) - } - } - var _6: Int64? - if Int(_1!) & Int(1 << 1) != 0 { - _6 = reader.readInt64() - } - var _7: [Api.Chat]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _8: [Api.StarGiftAttributeCounter]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttributeCounter.self) - } - } - var _9: [Api.User]? - if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.ResaleStarGifts.resaleStarGifts(Cons_resaleStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, attributes: _5, attributesHash: _6, chats: _7!, counters: _8, users: _9!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum SavedInfo: TypeConstructorDescription { - public class Cons_savedInfo: TypeConstructorDescription { - public var flags: Int32 - public var savedInfo: Api.PaymentRequestedInfo? - public init(flags: Int32, savedInfo: Api.PaymentRequestedInfo?) { - self.flags = flags - self.savedInfo = savedInfo - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("savedInfo", ConstructorParameterDescription(self.savedInfo))]) - } - } - case savedInfo(Cons_savedInfo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedInfo(let _data): - if boxed { - buffer.appendInt32(-74456004) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.savedInfo!.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedInfo(let _data): - return ("savedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("savedInfo", ConstructorParameterDescription(_data.savedInfo))]) - } - } - - public static func parse_savedInfo(_ reader: BufferReader) -> SavedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 { - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - if _c1 && _c2 { - return Api.payments.SavedInfo.savedInfo(Cons_savedInfo(flags: _1!, savedInfo: _2)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum SavedStarGifts: TypeConstructorDescription { - public class Cons_savedStarGifts: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var chatNotificationsEnabled: Api.Bool? - public var gifts: [Api.SavedStarGift] - public var nextOffset: String? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.count = count - self.chatNotificationsEnabled = chatNotificationsEnabled - self.gifts = gifts - self.nextOffset = nextOffset - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedStarGifts", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("chatNotificationsEnabled", ConstructorParameterDescription(self.chatNotificationsEnabled)), ("gifts", ConstructorParameterDescription(self.gifts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case savedStarGifts(Cons_savedStarGifts) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedStarGifts(let _data): - if boxed { - buffer.appendInt32(-1779201615) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.chatNotificationsEnabled!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.gifts.count)) - for item in _data.gifts { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedStarGifts(let _data): - return ("savedStarGifts", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("chatNotificationsEnabled", ConstructorParameterDescription(_data.chatNotificationsEnabled)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_savedStarGifts(_ reader: BufferReader) -> SavedStarGifts? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.Bool? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Bool - } - } - var _4: [Api.SavedStarGift]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self) - } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseString(reader) - } - var _6: [Api.Chat]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _7: [Api.User]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.payments.SavedStarGifts.savedStarGifts(Cons_savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGiftActiveAuctions: TypeConstructorDescription { - public class Cons_starGiftActiveAuctions: TypeConstructorDescription { - public var auctions: [Api.StarGiftActiveAuctionState] - public var users: [Api.User] - public var chats: [Api.Chat] - public init(auctions: [Api.StarGiftActiveAuctionState], users: [Api.User], chats: [Api.Chat]) { - self.auctions = auctions - self.users = users - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftActiveAuctions", [("auctions", ConstructorParameterDescription(self.auctions)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) - } - } - case starGiftActiveAuctions(Cons_starGiftActiveAuctions) - case starGiftActiveAuctionsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftActiveAuctions(let _data): - if boxed { - buffer.appendInt32(-1359565892) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.auctions.count)) - for item in _data.auctions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - break - case .starGiftActiveAuctionsNotModified: - if boxed { - buffer.appendInt32(-617358640) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftActiveAuctions(let _data): - return ("starGiftActiveAuctions", [("auctions", ConstructorParameterDescription(_data.auctions)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) - case .starGiftActiveAuctionsNotModified: - return ("starGiftActiveAuctionsNotModified", []) - } - } - - public static func parse_starGiftActiveAuctions(_ reader: BufferReader) -> StarGiftActiveAuctions? { - var _1: [Api.StarGiftActiveAuctionState]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftActiveAuctionState.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctions(Cons_starGiftActiveAuctions(auctions: _1!, users: _2!, chats: _3!)) - } - else { - return nil - } - } - public static func parse_starGiftActiveAuctionsNotModified(_ reader: BufferReader) -> StarGiftActiveAuctions? { - return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctionsNotModified - } - } -} -public extension Api.payments { - enum StarGiftAuctionAcquiredGifts: TypeConstructorDescription { - public class Cons_starGiftAuctionAcquiredGifts: TypeConstructorDescription { - public var gifts: [Api.StarGiftAuctionAcquiredGift] - public var users: [Api.User] - public var chats: [Api.Chat] - public init(gifts: [Api.StarGiftAuctionAcquiredGift], users: [Api.User], chats: [Api.Chat]) { - self.gifts = gifts - self.users = users - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionAcquiredGifts", [("gifts", ConstructorParameterDescription(self.gifts)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) - } - } - case starGiftAuctionAcquiredGifts(Cons_starGiftAuctionAcquiredGifts) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAuctionAcquiredGifts(let _data): - if boxed { - buffer.appendInt32(2103169520) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.gifts.count)) - for item in _data.gifts { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAuctionAcquiredGifts(let _data): - return ("starGiftAuctionAcquiredGifts", [("gifts", ConstructorParameterDescription(_data.gifts)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) - } - } - - public static func parse_starGiftAuctionAcquiredGifts(_ reader: BufferReader) -> StarGiftAuctionAcquiredGifts? { - var _1: [Api.StarGiftAuctionAcquiredGift]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAuctionAcquiredGift.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftAuctionAcquiredGifts.starGiftAuctionAcquiredGifts(Cons_starGiftAuctionAcquiredGifts(gifts: _1!, users: _2!, chats: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGiftAuctionState: TypeConstructorDescription { - public class Cons_starGiftAuctionState: TypeConstructorDescription { - public var gift: Api.StarGift - public var state: Api.StarGiftAuctionState - public var userState: Api.StarGiftAuctionUserState - public var timeout: Int32 - public var users: [Api.User] - public var chats: [Api.Chat] - public init(gift: Api.StarGift, state: Api.StarGiftAuctionState, userState: Api.StarGiftAuctionUserState, timeout: Int32, users: [Api.User], chats: [Api.Chat]) { - self.gift = gift - self.state = state - self.userState = userState - self.timeout = timeout - self.users = users - self.chats = chats - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftAuctionState", [("gift", ConstructorParameterDescription(self.gift)), ("state", ConstructorParameterDescription(self.state)), ("userState", ConstructorParameterDescription(self.userState)), ("timeout", ConstructorParameterDescription(self.timeout)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) - } - } - case starGiftAuctionState(Cons_starGiftAuctionState) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftAuctionState(let _data): - if boxed { - buffer.appendInt32(1798960364) - } - _data.gift.serialize(buffer, true) - _data.state.serialize(buffer, true) - _data.userState.serialize(buffer, true) - serializeInt32(_data.timeout, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftAuctionState(let _data): - return ("starGiftAuctionState", [("gift", ConstructorParameterDescription(_data.gift)), ("state", ConstructorParameterDescription(_data.state)), ("userState", ConstructorParameterDescription(_data.userState)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) - } - } - - public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { - var _1: Api.StarGift? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGift - } - var _2: Api.StarGiftAuctionState? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState - } - var _3: Api.StarGiftAuctionUserState? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState - } - var _4: Int32? - _4 = reader.readInt32() - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: [Api.Chat]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.payments.StarGiftAuctionState.starGiftAuctionState(Cons_starGiftAuctionState(gift: _1!, state: _2!, userState: _3!, timeout: _4!, users: _5!, chats: _6!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGiftCollections: TypeConstructorDescription { - public class Cons_starGiftCollections: TypeConstructorDescription { - public var collections: [Api.StarGiftCollection] - public init(collections: [Api.StarGiftCollection]) { - self.collections = collections - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftCollections", [("collections", ConstructorParameterDescription(self.collections))]) - } - } - case starGiftCollections(Cons_starGiftCollections) - case starGiftCollectionsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftCollections(let _data): - if boxed { - buffer.appendInt32(-1977011469) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.collections.count)) - for item in _data.collections { - item.serialize(buffer, true) - } - break - case .starGiftCollectionsNotModified: - if boxed { - buffer.appendInt32(-1598402793) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftCollections(let _data): - return ("starGiftCollections", [("collections", ConstructorParameterDescription(_data.collections))]) - case .starGiftCollectionsNotModified: - return ("starGiftCollectionsNotModified", []) - } - } - - public static func parse_starGiftCollections(_ reader: BufferReader) -> StarGiftCollections? { - var _1: [Api.StarGiftCollection]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftCollection.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftCollections.starGiftCollections(Cons_starGiftCollections(collections: _1!)) - } - else { - return nil - } - } - public static func parse_starGiftCollectionsNotModified(_ reader: BufferReader) -> StarGiftCollections? { - return Api.payments.StarGiftCollections.starGiftCollectionsNotModified - } - } -} -public extension Api.payments { - enum StarGiftUpgradeAttributes: TypeConstructorDescription { - public class Cons_starGiftUpgradeAttributes: TypeConstructorDescription { - public var attributes: [Api.StarGiftAttribute] - public init(attributes: [Api.StarGiftAttribute]) { - self.attributes = attributes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftUpgradeAttributes", [("attributes", ConstructorParameterDescription(self.attributes))]) - } - } - case starGiftUpgradeAttributes(Cons_starGiftUpgradeAttributes) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftUpgradeAttributes(let _data): - if boxed { - buffer.appendInt32(1187439471) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.attributes.count)) - for item in _data.attributes { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftUpgradeAttributes(let _data): - return ("starGiftUpgradeAttributes", [("attributes", ConstructorParameterDescription(_data.attributes))]) - } - } - - public static func parse_starGiftUpgradeAttributes(_ reader: BufferReader) -> StarGiftUpgradeAttributes? { - var _1: [Api.StarGiftAttribute]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftUpgradeAttributes.starGiftUpgradeAttributes(Cons_starGiftUpgradeAttributes(attributes: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGiftUpgradePreview: TypeConstructorDescription { - public class Cons_starGiftUpgradePreview: TypeConstructorDescription { - public var sampleAttributes: [Api.StarGiftAttribute] - public var prices: [Api.StarGiftUpgradePrice] - public var nextPrices: [Api.StarGiftUpgradePrice] - public init(sampleAttributes: [Api.StarGiftAttribute], prices: [Api.StarGiftUpgradePrice], nextPrices: [Api.StarGiftUpgradePrice]) { - self.sampleAttributes = sampleAttributes - self.prices = prices - self.nextPrices = nextPrices - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftUpgradePreview", [("sampleAttributes", ConstructorParameterDescription(self.sampleAttributes)), ("prices", ConstructorParameterDescription(self.prices)), ("nextPrices", ConstructorParameterDescription(self.nextPrices))]) - } - } - case starGiftUpgradePreview(Cons_starGiftUpgradePreview) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftUpgradePreview(let _data): - if boxed { - buffer.appendInt32(1038213101) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.sampleAttributes.count)) - for item in _data.sampleAttributes { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.prices.count)) - for item in _data.prices { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.nextPrices.count)) - for item in _data.nextPrices { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftUpgradePreview(let _data): - return ("starGiftUpgradePreview", [("sampleAttributes", ConstructorParameterDescription(_data.sampleAttributes)), ("prices", ConstructorParameterDescription(_data.prices)), ("nextPrices", ConstructorParameterDescription(_data.nextPrices))]) - } - } - - public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? { - var _1: [Api.StarGiftAttribute]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) - } - var _2: [Api.StarGiftUpgradePrice]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftUpgradePrice.self) - } - var _3: [Api.StarGiftUpgradePrice]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftUpgradePrice.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(Cons_starGiftUpgradePreview(sampleAttributes: _1!, prices: _2!, nextPrices: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGiftWithdrawalUrl: TypeConstructorDescription { - public class Cons_starGiftWithdrawalUrl: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGiftWithdrawalUrl", [("url", ConstructorParameterDescription(self.url))]) - } - } - case starGiftWithdrawalUrl(Cons_starGiftWithdrawalUrl) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGiftWithdrawalUrl(let _data): - if boxed { - buffer.appendInt32(-2069218660) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGiftWithdrawalUrl(let _data): - return ("starGiftWithdrawalUrl", [("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_starGiftWithdrawalUrl(_ reader: BufferReader) -> StarGiftWithdrawalUrl? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarGiftWithdrawalUrl.starGiftWithdrawalUrl(Cons_starGiftWithdrawalUrl(url: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarGifts: TypeConstructorDescription { - public class Cons_starGifts: TypeConstructorDescription { - public var hash: Int32 - public var gifts: [Api.StarGift] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(hash: Int32, gifts: [Api.StarGift], chats: [Api.Chat], users: [Api.User]) { - self.hash = hash - self.gifts = gifts - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starGifts", [("hash", ConstructorParameterDescription(self.hash)), ("gifts", ConstructorParameterDescription(self.gifts)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case starGifts(Cons_starGifts) - case starGiftsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starGifts(let _data): - if boxed { - buffer.appendInt32(785918357) - } - serializeInt32(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.gifts.count)) - for item in _data.gifts { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .starGiftsNotModified: - if boxed { - buffer.appendInt32(-1551326360) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starGifts(let _data): - return ("starGifts", [("hash", ConstructorParameterDescription(_data.hash)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .starGiftsNotModified: - return ("starGiftsNotModified", []) - } - } - - public static func parse_starGifts(_ reader: BufferReader) -> StarGifts? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.StarGift]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) } var _3: [Api.Chat]? if let _ = reader.readInt32() { @@ -1697,294 +763,49 @@ public extension Api.payments { let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.payments.StarGifts.starGifts(Cons_starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!)) + return Api.messages.Messages.messages(Cons_messages(messages: _1!, topics: _2!, chats: _3!, users: _4!)) } else { return nil } } - public static func parse_starGiftsNotModified(_ reader: BufferReader) -> StarGifts? { - return Api.payments.StarGifts.starGiftsNotModified - } - } -} -public extension Api.payments { - enum StarsRevenueAdsAccountUrl: TypeConstructorDescription { - public class Cons_starsRevenueAdsAccountUrl: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsRevenueAdsAccountUrl", [("url", ConstructorParameterDescription(self.url))]) - } - } - case starsRevenueAdsAccountUrl(Cons_starsRevenueAdsAccountUrl) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsRevenueAdsAccountUrl(let _data): - if boxed { - buffer.appendInt32(961445665) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsRevenueAdsAccountUrl(let _data): - return ("starsRevenueAdsAccountUrl", [("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_starsRevenueAdsAccountUrl(_ reader: BufferReader) -> StarsRevenueAdsAccountUrl? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarsRevenueAdsAccountUrl.starsRevenueAdsAccountUrl(Cons_starsRevenueAdsAccountUrl(url: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarsRevenueStats: TypeConstructorDescription { - public class Cons_starsRevenueStats: TypeConstructorDescription { - public var flags: Int32 - public var topHoursGraph: Api.StatsGraph? - public var revenueGraph: Api.StatsGraph - public var status: Api.StarsRevenueStatus - public var usdRate: Double - public init(flags: Int32, topHoursGraph: Api.StatsGraph?, revenueGraph: Api.StatsGraph, status: Api.StarsRevenueStatus, usdRate: Double) { - self.flags = flags - self.topHoursGraph = topHoursGraph - self.revenueGraph = revenueGraph - self.status = status - self.usdRate = usdRate - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsRevenueStats", [("flags", ConstructorParameterDescription(self.flags)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("revenueGraph", ConstructorParameterDescription(self.revenueGraph)), ("status", ConstructorParameterDescription(self.status)), ("usdRate", ConstructorParameterDescription(self.usdRate))]) - } - } - case starsRevenueStats(Cons_starsRevenueStats) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsRevenueStats(let _data): - if boxed { - buffer.appendInt32(1814066038) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - _data.topHoursGraph!.serialize(buffer, true) - } - _data.revenueGraph.serialize(buffer, true) - _data.status.serialize(buffer, true) - serializeDouble(_data.usdRate, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsRevenueStats(let _data): - return ("starsRevenueStats", [("flags", ConstructorParameterDescription(_data.flags)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("revenueGraph", ConstructorParameterDescription(_data.revenueGraph)), ("status", ConstructorParameterDescription(_data.status)), ("usdRate", ConstructorParameterDescription(_data.usdRate))]) - } - } - - public static func parse_starsRevenueStats(_ reader: BufferReader) -> StarsRevenueStats? { + public static func parse_messagesNotModified(_ reader: BufferReader) -> Messages? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.StatsGraph? - if Int(_1!) & Int(1 << 0) != 0 { + let _c1 = _1 != nil + if _c1 { + return Api.messages.Messages.messagesNotModified(Cons_messagesNotModified(count: _1!)) + } + else { + return nil + } + } + public static func parse_messagesSlice(_ reader: BufferReader) -> Messages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = reader.readInt32() + } + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + var _5: Api.SearchPostsFlood? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + _5 = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood } } - var _3: Api.StatsGraph? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StatsGraph + var _6: [Api.Message]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) } - var _4: Api.StarsRevenueStatus? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus - } - var _5: Double? - _5 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.StarsRevenueStats.starsRevenueStats(Cons_starsRevenueStats(flags: _1!, topHoursGraph: _2, revenueGraph: _3!, status: _4!, usdRate: _5!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarsRevenueWithdrawalUrl: TypeConstructorDescription { - public class Cons_starsRevenueWithdrawalUrl: TypeConstructorDescription { - public var url: String - public init(url: String) { - self.url = url - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsRevenueWithdrawalUrl", [("url", ConstructorParameterDescription(self.url))]) - } - } - case starsRevenueWithdrawalUrl(Cons_starsRevenueWithdrawalUrl) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsRevenueWithdrawalUrl(let _data): - if boxed { - buffer.appendInt32(497778871) - } - serializeString(_data.url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsRevenueWithdrawalUrl(let _data): - return ("starsRevenueWithdrawalUrl", [("url", ConstructorParameterDescription(_data.url))]) - } - } - - public static func parse_starsRevenueWithdrawalUrl(_ reader: BufferReader) -> StarsRevenueWithdrawalUrl? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(Cons_starsRevenueWithdrawalUrl(url: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.payments { - enum StarsStatus: TypeConstructorDescription { - public class Cons_starsStatus: TypeConstructorDescription { - public var flags: Int32 - public var balance: Api.StarsAmount - public var subscriptions: [Api.StarsSubscription]? - public var subscriptionsNextOffset: String? - public var subscriptionsMissingBalance: Int64? - public var history: [Api.StarsTransaction]? - public var nextOffset: String? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, balance: Api.StarsAmount, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.balance = balance - self.subscriptions = subscriptions - self.subscriptionsNextOffset = subscriptionsNextOffset - self.subscriptionsMissingBalance = subscriptionsMissingBalance - self.history = history - self.nextOffset = nextOffset - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("starsStatus", [("flags", ConstructorParameterDescription(self.flags)), ("balance", ConstructorParameterDescription(self.balance)), ("subscriptions", ConstructorParameterDescription(self.subscriptions)), ("subscriptionsNextOffset", ConstructorParameterDescription(self.subscriptionsNextOffset)), ("subscriptionsMissingBalance", ConstructorParameterDescription(self.subscriptionsMissingBalance)), ("history", ConstructorParameterDescription(self.history)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case starsStatus(Cons_starsStatus) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .starsStatus(let _data): - if boxed { - buffer.appendInt32(1822222573) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - _data.balance.serialize(buffer, true) - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.subscriptions!.count)) - for item in _data.subscriptions! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeString(_data.subscriptionsNextOffset!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt64(_data.subscriptionsMissingBalance!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.history!.count)) - for item in _data.history! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .starsStatus(let _data): - return ("starsStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("balance", ConstructorParameterDescription(_data.balance)), ("subscriptions", ConstructorParameterDescription(_data.subscriptions)), ("subscriptionsNextOffset", ConstructorParameterDescription(_data.subscriptionsNextOffset)), ("subscriptionsMissingBalance", ConstructorParameterDescription(_data.subscriptionsMissingBalance)), ("history", ConstructorParameterDescription(_data.history)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.StarsAmount? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount - } - var _3: [Api.StarsSubscription]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self) - } - } - var _4: String? - if Int(_1!) & Int(1 << 2) != 0 { - _4 = parseString(reader) - } - var _5: Int64? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = reader.readInt64() - } - var _6: [Api.StarsTransaction]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self) - } - } - var _7: String? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = parseString(reader) + var _7: [Api.ForumTopic]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ForumTopic.self) } var _8: [Api.Chat]? if let _ = reader.readInt32() { @@ -1996,15 +817,15 @@ public extension Api.payments { } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.payments.StarsStatus.starsStatus(Cons_starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!)) + return Api.messages.Messages.messagesSlice(Cons_messagesSlice(flags: _1!, count: _2!, nextRate: _3, offsetIdOffset: _4, searchFlood: _5, messages: _6!, topics: _7!, chats: _8!, users: _9!)) } else { return nil @@ -2012,83 +833,55 @@ public extension Api.payments { } } } -public extension Api.payments { - enum SuggestedStarRefBots: TypeConstructorDescription { - public class Cons_suggestedStarRefBots: TypeConstructorDescription { - public var flags: Int32 +public extension Api.messages { + enum MyStickers: TypeConstructorDescription { + public class Cons_myStickers: TypeConstructorDescription { public var count: Int32 - public var suggestedBots: [Api.StarRefProgram] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, count: Int32, suggestedBots: [Api.StarRefProgram], users: [Api.User], nextOffset: String?) { - self.flags = flags + public var sets: [Api.StickerSetCovered] + public init(count: Int32, sets: [Api.StickerSetCovered]) { self.count = count - self.suggestedBots = suggestedBots - self.users = users - self.nextOffset = nextOffset + self.sets = sets } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("suggestedStarRefBots", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("suggestedBots", ConstructorParameterDescription(self.suggestedBots)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + return ("myStickers", [("count", ConstructorParameterDescription(self.count)), ("sets", ConstructorParameterDescription(self.sets))]) } } - case suggestedStarRefBots(Cons_suggestedStarRefBots) + case myStickers(Cons_myStickers) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .suggestedStarRefBots(let _data): + case .myStickers(let _data): if boxed { - buffer.appendInt32(-1261053863) + buffer.appendInt32(-83926371) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.suggestedBots.count)) - for item in _data.suggestedBots { + buffer.appendInt32(Int32(_data.sets.count)) + for item in _data.sets { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .suggestedStarRefBots(let _data): - return ("suggestedStarRefBots", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("suggestedBots", ConstructorParameterDescription(_data.suggestedBots)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + case .myStickers(let _data): + return ("myStickers", [("count", ConstructorParameterDescription(_data.count)), ("sets", ConstructorParameterDescription(_data.sets))]) } } - public static func parse_suggestedStarRefBots(_ reader: BufferReader) -> SuggestedStarRefBots? { + public static func parse_myStickers(_ reader: BufferReader) -> MyStickers? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StarRefProgram]? + var _2: [Api.StickerSetCovered]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarRefProgram.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { - _5 = parseString(reader) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.payments.SuggestedStarRefBots.suggestedStarRefBots(Cons_suggestedStarRefBots(flags: _1!, count: _2!, suggestedBots: _3!, users: _4!, nextOffset: _5)) + if _c1 && _c2 { + return Api.messages.MyStickers.myStickers(Cons_myStickers(count: _1!, sets: _2!)) } else { return nil @@ -2096,30 +889,124 @@ public extension Api.payments { } } } -public extension Api.payments { - enum UniqueStarGift: TypeConstructorDescription { - public class Cons_uniqueStarGift: TypeConstructorDescription { - public var gift: Api.StarGift +public extension Api.messages { + enum PeerDialogs: TypeConstructorDescription { + public class Cons_peerDialogs: TypeConstructorDescription { + public var dialogs: [Api.Dialog] + public var messages: [Api.Message] public var chats: [Api.Chat] public var users: [Api.User] - public init(gift: Api.StarGift, chats: [Api.Chat], users: [Api.User]) { - self.gift = gift + public var state: Api.updates.State + public init(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) { + self.dialogs = dialogs + self.messages = messages + self.chats = chats + self.users = users + self.state = state + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerDialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("state", ConstructorParameterDescription(self.state))]) + } + } + case peerDialogs(Cons_peerDialogs) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerDialogs(let _data): + if boxed { + buffer.appendInt32(863093588) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dialogs.count)) + for item in _data.dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + _data.state.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerDialogs(let _data): + return ("peerDialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("state", ConstructorParameterDescription(_data.state))]) + } + } + + public static func parse_peerDialogs(_ reader: BufferReader) -> PeerDialogs? { + var _1: [Api.Dialog]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _5: Api.updates.State? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.updates.State + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.PeerDialogs.peerDialogs(Cons_peerDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!, state: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum PeerSettings: TypeConstructorDescription { + public class Cons_peerSettings: TypeConstructorDescription { + public var settings: Api.PeerSettings + public var chats: [Api.Chat] + public var users: [Api.User] + public init(settings: Api.PeerSettings, chats: [Api.Chat], users: [Api.User]) { + self.settings = settings self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("uniqueStarGift", [("gift", ConstructorParameterDescription(self.gift)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("peerSettings", [("settings", ConstructorParameterDescription(self.settings)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case uniqueStarGift(Cons_uniqueStarGift) + case peerSettings(Cons_peerSettings) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .uniqueStarGift(let _data): + case .peerSettings(let _data): if boxed { - buffer.appendInt32(1097619176) + buffer.appendInt32(1753266509) } - _data.gift.serialize(buffer, true) + _data.settings.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -2136,15 +1023,15 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .uniqueStarGift(let _data): - return ("uniqueStarGift", [("gift", ConstructorParameterDescription(_data.gift)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .peerSettings(let _data): + return ("peerSettings", [("settings", ConstructorParameterDescription(_data.settings)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_uniqueStarGift(_ reader: BufferReader) -> UniqueStarGift? { - var _1: Api.StarGift? + public static func parse_peerSettings(_ reader: BufferReader) -> PeerSettings? { + var _1: Api.PeerSettings? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StarGift + _1 = Api.parse(reader, signature: signature) as? Api.PeerSettings } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -2158,7 +1045,7 @@ public extension Api.payments { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.payments.UniqueStarGift.uniqueStarGift(Cons_uniqueStarGift(gift: _1!, chats: _2!, users: _3!)) + return Api.messages.PeerSettings.peerSettings(Cons_peerSettings(settings: _1!, chats: _2!, users: _3!)) } else { return nil @@ -2166,75 +1053,45 @@ public extension Api.payments { } } } -public extension Api.payments { - enum UniqueStarGiftValueInfo: TypeConstructorDescription { - public class Cons_uniqueStarGiftValueInfo: TypeConstructorDescription { - public var flags: Int32 - public var currency: String - public var value: Int64 - public var initialSaleDate: Int32 - public var initialSaleStars: Int64 - public var initialSalePrice: Int64 - public var lastSaleDate: Int32? - public var lastSalePrice: Int64? - public var floorPrice: Int64? - public var averagePrice: Int64? - public var listedCount: Int32? - public var fragmentListedCount: Int32? - public var fragmentListedUrl: String? - public init(flags: Int32, currency: String, value: Int64, initialSaleDate: Int32, initialSaleStars: Int64, initialSalePrice: Int64, lastSaleDate: Int32?, lastSalePrice: Int64?, floorPrice: Int64?, averagePrice: Int64?, listedCount: Int32?, fragmentListedCount: Int32?, fragmentListedUrl: String?) { - self.flags = flags - self.currency = currency - self.value = value - self.initialSaleDate = initialSaleDate - self.initialSaleStars = initialSaleStars - self.initialSalePrice = initialSalePrice - self.lastSaleDate = lastSaleDate - self.lastSalePrice = lastSalePrice - self.floorPrice = floorPrice - self.averagePrice = averagePrice - self.listedCount = listedCount - self.fragmentListedCount = fragmentListedCount - self.fragmentListedUrl = fragmentListedUrl +public extension Api.messages { + enum PreparedInlineMessage: TypeConstructorDescription { + public class Cons_preparedInlineMessage: TypeConstructorDescription { + public var queryId: Int64 + public var result: Api.BotInlineResult + public var peerTypes: [Api.InlineQueryPeerType] + public var cacheTime: Int32 + public var users: [Api.User] + public init(queryId: Int64, result: Api.BotInlineResult, peerTypes: [Api.InlineQueryPeerType], cacheTime: Int32, users: [Api.User]) { + self.queryId = queryId + self.result = result + self.peerTypes = peerTypes + self.cacheTime = cacheTime + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("uniqueStarGiftValueInfo", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("value", ConstructorParameterDescription(self.value)), ("initialSaleDate", ConstructorParameterDescription(self.initialSaleDate)), ("initialSaleStars", ConstructorParameterDescription(self.initialSaleStars)), ("initialSalePrice", ConstructorParameterDescription(self.initialSalePrice)), ("lastSaleDate", ConstructorParameterDescription(self.lastSaleDate)), ("lastSalePrice", ConstructorParameterDescription(self.lastSalePrice)), ("floorPrice", ConstructorParameterDescription(self.floorPrice)), ("averagePrice", ConstructorParameterDescription(self.averagePrice)), ("listedCount", ConstructorParameterDescription(self.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(self.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(self.fragmentListedUrl))]) + return ("preparedInlineMessage", [("queryId", ConstructorParameterDescription(self.queryId)), ("result", ConstructorParameterDescription(self.result)), ("peerTypes", ConstructorParameterDescription(self.peerTypes)), ("cacheTime", ConstructorParameterDescription(self.cacheTime)), ("users", ConstructorParameterDescription(self.users))]) } } - case uniqueStarGiftValueInfo(Cons_uniqueStarGiftValueInfo) + case preparedInlineMessage(Cons_preparedInlineMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .uniqueStarGiftValueInfo(let _data): + case .preparedInlineMessage(let _data): if boxed { - buffer.appendInt32(1362093126) + buffer.appendInt32(-11046771) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.currency, buffer: buffer, boxed: false) - serializeInt64(_data.value, buffer: buffer, boxed: false) - serializeInt32(_data.initialSaleDate, buffer: buffer, boxed: false) - serializeInt64(_data.initialSaleStars, buffer: buffer, boxed: false) - serializeInt64(_data.initialSalePrice, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.lastSaleDate!, buffer: buffer, boxed: false) + serializeInt64(_data.queryId, buffer: buffer, boxed: false) + _data.result.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peerTypes.count)) + for item in _data.peerTypes { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt64(_data.lastSalePrice!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 2) != 0 { - serializeInt64(_data.floorPrice!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 3) != 0 { - serializeInt64(_data.averagePrice!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.listedCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeInt32(_data.fragmentListedCount!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 5) != 0 { - serializeString(_data.fragmentListedUrl!, buffer: buffer, boxed: false) + serializeInt32(_data.cacheTime, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) } break } @@ -2242,67 +1099,35 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .uniqueStarGiftValueInfo(let _data): - return ("uniqueStarGiftValueInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("value", ConstructorParameterDescription(_data.value)), ("initialSaleDate", ConstructorParameterDescription(_data.initialSaleDate)), ("initialSaleStars", ConstructorParameterDescription(_data.initialSaleStars)), ("initialSalePrice", ConstructorParameterDescription(_data.initialSalePrice)), ("lastSaleDate", ConstructorParameterDescription(_data.lastSaleDate)), ("lastSalePrice", ConstructorParameterDescription(_data.lastSalePrice)), ("floorPrice", ConstructorParameterDescription(_data.floorPrice)), ("averagePrice", ConstructorParameterDescription(_data.averagePrice)), ("listedCount", ConstructorParameterDescription(_data.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(_data.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(_data.fragmentListedUrl))]) + case .preparedInlineMessage(let _data): + return ("preparedInlineMessage", [("queryId", ConstructorParameterDescription(_data.queryId)), ("result", ConstructorParameterDescription(_data.result)), ("peerTypes", ConstructorParameterDescription(_data.peerTypes)), ("cacheTime", ConstructorParameterDescription(_data.cacheTime)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_uniqueStarGiftValueInfo(_ reader: BufferReader) -> UniqueStarGiftValueInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Int64? - _3 = reader.readInt64() + public static func parse_preparedInlineMessage(_ reader: BufferReader) -> PreparedInlineMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.BotInlineResult? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.BotInlineResult + } + var _3: [Api.InlineQueryPeerType]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InlineQueryPeerType.self) + } var _4: Int32? _4 = reader.readInt32() - var _5: Int64? - _5 = reader.readInt64() - var _6: Int64? - _6 = reader.readInt64() - var _7: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _7 = reader.readInt32() - } - var _8: Int64? - if Int(_1!) & Int(1 << 0) != 0 { - _8 = reader.readInt64() - } - var _9: Int64? - if Int(_1!) & Int(1 << 2) != 0 { - _9 = reader.readInt64() - } - var _10: Int64? - if Int(_1!) & Int(1 << 3) != 0 { - _10 = reader.readInt64() - } - var _11: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _11 = reader.readInt32() - } - var _12: Int32? - if Int(_1!) & Int(1 << 5) != 0 { - _12 = reader.readInt32() - } - var _13: String? - if Int(_1!) & Int(1 << 5) != 0 { - _13 = parseString(reader) + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 4) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 5) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.payments.UniqueStarGiftValueInfo.uniqueStarGiftValueInfo(Cons_uniqueStarGiftValueInfo(flags: _1!, currency: _2!, value: _3!, initialSaleDate: _4!, initialSaleStars: _5!, initialSalePrice: _6!, lastSaleDate: _7, lastSalePrice: _8, floorPrice: _9, averagePrice: _10, listedCount: _11, fragmentListedCount: _12, fragmentListedUrl: _13)) + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.PreparedInlineMessage.preparedInlineMessage(Cons_preparedInlineMessage(queryId: _1!, result: _2!, peerTypes: _3!, cacheTime: _4!, users: _5!)) } else { return nil @@ -2310,39 +1135,56 @@ public extension Api.payments { } } } -public extension Api.payments { - enum ValidatedRequestedInfo: TypeConstructorDescription { - public class Cons_validatedRequestedInfo: TypeConstructorDescription { - public var flags: Int32 - public var id: String? - public var shippingOptions: [Api.ShippingOption]? - public init(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) { - self.flags = flags - self.id = id - self.shippingOptions = shippingOptions +public extension Api.messages { + enum QuickReplies: TypeConstructorDescription { + public class Cons_quickReplies: TypeConstructorDescription { + public var quickReplies: [Api.QuickReply] + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(quickReplies: [Api.QuickReply], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + self.quickReplies = quickReplies + self.messages = messages + self.chats = chats + self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("validatedRequestedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("shippingOptions", ConstructorParameterDescription(self.shippingOptions))]) + return ("quickReplies", [("quickReplies", ConstructorParameterDescription(self.quickReplies)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case validatedRequestedInfo(Cons_validatedRequestedInfo) + case quickReplies(Cons_quickReplies) + case quickRepliesNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .validatedRequestedInfo(let _data): + case .quickReplies(let _data): if boxed { - buffer.appendInt32(-784000893) + buffer.appendInt32(-963811691) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.id!, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.quickReplies.count)) + for item in _data.quickReplies { + item.serialize(buffer, true) } - if Int(_data.flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.shippingOptions!.count)) - for item in _data.shippingOptions! { - item.serialize(buffer, true) - } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .quickRepliesNotModified: + if boxed { + buffer.appendInt32(1603398491) } break } @@ -2350,29 +1192,757 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .validatedRequestedInfo(let _data): - return ("validatedRequestedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("shippingOptions", ConstructorParameterDescription(_data.shippingOptions))]) + case .quickReplies(let _data): + return ("quickReplies", [("quickReplies", ConstructorParameterDescription(_data.quickReplies)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .quickRepliesNotModified: + return ("quickRepliesNotModified", []) } } - public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { - _2 = parseString(reader) + public static func parse_quickReplies(_ reader: BufferReader) -> QuickReplies? { + var _1: [Api.QuickReply]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.QuickReply.self) } - var _3: [Api.ShippingOption]? - if Int(_1!) & Int(1 << 1) != 0 { - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) - } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(Cons_validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3)) + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.QuickReplies.quickReplies(Cons_quickReplies(quickReplies: _1!, messages: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + public static func parse_quickRepliesNotModified(_ reader: BufferReader) -> QuickReplies? { + return Api.messages.QuickReplies.quickRepliesNotModified + } + } +} +public extension Api.messages { + enum Reactions: TypeConstructorDescription { + public class Cons_reactions: TypeConstructorDescription { + public var hash: Int64 + public var reactions: [Api.Reaction] + public init(hash: Int64, reactions: [Api.Reaction]) { + self.hash = hash + self.reactions = reactions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("reactions", [("hash", ConstructorParameterDescription(self.hash)), ("reactions", ConstructorParameterDescription(self.reactions))]) + } + } + case reactions(Cons_reactions) + case reactionsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .reactions(let _data): + if boxed { + buffer.appendInt32(-352454890) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.reactions.count)) + for item in _data.reactions { + item.serialize(buffer, true) + } + break + case .reactionsNotModified: + if boxed { + buffer.appendInt32(-1334846497) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .reactions(let _data): + return ("reactions", [("hash", ConstructorParameterDescription(_data.hash)), ("reactions", ConstructorParameterDescription(_data.reactions))]) + case .reactionsNotModified: + return ("reactionsNotModified", []) + } + } + + public static func parse_reactions(_ reader: BufferReader) -> Reactions? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Reaction]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Reaction.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.Reactions.reactions(Cons_reactions(hash: _1!, reactions: _2!)) + } + else { + return nil + } + } + public static func parse_reactionsNotModified(_ reader: BufferReader) -> Reactions? { + return Api.messages.Reactions.reactionsNotModified + } + } +} +public extension Api.messages { + enum RecentStickers: TypeConstructorDescription { + public class Cons_recentStickers: TypeConstructorDescription { + public var hash: Int64 + public var packs: [Api.StickerPack] + public var stickers: [Api.Document] + public var dates: [Int32] + public init(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) { + self.hash = hash + self.packs = packs + self.stickers = stickers + self.dates = dates + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("recentStickers", [("hash", ConstructorParameterDescription(self.hash)), ("packs", ConstructorParameterDescription(self.packs)), ("stickers", ConstructorParameterDescription(self.stickers)), ("dates", ConstructorParameterDescription(self.dates))]) + } + } + case recentStickers(Cons_recentStickers) + case recentStickersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .recentStickers(let _data): + if boxed { + buffer.appendInt32(-1999405994) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.packs.count)) + for item in _data.packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stickers.count)) + for item in _data.stickers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dates.count)) + for item in _data.dates { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .recentStickersNotModified: + if boxed { + buffer.appendInt32(186120336) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .recentStickers(let _data): + return ("recentStickers", [("hash", ConstructorParameterDescription(_data.hash)), ("packs", ConstructorParameterDescription(_data.packs)), ("stickers", ConstructorParameterDescription(_data.stickers)), ("dates", ConstructorParameterDescription(_data.dates))]) + case .recentStickersNotModified: + return ("recentStickersNotModified", []) + } + } + + public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.Document]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.RecentStickers.recentStickers(Cons_recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!)) + } + else { + return nil + } + } + public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { + return Api.messages.RecentStickers.recentStickersNotModified + } + } +} +public extension Api.messages { + enum SavedDialogs: TypeConstructorDescription { + public class Cons_savedDialogs: TypeConstructorDescription { + public var dialogs: [Api.SavedDialog] + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + self.dialogs = dialogs + self.messages = messages + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedDialogs", [("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_savedDialogsNotModified: TypeConstructorDescription { + public var count: Int32 + public init(count: Int32) { + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedDialogsNotModified", [("count", ConstructorParameterDescription(self.count))]) + } + } + public class Cons_savedDialogsSlice: TypeConstructorDescription { + public var count: Int32 + public var dialogs: [Api.SavedDialog] + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(count: Int32, dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + self.count = count + self.dialogs = dialogs + self.messages = messages + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedDialogsSlice", [("count", ConstructorParameterDescription(self.count)), ("dialogs", ConstructorParameterDescription(self.dialogs)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case savedDialogs(Cons_savedDialogs) + case savedDialogsNotModified(Cons_savedDialogsNotModified) + case savedDialogsSlice(Cons_savedDialogsSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedDialogs(let _data): + if boxed { + buffer.appendInt32(-130358751) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dialogs.count)) + for item in _data.dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .savedDialogsNotModified(let _data): + if boxed { + buffer.appendInt32(-1071681560) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + case .savedDialogsSlice(let _data): + if boxed { + buffer.appendInt32(1153080793) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.dialogs.count)) + for item in _data.dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedDialogs(let _data): + return ("savedDialogs", [("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .savedDialogsNotModified(let _data): + return ("savedDialogsNotModified", [("count", ConstructorParameterDescription(_data.count))]) + case .savedDialogsSlice(let _data): + return ("savedDialogsSlice", [("count", ConstructorParameterDescription(_data.count)), ("dialogs", ConstructorParameterDescription(_data.dialogs)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_savedDialogs(_ reader: BufferReader) -> SavedDialogs? { + var _1: [Api.SavedDialog]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.SavedDialogs.savedDialogs(Cons_savedDialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + public static func parse_savedDialogsNotModified(_ reader: BufferReader) -> SavedDialogs? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.SavedDialogs.savedDialogsNotModified(Cons_savedDialogsNotModified(count: _1!)) + } + else { + return nil + } + } + public static func parse_savedDialogsSlice(_ reader: BufferReader) -> SavedDialogs? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SavedDialog]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedDialog.self) + } + var _3: [Api.Message]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.SavedDialogs.savedDialogsSlice(Cons_savedDialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum SavedGifs: TypeConstructorDescription { + public class Cons_savedGifs: TypeConstructorDescription { + public var hash: Int64 + public var gifs: [Api.Document] + public init(hash: Int64, gifs: [Api.Document]) { + self.hash = hash + self.gifs = gifs + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedGifs", [("hash", ConstructorParameterDescription(self.hash)), ("gifs", ConstructorParameterDescription(self.gifs))]) + } + } + case savedGifs(Cons_savedGifs) + case savedGifsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedGifs(let _data): + if boxed { + buffer.appendInt32(-2069878259) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.gifs.count)) + for item in _data.gifs { + item.serialize(buffer, true) + } + break + case .savedGifsNotModified: + if boxed { + buffer.appendInt32(-402498398) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedGifs(let _data): + return ("savedGifs", [("hash", ConstructorParameterDescription(_data.hash)), ("gifs", ConstructorParameterDescription(_data.gifs))]) + case .savedGifsNotModified: + return ("savedGifsNotModified", []) + } + } + + public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.SavedGifs.savedGifs(Cons_savedGifs(hash: _1!, gifs: _2!)) + } + else { + return nil + } + } + public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? { + return Api.messages.SavedGifs.savedGifsNotModified + } + } +} +public extension Api.messages { + enum SavedReactionTags: TypeConstructorDescription { + public class Cons_savedReactionTags: TypeConstructorDescription { + public var tags: [Api.SavedReactionTag] + public var hash: Int64 + public init(tags: [Api.SavedReactionTag], hash: Int64) { + self.tags = tags + self.hash = hash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedReactionTags", [("tags", ConstructorParameterDescription(self.tags)), ("hash", ConstructorParameterDescription(self.hash))]) + } + } + case savedReactionTags(Cons_savedReactionTags) + case savedReactionTagsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedReactionTags(let _data): + if boxed { + buffer.appendInt32(844731658) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.tags.count)) + for item in _data.tags { + item.serialize(buffer, true) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + break + case .savedReactionTagsNotModified: + if boxed { + buffer.appendInt32(-2003084817) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedReactionTags(let _data): + return ("savedReactionTags", [("tags", ConstructorParameterDescription(_data.tags)), ("hash", ConstructorParameterDescription(_data.hash))]) + case .savedReactionTagsNotModified: + return ("savedReactionTagsNotModified", []) + } + } + + public static func parse_savedReactionTags(_ reader: BufferReader) -> SavedReactionTags? { + var _1: [Api.SavedReactionTag]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedReactionTag.self) + } + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.SavedReactionTags.savedReactionTags(Cons_savedReactionTags(tags: _1!, hash: _2!)) + } + else { + return nil + } + } + public static func parse_savedReactionTagsNotModified(_ reader: BufferReader) -> SavedReactionTags? { + return Api.messages.SavedReactionTags.savedReactionTagsNotModified + } + } +} +public extension Api.messages { + enum SearchCounter: TypeConstructorDescription { + public class Cons_searchCounter: TypeConstructorDescription { + public var flags: Int32 + public var filter: Api.MessagesFilter + public var count: Int32 + public init(flags: Int32, filter: Api.MessagesFilter, count: Int32) { + self.flags = flags + self.filter = filter + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("searchCounter", [("flags", ConstructorParameterDescription(self.flags)), ("filter", ConstructorParameterDescription(self.filter)), ("count", ConstructorParameterDescription(self.count))]) + } + } + case searchCounter(Cons_searchCounter) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchCounter(let _data): + if boxed { + buffer.appendInt32(-398136321) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.filter.serialize(buffer, true) + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .searchCounter(let _data): + return ("searchCounter", [("flags", ConstructorParameterDescription(_data.flags)), ("filter", ConstructorParameterDescription(_data.filter)), ("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.MessagesFilter? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.messages.SearchCounter.searchCounter(Cons_searchCounter(flags: _1!, filter: _2!, count: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum SearchResultsCalendar: TypeConstructorDescription { + public class Cons_searchResultsCalendar: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var minDate: Int32 + public var minMsgId: Int32 + public var offsetIdOffset: Int32? + public var periods: [Api.SearchResultsCalendarPeriod] + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.count = count + self.minDate = minDate + self.minMsgId = minMsgId + self.offsetIdOffset = offsetIdOffset + self.periods = periods + self.messages = messages + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("searchResultsCalendar", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("minDate", ConstructorParameterDescription(self.minDate)), ("minMsgId", ConstructorParameterDescription(self.minMsgId)), ("offsetIdOffset", ConstructorParameterDescription(self.offsetIdOffset)), ("periods", ConstructorParameterDescription(self.periods)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case searchResultsCalendar(Cons_searchResultsCalendar) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsCalendar(let _data): + if boxed { + buffer.appendInt32(343859772) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.minDate, buffer: buffer, boxed: false) + serializeInt32(_data.minMsgId, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.offsetIdOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.periods.count)) + for item in _data.periods { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .searchResultsCalendar(let _data): + return ("searchResultsCalendar", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("minDate", ConstructorParameterDescription(_data.minDate)), ("minMsgId", ConstructorParameterDescription(_data.minMsgId)), ("offsetIdOffset", ConstructorParameterDescription(_data.offsetIdOffset)), ("periods", ConstructorParameterDescription(_data.periods)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + var _6: [Api.SearchResultsCalendarPeriod]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self) + } + var _7: [Api.Message]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _8: [Api.Chat]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _9: [Api.User]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.messages.SearchResultsCalendar.searchResultsCalendar(Cons_searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum SearchResultsPositions: TypeConstructorDescription { + public class Cons_searchResultsPositions: TypeConstructorDescription { + public var count: Int32 + public var positions: [Api.SearchResultsPosition] + public init(count: Int32, positions: [Api.SearchResultsPosition]) { + self.count = count + self.positions = positions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("searchResultsPositions", [("count", ConstructorParameterDescription(self.count)), ("positions", ConstructorParameterDescription(self.positions))]) + } + } + case searchResultsPositions(Cons_searchResultsPositions) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsPositions(let _data): + if boxed { + buffer.appendInt32(1404185519) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.positions.count)) + for item in _data.positions { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .searchResultsPositions(let _data): + return ("searchResultsPositions", [("count", ConstructorParameterDescription(_data.count)), ("positions", ConstructorParameterDescription(_data.positions))]) + } + } + + public static func parse_searchResultsPositions(_ reader: BufferReader) -> SearchResultsPositions? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.SearchResultsPosition]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsPosition.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.SearchResultsPositions.searchResultsPositions(Cons_searchResultsPositions(count: _1!, positions: _2!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 36500500ca..d364c7d0be 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -1,128 +1,77 @@ -public extension Api.phone { - enum ExportedGroupCallInvite: TypeConstructorDescription { - public class Cons_exportedGroupCallInvite: TypeConstructorDescription { - public var link: String - public init(link: String) { - self.link = link +public extension Api.messages { + enum SentEncryptedMessage: TypeConstructorDescription { + public class Cons_sentEncryptedFile: TypeConstructorDescription { + public var date: Int32 + public var file: Api.EncryptedFile + public init(date: Int32, file: Api.EncryptedFile) { + self.date = date + self.file = file } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("exportedGroupCallInvite", [("link", ConstructorParameterDescription(self.link))]) + return ("sentEncryptedFile", [("date", ConstructorParameterDescription(self.date)), ("file", ConstructorParameterDescription(self.file))]) } } - case exportedGroupCallInvite(Cons_exportedGroupCallInvite) + public class Cons_sentEncryptedMessage: TypeConstructorDescription { + public var date: Int32 + public init(date: Int32) { + self.date = date + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("sentEncryptedMessage", [("date", ConstructorParameterDescription(self.date))]) + } + } + case sentEncryptedFile(Cons_sentEncryptedFile) + case sentEncryptedMessage(Cons_sentEncryptedMessage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .exportedGroupCallInvite(let _data): + case .sentEncryptedFile(let _data): if boxed { - buffer.appendInt32(541839704) + buffer.appendInt32(-1802240206) } - serializeString(_data.link, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + _data.file.serialize(buffer, true) + break + case .sentEncryptedMessage(let _data): + if boxed { + buffer.appendInt32(1443858741) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .exportedGroupCallInvite(let _data): - return ("exportedGroupCallInvite", [("link", ConstructorParameterDescription(_data.link))]) + case .sentEncryptedFile(let _data): + return ("sentEncryptedFile", [("date", ConstructorParameterDescription(_data.date)), ("file", ConstructorParameterDescription(_data.file))]) + case .sentEncryptedMessage(let _data): + return ("sentEncryptedMessage", [("date", ConstructorParameterDescription(_data.date))]) } } - public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(Cons_exportedGroupCallInvite(link: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.phone { - enum GroupCall: TypeConstructorDescription { - public class Cons_groupCall: TypeConstructorDescription { - public var call: Api.GroupCall - public var participants: [Api.GroupCallParticipant] - public var participantsNextOffset: String - public var chats: [Api.Chat] - public var users: [Api.User] - public init(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) { - self.call = call - self.participants = participants - self.participantsNextOffset = participantsNextOffset - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("groupCall", [("call", ConstructorParameterDescription(self.call)), ("participants", ConstructorParameterDescription(self.participants)), ("participantsNextOffset", ConstructorParameterDescription(self.participantsNextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case groupCall(Cons_groupCall) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .groupCall(let _data): - if boxed { - buffer.appendInt32(-1636664659) - } - _data.call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.participants.count)) - for item in _data.participants { - item.serialize(buffer, true) - } - serializeString(_data.participantsNextOffset, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .groupCall(let _data): - return ("groupCall", [("call", ConstructorParameterDescription(_data.call)), ("participants", ConstructorParameterDescription(_data.participants)), ("participantsNextOffset", ConstructorParameterDescription(_data.participantsNextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { - var _1: Api.GroupCall? + public static func parse_sentEncryptedFile(_ reader: BufferReader) -> SentEncryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.EncryptedFile? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.GroupCall - } - var _2: [Api.GroupCallParticipant]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) - } - var _3: String? - _3 = parseString(reader) - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parse(reader, signature: signature) as? Api.EncryptedFile } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.phone.GroupCall.groupCall(Cons_groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!)) + if _c1 && _c2 { + return Api.messages.SentEncryptedMessage.sentEncryptedFile(Cons_sentEncryptedFile(date: _1!, file: _2!)) + } + else { + return nil + } + } + public static func parse_sentEncryptedMessage(_ reader: BufferReader) -> SentEncryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.SentEncryptedMessage.sentEncryptedMessage(Cons_sentEncryptedMessage(date: _1!)) } else { return nil @@ -130,35 +79,51 @@ public extension Api.phone { } } } -public extension Api.phone { - enum GroupCallStars: TypeConstructorDescription { - public class Cons_groupCallStars: TypeConstructorDescription { - public var totalStars: Int64 - public var topDonors: [Api.GroupCallDonor] +public extension Api.messages { + enum SponsoredMessages: TypeConstructorDescription { + public class Cons_sponsoredMessages: TypeConstructorDescription { + public var flags: Int32 + public var postsBetween: Int32? + public var startDelay: Int32? + public var betweenDelay: Int32? + public var messages: [Api.SponsoredMessage] public var chats: [Api.Chat] public var users: [Api.User] - public init(totalStars: Int64, topDonors: [Api.GroupCallDonor], chats: [Api.Chat], users: [Api.User]) { - self.totalStars = totalStars - self.topDonors = topDonors + public init(flags: Int32, postsBetween: Int32?, startDelay: Int32?, betweenDelay: Int32?, messages: [Api.SponsoredMessage], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.postsBetween = postsBetween + self.startDelay = startDelay + self.betweenDelay = betweenDelay + self.messages = messages self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("groupCallStars", [("totalStars", ConstructorParameterDescription(self.totalStars)), ("topDonors", ConstructorParameterDescription(self.topDonors)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("sponsoredMessages", [("flags", ConstructorParameterDescription(self.flags)), ("postsBetween", ConstructorParameterDescription(self.postsBetween)), ("startDelay", ConstructorParameterDescription(self.startDelay)), ("betweenDelay", ConstructorParameterDescription(self.betweenDelay)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case groupCallStars(Cons_groupCallStars) + case sponsoredMessages(Cons_sponsoredMessages) + case sponsoredMessagesEmpty public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallStars(let _data): + case .sponsoredMessages(let _data): if boxed { - buffer.appendInt32(-1658995418) + buffer.appendInt32(-2464403) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.postsBetween!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.startDelay!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.betweenDelay!, buffer: buffer, boxed: false) } - serializeInt64(_data.totalStars, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topDonors.count)) - for item in _data.topDonors { + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -172,137 +137,359 @@ public extension Api.phone { item.serialize(buffer, true) } break + case .sponsoredMessagesEmpty: + if boxed { + buffer.appendInt32(406407439) + } + break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .groupCallStars(let _data): - return ("groupCallStars", [("totalStars", ConstructorParameterDescription(_data.totalStars)), ("topDonors", ConstructorParameterDescription(_data.topDonors)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .sponsoredMessages(let _data): + return ("sponsoredMessages", [("flags", ConstructorParameterDescription(_data.flags)), ("postsBetween", ConstructorParameterDescription(_data.postsBetween)), ("startDelay", ConstructorParameterDescription(_data.startDelay)), ("betweenDelay", ConstructorParameterDescription(_data.betweenDelay)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .sponsoredMessagesEmpty: + return ("sponsoredMessagesEmpty", []) } } - public static func parse_groupCallStars(_ reader: BufferReader) -> GroupCallStars? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.GroupCallDonor]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallDonor.self) + public static func parse_sponsoredMessages(_ reader: BufferReader) -> SponsoredMessages? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = reader.readInt32() } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() } - var _4: [Api.User]? + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt32() + } + var _5: [Api.SponsoredMessage]? if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SponsoredMessage.self) + } + var _6: [Api.Chat]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.messages.SponsoredMessages.sponsoredMessages(Cons_sponsoredMessages(flags: _1!, postsBetween: _2, startDelay: _3, betweenDelay: _4, messages: _5!, chats: _6!, users: _7!)) + } + else { + return nil + } + } + public static func parse_sponsoredMessagesEmpty(_ reader: BufferReader) -> SponsoredMessages? { + return Api.messages.SponsoredMessages.sponsoredMessagesEmpty + } + } +} +public extension Api.messages { + enum StickerSet: TypeConstructorDescription { + public class Cons_stickerSet: TypeConstructorDescription { + public var set: Api.StickerSet + public var packs: [Api.StickerPack] + public var keywords: [Api.StickerKeyword] + public var documents: [Api.Document] + public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) { + self.set = set + self.packs = packs + self.keywords = keywords + self.documents = documents + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stickerSet", [("set", ConstructorParameterDescription(self.set)), ("packs", ConstructorParameterDescription(self.packs)), ("keywords", ConstructorParameterDescription(self.keywords)), ("documents", ConstructorParameterDescription(self.documents))]) + } + } + case stickerSet(Cons_stickerSet) + case stickerSetNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stickerSet(let _data): + if boxed { + buffer.appendInt32(1846886166) + } + _data.set.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.packs.count)) + for item in _data.packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.keywords.count)) + for item in _data.keywords { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + item.serialize(buffer, true) + } + break + case .stickerSetNotModified: + if boxed { + buffer.appendInt32(-738646805) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stickerSet(let _data): + return ("stickerSet", [("set", ConstructorParameterDescription(_data.set)), ("packs", ConstructorParameterDescription(_data.packs)), ("keywords", ConstructorParameterDescription(_data.keywords)), ("documents", ConstructorParameterDescription(_data.documents))]) + case .stickerSetNotModified: + return ("stickerSetNotModified", []) + } + } + + public static func parse_stickerSet(_ reader: BufferReader) -> StickerSet? { + var _1: Api.StickerSet? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StickerSet + } + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.StickerKeyword]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self) + } + var _4: [Api.Document]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { - return Api.phone.GroupCallStars.groupCallStars(Cons_groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!)) + return Api.messages.StickerSet.stickerSet(Cons_stickerSet(set: _1!, packs: _2!, keywords: _3!, documents: _4!)) } else { return nil } } + public static func parse_stickerSetNotModified(_ reader: BufferReader) -> StickerSet? { + return Api.messages.StickerSet.stickerSetNotModified + } } } -public extension Api.phone { - enum GroupCallStreamChannels: TypeConstructorDescription { - public class Cons_groupCallStreamChannels: TypeConstructorDescription { - public var channels: [Api.GroupCallStreamChannel] - public init(channels: [Api.GroupCallStreamChannel]) { - self.channels = channels +public extension Api.messages { + enum StickerSetInstallResult: TypeConstructorDescription { + public class Cons_stickerSetInstallResultArchive: TypeConstructorDescription { + public var sets: [Api.StickerSetCovered] + public init(sets: [Api.StickerSetCovered]) { + self.sets = sets } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("groupCallStreamChannels", [("channels", ConstructorParameterDescription(self.channels))]) + return ("stickerSetInstallResultArchive", [("sets", ConstructorParameterDescription(self.sets))]) } } - case groupCallStreamChannels(Cons_groupCallStreamChannels) + case stickerSetInstallResultArchive(Cons_stickerSetInstallResultArchive) + case stickerSetInstallResultSuccess public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallStreamChannels(let _data): + case .stickerSetInstallResultArchive(let _data): if boxed { - buffer.appendInt32(-790330702) + buffer.appendInt32(904138920) } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.channels.count)) - for item in _data.channels { + buffer.appendInt32(Int32(_data.sets.count)) + for item in _data.sets { item.serialize(buffer, true) } break + case .stickerSetInstallResultSuccess: + if boxed { + buffer.appendInt32(946083368) + } + break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .groupCallStreamChannels(let _data): - return ("groupCallStreamChannels", [("channels", ConstructorParameterDescription(_data.channels))]) + case .stickerSetInstallResultArchive(let _data): + return ("stickerSetInstallResultArchive", [("sets", ConstructorParameterDescription(_data.sets))]) + case .stickerSetInstallResultSuccess: + return ("stickerSetInstallResultSuccess", []) } } - public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { - var _1: [Api.GroupCallStreamChannel]? + public static func parse_stickerSetInstallResultArchive(_ reader: BufferReader) -> StickerSetInstallResult? { + var _1: [Api.StickerSetCovered]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) } let _c1 = _1 != nil if _c1 { - return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(Cons_groupCallStreamChannels(channels: _1!)) + return Api.messages.StickerSetInstallResult.stickerSetInstallResultArchive(Cons_stickerSetInstallResultArchive(sets: _1!)) } else { return nil } } + public static func parse_stickerSetInstallResultSuccess(_ reader: BufferReader) -> StickerSetInstallResult? { + return Api.messages.StickerSetInstallResult.stickerSetInstallResultSuccess + } } } -public extension Api.phone { - enum GroupCallStreamRtmpUrl: TypeConstructorDescription { - public class Cons_groupCallStreamRtmpUrl: TypeConstructorDescription { - public var url: String - public var key: String - public init(url: String, key: String) { - self.url = url - self.key = key +public extension Api.messages { + enum Stickers: TypeConstructorDescription { + public class Cons_stickers: TypeConstructorDescription { + public var hash: Int64 + public var stickers: [Api.Document] + public init(hash: Int64, stickers: [Api.Document]) { + self.hash = hash + self.stickers = stickers } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("groupCallStreamRtmpUrl", [("url", ConstructorParameterDescription(self.url)), ("key", ConstructorParameterDescription(self.key))]) + return ("stickers", [("hash", ConstructorParameterDescription(self.hash)), ("stickers", ConstructorParameterDescription(self.stickers))]) } } - case groupCallStreamRtmpUrl(Cons_groupCallStreamRtmpUrl) + case stickers(Cons_stickers) + case stickersNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupCallStreamRtmpUrl(let _data): + case .stickers(let _data): if boxed { - buffer.appendInt32(767505458) + buffer.appendInt32(816245886) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stickers.count)) + for item in _data.stickers { + item.serialize(buffer, true) + } + break + case .stickersNotModified: + if boxed { + buffer.appendInt32(-244016606) } - serializeString(_data.url, buffer: buffer, boxed: false) - serializeString(_data.key, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .groupCallStreamRtmpUrl(let _data): - return ("groupCallStreamRtmpUrl", [("url", ConstructorParameterDescription(_data.url)), ("key", ConstructorParameterDescription(_data.key))]) + case .stickers(let _data): + return ("stickers", [("hash", ConstructorParameterDescription(_data.hash)), ("stickers", ConstructorParameterDescription(_data.stickers))]) + case .stickersNotModified: + return ("stickersNotModified", []) } } - public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) + public static func parse_stickers(_ reader: BufferReader) -> Stickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(Cons_groupCallStreamRtmpUrl(url: _1!, key: _2!)) + return Api.messages.Stickers.stickers(Cons_stickers(hash: _1!, stickers: _2!)) + } + else { + return nil + } + } + public static func parse_stickersNotModified(_ reader: BufferReader) -> Stickers? { + return Api.messages.Stickers.stickersNotModified + } + } +} +public extension Api.messages { + enum TranscribedAudio: TypeConstructorDescription { + public class Cons_transcribedAudio: TypeConstructorDescription { + public var flags: Int32 + public var transcriptionId: Int64 + public var text: String + public var trialRemainsNum: Int32? + public var trialRemainsUntilDate: Int32? + public init(flags: Int32, transcriptionId: Int64, text: String, trialRemainsNum: Int32?, trialRemainsUntilDate: Int32?) { + self.flags = flags + self.transcriptionId = transcriptionId + self.text = text + self.trialRemainsNum = trialRemainsNum + self.trialRemainsUntilDate = trialRemainsUntilDate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("transcribedAudio", [("flags", ConstructorParameterDescription(self.flags)), ("transcriptionId", ConstructorParameterDescription(self.transcriptionId)), ("text", ConstructorParameterDescription(self.text)), ("trialRemainsNum", ConstructorParameterDescription(self.trialRemainsNum)), ("trialRemainsUntilDate", ConstructorParameterDescription(self.trialRemainsUntilDate))]) + } + } + case transcribedAudio(Cons_transcribedAudio) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .transcribedAudio(let _data): + if boxed { + buffer.appendInt32(-809903785) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.transcriptionId, buffer: buffer, boxed: false) + serializeString(_data.text, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.trialRemainsNum!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.trialRemainsUntilDate!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .transcribedAudio(let _data): + return ("transcribedAudio", [("flags", ConstructorParameterDescription(_data.flags)), ("transcriptionId", ConstructorParameterDescription(_data.transcriptionId)), ("text", ConstructorParameterDescription(_data.text)), ("trialRemainsNum", ConstructorParameterDescription(_data.trialRemainsNum)), ("trialRemainsUntilDate", ConstructorParameterDescription(_data.trialRemainsUntilDate))]) + } + } + + public static func parse_transcribedAudio(_ reader: BufferReader) -> TranscribedAudio? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: String? + _3 = parseString(reader) + var _4: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = reader.readInt32() + } + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _5 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.TranscribedAudio.transcribedAudio(Cons_transcribedAudio(flags: _1!, transcriptionId: _2!, text: _3!, trialRemainsNum: _4, trialRemainsUntilDate: _5)) } else { return nil @@ -310,42 +497,92 @@ public extension Api.phone { } } } -public extension Api.phone { - enum GroupParticipants: TypeConstructorDescription { - public class Cons_groupParticipants: TypeConstructorDescription { - public var count: Int32 - public var participants: [Api.GroupCallParticipant] - public var nextOffset: String - public var chats: [Api.Chat] - public var users: [Api.User] - public var version: Int32 - public init(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) { - self.count = count - self.participants = participants - self.nextOffset = nextOffset - self.chats = chats - self.users = users - self.version = version +public extension Api.messages { + enum TranslatedText: TypeConstructorDescription { + public class Cons_translateResult: TypeConstructorDescription { + public var result: [Api.TextWithEntities] + public init(result: [Api.TextWithEntities]) { + self.result = result } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("groupParticipants", [("count", ConstructorParameterDescription(self.count)), ("participants", ConstructorParameterDescription(self.participants)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("version", ConstructorParameterDescription(self.version))]) + return ("translateResult", [("result", ConstructorParameterDescription(self.result))]) } } - case groupParticipants(Cons_groupParticipants) + case translateResult(Cons_translateResult) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .groupParticipants(let _data): + case .translateResult(let _data): if boxed { - buffer.appendInt32(-193506890) + buffer.appendInt32(870003448) } - serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.participants.count)) - for item in _data.participants { + buffer.appendInt32(Int32(_data.result.count)) + for item in _data.result { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .translateResult(let _data): + return ("translateResult", [("result", ConstructorParameterDescription(_data.result))]) + } + } + + public static func parse_translateResult(_ reader: BufferReader) -> TranslatedText? { + var _1: [Api.TextWithEntities]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TextWithEntities.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.messages.TranslatedText.translateResult(Cons_translateResult(result: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.messages { + enum VotesList: TypeConstructorDescription { + public class Cons_votesList: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var votes: [Api.MessagePeerVote] + public var chats: [Api.Chat] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, count: Int32, votes: [Api.MessagePeerVote], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.count = count + self.votes = votes + self.chats = chats + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("votesList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("votes", ConstructorParameterDescription(self.votes)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case votesList(Cons_votesList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .votesList(let _data): + if boxed { + buffer.appendInt32(1218005070) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.votes.count)) + for item in _data.votes { item.serialize(buffer, true) } - serializeString(_data.nextOffset, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -356,27 +593,29 @@ public extension Api.phone { for item in _data.users { item.serialize(buffer, true) } - serializeInt32(_data.version, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .groupParticipants(let _data): - return ("groupParticipants", [("count", ConstructorParameterDescription(_data.count)), ("participants", ConstructorParameterDescription(_data.participants)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("version", ConstructorParameterDescription(_data.version))]) + case .votesList(let _data): + return ("votesList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("votes", ConstructorParameterDescription(_data.votes)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) } } - public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { + public static func parse_votesList(_ reader: BufferReader) -> VotesList? { var _1: Int32? _1 = reader.readInt32() - var _2: [Api.GroupCallParticipant]? + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.MessagePeerVote]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessagePeerVote.self) } - var _3: String? - _3 = parseString(reader) var _4: [Api.Chat]? if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) @@ -385,16 +624,18 @@ public extension Api.phone { if let _ = reader.readInt32() { _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _6: Int32? - _6 = reader.readInt32() + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.phone.GroupParticipants.groupParticipants(Cons_groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!)) + return Api.messages.VotesList.votesList(Cons_votesList(flags: _1!, count: _2!, votes: _3!, chats: _4!, users: _5!, nextOffset: _6)) } else { return nil @@ -402,34 +643,30 @@ public extension Api.phone { } } } -public extension Api.phone { - enum JoinAsPeers: TypeConstructorDescription { - public class Cons_joinAsPeers: TypeConstructorDescription { - public var peers: [Api.Peer] +public extension Api.messages { + enum WebPage: TypeConstructorDescription { + public class Cons_webPage: TypeConstructorDescription { + public var webpage: Api.WebPage public var chats: [Api.Chat] public var users: [Api.User] - public init(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { - self.peers = peers + public init(webpage: Api.WebPage, chats: [Api.Chat], users: [Api.User]) { + self.webpage = webpage self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("joinAsPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("webPage", [("webpage", ConstructorParameterDescription(self.webpage)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case joinAsPeers(Cons_joinAsPeers) + case webPage(Cons_webPage) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .joinAsPeers(let _data): + case .webPage(let _data): if boxed { - buffer.appendInt32(-1343921601) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peers.count)) - for item in _data.peers { - item.serialize(buffer, true) + buffer.appendInt32(-44166467) } + _data.webpage.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -446,15 +683,15 @@ public extension Api.phone { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .joinAsPeers(let _data): - return ("joinAsPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .webPage(let _data): + return ("webPage", [("webpage", ConstructorParameterDescription(_data.webpage)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { - var _1: [Api.Peer]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + public static func parse_webPage(_ reader: BufferReader) -> WebPage? { + var _1: Api.WebPage? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.WebPage } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -468,7 +705,7 @@ public extension Api.phone { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.phone.JoinAsPeers.joinAsPeers(Cons_joinAsPeers(peers: _1!, chats: _2!, users: _3!)) + return Api.messages.WebPage.webPage(Cons_webPage(webpage: _1!, chats: _2!, users: _3!)) } else { return nil @@ -476,481 +713,30 @@ public extension Api.phone { } } } -public extension Api.phone { - enum PhoneCall: TypeConstructorDescription { - public class Cons_phoneCall: TypeConstructorDescription { - public var phoneCall: Api.PhoneCall - public var users: [Api.User] - public init(phoneCall: Api.PhoneCall, users: [Api.User]) { - self.phoneCall = phoneCall - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("phoneCall", [("phoneCall", ConstructorParameterDescription(self.phoneCall)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case phoneCall(Cons_phoneCall) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .phoneCall(let _data): - if boxed { - buffer.appendInt32(-326966976) - } - _data.phoneCall.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .phoneCall(let _data): - return ("phoneCall", [("phoneCall", ConstructorParameterDescription(_data.phoneCall)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { - var _1: Api.PhoneCall? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.phone.PhoneCall.phoneCall(Cons_phoneCall(phoneCall: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.photos { - enum Photo: TypeConstructorDescription { - public class Cons_photo: TypeConstructorDescription { - public var photo: Api.Photo - public var users: [Api.User] - public init(photo: Api.Photo, users: [Api.User]) { - self.photo = photo - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photo", [("photo", ConstructorParameterDescription(self.photo)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case photo(Cons_photo) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photo(let _data): - if boxed { - buffer.appendInt32(539045032) - } - _data.photo.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .photo(let _data): - return ("photo", [("photo", ConstructorParameterDescription(_data.photo)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_photo(_ reader: BufferReader) -> Photo? { - var _1: Api.Photo? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Photo - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.photos.Photo.photo(Cons_photo(photo: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.photos { - enum Photos: TypeConstructorDescription { - public class Cons_photos: TypeConstructorDescription { - public var photos: [Api.Photo] - public var users: [Api.User] - public init(photos: [Api.Photo], users: [Api.User]) { - self.photos = photos - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photos", [("photos", ConstructorParameterDescription(self.photos)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_photosSlice: TypeConstructorDescription { - public var count: Int32 - public var photos: [Api.Photo] - public var users: [Api.User] - public init(count: Int32, photos: [Api.Photo], users: [Api.User]) { - self.count = count - self.photos = photos - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("photosSlice", [("count", ConstructorParameterDescription(self.count)), ("photos", ConstructorParameterDescription(self.photos)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case photos(Cons_photos) - case photosSlice(Cons_photosSlice) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photos(let _data): - if boxed { - buffer.appendInt32(-1916114267) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.photos.count)) - for item in _data.photos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .photosSlice(let _data): - if boxed { - buffer.appendInt32(352657236) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.photos.count)) - for item in _data.photos { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .photos(let _data): - return ("photos", [("photos", ConstructorParameterDescription(_data.photos)), ("users", ConstructorParameterDescription(_data.users))]) - case .photosSlice(let _data): - return ("photosSlice", [("count", ConstructorParameterDescription(_data.count)), ("photos", ConstructorParameterDescription(_data.photos)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_photos(_ reader: BufferReader) -> Photos? { - var _1: [Api.Photo]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.photos.Photos.photos(Cons_photos(photos: _1!, users: _2!)) - } - else { - return nil - } - } - public static func parse_photosSlice(_ reader: BufferReader) -> Photos? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Photo]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.photos.Photos.photosSlice(Cons_photosSlice(count: _1!, photos: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.premium { - enum BoostsList: TypeConstructorDescription { - public class Cons_boostsList: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var boosts: [Api.Boost] - public var nextOffset: String? - public var users: [Api.User] - public init(flags: Int32, count: Int32, boosts: [Api.Boost], nextOffset: String?, users: [Api.User]) { - self.flags = flags - self.count = count - self.boosts = boosts - self.nextOffset = nextOffset - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("boostsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("boosts", ConstructorParameterDescription(self.boosts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case boostsList(Cons_boostsList) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .boostsList(let _data): - if boxed { - buffer.appendInt32(-2030542532) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.boosts.count)) - for item in _data.boosts { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .boostsList(let _data): - return ("boostsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_boostsList(_ reader: BufferReader) -> BoostsList? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.Boost]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Boost.self) - } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.premium.BoostsList.boostsList(Cons_boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!)) - } - else { - return nil - } - } - } -} -public extension Api.premium { - enum BoostsStatus: TypeConstructorDescription { - public class Cons_boostsStatus: TypeConstructorDescription { - public var flags: Int32 - public var level: Int32 - public var currentLevelBoosts: Int32 - public var boosts: Int32 - public var giftBoosts: Int32? - public var nextLevelBoosts: Int32? - public var premiumAudience: Api.StatsPercentValue? - public var boostUrl: String - public var prepaidGiveaways: [Api.PrepaidGiveaway]? - public var myBoostSlots: [Int32]? - public init(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) { - self.flags = flags - self.level = level - self.currentLevelBoosts = currentLevelBoosts - self.boosts = boosts - self.giftBoosts = giftBoosts - self.nextLevelBoosts = nextLevelBoosts - self.premiumAudience = premiumAudience - self.boostUrl = boostUrl - self.prepaidGiveaways = prepaidGiveaways - self.myBoostSlots = myBoostSlots - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("boostsStatus", [("flags", ConstructorParameterDescription(self.flags)), ("level", ConstructorParameterDescription(self.level)), ("currentLevelBoosts", ConstructorParameterDescription(self.currentLevelBoosts)), ("boosts", ConstructorParameterDescription(self.boosts)), ("giftBoosts", ConstructorParameterDescription(self.giftBoosts)), ("nextLevelBoosts", ConstructorParameterDescription(self.nextLevelBoosts)), ("premiumAudience", ConstructorParameterDescription(self.premiumAudience)), ("boostUrl", ConstructorParameterDescription(self.boostUrl)), ("prepaidGiveaways", ConstructorParameterDescription(self.prepaidGiveaways)), ("myBoostSlots", ConstructorParameterDescription(self.myBoostSlots))]) - } - } - case boostsStatus(Cons_boostsStatus) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .boostsStatus(let _data): - if boxed { - buffer.appendInt32(1230586490) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.level, buffer: buffer, boxed: false) - serializeInt32(_data.currentLevelBoosts, buffer: buffer, boxed: false) - serializeInt32(_data.boosts, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 4) != 0 { - serializeInt32(_data.giftBoosts!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeInt32(_data.nextLevelBoosts!, buffer: buffer, boxed: false) - } - if Int(_data.flags) & Int(1 << 1) != 0 { - _data.premiumAudience!.serialize(buffer, true) - } - serializeString(_data.boostUrl, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.prepaidGiveaways!.count)) - for item in _data.prepaidGiveaways! { - item.serialize(buffer, true) - } - } - if Int(_data.flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.myBoostSlots!.count)) - for item in _data.myBoostSlots! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .boostsStatus(let _data): - return ("boostsStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("level", ConstructorParameterDescription(_data.level)), ("currentLevelBoosts", ConstructorParameterDescription(_data.currentLevelBoosts)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("giftBoosts", ConstructorParameterDescription(_data.giftBoosts)), ("nextLevelBoosts", ConstructorParameterDescription(_data.nextLevelBoosts)), ("premiumAudience", ConstructorParameterDescription(_data.premiumAudience)), ("boostUrl", ConstructorParameterDescription(_data.boostUrl)), ("prepaidGiveaways", ConstructorParameterDescription(_data.prepaidGiveaways)), ("myBoostSlots", ConstructorParameterDescription(_data.myBoostSlots))]) - } - } - - public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 { - _5 = reader.readInt32() - } - var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = reader.readInt32() - } - var _7: Api.StatsPercentValue? - if Int(_1!) & Int(1 << 1) != 0 { - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue - } - } - var _8: String? - _8 = parseString(reader) - var _9: [Api.PrepaidGiveaway]? - if Int(_1!) & Int(1 << 3) != 0 { - if let _ = reader.readInt32() { - _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) - } - } - var _10: [Int32]? - if Int(_1!) & Int(1 << 2) != 0 { - if let _ = reader.readInt32() { - _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 2) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.premium.BoostsStatus.boostsStatus(Cons_boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10)) - } - else { - return nil - } - } - } -} -public extension Api.premium { - enum MyBoosts: TypeConstructorDescription { - public class Cons_myBoosts: TypeConstructorDescription { - public var myBoosts: [Api.MyBoost] +public extension Api.messages { + indirect enum WebPagePreview: TypeConstructorDescription { + public class Cons_webPagePreview: TypeConstructorDescription { + public var media: Api.MessageMedia public var chats: [Api.Chat] public var users: [Api.User] - public init(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User]) { - self.myBoosts = myBoosts + public init(media: Api.MessageMedia, chats: [Api.Chat], users: [Api.User]) { + self.media = media self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("myBoosts", [("myBoosts", ConstructorParameterDescription(self.myBoosts)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("webPagePreview", [("media", ConstructorParameterDescription(self.media)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case myBoosts(Cons_myBoosts) + case webPagePreview(Cons_webPagePreview) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .myBoosts(let _data): + case .webPagePreview(let _data): if boxed { - buffer.appendInt32(-1696454430) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.myBoosts.count)) - for item in _data.myBoosts { - item.serialize(buffer, true) + buffer.appendInt32(-1936029524) } + _data.media.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -967,15 +753,15 @@ public extension Api.premium { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .myBoosts(let _data): - return ("myBoosts", [("myBoosts", ConstructorParameterDescription(_data.myBoosts)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .webPagePreview(let _data): + return ("webPagePreview", [("media", ConstructorParameterDescription(_data.media)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? { - var _1: [Api.MyBoost]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self) + public static func parse_webPagePreview(_ reader: BufferReader) -> WebPagePreview? { + var _1: Api.MessageMedia? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.MessageMedia } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -989,7 +775,7 @@ public extension Api.premium { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.premium.MyBoosts.myBoosts(Cons_myBoosts(myBoosts: _1!, chats: _2!, users: _3!)) + return Api.messages.WebPagePreview.webPagePreview(Cons_webPagePreview(media: _1!, chats: _2!, users: _3!)) } else { return nil @@ -997,49 +783,55 @@ public extension Api.premium { } } } -public extension Api.smsjobs { - enum EligibilityToJoin: TypeConstructorDescription { - public class Cons_eligibleToJoin: TypeConstructorDescription { - public var termsUrl: String - public var monthlySentSms: Int32 - public init(termsUrl: String, monthlySentSms: Int32) { - self.termsUrl = termsUrl - self.monthlySentSms = monthlySentSms +public extension Api.payments { + enum BankCardData: TypeConstructorDescription { + public class Cons_bankCardData: TypeConstructorDescription { + public var title: String + public var openUrls: [Api.BankCardOpenUrl] + public init(title: String, openUrls: [Api.BankCardOpenUrl]) { + self.title = title + self.openUrls = openUrls } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("eligibleToJoin", [("termsUrl", ConstructorParameterDescription(self.termsUrl)), ("monthlySentSms", ConstructorParameterDescription(self.monthlySentSms))]) + return ("bankCardData", [("title", ConstructorParameterDescription(self.title)), ("openUrls", ConstructorParameterDescription(self.openUrls))]) } } - case eligibleToJoin(Cons_eligibleToJoin) + case bankCardData(Cons_bankCardData) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .eligibleToJoin(let _data): + case .bankCardData(let _data): if boxed { - buffer.appendInt32(-594852657) + buffer.appendInt32(1042605427) + } + serializeString(_data.title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.openUrls.count)) + for item in _data.openUrls { + item.serialize(buffer, true) } - serializeString(_data.termsUrl, buffer: buffer, boxed: false) - serializeInt32(_data.monthlySentSms, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .eligibleToJoin(let _data): - return ("eligibleToJoin", [("termsUrl", ConstructorParameterDescription(_data.termsUrl)), ("monthlySentSms", ConstructorParameterDescription(_data.monthlySentSms))]) + case .bankCardData(let _data): + return ("bankCardData", [("title", ConstructorParameterDescription(_data.title)), ("openUrls", ConstructorParameterDescription(_data.openUrls))]) } } - public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? { + public static func parse_bankCardData(_ reader: BufferReader) -> BankCardData? { var _1: String? _1 = parseString(reader) - var _2: Int32? - _2 = reader.readInt32() + var _2: [Api.BankCardOpenUrl]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BankCardOpenUrl.self) + } let _c1 = _1 != nil let _c2 = _2 != nil if _c1 && _c2 { - return Api.smsjobs.EligibilityToJoin.eligibleToJoin(Cons_eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!)) + return Api.payments.BankCardData.bankCardData(Cons_bankCardData(title: _1!, openUrls: _2!)) } else { return nil @@ -1047,307 +839,178 @@ public extension Api.smsjobs { } } } -public extension Api.smsjobs { - enum Status: TypeConstructorDescription { - public class Cons_status: TypeConstructorDescription { - public var flags: Int32 - public var recentSent: Int32 - public var recentSince: Int32 - public var recentRemains: Int32 - public var totalSent: Int32 - public var totalSince: Int32 - public var lastGiftSlug: String? - public var termsUrl: String - public init(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String) { - self.flags = flags - self.recentSent = recentSent - self.recentSince = recentSince - self.recentRemains = recentRemains - self.totalSent = totalSent - self.totalSince = totalSince - self.lastGiftSlug = lastGiftSlug - self.termsUrl = termsUrl +public extension Api.payments { + enum CheckCanSendGiftResult: TypeConstructorDescription { + public class Cons_checkCanSendGiftResultFail: TypeConstructorDescription { + public var reason: Api.TextWithEntities + public init(reason: Api.TextWithEntities) { + self.reason = reason } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("status", [("flags", ConstructorParameterDescription(self.flags)), ("recentSent", ConstructorParameterDescription(self.recentSent)), ("recentSince", ConstructorParameterDescription(self.recentSince)), ("recentRemains", ConstructorParameterDescription(self.recentRemains)), ("totalSent", ConstructorParameterDescription(self.totalSent)), ("totalSince", ConstructorParameterDescription(self.totalSince)), ("lastGiftSlug", ConstructorParameterDescription(self.lastGiftSlug)), ("termsUrl", ConstructorParameterDescription(self.termsUrl))]) + return ("checkCanSendGiftResultFail", [("reason", ConstructorParameterDescription(self.reason))]) } } - case status(Cons_status) + case checkCanSendGiftResultFail(Cons_checkCanSendGiftResultFail) + case checkCanSendGiftResultOk public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .status(let _data): + case .checkCanSendGiftResultFail(let _data): if boxed { - buffer.appendInt32(720277905) + buffer.appendInt32(-706379148) } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.recentSent, buffer: buffer, boxed: false) - serializeInt32(_data.recentSince, buffer: buffer, boxed: false) - serializeInt32(_data.recentRemains, buffer: buffer, boxed: false) - serializeInt32(_data.totalSent, buffer: buffer, boxed: false) - serializeInt32(_data.totalSince, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeString(_data.lastGiftSlug!, buffer: buffer, boxed: false) + _data.reason.serialize(buffer, true) + break + case .checkCanSendGiftResultOk: + if boxed { + buffer.appendInt32(927967149) } - serializeString(_data.termsUrl, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .status(let _data): - return ("status", [("flags", ConstructorParameterDescription(_data.flags)), ("recentSent", ConstructorParameterDescription(_data.recentSent)), ("recentSince", ConstructorParameterDescription(_data.recentSince)), ("recentRemains", ConstructorParameterDescription(_data.recentRemains)), ("totalSent", ConstructorParameterDescription(_data.totalSent)), ("totalSince", ConstructorParameterDescription(_data.totalSince)), ("lastGiftSlug", ConstructorParameterDescription(_data.lastGiftSlug)), ("termsUrl", ConstructorParameterDescription(_data.termsUrl))]) + case .checkCanSendGiftResultFail(let _data): + return ("checkCanSendGiftResultFail", [("reason", ConstructorParameterDescription(_data.reason))]) + case .checkCanSendGiftResultOk: + return ("checkCanSendGiftResultOk", []) } } - public static func parse_status(_ reader: BufferReader) -> Status? { + public static func parse_checkCanSendGiftResultFail(_ reader: BufferReader) -> CheckCanSendGiftResult? { + var _1: Api.TextWithEntities? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + if _c1 { + return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultFail(Cons_checkCanSendGiftResultFail(reason: _1!)) + } + else { + return nil + } + } + public static func parse_checkCanSendGiftResultOk(_ reader: BufferReader) -> CheckCanSendGiftResult? { + return Api.payments.CheckCanSendGiftResult.checkCanSendGiftResultOk + } + } +} +public extension Api.payments { + enum CheckedGiftCode: TypeConstructorDescription { + public class Cons_checkedGiftCode: TypeConstructorDescription { + public var flags: Int32 + public var fromId: Api.Peer? + public var giveawayMsgId: Int32? + public var toId: Int64? + public var date: Int32 + public var days: Int32 + public var usedDate: Int32? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, fromId: Api.Peer?, giveawayMsgId: Int32?, toId: Int64?, date: Int32, days: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.fromId = fromId + self.giveawayMsgId = giveawayMsgId + self.toId = toId + self.date = date + self.days = days + self.usedDate = usedDate + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("checkedGiftCode", [("flags", ConstructorParameterDescription(self.flags)), ("fromId", ConstructorParameterDescription(self.fromId)), ("giveawayMsgId", ConstructorParameterDescription(self.giveawayMsgId)), ("toId", ConstructorParameterDescription(self.toId)), ("date", ConstructorParameterDescription(self.date)), ("days", ConstructorParameterDescription(self.days)), ("usedDate", ConstructorParameterDescription(self.usedDate)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case checkedGiftCode(Cons_checkedGiftCode) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .checkedGiftCode(let _data): + if boxed { + buffer.appendInt32(-342343793) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.fromId!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt32(_data.giveawayMsgId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.toId!, buffer: buffer, boxed: false) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.days, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.usedDate!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .checkedGiftCode(let _data): + return ("checkedGiftCode", [("flags", ConstructorParameterDescription(_data.flags)), ("fromId", ConstructorParameterDescription(_data.fromId)), ("giveawayMsgId", ConstructorParameterDescription(_data.giveawayMsgId)), ("toId", ConstructorParameterDescription(_data.toId)), ("date", ConstructorParameterDescription(_data.date)), ("days", ConstructorParameterDescription(_data.days)), ("usedDate", ConstructorParameterDescription(_data.usedDate)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_checkedGiftCode(_ reader: BufferReader) -> CheckedGiftCode? { var _1: Int32? _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() + var _2: Api.Peer? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + } var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = reader.readInt32() + } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = reader.readInt64() + } var _5: Int32? _5 = reader.readInt32() var _6: Int32? _6 = reader.readInt32() - var _7: String? - if Int(_1!) & Int(1 << 1) != 0 { - _7 = parseString(reader) + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = reader.readInt32() } - var _8: String? - _8 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.smsjobs.Status.status(Cons_status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!)) - } - else { - return nil - } - } - } -} -public extension Api.stats { - enum BroadcastStats: TypeConstructorDescription { - public class Cons_broadcastStats: TypeConstructorDescription { - public var period: Api.StatsDateRangeDays - public var followers: Api.StatsAbsValueAndPrev - public var viewsPerPost: Api.StatsAbsValueAndPrev - public var sharesPerPost: Api.StatsAbsValueAndPrev - public var reactionsPerPost: Api.StatsAbsValueAndPrev - public var viewsPerStory: Api.StatsAbsValueAndPrev - public var sharesPerStory: Api.StatsAbsValueAndPrev - public var reactionsPerStory: Api.StatsAbsValueAndPrev - public var enabledNotifications: Api.StatsPercentValue - public var growthGraph: Api.StatsGraph - public var followersGraph: Api.StatsGraph - public var muteGraph: Api.StatsGraph - public var topHoursGraph: Api.StatsGraph - public var interactionsGraph: Api.StatsGraph - public var ivInteractionsGraph: Api.StatsGraph - public var viewsBySourceGraph: Api.StatsGraph - public var newFollowersBySourceGraph: Api.StatsGraph - public var languagesGraph: Api.StatsGraph - public var reactionsByEmotionGraph: Api.StatsGraph - public var storyInteractionsGraph: Api.StatsGraph - public var storyReactionsByEmotionGraph: Api.StatsGraph - public var recentPostsInteractions: [Api.PostInteractionCounters] - public init(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters]) { - self.period = period - self.followers = followers - self.viewsPerPost = viewsPerPost - self.sharesPerPost = sharesPerPost - self.reactionsPerPost = reactionsPerPost - self.viewsPerStory = viewsPerStory - self.sharesPerStory = sharesPerStory - self.reactionsPerStory = reactionsPerStory - self.enabledNotifications = enabledNotifications - self.growthGraph = growthGraph - self.followersGraph = followersGraph - self.muteGraph = muteGraph - self.topHoursGraph = topHoursGraph - self.interactionsGraph = interactionsGraph - self.ivInteractionsGraph = ivInteractionsGraph - self.viewsBySourceGraph = viewsBySourceGraph - self.newFollowersBySourceGraph = newFollowersBySourceGraph - self.languagesGraph = languagesGraph - self.reactionsByEmotionGraph = reactionsByEmotionGraph - self.storyInteractionsGraph = storyInteractionsGraph - self.storyReactionsByEmotionGraph = storyReactionsByEmotionGraph - self.recentPostsInteractions = recentPostsInteractions - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("broadcastStats", [("period", ConstructorParameterDescription(self.period)), ("followers", ConstructorParameterDescription(self.followers)), ("viewsPerPost", ConstructorParameterDescription(self.viewsPerPost)), ("sharesPerPost", ConstructorParameterDescription(self.sharesPerPost)), ("reactionsPerPost", ConstructorParameterDescription(self.reactionsPerPost)), ("viewsPerStory", ConstructorParameterDescription(self.viewsPerStory)), ("sharesPerStory", ConstructorParameterDescription(self.sharesPerStory)), ("reactionsPerStory", ConstructorParameterDescription(self.reactionsPerStory)), ("enabledNotifications", ConstructorParameterDescription(self.enabledNotifications)), ("growthGraph", ConstructorParameterDescription(self.growthGraph)), ("followersGraph", ConstructorParameterDescription(self.followersGraph)), ("muteGraph", ConstructorParameterDescription(self.muteGraph)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("interactionsGraph", ConstructorParameterDescription(self.interactionsGraph)), ("ivInteractionsGraph", ConstructorParameterDescription(self.ivInteractionsGraph)), ("viewsBySourceGraph", ConstructorParameterDescription(self.viewsBySourceGraph)), ("newFollowersBySourceGraph", ConstructorParameterDescription(self.newFollowersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(self.languagesGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph)), ("storyInteractionsGraph", ConstructorParameterDescription(self.storyInteractionsGraph)), ("storyReactionsByEmotionGraph", ConstructorParameterDescription(self.storyReactionsByEmotionGraph)), ("recentPostsInteractions", ConstructorParameterDescription(self.recentPostsInteractions))]) - } - } - case broadcastStats(Cons_broadcastStats) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .broadcastStats(let _data): - if boxed { - buffer.appendInt32(963421692) - } - _data.period.serialize(buffer, true) - _data.followers.serialize(buffer, true) - _data.viewsPerPost.serialize(buffer, true) - _data.sharesPerPost.serialize(buffer, true) - _data.reactionsPerPost.serialize(buffer, true) - _data.viewsPerStory.serialize(buffer, true) - _data.sharesPerStory.serialize(buffer, true) - _data.reactionsPerStory.serialize(buffer, true) - _data.enabledNotifications.serialize(buffer, true) - _data.growthGraph.serialize(buffer, true) - _data.followersGraph.serialize(buffer, true) - _data.muteGraph.serialize(buffer, true) - _data.topHoursGraph.serialize(buffer, true) - _data.interactionsGraph.serialize(buffer, true) - _data.ivInteractionsGraph.serialize(buffer, true) - _data.viewsBySourceGraph.serialize(buffer, true) - _data.newFollowersBySourceGraph.serialize(buffer, true) - _data.languagesGraph.serialize(buffer, true) - _data.reactionsByEmotionGraph.serialize(buffer, true) - _data.storyInteractionsGraph.serialize(buffer, true) - _data.storyReactionsByEmotionGraph.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.recentPostsInteractions.count)) - for item in _data.recentPostsInteractions { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .broadcastStats(let _data): - return ("broadcastStats", [("period", ConstructorParameterDescription(_data.period)), ("followers", ConstructorParameterDescription(_data.followers)), ("viewsPerPost", ConstructorParameterDescription(_data.viewsPerPost)), ("sharesPerPost", ConstructorParameterDescription(_data.sharesPerPost)), ("reactionsPerPost", ConstructorParameterDescription(_data.reactionsPerPost)), ("viewsPerStory", ConstructorParameterDescription(_data.viewsPerStory)), ("sharesPerStory", ConstructorParameterDescription(_data.sharesPerStory)), ("reactionsPerStory", ConstructorParameterDescription(_data.reactionsPerStory)), ("enabledNotifications", ConstructorParameterDescription(_data.enabledNotifications)), ("growthGraph", ConstructorParameterDescription(_data.growthGraph)), ("followersGraph", ConstructorParameterDescription(_data.followersGraph)), ("muteGraph", ConstructorParameterDescription(_data.muteGraph)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("interactionsGraph", ConstructorParameterDescription(_data.interactionsGraph)), ("ivInteractionsGraph", ConstructorParameterDescription(_data.ivInteractionsGraph)), ("viewsBySourceGraph", ConstructorParameterDescription(_data.viewsBySourceGraph)), ("newFollowersBySourceGraph", ConstructorParameterDescription(_data.newFollowersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(_data.languagesGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph)), ("storyInteractionsGraph", ConstructorParameterDescription(_data.storyInteractionsGraph)), ("storyReactionsByEmotionGraph", ConstructorParameterDescription(_data.storyReactionsByEmotionGraph)), ("recentPostsInteractions", ConstructorParameterDescription(_data.recentPostsInteractions))]) - } - } - - public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? { - var _1: Api.StatsDateRangeDays? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays - } - var _2: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _3: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _4: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _5: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _6: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _7: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _8: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _9: Api.StatsPercentValue? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue - } - var _10: Api.StatsGraph? - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _11: Api.StatsGraph? - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _12: Api.StatsGraph? - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _13: Api.StatsGraph? - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _14: Api.StatsGraph? - if let signature = reader.readInt32() { - _14 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _15: Api.StatsGraph? - if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _16: Api.StatsGraph? - if let signature = reader.readInt32() { - _16 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _17: Api.StatsGraph? - if let signature = reader.readInt32() { - _17 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _18: Api.StatsGraph? - if let signature = reader.readInt32() { - _18 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _19: Api.StatsGraph? - if let signature = reader.readInt32() { - _19 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _20: Api.StatsGraph? - if let signature = reader.readInt32() { - _20 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _21: Api.StatsGraph? - if let signature = reader.readInt32() { - _21 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _22: [Api.PostInteractionCounters]? + var _8: [Api.Chat]? if let _ = reader.readInt32() { - _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _9: [Api.User]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil - let _c10 = _10 != nil - let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - let _c16 = _16 != nil - let _c17 = _17 != nil - let _c18 = _18 != nil - let _c19 = _19 != nil - let _c20 = _20 != nil - let _c21 = _21 != nil - let _c22 = _22 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { - return Api.stats.BroadcastStats.broadcastStats(Cons_broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!)) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.payments.CheckedGiftCode.checkedGiftCode(Cons_checkedGiftCode(flags: _1!, fromId: _2, giveawayMsgId: _3, toId: _4, date: _5!, days: _6!, usedDate: _7, chats: _8!, users: _9!)) } else { return nil @@ -1355,83 +1018,33 @@ public extension Api.stats { } } } -public extension Api.stats { - enum MegagroupStats: TypeConstructorDescription { - public class Cons_megagroupStats: TypeConstructorDescription { - public var period: Api.StatsDateRangeDays - public var members: Api.StatsAbsValueAndPrev - public var messages: Api.StatsAbsValueAndPrev - public var viewers: Api.StatsAbsValueAndPrev - public var posters: Api.StatsAbsValueAndPrev - public var growthGraph: Api.StatsGraph - public var membersGraph: Api.StatsGraph - public var newMembersBySourceGraph: Api.StatsGraph - public var languagesGraph: Api.StatsGraph - public var messagesGraph: Api.StatsGraph - public var actionsGraph: Api.StatsGraph - public var topHoursGraph: Api.StatsGraph - public var weekdaysGraph: Api.StatsGraph - public var topPosters: [Api.StatsGroupTopPoster] - public var topAdmins: [Api.StatsGroupTopAdmin] - public var topInviters: [Api.StatsGroupTopInviter] +public extension Api.payments { + enum ConnectedStarRefBots: TypeConstructorDescription { + public class Cons_connectedStarRefBots: TypeConstructorDescription { + public var count: Int32 + public var connectedBots: [Api.ConnectedBotStarRef] public var users: [Api.User] - public init(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User]) { - self.period = period - self.members = members - self.messages = messages - self.viewers = viewers - self.posters = posters - self.growthGraph = growthGraph - self.membersGraph = membersGraph - self.newMembersBySourceGraph = newMembersBySourceGraph - self.languagesGraph = languagesGraph - self.messagesGraph = messagesGraph - self.actionsGraph = actionsGraph - self.topHoursGraph = topHoursGraph - self.weekdaysGraph = weekdaysGraph - self.topPosters = topPosters - self.topAdmins = topAdmins - self.topInviters = topInviters + public init(count: Int32, connectedBots: [Api.ConnectedBotStarRef], users: [Api.User]) { + self.count = count + self.connectedBots = connectedBots self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("megagroupStats", [("period", ConstructorParameterDescription(self.period)), ("members", ConstructorParameterDescription(self.members)), ("messages", ConstructorParameterDescription(self.messages)), ("viewers", ConstructorParameterDescription(self.viewers)), ("posters", ConstructorParameterDescription(self.posters)), ("growthGraph", ConstructorParameterDescription(self.growthGraph)), ("membersGraph", ConstructorParameterDescription(self.membersGraph)), ("newMembersBySourceGraph", ConstructorParameterDescription(self.newMembersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(self.languagesGraph)), ("messagesGraph", ConstructorParameterDescription(self.messagesGraph)), ("actionsGraph", ConstructorParameterDescription(self.actionsGraph)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("weekdaysGraph", ConstructorParameterDescription(self.weekdaysGraph)), ("topPosters", ConstructorParameterDescription(self.topPosters)), ("topAdmins", ConstructorParameterDescription(self.topAdmins)), ("topInviters", ConstructorParameterDescription(self.topInviters)), ("users", ConstructorParameterDescription(self.users))]) + return ("connectedStarRefBots", [("count", ConstructorParameterDescription(self.count)), ("connectedBots", ConstructorParameterDescription(self.connectedBots)), ("users", ConstructorParameterDescription(self.users))]) } } - case megagroupStats(Cons_megagroupStats) + case connectedStarRefBots(Cons_connectedStarRefBots) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .megagroupStats(let _data): + case .connectedStarRefBots(let _data): if boxed { - buffer.appendInt32(-276825834) + buffer.appendInt32(-1730811363) } - _data.period.serialize(buffer, true) - _data.members.serialize(buffer, true) - _data.messages.serialize(buffer, true) - _data.viewers.serialize(buffer, true) - _data.posters.serialize(buffer, true) - _data.growthGraph.serialize(buffer, true) - _data.membersGraph.serialize(buffer, true) - _data.newMembersBySourceGraph.serialize(buffer, true) - _data.languagesGraph.serialize(buffer, true) - _data.messagesGraph.serialize(buffer, true) - _data.actionsGraph.serialize(buffer, true) - _data.topHoursGraph.serialize(buffer, true) - _data.weekdaysGraph.serialize(buffer, true) + serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topPosters.count)) - for item in _data.topPosters { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topAdmins.count)) - for item in _data.topAdmins { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.topInviters.count)) - for item in _data.topInviters { + buffer.appendInt32(Int32(_data.connectedBots.count)) + for item in _data.connectedBots { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1445,79 +1058,722 @@ public extension Api.stats { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .megagroupStats(let _data): - return ("megagroupStats", [("period", ConstructorParameterDescription(_data.period)), ("members", ConstructorParameterDescription(_data.members)), ("messages", ConstructorParameterDescription(_data.messages)), ("viewers", ConstructorParameterDescription(_data.viewers)), ("posters", ConstructorParameterDescription(_data.posters)), ("growthGraph", ConstructorParameterDescription(_data.growthGraph)), ("membersGraph", ConstructorParameterDescription(_data.membersGraph)), ("newMembersBySourceGraph", ConstructorParameterDescription(_data.newMembersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(_data.languagesGraph)), ("messagesGraph", ConstructorParameterDescription(_data.messagesGraph)), ("actionsGraph", ConstructorParameterDescription(_data.actionsGraph)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("weekdaysGraph", ConstructorParameterDescription(_data.weekdaysGraph)), ("topPosters", ConstructorParameterDescription(_data.topPosters)), ("topAdmins", ConstructorParameterDescription(_data.topAdmins)), ("topInviters", ConstructorParameterDescription(_data.topInviters)), ("users", ConstructorParameterDescription(_data.users))]) + case .connectedStarRefBots(let _data): + return ("connectedStarRefBots", [("count", ConstructorParameterDescription(_data.count)), ("connectedBots", ConstructorParameterDescription(_data.connectedBots)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_megagroupStats(_ reader: BufferReader) -> MegagroupStats? { - var _1: Api.StatsDateRangeDays? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays - } - var _2: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _3: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _4: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _5: Api.StatsAbsValueAndPrev? - if let signature = reader.readInt32() { - _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev - } - var _6: Api.StatsGraph? - if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _7: Api.StatsGraph? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _8: Api.StatsGraph? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _9: Api.StatsGraph? - if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _10: Api.StatsGraph? - if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _11: Api.StatsGraph? - if let signature = reader.readInt32() { - _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _12: Api.StatsGraph? - if let signature = reader.readInt32() { - _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _13: Api.StatsGraph? - if let signature = reader.readInt32() { - _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _14: [Api.StatsGroupTopPoster]? + public static func parse_connectedStarRefBots(_ reader: BufferReader) -> ConnectedStarRefBots? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.ConnectedBotStarRef]? if let _ = reader.readInt32() { - _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ConnectedBotStarRef.self) } - var _15: [Api.StatsGroupTopAdmin]? + var _3: [Api.User]? if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - var _16: [Api.StatsGroupTopInviter]? - if let _ = reader.readInt32() { - _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.payments.ConnectedStarRefBots.connectedStarRefBots(Cons_connectedStarRefBots(count: _1!, connectedBots: _2!, users: _3!)) } - var _17: [Api.User]? + else { + return nil + } + } + } +} +public extension Api.payments { + enum ExportedInvoice: TypeConstructorDescription { + public class Cons_exportedInvoice: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedInvoice", [("url", ConstructorParameterDescription(self.url))]) + } + } + case exportedInvoice(Cons_exportedInvoice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedInvoice(let _data): + if boxed { + buffer.appendInt32(-1362048039) + } + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedInvoice(let _data): + return ("exportedInvoice", [("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_exportedInvoice(_ reader: BufferReader) -> ExportedInvoice? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.ExportedInvoice.exportedInvoice(Cons_exportedInvoice(url: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum GiveawayInfo: TypeConstructorDescription { + public class Cons_giveawayInfo: TypeConstructorDescription { + public var flags: Int32 + public var startDate: Int32 + public var joinedTooEarlyDate: Int32? + public var adminDisallowedChatId: Int64? + public var disallowedCountry: String? + public init(flags: Int32, startDate: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?, disallowedCountry: String?) { + self.flags = flags + self.startDate = startDate + self.joinedTooEarlyDate = joinedTooEarlyDate + self.adminDisallowedChatId = adminDisallowedChatId + self.disallowedCountry = disallowedCountry + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("giveawayInfo", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("joinedTooEarlyDate", ConstructorParameterDescription(self.joinedTooEarlyDate)), ("adminDisallowedChatId", ConstructorParameterDescription(self.adminDisallowedChatId)), ("disallowedCountry", ConstructorParameterDescription(self.disallowedCountry))]) + } + } + public class Cons_giveawayInfoResults: TypeConstructorDescription { + public var flags: Int32 + public var startDate: Int32 + public var giftCodeSlug: String? + public var starsPrize: Int64? + public var finishDate: Int32 + public var winnersCount: Int32 + public var activatedCount: Int32? + public init(flags: Int32, startDate: Int32, giftCodeSlug: String?, starsPrize: Int64?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32?) { + self.flags = flags + self.startDate = startDate + self.giftCodeSlug = giftCodeSlug + self.starsPrize = starsPrize + self.finishDate = finishDate + self.winnersCount = winnersCount + self.activatedCount = activatedCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("giveawayInfoResults", [("flags", ConstructorParameterDescription(self.flags)), ("startDate", ConstructorParameterDescription(self.startDate)), ("giftCodeSlug", ConstructorParameterDescription(self.giftCodeSlug)), ("starsPrize", ConstructorParameterDescription(self.starsPrize)), ("finishDate", ConstructorParameterDescription(self.finishDate)), ("winnersCount", ConstructorParameterDescription(self.winnersCount)), ("activatedCount", ConstructorParameterDescription(self.activatedCount))]) + } + } + case giveawayInfo(Cons_giveawayInfo) + case giveawayInfoResults(Cons_giveawayInfoResults) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .giveawayInfo(let _data): + if boxed { + buffer.appendInt32(1130879648) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.startDate, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.joinedTooEarlyDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.adminDisallowedChatId!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.disallowedCountry!, buffer: buffer, boxed: false) + } + break + case .giveawayInfoResults(let _data): + if boxed { + buffer.appendInt32(-512366993) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.startDate, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeString(_data.giftCodeSlug!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.starsPrize!, buffer: buffer, boxed: false) + } + serializeInt32(_data.finishDate, buffer: buffer, boxed: false) + serializeInt32(_data.winnersCount, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt32(_data.activatedCount!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .giveawayInfo(let _data): + return ("giveawayInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("joinedTooEarlyDate", ConstructorParameterDescription(_data.joinedTooEarlyDate)), ("adminDisallowedChatId", ConstructorParameterDescription(_data.adminDisallowedChatId)), ("disallowedCountry", ConstructorParameterDescription(_data.disallowedCountry))]) + case .giveawayInfoResults(let _data): + return ("giveawayInfoResults", [("flags", ConstructorParameterDescription(_data.flags)), ("startDate", ConstructorParameterDescription(_data.startDate)), ("giftCodeSlug", ConstructorParameterDescription(_data.giftCodeSlug)), ("starsPrize", ConstructorParameterDescription(_data.starsPrize)), ("finishDate", ConstructorParameterDescription(_data.finishDate)), ("winnersCount", ConstructorParameterDescription(_data.winnersCount)), ("activatedCount", ConstructorParameterDescription(_data.activatedCount))]) + } + } + + public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = reader.readInt64() + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.payments.GiveawayInfo.giveawayInfo(Cons_giveawayInfo(flags: _1!, startDate: _2!, joinedTooEarlyDate: _3, adminDisallowedChatId: _4, disallowedCountry: _5)) + } + else { + return nil + } + } + public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _3 = parseString(reader) + } + var _4: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _4 = reader.readInt64() + } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _7 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.payments.GiveawayInfo.giveawayInfoResults(Cons_giveawayInfoResults(flags: _1!, startDate: _2!, giftCodeSlug: _3, starsPrize: _4, finishDate: _5!, winnersCount: _6!, activatedCount: _7)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum PaymentForm: TypeConstructorDescription { + public class Cons_paymentForm: TypeConstructorDescription { + public var flags: Int32 + public var formId: Int64 + public var botId: Int64 + public var title: String + public var description: String + public var photo: Api.WebDocument? + public var invoice: Api.Invoice + public var providerId: Int64 + public var url: String + public var nativeProvider: String? + public var nativeParams: Api.DataJSON? + public var additionalMethods: [Api.PaymentFormMethod]? + public var savedInfo: Api.PaymentRequestedInfo? + public var savedCredentials: [Api.PaymentSavedCredentials]? + public var users: [Api.User] + public init(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User]) { + self.flags = flags + self.formId = formId + self.botId = botId + self.title = title + self.description = description + self.photo = photo + self.invoice = invoice + self.providerId = providerId + self.url = url + self.nativeProvider = nativeProvider + self.nativeParams = nativeParams + self.additionalMethods = additionalMethods + self.savedInfo = savedInfo + self.savedCredentials = savedCredentials + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentForm", [("flags", ConstructorParameterDescription(self.flags)), ("formId", ConstructorParameterDescription(self.formId)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("providerId", ConstructorParameterDescription(self.providerId)), ("url", ConstructorParameterDescription(self.url)), ("nativeProvider", ConstructorParameterDescription(self.nativeProvider)), ("nativeParams", ConstructorParameterDescription(self.nativeParams)), ("additionalMethods", ConstructorParameterDescription(self.additionalMethods)), ("savedInfo", ConstructorParameterDescription(self.savedInfo)), ("savedCredentials", ConstructorParameterDescription(self.savedCredentials)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_paymentFormStarGift: TypeConstructorDescription { + public var formId: Int64 + public var invoice: Api.Invoice + public init(formId: Int64, invoice: Api.Invoice) { + self.formId = formId + self.invoice = invoice + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentFormStarGift", [("formId", ConstructorParameterDescription(self.formId)), ("invoice", ConstructorParameterDescription(self.invoice))]) + } + } + public class Cons_paymentFormStars: TypeConstructorDescription { + public var flags: Int32 + public var formId: Int64 + public var botId: Int64 + public var title: String + public var description: String + public var photo: Api.WebDocument? + public var invoice: Api.Invoice + public var users: [Api.User] + public init(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, users: [Api.User]) { + self.flags = flags + self.formId = formId + self.botId = botId + self.title = title + self.description = description + self.photo = photo + self.invoice = invoice + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentFormStars", [("flags", ConstructorParameterDescription(self.flags)), ("formId", ConstructorParameterDescription(self.formId)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case paymentForm(Cons_paymentForm) + case paymentFormStarGift(Cons_paymentFormStarGift) + case paymentFormStars(Cons_paymentFormStars) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentForm(let _data): + if boxed { + buffer.appendInt32(-1610250415) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.formId, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.photo!.serialize(buffer, true) + } + _data.invoice.serialize(buffer, true) + serializeInt64(_data.providerId, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeString(_data.nativeProvider!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + _data.nativeParams!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 6) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.additionalMethods!.count)) + for item in _data.additionalMethods! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.savedInfo!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.savedCredentials!.count)) + for item in _data.savedCredentials! { + item.serialize(buffer, true) + } + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .paymentFormStarGift(let _data): + if boxed { + buffer.appendInt32(-1272590367) + } + serializeInt64(_data.formId, buffer: buffer, boxed: false) + _data.invoice.serialize(buffer, true) + break + case .paymentFormStars(let _data): + if boxed { + buffer.appendInt32(2079764828) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt64(_data.formId, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 5) != 0 { + _data.photo!.serialize(buffer, true) + } + _data.invoice.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paymentForm(let _data): + return ("paymentForm", [("flags", ConstructorParameterDescription(_data.flags)), ("formId", ConstructorParameterDescription(_data.formId)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("providerId", ConstructorParameterDescription(_data.providerId)), ("url", ConstructorParameterDescription(_data.url)), ("nativeProvider", ConstructorParameterDescription(_data.nativeProvider)), ("nativeParams", ConstructorParameterDescription(_data.nativeParams)), ("additionalMethods", ConstructorParameterDescription(_data.additionalMethods)), ("savedInfo", ConstructorParameterDescription(_data.savedInfo)), ("savedCredentials", ConstructorParameterDescription(_data.savedCredentials)), ("users", ConstructorParameterDescription(_data.users))]) + case .paymentFormStarGift(let _data): + return ("paymentFormStarGift", [("formId", ConstructorParameterDescription(_data.formId)), ("invoice", ConstructorParameterDescription(_data.invoice))]) + case .paymentFormStars(let _data): + return ("paymentFormStars", [("flags", ConstructorParameterDescription(_data.flags)), ("formId", ConstructorParameterDescription(_data.formId)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_paymentForm(_ reader: BufferReader) -> PaymentForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: Int64? + _8 = reader.readInt64() + var _9: String? + _9 = parseString(reader) + var _10: String? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _10 = parseString(reader) + } + var _11: Api.DataJSON? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.DataJSON + } + } + var _12: [Api.PaymentFormMethod]? + if Int(_1 ?? 0) & Int(1 << 6) != 0 { + if let _ = reader.readInt32() { + _12 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentFormMethod.self) + } + } + var _13: Api.PaymentRequestedInfo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } + } + var _14: [Api.PaymentSavedCredentials]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PaymentSavedCredentials.self) + } + } + var _15: [Api.User]? if let _ = reader.readInt32() { - _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentForm.paymentForm(Cons_paymentForm(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, providerId: _8!, url: _9!, nativeProvider: _10, nativeParams: _11, additionalMethods: _12, savedInfo: _13, savedCredentials: _14, users: _15!)) + } + else { + return nil + } + } + public static func parse_paymentFormStarGift(_ reader: BufferReader) -> PaymentForm? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Api.Invoice? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Invoice + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.payments.PaymentForm.paymentFormStarGift(Cons_paymentFormStarGift(formId: _1!, invoice: _2!)) + } + else { + return nil + } + } + public static func parse_paymentFormStars(_ reader: BufferReader) -> PaymentForm? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: [Api.User]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.payments.PaymentForm.paymentFormStars(Cons_paymentFormStars(flags: _1!, formId: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, users: _8!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum PaymentReceipt: TypeConstructorDescription { + public class Cons_paymentReceipt: TypeConstructorDescription { + public var flags: Int32 + public var date: Int32 + public var botId: Int64 + public var providerId: Int64 + public var title: String + public var description: String + public var photo: Api.WebDocument? + public var invoice: Api.Invoice + public var info: Api.PaymentRequestedInfo? + public var shipping: Api.ShippingOption? + public var tipAmount: Int64? + public var currency: String + public var totalAmount: Int64 + public var credentialsTitle: String + public var users: [Api.User] + public init(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) { + self.flags = flags + self.date = date + self.botId = botId + self.providerId = providerId + self.title = title + self.description = description + self.photo = photo + self.invoice = invoice + self.info = info + self.shipping = shipping + self.tipAmount = tipAmount + self.currency = currency + self.totalAmount = totalAmount + self.credentialsTitle = credentialsTitle + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentReceipt", [("flags", ConstructorParameterDescription(self.flags)), ("date", ConstructorParameterDescription(self.date)), ("botId", ConstructorParameterDescription(self.botId)), ("providerId", ConstructorParameterDescription(self.providerId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("info", ConstructorParameterDescription(self.info)), ("shipping", ConstructorParameterDescription(self.shipping)), ("tipAmount", ConstructorParameterDescription(self.tipAmount)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("credentialsTitle", ConstructorParameterDescription(self.credentialsTitle)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_paymentReceiptStars: TypeConstructorDescription { + public var flags: Int32 + public var date: Int32 + public var botId: Int64 + public var title: String + public var description: String + public var photo: Api.WebDocument? + public var invoice: Api.Invoice + public var currency: String + public var totalAmount: Int64 + public var transactionId: String + public var users: [Api.User] + public init(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User]) { + self.flags = flags + self.date = date + self.botId = botId + self.title = title + self.description = description + self.photo = photo + self.invoice = invoice + self.currency = currency + self.totalAmount = totalAmount + self.transactionId = transactionId + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentReceiptStars", [("flags", ConstructorParameterDescription(self.flags)), ("date", ConstructorParameterDescription(self.date)), ("botId", ConstructorParameterDescription(self.botId)), ("title", ConstructorParameterDescription(self.title)), ("description", ConstructorParameterDescription(self.description)), ("photo", ConstructorParameterDescription(self.photo)), ("invoice", ConstructorParameterDescription(self.invoice)), ("currency", ConstructorParameterDescription(self.currency)), ("totalAmount", ConstructorParameterDescription(self.totalAmount)), ("transactionId", ConstructorParameterDescription(self.transactionId)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case paymentReceipt(Cons_paymentReceipt) + case paymentReceiptStars(Cons_paymentReceiptStars) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentReceipt(let _data): + if boxed { + buffer.appendInt32(1891958275) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeInt64(_data.providerId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.photo!.serialize(buffer, true) + } + _data.invoice.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.info!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.shipping!.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt64(_data.tipAmount!, buffer: buffer, boxed: false) + } + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + serializeString(_data.credentialsTitle, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .paymentReceiptStars(let _data): + if boxed { + buffer.appendInt32(-625215430) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt64(_data.botId, buffer: buffer, boxed: false) + serializeString(_data.title, buffer: buffer, boxed: false) + serializeString(_data.description, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.photo!.serialize(buffer, true) + } + _data.invoice.serialize(buffer, true) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.totalAmount, buffer: buffer, boxed: false) + serializeString(_data.transactionId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .paymentReceipt(let _data): + return ("paymentReceipt", [("flags", ConstructorParameterDescription(_data.flags)), ("date", ConstructorParameterDescription(_data.date)), ("botId", ConstructorParameterDescription(_data.botId)), ("providerId", ConstructorParameterDescription(_data.providerId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("info", ConstructorParameterDescription(_data.info)), ("shipping", ConstructorParameterDescription(_data.shipping)), ("tipAmount", ConstructorParameterDescription(_data.tipAmount)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("credentialsTitle", ConstructorParameterDescription(_data.credentialsTitle)), ("users", ConstructorParameterDescription(_data.users))]) + case .paymentReceiptStars(let _data): + return ("paymentReceiptStars", [("flags", ConstructorParameterDescription(_data.flags)), ("date", ConstructorParameterDescription(_data.date)), ("botId", ConstructorParameterDescription(_data.botId)), ("title", ConstructorParameterDescription(_data.title)), ("description", ConstructorParameterDescription(_data.description)), ("photo", ConstructorParameterDescription(_data.photo)), ("invoice", ConstructorParameterDescription(_data.invoice)), ("currency", ConstructorParameterDescription(_data.currency)), ("totalAmount", ConstructorParameterDescription(_data.totalAmount)), ("transactionId", ConstructorParameterDescription(_data.transactionId)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _8: Api.Invoice? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _9: Api.PaymentRequestedInfo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } + } + var _10: Api.ShippingOption? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption + } + } + var _11: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _11 = reader.readInt64() + } + var _12: String? + _12 = parseString(reader) + var _13: Int64? + _13 = reader.readInt64() + var _14: String? + _14 = parseString(reader) + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil @@ -1525,19 +1781,66 @@ public extension Api.stats { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentReceipt.paymentReceipt(Cons_paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)) + } + else { + return nil + } + } + public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Api.WebDocument? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.WebDocument + } + } + var _7: Api.Invoice? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _8: String? + _8 = parseString(reader) + var _9: Int64? + _9 = reader.readInt64() + var _10: String? + _10 = parseString(reader) + var _11: [Api.User]? + if let _ = reader.readInt32() { + _11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - let _c16 = _16 != nil - let _c17 = _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - return Api.stats.MegagroupStats.megagroupStats(Cons_megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!)) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.payments.PaymentReceipt.paymentReceiptStars(Cons_paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!)) } else { return nil @@ -1545,53 +1848,74 @@ public extension Api.stats { } } } -public extension Api.stats { - enum MessageStats: TypeConstructorDescription { - public class Cons_messageStats: TypeConstructorDescription { - public var viewsGraph: Api.StatsGraph - public var reactionsByEmotionGraph: Api.StatsGraph - public init(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) { - self.viewsGraph = viewsGraph - self.reactionsByEmotionGraph = reactionsByEmotionGraph +public extension Api.payments { + indirect enum PaymentResult: TypeConstructorDescription { + public class Cons_paymentResult: TypeConstructorDescription { + public var updates: Api.Updates + public init(updates: Api.Updates) { + self.updates = updates } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("messageStats", [("viewsGraph", ConstructorParameterDescription(self.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph))]) + return ("paymentResult", [("updates", ConstructorParameterDescription(self.updates))]) } } - case messageStats(Cons_messageStats) + public class Cons_paymentVerificationNeeded: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("paymentVerificationNeeded", [("url", ConstructorParameterDescription(self.url))]) + } + } + case paymentResult(Cons_paymentResult) + case paymentVerificationNeeded(Cons_paymentVerificationNeeded) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .messageStats(let _data): + case .paymentResult(let _data): if boxed { - buffer.appendInt32(2145983508) + buffer.appendInt32(1314881805) } - _data.viewsGraph.serialize(buffer, true) - _data.reactionsByEmotionGraph.serialize(buffer, true) + _data.updates.serialize(buffer, true) + break + case .paymentVerificationNeeded(let _data): + if boxed { + buffer.appendInt32(-666824391) + } + serializeString(_data.url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .messageStats(let _data): - return ("messageStats", [("viewsGraph", ConstructorParameterDescription(_data.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph))]) + case .paymentResult(let _data): + return ("paymentResult", [("updates", ConstructorParameterDescription(_data.updates))]) + case .paymentVerificationNeeded(let _data): + return ("paymentVerificationNeeded", [("url", ConstructorParameterDescription(_data.url))]) } } - public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { - var _1: Api.StatsGraph? + public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { + var _1: Api.Updates? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + _1 = Api.parse(reader, signature: signature) as? Api.Updates } let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stats.MessageStats.messageStats(Cons_messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)) + if _c1 { + return Api.payments.PaymentResult.paymentResult(Cons_paymentResult(updates: _1!)) + } + else { + return nil + } + } + public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.PaymentResult.paymentVerificationNeeded(Cons_paymentVerificationNeeded(url: _1!)) } else { return nil @@ -1599,50 +1923,73 @@ public extension Api.stats { } } } -public extension Api.stats { - enum PublicForwards: TypeConstructorDescription { - public class Cons_publicForwards: TypeConstructorDescription { +public extension Api.payments { + enum ResaleStarGifts: TypeConstructorDescription { + public class Cons_resaleStarGifts: TypeConstructorDescription { public var flags: Int32 public var count: Int32 - public var forwards: [Api.PublicForward] + public var gifts: [Api.StarGift] public var nextOffset: String? + public var attributes: [Api.StarGiftAttribute]? + public var attributesHash: Int64? public var chats: [Api.Chat] + public var counters: [Api.StarGiftAttributeCounter]? public var users: [Api.User] - public init(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { + public init(flags: Int32, count: Int32, gifts: [Api.StarGift], nextOffset: String?, attributes: [Api.StarGiftAttribute]?, attributesHash: Int64?, chats: [Api.Chat], counters: [Api.StarGiftAttributeCounter]?, users: [Api.User]) { self.flags = flags self.count = count - self.forwards = forwards + self.gifts = gifts self.nextOffset = nextOffset + self.attributes = attributes + self.attributesHash = attributesHash self.chats = chats + self.counters = counters self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("publicForwards", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("forwards", ConstructorParameterDescription(self.forwards)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("resaleStarGifts", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("gifts", ConstructorParameterDescription(self.gifts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("attributes", ConstructorParameterDescription(self.attributes)), ("attributesHash", ConstructorParameterDescription(self.attributesHash)), ("chats", ConstructorParameterDescription(self.chats)), ("counters", ConstructorParameterDescription(self.counters)), ("users", ConstructorParameterDescription(self.users))]) } } - case publicForwards(Cons_publicForwards) + case resaleStarGifts(Cons_resaleStarGifts) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .publicForwards(let _data): + case .resaleStarGifts(let _data): if boxed { - buffer.appendInt32(-1828487648) + buffer.appendInt32(-1803939105) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.forwards.count)) - for item in _data.forwards { + buffer.appendInt32(Int32(_data.gifts.count)) + for item in _data.gifts { item.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 0) != 0 { serializeString(_data.nextOffset!, buffer: buffer, boxed: false) } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.attributes!.count)) + for item in _data.attributes! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt64(_data.attributesHash!, buffer: buffer, boxed: false) + } buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { item.serialize(buffer, true) } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.counters!.count)) + for item in _data.counters! { + item.serialize(buffer, true) + } + } buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.users.count)) for item in _data.users { @@ -1654,40 +2001,59 @@ public extension Api.stats { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .publicForwards(let _data): - return ("publicForwards", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .resaleStarGifts(let _data): + return ("resaleStarGifts", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("attributes", ConstructorParameterDescription(_data.attributes)), ("attributesHash", ConstructorParameterDescription(_data.attributesHash)), ("chats", ConstructorParameterDescription(_data.chats)), ("counters", ConstructorParameterDescription(_data.counters)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { + public static func parse_resaleStarGifts(_ reader: BufferReader) -> ResaleStarGifts? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.PublicForward]? + var _3: [Api.StarGift]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) } var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = parseString(reader) } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + var _5: [Api.StarGiftAttribute]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + } } - var _6: [Api.User]? + var _6: Int64? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt64() + } + var _7: [Api.Chat]? if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _8: [Api.StarGiftAttributeCounter]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttributeCounter.self) + } + } + var _9: [Api.User]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stats.PublicForwards.publicForwards(Cons_publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!)) + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.payments.ResaleStarGifts.resaleStarGifts(Cons_resaleStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, attributes: _5, attributesHash: _6, chats: _7!, counters: _8, users: _9!)) } else { return nil @@ -1695,53 +2061,55 @@ public extension Api.stats { } } } -public extension Api.stats { - enum StoryStats: TypeConstructorDescription { - public class Cons_storyStats: TypeConstructorDescription { - public var viewsGraph: Api.StatsGraph - public var reactionsByEmotionGraph: Api.StatsGraph - public init(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) { - self.viewsGraph = viewsGraph - self.reactionsByEmotionGraph = reactionsByEmotionGraph +public extension Api.payments { + enum SavedInfo: TypeConstructorDescription { + public class Cons_savedInfo: TypeConstructorDescription { + public var flags: Int32 + public var savedInfo: Api.PaymentRequestedInfo? + public init(flags: Int32, savedInfo: Api.PaymentRequestedInfo?) { + self.flags = flags + self.savedInfo = savedInfo } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyStats", [("viewsGraph", ConstructorParameterDescription(self.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph))]) + return ("savedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("savedInfo", ConstructorParameterDescription(self.savedInfo))]) } } - case storyStats(Cons_storyStats) + case savedInfo(Cons_savedInfo) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .storyStats(let _data): + case .savedInfo(let _data): if boxed { - buffer.appendInt32(1355613820) + buffer.appendInt32(-74456004) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.savedInfo!.serialize(buffer, true) } - _data.viewsGraph.serialize(buffer, true) - _data.reactionsByEmotionGraph.serialize(buffer, true) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .storyStats(let _data): - return ("storyStats", [("viewsGraph", ConstructorParameterDescription(_data.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph))]) + case .savedInfo(let _data): + return ("savedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("savedInfo", ConstructorParameterDescription(_data.savedInfo))]) } } - public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { - var _1: Api.StatsGraph? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - var _2: Api.StatsGraph? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + public static func parse_savedInfo(_ reader: BufferReader) -> SavedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.PaymentRequestedInfo? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } let _c1 = _1 != nil - let _c2 = _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { - return Api.stats.StoryStats.storyStats(Cons_storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)) + return Api.payments.SavedInfo.savedInfo(Cons_savedInfo(flags: _1!, savedInfo: _2)) } else { return nil @@ -1749,172 +2117,3 @@ public extension Api.stats { } } } -public extension Api.stickers { - enum SuggestedShortName: TypeConstructorDescription { - public class Cons_suggestedShortName: TypeConstructorDescription { - public var shortName: String - public init(shortName: String) { - self.shortName = shortName - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("suggestedShortName", [("shortName", ConstructorParameterDescription(self.shortName))]) - } - } - case suggestedShortName(Cons_suggestedShortName) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .suggestedShortName(let _data): - if boxed { - buffer.appendInt32(-2046910401) - } - serializeString(_data.shortName, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .suggestedShortName(let _data): - return ("suggestedShortName", [("shortName", ConstructorParameterDescription(_data.shortName))]) - } - } - - public static func parse_suggestedShortName(_ reader: BufferReader) -> SuggestedShortName? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.stickers.SuggestedShortName.suggestedShortName(Cons_suggestedShortName(shortName: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.storage { - enum FileType: TypeConstructorDescription { - case fileGif - case fileJpeg - case fileMov - case fileMp3 - case fileMp4 - case filePartial - case filePdf - case filePng - case fileUnknown - case fileWebp - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .fileGif: - if boxed { - buffer.appendInt32(-891180321) - } - break - case .fileJpeg: - if boxed { - buffer.appendInt32(8322574) - } - break - case .fileMov: - if boxed { - buffer.appendInt32(1258941372) - } - break - case .fileMp3: - if boxed { - buffer.appendInt32(1384777335) - } - break - case .fileMp4: - if boxed { - buffer.appendInt32(-1278304028) - } - break - case .filePartial: - if boxed { - buffer.appendInt32(1086091090) - } - break - case .filePdf: - if boxed { - buffer.appendInt32(-1373745011) - } - break - case .filePng: - if boxed { - buffer.appendInt32(172975040) - } - break - case .fileUnknown: - if boxed { - buffer.appendInt32(-1432995067) - } - break - case .fileWebp: - if boxed { - buffer.appendInt32(276907596) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .fileGif: - return ("fileGif", []) - case .fileJpeg: - return ("fileJpeg", []) - case .fileMov: - return ("fileMov", []) - case .fileMp3: - return ("fileMp3", []) - case .fileMp4: - return ("fileMp4", []) - case .filePartial: - return ("filePartial", []) - case .filePdf: - return ("filePdf", []) - case .filePng: - return ("filePng", []) - case .fileUnknown: - return ("fileUnknown", []) - case .fileWebp: - return ("fileWebp", []) - } - } - - public static func parse_fileGif(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileGif - } - public static func parse_fileJpeg(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileJpeg - } - public static func parse_fileMov(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMov - } - public static func parse_fileMp3(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMp3 - } - public static func parse_fileMp4(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileMp4 - } - public static func parse_filePartial(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePartial - } - public static func parse_filePdf(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePdf - } - public static func parse_filePng(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.filePng - } - public static func parse_fileUnknown(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileUnknown - } - public static func parse_fileWebp(_ reader: BufferReader) -> FileType? { - return Api.storage.FileType.fileWebp - } - } -} diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index e2afa538c0..68cb43d755 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -1,291 +1,42 @@ -public extension Api.stories { - enum Albums: TypeConstructorDescription { - public class Cons_albums: TypeConstructorDescription { - public var hash: Int64 - public var albums: [Api.StoryAlbum] - public init(hash: Int64, albums: [Api.StoryAlbum]) { - self.hash = hash - self.albums = albums - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("albums", [("hash", ConstructorParameterDescription(self.hash)), ("albums", ConstructorParameterDescription(self.albums))]) - } - } - case albums(Cons_albums) - case albumsNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .albums(let _data): - if boxed { - buffer.appendInt32(-1013417414) - } - serializeInt64(_data.hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.albums.count)) - for item in _data.albums { - item.serialize(buffer, true) - } - break - case .albumsNotModified: - if boxed { - buffer.appendInt32(1448008427) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .albums(let _data): - return ("albums", [("hash", ConstructorParameterDescription(_data.hash)), ("albums", ConstructorParameterDescription(_data.albums))]) - case .albumsNotModified: - return ("albumsNotModified", []) - } - } - - public static func parse_albums(_ reader: BufferReader) -> Albums? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StoryAlbum]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryAlbum.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stories.Albums.albums(Cons_albums(hash: _1!, albums: _2!)) - } - else { - return nil - } - } - public static func parse_albumsNotModified(_ reader: BufferReader) -> Albums? { - return Api.stories.Albums.albumsNotModified - } - } -} -public extension Api.stories { - enum AllStories: TypeConstructorDescription { - public class Cons_allStories: TypeConstructorDescription { +public extension Api.payments { + enum SavedStarGifts: TypeConstructorDescription { + public class Cons_savedStarGifts: TypeConstructorDescription { public var flags: Int32 public var count: Int32 - public var state: String - public var peerStories: [Api.PeerStories] - public var chats: [Api.Chat] - public var users: [Api.User] - public var stealthMode: Api.StoriesStealthMode - public init(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) { - self.flags = flags - self.count = count - self.state = state - self.peerStories = peerStories - self.chats = chats - self.users = users - self.stealthMode = stealthMode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("allStories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("state", ConstructorParameterDescription(self.state)), ("peerStories", ConstructorParameterDescription(self.peerStories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("stealthMode", ConstructorParameterDescription(self.stealthMode))]) - } - } - public class Cons_allStoriesNotModified: TypeConstructorDescription { - public var flags: Int32 - public var state: String - public var stealthMode: Api.StoriesStealthMode - public init(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) { - self.flags = flags - self.state = state - self.stealthMode = stealthMode - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("allStoriesNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("state", ConstructorParameterDescription(self.state)), ("stealthMode", ConstructorParameterDescription(self.stealthMode))]) - } - } - case allStories(Cons_allStories) - case allStoriesNotModified(Cons_allStoriesNotModified) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .allStories(let _data): - if boxed { - buffer.appendInt32(1862033025) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - serializeString(_data.state, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.peerStories.count)) - for item in _data.peerStories { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - _data.stealthMode.serialize(buffer, true) - break - case .allStoriesNotModified(let _data): - if boxed { - buffer.appendInt32(291044926) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeString(_data.state, buffer: buffer, boxed: false) - _data.stealthMode.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .allStories(let _data): - return ("allStories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("state", ConstructorParameterDescription(_data.state)), ("peerStories", ConstructorParameterDescription(_data.peerStories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) - case .allStoriesNotModified(let _data): - return ("allStoriesNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("state", ConstructorParameterDescription(_data.state)), ("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) - } - } - - public static func parse_allStories(_ reader: BufferReader) -> AllStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: [Api.PeerStories]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _7: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.stories.AllStories.allStories(Cons_allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!)) - } - else { - return nil - } - } - public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.StoriesStealthMode? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.AllStories.allStoriesNotModified(Cons_allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum CanSendStoryCount: TypeConstructorDescription { - public class Cons_canSendStoryCount: TypeConstructorDescription { - public var countRemains: Int32 - public init(countRemains: Int32) { - self.countRemains = countRemains - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("canSendStoryCount", [("countRemains", ConstructorParameterDescription(self.countRemains))]) - } - } - case canSendStoryCount(Cons_canSendStoryCount) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .canSendStoryCount(let _data): - if boxed { - buffer.appendInt32(-1014513586) - } - serializeInt32(_data.countRemains, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .canSendStoryCount(let _data): - return ("canSendStoryCount", [("countRemains", ConstructorParameterDescription(_data.countRemains))]) - } - } - - public static func parse_canSendStoryCount(_ reader: BufferReader) -> CanSendStoryCount? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.stories.CanSendStoryCount.canSendStoryCount(Cons_canSendStoryCount(countRemains: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum FoundStories: TypeConstructorDescription { - public class Cons_foundStories: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var stories: [Api.FoundStory] + public var chatNotificationsEnabled: Api.Bool? + public var gifts: [Api.SavedStarGift] public var nextOffset: String? public var chats: [Api.Chat] public var users: [Api.User] - public init(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { + public init(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { self.flags = flags self.count = count - self.stories = stories + self.chatNotificationsEnabled = chatNotificationsEnabled + self.gifts = gifts self.nextOffset = nextOffset self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("foundStories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("stories", ConstructorParameterDescription(self.stories)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("savedStarGifts", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("chatNotificationsEnabled", ConstructorParameterDescription(self.chatNotificationsEnabled)), ("gifts", ConstructorParameterDescription(self.gifts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case foundStories(Cons_foundStories) + case savedStarGifts(Cons_savedStarGifts) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .foundStories(let _data): + case .savedStarGifts(let _data): if boxed { - buffer.appendInt32(-488736969) + buffer.appendInt32(-1779201615) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt32(_data.count, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.chatNotificationsEnabled!.serialize(buffer, true) + } buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stories.count)) - for item in _data.stories { + buffer.appendInt32(Int32(_data.gifts.count)) + for item in _data.gifts { item.serialize(buffer, true) } if Int(_data.flags) & Int(1 << 0) != 0 { @@ -307,647 +58,29 @@ public extension Api.stories { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .foundStories(let _data): - return ("foundStories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("stories", ConstructorParameterDescription(_data.stories)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .savedStarGifts(let _data): + return ("savedStarGifts", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("chatNotificationsEnabled", ConstructorParameterDescription(_data.chatNotificationsEnabled)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_foundStories(_ reader: BufferReader) -> FoundStories? { + public static func parse_savedStarGifts(_ reader: BufferReader) -> SavedStarGifts? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? _2 = reader.readInt32() - var _3: [Api.FoundStory]? + var _3: Api.Bool? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Bool + } + } + var _4: [Api.SavedStarGift]? if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FoundStory.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self) } - var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { - _4 = parseString(reader) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.FoundStories.foundStories(Cons_foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum PeerStories: TypeConstructorDescription { - public class Cons_peerStories: TypeConstructorDescription { - public var stories: Api.PeerStories - public var chats: [Api.Chat] - public var users: [Api.User] - public init(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) { - self.stories = stories - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("peerStories", [("stories", ConstructorParameterDescription(self.stories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case peerStories(Cons_peerStories) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .peerStories(let _data): - if boxed { - buffer.appendInt32(-890861720) - } - _data.stories.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .peerStories(let _data): - return ("peerStories", [("stories", ConstructorParameterDescription(_data.stories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { - var _1: Api.PeerStories? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.PeerStories - } - var _2: [Api.Chat]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _3: [Api.User]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.stories.PeerStories.peerStories(Cons_peerStories(stories: _1!, chats: _2!, users: _3!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum Stories: TypeConstructorDescription { - public class Cons_stories: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var stories: [Api.StoryItem] - public var pinnedToTop: [Int32]? - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.count = count - self.stories = stories - self.pinnedToTop = pinnedToTop - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("stories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("stories", ConstructorParameterDescription(self.stories)), ("pinnedToTop", ConstructorParameterDescription(self.pinnedToTop)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case stories(Cons_stories) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .stories(let _data): - if boxed { - buffer.appendInt32(1673780490) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.stories.count)) - for item in _data.stories { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.pinnedToTop!.count)) - for item in _data.pinnedToTop! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .stories(let _data): - return ("stories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("stories", ConstructorParameterDescription(_data.stories)), ("pinnedToTop", ConstructorParameterDescription(_data.pinnedToTop)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_stories(_ reader: BufferReader) -> Stories? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StoryItem]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) - } - var _4: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 { - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.Stories.stories(Cons_stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum StoryReactionsList: TypeConstructorDescription { - public class Cons_storyReactionsList: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var reactions: [Api.StoryReaction] - public var chats: [Api.Chat] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { - self.flags = flags - self.count = count - self.reactions = reactions - self.chats = chats - self.users = users - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyReactionsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("reactions", ConstructorParameterDescription(self.reactions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - case storyReactionsList(Cons_storyReactionsList) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyReactionsList(let _data): - if boxed { - buffer.appendInt32(-1436583780) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.reactions.count)) - for item in _data.reactions { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyReactionsList(let _data): - return ("storyReactionsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) - } - } - - public static func parse_storyReactionsList(_ reader: BufferReader) -> StoryReactionsList? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: [Api.StoryReaction]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryReaction.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: String? - if Int(_1!) & Int(1 << 0) != 0 { - _6 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.stories.StoryReactionsList.storyReactionsList(Cons_storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum StoryViews: TypeConstructorDescription { - public class Cons_storyViews: TypeConstructorDescription { - public var views: [Api.StoryViews] - public var users: [Api.User] - public init(views: [Api.StoryViews], users: [Api.User]) { - self.views = views - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyViews", [("views", ConstructorParameterDescription(self.views)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case storyViews(Cons_storyViews) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyViews(let _data): - if boxed { - buffer.appendInt32(-560009955) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.views.count)) - for item in _data.views { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyViews(let _data): - return ("storyViews", [("views", ConstructorParameterDescription(_data.views)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { - var _1: [Api.StoryViews]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryViews.self) - } - var _2: [Api.User]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.stories.StoryViews.storyViews(Cons_storyViews(views: _1!, users: _2!)) - } - else { - return nil - } - } - } -} -public extension Api.stories { - enum StoryViewsList: TypeConstructorDescription { - public class Cons_storyViewsList: TypeConstructorDescription { - public var flags: Int32 - public var count: Int32 - public var viewsCount: Int32 - public var forwardsCount: Int32 - public var reactionsCount: Int32 - public var views: [Api.StoryView] - public var chats: [Api.Chat] - public var users: [Api.User] - public var nextOffset: String? - public init(flags: Int32, count: Int32, viewsCount: Int32, forwardsCount: Int32, reactionsCount: Int32, views: [Api.StoryView], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { - self.flags = flags - self.count = count - self.viewsCount = viewsCount - self.forwardsCount = forwardsCount - self.reactionsCount = reactionsCount - self.views = views - self.chats = chats - self.users = users - self.nextOffset = nextOffset - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("storyViewsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("viewsCount", ConstructorParameterDescription(self.viewsCount)), ("forwardsCount", ConstructorParameterDescription(self.forwardsCount)), ("reactionsCount", ConstructorParameterDescription(self.reactionsCount)), ("views", ConstructorParameterDescription(self.views)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) - } - } - case storyViewsList(Cons_storyViewsList) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .storyViewsList(let _data): - if boxed { - buffer.appendInt32(1507299269) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.count, buffer: buffer, boxed: false) - serializeInt32(_data.viewsCount, buffer: buffer, boxed: false) - serializeInt32(_data.forwardsCount, buffer: buffer, boxed: false) - serializeInt32(_data.reactionsCount, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.views.count)) - for item in _data.views { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - if Int(_data.flags) & Int(1 << 0) != 0 { - serializeString(_data.nextOffset!, buffer: buffer, boxed: false) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .storyViewsList(let _data): - return ("storyViewsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("viewsCount", ConstructorParameterDescription(_data.viewsCount)), ("forwardsCount", ConstructorParameterDescription(_data.forwardsCount)), ("reactionsCount", ConstructorParameterDescription(_data.reactionsCount)), ("views", ConstructorParameterDescription(_data.views)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) - } - } - - public static func parse_storyViewsList(_ reader: BufferReader) -> StoryViewsList? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - var _6: [Api.StoryView]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryView.self) - } - var _7: [Api.Chat]? - if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _8: [Api.User]? - if let _ = reader.readInt32() { - _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _9: String? - if Int(_1!) & Int(1 << 0) != 0 { - _9 = parseString(reader) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.stories.StoryViewsList.storyViewsList(Cons_storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9)) - } - else { - return nil - } - } - } -} -public extension Api.updates { - indirect enum ChannelDifference: TypeConstructorDescription { - public class Cons_channelDifference: TypeConstructorDescription { - public var flags: Int32 - public var pts: Int32 - public var timeout: Int32? - public var newMessages: [Api.Message] - public var otherUpdates: [Api.Update] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.pts = pts - self.timeout = timeout - self.newMessages = newMessages - self.otherUpdates = otherUpdates - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelDifference", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("timeout", ConstructorParameterDescription(self.timeout)), ("newMessages", ConstructorParameterDescription(self.newMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - public class Cons_channelDifferenceEmpty: TypeConstructorDescription { - public var flags: Int32 - public var pts: Int32 - public var timeout: Int32? - public init(flags: Int32, pts: Int32, timeout: Int32?) { - self.flags = flags - self.pts = pts - self.timeout = timeout - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelDifferenceEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("timeout", ConstructorParameterDescription(self.timeout))]) - } - } - public class Cons_channelDifferenceTooLong: TypeConstructorDescription { - public var flags: Int32 - public var timeout: Int32? - public var dialog: Api.Dialog - public var messages: [Api.Message] - public var chats: [Api.Chat] - public var users: [Api.User] - public init(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { - self.flags = flags - self.timeout = timeout - self.dialog = dialog - self.messages = messages - self.chats = chats - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelDifferenceTooLong", [("flags", ConstructorParameterDescription(self.flags)), ("timeout", ConstructorParameterDescription(self.timeout)), ("dialog", ConstructorParameterDescription(self.dialog)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case channelDifference(Cons_channelDifference) - case channelDifferenceEmpty(Cons_channelDifferenceEmpty) - case channelDifferenceTooLong(Cons_channelDifferenceTooLong) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .channelDifference(let _data): - if boxed { - buffer.appendInt32(543450958) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.timeout!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newMessages.count)) - for item in _data.newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.otherUpdates.count)) - for item in _data.otherUpdates { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - case .channelDifferenceEmpty(let _data): - if boxed { - buffer.appendInt32(1041346555) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.pts, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.timeout!, buffer: buffer, boxed: false) - } - break - case .channelDifferenceTooLong(let _data): - if boxed { - buffer.appendInt32(-1531132162) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - if Int(_data.flags) & Int(1 << 1) != 0 { - serializeInt32(_data.timeout!, buffer: buffer, boxed: false) - } - _data.dialog.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.messages.count)) - for item in _data.messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .channelDifference(let _data): - return ("channelDifference", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("newMessages", ConstructorParameterDescription(_data.newMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - case .channelDifferenceEmpty(let _data): - return ("channelDifferenceEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("timeout", ConstructorParameterDescription(_data.timeout))]) - case .channelDifferenceTooLong(let _data): - return ("channelDifferenceTooLong", [("flags", ConstructorParameterDescription(_data.flags)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("dialog", ConstructorParameterDescription(_data.dialog)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) - } - } - - public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Update]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) } var _6: [Api.Chat]? if let _ = reader.readInt32() { @@ -959,68 +92,13 @@ public extension Api.updates { } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.updates.ChannelDifference.channelDifference(Cons_channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)) - } - else { - return nil - } - } - public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _3 = reader.readInt32() - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.updates.ChannelDifference.channelDifferenceEmpty(Cons_channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)) - } - else { - return nil - } - } - public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - if Int(_1!) & Int(1 << 1) != 0 { - _2 = reader.readInt32() - } - var _3: Api.Dialog? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Dialog - } - var _4: [Api.Message]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _5: [Api.Chat]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _6: [Api.User]? - if let _ = reader.readInt32() { - _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.ChannelDifference.channelDifferenceTooLong(Cons_channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)) + return Api.payments.SavedStarGifts.savedStarGifts(Cons_savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!)) } else { return nil @@ -1028,128 +106,201 @@ public extension Api.updates { } } } -public extension Api.updates { - enum Difference: TypeConstructorDescription { - public class Cons_difference: TypeConstructorDescription { - public var newMessages: [Api.Message] - public var newEncryptedMessages: [Api.EncryptedMessage] - public var otherUpdates: [Api.Update] - public var chats: [Api.Chat] +public extension Api.payments { + enum StarGiftActiveAuctions: TypeConstructorDescription { + public class Cons_starGiftActiveAuctions: TypeConstructorDescription { + public var auctions: [Api.StarGiftActiveAuctionState] public var users: [Api.User] - public var state: Api.updates.State - public init(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) { - self.newMessages = newMessages - self.newEncryptedMessages = newEncryptedMessages - self.otherUpdates = otherUpdates - self.chats = chats + public var chats: [Api.Chat] + public init(auctions: [Api.StarGiftActiveAuctionState], users: [Api.User], chats: [Api.Chat]) { + self.auctions = auctions self.users = users + self.chats = chats + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftActiveAuctions", [("auctions", ConstructorParameterDescription(self.auctions)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) + } + } + case starGiftActiveAuctions(Cons_starGiftActiveAuctions) + case starGiftActiveAuctionsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftActiveAuctions(let _data): + if boxed { + buffer.appendInt32(-1359565892) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.auctions.count)) + for item in _data.auctions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + break + case .starGiftActiveAuctionsNotModified: + if boxed { + buffer.appendInt32(-617358640) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftActiveAuctions(let _data): + return ("starGiftActiveAuctions", [("auctions", ConstructorParameterDescription(_data.auctions)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) + case .starGiftActiveAuctionsNotModified: + return ("starGiftActiveAuctionsNotModified", []) + } + } + + public static func parse_starGiftActiveAuctions(_ reader: BufferReader) -> StarGiftActiveAuctions? { + var _1: [Api.StarGiftActiveAuctionState]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftActiveAuctionState.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctions(Cons_starGiftActiveAuctions(auctions: _1!, users: _2!, chats: _3!)) + } + else { + return nil + } + } + public static func parse_starGiftActiveAuctionsNotModified(_ reader: BufferReader) -> StarGiftActiveAuctions? { + return Api.payments.StarGiftActiveAuctions.starGiftActiveAuctionsNotModified + } + } +} +public extension Api.payments { + enum StarGiftAuctionAcquiredGifts: TypeConstructorDescription { + public class Cons_starGiftAuctionAcquiredGifts: TypeConstructorDescription { + public var gifts: [Api.StarGiftAuctionAcquiredGift] + public var users: [Api.User] + public var chats: [Api.Chat] + public init(gifts: [Api.StarGiftAuctionAcquiredGift], users: [Api.User], chats: [Api.Chat]) { + self.gifts = gifts + self.users = users + self.chats = chats + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftAuctionAcquiredGifts", [("gifts", ConstructorParameterDescription(self.gifts)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) + } + } + case starGiftAuctionAcquiredGifts(Cons_starGiftAuctionAcquiredGifts) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftAuctionAcquiredGifts(let _data): + if boxed { + buffer.appendInt32(2103169520) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.gifts.count)) + for item in _data.gifts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftAuctionAcquiredGifts(let _data): + return ("starGiftAuctionAcquiredGifts", [("gifts", ConstructorParameterDescription(_data.gifts)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) + } + } + + public static func parse_starGiftAuctionAcquiredGifts(_ reader: BufferReader) -> StarGiftAuctionAcquiredGifts? { + var _1: [Api.StarGiftAuctionAcquiredGift]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAuctionAcquiredGift.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.payments.StarGiftAuctionAcquiredGifts.starGiftAuctionAcquiredGifts(Cons_starGiftAuctionAcquiredGifts(gifts: _1!, users: _2!, chats: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum StarGiftAuctionState: TypeConstructorDescription { + public class Cons_starGiftAuctionState: TypeConstructorDescription { + public var gift: Api.StarGift + public var state: Api.StarGiftAuctionState + public var userState: Api.StarGiftAuctionUserState + public var timeout: Int32 + public var users: [Api.User] + public var chats: [Api.Chat] + public init(gift: Api.StarGift, state: Api.StarGiftAuctionState, userState: Api.StarGiftAuctionUserState, timeout: Int32, users: [Api.User], chats: [Api.Chat]) { + self.gift = gift self.state = state - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("difference", [("newMessages", ConstructorParameterDescription(self.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(self.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("state", ConstructorParameterDescription(self.state))]) - } - } - public class Cons_differenceEmpty: TypeConstructorDescription { - public var date: Int32 - public var seq: Int32 - public init(date: Int32, seq: Int32) { - self.date = date - self.seq = seq - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("differenceEmpty", [("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq))]) - } - } - public class Cons_differenceSlice: TypeConstructorDescription { - public var newMessages: [Api.Message] - public var newEncryptedMessages: [Api.EncryptedMessage] - public var otherUpdates: [Api.Update] - public var chats: [Api.Chat] - public var users: [Api.User] - public var intermediateState: Api.updates.State - public init(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State) { - self.newMessages = newMessages - self.newEncryptedMessages = newEncryptedMessages - self.otherUpdates = otherUpdates - self.chats = chats + self.userState = userState + self.timeout = timeout self.users = users - self.intermediateState = intermediateState + self.chats = chats } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("differenceSlice", [("newMessages", ConstructorParameterDescription(self.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(self.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("intermediateState", ConstructorParameterDescription(self.intermediateState))]) + return ("starGiftAuctionState", [("gift", ConstructorParameterDescription(self.gift)), ("state", ConstructorParameterDescription(self.state)), ("userState", ConstructorParameterDescription(self.userState)), ("timeout", ConstructorParameterDescription(self.timeout)), ("users", ConstructorParameterDescription(self.users)), ("chats", ConstructorParameterDescription(self.chats))]) } } - public class Cons_differenceTooLong: TypeConstructorDescription { - public var pts: Int32 - public init(pts: Int32) { - self.pts = pts - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("differenceTooLong", [("pts", ConstructorParameterDescription(self.pts))]) - } - } - case difference(Cons_difference) - case differenceEmpty(Cons_differenceEmpty) - case differenceSlice(Cons_differenceSlice) - case differenceTooLong(Cons_differenceTooLong) + case starGiftAuctionState(Cons_starGiftAuctionState) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .difference(let _data): + case .starGiftAuctionState(let _data): if boxed { - buffer.appendInt32(16030880) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newMessages.count)) - for item in _data.newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newEncryptedMessages.count)) - for item in _data.newEncryptedMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.otherUpdates.count)) - for item in _data.otherUpdates { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.chats.count)) - for item in _data.chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + buffer.appendInt32(1798960364) } + _data.gift.serialize(buffer, true) _data.state.serialize(buffer, true) - break - case .differenceEmpty(let _data): - if boxed { - buffer.appendInt32(1567990072) - } - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.seq, buffer: buffer, boxed: false) - break - case .differenceSlice(let _data): - if boxed { - buffer.appendInt32(-1459938943) - } + _data.userState.serialize(buffer, true) + serializeInt32(_data.timeout, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newMessages.count)) - for item in _data.newMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.newEncryptedMessages.count)) - for item in _data.newEncryptedMessages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.otherUpdates.count)) - for item in _data.otherUpdates { + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { item.serialize(buffer, true) } buffer.appendInt32(481674261) @@ -1157,199 +308,48 @@ public extension Api.updates { for item in _data.chats { item.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) - } - _data.intermediateState.serialize(buffer, true) - break - case .differenceTooLong(let _data): - if boxed { - buffer.appendInt32(1258196845) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .difference(let _data): - return ("difference", [("newMessages", ConstructorParameterDescription(_data.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(_data.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("state", ConstructorParameterDescription(_data.state))]) - case .differenceEmpty(let _data): - return ("differenceEmpty", [("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq))]) - case .differenceSlice(let _data): - return ("differenceSlice", [("newMessages", ConstructorParameterDescription(_data.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(_data.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("intermediateState", ConstructorParameterDescription(_data.intermediateState))]) - case .differenceTooLong(let _data): - return ("differenceTooLong", [("pts", ConstructorParameterDescription(_data.pts))]) + case .starGiftAuctionState(let _data): + return ("starGiftAuctionState", [("gift", ConstructorParameterDescription(_data.gift)), ("state", ConstructorParameterDescription(_data.state)), ("userState", ConstructorParameterDescription(_data.userState)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("users", ConstructorParameterDescription(_data.users)), ("chats", ConstructorParameterDescription(_data.chats))]) } } - public static func parse_difference(_ reader: BufferReader) -> Difference? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.EncryptedMessage]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) - } - var _3: [Api.Update]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: Api.updates.State? + public static func parse_starGiftAuctionState(_ reader: BufferReader) -> StarGiftAuctionState? { + var _1: Api.StarGift? if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.updates.State + _1 = Api.parse(reader, signature: signature) as? Api.StarGift } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.difference(Cons_difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)) - } - else { - return nil - } - } - public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.updates.Difference.differenceEmpty(Cons_differenceEmpty(date: _1!, seq: _2!)) - } - else { - return nil - } - } - public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? { - var _1: [Api.Message]? - if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _2: [Api.EncryptedMessage]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) - } - var _3: [Api.Update]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - var _6: Api.updates.State? + var _2: Api.StarGiftAuctionState? if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.updates.State + _2 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionState } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.updates.Difference.differenceSlice(Cons_differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)) + var _3: Api.StarGiftAuctionUserState? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StarGiftAuctionUserState } - else { - return nil - } - } - public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.updates.Difference.differenceTooLong(Cons_differenceTooLong(pts: _1!)) - } - else { - return nil - } - } - } -} -public extension Api.updates { - enum State: TypeConstructorDescription { - public class Cons_state: TypeConstructorDescription { - public var pts: Int32 - public var qts: Int32 - public var date: Int32 - public var seq: Int32 - public var unreadCount: Int32 - public init(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32) { - self.pts = pts - self.qts = qts - self.date = date - self.seq = seq - self.unreadCount = unreadCount - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("state", [("pts", ConstructorParameterDescription(self.pts)), ("qts", ConstructorParameterDescription(self.qts)), ("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq)), ("unreadCount", ConstructorParameterDescription(self.unreadCount))]) - } - } - case state(Cons_state) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .state(let _data): - if boxed { - buffer.appendInt32(-1519637954) - } - serializeInt32(_data.pts, buffer: buffer, boxed: false) - serializeInt32(_data.qts, buffer: buffer, boxed: false) - serializeInt32(_data.date, buffer: buffer, boxed: false) - serializeInt32(_data.seq, buffer: buffer, boxed: false) - serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .state(let _data): - return ("state", [("pts", ConstructorParameterDescription(_data.pts)), ("qts", ConstructorParameterDescription(_data.qts)), ("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount))]) - } - } - - public static func parse_state(_ reader: BufferReader) -> State? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() var _4: Int32? _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: [Api.Chat]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.updates.State.state(Cons_state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)) + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.payments.StarGiftAuctionState.starGiftAuctionState(Cons_starGiftAuctionState(gift: _1!, state: _2!, userState: _3!, timeout: _4!, users: _5!, chats: _6!)) } else { return nil @@ -1357,135 +357,89 @@ public extension Api.updates { } } } -public extension Api.upload { - enum CdnFile: TypeConstructorDescription { - public class Cons_cdnFile: TypeConstructorDescription { - public var bytes: Buffer - public init(bytes: Buffer) { - self.bytes = bytes +public extension Api.payments { + enum StarGiftCollections: TypeConstructorDescription { + public class Cons_starGiftCollections: TypeConstructorDescription { + public var collections: [Api.StarGiftCollection] + public init(collections: [Api.StarGiftCollection]) { + self.collections = collections } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("cdnFile", [("bytes", ConstructorParameterDescription(self.bytes))]) + return ("starGiftCollections", [("collections", ConstructorParameterDescription(self.collections))]) } } - public class Cons_cdnFileReuploadNeeded: TypeConstructorDescription { - public var requestToken: Buffer - public init(requestToken: Buffer) { - self.requestToken = requestToken - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("cdnFileReuploadNeeded", [("requestToken", ConstructorParameterDescription(self.requestToken))]) - } - } - case cdnFile(Cons_cdnFile) - case cdnFileReuploadNeeded(Cons_cdnFileReuploadNeeded) + case starGiftCollections(Cons_starGiftCollections) + case starGiftCollectionsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .cdnFile(let _data): + case .starGiftCollections(let _data): if boxed { - buffer.appendInt32(-1449145777) + buffer.appendInt32(-1977011469) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.collections.count)) + for item in _data.collections { + item.serialize(buffer, true) } - serializeBytes(_data.bytes, buffer: buffer, boxed: false) break - case .cdnFileReuploadNeeded(let _data): + case .starGiftCollectionsNotModified: if boxed { - buffer.appendInt32(-290921362) + buffer.appendInt32(-1598402793) } - serializeBytes(_data.requestToken, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .cdnFile(let _data): - return ("cdnFile", [("bytes", ConstructorParameterDescription(_data.bytes))]) - case .cdnFileReuploadNeeded(let _data): - return ("cdnFileReuploadNeeded", [("requestToken", ConstructorParameterDescription(_data.requestToken))]) + case .starGiftCollections(let _data): + return ("starGiftCollections", [("collections", ConstructorParameterDescription(_data.collections))]) + case .starGiftCollectionsNotModified: + return ("starGiftCollectionsNotModified", []) } } - public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? { - var _1: Buffer? - _1 = parseBytes(reader) + public static func parse_starGiftCollections(_ reader: BufferReader) -> StarGiftCollections? { + var _1: [Api.StarGiftCollection]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftCollection.self) + } let _c1 = _1 != nil if _c1 { - return Api.upload.CdnFile.cdnFile(Cons_cdnFile(bytes: _1!)) + return Api.payments.StarGiftCollections.starGiftCollections(Cons_starGiftCollections(collections: _1!)) } else { return nil } } - public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? { - var _1: Buffer? - _1 = parseBytes(reader) - let _c1 = _1 != nil - if _c1 { - return Api.upload.CdnFile.cdnFileReuploadNeeded(Cons_cdnFileReuploadNeeded(requestToken: _1!)) - } - else { - return nil - } + public static func parse_starGiftCollectionsNotModified(_ reader: BufferReader) -> StarGiftCollections? { + return Api.payments.StarGiftCollections.starGiftCollectionsNotModified } } } -public extension Api.upload { - enum File: TypeConstructorDescription { - public class Cons_file: TypeConstructorDescription { - public var type: Api.storage.FileType - public var mtime: Int32 - public var bytes: Buffer - public init(type: Api.storage.FileType, mtime: Int32, bytes: Buffer) { - self.type = type - self.mtime = mtime - self.bytes = bytes +public extension Api.payments { + enum StarGiftUpgradeAttributes: TypeConstructorDescription { + public class Cons_starGiftUpgradeAttributes: TypeConstructorDescription { + public var attributes: [Api.StarGiftAttribute] + public init(attributes: [Api.StarGiftAttribute]) { + self.attributes = attributes } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("file", [("type", ConstructorParameterDescription(self.type)), ("mtime", ConstructorParameterDescription(self.mtime)), ("bytes", ConstructorParameterDescription(self.bytes))]) + return ("starGiftUpgradeAttributes", [("attributes", ConstructorParameterDescription(self.attributes))]) } } - public class Cons_fileCdnRedirect: TypeConstructorDescription { - public var dcId: Int32 - public var fileToken: Buffer - public var encryptionKey: Buffer - public var encryptionIv: Buffer - public var fileHashes: [Api.FileHash] - public init(dcId: Int32, fileToken: Buffer, encryptionKey: Buffer, encryptionIv: Buffer, fileHashes: [Api.FileHash]) { - self.dcId = dcId - self.fileToken = fileToken - self.encryptionKey = encryptionKey - self.encryptionIv = encryptionIv - self.fileHashes = fileHashes - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("fileCdnRedirect", [("dcId", ConstructorParameterDescription(self.dcId)), ("fileToken", ConstructorParameterDescription(self.fileToken)), ("encryptionKey", ConstructorParameterDescription(self.encryptionKey)), ("encryptionIv", ConstructorParameterDescription(self.encryptionIv)), ("fileHashes", ConstructorParameterDescription(self.fileHashes))]) - } - } - case file(Cons_file) - case fileCdnRedirect(Cons_fileCdnRedirect) + case starGiftUpgradeAttributes(Cons_starGiftUpgradeAttributes) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .file(let _data): + case .starGiftUpgradeAttributes(let _data): if boxed { - buffer.appendInt32(157948117) + buffer.appendInt32(1187439471) } - _data.type.serialize(buffer, true) - serializeInt32(_data.mtime, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) - break - case .fileCdnRedirect(let _data): - if boxed { - buffer.appendInt32(-242427324) - } - serializeInt32(_data.dcId, buffer: buffer, boxed: false) - serializeBytes(_data.fileToken, buffer: buffer, boxed: false) - serializeBytes(_data.encryptionKey, buffer: buffer, boxed: false) - serializeBytes(_data.encryptionIv, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.fileHashes.count)) - for item in _data.fileHashes { + buffer.appendInt32(Int32(_data.attributes.count)) + for item in _data.attributes { item.serialize(buffer, true) } break @@ -1494,52 +448,93 @@ public extension Api.upload { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .file(let _data): - return ("file", [("type", ConstructorParameterDescription(_data.type)), ("mtime", ConstructorParameterDescription(_data.mtime)), ("bytes", ConstructorParameterDescription(_data.bytes))]) - case .fileCdnRedirect(let _data): - return ("fileCdnRedirect", [("dcId", ConstructorParameterDescription(_data.dcId)), ("fileToken", ConstructorParameterDescription(_data.fileToken)), ("encryptionKey", ConstructorParameterDescription(_data.encryptionKey)), ("encryptionIv", ConstructorParameterDescription(_data.encryptionIv)), ("fileHashes", ConstructorParameterDescription(_data.fileHashes))]) + case .starGiftUpgradeAttributes(let _data): + return ("starGiftUpgradeAttributes", [("attributes", ConstructorParameterDescription(_data.attributes))]) } } - public static func parse_file(_ reader: BufferReader) -> File? { - var _1: Api.storage.FileType? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.storage.FileType + public static func parse_starGiftUpgradeAttributes(_ reader: BufferReader) -> StarGiftUpgradeAttributes? { + var _1: [Api.StarGiftAttribute]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.payments.StarGiftUpgradeAttributes.starGiftUpgradeAttributes(Cons_starGiftUpgradeAttributes(attributes: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum StarGiftUpgradePreview: TypeConstructorDescription { + public class Cons_starGiftUpgradePreview: TypeConstructorDescription { + public var sampleAttributes: [Api.StarGiftAttribute] + public var prices: [Api.StarGiftUpgradePrice] + public var nextPrices: [Api.StarGiftUpgradePrice] + public init(sampleAttributes: [Api.StarGiftAttribute], prices: [Api.StarGiftUpgradePrice], nextPrices: [Api.StarGiftUpgradePrice]) { + self.sampleAttributes = sampleAttributes + self.prices = prices + self.nextPrices = nextPrices + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starGiftUpgradePreview", [("sampleAttributes", ConstructorParameterDescription(self.sampleAttributes)), ("prices", ConstructorParameterDescription(self.prices)), ("nextPrices", ConstructorParameterDescription(self.nextPrices))]) + } + } + case starGiftUpgradePreview(Cons_starGiftUpgradePreview) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starGiftUpgradePreview(let _data): + if boxed { + buffer.appendInt32(1038213101) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.sampleAttributes.count)) + for item in _data.sampleAttributes { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.prices.count)) + for item in _data.prices { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.nextPrices.count)) + for item in _data.nextPrices { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGiftUpgradePreview(let _data): + return ("starGiftUpgradePreview", [("sampleAttributes", ConstructorParameterDescription(_data.sampleAttributes)), ("prices", ConstructorParameterDescription(_data.prices)), ("nextPrices", ConstructorParameterDescription(_data.nextPrices))]) + } + } + + public static func parse_starGiftUpgradePreview(_ reader: BufferReader) -> StarGiftUpgradePreview? { + var _1: [Api.StarGiftAttribute]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self) + } + var _2: [Api.StarGiftUpgradePrice]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftUpgradePrice.self) + } + var _3: [Api.StarGiftUpgradePrice]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftUpgradePrice.self) } - var _2: Int32? - _2 = reader.readInt32() - var _3: Buffer? - _3 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.upload.File.file(Cons_file(type: _1!, mtime: _2!, bytes: _3!)) - } - else { - return nil - } - } - public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Buffer? - _2 = parseBytes(reader) - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Buffer? - _4 = parseBytes(reader) - var _5: [Api.FileHash]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.File.fileCdnRedirect(Cons_fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!)) + return Api.payments.StarGiftUpgradePreview.starGiftUpgradePreview(Cons_starGiftUpgradePreview(sampleAttributes: _1!, prices: _2!, nextPrices: _3!)) } else { return nil @@ -1547,154 +542,43 @@ public extension Api.upload { } } } -public extension Api.upload { - enum WebFile: TypeConstructorDescription { - public class Cons_webFile: TypeConstructorDescription { - public var size: Int32 - public var mimeType: String - public var fileType: Api.storage.FileType - public var mtime: Int32 - public var bytes: Buffer - public init(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) { - self.size = size - self.mimeType = mimeType - self.fileType = fileType - self.mtime = mtime - self.bytes = bytes +public extension Api.payments { + enum StarGiftWithdrawalUrl: TypeConstructorDescription { + public class Cons_starGiftWithdrawalUrl: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("webFile", [("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("fileType", ConstructorParameterDescription(self.fileType)), ("mtime", ConstructorParameterDescription(self.mtime)), ("bytes", ConstructorParameterDescription(self.bytes))]) + return ("starGiftWithdrawalUrl", [("url", ConstructorParameterDescription(self.url))]) } } - case webFile(Cons_webFile) + case starGiftWithdrawalUrl(Cons_starGiftWithdrawalUrl) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .webFile(let _data): + case .starGiftWithdrawalUrl(let _data): if boxed { - buffer.appendInt32(568808380) + buffer.appendInt32(-2069218660) } - serializeInt32(_data.size, buffer: buffer, boxed: false) - serializeString(_data.mimeType, buffer: buffer, boxed: false) - _data.fileType.serialize(buffer, true) - serializeInt32(_data.mtime, buffer: buffer, boxed: false) - serializeBytes(_data.bytes, buffer: buffer, boxed: false) + serializeString(_data.url, buffer: buffer, boxed: false) break } } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .webFile(let _data): - return ("webFile", [("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("fileType", ConstructorParameterDescription(_data.fileType)), ("mtime", ConstructorParameterDescription(_data.mtime)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + case .starGiftWithdrawalUrl(let _data): + return ("starGiftWithdrawalUrl", [("url", ConstructorParameterDescription(_data.url))]) } } - public static func parse_webFile(_ reader: BufferReader) -> WebFile? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) - var _3: Api.storage.FileType? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType - } - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.upload.WebFile.webFile(Cons_webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!)) - } - else { - return nil - } - } - } -} -public extension Api.users { - enum SavedMusic: TypeConstructorDescription { - public class Cons_savedMusic: TypeConstructorDescription { - public var count: Int32 - public var documents: [Api.Document] - public init(count: Int32, documents: [Api.Document]) { - self.count = count - self.documents = documents - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedMusic", [("count", ConstructorParameterDescription(self.count)), ("documents", ConstructorParameterDescription(self.documents))]) - } - } - public class Cons_savedMusicNotModified: TypeConstructorDescription { - public var count: Int32 - public init(count: Int32) { - self.count = count - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("savedMusicNotModified", [("count", ConstructorParameterDescription(self.count))]) - } - } - case savedMusic(Cons_savedMusic) - case savedMusicNotModified(Cons_savedMusicNotModified) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .savedMusic(let _data): - if boxed { - buffer.appendInt32(883094167) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.documents.count)) - for item in _data.documents { - item.serialize(buffer, true) - } - break - case .savedMusicNotModified(let _data): - if boxed { - buffer.appendInt32(-477656412) - } - serializeInt32(_data.count, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .savedMusic(let _data): - return ("savedMusic", [("count", ConstructorParameterDescription(_data.count)), ("documents", ConstructorParameterDescription(_data.documents))]) - case .savedMusicNotModified(let _data): - return ("savedMusicNotModified", [("count", ConstructorParameterDescription(_data.count))]) - } - } - - public static func parse_savedMusic(_ reader: BufferReader) -> SavedMusic? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Document]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.users.SavedMusic.savedMusic(Cons_savedMusic(count: _1!, documents: _2!)) - } - else { - return nil - } - } - public static func parse_savedMusicNotModified(_ reader: BufferReader) -> SavedMusic? { - var _1: Int32? - _1 = reader.readInt32() + public static func parse_starGiftWithdrawalUrl(_ reader: BufferReader) -> StarGiftWithdrawalUrl? { + var _1: String? + _1 = parseString(reader) let _c1 = _1 != nil if _c1 { - return Api.users.SavedMusic.savedMusicNotModified(Cons_savedMusicNotModified(count: _1!)) + return Api.payments.StarGiftWithdrawalUrl.starGiftWithdrawalUrl(Cons_starGiftWithdrawalUrl(url: _1!)) } else { return nil @@ -1702,30 +586,323 @@ public extension Api.users { } } } -public extension Api.users { - enum UserFull: TypeConstructorDescription { - public class Cons_userFull: TypeConstructorDescription { - public var fullUser: Api.UserFull +public extension Api.payments { + enum StarGifts: TypeConstructorDescription { + public class Cons_starGifts: TypeConstructorDescription { + public var hash: Int32 + public var gifts: [Api.StarGift] public var chats: [Api.Chat] public var users: [Api.User] - public init(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) { - self.fullUser = fullUser + public init(hash: Int32, gifts: [Api.StarGift], chats: [Api.Chat], users: [Api.User]) { + self.hash = hash + self.gifts = gifts self.chats = chats self.users = users } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("userFull", [("fullUser", ConstructorParameterDescription(self.fullUser)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + return ("starGifts", [("hash", ConstructorParameterDescription(self.hash)), ("gifts", ConstructorParameterDescription(self.gifts)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } } - case userFull(Cons_userFull) + case starGifts(Cons_starGifts) + case starGiftsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let _data): + case .starGifts(let _data): if boxed { - buffer.appendInt32(997004590) + buffer.appendInt32(785918357) + } + serializeInt32(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.gifts.count)) + for item in _data.gifts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .starGiftsNotModified: + if boxed { + buffer.appendInt32(-1551326360) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starGifts(let _data): + return ("starGifts", [("hash", ConstructorParameterDescription(_data.hash)), ("gifts", ConstructorParameterDescription(_data.gifts)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .starGiftsNotModified: + return ("starGiftsNotModified", []) + } + } + + public static func parse_starGifts(_ reader: BufferReader) -> StarGifts? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.StarGift]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.payments.StarGifts.starGifts(Cons_starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + public static func parse_starGiftsNotModified(_ reader: BufferReader) -> StarGifts? { + return Api.payments.StarGifts.starGiftsNotModified + } + } +} +public extension Api.payments { + enum StarsRevenueAdsAccountUrl: TypeConstructorDescription { + public class Cons_starsRevenueAdsAccountUrl: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsRevenueAdsAccountUrl", [("url", ConstructorParameterDescription(self.url))]) + } + } + case starsRevenueAdsAccountUrl(Cons_starsRevenueAdsAccountUrl) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsRevenueAdsAccountUrl(let _data): + if boxed { + buffer.appendInt32(961445665) + } + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsRevenueAdsAccountUrl(let _data): + return ("starsRevenueAdsAccountUrl", [("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_starsRevenueAdsAccountUrl(_ reader: BufferReader) -> StarsRevenueAdsAccountUrl? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.StarsRevenueAdsAccountUrl.starsRevenueAdsAccountUrl(Cons_starsRevenueAdsAccountUrl(url: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum StarsRevenueStats: TypeConstructorDescription { + public class Cons_starsRevenueStats: TypeConstructorDescription { + public var flags: Int32 + public var topHoursGraph: Api.StatsGraph? + public var revenueGraph: Api.StatsGraph + public var status: Api.StarsRevenueStatus + public var usdRate: Double + public init(flags: Int32, topHoursGraph: Api.StatsGraph?, revenueGraph: Api.StatsGraph, status: Api.StarsRevenueStatus, usdRate: Double) { + self.flags = flags + self.topHoursGraph = topHoursGraph + self.revenueGraph = revenueGraph + self.status = status + self.usdRate = usdRate + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsRevenueStats", [("flags", ConstructorParameterDescription(self.flags)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("revenueGraph", ConstructorParameterDescription(self.revenueGraph)), ("status", ConstructorParameterDescription(self.status)), ("usdRate", ConstructorParameterDescription(self.usdRate))]) + } + } + case starsRevenueStats(Cons_starsRevenueStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsRevenueStats(let _data): + if boxed { + buffer.appendInt32(1814066038) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + _data.topHoursGraph!.serialize(buffer, true) + } + _data.revenueGraph.serialize(buffer, true) + _data.status.serialize(buffer, true) + serializeDouble(_data.usdRate, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsRevenueStats(let _data): + return ("starsRevenueStats", [("flags", ConstructorParameterDescription(_data.flags)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("revenueGraph", ConstructorParameterDescription(_data.revenueGraph)), ("status", ConstructorParameterDescription(_data.status)), ("usdRate", ConstructorParameterDescription(_data.usdRate))]) + } + } + + public static func parse_starsRevenueStats(_ reader: BufferReader) -> StarsRevenueStats? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StatsGraph? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + } + var _3: Api.StatsGraph? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _4: Api.StarsRevenueStatus? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StarsRevenueStatus + } + var _5: Double? + _5 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.payments.StarsRevenueStats.starsRevenueStats(Cons_starsRevenueStats(flags: _1!, topHoursGraph: _2, revenueGraph: _3!, status: _4!, usdRate: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum StarsRevenueWithdrawalUrl: TypeConstructorDescription { + public class Cons_starsRevenueWithdrawalUrl: TypeConstructorDescription { + public var url: String + public init(url: String) { + self.url = url + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsRevenueWithdrawalUrl", [("url", ConstructorParameterDescription(self.url))]) + } + } + case starsRevenueWithdrawalUrl(Cons_starsRevenueWithdrawalUrl) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsRevenueWithdrawalUrl(let _data): + if boxed { + buffer.appendInt32(497778871) + } + serializeString(_data.url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .starsRevenueWithdrawalUrl(let _data): + return ("starsRevenueWithdrawalUrl", [("url", ConstructorParameterDescription(_data.url))]) + } + } + + public static func parse_starsRevenueWithdrawalUrl(_ reader: BufferReader) -> StarsRevenueWithdrawalUrl? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.StarsRevenueWithdrawalUrl.starsRevenueWithdrawalUrl(Cons_starsRevenueWithdrawalUrl(url: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum StarsStatus: TypeConstructorDescription { + public class Cons_starsStatus: TypeConstructorDescription { + public var flags: Int32 + public var balance: Api.StarsAmount + public var subscriptions: [Api.StarsSubscription]? + public var subscriptionsNextOffset: String? + public var subscriptionsMissingBalance: Int64? + public var history: [Api.StarsTransaction]? + public var nextOffset: String? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, balance: Api.StarsAmount, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.balance = balance + self.subscriptions = subscriptions + self.subscriptionsNextOffset = subscriptionsNextOffset + self.subscriptionsMissingBalance = subscriptionsMissingBalance + self.history = history + self.nextOffset = nextOffset + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("starsStatus", [("flags", ConstructorParameterDescription(self.flags)), ("balance", ConstructorParameterDescription(self.balance)), ("subscriptions", ConstructorParameterDescription(self.subscriptions)), ("subscriptionsNextOffset", ConstructorParameterDescription(self.subscriptionsNextOffset)), ("subscriptionsMissingBalance", ConstructorParameterDescription(self.subscriptionsMissingBalance)), ("history", ConstructorParameterDescription(self.history)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case starsStatus(Cons_starsStatus) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .starsStatus(let _data): + if boxed { + buffer.appendInt32(1822222573) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + _data.balance.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.subscriptions!.count)) + for item in _data.subscriptions! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.subscriptionsNextOffset!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt64(_data.subscriptionsMissingBalance!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.history!.count)) + for item in _data.history! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) } - _data.fullUser.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.chats.count)) for item in _data.chats { @@ -1742,15 +919,201 @@ public extension Api.users { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .userFull(let _data): - return ("userFull", [("fullUser", ConstructorParameterDescription(_data.fullUser)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .starsStatus(let _data): + return ("starsStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("balance", ConstructorParameterDescription(_data.balance)), ("subscriptions", ConstructorParameterDescription(_data.subscriptions)), ("subscriptionsNextOffset", ConstructorParameterDescription(_data.subscriptionsNextOffset)), ("subscriptionsMissingBalance", ConstructorParameterDescription(_data.subscriptionsMissingBalance)), ("history", ConstructorParameterDescription(_data.history)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_userFull(_ reader: BufferReader) -> UserFull? { - var _1: Api.UserFull? + public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.StarsAmount? if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.UserFull + _2 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + var _3: [Api.StarsSubscription]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self) + } + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _4 = parseString(reader) + } + var _5: Int64? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt64() + } + var _6: [Api.StarsTransaction]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = parseString(reader) + } + var _8: [Api.Chat]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _9: [Api.User]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.payments.StarsStatus.starsStatus(Cons_starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum SuggestedStarRefBots: TypeConstructorDescription { + public class Cons_suggestedStarRefBots: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var suggestedBots: [Api.StarRefProgram] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, count: Int32, suggestedBots: [Api.StarRefProgram], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.count = count + self.suggestedBots = suggestedBots + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("suggestedStarRefBots", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("suggestedBots", ConstructorParameterDescription(self.suggestedBots)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case suggestedStarRefBots(Cons_suggestedStarRefBots) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedStarRefBots(let _data): + if boxed { + buffer.appendInt32(-1261053863) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.suggestedBots.count)) + for item in _data.suggestedBots { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .suggestedStarRefBots(let _data): + return ("suggestedStarRefBots", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("suggestedBots", ConstructorParameterDescription(_data.suggestedBots)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + } + } + + public static func parse_suggestedStarRefBots(_ reader: BufferReader) -> SuggestedStarRefBots? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StarRefProgram]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarRefProgram.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.payments.SuggestedStarRefBots.suggestedStarRefBots(Cons_suggestedStarRefBots(flags: _1!, count: _2!, suggestedBots: _3!, users: _4!, nextOffset: _5)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum UniqueStarGift: TypeConstructorDescription { + public class Cons_uniqueStarGift: TypeConstructorDescription { + public var gift: Api.StarGift + public var chats: [Api.Chat] + public var users: [Api.User] + public init(gift: Api.StarGift, chats: [Api.Chat], users: [Api.User]) { + self.gift = gift + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("uniqueStarGift", [("gift", ConstructorParameterDescription(self.gift)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case uniqueStarGift(Cons_uniqueStarGift) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .uniqueStarGift(let _data): + if boxed { + buffer.appendInt32(1097619176) + } + _data.gift.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .uniqueStarGift(let _data): + return ("uniqueStarGift", [("gift", ConstructorParameterDescription(_data.gift)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_uniqueStarGift(_ reader: BufferReader) -> UniqueStarGift? { + var _1: Api.StarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarGift } var _2: [Api.Chat]? if let _ = reader.readInt32() { @@ -1764,7 +1127,7 @@ public extension Api.users { let _c2 = _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { - return Api.users.UserFull.userFull(Cons_userFull(fullUser: _1!, chats: _2!, users: _3!)) + return Api.payments.UniqueStarGift.uniqueStarGift(Cons_uniqueStarGift(gift: _1!, chats: _2!, users: _3!)) } else { return nil @@ -1772,48 +1135,303 @@ public extension Api.users { } } } -public extension Api.users { - enum Users: TypeConstructorDescription { - public class Cons_users: TypeConstructorDescription { - public var users: [Api.User] - public init(users: [Api.User]) { - self.users = users +public extension Api.payments { + enum UniqueStarGiftValueInfo: TypeConstructorDescription { + public class Cons_uniqueStarGiftValueInfo: TypeConstructorDescription { + public var flags: Int32 + public var currency: String + public var value: Int64 + public var initialSaleDate: Int32 + public var initialSaleStars: Int64 + public var initialSalePrice: Int64 + public var lastSaleDate: Int32? + public var lastSalePrice: Int64? + public var floorPrice: Int64? + public var averagePrice: Int64? + public var listedCount: Int32? + public var fragmentListedCount: Int32? + public var fragmentListedUrl: String? + public init(flags: Int32, currency: String, value: Int64, initialSaleDate: Int32, initialSaleStars: Int64, initialSalePrice: Int64, lastSaleDate: Int32?, lastSalePrice: Int64?, floorPrice: Int64?, averagePrice: Int64?, listedCount: Int32?, fragmentListedCount: Int32?, fragmentListedUrl: String?) { + self.flags = flags + self.currency = currency + self.value = value + self.initialSaleDate = initialSaleDate + self.initialSaleStars = initialSaleStars + self.initialSalePrice = initialSalePrice + self.lastSaleDate = lastSaleDate + self.lastSalePrice = lastSalePrice + self.floorPrice = floorPrice + self.averagePrice = averagePrice + self.listedCount = listedCount + self.fragmentListedCount = fragmentListedCount + self.fragmentListedUrl = fragmentListedUrl } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("users", [("users", ConstructorParameterDescription(self.users))]) + return ("uniqueStarGiftValueInfo", [("flags", ConstructorParameterDescription(self.flags)), ("currency", ConstructorParameterDescription(self.currency)), ("value", ConstructorParameterDescription(self.value)), ("initialSaleDate", ConstructorParameterDescription(self.initialSaleDate)), ("initialSaleStars", ConstructorParameterDescription(self.initialSaleStars)), ("initialSalePrice", ConstructorParameterDescription(self.initialSalePrice)), ("lastSaleDate", ConstructorParameterDescription(self.lastSaleDate)), ("lastSalePrice", ConstructorParameterDescription(self.lastSalePrice)), ("floorPrice", ConstructorParameterDescription(self.floorPrice)), ("averagePrice", ConstructorParameterDescription(self.averagePrice)), ("listedCount", ConstructorParameterDescription(self.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(self.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(self.fragmentListedUrl))]) } } - public class Cons_usersSlice: TypeConstructorDescription { - public var count: Int32 - public var users: [Api.User] - public init(count: Int32, users: [Api.User]) { - self.count = count - self.users = users - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("usersSlice", [("count", ConstructorParameterDescription(self.count)), ("users", ConstructorParameterDescription(self.users))]) - } - } - case users(Cons_users) - case usersSlice(Cons_usersSlice) + case uniqueStarGiftValueInfo(Cons_uniqueStarGiftValueInfo) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .users(let _data): + case .uniqueStarGiftValueInfo(let _data): if boxed { - buffer.appendInt32(1658259128) + buffer.appendInt32(1362093126) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(_data.users.count)) - for item in _data.users { - item.serialize(buffer, true) + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.currency, buffer: buffer, boxed: false) + serializeInt64(_data.value, buffer: buffer, boxed: false) + serializeInt32(_data.initialSaleDate, buffer: buffer, boxed: false) + serializeInt64(_data.initialSaleStars, buffer: buffer, boxed: false) + serializeInt64(_data.initialSalePrice, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.lastSaleDate!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt64(_data.lastSalePrice!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeInt64(_data.floorPrice!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 3) != 0 { + serializeInt64(_data.averagePrice!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.listedCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeInt32(_data.fragmentListedCount!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 5) != 0 { + serializeString(_data.fragmentListedUrl!, buffer: buffer, boxed: false) } break - case .usersSlice(let _data): + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .uniqueStarGiftValueInfo(let _data): + return ("uniqueStarGiftValueInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("currency", ConstructorParameterDescription(_data.currency)), ("value", ConstructorParameterDescription(_data.value)), ("initialSaleDate", ConstructorParameterDescription(_data.initialSaleDate)), ("initialSaleStars", ConstructorParameterDescription(_data.initialSaleStars)), ("initialSalePrice", ConstructorParameterDescription(_data.initialSalePrice)), ("lastSaleDate", ConstructorParameterDescription(_data.lastSaleDate)), ("lastSalePrice", ConstructorParameterDescription(_data.lastSalePrice)), ("floorPrice", ConstructorParameterDescription(_data.floorPrice)), ("averagePrice", ConstructorParameterDescription(_data.averagePrice)), ("listedCount", ConstructorParameterDescription(_data.listedCount)), ("fragmentListedCount", ConstructorParameterDescription(_data.fragmentListedCount)), ("fragmentListedUrl", ConstructorParameterDescription(_data.fragmentListedUrl))]) + } + } + + public static func parse_uniqueStarGiftValueInfo(_ reader: BufferReader) -> UniqueStarGiftValueInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int64? + _5 = reader.readInt64() + var _6: Int64? + _6 = reader.readInt64() + var _7: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _7 = reader.readInt32() + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _9 = reader.readInt64() + } + var _10: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _10 = reader.readInt64() + } + var _11: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _11 = reader.readInt32() + } + var _12: Int32? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _12 = reader.readInt32() + } + var _13: String? + if Int(_1 ?? 0) & Int(1 << 5) != 0 { + _13 = parseString(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _13 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { + return Api.payments.UniqueStarGiftValueInfo.uniqueStarGiftValueInfo(Cons_uniqueStarGiftValueInfo(flags: _1!, currency: _2!, value: _3!, initialSaleDate: _4!, initialSaleStars: _5!, initialSalePrice: _6!, lastSaleDate: _7, lastSalePrice: _8, floorPrice: _9, averagePrice: _10, listedCount: _11, fragmentListedCount: _12, fragmentListedUrl: _13)) + } + else { + return nil + } + } + } +} +public extension Api.payments { + enum ValidatedRequestedInfo: TypeConstructorDescription { + public class Cons_validatedRequestedInfo: TypeConstructorDescription { + public var flags: Int32 + public var id: String? + public var shippingOptions: [Api.ShippingOption]? + public init(flags: Int32, id: String?, shippingOptions: [Api.ShippingOption]?) { + self.flags = flags + self.id = id + self.shippingOptions = shippingOptions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("validatedRequestedInfo", [("flags", ConstructorParameterDescription(self.flags)), ("id", ConstructorParameterDescription(self.id)), ("shippingOptions", ConstructorParameterDescription(self.shippingOptions))]) + } + } + case validatedRequestedInfo(Cons_validatedRequestedInfo) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .validatedRequestedInfo(let _data): if boxed { - buffer.appendInt32(828000628) + buffer.appendInt32(-784000893) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.id!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.shippingOptions!.count)) + for item in _data.shippingOptions! { + item.serialize(buffer, true) + } + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .validatedRequestedInfo(let _data): + return ("validatedRequestedInfo", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("shippingOptions", ConstructorParameterDescription(_data.shippingOptions))]) + } + } + + public static func parse_validatedRequestedInfo(_ reader: BufferReader) -> ValidatedRequestedInfo? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _2 = parseString(reader) + } + var _3: [Api.ShippingOption]? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ShippingOption.self) + } + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.payments.ValidatedRequestedInfo.validatedRequestedInfo(Cons_validatedRequestedInfo(flags: _1!, id: _2, shippingOptions: _3)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum ExportedGroupCallInvite: TypeConstructorDescription { + public class Cons_exportedGroupCallInvite: TypeConstructorDescription { + public var link: String + public init(link: String) { + self.link = link + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("exportedGroupCallInvite", [("link", ConstructorParameterDescription(self.link))]) + } + } + case exportedGroupCallInvite(Cons_exportedGroupCallInvite) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .exportedGroupCallInvite(let _data): + if boxed { + buffer.appendInt32(541839704) + } + serializeString(_data.link, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .exportedGroupCallInvite(let _data): + return ("exportedGroupCallInvite", [("link", ConstructorParameterDescription(_data.link))]) + } + } + + public static func parse_exportedGroupCallInvite(_ reader: BufferReader) -> ExportedGroupCallInvite? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.phone.ExportedGroupCallInvite.exportedGroupCallInvite(Cons_exportedGroupCallInvite(link: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum GroupCall: TypeConstructorDescription { + public class Cons_groupCall: TypeConstructorDescription { + public var call: Api.GroupCall + public var participants: [Api.GroupCallParticipant] + public var participantsNextOffset: String + public var chats: [Api.Chat] + public var users: [Api.User] + public init(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) { + self.call = call + self.participants = participants + self.participantsNextOffset = participantsNextOffset + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("groupCall", [("call", ConstructorParameterDescription(self.call)), ("participants", ConstructorParameterDescription(self.participants)), ("participantsNextOffset", ConstructorParameterDescription(self.participantsNextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case groupCall(Cons_groupCall) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCall(let _data): + if boxed { + buffer.appendInt32(-1636664659) + } + _data.call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.participants.count)) + for item in _data.participants { + item.serialize(buffer, true) + } + serializeString(_data.participantsNextOffset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) } - serializeInt32(_data.count, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(_data.users.count)) for item in _data.users { @@ -1825,37 +1443,698 @@ public extension Api.users { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { - case .users(let _data): - return ("users", [("users", ConstructorParameterDescription(_data.users))]) - case .usersSlice(let _data): - return ("usersSlice", [("count", ConstructorParameterDescription(_data.count)), ("users", ConstructorParameterDescription(_data.users))]) + case .groupCall(let _data): + return ("groupCall", [("call", ConstructorParameterDescription(_data.call)), ("participants", ConstructorParameterDescription(_data.participants)), ("participantsNextOffset", ConstructorParameterDescription(_data.participantsNextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } } - public static func parse_users(_ reader: BufferReader) -> Users? { - var _1: [Api.User]? + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + var _2: [Api.GroupCallParticipant]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) } - let _c1 = _1 != nil - if _c1 { - return Api.users.Users.users(Cons_users(users: _1!)) - } - else { - return nil - } - } - public static func parse_usersSlice(_ reader: BufferReader) -> Users? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.User]? + var _3: String? + _3 = parseString(reader) + var _4: [Api.Chat]? if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.users.Users.usersSlice(Cons_usersSlice(count: _1!, users: _2!)) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.phone.GroupCall.groupCall(Cons_groupCall(call: _1!, participants: _2!, participantsNextOffset: _3!, chats: _4!, users: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum GroupCallStars: TypeConstructorDescription { + public class Cons_groupCallStars: TypeConstructorDescription { + public var totalStars: Int64 + public var topDonors: [Api.GroupCallDonor] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(totalStars: Int64, topDonors: [Api.GroupCallDonor], chats: [Api.Chat], users: [Api.User]) { + self.totalStars = totalStars + self.topDonors = topDonors + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("groupCallStars", [("totalStars", ConstructorParameterDescription(self.totalStars)), ("topDonors", ConstructorParameterDescription(self.topDonors)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case groupCallStars(Cons_groupCallStars) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStars(let _data): + if boxed { + buffer.appendInt32(-1658995418) + } + serializeInt64(_data.totalStars, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topDonors.count)) + for item in _data.topDonors { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .groupCallStars(let _data): + return ("groupCallStars", [("totalStars", ConstructorParameterDescription(_data.totalStars)), ("topDonors", ConstructorParameterDescription(_data.topDonors)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_groupCallStars(_ reader: BufferReader) -> GroupCallStars? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.GroupCallDonor]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallDonor.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.phone.GroupCallStars.groupCallStars(Cons_groupCallStars(totalStars: _1!, topDonors: _2!, chats: _3!, users: _4!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum GroupCallStreamChannels: TypeConstructorDescription { + public class Cons_groupCallStreamChannels: TypeConstructorDescription { + public var channels: [Api.GroupCallStreamChannel] + public init(channels: [Api.GroupCallStreamChannel]) { + self.channels = channels + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("groupCallStreamChannels", [("channels", ConstructorParameterDescription(self.channels))]) + } + } + case groupCallStreamChannels(Cons_groupCallStreamChannels) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamChannels(let _data): + if boxed { + buffer.appendInt32(-790330702) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.channels.count)) + for item in _data.channels { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .groupCallStreamChannels(let _data): + return ("groupCallStreamChannels", [("channels", ConstructorParameterDescription(_data.channels))]) + } + } + + public static func parse_groupCallStreamChannels(_ reader: BufferReader) -> GroupCallStreamChannels? { + var _1: [Api.GroupCallStreamChannel]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallStreamChannel.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.phone.GroupCallStreamChannels.groupCallStreamChannels(Cons_groupCallStreamChannels(channels: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum GroupCallStreamRtmpUrl: TypeConstructorDescription { + public class Cons_groupCallStreamRtmpUrl: TypeConstructorDescription { + public var url: String + public var key: String + public init(url: String, key: String) { + self.url = url + self.key = key + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("groupCallStreamRtmpUrl", [("url", ConstructorParameterDescription(self.url)), ("key", ConstructorParameterDescription(self.key))]) + } + } + case groupCallStreamRtmpUrl(Cons_groupCallStreamRtmpUrl) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamRtmpUrl(let _data): + if boxed { + buffer.appendInt32(767505458) + } + serializeString(_data.url, buffer: buffer, boxed: false) + serializeString(_data.key, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .groupCallStreamRtmpUrl(let _data): + return ("groupCallStreamRtmpUrl", [("url", ConstructorParameterDescription(_data.url)), ("key", ConstructorParameterDescription(_data.key))]) + } + } + + public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(Cons_groupCallStreamRtmpUrl(url: _1!, key: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum GroupParticipants: TypeConstructorDescription { + public class Cons_groupParticipants: TypeConstructorDescription { + public var count: Int32 + public var participants: [Api.GroupCallParticipant] + public var nextOffset: String + public var chats: [Api.Chat] + public var users: [Api.User] + public var version: Int32 + public init(count: Int32, participants: [Api.GroupCallParticipant], nextOffset: String, chats: [Api.Chat], users: [Api.User], version: Int32) { + self.count = count + self.participants = participants + self.nextOffset = nextOffset + self.chats = chats + self.users = users + self.version = version + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("groupParticipants", [("count", ConstructorParameterDescription(self.count)), ("participants", ConstructorParameterDescription(self.participants)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("version", ConstructorParameterDescription(self.version))]) + } + } + case groupParticipants(Cons_groupParticipants) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupParticipants(let _data): + if boxed { + buffer.appendInt32(-193506890) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.participants.count)) + for item in _data.participants { + item.serialize(buffer, true) + } + serializeString(_data.nextOffset, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + serializeInt32(_data.version, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .groupParticipants(let _data): + return ("groupParticipants", [("count", ConstructorParameterDescription(_data.count)), ("participants", ConstructorParameterDescription(_data.participants)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("version", ConstructorParameterDescription(_data.version))]) + } + } + + public static func parse_groupParticipants(_ reader: BufferReader) -> GroupParticipants? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: String? + _3 = parseString(reader) + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.phone.GroupParticipants.groupParticipants(Cons_groupParticipants(count: _1!, participants: _2!, nextOffset: _3!, chats: _4!, users: _5!, version: _6!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum JoinAsPeers: TypeConstructorDescription { + public class Cons_joinAsPeers: TypeConstructorDescription { + public var peers: [Api.Peer] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) { + self.peers = peers + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("joinAsPeers", [("peers", ConstructorParameterDescription(self.peers)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case joinAsPeers(Cons_joinAsPeers) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .joinAsPeers(let _data): + if boxed { + buffer.appendInt32(-1343921601) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peers.count)) + for item in _data.peers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .joinAsPeers(let _data): + return ("joinAsPeers", [("peers", ConstructorParameterDescription(_data.peers)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? { + var _1: [Api.Peer]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.phone.JoinAsPeers.joinAsPeers(Cons_joinAsPeers(peers: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.phone { + enum PhoneCall: TypeConstructorDescription { + public class Cons_phoneCall: TypeConstructorDescription { + public var phoneCall: Api.PhoneCall + public var users: [Api.User] + public init(phoneCall: Api.PhoneCall, users: [Api.User]) { + self.phoneCall = phoneCall + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("phoneCall", [("phoneCall", ConstructorParameterDescription(self.phoneCall)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case phoneCall(Cons_phoneCall) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .phoneCall(let _data): + if boxed { + buffer.appendInt32(-326966976) + } + _data.phoneCall.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .phoneCall(let _data): + return ("phoneCall", [("phoneCall", ConstructorParameterDescription(_data.phoneCall)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? { + var _1: Api.PhoneCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.PhoneCall + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.phone.PhoneCall.phoneCall(Cons_phoneCall(phoneCall: _1!, users: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.photos { + enum Photo: TypeConstructorDescription { + public class Cons_photo: TypeConstructorDescription { + public var photo: Api.Photo + public var users: [Api.User] + public init(photo: Api.Photo, users: [Api.User]) { + self.photo = photo + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photo", [("photo", ConstructorParameterDescription(self.photo)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case photo(Cons_photo) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photo(let _data): + if boxed { + buffer.appendInt32(539045032) + } + _data.photo.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .photo(let _data): + return ("photo", [("photo", ConstructorParameterDescription(_data.photo)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_photo(_ reader: BufferReader) -> Photo? { + var _1: Api.Photo? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Photo + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.photos.Photo.photo(Cons_photo(photo: _1!, users: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.photos { + enum Photos: TypeConstructorDescription { + public class Cons_photos: TypeConstructorDescription { + public var photos: [Api.Photo] + public var users: [Api.User] + public init(photos: [Api.Photo], users: [Api.User]) { + self.photos = photos + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photos", [("photos", ConstructorParameterDescription(self.photos)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_photosSlice: TypeConstructorDescription { + public var count: Int32 + public var photos: [Api.Photo] + public var users: [Api.User] + public init(count: Int32, photos: [Api.Photo], users: [Api.User]) { + self.count = count + self.photos = photos + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("photosSlice", [("count", ConstructorParameterDescription(self.count)), ("photos", ConstructorParameterDescription(self.photos)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case photos(Cons_photos) + case photosSlice(Cons_photosSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photos(let _data): + if boxed { + buffer.appendInt32(-1916114267) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos.count)) + for item in _data.photos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .photosSlice(let _data): + if boxed { + buffer.appendInt32(352657236) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.photos.count)) + for item in _data.photos { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .photos(let _data): + return ("photos", [("photos", ConstructorParameterDescription(_data.photos)), ("users", ConstructorParameterDescription(_data.users))]) + case .photosSlice(let _data): + return ("photosSlice", [("count", ConstructorParameterDescription(_data.count)), ("photos", ConstructorParameterDescription(_data.photos)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_photos(_ reader: BufferReader) -> Photos? { + var _1: [Api.Photo]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) + } + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.photos.Photos.photos(Cons_photos(photos: _1!, users: _2!)) + } + else { + return nil + } + } + public static func parse_photosSlice(_ reader: BufferReader) -> Photos? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Photo]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Photo.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.photos.Photos.photosSlice(Cons_photosSlice(count: _1!, photos: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.premium { + enum BoostsList: TypeConstructorDescription { + public class Cons_boostsList: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var boosts: [Api.Boost] + public var nextOffset: String? + public var users: [Api.User] + public init(flags: Int32, count: Int32, boosts: [Api.Boost], nextOffset: String?, users: [Api.User]) { + self.flags = flags + self.count = count + self.boosts = boosts + self.nextOffset = nextOffset + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("boostsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("boosts", ConstructorParameterDescription(self.boosts)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case boostsList(Cons_boostsList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boostsList(let _data): + if boxed { + buffer.appendInt32(-2030542532) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.boosts.count)) + for item in _data.boosts { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .boostsList(let _data): + return ("boostsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_boostsList(_ reader: BufferReader) -> BoostsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.Boost]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Boost.self) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.premium.BoostsList.boostsList(Cons_boostsList(flags: _1!, count: _2!, boosts: _3!, nextOffset: _4, users: _5!)) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 1b2814cb3a..340a6ebf9d 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -386,18 +386,18 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = reader.readInt32() } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.ChannelParticipant.channelParticipant(Cons_channelParticipant(flags: _1!, userId: _2!, date: _3!, subscriptionUntilDate: _4, rank: _5)) } @@ -411,7 +411,7 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: Int64? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _3 = reader.readInt64() } var _4: Int64? @@ -423,16 +423,16 @@ public extension Api { _6 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights } var _7: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _7 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.ChannelParticipant.channelParticipantAdmin(Cons_channelParticipantAdmin(flags: _1!, userId: _2!, inviterId: _3, promotedBy: _4!, date: _5!, adminRights: _6!, rank: _7)) } @@ -456,7 +456,7 @@ public extension Api { _5 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights } var _6: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _6 = parseString(reader) } let _c1 = _1 != nil @@ -464,7 +464,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.ChannelParticipant.channelParticipantBanned(Cons_channelParticipantBanned(flags: _1!, peer: _2!, kickedBy: _3!, date: _4!, bannedRights: _5!, rank: _6)) } @@ -482,13 +482,13 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights } var _4: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.ChannelParticipant.channelParticipantCreator(Cons_channelParticipantCreator(flags: _1!, userId: _2!, adminRights: _3!, rank: _4)) } @@ -519,19 +519,19 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _5 = reader.readInt32() } var _6: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _6 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.ChannelParticipant.channelParticipantSelf(Cons_channelParticipantSelf(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, subscriptionUntilDate: _5, rank: _6)) } @@ -721,16 +721,16 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _2 = parseString(reader) } var _3: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _3 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.ChannelParticipantsFilter.channelParticipantsMentions(Cons_channelParticipantsMentions(flags: _1!, q: _2, topMsgId: _3)) } @@ -1023,13 +1023,13 @@ public extension Api { var _3: Int64? _3 = reader.readInt64() var _4: Int64? - if Int(_1!) & Int(1 << 13) != 0 { + if Int(_1 ?? 0) & Int(1 << 13) != 0 { _4 = reader.readInt64() } var _5: String? _5 = parseString(reader) var _6: String? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { _6 = parseString(reader) } var _7: Api.ChatPhoto? @@ -1039,106 +1039,106 @@ public extension Api { var _8: Int32? _8 = reader.readInt32() var _9: [Api.RestrictionReason]? - if Int(_1!) & Int(1 << 9) != 0 { + if Int(_1 ?? 0) & Int(1 << 9) != 0 { if let _ = reader.readInt32() { _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.RestrictionReason.self) } } var _10: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 14) != 0 { + if Int(_1 ?? 0) & Int(1 << 14) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights } } var _11: Api.ChatBannedRights? - if Int(_1!) & Int(1 << 15) != 0 { + if Int(_1 ?? 0) & Int(1 << 15) != 0 { if let signature = reader.readInt32() { _11 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights } } var _12: Api.ChatBannedRights? - if Int(_1!) & Int(1 << 18) != 0 { + if Int(_1 ?? 0) & Int(1 << 18) != 0 { if let signature = reader.readInt32() { _12 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights } } var _13: Int32? - if Int(_1!) & Int(1 << 17) != 0 { + if Int(_1 ?? 0) & Int(1 << 17) != 0 { _13 = reader.readInt32() } var _14: [Api.Username]? - if Int(_2!) & Int(1 << 0) != 0 { + if Int(_2 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self) } } var _15: Api.RecentStory? - if Int(_2!) & Int(1 << 4) != 0 { + if Int(_2 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _15 = Api.parse(reader, signature: signature) as? Api.RecentStory } } var _16: Api.PeerColor? - if Int(_2!) & Int(1 << 7) != 0 { + if Int(_2 ?? 0) & Int(1 << 7) != 0 { if let signature = reader.readInt32() { _16 = Api.parse(reader, signature: signature) as? Api.PeerColor } } var _17: Api.PeerColor? - if Int(_2!) & Int(1 << 8) != 0 { + if Int(_2 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _17 = Api.parse(reader, signature: signature) as? Api.PeerColor } } var _18: Api.EmojiStatus? - if Int(_2!) & Int(1 << 9) != 0 { + if Int(_2 ?? 0) & Int(1 << 9) != 0 { if let signature = reader.readInt32() { _18 = Api.parse(reader, signature: signature) as? Api.EmojiStatus } } var _19: Int32? - if Int(_2!) & Int(1 << 10) != 0 { + if Int(_2 ?? 0) & Int(1 << 10) != 0 { _19 = reader.readInt32() } var _20: Int32? - if Int(_2!) & Int(1 << 11) != 0 { + if Int(_2 ?? 0) & Int(1 << 11) != 0 { _20 = reader.readInt32() } var _21: Int64? - if Int(_2!) & Int(1 << 13) != 0 { + if Int(_2 ?? 0) & Int(1 << 13) != 0 { _21 = reader.readInt64() } var _22: Int64? - if Int(_2!) & Int(1 << 14) != 0 { + if Int(_2 ?? 0) & Int(1 << 14) != 0 { _22 = reader.readInt64() } var _23: Int64? - if Int(_2!) & Int(1 << 18) != 0 { + if Int(_2 ?? 0) & Int(1 << 18) != 0 { _23 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 13) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 15) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 18) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 17) == 0) || _13 != nil - let _c14 = (Int(_2!) & Int(1 << 0) == 0) || _14 != nil - let _c15 = (Int(_2!) & Int(1 << 4) == 0) || _15 != nil - let _c16 = (Int(_2!) & Int(1 << 7) == 0) || _16 != nil - let _c17 = (Int(_2!) & Int(1 << 8) == 0) || _17 != nil - let _c18 = (Int(_2!) & Int(1 << 9) == 0) || _18 != nil - let _c19 = (Int(_2!) & Int(1 << 10) == 0) || _19 != nil - let _c20 = (Int(_2!) & Int(1 << 11) == 0) || _20 != nil - let _c21 = (Int(_2!) & Int(1 << 13) == 0) || _21 != nil - let _c22 = (Int(_2!) & Int(1 << 14) == 0) || _22 != nil - let _c23 = (Int(_2!) & Int(1 << 18) == 0) || _23 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _13 != nil + let _c14 = (Int(_2 ?? 0) & Int(1 << 0) == 0) || _14 != nil + let _c15 = (Int(_2 ?? 0) & Int(1 << 4) == 0) || _15 != nil + let _c16 = (Int(_2 ?? 0) & Int(1 << 7) == 0) || _16 != nil + let _c17 = (Int(_2 ?? 0) & Int(1 << 8) == 0) || _17 != nil + let _c18 = (Int(_2 ?? 0) & Int(1 << 9) == 0) || _18 != nil + let _c19 = (Int(_2 ?? 0) & Int(1 << 10) == 0) || _19 != nil + let _c20 = (Int(_2 ?? 0) & Int(1 << 11) == 0) || _20 != nil + let _c21 = (Int(_2 ?? 0) & Int(1 << 13) == 0) || _21 != nil + let _c22 = (Int(_2 ?? 0) & Int(1 << 14) == 0) || _22 != nil + let _c23 = (Int(_2 ?? 0) & Int(1 << 18) == 0) || _23 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 { return Api.Chat.channel(Cons_channel(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, title: _5!, username: _6, photo: _7!, date: _8!, restrictionReason: _9, adminRights: _10, bannedRights: _11, defaultBannedRights: _12, participantsCount: _13, usernames: _14, storiesMaxId: _15, color: _16, profileColor: _17, emojiStatus: _18, level: _19, subscriptionUntilDate: _20, botVerificationIcon: _21, sendPaidMessagesStars: _22, linkedMonoforumId: _23)) } @@ -1156,14 +1156,14 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: Int32? - if Int(_1!) & Int(1 << 16) != 0 { + if Int(_1 ?? 0) & Int(1 << 16) != 0 { _5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 16) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.Chat.channelForbidden(Cons_channelForbidden(flags: _1!, id: _2!, accessHash: _3!, title: _4!, untilDate: _5)) } @@ -1189,19 +1189,19 @@ public extension Api { var _7: Int32? _7 = reader.readInt32() var _8: Api.InputChannel? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.InputChannel } } var _9: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 14) != 0 { + if Int(_1 ?? 0) & Int(1 << 14) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights } } var _10: Api.ChatBannedRights? - if Int(_1!) & Int(1 << 18) != 0 { + if Int(_1 ?? 0) & Int(1 << 18) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.ChatBannedRights } @@ -1213,9 +1213,9 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 6) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 14) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 18) == 0) || _10 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.Chat.chat(Cons_chat(flags: _1!, id: _2!, title: _3!, photo: _4!, participantsCount: _5!, date: _6!, version: _7!, migratedTo: _8, adminRights: _9, defaultBannedRights: _10)) } @@ -1450,7 +1450,8 @@ public extension Api { public var stargiftsCount: Int32? public var sendPaidMessagesStars: Int64? public var mainTab: Api.ProfileTab? - public init(flags: Int32, flags2: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, reactionsLimit: Int32?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?, boostsApplied: Int32?, boostsUnrestrict: Int32?, emojiset: Api.StickerSet?, botVerification: Api.BotVerification?, stargiftsCount: Int32?, sendPaidMessagesStars: Int64?, mainTab: Api.ProfileTab?) { + public var guardBotId: Int64? + public init(flags: Int32, flags2: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?, recentRequesters: [Int64]?, defaultSendAs: Api.Peer?, availableReactions: Api.ChatReactions?, reactionsLimit: Int32?, stories: Api.PeerStories?, wallpaper: Api.WallPaper?, boostsApplied: Int32?, boostsUnrestrict: Int32?, emojiset: Api.StickerSet?, botVerification: Api.BotVerification?, stargiftsCount: Int32?, sendPaidMessagesStars: Int64?, mainTab: Api.ProfileTab?, guardBotId: Int64?) { self.flags = flags self.flags2 = flags2 self.id = id @@ -1498,9 +1499,10 @@ public extension Api { self.stargiftsCount = stargiftsCount self.sendPaidMessagesStars = sendPaidMessagesStars self.mainTab = mainTab + self.guardBotId = guardBotId } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("channelFull", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("about", ConstructorParameterDescription(self.about)), ("participantsCount", ConstructorParameterDescription(self.participantsCount)), ("adminsCount", ConstructorParameterDescription(self.adminsCount)), ("kickedCount", ConstructorParameterDescription(self.kickedCount)), ("bannedCount", ConstructorParameterDescription(self.bannedCount)), ("onlineCount", ConstructorParameterDescription(self.onlineCount)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("chatPhoto", ConstructorParameterDescription(self.chatPhoto)), ("notifySettings", ConstructorParameterDescription(self.notifySettings)), ("exportedInvite", ConstructorParameterDescription(self.exportedInvite)), ("botInfo", ConstructorParameterDescription(self.botInfo)), ("migratedFromChatId", ConstructorParameterDescription(self.migratedFromChatId)), ("migratedFromMaxId", ConstructorParameterDescription(self.migratedFromMaxId)), ("pinnedMsgId", ConstructorParameterDescription(self.pinnedMsgId)), ("stickerset", ConstructorParameterDescription(self.stickerset)), ("availableMinId", ConstructorParameterDescription(self.availableMinId)), ("folderId", ConstructorParameterDescription(self.folderId)), ("linkedChatId", ConstructorParameterDescription(self.linkedChatId)), ("location", ConstructorParameterDescription(self.location)), ("slowmodeSeconds", ConstructorParameterDescription(self.slowmodeSeconds)), ("slowmodeNextSendDate", ConstructorParameterDescription(self.slowmodeNextSendDate)), ("statsDc", ConstructorParameterDescription(self.statsDc)), ("pts", ConstructorParameterDescription(self.pts)), ("call", ConstructorParameterDescription(self.call)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("pendingSuggestions", ConstructorParameterDescription(self.pendingSuggestions)), ("groupcallDefaultJoinAs", ConstructorParameterDescription(self.groupcallDefaultJoinAs)), ("themeEmoticon", ConstructorParameterDescription(self.themeEmoticon)), ("requestsPending", ConstructorParameterDescription(self.requestsPending)), ("recentRequesters", ConstructorParameterDescription(self.recentRequesters)), ("defaultSendAs", ConstructorParameterDescription(self.defaultSendAs)), ("availableReactions", ConstructorParameterDescription(self.availableReactions)), ("reactionsLimit", ConstructorParameterDescription(self.reactionsLimit)), ("stories", ConstructorParameterDescription(self.stories)), ("wallpaper", ConstructorParameterDescription(self.wallpaper)), ("boostsApplied", ConstructorParameterDescription(self.boostsApplied)), ("boostsUnrestrict", ConstructorParameterDescription(self.boostsUnrestrict)), ("emojiset", ConstructorParameterDescription(self.emojiset)), ("botVerification", ConstructorParameterDescription(self.botVerification)), ("stargiftsCount", ConstructorParameterDescription(self.stargiftsCount)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars)), ("mainTab", ConstructorParameterDescription(self.mainTab))]) + return ("channelFull", [("flags", ConstructorParameterDescription(self.flags)), ("flags2", ConstructorParameterDescription(self.flags2)), ("id", ConstructorParameterDescription(self.id)), ("about", ConstructorParameterDescription(self.about)), ("participantsCount", ConstructorParameterDescription(self.participantsCount)), ("adminsCount", ConstructorParameterDescription(self.adminsCount)), ("kickedCount", ConstructorParameterDescription(self.kickedCount)), ("bannedCount", ConstructorParameterDescription(self.bannedCount)), ("onlineCount", ConstructorParameterDescription(self.onlineCount)), ("readInboxMaxId", ConstructorParameterDescription(self.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(self.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(self.unreadCount)), ("chatPhoto", ConstructorParameterDescription(self.chatPhoto)), ("notifySettings", ConstructorParameterDescription(self.notifySettings)), ("exportedInvite", ConstructorParameterDescription(self.exportedInvite)), ("botInfo", ConstructorParameterDescription(self.botInfo)), ("migratedFromChatId", ConstructorParameterDescription(self.migratedFromChatId)), ("migratedFromMaxId", ConstructorParameterDescription(self.migratedFromMaxId)), ("pinnedMsgId", ConstructorParameterDescription(self.pinnedMsgId)), ("stickerset", ConstructorParameterDescription(self.stickerset)), ("availableMinId", ConstructorParameterDescription(self.availableMinId)), ("folderId", ConstructorParameterDescription(self.folderId)), ("linkedChatId", ConstructorParameterDescription(self.linkedChatId)), ("location", ConstructorParameterDescription(self.location)), ("slowmodeSeconds", ConstructorParameterDescription(self.slowmodeSeconds)), ("slowmodeNextSendDate", ConstructorParameterDescription(self.slowmodeNextSendDate)), ("statsDc", ConstructorParameterDescription(self.statsDc)), ("pts", ConstructorParameterDescription(self.pts)), ("call", ConstructorParameterDescription(self.call)), ("ttlPeriod", ConstructorParameterDescription(self.ttlPeriod)), ("pendingSuggestions", ConstructorParameterDescription(self.pendingSuggestions)), ("groupcallDefaultJoinAs", ConstructorParameterDescription(self.groupcallDefaultJoinAs)), ("themeEmoticon", ConstructorParameterDescription(self.themeEmoticon)), ("requestsPending", ConstructorParameterDescription(self.requestsPending)), ("recentRequesters", ConstructorParameterDescription(self.recentRequesters)), ("defaultSendAs", ConstructorParameterDescription(self.defaultSendAs)), ("availableReactions", ConstructorParameterDescription(self.availableReactions)), ("reactionsLimit", ConstructorParameterDescription(self.reactionsLimit)), ("stories", ConstructorParameterDescription(self.stories)), ("wallpaper", ConstructorParameterDescription(self.wallpaper)), ("boostsApplied", ConstructorParameterDescription(self.boostsApplied)), ("boostsUnrestrict", ConstructorParameterDescription(self.boostsUnrestrict)), ("emojiset", ConstructorParameterDescription(self.emojiset)), ("botVerification", ConstructorParameterDescription(self.botVerification)), ("stargiftsCount", ConstructorParameterDescription(self.stargiftsCount)), ("sendPaidMessagesStars", ConstructorParameterDescription(self.sendPaidMessagesStars)), ("mainTab", ConstructorParameterDescription(self.mainTab)), ("guardBotId", ConstructorParameterDescription(self.guardBotId))]) } } public class Cons_chatFull: TypeConstructorDescription { @@ -1553,7 +1555,7 @@ public extension Api { switch self { case .channelFull(let _data): if boxed { - buffer.appendInt32(-455036259) + buffer.appendInt32(-1605464774) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt32(_data.flags2, buffer: buffer, boxed: false) @@ -1686,6 +1688,9 @@ public extension Api { if Int(_data.flags2) & Int(1 << 22) != 0 { _data.mainTab!.serialize(buffer, true) } + if Int(_data.flags2) & Int(1 << 23) != 0 { + serializeInt64(_data.guardBotId!, buffer: buffer, boxed: false) + } break case .chatFull(let _data): if boxed { @@ -1750,7 +1755,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { case .channelFull(let _data): - return ("channelFull", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("about", ConstructorParameterDescription(_data.about)), ("participantsCount", ConstructorParameterDescription(_data.participantsCount)), ("adminsCount", ConstructorParameterDescription(_data.adminsCount)), ("kickedCount", ConstructorParameterDescription(_data.kickedCount)), ("bannedCount", ConstructorParameterDescription(_data.bannedCount)), ("onlineCount", ConstructorParameterDescription(_data.onlineCount)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("chatPhoto", ConstructorParameterDescription(_data.chatPhoto)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings)), ("exportedInvite", ConstructorParameterDescription(_data.exportedInvite)), ("botInfo", ConstructorParameterDescription(_data.botInfo)), ("migratedFromChatId", ConstructorParameterDescription(_data.migratedFromChatId)), ("migratedFromMaxId", ConstructorParameterDescription(_data.migratedFromMaxId)), ("pinnedMsgId", ConstructorParameterDescription(_data.pinnedMsgId)), ("stickerset", ConstructorParameterDescription(_data.stickerset)), ("availableMinId", ConstructorParameterDescription(_data.availableMinId)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("linkedChatId", ConstructorParameterDescription(_data.linkedChatId)), ("location", ConstructorParameterDescription(_data.location)), ("slowmodeSeconds", ConstructorParameterDescription(_data.slowmodeSeconds)), ("slowmodeNextSendDate", ConstructorParameterDescription(_data.slowmodeNextSendDate)), ("statsDc", ConstructorParameterDescription(_data.statsDc)), ("pts", ConstructorParameterDescription(_data.pts)), ("call", ConstructorParameterDescription(_data.call)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("pendingSuggestions", ConstructorParameterDescription(_data.pendingSuggestions)), ("groupcallDefaultJoinAs", ConstructorParameterDescription(_data.groupcallDefaultJoinAs)), ("themeEmoticon", ConstructorParameterDescription(_data.themeEmoticon)), ("requestsPending", ConstructorParameterDescription(_data.requestsPending)), ("recentRequesters", ConstructorParameterDescription(_data.recentRequesters)), ("defaultSendAs", ConstructorParameterDescription(_data.defaultSendAs)), ("availableReactions", ConstructorParameterDescription(_data.availableReactions)), ("reactionsLimit", ConstructorParameterDescription(_data.reactionsLimit)), ("stories", ConstructorParameterDescription(_data.stories)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper)), ("boostsApplied", ConstructorParameterDescription(_data.boostsApplied)), ("boostsUnrestrict", ConstructorParameterDescription(_data.boostsUnrestrict)), ("emojiset", ConstructorParameterDescription(_data.emojiset)), ("botVerification", ConstructorParameterDescription(_data.botVerification)), ("stargiftsCount", ConstructorParameterDescription(_data.stargiftsCount)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars)), ("mainTab", ConstructorParameterDescription(_data.mainTab))]) + return ("channelFull", [("flags", ConstructorParameterDescription(_data.flags)), ("flags2", ConstructorParameterDescription(_data.flags2)), ("id", ConstructorParameterDescription(_data.id)), ("about", ConstructorParameterDescription(_data.about)), ("participantsCount", ConstructorParameterDescription(_data.participantsCount)), ("adminsCount", ConstructorParameterDescription(_data.adminsCount)), ("kickedCount", ConstructorParameterDescription(_data.kickedCount)), ("bannedCount", ConstructorParameterDescription(_data.bannedCount)), ("onlineCount", ConstructorParameterDescription(_data.onlineCount)), ("readInboxMaxId", ConstructorParameterDescription(_data.readInboxMaxId)), ("readOutboxMaxId", ConstructorParameterDescription(_data.readOutboxMaxId)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount)), ("chatPhoto", ConstructorParameterDescription(_data.chatPhoto)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings)), ("exportedInvite", ConstructorParameterDescription(_data.exportedInvite)), ("botInfo", ConstructorParameterDescription(_data.botInfo)), ("migratedFromChatId", ConstructorParameterDescription(_data.migratedFromChatId)), ("migratedFromMaxId", ConstructorParameterDescription(_data.migratedFromMaxId)), ("pinnedMsgId", ConstructorParameterDescription(_data.pinnedMsgId)), ("stickerset", ConstructorParameterDescription(_data.stickerset)), ("availableMinId", ConstructorParameterDescription(_data.availableMinId)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("linkedChatId", ConstructorParameterDescription(_data.linkedChatId)), ("location", ConstructorParameterDescription(_data.location)), ("slowmodeSeconds", ConstructorParameterDescription(_data.slowmodeSeconds)), ("slowmodeNextSendDate", ConstructorParameterDescription(_data.slowmodeNextSendDate)), ("statsDc", ConstructorParameterDescription(_data.statsDc)), ("pts", ConstructorParameterDescription(_data.pts)), ("call", ConstructorParameterDescription(_data.call)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("pendingSuggestions", ConstructorParameterDescription(_data.pendingSuggestions)), ("groupcallDefaultJoinAs", ConstructorParameterDescription(_data.groupcallDefaultJoinAs)), ("themeEmoticon", ConstructorParameterDescription(_data.themeEmoticon)), ("requestsPending", ConstructorParameterDescription(_data.requestsPending)), ("recentRequesters", ConstructorParameterDescription(_data.recentRequesters)), ("defaultSendAs", ConstructorParameterDescription(_data.defaultSendAs)), ("availableReactions", ConstructorParameterDescription(_data.availableReactions)), ("reactionsLimit", ConstructorParameterDescription(_data.reactionsLimit)), ("stories", ConstructorParameterDescription(_data.stories)), ("wallpaper", ConstructorParameterDescription(_data.wallpaper)), ("boostsApplied", ConstructorParameterDescription(_data.boostsApplied)), ("boostsUnrestrict", ConstructorParameterDescription(_data.boostsUnrestrict)), ("emojiset", ConstructorParameterDescription(_data.emojiset)), ("botVerification", ConstructorParameterDescription(_data.botVerification)), ("stargiftsCount", ConstructorParameterDescription(_data.stargiftsCount)), ("sendPaidMessagesStars", ConstructorParameterDescription(_data.sendPaidMessagesStars)), ("mainTab", ConstructorParameterDescription(_data.mainTab)), ("guardBotId", ConstructorParameterDescription(_data.guardBotId))]) case .chatFull(let _data): return ("chatFull", [("flags", ConstructorParameterDescription(_data.flags)), ("id", ConstructorParameterDescription(_data.id)), ("about", ConstructorParameterDescription(_data.about)), ("participants", ConstructorParameterDescription(_data.participants)), ("chatPhoto", ConstructorParameterDescription(_data.chatPhoto)), ("notifySettings", ConstructorParameterDescription(_data.notifySettings)), ("exportedInvite", ConstructorParameterDescription(_data.exportedInvite)), ("botInfo", ConstructorParameterDescription(_data.botInfo)), ("pinnedMsgId", ConstructorParameterDescription(_data.pinnedMsgId)), ("folderId", ConstructorParameterDescription(_data.folderId)), ("call", ConstructorParameterDescription(_data.call)), ("ttlPeriod", ConstructorParameterDescription(_data.ttlPeriod)), ("groupcallDefaultJoinAs", ConstructorParameterDescription(_data.groupcallDefaultJoinAs)), ("themeEmoticon", ConstructorParameterDescription(_data.themeEmoticon)), ("requestsPending", ConstructorParameterDescription(_data.requestsPending)), ("recentRequesters", ConstructorParameterDescription(_data.recentRequesters)), ("availableReactions", ConstructorParameterDescription(_data.availableReactions)), ("reactionsLimit", ConstructorParameterDescription(_data.reactionsLimit))]) } @@ -1766,23 +1771,23 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = reader.readInt32() } var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _6 = reader.readInt32() } var _7: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _7 = reader.readInt32() } var _8: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _8 = reader.readInt32() } var _9: Int32? - if Int(_1!) & Int(1 << 13) != 0 { + if Int(_1 ?? 0) & Int(1 << 13) != 0 { _9 = reader.readInt32() } var _10: Int32? @@ -1800,7 +1805,7 @@ public extension Api { _14 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } var _15: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 23) != 0 { + if Int(_1 ?? 0) & Int(1 << 23) != 0 { if let signature = reader.readInt32() { _15 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } @@ -1810,202 +1815,207 @@ public extension Api { _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self) } var _17: Int64? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _17 = reader.readInt64() } var _18: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _18 = reader.readInt32() } var _19: Int32? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _19 = reader.readInt32() } var _20: Api.StickerSet? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _20 = Api.parse(reader, signature: signature) as? Api.StickerSet } } var _21: Int32? - if Int(_1!) & Int(1 << 9) != 0 { + if Int(_1 ?? 0) & Int(1 << 9) != 0 { _21 = reader.readInt32() } var _22: Int32? - if Int(_1!) & Int(1 << 11) != 0 { + if Int(_1 ?? 0) & Int(1 << 11) != 0 { _22 = reader.readInt32() } var _23: Int64? - if Int(_1!) & Int(1 << 14) != 0 { + if Int(_1 ?? 0) & Int(1 << 14) != 0 { _23 = reader.readInt64() } var _24: Api.ChannelLocation? - if Int(_1!) & Int(1 << 15) != 0 { + if Int(_1 ?? 0) & Int(1 << 15) != 0 { if let signature = reader.readInt32() { _24 = Api.parse(reader, signature: signature) as? Api.ChannelLocation } } var _25: Int32? - if Int(_1!) & Int(1 << 17) != 0 { + if Int(_1 ?? 0) & Int(1 << 17) != 0 { _25 = reader.readInt32() } var _26: Int32? - if Int(_1!) & Int(1 << 18) != 0 { + if Int(_1 ?? 0) & Int(1 << 18) != 0 { _26 = reader.readInt32() } var _27: Int32? - if Int(_1!) & Int(1 << 12) != 0 { + if Int(_1 ?? 0) & Int(1 << 12) != 0 { _27 = reader.readInt32() } var _28: Int32? _28 = reader.readInt32() var _29: Api.InputGroupCall? - if Int(_1!) & Int(1 << 21) != 0 { + if Int(_1 ?? 0) & Int(1 << 21) != 0 { if let signature = reader.readInt32() { _29 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } } var _30: Int32? - if Int(_1!) & Int(1 << 24) != 0 { + if Int(_1 ?? 0) & Int(1 << 24) != 0 { _30 = reader.readInt32() } var _31: [String]? - if Int(_1!) & Int(1 << 25) != 0 { + if Int(_1 ?? 0) & Int(1 << 25) != 0 { if let _ = reader.readInt32() { _31 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) } } var _32: Api.Peer? - if Int(_1!) & Int(1 << 26) != 0 { + if Int(_1 ?? 0) & Int(1 << 26) != 0 { if let signature = reader.readInt32() { _32 = Api.parse(reader, signature: signature) as? Api.Peer } } var _33: String? - if Int(_1!) & Int(1 << 27) != 0 { + if Int(_1 ?? 0) & Int(1 << 27) != 0 { _33 = parseString(reader) } var _34: Int32? - if Int(_1!) & Int(1 << 28) != 0 { + if Int(_1 ?? 0) & Int(1 << 28) != 0 { _34 = reader.readInt32() } var _35: [Int64]? - if Int(_1!) & Int(1 << 28) != 0 { + if Int(_1 ?? 0) & Int(1 << 28) != 0 { if let _ = reader.readInt32() { _35 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } } var _36: Api.Peer? - if Int(_1!) & Int(1 << 29) != 0 { + if Int(_1 ?? 0) & Int(1 << 29) != 0 { if let signature = reader.readInt32() { _36 = Api.parse(reader, signature: signature) as? Api.Peer } } var _37: Api.ChatReactions? - if Int(_1!) & Int(1 << 30) != 0 { + if Int(_1 ?? 0) & Int(1 << 30) != 0 { if let signature = reader.readInt32() { _37 = Api.parse(reader, signature: signature) as? Api.ChatReactions } } var _38: Int32? - if Int(_2!) & Int(1 << 13) != 0 { + if Int(_2 ?? 0) & Int(1 << 13) != 0 { _38 = reader.readInt32() } var _39: Api.PeerStories? - if Int(_2!) & Int(1 << 4) != 0 { + if Int(_2 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _39 = Api.parse(reader, signature: signature) as? Api.PeerStories } } var _40: Api.WallPaper? - if Int(_2!) & Int(1 << 7) != 0 { + if Int(_2 ?? 0) & Int(1 << 7) != 0 { if let signature = reader.readInt32() { _40 = Api.parse(reader, signature: signature) as? Api.WallPaper } } var _41: Int32? - if Int(_2!) & Int(1 << 8) != 0 { + if Int(_2 ?? 0) & Int(1 << 8) != 0 { _41 = reader.readInt32() } var _42: Int32? - if Int(_2!) & Int(1 << 9) != 0 { + if Int(_2 ?? 0) & Int(1 << 9) != 0 { _42 = reader.readInt32() } var _43: Api.StickerSet? - if Int(_2!) & Int(1 << 10) != 0 { + if Int(_2 ?? 0) & Int(1 << 10) != 0 { if let signature = reader.readInt32() { _43 = Api.parse(reader, signature: signature) as? Api.StickerSet } } var _44: Api.BotVerification? - if Int(_2!) & Int(1 << 17) != 0 { + if Int(_2 ?? 0) & Int(1 << 17) != 0 { if let signature = reader.readInt32() { _44 = Api.parse(reader, signature: signature) as? Api.BotVerification } } var _45: Int32? - if Int(_2!) & Int(1 << 18) != 0 { + if Int(_2 ?? 0) & Int(1 << 18) != 0 { _45 = reader.readInt32() } var _46: Int64? - if Int(_2!) & Int(1 << 21) != 0 { + if Int(_2 ?? 0) & Int(1 << 21) != 0 { _46 = reader.readInt64() } var _47: Api.ProfileTab? - if Int(_2!) & Int(1 << 22) != 0 { + if Int(_2 ?? 0) & Int(1 << 22) != 0 { if let signature = reader.readInt32() { _47 = Api.parse(reader, signature: signature) as? Api.ProfileTab } } + var _48: Int64? + if Int(_2 ?? 0) & Int(1 << 23) != 0 { + _48 = reader.readInt64() + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 13) == 0) || _9 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil let _c12 = _12 != nil let _c13 = _13 != nil let _c14 = _14 != nil - let _c15 = (Int(_1!) & Int(1 << 23) == 0) || _15 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 23) == 0) || _15 != nil let _c16 = _16 != nil - let _c17 = (Int(_1!) & Int(1 << 4) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 4) == 0) || _18 != nil - let _c19 = (Int(_1!) & Int(1 << 5) == 0) || _19 != nil - let _c20 = (Int(_1!) & Int(1 << 8) == 0) || _20 != nil - let _c21 = (Int(_1!) & Int(1 << 9) == 0) || _21 != nil - let _c22 = (Int(_1!) & Int(1 << 11) == 0) || _22 != nil - let _c23 = (Int(_1!) & Int(1 << 14) == 0) || _23 != nil - let _c24 = (Int(_1!) & Int(1 << 15) == 0) || _24 != nil - let _c25 = (Int(_1!) & Int(1 << 17) == 0) || _25 != nil - let _c26 = (Int(_1!) & Int(1 << 18) == 0) || _26 != nil - let _c27 = (Int(_1!) & Int(1 << 12) == 0) || _27 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _18 != nil + let _c19 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _19 != nil + let _c20 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _20 != nil + let _c21 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _21 != nil + let _c22 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _22 != nil + let _c23 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _23 != nil + let _c24 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _24 != nil + let _c25 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _25 != nil + let _c26 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _26 != nil + let _c27 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _27 != nil let _c28 = _28 != nil - let _c29 = (Int(_1!) & Int(1 << 21) == 0) || _29 != nil - let _c30 = (Int(_1!) & Int(1 << 24) == 0) || _30 != nil - let _c31 = (Int(_1!) & Int(1 << 25) == 0) || _31 != nil - let _c32 = (Int(_1!) & Int(1 << 26) == 0) || _32 != nil - let _c33 = (Int(_1!) & Int(1 << 27) == 0) || _33 != nil - let _c34 = (Int(_1!) & Int(1 << 28) == 0) || _34 != nil - let _c35 = (Int(_1!) & Int(1 << 28) == 0) || _35 != nil - let _c36 = (Int(_1!) & Int(1 << 29) == 0) || _36 != nil - let _c37 = (Int(_1!) & Int(1 << 30) == 0) || _37 != nil - let _c38 = (Int(_2!) & Int(1 << 13) == 0) || _38 != nil - let _c39 = (Int(_2!) & Int(1 << 4) == 0) || _39 != nil - let _c40 = (Int(_2!) & Int(1 << 7) == 0) || _40 != nil - let _c41 = (Int(_2!) & Int(1 << 8) == 0) || _41 != nil - let _c42 = (Int(_2!) & Int(1 << 9) == 0) || _42 != nil - let _c43 = (Int(_2!) & Int(1 << 10) == 0) || _43 != nil - let _c44 = (Int(_2!) & Int(1 << 17) == 0) || _44 != nil - let _c45 = (Int(_2!) & Int(1 << 18) == 0) || _45 != nil - let _c46 = (Int(_2!) & Int(1 << 21) == 0) || _46 != nil - let _c47 = (Int(_2!) & Int(1 << 22) == 0) || _47 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 && _c44 && _c45 && _c46 && _c47 { - return Api.ChatFull.channelFull(Cons_channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, reactionsLimit: _38, stories: _39, wallpaper: _40, boostsApplied: _41, boostsUnrestrict: _42, emojiset: _43, botVerification: _44, stargiftsCount: _45, sendPaidMessagesStars: _46, mainTab: _47)) + let _c29 = (Int(_1 ?? 0) & Int(1 << 21) == 0) || _29 != nil + let _c30 = (Int(_1 ?? 0) & Int(1 << 24) == 0) || _30 != nil + let _c31 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _31 != nil + let _c32 = (Int(_1 ?? 0) & Int(1 << 26) == 0) || _32 != nil + let _c33 = (Int(_1 ?? 0) & Int(1 << 27) == 0) || _33 != nil + let _c34 = (Int(_1 ?? 0) & Int(1 << 28) == 0) || _34 != nil + let _c35 = (Int(_1 ?? 0) & Int(1 << 28) == 0) || _35 != nil + let _c36 = (Int(_1 ?? 0) & Int(1 << 29) == 0) || _36 != nil + let _c37 = (Int(_1 ?? 0) & Int(1 << 30) == 0) || _37 != nil + let _c38 = (Int(_2 ?? 0) & Int(1 << 13) == 0) || _38 != nil + let _c39 = (Int(_2 ?? 0) & Int(1 << 4) == 0) || _39 != nil + let _c40 = (Int(_2 ?? 0) & Int(1 << 7) == 0) || _40 != nil + let _c41 = (Int(_2 ?? 0) & Int(1 << 8) == 0) || _41 != nil + let _c42 = (Int(_2 ?? 0) & Int(1 << 9) == 0) || _42 != nil + let _c43 = (Int(_2 ?? 0) & Int(1 << 10) == 0) || _43 != nil + let _c44 = (Int(_2 ?? 0) & Int(1 << 17) == 0) || _44 != nil + let _c45 = (Int(_2 ?? 0) & Int(1 << 18) == 0) || _45 != nil + let _c46 = (Int(_2 ?? 0) & Int(1 << 21) == 0) || _46 != nil + let _c47 = (Int(_2 ?? 0) & Int(1 << 22) == 0) || _47 != nil + let _c48 = (Int(_2 ?? 0) & Int(1 << 23) == 0) || _48 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 && _c44 && _c45 && _c46 && _c47 && _c48 { + return Api.ChatFull.channelFull(Cons_channelFull(flags: _1!, flags2: _2!, id: _3!, about: _4!, participantsCount: _5, adminsCount: _6, kickedCount: _7, bannedCount: _8, onlineCount: _9, readInboxMaxId: _10!, readOutboxMaxId: _11!, unreadCount: _12!, chatPhoto: _13!, notifySettings: _14!, exportedInvite: _15, botInfo: _16!, migratedFromChatId: _17, migratedFromMaxId: _18, pinnedMsgId: _19, stickerset: _20, availableMinId: _21, folderId: _22, linkedChatId: _23, location: _24, slowmodeSeconds: _25, slowmodeNextSendDate: _26, statsDc: _27, pts: _28!, call: _29, ttlPeriod: _30, pendingSuggestions: _31, groupcallDefaultJoinAs: _32, themeEmoticon: _33, requestsPending: _34, recentRequesters: _35, defaultSendAs: _36, availableReactions: _37, reactionsLimit: _38, stories: _39, wallpaper: _40, boostsApplied: _41, boostsUnrestrict: _42, emojiset: _43, botVerification: _44, stargiftsCount: _45, sendPaidMessagesStars: _46, mainTab: _47, guardBotId: _48)) } else { return nil @@ -2023,7 +2033,7 @@ public extension Api { _4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants } var _5: Api.Photo? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.Photo } @@ -2033,83 +2043,83 @@ public extension Api { _6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } var _7: Api.ExportedChatInvite? - if Int(_1!) & Int(1 << 13) != 0 { + if Int(_1 ?? 0) & Int(1 << 13) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite } } var _8: [Api.BotInfo]? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let _ = reader.readInt32() { _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self) } } var _9: Int32? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { _9 = reader.readInt32() } var _10: Int32? - if Int(_1!) & Int(1 << 11) != 0 { + if Int(_1 ?? 0) & Int(1 << 11) != 0 { _10 = reader.readInt32() } var _11: Api.InputGroupCall? - if Int(_1!) & Int(1 << 12) != 0 { + if Int(_1 ?? 0) & Int(1 << 12) != 0 { if let signature = reader.readInt32() { _11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall } } var _12: Int32? - if Int(_1!) & Int(1 << 14) != 0 { + if Int(_1 ?? 0) & Int(1 << 14) != 0 { _12 = reader.readInt32() } var _13: Api.Peer? - if Int(_1!) & Int(1 << 15) != 0 { + if Int(_1 ?? 0) & Int(1 << 15) != 0 { if let signature = reader.readInt32() { _13 = Api.parse(reader, signature: signature) as? Api.Peer } } var _14: String? - if Int(_1!) & Int(1 << 16) != 0 { + if Int(_1 ?? 0) & Int(1 << 16) != 0 { _14 = parseString(reader) } var _15: Int32? - if Int(_1!) & Int(1 << 17) != 0 { + if Int(_1 ?? 0) & Int(1 << 17) != 0 { _15 = reader.readInt32() } var _16: [Int64]? - if Int(_1!) & Int(1 << 17) != 0 { + if Int(_1 ?? 0) & Int(1 << 17) != 0 { if let _ = reader.readInt32() { _16 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) } } var _17: Api.ChatReactions? - if Int(_1!) & Int(1 << 18) != 0 { + if Int(_1 ?? 0) & Int(1 << 18) != 0 { if let signature = reader.readInt32() { _17 = Api.parse(reader, signature: signature) as? Api.ChatReactions } } var _18: Int32? - if Int(_1!) & Int(1 << 20) != 0 { + if Int(_1 ?? 0) & Int(1 << 20) != 0 { _18 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 17) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil - let _c18 = (Int(_1!) & Int(1 << 20) == 0) || _18 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _14 != nil + let _c15 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _15 != nil + let _c16 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _16 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 18) == 0) || _17 != nil + let _c18 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _18 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { return Api.ChatFull.chatFull(Cons_chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15, recentRequesters: _16, availableReactions: _17, reactionsLimit: _18)) } @@ -2236,7 +2246,7 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: String? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _3 = parseString(reader) } var _4: Api.Photo? @@ -2246,7 +2256,7 @@ public extension Api { var _5: Int32? _5 = reader.readInt32() var _6: [Api.User]? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } @@ -2254,31 +2264,31 @@ public extension Api { var _7: Int32? _7 = reader.readInt32() var _8: Api.StarsSubscriptionPricing? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing } } var _9: Int64? - if Int(_1!) & Int(1 << 12) != 0 { + if Int(_1 ?? 0) & Int(1 << 12) != 0 { _9 = reader.readInt64() } var _10: Api.BotVerification? - if Int(_1!) & Int(1 << 13) != 0 { + if Int(_1 ?? 0) & Int(1 << 13) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.BotVerification } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 10) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 12) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 13) == 0) || _10 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _10 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { return Api.ChatInvite.chatInvite(Cons_chatInvite(flags: _1!, title: _2!, about: _3, photo: _4!, participantsCount: _5!, participants: _6, color: _7!, subscriptionPricing: _8, subscriptionFormId: _9, botVerification: _10)) } diff --git a/submodules/TelegramApi/Sources/Api40.swift b/submodules/TelegramApi/Sources/Api40.swift index 3ed3e4f75b..275cb9603c 100644 --- a/submodules/TelegramApi/Sources/Api40.swift +++ b/submodules/TelegramApi/Sources/Api40.swift @@ -1,13740 +1,1967 @@ -public extension Api.functions.account { - static func acceptAuthorization(botId: Int64, scope: String, publicKey: String, valueHashes: [Api.SecureValueHash], credentials: Api.SecureCredentialsEncrypted) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-202552205) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(scope, buffer: buffer, boxed: false) - serializeString(publicKey, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(valueHashes.count)) - for item in valueHashes { - item.serialize(buffer, true) - } - credentials.serialize(buffer, true) - return (FunctionDescription(name: "account.acceptAuthorization", parameters: [("botId", ConstructorParameterDescription(botId)), ("scope", ConstructorParameterDescription(scope)), ("publicKey", ConstructorParameterDescription(publicKey)), ("valueHashes", ConstructorParameterDescription(valueHashes)), ("credentials", ConstructorParameterDescription(credentials))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool +public extension Api.premium { + enum BoostsStatus: TypeConstructorDescription { + public class Cons_boostsStatus: TypeConstructorDescription { + public var flags: Int32 + public var level: Int32 + public var currentLevelBoosts: Int32 + public var boosts: Int32 + public var giftBoosts: Int32? + public var nextLevelBoosts: Int32? + public var premiumAudience: Api.StatsPercentValue? + public var boostUrl: String + public var prepaidGiveaways: [Api.PrepaidGiveaway]? + public var myBoostSlots: [Int32]? + public init(flags: Int32, level: Int32, currentLevelBoosts: Int32, boosts: Int32, giftBoosts: Int32?, nextLevelBoosts: Int32?, premiumAudience: Api.StatsPercentValue?, boostUrl: String, prepaidGiveaways: [Api.PrepaidGiveaway]?, myBoostSlots: [Int32]?) { + self.flags = flags + self.level = level + self.currentLevelBoosts = currentLevelBoosts + self.boosts = boosts + self.giftBoosts = giftBoosts + self.nextLevelBoosts = nextLevelBoosts + self.premiumAudience = premiumAudience + self.boostUrl = boostUrl + self.prepaidGiveaways = prepaidGiveaways + self.myBoostSlots = myBoostSlots } - return result - }) - } -} -public extension Api.functions.account { - static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1043606090) - return (FunctionDescription(name: "account.cancelPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func changeAuthorizationSettings(flags: Int32, hash: Int64, encryptedRequestsDisabled: Api.Bool?, callRequestsDisabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1089766498) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - encryptedRequestsDisabled!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - callRequestsDisabled!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.changeAuthorizationSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("hash", ConstructorParameterDescription(hash)), ("encryptedRequestsDisabled", ConstructorParameterDescription(encryptedRequestsDisabled)), ("callRequestsDisabled", ConstructorParameterDescription(callRequestsDisabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func changePhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1891839707) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.changePhone", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.account { - static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(655677548) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.checkUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func clearRecentEmojiStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(404757166) - return (FunctionDescription(name: "account.clearRecentEmojiStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func confirmPasswordEmail(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1881204448) - serializeString(code, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.confirmPasswordEmail", parameters: [("code", ConstructorParameterDescription(code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func confirmPhone(phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1596029123) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.confirmPhone", parameters: [("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func createBusinessChatLink(link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2007898482) - link.serialize(buffer, true) - return (FunctionDescription(name: "account.createBusinessChatLink", parameters: [("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in - let reader = BufferReader(buffer) - var result: Api.BusinessChatLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink - } - return result - }) - } -} -public extension Api.functions.account { - static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1697530880) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(slug, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - document!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { - item.serialize(buffer, true) + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("boostsStatus", [("flags", ConstructorParameterDescription(self.flags)), ("level", ConstructorParameterDescription(self.level)), ("currentLevelBoosts", ConstructorParameterDescription(self.currentLevelBoosts)), ("boosts", ConstructorParameterDescription(self.boosts)), ("giftBoosts", ConstructorParameterDescription(self.giftBoosts)), ("nextLevelBoosts", ConstructorParameterDescription(self.nextLevelBoosts)), ("premiumAudience", ConstructorParameterDescription(self.premiumAudience)), ("boostUrl", ConstructorParameterDescription(self.boostUrl)), ("prepaidGiveaways", ConstructorParameterDescription(self.prepaidGiveaways)), ("myBoostSlots", ConstructorParameterDescription(self.myBoostSlots))]) } } - return (FunctionDescription(name: "account.createTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("document", ConstructorParameterDescription(document)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme + case boostsStatus(Cons_boostsStatus) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boostsStatus(let _data): + if boxed { + buffer.appendInt32(1230586490) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.level, buffer: buffer, boxed: false) + serializeInt32(_data.currentLevelBoosts, buffer: buffer, boxed: false) + serializeInt32(_data.boosts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 4) != 0 { + serializeInt32(_data.giftBoosts!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeInt32(_data.nextLevelBoosts!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + _data.premiumAudience!.serialize(buffer, true) + } + serializeString(_data.boostUrl, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.prepaidGiveaways!.count)) + for item in _data.prepaidGiveaways! { + item.serialize(buffer, true) + } + } + if Int(_data.flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.myBoostSlots!.count)) + for item in _data.myBoostSlots! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + break } - return result - }) - } -} -public extension Api.functions.account { - static func declinePasswordReset() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1284770294) - return (FunctionDescription(name: "account.declinePasswordReset", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteAccount(flags: Int32, reason: String, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1564422284) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(reason, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - password!.serialize(buffer, true) } - return (FunctionDescription(name: "account.deleteAccount", parameters: [("flags", ConstructorParameterDescription(flags)), ("reason", ConstructorParameterDescription(reason)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .boostsStatus(let _data): + return ("boostsStatus", [("flags", ConstructorParameterDescription(_data.flags)), ("level", ConstructorParameterDescription(_data.level)), ("currentLevelBoosts", ConstructorParameterDescription(_data.currentLevelBoosts)), ("boosts", ConstructorParameterDescription(_data.boosts)), ("giftBoosts", ConstructorParameterDescription(_data.giftBoosts)), ("nextLevelBoosts", ConstructorParameterDescription(_data.nextLevelBoosts)), ("premiumAudience", ConstructorParameterDescription(_data.premiumAudience)), ("boostUrl", ConstructorParameterDescription(_data.boostUrl)), ("prepaidGiveaways", ConstructorParameterDescription(_data.prepaidGiveaways)), ("myBoostSlots", ConstructorParameterDescription(_data.myBoostSlots))]) } - return result - }) - } -} -public extension Api.functions.account { - static func deleteAutoSaveExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1404829728) - return (FunctionDescription(name: "account.deleteAutoSaveExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1611085428) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.deleteBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deletePasskey(id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-172665281) - serializeString(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.deletePasskey", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1199522741) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) } - return (FunctionDescription(name: "account.deleteSecureValue", parameters: [("types", ConstructorParameterDescription(types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + + public static func parse_boostsStatus(_ reader: BufferReader) -> BoostsStatus? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1 ?? 0) & Int(1 << 4) != 0 { + _5 = reader.readInt32() } - return result - }) + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = reader.readInt32() + } + var _7: Api.StatsPercentValue? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue + } + } + var _8: String? + _8 = parseString(reader) + var _9: [Api.PrepaidGiveaway]? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PrepaidGiveaway.self) + } + } + var _10: [Int32]? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let _ = reader.readInt32() { + _10 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return Api.premium.BoostsStatus.boostsStatus(Cons_boostsStatus(flags: _1!, level: _2!, currentLevelBoosts: _3!, boosts: _4!, giftBoosts: _5, nextLevelBoosts: _6, premiumAudience: _7, boostUrl: _8!, prepaidGiveaways: _9, myBoostSlots: _10)) + } + else { + return nil + } + } } } -public extension Api.functions.account { - static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1581481689) - peer.serialize(buffer, true) - return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool +public extension Api.premium { + enum MyBoosts: TypeConstructorDescription { + public class Cons_myBoosts: TypeConstructorDescription { + public var myBoosts: [Api.MyBoost] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(myBoosts: [Api.MyBoost], chats: [Api.Chat], users: [Api.User]) { + self.myBoosts = myBoosts + self.chats = chats + self.users = users } - return result - }) - } -} -public extension Api.functions.account { - static func editBusinessChatLink(slug: String, link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1942744913) - serializeString(slug, buffer: buffer, boxed: false) - link.serialize(buffer, true) - return (FunctionDescription(name: "account.editBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in - let reader = BufferReader(buffer) - var result: Api.BusinessChatLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("myBoosts", [("myBoosts", ConstructorParameterDescription(self.myBoosts)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } - return result - }) - } -} -public extension Api.functions.account { - static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(489050862) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.finishTakeoutSession", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + } + case myBoosts(Cons_myBoosts) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .myBoosts(let _data): + if boxed { + buffer.appendInt32(-1696454430) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.myBoosts.count)) + for item in _data.myBoosts { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break } - return result - }) - } -} -public extension Api.functions.account { - static func getAccountTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(150761757) - return (FunctionDescription(name: "account.getAccountTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AccountDaysTTL? in - let reader = BufferReader(buffer) - var result: Api.AccountDaysTTL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AccountDaysTTL + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .myBoosts(let _data): + return ("myBoosts", [("myBoosts", ConstructorParameterDescription(_data.myBoosts)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } - return result - }) - } -} -public extension Api.functions.account { - static func getAllSecureValues() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { - let buffer = Buffer() - buffer.appendInt32(-1299661699) - return (FunctionDescription(name: "account.getAllSecureValues", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in - let reader = BufferReader(buffer) - var result: [Api.SecureValue]? + } + + public static func parse_myBoosts(_ reader: BufferReader) -> MyBoosts? { + var _1: [Api.MyBoost]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MyBoost.self) } - return result - }) - } -} -public extension Api.functions.account { - static func getAuthorizationForm(botId: Int64, scope: String, publicKey: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1456907910) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeString(scope, buffer: buffer, boxed: false) - serializeString(publicKey, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getAuthorizationForm", parameters: [("botId", ConstructorParameterDescription(botId)), ("scope", ConstructorParameterDescription(scope)), ("publicKey", ConstructorParameterDescription(publicKey))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AuthorizationForm? in - let reader = BufferReader(buffer) - var result: Api.account.AuthorizationForm? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AuthorizationForm - } - return result - }) - } -} -public extension Api.functions.account { - static func getAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-484392616) - return (FunctionDescription(name: "account.getAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Authorizations? in - let reader = BufferReader(buffer) - var result: Api.account.Authorizations? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Authorizations - } - return result - }) - } -} -public extension Api.functions.account { - static func getAutoDownloadSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1457130303) - return (FunctionDescription(name: "account.getAutoDownloadSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoDownloadSettings? in - let reader = BufferReader(buffer) - var result: Api.account.AutoDownloadSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AutoDownloadSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getAutoSaveSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1379156774) - return (FunctionDescription(name: "account.getAutoSaveSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoSaveSettings? in - let reader = BufferReader(buffer) - var result: Api.account.AutoSaveSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.AutoSaveSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getBotBusinessConnection(connectionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1990746736) - serializeString(connectionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getBotBusinessConnection", parameters: [("connectionId", ConstructorParameterDescription(connectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.account { - static func getBusinessChatLinks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1869667809) - return (FunctionDescription(name: "account.getBusinessChatLinks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.BusinessChatLinks? in - let reader = BufferReader(buffer) - var result: Api.account.BusinessChatLinks? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.BusinessChatLinks - } - return result - }) - } -} -public extension Api.functions.account { - static func getChannelDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1999087573) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChannelDefaultEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getChannelRestrictedStatusEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(900325589) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChannelRestrictedStatusEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-700916087) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in - let reader = BufferReader(buffer) - var result: Api.account.Themes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Themes - } - return result - }) - } -} -public extension Api.functions.account { - static func getCollectibleEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(779830595) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getCollectibleEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1319421967) - return (FunctionDescription(name: "account.getConnectedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ConnectedBots? in - let reader = BufferReader(buffer) - var result: Api.account.ConnectedBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ConnectedBots - } - return result - }) - } -} -public extension Api.functions.account { - static func getContactSignUpNotification() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1626880216) - return (FunctionDescription(name: "account.getContactSignUpNotification", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func getContentSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1952756306) - return (FunctionDescription(name: "account.getContentSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ContentSettings? in - let reader = BufferReader(buffer) - var result: Api.account.ContentSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ContentSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultBackgroundEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1509246514) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultBackgroundEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-696962170) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultGroupPhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1856479058) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultGroupPhotoEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getDefaultProfilePhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-495647960) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getDefaultProfilePhotoEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.account { - static func getGlobalPrivacySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-349483786) - return (FunctionDescription(name: "account.getGlobalPrivacySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in - let reader = BufferReader(buffer) - var result: Api.GlobalPrivacySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getMultiWallPapers(wallpapers: [Api.InputWallPaper]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.WallPaper]>) { - let buffer = Buffer() - buffer.appendInt32(1705865692) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(wallpapers.count)) - for item in wallpapers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.getMultiWallPapers", parameters: [("wallpapers", ConstructorParameterDescription(wallpapers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.WallPaper]? in - let reader = BufferReader(buffer) - var result: [Api.WallPaper]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.account { - static func getNotifyExceptions(flags: Int32, peer: Api.InputNotifyPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1398240377) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - peer!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.getNotifyExceptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.account { - static func getNotifySettings(peer: Api.InputNotifyPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(313765169) - peer.serialize(buffer, true) - return (FunctionDescription(name: "account.getNotifySettings", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.PeerNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.PeerNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getPaidMessagesRevenue(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(431639143) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - parentPeer!.serialize(buffer, true) - } - userId.serialize(buffer, true) - return (FunctionDescription(name: "account.getPaidMessagesRevenue", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PaidMessagesRevenue? in - let reader = BufferReader(buffer) - var result: Api.account.PaidMessagesRevenue? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PaidMessagesRevenue - } - return result - }) - } -} -public extension Api.functions.account { - static func getPasskeys() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-367063982) - return (FunctionDescription(name: "account.getPasskeys", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Passkeys? in - let reader = BufferReader(buffer) - var result: Api.account.Passkeys? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Passkeys - } - return result - }) - } -} -public extension Api.functions.account { - static func getPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1418342645) - return (FunctionDescription(name: "account.getPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Password? in - let reader = BufferReader(buffer) - var result: Api.account.Password? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Password - } - return result - }) - } -} -public extension Api.functions.account { - static func getPasswordSettings(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1663767815) - password.serialize(buffer, true) - return (FunctionDescription(name: "account.getPasswordSettings", parameters: [("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasswordSettings? in - let reader = BufferReader(buffer) - var result: Api.account.PasswordSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PasswordSettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getPrivacy(key: Api.InputPrivacyKey) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-623130288) - key.serialize(buffer, true) - return (FunctionDescription(name: "account.getPrivacy", parameters: [("key", ConstructorParameterDescription(key))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in - let reader = BufferReader(buffer) - var result: Api.account.PrivacyRules? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules - } - return result - }) - } -} -public extension Api.functions.account { - static func getReactionsNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(115172684) - return (FunctionDescription(name: "account.getReactionsNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.ReactionsNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func getRecentEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(257392901) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getRecentEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in - let reader = BufferReader(buffer) - var result: Api.account.EmojiStatuses? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses - } - return result - }) - } -} -public extension Api.functions.account { - static func getSavedMusicIds(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-526557265) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getSavedMusicIds", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedMusicIds? in - let reader = BufferReader(buffer) - var result: Api.account.SavedMusicIds? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SavedMusicIds - } - return result - }) - } -} -public extension Api.functions.account { - static func getSavedRingtones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-510647672) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getSavedRingtones", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtones? in - let reader = BufferReader(buffer) - var result: Api.account.SavedRingtones? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtones - } - return result - }) - } -} -public extension Api.functions.account { - static func getSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { - let buffer = Buffer() - buffer.appendInt32(1936088002) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(types.count)) - for item in types { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.getSecureValue", parameters: [("types", ConstructorParameterDescription(types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in - let reader = BufferReader(buffer) - var result: [Api.SecureValue]? + var _3: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.account { - static func getTheme(format: String, theme: Api.InputTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(978872812) - serializeString(format, buffer: buffer, boxed: false) - theme.serialize(buffer, true) - return (FunctionDescription(name: "account.getTheme", parameters: [("format", ConstructorParameterDescription(format)), ("theme", ConstructorParameterDescription(theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.premium.MyBoosts.myBoosts(Cons_myBoosts(myBoosts: _1!, chats: _2!, users: _3!)) } - return result - }) - } -} -public extension Api.functions.account { - static func getThemes(format: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1913054296) - serializeString(format, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getThemes", parameters: [("format", ConstructorParameterDescription(format)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in - let reader = BufferReader(buffer) - var result: Api.account.Themes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Themes - } - return result - }) - } -} -public extension Api.functions.account { - static func getTmpPassword(password: Api.InputCheckPasswordSRP, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1151208273) - password.serialize(buffer, true) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getTmpPassword", parameters: [("password", ConstructorParameterDescription(password)), ("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.TmpPassword? in - let reader = BufferReader(buffer) - var result: Api.account.TmpPassword? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.TmpPassword - } - return result - }) - } -} -public extension Api.functions.account { - static func getUniqueGiftChatThemes(offset: String, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-466818615) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getUniqueGiftChatThemes", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ChatThemes? in - let reader = BufferReader(buffer) - var result: Api.account.ChatThemes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ChatThemes - } - return result - }) - } -} -public extension Api.functions.account { - static func getWallPaper(wallpaper: Api.InputWallPaper) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-57811990) - wallpaper.serialize(buffer, true) - return (FunctionDescription(name: "account.getWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in - let reader = BufferReader(buffer) - var result: Api.WallPaper? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WallPaper - } - return result - }) - } -} -public extension Api.functions.account { - static func getWallPapers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(127302966) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.getWallPapers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WallPapers? in - let reader = BufferReader(buffer) - var result: Api.account.WallPapers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.WallPapers - } - return result - }) - } -} -public extension Api.functions.account { - static func getWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(405695855) - return (FunctionDescription(name: "account.getWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebAuthorizations? in - let reader = BufferReader(buffer) - var result: Api.account.WebAuthorizations? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.WebAuthorizations - } - return result - }) - } -} -public extension Api.functions.account { - static func initPasskeyRegistration() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1117079528) - return (FunctionDescription(name: "account.initPasskeyRegistration", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasskeyRegistrationOptions? in - let reader = BufferReader(buffer) - var result: Api.account.PasskeyRegistrationOptions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PasskeyRegistrationOptions - } - return result - }) - } -} -public extension Api.functions.account { - static func initTakeoutSession(flags: Int32, fileMaxSize: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1896617296) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 { - serializeInt64(fileMaxSize!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.initTakeoutSession", parameters: [("flags", ConstructorParameterDescription(flags)), ("fileMaxSize", ConstructorParameterDescription(fileMaxSize))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Takeout? in - let reader = BufferReader(buffer) - var result: Api.account.Takeout? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.Takeout - } - return result - }) - } -} -public extension Api.functions.account { - static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-953697477) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - theme!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(format!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - baseTheme!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.installTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("theme", ConstructorParameterDescription(theme)), ("format", ConstructorParameterDescription(format)), ("baseTheme", ConstructorParameterDescription(baseTheme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func installWallPaper(wallpaper: Api.InputWallPaper, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-18000023) - wallpaper.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.installWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func invalidateSignInCodes(codes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-896866118) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(codes.count)) - for item in codes { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.invalidateSignInCodes", parameters: [("codes", ConstructorParameterDescription(codes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-326762118) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(tokenType, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - appSandbox.serialize(buffer, true) - serializeBytes(secret, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUids.count)) - for item in otherUids { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.registerDevice", parameters: [("flags", ConstructorParameterDescription(flags)), ("tokenType", ConstructorParameterDescription(tokenType)), ("token", ConstructorParameterDescription(token)), ("appSandbox", ConstructorParameterDescription(appSandbox)), ("secret", ConstructorParameterDescription(secret)), ("otherUids", ConstructorParameterDescription(otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func registerPasskey(credential: Api.InputPasskeyCredential) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1437867990) - credential.serialize(buffer, true) - return (FunctionDescription(name: "account.registerPasskey", parameters: [("credential", ConstructorParameterDescription(credential))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Passkey? in - let reader = BufferReader(buffer) - var result: Api.Passkey? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Passkey - } - return result - }) - } -} -public extension Api.functions.account { - static func reorderUsernames(order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-279966037) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.reorderUsernames", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-977650298) - peer.serialize(buffer, true) - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", ConstructorParameterDescription(peer)), ("reason", ConstructorParameterDescription(reason)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-91437323) - peer.serialize(buffer, true) - photoId.serialize(buffer, true) - reason.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", ConstructorParameterDescription(peer)), ("photoId", ConstructorParameterDescription(photoId)), ("reason", ConstructorParameterDescription(reason)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resendPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2055154197) - return (FunctionDescription(name: "account.resendPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-545786948) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resetAuthorization", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-612493497) - return (FunctionDescription(name: "account.resetNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1828139493) - return (FunctionDescription(name: "account.resetPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResetPasswordResult? in - let reader = BufferReader(buffer) - var result: Api.account.ResetPasswordResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ResetPasswordResult - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWallPapers() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1153722364) - return (FunctionDescription(name: "account.resetWallPapers", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWebAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(755087855) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resetWebAuthorization", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resetWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1747789204) - return (FunctionDescription(name: "account.resetWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func resolveBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1418913262) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.resolveBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResolvedBusinessChatLinks? in - let reader = BufferReader(buffer) - var result: Api.account.ResolvedBusinessChatLinks? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.ResolvedBusinessChatLinks - } - return result - }) - } -} -public extension Api.functions.account { - static func saveAutoDownloadSettings(flags: Int32, settings: Api.AutoDownloadSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1995661875) - serializeInt32(flags, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveAutoDownloadSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveAutoSaveSettings(flags: Int32, peer: Api.InputPeer?, settings: Api.AutoSaveSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-694451359) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - peer!.serialize(buffer, true) - } - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveAutoSaveSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveMusic(flags: Int32, id: Api.InputDocument, afterId: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1301859671) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - afterId!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.saveMusic", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("afterId", ConstructorParameterDescription(afterId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1038768899) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "account.saveRingtone", parameters: [("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtone? in - let reader = BufferReader(buffer) - var result: Api.account.SavedRingtone? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtone - } - return result - }) - } -} -public extension Api.functions.account { - static func saveSecureValue(value: Api.InputSecureValue, secureSecretId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1986010339) - value.serialize(buffer, true) - serializeInt64(secureSecretId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.saveSecureValue", parameters: [("value", ConstructorParameterDescription(value)), ("secureSecretId", ConstructorParameterDescription(secureSecretId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SecureValue? in - let reader = BufferReader(buffer) - var result: Api.SecureValue? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SecureValue - } - return result - }) - } -} -public extension Api.functions.account { - static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-229175188) - theme.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", ConstructorParameterDescription(theme)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func saveWallPaper(wallpaper: Api.InputWallPaper, unsave: Api.Bool, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1817860919) - wallpaper.serialize(buffer, true) - unsave.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.saveWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper)), ("unsave", ConstructorParameterDescription(unsave)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func sendChangePhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2108208411) - serializeString(phoneNumber, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendConfirmPhoneCode(hash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(457157256) - serializeString(hash, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("hash", ConstructorParameterDescription(hash)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendVerifyEmailCode(purpose: Api.EmailVerifyPurpose, email: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1730136133) - purpose.serialize(buffer, true) - serializeString(email, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.sendVerifyEmailCode", parameters: [("purpose", ConstructorParameterDescription(purpose)), ("email", ConstructorParameterDescription(email))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SentEmailCode? in - let reader = BufferReader(buffer) - var result: Api.account.SentEmailCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.SentEmailCode - } - return result - }) - } -} -public extension Api.functions.account { - static func sendVerifyPhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1516022023) - serializeString(phoneNumber, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.account { - static func setAccountTTL(ttl: Api.AccountDaysTTL) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(608323678) - ttl.serialize(buffer, true) - return (FunctionDescription(name: "account.setAccountTTL", parameters: [("ttl", ConstructorParameterDescription(ttl))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setAuthorizationTTL(authorizationTtlDays: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1081501024) - serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.setAuthorizationTTL", parameters: [("authorizationTtlDays", ConstructorParameterDescription(authorizationTtlDays))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setContactSignUpNotification(silent: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-806076575) - silent.serialize(buffer, true) - return (FunctionDescription(name: "account.setContactSignUpNotification", parameters: [("silent", ConstructorParameterDescription(silent))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setContentSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1250643605) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.setContentSettings", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setGlobalPrivacySettings(settings: Api.GlobalPrivacySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(517647042) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.setGlobalPrivacySettings", parameters: [("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in - let reader = BufferReader(buffer) - var result: Api.GlobalPrivacySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func setMainProfileTab(tab: Api.ProfileTab) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1575909552) - tab.serialize(buffer, true) - return (FunctionDescription(name: "account.setMainProfileTab", parameters: [("tab", ConstructorParameterDescription(tab))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func setPrivacy(key: Api.InputPrivacyKey, rules: [Api.InputPrivacyRule]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-906486552) - key.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(rules.count)) - for item in rules { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "account.setPrivacy", parameters: [("key", ConstructorParameterDescription(key)), ("rules", ConstructorParameterDescription(rules))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in - let reader = BufferReader(buffer) - var result: Api.account.PrivacyRules? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules - } - return result - }) - } -} -public extension Api.functions.account { - static func setReactionsNotifySettings(settings: Api.ReactionsNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(829220168) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.setReactionsNotifySettings", parameters: [("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in - let reader = BufferReader(buffer) - var result: Api.ReactionsNotifySettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1684934807) - peer.serialize(buffer, true) - paused.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", ConstructorParameterDescription(peer)), ("paused", ConstructorParameterDescription(paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleNoPaidMessagesException(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-30483850) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - parentPeer!.serialize(buffer, true) - } - userId.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleNoPaidMessagesException", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1176919155) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleSponsoredMessages", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1490465654) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "account.toggleUsername", parameters: [("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func unregisterDevice(tokenType: Int32, token: String, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1779249670) - serializeInt32(tokenType, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(otherUids.count)) - for item in otherUids { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.unregisterDevice", parameters: [("tokenType", ConstructorParameterDescription(tokenType)), ("token", ConstructorParameterDescription(token)), ("otherUids", ConstructorParameterDescription(otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBirthday(flags: Int32, birthday: Api.Birthday?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-865203183) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - birthday!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateBirthday", parameters: [("flags", ConstructorParameterDescription(flags)), ("birthday", ConstructorParameterDescription(birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessAwayMessage(flags: Int32, message: Api.InputBusinessAwayMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1570078811) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - message!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateBusinessAwayMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessGreetingMessage(flags: Int32, message: Api.InputBusinessGreetingMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1724755908) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - message!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateBusinessGreetingMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessIntro(flags: Int32, intro: Api.InputBusinessIntro?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1508585420) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - intro!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateBusinessIntro", parameters: [("flags", ConstructorParameterDescription(flags)), ("intro", ConstructorParameterDescription(intro))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1637149926) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - geoPoint!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 { - serializeString(address!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.updateBusinessLocation", parameters: [("flags", ConstructorParameterDescription(flags)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateBusinessWorkHours(flags: Int32, businessWorkHours: Api.BusinessWorkHours?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1258348646) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - businessWorkHours!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateBusinessWorkHours", parameters: [("flags", ConstructorParameterDescription(flags)), ("businessWorkHours", ConstructorParameterDescription(businessWorkHours))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateColor(flags: Int32, color: Api.PeerColor?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1749885262) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - color!.serialize(buffer, true) - } - return (FunctionDescription(name: "account.updateColor", parameters: [("flags", ConstructorParameterDescription(flags)), ("color", ConstructorParameterDescription(color))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateConnectedBot(flags: Int32, rights: Api.BusinessBotRights?, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1721797758) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - rights!.serialize(buffer, true) - } - bot.serialize(buffer, true) - recipients.serialize(buffer, true) - return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("rights", ConstructorParameterDescription(rights)), ("bot", ConstructorParameterDescription(bot)), ("recipients", ConstructorParameterDescription(recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.account { - static func updateDeviceLocked(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(954152242) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.updateDeviceLocked", parameters: [("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateEmojiStatus(emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-70001045) - emojiStatus.serialize(buffer, true) - return (FunctionDescription(name: "account.updateEmojiStatus", parameters: [("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2067899501) - peer.serialize(buffer, true) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.updateNotifySettings", parameters: [("peer", ConstructorParameterDescription(peer)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updatePasswordSettings(password: Api.InputCheckPasswordSRP, newSettings: Api.account.PasswordInputSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1516564433) - password.serialize(buffer, true) - newSettings.serialize(buffer, true) - return (FunctionDescription(name: "account.updatePasswordSettings", parameters: [("password", ConstructorParameterDescription(password)), ("newSettings", ConstructorParameterDescription(newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updatePersonalChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-649919008) - channel.serialize(buffer, true) - return (FunctionDescription(name: "account.updatePersonalChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateProfile(flags: Int32, firstName: String?, lastName: String?, about: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2018596725) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(firstName!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(lastName!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(about!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "account.updateProfile", parameters: [("flags", ConstructorParameterDescription(flags)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName)), ("about", ConstructorParameterDescription(about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.account { - static func updateStatus(offline: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1713919532) - offline.serialize(buffer, true) - return (FunctionDescription(name: "account.updateStatus", parameters: [("offline", ConstructorParameterDescription(offline))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.account { - static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(737414348) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(format, buffer: buffer, boxed: false) - theme.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(slug!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - document!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(settings!.count)) - for item in settings! { - item.serialize(buffer, true) + else { + return nil } } - return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("format", ConstructorParameterDescription(format)), ("theme", ConstructorParameterDescription(theme)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("document", ConstructorParameterDescription(document)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in - let reader = BufferReader(buffer) - var result: Api.Theme? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Theme - } - return result - }) } } -public extension Api.functions.account { - static func updateUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1040964988) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.updateUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User +public extension Api.smsjobs { + enum EligibilityToJoin: TypeConstructorDescription { + public class Cons_eligibleToJoin: TypeConstructorDescription { + public var termsUrl: String + public var monthlySentSms: Int32 + public init(termsUrl: String, monthlySentSms: Int32) { + self.termsUrl = termsUrl + self.monthlySentSms = monthlySentSms } - return result - }) - } -} -public extension Api.functions.account { - static func uploadRingtone(file: Api.InputFile, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2095414366) - file.serialize(buffer, true) - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.uploadRingtone", parameters: [("file", ConstructorParameterDescription(file)), ("fileName", ConstructorParameterDescription(fileName)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("eligibleToJoin", [("termsUrl", ConstructorParameterDescription(self.termsUrl)), ("monthlySentSms", ConstructorParameterDescription(self.monthlySentSms))]) } - return result - }) - } -} -public extension Api.functions.account { - static func uploadTheme(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(473805619) - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - thumb!.serialize(buffer, true) } - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.uploadTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("file", ConstructorParameterDescription(file)), ("thumb", ConstructorParameterDescription(thumb)), ("fileName", ConstructorParameterDescription(fileName)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document + case eligibleToJoin(Cons_eligibleToJoin) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .eligibleToJoin(let _data): + if boxed { + buffer.appendInt32(-594852657) + } + serializeString(_data.termsUrl, buffer: buffer, boxed: false) + serializeInt32(_data.monthlySentSms, buffer: buffer, boxed: false) + break } - return result - }) - } -} -public extension Api.functions.account { - static func uploadWallPaper(flags: Int32, file: Api.InputFile, mimeType: String, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-476410109) - serializeInt32(flags, buffer: buffer, boxed: false) - file.serialize(buffer, true) - serializeString(mimeType, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "account.uploadWallPaper", parameters: [("flags", ConstructorParameterDescription(flags)), ("file", ConstructorParameterDescription(file)), ("mimeType", ConstructorParameterDescription(mimeType)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in - let reader = BufferReader(buffer) - var result: Api.WallPaper? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WallPaper - } - return result - }) - } -} -public extension Api.functions.account { - static func verifyEmail(purpose: Api.EmailVerifyPurpose, verification: Api.EmailVerification) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(53322959) - purpose.serialize(buffer, true) - verification.serialize(buffer, true) - return (FunctionDescription(name: "account.verifyEmail", parameters: [("purpose", ConstructorParameterDescription(purpose)), ("verification", ConstructorParameterDescription(verification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmailVerified? in - let reader = BufferReader(buffer) - var result: Api.account.EmailVerified? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.account.EmailVerified - } - return result - }) - } -} -public extension Api.functions.account { - static func verifyPhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1305716726) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(phoneCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "account.verifyPhone", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-392909491) - serializeBytes(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Authorization? in - let reader = BufferReader(buffer) - var result: Api.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func bindTempAuthKey(permAuthKeyId: Int64, nonce: Int64, expiresAt: Int32, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-841733627) - serializeInt64(permAuthKeyId, buffer: buffer, boxed: false) - serializeInt64(nonce, buffer: buffer, boxed: false) - serializeInt32(expiresAt, buffer: buffer, boxed: false) - serializeBytes(encryptedMessage, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.bindTempAuthKey", parameters: [("permAuthKeyId", ConstructorParameterDescription(permAuthKeyId)), ("nonce", ConstructorParameterDescription(nonce)), ("expiresAt", ConstructorParameterDescription(expiresAt)), ("encryptedMessage", ConstructorParameterDescription(encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func cancelCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(520357240) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.cancelCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func checkPaidAuth(phoneNumber: String, phoneCodeHash: String, formId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1457889180) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeInt64(formId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.checkPaidAuth", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("formId", ConstructorParameterDescription(formId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func checkPassword(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-779399914) - password.serialize(buffer, true) - return (FunctionDescription(name: "auth.checkPassword", parameters: [("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func checkRecoveryPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(221691769) - serializeString(code, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.checkRecoveryPassword", parameters: [("code", ConstructorParameterDescription(code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func dropTempAuthKeys(exceptAuthKeys: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1907842680) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(exceptAuthKeys.count)) - for item in exceptAuthKeys { - serializeInt64(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "auth.dropTempAuthKeys", parameters: [("exceptAuthKeys", ConstructorParameterDescription(exceptAuthKeys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .eligibleToJoin(let _data): + return ("eligibleToJoin", [("termsUrl", ConstructorParameterDescription(_data.termsUrl)), ("monthlySentSms", ConstructorParameterDescription(_data.monthlySentSms))]) } - return result - }) - } -} -public extension Api.functions.auth { - static func exportAuthorization(dcId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-440401971) - serializeInt32(dcId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.exportAuthorization", parameters: [("dcId", ConstructorParameterDescription(dcId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.ExportedAuthorization? in - let reader = BufferReader(buffer) - var result: Api.auth.ExportedAuthorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.ExportedAuthorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func exportLoginToken(apiId: Int32, apiHash: String, exceptIds: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1210022402) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(exceptIds.count)) - for item in exceptIds { - serializeInt64(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("exceptIds", ConstructorParameterDescription(exceptIds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in - let reader = BufferReader(buffer) - var result: Api.auth.LoginToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken + + public static func parse_eligibleToJoin(_ reader: BufferReader) -> EligibilityToJoin? { + var _1: String? + _1 = parseString(reader) + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.smsjobs.EligibilityToJoin.eligibleToJoin(Cons_eligibleToJoin(termsUrl: _1!, monthlySentSms: _2!)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.auth { - static func finishPasskeyLogin(flags: Int32, credential: Api.InputPasskeyCredential, fromDcId: Int32?, fromAuthKeyId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1739084537) - serializeInt32(flags, buffer: buffer, boxed: false) - credential.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(fromDcId!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(fromAuthKeyId!, buffer: buffer, boxed: false) + } +} +public extension Api.smsjobs { + enum Status: TypeConstructorDescription { + public class Cons_status: TypeConstructorDescription { + public var flags: Int32 + public var recentSent: Int32 + public var recentSince: Int32 + public var recentRemains: Int32 + public var totalSent: Int32 + public var totalSince: Int32 + public var lastGiftSlug: String? + public var termsUrl: String + public init(flags: Int32, recentSent: Int32, recentSince: Int32, recentRemains: Int32, totalSent: Int32, totalSince: Int32, lastGiftSlug: String?, termsUrl: String) { + self.flags = flags + self.recentSent = recentSent + self.recentSince = recentSince + self.recentRemains = recentRemains + self.totalSent = totalSent + self.totalSince = totalSince + self.lastGiftSlug = lastGiftSlug + self.termsUrl = termsUrl + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("status", [("flags", ConstructorParameterDescription(self.flags)), ("recentSent", ConstructorParameterDescription(self.recentSent)), ("recentSince", ConstructorParameterDescription(self.recentSince)), ("recentRemains", ConstructorParameterDescription(self.recentRemains)), ("totalSent", ConstructorParameterDescription(self.totalSent)), ("totalSince", ConstructorParameterDescription(self.totalSince)), ("lastGiftSlug", ConstructorParameterDescription(self.lastGiftSlug)), ("termsUrl", ConstructorParameterDescription(self.termsUrl))]) + } } - return (FunctionDescription(name: "auth.finishPasskeyLogin", parameters: [("flags", ConstructorParameterDescription(flags)), ("credential", ConstructorParameterDescription(credential)), ("fromDcId", ConstructorParameterDescription(fromDcId)), ("fromAuthKeyId", ConstructorParameterDescription(fromAuthKeyId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + case status(Cons_status) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .status(let _data): + if boxed { + buffer.appendInt32(720277905) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.recentSent, buffer: buffer, boxed: false) + serializeInt32(_data.recentSince, buffer: buffer, boxed: false) + serializeInt32(_data.recentRemains, buffer: buffer, boxed: false) + serializeInt32(_data.totalSent, buffer: buffer, boxed: false) + serializeInt32(_data.totalSince, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeString(_data.lastGiftSlug!, buffer: buffer, boxed: false) + } + serializeString(_data.termsUrl, buffer: buffer, boxed: false) + break } - return result - }) - } -} -public extension Api.functions.auth { - static func importAuthorization(id: Int64, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1518699091) - serializeInt64(id, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importAuthorization", parameters: [("id", ConstructorParameterDescription(id)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func importBotAuthorization(flags: Int32, apiId: Int32, apiHash: String, botAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1738800940) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - serializeString(botAuthToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importBotAuthorization", parameters: [("flags", ConstructorParameterDescription(flags)), ("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("botAuthToken", ConstructorParameterDescription(botAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1783866140) - serializeBytes(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in - let reader = BufferReader(buffer) - var result: Api.auth.LoginToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken - } - return result - }) - } -} -public extension Api.functions.auth { - static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(767062953) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - serializeString(webAuthToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("webAuthToken", ConstructorParameterDescription(webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization - } - return result - }) - } -} -public extension Api.functions.auth { - static func initPasskeyLogin(apiId: Int32, apiHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1368051895) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.initPasskeyLogin", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasskeyLoginOptions? in - let reader = BufferReader(buffer) - var result: Api.auth.PasskeyLoginOptions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.PasskeyLoginOptions - } - return result - }) - } -} -public extension Api.functions.auth { - static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1047706137) - return (FunctionDescription(name: "auth.logOut", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoggedOut? in - let reader = BufferReader(buffer) - var result: Api.auth.LoggedOut? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.LoggedOut - } - return result - }) - } -} -public extension Api.functions.auth { - static func recoverPassword(flags: Int32, code: String, newSettings: Api.account.PasswordInputSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(923364464) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(code, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - newSettings!.serialize(buffer, true) } - return (FunctionDescription(name: "auth.recoverPassword", parameters: [("flags", ConstructorParameterDescription(flags)), ("code", ConstructorParameterDescription(code)), ("newSettings", ConstructorParameterDescription(newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .status(let _data): + return ("status", [("flags", ConstructorParameterDescription(_data.flags)), ("recentSent", ConstructorParameterDescription(_data.recentSent)), ("recentSince", ConstructorParameterDescription(_data.recentSince)), ("recentRemains", ConstructorParameterDescription(_data.recentRemains)), ("totalSent", ConstructorParameterDescription(_data.totalSent)), ("totalSince", ConstructorParameterDescription(_data.totalSince)), ("lastGiftSlug", ConstructorParameterDescription(_data.lastGiftSlug)), ("termsUrl", ConstructorParameterDescription(_data.termsUrl))]) } - return result - }) - } -} -public extension Api.functions.auth { - static func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-878841866) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(mnc, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.reportMissingCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("mnc", ConstructorParameterDescription(mnc))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1908857314) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(safetyNetToken!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(playIntegrityToken!, buffer: buffer, boxed: false) + + public static func parse_status(_ reader: BufferReader) -> Status? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _7 = parseString(reader) + } + var _8: String? + _8 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.smsjobs.Status.status(Cons_status(flags: _1!, recentSent: _2!, recentSince: _3!, recentRemains: _4!, totalSent: _5!, totalSince: _6!, lastGiftSlug: _7, termsUrl: _8!)) + } + else { + return nil + } } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(iosPushSecret!, buffer: buffer, boxed: false) + } +} +public extension Api.stats { + enum BroadcastStats: TypeConstructorDescription { + public class Cons_broadcastStats: TypeConstructorDescription { + public var period: Api.StatsDateRangeDays + public var followers: Api.StatsAbsValueAndPrev + public var viewsPerPost: Api.StatsAbsValueAndPrev + public var sharesPerPost: Api.StatsAbsValueAndPrev + public var reactionsPerPost: Api.StatsAbsValueAndPrev + public var viewsPerStory: Api.StatsAbsValueAndPrev + public var sharesPerStory: Api.StatsAbsValueAndPrev + public var reactionsPerStory: Api.StatsAbsValueAndPrev + public var enabledNotifications: Api.StatsPercentValue + public var growthGraph: Api.StatsGraph + public var followersGraph: Api.StatsGraph + public var muteGraph: Api.StatsGraph + public var topHoursGraph: Api.StatsGraph + public var interactionsGraph: Api.StatsGraph + public var ivInteractionsGraph: Api.StatsGraph + public var viewsBySourceGraph: Api.StatsGraph + public var newFollowersBySourceGraph: Api.StatsGraph + public var languagesGraph: Api.StatsGraph + public var reactionsByEmotionGraph: Api.StatsGraph + public var storyInteractionsGraph: Api.StatsGraph + public var storyReactionsByEmotionGraph: Api.StatsGraph + public var recentPostsInteractions: [Api.PostInteractionCounters] + public init(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters]) { + self.period = period + self.followers = followers + self.viewsPerPost = viewsPerPost + self.sharesPerPost = sharesPerPost + self.reactionsPerPost = reactionsPerPost + self.viewsPerStory = viewsPerStory + self.sharesPerStory = sharesPerStory + self.reactionsPerStory = reactionsPerStory + self.enabledNotifications = enabledNotifications + self.growthGraph = growthGraph + self.followersGraph = followersGraph + self.muteGraph = muteGraph + self.topHoursGraph = topHoursGraph + self.interactionsGraph = interactionsGraph + self.ivInteractionsGraph = ivInteractionsGraph + self.viewsBySourceGraph = viewsBySourceGraph + self.newFollowersBySourceGraph = newFollowersBySourceGraph + self.languagesGraph = languagesGraph + self.reactionsByEmotionGraph = reactionsByEmotionGraph + self.storyInteractionsGraph = storyInteractionsGraph + self.storyReactionsByEmotionGraph = storyReactionsByEmotionGraph + self.recentPostsInteractions = recentPostsInteractions + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("broadcastStats", [("period", ConstructorParameterDescription(self.period)), ("followers", ConstructorParameterDescription(self.followers)), ("viewsPerPost", ConstructorParameterDescription(self.viewsPerPost)), ("sharesPerPost", ConstructorParameterDescription(self.sharesPerPost)), ("reactionsPerPost", ConstructorParameterDescription(self.reactionsPerPost)), ("viewsPerStory", ConstructorParameterDescription(self.viewsPerStory)), ("sharesPerStory", ConstructorParameterDescription(self.sharesPerStory)), ("reactionsPerStory", ConstructorParameterDescription(self.reactionsPerStory)), ("enabledNotifications", ConstructorParameterDescription(self.enabledNotifications)), ("growthGraph", ConstructorParameterDescription(self.growthGraph)), ("followersGraph", ConstructorParameterDescription(self.followersGraph)), ("muteGraph", ConstructorParameterDescription(self.muteGraph)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("interactionsGraph", ConstructorParameterDescription(self.interactionsGraph)), ("ivInteractionsGraph", ConstructorParameterDescription(self.ivInteractionsGraph)), ("viewsBySourceGraph", ConstructorParameterDescription(self.viewsBySourceGraph)), ("newFollowersBySourceGraph", ConstructorParameterDescription(self.newFollowersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(self.languagesGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph)), ("storyInteractionsGraph", ConstructorParameterDescription(self.storyInteractionsGraph)), ("storyReactionsByEmotionGraph", ConstructorParameterDescription(self.storyReactionsByEmotionGraph)), ("recentPostsInteractions", ConstructorParameterDescription(self.recentPostsInteractions))]) + } } - return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("safetyNetToken", ConstructorParameterDescription(safetyNetToken)), ("playIntegrityToken", ConstructorParameterDescription(playIntegrityToken)), ("iosPushSecret", ConstructorParameterDescription(iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + case broadcastStats(Cons_broadcastStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .broadcastStats(let _data): + if boxed { + buffer.appendInt32(963421692) + } + _data.period.serialize(buffer, true) + _data.followers.serialize(buffer, true) + _data.viewsPerPost.serialize(buffer, true) + _data.sharesPerPost.serialize(buffer, true) + _data.reactionsPerPost.serialize(buffer, true) + _data.viewsPerStory.serialize(buffer, true) + _data.sharesPerStory.serialize(buffer, true) + _data.reactionsPerStory.serialize(buffer, true) + _data.enabledNotifications.serialize(buffer, true) + _data.growthGraph.serialize(buffer, true) + _data.followersGraph.serialize(buffer, true) + _data.muteGraph.serialize(buffer, true) + _data.topHoursGraph.serialize(buffer, true) + _data.interactionsGraph.serialize(buffer, true) + _data.ivInteractionsGraph.serialize(buffer, true) + _data.viewsBySourceGraph.serialize(buffer, true) + _data.newFollowersBySourceGraph.serialize(buffer, true) + _data.languagesGraph.serialize(buffer, true) + _data.reactionsByEmotionGraph.serialize(buffer, true) + _data.storyInteractionsGraph.serialize(buffer, true) + _data.storyReactionsByEmotionGraph.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.recentPostsInteractions.count)) + for item in _data.recentPostsInteractions { + item.serialize(buffer, true) + } + break } - return result - }) - } -} -public extension Api.functions.auth { - static func requestPasswordRecovery() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-661144474) - return (FunctionDescription(name: "auth.requestPasswordRecovery", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasswordRecovery? in - let reader = BufferReader(buffer) - var result: Api.auth.PasswordRecovery? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.PasswordRecovery - } - return result - }) - } -} -public extension Api.functions.auth { - static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-890997469) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(reason!, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("reason", ConstructorParameterDescription(reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .broadcastStats(let _data): + return ("broadcastStats", [("period", ConstructorParameterDescription(_data.period)), ("followers", ConstructorParameterDescription(_data.followers)), ("viewsPerPost", ConstructorParameterDescription(_data.viewsPerPost)), ("sharesPerPost", ConstructorParameterDescription(_data.sharesPerPost)), ("reactionsPerPost", ConstructorParameterDescription(_data.reactionsPerPost)), ("viewsPerStory", ConstructorParameterDescription(_data.viewsPerStory)), ("sharesPerStory", ConstructorParameterDescription(_data.sharesPerStory)), ("reactionsPerStory", ConstructorParameterDescription(_data.reactionsPerStory)), ("enabledNotifications", ConstructorParameterDescription(_data.enabledNotifications)), ("growthGraph", ConstructorParameterDescription(_data.growthGraph)), ("followersGraph", ConstructorParameterDescription(_data.followersGraph)), ("muteGraph", ConstructorParameterDescription(_data.muteGraph)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("interactionsGraph", ConstructorParameterDescription(_data.interactionsGraph)), ("ivInteractionsGraph", ConstructorParameterDescription(_data.ivInteractionsGraph)), ("viewsBySourceGraph", ConstructorParameterDescription(_data.viewsBySourceGraph)), ("newFollowersBySourceGraph", ConstructorParameterDescription(_data.newFollowersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(_data.languagesGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph)), ("storyInteractionsGraph", ConstructorParameterDescription(_data.storyInteractionsGraph)), ("storyReactionsByEmotionGraph", ConstructorParameterDescription(_data.storyReactionsByEmotionGraph)), ("recentPostsInteractions", ConstructorParameterDescription(_data.recentPostsInteractions))]) } - return result - }) - } -} -public extension Api.functions.auth { - static func resetAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1616179942) - return (FunctionDescription(name: "auth.resetAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.auth { - static func resetLoginEmail(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2123760019) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.resetLoginEmail", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func sendCode(phoneNumber: String, apiId: Int32, apiHash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1502141361) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeInt32(apiId, buffer: buffer, boxed: false) - serializeString(apiHash, buffer: buffer, boxed: false) - settings.serialize(buffer, true) - return (FunctionDescription(name: "auth.sendCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in - let reader = BufferReader(buffer) - var result: Api.auth.SentCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.SentCode - } - return result - }) - } -} -public extension Api.functions.auth { - static func signIn(flags: Int32, phoneNumber: String, phoneCodeHash: String, phoneCode: String?, emailVerification: Api.EmailVerification?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1923962543) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(phoneCode!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 1) != 0 { - emailVerification!.serialize(buffer, true) - } - return (FunctionDescription(name: "auth.signIn", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode)), ("emailVerification", ConstructorParameterDescription(emailVerification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? + + public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? { + var _1: Api.StatsDateRangeDays? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays } - return result - }) - } -} -public extension Api.functions.auth { - static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1429752041) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(phoneCodeHash, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "auth.signUp", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in - let reader = BufferReader(buffer) - var result: Api.auth.Authorization? + var _2: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func addPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(397326170) - bot.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - media.serialize(buffer, true) - return (FunctionDescription(name: "bots.addPreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in - let reader = BufferReader(buffer) - var result: Api.BotPreviewMedia? + var _3: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia + _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func allowSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248323089) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.allowSendMessage", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? + var _4: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates + _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func answerWebhookJSONQuery(queryId: Int64, data: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-434028723) - serializeInt64(queryId, buffer: buffer, boxed: false) - data.serialize(buffer, true) - return (FunctionDescription(name: "bots.answerWebhookJSONQuery", parameters: [("queryId", ConstructorParameterDescription(queryId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? + var _5: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func canSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(324662502) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.canSendMessage", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? + var _6: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + _6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func checkDownloadFileParams(bot: Api.InputUser, fileName: String, url: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1342666121) - bot.serialize(buffer, true) - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.checkDownloadFileParams", parameters: [("bot", ConstructorParameterDescription(bot)), ("fileName", ConstructorParameterDescription(fileName)), ("url", ConstructorParameterDescription(url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? + var _7: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + _7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2014174821) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.checkUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? + var _8: Api.StatsAbsValueAndPrev? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + _8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev } - return result - }) - } -} -public extension Api.functions.bots { - static func createBot(flags: Int32, name: String, username: String, managerId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-441352405) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - serializeString(username, buffer: buffer, boxed: false) - managerId.serialize(buffer, true) - return (FunctionDescription(name: "bots.createBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("name", ConstructorParameterDescription(name)), ("username", ConstructorParameterDescription(username)), ("managerId", ConstructorParameterDescription(managerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? + var _9: Api.StatsPercentValue? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User + _9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue } - return result - }) - } -} -public extension Api.functions.bots { - static func deletePreviewMedia(bot: Api.InputUser, langCode: String, media: [Api.InputMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(755054003) - bot.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(media.count)) - for item in media { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "bots.deletePreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? + var _10: Api.StatsGraph? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - return result - }) - } -} -public extension Api.functions.bots { - static func editPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia, newMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2061148049) - bot.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - media.serialize(buffer, true) - newMedia.serialize(buffer, true) - return (FunctionDescription(name: "bots.editPreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media)), ("newMedia", ConstructorParameterDescription(newMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in - let reader = BufferReader(buffer) - var result: Api.BotPreviewMedia? + var _11: Api.StatsGraph? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia + _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - return result - }) - } -} -public extension Api.functions.bots { - static func exportBotToken(bot: Api.InputUser, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1123182101) - bot.serialize(buffer, true) - revoke.serialize(buffer, true) - return (FunctionDescription(name: "bots.exportBotToken", parameters: [("bot", ConstructorParameterDescription(bot)), ("revoke", ConstructorParameterDescription(revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.ExportedBotToken? in - let reader = BufferReader(buffer) - var result: Api.bots.ExportedBotToken? + var _12: Api.StatsGraph? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.ExportedBotToken + _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph } - return result - }) - } -} -public extension Api.functions.bots { - static func getAdminedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { - let buffer = Buffer() - buffer.appendInt32(-1334764157) - return (FunctionDescription(name: "bots.getAdminedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in - let reader = BufferReader(buffer) - var result: [Api.User]? + var _13: Api.StatsGraph? + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _14: Api.StatsGraph? + if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _15: Api.StatsGraph? + if let signature = reader.readInt32() { + _15 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _16: Api.StatsGraph? + if let signature = reader.readInt32() { + _16 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _17: Api.StatsGraph? + if let signature = reader.readInt32() { + _17 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _18: Api.StatsGraph? + if let signature = reader.readInt32() { + _18 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _19: Api.StatsGraph? + if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _20: Api.StatsGraph? + if let signature = reader.readInt32() { + _20 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _21: Api.StatsGraph? + if let signature = reader.readInt32() { + _21 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _22: [Api.PostInteractionCounters]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self) } - return result - }) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + let _c16 = _16 != nil + let _c17 = _17 != nil + let _c18 = _18 != nil + let _c19 = _19 != nil + let _c20 = _20 != nil + let _c21 = _21 != nil + let _c22 = _22 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 { + return Api.stats.BroadcastStats.broadcastStats(Cons_broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!)) + } + else { + return nil + } + } } } -public extension Api.functions.bots { - static func getBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotCommand]>) { - let buffer = Buffer() - buffer.appendInt32(-481554986) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotCommand]? in - let reader = BufferReader(buffer) - var result: [Api.BotCommand]? +public extension Api.stats { + enum MegagroupStats: TypeConstructorDescription { + public class Cons_megagroupStats: TypeConstructorDescription { + public var period: Api.StatsDateRangeDays + public var members: Api.StatsAbsValueAndPrev + public var messages: Api.StatsAbsValueAndPrev + public var viewers: Api.StatsAbsValueAndPrev + public var posters: Api.StatsAbsValueAndPrev + public var growthGraph: Api.StatsGraph + public var membersGraph: Api.StatsGraph + public var newMembersBySourceGraph: Api.StatsGraph + public var languagesGraph: Api.StatsGraph + public var messagesGraph: Api.StatsGraph + public var actionsGraph: Api.StatsGraph + public var topHoursGraph: Api.StatsGraph + public var weekdaysGraph: Api.StatsGraph + public var topPosters: [Api.StatsGroupTopPoster] + public var topAdmins: [Api.StatsGroupTopAdmin] + public var topInviters: [Api.StatsGroupTopInviter] + public var users: [Api.User] + public init(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User]) { + self.period = period + self.members = members + self.messages = messages + self.viewers = viewers + self.posters = posters + self.growthGraph = growthGraph + self.membersGraph = membersGraph + self.newMembersBySourceGraph = newMembersBySourceGraph + self.languagesGraph = languagesGraph + self.messagesGraph = messagesGraph + self.actionsGraph = actionsGraph + self.topHoursGraph = topHoursGraph + self.weekdaysGraph = weekdaysGraph + self.topPosters = topPosters + self.topAdmins = topAdmins + self.topInviters = topInviters + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("megagroupStats", [("period", ConstructorParameterDescription(self.period)), ("members", ConstructorParameterDescription(self.members)), ("messages", ConstructorParameterDescription(self.messages)), ("viewers", ConstructorParameterDescription(self.viewers)), ("posters", ConstructorParameterDescription(self.posters)), ("growthGraph", ConstructorParameterDescription(self.growthGraph)), ("membersGraph", ConstructorParameterDescription(self.membersGraph)), ("newMembersBySourceGraph", ConstructorParameterDescription(self.newMembersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(self.languagesGraph)), ("messagesGraph", ConstructorParameterDescription(self.messagesGraph)), ("actionsGraph", ConstructorParameterDescription(self.actionsGraph)), ("topHoursGraph", ConstructorParameterDescription(self.topHoursGraph)), ("weekdaysGraph", ConstructorParameterDescription(self.weekdaysGraph)), ("topPosters", ConstructorParameterDescription(self.topPosters)), ("topAdmins", ConstructorParameterDescription(self.topAdmins)), ("topInviters", ConstructorParameterDescription(self.topInviters)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case megagroupStats(Cons_megagroupStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .megagroupStats(let _data): + if boxed { + buffer.appendInt32(-276825834) + } + _data.period.serialize(buffer, true) + _data.members.serialize(buffer, true) + _data.messages.serialize(buffer, true) + _data.viewers.serialize(buffer, true) + _data.posters.serialize(buffer, true) + _data.growthGraph.serialize(buffer, true) + _data.membersGraph.serialize(buffer, true) + _data.newMembersBySourceGraph.serialize(buffer, true) + _data.languagesGraph.serialize(buffer, true) + _data.messagesGraph.serialize(buffer, true) + _data.actionsGraph.serialize(buffer, true) + _data.topHoursGraph.serialize(buffer, true) + _data.weekdaysGraph.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topPosters.count)) + for item in _data.topPosters { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topAdmins.count)) + for item in _data.topAdmins { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.topInviters.count)) + for item in _data.topInviters { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .megagroupStats(let _data): + return ("megagroupStats", [("period", ConstructorParameterDescription(_data.period)), ("members", ConstructorParameterDescription(_data.members)), ("messages", ConstructorParameterDescription(_data.messages)), ("viewers", ConstructorParameterDescription(_data.viewers)), ("posters", ConstructorParameterDescription(_data.posters)), ("growthGraph", ConstructorParameterDescription(_data.growthGraph)), ("membersGraph", ConstructorParameterDescription(_data.membersGraph)), ("newMembersBySourceGraph", ConstructorParameterDescription(_data.newMembersBySourceGraph)), ("languagesGraph", ConstructorParameterDescription(_data.languagesGraph)), ("messagesGraph", ConstructorParameterDescription(_data.messagesGraph)), ("actionsGraph", ConstructorParameterDescription(_data.actionsGraph)), ("topHoursGraph", ConstructorParameterDescription(_data.topHoursGraph)), ("weekdaysGraph", ConstructorParameterDescription(_data.weekdaysGraph)), ("topPosters", ConstructorParameterDescription(_data.topPosters)), ("topAdmins", ConstructorParameterDescription(_data.topAdmins)), ("topInviters", ConstructorParameterDescription(_data.topInviters)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_megagroupStats(_ reader: BufferReader) -> MegagroupStats? { + var _1: Api.StatsDateRangeDays? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays + } + var _2: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _3: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _4: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _5: Api.StatsAbsValueAndPrev? + if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev + } + var _6: Api.StatsGraph? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _7: Api.StatsGraph? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _8: Api.StatsGraph? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _9: Api.StatsGraph? + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _10: Api.StatsGraph? + if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _11: Api.StatsGraph? + if let signature = reader.readInt32() { + _11 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _12: Api.StatsGraph? + if let signature = reader.readInt32() { + _12 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _13: Api.StatsGraph? + if let signature = reader.readInt32() { + _13 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _14: [Api.StatsGroupTopPoster]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) + _14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self) } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-589753091) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - bot!.serialize(buffer, true) - } - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getBotInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.BotInfo? in - let reader = BufferReader(buffer) - var result: Api.bots.BotInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.BotInfo - } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotMenuButton(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1671369944) - userId.serialize(buffer, true) - return (FunctionDescription(name: "bots.getBotMenuButton", parameters: [("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotMenuButton? in - let reader = BufferReader(buffer) - var result: Api.BotMenuButton? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.BotMenuButton - } - return result - }) - } -} -public extension Api.functions.bots { - static func getBotRecommendations(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1581840363) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in - let reader = BufferReader(buffer) - var result: Api.users.Users? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.users.Users - } - return result - }) - } -} -public extension Api.functions.bots { - static func getPopularAppBots(offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1034878574) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getPopularAppBots", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.PopularAppBots? in - let reader = BufferReader(buffer) - var result: Api.bots.PopularAppBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.PopularAppBots - } - return result - }) - } -} -public extension Api.functions.bots { - static func getPreviewInfo(bot: Api.InputUser, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1111143341) - bot.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getPreviewInfo", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.PreviewInfo? in - let reader = BufferReader(buffer) - var result: Api.bots.PreviewInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.PreviewInfo - } - return result - }) - } -} -public extension Api.functions.bots { - static func getPreviewMedias(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotPreviewMedia]>) { - let buffer = Buffer() - buffer.appendInt32(-1566222003) - bot.serialize(buffer, true) - return (FunctionDescription(name: "bots.getPreviewMedias", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotPreviewMedia]? in - let reader = BufferReader(buffer) - var result: [Api.BotPreviewMedia]? + var _15: [Api.StatsGroupTopAdmin]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self) + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self) } - return result - }) - } -} -public extension Api.functions.bots { - static func getRequestedWebViewButton(bot: Api.InputUser, webappReqId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1088047117) - bot.serialize(buffer, true) - serializeString(webappReqId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.getRequestedWebViewButton", parameters: [("bot", ConstructorParameterDescription(bot)), ("webappReqId", ConstructorParameterDescription(webappReqId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.KeyboardButton? in - let reader = BufferReader(buffer) - var result: Api.KeyboardButton? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.KeyboardButton - } - return result - }) - } -} -public extension Api.functions.bots { - static func invokeWebViewCustomMethod(bot: Api.InputUser, customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(142591463) - bot.serialize(buffer, true) - serializeString(customMethod, buffer: buffer, boxed: false) - params.serialize(buffer, true) - return (FunctionDescription(name: "bots.invokeWebViewCustomMethod", parameters: [("bot", ConstructorParameterDescription(bot)), ("customMethod", ConstructorParameterDescription(customMethod)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.bots { - static func reorderPreviewMedias(bot: Api.InputUser, langCode: String, order: [Api.InputMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1238895702) - bot.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "bots.reorderPreviewMedias", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func reorderUsernames(bot: Api.InputUser, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1760972350) - bot.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "bots.reorderUsernames", parameters: [("bot", ConstructorParameterDescription(bot)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func requestWebViewButton(userId: Api.InputUser, button: Api.KeyboardButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(832742238) - userId.serialize(buffer, true) - button.serialize(buffer, true) - return (FunctionDescription(name: "bots.requestWebViewButton", parameters: [("userId", ConstructorParameterDescription(userId)), ("button", ConstructorParameterDescription(button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.RequestedButton? in - let reader = BufferReader(buffer) - var result: Api.bots.RequestedButton? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.bots.RequestedButton - } - return result - }) - } -} -public extension Api.functions.bots { - static func resetBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1032708345) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "bots.resetBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1440257555) - serializeString(customMethod, buffer: buffer, boxed: false) - params.serialize(buffer, true) - return (FunctionDescription(name: "bots.sendCustomRequest", parameters: [("customMethod", ConstructorParameterDescription(customMethod)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotBroadcastDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2021942497) - adminRights.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotBroadcastDefaultAdminRights", parameters: [("adminRights", ConstructorParameterDescription(adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotCommands(scope: Api.BotCommandScope, langCode: String, commands: [Api.BotCommand]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(85399130) - scope.serialize(buffer, true) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(commands.count)) - for item in commands { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "bots.setBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode)), ("commands", ConstructorParameterDescription(commands))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotGroupDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1839281686) - adminRights.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotGroupDefaultAdminRights", parameters: [("adminRights", ConstructorParameterDescription(adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String, name: String?, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(282013987) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - bot!.serialize(buffer, true) - } - serializeString(langCode, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - serializeString(name!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 0) != 0 { - serializeString(about!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(description!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("name", ConstructorParameterDescription(name)), ("about", ConstructorParameterDescription(about)), ("description", ConstructorParameterDescription(description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setBotMenuButton(userId: Api.InputUser, button: Api.BotMenuButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1157944655) - userId.serialize(buffer, true) - button.serialize(buffer, true) - return (FunctionDescription(name: "bots.setBotMenuButton", parameters: [("userId", ConstructorParameterDescription(userId)), ("button", ConstructorParameterDescription(button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func setCustomVerification(flags: Int32, bot: Api.InputUser?, peer: Api.InputPeer, customDescription: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1953898563) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - bot!.serialize(buffer, true) - } - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - serializeString(customDescription!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "bots.setCustomVerification", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("customDescription", ConstructorParameterDescription(customDescription))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func toggleUserEmojiStatusPermission(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(115237778) - bot.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "bots.toggleUserEmojiStatusPermission", parameters: [("bot", ConstructorParameterDescription(bot)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(87861619) - bot.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "bots.toggleUsername", parameters: [("bot", ConstructorParameterDescription(bot)), ("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.bots { - static func updateStarRefProgram(flags: Int32, bot: Api.InputUser, commissionPermille: Int32, durationMonths: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2005621427) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - serializeInt32(commissionPermille, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(durationMonths!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "bots.updateStarRefProgram", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("commissionPermille", ConstructorParameterDescription(commissionPermille)), ("durationMonths", ConstructorParameterDescription(durationMonths))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarRefProgram? in - let reader = BufferReader(buffer) - var result: Api.StarRefProgram? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StarRefProgram - } - return result - }) - } -} -public extension Api.functions.bots { - static func updateUserEmojiStatus(userId: Api.InputUser, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-308334395) - userId.serialize(buffer, true) - emojiStatus.serialize(buffer, true) - return (FunctionDescription(name: "bots.updateUserEmojiStatus", parameters: [("userId", ConstructorParameterDescription(userId)), ("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func checkSearchPostsFlood(flags: Int32, query: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(576090389) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(query!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.checkSearchPostsFlood", parameters: [("flags", ConstructorParameterDescription(flags)), ("query", ConstructorParameterDescription(query))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SearchPostsFlood? in - let reader = BufferReader(buffer) - var result: Api.SearchPostsFlood? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood - } - return result - }) - } -} -public extension Api.functions.channels { - static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(283557164) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.checkUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func convertToGigagroup(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(187239529) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.convertToGigagroup", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1862244601) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(about, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - geoPoint!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(address!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 4) != 0 { - serializeInt32(ttlPeriod!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", ConstructorParameterDescription(flags)), ("title", ConstructorParameterDescription(title)), ("about", ConstructorParameterDescription(about)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address)), ("ttlPeriod", ConstructorParameterDescription(ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func deactivateAllUsernames(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(170155475) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.deactivateAllUsernames", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1072619549) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.deleteChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteHistory(flags: Int32, channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1683319225) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.deleteHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteMessages(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2067661490) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.deleteMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.channels { - static func deleteParticipantHistory(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(913655003) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - return (FunctionDescription(name: "channels.deleteParticipantHistory", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.channels { - static func editAdmin(flags: Int32, channel: Api.InputChannel, userId: Api.InputUser, adminRights: Api.ChatAdminRights, rank: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1701270168) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - userId.serialize(buffer, true) - adminRights.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(rank!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.editAdmin", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("userId", ConstructorParameterDescription(userId)), ("adminRights", ConstructorParameterDescription(adminRights)), ("rank", ConstructorParameterDescription(rank))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func editBanned(channel: Api.InputChannel, participant: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1763259007) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - bannedRights.serialize(buffer, true) - return (FunctionDescription(name: "channels.editBanned", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant)), ("bannedRights", ConstructorParameterDescription(bannedRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1491484525) - channel.serialize(buffer, true) - geoPoint.serialize(buffer, true) - serializeString(address, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.editLocation", parameters: [("channel", ConstructorParameterDescription(channel)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func editPhoto(channel: Api.InputChannel, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248621111) - channel.serialize(buffer, true) - photo.serialize(buffer, true) - return (FunctionDescription(name: "channels.editPhoto", parameters: [("channel", ConstructorParameterDescription(channel)), ("photo", ConstructorParameterDescription(photo))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1450044624) - channel.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.editTitle", parameters: [("channel", ConstructorParameterDescription(channel)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func exportMessageLink(flags: Int32, channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-432034325) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.exportMessageLink", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedMessageLink? in - let reader = BufferReader(buffer) - var result: Api.ExportedMessageLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedMessageLink - } - return result - }) - } -} -public extension Api.functions.channels { - static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(870184064) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - eventsFilter!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(admins!.count)) - for item in admins! { - item.serialize(buffer, true) - } - } - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt64(minId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getAdminLog", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("q", ConstructorParameterDescription(q)), ("eventsFilter", ConstructorParameterDescription(eventsFilter)), ("admins", ConstructorParameterDescription(admins)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.AdminLogResults? in - let reader = BufferReader(buffer) - var result: Api.channels.AdminLogResults? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.AdminLogResults - } - return result - }) - } -} -public extension Api.functions.channels { - static func getAdminedPublicChannels(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-122669393) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getChannelRecommendations(flags: Int32, channel: Api.InputChannel?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(631707458) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - channel!.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.getChannelRecommendations", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getChannels(id: [Api.InputChannel]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(176122811) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.getChannels", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getFullChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(141781513) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.getFullChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull - } - return result - }) - } -} -public extension Api.functions.channels { - static func getGroupsForDiscussion() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-170208392) - return (FunctionDescription(name: "channels.getGroupsForDiscussion", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getInactiveChannels() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(300429806) - return (FunctionDescription(name: "channels.getInactiveChannels", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InactiveChats? in - let reader = BufferReader(buffer) - var result: Api.messages.InactiveChats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InactiveChats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getLeftChannels(offset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2092831552) - serializeInt32(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getLeftChannels", parameters: [("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.channels { - static func getMessageAuthor(channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-320691994) - channel.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getMessageAuthor", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.channels { - static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1383294429) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.getMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.channels { - static func getParticipant(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1599378234) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - return (FunctionDescription(name: "channels.getParticipant", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipant? in - let reader = BufferReader(buffer) - var result: Api.channels.ChannelParticipant? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipant - } - return result - }) - } -} -public extension Api.functions.channels { - static func getParticipants(channel: Api.InputChannel, filter: Api.ChannelParticipantsFilter, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2010044880) - channel.serialize(buffer, true) - filter.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.getParticipants", parameters: [("channel", ConstructorParameterDescription(channel)), ("filter", ConstructorParameterDescription(filter)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipants? in - let reader = BufferReader(buffer) - var result: Api.channels.ChannelParticipants? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipants - } - return result - }) - } -} -public extension Api.functions.channels { - static func getSendAs(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-410672065) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "channels.getSendAs", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SendAsPeers? in - let reader = BufferReader(buffer) - var result: Api.channels.SendAsPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.SendAsPeers - } - return result - }) - } -} -public extension Api.functions.channels { - static func inviteToChannel(channel: Api.InputChannel, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-907854508) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "channels.inviteToChannel", parameters: [("channel", ConstructorParameterDescription(channel)), ("users", ConstructorParameterDescription(users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.channels { - static func joinChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(615851205) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.joinChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func leaveChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-130635115) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.leaveChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-871347913) - channel.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.readHistory", parameters: [("channel", ConstructorParameterDescription(channel)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func readMessageContents(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-357180360) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.readMessageContents", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1268978403) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reorderUsernames", parameters: [("channel", ConstructorParameterDescription(channel)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1471109485) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-196443371) - channel.serialize(buffer, true) - participant.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reportSpam", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func restrictSponsoredMessages(channel: Api.InputChannel, restricted: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1696000743) - channel.serialize(buffer, true) - restricted.serialize(buffer, true) - return (FunctionDescription(name: "channels.restrictSponsoredMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("restricted", ConstructorParameterDescription(restricted))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func searchPosts(flags: Int32, hashtag: String?, query: String?, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-221973939) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(hashtag!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(query!, buffer: buffer, boxed: false) - } - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.searchPosts", parameters: [("flags", ConstructorParameterDescription(flags)), ("hashtag", ConstructorParameterDescription(hashtag)), ("query", ConstructorParameterDescription(query)), ("offsetRate", ConstructorParameterDescription(offsetRate)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.channels { - static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1388733202) - channel.serialize(buffer, true) - serializeInt32(boosts, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", ConstructorParameterDescription(channel)), ("boosts", ConstructorParameterDescription(boosts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1079520178) - broadcast.serialize(buffer, true) - group.serialize(buffer, true) - return (FunctionDescription(name: "channels.setDiscussionGroup", parameters: [("broadcast", ConstructorParameterDescription(broadcast)), ("group", ConstructorParameterDescription(group))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1020866743) - channel.serialize(buffer, true) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", ConstructorParameterDescription(channel)), ("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func setMainProfileTab(channel: Api.InputChannel, tab: Api.ProfileTab) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(897842353) - channel.serialize(buffer, true) - tab.serialize(buffer, true) - return (FunctionDescription(name: "channels.setMainProfileTab", parameters: [("channel", ConstructorParameterDescription(channel)), ("tab", ConstructorParameterDescription(tab))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-359881479) - channel.serialize(buffer, true) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "channels.setStickers", parameters: [("channel", ConstructorParameterDescription(channel)), ("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1760814315) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleAutotranslation(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(377471137) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleAutotranslation", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool, tabs: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1073174324) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - tabs.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleForum", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled)), ("tabs", ConstructorParameterDescription(tabs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleJoinRequest(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1277789622) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleJoinRequest", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleJoinToSend(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-456419968) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleJoinToSend", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleParticipantsHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1785624660) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleParticipantsHidden", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func togglePreHistoryHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-356796084) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.togglePreHistoryHidden", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleSignatures(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1099781276) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleSignatures", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleSlowMode(channel: Api.InputChannel, seconds: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-304832784) - channel.serialize(buffer, true) - serializeInt32(seconds, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.toggleSlowMode", parameters: [("channel", ConstructorParameterDescription(channel)), ("seconds", ConstructorParameterDescription(seconds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleUsername(channel: Api.InputChannel, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1358053637) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - active.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.channels { - static func toggleViewForumAsMessages(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1757889771) - channel.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "channels.toggleViewForumAsMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func updateColor(flags: Int32, channel: Api.InputChannel, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-659933583) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(color!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.updateColor", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("color", ConstructorParameterDescription(color)), ("backgroundEmojiId", ConstructorParameterDescription(backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func updateEmojiStatus(channel: Api.InputChannel, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-254548312) - channel.serialize(buffer, true) - emojiStatus.serialize(buffer, true) - return (FunctionDescription(name: "channels.updateEmojiStatus", parameters: [("channel", ConstructorParameterDescription(channel)), ("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func updatePaidMessagesPrice(flags: Int32, channel: Api.InputChannel, sendPaidMessagesStars: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1259483771) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt64(sendPaidMessagesStars, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.updatePaidMessagesPrice", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.channels { - static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(890549214) - channel.serialize(buffer, true) - serializeString(username, buffer: buffer, boxed: false) - return (FunctionDescription(name: "channels.updateUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func checkChatlistInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1103171583) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "chatlists.checkChatlistInvite", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func deleteExportedInvite(chatlist: Api.InputChatlist, slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1906072670) - chatlist.serialize(buffer, true) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "chatlists.deleteExportedInvite", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func editExportedInvite(flags: Int32, chatlist: Api.InputChatlist, slug: String, title: String?, peers: [Api.InputPeer]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1698543165) - serializeInt32(flags, buffer: buffer, boxed: false) - chatlist.serialize(buffer, true) - serializeString(slug, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers!.count)) - for item in peers! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "chatlists.editExportedInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatlist", ConstructorParameterDescription(chatlist)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.ExportedChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func exportChatlistInvite(chatlist: Api.InputChatlist, title: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2072885362) - chatlist.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.exportChatlistInvite", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("title", ConstructorParameterDescription(title)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedChatlistInvite? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ExportedChatlistInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedChatlistInvite - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1992190687) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistUpdates? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ChatlistUpdates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistUpdates - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getExportedInvites(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-838608253) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getExportedInvites", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedInvites? in - let reader = BufferReader(buffer) - var result: Api.chatlists.ExportedInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedInvites - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func getLeaveChatlistSuggestions(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Peer]>) { - let buffer = Buffer() - buffer.appendInt32(-37955820) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.getLeaveChatlistSuggestions", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Peer]? in - let reader = BufferReader(buffer) - var result: [Api.Peer]? + var _16: [Api.StatsGroupTopInviter]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + _16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self) } - return result - }) - } -} -public extension Api.functions.chatlists { - static func hideChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1726252795) - chatlist.serialize(buffer, true) - return (FunctionDescription(name: "chatlists.hideChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func joinChatlistInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1498291302) - serializeString(slug, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.joinChatlistInvite", parameters: [("slug", ConstructorParameterDescription(slug)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func joinChatlistUpdates(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-527828747) - chatlist.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.joinChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.chatlists { - static func leaveChatlist(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1962598714) - chatlist.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "chatlists.leaveChatlist", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-130964977) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.acceptContact", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String, note: Api.TextWithEntities?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-642109868) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeString(phone, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - note!.serialize(buffer, true) - } - return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName)), ("phone", ConstructorParameterDescription(phone)), ("note", ConstructorParameterDescription(note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func block(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(774801204) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.block", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func blockFromReplies(flags: Int32, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(698914348) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.blockFromReplies", parameters: [("flags", ConstructorParameterDescription(flags)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func deleteByPhones(phones: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(269745566) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(phones.count)) - for item in phones { - serializeString(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "contacts.deleteByPhones", parameters: [("phones", ConstructorParameterDescription(phones))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func deleteContacts(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(157945344) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "contacts.deleteContacts", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.contacts { - static func editCloseFriends(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1167653392) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "contacts.editCloseFriends", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func exportContactToken() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-127582169) - return (FunctionDescription(name: "contacts.exportContactToken", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedContactToken? in - let reader = BufferReader(buffer) - var result: Api.ExportedContactToken? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedContactToken - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getBirthdays() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-621959068) - return (FunctionDescription(name: "contacts.getBirthdays", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ContactBirthdays? in - let reader = BufferReader(buffer) - var result: Api.contacts.ContactBirthdays? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ContactBirthdays - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getBlocked(flags: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1702457472) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getBlocked", parameters: [("flags", ConstructorParameterDescription(flags)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Blocked? in - let reader = BufferReader(buffer) - var result: Api.contacts.Blocked? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Blocked - } - return result - }) - } -} -public extension Api.functions.contacts { - static func getContactIDs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(2061264541) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getContactIDs", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? + var _17: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.contacts { - static func getContacts(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1574346258) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getContacts", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Contacts? in - let reader = BufferReader(buffer) - var result: Api.contacts.Contacts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Contacts + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + let _c16 = _16 != nil + let _c17 = _17 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { + return Api.stats.MegagroupStats.megagroupStats(Cons_megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, weekdaysGraph: _13!, topPosters: _14!, topAdmins: _15!, topInviters: _16!, users: _17!)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.contacts { - static func getLocated(flags: Int32, geoPoint: Api.InputGeoPoint, selfExpires: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-750207932) - serializeInt32(flags, buffer: buffer, boxed: false) - geoPoint.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(selfExpires!, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "contacts.getLocated", parameters: [("flags", ConstructorParameterDescription(flags)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("selfExpires", ConstructorParameterDescription(selfExpires))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) } } -public extension Api.functions.contacts { - static func getSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SavedContact]>) { - let buffer = Buffer() - buffer.appendInt32(-2098076769) - return (FunctionDescription(name: "contacts.getSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SavedContact]? in - let reader = BufferReader(buffer) - var result: [Api.SavedContact]? +public extension Api.stats { + enum MessageStats: TypeConstructorDescription { + public class Cons_messageStats: TypeConstructorDescription { + public var viewsGraph: Api.StatsGraph + public var reactionsByEmotionGraph: Api.StatsGraph + public init(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) { + self.viewsGraph = viewsGraph + self.reactionsByEmotionGraph = reactionsByEmotionGraph + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("messageStats", [("viewsGraph", ConstructorParameterDescription(self.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph))]) + } + } + case messageStats(Cons_messageStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageStats(let _data): + if boxed { + buffer.appendInt32(2145983508) + } + _data.viewsGraph.serialize(buffer, true) + _data.reactionsByEmotionGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .messageStats(let _data): + return ("messageStats", [("viewsGraph", ConstructorParameterDescription(_data.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph))]) + } + } + + public static func parse_messageStats(_ reader: BufferReader) -> MessageStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stats.MessageStats.messageStats(Cons_messageStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.stats { + enum PollStats: TypeConstructorDescription { + public class Cons_pollStats: TypeConstructorDescription { + public var votesGraph: Api.StatsGraph + public init(votesGraph: Api.StatsGraph) { + self.votesGraph = votesGraph + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("pollStats", [("votesGraph", ConstructorParameterDescription(self.votesGraph))]) + } + } + case pollStats(Cons_pollStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .pollStats(let _data): + if boxed { + buffer.appendInt32(697941741) + } + _data.votesGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .pollStats(let _data): + return ("pollStats", [("votesGraph", ConstructorParameterDescription(_data.votesGraph))]) + } + } + + public static func parse_pollStats(_ reader: BufferReader) -> PollStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + if _c1 { + return Api.stats.PollStats.pollStats(Cons_pollStats(votesGraph: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.stats { + enum PublicForwards: TypeConstructorDescription { + public class Cons_publicForwards: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var forwards: [Api.PublicForward] + public var nextOffset: String? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, count: Int32, forwards: [Api.PublicForward], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.count = count + self.forwards = forwards + self.nextOffset = nextOffset + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("publicForwards", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("forwards", ConstructorParameterDescription(self.forwards)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case publicForwards(Cons_publicForwards) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .publicForwards(let _data): + if boxed { + buffer.appendInt32(-1828487648) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.forwards.count)) + for item in _data.forwards { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .publicForwards(let _data): + return ("publicForwards", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("forwards", ConstructorParameterDescription(_data.forwards)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_publicForwards(_ reader: BufferReader) -> PublicForwards? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.PublicForward]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedContact.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PublicForward.self) } - return result - }) - } -} -public extension Api.functions.contacts { - static func getSponsoredPeers(q: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1228356717) - serializeString(q, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getSponsoredPeers", parameters: [("q", ConstructorParameterDescription(q))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.SponsoredPeers? in - let reader = BufferReader(buffer) - var result: Api.contacts.SponsoredPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.SponsoredPeers + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) } - return result - }) - } -} -public extension Api.functions.contacts { - static func getStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ContactStatus]>) { - let buffer = Buffer() - buffer.appendInt32(-995929106) - return (FunctionDescription(name: "contacts.getStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ContactStatus]? in - let reader = BufferReader(buffer) - var result: [Api.ContactStatus]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactStatus.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.contacts { - static func getTopPeers(flags: Int32, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1758168906) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.getTopPeers", parameters: [("flags", ConstructorParameterDescription(flags)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.TopPeers? in - let reader = BufferReader(buffer) - var result: Api.contacts.TopPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.TopPeers - } - return result - }) - } -} -public extension Api.functions.contacts { - static func importContactToken(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(318789512) - serializeString(token, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.importContactToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.contacts { - static func importContacts(contacts: [Api.InputContact]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(746589157) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(contacts.count)) - for item in contacts { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "contacts.importContacts", parameters: [("contacts", ConstructorParameterDescription(contacts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ImportedContacts? in - let reader = BufferReader(buffer) - var result: Api.contacts.ImportedContacts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ImportedContacts - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resetSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2020263951) - return (FunctionDescription(name: "contacts.resetSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resetTopPeerRating(category: Api.TopPeerCategory, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(451113900) - category.serialize(buffer, true) - peer.serialize(buffer, true) - return (FunctionDescription(name: "contacts.resetTopPeerRating", parameters: [("category", ConstructorParameterDescription(category)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resolvePhone(phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1963375804) - serializeString(phone, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.resolvePhone", parameters: [("phone", ConstructorParameterDescription(phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in - let reader = BufferReader(buffer) - var result: Api.contacts.ResolvedPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer - } - return result - }) - } -} -public extension Api.functions.contacts { - static func resolveUsername(flags: Int32, username: String, referer: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1918565308) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(username, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(referer!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "contacts.resolveUsername", parameters: [("flags", ConstructorParameterDescription(flags)), ("username", ConstructorParameterDescription(username)), ("referer", ConstructorParameterDescription(referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in - let reader = BufferReader(buffer) - var result: Api.contacts.ResolvedPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer - } - return result - }) - } -} -public extension Api.functions.contacts { - static func search(q: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(301470424) - serializeString(q, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.search", parameters: [("q", ConstructorParameterDescription(q)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Found? in - let reader = BufferReader(buffer) - var result: Api.contacts.Found? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.contacts.Found - } - return result - }) - } -} -public extension Api.functions.contacts { - static func setBlocked(flags: Int32, id: [Api.InputPeer], limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1798939530) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.setBlocked", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2062238246) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "contacts.toggleTopPeers", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func unblock(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1252994264) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - return (FunctionDescription(name: "contacts.unblock", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.contacts { - static func updateContactNote(id: Api.InputUser, note: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(329212923) - id.serialize(buffer, true) - note.serialize(buffer, true) - return (FunctionDescription(name: "contacts.updateContactNote", parameters: [("id", ConstructorParameterDescription(id)), ("note", ConstructorParameterDescription(note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.folders { - static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1749536939) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(folderPeers.count)) - for item in folderPeers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "folders.editPeerFolders", parameters: [("folderPeers", ConstructorParameterDescription(folderPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.fragment { - static func getCollectibleInfo(collectible: Api.InputCollectible) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1105295942) - collectible.serialize(buffer, true) - return (FunctionDescription(name: "fragment.getCollectibleInfo", parameters: [("collectible", ConstructorParameterDescription(collectible))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.fragment.CollectibleInfo? in - let reader = BufferReader(buffer) - var result: Api.fragment.CollectibleInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.fragment.CollectibleInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func acceptTermsOfService(id: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-294455398) - id.serialize(buffer, true) - return (FunctionDescription(name: "help.acceptTermsOfService", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func dismissSuggestion(peer: Api.InputPeer, suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-183649631) - peer.serialize(buffer, true) - serializeString(suggestion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.dismissSuggestion", parameters: [("peer", ConstructorParameterDescription(peer)), ("suggestion", ConstructorParameterDescription(suggestion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1723407216) - userId.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities.count)) - for item in entities { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "help.editUserInfo", parameters: [("userId", ConstructorParameterDescription(userId)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in - let reader = BufferReader(buffer) - var result: Api.help.UserInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.UserInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func getAppConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1642330196) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getAppConfig", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppConfig? in - let reader = BufferReader(buffer) - var result: Api.help.AppConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.AppConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getAppUpdate(source: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1378703997) - serializeString(source, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getAppUpdate", parameters: [("source", ConstructorParameterDescription(source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppUpdate? in - let reader = BufferReader(buffer) - var result: Api.help.AppUpdate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.AppUpdate - } - return result - }) - } -} -public extension Api.functions.help { - static func getCdnConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1375900482) - return (FunctionDescription(name: "help.getCdnConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.CdnConfig? in - let reader = BufferReader(buffer) - var result: Api.CdnConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.CdnConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-990308245) - return (FunctionDescription(name: "help.getConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Config? in - let reader = BufferReader(buffer) - var result: Api.Config? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Config - } - return result - }) - } -} -public extension Api.functions.help { - static func getCountriesList(langCode: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1935116200) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getCountriesList", parameters: [("langCode", ConstructorParameterDescription(langCode)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.CountriesList? in - let reader = BufferReader(buffer) - var result: Api.help.CountriesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.CountriesList - } - return result - }) - } -} -public extension Api.functions.help { - static func getDeepLinkInfo(path: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1072547679) - serializeString(path, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getDeepLinkInfo", parameters: [("path", ConstructorParameterDescription(path))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.DeepLinkInfo? in - let reader = BufferReader(buffer) - var result: Api.help.DeepLinkInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.DeepLinkInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func getInviteText() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1295590211) - return (FunctionDescription(name: "help.getInviteText", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.InviteText? in - let reader = BufferReader(buffer) - var result: Api.help.InviteText? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.InviteText - } - return result - }) - } -} -public extension Api.functions.help { - static func getNearestDc() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(531836966) - return (FunctionDescription(name: "help.getNearestDc", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.NearestDc? in - let reader = BufferReader(buffer) - var result: Api.NearestDc? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.NearestDc - } - return result - }) - } -} -public extension Api.functions.help { - static func getPassportConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-966677240) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPassportConfig", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PassportConfig? in - let reader = BufferReader(buffer) - var result: Api.help.PassportConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PassportConfig - } - return result - }) - } -} -public extension Api.functions.help { - static func getPeerColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-629083089) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPeerColors", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in - let reader = BufferReader(buffer) - var result: Api.help.PeerColors? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PeerColors - } - return result - }) - } -} -public extension Api.functions.help { - static func getPeerProfileColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1412453891) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getPeerProfileColors", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in - let reader = BufferReader(buffer) - var result: Api.help.PeerColors? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PeerColors - } - return result - }) - } -} -public extension Api.functions.help { - static func getPremiumPromo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1206152236) - return (FunctionDescription(name: "help.getPremiumPromo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PremiumPromo? in - let reader = BufferReader(buffer) - var result: Api.help.PremiumPromo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PremiumPromo - } - return result - }) - } -} -public extension Api.functions.help { - static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1063816159) - return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in - let reader = BufferReader(buffer) - var result: Api.help.PromoData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.PromoData - } - return result - }) - } -} -public extension Api.functions.help { - static func getRecentMeUrls(referer: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1036054804) - serializeString(referer, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getRecentMeUrls", parameters: [("referer", ConstructorParameterDescription(referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.RecentMeUrls? in - let reader = BufferReader(buffer) - var result: Api.help.RecentMeUrls? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.RecentMeUrls - } - return result - }) - } -} -public extension Api.functions.help { - static func getSupport() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1663104819) - return (FunctionDescription(name: "help.getSupport", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.Support? in - let reader = BufferReader(buffer) - var result: Api.help.Support? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.Support - } - return result - }) - } -} -public extension Api.functions.help { - static func getSupportName() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-748624084) - return (FunctionDescription(name: "help.getSupportName", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.SupportName? in - let reader = BufferReader(buffer) - var result: Api.help.SupportName? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.SupportName - } - return result - }) - } -} -public extension Api.functions.help { - static func getTermsOfServiceUpdate() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(749019089) - return (FunctionDescription(name: "help.getTermsOfServiceUpdate", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TermsOfServiceUpdate? in - let reader = BufferReader(buffer) - var result: Api.help.TermsOfServiceUpdate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.TermsOfServiceUpdate - } - return result - }) - } -} -public extension Api.functions.help { - static func getTimezonesList(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1236468288) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.getTimezonesList", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TimezonesList? in - let reader = BufferReader(buffer) - var result: Api.help.TimezonesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.TimezonesList - } - return result - }) - } -} -public extension Api.functions.help { - static func getUserInfo(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(59377875) - userId.serialize(buffer, true) - return (FunctionDescription(name: "help.getUserInfo", parameters: [("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in - let reader = BufferReader(buffer) - var result: Api.help.UserInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.help.UserInfo - } - return result - }) - } -} -public extension Api.functions.help { - static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(505748629) - peer.serialize(buffer, true) - return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1862465352) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(events.count)) - for item in events { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", ConstructorParameterDescription(events))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func setBotUpdatesStatus(pendingUpdatesCount: Int32, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-333262899) - serializeInt32(pendingUpdatesCount, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "help.setBotUpdatesStatus", parameters: [("pendingUpdatesCount", ConstructorParameterDescription(pendingUpdatesCount)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.help { - static func test() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1058929929) - return (FunctionDescription(name: "help.test", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getDifference(langPack: String, langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-845657435) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getDifference", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode)), ("fromVersion", ConstructorParameterDescription(fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in - let reader = BufferReader(buffer) - var result: Api.LangPackDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-219008246) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLangPack", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in - let reader = BufferReader(buffer) - var result: Api.LangPackDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackDifference - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLanguage(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1784243458) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLanguage", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackLanguage? in - let reader = BufferReader(buffer) - var result: Api.LangPackLanguage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.LangPackLanguage - } - return result - }) - } -} -public extension Api.functions.langpack { - static func getLanguages(langPack: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackLanguage]>) { - let buffer = Buffer() - buffer.appendInt32(1120311183) - serializeString(langPack, buffer: buffer, boxed: false) - return (FunctionDescription(name: "langpack.getLanguages", parameters: [("langPack", ConstructorParameterDescription(langPack))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackLanguage]? in - let reader = BufferReader(buffer) - var result: [Api.LangPackLanguage]? + var _6: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackLanguage.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stats.PublicForwards.publicForwards(Cons_publicForwards(flags: _1!, count: _2!, forwards: _3!, nextOffset: _4, chats: _5!, users: _6!)) + } + else { + return nil + } + } } } -public extension Api.functions.langpack { - static func getStrings(langPack: String, langCode: String, keys: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackString]>) { - let buffer = Buffer() - buffer.appendInt32(-269862909) - serializeString(langPack, buffer: buffer, boxed: false) - serializeString(langCode, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(keys.count)) - for item in keys { - serializeString(item, buffer: buffer, boxed: false) +public extension Api.stats { + enum StoryStats: TypeConstructorDescription { + public class Cons_storyStats: TypeConstructorDescription { + public var viewsGraph: Api.StatsGraph + public var reactionsByEmotionGraph: Api.StatsGraph + public init(viewsGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph) { + self.viewsGraph = viewsGraph + self.reactionsByEmotionGraph = reactionsByEmotionGraph + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyStats", [("viewsGraph", ConstructorParameterDescription(self.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(self.reactionsByEmotionGraph))]) + } } - return (FunctionDescription(name: "langpack.getStrings", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode)), ("keys", ConstructorParameterDescription(keys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackString]? in - let reader = BufferReader(buffer) - var result: [Api.LangPackString]? + case storyStats(Cons_storyStats) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyStats(let _data): + if boxed { + buffer.appendInt32(1355613820) + } + _data.viewsGraph.serialize(buffer, true) + _data.reactionsByEmotionGraph.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyStats(let _data): + return ("storyStats", [("viewsGraph", ConstructorParameterDescription(_data.viewsGraph)), ("reactionsByEmotionGraph", ConstructorParameterDescription(_data.reactionsByEmotionGraph))]) + } + } + + public static func parse_storyStats(_ reader: BufferReader) -> StoryStats? { + var _1: Api.StatsGraph? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + var _2: Api.StatsGraph? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stats.StoryStats.storyStats(Cons_storyStats(viewsGraph: _1!, reactionsByEmotionGraph: _2!)) + } + else { + return nil + } + } + } +} +public extension Api.stickers { + enum SuggestedShortName: TypeConstructorDescription { + public class Cons_suggestedShortName: TypeConstructorDescription { + public var shortName: String + public init(shortName: String) { + self.shortName = shortName + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("suggestedShortName", [("shortName", ConstructorParameterDescription(self.shortName))]) + } + } + case suggestedShortName(Cons_suggestedShortName) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .suggestedShortName(let _data): + if boxed { + buffer.appendInt32(-2046910401) + } + serializeString(_data.shortName, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .suggestedShortName(let _data): + return ("suggestedShortName", [("shortName", ConstructorParameterDescription(_data.shortName))]) + } + } + + public static func parse_suggestedShortName(_ reader: BufferReader) -> SuggestedShortName? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.stickers.SuggestedShortName.suggestedShortName(Cons_suggestedShortName(shortName: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.storage { + enum FileType: TypeConstructorDescription { + case fileGif + case fileJpeg + case fileMov + case fileMp3 + case fileMp4 + case filePartial + case filePdf + case filePng + case fileUnknown + case fileWebp + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileGif: + if boxed { + buffer.appendInt32(-891180321) + } + break + case .fileJpeg: + if boxed { + buffer.appendInt32(8322574) + } + break + case .fileMov: + if boxed { + buffer.appendInt32(1258941372) + } + break + case .fileMp3: + if boxed { + buffer.appendInt32(1384777335) + } + break + case .fileMp4: + if boxed { + buffer.appendInt32(-1278304028) + } + break + case .filePartial: + if boxed { + buffer.appendInt32(1086091090) + } + break + case .filePdf: + if boxed { + buffer.appendInt32(-1373745011) + } + break + case .filePng: + if boxed { + buffer.appendInt32(172975040) + } + break + case .fileUnknown: + if boxed { + buffer.appendInt32(-1432995067) + } + break + case .fileWebp: + if boxed { + buffer.appendInt32(276907596) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .fileGif: + return ("fileGif", []) + case .fileJpeg: + return ("fileJpeg", []) + case .fileMov: + return ("fileMov", []) + case .fileMp3: + return ("fileMp3", []) + case .fileMp4: + return ("fileMp4", []) + case .filePartial: + return ("filePartial", []) + case .filePdf: + return ("filePdf", []) + case .filePng: + return ("filePng", []) + case .fileUnknown: + return ("fileUnknown", []) + case .fileWebp: + return ("fileWebp", []) + } + } + + public static func parse_fileGif(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileGif + } + public static func parse_fileJpeg(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileJpeg + } + public static func parse_fileMov(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMov + } + public static func parse_fileMp3(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMp3 + } + public static func parse_fileMp4(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileMp4 + } + public static func parse_filePartial(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePartial + } + public static func parse_filePdf(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePdf + } + public static func parse_filePng(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.filePng + } + public static func parse_fileUnknown(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileUnknown + } + public static func parse_fileWebp(_ reader: BufferReader) -> FileType? { + return Api.storage.FileType.fileWebp + } + } +} +public extension Api.stories { + enum Albums: TypeConstructorDescription { + public class Cons_albums: TypeConstructorDescription { + public var hash: Int64 + public var albums: [Api.StoryAlbum] + public init(hash: Int64, albums: [Api.StoryAlbum]) { + self.hash = hash + self.albums = albums + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("albums", [("hash", ConstructorParameterDescription(self.hash)), ("albums", ConstructorParameterDescription(self.albums))]) + } + } + case albums(Cons_albums) + case albumsNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .albums(let _data): + if boxed { + buffer.appendInt32(-1013417414) + } + serializeInt64(_data.hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.albums.count)) + for item in _data.albums { + item.serialize(buffer, true) + } + break + case .albumsNotModified: + if boxed { + buffer.appendInt32(1448008427) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .albums(let _data): + return ("albums", [("hash", ConstructorParameterDescription(_data.hash)), ("albums", ConstructorParameterDescription(_data.albums))]) + case .albumsNotModified: + return ("albumsNotModified", []) + } + } + + public static func parse_albums(_ reader: BufferReader) -> Albums? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StoryAlbum]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryAlbum.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func acceptEncryption(peer: Api.InputEncryptedChat, gB: Buffer, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1035731989) - peer.serialize(buffer, true) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.acceptEncryption", parameters: [("peer", ConstructorParameterDescription(peer)), ("gB", ConstructorParameterDescription(gB)), ("keyFingerprint", ConstructorParameterDescription(keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in - let reader = BufferReader(buffer) - var result: Api.EncryptedChat? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedChat + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stories.Albums.albums(Cons_albums(hash: _1!, albums: _2!)) } - return result - }) - } -} -public extension Api.functions.messages { - static func acceptUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?, matchCode: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1738797278) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - peer!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(msgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(buttonId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(url!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 4) != 0 { - serializeString(matchCode!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.acceptUrlAuth", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("url", ConstructorParameterDescription(url)), ("matchCode", ConstructorParameterDescription(matchCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in - let reader = BufferReader(buffer) - var result: Api.UrlAuthResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func addChatUser(chatId: Int64, userId: Api.InputUser, fwdLimit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-876162809) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(fwdLimit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.addChatUser", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId)), ("fwdLimit", ConstructorParameterDescription(fwdLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.messages { - static func addPollAnswer(peer: Api.InputPeer, msgId: Int32, answer: Api.PollAnswer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(431770477) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - answer.serialize(buffer, true) - return (FunctionDescription(name: "messages.addPollAnswer", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("answer", ConstructorParameterDescription(answer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func appendTodoList(peer: Api.InputPeer, msgId: Int32, list: [Api.TodoItem]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(564531287) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(list.count)) - for item in list { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.appendTodoList", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("list", ConstructorParameterDescription(list))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1051570619) - serializeString(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkChatInvite", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatInvite? in - let reader = BufferReader(buffer) - var result: Api.ChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1140726259) - serializeString(importHead, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", ConstructorParameterDescription(importHead))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in - let reader = BufferReader(buffer) - var result: Api.messages.HistoryImportParsed? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkHistoryImportPeer(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1573261059) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.checkHistoryImportPeer", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.CheckedHistoryImportPeer? in - let reader = BufferReader(buffer) - var result: Api.messages.CheckedHistoryImportPeer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.CheckedHistoryImportPeer - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkQuickReplyShortcut(shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-237962285) - serializeString(shortcut, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkQuickReplyShortcut", parameters: [("shortcut", ConstructorParameterDescription(shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func checkUrlAuthMatchCode(url: String, matchCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-911967477) - serializeString(url, buffer: buffer, boxed: false) - serializeString(matchCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.checkUrlAuthMatchCode", parameters: [("url", ConstructorParameterDescription(url)), ("matchCode", ConstructorParameterDescription(matchCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2119757468) - return (FunctionDescription(name: "messages.clearAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearRecentReactions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1644236876) - return (FunctionDescription(name: "messages.clearRecentReactions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clearRecentStickers(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1986437075) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.clearRecentStickers", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func clickSponsoredMessage(flags: Int32, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2110454402) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeBytes(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.clickSponsoredMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func composeMessageWithAI(flags: Int32, text: Api.TextWithEntities, translateToLang: String?, changeTone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-45978882) - serializeInt32(flags, buffer: buffer, boxed: false) - text.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(translateToLang!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(changeTone!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.composeMessageWithAI", parameters: [("flags", ConstructorParameterDescription(flags)), ("text", ConstructorParameterDescription(text)), ("translateToLang", ConstructorParameterDescription(translateToLang)), ("changeTone", ConstructorParameterDescription(changeTone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ComposedMessageWithAI? in - let reader = BufferReader(buffer) - var result: Api.messages.ComposedMessageWithAI? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ComposedMessageWithAI - } - return result - }) - } -} -public extension Api.functions.messages { - static func createChat(flags: Int32, users: [Api.InputUser], title: String, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1831936556) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(ttlPeriod!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.createChat", parameters: [("flags", ConstructorParameterDescription(flags)), ("users", ConstructorParameterDescription(users)), ("title", ConstructorParameterDescription(title)), ("ttlPeriod", ConstructorParameterDescription(ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in - let reader = BufferReader(buffer) - var result: Api.messages.InvitedUsers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers - } - return result - }) - } -} -public extension Api.functions.messages { - static func createForumTopic(flags: Int32, peer: Api.InputPeer, title: String, iconColor: Int32?, iconEmojiId: Int64?, randomId: Int64, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(798540757) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(iconColor!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeInt64(iconEmojiId!, buffer: buffer, boxed: false) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - sendAs!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.createForumTopic", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("iconColor", ConstructorParameterDescription(iconColor)), ("iconEmojiId", ConstructorParameterDescription(iconEmojiId)), ("randomId", ConstructorParameterDescription(randomId)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func declineUrlAuth(url: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(893610940) - serializeString(url, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.declineUrlAuth", parameters: [("url", ConstructorParameterDescription(url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1540419152) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteChatUser(flags: Int32, chatId: Int64, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1575461717) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-731601877) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteExportedChatInvite", parameters: [("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteFactCheck(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-774204404) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1332768214) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(minDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeInt32(maxDate!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteMessages(flags: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-443640366) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-104078327) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deletePhoneCallHistory", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedFoundMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedFoundMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedFoundMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func deletePollAnswer(peer: Api.InputPeer, msgId: Int32, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1400568411) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeBytes(option, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deletePollAnswer", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("option", ConstructorParameterDescription(option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteQuickReplyMessages(shortcutId: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-519706352) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteQuickReplyMessages", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteQuickReplyShortcut(shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1019234112) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteQuickReplyShortcut", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteRevokedExportedChatInvites(peer: Api.InputPeer, adminId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1452833749) - peer.serialize(buffer, true) - adminId.serialize(buffer, true) - return (FunctionDescription(name: "messages.deleteRevokedExportedChatInvites", parameters: [("peer", ConstructorParameterDescription(peer)), ("adminId", ConstructorParameterDescription(adminId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1304758367) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - parentPeer!.serialize(buffer, true) - } - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(minDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeInt32(maxDate!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1504586518) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func deleteTopicHistory(peer: Api.InputPeer, topMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-763269360) - peer.serialize(buffer, true) - serializeInt32(topMsgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.deleteTopicHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-208425312) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.discardEncryption", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatAbout(peer: Api.InputPeer, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-554301545) - peer.serialize(buffer, true) - serializeString(about, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editChatAbout", parameters: [("peer", ConstructorParameterDescription(peer)), ("about", ConstructorParameterDescription(about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatAdmin(chatId: Int64, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1470377534) - serializeInt64(chatId, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - isAdmin.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatAdmin", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId)), ("isAdmin", ConstructorParameterDescription(isAdmin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatCreator(peer: Api.InputPeer, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-146556841) - peer.serialize(buffer, true) - userId.serialize(buffer, true) - password.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatCreator", parameters: [("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatDefaultBannedRights(peer: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1517917375) - peer.serialize(buffer, true) - bannedRights.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatDefaultBannedRights", parameters: [("peer", ConstructorParameterDescription(peer)), ("bannedRights", ConstructorParameterDescription(bannedRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatParticipantRank(peer: Api.InputPeer, participant: Api.InputPeer, rank: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1609616720) - peer.serialize(buffer, true) - participant.serialize(buffer, true) - serializeString(rank, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editChatParticipantRank", parameters: [("peer", ConstructorParameterDescription(peer)), ("participant", ConstructorParameterDescription(participant)), ("rank", ConstructorParameterDescription(rank))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatPhoto(chatId: Int64, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(903730804) - serializeInt64(chatId, buffer: buffer, boxed: false) - photo.serialize(buffer, true) - return (FunctionDescription(name: "messages.editChatPhoto", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("photo", ConstructorParameterDescription(photo))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editChatTitle(chatId: Int64, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1937260541) - serializeInt64(chatId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editChatTitle", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?, requestNeeded: Api.Bool?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1110823051) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(expireDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(usageLimit!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - requestNeeded!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 4) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link)), ("expireDate", ConstructorParameterDescription(expireDate)), ("usageLimit", ConstructorParameterDescription(usageLimit)), ("requestNeeded", ConstructorParameterDescription(requestNeeded)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func editFactCheck(peer: Api.InputPeer, msgId: Int32, text: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(92925557) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - text.serialize(buffer, true) - return (FunctionDescription(name: "messages.editFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("text", ConstructorParameterDescription(text))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editForumTopic(flags: Int32, peer: Api.InputPeer, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-825487052) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt64(iconEmojiId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - closed!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - hidden!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.editForumTopic", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topicId", ConstructorParameterDescription(topicId)), ("title", ConstructorParameterDescription(title)), ("iconEmojiId", ConstructorParameterDescription(iconEmojiId)), ("closed", ConstructorParameterDescription(closed)), ("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2091549254) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - if Int(flags) & Int(1 << 11) != 0 { - serializeString(message!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 14) != 0 { - media!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - replyMarkup!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) + else { + return nil } } - return (FunctionDescription(name: "messages.editInlineBotMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("message", ConstructorParameterDescription(message)), ("media", ConstructorParameterDescription(media)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) + public static func parse_albumsNotModified(_ reader: BufferReader) -> Albums? { + return Api.stories.Albums.albumsNotModified + } } } -public extension Api.functions.messages { - static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, quickReplyShortcutId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1374175969) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 11) != 0 { - serializeString(message!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 14) != 0 { - media!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - replyMarkup!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) +public extension Api.stories { + enum AllStories: TypeConstructorDescription { + public class Cons_allStories: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var state: String + public var peerStories: [Api.PeerStories] + public var chats: [Api.Chat] + public var users: [Api.User] + public var stealthMode: Api.StoriesStealthMode + public init(flags: Int32, count: Int32, state: String, peerStories: [Api.PeerStories], chats: [Api.Chat], users: [Api.User], stealthMode: Api.StoriesStealthMode) { + self.flags = flags + self.count = count + self.state = state + self.peerStories = peerStories + self.chats = chats + self.users = users + self.stealthMode = stealthMode + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("allStories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("state", ConstructorParameterDescription(self.state)), ("peerStories", ConstructorParameterDescription(self.peerStories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("stealthMode", ConstructorParameterDescription(self.stealthMode))]) } } - if Int(flags) & Int(1 << 15) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 18) != 0 { - serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 17) != 0 { - serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("message", ConstructorParameterDescription(message)), ("media", ConstructorParameterDescription(media)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(quickReplyShortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates + public class Cons_allStoriesNotModified: TypeConstructorDescription { + public var flags: Int32 + public var state: String + public var stealthMode: Api.StoriesStealthMode + public init(flags: Int32, state: String, stealthMode: Api.StoriesStealthMode) { + self.flags = flags + self.state = state + self.stealthMode = stealthMode } - return result - }) - } -} -public extension Api.functions.messages { - static func editQuickReplyShortcut(shortcutId: Int32, shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1543519471) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - serializeString(shortcut, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.editQuickReplyShortcut", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId)), ("shortcut", ConstructorParameterDescription(shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("allStoriesNotModified", [("flags", ConstructorParameterDescription(self.flags)), ("state", ConstructorParameterDescription(self.state)), ("stealthMode", ConstructorParameterDescription(self.stealthMode))]) } - return result - }) - } -} -public extension Api.functions.messages { - static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?, title: String?, subscriptionPricing: Api.StarsSubscriptionPricing?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1537876336) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(expireDate!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(usageLimit!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 4) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 5) != 0 { - subscriptionPricing!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.exportChatInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("expireDate", ConstructorParameterDescription(expireDate)), ("usageLimit", ConstructorParameterDescription(usageLimit)), ("title", ConstructorParameterDescription(title)), ("subscriptionPricing", ConstructorParameterDescription(subscriptionPricing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + case allStories(Cons_allStories) + case allStoriesNotModified(Cons_allStoriesNotModified) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .allStories(let _data): + if boxed { + buffer.appendInt32(1862033025) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeString(_data.state, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.peerStories.count)) + for item in _data.peerStories { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + _data.stealthMode.serialize(buffer, true) + break + case .allStoriesNotModified(let _data): + if boxed { + buffer.appendInt32(291044926) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeString(_data.state, buffer: buffer, boxed: false) + _data.stealthMode.serialize(buffer, true) + break } - return result - }) - } -} -public extension Api.functions.messages { - static func faveSticker(id: Api.InputDocument, unfave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1174420133) - id.serialize(buffer, true) - unfave.serialize(buffer, true) - return (FunctionDescription(name: "messages.faveSticker", parameters: [("id", ConstructorParameterDescription(id)), ("unfave", ConstructorParameterDescription(unfave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .allStories(let _data): + return ("allStories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("state", ConstructorParameterDescription(_data.state)), ("peerStories", ConstructorParameterDescription(_data.peerStories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) + case .allStoriesNotModified(let _data): + return ("allStoriesNotModified", [("flags", ConstructorParameterDescription(_data.flags)), ("state", ConstructorParameterDescription(_data.state)), ("stealthMode", ConstructorParameterDescription(_data.stealthMode))]) } - return result - }) - } -} -public extension Api.functions.messages { - static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(326126204) - serializeInt32(flags, buffer: buffer, boxed: false) - fromPeer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - toPeer.serialize(buffer, true) - if Int(flags) & Int(1 << 9) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 22) != 0 { - replyTo!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 10) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 24) != 0 { - serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 17) != 0 { - quickReplyShortcut!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 18) != 0 { - serializeInt64(effect!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 20) != 0 { - serializeInt32(videoTimestamp!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 21) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 23) != 0 { - suggestedPost!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("fromPeer", ConstructorParameterDescription(fromPeer)), ("id", ConstructorParameterDescription(id)), ("randomId", ConstructorParameterDescription(randomId)), ("toPeer", ConstructorParameterDescription(toPeer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("replyTo", ConstructorParameterDescription(replyTo)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("videoTimestamp", ConstructorParameterDescription(videoTimestamp)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAdminsWithInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(958457583) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAdminsWithInvites", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatAdminsWithInvites? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatAdminsWithInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatAdminsWithInvites - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1782549861) - return (FunctionDescription(name: "messages.getAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAllStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1197432408) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAllStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getArchivedStickers(flags: Int32, offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1475442322) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getArchivedStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ArchivedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.ArchivedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ArchivedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachMenuBot(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1998676370) - bot.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAttachMenuBot", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBotsBot? in - let reader = BufferReader(buffer) - var result: Api.AttachMenuBotsBot? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AttachMenuBotsBot - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachMenuBots(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(385663691) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAttachMenuBots", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBots? in - let reader = BufferReader(buffer) - var result: Api.AttachMenuBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.AttachMenuBots - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAttachedStickers(media: Api.InputStickeredMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StickerSetCovered]>) { - let buffer = Buffer() - buffer.appendInt32(-866424884) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.getAttachedStickers", parameters: [("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StickerSetCovered]? in - let reader = BufferReader(buffer) - var result: [Api.StickerSetCovered]? + + public static func parse_allStories(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: [Api.PeerStories]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PeerStories.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getAvailableEffects(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-559805895) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAvailableEffects", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableEffects? in - let reader = BufferReader(buffer) - var result: Api.messages.AvailableEffects? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AvailableEffects - } - return result - }) - } -} -public extension Api.functions.messages { - static func getAvailableReactions(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(417243308) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getAvailableReactions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableReactions? in - let reader = BufferReader(buffer) - var result: Api.messages.AvailableReactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AvailableReactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getBotApp(app: Api.InputBotApp, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(889046467) - app.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getBotApp", parameters: [("app", ConstructorParameterDescription(app)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotApp? in - let reader = BufferReader(buffer) - var result: Api.messages.BotApp? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotApp - } - return result - }) - } -} -public extension Api.functions.messages { - static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1824339449) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeBytes(data!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - password!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("data", ConstructorParameterDescription(data)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in - let reader = BufferReader(buffer) - var result: Api.messages.BotCallbackAnswer? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer - } - return result - }) - } -} -public extension Api.functions.messages { - static func getChatInviteImporters(flags: Int32, peer: Api.InputPeer, link: String?, q: String?, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-553329330) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(link!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(q!, buffer: buffer, boxed: false) - } - serializeInt32(offsetDate, buffer: buffer, boxed: false) - offsetUser.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link)), ("q", ConstructorParameterDescription(q)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetUser", ConstructorParameterDescription(offsetUser)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatInviteImporters? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters - } - return result - }) - } -} -public extension Api.functions.messages { - static func getChats(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1240027791) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getChats", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.messages { - static func getCommonChats(userId: Api.InputUser, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-468934396) - userId.serialize(buffer, true) - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getCommonChats", parameters: [("userId", ConstructorParameterDescription(userId)), ("maxId", ConstructorParameterDescription(maxId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.messages { - static func getCustomEmojiDocuments(documentId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Document]>) { - let buffer = Buffer() - buffer.appendInt32(-643100844) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documentId.count)) - for item in documentId { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getCustomEmojiDocuments", parameters: [("documentId", ConstructorParameterDescription(documentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Document]? in - let reader = BufferReader(buffer) - var result: [Api.Document]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getDefaultHistoryTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1703637384) - return (FunctionDescription(name: "messages.getDefaultHistoryTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DefaultHistoryTTL? in - let reader = BufferReader(buffer) - var result: Api.DefaultHistoryTTL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DefaultHistoryTTL - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDefaultTagReactions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1107741656) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDefaultTagReactions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(651135312) - serializeInt32(version, buffer: buffer, boxed: false) - serializeInt32(randomLength, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDhConfig", parameters: [("version", ConstructorParameterDescription(version)), ("randomLength", ConstructorParameterDescription(randomLength))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DhConfig? in - let reader = BufferReader(buffer) - var result: Api.messages.DhConfig? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DhConfig - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-271283063) - return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in - let reader = BufferReader(buffer) - var result: Api.messages.DialogFilters? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters - } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogUnreadMarks(flags: Int32, parentPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { - let buffer = Buffer() - buffer.appendInt32(555754018) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - parentPeer!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in - let reader = BufferReader(buffer) - var result: [Api.DialogPeer]? + var _6: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _7: Api.StoriesStealthMode? + if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.stories.AllStories.allStories(Cons_allStories(flags: _1!, count: _2!, state: _3!, peerStories: _4!, chats: _5!, users: _6!, stealthMode: _7!)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.messages { - static func getDialogs(flags: Int32, folderId: Int32?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1594569905) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(folderId!, buffer: buffer, boxed: false) } - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Dialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.Dialogs? + public static func parse_allStoriesNotModified(_ reader: BufferReader) -> AllStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.StoriesStealthMode? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Dialogs + _3 = Api.parse(reader, signature: signature) as? Api.StoriesStealthMode } - return result - }) - } -} -public extension Api.functions.messages { - static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1147761405) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.DiscussionMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.stories.AllStories.allStoriesNotModified(Cons_allStoriesNotModified(flags: _1!, state: _2!, stealthMode: _3!)) } - return result - }) - } -} -public extension Api.functions.messages { - static func getDocumentByHash(sha256: Buffer, size: Int64, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1309538785) - serializeBytes(sha256, buffer: buffer, boxed: false) - serializeInt64(size, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getDocumentByHash", parameters: [("sha256", ConstructorParameterDescription(sha256)), ("size", ConstructorParameterDescription(size)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in - let reader = BufferReader(buffer) - var result: Api.Document? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Document + else { + return nil } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiGameInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-75592537) - return (FunctionDescription(name: "messages.getEmojiGameInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGameInfo? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGameInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1955122779) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywords(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(899735650) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiKeywords", parameters: [("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in - let reader = BufferReader(buffer) - var result: Api.EmojiKeywordsDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywordsDifference(langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(352892591) - serializeString(langCode, buffer: buffer, boxed: false) - serializeInt32(fromVersion, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiKeywordsDifference", parameters: [("langCode", ConstructorParameterDescription(langCode)), ("fromVersion", ConstructorParameterDescription(fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in - let reader = BufferReader(buffer) - var result: Api.EmojiKeywordsDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiKeywordsLanguages(langCodes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.EmojiLanguage]>) { - let buffer = Buffer() - buffer.appendInt32(1318675378) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(langCodes.count)) - for item in langCodes { - serializeString(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "messages.getEmojiKeywordsLanguages", parameters: [("langCodes", ConstructorParameterDescription(langCodes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.EmojiLanguage]? in - let reader = BufferReader(buffer) - var result: [Api.EmojiLanguage]? + } +} +public extension Api.stories { + enum CanSendStoryCount: TypeConstructorDescription { + public class Cons_canSendStoryCount: TypeConstructorDescription { + public var countRemains: Int32 + public init(countRemains: Int32) { + self.countRemains = countRemains + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("canSendStoryCount", [("countRemains", ConstructorParameterDescription(self.countRemains))]) + } + } + case canSendStoryCount(Cons_canSendStoryCount) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .canSendStoryCount(let _data): + if boxed { + buffer.appendInt32(-1014513586) + } + serializeInt32(_data.countRemains, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .canSendStoryCount(let _data): + return ("canSendStoryCount", [("countRemains", ConstructorParameterDescription(_data.countRemains))]) + } + } + + public static func parse_canSendStoryCount(_ reader: BufferReader) -> CanSendStoryCount? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.stories.CanSendStoryCount.canSendStoryCount(Cons_canSendStoryCount(countRemains: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.stories { + enum FoundStories: TypeConstructorDescription { + public class Cons_foundStories: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var stories: [Api.FoundStory] + public var nextOffset: String? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, count: Int32, stories: [Api.FoundStory], nextOffset: String?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.count = count + self.stories = stories + self.nextOffset = nextOffset + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("foundStories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("stories", ConstructorParameterDescription(self.stories)), ("nextOffset", ConstructorParameterDescription(self.nextOffset)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case foundStories(Cons_foundStories) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .foundStories(let _data): + if boxed { + buffer.appendInt32(-488736969) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stories.count)) + for item in _data.stories { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .foundStories(let _data): + return ("foundStories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("stories", ConstructorParameterDescription(_data.stories)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_foundStories(_ reader: BufferReader) -> FoundStories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.FoundStory]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiLanguage.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FoundStory.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiProfilePhotoGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(564480243) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiProfilePhotoGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + var _4: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _4 = parseString(reader) } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStatusGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(785209037) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStatusGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStickerGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(500711669) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStickerGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in - let reader = BufferReader(buffer) - var result: Api.messages.EmojiGroups? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-67329649) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getEmojiURL(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-709817306) - serializeString(langCode, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getEmojiURL", parameters: [("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiURL? in - let reader = BufferReader(buffer) - var result: Api.EmojiURL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiURL - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1937010524) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getExportedChatInvite", parameters: [("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1565154314) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - adminId.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(offsetDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(offsetLink!, buffer: buffer, boxed: false) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("adminId", ConstructorParameterDescription(adminId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetLink", ConstructorParameterDescription(offsetLink)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in - let reader = BufferReader(buffer) - var result: Api.messages.ExportedChatInvites? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites - } - return result - }) - } -} -public extension Api.functions.messages { - static func getExtendedMedia(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2064119788) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getExtendedMedia", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFactCheck(peer: Api.InputPeer, msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FactCheck]>) { - let buffer = Buffer() - buffer.appendInt32(-1177696786) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(msgId.count)) - for item in msgId { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FactCheck]? in - let reader = BufferReader(buffer) - var result: [Api.FactCheck]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FactCheck.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(82946729) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFavedStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FavedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FavedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FavedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFeaturedEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(248473398) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFeaturedEmojiStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFeaturedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1685588756) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFeaturedStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getForumTopics(flags: Int32, peer: Api.InputPeer, q: String?, offsetDate: Int32, offsetId: Int32, offsetTopic: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1000635391) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(q!, buffer: buffer, boxed: false) - } - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetTopic, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getForumTopics", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetTopic", ConstructorParameterDescription(offsetTopic)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in - let reader = BufferReader(buffer) - var result: Api.messages.ForumTopics? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics - } - return result - }) - } -} -public extension Api.functions.messages { - static func getForumTopicsByID(peer: Api.InputPeer, topics: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1358280184) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(topics.count)) - for item in topics { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getForumTopicsByID", parameters: [("peer", ConstructorParameterDescription(peer)), ("topics", ConstructorParameterDescription(topics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in - let reader = BufferReader(buffer) - var result: Api.messages.ForumTopics? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFullChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1364194508) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getFullChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in - let reader = BufferReader(buffer) - var result: Api.messages.ChatFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull - } - return result - }) - } -} -public extension Api.functions.messages { - static func getFutureChatCreatorAfterLeave(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(998051494) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getFutureChatCreatorAfterLeave", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in - let reader = BufferReader(buffer) - var result: Api.User? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.User - } - return result - }) - } -} -public extension Api.functions.messages { - static func getGameHighScores(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-400399203) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.getGameHighScores", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in - let reader = BufferReader(buffer) - var result: Api.messages.HighScores? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HighScores - } - return result - }) - } -} -public extension Api.functions.messages { - static func getHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1143203525) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getInlineBotResults(flags: Int32, bot: Api.InputUser, peer: Api.InputPeer, geoPoint: Api.InputGeoPoint?, query: String, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1364105629) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - geoPoint!.serialize(buffer, true) - } - serializeString(query, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getInlineBotResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("query", ConstructorParameterDescription(query)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotResults? in - let reader = BufferReader(buffer) - var result: Api.messages.BotResults? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotResults - } - return result - }) - } -} -public extension Api.functions.messages { - static func getInlineGameHighScores(id: Api.InputBotInlineMessageID, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(258170395) - id.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.getInlineGameHighScores", parameters: [("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in - let reader = BufferReader(buffer) - var result: Api.messages.HighScores? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HighScores - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMaskStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1678738104) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMaskStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.AllStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-39416522) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageEditData", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageEditData? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageEditData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageEditData - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1176190792) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - reaction!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(offset!, buffer: buffer, boxed: false) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reaction", ConstructorParameterDescription(reaction)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageReactionsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessageReadParticipants(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReadParticipantDate]>) { - let buffer = Buffer() - buffer.appendInt32(834782287) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMessageReadParticipants", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReadParticipantDate]? in - let reader = BufferReader(buffer) - var result: [Api.ReadParticipantDate]? + var _6: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReadParticipantDate.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1673946374) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getMessages", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.FoundStories.foundStories(Cons_foundStories(flags: _1!, count: _2!, stories: _3!, nextOffset: _4, chats: _5!, users: _6!)) } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1950707482) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1468322785) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - increment.serialize(buffer, true) - return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("increment", ConstructorParameterDescription(increment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in - let reader = BufferReader(buffer) - var result: Api.messages.MessageViews? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews - } - return result - }) - } -} -public extension Api.functions.messages { - static func getMyStickers(offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-793386500) - serializeInt64(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getMyStickers", parameters: [("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MyStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.MyStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.MyStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOldFeaturedStickers(offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2127598753) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getOldFeaturedStickers", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FeaturedStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOnlines(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1848369232) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getOnlines", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatOnlines? in - let reader = BufferReader(buffer) - var result: Api.ChatOnlines? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ChatOnlines - } - return result - }) - } -} -public extension Api.functions.messages { - static func getOutboxReadDate(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1941176739) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getOutboxReadDate", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.OutboxReadDate? in - let reader = BufferReader(buffer) - var result: Api.OutboxReadDate? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.OutboxReadDate - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPaidReactionPrivacy() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1193563562) - return (FunctionDescription(name: "messages.getPaidReactionPrivacy", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPeerDialogs(peers: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-462373635) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peers.count)) - for item in peers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getPeerDialogs", parameters: [("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPeerSettings(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-270948702) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.getPeerSettings", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerSettings? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerSettings? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerSettings - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPinnedDialogs(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-692498958) - serializeInt32(folderId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPinnedDialogs", parameters: [("folderId", ConstructorParameterDescription(folderId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.PeerDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPinnedSavedDialogs() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-700607264) - return (FunctionDescription(name: "messages.getPinnedSavedDialogs", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPollResults(peer: Api.InputPeer, msgId: Int32, pollHash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-308026565) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(pollHash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPollResults", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("pollHash", ConstructorParameterDescription(pollHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPollVotes(flags: Int32, peer: Api.InputPeer, id: Int32, option: Buffer?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1200736242) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeBytes(option!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(offset!, buffer: buffer, boxed: false) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.VotesList? in - let reader = BufferReader(buffer) - var result: Api.messages.VotesList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.VotesList - } - return result - }) - } -} -public extension Api.functions.messages { - static func getPreparedInlineMessage(bot: Api.InputUser, id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2055291464) - bot.serialize(buffer, true) - serializeString(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getPreparedInlineMessage", parameters: [("bot", ConstructorParameterDescription(bot)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PreparedInlineMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.PreparedInlineMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.PreparedInlineMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func getQuickReplies(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-729550168) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getQuickReplies", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.QuickReplies? in - let reader = BufferReader(buffer) - var result: Api.messages.QuickReplies? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.QuickReplies - } - return result - }) - } -} -public extension Api.functions.messages { - static func getQuickReplyMessages(flags: Int32, shortcutId: Int32, id: [Int32]?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1801153085) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id!.count)) - for item in id! { - serializeInt32(item, buffer: buffer, boxed: false) + else { + return nil } } - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getQuickReplyMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) } } -public extension Api.functions.messages { - static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1881817312) - peer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentLocations", parameters: [("peer", ConstructorParameterDescription(peer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages +public extension Api.stories { + enum PeerStories: TypeConstructorDescription { + public class Cons_peerStories: TypeConstructorDescription { + public var stories: Api.PeerStories + public var chats: [Api.Chat] + public var users: [Api.User] + public init(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User]) { + self.stories = stories + self.chats = chats + self.users = users } - return result - }) - } -} -public extension Api.functions.messages { - static func getRecentReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(960896434) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentReactions", parameters: [("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("peerStories", [("stories", ConstructorParameterDescription(self.stories)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } - return result - }) - } -} -public extension Api.functions.messages { - static func getRecentStickers(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1649852357) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getRecentStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.RecentStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.RecentStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.RecentStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(584962828) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedDialogs(flags: Int32, parentPeer: Api.InputPeer?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(512883865) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - parentPeer!.serialize(buffer, true) } - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + case peerStories(Cons_peerStories) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .peerStories(let _data): + if boxed { + buffer.appendInt32(-890861720) + } + _data.stories.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedDialogsByID(flags: Int32, parentPeer: Api.InputPeer?, ids: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1869585558) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - parentPeer!.serialize(buffer, true) } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(ids.count)) - for item in ids { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getSavedDialogsByID", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("ids", ConstructorParameterDescription(ids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedDialogs? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .peerStories(let _data): + return ("peerStories", [("stories", ConstructorParameterDescription(_data.stories)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedGifs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1559270965) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedGifs", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedGifs? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedGifs? + } + + public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? { + var _1: Api.PeerStories? if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedGifs + _1 = Api.parse(reader, signature: signature) as? Api.PeerStories } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1718964215) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - parentPeer!.serialize(buffer, true) - } - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(909631579) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - peer!.serialize(buffer, true) - } - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in - let reader = BufferReader(buffer) - var result: Api.messages.SavedReactionTags? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SavedReactionTags - } - return result - }) - } -} -public extension Api.functions.messages { - static func getScheduledHistory(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-183077365) - peer.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1111817116) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchCounters(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, topMsgId: Int32?, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { - let buffer = Buffer() - buffer.appendInt32(465367808) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - savedPeerId!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(filters.count)) - for item in filters { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("filters", ConstructorParameterDescription(filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in - let reader = BufferReader(buffer) - var result: [Api.messages.SearchCounter]? + var _2: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.SearchCounter.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1789130429) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - savedPeerId!.serialize(buffer, true) - } - filter.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(offsetDate, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("filter", ConstructorParameterDescription(filter)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in - let reader = BufferReader(buffer) - var result: Api.messages.SearchResultsCalendar? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsCalendar - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSearchResultsPositions(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1669386480) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - savedPeerId!.serialize(buffer, true) - } - filter.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getSearchResultsPositions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("filter", ConstructorParameterDescription(filter)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsPositions? in - let reader = BufferReader(buffer) - var result: Api.messages.SearchResultsPositions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsPositions - } - return result - }) - } -} -public extension Api.functions.messages { - static func getSplitRanges() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageRange]>) { - let buffer = Buffer() - buffer.appendInt32(486505992) - return (FunctionDescription(name: "messages.getSplitRanges", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageRange]? in - let reader = BufferReader(buffer) - var result: [Api.MessageRange]? + var _3: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageRange.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.stories.PeerStories.peerStories(Cons_peerStories(stories: _1!, chats: _2!, users: _3!)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.messages { - static func getSponsoredMessages(flags: Int32, peer: Api.InputPeer, msgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1030547536) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(msgId!, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "messages.getSponsoredMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.SponsoredMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages - } - return result - }) } } -public extension Api.functions.messages { - static func getStickerSet(stickerset: Api.InputStickerSet, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-928977804) - stickerset.serialize(buffer, true) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet +public extension Api.stories { + enum Stories: TypeConstructorDescription { + public class Cons_stories: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var stories: [Api.StoryItem] + public var pinnedToTop: [Int32]? + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.count = count + self.stories = stories + self.pinnedToTop = pinnedToTop + self.chats = chats + self.users = users } - return result - }) - } -} -public extension Api.functions.messages { - static func getStickers(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-710552671) - serializeString(emoticon, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getStickers", parameters: [("emoticon", ConstructorParameterDescription(emoticon)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Stickers? in - let reader = BufferReader(buffer) - var result: Api.messages.Stickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Stickers + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("stories", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("stories", ConstructorParameterDescription(self.stories)), ("pinnedToTop", ConstructorParameterDescription(self.pinnedToTop)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) } - return result - }) - } -} -public extension Api.functions.messages { - static func getSuggestedDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilterSuggested]>) { - let buffer = Buffer() - buffer.appendInt32(-1566780372) - return (FunctionDescription(name: "messages.getSuggestedDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilterSuggested]? in - let reader = BufferReader(buffer) - var result: [Api.DialogFilterSuggested]? + } + case stories(Cons_stories) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .stories(let _data): + if boxed { + buffer.appendInt32(1673780490) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.stories.count)) + for item in _data.stories { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.pinnedToTop!.count)) + for item in _data.pinnedToTop! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .stories(let _data): + return ("stories", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("stories", ConstructorParameterDescription(_data.stories)), ("pinnedToTop", ConstructorParameterDescription(_data.pinnedToTop)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_stories(_ reader: BufferReader) -> Stories? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StoryItem]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilterSuggested.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func getTopReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1149164102) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getTopReactions", parameters: [("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in - let reader = BufferReader(buffer) - var result: Api.messages.Reactions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + var _4: [Int32]? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } - return result - }) - } -} -public extension Api.functions.messages { - static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-251140208) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getUnreadPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1126722802) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1115713364) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - savedPeerId!.serialize(buffer, true) - } - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func getWebPage(url: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1919511901) - serializeString(url, buffer: buffer, boxed: false) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getWebPage", parameters: [("url", ConstructorParameterDescription(url)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPage? in - let reader = BufferReader(buffer) - var result: Api.messages.WebPage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.WebPage - } - return result - }) - } -} -public extension Api.functions.messages { - static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1460498287) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPagePreview? in - let reader = BufferReader(buffer) - var result: Api.messages.WebPagePreview? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.WebPagePreview - } - return result - }) - } -} -public extension Api.functions.messages { - static func hideAllChatJoinRequests(flags: Int32, peer: Api.InputPeer, link: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-528091926) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(link!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.hideAllChatJoinRequests", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2145904661) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1336717624) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func importChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1817183516) - serializeString(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.importChatInvite", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(873008187) - peer.serialize(buffer, true) - file.serialize(buffer, true) - serializeInt32(mediaCount, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.initHistoryImport", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file)), ("mediaCount", ConstructorParameterDescription(mediaCount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImport? in - let reader = BufferReader(buffer) - var result: Api.messages.HistoryImport? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImport - } - return result - }) - } -} -public extension Api.functions.messages { - static func installStickerSet(stickerset: Api.InputStickerSet, archived: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-946871200) - stickerset.serialize(buffer, true) - archived.serialize(buffer, true) - return (FunctionDescription(name: "messages.installStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("archived", ConstructorParameterDescription(archived))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSetInstallResult? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSetInstallResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSetInstallResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func markDialogUnread(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1940912392) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - parentPeer!.serialize(buffer, true) - } - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func migrateChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1568189671) - serializeInt64(chatId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.migrateChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1328014717) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("queryId", ConstructorParameterDescription(queryId)), ("replyTo", ConstructorParameterDescription(replyTo)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func rateTranscribedAudio(peer: Api.InputPeer, msgId: Int32, transcriptionId: Int64, good: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2132608815) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt64(transcriptionId, buffer: buffer, boxed: false) - good.serialize(buffer, true) - return (FunctionDescription(name: "messages.rateTranscribedAudio", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("transcriptionId", ConstructorParameterDescription(transcriptionId)), ("good", ConstructorParameterDescription(good))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-147740172) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(readMaxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("readMaxId", ConstructorParameterDescription(readMaxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readEncryptedHistory(peer: Api.InputEncryptedChat, maxDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2135648522) - peer.serialize(buffer, true) - serializeInt32(maxDate, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readEncryptedHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readFeaturedStickers(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1527873830) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readFeaturedStickers", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func readHistory(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(238054714) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(921026381) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func readMessageContents(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(916930423) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readMessageContents", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedMessages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages - } - return result - }) - } -} -public extension Api.functions.messages { - static func readPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(388019416) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.readPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1631301741) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - savedPeerId!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.readReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func readSavedHistory(parentPeer: Api.InputPeer, peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1169540261) - parentPeer.serialize(buffer, true) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.readSavedHistory", parameters: [("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func receivedMessages(maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReceivedNotifyMessage]>) { - let buffer = Buffer() - buffer.appendInt32(94983360) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.receivedMessages", parameters: [("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReceivedNotifyMessage]? in - let reader = BufferReader(buffer) - var result: [Api.ReceivedNotifyMessage]? + var _5: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReceivedNotifyMessage.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.messages { - static func receivedQueue(maxQts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { - let buffer = Buffer() - buffer.appendInt32(1436924774) - serializeInt32(maxQts, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.receivedQueue", parameters: [("maxQts", ConstructorParameterDescription(maxQts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in - let reader = BufferReader(buffer) - var result: [Int64]? + var _6: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderPinnedDialogs(flags: Int32, folderId: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(991616823) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(folderId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.reorderPinnedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderPinnedForumTopics(flags: Int32, peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(242762224) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reorderPinnedForumTopics", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1955502713) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.reorderPinnedSavedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderQuickReplies(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1613961479) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reorderQuickReplies", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reorderStickerSets(flags: Int32, order: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2016638777) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reorderStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func report(peer: Api.InputPeer, id: [Int32], option: Buffer, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-59199589) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeBytes(option, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.report", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReportResult? in - let reader = BufferReader(buffer) - var result: Api.ReportResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReportResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportEncryptedSpam(peer: Api.InputEncryptedChat) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1259113487) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportEncryptedSpam", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportMessagesDelivery(flags: Int32, peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1517122453) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.reportMessagesDelivery", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportMusicListen(id: Api.InputDocument, listenedDuration: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-574826471) - id.serialize(buffer, true) - serializeInt32(listenedDuration, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.reportMusicListen", parameters: [("id", ConstructorParameterDescription(id)), ("listenedDuration", ConstructorParameterDescription(listenedDuration))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1063567478) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - reactionPeer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reactionPeer", ConstructorParameterDescription(reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportReadMetrics(peer: Api.InputPeer, metrics: [Api.InputMessageReadMetric]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1080542694) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(metrics.count)) - for item in metrics { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.reportReadMetrics", parameters: [("peer", ConstructorParameterDescription(peer)), ("metrics", ConstructorParameterDescription(metrics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-820669733) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportSpam", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func reportSponsoredMessage(randomId: Buffer, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(315355332) - serializeBytes(randomId, buffer: buffer, boxed: false) - serializeBytes(option, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.reportSponsoredMessage", parameters: [("randomId", ConstructorParameterDescription(randomId)), ("option", ConstructorParameterDescription(option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SponsoredMessageReportResult? in - let reader = BufferReader(buffer) - var result: Api.channels.SponsoredMessageReportResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.channels.SponsoredMessageReportResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestAppWebView(flags: Int32, peer: Api.InputPeer, app: Api.InputBotApp, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1398901710) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - app.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(startParam!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - themeParams!.serialize(buffer, true) - } - serializeString(platform, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestAppWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("app", ConstructorParameterDescription(app)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in - let reader = BufferReader(buffer) - var result: Api.WebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestEncryption(userId: Api.InputUser, randomId: Int32, gA: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-162681021) - userId.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestEncryption", parameters: [("userId", ConstructorParameterDescription(userId)), ("randomId", ConstructorParameterDescription(randomId)), ("gA", ConstructorParameterDescription(gA))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in - let reader = BufferReader(buffer) - var result: Api.EncryptedChat? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedChat - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestMainWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-908059013) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(startParam!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 0) != 0 { - themeParams!.serialize(buffer, true) - } - serializeString(platform, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestMainWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in - let reader = BufferReader(buffer) - var result: Api.WebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1094336115) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - if Int(flags) & Int(1 << 3) != 0 { - serializeString(url!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 4) != 0 { - serializeString(startParam!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 0) != 0 { - themeParams!.serialize(buffer, true) - } - serializeString(platform, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("url", ConstructorParameterDescription(url)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in - let reader = BufferReader(buffer) - var result: Api.WebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?, inAppOrigin: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1991456356) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - peer!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(msgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(buttonId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(url!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeString(inAppOrigin!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.requestUrlAuth", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("url", ConstructorParameterDescription(url)), ("inAppOrigin", ConstructorParameterDescription(inAppOrigin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in - let reader = BufferReader(buffer) - var result: Api.UrlAuthResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(647873217) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(url!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeString(startParam!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - themeParams!.serialize(buffer, true) - } - serializeString(platform, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("url", ConstructorParameterDescription(url)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform)), ("replyTo", ConstructorParameterDescription(replyTo)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in - let reader = BufferReader(buffer) - var result: Api.WebViewResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewResult - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveDefaultSendAs(peer: Api.InputPeer, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-855777386) - peer.serialize(buffer, true) - sendAs.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveDefaultSendAs", parameters: [("peer", ConstructorParameterDescription(peer)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?, effect: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1420701838) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 4) != 0 { - replyTo!.serialize(buffer, true) - } - peer.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 5) != 0 { - media!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 7) != 0 { - serializeInt64(effect!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 8) != 0 { - suggestedPost!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", ConstructorParameterDescription(flags)), ("replyTo", ConstructorParameterDescription(replyTo)), ("peer", ConstructorParameterDescription(peer)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities)), ("media", ConstructorParameterDescription(media)), ("effect", ConstructorParameterDescription(effect)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveGif(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(846868683) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveGif", parameters: [("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func savePreparedInlineMessage(flags: Int32, result: Api.InputBotInlineResult, userId: Api.InputUser, peerTypes: [Api.InlineQueryPeerType]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-232816849) - serializeInt32(flags, buffer: buffer, boxed: false) - result.serialize(buffer, true) - userId.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(peerTypes!.count)) - for item in peerTypes! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "messages.savePreparedInlineMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("result", ConstructorParameterDescription(result)), ("userId", ConstructorParameterDescription(userId)), ("peerTypes", ConstructorParameterDescription(peerTypes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotPreparedInlineMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.BotPreparedInlineMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.BotPreparedInlineMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func saveRecentSticker(flags: Int32, id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(958863608) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - unsave.serialize(buffer, true) - return (FunctionDescription(name: "messages.saveRecentSticker", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputPeer?, savedPeerId: Api.InputPeer?, savedReaction: [Api.Reaction]?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(703497338) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(q, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - fromId!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - savedPeerId!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(savedReaction!.count)) - for item in savedReaction! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(addOffset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt32(maxId, buffer: buffer, boxed: false) - serializeInt32(minId, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.search", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("fromId", ConstructorParameterDescription(fromId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("savedReaction", ConstructorParameterDescription(savedReaction)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("filter", ConstructorParameterDescription(filter)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchCustomEmoji(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(739360983) - serializeString(emoticon, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchCustomEmoji", parameters: [("emoticon", ConstructorParameterDescription(emoticon)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in - let reader = BufferReader(buffer) - var result: Api.EmojiList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EmojiList - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchEmojiStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1833678516) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(q, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchEmojiStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in - let reader = BufferReader(buffer) - var result: Api.messages.FoundStickerSets? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1271290010) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(folderId!, buffer: buffer, boxed: false) - } - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(minDate, buffer: buffer, boxed: false) - serializeInt32(maxDate, buffer: buffer, boxed: false) - serializeInt32(offsetRate, buffer: buffer, boxed: false) - offsetPeer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("q", ConstructorParameterDescription(q)), ("filter", ConstructorParameterDescription(filter)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate)), ("offsetRate", ConstructorParameterDescription(offsetRate)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchSentMedia(q: String, filter: Api.MessagesFilter, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(276705696) - serializeString(q, buffer: buffer, boxed: false) - filter.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchSentMedia", parameters: [("q", ConstructorParameterDescription(q)), ("filter", ConstructorParameterDescription(filter)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in - let reader = BufferReader(buffer) - var result: Api.messages.Messages? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Messages - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(896555914) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(q, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in - let reader = BufferReader(buffer) - var result: Api.messages.FoundStickerSets? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets - } - return result - }) - } -} -public extension Api.functions.messages { - static func searchStickers(flags: Int32, q: String, emoticon: String, langCode: [String], offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(699516522) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(q, buffer: buffer, boxed: false) - serializeString(emoticon, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(langCode.count)) - for item in langCode { - serializeString(item, buffer: buffer, boxed: false) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.searchStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("emoticon", ConstructorParameterDescription(emoticon)), ("langCode", ConstructorParameterDescription(langCode)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickers? in - let reader = BufferReader(buffer) - var result: Api.messages.FoundStickers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickers - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendBotRequestedPeer(flags: Int32, peer: Api.InputPeer, msgId: Int32?, webappReqId: String?, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1818030759) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(msgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(webappReqId!, buffer: buffer, boxed: false) - } - serializeInt32(buttonId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(requestedPeers.count)) - for item in requestedPeers { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("webappReqId", ConstructorParameterDescription(webappReqId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("requestedPeers", ConstructorParameterDescription(requestedPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendEncrypted(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1157265941) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendEncrypted", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendEncryptedFile(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1431914525) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - file.serialize(buffer, true) - return (FunctionDescription(name: "messages.sendEncryptedFile", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendEncryptedService(peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(852769188) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendEncryptedService", parameters: [("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in - let reader = BufferReader(buffer) - var result: Api.messages.SentEncryptedMessage? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1060145594) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - serializeString(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 10) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 17) != 0 { - quickReplyShortcut!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 21) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("randomId", ConstructorParameterDescription(randomId)), ("queryId", ConstructorParameterDescription(queryId)), ("id", ConstructorParameterDescription(id)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(53536639) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - media.serialize(buffer, true) - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - replyMarkup!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 10) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 24) != 0 { - serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 17) != 0 { - quickReplyShortcut!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 18) != 0 { - serializeInt64(effect!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 21) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 22) != 0 { - suggestedPost!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("media", ConstructorParameterDescription(media)), ("message", ConstructorParameterDescription(message)), ("randomId", ConstructorParameterDescription(randomId)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1415369050) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - serializeString(message, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - replyMarkup!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 10) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 24) != 0 { - serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 17) != 0 { - quickReplyShortcut!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 18) != 0 { - serializeInt64(effect!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 21) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 22) != 0 { - suggestedPost!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("message", ConstructorParameterDescription(message)), ("randomId", ConstructorParameterDescription(randomId)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(469278068) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - replyTo!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(multiMedia.count)) - for item in multiMedia { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 10) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 13) != 0 { - sendAs!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 17) != 0 { - quickReplyShortcut!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 18) != 0 { - serializeInt64(effect!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 21) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("multiMedia", ConstructorParameterDescription(multiMedia)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendPaidReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64, `private`: Api.PaidReactionPrivacy?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1488702288) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeInt32(count, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - `private`!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.sendPaidReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("count", ConstructorParameterDescription(count)), ("randomId", ConstructorParameterDescription(randomId)), ("`private`", ConstructorParameterDescription(`private`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1819610593) - peer.serialize(buffer, true) - serializeInt32(shortcutId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomId.count)) - for item in randomId { - serializeInt64(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: [Api.Reaction]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-754091820) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(reaction!.count)) - for item in reaction! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1120369398) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendScreenshotNotification(peer: Api.InputPeer, replyTo: Api.InputReplyTo, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1589618665) - peer.serialize(buffer, true) - replyTo.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendVote(peer: Api.InputPeer, msgId: Int32, options: [Buffer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(283795844) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(options.count)) - for item in options { - serializeBytes(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.sendVote", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("options", ConstructorParameterDescription(options))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendWebViewData(bot: Api.InputUser, randomId: Int64, buttonText: String, data: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-603831608) - bot.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeString(buttonText, buffer: buffer, boxed: false) - serializeString(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.sendWebViewData", parameters: [("bot", ConstructorParameterDescription(bot)), ("randomId", ConstructorParameterDescription(randomId)), ("buttonText", ConstructorParameterDescription(buttonText)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func sendWebViewResultMessage(botQueryId: String, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(172168437) - serializeString(botQueryId, buffer: buffer, boxed: false) - result.serialize(buffer, true) - return (FunctionDescription(name: "messages.sendWebViewResultMessage", parameters: [("botQueryId", ConstructorParameterDescription(botQueryId)), ("result", ConstructorParameterDescription(result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewMessageSent? in - let reader = BufferReader(buffer) - var result: Api.WebViewMessageSent? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.WebViewMessageSent - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotCallbackAnswer(flags: Int32, queryId: Int64, message: String?, url: String?, cacheTime: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-712043766) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(message!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(url!, buffer: buffer, boxed: false) - } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setBotCallbackAnswer", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("message", ConstructorParameterDescription(message)), ("url", ConstructorParameterDescription(url)), ("cacheTime", ConstructorParameterDescription(cacheTime))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotPrecheckoutResults(flags: Int32, queryId: Int64, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(163765653) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(error!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.setBotPrecheckoutResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("error", ConstructorParameterDescription(error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setBotShippingResults(flags: Int32, queryId: Int64, error: String?, shippingOptions: [Api.ShippingOption]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-436833542) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(error!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(shippingOptions!.count)) - for item in shippingOptions! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "messages.setBotShippingResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("error", ConstructorParameterDescription(error)), ("shippingOptions", ConstructorParameterDescription(shippingOptions))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?, paidEnabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2041895551) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - availableReactions.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(reactionsLimit!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - paidEnabled!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("availableReactions", ConstructorParameterDescription(availableReactions)), ("reactionsLimit", ConstructorParameterDescription(reactionsLimit)), ("paidEnabled", ConstructorParameterDescription(paidEnabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setChatTheme(peer: Api.InputPeer, theme: Api.InputChatTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(135398089) - peer.serialize(buffer, true) - theme.serialize(buffer, true) - return (FunctionDescription(name: "messages.setChatTheme", parameters: [("peer", ConstructorParameterDescription(peer)), ("theme", ConstructorParameterDescription(theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1879389471) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - wallpaper!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - settings!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(id!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("wallpaper", ConstructorParameterDescription(wallpaper)), ("settings", ConstructorParameterDescription(settings)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1632299963) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setDefaultHistoryTTL", parameters: [("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1330094102) - reaction.serialize(buffer, true) - return (FunctionDescription(name: "messages.setDefaultReaction", parameters: [("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setEncryptedTyping(peer: Api.InputEncryptedChat, typing: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2031374829) - peer.serialize(buffer, true) - typing.serialize(buffer, true) - return (FunctionDescription(name: "messages.setEncryptedTyping", parameters: [("peer", ConstructorParameterDescription(peer)), ("typing", ConstructorParameterDescription(typing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setGameScore(flags: Int32, peer: Api.InputPeer, id: Int32, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1896289088) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(score, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setGameScore", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId)), ("score", ConstructorParameterDescription(score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setHistoryTTL(peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1207017500) - peer.serialize(buffer, true) - serializeInt32(period, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setHistoryTTL", parameters: [("peer", ConstructorParameterDescription(peer)), ("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func setInlineBotResults(flags: Int32, queryId: Int64, results: [Api.InputBotInlineResult], cacheTime: Int32, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1156406247) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(queryId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(results.count)) - for item in results { - item.serialize(buffer, true) - } - serializeInt32(cacheTime, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeString(nextOffset!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - switchPm!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 4) != 0 { - switchWebview!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.setInlineBotResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("results", ConstructorParameterDescription(results)), ("cacheTime", ConstructorParameterDescription(cacheTime)), ("nextOffset", ConstructorParameterDescription(nextOffset)), ("switchPm", ConstructorParameterDescription(switchPm)), ("switchWebview", ConstructorParameterDescription(switchWebview))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setInlineGameScore(flags: Int32, id: Api.InputBotInlineMessageID, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(363700068) - serializeInt32(flags, buffer: buffer, boxed: false) - id.serialize(buffer, true) - userId.serialize(buffer, true) - serializeInt32(score, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.setInlineGameScore", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId)), ("score", ConstructorParameterDescription(score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1486110434) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - action.serialize(buffer, true) - return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("action", ConstructorParameterDescription(action))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func startBot(bot: Api.InputUser, peer: Api.InputPeer, randomId: Int64, startParam: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-421563528) - bot.serialize(buffer, true) - peer.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeString(startParam, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.startBot", parameters: [("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("startParam", ConstructorParameterDescription(startParam))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func startHistoryImport(peer: Api.InputPeer, importId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1271008444) - peer.serialize(buffer, true) - serializeInt64(importId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.startHistoryImport", parameters: [("peer", ConstructorParameterDescription(peer)), ("importId", ConstructorParameterDescription(importId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?, tone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1413754042) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(toLang!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(tone!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("toLang", ConstructorParameterDescription(toLang)), ("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in - let reader = BufferReader(buffer) - var result: Api.TextWithEntities? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.TextWithEntities - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1777704297) - serializeInt32(flags, buffer: buffer, boxed: false) - bot.serialize(buffer, true) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-47326647) - enabled.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1489903017) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleDialogPin", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleNoForwards(flags: Int32, peer: Api.InputPeer, enabled: Api.Bool, requestMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1308091851) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - enabled.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(requestMsgId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.toggleNoForwards", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("enabled", ConstructorParameterDescription(enabled)), ("requestMsgId", ConstructorParameterDescription(requestMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func togglePaidReactionPrivacy(peer: Api.InputPeer, msgId: Int32, `private`: Api.PaidReactionPrivacy) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1129874869) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - `private`.serialize(buffer, true) - return (FunctionDescription(name: "messages.togglePaidReactionPrivacy", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("`private`", ConstructorParameterDescription(`private`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func togglePeerTranslations(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-461589127) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.togglePeerTranslations", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleSavedDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1400783906) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "messages.toggleSavedDialogPin", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleStickerSets(flags: Int32, stickersets: [Api.InputStickerSet]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1257951254) - serializeInt32(flags, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickersets.count)) - for item in stickersets { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.toggleStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("stickersets", ConstructorParameterDescription(stickersets))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?, rejectComment: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2130229924) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(rejectComment!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.toggleSuggestedPostApproval", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("rejectComment", ConstructorParameterDescription(rejectComment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func toggleTodoCompleted(peer: Api.InputPeer, msgId: Int32, completed: [Int32], incompleted: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-740282076) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(completed.count)) - for item in completed { - serializeInt32(item, buffer: buffer, boxed: false) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(incompleted.count)) - for item in incompleted { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.toggleTodoCompleted", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("completed", ConstructorParameterDescription(completed)), ("incompleted", ConstructorParameterDescription(incompleted))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(647928393) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.transcribeAudio", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranscribedAudio? in - let reader = BufferReader(buffer) - var result: Api.messages.TranscribedAudio? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.TranscribedAudio - } - return result - }) - } -} -public extension Api.functions.messages { - static func translateText(flags: Int32, peer: Api.InputPeer?, id: [Int32]?, text: [Api.TextWithEntities]?, toLang: String, tone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1511079099) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - peer!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id!.count)) - for item in id! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(text!.count)) - for item in text! { - item.serialize(buffer, true) - } - } - serializeString(toLang, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeString(tone!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.translateText", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("text", ConstructorParameterDescription(text)), ("toLang", ConstructorParameterDescription(toLang)), ("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranslatedText? in - let reader = BufferReader(buffer) - var result: Api.messages.TranslatedText? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.TranslatedText - } - return result - }) - } -} -public extension Api.functions.messages { - static func uninstallStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-110209570) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "messages.uninstallStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func unpinAllMessages(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(103667527) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(topMsgId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - savedPeerId!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.unpinAllMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in - let reader = BufferReader(buffer) - var result: Api.messages.AffectedHistory? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(450142282) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - filter!.serialize(buffer, true) - } - return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("filter", ConstructorParameterDescription(filter))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-983318044) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func updatePinnedForumTopic(peer: Api.InputPeer, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(392032849) - peer.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - pinned.serialize(buffer, true) - return (FunctionDescription(name: "messages.updatePinnedForumTopic", parameters: [("peer", ConstructorParameterDescription(peer)), ("topicId", ConstructorParameterDescription(topicId)), ("pinned", ConstructorParameterDescription(pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-760547348) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.updatePinnedMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.messages { - static func updateSavedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1613331948) - serializeInt32(flags, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "messages.updateSavedReactionTag", parameters: [("flags", ConstructorParameterDescription(flags)), ("reaction", ConstructorParameterDescription(reaction)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1347929239) - peer.serialize(buffer, true) - file.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadEncryptedFile", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedFile? in - let reader = BufferReader(buffer) - var result: Api.EncryptedFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.EncryptedFile - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadImportedMedia(peer: Api.InputPeer, importId: Int64, fileName: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(713433234) - peer.serialize(buffer, true) - serializeInt64(importId, buffer: buffer, boxed: false) - serializeString(fileName, buffer: buffer, boxed: false) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadImportedMedia", parameters: [("peer", ConstructorParameterDescription(peer)), ("importId", ConstructorParameterDescription(importId)), ("fileName", ConstructorParameterDescription(fileName)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in - let reader = BufferReader(buffer) - var result: Api.MessageMedia? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - return result - }) - } -} -public extension Api.functions.messages { - static func uploadMedia(flags: Int32, businessConnectionId: String?, peer: Api.InputPeer, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(345405816) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(businessConnectionId!, buffer: buffer, boxed: false) - } - peer.serialize(buffer, true) - media.serialize(buffer, true) - return (FunctionDescription(name: "messages.uploadMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("businessConnectionId", ConstructorParameterDescription(businessConnectionId)), ("peer", ConstructorParameterDescription(peer)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in - let reader = BufferReader(buffer) - var result: Api.MessageMedia? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.MessageMedia - } - return result - }) - } -} -public extension Api.functions.messages { - static func viewSponsoredMessage(randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(647902787) - serializeBytes(randomId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.viewSponsoredMessage", parameters: [("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func applyGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-152934316) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.applyGiftCode", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func assignAppStoreTransaction(receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2131921795) - serializeBytes(receipt, buffer: buffer, boxed: false) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("receipt", ConstructorParameterDescription(receipt)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func assignPlayMarketTransaction(receipt: Api.DataJSON, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-537046829) - receipt.serialize(buffer, true) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.assignPlayMarketTransaction", parameters: [("receipt", ConstructorParameterDescription(receipt)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func botCancelStarsSubscription(flags: Int32, userId: Api.InputUser, chargeId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1845102114) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeString(chargeId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.botCancelStarsSubscription", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("chargeId", ConstructorParameterDescription(chargeId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func canPurchaseStore(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1339842215) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.canPurchaseStore", parameters: [("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func changeStarsSubscription(flags: Int32, peer: Api.InputPeer, subscriptionId: String, canceled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-948500360) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(subscriptionId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - canceled!.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.changeStarsSubscription", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("subscriptionId", ConstructorParameterDescription(subscriptionId)), ("canceled", ConstructorParameterDescription(canceled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func checkCanSendGift(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1060835895) - serializeInt64(giftId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.checkCanSendGift", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckCanSendGiftResult? in - let reader = BufferReader(buffer) - var result: Api.payments.CheckCanSendGiftResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.CheckCanSendGiftResult - } - return result - }) - } -} -public extension Api.functions.payments { - static func checkGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1907247935) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.checkGiftCode", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckedGiftCode? in - let reader = BufferReader(buffer) - var result: Api.payments.CheckedGiftCode? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.CheckedGiftCode - } - return result - }) - } -} -public extension Api.functions.payments { - static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-667062079) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.clearSavedInfo", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func connectStarRefBot(peer: Api.InputPeer, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2127901834) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - return (FunctionDescription(name: "payments.connectStarRefBot", parameters: [("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in - let reader = BufferReader(buffer) - var result: Api.payments.ConnectedStarRefBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots - } - return result - }) - } -} -public extension Api.functions.payments { - static func convertStarGift(stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1958676331) - stargift.serialize(buffer, true) - return (FunctionDescription(name: "payments.convertStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func craftStarGift(stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1325832113) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stargift.count)) - for item in stargift { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.craftStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func createStarGiftCollection(peer: Api.InputPeer, title: String, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(524947079) - peer.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stargift.count)) - for item in stargift { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.createStarGiftCollection", parameters: [("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarGiftCollection? in - let reader = BufferReader(buffer) - var result: Api.StarGiftCollection? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StarGiftCollection - } - return result - }) - } -} -public extension Api.functions.payments { - static func deleteStarGiftCollection(peer: Api.InputPeer, collectionId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1386854168) - peer.serialize(buffer, true) - serializeInt32(collectionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.deleteStarGiftCollection", parameters: [("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func editConnectedStarRefBot(flags: Int32, peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-453204829) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.editConnectedStarRefBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in - let reader = BufferReader(buffer) - var result: Api.payments.ConnectedStarRefBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots - } - return result - }) - } -} -public extension Api.functions.payments { - static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(261206117) - invoiceMedia.serialize(buffer, true) - return (FunctionDescription(name: "payments.exportInvoice", parameters: [("invoiceMedia", ConstructorParameterDescription(invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ExportedInvoice? in - let reader = BufferReader(buffer) - var result: Api.payments.ExportedInvoice? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ExportedInvoice - } - return result - }) - } -} -public extension Api.functions.payments { - static func fulfillStarsSubscription(peer: Api.InputPeer, subscriptionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-866391117) - peer.serialize(buffer, true) - serializeString(subscriptionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.fulfillStarsSubscription", parameters: [("peer", ConstructorParameterDescription(peer)), ("subscriptionId", ConstructorParameterDescription(subscriptionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(779736953) - serializeString(number, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getBankCardData", parameters: [("number", ConstructorParameterDescription(number))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.BankCardData? in - let reader = BufferReader(buffer) - var result: Api.payments.BankCardData? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.BankCardData - } - return result - }) - } -} -public extension Api.functions.payments { - static func getConnectedStarRefBot(peer: Api.InputPeer, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1210476304) - peer.serialize(buffer, true) - bot.serialize(buffer, true) - return (FunctionDescription(name: "payments.getConnectedStarRefBot", parameters: [("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in - let reader = BufferReader(buffer) - var result: Api.payments.ConnectedStarRefBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots - } - return result - }) - } -} -public extension Api.functions.payments { - static func getConnectedStarRefBots(flags: Int32, peer: Api.InputPeer, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1483318611) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(offsetDate!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(offsetLink!, buffer: buffer, boxed: false) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getConnectedStarRefBots", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetLink", ConstructorParameterDescription(offsetLink)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in - let reader = BufferReader(buffer) - var result: Api.payments.ConnectedStarRefBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots - } - return result - }) - } -} -public extension Api.functions.payments { - static func getCraftStarGifts(giftId: Int64, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-49947392) - serializeInt64(giftId, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getCraftStarGifts", parameters: [("giftId", ConstructorParameterDescription(giftId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.SavedStarGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getGiveawayInfo(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-198994907) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getGiveawayInfo", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.GiveawayInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.GiveawayInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.GiveawayInfo - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(924093883) - serializeInt32(flags, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - themeParams!.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", ConstructorParameterDescription(flags)), ("invoice", ConstructorParameterDescription(invoice)), ("themeParams", ConstructorParameterDescription(themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentForm? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentForm - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPaymentReceipt(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(611897804) - peer.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getPaymentReceipt", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentReceipt? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentReceipt? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentReceipt - } - return result - }) - } -} -public extension Api.functions.payments { - static func getPremiumGiftCodeOptions(flags: Int32, boostPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.PremiumGiftCodeOption]>) { - let buffer = Buffer() - buffer.appendInt32(660060756) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - boostPeer!.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.getPremiumGiftCodeOptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("boostPeer", ConstructorParameterDescription(boostPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.PremiumGiftCodeOption]? in - let reader = BufferReader(buffer) - var result: [Api.PremiumGiftCodeOption]? + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.Stories.stories(Cons_stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!)) + } + else { + return nil + } + } + } +} +public extension Api.stories { + enum StoryReactionsList: TypeConstructorDescription { + public class Cons_storyReactionsList: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var reactions: [Api.StoryReaction] + public var chats: [Api.Chat] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.count = count + self.reactions = reactions + self.chats = chats + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyReactionsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("reactions", ConstructorParameterDescription(self.reactions)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } + } + case storyReactionsList(Cons_storyReactionsList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyReactionsList(let _data): + if boxed { + buffer.appendInt32(-1436583780) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.reactions.count)) + for item in _data.reactions { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyReactionsList(let _data): + return ("storyReactionsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("reactions", ConstructorParameterDescription(_data.reactions)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) + } + } + + public static func parse_storyReactionsList(_ reader: BufferReader) -> StoryReactionsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: [Api.StoryReaction]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftCodeOption.self) + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryReaction.self) } - return result - }) - } -} -public extension Api.functions.payments { - static func getResaleStarGifts(flags: Int32, attributesHash: Int64?, giftId: Int64, attributes: [Api.StarGiftAttributeId]?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2053087798) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(attributesHash!, buffer: buffer, boxed: false) - } - serializeInt64(giftId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes!.count)) - for item in attributes! { - item.serialize(buffer, true) - } - } - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getResaleStarGifts", parameters: [("flags", ConstructorParameterDescription(flags)), ("attributesHash", ConstructorParameterDescription(attributesHash)), ("giftId", ConstructorParameterDescription(giftId)), ("attributes", ConstructorParameterDescription(attributes)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ResaleStarGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.ResaleStarGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ResaleStarGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getSavedInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(578650699) - return (FunctionDescription(name: "payments.getSavedInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.SavedInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SavedInfo - } - return result - }) - } -} -public extension Api.functions.payments { - static func getSavedStarGift(stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1269456634) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stargift.count)) - for item in stargift { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.getSavedStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.SavedStarGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getSavedStarGifts(flags: Int32, peer: Api.InputPeer, collectionId: Int32?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1558583959) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 6) != 0 { - serializeInt32(collectionId!, buffer: buffer, boxed: false) - } - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getSavedStarGifts", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.SavedStarGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftActiveAuctions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1513074355) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftActiveAuctions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftActiveAuctions? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftActiveAuctions? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftActiveAuctions - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftAuctionAcquiredGifts(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1805831148) - serializeInt64(giftId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftAuctionAcquiredGifts", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftAuctionAcquiredGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftAuctionAcquiredGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftAuctionAcquiredGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftAuctionState(auction: Api.InputStarGiftAuction, version: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1553986774) - auction.serialize(buffer, true) - serializeInt32(version, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftAuctionState", parameters: [("auction", ConstructorParameterDescription(auction)), ("version", ConstructorParameterDescription(version))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftAuctionState? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftAuctionState? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftAuctionState - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftCollections(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1743023651) - peer.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftCollections", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftCollections? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftCollections? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftCollections - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftUpgradeAttributes(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1828948824) - serializeInt64(giftId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftUpgradeAttributes", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftUpgradeAttributes? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftUpgradeAttributes? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftUpgradeAttributes - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftUpgradePreview(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1667580751) - serializeInt64(giftId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGiftUpgradePreview", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftUpgradePreview? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftUpgradePreview? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftUpgradePreview - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGiftWithdrawalUrl(stargift: Api.InputSavedStarGift, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-798059608) - stargift.serialize(buffer, true) - password.serialize(buffer, true) - return (FunctionDescription(name: "payments.getStarGiftWithdrawalUrl", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftWithdrawalUrl? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGiftWithdrawalUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftWithdrawalUrl - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarGifts(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1000983152) - serializeInt32(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarGifts", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGifts? in - let reader = BufferReader(buffer) - var result: Api.payments.StarGifts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarGifts - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsGiftOptions(flags: Int32, userId: Api.InputUser?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsGiftOption]>) { - let buffer = Buffer() - buffer.appendInt32(-741774392) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - userId!.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.getStarsGiftOptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsGiftOption]? in - let reader = BufferReader(buffer) - var result: [Api.StarsGiftOption]? + var _4: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiftOption.self) + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsGiveawayOptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsGiveawayOption]>) { - let buffer = Buffer() - buffer.appendInt32(-1122042562) - return (FunctionDescription(name: "payments.getStarsGiveawayOptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsGiveawayOption]? in - let reader = BufferReader(buffer) - var result: [Api.StarsGiveawayOption]? + var _5: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiveawayOption.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsRevenueAdsAccountUrl(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-774377531) - peer.serialize(buffer, true) - return (FunctionDescription(name: "payments.getStarsRevenueAdsAccountUrl", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueAdsAccountUrl? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsRevenueAdsAccountUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueAdsAccountUrl + var _6: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _6 = parseString(reader) } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsRevenueStats(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-652215594) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "payments.getStarsRevenueStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueStats? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsRevenueStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueStats + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.stories.StoryReactionsList.storyReactionsList(Cons_storyReactionsList(flags: _1!, count: _2!, reactions: _3!, chats: _4!, users: _5!, nextOffset: _6)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsRevenueWithdrawalUrl(flags: Int32, peer: Api.InputPeer, amount: Int64?, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(607378578) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeInt64(amount!, buffer: buffer, boxed: false) } - password.serialize(buffer, true) - return (FunctionDescription(name: "payments.getStarsRevenueWithdrawalUrl", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("amount", ConstructorParameterDescription(amount)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueWithdrawalUrl? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsRevenueWithdrawalUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueWithdrawalUrl - } - return result - }) } } -public extension Api.functions.payments { - static func getStarsStatus(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1319744447) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "payments.getStarsStatus", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus +public extension Api.stories { + enum StoryViews: TypeConstructorDescription { + public class Cons_storyViews: TypeConstructorDescription { + public var views: [Api.StoryViews] + public var users: [Api.User] + public init(views: [Api.StoryViews], users: [Api.User]) { + self.views = views + self.users = users } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsSubscriptions(flags: Int32, peer: Api.InputPeer, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(52761285) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarsSubscriptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyViews", [("views", ConstructorParameterDescription(self.views)), ("users", ConstructorParameterDescription(self.users))]) } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsTopupOptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsTopupOption]>) { - let buffer = Buffer() - buffer.appendInt32(-1072773165) - return (FunctionDescription(name: "payments.getStarsTopupOptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsTopupOption]? in - let reader = BufferReader(buffer) - var result: [Api.StarsTopupOption]? + } + case storyViews(Cons_storyViews) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViews(let _data): + if boxed { + buffer.appendInt32(-560009955) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.views.count)) + for item in _data.views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyViews(let _data): + return ("storyViews", [("views", ConstructorParameterDescription(_data.views)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_storyViews(_ reader: BufferReader) -> StoryViews? { + var _1: [Api.StoryViews]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTopupOption.self) + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryViews.self) } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsTransactions(flags: Int32, subscriptionId: String?, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1775912279) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - serializeString(subscriptionId!, buffer: buffer, boxed: false) - } - peer.serialize(buffer, true) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getStarsTransactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("subscriptionId", ConstructorParameterDescription(subscriptionId)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus - } - return result - }) - } -} -public extension Api.functions.payments { - static func getStarsTransactionsByID(flags: Int32, peer: Api.InputPeer, id: [Api.InputStarsTransaction]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(768218808) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.getStarsTransactionsByID", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in - let reader = BufferReader(buffer) - var result: Api.payments.StarsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus - } - return result - }) - } -} -public extension Api.functions.payments { - static func getSuggestedStarRefBots(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(225134839) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getSuggestedStarRefBots", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SuggestedStarRefBots? in - let reader = BufferReader(buffer) - var result: Api.payments.SuggestedStarRefBots? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.SuggestedStarRefBots - } - return result - }) - } -} -public extension Api.functions.payments { - static func getUniqueStarGift(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1583919758) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getUniqueStarGift", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGift? in - let reader = BufferReader(buffer) - var result: Api.payments.UniqueStarGift? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGift - } - return result - }) - } -} -public extension Api.functions.payments { - static func getUniqueStarGiftValueInfo(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1130737515) - serializeString(slug, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.getUniqueStarGiftValueInfo", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGiftValueInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.UniqueStarGiftValueInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGiftValueInfo - } - return result - }) - } -} -public extension Api.functions.payments { - static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1609928480) - peer.serialize(buffer, true) - serializeInt64(giveawayId, buffer: buffer, boxed: false) - purpose.serialize(buffer, true) - return (FunctionDescription(name: "payments.launchPrepaidGiveaway", parameters: [("peer", ConstructorParameterDescription(peer)), ("giveawayId", ConstructorParameterDescription(giveawayId)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func refundStarsCharge(userId: Api.InputUser, chargeId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(632196938) - userId.serialize(buffer, true) - serializeString(chargeId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.refundStarsCharge", parameters: [("userId", ConstructorParameterDescription(userId)), ("chargeId", ConstructorParameterDescription(chargeId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func reorderStarGiftCollections(peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1020594996) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "payments.reorderStarGiftCollections", parameters: [("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func resolveStarGiftOffer(flags: Int32, offerMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-372344804) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(offerMsgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "payments.resolveStarGiftOffer", parameters: [("flags", ConstructorParameterDescription(flags)), ("offerMsgId", ConstructorParameterDescription(offerMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func saveStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(707422588) - serializeInt32(flags, buffer: buffer, boxed: false) - stargift.serialize(buffer, true) - return (FunctionDescription(name: "payments.saveStarGift", parameters: [("flags", ConstructorParameterDescription(flags)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(755192367) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(formId, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(requestedInfoId!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(shippingOptionId!, buffer: buffer, boxed: false) - } - credentials.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt64(tipAmount!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", ConstructorParameterDescription(flags)), ("formId", ConstructorParameterDescription(formId)), ("invoice", ConstructorParameterDescription(invoice)), ("requestedInfoId", ConstructorParameterDescription(requestedInfoId)), ("shippingOptionId", ConstructorParameterDescription(shippingOptionId)), ("credentials", ConstructorParameterDescription(credentials)), ("tipAmount", ConstructorParameterDescription(tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult - } - return result - }) - } -} -public extension Api.functions.payments { - static func sendStarGiftOffer(flags: Int32, peer: Api.InputPeer, slug: String, price: Api.StarsAmount, duration: Int32, randomId: Int64, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1883739327) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(slug, buffer: buffer, boxed: false) - price.serialize(buffer, true) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "payments.sendStarGiftOffer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("slug", ConstructorParameterDescription(slug)), ("price", ConstructorParameterDescription(price)), ("duration", ConstructorParameterDescription(duration)), ("randomId", ConstructorParameterDescription(randomId)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func sendStarsForm(formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2040056084) - serializeInt64(formId, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - return (FunctionDescription(name: "payments.sendStarsForm", parameters: [("formId", ConstructorParameterDescription(formId)), ("invoice", ConstructorParameterDescription(invoice))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in - let reader = BufferReader(buffer) - var result: Api.payments.PaymentResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult - } - return result - }) - } -} -public extension Api.functions.payments { - static func toggleChatStarGiftNotifications(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1626009505) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - return (FunctionDescription(name: "payments.toggleChatStarGiftNotifications", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func toggleStarGiftsPinnedToTop(peer: Api.InputPeer, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(353626032) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stargift.count)) - for item in stargift { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "payments.toggleStarGiftsPinnedToTop", parameters: [("peer", ConstructorParameterDescription(peer)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.payments { - static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2132285290) - stargift.serialize(buffer, true) - toId.serialize(buffer, true) - return (FunctionDescription(name: "payments.transferStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("toId", ConstructorParameterDescription(toId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func updateStarGiftCollection(flags: Int32, peer: Api.InputPeer, collectionId: Int32, title: String?, deleteStargift: [Api.InputSavedStarGift]?, addStargift: [Api.InputSavedStarGift]?, order: [Api.InputSavedStarGift]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1339932391) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(collectionId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(deleteStargift!.count)) - for item in deleteStargift! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(addStargift!.count)) - for item in addStargift! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - item.serialize(buffer, true) - } - } - return (FunctionDescription(name: "payments.updateStarGiftCollection", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId)), ("title", ConstructorParameterDescription(title)), ("deleteStargift", ConstructorParameterDescription(deleteStargift)), ("addStargift", ConstructorParameterDescription(addStargift)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarGiftCollection? in - let reader = BufferReader(buffer) - var result: Api.StarGiftCollection? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StarGiftCollection - } - return result - }) - } -} -public extension Api.functions.payments { - static func updateStarGiftPrice(stargift: Api.InputSavedStarGift, resellAmount: Api.StarsAmount) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-306287413) - stargift.serialize(buffer, true) - resellAmount.serialize(buffer, true) - return (FunctionDescription(name: "payments.updateStarGiftPrice", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("resellAmount", ConstructorParameterDescription(resellAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func upgradeStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1361648395) - serializeInt32(flags, buffer: buffer, boxed: false) - stargift.serialize(buffer, true) - return (FunctionDescription(name: "payments.upgradeStarGift", parameters: [("flags", ConstructorParameterDescription(flags)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.payments { - static func validateRequestedInfo(flags: Int32, invoice: Api.InputInvoice, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1228345045) - serializeInt32(flags, buffer: buffer, boxed: false) - invoice.serialize(buffer, true) - info.serialize(buffer, true) - return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("invoice", ConstructorParameterDescription(invoice)), ("info", ConstructorParameterDescription(info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in - let reader = BufferReader(buffer) - var result: Api.payments.ValidatedRequestedInfo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.payments.ValidatedRequestedInfo - } - return result - }) - } -} -public extension Api.functions.phone { - static func acceptCall(peer: Api.InputPhoneCall, gB: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1003664544) - peer.serialize(buffer, true) - serializeBytes(gB, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.acceptCall", parameters: [("peer", ConstructorParameterDescription(peer)), ("gB", ConstructorParameterDescription(gB)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func checkGroupCall(call: Api.InputGroupCall, sources: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1248003721) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sources.count)) - for item in sources { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "phone.checkGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("sources", ConstructorParameterDescription(sources))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? + var _2: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.phone { - static func confirmCall(peer: Api.InputPhoneCall, gA: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(788404002) - peer.serialize(buffer, true) - serializeBytes(gA, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.confirmCall", parameters: [("peer", ConstructorParameterDescription(peer)), ("gA", ConstructorParameterDescription(gA)), ("keyFingerprint", ConstructorParameterDescription(keyFingerprint)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.stories.StoryViews.storyViews(Cons_storyViews(views: _1!, users: _2!)) + } + else { + return nil } - return result - }) - } -} -public extension Api.functions.phone { - static func createConferenceCall(flags: Int32, randomId: Int32, publicKey: Int256?, block: Buffer?, params: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2097431739) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - serializeInt256(publicKey!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 3) != 0 { - serializeBytes(block!, buffer: buffer, boxed: false) + } +} +public extension Api.stories { + enum StoryViewsList: TypeConstructorDescription { + public class Cons_storyViewsList: TypeConstructorDescription { + public var flags: Int32 + public var count: Int32 + public var viewsCount: Int32 + public var forwardsCount: Int32 + public var reactionsCount: Int32 + public var views: [Api.StoryView] + public var chats: [Api.Chat] + public var users: [Api.User] + public var nextOffset: String? + public init(flags: Int32, count: Int32, viewsCount: Int32, forwardsCount: Int32, reactionsCount: Int32, views: [Api.StoryView], chats: [Api.Chat], users: [Api.User], nextOffset: String?) { + self.flags = flags + self.count = count + self.viewsCount = viewsCount + self.forwardsCount = forwardsCount + self.reactionsCount = reactionsCount + self.views = views + self.chats = chats + self.users = users + self.nextOffset = nextOffset + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("storyViewsList", [("flags", ConstructorParameterDescription(self.flags)), ("count", ConstructorParameterDescription(self.count)), ("viewsCount", ConstructorParameterDescription(self.viewsCount)), ("forwardsCount", ConstructorParameterDescription(self.forwardsCount)), ("reactionsCount", ConstructorParameterDescription(self.reactionsCount)), ("views", ConstructorParameterDescription(self.views)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("nextOffset", ConstructorParameterDescription(self.nextOffset))]) + } } - if Int(flags) & Int(1 << 3) != 0 { - params!.serialize(buffer, true) + case storyViewsList(Cons_storyViewsList) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .storyViewsList(let _data): + if boxed { + buffer.appendInt32(1507299269) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.count, buffer: buffer, boxed: false) + serializeInt32(_data.viewsCount, buffer: buffer, boxed: false) + serializeInt32(_data.forwardsCount, buffer: buffer, boxed: false) + serializeInt32(_data.reactionsCount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.views.count)) + for item in _data.views { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.nextOffset!, buffer: buffer, boxed: false) + } + break + } } - return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("randomId", ConstructorParameterDescription(randomId)), ("publicKey", ConstructorParameterDescription(publicKey)), ("block", ConstructorParameterDescription(block)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .storyViewsList(let _data): + return ("storyViewsList", [("flags", ConstructorParameterDescription(_data.flags)), ("count", ConstructorParameterDescription(_data.count)), ("viewsCount", ConstructorParameterDescription(_data.viewsCount)), ("forwardsCount", ConstructorParameterDescription(_data.forwardsCount)), ("reactionsCount", ConstructorParameterDescription(_data.reactionsCount)), ("views", ConstructorParameterDescription(_data.views)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("nextOffset", ConstructorParameterDescription(_data.nextOffset))]) } - return result - }) - } -} -public extension Api.functions.phone { - static func createGroupCall(flags: Int32, peer: Api.InputPeer, randomId: Int32, title: String?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1221445336) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(title!, buffer: buffer, boxed: false) } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(scheduleDate!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("title", ConstructorParameterDescription(title)), ("scheduleDate", ConstructorParameterDescription(scheduleDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func declineConferenceCallInvite(msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1011325297) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.declineConferenceCallInvite", parameters: [("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func deleteConferenceCallParticipants(flags: Int32, call: Api.InputGroupCall, ids: [Int64], block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1935276763) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(ids.count)) - for item in ids { - serializeInt64(item, buffer: buffer, boxed: false) - } - serializeBytes(block, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.deleteConferenceCallParticipants", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("ids", ConstructorParameterDescription(ids)), ("block", ConstructorParameterDescription(block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func deleteGroupCallMessages(flags: Int32, call: Api.InputGroupCall, messages: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-162573065) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "phone.deleteGroupCallMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("messages", ConstructorParameterDescription(messages))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func deleteGroupCallParticipantMessages(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(499117216) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - participant.serialize(buffer, true) - return (FunctionDescription(name: "phone.deleteGroupCallParticipantMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1295269440) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(duration, buffer: buffer, boxed: false) - reason.serialize(buffer, true) - serializeInt64(connectionId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.discardCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("duration", ConstructorParameterDescription(duration)), ("reason", ConstructorParameterDescription(reason)), ("connectionId", ConstructorParameterDescription(connectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2054648117) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func editGroupCallParticipant(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer, muted: Api.Bool?, volume: Int32?, raiseHand: Api.Bool?, videoStopped: Api.Bool?, videoPaused: Api.Bool?, presentationPaused: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1524155713) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - participant.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - muted!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(volume!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - raiseHand!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - videoStopped!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 4) != 0 { - videoPaused!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 5) != 0 { - presentationPaused!.serialize(buffer, true) - } - return (FunctionDescription(name: "phone.editGroupCallParticipant", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("participant", ConstructorParameterDescription(participant)), ("muted", ConstructorParameterDescription(muted)), ("volume", ConstructorParameterDescription(volume)), ("raiseHand", ConstructorParameterDescription(raiseHand)), ("videoStopped", ConstructorParameterDescription(videoStopped)), ("videoPaused", ConstructorParameterDescription(videoPaused)), ("presentationPaused", ConstructorParameterDescription(presentationPaused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func editGroupCallTitle(call: Api.InputGroupCall, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(480685066) - call.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.editGroupCallTitle", parameters: [("call", ConstructorParameterDescription(call)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func exportGroupCallInvite(flags: Int32, call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-425040769) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.exportGroupCallInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.ExportedGroupCallInvite? in - let reader = BufferReader(buffer) - var result: Api.phone.ExportedGroupCallInvite? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.ExportedGroupCallInvite - } - return result - }) - } -} -public extension Api.functions.phone { - static func getCallConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1430593449) - return (FunctionDescription(name: "phone.getCallConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in - let reader = BufferReader(buffer) - var result: Api.DataJSON? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.DataJSON - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCall(call: Api.InputGroupCall, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(68699611) - call.serialize(buffer, true) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-291534682) - call.serialize(buffer, true) - serializeInt32(subChainId, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.getGroupCallChainBlocks", parameters: [("call", ConstructorParameterDescription(call)), ("subChainId", ConstructorParameterDescription(subChainId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-277077702) - peer.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallJoinAs", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.JoinAsPeers? in - let reader = BufferReader(buffer) - var result: Api.phone.JoinAsPeers? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.JoinAsPeers - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallStars(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1868784386) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallStars", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStars? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCallStars? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStars - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(447879488) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallStreamChannels", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamChannels? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCallStreamChannels? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamChannels - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupCallStreamRtmpUrl(flags: Int32, peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1525991226) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - revoke.serialize(buffer, true) - return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("revoke", ConstructorParameterDescription(revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupCallStreamRtmpUrl? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamRtmpUrl - } - return result - }) - } -} -public extension Api.functions.phone { - static func getGroupParticipants(call: Api.InputGroupCall, ids: [Api.InputPeer], sources: [Int32], offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-984033109) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(ids.count)) - for item in ids { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(sources.count)) - for item in sources { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", ConstructorParameterDescription(call)), ("ids", ConstructorParameterDescription(ids)), ("sources", ConstructorParameterDescription(sources)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in - let reader = BufferReader(buffer) - var result: Api.phone.GroupParticipants? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.GroupParticipants - } - return result - }) - } -} -public extension Api.functions.phone { - static func inviteConferenceCallParticipant(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1124981115) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "phone.inviteConferenceCallParticipant", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2067345760) - call.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("users", ConstructorParameterDescription(users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, publicKey: Int256?, block: Buffer?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1883951017) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - joinAs.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(inviteHash!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeInt256(publicKey!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeBytes(block!, buffer: buffer, boxed: false) - } - params.serialize(buffer, true) - return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("joinAs", ConstructorParameterDescription(joinAs)), ("inviteHash", ConstructorParameterDescription(inviteHash)), ("publicKey", ConstructorParameterDescription(publicKey)), ("block", ConstructorParameterDescription(block)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func joinGroupCallPresentation(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-873829436) - call.serialize(buffer, true) - params.serialize(buffer, true) - return (FunctionDescription(name: "phone.joinGroupCallPresentation", parameters: [("call", ConstructorParameterDescription(call)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func leaveGroupCall(call: Api.InputGroupCall, source: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1342404601) - call.serialize(buffer, true) - serializeInt32(source, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("source", ConstructorParameterDescription(source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func leaveGroupCallPresentation(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(475058500) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.leaveGroupCallPresentation", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func receivedCall(peer: Api.InputPhoneCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(399855457) - peer.serialize(buffer, true) - return (FunctionDescription(name: "phone.receivedCall", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1124046573) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeInt32(randomId, buffer: buffer, boxed: false) - serializeBytes(gAHash, buffer: buffer, boxed: false) - `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("randomId", ConstructorParameterDescription(randomId)), ("gAHash", ConstructorParameterDescription(gAHash)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in - let reader = BufferReader(buffer) - var result: Api.phone.PhoneCall? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveCallDebug(peer: Api.InputPhoneCall, debug: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(662363518) - peer.serialize(buffer, true) - debug.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveCallDebug", parameters: [("peer", ConstructorParameterDescription(peer)), ("debug", ConstructorParameterDescription(debug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveCallLog(peer: Api.InputPhoneCall, file: Api.InputFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1092913030) - peer.serialize(buffer, true) - file.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveCallLog", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveDefaultGroupCallJoinAs(peer: Api.InputPeer, joinAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1465786252) - peer.serialize(buffer, true) - joinAs.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveDefaultGroupCallJoinAs", parameters: [("peer", ConstructorParameterDescription(peer)), ("joinAs", ConstructorParameterDescription(joinAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func saveDefaultSendAs(call: Api.InputGroupCall, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1097313745) - call.serialize(buffer, true) - sendAs.serialize(buffer, true) - return (FunctionDescription(name: "phone.saveDefaultSendAs", parameters: [("call", ConstructorParameterDescription(call)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func sendConferenceCallBroadcast(call: Api.InputGroupCall, block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-965732096) - call.serialize(buffer, true) - serializeBytes(block, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.sendConferenceCallBroadcast", parameters: [("call", ConstructorParameterDescription(call)), ("block", ConstructorParameterDescription(block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func sendGroupCallEncryptedMessage(call: Api.InputGroupCall, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-441473683) - call.serialize(buffer, true) - serializeBytes(encryptedMessage, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.sendGroupCallEncryptedMessage", parameters: [("call", ConstructorParameterDescription(call)), ("encryptedMessage", ConstructorParameterDescription(encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1311697904) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - serializeInt64(randomId, buffer: buffer, boxed: false) - message.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - sendAs!.serialize(buffer, true) - } - return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("randomId", ConstructorParameterDescription(randomId)), ("message", ConstructorParameterDescription(message)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-8744061) - peer.serialize(buffer, true) - serializeBytes(data, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.sendSignalingData", parameters: [("peer", ConstructorParameterDescription(peer)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.phone { - static func setCallRating(flags: Int32, peer: Api.InputPhoneCall, rating: Int32, comment: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1508562471) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(rating, buffer: buffer, boxed: false) - serializeString(comment, buffer: buffer, boxed: false) - return (FunctionDescription(name: "phone.setCallRating", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("rating", ConstructorParameterDescription(rating)), ("comment", ConstructorParameterDescription(comment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func startScheduledGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1451287362) - call.serialize(buffer, true) - return (FunctionDescription(name: "phone.startScheduledGroupCall", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-248985848) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 2) != 0 { - videoPortrait!.serialize(buffer, true) - } - return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("title", ConstructorParameterDescription(title)), ("videoPortrait", ConstructorParameterDescription(videoPortrait))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1757179150) - serializeInt32(flags, buffer: buffer, boxed: false) - call.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - joinMuted!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - messagesEnabled!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("joinMuted", ConstructorParameterDescription(joinMuted)), ("messagesEnabled", ConstructorParameterDescription(messagesEnabled)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.phone { - static func toggleGroupCallStartSubscription(call: Api.InputGroupCall, subscribed: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(563885286) - call.serialize(buffer, true) - subscribed.serialize(buffer, true) - return (FunctionDescription(name: "phone.toggleGroupCallStartSubscription", parameters: [("call", ConstructorParameterDescription(call)), ("subscribed", ConstructorParameterDescription(subscribed))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.photos { - static func deletePhotos(id: [Api.InputPhoto]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { - let buffer = Buffer() - buffer.appendInt32(-2016444625) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "photos.deletePhotos", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in - let reader = BufferReader(buffer) - var result: [Int64]? + + public static func parse_storyViewsList(_ reader: BufferReader) -> StoryViewsList? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.StoryView]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryView.self) } - return result - }) - } -} -public extension Api.functions.photos { - static func getUserPhotos(userId: Api.InputUser, offset: Int32, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1848823128) - userId.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt64(maxId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "photos.getUserPhotos", parameters: [("userId", ConstructorParameterDescription(userId)), ("offset", ConstructorParameterDescription(offset)), ("maxId", ConstructorParameterDescription(maxId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photos? in - let reader = BufferReader(buffer) - var result: Api.photos.Photos? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photos - } - return result - }) - } -} -public extension Api.functions.photos { - static func updateProfilePhoto(flags: Int32, bot: Api.InputUser?, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(166207545) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - bot!.serialize(buffer, true) - } - id.serialize(buffer, true) - return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.photos { - static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-515093903) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - file!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - video!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeDouble(videoStartTs!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 5) != 0 { - videoEmojiMarkup!.serialize(buffer, true) - } - return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("file", ConstructorParameterDescription(file)), ("video", ConstructorParameterDescription(video)), ("videoStartTs", ConstructorParameterDescription(videoStartTs)), ("videoEmojiMarkup", ConstructorParameterDescription(videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.photos { - static func uploadProfilePhoto(flags: Int32, bot: Api.InputUser?, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(59286453) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 5) != 0 { - bot!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 0) != 0 { - file!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - video!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeDouble(videoStartTs!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 4) != 0 { - videoEmojiMarkup!.serialize(buffer, true) - } - return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("file", ConstructorParameterDescription(file)), ("video", ConstructorParameterDescription(video)), ("videoStartTs", ConstructorParameterDescription(videoStartTs)), ("videoEmojiMarkup", ConstructorParameterDescription(videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in - let reader = BufferReader(buffer) - var result: Api.photos.Photo? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.photos.Photo - } - return result - }) - } -} -public extension Api.functions.premium { - static func applyBoost(flags: Int32, slots: [Int32]?, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1803396934) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(slots!.count)) - for item in slots! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - peer.serialize(buffer, true) - return (FunctionDescription(name: "premium.applyBoost", parameters: [("flags", ConstructorParameterDescription(flags)), ("slots", ConstructorParameterDescription(slots)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in - let reader = BufferReader(buffer) - var result: Api.premium.MyBoosts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts - } - return result - }) - } -} -public extension Api.functions.premium { - static func getBoostsList(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1626764896) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "premium.getBoostsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList - } - return result - }) - } -} -public extension Api.functions.premium { - static func getBoostsStatus(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(70197089) - peer.serialize(buffer, true) - return (FunctionDescription(name: "premium.getBoostsStatus", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsStatus? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsStatus? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsStatus - } - return result - }) - } -} -public extension Api.functions.premium { - static func getMyBoosts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(199719754) - return (FunctionDescription(name: "premium.getMyBoosts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in - let reader = BufferReader(buffer) - var result: Api.premium.MyBoosts? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts - } - return result - }) - } -} -public extension Api.functions.premium { - static func getUserBoosts(peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(965037343) - peer.serialize(buffer, true) - userId.serialize(buffer, true) - return (FunctionDescription(name: "premium.getUserBoosts", parameters: [("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in - let reader = BufferReader(buffer) - var result: Api.premium.BoostsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func finishJob(flags: Int32, jobId: String, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1327415076) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(jobId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(error!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "smsjobs.finishJob", parameters: [("flags", ConstructorParameterDescription(flags)), ("jobId", ConstructorParameterDescription(jobId)), ("error", ConstructorParameterDescription(error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func getSmsJob(jobId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2005766191) - serializeString(jobId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "smsjobs.getSmsJob", parameters: [("jobId", ConstructorParameterDescription(jobId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SmsJob? in - let reader = BufferReader(buffer) - var result: Api.SmsJob? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.SmsJob - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func getStatus() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(279353576) - return (FunctionDescription(name: "smsjobs.getStatus", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.Status? in - let reader = BufferReader(buffer) - var result: Api.smsjobs.Status? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.smsjobs.Status - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func isEligibleToJoin() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(249313744) - return (FunctionDescription(name: "smsjobs.isEligibleToJoin", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.EligibilityToJoin? in - let reader = BufferReader(buffer) - var result: Api.smsjobs.EligibilityToJoin? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.smsjobs.EligibilityToJoin - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func join() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1488007635) - return (FunctionDescription(name: "smsjobs.join", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func leave() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1734824589) - return (FunctionDescription(name: "smsjobs.leave", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.smsjobs { - static func updateSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(155164863) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "smsjobs.updateSettings", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stats { - static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1421720550) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in - let reader = BufferReader(buffer) - var result: Api.stats.BroadcastStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMegagroupStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-589330937) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - return (FunctionDescription(name: "stats.getMegagroupStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MegagroupStats? in - let reader = BufferReader(buffer) - var result: Api.stats.MegagroupStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.MegagroupStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1595212100) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in - let reader = BufferReader(buffer) - var result: Api.stats.PublicForwards? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards - } - return result - }) - } -} -public extension Api.functions.stats { - static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1226791947) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - serializeInt32(msgId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in - let reader = BufferReader(buffer) - var result: Api.stats.MessageStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func getStoryPublicForwards(peer: Api.InputPeer, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1505526026) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getStoryPublicForwards", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in - let reader = BufferReader(buffer) - var result: Api.stats.PublicForwards? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards - } - return result - }) - } -} -public extension Api.functions.stats { - static func getStoryStats(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(927985472) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stats.getStoryStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.StoryStats? in - let reader = BufferReader(buffer) - var result: Api.stats.StoryStats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stats.StoryStats - } - return result - }) - } -} -public extension Api.functions.stats { - static func loadAsyncGraph(flags: Int32, token: String, x: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1646092192) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeString(token, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeInt64(x!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("flags", ConstructorParameterDescription(flags)), ("token", ConstructorParameterDescription(token)), ("x", ConstructorParameterDescription(x))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in - let reader = BufferReader(buffer) - var result: Api.StatsGraph? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StatsGraph - } - return result - }) - } -} -public extension Api.functions.stickers { - static func addStickerToSet(stickerset: Api.InputStickerSet, sticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2041315650) - stickerset.serialize(buffer, true) - sticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.addStickerToSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("sticker", ConstructorParameterDescription(sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func changeSticker(flags: Int32, sticker: Api.InputDocument, emoji: String?, maskCoords: Api.MaskCoords?, keywords: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-179077444) - serializeInt32(flags, buffer: buffer, boxed: false) - sticker.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(emoji!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - maskCoords!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - serializeString(keywords!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stickers.changeSticker", parameters: [("flags", ConstructorParameterDescription(flags)), ("sticker", ConstructorParameterDescription(sticker)), ("emoji", ConstructorParameterDescription(emoji)), ("maskCoords", ConstructorParameterDescription(maskCoords)), ("keywords", ConstructorParameterDescription(keywords))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func changeStickerPosition(sticker: Api.InputDocument, position: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-4795190) - sticker.serialize(buffer, true) - serializeInt32(position, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.changeStickerPosition", parameters: [("sticker", ConstructorParameterDescription(sticker)), ("position", ConstructorParameterDescription(position))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func checkShortName(shortName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(676017721) - serializeString(shortName, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.checkShortName", parameters: [("shortName", ConstructorParameterDescription(shortName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stickers { - static func createStickerSet(flags: Int32, userId: Api.InputUser, title: String, shortName: String, thumb: Api.InputDocument?, stickers: [Api.InputStickerSetItem], software: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1876841625) - serializeInt32(flags, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - serializeString(shortName, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - thumb!.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - serializeString(software!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stickers.createStickerSet", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("title", ConstructorParameterDescription(title)), ("shortName", ConstructorParameterDescription(shortName)), ("thumb", ConstructorParameterDescription(thumb)), ("stickers", ConstructorParameterDescription(stickers)), ("software", ConstructorParameterDescription(software))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func deleteStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2022685804) - stickerset.serialize(buffer, true) - return (FunctionDescription(name: "stickers.deleteStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stickers { - static func removeStickerFromSet(sticker: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-143257775) - sticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.removeStickerFromSet", parameters: [("sticker", ConstructorParameterDescription(sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func renameStickerSet(stickerset: Api.InputStickerSet, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(306912256) - stickerset.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.renameStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func replaceSticker(sticker: Api.InputDocument, newSticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1184253338) - sticker.serialize(buffer, true) - newSticker.serialize(buffer, true) - return (FunctionDescription(name: "stickers.replaceSticker", parameters: [("sticker", ConstructorParameterDescription(sticker)), ("newSticker", ConstructorParameterDescription(newSticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1486204014) - serializeInt32(flags, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - thumb!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("flags", ConstructorParameterDescription(flags)), ("stickerset", ConstructorParameterDescription(stickerset)), ("thumb", ConstructorParameterDescription(thumb)), ("thumbDocumentId", ConstructorParameterDescription(thumbDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in - let reader = BufferReader(buffer) - var result: Api.messages.StickerSet? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet - } - return result - }) - } -} -public extension Api.functions.stickers { - static func suggestShortName(title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1303364867) - serializeString(title, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stickers.suggestShortName", parameters: [("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stickers.SuggestedShortName? in - let reader = BufferReader(buffer) - var result: Api.stickers.SuggestedShortName? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stickers.SuggestedShortName - } - return result - }) - } -} -public extension Api.functions.stories { - static func activateStealthMode(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1471926630) - serializeInt32(flags, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.activateStealthMode", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(820732912) - peer.serialize(buffer, true) - return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.CanSendStoryCount? in - let reader = BufferReader(buffer) - var result: Api.stories.CanSendStoryCount? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.CanSendStoryCount - } - return result - }) - } -} -public extension Api.functions.stories { - static func createAlbum(peer: Api.InputPeer, title: String, stories: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1553754395) - peer.serialize(buffer, true) - serializeString(title, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stories.count)) - for item in stories { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.createAlbum", parameters: [("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("stories", ConstructorParameterDescription(stories))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StoryAlbum? in - let reader = BufferReader(buffer) - var result: Api.StoryAlbum? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StoryAlbum - } - return result - }) - } -} -public extension Api.functions.stories { - static func deleteAlbum(peer: Api.InputPeer, albumId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1925949744) - peer.serialize(buffer, true) - serializeInt32(albumId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.deleteAlbum", parameters: [("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func deleteStories(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1369842849) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.deleteStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? + var _7: [Api.Chat]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) } - return result - }) - } -} -public extension Api.functions.stories { - static func editStory(flags: Int32, peer: Api.InputPeer, id: Int32, media: Api.InputMedia?, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule]?, music: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(744728363) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - media!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(mediaAreas!.count)) - for item in mediaAreas! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(caption!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacyRules!.count)) - for item in privacyRules! { - item.serialize(buffer, true) - } - } - if Int(flags) & Int(1 << 4) != 0 { - music!.serialize(buffer, true) - } - return (FunctionDescription(name: "stories.editStory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("media", ConstructorParameterDescription(media)), ("mediaAreas", ConstructorParameterDescription(mediaAreas)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("music", ConstructorParameterDescription(music))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func exportStoryLink(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2072899360) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.exportStoryLink", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedStoryLink? in - let reader = BufferReader(buffer) - var result: Api.ExportedStoryLink? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ExportedStoryLink - } - return result - }) - } -} -public extension Api.functions.stories { - static func getAlbumStories(peer: Api.InputPeer, albumId: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1400869535) - peer.serialize(buffer, true) - serializeInt32(albumId, buffer: buffer, boxed: false) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getAlbumStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getAlbums(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(632548039) - peer.serialize(buffer, true) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getAlbums", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Albums? in - let reader = BufferReader(buffer) - var result: Api.stories.Albums? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Albums - } - return result - }) - } -} -public extension Api.functions.stories { - static func getAllReadPeerStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1688541191) - return (FunctionDescription(name: "stories.getAllReadPeerStories", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func getAllStories(flags: Int32, state: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-290400731) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(state!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.getAllStories", parameters: [("flags", ConstructorParameterDescription(flags)), ("state", ConstructorParameterDescription(state))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.AllStories? in - let reader = BufferReader(buffer) - var result: Api.stories.AllStories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.AllStories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getChatsToSend() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1519744160) - return (FunctionDescription(name: "stories.getChatsToSend", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in - let reader = BufferReader(buffer) - var result: Api.messages.Chats? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.messages.Chats - } - return result - }) - } -} -public extension Api.functions.stories { - static func getPeerMaxIDs(id: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.RecentStory]>) { - let buffer = Buffer() - buffer.appendInt32(2018087280) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "stories.getPeerMaxIDs", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.RecentStory]? in - let reader = BufferReader(buffer) - var result: [Api.RecentStory]? + var _8: [Api.User]? if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentStory.self) + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } - return result - }) - } -} -public extension Api.functions.stories { - static func getPeerStories(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(743103056) - peer.serialize(buffer, true) - return (FunctionDescription(name: "stories.getPeerStories", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.PeerStories? in - let reader = BufferReader(buffer) - var result: Api.stories.PeerStories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.PeerStories + var _9: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _9 = parseString(reader) } - return result - }) - } -} -public extension Api.functions.stories { - static func getPinnedStories(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1478600156) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.stories.StoryViewsList.storyViewsList(Cons_storyViewsList(flags: _1!, count: _2!, viewsCount: _3!, forwardsCount: _4!, reactionsCount: _5!, views: _6!, chats: _7!, users: _8!, nextOffset: _9)) } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesArchive(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1271586794) - peer.serialize(buffer, true) - serializeInt32(offsetId, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoriesArchive", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesByID(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1467271796) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.getStoriesByID", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in - let reader = BufferReader(buffer) - var result: Api.stories.Stories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.Stories - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoriesViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(685862088) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryViews? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryViews - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoryReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1179482081) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(id, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - reaction!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 1) != 0 { - serializeString(offset!, buffer: buffer, boxed: false) - } - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoryReactionsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reaction", ConstructorParameterDescription(reaction)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryReactionsList? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryReactionsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryReactionsList - } - return result - }) - } -} -public extension Api.functions.stories { - static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2127707223) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 1) != 0 { - serializeString(q!, buffer: buffer, boxed: false) - } - serializeInt32(id, buffer: buffer, boxed: false) - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in - let reader = BufferReader(buffer) - var result: Api.stories.StoryViewsList? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.StoryViewsList - } - return result - }) - } -} -public extension Api.functions.stories { - static func incrementStoryViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1308456197) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.incrementStoryViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func readStories(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1521034552) - peer.serialize(buffer, true) - serializeInt32(maxId, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.readStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func reorderAlbums(peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2060059687) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.reorderAlbums", parameters: [("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func report(peer: Api.InputPeer, id: [Int32], option: Buffer, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(433646405) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - serializeBytes(option, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.report", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReportResult? in - let reader = BufferReader(buffer) - var result: Api.ReportResult? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.ReportResult - } - return result - }) - } -} -public extension Api.functions.stories { - static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, peer: Api.InputPeer?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-780072697) - serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(hashtag!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - area!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 2) != 0 { - peer!.serialize(buffer, true) - } - serializeString(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", ConstructorParameterDescription(flags)), ("hashtag", ConstructorParameterDescription(hashtag)), ("area", ConstructorParameterDescription(area)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in - let reader = BufferReader(buffer) - var result: Api.stories.FoundStories? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.stories.FoundStories - } - return result - }) - } -} -public extension Api.functions.stories { - static func sendReaction(flags: Int32, peer: Api.InputPeer, storyId: Int32, reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2144810674) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(storyId, buffer: buffer, boxed: false) - reaction.serialize(buffer, true) - return (FunctionDescription(name: "stories.sendReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("storyId", ConstructorParameterDescription(storyId)), ("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func sendStory(flags: Int32, peer: Api.InputPeer, media: Api.InputMedia, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, period: Int32?, fwdFromId: Api.InputPeer?, fwdFromStory: Int32?, albums: [Int32]?, music: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1885443944) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - media.serialize(buffer, true) - if Int(flags) & Int(1 << 5) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(mediaAreas!.count)) - for item in mediaAreas! { - item.serialize(buffer, true) + else { + return nil } } - if Int(flags) & Int(1 << 0) != 0 { - serializeString(caption!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacyRules.count)) - for item in privacyRules { - item.serialize(buffer, true) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 3) != 0 { - serializeInt32(period!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 6) != 0 { - fwdFromId!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 6) != 0 { - serializeInt32(fwdFromStory!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 8) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(albums!.count)) - for item in albums! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(flags) & Int(1 << 9) != 0 { - music!.serialize(buffer, true) - } - return (FunctionDescription(name: "stories.sendStory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("media", ConstructorParameterDescription(media)), ("mediaAreas", ConstructorParameterDescription(mediaAreas)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("randomId", ConstructorParameterDescription(randomId)), ("period", ConstructorParameterDescription(period)), ("fwdFromId", ConstructorParameterDescription(fwdFromId)), ("fwdFromStory", ConstructorParameterDescription(fwdFromStory)), ("albums", ConstructorParameterDescription(albums)), ("music", ConstructorParameterDescription(music))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func startLive(flags: Int32, peer: Api.InputPeer, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-798372642) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(caption!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - } - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(privacyRules.count)) - for item in privacyRules { - item.serialize(buffer, true) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 6) != 0 { - messagesEnabled!.serialize(buffer, true) - } - if Int(flags) & Int(1 << 7) != 0 { - serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.startLive", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("randomId", ConstructorParameterDescription(randomId)), ("messagesEnabled", ConstructorParameterDescription(messagesEnabled)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} -public extension Api.functions.stories { - static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2082822084) - hidden.serialize(buffer, true) - return (FunctionDescription(name: "stories.toggleAllStoriesHidden", parameters: [("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePeerStoriesHidden(peer: Api.InputPeer, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1123805756) - peer.serialize(buffer, true) - hidden.serialize(buffer, true) - return (FunctionDescription(name: "stories.togglePeerStoriesHidden", parameters: [("peer", ConstructorParameterDescription(peer)), ("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePinned(peer: Api.InputPeer, id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { - let buffer = Buffer() - buffer.appendInt32(-1703566865) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - pinned.serialize(buffer, true) - return (FunctionDescription(name: "stories.togglePinned", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("pinned", ConstructorParameterDescription(pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in - let reader = BufferReader(buffer) - var result: [Int32]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - return result - }) - } -} -public extension Api.functions.stories { - static func togglePinnedToTop(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(187268763) - peer.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "stories.togglePinnedToTop", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.stories { - static func updateAlbum(flags: Int32, peer: Api.InputPeer, albumId: Int32, title: String?, deleteStories: [Int32]?, addStories: [Int32]?, order: [Int32]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1582455222) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeInt32(albumId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 { - serializeString(title!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 1) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(deleteStories!.count)) - for item in deleteStories! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(flags) & Int(1 << 2) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(addStories!.count)) - for item in addStories! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - if Int(flags) & Int(1 << 3) != 0 { - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - serializeInt32(item, buffer: buffer, boxed: false) - } - } - return (FunctionDescription(name: "stories.updateAlbum", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId)), ("title", ConstructorParameterDescription(title)), ("deleteStories", ConstructorParameterDescription(deleteStories)), ("addStories", ConstructorParameterDescription(addStories)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StoryAlbum? in - let reader = BufferReader(buffer) - var result: Api.StoryAlbum? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StoryAlbum - } - return result - }) - } -} -public extension Api.functions.updates { - static func getChannelDifference(flags: Int32, channel: Api.InputChannel, filter: Api.ChannelMessagesFilter, pts: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(51854712) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - filter.serialize(buffer, true) - serializeInt32(pts, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "updates.getChannelDifference", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("filter", ConstructorParameterDescription(filter)), ("pts", ConstructorParameterDescription(pts)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.ChannelDifference? in - let reader = BufferReader(buffer) - var result: Api.updates.ChannelDifference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.ChannelDifference - } - return result - }) - } -} -public extension Api.functions.updates { - static func getDifference(flags: Int32, pts: Int32, ptsLimit: Int32?, ptsTotalLimit: Int32?, date: Int32, qts: Int32, qtsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(432207715) - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(pts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 1) != 0 { - serializeInt32(ptsLimit!, buffer: buffer, boxed: false) - } - if Int(flags) & Int(1 << 0) != 0 { - serializeInt32(ptsTotalLimit!, buffer: buffer, boxed: false) - } - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt32(qts, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 { - serializeInt32(qtsLimit!, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "updates.getDifference", parameters: [("flags", ConstructorParameterDescription(flags)), ("pts", ConstructorParameterDescription(pts)), ("ptsLimit", ConstructorParameterDescription(ptsLimit)), ("ptsTotalLimit", ConstructorParameterDescription(ptsTotalLimit)), ("date", ConstructorParameterDescription(date)), ("qts", ConstructorParameterDescription(qts)), ("qtsLimit", ConstructorParameterDescription(qtsLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.Difference? in - let reader = BufferReader(buffer) - var result: Api.updates.Difference? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.Difference - } - return result - }) - } -} -public extension Api.functions.updates { - static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-304838614) - return (FunctionDescription(name: "updates.getState", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.State? in - let reader = BufferReader(buffer) - var result: Api.updates.State? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.updates.State - } - return result - }) - } -} -public extension Api.functions.upload { - static func getCdnFile(fileToken: Buffer, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(962554330) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeInt64(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getCdnFile", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.CdnFile? in - let reader = BufferReader(buffer) - var result: Api.upload.CdnFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.CdnFile - } - return result - }) - } -} -public extension Api.functions.upload { - static func getCdnFileHashes(fileToken: Buffer, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1847836879) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeInt64(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getCdnFileHashes", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1101843010) - serializeInt32(flags, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt64(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getFile", parameters: [("flags", ConstructorParameterDescription(flags)), ("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in - let reader = BufferReader(buffer) - var result: Api.upload.File? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.File - } - return result - }) - } -} -public extension Api.functions.upload { - static func getFileHashes(location: Api.InputFileLocation, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1856595926) - location.serialize(buffer, true) - serializeInt64(offset, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getFileHashes", parameters: [("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func getWebFile(location: Api.InputWebFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(619086221) - location.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.getWebFile", parameters: [("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.WebFile? in - let reader = BufferReader(buffer) - var result: Api.upload.WebFile? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.upload.WebFile - } - return result - }) - } -} -public extension Api.functions.upload { - static func reuploadCdnFile(fileToken: Buffer, requestToken: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { - let buffer = Buffer() - buffer.appendInt32(-1691921240) - serializeBytes(fileToken, buffer: buffer, boxed: false) - serializeBytes(requestToken, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.reuploadCdnFile", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("requestToken", ConstructorParameterDescription(requestToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in - let reader = BufferReader(buffer) - var result: [Api.FileHash]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) - } - return result - }) - } -} -public extension Api.functions.upload { - static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-562337987) - serializeInt64(fileId, buffer: buffer, boxed: false) - serializeInt32(filePart, buffer: buffer, boxed: false) - serializeInt32(fileTotalParts, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.saveBigFilePart", parameters: [("fileId", ConstructorParameterDescription(fileId)), ("filePart", ConstructorParameterDescription(filePart)), ("fileTotalParts", ConstructorParameterDescription(fileTotalParts)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.upload { - static func saveFilePart(fileId: Int64, filePart: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1291540959) - serializeInt64(fileId, buffer: buffer, boxed: false) - serializeInt32(filePart, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - return (FunctionDescription(name: "upload.saveFilePart", parameters: [("fileId", ConstructorParameterDescription(fileId)), ("filePart", ConstructorParameterDescription(filePart)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.users { - static func getFullUser(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1240508136) - id.serialize(buffer, true) - return (FunctionDescription(name: "users.getFullUser", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.UserFull? in - let reader = BufferReader(buffer) - var result: Api.users.UserFull? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.users.UserFull - } - return result - }) - } -} -public extension Api.functions.users { - static func getRequirementsToContact(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.RequirementToContact]>) { - let buffer = Buffer() - buffer.appendInt32(-660962397) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "users.getRequirementsToContact", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.RequirementToContact]? in - let reader = BufferReader(buffer) - var result: [Api.RequirementToContact]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequirementToContact.self) - } - return result - }) - } -} -public extension Api.functions.users { - static func getSavedMusic(id: Api.InputUser, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(2022539235) - id.serialize(buffer, true) - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(limit, buffer: buffer, boxed: false) - serializeInt64(hash, buffer: buffer, boxed: false) - return (FunctionDescription(name: "users.getSavedMusic", parameters: [("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.SavedMusic? in - let reader = BufferReader(buffer) - var result: Api.users.SavedMusic? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.users.SavedMusic - } - return result - }) - } -} -public extension Api.functions.users { - static func getSavedMusicByID(id: Api.InputUser, documents: [Api.InputDocument]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1970513129) - id.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(documents.count)) - for item in documents { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "users.getSavedMusicByID", parameters: [("id", ConstructorParameterDescription(id)), ("documents", ConstructorParameterDescription(documents))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.SavedMusic? in - let reader = BufferReader(buffer) - var result: Api.users.SavedMusic? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.users.SavedMusic - } - return result - }) - } -} -public extension Api.functions.users { - static func getUsers(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { - let buffer = Buffer() - buffer.appendInt32(227648840) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(id.count)) - for item in id { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "users.getUsers", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in - let reader = BufferReader(buffer) - var result: [Api.User]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - return result - }) - } -} -public extension Api.functions.users { - static func setSecureValueErrors(id: Api.InputUser, errors: [Api.SecureValueError]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-1865902923) - id.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(errors.count)) - for item in errors { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "users.setSecureValueErrors", parameters: [("id", ConstructorParameterDescription(id)), ("errors", ConstructorParameterDescription(errors))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} -public extension Api.functions.users { - static func suggestBirthday(id: Api.InputUser, birthday: Api.Birthday) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-61656206) - id.serialize(buffer, true) - birthday.serialize(buffer, true) - return (FunctionDescription(name: "users.suggestBirthday", parameters: [("id", ConstructorParameterDescription(id)), ("birthday", ConstructorParameterDescription(birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) } } diff --git a/submodules/TelegramApi/Sources/Api41.swift b/submodules/TelegramApi/Sources/Api41.swift new file mode 100644 index 0000000000..8b6e86504c --- /dev/null +++ b/submodules/TelegramApi/Sources/Api41.swift @@ -0,0 +1,1071 @@ +public extension Api.updates { + indirect enum ChannelDifference: TypeConstructorDescription { + public class Cons_channelDifference: TypeConstructorDescription { + public var flags: Int32 + public var pts: Int32 + public var timeout: Int32? + public var newMessages: [Api.Message] + public var otherUpdates: [Api.Update] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.pts = pts + self.timeout = timeout + self.newMessages = newMessages + self.otherUpdates = otherUpdates + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("channelDifference", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("timeout", ConstructorParameterDescription(self.timeout)), ("newMessages", ConstructorParameterDescription(self.newMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_channelDifferenceEmpty: TypeConstructorDescription { + public var flags: Int32 + public var pts: Int32 + public var timeout: Int32? + public init(flags: Int32, pts: Int32, timeout: Int32?) { + self.flags = flags + self.pts = pts + self.timeout = timeout + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("channelDifferenceEmpty", [("flags", ConstructorParameterDescription(self.flags)), ("pts", ConstructorParameterDescription(self.pts)), ("timeout", ConstructorParameterDescription(self.timeout))]) + } + } + public class Cons_channelDifferenceTooLong: TypeConstructorDescription { + public var flags: Int32 + public var timeout: Int32? + public var dialog: Api.Dialog + public var messages: [Api.Message] + public var chats: [Api.Chat] + public var users: [Api.User] + public init(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) { + self.flags = flags + self.timeout = timeout + self.dialog = dialog + self.messages = messages + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("channelDifferenceTooLong", [("flags", ConstructorParameterDescription(self.flags)), ("timeout", ConstructorParameterDescription(self.timeout)), ("dialog", ConstructorParameterDescription(self.dialog)), ("messages", ConstructorParameterDescription(self.messages)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case channelDifference(Cons_channelDifference) + case channelDifferenceEmpty(Cons_channelDifferenceEmpty) + case channelDifferenceTooLong(Cons_channelDifferenceTooLong) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .channelDifference(let _data): + if boxed { + buffer.appendInt32(543450958) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.timeout!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newMessages.count)) + for item in _data.newMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.otherUpdates.count)) + for item in _data.otherUpdates { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .channelDifferenceEmpty(let _data): + if boxed { + buffer.appendInt32(1041346555) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.pts, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.timeout!, buffer: buffer, boxed: false) + } + break + case .channelDifferenceTooLong(let _data): + if boxed { + buffer.appendInt32(-1531132162) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.timeout!, buffer: buffer, boxed: false) + } + _data.dialog.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.messages.count)) + for item in _data.messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .channelDifference(let _data): + return ("channelDifference", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("newMessages", ConstructorParameterDescription(_data.newMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + case .channelDifferenceEmpty(let _data): + return ("channelDifferenceEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("pts", ConstructorParameterDescription(_data.pts)), ("timeout", ConstructorParameterDescription(_data.timeout))]) + case .channelDifferenceTooLong(let _data): + return ("channelDifferenceTooLong", [("flags", ConstructorParameterDescription(_data.flags)), ("timeout", ConstructorParameterDescription(_data.timeout)), ("dialog", ConstructorParameterDescription(_data.dialog)), ("messages", ConstructorParameterDescription(_data.messages)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Update]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _6: [Api.Chat]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _7: [Api.User]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.updates.ChannelDifference.channelDifference(Cons_channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)) + } + else { + return nil + } + } + public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _3 = reader.readInt32() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.updates.ChannelDifference.channelDifferenceEmpty(Cons_channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)) + } + else { + return nil + } + } + public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _2 = reader.readInt32() + } + var _3: Api.Dialog? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.Dialog + } + var _4: [Api.Message]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _5: [Api.Chat]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.ChannelDifference.channelDifferenceTooLong(Cons_channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)) + } + else { + return nil + } + } + } +} +public extension Api.updates { + enum Difference: TypeConstructorDescription { + public class Cons_difference: TypeConstructorDescription { + public var newMessages: [Api.Message] + public var newEncryptedMessages: [Api.EncryptedMessage] + public var otherUpdates: [Api.Update] + public var chats: [Api.Chat] + public var users: [Api.User] + public var state: Api.updates.State + public init(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State) { + self.newMessages = newMessages + self.newEncryptedMessages = newEncryptedMessages + self.otherUpdates = otherUpdates + self.chats = chats + self.users = users + self.state = state + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("difference", [("newMessages", ConstructorParameterDescription(self.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(self.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("state", ConstructorParameterDescription(self.state))]) + } + } + public class Cons_differenceEmpty: TypeConstructorDescription { + public var date: Int32 + public var seq: Int32 + public init(date: Int32, seq: Int32) { + self.date = date + self.seq = seq + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("differenceEmpty", [("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq))]) + } + } + public class Cons_differenceSlice: TypeConstructorDescription { + public var newMessages: [Api.Message] + public var newEncryptedMessages: [Api.EncryptedMessage] + public var otherUpdates: [Api.Update] + public var chats: [Api.Chat] + public var users: [Api.User] + public var intermediateState: Api.updates.State + public init(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State) { + self.newMessages = newMessages + self.newEncryptedMessages = newEncryptedMessages + self.otherUpdates = otherUpdates + self.chats = chats + self.users = users + self.intermediateState = intermediateState + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("differenceSlice", [("newMessages", ConstructorParameterDescription(self.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(self.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(self.otherUpdates)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users)), ("intermediateState", ConstructorParameterDescription(self.intermediateState))]) + } + } + public class Cons_differenceTooLong: TypeConstructorDescription { + public var pts: Int32 + public init(pts: Int32) { + self.pts = pts + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("differenceTooLong", [("pts", ConstructorParameterDescription(self.pts))]) + } + } + case difference(Cons_difference) + case differenceEmpty(Cons_differenceEmpty) + case differenceSlice(Cons_differenceSlice) + case differenceTooLong(Cons_differenceTooLong) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .difference(let _data): + if boxed { + buffer.appendInt32(16030880) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newMessages.count)) + for item in _data.newMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newEncryptedMessages.count)) + for item in _data.newEncryptedMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.otherUpdates.count)) + for item in _data.otherUpdates { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + _data.state.serialize(buffer, true) + break + case .differenceEmpty(let _data): + if boxed { + buffer.appendInt32(1567990072) + } + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.seq, buffer: buffer, boxed: false) + break + case .differenceSlice(let _data): + if boxed { + buffer.appendInt32(-1459938943) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newMessages.count)) + for item in _data.newMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.newEncryptedMessages.count)) + for item in _data.newEncryptedMessages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.otherUpdates.count)) + for item in _data.otherUpdates { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + _data.intermediateState.serialize(buffer, true) + break + case .differenceTooLong(let _data): + if boxed { + buffer.appendInt32(1258196845) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .difference(let _data): + return ("difference", [("newMessages", ConstructorParameterDescription(_data.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(_data.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("state", ConstructorParameterDescription(_data.state))]) + case .differenceEmpty(let _data): + return ("differenceEmpty", [("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq))]) + case .differenceSlice(let _data): + return ("differenceSlice", [("newMessages", ConstructorParameterDescription(_data.newMessages)), ("newEncryptedMessages", ConstructorParameterDescription(_data.newEncryptedMessages)), ("otherUpdates", ConstructorParameterDescription(_data.otherUpdates)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users)), ("intermediateState", ConstructorParameterDescription(_data.intermediateState))]) + case .differenceTooLong(let _data): + return ("differenceTooLong", [("pts", ConstructorParameterDescription(_data.pts))]) + } + } + + public static func parse_difference(_ reader: BufferReader) -> Difference? { + var _1: [Api.Message]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _2: [Api.EncryptedMessage]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) + } + var _3: [Api.Update]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Api.updates.State? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.updates.State + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.Difference.difference(Cons_difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)) + } + else { + return nil + } + } + public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.updates.Difference.differenceEmpty(Cons_differenceEmpty(date: _1!, seq: _2!)) + } + else { + return nil + } + } + public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? { + var _1: [Api.Message]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _2: [Api.EncryptedMessage]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self) + } + var _3: [Api.Update]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + var _6: Api.updates.State? + if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.updates.State + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.updates.Difference.differenceSlice(Cons_differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)) + } + else { + return nil + } + } + public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.updates.Difference.differenceTooLong(Cons_differenceTooLong(pts: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.updates { + enum State: TypeConstructorDescription { + public class Cons_state: TypeConstructorDescription { + public var pts: Int32 + public var qts: Int32 + public var date: Int32 + public var seq: Int32 + public var unreadCount: Int32 + public init(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32) { + self.pts = pts + self.qts = qts + self.date = date + self.seq = seq + self.unreadCount = unreadCount + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("state", [("pts", ConstructorParameterDescription(self.pts)), ("qts", ConstructorParameterDescription(self.qts)), ("date", ConstructorParameterDescription(self.date)), ("seq", ConstructorParameterDescription(self.seq)), ("unreadCount", ConstructorParameterDescription(self.unreadCount))]) + } + } + case state(Cons_state) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .state(let _data): + if boxed { + buffer.appendInt32(-1519637954) + } + serializeInt32(_data.pts, buffer: buffer, boxed: false) + serializeInt32(_data.qts, buffer: buffer, boxed: false) + serializeInt32(_data.date, buffer: buffer, boxed: false) + serializeInt32(_data.seq, buffer: buffer, boxed: false) + serializeInt32(_data.unreadCount, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .state(let _data): + return ("state", [("pts", ConstructorParameterDescription(_data.pts)), ("qts", ConstructorParameterDescription(_data.qts)), ("date", ConstructorParameterDescription(_data.date)), ("seq", ConstructorParameterDescription(_data.seq)), ("unreadCount", ConstructorParameterDescription(_data.unreadCount))]) + } + } + + public static func parse_state(_ reader: BufferReader) -> State? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.updates.State.state(Cons_state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.upload { + enum CdnFile: TypeConstructorDescription { + public class Cons_cdnFile: TypeConstructorDescription { + public var bytes: Buffer + public init(bytes: Buffer) { + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("cdnFile", [("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + public class Cons_cdnFileReuploadNeeded: TypeConstructorDescription { + public var requestToken: Buffer + public init(requestToken: Buffer) { + self.requestToken = requestToken + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("cdnFileReuploadNeeded", [("requestToken", ConstructorParameterDescription(self.requestToken))]) + } + } + case cdnFile(Cons_cdnFile) + case cdnFileReuploadNeeded(Cons_cdnFileReuploadNeeded) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .cdnFile(let _data): + if boxed { + buffer.appendInt32(-1449145777) + } + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + case .cdnFileReuploadNeeded(let _data): + if boxed { + buffer.appendInt32(-290921362) + } + serializeBytes(_data.requestToken, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .cdnFile(let _data): + return ("cdnFile", [("bytes", ConstructorParameterDescription(_data.bytes))]) + case .cdnFileReuploadNeeded(let _data): + return ("cdnFileReuploadNeeded", [("requestToken", ConstructorParameterDescription(_data.requestToken))]) + } + } + + public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.upload.CdnFile.cdnFile(Cons_cdnFile(bytes: _1!)) + } + else { + return nil + } + } + public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.upload.CdnFile.cdnFileReuploadNeeded(Cons_cdnFileReuploadNeeded(requestToken: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.upload { + enum File: TypeConstructorDescription { + public class Cons_file: TypeConstructorDescription { + public var type: Api.storage.FileType + public var mtime: Int32 + public var bytes: Buffer + public init(type: Api.storage.FileType, mtime: Int32, bytes: Buffer) { + self.type = type + self.mtime = mtime + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("file", [("type", ConstructorParameterDescription(self.type)), ("mtime", ConstructorParameterDescription(self.mtime)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + public class Cons_fileCdnRedirect: TypeConstructorDescription { + public var dcId: Int32 + public var fileToken: Buffer + public var encryptionKey: Buffer + public var encryptionIv: Buffer + public var fileHashes: [Api.FileHash] + public init(dcId: Int32, fileToken: Buffer, encryptionKey: Buffer, encryptionIv: Buffer, fileHashes: [Api.FileHash]) { + self.dcId = dcId + self.fileToken = fileToken + self.encryptionKey = encryptionKey + self.encryptionIv = encryptionIv + self.fileHashes = fileHashes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("fileCdnRedirect", [("dcId", ConstructorParameterDescription(self.dcId)), ("fileToken", ConstructorParameterDescription(self.fileToken)), ("encryptionKey", ConstructorParameterDescription(self.encryptionKey)), ("encryptionIv", ConstructorParameterDescription(self.encryptionIv)), ("fileHashes", ConstructorParameterDescription(self.fileHashes))]) + } + } + case file(Cons_file) + case fileCdnRedirect(Cons_fileCdnRedirect) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .file(let _data): + if boxed { + buffer.appendInt32(157948117) + } + _data.type.serialize(buffer, true) + serializeInt32(_data.mtime, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + case .fileCdnRedirect(let _data): + if boxed { + buffer.appendInt32(-242427324) + } + serializeInt32(_data.dcId, buffer: buffer, boxed: false) + serializeBytes(_data.fileToken, buffer: buffer, boxed: false) + serializeBytes(_data.encryptionKey, buffer: buffer, boxed: false) + serializeBytes(_data.encryptionIv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.fileHashes.count)) + for item in _data.fileHashes { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .file(let _data): + return ("file", [("type", ConstructorParameterDescription(_data.type)), ("mtime", ConstructorParameterDescription(_data.mtime)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + case .fileCdnRedirect(let _data): + return ("fileCdnRedirect", [("dcId", ConstructorParameterDescription(_data.dcId)), ("fileToken", ConstructorParameterDescription(_data.fileToken)), ("encryptionKey", ConstructorParameterDescription(_data.encryptionKey)), ("encryptionIv", ConstructorParameterDescription(_data.encryptionIv)), ("fileHashes", ConstructorParameterDescription(_data.fileHashes))]) + } + } + + public static func parse_file(_ reader: BufferReader) -> File? { + var _1: Api.storage.FileType? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.storage.FileType + } + var _2: Int32? + _2 = reader.readInt32() + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.upload.File.file(Cons_file(type: _1!, mtime: _2!, bytes: _3!)) + } + else { + return nil + } + } + public static func parse_fileCdnRedirect(_ reader: BufferReader) -> File? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Buffer? + _4 = parseBytes(reader) + var _5: [Api.FileHash]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.upload.File.fileCdnRedirect(Cons_fileCdnRedirect(dcId: _1!, fileToken: _2!, encryptionKey: _3!, encryptionIv: _4!, fileHashes: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.upload { + enum WebFile: TypeConstructorDescription { + public class Cons_webFile: TypeConstructorDescription { + public var size: Int32 + public var mimeType: String + public var fileType: Api.storage.FileType + public var mtime: Int32 + public var bytes: Buffer + public init(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer) { + self.size = size + self.mimeType = mimeType + self.fileType = fileType + self.mtime = mtime + self.bytes = bytes + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("webFile", [("size", ConstructorParameterDescription(self.size)), ("mimeType", ConstructorParameterDescription(self.mimeType)), ("fileType", ConstructorParameterDescription(self.fileType)), ("mtime", ConstructorParameterDescription(self.mtime)), ("bytes", ConstructorParameterDescription(self.bytes))]) + } + } + case webFile(Cons_webFile) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .webFile(let _data): + if boxed { + buffer.appendInt32(568808380) + } + serializeInt32(_data.size, buffer: buffer, boxed: false) + serializeString(_data.mimeType, buffer: buffer, boxed: false) + _data.fileType.serialize(buffer, true) + serializeInt32(_data.mtime, buffer: buffer, boxed: false) + serializeBytes(_data.bytes, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .webFile(let _data): + return ("webFile", [("size", ConstructorParameterDescription(_data.size)), ("mimeType", ConstructorParameterDescription(_data.mimeType)), ("fileType", ConstructorParameterDescription(_data.fileType)), ("mtime", ConstructorParameterDescription(_data.mtime)), ("bytes", ConstructorParameterDescription(_data.bytes))]) + } + } + + public static func parse_webFile(_ reader: BufferReader) -> WebFile? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.storage.FileType? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.storage.FileType + } + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.upload.WebFile.webFile(Cons_webFile(size: _1!, mimeType: _2!, fileType: _3!, mtime: _4!, bytes: _5!)) + } + else { + return nil + } + } + } +} +public extension Api.users { + enum SavedMusic: TypeConstructorDescription { + public class Cons_savedMusic: TypeConstructorDescription { + public var count: Int32 + public var documents: [Api.Document] + public init(count: Int32, documents: [Api.Document]) { + self.count = count + self.documents = documents + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedMusic", [("count", ConstructorParameterDescription(self.count)), ("documents", ConstructorParameterDescription(self.documents))]) + } + } + public class Cons_savedMusicNotModified: TypeConstructorDescription { + public var count: Int32 + public init(count: Int32) { + self.count = count + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("savedMusicNotModified", [("count", ConstructorParameterDescription(self.count))]) + } + } + case savedMusic(Cons_savedMusic) + case savedMusicNotModified(Cons_savedMusicNotModified) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .savedMusic(let _data): + if boxed { + buffer.appendInt32(883094167) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.documents.count)) + for item in _data.documents { + item.serialize(buffer, true) + } + break + case .savedMusicNotModified(let _data): + if boxed { + buffer.appendInt32(-477656412) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .savedMusic(let _data): + return ("savedMusic", [("count", ConstructorParameterDescription(_data.count)), ("documents", ConstructorParameterDescription(_data.documents))]) + case .savedMusicNotModified(let _data): + return ("savedMusicNotModified", [("count", ConstructorParameterDescription(_data.count))]) + } + } + + public static func parse_savedMusic(_ reader: BufferReader) -> SavedMusic? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Document]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.users.SavedMusic.savedMusic(Cons_savedMusic(count: _1!, documents: _2!)) + } + else { + return nil + } + } + public static func parse_savedMusicNotModified(_ reader: BufferReader) -> SavedMusic? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.users.SavedMusic.savedMusicNotModified(Cons_savedMusicNotModified(count: _1!)) + } + else { + return nil + } + } + } +} +public extension Api.users { + enum UserFull: TypeConstructorDescription { + public class Cons_userFull: TypeConstructorDescription { + public var fullUser: Api.UserFull + public var chats: [Api.Chat] + public var users: [Api.User] + public init(fullUser: Api.UserFull, chats: [Api.Chat], users: [Api.User]) { + self.fullUser = fullUser + self.chats = chats + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("userFull", [("fullUser", ConstructorParameterDescription(self.fullUser)), ("chats", ConstructorParameterDescription(self.chats)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case userFull(Cons_userFull) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .userFull(let _data): + if boxed { + buffer.appendInt32(997004590) + } + _data.fullUser.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.chats.count)) + for item in _data.chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .userFull(let _data): + return ("userFull", [("fullUser", ConstructorParameterDescription(_data.fullUser)), ("chats", ConstructorParameterDescription(_data.chats)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_userFull(_ reader: BufferReader) -> UserFull? { + var _1: Api.UserFull? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.UserFull + } + var _2: [Api.Chat]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _3: [Api.User]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.users.UserFull.userFull(Cons_userFull(fullUser: _1!, chats: _2!, users: _3!)) + } + else { + return nil + } + } + } +} +public extension Api.users { + enum Users: TypeConstructorDescription { + public class Cons_users: TypeConstructorDescription { + public var users: [Api.User] + public init(users: [Api.User]) { + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("users", [("users", ConstructorParameterDescription(self.users))]) + } + } + public class Cons_usersSlice: TypeConstructorDescription { + public var count: Int32 + public var users: [Api.User] + public init(count: Int32, users: [Api.User]) { + self.count = count + self.users = users + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("usersSlice", [("count", ConstructorParameterDescription(self.count)), ("users", ConstructorParameterDescription(self.users))]) + } + } + case users(Cons_users) + case usersSlice(Cons_usersSlice) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .users(let _data): + if boxed { + buffer.appendInt32(1658259128) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + case .usersSlice(let _data): + if boxed { + buffer.appendInt32(828000628) + } + serializeInt32(_data.count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(_data.users.count)) + for item in _data.users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .users(let _data): + return ("users", [("users", ConstructorParameterDescription(_data.users))]) + case .usersSlice(let _data): + return ("usersSlice", [("count", ConstructorParameterDescription(_data.count)), ("users", ConstructorParameterDescription(_data.users))]) + } + } + + public static func parse_users(_ reader: BufferReader) -> Users? { + var _1: [Api.User]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.users.Users.users(Cons_users(users: _1!)) + } + else { + return nil + } + } + public static func parse_usersSlice(_ reader: BufferReader) -> Users? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.User]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.users.Users.usersSlice(Cons_usersSlice(count: _1!, users: _2!)) + } + else { + return nil + } + } + } +} diff --git a/submodules/TelegramApi/Sources/Api42.swift b/submodules/TelegramApi/Sources/Api42.swift new file mode 100644 index 0000000000..a9b40045e3 --- /dev/null +++ b/submodules/TelegramApi/Sources/Api42.swift @@ -0,0 +1,14113 @@ +public extension Api.functions.account { + static func acceptAuthorization(botId: Int64, scope: String, publicKey: String, valueHashes: [Api.SecureValueHash], credentials: Api.SecureCredentialsEncrypted) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-202552205) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(scope, buffer: buffer, boxed: false) + serializeString(publicKey, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(valueHashes.count)) + for item in valueHashes { + item.serialize(buffer, true) + } + credentials.serialize(buffer, true) + return (FunctionDescription(name: "account.acceptAuthorization", parameters: [("botId", ConstructorParameterDescription(botId)), ("scope", ConstructorParameterDescription(scope)), ("publicKey", ConstructorParameterDescription(publicKey)), ("valueHashes", ConstructorParameterDescription(valueHashes)), ("credentials", ConstructorParameterDescription(credentials))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func cancelPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1043606090) + return (FunctionDescription(name: "account.cancelPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func changeAuthorizationSettings(flags: Int32, hash: Int64, encryptedRequestsDisabled: Api.Bool?, callRequestsDisabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1089766498) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + encryptedRequestsDisabled!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + callRequestsDisabled!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.changeAuthorizationSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("hash", ConstructorParameterDescription(hash)), ("encryptedRequestsDisabled", ConstructorParameterDescription(encryptedRequestsDisabled)), ("callRequestsDisabled", ConstructorParameterDescription(callRequestsDisabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func changePhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1891839707) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.changePhone", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(655677548) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.checkUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func clearRecentEmojiStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(404757166) + return (FunctionDescription(name: "account.clearRecentEmojiStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func confirmBotConnection(botId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1743593320) + botId.serialize(buffer, true) + return (FunctionDescription(name: "account.confirmBotConnection", parameters: [("botId", ConstructorParameterDescription(botId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func confirmPasswordEmail(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1881204448) + serializeString(code, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.confirmPasswordEmail", parameters: [("code", ConstructorParameterDescription(code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func confirmPhone(phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1596029123) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.confirmPhone", parameters: [("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func createBusinessChatLink(link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2007898482) + link.serialize(buffer, true) + return (FunctionDescription(name: "account.createBusinessChatLink", parameters: [("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in + let reader = BufferReader(buffer) + var result: Api.BusinessChatLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink + } + return result + }) + } +} +public extension Api.functions.account { + static func createTheme(flags: Int32, slug: String, title: String, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1697530880) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(slug, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + document!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "account.createTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("document", ConstructorParameterDescription(document)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func declinePasswordReset() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1284770294) + return (FunctionDescription(name: "account.declinePasswordReset", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteAccount(flags: Int32, reason: String, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1564422284) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(reason, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + password!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.deleteAccount", parameters: [("flags", ConstructorParameterDescription(flags)), ("reason", ConstructorParameterDescription(reason)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteAutoSaveExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1404829728) + return (FunctionDescription(name: "account.deleteAutoSaveExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1611085428) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.deleteBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deletePasskey(id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-172665281) + serializeString(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.deletePasskey", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1199522741) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(types.count)) + for item in types { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.deleteSecureValue", parameters: [("types", ConstructorParameterDescription(types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func deleteWebBrowserSettingsExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2036304291) + return (FunctionDescription(name: "account.deleteWebBrowserSettingsExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebBrowserSettings? in + let reader = BufferReader(buffer) + var result: Api.account.WebBrowserSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WebBrowserSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1581481689) + peer.serialize(buffer, true) + return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func editBusinessChatLink(slug: String, link: Api.InputBusinessChatLink) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1942744913) + serializeString(slug, buffer: buffer, boxed: false) + link.serialize(buffer, true) + return (FunctionDescription(name: "account.editBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BusinessChatLink? in + let reader = BufferReader(buffer) + var result: Api.BusinessChatLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BusinessChatLink + } + return result + }) + } +} +public extension Api.functions.account { + static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(489050862) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.finishTakeoutSession", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func getAccountTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(150761757) + return (FunctionDescription(name: "account.getAccountTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AccountDaysTTL? in + let reader = BufferReader(buffer) + var result: Api.AccountDaysTTL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AccountDaysTTL + } + return result + }) + } +} +public extension Api.functions.account { + static func getAllSecureValues() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { + let buffer = Buffer() + buffer.appendInt32(-1299661699) + return (FunctionDescription(name: "account.getAllSecureValues", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in + let reader = BufferReader(buffer) + var result: [Api.SecureValue]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getAuthorizationForm(botId: Int64, scope: String, publicKey: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1456907910) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeString(scope, buffer: buffer, boxed: false) + serializeString(publicKey, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getAuthorizationForm", parameters: [("botId", ConstructorParameterDescription(botId)), ("scope", ConstructorParameterDescription(scope)), ("publicKey", ConstructorParameterDescription(publicKey))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AuthorizationForm? in + let reader = BufferReader(buffer) + var result: Api.account.AuthorizationForm? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AuthorizationForm + } + return result + }) + } +} +public extension Api.functions.account { + static func getAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-484392616) + return (FunctionDescription(name: "account.getAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Authorizations? in + let reader = BufferReader(buffer) + var result: Api.account.Authorizations? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Authorizations + } + return result + }) + } +} +public extension Api.functions.account { + static func getAutoDownloadSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1457130303) + return (FunctionDescription(name: "account.getAutoDownloadSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoDownloadSettings? in + let reader = BufferReader(buffer) + var result: Api.account.AutoDownloadSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AutoDownloadSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getAutoSaveSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1379156774) + return (FunctionDescription(name: "account.getAutoSaveSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoSaveSettings? in + let reader = BufferReader(buffer) + var result: Api.account.AutoSaveSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.AutoSaveSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getBotBusinessConnection(connectionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1990746736) + serializeString(connectionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getBotBusinessConnection", parameters: [("connectionId", ConstructorParameterDescription(connectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.account { + static func getBusinessChatLinks() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1869667809) + return (FunctionDescription(name: "account.getBusinessChatLinks", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.BusinessChatLinks? in + let reader = BufferReader(buffer) + var result: Api.account.BusinessChatLinks? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.BusinessChatLinks + } + return result + }) + } +} +public extension Api.functions.account { + static func getChannelDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1999087573) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChannelDefaultEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getChannelRestrictedStatusEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(900325589) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChannelRestrictedStatusEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-700916087) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getChatThemes", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in + let reader = BufferReader(buffer) + var result: Api.account.Themes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Themes + } + return result + }) + } +} +public extension Api.functions.account { + static func getCollectibleEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(779830595) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getCollectibleEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getConnectedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1319421967) + return (FunctionDescription(name: "account.getConnectedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ConnectedBots? in + let reader = BufferReader(buffer) + var result: Api.account.ConnectedBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ConnectedBots + } + return result + }) + } +} +public extension Api.functions.account { + static func getContactSignUpNotification() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1626880216) + return (FunctionDescription(name: "account.getContactSignUpNotification", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func getContentSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1952756306) + return (FunctionDescription(name: "account.getContentSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ContentSettings? in + let reader = BufferReader(buffer) + var result: Api.account.ContentSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ContentSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultBackgroundEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1509246514) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultBackgroundEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-696962170) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultGroupPhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1856479058) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultGroupPhotoEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getDefaultProfilePhotoEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-495647960) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getDefaultProfilePhotoEmojis", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.account { + static func getGlobalPrivacySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-349483786) + return (FunctionDescription(name: "account.getGlobalPrivacySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in + let reader = BufferReader(buffer) + var result: Api.GlobalPrivacySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getMultiWallPapers(wallpapers: [Api.InputWallPaper]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.WallPaper]>) { + let buffer = Buffer() + buffer.appendInt32(1705865692) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(wallpapers.count)) + for item in wallpapers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.getMultiWallPapers", parameters: [("wallpapers", ConstructorParameterDescription(wallpapers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.WallPaper]? in + let reader = BufferReader(buffer) + var result: [Api.WallPaper]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.WallPaper.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getNotifyExceptions(flags: Int32, peer: Api.InputNotifyPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1398240377) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + peer!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.getNotifyExceptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.account { + static func getNotifySettings(peer: Api.InputNotifyPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(313765169) + peer.serialize(buffer, true) + return (FunctionDescription(name: "account.getNotifySettings", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.PeerNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.PeerNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getPaidMessagesRevenue(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(431639143) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + parentPeer!.serialize(buffer, true) + } + userId.serialize(buffer, true) + return (FunctionDescription(name: "account.getPaidMessagesRevenue", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PaidMessagesRevenue? in + let reader = BufferReader(buffer) + var result: Api.account.PaidMessagesRevenue? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PaidMessagesRevenue + } + return result + }) + } +} +public extension Api.functions.account { + static func getPasskeys() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-367063982) + return (FunctionDescription(name: "account.getPasskeys", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Passkeys? in + let reader = BufferReader(buffer) + var result: Api.account.Passkeys? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Passkeys + } + return result + }) + } +} +public extension Api.functions.account { + static func getPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1418342645) + return (FunctionDescription(name: "account.getPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Password? in + let reader = BufferReader(buffer) + var result: Api.account.Password? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Password + } + return result + }) + } +} +public extension Api.functions.account { + static func getPasswordSettings(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1663767815) + password.serialize(buffer, true) + return (FunctionDescription(name: "account.getPasswordSettings", parameters: [("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasswordSettings? in + let reader = BufferReader(buffer) + var result: Api.account.PasswordSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PasswordSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getPrivacy(key: Api.InputPrivacyKey) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-623130288) + key.serialize(buffer, true) + return (FunctionDescription(name: "account.getPrivacy", parameters: [("key", ConstructorParameterDescription(key))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in + let reader = BufferReader(buffer) + var result: Api.account.PrivacyRules? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules + } + return result + }) + } +} +public extension Api.functions.account { + static func getReactionsNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(115172684) + return (FunctionDescription(name: "account.getReactionsNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.ReactionsNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func getRecentEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(257392901) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getRecentEmojiStatuses", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in + let reader = BufferReader(buffer) + var result: Api.account.EmojiStatuses? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses + } + return result + }) + } +} +public extension Api.functions.account { + static func getSavedMusicIds(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-526557265) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getSavedMusicIds", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedMusicIds? in + let reader = BufferReader(buffer) + var result: Api.account.SavedMusicIds? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SavedMusicIds + } + return result + }) + } +} +public extension Api.functions.account { + static func getSavedRingtones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-510647672) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getSavedRingtones", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtones? in + let reader = BufferReader(buffer) + var result: Api.account.SavedRingtones? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtones + } + return result + }) + } +} +public extension Api.functions.account { + static func getSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SecureValue]>) { + let buffer = Buffer() + buffer.appendInt32(1936088002) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(types.count)) + for item in types { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.getSecureValue", parameters: [("types", ConstructorParameterDescription(types))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SecureValue]? in + let reader = BufferReader(buffer) + var result: [Api.SecureValue]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SecureValue.self) + } + return result + }) + } +} +public extension Api.functions.account { + static func getTheme(format: String, theme: Api.InputTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(978872812) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + return (FunctionDescription(name: "account.getTheme", parameters: [("format", ConstructorParameterDescription(format)), ("theme", ConstructorParameterDescription(theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func getThemes(format: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1913054296) + serializeString(format, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getThemes", parameters: [("format", ConstructorParameterDescription(format)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Themes? in + let reader = BufferReader(buffer) + var result: Api.account.Themes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Themes + } + return result + }) + } +} +public extension Api.functions.account { + static func getTmpPassword(password: Api.InputCheckPasswordSRP, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1151208273) + password.serialize(buffer, true) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getTmpPassword", parameters: [("password", ConstructorParameterDescription(password)), ("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.TmpPassword? in + let reader = BufferReader(buffer) + var result: Api.account.TmpPassword? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.TmpPassword + } + return result + }) + } +} +public extension Api.functions.account { + static func getUniqueGiftChatThemes(offset: String, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-466818615) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getUniqueGiftChatThemes", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ChatThemes? in + let reader = BufferReader(buffer) + var result: Api.account.ChatThemes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ChatThemes + } + return result + }) + } +} +public extension Api.functions.account { + static func getWallPaper(wallpaper: Api.InputWallPaper) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-57811990) + wallpaper.serialize(buffer, true) + return (FunctionDescription(name: "account.getWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in + let reader = BufferReader(buffer) + var result: Api.WallPaper? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WallPaper + } + return result + }) + } +} +public extension Api.functions.account { + static func getWallPapers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(127302966) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getWallPapers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WallPapers? in + let reader = BufferReader(buffer) + var result: Api.account.WallPapers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WallPapers + } + return result + }) + } +} +public extension Api.functions.account { + static func getWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(405695855) + return (FunctionDescription(name: "account.getWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebAuthorizations? in + let reader = BufferReader(buffer) + var result: Api.account.WebAuthorizations? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WebAuthorizations + } + return result + }) + } +} +public extension Api.functions.account { + static func getWebBrowserSettings(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1449482088) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.getWebBrowserSettings", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebBrowserSettings? in + let reader = BufferReader(buffer) + var result: Api.account.WebBrowserSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WebBrowserSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func initPasskeyRegistration() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1117079528) + return (FunctionDescription(name: "account.initPasskeyRegistration", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PasskeyRegistrationOptions? in + let reader = BufferReader(buffer) + var result: Api.account.PasskeyRegistrationOptions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PasskeyRegistrationOptions + } + return result + }) + } +} +public extension Api.functions.account { + static func initTakeoutSession(flags: Int32, fileMaxSize: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1896617296) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 { + serializeInt64(fileMaxSize!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.initTakeoutSession", parameters: [("flags", ConstructorParameterDescription(flags)), ("fileMaxSize", ConstructorParameterDescription(fileMaxSize))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.Takeout? in + let reader = BufferReader(buffer) + var result: Api.account.Takeout? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.Takeout + } + return result + }) + } +} +public extension Api.functions.account { + static func installTheme(flags: Int32, theme: Api.InputTheme?, format: String?, baseTheme: Api.BaseTheme?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-953697477) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + theme!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(format!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + baseTheme!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.installTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("theme", ConstructorParameterDescription(theme)), ("format", ConstructorParameterDescription(format)), ("baseTheme", ConstructorParameterDescription(baseTheme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func installWallPaper(wallpaper: Api.InputWallPaper, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-18000023) + wallpaper.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.installWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func invalidateSignInCodes(codes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-896866118) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(codes.count)) + for item in codes { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.invalidateSignInCodes", parameters: [("codes", ConstructorParameterDescription(codes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func registerDevice(flags: Int32, tokenType: Int32, token: String, appSandbox: Api.Bool, secret: Buffer, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-326762118) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(tokenType, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + appSandbox.serialize(buffer, true) + serializeBytes(secret, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUids.count)) + for item in otherUids { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.registerDevice", parameters: [("flags", ConstructorParameterDescription(flags)), ("tokenType", ConstructorParameterDescription(tokenType)), ("token", ConstructorParameterDescription(token)), ("appSandbox", ConstructorParameterDescription(appSandbox)), ("secret", ConstructorParameterDescription(secret)), ("otherUids", ConstructorParameterDescription(otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func registerPasskey(credential: Api.InputPasskeyCredential) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1437867990) + credential.serialize(buffer, true) + return (FunctionDescription(name: "account.registerPasskey", parameters: [("credential", ConstructorParameterDescription(credential))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Passkey? in + let reader = BufferReader(buffer) + var result: Api.Passkey? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Passkey + } + return result + }) + } +} +public extension Api.functions.account { + static func reorderUsernames(order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-279966037) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.reorderUsernames", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func reportPeer(peer: Api.InputPeer, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-977650298) + peer.serialize(buffer, true) + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.reportPeer", parameters: [("peer", ConstructorParameterDescription(peer)), ("reason", ConstructorParameterDescription(reason)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func reportProfilePhoto(peer: Api.InputPeer, photoId: Api.InputPhoto, reason: Api.ReportReason, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-91437323) + peer.serialize(buffer, true) + photoId.serialize(buffer, true) + reason.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.reportProfilePhoto", parameters: [("peer", ConstructorParameterDescription(peer)), ("photoId", ConstructorParameterDescription(photoId)), ("reason", ConstructorParameterDescription(reason)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resendPasswordEmail() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2055154197) + return (FunctionDescription(name: "account.resendPasswordEmail", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-545786948) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resetAuthorization", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetNotifySettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-612493497) + return (FunctionDescription(name: "account.resetNotifySettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetPassword() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1828139493) + return (FunctionDescription(name: "account.resetPassword", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResetPasswordResult? in + let reader = BufferReader(buffer) + var result: Api.account.ResetPasswordResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ResetPasswordResult + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWallPapers() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1153722364) + return (FunctionDescription(name: "account.resetWallPapers", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWebAuthorization(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(755087855) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resetWebAuthorization", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resetWebAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1747789204) + return (FunctionDescription(name: "account.resetWebAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func resolveBusinessChatLink(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1418913262) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.resolveBusinessChatLink", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.ResolvedBusinessChatLinks? in + let reader = BufferReader(buffer) + var result: Api.account.ResolvedBusinessChatLinks? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.ResolvedBusinessChatLinks + } + return result + }) + } +} +public extension Api.functions.account { + static func saveAutoDownloadSettings(flags: Int32, settings: Api.AutoDownloadSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1995661875) + serializeInt32(flags, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveAutoDownloadSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveAutoSaveSettings(flags: Int32, peer: Api.InputPeer?, settings: Api.AutoSaveSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-694451359) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + peer!.serialize(buffer, true) + } + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveAutoSaveSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveMusic(flags: Int32, id: Api.InputDocument, afterId: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1301859671) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + afterId!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.saveMusic", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("afterId", ConstructorParameterDescription(afterId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1038768899) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "account.saveRingtone", parameters: [("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SavedRingtone? in + let reader = BufferReader(buffer) + var result: Api.account.SavedRingtone? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SavedRingtone + } + return result + }) + } +} +public extension Api.functions.account { + static func saveSecureValue(value: Api.InputSecureValue, secureSecretId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1986010339) + value.serialize(buffer, true) + serializeInt64(secureSecretId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.saveSecureValue", parameters: [("value", ConstructorParameterDescription(value)), ("secureSecretId", ConstructorParameterDescription(secureSecretId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SecureValue? in + let reader = BufferReader(buffer) + var result: Api.SecureValue? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SecureValue + } + return result + }) + } +} +public extension Api.functions.account { + static func saveTheme(theme: Api.InputTheme, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-229175188) + theme.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "account.saveTheme", parameters: [("theme", ConstructorParameterDescription(theme)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func saveWallPaper(wallpaper: Api.InputWallPaper, unsave: Api.Bool, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1817860919) + wallpaper.serialize(buffer, true) + unsave.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.saveWallPaper", parameters: [("wallpaper", ConstructorParameterDescription(wallpaper)), ("unsave", ConstructorParameterDescription(unsave)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func sendChangePhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2108208411) + serializeString(phoneNumber, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendChangePhoneCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendConfirmPhoneCode(hash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(457157256) + serializeString(hash, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendConfirmPhoneCode", parameters: [("hash", ConstructorParameterDescription(hash)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendVerifyEmailCode(purpose: Api.EmailVerifyPurpose, email: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1730136133) + purpose.serialize(buffer, true) + serializeString(email, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.sendVerifyEmailCode", parameters: [("purpose", ConstructorParameterDescription(purpose)), ("email", ConstructorParameterDescription(email))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.SentEmailCode? in + let reader = BufferReader(buffer) + var result: Api.account.SentEmailCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.SentEmailCode + } + return result + }) + } +} +public extension Api.functions.account { + static func sendVerifyPhoneCode(phoneNumber: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1516022023) + serializeString(phoneNumber, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.sendVerifyPhoneCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.account { + static func setAccountTTL(ttl: Api.AccountDaysTTL) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(608323678) + ttl.serialize(buffer, true) + return (FunctionDescription(name: "account.setAccountTTL", parameters: [("ttl", ConstructorParameterDescription(ttl))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setAuthorizationTTL(authorizationTtlDays: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1081501024) + serializeInt32(authorizationTtlDays, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.setAuthorizationTTL", parameters: [("authorizationTtlDays", ConstructorParameterDescription(authorizationTtlDays))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setContactSignUpNotification(silent: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-806076575) + silent.serialize(buffer, true) + return (FunctionDescription(name: "account.setContactSignUpNotification", parameters: [("silent", ConstructorParameterDescription(silent))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setContentSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1250643605) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.setContentSettings", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setGlobalPrivacySettings(settings: Api.GlobalPrivacySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(517647042) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.setGlobalPrivacySettings", parameters: [("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.GlobalPrivacySettings? in + let reader = BufferReader(buffer) + var result: Api.GlobalPrivacySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.GlobalPrivacySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func setMainProfileTab(tab: Api.ProfileTab) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1575909552) + tab.serialize(buffer, true) + return (FunctionDescription(name: "account.setMainProfileTab", parameters: [("tab", ConstructorParameterDescription(tab))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func setPrivacy(key: Api.InputPrivacyKey, rules: [Api.InputPrivacyRule]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-906486552) + key.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(rules.count)) + for item in rules { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "account.setPrivacy", parameters: [("key", ConstructorParameterDescription(key)), ("rules", ConstructorParameterDescription(rules))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.PrivacyRules? in + let reader = BufferReader(buffer) + var result: Api.account.PrivacyRules? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.PrivacyRules + } + return result + }) + } +} +public extension Api.functions.account { + static func setReactionsNotifySettings(settings: Api.ReactionsNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(829220168) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.setReactionsNotifySettings", parameters: [("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReactionsNotifySettings? in + let reader = BufferReader(buffer) + var result: Api.ReactionsNotifySettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReactionsNotifySettings + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1684934807) + peer.serialize(buffer, true) + paused.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", ConstructorParameterDescription(peer)), ("paused", ConstructorParameterDescription(paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleNoPaidMessagesException(flags: Int32, parentPeer: Api.InputPeer?, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-30483850) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + parentPeer!.serialize(buffer, true) + } + userId.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleNoPaidMessagesException", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleSponsoredMessages(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1176919155) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleSponsoredMessages", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1490465654) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleUsername", parameters: [("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func toggleWebBrowserSettingsException(flags: Int32, openExternalBrowser: Api.Bool?, url: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1626161705) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + openExternalBrowser!.serialize(buffer, true) + } + serializeString(url, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.toggleWebBrowserSettingsException", parameters: [("flags", ConstructorParameterDescription(flags)), ("openExternalBrowser", ConstructorParameterDescription(openExternalBrowser)), ("url", ConstructorParameterDescription(url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.account { + static func unregisterDevice(tokenType: Int32, token: String, otherUids: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1779249670) + serializeInt32(tokenType, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(otherUids.count)) + for item in otherUids { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.unregisterDevice", parameters: [("tokenType", ConstructorParameterDescription(tokenType)), ("token", ConstructorParameterDescription(token)), ("otherUids", ConstructorParameterDescription(otherUids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBirthday(flags: Int32, birthday: Api.Birthday?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-865203183) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + birthday!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateBirthday", parameters: [("flags", ConstructorParameterDescription(flags)), ("birthday", ConstructorParameterDescription(birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessAwayMessage(flags: Int32, message: Api.InputBusinessAwayMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1570078811) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + message!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateBusinessAwayMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessGreetingMessage(flags: Int32, message: Api.InputBusinessGreetingMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1724755908) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + message!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateBusinessGreetingMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessIntro(flags: Int32, intro: Api.InputBusinessIntro?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1508585420) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + intro!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateBusinessIntro", parameters: [("flags", ConstructorParameterDescription(flags)), ("intro", ConstructorParameterDescription(intro))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1637149926) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + geoPoint!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeString(address!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.updateBusinessLocation", parameters: [("flags", ConstructorParameterDescription(flags)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateBusinessWorkHours(flags: Int32, businessWorkHours: Api.BusinessWorkHours?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1258348646) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + businessWorkHours!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateBusinessWorkHours", parameters: [("flags", ConstructorParameterDescription(flags)), ("businessWorkHours", ConstructorParameterDescription(businessWorkHours))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateColor(flags: Int32, color: Api.PeerColor?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1749885262) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + color!.serialize(buffer, true) + } + return (FunctionDescription(name: "account.updateColor", parameters: [("flags", ConstructorParameterDescription(flags)), ("color", ConstructorParameterDescription(color))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateConnectedBot(flags: Int32, rights: Api.BusinessBotRights?, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1721797758) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + rights!.serialize(buffer, true) + } + bot.serialize(buffer, true) + recipients.serialize(buffer, true) + return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("rights", ConstructorParameterDescription(rights)), ("bot", ConstructorParameterDescription(bot)), ("recipients", ConstructorParameterDescription(recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.account { + static func updateDeviceLocked(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(954152242) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.updateDeviceLocked", parameters: [("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateEmojiStatus(emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-70001045) + emojiStatus.serialize(buffer, true) + return (FunctionDescription(name: "account.updateEmojiStatus", parameters: [("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateNotifySettings(peer: Api.InputNotifyPeer, settings: Api.InputPeerNotifySettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2067899501) + peer.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.updateNotifySettings", parameters: [("peer", ConstructorParameterDescription(peer)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updatePasswordSettings(password: Api.InputCheckPasswordSRP, newSettings: Api.account.PasswordInputSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1516564433) + password.serialize(buffer, true) + newSettings.serialize(buffer, true) + return (FunctionDescription(name: "account.updatePasswordSettings", parameters: [("password", ConstructorParameterDescription(password)), ("newSettings", ConstructorParameterDescription(newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updatePersonalChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-649919008) + channel.serialize(buffer, true) + return (FunctionDescription(name: "account.updatePersonalChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateProfile(flags: Int32, firstName: String?, lastName: String?, about: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2018596725) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(firstName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(lastName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(about!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "account.updateProfile", parameters: [("flags", ConstructorParameterDescription(flags)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName)), ("about", ConstructorParameterDescription(about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func updateStatus(offline: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1713919532) + offline.serialize(buffer, true) + return (FunctionDescription(name: "account.updateStatus", parameters: [("offline", ConstructorParameterDescription(offline))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.account { + static func updateTheme(flags: Int32, format: String, theme: Api.InputTheme, slug: String?, title: String?, document: Api.InputDocument?, settings: [Api.InputThemeSettings]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(737414348) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(format, buffer: buffer, boxed: false) + theme.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(slug!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + document!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(settings!.count)) + for item in settings! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "account.updateTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("format", ConstructorParameterDescription(format)), ("theme", ConstructorParameterDescription(theme)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("document", ConstructorParameterDescription(document)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Theme? in + let reader = BufferReader(buffer) + var result: Api.Theme? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Theme + } + return result + }) + } +} +public extension Api.functions.account { + static func updateUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1040964988) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.updateUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.account { + static func updateWebBrowserSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1696627970) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.updateWebBrowserSettings", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.WebBrowserSettings? in + let reader = BufferReader(buffer) + var result: Api.account.WebBrowserSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.WebBrowserSettings + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadRingtone(file: Api.InputFile, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2095414366) + file.serialize(buffer, true) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.uploadRingtone", parameters: [("file", ConstructorParameterDescription(file)), ("fileName", ConstructorParameterDescription(fileName)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadTheme(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, fileName: String, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(473805619) + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + thumb!.serialize(buffer, true) + } + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.uploadTheme", parameters: [("flags", ConstructorParameterDescription(flags)), ("file", ConstructorParameterDescription(file)), ("thumb", ConstructorParameterDescription(thumb)), ("fileName", ConstructorParameterDescription(fileName)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.account { + static func uploadWallPaper(flags: Int32, file: Api.InputFile, mimeType: String, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-476410109) + serializeInt32(flags, buffer: buffer, boxed: false) + file.serialize(buffer, true) + serializeString(mimeType, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "account.uploadWallPaper", parameters: [("flags", ConstructorParameterDescription(flags)), ("file", ConstructorParameterDescription(file)), ("mimeType", ConstructorParameterDescription(mimeType)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WallPaper? in + let reader = BufferReader(buffer) + var result: Api.WallPaper? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WallPaper + } + return result + }) + } +} +public extension Api.functions.account { + static func verifyEmail(purpose: Api.EmailVerifyPurpose, verification: Api.EmailVerification) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(53322959) + purpose.serialize(buffer, true) + verification.serialize(buffer, true) + return (FunctionDescription(name: "account.verifyEmail", parameters: [("purpose", ConstructorParameterDescription(purpose)), ("verification", ConstructorParameterDescription(verification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmailVerified? in + let reader = BufferReader(buffer) + var result: Api.account.EmailVerified? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.account.EmailVerified + } + return result + }) + } +} +public extension Api.functions.account { + static func verifyPhone(phoneNumber: String, phoneCodeHash: String, phoneCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1305716726) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(phoneCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "account.verifyPhone", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func createTone(flags: Int32, emojiId: Int64, title: String, prompt: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1252538643) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(emojiId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(prompt, buffer: buffer, boxed: false) + return (FunctionDescription(name: "aicompose.createTone", parameters: [("flags", ConstructorParameterDescription(flags)), ("emojiId", ConstructorParameterDescription(emojiId)), ("title", ConstructorParameterDescription(title)), ("prompt", ConstructorParameterDescription(prompt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AiComposeTone? in + let reader = BufferReader(buffer) + var result: Api.AiComposeTone? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AiComposeTone + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func deleteTone(tone: Api.InputAiComposeTone) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-583454358) + tone.serialize(buffer, true) + return (FunctionDescription(name: "aicompose.deleteTone", parameters: [("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func getTone(tone: Api.InputAiComposeTone) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1293370877) + tone.serialize(buffer, true) + return (FunctionDescription(name: "aicompose.getTone", parameters: [("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.aicompose.Tones? in + let reader = BufferReader(buffer) + var result: Api.aicompose.Tones? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.aicompose.Tones + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func getToneExample(tone: Api.InputAiComposeTone, num: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-776688876) + tone.serialize(buffer, true) + serializeInt32(num, buffer: buffer, boxed: false) + return (FunctionDescription(name: "aicompose.getToneExample", parameters: [("tone", ConstructorParameterDescription(tone)), ("num", ConstructorParameterDescription(num))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AiComposeToneExample? in + let reader = BufferReader(buffer) + var result: Api.AiComposeToneExample? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AiComposeToneExample + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func getTones(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1412066815) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "aicompose.getTones", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.aicompose.Tones? in + let reader = BufferReader(buffer) + var result: Api.aicompose.Tones? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.aicompose.Tones + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func saveTone(tone: Api.InputAiComposeTone, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(394447793) + tone.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "aicompose.saveTone", parameters: [("tone", ConstructorParameterDescription(tone)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.aicompose { + static func updateTone(flags: Int32, tone: Api.InputAiComposeTone, displayAuthor: Api.Bool?, emojiId: Int64?, title: String?, prompt: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1875128487) + serializeInt32(flags, buffer: buffer, boxed: false) + tone.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + displayAuthor!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt64(emojiId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeString(prompt!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "aicompose.updateTone", parameters: [("flags", ConstructorParameterDescription(flags)), ("tone", ConstructorParameterDescription(tone)), ("displayAuthor", ConstructorParameterDescription(displayAuthor)), ("emojiId", ConstructorParameterDescription(emojiId)), ("title", ConstructorParameterDescription(title)), ("prompt", ConstructorParameterDescription(prompt))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AiComposeTone? in + let reader = BufferReader(buffer) + var result: Api.AiComposeTone? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AiComposeTone + } + return result + }) + } +} +public extension Api.functions.auth { + static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-392909491) + serializeBytes(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Authorization? in + let reader = BufferReader(buffer) + var result: Api.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func bindTempAuthKey(permAuthKeyId: Int64, nonce: Int64, expiresAt: Int32, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-841733627) + serializeInt64(permAuthKeyId, buffer: buffer, boxed: false) + serializeInt64(nonce, buffer: buffer, boxed: false) + serializeInt32(expiresAt, buffer: buffer, boxed: false) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.bindTempAuthKey", parameters: [("permAuthKeyId", ConstructorParameterDescription(permAuthKeyId)), ("nonce", ConstructorParameterDescription(nonce)), ("expiresAt", ConstructorParameterDescription(expiresAt)), ("encryptedMessage", ConstructorParameterDescription(encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func cancelCode(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(520357240) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.cancelCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func checkPaidAuth(phoneNumber: String, phoneCodeHash: String, formId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1457889180) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.checkPaidAuth", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("formId", ConstructorParameterDescription(formId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func checkPassword(password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-779399914) + password.serialize(buffer, true) + return (FunctionDescription(name: "auth.checkPassword", parameters: [("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func checkRecoveryPassword(code: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(221691769) + serializeString(code, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.checkRecoveryPassword", parameters: [("code", ConstructorParameterDescription(code))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func dropTempAuthKeys(exceptAuthKeys: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1907842680) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(exceptAuthKeys.count)) + for item in exceptAuthKeys { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.dropTempAuthKeys", parameters: [("exceptAuthKeys", ConstructorParameterDescription(exceptAuthKeys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func exportAuthorization(dcId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-440401971) + serializeInt32(dcId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.exportAuthorization", parameters: [("dcId", ConstructorParameterDescription(dcId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.ExportedAuthorization? in + let reader = BufferReader(buffer) + var result: Api.auth.ExportedAuthorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.ExportedAuthorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func exportLoginToken(apiId: Int32, apiHash: String, exceptIds: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1210022402) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(exceptIds.count)) + for item in exceptIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("exceptIds", ConstructorParameterDescription(exceptIds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in + let reader = BufferReader(buffer) + var result: Api.auth.LoginToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken + } + return result + }) + } +} +public extension Api.functions.auth { + static func finishPasskeyLogin(flags: Int32, credential: Api.InputPasskeyCredential, fromDcId: Int32?, fromAuthKeyId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1739084537) + serializeInt32(flags, buffer: buffer, boxed: false) + credential.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(fromDcId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(fromAuthKeyId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.finishPasskeyLogin", parameters: [("flags", ConstructorParameterDescription(flags)), ("credential", ConstructorParameterDescription(credential)), ("fromDcId", ConstructorParameterDescription(fromDcId)), ("fromAuthKeyId", ConstructorParameterDescription(fromAuthKeyId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func importAuthorization(id: Int64, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1518699091) + serializeInt64(id, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importAuthorization", parameters: [("id", ConstructorParameterDescription(id)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func importBotAuthorization(flags: Int32, apiId: Int32, apiHash: String, botAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1738800940) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(botAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importBotAuthorization", parameters: [("flags", ConstructorParameterDescription(flags)), ("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("botAuthToken", ConstructorParameterDescription(botAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1783866140) + serializeBytes(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in + let reader = BufferReader(buffer) + var result: Api.auth.LoginToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken + } + return result + }) + } +} +public extension Api.functions.auth { + static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(767062953) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(webAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("webAuthToken", ConstructorParameterDescription(webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func initPasskeyLogin(apiId: Int32, apiHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1368051895) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.initPasskeyLogin", parameters: [("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasskeyLoginOptions? in + let reader = BufferReader(buffer) + var result: Api.auth.PasskeyLoginOptions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.PasskeyLoginOptions + } + return result + }) + } +} +public extension Api.functions.auth { + static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1047706137) + return (FunctionDescription(name: "auth.logOut", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoggedOut? in + let reader = BufferReader(buffer) + var result: Api.auth.LoggedOut? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.LoggedOut + } + return result + }) + } +} +public extension Api.functions.auth { + static func recoverPassword(flags: Int32, code: String, newSettings: Api.account.PasswordInputSettings?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(923364464) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(code, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + newSettings!.serialize(buffer, true) + } + return (FunctionDescription(name: "auth.recoverPassword", parameters: [("flags", ConstructorParameterDescription(flags)), ("code", ConstructorParameterDescription(code)), ("newSettings", ConstructorParameterDescription(newSettings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-878841866) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(mnc, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.reportMissingCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("mnc", ConstructorParameterDescription(mnc))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func requestFirebaseSms(flags: Int32, phoneNumber: String, phoneCodeHash: String, safetyNetToken: String?, playIntegrityToken: String?, iosPushSecret: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1908857314) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(safetyNetToken!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(playIntegrityToken!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(iosPushSecret!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.requestFirebaseSms", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("safetyNetToken", ConstructorParameterDescription(safetyNetToken)), ("playIntegrityToken", ConstructorParameterDescription(playIntegrityToken)), ("iosPushSecret", ConstructorParameterDescription(iosPushSecret))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func requestPasswordRecovery() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-661144474) + return (FunctionDescription(name: "auth.requestPasswordRecovery", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.PasswordRecovery? in + let reader = BufferReader(buffer) + var result: Api.auth.PasswordRecovery? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.PasswordRecovery + } + return result + }) + } +} +public extension Api.functions.auth { + static func resendCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, reason: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-890997469) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(reason!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "auth.resendCode", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("reason", ConstructorParameterDescription(reason))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func resetAuthorizations() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1616179942) + return (FunctionDescription(name: "auth.resetAuthorizations", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.auth { + static func resetLoginEmail(phoneNumber: String, phoneCodeHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2123760019) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.resetLoginEmail", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func sendCode(phoneNumber: String, apiId: Int32, apiHash: String, settings: Api.CodeSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1502141361) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + settings.serialize(buffer, true) + return (FunctionDescription(name: "auth.sendCode", parameters: [("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("apiId", ConstructorParameterDescription(apiId)), ("apiHash", ConstructorParameterDescription(apiHash)), ("settings", ConstructorParameterDescription(settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.SentCode? in + let reader = BufferReader(buffer) + var result: Api.auth.SentCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.SentCode + } + return result + }) + } +} +public extension Api.functions.auth { + static func signIn(flags: Int32, phoneNumber: String, phoneCodeHash: String, phoneCode: String?, emailVerification: Api.EmailVerification?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1923962543) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(phoneCode!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + emailVerification!.serialize(buffer, true) + } + return (FunctionDescription(name: "auth.signIn", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("phoneCode", ConstructorParameterDescription(phoneCode)), ("emailVerification", ConstructorParameterDescription(emailVerification))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.auth { + static func signUp(flags: Int32, phoneNumber: String, phoneCodeHash: String, firstName: String, lastName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1429752041) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(phoneCodeHash, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.signUp", parameters: [("flags", ConstructorParameterDescription(flags)), ("phoneNumber", ConstructorParameterDescription(phoneNumber)), ("phoneCodeHash", ConstructorParameterDescription(phoneCodeHash)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} +public extension Api.functions.bots { + static func addPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(397326170) + bot.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + media.serialize(buffer, true) + return (FunctionDescription(name: "bots.addPreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in + let reader = BufferReader(buffer) + var result: Api.BotPreviewMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia + } + return result + }) + } +} +public extension Api.functions.bots { + static func allowSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248323089) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.allowSendMessage", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.bots { + static func answerWebhookJSONQuery(queryId: Int64, data: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-434028723) + serializeInt64(queryId, buffer: buffer, boxed: false) + data.serialize(buffer, true) + return (FunctionDescription(name: "bots.answerWebhookJSONQuery", parameters: [("queryId", ConstructorParameterDescription(queryId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func canSendMessage(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(324662502) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.canSendMessage", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func checkDownloadFileParams(bot: Api.InputUser, fileName: String, url: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1342666121) + bot.serialize(buffer, true) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.checkDownloadFileParams", parameters: [("bot", ConstructorParameterDescription(bot)), ("fileName", ConstructorParameterDescription(fileName)), ("url", ConstructorParameterDescription(url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func checkUsername(username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2014174821) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.checkUsername", parameters: [("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func createBot(flags: Int32, name: String, username: String, managerId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-441352405) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) + managerId.serialize(buffer, true) + return (FunctionDescription(name: "bots.createBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("name", ConstructorParameterDescription(name)), ("username", ConstructorParameterDescription(username)), ("managerId", ConstructorParameterDescription(managerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.bots { + static func deletePreviewMedia(bot: Api.InputUser, langCode: String, media: [Api.InputMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(755054003) + bot.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(media.count)) + for item in media { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "bots.deletePreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func editAccessSettings(flags: Int32, bot: Api.InputUser, addUsers: [Api.InputUser]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(830553304) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(addUsers!.count)) + for item in addUsers! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "bots.editAccessSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("addUsers", ConstructorParameterDescription(addUsers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func editPreviewMedia(bot: Api.InputUser, langCode: String, media: Api.InputMedia, newMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2061148049) + bot.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + media.serialize(buffer, true) + newMedia.serialize(buffer, true) + return (FunctionDescription(name: "bots.editPreviewMedia", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("media", ConstructorParameterDescription(media)), ("newMedia", ConstructorParameterDescription(newMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotPreviewMedia? in + let reader = BufferReader(buffer) + var result: Api.BotPreviewMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BotPreviewMedia + } + return result + }) + } +} +public extension Api.functions.bots { + static func exportBotToken(bot: Api.InputUser, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1123182101) + bot.serialize(buffer, true) + revoke.serialize(buffer, true) + return (FunctionDescription(name: "bots.exportBotToken", parameters: [("bot", ConstructorParameterDescription(bot)), ("revoke", ConstructorParameterDescription(revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.ExportedBotToken? in + let reader = BufferReader(buffer) + var result: Api.bots.ExportedBotToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.ExportedBotToken + } + return result + }) + } +} +public extension Api.functions.bots { + static func getAccessSettings(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(557339555) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.getAccessSettings", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.AccessSettings? in + let reader = BufferReader(buffer) + var result: Api.bots.AccessSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.AccessSettings + } + return result + }) + } +} +public extension Api.functions.bots { + static func getAdminedBots() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { + let buffer = Buffer() + buffer.appendInt32(-1334764157) + return (FunctionDescription(name: "bots.getAdminedBots", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in + let reader = BufferReader(buffer) + var result: [Api.User]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotCommand]>) { + let buffer = Buffer() + buffer.appendInt32(-481554986) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotCommand]? in + let reader = BufferReader(buffer) + var result: [Api.BotCommand]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self) + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-589753091) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + bot!.serialize(buffer, true) + } + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getBotInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.BotInfo? in + let reader = BufferReader(buffer) + var result: Api.bots.BotInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.BotInfo + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotMenuButton(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1671369944) + userId.serialize(buffer, true) + return (FunctionDescription(name: "bots.getBotMenuButton", parameters: [("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.BotMenuButton? in + let reader = BufferReader(buffer) + var result: Api.BotMenuButton? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.BotMenuButton + } + return result + }) + } +} +public extension Api.functions.bots { + static func getBotRecommendations(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1581840363) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in + let reader = BufferReader(buffer) + var result: Api.users.Users? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.Users + } + return result + }) + } +} +public extension Api.functions.bots { + static func getPopularAppBots(offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1034878574) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getPopularAppBots", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.PopularAppBots? in + let reader = BufferReader(buffer) + var result: Api.bots.PopularAppBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.PopularAppBots + } + return result + }) + } +} +public extension Api.functions.bots { + static func getPreviewInfo(bot: Api.InputUser, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1111143341) + bot.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getPreviewInfo", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.PreviewInfo? in + let reader = BufferReader(buffer) + var result: Api.bots.PreviewInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.PreviewInfo + } + return result + }) + } +} +public extension Api.functions.bots { + static func getPreviewMedias(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.BotPreviewMedia]>) { + let buffer = Buffer() + buffer.appendInt32(-1566222003) + bot.serialize(buffer, true) + return (FunctionDescription(name: "bots.getPreviewMedias", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.BotPreviewMedia]? in + let reader = BufferReader(buffer) + var result: [Api.BotPreviewMedia]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotPreviewMedia.self) + } + return result + }) + } +} +public extension Api.functions.bots { + static func getRequestedWebViewButton(bot: Api.InputUser, webappReqId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1088047117) + bot.serialize(buffer, true) + serializeString(webappReqId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.getRequestedWebViewButton", parameters: [("bot", ConstructorParameterDescription(bot)), ("webappReqId", ConstructorParameterDescription(webappReqId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.KeyboardButton? in + let reader = BufferReader(buffer) + var result: Api.KeyboardButton? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.KeyboardButton + } + return result + }) + } +} +public extension Api.functions.bots { + static func invokeWebViewCustomMethod(bot: Api.InputUser, customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(142591463) + bot.serialize(buffer, true) + serializeString(customMethod, buffer: buffer, boxed: false) + params.serialize(buffer, true) + return (FunctionDescription(name: "bots.invokeWebViewCustomMethod", parameters: [("bot", ConstructorParameterDescription(bot)), ("customMethod", ConstructorParameterDescription(customMethod)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.bots { + static func reorderPreviewMedias(bot: Api.InputUser, langCode: String, order: [Api.InputMedia]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1238895702) + bot.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "bots.reorderPreviewMedias", parameters: [("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func reorderUsernames(bot: Api.InputUser, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1760972350) + bot.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "bots.reorderUsernames", parameters: [("bot", ConstructorParameterDescription(bot)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func requestWebViewButton(userId: Api.InputUser, button: Api.KeyboardButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(832742238) + userId.serialize(buffer, true) + button.serialize(buffer, true) + return (FunctionDescription(name: "bots.requestWebViewButton", parameters: [("userId", ConstructorParameterDescription(userId)), ("button", ConstructorParameterDescription(button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.bots.RequestedButton? in + let reader = BufferReader(buffer) + var result: Api.bots.RequestedButton? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.bots.RequestedButton + } + return result + }) + } +} +public extension Api.functions.bots { + static func resetBotCommands(scope: Api.BotCommandScope, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1032708345) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "bots.resetBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1440257555) + serializeString(customMethod, buffer: buffer, boxed: false) + params.serialize(buffer, true) + return (FunctionDescription(name: "bots.sendCustomRequest", parameters: [("customMethod", ConstructorParameterDescription(customMethod)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotBroadcastDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2021942497) + adminRights.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotBroadcastDefaultAdminRights", parameters: [("adminRights", ConstructorParameterDescription(adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotCommands(scope: Api.BotCommandScope, langCode: String, commands: [Api.BotCommand]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(85399130) + scope.serialize(buffer, true) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(commands.count)) + for item in commands { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "bots.setBotCommands", parameters: [("scope", ConstructorParameterDescription(scope)), ("langCode", ConstructorParameterDescription(langCode)), ("commands", ConstructorParameterDescription(commands))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotGroupDefaultAdminRights(adminRights: Api.ChatAdminRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1839281686) + adminRights.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotGroupDefaultAdminRights", parameters: [("adminRights", ConstructorParameterDescription(adminRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotInfo(flags: Int32, bot: Api.InputUser?, langCode: String, name: String?, about: String?, description: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(282013987) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + bot!.serialize(buffer, true) + } + serializeString(langCode, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + serializeString(name!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeString(about!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(description!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "bots.setBotInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("langCode", ConstructorParameterDescription(langCode)), ("name", ConstructorParameterDescription(name)), ("about", ConstructorParameterDescription(about)), ("description", ConstructorParameterDescription(description))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setBotMenuButton(userId: Api.InputUser, button: Api.BotMenuButton) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1157944655) + userId.serialize(buffer, true) + button.serialize(buffer, true) + return (FunctionDescription(name: "bots.setBotMenuButton", parameters: [("userId", ConstructorParameterDescription(userId)), ("button", ConstructorParameterDescription(button))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setCustomVerification(flags: Int32, bot: Api.InputUser?, peer: Api.InputPeer, customDescription: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1953898563) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + bot!.serialize(buffer, true) + } + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + serializeString(customDescription!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "bots.setCustomVerification", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("customDescription", ConstructorParameterDescription(customDescription))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func setJoinChatResults(queryId: Int64, result: Api.JoinChatBotResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-417708016) + serializeInt64(queryId, buffer: buffer, boxed: false) + result.serialize(buffer, true) + return (FunctionDescription(name: "bots.setJoinChatResults", parameters: [("queryId", ConstructorParameterDescription(queryId)), ("result", ConstructorParameterDescription(result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func toggleUserEmojiStatusPermission(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(115237778) + bot.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "bots.toggleUserEmojiStatusPermission", parameters: [("bot", ConstructorParameterDescription(bot)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func toggleUsername(bot: Api.InputUser, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(87861619) + bot.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "bots.toggleUsername", parameters: [("bot", ConstructorParameterDescription(bot)), ("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.bots { + static func updateStarRefProgram(flags: Int32, bot: Api.InputUser, commissionPermille: Int32, durationMonths: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2005621427) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + serializeInt32(commissionPermille, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(durationMonths!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "bots.updateStarRefProgram", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("commissionPermille", ConstructorParameterDescription(commissionPermille)), ("durationMonths", ConstructorParameterDescription(durationMonths))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarRefProgram? in + let reader = BufferReader(buffer) + var result: Api.StarRefProgram? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StarRefProgram + } + return result + }) + } +} +public extension Api.functions.bots { + static func updateUserEmojiStatus(userId: Api.InputUser, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-308334395) + userId.serialize(buffer, true) + emojiStatus.serialize(buffer, true) + return (FunctionDescription(name: "bots.updateUserEmojiStatus", parameters: [("userId", ConstructorParameterDescription(userId)), ("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func checkSearchPostsFlood(flags: Int32, query: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(576090389) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(query!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.checkSearchPostsFlood", parameters: [("flags", ConstructorParameterDescription(flags)), ("query", ConstructorParameterDescription(query))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SearchPostsFlood? in + let reader = BufferReader(buffer) + var result: Api.SearchPostsFlood? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SearchPostsFlood + } + return result + }) + } +} +public extension Api.functions.channels { + static func checkUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(283557164) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.checkUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func convertToGigagroup(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(187239529) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.convertToGigagroup", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func createChannel(flags: Int32, title: String, about: String, geoPoint: Api.InputGeoPoint?, address: String?, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1862244601) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(about, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + geoPoint!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(address!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 4) != 0 { + serializeInt32(ttlPeriod!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.createChannel", parameters: [("flags", ConstructorParameterDescription(flags)), ("title", ConstructorParameterDescription(title)), ("about", ConstructorParameterDescription(about)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address)), ("ttlPeriod", ConstructorParameterDescription(ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func deactivateAllUsernames(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(170155475) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.deactivateAllUsernames", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1072619549) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.deleteChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteHistory(flags: Int32, channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1683319225) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.deleteHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteMessages(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2067661490) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.deleteMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.channels { + static func deleteParticipantHistory(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(913655003) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "channels.deleteParticipantHistory", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.channels { + static func editAdmin(flags: Int32, channel: Api.InputChannel, userId: Api.InputUser, adminRights: Api.ChatAdminRights, rank: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1701270168) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + userId.serialize(buffer, true) + adminRights.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(rank!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.editAdmin", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("userId", ConstructorParameterDescription(userId)), ("adminRights", ConstructorParameterDescription(adminRights)), ("rank", ConstructorParameterDescription(rank))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func editBanned(channel: Api.InputChannel, participant: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1763259007) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + bannedRights.serialize(buffer, true) + return (FunctionDescription(name: "channels.editBanned", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant)), ("bannedRights", ConstructorParameterDescription(bannedRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1491484525) + channel.serialize(buffer, true) + geoPoint.serialize(buffer, true) + serializeString(address, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editLocation", parameters: [("channel", ConstructorParameterDescription(channel)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("address", ConstructorParameterDescription(address))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func editPhoto(channel: Api.InputChannel, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248621111) + channel.serialize(buffer, true) + photo.serialize(buffer, true) + return (FunctionDescription(name: "channels.editPhoto", parameters: [("channel", ConstructorParameterDescription(channel)), ("photo", ConstructorParameterDescription(photo))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func editTitle(channel: Api.InputChannel, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1450044624) + channel.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.editTitle", parameters: [("channel", ConstructorParameterDescription(channel)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func exportMessageLink(flags: Int32, channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-432034325) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.exportMessageLink", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedMessageLink? in + let reader = BufferReader(buffer) + var result: Api.ExportedMessageLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedMessageLink + } + return result + }) + } +} +public extension Api.functions.channels { + static func getAdminLog(flags: Int32, channel: Api.InputChannel, q: String, eventsFilter: Api.ChannelAdminLogEventsFilter?, admins: [Api.InputUser]?, maxId: Int64, minId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(870184064) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + eventsFilter!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(admins!.count)) + for item in admins! { + item.serialize(buffer, true) + } + } + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt64(minId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getAdminLog", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("q", ConstructorParameterDescription(q)), ("eventsFilter", ConstructorParameterDescription(eventsFilter)), ("admins", ConstructorParameterDescription(admins)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.AdminLogResults? in + let reader = BufferReader(buffer) + var result: Api.channels.AdminLogResults? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.AdminLogResults + } + return result + }) + } +} +public extension Api.functions.channels { + static func getAdminedPublicChannels(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-122669393) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getAdminedPublicChannels", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getChannelRecommendations(flags: Int32, channel: Api.InputChannel?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(631707458) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + channel!.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.getChannelRecommendations", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getChannels(id: [Api.InputChannel]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(176122811) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.getChannels", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getFullChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(141781513) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.getFullChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull + } + return result + }) + } +} +public extension Api.functions.channels { + static func getGroupsForDiscussion() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-170208392) + return (FunctionDescription(name: "channels.getGroupsForDiscussion", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getInactiveChannels() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(300429806) + return (FunctionDescription(name: "channels.getInactiveChannels", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InactiveChats? in + let reader = BufferReader(buffer) + var result: Api.messages.InactiveChats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InactiveChats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getLeftChannels(offset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2092831552) + serializeInt32(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getLeftChannels", parameters: [("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.channels { + static func getMessageAuthor(channel: Api.InputChannel, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-320691994) + channel.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getMessageAuthor", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.channels { + static func getMessages(channel: Api.InputChannel, id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1383294429) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.getMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.channels { + static func getParticipant(channel: Api.InputChannel, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1599378234) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "channels.getParticipant", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipant? in + let reader = BufferReader(buffer) + var result: Api.channels.ChannelParticipant? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipant + } + return result + }) + } +} +public extension Api.functions.channels { + static func getParticipants(channel: Api.InputChannel, filter: Api.ChannelParticipantsFilter, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2010044880) + channel.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.getParticipants", parameters: [("channel", ConstructorParameterDescription(channel)), ("filter", ConstructorParameterDescription(filter)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.ChannelParticipants? in + let reader = BufferReader(buffer) + var result: Api.channels.ChannelParticipants? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.ChannelParticipants + } + return result + }) + } +} +public extension Api.functions.channels { + static func getSendAs(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-410672065) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "channels.getSendAs", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SendAsPeers? in + let reader = BufferReader(buffer) + var result: Api.channels.SendAsPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.SendAsPeers + } + return result + }) + } +} +public extension Api.functions.channels { + static func inviteToChannel(channel: Api.InputChannel, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-907854508) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.inviteToChannel", parameters: [("channel", ConstructorParameterDescription(channel)), ("users", ConstructorParameterDescription(users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.channels { + static func joinChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2137660962) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.joinChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteJoinResult? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatInviteJoinResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteJoinResult + } + return result + }) + } +} +public extension Api.functions.channels { + static func leaveChannel(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-130635115) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.leaveChannel", parameters: [("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-871347913) + channel.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.readHistory", parameters: [("channel", ConstructorParameterDescription(channel)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func readMessageContents(channel: Api.InputChannel, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-357180360) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.readMessageContents", parameters: [("channel", ConstructorParameterDescription(channel)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1268978403) + channel.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.reorderUsernames", parameters: [("channel", ConstructorParameterDescription(channel)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1471109485) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-196443371) + channel.serialize(buffer, true) + participant.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.reportSpam", parameters: [("channel", ConstructorParameterDescription(channel)), ("participant", ConstructorParameterDescription(participant)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func restrictSponsoredMessages(channel: Api.InputChannel, restricted: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1696000743) + channel.serialize(buffer, true) + restricted.serialize(buffer, true) + return (FunctionDescription(name: "channels.restrictSponsoredMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("restricted", ConstructorParameterDescription(restricted))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func searchPosts(flags: Int32, hashtag: String?, query: String?, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-221973939) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(hashtag!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(query!, buffer: buffer, boxed: false) + } + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.searchPosts", parameters: [("flags", ConstructorParameterDescription(flags)), ("hashtag", ConstructorParameterDescription(hashtag)), ("query", ConstructorParameterDescription(query)), ("offsetRate", ConstructorParameterDescription(offsetRate)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.channels { + static func setBoostsToUnblockRestrictions(channel: Api.InputChannel, boosts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1388733202) + channel.serialize(buffer, true) + serializeInt32(boosts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.setBoostsToUnblockRestrictions", parameters: [("channel", ConstructorParameterDescription(channel)), ("boosts", ConstructorParameterDescription(boosts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func setDiscussionGroup(broadcast: Api.InputChannel, group: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1079520178) + broadcast.serialize(buffer, true) + group.serialize(buffer, true) + return (FunctionDescription(name: "channels.setDiscussionGroup", parameters: [("broadcast", ConstructorParameterDescription(broadcast)), ("group", ConstructorParameterDescription(group))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func setEmojiStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1020866743) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setEmojiStickers", parameters: [("channel", ConstructorParameterDescription(channel)), ("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func setMainProfileTab(channel: Api.InputChannel, tab: Api.ProfileTab) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(897842353) + channel.serialize(buffer, true) + tab.serialize(buffer, true) + return (FunctionDescription(name: "channels.setMainProfileTab", parameters: [("channel", ConstructorParameterDescription(channel)), ("tab", ConstructorParameterDescription(tab))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func setStickers(channel: Api.InputChannel, stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-359881479) + channel.serialize(buffer, true) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "channels.setStickers", parameters: [("channel", ConstructorParameterDescription(channel)), ("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1760814315) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleAutotranslation(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(377471137) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleAutotranslation", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool, tabs: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1073174324) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + tabs.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleForum", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled)), ("tabs", ConstructorParameterDescription(tabs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleJoinRequest(flags: Int32, channel: Api.InputChannel, enabled: Api.Bool, guardBot: Api.InputUser?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(248260120) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + guardBot!.serialize(buffer, true) + } + return (FunctionDescription(name: "channels.toggleJoinRequest", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled)), ("guardBot", ConstructorParameterDescription(guardBot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleJoinToSend(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-456419968) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleJoinToSend", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleParticipantsHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1785624660) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleParticipantsHidden", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func togglePreHistoryHidden(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-356796084) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.togglePreHistoryHidden", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleSignatures(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1099781276) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleSignatures", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleSlowMode(channel: Api.InputChannel, seconds: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-304832784) + channel.serialize(buffer, true) + serializeInt32(seconds, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.toggleSlowMode", parameters: [("channel", ConstructorParameterDescription(channel)), ("seconds", ConstructorParameterDescription(seconds))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleUsername(channel: Api.InputChannel, username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1358053637) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + active.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username)), ("active", ConstructorParameterDescription(active))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.channels { + static func toggleViewForumAsMessages(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1757889771) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleViewForumAsMessages", parameters: [("channel", ConstructorParameterDescription(channel)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func updateColor(flags: Int32, channel: Api.InputChannel, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-659933583) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(color!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "channels.updateColor", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("color", ConstructorParameterDescription(color)), ("backgroundEmojiId", ConstructorParameterDescription(backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func updateEmojiStatus(channel: Api.InputChannel, emojiStatus: Api.EmojiStatus) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-254548312) + channel.serialize(buffer, true) + emojiStatus.serialize(buffer, true) + return (FunctionDescription(name: "channels.updateEmojiStatus", parameters: [("channel", ConstructorParameterDescription(channel)), ("emojiStatus", ConstructorParameterDescription(emojiStatus))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func updatePaidMessagesPrice(flags: Int32, channel: Api.InputChannel, sendPaidMessagesStars: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1259483771) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt64(sendPaidMessagesStars, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.updatePaidMessagesPrice", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.channels { + static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(890549214) + channel.serialize(buffer, true) + serializeString(username, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.updateUsername", parameters: [("channel", ConstructorParameterDescription(channel)), ("username", ConstructorParameterDescription(username))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func checkChatlistInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1103171583) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "chatlists.checkChatlistInvite", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func deleteExportedInvite(chatlist: Api.InputChatlist, slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1906072670) + chatlist.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "chatlists.deleteExportedInvite", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func editExportedInvite(flags: Int32, chatlist: Api.InputChatlist, slug: String, title: String?, peers: [Api.InputPeer]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1698543165) + serializeInt32(flags, buffer: buffer, boxed: false) + chatlist.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers!.count)) + for item in peers! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "chatlists.editExportedInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatlist", ConstructorParameterDescription(chatlist)), ("slug", ConstructorParameterDescription(slug)), ("title", ConstructorParameterDescription(title)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.ExportedChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func exportChatlistInvite(chatlist: Api.InputChatlist, title: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2072885362) + chatlist.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.exportChatlistInvite", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("title", ConstructorParameterDescription(title)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedChatlistInvite? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ExportedChatlistInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedChatlistInvite + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1992190687) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ChatlistUpdates? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ChatlistUpdates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ChatlistUpdates + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getExportedInvites(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-838608253) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getExportedInvites", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.chatlists.ExportedInvites? in + let reader = BufferReader(buffer) + var result: Api.chatlists.ExportedInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.chatlists.ExportedInvites + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func getLeaveChatlistSuggestions(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Peer]>) { + let buffer = Buffer() + buffer.appendInt32(-37955820) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.getLeaveChatlistSuggestions", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Peer]? in + let reader = BufferReader(buffer) + var result: [Api.Peer]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func hideChatlistUpdates(chatlist: Api.InputChatlist) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1726252795) + chatlist.serialize(buffer, true) + return (FunctionDescription(name: "chatlists.hideChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func joinChatlistInvite(slug: String, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1498291302) + serializeString(slug, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.joinChatlistInvite", parameters: [("slug", ConstructorParameterDescription(slug)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func joinChatlistUpdates(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-527828747) + chatlist.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.joinChatlistUpdates", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.chatlists { + static func leaveChatlist(chatlist: Api.InputChatlist, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1962598714) + chatlist.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "chatlists.leaveChatlist", parameters: [("chatlist", ConstructorParameterDescription(chatlist)), ("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-130964977) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.acceptContact", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String, note: Api.TextWithEntities?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-642109868) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeString(phone, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + note!.serialize(buffer, true) + } + return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("firstName", ConstructorParameterDescription(firstName)), ("lastName", ConstructorParameterDescription(lastName)), ("phone", ConstructorParameterDescription(phone)), ("note", ConstructorParameterDescription(note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func block(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(774801204) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.block", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func blockFromReplies(flags: Int32, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(698914348) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.blockFromReplies", parameters: [("flags", ConstructorParameterDescription(flags)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func deleteByPhones(phones: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(269745566) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(phones.count)) + for item in phones { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.deleteByPhones", parameters: [("phones", ConstructorParameterDescription(phones))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func deleteContacts(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(157945344) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "contacts.deleteContacts", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func editCloseFriends(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1167653392) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.editCloseFriends", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func exportContactToken() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-127582169) + return (FunctionDescription(name: "contacts.exportContactToken", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedContactToken? in + let reader = BufferReader(buffer) + var result: Api.ExportedContactToken? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedContactToken + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getBirthdays() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-621959068) + return (FunctionDescription(name: "contacts.getBirthdays", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ContactBirthdays? in + let reader = BufferReader(buffer) + var result: Api.contacts.ContactBirthdays? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ContactBirthdays + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getBlocked(flags: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1702457472) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getBlocked", parameters: [("flags", ConstructorParameterDescription(flags)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Blocked? in + let reader = BufferReader(buffer) + var result: Api.contacts.Blocked? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Blocked + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getContactIDs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(2061264541) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getContactIDs", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getContacts(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1574346258) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getContacts", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Contacts? in + let reader = BufferReader(buffer) + var result: Api.contacts.Contacts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Contacts + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getLocated(flags: Int32, geoPoint: Api.InputGeoPoint, selfExpires: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-750207932) + serializeInt32(flags, buffer: buffer, boxed: false) + geoPoint.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(selfExpires!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.getLocated", parameters: [("flags", ConstructorParameterDescription(flags)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("selfExpires", ConstructorParameterDescription(selfExpires))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.SavedContact]>) { + let buffer = Buffer() + buffer.appendInt32(-2098076769) + return (FunctionDescription(name: "contacts.getSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.SavedContact]? in + let reader = BufferReader(buffer) + var result: [Api.SavedContact]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedContact.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getSponsoredPeers(q: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1228356717) + serializeString(q, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getSponsoredPeers", parameters: [("q", ConstructorParameterDescription(q))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.SponsoredPeers? in + let reader = BufferReader(buffer) + var result: Api.contacts.SponsoredPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.SponsoredPeers + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getStatuses() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ContactStatus]>) { + let buffer = Buffer() + buffer.appendInt32(-995929106) + return (FunctionDescription(name: "contacts.getStatuses", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ContactStatus]? in + let reader = BufferReader(buffer) + var result: [Api.ContactStatus]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ContactStatus.self) + } + return result + }) + } +} +public extension Api.functions.contacts { + static func getTopPeers(flags: Int32, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1758168906) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.getTopPeers", parameters: [("flags", ConstructorParameterDescription(flags)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.TopPeers? in + let reader = BufferReader(buffer) + var result: Api.contacts.TopPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.TopPeers + } + return result + }) + } +} +public extension Api.functions.contacts { + static func importContactToken(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(318789512) + serializeString(token, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.importContactToken", parameters: [("token", ConstructorParameterDescription(token))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.contacts { + static func importContacts(contacts: [Api.InputContact]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(746589157) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(contacts.count)) + for item in contacts { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "contacts.importContacts", parameters: [("contacts", ConstructorParameterDescription(contacts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ImportedContacts? in + let reader = BufferReader(buffer) + var result: Api.contacts.ImportedContacts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ImportedContacts + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resetSaved() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2020263951) + return (FunctionDescription(name: "contacts.resetSaved", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resetTopPeerRating(category: Api.TopPeerCategory, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(451113900) + category.serialize(buffer, true) + peer.serialize(buffer, true) + return (FunctionDescription(name: "contacts.resetTopPeerRating", parameters: [("category", ConstructorParameterDescription(category)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resolvePhone(phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1963375804) + serializeString(phone, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.resolvePhone", parameters: [("phone", ConstructorParameterDescription(phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in + let reader = BufferReader(buffer) + var result: Api.contacts.ResolvedPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer + } + return result + }) + } +} +public extension Api.functions.contacts { + static func resolveUsername(flags: Int32, username: String, referer: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1918565308) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(username, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(referer!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "contacts.resolveUsername", parameters: [("flags", ConstructorParameterDescription(flags)), ("username", ConstructorParameterDescription(username)), ("referer", ConstructorParameterDescription(referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.ResolvedPeer? in + let reader = BufferReader(buffer) + var result: Api.contacts.ResolvedPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.ResolvedPeer + } + return result + }) + } +} +public extension Api.functions.contacts { + static func search(flags: Int32, q: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(99978511) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.search", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.contacts.Found? in + let reader = BufferReader(buffer) + var result: Api.contacts.Found? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.contacts.Found + } + return result + }) + } +} +public extension Api.functions.contacts { + static func setBlocked(flags: Int32, id: [Api.InputPeer], limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1798939530) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "contacts.setBlocked", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2062238246) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "contacts.toggleTopPeers", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func unblock(flags: Int32, id: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1252994264) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + return (FunctionDescription(name: "contacts.unblock", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.contacts { + static func updateContactNote(id: Api.InputUser, note: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(329212923) + id.serialize(buffer, true) + note.serialize(buffer, true) + return (FunctionDescription(name: "contacts.updateContactNote", parameters: [("id", ConstructorParameterDescription(id)), ("note", ConstructorParameterDescription(note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.folders { + static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1749536939) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(folderPeers.count)) + for item in folderPeers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "folders.editPeerFolders", parameters: [("folderPeers", ConstructorParameterDescription(folderPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.fragment { + static func getCollectibleInfo(collectible: Api.InputCollectible) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1105295942) + collectible.serialize(buffer, true) + return (FunctionDescription(name: "fragment.getCollectibleInfo", parameters: [("collectible", ConstructorParameterDescription(collectible))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.fragment.CollectibleInfo? in + let reader = BufferReader(buffer) + var result: Api.fragment.CollectibleInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.fragment.CollectibleInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func acceptTermsOfService(id: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-294455398) + id.serialize(buffer, true) + return (FunctionDescription(name: "help.acceptTermsOfService", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func dismissSuggestion(peer: Api.InputPeer, suggestion: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-183649631) + peer.serialize(buffer, true) + serializeString(suggestion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.dismissSuggestion", parameters: [("peer", ConstructorParameterDescription(peer)), ("suggestion", ConstructorParameterDescription(suggestion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func editUserInfo(userId: Api.InputUser, message: String, entities: [Api.MessageEntity]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1723407216) + userId.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities.count)) + for item in entities { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "help.editUserInfo", parameters: [("userId", ConstructorParameterDescription(userId)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in + let reader = BufferReader(buffer) + var result: Api.help.UserInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.UserInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func getAppConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1642330196) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getAppConfig", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppConfig? in + let reader = BufferReader(buffer) + var result: Api.help.AppConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.AppConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getAppUpdate(source: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1378703997) + serializeString(source, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getAppUpdate", parameters: [("source", ConstructorParameterDescription(source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.AppUpdate? in + let reader = BufferReader(buffer) + var result: Api.help.AppUpdate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.AppUpdate + } + return result + }) + } +} +public extension Api.functions.help { + static func getCdnConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1375900482) + return (FunctionDescription(name: "help.getCdnConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.CdnConfig? in + let reader = BufferReader(buffer) + var result: Api.CdnConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.CdnConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-990308245) + return (FunctionDescription(name: "help.getConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Config? in + let reader = BufferReader(buffer) + var result: Api.Config? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Config + } + return result + }) + } +} +public extension Api.functions.help { + static func getCountriesList(langCode: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1935116200) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getCountriesList", parameters: [("langCode", ConstructorParameterDescription(langCode)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.CountriesList? in + let reader = BufferReader(buffer) + var result: Api.help.CountriesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.CountriesList + } + return result + }) + } +} +public extension Api.functions.help { + static func getDeepLinkInfo(path: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1072547679) + serializeString(path, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getDeepLinkInfo", parameters: [("path", ConstructorParameterDescription(path))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.DeepLinkInfo? in + let reader = BufferReader(buffer) + var result: Api.help.DeepLinkInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.DeepLinkInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func getInviteText() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1295590211) + return (FunctionDescription(name: "help.getInviteText", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.InviteText? in + let reader = BufferReader(buffer) + var result: Api.help.InviteText? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.InviteText + } + return result + }) + } +} +public extension Api.functions.help { + static func getNearestDc() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(531836966) + return (FunctionDescription(name: "help.getNearestDc", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.NearestDc? in + let reader = BufferReader(buffer) + var result: Api.NearestDc? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.NearestDc + } + return result + }) + } +} +public extension Api.functions.help { + static func getPassportConfig(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-966677240) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPassportConfig", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PassportConfig? in + let reader = BufferReader(buffer) + var result: Api.help.PassportConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PassportConfig + } + return result + }) + } +} +public extension Api.functions.help { + static func getPeerColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-629083089) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPeerColors", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in + let reader = BufferReader(buffer) + var result: Api.help.PeerColors? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PeerColors + } + return result + }) + } +} +public extension Api.functions.help { + static func getPeerProfileColors(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1412453891) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getPeerProfileColors", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PeerColors? in + let reader = BufferReader(buffer) + var result: Api.help.PeerColors? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PeerColors + } + return result + }) + } +} +public extension Api.functions.help { + static func getPremiumPromo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1206152236) + return (FunctionDescription(name: "help.getPremiumPromo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PremiumPromo? in + let reader = BufferReader(buffer) + var result: Api.help.PremiumPromo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PremiumPromo + } + return result + }) + } +} +public extension Api.functions.help { + static func getPromoData() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1063816159) + return (FunctionDescription(name: "help.getPromoData", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.PromoData? in + let reader = BufferReader(buffer) + var result: Api.help.PromoData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.PromoData + } + return result + }) + } +} +public extension Api.functions.help { + static func getRecentMeUrls(referer: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1036054804) + serializeString(referer, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getRecentMeUrls", parameters: [("referer", ConstructorParameterDescription(referer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.RecentMeUrls? in + let reader = BufferReader(buffer) + var result: Api.help.RecentMeUrls? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.RecentMeUrls + } + return result + }) + } +} +public extension Api.functions.help { + static func getSupport() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1663104819) + return (FunctionDescription(name: "help.getSupport", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.Support? in + let reader = BufferReader(buffer) + var result: Api.help.Support? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.Support + } + return result + }) + } +} +public extension Api.functions.help { + static func getSupportName() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-748624084) + return (FunctionDescription(name: "help.getSupportName", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.SupportName? in + let reader = BufferReader(buffer) + var result: Api.help.SupportName? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.SupportName + } + return result + }) + } +} +public extension Api.functions.help { + static func getTermsOfServiceUpdate() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(749019089) + return (FunctionDescription(name: "help.getTermsOfServiceUpdate", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TermsOfServiceUpdate? in + let reader = BufferReader(buffer) + var result: Api.help.TermsOfServiceUpdate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.TermsOfServiceUpdate + } + return result + }) + } +} +public extension Api.functions.help { + static func getTimezonesList(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1236468288) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.getTimezonesList", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.TimezonesList? in + let reader = BufferReader(buffer) + var result: Api.help.TimezonesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.TimezonesList + } + return result + }) + } +} +public extension Api.functions.help { + static func getUserInfo(userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(59377875) + userId.serialize(buffer, true) + return (FunctionDescription(name: "help.getUserInfo", parameters: [("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.help.UserInfo? in + let reader = BufferReader(buffer) + var result: Api.help.UserInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.help.UserInfo + } + return result + }) + } +} +public extension Api.functions.help { + static func hidePromoData(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(505748629) + peer.serialize(buffer, true) + return (FunctionDescription(name: "help.hidePromoData", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1862465352) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(events.count)) + for item in events { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", ConstructorParameterDescription(events))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func setBotUpdatesStatus(pendingUpdatesCount: Int32, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-333262899) + serializeInt32(pendingUpdatesCount, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "help.setBotUpdatesStatus", parameters: [("pendingUpdatesCount", ConstructorParameterDescription(pendingUpdatesCount)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.help { + static func test() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1058929929) + return (FunctionDescription(name: "help.test", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getDifference(langPack: String, langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-845657435) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getDifference", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode)), ("fromVersion", ConstructorParameterDescription(fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in + let reader = BufferReader(buffer) + var result: Api.LangPackDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackDifference + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLangPack(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-219008246) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLangPack", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackDifference? in + let reader = BufferReader(buffer) + var result: Api.LangPackDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackDifference + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLanguage(langPack: String, langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1784243458) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLanguage", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.LangPackLanguage? in + let reader = BufferReader(buffer) + var result: Api.LangPackLanguage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.LangPackLanguage + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getLanguages(langPack: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackLanguage]>) { + let buffer = Buffer() + buffer.appendInt32(1120311183) + serializeString(langPack, buffer: buffer, boxed: false) + return (FunctionDescription(name: "langpack.getLanguages", parameters: [("langPack", ConstructorParameterDescription(langPack))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackLanguage]? in + let reader = BufferReader(buffer) + var result: [Api.LangPackLanguage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackLanguage.self) + } + return result + }) + } +} +public extension Api.functions.langpack { + static func getStrings(langPack: String, langCode: String, keys: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.LangPackString]>) { + let buffer = Buffer() + buffer.appendInt32(-269862909) + serializeString(langPack, buffer: buffer, boxed: false) + serializeString(langCode, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(keys.count)) + for item in keys { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "langpack.getStrings", parameters: [("langPack", ConstructorParameterDescription(langPack)), ("langCode", ConstructorParameterDescription(langCode)), ("keys", ConstructorParameterDescription(keys))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.LangPackString]? in + let reader = BufferReader(buffer) + var result: [Api.LangPackString]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.LangPackString.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func acceptEncryption(peer: Api.InputEncryptedChat, gB: Buffer, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1035731989) + peer.serialize(buffer, true) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.acceptEncryption", parameters: [("peer", ConstructorParameterDescription(peer)), ("gB", ConstructorParameterDescription(gB)), ("keyFingerprint", ConstructorParameterDescription(keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in + let reader = BufferReader(buffer) + var result: Api.EncryptedChat? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + return result + }) + } +} +public extension Api.functions.messages { + static func acceptUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?, matchCode: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1738797278) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + peer!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(msgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(buttonId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(url!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 4) != 0 { + serializeString(matchCode!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.acceptUrlAuth", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("url", ConstructorParameterDescription(url)), ("matchCode", ConstructorParameterDescription(matchCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in + let reader = BufferReader(buffer) + var result: Api.UrlAuthResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func addChatUser(chatId: Int64, userId: Api.InputUser, fwdLimit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-876162809) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(fwdLimit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.addChatUser", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId)), ("fwdLimit", ConstructorParameterDescription(fwdLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.messages { + static func addPollAnswer(peer: Api.InputPeer, msgId: Int32, answer: Api.PollAnswer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(431770477) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + answer.serialize(buffer, true) + return (FunctionDescription(name: "messages.addPollAnswer", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("answer", ConstructorParameterDescription(answer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func appendTodoList(peer: Api.InputPeer, msgId: Int32, list: [Api.TodoItem]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(564531287) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(list.count)) + for item in list { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.appendTodoList", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("list", ConstructorParameterDescription(list))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1051570619) + serializeString(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkChatInvite", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatInvite? in + let reader = BufferReader(buffer) + var result: Api.ChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1140726259) + serializeString(importHead, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", ConstructorParameterDescription(importHead))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in + let reader = BufferReader(buffer) + var result: Api.messages.HistoryImportParsed? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkHistoryImportPeer(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1573261059) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.checkHistoryImportPeer", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.CheckedHistoryImportPeer? in + let reader = BufferReader(buffer) + var result: Api.messages.CheckedHistoryImportPeer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.CheckedHistoryImportPeer + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkQuickReplyShortcut(shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-237962285) + serializeString(shortcut, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkQuickReplyShortcut", parameters: [("shortcut", ConstructorParameterDescription(shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func checkUrlAuthMatchCode(url: String, matchCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-911967477) + serializeString(url, buffer: buffer, boxed: false) + serializeString(matchCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.checkUrlAuthMatchCode", parameters: [("url", ConstructorParameterDescription(url)), ("matchCode", ConstructorParameterDescription(matchCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2119757468) + return (FunctionDescription(name: "messages.clearAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearRecentReactions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1644236876) + return (FunctionDescription(name: "messages.clearRecentReactions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clearRecentStickers(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1986437075) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.clearRecentStickers", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func clickSponsoredMessage(flags: Int32, randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2110454402) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeBytes(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.clickSponsoredMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func composeMessageWithAI(flags: Int32, text: Api.TextWithEntities, translateToLang: String?, tone: Api.InputAiComposeTone?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-622017143) + serializeInt32(flags, buffer: buffer, boxed: false) + text.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(translateToLang!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + tone!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.composeMessageWithAI", parameters: [("flags", ConstructorParameterDescription(flags)), ("text", ConstructorParameterDescription(text)), ("translateToLang", ConstructorParameterDescription(translateToLang)), ("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ComposedMessageWithAI? in + let reader = BufferReader(buffer) + var result: Api.messages.ComposedMessageWithAI? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ComposedMessageWithAI + } + return result + }) + } +} +public extension Api.functions.messages { + static func createChat(flags: Int32, users: [Api.InputUser], title: String, ttlPeriod: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1831936556) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(ttlPeriod!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.createChat", parameters: [("flags", ConstructorParameterDescription(flags)), ("users", ConstructorParameterDescription(users)), ("title", ConstructorParameterDescription(title)), ("ttlPeriod", ConstructorParameterDescription(ttlPeriod))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.InvitedUsers? in + let reader = BufferReader(buffer) + var result: Api.messages.InvitedUsers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.InvitedUsers + } + return result + }) + } +} +public extension Api.functions.messages { + static func createForumTopic(flags: Int32, peer: Api.InputPeer, title: String, iconColor: Int32?, iconEmojiId: Int64?, randomId: Int64, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(798540757) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(iconColor!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(iconEmojiId!, buffer: buffer, boxed: false) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + sendAs!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.createForumTopic", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("iconColor", ConstructorParameterDescription(iconColor)), ("iconEmojiId", ConstructorParameterDescription(iconEmojiId)), ("randomId", ConstructorParameterDescription(randomId)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func declineUrlAuth(url: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(893610940) + serializeString(url, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.declineUrlAuth", parameters: [("url", ConstructorParameterDescription(url))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1540419152) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteChatUser(flags: Int32, chatId: Int64, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1575461717) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-731601877) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteExportedChatInvite", parameters: [("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteFactCheck(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-774204404) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1332768214) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(minDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt32(maxDate!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteMessages(flags: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-443640366) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteParticipantReaction(peer: Api.InputPeer, msgId: Int32, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-474482644) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + participant.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteParticipantReaction", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteParticipantReactions(peer: Api.InputPeer, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1598550792) + peer.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteParticipantReactions", parameters: [("peer", ConstructorParameterDescription(peer)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-104078327) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deletePhoneCallHistory", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedFoundMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedFoundMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedFoundMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func deletePollAnswer(peer: Api.InputPeer, msgId: Int32, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1400568411) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeBytes(option, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deletePollAnswer", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("option", ConstructorParameterDescription(option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteQuickReplyMessages(shortcutId: Int32, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-519706352) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteQuickReplyMessages", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteQuickReplyShortcut(shortcutId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1019234112) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteQuickReplyShortcut", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteRevokedExportedChatInvites(peer: Api.InputPeer, adminId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1452833749) + peer.serialize(buffer, true) + adminId.serialize(buffer, true) + return (FunctionDescription(name: "messages.deleteRevokedExportedChatInvites", parameters: [("peer", ConstructorParameterDescription(peer)), ("adminId", ConstructorParameterDescription(adminId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1304758367) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + parentPeer!.serialize(buffer, true) + } + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(minDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt32(maxDate!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1504586518) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.deleteScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func deleteTopicHistory(peer: Api.InputPeer, topMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-763269360) + peer.serialize(buffer, true) + serializeInt32(topMsgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.deleteTopicHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-208425312) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.discardEncryption", parameters: [("flags", ConstructorParameterDescription(flags)), ("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatAbout(peer: Api.InputPeer, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-554301545) + peer.serialize(buffer, true) + serializeString(about, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editChatAbout", parameters: [("peer", ConstructorParameterDescription(peer)), ("about", ConstructorParameterDescription(about))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatAdmin(chatId: Int64, userId: Api.InputUser, isAdmin: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1470377534) + serializeInt64(chatId, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + isAdmin.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatAdmin", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("userId", ConstructorParameterDescription(userId)), ("isAdmin", ConstructorParameterDescription(isAdmin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatCreator(peer: Api.InputPeer, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-146556841) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + password.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatCreator", parameters: [("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatDefaultBannedRights(peer: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1517917375) + peer.serialize(buffer, true) + bannedRights.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatDefaultBannedRights", parameters: [("peer", ConstructorParameterDescription(peer)), ("bannedRights", ConstructorParameterDescription(bannedRights))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatParticipantRank(peer: Api.InputPeer, participant: Api.InputPeer, rank: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1609616720) + peer.serialize(buffer, true) + participant.serialize(buffer, true) + serializeString(rank, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editChatParticipantRank", parameters: [("peer", ConstructorParameterDescription(peer)), ("participant", ConstructorParameterDescription(participant)), ("rank", ConstructorParameterDescription(rank))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatPhoto(chatId: Int64, photo: Api.InputChatPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(903730804) + serializeInt64(chatId, buffer: buffer, boxed: false) + photo.serialize(buffer, true) + return (FunctionDescription(name: "messages.editChatPhoto", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("photo", ConstructorParameterDescription(photo))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editChatTitle(chatId: Int64, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1937260541) + serializeInt64(chatId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editChatTitle", parameters: [("chatId", ConstructorParameterDescription(chatId)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?, requestNeeded: Api.Bool?, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1110823051) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(expireDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(usageLimit!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + requestNeeded!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 4) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link)), ("expireDate", ConstructorParameterDescription(expireDate)), ("usageLimit", ConstructorParameterDescription(usageLimit)), ("requestNeeded", ConstructorParameterDescription(requestNeeded)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func editFactCheck(peer: Api.InputPeer, msgId: Int32, text: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(92925557) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + text.serialize(buffer, true) + return (FunctionDescription(name: "messages.editFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("text", ConstructorParameterDescription(text))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editForumTopic(flags: Int32, peer: Api.InputPeer, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-825487052) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt64(iconEmojiId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + closed!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + hidden!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.editForumTopic", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topicId", ConstructorParameterDescription(topicId)), ("title", ConstructorParameterDescription(title)), ("iconEmojiId", ConstructorParameterDescription(iconEmojiId)), ("closed", ConstructorParameterDescription(closed)), ("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editInlineBotMessage(flags: Int32, id: Api.InputBotInlineMessageID, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, richMessage: Api.InputRichMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1541162159) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + if Int(flags) & Int(1 << 11) != 0 { + serializeString(message!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 14) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + replyMarkup!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 23) != 0 { + richMessage!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.editInlineBotMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("message", ConstructorParameterDescription(message)), ("media", ConstructorParameterDescription(media)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("richMessage", ConstructorParameterDescription(richMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func editMessage(flags: Int32, peer: Api.InputPeer, id: Int32, message: String?, media: Api.InputMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, quickReplyShortcutId: Int32?, richMessage: Api.InputRichMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1324947860) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 11) != 0 { + serializeString(message!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 14) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + replyMarkup!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 15) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 18) != 0 { + serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 17) != 0 { + serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 23) != 0 { + richMessage!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.editMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("message", ConstructorParameterDescription(message)), ("media", ConstructorParameterDescription(media)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("quickReplyShortcutId", ConstructorParameterDescription(quickReplyShortcutId)), ("richMessage", ConstructorParameterDescription(richMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func editQuickReplyShortcut(shortcutId: Int32, shortcut: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1543519471) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + serializeString(shortcut, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.editQuickReplyShortcut", parameters: [("shortcutId", ConstructorParameterDescription(shortcutId)), ("shortcut", ConstructorParameterDescription(shortcut))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?, title: String?, subscriptionPricing: Api.StarsSubscriptionPricing?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1537876336) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(expireDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(usageLimit!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 4) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 5) != 0 { + subscriptionPricing!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.exportChatInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("expireDate", ConstructorParameterDescription(expireDate)), ("usageLimit", ConstructorParameterDescription(usageLimit)), ("title", ConstructorParameterDescription(title)), ("subscriptionPricing", ConstructorParameterDescription(subscriptionPricing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func faveSticker(id: Api.InputDocument, unfave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1174420133) + id.serialize(buffer, true) + unfave.serialize(buffer, true) + return (FunctionDescription(name: "messages.faveSticker", parameters: [("id", ConstructorParameterDescription(id)), ("unfave", ConstructorParameterDescription(unfave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(326126204) + serializeInt32(flags, buffer: buffer, boxed: false) + fromPeer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + toPeer.serialize(buffer, true) + if Int(flags) & Int(1 << 9) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 22) != 0 { + replyTo!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 10) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 24) != 0 { + serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 17) != 0 { + quickReplyShortcut!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 18) != 0 { + serializeInt64(effect!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 20) != 0 { + serializeInt32(videoTimestamp!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 21) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 23) != 0 { + suggestedPost!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("fromPeer", ConstructorParameterDescription(fromPeer)), ("id", ConstructorParameterDescription(id)), ("randomId", ConstructorParameterDescription(randomId)), ("toPeer", ConstructorParameterDescription(toPeer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("replyTo", ConstructorParameterDescription(replyTo)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("videoTimestamp", ConstructorParameterDescription(videoTimestamp)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAdminsWithInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(958457583) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAdminsWithInvites", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatAdminsWithInvites? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatAdminsWithInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatAdminsWithInvites + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAllDrafts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1782549861) + return (FunctionDescription(name: "messages.getAllDrafts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAllStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1197432408) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAllStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getArchivedStickers(flags: Int32, offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1475442322) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getArchivedStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ArchivedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.ArchivedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ArchivedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachMenuBot(bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1998676370) + bot.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAttachMenuBot", parameters: [("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBotsBot? in + let reader = BufferReader(buffer) + var result: Api.AttachMenuBotsBot? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AttachMenuBotsBot + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachMenuBots(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(385663691) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAttachMenuBots", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.AttachMenuBots? in + let reader = BufferReader(buffer) + var result: Api.AttachMenuBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.AttachMenuBots + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAttachedStickers(media: Api.InputStickeredMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StickerSetCovered]>) { + let buffer = Buffer() + buffer.appendInt32(-866424884) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.getAttachedStickers", parameters: [("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StickerSetCovered]? in + let reader = BufferReader(buffer) + var result: [Api.StickerSetCovered]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAvailableEffects(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-559805895) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAvailableEffects", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableEffects? in + let reader = BufferReader(buffer) + var result: Api.messages.AvailableEffects? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AvailableEffects + } + return result + }) + } +} +public extension Api.functions.messages { + static func getAvailableReactions(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(417243308) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getAvailableReactions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AvailableReactions? in + let reader = BufferReader(buffer) + var result: Api.messages.AvailableReactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AvailableReactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getBotApp(app: Api.InputBotApp, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(889046467) + app.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getBotApp", parameters: [("app", ConstructorParameterDescription(app)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotApp? in + let reader = BufferReader(buffer) + var result: Api.messages.BotApp? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotApp + } + return result + }) + } +} +public extension Api.functions.messages { + static func getBotCallbackAnswer(flags: Int32, peer: Api.InputPeer, msgId: Int32, data: Buffer?, password: Api.InputCheckPasswordSRP?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1824339449) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeBytes(data!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + password!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getBotCallbackAnswer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("data", ConstructorParameterDescription(data)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotCallbackAnswer? in + let reader = BufferReader(buffer) + var result: Api.messages.BotCallbackAnswer? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotCallbackAnswer + } + return result + }) + } +} +public extension Api.functions.messages { + static func getChatInviteImporters(flags: Int32, peer: Api.InputPeer, link: String?, q: String?, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-553329330) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(link!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(q!, buffer: buffer, boxed: false) + } + serializeInt32(offsetDate, buffer: buffer, boxed: false) + offsetUser.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link)), ("q", ConstructorParameterDescription(q)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetUser", ConstructorParameterDescription(offsetUser)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatInviteImporters? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters + } + return result + }) + } +} +public extension Api.functions.messages { + static func getChats(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1240027791) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getChats", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.messages { + static func getCommonChats(userId: Api.InputUser, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-468934396) + userId.serialize(buffer, true) + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getCommonChats", parameters: [("userId", ConstructorParameterDescription(userId)), ("maxId", ConstructorParameterDescription(maxId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.messages { + static func getCustomEmojiDocuments(documentId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Document]>) { + let buffer = Buffer() + buffer.appendInt32(-643100844) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documentId.count)) + for item in documentId { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getCustomEmojiDocuments", parameters: [("documentId", ConstructorParameterDescription(documentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Document]? in + let reader = BufferReader(buffer) + var result: [Api.Document]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDefaultHistoryTTL() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1703637384) + return (FunctionDescription(name: "messages.getDefaultHistoryTTL", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DefaultHistoryTTL? in + let reader = BufferReader(buffer) + var result: Api.DefaultHistoryTTL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DefaultHistoryTTL + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDefaultTagReactions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1107741656) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDefaultTagReactions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDhConfig(version: Int32, randomLength: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(651135312) + serializeInt32(version, buffer: buffer, boxed: false) + serializeInt32(randomLength, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDhConfig", parameters: [("version", ConstructorParameterDescription(version)), ("randomLength", ConstructorParameterDescription(randomLength))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DhConfig? in + let reader = BufferReader(buffer) + var result: Api.messages.DhConfig? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DhConfig + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-271283063) + return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in + let reader = BufferReader(buffer) + var result: Api.messages.DialogFilters? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogUnreadMarks(flags: Int32, parentPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogPeer]>) { + let buffer = Buffer() + buffer.appendInt32(555754018) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + parentPeer!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getDialogUnreadMarks", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogPeer]? in + let reader = BufferReader(buffer) + var result: [Api.DialogPeer]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogPeer.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDialogs(flags: Int32, folderId: Int32?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1594569905) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(folderId!, buffer: buffer, boxed: false) + } + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Dialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.Dialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Dialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDiscussionMessage(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1147761405) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDiscussionMessage", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DiscussionMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.DiscussionMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DiscussionMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func getDocumentByHash(sha256: Buffer, size: Int64, mimeType: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1309538785) + serializeBytes(sha256, buffer: buffer, boxed: false) + serializeInt64(size, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getDocumentByHash", parameters: [("sha256", ConstructorParameterDescription(sha256)), ("size", ConstructorParameterDescription(size)), ("mimeType", ConstructorParameterDescription(mimeType))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Document? in + let reader = BufferReader(buffer) + var result: Api.Document? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Document + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiGameInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-75592537) + return (FunctionDescription(name: "messages.getEmojiGameInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGameInfo? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGameInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1955122779) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywords(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(899735650) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiKeywords", parameters: [("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in + let reader = BufferReader(buffer) + var result: Api.EmojiKeywordsDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywordsDifference(langCode: String, fromVersion: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(352892591) + serializeString(langCode, buffer: buffer, boxed: false) + serializeInt32(fromVersion, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiKeywordsDifference", parameters: [("langCode", ConstructorParameterDescription(langCode)), ("fromVersion", ConstructorParameterDescription(fromVersion))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiKeywordsDifference? in + let reader = BufferReader(buffer) + var result: Api.EmojiKeywordsDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiKeywordsDifference + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiKeywordsLanguages(langCodes: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.EmojiLanguage]>) { + let buffer = Buffer() + buffer.appendInt32(1318675378) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(langCodes.count)) + for item in langCodes { + serializeString(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getEmojiKeywordsLanguages", parameters: [("langCodes", ConstructorParameterDescription(langCodes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.EmojiLanguage]? in + let reader = BufferReader(buffer) + var result: [Api.EmojiLanguage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.EmojiLanguage.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiProfilePhotoGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(564480243) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiProfilePhotoGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStatusGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(785209037) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStatusGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStickerGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(500711669) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStickerGroups", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGroups? in + let reader = BufferReader(buffer) + var result: Api.messages.EmojiGroups? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGroups + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-67329649) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getEmojiURL(langCode: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-709817306) + serializeString(langCode, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getEmojiURL", parameters: [("langCode", ConstructorParameterDescription(langCode))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiURL? in + let reader = BufferReader(buffer) + var result: Api.EmojiURL? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiURL + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExportedChatInvite(peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1937010524) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getExportedChatInvite", parameters: [("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvite + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1565154314) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + adminId.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(offsetDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(offsetLink!, buffer: buffer, boxed: false) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("adminId", ConstructorParameterDescription(adminId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetLink", ConstructorParameterDescription(offsetLink)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in + let reader = BufferReader(buffer) + var result: Api.messages.ExportedChatInvites? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites + } + return result + }) + } +} +public extension Api.functions.messages { + static func getExtendedMedia(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2064119788) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getExtendedMedia", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFactCheck(peer: Api.InputPeer, msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FactCheck]>) { + let buffer = Buffer() + buffer.appendInt32(-1177696786) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(msgId.count)) + for item in msgId { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getFactCheck", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FactCheck]? in + let reader = BufferReader(buffer) + var result: [Api.FactCheck]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FactCheck.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFavedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(82946729) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFavedStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FavedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FavedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FavedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFeaturedEmojiStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(248473398) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFeaturedEmojiStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFeaturedStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1685588756) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFeaturedStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getForumTopics(flags: Int32, peer: Api.InputPeer, q: String?, offsetDate: Int32, offsetId: Int32, offsetTopic: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1000635391) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(q!, buffer: buffer, boxed: false) + } + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetTopic, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getForumTopics", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetTopic", ConstructorParameterDescription(offsetTopic)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} +public extension Api.functions.messages { + static func getForumTopicsByID(peer: Api.InputPeer, topics: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1358280184) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(topics.count)) + for item in topics { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getForumTopicsByID", parameters: [("peer", ConstructorParameterDescription(peer)), ("topics", ConstructorParameterDescription(topics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ForumTopics? in + let reader = BufferReader(buffer) + var result: Api.messages.ForumTopics? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ForumTopics + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFullChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1364194508) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getFullChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatFull? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatFull + } + return result + }) + } +} +public extension Api.functions.messages { + static func getFutureChatCreatorAfterLeave(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(998051494) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getFutureChatCreatorAfterLeave", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in + let reader = BufferReader(buffer) + var result: Api.User? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.User + } + return result + }) + } +} +public extension Api.functions.messages { + static func getGameHighScores(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-400399203) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.getGameHighScores", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in + let reader = BufferReader(buffer) + var result: Api.messages.HighScores? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HighScores + } + return result + }) + } +} +public extension Api.functions.messages { + static func getHistory(peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1143203525) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getInlineBotResults(flags: Int32, bot: Api.InputUser, peer: Api.InputPeer, geoPoint: Api.InputGeoPoint?, query: String, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1364105629) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + geoPoint!.serialize(buffer, true) + } + serializeString(query, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getInlineBotResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("geoPoint", ConstructorParameterDescription(geoPoint)), ("query", ConstructorParameterDescription(query)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotResults? in + let reader = BufferReader(buffer) + var result: Api.messages.BotResults? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotResults + } + return result + }) + } +} +public extension Api.functions.messages { + static func getInlineGameHighScores(id: Api.InputBotInlineMessageID, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(258170395) + id.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.getInlineGameHighScores", parameters: [("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HighScores? in + let reader = BufferReader(buffer) + var result: Api.messages.HighScores? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HighScores + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMaskStickers(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1678738104) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMaskStickers", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AllStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.AllStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AllStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageEditData(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-39416522) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageEditData", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageEditData? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageEditData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageEditData + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1176190792) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + reaction!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(offset!, buffer: buffer, boxed: false) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageReactionsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reaction", ConstructorParameterDescription(reaction)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageReactionsList? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageReactionsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageReactionsList + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessageReadParticipants(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReadParticipantDate]>) { + let buffer = Buffer() + buffer.appendInt32(834782287) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMessageReadParticipants", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReadParticipantDate]? in + let reader = BufferReader(buffer) + var result: [Api.ReadParticipantDate]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReadParticipantDate.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessages(id: [Api.InputMessage]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1673946374) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getMessages", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessagesReactions(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1950707482) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getMessagesReactions", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMessagesViews(peer: Api.InputPeer, id: [Int32], increment: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1468322785) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + increment.serialize(buffer, true) + return (FunctionDescription(name: "messages.getMessagesViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("increment", ConstructorParameterDescription(increment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MessageViews? in + let reader = BufferReader(buffer) + var result: Api.messages.MessageViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MessageViews + } + return result + }) + } +} +public extension Api.functions.messages { + static func getMyStickers(offsetId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-793386500) + serializeInt64(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getMyStickers", parameters: [("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.MyStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.MyStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.MyStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOldFeaturedStickers(offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2127598753) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getOldFeaturedStickers", parameters: [("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FeaturedStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FeaturedStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FeaturedStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOnlines(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1848369232) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getOnlines", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ChatOnlines? in + let reader = BufferReader(buffer) + var result: Api.ChatOnlines? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ChatOnlines + } + return result + }) + } +} +public extension Api.functions.messages { + static func getOutboxReadDate(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1941176739) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getOutboxReadDate", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.OutboxReadDate? in + let reader = BufferReader(buffer) + var result: Api.OutboxReadDate? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.OutboxReadDate + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPaidReactionPrivacy() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1193563562) + return (FunctionDescription(name: "messages.getPaidReactionPrivacy", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPeerDialogs(peers: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-462373635) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peers.count)) + for item in peers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getPeerDialogs", parameters: [("peers", ConstructorParameterDescription(peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPeerSettings(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-270948702) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.getPeerSettings", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerSettings? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerSettings? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerSettings + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPersonalChannelHistory(userId: Api.InputUser, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1442515350) + userId.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPersonalChannelHistory", parameters: [("userId", ConstructorParameterDescription(userId)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPinnedDialogs(folderId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-692498958) + serializeInt32(folderId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPinnedDialogs", parameters: [("folderId", ConstructorParameterDescription(folderId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PeerDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.PeerDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PeerDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPinnedSavedDialogs() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-700607264) + return (FunctionDescription(name: "messages.getPinnedSavedDialogs", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPollResults(peer: Api.InputPeer, msgId: Int32, pollHash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-308026565) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(pollHash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPollResults", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("pollHash", ConstructorParameterDescription(pollHash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPollVotes(flags: Int32, peer: Api.InputPeer, id: Int32, option: Buffer?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1200736242) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeBytes(option!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(offset!, buffer: buffer, boxed: false) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.VotesList? in + let reader = BufferReader(buffer) + var result: Api.messages.VotesList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.VotesList + } + return result + }) + } +} +public extension Api.functions.messages { + static func getPreparedInlineMessage(bot: Api.InputUser, id: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2055291464) + bot.serialize(buffer, true) + serializeString(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getPreparedInlineMessage", parameters: [("bot", ConstructorParameterDescription(bot)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.PreparedInlineMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.PreparedInlineMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.PreparedInlineMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func getQuickReplies(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-729550168) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getQuickReplies", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.QuickReplies? in + let reader = BufferReader(buffer) + var result: Api.messages.QuickReplies? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.QuickReplies + } + return result + }) + } +} +public extension Api.functions.messages { + static func getQuickReplyMessages(flags: Int32, shortcutId: Int32, id: [Int32]?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1801153085) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id!.count)) + for item in id! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getQuickReplyMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentLocations(peer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1881817312) + peer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentLocations", parameters: [("peer", ConstructorParameterDescription(peer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(960896434) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentReactions", parameters: [("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRecentStickers(flags: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1649852357) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRecentStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.RecentStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.RecentStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.RecentStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getReplies(peer: Api.InputPeer, msgId: Int32, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(584962828) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getReplies", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getRichMessage(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1343580623) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getRichMessage", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedDialogs(flags: Int32, parentPeer: Api.InputPeer?, offsetDate: Int32, offsetId: Int32, offsetPeer: Api.InputPeer, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(512883865) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + parentPeer!.serialize(buffer, true) + } + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedDialogsByID(flags: Int32, parentPeer: Api.InputPeer?, ids: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1869585558) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + parentPeer!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(ids.count)) + for item in ids { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getSavedDialogsByID", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("ids", ConstructorParameterDescription(ids))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedDialogs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedDialogs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedDialogs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedGifs(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1559270965) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedGifs", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedGifs? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedGifs? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedGifs + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedHistory(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputPeer, offsetId: Int32, offsetDate: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1718964215) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + parentPeer!.serialize(buffer, true) + } + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedHistory", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSavedReactionTags(flags: Int32, peer: Api.InputPeer?, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(909631579) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + peer!.serialize(buffer, true) + } + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSavedReactionTags", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SavedReactionTags? in + let reader = BufferReader(buffer) + var result: Api.messages.SavedReactionTags? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SavedReactionTags + } + return result + }) + } +} +public extension Api.functions.messages { + static func getScheduledHistory(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-183077365) + peer.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getScheduledHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1111817116) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchCounters(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, topMsgId: Int32?, filters: [Api.MessagesFilter]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.messages.SearchCounter]>) { + let buffer = Buffer() + buffer.appendInt32(465367808) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + savedPeerId!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(filters.count)) + for item in filters { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.getSearchCounters", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("filters", ConstructorParameterDescription(filters))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.messages.SearchCounter]? in + let reader = BufferReader(buffer) + var result: [Api.messages.SearchCounter]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.messages.SearchCounter.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1789130429) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + savedPeerId!.serialize(buffer, true) + } + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("filter", ConstructorParameterDescription(filter)), ("offsetId", ConstructorParameterDescription(offsetId)), ("offsetDate", ConstructorParameterDescription(offsetDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsCalendar? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsCalendar + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSearchResultsPositions(flags: Int32, peer: Api.InputPeer, savedPeerId: Api.InputPeer?, filter: Api.MessagesFilter, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1669386480) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + savedPeerId!.serialize(buffer, true) + } + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsPositions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("filter", ConstructorParameterDescription(filter)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsPositions? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsPositions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsPositions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSplitRanges() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.MessageRange]>) { + let buffer = Buffer() + buffer.appendInt32(486505992) + return (FunctionDescription(name: "messages.getSplitRanges", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.MessageRange]? in + let reader = BufferReader(buffer) + var result: [Api.MessageRange]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageRange.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSponsoredMessages(flags: Int32, peer: Api.InputPeer, msgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1030547536) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(msgId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.getSponsoredMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SponsoredMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.SponsoredMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SponsoredMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getStickerSet(stickerset: Api.InputStickerSet, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-928977804) + stickerset.serialize(buffer, true) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.messages { + static func getStickers(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-710552671) + serializeString(emoticon, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getStickers", parameters: [("emoticon", ConstructorParameterDescription(emoticon)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Stickers? in + let reader = BufferReader(buffer) + var result: Api.messages.Stickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Stickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func getSuggestedDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilterSuggested]>) { + let buffer = Buffer() + buffer.appendInt32(-1566780372) + return (FunctionDescription(name: "messages.getSuggestedDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilterSuggested]? in + let reader = BufferReader(buffer) + var result: [Api.DialogFilterSuggested]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilterSuggested.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func getTopReactions(limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1149164102) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getTopReactions", parameters: [("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Reactions? in + let reader = BufferReader(buffer) + var result: Api.messages.Reactions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Reactions + } + return result + }) + } +} +public extension Api.functions.messages { + static func getUnreadMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-251140208) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadMentions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getUnreadPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1126722802) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getUnreadReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1115713364) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + savedPeerId!.serialize(buffer, true) + } + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getUnreadReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func getWebPage(url: String, hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1919511901) + serializeString(url, buffer: buffer, boxed: false) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getWebPage", parameters: [("url", ConstructorParameterDescription(url)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPage? in + let reader = BufferReader(buffer) + var result: Api.messages.WebPage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.WebPage + } + return result + }) + } +} +public extension Api.functions.messages { + static func getWebPagePreview(flags: Int32, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1460498287) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "messages.getWebPagePreview", parameters: [("flags", ConstructorParameterDescription(flags)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.WebPagePreview? in + let reader = BufferReader(buffer) + var result: Api.messages.WebPagePreview? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.WebPagePreview + } + return result + }) + } +} +public extension Api.functions.messages { + static func hideAllChatJoinRequests(flags: Int32, peer: Api.InputPeer, link: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-528091926) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(link!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.hideAllChatJoinRequests", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2145904661) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func hidePeerSettingsBar(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1336717624) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.hidePeerSettingsBar", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func importChatInvite(hash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-560905362) + serializeString(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.importChatInvite", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteJoinResult? in + let reader = BufferReader(buffer) + var result: Api.messages.ChatInviteJoinResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteJoinResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(873008187) + peer.serialize(buffer, true) + file.serialize(buffer, true) + serializeInt32(mediaCount, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.initHistoryImport", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file)), ("mediaCount", ConstructorParameterDescription(mediaCount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImport? in + let reader = BufferReader(buffer) + var result: Api.messages.HistoryImport? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImport + } + return result + }) + } +} +public extension Api.functions.messages { + static func installStickerSet(stickerset: Api.InputStickerSet, archived: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-946871200) + stickerset.serialize(buffer, true) + archived.serialize(buffer, true) + return (FunctionDescription(name: "messages.installStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("archived", ConstructorParameterDescription(archived))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSetInstallResult? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSetInstallResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSetInstallResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func markDialogUnread(flags: Int32, parentPeer: Api.InputPeer?, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1940912392) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + parentPeer!.serialize(buffer, true) + } + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.markDialogUnread", parameters: [("flags", ConstructorParameterDescription(flags)), ("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func migrateChat(chatId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1568189671) + serializeInt64(chatId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.migrateChat", parameters: [("chatId", ConstructorParameterDescription(chatId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func prolongWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, queryId: Int64, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1328014717) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.prolongWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("queryId", ConstructorParameterDescription(queryId)), ("replyTo", ConstructorParameterDescription(replyTo)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func rateTranscribedAudio(peer: Api.InputPeer, msgId: Int32, transcriptionId: Int64, good: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2132608815) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt64(transcriptionId, buffer: buffer, boxed: false) + good.serialize(buffer, true) + return (FunctionDescription(name: "messages.rateTranscribedAudio", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("transcriptionId", ConstructorParameterDescription(transcriptionId)), ("good", ConstructorParameterDescription(good))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readDiscussion(peer: Api.InputPeer, msgId: Int32, readMaxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-147740172) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(readMaxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readDiscussion", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("readMaxId", ConstructorParameterDescription(readMaxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readEncryptedHistory(peer: Api.InputEncryptedChat, maxDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2135648522) + peer.serialize(buffer, true) + serializeInt32(maxDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readEncryptedHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxDate", ConstructorParameterDescription(maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readFeaturedStickers(id: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1527873830) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readFeaturedStickers", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func readHistory(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(238054714) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readHistory", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func readMentions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(921026381) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readMentions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func readMessageContents(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(916930423) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readMessageContents", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedMessages + } + return result + }) + } +} +public extension Api.functions.messages { + static func readPollVotes(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(388019416) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.readPollVotes", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func readReactions(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1631301741) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + savedPeerId!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.readReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func readSavedHistory(parentPeer: Api.InputPeer, peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1169540261) + parentPeer.serialize(buffer, true) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.readSavedHistory", parameters: [("parentPeer", ConstructorParameterDescription(parentPeer)), ("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func receivedMessages(maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.ReceivedNotifyMessage]>) { + let buffer = Buffer() + buffer.appendInt32(94983360) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.receivedMessages", parameters: [("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.ReceivedNotifyMessage]? in + let reader = BufferReader(buffer) + var result: [Api.ReceivedNotifyMessage]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.ReceivedNotifyMessage.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func receivedQueue(maxQts: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { + let buffer = Buffer() + buffer.appendInt32(1436924774) + serializeInt32(maxQts, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.receivedQueue", parameters: [("maxQts", ConstructorParameterDescription(maxQts))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in + let reader = BufferReader(buffer) + var result: [Int64]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderPinnedDialogs(flags: Int32, folderId: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(991616823) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(folderId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.reorderPinnedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderPinnedForumTopics(flags: Int32, peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(242762224) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderPinnedForumTopics", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1955502713) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.reorderPinnedSavedDialogs", parameters: [("flags", ConstructorParameterDescription(flags)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderQuickReplies(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1613961479) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderQuickReplies", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reorderStickerSets(flags: Int32, order: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2016638777) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func report(peer: Api.InputPeer, id: [Int32], option: Buffer, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-59199589) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeBytes(option, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.report", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReportResult? in + let reader = BufferReader(buffer) + var result: Api.ReportResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReportResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportEncryptedSpam(peer: Api.InputEncryptedChat) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1259113487) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportEncryptedSpam", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportMessagesDelivery(flags: Int32, peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1517122453) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reportMessagesDelivery", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportMusicListen(id: Api.InputDocument, listenedDuration: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-574826471) + id.serialize(buffer, true) + serializeInt32(listenedDuration, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.reportMusicListen", parameters: [("id", ConstructorParameterDescription(id)), ("listenedDuration", ConstructorParameterDescription(listenedDuration))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1063567478) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + reactionPeer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reactionPeer", ConstructorParameterDescription(reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportReadMetrics(peer: Api.InputPeer, metrics: [Api.InputMessageReadMetric]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1080542694) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(metrics.count)) + for item in metrics { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.reportReadMetrics", parameters: [("peer", ConstructorParameterDescription(peer)), ("metrics", ConstructorParameterDescription(metrics))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportSpam(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-820669733) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportSpam", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func reportSponsoredMessage(randomId: Buffer, option: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(315355332) + serializeBytes(randomId, buffer: buffer, boxed: false) + serializeBytes(option, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.reportSponsoredMessage", parameters: [("randomId", ConstructorParameterDescription(randomId)), ("option", ConstructorParameterDescription(option))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.channels.SponsoredMessageReportResult? in + let reader = BufferReader(buffer) + var result: Api.channels.SponsoredMessageReportResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.channels.SponsoredMessageReportResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestAppWebView(flags: Int32, peer: Api.InputPeer, app: Api.InputBotApp, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1398901710) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + app.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(startParam!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + themeParams!.serialize(buffer, true) + } + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestAppWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("app", ConstructorParameterDescription(app)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + let reader = BufferReader(buffer) + var result: Api.WebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestEncryption(userId: Api.InputUser, randomId: Int32, gA: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-162681021) + userId.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestEncryption", parameters: [("userId", ConstructorParameterDescription(userId)), ("randomId", ConstructorParameterDescription(randomId)), ("gA", ConstructorParameterDescription(gA))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedChat? in + let reader = BufferReader(buffer) + var result: Api.EncryptedChat? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedChat + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestMainWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-908059013) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(startParam!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + themeParams!.serialize(buffer, true) + } + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestMainWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + let reader = BufferReader(buffer) + var result: Api.WebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1094336115) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 3) != 0 { + serializeString(url!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 4) != 0 { + serializeString(startParam!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + themeParams!.serialize(buffer, true) + } + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("url", ConstructorParameterDescription(url)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + let reader = BufferReader(buffer) + var result: Api.WebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestUrlAuth(flags: Int32, peer: Api.InputPeer?, msgId: Int32?, buttonId: Int32?, url: String?, inAppOrigin: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1991456356) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + peer!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(msgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(buttonId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(url!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeString(inAppOrigin!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.requestUrlAuth", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("url", ConstructorParameterDescription(url)), ("inAppOrigin", ConstructorParameterDescription(inAppOrigin))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.UrlAuthResult? in + let reader = BufferReader(buffer) + var result: Api.UrlAuthResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.UrlAuthResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyTo: Api.InputReplyTo?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(647873217) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(url!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeString(startParam!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + themeParams!.serialize(buffer, true) + } + serializeString(platform, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot)), ("url", ConstructorParameterDescription(url)), ("startParam", ConstructorParameterDescription(startParam)), ("themeParams", ConstructorParameterDescription(themeParams)), ("platform", ConstructorParameterDescription(platform)), ("replyTo", ConstructorParameterDescription(replyTo)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + let reader = BufferReader(buffer) + var result: Api.WebViewResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewResult + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveDefaultSendAs(peer: Api.InputPeer, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-855777386) + peer.serialize(buffer, true) + sendAs.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveDefaultSendAs", parameters: [("peer", ConstructorParameterDescription(peer)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?, effect: Int64?, suggestedPost: Api.SuggestedPost?, richMessage: Api.InputRichMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1391484580) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 4) != 0 { + replyTo!.serialize(buffer, true) + } + peer.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 5) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + serializeInt64(effect!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 8) != 0 { + suggestedPost!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 9) != 0 { + richMessage!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", ConstructorParameterDescription(flags)), ("replyTo", ConstructorParameterDescription(replyTo)), ("peer", ConstructorParameterDescription(peer)), ("message", ConstructorParameterDescription(message)), ("entities", ConstructorParameterDescription(entities)), ("media", ConstructorParameterDescription(media)), ("effect", ConstructorParameterDescription(effect)), ("suggestedPost", ConstructorParameterDescription(suggestedPost)), ("richMessage", ConstructorParameterDescription(richMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveGif(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(846868683) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveGif", parameters: [("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func savePreparedInlineMessage(flags: Int32, result: Api.InputBotInlineResult, userId: Api.InputUser, peerTypes: [Api.InlineQueryPeerType]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-232816849) + serializeInt32(flags, buffer: buffer, boxed: false) + result.serialize(buffer, true) + userId.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(peerTypes!.count)) + for item in peerTypes! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "messages.savePreparedInlineMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("result", ConstructorParameterDescription(result)), ("userId", ConstructorParameterDescription(userId)), ("peerTypes", ConstructorParameterDescription(peerTypes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.BotPreparedInlineMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.BotPreparedInlineMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.BotPreparedInlineMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func saveRecentSticker(flags: Int32, id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(958863608) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + unsave.serialize(buffer, true) + return (FunctionDescription(name: "messages.saveRecentSticker", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("unsave", ConstructorParameterDescription(unsave))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func search(flags: Int32, peer: Api.InputPeer, q: String, fromId: Api.InputPeer?, savedPeerId: Api.InputPeer?, savedReaction: [Api.Reaction]?, topMsgId: Int32?, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetId: Int32, addOffset: Int32, limit: Int32, maxId: Int32, minId: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(703497338) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(q, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + fromId!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + savedPeerId!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(savedReaction!.count)) + for item in savedReaction! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(addOffset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt32(maxId, buffer: buffer, boxed: false) + serializeInt32(minId, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.search", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("fromId", ConstructorParameterDescription(fromId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId)), ("savedReaction", ConstructorParameterDescription(savedReaction)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("filter", ConstructorParameterDescription(filter)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate)), ("offsetId", ConstructorParameterDescription(offsetId)), ("addOffset", ConstructorParameterDescription(addOffset)), ("limit", ConstructorParameterDescription(limit)), ("maxId", ConstructorParameterDescription(maxId)), ("minId", ConstructorParameterDescription(minId)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchCustomEmoji(emoticon: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(739360983) + serializeString(emoticon, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchCustomEmoji", parameters: [("emoticon", ConstructorParameterDescription(emoticon)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EmojiList? in + let reader = BufferReader(buffer) + var result: Api.EmojiList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EmojiList + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchEmojiStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1833678516) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchEmojiStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in + let reader = BufferReader(buffer) + var result: Api.messages.FoundStickerSets? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchGlobal(flags: Int32, folderId: Int32?, q: String, filter: Api.MessagesFilter, minDate: Int32, maxDate: Int32, offsetRate: Int32, offsetPeer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1271290010) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(folderId!, buffer: buffer, boxed: false) + } + serializeString(q, buffer: buffer, boxed: false) + filter.serialize(buffer, true) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(maxDate, buffer: buffer, boxed: false) + serializeInt32(offsetRate, buffer: buffer, boxed: false) + offsetPeer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchGlobal", parameters: [("flags", ConstructorParameterDescription(flags)), ("folderId", ConstructorParameterDescription(folderId)), ("q", ConstructorParameterDescription(q)), ("filter", ConstructorParameterDescription(filter)), ("minDate", ConstructorParameterDescription(minDate)), ("maxDate", ConstructorParameterDescription(maxDate)), ("offsetRate", ConstructorParameterDescription(offsetRate)), ("offsetPeer", ConstructorParameterDescription(offsetPeer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchSentMedia(q: String, filter: Api.MessagesFilter, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(276705696) + serializeString(q, buffer: buffer, boxed: false) + filter.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchSentMedia", parameters: [("q", ConstructorParameterDescription(q)), ("filter", ConstructorParameterDescription(filter)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Messages? in + let reader = BufferReader(buffer) + var result: Api.messages.Messages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Messages + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchStickerSets(flags: Int32, q: String, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(896555914) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickerSets? in + let reader = BufferReader(buffer) + var result: Api.messages.FoundStickerSets? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickerSets + } + return result + }) + } +} +public extension Api.functions.messages { + static func searchStickers(flags: Int32, q: String, emoticon: String, langCode: [String], offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(699516522) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(q, buffer: buffer, boxed: false) + serializeString(emoticon, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(langCode.count)) + for item in langCode { + serializeString(item, buffer: buffer, boxed: false) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.searchStickers", parameters: [("flags", ConstructorParameterDescription(flags)), ("q", ConstructorParameterDescription(q)), ("emoticon", ConstructorParameterDescription(emoticon)), ("langCode", ConstructorParameterDescription(langCode)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickers? in + let reader = BufferReader(buffer) + var result: Api.messages.FoundStickers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickers + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendBotRequestedPeer(flags: Int32, peer: Api.InputPeer, msgId: Int32?, webappReqId: String?, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1818030759) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(msgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(webappReqId!, buffer: buffer, boxed: false) + } + serializeInt32(buttonId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(requestedPeers.count)) + for item in requestedPeers { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("webappReqId", ConstructorParameterDescription(webappReqId)), ("buttonId", ConstructorParameterDescription(buttonId)), ("requestedPeers", ConstructorParameterDescription(requestedPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendEncrypted(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1157265941) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendEncrypted", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendEncryptedFile(flags: Int32, peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1431914525) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + file.serialize(buffer, true) + return (FunctionDescription(name: "messages.sendEncryptedFile", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendEncryptedService(peer: Api.InputEncryptedChat, randomId: Int64, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(852769188) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendEncryptedService", parameters: [("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SentEncryptedMessage? in + let reader = BufferReader(buffer) + var result: Api.messages.SentEncryptedMessage? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SentEncryptedMessage + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendInlineBotResult(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, randomId: Int64, queryId: Int64, id: String, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1060145594) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + serializeString(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 10) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 17) != 0 { + quickReplyShortcut!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 21) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendInlineBotResult", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("randomId", ConstructorParameterDescription(randomId)), ("queryId", ConstructorParameterDescription(queryId)), ("id", ConstructorParameterDescription(id)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, media: Api.InputMedia, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(53536639) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + media.serialize(buffer, true) + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + replyMarkup!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 10) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 24) != 0 { + serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 17) != 0 { + quickReplyShortcut!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 18) != 0 { + serializeInt64(effect!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 21) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 22) != 0 { + suggestedPost!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.sendMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("media", ConstructorParameterDescription(media)), ("message", ConstructorParameterDescription(message)), ("randomId", ConstructorParameterDescription(randomId)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendMessage(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, message: String, randomId: Int64, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, scheduleDate: Int32?, scheduleRepeatPeriod: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?, richMessage: Api.InputRichMessage?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-17526942) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + serializeString(message, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + replyMarkup!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 10) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 24) != 0 { + serializeInt32(scheduleRepeatPeriod!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 17) != 0 { + quickReplyShortcut!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 18) != 0 { + serializeInt64(effect!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 21) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 22) != 0 { + suggestedPost!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 23) != 0 { + richMessage!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.sendMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("message", ConstructorParameterDescription(message)), ("randomId", ConstructorParameterDescription(randomId)), ("replyMarkup", ConstructorParameterDescription(replyMarkup)), ("entities", ConstructorParameterDescription(entities)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("scheduleRepeatPeriod", ConstructorParameterDescription(scheduleRepeatPeriod)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("suggestedPost", ConstructorParameterDescription(suggestedPost)), ("richMessage", ConstructorParameterDescription(richMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendMultiMedia(flags: Int32, peer: Api.InputPeer, replyTo: Api.InputReplyTo?, multiMedia: [Api.InputSingleMedia], scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, effect: Int64?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(469278068) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + replyTo!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(multiMedia.count)) + for item in multiMedia { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 10) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 13) != 0 { + sendAs!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 17) != 0 { + quickReplyShortcut!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 18) != 0 { + serializeInt64(effect!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 21) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendMultiMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("multiMedia", ConstructorParameterDescription(multiMedia)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("sendAs", ConstructorParameterDescription(sendAs)), ("quickReplyShortcut", ConstructorParameterDescription(quickReplyShortcut)), ("effect", ConstructorParameterDescription(effect)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendPaidReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, count: Int32, randomId: Int64, `private`: Api.PaidReactionPrivacy?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1488702288) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + `private`!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.sendPaidReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("count", ConstructorParameterDescription(count)), ("randomId", ConstructorParameterDescription(randomId)), ("`private`", ConstructorParameterDescription(`private`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendQuickReplyMessages(peer: Api.InputPeer, shortcutId: Int32, id: [Int32], randomId: [Int64]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1819610593) + peer.serialize(buffer, true) + serializeInt32(shortcutId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomId.count)) + for item in randomId { + serializeInt64(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendQuickReplyMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("shortcutId", ConstructorParameterDescription(shortcutId)), ("id", ConstructorParameterDescription(id)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendReaction(flags: Int32, peer: Api.InputPeer, msgId: Int32, reaction: [Api.Reaction]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-754091820) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(reaction!.count)) + for item in reaction! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "messages.sendReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1120369398) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendScheduledMessages", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendScreenshotNotification(peer: Api.InputPeer, replyTo: Api.InputReplyTo, randomId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1589618665) + peer.serialize(buffer, true) + replyTo.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendScreenshotNotification", parameters: [("peer", ConstructorParameterDescription(peer)), ("replyTo", ConstructorParameterDescription(replyTo)), ("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendVote(peer: Api.InputPeer, msgId: Int32, options: [Buffer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(283795844) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(options.count)) + for item in options { + serializeBytes(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.sendVote", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("options", ConstructorParameterDescription(options))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendWebViewData(bot: Api.InputUser, randomId: Int64, buttonText: String, data: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-603831608) + bot.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeString(buttonText, buffer: buffer, boxed: false) + serializeString(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.sendWebViewData", parameters: [("bot", ConstructorParameterDescription(bot)), ("randomId", ConstructorParameterDescription(randomId)), ("buttonText", ConstructorParameterDescription(buttonText)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func sendWebViewResultMessage(botQueryId: String, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(172168437) + serializeString(botQueryId, buffer: buffer, boxed: false) + result.serialize(buffer, true) + return (FunctionDescription(name: "messages.sendWebViewResultMessage", parameters: [("botQueryId", ConstructorParameterDescription(botQueryId)), ("result", ConstructorParameterDescription(result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewMessageSent? in + let reader = BufferReader(buffer) + var result: Api.WebViewMessageSent? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.WebViewMessageSent + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotCallbackAnswer(flags: Int32, queryId: Int64, message: String?, url: String?, cacheTime: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-712043766) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(message!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(url!, buffer: buffer, boxed: false) + } + serializeInt32(cacheTime, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setBotCallbackAnswer", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("message", ConstructorParameterDescription(message)), ("url", ConstructorParameterDescription(url)), ("cacheTime", ConstructorParameterDescription(cacheTime))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotGuestChatResult(queryId: Int64, result: Api.InputBotInlineResult) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1192163613) + serializeInt64(queryId, buffer: buffer, boxed: false) + result.serialize(buffer, true) + return (FunctionDescription(name: "messages.setBotGuestChatResult", parameters: [("queryId", ConstructorParameterDescription(queryId)), ("result", ConstructorParameterDescription(result))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.InputBotInlineMessageID? in + let reader = BufferReader(buffer) + var result: Api.InputBotInlineMessageID? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.InputBotInlineMessageID + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotPrecheckoutResults(flags: Int32, queryId: Int64, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(163765653) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(error!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.setBotPrecheckoutResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("error", ConstructorParameterDescription(error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setBotShippingResults(flags: Int32, queryId: Int64, error: String?, shippingOptions: [Api.ShippingOption]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-436833542) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(error!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(shippingOptions!.count)) + for item in shippingOptions! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "messages.setBotShippingResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("error", ConstructorParameterDescription(error)), ("shippingOptions", ConstructorParameterDescription(shippingOptions))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setChatAvailableReactions(flags: Int32, peer: Api.InputPeer, availableReactions: Api.ChatReactions, reactionsLimit: Int32?, paidEnabled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2041895551) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + availableReactions.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(reactionsLimit!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + paidEnabled!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.setChatAvailableReactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("availableReactions", ConstructorParameterDescription(availableReactions)), ("reactionsLimit", ConstructorParameterDescription(reactionsLimit)), ("paidEnabled", ConstructorParameterDescription(paidEnabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setChatTheme(peer: Api.InputPeer, theme: Api.InputChatTheme) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(135398089) + peer.serialize(buffer, true) + theme.serialize(buffer, true) + return (FunctionDescription(name: "messages.setChatTheme", parameters: [("peer", ConstructorParameterDescription(peer)), ("theme", ConstructorParameterDescription(theme))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1879389471) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + wallpaper!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + settings!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(id!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("wallpaper", ConstructorParameterDescription(wallpaper)), ("settings", ConstructorParameterDescription(settings)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1632299963) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setDefaultHistoryTTL", parameters: [("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setDefaultReaction(reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1330094102) + reaction.serialize(buffer, true) + return (FunctionDescription(name: "messages.setDefaultReaction", parameters: [("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setEncryptedTyping(peer: Api.InputEncryptedChat, typing: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2031374829) + peer.serialize(buffer, true) + typing.serialize(buffer, true) + return (FunctionDescription(name: "messages.setEncryptedTyping", parameters: [("peer", ConstructorParameterDescription(peer)), ("typing", ConstructorParameterDescription(typing))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setGameScore(flags: Int32, peer: Api.InputPeer, id: Int32, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1896289088) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(score, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setGameScore", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId)), ("score", ConstructorParameterDescription(score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setHistoryTTL(peer: Api.InputPeer, period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1207017500) + peer.serialize(buffer, true) + serializeInt32(period, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setHistoryTTL", parameters: [("peer", ConstructorParameterDescription(peer)), ("period", ConstructorParameterDescription(period))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func setInlineBotResults(flags: Int32, queryId: Int64, results: [Api.InputBotInlineResult], cacheTime: Int32, nextOffset: String?, switchPm: Api.InlineBotSwitchPM?, switchWebview: Api.InlineBotWebView?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1156406247) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(queryId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(results.count)) + for item in results { + item.serialize(buffer, true) + } + serializeInt32(cacheTime, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeString(nextOffset!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + switchPm!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 4) != 0 { + switchWebview!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.setInlineBotResults", parameters: [("flags", ConstructorParameterDescription(flags)), ("queryId", ConstructorParameterDescription(queryId)), ("results", ConstructorParameterDescription(results)), ("cacheTime", ConstructorParameterDescription(cacheTime)), ("nextOffset", ConstructorParameterDescription(nextOffset)), ("switchPm", ConstructorParameterDescription(switchPm)), ("switchWebview", ConstructorParameterDescription(switchWebview))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setInlineGameScore(flags: Int32, id: Api.InputBotInlineMessageID, userId: Api.InputUser, score: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(363700068) + serializeInt32(flags, buffer: buffer, boxed: false) + id.serialize(buffer, true) + userId.serialize(buffer, true) + serializeInt32(score, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.setInlineGameScore", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("userId", ConstructorParameterDescription(userId)), ("score", ConstructorParameterDescription(score))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func setTyping(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, action: Api.SendMessageAction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1486110434) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + action.serialize(buffer, true) + return (FunctionDescription(name: "messages.setTyping", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("action", ConstructorParameterDescription(action))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func startBot(bot: Api.InputUser, peer: Api.InputPeer, randomId: Int64, startParam: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-421563528) + bot.serialize(buffer, true) + peer.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeString(startParam, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.startBot", parameters: [("bot", ConstructorParameterDescription(bot)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("startParam", ConstructorParameterDescription(startParam))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func startHistoryImport(peer: Api.InputPeer, importId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1271008444) + peer.serialize(buffer, true) + serializeInt64(importId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.startHistoryImport", parameters: [("peer", ConstructorParameterDescription(peer)), ("importId", ConstructorParameterDescription(importId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func summarizeText(flags: Int32, peer: Api.InputPeer, id: Int32, toLang: String?, tone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1413754042) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(toLang!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(tone!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.summarizeText", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("toLang", ConstructorParameterDescription(toLang)), ("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.TextWithEntities? in + let reader = BufferReader(buffer) + var result: Api.TextWithEntities? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1777704297) + serializeInt32(flags, buffer: buffer, boxed: false) + bot.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-47326647) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", ConstructorParameterDescription(enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1489903017) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleDialogPin", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleNoForwards(flags: Int32, peer: Api.InputPeer, enabled: Api.Bool, requestMsgId: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1308091851) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + enabled.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(requestMsgId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.toggleNoForwards", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("enabled", ConstructorParameterDescription(enabled)), ("requestMsgId", ConstructorParameterDescription(requestMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func togglePaidReactionPrivacy(peer: Api.InputPeer, msgId: Int32, `private`: Api.PaidReactionPrivacy) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1129874869) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + `private`.serialize(buffer, true) + return (FunctionDescription(name: "messages.togglePaidReactionPrivacy", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("`private`", ConstructorParameterDescription(`private`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func togglePeerTranslations(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-461589127) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.togglePeerTranslations", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleSavedDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1400783906) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleSavedDialogPin", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleStickerSets(flags: Int32, stickersets: [Api.InputStickerSet]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1257951254) + serializeInt32(flags, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickersets.count)) + for item in stickersets { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.toggleStickerSets", parameters: [("flags", ConstructorParameterDescription(flags)), ("stickersets", ConstructorParameterDescription(stickersets))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleSuggestedPostApproval(flags: Int32, peer: Api.InputPeer, msgId: Int32, scheduleDate: Int32?, rejectComment: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2130229924) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(rejectComment!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.toggleSuggestedPostApproval", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("scheduleDate", ConstructorParameterDescription(scheduleDate)), ("rejectComment", ConstructorParameterDescription(rejectComment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func toggleTodoCompleted(peer: Api.InputPeer, msgId: Int32, completed: [Int32], incompleted: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-740282076) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(completed.count)) + for item in completed { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(incompleted.count)) + for item in incompleted { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.toggleTodoCompleted", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId)), ("completed", ConstructorParameterDescription(completed)), ("incompleted", ConstructorParameterDescription(incompleted))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func transcribeAudio(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(647928393) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.transcribeAudio", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranscribedAudio? in + let reader = BufferReader(buffer) + var result: Api.messages.TranscribedAudio? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.TranscribedAudio + } + return result + }) + } +} +public extension Api.functions.messages { + static func translateText(flags: Int32, peer: Api.InputPeer?, id: [Int32]?, text: [Api.TextWithEntities]?, toLang: String, tone: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1511079099) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + peer!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id!.count)) + for item in id! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(text!.count)) + for item in text! { + item.serialize(buffer, true) + } + } + serializeString(toLang, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeString(tone!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.translateText", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("text", ConstructorParameterDescription(text)), ("toLang", ConstructorParameterDescription(toLang)), ("tone", ConstructorParameterDescription(tone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.TranslatedText? in + let reader = BufferReader(buffer) + var result: Api.messages.TranslatedText? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.TranslatedText + } + return result + }) + } +} +public extension Api.functions.messages { + static func uninstallStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-110209570) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "messages.uninstallStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func unpinAllMessages(flags: Int32, peer: Api.InputPeer, topMsgId: Int32?, savedPeerId: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(103667527) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(topMsgId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + savedPeerId!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.unpinAllMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("topMsgId", ConstructorParameterDescription(topMsgId)), ("savedPeerId", ConstructorParameterDescription(savedPeerId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in + let reader = BufferReader(buffer) + var result: Api.messages.AffectedHistory? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(450142282) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + filter!.serialize(buffer, true) + } + return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", ConstructorParameterDescription(flags)), ("id", ConstructorParameterDescription(id)), ("filter", ConstructorParameterDescription(filter))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-983318044) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func updatePinnedForumTopic(peer: Api.InputPeer, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(392032849) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + pinned.serialize(buffer, true) + return (FunctionDescription(name: "messages.updatePinnedForumTopic", parameters: [("peer", ConstructorParameterDescription(peer)), ("topicId", ConstructorParameterDescription(topicId)), ("pinned", ConstructorParameterDescription(pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-760547348) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.updatePinnedMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.messages { + static func updateSavedReactionTag(flags: Int32, reaction: Api.Reaction, title: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1613331948) + serializeInt32(flags, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.updateSavedReactionTag", parameters: [("flags", ConstructorParameterDescription(flags)), ("reaction", ConstructorParameterDescription(reaction)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadEncryptedFile(peer: Api.InputEncryptedChat, file: Api.InputEncryptedFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1347929239) + peer.serialize(buffer, true) + file.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadEncryptedFile", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.EncryptedFile? in + let reader = BufferReader(buffer) + var result: Api.EncryptedFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.EncryptedFile + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadImportedMedia(peer: Api.InputPeer, importId: Int64, fileName: String, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(713433234) + peer.serialize(buffer, true) + serializeInt64(importId, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadImportedMedia", parameters: [("peer", ConstructorParameterDescription(peer)), ("importId", ConstructorParameterDescription(importId)), ("fileName", ConstructorParameterDescription(fileName)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + let reader = BufferReader(buffer) + var result: Api.MessageMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + return result + }) + } +} +public extension Api.functions.messages { + static func uploadMedia(flags: Int32, businessConnectionId: String?, peer: Api.InputPeer, media: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(345405816) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(businessConnectionId!, buffer: buffer, boxed: false) + } + peer.serialize(buffer, true) + media.serialize(buffer, true) + return (FunctionDescription(name: "messages.uploadMedia", parameters: [("flags", ConstructorParameterDescription(flags)), ("businessConnectionId", ConstructorParameterDescription(businessConnectionId)), ("peer", ConstructorParameterDescription(peer)), ("media", ConstructorParameterDescription(media))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in + let reader = BufferReader(buffer) + var result: Api.MessageMedia? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.MessageMedia + } + return result + }) + } +} +public extension Api.functions.messages { + static func viewSponsoredMessage(randomId: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(647902787) + serializeBytes(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.viewSponsoredMessage", parameters: [("randomId", ConstructorParameterDescription(randomId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func applyGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-152934316) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.applyGiftCode", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func assignAppStoreTransaction(receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2131921795) + serializeBytes(receipt, buffer: buffer, boxed: false) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("receipt", ConstructorParameterDescription(receipt)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func assignPlayMarketTransaction(receipt: Api.DataJSON, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-537046829) + receipt.serialize(buffer, true) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.assignPlayMarketTransaction", parameters: [("receipt", ConstructorParameterDescription(receipt)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func botCancelStarsSubscription(flags: Int32, userId: Api.InputUser, chargeId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1845102114) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeString(chargeId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.botCancelStarsSubscription", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("chargeId", ConstructorParameterDescription(chargeId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func canPurchaseStore(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1339842215) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.canPurchaseStore", parameters: [("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func changeStarsSubscription(flags: Int32, peer: Api.InputPeer, subscriptionId: String, canceled: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-948500360) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(subscriptionId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + canceled!.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.changeStarsSubscription", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("subscriptionId", ConstructorParameterDescription(subscriptionId)), ("canceled", ConstructorParameterDescription(canceled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func checkCanSendGift(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1060835895) + serializeInt64(giftId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.checkCanSendGift", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckCanSendGiftResult? in + let reader = BufferReader(buffer) + var result: Api.payments.CheckCanSendGiftResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.CheckCanSendGiftResult + } + return result + }) + } +} +public extension Api.functions.payments { + static func checkGiftCode(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1907247935) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.checkGiftCode", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.CheckedGiftCode? in + let reader = BufferReader(buffer) + var result: Api.payments.CheckedGiftCode? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.CheckedGiftCode + } + return result + }) + } +} +public extension Api.functions.payments { + static func clearSavedInfo(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-667062079) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.clearSavedInfo", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func connectStarRefBot(peer: Api.InputPeer, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2127901834) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + return (FunctionDescription(name: "payments.connectStarRefBot", parameters: [("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in + let reader = BufferReader(buffer) + var result: Api.payments.ConnectedStarRefBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots + } + return result + }) + } +} +public extension Api.functions.payments { + static func convertStarGift(stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1958676331) + stargift.serialize(buffer, true) + return (FunctionDescription(name: "payments.convertStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func craftStarGift(stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1325832113) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stargift.count)) + for item in stargift { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.craftStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func createStarGiftCollection(peer: Api.InputPeer, title: String, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(524947079) + peer.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stargift.count)) + for item in stargift { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.createStarGiftCollection", parameters: [("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarGiftCollection? in + let reader = BufferReader(buffer) + var result: Api.StarGiftCollection? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StarGiftCollection + } + return result + }) + } +} +public extension Api.functions.payments { + static func deleteStarGiftCollection(peer: Api.InputPeer, collectionId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1386854168) + peer.serialize(buffer, true) + serializeInt32(collectionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.deleteStarGiftCollection", parameters: [("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func editConnectedStarRefBot(flags: Int32, peer: Api.InputPeer, link: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-453204829) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(link, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.editConnectedStarRefBot", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("link", ConstructorParameterDescription(link))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in + let reader = BufferReader(buffer) + var result: Api.payments.ConnectedStarRefBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots + } + return result + }) + } +} +public extension Api.functions.payments { + static func exportInvoice(invoiceMedia: Api.InputMedia) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(261206117) + invoiceMedia.serialize(buffer, true) + return (FunctionDescription(name: "payments.exportInvoice", parameters: [("invoiceMedia", ConstructorParameterDescription(invoiceMedia))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ExportedInvoice? in + let reader = BufferReader(buffer) + var result: Api.payments.ExportedInvoice? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ExportedInvoice + } + return result + }) + } +} +public extension Api.functions.payments { + static func fulfillStarsSubscription(peer: Api.InputPeer, subscriptionId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-866391117) + peer.serialize(buffer, true) + serializeString(subscriptionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.fulfillStarsSubscription", parameters: [("peer", ConstructorParameterDescription(peer)), ("subscriptionId", ConstructorParameterDescription(subscriptionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func getBankCardData(number: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(779736953) + serializeString(number, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getBankCardData", parameters: [("number", ConstructorParameterDescription(number))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.BankCardData? in + let reader = BufferReader(buffer) + var result: Api.payments.BankCardData? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.BankCardData + } + return result + }) + } +} +public extension Api.functions.payments { + static func getConnectedStarRefBot(peer: Api.InputPeer, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1210476304) + peer.serialize(buffer, true) + bot.serialize(buffer, true) + return (FunctionDescription(name: "payments.getConnectedStarRefBot", parameters: [("peer", ConstructorParameterDescription(peer)), ("bot", ConstructorParameterDescription(bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in + let reader = BufferReader(buffer) + var result: Api.payments.ConnectedStarRefBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots + } + return result + }) + } +} +public extension Api.functions.payments { + static func getConnectedStarRefBots(flags: Int32, peer: Api.InputPeer, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1483318611) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(offsetDate!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(offsetLink!, buffer: buffer, boxed: false) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getConnectedStarRefBots", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offsetDate", ConstructorParameterDescription(offsetDate)), ("offsetLink", ConstructorParameterDescription(offsetLink)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ConnectedStarRefBots? in + let reader = BufferReader(buffer) + var result: Api.payments.ConnectedStarRefBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ConnectedStarRefBots + } + return result + }) + } +} +public extension Api.functions.payments { + static func getCraftStarGifts(giftId: Int64, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-49947392) + serializeInt64(giftId, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getCraftStarGifts", parameters: [("giftId", ConstructorParameterDescription(giftId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.SavedStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getGiveawayInfo(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-198994907) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getGiveawayInfo", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.GiveawayInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.GiveawayInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.GiveawayInfo + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(924093883) + serializeInt32(flags, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + themeParams!.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.getPaymentForm", parameters: [("flags", ConstructorParameterDescription(flags)), ("invoice", ConstructorParameterDescription(invoice)), ("themeParams", ConstructorParameterDescription(themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentForm? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentForm? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentForm + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPaymentReceipt(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(611897804) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getPaymentReceipt", parameters: [("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentReceipt? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentReceipt? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentReceipt + } + return result + }) + } +} +public extension Api.functions.payments { + static func getPremiumGiftCodeOptions(flags: Int32, boostPeer: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.PremiumGiftCodeOption]>) { + let buffer = Buffer() + buffer.appendInt32(660060756) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + boostPeer!.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.getPremiumGiftCodeOptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("boostPeer", ConstructorParameterDescription(boostPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.PremiumGiftCodeOption]? in + let reader = BufferReader(buffer) + var result: [Api.PremiumGiftCodeOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftCodeOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getResaleStarGifts(flags: Int32, attributesHash: Int64?, giftId: Int64, attributes: [Api.StarGiftAttributeId]?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2053087798) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(attributesHash!, buffer: buffer, boxed: false) + } + serializeInt64(giftId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes!.count)) + for item in attributes! { + item.serialize(buffer, true) + } + } + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getResaleStarGifts", parameters: [("flags", ConstructorParameterDescription(flags)), ("attributesHash", ConstructorParameterDescription(attributesHash)), ("giftId", ConstructorParameterDescription(giftId)), ("attributes", ConstructorParameterDescription(attributes)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ResaleStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.ResaleStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ResaleStarGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getSavedInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(578650699) + return (FunctionDescription(name: "payments.getSavedInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.SavedInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SavedInfo + } + return result + }) + } +} +public extension Api.functions.payments { + static func getSavedStarGift(stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1269456634) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stargift.count)) + for item in stargift { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.getSavedStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.SavedStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getSavedStarGifts(flags: Int32, peer: Api.InputPeer, collectionId: Int32?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1558583959) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 6) != 0 { + serializeInt32(collectionId!, buffer: buffer, boxed: false) + } + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getSavedStarGifts", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SavedStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.SavedStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SavedStarGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftActiveAuctions(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1513074355) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftActiveAuctions", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftActiveAuctions? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftActiveAuctions? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftActiveAuctions + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftAuctionAcquiredGifts(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1805831148) + serializeInt64(giftId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftAuctionAcquiredGifts", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftAuctionAcquiredGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftAuctionAcquiredGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftAuctionAcquiredGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftAuctionState(auction: Api.InputStarGiftAuction, version: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1553986774) + auction.serialize(buffer, true) + serializeInt32(version, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftAuctionState", parameters: [("auction", ConstructorParameterDescription(auction)), ("version", ConstructorParameterDescription(version))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftAuctionState? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftAuctionState? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftAuctionState + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftCollections(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1743023651) + peer.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftCollections", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftCollections? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftCollections? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftCollections + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftUpgradeAttributes(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1828948824) + serializeInt64(giftId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftUpgradeAttributes", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftUpgradeAttributes? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftUpgradeAttributes? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftUpgradeAttributes + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftUpgradePreview(giftId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1667580751) + serializeInt64(giftId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGiftUpgradePreview", parameters: [("giftId", ConstructorParameterDescription(giftId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftUpgradePreview? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftUpgradePreview? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftUpgradePreview + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGiftWithdrawalUrl(stargift: Api.InputSavedStarGift, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-798059608) + stargift.serialize(buffer, true) + password.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarGiftWithdrawalUrl", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGiftWithdrawalUrl? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGiftWithdrawalUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGiftWithdrawalUrl + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarGifts(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1000983152) + serializeInt32(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarGifts", parameters: [("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.StarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarGifts + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsGiftOptions(flags: Int32, userId: Api.InputUser?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsGiftOption]>) { + let buffer = Buffer() + buffer.appendInt32(-741774392) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + userId!.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.getStarsGiftOptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsGiftOption]? in + let reader = BufferReader(buffer) + var result: [Api.StarsGiftOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiftOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsGiveawayOptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsGiveawayOption]>) { + let buffer = Buffer() + buffer.appendInt32(-1122042562) + return (FunctionDescription(name: "payments.getStarsGiveawayOptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsGiveawayOption]? in + let reader = BufferReader(buffer) + var result: [Api.StarsGiveawayOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsGiveawayOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsRevenueAdsAccountUrl(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-774377531) + peer.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarsRevenueAdsAccountUrl", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueAdsAccountUrl? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsRevenueAdsAccountUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueAdsAccountUrl + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsRevenueStats(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-652215594) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarsRevenueStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueStats? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsRevenueStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueStats + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsRevenueWithdrawalUrl(flags: Int32, peer: Api.InputPeer, amount: Int64?, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(607378578) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeInt64(amount!, buffer: buffer, boxed: false) + } + password.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarsRevenueWithdrawalUrl", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("amount", ConstructorParameterDescription(amount)), ("password", ConstructorParameterDescription(password))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsRevenueWithdrawalUrl? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsRevenueWithdrawalUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsRevenueWithdrawalUrl + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsStatus(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1319744447) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "payments.getStarsStatus", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsSubscriptions(flags: Int32, peer: Api.InputPeer, offset: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(52761285) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarsSubscriptions", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsTopupOptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.StarsTopupOption]>) { + let buffer = Buffer() + buffer.appendInt32(-1072773165) + return (FunctionDescription(name: "payments.getStarsTopupOptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.StarsTopupOption]? in + let reader = BufferReader(buffer) + var result: [Api.StarsTopupOption]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTopupOption.self) + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsTransactions(flags: Int32, subscriptionId: String?, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1775912279) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + serializeString(subscriptionId!, buffer: buffer, boxed: false) + } + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getStarsTransactions", parameters: [("flags", ConstructorParameterDescription(flags)), ("subscriptionId", ConstructorParameterDescription(subscriptionId)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func getStarsTransactionsByID(flags: Int32, peer: Api.InputPeer, id: [Api.InputStarsTransaction]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(768218808) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.getStarsTransactionsByID", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.StarsStatus? in + let reader = BufferReader(buffer) + var result: Api.payments.StarsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.StarsStatus + } + return result + }) + } +} +public extension Api.functions.payments { + static func getSuggestedStarRefBots(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(225134839) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getSuggestedStarRefBots", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.SuggestedStarRefBots? in + let reader = BufferReader(buffer) + var result: Api.payments.SuggestedStarRefBots? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.SuggestedStarRefBots + } + return result + }) + } +} +public extension Api.functions.payments { + static func getUniqueStarGift(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1583919758) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getUniqueStarGift", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGift? in + let reader = BufferReader(buffer) + var result: Api.payments.UniqueStarGift? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGift + } + return result + }) + } +} +public extension Api.functions.payments { + static func getUniqueStarGiftValueInfo(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1130737515) + serializeString(slug, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.getUniqueStarGiftValueInfo", parameters: [("slug", ConstructorParameterDescription(slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UniqueStarGiftValueInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.UniqueStarGiftValueInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.UniqueStarGiftValueInfo + } + return result + }) + } +} +public extension Api.functions.payments { + static func launchPrepaidGiveaway(peer: Api.InputPeer, giveawayId: Int64, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1609928480) + peer.serialize(buffer, true) + serializeInt64(giveawayId, buffer: buffer, boxed: false) + purpose.serialize(buffer, true) + return (FunctionDescription(name: "payments.launchPrepaidGiveaway", parameters: [("peer", ConstructorParameterDescription(peer)), ("giveawayId", ConstructorParameterDescription(giveawayId)), ("purpose", ConstructorParameterDescription(purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func refundStarsCharge(userId: Api.InputUser, chargeId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(632196938) + userId.serialize(buffer, true) + serializeString(chargeId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.refundStarsCharge", parameters: [("userId", ConstructorParameterDescription(userId)), ("chargeId", ConstructorParameterDescription(chargeId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func reorderStarGiftCollections(peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1020594996) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "payments.reorderStarGiftCollections", parameters: [("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func resolveStarGiftOffer(flags: Int32, offerMsgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-372344804) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(offerMsgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "payments.resolveStarGiftOffer", parameters: [("flags", ConstructorParameterDescription(flags)), ("offerMsgId", ConstructorParameterDescription(offerMsgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func saveStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(707422588) + serializeInt32(flags, buffer: buffer, boxed: false) + stargift.serialize(buffer, true) + return (FunctionDescription(name: "payments.saveStarGift", parameters: [("flags", ConstructorParameterDescription(flags)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func sendPaymentForm(flags: Int32, formId: Int64, invoice: Api.InputInvoice, requestedInfoId: String?, shippingOptionId: String?, credentials: Api.InputPaymentCredentials, tipAmount: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(755192367) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(formId, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(requestedInfoId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(shippingOptionId!, buffer: buffer, boxed: false) + } + credentials.serialize(buffer, true) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt64(tipAmount!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "payments.sendPaymentForm", parameters: [("flags", ConstructorParameterDescription(flags)), ("formId", ConstructorParameterDescription(formId)), ("invoice", ConstructorParameterDescription(invoice)), ("requestedInfoId", ConstructorParameterDescription(requestedInfoId)), ("shippingOptionId", ConstructorParameterDescription(shippingOptionId)), ("credentials", ConstructorParameterDescription(credentials)), ("tipAmount", ConstructorParameterDescription(tipAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult + } + return result + }) + } +} +public extension Api.functions.payments { + static func sendStarGiftOffer(flags: Int32, peer: Api.InputPeer, slug: String, price: Api.StarsAmount, duration: Int32, randomId: Int64, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1883739327) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(slug, buffer: buffer, boxed: false) + price.serialize(buffer, true) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "payments.sendStarGiftOffer", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("slug", ConstructorParameterDescription(slug)), ("price", ConstructorParameterDescription(price)), ("duration", ConstructorParameterDescription(duration)), ("randomId", ConstructorParameterDescription(randomId)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func sendStarsForm(formId: Int64, invoice: Api.InputInvoice) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2040056084) + serializeInt64(formId, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + return (FunctionDescription(name: "payments.sendStarsForm", parameters: [("formId", ConstructorParameterDescription(formId)), ("invoice", ConstructorParameterDescription(invoice))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.PaymentResult? in + let reader = BufferReader(buffer) + var result: Api.payments.PaymentResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.PaymentResult + } + return result + }) + } +} +public extension Api.functions.payments { + static func toggleChatStarGiftNotifications(flags: Int32, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1626009505) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + return (FunctionDescription(name: "payments.toggleChatStarGiftNotifications", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func toggleStarGiftsPinnedToTop(peer: Api.InputPeer, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(353626032) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stargift.count)) + for item in stargift { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "payments.toggleStarGiftsPinnedToTop", parameters: [("peer", ConstructorParameterDescription(peer)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.payments { + static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2132285290) + stargift.serialize(buffer, true) + toId.serialize(buffer, true) + return (FunctionDescription(name: "payments.transferStarGift", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("toId", ConstructorParameterDescription(toId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func updateStarGiftCollection(flags: Int32, peer: Api.InputPeer, collectionId: Int32, title: String?, deleteStargift: [Api.InputSavedStarGift]?, addStargift: [Api.InputSavedStarGift]?, order: [Api.InputSavedStarGift]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1339932391) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(collectionId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(deleteStargift!.count)) + for item in deleteStargift! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(addStargift!.count)) + for item in addStargift! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + item.serialize(buffer, true) + } + } + return (FunctionDescription(name: "payments.updateStarGiftCollection", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("collectionId", ConstructorParameterDescription(collectionId)), ("title", ConstructorParameterDescription(title)), ("deleteStargift", ConstructorParameterDescription(deleteStargift)), ("addStargift", ConstructorParameterDescription(addStargift)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StarGiftCollection? in + let reader = BufferReader(buffer) + var result: Api.StarGiftCollection? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StarGiftCollection + } + return result + }) + } +} +public extension Api.functions.payments { + static func updateStarGiftPrice(stargift: Api.InputSavedStarGift, resellAmount: Api.StarsAmount) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-306287413) + stargift.serialize(buffer, true) + resellAmount.serialize(buffer, true) + return (FunctionDescription(name: "payments.updateStarGiftPrice", parameters: [("stargift", ConstructorParameterDescription(stargift)), ("resellAmount", ConstructorParameterDescription(resellAmount))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func upgradeStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1361648395) + serializeInt32(flags, buffer: buffer, boxed: false) + stargift.serialize(buffer, true) + return (FunctionDescription(name: "payments.upgradeStarGift", parameters: [("flags", ConstructorParameterDescription(flags)), ("stargift", ConstructorParameterDescription(stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.payments { + static func validateRequestedInfo(flags: Int32, invoice: Api.InputInvoice, info: Api.PaymentRequestedInfo) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1228345045) + serializeInt32(flags, buffer: buffer, boxed: false) + invoice.serialize(buffer, true) + info.serialize(buffer, true) + return (FunctionDescription(name: "payments.validateRequestedInfo", parameters: [("flags", ConstructorParameterDescription(flags)), ("invoice", ConstructorParameterDescription(invoice)), ("info", ConstructorParameterDescription(info))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ValidatedRequestedInfo? in + let reader = BufferReader(buffer) + var result: Api.payments.ValidatedRequestedInfo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.ValidatedRequestedInfo + } + return result + }) + } +} +public extension Api.functions.phone { + static func acceptCall(peer: Api.InputPhoneCall, gB: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1003664544) + peer.serialize(buffer, true) + serializeBytes(gB, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.acceptCall", parameters: [("peer", ConstructorParameterDescription(peer)), ("gB", ConstructorParameterDescription(gB)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func checkGroupCall(call: Api.InputGroupCall, sources: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1248003721) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sources.count)) + for item in sources { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "phone.checkGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("sources", ConstructorParameterDescription(sources))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.phone { + static func confirmCall(peer: Api.InputPhoneCall, gA: Buffer, keyFingerprint: Int64, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(788404002) + peer.serialize(buffer, true) + serializeBytes(gA, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.confirmCall", parameters: [("peer", ConstructorParameterDescription(peer)), ("gA", ConstructorParameterDescription(gA)), ("keyFingerprint", ConstructorParameterDescription(keyFingerprint)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func createConferenceCall(flags: Int32, randomId: Int32, publicKey: Int256?, block: Buffer?, params: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2097431739) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + serializeInt256(publicKey!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeBytes(block!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + params!.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("randomId", ConstructorParameterDescription(randomId)), ("publicKey", ConstructorParameterDescription(publicKey)), ("block", ConstructorParameterDescription(block)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func createGroupCall(flags: Int32, peer: Api.InputPeer, randomId: Int32, title: String?, scheduleDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1221445336) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(scheduleDate!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("randomId", ConstructorParameterDescription(randomId)), ("title", ConstructorParameterDescription(title)), ("scheduleDate", ConstructorParameterDescription(scheduleDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func declineConferenceCallInvite(msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1011325297) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.declineConferenceCallInvite", parameters: [("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func deleteConferenceCallParticipants(flags: Int32, call: Api.InputGroupCall, ids: [Int64], block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1935276763) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(ids.count)) + for item in ids { + serializeInt64(item, buffer: buffer, boxed: false) + } + serializeBytes(block, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.deleteConferenceCallParticipants", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("ids", ConstructorParameterDescription(ids)), ("block", ConstructorParameterDescription(block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func deleteGroupCallMessages(flags: Int32, call: Api.InputGroupCall, messages: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-162573065) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "phone.deleteGroupCallMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("messages", ConstructorParameterDescription(messages))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func deleteGroupCallParticipantMessages(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(499117216) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + participant.serialize(buffer, true) + return (FunctionDescription(name: "phone.deleteGroupCallParticipantMessages", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("participant", ConstructorParameterDescription(participant))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func discardCall(flags: Int32, peer: Api.InputPhoneCall, duration: Int32, reason: Api.PhoneCallDiscardReason, connectionId: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1295269440) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(duration, buffer: buffer, boxed: false) + reason.serialize(buffer, true) + serializeInt64(connectionId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.discardCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("duration", ConstructorParameterDescription(duration)), ("reason", ConstructorParameterDescription(reason)), ("connectionId", ConstructorParameterDescription(connectionId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2054648117) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func editGroupCallParticipant(flags: Int32, call: Api.InputGroupCall, participant: Api.InputPeer, muted: Api.Bool?, volume: Int32?, raiseHand: Api.Bool?, videoStopped: Api.Bool?, videoPaused: Api.Bool?, presentationPaused: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1524155713) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + participant.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + muted!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(volume!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + raiseHand!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + videoStopped!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 4) != 0 { + videoPaused!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 5) != 0 { + presentationPaused!.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.editGroupCallParticipant", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("participant", ConstructorParameterDescription(participant)), ("muted", ConstructorParameterDescription(muted)), ("volume", ConstructorParameterDescription(volume)), ("raiseHand", ConstructorParameterDescription(raiseHand)), ("videoStopped", ConstructorParameterDescription(videoStopped)), ("videoPaused", ConstructorParameterDescription(videoPaused)), ("presentationPaused", ConstructorParameterDescription(presentationPaused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func editGroupCallTitle(call: Api.InputGroupCall, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(480685066) + call.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.editGroupCallTitle", parameters: [("call", ConstructorParameterDescription(call)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func exportGroupCallInvite(flags: Int32, call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-425040769) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.exportGroupCallInvite", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.ExportedGroupCallInvite? in + let reader = BufferReader(buffer) + var result: Api.phone.ExportedGroupCallInvite? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.ExportedGroupCallInvite + } + return result + }) + } +} +public extension Api.functions.phone { + static func getCallConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1430593449) + return (FunctionDescription(name: "phone.getCallConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.DataJSON? in + let reader = BufferReader(buffer) + var result: Api.DataJSON? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.DataJSON + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCall(call: Api.InputGroupCall, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(68699611) + call.serialize(buffer, true) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-291534682) + call.serialize(buffer, true) + serializeInt32(subChainId, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.getGroupCallChainBlocks", parameters: [("call", ConstructorParameterDescription(call)), ("subChainId", ConstructorParameterDescription(subChainId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallJoinAs(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-277077702) + peer.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallJoinAs", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.JoinAsPeers? in + let reader = BufferReader(buffer) + var result: Api.phone.JoinAsPeers? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.JoinAsPeers + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallStars(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1868784386) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStars", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStars? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStars? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStars + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallStreamChannels(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(447879488) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStreamChannels", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamChannels? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStreamChannels? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamChannels + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupCallStreamRtmpUrl(flags: Int32, peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1525991226) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + revoke.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("revoke", ConstructorParameterDescription(revoke))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStreamRtmpUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamRtmpUrl + } + return result + }) + } +} +public extension Api.functions.phone { + static func getGroupParticipants(call: Api.InputGroupCall, ids: [Api.InputPeer], sources: [Int32], offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-984033109) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(ids.count)) + for item in ids { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(sources.count)) + for item in sources { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.getGroupParticipants", parameters: [("call", ConstructorParameterDescription(call)), ("ids", ConstructorParameterDescription(ids)), ("sources", ConstructorParameterDescription(sources)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupParticipants? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupParticipants? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupParticipants + } + return result + }) + } +} +public extension Api.functions.phone { + static func inviteConferenceCallParticipant(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1124981115) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "phone.inviteConferenceCallParticipant", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func inviteToGroupCall(call: Api.InputGroupCall, users: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2067345760) + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.inviteToGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("users", ConstructorParameterDescription(users))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, publicKey: Int256?, block: Buffer?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1883951017) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + joinAs.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(inviteHash!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt256(publicKey!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeBytes(block!, buffer: buffer, boxed: false) + } + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("joinAs", ConstructorParameterDescription(joinAs)), ("inviteHash", ConstructorParameterDescription(inviteHash)), ("publicKey", ConstructorParameterDescription(publicKey)), ("block", ConstructorParameterDescription(block)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func joinGroupCallPresentation(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-873829436) + call.serialize(buffer, true) + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCallPresentation", parameters: [("call", ConstructorParameterDescription(call)), ("params", ConstructorParameterDescription(params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func leaveGroupCall(call: Api.InputGroupCall, source: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1342404601) + call.serialize(buffer, true) + serializeInt32(source, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", ConstructorParameterDescription(call)), ("source", ConstructorParameterDescription(source))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func leaveGroupCallPresentation(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(475058500) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.leaveGroupCallPresentation", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func receivedCall(peer: Api.InputPhoneCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(399855457) + peer.serialize(buffer, true) + return (FunctionDescription(name: "phone.receivedCall", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1124046573) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + serializeBytes(gAHash, buffer: buffer, boxed: false) + `protocol`.serialize(buffer, true) + return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("randomId", ConstructorParameterDescription(randomId)), ("gAHash", ConstructorParameterDescription(gAHash)), ("`protocol`", ConstructorParameterDescription(`protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + let reader = BufferReader(buffer) + var result: Api.phone.PhoneCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.PhoneCall + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveCallDebug(peer: Api.InputPhoneCall, debug: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(662363518) + peer.serialize(buffer, true) + debug.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveCallDebug", parameters: [("peer", ConstructorParameterDescription(peer)), ("debug", ConstructorParameterDescription(debug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveCallLog(peer: Api.InputPhoneCall, file: Api.InputFile) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1092913030) + peer.serialize(buffer, true) + file.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveCallLog", parameters: [("peer", ConstructorParameterDescription(peer)), ("file", ConstructorParameterDescription(file))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveDefaultGroupCallJoinAs(peer: Api.InputPeer, joinAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1465786252) + peer.serialize(buffer, true) + joinAs.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveDefaultGroupCallJoinAs", parameters: [("peer", ConstructorParameterDescription(peer)), ("joinAs", ConstructorParameterDescription(joinAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func saveDefaultSendAs(call: Api.InputGroupCall, sendAs: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1097313745) + call.serialize(buffer, true) + sendAs.serialize(buffer, true) + return (FunctionDescription(name: "phone.saveDefaultSendAs", parameters: [("call", ConstructorParameterDescription(call)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func sendConferenceCallBroadcast(call: Api.InputGroupCall, block: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-965732096) + call.serialize(buffer, true) + serializeBytes(block, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendConferenceCallBroadcast", parameters: [("call", ConstructorParameterDescription(call)), ("block", ConstructorParameterDescription(block))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func sendGroupCallEncryptedMessage(call: Api.InputGroupCall, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-441473683) + call.serialize(buffer, true) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendGroupCallEncryptedMessage", parameters: [("call", ConstructorParameterDescription(call)), ("encryptedMessage", ConstructorParameterDescription(encryptedMessage))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func sendGroupCallMessage(flags: Int32, call: Api.InputGroupCall, randomId: Int64, message: Api.TextWithEntities, allowPaidStars: Int64?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1311697904) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + serializeInt64(randomId, buffer: buffer, boxed: false) + message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(allowPaidStars!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + sendAs!.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("randomId", ConstructorParameterDescription(randomId)), ("message", ConstructorParameterDescription(message)), ("allowPaidStars", ConstructorParameterDescription(allowPaidStars)), ("sendAs", ConstructorParameterDescription(sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-8744061) + peer.serialize(buffer, true) + serializeBytes(data, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendSignalingData", parameters: [("peer", ConstructorParameterDescription(peer)), ("data", ConstructorParameterDescription(data))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.phone { + static func setCallRating(flags: Int32, peer: Api.InputPhoneCall, rating: Int32, comment: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1508562471) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(rating, buffer: buffer, boxed: false) + serializeString(comment, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.setCallRating", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("rating", ConstructorParameterDescription(rating)), ("comment", ConstructorParameterDescription(comment))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func startScheduledGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1451287362) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.startScheduledGroupCall", parameters: [("call", ConstructorParameterDescription(call))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func toggleGroupCallRecord(flags: Int32, call: Api.InputGroupCall, title: String?, videoPortrait: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-248985848) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + videoPortrait!.serialize(buffer, true) + } + return (FunctionDescription(name: "phone.toggleGroupCallRecord", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("title", ConstructorParameterDescription(title)), ("videoPortrait", ConstructorParameterDescription(videoPortrait))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func toggleGroupCallSettings(flags: Int32, call: Api.InputGroupCall, joinMuted: Api.Bool?, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1757179150) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + joinMuted!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + messagesEnabled!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "phone.toggleGroupCallSettings", parameters: [("flags", ConstructorParameterDescription(flags)), ("call", ConstructorParameterDescription(call)), ("joinMuted", ConstructorParameterDescription(joinMuted)), ("messagesEnabled", ConstructorParameterDescription(messagesEnabled)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.phone { + static func toggleGroupCallStartSubscription(call: Api.InputGroupCall, subscribed: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(563885286) + call.serialize(buffer, true) + subscribed.serialize(buffer, true) + return (FunctionDescription(name: "phone.toggleGroupCallStartSubscription", parameters: [("call", ConstructorParameterDescription(call)), ("subscribed", ConstructorParameterDescription(subscribed))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.photos { + static func deletePhotos(id: [Api.InputPhoto]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int64]>) { + let buffer = Buffer() + buffer.appendInt32(-2016444625) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "photos.deletePhotos", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int64]? in + let reader = BufferReader(buffer) + var result: [Int64]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + return result + }) + } +} +public extension Api.functions.photos { + static func getUserPhotos(userId: Api.InputUser, offset: Int32, maxId: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1848823128) + userId.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt64(maxId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "photos.getUserPhotos", parameters: [("userId", ConstructorParameterDescription(userId)), ("offset", ConstructorParameterDescription(offset)), ("maxId", ConstructorParameterDescription(maxId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photos? in + let reader = BufferReader(buffer) + var result: Api.photos.Photos? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photos + } + return result + }) + } +} +public extension Api.functions.photos { + static func updateProfilePhoto(flags: Int32, bot: Api.InputUser?, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(166207545) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + bot!.serialize(buffer, true) + } + id.serialize(buffer, true) + return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.photos { + static func uploadContactProfilePhoto(flags: Int32, userId: Api.InputUser, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-515093903) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + file!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + video!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeDouble(videoStartTs!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 5) != 0 { + videoEmojiMarkup!.serialize(buffer, true) + } + return (FunctionDescription(name: "photos.uploadContactProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("file", ConstructorParameterDescription(file)), ("video", ConstructorParameterDescription(video)), ("videoStartTs", ConstructorParameterDescription(videoStartTs)), ("videoEmojiMarkup", ConstructorParameterDescription(videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.photos { + static func uploadProfilePhoto(flags: Int32, bot: Api.InputUser?, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(59286453) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 { + bot!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 0) != 0 { + file!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + video!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeDouble(videoStartTs!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 4) != 0 { + videoEmojiMarkup!.serialize(buffer, true) + } + return (FunctionDescription(name: "photos.uploadProfilePhoto", parameters: [("flags", ConstructorParameterDescription(flags)), ("bot", ConstructorParameterDescription(bot)), ("file", ConstructorParameterDescription(file)), ("video", ConstructorParameterDescription(video)), ("videoStartTs", ConstructorParameterDescription(videoStartTs)), ("videoEmojiMarkup", ConstructorParameterDescription(videoEmojiMarkup))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in + let reader = BufferReader(buffer) + var result: Api.photos.Photo? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.photos.Photo + } + return result + }) + } +} +public extension Api.functions.premium { + static func applyBoost(flags: Int32, slots: [Int32]?, peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1803396934) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(slots!.count)) + for item in slots! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + peer.serialize(buffer, true) + return (FunctionDescription(name: "premium.applyBoost", parameters: [("flags", ConstructorParameterDescription(flags)), ("slots", ConstructorParameterDescription(slots)), ("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in + let reader = BufferReader(buffer) + var result: Api.premium.MyBoosts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts + } + return result + }) + } +} +public extension Api.functions.premium { + static func getBoostsList(flags: Int32, peer: Api.InputPeer, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1626764896) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "premium.getBoostsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList + } + return result + }) + } +} +public extension Api.functions.premium { + static func getBoostsStatus(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(70197089) + peer.serialize(buffer, true) + return (FunctionDescription(name: "premium.getBoostsStatus", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsStatus? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsStatus? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsStatus + } + return result + }) + } +} +public extension Api.functions.premium { + static func getMyBoosts() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(199719754) + return (FunctionDescription(name: "premium.getMyBoosts", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.MyBoosts? in + let reader = BufferReader(buffer) + var result: Api.premium.MyBoosts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.MyBoosts + } + return result + }) + } +} +public extension Api.functions.premium { + static func getUserBoosts(peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(965037343) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "premium.getUserBoosts", parameters: [("peer", ConstructorParameterDescription(peer)), ("userId", ConstructorParameterDescription(userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.premium.BoostsList? in + let reader = BufferReader(buffer) + var result: Api.premium.BoostsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.premium.BoostsList + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func finishJob(flags: Int32, jobId: String, error: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1327415076) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(jobId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(error!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "smsjobs.finishJob", parameters: [("flags", ConstructorParameterDescription(flags)), ("jobId", ConstructorParameterDescription(jobId)), ("error", ConstructorParameterDescription(error))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func getSmsJob(jobId: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2005766191) + serializeString(jobId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "smsjobs.getSmsJob", parameters: [("jobId", ConstructorParameterDescription(jobId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SmsJob? in + let reader = BufferReader(buffer) + var result: Api.SmsJob? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.SmsJob + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func getStatus() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(279353576) + return (FunctionDescription(name: "smsjobs.getStatus", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.Status? in + let reader = BufferReader(buffer) + var result: Api.smsjobs.Status? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.smsjobs.Status + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func isEligibleToJoin() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(249313744) + return (FunctionDescription(name: "smsjobs.isEligibleToJoin", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.smsjobs.EligibilityToJoin? in + let reader = BufferReader(buffer) + var result: Api.smsjobs.EligibilityToJoin? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.smsjobs.EligibilityToJoin + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func join() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1488007635) + return (FunctionDescription(name: "smsjobs.join", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func leave() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1734824589) + return (FunctionDescription(name: "smsjobs.leave", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.smsjobs { + static func updateSettings(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(155164863) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "smsjobs.updateSettings", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stats { + static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1421720550) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in + let reader = BufferReader(buffer) + var result: Api.stats.BroadcastStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMegagroupStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-589330937) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + return (FunctionDescription(name: "stats.getMegagroupStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MegagroupStats? in + let reader = BufferReader(buffer) + var result: Api.stats.MegagroupStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.MegagroupStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMessagePublicForwards(channel: Api.InputChannel, msgId: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1595212100) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessagePublicForwards", parameters: [("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in + let reader = BufferReader(buffer) + var result: Api.stats.PublicForwards? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards + } + return result + }) + } +} +public extension Api.functions.stats { + static func getMessageStats(flags: Int32, channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1226791947) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getMessageStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MessageStats? in + let reader = BufferReader(buffer) + var result: Api.stats.MessageStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.MessageStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getPollStats(flags: Int32, peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1031931288) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getPollStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("msgId", ConstructorParameterDescription(msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PollStats? in + let reader = BufferReader(buffer) + var result: Api.stats.PollStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.PollStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func getStoryPublicForwards(peer: Api.InputPeer, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1505526026) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getStoryPublicForwards", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.PublicForwards? in + let reader = BufferReader(buffer) + var result: Api.stats.PublicForwards? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.PublicForwards + } + return result + }) + } +} +public extension Api.functions.stats { + static func getStoryStats(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(927985472) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stats.getStoryStats", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.StoryStats? in + let reader = BufferReader(buffer) + var result: Api.stats.StoryStats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stats.StoryStats + } + return result + }) + } +} +public extension Api.functions.stats { + static func loadAsyncGraph(flags: Int32, token: String, x: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1646092192) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(token, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeInt64(x!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("flags", ConstructorParameterDescription(flags)), ("token", ConstructorParameterDescription(token)), ("x", ConstructorParameterDescription(x))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in + let reader = BufferReader(buffer) + var result: Api.StatsGraph? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StatsGraph + } + return result + }) + } +} +public extension Api.functions.stickers { + static func addStickerToSet(stickerset: Api.InputStickerSet, sticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2041315650) + stickerset.serialize(buffer, true) + sticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.addStickerToSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("sticker", ConstructorParameterDescription(sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func changeSticker(flags: Int32, sticker: Api.InputDocument, emoji: String?, maskCoords: Api.MaskCoords?, keywords: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-179077444) + serializeInt32(flags, buffer: buffer, boxed: false) + sticker.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(emoji!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + maskCoords!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeString(keywords!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stickers.changeSticker", parameters: [("flags", ConstructorParameterDescription(flags)), ("sticker", ConstructorParameterDescription(sticker)), ("emoji", ConstructorParameterDescription(emoji)), ("maskCoords", ConstructorParameterDescription(maskCoords)), ("keywords", ConstructorParameterDescription(keywords))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func changeStickerPosition(sticker: Api.InputDocument, position: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-4795190) + sticker.serialize(buffer, true) + serializeInt32(position, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.changeStickerPosition", parameters: [("sticker", ConstructorParameterDescription(sticker)), ("position", ConstructorParameterDescription(position))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func checkShortName(shortName: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(676017721) + serializeString(shortName, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.checkShortName", parameters: [("shortName", ConstructorParameterDescription(shortName))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stickers { + static func createStickerSet(flags: Int32, userId: Api.InputUser, title: String, shortName: String, thumb: Api.InputDocument?, stickers: [Api.InputStickerSetItem], software: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1876841625) + serializeInt32(flags, buffer: buffer, boxed: false) + userId.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + serializeString(shortName, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + thumb!.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeString(software!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stickers.createStickerSet", parameters: [("flags", ConstructorParameterDescription(flags)), ("userId", ConstructorParameterDescription(userId)), ("title", ConstructorParameterDescription(title)), ("shortName", ConstructorParameterDescription(shortName)), ("thumb", ConstructorParameterDescription(thumb)), ("stickers", ConstructorParameterDescription(stickers)), ("software", ConstructorParameterDescription(software))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func deleteStickerSet(stickerset: Api.InputStickerSet) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2022685804) + stickerset.serialize(buffer, true) + return (FunctionDescription(name: "stickers.deleteStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stickers { + static func removeStickerFromSet(sticker: Api.InputDocument) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-143257775) + sticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.removeStickerFromSet", parameters: [("sticker", ConstructorParameterDescription(sticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func renameStickerSet(stickerset: Api.InputStickerSet, title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(306912256) + stickerset.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.renameStickerSet", parameters: [("stickerset", ConstructorParameterDescription(stickerset)), ("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func replaceSticker(sticker: Api.InputDocument, newSticker: Api.InputStickerSetItem) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1184253338) + sticker.serialize(buffer, true) + newSticker.serialize(buffer, true) + return (FunctionDescription(name: "stickers.replaceSticker", parameters: [("sticker", ConstructorParameterDescription(sticker)), ("newSticker", ConstructorParameterDescription(newSticker))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func setStickerSetThumb(flags: Int32, stickerset: Api.InputStickerSet, thumb: Api.InputDocument?, thumbDocumentId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1486204014) + serializeInt32(flags, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + thumb!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeInt64(thumbDocumentId!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stickers.setStickerSetThumb", parameters: [("flags", ConstructorParameterDescription(flags)), ("stickerset", ConstructorParameterDescription(stickerset)), ("thumb", ConstructorParameterDescription(thumb)), ("thumbDocumentId", ConstructorParameterDescription(thumbDocumentId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.StickerSet? in + let reader = BufferReader(buffer) + var result: Api.messages.StickerSet? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.StickerSet + } + return result + }) + } +} +public extension Api.functions.stickers { + static func suggestShortName(title: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1303364867) + serializeString(title, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stickers.suggestShortName", parameters: [("title", ConstructorParameterDescription(title))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stickers.SuggestedShortName? in + let reader = BufferReader(buffer) + var result: Api.stickers.SuggestedShortName? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stickers.SuggestedShortName + } + return result + }) + } +} +public extension Api.functions.stories { + static func activateStealthMode(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1471926630) + serializeInt32(flags, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.activateStealthMode", parameters: [("flags", ConstructorParameterDescription(flags))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func canSendStory(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(820732912) + peer.serialize(buffer, true) + return (FunctionDescription(name: "stories.canSendStory", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.CanSendStoryCount? in + let reader = BufferReader(buffer) + var result: Api.stories.CanSendStoryCount? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.CanSendStoryCount + } + return result + }) + } +} +public extension Api.functions.stories { + static func createAlbum(peer: Api.InputPeer, title: String, stories: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1553754395) + peer.serialize(buffer, true) + serializeString(title, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stories.count)) + for item in stories { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.createAlbum", parameters: [("peer", ConstructorParameterDescription(peer)), ("title", ConstructorParameterDescription(title)), ("stories", ConstructorParameterDescription(stories))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StoryAlbum? in + let reader = BufferReader(buffer) + var result: Api.StoryAlbum? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StoryAlbum + } + return result + }) + } +} +public extension Api.functions.stories { + static func deleteAlbum(peer: Api.InputPeer, albumId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1925949744) + peer.serialize(buffer, true) + serializeInt32(albumId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.deleteAlbum", parameters: [("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func deleteStories(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1369842849) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.deleteStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func editStory(flags: Int32, peer: Api.InputPeer, id: Int32, media: Api.InputMedia?, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule]?, music: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(744728363) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(mediaAreas!.count)) + for item in mediaAreas! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(caption!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacyRules!.count)) + for item in privacyRules! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 4) != 0 { + music!.serialize(buffer, true) + } + return (FunctionDescription(name: "stories.editStory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("media", ConstructorParameterDescription(media)), ("mediaAreas", ConstructorParameterDescription(mediaAreas)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("music", ConstructorParameterDescription(music))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func exportStoryLink(peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2072899360) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.exportStoryLink", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ExportedStoryLink? in + let reader = BufferReader(buffer) + var result: Api.ExportedStoryLink? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ExportedStoryLink + } + return result + }) + } +} +public extension Api.functions.stories { + static func getAlbumStories(peer: Api.InputPeer, albumId: Int32, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1400869535) + peer.serialize(buffer, true) + serializeInt32(albumId, buffer: buffer, boxed: false) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getAlbumStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getAlbums(peer: Api.InputPeer, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(632548039) + peer.serialize(buffer, true) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getAlbums", parameters: [("peer", ConstructorParameterDescription(peer)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Albums? in + let reader = BufferReader(buffer) + var result: Api.stories.Albums? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Albums + } + return result + }) + } +} +public extension Api.functions.stories { + static func getAllReadPeerStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1688541191) + return (FunctionDescription(name: "stories.getAllReadPeerStories", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func getAllStories(flags: Int32, state: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-290400731) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(state!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.getAllStories", parameters: [("flags", ConstructorParameterDescription(flags)), ("state", ConstructorParameterDescription(state))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.AllStories? in + let reader = BufferReader(buffer) + var result: Api.stories.AllStories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.AllStories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getChatsToSend() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1519744160) + return (FunctionDescription(name: "stories.getChatsToSend", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.Chats? in + let reader = BufferReader(buffer) + var result: Api.messages.Chats? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.Chats + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPeerMaxIDs(id: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.RecentStory]>) { + let buffer = Buffer() + buffer.appendInt32(2018087280) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "stories.getPeerMaxIDs", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.RecentStory]? in + let reader = BufferReader(buffer) + var result: [Api.RecentStory]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.RecentStory.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPeerStories(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(743103056) + peer.serialize(buffer, true) + return (FunctionDescription(name: "stories.getPeerStories", parameters: [("peer", ConstructorParameterDescription(peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.PeerStories? in + let reader = BufferReader(buffer) + var result: Api.stories.PeerStories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.PeerStories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getPinnedStories(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1478600156) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getPinnedStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesArchive(peer: Api.InputPeer, offsetId: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1271586794) + peer.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoriesArchive", parameters: [("peer", ConstructorParameterDescription(peer)), ("offsetId", ConstructorParameterDescription(offsetId)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesByID(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1467271796) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.getStoriesByID", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.Stories? in + let reader = BufferReader(buffer) + var result: Api.stories.Stories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.Stories + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoriesViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(685862088) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryViews? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryViews + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoryReactionsList(flags: Int32, peer: Api.InputPeer, id: Int32, reaction: Api.Reaction?, offset: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1179482081) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(id, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + reaction!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(offset!, buffer: buffer, boxed: false) + } + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoryReactionsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("reaction", ConstructorParameterDescription(reaction)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryReactionsList? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryReactionsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryReactionsList + } + return result + }) + } +} +public extension Api.functions.stories { + static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2127707223) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 { + serializeString(q!, buffer: buffer, boxed: false) + } + serializeInt32(id, buffer: buffer, boxed: false) + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("q", ConstructorParameterDescription(q)), ("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in + let reader = BufferReader(buffer) + var result: Api.stories.StoryViewsList? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.StoryViewsList + } + return result + }) + } +} +public extension Api.functions.stories { + static func incrementStoryViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1308456197) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.incrementStoryViews", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func readStories(peer: Api.InputPeer, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1521034552) + peer.serialize(buffer, true) + serializeInt32(maxId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.readStories", parameters: [("peer", ConstructorParameterDescription(peer)), ("maxId", ConstructorParameterDescription(maxId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func reorderAlbums(peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-2060059687) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.reorderAlbums", parameters: [("peer", ConstructorParameterDescription(peer)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func report(peer: Api.InputPeer, id: [Int32], option: Buffer, message: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(433646405) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + serializeBytes(option, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.report", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("option", ConstructorParameterDescription(option)), ("message", ConstructorParameterDescription(message))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.ReportResult? in + let reader = BufferReader(buffer) + var result: Api.ReportResult? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.ReportResult + } + return result + }) + } +} +public extension Api.functions.stories { + static func searchPosts(flags: Int32, hashtag: String?, area: Api.MediaArea?, peer: Api.InputPeer?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-780072697) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(hashtag!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + area!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 2) != 0 { + peer!.serialize(buffer, true) + } + serializeString(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "stories.searchPosts", parameters: [("flags", ConstructorParameterDescription(flags)), ("hashtag", ConstructorParameterDescription(hashtag)), ("area", ConstructorParameterDescription(area)), ("peer", ConstructorParameterDescription(peer)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.FoundStories? in + let reader = BufferReader(buffer) + var result: Api.stories.FoundStories? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.stories.FoundStories + } + return result + }) + } +} +public extension Api.functions.stories { + static func sendReaction(flags: Int32, peer: Api.InputPeer, storyId: Int32, reaction: Api.Reaction) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2144810674) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(storyId, buffer: buffer, boxed: false) + reaction.serialize(buffer, true) + return (FunctionDescription(name: "stories.sendReaction", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("storyId", ConstructorParameterDescription(storyId)), ("reaction", ConstructorParameterDescription(reaction))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func sendStory(flags: Int32, peer: Api.InputPeer, media: Api.InputMedia, mediaAreas: [Api.MediaArea]?, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, period: Int32?, fwdFromId: Api.InputPeer?, fwdFromStory: Int32?, albums: [Int32]?, music: Api.InputDocument?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1885443944) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + media.serialize(buffer, true) + if Int(flags) & Int(1 << 5) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(mediaAreas!.count)) + for item in mediaAreas! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 0) != 0 { + serializeString(caption!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacyRules.count)) + for item in privacyRules { + item.serialize(buffer, true) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 3) != 0 { + serializeInt32(period!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 6) != 0 { + fwdFromId!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 6) != 0 { + serializeInt32(fwdFromStory!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 8) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(albums!.count)) + for item in albums! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(flags) & Int(1 << 9) != 0 { + music!.serialize(buffer, true) + } + return (FunctionDescription(name: "stories.sendStory", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("media", ConstructorParameterDescription(media)), ("mediaAreas", ConstructorParameterDescription(mediaAreas)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("randomId", ConstructorParameterDescription(randomId)), ("period", ConstructorParameterDescription(period)), ("fwdFromId", ConstructorParameterDescription(fwdFromId)), ("fwdFromStory", ConstructorParameterDescription(fwdFromStory)), ("albums", ConstructorParameterDescription(albums)), ("music", ConstructorParameterDescription(music))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func startLive(flags: Int32, peer: Api.InputPeer, caption: String?, entities: [Api.MessageEntity]?, privacyRules: [Api.InputPrivacyRule], randomId: Int64, messagesEnabled: Api.Bool?, sendPaidMessagesStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-798372642) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(caption!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(privacyRules.count)) + for item in privacyRules { + item.serialize(buffer, true) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 6) != 0 { + messagesEnabled!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + serializeInt64(sendPaidMessagesStars!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.startLive", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("caption", ConstructorParameterDescription(caption)), ("entities", ConstructorParameterDescription(entities)), ("privacyRules", ConstructorParameterDescription(privacyRules)), ("randomId", ConstructorParameterDescription(randomId)), ("messagesEnabled", ConstructorParameterDescription(messagesEnabled)), ("sendPaidMessagesStars", ConstructorParameterDescription(sendPaidMessagesStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} +public extension Api.functions.stories { + static func toggleAllStoriesHidden(hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2082822084) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.toggleAllStoriesHidden", parameters: [("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePeerStoriesHidden(peer: Api.InputPeer, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1123805756) + peer.serialize(buffer, true) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.togglePeerStoriesHidden", parameters: [("peer", ConstructorParameterDescription(peer)), ("hidden", ConstructorParameterDescription(hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePinned(peer: Api.InputPeer, id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { + let buffer = Buffer() + buffer.appendInt32(-1703566865) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + pinned.serialize(buffer, true) + return (FunctionDescription(name: "stories.togglePinned", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id)), ("pinned", ConstructorParameterDescription(pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Int32]? in + let reader = BufferReader(buffer) + var result: [Int32]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + return result + }) + } +} +public extension Api.functions.stories { + static func togglePinnedToTop(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(187268763) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "stories.togglePinnedToTop", parameters: [("peer", ConstructorParameterDescription(peer)), ("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.stories { + static func updateAlbum(flags: Int32, peer: Api.InputPeer, albumId: Int32, title: String?, deleteStories: [Int32]?, addStories: [Int32]?, order: [Int32]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1582455222) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(albumId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(deleteStories!.count)) + for item in deleteStories! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(flags) & Int(1 << 2) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(addStories!.count)) + for item in addStories! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + if Int(flags) & Int(1 << 3) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + serializeInt32(item, buffer: buffer, boxed: false) + } + } + return (FunctionDescription(name: "stories.updateAlbum", parameters: [("flags", ConstructorParameterDescription(flags)), ("peer", ConstructorParameterDescription(peer)), ("albumId", ConstructorParameterDescription(albumId)), ("title", ConstructorParameterDescription(title)), ("deleteStories", ConstructorParameterDescription(deleteStories)), ("addStories", ConstructorParameterDescription(addStories)), ("order", ConstructorParameterDescription(order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StoryAlbum? in + let reader = BufferReader(buffer) + var result: Api.StoryAlbum? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.StoryAlbum + } + return result + }) + } +} +public extension Api.functions.updates { + static func getChannelDifference(flags: Int32, channel: Api.InputChannel, filter: Api.ChannelMessagesFilter, pts: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(51854712) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(pts, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "updates.getChannelDifference", parameters: [("flags", ConstructorParameterDescription(flags)), ("channel", ConstructorParameterDescription(channel)), ("filter", ConstructorParameterDescription(filter)), ("pts", ConstructorParameterDescription(pts)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.ChannelDifference? in + let reader = BufferReader(buffer) + var result: Api.updates.ChannelDifference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.ChannelDifference + } + return result + }) + } +} +public extension Api.functions.updates { + static func getDifference(flags: Int32, pts: Int32, ptsLimit: Int32?, ptsTotalLimit: Int32?, date: Int32, qts: Int32, qtsLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(432207715) + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 { + serializeInt32(ptsLimit!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 { + serializeInt32(ptsTotalLimit!, buffer: buffer, boxed: false) + } + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(qts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 { + serializeInt32(qtsLimit!, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "updates.getDifference", parameters: [("flags", ConstructorParameterDescription(flags)), ("pts", ConstructorParameterDescription(pts)), ("ptsLimit", ConstructorParameterDescription(ptsLimit)), ("ptsTotalLimit", ConstructorParameterDescription(ptsTotalLimit)), ("date", ConstructorParameterDescription(date)), ("qts", ConstructorParameterDescription(qts)), ("qtsLimit", ConstructorParameterDescription(qtsLimit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.Difference? in + let reader = BufferReader(buffer) + var result: Api.updates.Difference? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.Difference + } + return result + }) + } +} +public extension Api.functions.updates { + static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-304838614) + return (FunctionDescription(name: "updates.getState", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.updates.State? in + let reader = BufferReader(buffer) + var result: Api.updates.State? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.updates.State + } + return result + }) + } +} +public extension Api.functions.upload { + static func getCdnFile(fileToken: Buffer, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(962554330) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeInt64(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getCdnFile", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.CdnFile? in + let reader = BufferReader(buffer) + var result: Api.upload.CdnFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.CdnFile + } + return result + }) + } +} +public extension Api.functions.upload { + static func getCdnFileHashes(fileToken: Buffer, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1847836879) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeInt64(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getCdnFileHashes", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func getFile(flags: Int32, location: Api.InputFileLocation, offset: Int64, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1101843010) + serializeInt32(flags, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt64(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getFile", parameters: [("flags", ConstructorParameterDescription(flags)), ("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.File? in + let reader = BufferReader(buffer) + var result: Api.upload.File? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.File + } + return result + }) + } +} +public extension Api.functions.upload { + static func getFileHashes(location: Api.InputFileLocation, offset: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1856595926) + location.serialize(buffer, true) + serializeInt64(offset, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getFileHashes", parameters: [("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func getWebFile(location: Api.InputWebFileLocation, offset: Int32, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(619086221) + location.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.getWebFile", parameters: [("location", ConstructorParameterDescription(location)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.upload.WebFile? in + let reader = BufferReader(buffer) + var result: Api.upload.WebFile? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.upload.WebFile + } + return result + }) + } +} +public extension Api.functions.upload { + static func reuploadCdnFile(fileToken: Buffer, requestToken: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.FileHash]>) { + let buffer = Buffer() + buffer.appendInt32(-1691921240) + serializeBytes(fileToken, buffer: buffer, boxed: false) + serializeBytes(requestToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.reuploadCdnFile", parameters: [("fileToken", ConstructorParameterDescription(fileToken)), ("requestToken", ConstructorParameterDescription(requestToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.FileHash]? in + let reader = BufferReader(buffer) + var result: [Api.FileHash]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.FileHash.self) + } + return result + }) + } +} +public extension Api.functions.upload { + static func saveBigFilePart(fileId: Int64, filePart: Int32, fileTotalParts: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-562337987) + serializeInt64(fileId, buffer: buffer, boxed: false) + serializeInt32(filePart, buffer: buffer, boxed: false) + serializeInt32(fileTotalParts, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.saveBigFilePart", parameters: [("fileId", ConstructorParameterDescription(fileId)), ("filePart", ConstructorParameterDescription(filePart)), ("fileTotalParts", ConstructorParameterDescription(fileTotalParts)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.upload { + static func saveFilePart(fileId: Int64, filePart: Int32, bytes: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1291540959) + serializeInt64(fileId, buffer: buffer, boxed: false) + serializeInt32(filePart, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + return (FunctionDescription(name: "upload.saveFilePart", parameters: [("fileId", ConstructorParameterDescription(fileId)), ("filePart", ConstructorParameterDescription(filePart)), ("bytes", ConstructorParameterDescription(bytes))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.users { + static func getFullUser(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1240508136) + id.serialize(buffer, true) + return (FunctionDescription(name: "users.getFullUser", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.UserFull? in + let reader = BufferReader(buffer) + var result: Api.users.UserFull? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.UserFull + } + return result + }) + } +} +public extension Api.functions.users { + static func getRequirementsToContact(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.RequirementToContact]>) { + let buffer = Buffer() + buffer.appendInt32(-660962397) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.getRequirementsToContact", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.RequirementToContact]? in + let reader = BufferReader(buffer) + var result: [Api.RequirementToContact]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.RequirementToContact.self) + } + return result + }) + } +} +public extension Api.functions.users { + static func getSavedMusic(id: Api.InputUser, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2022539235) + id.serialize(buffer, true) + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(limit, buffer: buffer, boxed: false) + serializeInt64(hash, buffer: buffer, boxed: false) + return (FunctionDescription(name: "users.getSavedMusic", parameters: [("id", ConstructorParameterDescription(id)), ("offset", ConstructorParameterDescription(offset)), ("limit", ConstructorParameterDescription(limit)), ("hash", ConstructorParameterDescription(hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.SavedMusic? in + let reader = BufferReader(buffer) + var result: Api.users.SavedMusic? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.SavedMusic + } + return result + }) + } +} +public extension Api.functions.users { + static func getSavedMusicByID(id: Api.InputUser, documents: [Api.InputDocument]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1970513129) + id.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(documents.count)) + for item in documents { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.getSavedMusicByID", parameters: [("id", ConstructorParameterDescription(id)), ("documents", ConstructorParameterDescription(documents))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.SavedMusic? in + let reader = BufferReader(buffer) + var result: Api.users.SavedMusic? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.users.SavedMusic + } + return result + }) + } +} +public extension Api.functions.users { + static func getUsers(id: [Api.InputUser]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.User]>) { + let buffer = Buffer() + buffer.appendInt32(227648840) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.getUsers", parameters: [("id", ConstructorParameterDescription(id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.User]? in + let reader = BufferReader(buffer) + var result: [Api.User]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + return result + }) + } +} +public extension Api.functions.users { + static func setSecureValueErrors(id: Api.InputUser, errors: [Api.SecureValueError]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1865902923) + id.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(errors.count)) + for item in errors { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "users.setSecureValueErrors", parameters: [("id", ConstructorParameterDescription(id)), ("errors", ConstructorParameterDescription(errors))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} +public extension Api.functions.users { + static func suggestBirthday(id: Api.InputUser, birthday: Api.Birthday) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-61656206) + id.serialize(buffer, true) + birthday.serialize(buffer, true) + return (FunctionDescription(name: "users.suggestBirthday", parameters: [("id", ConstructorParameterDescription(id)), ("birthday", ConstructorParameterDescription(birthday))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} diff --git a/submodules/TelegramApi/Sources/Api5.swift b/submodules/TelegramApi/Sources/Api5.swift index d5bc6b26a1..8d300cd9e7 100644 --- a/submodules/TelegramApi/Sources/Api5.swift +++ b/submodules/TelegramApi/Sources/Api5.swift @@ -53,18 +53,18 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _4 = parseString(reader) } var _5: Int64? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _5 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.ChatInviteImporter.chatInviteImporter(Cons_chatInviteImporter(flags: _1!, userId: _2!, date: _3!, about: _4, approvedBy: _5)) } @@ -231,14 +231,14 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.ChatParticipant.chatParticipant(Cons_chatParticipant(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, rank: _5)) } @@ -256,14 +256,14 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.ChatParticipant.chatParticipantAdmin(Cons_chatParticipantAdmin(flags: _1!, userId: _2!, inviterId: _3!, date: _4!, rank: _5)) } @@ -277,12 +277,12 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.ChatParticipant.chatParticipantCreator(Cons_chatParticipantCreator(flags: _1!, userId: _2!, rank: _3)) } @@ -384,14 +384,14 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: Api.ChatParticipant? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.ChatParticipant } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.ChatParticipants.chatParticipantsForbidden(Cons_chatParticipantsForbidden(flags: _1!, chatId: _2!, selfParticipant: _3)) } @@ -457,14 +457,14 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: Buffer? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _3 = parseBytes(reader) } var _4: Int32? _4 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.ChatPhoto.chatPhoto(Cons_chatPhoto(flags: _1!, photoId: _2!, strippedThumb: _3, dcId: _4!)) @@ -709,25 +709,25 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: [Buffer]? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: Buffer.self) } } var _3: String? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { _3 = parseString(reader) } var _4: Api.Bool? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Bool } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 8) == 0) || _4 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.CodeSettings.codeSettings(Cons_codeSettings(flags: _1!, logoutTokens: _2, token: _3, appSandbox: _4)) } @@ -974,7 +974,7 @@ public extension Api { var _24: Int32? _24 = reader.readInt32() var _25: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _25 = reader.readInt32() } var _26: Int32? @@ -988,23 +988,23 @@ public extension Api { var _30: String? _30 = parseString(reader) var _31: String? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _31 = parseString(reader) } var _32: String? - if Int(_1!) & Int(1 << 9) != 0 { + if Int(_1 ?? 0) & Int(1 << 9) != 0 { _32 = parseString(reader) } var _33: String? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { _33 = parseString(reader) } var _34: String? - if Int(_1!) & Int(1 << 11) != 0 { + if Int(_1 ?? 0) & Int(1 << 11) != 0 { _34 = parseString(reader) } var _35: String? - if Int(_1!) & Int(1 << 12) != 0 { + if Int(_1 ?? 0) & Int(1 << 12) != 0 { _35 = parseString(reader) } var _36: Int32? @@ -1014,25 +1014,25 @@ public extension Api { var _38: Int32? _38 = reader.readInt32() var _39: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _39 = parseString(reader) } var _40: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _40 = reader.readInt32() } var _41: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _41 = reader.readInt32() } var _42: Api.Reaction? - if Int(_1!) & Int(1 << 15) != 0 { + if Int(_1 ?? 0) & Int(1 << 15) != 0 { if let signature = reader.readInt32() { _42 = Api.parse(reader, signature: signature) as? Api.Reaction } } var _43: String? - if Int(_1!) & Int(1 << 16) != 0 { + if Int(_1 ?? 0) & Int(1 << 16) != 0 { _43 = parseString(reader) } let _c1 = _1 != nil @@ -1059,25 +1059,25 @@ public extension Api { let _c22 = _22 != nil let _c23 = _23 != nil let _c24 = _24 != nil - let _c25 = (Int(_1!) & Int(1 << 0) == 0) || _25 != nil + let _c25 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _25 != nil let _c26 = _26 != nil let _c27 = _27 != nil let _c28 = _28 != nil let _c29 = _29 != nil let _c30 = _30 != nil - let _c31 = (Int(_1!) & Int(1 << 7) == 0) || _31 != nil - let _c32 = (Int(_1!) & Int(1 << 9) == 0) || _32 != nil - let _c33 = (Int(_1!) & Int(1 << 10) == 0) || _33 != nil - let _c34 = (Int(_1!) & Int(1 << 11) == 0) || _34 != nil - let _c35 = (Int(_1!) & Int(1 << 12) == 0) || _35 != nil + let _c31 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _31 != nil + let _c32 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _32 != nil + let _c33 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _33 != nil + let _c34 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _34 != nil + let _c35 = (Int(_1 ?? 0) & Int(1 << 12) == 0) || _35 != nil let _c36 = _36 != nil let _c37 = _37 != nil let _c38 = _38 != nil - let _c39 = (Int(_1!) & Int(1 << 2) == 0) || _39 != nil - let _c40 = (Int(_1!) & Int(1 << 2) == 0) || _40 != nil - let _c41 = (Int(_1!) & Int(1 << 2) == 0) || _41 != nil - let _c42 = (Int(_1!) & Int(1 << 15) == 0) || _42 != nil - let _c43 = (Int(_1!) & Int(1 << 16) == 0) || _43 != nil + let _c39 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _39 != nil + let _c40 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _40 != nil + let _c41 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _41 != nil + let _c42 = (Int(_1 ?? 0) & Int(1 << 15) == 0) || _42 != nil + let _c43 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _43 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 { return Api.Config.config(Cons_config(flags: _1!, date: _2!, expires: _3!, testMode: _4!, thisDc: _5!, dcOptions: _6!, dcTxtDomainName: _7!, chatSizeMax: _8!, megagroupSizeMax: _9!, forwardedCountMax: _10!, onlineUpdatePeriodMs: _11!, offlineBlurTimeoutMs: _12!, offlineIdleTimeoutMs: _13!, onlineCloudTimeoutMs: _14!, notifyCloudDelayMs: _15!, notifyDefaultDelayMs: _16!, pushChatPeriodMs: _17!, pushChatLimit: _18!, editTimeLimit: _19!, revokeTimeLimit: _20!, revokePmTimeLimit: _21!, ratingEDecay: _22!, stickersRecentLimit: _23!, channelsReadMediaPeriod: _24!, tmpSessions: _25, callReceiveTimeoutMs: _26!, callRingTimeoutMs: _27!, callConnectTimeoutMs: _28!, callPacketTimeoutMs: _29!, meUrlPrefix: _30!, autoupdateUrlPrefix: _31, gifSearchUsername: _32, venueSearchUsername: _33, imgSearchUsername: _34, staticMapsProvider: _35, captionLengthMax: _36!, messageLengthMax: _37!, webfileDcId: _38!, suggestedLangCode: _39, langPackVersion: _40, baseLangPackVersion: _41, reactionsDefault: _42, autologinToken: _43)) } @@ -1094,14 +1094,20 @@ public extension Api { public var botId: Int64 public var recipients: Api.BusinessBotRecipients public var rights: Api.BusinessBotRights - public init(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients, rights: Api.BusinessBotRights) { + public var device: String? + public var date: Int32? + public var location: String? + public init(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients, rights: Api.BusinessBotRights, device: String?, date: Int32?, location: String?) { self.flags = flags self.botId = botId self.recipients = recipients self.rights = rights + self.device = device + self.date = date + self.location = location } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("connectedBot", [("flags", ConstructorParameterDescription(self.flags)), ("botId", ConstructorParameterDescription(self.botId)), ("recipients", ConstructorParameterDescription(self.recipients)), ("rights", ConstructorParameterDescription(self.rights))]) + return ("connectedBot", [("flags", ConstructorParameterDescription(self.flags)), ("botId", ConstructorParameterDescription(self.botId)), ("recipients", ConstructorParameterDescription(self.recipients)), ("rights", ConstructorParameterDescription(self.rights)), ("device", ConstructorParameterDescription(self.device)), ("date", ConstructorParameterDescription(self.date)), ("location", ConstructorParameterDescription(self.location))]) } } case connectedBot(Cons_connectedBot) @@ -1110,12 +1116,21 @@ public extension Api { switch self { case .connectedBot(let _data): if boxed { - buffer.appendInt32(-849058964) + buffer.appendInt32(54448129) } serializeInt32(_data.flags, buffer: buffer, boxed: false) serializeInt64(_data.botId, buffer: buffer, boxed: false) _data.recipients.serialize(buffer, true) _data.rights.serialize(buffer, true) + if Int(_data.flags) & Int(1 << 0) != 0 { + serializeString(_data.device!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 1) != 0 { + serializeInt32(_data.date!, buffer: buffer, boxed: false) + } + if Int(_data.flags) & Int(1 << 2) != 0 { + serializeString(_data.location!, buffer: buffer, boxed: false) + } break } } @@ -1123,7 +1138,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { case .connectedBot(let _data): - return ("connectedBot", [("flags", ConstructorParameterDescription(_data.flags)), ("botId", ConstructorParameterDescription(_data.botId)), ("recipients", ConstructorParameterDescription(_data.recipients)), ("rights", ConstructorParameterDescription(_data.rights))]) + return ("connectedBot", [("flags", ConstructorParameterDescription(_data.flags)), ("botId", ConstructorParameterDescription(_data.botId)), ("recipients", ConstructorParameterDescription(_data.recipients)), ("rights", ConstructorParameterDescription(_data.rights)), ("device", ConstructorParameterDescription(_data.device)), ("date", ConstructorParameterDescription(_data.date)), ("location", ConstructorParameterDescription(_data.location))]) } } @@ -1140,12 +1155,27 @@ public extension Api { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights } + var _5: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _5 = parseString(reader) + } + var _6: Int32? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _6 = reader.readInt32() + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _7 = parseString(reader) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.ConnectedBot.connectedBot(Cons_connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!)) + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.ConnectedBot.connectedBot(Cons_connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!, device: _5, date: _6, location: _7)) } else { return nil @@ -1219,7 +1249,7 @@ public extension Api { var _5: Int32? _5 = reader.readInt32() var _6: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _6 = reader.readInt32() } var _7: Int64? @@ -1231,7 +1261,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { @@ -1498,14 +1528,14 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: Buffer? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { _5 = parseBytes(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 10) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.DcOption.dcOption(Cons_dcOption(flags: _1!, id: _2!, ipAddress: _3!, port: _4!, secret: _5)) } @@ -1702,21 +1732,21 @@ public extension Api { _10 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } var _11: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _11 = reader.readInt32() } var _12: Api.DraftMessage? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _12 = Api.parse(reader, signature: signature) as? Api.DraftMessage } } var _13: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _13 = reader.readInt32() } var _14: Int32? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _14 = reader.readInt32() } let _c1 = _1 != nil @@ -1729,10 +1759,10 @@ public extension Api { let _c8 = _8 != nil let _c9 = _9 != nil let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 0) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 1) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 5) == 0) || _14 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _14 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { return Api.Dialog.dialog(Cons_dialog(flags: _1!, peer: _2!, topMessage: _3!, readInboxMaxId: _4!, readOutboxMaxId: _5!, unreadCount: _6!, unreadMentionsCount: _7!, unreadReactionsCount: _8!, unreadPollVotesCount: _9!, notifySettings: _10!, pts: _11, draft: _12, folderId: _13, ttlPeriod: _14)) } @@ -1912,11 +1942,11 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } var _4: String? - if Int(_1!) & Int(1 << 25) != 0 { + if Int(_1 ?? 0) & Int(1 << 25) != 0 { _4 = parseString(reader) } var _5: Int32? - if Int(_1!) & Int(1 << 27) != 0 { + if Int(_1 ?? 0) & Int(1 << 27) != 0 { _5 = reader.readInt32() } var _6: [Api.InputPeer]? @@ -1934,8 +1964,8 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 27) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil @@ -1956,11 +1986,11 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } var _4: String? - if Int(_1!) & Int(1 << 25) != 0 { + if Int(_1 ?? 0) & Int(1 << 25) != 0 { _4 = parseString(reader) } var _5: Int32? - if Int(_1!) & Int(1 << 27) != 0 { + if Int(_1 ?? 0) & Int(1 << 27) != 0 { _5 = reader.readInt32() } var _6: [Api.InputPeer]? @@ -1974,8 +2004,8 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 25) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 27) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { diff --git a/submodules/TelegramApi/Sources/Api6.swift b/submodules/TelegramApi/Sources/Api6.swift index 355cd7863e..0192271ffb 100644 --- a/submodules/TelegramApi/Sources/Api6.swift +++ b/submodules/TelegramApi/Sources/Api6.swift @@ -153,13 +153,13 @@ public extension Api { var _7: Int64? _7 = reader.readInt64() var _8: [Api.PhotoSize]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) } } var _9: [Api.VideoSize]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.VideoSize.self) } @@ -177,8 +177,8 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { @@ -409,22 +409,22 @@ public extension Api { var _2: Int32? _2 = reader.readInt32() var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = parseString(reader) } var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: Buffer? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseBytes(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.DocumentAttribute.documentAttributeAudio(Cons_documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5)) } @@ -489,7 +489,7 @@ public extension Api { _3 = Api.parse(reader, signature: signature) as? Api.InputStickerSet } var _4: Api.MaskCoords? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.MaskCoords } @@ -497,7 +497,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.DocumentAttribute.documentAttributeSticker(Cons_documentAttributeSticker(flags: _1!, alt: _2!, stickerset: _3!, maskCoords: _4)) } @@ -515,24 +515,24 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = reader.readInt32() } var _6: Double? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _6 = reader.readDouble() } var _7: String? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _7 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { return Api.DocumentAttribute.documentAttributeVideo(Cons_documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!, preloadPrefixSize: _5, videoStartTs: _6, videoCodec: _7)) } @@ -553,7 +553,8 @@ public extension Api { public var date: Int32 public var effect: Int64? public var suggestedPost: Api.SuggestedPost? - public init(flags: Int32, replyTo: Api.InputReplyTo?, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?, date: Int32, effect: Int64?, suggestedPost: Api.SuggestedPost?) { + public var richMessage: Api.RichMessage? + public init(flags: Int32, replyTo: Api.InputReplyTo?, message: String, entities: [Api.MessageEntity]?, media: Api.InputMedia?, date: Int32, effect: Int64?, suggestedPost: Api.SuggestedPost?, richMessage: Api.RichMessage?) { self.flags = flags self.replyTo = replyTo self.message = message @@ -562,9 +563,10 @@ public extension Api { self.date = date self.effect = effect self.suggestedPost = suggestedPost + self.richMessage = richMessage } public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("draftMessage", [("flags", ConstructorParameterDescription(self.flags)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("media", ConstructorParameterDescription(self.media)), ("date", ConstructorParameterDescription(self.date)), ("effect", ConstructorParameterDescription(self.effect)), ("suggestedPost", ConstructorParameterDescription(self.suggestedPost))]) + return ("draftMessage", [("flags", ConstructorParameterDescription(self.flags)), ("replyTo", ConstructorParameterDescription(self.replyTo)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("media", ConstructorParameterDescription(self.media)), ("date", ConstructorParameterDescription(self.date)), ("effect", ConstructorParameterDescription(self.effect)), ("suggestedPost", ConstructorParameterDescription(self.suggestedPost)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) } } public class Cons_draftMessageEmpty: TypeConstructorDescription { @@ -585,7 +587,7 @@ public extension Api { switch self { case .draftMessage(let _data): if boxed { - buffer.appendInt32(-1763006997) + buffer.appendInt32(1627271828) } serializeInt32(_data.flags, buffer: buffer, boxed: false) if Int(_data.flags) & Int(1 << 4) != 0 { @@ -609,6 +611,9 @@ public extension Api { if Int(_data.flags) & Int(1 << 8) != 0 { _data.suggestedPost!.serialize(buffer, true) } + if Int(_data.flags) & Int(1 << 9) != 0 { + _data.richMessage!.serialize(buffer, true) + } break case .draftMessageEmpty(let _data): if boxed { @@ -625,7 +630,7 @@ public extension Api { public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { switch self { case .draftMessage(let _data): - return ("draftMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("media", ConstructorParameterDescription(_data.media)), ("date", ConstructorParameterDescription(_data.date)), ("effect", ConstructorParameterDescription(_data.effect)), ("suggestedPost", ConstructorParameterDescription(_data.suggestedPost))]) + return ("draftMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("replyTo", ConstructorParameterDescription(_data.replyTo)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("media", ConstructorParameterDescription(_data.media)), ("date", ConstructorParameterDescription(_data.date)), ("effect", ConstructorParameterDescription(_data.effect)), ("suggestedPost", ConstructorParameterDescription(_data.suggestedPost)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) case .draftMessageEmpty(let _data): return ("draftMessageEmpty", [("flags", ConstructorParameterDescription(_data.flags)), ("date", ConstructorParameterDescription(_data.date))]) } @@ -635,7 +640,7 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.InputReplyTo? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.InputReplyTo } @@ -643,13 +648,13 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _5: Api.InputMedia? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.InputMedia } @@ -657,25 +662,32 @@ public extension Api { var _6: Int32? _6 = reader.readInt32() var _7: Int64? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _7 = reader.readInt64() } var _8: Api.SuggestedPost? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.SuggestedPost } } + var _9: Api.RichMessage? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.RichMessage + } + } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.DraftMessage.draftMessage(Cons_draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, media: _5, date: _6!, effect: _7, suggestedPost: _8)) + let _c7 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.DraftMessage.draftMessage(Cons_draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, media: _5, date: _6!, effect: _7, suggestedPost: _8, richMessage: _9)) } else { return nil @@ -685,11 +697,11 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _2 = reader.readInt32() } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil if _c1 && _c2 { return Api.DraftMessage.draftMessageEmpty(Cons_draftMessageEmpty(flags: _1!, date: _2)) } @@ -1422,12 +1434,12 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.EmojiStatus.emojiStatus(Cons_emojiStatus(flags: _1!, documentId: _2!, until: _3)) } @@ -1457,7 +1469,7 @@ public extension Api { var _10: Int32? _10 = reader.readInt32() var _11: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _11 = reader.readInt32() } let _c1 = _1 != nil @@ -1470,7 +1482,7 @@ public extension Api { let _c8 = _8 != nil let _c9 = _9 != nil let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 0) == 0) || _11 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _11 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { return Api.EmojiStatus.emojiStatusCollectible(Cons_emojiStatusCollectible(flags: _1!, collectibleId: _2!, documentId: _3!, title: _4!, slug: _5!, patternDocumentId: _6!, centerColor: _7!, edgeColor: _8!, patternColor: _9!, textColor: _10!, until: _11)) } @@ -1487,12 +1499,12 @@ public extension Api { var _2: Int64? _2 = reader.readInt64() var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.EmojiStatus.inputEmojiStatusCollectible(Cons_inputEmojiStatusCollectible(flags: _1!, collectibleId: _2!, until: _3)) } diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 035ec1c502..38afddaed2 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -259,7 +259,7 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _2 = reader.readInt32() } var _3: Int32? @@ -275,7 +275,7 @@ public extension Api { var _8: Buffer? _8 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil @@ -608,35 +608,35 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _5 = reader.readInt32() } var _6: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _6 = reader.readInt32() } var _7: Int32? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _7 = reader.readInt32() } var _8: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _8 = reader.readInt32() } var _9: Int32? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _9 = reader.readInt32() } var _10: Int32? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { _10 = reader.readInt32() } var _11: String? - if Int(_1!) & Int(1 << 8) != 0 { + if Int(_1 ?? 0) & Int(1 << 8) != 0 { _11 = parseString(reader) } var _12: Api.StarsSubscriptionPricing? - if Int(_1!) & Int(1 << 9) != 0 { + if Int(_1 ?? 0) & Int(1 << 9) != 0 { if let signature = reader.readInt32() { _12 = Api.parse(reader, signature: signature) as? Api.StarsSubscriptionPricing } @@ -645,14 +645,14 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 9) == 0) || _12 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 8) == 0) || _11 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _12 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { return Api.ExportedChatInvite.chatInviteExported(Cons_chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, requested: _9, subscriptionExpired: _10, title: _11, subscriptionPricing: _12)) } @@ -925,11 +925,11 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _2 = parseString(reader) } var _3: Api.TextWithEntities? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } @@ -937,8 +937,8 @@ public extension Api { var _4: Int64? _4 = reader.readInt64() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.FactCheck.factCheck(Cons_factCheck(flags: _1!, country: _2, text: _3, hash: _4!)) @@ -1055,7 +1055,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.ChatPhoto? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ChatPhoto } @@ -1063,7 +1063,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.Folder.folder(Cons_folder(flags: _1!, id: _2!, title: _3!, photo: _4)) } @@ -1242,7 +1242,7 @@ public extension Api { var _6: Int32? _6 = reader.readInt32() var _7: Int64? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _7 = reader.readInt64() } var _8: Int32? @@ -1268,7 +1268,7 @@ public extension Api { _16 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings } var _17: Api.DraftMessage? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _17 = Api.parse(reader, signature: signature) as? Api.DraftMessage } @@ -1279,7 +1279,7 @@ public extension Api { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _7 != nil let _c8 = _8 != nil let _c9 = _9 != nil let _c10 = _10 != nil @@ -1289,7 +1289,7 @@ public extension Api { let _c14 = _14 != nil let _c15 = _15 != nil let _c16 = _16 != nil - let _c17 = (Int(_1!) & Int(1 << 4) == 0) || _17 != nil + let _c17 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _17 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { return Api.ForumTopic.forumTopic(Cons_forumTopic(flags: _1!, id: _2!, date: _3!, peer: _4!, title: _5!, iconColor: _6!, iconEmojiId: _7, topMessage: _8!, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, unreadMentionsCount: _12!, unreadReactionsCount: _13!, unreadPollVotesCount: _14!, fromId: _15!, notifySettings: _16!, draft: _17)) } @@ -1436,7 +1436,7 @@ public extension Api { _7 = Api.parse(reader, signature: signature) as? Api.Photo } var _8: Api.Document? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.Document } @@ -1448,7 +1448,7 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.Game.game(Cons_game(flags: _1!, id: _2!, accessHash: _3!, shortName: _4!, title: _5!, description: _6!, photo: _7!, document: _8)) } @@ -1521,14 +1521,14 @@ public extension Api { var _4: Int64? _4 = reader.readInt64() var _5: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _5 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.GeoPoint.geoPoint(Cons_geoPoint(flags: _1!, long: _2!, lat: _3!, accessHash: _4!, accuracyRadius: _5)) } @@ -1596,22 +1596,22 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: String? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = parseString(reader) } var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.GeoPointAddress.geoPointAddress(Cons_geoPointAddress(flags: _1!, countryIso2: _2!, state: _3, city: _4, street: _5)) } @@ -1666,18 +1666,18 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Int64? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _2 = reader.readInt64() } var _3: Api.DisallowedGiftsSettings? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.DisallowedGiftsSettings } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 5) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.GlobalPrivacySettings.globalPrivacySettings(Cons_globalPrivacySettings(flags: _1!, noncontactPeersPaidStars: _2, disallowedGifts: _3)) } @@ -1807,23 +1807,23 @@ public extension Api { var _4: Int32? _4 = reader.readInt32() var _5: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = parseString(reader) } var _6: Int32? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { _6 = reader.readInt32() } var _7: Int32? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { _7 = reader.readInt32() } var _8: Int32? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _8 = reader.readInt32() } var _9: Int32? - if Int(_1!) & Int(1 << 10) != 0 { + if Int(_1 ?? 0) & Int(1 << 10) != 0 { _9 = reader.readInt32() } var _10: Int32? @@ -1831,15 +1831,15 @@ public extension Api { var _11: Int32? _11 = reader.readInt32() var _12: String? - if Int(_1!) & Int(1 << 16) != 0 { + if Int(_1 ?? 0) & Int(1 << 16) != 0 { _12 = parseString(reader) } var _13: Int64? - if Int(_1!) & Int(1 << 20) != 0 { + if Int(_1 ?? 0) & Int(1 << 20) != 0 { _13 = reader.readInt64() } var _14: Api.Peer? - if Int(_1!) & Int(1 << 21) != 0 { + if Int(_1 ?? 0) & Int(1 << 21) != 0 { if let signature = reader.readInt32() { _14 = Api.parse(reader, signature: signature) as? Api.Peer } @@ -1848,16 +1848,16 @@ public extension Api { let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 5) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 7) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 10) == 0) || _9 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 10) == 0) || _9 != nil let _c10 = _10 != nil let _c11 = _11 != nil - let _c12 = (Int(_1!) & Int(1 << 16) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 20) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 21) == 0) || _14 != nil + let _c12 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _12 != nil + let _c13 = (Int(_1 ?? 0) & Int(1 << 20) == 0) || _13 != nil + let _c14 = (Int(_1 ?? 0) & Int(1 << 21) == 0) || _14 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { return Api.GroupCall.groupCall(Cons_groupCall(flags: _1!, id: _2!, accessHash: _3!, participantsCount: _4!, title: _5, streamDcId: _6, recordStartDate: _7, scheduleDate: _8, unmutedVideoCount: _9, unmutedVideoLimit: _10!, version: _11!, inviteLink: _12, sendPaidMessagesStars: _13, defaultSendAs: _14)) } @@ -1927,7 +1927,7 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.Peer? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.Peer } @@ -1935,7 +1935,7 @@ public extension Api { var _3: Int64? _3 = reader.readInt64() let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 3) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _2 != nil let _c3 = _3 != nil if _c1 && _c2 && _c3 { return Api.GroupCallDonor.groupCallDonor(Cons_groupCallDonor(flags: _1!, peerId: _2, stars: _3!)) @@ -2010,7 +2010,7 @@ public extension Api { _5 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } var _6: Int64? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _6 = reader.readInt64() } let _c1 = _1 != nil @@ -2018,7 +2018,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.GroupCallMessage.groupCallMessage(Cons_groupCallMessage(flags: _1!, id: _2!, fromId: _3!, date: _4!, message: _5!, paidMessageStars: _6)) } diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 0a41db4fc6..bf3ed2bfc0 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -83,50 +83,50 @@ public extension Api { var _3: Int32? _3 = reader.readInt32() var _4: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _4 = reader.readInt32() } var _5: Int32? _5 = reader.readInt32() var _6: Int32? - if Int(_1!) & Int(1 << 7) != 0 { + if Int(_1 ?? 0) & Int(1 << 7) != 0 { _6 = reader.readInt32() } var _7: String? - if Int(_1!) & Int(1 << 11) != 0 { + if Int(_1 ?? 0) & Int(1 << 11) != 0 { _7 = parseString(reader) } var _8: Int64? - if Int(_1!) & Int(1 << 13) != 0 { + if Int(_1 ?? 0) & Int(1 << 13) != 0 { _8 = reader.readInt64() } var _9: Api.GroupCallParticipantVideo? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo } } var _10: Api.GroupCallParticipantVideo? - if Int(_1!) & Int(1 << 14) != 0 { + if Int(_1 ?? 0) & Int(1 << 14) != 0 { if let signature = reader.readInt32() { _10 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipantVideo } } var _11: Int64? - if Int(_1!) & Int(1 << 16) != 0 { + if Int(_1 ?? 0) & Int(1 << 16) != 0 { _11 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 14) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 16) == 0) || _11 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 13) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _9 != nil + let _c10 = (Int(_1 ?? 0) & Int(1 << 14) == 0) || _10 != nil + let _c11 = (Int(_1 ?? 0) & Int(1 << 16) == 0) || _11 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { return Api.GroupCallParticipant.groupCallParticipant(Cons_groupCallParticipant(flags: _1!, peer: _2!, date: _3!, activeDate: _4, source: _5!, volume: _6, about: _7, raiseHandRating: _8, video: _9, presentation: _10, paidStarsTotal: _11)) } @@ -192,13 +192,13 @@ public extension Api { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipantVideoSourceGroup.self) } var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.GroupCallParticipantVideo.groupCallParticipantVideo(Cons_groupCallParticipantVideo(flags: _1!, endpoint: _2!, sourceGroups: _3!, audioSource: _4)) } @@ -607,6 +607,114 @@ public extension Api { } } } +public extension Api { + enum InputAiComposeTone: TypeConstructorDescription { + public class Cons_inputAiComposeToneDefault: TypeConstructorDescription { + public var tone: String + public init(tone: String) { + self.tone = tone + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputAiComposeToneDefault", [("tone", ConstructorParameterDescription(self.tone))]) + } + } + public class Cons_inputAiComposeToneID: TypeConstructorDescription { + public var id: Int64 + public var accessHash: Int64 + public init(id: Int64, accessHash: Int64) { + self.id = id + self.accessHash = accessHash + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputAiComposeToneID", [("id", ConstructorParameterDescription(self.id)), ("accessHash", ConstructorParameterDescription(self.accessHash))]) + } + } + public class Cons_inputAiComposeToneSlug: TypeConstructorDescription { + public var slug: String + public init(slug: String) { + self.slug = slug + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputAiComposeToneSlug", [("slug", ConstructorParameterDescription(self.slug))]) + } + } + case inputAiComposeToneDefault(Cons_inputAiComposeToneDefault) + case inputAiComposeToneID(Cons_inputAiComposeToneID) + case inputAiComposeToneSlug(Cons_inputAiComposeToneSlug) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputAiComposeToneDefault(let _data): + if boxed { + buffer.appendInt32(535407039) + } + serializeString(_data.tone, buffer: buffer, boxed: false) + break + case .inputAiComposeToneID(let _data): + if boxed { + buffer.appendInt32(125026432) + } + serializeInt64(_data.id, buffer: buffer, boxed: false) + serializeInt64(_data.accessHash, buffer: buffer, boxed: false) + break + case .inputAiComposeToneSlug(let _data): + if boxed { + buffer.appendInt32(530584407) + } + serializeString(_data.slug, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputAiComposeToneDefault(let _data): + return ("inputAiComposeToneDefault", [("tone", ConstructorParameterDescription(_data.tone))]) + case .inputAiComposeToneID(let _data): + return ("inputAiComposeToneID", [("id", ConstructorParameterDescription(_data.id)), ("accessHash", ConstructorParameterDescription(_data.accessHash))]) + case .inputAiComposeToneSlug(let _data): + return ("inputAiComposeToneSlug", [("slug", ConstructorParameterDescription(_data.slug))]) + } + } + + public static func parse_inputAiComposeToneDefault(_ reader: BufferReader) -> InputAiComposeTone? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputAiComposeTone.inputAiComposeToneDefault(Cons_inputAiComposeToneDefault(tone: _1!)) + } + else { + return nil + } + } + public static func parse_inputAiComposeToneID(_ reader: BufferReader) -> InputAiComposeTone? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputAiComposeTone.inputAiComposeToneID(Cons_inputAiComposeToneID(id: _1!, accessHash: _2!)) + } + else { + return nil + } + } + public static func parse_inputAiComposeToneSlug(_ reader: BufferReader) -> InputAiComposeTone? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.InputAiComposeTone.inputAiComposeToneSlug(Cons_inputAiComposeToneSlug(slug: _1!)) + } + else { + return nil + } + } + } +} public extension Api { enum InputAppEvent: TypeConstructorDescription { public class Cons_inputAppEvent: TypeConstructorDescription { @@ -889,6 +997,19 @@ public extension Api { return ("inputBotInlineMessageMediaWebPage", [("flags", ConstructorParameterDescription(self.flags)), ("message", ConstructorParameterDescription(self.message)), ("entities", ConstructorParameterDescription(self.entities)), ("url", ConstructorParameterDescription(self.url)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup))]) } } + public class Cons_inputBotInlineMessageRichMessage: TypeConstructorDescription { + public var flags: Int32 + public var replyMarkup: Api.ReplyMarkup? + public var richMessage: Api.InputRichMessage + public init(flags: Int32, replyMarkup: Api.ReplyMarkup?, richMessage: Api.InputRichMessage) { + self.flags = flags + self.replyMarkup = replyMarkup + self.richMessage = richMessage + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputBotInlineMessageRichMessage", [("flags", ConstructorParameterDescription(self.flags)), ("replyMarkup", ConstructorParameterDescription(self.replyMarkup)), ("richMessage", ConstructorParameterDescription(self.richMessage))]) + } + } public class Cons_inputBotInlineMessageText: TypeConstructorDescription { public var flags: Int32 public var message: String @@ -911,6 +1032,7 @@ public extension Api { case inputBotInlineMessageMediaInvoice(Cons_inputBotInlineMessageMediaInvoice) case inputBotInlineMessageMediaVenue(Cons_inputBotInlineMessageMediaVenue) case inputBotInlineMessageMediaWebPage(Cons_inputBotInlineMessageMediaWebPage) + case inputBotInlineMessageRichMessage(Cons_inputBotInlineMessageRichMessage) case inputBotInlineMessageText(Cons_inputBotInlineMessageText) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -1024,6 +1146,16 @@ public extension Api { _data.replyMarkup!.serialize(buffer, true) } break + case .inputBotInlineMessageRichMessage(let _data): + if boxed { + buffer.appendInt32(-1271007892) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + if Int(_data.flags) & Int(1 << 2) != 0 { + _data.replyMarkup!.serialize(buffer, true) + } + _data.richMessage.serialize(buffer, true) + break case .inputBotInlineMessageText(let _data): if boxed { buffer.appendInt32(1036876423) @@ -1060,6 +1192,8 @@ public extension Api { return ("inputBotInlineMessageMediaVenue", [("flags", ConstructorParameterDescription(_data.flags)), ("geoPoint", ConstructorParameterDescription(_data.geoPoint)), ("title", ConstructorParameterDescription(_data.title)), ("address", ConstructorParameterDescription(_data.address)), ("provider", ConstructorParameterDescription(_data.provider)), ("venueId", ConstructorParameterDescription(_data.venueId)), ("venueType", ConstructorParameterDescription(_data.venueType)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) case .inputBotInlineMessageMediaWebPage(let _data): return ("inputBotInlineMessageMediaWebPage", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("url", ConstructorParameterDescription(_data.url)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) + case .inputBotInlineMessageRichMessage(let _data): + return ("inputBotInlineMessageRichMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup)), ("richMessage", ConstructorParameterDescription(_data.richMessage))]) case .inputBotInlineMessageText(let _data): return ("inputBotInlineMessageText", [("flags", ConstructorParameterDescription(_data.flags)), ("message", ConstructorParameterDescription(_data.message)), ("entities", ConstructorParameterDescription(_data.entities)), ("replyMarkup", ConstructorParameterDescription(_data.replyMarkup))]) } @@ -1069,13 +1203,13 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil if _c1 && _c2 { return Api.InputBotInlineMessage.inputBotInlineMessageGame(Cons_inputBotInlineMessageGame(flags: _1!, replyMarkup: _2)) } @@ -1089,21 +1223,21 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _4: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaAuto(Cons_inputBotInlineMessageMediaAuto(flags: _1!, message: _2!, entities: _3, replyMarkup: _4)) } @@ -1123,7 +1257,7 @@ public extension Api { var _5: String? _5 = parseString(reader) var _6: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -1133,7 +1267,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaContact(Cons_inputBotInlineMessageMediaContact(flags: _1!, phoneNumber: _2!, firstName: _3!, lastName: _4!, vcard: _5!, replyMarkup: _6)) } @@ -1149,29 +1283,29 @@ public extension Api { _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint } var _3: Int32? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { _3 = reader.readInt32() } var _4: Int32? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = reader.readInt32() } var _5: Int32? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _5 = reader.readInt32() } var _6: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(Cons_inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6)) } @@ -1187,7 +1321,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.InputWebDocument? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputWebDocument } @@ -1205,7 +1339,7 @@ public extension Api { _8 = Api.parse(reader, signature: signature) as? Api.DataJSON } var _9: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _9 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -1213,12 +1347,12 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaInvoice(Cons_inputBotInlineMessageMediaInvoice(flags: _1!, title: _2!, description: _3!, photo: _4, invoice: _5!, payload: _6!, provider: _7!, providerData: _8!, replyMarkup: _9)) } @@ -1244,7 +1378,7 @@ public extension Api { var _7: String? _7 = parseString(reader) var _8: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } @@ -1256,7 +1390,7 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _8 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaVenue(Cons_inputBotInlineMessageMediaVenue(flags: _1!, geoPoint: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!, venueType: _7!, replyMarkup: _8)) } @@ -1270,7 +1404,7 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } @@ -1278,16 +1412,16 @@ public extension Api { var _4: String? _4 = parseString(reader) var _5: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputBotInlineMessage.inputBotInlineMessageMediaWebPage(Cons_inputBotInlineMessageMediaWebPage(flags: _1!, message: _2!, entities: _3, url: _4!, replyMarkup: _5)) } @@ -1295,27 +1429,50 @@ public extension Api { return nil } } + public static func parse_inputBotInlineMessageRichMessage(_ reader: BufferReader) -> InputBotInlineMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.ReplyMarkup? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } + } + var _3: Api.InputRichMessage? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.InputRichMessage + } + let _c1 = _1 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputBotInlineMessage.inputBotInlineMessageRichMessage(Cons_inputBotInlineMessageRichMessage(flags: _1!, replyMarkup: _2, richMessage: _3!)) + } + else { + return nil + } + } public static func parse_inputBotInlineMessageText(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? _1 = reader.readInt32() var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _4: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputBotInlineMessage.inputBotInlineMessageText(Cons_inputBotInlineMessageText(flags: _1!, message: _2!, entities: _3, replyMarkup: _4)) } @@ -1592,25 +1749,25 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } var _6: String? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { _6 = parseString(reader) } var _7: Api.InputWebDocument? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.InputWebDocument } } var _8: Api.InputWebDocument? - if Int(_1!) & Int(1 << 5) != 0 { + if Int(_1 ?? 0) & Int(1 << 5) != 0 { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.InputWebDocument } @@ -1622,11 +1779,11 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 5) == 0) || _8 != nil let _c9 = _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { return Api.InputBotInlineResult.inputBotInlineResult(Cons_inputBotInlineResult(flags: _1!, id: _2!, type: _3!, title: _4, description: _5, url: _6, thumb: _7, content: _8, sendMessage: _9!)) @@ -1643,11 +1800,11 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } var _5: String? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _5 = parseString(reader) } var _6: Api.InputDocument? @@ -1661,8 +1818,8 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { @@ -1717,69 +1874,3 @@ public extension Api { } } } -public extension Api { - enum InputBusinessAwayMessage: TypeConstructorDescription { - public class Cons_inputBusinessAwayMessage: TypeConstructorDescription { - public var flags: Int32 - public var shortcutId: Int32 - public var schedule: Api.BusinessAwayMessageSchedule - public var recipients: Api.InputBusinessRecipients - public init(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients) { - self.flags = flags - self.shortcutId = shortcutId - self.schedule = schedule - self.recipients = recipients - } - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - return ("inputBusinessAwayMessage", [("flags", ConstructorParameterDescription(self.flags)), ("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("schedule", ConstructorParameterDescription(self.schedule)), ("recipients", ConstructorParameterDescription(self.recipients))]) - } - } - case inputBusinessAwayMessage(Cons_inputBusinessAwayMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputBusinessAwayMessage(let _data): - if boxed { - buffer.appendInt32(-2094959136) - } - serializeInt32(_data.flags, buffer: buffer, boxed: false) - serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) - _data.schedule.serialize(buffer, true) - _data.recipients.serialize(buffer, true) - break - } - } - - public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { - switch self { - case .inputBusinessAwayMessage(let _data): - return ("inputBusinessAwayMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("schedule", ConstructorParameterDescription(_data.schedule)), ("recipients", ConstructorParameterDescription(_data.recipients))]) - } - } - - public static func parse_inputBusinessAwayMessage(_ reader: BufferReader) -> InputBusinessAwayMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Api.BusinessAwayMessageSchedule? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule - } - var _4: Api.InputBusinessRecipients? - if let signature = reader.readInt32() { - _4 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(Cons_inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!)) - } - else { - return nil - } - } - } -} diff --git a/submodules/TelegramApi/Sources/Api9.swift b/submodules/TelegramApi/Sources/Api9.swift index c31a55774b..aacd42f393 100644 --- a/submodules/TelegramApi/Sources/Api9.swift +++ b/submodules/TelegramApi/Sources/Api9.swift @@ -1,3 +1,69 @@ +public extension Api { + enum InputBusinessAwayMessage: TypeConstructorDescription { + public class Cons_inputBusinessAwayMessage: TypeConstructorDescription { + public var flags: Int32 + public var shortcutId: Int32 + public var schedule: Api.BusinessAwayMessageSchedule + public var recipients: Api.InputBusinessRecipients + public init(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients) { + self.flags = flags + self.shortcutId = shortcutId + self.schedule = schedule + self.recipients = recipients + } + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + return ("inputBusinessAwayMessage", [("flags", ConstructorParameterDescription(self.flags)), ("shortcutId", ConstructorParameterDescription(self.shortcutId)), ("schedule", ConstructorParameterDescription(self.schedule)), ("recipients", ConstructorParameterDescription(self.recipients))]) + } + } + case inputBusinessAwayMessage(Cons_inputBusinessAwayMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputBusinessAwayMessage(let _data): + if boxed { + buffer.appendInt32(-2094959136) + } + serializeInt32(_data.flags, buffer: buffer, boxed: false) + serializeInt32(_data.shortcutId, buffer: buffer, boxed: false) + _data.schedule.serialize(buffer, true) + _data.recipients.serialize(buffer, true) + break + } + } + + public func descriptionFields() -> (String, [(String, ConstructorParameterDescription)]) { + switch self { + case .inputBusinessAwayMessage(let _data): + return ("inputBusinessAwayMessage", [("flags", ConstructorParameterDescription(_data.flags)), ("shortcutId", ConstructorParameterDescription(_data.shortcutId)), ("schedule", ConstructorParameterDescription(_data.schedule)), ("recipients", ConstructorParameterDescription(_data.recipients))]) + } + } + + public static func parse_inputBusinessAwayMessage(_ reader: BufferReader) -> InputBusinessAwayMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.BusinessAwayMessageSchedule? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule + } + var _4: Api.InputBusinessRecipients? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(Cons_inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!)) + } + else { + return nil + } + } + } +} public extension Api { enum InputBusinessBotRecipients: TypeConstructorDescription { public class Cons_inputBusinessBotRecipients: TypeConstructorDescription { @@ -51,20 +117,20 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: [Api.InputUser]? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) } } var _3: [Api.InputUser]? - if Int(_1!) & Int(1 << 6) != 0 { + if Int(_1 ?? 0) & Int(1 << 6) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 6) == 0) || _3 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 6) == 0) || _3 != nil if _c1 && _c2 && _c3 { return Api.InputBusinessBotRecipients.inputBusinessBotRecipients(Cons_inputBusinessBotRecipients(flags: _1!, users: _2, excludeUsers: _3)) } @@ -128,19 +194,19 @@ public extension Api { var _2: String? _2 = parseString(reader) var _3: [Api.MessageEntity]? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let _ = reader.readInt32() { _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self) } } var _4: String? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { _4 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputBusinessChatLink.inputBusinessChatLink(Cons_inputBusinessChatLink(flags: _1!, message: _2!, entities: _3, title: _4)) } @@ -258,7 +324,7 @@ public extension Api { var _3: String? _3 = parseString(reader) var _4: Api.InputDocument? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.InputDocument } @@ -266,7 +332,7 @@ public extension Api { let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _4 != nil if _c1 && _c2 && _c3 && _c4 { return Api.InputBusinessIntro.inputBusinessIntro(Cons_inputBusinessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4)) } @@ -320,13 +386,13 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: [Api.InputUser]? - if Int(_1!) & Int(1 << 4) != 0 { + if Int(_1 ?? 0) & Int(1 << 4) != 0 { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputUser.self) } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 4) == 0) || _2 != nil if _c1 && _c2 { return Api.InputBusinessRecipients.inputBusinessRecipients(Cons_inputBusinessRecipients(flags: _1!, users: _2)) } @@ -537,32 +603,32 @@ public extension Api { var _1: Int32? _1 = reader.readInt32() var _2: Api.InputFile? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _2 = Api.parse(reader, signature: signature) as? Api.InputFile } } var _3: Api.InputFile? - if Int(_1!) & Int(1 << 1) != 0 { + if Int(_1 ?? 0) & Int(1 << 1) != 0 { if let signature = reader.readInt32() { _3 = Api.parse(reader, signature: signature) as? Api.InputFile } } var _4: Double? - if Int(_1!) & Int(1 << 2) != 0 { + if Int(_1 ?? 0) & Int(1 << 2) != 0 { _4 = reader.readDouble() } var _5: Api.VideoSize? - if Int(_1!) & Int(1 << 3) != 0 { + if Int(_1 ?? 0) & Int(1 << 3) != 0 { if let signature = reader.readInt32() { _5 = Api.parse(reader, signature: signature) as? Api.VideoSize } } let _c1 = _1 != nil - let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil - let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c2 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { return Api.InputChatPhoto.inputChatUploadedPhoto(Cons_inputChatUploadedPhoto(flags: _1!, file: _2, video: _3, videoStartTs: _4, videoEmojiMarkup: _5)) } @@ -950,7 +1016,7 @@ public extension Api { var _5: String? _5 = parseString(reader) var _6: Api.TextWithEntities? - if Int(_1!) & Int(1 << 0) != 0 { + if Int(_1 ?? 0) & Int(1 << 0) != 0 { if let signature = reader.readInt32() { _6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities } @@ -960,7 +1026,7 @@ public extension Api { let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _6 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { return Api.InputContact.inputPhoneContact(Cons_inputPhoneContact(flags: _1!, clientId: _2!, phone: _3!, firstName: _4!, lastName: _5!, note: _6)) } diff --git a/submodules/TelegramApi/Sources/SecretApiLayer101.swift b/submodules/TelegramApi/Sources/SecretApiLayer101.swift index 4c870f3f02..1d9d2acc95 100644 --- a/submodules/TelegramApi/Sources/SecretApiLayer101.swift +++ b/submodules/TelegramApi/Sources/SecretApiLayer101.swift @@ -5,58 +5,73 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[570911930] = { return $0.readInt64() } dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } - dict[-1586283796] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } - dict[206520510] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } - dict[1700872964] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } - dict[-1967000459] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } - dict[1729750108] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } - dict[-217806717] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } - dict[1360072880] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } - dict[-204906213] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } - dict[1877046107] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } - dict[-586814357] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } - dict[-332526693] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } - dict[-1473258141] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } - dict[236446268] = { return SecretApi101.PhotoSize.parse_photoSizeEmpty($0) } - dict[2009052699] = { return SecretApi101.PhotoSize.parse_photoSize($0) } - dict[-374917894] = { return SecretApi101.PhotoSize.parse_photoCachedSize($0) } - dict[2086234950] = { return SecretApi101.FileLocation.parse_fileLocationUnavailable($0) } - dict[1406570614] = { return SecretApi101.FileLocation.parse_fileLocation($0) } - dict[467867529] = { return SecretApi101.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } - dict[1930838368] = { return SecretApi101.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-1132882121] = { return SecretApi101.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi101.Bool.parse_boolTrue($0) } dict[-1848883596] = { return SecretApi101.DecryptedMessage.parse_decryptedMessage($0) } - dict[1815593308] = { return SecretApi101.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[1930838368] = { return SecretApi101.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi101.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi101.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[2063502050] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } dict[297109817] = { return SecretApi101.DocumentAttribute.parse_documentAttributeAnimated($0) } - dict[358154344] = { return SecretApi101.DocumentAttribute.parse_documentAttributeFilename($0) } - dict[978674434] = { return SecretApi101.DocumentAttribute.parse_documentAttributeSticker($0) } dict[-1739392570] = { return SecretApi101.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi101.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi101.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi101.DocumentAttribute.parse_documentAttributeSticker($0) } dict[250621158] = { return SecretApi101.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[-2044933984] = { return SecretApi101.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[1406570614] = { return SecretApi101.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi101.FileLocation.parse_fileLocationUnavailable($0) } dict[-4838507] = { return SecretApi101.InputStickerSet.parse_inputStickerSetEmpty($0) } - dict[-1148011883] = { return SecretApi101.MessageEntity.parse_messageEntityUnknown($0) } - dict[-100378723] = { return SecretApi101.MessageEntity.parse_messageEntityMention($0) } - dict[1868782349] = { return SecretApi101.MessageEntity.parse_messageEntityHashtag($0) } - dict[1827637959] = { return SecretApi101.MessageEntity.parse_messageEntityBotCommand($0) } - dict[1859134776] = { return SecretApi101.MessageEntity.parse_messageEntityUrl($0) } - dict[1692693954] = { return SecretApi101.MessageEntity.parse_messageEntityEmail($0) } + dict[-2044933984] = { return SecretApi101.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[34469328] = { return SecretApi101.MessageEntity.parse_messageEntityBlockquote($0) } dict[-1117713463] = { return SecretApi101.MessageEntity.parse_messageEntityBold($0) } - dict[-2106619040] = { return SecretApi101.MessageEntity.parse_messageEntityItalic($0) } + dict[1827637959] = { return SecretApi101.MessageEntity.parse_messageEntityBotCommand($0) } dict[681706865] = { return SecretApi101.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi101.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi101.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi101.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi101.MessageEntity.parse_messageEntityMention($0) } dict[1938967520] = { return SecretApi101.MessageEntity.parse_messageEntityPre($0) } + dict[-1090087980] = { return SecretApi101.MessageEntity.parse_messageEntityStrike($0) } dict[1990644519] = { return SecretApi101.MessageEntity.parse_messageEntityTextUrl($0) } dict[-1672577397] = { return SecretApi101.MessageEntity.parse_messageEntityUnderline($0) } - dict[-1090087980] = { return SecretApi101.MessageEntity.parse_messageEntityStrike($0) } - dict[34469328] = { return SecretApi101.MessageEntity.parse_messageEntityBlockquote($0) } - dict[144661578] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } - dict[893913689] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } - dict[1485441687] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } - dict[1474341323] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } - dict[-90853155] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } - dict[-235238024] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } - dict[2063502050] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } - dict[-1760785394] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } - dict[-1978796689] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } - dict[-452652584] = { return SecretApi101.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[-1148011883] = { return SecretApi101.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi101.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi101.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi101.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi101.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi101.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi101.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi101.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi101.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1997373508] = { return SecretApi101.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1584933265] = { return SecretApi101.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi101.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi101.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi101.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi101.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1150187996] = { return SecretApi101.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-1845219337] = { return SecretApi101.SendMessageAction.parse_sendMessageUploadVideoAction($0) } return dict }() @@ -68,18 +83,18 @@ public struct SecretApi101 { } return nil } - - fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { - if let parser = parsers[signature] { - return parser(reader) - } - else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") - return nil - } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) } - - fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -105,226 +120,293 @@ public struct SecretApi101 { } return nil } - + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { - case let _1 as SecretApi101.DecryptedMessageAction: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.PhotoSize: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.FileLocation: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.DecryptedMessageLayer: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.DecryptedMessage: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.DocumentAttribute: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.InputStickerSet: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.MessageEntity: - _1.serialize(buffer, boxed) - case let _1 as SecretApi101.DecryptedMessageMedia: - _1.serialize(buffer, boxed) - default: + case let _1 as SecretApi101.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi101.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi101.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi101.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi101.DecryptedMessageMedia?, entities: [SecretApi101.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi101.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): + if boxed { + buffer.appendInt32(-1848883596) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 17) != 0 { + serializeInt64(groupedId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi101.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.DecryptedMessageMedia + } + } + var _6: [SecretApi101.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi101.parseVector(reader, elementSignature: 0, elementType: SecretApi101.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _9 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi101.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi101.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } } } public enum DecryptedMessageAction { - case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) - case decryptedMessageActionReadMessages(randomIds: [Int64]) - case decryptedMessageActionDeleteMessages(randomIds: [Int64]) - case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) - case decryptedMessageActionFlushHistory - case decryptedMessageActionNotifyLayer(layer: Int32) - case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) - case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) - case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory case decryptedMessageActionNoop - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageActionSetMessageTTL(let ttlSeconds): - if boxed { - buffer.appendInt32(-1586283796) - } - serializeInt32(ttlSeconds, buffer: buffer, boxed: false) - break - case .decryptedMessageActionReadMessages(let randomIds): - if boxed { - buffer.appendInt32(206520510) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionDeleteMessages(let randomIds): - if boxed { - buffer.appendInt32(1700872964) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionScreenshotMessages(let randomIds): - if boxed { - buffer.appendInt32(-1967000459) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionFlushHistory: - if boxed { - buffer.appendInt32(1729750108) - } - - break - case .decryptedMessageActionNotifyLayer(let layer): - if boxed { - buffer.appendInt32(-217806717) - } - serializeInt32(layer, buffer: buffer, boxed: false) - break - case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): - if boxed { - buffer.appendInt32(1360072880) - } - serializeInt32(startSeqNo, buffer: buffer, boxed: false) - serializeInt32(endSeqNo, buffer: buffer, boxed: false) - break - case .decryptedMessageActionRequestKey(let exchangeId, let gA): - if boxed { - buffer.appendInt32(-204906213) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): - if boxed { - buffer.appendInt32(1877046107) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAbortKey(let exchangeId): - if boxed { - buffer.appendInt32(-586814357) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - break - case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): - if boxed { - buffer.appendInt32(-332526693) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionNoop: - if boxed { - buffer.appendInt32(-1473258141) - } - - break - } - } - fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) - } - else { - return nil + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi101.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break } } - fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionFlushHistory - } - fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) } else { return nil @@ -347,17 +429,6 @@ public struct SecretApi101 { return nil } } - fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() @@ -372,196 +443,134 @@ public struct SecretApi101 { return nil } } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionFlushHistory + } fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { return SecretApi101.DecryptedMessageAction.decryptedMessageActionNoop } - - - } - - public enum PhotoSize { - case photoSizeEmpty(type: String) - case photoSize(type: String, location: SecretApi101.FileLocation, w: Int32, h: Int32, size: Int32) - case photoCachedSize(type: String, location: SecretApi101.FileLocation, w: Int32, h: Int32, bytes: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photoSizeEmpty(let type): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(type, buffer: buffer, boxed: false) - break - case .photoSize(let type, let location, let w, let h, let size): - if boxed { - buffer.appendInt32(2009052699) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - break - case .photoCachedSize(let type, let location, let w, let h, let bytes): - if boxed { - buffer.appendInt32(-374917894) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi101.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } - } - fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi101.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi101.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) - } - else { - return nil - } - } - fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi101.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi101.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) - } - else { - return nil - } - } - - - } - - public enum FileLocation { - case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) - case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .fileLocationUnavailable(let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(2086234950) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - case .fileLocation(let dcId, let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(1406570614) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi101.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) - } - else { - return nil - } - } - fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi101.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi101.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi101.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) } else { return nil } } - - } public enum DecryptedMessageLayer { case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi101.DecryptedMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): - if boxed { - buffer.appendInt32(467867529) - } - serializeBytes(randomBytes, buffer: buffer, boxed: false) - serializeInt32(layer, buffer: buffer, boxed: false) - serializeInt32(inSeqNo, buffer: buffer, boxed: false) - serializeInt32(outSeqNo, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - } - } + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { var _1: Buffer? _1 = parseBytes(reader) @@ -587,755 +596,156 @@ public struct SecretApi101 { return nil } } - - - } - - public enum DecryptedMessage { - case decryptedMessageService(randomId: Int64, action: SecretApi101.DecryptedMessageAction) - case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi101.DecryptedMessageMedia?, entities: [SecretApi101.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageService(let randomId, let action): - if boxed { - buffer.appendInt32(1930838368) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): - if boxed { - buffer.appendInt32(-1848883596) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt32(ttl, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 11) != 0 {serializeString(viaBotName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(replyToRandomId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} - break - } - } - fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: SecretApi101.DecryptedMessageAction? - if let signature = reader.readInt32() { - _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.DecryptedMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: SecretApi101.DecryptedMessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _5 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.DecryptedMessageMedia - } } - var _6: [SecretApi101.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _6 = SecretApi101.parseVector(reader, elementSignature: 0, elementType: SecretApi101.MessageEntity.self) - } } - var _7: String? - if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } - var _9: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_9 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 9) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 17) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi101.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) - } - else { - return nil - } - } - - - } - - public enum DocumentAttribute { - case documentAttributeImageSize(w: Int32, h: Int32) - case documentAttributeAnimated - case documentAttributeFilename(fileName: String) - case documentAttributeSticker(alt: String, stickerset: SecretApi101.InputStickerSet) - case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) - case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .documentAttributeImageSize(let w, let h): - if boxed { - buffer.appendInt32(1815593308) - } - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .documentAttributeAnimated: - if boxed { - buffer.appendInt32(297109817) - } - - break - case .documentAttributeFilename(let fileName): - if boxed { - buffer.appendInt32(358154344) - } - serializeString(fileName, buffer: buffer, boxed: false) - break - case .documentAttributeSticker(let alt, let stickerset): - if boxed { - buffer.appendInt32(978674434) - } - serializeString(alt, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - break - case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): - if boxed { - buffer.appendInt32(-1739392570) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(waveform!, buffer: buffer, boxed: false)} - break - case .documentAttributeVideo(let flags, let duration, let w, let h): - if boxed { - buffer.appendInt32(250621158) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { - return SecretApi101.DocumentAttribute.documentAttributeAnimated - } - fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi101.DocumentAttribute.documentAttributeFilename(fileName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi101.InputStickerSet? - if let signature = reader.readInt32() { - _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.InputStickerSet - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseBytes(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi101.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi101.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) - } - else { - return nil - } - } - - - } - - public enum InputStickerSet { - case inputStickerSetShortName(shortName: String) - case inputStickerSetEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - } - } - fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi101.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return SecretApi101.InputStickerSet.inputStickerSetEmpty - } - - - } - - public enum MessageEntity { - case messageEntityUnknown(offset: Int32, length: Int32) - case messageEntityMention(offset: Int32, length: Int32) - case messageEntityHashtag(offset: Int32, length: Int32) - case messageEntityBotCommand(offset: Int32, length: Int32) - case messageEntityUrl(offset: Int32, length: Int32) - case messageEntityEmail(offset: Int32, length: Int32) - case messageEntityBold(offset: Int32, length: Int32) - case messageEntityItalic(offset: Int32, length: Int32) - case messageEntityCode(offset: Int32, length: Int32) - case messageEntityPre(offset: Int32, length: Int32, language: String) - case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - case messageEntityUnderline(offset: Int32, length: Int32) - case messageEntityStrike(offset: Int32, length: Int32) - case messageEntityBlockquote(offset: Int32, length: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageEntityUnknown(let offset, let length): - if boxed { - buffer.appendInt32(-1148011883) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let offset, let length): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let offset, let length): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let offset, let length): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityUrl(let offset, let length): - if boxed { - buffer.appendInt32(1859134776) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let offset, let length): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let offset, let length): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let offset, let length): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let offset, let length): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let offset, let length, let language): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(language, buffer: buffer, boxed: false) - break - case .messageEntityTextUrl(let offset, let length, let url): - if boxed { - buffer.appendInt32(1990644519) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .messageEntityUnderline(let offset, let length): - if boxed { - buffer.appendInt32(-1672577397) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityStrike(let offset, let length): - if boxed { - buffer.appendInt32(-1090087980) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBlockquote(let offset, let length): - if boxed { - buffer.appendInt32(34469328) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityBold(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityCode(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi101.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi101.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) - } - else { - return nil - } - } - } public enum DecryptedMessageMedia { - case decryptedMessageMediaEmpty - case decryptedMessageMediaGeoPoint(lat: Double, long: Double) - case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi101.PhotoSize, dcId: Int32, attributes: [SecretApi101.DocumentAttribute]) - case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi101.DocumentAttribute], caption: String) - case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi101.PhotoSize, dcId: Int32, attributes: [SecretApi101.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaWebPage(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageMediaEmpty: - if boxed { - buffer.appendInt32(144661578) - } - - break - case .decryptedMessageMediaGeoPoint(let lat, let long): - if boxed { - buffer.appendInt32(893913689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): - if boxed { - buffer.appendInt32(1485441687) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): - if boxed { - buffer.appendInt32(1474341323) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): - if boxed { - buffer.appendInt32(-90853155) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - thumb.serialize(buffer, true) - serializeInt32(dcId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-235238024) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): - if boxed { - buffer.appendInt32(2063502050) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-1760785394) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): - if boxed { - buffer.appendInt32(-1978796689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaWebPage(let url): - if boxed { - buffer.appendInt32(-452652584) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { - return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(2063502050) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } } - fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) } else { return nil @@ -1361,29 +771,46 @@ public struct SecretApi101 { return nil } } - fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Buffer? - _5 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi101.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi101.parseVector(reader, elementSignature: 0, elementType: SecretApi101.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) } else { return nil } } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaEmpty + } fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Int64? _1 = reader.readInt64() @@ -1420,6 +847,20 @@ public struct SecretApi101 { return nil } } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Buffer? _1 = parseBytes(reader) @@ -1455,38 +896,27 @@ public struct SecretApi101 { return nil } } - fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) - var _8: [SecretApi101.DocumentAttribute]? - if let _ = reader.readInt32() { - _8 = SecretApi101.parseVector(reader, elementSignature: 0, elementType: SecretApi101.DocumentAttribute.self) - } - var _9: String? - _9 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) } else { return nil @@ -1533,32 +963,6 @@ public struct SecretApi101 { return nil } } - fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return SecretApi101.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: String? _1 = parseString(reader) @@ -1570,12 +974,806 @@ public struct SecretApi101 { return nil } } - - } - public struct functions { - + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi101.InputStickerSet) + case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let flags, let duration, let w, let h): + if boxed { + buffer.appendInt32(250621158) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi101.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi101.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi101.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi101.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi101.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi101.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi101.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi101.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi101.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBlockquote(offset: Int32, length: Int32) + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityStrike(offset: Int32, length: Int32) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnderline(offset: Int32, length: Int32) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBlockquote(let offset, let length): + if boxed { + buffer.appendInt32(34469328) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityStrike(let offset, let length): + if boxed { + buffer.appendInt32(-1090087980) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnderline(let offset, let length): + if boxed { + buffer.appendInt32(-1672577397) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi101.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi101.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi101.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi101.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi101.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi101.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi101.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi101.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi101.parse(reader, signature: signature) as? SecretApi101.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi101.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi101.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadRoundAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageUploadRoundAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi101.SendMessageAction.sendMessageUploadVideoAction + } } } diff --git a/submodules/TelegramApi/Sources/SecretApiLayer143.swift b/submodules/TelegramApi/Sources/SecretApiLayer143.swift new file mode 100644 index 0000000000..2cc35b0c6e --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer143.swift @@ -0,0 +1,1779 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi143.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi143.Bool.parse_boolTrue($0) } + dict[-1848883596] = { return SecretApi143.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi143.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi143.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi143.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[1790809986] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi143.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[297109817] = { return SecretApi143.DocumentAttribute.parse_documentAttributeAnimated($0) } + dict[-1739392570] = { return SecretApi143.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi143.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi143.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi143.DocumentAttribute.parse_documentAttributeSticker($0) } + dict[250621158] = { return SecretApi143.DocumentAttribute.parse_documentAttributeVideo($0) } + dict[1406570614] = { return SecretApi143.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi143.FileLocation.parse_fileLocationUnavailable($0) } + dict[-4838507] = { return SecretApi143.InputStickerSet.parse_inputStickerSetEmpty($0) } + dict[-2044933984] = { return SecretApi143.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[34469328] = { return SecretApi143.MessageEntity.parse_messageEntityBlockquote($0) } + dict[-1117713463] = { return SecretApi143.MessageEntity.parse_messageEntityBold($0) } + dict[1827637959] = { return SecretApi143.MessageEntity.parse_messageEntityBotCommand($0) } + dict[681706865] = { return SecretApi143.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi143.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi143.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi143.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi143.MessageEntity.parse_messageEntityMention($0) } + dict[1938967520] = { return SecretApi143.MessageEntity.parse_messageEntityPre($0) } + dict[-1090087980] = { return SecretApi143.MessageEntity.parse_messageEntityStrike($0) } + dict[1990644519] = { return SecretApi143.MessageEntity.parse_messageEntityTextUrl($0) } + dict[-1672577397] = { return SecretApi143.MessageEntity.parse_messageEntityUnderline($0) } + dict[-1148011883] = { return SecretApi143.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi143.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi143.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi143.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi143.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi143.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi143.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi143.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi143.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1997373508] = { return SecretApi143.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1584933265] = { return SecretApi143.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi143.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi143.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi143.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi143.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1150187996] = { return SecretApi143.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-1845219337] = { return SecretApi143.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi143 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi143.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi143.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi143.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi143.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi143.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi143.DecryptedMessageMedia?, entities: [SecretApi143.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi143.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): + if boxed { + buffer.appendInt32(-1848883596) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 17) != 0 { + serializeInt64(groupedId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi143.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.DecryptedMessageMedia + } + } + var _6: [SecretApi143.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi143.parseVector(reader, elementSignature: 0, elementType: SecretApi143.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _9 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi143.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi143.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) + case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNoop + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi143.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionAcceptKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionAcceptKey(exchangeId: _1!, gB: _2!, keyFingerprint: _3!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionCommitKey(exchangeId: _1!, keyFingerprint: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi143.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionNoop + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi143.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi143.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi143.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi143.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi143.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi143.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int64, key: Buffer, iv: Buffer, attributes: [SecretApi143.DocumentAttribute], caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi143.PhotoSize, dcId: Int32, attributes: [SecretApi143.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaWebPage(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(1790809986) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt64(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi143.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi143.parseVector(reader, elementSignature: 0, elementType: SecretApi143.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: SecretApi143.PhotoSize? + if let signature = reader.readInt32() { + _6 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.PhotoSize + } + var _7: Int32? + _7 = reader.readInt32() + var _8: [SecretApi143.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi143.parseVector(reader, elementSignature: 0, elementType: SecretApi143.DocumentAttribute.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: _1!, accessHash: _2!, date: _3!, mimeType: _4!, size: _5!, thumb: _6!, dcId: _7!, attributes: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + var _11: String? + _11 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!, caption: _11!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: _1!) + } + else { + return nil + } + } + } + + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi143.InputStickerSet) + case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let flags, let duration, let w, let h): + if boxed { + buffer.appendInt32(250621158) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi143.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi143.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi143.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi143.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi143.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi143.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi143.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi143.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi143.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBlockquote(offset: Int32, length: Int32) + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityStrike(offset: Int32, length: Int32) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnderline(offset: Int32, length: Int32) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBlockquote(let offset, let length): + if boxed { + buffer.appendInt32(34469328) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityStrike(let offset, let length): + if boxed { + buffer.appendInt32(-1090087980) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnderline(let offset, let length): + if boxed { + buffer.appendInt32(-1672577397) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi143.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi143.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi143.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi143.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi143.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi143.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi143.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi143.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi143.parse(reader, signature: signature) as? SecretApi143.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi143.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi143.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadRoundAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageUploadRoundAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi143.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer144.swift b/submodules/TelegramApi/Sources/SecretApiLayer144.swift index 82f0b0bfe3..99b74b877c 100644 --- a/submodules/TelegramApi/Sources/SecretApiLayer144.swift +++ b/submodules/TelegramApi/Sources/SecretApiLayer144.swift @@ -5,60 +5,75 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[570911930] = { return $0.readInt64() } dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } - dict[-1586283796] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } - dict[206520510] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } - dict[1700872964] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } - dict[-1967000459] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } - dict[1729750108] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } - dict[-217806717] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } - dict[1360072880] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } - dict[-204906213] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } - dict[1877046107] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } - dict[-586814357] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } - dict[-332526693] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } - dict[-1473258141] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } - dict[236446268] = { return SecretApi144.PhotoSize.parse_photoSizeEmpty($0) } - dict[2009052699] = { return SecretApi144.PhotoSize.parse_photoSize($0) } - dict[-374917894] = { return SecretApi144.PhotoSize.parse_photoCachedSize($0) } - dict[2086234950] = { return SecretApi144.FileLocation.parse_fileLocationUnavailable($0) } - dict[1406570614] = { return SecretApi144.FileLocation.parse_fileLocation($0) } - dict[467867529] = { return SecretApi144.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } - dict[1930838368] = { return SecretApi144.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-1132882121] = { return SecretApi144.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi144.Bool.parse_boolTrue($0) } dict[-1848883596] = { return SecretApi144.DecryptedMessage.parse_decryptedMessage($0) } - dict[1815593308] = { return SecretApi144.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[1930838368] = { return SecretApi144.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi144.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi144.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[1790809986] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } dict[297109817] = { return SecretApi144.DocumentAttribute.parse_documentAttributeAnimated($0) } - dict[358154344] = { return SecretApi144.DocumentAttribute.parse_documentAttributeFilename($0) } - dict[978674434] = { return SecretApi144.DocumentAttribute.parse_documentAttributeSticker($0) } dict[-1739392570] = { return SecretApi144.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi144.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi144.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi144.DocumentAttribute.parse_documentAttributeSticker($0) } dict[250621158] = { return SecretApi144.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[-2044933984] = { return SecretApi144.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[1406570614] = { return SecretApi144.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi144.FileLocation.parse_fileLocationUnavailable($0) } dict[-4838507] = { return SecretApi144.InputStickerSet.parse_inputStickerSetEmpty($0) } - dict[-1148011883] = { return SecretApi144.MessageEntity.parse_messageEntityUnknown($0) } - dict[-100378723] = { return SecretApi144.MessageEntity.parse_messageEntityMention($0) } - dict[1868782349] = { return SecretApi144.MessageEntity.parse_messageEntityHashtag($0) } - dict[1827637959] = { return SecretApi144.MessageEntity.parse_messageEntityBotCommand($0) } - dict[1859134776] = { return SecretApi144.MessageEntity.parse_messageEntityUrl($0) } - dict[1692693954] = { return SecretApi144.MessageEntity.parse_messageEntityEmail($0) } + dict[-2044933984] = { return SecretApi144.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[34469328] = { return SecretApi144.MessageEntity.parse_messageEntityBlockquote($0) } dict[-1117713463] = { return SecretApi144.MessageEntity.parse_messageEntityBold($0) } - dict[-2106619040] = { return SecretApi144.MessageEntity.parse_messageEntityItalic($0) } + dict[1827637959] = { return SecretApi144.MessageEntity.parse_messageEntityBotCommand($0) } dict[681706865] = { return SecretApi144.MessageEntity.parse_messageEntityCode($0) } + dict[-925956616] = { return SecretApi144.MessageEntity.parse_messageEntityCustomEmoji($0) } + dict[1692693954] = { return SecretApi144.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi144.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi144.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi144.MessageEntity.parse_messageEntityMention($0) } dict[1938967520] = { return SecretApi144.MessageEntity.parse_messageEntityPre($0) } + dict[852137487] = { return SecretApi144.MessageEntity.parse_messageEntitySpoiler($0) } + dict[-1090087980] = { return SecretApi144.MessageEntity.parse_messageEntityStrike($0) } dict[1990644519] = { return SecretApi144.MessageEntity.parse_messageEntityTextUrl($0) } dict[-1672577397] = { return SecretApi144.MessageEntity.parse_messageEntityUnderline($0) } - dict[-1090087980] = { return SecretApi144.MessageEntity.parse_messageEntityStrike($0) } - dict[34469328] = { return SecretApi144.MessageEntity.parse_messageEntityBlockquote($0) } - dict[Int32(bitPattern: 0xc8cf05f8 as UInt32)] = { return SecretApi144.MessageEntity.parse_messageEntityCustomEmoji($0) } - dict[Int32(bitPattern: 0x32ca960f as UInt32)] = { return SecretApi144.MessageEntity.parse_messageEntitySpoiler($0) } - dict[144661578] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } - dict[893913689] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } - dict[1485441687] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } - dict[1474341323] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } - dict[-90853155] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } - dict[-235238024] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } - dict[Int32(bitPattern: 0x6abd9782 as UInt32)] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } - dict[-1760785394] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } - dict[-1978796689] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } - dict[-452652584] = { return SecretApi144.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[-1148011883] = { return SecretApi144.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi144.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi144.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi144.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi144.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi144.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi144.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi144.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi144.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1997373508] = { return SecretApi144.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1584933265] = { return SecretApi144.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi144.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi144.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi144.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi144.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1150187996] = { return SecretApi144.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-1845219337] = { return SecretApi144.SendMessageAction.parse_sendMessageUploadVideoAction($0) } return dict }() @@ -70,18 +85,18 @@ public struct SecretApi144 { } return nil } - - fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { - if let parser = parsers[signature] { - return parser(reader) - } - else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") - return nil - } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) } - - fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -107,226 +122,293 @@ public struct SecretApi144 { } return nil } - + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { - case let _1 as SecretApi144.DecryptedMessageAction: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.PhotoSize: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.FileLocation: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.DecryptedMessageLayer: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.DecryptedMessage: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.DocumentAttribute: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.InputStickerSet: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.MessageEntity: - _1.serialize(buffer, boxed) - case let _1 as SecretApi144.DecryptedMessageMedia: - _1.serialize(buffer, boxed) - default: + case let _1 as SecretApi144.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi144.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi144.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi144.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi144.DecryptedMessageMedia?, entities: [SecretApi144.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi144.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): + if boxed { + buffer.appendInt32(-1848883596) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 17) != 0 { + serializeInt64(groupedId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi144.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.DecryptedMessageMedia + } + } + var _6: [SecretApi144.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi144.parseVector(reader, elementSignature: 0, elementType: SecretApi144.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _9 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi144.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi144.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } } } public enum DecryptedMessageAction { - case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) - case decryptedMessageActionReadMessages(randomIds: [Int64]) - case decryptedMessageActionDeleteMessages(randomIds: [Int64]) - case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) - case decryptedMessageActionFlushHistory - case decryptedMessageActionNotifyLayer(layer: Int32) - case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) - case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) - case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory case decryptedMessageActionNoop - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageActionSetMessageTTL(let ttlSeconds): - if boxed { - buffer.appendInt32(-1586283796) - } - serializeInt32(ttlSeconds, buffer: buffer, boxed: false) - break - case .decryptedMessageActionReadMessages(let randomIds): - if boxed { - buffer.appendInt32(206520510) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionDeleteMessages(let randomIds): - if boxed { - buffer.appendInt32(1700872964) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionScreenshotMessages(let randomIds): - if boxed { - buffer.appendInt32(-1967000459) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionFlushHistory: - if boxed { - buffer.appendInt32(1729750108) - } - - break - case .decryptedMessageActionNotifyLayer(let layer): - if boxed { - buffer.appendInt32(-217806717) - } - serializeInt32(layer, buffer: buffer, boxed: false) - break - case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): - if boxed { - buffer.appendInt32(1360072880) - } - serializeInt32(startSeqNo, buffer: buffer, boxed: false) - serializeInt32(endSeqNo, buffer: buffer, boxed: false) - break - case .decryptedMessageActionRequestKey(let exchangeId, let gA): - if boxed { - buffer.appendInt32(-204906213) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): - if boxed { - buffer.appendInt32(1877046107) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAbortKey(let exchangeId): - if boxed { - buffer.appendInt32(-586814357) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - break - case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): - if boxed { - buffer.appendInt32(-332526693) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionNoop: - if boxed { - buffer.appendInt32(-1473258141) - } - - break - } - } - fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) - } - else { - return nil + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi144.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break } } - fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionFlushHistory - } - fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) } else { return nil @@ -349,17 +431,6 @@ public struct SecretApi144 { return nil } } - fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() @@ -374,196 +445,134 @@ public struct SecretApi144 { return nil } } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionFlushHistory + } fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { return SecretApi144.DecryptedMessageAction.decryptedMessageActionNoop } - - - } - - public enum PhotoSize { - case photoSizeEmpty(type: String) - case photoSize(type: String, location: SecretApi144.FileLocation, w: Int32, h: Int32, size: Int32) - case photoCachedSize(type: String, location: SecretApi144.FileLocation, w: Int32, h: Int32, bytes: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photoSizeEmpty(let type): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(type, buffer: buffer, boxed: false) - break - case .photoSize(let type, let location, let w, let h, let size): - if boxed { - buffer.appendInt32(2009052699) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - break - case .photoCachedSize(let type, let location, let w, let h, let bytes): - if boxed { - buffer.appendInt32(-374917894) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi144.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } - } - fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi144.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi144.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) - } - else { - return nil - } - } - fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi144.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi144.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) - } - else { - return nil - } - } - - - } - - public enum FileLocation { - case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) - case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .fileLocationUnavailable(let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(2086234950) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - case .fileLocation(let dcId, let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(1406570614) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi144.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) - } - else { - return nil - } - } - fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi144.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi144.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi144.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) } else { return nil } } - - } public enum DecryptedMessageLayer { case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi144.DecryptedMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): - if boxed { - buffer.appendInt32(467867529) - } - serializeBytes(randomBytes, buffer: buffer, boxed: false) - serializeInt32(layer, buffer: buffer, boxed: false) - serializeInt32(inSeqNo, buffer: buffer, boxed: false) - serializeInt32(outSeqNo, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - } - } + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { var _1: Buffer? _1 = parseBytes(reader) @@ -589,802 +598,156 @@ public struct SecretApi144 { return nil } } - - - } - - public enum DecryptedMessage { - case decryptedMessageService(randomId: Int64, action: SecretApi144.DecryptedMessageAction) - case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi144.DecryptedMessageMedia?, entities: [SecretApi144.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageService(let randomId, let action): - if boxed { - buffer.appendInt32(1930838368) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): - if boxed { - buffer.appendInt32(-1848883596) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt32(ttl, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 11) != 0 {serializeString(viaBotName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(replyToRandomId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} - break - } - } - fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: SecretApi144.DecryptedMessageAction? - if let signature = reader.readInt32() { - _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.DecryptedMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: SecretApi144.DecryptedMessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _5 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.DecryptedMessageMedia - } } - var _6: [SecretApi144.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _6 = SecretApi144.parseVector(reader, elementSignature: 0, elementType: SecretApi144.MessageEntity.self) - } } - var _7: String? - if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } - var _9: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_9 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 9) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 17) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi144.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) - } - else { - return nil - } - } - - - } - - public enum DocumentAttribute { - case documentAttributeImageSize(w: Int32, h: Int32) - case documentAttributeAnimated - case documentAttributeFilename(fileName: String) - case documentAttributeSticker(alt: String, stickerset: SecretApi144.InputStickerSet) - case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) - case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .documentAttributeImageSize(let w, let h): - if boxed { - buffer.appendInt32(1815593308) - } - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .documentAttributeAnimated: - if boxed { - buffer.appendInt32(297109817) - } - - break - case .documentAttributeFilename(let fileName): - if boxed { - buffer.appendInt32(358154344) - } - serializeString(fileName, buffer: buffer, boxed: false) - break - case .documentAttributeSticker(let alt, let stickerset): - if boxed { - buffer.appendInt32(978674434) - } - serializeString(alt, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - break - case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): - if boxed { - buffer.appendInt32(-1739392570) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(waveform!, buffer: buffer, boxed: false)} - break - case .documentAttributeVideo(let flags, let duration, let w, let h): - if boxed { - buffer.appendInt32(250621158) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { - return SecretApi144.DocumentAttribute.documentAttributeAnimated - } - fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi144.DocumentAttribute.documentAttributeFilename(fileName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi144.InputStickerSet? - if let signature = reader.readInt32() { - _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.InputStickerSet - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseBytes(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi144.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi144.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) - } - else { - return nil - } - } - - - } - - public enum InputStickerSet { - case inputStickerSetShortName(shortName: String) - case inputStickerSetEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - } - } - fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi144.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return SecretApi144.InputStickerSet.inputStickerSetEmpty - } - - - } - - public enum MessageEntity { - case messageEntityUnknown(offset: Int32, length: Int32) - case messageEntityMention(offset: Int32, length: Int32) - case messageEntityHashtag(offset: Int32, length: Int32) - case messageEntityBotCommand(offset: Int32, length: Int32) - case messageEntityUrl(offset: Int32, length: Int32) - case messageEntityEmail(offset: Int32, length: Int32) - case messageEntityBold(offset: Int32, length: Int32) - case messageEntityItalic(offset: Int32, length: Int32) - case messageEntityCode(offset: Int32, length: Int32) - case messageEntityPre(offset: Int32, length: Int32, language: String) - case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - case messageEntityUnderline(offset: Int32, length: Int32) - case messageEntityStrike(offset: Int32, length: Int32) - case messageEntityBlockquote(offset: Int32, length: Int32) - case messageEntityCustomEmoji(offset: Int32, length: Int32, documentId: Int64) - case messageEntitySpoiler(offset: Int32, length: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageEntityUnknown(let offset, let length): - if boxed { - buffer.appendInt32(-1148011883) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let offset, let length): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let offset, let length): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let offset, let length): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityUrl(let offset, let length): - if boxed { - buffer.appendInt32(1859134776) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let offset, let length): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let offset, let length): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let offset, let length): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let offset, let length): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let offset, let length, let language): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(language, buffer: buffer, boxed: false) - break - case .messageEntityTextUrl(let offset, let length, let url): - if boxed { - buffer.appendInt32(1990644519) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - case .messageEntityUnderline(let offset, let length): - if boxed { - buffer.appendInt32(-1672577397) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityStrike(let offset, let length): - if boxed { - buffer.appendInt32(-1090087980) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBlockquote(let offset, let length): - if boxed { - buffer.appendInt32(34469328) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCustomEmoji(let offset, let length, let documentId): - if boxed { - buffer.appendInt32(Int32(bitPattern: 0xc8cf05f8 as UInt32)) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeInt64(documentId, buffer: buffer, boxed: false) - break - case .messageEntitySpoiler(let offset, let length): - if boxed { - buffer.appendInt32(Int32(bitPattern: 0x32ca960f as UInt32)) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityBold(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityCode(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi144.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi144.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi144.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) - } - else { - return nil - } - } } public enum DecryptedMessageMedia { - case decryptedMessageMediaEmpty - case decryptedMessageMediaGeoPoint(lat: Double, long: Double) - case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi144.PhotoSize, dcId: Int32, attributes: [SecretApi144.DocumentAttribute]) - case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int64, key: Buffer, iv: Buffer, attributes: [SecretApi144.DocumentAttribute], caption: String) - case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi144.PhotoSize, dcId: Int32, attributes: [SecretApi144.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaWebPage(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageMediaEmpty: - if boxed { - buffer.appendInt32(144661578) - } - - break - case .decryptedMessageMediaGeoPoint(let lat, let long): - if boxed { - buffer.appendInt32(893913689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): - if boxed { - buffer.appendInt32(1485441687) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): - if boxed { - buffer.appendInt32(1474341323) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): - if boxed { - buffer.appendInt32(-90853155) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - thumb.serialize(buffer, true) - serializeInt32(dcId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-235238024) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): - if boxed { - buffer.appendInt32(Int32(bitPattern: 0x6abd9782 as UInt32)) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt64(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-1760785394) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): - if boxed { - buffer.appendInt32(-1978796689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaWebPage(let url): - if boxed { - buffer.appendInt32(-452652584) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { - return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(1790809986) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt64(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } } - fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) } else { return nil @@ -1410,29 +773,46 @@ public struct SecretApi144 { return nil } } - fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Buffer? - _5 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int64? + _5 = reader.readInt64() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi144.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi144.parseVector(reader, elementSignature: 0, elementType: SecretApi144.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) } else { return nil } } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaEmpty + } fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Int64? _1 = reader.readInt64() @@ -1469,6 +849,20 @@ public struct SecretApi144 { return nil } } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Buffer? _1 = parseBytes(reader) @@ -1504,38 +898,27 @@ public struct SecretApi144 { return nil } } - fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: Int64? - _5 = reader.readInt64() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) - var _8: [SecretApi144.DocumentAttribute]? - if let _ = reader.readInt32() { - _8 = SecretApi144.parseVector(reader, elementSignature: 0, elementType: SecretApi144.DocumentAttribute.self) - } - var _9: String? - _9 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) } else { return nil @@ -1582,32 +965,6 @@ public struct SecretApi144 { return nil } } - fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return SecretApi144.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: String? _1 = parseString(reader) @@ -1619,12 +976,854 @@ public struct SecretApi144 { return nil } } - - } - public struct functions { - + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi144.InputStickerSet) + case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let flags, let duration, let w, let h): + if boxed { + buffer.appendInt32(250621158) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi144.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi144.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi144.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi144.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi144.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi144.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi144.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi144.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi144.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBlockquote(offset: Int32, length: Int32) + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityCustomEmoji(offset: Int32, length: Int32, documentId: Int64) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntitySpoiler(offset: Int32, length: Int32) + case messageEntityStrike(offset: Int32, length: Int32) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnderline(offset: Int32, length: Int32) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBlockquote(let offset, let length): + if boxed { + buffer.appendInt32(34469328) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCustomEmoji(let offset, let length, let documentId): + if boxed { + buffer.appendInt32(-925956616) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeInt64(documentId, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntitySpoiler(let offset, let length): + if boxed { + buffer.appendInt32(852137487) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityStrike(let offset, let length): + if boxed { + buffer.appendInt32(-1090087980) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnderline(let offset, let length): + if boxed { + buffer.appendInt32(-1672577397) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBlockquote(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityBlockquote(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCustomEmoji(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi144.MessageEntity.messageEntityCustomEmoji(offset: _1!, length: _2!, documentId: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi144.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntitySpoiler(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntitySpoiler(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityStrike(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityStrike(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi144.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnderline(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityUnderline(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi144.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi144.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi144.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi144.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi144.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi144.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi144.parse(reader, signature: signature) as? SecretApi144.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi144.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi144.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadRoundAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageUploadRoundAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi144.SendMessageAction.sendMessageUploadVideoAction + } } } diff --git a/submodules/TelegramApi/Sources/SecretApiLayer17.swift b/submodules/TelegramApi/Sources/SecretApiLayer17.swift new file mode 100644 index 0000000000..b198ac471f --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer17.swift @@ -0,0 +1,760 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi17.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi17.Bool.parse_boolTrue($0) } + dict[541931640] = { return SecretApi17.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi17.DecryptedMessage.parse_decryptedMessageService($0) } + dict[1700872964] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-217806717] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[1360072880] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi17.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi17.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[-1332395189] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[893913689] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[846826124] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[1380598109] = { return SecretApi17.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-44119819] = { return SecretApi17.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi17.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi17.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi17.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1584933265] = { return SecretApi17.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi17.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi17.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi17.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi17.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1845219337] = { return SecretApi17.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi17 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi17.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi17.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi17.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi17.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi17.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi17.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi17.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi17.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi17.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(randomId: Int64, ttl: Int32, message: String, media: SecretApi17.DecryptedMessageMedia) + case decryptedMessageService(randomId: Int64, action: SecretApi17.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let randomId, let ttl, let message, let media): + if boxed { + buffer.appendInt32(541931640) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + media.serialize(buffer, true) + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: SecretApi17.DecryptedMessageMedia? + if let signature = reader.readInt32() { + _4 = SecretApi17.parse(reader, signature: signature) as? SecretApi17.DecryptedMessageMedia + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi17.DecryptedMessage.decryptedMessage(randomId: _1!, ttl: _2!, message: _3!, media: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi17.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi17.parse(reader, signature: signature) as? SecretApi17.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi17.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi17.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi17.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi17.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi17.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi17.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi17.parse(reader, signature: signature) as? SecretApi17.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi17.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi17.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi17.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi17.parse(reader, signature: signature) as? SecretApi17.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi17.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, fileName: String, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaEmpty + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(-1332395189) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(846826124) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(1380598109) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return SecretApi17.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi17.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer20.swift b/submodules/TelegramApi/Sources/SecretApiLayer20.swift new file mode 100644 index 0000000000..a77d3f85d0 --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer20.swift @@ -0,0 +1,862 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi20.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi20.Bool.parse_boolTrue($0) } + dict[541931640] = { return SecretApi20.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi20.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi20.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi20.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[-1332395189] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[893913689] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[846826124] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[1380598109] = { return SecretApi20.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-44119819] = { return SecretApi20.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi20.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi20.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi20.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1584933265] = { return SecretApi20.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi20.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi20.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi20.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi20.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1845219337] = { return SecretApi20.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi20 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi20.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi20.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi20.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi20.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi20.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi20.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi20.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi20.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi20.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(randomId: Int64, ttl: Int32, message: String, media: SecretApi20.DecryptedMessageMedia) + case decryptedMessageService(randomId: Int64, action: SecretApi20.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let randomId, let ttl, let message, let media): + if boxed { + buffer.appendInt32(541931640) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + media.serialize(buffer, true) + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: SecretApi20.DecryptedMessageMedia? + if let signature = reader.readInt32() { + _4 = SecretApi20.parse(reader, signature: signature) as? SecretApi20.DecryptedMessageMedia + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi20.DecryptedMessage.decryptedMessage(randomId: _1!, ttl: _2!, message: _3!, media: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi20.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi20.parse(reader, signature: signature) as? SecretApi20.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi20.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) + case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNoop + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi20.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionAcceptKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionAcceptKey(exchangeId: _1!, gB: _2!, keyFingerprint: _3!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionCommitKey(exchangeId: _1!, keyFingerprint: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi20.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionNoop + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi20.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi20.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi20.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi20.parse(reader, signature: signature) as? SecretApi20.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi20.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi20.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi20.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi20.parse(reader, signature: signature) as? SecretApi20.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi20.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, fileName: String, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaEmpty + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(-1332395189) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(846826124) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(1380598109) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return SecretApi20.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi20.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer23.swift b/submodules/TelegramApi/Sources/SecretApiLayer23.swift new file mode 100644 index 0000000000..c8c876e439 --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer23.swift @@ -0,0 +1,1209 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi23.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi23.Bool.parse_boolTrue($0) } + dict[541931640] = { return SecretApi23.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi23.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi23.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi23.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[-1332395189] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[846826124] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[1380598109] = { return SecretApi23.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[297109817] = { return SecretApi23.DocumentAttribute.parse_documentAttributeAnimated($0) } + dict[85215461] = { return SecretApi23.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi23.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi23.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[-83208409] = { return SecretApi23.DocumentAttribute.parse_documentAttributeSticker($0) } + dict[1494273227] = { return SecretApi23.DocumentAttribute.parse_documentAttributeVideo($0) } + dict[1406570614] = { return SecretApi23.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi23.FileLocation.parse_fileLocationUnavailable($0) } + dict[-374917894] = { return SecretApi23.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi23.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi23.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi23.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi23.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi23.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi23.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1584933265] = { return SecretApi23.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi23.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi23.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi23.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi23.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1845219337] = { return SecretApi23.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi23 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi23.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi23.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi23.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi23.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi23.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(randomId: Int64, ttl: Int32, message: String, media: SecretApi23.DecryptedMessageMedia) + case decryptedMessageService(randomId: Int64, action: SecretApi23.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let randomId, let ttl, let message, let media): + if boxed { + buffer.appendInt32(541931640) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + media.serialize(buffer, true) + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: SecretApi23.DecryptedMessageMedia? + if let signature = reader.readInt32() { + _4 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.DecryptedMessageMedia + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi23.DecryptedMessage.decryptedMessage(randomId: _1!, ttl: _2!, message: _3!, media: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi23.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) + case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNoop + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi23.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionAcceptKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionAcceptKey(exchangeId: _1!, gB: _2!, keyFingerprint: _3!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionCommitKey(exchangeId: _1!, keyFingerprint: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi23.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionNoop + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi23.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi23.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi23.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi23.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi23.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi23.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, fileName: String, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi23.PhotoSize, dcId: Int32, attributes: [SecretApi23.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(-1332395189) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(846826124) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(1380598109) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: SecretApi23.PhotoSize? + if let signature = reader.readInt32() { + _6 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.PhotoSize + } + var _7: Int32? + _7 = reader.readInt32() + var _8: [SecretApi23.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi23.parseVector(reader, elementSignature: 0, elementType: SecretApi23.DocumentAttribute.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: _1!, accessHash: _2!, date: _3!, mimeType: _4!, size: _5!, thumb: _6!, dcId: _7!, attributes: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { + return SecretApi23.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!) + } + else { + return nil + } + } + } + + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(duration: Int32) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker + case documentAttributeVideo(duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let duration): + if boxed { + buffer.appendInt32(85215461) + } + serializeInt32(duration, buffer: buffer, boxed: false) + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker: + if boxed { + buffer.appendInt32(-83208409) + } + break + case .documentAttributeVideo(let duration, let w, let h): + if boxed { + buffer.appendInt32(1494273227) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi23.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DocumentAttribute.documentAttributeAudio(duration: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi23.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi23.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi23.DocumentAttribute.documentAttributeSticker + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi23.DocumentAttribute.documentAttributeVideo(duration: _1!, w: _2!, h: _3!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi23.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi23.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi23.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi23.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi23.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi23.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi23.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi23.parse(reader, signature: signature) as? SecretApi23.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi23.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi23.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi23.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer45.swift b/submodules/TelegramApi/Sources/SecretApiLayer45.swift new file mode 100644 index 0000000000..032b2d04fc --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer45.swift @@ -0,0 +1,1658 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi45.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi45.Bool.parse_boolTrue($0) } + dict[917541342] = { return SecretApi45.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi45.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi45.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi45.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[2063502050] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi45.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[297109817] = { return SecretApi45.DocumentAttribute.parse_documentAttributeAnimated($0) } + dict[-556656416] = { return SecretApi45.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi45.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi45.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi45.DocumentAttribute.parse_documentAttributeSticker($0) } + dict[1494273227] = { return SecretApi45.DocumentAttribute.parse_documentAttributeVideo($0) } + dict[1406570614] = { return SecretApi45.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi45.FileLocation.parse_fileLocationUnavailable($0) } + dict[-4838507] = { return SecretApi45.InputStickerSet.parse_inputStickerSetEmpty($0) } + dict[-2044933984] = { return SecretApi45.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[-1117713463] = { return SecretApi45.MessageEntity.parse_messageEntityBold($0) } + dict[1827637959] = { return SecretApi45.MessageEntity.parse_messageEntityBotCommand($0) } + dict[681706865] = { return SecretApi45.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi45.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi45.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi45.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi45.MessageEntity.parse_messageEntityMention($0) } + dict[1938967520] = { return SecretApi45.MessageEntity.parse_messageEntityPre($0) } + dict[1990644519] = { return SecretApi45.MessageEntity.parse_messageEntityTextUrl($0) } + dict[-1148011883] = { return SecretApi45.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi45.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi45.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi45.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi45.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi45.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi45.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi45.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi45.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1584933265] = { return SecretApi45.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi45.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi45.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi45.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi45.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1845219337] = { return SecretApi45.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi45 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi45.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi45.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi45.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi45.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi45.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi45.DecryptedMessageMedia?, entities: [SecretApi45.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi45.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId): + if boxed { + buffer.appendInt32(917541342) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi45.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.DecryptedMessageMedia + } + } + var _6: [SecretApi45.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi45.parseVector(reader, elementSignature: 0, elementType: SecretApi45.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi45.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi45.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) + case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNoop + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi45.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionAcceptKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionAcceptKey(exchangeId: _1!, gB: _2!, keyFingerprint: _3!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionCommitKey(exchangeId: _1!, keyFingerprint: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi45.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionNoop + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi45.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi45.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi45.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi45.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi45.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi45.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi45.DocumentAttribute], caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi45.PhotoSize, dcId: Int32, attributes: [SecretApi45.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaWebPage(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(2063502050) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi45.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi45.parseVector(reader, elementSignature: 0, elementType: SecretApi45.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: SecretApi45.PhotoSize? + if let signature = reader.readInt32() { + _6 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.PhotoSize + } + var _7: Int32? + _7 = reader.readInt32() + var _8: [SecretApi45.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi45.parseVector(reader, elementSignature: 0, elementType: SecretApi45.DocumentAttribute.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: _1!, accessHash: _2!, date: _3!, mimeType: _4!, size: _5!, thumb: _6!, dcId: _7!, attributes: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + var _11: String? + _11 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!, caption: _11!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: _1!) + } + else { + return nil + } + } + } + + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(duration: Int32, title: String, performer: String) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi45.InputStickerSet) + case documentAttributeVideo(duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let duration, let title, let performer): + if boxed { + buffer.appendInt32(-556656416) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(performer, buffer: buffer, boxed: false) + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let duration, let w, let h): + if boxed { + buffer.appendInt32(1494273227) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi45.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.DocumentAttribute.documentAttributeAudio(duration: _1!, title: _2!, performer: _3!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi45.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi45.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.DocumentAttribute.documentAttributeVideo(duration: _1!, w: _2!, h: _3!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi45.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi45.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi45.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi45.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi45.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi45.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi45.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi45.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi45.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi45.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi45.parse(reader, signature: signature) as? SecretApi45.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi45.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi45.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi45.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer46.swift b/submodules/TelegramApi/Sources/SecretApiLayer46.swift index 2b0bca2502..4986c5444a 100644 --- a/submodules/TelegramApi/Sources/SecretApiLayer46.swift +++ b/submodules/TelegramApi/Sources/SecretApiLayer46.swift @@ -5,55 +5,68 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[570911930] = { return $0.readInt64() } dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } - dict[-1586283796] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } - dict[206520510] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } - dict[1700872964] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } - dict[-1967000459] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } - dict[1729750108] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } - dict[-217806717] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } - dict[1360072880] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } - dict[-204906213] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } - dict[1877046107] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } - dict[-586814357] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } - dict[-332526693] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } - dict[-1473258141] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } - dict[236446268] = { return SecretApi46.PhotoSize.parse_photoSizeEmpty($0) } - dict[2009052699] = { return SecretApi46.PhotoSize.parse_photoSize($0) } - dict[-374917894] = { return SecretApi46.PhotoSize.parse_photoCachedSize($0) } - dict[2086234950] = { return SecretApi46.FileLocation.parse_fileLocationUnavailable($0) } - dict[1406570614] = { return SecretApi46.FileLocation.parse_fileLocation($0) } - dict[467867529] = { return SecretApi46.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } - dict[1930838368] = { return SecretApi46.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-1132882121] = { return SecretApi46.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi46.Bool.parse_boolTrue($0) } dict[917541342] = { return SecretApi46.DecryptedMessage.parse_decryptedMessage($0) } - dict[1815593308] = { return SecretApi46.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[1930838368] = { return SecretApi46.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi46.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi46.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[2063502050] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } dict[297109817] = { return SecretApi46.DocumentAttribute.parse_documentAttributeAnimated($0) } - dict[1494273227] = { return SecretApi46.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[358154344] = { return SecretApi46.DocumentAttribute.parse_documentAttributeFilename($0) } - dict[978674434] = { return SecretApi46.DocumentAttribute.parse_documentAttributeSticker($0) } dict[-1739392570] = { return SecretApi46.DocumentAttribute.parse_documentAttributeAudio($0) } - dict[-2044933984] = { return SecretApi46.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[358154344] = { return SecretApi46.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi46.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi46.DocumentAttribute.parse_documentAttributeSticker($0) } + dict[1494273227] = { return SecretApi46.DocumentAttribute.parse_documentAttributeVideo($0) } + dict[1406570614] = { return SecretApi46.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi46.FileLocation.parse_fileLocationUnavailable($0) } dict[-4838507] = { return SecretApi46.InputStickerSet.parse_inputStickerSetEmpty($0) } - dict[-1148011883] = { return SecretApi46.MessageEntity.parse_messageEntityUnknown($0) } - dict[-100378723] = { return SecretApi46.MessageEntity.parse_messageEntityMention($0) } - dict[1868782349] = { return SecretApi46.MessageEntity.parse_messageEntityHashtag($0) } - dict[1827637959] = { return SecretApi46.MessageEntity.parse_messageEntityBotCommand($0) } - dict[1859134776] = { return SecretApi46.MessageEntity.parse_messageEntityUrl($0) } - dict[1692693954] = { return SecretApi46.MessageEntity.parse_messageEntityEmail($0) } + dict[-2044933984] = { return SecretApi46.InputStickerSet.parse_inputStickerSetShortName($0) } dict[-1117713463] = { return SecretApi46.MessageEntity.parse_messageEntityBold($0) } - dict[-2106619040] = { return SecretApi46.MessageEntity.parse_messageEntityItalic($0) } + dict[1827637959] = { return SecretApi46.MessageEntity.parse_messageEntityBotCommand($0) } dict[681706865] = { return SecretApi46.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi46.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi46.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi46.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi46.MessageEntity.parse_messageEntityMention($0) } dict[1938967520] = { return SecretApi46.MessageEntity.parse_messageEntityPre($0) } dict[1990644519] = { return SecretApi46.MessageEntity.parse_messageEntityTextUrl($0) } - dict[144661578] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } - dict[893913689] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } - dict[1485441687] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } - dict[1474341323] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } - dict[-90853155] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } - dict[-235238024] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } - dict[2063502050] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } - dict[-1760785394] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } - dict[-1978796689] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } - dict[-452652584] = { return SecretApi46.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[-1148011883] = { return SecretApi46.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi46.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi46.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi46.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi46.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi46.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi46.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi46.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi46.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1584933265] = { return SecretApi46.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi46.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi46.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi46.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi46.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1845219337] = { return SecretApi46.SendMessageAction.parse_sendMessageUploadVideoAction($0) } return dict }() @@ -65,18 +78,18 @@ public struct SecretApi46 { } return nil } - - fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { - if let parser = parsers[signature] { - return parser(reader) - } - else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") - return nil - } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) } - - fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -102,227 +115,285 @@ public struct SecretApi46 { } return nil } - + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { - case let _1 as SecretApi46.DecryptedMessageAction: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.PhotoSize: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.FileLocation: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.DecryptedMessageLayer: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.DecryptedMessage: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.DocumentAttribute: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.InputStickerSet: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.MessageEntity: - _1.serialize(buffer, boxed) - case let _1 as SecretApi46.DecryptedMessageMedia: - _1.serialize(buffer, boxed) - default: + case let _1 as SecretApi46.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi46.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi46.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi46.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi46.DecryptedMessageMedia?, entities: [SecretApi46.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi46.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId): + if boxed { + buffer.appendInt32(917541342) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi46.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.DecryptedMessageMedia + } + } + var _6: [SecretApi46.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi46.parseVector(reader, elementSignature: 0, elementType: SecretApi46.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi46.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi46.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } } } public enum DecryptedMessageAction { - case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) - case decryptedMessageActionReadMessages(randomIds: [Int64]) - case decryptedMessageActionDeleteMessages(randomIds: [Int64]) - case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) - case decryptedMessageActionFlushHistory - case decryptedMessageActionNotifyLayer(layer: Int32) - case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) - case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) - case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory case decryptedMessageActionNoop - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageActionSetMessageTTL(let ttlSeconds): - if boxed { - buffer.appendInt32(-1586283796) - } - serializeInt32(ttlSeconds, buffer: buffer, boxed: false) - break - case .decryptedMessageActionReadMessages(let randomIds): - if boxed { - buffer.appendInt32(206520510) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionDeleteMessages(let randomIds): - if boxed { - buffer.appendInt32(1700872964) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionScreenshotMessages(let randomIds): - if boxed { - buffer.appendInt32(-1967000459) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionFlushHistory: - if boxed { - buffer.appendInt32(1729750108) - } - - break - case .decryptedMessageActionNotifyLayer(let layer): - if boxed { - buffer.appendInt32(-217806717) - } - serializeInt32(layer, buffer: buffer, boxed: false) - break - case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): - if boxed { - buffer.appendInt32(1360072880) - } - serializeInt32(startSeqNo, buffer: buffer, boxed: false) - serializeInt32(endSeqNo, buffer: buffer, boxed: false) - break - case .decryptedMessageActionRequestKey(let exchangeId, let gA): - if boxed { - buffer.appendInt32(-204906213) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): - if boxed { - buffer.appendInt32(1877046107) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAbortKey(let exchangeId): - if boxed { - buffer.appendInt32(-586814357) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - break - case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): - if boxed { - buffer.appendInt32(-332526693) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionNoop: - if boxed { - buffer.appendInt32(-1473258141) - } - - break - } - } - - fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) - } - else { - return nil + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi46.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break } } - fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionFlushHistory - } - fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) } else { return nil @@ -345,17 +416,6 @@ public struct SecretApi46 { return nil } } - fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() @@ -370,196 +430,134 @@ public struct SecretApi46 { return nil } } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionFlushHistory + } fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { return SecretApi46.DecryptedMessageAction.decryptedMessageActionNoop } - - } - - public enum PhotoSize { - case photoSizeEmpty(type: String) - case photoSize(type: String, location: SecretApi46.FileLocation, w: Int32, h: Int32, size: Int32) - case photoCachedSize(type: String, location: SecretApi46.FileLocation, w: Int32, h: Int32, bytes: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photoSizeEmpty(let type): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(type, buffer: buffer, boxed: false) - break - case .photoSize(let type, let location, let w, let h, let size): - if boxed { - buffer.appendInt32(2009052699) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - break - case .photoCachedSize(let type, let location, let w, let h, let bytes): - if boxed { - buffer.appendInt32(-374917894) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - } - } - - fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi46.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } - } - fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi46.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi46.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) - } - else { - return nil - } - } - fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi46.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi46.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) - } - else { - return nil - } - } - - } - - public enum FileLocation { - case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) - case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .fileLocationUnavailable(let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(2086234950) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - case .fileLocation(let dcId, let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(1406570614) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - } - } - - fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi46.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) - } - else { - return nil - } - } - fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi46.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi46.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi46.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) } else { return nil } } - } public enum DecryptedMessageLayer { case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi46.DecryptedMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): - if boxed { - buffer.appendInt32(467867529) - } - serializeBytes(randomBytes, buffer: buffer, boxed: false) - serializeInt32(layer, buffer: buffer, boxed: false) - serializeInt32(inSeqNo, buffer: buffer, boxed: false) - serializeInt32(outSeqNo, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - } - } - + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { var _1: Buffer? _1 = parseBytes(reader) @@ -585,682 +583,156 @@ public struct SecretApi46 { return nil } } - } - public enum DecryptedMessage { - case decryptedMessageService(randomId: Int64, action: SecretApi46.DecryptedMessageAction) - case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi46.DecryptedMessageMedia?, entities: [SecretApi46.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageService(let randomId, let action): - if boxed { - buffer.appendInt32(1930838368) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId): - if boxed { - buffer.appendInt32(917541342) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt32(ttl, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 11) != 0 {serializeString(viaBotName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(replyToRandomId!, buffer: buffer, boxed: false)} - break - } - } - - fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: SecretApi46.DecryptedMessageAction? - if let signature = reader.readInt32() { - _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.DecryptedMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) - } - else { - return nil + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi46.DocumentAttribute], caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi46.PhotoSize, dcId: Int32, attributes: [SecretApi46.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaWebPage(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(2063502050) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break } } - fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() + var _2: String? + _2 = parseString(reader) var _3: Int32? _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: SecretApi46.DecryptedMessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _5 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.DecryptedMessageMedia - } } - var _6: [SecretApi46.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _6 = SecretApi46.parseVector(reader, elementSignature: 0, elementType: SecretApi46.MessageEntity.self) - } } - var _7: String? - if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 9) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return SecretApi46.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8) - } - else { - return nil - } - } - - } - - public enum DocumentAttribute { - case documentAttributeImageSize(w: Int32, h: Int32) - case documentAttributeAnimated - case documentAttributeVideo(duration: Int32, w: Int32, h: Int32) - case documentAttributeFilename(fileName: String) - case documentAttributeSticker(alt: String, stickerset: SecretApi46.InputStickerSet) - case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .documentAttributeImageSize(let w, let h): - if boxed { - buffer.appendInt32(1815593308) - } - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .documentAttributeAnimated: - if boxed { - buffer.appendInt32(297109817) - } - - break - case .documentAttributeVideo(let duration, let w, let h): - if boxed { - buffer.appendInt32(1494273227) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .documentAttributeFilename(let fileName): - if boxed { - buffer.appendInt32(358154344) - } - serializeString(fileName, buffer: buffer, boxed: false) - break - case .documentAttributeSticker(let alt, let stickerset): - if boxed { - buffer.appendInt32(978674434) - } - serializeString(alt, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - break - case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): - if boxed { - buffer.appendInt32(-1739392570) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(waveform!, buffer: buffer, boxed: false)} - break - } - } - - fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { - return SecretApi46.DocumentAttribute.documentAttributeAnimated - } - fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi46.DocumentAttribute.documentAttributeVideo(duration: _1!, w: _2!, h: _3!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi46.DocumentAttribute.documentAttributeFilename(fileName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi46.InputStickerSet? - if let signature = reader.readInt32() { - _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.InputStickerSet - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseBytes(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil + let _c5 = _5 != nil if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi46.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) - } - else { - return nil - } - } - - } - - public enum InputStickerSet { - case inputStickerSetShortName(shortName: String) - case inputStickerSetEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - } - } - - fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi46.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return SecretApi46.InputStickerSet.inputStickerSetEmpty - } - - } - - public enum MessageEntity { - case messageEntityUnknown(offset: Int32, length: Int32) - case messageEntityMention(offset: Int32, length: Int32) - case messageEntityHashtag(offset: Int32, length: Int32) - case messageEntityBotCommand(offset: Int32, length: Int32) - case messageEntityUrl(offset: Int32, length: Int32) - case messageEntityEmail(offset: Int32, length: Int32) - case messageEntityBold(offset: Int32, length: Int32) - case messageEntityItalic(offset: Int32, length: Int32) - case messageEntityCode(offset: Int32, length: Int32) - case messageEntityPre(offset: Int32, length: Int32, language: String) - case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageEntityUnknown(let offset, let length): - if boxed { - buffer.appendInt32(-1148011883) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let offset, let length): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let offset, let length): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let offset, let length): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityUrl(let offset, let length): - if boxed { - buffer.appendInt32(1859134776) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let offset, let length): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let offset, let length): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let offset, let length): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let offset, let length): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let offset, let length, let language): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(language, buffer: buffer, boxed: false) - break - case .messageEntityTextUrl(let offset, let length, let url): - if boxed { - buffer.appendInt32(1990644519) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityBold(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.MessageEntity.messageEntityCode(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi46.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi46.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } - } - - } - - public enum DecryptedMessageMedia { - case decryptedMessageMediaEmpty - case decryptedMessageMediaGeoPoint(lat: Double, long: Double) - case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) - case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi46.PhotoSize, dcId: Int32, attributes: [SecretApi46.DocumentAttribute]) - case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) - case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi46.DocumentAttribute], caption: String) - case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) - case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) - case decryptedMessageMediaWebPage(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageMediaEmpty: - if boxed { - buffer.appendInt32(144661578) - } - - break - case .decryptedMessageMediaGeoPoint(let lat, let long): - if boxed { - buffer.appendInt32(893913689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): - if boxed { - buffer.appendInt32(1485441687) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): - if boxed { - buffer.appendInt32(1474341323) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): - if boxed { - buffer.appendInt32(-90853155) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - thumb.serialize(buffer, true) - serializeInt32(dcId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-235238024) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): - if boxed { - buffer.appendInt32(2063502050) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-1760785394) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): - if boxed { - buffer.appendInt32(-1978796689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaWebPage(let url): - if boxed { - buffer.appendInt32(-452652584) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { - return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaEmpty - } - fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) } else { return nil @@ -1286,29 +758,46 @@ public struct SecretApi46 { return nil } } - fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Buffer? - _5 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi46.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi46.parseVector(reader, elementSignature: 0, elementType: SecretApi46.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) } else { return nil } } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaEmpty + } fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Int64? _1 = reader.readInt64() @@ -1345,6 +834,20 @@ public struct SecretApi46 { return nil } } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Buffer? _1 = parseBytes(reader) @@ -1380,38 +883,27 @@ public struct SecretApi46 { return nil } } - fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) - var _8: [SecretApi46.DocumentAttribute]? - if let _ = reader.readInt32() { - _8 = SecretApi46.parseVector(reader, elementSignature: 0, elementType: SecretApi46.DocumentAttribute.self) - } - var _9: String? - _9 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) } else { return nil @@ -1458,32 +950,6 @@ public struct SecretApi46 { return nil } } - fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return SecretApi46.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: String? _1 = parseString(reader) @@ -1495,11 +961,718 @@ public struct SecretApi46 { return nil } } - } - public struct functions { - + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi46.InputStickerSet) + case documentAttributeVideo(duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let duration, let w, let h): + if boxed { + buffer.appendInt32(1494273227) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi46.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi46.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi46.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi46.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi46.DocumentAttribute.documentAttributeVideo(duration: _1!, w: _2!, h: _3!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi46.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi46.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi46.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi46.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi46.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi46.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi46.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi46.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi46.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi46.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi46.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi46.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi46.parse(reader, signature: signature) as? SecretApi46.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi46.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi46.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi46.SendMessageAction.sendMessageUploadVideoAction + } } } diff --git a/submodules/TelegramApi/Sources/SecretApiLayer66.swift b/submodules/TelegramApi/Sources/SecretApiLayer66.swift new file mode 100644 index 0000000000..5a9957d87a --- /dev/null +++ b/submodules/TelegramApi/Sources/SecretApiLayer66.swift @@ -0,0 +1,1702 @@ + +fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { + var dict: [Int32 : (BufferReader) -> Any?] = [:] + dict[-1471112230] = { return $0.readInt32() } + dict[570911930] = { return $0.readInt64() } + dict[571523412] = { return $0.readDouble() } + dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi66.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi66.Bool.parse_boolTrue($0) } + dict[917541342] = { return SecretApi66.DecryptedMessage.parse_decryptedMessage($0) } + dict[1930838368] = { return SecretApi66.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi66.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi66.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[2063502050] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi66.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[297109817] = { return SecretApi66.DocumentAttribute.parse_documentAttributeAnimated($0) } + dict[-1739392570] = { return SecretApi66.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi66.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi66.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi66.DocumentAttribute.parse_documentAttributeSticker($0) } + dict[250621158] = { return SecretApi66.DocumentAttribute.parse_documentAttributeVideo($0) } + dict[1406570614] = { return SecretApi66.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi66.FileLocation.parse_fileLocationUnavailable($0) } + dict[-4838507] = { return SecretApi66.InputStickerSet.parse_inputStickerSetEmpty($0) } + dict[-2044933984] = { return SecretApi66.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[-1117713463] = { return SecretApi66.MessageEntity.parse_messageEntityBold($0) } + dict[1827637959] = { return SecretApi66.MessageEntity.parse_messageEntityBotCommand($0) } + dict[681706865] = { return SecretApi66.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi66.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi66.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi66.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi66.MessageEntity.parse_messageEntityMention($0) } + dict[1938967520] = { return SecretApi66.MessageEntity.parse_messageEntityPre($0) } + dict[1990644519] = { return SecretApi66.MessageEntity.parse_messageEntityTextUrl($0) } + dict[-1148011883] = { return SecretApi66.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi66.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi66.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi66.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi66.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi66.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi66.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi66.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi66.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1997373508] = { return SecretApi66.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1584933265] = { return SecretApi66.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi66.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi66.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi66.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi66.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1150187996] = { return SecretApi66.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-1845219337] = { return SecretApi66.SendMessageAction.parse_sendMessageUploadVideoAction($0) } + return dict +}() + +public struct SecretApi66 { + public static func parse(_ buffer: Buffer) -> Any? { + let reader = BufferReader(buffer) + if let signature = reader.readInt32() { + return parse(reader, signature: signature) + } + return nil + } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) + } + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + if let count = reader.readInt32() { + var array = [T]() + var i: Int32 = 0 + while i < count { + var signature = elementSignature + if elementSignature == 0 { + if let unboxedSignature = reader.readInt32() { + signature = unboxedSignature + } + else { + return nil + } + } + if let item = SecretApi66.parse(reader, signature: signature) as? T { + array.append(item) + } + else { + return nil + } + i += 1 + } + return array + } + return nil + } + + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + switch object { + case let _1 as SecretApi66.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi66.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } + break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi66.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi66.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi66.DecryptedMessageMedia?, entities: [SecretApi66.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi66.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId): + if boxed { + buffer.appendInt32(917541342) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi66.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.DecryptedMessageMedia + } + } + var _6: [SecretApi66.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi66.parseVector(reader, elementSignature: 0, elementType: SecretApi66.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi66.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi66.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageAction { + case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) + case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNoop + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi66.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionAcceptKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionAcceptKey(exchangeId: _1!, gB: _2!, keyFingerprint: _3!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionCommitKey(exchangeId: _1!, keyFingerprint: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi66.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionNoop + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi66.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi66.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi66.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageLayer { + case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi66.DecryptedMessage) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: SecretApi66.DecryptedMessage? + if let signature = reader.readInt32() { + _5 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.DecryptedMessage + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi66.DecryptedMessageLayer.decryptedMessageLayer(randomBytes: _1!, layer: _2!, inSeqNo: _3!, outSeqNo: _4!, message: _5!) + } + else { + return nil + } + } + } + + public enum DecryptedMessageMedia { + case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) + case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi66.DocumentAttribute], caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi66.PhotoSize, dcId: Int32, attributes: [SecretApi66.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaWebPage(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(2063502050) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi66.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi66.parseVector(reader, elementSignature: 0, elementType: SecretApi66.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaEmpty + } + fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: SecretApi66.PhotoSize? + if let signature = reader.readInt32() { + _6 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.PhotoSize + } + var _7: Int32? + _7 = reader.readInt32() + var _8: [SecretApi66.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi66.parseVector(reader, elementSignature: 0, elementType: SecretApi66.DocumentAttribute.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: _1!, accessHash: _2!, date: _3!, mimeType: _4!, size: _5!, thumb: _6!, dcId: _7!, attributes: _8!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + var _9: String? + _9 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: _1!, thumbW: _2!, thumbH: _3!, w: _4!, h: _5!, size: _6!, key: _7!, iv: _8!, caption: _9!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaVideo(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Int32? + _7 = reader.readInt32() + var _8: Int32? + _8 = reader.readInt32() + var _9: Buffer? + _9 = parseBytes(reader) + var _10: Buffer? + _10 = parseBytes(reader) + var _11: String? + _11 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + let _c10 = _10 != nil + let _c11 = _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaVideo(thumb: _1!, thumbW: _2!, thumbH: _3!, duration: _4!, mimeType: _5!, w: _6!, h: _7!, size: _8!, key: _9!, iv: _10!, caption: _11!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: _1!) + } + else { + return nil + } + } + } + + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi66.InputStickerSet) + case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let flags, let duration, let w, let h): + if boxed { + buffer.appendInt32(250621158) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi66.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi66.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi66.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi66.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi66.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi66.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi66.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi66.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi66.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi66.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi66.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi66.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi66.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi66.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi66.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi66.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi66.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi66.parse(reader, signature: signature) as? SecretApi66.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi66.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi66.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadRoundAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageUploadRoundAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi66.SendMessageAction.sendMessageUploadVideoAction + } + } + +} diff --git a/submodules/TelegramApi/Sources/SecretApiLayer73.swift b/submodules/TelegramApi/Sources/SecretApiLayer73.swift index 9aec5f6b73..5253a5c3c3 100644 --- a/submodules/TelegramApi/Sources/SecretApiLayer73.swift +++ b/submodules/TelegramApi/Sources/SecretApiLayer73.swift @@ -5,55 +5,70 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[570911930] = { return $0.readInt64() } dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } - dict[-1586283796] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } - dict[206520510] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } - dict[1700872964] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } - dict[-1967000459] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } - dict[1729750108] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } - dict[-217806717] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } - dict[1360072880] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } - dict[-204906213] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } - dict[1877046107] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } - dict[-586814357] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } - dict[-332526693] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } - dict[-1473258141] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } - dict[236446268] = { return SecretApi73.PhotoSize.parse_photoSizeEmpty($0) } - dict[2009052699] = { return SecretApi73.PhotoSize.parse_photoSize($0) } - dict[-374917894] = { return SecretApi73.PhotoSize.parse_photoCachedSize($0) } - dict[2086234950] = { return SecretApi73.FileLocation.parse_fileLocationUnavailable($0) } - dict[1406570614] = { return SecretApi73.FileLocation.parse_fileLocation($0) } - dict[467867529] = { return SecretApi73.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } - dict[1930838368] = { return SecretApi73.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-1132882121] = { return SecretApi73.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi73.Bool.parse_boolTrue($0) } dict[-1848883596] = { return SecretApi73.DecryptedMessage.parse_decryptedMessage($0) } - dict[1815593308] = { return SecretApi73.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[1930838368] = { return SecretApi73.DecryptedMessage.parse_decryptedMessageService($0) } + dict[-586814357] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionAbortKey($0) } + dict[1877046107] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionAcceptKey($0) } + dict[-332526693] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionCommitKey($0) } + dict[1700872964] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } + dict[1729750108] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } + dict[-1473258141] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionNoop($0) } + dict[-217806717] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-204906213] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionRequestKey($0) } + dict[1360072880] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionResend($0) } + dict[-1967000459] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[-860719551] = { return SecretApi73.DecryptedMessageAction.parse_decryptedMessageActionTyping($0) } + dict[467867529] = { return SecretApi73.DecryptedMessageLayer.parse_decryptedMessageLayer($0) } + dict[1474341323] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[2063502050] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[-90853155] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } + dict[893913689] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[-235238024] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[-1978796689] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } + dict[-1760785394] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } + dict[-452652584] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } dict[297109817] = { return SecretApi73.DocumentAttribute.parse_documentAttributeAnimated($0) } - dict[358154344] = { return SecretApi73.DocumentAttribute.parse_documentAttributeFilename($0) } - dict[978674434] = { return SecretApi73.DocumentAttribute.parse_documentAttributeSticker($0) } dict[-1739392570] = { return SecretApi73.DocumentAttribute.parse_documentAttributeAudio($0) } + dict[358154344] = { return SecretApi73.DocumentAttribute.parse_documentAttributeFilename($0) } + dict[1815593308] = { return SecretApi73.DocumentAttribute.parse_documentAttributeImageSize($0) } + dict[978674434] = { return SecretApi73.DocumentAttribute.parse_documentAttributeSticker($0) } dict[250621158] = { return SecretApi73.DocumentAttribute.parse_documentAttributeVideo($0) } - dict[-2044933984] = { return SecretApi73.InputStickerSet.parse_inputStickerSetShortName($0) } + dict[1406570614] = { return SecretApi73.FileLocation.parse_fileLocation($0) } + dict[2086234950] = { return SecretApi73.FileLocation.parse_fileLocationUnavailable($0) } dict[-4838507] = { return SecretApi73.InputStickerSet.parse_inputStickerSetEmpty($0) } - dict[-1148011883] = { return SecretApi73.MessageEntity.parse_messageEntityUnknown($0) } - dict[-100378723] = { return SecretApi73.MessageEntity.parse_messageEntityMention($0) } - dict[1868782349] = { return SecretApi73.MessageEntity.parse_messageEntityHashtag($0) } - dict[1827637959] = { return SecretApi73.MessageEntity.parse_messageEntityBotCommand($0) } - dict[1859134776] = { return SecretApi73.MessageEntity.parse_messageEntityUrl($0) } - dict[1692693954] = { return SecretApi73.MessageEntity.parse_messageEntityEmail($0) } + dict[-2044933984] = { return SecretApi73.InputStickerSet.parse_inputStickerSetShortName($0) } dict[-1117713463] = { return SecretApi73.MessageEntity.parse_messageEntityBold($0) } - dict[-2106619040] = { return SecretApi73.MessageEntity.parse_messageEntityItalic($0) } + dict[1827637959] = { return SecretApi73.MessageEntity.parse_messageEntityBotCommand($0) } dict[681706865] = { return SecretApi73.MessageEntity.parse_messageEntityCode($0) } + dict[1692693954] = { return SecretApi73.MessageEntity.parse_messageEntityEmail($0) } + dict[1868782349] = { return SecretApi73.MessageEntity.parse_messageEntityHashtag($0) } + dict[-2106619040] = { return SecretApi73.MessageEntity.parse_messageEntityItalic($0) } + dict[-100378723] = { return SecretApi73.MessageEntity.parse_messageEntityMention($0) } dict[1938967520] = { return SecretApi73.MessageEntity.parse_messageEntityPre($0) } dict[1990644519] = { return SecretApi73.MessageEntity.parse_messageEntityTextUrl($0) } - dict[144661578] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } - dict[893913689] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } - dict[1485441687] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } - dict[1474341323] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } - dict[-90853155] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaExternalDocument($0) } - dict[-235238024] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } - dict[2063502050] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } - dict[-1760785394] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } - dict[-1978796689] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaVenue($0) } - dict[-452652584] = { return SecretApi73.DecryptedMessageMedia.parse_decryptedMessageMediaWebPage($0) } + dict[-1148011883] = { return SecretApi73.MessageEntity.parse_messageEntityUnknown($0) } + dict[1859134776] = { return SecretApi73.MessageEntity.parse_messageEntityUrl($0) } + dict[-374917894] = { return SecretApi73.PhotoSize.parse_photoCachedSize($0) } + dict[2009052699] = { return SecretApi73.PhotoSize.parse_photoSize($0) } + dict[236446268] = { return SecretApi73.PhotoSize.parse_photoSizeEmpty($0) } + dict[-44119819] = { return SecretApi73.SendMessageAction.parse_sendMessageCancelAction($0) } + dict[1653390447] = { return SecretApi73.SendMessageAction.parse_sendMessageChooseContactAction($0) } + dict[393186209] = { return SecretApi73.SendMessageAction.parse_sendMessageGeoLocationAction($0) } + dict[-718310409] = { return SecretApi73.SendMessageAction.parse_sendMessageRecordAudioAction($0) } + dict[-1997373508] = { return SecretApi73.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1584933265] = { return SecretApi73.SendMessageAction.parse_sendMessageRecordVideoAction($0) } + dict[381645902] = { return SecretApi73.SendMessageAction.parse_sendMessageTypingAction($0) } + dict[-424899985] = { return SecretApi73.SendMessageAction.parse_sendMessageUploadAudioAction($0) } + dict[-1884362354] = { return SecretApi73.SendMessageAction.parse_sendMessageUploadDocumentAction($0) } + dict[-1727382502] = { return SecretApi73.SendMessageAction.parse_sendMessageUploadPhotoAction($0) } + dict[-1150187996] = { return SecretApi73.SendMessageAction.parse_sendMessageUploadRoundAction($0) } + dict[-1845219337] = { return SecretApi73.SendMessageAction.parse_sendMessageUploadVideoAction($0) } return dict }() @@ -65,18 +80,18 @@ public struct SecretApi73 { } return nil } - - fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { - if let parser = parsers[signature] { - return parser(reader) - } - else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") - return nil - } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) } - - fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -102,226 +117,293 @@ public struct SecretApi73 { } return nil } - + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { - case let _1 as SecretApi73.DecryptedMessageAction: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.PhotoSize: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.FileLocation: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.DecryptedMessageLayer: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.DecryptedMessage: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.DocumentAttribute: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.InputStickerSet: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.MessageEntity: - _1.serialize(buffer, boxed) - case let _1 as SecretApi73.DecryptedMessageMedia: - _1.serialize(buffer, boxed) - default: + case let _1 as SecretApi73.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.DecryptedMessageLayer: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.DocumentAttribute: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.FileLocation: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.InputStickerSet: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.MessageEntity: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.PhotoSize: + _1.serialize(buffer, boxed) + case let _1 as SecretApi73.SendMessageAction: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi73.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi73.Bool.boolTrue + } + } + + public enum DecryptedMessage { + case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi73.DecryptedMessageMedia?, entities: [SecretApi73.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) + case decryptedMessageService(randomId: Int64, action: SecretApi73.DecryptedMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): + if boxed { + buffer.appendInt32(-1848883596) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeInt32(ttl, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 9) != 0 { + media!.serialize(buffer, true) + } + if Int(flags) & Int(1 << 7) != 0 { + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(entities!.count)) + for item in entities! { + item.serialize(buffer, true) + } + } + if Int(flags) & Int(1 << 11) != 0 { + serializeString(viaBotName!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 3) != 0 { + serializeInt64(replyToRandomId!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 17) != 0 { + serializeInt64(groupedId!, buffer: buffer, boxed: false) + } + break + case .decryptedMessageService(let randomId, let action): + if boxed { + buffer.appendInt32(1930838368) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: SecretApi73.DecryptedMessageMedia? + if Int(_1 ?? 0) & Int(1 << 9) != 0 { + if let signature = reader.readInt32() { + _5 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.DecryptedMessageMedia + } + } + var _6: [SecretApi73.MessageEntity]? + if Int(_1 ?? 0) & Int(1 << 7) != 0 { + if let _ = reader.readInt32() { + _6 = SecretApi73.parseVector(reader, elementSignature: 0, elementType: SecretApi73.MessageEntity.self) + } + } + var _7: String? + if Int(_1 ?? 0) & Int(1 << 11) != 0 { + _7 = parseString(reader) + } + var _8: Int64? + if Int(_1 ?? 0) & Int(1 << 3) != 0 { + _8 = reader.readInt64() + } + var _9: Int64? + if Int(_1 ?? 0) & Int(1 << 17) != 0 { + _9 = reader.readInt64() + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 9) == 0) || _5 != nil + let _c6 = (Int(_1 ?? 0) & Int(1 << 7) == 0) || _6 != nil + let _c7 = (Int(_1 ?? 0) & Int(1 << 11) == 0) || _7 != nil + let _c8 = (Int(_1 ?? 0) & Int(1 << 3) == 0) || _8 != nil + let _c9 = (Int(_1 ?? 0) & Int(1 << 17) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi73.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { + var _1: Int64? + _1 = reader.readInt64() + var _2: SecretApi73.DecryptedMessageAction? + if let signature = reader.readInt32() { + _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.DecryptedMessageAction + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) + } + else { + return nil + } } } public enum DecryptedMessageAction { - case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) - case decryptedMessageActionReadMessages(randomIds: [Int64]) - case decryptedMessageActionDeleteMessages(randomIds: [Int64]) - case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) - case decryptedMessageActionFlushHistory - case decryptedMessageActionNotifyLayer(layer: Int32) - case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) - case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) - case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionAbortKey(exchangeId: Int64) + case decryptedMessageActionAcceptKey(exchangeId: Int64, gB: Buffer, keyFingerprint: Int64) case decryptedMessageActionCommitKey(exchangeId: Int64, keyFingerprint: Int64) + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory case decryptedMessageActionNoop - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageActionSetMessageTTL(let ttlSeconds): - if boxed { - buffer.appendInt32(-1586283796) - } - serializeInt32(ttlSeconds, buffer: buffer, boxed: false) - break - case .decryptedMessageActionReadMessages(let randomIds): - if boxed { - buffer.appendInt32(206520510) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionDeleteMessages(let randomIds): - if boxed { - buffer.appendInt32(1700872964) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionScreenshotMessages(let randomIds): - if boxed { - buffer.appendInt32(-1967000459) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionFlushHistory: - if boxed { - buffer.appendInt32(1729750108) - } - - break - case .decryptedMessageActionNotifyLayer(let layer): - if boxed { - buffer.appendInt32(-217806717) - } - serializeInt32(layer, buffer: buffer, boxed: false) - break - case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): - if boxed { - buffer.appendInt32(1360072880) - } - serializeInt32(startSeqNo, buffer: buffer, boxed: false) - serializeInt32(endSeqNo, buffer: buffer, boxed: false) - break - case .decryptedMessageActionRequestKey(let exchangeId, let gA): - if boxed { - buffer.appendInt32(-204906213) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gA, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): - if boxed { - buffer.appendInt32(1877046107) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeBytes(gB, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionAbortKey(let exchangeId): - if boxed { - buffer.appendInt32(-586814357) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - break - case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): - if boxed { - buffer.appendInt32(-332526693) - } - serializeInt64(exchangeId, buffer: buffer, boxed: false) - serializeInt64(keyFingerprint, buffer: buffer, boxed: false) - break - case .decryptedMessageActionNoop: - if boxed { - buffer.appendInt32(-1473258141) - } - - break - } - } - fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) - } - else { - return nil + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionRequestKey(exchangeId: Int64, gA: Buffer) + case decryptedMessageActionResend(startSeqNo: Int32, endSeqNo: Int32) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + case decryptedMessageActionTyping(action: SecretApi73.SendMessageAction) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionAbortKey(let exchangeId): + if boxed { + buffer.appendInt32(-586814357) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + break + case .decryptedMessageActionAcceptKey(let exchangeId, let gB, let keyFingerprint): + if boxed { + buffer.appendInt32(1877046107) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gB, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionCommitKey(let exchangeId, let keyFingerprint): + if boxed { + buffer.appendInt32(-332526693) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + break + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNoop: + if boxed { + buffer.appendInt32(-1473258141) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionRequestKey(let exchangeId, let gA): + if boxed { + buffer.appendInt32(-204906213) + } + serializeInt64(exchangeId, buffer: buffer, boxed: false) + serializeBytes(gA, buffer: buffer, boxed: false) + break + case .decryptedMessageActionResend(let startSeqNo, let endSeqNo): + if boxed { + buffer.appendInt32(1360072880) + } + serializeInt32(startSeqNo, buffer: buffer, boxed: false) + serializeInt32(endSeqNo, buffer: buffer, boxed: false) + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + case .decryptedMessageActionTyping(let action): + if boxed { + buffer.appendInt32(-860719551) + } + action.serialize(buffer, true) + break } } - fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionFlushHistory - } - fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + + fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() - var _2: Buffer? - _2 = parseBytes(reader) let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) } else { return nil @@ -344,17 +426,6 @@ public struct SecretApi73 { return nil } } - fileprivate static func parse_decryptedMessageActionAbortKey(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int64? - _1 = reader.readInt64() - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DecryptedMessageAction.decryptedMessageActionAbortKey(exchangeId: _1!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageActionCommitKey(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int64? _1 = reader.readInt64() @@ -369,196 +440,134 @@ public struct SecretApi73 { return nil } } + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionFlushHistory + } fileprivate static func parse_decryptedMessageActionNoop(_ reader: BufferReader) -> DecryptedMessageAction? { return SecretApi73.DecryptedMessageAction.decryptedMessageActionNoop } - - - } - - public enum PhotoSize { - case photoSizeEmpty(type: String) - case photoSize(type: String, location: SecretApi73.FileLocation, w: Int32, h: Int32, size: Int32) - case photoCachedSize(type: String, location: SecretApi73.FileLocation, w: Int32, h: Int32, bytes: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .photoSizeEmpty(let type): - if boxed { - buffer.appendInt32(236446268) - } - serializeString(type, buffer: buffer, boxed: false) - break - case .photoSize(let type, let location, let w, let h, let size): - if boxed { - buffer.appendInt32(2009052699) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - break - case .photoCachedSize(let type, let location, let w, let h, let bytes): - if boxed { - buffer.appendInt32(-374917894) - } - serializeString(type, buffer: buffer, boxed: false) - location.serialize(buffer, true) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeBytes(bytes, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi73.PhotoSize.photoSizeEmpty(type: _1!) - } - else { - return nil - } - } - fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi73.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Int32? - _5 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi73.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) - } - else { - return nil - } - } - fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi73.FileLocation? - if let signature = reader.readInt32() { - _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.FileLocation - } - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - var _5: Buffer? - _5 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi73.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) - } - else { - return nil - } - } - - - } - - public enum FileLocation { - case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) - case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .fileLocationUnavailable(let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(2086234950) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - case .fileLocation(let dcId, let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(1406570614) - } - serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi73.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) - } - else { - return nil - } - } - fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { var _1: Int32? _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int64? - _4 = reader.readInt64() let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi73.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionRequestKey(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Buffer? + _2 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionRequestKey(exchangeId: _1!, gA: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionResend(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionResend(startSeqNo: _1!, endSeqNo: _2!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi73.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionTyping(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: SecretApi73.SendMessageAction? + if let signature = reader.readInt32() { + _1 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.SendMessageAction + } + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DecryptedMessageAction.decryptedMessageActionTyping(action: _1!) } else { return nil } } - - } public enum DecryptedMessageLayer { case decryptedMessageLayer(randomBytes: Buffer, layer: Int32, inSeqNo: Int32, outSeqNo: Int32, message: SecretApi73.DecryptedMessage) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): - if boxed { - buffer.appendInt32(467867529) - } - serializeBytes(randomBytes, buffer: buffer, boxed: false) - serializeInt32(layer, buffer: buffer, boxed: false) - serializeInt32(inSeqNo, buffer: buffer, boxed: false) - serializeInt32(outSeqNo, buffer: buffer, boxed: false) - message.serialize(buffer, true) - break - } - } + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageLayer(let randomBytes, let layer, let inSeqNo, let outSeqNo, let message): + if boxed { + buffer.appendInt32(467867529) + } + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeInt32(layer, buffer: buffer, boxed: false) + serializeInt32(inSeqNo, buffer: buffer, boxed: false) + serializeInt32(outSeqNo, buffer: buffer, boxed: false) + message.serialize(buffer, true) + break + } + } + fileprivate static func parse_decryptedMessageLayer(_ reader: BufferReader) -> DecryptedMessageLayer? { var _1: Buffer? _1 = parseBytes(reader) @@ -584,690 +593,156 @@ public struct SecretApi73 { return nil } } - - - } - - public enum DecryptedMessage { - case decryptedMessageService(randomId: Int64, action: SecretApi73.DecryptedMessageAction) - case decryptedMessage(flags: Int32, randomId: Int64, ttl: Int32, message: String, media: SecretApi73.DecryptedMessageMedia?, entities: [SecretApi73.MessageEntity]?, viaBotName: String?, replyToRandomId: Int64?, groupedId: Int64?) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageService(let randomId, let action): - if boxed { - buffer.appendInt32(1930838368) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - case .decryptedMessage(let flags, let randomId, let ttl, let message, let media, let entities, let viaBotName, let replyToRandomId, let groupedId): - if boxed { - buffer.appendInt32(-1848883596) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeInt32(ttl, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 9) != 0 {media!.serialize(buffer, true)} - if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(entities!.count)) - for item in entities! { - item.serialize(buffer, true) - }} - if Int(flags) & Int(1 << 11) != 0 {serializeString(viaBotName!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(replyToRandomId!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 17) != 0 {serializeInt64(groupedId!, buffer: buffer, boxed: false)} - break - } - } - fileprivate static func parse_decryptedMessageService(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int64? - _1 = reader.readInt64() - var _2: SecretApi73.DecryptedMessageAction? - if let signature = reader.readInt32() { - _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.DecryptedMessageAction - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DecryptedMessage.decryptedMessageService(randomId: _1!, action: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: SecretApi73.DecryptedMessageMedia? - if Int(_1!) & Int(1 << 9) != 0 {if let signature = reader.readInt32() { - _5 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.DecryptedMessageMedia - } } - var _6: [SecretApi73.MessageEntity]? - if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() { - _6 = SecretApi73.parseVector(reader, elementSignature: 0, elementType: SecretApi73.MessageEntity.self) - } } - var _7: String? - if Int(_1!) & Int(1 << 11) != 0 {_7 = parseString(reader) } - var _8: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } - var _9: Int64? - if Int(_1!) & Int(1 << 17) != 0 {_9 = reader.readInt64() } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 9) == 0) || _5 != nil - let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil - let _c7 = (Int(_1!) & Int(1 << 11) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 17) == 0) || _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi73.DecryptedMessage.decryptedMessage(flags: _1!, randomId: _2!, ttl: _3!, message: _4!, media: _5, entities: _6, viaBotName: _7, replyToRandomId: _8, groupedId: _9) - } - else { - return nil - } - } - - - } - - public enum DocumentAttribute { - case documentAttributeImageSize(w: Int32, h: Int32) - case documentAttributeAnimated - case documentAttributeFilename(fileName: String) - case documentAttributeSticker(alt: String, stickerset: SecretApi73.InputStickerSet) - case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) - case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .documentAttributeImageSize(let w, let h): - if boxed { - buffer.appendInt32(1815593308) - } - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - case .documentAttributeAnimated: - if boxed { - buffer.appendInt32(297109817) - } - - break - case .documentAttributeFilename(let fileName): - if boxed { - buffer.appendInt32(358154344) - } - serializeString(fileName, buffer: buffer, boxed: false) - break - case .documentAttributeSticker(let alt, let stickerset): - if boxed { - buffer.appendInt32(978674434) - } - serializeString(alt, buffer: buffer, boxed: false) - stickerset.serialize(buffer, true) - break - case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): - if boxed { - buffer.appendInt32(-1739392570) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 1) != 0 {serializeString(performer!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 2) != 0 {serializeBytes(waveform!, buffer: buffer, boxed: false)} - break - case .documentAttributeVideo(let flags, let duration, let w, let h): - if boxed { - buffer.appendInt32(250621158) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { - return SecretApi73.DocumentAttribute.documentAttributeAnimated - } - fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi73.DocumentAttribute.documentAttributeFilename(fileName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { - var _1: String? - _1 = parseString(reader) - var _2: SecretApi73.InputStickerSet? - if let signature = reader.readInt32() { - _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.InputStickerSet - } - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } - var _4: String? - if Int(_1!) & Int(1 << 1) != 0 {_4 = parseString(reader) } - var _5: Buffer? - if Int(_1!) & Int(1 << 2) != 0 {_5 = parseBytes(reader) } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi73.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) - } - else { - return nil - } - } - fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi73.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) - } - else { - return nil - } - } - - - } - - public enum InputStickerSet { - case inputStickerSetShortName(shortName: String) - case inputStickerSetEmpty - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputStickerSetShortName(let shortName): - if boxed { - buffer.appendInt32(-2044933984) - } - serializeString(shortName, buffer: buffer, boxed: false) - break - case .inputStickerSetEmpty: - if boxed { - buffer.appendInt32(-4838507) - } - - break - } - } - fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return SecretApi73.InputStickerSet.inputStickerSetShortName(shortName: _1!) - } - else { - return nil - } - } - fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { - return SecretApi73.InputStickerSet.inputStickerSetEmpty - } - - - } - - public enum MessageEntity { - case messageEntityUnknown(offset: Int32, length: Int32) - case messageEntityMention(offset: Int32, length: Int32) - case messageEntityHashtag(offset: Int32, length: Int32) - case messageEntityBotCommand(offset: Int32, length: Int32) - case messageEntityUrl(offset: Int32, length: Int32) - case messageEntityEmail(offset: Int32, length: Int32) - case messageEntityBold(offset: Int32, length: Int32) - case messageEntityItalic(offset: Int32, length: Int32) - case messageEntityCode(offset: Int32, length: Int32) - case messageEntityPre(offset: Int32, length: Int32, language: String) - case messageEntityTextUrl(offset: Int32, length: Int32, url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .messageEntityUnknown(let offset, let length): - if boxed { - buffer.appendInt32(-1148011883) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityMention(let offset, let length): - if boxed { - buffer.appendInt32(-100378723) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityHashtag(let offset, let length): - if boxed { - buffer.appendInt32(1868782349) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBotCommand(let offset, let length): - if boxed { - buffer.appendInt32(1827637959) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityUrl(let offset, let length): - if boxed { - buffer.appendInt32(1859134776) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityEmail(let offset, let length): - if boxed { - buffer.appendInt32(1692693954) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityBold(let offset, let length): - if boxed { - buffer.appendInt32(-1117713463) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityItalic(let offset, let length): - if boxed { - buffer.appendInt32(-2106619040) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityCode(let offset, let length): - if boxed { - buffer.appendInt32(681706865) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - break - case .messageEntityPre(let offset, let length, let language): - if boxed { - buffer.appendInt32(1938967520) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(language, buffer: buffer, boxed: false) - break - case .messageEntityTextUrl(let offset, let length, let url): - if boxed { - buffer.appendInt32(1990644519) - } - serializeInt32(offset, buffer: buffer, boxed: false) - serializeInt32(length, buffer: buffer, boxed: false) - serializeString(url, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityMention(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityBold(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.MessageEntity.messageEntityCode(offset: _1!, length: _2!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi73.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) - } - else { - return nil - } - } - fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return SecretApi73.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) - } - else { - return nil - } - } - - } public enum DecryptedMessageMedia { - case decryptedMessageMediaEmpty - case decryptedMessageMediaGeoPoint(lat: Double, long: Double) - case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaAudio(duration: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi73.PhotoSize, dcId: Int32, attributes: [SecretApi73.DocumentAttribute]) - case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, mimeType: String, size: Int32, key: Buffer, iv: Buffer, attributes: [SecretApi73.DocumentAttribute], caption: String) - case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) + case decryptedMessageMediaEmpty + case decryptedMessageMediaExternalDocument(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: SecretApi73.PhotoSize, dcId: Int32, attributes: [SecretApi73.DocumentAttribute]) + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaVenue(lat: Double, long: Double, title: String, address: String, provider: String, venueId: String) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, mimeType: String, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer, caption: String) case decryptedMessageMediaWebPage(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageMediaEmpty: - if boxed { - buffer.appendInt32(144661578) - } - - break - case .decryptedMessageMediaGeoPoint(let lat, let long): - if boxed { - buffer.appendInt32(893913689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): - if boxed { - buffer.appendInt32(1485441687) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): - if boxed { - buffer.appendInt32(1474341323) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): - if boxed { - buffer.appendInt32(-90853155) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - thumb.serialize(buffer, true) - serializeInt32(dcId, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - break - case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-235238024) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): - if boxed { - buffer.appendInt32(2063502050) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(attributes.count)) - for item in attributes { - item.serialize(buffer, true) - } - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): - if boxed { - buffer.appendInt32(-1760785394) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - serializeString(caption, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): - if boxed { - buffer.appendInt32(-1978796689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(address, buffer: buffer, boxed: false) - serializeString(provider, buffer: buffer, boxed: false) - serializeString(venueId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaWebPage(let url): - if boxed { - buffer.appendInt32(-452652584) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { - return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaEmpty + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(1474341323) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let mimeType, let size, let key, let iv, let attributes, let caption): + if boxed { + buffer.appendInt32(2063502050) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaExternalDocument(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let attributes): + if boxed { + buffer.appendInt32(-90853155) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + thumb.serialize(buffer, true) + serializeInt32(dcId, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(attributes.count)) + for item in attributes { + item.serialize(buffer, true) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-235238024) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVenue(let lat, let long, let title, let address, let provider, let venueId): + if boxed { + buffer.appendInt32(-1978796689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(address, buffer: buffer, boxed: false) + serializeString(provider, buffer: buffer, boxed: false) + serializeString(venueId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let mimeType, let w, let h, let size, let key, let iv, let caption): + if boxed { + buffer.appendInt32(-1760785394) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + serializeString(caption, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaWebPage(let url): + if boxed { + buffer.appendInt32(-452652584) + } + serializeString(url, buffer: buffer, boxed: false) + break + } } - fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Int32? + _3 = reader.readInt32() + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Buffer? + _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) } else { return nil @@ -1293,29 +768,46 @@ public struct SecretApi73 { return nil } } - fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: String? - _2 = parseString(reader) + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() var _3: Int32? _3 = reader.readInt32() - var _4: Buffer? - _4 = parseBytes(reader) - var _5: Buffer? - _5 = parseBytes(reader) + var _4: String? + _4 = parseString(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: Buffer? + _6 = parseBytes(reader) + var _7: Buffer? + _7 = parseBytes(reader) + var _8: [SecretApi73.DocumentAttribute]? + if let _ = reader.readInt32() { + _8 = SecretApi73.parseVector(reader, elementSignature: 0, elementType: SecretApi73.DocumentAttribute.self) + } + var _9: String? + _9 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, mimeType: _2!, size: _3!, key: _4!, iv: _5!) + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) } else { return nil } } + fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { + return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaEmpty + } fileprivate static func parse_decryptedMessageMediaExternalDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Int64? _1 = reader.readInt64() @@ -1352,6 +844,20 @@ public struct SecretApi73 { return nil } } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Buffer? _1 = parseBytes(reader) @@ -1387,38 +893,27 @@ public struct SecretApi73 { return nil } } - fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() + fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + var _3: String? + _3 = parseString(reader) var _4: String? _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Buffer? - _6 = parseBytes(reader) - var _7: Buffer? - _7 = parseBytes(reader) - var _8: [SecretApi73.DocumentAttribute]? - if let _ = reader.readInt32() { - _8 = SecretApi73.parseVector(reader, elementSignature: 0, elementType: SecretApi73.DocumentAttribute.self) - } - var _9: String? - _9 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - let _c9 = _9 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, mimeType: _4!, size: _5!, key: _6!, iv: _7!, attributes: _8!, caption: _9!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) } else { return nil @@ -1465,32 +960,6 @@ public struct SecretApi73 { return nil } } - fileprivate static func parse_decryptedMessageMediaVenue(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return SecretApi73.DecryptedMessageMedia.decryptedMessageMediaVenue(lat: _1!, long: _2!, title: _3!, address: _4!, provider: _5!, venueId: _6!) - } - else { - return nil - } - } fileprivate static func parse_decryptedMessageMediaWebPage(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: String? _1 = parseString(reader) @@ -1502,12 +971,740 @@ public struct SecretApi73 { return nil } } - - } - public struct functions { - + public enum DocumentAttribute { + case documentAttributeAnimated + case documentAttributeAudio(flags: Int32, duration: Int32, title: String?, performer: String?, waveform: Buffer?) + case documentAttributeFilename(fileName: String) + case documentAttributeImageSize(w: Int32, h: Int32) + case documentAttributeSticker(alt: String, stickerset: SecretApi73.InputStickerSet) + case documentAttributeVideo(flags: Int32, duration: Int32, w: Int32, h: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .documentAttributeAnimated: + if boxed { + buffer.appendInt32(297109817) + } + break + case .documentAttributeAudio(let flags, let duration, let title, let performer, let waveform): + if boxed { + buffer.appendInt32(-1739392570) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 { + serializeString(title!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 1) != 0 { + serializeString(performer!, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 2) != 0 { + serializeBytes(waveform!, buffer: buffer, boxed: false) + } + break + case .documentAttributeFilename(let fileName): + if boxed { + buffer.appendInt32(358154344) + } + serializeString(fileName, buffer: buffer, boxed: false) + break + case .documentAttributeImageSize(let w, let h): + if boxed { + buffer.appendInt32(1815593308) + } + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + case .documentAttributeSticker(let alt, let stickerset): + if boxed { + buffer.appendInt32(978674434) + } + serializeString(alt, buffer: buffer, boxed: false) + stickerset.serialize(buffer, true) + break + case .documentAttributeVideo(let flags, let duration, let w, let h): + if boxed { + buffer.appendInt32(250621158) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + return SecretApi73.DocumentAttribute.documentAttributeAnimated + } + fileprivate static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + if Int(_1 ?? 0) & Int(1 << 0) != 0 { + _3 = parseString(reader) + } + var _4: String? + if Int(_1 ?? 0) & Int(1 << 1) != 0 { + _4 = parseString(reader) + } + var _5: Buffer? + if Int(_1 ?? 0) & Int(1 << 2) != 0 { + _5 = parseBytes(reader) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1 ?? 0) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1 ?? 0) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1 ?? 0) & Int(1 << 2) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi73.DocumentAttribute.documentAttributeAudio(flags: _1!, duration: _2!, title: _3, performer: _4, waveform: _5) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi73.DocumentAttribute.documentAttributeFilename(fileName: _1!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DocumentAttribute.documentAttributeImageSize(w: _1!, h: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi73.InputStickerSet? + if let signature = reader.readInt32() { + _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.InputStickerSet + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.DocumentAttribute.documentAttributeSticker(alt: _1!, stickerset: _2!) + } + else { + return nil + } + } + fileprivate static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi73.DocumentAttribute.documentAttributeVideo(flags: _1!, duration: _2!, w: _3!, h: _4!) + } + else { + return nil + } + } + } + + public enum FileLocation { + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .fileLocation(let dcId, let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(1406570614) + } + serializeInt32(dcId, buffer: buffer, boxed: false) + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + case .fileLocationUnavailable(let volumeId, let localId, let secret): + if boxed { + buffer.appendInt32(2086234950) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_fileLocation(_ reader: BufferReader) -> FileLocation? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + _4 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi73.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + } + else { + return nil + } + } + fileprivate static func parse_fileLocationUnavailable(_ reader: BufferReader) -> FileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi73.FileLocation.fileLocationUnavailable(volumeId: _1!, localId: _2!, secret: _3!) + } + else { + return nil + } + } + } + + public enum InputStickerSet { + case inputStickerSetEmpty + case inputStickerSetShortName(shortName: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputStickerSetEmpty: + if boxed { + buffer.appendInt32(-4838507) + } + break + case .inputStickerSetShortName(let shortName): + if boxed { + buffer.appendInt32(-2044933984) + } + serializeString(shortName, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + return SecretApi73.InputStickerSet.inputStickerSetEmpty + } + fileprivate static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi73.InputStickerSet.inputStickerSetShortName(shortName: _1!) + } + else { + return nil + } + } + } + + public enum MessageEntity { + case messageEntityBold(offset: Int32, length: Int32) + case messageEntityBotCommand(offset: Int32, length: Int32) + case messageEntityCode(offset: Int32, length: Int32) + case messageEntityEmail(offset: Int32, length: Int32) + case messageEntityHashtag(offset: Int32, length: Int32) + case messageEntityItalic(offset: Int32, length: Int32) + case messageEntityMention(offset: Int32, length: Int32) + case messageEntityPre(offset: Int32, length: Int32, language: String) + case messageEntityTextUrl(offset: Int32, length: Int32, url: String) + case messageEntityUnknown(offset: Int32, length: Int32) + case messageEntityUrl(offset: Int32, length: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .messageEntityBold(let offset, let length): + if boxed { + buffer.appendInt32(-1117713463) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityBotCommand(let offset, let length): + if boxed { + buffer.appendInt32(1827637959) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityCode(let offset, let length): + if boxed { + buffer.appendInt32(681706865) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityEmail(let offset, let length): + if boxed { + buffer.appendInt32(1692693954) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityHashtag(let offset, let length): + if boxed { + buffer.appendInt32(1868782349) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityItalic(let offset, let length): + if boxed { + buffer.appendInt32(-2106619040) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityMention(let offset, let length): + if boxed { + buffer.appendInt32(-100378723) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityPre(let offset, let length, let language): + if boxed { + buffer.appendInt32(1938967520) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(language, buffer: buffer, boxed: false) + break + case .messageEntityTextUrl(let offset, let length, let url): + if boxed { + buffer.appendInt32(1990644519) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + serializeString(url, buffer: buffer, boxed: false) + break + case .messageEntityUnknown(let offset, let length): + if boxed { + buffer.appendInt32(-1148011883) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + case .messageEntityUrl(let offset, let length): + if boxed { + buffer.appendInt32(1859134776) + } + serializeInt32(offset, buffer: buffer, boxed: false) + serializeInt32(length, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_messageEntityBold(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityBold(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityBotCommand(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityBotCommand(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityCode(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityCode(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityEmail(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityEmail(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityHashtag(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityHashtag(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityItalic(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityItalic(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityMention(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityMention(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityPre(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi73.MessageEntity.messageEntityPre(offset: _1!, length: _2!, language: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityTextUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return SecretApi73.MessageEntity.messageEntityTextUrl(offset: _1!, length: _2!, url: _3!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUnknown(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityUnknown(offset: _1!, length: _2!) + } + else { + return nil + } + } + fileprivate static func parse_messageEntityUrl(_ reader: BufferReader) -> MessageEntity? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi73.MessageEntity.messageEntityUrl(offset: _1!, length: _2!) + } + else { + return nil + } + } + } + + public enum PhotoSize { + case photoCachedSize(type: String, location: SecretApi73.FileLocation, w: Int32, h: Int32, bytes: Buffer) + case photoSize(type: String, location: SecretApi73.FileLocation, w: Int32, h: Int32, size: Int32) + case photoSizeEmpty(type: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .photoCachedSize(let type, let location, let w, let h, let bytes): + if boxed { + buffer.appendInt32(-374917894) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeBytes(bytes, buffer: buffer, boxed: false) + break + case .photoSize(let type, let location, let w, let h, let size): + if boxed { + buffer.appendInt32(2009052699) + } + serializeString(type, buffer: buffer, boxed: false) + location.serialize(buffer, true) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + break + case .photoSizeEmpty(let type): + if boxed { + buffer.appendInt32(236446268) + } + serializeString(type, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi73.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Buffer? + _5 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi73.PhotoSize.photoCachedSize(type: _1!, location: _2!, w: _3!, h: _4!, bytes: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + var _2: SecretApi73.FileLocation? + if let signature = reader.readInt32() { + _2 = SecretApi73.parse(reader, signature: signature) as? SecretApi73.FileLocation + } + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + _5 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return SecretApi73.PhotoSize.photoSize(type: _1!, location: _2!, w: _3!, h: _4!, size: _5!) + } + else { + return nil + } + } + fileprivate static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return SecretApi73.PhotoSize.photoSizeEmpty(type: _1!) + } + else { + return nil + } + } + } + + public enum SendMessageAction { + case sendMessageCancelAction + case sendMessageChooseContactAction + case sendMessageGeoLocationAction + case sendMessageRecordAudioAction + case sendMessageRecordRoundAction + case sendMessageRecordVideoAction + case sendMessageTypingAction + case sendMessageUploadAudioAction + case sendMessageUploadDocumentAction + case sendMessageUploadPhotoAction + case sendMessageUploadRoundAction + case sendMessageUploadVideoAction + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .sendMessageCancelAction: + if boxed { + buffer.appendInt32(-44119819) + } + break + case .sendMessageChooseContactAction: + if boxed { + buffer.appendInt32(1653390447) + } + break + case .sendMessageGeoLocationAction: + if boxed { + buffer.appendInt32(393186209) + } + break + case .sendMessageRecordAudioAction: + if boxed { + buffer.appendInt32(-718310409) + } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + break + case .sendMessageRecordVideoAction: + if boxed { + buffer.appendInt32(-1584933265) + } + break + case .sendMessageTypingAction: + if boxed { + buffer.appendInt32(381645902) + } + break + case .sendMessageUploadAudioAction: + if boxed { + buffer.appendInt32(-424899985) + } + break + case .sendMessageUploadDocumentAction: + if boxed { + buffer.appendInt32(-1884362354) + } + break + case .sendMessageUploadPhotoAction: + if boxed { + buffer.appendInt32(-1727382502) + } + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break + case .sendMessageUploadVideoAction: + if boxed { + buffer.appendInt32(-1845219337) + } + break + } + } + + fileprivate static func parse_sendMessageCancelAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageCancelAction + } + fileprivate static func parse_sendMessageChooseContactAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageChooseContactAction + } + fileprivate static func parse_sendMessageGeoLocationAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageGeoLocationAction + } + fileprivate static func parse_sendMessageRecordAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageRecordAudioAction + } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageRecordVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageRecordVideoAction + } + fileprivate static func parse_sendMessageTypingAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageTypingAction + } + fileprivate static func parse_sendMessageUploadAudioAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageUploadAudioAction + } + fileprivate static func parse_sendMessageUploadDocumentAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageUploadDocumentAction + } + fileprivate static func parse_sendMessageUploadPhotoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageUploadPhotoAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageUploadRoundAction + } + fileprivate static func parse_sendMessageUploadVideoAction(_ reader: BufferReader) -> SendMessageAction? { + return SecretApi73.SendMessageAction.sendMessageUploadVideoAction + } } } diff --git a/submodules/TelegramApi/Sources/SecretApiLayer8.swift b/submodules/TelegramApi/Sources/SecretApiLayer8.swift index 9d070f35df..1b3acd81fa 100644 --- a/submodules/TelegramApi/Sources/SecretApiLayer8.swift +++ b/submodules/TelegramApi/Sources/SecretApiLayer8.swift @@ -5,21 +5,23 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[570911930] = { return $0.readInt64() } dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } + dict[-1132882121] = { return SecretApi8.Bool.parse_boolFalse($0) } + dict[-1720552011] = { return SecretApi8.Bool.parse_boolTrue($0) } dict[528568095] = { return SecretApi8.DecryptedMessage.parse_decryptedMessage($0) } dict[-1438109059] = { return SecretApi8.DecryptedMessage.parse_decryptedMessageService($0) } - dict[144661578] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } - dict[846826124] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } - dict[1290694387] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } - dict[893913689] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } - dict[1485441687] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } - dict[-1332395189] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } - dict[1619031439] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } - dict[-1586283796] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } - dict[206520510] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } dict[1700872964] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionDeleteMessages($0) } - dict[-1967000459] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } dict[1729750108] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionFlushHistory($0) } dict[-217806717] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionNotifyLayer($0) } + dict[206520510] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionReadMessages($0) } + dict[-1967000459] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionScreenshotMessages($0) } + dict[-1586283796] = { return SecretApi8.DecryptedMessageAction.parse_decryptedMessageActionSetMessageTTL($0) } + dict[1619031439] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaAudio($0) } + dict[1485441687] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaContact($0) } + dict[-1332395189] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaDocument($0) } + dict[144661578] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaEmpty($0) } + dict[893913689] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaGeoPoint($0) } + dict[846826124] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaPhoto($0) } + dict[1290694387] = { return SecretApi8.DecryptedMessageMedia.parse_decryptedMessageMediaVideo($0) } return dict }() @@ -31,18 +33,18 @@ public struct SecretApi8 { } return nil } - - fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { - if let parser = parsers[signature] { - return parser(reader) - } - else { - telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") - return nil - } + + fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + if let parser = parsers[signature] { + return parser(reader) } - - fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + else { + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + return nil + } + } + + fileprivate static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -68,46 +70,75 @@ public struct SecretApi8 { } return nil } - + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { - case let _1 as SecretApi8.DecryptedMessage: - _1.serialize(buffer, boxed) - case let _1 as SecretApi8.DecryptedMessageMedia: - _1.serialize(buffer, boxed) - case let _1 as SecretApi8.DecryptedMessageAction: - _1.serialize(buffer, boxed) - default: + case let _1 as SecretApi8.Bool: + _1.serialize(buffer, boxed) + case let _1 as SecretApi8.DecryptedMessage: + _1.serialize(buffer, boxed) + case let _1 as SecretApi8.DecryptedMessageAction: + _1.serialize(buffer, boxed) + case let _1 as SecretApi8.DecryptedMessageMedia: + _1.serialize(buffer, boxed) + default: + break + } + } + + public enum Bool { + case boolFalse + case boolTrue + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .boolFalse: + if boxed { + buffer.appendInt32(-1132882121) + } break + case .boolTrue: + if boxed { + buffer.appendInt32(-1720552011) + } + break + } + } + + fileprivate static func parse_boolFalse(_ reader: BufferReader) -> Bool? { + return SecretApi8.Bool.boolFalse + } + fileprivate static func parse_boolTrue(_ reader: BufferReader) -> Bool? { + return SecretApi8.Bool.boolTrue } } public enum DecryptedMessage { case decryptedMessage(randomId: Int64, randomBytes: Buffer, message: String, media: SecretApi8.DecryptedMessageMedia) case decryptedMessageService(randomId: Int64, randomBytes: Buffer, action: SecretApi8.DecryptedMessageAction) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessage(let randomId, let randomBytes, let message, let media): - if boxed { - buffer.appendInt32(528568095) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(randomBytes, buffer: buffer, boxed: false) - serializeString(message, buffer: buffer, boxed: false) - media.serialize(buffer, true) - break - case .decryptedMessageService(let randomId, let randomBytes, let action): - if boxed { - buffer.appendInt32(-1438109059) - } - serializeInt64(randomId, buffer: buffer, boxed: false) - serializeBytes(randomBytes, buffer: buffer, boxed: false) - action.serialize(buffer, true) - break - } - } - + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessage(let randomId, let randomBytes, let message, let media): + if boxed { + buffer.appendInt32(528568095) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(randomBytes, buffer: buffer, boxed: false) + serializeString(message, buffer: buffer, boxed: false) + media.serialize(buffer, true) + break + case .decryptedMessageService(let randomId, let randomBytes, let action): + if boxed { + buffer.appendInt32(-1438109059) + } + serializeInt64(randomId, buffer: buffer, boxed: false) + serializeBytes(randomBytes, buffer: buffer, boxed: false) + action.serialize(buffer, true) + break + } + } + fileprivate static func parse_decryptedMessage(_ reader: BufferReader) -> DecryptedMessage? { var _1: Int64? _1 = reader.readInt64() @@ -149,97 +180,307 @@ public struct SecretApi8 { return nil } } - + } + + public enum DecryptedMessageAction { + case decryptedMessageActionDeleteMessages(randomIds: [Int64]) + case decryptedMessageActionFlushHistory + case decryptedMessageActionNotifyLayer(layer: Int32) + case decryptedMessageActionReadMessages(randomIds: [Int64]) + case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) + case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageActionDeleteMessages(let randomIds): + if boxed { + buffer.appendInt32(1700872964) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionFlushHistory: + if boxed { + buffer.appendInt32(1729750108) + } + break + case .decryptedMessageActionNotifyLayer(let layer): + if boxed { + buffer.appendInt32(-217806717) + } + serializeInt32(layer, buffer: buffer, boxed: false) + break + case .decryptedMessageActionReadMessages(let randomIds): + if boxed { + buffer.appendInt32(206520510) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionScreenshotMessages(let randomIds): + if boxed { + buffer.appendInt32(-1967000459) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(randomIds.count)) + for item in randomIds { + serializeInt64(item, buffer: buffer, boxed: false) + } + break + case .decryptedMessageActionSetMessageTTL(let ttlSeconds): + if boxed { + buffer.appendInt32(-1586283796) + } + serializeInt32(ttlSeconds, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionFlushHistory + } + fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: [Int64]? + if let _ = reader.readInt32() { + _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) + } + let _c1 = _1 != nil + if _c1 { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return SecretApi8.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) + } + else { + return nil + } + } } public enum DecryptedMessageMedia { - case decryptedMessageMediaEmpty - case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaAudio(duration: Int32, size: Int32, key: Buffer, iv: Buffer) case decryptedMessageMediaContact(phoneNumber: String, firstName: String, lastName: String, userId: Int32) case decryptedMessageMediaDocument(thumb: Buffer, thumbW: Int32, thumbH: Int32, fileName: String, mimeType: String, size: Int32, key: Buffer, iv: Buffer) - case decryptedMessageMediaAudio(duration: Int32, size: Int32, key: Buffer, iv: Buffer) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageMediaEmpty: - if boxed { - buffer.appendInt32(144661578) - } - - break - case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): - if boxed { - buffer.appendInt32(846826124) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let w, let h, let size, let key, let iv): - if boxed { - buffer.appendInt32(1290694387) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(w, buffer: buffer, boxed: false) - serializeInt32(h, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaGeoPoint(let lat, let long): - if boxed { - buffer.appendInt32(893913689) - } - serializeDouble(lat, buffer: buffer, boxed: false) - serializeDouble(long, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): - if boxed { - buffer.appendInt32(1485441687) - } - serializeString(phoneNumber, buffer: buffer, boxed: false) - serializeString(firstName, buffer: buffer, boxed: false) - serializeString(lastName, buffer: buffer, boxed: false) - serializeInt32(userId, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): - if boxed { - buffer.appendInt32(-1332395189) - } - serializeBytes(thumb, buffer: buffer, boxed: false) - serializeInt32(thumbW, buffer: buffer, boxed: false) - serializeInt32(thumbH, buffer: buffer, boxed: false) - serializeString(fileName, buffer: buffer, boxed: false) - serializeString(mimeType, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - case .decryptedMessageMediaAudio(let duration, let size, let key, let iv): - if boxed { - buffer.appendInt32(1619031439) - } - serializeInt32(duration, buffer: buffer, boxed: false) - serializeInt32(size, buffer: buffer, boxed: false) - serializeBytes(key, buffer: buffer, boxed: false) - serializeBytes(iv, buffer: buffer, boxed: false) - break - } - } - + case decryptedMessageMediaEmpty + case decryptedMessageMediaGeoPoint(lat: Double, long: Double) + case decryptedMessageMediaPhoto(thumb: Buffer, thumbW: Int32, thumbH: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + case decryptedMessageMediaVideo(thumb: Buffer, thumbW: Int32, thumbH: Int32, duration: Int32, w: Int32, h: Int32, size: Int32, key: Buffer, iv: Buffer) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .decryptedMessageMediaAudio(let duration, let size, let key, let iv): + if boxed { + buffer.appendInt32(1619031439) + } + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaContact(let phoneNumber, let firstName, let lastName, let userId): + if boxed { + buffer.appendInt32(1485441687) + } + serializeString(phoneNumber, buffer: buffer, boxed: false) + serializeString(firstName, buffer: buffer, boxed: false) + serializeString(lastName, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaDocument(let thumb, let thumbW, let thumbH, let fileName, let mimeType, let size, let key, let iv): + if boxed { + buffer.appendInt32(-1332395189) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeString(fileName, buffer: buffer, boxed: false) + serializeString(mimeType, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaEmpty: + if boxed { + buffer.appendInt32(144661578) + } + break + case .decryptedMessageMediaGeoPoint(let lat, let long): + if boxed { + buffer.appendInt32(893913689) + } + serializeDouble(lat, buffer: buffer, boxed: false) + serializeDouble(long, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaPhoto(let thumb, let thumbW, let thumbH, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(846826124) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + case .decryptedMessageMediaVideo(let thumb, let thumbW, let thumbH, let duration, let w, let h, let size, let key, let iv): + if boxed { + buffer.appendInt32(1290694387) + } + serializeBytes(thumb, buffer: buffer, boxed: false) + serializeInt32(thumbW, buffer: buffer, boxed: false) + serializeInt32(thumbH, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + serializeInt32(w, buffer: buffer, boxed: false) + serializeInt32(h, buffer: buffer, boxed: false) + serializeInt32(size, buffer: buffer, boxed: false) + serializeBytes(key, buffer: buffer, boxed: false) + serializeBytes(iv, buffer: buffer, boxed: false) + break + } + } + + fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, size: _2!, key: _3!, iv: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) + } + else { + return nil + } + } + fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Buffer? + _1 = parseBytes(reader) + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: String? + _4 = parseString(reader) + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Buffer? + _7 = parseBytes(reader) + var _8: Buffer? + _8 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaEmpty(_ reader: BufferReader) -> DecryptedMessageMedia? { return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaEmpty } + fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { + var _1: Double? + _1 = reader.readDouble() + var _2: Double? + _2 = reader.readDouble() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) + } + else { + return nil + } + } fileprivate static func parse_decryptedMessageMediaPhoto(_ reader: BufferReader) -> DecryptedMessageMedia? { var _1: Buffer? _1 = parseBytes(reader) @@ -307,225 +548,6 @@ public struct SecretApi8 { return nil } } - fileprivate static func parse_decryptedMessageMediaGeoPoint(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Double? - _1 = reader.readDouble() - var _2: Double? - _2 = reader.readDouble() - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaGeoPoint(lat: _1!, long: _2!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageMediaContact(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - var _3: String? - _3 = parseString(reader) - var _4: Int32? - _4 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaContact(phoneNumber: _1!, firstName: _2!, lastName: _3!, userId: _4!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageMediaDocument(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Buffer? - _1 = parseBytes(reader) - var _2: Int32? - _2 = reader.readInt32() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: String? - _5 = parseString(reader) - var _6: Int32? - _6 = reader.readInt32() - var _7: Buffer? - _7 = parseBytes(reader) - var _8: Buffer? - _8 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = _7 != nil - let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: _1!, thumbW: _2!, thumbH: _3!, fileName: _4!, mimeType: _5!, size: _6!, key: _7!, iv: _8!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageMediaAudio(_ reader: BufferReader) -> DecryptedMessageMedia? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Buffer? - _3 = parseBytes(reader) - var _4: Buffer? - _4 = parseBytes(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return SecretApi8.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: _1!, size: _2!, key: _3!, iv: _4!) - } - else { - return nil - } - } - - } - - public enum DecryptedMessageAction { - case decryptedMessageActionSetMessageTTL(ttlSeconds: Int32) - case decryptedMessageActionReadMessages(randomIds: [Int64]) - case decryptedMessageActionDeleteMessages(randomIds: [Int64]) - case decryptedMessageActionScreenshotMessages(randomIds: [Int64]) - case decryptedMessageActionFlushHistory - case decryptedMessageActionNotifyLayer(layer: Int32) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .decryptedMessageActionSetMessageTTL(let ttlSeconds): - if boxed { - buffer.appendInt32(-1586283796) - } - serializeInt32(ttlSeconds, buffer: buffer, boxed: false) - break - case .decryptedMessageActionReadMessages(let randomIds): - if boxed { - buffer.appendInt32(206520510) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionDeleteMessages(let randomIds): - if boxed { - buffer.appendInt32(1700872964) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionScreenshotMessages(let randomIds): - if boxed { - buffer.appendInt32(-1967000459) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(randomIds.count)) - for item in randomIds { - serializeInt64(item, buffer: buffer, boxed: false) - } - break - case .decryptedMessageActionFlushHistory: - if boxed { - buffer.appendInt32(1729750108) - } - - break - case .decryptedMessageActionNotifyLayer(let layer): - if boxed { - buffer.appendInt32(-217806717) - } - serializeInt32(layer, buffer: buffer, boxed: false) - break - } - } - - fileprivate static func parse_decryptedMessageActionSetMessageTTL(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionSetMessageTTL(ttlSeconds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionReadMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionReadMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionDeleteMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionDeleteMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionScreenshotMessages(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: [Int64]? - if let _ = reader.readInt32() { - _1 = SecretApi8.parseVector(reader, elementSignature: 570911930, elementType: Int64.self) - } - let _c1 = _1 != nil - if _c1 { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionScreenshotMessages(randomIds: _1!) - } - else { - return nil - } - } - fileprivate static func parse_decryptedMessageActionFlushHistory(_ reader: BufferReader) -> DecryptedMessageAction? { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionFlushHistory - } - fileprivate static func parse_decryptedMessageActionNotifyLayer(_ reader: BufferReader) -> DecryptedMessageAction? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return SecretApi8.DecryptedMessageAction.decryptedMessageActionNotifyLayer(layer: _1!) - } - else { - return nil - } - } - - } - - public struct functions { - } } diff --git a/submodules/TelegramBaseController/BUILD b/submodules/TelegramBaseController/BUILD index 57b114255f..cc8f0bd82b 100644 --- a/submodules/TelegramBaseController/BUILD +++ b/submodules/TelegramBaseController/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", diff --git a/submodules/TelegramBaseController/Sources/LocationBroadcastActionSheetItem.swift b/submodules/TelegramBaseController/Sources/LocationBroadcastActionSheetItem.swift index 30bdac8506..105be70e18 100644 --- a/submodules/TelegramBaseController/Sources/LocationBroadcastActionSheetItem.swift +++ b/submodules/TelegramBaseController/Sources/LocationBroadcastActionSheetItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AccountContext import LiveLocationTimerNode @@ -11,14 +10,14 @@ import AvatarNode public class LocationBroadcastActionSheetItem: ActionSheetItem { public let context: AccountContext - public let peer: Peer + public let peer: EnginePeer public let title: String public let beginTimestamp: Double public let timeout: Double public let strings: PresentationStrings public let action: () -> Void - public init(context: AccountContext, peer: Peer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) { + public init(context: AccountContext, peer: EnginePeer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) { self.context = context self.peer = peer self.title = title @@ -105,7 +104,7 @@ public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode { let textColor: UIColor = self.theme.primaryTextColor self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: textColor) - self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(item.peer)) + self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: item.peer) self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: Int32(item.timeout) == liveLocationIndefinitePeriod ? -1.0 : item.timeout, strings: item.strings) } diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift index e76f43d4a4..1ffa22114d 100644 --- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -352,6 +352,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.tapRecognizer = tapRecognizer diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 87cad945ee..ca2c1b5fbb 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import UniversalMediaPlayer @@ -14,7 +13,7 @@ import PresentationDataUtils import TelegramCallsUI import UndoUI -private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { +private func presentLiveLocationController(context: AccountContext, peerId: EnginePeer.Id, controller: ViewController) { let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { @@ -189,14 +188,21 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { })] } - open func joinGroupCall(peerId: PeerId, invite: String?, activeCall: EngineGroupCallDescription) { + open func joinGroupCall(peerId: EnginePeer.Id, invite: String?, activeCall: EngineGroupCallDescription) { let context = self.context let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.view.endEditing(true) self.context.joinGroupCall(peerId: peerId, invite: invite, requestJoinAsPeerId: { completion in - let currentAccountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) + let currentAccountPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -217,7 +223,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { return } - let defaultJoinAsPeerId: PeerId? = callJoinAsPeerId + let defaultJoinAsPeerId: EnginePeer.Id? = callJoinAsPeerId if peers.count == 1, let peer = peers.first { completion(peer.peer.id) @@ -233,10 +239,10 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { var items: [ActionSheetItem] = [] var isGroup = false for peer in peers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -248,14 +254,14 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) } } - items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { + items.append(VoiceChatPeerActionSheetItem(context: context, peer: peer.peer, title: peer.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { dismissAction() completion(peer.peer.id) })) diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index c7dd71d3fc..f6df75ee72 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -33,7 +32,7 @@ protocol CallControllerNodeProtocol: AnyObject { func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) func updateCallState(_ callState: PresentationCallState) - func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) + func updatePeer(accountPeer: EnginePeer, peer: EnginePeer, hasOther: Bool) func animateIn() func animateOut(completion: @escaping () -> Void) @@ -63,8 +62,6 @@ public final class CallController: ViewController { private var presentationData: PresentationData private var didPlayPresentationAnimation = false - private var peer: Peer? - private var peerDisposable: Disposable? private var disposable: Disposable? @@ -164,7 +161,6 @@ public final class CallController: ViewController { override public func loadDisplayNode() { let displayNode = CallControllerNodeV2( sharedContext: self.sharedContext, - account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), @@ -317,34 +313,34 @@ public final class CallController: ViewController { } } - self.controllerNode.callEnded = { [weak self] didPresentRating in - if let strongSelf = self, !didPresentRating { - let _ = (combineLatest(strongSelf.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings]), ApplicationSpecificNotice.getCallsTabTip(accountManager: strongSelf.sharedContext.accountManager)) - |> map { sharedData, callsTabTip -> Int32 in - var value = false - if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings]?.get(CallListSettings.self) { - value = settings.showTab - } - if value { - return 3 - } else { - return callsTabTip - } - } |> deliverOnMainQueue).start(next: { [weak self] callsTabTip in - if let strongSelf = self { - if callsTabTip == 2 { - Queue.mainQueue().after(1.0) { - let controller = callSuggestTabController(sharedContext: strongSelf.sharedContext) - strongSelf.present(controller, in: .window(.root)) - } - } - if callsTabTip < 3 { - let _ = ApplicationSpecificNotice.incrementCallsTabTips(accountManager: strongSelf.sharedContext.accountManager).start() - } - } - }) - } - } +// self.controllerNode.callEnded = { [weak self] didPresentRating in +// if let strongSelf = self, !didPresentRating { +// let _ = (combineLatest(strongSelf.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings]), ApplicationSpecificNotice.getCallsTabTip(accountManager: strongSelf.sharedContext.accountManager)) +// |> map { sharedData, callsTabTip -> Int32 in +// var value = false +// if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings]?.get(CallListSettings.self) { +// value = settings.showTab +// } +// if value { +// return 3 +// } else { +// return callsTabTip +// } +// } |> deliverOnMainQueue).start(next: { [weak self] callsTabTip in +// if let strongSelf = self { +// if callsTabTip == 2 { +// Queue.mainQueue().after(1.0) { +// let controller = callSuggestTabController(sharedContext: strongSelf.sharedContext) +// strongSelf.present(controller, in: .window(.root)) +// } +// } +// if callsTabTip < 3 { +// let _ = ApplicationSpecificNotice.incrementCallsTabTips(accountManager: strongSelf.sharedContext.accountManager).start() +// } +// } +// }) +// } +// } self.controllerNode.willBeDismissedInteractively = { [weak self] in guard let self else { @@ -360,20 +356,19 @@ public final class CallController: ViewController { self.superDismiss() } - let callPeerView: Signal - callPeerView = self.account.postbox.peerView(id: self.call.peerId) |> map(Optional.init) - + let callPeerView: Signal + callPeerView = TelegramEngine(account: self.account).data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.call.peerId)) + self.peerDisposable = (combineLatest(queue: .mainQueue(), - self.account.postbox.peerView(id: self.account.peerId) |> take(1), + TelegramEngine(account: self.account).data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.account.peerId)) |> take(1), callPeerView, self.sharedContext.activeAccountsWithInfo |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] accountView, view, activeAccountsWithInfo in if let strongSelf = self { if let view { - if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] { - strongSelf.peer = peer - strongSelf.controllerNode.updatePeer(accountPeer: accountPeer, peer: peer, hasOther: activeAccountsWithInfo.accounts.count > 1) + if let accountPeer = accountView { + strongSelf.controllerNode.updatePeer(accountPeer: accountPeer, peer: view, hasOther: activeAccountsWithInfo.accounts.count > 1) strongSelf.isDataReady.set(.single(true)) } } else { @@ -521,7 +516,6 @@ public final class CallController: ViewController { confirmation: { peer in switch peer { case let .peer(peer, _, _): - let peer = EnginePeer(peer) guard case let .user(user) = peer else { return .single(false) } @@ -539,7 +533,6 @@ public final class CallController: ViewController { isPeerEnabled: { peer in switch peer { case let .peer(peer, _, _): - let peer = EnginePeer(peer) guard case let .user(user) = peer else { return false } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 5c0c2d233a..7833a53670 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index f0ea64fe0d..fb75030cf3 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramAudio import AccountContext import TelegramPresentationData @@ -30,7 +29,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } private let sharedContext: SharedAccountContext - private let account: Account private let presentationData: PresentationData private let statusBar: StatusBar private let call: PresentationCall @@ -88,7 +86,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP init( sharedContext: SharedAccountContext, - account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, @@ -96,7 +93,6 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP call: PresentationCall ) { self.sharedContext = sharedContext - self.account = account self.presentationData = presentationData self.statusBar = statusBar self.call = call @@ -180,6 +176,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP shortName: " ", avatarImage: nil, audioOutput: .internalSpeaker, + canSwitchAudioOutput: true, isLocalAudioMuted: false, isRemoteAudioMuted: false, localVideo: nil, @@ -275,6 +272,11 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.currentAudioOutput = currentOutput if var callScreenState = self.callScreenState { + var canSwitchAudioOutput = false + if availableOutputs.count > 1 { + canSwitchAudioOutput = true + } + let mappedOutput: PrivateCallScreen.State.AudioOutput if let currentOutput { switch currentOutput { @@ -305,8 +307,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP mappedOutput = .internalSpeaker } - if callScreenState.audioOutput != mappedOutput { + if callScreenState.audioOutput != mappedOutput || callScreenState.canSwitchAudioOutput != canSwitchAudioOutput { callScreenState.audioOutput = mappedOutput + callScreenState.canSwitchAudioOutput = canSwitchAudioOutput self.callScreenState = callScreenState self.update(transition: .animated(duration: 0.3, curve: .spring)) @@ -611,8 +614,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } } - func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) { - self.updatePeer(peer: EnginePeer(peer)) + func updatePeer(accountPeer: EnginePeer, peer: EnginePeer, hasOther: Bool) { + self.updatePeer(peer: peer) } private func updatePeer(peer: EnginePeer) { @@ -626,7 +629,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.peerAvatarDisposable?.dispose() let size = CGSize(width: 128.0, height: 128.0) - if let representation = peer.largeProfileImage, let signal = peerAvatarImage(account: self.call.context.account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: self.callScreenState?.avatarImage == nil) { + if let representation = peer.largeProfileImage, let signal = peerAvatarImage(account: self.call.context.account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: representation, displayDimensions: size, synchronousLoad: self.callScreenState?.avatarImage == nil) { self.peerAvatarDisposable = (signal |> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in guard let self else { diff --git a/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift b/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift index 9e8a045458..7b491b7ffc 100644 --- a/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift +++ b/submodules/TelegramCallsUI/Sources/CallFeedbackController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -294,7 +293,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account: let signal = combineLatest(sharedContext.presentationData, statePromise.get()) |> deliverOnMainQueue |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.CallFeedback_Send), style: .bold, enabled: true, action: { diff --git a/submodules/TelegramCallsUI/Sources/CallRatingController.swift b/submodules/TelegramCallsUI/Sources/CallRatingController.swift index 7a08626152..8f392fe1d8 100644 --- a/submodules/TelegramCallsUI/Sources/CallRatingController.swift +++ b/submodules/TelegramCallsUI/Sources/CallRatingController.swift @@ -4,7 +4,6 @@ import SwiftSignalKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import TelegramPresentationData import TelegramVoip @@ -12,14 +11,14 @@ import AccountContext import AlertComponent func rateCallAndSendLogs(engine: TelegramEngine, callId: CallId, starsCount: Int, comment: String, userInitiated: Bool, includeLogs: Bool) -> Signal { - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4244000)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4244000)) let rate = engine.calls.rateCall(callId: callId, starsCount: Int32(starsCount), comment: comment, userInitiated: userInitiated) if includeLogs { let id = Int64.random(in: Int64.min ... Int64.max) let name = "\(callId.id)_\(callId.accessHash).log.json" let path = callLogsPath(account: engine.account) + "/" + name - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)], alternativeRepresentations: []) let message = EnqueueMessage.message(text: comment, attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) return rate |> then(enqueueMessages(account: engine.account, peerId: peerId, messages: [message]) diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index 04529dbea6..0e65484512 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -221,7 +220,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { private var presentationData: PresentationData? private let presentationDataDisposable = MetaDisposable() - private var currentPeer: Peer? + private var currentPeer: EnginePeer? private var currentCallTimer: SwiftSignalKit.Timer? private var currentCallState: PresentationCallState? private var currentGroupCallState: PresentationGroupCallSummaryState? @@ -319,9 +318,17 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { switch content { case let .call(sharedContext, account, call): self.presentationData = sharedContext.currentPresentationData.with { $0 } + let callPeer = TelegramEngine(account: account).data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } self.stateDisposable.set( (combineLatest( - account.postbox.loadedPeerWithId(call.peerId), + callPeer, call.state, call.isMuted ) @@ -360,9 +367,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { strongSelf.update() } })) - let callPeerView: Signal + let callPeerView: Signal if let peerId = call.peerId { - callPeerView = account.postbox.peerView(id: peerId) |> map(Optional.init) + callPeerView = TelegramEngine(account: account).data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) } else { callPeerView = .single(nil) } @@ -376,7 +383,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { |> deliverOnMainQueue).start(next: { [weak self] view, state, isMuted, members in if let strongSelf = self { if let view { - strongSelf.currentPeer = view.peers[view.peerId] + strongSelf.currentPeer = view } else { strongSelf.currentPeer = nil } @@ -421,14 +428,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { var effectiveLevel: Float = 0.0 var audioLevels = audioLevels if !strongSelf.currentIsMuted { - audioLevels.append((PeerId(0), 0, myAudioLevel, true)) + audioLevels.append((EnginePeer.Id(0), 0, myAudioLevel, true)) } effectiveLevel = audioLevels.map { $0.2 }.max() ?? 0.0 strongSelf.backgroundNode.audioLevel = effectiveLevel })) if let groupCall = call as? PresentationGroupCallImpl { - let _ = (allowedStoryReactions(account: account) + let _ = (allowedStoryReactions(engine: TelegramEngine(account: account)) |> deliverOnMainQueue).start(next: { [weak self] reactionItems in self?.reactionItems = reactionItems }) @@ -462,7 +469,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { if let voiceChatTitle = self.currentGroupCallState?.info?.title, !voiceChatTitle.isEmpty { title = voiceChatTitle } else if let currentPeer = self.currentPeer { - title = EnginePeer(currentPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + title = currentPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } var membersCount: Int32? if let groupCallState = self.currentGroupCallState { @@ -471,19 +478,19 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { membersCount = 1 } - var speakingPeer: Peer? + var speakingPeer: EnginePeer? if let members = currentMembers { - var speakingPeers: [Peer] = [] + var speakingPeers: [EnginePeer] = [] for member in members.participants { if let memberPeer = member.peer, members.speakingParticipants.contains(memberPeer.id) { - speakingPeers.append(memberPeer._asPeer()) + speakingPeers.append(memberPeer) } } speakingPeer = speakingPeers.first } - + if let speakingPeer = speakingPeer { - speakerSubtitle = EnginePeer(speakingPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + speakerSubtitle = speakingPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } displaySpeakerSubtitle = speakerSubtitle != title && !speakerSubtitle.isEmpty diff --git a/submodules/TelegramCallsUI/Sources/Components/LivestreamVideoViewV1.swift b/submodules/TelegramCallsUI/Sources/Components/LivestreamVideoViewV1.swift index e9d61c78a6..f5ee73edea 100644 --- a/submodules/TelegramCallsUI/Sources/Components/LivestreamVideoViewV1.swift +++ b/submodules/TelegramCallsUI/Sources/Components/LivestreamVideoViewV1.swift @@ -3,7 +3,6 @@ import UIKit import AVFoundation import SwiftSignalKit import UniversalMediaPlayer -import Postbox import TelegramCore import AccountContext import TelegramAudio @@ -19,7 +18,7 @@ final class LivestreamVideoViewV1: UIView { let part: DirectMediaStreamingContext.Playlist.Part let disposable = MetaDisposable() var resolvedTimeOffset: Double? - var data: TempBoxFile? + var data: EngineTempBoxFile? var info: FFMpegMediaInfo? init(part: DirectMediaStreamingContext.Playlist.Part) { @@ -164,11 +163,11 @@ final class LivestreamVideoViewV1: UIView { if let streamingContext = self.streamingContext { partContext.disposable.set((streamingContext.partData(index: part.index) |> deliverOn(Queue.concurrentDefaultQueue()) - |> map { data -> (file: TempBoxFile, info: FFMpegMediaInfo)? in + |> map { data -> (file: EngineTempBoxFile, info: FFMpegMediaInfo)? in guard let data else { return nil } - let tempFile = TempBox.shared.tempFile(fileName: "part.mp4") + let tempFile = EngineTempBox.shared.tempFile(fileName: "part.mp4") if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) { if let info = extractFFMpegMediaInfo(path: tempFile.path) { return (tempFile, info) @@ -176,7 +175,7 @@ final class LivestreamVideoViewV1: UIView { return nil } } else { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return nil } } diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift index d9fb545339..77fcad9098 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -6,7 +6,6 @@ import AccountContext import SwiftSignalKit import AVKit import TelegramCore -import Postbox import UndoUI import TelegramPresentationData @@ -66,7 +65,7 @@ public final class MediaStreamComponent: CombinedComponent { private(set) var recordingStartTimestamp: Int32? private(set) var peerTitle: String = "" - private(set) var chatPeer: Peer? + private(set) var chatPeer: EnginePeer? private(set) var isVisibleInHierarchy: Bool = false private var isVisibleInHierarchyDisposable: Disposable? @@ -152,7 +151,7 @@ public final class MediaStreamComponent: CombinedComponent { strongSelf.peerTitle = callPeer.debugDisplayTitle updated = true } - strongSelf.chatPeer = callPeer._asPeer() + strongSelf.chatPeer = callPeer if strongSelf.callTitle != state.title { strongSelf.callTitle = state.title @@ -508,7 +507,7 @@ public final class MediaStreamComponent: CombinedComponent { let title: String = presentationData.strings.LiveStream_EditTitle let text: String = presentationData.strings.LiveStream_EditTitleText - let editController = voiceChatTitleEditController(context: call.accountContext, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak call] title in + let editController = voiceChatTitleEditController(context: call.accountContext, forceTheme: defaultDarkPresentationTheme, title: title, text: text, placeholder: chatPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak call] title in guard let call = call else { return } @@ -1185,12 +1184,20 @@ public final class MediaStreamComponentController: ViewControllerComponentContai return } - let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(peerId), self.callImpl.state |> take(1)) + let sharedPeerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + let _ = (combineLatest(queue: .mainQueue(), sharedPeerSignal, self.callImpl.state |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] peer, callState in if let strongSelf = self { var inviteLinks = inviteLinks - - if let peer = peer as? TelegramChannel, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState { + + if case let .channel(channel) = peer, case .group = channel.info, !channel.flags.contains(.isGigagroup), !(channel.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState { let isMuted = defaultParticipantMuteState == .muted if !isMuted { diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift index eece3a8a6a..495eb5ff61 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift @@ -9,7 +9,6 @@ import ShimmerEffect import TelegramCore import SwiftSignalKit import AvatarNode -import Postbox import TelegramVoip import ComponentDisplayAdapters @@ -28,7 +27,7 @@ public final class MediaStreamVideoComponent: Component { let isFullscreen: Bool let onVideoSizeRetrieved: (CGSize) -> Void let videoLoading: Bool - let callPeer: Peer? + let callPeer: EnginePeer? let onVideoPlaybackLiveChange: (Bool) -> Void public init( @@ -40,7 +39,7 @@ public final class MediaStreamVideoComponent: Component { addInset: Bool, isFullscreen: Bool, videoLoading: Bool, - callPeer: Peer?, + callPeer: EnginePeer?, enablePictureInPicture: Bool, activatePictureInPicture: ActionSlot>, deactivatePictureInPicture: ActionSlot, @@ -306,7 +305,7 @@ public final class MediaStreamVideoComponent: Component { if let peer = component.callPeer, !didBeginLoadingAvatar { didBeginLoadingAvatar = true - avatarDisposable = peerAvatarCompleteImage(account: component.call.account, peer: EnginePeer(peer), size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true).start(next: { [weak self] image in + avatarDisposable = peerAvatarCompleteImage(account: component.call.account, peer: peer, size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true).start(next: { [weak self] image in DispatchQueue.main.async { self?.placeholderView.contentMode = .scaleAspectFill self?.placeholderView.image = image diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift index b45a3c8b88..aa35f3f48c 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift @@ -3,7 +3,6 @@ import Display import UIKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import AvatarNode import GlassBackgroundComponent @@ -89,7 +88,7 @@ final class MessageItemComponent: Component { weak var standaloneReactionAnimation: StandaloneReactionAnimation? private var cachedEntities: [MessageTextEntity]? - private var entityFiles: [MediaId: TelegramMediaFile] = [:] + private var entityFiles: [EngineMedia.Id: TelegramMediaFile] = [:] private var component: MessageItemComponent? diff --git a/submodules/TelegramCallsUI/Sources/GroupCallScreencast.swift b/submodules/TelegramCallsUI/Sources/GroupCallScreencast.swift index 4e95083d02..b2ae2e0465 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallScreencast.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallScreencast.swift @@ -86,6 +86,7 @@ final class ScreencastInProcessIPCContext: ScreencastIPCContext { enableNoiseSuppression: false, disableAudioInput: true, enableSystemMute: false, + useReferenceImpl: false, prioritizeVP8: false, logPath: "", onMutedSpeechActivityDetected: { _ in }, diff --git a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift index 9b5caf3cc0..1bb34d814e 100644 --- a/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/LegacyCallControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -20,7 +19,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol private let statusBar: StatusBar private var presentationData: PresentationData - private var peer: Peer? + private var peer: EnginePeer? private let debugInfo: Signal<(String, String), NoError> private var forceReportRating = false private let easyDebugAccess: Bool @@ -167,10 +166,10 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol self.view.addGestureRecognizer(tapRecognizer) } - func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) { - if !arePeersEqual(self.peer, peer) { + func updatePeer(accountPeer: EnginePeer, peer: EnginePeer, hasOther: Bool) { + if self.peer != peer { self.peer = peer - if let peerReference = PeerReference(peer), !peer.profileImageRepresentations.isEmpty { + if let peerReference = PeerReference(peer._asPeer()), !peer.profileImageRepresentations.isEmpty { let representations: [ImageRepresentationWithReference] = peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: .avatar(peer: peerReference, resource: $0.resource)) }) self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.account, representations: representations, immediateThumbnailData: nil, autoFetchFullSize: true)) self.dimNode.isHidden = false @@ -178,10 +177,10 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol self.imageNode.setSignal(callDefaultBackground()) self.dimNode.isHidden = true } - - self.statusNode.title = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) + + self.statusNode.title = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) if hasOther { - self.statusNode.subtitle = self.presentationData.strings.Call_AnsweringWithAccount(EnginePeer(accountPeer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string + self.statusNode.subtitle = self.presentationData.strings.Call_AnsweringWithAccount(accountPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).string if let callState = callState { self.updateCallState(callState) @@ -444,7 +443,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol @objc func keyPressed() { if self.keyPreviewNode == nil, let keyText = self.keyTextData?.1, let peer = self.peer { - let keyPreviewNode = CallControllerKeyPreviewNode(keyText: keyText, infoText: self.presentationData.strings.Call_EmojiDescription(EnginePeer(peer).compactDisplayTitle).string.replacingOccurrences(of: "%%", with: "%"), dismiss: { [weak self] in + let keyPreviewNode = CallControllerKeyPreviewNode(keyText: keyText, infoText: self.presentationData.strings.Call_EmojiDescription(peer.compactDisplayTitle).string.replacingOccurrences(of: "%%", with: "%"), dismiss: { [weak self] in if let _ = self?.keyPreviewNode { self?.backPressed() } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index c3d892b2c1..5613e76cdc 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 2a4275f114..6d7cd2b900 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -320,7 +320,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if let firstState = ringingStates.first { if self.currentCall == nil && self.currentGroupCall == nil { self.currentCallDisposable.set((combineLatest( - firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + firstState.0.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in @@ -330,12 +333,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id { return } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue - + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let call = PresentationCallImpl( context: firstState.0, audioSession: strongSelf.audioSession, @@ -366,7 +369,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { let _ = currentCall.hangUp().startStandalone() self.currentCallDisposable.set((combineLatest( - firstState.0.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + firstState.0.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] preferences, sharedData in @@ -376,11 +382,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if strongSelf.currentUpgradedToConferenceCallId == firstState.2.id { return } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings let experimentalSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings]?.get(ExperimentalUISettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let call = PresentationCallImpl( context: firstState.0, @@ -628,7 +634,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return peerView.peerIsContact } |> take(1), - context.account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, PreferencesKeys.appConfiguration]) |> take(1), + context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.voipConfiguration), + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration) + ) |> take(1), accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings, ApplicationSpecificSharedDataKeys.experimentalUISettings]) |> take(1), areVideoCallsAvailable ) @@ -638,10 +647,10 @@ public final class PresentationCallManagerImpl: PresentationCallManager { if let currentCall = strongSelf.currentCall { currentCall.rejectBusy() } - - let configuration = preferences.values[PreferencesKeys.voipConfiguration]?.get(VoipConfiguration.self) ?? .defaultValue + + let configuration = preferences.0?.get(VoipConfiguration.self) ?? .defaultValue let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings]?.get(AutodownloadSettings.self) ?? .defaultSettings - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration = preferences.1?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue let isVideoPossible: Bool = areVideoCallsAvailable diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 4cb912c60e..cb51d94260 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1182,25 +1182,32 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) if let peerId { - let _ = (self.account.postbox.loadedPeerWithId(peerId) + let _ = (self.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self else { return } var canManageCall = false - if let peer = peer as? TelegramGroup { - if case .creator = peer.role { + if case let .legacyGroup(group) = peer { + if case .creator = group.role { canManageCall = true - } else if case let .admin(rights, _) = peer.role, rights.rights.contains(.canManageCalls) { + } else if case let .admin(rights, _) = group.role, rights.rights.contains(.canManageCalls) { canManageCall = true } - } else if let peer = peer as? TelegramChannel { - if peer.flags.contains(.isCreator) { + } else if case let .channel(channel) = peer { + if channel.flags.contains(.isCreator) { canManageCall = true - } else if (peer.adminRights?.rights.contains(.canManageCalls) == true) { + } else if (channel.adminRights?.rights.contains(.canManageCalls) == true) { canManageCall = true } - self.peerUpdatesSubscription = self.accountContext.account.viewTracker.polledChannel(peerId: peer.id).start() + self.peerUpdatesSubscription = self.accountContext.account.viewTracker.polledChannel(peerId: channel.id).start() } var updatedValue = self.stateValue updatedValue.canManageCall = canManageCall @@ -1490,7 +1497,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if let peerId { if peerId.namespace == Namespaces.Peer.CloudChannel { rawAdminIds = Signal { subscriber in - let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in + let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in var peerIds = Set() for item in list.list { if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { @@ -1849,12 +1856,17 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } var prioritizeVP8 = false - #if DEBUG && false - prioritizeVP8 = "".isEmpty - #endif if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data, let value = data["ios_calls_prioritize_vp8"] as? Double { prioritizeVP8 = value != 0.0 } + + var useReferenceImpl = false + #if DEBUG && true + useReferenceImpl = "".isEmpty + #endif + if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data, let value = data["ios_calls_group_reference_impl"] as? Double { + useReferenceImpl = value != 0.0 + } genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: contextAudioSessionActive, video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in let disposable = MetaDisposable() @@ -1874,7 +1886,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.requestCall(movingFromBroadcastToRtc: false) } } - }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, enableSystemMute: self.accountContext.sharedContext.immediateExperimentalUISettings.experimentalCallMute, prioritizeVP8: prioritizeVP8, logPath: allocateCallLogPath(account: self.account), onMutedSpeechActivityDetected: { [weak self] value in + }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit,videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, enableSystemMute: self.accountContext.sharedContext.immediateExperimentalUISettings.experimentalCallMute, useReferenceImpl: useReferenceImpl, prioritizeVP8: prioritizeVP8, logPath: allocateCallLogPath(account: self.account), onMutedSpeechActivityDetected: { [weak self] value in Queue.mainQueue().async { guard let self else { return @@ -1970,7 +1982,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return EmptyDisposable } - let (disposable, _) = self.accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: self.accountContext.engine, postbox: self.accountContext.account.postbox, network: self.accountContext.account.network, accountPeerId: self.accountContext.account.peerId, peerId: peerId, updated: { list in + let (disposable, _) = self.accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: self.accountContext.engine, accountPeerId: self.accountContext.account.peerId, peerId: peerId, updated: { list in var peerIds = Set() for item in list.list { if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { @@ -2314,7 +2326,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if let peerId = peerId ?? self.streamPeerId { if peerId.namespace == Namespaces.Peer.CloudChannel { rawAdminIds = Signal { subscriber in - let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in + let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(engine: accountContext.engine, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in var peerIds = Set() for item in list.list { if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift index 8ac79574cb..8ce1fde79a 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift @@ -156,11 +156,11 @@ final class VideoChatActionButtonComponent: Component { switch component.content { case let .audio(audio, isEnabledValue): var isActive = false + isEnabled = isEnabledValue switch audio { case .none, .builtin: titleText = component.strings.Call_Speaker case .speaker: - isEnabled = isEnabledValue isActive = isEnabledValue titleText = component.strings.Call_Speaker case .headphones: @@ -324,7 +324,7 @@ final class VideoChatActionButtonComponent: Component { } transition.setPosition(view: iconView, position: iconFrame.center) transition.setBounds(view: iconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setAlpha(view: iconView, alpha: isEnabled ? 1.0 : 0.6) + transition.setAlpha(view: iconView, alpha: isEnabled ? 1.0 : 0.4) transition.setScale(view: iconView, scale: availableSize.height / 56.0) } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index 4c6ef24fcc..128651bb7e 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -350,7 +350,7 @@ final class VideoChatParticipantVideoComponent: Component { if self.blurredAvatarDisposable == nil { //TODO:release synchronous - if let participantPeer = component.participant.peer, let imageCache = component.call.accountContext.imageCache as? DirectMediaImageCache, let peerReference = PeerReference(participantPeer._asPeer()) { + if let participantPeer = component.participant.peer, let imageCache = component.call.accountContext.imageCache as? DirectMediaImageCache, let peerReference = PeerReference(participantPeer) { if let result = imageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: participantPeer.profileImageRepresentations.first?.immediateThumbnailData, size: 64, synchronous: false) { if let image = result.image { blurredAvatarView.image = blurredAvatarImage(image) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index 5aa0a630c3..80b80058f5 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import ComponentFlow -import Postbox import TelegramCore import AccountContext import PlainButtonComponent diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index bee8112161..b11c721d82 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import Display import ComponentFlow import ViewControllerComponent -import Postbox import TelegramCore import AccountContext import PlainButtonComponent @@ -677,7 +676,14 @@ final class VideoChatScreenComponent: Component { return } - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] chatPeer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return @@ -685,7 +691,7 @@ final class VideoChatScreenComponent: Component { guard let callState = self.callState, let peer = self.peer else { return } - + let initialTitle = callState.title let title: String @@ -698,7 +704,7 @@ final class VideoChatScreenComponent: Component { text = environment.strings.VoiceChat_EditTitleText } - let controller = voiceChatTitleEditController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: EnginePeer(chatPeer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in + let controller = voiceChatTitleEditController(context: groupCall.accountContext, forceTheme: environment.theme, title: title, text: text, placeholder: chatPeer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), value: initialTitle, maxLength: 40, apply: { [weak self] title in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } @@ -796,7 +802,14 @@ final class VideoChatScreenComponent: Component { } if let peerId = groupCall.peerId { - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peerId) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return @@ -1405,7 +1418,7 @@ final class VideoChatScreenComponent: Component { ) self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme - let _ = (allowedStoryReactions(account: context.account) + let _ = (allowedStoryReactions(engine: context.engine) |> deliverOnMainQueue).start(next: { [weak self] reactionItems in self?.reactionItems = reactionItems }) @@ -1807,7 +1820,14 @@ final class VideoChatScreenComponent: Component { } }) - let currentAccountPeer = groupCall.accountContext.account.postbox.loadedPeerWithId(groupCall.accountContext.account.peerId) + let currentAccountPeer = groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: groupCall.accountContext.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -1890,11 +1910,10 @@ final class VideoChatScreenComponent: Component { } } } else { - //TODO:localized if event.joined { - self.lastTitleEvent = "\(event.peer.compactDisplayTitle) joined" + self.lastTitleEvent = environment.strings.VideoChat_StatusPeerJoined(event.peer.compactDisplayTitle).string } else { - self.lastTitleEvent = "\(event.peer.compactDisplayTitle) left" + self.lastTitleEvent = environment.strings.VideoChat_StatusPeerLeft(event.peer.compactDisplayTitle).string } if !self.isUpdating { self.state?.updated(transition: .spring(duration: 0.4)) @@ -2600,6 +2619,7 @@ final class VideoChatScreenComponent: Component { let videoButtonContent: VideoChatActionButtonComponent.Content? let videoControlButtonContent: VideoChatActionButtonComponent.Content + let videoControlButtonEnabled: Bool let messageButtonContent: VideoChatActionButtonComponent.Content? var buttonAudio: VideoChatActionButtonComponent.Content.Audio = .speaker @@ -2633,13 +2653,16 @@ final class VideoChatScreenComponent: Component { if let callState = self.callState, let muteState = callState.muteState, !muteState.canUnmute { videoButtonContent = nil videoControlButtonContent = .audio(audio: buttonAudio, isEnabled: buttonIsEnabled) + videoControlButtonEnabled = buttonIsEnabled } else { let isVideoActive = self.callState?.isMyVideoActive ?? false videoButtonContent = .video(isActive: isVideoActive) if isVideoActive { videoControlButtonContent = .rotateCamera + videoControlButtonEnabled = true } else { videoControlButtonContent = .audio(audio: buttonAudio, isEnabled: buttonIsEnabled) + videoControlButtonEnabled = buttonIsEnabled } } @@ -3206,7 +3229,7 @@ final class VideoChatScreenComponent: Component { transition.setPosition(view: microphoneButtonView, position: microphoneButtonFrame.center) transition.setBounds(view: microphoneButtonView, bounds: CGRect(origin: CGPoint(), size: microphoneButtonFrame.size)) } - + let _ = self.speakerButton.update( transition: transition, component: AnyComponent(PlainButtonComponent( @@ -3229,6 +3252,7 @@ final class VideoChatScreenComponent: Component { self.onAudioRoutePressed() } }, + isEnabled: videoControlButtonEnabled, animateAlpha: false )), environment: {}, @@ -3449,7 +3473,6 @@ final class VideoChatScreenComponent: Component { mode: .standard(.default), chatLocation: .peer(id: call.accountContext.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -4250,20 +4273,16 @@ private func hasFirstResponder(_ view: UIView) -> Bool { return false } -func allowedStoryReactions(account: Account) -> Signal<[ReactionItem], NoError> { - let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) - let topReactions = account.postbox.combinedView(keys: [viewKey]) - |> map { views -> [RecentReactionItem] in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return [] - } - return view.items.compactMap { item -> RecentReactionItem? in +func allowedStoryReactions(engine: TelegramEngine) -> Signal<[ReactionItem], NoError> { + let topReactions = engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudTopReactions)) + |> map { items -> [RecentReactionItem] in + return items.compactMap { item -> RecentReactionItem? in return item.contents.get(RecentReactionItem.self) } } return combineLatest( - TelegramEngine(account: account).stickers.availableReactions(), + engine.stickers.availableReactions(), topReactions ) |> take(1) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index dd47fdab48..34b08a5114 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -136,7 +136,7 @@ extension VideoChatScreenComponent.View { } else { text = environment.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)).string } - self.presentToast(icon: .peer(EnginePeer(participant.peer)), text: text, duration: 3) + self.presentToast(icon: .peer(participant.peer), text: text, duration: 3) } } else { if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) { @@ -292,12 +292,19 @@ extension VideoChatScreenComponent.View { switch error { case .privacy: - let _ = (groupCall.accountContext.account.postbox.loadedPeerWithId(peer.id) + let _ = (groupCall.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } - environment.controller()?.present(textAlertController(context: groupCall.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(EnginePeer(peer).compactDisplayTitle, EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) + environment.controller()?.present(textAlertController(context: groupCall.accountContext, title: nil, text: environment.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).string, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) }) case .notMutualContact: environment.controller()?.present(textAlertController(context: context, title: nil, text: environment.strings.GroupInfo_AddUserLeftError, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_OK, action: {})]), in: .window(.root)) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index 8514be1ed1..41b9b2ff45 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -168,7 +168,7 @@ extension VideoChatScreenComponent.View { for peer in displayAsPeers { if peer.peer.id == callState.myPeerId { let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { [weak self] c, _ in + items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: environment.strings, displayOrder: currentCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: currentCall.accountContext.account, peer: peer.peer, size: avatarSize)), action: { [weak self] c, _ in guard let self else { return } @@ -625,10 +625,10 @@ extension VideoChatScreenComponent.View { var isGroup = false if let displayAsPeers = self.displayAsPeers { for peer in displayAsPeers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -645,7 +645,7 @@ extension VideoChatScreenComponent.View { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = environment.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = environment.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = environment.strings.Conversation_StatusMembers(subscribers) @@ -655,7 +655,7 @@ extension VideoChatScreenComponent.View { let isSelected = peer.peer.id == myPeerId let extendedAvatarSize = CGSize(width: 35.0, height: 35.0) let theme = environment.theme - let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: EnginePeer(peer.peer), size: avatarSize) + let avatarSignal = peerAvatarCompleteImage(account: groupCall.accountContext.account, peer: peer.peer, size: avatarSize) |> map { image -> UIImage? in if isSelected, let image = image { return generateImage(extendedAvatarSize, rotatedContext: { size, context in @@ -676,7 +676,7 @@ extension VideoChatScreenComponent.View { } } - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: environment.strings, displayOrder: groupCall.accountContext.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { [weak self] _, f in f(.default) guard let self, case let .group(groupCall) = self.currentCall else { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift index c4ba7b067f..0bfe632b7e 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenParticipantContextMenu.swift @@ -497,9 +497,8 @@ extension VideoChatScreenComponent.View { } let _ = self.currentAvatarMixin.swap(nil) - let postbox = currentCall.accountContext.account.postbox self.updateAvatarDisposable.set((currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations) }) |> deliverOnMainQueue).start()) } @@ -552,16 +551,15 @@ extension VideoChatScreenComponent.View { let peerId = callState.myPeerId let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - currentCall.accountContext.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + currentCall.accountContext.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) self.currentUpdatingAvatar = (representation, 0.0) - let postbox = currentCall.accountContext.account.postbox - let signal = peerId.namespace == Namespaces.Peer.CloudUser ? currentCall.accountContext.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) - }) : currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: currentCall.accountContext.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + let signal = peerId.namespace == Namespaces.Peer.CloudUser ? currentCall.accountContext.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations) + }) : currentCall.accountContext.engine.peers.updatePeerPhoto(peerId: peerId, photo: currentCall.accountContext.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: currentCall.accountContext.engine, resource: resource, representations: representations) }) self.updateAvatarDisposable.set((signal @@ -594,7 +592,7 @@ extension VideoChatScreenComponent.View { let peerId = callState.myPeerId let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - currentCall.accountContext.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) + currentCall.accountContext.engine.resources.storeResourceData(id: EngineMediaResource.Id(photoResource.id), data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) self.currentUpdatingAvatar = (representation, 0.0) @@ -693,12 +691,12 @@ extension VideoChatScreenComponent.View { self.updateAvatarDisposable.set((signal |> mapToSignal { videoResource -> Signal in if peerId.namespace == Namespaces.Peer.CloudUser { - return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: EngineMediaResource(videoResource), videoStartTimestamp: videoStartTimestamp, markup: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else { - return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: context.engine.peers.uploadedPeerVideo(resource: videoResource) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource(videoResource)) |> map(Optional.init), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatAccountHeaderActionSheetItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatAccountHeaderActionSheetItem.swift index 86fffedf79..2edf8c66e4 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatAccountHeaderActionSheetItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatAccountHeaderActionSheetItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift index e6de2d4862..b0adada425 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -159,6 +158,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, A self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 1e883ee6f3..dfedb7852c 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -9,7 +9,6 @@ import TelegramStringFormatting import TelegramVoip import TelegramAudio import AccountContext -import Postbox import TelegramCore import MergeLists import ItemListUI @@ -124,7 +123,7 @@ struct VoiceChatPeerEntry: Identifiable { case raisedHand } - var peer: Peer + var peer: EnginePeer var about: String? var isMyPeer: Bool var videoEndpointId: String? @@ -146,7 +145,7 @@ struct VoiceChatPeerEntry: Identifiable { } init( - peer: Peer, + peer: EnginePeer, about: String?, isMyPeer: Bool, videoEndpointId: String?, @@ -181,12 +180,12 @@ struct VoiceChatPeerEntry: Identifiable { self.isLandscape = isLandscape } - var stableId: PeerId { + var stableId: EnginePeer.Id { return self.peer.id } - + static func ==(lhs: VoiceChatPeerEntry, rhs: VoiceChatPeerEntry) -> Bool { - if !lhs.peer.isEqual(rhs.peer) { + if lhs.peer != rhs.peer { return false } if lhs.about != rhs.about { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index fc62365030..b2c90c80c5 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -73,7 +72,7 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem { let presentationData: ItemListPresentationData let nameDisplayOrder: PresentationPersonNameOrder let context: AccountContext - let peer: Peer + let peer: EnginePeer let videoEndpointId: String? let isPaused: Bool let icon: Icon @@ -91,7 +90,7 @@ final class VoiceChatFullscreenParticipantItem: ListViewItem { public let selectable: Bool = true - public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, videoEndpointId: String?, isPaused: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, textColor: Color, color: Color, isLandscape: Bool, active: Bool, showVideoWhenActive: Bool, getAudioLevel: (() -> Signal)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) { + public init(presentationData: ItemListPresentationData, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, videoEndpointId: String?, isPaused: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, textColor: Color, color: Color, isLandscape: Bool, active: Bool, showVideoWhenActive: Bool, getAudioLevel: (() -> Signal)?, getVideo: @escaping () -> GroupVideoNode?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) { self.presentationData = presentationData self.nameDisplayOrder = nameDisplayOrder self.context = context @@ -384,7 +383,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if let videoNode = self.videoNode, videoNode.supernode == self.videoContainerNode, !videoNode.alpha.isZero { hasVideo = true } - let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: EnginePeer(item.peer), text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in + let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in self?.contextSourceNode.requestDismiss?() }) profileNode.frame = CGRect(origin: CGPoint(), size: extractedRect.size) @@ -457,7 +456,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } let currentBoldFont: UIFont = titleFont - if let user = item.peer as? TelegramUser { + if case let .user(user) = item.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { titleAttributedString = NSAttributedString(string: firstName, font: titleFont, textColor: titleColor) } else if let firstName = user.firstName, !firstName.isEmpty { @@ -467,9 +466,9 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { } else { titleAttributedString = NSAttributedString(string: item.presentationData.strings.User_DeletedAccount, font: currentBoldFont, textColor: titleColor) } - } else if let group = item.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = item.peer { titleAttributedString = NSAttributedString(string: group.title, font: currentBoldFont, textColor: titleColor) - } else if let channel = item.peer as? TelegramChannel { + } else if case let .channel(channel) = item.peer { titleAttributedString = NSAttributedString(string: channel.title, font: currentBoldFont, textColor: titleColor) } @@ -763,7 +762,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if item.peer.isDeleted { overrideImage = .deletedIcon } - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer), overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad, storeUnrounded: true) + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad, storeUnrounded: true) var hadMicrophoneNode = false var hadRaiseHandNode = false diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift index a4bf02de6f..c6f42e60b5 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatHeaderButton.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -69,7 +68,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { enum Content { case image(UIImage?) case more(UIImage?) - case avatar(Peer) + case avatar(EnginePeer) } private let context: AccountContext @@ -155,7 +154,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { self.iconNode.isHidden = false self.avatarNode.isHidden = true case let .avatar(peer): - self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: EnginePeer(peer)) + self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer) self.iconNode.isHidden = true self.avatarNode.isHidden = false self.animationNode?.isHidden = true @@ -173,7 +172,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { self.iconNode.isHidden = false self.avatarNode.isHidden = true case let .avatar(peer): - self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: EnginePeer(peer)) + self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer) self.iconNode.isHidden = true self.avatarNode.isHidden = false self.animationNode?.isHidden = true diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift index fe3f0c60ce..ec5b336544 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -22,7 +21,7 @@ public final class VoiceChatJoinScreen: ViewController { private var animatedIn = false private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let invite: String? private var join: (CachedChannelData.ActiveCall) -> Void @@ -30,7 +29,7 @@ public final class VoiceChatJoinScreen: ViewController { private let disposable = MetaDisposable() - public init(context: AccountContext, peerId: PeerId, invite: String?, join: @escaping (CachedChannelData.ActiveCall) -> Void) { + public init(context: AccountContext, peerId: EnginePeer.Id, invite: String?, join: @escaping (CachedChannelData.ActiveCall) -> Void) { self.context = context self.peerId = peerId self.invite = invite @@ -132,14 +131,14 @@ public final class VoiceChatJoinScreen: ViewController { return } - let defaultJoinAsPeerId: PeerId? = callJoinAsPeerId + let defaultJoinAsPeerId: EnginePeer.Id? = callJoinAsPeerId let activeCall = CachedChannelData.ActiveCall(id: call.info.id, accessHash: call.info.accessHash, title: call.info.title, scheduleTimestamp: call.info.scheduleTimestamp, subscribedToScheduled: call.info.subscribedToScheduled, isStream: call.info.isStream) if availablePeers.count > 0 && defaultJoinAsPeerId == nil { strongSelf.dismiss() strongSelf.join(activeCall) } else { - strongSelf.controllerNode.setPeer(call: activeCall, peer: peer._asPeer(), title: call.info.title, memberCount: call.info.participantCount, isStream: call.info.isStream) + strongSelf.controllerNode.setPeer(call: activeCall, peer: peer, title: call.info.title, memberCount: call.info.participantCount, isStream: call.info.isStream) } } else { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -242,6 +241,7 @@ public final class VoiceChatJoinScreen: ViewController { self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -622,7 +622,7 @@ public final class VoiceChatJoinScreen: ViewController { })) } - func setPeer(call: CachedChannelData.ActiveCall, peer: Peer, title: String?, memberCount: Int, isStream: Bool) { + func setPeer(call: CachedChannelData.ActiveCall, peer: EnginePeer, title: String?, memberCount: Int, isStream: Bool) { self.call = call let transition = ContainedViewLayoutTransition.animated(duration: 0.22, curve: .easeInOut) @@ -647,7 +647,7 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod private let titleNode: ImmediateTextNode private let countNode: ImmediateTextNode - init(context: AccountContext, peer: Peer, title: String?, memberCount: Int, isStream: Bool, theme: PresentationTheme, strings: PresentationStrings, displayOrder: PresentationPersonNameOrder) { + init(context: AccountContext, peer: EnginePeer, title: String?, memberCount: Int, isStream: Bool, theme: PresentationTheme, strings: PresentationStrings, displayOrder: PresentationPersonNameOrder) { self.avatarNode = AvatarNode(font: avatarFont) self.titleNode = ImmediateTextNode() self.titleNode.maximumNumberOfLines = 4 @@ -659,10 +659,10 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod super.init() self.addSubnode(self.avatarNode) - self.avatarNode.setPeer(context: context, theme: theme, peer: EnginePeer(peer), emptyColor: theme.list.mediaPlaceholderColor) + self.avatarNode.setPeer(context: context, theme: theme, peer: peer, emptyColor: theme.list.mediaPlaceholderColor) self.addSubnode(self.titleNode) - self.titleNode.attributedText = NSAttributedString(string: title ?? EnginePeer(peer).displayTitle(strings: strings, displayOrder: displayOrder), font: Font.semibold(16.0), textColor: theme.actionSheet.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title ?? peer.displayTitle(strings: strings, displayOrder: displayOrder), font: Font.semibold(16.0), textColor: theme.actionSheet.primaryTextColor) self.addSubnode(self.countNode) @@ -682,7 +682,7 @@ final class VoiceChatPreviewContentNode: ASDisplayNode, ShareContentContainerNod func deactivate() { } - func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { } func setDidBeginDragging(_ f: (() -> Void)?) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index 1e3a3d83db..4afedb47e8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -9,7 +9,6 @@ import TelegramStringFormatting import TelegramVoip import TelegramAudio import AccountContext -import Postbox import TelegramCore import AppBundle import PresentationDataUtils @@ -95,7 +94,7 @@ class VoiceChatPinButtonNode: HighlightTrackingButtonNode { final class VoiceChatMainStageNode: ASDisplayNode { private let context: AccountContext private let call: PresentationGroupCall - private(set) var currentPeer: (PeerId, String?, Bool, Bool, Bool)? + private(set) var currentPeer: (EnginePeer.Id, String?, Bool, Bool, Bool)? private var currentPeerEntry: VoiceChatPeerEntry? var callState: PresentationGroupCallState? @@ -137,12 +136,12 @@ final class VoiceChatMainStageNode: ASDisplayNode { var tapped: (() -> Void)? var back: (() -> Void)? var togglePin: (() -> Void)? - var switchTo: ((PeerId) -> Void)? + var switchTo: ((EnginePeer.Id) -> Void)? var stopScreencast: (() -> Void)? var controlsHidden: ((Bool) -> Void)? - var getAudioLevel: ((PeerId) -> Signal)? + var getAudioLevel: ((EnginePeer.Id) -> Signal)? var getVideo: ((String, Bool, @escaping (GroupVideoNode?) -> Void) -> Void)? private let videoReadyDisposable = MetaDisposable() private var silenceTimer: SwiftSignalKit.Timer? @@ -610,7 +609,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.update(speakingPeerId: nil) } - private var effectiveSpeakingPeerId: PeerId? + private var effectiveSpeakingPeerId: EnginePeer.Id? private func updateSpeakingPeer() { guard let (_, _, _, _, isTablet) = self.validLayout else { return @@ -630,18 +629,25 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.speakingAudioLevelView = nil } - self.speakingPeerDisposable.set((self.context.account.postbox.loadedPeerWithId(peerId) + self.speakingPeerDisposable.set((self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { return } - + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: EnginePeer(peer)) - + strongSelf.speakingAvatarNode.setPeer(context: strongSelf.context, theme: presentationData.theme, peer: peer) + let bodyAttributes = MarkdownAttributeSet(font: Font.regular(15.0), textColor: .white, additionalAttributes: [:]) let boldAttributes = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: .white, additionalAttributes: [:]) - let attributedText = addAttributesToStringWithRanges(presentationData.strings.VoiceChat_ParticipantIsSpeaking(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + let attributedText = addAttributesToStringWithRanges(presentationData.strings.VoiceChat_ParticipantIsSpeaking(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) strongSelf.speakingTitleNode.attributedText = attributedText strongSelf.speakingContainerNode.alpha = 0.0 @@ -713,14 +719,14 @@ final class VoiceChatMainStageNode: ASDisplayNode { } } - private var visiblePeerIds = Set() - func update(visiblePeerIds: Set) { + private var visiblePeerIds = Set() + func update(visiblePeerIds: Set) { self.visiblePeerIds = visiblePeerIds self.updateSpeakingPeer() } - private var speakingPeerId: PeerId? - func update(speakingPeerId: PeerId?) { + private var speakingPeerId: EnginePeer.Id? + func update(speakingPeerId: EnginePeer.Id?) { self.speakingPeerId = speakingPeerId self.updateSpeakingPeer() } @@ -731,9 +737,9 @@ final class VoiceChatMainStageNode: ASDisplayNode { let peer = peerEntry.peer let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - if !arePeersEqual(previousPeerEntry?.peer, peerEntry.peer) { - self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), size: CGSize(width: 240.0, height: 240.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false, blurred: true)) - self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)) + if previousPeerEntry?.peer != peerEntry.peer { + self.backdropAvatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 240.0, height: 240.0), round: false, font: avatarPlaceholderFont(size: 78.0), drawLetters: false, blurred: true)) + self.avatarNode.setSignal(peerAvatarCompleteImage(account: self.context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)) } var gradient: VoiceChatBlobNode.Gradient = .active @@ -771,7 +777,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { } var microphoneColor = UIColor.white - var titleAttributedString = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white) + var titleAttributedString = NSAttributedString(string: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.semibold(15.0), textColor: .white) if mutedForYou { microphoneColor = destructiveColor @@ -829,7 +835,7 @@ final class VoiceChatMainStageNode: ASDisplayNode { self.audioLevelNode.isHidden = hidden } - func update(peer: (peer: PeerId, endpointId: String?, isMyPeer: Bool, isPresentation: Bool, isPaused: Bool)?, isReady: Bool = true, waitForFullSize: Bool, completion: (() -> Void)? = nil) { + func update(peer: (peer: EnginePeer.Id, endpointId: String?, isMyPeer: Bool, isPresentation: Bool, isPaused: Bool)?, isReady: Bool = true, waitForFullSize: Bool, completion: (() -> Void)? = nil) { let previousPeer = self.currentPeer if previousPeer?.0 == peer?.0 && previousPeer?.1 == peer?.1 && previousPeer?.2 == peer?.2 && previousPeer?.3 == peer?.3 && previousPeer?.4 == peer?.4 { completion?() diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift index 8d61502466..0e5c02ff8e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -158,6 +158,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs b/submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs index 5822db8b5f..07f74e3963 100644 --- a/submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs +++ b/submodules/TelegramCore/FlatSerialization/Models/InstantPageBlock.fbs @@ -34,7 +34,10 @@ union InstantPageBlock_Value { InstantPageBlock_Table, InstantPageBlock_Details, InstantPageBlock_RelatedArticles, - InstantPageBlock_Map + InstantPageBlock_Map, + InstantPageBlock_Heading, + InstantPageBlock_Formula, + InstantPageBlock_Thinking } table InstantPageBlock { @@ -70,6 +73,7 @@ table InstantPageBlock_Paragraph { table InstantPageBlock_Preformatted { text:RichText (id: 0, required); + language:string (id: 1); } table InstantPageBlock_Footer { @@ -88,8 +92,9 @@ table InstantPageBlock_List { } table InstantPageBlock_BlockQuote { - text:RichText (id: 0, required); + text:RichText (id: 0); caption:RichText (id: 1, required); + blocks:[InstantPageBlock] (id: 2); } table InstantPageBlock_PullQuote { @@ -184,6 +189,19 @@ table InstantPageBlock_Map { caption:InstantPageCaption (id: 4, required); } +table InstantPageBlock_Heading { + text:RichText (id: 0, required); + level:int32 (id: 1); +} + +table InstantPageBlock_Formula { + latex:string (id: 0, required); +} + +table InstantPageBlock_Thinking { + text:RichText (id: 0, required); +} + table InstantPageCaption { text:RichText (id: 0, required); credit:RichText (id: 1, required); @@ -202,11 +220,13 @@ table InstantPageListItem { table InstantPageListItem_Text { text:RichText (id: 0, required); number:string (id: 1); + checkState:int32 (id: 2); } table InstantPageListItem_Blocks { blocks:[InstantPageBlock] (id: 0, required); number:string (id: 1); + checkState:int32 (id: 2); } table InstantPageListItem_Unknown {} diff --git a/submodules/TelegramCore/FlatSerialization/Models/RichText.fbs b/submodules/TelegramCore/FlatSerialization/Models/RichText.fbs index 7a1da32aff..3925effab6 100644 --- a/submodules/TelegramCore/FlatSerialization/Models/RichText.fbs +++ b/submodules/TelegramCore/FlatSerialization/Models/RichText.fbs @@ -19,7 +19,20 @@ union RichText_Value { RichText_Marked, RichText_Phone, RichText_Image, - RichText_Anchor + RichText_Anchor, + RichText_Formula, + RichText_CustomEmoji, + RichText_AutoEmail, + RichText_AutoPhone, + RichText_AutoUrl, + RichText_BankCard, + RichText_BotCommand, + RichText_Cashtag, + RichText_Hashtag, + RichText_Mention, + RichText_MentionName, + RichText_Spoiler, + RichText_Date } table RichText { @@ -92,4 +105,60 @@ table RichText_Image { table RichText_Anchor { text:RichText (id: 0, required); name:string (id: 1, required); -} \ No newline at end of file +} + +table RichText_Formula { + latex:string (id: 0, required); +} + +table RichText_CustomEmoji { + fileId:long (id: 0); + alt:string (id: 1, required); +} + +table RichText_AutoEmail { + text:RichText (id: 0, required); +} + +table RichText_AutoPhone { + text:RichText (id: 0, required); +} + +table RichText_AutoUrl { + text:RichText (id: 0, required); +} + +table RichText_BankCard { + text:RichText (id: 0, required); +} + +table RichText_BotCommand { + text:RichText (id: 0, required); +} + +table RichText_Cashtag { + text:RichText (id: 0, required); +} + +table RichText_Hashtag { + text:RichText (id: 0, required); +} + +table RichText_Mention { + text:RichText (id: 0, required); +} + +table RichText_MentionName { + text:RichText (id: 0, required); + peerId:long (id: 1); +} + +table RichText_Spoiler { + text:RichText (id: 0, required); +} + +table RichText_Date { + text:RichText (id: 0, required); + date:int (id: 1); + format:int = -1 (id: 2); +} diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index ee663bb5aa..232b84e1e8 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -128,7 +128,7 @@ public class UnauthorizedAccount { if let nextType = nextType { parsedNextType = AuthorizationCodeNextType(apiType: nextType) } - if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, _, _, syncContacts) = state.contents { + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, _, _, _, syncContacts) = state.contents { transaction.setState(UnauthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false))) } }).start() @@ -139,7 +139,7 @@ public class UnauthorizedAccount { let (futureAuthToken, apiUser) = (authorizationData.futureAuthToken, authorizationData.user) let _ = postbox.transaction({ [weak self] transaction in var syncContacts = true - if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, _, _, syncContactsValue) = state.contents { + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, _, _, _, syncContactsValue) = state.contents { syncContacts = syncContactsValue } @@ -166,7 +166,7 @@ public class UnauthorizedAccount { let termsOfService = authorizationSignUpRequiredData.termsOfService let _ = postbox.transaction({ [weak self] transaction in if let self { - if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, _, _, syncContacts) = state.contents { + if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, _, _, _, syncContacts) = state.contents { let _ = beginSignUp( account: self, data: AuthorizationSignUpData( diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 207d05b122..fd5c7fa427 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -13,6 +13,11 @@ struct AccountStateChannelState: Equatable { var pts: Int32 } +enum PeerLiveTypingDraftUpdateContent { + case plain(text: String, entities: [MessageTextEntity]) + case rich(RichTextMessageAttribute) +} + final class AccountInitialState { let state: AuthorizedAccountState.State let peerIds: Set @@ -59,6 +64,16 @@ enum AccountStateGlobalNotificationSettingsSubject { case channels } +struct UpdatedApiPresence { + var status: Api.UserStatus + var isMin: Bool + + init(status: Api.UserStatus, isMin: Bool) { + self.status = status + self.isMin = isMin + } +} + enum AccountStateMutationOperation { case AddMessages([StoreMessage], AddMessagesLocation) case AddScheduledMessages([StoreMessage]) @@ -88,12 +103,12 @@ enum AccountStateMutationOperation { case UpdateCachedPeerData(PeerId, (CachedPeerData?) -> CachedPeerData?) case UpdateMessagesPinned([MessageId], Bool) case MergeApiUsers([Api.User]) - case MergePeerPresences([PeerId: Api.UserStatus], Bool) + case MergePeerPresences([PeerId: UpdatedApiPresence], Bool) case UpdateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) case AddSecretMessages([Api.EncryptedMessage]) case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32) case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?) - case AddPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId, id: Int64, timestamp: Int32, peerId: PeerId, text: String, entities: [MessageTextEntity]) + case AddPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId, id: Int64, timestamp: Int32, peerId: PeerId, content: PeerLiveTypingDraftUpdateContent) case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation) case UpdatePinnedSavedItemIds(AccountStateUpdatePinnedItemIdsOperation) case UpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) @@ -109,7 +124,6 @@ enum AccountStateMutationOperation { case UpdateLangPack(String, Api.LangPackDifference?) case UpdateMinAvailableMessage(MessageId) case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) - case UpdatePeersNearby([PeerNearby]) case UpdateTheme(TelegramTheme) case SyncChatListFilters case UpdateChatListFilterOrder(order: [Int32]) @@ -131,6 +145,9 @@ enum AccountStateMutationOperation { case UpdateStoryStealthMode(data: Api.StoriesStealthMode) case UpdateStorySentReaction(peerId: PeerId, id: Int32, reaction: Api.Reaction) case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String) + case UpdateNewBotConnection(confirmed: Bool, botId: PeerId, date: Int32?, device: String?, location: String?) + case UpdateWebBrowserSettings(openExternalBrowser: Bool) + case UpdateWebBrowserException(openExternalBrowser: Bool?, delete: Bool, exception: AccountWebBrowserException) case UpdateWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) case UpdateStarsBalance(peerId: PeerId, currency: CurrencyAmount.Currency, balance: StarsAmount) case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances) @@ -227,6 +244,7 @@ struct AccountMutableState { var storedMessagesByPeerIdAndTimestamp: [PeerId: Set] var displayAlerts: [(text: String, isDropAuth: Bool)] = [] var dismissBotWebViews: [Int64] = [] + var joinChatWebViewDecisions: [JoinChatWebViewDecision] = [] var insertedPeers: [PeerId: Peer] = [:] @@ -259,7 +277,7 @@ struct AccountMutableState { self.updatedOutgoingUniqueMessageIds = [:] } - init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set, storedMessages: Set, storedStories: [StoryId: UpdatesStoredStory], sentScheduledMessageIds: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) { + init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set, storedMessages: Set, storedStories: [StoryId: UpdatesStoredStory], sentScheduledMessageIds: Set, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], joinChatWebViewDecisions: [JoinChatWebViewDecision], branchOperationIndex: Int) { self.initialState = initialState self.operations = operations self.state = state @@ -278,11 +296,12 @@ struct AccountMutableState { self.updatedOutgoingUniqueMessageIds = updatedOutgoingUniqueMessageIds self.displayAlerts = displayAlerts self.dismissBotWebViews = dismissBotWebViews + self.joinChatWebViewDecisions = joinChatWebViewDecisions self.branchOperationIndex = branchOperationIndex } func branch() -> AccountMutableState { - return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, sentScheduledMessageIds: self.sentScheduledMessageIds, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count) + return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, sentScheduledMessageIds: self.sentScheduledMessageIds, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, joinChatWebViewDecisions: self.joinChatWebViewDecisions, branchOperationIndex: self.operations.count) } mutating func merge(_ other: AccountMutableState) { @@ -326,6 +345,7 @@ struct AccountMutableState { self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs }) self.displayAlerts.append(contentsOf: other.displayAlerts) self.dismissBotWebViews.append(contentsOf: other.dismissBotWebViews) + self.joinChatWebViewDecisions.append(contentsOf: other.joinChatWebViewDecisions) self.resetForumTopicLists.merge(other.resetForumTopicLists, uniquingKeysWith: { lhs, _ in lhs }) } @@ -362,6 +382,10 @@ struct AccountMutableState { self.dismissBotWebViews.append(queryId) } + mutating func addJoinChatWebViewDecision(_ decision: JoinChatWebViewDecision) { + self.joinChatWebViewDecisions.append(decision) + } + mutating func deleteMessagesWithGlobalIds(_ globalIds: [Int32]) { self.addOperation(.DeleteMessagesWithGlobalIds(globalIds)) } @@ -554,11 +578,7 @@ struct AccountMutableState { mutating func updatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) { self.addOperation(.UpdatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: changedGroup)) } - - mutating func updatePeersNearby(_ peersNearby: [PeerNearby]) { - self.addOperation(.UpdatePeersNearby(peersNearby)) - } - + mutating func updateTheme(_ theme: TelegramTheme) { self.addOperation(.UpdateTheme(theme)) } @@ -570,17 +590,18 @@ struct AccountMutableState { mutating func mergeUsers(_ users: [Api.User]) { self.addOperation(.MergeApiUsers(users)) - var presences: [PeerId: Api.UserStatus] = [:] + var presences: [PeerId: UpdatedApiPresence] = [:] for user in users { switch user { - case let .user(userData): - let (id, status) = (userData.id, userData.status) - if let status = status { - presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] = status - } - break - case .userEmpty: - break + case let .user(userData): + let (id, status) = (userData.id, userData.status) + let isMin = (userData.flags & (1 << 20)) != 0 + if let status = status { + presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] = UpdatedApiPresence(status: status, isMin: isMin) + } + break + case .userEmpty: + break } } if !presences.isEmpty { @@ -589,7 +610,7 @@ struct AccountMutableState { } mutating func mergePeerPresences(_ presences: [PeerId: Api.UserStatus], explicit: Bool) { - self.addOperation(.MergePeerPresences(presences, explicit)) + self.addOperation(.MergePeerPresences(presences.mapValues({ UpdatedApiPresence(status: $0, isMin: false) }), explicit)) } mutating func updateSecretChat(chat: Api.EncryptedChat, timestamp: Int32) { @@ -608,8 +629,8 @@ struct AccountMutableState { self.addOperation(.AddPeerInputActivity(chatPeerId: chatPeerId, peerId: peerId, activity: activity)) } - mutating func addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId, id: Int64, timestamp: Int32, peerId: PeerId, text: String, entities: [MessageTextEntity]) { - self.addOperation(.AddPeerLiveTypingDraftUpdate(peerAndThreadId: peerAndThreadId, id: id, timestamp: timestamp, peerId: peerId, text: text, entities: entities)) + mutating func addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId, id: Int64, timestamp: Int32, peerId: PeerId, content: PeerLiveTypingDraftUpdateContent) { + self.addOperation(.AddPeerLiveTypingDraftUpdate(peerAndThreadId: peerAndThreadId, id: id, timestamp: timestamp, peerId: peerId, content: content)) } mutating func addUpdatePinnedItemIds(groupId: PeerGroupId, operation: AccountStateUpdatePinnedItemIdsOperation) { @@ -712,6 +733,18 @@ struct AccountMutableState { self.addOperation(.UpdateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date, device: device, location: location)) } + mutating func updateNewBotConnection(confirmed: Bool, botId: PeerId, date: Int32?, device: String?, location: String?) { + self.addOperation(.UpdateNewBotConnection(confirmed: confirmed, botId: botId, date: date, device: device, location: location)) + } + + mutating func updateWebBrowserSettings(openExternalBrowser: Bool) { + self.addOperation(.UpdateWebBrowserSettings(openExternalBrowser: openExternalBrowser)) + } + + mutating func updateWebBrowserException(openExternalBrowser: Bool?, delete: Bool, exception: AccountWebBrowserException) { + self.addOperation(.UpdateWebBrowserException(openExternalBrowser: openExternalBrowser, delete: delete, exception: exception)) + } + mutating func updateStarsBalance(peerId: PeerId, currency: CurrencyAmount.Currency, balance: StarsAmount) { self.addOperation(.UpdateStarsBalance(peerId: peerId, currency: currency, balance: balance)) } @@ -746,7 +779,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateNewBotConnection, .UpdateWebBrowserSettings, .UpdateWebBrowserException, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: break case let .AddMessages(messages, location): for message in messages { @@ -884,7 +917,6 @@ struct AccountReplayedFinalState { let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] - let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? let updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] @@ -915,10 +947,10 @@ struct AccountFinalStateEvents { let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] - let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] let dismissBotWebViews: [Int64] + let joinChatWebViewDecisions: [JoinChatWebViewDecision] let delayNotificatonsUntil: Int32? let updatedMaxMessageId: Int32? let updatedQts: Int32? @@ -938,10 +970,10 @@ struct AccountFinalStateEvents { let updatedEmojiGameInfo: EmojiGameInfo? var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.joinChatWebViewDecisions.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], joinChatWebViewDecisions: [JoinChatWebViewDecision] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -953,10 +985,10 @@ struct AccountFinalStateEvents { self.updatedGroupCallParticipants = updatedGroupCallParticipants self.groupCallMessageUpdates = groupCallMessageUpdates self.storyUpdates = storyUpdates - self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates self.displayAlerts = displayAlerts self.dismissBotWebViews = dismissBotWebViews + self.joinChatWebViewDecisions = joinChatWebViewDecisions self.delayNotificatonsUntil = delayNotificatonsUntil self.updatedMaxMessageId = updatedMaxMessageId self.updatedQts = updatedQts @@ -989,10 +1021,10 @@ struct AccountFinalStateEvents { self.updatedGroupCallParticipants = state.updatedGroupCallParticipants self.groupCallMessageUpdates = state.groupCallMessageUpdates self.storyUpdates = state.storyUpdates - self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates self.displayAlerts = state.state.state.displayAlerts self.dismissBotWebViews = state.state.state.dismissBotWebViews + self.joinChatWebViewDecisions = state.state.state.joinChatWebViewDecisions self.delayNotificatonsUntil = state.delayNotificatonsUntil self.updatedMaxMessageId = state.state.state.updatedMaxMessageId self.updatedQts = state.state.state.updatedQts @@ -1064,6 +1096,7 @@ struct AccountFinalStateEvents { isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, + joinChatWebViewDecisions: self.joinChatWebViewDecisions + other.joinChatWebViewDecisions, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 0024856b50..334e675922 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -103,6 +103,7 @@ private var declaredEncodables: Void = { declareEncodable(InlineBotMessageAttribute.self, f: { InlineBotMessageAttribute(decoder: $0) }) declareEncodable(InlineBusinessBotMessageAttribute.self, f: { InlineBusinessBotMessageAttribute(decoder: $0) }) declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) }) + declareEncodable(RichTextMessageAttribute.self, f: { RichTextMessageAttribute(decoder: $0) }) declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) }) declareEncodable(QuotedReplyMessageAttribute.self, f: { QuotedReplyMessageAttribute(decoder: $0) }) declareEncodable(ReplyStoryAttribute.self, f: { ReplyStoryAttribute(decoder: $0) }) @@ -242,6 +243,7 @@ private var declaredEncodables: Void = { declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) }) declareEncodable(ScheduledRepeatAttribute.self, f: { ScheduledRepeatAttribute(decoder: $0) }) declareEncodable(SummarizationMessageAttribute.self, f: { SummarizationMessageAttribute(decoder: $0) }) + declareEncodable(GuestChatMessageAttribute.self, f: { GuestChatMessageAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/AccountManager/AccountManagerResources.swift b/submodules/TelegramCore/Sources/AccountManager/AccountManagerResources.swift new file mode 100644 index 0000000000..9c53cd3732 --- /dev/null +++ b/submodules/TelegramCore/Sources/AccountManager/AccountManagerResources.swift @@ -0,0 +1,57 @@ +import Foundation +import SwiftSignalKit +import Postbox + +public final class AccountManagerResources { + private let mediaBox: MediaBox + + init(mediaBox: MediaBox) { + self.mediaBox = mediaBox + } + + public func data( + resource: EngineMediaResource, + pathExtension: String? = nil, + waitUntilFetchStatus: Bool = false, + attemptSynchronously: Bool = false + ) -> Signal { + return self.mediaBox.resourceData( + resource._asResource(), + pathExtension: pathExtension, + option: .complete(waitUntilFetchStatus: waitUntilFetchStatus), + attemptSynchronously: attemptSynchronously + ) + |> map { EngineMediaResource.ResourceData($0) } + } + + public func storeResourceData(id: EngineMediaResource.Id, data: Data, synchronous: Bool = false) { + self.mediaBox.storeResourceData(MediaResourceId(id.stringRepresentation), data: data, synchronous: synchronous) + } + + public func completedResourcePath(resource: EngineMediaResource, pathExtension: String? = nil) -> String? { + return self.mediaBox.completedResourcePath(resource._asResource(), pathExtension: pathExtension) + } + + public func completedResourcePath(id: EngineMediaResource.Id, pathExtension: String? = nil) -> String? { + return self.mediaBox.completedResourcePath(id: MediaResourceId(id.stringRepresentation), pathExtension: pathExtension) + } + + public func status(resource: EngineMediaResource, approximateSynchronousValue: Bool = false) -> Signal { + return self.mediaBox.resourceStatus(resource._asResource(), approximateSynchronousValue: approximateSynchronousValue) + |> map { EngineMediaResource.FetchStatus($0) } + } + + public func moveResourceData(from: EngineMediaResource.Id, to: EngineMediaResource.Id, synchronous: Bool = false) { + self.mediaBox.moveResourceData(from: MediaResourceId(from.stringRepresentation), to: MediaResourceId(to.stringRepresentation), synchronous: synchronous) + } + + public func fetch(reference: MediaResourceReference, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType) -> Signal { + return fetchedMediaResource(mediaBox: self.mediaBox, userLocation: userLocation, userContentType: userContentType, reference: reference) + } +} + +public extension AccountManager { + var resources: AccountManagerResources { + return AccountManagerResources(mediaBox: self.mediaBox) + } +} diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiUtils.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiUtils.swift index 43a8d691e6..7c585c3d32 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiUtils.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiUtils.swift @@ -14,6 +14,10 @@ public extension PeerReference { return PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)) } } + + init?(_ peer: EnginePeer) { + self.init(peer._asPeer()) + } } extension PeerReference { @@ -79,6 +83,60 @@ func apiInputPeer(_ peer: Peer) -> Api.InputPeer? { } } +private func apiInputPeerFromSourceMessage(_ sourceMessageId: MessageId?, transaction: Transaction) -> (MessageId, Api.InputPeer)? { + guard let sourceMessageId else { + return nil + } + guard let sourcePeer = transaction.getPeer(sourceMessageId.peerId) else { + return nil + } + guard let inputPeer = apiInputPeer(sourcePeer) else { + return nil + } + return (sourceMessageId, inputPeer) +} + +func apiInputPeer(_ peer: Peer, sourceMessageId: MessageId?, transaction: Transaction) -> Api.InputPeer? { + switch peer { + case let user as TelegramUser: + if let accessHash = user.accessHash { + switch accessHash { + case let .personal(value): + return Api.InputPeer.inputPeerUser(.init(userId: user.id.id._internalGetInt64Value(), accessHash: value)) + case let .genericPublic(value): + if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputPeer.inputPeerUserFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, userId: user.id.id._internalGetInt64Value())) + } + return Api.InputPeer.inputPeerUser(.init(userId: user.id.id._internalGetInt64Value(), accessHash: value)) + } + } else if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputPeer.inputPeerUserFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, userId: user.id.id._internalGetInt64Value())) + } else { + return nil + } + case let group as TelegramGroup: + return Api.InputPeer.inputPeerChat(.init(chatId: group.id.id._internalGetInt64Value())) + case let channel as TelegramChannel: + if let accessHash = channel.accessHash { + switch accessHash { + case let .personal(value): + return Api.InputPeer.inputPeerChannel(.init(channelId: channel.id.id._internalGetInt64Value(), accessHash: value)) + case let .genericPublic(value): + if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputPeer.inputPeerChannelFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, channelId: channel.id.id._internalGetInt64Value())) + } + return Api.InputPeer.inputPeerChannel(.init(channelId: channel.id.id._internalGetInt64Value(), accessHash: value)) + } + } else if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputPeer.inputPeerChannelFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, channelId: channel.id.id._internalGetInt64Value())) + } else { + return nil + } + default: + return nil + } +} + func apiInputPeerOrSelf(_ peer: Peer, accountPeerId: PeerId) -> Api.InputPeer? { if peer.id == accountPeerId { return .inputPeerSelf @@ -94,6 +152,27 @@ func apiInputChannel(_ peer: Peer) -> Api.InputChannel? { } } +func apiInputChannel(_ peer: Peer, sourceMessageId: MessageId?, transaction: Transaction) -> Api.InputChannel? { + guard let channel = peer as? TelegramChannel else { + return nil + } + if let accessHash = channel.accessHash { + switch accessHash { + case let .personal(value): + return Api.InputChannel.inputChannel(.init(channelId: channel.id.id._internalGetInt64Value(), accessHash: value)) + case let .genericPublic(value): + if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputChannel.inputChannelFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, channelId: channel.id.id._internalGetInt64Value())) + } + return Api.InputChannel.inputChannel(.init(channelId: channel.id.id._internalGetInt64Value(), accessHash: value)) + } + } else if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputChannel.inputChannelFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, channelId: channel.id.id._internalGetInt64Value())) + } else { + return nil + } +} + func apiInputUser(_ peer: Peer) -> Api.InputUser? { if let user = peer as? TelegramUser, let accessHash = user.accessHash { return Api.InputUser.inputUser(.init(userId: user.id.id._internalGetInt64Value(), accessHash: accessHash.value)) @@ -102,6 +181,27 @@ func apiInputUser(_ peer: Peer) -> Api.InputUser? { } } +func apiInputUser(_ peer: Peer, sourceMessageId: MessageId?, transaction: Transaction) -> Api.InputUser? { + guard let user = peer as? TelegramUser else { + return nil + } + if let accessHash = user.accessHash { + switch accessHash { + case let .personal(value): + return Api.InputUser.inputUser(.init(userId: user.id.id._internalGetInt64Value(), accessHash: value)) + case let .genericPublic(value): + if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputUser.inputUserFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, userId: user.id.id._internalGetInt64Value())) + } + return Api.InputUser.inputUser(.init(userId: user.id.id._internalGetInt64Value(), accessHash: value)) + } + } else if let (sourceMessageId, sourcePeer) = apiInputPeerFromSourceMessage(sourceMessageId, transaction: transaction) { + return Api.InputUser.inputUserFromMessage(.init(peer: sourcePeer, msgId: sourceMessageId.id, userId: user.id.id._internalGetInt64Value())) + } else { + return nil + } +} + func apiInputSecretChat(_ peer: Peer) -> Api.InputEncryptedChat? { if let chat = peer as? TelegramSecretChat { return Api.InputEncryptedChat.inputEncryptedChat(.init(chatId: Int32(peer.id.id._internalGetInt64Value()), accessHash: chat.accessHash)) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift index 2a56151233..8fc467b2be 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ChatContextResult.swift @@ -5,17 +5,13 @@ import TelegramApi import MtProtoKit -public enum ChatContextResultMessageDecodingError: Error { - case generic -} - public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable { enum CodingKeys: String, CodingKey { case data } case auto(caption: String, entities: TextEntitiesMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?) - case text(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, previewParameters: WebpagePreviewMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?) + case text(text: String, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, disableUrlPreview: Bool, previewParameters: WebpagePreviewMessageAttribute?, replyMarkup: ReplyMarkupMessageAttribute?) case mapLocation(media: TelegramMediaMap, replyMarkup: ReplyMarkupMessageAttribute?) case contact(media: TelegramMediaContact, replyMarkup: ReplyMarkupMessageAttribute?) case invoice(media: TelegramMediaInvoice, replyMarkup: ReplyMarkupMessageAttribute?) @@ -26,7 +22,7 @@ public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable { case 0: self = .auto(caption: decoder.decodeStringForKey("c", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) case 1: - self = .text(text: decoder.decodeStringForKey("t", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, disableUrlPreview: decoder.decodeInt32ForKey("du", orElse: 0) != 0, previewParameters: decoder.decodeObjectForKey("prp") as? WebpagePreviewMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) + self = .text(text: decoder.decodeStringForKey("t", orElse: ""), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, richText: decoder.decodeObjectForKey("rt", decoder: { RichTextMessageAttribute(decoder: $0) }) as? RichTextMessageAttribute, disableUrlPreview: decoder.decodeInt32ForKey("du", orElse: 0) != 0, previewParameters: decoder.decodeObjectForKey("prp") as? WebpagePreviewMessageAttribute, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) case 2: self = .mapLocation(media: decoder.decodeObjectForKey("l") as! TelegramMediaMap, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) case 3: @@ -55,7 +51,7 @@ public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable { } else { encoder.encodeNil(forKey: "m") } - case let .text(text, entities, disableUrlPreview, previewParameters, replyMarkup): + case let .text(text, entities, richText, disableUrlPreview, previewParameters, replyMarkup): encoder.encodeInt32(1, forKey: "_v") encoder.encodeString(text, forKey: "t") if let entities = entities { @@ -63,6 +59,11 @@ public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable { } else { encoder.encodeNil(forKey: "e") } + if let richText { + encoder.encodeObject(richText, forKey: "rt") + } else { + encoder.encodeNil(forKey: "rt") + } encoder.encodeInt32(disableUrlPreview ? 1 : 0, forKey: "du") if let previewParameters = previewParameters { encoder.encodeObject(previewParameters, forKey: "prp") @@ -152,14 +153,17 @@ public enum ChatContextResultMessage: PostboxCoding, Equatable, Codable { } else { return false } - case let .text(lhsText, lhsEntities, lhsDisableUrlPreview, lhsPreviewParameters, lhsReplyMarkup): - if case let .text(rhsText, rhsEntities, rhsDisableUrlPreview, rhsPreviewParameters, rhsReplyMarkup) = rhs { + case let .text(lhsText, lhsEntities, lhsRichText, lhsDisableUrlPreview, lhsPreviewParameters, lhsReplyMarkup): + if case let .text(rhsText, rhsEntities, rhsRichText, rhsDisableUrlPreview, rhsPreviewParameters, rhsReplyMarkup) = rhs { if lhsText != rhsText { return false } if lhsEntities != rhsEntities { return false } + if lhsRichText != rhsRichText { + return false + } if lhsDisableUrlPreview != rhsDisableUrlPreview { return false } @@ -512,12 +516,18 @@ extension ChatContextResultMessage { parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) } let leadingPreview = (flags & (1 << 3)) != 0 - self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, previewParameters: WebpagePreviewMessageAttribute( + self = .text(text: message, entities: parsedEntities, richText: nil, disableUrlPreview: (flags & (1 << 0)) != 0, previewParameters: WebpagePreviewMessageAttribute( leadingPreview: leadingPreview, forceLargeMedia: nil, isManuallyAdded: false, isSafe: false ), replyMarkup: parsedReplyMarkup) + case let .botInlineMessageRichMessage(botInlineMessageRichMessage): + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = botInlineMessageRichMessage.replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .text(text: "", entities: nil, richText: RichTextMessageAttribute(apiRichMessage: botInlineMessageRichMessage.richMessage), disableUrlPreview: false, previewParameters: nil, replyMarkup: parsedReplyMarkup) case let .botInlineMessageMediaGeo(botInlineMessageMediaGeoData): let (_, geo, heading, period, proximityNotificationRadius, replyMarkup) = (botInlineMessageMediaGeoData.flags, botInlineMessageMediaGeoData.geo, botInlineMessageMediaGeoData.heading, botInlineMessageMediaGeoData.period, botInlineMessageMediaGeoData.proximityNotificationRadius, botInlineMessageMediaGeoData.replyMarkup) let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, liveProximityNotificationRadius: proximityNotificationRadius, heading: heading) diff --git a/submodules/TelegramCore/Sources/ApiUtils/CloudMediaResourceParameters.swift b/submodules/TelegramCore/Sources/ApiUtils/CloudMediaResourceParameters.swift deleted file mode 100644 index e9e165bf24..0000000000 --- a/submodules/TelegramCore/Sources/ApiUtils/CloudMediaResourceParameters.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -public enum CloudMediaResourceLocation: Equatable { - case photo(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String) - case file(id: Int64, accessHash: Int64, fileReference: Data, thumbSize: String) - case peerPhoto(peer: PeerReference, fullSize: Bool, volumeId: Int64, localId: Int64) - case stickerPackThumbnail(packReference: StickerPackReference, volumeId: Int64, localId: Int64) -} diff --git a/submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift b/submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift index 262901d2a0..a5d4d4ad1f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/InstantPage.swift @@ -16,14 +16,43 @@ extension InstantPageCaption { public extension InstantPageListItem { var num: String? { switch self { - case let .text(_, num): + case let .text(_, num, _): return num - case let .blocks(_, num): + case let .blocks(_, num, _): return num default: return nil } } + + var checked: Bool? { + switch self { + case let .text(_, _, checked): + return checked + case let .blocks(_, _, checked): + return checked + default: + return nil + } + } + + static func checkedFromApiFlags(_ flags: Int32) -> Bool? { + guard (flags & (1 << 0)) != 0 else { + return nil + } + return (flags & (1 << 1)) != 0 + } + + static func apiFlags(fromChecked checked: Bool?) -> Int32 { + guard let checked else { + return 0 + } + var flags: Int32 = 1 << 0 + if checked { + flags |= (1 << 1) + } + return flags + } } extension InstantPageListItem { @@ -31,10 +60,10 @@ extension InstantPageListItem { switch apiListItem { case let .pageListItemText(pageListItemTextData): let text = pageListItemTextData.text - self = .text(RichText(apiText: text), nil) + self = .text(RichText(apiText: text), nil, InstantPageListItem.checkedFromApiFlags(pageListItemTextData.flags)) case let .pageListItemBlocks(pageListItemBlocksData): let blocks = pageListItemBlocksData.blocks - self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), nil) + self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), nil, InstantPageListItem.checkedFromApiFlags(pageListItemBlocksData.flags)) } } @@ -42,10 +71,43 @@ extension InstantPageListItem { switch apiListOrderedItem { case let .pageListOrderedItemText(pageListOrderedItemTextData): let (num, text) = (pageListOrderedItemTextData.num, pageListOrderedItemTextData.text) - self = .text(RichText(apiText: text), num) + self = .text(RichText(apiText: text), num, InstantPageListItem.checkedFromApiFlags(pageListOrderedItemTextData.flags)) case let .pageListOrderedItemBlocks(pageListOrderedItemBlocksData): let (num, blocks) = (pageListOrderedItemBlocksData.num, pageListOrderedItemBlocksData.blocks) - self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), num) + self = .blocks(blocks.map({ InstantPageBlock(apiBlock: $0) }), num, InstantPageListItem.checkedFromApiFlags(pageListOrderedItemBlocksData.flags)) + } + } + + func apiInputPageListItem() -> Api.PageListItem { + switch self { + case let .text(value, _, checked): + return .pageListItemText(Api.PageListItem.Cons_pageListItemText(flags: InstantPageListItem.apiFlags(fromChecked: checked), text: value.apiRichText())) + case let .blocks(blocks, _, checked): + return .pageListItemBlocks(Api.PageListItem.Cons_pageListItemBlocks(flags: InstantPageListItem.apiFlags(fromChecked: checked), blocks: blocks.compactMap { $0.apiInputBlock() })) + case .unknown: + return .pageListItemText(Api.PageListItem.Cons_pageListItemText(flags: 0, text: .textPlain(Api.RichText.Cons_textPlain(text: "")))) + } + } + + func apiInputPageOrderedListItem() -> Api.PageListOrderedItem { + switch self { + case let .text(value, num, checked): + var flags: Int32 = InstantPageListItem.apiFlags(fromChecked: checked) + + if num != nil { + flags |= (1 << 2) + } + return .pageListOrderedItemText(Api.PageListOrderedItem.Cons_pageListOrderedItemText(flags: flags, num: num, text: value.apiRichText(), value: nil, type: nil)) + case let .blocks(blocks, num, checked): + var flags: Int32 = InstantPageListItem.apiFlags(fromChecked: checked) + + if num != nil { + flags |= (1 << 2) + } + + return .pageListOrderedItemBlocks(Api.PageListOrderedItem.Cons_pageListOrderedItemBlocks(flags: flags, num: num, blocks: blocks.compactMap { $0.apiInputBlock() }, value: nil, type: nil)) + case .unknown: + return .pageListOrderedItemText(Api.PageListOrderedItem.Cons_pageListOrderedItemText(flags: 0, num: nil, text: .textPlain(Api.RichText.Cons_textPlain(text: "")), value: nil, type: nil)) } } } @@ -53,33 +115,85 @@ extension InstantPageListItem { extension InstantPageTableCell { convenience init(apiTableCell: Api.PageTableCell) { switch apiTableCell { - case let .pageTableCell(pageTableCellData): - let (flags, text, colspan, rowspan) = (pageTableCellData.flags, pageTableCellData.text, pageTableCellData.colspan, pageTableCellData.rowspan) - var alignment = TableHorizontalAlignment.left - if (flags & (1 << 3)) != 0 { - alignment = .center - } else if (flags & (1 << 4)) != 0 { - alignment = .right - } - var verticalAlignment = TableVerticalAlignment.top - if (flags & (1 << 5)) != 0 { - verticalAlignment = .middle - } else if (flags & (1 << 6)) != 0 { - verticalAlignment = .bottom - } - self.init(text: text != nil ? RichText(apiText: text!) : nil, header: (flags & (1 << 0)) != 0, alignment: alignment, verticalAlignment: verticalAlignment, colspan: colspan ?? 0, rowspan: rowspan ?? 0) + case let .pageTableCell(pageTableCellData): + let (flags, text, colspan, rowspan) = (pageTableCellData.flags, pageTableCellData.text, pageTableCellData.colspan, pageTableCellData.rowspan) + var alignment = TableHorizontalAlignment.left + if (flags & (1 << 3)) != 0 { + alignment = .center + } else if (flags & (1 << 4)) != 0 { + alignment = .right + } + var verticalAlignment = TableVerticalAlignment.top + if (flags & (1 << 5)) != 0 { + verticalAlignment = .middle + } else if (flags & (1 << 6)) != 0 { + verticalAlignment = .bottom + } + self.init(text: text != nil ? RichText(apiText: text!) : nil, header: (flags & (1 << 0)) != 0, alignment: alignment, verticalAlignment: verticalAlignment, colspan: colspan ?? 0, rowspan: rowspan ?? 0) } } + + func inputPageTableCell() -> Api.PageTableCell { + var flags: Int32 = 0 + + switch self.alignment { + case .left: + break + case .center: + flags |= (1 << 3) + case .right: + flags |= (1 << 4) + } + + switch self.verticalAlignment { + case .top: + break + case .middle: + flags |= (1 << 5) + case .bottom: + flags |= (1 << 6) + } + + if self.header { + flags |= (1 << 0) + } + + var inputText: Api.RichText? + if let text = self.text { + inputText = text.apiRichText() + if inputText != nil { + flags |= (1 << 7) + } + } + + var inputColspan: Int32? + if self.colspan != 0 { + inputColspan = self.colspan + flags |= (1 << 1) + } + + var inputRowspan: Int32? + if self.rowspan != 0 { + inputRowspan = self.rowspan + flags |= (1 << 2) + } + + return .pageTableCell(Api.PageTableCell.Cons_pageTableCell(flags: flags, text: inputText, colspan: inputColspan, rowspan: inputRowspan)) + } } extension InstantPageTableRow { convenience init(apiTableRow: Api.PageTableRow) { switch apiTableRow { - case let .pageTableRow(pageTableRowData): - let cells = pageTableRowData.cells - self.init(cells: cells.map({ InstantPageTableCell(apiTableCell: $0) })) + case let .pageTableRow(pageTableRowData): + let cells = pageTableRowData.cells + self.init(cells: cells.map({ InstantPageTableCell(apiTableCell: $0) })) } } + + func inputPageTableRow() -> Api.PageTableRow { + return .pageTableRow(Api.PageTableRow.Cons_pageTableRow(cells: self.cells.map { $0.inputPageTableCell() })) + } } extension InstantPageRelatedArticle { @@ -121,7 +235,7 @@ extension InstantPageBlock { self = .paragraph(RichText(apiText: text)) case let .pageBlockPreformatted(pageBlockPreformattedData): let text = pageBlockPreformattedData.text - self = .preformatted(RichText(apiText: text)) + self = .preformatted(text: RichText(apiText: text), language: nil) case let .pageBlockFooter(pageBlockFooterData): let text = pageBlockFooterData.text self = .footer(RichText(apiText: text)) @@ -132,7 +246,9 @@ extension InstantPageBlock { self = .anchor(name) case let .pageBlockBlockquote(pageBlockBlockquoteData): let (text, caption) = (pageBlockBlockquoteData.text, pageBlockBlockquoteData.caption) - self = .blockQuote(text: RichText(apiText: text), caption: RichText(apiText: caption)) + self = .blockQuote(blocks: [.paragraph(RichText(apiText: text))], caption: RichText(apiText: caption)) + case let .pageBlockBlockquoteBlocks(pageBlockBlockquoteBlocksData): + self = .blockQuote(blocks: pageBlockBlockquoteBlocksData.blocks.map { InstantPageBlock(apiBlock: $0) }, caption: RichText(apiText: pageBlockBlockquoteBlocksData.caption)) case let .pageBlockPullquote(pageBlockPullquoteData): let (text, caption) = (pageBlockPullquoteData.text, pageBlockPullquoteData.caption) self = .pullQuote(text: RichText(apiText: text), caption: RichText(apiText: caption)) @@ -198,6 +314,114 @@ extension InstantPageBlock { default: self = .unsupported } + case let .pageBlockHeading1(pageBlockHeading1): + self = .heading(text: RichText(apiText: pageBlockHeading1.text), level: 1) + case let .pageBlockHeading2(pageBlockHeading2): + self = .heading(text: RichText(apiText: pageBlockHeading2.text), level: 2) + case let .pageBlockHeading3(pageBlockHeading3): + self = .heading(text: RichText(apiText: pageBlockHeading3.text), level: 3) + case let .pageBlockHeading4(pageBlockHeading4): + self = .heading(text: RichText(apiText: pageBlockHeading4.text), level: 4) + case let .pageBlockHeading5(pageBlockHeading5): + self = .heading(text: RichText(apiText: pageBlockHeading5.text), level: 5) + case let .pageBlockHeading6(pageBlockHeading6): + self = .heading(text: RichText(apiText: pageBlockHeading6.text), level: 6) + case let .pageBlockMath(pageBlockMath): + self = .formula(latex: pageBlockMath.source) + case let .pageBlockThinking(pageBlockThinking): + self = .thinking(RichText(apiText: pageBlockThinking.text)) + case .inputPageBlockMap: + self = .unsupported + } + } + + func apiInputBlock() -> Api.PageBlock? { + switch self { + case .unsupported, .title, .subtitle, .kicker, .header, .subheader, .cover, .channelBanner, .authorDate, .relatedArticles, .webEmbed, .postEmbed, .thinking: + return nil + case let .heading(text, level): + let block: Api.PageBlock + switch level { + case 0, 1: + block = .pageBlockHeading1(Api.PageBlock.Cons_pageBlockHeading1(text: text.apiRichText())) + case 2: + block = .pageBlockHeading2(Api.PageBlock.Cons_pageBlockHeading2(text: text.apiRichText())) + case 3: + block = .pageBlockHeading3(Api.PageBlock.Cons_pageBlockHeading3(text: text.apiRichText())) + case 4: + block = .pageBlockHeading4(Api.PageBlock.Cons_pageBlockHeading4(text: text.apiRichText())) + case 5: + block = .pageBlockHeading5(Api.PageBlock.Cons_pageBlockHeading5(text: text.apiRichText())) + default: + block = .pageBlockHeading6(Api.PageBlock.Cons_pageBlockHeading6(text: text.apiRichText())) + } + return block + case let .formula(latex): + return .pageBlockMath(Api.PageBlock.Cons_pageBlockMath(source: latex)) + case let .paragraph(value): + return .pageBlockParagraph(Api.PageBlock.Cons_pageBlockParagraph(text: value.apiRichText())) + case let .preformatted(text, language): + return .pageBlockPreformatted(Api.PageBlock.Cons_pageBlockPreformatted(text: text.apiRichText(), language: language ?? "")) + case let .footer(value): + return .pageBlockFooter(Api.PageBlock.Cons_pageBlockFooter(text: value.apiRichText())) + case .divider: + return .pageBlockDivider + case let .anchor(value): + return .pageBlockAnchor(Api.PageBlock.Cons_pageBlockAnchor(name: value)) + case let .list(items, ordered): + if ordered { + return .pageBlockOrderedList(Api.PageBlock.Cons_pageBlockOrderedList(flags: 0, items: items.map { $0.apiInputPageOrderedListItem() }, start: nil, type: nil)) + } else { + return .pageBlockList(Api.PageBlock.Cons_pageBlockList(items: items.map { $0.apiInputPageListItem() })) + } + case let .blockQuote(blocks, caption): + if blocks.isEmpty { + return .pageBlockBlockquote(Api.PageBlock.Cons_pageBlockBlockquote(text: RichText.empty.apiRichText(), caption: caption.apiRichText())) + } + if blocks.count == 1, case let .paragraph(text) = blocks[0] { + return .pageBlockBlockquote(Api.PageBlock.Cons_pageBlockBlockquote(text: text.apiRichText(), caption: caption.apiRichText())) + } + return .pageBlockBlockquoteBlocks(Api.PageBlock.Cons_pageBlockBlockquoteBlocks(blocks: blocks.compactMap { $0.apiInputBlock() }, caption: caption.apiRichText())) + case let .pullQuote(text, caption): + return .pageBlockPullquote(Api.PageBlock.Cons_pageBlockPullquote(text: text.apiRichText(), caption: caption.apiRichText())) + case let .image(id, caption, url, webpageId): + var flags: Int32 = 0 + if url != nil && webpageId != nil { + flags |= 1 << 0 + } + return .pageBlockPhoto(Api.PageBlock.Cons_pageBlockPhoto(flags: flags, photoId: id.id, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())), url: url, webpageId: webpageId?.id)) + case let .video(id, caption, autoplay, loop): + var flags: Int32 = 0 + if autoplay { + flags |= 1 << 0 + } + if loop { + flags |= 1 << 1 + } + return .pageBlockVideo(Api.PageBlock.Cons_pageBlockVideo(flags: flags, videoId: id.id, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())))) + case let .audio(id, caption): + return .pageBlockAudio(Api.PageBlock.Cons_pageBlockAudio(audioId: id.id, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())))) + case let .collage(items, caption): + return .pageBlockCollage(Api.PageBlock.Cons_pageBlockCollage(items: items.compactMap { $0.apiInputBlock() }, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())))) + case let .slideshow(items, caption): + return .pageBlockSlideshow(Api.PageBlock.Cons_pageBlockSlideshow(items: items.compactMap { $0.apiInputBlock() }, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())))) + case let .table(title, rows, bordered, striped): + var flags: Int32 = 0 + if bordered { + flags |= (1 << 0) + } + if striped { + flags |= (1 << 1) + } + return .pageBlockTable(Api.PageBlock.Cons_pageBlockTable(flags: flags, title: title.apiRichText(), rows: rows.map { $0.inputPageTableRow() })) + case let .details(title, blocks, expanded): + var flags: Int32 = 0 + if expanded { + flags |= (1 << 0) + } + return .pageBlockDetails(Api.PageBlock.Cons_pageBlockDetails(flags: flags, blocks: blocks.compactMap { $0.apiInputBlock() }, title: title.apiRichText())) + case let .map(latitude, longitude, zoom, dimensions, caption): + return .inputPageBlockMap(Api.PageBlock.Cons_inputPageBlockMap(geo: .inputGeoPoint(Api.InputGeoPoint.Cons_inputGeoPoint(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)), zoom: zoom, w: dimensions.width, h: dimensions.height, caption: .pageCaption(Api.PageCaption.Cons_pageCaption(text: caption.text.apiRichText(), credit: caption.credit.apiRichText())))) } } } @@ -212,15 +436,15 @@ extension InstantPage { let url: String let views: Int32? switch apiPage { - case let .page(pageData): - let (flags, pageUrl, pageBlocks, pagePhotos, pageDocuments, pageViews) = (pageData.flags, pageData.url, pageData.blocks, pageData.photos, pageData.documents, pageData.views) - url = pageUrl - blocks = pageBlocks - photos = pagePhotos - files = pageDocuments - isComplete = (flags & (1 << 0)) == 0 - rtl = (flags & (1 << 1)) != 0 - views = pageViews + case let .page(pageData): + let (flags, pageUrl, pageBlocks, pagePhotos, pageDocuments, pageViews) = (pageData.flags, pageData.url, pageData.blocks, pageData.photos, pageData.documents, pageData.views) + url = pageUrl + blocks = pageBlocks + photos = pagePhotos + files = pageDocuments + isComplete = (flags & (1 << 0)) == 0 + rtl = (flags & (1 << 1)) != 0 + views = pageViews } var media: [MediaId: Media] = [:] for photo in photos { diff --git a/submodules/TelegramCore/Sources/ApiUtils/RichText.swift b/submodules/TelegramCore/Sources/ApiUtils/RichText.swift index 1bb3c1d9c8..0507feaceb 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/RichText.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/RichText.swift @@ -6,53 +6,143 @@ import TelegramApi extension RichText { init(apiText: Api.RichText) { switch apiText { - case .textEmpty: - self = .empty - case let .textPlain(textPlainData): - let text = textPlainData.text - self = .plain(text) - case let .textBold(textBoldData): - let text = textBoldData.text - self = .bold(RichText(apiText: text)) - case let .textItalic(textItalicData): - let text = textItalicData.text - self = .italic(RichText(apiText: text)) - case let .textUnderline(textUnderlineData): - let text = textUnderlineData.text - self = .underline(RichText(apiText: text)) - case let .textStrike(textStrikeData): - let text = textStrikeData.text - self = .strikethrough(RichText(apiText: text)) - case let .textFixed(textFixedData): - let text = textFixedData.text - self = .fixed(RichText(apiText: text)) - case let .textUrl(textUrlData): - let (text, url, webpageId) = (textUrlData.text, textUrlData.url, textUrlData.webpageId) - self = .url(text: RichText(apiText: text), url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId)) - case let .textEmail(textEmailData): - let (text, email) = (textEmailData.text, textEmailData.email) - self = .email(text: RichText(apiText: text), email: email) - case let .textConcat(textConcatData): - let texts = textConcatData.texts - self = .concat(texts.map({ RichText(apiText: $0) })) - case let .textSubscript(textSubscriptData): - let text = textSubscriptData.text - self = .subscript(RichText(apiText: text)) - case let .textSuperscript(textSuperscriptData): - let text = textSuperscriptData.text - self = .superscript(RichText(apiText: text)) - case let .textMarked(textMarkedData): - let text = textMarkedData.text - self = .marked(RichText(apiText: text)) - case let .textPhone(textPhoneData): - let (text, phone) = (textPhoneData.text, textPhoneData.phone) - self = .phone(text: RichText(apiText: text), phone: phone) - case let .textImage(textImageData): - let (documentId, w, h) = (textImageData.documentId, textImageData.w, textImageData.h) - self = .image(id: MediaId(namespace: Namespaces.Media.CloudFile, id: documentId), dimensions: PixelDimensions(width: w, height: h)) - case let .textAnchor(textAnchorData): - let (text, name) = (textAnchorData.text, textAnchorData.name) - self = .anchor(text: RichText(apiText: text), name: name) + case .textEmpty: + self = .empty + case let .textPlain(textPlainData): + let text = textPlainData.text + self = .plain(text) + case let .textBold(textBoldData): + let text = textBoldData.text + self = .bold(RichText(apiText: text)) + case let .textItalic(textItalicData): + let text = textItalicData.text + self = .italic(RichText(apiText: text)) + case let .textUnderline(textUnderlineData): + let text = textUnderlineData.text + self = .underline(RichText(apiText: text)) + case let .textStrike(textStrikeData): + let text = textStrikeData.text + self = .strikethrough(RichText(apiText: text)) + case let .textFixed(textFixedData): + let text = textFixedData.text + self = .fixed(RichText(apiText: text)) + case let .textUrl(textUrlData): + let (text, url, webpageId) = (textUrlData.text, textUrlData.url, textUrlData.webpageId) + self = .url(text: RichText(apiText: text), url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId)) + case let .textEmail(textEmailData): + let (text, email) = (textEmailData.text, textEmailData.email) + self = .email(text: RichText(apiText: text), email: email) + case let .textConcat(textConcatData): + let texts = textConcatData.texts + self = .concat(texts.map({ RichText(apiText: $0) })) + case let .textSubscript(textSubscriptData): + let text = textSubscriptData.text + self = .subscript(RichText(apiText: text)) + case let .textSuperscript(textSuperscriptData): + let text = textSuperscriptData.text + self = .superscript(RichText(apiText: text)) + case let .textMarked(textMarkedData): + let text = textMarkedData.text + self = .marked(RichText(apiText: text)) + case let .textPhone(textPhoneData): + let (text, phone) = (textPhoneData.text, textPhoneData.phone) + self = .phone(text: RichText(apiText: text), phone: phone) + case let .textImage(textImageData): + let (documentId, w, h) = (textImageData.documentId, textImageData.w, textImageData.h) + self = .image(id: MediaId(namespace: Namespaces.Media.CloudFile, id: documentId), dimensions: PixelDimensions(width: w, height: h)) + case let .textAnchor(textAnchorData): + let (text, name) = (textAnchorData.text, textAnchorData.name) + self = .anchor(text: RichText(apiText: text), name: name) + case let .textCustomEmoji(data): + self = .textCustomEmoji(fileId: data.documentId, alt: data.alt) + case let .textMath(textMath): + self = .formula(latex: textMath.source) + case let .textAutoEmail(textAutoEmailData): + self = .textAutoEmail(text: RichText(apiText: textAutoEmailData.text)) + case let .textAutoPhone(textAutoPhoneData): + self = .textAutoPhone(text: RichText(apiText: textAutoPhoneData.text)) + case let .textAutoUrl(textAutoUrlData): + self = .textAutoUrl(text: RichText(apiText: textAutoUrlData.text)) + case let .textBankCard(textBankCardData): + self = .textBankCard(text: RichText(apiText: textBankCardData.text)) + case let .textBotCommand(textBotCommandData): + self = .textBotCommand(text: RichText(apiText: textBotCommandData.text)) + case let .textCashtag(textCashtagData): + self = .textCashtag(text: RichText(apiText: textCashtagData.text)) + case let .textDate(value): + let format: MessageTextEntityType.DateTimeFormat? = value.flags == 0 ? nil : MessageTextEntityType.DateTimeFormat(rawValue: value.flags) + self = .textDate(text: RichText(apiText: value.text), date: value.date, format: format) + case let .textHashtag(textHashtagData): + self = .textHashtag(text: RichText(apiText: textHashtagData.text)) + case let .textMention(textMentionData): + self = .textMention(text: RichText(apiText: textMentionData.text)) + case let .textMentionName(textMentionNameData): + self = .textMentionName(text: RichText(apiText: textMentionNameData.text), peerId: textMentionNameData.userId) + case let .textSpoiler(textSpoilerData): + self = .textSpoiler(text: RichText(apiText: textSpoilerData.text)) + } + } + + func apiRichText() -> Api.RichText { + switch self { + case .empty: + return .textPlain(Api.RichText.Cons_textPlain(text: "")) + case let .plain(value): + return .textPlain(Api.RichText.Cons_textPlain(text: value)) + case let .bold(value): + return .textBold(Api.RichText.Cons_textBold(text: value.apiRichText())) + case let .italic(value): + return .textItalic(Api.RichText.Cons_textItalic(text: value.apiRichText())) + case let .underline(value): + return .textUnderline(Api.RichText.Cons_textUnderline(text: value.apiRichText())) + case let .strikethrough(value): + return .textStrike(Api.RichText.Cons_textStrike(text: value.apiRichText())) + case let .fixed(value): + return .textFixed(Api.RichText.Cons_textFixed(text: value.apiRichText())) + case let .url(text, url, webpageId): + return .textUrl(Api.RichText.Cons_textUrl(text: text.apiRichText(), url: url, webpageId: webpageId?.id ?? 0)) + case let .email(text, email): + return .textEmail(Api.RichText.Cons_textEmail(text: text.apiRichText(), email: email)) + case let .concat(values): + return .textConcat(Api.RichText.Cons_textConcat(texts: values.map { $0.apiRichText() })) + case let .`subscript`(value): + return .textSubscript(Api.RichText.Cons_textSubscript(text: value.apiRichText())) + case let .superscript(value): + return .textSuperscript(Api.RichText.Cons_textSuperscript(text: value.apiRichText())) + case let .marked(value): + return .textMarked(Api.RichText.Cons_textMarked(text: value.apiRichText())) + case let .phone(text, phone): + return .textPhone(Api.RichText.Cons_textPhone(text: text.apiRichText(), phone: phone)) + case let .image(id, dimensions): + return .textImage(Api.RichText.Cons_textImage(documentId: id.id, w: dimensions.width, h: dimensions.height)) + case let .anchor(text, name): + return .textAnchor(Api.RichText.Cons_textAnchor(text: text.apiRichText(), name: name)) + case let .formula(latex): + return .textMath(Api.RichText.Cons_textMath(source: latex)) + case let .textCustomEmoji(fileId, alt): + return .textCustomEmoji(Api.RichText.Cons_textCustomEmoji(documentId: fileId, alt: alt)) + case let .textAutoEmail(text): + return .textAutoEmail(Api.RichText.Cons_textAutoEmail(text: text.apiRichText())) + case let .textAutoPhone(text): + return .textAutoPhone(Api.RichText.Cons_textAutoPhone(text: text.apiRichText())) + case let .textAutoUrl(text): + return .textAutoUrl(Api.RichText.Cons_textAutoUrl(text: text.apiRichText())) + case let .textBankCard(text): + return .textBankCard(Api.RichText.Cons_textBankCard(text: text.apiRichText())) + case let .textBotCommand(text): + return .textBotCommand(Api.RichText.Cons_textBotCommand(text: text.apiRichText())) + case let .textCashtag(text): + return .textCashtag(Api.RichText.Cons_textCashtag(text: text.apiRichText())) + case let .textHashtag(text): + return .textHashtag(Api.RichText.Cons_textHashtag(text: text.apiRichText())) + case let .textMention(text): + return .textMention(Api.RichText.Cons_textMention(text: text.apiRichText())) + case let .textMentionName(text, peerId): + return .textMentionName(Api.RichText.Cons_textMentionName(text: text.apiRichText(), userId: peerId)) + case let .textSpoiler(text): + return .textSpoiler(Api.RichText.Cons_textSpoiler(text: text.apiRichText())) + case let .textDate(text, date, format): + return .textDate(Api.RichText.Cons_textDate(flags: format?.rawValue ?? 0, text: text.apiRichText(), date: date)) } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index e36aff5d0c..42346f3590 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -7,6 +7,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], var isSecret = false var isUnconsumedPersonalMention = false var hasUnseenReactions = false + var richText: RichTextMessageAttribute? for attribute in attributes { if let timerAttribute = attribute as? AutoclearTimeoutMessageAttribute { if timerAttribute.timeout > 0 && (timerAttribute.timeout <= 60 || timerAttribute.timeout == viewOnceTimeout) { @@ -22,6 +23,8 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], } } else if let attribute = attribute as? ReactionsMessageAttribute, attribute.hasUnseen { hasUnseenReactions = true + } else if let attribute = attribute as? RichTextMessageAttribute { + richText = attribute } } @@ -134,6 +137,63 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], tags.insert(.unseenPollVote) } + if let attribute = richText { + //TODO:rewrite to take all media + for media in attribute.instantPage.allMedia() { + switch media { + case _ as TelegramMediaImage: + tags.insert(.photo) + tags.insert(.photoOrVideo) + case let file as TelegramMediaFile: + var refinedTag: MessageTags? = .file + var isAnimated = false + inner: for attribute in file.attributes { + switch attribute { + case let .Video(_, _, flags, _, _, _): + if flags.contains(.instantRoundVideo) { + refinedTag = .voiceOrInstantVideo + } else { + if !isSecret { + refinedTag = [.photoOrVideo, .video] + } else { + refinedTag = nil + } + } + case let .Audio(isVoice, _, _, _, _): + if isVoice { + refinedTag = .voiceOrInstantVideo + } else { + if file.isInstantVideo { + refinedTag = .voiceOrInstantVideo + } else { + refinedTag = .music + } + } + break inner + case .Sticker: + refinedTag = nil + break inner + case .Animated: + isAnimated = true + default: + break + } + } + if isAnimated { + refinedTag = .gif + } + if file.isAnimatedSticker { + refinedTag = nil + } + if let refinedTag { + tags.insert(refinedTag) + } + default: + break + } + } + } + return (tags, globalTags) } @@ -463,7 +523,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI let (poll, results) = (messageMediaPollData.poll, messageMediaPollData.results) switch poll { case let .poll(pollData): - let (id, flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash) + let (id, flags, question, answers, closePeriod, closeDate, pollHash, countries) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash, pollData.countriesIso2) let publicity: TelegramMediaPollPublicity if (flags & (1 << 1)) != 0 { publicity = .public @@ -482,7 +542,8 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI let shuffleAnswers = (flags & (1 << 8)) != 0 let hideResultsUntilClose = (flags & (1 << 9)) != 0 let isCreator = (flags & (1 << 10)) != 0 - + let restrictToSubscribers = (flags & (1 << 11)) != 0 + let questionText: String let questionEntities: [MessageTextEntity] switch question { @@ -496,7 +557,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI if let apiAttachedMedia = messageMediaPollData.attachedMedia { parsedAttachedMedia = textMediaAndExpirationTimerFromApiMedia(apiAttachedMedia, peerId).media } - return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: parsedAttachedMedia), nil, nil, nil, nil, nil) + return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: parsedAttachedMedia, restrictToSubscribers: restrictToSubscribers, countries: countries ?? []), nil, nil, nil, nil, nil) } case let .messageMediaToDo(messageMediaToDoData): let (todo, completions) = (messageMediaToDoData.todo, messageMediaToDoData.completions) @@ -847,7 +908,7 @@ extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { case let .message(messageData): - let (flags, flags2, id, fromId, boosts, rank, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod, summaryFromLanguage) = (messageData.flags, messageData.flags2, messageData.id, messageData.fromId, messageData.fromBoostsApplied, messageData.fromRank, messageData.peerId, messageData.savedPeerId, messageData.fwdFrom, messageData.viaBotId, messageData.viaBusinessBotId, messageData.replyTo, messageData.date, messageData.message, messageData.media, messageData.replyMarkup, messageData.entities, messageData.views, messageData.forwards, messageData.replies, messageData.editDate, messageData.postAuthor, messageData.groupedId, messageData.reactions, messageData.restrictionReason, messageData.ttlPeriod, messageData.quickReplyShortcutId, messageData.effect, messageData.factcheck, messageData.reportDeliveryUntilDate, messageData.paidMessageStars, messageData.suggestedPost, messageData.scheduleRepeatPeriod, messageData.summaryFromLanguage) + let (flags, flags2, id, fromId, boosts, rank, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, guestChatViaFrom, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod, summaryFromLanguage) = (messageData.flags, messageData.flags2, messageData.id, messageData.fromId, messageData.fromBoostsApplied, messageData.fromRank, messageData.peerId, messageData.savedPeerId, messageData.fwdFrom, messageData.viaBotId, messageData.viaBusinessBotId, messageData.guestchatViaFrom, messageData.replyTo, messageData.date, messageData.message, messageData.media, messageData.replyMarkup, messageData.entities, messageData.views, messageData.forwards, messageData.replies, messageData.editDate, messageData.postAuthor, messageData.groupedId, messageData.reactions, messageData.restrictionReason, messageData.ttlPeriod, messageData.quickReplyShortcutId, messageData.effect, messageData.factcheck, messageData.reportDeliveryUntilDate, messageData.paidMessageStars, messageData.suggestedPost, messageData.scheduleRepeatPeriod, messageData.summaryFromLanguage) var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { @@ -1086,6 +1147,10 @@ extension StoreMessage { if let viaBusinessBotId { attributes.append(InlineBusinessBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(viaBusinessBotId)), title: nil)) } + + if let guestChatViaFrom { + attributes.append(GuestChatMessageAttribute(peerId: guestChatViaFrom.peerId)) + } if !Namespaces.Message.allNonRegular.contains(namespace) { if let views = views { @@ -1128,7 +1193,7 @@ extension StoreMessage { } var entitiesAttribute: TextEntitiesMessageAttribute? - if let entities = entities, !entities.isEmpty { + if let entities, !entities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)) entitiesAttribute = attribute attributes.append(attribute) @@ -1150,6 +1215,10 @@ extension StoreMessage { attributes.append(attribute) } } + + if let richMessage = messageData.richMessage { + attributes.append(RichTextMessageAttribute(apiRichMessage: richMessage)) + } if (flags & (1 << 19)) != 0 { attributes.append(ContentRequiresValidationMessageAttribute()) @@ -1341,6 +1410,8 @@ extension StoreMessage { attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId, quote: quote, isQuote: isQuote, innerSubject: innerSubject)) } else if let replyHeader = replyHeader { attributes.append(QuotedReplyMessageAttribute(apiHeader: replyHeader, quote: quote, isQuote: isQuote)) + } else if let replyToTopId, peerIsForum { + threadId = Int64(replyToTopId) } case let .messageReplyStoryHeader(messageReplyStoryHeaderData): let (peer, storyId) = (messageReplyStoryHeaderData.peer, messageReplyStoryHeaderData.storyId) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift index 7e5d355ecb..4580acf837 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift @@ -71,10 +71,12 @@ extension TelegramMediaPollResults { if (flags & (1 << 0)) == 0 {//isMin hasUnseenVotes = (flags & (1 << 6)) != 0 } + + let canViewStats = (flags & (1 << 7)) != 0 self.init(voters: results.flatMap({ $0.map(TelegramMediaPollOptionVoters.init(apiVoters:)) }), totalVoters: totalVoters, recentVoters: recentVoters.flatMap { recentVoters in return recentVoters.map { $0.peerId } - } ?? [], solution: parsedSolution, hasUnseenVotes: hasUnseenVotes) + } ?? [], solution: parsedSolution, hasUnseenVotes: hasUnseenVotes, canViewStats: canViewStats) } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index 37327ac121..d1fa5fc352 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -44,6 +44,8 @@ func telegramMediaWebpageAttributeFromApiWebpageAttribute(_ attribute: Api.WebPa return .giftAuction(TelegramMediaWebpageGiftAuctionAttribute(gift: gift, endDate: endDate)) case .webPageAttributeStory: return nil + case let .webPageAttributeAiComposeTone(webPageAttributeAiComposeTone): + return .aiTextStyle(TelegramMediaWebpageAITextStyleAttribute(emojiFileId: webPageAttributeAiComposeTone.emojiId)) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift index e19c5f723f..8bdb2d2f9c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift @@ -131,6 +131,12 @@ extension TelegramUser { if (flags2 & (1 << 18)) != 0 { botFlags.insert(.canManageBots) } + if (flags2 & (1 << 19)) != 0 { + botFlags.insert(.isGuestChat) + } + if (flags2 & (1 << 20)) != 0 { + botFlags.insert(.isGuardBot) + } botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder) } diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 25caa0afc8..61c1d41955 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -527,8 +527,8 @@ private func internalResendAuthorizationCode(accountManager: AccountManager switchToLatest } -public enum AuthorizationStateReset { - case empty -} - -public func resetAuthorizationState(account: UnauthorizedAccount, to value: AuthorizationStateReset) -> Signal { - return account.postbox.transaction { transaction -> Void in - if let state = transaction.getState() as? UnauthorizedAccountState { - transaction.setState(UnauthorizedAccountState(isTestingEnvironment: state.isTestingEnvironment, masterDatacenterId: state.masterDatacenterId, contents: .empty)) - } - } -} - public func togglePreviousCodeEntry(account: UnauthorizedAccount) -> Signal { return account.postbox.transaction { transaction -> Void in if let state = transaction.getState() as? UnauthorizedAccountState { diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index c922b6669c..caee073654 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -3,8 +3,8 @@ import SwiftSignalKit import Postbox import TelegramApi -public extension EngineMessageHistoryThread { - final class Info: Equatable, Codable { +public final class EngineMessageHistoryThread { + public final class Info: Equatable, Codable { private enum CodingKeys: String, CodingKey { case title case icon diff --git a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift index 553b840e4f..1f6b2e0832 100644 --- a/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift +++ b/submodules/TelegramCore/Sources/Network/FetchedMediaResource.swift @@ -240,7 +240,7 @@ private func findMediaResource(media: Media, previousMedia: Media?, resource: Me } public func findMediaResourceById(message: EngineMessage, resourceId: MediaResourceId) -> TelegramMediaResource? { - for media in message.media { + for media in message.effectiveMedia { if let result = findMediaResourceById(media: media, resourceId: resourceId) { return result } @@ -671,7 +671,7 @@ final class MediaReferenceRevalidationContext { } |> map { [$0] } } else { - signal = telegramWallpapers(postbox: postbox, network: network, forceUpdate: true) + signal = _internal_telegramWallpapers(postbox: postbox, network: network, forceUpdate: true) |> last |> mapError { _ -> RevalidateMediaReferenceError in } @@ -697,7 +697,7 @@ final class MediaReferenceRevalidationContext { func themes(postbox: Postbox, network: Network, background: Bool) -> Signal<[TelegramTheme], RevalidateMediaReferenceError> { return self.genericItem(key: .themes, background: background, request: { next, error in - return (telegramThemes(postbox: postbox, network: network, accountManager: nil, forceUpdate: true) + return (_internal_telegramThemes(postbox: postbox, network: network, accountManager: nil, forceUpdate: true) |> take(1) |> mapError { _ -> RevalidateMediaReferenceError in }).start(next: { value in @@ -901,6 +901,18 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } + // Rich-text messages (`RichTextMessageAttribute`) embed their media in the + // attribute's `InstantPage`, not in `message.media` — search there too so a + // stale instant-page audio/image file reference can revalidate. + for attribute in message.attributes { + if let attribute = attribute as? RichTextMessageAttribute { + for (_, pageMedia) in attribute.instantPage.media { + if let updatedResource = findUpdatedMediaResource(media: pageMedia, previousMedia: previousMedia, resource: resource) { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + } + } + } + } return .fail(.generic) } case let .stickerPack(stickerPack, media): @@ -1013,6 +1025,11 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } + if let music = item.music { + if let updatedResource = findUpdatedMediaResource(media: music, previousMedia: nil, resource: resource) { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) + } + } return .fail(.generic) } } diff --git a/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift index 9b23e2aafa..5c5f0d885f 100644 --- a/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift +++ b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift @@ -87,7 +87,7 @@ final class NetworkStatsContext { for (targetKey, averageStats) in self.averageTargetStats { if averageStats.count >= 1000 || averageStats.size >= 4 * 1024 * 1024 { if let postbox = self.postbox { - addAppLogEvent(postbox: postbox, type: "download", data: .dictionary([ + _internal_addAppLogEvent(postbox: postbox, type: "download", data: .dictionary([ "n": .number(Double(targetKey.networkType.rawValue)), "d": .number(Double(targetKey.datacenterId)), "b": .number(averageStats.networkBps / Double(averageStats.count)), diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index c02be139c0..417d0f8810 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -255,6 +255,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt switch attribute { case _ as TextEntitiesMessageAttribute: return true + case _ as RichTextMessageAttribute: + return true case _ as InlineBotMessageAttribute: return true case _ as OutgoingMessageInfoAttribute: @@ -306,6 +308,8 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt switch attribute { case _ as TextEntitiesMessageAttribute: return true + case _ as RichTextMessageAttribute: + return true case _ as InlineBotMessageAttribute: return true case _ as NotificationInfoMessageAttribute: @@ -408,37 +412,6 @@ public func enqueueMessages(account: Account, peerId: PeerId, messages: [Enqueue } } -public func enqueueMessagesToMultiplePeers(account: Account, peerIds: [PeerId], threadIds: [PeerId: Int64], messages: [EnqueueMessage]) -> Signal<[MessageId], NoError> { - let signal: Signal<[(Bool, EnqueueMessage)], NoError> - if let transformOutgoingMessageMedia = account.transformOutgoingMessageMedia { - signal = opportunisticallyTransformOutgoingMedia(network: account.network, postbox: account.postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messages: messages, userInteractive: true) - } else { - signal = .single(messages.map { (false, $0) }) - } - return signal - |> mapToSignal { messages -> Signal<[MessageId], NoError> in - return account.postbox.transaction { transaction -> [MessageId] in - var messageIds: [MessageId] = [] - for peerId in peerIds { - var replyToMessageId: EngineMessageReplySubject? - if let threadIds = threadIds[peerId] { - replyToMessageId = EngineMessageReplySubject(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadIds)), quote: nil, innerSubject: nil) - } - var messages = messages - if let replyToMessageId = replyToMessageId { - messages = messages.map { ($0.0, $0.1.withUpdatedReplyToMessageId(replyToMessageId)) } - } - for id in enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages, disableAutoremove: false, transformGroupingKeysWithPeerId: true) { - if let id = id { - messageIds.append(id) - } - } - } - return messageIds - } - } -} - public func resendMessages(account: Account, messageIds: [MessageId]) -> Signal { return account.postbox.transaction { transaction -> Void in var removeMessageIds: [MessageId] = [] @@ -864,7 +837,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum { + if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum { threadId = 1 } @@ -988,35 +961,24 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, forwardInfo = StoreMessageForwardInfo(authorId: sourceForwardInfo.author?.id, sourceId: sourceForwardInfo.source?.id, sourceMessageId: sourceForwardInfo.sourceMessageId, date: sourceForwardInfo.date, authorSignature: sourceForwardInfo.authorSignature, psaType: nil, flags: []) } else { if sourceMessage.id.peerId != account.peerId { - var hasHiddenForwardMedia = false - for media in sourceMessage.media { - if let file = media as? TelegramMediaFile { - if file.isMusic { - hasHiddenForwardMedia = true - } + var sourceId: PeerId? = nil + var sourceMessageId: MessageId? = nil + if case let .channel(peer) = messageMainPeer(EngineMessage(sourceMessage)), case .broadcast = peer.info { + sourceId = peer.id + sourceMessageId = sourceMessage.id + } + + var authorSignature: String? + for attribute in sourceMessage.attributes { + if let attribute = attribute as? AuthorSignatureMessageAttribute { + authorSignature = attribute.signature + break } } - - if !hasHiddenForwardMedia { - var sourceId: PeerId? = nil - var sourceMessageId: MessageId? = nil - if case let .channel(peer) = messageMainPeer(EngineMessage(sourceMessage)), case .broadcast = peer.info { - sourceId = peer.id - sourceMessageId = sourceMessage.id - } - - var authorSignature: String? - for attribute in sourceMessage.attributes { - if let attribute = attribute as? AuthorSignatureMessageAttribute { - authorSignature = attribute.signature - break - } - } - - let psaType: String? = nil - - forwardInfo = StoreMessageForwardInfo(authorId: author.id, sourceId: sourceId, sourceMessageId: sourceMessageId, date: sourceMessage.timestamp, authorSignature: authorSignature, psaType: psaType, flags: []) - } + + let psaType: String? = nil + + forwardInfo = StoreMessageForwardInfo(authorId: author.id, sourceId: sourceId, sourceMessageId: sourceMessageId, date: sourceMessage.timestamp, authorSignature: authorSignature, psaType: psaType, flags: []) } else { forwardInfo = nil } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index bea55f4b28..b1ec80f12f 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -345,11 +345,25 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post if poll.deadlineDate != nil { pollFlags |= 1 << 5 } - if poll.openAnswers { pollFlags |= 1 << 6 } - if poll.revotingDisabled { pollFlags |= 1 << 7 } - if poll.shuffleAnswers { pollFlags |= 1 << 8 } - if poll.hideResultsUntilClose { pollFlags |= 1 << 9 } - + if poll.openAnswers { + pollFlags |= 1 << 6 + } + if poll.revotingDisabled { + pollFlags |= 1 << 7 + } + if poll.shuffleAnswers { + pollFlags |= 1 << 8 + } + if poll.hideResultsUntilClose { + pollFlags |= 1 << 9 + } + if poll.restrictToSubscribers { + pollFlags |= 1 << 11 + } + if !poll.countries.isEmpty { + pollFlags |= 1 << 12 + } + var mappedSolution: String? var mappedSolutionEntities: [Api.MessageEntity]? if let solution = poll.results.solution { @@ -376,7 +390,24 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post if solutionInputMedia != nil { pollMediaFlags |= 1 << 2 } - let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init( text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()) )), answers: apiAnswers, closePeriod: poll.deadlineTimeout, closeDate: poll.deadlineDate, hash: 0)), correctAnswers: correctAnswers, attachedMedia: attachedInputMedia, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: solutionInputMedia)) + let inputPoll = Api.InputMedia.inputMediaPoll(.init( + flags: pollMediaFlags, + poll: .poll(.init( + id: 0, + flags: pollFlags, + question: .textWithEntities(.init( text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()) )), + answers: apiAnswers, + closePeriod: poll.deadlineTimeout, + closeDate: poll.deadlineDate, + countriesIso2: poll.countries, + hash: 0 + )), + correctAnswers: correctAnswers, + attachedMedia: attachedInputMedia, + solution: mappedSolution, + solutionEntities: mappedSolutionEntities, + solutionMedia: solutionInputMedia + )) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else if let todo = media as? TelegramMediaTodo { var flags: Int32 = 0 diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift index a2f1fafa5a..beaef4f937 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift @@ -64,7 +64,7 @@ private final class PendingUpdateMessageManagerImpl { } } - func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool) { + func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool) { if let context = self.contexts[messageId] { self.contexts.removeValue(forKey: messageId) context.disposable.dispose() @@ -75,7 +75,7 @@ private final class PendingUpdateMessageManagerImpl { self.contexts[messageId] = context let queue = self.queue - disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: nil, invertMediaAttribute: invertMediaAttribute) + disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: nil, invertMediaAttribute: invertMediaAttribute) |> deliverOn(self.queue)).start(next: { [weak self, weak context] value in queue.async { guard let strongSelf = self, let initialContext = context else { @@ -163,9 +163,9 @@ public final class PendingUpdateMessageManager { }) } - public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false) { + public func add(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false) { self.impl.with { impl in - impl.add(messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview) + impl.add(messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview) } } diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index 7df68a0898..d0f555d1a4 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -27,15 +27,15 @@ public enum RequestEditMessageError { case invalidGrouping } -func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { - return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) +func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) } -func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: false) +func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: false) |> `catch` { error -> Signal in if case .invalidReference = error { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: true) + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: true) } else { return .fail(error) } @@ -50,7 +50,7 @@ func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Networ } } -private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, forceReupload: Bool) -> Signal { +private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, forceReupload: Bool) -> Signal { let uploadedMedia: Signal switch media { case .keep: @@ -146,15 +146,21 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, } |> mapError { _ -> RequestEditMessageInternalError in } |> mapToSignal { peer, message, associatedPeers -> Signal in - if let peer = peer, let message = message, let inputPeer = apiInputPeer(peer) { + if let peer, let message, let inputPeer = apiInputPeer(peer) { var flags: Int32 = 1 << 11 var apiEntities: [Api.MessageEntity]? - if let entities = entities { + if let entities { apiEntities = apiTextAttributeEntities(entities, associatedPeers: associatedPeers) flags |= Int32(1 << 3) } + var apiRichMessage: Api.InputRichMessage? + if let richText { + apiRichMessage = richText.apiInputRichMessage() + flags |= Int32(1 << 23) + } + if disableUrlPreview { flags |= Int32(1 << 1) } @@ -188,6 +194,9 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, } } + if let webpagePreviewAttribute, webpagePreviewAttribute.leadingPreview { + flags |= Int32(1 << 16) + } if let _ = invertMediaAttribute { flags |= Int32(1 << 16) } @@ -198,7 +207,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, flags |= Int32(1 << 17) } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: effectiveScheduleRepeatPeriod, quickReplyShortcutId: quickReplyShortcutId)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: effectiveScheduleRepeatPeriod, quickReplyShortcutId: quickReplyShortcutId, richMessage: apiRichMessage)) |> map { result -> Api.Updates? in return result } @@ -426,7 +435,7 @@ func _internal_requestEditLiveLocation(postbox: Postbox, network: Network, state inputMedia = .inputMediaGeoLive(.init(flags: 1 << 0, geoPoint: .inputGeoPoint(.init(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil)), heading: nil, period: nil, proximityNotificationRadius: nil)) } - return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) + return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil, richMessage: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index b4bf907713..79256e6c48 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -33,7 +33,7 @@ public struct StandaloneSendMessagesError { public var peerId: PeerId public var reason: PendingMessageFailureReason? - init( + public init( peerId: PeerId, reason: PendingMessageFailureReason? ) { @@ -217,6 +217,53 @@ public func standaloneSendEnqueueMessages( } } if allDone { + if peerId.namespace == Namespaces.Peer.SecretChat { + return postbox.transaction { transaction -> Signal in + var state = transaction.getPeerChatState(peerId) as? SecretChatState + + for (content, media, attributes) in allResults { + var text: String = "" + switch content.content { + case let .text(textValue): + text = textValue + case let .media(_, textValue): + text = textValue + default: + break + } + + if let currentState = state, let updatedState = enqueueSecretChatUploadedMessageContent( + transaction: transaction, + peerId: peerId, + state: currentState, + content: content, + text: text, + attributes: attributes, + media: media + ) { + state = updatedState + } else { + return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) + } + } + + return managedSecretChatOutgoingOperations( + auxiliaryMethods: auxiliaryMethods, + postbox: postbox, + network: network, + accountPeerId: accountPeerId, + mode: .standaloneComplete(peerId: peerId) + ) + |> castError(StandaloneSendMessagesError.self) + |> ignoreValues + } + |> castError(StandaloneSendMessagesError.self) + |> switchToLatest + |> map { _ -> StandaloneSendMessageStatus in + } + |> then(.single(.done)) + } + var sendSignals: [Signal] = [] for (content, media, attributes) in allResults { @@ -231,11 +278,10 @@ public func standaloneSendEnqueueMessages( } sendSignals.append(sendUploadedMessageContent( - auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, stateManager: stateManager, - accountPeerId: stateManager.accountPeerId, + accountPeerId: accountPeerId, peerId: peerId, content: content, text: text, @@ -256,8 +302,52 @@ public func standaloneSendEnqueueMessages( } } +private func enqueueSecretChatUploadedMessageContent( + transaction: Transaction, + peerId: PeerId, + state: SecretChatState, + content: PendingMessageUploadedContentAndReuploadInfo, + text: String, + attributes: [MessageAttribute], + media: [Media] +) -> SecretChatState? { + var secretFile: SecretChatOutgoingFile? + switch content.content { + case let .secretMedia(file, size, key): + if let fileReference = SecretChatOutgoingFileReference(file) { + secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) + } + default: + break + } + + let layer: SecretChatLayer + switch state.embeddedState { + case .terminated, .handshake: + return nil + case .basicLayer: + layer = .layer8 + case let .sequenceBasedLayer(sequenceState): + layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer + } + + let messageContents = StandaloneSecretMessageContents( + id: Int64.random(in: Int64.min ... Int64.max), + text: text, + attributes: attributes, + media: media.first, + file: secretFile + ) + + let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state) + if updatedState != state { + transaction.setPeerChatState(peerId, state: updatedState) + } + + return updatedState +} + private func sendUploadedMessageContent( - auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, stateManager: AccountStateManager, @@ -271,59 +361,12 @@ private func sendUploadedMessageContent( ) -> Signal { return postbox.transaction { transaction -> Signal in if peerId.namespace == Namespaces.Peer.SecretChat { - var secretFile: SecretChatOutgoingFile? - switch content.content { - case let .secretMedia(file, size, key): - if let fileReference = SecretChatOutgoingFileReference(file) { - secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) - } - default: - break - } - - var layer: SecretChatLayer? - let state = transaction.getPeerChatState(peerId) as? SecretChatState - if let state = state { - switch state.embeddedState { - case .terminated, .handshake: - break - case .basicLayer: - layer = .layer8 - case let .sequenceBasedLayer(sequenceState): - layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer - } - } - - if let state = state, let layer = layer { - let messageContents = StandaloneSecretMessageContents( - id: Int64.random(in: Int64.min ... Int64.max), - text: text, - attributes: attributes, - media: media.first, - file: secretFile - ) - - let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state) - if updatedState != state { - transaction.setPeerChatState(peerId, state: updatedState) - } - - return managedSecretChatOutgoingOperations( - auxiliaryMethods: auxiliaryMethods, - postbox: postbox, - network: network, - accountPeerId: accountPeerId, - mode: .standaloneComplete(peerId: peerId) - ) - |> castError(StandaloneSendMessagesError.self) - |> ignoreValues - } else { - return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) - } + return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) } else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var uniqueId: Int64 = 0 var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? var messageEntities: [Api.MessageEntity]? + var apiRichMessage: Api.InputRichMessage? var replyMessageId: Int32? var topMsgId: Int32? var monoforumPeerId: Api.InputPeer? @@ -400,6 +443,9 @@ private func sendUploadedMessageContent( allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) + } else if let attribute = attribute as? RichTextMessageAttribute { + apiRichMessage = attribute.apiInputRichMessage() + flags |= Int32(1 << 23) } } @@ -470,7 +516,7 @@ private func sendUploadedMessageContent( flags |= 1 << 22 } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost, richMessage: apiRichMessage), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -650,6 +696,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M var uniqueId: Int64 = Int64.random(in: Int64.min ... Int64.max) //var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? var messageEntities: [Api.MessageEntity]? + var apiRichMessage: Api.InputRichMessage? var replyMessageId: Int32? var replyToStoryId: StoryId? var scheduleTime: Int32? @@ -689,6 +736,9 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) + } else if let attribute = attribute as? RichTextMessageAttribute { + apiRichMessage = attribute.apiInputRichMessage() + flags |= Int32(1 << 23) } } @@ -725,7 +775,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(.init(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil, pollOption: nil)) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil, richMessage: apiRichMessage)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift index 1362d20c6e..4c2f0cfdd0 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneUploadedMedia.swift @@ -8,12 +8,6 @@ public enum StandaloneUploadMediaError { case generic } -public struct StandaloneUploadSecretFile { - let file: Api.InputEncryptedFile - let size: Int32 - let key: SecretFileEncryptionKey -} - public enum StandaloneUploadMediaThumbnailResult { case pending case file(Api.InputFile) diff --git a/submodules/TelegramCore/Sources/Settings/AccountWebBrowserSettings.swift b/submodules/TelegramCore/Sources/Settings/AccountWebBrowserSettings.swift new file mode 100644 index 0000000000..dec7863101 --- /dev/null +++ b/submodules/TelegramCore/Sources/Settings/AccountWebBrowserSettings.swift @@ -0,0 +1,237 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi + +public struct AccountWebBrowserException: Codable, Equatable { + public let domain: String + public let url: String + public let title: String + public let favicon: Int64? + + public init(domain: String, url: String, title: String, favicon: Int64?) { + self.domain = domain + self.url = url + self.title = title + self.favicon = favicon + } + + public init(apiWebDomainException: Api.WebDomainException) { + switch apiWebDomainException { + case let .webDomainException(data): + self.init(domain: data.domain, url: data.url, title: data.title, favicon: data.favicon) + } + } +} + +public struct AccountWebBrowserSettings: Codable, Equatable { + public let openExternalBrowser: Bool + public let externalExceptions: [AccountWebBrowserException] + public let inAppExceptions: [AccountWebBrowserException] + public let hash: Int64 + + public static var defaultSettings: AccountWebBrowserSettings { + return AccountWebBrowserSettings(openExternalBrowser: false, externalExceptions: [], inAppExceptions: [], hash: 0) + } + + public init(openExternalBrowser: Bool, externalExceptions: [AccountWebBrowserException], inAppExceptions: [AccountWebBrowserException], hash: Int64) { + self.openExternalBrowser = openExternalBrowser + self.externalExceptions = externalExceptions + self.inAppExceptions = inAppExceptions + self.hash = hash + } + + public init(apiWebBrowserSettings: Api.account.WebBrowserSettings, current: AccountWebBrowserSettings?) { + switch apiWebBrowserSettings { + case let .webBrowserSettings(data): + self.init( + openExternalBrowser: (data.flags & (1 << 0)) != 0, + externalExceptions: data.externalExceptions.map(AccountWebBrowserException.init(apiWebDomainException:)), + inAppExceptions: data.inappExceptions.map(AccountWebBrowserException.init(apiWebDomainException:)), + hash: data.hash + ) + case .webBrowserSettingsNotModified: + self = current ?? .defaultSettings + } + } + + public func withUpdatedOpenExternalBrowser(_ openExternalBrowser: Bool) -> AccountWebBrowserSettings { + return AccountWebBrowserSettings(openExternalBrowser: openExternalBrowser, externalExceptions: self.externalExceptions, inAppExceptions: self.inAppExceptions, hash: 0) + } + + public func withAppliedExceptionUpdate(openExternalBrowser: Bool?, delete: Bool, exception: AccountWebBrowserException) -> AccountWebBrowserSettings { + var externalExceptions = self.externalExceptions + var inAppExceptions = self.inAppExceptions + + let removeMatching: (inout [AccountWebBrowserException]) -> Void = { list in + list.removeAll(where: { item in + if !exception.url.isEmpty && item.url == exception.url { + return true + } + return item.domain == exception.domain + }) + } + + if delete { + if let openExternalBrowser { + if openExternalBrowser { + removeMatching(&externalExceptions) + } else { + removeMatching(&inAppExceptions) + } + } else { + removeMatching(&externalExceptions) + removeMatching(&inAppExceptions) + } + } else if let openExternalBrowser { + if openExternalBrowser { + removeMatching(&externalExceptions) + externalExceptions.append(exception) + } else { + removeMatching(&inAppExceptions) + inAppExceptions.append(exception) + } + } + + return AccountWebBrowserSettings(openExternalBrowser: self.openExternalBrowser, externalExceptions: externalExceptions, inAppExceptions: inAppExceptions, hash: 0) + } +} + +private func storeAccountWebBrowserSettings(postbox: Postbox, settings: AccountWebBrowserSettings) -> Signal { + return postbox.transaction { transaction -> Void in + transaction.updatePreferencesEntry(key: PreferencesKeys.webBrowserSettings, { _ in + return PreferencesEntry(settings) + }) + } +} + +func _internal_getAccountWebBrowserSettings(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal { + let fetch: (AccountWebBrowserSettings?, Int64) -> Signal = { current, hash in + return network.request(Api.functions.account.getWebBrowserSettings(hash: hash)) + |> retryRequestIfNotFrozen + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + switch result { + case .webBrowserSettingsNotModified: + return .complete() + case .webBrowserSettings: + let settings = AccountWebBrowserSettings(apiWebBrowserSettings: result, current: current) + if let current, settings == current { + return .complete() + } else { + return storeAccountWebBrowserSettings(postbox: postbox, settings: settings) + |> mapToSignal { _ -> Signal in + return .single(settings) + } + } + } + } + } + + return postbox.transaction { transaction -> AccountWebBrowserSettings in + return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + } + |> mapToSignal { current -> Signal in + let hash: Int64 = forceUpdate ? 0 : current.hash + return .single(current) + |> then(fetch(current, hash)) + } +} + +public func updateRemoteWebBrowserSettings(postbox: Postbox, network: Network, openExternalBrowser: Bool) -> Signal { + var flags: Int32 = 0 + if openExternalBrowser { + flags |= (1 << 0) + } + return network.request(Api.functions.account.updateWebBrowserSettings(flags: flags)) + |> retryRequestIfNotFrozen + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + return postbox.transaction { transaction -> AccountWebBrowserSettings? in + return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) + } + |> mapToSignal { current -> Signal in + return storeAccountWebBrowserSettings(postbox: postbox, settings: AccountWebBrowserSettings(apiWebBrowserSettings: result, current: current)) + } + } +} + +public func toggleWebBrowserSettingsException(postbox: Postbox, network: Network, openExternalBrowser: Bool?, delete: Bool, url: String) -> Signal { + var flags: Int32 = 0 + var apiOpenExternalBrowser: Api.Bool? + if let openExternalBrowser { + flags |= (1 << 0) + apiOpenExternalBrowser = openExternalBrowser ? .boolTrue : .boolFalse + } + if delete { + flags |= (1 << 1) + } + return network.request(Api.functions.account.toggleWebBrowserSettingsException(flags: flags, openExternalBrowser: apiOpenExternalBrowser, url: url)) + |> retryRequestIfNotFrozen + |> mapToSignal { result -> Signal in + guard let result else { + return .single(false) + } + + return postbox.transaction { transaction -> Bool in + var settings = transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + var updated = false + for update in result.allUpdates { + switch update { + case let .updateWebBrowserSettings(updateWebBrowserSettingsData): + settings = settings.withUpdatedOpenExternalBrowser((updateWebBrowserSettingsData.flags & (1 << 0)) != 0) + updated = true + case let .updateWebBrowserException(updateWebBrowserExceptionData): + let openExternalBrowser: Bool? + if let value = updateWebBrowserExceptionData.openExternalBrowser { + switch value { + case .boolFalse: + openExternalBrowser = false + case .boolTrue: + openExternalBrowser = true + } + } else { + openExternalBrowser = nil + } + + settings = settings.withAppliedExceptionUpdate( + openExternalBrowser: openExternalBrowser, + delete: (updateWebBrowserExceptionData.flags & (1 << 1)) != 0, + exception: AccountWebBrowserException(apiWebDomainException: updateWebBrowserExceptionData.exception) + ) + updated = true + default: + break + } + } + + if updated { + transaction.updatePreferencesEntry(key: PreferencesKeys.webBrowserSettings, { _ in + return PreferencesEntry(settings) + }) + } + + return updated + } + } +} + +public func deleteWebBrowserSettingsExceptions(postbox: Postbox, network: Network) -> Signal { + return network.request(Api.functions.account.deleteWebBrowserSettingsExceptions()) + |> retryRequestIfNotFrozen + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + return postbox.transaction { transaction -> AccountWebBrowserSettings? in + return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) + } + |> mapToSignal { current -> Signal in + return storeAccountWebBrowserSettings(postbox: postbox, settings: AccountWebBrowserSettings(apiWebBrowserSettings: result, current: current)) + } + } +} diff --git a/submodules/TelegramCore/Sources/Settings/ContentPrivacySettings.swift b/submodules/TelegramCore/Sources/Settings/ContentPrivacySettings.swift deleted file mode 100644 index 02487dc5c5..0000000000 --- a/submodules/TelegramCore/Sources/Settings/ContentPrivacySettings.swift +++ /dev/null @@ -1,20 +0,0 @@ -import Foundation -import Postbox -import SwiftSignalKit -import MtProtoKit - - -public func updateContentPrivacySettings(postbox: Postbox, _ f: @escaping (ContentPrivacySettings) -> ContentPrivacySettings) -> Signal { - return postbox.transaction { transaction -> Void in - var updated: ContentPrivacySettings? - transaction.updatePreferencesEntry(key: PreferencesKeys.contentPrivacySettings, { current in - if let current = current?.get(ContentPrivacySettings.self) { - updated = f(current) - return PreferencesEntry(updated) - } else { - updated = f(ContentPrivacySettings.defaultSettings) - return PreferencesEntry(updated) - } - }) - } -} diff --git a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift index 8b4d19f0a2..69d84fa463 100644 --- a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift @@ -7,7 +7,7 @@ extension PeerStatusSettings { init(apiSettings: Api.PeerSettings) { switch apiSettings { case let .peerSettings(peerSettingsData): - let (flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, nameChangeDate, photoChangeDate) = (peerSettingsData.flags, peerSettingsData.geoDistance, peerSettingsData.requestChatTitle, peerSettingsData.requestChatDate, peerSettingsData.businessBotId, peerSettingsData.businessBotManageUrl, peerSettingsData.chargePaidMessageStars, peerSettingsData.registrationMonth, peerSettingsData.phoneCountry, peerSettingsData.nameChangeDate, peerSettingsData.photoChangeDate) + let (flags, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, nameChangeDate, photoChangeDate) = (peerSettingsData.flags, peerSettingsData.requestChatTitle, peerSettingsData.requestChatDate, peerSettingsData.businessBotId, peerSettingsData.businessBotManageUrl, peerSettingsData.chargePaidMessageStars, peerSettingsData.registrationMonth, peerSettingsData.phoneCountry, peerSettingsData.nameChangeDate, peerSettingsData.photoChangeDate) var result = PeerStatusSettings.Flags() if (flags & (1 << 1)) != 0 { result.insert(.canAddContact) @@ -44,7 +44,6 @@ extension PeerStatusSettings { } self = PeerStatusSettings( flags: result, - geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, diff --git a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift index e8495e3b02..b12e59e661 100644 --- a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift @@ -11,6 +11,10 @@ public final class SelectivePrivacyPeer: Equatable { self.peer = peer self.participantCount = participantCount } + + public convenience init(peer: EnginePeer, participantCount: Int32?) { + self.init(peer: peer._asPeer(), participantCount: participantCount) + } public static func ==(lhs: SelectivePrivacyPeer, rhs: SelectivePrivacyPeer) -> Bool { if !lhs.peer.isEqual(rhs.peer) { diff --git a/submodules/TelegramCore/Sources/SplitTest.swift b/submodules/TelegramCore/Sources/SplitTest.swift index 84f6211ee6..4bce45abdd 100644 --- a/submodules/TelegramCore/Sources/SplitTest.swift +++ b/submodules/TelegramCore/Sources/SplitTest.swift @@ -24,7 +24,7 @@ extension SplitTest { public func addEvent(_ event: Self.Event, data: JSON = []) { if let bucket = self.bucket { //TODO: merge additional data - addAppLogEvent(postbox: self.postbox, type: event.rawValue, data: ["bucket": bucket]) + _internal_addAppLogEvent(postbox: self.postbox, type: event.rawValue, data: ["bucket": bucket]) } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 555b9d62cb..d80793f3b2 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -924,6 +924,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: var missingUpdatesFromChannels = Set() + enum TypingDraftText { + case plain(Api.TextWithEntities) + case rich(Api.RichMessage) + } + for update in sortedUpdates(updates) { switch update { case let .updateChannelTooLong(updateChannelTooLongData): @@ -1516,12 +1521,22 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: let threadId = topMsgId.flatMap { Int64($0) } if let date = updatesDate, date + 60 > serverTime { + var typingDraftData: (randomId: Int64, text: TypingDraftText)? + if case let .sendMessageTextDraftAction(sendMessageTextDraftActionData) = type { - let (randomId, text) = (sendMessageTextDraftActionData.randomId, sendMessageTextDraftActionData.text) - switch text { - case let .textWithEntities(textWithEntitiesData): - let (text, entities) = (textWithEntitiesData.text, textWithEntitiesData.entities) - updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), threadId: threadId), id: randomId, timestamp: date, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), text: text, entities: messageTextEntitiesFromApiEntities(entities)) + typingDraftData = (sendMessageTextDraftActionData.randomId, .plain(sendMessageTextDraftActionData.text)) + } else if case let .sendMessageRichMessageDraftAction(sendMessageRichMessageDraftActionData) = type { + typingDraftData = (sendMessageRichMessageDraftActionData.randomId, .rich(sendMessageRichMessageDraftActionData.richMessage)) + } + if let typingDraftData { + switch typingDraftData.text { + case let .plain(plain): + if case let .textWithEntities(textWithEntitiesData) = plain { + updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), threadId: threadId), id: typingDraftData.randomId, timestamp: date, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), content: .plain(text: textWithEntitiesData.text, entities: messageTextEntitiesFromApiEntities(textWithEntitiesData.entities))) + } + case let .rich(richMessage): + let parsedRichMessage = RichTextMessageAttribute(apiRichMessage: richMessage) + updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), threadId: threadId), id: typingDraftData.randomId, timestamp: date, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), content: .rich(parsedRichMessage)) } } else { let activity = PeerInputActivity(apiType: type, peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), timestamp: date) @@ -1555,8 +1570,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: switch text { case let .textWithEntities(textWithEntitiesData): let (text, entities) = (textWithEntitiesData.text, textWithEntitiesData.entities) - updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: channelPeerId, threadId: threadId), id: randomId, timestamp: date, peerId: userId.peerId, text: text, entities: messageTextEntitiesFromApiEntities(entities)) + updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: channelPeerId, threadId: threadId), id: randomId, timestamp: date, peerId: userId.peerId, content: .plain(text: text, entities: messageTextEntitiesFromApiEntities(entities))) } + } else if case let .sendMessageRichMessageDraftAction(sendMessageRichMessageDraftActionData) = type { + let parsedRichMessage = RichTextMessageAttribute(apiRichMessage: sendMessageRichMessageDraftActionData.richMessage) + updatedState.addPeerLiveTypingDraftUpdate(peerAndThreadId: PeerAndThreadId(peerId: channelPeerId, threadId: threadId), id: sendMessageRichMessageDraftActionData.randomId, timestamp: date, peerId: userId.peerId, content: .rich(parsedRichMessage)) } else { let activity = PeerInputActivity(apiType: type, peerId: nil, timestamp: date) var category: PeerActivitySpace.Category = .global @@ -1819,19 +1837,6 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.updatePeerChatInclusion(peerId: peer.peerId, groupId: PeerGroupId(rawValue: folderId), changedGroup: true) } } - case let .updatePeerLocated(updatePeerLocatedData): - var peersNearby: [PeerNearby] = [] - for peer in updatePeerLocatedData.peers { - switch peer { - case let .peerLocated(peerLocatedData): - let (peer, expires, distance) = (peerLocatedData.peer, peerLocatedData.expires, peerLocatedData.distance) - peersNearby.append(.peer(id: peer.peerId, expires: expires, distance: distance)) - case let .peerSelfLocated(peerSelfLocatedData): - let expires = peerSelfLocatedData.expires - peersNearby.append(.selfPeer(expires: expires)) - } - } - updatedState.updatePeersNearby(peersNearby) case let .updateNewScheduledMessage(updateNewScheduledMessageData): var peerIsForum = false if let peerId = updateNewScheduledMessageData.message.peerId { @@ -1961,6 +1966,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: updatedState.addUpdateAttachMenuBots() case let .updateWebViewResultSent(updateWebViewResultSentData): updatedState.addDismissWebView(updateWebViewResultSentData.queryId) + case let .updateJoinChatWebViewDecision(updateJoinChatWebViewDecisionData): + updatedState.addJoinChatWebViewDecision(JoinChatWebViewDecision(peerId: updateJoinChatWebViewDecisionData.peer.peerId, queryId: updateJoinChatWebViewDecisionData.queryId, result: JoinChatWebViewResult(apiResult: updateJoinChatWebViewDecisionData.result))) case .updateConfig: updatedState.reloadConfig() case let .updateMessageExtendedMedia(updateMessageExtendedMediaData): @@ -1978,6 +1985,34 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: let (flags, hash, date, device, location) = (updateNewAuthorizationData.flags, updateNewAuthorizationData.hash, updateNewAuthorizationData.date, updateNewAuthorizationData.device, updateNewAuthorizationData.location) let isUnconfirmed = (flags & (1 << 0)) != 0 updatedState.updateNewAuthorization(isUnconfirmed: isUnconfirmed, hash: hash, date: date ?? 0, device: device ?? "", location: location ?? "") + case let .updateNewBotConnection(updateNewBotConnectionData): + let (flags, botId, date, device, location) = (updateNewBotConnectionData.flags, updateNewBotConnectionData.botId, updateNewBotConnectionData.date, updateNewBotConnectionData.device, updateNewBotConnectionData.location) + updatedState.updateNewBotConnection( + confirmed: (flags & (1 << 0)) != 0, + botId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), + date: date, + device: device, + location: location + ) + case let .updateWebBrowserSettings(updateWebBrowserSettingsData): + updatedState.updateWebBrowserSettings(openExternalBrowser: (updateWebBrowserSettingsData.flags & (1 << 0)) != 0) + case let .updateWebBrowserException(updateWebBrowserExceptionData): + let openExternalBrowser: Bool? + if let value = updateWebBrowserExceptionData.openExternalBrowser { + switch value { + case .boolFalse: + openExternalBrowser = false + case .boolTrue: + openExternalBrowser = true + } + } else { + openExternalBrowser = nil + } + updatedState.updateWebBrowserException( + openExternalBrowser: openExternalBrowser, + delete: (updateWebBrowserExceptionData.flags & (1 << 1)) != 0, + exception: AccountWebBrowserException(apiWebDomainException: updateWebBrowserExceptionData.exception) + ) case let .updatePeerWallpaper(updatePeerWallpaperData): updatedState.updateWallpaper(peerId: updatePeerWallpaperData.peer.peerId, wallpaper: updatePeerWallpaperData.wallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) }) case let .updateStarsBalance(updateStarsBalanceData): @@ -2806,6 +2841,15 @@ func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set) { } } } + for media in message.media { + if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content { + for attribute in content.attributes { + if case let .aiTextStyle(aiTextStyle) = attribute { + fileIds.insert(aiTextStyle.emojiFileId) + } + } + } + } } private func messagesFromOperations(state: AccountMutableState) -> [StoreMessage] { @@ -3309,7 +3353,7 @@ func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Network, pe resetForumTopics.insert(peerId) } - + for message in messages { var peerIsForum = false if let peerId = message.peerId { @@ -3799,7 +3843,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateNewBotConnection, .UpdateWebBrowserSettings, .UpdateWebBrowserException, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3918,7 +3962,6 @@ func replayFinalState( var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [] var groupCallMessageUpdates: [GroupCallMessageUpdate] = [] var storyUpdates: [InternalStoryUpdate] = [] - var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] var recentlyUsedStickers: [MediaId: (MessageIndex, TelegramMediaFile)] = [:] @@ -3943,6 +3986,8 @@ func replayFinalState( var updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:] var updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:] var updatedEmojiGameInfo: EmojiGameInfo? + var recentlyUsedGuestChatBots = Set() + var webBrowserSettingsUpdates: [(AccountWebBrowserSettings) -> AccountWebBrowserSettings] = [] var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] @@ -3994,24 +4039,22 @@ func replayFinalState( var threadId: Int64? var authorId: PeerId var timestamp: Int32 - var text: String - var entities: [MessageTextEntity] + var content: PeerLiveTypingDraftUpdateContent - init(id: Int64, threadId: Int64?, authorId: PeerId, timestamp: Int32, text: String, entities: [MessageTextEntity]) { + init(id: Int64, threadId: Int64?, authorId: PeerId, timestamp: Int32, content: PeerLiveTypingDraftUpdateContent) { self.id = id self.threadId = threadId self.authorId = authorId self.timestamp = timestamp - self.text = text - self.entities = entities + self.content = content } } case update(Update) - case cancel + case cancel(updatedTimestamp: Int32) } - var liveTypingDraftUpdates: [PeerAndThreadId: LiveTypingDraftUpdate] = [:] + var liveTypingDraftUpdates: [PeerAndThreadId: [LiveTypingDraftUpdate]] = [:] for operation in finalState.state.operations { switch operation { @@ -4226,11 +4269,11 @@ func replayFinalState( let allKey = PeerAndThreadId(peerId: chatPeerId, threadId: nil) if liveTypingDraftUpdates[key] != nil { - liveTypingDraftUpdates[key] = .cancel - liveTypingDraftUpdates[allKey] = .cancel + liveTypingDraftUpdates[key] = [.cancel(updatedTimestamp: message.timestamp)] + liveTypingDraftUpdates[allKey] = [.cancel(updatedTimestamp: message.timestamp)] } else if let currentDraft = transaction.getCurrentTypingDraft(location: key) { - liveTypingDraftUpdates[key] = .cancel - liveTypingDraftUpdates[allKey] = .cancel + liveTypingDraftUpdates[key] = [.cancel(updatedTimestamp: message.timestamp)] + liveTypingDraftUpdates[allKey] = [.cancel(updatedTimestamp: message.timestamp)] messages[i] = messages[i].withUpdatedCustomStableId(currentDraft.stableId) } } @@ -4341,6 +4384,15 @@ func replayFinalState( } } } + + if message.flags.contains(.Incoming), let authorId = message.authorId { + for attribute in message.attributes { + if let attribute = attribute as? GuestChatMessageAttribute, attribute.peerId == accountPeerId { + recentlyUsedGuestChatBots.insert(authorId) + break + } + } + } } if !message.flags.contains(.Incoming) && !message.flags.contains(.Unsent) { if message.id.peerId.namespace == Namespaces.Peer.CloudChannel { @@ -4350,7 +4402,7 @@ func replayFinalState( if !message.flags.contains(.Incoming), message.forwardInfo == nil { if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(message.id.peerId.namespace), let peer = transaction.getPeer(message.id.peerId), peer.isCopyProtectionEnabled { - + } else if message.id.peerId.namespace == Namespaces.Peer.CloudUser, let cachedUserData = transaction.getPeerCachedData(peerId: message.id.peerId) as? CachedUserData, cachedUserData.flags.contains(.copyProtectionEnabled) || cachedUserData.flags.contains(.myCopyProtectionEnabled) { } else { @@ -4515,7 +4567,7 @@ func replayFinalState( if let apiPoll = apiPoll { switch apiPoll { case let .poll(pollData): - let (id, flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash) + let (id, flags, question, answers, closePeriod, closeDate, pollHash, countries) = (pollData.id, pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash, pollData.countriesIso2) let publicity: TelegramMediaPollPublicity if (flags & (1 << 1)) != 0 { publicity = .public @@ -4533,6 +4585,7 @@ func replayFinalState( let shuffleAnswers = (flags & (1 << 8)) != 0 let hideResultsUntilClose = (flags & (1 << 9)) != 0 let isCreator = (flags & (1 << 10)) != 0 + let restrictToSubscribers = (flags & (1 << 11)) != 0 let questionText: String let questionEntities: [MessageTextEntity] @@ -4543,7 +4596,7 @@ func replayFinalState( questionEntities = messageTextEntitiesFromApiEntities(entities) } - updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: poll.attachedMedia) + updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, isCreator: isCreator, attachedMedia: poll.attachedMedia, restrictToSubscribers: restrictToSubscribers, countries: countries ?? []) } } updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin) @@ -4887,26 +4940,24 @@ func replayFinalState( }) } case let .MergePeerPresences(statuses, explicit): - var presences: [PeerId: PeerPresence] = [:] + var presences: [PeerId: UpdatedApiPresence] = [:] for (peerId, status) in statuses { if peerId == accountPeerId { if explicit { - switch status { - case let .userStatusOnline(userStatusOnlineData): - let timestamp = userStatusOnlineData.expires - delayNotificatonsUntil = timestamp + 30 - case let .userStatusOffline(userStatusOfflineData): - let timestamp = userStatusOfflineData.wasOnline - delayNotificatonsUntil = timestamp - default: - break + switch status.status { + case let .userStatusOnline(userStatusOnlineData): + let timestamp = userStatusOnlineData.expires + delayNotificatonsUntil = timestamp + 30 + case let .userStatusOffline(userStatusOfflineData): + let timestamp = userStatusOfflineData.wasOnline + delayNotificatonsUntil = timestamp + default: + break } } } else { - let presence = TelegramUserPresence(apiStatus: status) - presences[peerId] = presence + presences[peerId] = status } - } updatePeerPresencesClean(transaction: transaction, accountPeerId: accountPeerId, peerPresences: presences) case let .UpdateSecretChat(chat, _): @@ -4929,24 +4980,29 @@ func replayFinalState( } else if chatPeerId.peerId.namespace == Namespaces.Peer.SecretChat { updatedSecretChatTypingActivities.insert(chatPeerId.peerId) } - case let .AddPeerLiveTypingDraftUpdate(peerAndThreadId, id, timestamp, authorId, text, entities): - liveTypingDraftUpdates[peerAndThreadId] = .update(LiveTypingDraftUpdate.Update( + case let .AddPeerLiveTypingDraftUpdate(peerAndThreadId, id, timestamp, authorId, content): + if liveTypingDraftUpdates[peerAndThreadId] == nil { + liveTypingDraftUpdates[peerAndThreadId] = [] + } + liveTypingDraftUpdates[peerAndThreadId]?.append(.update(LiveTypingDraftUpdate.Update( id: id, threadId: peerAndThreadId.threadId, authorId: authorId, timestamp: timestamp, - text: text, - entities: entities - )) + content: content + ))) if peerAndThreadId.threadId != nil { - liveTypingDraftUpdates[PeerAndThreadId(peerId: peerAndThreadId.peerId, threadId: nil)] = .update(LiveTypingDraftUpdate.Update( + let allKey = PeerAndThreadId(peerId: peerAndThreadId.peerId, threadId: nil) + if liveTypingDraftUpdates[allKey] == nil { + liveTypingDraftUpdates[allKey] = [] + } + liveTypingDraftUpdates[allKey]?.append(.update(LiveTypingDraftUpdate.Update( id: id, threadId: peerAndThreadId.threadId, authorId: authorId, timestamp: timestamp, - text: text, - entities: entities - )) + content: content + ))) } case let .UpdatePinnedItemIds(groupId, pinnedOperation): switch pinnedOperation { @@ -5193,8 +5249,6 @@ func replayFinalState( } case let .UpdateIsContact(peerId, value): isContactUpdates.append((peerId, value)) - case let .UpdatePeersNearby(peersNearby): - updatedPeersNearby = peersNearby case let .UpdateTheme(theme): updatedThemes[theme.id] = theme case let .UpdateWallpaper(peerId, wallpaper): @@ -5516,6 +5570,26 @@ func replayFinalState( } else { transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, itemId: id.rawValue) } + case let .UpdateNewBotConnection(confirmed, botId, date, device, location): + let id = NewBotConnectionReview.Id(botId: botId) + if confirmed { + transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewBotConnectionReviews, itemId: id.rawValue) + } else if let entry = CodableEntry(NewBotConnectionReview( + botId: botId, + device: device, + location: location, + timestamp: date + )) { + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewBotConnectionReviews, item: OrderedItemListEntry(id: id.rawValue, contents: entry), removeTailIfCountExceeds: 200) + } + case let .UpdateWebBrowserSettings(openExternalBrowser): + webBrowserSettingsUpdates.append { settings in + settings.withUpdatedOpenExternalBrowser(openExternalBrowser) + } + case let .UpdateWebBrowserException(openExternalBrowser, delete, exception): + webBrowserSettingsUpdates.append { settings in + settings.withAppliedExceptionUpdate(openExternalBrowser: openExternalBrowser, delete: delete, exception: exception) + } case let .UpdateStarsBalance(peerId, currency, balance): switch currency { case .ton: @@ -5859,6 +5933,10 @@ func replayFinalState( } } + for peerId in recentlyUsedGuestChatBots { + _internal_addRecentlyUsedInlineBot(transaction: transaction, peerId: peerId) + } + if syncAttachMenuBots { // addSynchronizeAttachMenuBotsOperation(transaction: transaction) } @@ -5881,6 +5959,7 @@ func replayFinalState( var addedSecretMessageIds: [MessageId] = [] var addedSecretMessageAuthorIds: [PeerId: PeerId] = [:] + let keepArchivedUnmuted = fetchGlobalPrivacySettings(transaction: transaction).keepArchivedUnmuted for peerId in peerIdsWithAddedSecretMessages { inner: while true { @@ -5889,7 +5968,7 @@ func replayFinalState( let processResult = processSecretChatIncomingDecryptedOperations(encryptionProvider: encryptionProvider, mediaBox: mediaBox, transaction: transaction, peerId: peerId) if !processResult.addedMessages.isEmpty { let currentInclusion = transaction.getPeerChatListInclusion(peerId) - if let groupId = currentInclusion.groupId, groupId == Namespaces.PeerGroup.archive { + if let groupId = currentInclusion.groupId, groupId == Namespaces.PeerGroup.archive, !keepArchivedUnmuted { if let peer = transaction.getPeer(peerId) as? TelegramSecretChat { let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: transaction.getGlobalNotificationSettings(), peer: peer, peerSettings: transaction.getPeerNotificationSettings(id: peer.regularPeerId)) @@ -5989,6 +6068,16 @@ func replayFinalState( }) }.start() } + + if !webBrowserSettingsUpdates.isEmpty { + transaction.updatePreferencesEntry(key: PreferencesKeys.webBrowserSettings, { current in + var settings = current?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + for update in webBrowserSettingsUpdates { + settings = update(settings) + } + return PreferencesEntry(settings) + }) + } if !updatedWallpapers.isEmpty { for (peerId, wallpaper) in updatedWallpapers { @@ -6048,23 +6137,79 @@ func replayFinalState( } if !liveTypingDraftUpdates.isEmpty { + for (key, updates) in liveTypingDraftUpdates { + if key.threadId == nil { + var maxCancelledTimestamp: Int32? + for update in updates { + if case let .cancel(updatedTimestamp) = update { + if let current = maxCancelledTimestamp { + maxCancelledTimestamp = max(current, updatedTimestamp) + } else { + maxCancelledTimestamp = updatedTimestamp + } + } + } + if let maxCancelledTimestamp { + transaction.offsetPendingMessagesTimestamps(lowerBound: MessageId(peerId: key.peerId, namespace: Namespaces.Message.Local, id: 1), excludeIds: Set(), timestamp: maxCancelledTimestamp) + } + } + } transaction.combineTypingDrafts(locations: Set(liveTypingDraftUpdates.keys), update: { key, current in - guard let update = liveTypingDraftUpdates[key] else { + guard let update = liveTypingDraftUpdates[key]?.max(by: { lhs, rhs in + switch lhs { + case .cancel: + return false + case let .update(lhsUpdate): + switch rhs { + case .cancel: + return true + case let .update(rhsUpdate): + return lhsUpdate.timestamp < rhsUpdate.timestamp + } + } + }) else { return current } switch update { case let .update(update): + if let current, current.id > update.id { + return current + } + var timestamp = update.timestamp + if let current, current.id == update.id { + timestamp = current.timestamp + } + if current == nil { + if let index = transaction.getTopPeerMessageIndex(peerId: key.peerId) { + timestamp = max(timestamp, index.timestamp) + } + } + + let draftText: String + let draftAttributes: [MessageAttribute] + switch update.content { + case let .plain(text, entities): + draftText = text + draftAttributes = [ + TypingDraftMessageAttribute(), + TextEntitiesMessageAttribute(entities: entities) + ] + case let .rich(richData): + draftText = "" + draftAttributes = [ + TypingDraftMessageAttribute(), + richData + ] + } + return ( update.id, Namespaces.Message.Cloud, update.threadId, update.authorId, - update.timestamp, - update.text, - [ - TypingDraftMessageAttribute(), - TextEntitiesMessageAttribute(entities: update.entities) - ] + timestamp, + draftText, + draftAttributes ) case .cancel: return nil @@ -6086,7 +6231,6 @@ func replayFinalState( updatedGroupCallParticipants: updatedGroupCallParticipants, groupCallMessageUpdates: groupCallMessageUpdates, storyUpdates: storyUpdates, - updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index eea995bc40..101f25a391 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -40,10 +40,6 @@ private final class UpdatedWebpageSubscriberContext { let subscribers = Bag<(TelegramMediaWebpage) -> Void>() } -private final class UpdatedPeersNearbySubscriberContext { - let subscribers = Bag<([PeerNearby]) -> Void>() -} - private final class UpdatedStarsBalanceSubscriberContext { let subscribers = Bag<([PeerId: StarsAmount]) -> Void>() } @@ -264,6 +260,11 @@ public final class AccountStateManager { public var dismissBotWebViews: Signal<[Int64], NoError> { return self.dismissBotWebViewsPipe.signal() } + + private let joinChatWebViewDecisionsPipe = ValuePipe<[JoinChatWebViewDecision]>() + public var joinChatWebViewDecisions: Signal<[JoinChatWebViewDecision], NoError> { + return self.joinChatWebViewDecisionsPipe.signal() + } private let externallyUpdatedPeerIdsPipe = ValuePipe<[PeerId]>() var externallyUpdatedPeerIds: Signal<[PeerId], NoError> { @@ -360,8 +361,12 @@ public final class AccountStateManager { return self.starRefBotConnectionEventsPipe.signal() } + fileprivate let installedStickerPacksArchivedEventsPipe = ValuePipe() + var installedStickerPacksArchivedEvents: Signal { + return self.installedStickerPacksArchivedEventsPipe.signal() + } + private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:] - private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext() private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedTonBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext() @@ -1122,9 +1127,6 @@ public final class AccountStateManager { if !events.updatedWebpages.isEmpty { strongSelf.notifyUpdatedWebpages(events.updatedWebpages) } - if let updatedPeersNearby = events.updatedPeersNearby { - strongSelf.notifyUpdatedPeersNearby(updatedPeersNearby) - } if !events.updatedStarsBalance.isEmpty { strongSelf.notifyUpdatedStarsBalance(events.updatedStarsBalance) } @@ -1266,6 +1268,10 @@ public final class AccountStateManager { self.dismissBotWebViewsPipe.putNext(events.dismissBotWebViews) } + if !events.joinChatWebViewDecisions.isEmpty { + self.joinChatWebViewDecisionsPipe.putNext(events.joinChatWebViewDecisions) + } + if !events.externallyUpdatedPeerId.isEmpty { self.externallyUpdatedPeerIdsPipe.putNext(Array(events.externallyUpdatedPeerId)) } @@ -1704,34 +1710,7 @@ public final class AccountStateManager { } } } - - public func updatedPeersNearby() -> Signal<[PeerNearby], NoError> { - let queue = self.queue - return Signal { [weak self] subscriber in - let disposable = MetaDisposable() - queue.async { - if let strongSelf = self { - let index = strongSelf.updatedPeersNearbyContext.subscribers.add({ peersNearby in - subscriber.putNext(peersNearby) - }) - - disposable.set(ActionDisposable { - if let strongSelf = self { - strongSelf.updatedPeersNearbyContext.subscribers.remove(index) - } - }) - } - } - return disposable - } - } - - private func notifyUpdatedPeersNearby(_ updatedPeersNearby: [PeerNearby]) { - for subscriber in self.updatedPeersNearbyContext.subscribers.copyItems() { - subscriber(updatedPeersNearby) - } - } - + public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> { let queue = self.queue return Signal { [weak self] subscriber in @@ -1958,6 +1937,12 @@ public final class AccountStateManager { } } + public var joinChatWebViewDecisions: Signal<[JoinChatWebViewDecision], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.joinChatWebViewDecisions.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + var externallyUpdatedPeerIds: Signal<[PeerId], NoError> { return self.impl.signalWith { impl, subscriber in return impl.externallyUpdatedPeerIds.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) @@ -2036,6 +2021,18 @@ public final class AccountStateManager { } } + public var installedStickerPacksArchivedEvents: Signal { + return self.impl.signalWith { impl, subscriber in + return impl.installedStickerPacksArchivedEvents.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + + func installedStickerPacksArchived(count: Int) { + self.impl.with { impl in + impl.installedStickerPacksArchivedEventsPipe.putNext(count) + } + } + var botPreviewUpdates: Signal<[InternalBotPreviewUpdate], NoError> { return self.impl.signalWith { impl, subscriber in return impl.botPreviewUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) @@ -2218,12 +2215,6 @@ public final class AccountStateManager { } } - public func updatedPeersNearby() -> Signal<[PeerNearby], NoError> { - return self.impl.signalWith { impl, subscriber in - return impl.updatedPeersNearby().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) - } - } - public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> { return self.impl.signalWith { impl, subscriber in return impl.updatedStarsBalance().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 84ba2acf28..0cc816fc04 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -127,6 +127,7 @@ final class AccountTaskManager { tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) tasks.add(managedSavedMusicIdsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) tasks.add(managedEmojiGameUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedSynchronizeCloudAITextStyles(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) @@ -134,6 +135,7 @@ final class AccountTaskManager { self.reloadAppConfiguration() tasks.add(managedPremiumPromoConfigurationUpdates(accountPeerId: self.accountPeerId, postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedAutodownloadSettingsUpdates(accountManager: self.accountManager, network: self.stateManager.network).start()) + tasks.add(managedWebBrowserSettingsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(managedTermsOfServiceUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start()) tasks.add(managedAppUpdateInfo(network: self.stateManager.network, stateManager: self.stateManager).start()) tasks.add(managedLocalizationUpdatesOperations(accountManager: self.accountManager, postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index f5eb58ef58..3c5bfd89ba 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -1423,10 +1423,6 @@ private final class CallSessionManagerContext { } } -public enum CallRequestError { - case generic -} - public final class CallSessionManager { public static func getStableIncomingUUID(stableId: Int64) -> UUID { return StableIncomingUUIDs.shared.with { impl in diff --git a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift index 76c2660344..7c6cfff113 100644 --- a/submodules/TelegramCore/Sources/State/ContactSyncManager.swift +++ b/submodules/TelegramCore/Sources/State/ContactSyncManager.swift @@ -405,12 +405,12 @@ private func updateContactPresences(postbox: Postbox, network: Network, accountP } |> mapToSignal { statuses -> Signal in return postbox.transaction { transaction -> Void in - var peerPresences: [PeerId: PeerPresence] = [:] + var peerPresences: [PeerId: UpdatedApiPresence] = [:] for status in statuses { switch status { - case let .contactStatus(contactStatusData): - let (userId, status) = (contactStatusData.userId, contactStatusData.status) - peerPresences[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))] = TelegramUserPresence(apiStatus: status) + case let .contactStatus(contactStatusData): + let (userId, status) = (contactStatusData.userId, contactStatusData.status) + peerPresences[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))] = UpdatedApiPresence(status: status, isMin: false) } } updatePeerPresencesClean(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences) diff --git a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift index 1ebe2f011e..5e2cdf1b07 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift @@ -1889,7 +1889,7 @@ private func sendStandaloneMessage(auxiliaryMethods: AccountAuxiliaryMethods, po var attributes = contents.attributes if !attributes.contains(where: { $0 is AutoremoveTimeoutMessageAttribute }), let messageAutoremoveTimeout = state.messageAutoremoveTimeout { - attributes.append(AutoclearTimeoutMessageAttribute(timeout: messageAutoremoveTimeout, countdownBeginTime: nil)) + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: messageAutoremoveTimeout, countdownBeginTime: nil)) } let message = Message( diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift index a3f9e97831..c6d1603dd7 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeChatInputStateOperations.swift @@ -251,7 +251,7 @@ private func synchronizeChatInputState(transaction: Transaction, postbox: Postbo flags |= 1 << 8 } - return network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: inputState?.text ?? "", entities: apiEntitiesFromMessageTextEntities(inputState?.entities ?? [], associatedPeers: SimpleDictionary()), media: nil, effect: nil, suggestedPost: suggestedPost)) + return network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: inputState?.text ?? "", entities: apiEntitiesFromMessageTextEntities(inputState?.entities ?? [], associatedPeers: SimpleDictionary()), media: nil, effect: nil, suggestedPost: suggestedPost, richMessage: nil)) |> delay(2.0, queue: Queue.concurrentDefaultQueue()) |> `catch` { _ -> Signal in return .single(.boolFalse) diff --git a/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift index d7e1728e67..58c7a11fef 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSynchronizeInstalledStickerPacksOperations.swift @@ -228,49 +228,60 @@ private func resolveStickerPacks(network: Network, remoteInfos: [ItemCollectionI } } -private func installRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal, NoError> { - var signals: [Signal, NoError>] = [] +private struct InstallRemoteStickerPacksResult { + var ids: Set + var archivedCount: Int + + init(ids: Set, archivedCount: Int) { + self.ids = ids + self.archivedCount = archivedCount + } +} + +private func installRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal { + var signals: [Signal] = [] for info in infos { let install = network.request(Api.functions.messages.installStickerSet(stickerset: .inputStickerSetID(.init(id: info.id.id, accessHash: info.accessHash)), archived: .boolFalse)) - |> map { result -> Set in - switch result { - case .stickerSetInstallResultSuccess: - return Set() - case let .stickerSetInstallResultArchive(stickerSetInstallResultArchiveData): - let archivedSets = stickerSetInstallResultArchiveData.sets - var archivedIds = Set() - for archivedSet in archivedSets { - switch archivedSet { - case let .stickerSetCovered(stickerSetCoveredData): - let set = stickerSetCoveredData.set - archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) - case let .stickerSetMultiCovered(stickerSetMultiCoveredData): - let set = stickerSetMultiCoveredData.set - archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) - case let .stickerSetFullCovered(stickerSetFullCoveredData): - let set = stickerSetFullCoveredData.set - archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) - case let .stickerSetNoCovered(stickerSetNoCoveredData): - let set = stickerSetNoCoveredData.set - archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) - } - } - return archivedIds + |> map { result -> InstallRemoteStickerPacksResult in + switch result { + case .stickerSetInstallResultSuccess: + return InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0) + case let .stickerSetInstallResultArchive(stickerSetInstallResultArchiveData): + let archivedSets = stickerSetInstallResultArchiveData.sets + var archivedIds = Set() + for archivedSet in archivedSets { + switch archivedSet { + case let .stickerSetCovered(stickerSetCoveredData): + let set = stickerSetCoveredData.set + archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) + case let .stickerSetMultiCovered(stickerSetMultiCoveredData): + let set = stickerSetMultiCoveredData.set + archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) + case let .stickerSetFullCovered(stickerSetFullCoveredData): + let set = stickerSetFullCoveredData.set + archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) + case let .stickerSetNoCovered(stickerSetNoCoveredData): + let set = stickerSetNoCoveredData.set + archivedIds.insert(StickerPackCollectionInfo(apiSet: set, namespace: info.id.namespace).id) + } } + return InstallRemoteStickerPacksResult(ids: archivedIds, archivedCount: archivedIds.count) } - |> `catch` { _ -> Signal, NoError> in - return .single(Set()) - } + } + |> `catch` { _ -> Signal in + return .single(InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0)) + } signals.append(install) } return combineLatest(signals) - |> map { idsSets -> Set in - var result = Set() - for ids in idsSets { - result.formUnion(ids) - } - return result + |> map { results -> InstallRemoteStickerPacksResult in + var result = InstallRemoteStickerPacksResult(ids: Set(), archivedCount: 0) + for resultValue in results { + result.ids.formUnion(resultValue.ids) + result.archivedCount += resultValue.archivedCount } + return result + } } private func removeRemoteStickerPacks(network: Network, infos: [StickerPackCollectionInfo]) -> Signal { @@ -341,12 +352,12 @@ private func reorderRemoteStickerPacks(network: Network, namespace: SynchronizeI private func synchronizeInstalledStickerPacks(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, namespace: SynchronizeInstalledStickerPacksOperationNamespace, operation: SynchronizeInstalledStickerPacksOperation) -> Signal { let collectionNamespace: ItemCollectionId.Namespace switch namespace { - case .stickers: - collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks - case .masks: - collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks - case .emoji: - collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks + case .stickers: + collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks + case .masks: + collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks + case .emoji: + collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks } let localCollectionInfos = transaction.getItemCollectionsInfos(namespace: collectionNamespace).map { $0.1 as! StickerPackCollectionInfo } @@ -466,12 +477,12 @@ func debugFetchAllStickers(account: Account) -> Signal { private func continueSynchronizeInstalledStickerPacks(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, namespace: SynchronizeInstalledStickerPacksOperationNamespace, operation: SynchronizeInstalledStickerPacksOperation) -> Signal { let collectionNamespace: ItemCollectionId.Namespace switch namespace { - case .stickers: - collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks - case .masks: - collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks - case .emoji: - collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks + case .stickers: + collectionNamespace = Namespaces.ItemCollection.CloudStickerPacks + case .masks: + collectionNamespace = Namespaces.ItemCollection.CloudMaskPacks + case .emoji: + collectionNamespace = Namespaces.ItemCollection.CloudEmojiPacks } let localCollectionInfos = transaction.getItemCollectionsInfos(namespace: collectionNamespace).map { $0.1 as! StickerPackCollectionInfo } @@ -479,12 +490,12 @@ private func continueSynchronizeInstalledStickerPacks(transaction: Transaction, let request: Signal switch namespace { - case .stickers: - request = network.request(Api.functions.messages.getAllStickers(hash: initialLocalHash)) - case .masks: - request = network.request(Api.functions.messages.getMaskStickers(hash: initialLocalHash)) - case .emoji: - request = network.request(Api.functions.messages.getEmojiStickers(hash: initialLocalHash)) + case .stickers: + request = network.request(Api.functions.messages.getAllStickers(hash: initialLocalHash)) + case .masks: + request = network.request(Api.functions.messages.getMaskStickers(hash: initialLocalHash)) + case .emoji: + request = network.request(Api.functions.messages.getEmojiStickers(hash: initialLocalHash)) } let sequence = request @@ -661,7 +672,12 @@ private func continueSynchronizeInstalledStickerPacks(transaction: Transaction, |> then(Signal.single(Void()))) |> mapToSignal { _ -> Signal, NoError> in return installRemoteStickerPacks(network: network, infos: addRemoteCollectionInfos) - |> mapToSignal { ids -> Signal, NoError> in + |> mapToSignal { result -> Signal, NoError> in + let ids = result.ids + if result.archivedCount != 0 { + stateManager.installedStickerPacksArchived(count: result.archivedCount) + } + return (reorderRemoteStickerPacks(network: network, namespace: namespace, ids: resultingCollectionInfos.map({ $0.0.id }).filter({ !ids.contains($0) })) |> then(Signal.single(Void()))) |> map { _ -> Set in diff --git a/submodules/TelegramCore/Sources/State/ManagedWebBrowserSettingsUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedWebBrowserSettingsUpdates.swift new file mode 100644 index 0000000000..51ab736b4a --- /dev/null +++ b/submodules/TelegramCore/Sources/State/ManagedWebBrowserSettingsUpdates.swift @@ -0,0 +1,15 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + +func managedWebBrowserSettingsUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + return (_internal_getAccountWebBrowserSettings(postbox: postbox, network: network) + |> ignoreValues).start(completed: { + subscriber.putCompletion() + }) + } + return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift index 587b8e8f1a..54f5eead31 100644 --- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift +++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift @@ -117,46 +117,50 @@ public enum PeerInputActivity: Comparable { extension PeerInputActivity { init?(apiType: Api.SendMessageAction, peerId: PeerId?, timestamp: Int32) { switch apiType { - case .sendMessageCancelAction, .sendMessageChooseContactAction, .sendMessageGeoLocationAction, .sendMessageRecordVideoAction: - return nil - case .sendMessageGamePlayAction: - self = .playingGame - case .sendMessageRecordAudioAction, .sendMessageUploadAudioAction: - self = .recordingVoice - case .sendMessageTypingAction: - self = .typingText - case let .sendMessageUploadDocumentAction(sendMessageUploadDocumentActionData): - let progress = sendMessageUploadDocumentActionData.progress - self = .uploadingFile(progress: progress) - case let .sendMessageUploadPhotoAction(sendMessageUploadPhotoActionData): - let progress = sendMessageUploadPhotoActionData.progress - self = .uploadingPhoto(progress: progress) - case let .sendMessageUploadVideoAction(sendMessageUploadVideoActionData): - let progress = sendMessageUploadVideoActionData.progress - self = .uploadingVideo(progress: progress) - case .sendMessageRecordRoundAction: - self = .recordingInstantVideo - case let .sendMessageUploadRoundAction(sendMessageUploadRoundActionData): - let progress = sendMessageUploadRoundActionData.progress - self = .uploadingInstantVideo(progress: progress) - case .speakingInGroupCallAction: - self = .speakingInGroupCall(timestamp: timestamp) - case .sendMessageChooseStickerAction: - self = .choosingSticker - case .sendMessageHistoryImportAction: - return nil - case let .sendMessageEmojiInteraction(sendMessageEmojiInteractionData): - let (emoticon, messageId, interaction) = (sendMessageEmojiInteractionData.emoticon, sendMessageEmojiInteractionData.msgId, sendMessageEmojiInteractionData.interaction) - if let peerId = peerId { - self = .interactingWithEmoji(emoticon: emoticon, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId), interaction: EmojiInteraction(apiDataJson: interaction)) - } else { - return nil - } - case let .sendMessageEmojiInteractionSeen(sendMessageEmojiInteractionSeenData): - let emoticon = sendMessageEmojiInteractionSeenData.emoticon - self = .seeingEmojiInteraction(emoticon: emoticon) - case .sendMessageTextDraftAction: + case .sendMessageCancelAction, .sendMessageChooseContactAction, .sendMessageGeoLocationAction, .sendMessageRecordVideoAction: + return nil + case .sendMessageGamePlayAction: + self = .playingGame + case .sendMessageRecordAudioAction, .sendMessageUploadAudioAction: + self = .recordingVoice + case .sendMessageTypingAction: + self = .typingText + case let .sendMessageUploadDocumentAction(sendMessageUploadDocumentActionData): + let progress = sendMessageUploadDocumentActionData.progress + self = .uploadingFile(progress: progress) + case let .sendMessageUploadPhotoAction(sendMessageUploadPhotoActionData): + let progress = sendMessageUploadPhotoActionData.progress + self = .uploadingPhoto(progress: progress) + case let .sendMessageUploadVideoAction(sendMessageUploadVideoActionData): + let progress = sendMessageUploadVideoActionData.progress + self = .uploadingVideo(progress: progress) + case .sendMessageRecordRoundAction: + self = .recordingInstantVideo + case let .sendMessageUploadRoundAction(sendMessageUploadRoundActionData): + let progress = sendMessageUploadRoundActionData.progress + self = .uploadingInstantVideo(progress: progress) + case .speakingInGroupCallAction: + self = .speakingInGroupCall(timestamp: timestamp) + case .sendMessageChooseStickerAction: + self = .choosingSticker + case .sendMessageHistoryImportAction: + return nil + case let .sendMessageEmojiInteraction(sendMessageEmojiInteractionData): + let (emoticon, messageId, interaction) = (sendMessageEmojiInteractionData.emoticon, sendMessageEmojiInteractionData.msgId, sendMessageEmojiInteractionData.interaction) + if let peerId = peerId { + self = .interactingWithEmoji(emoticon: emoticon, messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId), interaction: EmojiInteraction(apiDataJson: interaction)) + } else { return nil + } + case let .sendMessageEmojiInteractionSeen(sendMessageEmojiInteractionSeenData): + let emoticon = sendMessageEmojiInteractionSeenData.emoticon + self = .seeingEmojiInteraction(emoticon: emoticon) + case .sendMessageTextDraftAction: + return nil + case .sendMessageRichMessageDraftAction: + return nil + case .inputSendMessageRichMessageDraftAction: + return nil } } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 0434b0e286..8fe99f1e7d 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -31,6 +31,8 @@ private enum PendingMessageState { case waitingForUploadToStart(groupId: Int64?, upload: Signal) case uploading(groupId: Int64?) case waitingToBeSent(groupId: Int64?, content: PendingMessageUploadedContentAndReuploadInfo) + case waitingForSendGate(groupId: Int64?, content: PendingMessageUploadedContentAndReuploadInfo) + case waitingForForwardSendGate case sending(groupId: Int64?) case waitingForNewTopic(message: Message) @@ -46,6 +48,10 @@ private enum PendingMessageState { return groupId case let .waitingToBeSent(groupId, _): return groupId + case let .waitingForSendGate(groupId, _): + return groupId + case .waitingForForwardSendGate: + return nil case let .sending(groupId): return groupId case let .waitingForNewTopic(message): @@ -117,7 +123,7 @@ public struct PeerPendingMessageDelivered { private final class PeerPendingMessagesSummaryContext { var messageDeliveredSubscribers = Bag<([PeerPendingMessageDelivered]) -> Void>() - var messageFailedSubscribers = Bag<(PendingMessageFailureReason) -> Void>() + var messageFailedSubscribers = Bag<(MessageId.Namespace, PendingMessageFailureReason) -> Void>() var newTopicEvents = Bag<(PendingMessageManager.NewTopicEvent) -> Void>() var isEmpty: Bool { @@ -230,6 +236,9 @@ public final class PendingMessageManager { private var messageContexts: [MessageId: PendingMessageContext] = [:] private var pendingMessageIds = Set() + private var liveTypingDraftKeys: Set = [] + private let allTypingDraftsDisposable = MetaDisposable() + private var forwardSendGateGroups: [PeerAndThreadId: [[(PendingMessageContext, Message, ForwardSourceInfoAttribute)]]] = [:] private let beginSendingMessagesDisposables = DisposableSet() private var newTopicDisposables: [PeerId: Disposable] = [:] @@ -250,6 +259,14 @@ public final class PendingMessageManager { self.localInputActivityManager = localInputActivityManager self.messageMediaPreuploadManager = messageMediaPreuploadManager self.revalidationContext = revalidationContext + + let queue = self.queue + self.allTypingDraftsDisposable.set( + (postbox.combinedView(keys: [.allTypingDrafts]) + |> deliverOn(queue)).start(next: { [weak self] view in + self?.handleLiveTypingDraftsUpdate(view) + }) + ) } deinit { @@ -257,8 +274,117 @@ public final class PendingMessageManager { for (_, disposable) in self.newTopicDisposables { disposable.dispose() } + self.allTypingDraftsDisposable.dispose() } + private func handleLiveTypingDraftsUpdate(_ combined: CombinedView) { + assert(self.queue.isCurrent()) + + let new: Set + if let view = combined.views[.allTypingDrafts] as? AllTypingDraftsView { + new = view.keys + } else { + new = [] + } + let cleared = self.liveTypingDraftKeys.subtracting(new) + self.liveTypingDraftKeys = new + for key in cleared { + self.drainSendGate(key: key) + } + } + + private func shouldGateSend(messageId: MessageId, threadId: Int64?) -> Bool { + if messageId.namespace == Namespaces.Message.ScheduledCloud { + return false + } + if messageId.peerId.namespace == Namespaces.Peer.SecretChat { + return false + } + if messageId.peerId == self.accountPeerId { + return false + } + if threadId == Message.newTopicThreadId { + return false + } + return true + } + + private func isSendGateOpen(for key: PeerAndThreadId) -> Bool { + return !self.liveTypingDraftKeys.contains(key) + } + + private func drainSendGate(key: PeerAndThreadId) { + assert(self.queue.isCurrent()) + + // (1) Single-message drain: snapshot then commit in messageId.id order. + var singleDrains: [(context: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)] = [] + for (id, context) in self.messageContexts { + if id.peerId != key.peerId { + continue + } + if context.threadId != key.threadId { + continue + } + if case let .waitingForSendGate(groupId, content) = context.state, groupId == nil { + singleDrains.append((context, id, content)) + } + } + singleDrains.sort(by: { $0.messageId.id < $1.messageId.id }) + for entry in singleDrains { + self.commitSendingSingleMessage(messageContext: entry.context, messageId: entry.messageId, content: entry.content) + } + + // (2) Grouped-album drain: collect distinct groupIds whose members match the key, + // iterate ascending by min messageId.id, fire commitSendingMessageGroup. + var groupKeys: [(groupId: Int64, minMessageId: Int32)] = [] + var seenGroupIds = Set() + for (id, context) in self.messageContexts { + if id.peerId != key.peerId { + continue + } + if context.threadId != key.threadId { + continue + } + if case let .waitingForSendGate(groupId, _) = context.state, let groupId = groupId { + if !seenGroupIds.contains(groupId) { + seenGroupIds.insert(groupId) + groupKeys.append((groupId, id.id)) + } else { + if let index = groupKeys.firstIndex(where: { $0.groupId == groupId }), id.id < groupKeys[index].minMessageId { + groupKeys[index].minMessageId = id.id + } + } + } + } + groupKeys.sort(by: { $0.minMessageId < $1.minMessageId }) + for (groupId, _) in groupKeys { + if let data = self.dataForPendingMessageGroup(groupId) { + self.commitSendingMessageGroup(groupId: groupId, messages: data) + } + } + + // (3) Forward drain: pop parked groups for this key in FIFO order; fire each. + if let parkedGroups = self.forwardSendGateGroups.removeValue(forKey: key) { + for messages in parkedGroups { + for (context, _, _) in messages { + context.state = .sending(groupId: nil) + } + let sendMessage: Signal = self.sendGroupMessagesContent(network: self.network, postbox: self.postbox, stateManager: self.stateManager, accountPeerId: self.accountPeerId, group: messages.map { data in + let (_, message, forwardInfo) = data + return (message.id, PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil, cacheReferenceKey: nil)) + }) + |> map { _ -> PendingMessageResult in + return .progress(1.0) + } + messages[0].0.sendDisposable.set((sendMessage + |> deliverOn(self.queue)).start()) + } + } + + self.updateWaitingUploads(peerId: key.peerId) + self.updatePendingMediaUploads() + } + private func updatePendingMediaUploads() { assert(self.queue.isCurrent()) @@ -321,7 +447,29 @@ public final class PendingMessageManager { } } } - + + if !removedMessageIds.isEmpty && !self.forwardSendGateGroups.isEmpty { + for key in Array(self.forwardSendGateGroups.keys) { + guard let parkedGroups = self.forwardSendGateGroups[key] else { + continue + } + var rebuilt: [[(PendingMessageContext, Message, ForwardSourceInfoAttribute)]] = [] + for group in parkedGroups { + let filtered = group.filter { entry in + return !removedMessageIds.contains(entry.1.id) + } + if !filtered.isEmpty { + rebuilt.append(filtered) + } + } + if rebuilt.isEmpty { + self.forwardSendGateGroups.removeValue(forKey: key) + } else { + self.forwardSendGateGroups[key] = rebuilt + } + } + } + if !addedMessageIds.isEmpty { Logger.shared.log("PendingMessageManager", "added messages: \(addedMessageIds)") self.beginSendingMessages(Array(addedMessageIds).sorted()) @@ -715,11 +863,24 @@ public final class PendingMessageManager { if messages.isEmpty { continue } - + + let firstMessage = messages[0].1 + let key = PeerAndThreadId(peerId: firstMessage.id.peerId, threadId: firstMessage.threadId) + if strongSelf.shouldGateSend(messageId: firstMessage.id, threadId: firstMessage.threadId) && !strongSelf.isSendGateOpen(for: key) { + for (context, _, _) in messages { + context.state = .waitingForForwardSendGate + } + if strongSelf.forwardSendGateGroups[key] == nil { + strongSelf.forwardSendGateGroups[key] = [] + } + strongSelf.forwardSendGateGroups[key]!.append(messages) + continue + } + for (context, _, _) in messages { context.state = .sending(groupId: nil) } - + let sendMessage: Signal = strongSelf.sendGroupMessagesContent(network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, accountPeerId: strongSelf.accountPeerId, group: messages.map { data in let (_, message, forwardInfo) = data return (message.id, PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil, cacheReferenceKey: nil)) @@ -739,7 +900,12 @@ public final class PendingMessageManager { if let groupId = groupId { messageContext.state = .waitingToBeSent(groupId: groupId, content: content) } else { - self.commitSendingSingleMessage(messageContext: messageContext, messageId: messageId, content: content) + let key = PeerAndThreadId(peerId: messageId.peerId, threadId: messageContext.threadId) + if self.shouldGateSend(messageId: messageId, threadId: messageContext.threadId) && !self.isSendGateOpen(for: key) { + messageContext.state = .waitingForSendGate(groupId: nil, content: content) + } else { + self.commitSendingSingleMessage(messageContext: messageContext, messageId: messageId, content: content) + } } self.updatePendingMediaUploads() } @@ -757,6 +923,8 @@ public final class PendingMessageManager { switch context.state { case .none: continue loop + case .waitingForForwardSendGate: + continue loop case let .collectingInfo(message): if message.groupingKey == groupId { return nil @@ -773,6 +941,10 @@ public final class PendingMessageManager { if contextGroupId == groupId { result.append((context, id, content)) } + case let .waitingForSendGate(contextGroupId, content): + if contextGroupId == groupId { + result.append((context, id, content)) + } case let .sending(contextGroupId): if contextGroupId == groupId { return nil @@ -792,6 +964,16 @@ public final class PendingMessageManager { } private func commitSendingMessageGroup(groupId: Int64, messages: [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)]) { + let firstMessageId = messages[0].messageId + let firstThreadId = messages[0].messageContext.threadId + let key = PeerAndThreadId(peerId: firstMessageId.peerId, threadId: firstThreadId) + if self.shouldGateSend(messageId: firstMessageId, threadId: firstThreadId) && !self.isSendGateOpen(for: key) { + for entry in messages { + entry.messageContext.state = .waitingForSendGate(groupId: groupId, content: entry.content) + } + return + } + for (context, _, _) in messages { context.state = .sending(groupId: groupId) } @@ -1423,7 +1605,7 @@ public final class PendingMessageManager { if let context = strongSelf.peerSummaryContexts[message.id.peerId] { for subscriber in context.messageFailedSubscribers.copyItems() { - subscriber(failureReason) + subscriber(message.id.namespace, failureReason) } } } @@ -1538,6 +1720,7 @@ public final class PendingMessageManager { var uniqueId: Int64 = 0 var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? var messageEntities: [Api.MessageEntity]? + var apiRichMessage: Api.InputRichMessage? var replyMessageId: Int32? var replyPeerId: PeerId? var replyQuote: EngineMessageReplyQuote? @@ -1621,6 +1804,9 @@ public final class PendingMessageManager { allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) + } else if let attribute = attribute as? RichTextMessageAttribute { + apiRichMessage = attribute.apiInputRichMessage() + flags |= Int32(1 << 23) } } @@ -1746,7 +1932,7 @@ public final class PendingMessageManager { flags |= 1 << 22 } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost, richMessage: apiRichMessage), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -2049,7 +2235,7 @@ public final class PendingMessageManager { if let context = strongSelf.peerSummaryContexts[message.id.peerId] { for subscriber in context.messageFailedSubscribers.copyItems() { - subscriber(failureReason) + subscriber(message.id.namespace, failureReason) } } } @@ -2252,7 +2438,7 @@ public final class PendingMessageManager { } } - public func failedMessageEvents(peerId: PeerId) -> Signal { + public func failedMessageEvents(peerId: PeerId, isScheduled: Bool) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() @@ -2265,8 +2451,16 @@ public final class PendingMessageManager { self.peerSummaryContexts[peerId] = summaryContext } - let index = summaryContext.messageFailedSubscribers.add({ reason in - subscriber.putNext(reason) + let index = summaryContext.messageFailedSubscribers.add({ namespace, reason in + if isScheduled { + if Namespaces.Message.allScheduled.contains(namespace) { + subscriber.putNext(reason) + } + } else { + if !Namespaces.Message.allScheduled.contains(namespace) { + subscriber.putNext(reason) + } + } }) disposable.set(ActionDisposable { diff --git a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift index d90c362ca2..d471cf313f 100644 --- a/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift +++ b/submodules/TelegramCore/Sources/State/PremiumRequiredToContact.swift @@ -12,6 +12,9 @@ internal func _internal_updateIsPremiumRequiredToContact(account: Account, peerI var inputUsers: [Api.InputUser] = [] var ids: [PeerId] = [] for id in peerIds { + guard id != account.peerId else { + continue + } if let peer = transaction.getPeer(id), let inputUser = apiInputUser(peer) { if peer.isPremium { if let cachedData = transaction.getPeerCachedData(peerId: id) as? CachedUserData { diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index 2867a94d87..425e5617da 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -448,7 +448,7 @@ extension SecretChatServiceAction { self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint)) case let .decryptedMessageActionAbortKey(exchangeId): self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId)) - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -484,7 +484,7 @@ extension SecretChatServiceAction { self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint)) case let .decryptedMessageActionAbortKey(exchangeId): self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId)) - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -520,7 +520,7 @@ extension SecretChatServiceAction { self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint)) case let .decryptedMessageActionAbortKey(exchangeId): self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId)) - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -556,7 +556,7 @@ extension SecretChatServiceAction { self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint)) case let .decryptedMessageActionAbortKey(exchangeId): self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId)) - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -919,7 +919,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 return nil case .decryptedMessageActionAbortKey: return nil - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -1151,7 +1151,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 return nil case .decryptedMessageActionAbortKey: return nil - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -1430,7 +1430,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 return nil case .decryptedMessageActionAbortKey: return nil - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } @@ -1631,7 +1631,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 return nil case .decryptedMessageActionAbortKey: return nil - case .decryptedMessageActionNoop: + case .decryptedMessageActionNoop, .decryptedMessageActionTyping: return nil } } diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 913e49ab09..f18e05b294 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -260,7 +260,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 224 + return 227 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/State/SynchronizeAppLogEventsOperation.swift b/submodules/TelegramCore/Sources/State/SynchronizeAppLogEventsOperation.swift index 2a537b4eca..25aa617acf 100644 --- a/submodules/TelegramCore/Sources/State/SynchronizeAppLogEventsOperation.swift +++ b/submodules/TelegramCore/Sources/State/SynchronizeAppLogEventsOperation.swift @@ -4,7 +4,7 @@ import SwiftSignalKit import MtProtoKit -public func addAppLogEvent(postbox: Postbox, time: Double = Date().timeIntervalSince1970, type: String, peerId: PeerId? = nil, data: JSON = .dictionary([:])) { +func _internal_addAppLogEvent(postbox: Postbox, time: Double = Date().timeIntervalSince1970, type: String, peerId: PeerId? = nil, data: JSON = .dictionary([:])) { let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents let peerId = PeerId(0) let _ = (postbox.transaction { transaction in diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 5207a6c23c..7de0a48a34 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -62,7 +62,7 @@ class UpdateMessageService: NSObject, MTMessageService { } case let .updateShortChatMessage(updateShortChatMessageData): let (flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod) = (updateShortChatMessageData.flags, updateShortChatMessageData.id, updateShortChatMessageData.fromId, updateShortChatMessageData.chatId, updateShortChatMessageData.message, updateShortChatMessageData.pts, updateShortChatMessageData.ptsCount, updateShortChatMessageData.date, updateShortChatMessageData.fwdFrom, updateShortChatMessageData.viaBotId, updateShortChatMessageData.replyTo, updateShortChatMessageData.entities, updateShortChatMessageData.ttlPeriod) - let generatedMessage = Api.Message.message(.init(flags: flags, flags2: 0, id: id, fromId: .peerUser(.init(userId: fromId)), fromBoostsApplied: nil, fromRank: nil, peerId: Api.Peer.peerChat(.init(chatId: chatId)), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil)) + let generatedMessage = Api.Message.message(.init(flags: flags, flags2: 0, id: id, fromId: .peerUser(.init(userId: fromId)), fromBoostsApplied: nil, fromRank: nil, peerId: Api.Peer.peerChat(.init(chatId: chatId)), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, guestchatViaFrom: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil, richMessage: nil)) let update = Api.Update.updateNewMessage(.init(message: generatedMessage, pts: pts, ptsCount: ptsCount)) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -79,7 +79,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(.init(userId: userId)) - let generatedMessage = Api.Message.message(.init(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, fromRank: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil)) + let generatedMessage = Api.Message.message(.init(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, fromRank: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, guestchatViaFrom: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil, richMessage: nil)) let update = Api.Update.updateNewMessage(.init(message: generatedMessage, pts: pts, ptsCount: ptsCount)) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift index 04720616ac..69f123c044 100644 --- a/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift +++ b/submodules/TelegramCore/Sources/State/UserLimitsConfiguration.swift @@ -30,6 +30,7 @@ public struct UserLimitsConfiguration: Equatable { public var maxChannelRecommendationsCount: Int32 public var maxConferenceParticipantCount: Int32 public var maxBotsCreated: Int32 + public var maxOwnedAITextStyles: Int32 public static var defaultValue: UserLimitsConfiguration { return UserLimitsConfiguration( @@ -60,7 +61,8 @@ public struct UserLimitsConfiguration: Equatable { maxGiveawayPeriodSeconds: 86400 * 31, maxChannelRecommendationsCount: 10, maxConferenceParticipantCount: 100, - maxBotsCreated: 20 + maxBotsCreated: 20, + maxOwnedAITextStyles: 5 ) } @@ -92,7 +94,8 @@ public struct UserLimitsConfiguration: Equatable { maxGiveawayPeriodSeconds: Int32, maxChannelRecommendationsCount: Int32, maxConferenceParticipantCount: Int32, - maxBotsCreated: Int32 + maxBotsCreated: Int32, + maxOwnedAITextStyles: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxPinnedSavedChatCount = maxPinnedSavedChatCount @@ -122,6 +125,7 @@ public struct UserLimitsConfiguration: Equatable { self.maxChannelRecommendationsCount = maxChannelRecommendationsCount self.maxConferenceParticipantCount = maxConferenceParticipantCount self.maxBotsCreated = maxBotsCreated + self.maxOwnedAITextStyles = maxOwnedAITextStyles } } @@ -177,5 +181,6 @@ extension UserLimitsConfiguration { self.maxChannelRecommendationsCount = getValue("recommended_channels_limit", orElse: defaultValue.maxChannelRecommendationsCount) self.maxConferenceParticipantCount = getGeneralValue("conference_call_size_limit", orElse: defaultValue.maxConferenceParticipantCount) self.maxBotsCreated = getValue("bots_create_limit", orElse: defaultValue.maxBotsCreated) + self.maxOwnedAITextStyles = getValue("aicompose_tone_saved_limit", orElse: defaultValue.maxBotsCreated) } } diff --git a/submodules/TelegramCore/Sources/Statistics/PollStatistics.swift b/submodules/TelegramCore/Sources/Statistics/PollStatistics.swift new file mode 100644 index 0000000000..ff24a510a2 --- /dev/null +++ b/submodules/TelegramCore/Sources/Statistics/PollStatistics.swift @@ -0,0 +1,193 @@ +import Foundation +import SwiftSignalKit +import Postbox +import TelegramApi +import MtProtoKit + +public struct PollStats: Equatable { + public let votesGraph: StatsGraph + + init(votesGraph: StatsGraph) { + self.votesGraph = votesGraph + } + + public static func == (lhs: PollStats, rhs: PollStats) -> Bool { + if lhs.votesGraph != rhs.votesGraph { + return false + } + return true + } + + public func withUpdatedVotesGraph(_ votesGraph: StatsGraph) -> PollStats { + return PollStats(votesGraph: votesGraph) + } +} + +public struct PollStatsContextState: Equatable { + public var stats: PollStats? +} + +private func requestPollStats(postbox: Postbox, network: Network, messageId: MessageId, dark: Bool = false) -> Signal { + return postbox.transaction { transaction -> (Int32, Peer)? in + if let peer = transaction.getPeer(messageId.peerId){ + if let cachedData = transaction.getPeerCachedData(peerId: messageId.peerId) as? CachedChannelData, cachedData.statsDatacenterId != 0 { + return (cachedData.statsDatacenterId, peer) + } else { + return (Int32(network.datacenterId), peer) + } + } else { + return nil + } + } |> mapToSignal { data -> Signal in + guard let (datacenterId, peer) = data, let inputPeer = apiInputPeer(peer) else { + return .never() + } + + var flags: Int32 = 0 + if dark { + flags |= (1 << 1) + } + + let request = Api.functions.stats.getPollStats(flags: flags, peer: inputPeer, msgId: messageId.id) + let signal: Signal + if network.datacenterId != datacenterId { + signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil) + |> castError(MTRpcError.self) + |> mapToSignal { worker in + return worker.request(request) + } + } else { + signal = network.request(request) + } + + return signal + |> mapToSignal { result -> Signal in + switch result { + case let .pollStats(pollStatsData): + let votesGraph = StatsGraph(apiStatsGraph: pollStatsData.votesGraph) + return .single(PollStats(votesGraph: votesGraph)) + } + } + |> retryRequest + } +} + +private final class PollStatsContextImpl { + private let postbox: Postbox + private let network: Network + private let messageId: MessageId + + private var _state: PollStatsContextState { + didSet { + if self._state != oldValue { + self._statePromise.set(.single(self._state)) + } + } + } + private let _statePromise = Promise() + var state: Signal { + return self._statePromise.get() + } + + private let disposable = MetaDisposable() + private let disposables = DisposableDict() + + init(postbox: Postbox, network: Network, messageId: MessageId) { + assert(Queue.mainQueue().isCurrent()) + + self.postbox = postbox + self.network = network + self.messageId = messageId + self._state = PollStatsContextState(stats: nil) + self._statePromise.set(.single(self._state)) + + self.load() + } + + deinit { + assert(Queue.mainQueue().isCurrent()) + self.disposable.dispose() + self.disposables.dispose() + } + + private func load() { + assert(Queue.mainQueue().isCurrent()) + + self.disposable.set((requestPollStats(postbox: self.postbox, network: self.network, messageId: self.messageId) + |> deliverOnMainQueue).start(next: { [weak self] stats in + if let strongSelf = self { + strongSelf._state = PollStatsContextState(stats: stats) + strongSelf._statePromise.set(.single(strongSelf._state)) + } + })) + } + + func loadVotesGraph() { + assert(Queue.mainQueue().isCurrent()) + + guard let stats = self._state.stats else { + return + } + if case let .OnDemand(token) = stats.votesGraph { + guard !token.isEmpty else { + return + } + self.disposables.set((requestGraph(postbox: self.postbox, network: self.network, peerId: self.messageId.peerId, token: token) + |> deliverOnMainQueue).start(next: { [weak self] graph in + if let strongSelf = self, let graph = graph { + strongSelf._state = PollStatsContextState(stats: strongSelf._state.stats?.withUpdatedVotesGraph(graph)) + strongSelf._statePromise.set(.single(strongSelf._state)) + } + }), forKey: token) + } + } + + func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal { + if let token = graph.token { + return requestGraph(postbox: self.postbox, network: self.network, peerId: self.messageId.peerId, token: token, x: x) + } else { + return .single(nil) + } + } +} + +public final class PollStatsContext { + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.state.start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public init(account: Account, messageId: MessageId) { + self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { + return PollStatsContextImpl(postbox: account.postbox, network: account.network, messageId: messageId) + }) + } + + public func loadVotesGraph() { + self.impl.with { impl in + impl.loadVotesGraph() + } + } + + public func loadDetailedGraph(_ graph: StatsGraph, x: Int64) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.loadDetailedGraph(graph, x: x).start(next: { value in + subscriber.putNext(value) + subscriber.putCompletion() + })) + } + return disposable + } + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/GuestChatMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/GuestChatMessageAttribute.swift new file mode 100644 index 0000000000..cb5b4d2511 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/GuestChatMessageAttribute.swift @@ -0,0 +1,22 @@ +import Foundation +import Postbox + +public class GuestChatMessageAttribute: MessageAttribute { + public let peerId: EnginePeer.Id + + public var associatedPeerIds: [PeerId] { + return [self.peerId] + } + + public init(peerId: EnginePeer.Id) { + self.peerId = peerId + } + + required public init(decoder: PostboxDecoder) { + self.peerId = EnginePeer.Id(decoder.decodeInt64ForKey("p", orElse: 0)) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.peerId.toInt64(), forKey: "p") + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift index e90d25c956..b4057267bb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/ParticipantRankMessageAttribute.swift @@ -4,8 +4,6 @@ import Postbox public class ParticipantRankMessageAttribute: MessageAttribute { public let rank: String - public var associatedMessageIds: [MessageId] = [] - public init(rank: String) { self.rank = rank } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ArchivedStickerPacksInfo.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ArchivedStickerPacksInfo.swift deleted file mode 100644 index 351efd445d..0000000000 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ArchivedStickerPacksInfo.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import Postbox - -public struct ArchivedStickerPacksInfoId { - public let rawValue: MemoryBuffer - public let id: Int32 - - init(_ rawValue: MemoryBuffer) { - self.rawValue = rawValue - assert(rawValue.length == 4) - var idValue: Int32 = 0 - memcpy(&idValue, rawValue.memory, 4) - self.id = idValue - } - - init(_ id: Int32) { - self.id = id - var idValue: Int32 = id - self.rawValue = MemoryBuffer(memory: malloc(4)!, capacity: 4, length: 4, freeWhenDone: true) - memcpy(self.rawValue.memory, &idValue, 4) - } -} - -public final class ArchivedStickerPacksInfo: Codable { - public let count: Int32 - - init(count: Int32) { - self.count = count - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: StringCodingKey.self) - - self.count = try container.decode(Int32.self, forKey: "c") - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StringCodingKey.self) - - try container.encode(self.count, forKey: "c") - } -} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutodownloadSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutodownloadSettings.swift index 0af53f8312..5e4d50bd4d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutodownloadSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_AutodownloadSettings.swift @@ -1,11 +1,5 @@ import Postbox -public enum AutodownloadPreset { - case low - case medium - case high -} - public struct AutodownloadPresetSettings: Codable { public let disabled: Bool public let photoSizeMax: Int64 diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index e04ff49eb1..d20b7f5a00 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -273,6 +273,7 @@ public final class CachedChannelData: CachedPeerData { public let starGiftsCount: Int32? public let sendPaidMessageStars: StarsAmount? public let mainProfileTab: TelegramProfileTab? + public let guardBotId: EnginePeer.Id? public let peerIds: Set public let messageIds: Set @@ -321,6 +322,7 @@ public final class CachedChannelData: CachedPeerData { self.starGiftsCount = nil self.sendPaidMessageStars = nil self.mainProfileTab = nil + self.guardBotId = nil } public init( @@ -361,7 +363,8 @@ public final class CachedChannelData: CachedPeerData { verification: PeerVerification?, starGiftsCount: Int32?, sendPaidMessageStars: StarsAmount?, - mainProfileTab: TelegramProfileTab? + mainProfileTab: TelegramProfileTab?, + guardBotId: EnginePeer.Id? ) { self.isNotAccessible = isNotAccessible self.flags = flags @@ -401,22 +404,27 @@ public final class CachedChannelData: CachedPeerData { self.starGiftsCount = starGiftsCount self.sendPaidMessageStars = sendPaidMessageStars self.mainProfileTab = mainProfileTab - + self.guardBotId = guardBotId + var peerIds = Set() for botInfo in botInfos { peerIds.insert(botInfo.peerId) } - + if case let .known(linkedDiscussionPeerIdValue) = linkedDiscussionPeerId { - if let linkedDiscussionPeerIdValue = linkedDiscussionPeerIdValue { + if let linkedDiscussionPeerIdValue { peerIds.insert(linkedDiscussionPeerIdValue) } } - if let invitedBy = invitedBy { + if let invitedBy { peerIds.insert(invitedBy) } + if let guardBotId { + peerIds.insert(guardBotId) + } + self.peerIds = peerIds var messageIds = Set() @@ -428,157 +436,161 @@ public final class CachedChannelData: CachedPeerData { } public func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedAbout(_ about: String?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedSlowModeTimeout(_ slowModeTimeout: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedSlowModeValidUntilTimestamp(_ slowModeValidUntilTimestamp: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedStatsDatacenterId(_ statsDatacenterId: Int32) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedInvitedOn(_ invitedOn: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedActiveCall(_ activeCall: ActiveCall?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedCallJoinPeerId(_ callJoinPeerId: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: autoremoveTimeout, pendingSuggestions: self.pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedPendingSuggestions(_ pendingSuggestions: [String]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedChatTheme(_ chatTheme: ChatTheme?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedInviteRequestsPending(_ inviteRequestsPending: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedSendAsPeerId(_ sendAsPeerId: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedReactionSettings(_ reactionSettings: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedMembersHidden(_ membersHidden: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedViewForumAsMessages(_ viewForumAsMessages: EnginePeerCachedInfoItem) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedBoostsToUnrestrict(_ boostsToUnrestrict: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedAppliedBoosts(_ appliedBoosts: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedEmojiPack(_ emojiPack: StickerPackCollectionInfo?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedVerification(_ verification: PeerVerification?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedStarGiftsCount(_ starGiftsCount: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedSendPaidMessageStars(_ sendPaidMessageStars: StarsAmount?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: sendPaidMessageStars, mainProfileTab: self.mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: self.guardBotId) } public func withUpdatedMainProfileTab(_ mainProfileTab: TelegramProfileTab?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: mainProfileTab) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: mainProfileTab, guardBotId: self.guardBotId) } + public func withUpdatedGuardBotId(_ guardBotId: EnginePeer.Id?) -> CachedChannelData { + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, chatTheme: self.chatTheme, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, reactionSettings: self.reactionSettings, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack, verification: self.verification, starGiftsCount: self.starGiftsCount, sendPaidMessageStars: self.sendPaidMessageStars, mainProfileTab: self.mainProfileTab, guardBotId: guardBotId) + } + public init(decoder: PostboxDecoder) { self.isNotAccessible = decoder.decodeInt32ForKey("isNotAccessible", orElse: 0) != 0 self.flags = CachedChannelFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)) @@ -589,7 +601,7 @@ public final class CachedChannelData: CachedPeerData { var peerIds = Set() if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { @@ -722,6 +734,11 @@ public final class CachedChannelData: CachedPeerData { self.mainProfileTab = decoder.decodeCodable(TelegramProfileTab.self, forKey: "mainProfileTab") + self.guardBotId = decoder.decodeOptionalInt64ForKey("guardBotId").flatMap(EnginePeer.Id.init) + if let guardBotId = self.guardBotId { + peerIds.insert(guardBotId) + } + self.peerIds = peerIds var messageIds = Set() @@ -929,6 +946,12 @@ public final class CachedChannelData: CachedPeerData { } else { encoder.encodeNil(forKey: "mainProfileTab") } + + if let guardBotId = self.guardBotId { + encoder.encodeInt64(guardBotId.toInt64(), forKey: "guardBotId") + } else { + encoder.encodeNil(forKey: "guardBotId") + } } public func isEqual(to: CachedPeerData) -> Bool { @@ -1075,9 +1098,14 @@ public final class CachedChannelData: CachedPeerData { if other.verification != self.verification { return false } + if other.mainProfileTab != self.mainProfileTab { return false } + + if other.guardBotId != self.guardBotId { + return false + } return true } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift index a2dfa1b17b..65104f04fc 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift @@ -232,7 +232,7 @@ public final class CachedGroupData: CachedPeerData { self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i") self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo] if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index fa9819893f..8a6008028f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -1418,7 +1418,7 @@ public final class CachedUserData: CachedPeerData { self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift index a721277bd6..3ad8b904fd 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_InstantPage.swift @@ -32,6 +32,9 @@ private enum InstantPageBlockType: Int32 { case details = 25 case relatedArticles = 26 case map = 27 + case heading = 28 + case formula = 29 + case thinking = 30 } private func decodeListItems(_ decoder: PostboxDecoder) -> [InstantPageListItem] { @@ -39,7 +42,7 @@ private func decodeListItems(_ decoder: PostboxDecoder) -> [InstantPageListItem] if !legacyItems.isEmpty { var items: [InstantPageListItem] = [] for item in legacyItems { - items.append(.text(item, nil)) + items.append(.text(item, nil, nil)) } return items } @@ -60,13 +63,15 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { case authorDate(author: RichText, date: Int32) case header(RichText) case subheader(RichText) + case heading(text: RichText, level: Int32) + case formula(latex: String) case paragraph(RichText) - case preformatted(RichText) + case preformatted(text: RichText, language: String?) case footer(RichText) case divider case anchor(String) case list(items: [InstantPageListItem], ordered: Bool) - case blockQuote(text: RichText, caption: RichText) + case blockQuote(blocks: [InstantPageBlock], caption: RichText) case pullQuote(text: RichText, caption: RichText) case image(id: MediaId, caption: InstantPageCaption, url: String?, webpageId: MediaId?) case video(id: MediaId, caption: InstantPageCaption, autoplay: Bool, loop: Bool) @@ -78,6 +83,7 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { case slideshow(items: [InstantPageBlock], caption: InstantPageCaption) case channelBanner(TelegramChannel?) case kicker(RichText) + case thinking(RichText) case table(title: RichText, rows: [InstantPageTableRow], bordered: Bool, striped: Bool) case details(title: RichText, blocks: [InstantPageBlock], expanded: Bool) case relatedArticles(title: RichText, articles: [InstantPageRelatedArticle]) @@ -97,10 +103,17 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { self = .header(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.subheader.rawValue: self = .subheader(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case InstantPageBlockType.heading.rawValue: + self = .heading(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, level: decoder.decodeInt32ForKey("l", orElse: 3)) + case InstantPageBlockType.formula.rawValue: + self = .formula(latex: decoder.decodeStringForKey("l", orElse: "")) case InstantPageBlockType.paragraph.rawValue: self = .paragraph(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.preformatted.rawValue: - self = .preformatted(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + self = .preformatted( + text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, + language: decoder.decodeOptionalStringForKey("l") + ) case InstantPageBlockType.footer.rawValue: self = .footer(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.divider.rawValue: @@ -110,7 +123,13 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { case InstantPageBlockType.list.rawValue: self = .list(items: decodeListItems(decoder), ordered: decoder.decodeOptionalInt32ForKey("o") != 0) case InstantPageBlockType.blockQuote.rawValue: - self = .blockQuote(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText) + let caption = decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText + if let legacyText = decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as? RichText { + self = .blockQuote(blocks: [.paragraph(legacyText)], caption: caption) + } else { + let blocks: [InstantPageBlock] = decoder.decodeObjectArrayWithDecoderForKey("b") + self = .blockQuote(blocks: blocks, caption: caption) + } case InstantPageBlockType.pullQuote.rawValue: self = .pullQuote(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.image.rawValue: @@ -151,6 +170,8 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { self = .audio(id: MediaId(namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0)), caption: decodeCaption(decoder)) case InstantPageBlockType.kicker.rawValue: self = .kicker(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case InstantPageBlockType.thinking.rawValue: + self = .thinking(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) case InstantPageBlockType.table.rawValue: self = .table(title: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, rows: decoder.decodeObjectArrayWithDecoderForKey("r"), bordered: decoder.decodeInt32ForKey("b", orElse: 0) != 0, striped: decoder.decodeInt32ForKey("s", orElse: 0) != 0) case InstantPageBlockType.details.rawValue: @@ -184,12 +205,24 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { case let .subheader(text): encoder.encodeInt32(InstantPageBlockType.subheader.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") + case let .heading(text, level): + encoder.encodeInt32(InstantPageBlockType.heading.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + encoder.encodeInt32(level, forKey: "l") + case let .formula(latex): + encoder.encodeInt32(InstantPageBlockType.formula.rawValue, forKey: "r") + encoder.encodeString(latex, forKey: "l") case let .paragraph(text): encoder.encodeInt32(InstantPageBlockType.paragraph.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") - case let .preformatted(text): + case let .preformatted(text, language): encoder.encodeInt32(InstantPageBlockType.preformatted.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") + if let language { + encoder.encodeString(language, forKey: "l") + } else { + encoder.encodeNil(forKey: "l") + } case let .footer(text): encoder.encodeInt32(InstantPageBlockType.footer.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") @@ -202,9 +235,9 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { encoder.encodeInt32(InstantPageBlockType.list.rawValue, forKey: "r") encoder.encodeObjectArray(items, forKey: "ml") encoder.encodeInt32(ordered ? 1 : 0, forKey: "o") - case let .blockQuote(text, caption): + case let .blockQuote(blocks, caption): encoder.encodeInt32(InstantPageBlockType.blockQuote.rawValue, forKey: "r") - encoder.encodeObject(text, forKey: "t") + encoder.encodeObjectArray(blocks, forKey: "b") encoder.encodeObject(caption, forKey: "c") case let .pullQuote(text, caption): encoder.encodeInt32(InstantPageBlockType.pullQuote.rawValue, forKey: "r") @@ -310,6 +343,9 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { case let .kicker(text): encoder.encodeInt32(InstantPageBlockType.kicker.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") + case let .thinking(text): + encoder.encodeInt32(InstantPageBlockType.thinking.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") case let .table(title, rows, bordered, striped): encoder.encodeInt32(InstantPageBlockType.table.rawValue, forKey: "r") encoder.encodeObject(title, forKey: "t") @@ -374,14 +410,26 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { } else { return false } + case let .heading(lhsText, lhsLevel): + if case let .heading(rhsText, rhsLevel) = rhs, lhsText == rhsText, lhsLevel == rhsLevel { + return true + } else { + return false + } + case let .formula(lhsLatex): + if case let .formula(rhsLatex) = rhs, lhsLatex == rhsLatex { + return true + } else { + return false + } case let .paragraph(text): if case .paragraph(text) = rhs { return true } else { return false } - case let .preformatted(text): - if case .preformatted(text) = rhs { + case let .preformatted(lhsText, lhsLanguage): + if case let .preformatted(rhsText, rhsLanguage) = rhs, lhsText == rhsText, lhsLanguage == rhsLanguage { return true } else { return false @@ -410,8 +458,8 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { } else { return false } - case let .blockQuote(text, caption): - if case .blockQuote(text, caption) = rhs { + case let .blockQuote(lhsBlocks, lhsCaption): + if case let .blockQuote(rhsBlocks, rhsCaption) = rhs, lhsBlocks == rhsBlocks, lhsCaption == rhsCaption { return true } else { return false @@ -489,6 +537,12 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { } else { return false } + case let .thinking(text): + if case .thinking(text) = rhs { + return true + } else { + return false + } case let .table(lhsTitle, lhsRows, lhsBordered, lhsStriped): if case let .table(rhsTitle, rhsRows, rhsBordered, rhsStriped) = rhs, lhsTitle == rhsTitle, lhsRows == rhsRows, lhsBordered == rhsBordered, lhsStriped == rhsStriped { return true @@ -545,6 +599,16 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { throw FlatBuffersError.missingRequiredField() } self = .subheader(try RichText(flatBuffersObject: value.text)) + case .instantpageblockHeading: + guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Heading.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .heading(text: try RichText(flatBuffersObject: value.text), level: value.level) + case .instantpageblockFormula: + guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Formula.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .formula(latex: value.latex) case .instantpageblockParagraph: guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Paragraph.self) else { throw FlatBuffersError.missingRequiredField() @@ -554,7 +618,7 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Preformatted.self) else { throw FlatBuffersError.missingRequiredField() } - self = .preformatted(try RichText(flatBuffersObject: value.text)) + self = .preformatted(text: try RichText(flatBuffersObject: value.text), language: value.language) case .instantpageblockFooter: guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Footer.self) else { throw FlatBuffersError.missingRequiredField() @@ -576,7 +640,15 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_BlockQuote.self) else { throw FlatBuffersError.missingRequiredField() } - self = .blockQuote(text: try RichText(flatBuffersObject: value.text), caption: try RichText(flatBuffersObject: value.caption)) + let caption = try RichText(flatBuffersObject: value.caption) + if value.blocksCount > 0 { + let blocks = try (0 ..< value.blocksCount).map { try InstantPageBlock(flatBuffersObject: value.blocks(at: $0)!) } + self = .blockQuote(blocks: blocks, caption: caption) + } else if let legacyText = value.text { + self = .blockQuote(blocks: [.paragraph(try RichText(flatBuffersObject: legacyText))], caption: caption) + } else { + self = .blockQuote(blocks: [], caption: caption) + } case .instantpageblockPullquote: guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_PullQuote.self) else { throw FlatBuffersError.missingRequiredField() @@ -633,6 +705,11 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { throw FlatBuffersError.missingRequiredField() } self = .kicker(try RichText(flatBuffersObject: value.text)) + case .instantpageblockThinking: + guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Thinking.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .thinking(try RichText(flatBuffersObject: value.text)) case .instantpageblockTable: guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Table.self) else { throw FlatBuffersError.missingRequiredField() @@ -698,17 +775,34 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { let start = TelegramCore_InstantPageBlock_Subheader.startInstantPageBlock_Subheader(&builder) TelegramCore_InstantPageBlock_Subheader.add(text: textOffset, &builder) offset = TelegramCore_InstantPageBlock_Subheader.endInstantPageBlock_Subheader(&builder, start: start) + case let .heading(text, level): + valueType = .instantpageblockHeading + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_InstantPageBlock_Heading.startInstantPageBlock_Heading(&builder) + TelegramCore_InstantPageBlock_Heading.add(text: textOffset, &builder) + TelegramCore_InstantPageBlock_Heading.add(level: level, &builder) + offset = TelegramCore_InstantPageBlock_Heading.endInstantPageBlock_Heading(&builder, start: start) + case let .formula(latex): + valueType = .instantpageblockFormula + let latexOffset = builder.create(string: latex) + let start = TelegramCore_InstantPageBlock_Formula.startInstantPageBlock_Formula(&builder) + TelegramCore_InstantPageBlock_Formula.add(latex: latexOffset, &builder) + offset = TelegramCore_InstantPageBlock_Formula.endInstantPageBlock_Formula(&builder, start: start) case let .paragraph(text): valueType = .instantpageblockParagraph let textOffset = text.encodeToFlatBuffers(builder: &builder) let start = TelegramCore_InstantPageBlock_Paragraph.startInstantPageBlock_Paragraph(&builder) TelegramCore_InstantPageBlock_Paragraph.add(text: textOffset, &builder) offset = TelegramCore_InstantPageBlock_Paragraph.endInstantPageBlock_Paragraph(&builder, start: start) - case let .preformatted(text): + case let .preformatted(text, language): valueType = .instantpageblockPreformatted let textOffset = text.encodeToFlatBuffers(builder: &builder) + let languageOffset = language.flatMap { builder.create(string: $0) } let start = TelegramCore_InstantPageBlock_Preformatted.startInstantPageBlock_Preformatted(&builder) TelegramCore_InstantPageBlock_Preformatted.add(text: textOffset, &builder) + if let languageOffset { + TelegramCore_InstantPageBlock_Preformatted.add(language: languageOffset, &builder) + } offset = TelegramCore_InstantPageBlock_Preformatted.endInstantPageBlock_Preformatted(&builder, start: start) case let .footer(text): valueType = .instantpageblockFooter @@ -734,12 +828,13 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { TelegramCore_InstantPageBlock_List.addVectorOf(items: itemsOffset, &builder) TelegramCore_InstantPageBlock_List.add(ordered: ordered, &builder) offset = TelegramCore_InstantPageBlock_List.endInstantPageBlock_List(&builder, start: start) - case let .blockQuote(text, caption): + case let .blockQuote(blocks, caption): valueType = .instantpageblockBlockquote - let textOffset = text.encodeToFlatBuffers(builder: &builder) + let blocksOffsets = blocks.map { $0.encodeToFlatBuffers(builder: &builder) } + let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count) let captionOffset = caption.encodeToFlatBuffers(builder: &builder) let start = TelegramCore_InstantPageBlock_BlockQuote.startInstantPageBlock_BlockQuote(&builder) - TelegramCore_InstantPageBlock_BlockQuote.add(text: textOffset, &builder) + TelegramCore_InstantPageBlock_BlockQuote.addVectorOf(blocks: blocksOffset, &builder) TelegramCore_InstantPageBlock_BlockQuote.add(caption: captionOffset, &builder) offset = TelegramCore_InstantPageBlock_BlockQuote.endInstantPageBlock_BlockQuote(&builder, start: start) case let .pullQuote(text, caption): @@ -860,6 +955,12 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable { let start = TelegramCore_InstantPageBlock_Kicker.startInstantPageBlock_Kicker(&builder) TelegramCore_InstantPageBlock_Kicker.add(text: textOffset, &builder) offset = TelegramCore_InstantPageBlock_Kicker.endInstantPageBlock_Kicker(&builder, start: start) + case let .thinking(text): + valueType = .instantpageblockThinking + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_InstantPageBlock_Thinking.startInstantPageBlock_Thinking(&builder) + TelegramCore_InstantPageBlock_Thinking.add(text: textOffset, &builder) + offset = TelegramCore_InstantPageBlock_Thinking.endInstantPageBlock_Thinking(&builder, start: start) case let .table(title, rows, bordered, striped): valueType = .instantpageblockTable let titleOffset = title.encodeToFlatBuffers(builder: &builder) @@ -959,15 +1060,15 @@ private enum InstantPageListItemType: Int32 { public indirect enum InstantPageListItem: PostboxCoding, Equatable { case unknown - case text(RichText, String?) - case blocks([InstantPageBlock], String?) + case text(RichText, String?, Bool?) + case blocks([InstantPageBlock], String?, Bool?) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { case InstantPageListItemType.text.rawValue: - self = .text(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, decoder.decodeOptionalStringForKey("n")) + self = .text(decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, decoder.decodeOptionalStringForKey("n"), InstantPageListItem.checkedFromTriState(decoder.decodeInt32ForKey("ck", orElse: 0))) case InstantPageListItemType.blocks.rawValue: - self = .blocks(decoder.decodeObjectArrayWithDecoderForKey("b"), decoder.decodeOptionalStringForKey("n")) + self = .blocks(decoder.decodeObjectArrayWithDecoderForKey("b"), decoder.decodeOptionalStringForKey("n"), InstantPageListItem.checkedFromTriState(decoder.decodeInt32ForKey("ck", orElse: 0))) default: self = .unknown } @@ -975,7 +1076,7 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { public func encode(_ encoder: PostboxEncoder) { switch self { - case let .text(text, num): + case let .text(text, num, checked): encoder.encodeInt32(InstantPageListItemType.text.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") if let num = num { @@ -983,7 +1084,12 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "n") } - case let .blocks(blocks, num): + if let triState = InstantPageListItem.triState(fromChecked: checked) { + encoder.encodeInt32(triState, forKey: "ck") + } else { + encoder.encodeNil(forKey: "ck") + } + case let .blocks(blocks, num, checked): encoder.encodeInt32(InstantPageListItemType.blocks.rawValue, forKey: "r") encoder.encodeObjectArray(blocks, forKey: "b") if let num = num { @@ -991,11 +1097,33 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "n") } + if let triState = InstantPageListItem.triState(fromChecked: checked) { + encoder.encodeInt32(triState, forKey: "ck") + } else { + encoder.encodeNil(forKey: "ck") + } default: break } } + static func checkedFromTriState(_ value: Int32) -> Bool? { + switch value { + case 1: return false + case 2: return true + default: return nil + } + } + + /// Returns the persisted tri-state (1 = unchecked, 2 = checked) or nil when not a checkbox item. + static func triState(fromChecked checked: Bool?) -> Int32? { + switch checked { + case .some(false): return 1 + case .some(true): return 2 + case .none: return nil + } + } + public static func ==(lhs: InstantPageListItem, rhs: InstantPageListItem) -> Bool { switch lhs { case .unknown: @@ -1004,14 +1132,14 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { } else { return false } - case let .text(lhsText, lhsNum): - if case let .text(rhsText, rhsNum) = rhs, lhsText == rhsText, lhsNum == rhsNum { + case let .text(lhsText, lhsNum, lhsChecked): + if case let .text(rhsText, rhsNum, rhsChecked) = rhs, lhsText == rhsText, lhsNum == rhsNum, lhsChecked == rhsChecked { return true } else { return false } - case let .blocks(lhsBlocks, lhsNum): - if case let .blocks(rhsBlocks, rhsNum) = rhs, lhsBlocks == rhsBlocks, lhsNum == rhsNum { + case let .blocks(lhsBlocks, lhsNum, lhsChecked): + if case let .blocks(rhsBlocks, rhsNum, rhsChecked) = rhs, lhsBlocks == rhsBlocks, lhsNum == rhsNum, lhsChecked == rhsChecked { return true } else { return false @@ -1025,7 +1153,7 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { guard let textValue = flatBuffersObject.value(type: TelegramCore_InstantPageListItem_Text.self) else { throw FlatBuffersError.missingRequiredField() } - self = .text(try RichText(flatBuffersObject: textValue.text), textValue.number) + self = .text(try RichText(flatBuffersObject: textValue.text), textValue.number, InstantPageListItem.checkedFromTriState(textValue.checkState)) case .instantpagelistitemBlocks: guard let blocksValue = flatBuffersObject.value(type: TelegramCore_InstantPageListItem_Blocks.self) else { @@ -1034,7 +1162,7 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { let blocks = try (0 ..< blocksValue.blocksCount).map { i in return try InstantPageBlock(flatBuffersObject: blocksValue.blocks(at: i)!) } - self = .blocks(blocks, blocksValue.number) + self = .blocks(blocks, blocksValue.number, InstantPageListItem.checkedFromTriState(blocksValue.checkState)) case .instantpagelistitemUnknown: self = .unknown case .none_: @@ -1047,28 +1175,34 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable { let offset: Offset switch self { - case let .text(text, number): + case let .text(text, number, checked): valueType = .instantpagelistitemText let textOffset = text.encodeToFlatBuffers(builder: &builder) let numberOffset = number.map { builder.create(string: $0) } ?? Offset() - + let start = TelegramCore_InstantPageListItem_Text.startInstantPageListItem_Text(&builder) TelegramCore_InstantPageListItem_Text.add(text: textOffset, &builder) if let _ = number { TelegramCore_InstantPageListItem_Text.add(number: numberOffset, &builder) } + if let triState = InstantPageListItem.triState(fromChecked: checked) { + TelegramCore_InstantPageListItem_Text.add(checkState: triState, &builder) + } offset = TelegramCore_InstantPageListItem_Text.endInstantPageListItem_Text(&builder, start: start) - case let .blocks(blocks, number): + case let .blocks(blocks, number, checked): valueType = .instantpagelistitemBlocks let blocksOffsets = blocks.map { $0.encodeToFlatBuffers(builder: &builder) } let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count) let numberOffset = number.map { builder.create(string: $0) } ?? Offset() - + let start = TelegramCore_InstantPageListItem_Blocks.startInstantPageListItem_Blocks(&builder) TelegramCore_InstantPageListItem_Blocks.addVectorOf(blocks: blocksOffset, &builder) if let _ = number { TelegramCore_InstantPageListItem_Blocks.add(number: numberOffset, &builder) } + if let triState = InstantPageListItem.triState(fromChecked: checked) { + TelegramCore_InstantPageListItem_Blocks.add(checkState: triState, &builder) + } offset = TelegramCore_InstantPageListItem_Blocks.endInstantPageListItem_Blocks(&builder, start: start) case .unknown: valueType = .instantpagelistitemUnknown @@ -1518,6 +1652,80 @@ public final class InstantPage: PostboxCoding, Equatable { } } +private extension InstantPageBlock { + func allMedia(mediaDict: [MediaId: Media]) -> [Media] { + switch self { + case let .audio(id, _): + if let file = mediaDict[id] { + return [file] + } else { + return [] + } + case let .collage(items, _): + var result: [Media] = [] + for item in items { + result.append(contentsOf: item.allMedia(mediaDict: mediaDict)) + } + return result + case let .cover(block): + return block.allMedia(mediaDict: mediaDict) + case let .details(_, blocks, _): + var result: [Media] = [] + for item in blocks { + result.append(contentsOf: item.allMedia(mediaDict: mediaDict)) + } + return result + case let .image(id, _, _, _): + if let image = mediaDict[id] { + return [image] + } else { + return [] + } + case let .list(items, _): + for item in items { + switch item { + case let .blocks(blocks, _, _): + var result: [Media] = [] + for block in blocks { + result.append(contentsOf: block.allMedia(mediaDict: mediaDict)) + } + return result + case .text, .unknown: + break + } + } + return [] + case let .slideshow(items, _): + var result: [Media] = [] + for item in items { + result.append(contentsOf: item.allMedia(mediaDict: mediaDict)) + } + return result + case let .video(id, _, _, _): + if let video = mediaDict[id] { + return [video] + } else { + return [] + } + default: + return [] + } + } +} + +public extension InstantPage { + func allMedia() -> [Media] { + if self.media.isEmpty { + return [] + } + var result: [Media] = [] + for block in self.blocks { + result.append(contentsOf: block.allMedia(mediaDict: self.media)) + } + return result + } +} + public extension InstantPage { struct Accessor: Equatable { let _wrappedInstantPage: InstantPage? diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_LocalMediaPlaybackInfoAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_LocalMediaPlaybackInfoAttribute.swift deleted file mode 100644 index ecc83d4a59..0000000000 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_LocalMediaPlaybackInfoAttribute.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import Postbox - -public class LocalMediaPlaybackInfoAttribute: MessageAttribute { - public let data: Data - - public init(data: Data) { - self.data = data - } - - required public init(decoder: PostboxDecoder) { - self.data = decoder.decodeDataForKey("d") ?? Data() - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeData(self.data, forKey: "d") - } -} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index d066310c99..ff5665e5f3 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -97,6 +97,7 @@ public struct Namespaces { public static let CloudDisabledChannelStatusEmoji: Int32 = 28 public static let CloudDefaultTagReactions: Int32 = 29 public static let CloudUniqueStarGifts: Int32 = 30 + public static let NewBotConnectionReviews: Int32 = 31 } public struct CachedItemCollection { @@ -149,6 +150,7 @@ public struct Namespaces { public static let cachedChatThemes: Int8 = 50 public static let cachedLiveStorySendAsPeers: Int8 = 51 public static let cachedGiftUpgradesAttributes: Int8 = 52 + public static let cachedCloudAITextStyles: Int8 = 53 } public struct UnorderedItemList { @@ -327,6 +329,7 @@ private enum PreferencesKeyValues: Int32 { case globalPostSearchState = 46 case savedMusicIds = 47 case emojiGameInfo = 48 + case webBrowserSettings = 49 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -426,15 +429,15 @@ public struct PreferencesKeys { return key }() - public static let chatListFilters: ValueBoxKey = { + public static let webBrowserSettings: ValueBoxKey = { let key = ValueBoxKey(length: 4) - key.setInt32(0, value: PreferencesKeyValues.chatListFilters.rawValue) + key.setInt32(0, value: PreferencesKeyValues.webBrowserSettings.rawValue) return key }() - public static let peersNearby: ValueBoxKey = { + public static let chatListFilters: ValueBoxKey = { let key = ValueBoxKey(length: 4) - key.setInt32(0, value: PreferencesKeyValues.peersNearby.rawValue) + key.setInt32(0, value: PreferencesKeyValues.chatListFilters.rawValue) return key }() diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewBotConnectionReview.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewBotConnectionReview.swift new file mode 100644 index 0000000000..201059cc3b --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewBotConnectionReview.swift @@ -0,0 +1,85 @@ +import Foundation +import Postbox +import SwiftSignalKit + +public final class NewBotConnectionReview: Codable, Equatable { + struct Id { + var rawValue: MemoryBuffer + + init(botId: PeerId) { + let buffer = WriteBuffer() + + var rawBotId = botId.toInt64() + buffer.write(&rawBotId, length: 8) + + self.rawValue = buffer.makeReadBufferAndReset() + } + } + + public let botId: PeerId + public let device: String? + public let location: String? + public let timestamp: Int32? + + public init(botId: PeerId, device: String?, location: String?, timestamp: Int32?) { + self.botId = botId + self.device = device + self.location = location + self.timestamp = timestamp + } + + public static func ==(lhs: NewBotConnectionReview, rhs: NewBotConnectionReview) -> Bool { + if lhs.botId != rhs.botId { + return false + } + if lhs.device != rhs.device { + return false + } + if lhs.location != rhs.location { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + return true + } +} + +public func newBotConnectionReviews(postbox: Postbox) -> Signal<[NewBotConnectionReview], NoError> { + let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.NewBotConnectionReviews) + return postbox.combinedView(keys: [viewKey]) + |> mapToSignal { views -> Signal<[NewBotConnectionReview], NoError> in + guard let view = views.views[viewKey] as? OrderedItemListView else { + return .single([]) + } + + var result: [NewBotConnectionReview] = [] + for item in view.items { + guard let item = item.contents.get(NewBotConnectionReview.self) else { + continue + } + result.append(item) + } + + return .single(result) + } +} + +public func addNewBotConnectionReview(postbox: Postbox, item: NewBotConnectionReview) -> Signal { + return postbox.transaction { transaction -> Void in + guard let entry = CodableEntry(item) else { + return + } + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewBotConnectionReviews, item: OrderedItemListEntry(id: NewBotConnectionReview.Id(botId: item.botId).rawValue, contents: entry), removeTailIfCountExceeds: 200) + } + |> ignoreValues +} + +public func removeNewBotConnectionReviews(postbox: Postbox, botIds: [PeerId]) -> Signal { + return postbox.transaction { transaction -> Void in + for botId in botIds { + transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewBotConnectionReviews, itemId: NewBotConnectionReview.Id(botId: botId).rawValue) + } + } + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift index 71e3094910..639434860b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_NewSessionReview.swift @@ -114,23 +114,6 @@ public func newSessionReviews(postbox: Postbox) -> Signal<[NewSessionReview], No } } -public func addNewSessionReview(postbox: Postbox, item: NewSessionReview) -> Signal { - return postbox.transaction { transaction -> Void in - guard let entry = CodableEntry(item) else { - return - } - transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.NewSessionReviews, item: OrderedItemListEntry(id: NewSessionReview.Id(id: item.id).rawValue, contents: entry), removeTailIfCountExceeds: 200) - } - |> ignoreValues -} - -public func clearNewSessionReviews(postbox: Postbox) -> Signal { - return postbox.transaction { transaction -> Void in - transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.NewSessionReviews, items: []) - } - |> ignoreValues -} - public func removeNewSessionReviews(postbox: Postbox, ids: [Int64]) -> Signal { return postbox.transaction { transaction -> Void in for id in ids { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift index 1c2f485dff..2e6e74475c 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift @@ -33,7 +33,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { } public var flags: PeerStatusSettings.Flags - public var geoDistance: Int32? public var requestChatTitle: String? public var requestChatDate: Int32? public var requestChatIsChannel: Bool? @@ -46,7 +45,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public init() { self.flags = PeerStatusSettings.Flags() - self.geoDistance = nil self.requestChatTitle = nil self.requestChatDate = nil self.managingBot = nil @@ -59,7 +57,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public init( flags: PeerStatusSettings.Flags, - geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil, @@ -71,7 +68,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { photoChangeDate: Int32? = nil ) { self.flags = flags - self.geoDistance = geoDistance self.requestChatTitle = requestChatTitle self.requestChatDate = requestChatDate self.requestChatIsChannel = requestChatIsChannel @@ -85,7 +81,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public init(decoder: PostboxDecoder) { self.flags = Flags(rawValue: decoder.decodeInt32ForKey("flags", orElse: 0)) - self.geoDistance = decoder.decodeOptionalInt32ForKey("geoDistance") self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle") self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate") self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel") @@ -99,11 +94,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.flags.rawValue, forKey: "flags") - if let geoDistance = self.geoDistance { - encoder.encodeInt32(geoDistance, forKey: "geoDistance") - } else { - encoder.encodeNil(forKey: "geoDistance") - } if let requestChatTitle = self.requestChatTitle { encoder.encodeString(requestChatTitle, forKey: "requestChatTitle") } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentDownloadItem.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentDownloadItem.swift index 1994313d47..dd306e9be9 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentDownloadItem.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RecentDownloadItem.swift @@ -136,7 +136,7 @@ public func recentDownloadItems(postbox: Postbox) -> Signal<[RenderedRecentDownl } var size: Int64? - for media in message.media { + for media in message.effectiveMedia { if let result = findMediaResourceById(media: media, resourceId: MediaResourceId(item.resourceId)) { size = result.size break diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift index 69311fda00..d55bba650a 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichText.swift @@ -19,6 +19,19 @@ private enum RichTextTypes: Int32 { case phone = 13 case image = 14 case anchor = 15 + case formula = 16 + case textCustomEmoji = 17 + case textAutoEmail = 18 + case textAutoPhone = 19 + case textAutoUrl = 20 + case textBankCard = 21 + case textBotCommand = 22 + case textCashtag = 23 + case textHashtag = 24 + case textMention = 25 + case textMentionName = 26 + case textSpoiler = 27 + case textDate = 28 } public indirect enum RichText: PostboxCoding, Equatable { @@ -38,7 +51,20 @@ public indirect enum RichText: PostboxCoding, Equatable { case phone(text: RichText, phone: String) case image(id: MediaId, dimensions: PixelDimensions) case anchor(text: RichText, name: String) - + case formula(latex: String) + case textCustomEmoji(fileId: Int64, alt: String) + case textAutoEmail(text: RichText) + case textAutoPhone(text: RichText) + case textAutoUrl(text: RichText) + case textBankCard(text: RichText) + case textBotCommand(text: RichText) + case textCashtag(text: RichText) + case textHashtag(text: RichText) + case textMention(text: RichText) + case textMentionName(text: RichText, peerId: Int64) + case textSpoiler(text: RichText) + case textDate(text: RichText, date: Int32, format: MessageTextEntityType.DateTimeFormat?) + public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { case RichTextTypes.empty.rawValue: @@ -79,6 +105,32 @@ public indirect enum RichText: PostboxCoding, Equatable { self = .image(id: MediaId(namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0)), dimensions: PixelDimensions(width: decoder.decodeInt32ForKey("sw", orElse: 0), height: decoder.decodeInt32ForKey("sh", orElse: 0))) case RichTextTypes.anchor.rawValue: self = .anchor(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, name: decoder.decodeStringForKey("n", orElse: "")) + case RichTextTypes.formula.rawValue: + self = .formula(latex: decoder.decodeStringForKey("l", orElse: "")) + case RichTextTypes.textCustomEmoji.rawValue: + self = .textCustomEmoji(fileId: decoder.decodeInt64ForKey("ce.f", orElse: 0), alt: decoder.decodeStringForKey("ce.a", orElse: "")) + case RichTextTypes.textAutoEmail.rawValue: + self = .textAutoEmail(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textAutoPhone.rawValue: + self = .textAutoPhone(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textAutoUrl.rawValue: + self = .textAutoUrl(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textBankCard.rawValue: + self = .textBankCard(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textBotCommand.rawValue: + self = .textBotCommand(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textCashtag.rawValue: + self = .textCashtag(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textHashtag.rawValue: + self = .textHashtag(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textMention.rawValue: + self = .textMention(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textMentionName.rawValue: + self = .textMentionName(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, peerId: decoder.decodeInt64ForKey("mn.p", orElse: 0)) + case RichTextTypes.textSpoiler.rawValue: + self = .textSpoiler(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText) + case RichTextTypes.textDate.rawValue: + self = .textDate(text: decoder.decodeObjectForKey("t", decoder: { RichText(decoder: $0) }) as! RichText, date: decoder.decodeInt32ForKey("dt", orElse: 0), format: decoder.decodeOptionalInt32ForKey("df").flatMap { MessageTextEntityType.DateTimeFormat(rawValue: $0) }) default: self = .empty } @@ -147,9 +199,56 @@ public indirect enum RichText: PostboxCoding, Equatable { encoder.encodeInt32(RichTextTypes.anchor.rawValue, forKey: "r") encoder.encodeObject(text, forKey: "t") encoder.encodeString(name, forKey: "n") + case let .formula(latex): + encoder.encodeInt32(RichTextTypes.formula.rawValue, forKey: "r") + encoder.encodeString(latex, forKey: "l") + case let .textCustomEmoji(fileId, alt): + encoder.encodeInt32(RichTextTypes.textCustomEmoji.rawValue, forKey: "r") + encoder.encodeInt64(fileId, forKey: "ce.f") + encoder.encodeString(alt, forKey: "ce.a") + case let .textAutoEmail(text): + encoder.encodeInt32(RichTextTypes.textAutoEmail.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textAutoPhone(text): + encoder.encodeInt32(RichTextTypes.textAutoPhone.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textAutoUrl(text): + encoder.encodeInt32(RichTextTypes.textAutoUrl.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textBankCard(text): + encoder.encodeInt32(RichTextTypes.textBankCard.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textBotCommand(text): + encoder.encodeInt32(RichTextTypes.textBotCommand.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textCashtag(text): + encoder.encodeInt32(RichTextTypes.textCashtag.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textHashtag(text): + encoder.encodeInt32(RichTextTypes.textHashtag.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textMention(text): + encoder.encodeInt32(RichTextTypes.textMention.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textMentionName(text, peerId): + encoder.encodeInt32(RichTextTypes.textMentionName.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + encoder.encodeInt64(peerId, forKey: "mn.p") + case let .textSpoiler(text): + encoder.encodeInt32(RichTextTypes.textSpoiler.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + case let .textDate(text, date, format): + encoder.encodeInt32(RichTextTypes.textDate.rawValue, forKey: "r") + encoder.encodeObject(text, forKey: "t") + encoder.encodeInt32(date, forKey: "dt") + if let format { + encoder.encodeInt32(format.rawValue, forKey: "df") + } else { + encoder.encodeNil(forKey: "df") + } } } - + public static func ==(lhs: RichText, rhs: RichText) -> Bool { switch lhs { case .empty: @@ -248,6 +347,40 @@ public indirect enum RichText: PostboxCoding, Equatable { } else { return false } + case let .formula(lhsLatex): + if case let .formula(rhsLatex) = rhs, lhsLatex == rhsLatex { + return true + } else { + return false + } + case let .textCustomEmoji(lhsFileId, lhsAlt): + if case let .textCustomEmoji(rhsFileId, rhsAlt) = rhs, lhsFileId == rhsFileId, lhsAlt == rhsAlt { + return true + } else { + return false + } + case let .textAutoEmail(text): + if case .textAutoEmail(text) = rhs { return true } else { return false } + case let .textAutoPhone(text): + if case .textAutoPhone(text) = rhs { return true } else { return false } + case let .textAutoUrl(text): + if case .textAutoUrl(text) = rhs { return true } else { return false } + case let .textBankCard(text): + if case .textBankCard(text) = rhs { return true } else { return false } + case let .textBotCommand(text): + if case .textBotCommand(text) = rhs { return true } else { return false } + case let .textCashtag(text): + if case .textCashtag(text) = rhs { return true } else { return false } + case let .textHashtag(text): + if case .textHashtag(text) = rhs { return true } else { return false } + case let .textMention(text): + if case .textMention(text) = rhs { return true } else { return false } + case let .textMentionName(lhsText, lhsPeerId): + if case let .textMentionName(rhsText, rhsPeerId) = rhs, lhsText == rhsText, lhsPeerId == rhsPeerId { return true } else { return false } + case let .textSpoiler(text): + if case .textSpoiler(text) = rhs { return true } else { return false } + case let .textDate(lhsText, lhsDate, lhsFormat): + if case let .textDate(rhsText, rhsDate, rhsFormat) = rhs, lhsText == rhsText, lhsDate == rhsDate, lhsFormat == rhsFormat { return true } else { return false } } } } @@ -291,6 +424,32 @@ public extension RichText { return "" case let .anchor(text, _): return text.plainText + case let .formula(latex): + return latex + case let .textCustomEmoji(_, alt): + return alt + case let .textAutoEmail(text): + return text.plainText + case let .textAutoPhone(text): + return text.plainText + case let .textAutoUrl(text): + return text.plainText + case let .textBankCard(text): + return text.plainText + case let .textBotCommand(text): + return text.plainText + case let .textCashtag(text): + return text.plainText + case let .textHashtag(text): + return text.plainText + case let .textMention(text): + return text.plainText + case let .textMentionName(text, _): + return text.plainText + case let .textSpoiler(text): + return text.plainText + case let .textDate(text, _, _): + return text.plainText } } } @@ -378,6 +537,72 @@ extension RichText { } self = .anchor(text: try RichText(flatBuffersObject: value.text), name: value.name) + case .richtextFormula: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Formula.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .formula(latex: value.latex) + case .richtextCustomemoji: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_CustomEmoji.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textCustomEmoji(fileId: value.fileId, alt: value.alt) + case .richtextAutoemail: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_AutoEmail.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textAutoEmail(text: try RichText(flatBuffersObject: value.text)) + case .richtextAutophone: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_AutoPhone.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textAutoPhone(text: try RichText(flatBuffersObject: value.text)) + case .richtextAutourl: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_AutoUrl.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textAutoUrl(text: try RichText(flatBuffersObject: value.text)) + case .richtextBankcard: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_BankCard.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textBankCard(text: try RichText(flatBuffersObject: value.text)) + case .richtextBotcommand: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_BotCommand.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textBotCommand(text: try RichText(flatBuffersObject: value.text)) + case .richtextCashtag: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Cashtag.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textCashtag(text: try RichText(flatBuffersObject: value.text)) + case .richtextHashtag: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Hashtag.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textHashtag(text: try RichText(flatBuffersObject: value.text)) + case .richtextMention: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Mention.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textMention(text: try RichText(flatBuffersObject: value.text)) + case .richtextMentionname: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_MentionName.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textMentionName(text: try RichText(flatBuffersObject: value.text), peerId: value.peerId) + case .richtextSpoiler: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Spoiler.self) else { + throw FlatBuffersError.missingRequiredField() + } + self = .textSpoiler(text: try RichText(flatBuffersObject: value.text)) + case .richtextDate: + guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Date.self) else { + throw FlatBuffersError.missingRequiredField() + } + let formatValue = value.format + self = .textDate(text: try RichText(flatBuffersObject: value.text), date: value.date, format: formatValue == -1 ? nil : MessageTextEntityType.DateTimeFormat(rawValue: formatValue)) case .none_: self = .empty } @@ -494,8 +719,90 @@ extension RichText { TelegramCore_RichText_Anchor.add(text: textOffset, &builder) TelegramCore_RichText_Anchor.add(name: nameOffset, &builder) offset = TelegramCore_RichText_Anchor.endRichText_Anchor(&builder, start: start) + case let .formula(latex): + valueType = .richtextFormula + let latexOffset = builder.create(string: latex) + let start = TelegramCore_RichText_Formula.startRichText_Formula(&builder) + TelegramCore_RichText_Formula.add(latex: latexOffset, &builder) + offset = TelegramCore_RichText_Formula.endRichText_Formula(&builder, start: start) + case let .textCustomEmoji(fileId, alt): + valueType = .richtextCustomemoji + let altOffset = builder.create(string: alt) + let start = TelegramCore_RichText_CustomEmoji.startRichText_CustomEmoji(&builder) + TelegramCore_RichText_CustomEmoji.add(fileId: fileId, &builder) + TelegramCore_RichText_CustomEmoji.add(alt: altOffset, &builder) + offset = TelegramCore_RichText_CustomEmoji.endRichText_CustomEmoji(&builder, start: start) + case let .textAutoEmail(text): + valueType = .richtextAutoemail + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_AutoEmail.startRichText_AutoEmail(&builder) + TelegramCore_RichText_AutoEmail.add(text: textOffset, &builder) + offset = TelegramCore_RichText_AutoEmail.endRichText_AutoEmail(&builder, start: start) + case let .textAutoPhone(text): + valueType = .richtextAutophone + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_AutoPhone.startRichText_AutoPhone(&builder) + TelegramCore_RichText_AutoPhone.add(text: textOffset, &builder) + offset = TelegramCore_RichText_AutoPhone.endRichText_AutoPhone(&builder, start: start) + case let .textAutoUrl(text): + valueType = .richtextAutourl + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_AutoUrl.startRichText_AutoUrl(&builder) + TelegramCore_RichText_AutoUrl.add(text: textOffset, &builder) + offset = TelegramCore_RichText_AutoUrl.endRichText_AutoUrl(&builder, start: start) + case let .textBankCard(text): + valueType = .richtextBankcard + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_BankCard.startRichText_BankCard(&builder) + TelegramCore_RichText_BankCard.add(text: textOffset, &builder) + offset = TelegramCore_RichText_BankCard.endRichText_BankCard(&builder, start: start) + case let .textBotCommand(text): + valueType = .richtextBotcommand + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_BotCommand.startRichText_BotCommand(&builder) + TelegramCore_RichText_BotCommand.add(text: textOffset, &builder) + offset = TelegramCore_RichText_BotCommand.endRichText_BotCommand(&builder, start: start) + case let .textCashtag(text): + valueType = .richtextCashtag + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_Cashtag.startRichText_Cashtag(&builder) + TelegramCore_RichText_Cashtag.add(text: textOffset, &builder) + offset = TelegramCore_RichText_Cashtag.endRichText_Cashtag(&builder, start: start) + case let .textHashtag(text): + valueType = .richtextHashtag + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_Hashtag.startRichText_Hashtag(&builder) + TelegramCore_RichText_Hashtag.add(text: textOffset, &builder) + offset = TelegramCore_RichText_Hashtag.endRichText_Hashtag(&builder, start: start) + case let .textMention(text): + valueType = .richtextMention + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_Mention.startRichText_Mention(&builder) + TelegramCore_RichText_Mention.add(text: textOffset, &builder) + offset = TelegramCore_RichText_Mention.endRichText_Mention(&builder, start: start) + case let .textMentionName(text, peerId): + valueType = .richtextMentionname + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_MentionName.startRichText_MentionName(&builder) + TelegramCore_RichText_MentionName.add(text: textOffset, &builder) + TelegramCore_RichText_MentionName.add(peerId: peerId, &builder) + offset = TelegramCore_RichText_MentionName.endRichText_MentionName(&builder, start: start) + case let .textSpoiler(text): + valueType = .richtextSpoiler + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_Spoiler.startRichText_Spoiler(&builder) + TelegramCore_RichText_Spoiler.add(text: textOffset, &builder) + offset = TelegramCore_RichText_Spoiler.endRichText_Spoiler(&builder, start: start) + case let .textDate(text, date, format): + valueType = .richtextDate + let textOffset = text.encodeToFlatBuffers(builder: &builder) + let start = TelegramCore_RichText_Date.startRichText_Date(&builder) + TelegramCore_RichText_Date.add(text: textOffset, &builder) + TelegramCore_RichText_Date.add(date: date, &builder) + TelegramCore_RichText_Date.add(format: format?.rawValue ?? -1, &builder) + offset = TelegramCore_RichText_Date.endRichText_Date(&builder, start: start) } - + return TelegramCore_RichText.createRichText(&builder, valueType: valueType, valueOffset: offset) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichTextMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichTextMessageAttribute.swift new file mode 100644 index 0000000000..95ef406146 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_RichTextMessageAttribute.swift @@ -0,0 +1,77 @@ +import Foundation +import Postbox +import TelegramApi + +public class RichTextMessageAttribute: MessageAttribute, Equatable { + public let instantPage: InstantPage + public var fullInstantPage: InstantPage? + + public var associatedPeerIds: [PeerId] { + return [] + } + + public var associatedMediaIds: [MediaId] { + return [] + } + + public init(instantPage: InstantPage, fullInstantPage: InstantPage?) { + self.instantPage = instantPage + self.fullInstantPage = fullInstantPage + } + + required public init(decoder: PostboxDecoder) { + self.instantPage = decoder.decodeObjectForKey("instantPage", decoder: { InstantPage(decoder: $0) }) as! InstantPage + self.fullInstantPage = decoder.decodeObjectForKey("fullInstantPage", decoder: { InstantPage(decoder: $0) }) as? InstantPage + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.instantPage, forKey: "instantPage") + if let fullInstantPage = self.fullInstantPage { + encoder.encodeObject(fullInstantPage, forKey: "fullInstantPage") + } else { + encoder.encodeNil(forKey: "fullInstantPage") + } + } + + public static func ==(lhs: RichTextMessageAttribute, rhs: RichTextMessageAttribute) -> Bool { + return lhs.instantPage == rhs.instantPage && lhs.fullInstantPage == rhs.fullInstantPage + } +} + +extension RichTextMessageAttribute { + convenience init(apiRichMessage: Api.RichMessage) { + switch apiRichMessage { + case let .richMessage(richMessage): + var media: [MediaId: Media] = [:] + for photo in richMessage.photos { + if let image = telegramMediaImageFromApiPhoto(photo), let id = image.id { + media[id] = image + } + } + for file in richMessage.documents { + if let file = telegramMediaFileFromApiDocument(file, altDocuments: []), let id = file.id { + media[id] = file + } + } + let isRtl = (richMessage.flags & (1 << 0)) != 0 + let isPartial = (richMessage.flags & (1 << 1)) != 0 + let instantPage = InstantPage(blocks: richMessage.blocks.map({ InstantPageBlock(apiBlock: $0) }), media: media, isComplete: !isPartial, rtl: isRtl, url: "", views: nil) + self.init(instantPage: instantPage, fullInstantPage: nil) + } + } + + func apiInputRichMessage() -> Api.InputRichMessage { + var flags: Int32 = 0 + if self.instantPage.rtl { + flags |= (1 << 0) + } + + return Api.InputRichMessage.inputRichMessage(Api.InputRichMessage.Cons_inputRichMessage( + flags: flags, + blocks: self.instantPage.blocks.compactMap { $0.apiInputBlock() }, + photos: nil, + documents: nil, + users: nil + )) + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index cbb1035e4c..32b775a717 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -129,8 +129,15 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { break } } + var previousRichText: RichTextMessageAttribute? + for attribute in previous { + if let attribute = attribute as? RichTextMessageAttribute { + previousRichText = attribute + break + } + } - if let audioTranscription = audioTranscription { + if let audioTranscription { var found = false for i in 0 ..< updated.count { if let attribute = updated[i] as? AudioTranscriptionMessageAttribute { @@ -155,6 +162,18 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { updated.append(previousDerivedData) } } + if let previousRichText, previousRichText.fullInstantPage != nil { + for i in 0 ..< updated.count { + if let attribute = updated[i] as? RichTextMessageAttribute { + if attribute.fullInstantPage == nil { + if attribute.instantPage == previousRichText.instantPage { + updated[i] = previousRichText + } + } + break + } + } + } }, decodeMessageThreadInfo: { entry in guard let data = entry.get(MessageHistoryThreadData.self) else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatBannedRights.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatBannedRights.swift index f0ca1387f8..628c94ccd3 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatBannedRights.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChatBannedRights.swift @@ -32,6 +32,7 @@ public struct TelegramChatBannedRightsFlags: OptionSet, Hashable { public static let banSendVoice = TelegramChatBannedRightsFlags(rawValue: 1 << 23) public static let banSendFiles = TelegramChatBannedRightsFlags(rawValue: 1 << 24) public static let banSendText = TelegramChatBannedRightsFlags(rawValue: 1 << 25) + public static let banSendReactions = TelegramChatBannedRightsFlags(rawValue: 1 << 27) public static let banEditRank = TelegramChatBannedRightsFlags(rawValue: 1 << 26) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index c999ba735b..ad6480a0d2 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -653,34 +653,6 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { } } -public enum TelegramMediaFileReference: PostboxCoding, Equatable { - case cloud(fileId: Int64, accessHash: Int64, fileReference: Data?) - - public init(decoder: PostboxDecoder) { - switch decoder.decodeInt32ForKey("_v", orElse: 0) { - case 0: - self = .cloud(fileId: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0), fileReference: decoder.decodeBytesForKey("fr")?.makeData()) - default: - self = .cloud(fileId: 0, accessHash: 0, fileReference: nil) - assertionFailure() - } - } - - public func encode(_ encoder: PostboxEncoder) { - switch self { - case let .cloud(imageId, accessHash, fileReference): - encoder.encodeInt32(0, forKey: "_v") - encoder.encodeInt64(imageId, forKey: "i") - encoder.encodeInt64(accessHash, forKey: "h") - if let fileReference = fileReference { - encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr") - } else { - encoder.encodeNil(forKey: "fr") - } - } - } -} - public enum TelegramMediaFileDecodingError: Error { case generic } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift index 703a6a0f46..285d87b9eb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift @@ -140,13 +140,15 @@ public struct TelegramMediaPollResults: Equatable, PostboxCoding { public let recentVoters: [PeerId] public let solution: TelegramMediaPollResults.Solution? public let hasUnseenVotes: Bool? + public let canViewStats: Bool - public init(voters: [TelegramMediaPollOptionVoters]?, totalVoters: Int32?, recentVoters: [PeerId], solution: TelegramMediaPollResults.Solution?, hasUnseenVotes: Bool?) { + public init(voters: [TelegramMediaPollOptionVoters]?, totalVoters: Int32?, recentVoters: [PeerId], solution: TelegramMediaPollResults.Solution?, hasUnseenVotes: Bool?, canViewStats: Bool) { self.voters = voters self.totalVoters = totalVoters self.recentVoters = recentVoters self.solution = solution self.hasUnseenVotes = hasUnseenVotes + self.canViewStats = canViewStats } public init(decoder: PostboxDecoder) { @@ -161,6 +163,7 @@ public struct TelegramMediaPollResults: Equatable, PostboxCoding { self.solution = nil } self.hasUnseenVotes = decoder.decodeOptionalBoolForKey("uns") + self.canViewStats = decoder.decodeBoolForKey("cvs", orElse: false) } public func encode(_ encoder: PostboxEncoder) { @@ -191,6 +194,7 @@ public struct TelegramMediaPollResults: Equatable, PostboxCoding { } else { encoder.encodeNil(forKey: "uns") } + encoder.encodeBool(self.canViewStats, forKey: "cvs") } } @@ -273,8 +277,10 @@ public final class TelegramMediaPoll: Media, Equatable { public let hideResultsUntilClose: Bool public let isCreator: Bool public let attachedMedia: Media? + public let restrictToSubscribers: Bool + public let countries: [String] - public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?, deadlineDate: Int32?, pollHash: Int64, openAnswers: Bool = false, revotingDisabled: Bool = false, shuffleAnswers: Bool = false, hideResultsUntilClose: Bool = false, isCreator: Bool = false, attachedMedia: Media? = nil) { + public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?, deadlineDate: Int32?, pollHash: Int64, openAnswers: Bool = false, revotingDisabled: Bool = false, shuffleAnswers: Bool = false, hideResultsUntilClose: Bool = false, isCreator: Bool = false, attachedMedia: Media? = nil, restrictToSubscribers: Bool = false, countries: [String] = []) { self.pollId = pollId self.publicity = publicity self.kind = kind @@ -293,6 +299,8 @@ public final class TelegramMediaPoll: Media, Equatable { self.hideResultsUntilClose = hideResultsUntilClose self.isCreator = isCreator self.attachedMedia = attachedMedia + self.restrictToSubscribers = restrictToSubscribers + self.countries = countries } public init(decoder: PostboxDecoder) { @@ -307,7 +315,7 @@ public final class TelegramMediaPoll: Media, Equatable { self.textEntities = decoder.decodeObjectArrayWithDecoderForKey("te") self.options = decoder.decodeObjectArrayWithDecoderForKey("os") self.correctAnswers = decoder.decodeOptionalDataArrayForKey("ca") - self.results = decoder.decodeObjectForKey("rs", decoder: { TelegramMediaPollResults(decoder: $0) }) as? TelegramMediaPollResults ?? TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil, hasUnseenVotes: nil) + self.results = decoder.decodeObjectForKey("rs", decoder: { TelegramMediaPollResults(decoder: $0) }) as? TelegramMediaPollResults ?? TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil, hasUnseenVotes: nil, canViewStats: false) self.isClosed = decoder.decodeInt32ForKey("ic", orElse: 0) != 0 self.deadlineTimeout = decoder.decodeOptionalInt32ForKey("dt") self.deadlineDate = decoder.decodeOptionalInt32ForKey("dd") @@ -318,6 +326,8 @@ public final class TelegramMediaPoll: Media, Equatable { self.hideResultsUntilClose = decoder.decodeInt32ForKey("hr", orElse: 0) != 0 self.isCreator = decoder.decodeInt32ForKey("cr", orElse: 0) != 0 self.attachedMedia = decoder.decodeObjectForKey("am") as? Media + self.restrictToSubscribers = decoder.decodeInt32ForKey("sub", orElse: 0) != 0 + self.countries = decoder.decodeStringArrayForKey("cnt") } public func encode(_ encoder: PostboxEncoder) { @@ -357,6 +367,8 @@ public final class TelegramMediaPoll: Media, Equatable { } else { encoder.encodeNil(forKey: "am") } + encoder.encodeInt32(self.restrictToSubscribers ? 1 : 0, forKey: "sub") + encoder.encodeStringArray(self.countries, forKey: "cnt") } public func isEqual(to other: Media) -> Bool { @@ -427,6 +439,12 @@ public final class TelegramMediaPoll: Media, Equatable { } else if (lhs.attachedMedia == nil) != (rhs.attachedMedia == nil) { return false } + if lhs.restrictToSubscribers != rhs.restrictToSubscribers { + return false + } + if lhs.countries != rhs.countries { + return false + } return true } @@ -445,22 +463,21 @@ public final class TelegramMediaPoll: Media, Equatable { } } updatedResults = TelegramMediaPollResults(voters: updatedVoters.map({ voters in - return TelegramMediaPollOptionVoters(selected: selectedOpaqueIdentifiers.contains(voters.opaqueIdentifier), opaqueIdentifier: voters.opaqueIdentifier, count: voters.count, isCorrect: correctOpaqueIdentifiers.contains(voters.opaqueIdentifier), recentVoters: voters.recentVoters) - }), totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes) + return TelegramMediaPollOptionVoters(selected: selectedOpaqueIdentifiers.contains(voters.opaqueIdentifier), opaqueIdentifier: voters.opaqueIdentifier, count: voters.count, isCorrect: voters.isCorrect || correctOpaqueIdentifiers.contains(voters.opaqueIdentifier), recentVoters: voters.recentVoters) + }), totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes, canViewStats: results.canViewStats) } else if let updatedVoters = results.voters { - updatedResults = TelegramMediaPollResults(voters: updatedVoters, totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes) + updatedResults = TelegramMediaPollResults(voters: updatedVoters, totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes, canViewStats: results.canViewStats) } else { - updatedResults = TelegramMediaPollResults(voters: self.results.voters, totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes) + updatedResults = TelegramMediaPollResults(voters: self.results.voters, totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution, hasUnseenVotes: results.hasUnseenVotes ?? self.results.hasUnseenVotes, canViewStats: results.canViewStats) } } else { updatedResults = results } - return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, pollHash: self.pollHash, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, isCreator: self.isCreator, attachedMedia: self.attachedMedia) + return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, pollHash: self.pollHash, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, isCreator: self.isCreator, attachedMedia: self.attachedMedia, restrictToSubscribers: self.restrictToSubscribers, countries: self.countries) } public func withoutUnreadResults() -> TelegramMediaPoll { - let updatedResults = TelegramMediaPollResults(voters: self.results.voters, totalVoters: self.results.totalVoters, recentVoters: self.results.recentVoters, solution: self.results.solution, hasUnseenVotes: false) - return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, pollHash: self.pollHash, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, isCreator: self.isCreator, attachedMedia: self.attachedMedia) + let updatedResults = TelegramMediaPollResults(voters: self.results.voters, totalVoters: self.results.totalVoters, recentVoters: self.results.recentVoters, solution: self.results.solution, hasUnseenVotes: false, canViewStats: self.results.canViewStats) + return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, deadlineDate: self.deadlineDate, pollHash: self.pollHash, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, isCreator: self.isCreator, attachedMedia: self.attachedMedia, restrictToSubscribers: self.restrictToSubscribers, countries: self.countries) } } - diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift index bef68016e0..fae9ea4809 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaWebpage.swift @@ -9,6 +9,7 @@ private enum TelegramMediaWebpageAttributeTypes: Int32 { case starGift case giftCollection case giftAuction + case aiTextStyle } public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { @@ -18,6 +19,7 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { case starGift(TelegramMediaWebpageStarGiftAttribute) case giftCollection(TelegramMediaWebpageGiftCollectionAttribute) case giftAuction(TelegramMediaWebpageGiftAuctionAttribute) + case aiTextStyle(TelegramMediaWebpageAITextStyleAttribute) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { @@ -31,6 +33,8 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { self = .giftCollection(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageGiftCollectionAttribute(decoder: $0) }) as! TelegramMediaWebpageGiftCollectionAttribute) case TelegramMediaWebpageAttributeTypes.giftAuction.rawValue: self = .giftAuction(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageGiftAuctionAttribute(decoder: $0) }) as! TelegramMediaWebpageGiftAuctionAttribute) + case TelegramMediaWebpageAttributeTypes.aiTextStyle.rawValue: + self = .aiTextStyle(decoder.decodeObjectForKey("a", decoder: { TelegramMediaWebpageAITextStyleAttribute(decoder: $0) }) as! TelegramMediaWebpageAITextStyleAttribute) default: self = .unsupported } @@ -55,6 +59,17 @@ public enum TelegramMediaWebpageAttribute: PostboxCoding, Equatable { case let .giftAuction(attribute): encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.giftAuction.rawValue, forKey: "r") encoder.encodeObject(attribute, forKey: "a") + case let .aiTextStyle(attribute): + encoder.encodeInt32(TelegramMediaWebpageAttributeTypes.aiTextStyle.rawValue, forKey: "r") + encoder.encodeObject(attribute, forKey: "a") + } + } + + public var mediaIds: [MediaId] { + if case let .aiTextStyle(attribute) = self { + return [MediaId(namespace: Namespaces.Media.CloudFile, id: attribute.emojiFileId)] + } else { + return [] } } } @@ -235,6 +250,32 @@ public final class TelegramMediaWebpageGiftAuctionAttribute: PostboxCoding, Equa } } +public final class TelegramMediaWebpageAITextStyleAttribute: PostboxCoding, Equatable { + public static func == (lhs: TelegramMediaWebpageAITextStyleAttribute, rhs: TelegramMediaWebpageAITextStyleAttribute) -> Bool { + if lhs.emojiFileId != rhs.emojiFileId { + return false + } + return true + } + + public let emojiFileId: Int64 + + public init( + emojiFileId: Int64 + ) { + self.emojiFileId = emojiFileId + } + + public init(decoder: PostboxDecoder) { + self.emojiFileId = decoder.decodeInt64ForKey("eid", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64(self.emojiFileId, forKey: "eid") + } +} + + public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable { public let url: String public let displayUrl: String @@ -554,6 +595,17 @@ public final class TelegramMediaWebpage: Media, Equatable { } } + public var mediaIds: [MediaId] { + guard case let .Loaded(content) = self.content else { + return [] + } + var result: [MediaId] = [] + for attribute in content.attributes { + result.append(contentsOf: attribute.mediaIds) + } + return result + } + public let webpageId: MediaId public let content: TelegramMediaWebpageContent diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift index 054b229e73..da15cbfcad 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift @@ -98,7 +98,7 @@ public final class CachedSecretChatData: CachedPeerData { public init(decoder: PostboxDecoder) { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift index bcc02ef022..f3d2a94ecd 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramUser.swift @@ -45,6 +45,8 @@ public struct BotUserInfoFlags: OptionSet { public static let hasForum = BotUserInfoFlags(rawValue: (1 << 8)) public static let forumManagedByUser = BotUserInfoFlags(rawValue: (1 << 9)) public static let canManageBots = BotUserInfoFlags(rawValue: (1 << 10)) + public static let isGuestChat = BotUserInfoFlags(rawValue: (1 << 11)) + public static let isGuardBot = BotUserInfoFlags(rawValue: (1 << 12)) } public struct BotUserInfo: PostboxCoding, Equatable { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift index 7a43a6dea9..4389519a95 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_UnauthorizedAccountState.swift @@ -182,7 +182,7 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool) case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool) case signUp(number: String, codeHash: String, firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, syncContacts: Bool) - case payment(number: String, codeHash: String, storeProduct: String, supportEmailAddress: String, supportEmailSubject: String, syncContacts: Bool) + case payment(number: String, codeHash: String, storeProduct: String, premiumDays: Int32, supportEmailAddress: String, supportEmailSubject: String, syncContacts: Bool) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { @@ -217,7 +217,7 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable case UnauthorizedAccountStateContentsValue.signUp.rawValue: self = .signUp(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), firstName: decoder.decodeStringForKey("f", orElse: ""), lastName: decoder.decodeStringForKey("l", orElse: ""), termsOfService: decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) case UnauthorizedAccountStateContentsValue.payment.rawValue: - self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""), supportEmailAddress: decoder.decodeStringForKey("supportEmailAddress", orElse: ""), supportEmailSubject: decoder.decodeStringForKey("supportEmailSubject", orElse: ""), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) + self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""), premiumDays: decoder.decodeInt32ForKey("premiumDays", orElse: 7), supportEmailAddress: decoder.decodeStringForKey("supportEmailAddress", orElse: ""), supportEmailSubject: decoder.decodeStringForKey("supportEmailSubject", orElse: ""), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0) default: assertionFailure() self = .empty @@ -307,11 +307,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable encoder.encodeNil(forKey: "tos") } encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") - case let .payment(number, codeHash, storeProduct, supportEmailAddress, supportEmailSubject, syncContacts): + case let .payment(number, codeHash, storeProduct, premiumDays, supportEmailAddress, supportEmailSubject, syncContacts): encoder.encodeInt32(UnauthorizedAccountStateContentsValue.payment.rawValue, forKey: "v") encoder.encodeString(number, forKey: "n") encoder.encodeString(codeHash, forKey: "h") encoder.encodeString(storeProduct, forKey: "storeProduct") + encoder.encodeInt32(premiumDays, forKey: "premiumDays") encoder.encodeString(supportEmailAddress, forKey: "supportEmailAddress") encoder.encodeString(supportEmailSubject, forKey: "supportEmailSubject") encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts") @@ -386,8 +387,8 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable } else { return false } - case let .payment(number, codeHash, storeProduct, supportEmailAddress, supportEmailSubject, syncContacts): - if case .payment(number, codeHash, storeProduct, supportEmailAddress, supportEmailSubject, syncContacts) = rhs { + case let .payment(number, codeHash, storeProduct, premiumDays, supportEmailAddress, supportEmailSubject, syncContacts): + if case .payment(number, codeHash, storeProduct, premiumDays, supportEmailAddress, supportEmailSubject, syncContacts) = rhs { return true } else { return false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_VoipConfiguration.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_VoipConfiguration.swift index c91b598a8b..76f3957519 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_VoipConfiguration.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_VoipConfiguration.swift @@ -1,12 +1,6 @@ import Foundation import Postbox -public enum VoiceCallP2PMode: Int32 { - case never = 0 - case contacts = 1 - case always = 2 -} - public struct VoipConfiguration: Codable, Equatable { public var serializedData: String? diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/BotConnectionReviews.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/BotConnectionReviews.swift new file mode 100644 index 0000000000..10a3da7c67 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/BotConnectionReviews.swift @@ -0,0 +1,35 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi + +func _internal_confirmBotConnectionReview(account: Account, botId: PeerId) -> Signal { + return account.network.request(Api.functions.account.getConnectedBots()) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + + return account.postbox.transaction { transaction -> Api.InputUser? in + switch result { + case let .connectedBots(connectedBotsData): + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: connectedBotsData.users)) + } + return transaction.getPeer(botId).flatMap(apiInputUser) + } + |> mapToSignal { inputUser -> Signal in + guard let inputUser else { + return .complete() + } + return account.network.request(Api.functions.account.confirmBotConnection(botId: inputUser)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index d2b33e6456..cb64547812 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -15,6 +15,14 @@ public extension TelegramEngine { return _internal_acceptTermsOfService(account: self.account, id: id) } + public func addAppLogEvent(time: Double = Date().timeIntervalSince1970, type: String, data: JSON = .dictionary([:])) { + _internal_addAppLogEvent(postbox: self.account.postbox, time: time, type: type, peerId: nil, data: data) + } + + public func addAppLogEvent(time: Double = Date().timeIntervalSince1970, type: String, peerId: EnginePeer.Id?, data: JSON = .dictionary([:])) { + _internal_addAppLogEvent(postbox: self.account.postbox, time: time, type: type, peerId: peerId, data: data) + } + public func requestChangeAccountPhoneNumberVerification(apiId: Int32, apiHash: String, phoneNumber: String, pushNotificationConfiguration: AuthorizationCodePushNotificationConfiguration?, firebaseSecretStream: Signal<[String: String], NoError>) -> Signal { return _internal_requestChangeAccountPhoneNumberVerification(account: self.account, apiId: apiId, apiHash: apiHash, phoneNumber: phoneNumber, pushNotificationConfiguration: pushNotificationConfiguration, firebaseSecretStream: firebaseSecretStream) } @@ -55,8 +63,10 @@ public extension TelegramEngine { return _internal_registerNotificationToken(account: self.account, token: token, type: type, sandbox: sandbox, otherAccountUserIds: otherAccountUserIds, excludeMutedChats: excludeMutedChats) } - public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: false, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + public func updateAccountPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updateAccountPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: false, mapResourceToAvatarSizes: { rawResource, representations in + return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations) + }) } public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal { @@ -67,8 +77,10 @@ public extension TelegramEngine { return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: false) } - public func updateFallbackPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: true, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + public func updateFallbackPhoto(resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updateAccountPhoto(account: self.account, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, fallback: true, mapResourceToAvatarSizes: { rawResource, representations in + return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations) + }) } public func removeFallbackPhoto(reference: TelegramMediaImageReference?) -> Signal { @@ -270,6 +282,10 @@ public extension TelegramEngine { return _internal_setAccountConnectedBot(account: self.account, bot: bot) } + public func confirmBotConnectionReview(botId: PeerId) -> Signal { + return _internal_confirmBotConnectionReview(account: self.account, botId: botId) + } + public func updateBusinessIntro(intro: TelegramBusinessIntro?) -> Signal { return _internal_updateBusinessIntro(account: self.account, intro: intro) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift index 6fbca60760..89f3553c61 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TelegramEngineAuth.swift @@ -48,8 +48,8 @@ public extension TelegramEngineUnauthorized { return _internal_resendTwoStepRecoveryEmail(network: self.account.network) } - public func uploadedPeerVideo(resource: MediaResource) -> Signal { - return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource) + public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal { + return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: nil, resource: resource._asResource()) } public func reportMissingCode(phoneNumber: String, phoneCodeHash: String, mnc: String) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TwoStepVerification.swift b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TwoStepVerification.swift index d0e005a8ae..1fd31b354e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Auth/TwoStepVerification.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Auth/TwoStepVerification.swift @@ -337,13 +337,6 @@ func _internal_requestTwoStepVerificationPasswordRecoveryCode(network: Network) } } -public enum RecoverTwoStepVerificationPasswordError { - case generic - case codeExpired - case limitExceeded - case invalidCode -} - func _internal_cachedTwoStepPasswordToken(postbox: Postbox) -> Signal { return postbox.transaction { transaction -> TemporaryTwoStepPasswordToken? in let key = ValueBoxKey(length: 1) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index e829e578d6..711546a2fc 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -2957,7 +2957,7 @@ func _internal_groupCallDisplayAsAvailablePeers(accountPeerId: PeerId, network: } } - return peers.map { FoundPeer(peer: $0, subscribers: subscribers[$0.id]) } + return peers.map { FoundPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id]) } } } } @@ -3011,7 +3011,7 @@ func _internal_cachedGroupCallDisplayAsAvailablePeers(account: Account, peerId: if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(FoundPeer(peer: peer, subscribers: subscribers)) + peers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers)) } } return (peers, cached.timestamp) @@ -3415,10 +3415,6 @@ func _internal_revokeConferenceInviteLink(account: Account, reference: InternalG } } -public enum ConfirmAddConferenceParticipantError { - case generic -} - func _internal_pollConferenceCallBlockchain(network: Network, reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> { return network.request(Api.functions.phone.getGroupCallChainBlocks(call: reference.apiInputGroupCall, subChainId: Int32(subChainId), offset: Int32(offset), limit: Int32(limit))) |> map(Optional.init) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index aedbdbeae3..bc41811d57 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -30,8 +30,10 @@ public extension TelegramEngine { return _internal_updateContactName(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName) } - public func updateContactPhoto(peerId: PeerId, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + public func updateContactPhoto(peerId: PeerId, resource: EngineMediaResource?, videoResource: EngineMediaResource?, videoStartTimestamp: Double?, markup: UploadPeerPhotoMarkup?, mode: SetCustomPeerPhotoMode, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updateContactPhoto(account: self.account, peerId: peerId, resource: resource?._asResource(), videoResource: videoResource?._asResource(), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: mode, mapResourceToAvatarSizes: { rawResource, representations in + return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations) + }) } public func updateContactNote(peerId: PeerId, text: String, entities: [MessageTextEntity]) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift index 15e164f6ed..3991971896 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift @@ -64,6 +64,7 @@ public enum EngineConfiguration { public let maxChannelRecommendationsCount: Int32 public let maxConferenceParticipantCount: Int32 public let maxBotsCreated: Int32 + public let maxOwnedAITextStyles: Int32 public static var defaultValue: UserLimits { return UserLimits(UserLimitsConfiguration.defaultValue) @@ -97,7 +98,8 @@ public enum EngineConfiguration { maxGiveawayPeriodSeconds: Int32, maxChannelRecommendationsCount: Int32, maxConferenceParticipantCount: Int32, - maxBotsCreated: Int32 + maxBotsCreated: Int32, + maxOwnedAITextStyles: Int32 ) { self.maxPinnedChatCount = maxPinnedChatCount self.maxPinnedSavedChatCount = maxPinnedSavedChatCount @@ -127,6 +129,7 @@ public enum EngineConfiguration { self.maxChannelRecommendationsCount = maxChannelRecommendationsCount self.maxConferenceParticipantCount = maxConferenceParticipantCount self.maxBotsCreated = maxBotsCreated + self.maxOwnedAITextStyles = maxOwnedAITextStyles } } } @@ -191,7 +194,8 @@ public extension EngineConfiguration.UserLimits { maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds, maxChannelRecommendationsCount: userLimitsConfiguration.maxChannelRecommendationsCount, maxConferenceParticipantCount: userLimitsConfiguration.maxConferenceParticipantCount, - maxBotsCreated: userLimitsConfiguration.maxBotsCreated + maxBotsCreated: userLimitsConfiguration.maxBotsCreated, + maxOwnedAITextStyles: userLimitsConfiguration.maxOwnedAITextStyles ) } } @@ -576,6 +580,27 @@ public extension TelegramEngine.EngineData.Item { } } + public struct ContactsSettings: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = TelegramCore.ContactsSettings + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.contactsSettings])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + guard let value = view.values[PreferencesKeys.contactsSettings]?.get(TelegramCore.ContactsSettings.self) else { + return TelegramCore.ContactsSettings.defaultSettings + } + return value + } + } + public struct EmojiGame: TelegramEngineDataItem, PostboxViewDataItem { public typealias Result = EmojiGameInfo diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift index 0d087fd442..604bf063c2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/MessagesData.swift @@ -206,16 +206,18 @@ public extension TelegramEngine.EngineData.Item { public typealias Result = Int fileprivate let id: EnginePeer.Id + fileprivate let handleThreads: Bool public var mapKey: EnginePeer.Id { return self.id } var key: PostboxViewKey { - return .unreadCounts(items: [.peer(id: self.id, handleThreads: true)]) + return .unreadCounts(items: [.peer(id: self.id, handleThreads: self.handleThreads)]) } - public init(id: EnginePeer.Id) { + public init(id: EnginePeer.Id, handleThreads: Bool = true) { self.id = id + self.handleThreads = handleThreads } func extract(view: PostboxView) -> Result { @@ -223,7 +225,7 @@ public extension TelegramEngine.EngineData.Item { preconditionFailure() } - return Int(view.count(for: .peer(id: self.id, handleThreads: true)) ?? 0) + return Int(view.count(for: .peer(id: self.id, handleThreads: self.handleThreads)) ?? 0) } } @@ -337,25 +339,28 @@ public extension TelegramEngine.EngineData.Item { public var peerId: EnginePeer.Id public var tag: MessageTags public var threadId: Int64? + public var namespace: Int32 } - + public typealias Result = Int? - + fileprivate var peerId: EnginePeer.Id fileprivate var tag: MessageTags fileprivate var threadId: Int64? + fileprivate var namespace: Int32 public var mapKey: ItemKey { - return ItemKey(peerId: self.peerId, tag: self.tag, threadId: self.threadId) + return ItemKey(peerId: self.peerId, tag: self.tag, threadId: self.threadId, namespace: self.namespace) } - - public init(peerId: EnginePeer.Id, threadId: Int64?, tag: MessageTags) { + + public init(peerId: EnginePeer.Id, threadId: Int64?, tag: MessageTags, namespace: Int32 = Namespaces.Message.Cloud) { self.peerId = peerId self.threadId = threadId self.tag = tag + self.namespace = namespace } var key: PostboxViewKey { - return .historyTagSummaryView(tag: self.tag, peerId: self.peerId, threadId: self.threadId, namespace: Namespaces.Message.Cloud, customTag: nil) + return .historyTagSummaryView(tag: self.tag, peerId: self.peerId, threadId: self.threadId, namespace: self.namespace, customTag: nil) } func extract(view: PostboxView) -> Result { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift index 84ee94709c..d7d2592844 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift @@ -45,6 +45,26 @@ public extension TelegramEngine.EngineData.Item { } enum OrderedLists { + public struct NewBotConnectionReviews: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = [NewBotConnectionReview] + + public init() { + } + + var key: PostboxViewKey { + return .orderedItemList(id: Namespaces.OrderedItemList.NewBotConnectionReviews) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? OrderedItemListView else { + preconditionFailure() + } + return view.items.compactMap { item in + return item.contents.get(NewBotConnectionReview.self) + } + } + } + public struct ListItems: TelegramEngineDataItem, PostboxViewDataItem { public typealias Result = [OrderedItemListEntry] @@ -66,4 +86,48 @@ public extension TelegramEngine.EngineData.Item { } } } + + enum ItemCollections { + public struct InstalledPackInfos: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = [EngineRawItemCollectionInfoEntry] + + private let namespace: ItemCollectionId.Namespace + + public init(namespace: ItemCollectionId.Namespace) { + self.namespace = namespace + } + + var key: PostboxViewKey { + return .itemCollectionInfos(namespaces: [self.namespace]) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? ItemCollectionInfosView else { + preconditionFailure() + } + return view.entriesByNamespace[self.namespace] ?? [] + } + } + + public struct InstalledPackIds: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = [ItemCollectionId] + + private let namespace: ItemCollectionId.Namespace + + public init(namespace: ItemCollectionId.Namespace) { + self.namespace = namespace + } + + var key: PostboxViewKey { + return .itemCollectionIds(namespaces: [self.namespace]) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? ItemCollectionIdsView else { + preconditionFailure() + } + return Array(view.idsByNamespace[self.namespace] ?? []) + } + } + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index a793b7d779..db19395271 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -184,6 +184,36 @@ public extension TelegramEngine.EngineData.Item { } } + public struct MainPeer: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = Optional + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .peer(peerId: self.id, components: []) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PeerView else { + preconditionFailure() + } + guard let peer = view.peers[self.id] else { + return nil + } + if let secretChat = peer as? TelegramSecretChat { + return view.peers[secretChat.regularPeerId].flatMap(EnginePeer.init) + } + return EnginePeer(peer) + } + } + public struct RenderedPeer: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = Optional @@ -310,6 +340,30 @@ public extension TelegramEngine.EngineData.Item { } } + public struct CachedData: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = Optional + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + return view.cachedPeerData + } + } + public struct ParticipantCount: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = Optional @@ -1688,6 +1742,9 @@ public extension TelegramEngine.EngineData.Item { } func _extract(data: TelegramEngine.EngineData, views: [PostboxViewKey: PostboxView]) -> Any { + guard self.id != data.accountPeerId else { + return false + } guard let basicPeerView = views[.basicPeer(data.accountPeerId)] as? BasicPeerView else { assertionFailure() return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/ItemCollections/TelegramEngineItemCollections.swift b/submodules/TelegramCore/Sources/TelegramEngine/ItemCollections/TelegramEngineItemCollections.swift new file mode 100644 index 0000000000..bcf815f141 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/ItemCollections/TelegramEngineItemCollections.swift @@ -0,0 +1,20 @@ +import Foundation +import SwiftSignalKit +import Postbox + +public extension TelegramEngine { + final class ItemCollections { + private let account: Account + + init(account: Account) { + self.account = account + } + + public func allItems(namespace: ItemCollectionId.Namespace) -> Signal<[EngineRawItemCollectionItem], NoError> { + return self.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [namespace], aroundIndex: nil, count: 10000000) + |> map { view -> [EngineRawItemCollectionItem] in + return view.entries.map { $0.item } + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 530a2d2535..93d1d77c46 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -60,6 +60,9 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P if (flags & (1 << 2)) != 0 { resultFlags.insert(.fullScreen) } + if (flags & (1 << 3)) != 0 { + resultFlags.insert(.sameOrigin) + } return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil)) } } @@ -111,6 +114,9 @@ func _internal_requestMainWebView(postbox: Postbox, network: Network, peerId: Pe if (flags & (1 << 2)) != 0 { resultFlags.insert(.fullScreen) } + if (flags & (1 << 3)) != 0 { + resultFlags.insert(.sameOrigin) + } return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil)) } } @@ -137,12 +143,20 @@ public struct RequestWebViewResult { public static let fullSize = Flags(rawValue: 1 << 0) public static let fullScreen = Flags(rawValue: 1 << 1) + public static let sameOrigin = Flags(rawValue: 1 << 2) } public let flags: Flags public let queryId: Int64? public let url: String public let keepAliveSignal: Signal? + + public init(flags: Flags, queryId: Int64?, url: String, keepAliveSignal: Signal?) { + self.flags = flags + self.queryId = queryId + self.url = url + self.keepAliveSignal = keepAliveSignal + } } public enum RequestWebViewError { @@ -270,6 +284,9 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager: if (webViewFlags & (1 << 2)) != 0 { resultFlags.insert(.fullScreen) } + if (webViewFlags & (1 << 3)) != 0 { + resultFlags.insert(.sameOrigin) + } let keepAlive: Signal? if let queryId { keepAlive = keepWebViewSignal(network: network, stateManager: stateManager, flags: flags, peer: inputPeer, monoforumPeerId: monoforumPeerId, bot: inputBot, queryId: queryId, replyToMessageId: replyToMessageId, threadId: threadId, sendAs: nil) @@ -363,6 +380,9 @@ func _internal_requestAppWebView(postbox: Postbox, network: Network, stateManage if (flags & (1 << 2)) != 0 { resultFlags.insert(.fullScreen) } + if (flags & (1 << 3)) != 0 { + resultFlags.insert(.sameOrigin) + } return .single(RequestWebViewResult(flags: resultFlags, queryId: queryId, url: url, keepAliveSignal: nil)) } } @@ -702,7 +722,10 @@ func _internal_removeChatManagingBot(account: Account, chatId: EnginePeer.Id) -> excludePeers: excludePeers, exclude: connectedBot.recipients.exclude ), - rights: connectedBot.rights + rights: connectedBot.rights, + device: connectedBot.device, + date: connectedBot.date, + location: connectedBot.location )) } else { return current diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift index 65a2d3a3d2..63967e2685 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ChatList.swift @@ -474,10 +474,13 @@ public extension EngineChatList.RelativePosition { } } -private func calculateIsPremiumRequiredToMessage(isPremium: Bool, targetPeer: Peer, cachedIsPremiumRequired: Bool) -> Bool { +private func calculateIsPremiumRequiredToMessage(accountPeerId: PeerId?, isPremium: Bool, targetPeer: Peer, cachedIsPremiumRequired: Bool) -> Bool { if isPremium { return false } + if let accountPeerId, targetPeer.id == accountPeerId { + return false + } guard let targetPeer = targetPeer as? TelegramUser else { return false } @@ -488,7 +491,7 @@ private func calculateIsPremiumRequiredToMessage(isPremium: Bool, targetPeer: Pe } extension EngineChatList.Item { - convenience init?(_ entry: ChatListEntry, isPremium: Bool, displayAsTopicList: Bool) { + convenience init?(_ entry: ChatListEntry, accountPeerId: PeerId?, isPremium: Bool, displayAsTopicList: Bool) { switch entry { case let .MessageEntry(entryData): let index = entryData.index @@ -507,7 +510,7 @@ extension EngineChatList.Item { var isPremiumRequiredToMessage = false if let targetPeer = renderedPeer.chatMainPeer, let extractedData = entryData.extractedCachedData?.base as? ExtractedChatListItemCachedData { - isPremiumRequiredToMessage = calculateIsPremiumRequiredToMessage(isPremium: isPremium, targetPeer: targetPeer, cachedIsPremiumRequired: extractedData.isPremiumRequiredToMessage) + isPremiumRequiredToMessage = calculateIsPremiumRequiredToMessage(accountPeerId: accountPeerId, isPremium: isPremium, targetPeer: targetPeer, cachedIsPremiumRequired: extractedData.isPremiumRequiredToMessage) } var draft: EngineChatList.Draft? @@ -632,7 +635,7 @@ extension EngineChatList.AdditionalItem.PromoInfo { extension EngineChatList.AdditionalItem { convenience init?(_ entry: ChatListAdditionalItemEntry) { - guard let item = EngineChatList.Item(entry.entry, isPremium: false, displayAsTopicList: false) else { + guard let item = EngineChatList.Item(entry.entry, accountPeerId: nil, isPremium: false, displayAsTopicList: false) else { return nil } guard let promoInfo = (entry.info as? PromoChatListItem).flatMap(EngineChatList.AdditionalItem.PromoInfo.init) else { @@ -657,7 +660,7 @@ public extension EngineChatList { loop: for entry in view.entries { switch entry { case .MessageEntry: - if let item = EngineChatList.Item(entry, isPremium: isPremium, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { + if let item = EngineChatList.Item(entry, accountPeerId: accountPeerId, isPremium: isPremium, displayAsTopicList: entry.index.messageIndex.id.peerId == accountPeerId ? displaySavedMessagesAsTopicList : false) { items.append(item) } case .HoleEntry: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift index 07f7b0fa8f..3405d572eb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ClearCloudDrafts.swift @@ -62,7 +62,7 @@ func _internal_clearCloudDraftsInteractively(postbox: Postbox, network: Network, flags |= (1 << 0) replyTo = .inputReplyToMonoForum(.init(monoforumPeerId: monoforumPeerId)) } - signals.append(network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: "", entities: nil, media: nil, effect: nil, suggestedPost: nil)) + signals.append(network.request(Api.functions.messages.saveDraft(flags: flags, replyTo: replyTo, peer: inputPeer, message: "", entities: nil, media: nil, effect: nil, suggestedPost: nil, richMessage: nil)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessages.swift index 001fcc1282..2bed4870bd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/DeleteMessages.swift @@ -17,7 +17,7 @@ func addMessageMediaResourceIdsToRemove(media: Media, resourceIds: inout [MediaR } func addMessageMediaResourceIdsToRemove(message: Message, resourceIds: inout [MediaResourceId]) { - for media in message.media { + for media in message.effectiveMedia { addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds) } } @@ -76,6 +76,203 @@ func _internal_deleteAllMessagesWithForwardAuthor(transaction: Transaction, medi } } +func _internal_deleteAllReactionsWithAuthor(account: Account, peerId: PeerId, authorId: PeerId, aroundMessageId: MessageId?) -> Signal { + return account.postbox.transaction { transaction -> (Peer?, Peer?) in + let peer = transaction.getPeer(peerId) + let author = transaction.getPeer(authorId) + + if peer.flatMap(apiInputPeer) != nil && author.flatMap(apiInputPeer) != nil { + let anchor: HistoryViewInputAnchor + if let aroundMessageId, aroundMessageId.peerId == peerId { + anchor = .message(aroundMessageId) + } else { + anchor = .upperBound + } + let historyView = transaction.getMessagesHistoryViewState(input: .single(peerId: peerId, threadId: nil), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 50, clipHoles: true, anchor: anchor, namespaces: .just(Set([Namespaces.Message.Cloud]))) + for entry in historyView.entries { + transaction.updateMessage(entry.message.id, update: { currentMessage in + var attributes = currentMessage.attributes + var updated = false + + for i in 0 ..< attributes.count { + guard let attribute = attributes[i] as? ReactionsMessageAttribute else { + continue + } + + let removedRecentPeers = attribute.recentPeers.filter { $0.peerId == authorId } + var updatedTopPeers = attribute.topPeers + var removedStarsTopPeerCount: Int32 = 0 + for j in (0 ..< updatedTopPeers.count).reversed() { + if updatedTopPeers[j].peerId == authorId { + removedStarsTopPeerCount += updatedTopPeers[j].count + updatedTopPeers.remove(at: j) + } + } + + if removedRecentPeers.isEmpty && removedStarsTopPeerCount == 0 { + continue + } + + var updatedReactions = attribute.reactions + for removedRecentPeer in removedRecentPeers { + if let index = updatedReactions.firstIndex(where: { $0.value == removedRecentPeer.value }) { + if removedRecentPeer.value != .stars || removedStarsTopPeerCount == 0 { + updatedReactions[index].count -= 1 + } + if removedRecentPeer.isMy { + updatedReactions[index].chosenOrder = nil + } + } + } + if removedStarsTopPeerCount != 0, let index = updatedReactions.firstIndex(where: { $0.value == .stars }) { + updatedReactions[index].count -= removedStarsTopPeerCount + } + for j in (0 ..< updatedReactions.count).reversed() { + if updatedReactions[j].count <= 0 { + updatedReactions.remove(at: j) + } + } + + let updatedRecentPeers = attribute.recentPeers.filter { $0.peerId != authorId } + let updatedAttribute = ReactionsMessageAttribute(canViewList: attribute.canViewList, isTags: attribute.isTags, reactions: updatedReactions, recentPeers: updatedRecentPeers, topPeers: updatedTopPeers) + if updatedAttribute != attribute { + attributes[i] = updatedAttribute + updated = true + } + } + + if !updated { + return .skip + } + + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + + var tags = currentMessage.tags + if attributes.contains(where: { ($0 as? ReactionsMessageAttribute)?.hasUnseen == true }) { + tags.insert(.unseenReaction) + } else { + tags.remove(.unseenReaction) + } + + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + } + + return (peer, author) + } + |> mapToSignal { peer, author in + guard let inputPeer = peer.flatMap(apiInputPeer), let inputAuthor = author.flatMap(apiInputPeer) else { + return .complete() + } + return account.network.request(Api.functions.messages.deleteParticipantReactions(peer: inputPeer, participant: inputAuthor)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + } +} + +func _internal_deleteReaction(account: Account, messageId: MessageId, authorId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> (Peer?, Peer?) in + let peer = transaction.getPeer(messageId.peerId) + let author = transaction.getPeer(authorId) + + if peer.flatMap(apiInputPeer) != nil && author.flatMap(apiInputPeer) != nil { + transaction.updateMessage(messageId, update: { currentMessage in + var attributes = currentMessage.attributes + var updated = false + + for i in 0 ..< attributes.count { + guard let attribute = attributes[i] as? ReactionsMessageAttribute else { + continue + } + + let removedRecentPeers = attribute.recentPeers.filter { $0.peerId == authorId } + var updatedTopPeers = attribute.topPeers + var removedStarsTopPeerCount: Int32 = 0 + for j in (0 ..< updatedTopPeers.count).reversed() { + if updatedTopPeers[j].peerId == authorId { + removedStarsTopPeerCount += updatedTopPeers[j].count + updatedTopPeers.remove(at: j) + } + } + + if removedRecentPeers.isEmpty && removedStarsTopPeerCount == 0 { + continue + } + + var updatedReactions = attribute.reactions + for removedRecentPeer in removedRecentPeers { + if let index = updatedReactions.firstIndex(where: { $0.value == removedRecentPeer.value }) { + if removedRecentPeer.value != .stars || removedStarsTopPeerCount == 0 { + updatedReactions[index].count -= 1 + } + if removedRecentPeer.isMy { + updatedReactions[index].chosenOrder = nil + } + } + } + if removedStarsTopPeerCount != 0, let index = updatedReactions.firstIndex(where: { $0.value == .stars }) { + updatedReactions[index].count -= removedStarsTopPeerCount + } + for j in (0 ..< updatedReactions.count).reversed() { + if updatedReactions[j].count <= 0 { + updatedReactions.remove(at: j) + } + } + + let updatedRecentPeers = attribute.recentPeers.filter { $0.peerId != authorId } + let updatedAttribute = ReactionsMessageAttribute(canViewList: attribute.canViewList, isTags: attribute.isTags, reactions: updatedReactions, recentPeers: updatedRecentPeers, topPeers: updatedTopPeers) + if updatedAttribute != attribute { + attributes[i] = updatedAttribute + updated = true + } + } + + if !updated { + return .skip + } + + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + + var tags = currentMessage.tags + if attributes.contains(where: { ($0 as? ReactionsMessageAttribute)?.hasUnseen == true }) { + tags.insert(.unseenReaction) + } else { + tags.remove(.unseenReaction) + } + + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + + return (peer, author) + } + |> mapToSignal { peer, author in + guard let inputPeer = peer.flatMap(apiInputPeer), let inputAuthor = author.flatMap(apiInputPeer) else { + return .complete() + } + return account.network.request(Api.functions.messages.deleteParticipantReaction(peer: inputPeer, msgId: messageId.id, participant: inputAuthor)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { updates -> Signal in + if let updates { + account.stateManager.addUpdates(updates) + } + return .complete() + } + } +} + func _internal_clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, threadId: Int64?, namespaces: MessageIdNamespaces) { if peerId.namespace == Namespaces.Peer.SecretChat { var resourceIds: [MediaResourceId] = [] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift index 95e6913650..ef127aa336 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift @@ -3,6 +3,38 @@ import Postbox import TelegramApi import SwiftSignalKit +public enum SendBotGameError { + case generic +} + +func _internal_sendBotGame(account: Account, botPeerId: PeerId, game: String, to peerId: PeerId, threadId: Int64?) -> Signal { + return account.postbox.transaction { transaction -> Signal in + guard !game.isEmpty, let botPeer = transaction.getPeer(botPeerId), let inputBot = apiInputUser(botPeer), let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else { + return .fail(.generic) + } + + var flags: Int32 = 1 << 7 + var replyTo: Api.InputReplyTo? + if let threadId { + flags |= 1 << 0 + let threadMessageId = Int32(clamping: threadId) + let replyFlags: Int32 = 1 << 0 + replyTo = .inputReplyToMessage(.init(flags: replyFlags, replyToMsgId: threadMessageId, topMsgId: threadMessageId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil, pollOption: nil)) + } + + return account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: .inputMediaGame(.init(id: .inputGameShortName(.init(botId: inputBot, shortName: game)))), message: "", randomId: Int64.random(in: Int64.min ... Int64.max), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, sendAs: nil, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil, suggestedPost: nil)) + |> mapError { _ -> SendBotGameError in + return .generic + } + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + } + |> castError(SendBotGameError.self) + |> switchToLatest +} + func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId, threadId: Int64?, as sendAsPeerId: PeerId?) -> Signal { return account.postbox.transaction { transaction -> Signal in if let _ = transaction.getMessage(messageId), let fromPeer = transaction.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = transaction.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift index b53c8606d3..5a936112a3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/InstallInteractiveReadMessagesAction.swift @@ -100,11 +100,13 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag for (_, index) in readMessageIndexByNamespace { if let threadId { + var newCountIsZero = false if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if index.id.id >= data.maxIncomingReadId { if let count = transaction.getThreadMessageCount(peerId: peerId, threadId: threadId, namespace: Namespaces.Message.Cloud, fromIdExclusive: data.maxIncomingReadId, toIndex: index) { data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count)) data.maxIncomingReadId = index.id.id + newCountIsZero = data.incomingUnreadCount == 0 } if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { @@ -124,6 +126,23 @@ func _internal_installInteractiveReadMessagesAction(postbox: Postbox, stateManag } } } + + if newCountIsZero, let peer = transaction.getPeer(peerId), peer.isForumOrMonoForum { + var allTopicsAreRead = true + for item in transaction.getMessageHistoryThreadIndex(peerId: peer.id, limit: 100) { + guard let data = transaction.getMessageHistoryThreadInfo(peerId: index.id.peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { + continue + } + if data.incomingUnreadCount != 0 { + allTopicsAreRead = false + break + } + } + + if allTopicsAreRead { + _internal_applyMaxReadIndexInteractively(transaction: transaction, stateManager: stateManager, index: index) + } + } } else { _internal_applyMaxReadIndexInteractively(transaction: transaction, stateManager: stateManager, index: index) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift index 30b173ed59..16dc9d92e2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Message.swift @@ -72,15 +72,28 @@ public final class EngineMessage: Equatable { public var text: String { return self.impl.text } + public var richText: RichTextMessageAttribute? { + return self.impl.richText + } public var attributes: [Attribute] { return self.impl.attributes } public var media: [Media] { return self.impl.media } + public var engineMedia: [EngineMedia] { + return self.impl.media.map(EngineMedia.init) + } public var peers: SimpleDictionary { return self.impl.peers } + public var enginePeers: [EnginePeer.Id: EnginePeer] { + var result: [EnginePeer.Id: EnginePeer] = [:] + for (id, peer) in self.impl.peers { + result[id] = EnginePeer(peer) + } + return result + } public var associatedMessages: SimpleDictionary { return self.impl.associatedMessages } @@ -228,3 +241,45 @@ public final class EngineMessage: Equatable { return true } } + +public extension EngineMessage { + var containsSecretMedia: Bool { + return self._asMessage().containsSecretMedia + } + + func effectiveReactions(isTags: Bool) -> [MessageReaction]? { + return self._asMessage().effectiveReactions(isTags: isTags) + } + + func areReactionsTags(accountPeerId: EnginePeer.Id) -> Bool { + return self._asMessage().areReactionsTags(accountPeerId: accountPeerId) + } + + var textEntitiesAttribute: TextEntitiesMessageAttribute? { + return self._asMessage().textEntitiesAttribute + } + + var adAttribute: AdMessageAttribute? { + return self._asMessage().adAttribute + } + + func effectivelyIncoming(_ accountPeerId: EnginePeer.Id) -> Bool { + return self._asMessage().effectivelyIncoming(accountPeerId) + } + + var pendingProcessingAttribute: PendingProcessingMessageAttribute? { + return self._asMessage().pendingProcessingAttribute + } + + var scheduleTime: Int32? { + return self._asMessage().scheduleTime + } + + var scheduleRepeatPeriod: Int32? { + return self._asMessage().scheduleRepeatPeriod + } + + var sourceAuthorInfo: SourceAuthorInfoMessageAttribute? { + return self._asMessage().sourceAuthorInfo + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index e7d30b530f..0809161243 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -145,9 +145,13 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: return .message(text: caption, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: []) } } - case let .text(text, entities, disableUrlPreview, previewParameters, replyMarkup): - if let entities = entities { - attributes.append(entities) + case let .text(text, entities, richText, disableUrlPreview, previewParameters, replyMarkup): + if let richText { + attributes.append(richText) + } else { + if let entities = entities { + attributes.append(entities) + } } if let replyMarkup = replyMarkup { attributes.append(replyMarkup) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index fc9335f722..921ab28308 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -24,6 +24,9 @@ func pollCloudMediaToInputMedia(_ media: Media) -> Api.InputMedia? { } else { return .inputMediaGeoPoint(.init(geoPoint: geoPoint)) } + } else if let webpage = media as? TelegramMediaWebpage, let url = webpage.content.url { + let flags: Int32 = 1 << 2 + return .inputMediaWebPage(.init(flags: flags, url: url)) } return nil } @@ -31,6 +34,7 @@ func pollCloudMediaToInputMedia(_ media: Media) -> Api.InputMedia? { public enum RequestMessageSelectPollOptionError { case generic + case restrictedToSubscribers } func _internal_requestMessageSelectPollOption(account: Account, messageId: MessageId, opaqueIdentifiers: [Data]) -> Signal { @@ -40,7 +44,10 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa |> mapToSignal { peer in if let inputPeer = apiInputPeer(peer) { return account.network.request(Api.functions.messages.sendVote(peer: inputPeer, msgId: messageId.id, options: opaqueIdentifiers.map { Buffer(data: $0) })) - |> mapError { _ -> RequestMessageSelectPollOptionError in + |> mapError { error -> RequestMessageSelectPollOptionError in + if error.errorDescription == "POLL_MEMBER_RESTRICTED" { + return .restrictedToSubscribers + } return .generic } |> mapToSignal { result -> Signal in @@ -58,7 +65,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa if let poll = poll { switch poll { case let .poll(pollData): - let (flags, question, answers, closePeriod, closeDate, pollHash) = (pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash) + let (flags, question, answers, closePeriod, closeDate, pollHash, countries) = (pollData.flags, pollData.question, pollData.answers, pollData.closePeriod, pollData.closeDate, pollData.hash, pollData.countriesIso2) let publicity: TelegramMediaPollPublicity if (flags & (1 << 1)) != 0 { publicity = .public @@ -75,6 +82,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa let revotingDisabled = (flags & (1 << 7)) != 0 let shuffleAnswers = (flags & (1 << 8)) != 0 let hideResultsUntilClose = (flags & (1 << 9)) != 0 + let restrictToSubscribers = (flags & (1 << 11)) != 0 let questionText: String let questionEntities: [MessageTextEntity] switch question { @@ -83,7 +91,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa questionText = text questionEntities = messageTextEntitiesFromApiEntities(entities) } - resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: resultPoll?.attachedMedia) + resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, deadlineDate: closeDate, pollHash: pollHash, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: resultPoll?.attachedMedia, restrictToSubscribers: restrictToSubscribers, countries: countries ?? []) } } @@ -217,10 +225,17 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager pollFlags |= 1 << 1 } var pollMediaFlags: Int32 = 0 - var correctAnswers: [Buffer]? + var correctAnswersIndices: [Int32]? if let correctAnswersValue = poll.correctAnswers { pollMediaFlags |= 1 << 0 - correctAnswers = correctAnswersValue.map { Buffer(data: $0) } + + var indices: [Int32] = [] + for i in 0 ..< poll.options.count { + if correctAnswersValue.contains(where: { poll.options[i].opaqueIdentifier == $0 }) { + indices.append(Int32(i)) + } + } + correctAnswersIndices = indices } pollFlags |= 1 << 0 @@ -228,10 +243,27 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager if poll.deadlineTimeout != nil { pollFlags |= 1 << 4 } - if poll.openAnswers { pollFlags |= 1 << 6 } - if poll.revotingDisabled { pollFlags |= 1 << 7 } - if poll.shuffleAnswers { pollFlags |= 1 << 8 } - if poll.hideResultsUntilClose { pollFlags |= 1 << 9 } + if poll.deadlineDate != nil { + pollFlags |= 1 << 5 + } + if poll.openAnswers { + pollFlags |= 1 << 6 + } + if poll.revotingDisabled { + pollFlags |= 1 << 7 + } + if poll.shuffleAnswers { + pollFlags |= 1 << 8 + } + if poll.hideResultsUntilClose { + pollFlags |= 1 << 9 + } + if poll.restrictToSubscribers { + pollFlags |= 1 << 11 + } + if !poll.countries.isEmpty { + pollFlags |= 1 << 12 + } var mappedSolution: String? var mappedSolutionEntities: [Api.MessageEntity]? @@ -240,12 +272,8 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager mappedSolutionEntities = apiTextAttributeEntities(TextEntitiesMessageAttribute(entities: solution.entities), associatedPeers: SimpleDictionary()) pollMediaFlags |= 1 << 1 } - - //TODO:fix - let _ = correctAnswers - let correctAnswerss: [Int32] = [] - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil, hash: 0)), correctAnswers: correctAnswerss, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: poll.deadlineDate, countriesIso2: poll.countries, hash: 0)), correctAnswers: correctAnswersIndices, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil, richMessage: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift index aaf771f1e3..b3c22b7c78 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift @@ -1085,11 +1085,17 @@ public final class TelegramAccountConnectedBot: Codable, Equatable { public let id: PeerId public let recipients: TelegramBusinessRecipients public let rights: TelegramBusinessBotRights + public let device: String? + public let date: Int32? + public let location: String? - public init(id: PeerId, recipients: TelegramBusinessRecipients, rights: TelegramBusinessBotRights) { + public init(id: PeerId, recipients: TelegramBusinessRecipients, rights: TelegramBusinessBotRights, device: String? = nil, date: Int32? = nil, location: String? = nil) { self.id = id self.recipients = recipients self.rights = rights + self.device = device + self.date = date + self.location = location } public static func ==(lhs: TelegramAccountConnectedBot, rhs: TelegramAccountConnectedBot) -> Bool { @@ -1105,8 +1111,34 @@ public final class TelegramAccountConnectedBot: Codable, Equatable { if lhs.rights != rhs.rights { return false } + if lhs.device != rhs.device { + return false + } + if lhs.date != rhs.date { + return false + } + if lhs.location != rhs.location { + return false + } return true } + + fileprivate func preservingConnectionMetadata(from current: TelegramAccountConnectedBot?) -> TelegramAccountConnectedBot { + guard let current, current.id == self.id else { + return self + } + if self.device != nil || self.date != nil || self.location != nil { + return self + } + return TelegramAccountConnectedBot( + id: self.id, + recipients: self.recipients, + rights: self.rights, + device: current.device, + date: current.date, + location: current.location + ) + } } public struct TelegramBusinessBotRights: OptionSet, Codable { @@ -1251,7 +1283,7 @@ public func _internal_setAccountConnectedBot(account: Account, bot: TelegramAcco return account.postbox.transaction { transaction in transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in var current = (current as? CachedUserData) ?? CachedUserData() - current = current.withUpdatedConnectedBot(bot) + current = current.withUpdatedConnectedBot(bot?.preservingConnectionMetadata(from: current.connectedBot)) return current }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift index 404693261f..1d6e1927c1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReplyThreadHistory.swift @@ -339,7 +339,6 @@ private class ReplyThreadHistoryContextImpl { } var markMainAsRead = false - markMainAsRead = !"".isEmpty if var data = transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { if messageIndex.id.id >= data.maxIncomingReadId { @@ -348,6 +347,7 @@ private class ReplyThreadHistoryContextImpl { data.maxIncomingReadId = messageIndex.id.id } + var newCountIsZero = false if let topMessageIndex = transaction.getMessageHistoryThreadTopMessage(peerId: peerId, threadId: threadId, namespaces: Set([Namespaces.Message.Cloud])) { if messageIndex.id.id >= topMessageIndex.id.id { let containingHole = transaction.getThreadIndexHole(peerId: peerId, threadId: threadId, namespace: topMessageIndex.id.namespace, containing: topMessageIndex.id.id) @@ -357,6 +357,24 @@ private class ReplyThreadHistoryContextImpl { } } } + newCountIsZero = data.incomingUnreadCount == 0 + + if newCountIsZero, let peer = transaction.getPeer(peerId), peer.isForumOrMonoForum { + var allTopicsAreRead = true + for item in transaction.getMessageHistoryThreadIndex(peerId: peer.id, limit: 100) { + guard let data = transaction.getMessageHistoryThreadInfo(peerId: messageIndex.id.peerId, threadId: item.threadId)?.data.get(MessageHistoryThreadData.self) else { + continue + } + if data.incomingUnreadCount != 0 { + allTopicsAreRead = false + break + } + } + + if allTopicsAreRead { + markMainAsRead = true + } + } data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReportContent.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReportContent.swift index 4334c6ace5..bf261814fa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReportContent.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ReportContent.swift @@ -21,13 +21,13 @@ public enum ReportContentError { } public enum ReportContentSubject: Equatable { - case peer(EnginePeer.Id) + case peer(EnginePeer.Id, sourceMessageId: EngineMessage.Id? = nil) case messages([EngineMessage.Id]) case stories(EnginePeer.Id, [Int32]) public var peerId: EnginePeer.Id { switch self { - case let .peer(peerId): + case let .peer(peerId, _): return peerId case let .messages(messageIds): return messageIds.first!.peerId @@ -39,7 +39,13 @@ public enum ReportContentSubject: Equatable { func _internal_reportContent(account: Account, subject: ReportContentSubject, option: Data?, message: String?) -> Signal { return account.postbox.transaction { transaction -> Signal in - guard let peer = transaction.getPeer(subject.peerId), let inputPeer = apiInputPeer(peer) else { + let sourceMessageId: MessageId? + if case let .peer(_, messageId) = subject { + sourceMessageId = messageId + } else { + sourceMessageId = nil + } + guard let peer = transaction.getPeer(subject.peerId), let inputPeer = apiInputPeer(peer, sourceMessageId: sourceMessageId, transaction: transaction) else { return .fail(.generic) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift index 47fae09fa3..bd93076922 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/RequestStartBot.swift @@ -58,11 +58,11 @@ func _internal_requestStartBotInGroup(account: Account, botPeerId: PeerId, group |> mapToSignal { participant -> Signal in return account.postbox.transaction { transaction -> StartBotInGroupResult in if let participant = participant, let peer = transaction.getPeer(participant.peerId) { - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] let presences: [PeerId: PeerPresence] = [:] - - peers[peer.id] = peer - return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: presences)) + + peers[peer.id] = EnginePeer(peer) + return .channelParticipant(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: presences)) } else { return .none } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index f385472317..f67efe8a1c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -6,8 +6,7 @@ import MtProtoKit public enum SearchMessagesLocation: Equatable { - case general(scope: TelegramSearchPeersScope, tags: MessageTags?, minDate: Int32?, maxDate: Int32?, folderId: Int32?) - case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) + case general(scope: TelegramSearchPeersScope, groupId: PeerGroupId?, tags: MessageTags?, minDate: Int32?, maxDate: Int32?, folderId: Int32?) case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, reactions: [MessageReaction.Reaction]?, threadId: Int64?, minDate: Int32?, maxDate: Int32?) case sentMedia(tags: MessageTags?) } @@ -471,17 +470,17 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } return combineLatest(peerMessages, additionalPeerMessages) } - case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate): + case let .general(_, groupId, tags, minDate, maxDate, _): var flags: Int32 = 0 let folderId: Int32? - if case let .group(groupId, _, _, _) = location { + if let groupId { folderId = groupId.rawValue flags |= (1 << 0) } else { folderId = nil } - if case let .general(scope, _, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope { + if case let .general(scope, _, _, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope { remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in var lowerBound: MessageIndex? if let state = state, let message = state.main.messages.last { @@ -508,7 +507,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation } } } else { - if case let .general(scope, _, _, _, _) = location { + if case let .general(scope, _, _, _, _, _) = location { switch scope { case .everywhere: break @@ -567,9 +566,9 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation if state?.additional == nil { switch location { - case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate): + case let .general(_, _, tags, minDate, maxDate, _): let secretMessages: [Message] - if case let .general(scope, _, _, _, _) = location, case .channels = scope { + if case let .general(scope, _, _, _, _, _) = location, case .channels = scope { secretMessages = [] } else { secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift index 67aa4096db..a0603c10be 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SendAsPeers.swift @@ -5,19 +5,15 @@ import TelegramApi import MtProtoKit public struct SendAsPeer: Equatable { - public let peer: Peer + public let peer: EnginePeer public let subscribers: Int32? public let isPremiumRequired: Bool - - public init(peer: Peer, subscribers: Int32?, isPremiumRequired: Bool) { + + public init(peer: EnginePeer, subscribers: Int32?, isPremiumRequired: Bool) { self.peer = peer self.subscribers = subscribers self.isPremiumRequired = isPremiumRequired } - - public static func ==(lhs: SendAsPeer, rhs: SendAsPeer) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers && lhs.isPremiumRequired == rhs.isPremiumRequired - } } public final class CachedSendAsPeers: Codable { @@ -61,7 +57,7 @@ func _internal_cachedPeerSendAsAvailablePeers(account: Account, peerId: PeerId) if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) } } return (peers, cached.timestamp) @@ -167,7 +163,7 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network, peers.append(peer) } } - return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } } } } @@ -233,7 +229,7 @@ func _internal_cachedLiveStorySendAsAvailablePeers(account: Account, peerId: Pee if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { subscribers = cachedData.participantsSummary.memberCount } - peers.append(SendAsPeer(peer: peer, subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) + peers.append(SendAsPeer(peer: EnginePeer(peer), subscribers: subscribers, isPremiumRequired: cached.premiumRequiredPeerIds.contains(peer.id))) } } return (peers, cached.timestamp) @@ -327,7 +323,7 @@ func _internal_liveStorySendAsAvailablePeers(account: Account, peerId: PeerId) - peers.append(peer) } } - return peers.map { SendAsPeer(peer: $0, subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } + return peers.map { SendAsPeer(peer: EnginePeer($0), subscribers: subscribers[$0.id], isPremiumRequired: premiumRequiredPeerIds.contains($0.id)) } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 5740b8b987..babcdf2ff3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -141,8 +141,11 @@ public extension TelegramEngine { return _internal_searchHashtagPosts(account: self.account, hashtag: hashtag, state: state, limit: limit) } - public func downloadMessage(messageId: MessageId) -> Signal { + public func downloadMessage(messageId: EngineMessage.Id) -> Signal { return _internal_downloadMessage(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, messageId: messageId) + |> map { message -> EngineMessage? in + return message.flatMap(EngineMessage.init) + } } public func searchMessageIdByTimestamp(peerId: PeerId, threadId: Int64?, timestamp: Int32) -> Signal { @@ -166,6 +169,14 @@ public extension TelegramEngine { } |> ignoreValues } + + public func deleteAllReactionsWithAuthor(peerId: EnginePeer.Id, authorId: EnginePeer.Id, aroundMessageId: EngineMessage.Id? = nil) -> Signal { + return _internal_deleteAllReactionsWithAuthor(account: self.account, peerId: peerId, authorId: authorId, aroundMessageId: aroundMessageId) + } + + public func deleteReaction(messageId: EngineMessage.Id, authorId: EnginePeer.Id) -> Signal { + return _internal_deleteReaction(account: self.account, messageId: messageId, authorId: authorId) + } public func clearCallHistory(forEveryone: Bool) -> Signal { return _internal_clearCallHistory(account: self.account, forEveryone: forEveryone) @@ -191,8 +202,8 @@ public extension TelegramEngine { return _internal_clearAuthorHistory(account: self.account, peerId: peerId, memberId: memberId) } - public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute? = nil) -> Signal { - return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) + public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, richText: RichTextMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute? = nil) -> Signal { + return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, richText: richText, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) } public func requestEditLiveLocation(messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?, extendPeriod: Int32?) -> Signal { @@ -208,6 +219,10 @@ public extension TelegramEngine { return _internal_forwardGameWithScore(account: self.account, messageId: messageId, to: peerId, threadId: threadId, as: senderPeerId) } + public func sendBotGame(botPeerId: PeerId, game: String, to peerId: PeerId, threadId: Int64?) -> Signal { + return _internal_sendBotGame(account: self.account, botPeerId: botPeerId, game: game, to: peerId, threadId: threadId) + } + public func requestUpdatePinnedMessage(peerId: PeerId, update: PinnedMessageUpdate) -> Signal { return _internal_requestUpdatePinnedMessage(account: self.account, peerId: peerId, update: update) } @@ -461,8 +476,11 @@ public extension TelegramEngine { return _internal_recentlyUsedHashtags(postbox: self.account.postbox) } - public func topPeerActiveLiveLocationMessages(peerId: PeerId) -> Signal<(Peer?, [Message]), NoError> { + public func topPeerActiveLiveLocationMessages(peerId: EnginePeer.Id) -> Signal<(EnginePeer?, [EngineMessage]), NoError> { return _internal_topPeerActiveLiveLocationMessages(viewTracker: self.account.viewTracker, accountPeerId: self.account.peerId, peerId: peerId) + |> map { peer, messages -> (EnginePeer?, [EngineMessage]) in + return (peer.flatMap(EnginePeer.init), messages.map(EngineMessage.init)) + } } public func chatList(group: EngineChatList.Group, count: Int) -> Signal { @@ -655,8 +673,32 @@ public extension TelegramEngine { } } - public func composeMessageWithAI(text: String, entities: [MessageTextEntity], proofread: Bool = false, translateToLang: String? = nil, changeTone: String? = nil, emojify: Bool = false) -> Signal<(String, [MessageTextEntity]), TranslationError> { - return _internal_composeMessageWithAI(network: self.account.network, text: text, entities: entities, proofread: proofread, translateToLang: translateToLang, changeTone: changeTone, emojify: emojify) + public func composeMessageWithAI(text: String, entities: [MessageTextEntity], proofread: Bool = false, translateToLang: String? = nil, changeStyle: TelegramComposeAIMessageMode.CloudStyle.Reference? = nil, emojify: Bool = false) -> Signal<(String, [MessageTextEntity]), TranslationError> { + return _internal_composeMessageWithAI(account: self.account, text: text, entities: entities, proofread: proofread, translateToLang: translateToLang, changeStyle: changeStyle, emojify: emojify) + } + + public func createAITextStyle(displayAuthor: Bool, emojiFileId: Int64, title: String, prompt: String) -> Signal { + return _internal_createAITextStyle(account: self.account, displayAuthor: displayAuthor, emojiFileId: emojiFileId, title: title, prompt: prompt) + } + + public func editAITextStyle(id: Int64, accessHash: Int64, displayAuthor: Bool, emojiFileId: Int64, title: String, prompt: String) -> Signal { + return _internal_editAITextStyle(account: self.account, id: id, accessHash: accessHash, displayAuthor: displayAuthor, emojiFileId: emojiFileId, title: title, prompt: prompt) + } + + public func deleteAITextStyle(id: Int64, accessHash: Int64) -> Signal { + return _internal_deleteAITextStyle(account: self.account, id: id, accessHash: accessHash) + } + + public func unsaveAITextStyle(id: Int64, accessHash: Int64) -> Signal { + return _internal_unsaveAITextStyle(account: self.account, id: id, accessHash: accessHash) + } + + public func getAIComposeToneExample(slug: String, num: Int32) -> Signal { + return _internal_getAIComposeToneExample(network: self.account.network, slug: slug, num: num) + } + + public func getAIComposeToneExample(reference: TelegramComposeAIMessageMode.CloudStyle.Reference, num: Int32) -> Signal { + return _internal_getAIComposeToneExample(network: self.account.network, tone: reference, num: num) } public func translate(texts: [(String, [MessageTextEntity])], toLang: String, tone: TranslationTone = .neutral) -> Signal<[(String, [MessageTextEntity])], TranslationError> { @@ -919,12 +961,6 @@ public extension TelegramEngine { |> ignoreValues } - public func getSynchronizeAutosaveItemOperations() -> Signal<[(index: Int32, message: Message, mediaId: MediaId)], NoError> { - return self.account.postbox.transaction { transaction -> [(index: Int32, message: Message, mediaId: MediaId)] in - return _internal_getSynchronizeAutosaveItemOperations(transaction: transaction) - } - } - func removeSyncrhonizeAutosaveItemOperations(indices: [Int32]) { let _ = (self.account.postbox.transaction { transaction -> Void in _internal_removeSyncrhonizeAutosaveItemOperations(transaction: transaction, indices: indices) @@ -1788,14 +1824,29 @@ public extension TelegramEngine { |> ignoreValues } - public func composeAIMessageStyles() -> Signal<[TelegramComposeAIMessageMode.Style], NoError> { - return _internal_composeAIMessageStyles(account: self.account) + public func composeAIMessageStyles() -> Signal<[TelegramComposeAIMessageMode.CloudStyle], NoError> { + return _internal_cachedCloudAITextStyles(postbox: self.account.postbox) + |> map { value in + return value?.items ?? [] + } + } + + public func requestAIMessageStyle(slug: String) -> Signal<(style: TelegramComposeAIMessageMode.CloudStyle, initialPreview: AIMessageStylePreview?)?, NoError> { + return _internal_requestAIMessageStyle(account: self.account, slug: slug) + } + + public func installAIMessageStyle(style: TelegramComposeAIMessageMode.CloudStyle.Custom) -> Signal { + return _internal_installAIMessageStyle(account: self.account, style: style) } public func composeAIMessage(text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal { return _internal_composeAIMessage(account: self.account, text: text, mode: mode) } + public func requestAIMessageStylePreview(reference: TelegramComposeAIMessageMode.CloudStyle.Reference, index: Int) -> Signal { + return _internal_requestAIMessageStylePreview(account: self.account, reference: reference, index: index) + } + public func requestMiniAppButton(peerId: EnginePeer.Id, requestId: String) -> Signal { let account = self.account return self.account.postbox.transaction { transaction -> Api.InputUser? in @@ -1815,6 +1866,78 @@ public extension TelegramEngine { } } } + + public func requestFullRichText(id: EngineMessage.Id) -> Signal { + let account = self.account + return self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(id.peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { + return .single(nil) + } + if id.namespace != Namespaces.Message.Cloud { + return .single(nil) + } + return account.network.request(Api.functions.messages.getRichMessage(peer: inputPeer, id: id.id)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .single(nil) + } + + let messages: [Api.Message] + let users: [Api.User] + let chats: [Api.Chat] + switch result { + case let .channelMessages(channelMessages): + messages = channelMessages.messages + users = channelMessages.users + chats = channelMessages.chats + case let .messages(messagesValue): + messages = messagesValue.messages + users = messagesValue.users + chats = messagesValue.chats + case .messagesNotModified: + return .single(nil) + case let .messagesSlice(messagesSlice): + messages = messagesSlice.messages + users = messagesSlice.users + chats = messagesSlice.chats + } + + return account.postbox.transaction { transaction -> RichTextMessageAttribute? in + var peerIsForum = false + if let peer = transaction.getPeer(id.peerId), peer.isForum { + peerIsForum = true + } + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(chats: chats, users: users)) + + if let apiMessage = messages.first, let storeMessage = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: peerIsForum) { + transaction.updateMessage(id, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + var updatedAttributes = currentMessage.attributes + if let updatedRichAttribute = storeMessage.attributes.first(where: { $0 is RichTextMessageAttribute }) as? RichTextMessageAttribute { + if let index = updatedAttributes.firstIndex(where: { $0 is RichTextMessageAttribute }), let previous = updatedAttributes[index] as? RichTextMessageAttribute { + updatedAttributes[index] = RichTextMessageAttribute(instantPage: previous.instantPage, fullInstantPage: updatedRichAttribute.instantPage) + } + } + + return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: updatedAttributes, media: currentMessage.media)) + }) + } + + return transaction.getMessage(id)?.attributes.first(where: { $0 is RichTextMessageAttribute }) as? RichTextMessageAttribute + } + } + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift index 42cd3a48d1..3f0d2e8462 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Translate.swift @@ -68,7 +68,7 @@ func _internal_translate(network: Network, text: String, toLang: String, entitie } } -func _internal_composeMessageWithAI(network: Network, text: String, entities: [MessageTextEntity], proofread: Bool = false, translateToLang: String? = nil, changeTone: String? = nil, emojify: Bool = false) -> Signal<(String, [MessageTextEntity]), TranslationError> { +func _internal_composeMessageWithAI(account: Account, text: String, entities: [MessageTextEntity], proofread: Bool = false, translateToLang: String? = nil, changeStyle: TelegramComposeAIMessageMode.CloudStyle.Reference? = nil, emojify: Bool = false) -> Signal<(String, [MessageTextEntity]), TranslationError> { var flags: Int32 = 0 if proofread { flags |= (1 << 0) @@ -76,8 +76,11 @@ func _internal_composeMessageWithAI(network: Network, text: String, entities: [M if translateToLang != nil { flags |= (1 << 1) } - if changeTone != nil { + + var changeTone: Api.InputAiComposeTone? + if let changeStyle { flags |= (1 << 2) + changeTone = changeStyle.apiInputStyle } if emojify { flags |= (1 << 3) @@ -85,7 +88,7 @@ func _internal_composeMessageWithAI(network: Network, text: String, entities: [M let apiText: Api.TextWithEntities = .textWithEntities(.init(text: text, entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()))) - return network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: apiText, translateToLang: translateToLang, changeTone: changeTone)) + return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: apiText, translateToLang: translateToLang, tone: changeTone)) |> mapError { error -> TranslationError in if error.errorDescription.hasPrefix("FLOOD_WAIT") { return .limitExceeded @@ -409,32 +412,289 @@ func _internal_togglePeerMessagesTranslationHidden(account: Account, peerId: Eng } public enum TelegramComposeAIMessageMode { - public enum StyleId: Hashable { - case neutral - case style(String) - } - - public struct Style: Equatable { - public let type: String - public let title: String - public let emojiFileId: Int64? + public final class CloudStyle: Equatable, Codable { + public enum Id: Hashable { + case standard(String) + case custom(Int64) + } + + public enum Reference: Hashable { + case standard(String) + case custom(id: Int64, accessHash: Int64) + + public var id: Id { + switch self { + case let .standard(type): + return .standard(type) + case let .custom(id, _): + return .custom(id) + } + } + } + + public final class Standard: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case type + case title + case emojiFileId + } + + public let type: String + public let title: String + public let emojiFileId: Int64? + + public init(type: String, title: String, emojiFileId: Int64?) { + self.type = type + self.title = title + self.emojiFileId = emojiFileId + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.type = try container.decode(String.self, forKey: .type) + self.title = try container.decode(String.self, forKey: .title) + self.emojiFileId = try container.decodeIfPresent(Int64.self, forKey: .emojiFileId) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.type, forKey: .type) + try container.encode(self.title, forKey: .title) + try container.encodeIfPresent(self.emojiFileId, forKey: .emojiFileId) + } + + public static func ==(lhs: Standard, rhs: Standard) -> Bool { + if lhs.type != rhs.type { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.emojiFileId != rhs.emojiFileId { + return false + } + return true + } + } + + public final class Custom: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case isCreator + case id + case accessHash + case slug + case emojiFileId + case title + case prompt + case userCount + case authorId + } + + public let isCreator: Bool + public let id: Int64 + public let accessHash: Int64 + public let slug: String + public let emojiFileId: Int64? + public let title: String + public let prompt: String? + public let userCount: Int? + public let authorId: EnginePeer.Id? + + public init(isCreator: Bool, id: Int64, accessHash: Int64, slug: String, emojiFileId: Int64?, title: String, prompt: String?, userCount: Int?, authorId: EnginePeer.Id?) { + self.isCreator = isCreator + self.id = id + self.accessHash = accessHash + self.slug = slug + self.emojiFileId = emojiFileId + self.title = title + self.prompt = prompt + self.userCount = userCount + self.authorId = authorId + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.isCreator = try container.decode(Bool.self, forKey: .isCreator) + self.id = try container.decode(Int64.self, forKey: .id) + self.accessHash = try container.decode(Int64.self, forKey: .accessHash) + self.slug = try container.decode(String.self, forKey: .slug) + self.emojiFileId = try container.decodeIfPresent(Int64.self, forKey: .emojiFileId) + self.title = try container.decode(String.self, forKey: .title) + self.prompt = try container.decodeIfPresent(String.self, forKey: .prompt) + self.userCount = try container.decodeIfPresent(Int32.self, forKey: .userCount).flatMap(Int.init) + self.authorId = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .authorId) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.isCreator, forKey: .isCreator) + try container.encode(self.id, forKey: .id) + try container.encode(self.accessHash, forKey: .accessHash) + try container.encode(self.slug, forKey: .slug) + try container.encodeIfPresent(self.emojiFileId, forKey: .emojiFileId) + try container.encode(self.title, forKey: .title) + try container.encodeIfPresent(self.prompt, forKey: .prompt) + try container.encodeIfPresent(self.userCount.flatMap(Int32.init(clamping:)), forKey: .userCount) + try container.encodeIfPresent(self.authorId, forKey: .authorId) + } + + public static func ==(lhs: Custom, rhs: Custom) -> Bool { + if lhs.isCreator != rhs.isCreator { + return false + } + if lhs.id != rhs.id { + return false + } + if lhs.accessHash != rhs.accessHash { + return false + } + if lhs.slug != rhs.slug { + return false + } + if lhs.emojiFileId != rhs.emojiFileId { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.prompt != rhs.prompt { + return false + } + if lhs.userCount != rhs.userCount { + return false + } + if lhs.authorId != rhs.authorId { + return false + } + return true + } + } + + public enum Content: Equatable, Codable { + private enum CodingKeys: String, CodingKey { + case discriminator = "d" + case value = "v" + } + + case standard(Standard) + case custom(Custom) + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + switch try container.decode(Int32.self, forKey: .discriminator) { + case 0: + self = .standard(try container.decode(Standard.self, forKey: .value)) + case 1: + self = .custom(try container.decode(Custom.self, forKey: .value)) + default: + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "")) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case let .standard(standard): + try container.encode(0 as Int32, forKey: .discriminator) + try container.encode(standard, forKey: .value) + case let .custom(custom): + try container.encode(1 as Int32, forKey: .discriminator) + try container.encode(custom, forKey: .value) + } + } + } + + public let content: Content public var id: StyleId { - return .style(self.type) + switch self.content { + case let .standard(standard): + return .style(.standard(standard.type)) + case let .custom(custom): + return .style(.custom(custom.id)) + } } - public init(type: String, title: String, emojiFileId: Int64?) { - self.type = type - self.title = title - self.emojiFileId = emojiFileId + public var reference: Reference { + switch self.content { + case let .standard(standard): + return .standard(standard.type) + case let .custom(custom): + return .custom(id: custom.id, accessHash: custom.accessHash) + } + } + + public init(content: Content) { + self.content = content + } + + public static func ==(lhs: CloudStyle, rhs: CloudStyle) -> Bool { + if lhs.content != rhs.content { + return false + } + return true } } - case translate(toLanguage: String, emojify: Bool, style: StyleId) - case stylize(emojify: Bool, style: StyleId) + public enum StyleId: Hashable { + case neutral + case style(CloudStyle.Id) + } + + public enum StyleReference: Hashable { + case neutral + case style(CloudStyle.Reference) + + public var id: StyleId { + switch self { + case .neutral: + return .neutral + case let .style(style): + return .style(style.id) + } + } + } + + case translate(toLanguage: String, emojify: Bool, style: StyleReference) + case stylize(emojify: Bool, style: StyleReference) case proofread } +extension TelegramComposeAIMessageMode.CloudStyle { + convenience init(apiTone: Api.AiComposeTone) { + /* + aiComposeTone#12ea1465 flags:# creator:flags.0?true id:long access_hash:long slug:string title:string emoji_id:flags.1?long prompt:string installs_count:flags.2?int author_id:flags.3?long = AiComposeTone; + aiComposeToneDefault#9bad6414 tone:string emoji_id:long title:string = AiComposeTone; + */ + switch apiTone { + case let .aiComposeTone(aiComposeTone): + self.init(content: .custom(Custom( + isCreator: (aiComposeTone.flags & (1 << 0)) != 0, + id: aiComposeTone.id, + accessHash: aiComposeTone.accessHash, + slug: aiComposeTone.slug, + emojiFileId: aiComposeTone.emojiId, + title: aiComposeTone.title, + prompt: aiComposeTone.prompt, + userCount: aiComposeTone.installsCount.flatMap(Int.init), + authorId: aiComposeTone.authorId.flatMap { id -> EnginePeer.Id in + return EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)) + } + ))) + case let .aiComposeToneDefault(aiComposeToneDefault): + self.init(content: .standard(Standard( + type: aiComposeToneDefault.tone, + title: aiComposeToneDefault.title, + emojiFileId: aiComposeToneDefault.emojiId + ))) + } + } +} + public final class TelegramAIComposeMessageResult { public let text: TextWithEntities public let diffRanges: [Range] @@ -454,23 +714,14 @@ extension TextWithEntities { } } -func _internal_composeAIMessageStyles(account: Account) -> Signal<[TelegramComposeAIMessageMode.Style], NoError> { - return account.postbox.transaction { transaction -> [TelegramComposeAIMessageMode.Style] in - var result: [TelegramComposeAIMessageMode.Style] = [] - if let data = currentAppConfiguration(transaction: transaction).data, let value = data["ai_compose_styles"] as? [[String]] { - for item in value { - if item.count >= 3 { - let emojiFileId: Int64? = Int64(item[1]) - result.append(TelegramComposeAIMessageMode.Style(type: item[0], title: item[2], emojiFileId: emojiFileId)) - } - } +extension TelegramComposeAIMessageMode.CloudStyle.Reference { + var apiInputStyle: Api.InputAiComposeTone { + switch self { + case let .standard(type): + return .inputAiComposeToneDefault(Api.InputAiComposeTone.Cons_inputAiComposeToneDefault(tone: type)) + case let .custom(id, accessHash): + return .inputAiComposeToneID(Api.InputAiComposeTone.Cons_inputAiComposeToneID(id: id, accessHash: accessHash)) } - #if DEBUG && false - for item in Array(result) { - result.append(TelegramComposeAIMessageMode.Style(name: item.name + "_", emoji: item.emoji)) - } - #endif - return result } } @@ -482,7 +733,7 @@ public enum TelegramAIComposeMessageError { func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: TelegramComposeAIMessageMode) -> Signal { var flags: Int32 = 0 var translateToLang: String? - var changeTone: String? + var changeTone: Api.InputAiComposeTone? switch mode { case let .translate(toLanguage, emojify, style): translateToLang = toLanguage @@ -492,8 +743,8 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: flags |= (1 << 3) } - if case let .style(name) = style { - changeTone = name + if case let .style(reference) = style { + changeTone = reference.apiInputStyle flags |= (1 << 2) } case let .stylize(emojify, style): @@ -501,8 +752,8 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: flags |= (1 << 3) } - if case let .style(name) = style { - changeTone = name + if case let .style(reference) = style { + changeTone = reference.apiInputStyle flags |= (1 << 2) } case .proofread: @@ -511,7 +762,7 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: let inputText: Api.TextWithEntities = .textWithEntities(Api.TextWithEntities.Cons_textWithEntities(text: text.text, entities: apiEntitiesFromMessageTextEntities(text.entities, associatedPeers: SimpleDictionary()))) - return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: inputText, translateToLang: translateToLang, changeTone: changeTone)) + return account.network.request(Api.functions.messages.composeMessageWithAI(flags: flags, text: inputText, translateToLang: translateToLang, tone: changeTone)) |> `catch` { error -> Signal in if error.errorDescription == "AICOMPOSE_FLOOD_PREMIUM" { return .fail(.nonPremiumFlood) @@ -549,3 +800,325 @@ func _internal_composeAIMessage(account: Account, text: TextWithEntities, mode: } } } + +public final class AIMessageStylePreview { + public let from: TextWithEntities + public let to: TextWithEntities + public let index: Int? + + public init(from: TextWithEntities, to: TextWithEntities, index: Int?) { + self.from = from + self.to = to + self.index = index + } +} + +extension AIMessageStylePreview { + convenience init(apiPreview: Api.AiComposeToneExample, index: Int) { + switch apiPreview { + case let .aiComposeToneExample(aiComposeToneExample): + self.init(from: TextWithEntities(apiValue: aiComposeToneExample.from), to: TextWithEntities(apiValue: aiComposeToneExample.to), index: index) + } + } +} + +public func _internal_requestAIMessageStylePreview(account: Account, reference: TelegramComposeAIMessageMode.CloudStyle.Reference, index: Int) -> Signal { + return account.network.request(Api.functions.aicompose.getToneExample(tone: reference.apiInputStyle, num: Int32(index))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> map { result -> AIMessageStylePreview? in + guard let result else { + return nil + } + return AIMessageStylePreview( + apiPreview: result, + index: index + ) + } +} + +public enum CreateAITextStyleError { + case generic + case premiumRequired +} + +func _internal_createAITextStyle(account: Account, displayAuthor: Bool, emojiFileId: Int64, title: String, prompt: String) -> Signal { + var flags: Int32 = 0 + if displayAuthor { + flags |= (1 << 0) + } + return account.network.request(Api.functions.aicompose.createTone( + flags: flags, + emojiId: emojiFileId, + title: title, + prompt: prompt + )) + |> mapError { error -> CreateAITextStyleError in + if error.errorDescription == "TONES_SAVED_TOO_MANY" { + return .premiumRequired + } + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> TelegramComposeAIMessageMode.CloudStyle in + let style = TelegramComposeAIMessageMode.CloudStyle(apiTone: result) + + let styles = _internal_cachedCloudAITextStyles(transaction: transaction) + var items = styles?.items ?? [] + items.insert(style, at: 0) + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0)) + return style + } + |> castError(CreateAITextStyleError.self) + } +} + +public enum EditAITextStyleError { + case generic +} + +func _internal_editAITextStyle(account: Account, id: Int64, accessHash: Int64, displayAuthor: Bool, emojiFileId: Int64, title: String, prompt: String) -> Signal { + var flags: Int32 = 0 + flags |= (1 << 0) + flags |= (1 << 1) + flags |= (1 << 2) + flags |= (1 << 3) + return account.network.request(Api.functions.aicompose.updateTone( + flags: flags, + tone: .inputAiComposeToneID(Api.InputAiComposeTone.Cons_inputAiComposeToneID(id: id, accessHash: accessHash)), + displayAuthor: displayAuthor ? .boolTrue : .boolFalse, + emojiId: emojiFileId, + title: title, + prompt: prompt + )) + |> mapError { _ -> EditAITextStyleError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> TelegramComposeAIMessageMode.CloudStyle in + let style = TelegramComposeAIMessageMode.CloudStyle(apiTone: result) + + let styles = _internal_cachedCloudAITextStyles(transaction: transaction) + var items = styles?.items ?? [] + if let index = items.firstIndex(where: { $0.id == .style(.custom(id)) }) { + items[index] = style + } + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0)) + return style + } + |> castError(EditAITextStyleError.self) + } +} + +public struct TelegramAIComposeToneExample: Equatable { + public let from: TextWithEntities + public let to: TextWithEntities + public init(from: TextWithEntities, to: TextWithEntities) { + self.from = from + self.to = to + } +} + +private func _internal_mapToneExample(_ apiExample: Api.AiComposeToneExample) -> TelegramAIComposeToneExample { + switch apiExample { + case let .aiComposeToneExample(data): + return TelegramAIComposeToneExample(from: TextWithEntities(apiValue: data.from), to: TextWithEntities(apiValue: data.to)) + } +} + +func _internal_getAIComposeToneExample(network: Network, tone: TelegramComposeAIMessageMode.CloudStyle.Reference, num: Int32) -> Signal { + return network.request(Api.functions.aicompose.getToneExample(tone: tone.apiInputStyle, num: num)) + |> map { apiExample -> TelegramAIComposeToneExample? in + return _internal_mapToneExample(apiExample) + } + |> `catch` { _ -> Signal in + return .single(nil) + } +} + +func _internal_getAIComposeToneExample(network: Network, slug: String, num: Int32) -> Signal { + return network.request(Api.functions.aicompose.getToneExample(tone: .inputAiComposeToneSlug(Api.InputAiComposeTone.Cons_inputAiComposeToneSlug(slug: slug)), num: num)) + |> map { apiExample -> TelegramAIComposeToneExample? in + return _internal_mapToneExample(apiExample) + } + |> `catch` { _ -> Signal in + return .single(nil) + } +} + +public enum DeleteAITextStyleError { + case generic +} + +func _internal_deleteAITextStyle(account: Account, id: Int64, accessHash: Int64) -> Signal { + return account.network.request(Api.functions.aicompose.deleteTone(tone: .inputAiComposeToneID(Api.InputAiComposeTone.Cons_inputAiComposeToneID(id: id, accessHash: accessHash)))) + |> mapError { _ -> DeleteAITextStyleError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> Void in + let styles = _internal_cachedCloudAITextStyles(transaction: transaction) + var items = styles?.items ?? [] + items.removeAll(where: { $0.id == .style(.custom(id)) }) + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0)) + } + |> castError(DeleteAITextStyleError.self) + |> ignoreValues + } +} + +func _internal_unsaveAITextStyle(account: Account, id: Int64, accessHash: Int64) -> Signal { + return account.network.request(Api.functions.aicompose.saveTone(tone: .inputAiComposeToneID(Api.InputAiComposeTone.Cons_inputAiComposeToneID(id: id, accessHash: accessHash)), unsave: .boolTrue)) + |> mapError { _ -> DeleteAITextStyleError in + return .generic + } + |> mapToSignal { _ -> Signal in + return account.postbox.transaction { transaction -> Void in + let styles = _internal_cachedCloudAITextStyles(transaction: transaction) + var items = styles?.items ?? [] + items.removeAll(where: { $0.id == .style(.custom(id)) }) + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0)) + } + |> castError(DeleteAITextStyleError.self) + |> ignoreValues + } +} + +final class CachedCloudAITextStyles: Codable { + let items: [TelegramComposeAIMessageMode.CloudStyle] + let hash: Int64 + + init(items: [TelegramComposeAIMessageMode.CloudStyle], hash: Int64) { + self.items = items + self.hash = hash + } +} + +func _internal_cachedCloudAITextStyles(postbox: Postbox) -> Signal { + return postbox.transaction { transaction -> CachedCloudAITextStyles? in + return _internal_cachedCloudAITextStyles(transaction: transaction) + } +} + +func _internal_cachedCloudAITextStyles(transaction: Transaction) -> CachedCloudAITextStyles? { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: 0) + + let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedCloudAITextStyles, key: key))?.get(CachedCloudAITextStyles.self) + if let cached { + return cached + } else { + return nil + } +} + +func _internal_setCachedCloudAITextStyles(transaction: Transaction, styles: CachedCloudAITextStyles) { + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: 0) + + if let entry = CodableEntry(styles) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedCloudAITextStyles, key: key), entry: entry) + } +} + +func _internal_requestAIMessageStyle(account: Account, slug: String) -> Signal<(style: TelegramComposeAIMessageMode.CloudStyle, initialPreview: AIMessageStylePreview?)?, NoError> { + return account.network.request(Api.functions.aicompose.getTone(tone: .inputAiComposeToneSlug(Api.InputAiComposeTone.Cons_inputAiComposeToneSlug(slug: slug)))) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<(style: TelegramComposeAIMessageMode.CloudStyle, initialPreview: AIMessageStylePreview?)?, NoError> in + guard let result else { + return .single(nil) + } + return account.postbox.transaction { transaction -> (style: TelegramComposeAIMessageMode.CloudStyle, initialPreview: AIMessageStylePreview?)? in + switch result { + case let .tones(tones): + guard let tone = tones.tones.first else { + return nil + } + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: tones.users)) + return (TelegramComposeAIMessageMode.CloudStyle(apiTone: tone), nil) + case .tonesNotModified: + return nil + } + } + } +} + +public enum InstallAIMessageStyleError { + case generic +} + +func _internal_installAIMessageStyle(account: Account, style: TelegramComposeAIMessageMode.CloudStyle.Custom) -> Signal { + return account.network.request(Api.functions.aicompose.saveTone(tone: .inputAiComposeToneID(Api.InputAiComposeTone.Cons_inputAiComposeToneID(id: style.id, accessHash: style.accessHash)), unsave: .boolFalse)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> castError(InstallAIMessageStyleError.self) + |> mapToSignal { result -> Signal in + if result != nil { + return account.postbox.transaction { transaction -> Void in + var items = _internal_cachedCloudAITextStyles(transaction: transaction)?.items ?? [] + if let index = items.firstIndex(where: { $0.id == .style(.custom(style.id)) }) { + items.remove(at: index) + } + items.insert(TelegramComposeAIMessageMode.CloudStyle(content: .custom(style)), at: 0) + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0)) + } + |> ignoreValues + |> castError(InstallAIMessageStyleError.self) + } else { + return .fail(.generic) + } + } +} + +func managedSynchronizeCloudAITextStyles(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + let signal: Signal = _internal_cachedCloudAITextStyles(postbox: postbox) + |> mapToSignal { current in + return (network.request(Api.functions.aicompose.getTones(hash: current?.hash ?? 0)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Signal in + guard let result = result else { + return .complete() + } + + switch result { + case let .tones(tones): + _internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles( + items: tones.tones.map(TelegramComposeAIMessageMode.CloudStyle.init(apiTone:)), + hash: tones.hash + )) + case .tonesNotModified: + break + } + + return .complete() + } + |> switchToLatest + }) + } + + return signal.start(completed: { + subscriber.putCompletion() + }) + } + + return ( + poll + |> then( + .complete() + |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()) + ) + ) + |> restart +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift index 56455cee9c..e874239521 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/AppStore.swift @@ -20,7 +20,7 @@ public enum AppStoreTransactionPurpose { case stars(count: Int64, currency: String, amount: Int64, peerId: EnginePeer.Id?) case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64) case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32) - case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64) + case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, premiumDays: Int32, currency: String, amount: Int64) } private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTransactionPurpose) -> Signal { @@ -155,12 +155,12 @@ private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTran return .single(.inputStorePaymentStarsGiveaway(.init(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users))) } |> switchToLatest - case let .authCode(restore, phoneNumber, phoneCodeHash, currency, amount): + case let .authCode(restore, phoneNumber, phoneCodeHash, premiumDays, currency, amount): var flags: Int32 = 0 if restore { flags |= (1 << 0) } - return .single(.inputStorePaymentAuthCode(.init(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount))) + return .single(.inputStorePaymentAuthCode(.init(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, premiumDays: premiumDays, currency: currency, amount: amount))) } } @@ -202,10 +202,6 @@ func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateMana } } -public enum RestoreAppStoreReceiptError { - case generic -} - func _internal_canPurchasePremium(postbox: Postbox, network: Network, purpose: AppStoreTransactionPurpose) -> Signal { return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose) |> mapToSignal { purpose -> Signal in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift index a1a5efa82f..2d7f0380db 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddPeerMember.swift @@ -239,20 +239,20 @@ func _internal_addChannelMember(account: Account, peerId: PeerId, memberId: Peer return cachedData } }) - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] var presences: [PeerId: PeerPresence] = [:] - peers[memberPeer.id] = memberPeer + peers[memberPeer.id] = EnginePeer(memberPeer) if let presence = transaction.getPeerPresence(peerId: memberPeer.id) { presences[memberPeer.id] = presence } if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant { if let adminInfo = maybeAdminInfo { if let peer = transaction.getPeer(adminInfo.promotedBy) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } } - return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences)) + return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences)) } |> mapError { _ -> AddChannelMemberError in } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift index fe3110e357..058519ec1a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChangePeerNotificationSettings.swift @@ -62,7 +62,7 @@ func _internal_togglePeerMuted(account: Account, peerId: PeerId, threadId: Int64 } } -public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: Peer, peerSettings: TelegramPeerNotificationSettings?, topSearchPeers: [PeerId]) -> Bool { +public func resolvedAreStoriesMuted(globalSettings: GlobalNotificationSettingsSet, peer: EnginePeer, peerSettings: TelegramPeerNotificationSettings?, topSearchPeers: [PeerId]) -> Bool { let defaultIsMuted: Bool switch globalSettings.privateChats.storySettings.mute { case .muted: @@ -114,7 +114,7 @@ func _internal_togglePeerStoriesMuted(account: Account, peerId: PeerId) -> Signa case .default: let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings - if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: peer, peerSettings: previousSettings, topSearchPeers: topSearchPeers) { + if resolvedAreStoriesMuted(globalSettings: globalNotificationSettings.effective, peer: EnginePeer(peer), peerSettings: previousSettings, topSearchPeers: topSearchPeers) { storySettings.mute = .unmuted } else { storySettings.mute = .muted diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index f1481f4e1c..3d37a28416 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -268,7 +268,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net let participant = ChannelParticipant(apiParticipant: channelAdminLogEventActionParticipantInviteData.participant) if let peer = peers[participant.peerId] { - action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: peer)) + action = .participantInvite(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer))) } case let .channelAdminLogEventActionParticipantToggleBan(channelAdminLogEventActionParticipantToggleBanData): let (prev, new) = (channelAdminLogEventActionParticipantToggleBanData.prevParticipant, channelAdminLogEventActionParticipantToggleBanData.newParticipant) @@ -276,7 +276,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net let newParticipant = ChannelParticipant(apiParticipant: new) if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] { - action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer)) + action = .participantToggleBan(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer))) } case let .channelAdminLogEventActionParticipantToggleAdmin(channelAdminLogEventActionParticipantToggleAdminData): let (prev, new) = (channelAdminLogEventActionParticipantToggleAdminData.prevParticipant, channelAdminLogEventActionParticipantToggleAdminData.newParticipant) @@ -284,7 +284,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net let newParticipant = ChannelParticipant(apiParticipant: new) if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] { - action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer)) + action = .participantToggleAdmin(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer))) } case let .channelAdminLogEventActionChangeStickerSet(channelAdminLogEventActionChangeStickerSetData): let (prevStickerset, newStickerset) = (channelAdminLogEventActionChangeStickerSetData.prevStickerset, channelAdminLogEventActionChangeStickerSetData.newStickerset) @@ -480,7 +480,7 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net let newParticipant = ChannelParticipant(apiParticipant: new) if let prevPeer = peers[prevParticipant.peerId], let newPeer = peers[newParticipant.peerId] { - action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: prevPeer), new: RenderedChannelParticipant(participant: newParticipant, peer: newPeer)) + action = .participantSubscriptionExtended(prev: RenderedChannelParticipant(participant: prevParticipant, peer: EnginePeer(prevPeer)), new: RenderedChannelParticipant(participant: newParticipant, peer: EnginePeer(newPeer))) } case let .channelAdminLogEventActionToggleAutotranslation(channelAdminLogEventActionToggleAutotranslationData): let newValue = channelAdminLogEventActionToggleAutotranslationData.newValue diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift index 0630e1db6f..228c50bb41 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelBlacklist.swift @@ -125,19 +125,19 @@ func _internal_updateChannelMemberBannedRights(account: Account, peerId: PeerId, return cachedData } }) - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] var presences: [PeerId: PeerPresence] = [:] - peers[memberPeer.id] = memberPeer + peers[memberPeer.id] = EnginePeer(memberPeer) if let presence = transaction.getPeerPresence(peerId: memberPeer.id) { presences[memberPeer.id] = presence } if case let .member(_, _, _, maybeBanInfo, _, _) = updatedParticipant, let banInfo = maybeBanInfo { if let peer = transaction.getPeer(banInfo.restrictedBy) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } - return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: memberPeer, peers: peers, presences: presences), isMember) + return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(memberPeer), peers: peers, presences: presences), isMember) } } } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift index c90272d402..496c6f251a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift @@ -99,10 +99,10 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId: let (participants, chats, users) = (channelParticipantsData.participants, channelParticipantsData.chats, channelParticipantsData.users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] for id in parsedPeers.allIds { if let peer = transaction.getPeer(id) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } @@ -112,7 +112,7 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId: if let presence = transaction.getPeerPresence(peerId: participant.peerId) { renderedPresences[participant.peerId] = presence } - items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences)) + items.append(RenderedChannelParticipant(participant: participant, peer: EnginePeer(peer), peers: peers, presences: renderedPresences)) } } case .channelParticipantsNotModified: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift index dc1f7012a9..b7784ca041 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelOwnershipTransfer.swift @@ -167,17 +167,17 @@ func _internal_updateChatOwnership(account: Account, peerId: PeerId, memberId: P return cachedData } }) - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] var presences: [PeerId: PeerPresence] = [:] - peers[accountUser.id] = accountUser + peers[accountUser.id] = EnginePeer(accountUser) if let presence = transaction.getPeerPresence(peerId: accountUser.id) { presences[accountUser.id] = presence } - peers[user.id] = user + peers[user.id] = EnginePeer(user) if let presence = transaction.getPeerPresence(peerId: user.id) { presences[user.id] = presence } - return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: accountUser, peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))] + return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: EnginePeer(accountUser), peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(user), peers: peers, presences: presences))] } |> mapError { _ -> ChatOwnershipTransferError in } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift index aa845ecc20..c9cfe46d38 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelParticipants.swift @@ -7,18 +7,18 @@ import MtProtoKit public struct RenderedChannelParticipant: Equatable { public let participant: ChannelParticipant - public let peer: Peer - public let peers: [PeerId: Peer] + public let peer: EnginePeer + public let peers: [EnginePeer.Id: EnginePeer] public let presences: [PeerId: PeerPresence] - - public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presences: [PeerId: PeerPresence] = [:]) { + + public init(participant: ChannelParticipant, peer: EnginePeer, peers: [EnginePeer.Id: EnginePeer] = [:], presences: [PeerId: PeerPresence] = [:]) { self.participant = participant self.peer = peer self.peers = peers self.presences = presences } - + public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool { - return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer) + return lhs.participant == rhs.participant && lhs.peer == rhs.peer } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelSendRestriction.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelSendRestriction.swift index 20f562dfe8..53fcc16b3d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelSendRestriction.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelSendRestriction.swift @@ -30,21 +30,67 @@ public enum UpdateChannelJoinRequestError { case generic } -func _internal_toggleChannelJoinRequest(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, enabled: Bool) -> Signal { - return postbox.transaction { transaction -> Peer? in - return transaction.getPeer(peerId) +func _internal_toggleChannelJoinRequest(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, peerId: PeerId, enabled: Bool, guardBotId: PeerId?, applyToInvites: Bool, clearGuardBot: Bool) -> Signal { + let updatedGuardBotId: PeerId? + let shouldUpdateGuardBotId: Bool + if clearGuardBot { + updatedGuardBotId = nil + shouldUpdateGuardBotId = true + } else if let guardBotId { + updatedGuardBotId = guardBotId + shouldUpdateGuardBotId = true + } else { + updatedGuardBotId = nil + shouldUpdateGuardBotId = false + } + + return postbox.transaction { transaction -> (Peer?, Api.InputUser?) in + let guardBot: Api.InputUser? + if clearGuardBot { + guardBot = .inputUserEmpty + } else if let guardBotId { + guardBot = transaction.getPeer(guardBotId).flatMap(apiInputUser) + } else { + guardBot = nil + } + return (transaction.getPeer(peerId), guardBot) } |> castError(UpdateChannelJoinRequestError.self) - |> mapToSignal { peer in + |> mapToSignal { result in + let (peer, guardBot) = result guard let peer = peer, let inputChannel = apiInputChannel(peer) else { return .fail(.generic) } - return network.request(Api.functions.channels.toggleJoinRequest(channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse)) + if guardBotId != nil && guardBot == nil { + return .fail(.generic) + } + var flags: Int32 = 0 + if guardBot != nil { + flags |= 1 << 0 + } + if applyToInvites { + flags |= 1 << 1 + } + return network.request(Api.functions.channels.toggleJoinRequest(flags: flags, channel: inputChannel, enabled: enabled ? .boolTrue : .boolFalse, guardBot: guardBot)) |> `catch` { _ -> Signal in return .fail(.generic) } |> mapToSignal { updates -> Signal in accountStateManager.addUpdates(updates) + + if shouldUpdateGuardBotId { + return postbox.transaction { transaction -> Void in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + guard let current = current as? CachedChannelData else { + return current + } + return current.withUpdatedGuardBotId(updatedGuardBotId) + }) + } + |> castError(UpdateChannelJoinRequestError.self) + |> ignoreValues + } + return .complete() } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 8629381181..efa36b984c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -4,18 +4,6 @@ import SwiftSignalKit import TelegramApi -public struct ChatListFilteringConfiguration: Equatable { - public let isEnabled: Bool - - public init(appConfiguration: AppConfiguration) { - var isEnabled = false - if let data = appConfiguration.data, let value = data["dialog_filters_enabled"] as? Bool, value { - isEnabled = true - } - self.isEnabled = isEnabled - } -} - public struct ChatListFilterPeerCategories: OptionSet, Hashable { public var rawValue: Int32 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift index dceedea39a..b6de8a22e1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift @@ -5,17 +5,17 @@ import Postbox import TelegramApi public struct InactiveChannel : Equatable { - public let peer: Peer + public let peer: EnginePeer public let lastActivityDate: Int32 public let participantsCount: Int32? - - init(peer: Peer, lastActivityDate: Int32, participantsCount: Int32?) { + + init(peer: EnginePeer, lastActivityDate: Int32, participantsCount: Int32?) { self.peer = peer self.lastActivityDate = lastActivityDate self.participantsCount = participantsCount } public static func ==(lhs: InactiveChannel, rhs: InactiveChannel) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.lastActivityDate == rhs.lastActivityDate && lhs.participantsCount == rhs.participantsCount + return lhs.peer == rhs.peer && lhs.lastActivityDate == rhs.lastActivityDate && lhs.participantsCount == rhs.participantsCount } } @@ -43,7 +43,7 @@ func _internal_inactiveChannelList(network: Network) -> Signal<[InactiveChannel] } var inactive: [InactiveChannel] = [] for (i, channel) in channels.enumerated() { - inactive.append(InactiveChannel(peer: channel, lastActivityDate: i < dates.count ? dates[i] : 0, participantsCount: participantsCounts[channel.id])) + inactive.append(InactiveChannel(peer: EnginePeer(channel), lastActivityDate: i < dates.count ? dates[i] : 0, participantsCount: participantsCounts[channel.id])) } return inactive } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift index 5bf4891b4f..938ed2a358 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinChannel.swift @@ -12,13 +12,17 @@ public enum JoinChannelError { case inviteRequestSent } -func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal { +public enum JoinChannelResult { + case joined(RenderedChannelParticipant?) + case webView(JoinChatWebView) +} + +func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> Signal { return account.postbox.loadedPeerWithId(peerId) |> take(1) |> castError(JoinChannelError.self) - |> mapToSignal { peer -> Signal in - - let request: Signal + |> mapToSignal { peer -> Signal in + let request: Signal if let hash = hash { request = account.network.request(Api.functions.messages.importChatInvite(hash: hash)) } else if let inputChannel = apiInputChannel(peer) { @@ -26,7 +30,7 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S } else { request = .fail(.init()) } - + return request |> mapError { error -> JoinChannelError in switch error.errorDescription { @@ -40,28 +44,51 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S return .generic } } - |> mapToSignal { updates -> Signal in + |> mapToSignal { result -> Signal in + let updates: Api.Updates + switch result { + case let .chatInviteJoinResultOk(data): + updates = data.updates + case let .chatInviteJoinResultWebView(data): + switch data.webview { + case let .webViewResultUrl(urlData): + let botPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(data.botId)) + return account.postbox.transaction { transaction -> Signal in + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: data.users)) + guard let botPeer = transaction.getPeer(botPeerId) else { + return .fail(.generic) + } + return .single(.webView(JoinChatWebView(botPeer: EnginePeer(botPeer), url: urlData.url, queryId: urlData.queryId ?? 0, peerId: peerId))) + } + |> castError(JoinChannelError.self) + |> switchToLatest + } + } + account.stateManager.addUpdates(updates) - + if hash == nil { + let _ = _internal_requestRecommendedChannels(account: account, peerId: peerId, forceUpdate: true).startStandalone() + } + let channels = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) }.compactMap(apiInputChannel) - + if let inputChannel = channels.first { return account.network.request(Api.functions.channels.getParticipant(channel: inputChannel, participant: .inputPeerSelf)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in guard let result = result else { return .fail(.generic) } - return account.postbox.transaction { transaction -> RenderedChannelParticipant? in - var peers: [PeerId: Peer] = [:] + return account.postbox.transaction { transaction -> JoinChannelResult in + var peers: [EnginePeer.Id: EnginePeer] = [:] var presences: [PeerId: PeerPresence] = [:] guard let peer = transaction.getPeer(account.peerId) else { - return nil + return .joined(nil) } - peers[account.peerId] = peer + peers[account.peerId] = EnginePeer(peer) if let presence = transaction.getPeerPresence(peerId: account.peerId) { presences[account.peerId] = presence } @@ -74,25 +101,19 @@ func _internal_joinChannel(account: Account, peerId: PeerId, hash: String?) -> S if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant { if let adminInfo = maybeAdminInfo { if let peer = transaction.getPeer(adminInfo.promotedBy) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } } - - return RenderedChannelParticipant(participant: updatedParticipant, peer: peer, peers: peers, presences: presences) + + return .joined(RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(peer), peers: peers, presences: presences)) } |> castError(JoinChannelError.self) } } else { return .fail(.generic) } - - - } - |> afterCompleted { - if hash == nil { - let _ = _internal_requestRecommendedChannels(account: account, peerId: peerId, forceUpdate: true).startStandalone() - } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift index e54c34896d..f05f549fef 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift @@ -17,6 +17,64 @@ public enum JoinLinkError { case flood } +public enum JoinChatWebViewResult { + case approved + case declined + case queued + case webView(url: String) +} + +extension JoinChatWebViewResult { + init(apiResult: Api.JoinChatBotResult) { + switch apiResult { + case .joinChatBotResultApproved: + self = .approved + case .joinChatBotResultDeclined: + self = .declined + case .joinChatBotResultQueued: + self = .queued + case let .joinChatBotResultWebView(data): + self = .webView(url: data.url) + } + } +} + +public struct JoinChatWebView { + public let botPeer: EnginePeer + public let url: String + public let queryId: Int64 + public let peerId: EnginePeer.Id? + + public init(botPeer: EnginePeer, url: String, queryId: Int64, peerId: EnginePeer.Id?) { + self.botPeer = botPeer + self.url = url + self.queryId = queryId + self.peerId = peerId + } +} + +public struct JoinChatWebViewDecision { + public let peerId: EnginePeer.Id + public let queryId: Int64 + public let result: JoinChatWebViewResult + + public init(peerId: EnginePeer.Id, queryId: Int64, result: JoinChatWebViewResult) { + self.peerId = peerId + self.queryId = queryId + self.result = result + } +} + +public enum JoinLinkResult { + case joined(EnginePeer?) + case webView(JoinChatWebView) +} + +enum InternalJoinLinkResult { + case joined(PeerId?) + case webView(JoinChatWebView) +} + func apiUpdatesGroups(_ updates: Api.Updates) -> [Api.Chat] { switch updates { case let .updates(updatesData): @@ -62,7 +120,7 @@ public enum ExternalJoiningChatState { case peek(EnginePeer, Int32) } -func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal { +func _internal_joinChatInteractively(with hash: String, account: Account) -> Signal { return account.network.request(Api.functions.messages.importChatInvite(hash: hash), automaticFloodWait: false) |> mapError { error -> JoinLinkError in switch error.errorDescription { @@ -80,21 +138,39 @@ func _internal_joinChatInteractively(with hash: String, account: Account) -> Sig } } } - |> mapToSignal { updates -> Signal in - account.stateManager.addUpdates(updates) - if let peerId = apiUpdatesGroups(updates).first?.peerId { - return account.postbox.multiplePeersView([peerId]) - |> castError(JoinLinkError.self) - |> filter { view in - return view.peers[peerId] != nil + |> mapToSignal { result -> Signal in + switch result { + case let .chatInviteJoinResultOk(data): + let updates = data.updates + account.stateManager.addUpdates(updates) + if let peerId = apiUpdatesGroups(updates).first?.peerId { + return account.postbox.multiplePeersView([peerId]) + |> castError(JoinLinkError.self) + |> filter { view in + return view.peers[peerId] != nil + } + |> take(1) + |> map { _ in + return .joined(peerId) + } + |> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(.joined(nil)) |> castError(JoinLinkError.self)) } - |> take(1) - |> map { _ in - return peerId + return .single(.joined(nil)) + case let .chatInviteJoinResultWebView(data): + switch data.webview { + case let .webViewResultUrl(urlData): + let botPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(data.botId)) + return account.postbox.transaction { transaction -> Signal in + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: data.users)) + guard let botPeer = transaction.getPeer(botPeerId) else { + return .fail(.generic) + } + return .single(.webView(JoinChatWebView(botPeer: EnginePeer(botPeer), url: urlData.url, queryId: urlData.queryId ?? 0, peerId: nil))) + } + |> castError(JoinLinkError.self) + |> switchToLatest } - |> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil) |> castError(JoinLinkError.self)) } - return .single(nil) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift index 022d4a046e..23b6e63be8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/NotificationExceptionsList.swift @@ -5,14 +5,14 @@ import TelegramApi public final class NotificationExceptionsList: Equatable { - public let peers: [PeerId: Peer] + public let peers: [PeerId: EnginePeer] public let settings: [PeerId: TelegramPeerNotificationSettings] - - public init(peers: [PeerId: Peer], settings: [PeerId: TelegramPeerNotificationSettings]) { + + public init(peers: [PeerId: EnginePeer], settings: [PeerId: TelegramPeerNotificationSettings]) { self.peers = peers self.settings = settings } - + public static func ==(lhs: NotificationExceptionsList, rhs: NotificationExceptionsList) -> Bool { return lhs === rhs } @@ -41,10 +41,10 @@ func _internal_notificationExceptionsList(accountPeerId: PeerId, postbox: Postbo let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) updatePeers(transaction: transaction, accountPeerId: accountPeerId,peers: parsedPeers) - var peers: [PeerId: Peer] = [:] + var peers: [PeerId: EnginePeer] = [:] for id in parsedPeers.allIds { if let peer = transaction.getPeer(id) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift index bc0b5ab1ec..1e3761fae4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/Peer.swift @@ -118,20 +118,17 @@ public enum EnginePeer: Equatable { } public var flags: Flags - public var geoDistance: Int32? public var requestChatTitle: String? public var requestChatDate: Int32? public var requestChatIsChannel: Bool? public init( flags: Flags, - geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, requestChatIsChannel: Bool? ) { self.flags = flags - self.geoDistance = geoDistance self.requestChatTitle = requestChatTitle self.requestChatDate = requestChatDate self.requestChatIsChannel = requestChatIsChannel @@ -363,7 +360,6 @@ public extension EnginePeer.StatusSettings { init(_ statusSettings: PeerStatusSettings) { self.init( flags: Flags(rawValue: statusSettings.flags.rawValue), - geoDistance: statusSettings.geoDistance, requestChatTitle: statusSettings.requestChatTitle, requestChatDate: statusSettings.requestChatDate, requestChatIsChannel: statusSettings.requestChatIsChannel @@ -558,6 +554,26 @@ public extension EnginePeer { var profileBackgroundEmojiId: Int64? { return self._asPeer().profileBackgroundEmojiId } + + var isCopyProtectionEnabled: Bool { + return self._asPeer().isCopyProtectionEnabled + } + + var isMonoForum: Bool { + return self._asPeer().isMonoForum + } + + var associatedPeerId: Id? { + return self._asPeer().associatedPeerId + } + + var hasCustomNameColor: Bool { + return self._asPeer().hasCustomNameColor + } + + func hasSensitiveContent(platform: String) -> Bool { + return self._asPeer().hasSensitiveContent(platform: platform) + } } public extension EnginePeer { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift index f29b60c549..fc494c5ed8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerAdmins.swift @@ -248,18 +248,18 @@ func _internal_updateChannelAdminRights(account: Account, peerId: PeerId, adminI return cachedData } }) - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] var presences: [PeerId: PeerPresence] = [:] - peers[adminPeer.id] = adminPeer + peers[adminPeer.id] = EnginePeer(adminPeer) if let presence = transaction.getPeerPresence(peerId: adminPeer.id) { presences[adminPeer.id] = presence } if case let .member(_, _, maybeAdminInfo, _, _, _) = updatedParticipant, let adminInfo = maybeAdminInfo { if let peer = transaction.getPeer(adminInfo.promotedBy) { - peers[peer.id] = peer + peers[peer.id] = EnginePeer(peer) } } - return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: adminPeer, peers: peers, presences: presences)) + return (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: EnginePeer(adminPeer), peers: peers, presences: presences)) } |> mapError { _ -> UpdateChannelAdminRightsError in } } } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerCommands.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerCommands.swift index 3471b95118..53cd10b608 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerCommands.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerCommands.swift @@ -4,13 +4,13 @@ import SwiftSignalKit public struct PeerCommand: Hashable { - public let peer: Peer + public let peer: EnginePeer public let command: BotCommand - + public static func ==(lhs: PeerCommand, rhs: PeerCommand) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.command == rhs.command + return lhs.peer == rhs.peer && lhs.command == rhs.command } - + public func hash(into hasher: inout Hasher) { hasher.combine(self.peer.id) hasher.combine(self.command) @@ -32,7 +32,7 @@ func _internal_peerCommands(account: Account, id: PeerId) -> Signal Signal Signal castError(UpdateChatRankError.self) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift index c4de6ebe66..fe9e694a9f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/RecentPeers.swift @@ -151,7 +151,10 @@ func _internal_updateRecentPeersEnabled(postbox: Postbox, network: Network, enab } func _internal_managedRecentlyUsedInlineBots(postbox: Postbox, network: Network, accountPeerId: PeerId) -> Signal { - let remotePeers = network.request(Api.functions.contacts.getTopPeers(flags: 1 << 2, offset: 0, limit: 16, hash: 0)) + var flags: Int32 = 0 + flags |= 1 << 2 + flags |= 1 << 17 + let remotePeers = network.request(Api.functions.contacts.getTopPeers(flags: flags, offset: 0, limit: 24, hash: 0)) |> retryRequestIfNotFrozen |> map { result -> (AccumulatedPeers, [(PeerId, Double)])? in switch result { @@ -208,17 +211,21 @@ func _internal_managedRecentlyUsedInlineBots(postbox: Postbox, network: Network, return updatedRemotePeers } +func _internal_addRecentlyUsedInlineBot(transaction: Transaction, peerId: PeerId) { + var maxRating = 1.0 + for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots) { + if let contents = entry.contents.get(RecentPeerItem.self) { + maxRating = max(maxRating, contents.rating) + } + } + if let entry = CodableEntry(RecentPeerItem(rating: maxRating)) { + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, item: OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: entry), removeTailIfCountExceeds: 20) + } +} + func _internal_addRecentlyUsedInlineBot(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in - var maxRating = 1.0 - for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots) { - if let contents = entry.contents.get(RecentPeerItem.self) { - maxRating = max(maxRating, contents.rating) - } - } - if let entry = CodableEntry(RecentPeerItem(rating: maxRating)) { - transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentInlineBots, item: OrderedItemListEntry(id: RecentPeerItemId(peerId).rawValue, contents: entry), removeTailIfCountExceeds: 20) - } + _internal_addRecentlyUsedInlineBot(transaction: transaction, peerId: peerId) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift index 54ede971c6..ba2969cd40 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift @@ -5,7 +5,7 @@ import TelegramApi import MtProtoKit -func _internal_reportPeer(account: Account, peerId: PeerId) -> Signal { +func _internal_reportPeer(account: Account, peerId: PeerId, sourceMessageId: MessageId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) { if let peer = peer as? TelegramSecretChat { @@ -37,7 +37,7 @@ func _internal_reportPeer(account: Account, peerId: PeerId) -> Signal map(Optional.init) |> `catch` { _ -> Signal in @@ -115,9 +115,9 @@ private extension ReportReason { } } -func _internal_reportPeer(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal { +func _internal_reportPeer(account: Account, peerId: PeerId, reason: ReportReason, message: String, sourceMessageId: MessageId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { + if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer, sourceMessageId: sourceMessageId, transaction: transaction) { return account.network.request(Api.functions.account.reportPeer(peer: inputPeer, reason: reason.apiReason, message: message)) |> `catch` { _ -> Signal in return .single(.boolFalse) @@ -131,9 +131,9 @@ func _internal_reportPeer(account: Account, peerId: PeerId, reason: ReportReason } |> switchToLatest } -func _internal_reportPeerPhoto(account: Account, peerId: PeerId, reason: ReportReason, message: String) -> Signal { +func _internal_reportPeerPhoto(account: Account, peerId: PeerId, reason: ReportReason, message: String, sourceMessageId: MessageId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { + if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer, sourceMessageId: sourceMessageId, transaction: transaction) { return account.network.request(Api.functions.account.reportProfilePhoto(peer: inputPeer, photoId: .inputPhotoEmpty, reason: reason.apiReason, message: message)) |> `catch` { _ -> Signal in return .single(.boolFalse) @@ -193,7 +193,7 @@ func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: guard let peer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { return nil } - guard let author = transaction.getPeer(authorId).flatMap(apiInputPeer) else { + guard let authorPeer = transaction.getPeer(authorId), let author = apiInputPeer(authorPeer, sourceMessageId: messageId, transaction: transaction) else { return nil } return (peer, author) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift index 2e290468fb..21698d63bf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ResolvePeerByName.swift @@ -3,17 +3,6 @@ import Postbox import TelegramApi import SwiftSignalKit -public enum ResolvePeerByNameOptionCached { - case none - case cached - case cachedIfLaterThan(timestamp: Int32) -} - -public enum ResolvePeerByNameOptionRemote { - case updateIfEarlierThan(timestamp: Int32) - case update -} - public enum ResolvePeerIdByNameResult { case progress case result(PeerId?) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SavedMusic.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SavedMusic.swift index c1d6b785d7..0bededb960 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SavedMusic.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SavedMusic.swift @@ -8,7 +8,7 @@ public enum AddSavedMusicError { case generic } -func revalidatedMusic(account: Account, file: FileMediaReference, signal: @escaping (CloudDocumentMediaResource) -> Signal) -> Signal { +public func revalidatedMusic(account: Account, file: FileMediaReference, signal: @escaping (CloudDocumentMediaResource) -> Signal) -> Signal { guard let resource = file.media.resource as? CloudDocumentMediaResource else { return .fail(MTRpcError(errorCode: 500, errorDescription: "Internal")) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchGroupMembers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchGroupMembers.swift index 5fbef54318..574bfb90db 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchGroupMembers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchGroupMembers.swift @@ -80,7 +80,7 @@ func _internal_searchGroupMembers(postbox: Postbox, network: Network, accountPee let existingIds = Set(local.map { $0.id }) let filtered: [Peer] if let participants = participants { - filtered = participants.map({ $0.peer }).filter({ peer in + filtered = participants.map({ $0.peer._asPeer() }).filter({ peer in if existingIds.contains(peer.id) { return false } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift index fbbb62a2c8..30e8a3c0e4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/SearchPeers.swift @@ -5,16 +5,16 @@ import TelegramApi import MtProtoKit public struct FoundPeer: Equatable { - public let peer: Peer + public let peer: EnginePeer public let subscribers: Int32? - - public init(peer: Peer, subscribers: Int32?) { + + public init(peer: EnginePeer, subscribers: Int32?) { self.peer = peer self.subscribers = subscribers } - + public static func ==(lhs: FoundPeer, rhs: FoundPeer) -> Bool { - return lhs.peer.isEqual(rhs.peer) && lhs.subscribers == rhs.subscribers + return lhs.peer == rhs.peer && lhs.subscribers == rhs.subscribers } } @@ -27,7 +27,7 @@ public enum TelegramSearchPeersScope: Equatable { } public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, network: Network, query: String, scope: TelegramSearchPeersScope) -> Signal<([FoundPeer], [FoundPeer]), NoError> { - let searchResult = network.request(Api.functions.contacts.search(q: query, limit: 20), automaticFloodWait: false) + let searchResult = network.request(Api.functions.contacts.search(flags: 0, q: query, limit: 20), automaticFloodWait: false) |> map(Optional.init) |> `catch` { _ in return Signal.single(nil) @@ -67,9 +67,9 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo continue } if let user = peer as? TelegramUser { - renderedMyPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) } else { - renderedMyPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + renderedMyPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) } } } @@ -82,9 +82,9 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo continue } if let user = peer as? TelegramUser { - renderedPeers.append(FoundPeer(peer: peer, subscribers: user.subscriberCount)) + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: user.subscriberCount)) } else { - renderedPeers.append(FoundPeer(peer: peer, subscribers: subscribers[peerId])) + renderedPeers.append(FoundPeer(peer: EnginePeer(peer), subscribers: subscribers[peerId])) } } } @@ -94,14 +94,14 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo break case .channels: renderedMyPeers = renderedMyPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = item.peer, case .broadcast = channel.info { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = item.peer, case .broadcast = channel.info { return true } else { return false @@ -109,18 +109,18 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo } case .groups: renderedMyPeers = renderedMyPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = item.peer, case .group = channel.info { return true - } else if item.peer is TelegramGroup { + } else if case .legacyGroup = item.peer { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if let channel = item.peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = item.peer, case .group = channel.info { return true - } else if item.peer is TelegramGroup { + } else if case .legacyGroup = item.peer { return true } else { return false @@ -128,14 +128,14 @@ public func _internal_searchPeers(accountPeerId: PeerId, postbox: Postbox, netwo } case .privateChats: renderedMyPeers = renderedMyPeers.filter { item in - if item.peer is TelegramUser { + if case .user = item.peer { return true } else { return false } } renderedPeers = renderedPeers.filter { item in - if item.peer is TelegramUser { + if case .user = item.peer { return true } else { return false diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 8a1236f933..face818b04 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -251,8 +251,9 @@ public extension TelegramEngine { } } - public func updatedRemotePeer(peer: PeerReference) -> Signal { + public func updatedRemotePeer(peer: PeerReference) -> Signal { return _internal_updatedRemotePeer(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, peer: peer) + |> map(EnginePeer.init) } public func chatOnlineMembers(peerId: PeerId) -> Signal { @@ -335,16 +336,16 @@ public extension TelegramEngine { return _internal_updateChannelSlowModeInteractively(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, timeout: timeout) } - public func reportPeer(peerId: PeerId) -> Signal { - return _internal_reportPeer(account: self.account, peerId: peerId) + public func reportPeer(peerId: PeerId, sourceMessageId: MessageId? = nil) -> Signal { + return _internal_reportPeer(account: self.account, peerId: peerId, sourceMessageId: sourceMessageId) } - public func reportPeer(peerId: PeerId, reason: ReportReason, message: String) -> Signal { - return _internal_reportPeer(account: self.account, peerId: peerId, reason: reason, message: message) + public func reportPeer(peerId: PeerId, reason: ReportReason, message: String, sourceMessageId: MessageId? = nil) -> Signal { + return _internal_reportPeer(account: self.account, peerId: peerId, reason: reason, message: message, sourceMessageId: sourceMessageId) } - public func reportPeerPhoto(peerId: PeerId, reason: ReportReason, message: String) -> Signal { - return _internal_reportPeerPhoto(account: self.account, peerId: peerId, reason: reason, message: message) + public func reportPeerPhoto(peerId: PeerId, reason: ReportReason, message: String, sourceMessageId: MessageId? = nil) -> Signal { + return _internal_reportPeerPhoto(account: self.account, peerId: peerId, reason: reason, message: message, sourceMessageId: sourceMessageId) } public func reportPeerMessages(messageIds: [MessageId], reason: ReportReason, message: String) -> Signal { @@ -513,8 +514,8 @@ public extension TelegramEngine { return _internal_toggleChannelJoinToSend(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled) } - public func toggleChannelJoinRequest(peerId: PeerId, enabled: Bool) -> Signal { - return _internal_toggleChannelJoinRequest(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled) + public func toggleChannelJoinRequest(peerId: PeerId, enabled: Bool, guardBotId: PeerId? = nil, applyToInvites: Bool = false, clearGuardBot: Bool = false) -> Signal { + return _internal_toggleChannelJoinRequest(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, enabled: enabled, guardBotId: guardBotId, applyToInvites: applyToInvites, clearGuardBot: clearGuardBot) } public func toggleAntiSpamProtection(peerId: PeerId, enabled: Bool) -> Signal { @@ -537,7 +538,7 @@ public extension TelegramEngine { return _internal_updateGroupSpecificEmojiset(postbox: self.account.postbox, network: self.account.network, peerId: peerId, info: info) } - public func joinChannel(peerId: PeerId, hash: String?) -> Signal { + public func joinChannel(peerId: PeerId, hash: String?) -> Signal { return _internal_joinChannel(account: self.account, peerId: peerId, hash: hash) } @@ -701,16 +702,18 @@ public extension TelegramEngine { return _internal_removeRecentlyUsedApp(account: self.account, peerId: peerId) } - public func uploadedPeerPhoto(resource: MediaResource) -> Signal { - return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource) + public func uploadedPeerPhoto(resource: EngineMediaResource) -> Signal { + return _internal_uploadedPeerPhoto(postbox: self.account.postbox, network: self.account.network, resource: resource._asResource()) } - public func uploadedPeerVideo(resource: MediaResource) -> Signal { - return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource) + public func uploadedPeerVideo(resource: EngineMediaResource) -> Signal { + return _internal_uploadedPeerVideo(postbox: self.account.postbox, network: self.account.network, messageMediaPreuploadManager: self.account.messageMediaPreuploadManager, resource: resource._asResource()) } - public func updatePeerPhoto(peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, markup: UploadPeerPhotoMarkup? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + public func updatePeerPhoto(peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, markup: UploadPeerPhotoMarkup? = nil, mapResourceToAvatarSizes: @escaping (EngineMediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + return _internal_updatePeerPhoto(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, accountPeerId: self.account.peerId, peerId: peerId, photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { rawResource, representations in + return mapResourceToAvatarSizes(EngineMediaResource(rawResource), representations) + }) } public func requestUpdateChatListFilter(id: Int32, filter: ChatListFilter?) -> Signal { @@ -820,8 +823,8 @@ public extension TelegramEngine { } } - public func fetchAndUpdateCachedPeerData(peerId: PeerId) -> Signal { - return _internal_fetchAndUpdateCachedPeerData(accountPeerId: self.account.peerId, peerId: peerId, network: self.account.network, postbox: self.account.postbox) + public func fetchAndUpdateCachedPeerData(peerId: PeerId, sourceMessageId: EngineMessage.Id? = nil) -> Signal { + return _internal_fetchAndUpdateCachedPeerData(accountPeerId: self.account.peerId, peerId: peerId, sourceMessageId: sourceMessageId, network: self.account.network, postbox: self.account.postbox) } public func toggleItemPinned(location: TogglePeerChatPinnedLocation, itemId: PinnedItemId) -> Signal { @@ -840,16 +843,26 @@ public extension TelegramEngine { } } - public func joinChatInteractively(with hash: String) -> Signal { + public func joinChatInteractively(with hash: String) -> Signal { let account = self.account return _internal_joinChatInteractively(with: hash, account: self.account) - |> mapToSignal { id -> Signal in + |> mapToSignal { result -> Signal in + let id: PeerId? + switch result { + case let .joined(peerId): + id = peerId + case let .webView(webView): + return .single(.webView(webView)) + } guard let id = id else { - return .single(nil) + return .single(.joined(nil)) } return account.postbox.transaction { transaction -> EnginePeer? in return transaction.getPeer(id).flatMap(EnginePeer.init) } + |> map { peer -> JoinLinkResult in + return .joined(peer) + } |> castError(JoinLinkError.self) } } @@ -1230,14 +1243,11 @@ public extension TelegramEngine { } public func ensurePeerIsLocallyAvailable(peer: EnginePeer) -> Signal { - return _internal_storedMessageFromSearchPeer(postbox: self.account.postbox, peer: peer._asPeer()) - |> map { result -> EnginePeer in - return EnginePeer(result) - } + return _internal_storedMessageFromSearchPeer(postbox: self.account.postbox, peer: peer) } public func ensurePeersAreLocallyAvailable(peers: [EnginePeer]) -> Signal { - return _internal_storedMessageFromSearchPeers(account: self.account, peers: peers.map { $0._asPeer() }) + return _internal_storedMessageFromSearchPeers(account: self.account, peers: peers) } public func mostRecentSecretChat(id: EnginePeer.Id) -> Signal { @@ -1513,6 +1523,10 @@ public extension TelegramEngine { public func tokenizeSearchString(string: String, transliteration: EngineStringIndexTokenTransliteration) -> [EngineDataBuffer] { return stringIndexTokens(string, transliteration: transliteration) } + + public func matchSearchTokens(_ tokens: [EngineDataBuffer], with other: [EngineDataBuffer]) -> Bool { + return matchStringIndexTokens(tokens, with: other) + } public func updatePeerStoriesHidden(id: PeerId, isHidden: Bool) { let _ = _internal_updatePeerStoriesHidden(account: self.account, id: id, isHidden: isHidden).start() @@ -1591,7 +1605,7 @@ public extension TelegramEngine { } public func subscribeIsPremiumRequiredForMessaging(id: EnginePeer.Id) -> Signal { - if id.namespace != Namespaces.Peer.CloudUser { + if id == self.account.peerId || id.namespace != Namespaces.Peer.CloudUser { return .single(false) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 5f1a6d6d8a..15b7bfbd8a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -140,7 +140,7 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP } } -func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, network: Network, postbox: Postbox) -> Signal { +func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerId, sourceMessageId: EngineMessage.Id? = nil, network: Network, postbox: Postbox) -> Signal { return postbox.combinedView(keys: [.basicPeer(rawPeerId)]) |> mapToSignal { views -> Signal in if accountPeerId == rawPeerId { @@ -178,7 +178,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee if rawPeerId == accountPeerId { return (.inputUserSelf, rawPeer, rawPeerId) } else { - return (apiInputUser(peer), peer, peer.id) + return (apiInputUser(peer, sourceMessageId: sourceMessageId, transaction: transaction), peer, peer.id) } } |> mapToSignal { inputUser, maybePeer, peerId -> Signal in @@ -258,11 +258,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee if let apiBot = connectedBots.first { switch apiBot { case let .connectedBot(connectedBotData): - let (botId, recipients, rights) = (connectedBotData.botId, connectedBotData.recipients, connectedBotData.rights) + let (botId, recipients, rights, device, date, location) = (connectedBotData.botId, connectedBotData.recipients, connectedBotData.rights, connectedBotData.device, connectedBotData.date, connectedBotData.location) mappedConnectedBot = TelegramAccountConnectedBot( id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), recipients: TelegramBusinessRecipients(apiValue: recipients), - rights: TelegramBusinessBotRights(apiValue: rights) + rights: TelegramBusinessBotRights(apiValue: rights), + device: device, + date: date, + location: location ) } } @@ -302,7 +305,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let unofficialSecurityRisk = (userFullFlags2 & (1 << 26)) != 0 var flags: CachedUserFlags = previous.flags - if premiumRequired { + if premiumRequired && peerId != accountPeerId { flags.insert(.premiumRequired) } else { flags.remove(.premiumRequired) @@ -675,7 +678,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee switch fullChat { case let .channelFull(channelFullData): - let (flags, flags2, about, participantsCount, adminsCount, kickedCount, bannedCount, chatPhoto, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, defaultSendAs, allowedReactions, reactionsLimit, wallpaper, appliedBoosts, boostsUnrestrict, emojiSet, verification, starGiftsCount, sendPaidMessageStars, mainTab) = (channelFullData.flags, channelFullData.flags2, channelFullData.about, channelFullData.participantsCount, channelFullData.adminsCount, channelFullData.kickedCount, channelFullData.bannedCount, channelFullData.chatPhoto, channelFullData.exportedInvite, channelFullData.botInfo, channelFullData.migratedFromChatId, channelFullData.migratedFromMaxId, channelFullData.pinnedMsgId, channelFullData.stickerset, channelFullData.availableMinId, channelFullData.linkedChatId, channelFullData.location, channelFullData.slowmodeSeconds, channelFullData.slowmodeNextSendDate, channelFullData.statsDc, channelFullData.call, channelFullData.ttlPeriod, channelFullData.pendingSuggestions, channelFullData.groupcallDefaultJoinAs, channelFullData.themeEmoticon, channelFullData.requestsPending, channelFullData.defaultSendAs, channelFullData.availableReactions, channelFullData.reactionsLimit, channelFullData.wallpaper, channelFullData.boostsApplied, channelFullData.boostsUnrestrict, channelFullData.emojiset, channelFullData.botVerification, channelFullData.stargiftsCount, channelFullData.sendPaidMessagesStars, channelFullData.mainTab) + let (flags, flags2, about, participantsCount, adminsCount, kickedCount, bannedCount, chatPhoto, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, requestsPending, defaultSendAs, allowedReactions, reactionsLimit, wallpaper, appliedBoosts, boostsUnrestrict, emojiSet, verification, starGiftsCount, sendPaidMessageStars, mainTab, guardBotId) = (channelFullData.flags, channelFullData.flags2, channelFullData.about, channelFullData.participantsCount, channelFullData.adminsCount, channelFullData.kickedCount, channelFullData.bannedCount, channelFullData.chatPhoto, channelFullData.exportedInvite, channelFullData.botInfo, channelFullData.migratedFromChatId, channelFullData.migratedFromMaxId, channelFullData.pinnedMsgId, channelFullData.stickerset, channelFullData.availableMinId, channelFullData.linkedChatId, channelFullData.location, channelFullData.slowmodeSeconds, channelFullData.slowmodeNextSendDate, channelFullData.statsDc, channelFullData.call, channelFullData.ttlPeriod, channelFullData.pendingSuggestions, channelFullData.groupcallDefaultJoinAs, channelFullData.themeEmoticon, channelFullData.requestsPending, channelFullData.defaultSendAs, channelFullData.availableReactions, channelFullData.reactionsLimit, channelFullData.wallpaper, channelFullData.boostsApplied, channelFullData.boostsUnrestrict, channelFullData.emojiset, channelFullData.botVerification, channelFullData.stargiftsCount, channelFullData.sendPaidMessagesStars, channelFullData.mainTab, channelFullData.guardBotId) var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -899,6 +902,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let mappedChatTheme: ChatTheme? = themeEmoticon.flatMap { .emoticon($0) } + let mappedGuardBotId = guardBotId.flatMap { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) } + return previous.withUpdatedFlags(channelFlags) .withUpdatedAbout(about) .withUpdatedParticipantsSummary(CachedChannelParticipantsSummary(memberCount: participantsCount, adminCount: adminsCount, bannedCount: bannedCount, kickedCount: kickedCount)) @@ -935,6 +940,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedStarGiftsCount(starGiftsCount) .withUpdatedSendPaidMessageStars(mappedSendPaidMessageStars) .withUpdatedMainProfileTab(mappedMainProfileTab) + .withUpdatedGuardBotId(mappedGuardBotId) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/PeersNearby.swift b/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/PeersNearby.swift deleted file mode 100644 index 39b45bccb0..0000000000 --- a/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/PeersNearby.swift +++ /dev/null @@ -1,309 +0,0 @@ -import Foundation -import SwiftSignalKit -import Postbox -import TelegramApi - - -private typealias SignalKitTimer = SwiftSignalKit.Timer - -public enum PeerNearby { - case selfPeer(expires: Int32) - case peer(id: PeerId, expires: Int32, distance: Int32) - - var expires: Int32 { - switch self { - case let .selfPeer(expires), let .peer(_, expires, _): - return expires - } - } -} - -public enum PeerNearbyVisibilityUpdate { - case visible(latitude: Double, longitude: Double) - case location(latitude: Double, longitude: Double) - case invisible -} - -func _internal_updatePeersNearbyVisibility(account: Account, update: PeerNearbyVisibilityUpdate, background: Bool) -> Signal { - var flags: Int32 = 0 - var geoPoint: Api.InputGeoPoint - var selfExpires: Int32? - - switch update { - case let .visible(latitude, longitude): - flags |= (1 << 0) - geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)) - selfExpires = 10800 - case let .location(latitude, longitude): - geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)) - case .invisible: - flags |= (1 << 0) - geoPoint = .inputGeoPointEmpty - selfExpires = 0 - } - - let _ = (account.postbox.transaction { transaction in - transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in - var settings = entry?.get(PeersNearbyState.self) ?? PeersNearbyState.default - if case .invisible = update { - settings.visibilityExpires = nil - } else if let expires = selfExpires { - settings.visibilityExpires = expires - } - return PreferencesEntry(settings) - }) - }).start() - - if background { - flags |= (1 << 1) - } - - return account.network.request(Api.functions.contacts.getLocated(flags: flags, geoPoint: geoPoint, selfExpires: selfExpires)) - |> map(Optional.init) - |> `catch` { error -> Signal in - if error.errorCode == 406 { - if error.errorDescription == "USERPIC_PRIVACY_REQUIRED" { - let _ = (account.postbox.transaction { transaction in - transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in - var settings = entry?.get(PeersNearbyState.self) ?? PeersNearbyState.default - settings.visibilityExpires = nil - return PreferencesEntry(settings) - }) - }).start() - } - return .single(nil) - } else { - return .single(nil) - } - } - |> mapToSignal { updates -> Signal in - if let updates = updates { - account.stateManager.addUpdates(updates) - } - return .complete() - } -} - -public final class PeersNearbyContext { - private let queue: Queue = Queue.mainQueue() - private var subscribers = Bag<([PeerNearby]?) -> Void>() - private let disposable = MetaDisposable() - private var timer: SignalKitTimer? - - private var entries: [PeerNearby]? - - public init(network: Network, stateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) { - let expiryExtension: Double = 10.0 - - let poll = network.request(Api.functions.contacts.getLocated(flags: 0, geoPoint: .inputGeoPoint(.init(flags: 0, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: nil)), selfExpires: nil)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> castError(Void.self) - |> mapToSignal { updates -> Signal<[PeerNearby], Void> in - var peersNearby: [PeerNearby] = [] - if let updates = updates { - switch updates { - case let .updates(updatesData): - let updates = updatesData.updates - for update in updates { - if case let .updatePeerLocated(updatePeerLocatedData) = update { - let peers = updatePeerLocatedData.peers - for peer in peers { - switch peer { - case let .peerLocated(peerLocatedData): - let (peer, expires, distance) = (peerLocatedData.peer, peerLocatedData.expires, peerLocatedData.distance) - peersNearby.append(.peer(id: peer.peerId, expires: expires, distance: distance)) - case let .peerSelfLocated(peerSelfLocatedData): - let expires = peerSelfLocatedData.expires - peersNearby.append(.selfPeer(expires: expires)) - } - } - } - } - default: - break - } - stateManager.addUpdates(updates) - } - return .single(peersNearby) - |> then( - stateManager.updatedPeersNearby() - |> castError(Void.self) - ) - } - - let error: Signal = .single(Void()) |> then(Signal.fail(Void()) |> suspendAwareDelay(25.0, queue: self.queue)) - let combined = combineLatest(poll, error) - |> map { data, _ -> [PeerNearby] in - return data - } - |> restartIfError - |> `catch` { _ -> Signal<[PeerNearby], NoError> in - } - - self.disposable.set((combined - |> deliverOn(self.queue)).start(next: { [weak self] updatedEntries in - guard let strongSelf = self else { - return - } - - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - var entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp } ?? [] - let updatedEntries = updatedEntries.filter { Double($0.expires) + expiryExtension > timestamp } - - var existingPeerIds: [PeerId: Int] = [:] - var existingSelfPeer: Int? - for i in 0 ..< entries.count { - if case let .peer(id, _, _) = entries[i] { - existingPeerIds[id] = i - } else if case .selfPeer = entries[i] { - existingSelfPeer = i - } - } - - var selfPeer: PeerNearby? - for entry in updatedEntries { - switch entry { - case .selfPeer: - if let index = existingSelfPeer { - entries[index] = entry - } else { - selfPeer = entry - } - case let .peer(id, _, _): - if let index = existingPeerIds[id] { - entries[index] = entry - } else { - entries.append(entry) - } - } - } - - if let peer = selfPeer { - entries.insert(peer, at: 0) - } - - strongSelf.entries = entries - for subscriber in strongSelf.subscribers.copyItems() { - subscriber(strongSelf.entries) - } - })) - - self.timer = SignalKitTimer(timeout: 2.0, repeat: true, completion: { [weak self] in - guard let strongSelf = self else { - return - } - - let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp } - for subscriber in strongSelf.subscribers.copyItems() { - subscriber(strongSelf.entries) - } - }, queue: self.queue) - self.timer?.start() - } - - deinit { - self.disposable.dispose() - self.timer?.invalidate() - } - - public func get() -> Signal<[PeerNearby]?, NoError> { - let queue = self.queue - return Signal { [weak self] subscriber in - if let strongSelf = self { - subscriber.putNext(strongSelf.entries) - - let index = strongSelf.subscribers.add({ entries in - subscriber.putNext(entries) - }) - - return ActionDisposable { - queue.async { - if let strongSelf = self { - strongSelf.subscribers.remove(index) - } - } - } - } else { - return EmptyDisposable - } - } |> runOn(queue) - } -} - -public func updateChannelGeoLocation(postbox: Postbox, network: Network, channelId: PeerId, coordinate: (latitude: Double, longitude: Double)?, address: String?) -> Signal { - return postbox.transaction { transaction -> Peer? in - return transaction.getPeer(channelId) - } - |> mapToSignal { channel -> Signal in - guard let channel = channel, let apiChannel = apiInputChannel(channel) else { - return .single(false) - } - - let geoPoint: Api.InputGeoPoint - if let (latitude, longitude) = coordinate, let _ = address { - geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil)) - } else { - geoPoint = .inputGeoPointEmpty - } - - return network.request(Api.functions.channels.editLocation(channel: apiChannel, geoPoint: geoPoint, address: address ?? "")) - |> map { result -> Bool in - switch result { - case .boolTrue: - return true - case .boolFalse: - return false - } - } - |> `catch` { error -> Signal in - return .single(false) - } - |> mapToSignal { result in - if result { - return postbox.transaction { transaction in - transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in - let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData() - let peerGeoLocation: PeerGeoLocation? - if let (latitude, longitude) = coordinate, let address = address { - peerGeoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address) - } else { - peerGeoLocation = nil - } - return current.withUpdatedPeerGeoLocation(peerGeoLocation) - }) - } - |> map { _ in - return result - } - } else { - return .single(result) - } - } - } -} - -public struct PeersNearbyState: Codable, Equatable { - public var visibilityExpires: Int32? - - public static var `default` = PeersNearbyState(visibilityExpires: nil) - - public init(visibilityExpires: Int32?) { - self.visibilityExpires = visibilityExpires - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: StringCodingKey.self) - - self.visibilityExpires = try container.decodeIfPresent(Int32.self, forKey: "expires") - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: StringCodingKey.self) - - try container.encodeIfPresent(self.visibilityExpires, forKey: "expires") - } -} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/TelegramEnginePeersNearby.swift b/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/TelegramEnginePeersNearby.swift deleted file mode 100644 index 3117401c2c..0000000000 --- a/submodules/TelegramCore/Sources/TelegramEngine/PeersNearby/TelegramEnginePeersNearby.swift +++ /dev/null @@ -1,15 +0,0 @@ -import SwiftSignalKit - -public extension TelegramEngine { - final class PeersNearby { - private let account: Account - - init(account: Account) { - self.account = account - } - - public func updatePeersNearbyVisibility(update: PeerNearbyVisibilityUpdate, background: Bool) -> Signal { - return _internal_updatePeersNearbyVisibility(account: self.account, update: update, background: background) - } - } -} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/ActiveSessionsContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/ActiveSessionsContext.swift index 92900626e2..c2914fea67 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/ActiveSessionsContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/ActiveSessionsContext.swift @@ -287,8 +287,8 @@ public final class ActiveSessionsContext { public struct WebSessionsContextState: Equatable { public var isLoadingMore: Bool public var sessions: [WebAuthorization] - public var peers: [PeerId: Peer] - + public var peers: [EnginePeer.Id: EnginePeer] + public static func ==(lhs: WebSessionsContextState, rhs: WebSessionsContextState) -> Bool { if lhs.isLoadingMore != rhs.isLoadingMore { return false @@ -296,7 +296,7 @@ public struct WebSessionsContextState: Equatable { if lhs.sessions != rhs.sessions { return false } - if !arePeerDictionariesEqual(lhs.peers, rhs.peers) { + if lhs.peers != rhs.peers { return false } return true @@ -342,14 +342,14 @@ public final class WebSessionsContext { } self._state = WebSessionsContextState(isLoadingMore: true, sessions: self._state.sessions, peers: self._state.peers) self.disposable.set((webSessions(network: account.network) - |> map { result -> (sessions: [WebAuthorization], peers: [PeerId: Peer], canLoadMore: Bool) in - return (result.0, result.1, false) + |> map { result -> (sessions: [WebAuthorization], peers: [EnginePeer.Id: EnginePeer], canLoadMore: Bool) in + return (result.0, result.1.mapValues(EnginePeer.init), false) } |> deliverOnMainQueue).start(next: { [weak self] (sessions, peers, canLoadMore) in guard let strongSelf = self else { return } - + strongSelf._state = WebSessionsContextState(isLoadingMore: false, sessions: sessions, peers: peers) })) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeers.swift index 1a300fe33e..a724c1ca7f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/BlockedPeers.swift @@ -4,9 +4,9 @@ import SwiftSignalKit import TelegramApi import MtProtoKit -func _internal_requestUpdatePeerIsBlocked(account: Account, peerId: PeerId, isBlocked: Bool) -> Signal { +func _internal_requestUpdatePeerIsBlocked(account: Account, peerId: PeerId, isBlocked: Bool, sourceMessageId: MessageId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in - if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { + if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer, sourceMessageId: sourceMessageId, transaction: transaction) { let signal: Signal if isBlocked { signal = account.network.request(Api.functions.contacts.block(flags: 0, id: inputPeer)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift index 05061edcee..1a4c8ce215 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/TelegramEnginePrivacy.swift @@ -9,8 +9,8 @@ public extension TelegramEngine { self.account = account } - public func requestUpdatePeerIsBlocked(peerId: PeerId, isBlocked: Bool) -> Signal { - return _internal_requestUpdatePeerIsBlocked(account: self.account, peerId: peerId, isBlocked: isBlocked) + public func requestUpdatePeerIsBlocked(peerId: PeerId, isBlocked: Bool, sourceMessageId: MessageId? = nil) -> Signal { + return _internal_requestUpdatePeerIsBlocked(account: self.account, peerId: peerId, isBlocked: isBlocked, sourceMessageId: sourceMessageId) } public func requestUpdatePeerIsBlockedFromStories(peerId: PeerId, isBlocked: Bool) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift index e51b9a840e..7e68766b5b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/CollectCacheUsageStats.swift @@ -14,15 +14,15 @@ public enum PeerCacheUsageCategory: Int32 { public struct CacheUsageStats { public let media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]] public let mediaResourceIds: [MediaId: [MediaResourceId]] - public let peers: [PeerId: Peer] + public let peers: [EnginePeer.Id: EnginePeer] public let otherSize: Int64 public let otherPaths: [String] public let cacheSize: Int64 public let tempPaths: [String] public let tempSize: Int64 public let immutableSize: Int64 - - public init(media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], mediaResourceIds: [MediaId: [MediaResourceId]], peers: [PeerId: Peer], otherSize: Int64, otherPaths: [String], cacheSize: Int64, tempPaths: [String], tempSize: Int64, immutableSize: Int64) { + + public init(media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], mediaResourceIds: [MediaId: [MediaResourceId]], peers: [EnginePeer.Id: EnginePeer], otherSize: Int64, otherPaths: [String], cacheSize: Int64, tempPaths: [String], tempSize: Int64, immutableSize: Int64) { self.media = media self.mediaResourceIds = mediaResourceIds self.peers = peers @@ -736,7 +736,7 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a continue } if let message = transaction.getMessage(MessageId(peerId: PeerId(reference.peerId), namespace: MessageId.Namespace(reference.messageNamespace), id: reference.messageId)) { - for mediaItem in message.media { + for mediaItem in message.effectiveMedia { guard let mediaId = mediaItem.id else { continue } @@ -894,7 +894,7 @@ func _internal_collectCacheUsageStats(account: Account, peerId: PeerId? = nil, a subscriber.putNext(.result(CacheUsageStats( media: state.media, mediaResourceIds: state.mediaResourceIds, - peers: state.peers, + peers: state.peers.mapValues(EnginePeer.init), otherSize: state.otherSize, otherPaths: state.otherPaths, cacheSize: cacheSize, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index d0758efd41..bedca3980a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -2,6 +2,7 @@ import Foundation import SwiftSignalKit import Postbox import TelegramApi +import RangeSet public enum MediaResourceUserContentType: UInt8, Equatable { case other = 0 @@ -253,24 +254,28 @@ public extension TelegramEngine { return _internal_collectStorageUsageStats(account: self.account) } - public func renderStorageUsageStatsMessages(stats: StorageUsageStats, categories: [StorageUsageStats.CategoryKey], existingMessages: [EngineMessage.Id: Message]) -> Signal<[EngineMessage.Id: Message], NoError> { - return _internal_renderStorageUsageStatsMessages(account: self.account, stats: stats, categories: categories, existingMessages: existingMessages) - } - - public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [Message], excludeMessages: [Message]) -> Signal { - return _internal_clearStorage(account: self.account, peerId: peerId, categories: categories, includeMessages: includeMessages, excludeMessages: excludeMessages) - } - - public func clearStorage(peerIds: Set, includeMessages: [Message], excludeMessages: [Message]) -> Signal { - _internal_clearStorage(account: self.account, peerIds: peerIds, includeMessages: includeMessages, excludeMessages: excludeMessages) - } - - public func clearStorage(messages: [Message]) -> Signal { - _internal_clearStorage(account: self.account, messages: messages) + public func renderStorageUsageStatsMessages(stats: StorageUsageStats, categories: [StorageUsageStats.CategoryKey], existingMessages: [EngineMessage.Id: EngineMessage]) -> Signal<[EngineMessage.Id: EngineMessage], NoError> { + let rawExisting = existingMessages.mapValues { $0._asMessage() } + return _internal_renderStorageUsageStatsMessages(account: self.account, stats: stats, categories: categories, existingMessages: rawExisting) + |> map { rawResult -> [EngineMessage.Id: EngineMessage] in + return rawResult.mapValues(EngineMessage.init) + } } - public func clearCachedMediaResources(mediaResourceIds: Set) -> Signal { - return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds) + public func clearStorage(peerId: EnginePeer.Id?, categories: [StorageUsageStats.CategoryKey], includeMessages: [EngineMessage], excludeMessages: [EngineMessage]) -> Signal { + return _internal_clearStorage(account: self.account, peerId: peerId, categories: categories, includeMessages: includeMessages.map { $0._asMessage() }, excludeMessages: excludeMessages.map { $0._asMessage() }) + } + + public func clearStorage(peerIds: Set, includeMessages: [EngineMessage], excludeMessages: [EngineMessage]) -> Signal { + _internal_clearStorage(account: self.account, peerIds: peerIds, includeMessages: includeMessages.map { $0._asMessage() }, excludeMessages: excludeMessages.map { $0._asMessage() }) + } + + public func clearStorage(messages: [EngineMessage]) -> Signal { + _internal_clearStorage(account: self.account, messages: messages.map { $0._asMessage() }) + } + + public func clearCachedMediaResources(mediaResourceIds: Set) -> Signal { + return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: Set(mediaResourceIds.map { MediaResourceId($0.stringRepresentation) })) } public func reindexCacheInBackground(lowImpact: Bool) -> Signal { @@ -415,5 +420,93 @@ public extension TelegramEngine { public func applicationIcons() -> Signal { return _internal_applicationIcons(account: account) } + + public func fetch( + reference: MediaResourceReference, + userLocation: MediaResourceUserLocation, + userContentType: MediaResourceUserContentType + ) -> Signal { + return fetchedMediaResource( + mediaBox: self.account.postbox.mediaBox, + userLocation: userLocation, + userContentType: userContentType, + reference: reference + ) + } + + public func status( + resource: EngineMediaResource, + approximateSynchronousValue: Bool = false + ) -> Signal { + return self.account.postbox.mediaBox.resourceStatus(resource._asResource(), approximateSynchronousValue: approximateSynchronousValue) + |> map { EngineMediaResource.FetchStatus($0) } + } + + public func status( + id: EngineMediaResource.Id, + resourceSize: Int64 + ) -> Signal { + return self.account.postbox.mediaBox.resourceStatus(MediaResourceId(id.stringRepresentation), resourceSize: resourceSize) + |> map { EngineMediaResource.FetchStatus($0) } + } + + public func data( + resource: EngineMediaResource, + pathExtension: String? = nil, + waitUntilFetchStatus: Bool = false, + incremental: Bool = false, + attemptSynchronously: Bool = false + ) -> Signal { + let option: ResourceDataRequestOption = incremental + ? .incremental(waitUntilFetchStatus: waitUntilFetchStatus) + : .complete(waitUntilFetchStatus: waitUntilFetchStatus) + return self.account.postbox.mediaBox.resourceData( + resource._asResource(), + pathExtension: pathExtension, + option: option, + attemptSynchronously: attemptSynchronously + ) + |> map { EngineMediaResource.ResourceData($0) } + } + + public func shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id) -> String { + return self.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(MediaResourceId(id.stringRepresentation)) + } + + public func completedResourcePath(id: EngineMediaResource.Id, pathExtension: String? = nil) -> String? { + return self.account.postbox.mediaBox.completedResourcePath(id: MediaResourceId(id.stringRepresentation), pathExtension: pathExtension) + } + + public func storeResourceData(id: EngineMediaResource.Id, data: Data, synchronous: Bool = false) { + self.account.postbox.mediaBox.storeResourceData(MediaResourceId(id.stringRepresentation), data: data, synchronous: synchronous) + } + + public func cancelInteractiveResourceFetch(id: EngineMediaResource.Id) { + self.account.postbox.mediaBox.cancelInteractiveResourceFetch(resourceId: MediaResourceId(id.stringRepresentation)) + } + + public func moveResourceData(id: EngineMediaResource.Id, toTempPath: String) { + self.account.postbox.mediaBox.moveResourceData(MediaResourceId(id.stringRepresentation), toTempPath: toTempPath) + } + + public func moveResourceData(from: EngineMediaResource.Id, to: EngineMediaResource.Id, synchronous: Bool = false) { + self.account.postbox.mediaBox.moveResourceData(from: MediaResourceId(from.stringRepresentation), to: MediaResourceId(to.stringRepresentation), synchronous: synchronous) + } + + public func copyResourceData(id: EngineMediaResource.Id, fromTempPath: String) { + self.account.postbox.mediaBox.copyResourceData(MediaResourceId(id.stringRepresentation), fromTempPath: fromTempPath) + } + + public func copyResourceData(from: EngineMediaResource.Id, to: EngineMediaResource.Id, synchronous: Bool = false) { + self.account.postbox.mediaBox.copyResourceData(from: MediaResourceId(from.stringRepresentation), to: MediaResourceId(to.stringRepresentation), synchronous: synchronous) + } + + public func resourceRangesStatus(resource: EngineMediaResource) -> Signal, NoError> { + return self.account.postbox.mediaBox.resourceRangesStatus(resource._asResource()) + } + + public func removeCachedResources(ids: [EngineMediaResource.Id], force: Bool = false, notify: Bool = false) -> Signal { + return self.account.postbox.mediaBox.removeCachedResources(ids.map { MediaResourceId($0.stringRepresentation) }, force: force, notify: notify) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/SecureId/SaveSecureIdValue.swift b/submodules/TelegramCore/Sources/TelegramEngine/SecureId/SaveSecureIdValue.swift index 818d662be9..a629a77757 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/SecureId/SaveSecureIdValue.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/SecureId/SaveSecureIdValue.swift @@ -276,44 +276,6 @@ public func deleteSecureIdValues(network: Network, keys: Set) } } -public func dropSecureId(network: Network, currentPassword: String) -> Signal { - return _internal_twoStepAuthData(network) - |> mapError { _ -> AuthorizationPasswordVerificationError in - return .generic - } - |> mapToSignal { authData -> Signal in - let checkPassword: Api.InputCheckPasswordSRP - if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData { - let kdfResult = passwordKDF(encryptionProvider: network.encryptionProvider, password: currentPassword, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) - if let kdfResult = kdfResult { - checkPassword = .inputCheckPasswordSRP(.init(srpId: kdfResult.id, A: Buffer(data: kdfResult.A), M1: Buffer(data: kdfResult.M1))) - } else { - return .fail(.generic) - } - } else { - checkPassword = .inputCheckPasswordEmpty - } - - let settings = network.request(Api.functions.account.getPasswordSettings(password: checkPassword), automaticFloodWait: false) - |> mapError { error in - return AuthorizationPasswordVerificationError.generic - } - - return settings - |> mapToSignal { value -> Signal in - switch value { - case .passwordSettings: - var flags: Int32 = 0 - flags |= (1 << 2) - return network.request(Api.functions.account.updatePasswordSettings(password: .inputCheckPasswordEmpty, newSettings: .passwordInputSettings(.init(flags: flags, newAlgo: nil, newPasswordHash: nil, hint: nil, email: nil, newSecureSettings: nil))), automaticFloodWait: false) - |> map { _ in } - |> mapError { _ in - return AuthorizationPasswordVerificationError.generic - } - } - } - } -} public enum GetAllSecureIdValuesError { case generic diff --git a/submodules/TelegramCore/Sources/TelegramEngine/SecureId/UploadSecureIdFile.swift b/submodules/TelegramCore/Sources/TelegramEngine/SecureId/UploadSecureIdFile.swift index fbc091f82e..0ee5c27c2d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/SecureId/UploadSecureIdFile.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/SecureId/UploadSecureIdFile.swift @@ -87,24 +87,24 @@ func decryptedSecureIdFile(context: SecureIdAccessContext, encryptedData: Data, return unpaddedFileData } -public func uploadSecureIdFile(context: SecureIdAccessContext, postbox: Postbox, network: Network, resource: MediaResource) -> Signal { - return postbox.mediaBox.resourceData(resource) +public func uploadSecureIdFile(context: SecureIdAccessContext, engine: TelegramEngine, resource: EngineMediaResource) -> Signal { + return engine.account.postbox.mediaBox.resourceData(resource._asResource()) |> mapError { _ -> UploadSecureIdFileError in } |> mapToSignal { next -> Signal in if !next.complete { return .complete() } - + guard let data = try? Data(contentsOf: URL(fileURLWithPath: next.path)) else { return .fail(.generic) } - + guard let encryptedData = encryptedSecureIdFile(context: context, data: data) else { return .fail(.generic) } - - return multipartUpload(network: network, postbox: postbox, source: .data(encryptedData.data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) + + return multipartUpload(network: engine.account.network, postbox: engine.account.postbox, source: .data(encryptedData.data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image, userContentType: .image), hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false) |> mapError { _ -> UploadSecureIdFileError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index ca0da2cf99..9d1ae5baae 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -6,7 +6,7 @@ import MtProtoKit public enum UploadStickerStatus { case progress(Float) - case complete(CloudDocumentMediaResource, String) + case complete(EngineMediaResource, String) } public enum UploadStickerError { @@ -94,7 +94,7 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour if let thumbnail, let previewRepresentation = file.previewRepresentations.first(where: { $0.dimensions == PixelDimensions(width: 320, height: 320) }) { account.postbox.mediaBox.copyResourceData(from: thumbnail.id, to: previewRepresentation.resource.id, synchronous: true) } - return .single(.complete(uploadedResource, file.mimeType)) + return .single(.complete(EngineMediaResource(uploadedResource), file.mimeType)) } default: break @@ -201,7 +201,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri } for sticker in stickers { if let resource = sticker.resource.resource as? CloudDocumentMediaResource { - uploadStickers.append(.single(.complete(resource, sticker.mimeType))) + uploadStickers.append(.single(.complete(EngineMediaResource(resource), sticker.mimeType))) } else { uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, thumbnail: sticker.thumbnailResource?.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, duration: sticker.duration, mimeType: sticker.mimeType) |> mapError { _ -> CreateStickerSetError in @@ -213,8 +213,8 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri |> mapToSignal { uploadedStickers -> Signal in var resources: [CloudDocumentMediaResource] = [] for sticker in uploadedStickers { - if case let .complete(resource, _) = sticker { - resources.append(resource) + if case let .complete(resource, _) = sticker, let rawResource = resource._asResource() as? CloudDocumentMediaResource { + resources.append(rawResource) } } if resources.count == stickers.count { @@ -368,7 +368,7 @@ private func revalidatedSticker(account: Account, sticker: FileMediaReference func _internal_addStickerToStickerSet(account: Account, packReference: StickerPackReference, sticker: ImportSticker) -> Signal { let uploadSticker: Signal if let resource = sticker.resource.resource as? CloudDocumentMediaResource { - uploadSticker = .single(.complete(resource, sticker.mimeType)) + uploadSticker = .single(.complete(EngineMediaResource(resource), sticker.mimeType)) } else { uploadSticker = account.postbox.loadedPeerWithId(account.peerId) |> castError(AddStickerToSetError.self) @@ -381,15 +381,15 @@ func _internal_addStickerToStickerSet(account: Account, packReference: StickerPa } return uploadSticker |> mapToSignal { uploadedSticker in - guard case let .complete(resource, _) = uploadedSticker else { + guard case let .complete(resource, _) = uploadedSticker, let rawResource = resource._asResource() as? CloudDocumentMediaResource else { return .complete() } - + var flags: Int32 = 0 if sticker.keywords.count > 0 { flags |= (1 << 1) } - let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(.init(flags: flags, document: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)) + let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(.init(flags: flags, document: .inputDocument(.init(id: rawResource.fileId, accessHash: rawResource.accessHash, fileReference: Buffer(data: rawResource.fileReference ?? Data()))), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)) return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker)) |> `catch` { error -> Signal in if error.errorDescription == "FILE_REFERENCE_EXPIRED" { @@ -489,7 +489,7 @@ public enum ReplaceStickerError { func _internal_replaceSticker(account: Account, previousSticker: FileMediaReference, sticker: ImportSticker) -> Signal { let uploadSticker: Signal if let resource = sticker.resource.resource as? CloudDocumentMediaResource { - uploadSticker = .single(.complete(resource, sticker.mimeType)) + uploadSticker = .single(.complete(EngineMediaResource(resource), sticker.mimeType)) } else { uploadSticker = account.postbox.loadedPeerWithId(account.peerId) |> castError(ReplaceStickerError.self) @@ -502,14 +502,14 @@ func _internal_replaceSticker(account: Account, previousSticker: FileMediaRefere } return uploadSticker |> mapToSignal { uploadedSticker in - guard case let .complete(resource, _) = uploadedSticker else { + guard case let .complete(resource, _) = uploadedSticker, let rawResource = resource._asResource() as? CloudDocumentMediaResource else { return .complete() } var flags: Int32 = 0 if sticker.keywords.count > 0 { flags |= (1 << 1) } - let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(.init(flags: flags, document: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)) + let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(.init(flags: flags, document: .inputDocument(.init(id: rawResource.fileId, accessHash: rawResource.accessHash, fileReference: Buffer(data: rawResource.fileReference ?? Data()))), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)) return revalidatedSticker(account: account, sticker: previousSticker, signal: { previousResource in return account.network.request(Api.functions.stickers.replaceSticker(sticker: .inputDocument(.init(id: previousResource.fileId, accessHash: previousResource.accessHash, fileReference: Buffer(data: previousResource.fileReference))), newSticker: inputSticker)) }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index a4bb852591..6f80a4270f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -58,6 +58,665 @@ public struct SearchStickersScope: OptionSet { public static let remote = SearchStickersScope(rawValue: 1 << 1) } +private struct StickerSearchContextInitialData { + let localItems: [FoundStickerItem] + let cached: CachedStickerQueryResult? + let isPremium: Bool + let searchStickersConfiguration: SearchStickersConfiguration + let cacheKey: String +} + +private func normalizedStickerSearchEmoticon(_ emoticon: [String]) -> [String] { + if emoticon == ["\u{2764}"] { + return ["\u{2764}\u{FE0F}"] + } else { + return emoticon + } +} + +private func stickerSearchCacheKey(query: String?, emoticon: [String]) -> String { + if let query, !query.isEmpty { + return query + } else { + return emoticon.sorted().joined() + } +} + +private func stickerSearchCombinedItems(localItems: [FoundStickerItem], files: [TelegramMediaFile], isPremium: Bool, searchStickersConfiguration: SearchStickersConfiguration) -> [FoundStickerItem] { + var premiumItems: [FoundStickerItem] = [] + var otherItems: [FoundStickerItem] = [] + for item in localItems { + if item.file.isPremiumSticker { + premiumItems.append(item) + } else { + otherItems.append(item) + } + } + + var foundItems: [FoundStickerItem] = [] + var foundAnimatedItems: [FoundStickerItem] = [] + var foundPremiumItems: [FoundStickerItem] = [] + + var currentItemIds = Set(localItems.map { $0.file.fileId }) + for file in files { + if currentItemIds.contains(file.fileId) { + continue + } + currentItemIds.insert(file.fileId) + + let item = FoundStickerItem(file: file, stringRepresentations: []) + if file.isPremiumSticker { + foundPremiumItems.append(item) + } else if file.isAnimatedSticker || file.isVideoSticker { + foundAnimatedItems.append(item) + } else { + foundItems.append(item) + } + } + + let allPremiumItems = premiumItems + foundPremiumItems + let allOtherItems = otherItems + foundAnimatedItems + foundItems + + var result: [FoundStickerItem] = [] + if isPremium { + let batchCount = Int(searchStickersConfiguration.normalStickersPerPremiumCount) + if batchCount == 0 { + result.append(contentsOf: allPremiumItems) + result.append(contentsOf: allOtherItems) + } else if allPremiumItems.isEmpty { + result.append(contentsOf: allOtherItems) + } else { + var i = 0 + for premiumItem in allPremiumItems { + if i < allOtherItems.count { + for j in i ..< min(i + batchCount, allOtherItems.count) { + result.append(allOtherItems[j]) + } + i += batchCount + } + result.append(premiumItem) + } + if i < allOtherItems.count { + for j in i ..< allOtherItems.count { + result.append(allOtherItems[j]) + } + } + } + } else { + result.append(contentsOf: allOtherItems) + result.append(contentsOf: allPremiumItems.prefix(max(0, Int(searchStickersConfiguration.premiumStickersCount)))) + } + return result +} + +private func stickerSearchApiFiles(_ stickers: [Api.Document]) -> [TelegramMediaFile] { + var files: [TelegramMediaFile] = [] + for sticker in stickers { + if let file = telegramMediaFileFromApiDocument(sticker, altDocuments: []), let _ = file.id { + files.append(file) + } + } + return files +} + +private func stickerSearchContextInitialData(account: Account, query: String, emoticon: [String], scope: SearchStickersScope) -> Signal { + let cacheKey = stickerSearchCacheKey(query: query, emoticon: emoticon) + + return account.postbox.transaction { transaction -> StickerSearchContextInitialData in + let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false + + var result: [FoundStickerItem] = [] + if scope.contains(.installed) { + for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) { + if let item = entry.contents.get(SavedStickerItem.self) { + for representation in item.stringRepresentations { + for queryItem in emoticon { + if representation.hasPrefix(queryItem) { + result.append(FoundStickerItem(file: item.file._parse(), stringRepresentations: item.stringRepresentations)) + break + } + } + } + } + } + + var currentItems = Set(result.map { $0.file.fileId }) + var recentItems: [TelegramMediaFile] = [] + var recentAnimatedItems: [TelegramMediaFile] = [] + var recentItemsIds = Set() + var matchingRecentItemsIds = Set() + + for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) { + if let item = entry.contents.get(RecentMediaItem.self) { + let file = item.media + if file.isPremiumSticker && !isPremium { + continue + } + + if !currentItems.contains(file.fileId) { + currentItems.insert(file.fileId) + + if let displayText = file.stickerDisplayText { + for queryItem in emoticon { + if displayText.hasPrefix(queryItem) { + matchingRecentItemsIds.insert(file.fileId) + break + } + } + recentItemsIds.insert(file.fileId) + if file.isAnimatedSticker || file.isVideoSticker { + recentAnimatedItems.append(file._parse()) + } else { + recentItems.append(file._parse()) + } + break + } + } + } + } + + let searchQueries: [ItemCollectionSearchQuery] = emoticon.map { queryItem -> ItemCollectionSearchQuery in + return .exact(ValueBoxKey(queryItem)) + } + + var installedItems: [FoundStickerItem] = [] + var installedAnimatedItems: [FoundStickerItem] = [] + var installedPremiumItems: [FoundStickerItem] = [] + + for searchQuery in searchQueries { + for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) { + if let item = item as? StickerPackItem { + if !currentItems.contains(item.file.fileId) { + currentItems.insert(item.file.fileId) + + let stringRepresentations = item.getStringRepresentationsOfIndexKeys() + if !recentItemsIds.contains(item.file.fileId) { + if item.file.isPremiumSticker { + installedPremiumItems.append(FoundStickerItem(file: item.file._parse(), stringRepresentations: stringRepresentations)) + } else if item.file.isAnimatedSticker || item.file.isVideoSticker { + installedAnimatedItems.append(FoundStickerItem(file: item.file._parse(), stringRepresentations: stringRepresentations)) + } else { + installedItems.append(FoundStickerItem(file: item.file._parse(), stringRepresentations: stringRepresentations)) + } + } else { + matchingRecentItemsIds.insert(item.file.fileId) + if item.file.isAnimatedSticker || item.file.isVideoSticker { + recentAnimatedItems.append(item.file._parse()) + } else { + recentItems.append(item.file._parse()) + } + } + } + } + } + } + + for file in recentAnimatedItems { + if file.isPremiumSticker && !isPremium { + continue + } + if matchingRecentItemsIds.contains(file.fileId) { + result.append(FoundStickerItem(file: file, stringRepresentations: emoticon)) + } + } + + for file in recentItems { + if file.isPremiumSticker && !isPremium { + continue + } + if matchingRecentItemsIds.contains(file.fileId) { + result.append(FoundStickerItem(file: file, stringRepresentations: emoticon)) + } + } + + result.append(contentsOf: installedPremiumItems) + result.append(contentsOf: installedAnimatedItems) + result.append(contentsOf: installedItems) + } + + var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)))?.get(CachedStickerQueryResult.self) + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let searchStickersConfiguration = SearchStickersConfiguration.with(appConfiguration: appConfiguration) + + if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout { + cached = nil + } + + return StickerSearchContextInitialData( + localItems: result, + cached: cached, + isPremium: isPremium, + searchStickersConfiguration: searchStickersConfiguration, + cacheKey: cacheKey + ) + } +} + +private func stickerSearchContextPage(account: Account, query: String, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope = [.installed, .remote], offset: Int32) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?), NoError> { + if scope.isEmpty { + return .single(([], true, nil)) + } + + let emoticon = normalizedStickerSearchEmoticon(emoticon) + if offset > 0 { + if !scope.contains(.remote) { + return .single(([], true, nil)) + } + + let flags: Int32 = 0 + return account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: offset, limit: 128, hash: 0)) + |> `catch` { _ -> Signal in + return .single(.foundStickersNotModified(.init(flags: 0, nextOffset: nil))) + } + |> map { result -> (items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?) in + switch result { + case let .foundStickers(foundStickersData): + let files = stickerSearchApiFiles(foundStickersData.stickers) + return (files.map { FoundStickerItem(file: $0, stringRepresentations: []) }, true, foundStickersData.nextOffset) + case let .foundStickersNotModified(data): + return ([], true, data.nextOffset) + } + } + } + + return stickerSearchContextInitialData(account: account, query: query, emoticon: emoticon, scope: scope) + |> mapToSignal { initialData -> Signal<(items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?), NoError> in + if !scope.contains(.remote) { + return .single((initialData.localItems, true, nil)) + } + + let tempResult: [FoundStickerItem] + if let cached = initialData.cached { + tempResult = stickerSearchCombinedItems(localItems: initialData.localItems, files: cached.items, isPremium: initialData.isPremium, searchStickersConfiguration: initialData.searchStickersConfiguration) + } else { + tempResult = initialData.localItems + } + + let flags: Int32 = 0 + let remote = account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: 0, limit: 128, hash: initialData.cached?.hash ?? 0)) + |> `catch` { _ -> Signal in + return .single(.foundStickersNotModified(.init(flags: 0, nextOffset: nil))) + } + |> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?), NoError> in + return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?) in + switch result { + case let .foundStickers(foundStickersData): + let files = stickerSearchApiFiles(foundStickersData.stickers) + let resultItems = stickerSearchCombinedItems(localItems: initialData.localItems, files: files, isPremium: initialData.isPremium, searchStickersConfiguration: initialData.searchStickersConfiguration) + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if foundStickersData.hash != 0, let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: foundStickersData.hash, timestamp: currentTime)) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(initialData.cacheKey)), entry: entry) + } + + return (resultItems, true, foundStickersData.nextOffset) + case let .foundStickersNotModified(data): + return (tempResult, true, data.nextOffset) + } + } + } + + return .single((tempResult, false, nil)) + |> then( + remote + |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + ) + } +} + +public final class StickerSearchContext { + public struct State: Equatable { + public let items: [FoundStickerItem] + public let canLoadMore: Bool + public let isLoadingMore: Bool + + public init(items: [FoundStickerItem], canLoadMore: Bool, isLoadingMore: Bool) { + self.items = items + self.canLoadMore = canLoadMore + self.isLoadingMore = isLoadingMore + } + } + + private let queue: Queue = .mainQueue() + private let account: Account + private let query: String? + private let emoticon: [String] + private let inputLanguageCode: String + private let scope: SearchStickersScope + + private let disposable = MetaDisposable() + + private var items: [FoundStickerItem] = [] + private var nextOffset: Int32? + private var canLoadMore = false + private var isLoadingMore = false + + private let stateValue = Promise() + public var state: Signal { + return self.stateValue.get() + } + + init(account: Account, query: String?, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope) { + self.account = account + self.query = query + self.emoticon = normalizedStickerSearchEmoticon(emoticon) + self.inputLanguageCode = inputLanguageCode + self.scope = scope + + self.loadInitial() + } + + deinit { + self.disposable.dispose() + } + + public func loadMore() { + guard let query = self.query, !query.isEmpty else { + return + } + guard !self.isLoadingMore, self.canLoadMore, let nextOffset = self.nextOffset else { + return + } + + self.isLoadingMore = true + self.pushState() + + self.disposable.set((stickerSearchContextPage(account: self.account, query: query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope, offset: nextOffset) + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + var currentItemIds = Set(self.items.map { $0.file.fileId }) + for item in result.items { + if currentItemIds.contains(item.file.fileId) { + continue + } + currentItemIds.insert(item.file.fileId) + self.items.append(item) + } + self.nextOffset = result.nextOffset + self.canLoadMore = result.nextOffset != nil + self.isLoadingMore = false + self.pushState() + })) + } + + private func loadInitial() { + self.items = [] + self.nextOffset = nil + self.canLoadMore = false + self.isLoadingMore = true + self.pushState() + + let signal: Signal<(items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?), NoError> + if let query = self.query, !query.isEmpty { + signal = stickerSearchContextPage(account: self.account, query: query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope, offset: 0) + } else { + signal = _internal_searchStickers(account: self.account, query: self.query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope) + |> map { result -> (items: [FoundStickerItem], isFinalResult: Bool, nextOffset: Int32?) in + return (result.items, result.isFinalResult, nil) + } + } + + self.disposable.set((signal + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + + self.items = result.items + self.nextOffset = result.nextOffset + self.canLoadMore = result.nextOffset != nil + self.isLoadingMore = !result.isFinalResult + self.pushState() + })) + } + + private func pushState() { + self.stateValue.set(.single(State(items: self.items, canLoadMore: self.canLoadMore, isLoadingMore: self.isLoadingMore))) + } +} + +private struct EmojiSearchContextInitialData { + let localItems: [TelegramMediaFile] + let cached: CachedStickerQueryResult? + let cacheKey: String +} + +private func emojiSearchAppendUniqueItems(currentItems: [TelegramMediaFile], files: [TelegramMediaFile]) -> [TelegramMediaFile] { + var result = currentItems + var currentItemIds = Set(currentItems.map { $0.fileId }) + for file in files { + if currentItemIds.contains(file.fileId) { + continue + } + currentItemIds.insert(file.fileId) + result.append(file) + } + return result +} + +private func emojiSearchContextInitialData(account: Account, query: String, emoticon: [String], scope: SearchStickersScope) -> Signal { + let cacheKey = stickerSearchCacheKey(query: query, emoticon: emoticon) + let querySet = Set(emoticon) + + return account.postbox.transaction { transaction -> EmojiSearchContextInitialData in + var result: [TelegramMediaFile] = [] + if scope.contains(.installed) { + var currentItems = Set() + for info in transaction.getItemCollectionsInfos(namespace: Namespaces.ItemCollection.CloudEmojiPacks) { + if let info = info.1 as? StickerPackCollectionInfo { + let items = transaction.getItemCollectionItems(collectionId: info.id) + for item in items { + if let item = item as? StickerPackItem { + let file = item.file + if currentItems.contains(file.fileId) { + continue + } + + let stringRepresentations = item.getStringRepresentationsOfIndexKeys() + for stringRepresentation in stringRepresentations { + if querySet.contains(stringRepresentation) { + currentItems.insert(file.fileId) + result.append(file._parse()) + break + } + } + } + } + } + } + } + + var cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(cacheKey)))?.get(CachedStickerQueryResult.self) + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let searchStickersConfiguration = SearchStickersConfiguration.with(appConfiguration: appConfiguration) + + if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout { + cached = nil + } + + return EmojiSearchContextInitialData(localItems: result, cached: cached, cacheKey: cacheKey) + } +} + +private func emojiSearchContextPage(account: Account, query: String, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope, offset: Int32) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?), NoError> { + if offset != 0 { + guard scope.contains(.remote) else { + return .single(([], true, nil)) + } + + let flags: Int32 = 1 << 0 + return account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: offset, limit: 128, hash: 0)) + |> `catch` { _ -> Signal in + return .single(.foundStickersNotModified(.init(flags: 0, nextOffset: nil))) + } + |> map { result -> (items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?) in + switch result { + case let .foundStickers(foundStickersData): + return (stickerSearchApiFiles(foundStickersData.stickers), true, foundStickersData.nextOffset) + case let .foundStickersNotModified(data): + return ([], true, data.nextOffset) + } + } + } + + return emojiSearchContextInitialData(account: account, query: query, emoticon: emoticon, scope: scope) + |> mapToSignal { initialData -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?), NoError> in + if !scope.contains(.remote) { + return .single((initialData.localItems, true, nil)) + } + + let tempResult: [TelegramMediaFile] + if let cached = initialData.cached { + tempResult = emojiSearchAppendUniqueItems(currentItems: initialData.localItems, files: cached.items) + } else { + tempResult = initialData.localItems + } + + let flags: Int32 = 1 << 0 + let remote = account.network.request(Api.functions.messages.searchStickers(flags: flags, q: query, emoticon: emoticon.joined(separator: ""), langCode: [inputLanguageCode], offset: 0, limit: 128, hash: initialData.cached?.hash ?? 0)) + |> `catch` { _ -> Signal in + return .single(.foundStickersNotModified(.init(flags: 0, nextOffset: nil))) + } + |> mapToSignal { result -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?), NoError> in + return account.postbox.transaction { transaction -> (items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?) in + switch result { + case let .foundStickers(foundStickersData): + let files = stickerSearchApiFiles(foundStickersData.stickers) + let resultItems = emojiSearchAppendUniqueItems(currentItems: initialData.localItems, files: files) + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: foundStickersData.hash, timestamp: currentTime)) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedEmojiQueryResults, key: CachedStickerQueryResult.cacheKey(initialData.cacheKey)), entry: entry) + } + + return (resultItems, true, foundStickersData.nextOffset) + case let .foundStickersNotModified(data): + return (tempResult, true, data.nextOffset) + } + } + } + + return .single((tempResult, false, nil)) + |> then( + remote + |> delay(0.2, queue: Queue.concurrentDefaultQueue()) + ) + } +} + +public final class EmojiSearchContext { + public struct State: Equatable { + public let items: [TelegramMediaFile] + public let canLoadMore: Bool + public let isLoadingMore: Bool + + public init(items: [TelegramMediaFile], canLoadMore: Bool, isLoadingMore: Bool) { + self.items = items + self.canLoadMore = canLoadMore + self.isLoadingMore = isLoadingMore + } + } + + private let queue: Queue = .mainQueue() + private let account: Account + private let query: String? + private let emoticon: [String] + private let inputLanguageCode: String + private let scope: SearchStickersScope + + private let disposable = MetaDisposable() + + private var items: [TelegramMediaFile] = [] + private var nextOffset: Int32? + private var canLoadMore = false + private var isLoadingMore = false + + private let stateValue = Promise() + public var state: Signal { + return self.stateValue.get() + } + + init(account: Account, query: String?, emoticon: [String], inputLanguageCode: String, scope: SearchStickersScope) { + self.account = account + self.query = query + self.emoticon = normalizedStickerSearchEmoticon(emoticon) + self.inputLanguageCode = inputLanguageCode + self.scope = scope + + self.loadInitial() + } + + deinit { + self.disposable.dispose() + } + + public func loadMore() { + guard let query = self.query, !query.isEmpty else { + return + } + guard !self.isLoadingMore, self.canLoadMore, let nextOffset = self.nextOffset else { + return + } + + self.isLoadingMore = true + self.pushState() + + self.disposable.set((emojiSearchContextPage(account: self.account, query: query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope, offset: nextOffset) + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + + self.items = emojiSearchAppendUniqueItems(currentItems: self.items, files: result.items) + self.nextOffset = result.nextOffset + self.canLoadMore = result.nextOffset != nil + self.isLoadingMore = false + self.pushState() + })) + } + + private func loadInitial() { + self.items = [] + self.nextOffset = nil + self.canLoadMore = false + self.isLoadingMore = true + self.pushState() + + let signal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?), NoError> + if let query = self.query, !query.isEmpty { + signal = emojiSearchContextPage(account: self.account, query: query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope, offset: 0) + } else { + signal = _internal_searchEmoji(account: self.account, query: self.query, emoticon: self.emoticon, inputLanguageCode: self.inputLanguageCode, scope: self.scope) + |> map { result -> (items: [TelegramMediaFile], isFinalResult: Bool, nextOffset: Int32?) in + return (result.items.map(\.file), result.isFinalResult, nil) + } + } + + self.disposable.set((signal + |> deliverOn(self.queue)).start(next: { [weak self] result in + guard let self else { + return + } + + self.items = result.items + self.nextOffset = result.nextOffset + self.canLoadMore = result.nextOffset != nil + self.isLoadingMore = !result.isFinalResult + self.pushState() + })) + } + + private func pushState() { + self.stateValue.set(.single(State(items: self.items, canLoadMore: self.canLoadMore, isLoadingMore: self.isLoadingMore))) + } +} + func _internal_randomGreetingSticker(account: Account) -> Signal { let key: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudGreetingStickers) return account.postbox.combinedView(keys: [key]) @@ -85,11 +744,7 @@ func _internal_searchStickers(account: Account, query: String?, emoticon: [Strin if scope.isEmpty { return .single(([], true)) } - var emoticon = emoticon - if emoticon == ["\u{2764}"] { - emoticon = ["\u{2764}\u{FE0F}"] - } - + let emoticon = normalizedStickerSearchEmoticon(emoticon) let cacheKey: String if let query, !query.isEmpty { cacheKey = query @@ -493,9 +1148,7 @@ func _internal_searchStickers(account: Account, category: EmojiSearchCategories. } var query = category.identifiers - if query == ["\u{2764}"] { - query = ["\u{2764}\u{FE0F}"] - } + query = normalizedStickerSearchEmoticon(query) return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false @@ -565,7 +1218,7 @@ func _internal_searchStickers(account: Account, category: EmojiSearchCategories. var searchQueries: [ItemCollectionSearchQuery] = query.map { queryItem -> ItemCollectionSearchQuery in return .exact(ValueBoxKey(queryItem)) } - if query == ["\u{2764}"] { + if query == ["\u{2764}\u{FE0F}"] { searchQueries = [.any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])] } @@ -877,11 +1530,8 @@ func _internal_searchEmoji(account: Account, query: String?, emoticon: [String], if scope.isEmpty { return .single(([], true)) } - var emoticon = emoticon - if emoticon == ["\u{2764}"] { - emoticon = ["\u{2764}\u{FE0F}"] - } - + + let emoticon = normalizedStickerSearchEmoticon(emoticon) let cacheKey: String if let query, !query.isEmpty { cacheKey = query @@ -1235,14 +1885,3 @@ func _internal_searchGifs(account: Account, query: String, nextOffset: String = } } } - -extension TelegramMediaFile { - var stickerString: String? { - for attr in attributes { - if case let .Sticker(displayText, _, _) = attr { - return displayText - } - } - return nil - } -} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift index 359a1a6a58..f622cbc6f5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/StickerPackInteractiveOperations.swift @@ -2,26 +2,28 @@ import Foundation import Postbox import SwiftSignalKit +public struct AddStickerPackResult { +} -func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal { - return postbox.transaction { transaction -> Void in +func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil, noDelay: Bool) -> Signal { + return postbox.transaction { transaction -> AddStickerPackResult in let namespace: SynchronizeInstalledStickerPacksOperationNamespace? switch info.id.namespace { - case Namespaces.ItemCollection.CloudStickerPacks: - namespace = .stickers - case Namespaces.ItemCollection.CloudMaskPacks: - namespace = .masks - case Namespaces.ItemCollection.CloudEmojiPacks: - namespace = .emoji - default: - namespace = nil + case Namespaces.ItemCollection.CloudStickerPacks: + namespace = .stickers + case Namespaces.ItemCollection.CloudMaskPacks: + namespace = .masks + case Namespaces.ItemCollection.CloudEmojiPacks: + namespace = .emoji + default: + namespace = nil } - if let namespace = namespace { + if let namespace { var mappedInfo = info if items.isEmpty { mappedInfo = StickerPackCollectionInfo(id: info.id, flags: info.flags, accessHash: info.accessHash, title: info.title, shortName: info.shortName, thumbnail: info.thumbnail, thumbnailFileId: info.thumbnailFileId, immediateThumbnailData: info.immediateThumbnailData, hash: Int32(bitPattern: arc4random()), count: info.count) } - addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: items.isEmpty) + addSynchronizeInstalledStickerPacksOperation(transaction: transaction, namespace: namespace, content: .add([mappedInfo.id]), noDelay: noDelay) var updatedInfos = transaction.getItemCollectionsInfos(namespace: mappedInfo.id.namespace).map { $0.1 as! StickerPackCollectionInfo } if let index = updatedInfos.firstIndex(where: { $0.id == mappedInfo.id }) { let currentInfo = updatedInfos[index] @@ -42,6 +44,10 @@ func _internal_addStickerPackInteractively(postbox: Postbox, info: StickerPackCo transaction.replaceItemCollectionItems(collectionId: mappedInfo.id, items: indexedItems) } transaction.replaceItemCollectionInfos(namespace: mappedInfo.id.namespace, itemCollectionInfos: updatedInfos.map { ($0.id, $0) }) + + return AddStickerPackResult() + } else { + return AddStickerPackResult() } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index f107a788b8..332ca02758 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -34,6 +34,10 @@ public extension TelegramEngine { return _internal_searchStickers(account: self.account, query: query, emoticon: emoticon, inputLanguageCode: inputLanguageCode, scope: scope) } + public func stickerSearchContext(query: String?, emoticon: [String], inputLanguageCode: String = "", scope: SearchStickersScope = [.installed, .remote]) -> StickerSearchContext { + return StickerSearchContext(account: self.account, query: query, emoticon: emoticon, inputLanguageCode: inputLanguageCode, scope: scope) + } + public func searchStickers(category: EmojiSearchCategories.Group, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { return _internal_searchStickers(account: self.account, category: category, scope: scope) } @@ -58,8 +62,8 @@ public extension TelegramEngine { return _internal_searchGifs(account: self.account, query: query, nextOffset: nextOffset) } - public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil) -> Signal { - return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList) + public func addStickerPackInteractively(info: StickerPackCollectionInfo, items: [StickerPackItem], positionInList: Int? = nil, noDelay: Bool = false) -> Signal { + return _internal_addStickerPackInteractively(postbox: self.account.postbox, info: info, items: items, positionInList: positionInList, noDelay: noDelay) } public func removeStickerPackInteractively(id: ItemCollectionId, option: RemoveStickerPackOption) -> Signal<(Int, [ItemCollectionItem])?, NoError> { @@ -82,8 +86,8 @@ public extension TelegramEngine { return _internal_stickerPacksAttachedToMedia(account: self.account, media: media) } - public func uploadSticker(peer: Peer, resource: MediaResource, thumbnail: MediaResource?, alt: String, dimensions: PixelDimensions, duration: Double?, mimeType: String) -> Signal { - return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, thumbnail: thumbnail, alt: alt, dimensions: dimensions, duration: duration, mimeType: mimeType) + public func uploadSticker(peer: EnginePeer, resource: EngineMediaResource, thumbnail: EngineMediaResource?, alt: String, dimensions: PixelDimensions, duration: Double?, mimeType: String) -> Signal { + return _internal_uploadSticker(account: self.account, peer: peer._asPeer(), resource: resource._asResource(), thumbnail: thumbnail?._asResource(), alt: alt, dimensions: dimensions, duration: duration, mimeType: mimeType) } public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, type: CreateStickerSetType, software: String?) -> Signal { @@ -294,6 +298,10 @@ public extension TelegramEngine { } } + public func emojiSearchContext(query: String?, emoticon: [String], inputLanguageCode: String = "", scope: SearchStickersScope = [.installed, .remote]) -> EmojiSearchContext { + return EmojiSearchContext(account: self.account, query: query, emoticon: emoticon, inputLanguageCode: inputLanguageCode, scope: scope) + } + public func searchEmoji(category: EmojiSearchCategories.Group) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> { return _internal_searchEmoji(account: self.account, query: nil, emoticon: category.identifiers, inputLanguageCode: "") |> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift index 35a49e9ea3..a58a26756c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/TelegramEngine.swift @@ -1,3 +1,4 @@ +import Foundation import SwiftSignalKit import Postbox @@ -12,10 +13,6 @@ public final class TelegramEngine { return SecureId(account: self.account) }() - public lazy var peersNearby: PeersNearby = { - return PeersNearby(account: self.account) - }() - public lazy var payments: Payments = { return Payments(account: self.account) }() @@ -79,7 +76,11 @@ public final class TelegramEngine { public lazy var orderedLists: OrderedLists = { return OrderedLists(account: self.account) }() - + + public lazy var itemCollections: ItemCollections = { + return ItemCollections(account: self.account) + }() + public lazy var itemCache: ItemCache = { return ItemCache(account: self.account) }() @@ -107,16 +108,34 @@ public final class TelegramEngineUnauthorized { public lazy var localization: Localization = { return Localization(account: self.account) }() - + public lazy var payments: Payments = { return Payments(account: self.account) }() + + public lazy var resources: UnauthorizedResources = { + return UnauthorizedResources(account: self.account) + }() public lazy var itemCache: ItemCache = { return ItemCache(account: self.account) }() } +public extension TelegramEngineUnauthorized { + final class UnauthorizedResources { + private let account: UnauthorizedAccount + + init(account: UnauthorizedAccount) { + self.account = account + } + + public func storeResourceData(id: EngineMediaResource.Id, data: Data, synchronous: Bool = false) { + self.account.postbox.mediaBox.storeResourceData(MediaResourceId(id.stringRepresentation), data: data, synchronous: synchronous) + } + } +} + public enum SomeTelegramEngine { case unauthorized(TelegramEngineUnauthorized) case authorized(TelegramEngine) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index b6b890c3a7..c4c5f760c8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -12,6 +12,14 @@ public extension TelegramEngine { public func getChatThemes(accountManager: AccountManager, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> { return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached) } + + public func themes(accountManager: AccountManager, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { + return _internal_telegramThemes(postbox: self.account.postbox, network: self.account.network, accountManager: accountManager, forceUpdate: forceUpdate) + } + + public func wallpapers(forceUpdate: Bool = false) -> Signal<[TelegramWallpaper], NoError> { + return _internal_telegramWallpapers(postbox: self.account.postbox, network: self.account.network, forceUpdate: forceUpdate) + } public func setChatTheme(peerId: PeerId, chatTheme: ChatTheme?) -> Signal { return _internal_setChatTheme(account: self.account, peerId: peerId, chatTheme: chatTheme) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Utils/EnginePostboxCoding.swift b/submodules/TelegramCore/Sources/TelegramEngine/Utils/EnginePostboxCoding.swift new file mode 100644 index 0000000000..22c3243c68 --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Utils/EnginePostboxCoding.swift @@ -0,0 +1,90 @@ +import Postbox + +public typealias EngineMemoryBuffer = MemoryBuffer +public typealias EnginePostboxDecoder = PostboxDecoder +public typealias EnginePostboxEncoder = PostboxEncoder +public typealias EngineAdaptedPostboxDecoder = AdaptedPostboxDecoder +public typealias EngineAdaptedPostboxEncoder = AdaptedPostboxEncoder +public typealias EngineItemCollectionId = ItemCollectionId +public typealias EngineStoryId = StoryId +public typealias EngineFetchResourceSourceType = FetchResourceSourceType +public typealias EngineFetchResourceError = FetchResourceError +public typealias EngineCodableEntry = CodableEntry +public typealias EngineNoticeEntryKey = NoticeEntryKey +public typealias EngineChatListIndex = ChatListIndex +public typealias EngineTempBoxFile = TempBoxFile +public typealias EngineItemCollectionItemIndex = ItemCollectionItemIndex +public typealias EngineItemCollectionViewEntryIndex = ItemCollectionViewEntryIndex +public typealias EngineValueBoxEncryptionParameters = ValueBoxEncryptionParameters +public typealias EngineMessageAndThreadId = MessageAndThreadId +public typealias EnginePeerStoryStats = PeerStoryStats +public typealias EngineMessageHistoryAnchorIndex = MessageHistoryAnchorIndex +public typealias EngineMessageHistoryEntryLocation = MessageHistoryEntryLocation +public typealias EngineChatListTotalUnreadStateCategory = ChatListTotalUnreadStateCategory +public typealias EngineChatListTotalUnreadStateStats = ChatListTotalUnreadStateStats +public typealias EngineChatListTotalUnreadState = ChatListTotalUnreadState +public typealias EngineItemCacheEntryId = ItemCacheEntryId +public typealias EnginePeerSummaryCounterTags = PeerSummaryCounterTags +public typealias EngineHashFunctions = HashFunctions +public typealias EngineCachedMediaResourceRepresentationResult = CachedMediaResourceRepresentationResult +public typealias EngineMediaResourceDataFetchResult = MediaResourceDataFetchResult +public typealias EngineMediaResourceDataFetchError = MediaResourceDataFetchError +public typealias EngineMediaResourceStatus = MediaResourceStatus +public typealias EnginePostboxCoding = PostboxCoding +public typealias EngineRawItemCollectionInfo = ItemCollectionInfo +public typealias EngineRawItemCollectionInfoEntry = ItemCollectionInfoEntry +public typealias EngineRawItemCollectionItem = ItemCollectionItem +public typealias EngineRawMedia = Media +public typealias EngineRawMessage = Message +public typealias EngineRawPeer = Peer +public typealias EngineRawMediaResource = MediaResource +public typealias EngineRawMediaResourceId = MediaResourceId +public typealias EngineRawMediaResourceData = MediaResourceData +public typealias EngineRawMediaResourceDataFetchCopyLocalItem = MediaResourceDataFetchCopyLocalItem +public typealias EngineRawCachedMediaResourceRepresentation = CachedMediaResourceRepresentation +public typealias EngineCachedMediaRepresentationKeepDuration = CachedMediaRepresentationKeepDuration +public typealias EngineCachedPeerData = CachedPeerData +public typealias EngineMessageHistoryThreadData = MessageHistoryThreadData +public typealias EngineViewUpdateType = ViewUpdateType +public typealias EngineRawPeerPresence = PeerPresence +public typealias EnginePeerGroupId = PeerGroupId +public typealias EngineChatLocationInput = ChatLocationInput +public typealias EngineHistoryViewInputTag = HistoryViewInputTag +public typealias EngineInitialMessageHistoryData = InitialMessageHistoryData +public typealias EngineSimpleDictionary = SimpleDictionary +public typealias EngineRawValueBoxKey = ValueBoxKey +public typealias EngineRawMessageHistoryView = MessageHistoryView +public typealias EngineRawPeerView = PeerView +public typealias EngineRawMessageHistoryEntry = MessageHistoryEntry +public typealias EngineRawMutableMessageHistoryEntryAttributes = MutableMessageHistoryEntryAttributes +public typealias EngineMessageHistoryViewReadState = MessageHistoryViewReadState +public typealias EngineMessageIdNamespaces = MessageIdNamespaces +public typealias EngineHistoryViewInputAnchor = HistoryViewInputAnchor +public typealias EngineRawPostboxViewKey = PostboxViewKey +public typealias EngineRawPreferencesView = PreferencesView +public typealias EngineRawOrderedItemListView = OrderedItemListView +public typealias EngineRawUnreadMessageCountsView = UnreadMessageCountsView +public typealias EngineRawMessageHistoryThreadIndexView = MessageHistoryThreadIndexView +public typealias EngineRawCombinedReadStateView = CombinedReadStateView +public typealias EngineRawMessageHistoryThreadInfoView = MessageHistoryThreadInfoView +public typealias EngineRawBasicPeerView = BasicPeerView +public typealias EngineRawCachedPeerDataView = CachedPeerDataView +public typealias EngineRawUnreadMessageCountsItem = UnreadMessageCountsItem +public typealias EngineRawMessageHistorySavedMessagesIndexView = MessageHistorySavedMessagesIndexView +public typealias EngineRawChatInterfaceStateView = ChatInterfaceStateView + +public func engineFileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { + return fileSize(path, useTotalFileAllocatedSize: useTotalFileAllocatedSize) +} + +public func engineAreMediaArraysEqual(_ lhs: [EngineRawMedia], _ rhs: [EngineRawMedia]) -> Bool { + return areMediaArraysEqual(lhs, rhs) +} + +public func enginePersistentHash32(_ string: String) -> Int32 { + return persistentHash32(string) +} + +public func engineDeclareEncodable(_ type: Any.Type, f: @escaping (EnginePostboxDecoder) -> EnginePostboxCoding) { + declareEncodable(type, f: f) +} diff --git a/submodules/TelegramCore/Sources/Themes.swift b/submodules/TelegramCore/Sources/Themes.swift index 4d8b2d4124..b7d49d7a19 100644 --- a/submodules/TelegramCore/Sources/Themes.swift +++ b/submodules/TelegramCore/Sources/Themes.swift @@ -11,7 +11,7 @@ let telegramThemeFormat = "ios" let telegramThemeFileExtension = "tgios-theme" #endif -public func telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager?, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { +func _internal_telegramThemes(postbox: Postbox, network: Network, accountManager: AccountManager?, forceUpdate: Bool = false) -> Signal<[TelegramTheme], NoError> { let fetch: ([TelegramTheme]?, Int64?) -> Signal<[TelegramTheme], NoError> = { current, hash in network.request(Api.functions.account.getThemes(format: telegramThemeFormat, hash: hash ?? 0)) |> retryRequest @@ -145,7 +145,7 @@ private func saveUnsaveTheme(account: Account, accountManager: AccountManager mapToSignal { _ -> Signal in - return telegramThemes(postbox: account.postbox, network: account.network, accountManager: accountManager, forceUpdate: true) + return _internal_telegramThemes(postbox: account.postbox, network: account.network, accountManager: accountManager, forceUpdate: true) |> take(1) |> mapToSignal { _ -> Signal in return .complete() @@ -542,7 +542,7 @@ func managedThemesUpdates(accountManager: AccountManager take(1)).start() } }.start() diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index 53db73d1ad..b7aaa24f26 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -360,12 +360,26 @@ func updatePeerPresences(transaction: Transaction, accountPeerId: PeerId, peerPr }) } -func updatePeerPresencesClean(transaction: Transaction, accountPeerId: PeerId, peerPresences: [PeerId: PeerPresence]) { - var peerPresences = peerPresences - if peerPresences[accountPeerId] != nil { - peerPresences.removeValue(forKey: accountPeerId) +func updatePeerPresencesClean(transaction: Transaction, accountPeerId: PeerId, peerPresences: [PeerId: UpdatedApiPresence]) { + var parsedPresences: [PeerId: PeerPresence] = [:] + for (peerId, status) in peerPresences { + let presence = TelegramUserPresence(apiStatus: status.status) + switch presence.status { + case .present: + parsedPresences[peerId] = presence + default: + if status.isMin, let _ = transaction.getPeerPresence(peerId: peerId) { + } else { + parsedPresences[peerId] = presence + } + } } - transaction.updatePeerPresencesInternal(presences: peerPresences, merge: { previous, updated in + + if parsedPresences[accountPeerId] != nil { + parsedPresences.removeValue(forKey: accountPeerId) + } + + transaction.updatePeerPresencesInternal(presences: parsedPresences, merge: { previous, updated in if let previous = previous as? TelegramUserPresence, let updated = updated as? TelegramUserPresence, previous.lastActivity != updated.lastActivity { return TelegramUserPresence(status: updated.status, lastActivity: max(previous.lastActivity, updated.lastActivity)) } diff --git a/submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift b/submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift index e58021f404..d1684d3f0b 100644 --- a/submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift +++ b/submodules/TelegramCore/Sources/Utils/CanSendMessagesToPeer.swift @@ -6,16 +6,37 @@ import Postbox private final class LinkHelperClass: NSObject { } -public func canSendMessagesToPeer(_ peer: Peer, ignoreDefault: Bool = false) -> Bool { - if let peer = peer as? TelegramUser, peer.addressName == "replies" { - return false - } else if peer is TelegramUser || peer is TelegramGroup { - return !peer.isDeleted - } else if let peer = peer as? TelegramSecretChat { - return peer.embeddedState == .active - } else if let peer = peer as? TelegramChannel { - return peer.hasPermission(.sendSomething, ignoreDefault: ignoreDefault) - } else { +public func canSendMessagesToPeer(_ peer: EnginePeer, ignoreDefault: Bool = false) -> Bool { + if case let .user(user) = peer, user.addressName == "replies" { return false } + switch peer { + case .user, .legacyGroup: + return !peer.isDeleted + case let .secretChat(secretChat): + return secretChat.embeddedState == .active + case let .channel(channel): + return channel.hasPermission(.sendSomething, ignoreDefault: ignoreDefault) + } +} + +public func canSendReactionsToPeer(_ peer: EnginePeer, ignoreDefault: Bool = false) -> Bool { + switch peer { + case .user: + return !peer.isDeleted + case let .legacyGroup(group): + switch group.role { + case .creator, .admin: + return !peer.isDeleted + case .member: + if let defaultBannedRights = group.defaultBannedRights, defaultBannedRights.flags.contains(.banSendReactions) && !ignoreDefault { + return false + } + return !peer.isDeleted + } + case let .secretChat(secretChat): + return secretChat.embeddedState == .active + case let .channel(channel): + return channel.hasBannedPermission(.banSendReactions, ignoreDefault: ignoreDefault) == nil + } } diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 393234c0a0..e9251fee0f 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -500,7 +500,17 @@ public extension Message { } return nil } + + var guestChatAttribute: GuestChatMessageAttribute? { + for attribute in self.attributes { + if let attribute = attribute as? GuestChatMessageAttribute { + return attribute + } + } + return nil + } } + public extension Message { var reactionsAttribute: ReactionsMessageAttribute? { for attribute in self.attributes { @@ -642,6 +652,39 @@ public extension Message { } } +public extension Message { + var richText: RichTextMessageAttribute? { + for attribute in self.attributes { + if let attribute = attribute as? RichTextMessageAttribute { + return attribute + } + } + return nil + } +} + +public extension Message { + /// The media that should drive gallery / shared-media / preview surfaces for this message. + /// For a normal message this is exactly `self.media`. For a rich message (`text == ""`, + /// empty `media`, carrying a `RichTextMessageAttribute`) the media lives inside the + /// instant page, so fall back to it. Scope note: callers take the FIRST media for now. + var effectiveMedia: [Media] { + if !self.media.isEmpty { + return self.media + } + if let richText = self.richText { + return richText.instantPage.allMedia() + } + return self.media + } +} + +public extension EngineMessage { + var effectiveMedia: [Media] { + return self._asMessage().effectiveMedia + } +} + public func _internal_parseMediaAttachment(data: Data) -> Media? { guard let object = Api.parse(Buffer(buffer: MemoryBuffer(data: data))) else { return nil diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index 49eb1535ee..955b0eccd7 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -478,19 +478,11 @@ public func peerViewMainPeer(_ view: PeerView) -> Peer? { } } -public func peerViewMonoforumMainPeer(_ view: PeerView) -> Peer? { - if let peer = peerViewMainPeer(view) { - if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { - return view.peers[linkedMonoforumId] - } else { - return nil - } - } else { - return nil - } -} - public extension RenderedPeer { + convenience init(peer: EnginePeer) { + self.init(peer: peer._asPeer()) + } + convenience init(message: Message) { var peers = SimpleDictionary() let peerId = message.id.peerId diff --git a/submodules/TelegramCore/Sources/Utils/StoredMessageFromSearchPeer.swift b/submodules/TelegramCore/Sources/Utils/StoredMessageFromSearchPeer.swift index 27e0300312..08a093eecd 100644 --- a/submodules/TelegramCore/Sources/Utils/StoredMessageFromSearchPeer.swift +++ b/submodules/TelegramCore/Sources/Utils/StoredMessageFromSearchPeer.swift @@ -2,16 +2,16 @@ import Foundation import Postbox import SwiftSignalKit -public func _internal_storedMessageFromSearchPeer(postbox: Postbox, peer: Peer) -> Signal { - return postbox.transaction { transaction -> Peer in +public func _internal_storedMessageFromSearchPeer(postbox: Postbox, peer: EnginePeer) -> Signal { + return postbox.transaction { transaction -> EnginePeer in if transaction.getPeer(peer.id) == nil { - updatePeersCustom(transaction: transaction, peers: [peer], update: { _, updatedPeer in + updatePeersCustom(transaction: transaction, peers: [peer._asPeer()], update: { _, updatedPeer in return updatedPeer }) } if let group = transaction.getPeer(peer.id) as? TelegramGroup, let migrationReference = group.migrationReference { if let migrationPeer = transaction.getPeer(migrationReference.peerId) { - return migrationPeer + return EnginePeer(migrationPeer) } else { return peer } @@ -20,11 +20,11 @@ public func _internal_storedMessageFromSearchPeer(postbox: Postbox, peer: Peer) } } -func _internal_storedMessageFromSearchPeers(account: Account, peers: [Peer]) -> Signal { +func _internal_storedMessageFromSearchPeers(account: Account, peers: [EnginePeer]) -> Signal { return account.postbox.transaction { transaction -> Void in for peer in peers { if transaction.getPeer(peer.id) == nil { - updatePeersCustom(transaction: transaction, peers: [peer], update: { _, updatedPeer in + updatePeersCustom(transaction: transaction, peers: [peer._asPeer()], update: { _, updatedPeer in return updatedPeer }) } diff --git a/submodules/TelegramCore/Sources/Wallpapers.swift b/submodules/TelegramCore/Sources/Wallpapers.swift index 84985dc5dd..670afaa857 100644 --- a/submodules/TelegramCore/Sources/Wallpapers.swift +++ b/submodules/TelegramCore/Sources/Wallpapers.swift @@ -4,7 +4,7 @@ import SwiftSignalKit import TelegramApi -public func telegramWallpapers(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramWallpaper], NoError> { +func _internal_telegramWallpapers(postbox: Postbox, network: Network, forceUpdate: Bool = false) -> Signal<[TelegramWallpaper], NoError> { let fetch: ([TelegramWallpaper]?, Int64?) -> Signal<[TelegramWallpaper], NoError> = { current, hash in network.request(Api.functions.account.getWallPapers(hash: hash ?? 0)) |> retryRequestIfNotFrozen diff --git a/submodules/TelegramCore/Sources/WebpagePreview.swift b/submodules/TelegramCore/Sources/WebpagePreview.swift index 926417f85e..770233baff 100644 --- a/submodules/TelegramCore/Sources/WebpagePreview.swift +++ b/submodules/TelegramCore/Sources/WebpagePreview.swift @@ -39,10 +39,6 @@ public enum WebpagePreviewWithProgressResult { case progress(Float) } -public func normalizedWebpagePreviewUrl(url: String) -> String { - return url -} - public func webpagePreviewWithProgress(account: Account, urls: [String], webpageId: MediaId? = nil, forPeerId: PeerId? = nil) -> Signal { return account.postbox.transaction { transaction -> Signal in if let webpageId = webpageId, let webpage = transaction.getMedia(webpageId) as? TelegramMediaWebpage, let url = webpage.content.url { diff --git a/submodules/TelegramIntents/Sources/TelegramIntents.swift b/submodules/TelegramIntents/Sources/TelegramIntents.swift index f6a782d87c..e4910f5905 100644 --- a/submodules/TelegramIntents/Sources/TelegramIntents.swift +++ b/submodules/TelegramIntents/Sources/TelegramIntents.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Intents import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences @@ -55,7 +54,7 @@ public enum SendMessageIntentSubject: CaseIterable { } } -public func donateSendMessageIntent(account: Account, sharedContext: SharedAccountContext, intentContext: SendMessageIntentContext, peerIds: [PeerId]) { +public func donateSendMessageIntent(account: Account, sharedContext: SharedAccountContext, intentContext: SendMessageIntentContext, peerIds: [EnginePeer.Id]) { if #available(iOSApplicationExtension 13.2, iOS 13.2, *) { let _ = (sharedContext.accountManager.transaction { transaction -> Bool in if case .none = transaction.getAccessChallengeData() { @@ -64,11 +63,11 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou return false } } - |> mapToSignal { unlocked -> Signal<[(Peer, SendMessageIntentSubject, UIImage?)], NoError> in + |> mapToSignal { unlocked -> Signal<[(EngineRawPeer, SendMessageIntentSubject, UIImage?)], NoError> in if unlocked { return sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.intentsSettings]) |> take(1) - |> mapToSignal { sharedData -> Signal<[(Peer, SendMessageIntentSubject)], NoError> in + |> mapToSignal { sharedData -> Signal<[(EngineRawPeer, SendMessageIntentSubject)], NoError> in let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.intentsSettings]?.get(IntentsSettings.self) ?? IntentsSettings.defaultSettings if let accountId = settings.account, accountId != account.peerId { return .single([]) @@ -82,8 +81,8 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.IsContact.init)), EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Messages.ChatListGroup.init)) ) - |> map { peerMap, isContactMap, chatListGroupMap -> [(Peer, SendMessageIntentSubject)] in - var peers: [(Peer, SendMessageIntentSubject)] = [] + |> map { peerMap, isContactMap, chatListGroupMap -> [(EngineRawPeer, SendMessageIntentSubject)] in + var peers: [(EngineRawPeer, SendMessageIntentSubject)] = [] for peerId in peerIds { if peerId.namespace != Namespaces.Peer.SecretChat, let maybePeer = peerMap[peerId], let peer = maybePeer { var subject: SendMessageIntentSubject? @@ -134,14 +133,14 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou return peers } } - |> mapToSignal { peers -> Signal<[(Peer, SendMessageIntentSubject, UIImage?)], NoError> in - var signals: [Signal<(Peer, SendMessageIntentSubject, UIImage?), NoError>] = [] + |> mapToSignal { peers -> Signal<[(EngineRawPeer, SendMessageIntentSubject, UIImage?)], NoError> in + var signals: [Signal<(EngineRawPeer, SendMessageIntentSubject, UIImage?), NoError>] = [] for (peer, subject) in peers { if peer.id == account.peerId { signals.append(.single((peer, subject, savedMessagesAvatar))) } else { let peerAndAvatar = (peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.smallProfileImage, clipStyle: .none) ?? .single(nil)) - |> map { imageVersions -> (Peer, SendMessageIntentSubject, UIImage?) in + |> map { imageVersions -> (EngineRawPeer, SendMessageIntentSubject, UIImage?) in var avatarImage: UIImage? if let image = imageVersions?.0 { avatarImage = image @@ -214,7 +213,7 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou } } -public func deleteSendMessageIntents(peerId: PeerId) { +public func deleteSendMessageIntents(peerId: EnginePeer.Id) { if #available(iOS 10.0, *) { INInteraction.delete(with: "sendMessage_\(peerId.toInt64())") } diff --git a/submodules/TelegramNotices/BUILD b/submodules/TelegramNotices/BUILD index 5a8b8127a4..00bc43ac10 100644 --- a/submodules/TelegramNotices/BUILD +++ b/submodules/TelegramNotices/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TelegramPermissions:TelegramPermissions", diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 7a7ac6d024..703598e887 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramPermissions @@ -119,14 +118,14 @@ public final class ApplicationSpecificInt64ArrayNotice: Codable { } } -private func noticeNamespace(namespace: Int32) -> ValueBoxKey { - let key = ValueBoxKey(length: 4) +private func noticeNamespace(namespace: Int32) -> EngineDataBuffer { + let key = EngineDataBuffer(length: 4) key.setInt32(0, value: namespace) return key } -private func noticeKey(peerId: PeerId, key: Int32) -> ValueBoxKey { - let v = ValueBoxKey(length: 8 + 4) +private func noticeKey(peerId: EnginePeer.Id, key: Int32) -> EngineDataBuffer { + let v = EngineDataBuffer(length: 8 + 4) v.setInt64(0, value: peerId.toInt64()) v.setInt32(8, value: key) return v @@ -209,16 +208,17 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case copyProtectionTips = 86 case aiTextProcessingStyleSelectionTips = 87 case savedMessagesChatListView = 88 + case guestChatMessageTooltip = 89 - var key: ValueBoxKey { - let v = ValueBoxKey(length: 4) + var key: EngineDataBuffer { + let v = EngineDataBuffer(length: 4) v.setInt32(0, value: self.rawValue) return v } } private extension PermissionKind { - var noticeKey: NoticeEntryKey? { + var noticeKey: EngineNoticeEntryKey? { switch self { case .contacts: return ApplicationSpecificNoticeKeys.contactsPermissionWarning() @@ -247,365 +247,369 @@ private struct ApplicationSpecificNoticeKeys { private static let displayedPeerVerificationNamespace: Int32 = 11 private static let dismissedPaidMessageWarningNamespace: Int32 = 11 - static func inlineBotLocationRequestNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func inlineBotLocationRequestNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: inlineBotLocationRequestNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func botPaymentLiabilityNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func botPaymentLiabilityNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: botPaymentLiabilityNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func botGameNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: botGameNoticeNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func botGameNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: botGameNoticeNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func irrelevantPeerGeoNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: peerReportNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func irrelevantPeerGeoNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: peerReportNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func dismissedPremiumGiftNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedPremiumGiftNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func dismissedPremiumGiftNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: dismissedPremiumGiftNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func groupEmojiPackNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: groupEmojiPackNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func groupEmojiPackNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: groupEmojiPackNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func forcedPasswordSetup() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) + static func forcedPasswordSetup() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) } - static func secretChatInlineBotUsage() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) + static func secretChatInlineBotUsage() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatInlineBotUsage.key) } - static func secretChatLinkPreviews() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatLinkPreviews.key) + static func secretChatLinkPreviews() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.secretChatLinkPreviews.key) } - static func archiveIntroDismissed() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.archiveIntroDismissed.key) + static func archiveIntroDismissed() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.archiveIntroDismissed.key) } - static func chatMediaMediaRecordingTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMediaMediaRecordingTips.key) + static func chatMediaMediaRecordingTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMediaMediaRecordingTips.key) } - static func archiveChatTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.archiveChatTips.key) + static func archiveChatTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.archiveChatTips.key) } - static func chatFolderTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatFolderTips.key) + static func chatFolderTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatFolderTips.key) } - static func profileCallTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.profileCallTips.key) + static func profileCallTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.profileCallTips.key) } - static func proxyAdsAcknowledgment() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.proxyAdsAcknowledgment.key) + static func proxyAdsAcknowledgment() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.proxyAdsAcknowledgment.key) } - static func psaAdsAcknowledgment(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: psaAcknowledgementNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func psaAdsAcknowledgment(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: psaAcknowledgementNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func setPublicChannelLink() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.setPublicChannelLink.key) + static func setPublicChannelLink() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.setPublicChannelLink.key) } - static func passcodeLockTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.passcodeLockTips.key) + static func passcodeLockTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.passcodeLockTips.key) } - static func contactsPermissionWarning() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.contactsPermissionWarning.key) + static func contactsPermissionWarning() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.contactsPermissionWarning.key) } - static func notificationsPermissionWarning() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.notificationsPermissionWarning.key) + static func notificationsPermissionWarning() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.notificationsPermissionWarning.key) } - static func cellularDataPermissionWarning() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.cellularDataPermissionWarning.key) + static func cellularDataPermissionWarning() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: permissionsNamespace), key: ApplicationSpecificGlobalNotice.cellularDataPermissionWarning.key) } - static func volumeButtonToUnmuteTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.volumeButtonToUnmuteTip.key) + static func volumeButtonToUnmuteTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.volumeButtonToUnmuteTip.key) } - static func callsTabTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.callsTabTip.key) + static func callsTabTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.callsTabTip.key) } - static func chatMessageSearchResultsTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMessageSearchResultsTip.key) + static func chatMessageSearchResultsTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMessageSearchResultsTip.key) } - static func chatMessageOptionsTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMessageOptionsTip.key) + static func chatMessageOptionsTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatMessageOptionsTip.key) } - static func chatTextSelectionTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatTextSelectionTip.key) + static func chatTextSelectionTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatTextSelectionTip.key) } - static func messageViewsPrivacyTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.messageViewsPrivacyTips.key) + static func messageViewsPrivacyTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.messageViewsPrivacyTips.key) } - static func themeChangeTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.themeChangeTip.key) + static func themeChangeTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.themeChangeTip.key) } - static func locationProximityAlertTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.locationProximityAlertTip.key) + static func locationProximityAlertTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.locationProximityAlertTip.key) } - static func nextChatSuggestionTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.nextChatSuggestionTip.key) + static func nextChatSuggestionTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.nextChatSuggestionTip.key) } - static func dismissedTrendingStickerPacks() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingStickerPacks.key) + static func dismissedTrendingStickerPacks() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingStickerPacks.key) } - static func chatSpecificThemeLightPreviewTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeLightPreviewTip.key) + static func chatSpecificThemeLightPreviewTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeLightPreviewTip.key) } - static func chatSpecificThemeDarkPreviewTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key) + static func chatSpecificThemeDarkPreviewTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key) } - static func chatWallpaperLightPreviewTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperLightPreviewTip.key) + static func chatWallpaperLightPreviewTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperLightPreviewTip.key) } - static func chatWallpaperDarkPreviewTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperDarkPreviewTip.key) + static func chatWallpaperDarkPreviewTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperDarkPreviewTip.key) } - static func chatForwardOptionsTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key) + static func chatForwardOptionsTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key) } - static func chatReplyOptionsTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatReplyOptionsTip.key) + static func chatReplyOptionsTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatReplyOptionsTip.key) } - static func interactiveEmojiSyncTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.interactiveEmojiSyncTip.key) + static func interactiveEmojiSyncTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.interactiveEmojiSyncTip.key) } - static func dismissedInvitationRequestsNotice(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: peerInviteRequestsNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func dismissedInvitationRequestsNotice(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: peerInviteRequestsNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func sharedMediaScrollingTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaScrollingTooltip.key) + static func sharedMediaScrollingTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaScrollingTooltip.key) } - static func sharedMediaFastScrollingTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaFastScrollingTooltip.key) + static func sharedMediaFastScrollingTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaFastScrollingTooltip.key) } - static func emojiTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.emojiTooltip.key) + static func emojiTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.emojiTooltip.key) } - static func audioTranscriptionSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioTranscriptionSuggestion.key) + static func audioTranscriptionSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioTranscriptionSuggestion.key) } - static func clearStorageDismissedTipSize() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.clearStorageDismissedTipSize.key) + static func clearStorageDismissedTipSize() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.clearStorageDismissedTipSize.key) } - static func dismissedTrendingEmojiPacks() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingEmojiPacks.key) + static func dismissedTrendingEmojiPacks() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingEmojiPacks.key) } - static func translationSuggestionNotice() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.translationSuggestion.key) + static func translationSuggestionNotice() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.translationSuggestion.key) } - static func audioRateOptionsTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key) + static func audioRateOptionsTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key) } - static func sendWhenOnlineTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key) + static func sendWhenOnlineTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key) } - static func displayChatListContacts() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key) + static func displayChatListContacts() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key) } - static func displayChatListStoriesTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListStoriesTooltip.key) + static func displayChatListStoriesTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListStoriesTooltip.key) } - static func storiesCameraTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storiesCameraTooltip.key) + static func storiesCameraTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storiesCameraTooltip.key) } - static func storiesDualCameraTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storiesDualCameraTooltip.key) + static func storiesDualCameraTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storiesDualCameraTooltip.key) } - static func displayChatListArchiveTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListArchiveTooltip.key) + static func displayChatListArchiveTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListArchiveTooltip.key) } - static func displayStoryReactionTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryReactionTooltip.key) + static func displayStoryReactionTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryReactionTooltip.key) } - static func storyStealthModeReplyCount() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storyStealthModeReplyCount.key) + static func storyStealthModeReplyCount() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.storyStealthModeReplyCount.key) } - static func viewOnceTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.viewOnceTooltip.key) + static func viewOnceTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.viewOnceTooltip.key) } - static func displayStoryUnmuteTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryUnmuteTooltip.key) + static func displayStoryUnmuteTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryUnmuteTooltip.key) } - static func displayStoryInteractionGuide() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryInteractionGuide.key) + static func displayStoryInteractionGuide() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayStoryInteractionGuide.key) } - static func replyQuoteTextSelectionTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.replyQuoteTextSelectionTip.key) + static func replyQuoteTextSelectionTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.replyQuoteTextSelectionTip.key) } - static func multipleReactionsSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleReactionsSuggestion.key) + static func multipleReactionsSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleReactionsSuggestion.key) } - static func savedMessagesChatsSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatsSuggestion.key) + static func savedMessagesChatsSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatsSuggestion.key) } - static func voiceMessagesPlayOnceSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesPlayOnceSuggestion.key) + static func voiceMessagesPlayOnceSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesPlayOnceSuggestion.key) } - static func incomingVoiceMessagePlayOnceTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.incomingVoiceMessagePlayOnceTip.key) + static func incomingVoiceMessagePlayOnceTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.incomingVoiceMessagePlayOnceTip.key) } - static func outgoingVoiceMessagePlayOnceTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVoiceMessagePlayOnceTip.key) + static func outgoingVoiceMessagePlayOnceTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVoiceMessagePlayOnceTip.key) } - static func videoMessagesPlayOnceSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.videoMessagesPlayOnceSuggestion.key) + static func videoMessagesPlayOnceSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.videoMessagesPlayOnceSuggestion.key) } - static func incomingVideoMessagePlayOnceTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.incomingVideoMessagePlayOnceTip.key) + static func incomingVideoMessagePlayOnceTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.incomingVideoMessagePlayOnceTip.key) } - static func outgoingVideoMessagePlayOnceTip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVideoMessagePlayOnceTip.key) + static func outgoingVideoMessagePlayOnceTip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.outgoingVideoMessagePlayOnceTip.key) } - static func savedMessageTagLabelSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key) + static func savedMessageTagLabelSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key) } - static func dismissedBirthdayPremiumGiftTip(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedBirthdayPremiumGiftTipNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func dismissedBirthdayPremiumGiftTip(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: dismissedBirthdayPremiumGiftTipNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func displayedPeerVerification(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: displayedPeerVerificationNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func displayedPeerVerification(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: displayedPeerVerificationNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func dismissedPaidMessageWarning(peerId: PeerId) -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedPaidMessageWarningNamespace), key: noticeKey(peerId: peerId, key: 0)) + static func dismissedPaidMessageWarning(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: dismissedPaidMessageWarningNamespace), key: noticeKey(peerId: peerId, key: 0)) } - static func monetizationIntroDismissed() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.monetizationIntroDismissed.key) + static func monetizationIntroDismissed() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.monetizationIntroDismissed.key) } - static func businessBotMessageTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.businessBotMessageTooltip.key) + static func businessBotMessageTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.businessBotMessageTooltip.key) } - static func captionAboveMediaTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key) + static func guestChatMessageTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.guestChatMessageTooltip.key) } - static func channelSendGiftTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSendGiftTooltip.key) + static func captionAboveMediaTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key) } - static func starGiftWearTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.starGiftWearTips.key) + static func channelSendGiftTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSendGiftTooltip.key) } - static func channelSuggestTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSuggestTooltip.key) + static func starGiftWearTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.starGiftWearTips.key) } - static func multipleStoriesTooltip() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleStoriesTooltip.key) + static func channelSuggestTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSuggestTooltip.key) } - static func voiceMessagesPauseSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesPauseSuggestion.key) + static func multipleStoriesTooltip() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleStoriesTooltip.key) } - static func videoMessagesPauseSuggestion() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.videoMessagesPauseSuggestion.key) + static func voiceMessagesPauseSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesPauseSuggestion.key) } - static func voiceMessagesResumeTrimWarning() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesResumeTrimWarning.key) + static func videoMessagesPauseSuggestion() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.videoMessagesPauseSuggestion.key) } - static func globalPostsSearch() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.globalPostsSearch.key) + static func voiceMessagesResumeTrimWarning() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.voiceMessagesResumeTrimWarning.key) } - static func giftAuctionTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftAuctionTips.key) + static func globalPostsSearch() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.globalPostsSearch.key) } - static func giftCraftingTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftCraftingTips.key) + static func giftAuctionTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftAuctionTips.key) } - static func copyProtectionTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.copyProtectionTips.key) + static func giftCraftingTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.giftCraftingTips.key) } - static func aiTextProcessingStyleSelectionTips() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.aiTextProcessingStyleSelectionTips.key) + static func copyProtectionTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.copyProtectionTips.key) } - static func savedMessagesChatListView() -> NoticeEntryKey { - return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatListView.key) + static func aiTextProcessingStyleSelectionTips() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.aiTextProcessingStyleSelectionTips.key) + } + + static func savedMessagesChatListView() -> EngineNoticeEntryKey { + return EngineNoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatListView.key) } } public struct ApplicationSpecificNotice { - public static func irrelevantPeerGeoReportKey(peerId: PeerId) -> NoticeEntryKey { + public static func irrelevantPeerGeoReportKey(peerId: EnginePeer.Id) -> EngineNoticeEntryKey { return ApplicationSpecificNoticeKeys.irrelevantPeerGeoNotice(peerId: peerId) } - public static func setIrrelevantPeerGeoReport(engine: TelegramEngine, peerId: PeerId) -> Signal { + public static func setIrrelevantPeerGeoReport(engine: TelegramEngine, peerId: EnginePeer.Id) -> Signal { return engine.notices.set(id: ApplicationSpecificNoticeKeys.irrelevantPeerGeoNotice(peerId: peerId), item: ApplicationSpecificBoolNotice()) } - public static func getBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getBotPaymentLiability(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId))?.get(ApplicationSpecificBoolNotice.self) { return true @@ -615,15 +619,15 @@ public struct ApplicationSpecificNotice { } } - public static func setBotPaymentLiability(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setBotPaymentLiability(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.botPaymentLiabilityNotice(peerId: peerId), entry) } } } - public static func getBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getBotGameNotice(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId))?.get(ApplicationSpecificBoolNotice.self) { return true @@ -633,15 +637,15 @@ public struct ApplicationSpecificNotice { } } - public static func setBotGameNotice(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setBotGameNotice(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.botGameNotice(peerId: peerId), entry) } } } - public static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getInlineBotLocationRequest(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Int32? in if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId))?.get(ApplicationSpecificTimestampNotice.self) { return notice.value @@ -651,7 +655,7 @@ public struct ApplicationSpecificNotice { } } - public static func inlineBotLocationRequestStatus(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func inlineBotLocationRequestStatus(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId)) |> map { view -> Bool in guard let value = view.value?.get(ApplicationSpecificTimestampNotice.self) else { @@ -665,13 +669,13 @@ public struct ApplicationSpecificNotice { } } - public static func updateInlineBotLocationRequestState(accountManager: AccountManager, peerId: PeerId, timestamp: Int32) -> Signal { + public static func updateInlineBotLocationRequestState(accountManager: AccountManager, peerId: EnginePeer.Id, timestamp: Int32) -> Signal { return accountManager.transaction { transaction -> Bool in if let notice = transaction.getNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId))?.get(ApplicationSpecificTimestampNotice.self), (notice.value == 0 || timestamp <= notice.value + 10 * 60) { return false } - if let entry = CodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId), entry) } @@ -679,9 +683,9 @@ public struct ApplicationSpecificNotice { } } - public static func setInlineBotLocationRequest(accountManager: AccountManager, peerId: PeerId, value: Int32) -> Signal { + public static func setInlineBotLocationRequest(accountManager: AccountManager, peerId: EnginePeer.Id, value: Int32) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificTimestampNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampNotice(value: value)) { transaction.setNotice(ApplicationSpecificNoticeKeys.inlineBotLocationRequestNotice(peerId: peerId), entry) } } @@ -699,14 +703,14 @@ public struct ApplicationSpecificNotice { public static func setSecretChatInlineBotUsage(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage(), entry) } } } public static func setSecretChatInlineBotUsage(transaction: AccountManagerModifier) { - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatInlineBotUsage(), entry) } } @@ -721,7 +725,7 @@ public struct ApplicationSpecificNotice { } } - public static func getSecretChatLinkPreviews(_ entry: CodableEntry) -> Bool? { + public static func getSecretChatLinkPreviews(_ entry: EngineCodableEntry) -> Bool? { if let value = entry.get(ApplicationSpecificVariantNotice.self) { return value.value } else { @@ -731,19 +735,19 @@ public struct ApplicationSpecificNotice { public static func setSecretChatLinkPreviews(accountManager: AccountManager, value: Bool) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificVariantNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificVariantNotice(value: value)) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatLinkPreviews(), entry) } } } public static func setSecretChatLinkPreviews(transaction: AccountManagerModifier, value: Bool) { - if let entry = CodableEntry(ApplicationSpecificVariantNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificVariantNotice(value: value)) { transaction.setNotice(ApplicationSpecificNoticeKeys.secretChatLinkPreviews(), entry) } } - public static func secretChatLinkPreviewsKey() -> NoticeEntryKey { + public static func secretChatLinkPreviewsKey() -> EngineNoticeEntryKey { return ApplicationSpecificNoticeKeys.secretChatLinkPreviews() } @@ -765,7 +769,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatMediaMediaRecordingTips(), entry) } } @@ -790,7 +794,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.archiveChatTips(), entry) } @@ -807,7 +811,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatFolderTips(), entry) } @@ -816,12 +820,12 @@ public struct ApplicationSpecificNotice { } public static func setArchiveIntroDismissed(transaction: AccountManagerModifier, value: Bool) { - if let entry = CodableEntry(ApplicationSpecificVariantNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificVariantNotice(value: value)) { transaction.setNotice(ApplicationSpecificNoticeKeys.archiveIntroDismissed(), entry) } } - public static func archiveIntroDismissedKey() -> NoticeEntryKey { + public static func archiveIntroDismissedKey() -> EngineNoticeEntryKey { return ApplicationSpecificNoticeKeys.archiveIntroDismissed() } @@ -843,7 +847,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.profileCallTips(), entry) } } @@ -861,7 +865,7 @@ public struct ApplicationSpecificNotice { public static func markAsSeenSetPublicChannelLink(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: 1)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: 1)) { transaction.setNotice(ApplicationSpecificNoticeKeys.profileCallTips(), entry) } } @@ -879,13 +883,13 @@ public struct ApplicationSpecificNotice { public static func setProxyAdsAcknowledgment(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.proxyAdsAcknowledgment(), entry) } } } - public static func getPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func getPsaAcknowledgment(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Bool in if let _ = transaction.getNotice(ApplicationSpecificNoticeKeys.psaAdsAcknowledgment(peerId: peerId))?.get(ApplicationSpecificBoolNotice.self) { return true @@ -895,9 +899,9 @@ public struct ApplicationSpecificNotice { } } - public static func setPsaAcknowledgment(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setPsaAcknowledgment(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.psaAdsAcknowledgment(peerId: peerId), entry) } } @@ -915,13 +919,13 @@ public struct ApplicationSpecificNotice { public static func setPasscodeLockTips(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.passcodeLockTips(), entry) } } } - public static func permissionWarningKey(permission: PermissionKind) -> NoticeEntryKey? { + public static func permissionWarningKey(permission: PermissionKind) -> EngineNoticeEntryKey? { return permission.noticeKey } @@ -930,13 +934,13 @@ public struct ApplicationSpecificNotice { return } let _ = (accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificTimestampNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampNotice(value: value)) { transaction.setNotice(noticeKey, entry) } }).start() } - public static func getTimestampValue(_ entry: CodableEntry) -> Int32? { + public static func getTimestampValue(_ entry: EngineCodableEntry) -> Int32? { if let value = entry.get(ApplicationSpecificTimestampNotice.self) { return value.value } else { @@ -956,7 +960,7 @@ public struct ApplicationSpecificNotice { public static func setVolumeButtonToUnmute(accountManager: AccountManager) { let _ = accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.volumeButtonToUnmuteTip(), entry) } }.start() @@ -981,7 +985,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += min(3, Int32(count)) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.callsTabTip(), entry) } @@ -991,7 +995,7 @@ public struct ApplicationSpecificNotice { public static func setCallsTabTip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.callsTabTip(), entry) } } @@ -1016,7 +1020,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatMessageSearchResultsTip(), entry) } } @@ -1040,7 +1044,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip(), entry) } } @@ -1064,7 +1068,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatTextSelectionTip(), entry) } } @@ -1088,7 +1092,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.replyQuoteTextSelectionTip(), entry) } } @@ -1112,7 +1116,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.messageViewsPrivacyTips(), entry) } } @@ -1130,7 +1134,7 @@ public struct ApplicationSpecificNotice { public static func markThemeChangeTipAsSeen(accountManager: AccountManager) { let _ = accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.themeChangeTip(), entry) } }.start() @@ -1154,7 +1158,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatMessageOptionsTip(), entry) } } @@ -1178,7 +1182,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.nextChatSuggestionTip(), entry) } } @@ -1202,7 +1206,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.sharedMediaScrollingTooltip(), entry) } } @@ -1226,7 +1230,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.sharedMediaFastScrollingTooltip(), entry) } } @@ -1250,7 +1254,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.emojiTooltip(), entry) } } @@ -1269,7 +1273,7 @@ public struct ApplicationSpecificNotice { public static func setDismissedTrendingStickerPacks(accountManager: AccountManager, values: [Int64]) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { + if let entry = EngineCodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks(), entry) } } @@ -1288,7 +1292,7 @@ public struct ApplicationSpecificNotice { public static func setDismissedTrendingEmojiPacks(accountManager: AccountManager, values: [Int64]) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { + if let entry = EngineCodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedTrendingEmojiPacks(), entry) } } @@ -1313,7 +1317,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeLightPreviewTip(), entry) } @@ -1340,7 +1344,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatSpecificThemeDarkPreviewTip(), entry) } @@ -1367,7 +1371,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip(), entry) } @@ -1394,7 +1398,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip(), entry) } @@ -1421,7 +1425,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatForwardOptionsTip(), entry) } @@ -1448,7 +1452,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.chatReplyOptionsTip(), entry) } @@ -1468,7 +1472,7 @@ public struct ApplicationSpecificNotice { public static func setClearStorageDismissedTipSize(accountManager: AccountManager, value: Int32) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value)) { transaction.setNotice(ApplicationSpecificNoticeKeys.clearStorageDismissedTipSize(), entry) } } @@ -1494,7 +1498,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.interactiveEmojiSyncTip(), entry) } @@ -1502,7 +1506,7 @@ public struct ApplicationSpecificNotice { } } - public static func dismissedInvitationRequests(accountManager: AccountManager, peerId: PeerId) -> Signal<[Int64]?, NoError> { + public static func dismissedInvitationRequests(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal<[Int64]?, NoError> { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedInvitationRequestsNotice(peerId: peerId)) |> map { view -> [Int64]? in if let value = view.value?.get(ApplicationSpecificInt64ArrayNotice.self) { @@ -1513,15 +1517,15 @@ public struct ApplicationSpecificNotice { } } - public static func setDismissedInvitationRequests(accountManager: AccountManager, peerId: PeerId, values: [Int64]) -> Signal { + public static func setDismissedInvitationRequests(accountManager: AccountManager, peerId: EnginePeer.Id, values: [Int64]) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { + if let entry = EngineCodableEntry(ApplicationSpecificInt64ArrayNotice(values: values)) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedInvitationRequestsNotice(peerId: peerId), entry) } } } - public static func forcedPasswordSetupKey() -> NoticeEntryKey { + public static func forcedPasswordSetupKey() -> EngineNoticeEntryKey { return ApplicationSpecificNoticeKeys.forcedPasswordSetup() } @@ -1533,7 +1537,7 @@ public struct ApplicationSpecificNotice { return engine.notices.set(id: ApplicationSpecificNoticeKeys.forcedPasswordSetup(), item: item) } - public static func audioTranscriptionSuggestionKey() -> NoticeEntryKey { + public static func audioTranscriptionSuggestionKey() -> EngineNoticeEntryKey { return ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion() } @@ -1556,7 +1560,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.audioTranscriptionSuggestion(), entry) } @@ -1590,7 +1594,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue = max(0, Int32(currentValue + count)) - if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.translationSuggestionNotice(), entry) } @@ -1618,14 +1622,14 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.audioRateOptionsTip(), entry) } return previousValue } } - public static func dismissedPremiumGiftSuggestion(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func dismissedPremiumGiftSuggestion(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedPremiumGiftNotice(peerId: peerId)) |> map { view -> Int32? in if let value = view.value?.get(ApplicationSpecificTimestampNotice.self) { @@ -1636,16 +1640,16 @@ public struct ApplicationSpecificNotice { } } - public static func incrementDismissedPremiumGiftSuggestion(accountManager: AccountManager, peerId: PeerId, timestamp: Int32) -> Signal { + public static func incrementDismissedPremiumGiftSuggestion(accountManager: AccountManager, peerId: EnginePeer.Id, timestamp: Int32) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedPremiumGiftNotice(peerId: peerId), entry) } } |> ignoreValues } - public static func groupEmojiPackSuggestion(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func groupEmojiPackSuggestion(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId)) |> map { view -> Int32 in if let value = view.value?.get(ApplicationSpecificCounterNotice.self) { @@ -1656,7 +1660,7 @@ public struct ApplicationSpecificNotice { } } - public static func incrementGroupEmojiPackSuggestion(accountManager: AccountManager, peerId: PeerId, count: Int32 = 1) -> Signal { + public static func incrementGroupEmojiPackSuggestion(accountManager: AccountManager, peerId: EnginePeer.Id, count: Int32 = 1) -> Signal { return accountManager.transaction { transaction -> Int32 in var currentValue: Int32 = 0 if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId))?.get(ApplicationSpecificCounterNotice.self) { @@ -1665,7 +1669,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.groupEmojiPackNotice(peerId: peerId), entry) } return previousValue @@ -1690,7 +1694,7 @@ public struct ApplicationSpecificNotice { } currentValue += count - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip(), entry) } } @@ -1709,7 +1713,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayChatListContacts(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListContacts(), entry) } } @@ -1729,7 +1733,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayChatListStoriesTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListStoriesTooltip(), entry) } } @@ -1745,7 +1749,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.storiesCameraTooltip(), entry) } @@ -1772,7 +1776,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.storiesDualCameraTooltip(), entry) } @@ -1799,7 +1803,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayStoryReactionTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayStoryReactionTooltip(), entry) } } @@ -1820,7 +1824,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayChatListArchiveTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListArchiveTooltip(), entry) } } @@ -1846,7 +1850,7 @@ public struct ApplicationSpecificNotice { value = item.value } - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: value + 1)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: value + 1)) { transaction.setNotice(ApplicationSpecificNoticeKeys.storyStealthModeReplyCount(), entry) } } @@ -1862,7 +1866,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.viewOnceTooltip(), entry) } @@ -1872,7 +1876,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayStoryUnmuteTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayStoryUnmuteTooltip(), entry) } } @@ -1893,7 +1897,7 @@ public struct ApplicationSpecificNotice { public static func setDisplayStoryInteractionGuide(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayStoryInteractionGuide(), entry) } } @@ -1931,7 +1935,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.multipleReactionsSuggestion(), entry) } @@ -1958,7 +1962,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessagesChatsSuggestion(), entry) } @@ -1985,7 +1989,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.voiceMessagesPlayOnceSuggestion(), entry) } @@ -2012,7 +2016,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.incomingVoiceMessagePlayOnceTip(), entry) } @@ -2039,7 +2043,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.outgoingVoiceMessagePlayOnceTip(), entry) } @@ -2066,7 +2070,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.videoMessagesPlayOnceSuggestion(), entry) } @@ -2093,7 +2097,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.incomingVideoMessagePlayOnceTip(), entry) } @@ -2120,7 +2124,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.outgoingVideoMessagePlayOnceTip(), entry) } @@ -2147,7 +2151,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessageTagLabelSuggestion(), entry) } @@ -2155,7 +2159,7 @@ public struct ApplicationSpecificNotice { } } - public static func dismissedBirthdayPremiumGiftTip(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func dismissedBirthdayPremiumGiftTip(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBirthdayPremiumGiftTip(peerId: peerId)) |> map { view -> Int32? in if let value = view.value?.get(ApplicationSpecificTimestampNotice.self) { @@ -2166,16 +2170,16 @@ public struct ApplicationSpecificNotice { } } - public static func incrementDismissedBirthdayPremiumGiftTip(accountManager: AccountManager, peerId: PeerId, timestamp: Int32) -> Signal { + public static func incrementDismissedBirthdayPremiumGiftTip(accountManager: AccountManager, peerId: EnginePeer.Id, timestamp: Int32) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { + if let entry = EngineCodableEntry(ApplicationSpecificTimestampNotice(value: timestamp)) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBirthdayPremiumGiftTip(peerId: peerId), entry) } } |> ignoreValues } - public static func displayedPeerVerification(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func displayedPeerVerification(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayedPeerVerification(peerId: peerId)) |> map { view -> Bool in if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { @@ -2186,16 +2190,16 @@ public struct ApplicationSpecificNotice { } } - public static func setDisplayedPeerVerification(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func setDisplayedPeerVerification(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.displayedPeerVerification(peerId: peerId), entry) } } |> ignoreValues } - public static func dismissedPaidMessageWarningNamespace(accountManager: AccountManager, peerId: PeerId) -> Signal { + public static func dismissedPaidMessageWarningNamespace(accountManager: AccountManager, peerId: EnginePeer.Id) -> Signal { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedPaidMessageWarning(peerId: peerId)) |> map { view -> Int64? in if let counter = view.value?.get(ApplicationSpecificCounterNotice.self) { @@ -2206,9 +2210,9 @@ public struct ApplicationSpecificNotice { } } - public static func setDismissedPaidMessageWarningNamespace(accountManager: AccountManager, peerId: PeerId, amount: Int64?) -> Signal { + public static func setDismissedPaidMessageWarningNamespace(accountManager: AccountManager, peerId: EnginePeer.Id, amount: Int64?) -> Signal { return accountManager.transaction { transaction -> Void in - if let amount, let entry = CodableEntry(ApplicationSpecificCounterNotice(value: Int32(amount))) { + if let amount, let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: Int32(amount))) { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedPaidMessageWarning(peerId: peerId), entry) } else { transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedPaidMessageWarning(peerId: peerId), nil) @@ -2219,7 +2223,7 @@ public struct ApplicationSpecificNotice { public static func setMonetizationIntroDismissed(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in - if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + if let entry = EngineCodableEntry(ApplicationSpecificBoolNotice()) { transaction.setNotice(ApplicationSpecificNoticeKeys.monetizationIntroDismissed(), entry) } } @@ -2257,7 +2261,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.businessBotMessageTooltip(), entry) } @@ -2265,6 +2269,33 @@ public struct ApplicationSpecificNotice { } } + public static func getGuestChatMessageTooltip(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.guestChatMessageTooltip())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementGuestChatMessageTooltip(accountManager: AccountManager, count: Int = 1) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.guestChatMessageTooltip())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.guestChatMessageTooltip(), entry) + } + + return Int(previousValue) + } + } + public static func getCaptionAboveMediaTooltip(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Int32 in if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) { @@ -2284,7 +2315,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip(), entry) } @@ -2311,7 +2342,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.channelSendGiftTooltip(), entry) } @@ -2338,7 +2369,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.channelSuggestTooltip(), entry) } @@ -2365,7 +2396,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.starGiftWearTips(), entry) } @@ -2392,7 +2423,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.multipleStoriesTooltip(), entry) } @@ -2419,7 +2450,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.voiceMessagesPauseSuggestion(), entry) } @@ -2446,7 +2477,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.videoMessagesPauseSuggestion(), entry) } @@ -2473,7 +2504,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.voiceMessagesResumeTrimWarning(), entry) } @@ -2500,7 +2531,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.globalPostsSearch(), entry) } @@ -2527,7 +2558,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.giftAuctionTips(), entry) } @@ -2554,7 +2585,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.giftCraftingTips(), entry) } @@ -2581,7 +2612,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.copyProtectionTips(), entry) } @@ -2608,7 +2639,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.aiTextProcessingStyleSelectionTips(), entry) } @@ -2635,7 +2666,7 @@ public struct ApplicationSpecificNotice { let previousValue = currentValue currentValue += Int32(count) - if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + if let entry = EngineCodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessagesChatListView(), entry) } diff --git a/submodules/TelegramPermissions/Sources/Permission.swift b/submodules/TelegramPermissions/Sources/Permission.swift index c48f46d83b..dbb933b105 100644 --- a/submodules/TelegramPermissions/Sources/Permission.swift +++ b/submodules/TelegramPermissions/Sources/Permission.swift @@ -9,7 +9,6 @@ public enum PermissionKind: Int32 { case notifications case siri case cellularData - case nearbyLocation } public enum PermissionRequestStatus { @@ -39,7 +38,6 @@ public enum PermissionState: Equatable { case notifications(status: PermissionRequestStatus) case siri(status: PermissionRequestStatus) case cellularData(status: PermissionRequestStatus) - case nearbyLocation(status: PermissionRequestStatus) public var kind: PermissionKind { switch self { @@ -51,8 +49,6 @@ public enum PermissionState: Equatable { return .siri case .cellularData: return .cellularData - case .nearbyLocation: - return .nearbyLocation } } @@ -66,8 +62,6 @@ public enum PermissionState: Equatable { return status case let .cellularData(status): return status - case let .nearbyLocation(status): - return status } } } diff --git a/submodules/TelegramPermissionsUI/BUILD b/submodules/TelegramPermissionsUI/BUILD index 54bc103cd8..6e695b124b 100644 --- a/submodules/TelegramPermissionsUI/BUILD +++ b/submodules/TelegramPermissionsUI/BUILD @@ -17,16 +17,16 @@ swift_library( "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", "//submodules/TextFormat:TextFormat", "//submodules/Markdown:Markdown", "//submodules/TelegramPermissions:TelegramPermissions", "//submodules/DeviceAccess:DeviceAccess", - "//submodules/PeersNearbyIconNode:PeersNearbyIconNode", - "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/TelegramUI/Components/ButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift index 9843334806..0ca0982ada 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift @@ -6,9 +6,9 @@ import TelegramCore import TelegramPresentationData import TextFormat import TelegramPermissions -import PeersNearbyIconNode -import SolidRoundedButtonNode -import PresentationDataUtils +import ComponentFlow +import ButtonComponent +import ComponentDisplayAdapters import Markdown import AnimatedStickerNode import TelegramAnimatedStickerNode @@ -38,18 +38,18 @@ public final class PermissionContentNode: ASDisplayNode { private let filterHitTest: Bool private let iconNode: ASImageNode - private let nearbyIconNode: PeersNearbyIconNode? private let animationNode: AnimatedStickerNode? private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode private let textNode: ImmediateTextNode - private let actionButton: SolidRoundedButtonNode + private let actionButton = ComponentView() private let footerNode: ImmediateTextNode private let privacyPolicyButton: HighlightableButtonNode private let icon: PermissionContentIcon private var title: String private var text: String + private let buttonTitle: String public var buttonAction: (() -> Void)? public var openPrivacyPolicy: (() -> Void)? @@ -67,6 +67,7 @@ public final class PermissionContentNode: ASDisplayNode { self.icon = icon self.title = title self.text = text + self.buttonTitle = buttonTitle self.iconNode = ASImageNode() self.iconNode.isLayerBacked = true @@ -78,13 +79,7 @@ public final class PermissionContentNode: ASDisplayNode { self.animationNode?.setup(source: AnimatedStickerNodeLocalFileSource(name: animation), width: 320, height: 320, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animationNode?.visibility = true - - self.nearbyIconNode = nil - } else if kind == PermissionKind.nearbyLocation.rawValue { - self.nearbyIconNode = PeersNearbyIconNode(theme: theme) - self.animationNode = nil } else { - self.nearbyIconNode = nil self.animationNode = nil } @@ -106,8 +101,7 @@ public final class PermissionContentNode: ASDisplayNode { self.textNode.maximumNumberOfLines = 0 self.textNode.displaysAsynchronously = false self.textNode.isAccessibilityElement = true - - self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 52.0, cornerRadius: 26.0, isShimmering: true) + self.textNode.lineSpacing = 0.1 self.footerNode = ImmediateTextNode() self.footerNode.textAlignment = .center @@ -135,7 +129,6 @@ public final class PermissionContentNode: ASDisplayNode { let link = MarkdownAttributeSet(font: Font.regular(16.0), textColor: theme.list.itemAccentColor, additionalAttributes: [TelegramTextAttributes.URL: ""]) self.textNode.attributedText = parseMarkdownIntoAttributedString(text.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in nil }), textAlignment: secondaryText ? .natural : .center) - self.actionButton.title = buttonTitle self.privacyPolicyButton.isHidden = openPrivacyPolicy == nil if let subtitle = subtitle { @@ -149,19 +142,13 @@ public final class PermissionContentNode: ASDisplayNode { } self.addSubnode(self.iconNode) - self.nearbyIconNode.flatMap { self.addSubnode($0) } self.animationNode.flatMap { self.addSubnode($0) } self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) self.addSubnode(self.textNode) - self.addSubnode(self.actionButton) self.addSubnode(self.footerNode) self.addSubnode(self.privacyPolicyButton) - self.actionButton.pressed = { [weak self] in - self?.buttonAction?() - } - self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside) } @@ -234,7 +221,33 @@ public final class PermissionContentNode: ASDisplayNode { let textSize = self.textNode.updateLayout(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) let buttonInset: CGFloat = 16.0 let buttonWidth = min(size.width, size.height) - buttonInset * 2.0 - insets.left - insets.right - let buttonHeight = self.actionButton.updateLayout(width: buttonWidth, transition: transition) + let buttonSize = self.actionButton.update( + transition: ComponentTransition(transition), + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: self.theme.list.itemCheckColors.fillColor, + foreground: self.theme.list.itemCheckColors.foregroundColor, + pressedColor: self.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 26.0, + isShimmering: true + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(Text( + text: self.buttonTitle, + font: Font.semibold(17.0), + color: self.theme.list.itemCheckColors.foregroundColor + )) + ), + action: { [weak self] in + self?.buttonAction?() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: 52.0) + ) + let buttonHeight = buttonSize.height let footerSize = self.footerNode.updateLayout(CGSize(width: size.width - smallerSidePadding * 2.0, height: .greatestFiniteMagnitude)) let privacyButtonSize = self.privacyPolicyButton.measure(CGSize(width: size.width - sidePadding * 2.0, height: .greatestFiniteMagnitude)) @@ -255,11 +268,6 @@ public final class PermissionContentNode: ASDisplayNode { imageSize = icon.size contentHeight += imageSize.height + imageSpacing } - if let _ = self.nearbyIconNode, size.width < size.height { - imageSpacing = floor(availableHeight * 0.12) - imageSize = CGSize(width: 120.0, height: 120.0) - contentHeight += imageSize.height + imageSpacing - } if let _ = self.animationNode, size.width < size.height { imageSpacing = floor(availableHeight * 0.12) imageSize = CGSize(width: 240.0, height: 240.0) @@ -275,7 +283,6 @@ public final class PermissionContentNode: ASDisplayNode { let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - verticalOffset let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize) - let nearbyIconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize) let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize) let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + imageSpacing), size: titleSize) @@ -300,9 +307,6 @@ public final class PermissionContentNode: ASDisplayNode { } transition.updateFrame(node: self.iconNode, frame: iconFrame) - if let nearbyIconNode = self.nearbyIconNode { - transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame) - } if let animationNode = self.animationNode { transition.updateFrame(node: animationNode, frame: animationFrame) animationNode.updateLayout(size: animationFrame.size) @@ -310,7 +314,15 @@ public final class PermissionContentNode: ASDisplayNode { transition.updateFrame(node: self.titleNode, frame: titleFrame) transition.updateFrame(node: self.subtitleNode, frame: subtitleFrame) transition.updateFrame(node: self.textNode, frame: textFrame) - transition.updateFrame(node: self.actionButton, frame: buttonFrame) + if let buttonView = self.actionButton.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + buttonView.isAccessibilityElement = true + buttonView.accessibilityLabel = self.buttonTitle + buttonView.accessibilityTraits = [.button] + transition.updateFrame(view: buttonView, frame: buttonFrame) + } transition.updateFrame(node: self.footerNode, frame: footerFrame) transition.updateFrame(node: self.privacyPolicyButton, frame: privacyButtonFrame) diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionController.swift b/submodules/TelegramPermissionsUI/Sources/PermissionController.swift index d0c2f05323..1bb200eb5e 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionController.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionController.swift @@ -114,10 +114,7 @@ public final class PermissionController: ViewController { self.state = state if case let .permission(permission) = state, let state = permission { - if case .nearbyLocation = state { - } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed)) - } + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed)) switch state { case let .contacts(status): @@ -186,28 +183,6 @@ public final class PermissionController: ViewController { strongSelf.proceed?(true) } } - case let .nearbyLocation(status): - self.title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0 - - if self.locationManager == nil { - self.locationManager = LocationManager() - } - - self.allow = { [weak self] in - if let strongSelf = self { - switch status { - case .requestable: - DeviceAccess.authorizeAccess(to: .location(.tracking), locationManager: strongSelf.locationManager, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, { [weak self] result in - self?.proceed?(result) - }) - case .denied, .unreachable: - strongSelf.openAppSettings() - strongSelf.proceed?(false) - default: - break - } - } - } } } else if case let .custom(icon, _, _, _, _, _, _) = state { if case .animation = icon, case .modal = self.navigationPresentation { diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionControllerNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionControllerNode.swift index 538b5a3925..f20b4fe20d 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionControllerNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionControllerNode.swift @@ -195,16 +195,6 @@ final class PermissionControllerNode: ASDisplayNode { text = self.presentationData.strings.Permissions_CellularDataText_v0 buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings_v0 hasPrivacyPolicy = false - case let .nearbyLocation(status): - icon = nil - title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0 - text = self.presentationData.strings.Permissions_PeopleNearbyText_v0 - if status == .denied { - buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllowInSettings_v0 - } else { - buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllow_v0 - } - hasPrivacyPolicy = false } let contentNode = PermissionContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: .image(icon), title: title, text: text, buttonTitle: buttonTitle, secondaryButtonTitle: nil, buttonAction: { [weak self] in diff --git a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift index 8dbd3e82b6..4f6c9b29b1 100644 --- a/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift +++ b/submodules/TelegramPresentationData/Sources/ComponentsThemes.swift @@ -69,7 +69,7 @@ public extension NavigationBarTheme { badgeTextColor = theme.badgeTextColor } - self.init(overallDarkAppearance: rootControllerTheme.overallDarkAppearance, buttonColor: buttonColor, disabledButtonColor: disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : badgeBackgroundColor, badgeStrokeColor: .clear, badgeTextColor: hideBadge ? .clear : badgeTextColor, edgeEffectColor: edgeEffectColor, accentButtonColor: rootControllerTheme.list.itemCheckColors.fillColor, accentForegroundColor: rootControllerTheme.list.itemCheckColors.foregroundColor, style: style, glassStyle: glassStyle) + self.init(overallDarkAppearance: rootControllerTheme.overallDarkAppearance, buttonColor: buttonColor, disabledButtonColor: disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : badgeBackgroundColor, badgeStrokeColor: .clear, badgeTextColor: hideBadge ? .clear : badgeTextColor, edgeEffectColor: edgeEffectColor, accentButtonColor: rootControllerTheme.list.itemCheckColors.fillColor, accentDisabledButtonColor: rootControllerTheme.chat.inputPanel.panelControlDisabledColor, accentForegroundColor: rootControllerTheme.list.itemCheckColors.foregroundColor, style: style, glassStyle: glassStyle) } } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift index 0fc5c68106..c67533ea1e 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkTintedPresentationTheme.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import TelegramCore import TelegramUIPreferences -import Postbox private let defaultDarkTintedAccentColor = UIColor(rgb: 0x2ea6ff) public let defaultDarkTintedPresentationTheme = makeDefaultDarkTintedPresentationTheme(preview: false) diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 4b5d5bd0f7..54111d6125 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -3,7 +3,6 @@ import UIKit import Display import TelegramCore import TelegramUIPreferences -import Postbox import SwiftSignalKit public func selectDateFillStaticColor(theme: PresentationTheme, wallpaper: TelegramWallpaper) -> UIColor { @@ -1270,7 +1269,7 @@ public extension BuiltinWallpaperData { fileSize: 122246 ) - static func generate(account: Account) { + static func generate(network: Network) { let slugToName: [(String, String)] = [ ("fqv01SQemVIBAAAApND8LDRUhRU", "`default`"), ("Ye7DfT2kCVIKAAAAhzXfrkdOjxs", "legacy"), @@ -1292,7 +1291,7 @@ public extension BuiltinWallpaperData { var signals: [Signal] = [] for (slug, name) in slugToName { - signals.append(getWallpaper(network: account.network, slug: slug) + signals.append(getWallpaper(network: network, slug: slug) |> map { wallpaper -> String? in switch wallpaper { case let .file(file): @@ -1346,7 +1345,7 @@ public func defaultBuiltinWallpaper(data: BuiltinWallpaperData, colors: [UInt32] isDark: false, slug: data.slug, file: TelegramMediaFile( - fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: data.fileId), + fileId: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: data.fileId), partialReference: nil, resource: CloudDocumentMediaResource( datacenterId: data.datacenterId, diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index ca3e549ff3..f557750fd4 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -504,11 +504,6 @@ private func automaticThemeShouldSwitchNow(_ parameters: AutomaticThemeSwitchPar } } -public func automaticThemeShouldSwitchNow(settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Bool { - let parameters = AutomaticThemeSwitchParameters(settings: settings) - return automaticThemeShouldSwitchNow(parameters, systemUserInterfaceStyle: systemUserInterfaceStyle) -} - private func automaticThemeShouldSwitch(_ settings: AutomaticThemeSwitchSetting, systemUserInterfaceStyle: WindowUserInterfaceStyle) -> Signal { if settings.force { return .single(true) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 51173eea52..37acda80e1 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -64,8 +64,6 @@ public enum PresentationResourceKey: Int32 { case itemListCloudFetchIcon case itemListCloseIconImage case itemListRemoveIconImage - case itemListMakeVisibleIcon - case itemListMakeInvisibleIcon case itemListEditThemeIcon case itemListCornersTop case itemListCornersBottom @@ -132,6 +130,13 @@ public enum PresentationResourceKey: Int32 { case chatListGiftIcon case chatListLocationIcon case chatListPollIcon + case chatListTodoIcon + case chatListGameIcon + case chatListCallIncomingIcon + case chatListCallOutgoingIcon + case chatListCallVideoIncomingIcon + case chatListCallVideoOutgoingIcon + case chatListVoiceMessageIcon case chatListGeneralTopicIcon case chatListGeneralTopicTemplateIcon @@ -337,6 +342,8 @@ public enum PresentationResourceKey: Int32 { case storyViewListLikeIcon case navigationPostStoryIcon case navigationSortIcon + case navigationBackIcon + case navigationCloseIcon case chatReplyBackgroundTemplateIncomingImage case chatReplyBackgroundTemplateOutgoingDashedImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 3bb0f88571..1c9c9a99da 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1286,7 +1286,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } @@ -1312,7 +1312,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } @@ -1338,7 +1338,7 @@ public struct PresentationResourcesChat { UIGraphicsPopContext() } - }) + })?.withRenderingMode(.alwaysTemplate) }) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index c0c7a791c6..0994b46a48 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -313,6 +313,48 @@ public struct PresentationResourcesChatList { }) } + public static func todoIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListTodoIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/TodoIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func gameIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListGameIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GameIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func callIncomingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListCallIncomingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/CallIncomingIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func callOutgoingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListCallOutgoingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/CallOutgoingIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func callVideoIncomingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListCallVideoIncomingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/CallVideoIncomingIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func callVideoOutgoingIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListCallVideoOutgoingIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/CallVideoOutgoingIcon"), color: theme.chatList.muteIconColor) + }) + } + + public static func voiceMessageIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListVoiceMessageIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/VoiceMessageIcon"), color: theme.chatList.muteIconColor) + }) + } + public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index da99384779..fac4f1a8a2 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -269,19 +269,7 @@ public struct PresentationResourcesItemList { }) }) } - - public static func makeVisibleIcon(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.itemListMakeVisibleIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeVisibleIcon"), color: theme.list.itemAccentColor) - }) - } - - public static func makeInvisibleIcon(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.itemListMakeInvisibleIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeInvisibleIcon"), color: theme.list.itemDestructiveColor) - }) - } - + public static func editThemeIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListEditThemeIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Settings/EditTheme"), color: theme.list.itemAccentColor) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index aa89cf5558..eec3004928 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -45,7 +45,7 @@ public struct PresentationResourcesRootController { public static func navigationShareIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.rootController.navigationBar.buttonColor) + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: theme.chat.inputPanel.panelControlColor) }) // return theme.image(PresentationResourceKey.navigationShareIcon.rawValue, generateShareButtonImage) } @@ -200,6 +200,18 @@ public struct PresentationResourcesRootController { }) } + public static func navigationBackIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationBackIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Navigation/Back"), color: .white) + }) + } + + public static func navigationCloseIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationCloseIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: .white) + }) + } + public static func callListCallIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.callListCallIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Call List/NewCallListIcon"), color: theme.rootController.navigationBar.accentTextColor) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index 944b7a2ee4..2cd47b82f8 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -132,6 +132,10 @@ public struct PresentationResourcesSettings { public static let business = renderSettingsIcon(name: "Item List/Icons/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)]) public static let myProfile = renderSettingsIcon(name: "Item List/Icons/Profile", backgroundColors: [colorRed]) + public static let birthday = renderSettingsIcon(name: "Item List/Icons/Cake", backgroundColors: [colorBlue]) + public static let aiTools = renderSettingsIcon(name: "Item List/Icons/AITools", backgroundColors: [colorPurple]) + public static let yourColor = renderSettingsIcon(name: "Item List/Icons/Brush", backgroundColors: [colorLightBlue]) + public static let storageUsage = renderSettingsIcon(name: "Item List/Icons/Pie", backgroundColors: [colorOrange]) public static let dataUsage = renderSettingsIcon(name: "Item List/Icons/Stats", backgroundColors: [colorPurple]) @@ -213,48 +217,7 @@ public struct PresentationResourcesSettings { }) public static let ton = renderSettingsIcon(name: "Ads/TonAbout", backgroundColors: [UIColor(rgb: 0x32ade6)]) - -// generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in -// let bounds = CGRect(origin: CGPoint(), size: size) -// context.clear(bounds) -// -// context.setFillColor(UIColor(rgb: 0x32ade6).cgColor) -// context.fill(bounds) -// -// if let gradientImage, let cgImage = gradientImage.cgImage { -// context.saveGState() -// context.setBlendMode(.plusLighter) -// context.draw(cgImage, in: CGRect(origin: .zero, size: size)) -// context.restoreGState() -// } -// -// if let backdropImage, let cgImage = backdropImage.cgImage { -// context.saveGState() -// context.setBlendMode(.overlay) -// context.draw(cgImage, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size)) -// context.restoreGState() -// } -// -// context.setBlendMode(.normal) -// -// if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage { -// context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size)) -// } -// -// let outerPath = UIBezierPath(rect: CGRect(origin: .zero, size: size)) -// let innerPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 8.0) -// outerPath.append(innerPath) -// -// context.saveGState() -// outerPath.usesEvenOddFillRule = true -// context.addPath(outerPath.cgPath) -// context.clip(using: .evenOdd) -// -// context.setBlendMode(.clear) -// context.fill(CGRect(origin: .zero, size: size)) -// context.restoreGState() -// }) - + public static let stars = generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) diff --git a/submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift b/submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift new file mode 100644 index 0000000000..0f39bb3697 --- /dev/null +++ b/submodules/TelegramStringFormatting/Sources/InstantPagePreviewText.swift @@ -0,0 +1,184 @@ +import Foundation +import Postbox +import TelegramCore +import TelegramPresentationData + +extension RichText { + public func previewText(strings: PresentationStrings) -> String { + switch self { + case .empty: + return "" + case let .plain(value): + return value + case let .bold(value): + return value.previewText(strings: strings) + case let .italic(value): + return value.previewText(strings: strings) + case let .underline(value): + return value.previewText(strings: strings) + case let .strikethrough(value): + return value.previewText(strings: strings) + case let .fixed(value): + return value.previewText(strings: strings) + case let .url(value, _, _): + return value.previewText(strings: strings) + case let .email(value, _): + return value.previewText(strings: strings) + case let .concat(values): + var result = "" + for value in values { + result.append(value.previewText(strings: strings)) + } + return result + case let .`subscript`(value): + return value.previewText(strings: strings) + case let .superscript(value): + return value.previewText(strings: strings) + case let .marked(value): + return value.previewText(strings: strings) + case let .phone(value, _): + return value.previewText(strings: strings) + case .image: + return strings.Message_Photo + case let .anchor(value, _): + return value.previewText(strings: strings) + case .formula: + return strings.RichTextPreview_Formula + case let .textCustomEmoji(_, alt): + return alt + case let .textAutoEmail(value), let .textAutoPhone(value), let .textAutoUrl(value), let .textBankCard(value), let .textBotCommand(value), let .textCashtag(value), let .textHashtag(value), let .textMention(value), let .textMentionName(value, _), let .textSpoiler(value), let .textDate(value, _, _): + return value.previewText(strings: strings) + } + } +} + +extension InstantPageListItem { + public func previewText(strings: PresentationStrings, media: [MediaId: Media]) -> String { + switch self { + case .unknown: + return "" + case let .text(text, num, checked): + let body = text.previewText(strings: strings) + if let checked { + return "\(checked ? "☑︎" : "☐") \(body)" + } else if let num, !num.isEmpty { + return "\(num). \(body)" + } else { + return body + } + case let .blocks(blocks, num, checked): + var blocksText = "" + for block in blocks { + if !blocksText.isEmpty { + blocksText.append("\n") + } + blocksText.append(block.previewText(strings: strings, media: media)) + } + if let checked { + return "\(checked ? "☑︎" : "☐") \(blocksText)" + } else if let num { + return "\(num). \(blocksText)" + } else { + return blocksText + } + } + } +} + +extension InstantPageBlock { + public func previewText(strings: PresentationStrings, media: [MediaId: Media]) -> String { + switch self { + case .unsupported: + return "" + case let .title(text): + return text.previewText(strings: strings) + case let .subtitle(text): + return text.previewText(strings: strings) + case let .authorDate(author, _): + return author.previewText(strings: strings) + case let .header(text): + return text.previewText(strings: strings) + case let .subheader(text): + return text.previewText(strings: strings) + case let .heading(text, _): + return text.previewText(strings: strings) + case .formula: + return strings.RichTextPreview_Formula + case let .paragraph(text): + return text.previewText(strings: strings) + case let .preformatted(text, _): + return text.previewText(strings: strings) + case let .footer(text): + return text.previewText(strings: strings) + case .divider: + return "\n" + case .anchor: + return "" + case let .list(items, _): + var result = "" + for item in items { + if !result.isEmpty { + result.append("\n") + } + result.append(item.previewText(strings: strings, media: media)) + } + return result + case let .blockQuote(blocks, caption): + let body = blocks.map { $0.previewText(strings: strings, media: media) }.joined(separator: " ") + return body + caption.previewText(strings: strings) + case let .pullQuote(text, caption): + return text.previewText(strings: strings) + caption.previewText(strings: strings) + case .image(_, _, _, _): + return strings.Message_Photo + case .video(_, _, _, _): + return strings.Message_Video + case let .audio(id, _): + if let file = media[id] as? TelegramMediaFile, file.isVoice { + return strings.Message_Audio + } else { + return strings.RichTextPreview_Music + } + case .cover: + return "" + case .webEmbed: + return "" + case .postEmbed: + return "" + case .collage: + return "" + case .slideshow: + return "" + case .channelBanner: + return "" + case .kicker: + return "" + case .thinking: + return "" + case .table: + return strings.RichTextPreview_Table + case .details: + return "" + case .relatedArticles: + return "" + case .map: + return strings.Message_Location + } + } +} + +extension InstantPage { + public func previewText(strings: PresentationStrings) -> String { + let maxLength: Int = 200 + var result = "" + for block in self.blocks { + if !result.isEmpty { + result.append("\n") + } + result.append(block.previewText(strings: strings, media: self.media)) + if result.count > maxLength { + break + } + } + return result + } +} diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 86e0333ee1..87925bbfa0 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -282,10 +281,13 @@ public func messageTextWithAttributes(message: EngineMessage) -> NSAttributedStr } let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + if range.upperBound >= updatedString.length { + continue + } let currentDict = updatedString.attributes(at: range.lowerBound, effectiveRange: nil) var updatedAttributes: [NSAttributedString.Key: Any] = currentDict - updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) + updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) let insertString = NSAttributedString(string: updatedString.attributedSubstring(from: range).string, attributes: updatedAttributes) updatedString.replaceCharacters(in: range, with: insertString) @@ -310,6 +312,11 @@ public func messageContentKind(contentSettings: ContentSettings, message: Engine return kind } } + for attribute in message.attributes { + if let attribute = attribute as? RichTextMessageAttribute { + return .text(NSAttributedString(string: attribute.instantPage.previewText(strings: strings))) + } + } return .text(messageTextWithAttributes(message: message)) } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 15cc9e0b4b..c90687cf93 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1924,8 +1924,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) } else if let dice = media as? TelegramMediaDice, let gameOutcome = dice.gameOutcome { if let value = dice.value { + let isAccountPeerOutcome: Bool + if let forwardInfo = message.forwardInfo { + isAccountPeerOutcome = forwardInfo.author?.id == accountPeerId + } else { + isAccountPeerOutcome = message.author?.id == accountPeerId + } + let rawString: String - if message.author?.id == accountPeerId { + if isAccountPeerOutcome { if value == 1, let tonAmount = dice.tonAmount { let value = formatTonAmountText(tonAmount, dateTimeFormat: dateTimeFormat) rawString = strings.Conversation_EmojiStake_LostYou(value).string diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 96e730748f..49975963fd 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -115,6 +115,7 @@ swift_library( "//submodules/TelegramUI/Components/ComposePollScreen", "//submodules/AlertUI:AlertUI", "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/TelegramUI/Components/OpenUserGeneratedUrl:OpenUserGeneratedUrl", "//submodules/TouchDownGesture:TouchDownGesture", "//submodules/SwipeToDismissGesture:SwipeToDismissGesture", "//submodules/DirectionalPanGesture:DirectionalPanGesture", @@ -137,7 +138,6 @@ swift_library( "//submodules/OpenInExternalAppUI:OpenInExternalAppUI", "//submodules/LegacyUI:LegacyUI", "//submodules/ImageCompression:ImageCompression", - "//submodules/DateSelectionUI:DateSelectionUI", "//submodules/PasswordSetupUI:PasswordSetupUI", "//submodules/Pdf:Pdf", "//submodules/InstantPageUI:InstantPageUI", @@ -154,7 +154,6 @@ swift_library( "//submodules/ItemListPeerItem:ItemListPeerItem", "//submodules/ContactsPeerItem:ContactsPeerItem", "//submodules/TelegramPermissionsUI:TelegramPermissionsUI", - "//submodules/PeersNearbyIconNode:PeersNearbyIconNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/PasscodeUI:PasscodeUI", "//submodules/CallListUI:CallListUI", @@ -171,11 +170,9 @@ swift_library( "//submodules/YuvConversion:YuvConversion", "//submodules/JoinLinkPreviewUI:JoinLinkPreviewUI", "//submodules/LanguageLinkPreviewUI:LanguageLinkPreviewUI", - "//submodules/WebSearchUI:WebSearchUI", "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", "//submodules/MimeTypes:MimeTypes", "//submodules/LocalMediaResources:LocalMediaResources", - "//submodules/PeersNearbyUI:PeersNearbyUI", "//submodules/Geocoding:Geocoding", "//submodules/PeerInfoUI:PeerInfoUI", "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", @@ -219,7 +216,7 @@ swift_library( "//submodules/ItemListVenueItem:ItemListVenueItem", "//submodules/SemanticStatusNode:SemanticStatusNode", "//submodules/AccountUtils:AccountUtils", - "//submodules/Svg:Svg", + "//submodules/Svg/LegacyImpl", "//submodules/ManagedAnimationNode:ManagedAnimationNode", "//submodules/TooltipUI:TooltipUI", "//submodules/ListMessageItem:ListMessageItem", @@ -244,6 +241,7 @@ swift_library( "//submodules/GradientBackground:GradientBackground", "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", "//submodules/ComponentFlow:ComponentFlow", + "//submodules/TelegramUI/Components/ChatTimerScreen", "//submodules/AdUI:AdUI", "//submodules/SparseItemGrid:SparseItemGrid", "//submodules/CalendarMessageScreen:CalendarMessageScreen", @@ -522,6 +520,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatParticipantRightsScreen", "//submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen", "//submodules/TelegramUI/Components/Chat/ChatRankInfoScreen", + "//submodules/TelegramUI/Components/PollStatsScreen", "//submodules/TelegramUI/Components/RankChatPreviewItem", "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/TelegramUI/Components/CreateBotScreen", diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD index cf80f8d1e8..64b95cb004 100644 --- a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUIPreferences", "//submodules/StickerResources", diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift index 69991d5d3b..2463a7a93b 100644 --- a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/AdPanelHeaderPanelComponent.swift @@ -7,7 +7,6 @@ import ComponentDisplayAdapters import AccountContext import TelegramCore import SwiftSignalKit -import Postbox import PresentationDataUtils import ContextUI import AsyncDisplayKit diff --git a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift index b6e7d92aae..b6af73d81a 100644 --- a/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift +++ b/submodules/TelegramUI/Components/AdPanelHeaderPanelComponent/Sources/ChatAdPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -252,7 +251,7 @@ final class ChatAdPanelNode: ASDisplayNode { return panelHeight } - private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, message: EngineMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) -> CGFloat { + private func enqueueTransition(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, message: EngineMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) -> CGFloat { var animationTransition: ContainedViewLayoutTransition = .immediate if let animation = animation { @@ -313,7 +312,7 @@ final class ChatAdPanelNode: ASDisplayNode { } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first { switch firstMedia { case let .preview(dimensions, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let thumbnailMedia = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) if let dimensions { imageDimensions = dimensions.cgSize } @@ -353,7 +352,7 @@ final class ChatAdPanelNode: ASDisplayNode { } var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchMediaSignal: Signal? + var updatedFetchMediaSignal: Signal? if mediaUpdated { if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { @@ -366,7 +365,7 @@ final class ChatAdPanelNode: ASDisplayNode { if fileReference.media.isAnimatedSticker { let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512) updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))) - updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)) + updatedFetchMediaSignal = context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media)) } else if fileReference.media.isVideo || fileReference.media.isAnimated { updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: false) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD b/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD index ab1b9a45f7..c2e9d97ddd 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/BUILD @@ -12,12 +12,12 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", "//submodules/Components/ViewControllerComponent", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ResizableSheetComponent", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BundleIconComponent", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift index dc1f55617c..f89ecc8d9f 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsPeerComponent.swift @@ -227,7 +227,7 @@ final class AdminUserActionsPeerComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.title, font: Font.semibold(component.baseFontSize), textColor: component.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: component.title, font: Font.medium(component.baseFontSize), textColor: component.theme.list.itemPrimaryTextColor)) )), environment: {}, containerSize: CGSize(width: maxTextSize, height: 100.0) diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift index 54d828be17..2f7a4dc717 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift @@ -1,24 +1,21 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import ComponentFlow import SwiftSignalKit import ViewControllerComponent -import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore import MultilineTextComponent import ButtonComponent import PresentationDataUtils -import Markdown -import UndoUI -import AvatarNode -import TelegramStringFormatting import ListSectionComponent import ListActionItemComponent import PlainButtonComponent +import ResizableSheetComponent +import GlassBarButtonComponent +import BundleIconComponent struct MediaRight: OptionSet, Hashable { var rawValue: Int @@ -32,6 +29,7 @@ struct MediaRight: OptionSet, Hashable { static let videoMessages = MediaRight(rawValue: 1 << 6) static let links = MediaRight(rawValue: 1 << 7) static let polls = MediaRight(rawValue: 1 << 8) + static let reactions = MediaRight(rawValue: 1 << 9) } extension MediaRight { @@ -78,7 +76,8 @@ private func rightsFromBannedRights(_ rights: TelegramChatBannedRightsFlags) -> .voiceMessages, .videoMessages, .links, - .polls + .polls, + .reactions ] if rights.contains(.banSendText) { @@ -171,6 +170,9 @@ private func rightFlagsFromRights(participantRights: ParticipantRight, mediaRigh if !mediaRights.contains(.polls) { result.insert(.banSendPolls) } + if !mediaRights.contains(.reactions) { + result.insert(.banSendReactions) + } return result } @@ -184,9 +186,779 @@ private let allMediaRightItems: [MediaRight] = [ .voiceMessages, .videoMessages, .links, - .polls + .polls, + .reactions ] +private enum AdminUserActionOptionSection { + case report + case deleteAll + case ban +} + +private enum AdminUserDeleteAllOption { + case messages + case reactions +} + +private enum AdminUserActionConfigItem: Hashable, CaseIterable { + case sendMessages + case sendMedia + case addUsers + case pinMessages + case changeInfo +} + +private struct AdminUserActionsSheetState: Equatable { + var isOptionReportExpanded: Bool + var optionReportSelectedPeers: Set + var isOptionDeleteAllExpanded: Bool + var optionDeleteAllSelectedPeers: Set + var optionDeleteAllReactionsSelectedPeers: Set + var isOptionBanExpanded: Bool + var optionBanSelectedPeers: Set + var isConfigurationExpanded: Bool + var isMediaSectionExpanded: Bool + var allowedParticipantRights: ParticipantRight + var allowedMediaRights: MediaRight + var participantRights: ParticipantRight + var mediaRights: MediaRight +} + +private func availableAdminUserActionOptionSections( + accountPeerId: EnginePeer.Id, + chatPeer: EnginePeer, + peers: [RenderedChannelParticipant], + mode: AdminUserActionsSheet.Mode +) -> [AdminUserActionOptionSection] { + var result: [AdminUserActionOptionSection] = [.report] + + switch mode { + case .monoforum: + result.append(.ban) + case .chat, .chatReaction: + if case let .channel(channel) = chatPeer { + if channel.hasPermission(.deleteAllMessages) { + result.append(.deleteAll) + + if channel.hasPermission(.banMembers) { + var canBanEveryone = true + for peer in peers { + if peer.peer.id == accountPeerId { + canBanEveryone = false + continue + } + + switch peer.participant { + case .creator: + canBanEveryone = false + case let .member(_, _, adminInfo, _, _, _): + if let adminInfo { + if channel.flags.contains(.isCreator) { + } else if adminInfo.promotedBy == accountPeerId { + } else { + canBanEveryone = false + } + } + } + } + + if canBanEveryone { + result.append(.ban) + } + } + } + } + case .liveStream: + result.append(.deleteAll) + result.append(.ban) + } + + return result +} + +private func adminUserActionsTitle( + strings: PresentationStrings, + mode: AdminUserActionsSheet.Mode, + peers: [RenderedChannelParticipant], + selectedDeleteAllPeers: Set +) -> String { + switch mode { + case .monoforum: + if let peer = peers.first { + return strings.Monoforum_DeleteTopic_Title(peer.peer.compactDisplayTitle).string + } else { + return strings.Common_Delete + } + case let .chat(messageCount, deleteAllMessageCount, _): + if let deleteAllMessageCount, selectedDeleteAllPeers == Set(peers.map { $0.peer.id }) { + return strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount)) + } else { + return strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount)) + } + case let .liveStream(messageCount, deleteAllMessageCount, _): + if let deleteAllMessageCount, selectedDeleteAllPeers == Set(peers.map { $0.peer.id }) { + return strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount)) + } else { + return strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount)) + } + case .chatReaction: + return strings.Chat_AdminActionSheet_DeleteReactionTitle + } +} + +private final class AdminUserActionsContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let chatPeer: EnginePeer + let peers: [RenderedChannelParticipant] + let mode: AdminUserActionsSheet.Mode + let theme: PresentationTheme + let strings: PresentationStrings + let presentationData: PresentationData + let sheetState: AdminUserActionsSheetState + let disableOptionsSectionAnimation: Bool + let toggleOptionSelection: (AdminUserActionOptionSection) -> Void + let toggleOptionExpansion: (AdminUserActionOptionSection) -> Void + let togglePeerSelection: (AdminUserActionOptionSection, EnginePeer) -> Void + let toggleDeleteAllOptionPeerSelection: (AdminUserDeleteAllOption, EnginePeer) -> Void + let toggleConfiguration: () -> Void + let toggleConfigItem: (AdminUserActionConfigItem) -> Void + let toggleMediaSectionExpansion: () -> Void + let toggleMediaRight: (MediaRight) -> Void + + init( + context: AccountContext, + chatPeer: EnginePeer, + peers: [RenderedChannelParticipant], + mode: AdminUserActionsSheet.Mode, + theme: PresentationTheme, + strings: PresentationStrings, + presentationData: PresentationData, + sheetState: AdminUserActionsSheetState, + disableOptionsSectionAnimation: Bool, + toggleOptionSelection: @escaping (AdminUserActionOptionSection) -> Void, + toggleOptionExpansion: @escaping (AdminUserActionOptionSection) -> Void, + togglePeerSelection: @escaping (AdminUserActionOptionSection, EnginePeer) -> Void, + toggleDeleteAllOptionPeerSelection: @escaping (AdminUserDeleteAllOption, EnginePeer) -> Void, + toggleConfiguration: @escaping () -> Void, + toggleConfigItem: @escaping (AdminUserActionConfigItem) -> Void, + toggleMediaSectionExpansion: @escaping () -> Void, + toggleMediaRight: @escaping (MediaRight) -> Void + ) { + self.context = context + self.chatPeer = chatPeer + self.peers = peers + self.mode = mode + self.theme = theme + self.strings = strings + self.presentationData = presentationData + self.sheetState = sheetState + self.disableOptionsSectionAnimation = disableOptionsSectionAnimation + self.toggleOptionSelection = toggleOptionSelection + self.toggleOptionExpansion = toggleOptionExpansion + self.togglePeerSelection = togglePeerSelection + self.toggleDeleteAllOptionPeerSelection = toggleDeleteAllOptionPeerSelection + self.toggleConfiguration = toggleConfiguration + self.toggleConfigItem = toggleConfigItem + self.toggleMediaSectionExpansion = toggleMediaSectionExpansion + self.toggleMediaRight = toggleMediaRight + } + + static func ==(lhs: AdminUserActionsContentComponent, rhs: AdminUserActionsContentComponent) -> Bool { + return false + } + + final class View: UIView { + private let optionsSection = ComponentView() + private let optionsFooter = ComponentView() + private let configSection = ComponentView() + + func update(component: AdminUserActionsContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let sideInset: CGFloat = 16.0 + var contentHeight: CGFloat = 76.0 + 15.0 + + let availableOptions = availableAdminUserActionOptionSections( + accountPeerId: component.context.account.peerId, + chatPeer: component.chatPeer, + peers: component.peers, + mode: component.mode + ) + + let optionsItem: (AdminUserActionOptionSection) -> AnyComponentWithIdentity = { section in + let sectionId: AnyHashable + let selectedPeers: Set + var additionalSelectedPeers = Set() + let isExpanded: Bool + let title: String + + switch section { + case .report: + sectionId = "report" + selectedPeers = component.sheetState.optionReportSelectedPeers + isExpanded = component.sheetState.isOptionReportExpanded + title = component.strings.Chat_AdminActionSheet_ReportSpam + case .deleteAll: + sectionId = "delete-all" + selectedPeers = component.sheetState.optionDeleteAllSelectedPeers + additionalSelectedPeers = component.sheetState.optionDeleteAllReactionsSelectedPeers + isExpanded = component.sheetState.isOptionDeleteAllExpanded + if component.peers.count == 1 { + title = component.strings.Chat_AdminActionSheet_DeleteAllSingle(component.peers[0].peer.compactDisplayTitle).string + } else { + title = component.strings.Chat_AdminActionSheet_DeleteAllMultiple + } + case .ban: + sectionId = "ban" + selectedPeers = component.sheetState.optionBanSelectedPeers + isExpanded = component.sheetState.isOptionBanExpanded + + let banTitle: String + let restrictTitle: String + if component.peers.count == 1 { + banTitle = component.strings.Chat_AdminActionSheet_BanSingle(component.peers[0].peer.compactDisplayTitle).string + restrictTitle = component.strings.Chat_AdminActionSheet_RestrictSingle(component.peers[0].peer.compactDisplayTitle).string + } else { + banTitle = component.strings.Chat_AdminActionSheet_BanMultiple + restrictTitle = component.strings.Chat_AdminActionSheet_RestrictMultiple + } + title = component.sheetState.isConfigurationExpanded ? restrictTitle : banTitle + } + + var titleItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: title, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ] + + var accessory: ListActionItemComponent.Accessory? + var isExpandable = false + if component.peers.count > 1 { + let selectedCount = selectedPeers.union(additionalSelectedPeers).count + accessory = .custom(ListActionItemComponent.CustomAccessory( + component: AnyComponentWithIdentity(id: 0, component: AnyComponent(PlainButtonComponent( + content: AnyComponent(OptionSectionExpandIndicatorComponent( + theme: component.theme, + count: selectedCount == 0 ? component.peers.count : selectedCount, + isExpanded: isExpanded + )), + effectAlignment: .center, + action: { + component.toggleOptionExpansion(section) + }, + animateScale: false + ))), + insets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 2.0), + isInteractive: true + )) + } else if case .deleteAll = section { + var count = 0 + if !selectedPeers.isEmpty { + count += 1 + } + if !additionalSelectedPeers.isEmpty { + count += 1 + } + titleItems.append( + AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent( + theme: component.theme, + title: "\(count)/2", + isExpanded: isExpanded + ))) + ) + isExpandable = true + } + + return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListActionItemComponent( + theme: component.theme, + style: .glass, + title: AnyComponent(HStack(titleItems, spacing: 7.0)), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: !selectedPeers.isEmpty || !additionalSelectedPeers.isEmpty, + toggle: { + component.toggleOptionSelection(section) + } + )), + icon: .none, + accessory: accessory, + action: { _ in + if isExpandable { + component.toggleOptionExpansion(section) + } else { + component.toggleOptionSelection(section) + } + }, + highlighting: .disabled + ))) + } + + let expandedPeersItem: (AdminUserActionOptionSection) -> AnyComponentWithIdentity = { section in + let sectionId: AnyHashable + let selectedPeers: Set + var additionalSelectedPeers = Set() + switch section { + case .report: + sectionId = "report-peers" + selectedPeers = component.sheetState.optionReportSelectedPeers + case .deleteAll: + sectionId = "delete-all-peers" + selectedPeers = component.sheetState.optionDeleteAllSelectedPeers + additionalSelectedPeers = component.sheetState.optionDeleteAllReactionsSelectedPeers + case .ban: + sectionId = "ban-peers" + selectedPeers = component.sheetState.optionBanSelectedPeers + } + + var subItems: [AnyComponentWithIdentity] = [] + if component.peers.count > 1 { + for peer in component.peers { + subItems.append(AnyComponentWithIdentity(id: peer.peer.id, component: AnyComponent(AdminUserActionsPeerComponent( + context: component.context, + theme: component.theme, + strings: component.strings, + baseFontSize: component.presentationData.listsFontSize.baseDisplaySize, + sideInset: 0.0, + title: peer.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), + peer: peer.peer, + selectionState: .editing(isSelected: selectedPeers.contains(peer.peer.id) || additionalSelectedPeers.contains(peer.peer.id)), + action: { peer in + component.togglePeerSelection(section, peer) + } + )))) + } + } else { + subItems.append( + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: component.theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Chat_AdminActionSheet_DeleteAllMessages, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: !selectedPeers.isEmpty, + toggle: { + component.toggleDeleteAllOptionPeerSelection(.messages, component.peers[0].peer) + } + )), + icon: .none, + accessory: nil, + action: { _ in + component.toggleDeleteAllOptionPeerSelection(.messages, component.peers[0].peer) + }, + highlighting: .disabled + ))) + ) + subItems.append( + AnyComponentWithIdentity(id: 1, component: AnyComponent(ListActionItemComponent( + theme: component.theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Chat_AdminActionSheet_DeleteAllReactions, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: !additionalSelectedPeers.isEmpty, + toggle: { + component.toggleDeleteAllOptionPeerSelection(.reactions, component.peers[0].peer) + } + )), + icon: .none, + accessory: nil, + action: { _ in + component.toggleDeleteAllOptionPeerSelection(.reactions, component.peers[0].peer) + }, + highlighting: .disabled + ))) + ) + } + + return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListSubSectionComponent( + theme: component.theme, + leftInset: 43.0, + items: subItems + ))) + } + + var optionsSectionItems: [AnyComponentWithIdentity] = [] + for option in availableOptions { + let isExpanded: Bool + switch option { + case .report: + isExpanded = component.sheetState.isOptionReportExpanded + case .deleteAll: + isExpanded = component.sheetState.isOptionDeleteAllExpanded + case .ban: + isExpanded = component.sheetState.isOptionBanExpanded + } + + optionsSectionItems.append(optionsItem(option)) + if isExpanded { + optionsSectionItems.append(expandedPeersItem(option)) + } + } + + let optionsSectionTransition: ComponentTransition = component.disableOptionsSectionAnimation ? transition.withAnimation(.none) : transition + let optionsSectionSize = self.optionsSection.update( + transition: optionsSectionTransition, + component: AnyComponent(ListSectionComponent( + theme: component.theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Chat_AdminActionSheet_RestrictSectionHeader, + font: Font.regular(component.presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: component.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: optionsSectionItems, + isModal: true + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) + ) + let optionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: optionsSectionSize) + self.optionsSection.parentState = state + if let optionsSectionView = self.optionsSection.view { + if optionsSectionView.superview == nil { + self.addSubview(optionsSectionView) + } + transition.setFrame(view: optionsSectionView, frame: optionsSectionFrame) + } + contentHeight += optionsSectionSize.height + + let partiallyRestrictTitle: String + let fullyBanTitle: String + if component.peers.count == 1 { + partiallyRestrictTitle = component.strings.Chat_AdminActionSheet_RestrictFooterSingle + fullyBanTitle = component.strings.Chat_AdminActionSheet_BanFooterSingle + } else { + partiallyRestrictTitle = component.strings.Chat_AdminActionSheet_RestrictFooterMultiple + fullyBanTitle = component.strings.Chat_AdminActionSheet_BanFooterMultiple + } + + let optionsFooterSize = self.optionsFooter.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(OptionsSectionFooterComponent( + theme: component.theme, + text: component.sheetState.isConfigurationExpanded ? fullyBanTitle : partiallyRestrictTitle, + fontSize: component.presentationData.listsFontSize.itemListBaseHeaderFontSize, + isExpanded: component.sheetState.isConfigurationExpanded + )), + effectAlignment: .left, + contentInsets: UIEdgeInsets(), + action: { + component.toggleConfiguration() + }, + animateAlpha: true, + animateScale: false, + animateContents: true + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + + var configSectionItems: [AnyComponentWithIdentity] = [] + + if case let .channel(channel) = component.chatPeer, channel.isMonoForum { + } else if case .liveStream = component.mode { + } else { + var allConfigItems: [(AdminUserActionConfigItem, Bool)] = [] + if !component.sheetState.allowedMediaRights.isEmpty || !component.sheetState.allowedParticipantRights.isEmpty { + for configItem in AdminUserActionConfigItem.allCases { + let isEnabled: Bool + switch configItem { + case .sendMessages: + isEnabled = component.sheetState.allowedParticipantRights.contains(.sendMessages) + case .sendMedia: + isEnabled = !component.sheetState.allowedMediaRights.isEmpty + case .addUsers: + isEnabled = component.sheetState.allowedParticipantRights.contains(.addMembers) + case .pinMessages: + isEnabled = component.sheetState.allowedParticipantRights.contains(.pinMessages) + case .changeInfo: + isEnabled = component.sheetState.allowedParticipantRights.contains(.changeInfo) + } + allConfigItems.append((configItem, isEnabled)) + } + } + + for (configItem, isEnabled) in allConfigItems { + let itemTitle: AnyComponent + let itemValue: Bool + switch configItem { + case .sendMessages: + itemTitle = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_BanUser_PermissionSendMessages, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )) + itemValue = component.sheetState.participantRights.contains(.sendMessages) + case .sendMedia: + if isEnabled { + itemTitle = AnyComponent(HStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_BanUser_PermissionSendMedia, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent( + theme: component.theme, + title: "\(component.sheetState.mediaRights.count)/\(component.sheetState.allowedMediaRights.count)", + isExpanded: component.sheetState.isMediaSectionExpanded + ))) + ], spacing: 7.0)) + } else { + itemTitle = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_BanUser_PermissionSendMedia, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )) + } + itemValue = !component.sheetState.mediaRights.isEmpty + case .addUsers: + itemTitle = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_BanUser_PermissionAddMembers, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )) + itemValue = component.sheetState.participantRights.contains(.addMembers) + case .pinMessages: + itemTitle = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_EditAdmin_PermissionPinMessages, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )) + itemValue = component.sheetState.participantRights.contains(.pinMessages) + case .changeInfo: + itemTitle = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.strings.Channel_BanUser_PermissionChangeGroupInfo, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )) + itemValue = component.sheetState.participantRights.contains(.changeInfo) + } + + configSectionItems.append(AnyComponentWithIdentity(id: configItem, component: AnyComponent(ListActionItemComponent( + theme: component.theme, + style: .glass, + title: itemTitle, + accessory: .toggle(ListActionItemComponent.Toggle( + style: isEnabled ? .icons : .lock, + isOn: itemValue, + isInteractive: isEnabled, + action: isEnabled ? { _ in + component.toggleConfigItem(configItem) + } : nil + )), + action: ((isEnabled && configItem == .sendMedia) || !isEnabled) ? { _ in + if !isEnabled { + environment.controller()?.present(textAlertController( + context: component.context, + title: nil, + text: component.strings.GroupPermission_PermissionDisabledByDefault, + actions: [ + TextAlertAction(type: .defaultAction, title: component.strings.Common_OK, action: { + }) + ] + ), in: .window(.root)) + } else { + component.toggleMediaSectionExpansion() + } + } : nil, + highlighting: .disabled + )))) + + if isEnabled, case .sendMedia = configItem, component.sheetState.isMediaSectionExpanded { + var mediaItems: [AnyComponentWithIdentity] = [] + mediaRightsLoop: for possibleMediaItem in allMediaRightItems { + if !component.sheetState.allowedMediaRights.contains(possibleMediaItem) { + continue + } + + let mediaItemTitle: String + switch possibleMediaItem { + case .photos: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendPhoto + case .videos: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendVideo + case .stickersAndGifs: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendStickersAndGifs + case .music: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendMusic + case .files: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendFile + case .voiceMessages: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendVoiceMessage + case .videoMessages: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendVideoMessage + case .links: + mediaItemTitle = component.strings.Channel_BanUser_PermissionEmbedLinks + case .polls: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendPolls + case .reactions: + mediaItemTitle = component.strings.Channel_BanUser_PermissionSendReactions + default: + continue mediaRightsLoop + } + + mediaItems.append(AnyComponentWithIdentity(id: possibleMediaItem, component: AnyComponent(ListActionItemComponent( + theme: component.theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: mediaItemTitle, + font: Font.regular(component.presentationData.listsFontSize.baseDisplaySize), + textColor: component.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))) + ], alignment: .left, spacing: 2.0)), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: component.sheetState.mediaRights.contains(possibleMediaItem), + toggle: { + component.toggleMediaRight(possibleMediaItem) + } + )), + icon: .none, + accessory: .none, + action: { _ in + component.toggleMediaRight(possibleMediaItem) + }, + highlighting: .disabled + )))) + } + configSectionItems.append(AnyComponentWithIdentity(id: "media-sub", component: AnyComponent(ListSubSectionComponent( + theme: component.theme, + leftInset: 0.0, + items: mediaItems + )))) + } + } + } + + let configSectionSize = self.configSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: component.theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: component.peers.count == 1 ? component.strings.Chat_AdminActionSheet_PermissionsSectionHeader : component.strings.Chat_AdminActionSheet_PermissionsSectionHeaderMultiple, + font: Font.regular(component.presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: component.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: configSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) + ) + let configSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 30.0), size: configSectionSize) + self.configSection.parentState = state + if let configSectionView = self.configSection.view { + if configSectionView.superview == nil { + configSectionView.clipsToBounds = true + configSectionView.layer.cornerRadius = 11.0 + self.addSubview(configSectionView) + } + let effectiveConfigSectionFrame: CGRect + if component.sheetState.isConfigurationExpanded { + effectiveConfigSectionFrame = configSectionFrame + } else { + effectiveConfigSectionFrame = CGRect(origin: CGPoint(x: configSectionFrame.minX, y: configSectionFrame.minY - 30.0), size: CGSize(width: configSectionFrame.width, height: 0.0)) + } + transition.setFrame(view: configSectionView, frame: effectiveConfigSectionFrame) + transition.setAlpha(view: configSectionView, alpha: component.sheetState.isConfigurationExpanded ? 1.0 : 0.0) + } + + if availableOptions.contains(.ban) && !configSectionItems.isEmpty { + let optionsFooterFrame: CGRect + if component.sheetState.isConfigurationExpanded { + contentHeight += 30.0 + contentHeight += configSectionSize.height + contentHeight += 7.0 + optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) + contentHeight += optionsFooterSize.height + } else { + contentHeight += 7.0 + optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) + contentHeight += optionsFooterSize.height + } + self.optionsFooter.parentState = state + if let optionsFooterView = self.optionsFooter.view { + if optionsFooterView.superview == nil { + self.addSubview(optionsFooterView) + } + transition.setFrame(view: optionsFooterView, frame: optionsFooterFrame) + transition.setAlpha(view: optionsFooterView, alpha: 1.0) + } + } else { + self.optionsFooter.parentState = state + if let optionsFooterView = self.optionsFooter.view { + if optionsFooterView.superview == nil { + self.addSubview(optionsFooterView) + } + let optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) + transition.setFrame(view: optionsFooterView, frame: optionsFooterFrame) + transition.setAlpha(view: optionsFooterView, alpha: 0.0) + } + } + + contentHeight += 36.0 + 52.0 + 30.0 + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + private final class AdminUserActionsSheetComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -211,62 +983,21 @@ private final class AdminUserActionsSheetComponent: Component { return true } - private struct ItemLayout: Equatable { - var containerSize: CGSize - var containerInset: CGFloat - var bottomInset: CGFloat - var topInset: CGFloat - - init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { - self.containerSize = containerSize - self.containerInset = containerInset - self.bottomInset = bottomInset - self.topInset = topInset - } - } - - private final class ScrollView: UIScrollView { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - } - - final class View: UIView, UIScrollViewDelegate { - private let dimView: UIView - private let backgroundLayer: SimpleLayer - private let navigationBarContainer: SparseContainerView - private let navigationBackgroundView: BlurredBackgroundView - private let navigationBarSeparator: SimpleLayer - private let scrollView: ScrollView - private let scrollContentClippingView: SparseContainerView - private let scrollContentView: UIView - - private let leftButton = ComponentView() - - private let title = ComponentView() - private let actionButton = ComponentView() - - private let optionsSection = ComponentView() - private let optionsFooter = ComponentView() - private let configSection = ComponentView() - - private let bottomOverscrollLimit: CGFloat - - private var ignoreScrolling: Bool = false + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() private var component: AdminUserActionsSheetComponent? private weak var state: EmptyComponentState? private var environment: ViewControllerComponentContainer.Environment? private var isUpdating: Bool = false - - private var itemLayout: ItemLayout? - - private var topOffsetDistance: CGFloat? + private var isDismissing: Bool = false private var isOptionReportExpanded: Bool = false private var optionReportSelectedPeers = Set() private var isOptionDeleteAllExpanded: Bool = false private var optionDeleteAllSelectedPeers = Set() + private var optionDeleteAllReactionsSelectedPeers = Set() private var isOptionBanExpanded: Bool = false private var optionBanSelectedPeers = Set() @@ -281,110 +1012,13 @@ private final class AdminUserActionsSheetComponent: Component { private var previousWasConfigurationExpanded: Bool = false override init(frame: CGRect) { - self.bottomOverscrollLimit = 200.0 - - self.dimView = UIView() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - self.backgroundLayer.cornerRadius = 10.0 - - self.navigationBarContainer = SparseContainerView() - - self.navigationBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) - self.navigationBarSeparator = SimpleLayer() - - self.scrollView = ScrollView() - - self.scrollContentClippingView = SparseContainerView() - self.scrollContentClippingView.clipsToBounds = true - - self.scrollContentView = UIView() - super.init(frame: frame) - - self.addSubview(self.dimView) - self.layer.addSublayer(self.backgroundLayer) - - self.scrollView.delaysContentTouches = true - self.scrollView.canCancelContentTouches = true - self.scrollView.clipsToBounds = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.alwaysBounceVertical = true - self.scrollView.scrollsToTop = false - self.scrollView.delegate = self - self.scrollView.clipsToBounds = true - - self.addSubview(self.scrollContentClippingView) - self.scrollContentClippingView.addSubview(self.scrollView) - - self.scrollView.addSubview(self.scrollContentView) - - self.addSubview(self.navigationBarContainer) - - self.navigationBarContainer.addSubview(self.navigationBackgroundView) - self.navigationBarContainer.layer.addSublayer(self.navigationBarSeparator) - - self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateScrolling(transition: .immediate) - } - } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - /*guard let itemLayout = self.itemLayout, let topOffsetDistance = self.topOffsetDistance else { - return - } - - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - topOffset = max(0.0, topOffset) - - if topOffset < topOffsetDistance { - targetContentOffset.pointee.y = scrollView.contentOffset.y - scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true) - }*/ - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - if !self.backgroundLayer.frame.contains(point) { - return self.dimView - } - - if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { - return result - } - - let result = super.hitTest(point, with: event) - return result - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - guard let environment = self.environment, let controller = environment.controller() else { - return - } - controller.dismiss() - } - } - private func calculateMonoforumResult() -> AdminUserActionsSheet.MonoforumResult { return AdminUserActionsSheet.MonoforumResult( ban: !self.optionBanSelectedPeers.isEmpty, @@ -395,6 +1029,7 @@ private final class AdminUserActionsSheetComponent: Component { private func calculateChatResult() -> AdminUserActionsSheet.ChatResult { var reportSpamPeers: [EnginePeer.Id] = [] var deleteAllFromPeers: [EnginePeer.Id] = [] + var deleteAllReactionsFromPeers: [EnginePeer.Id] = [] var banPeers: [EnginePeer.Id] = [] var updateBannedRights: [EnginePeer.Id: TelegramChatBannedRights] = [:] @@ -405,14 +1040,16 @@ private final class AdminUserActionsSheetComponent: Component { deleteAllFromPeers.append(id) } + for id in self.optionDeleteAllReactionsSelectedPeers.sorted() { + deleteAllReactionsFromPeers.append(id) + } + if !self.isConfigurationExpanded { for id in self.optionBanSelectedPeers.sorted() { banPeers.append(id) } } else { - var banFlags: TelegramChatBannedRightsFlags = [] - banFlags = rightFlagsFromRights(participantRights: self.participantRights, mediaRights: self.mediaRights) - + let banFlags = rightFlagsFromRights(participantRights: self.participantRights, mediaRights: self.mediaRights) let bannedRights = TelegramChatBannedRights(flags: banFlags, untilDate: Int32.max) for id in self.optionBanSelectedPeers.sorted() { updateBannedRights[id] = bannedRights @@ -422,12 +1059,13 @@ private final class AdminUserActionsSheetComponent: Component { return AdminUserActionsSheet.ChatResult( reportSpamPeers: reportSpamPeers, deleteAllFromPeers: deleteAllFromPeers, + deleteAllReactionsFromPeers: deleteAllReactionsFromPeers, banPeers: banPeers, updateBannedRights: updateBannedRights ) } - private func calculateLiveStreamResult() -> AdminUserActionsSheet.LiveStreamResult { + private func calculateLiveStreamResult() -> AdminUserActionsSheet.LiveStreamResult { return AdminUserActionsSheet.LiveStreamResult( reportSpam: !self.optionReportSelectedPeers.isEmpty, deleteAll: !self.optionDeleteAllSelectedPeers.isEmpty, @@ -435,85 +1073,16 @@ private final class AdminUserActionsSheetComponent: Component { ) } - private func updateScrolling(transition: ComponentTransition) { - guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { - return - } - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - - let navigationAlpha: CGFloat = 1.0 - max(0.0, min(1.0, (topOffset + 20.0) / 20.0)) - transition.setAlpha(view: self.navigationBackgroundView, alpha: navigationAlpha) - transition.setAlpha(layer: self.navigationBarSeparator, alpha: navigationAlpha) - - topOffset = max(0.0, topOffset) - transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) - - transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - - let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25)) - self.topOffsetDistance = topOffsetDistance - var topOffsetFraction = topOffset / topOffsetDistance - topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - - let transitionFactor: CGFloat = 1.0 - topOffsetFraction - if self.isUpdating { - DispatchQueue.main.async { [weak controller] in - guard let controller else { - return - } - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) - } - } else { - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) - } - } - - func animateIn() { - self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - } - - func animateOut(completion: @escaping () -> Void) { - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - } - - if let environment = self.environment, let controller = environment.controller() { - controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut)) - } - } - func update(component: AdminUserActionsSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { self.isUpdating = false } - let environment = environment[ViewControllerComponentContainer.Environment.self].value - let themeUpdated = self.environment?.theme !== environment.theme - - let resetScrolling = self.scrollView.bounds.width != availableSize.width - - let sideInset: CGFloat = 16.0 + environment.safeInsets.left - if self.component == nil { let _ = (component.context.account.postbox.peerView(id: component.chatPeer.id) |> take(1)).start(next: { [weak self] peerView in - guard let self else{ + guard let self else { return } @@ -582,188 +1151,90 @@ private final class AdminUserActionsSheetComponent: Component { self.component = component self.state = state - self.environment = environment - if themeUpdated { - self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor - - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - } + let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environmentValue + let controller = environmentValue.controller + let theme = environmentValue.theme.withModalBlocksBackground() let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) - - var contentHeight: CGFloat = 0.0 - contentHeight += 54.0 - contentHeight += 16.0 - - let leftButtonSize = self.leftButton.update( - transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), - action: { [weak self] in - guard let self, let controller = self.environment?.controller() else { - return - } - controller.dismiss() + let dismiss: (Bool) -> Void = { [weak self] animated in + guard let self, !self.isDismissing else { + return + } + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? AdminUserActionsSheet { + controller.completePendingDismiss() + controller.dismiss(animated: false) } - ).minSize(CGSize(width: 44.0, height: 56.0))), - environment: {}, - containerSize: CGSize(width: 120.0, height: 100.0) + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() + } + } + + let disableOptionsSectionAnimation = self.previousWasConfigurationExpanded != self.isConfigurationExpanded + self.previousWasConfigurationExpanded = self.isConfigurationExpanded + + let currentState = AdminUserActionsSheetState( + isOptionReportExpanded: self.isOptionReportExpanded, + optionReportSelectedPeers: self.optionReportSelectedPeers, + isOptionDeleteAllExpanded: self.isOptionDeleteAllExpanded, + optionDeleteAllSelectedPeers: self.optionDeleteAllSelectedPeers, + optionDeleteAllReactionsSelectedPeers: self.optionDeleteAllReactionsSelectedPeers, + isOptionBanExpanded: self.isOptionBanExpanded, + optionBanSelectedPeers: self.optionBanSelectedPeers, + isConfigurationExpanded: self.isConfigurationExpanded, + isMediaSectionExpanded: self.isMediaSectionExpanded, + allowedParticipantRights: self.allowedParticipantRights, + allowedMediaRights: self.allowedMediaRights, + participantRights: self.participantRights, + mediaRights: self.mediaRights ) - let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 0.0), size: leftButtonSize) - if let leftButtonView = self.leftButton.view { - if leftButtonView.superview == nil { - self.navigationBarContainer.addSubview(leftButtonView) + + let performMainAction: () -> Void = { [weak self] in + guard let self, let component = self.component else { + return + } + let monoforumResult = self.calculateMonoforumResult() + let chatResult = self.calculateChatResult() + let liveStreamResult = self.calculateLiveStreamResult() + + dismiss(true) + + switch component.mode { + case let .monoforum(completion): + completion(monoforumResult) + case let .chat(_, _, completion): + completion(chatResult) + case let .liveStream(_, _, completion): + completion(liveStreamResult) + case let .chatReaction(completion): + completion(chatResult) } - transition.setFrame(view: leftButtonView, frame: leftButtonFrame) } - let containerInset: CGFloat = environment.statusBarHeight + 10.0 - - let clippingY: CGFloat - - enum OptionsSection { - case report - case deleteAll - case ban - } - - var availableOptions: [OptionsSection] = [] - availableOptions.append(.report) - - switch component.mode { - case .monoforum: - availableOptions.append(.ban) - case .chat: - if case let .channel(channel) = component.chatPeer { - if channel.hasPermission(.deleteAllMessages) { - availableOptions.append(.deleteAll) - - if channel.hasPermission(.banMembers) { - var canBanEveryone = true - for peer in component.peers { - if peer.peer.id == component.context.account.peerId { - canBanEveryone = false - continue - } - - switch peer.participant { - case .creator: - canBanEveryone = false - case let .member(_, _, adminInfo, banInfo, _, _): - let _ = banInfo - if let adminInfo { - if channel.flags.contains(.isCreator) { - } else if adminInfo.promotedBy == component.context.account.peerId { - } else { - canBanEveryone = false - } - } - } - } - - if canBanEveryone { - availableOptions.append(.ban) - } - } - } - } - case .liveStream: - availableOptions.append(.deleteAll) - availableOptions.append(.ban) - } - - let optionsItem: (OptionsSection) -> AnyComponentWithIdentity = { section in - let sectionId: AnyHashable - let selectedPeers: Set - let isExpanded: Bool - var title: String - - switch section { - case .report: - sectionId = "report" - selectedPeers = self.optionReportSelectedPeers - isExpanded = self.isOptionReportExpanded - - title = environment.strings.Chat_AdminActionSheet_ReportSpam - case .deleteAll: - sectionId = "delete-all" - selectedPeers = self.optionDeleteAllSelectedPeers - isExpanded = self.isOptionDeleteAllExpanded - - if component.peers.count == 1 { - title = environment.strings.Chat_AdminActionSheet_DeleteAllSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string - } else { - title = environment.strings.Chat_AdminActionSheet_DeleteAllMultiple - } - case .ban: - sectionId = "ban" - selectedPeers = self.optionBanSelectedPeers - isExpanded = self.isOptionBanExpanded - - let banTitle: String - let restrictTitle: String - if component.peers.count == 1 { - banTitle = environment.strings.Chat_AdminActionSheet_BanSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string - restrictTitle = environment.strings.Chat_AdminActionSheet_RestrictSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string - } else { - banTitle = environment.strings.Chat_AdminActionSheet_BanMultiple - restrictTitle = environment.strings.Chat_AdminActionSheet_RestrictMultiple - } - title = self.isConfigurationExpanded ? restrictTitle : banTitle - } - - var accessory: ListActionItemComponent.Accessory? - if component.peers.count > 1 { - accessory = .custom(ListActionItemComponent.CustomAccessory( - component: AnyComponentWithIdentity(id: 0, component: AnyComponent(PlainButtonComponent( - content: AnyComponent(OptionSectionExpandIndicatorComponent( - theme: environment.theme, - count: selectedPeers.isEmpty ? component.peers.count : selectedPeers.count, - isExpanded: isExpanded - )), - effectAlignment: .center, - action: { [weak self] in - guard let self else { - return - } - - switch section { - case .report: - self.isOptionReportExpanded = !self.isOptionReportExpanded - case .deleteAll: - self.isOptionDeleteAllExpanded = !self.isOptionDeleteAllExpanded - case .ban: - self.isOptionBanExpanded = !self.isOptionBanExpanded - } - - self.state?.updated(transition: .spring(duration: 0.35)) - }, - animateScale: false - ))), - insets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 2.0), - isInteractive: true - )) - } - - return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: title, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - ], alignment: .left, spacing: 2.0)), - leftIcon: .check(ListActionItemComponent.LeftIcon.Check( - isSelected: !selectedPeers.isEmpty, - toggle: { [weak self] in + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(AdminUserActionsContentComponent( + context: component.context, + chatPeer: component.chatPeer, + peers: component.peers, + mode: component.mode, + theme: theme, + strings: environmentValue.strings, + presentationData: presentationData, + sheetState: currentState, + disableOptionsSectionAnimation: disableOptionsSectionAnimation, + toggleOptionSelection: { [weak self] section in guard let self, let component = self.component else { return } @@ -773,7 +1244,17 @@ private final class AdminUserActionsSheetComponent: Component { case .report: selectedPeers = self.optionReportSelectedPeers case .deleteAll: - selectedPeers = self.optionDeleteAllSelectedPeers + let allPeerIds = Set(component.peers.map { $0.peer.id }) + if self.optionDeleteAllSelectedPeers.isEmpty && self.optionDeleteAllReactionsSelectedPeers.isEmpty { + self.optionDeleteAllSelectedPeers = allPeerIds + self.optionDeleteAllReactionsSelectedPeers = allPeerIds + } else { + self.optionDeleteAllSelectedPeers.removeAll() + self.optionDeleteAllReactionsSelectedPeers.removeAll() + } + + self.state?.updated(transition: .spring(duration: 0.35)) + return case .ban: selectedPeers = self.optionBanSelectedPeers } @@ -799,75 +1280,24 @@ private final class AdminUserActionsSheetComponent: Component { } self.state?.updated(transition: .spring(duration: 0.35)) - } - )), - icon: .none, - accessory: accessory, - action: { [weak self] _ in - guard let self else { - return - } - - var selectedPeers: Set - switch section { - case .report: - selectedPeers = self.optionReportSelectedPeers - case .deleteAll: - selectedPeers = self.optionDeleteAllSelectedPeers - case .ban: - selectedPeers = self.optionBanSelectedPeers - } - - if selectedPeers.isEmpty { - for peer in component.peers { - selectedPeers.insert(peer.peer.id) + }, + toggleOptionExpansion: { [weak self] section in + guard let self else { + return } - } else { - selectedPeers.removeAll() - } - - switch section { - case .report: - self.optionReportSelectedPeers = selectedPeers - case .deleteAll: - self.optionDeleteAllSelectedPeers = selectedPeers - case .ban: - self.optionBanSelectedPeers = selectedPeers - } - - self.state?.updated(transition: .spring(duration: 0.35)) - }, - highlighting: .disabled - ))) - } - - let expandedPeersItem: (OptionsSection) -> AnyComponentWithIdentity = { section in - let sectionId: AnyHashable - let selectedPeers: Set - switch section { - case .report: - sectionId = "report-peers" - selectedPeers = self.optionReportSelectedPeers - case .deleteAll: - sectionId = "delete-all-peers" - selectedPeers = self.optionDeleteAllSelectedPeers - case .ban: - sectionId = "ban-peers" - selectedPeers = self.optionBanSelectedPeers - } - - var peerItems: [AnyComponentWithIdentity] = [] - for peer in component.peers { - peerItems.append(AnyComponentWithIdentity(id: peer.peer.id, component: AnyComponent(AdminUserActionsPeerComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - baseFontSize: presentationData.listsFontSize.baseDisplaySize, - sideInset: 0.0, - title: EnginePeer(peer.peer).displayTitle(strings: environment.strings, displayOrder: .firstLast), - peer: EnginePeer(peer.peer), - selectionState: .editing(isSelected: selectedPeers.contains(peer.peer.id)), - action: { [weak self] peer in + + switch section { + case .report: + self.isOptionReportExpanded = !self.isOptionReportExpanded + case .deleteAll: + self.isOptionDeleteAllExpanded = !self.isOptionDeleteAllExpanded + case .ban: + self.isOptionBanExpanded = !self.isOptionBanExpanded + } + + self.state?.updated(transition: .spring(duration: 0.35)) + }, + togglePeerSelection: { [weak self] section, peer in guard let self else { return } @@ -877,7 +1307,16 @@ private final class AdminUserActionsSheetComponent: Component { case .report: selectedPeers = self.optionReportSelectedPeers case .deleteAll: - selectedPeers = self.optionDeleteAllSelectedPeers + if self.optionDeleteAllSelectedPeers.contains(peer.id) || self.optionDeleteAllReactionsSelectedPeers.contains(peer.id) { + self.optionDeleteAllSelectedPeers.remove(peer.id) + self.optionDeleteAllReactionsSelectedPeers.remove(peer.id) + } else { + self.optionDeleteAllSelectedPeers.insert(peer.id) + self.optionDeleteAllReactionsSelectedPeers.insert(peer.id) + } + + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + return case .ban: selectedPeers = self.optionBanSelectedPeers } @@ -898,578 +1337,187 @@ private final class AdminUserActionsSheetComponent: Component { } self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } - )))) - } - return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListSubSectionComponent( - theme: environment.theme, - leftInset: 62.0, - items: peerItems - ))) - } - - var titleString: String - switch component.mode { - case .monoforum: - if let peer = component.peers.first { - titleString = environment.strings.Monoforum_DeleteTopic_Title(EnginePeer(peer.peer).compactDisplayTitle).string - } else { - titleString = environment.strings.Common_Delete - } - case let .chat(messageCount, deleteAllMessageCount, _): - titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount)) - if let deleteAllMessageCount { - if self.optionDeleteAllSelectedPeers == Set(component.peers.map(\.peer.id)) { - titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount)) - } - } - case let .liveStream(messageCount, deleteAllMessageCount, _): - titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(messageCount)) - if let deleteAllMessageCount { - if self.optionDeleteAllSelectedPeers == Set(component.peers.map(\.peer.id)) { - titleString = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(deleteAllMessageCount)) - } - } - } - - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) - ) - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((54.0 - titleSize.height) * 0.5)), size: titleSize) - if let titleView = title.view { - if titleView.superview == nil { - self.navigationBarContainer.addSubview(titleView) - } - //transition.setPosition(view: titleView, position: titleFrame.center) - titleView.center = titleFrame.center - titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) - } - - let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0)) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationBackgroundFrame) - self.navigationBackgroundView.update(size: navigationBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) - transition.setFrame(layer: self.navigationBarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: availableSize.width, height: UIScreenPixel))) - - var optionsSectionItems: [AnyComponentWithIdentity] = [] - - for option in availableOptions { - let isOptionExpanded: Bool - switch option { - case .report: - isOptionExpanded = self.isOptionReportExpanded - case .deleteAll: - isOptionExpanded = self.isOptionDeleteAllExpanded - case .ban: - isOptionExpanded = self.isOptionBanExpanded - } - - optionsSectionItems.append(optionsItem(option)) - if isOptionExpanded { - optionsSectionItems.append(expandedPeersItem(option)) - } - } - - var optionsSectionTransition = transition - if self.previousWasConfigurationExpanded != self.isConfigurationExpanded { - self.previousWasConfigurationExpanded = self.isConfigurationExpanded - optionsSectionTransition = optionsSectionTransition.withAnimation(.none) - } - let optionsSectionSize = self.optionsSection.update( - transition: optionsSectionTransition, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - style: .glass, - header: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Chat_AdminActionSheet_RestrictSectionHeader, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), - footer: nil, - items: optionsSectionItems, - isModal: true - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) - ) - - let optionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: optionsSectionSize) - if let optionsSectionView = self.optionsSection.view { - if optionsSectionView.superview == nil { - self.scrollContentView.addSubview(optionsSectionView) - self.optionsSection.parentState = state - } - transition.setFrame(view: optionsSectionView, frame: optionsSectionFrame) - } - contentHeight += optionsSectionSize.height - - let partiallyRestrictTitle: String - let fullyBanTitle: String - if component.peers.count == 1 { - partiallyRestrictTitle = environment.strings.Chat_AdminActionSheet_RestrictFooterSingle - fullyBanTitle = environment.strings.Chat_AdminActionSheet_BanFooterSingle - } else { - partiallyRestrictTitle = environment.strings.Chat_AdminActionSheet_RestrictFooterMultiple - fullyBanTitle = environment.strings.Chat_AdminActionSheet_BanFooterMultiple - } - - let optionsFooterSize = self.optionsFooter.update( - transition: transition, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(OptionsSectionFooterComponent( - theme: environment.theme, - text: self.isConfigurationExpanded ? fullyBanTitle : partiallyRestrictTitle, - fontSize: presentationData.listsFontSize.itemListBaseHeaderFontSize, - isExpanded: self.isConfigurationExpanded - )), - effectAlignment: .left, - contentInsets: UIEdgeInsets(), - action: { [weak self] in - guard let self, let component = self.component else { - return - } - self.isConfigurationExpanded = !self.isConfigurationExpanded - if self.isConfigurationExpanded && self.optionBanSelectedPeers.isEmpty { - for peer in component.peers { - self.optionBanSelectedPeers.insert(peer.peer.id) + }, + toggleDeleteAllOptionPeerSelection: { [weak self] option, peer in + guard let self else { + return } - } - self.state?.updated(transition: .spring(duration: 0.35)) - }, - animateAlpha: true, - animateScale: false, - animateContents: true - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - - var configSectionItems: [AnyComponentWithIdentity] = [] - - enum ConfigItem: Hashable, CaseIterable { - case sendMessages - case sendMedia - case addUsers - case pinMessages - case changeInfo - } - - if case let .channel(channel) = component.chatPeer, channel.isMonoForum { - } else if case .liveStream = component.mode { - } else { - var allConfigItems: [(ConfigItem, Bool)] = [] - if !self.allowedMediaRights.isEmpty || !self.allowedParticipantRights.isEmpty { - for configItem in ConfigItem.allCases { - let isEnabled: Bool - switch configItem { - case .sendMessages: - isEnabled = self.allowedParticipantRights.contains(.sendMessages) - case .sendMedia: - isEnabled = !self.allowedMediaRights.isEmpty - case .addUsers: - isEnabled = self.allowedParticipantRights.contains(.addMembers) - case .pinMessages: - isEnabled = self.allowedParticipantRights.contains(.pinMessages) - case .changeInfo: - isEnabled = self.allowedParticipantRights.contains(.changeInfo) - } - allConfigItems.append((configItem, isEnabled)) - } - } - - loop: for (configItem, isEnabled) in allConfigItems { - let itemTitle: AnyComponent - let itemValue: Bool - switch configItem { - case .sendMessages: - itemTitle = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_BanUser_PermissionSendMessages, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )) - itemValue = self.participantRights.contains(.sendMessages) - case .sendMedia: - if isEnabled { - itemTitle = AnyComponent(HStack([ - AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_BanUser_PermissionSendMedia, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent( - theme: environment.theme, - title: "\(self.mediaRights.count)/\(self.allowedMediaRights.count)", - isExpanded: self.isMediaSectionExpanded - ))) - ], spacing: 7.0)) - } else { - itemTitle = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_BanUser_PermissionSendMedia, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )) - } - - itemValue = !self.mediaRights.isEmpty - case .addUsers: - itemTitle = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_BanUser_PermissionAddMembers, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )) - itemValue = self.participantRights.contains(.addMembers) - case .pinMessages: - itemTitle = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_EditAdmin_PermissionPinMessages, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )) - itemValue = self.participantRights.contains(.pinMessages) - case .changeInfo: - itemTitle = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_BanUser_PermissionChangeGroupInfo, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - )) - itemValue = self.participantRights.contains(.changeInfo) - } - - configSectionItems.append(AnyComponentWithIdentity(id: configItem, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: itemTitle, - accessory: .toggle(ListActionItemComponent.Toggle( - style: isEnabled ? .icons : .lock, - isOn: itemValue, - isInteractive: isEnabled, - action: isEnabled ? { [weak self] _ in - guard let self else { - return + + switch option { + case .messages: + if self.optionDeleteAllSelectedPeers.contains(peer.id) { + self.optionDeleteAllSelectedPeers.remove(peer.id) + } else { + self.optionDeleteAllSelectedPeers.insert(peer.id) } - - switch configItem { - case .sendMessages: - if self.participantRights.contains(.sendMessages) { - self.participantRights.remove(.sendMessages) - } else { - self.participantRights.insert(.sendMessages) - } - case .sendMedia: - if self.mediaRights.isEmpty { - self.mediaRights = self.allowedMediaRights - } else { - self.mediaRights = [] - } - case .addUsers: - if self.participantRights.contains(.addMembers) { - self.participantRights.remove(.addMembers) - } else { - self.participantRights.insert(.addMembers) - } - case .pinMessages: - if self.participantRights.contains(.pinMessages) { - self.participantRights.remove(.pinMessages) - } else { - self.participantRights.insert(.pinMessages) - } - case .changeInfo: - if self.participantRights.contains(.changeInfo) { - self.participantRights.remove(.changeInfo) - } else { - self.participantRights.insert(.changeInfo) - } + case .reactions: + if self.optionDeleteAllReactionsSelectedPeers.contains(peer.id) { + self.optionDeleteAllReactionsSelectedPeers.remove(peer.id) + } else { + self.optionDeleteAllReactionsSelectedPeers.insert(peer.id) } - self.state?.updated(transition: .spring(duration: 0.35)) - } : nil - )), - action: ((isEnabled && configItem == .sendMedia) || !isEnabled) ? { [weak self] _ in + } + + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + }, + toggleConfiguration: { [weak self] in guard let self, let component = self.component else { return } - if !isEnabled { - self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: environment.strings.GroupPermission_PermissionDisabledByDefault, actions: [ - TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: { - }) - ]), in: .window(.root)) + self.isConfigurationExpanded = !self.isConfigurationExpanded + if self.isConfigurationExpanded && self.optionBanSelectedPeers.isEmpty { + for peer in component.peers { + self.optionBanSelectedPeers.insert(peer.peer.id) + } + } + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleConfigItem: { [weak self] configItem in + guard let self else { + return + } + + switch configItem { + case .sendMessages: + if self.participantRights.contains(.sendMessages) { + self.participantRights.remove(.sendMessages) + } else { + self.participantRights.insert(.sendMessages) + } + case .sendMedia: + if self.mediaRights.isEmpty { + self.mediaRights = self.allowedMediaRights + } else { + self.mediaRights = [] + } + case .addUsers: + if self.participantRights.contains(.addMembers) { + self.participantRights.remove(.addMembers) + } else { + self.participantRights.insert(.addMembers) + } + case .pinMessages: + if self.participantRights.contains(.pinMessages) { + self.participantRights.remove(.pinMessages) + } else { + self.participantRights.insert(.pinMessages) + } + case .changeInfo: + if self.participantRights.contains(.changeInfo) { + self.participantRights.remove(.changeInfo) + } else { + self.participantRights.insert(.changeInfo) + } + } + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleMediaSectionExpansion: { [weak self] in + guard let self else { + return + } + self.isMediaSectionExpanded = !self.isMediaSectionExpanded + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleMediaRight: { [weak self] mediaRight in + guard let self else { + return + } + + if self.mediaRights.contains(mediaRight) { + self.mediaRights.remove(mediaRight) } else { - self.isMediaSectionExpanded = !self.isMediaSectionExpanded - self.state?.updated(transition: .spring(duration: 0.35)) - } - } : nil, - highlighting: .disabled - )))) - - if isEnabled, case .sendMedia = configItem, self.isMediaSectionExpanded { - var mediaItems: [AnyComponentWithIdentity] = [] - mediaRightsLoop: for possibleMediaItem in allMediaRightItems { - if !self.allowedMediaRights.contains(possibleMediaItem) { - continue + self.mediaRights.insert(mediaRight) } - let mediaItemTitle: String - switch possibleMediaItem { - case .photos: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPhoto - case .videos: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideo - case .stickersAndGifs: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendStickersAndGifs - case .music: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendMusic - case .files: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendFile - case .voiceMessages: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVoiceMessage - case .videoMessages: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideoMessage - case .links: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionEmbedLinks - case .polls: - mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPolls - default: - continue mediaRightsLoop - } - - mediaItems.append(AnyComponentWithIdentity(id: possibleMediaItem, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: mediaItemTitle, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - ], alignment: .left, spacing: 2.0)), - leftIcon: .check(ListActionItemComponent.LeftIcon.Check( - isSelected: self.mediaRights.contains(possibleMediaItem), - toggle: { [weak self] in - guard let self else { - return - } - - if self.mediaRights.contains(possibleMediaItem) { - self.mediaRights.remove(possibleMediaItem) - } else { - self.mediaRights.insert(possibleMediaItem) - } - - self.state?.updated(transition: .spring(duration: 0.35)) - } - )), - icon: .none, - accessory: .none, - action: { [weak self] _ in - guard let self else { - return - } - - if self.mediaRights.contains(possibleMediaItem) { - self.mediaRights.remove(possibleMediaItem) - } else { - self.mediaRights.insert(possibleMediaItem) - } - - self.state?.updated(transition: .spring(duration: 0.35)) - }, - highlighting: .disabled - )))) + self.state?.updated(transition: .spring(duration: 0.35)) } - configSectionItems.append(AnyComponentWithIdentity(id: "media-sub", component: AnyComponent(ListSubSectionComponent( - theme: environment.theme, - leftInset: 0.0, - items: mediaItems - )))) - } - } - } - - let configSectionSize = self.configSection.update( - transition: transition, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - header: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: component.peers.count == 1 ? environment.strings.Chat_AdminActionSheet_PermissionsSectionHeader : environment.strings.Chat_AdminActionSheet_PermissionsSectionHeaderMultiple, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 )), - footer: nil, - items: configSectionItems - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) - ) - let configSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 30.0), size: configSectionSize) - if let configSectionView = self.configSection.view { - if configSectionView.superview == nil { - configSectionView.clipsToBounds = true - configSectionView.layer.cornerRadius = 11.0 - self.scrollContentView.addSubview(configSectionView) - self.configSection.parentState = state - } - let effectiveConfigSectionFrame: CGRect - if self.isConfigurationExpanded { - effectiveConfigSectionFrame = configSectionFrame - } else { - effectiveConfigSectionFrame = CGRect(origin: CGPoint(x: configSectionFrame.minX, y: configSectionFrame.minY - 30.0), size: CGSize(width: configSectionFrame.width, height: 0.0)) - } - transition.setFrame(view: configSectionView, frame: effectiveConfigSectionFrame) - transition.setAlpha(view: configSectionView, alpha: self.isConfigurationExpanded ? 1.0 : 0.0) - } - - if availableOptions.contains(.ban) && !configSectionItems.isEmpty { - let optionsFooterFrame: CGRect - if self.isConfigurationExpanded { - contentHeight += 30.0 - contentHeight += configSectionSize.height - contentHeight += 7.0 - optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) - contentHeight += optionsFooterSize.height - } else { - contentHeight += 7.0 - optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) - contentHeight += optionsFooterSize.height - } - if let optionsFooterView = self.optionsFooter.view { - if optionsFooterView.superview == nil { - self.scrollContentView.addSubview(optionsFooterView) - } - transition.setFrame(view: optionsFooterView, frame: optionsFooterFrame) - transition.setAlpha(view: optionsFooterView, alpha: 1.0) - } - } else { - if let optionsFooterView = self.optionsFooter.view { - if optionsFooterView.superview == nil { - self.scrollContentView.addSubview(optionsFooterView) - } - let optionsFooterFrame = CGRect(origin: CGPoint(x: sideInset + 16.0, y: contentHeight), size: optionsFooterSize) - transition.setFrame(view: optionsFooterView, frame: optionsFooterFrame) - transition.setAlpha(view: optionsFooterView, alpha: 0.0) - } - } - - contentHeight += 30.0 - - let actionButtonSize = self.actionButton.update( - transition: transition, - component: AnyComponent(ButtonComponent( - background: ButtonComponent.Background( - style: .glass, - color: environment.theme.list.itemCheckColors.fillColor, - foreground: environment.theme.list.itemCheckColors.foregroundColor, - pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 54.0 * 0.5 + titleItem: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: adminUserActionsTitle( + strings: environmentValue.strings, + mode: component.mode, + peers: component.peers, + selectedDeleteAllPeers: self.optionDeleteAllSelectedPeers + ), + font: Font.semibold(17.0), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) ), - content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent(ButtonTextContentComponent( - text: environment.strings.Chat_AdminActionSheet_ActionButton, - badge: 0, - textColor: environment.theme.list.itemCheckColors.foregroundColor, - badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, - badgeForeground: environment.theme.list.itemCheckColors.fillColor - )) - ), - isEnabled: true, - displaysProgress: false, - action: { [weak self] in - guard let self, let component = self.component else { - return + bottomItem: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 54.0 * 0.5 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(ButtonTextContentComponent( + text: environmentValue.strings.Chat_AdminActionSheet_ActionButton, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )) + ), + isEnabled: true, + displaysProgress: false, + action: { + performMainAction() } - self.environment?.controller()?.dismiss() - - switch component.mode { - case let .monoforum(completion): - completion(self.calculateMonoforumResult()) - case let .chat(_, _, completion): - completion(self.calculateChatResult()) - case let .liveStream(_, _, completion): - completion(self.calculateLiveStreamResult()) - } - } + )), + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + animateOut: self.animateOut )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 54.0) + environment: { + environmentValue + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environmentValue.statusBarHeight, + safeInsets: environmentValue.safeInsets, + inputHeight: 0.0, + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: environmentValue.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated) + } + ) + }, + forceUpdate: true, + containerSize: availableSize ) - let bottomPanelHeight = 8.0 + environment.safeInsets.bottom + actionButtonSize.height - let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) - if let actionButtonView = actionButton.view { - if actionButtonView.superview == nil { - self.addSubview(actionButtonView) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) } - transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) } - contentHeight += bottomPanelHeight - - clippingY = actionButtonFrame.minY - 24.0 - - let topInset: CGFloat = max(0.0, availableSize.height - containerInset - contentHeight) - - let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) - - self.scrollContentClippingView.layer.cornerRadius = 10.0 - - self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset) - - transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) - - transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) - - let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset)) - transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) - transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) - - self.ignoreScrolling = true - let previousBounds = self.scrollView.bounds - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) - let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) - if contentSize != self.scrollView.contentSize { - self.scrollView.contentSize = contentSize - } - if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) - } else { - if !previousBounds.isEmpty, !transition.animation.isImmediate { - let bounds = self.scrollView.bounds - if bounds.maxY != previousBounds.maxY { - let offsetY = previousBounds.maxY - bounds.maxY - transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) - } - } - } - self.ignoreScrolling = false - self.updateScrolling(transition: transition) - return availableSize } } @@ -1488,17 +1536,20 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer { case chat(messageCount: Int, deleteAllMessageCount: Int?, completion: (ChatResult) -> Void) case liveStream(messageCount: Int, deleteAllMessageCount: Int?, completion: (LiveStreamResult) -> Void) case monoforum(completion: (MonoforumResult) -> Void) + case chatReaction(completion: (ChatResult) -> Void) } public final class ChatResult { public let reportSpamPeers: [EnginePeer.Id] public let deleteAllFromPeers: [EnginePeer.Id] + public let deleteAllReactionsFromPeers: [EnginePeer.Id] public let banPeers: [EnginePeer.Id] public let updateBannedRights: [EnginePeer.Id: TelegramChatBannedRights] - init(reportSpamPeers: [EnginePeer.Id], deleteAllFromPeers: [EnginePeer.Id], banPeers: [EnginePeer.Id], updateBannedRights: [EnginePeer.Id: TelegramChatBannedRights]) { + init(reportSpamPeers: [EnginePeer.Id], deleteAllFromPeers: [EnginePeer.Id], deleteAllReactionsFromPeers: [EnginePeer.Id], banPeers: [EnginePeer.Id], updateBannedRights: [EnginePeer.Id: TelegramChatBannedRights]) { self.reportSpamPeers = reportSpamPeers self.deleteAllFromPeers = deleteAllFromPeers + self.deleteAllReactionsFromPeers = deleteAllReactionsFromPeers self.banPeers = banPeers self.updateBannedRights = updateBannedRights } @@ -1527,12 +1578,17 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer { } private let context: AccountContext - private var isDismissed: Bool = false + private var dismissCompletion: (() -> Void)? public init(context: AccountContext, chatPeer: EnginePeer, peers: [RenderedChannelParticipant], mode: Mode, customTheme: PresentationTheme? = nil) { self.context = context - super.init(context: context, component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), navigationBarAppearance: .none, theme: customTheme.flatMap({ .custom($0) }) ?? .default) + super.init( + context: context, + component: AdminUserActionsSheetComponent(context: context, chatPeer: chatPeer, peers: peers, mode: mode), + navigationBarAppearance: .none, + theme: customTheme.flatMap({ .custom($0) }) ?? .default + ) self.statusBar.statusBarStyle = .Ignore self.navigationPresentation = .flatModal @@ -1548,24 +1604,30 @@ public class AdminUserActionsSheet: ViewControllerComponentContainer { override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - self.view.disablesInteractiveModalDismiss = true - - if let componentView = self.node.hostView.componentView as? AdminUserActionsSheetComponent.View { - componentView.animateIn() + } + + fileprivate func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } } override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true + self.dismissCompletion = completion - if let componentView = self.node.hostView.componentView as? AdminUserActionsSheetComponent.View { - componentView.animateOut(completion: { [weak self] in - completion?() - self?.dismiss(animated: false) - }) + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } else { + self.completePendingDismiss() self.dismiss(animated: false) } } diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift index 5edc3135aa..a84335e979 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/RecentActionsSettingsSheet.swift @@ -1,23 +1,20 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import ComponentFlow import SwiftSignalKit import ViewControllerComponent import ComponentDisplayAdapters +import ResizableSheetComponent import TelegramPresentationData import AccountContext import TelegramCore import MultilineTextComponent import ButtonComponent import PresentationDataUtils -import Markdown -import UndoUI import TelegramStringFormatting import ListSectionComponent import ListActionItemComponent -import PlainButtonComponent import GlassBarButtonComponent import BundleIconComponent @@ -163,15 +160,351 @@ private enum ActionType: Hashable { } } -private final class RecentActionsSettingsSheetComponent: Component { +private struct RecentActionsSettingsSheetState: Equatable { + var expandedSections: Set + var selectedMembersActions: Set + var selectedSettingsActions: Set + var selectedMessagesActions: Set + var selectedAdmins: Set +} + +private final class RecentActionsSettingsContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment - + + let context: AccountContext + let peer: EnginePeer + let adminPeers: [EnginePeer] + let theme: PresentationTheme + let sheetState: RecentActionsSettingsSheetState + let toggleActionTypeSectionSelection: (ActionTypeSection) -> Void + let toggleActionTypeSectionExpansion: (ActionTypeSection) -> Void + let toggleActionType: (ActionType) -> Void + let toggleAdmin: (EnginePeer) -> Void + let toggleAllAdmins: () -> Void + + init( + context: AccountContext, + peer: EnginePeer, + adminPeers: [EnginePeer], + theme: PresentationTheme, + sheetState: RecentActionsSettingsSheetState, + toggleActionTypeSectionSelection: @escaping (ActionTypeSection) -> Void, + toggleActionTypeSectionExpansion: @escaping (ActionTypeSection) -> Void, + toggleActionType: @escaping (ActionType) -> Void, + toggleAdmin: @escaping (EnginePeer) -> Void, + toggleAllAdmins: @escaping () -> Void + ) { + self.context = context + self.peer = peer + self.adminPeers = adminPeers + self.theme = theme + self.sheetState = sheetState + self.toggleActionTypeSectionSelection = toggleActionTypeSectionSelection + self.toggleActionTypeSectionExpansion = toggleActionTypeSectionExpansion + self.toggleActionType = toggleActionType + self.toggleAdmin = toggleAdmin + self.toggleAllAdmins = toggleAllAdmins + } + + static func ==(lhs: RecentActionsSettingsContentComponent, rhs: RecentActionsSettingsContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.adminPeers != rhs.adminPeers { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.sheetState != rhs.sheetState { + return false + } + return true + } + + final class View: UIView { + private let optionsSection = ComponentView() + private let adminsSection = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: RecentActionsSettingsContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + let theme = component.theme + let sheetState = component.sheetState + let sideInset: CGFloat = 16.0 + + var isGroup = true + if case let .channel(channel) = component.peer, case .broadcast = channel.info { + isGroup = false + } + + var contentHeight: CGFloat = 76.0 + 15.0 + + let actionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity = { actionTypeSection in + let totalCount: Int + let selectedCount: Int + let title: String + let isExpanded = sheetState.expandedSections.contains(actionTypeSection) + + switch actionTypeSection { + case .members: + totalCount = MembersActionType.allCases.count + selectedCount = sheetState.selectedMembersActions.count + title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_MembersGroup : environment.strings.Channel_AdminLogFilter_Section_MembersChannel + case .settings: + totalCount = SettingsActionType.allCases.count + selectedCount = sheetState.selectedSettingsActions.count + title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_SettingsGroup : environment.strings.Channel_AdminLogFilter_Section_SettingsChannel + case .messages: + totalCount = MessagesActionType.allCases.count + selectedCount = sheetState.selectedMessagesActions.count + title = environment.strings.Channel_AdminLogFilter_Section_Messages + } + + let itemTitle: AnyComponent = AnyComponent(HStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: title, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent( + theme: theme, + title: "\(selectedCount)/\(totalCount)", + isExpanded: isExpanded + ))) + ], spacing: 7.0)) + + return AnyComponentWithIdentity(id: actionTypeSection, component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: itemTitle, + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: selectedCount == totalCount, + toggle: { + component.toggleActionTypeSectionSelection(actionTypeSection) + } + )), + icon: .none, + accessory: nil, + action: { _ in + component.toggleActionTypeSectionExpansion(actionTypeSection) + }, + highlighting: .disabled + ))) + } + + let expandedActionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity = { actionTypeSection in + let sectionId: AnyHashable + let selectedActionTypes: Set + let actionTypes: [ActionType] + switch actionTypeSection { + case .members: + sectionId = "members-sub" + actionTypes = MembersActionType.allCases.map(ActionType.members) + selectedActionTypes = Set(sheetState.selectedMembersActions.map(ActionType.members)) + case .settings: + sectionId = "settings-sub" + actionTypes = SettingsActionType.allCases.map(ActionType.settings) + selectedActionTypes = Set(sheetState.selectedSettingsActions.map(ActionType.settings)) + case .messages: + sectionId = "messages-sub" + actionTypes = MessagesActionType.allCases.map(ActionType.messages) + selectedActionTypes = Set(sheetState.selectedMessagesActions.map(ActionType.messages)) + } + + var subItems: [AnyComponentWithIdentity] = [] + for actionType in actionTypes { + let actionItemTitle: String = actionType.title(isGroup: isGroup, strings: environment.strings) + + subItems.append(AnyComponentWithIdentity(id: actionType, component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: actionItemTitle, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: selectedActionTypes.contains(actionType), + toggle: { + component.toggleActionType(actionType) + } + )), + icon: .none, + accessory: .none, + action: { _ in + component.toggleActionType(actionType) + }, + highlighting: .disabled + )))) + } + + return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListSubSectionComponent( + theme: theme, + leftInset: 62.0, + items: subItems + ))) + } + + var optionsSectionItems: [AnyComponentWithIdentity] = [] + for actionTypeSection in ActionTypeSection.allCases { + optionsSectionItems.append(actionTypeSectionItem(actionTypeSection)) + if sheetState.expandedSections.contains(actionTypeSection) { + optionsSectionItems.append(expandedActionTypeSectionItem(actionTypeSection)) + } + } + + self.optionsSection.parentState = state + let optionsSectionSize = self.optionsSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Channel_AdminLogFilter_FilterActionsTypeTitle, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: optionsSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) + ) + let optionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: optionsSectionSize) + if let optionsSectionView = self.optionsSection.view { + if optionsSectionView.superview == nil { + self.addSubview(optionsSectionView) + } + transition.setFrame(view: optionsSectionView, frame: optionsSectionFrame) + } + contentHeight += optionsSectionSize.height + contentHeight += 24.0 + + var peerItems: [AnyComponentWithIdentity] = [] + for peer in component.adminPeers { + peerItems.append(AnyComponentWithIdentity(id: peer.id, component: AnyComponent(AdminUserActionsPeerComponent( + context: component.context, + theme: theme, + strings: environment.strings, + baseFontSize: presentationData.listsFontSize.baseDisplaySize, + sideInset: 0.0, + title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), + peer: peer, + selectionState: .editing(isSelected: sheetState.selectedAdmins.contains(peer.id)), + action: { peer in + component.toggleAdmin(peer) + } + )))) + } + + var adminsSectionItems: [AnyComponentWithIdentity] = [] + adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Channel_AdminLogFilter_ShowAllAdminsActions, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + leftIcon: .check(ListActionItemComponent.LeftIcon.Check( + isSelected: sheetState.selectedAdmins.count == component.adminPeers.count, + toggle: { + component.toggleAllAdmins() + } + )), + icon: .none, + accessory: .none, + action: { _ in + component.toggleAllAdmins() + }, + highlighting: .disabled + )))) + adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListSubSectionComponent( + theme: theme, + leftInset: 62.0, + items: peerItems + )))) + + self.adminsSection.parentState = state + let adminsSectionSize = self.adminsSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.Channel_AdminLogFilter_FilterActionsAdminsTitle, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: adminsSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) + ) + let adminsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: adminsSectionSize) + if let adminsSectionView = self.adminsSection.view { + if adminsSectionView.superview == nil { + self.addSubview(adminsSectionView) + } + transition.setFrame(view: adminsSectionView, frame: adminsSectionFrame) + } + contentHeight += adminsSectionSize.height + contentHeight += 106.0 + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class RecentActionsSettingsResizableSheetComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + let context: AccountContext let peer: EnginePeer let adminPeers: [EnginePeer] let initialValue: RecentActionsSettingsSheet.Value let completion: (RecentActionsSettingsSheet.Value) -> Void - + init( context: AccountContext, peer: EnginePeer, @@ -185,8 +518,8 @@ private final class RecentActionsSettingsSheetComponent: Component { self.initialValue = initialValue self.completion = completion } - - static func ==(lhs: RecentActionsSettingsSheetComponent, rhs: RecentActionsSettingsSheetComponent) -> Bool { + + static func ==(lhs: RecentActionsSettingsResizableSheetComponent, rhs: RecentActionsSettingsResizableSheetComponent) -> Bool { if lhs.context !== rhs.context { return false } @@ -198,158 +531,29 @@ private final class RecentActionsSettingsSheetComponent: Component { } return true } - - private struct ItemLayout: Equatable { - var containerSize: CGSize - var containerInset: CGFloat - var bottomInset: CGFloat - var topInset: CGFloat - - init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { - self.containerSize = containerSize - self.containerInset = containerInset - self.bottomInset = bottomInset - self.topInset = topInset - } - } - - private final class ScrollView: UIScrollView { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - } - - final class View: UIView, UIScrollViewDelegate { - private let dimView: UIView - private let backgroundLayer: SimpleLayer - private let navigationBarContainer: SparseContainerView - private let navigationBackgroundView: BlurredBackgroundView - private let navigationBarSeparator: SimpleLayer - private let scrollView: ScrollView - private let scrollContentClippingView: SparseContainerView - private let scrollContentView: UIView - - private let leftButton = ComponentView() - - private let title = ComponentView() - private let actionButton = ComponentView() - - private let optionsSection = ComponentView() - private let adminsSection = ComponentView() - - private let bottomOverscrollLimit: CGFloat - - private var ignoreScrolling: Bool = false - - private var component: RecentActionsSettingsSheetComponent? + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var component: RecentActionsSettingsResizableSheetComponent? private weak var state: EmptyComponentState? - private var environment: ViewControllerComponentContainer.Environment? - private var isUpdating: Bool = false - - private var itemLayout: ItemLayout? - - private var topOffsetDistance: CGFloat? - + private var isDismissing: Bool = false + private var expandedSections = Set() private var selectedMembersActions = Set() private var selectedSettingsActions = Set() private var selectedMessagesActions = Set() private var selectedAdmins = Set() - + override init(frame: CGRect) { - self.bottomOverscrollLimit = 200.0 - - self.dimView = UIView() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - self.backgroundLayer.cornerRadius = 10.0 - - self.navigationBarContainer = SparseContainerView() - - self.navigationBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) - self.navigationBarSeparator = SimpleLayer() - - self.scrollView = ScrollView() - - self.scrollContentClippingView = SparseContainerView() - self.scrollContentClippingView.clipsToBounds = true - - self.scrollContentView = UIView() - super.init(frame: frame) - - self.addSubview(self.dimView) - self.layer.addSublayer(self.backgroundLayer) - - self.scrollView.delaysContentTouches = true - self.scrollView.canCancelContentTouches = true - self.scrollView.clipsToBounds = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.alwaysBounceVertical = true - self.scrollView.scrollsToTop = false - self.scrollView.delegate = self - self.scrollView.clipsToBounds = true - - self.addSubview(self.scrollContentClippingView) - self.scrollContentClippingView.addSubview(self.scrollView) - - self.scrollView.addSubview(self.scrollContentView) - - self.addSubview(self.navigationBarContainer) - - self.navigationBarContainer.addSubview(self.navigationBackgroundView) - self.navigationBarContainer.layer.addSublayer(self.navigationBarSeparator) - - self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateScrolling(transition: .immediate) - } - } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - if !self.backgroundLayer.frame.contains(point) { - return self.dimView - } - - if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { - return result - } - - let result = super.hitTest(point, with: event) - return result - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - guard let environment = self.environment, let controller = environment.controller() else { - return - } - controller.dismiss() - } - } - + private func calculateResult() -> RecentActionsSettingsSheet.Value { var events: AdminLogEventsFlags = [] var admins: [EnginePeer.Id] = [] @@ -370,598 +574,249 @@ private final class RecentActionsSettingsSheetComponent: Component { admins: admins ) } - - private func updateScrolling(isFirstTime: Bool = false, transition: ComponentTransition) { - guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { - return - } - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - - let navigationAlpha: CGFloat = 1.0 - max(0.0, min(1.0, (topOffset + 20.0) / 20.0)) - transition.setAlpha(view: self.navigationBackgroundView, alpha: navigationAlpha) - transition.setAlpha(layer: self.navigationBarSeparator, alpha: navigationAlpha) - - topOffset = max(0.0, topOffset) - transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) - - transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - - let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25)) - self.topOffsetDistance = topOffsetDistance - var topOffsetFraction = topOffset / topOffsetDistance - topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - - let modalStyleOverlayTransition: ContainedViewLayoutTransition - if isFirstTime { - modalStyleOverlayTransition = .animated(duration: 0.4, curve: .spring) - } else { - modalStyleOverlayTransition = transition.containedViewLayoutTransition - } - - let transitionFactor: CGFloat = 1.0 - topOffsetFraction - if self.isUpdating { - DispatchQueue.main.async { [weak controller] in - guard let controller else { - return - } - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalStyleOverlayTransition) - } - } else { - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalStyleOverlayTransition) - } - } - - func animateIn() { - self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - } - - func animateOut(completion: @escaping () -> Void) { - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - } - - if let environment = self.environment, let controller = environment.controller() { - controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut)) - } - } - - func update(component: RecentActionsSettingsSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.isUpdating = true - defer { - self.isUpdating = false - } - - let environment = environment[ViewControllerComponentContainer.Environment.self].value - let themeUpdated = self.environment?.theme !== environment.theme - - let resetScrolling = self.scrollView.bounds.width != availableSize.width - - let sideInset: CGFloat = 16.0 + environment.safeInsets.left - - var isFirstTime = false + + func update(component: RecentActionsSettingsResizableSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environmentValue.controller + let theme = environmentValue.theme.withModalBlocksBackground() + if self.component == nil { - isFirstTime = true self.selectedMembersActions = Set(MembersActionType.actionTypesFromFlags(component.initialValue.events)) self.selectedSettingsActions = Set(SettingsActionType.actionTypesFromFlags(component.initialValue.events)) self.selectedMessagesActions = Set(MessagesActionType.actionTypesFromFlags(component.initialValue.events)) self.selectedAdmins = component.initialValue.admins.flatMap { Set($0) } ?? Set(component.adminPeers.map(\.id)) } - - var isGroup = true - if case let .channel(channel) = component.peer, case .broadcast = channel.info { - isGroup = false - } - + self.component = component self.state = state - self.environment = environment - - if themeUpdated { - self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor - - self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - } - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - - transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) - - var contentHeight: CGFloat = 0.0 - contentHeight += 54.0 - contentHeight += 16.0 - - let leftButtonSize = self.leftButton.update( - transition: transition, - component: AnyComponent(GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: environment.theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "close", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Close", - tintColor: environment.theme.chat.inputPanel.panelControlColor - ) - )), - action: { [weak self] _ in - guard let self, let controller = self.environment?.controller() else { - return - } - controller.dismiss() - } - )), - environment: {}, - containerSize: CGSize(width: 44.0, height: 44.0) - ) - let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0 + environment.safeInsets.left, y: 16.0), size: leftButtonSize) - if let leftButtonView = self.leftButton.view { - if leftButtonView.superview == nil { - self.navigationBarContainer.addSubview(leftButtonView) - } - transition.setFrame(view: leftButtonView, frame: leftButtonFrame) - } - - let containerInset: CGFloat = environment.statusBarHeight + 10.0 - - let clippingY: CGFloat - - let actionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity = { actionTypeSection in - let sectionId: AnyHashable - let totalCount: Int - let selectedCount: Int - let isExpanded: Bool - let title: String - - sectionId = actionTypeSection - isExpanded = self.expandedSections.contains(actionTypeSection) - - switch actionTypeSection { - case .members: - totalCount = MembersActionType.allCases.count - selectedCount = self.selectedMembersActions.count - title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_MembersGroup : environment.strings.Channel_AdminLogFilter_Section_MembersChannel - case .settings: - totalCount = SettingsActionType.allCases.count - selectedCount = self.selectedSettingsActions.count - title = isGroup ? environment.strings.Channel_AdminLogFilter_Section_SettingsGroup : environment.strings.Channel_AdminLogFilter_Section_SettingsChannel - case .messages: - totalCount = MessagesActionType.allCases.count - selectedCount = self.selectedMessagesActions.count - title = environment.strings.Channel_AdminLogFilter_Section_Messages - } - - let itemTitle: AnyComponent = AnyComponent(HStack([ - AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: title, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - AnyComponentWithIdentity(id: 1, component: AnyComponent(MediaSectionExpandIndicatorComponent( - theme: environment.theme, - title: "\(selectedCount)/\(totalCount)", - isExpanded: isExpanded - ))) - ], spacing: 7.0)) - - let toggleAction: () -> Void = { [weak self] in - guard let self else { - return - } - - switch actionTypeSection { - case .members: - if self.selectedMembersActions.isEmpty { - self.selectedMembersActions = Set(MembersActionType.allCases) - } else { - self.selectedMembersActions.removeAll() - } - case .settings: - if self.selectedSettingsActions.isEmpty { - self.selectedSettingsActions = Set(SettingsActionType.allCases) - } else { - self.selectedSettingsActions.removeAll() - } - case .messages: - if self.selectedMessagesActions.isEmpty { - self.selectedMessagesActions = Set(MessagesActionType.allCases) - } else { - self.selectedMessagesActions.removeAll() - } - } - - self.state?.updated(transition: .spring(duration: 0.35)) - } - - return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - style: .glass, - title: itemTitle, - leftIcon: .check(ListActionItemComponent.LeftIcon.Check( - isSelected: selectedCount == totalCount, - toggle: { - toggleAction() - } - )), - icon: .none, - accessory: nil, - action: { [weak self] _ in - guard let self else { - return - } - if self.expandedSections.contains(actionTypeSection) { - self.expandedSections.remove(actionTypeSection) - } else { - self.expandedSections.insert(actionTypeSection) - } - - self.state?.updated(transition: .spring(duration: 0.35)) - }, - highlighting: .disabled - ))) - } - - let expandedActionTypeSectionItem: (ActionTypeSection) -> AnyComponentWithIdentity = { actionTypeSection in - let sectionId: AnyHashable - let selectedActionTypes: Set - let actionTypes: [ActionType] - switch actionTypeSection { - case .members: - sectionId = "members-sub" - actionTypes = MembersActionType.allCases.map(ActionType.members) - selectedActionTypes = Set(self.selectedMembersActions.map(ActionType.members)) - case .settings: - sectionId = "settings-sub" - actionTypes = SettingsActionType.allCases.map(ActionType.settings) - selectedActionTypes = Set(self.selectedSettingsActions.map(ActionType.settings)) - case .messages: - sectionId = "messages-sub" - actionTypes = MessagesActionType.allCases.map(ActionType.messages) - selectedActionTypes = Set(self.selectedMessagesActions.map(ActionType.messages)) - } - - var subItems: [AnyComponentWithIdentity] = [] - for actionType in actionTypes { - let actionItemTitle: String = actionType.title(isGroup: isGroup, strings: environment.strings) - - let subItemToggleAction: () -> Void = { [weak self] in - guard let self else { - return - } - - switch actionType { - case let .members(value): - if self.selectedMembersActions.contains(value) { - self.selectedMembersActions.remove(value) - } else { - self.selectedMembersActions.insert(value) - } - case let .settings(value): - if self.selectedSettingsActions.contains(value) { - self.selectedSettingsActions.remove(value) - } else { - self.selectedSettingsActions.insert(value) - } - case let .messages(value): - if self.selectedMessagesActions.contains(value) { - self.selectedMessagesActions.remove(value) - } else { - self.selectedMessagesActions.insert(value) - } - } - - self.state?.updated(transition: .spring(duration: 0.35)) - } - - subItems.append(AnyComponentWithIdentity(id: actionType, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - style: .glass, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: actionItemTitle, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - ], alignment: .left, spacing: 2.0)), - leftIcon: .check(ListActionItemComponent.LeftIcon.Check( - isSelected: selectedActionTypes.contains(actionType), - toggle: { - subItemToggleAction() - } - )), - icon: .none, - accessory: .none, - action: { _ in - subItemToggleAction() - }, - highlighting: .disabled - )))) - } - - return AnyComponentWithIdentity(id: sectionId, component: AnyComponent(ListSubSectionComponent( - theme: environment.theme, - leftInset: 62.0, - items: subItems - ))) - } - - let titleString: String = environment.strings.Channel_AdminLogFilter_RecentActionsTitle - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) - ) - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((72.0 - titleSize.height) * 0.5)), size: titleSize) - if let titleView = title.view { - if titleView.superview == nil { - self.navigationBarContainer.addSubview(titleView) - } - transition.setFrame(view: titleView, frame: titleFrame) - } - - let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0)) - transition.setFrame(view: self.navigationBackgroundView, frame: navigationBackgroundFrame) - self.navigationBackgroundView.update(size: navigationBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) - transition.setFrame(layer: self.navigationBarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: availableSize.width, height: UIScreenPixel))) - - var optionsSectionItems: [AnyComponentWithIdentity] = [] - for actionTypeSection in ActionTypeSection.allCases { - optionsSectionItems.append(actionTypeSectionItem(actionTypeSection)) - if self.expandedSections.contains(actionTypeSection) { - optionsSectionItems.append(expandedActionTypeSectionItem(actionTypeSection)) - } - } - - let optionsSectionTransition = transition - let optionsSectionSize = self.optionsSection.update( - transition: optionsSectionTransition, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - style: .glass, - header: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_AdminLogFilter_FilterActionsTypeTitle, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 - )), - footer: nil, - items: optionsSectionItems - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) - ) - let optionsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: optionsSectionSize) - if let optionsSectionView = self.optionsSection.view { - if optionsSectionView.superview == nil { - self.scrollContentView.addSubview(optionsSectionView) - self.optionsSection.parentState = state - } - transition.setFrame(view: optionsSectionView, frame: optionsSectionFrame) - } - contentHeight += optionsSectionSize.height - contentHeight += 24.0 - - var peerItems: [AnyComponentWithIdentity] = [] - for peer in component.adminPeers { - peerItems.append(AnyComponentWithIdentity(id: peer.id, component: AnyComponent(AdminUserActionsPeerComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - baseFontSize: presentationData.listsFontSize.baseDisplaySize, - sideInset: 0.0, - title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), - peer: peer, - selectionState: .editing(isSelected: self.selectedAdmins.contains(peer.id)), - action: { [weak self] peer in - guard let self else { - return - } - - if self.selectedAdmins.contains(peer.id) { - self.selectedAdmins.remove(peer.id) - } else { - self.selectedAdmins.insert(peer.id) - } - - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.35, curve: .easeInOut))) - } - )))) - } - - var adminsSectionItems: [AnyComponentWithIdentity] = [] - let allAdminsToggleAction: () -> Void = { [weak self] in - guard let self, let component = self.component else { + + let dismiss: (Bool) -> Void = { [weak self] animated in + guard let self, !self.isDismissing else { return } - - if self.selectedAdmins.isEmpty { - self.selectedAdmins = Set(component.adminPeers.map(\.id)) - } else { - self.selectedAdmins.removeAll() + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? RecentActionsSettingsSheet { + controller.completePendingDismiss() + controller.dismiss(animated: false) + } + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() } - - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.35, curve: .easeInOut))) } - adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - style: .glass, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + + let currentState = RecentActionsSettingsSheetState( + expandedSections: self.expandedSections, + selectedMembersActions: self.selectedMembersActions, + selectedSettingsActions: self.selectedSettingsActions, + selectedMessagesActions: self.selectedMessagesActions, + selectedAdmins: self.selectedAdmins + ) + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(RecentActionsSettingsContentComponent( + context: component.context, + peer: component.peer, + adminPeers: component.adminPeers, + theme: theme, + sheetState: currentState, + toggleActionTypeSectionSelection: { [weak self] actionTypeSection in + guard let self else { + return + } + + switch actionTypeSection { + case .members: + if self.selectedMembersActions.isEmpty { + self.selectedMembersActions = Set(MembersActionType.allCases) + } else { + self.selectedMembersActions.removeAll() + } + case .settings: + if self.selectedSettingsActions.isEmpty { + self.selectedSettingsActions = Set(SettingsActionType.allCases) + } else { + self.selectedSettingsActions.removeAll() + } + case .messages: + if self.selectedMessagesActions.isEmpty { + self.selectedMessagesActions = Set(MessagesActionType.allCases) + } else { + self.selectedMessagesActions.removeAll() + } + } + + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleActionTypeSectionExpansion: { [weak self] actionTypeSection in + guard let self else { + return + } + if self.expandedSections.contains(actionTypeSection) { + self.expandedSections.remove(actionTypeSection) + } else { + self.expandedSections.insert(actionTypeSection) + } + + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleActionType: { [weak self] actionType in + guard let self else { + return + } + + switch actionType { + case let .members(value): + if self.selectedMembersActions.contains(value) { + self.selectedMembersActions.remove(value) + } else { + self.selectedMembersActions.insert(value) + } + case let .settings(value): + if self.selectedSettingsActions.contains(value) { + self.selectedSettingsActions.remove(value) + } else { + self.selectedSettingsActions.insert(value) + } + case let .messages(value): + if self.selectedMessagesActions.contains(value) { + self.selectedMessagesActions.remove(value) + } else { + self.selectedMessagesActions.insert(value) + } + } + + self.state?.updated(transition: .spring(duration: 0.35)) + }, + toggleAdmin: { [weak self] peer in + guard let self else { + return + } + + if self.selectedAdmins.contains(peer.id) { + self.selectedAdmins.remove(peer.id) + } else { + self.selectedAdmins.insert(peer.id) + } + + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.35, curve: .easeInOut))) + }, + toggleAllAdmins: { [weak self] in + guard let self, let component = self.component else { + return + } + + if self.selectedAdmins.isEmpty { + self.selectedAdmins = Set(component.adminPeers.map(\.id)) + } else { + self.selectedAdmins.removeAll() + } + + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.35, curve: .easeInOut))) + } + )), + titleItem: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Channel_AdminLogFilter_ShowAllAdminsActions, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor + string: environmentValue.strings.Channel_AdminLogFilter_RecentActionsTitle, + font: Font.semibold(17.0), + textColor: theme.list.itemPrimaryTextColor )), maximumNumberOfLines: 1 - ))), - ], alignment: .left, spacing: 2.0)), - leftIcon: .check(ListActionItemComponent.LeftIcon.Check( - isSelected: self.selectedAdmins.count == component.adminPeers.count, - toggle: { - allAdminsToggleAction() - } - )), - icon: .none, - accessory: .none, - action: { _ in - allAdminsToggleAction() - }, - highlighting: .disabled - )))) - adminsSectionItems.append(AnyComponentWithIdentity(id: adminsSectionItems.count, component: AnyComponent(ListSubSectionComponent( - theme: environment.theme, - leftInset: 62.0, - items: peerItems - )))) - let adminsSectionSize = self.adminsSection.update( - transition: transition, - component: AnyComponent(ListSectionComponent( - theme: environment.theme, - style: .glass, - header: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Channel_AdminLogFilter_FilterActionsAdminsTitle, - font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), - textColor: environment.theme.list.freeTextColor - )), - maximumNumberOfLines: 0 )), - footer: nil, - items: adminsSectionItems - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100000.0) - ) - let adminsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: adminsSectionSize) - if let adminsSectionView = self.adminsSection.view { - if adminsSectionView.superview == nil { - self.scrollContentView.addSubview(adminsSectionView) - self.adminsSection.parentState = state - } - transition.setFrame(view: adminsSectionView, frame: adminsSectionFrame) - } - contentHeight += adminsSectionSize.height - - contentHeight += 30.0 - - let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) - let actionButtonSize = self.actionButton.update( - transition: transition, - component: AnyComponent(ButtonComponent( - background: ButtonComponent.Background( - style: .glass, - color: environment.theme.list.itemCheckColors.fillColor, - foreground: environment.theme.list.itemCheckColors.foregroundColor, - pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) ), - content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent(ButtonTextContentComponent( - text: environment.strings.Channel_AdminLogFilter_ApplyFilter, - badge: 0, - textColor: environment.theme.list.itemCheckColors.foregroundColor, - badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, - badgeForeground: environment.theme.list.itemCheckColors.fillColor - )) - ), - isEnabled: true, - displaysProgress: false, - action: { [weak self] in - guard let self, let component = self.component else { - return + bottomItem: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(ButtonTextContentComponent( + text: environmentValue.strings.Channel_AdminLogFilter_ApplyFilter, + badge: 0, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor + )) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + let result = self.calculateResult() + dismiss(true) + component.completion(result) } - self.environment?.controller()?.dismiss() - component.completion(self.calculateResult()) - } + )), + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + animateOut: self.animateOut )), - environment: {}, - containerSize: CGSize(width: availableSize.width - bottomInsets.left - bottomInsets.right, height: 52.0) + environment: { + environmentValue + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environmentValue.statusBarHeight, + safeInsets: environmentValue.safeInsets, + inputHeight: 0.0, + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: environmentValue.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated) + } + ) + }, + forceUpdate: true, + containerSize: availableSize ) - let bottomPanelHeight = actionButtonSize.height + bottomInsets.bottom - let actionButtonFrame = CGRect(origin: CGPoint(x: bottomInsets.left, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) - if let actionButtonView = actionButton.view { - if actionButtonView.superview == nil { - self.addSubview(actionButtonView) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) } - transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) } - - contentHeight += bottomPanelHeight - - clippingY = actionButtonFrame.minY - 24.0 - - let topInset: CGFloat = max(0.0, availableSize.height - containerInset - contentHeight) - - let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) - - self.scrollContentClippingView.layer.cornerRadius = 10.0 - - self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset) - - transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) - - transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) - - let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset)) - transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) - transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) - - self.ignoreScrolling = true - let previousBounds = self.scrollView.bounds - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) - let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) - if contentSize != self.scrollView.contentSize { - self.scrollView.contentSize = contentSize - } - if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) - } else { - if !previousBounds.isEmpty, !transition.animation.isImmediate { - let bounds = self.scrollView.bounds - if bounds.maxY != previousBounds.maxY { - let offsetY = previousBounds.maxY - bounds.maxY - transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) - } - } - } - self.ignoreScrolling = false - self.updateScrolling(isFirstTime: isFirstTime, transition: transition) - + return availableSize } } - + func makeView() -> View { return View(frame: CGRect()) } - + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } @@ -981,11 +836,12 @@ public class RecentActionsSettingsSheet: ViewControllerComponentContainer { private let context: AccountContext private var isDismissed: Bool = false + private var dismissCompletion: (() -> Void)? public init(context: AccountContext, peer: EnginePeer, adminPeers: [EnginePeer], initialValue: Value, completion: @escaping (Value) -> Void) { self.context = context - super.init(context: context, component: RecentActionsSettingsSheetComponent(context: context, peer: peer, adminPeers: adminPeers, initialValue: initialValue, completion: completion), navigationBarAppearance: .none) + super.init(context: context, component: RecentActionsSettingsResizableSheetComponent(context: context, peer: peer, adminPeers: adminPeers, initialValue: initialValue, completion: completion), navigationBarAppearance: .none) self.statusBar.statusBarStyle = .Ignore self.navigationPresentation = .flatModal @@ -1003,22 +859,29 @@ public class RecentActionsSettingsSheet: ViewControllerComponentContainer { super.viewDidAppear(animated) self.view.disablesInteractiveModalDismiss = true - - if let componentView = self.node.hostView.componentView as? RecentActionsSettingsSheetComponent.View { - componentView.animateIn() + } + + fileprivate func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } } override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true + self.dismissCompletion = completion - if let componentView = self.node.hostView.componentView as? RecentActionsSettingsSheetComponent.View { - componentView.animateOut(completion: { [weak self] in - completion?() - self?.dismiss(animated: false) - }) + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } else { + self.completePendingDismiss() self.dismiss(animated: false) } } diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD index fabf9626cc..7a00f00071 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/BUILD @@ -10,20 +10,15 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", "//submodules/Components/ViewControllerComponent", - "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ResizableSheetComponent", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", - "//submodules/Components/SolidRoundedButtonComponent", "//submodules/Components/BundleIconComponent", - "//submodules/Components/BlurredBackgroundComponent", - "//submodules/TelegramUI/Components/ScrollComponent", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/AppBundle", @@ -31,7 +26,10 @@ swift_library( "//submodules/PresentationDataUtils", "//submodules/ContextUI", "//submodules/UndoUI", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/Ads/AdsReportScreen", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift index a582b3979f..de4d0710fe 100644 --- a/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsInfoScreen/Sources/AdsInfoScreen.swift @@ -1,151 +1,149 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import Markdown import TextFormat import TelegramPresentationData import ViewControllerComponent -import ScrollComponent +import ResizableSheetComponent import BundleIconComponent import BalancedTextComponent import MultilineTextComponent -import SolidRoundedButtonComponent import AccountContext -import ScrollComponent -import BlurredBackgroundComponent import PresentationDataUtils import ContextUI import UndoUI +import GlassBarButtonComponent +import ButtonComponent import AdsReportScreen +import LottieComponent private let moreTag = GenericComponentViewTag() -private final class ScrollContent: CombinedComponent { - typealias EnvironmentType = (ViewControllerComponentContainer.Environment, ScrollChildEnvironment) - +private final class SheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + let context: AccountContext let mode: AdsInfoScreen.Mode + let bottomInset: CGFloat let openPremium: () -> Void - let dismiss: () -> Void - + init( context: AccountContext, mode: AdsInfoScreen.Mode, - openPremium: @escaping () -> Void, - dismiss: @escaping () -> Void + bottomInset: CGFloat, + openPremium: @escaping () -> Void ) { self.context = context self.mode = mode + self.bottomInset = bottomInset self.openPremium = openPremium - self.dismiss = dismiss } - - static func ==(lhs: ScrollContent, rhs: ScrollContent) -> Bool { + + static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { if lhs.context !== rhs.context { return false } + if lhs.mode != rhs.mode { + return false + } + if lhs.bottomInset != rhs.bottomInset { + return false + } return true } - + final class State: ComponentState { var cachedIconImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)? - - let playOnce = ActionSlot() - private var didPlayAnimation = false - - func playAnimationIfNeeded() { - guard !self.didPlayAnimation else { - return - } - self.didPlayAnimation = true - self.playOnce.invoke(Void()) - } } - + func makeState() -> State { return State() } - + static var body: Body { let iconBackground = Child(Image.self) let icon = Child(BundleIconComponent.self) - + let title = Child(BalancedTextComponent.self) let text = Child(BalancedTextComponent.self) let list = Child(List.self) - + let infoBackground = Child(RoundedRectangle.self) let infoTitle = Child(MultilineTextComponent.self) let infoText = Child(MultilineTextComponent.self) - + let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) - + return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let component = context.component let state = context.state - - let theme = environment.theme + + let theme = environment.theme.withModalBlocksBackground() let strings = environment.strings - let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let sideInset: CGFloat = 16.0 + environment.safeInsets.left let textSideInset: CGFloat = 30.0 + environment.safeInsets.left - + let titleFont = Font.semibold(20.0) let textFont = Font.regular(15.0) - + let textColor = theme.actionSheet.primaryTextColor let secondaryTextColor = theme.actionSheet.secondaryTextColor let linkColor = theme.actionSheet.controlAccentColor - - let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) - }) - + + let markdownAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: textColor), + bold: MarkdownAttributeSet(font: textFont, textColor: textColor), + link: MarkdownAttributeSet(font: textFont, textColor: linkColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + let spacing: CGFloat = 16.0 var contentSize = CGSize(width: context.availableSize.width, height: 30.0) - + let iconSize = CGSize(width: 90.0, height: 90.0) let gradientImage: UIImage - + if let (current, currentTheme) = state.cachedIconImage, currentTheme === theme { gradientImage = current } else { - gradientImage = generateGradientFilledCircleImage(diameter: iconSize.width, colors: [ - UIColor(rgb: 0x6e91ff).cgColor, - UIColor(rgb: 0x9472ff).cgColor, - UIColor(rgb: 0xcc6cdd).cgColor - ], direction: .diagonal)! - context.state.cachedIconImage = (gradientImage, theme) + gradientImage = generateGradientFilledCircleImage( + diameter: iconSize.width, + colors: [ + UIColor(rgb: 0x6e91ff).cgColor, + UIColor(rgb: 0x9472ff).cgColor, + UIColor(rgb: 0xcc6cdd).cgColor + ], + direction: .diagonal + )! + state.cachedIconImage = (gradientImage, theme) } - + let iconBackground = iconBackground.update( component: Image(image: gradientImage), availableSize: iconSize, transition: .immediate ) - context.add(iconBackground - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) - ) - + context.add(iconBackground.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))) + let icon = icon.update( - component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: theme.list.itemCheckColors.foregroundColor), - availableSize: CGSize(width: 90, height: 90), + component: BundleIconComponent(name: "Ads/AdsLogo", tintColor: .white), + availableSize: CGSize(width: 90.0, height: 90.0), transition: .immediate ) - context.add(icon - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0)) - ) + context.add(icon.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + iconBackground.size.height / 2.0))) contentSize.height += iconSize.height contentSize.height += spacing + 1.0 - + let title = title.update( component: BalancedTextComponent( text: .plain(NSAttributedString(string: strings.AdsInfo_Title, font: titleFont, textColor: textColor)), @@ -156,12 +154,10 @@ private final class ScrollContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) - ) + context.add(title.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))) contentSize.height += title.size.height contentSize.height += spacing - 8.0 - + let text = text.update( component: BalancedTextComponent( text: .plain(NSAttributedString(string: strings.AdsInfo_Info, font: textFont, textColor: secondaryTextColor)), @@ -172,14 +168,12 @@ private final class ScrollContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), transition: .immediate ) - context.add(text - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0)) - ) + context.add(text.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + text.size.height / 2.0))) contentSize.height += text.size.height contentSize.height += spacing - + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) - + let respectText: String let adsText: String let infoRawText: String @@ -190,76 +184,79 @@ private final class ScrollContent: CombinedComponent { infoRawText = strings.AdsInfo_Launch_Text case .bot: respectText = strings.AdsInfo_Bot_Respect_Text - adsText = strings.AdsInfo_Bot_Ads_Text + adsText = strings.AdsInfo_Bot_Ads_Text infoRawText = strings.AdsInfo_Bot_Launch_Text case .search: respectText = strings.AdsInfo_Search_Respect_Text adsText = strings.AdsInfo_Search_Ads_Text infoRawText = strings.AdsInfo_Search_Launch_Text } - + var items: [AnyComponentWithIdentity] = [] items.append( AnyComponentWithIdentity( id: "respect", - component: AnyComponent(ParagraphComponent( - title: strings.AdsInfo_Respect_Title, - titleColor: textColor, - text: respectText, - textColor: secondaryTextColor, - accentColor: linkColor, - iconName: "Ads/Privacy", - iconColor: linkColor - )) + component: AnyComponent( + ParagraphComponent( + title: strings.AdsInfo_Respect_Title, + titleColor: textColor, + text: respectText, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Ads/Privacy", + iconColor: linkColor + ) + ) ) ) if case .search = component.mode { - } else { items.append( AnyComponentWithIdentity( id: "split", - component: AnyComponent(ParagraphComponent( - title: component.mode == .bot ? strings.AdsInfo_Bot_Split_Title : strings.AdsInfo_Split_Title, - titleColor: textColor, - text: component.mode == .bot ? strings.AdsInfo_Bot_Split_Text : strings.AdsInfo_Split_Text, - textColor: secondaryTextColor, - accentColor: linkColor, - iconName: "Ads/Split", - iconColor: linkColor - )) + component: AnyComponent( + ParagraphComponent( + title: component.mode == .bot ? strings.AdsInfo_Bot_Split_Title : strings.AdsInfo_Split_Title, + titleColor: textColor, + text: component.mode == .bot ? strings.AdsInfo_Bot_Split_Text : strings.AdsInfo_Split_Text, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Ads/Split", + iconColor: linkColor + ) + ) ) ) } items.append( AnyComponentWithIdentity( id: "ads", - component: AnyComponent(ParagraphComponent( - title: strings.AdsInfo_Ads_Title, - titleColor: textColor, - text: adsText, - textColor: secondaryTextColor, - accentColor: linkColor, - iconName: "Premium/BoostPerk/NoAds", - iconColor: linkColor, - action: { - component.openPremium() - } - )) + component: AnyComponent( + ParagraphComponent( + title: strings.AdsInfo_Ads_Title, + titleColor: textColor, + text: adsText, + textColor: secondaryTextColor, + accentColor: linkColor, + iconName: "Premium/BoostPerk/NoAds", + iconColor: linkColor, + action: { + component.openPremium() + } + ) + ) ) ) - + let list = list.update( component: List(items), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 10000.0), transition: context.transition ) - context.add(list - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0)) - ) + context.add(list.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + list.size.height / 2.0))) contentSize.height += list.size.height contentSize.height += spacing - 9.0 - + let infoTitleAttributedString = NSMutableAttributedString(string: strings.AdsInfo_Launch_Title, font: titleFont, textColor: textColor) let infoTitle = infoTitle.update( component: MultilineTextComponent( @@ -271,17 +268,17 @@ private final class ScrollContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 3.5, height: context.availableSize.height), transition: .immediate ) - - if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { + + if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== theme { state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) } - + var infoString = infoRawText if let spaceRegex { let nsRange = NSRange(infoString.startIndex..., in: infoString) let matches = spaceRegex.matches(in: infoString, options: [], range: nsRange) var modifiedString = infoString - + for match in matches.reversed() { let matchRange = Range(match.range, in: infoString)! let matchedSubstring = String(infoString[matchRange]) @@ -290,10 +287,12 @@ private final class ScrollContent: CombinedComponent { } infoString = modifiedString } + let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { infoAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: infoAttributedString.string)) } + let infoText = infoText.update( component: MultilineTextComponent( text: .plain(infoAttributedString), @@ -310,190 +309,47 @@ private final class ScrollContent: CombinedComponent { } }, tapAction: { _, _ in - component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.AdsInfo_Launch_Text_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + component.context.sharedContext.openExternalUrl( + context: component.context, + urlContext: .generic, + url: strings.AdsInfo_Launch_Text_URL, + forceExternal: true, + presentationData: presentationData, + navigationController: nil, + dismissInput: {} + ) } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 3.5, height: context.availableSize.height), transition: .immediate ) - + let infoPadding: CGFloat = 13.0 let infoSpacing: CGFloat = 6.0 let totalInfoHeight = infoPadding + infoTitle.size.height + infoSpacing + infoText.size.height + infoPadding - + let infoBackground = infoBackground.update( component: RoundedRectangle( color: theme.overallDarkAppearance ? theme.list.itemModalBlocksBackgroundColor : theme.list.blocksBackgroundColor, - cornerRadius: 10.0 + cornerRadius: 26.0 ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: totalInfoHeight), transition: .immediate ) - context.add(infoBackground - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0)) - ) + context.add(infoBackground.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoBackground.size.height / 2.0))) contentSize.height += infoPadding - - context.add(infoTitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0)) - ) + + context.add(infoTitle.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoTitle.size.height / 2.0))) contentSize.height += infoTitle.size.height contentSize.height += infoSpacing - - context.add(infoText - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0)) - ) + + context.add(infoText.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + infoText.size.height / 2.0))) contentSize.height += infoText.size.height contentSize.height += infoPadding - contentSize.height += spacing - - contentSize.height += 12.0 + 50.0 - if environment.safeInsets.bottom > 0 { - contentSize.height += environment.safeInsets.bottom + 5.0 - } else { - contentSize.height += 12.0 - } - - state.playAnimationIfNeeded() - - return contentSize - } - } -} + contentSize.height += spacing - 5.0 + contentSize.height += component.bottomInset -private final class ContainerComponent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - class ExternalState { - var contentHeight: CGFloat = 0.0 - } - - let context: AccountContext - let mode: AdsInfoScreen.Mode - let externalState: ExternalState - let openPremium: () -> Void - let openContextMenu: () -> Void - let dismiss: () -> Void - - init( - context: AccountContext, - mode: AdsInfoScreen.Mode, - externalState: ExternalState, - openPremium: @escaping () -> Void, - openContextMenu: @escaping () -> Void, - dismiss: @escaping () -> Void - ) { - self.context = context - self.mode = mode - self.externalState = externalState - self.openPremium = openPremium - self.openContextMenu = openContextMenu - self.dismiss = dismiss - } - - static func ==(lhs: ContainerComponent, rhs: ContainerComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.mode != rhs.mode { - return false - } - return true - } - - final class State: ComponentState { - var topContentOffset: CGFloat? - var bottomContentOffset: CGFloat? - - var cachedMoreImage: (UIImage, PresentationTheme)? - } - - func makeState() -> State { - return State() - } - - static var body: Body { - let background = Child(Rectangle.self) - let scroll = Child(ScrollComponent.self) - let scrollExternalState = ScrollComponent.ExternalState() - - let moreButton = Child(Button.self) - - return { context in - let environment = context.environment[EnvironmentType.self] - let state = context.state - - let theme = environment.theme - - let openContextMenu = context.component.openContextMenu - let dismiss = context.component.dismiss - - let background = background.update( - component: Rectangle(color: theme.overallDarkAppearance ? theme.list.modalBlocksBackgroundColor : theme.list.plainBackgroundColor), - environment: {}, - availableSize: context.availableSize, - transition: context.transition - ) - context.add(background - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - let scroll = scroll.update( - component: ScrollComponent( - content: AnyComponent(ScrollContent( - context: context.component.context, - mode: context.component.mode, - openPremium: context.component.openPremium, - dismiss: { - dismiss() - } - )), - externalState: scrollExternalState, - contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 1.0, right: 0.0), - contentOffsetUpdated: { [weak state] topContentOffset, bottomContentOffset in - state?.topContentOffset = topContentOffset - state?.bottomContentOffset = bottomContentOffset - Queue.mainQueue().justDispatch { - state?.updated(transition: .immediate) - } - }, - contentOffsetWillCommit: { targetContentOffset in - } - ), - environment: { environment }, - availableSize: context.availableSize, - transition: context.transition - ) - context.component.externalState.contentHeight = scrollExternalState.contentHeight - - context.add(scroll - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - if case .bot = context.component.mode { - let moreImage: UIImage - if let (image, theme) = state.cachedMoreImage, theme === environment.theme { - moreImage = image - } else { - moreImage = generateMoreButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: environment.theme.actionSheet.inputClearButtonColor)! - state.cachedMoreImage = (moreImage, environment.theme) - } - let moreButton = moreButton.update( - component: Button( - content: AnyComponent(Image(image: moreImage)), - action: { - openContextMenu() - } - ).tagged(moreTag), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: .immediate - ) - context.add(moreButton - .position(CGPoint(x: context.availableSize.width - 16.0 - moreButton.size.width / 2.0, y: 13.0 + moreButton.size.height / 2.0)) - ) - } - - return context.availableSize + return contentSize } } } @@ -507,8 +363,8 @@ private final class ParagraphComponent: CombinedComponent { let iconName: String let iconColor: UIColor let action: () -> Void - - public init( + + init( title: String, titleColor: UIColor, text: String, @@ -527,7 +383,7 @@ private final class ParagraphComponent: CombinedComponent { self.iconColor = iconColor self.action = action } - + static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool { if lhs.title != rhs.title { return false @@ -552,37 +408,38 @@ private final class ParagraphComponent: CombinedComponent { } return true } - + static var body: Body { let title = Child(MultilineTextComponent.self) let text = Child(MultilineTextComponent.self) let icon = Child(BundleIconComponent.self) - + return { context in let component = context.component - + let leftInset: CGFloat = 32.0 let rightInset: CGFloat = 24.0 let textSideInset: CGFloat = leftInset + 8.0 let spacing: CGFloat = 5.0 - let textTopInset: CGFloat = 9.0 - + let title = title.update( component: MultilineTextComponent( - text: .plain(NSAttributedString( - string: component.title, - font: Font.semibold(15.0), - textColor: component.titleColor, - paragraphAlignment: .natural - )), + text: .plain( + NSAttributedString( + string: component.title, + font: Font.semibold(15.0), + textColor: component.titleColor, + paragraphAlignment: .natural + ) + ), horizontalAlignment: .center, maximumNumberOfLines: 1 ), availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) - + let textFont = Font.regular(15.0) let boldTextFont = Font.semibold(15.0) let textColor = component.textColor @@ -595,7 +452,7 @@ private final class ParagraphComponent: CombinedComponent { return (TelegramTextAttributes.URL, contents) } ) - + let text = text.update( component: MultilineTextComponent( text: .markdown(text: component.text, attributes: markdownAttributes), @@ -617,967 +474,549 @@ private final class ParagraphComponent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height), transition: .immediate ) - + let icon = icon.update( - component: BundleIconComponent( - name: component.iconName, - tintColor: component.iconColor - ), + component: BundleIconComponent(name: component.iconName, tintColor: component.iconColor), availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), transition: .immediate ) - - context.add(title - .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0)) - ) - - context.add(text - .position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0)) - ) - - context.add(icon - .position(CGPoint(x: 15.0, y: textTopInset + 18.0)) - ) - + + context.add(title.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))) + context.add(text.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))) + context.add(icon.position(CGPoint(x: 15.0, y: textTopInset + 18.0))) + return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 20.0) } } } +private final class AdsInfoSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment -public class AdsInfoScreen: ViewController { + let context: AccountContext + let mode: AdsInfoScreen.Mode + let message: EngineMessage? + + init( + context: AccountContext, + mode: AdsInfoScreen.Mode, + message: EngineMessage? + ) { + self.context = context + self.mode = mode + self.message = message + } + + static func ==(lhs: AdsInfoSheetComponent, rhs: AdsInfoSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.mode != rhs.mode { + return false + } + if lhs.message?.id != rhs.message?.id { + return false + } + return true + } + + static var body: Body { + let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self) + let animateOut = StoredActionSlot(Action.self) + + let moreButtonPlayOnce = ActionSlot() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let dismiss: (Bool) -> Void = { animated in + if animated { + animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } else if let controller = controller() { + controller.dismiss(completion: nil) + } + } + + let theme = environment.theme.withModalBlocksBackground() + + let bottomButtonInsets = ContainerViewLayout.concentricInsets( + bottomInset: environment.safeInsets.bottom, + innerDiameter: 52.0, + sideInset: 30.0 + ) + let contentBottomInset = bottomButtonInsets.bottom + 52.0 + 16.0 + + let defaultHeight: CGFloat? + if case .search = context.component.mode { + defaultHeight = nil + } else { + let footerBottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : 12.0 + let panelHeight: CGFloat = 12.0 + 50.0 + footerBottomInset + 28.0 + let containerTopInset = environment.statusBarHeight + 10.0 + defaultHeight = max(0.0, context.availableSize.width + 128.0 + panelHeight - containerTopInset) + } + + let rightItem: AnyComponent? + if case .bot = context.component.mode, context.component.message?.adAttribute != nil { + rightItem = AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity( + id: "more", + component: AnyComponent( + LottieComponent( + content: LottieComponent.AppBundleContent( + name: "anim_morewide" + ), + color: theme.chat.inputPanel.panelControlColor, + size: CGSize(width: 34.0, height: 34.0), + playOnce: moreButtonPlayOnce + ) + ) + ), + action: { _ in + (controller() as? AdsInfoScreen)?.infoPressed() + moreButtonPlayOnce.invoke(Void()) + }, + tag: moreTag + ) + ) + } else { + rightItem = nil + } + + let sheet = sheet.update( + component: ResizableSheetComponent( + content: AnyComponent( + SheetContent( + context: context.component.context, + mode: context.component.mode, + bottomInset: contentBottomInset, + openPremium: { + guard let controller = controller() as? AdsInfoScreen else { + return + } + let navigationController = controller.navigationController + let accountContext = controller.context + let forceDark = controller.forceDark + dismiss(true) + + Queue.mainQueue().after(0.3) { + let premiumController = accountContext.sharedContext.makePremiumIntroController( + context: accountContext, + source: .ads, + forceDark: forceDark, + dismissed: nil + ) + navigationController?.pushViewController(premiumController, animated: true) + } + } + ) + ), + titleItem: nil, + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity( + id: "close", + component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + ) + ), + action: { _ in + dismiss(true) + } + ) + ), + rightItem: rightItem, + hasTopEdgeEffect: false, + bottomItem: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent( + Text( + text: environment.strings.AdsInfo_Understood, + font: Font.semibold(17.0), + color: theme.list.itemCheckColors.foregroundColor + ) + ) + ), + action: { + dismiss(true) + } + ) + ), + backgroundColor: .color(theme.overallDarkAppearance ? theme.list.modalBlocksBackgroundColor : theme.list.plainBackgroundColor), + defaultHeight: defaultHeight, + animateOut: animateOut + ), + environment: { + environment + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, + inputHeight: 0.0, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + screenSize: context.availableSize, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + dismiss(animated) + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + context.add(sheet.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))) + + return context.availableSize + } + } +} + +public final class AdsInfoScreen: ViewControllerComponentContainer { public enum Mode: Equatable { case channel case bot case search } - - final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate { - private var presentationData: PresentationData - private weak var controller: AdsInfoScreen? - - let dim: ASDisplayNode - let wrappingView: UIView - let containerView: UIView - - let contentView: ComponentHostView - let footerContainerView: UIView - let footerView: ComponentHostView - - private var containerExternalState = ContainerComponent.ExternalState() - - private(set) var isExpanded = false - private var panGestureRecognizer: UIPanGestureRecognizer? - private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?)? - - private let hapticFeedback = HapticFeedback() - - private var currentIsVisible: Bool = false - private var currentLayout: ContainerViewLayout? - - init(context: AccountContext, controller: AdsInfoScreen) { - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - if controller.forceDark { - self.presentationData = self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) - } - self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground()) - - self.controller = controller - - self.dim = ASDisplayNode() - self.dim.alpha = 0.0 - self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) - - self.wrappingView = UIView() - self.containerView = UIView() - self.contentView = ComponentHostView() - - self.footerContainerView = UIView() - self.footerView = ComponentHostView() - - super.init() - - self.containerView.clipsToBounds = true - self.containerView.backgroundColor = self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.plainBackgroundColor - - self.addSubnode(self.dim) - - self.view.addSubview(self.wrappingView) - self.wrappingView.addSubview(self.containerView) - self.containerView.addSubview(self.contentView) - - self.containerView.addSubview(self.footerContainerView) - self.footerContainerView.addSubview(self.footerView) - } - - override func didLoad() { - super.didLoad() - - let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) - panRecognizer.delegate = self.wrappedGestureRecognizerDelegate - panRecognizer.delaysTouchesBegan = false - panRecognizer.cancelsTouchesInView = true - self.panGestureRecognizer = panRecognizer - self.wrappingView.addGestureRecognizer(panRecognizer) - - self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.controller?.dismiss(animated: true) - } - } - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if let layout = self.currentLayout { - if case .regular = layout.metrics.widthClass { - return false - } - } - return true - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { - if let scrollView = otherGestureRecognizer.view as? UIScrollView { - if scrollView.contentSize.width > scrollView.contentSize.height { - return false - } - } - return true - } - return false - } - - private var isDismissing = false - func animateIn() { - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) - - let targetPosition = self.containerView.center - let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) - - self.containerView.center = startPosition - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - transition.animateView(allowUserInteraction: true, { - self.containerView.center = targetPosition - }, completion: { _ in - }) - } - - func animateOut(completion: @escaping () -> Void = {}) { - self.isDismissing = true - - let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in - self?.controller?.dismiss(animated: false, completion: completion) - }) - let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) - - self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) - } - - func requestLayout(transition: ComponentTransition) { - guard let layout = self.currentLayout else { - return - } - self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition) - } - - private var dismissOffset: CGFloat? - func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, transition: ComponentTransition) { - guard !self.isDismissing else { - return - } - self.currentLayout = layout - - self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) - - let isLandscape = layout.orientation == .landscape - - var containerTopInset: CGFloat = 0.0 - let clipFrame: CGRect - if layout.metrics.widthClass == .compact { - self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) - if isLandscape { - self.containerView.layer.cornerRadius = 0.0 - } else { - self.containerView.layer.cornerRadius = 10.0 - } - - if #available(iOS 11.0, *) { - if layout.safeInsets.bottom.isZero { - self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - } else { - self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] - } - } - - if isLandscape { - clipFrame = CGRect(origin: CGPoint(), size: layout.size) - } else { - let coveredByModalTransition: CGFloat = 0.0 - containerTopInset = 10.0 - if let statusBarHeight = layout.statusBarHeight { - containerTopInset += statusBarHeight - } - - let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset)) - let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width - let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition - let maxScaledTopInset: CGFloat = containerTopInset - 10.0 - let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition - let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) - - clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) - } - } else { - self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) - self.containerView.layer.cornerRadius = 10.0 - - let verticalInset: CGFloat = 44.0 - - let maxSide = max(layout.size.width, layout.size.height) - let minSide = min(layout.size.width, layout.size.height) - let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) - clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) - } - - transition.setFrame(view: self.containerView, frame: clipFrame) - - var effectiveExpanded = self.isExpanded - if case .regular = layout.metrics.widthClass { - effectiveExpanded = true - } - - self.updated(transition: transition, forceUpdate: forceUpdate) - - let contentHeight = self.containerExternalState.contentHeight - if contentHeight > 0.0 && contentHeight < 400.0, let view = self.footerView.componentView as? FooterComponent.View { - view.backgroundView.alpha = 0.0 - view.separator.opacity = 0.0 - } - let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset - let topInset: CGFloat - if let (panInitialTopInset, panOffset, _) = self.panGestureArguments { - if effectiveExpanded { - topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) - } else { - topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) - } - } else if let dismissOffset = self.dismissOffset, !dismissOffset.isZero { - topInset = edgeTopInset * dismissOffset - } else { - topInset = effectiveExpanded ? 0.0 : edgeTopInset - } - transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil) - - let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset) - self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition) - - let footerHeight = self.footerHeight - let convertedFooterFrame = self.view.convert(CGRect(origin: CGPoint(x: clipFrame.minX, y: clipFrame.maxY - footerHeight), size: CGSize(width: clipFrame.width, height: footerHeight)), to: self.containerView) - transition.setFrame(view: self.footerContainerView, frame: convertedFooterFrame) - } - - func updated(transition: ComponentTransition, forceUpdate: Bool = false) { - guard let controller = self.controller, let layout = self.currentLayout else { - return - } - let environment = ViewControllerComponentContainer.Environment( - statusBarHeight: 0.0, - navigationHeight: 0.0, - safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), - additionalInsets: layout.additionalInsets, - inputHeight: layout.inputHeight ?? 0.0, - metrics: layout.metrics, - deviceMetrics: layout.deviceMetrics, - orientation: layout.metrics.orientation, - isVisible: self.currentIsVisible, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - controller: { [weak self] in - return self?.controller - } - ) - let contentSize = self.contentView.update( - transition: transition, - component: AnyComponent( - ContainerComponent( - context: controller.context, - mode: controller.mode, - externalState: self.containerExternalState, - openPremium: { [weak self] in - guard let self, let controller = self.controller else { - return - } - - let context = controller.context - let forceDark = controller.forceDark - let navigationController = controller.navigationController - controller.dismiss(animated: true) - - Queue.mainQueue().after(0.3) { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: forceDark, dismissed: nil) - navigationController?.pushViewController(controller, animated: true) - } - }, - openContextMenu: { [weak self] in - guard let self else { - return - } - self.infoPressed() - }, - dismiss: { [weak self] in - guard let self, let controller = self.controller else { - return - } - controller.dismiss(animated: true) - } - ) - ), - environment: { environment }, - forceUpdate: forceUpdate, - containerSize: self.containerView.bounds.size - ) - self.contentView.frame = CGRect(origin: .zero, size: contentSize) - - let footerHeight = self.footerHeight - let footerSize = self.footerView.update( - transition: .immediate, - component: AnyComponent( - FooterComponent( - context: controller.context, - theme: self.presentationData.theme, - title: self.presentationData.strings.AdsInfo_Understood, - showBackground: controller.mode != .search, - action: { [weak self] in - guard let self else { - return - } - self.buttonPressed() - } - ) - ), - environment: {}, - containerSize: CGSize(width: self.containerView.bounds.width, height: footerHeight) - ) - self.footerView.frame = CGRect(origin: .zero, size: footerSize) - } - - private var didPlayAppearAnimation = false - func updateIsVisible(isVisible: Bool) { - if self.currentIsVisible == isVisible { - return - } - self.currentIsVisible = isVisible - - guard let layout = self.currentLayout else { - return - } - self.containerLayoutUpdated(layout: layout, transition: .immediate) - - if !self.didPlayAppearAnimation { - self.didPlayAppearAnimation = true - self.animateIn() - } - } - - private var footerHeight: CGFloat { - guard let layout = self.currentLayout else { - return 58.0 - } - - var footerHeight: CGFloat = 8.0 + 50.0 - footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0 - return footerHeight - } - - private var defaultTopInset: CGFloat { - guard let layout = self.currentLayout, let controller = self.controller else { - return 210.0 - } - if case .compact = layout.metrics.widthClass { - let bottomPanelPadding: CGFloat = 12.0 - let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding - let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0 - - var defaultTopInset = layout.size.height - layout.size.width - 128.0 - panelHeight - - let containerTopInset = 10.0 + (layout.statusBarHeight ?? 0.0) - let contentHeight = self.containerExternalState.contentHeight - let footerHeight = self.footerHeight - if contentHeight > 0.0 { - if case .search = controller.mode { - return (layout.size.height - containerTopInset) - contentHeight - } else { - let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0 - if delta > 0.0 { - defaultTopInset += delta - } - } - } - return defaultTopInset - } else { - return 210.0 - } - } - - private func findVerticalScrollView(view: UIView?) -> UIScrollView? { - if let view = view { - if let view = view as? UIScrollView, view.contentSize.height > view.contentSize.width { - return view - } - return findVerticalScrollView(view: view.superview) - } else { - return nil - } - } - - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - guard let layout = self.currentLayout, let controller = self.controller else { - return - } - - let isLandscape = layout.orientation == .landscape - let edgeTopInset = isLandscape ? 0.0 : defaultTopInset - - switch recognizer.state { - case .began: - let point = recognizer.location(in: self.view) - let currentHitView = self.hitTest(point, with: nil) - - var scrollView = self.findVerticalScrollView(view: currentHitView) - if scrollView?.frame.height == self.frame.width { - scrollView = nil - } - if scrollView?.isDescendant(of: self.view) == false { - scrollView = nil - } - - let topInset: CGFloat - if self.isExpanded { - topInset = 0.0 - } else { - topInset = edgeTopInset - } - - self.panGestureArguments = (topInset, 0.0, scrollView) - case .changed: - guard let (topInset, panOffset, scrollView) = self.panGestureArguments else { - return - } - let contentOffset = scrollView?.contentOffset.y ?? 0.0 - - var translation = recognizer.translation(in: self.view).y - if case .search = controller.mode { - translation = max(0.0, translation) - } + fileprivate let context: AccountContext + fileprivate let mode: Mode + fileprivate let message: EngineMessage? + fileprivate let forceDark: Bool - var currentOffset = topInset + translation - - let epsilon = 1.0 - if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { - scrollView.bounces = false - scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) - } else if let scrollView = scrollView { - translation = panOffset - currentOffset = topInset + translation - if self.isExpanded { - recognizer.setTranslation(CGPoint(), in: self.view) - } else if currentOffset > 0.0 { - scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) - } - } - - if scrollView == nil { - translation = max(0.0, translation) - } - - self.panGestureArguments = (topInset, translation, scrollView) - - if !self.isExpanded { - if currentOffset > 0.0, let scrollView = scrollView { - scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) - } - } - - var bounds = self.bounds - if self.isExpanded { - bounds.origin.y = -max(0.0, translation - edgeTopInset) - } else { - bounds.origin.y = -translation - } - bounds.origin.y = min(0.0, bounds.origin.y) - self.bounds = bounds - - self.containerLayoutUpdated(layout: layout, transition: .immediate) - case .ended: - guard let (currentTopInset, panOffset, scrollView) = self.panGestureArguments else { - return - } - self.panGestureArguments = nil - - let contentOffset = scrollView?.contentOffset.y ?? 0.0 - - var translation = recognizer.translation(in: self.view).y - var velocity = recognizer.velocity(in: self.view) - if case .search = controller.mode { - translation = max(0.0, translation) - velocity.y = max(0.0, velocity.y) - } - - if self.isExpanded { - if contentOffset > 0.1 { - velocity = CGPoint() - } - } - - var bounds = self.bounds - if self.isExpanded { - bounds.origin.y = -max(0.0, translation - edgeTopInset) - } else { - bounds.origin.y = -translation - } - bounds.origin.y = min(0.0, bounds.origin.y) - - scrollView?.bounces = true - - let offset = currentTopInset + panOffset - let topInset: CGFloat = edgeTopInset - - var dismissing = false - if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) { - self.controller?.dismiss(animated: true, completion: nil) - dismissing = true - } else if self.isExpanded { - if velocity.y > 300.0 || offset > topInset / 2.0 { - self.isExpanded = false - if let scrollView = scrollView { - scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) - } - - let distance = topInset - offset - let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) - let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) - - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) - } else { - self.isExpanded = true - - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) - } - } else if scrollView != nil, (velocity.y < -300.0 || offset < topInset / 2.0) { - let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) - let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) - self.isExpanded = true - - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) - } else { - if let scrollView = scrollView { - scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) - } - - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) - } - - if !dismissing { - var bounds = self.bounds - let previousBounds = bounds - bounds.origin.y = 0.0 - self.bounds = bounds - self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - } - case .cancelled: - self.panGestureArguments = nil - - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut))) - default: - break - } - } - - func updateDismissOffset(_ offset: CGFloat) { - guard self.isExpanded, let layout = self.currentLayout else { - return - } - - self.dismissOffset = offset - self.containerLayoutUpdated(layout: layout, transition: .immediate) - } - - func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { - guard isExpanded != self.isExpanded else { - return - } - self.dismissOffset = nil - self.isExpanded = isExpanded - - guard let layout = self.currentLayout else { - return - } - self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) - } - - func displayUndo(_ content: UndoOverlayContent) { - guard let controller = self.controller else { - return - } - let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in - return true - }), in: .current) - } - - func infoPressed() { - guard let referenceView = self.contentView.findTaggedView(tag: moreTag), let controller = self.controller, let message = controller.message, let adAttribute = message.adAttribute else { - return - } - - let context = controller.context - let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } - - var actions: [ContextMenuItem] = [] - if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil { - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { [weak self] c, _ in - var subItems: [ContextMenuItem] = [] - - subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, textColor: .primary, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, iconPosition: .left, action: { c, _ in - c?.popItems() - }))) - - subItems.append(.separator) - - if let sponsorInfo = adAttribute.sponsorInfo { - subItems.append(.action(ContextMenuActionItem(text: sponsorInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return nil - }, iconSource: nil, action: { [weak self] c, _ in - c?.dismiss(completion: { - UIPasteboard.general.string = sponsorInfo - - self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) - }) - }))) - } - if let additionalInfo = adAttribute.additionalInfo { - subItems.append(.action(ContextMenuActionItem(text: additionalInfo, textColor: .primary, textLayout: .multiline, textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return nil - }, iconSource: nil, action: { [weak self] c, _ in - c?.dismiss(completion: { - UIPasteboard.general.string = additionalInfo - - self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) - }) - }))) - } - - c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) - }))) - } - - let removeAd = self.controller?.removeAd - if adAttribute.canReport { - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { [weak self] _, f in - f(.default) - - guard let navigationController = self?.controller?.navigationController as? NavigationController else { - return - } - - let _ = (context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil) - |> deliverOnMainQueue).start(next: { [weak navigationController] result in - if case let .options(title, options) = result { - Queue.mainQueue().after(0.2) { - navigationController?.pushViewController( - AdsReportScreen( - context: context, - opaqueId: adAttribute.opaqueId, - title: title, - options: options, - completed: { - // removeAd?(adAttribute.opaqueId) - } - ) - ) - } - } - }) - }))) - - actions.append(.separator) - - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { [weak self] c, _ in - c?.dismiss(completion: { - if context.isPremium { - removeAd?(adAttribute.opaqueId) - } else { - self?.presentNoAdsDemo() - } - }) - }))) - } else { - if !actions.isEmpty { - actions.append(.separator) - } - actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) - }, iconSource: nil, action: { [weak self] c, _ in - c?.dismiss(completion: { - if context.isPremium { - removeAd?(adAttribute.opaqueId) - } else { - self?.presentNoAdsDemo() - } - }) - }))) - } - - let contextController = makeContextController(presentationData: presentationData, source: .reference(AdsInfoContextReferenceContentSource(controller: controller, sourceView: referenceView, insets: .zero, contentInsets: .zero)), items: .single(ContextController.Items(content: .list(actions))), gesture: nil) - controller.presentInGlobalOverlay(contextController) - } - - func presentNoAdsDemo() { - guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { - return - } - let context = controller.context - var replaceImpl: ((ViewController) -> Void)? - let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak demoController] c in - demoController?.replace(with: c) - } - controller.dismiss(animated: true) - Queue.mainQueue().after(0.4) { - navigationController.pushViewController(demoController) - } - } - - func buttonPressed() { - self.controller?.dismiss(animated: true) - } - } - - var node: Node { - return self.displayNode as! Node - } - - private let context: AccountContext - private let mode: Mode - private let message: Message? - private let forceDark: Bool - - private var currentLayout: ContainerViewLayout? - public var removeAd: (Data) -> Void = { _ in } - + public init( context: AccountContext, mode: Mode, - message: Message? = nil, + message: EngineMessage? = nil, forceDark: Bool = false ) { self.context = context self.mode = mode self.message = message self.forceDark = forceDark - - super.init(navigationBarPresentationData: nil) - - self.navigationPresentation = .flatModal + + super.init( + context: context, + component: AdsInfoSheetComponent( + context: context, + mode: mode, + message: message + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: forceDark ? .dark : .default + ) + self.statusBar.statusBarStyle = .Ignore - + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) } - + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - override open func loadDisplayNode() { - self.displayNode = Node(context: self.context, controller: self) - self.displayNodeDidLoad() - + + public override func viewDidLoad() { + super.viewDidLoad() + self.view.disablesInteractiveModalDismiss = true } - - public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - self.view.endEditing(true) - if flag { - self.node.animateOut(completion: { - super.dismiss(animated: false, completion: {}) - completion?() - }) - } else { - super.dismiss(animated: false, completion: {}) - completion?() + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } } - - override open func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - self.node.updateIsVisible(isVisible: true) - } - - override open func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - self.node.updateIsVisible(isVisible: false) - } - - override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.currentLayout = layout - super.containerLayoutUpdated(layout, transition: transition) - - self.node.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) - } -} - -private final class FooterComponent: Component { - let context: AccountContext - let theme: PresentationTheme - let title: String - let showBackground: Bool - let action: () -> Void - - init(context: AccountContext, theme: PresentationTheme, title: String, showBackground: Bool, action: @escaping () -> Void) { - self.context = context - self.theme = theme - self.title = title - self.showBackground = showBackground - self.action = action - } - static func ==(lhs: FooterComponent, rhs: FooterComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.showBackground != rhs.showBackground { - return false - } - return true + fileprivate func displayUndo(_ content: UndoOverlayContent) { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.present( + UndoOverlayController( + presentationData: presentationData, + content: content, + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in + return true + } + ), + in: .current + ) } - final class View: UIView { - let backgroundView: BlurredBackgroundView - let separator = SimpleLayer() - - private let button = ComponentView() - - private var component: FooterComponent? - private weak var state: EmptyComponentState? - - override init(frame: CGRect) { - self.backgroundView = BlurredBackgroundView(color: nil) - - super.init(frame: frame) - - self.backgroundView.clipsToBounds = true - - self.addSubview(self.backgroundView) - self.layer.addSublayer(self.separator) + fileprivate func infoPressed() { + guard + let referenceView = self.node.hostView.findTaggedView(tag: moreTag), + let message = self.message, + let adAttribute = message.adAttribute + else { + return } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: FooterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.component = component - self.state = state - - let bounds = CGRect(origin: .zero, size: availableSize) - - self.backgroundView.updateColor(color: component.theme.rootController.tabBar.backgroundColor, transition: transition.containedViewLayoutTransition) - self.backgroundView.update(size: bounds.size, transition: transition.containedViewLayoutTransition) - transition.setFrame(view: self.backgroundView, frame: bounds) - - self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor - transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel))) - - self.backgroundView.isHidden = !component.showBackground - self.separator.isHidden = !component.showBackground - - let buttonSize = self.button.update( - transition: .immediate, - component: AnyComponent( - SolidRoundedButtonComponent( - title: component.title, - theme: SolidRoundedButtonComponent.Theme(theme: component.theme), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - animationName: nil, - iconPosition: .left, - action: { - component.action() + + let context = self.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + var actions: [ContextMenuItem] = [] + if adAttribute.sponsorInfo != nil || adAttribute.additionalInfo != nil { + actions.append( + .action( + ContextMenuActionItem( + text: presentationData.strings.Chat_ContextMenu_AdSponsorInfo, + textColor: .primary, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.actionSheet.primaryTextColor) + }, + iconSource: nil, + action: { [weak self] c, _ in + var subItems: [ContextMenuItem] = [] + + subItems.append( + .action( + ContextMenuActionItem( + text: presentationData.strings.Common_Back, + textColor: .primary, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, + iconSource: nil, + iconPosition: .left, + action: { c, _ in + c?.popItems() + } + ) + ) + ) + + subItems.append(.separator) + + if let sponsorInfo = adAttribute.sponsorInfo { + subItems.append( + .action( + ContextMenuActionItem( + text: sponsorInfo, + textColor: .primary, + textLayout: .multiline, + textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), + badge: nil, + icon: { _ in + return nil + }, + iconSource: nil, + action: { [weak self] c, _ in + c?.dismiss(completion: { + UIPasteboard.general.string = sponsorInfo + self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) + }) + } + ) + ) + ) + } + + if let additionalInfo = adAttribute.additionalInfo { + subItems.append( + .action( + ContextMenuActionItem( + text: additionalInfo, + textColor: .primary, + textLayout: .multiline, + textFont: .custom(font: Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 0.8)), height: nil, verticalOffset: nil), + badge: nil, + icon: { _ in + return nil + }, + iconSource: nil, + action: { [weak self] c, _ in + c?.dismiss(completion: { + UIPasteboard.general.string = additionalInfo + self?.displayUndo(.copy(text: presentationData.strings.Chat_ContextMenu_AdSponsorInfoCopied)) + }) + } + ) + ) + ) + } + + c?.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) } ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - 32.0, height: availableSize.height) + ) ) - - if let view = self.button.view { - if view.superview == nil { - self.addSubview(view) - } - let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: buttonSize) - view.frame = buttonFrame + } + + let removeAd = self.removeAd + if adAttribute.canReport { + actions.append( + .action( + ContextMenuActionItem( + text: presentationData.strings.Chat_ContextMenu_ReportAd, + textColor: .primary, + textLayout: .twoLinesMax, + textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), + badge: nil, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) + }, + iconSource: nil, + action: { [weak self] _, f in + f(.default) + + guard let navigationController = self?.navigationController as? NavigationController else { + return + } + + let _ = ( + context.engine.messages.reportAdMessage(opaqueId: adAttribute.opaqueId, option: nil) + |> deliverOnMainQueue + ).start(next: { [weak navigationController] result in + if case let .options(title, options) = result { + Queue.mainQueue().after(0.2) { + navigationController?.pushViewController( + AdsReportScreen( + context: context, + opaqueId: adAttribute.opaqueId, + title: title, + options: options, + completed: { + } + ) + ) + } + } + }) + } + ) + ) + ) + + actions.append(.separator) + + actions.append( + .action( + ContextMenuActionItem( + text: presentationData.strings.Chat_ContextMenu_RemoveAd, + textColor: .primary, + textLayout: .twoLinesMax, + textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), + badge: nil, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, + iconSource: nil, + action: { [weak self] c, _ in + c?.dismiss(completion: { + if context.isPremium { + removeAd(adAttribute.opaqueId) + } else { + self?.presentNoAdsDemo() + } + }) + } + ) + ) + ) + } else { + if !actions.isEmpty { + actions.append(.separator) } - - return availableSize + actions.append( + .action( + ContextMenuActionItem( + text: presentationData.strings.SponsoredMessageMenu_Hide, + textColor: .primary, + textLayout: .twoLinesMax, + textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), + badge: nil, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, + iconSource: nil, + action: { [weak self] c, _ in + c?.dismiss(completion: { + if context.isPremium { + removeAd(adAttribute.opaqueId) + } else { + self?.presentNoAdsDemo() + } + }) + } + ) + ) + ) + } + + let contextController = makeContextController( + presentationData: presentationData, + source: .reference( + AdsInfoContextReferenceContentSource( + controller: self, + sourceView: referenceView, + insets: .zero, + contentInsets: .zero + ) + ), + items: .single(ContextController.Items(content: .list(actions))), + gesture: nil + ) + self.presentInGlobalOverlay(contextController) + } + + fileprivate func presentNoAdsDemo() { + guard let navigationController = self.navigationController as? NavigationController else { + return + } + + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let demoController = context.sharedContext.makePremiumDemoController( + context: context, + subject: .noAds, + forceDark: false, + action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, + dismissed: nil + ) + replaceImpl = { [weak demoController] controller in + demoController?.replace(with: controller) + } + + self.dismissAnimated() + Queue.mainQueue().after(0.4) { + navigationController.pushViewController(demoController) } } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - -private func generateMoreButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { - return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(backgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(foregroundColor.cgColor) - - let circleSize = CGSize(width: 4.0, height: 4.0) - context.fillEllipse(in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.height - circleSize.width) / 2.0), y: floorToScreenPixels((size.height - circleSize.height) / 2.0)), size: circleSize)) - - context.fillEllipse(in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.height - circleSize.width) / 2.0) - circleSize.width - 3.0, y: floorToScreenPixels((size.height - circleSize.height) / 2.0)), size: circleSize)) - - context.fillEllipse(in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.height - circleSize.width) / 2.0) + circleSize.width + 3.0, y: floorToScreenPixels((size.height - circleSize.height) / 2.0)), size: circleSize)) - }) } private final class AdsInfoContextReferenceContentSource: ContextReferenceContentSource { @@ -1585,15 +1024,19 @@ private final class AdsInfoContextReferenceContentSource: ContextReferenceConten let sourceView: UIView let insets: UIEdgeInsets let contentInsets: UIEdgeInsets - + init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { self.controller = controller self.sourceView = sourceView self.insets = insets self.contentInsets = contentInsets } - + func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) + return ContextControllerReferenceViewInfo( + referenceView: self.sourceView, + contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), + insets: self.contentInsets + ) } } diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD index 1b071b8cfd..7d5de96873 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", @@ -20,6 +19,7 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/Components/BundleIconComponent", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/AppBundle", @@ -31,6 +31,7 @@ swift_library( "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", "//submodules/TelegramUI/Components/NavigationStackComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift index 86441c6117..ab0770cf96 100644 --- a/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift +++ b/submodules/TelegramUI/Components/Ads/AdsReportScreen/Sources/AdsReportScreen.swift @@ -17,6 +17,8 @@ import NavigationStackComponent import ItemListUI import UndoUI import AccountContext +import BundleIconComponent +import GlassBarButtonComponent private enum ReportResult { case reported @@ -37,22 +39,19 @@ private final class SheetPageContent: CombinedComponent { let subtitle: String let items: [Item] let action: (Item) -> Void - let pop: () -> Void init( context: AccountContext, title: String?, subtitle: String, items: [Item], - action: @escaping (Item) -> Void, - pop: @escaping () -> Void + action: @escaping (Item) -> Void ) { self.context = context self.title = title self.subtitle = subtitle self.items = items self.action = action - self.pop = pop } static func ==(lhs: SheetPageContent, rhs: SheetPageContent) -> Bool { @@ -72,7 +71,6 @@ private final class SheetPageContent: CombinedComponent { } final class State: ComponentState { - var backArrowImage: (UIImage, PresentationTheme)? } func makeState() -> State { @@ -80,8 +78,7 @@ private final class SheetPageContent: CombinedComponent { } static var body: Body { - let background = Child(RoundedRectangle.self) - let back = Child(Button.self) + let background = Child(Rectangle.self) let title = Child(Text.self) let subtitle = Child(MultilineTextComponent.self) let section = Child(ListSectionComponent.self) @@ -89,7 +86,6 @@ private final class SheetPageContent: CombinedComponent { return { context in let environment = context.environment[EnvironmentType.self] let component = context.component - let state = context.state let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let theme = environment.theme @@ -97,10 +93,10 @@ private final class SheetPageContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left - var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + var contentSize = CGSize(width: context.availableSize.width, height: 26.0) let background = background.update( - component: RoundedRectangle(color: theme.list.modalBlocksBackgroundColor, cornerRadius: 8.0), + component: Rectangle(color: theme.list.modalBlocksBackgroundColor), availableSize: CGSize(width: context.availableSize.width, height: 1000.0), transition: .immediate ) @@ -108,73 +104,38 @@ private final class SheetPageContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) ) - let backArrowImage: UIImage - if let (cached, cachedTheme) = state.backArrowImage, cachedTheme === theme { - backArrowImage = cached - } else { - backArrowImage = NavigationBarTheme.generateBackArrowImage(color: theme.list.itemAccentColor)! - state.backArrowImage = (backArrowImage, theme) - } - - let backContents: AnyComponent - if component.title == nil { - backContents = AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.list.itemAccentColor)) - } else { - backContents = AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "arrow", component: AnyComponent(Image(image: backArrowImage, contentMode: .center))), - AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: strings.Common_Back, font: Font.regular(17.0), color: theme.list.itemAccentColor))) - ], spacing: 6.0) - ) - } - let back = back.update( - component: Button( - content: backContents, - action: { - component.pop() - } - ), - availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height), - transition: .immediate - ) - context.add(back - .position(CGPoint(x: sideInset + back.size.width / 2.0 - (component.title != nil ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0)) - ) - - let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0 + let constrainedTitleWidth = context.availableSize.width - 60.0 * 2.0 let title = title.update( component: Text(text: strings.ReportAd_Title, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor), availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), transition: .immediate ) + context.add(title + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + ) + contentSize.height += title.size.height + if let subtitleText = component.title { let subtitle = subtitle.update( component: MultilineTextComponent(text: .plain(NSAttributedString(string: subtitleText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor)), truncationType: .end, maximumNumberOfLines: 1), availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height), transition: .immediate ) - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0 - 8.0)) - ) - contentSize.height += title.size.height context.add(subtitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + subtitle.size.height / 2.0 - 9.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + 4.0 + subtitle.size.height / 2.0)) ) - contentSize.height += subtitle.size.height - contentSize.height += 8.0 - } else { - context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) - ) - contentSize.height += title.size.height + contentSize.height += 4.0 + subtitle.size.height contentSize.height += 24.0 + } else { + contentSize.height += 40.0 } var items: [AnyComponentWithIdentity] = [] for item in component.items { items.append(AnyComponentWithIdentity(id: item.title, component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -195,6 +156,7 @@ private final class SheetPageContent: CombinedComponent { let section = section.update( component: ListSectionComponent( theme: theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: component.subtitle.uppercased(), @@ -315,6 +277,7 @@ private final class SheetContent: CombinedComponent { static var body: Body { let navigation = Child(NavigationStackComponent.self) + let backButton = Child(GlassBarButtonComponent.self) return { context in let environment = context.environment[EnvironmentType.self] @@ -360,9 +323,6 @@ private final class SheetContent: CombinedComponent { }, action: { item in action(item) - }, - pop: { - component.dismiss() } ) ))) @@ -377,10 +337,6 @@ private final class SheetContent: CombinedComponent { }, action: { item in action(item) - }, - pop: { [weak state] in - state?.pushedOptions.removeLast() - update(.spring(duration: 0.45)) } ) ))) @@ -402,10 +358,39 @@ private final class SheetContent: CombinedComponent { ) context.add(navigation .position(CGPoint(x: context.availableSize.width / 2.0, y: navigation.size.height / 2.0)) - .clipsToBounds(true) - .cornerRadius(8.0) ) contentSize.height += navigation.size.height + + let isBack = items.count > 1 + let barButtonSize = CGSize(width: 44.0, height: 44.0) + let backButton = backButton.update( + component: GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( + BundleIconComponent( + name: isBack ? "Navigation/Back" : "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak state] _ in + if isBack { + state?.pushedOptions.removeLast() + update(.spring(duration: 0.45)) + } else { + component.dismiss() + } + } + ), + environment: {}, + availableSize: barButtonSize, + transition: context.transition + ) + context.add(backButton + .position(CGPoint(x: 16.0 + backButton.size.width / 2.0, y: 16.0 + backButton.size.height / 2.0)) + ) return contentSize } @@ -495,8 +480,10 @@ private final class SheetContainerComponent: CombinedComponent { state?.updated(transition: transition) } )), + style: .glass, backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), followContentSizeChanges: true, + clipsContent: true, externalState: sheetExternalState, animateOut: animateOut ), diff --git a/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/BUILD similarity index 55% rename from submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD rename to submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/BUILD index b4680d882d..bfe177cde4 100644 --- a/submodules/TelegramUI/Components/TimeSelectionActionSheet/BUILD +++ b/submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/BUILD @@ -1,8 +1,8 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") swift_library( - name = "TimeSelectionActionSheet", - module_name = "TimeSelectionActionSheet", + name = "AlertHeaderComponent", + module_name = "AlertHeaderComponent", srcs = glob([ "Sources/**/*.swift", ]), @@ -10,14 +10,11 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Display", "//submodules/AsyncDisplayKit", - "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/TelegramCore", + "//submodules/Display", + "//submodules/ComponentFlow", "//submodules/TelegramPresentationData", - "//submodules/TelegramStringFormatting", - "//submodules/AccountContext", - "//submodules/UIKitRuntimeUtils", + "//submodules/TelegramUI/Components/AlertComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/Sources/AlertHeaderComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/Sources/AlertHeaderComponent.swift new file mode 100644 index 0000000000..31d3c1d9e7 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent/Sources/AlertHeaderComponent.swift @@ -0,0 +1,64 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramPresentationData +import AlertComponent + +public final class AlertHeaderComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let component: AnyComponentWithIdentity + + public init( + component: AnyComponentWithIdentity + ) { + self.component = component + } + + public static func ==(lhs: AlertHeaderComponent, rhs: AlertHeaderComponent) -> Bool { + if lhs.component != rhs.component { + return false + } + return true + } + + public final class View: UIView { + private let componentView = ComponentView() + + private var component: AlertHeaderComponent? + private weak var state: EmptyComponentState? + + func update(component: AlertHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let size: CGSize = CGSize(width: 80.0, height: 80.0) + + let componentSize = self.componentView.update( + transition: transition, + component: component.component.component, + environment: {}, + containerSize: size + ) + let frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - componentSize.width) / 2.0), y: 0.0), size: componentSize) + if let componentView = self.componentView.view { + if componentView.superview == nil { + self.addSubview(componentView) + } + transition.setFrame(view: componentView, frame: frame) + } + + return CGSize(width: availableSize.width, height: size.height + 11.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift index 49f9b1e7d7..1d484546b6 100644 --- a/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift +++ b/submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent/Sources/AlertMultilineInputFieldComponent.swift @@ -19,6 +19,7 @@ public final class AlertMultilineInputFieldComponent: Component { public fileprivate(set) var value: NSAttributedString = NSAttributedString() public fileprivate(set) var animateError: () -> Void = {} public fileprivate(set) var activateInput: () -> Void = {} + fileprivate var setValueImpl: ((NSAttributedString, Range) -> Void)? fileprivate let valuePromise = ValuePromise(NSAttributedString()) public var valueSignal: Signal { return self.valuePromise.get() @@ -32,6 +33,13 @@ public final class AlertMultilineInputFieldComponent: Component { public init() { } + + public func setValue(_ value: NSAttributedString, selectionRange: Range? = nil) { + let selectionRange = selectionRange ?? (value.length ..< value.length) + self.value = value + self.valuePromise.set(value) + self.setValueImpl?(value, selectionRange) + } } public enum FormatMenuAvailability: Equatable { @@ -226,13 +234,18 @@ public final class AlertMultilineInputFieldComponent: Component { func update(component: AlertMultilineInputFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { var resetText: NSAttributedString? if self.component == nil { - resetText = component.initialValue + resetText = component.initialValue ?? (component.externalState.value.length == 0 ? nil : component.externalState.value) component.externalState.animateError = { [weak self] in self?.animateError() } component.externalState.activateInput = { [weak self] in self?.activateInput() } + component.externalState.setValueImpl = { [weak self] value, selectionRange in + if let textFieldView = self?.textField.view as? TextFieldComponent.View { + textFieldView.updateText(value, selectionRange: selectionRange) + } + } } let isFirstTime = self.component == nil diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/BUILD b/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/BUILD new file mode 100644 index 0000000000..4bb3e82d62 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AlertWebpagePreviewComponent", + module_name = "AlertWebpagePreviewComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TextFormat", + "//submodules/AccountContext", + "//submodules/WebsiteType", + "//submodules/UrlHandling", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatHistoryEntry", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/WallpaperPreviewMedia", + "//submodules/ChatPresentationInterfaceState", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/Sources/AlertWebpagePreviewComponent.swift b/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/Sources/AlertWebpagePreviewComponent.swift new file mode 100644 index 0000000000..948f5eac49 --- /dev/null +++ b/submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent/Sources/AlertWebpagePreviewComponent.swift @@ -0,0 +1,590 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import AccountContext +import WebsiteType +import UrlHandling +import AlertComponent +import ChatMessageAttachedContentNode +import ChatMessageBubbleContentNode +import ChatHistoryEntry +import ChatMessageItemCommon +import ChatControllerInteraction +import WallpaperPreviewMedia +import ChatPresentationInterfaceState + +private struct AlertWebpagePreviewContent { + var title: String? + var subtitle: NSAttributedString? + var text: String? + var entities: [MessageTextEntity]? + var mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? + var mediaBadge: String? +} + +public final class AlertWebpagePreviewComponent: Component { + public typealias EnvironmentType = AlertComponentEnvironment + + let context: AccountContext + let presentationData: PresentationData + let webpage: TelegramMediaWebpage + let peer: EnginePeer? + + public init( + context: AccountContext, + presentationData: PresentationData, + webpage: TelegramMediaWebpage, + peer: EnginePeer? = nil + ) { + self.context = context + self.presentationData = presentationData + self.webpage = webpage + self.peer = peer + } + + public static func ==(lhs: AlertWebpagePreviewComponent, rhs: AlertWebpagePreviewComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.presentationData != rhs.presentationData { + return false + } + if lhs.webpage != rhs.webpage { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + public final class View: UIView { + private let contentNode: ChatMessageAttachedContentNode + + private var component: AlertWebpagePreviewComponent? + private var controllerInteraction: ChatControllerInteraction? + private weak var controllerInteractionContext: AccountContext? + + override init(frame: CGRect) { + self.contentNode = ChatMessageAttachedContentNode() + self.contentNode.isUserInteractionEnabled = false + + super.init(frame: frame) + + self.addSubview(self.contentNode.view) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: AlertWebpagePreviewComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + guard case let .Loaded(webpageContent) = component.webpage.content else { + return CGSize(width: availableSize.width, height: 0.0) + } + + let environment = environment[AlertComponentEnvironment.self] + let presentationData = ChatPresentationData( + theme: ChatPresentationThemeData( + theme: environment.theme, + wallpaper: component.presentationData.chatWallpaper + ), + fontSize: component.presentationData.chatFontSize, + strings: environment.strings, + dateTimeFormat: component.presentationData.dateTimeFormat, + nameDisplayOrder: component.presentationData.nameDisplayOrder, + disableAnimations: true, + largeEmoji: component.presentationData.largeEmoji, + chatBubbleCorners: PresentationChatBubbleCorners( + mainRadius: component.presentationData.chatBubbleCorners.mainRadius, + auxiliaryRadius: component.presentationData.chatBubbleCorners.auxiliaryRadius, + mergeBubbleCorners: component.presentationData.chatBubbleCorners.mergeBubbleCorners, + hasTails: false + ), + animatedEmojiScale: 1.0, + isPreview: true + ) + + let controllerInteraction: ChatControllerInteraction + if let current = self.controllerInteraction, self.controllerInteractionContext === component.context { + controllerInteraction = current + } else { + controllerInteraction = ChatControllerInteraction( + openMessage: { _, _ in + return false + }, + openPeer: { _, _, _, _ in }, + openPeerMention: { _, _ in }, + openMessageContextMenu: { _, _, _, _, _, _ in }, + openMessageReactionContextMenu: { _, _, _, _ in }, + updateMessageReaction: { _, _, _, _ in }, + activateMessagePinch: { _ in }, + openMessageContextActions: { _, _, _, _ in }, + navigateToMessage: { _, _, _ in }, + navigateToMessageStandalone: { _ in }, + navigateToThreadMessage: { _, _, _ in }, + tapMessage: nil, + clickThroughMessage: { _, _ in }, + toggleMessagesSelection: { _, _ in }, + sendCurrentMessage: { _, _ in }, + sendMessage: { _, _ in }, + sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, + sendEmoji: { _, _, _ in }, + sendGif: { _, _, _, _, _ in return false }, + sendBotContextResultAsGif: { _, _, _, _, _, _ in + return false + }, + editGif: { _, _ in }, + requestMessageActionCallback: { _, _, _, _, _ in }, + requestMessageActionUrlAuth: { _, _ in }, + activateSwitchInline: { _, _, _ in }, + openUrl: { _ in }, + openExternalInstantPage: { _ in }, + shareCurrentLocation: { _ in }, + shareAccountContact: { _ in }, + sendBotCommand: { _, _ in }, + openInstantPage: { _, _ in }, + openWallpaper: { _ in }, + openTheme: { _ in }, + openHashtag: { _, _ in }, + updateInputState: { _ in }, + updateInputMode: { _ in }, + updatePresentationState: { _ in }, + openMessageShareMenu: { _ in }, + presentController: { _, _ in }, + presentControllerInCurrent: { _, _ in }, + navigationController: { + return nil + }, + chatControllerNode: { + return nil + }, + presentGlobalOverlayController: { _, _ in }, + callPeer: { _, _ in }, + openConferenceCall: { _ in }, + longTap: { _, _ in }, + todoItemLongTap: { _, _ in }, + pollOptionLongTap: { _, _ in }, + openCheckoutOrReceipt: { _, _ in }, + openSearch: {}, + setupReply: { _ in }, + canSetupReply: { _ in + return .none + }, + canSendMessages: { + return false + }, + navigateToFirstDateMessage: { _, _ in }, + requestRedeliveryOfFailedMessages: { _ in }, + addContact: { _ in }, + rateCall: { _, _, _ in }, + requestSelectMessagePollOptions: { _, _ in }, + requestAddMessagePollOption: { _, _, _, _, _ in }, + requestOpenMessagePollResults: { _, _ in }, + openAppStorePage: {}, + displayMessageTooltip: { _, _, _, _, _ in }, + seekToTimecode: { _, _, _ in }, + scheduleCurrentMessage: { _ in }, + sendScheduledMessagesNow: { _ in }, + editScheduledMessagesTime: { _ in }, + performTextSelectionAction: { _, _, _, _, _ in }, + displayImportedMessageTooltip: { _ in }, + displaySwipeToReplyHint: {}, + dismissReplyMarkupMessage: { _ in }, + openMessagePollResults: { _, _ in }, + openPollCreation: { _, _ in }, + openPollMedia: { _, _ in }, + displayPollSolution: { _, _ in }, + displayPsa: { _, _ in }, + displayDiceTooltip: { _ in }, + animateDiceSuccess: { _, _ in }, + displayPremiumStickerTooltip: { _, _ in }, + displayEmojiPackTooltip: { _, _ in }, + openPeerContextMenu: { _, _, _, _, _ in }, + openMessageReplies: { _, _, _ in }, + openReplyThreadOriginalMessage: { _ in }, + openMessageStats: { _ in }, + editMessageMedia: { _, _ in }, + copyText: { _ in }, + displayUndo: { _ in }, + isAnimatingMessage: { _ in + return false + }, + getMessageTransitionNode: { + return nil + }, + updateChoosingSticker: { _ in }, + commitEmojiInteraction: { _, _, _, _ in }, + openLargeEmojiInfo: { _, _, _ in }, + openJoinLink: { _ in }, + openWebView: { _, _, _, _ in }, + activateAdAction: { _, _, _, _ in }, + adContextAction: { _, _, _ in }, + removeAd: { _ in }, + openRequestedPeerSelection: { _, _, _, _ in }, + saveMediaToFiles: { _ in }, + openNoAdsDemo: {}, + openAdsInfo: {}, + displayGiveawayParticipationStatus: { _ in }, + openPremiumStatusInfo: { _, _, _, _ in }, + openRecommendedChannelContextMenu: { _, _, _ in }, + openGroupBoostInfo: { _, _ in }, + openStickerEditor: {}, + openAgeRestrictedMessageMedia: { _, _ in }, + playMessageEffect: { _ in }, + editMessageFactCheck: { _ in }, + sendGift: { _ in }, + openUniqueGift: { _ in }, + openMessageFeeException: {}, + requestMessageUpdate: { _, _, _ in }, + cancelInteractiveKeyboardGestures: {}, + dismissTextInput: {}, + scrollToMessageId: { _, _ in }, + scrollToMessageIdWithAnchor: { _, _ in }, + navigateToStory: { _, _ in }, + attemptedNavigationToPrivateQuote: { _ in }, + forceUpdateWarpContents: {}, + playShakeAnimation: {}, + displayQuickShare: { _, _, _ in }, + updateChatLocationThread: { _, _ in }, + requestToggleTodoMessageItem: { _, _, _ in }, + displayTodoToggleUnavailable: { _ in }, + openStarsPurchase: { _ in }, + openRankInfo: { _, _, _ in }, + openSetPeerAvatar: {}, + displayPollRestrictedToast: { _ in }, + automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + pollActionState: ChatInterfacePollActionState(), + stickerSettings: ChatInterfaceStickerSettings(), + presentationContext: ChatPresentationContext(context: component.context, backgroundNode: nil) + ) + self.controllerInteraction = controllerInteraction + self.controllerInteractionContext = component.context + } + + let author = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + let message = EngineRawMessage( + stableId: 0, + stableVersion: 0, + id: EngineMessage.Id(peerId: author.id, namespace: Namespaces.Message.Local, id: 0), + globallyUniqueId: nil, + groupingKey: nil, + groupInfo: nil, + threadId: nil, + timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), + flags: [.Incoming], + tags: [], + globalTags: [], + localTags: [], + customTags: [], + forwardInfo: nil, + author: component.peer?._asPeer() ?? author, + text: "", + attributes: [], + media: [component.webpage], + peers: EngineSimpleDictionary(), + associatedMessages: EngineSimpleDictionary(), + associatedMessageIds: [], + associatedMedia: [:], + associatedThreadInfo: nil, + associatedStories: [:] + ) + let associatedData = ChatMessageItemAssociatedData( + automaticDownloadPeerType: .contact, + automaticDownloadPeerId: nil, + automaticDownloadNetworkType: .cellular, + isRecentActions: false, + subject: nil, + contactsPeerIds: Set(), + animatedEmojiStickers: [:], + forcedResourceStatus: nil, + availableReactions: nil, + availableMessageEffects: nil, + savedMessageTags: nil, + defaultReaction: nil, + areStarReactionsEnabled: false, + isPremium: false, + accountPeer: nil, + forceInlineReactions: true, + isStandalone: true + ) + let previewContent = makeAlertWebpagePreviewContent( + context: component.context, + presentationData: presentationData, + automaticDownloadSettings: controllerInteraction.automaticMediaDownloadSettings, + associatedData: associatedData, + message: message, + webpage: webpageContent + ) + + let constrainedWidth = max(1.0, availableSize.width + 50.0) + let constrainedSize = CGSize(width: constrainedWidth, height: 10000.0) + let contentNodeLayout = self.contentNode.asyncLayout() + let position = ChatMessageBubbleRelativePosition.None(.None(.Incoming)) + + let (initialWidth, continueLayout) = contentNodeLayout( + presentationData, + controllerInteraction.automaticMediaDownloadSettings, + associatedData, + ChatMessageEntryAttributes(), + component.context, + controllerInteraction, + message, + true, + .peer(id: message.id.peerId), + previewContent.title, + nil, + previewContent.subtitle, + previewContent.text, + previewContent.entities, + previewContent.mediaAndFlags, + previewContent.mediaBadge, + nil, + nil, + true, + ChatMessageItemLayoutConstants.compact, + .linear(top: position, bottom: position), + constrainedSize, + controllerInteraction.presentationContext.animationCache, + controllerInteraction.presentationContext.animationRenderer + ) + let (refinedWidth, finalizeLayout) = continueLayout(constrainedSize, .linear(top: position, bottom: position)) + let boundingWidth = min(constrainedWidth, max(initialWidth, refinedWidth)) + let (contentSize, apply) = finalizeLayout(boundingWidth) + apply(.None, true, nil) + + let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -10.0), size: contentSize) + transition.setFrame(view: self.contentNode.view, frame: contentFrame) + self.contentNode.visibility = .visible(1.0, CGRect(origin: CGPoint(), size: contentSize)) + + return CGSize(width: availableSize.width, height: contentSize.height - 18.0) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private func makeAlertWebpagePreviewContent( + context: AccountContext, + presentationData: ChatPresentationData, + automaticDownloadSettings: MediaAutoDownloadSettings, + associatedData: ChatMessageItemAssociatedData, + message: EngineRawMessage, + webpage: TelegramMediaWebpageLoadedContent +) -> AlertWebpagePreviewContent { + var result = AlertWebpagePreviewContent() + let type = websiteType(of: webpage.websiteName) + + if let websiteName = webpage.websiteName, !websiteName.isEmpty { + result.title = websiteName + } + + if let title = webpage.title, !title.isEmpty { + result.subtitle = NSAttributedString(string: title, font: Font.semibold(15.0)) + } + + if let textValue = webpage.text, !textValue.isEmpty { + result.text = textValue + var entityTypes: EnabledEntityTypes = [.allUrl] + switch type { + case .twitter, .instagram: + entityTypes.insert(.mention) + entityTypes.insert(.hashtag) + entityTypes.insert(.external) + default: + break + } + result.entities = generateTextEntities(textValue, enabledTypes: entityTypes) + } + + var mainMedia: EngineRawMedia? + var automaticPlayback = false + if let file = webpage.file, (file.isAnimated && context.sharedContext.energyUsageSettings.autoplayGif) || (!file.isAnimated && context.sharedContext.energyUsageSettings.autoplayVideo) { + if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, authorPeerId: message.author?.id, contactsPeerIds: associatedData.contactsPeerIds, media: file) { + automaticPlayback = true + } else { + automaticPlayback = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.resource.id)) != nil + } + } + + switch type { + case .instagram, .twitter: + if automaticPlayback { + mainMedia = webpage.story ?? webpage.file ?? webpage.image + } else { + mainMedia = webpage.story ?? webpage.image ?? webpage.file + } + default: + mainMedia = webpage.story ?? webpage.file ?? webpage.image + } + + let themeMimeType = "application/x-tgtheme-ios" + + switch webpage.type { + case "telegram_background": + var colors: [UInt32] = [] + var rotation: Int32? + if let wallpaper = parseWallpaperUrl(sharedContext: context.sharedContext, url: webpage.url) { + if case let .color(color) = wallpaper { + colors = [color.rgb] + } else if case let .gradient(colorsValue, rotationValue) = wallpaper { + colors = colorsValue + rotation = rotationValue + } + } + + var content: WallpaperPreviewMediaContent? + if !colors.isEmpty { + if colors.count >= 2 { + content = .gradient(colors, rotation) + } else { + content = .color(UIColor(rgb: colors[0])) + } + } + if let content { + let media = WallpaperPreviewMedia(content: content) + result.mediaAndFlags = ([media], []) + } + case "telegram_theme": + var file: TelegramMediaFile? + var settings: TelegramThemeSettings? + var isSupported = false + + for attribute in webpage.attributes { + if case let .theme(attribute) = attribute { + if let attributeSettings = attribute.settings { + settings = attributeSettings + isSupported = true + } else if let filteredFile = attribute.files.filter({ $0.mimeType == themeMimeType }).first { + file = filteredFile + isSupported = true + } + } + } + + if !isSupported, let contentFile = webpage.file { + isSupported = true + file = contentFile + } + if let file { + let media = WallpaperPreviewMedia(content: .file(file: file, colors: [], rotation: nil, intensity: nil, true, isSupported)) + result.mediaAndFlags = ([media], []) + } else if let settings { + let media = WallpaperPreviewMedia(content: .themeSettings(settings)) + result.mediaAndFlags = ([media], []) + } + case "telegram_nft": + for attribute in webpage.attributes { + if case let .starGift(gift) = attribute, case let .unique(uniqueGift) = gift.gift { + let media = UniqueGiftPreviewMedia(content: uniqueGift) + result.mediaAndFlags = ([media], []) + break + } + } + case "telegram_auction": + for attribute in webpage.attributes { + if case let .giftAuction(giftAuction) = attribute, case let .generic(gift) = giftAuction.gift { + let media = GiftAuctionPreviewMedia(content: gift, endTime: giftAuction.endDate) + result.mediaAndFlags = ([media], []) + break + } + } + default: + if var file = mainMedia as? TelegramMediaFile, webpage.type != "telegram_theme" { + if webpage.imageIsVideoCover, let image = webpage.image { + file = file.withUpdatedVideoCover(image) + } + + if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty { + if automaticPlayback { + result.mediaAndFlags = ([file], [.preferMediaBeforeText]) + } else { + result.mediaAndFlags = ([webpage.image ?? file], [.preferMediaBeforeText]) + } + } else if webpage.type == "telegram_background" { + var colors: [UInt32] = [] + var rotation: Int32? + var intensity: Int32? + if let wallpaper = parseWallpaperUrl(sharedContext: context.sharedContext, url: webpage.url), case let .slug(_, _, colorsValue, intensityValue, rotationValue) = wallpaper { + colors = colorsValue + rotation = rotationValue + intensity = intensityValue + } + let media = WallpaperPreviewMedia(content: .file(file: file, colors: colors, rotation: rotation, intensity: intensity, false, false)) + result.mediaAndFlags = ([media], [.preferMediaAspectFilled]) + if let fileSize = file.size { + result.mediaBadge = dataSizeString(fileSize, formatting: DataSizeStringFormatting(chatPresentationData: presentationData)) + } + } else { + result.mediaAndFlags = ([file], []) + } + } else if let image = mainMedia as? TelegramMediaImage { + if let type = webpage.type, ["photo", "video", "embed", "gif", "document", "telegram_album"].contains(type) { + var flags = ChatMessageAttachedContentNodeMediaFlags() + if webpage.instantPage != nil, let largest = largestImageRepresentation(image.representations) { + if largest.dimensions.width >= 256 { + flags.insert(.preferMediaBeforeText) + } + } else if let embedUrl = webpage.embedUrl, !embedUrl.isEmpty { + flags.insert(.preferMediaBeforeText) + } + result.mediaAndFlags = ([image], flags) + } else if largestImageRepresentation(image.representations)?.dimensions != nil { + result.mediaAndFlags = ([image], []) + } + } else if let story = mainMedia as? TelegramMediaStory { + result.mediaAndFlags = ([story], [.preferMediaBeforeText, .titleBeforeMedia]) + } + } + + switch webpage.type { + case "telegram_background": + result.title = presentationData.strings.Conversation_ChatBackground + result.subtitle = nil + result.text = nil + result.entities = nil + case "telegram_theme": + result.title = presentationData.strings.Conversation_Theme + result.text = nil + result.entities = nil + case "telegram_nft", "telegram_auction": + result.text = nil + result.entities = nil + default: + break + } + + for attribute in webpage.attributes { + if case let .stickerPack(stickerPack) = attribute, !stickerPack.files.isEmpty { + result.mediaAndFlags = (stickerPack.files, [.preferMediaInline, .stickerPack]) + break + } else if case let .giftCollection(giftCollection) = attribute, !giftCollection.files.isEmpty { + result.mediaAndFlags = (giftCollection.files, [.preferMediaInline, .stickerPack]) + break + } + } + + if defaultWebpageImageSizeIsSmall(webpage: webpage) { + result.mediaAndFlags?.1.insert(.preferMediaInline) + } + + if let isMediaLargeByDefault = webpage.isMediaLargeByDefault, !isMediaLargeByDefault { + result.mediaAndFlags?.1.insert(.preferMediaInline) + } + + return result +} diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift index 900b1c44c4..2cc5f0ef27 100644 --- a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertComponent.swift @@ -381,13 +381,30 @@ private final class AlertScreenComponent: Component { if itemView.superview == nil { self.backgroundView.contentView.addSubview(itemView) item.parentState = state + + transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) } - transition.setFrame(view: itemView, frame: itemFrame) + itemTransition.setFrame(view: itemView, frame: itemFrame) } contentOriginY += itemSize.height } } + var removeKeys: [AnyHashable] = [] + for key in self.contentItems.keys { + if !validContentIds.contains(key) { + removeKeys.append(key) + if let itemView = self.contentItems[key]?.view { + transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in + itemView.removeFromSuperview() + }) + } + } + } + for key in removeKeys { + self.contentItems.removeValue(forKey: key) + } + if !contentOriginY.isZero { alertHeight += contentOriginY alertHeight += contentBottomInset @@ -407,9 +424,9 @@ private final class AlertScreenComponent: Component { font: .bold ) let destructiveActionTheme = AlertActionComponent.Theme( - background: environment.theme.list.itemDestructiveColor, - foreground: .white, - secondary: .white.withMultipliedAlpha(0.6), + background: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1), + foreground: environment.theme.list.itemDestructiveColor, + secondary: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.6), font: .regular ) let defaultDestructiveActionTheme = AlertActionComponent.Theme( diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift index 4f2312a393..231d46cbbe 100644 --- a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift @@ -323,23 +323,23 @@ public final class AlertTextComponent: Component { } var size = CGSize(width: availableSize.width, height: textSize.height) if case .background = component.style { - let backgroundSize = CGSize(width: availableSize.width + 20.0, height: textSize.height + backgroundInset * 2.0) + let backgroundSize = CGSize(width: availableSize.width + 20.0 + component.insets.left + component.insets.right, height: textSize.height + backgroundInset * 2.0) size = backgroundSize - textOffset = CGPoint(x: textOffset.x, y: backgroundInset) + textOffset = CGPoint(x: textOffset.x, y: backgroundInset + 1.0) let _ = self.background.update( transition: transition, component: AnyComponent( FilledRoundedRectangleComponent( color: textColor.withMultipliedAlpha(0.1), - cornerRadius: .value(10.0), + cornerRadius: .value(12.0), smoothCorners: true ) ), environment: {}, containerSize: backgroundSize ) - let backgroundFrame = CGRect(origin: CGPoint(x: -10.0, y: component.insets.top), size: backgroundSize) + let backgroundFrame = CGRect(origin: CGPoint(x: -10.0 - component.insets.left, y: component.insets.top), size: backgroundSize) if let backgroundView = self.background.view { if backgroundView.superview == nil { self.addSubview(backgroundView) diff --git a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift index cd85867620..cc9cf65b6a 100644 --- a/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift +++ b/submodules/TelegramUI/Components/AsyncListComponent/Sources/AsyncListComponent.swift @@ -576,7 +576,7 @@ public final class AsyncListComponent: Component { case let .curve(duration, curve): updateSizeAndInsets.duration = duration switch curve { - case .linear, .easeInOut: + case .linear, .easeInOut, .easeIn: updateSizeAndInsets.curve = .Default(duration: duration) case .spring: updateSizeAndInsets.curve = .Spring(duration: duration) diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index e387d05658..23f3df50ec 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -32,14 +31,14 @@ private final class AttachmentFileControllerArguments { let scanDocument: () -> Void let expandSavedMusic: () -> Void let expandRecentMusic: () -> Void - let send: (Message) -> Void - let toggleMediaPlayback: (Message) -> Void + let send: (EngineRawMessage) -> Void + let toggleMediaPlayback: (EngineRawMessage) -> Void let isSelectionActive: () -> Bool - let toggleMessageSelection: (Message) -> Void - let setMessageSelection: ([MessageId], Message?, Bool) -> Void + let toggleMessageSelection: (EngineRawMessage) -> Void + let setMessageSelection: ([EngineMessage.Id], EngineRawMessage?, Bool) -> Void let openMessageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) - init(context: AccountContext, isAudio: Bool, isAttach: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (Message) -> Void, setMessageSelection: @escaping ([MessageId], Message?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) { + init(context: AccountContext, isAudio: Bool, isAttach: Bool, openGallery: @escaping () -> Void, openFiles: @escaping () -> Void, scanDocument: @escaping () -> Void, expandSavedMusic: @escaping () -> Void, expandRecentMusic: @escaping () -> Void, send: @escaping (EngineRawMessage) -> Void, toggleMediaPlayback: @escaping (EngineRawMessage) -> Void, isSelectionActive: @escaping () -> Bool, toggleMessageSelection: @escaping (EngineRawMessage) -> Void, setMessageSelection: @escaping ([EngineMessage.Id], EngineRawMessage?, Bool) -> Void, openMessageContextAction: @escaping ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)) { self.context = context self.isAudio = isAudio self.isAttach = isAttach @@ -64,7 +63,7 @@ private enum AttachmentFileSection: Int32 { case global } -private func areMessagesEqual(_ lhsMessage: Message?, _ rhsMessage: Message?) -> Bool { +private func areMessagesEqual(_ lhsMessage: EngineRawMessage?, _ rhsMessage: EngineRawMessage?) -> Bool { guard let lhsMessage = lhsMessage, let rhsMessage = rhsMessage else { return lhsMessage == nil && rhsMessage == nil } @@ -83,15 +82,15 @@ private enum AttachmentFileEntry: ItemListNodeEntry { case scanDocument(PresentationTheme, String) case savedHeader(PresentationTheme, String) - case savedFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection) + case savedFile(Int32, PresentationTheme, EngineRawMessage?, ChatHistoryMessageSelection) case savedShowMore(PresentationTheme, String) case recentHeader(PresentationTheme, String) - case recentFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection) + case recentFile(Int32, PresentationTheme, EngineRawMessage?, ChatHistoryMessageSelection) case recentShowMore(PresentationTheme, String) case globalHeader(PresentationTheme, String) - case globalFile(Int32, PresentationTheme, Message?, ChatHistoryMessageSelection) + case globalFile(Int32, PresentationTheme, EngineRawMessage?, ChatHistoryMessageSelection) case globalShowMore(PresentationTheme, String) var section: ItemListSectionId { @@ -277,7 +276,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: EnginePeer.Id(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) case let .recentShowMore(theme, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandRecentMusic() @@ -302,7 +301,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: EnginePeer.Id(0)), interaction: interaction, message: message, selection: selection, displayHeader: false, isDownloadList: arguments.isAudio, isStoryMusic: true, isAttachMusic: arguments.isAttach, displayFileInfo: true, displayBackground: true, style: .blocks, sectionId: self.section) case let .globalShowMore(theme, text): return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: text, sectionId: self.section, editing: false, action: { @@ -315,8 +314,8 @@ private func attachmentFileControllerEntries( presentationData: PresentationData, mode: AttachmentFileControllerMode, state: AttachmentFileControllerState, - savedMusic: [Message]?, - recentDocuments: [Message]?, + savedMusic: [EngineRawMessage]?, + recentDocuments: [EngineRawMessage]?, hasScan: Bool, empty: Bool ) -> [AttachmentFileEntry] { @@ -513,11 +512,11 @@ private struct AttachmentFileControllerState: Equatable { var searching: Bool var savedMusicExpanded: Bool var recentMusicExpanded: Bool - var selectedMessageIds: [MessageId]? - var messageMap: [MessageId: EngineMessage] + var selectedMessageIds: [EngineMessage.Id]? + var messageMap: [EngineMessage.Id: EngineMessage] } -private func messageSelectionState(state: AttachmentFileControllerState, message: Message?) -> ChatHistoryMessageSelection { +private func messageSelectionState(state: AttachmentFileControllerState, message: EngineRawMessage?) -> ChatHistoryMessageSelection { guard let message, let selectedMessageIds = state.selectedMessageIds else { return .none } @@ -629,7 +628,7 @@ public func makeAttachmentFileControllerImpl( if let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in - guard let peer, let peerReference = PeerReference(peer._asPeer()) else { + guard let peer, let peerReference = PeerReference(peer) else { return } send([.savedMusic(peer: peerReference, media: file)], false, nil, nil) @@ -641,7 +640,7 @@ public func makeAttachmentFileControllerImpl( |> `catch` { _ in return .single(.result([])) } - |> mapToSignal { result -> Signal<[Message], NoError> in + |> mapToSignal { result -> Signal<[EngineRawMessage], NoError> in guard case let .result(result) = result else { return .complete() } @@ -739,15 +738,15 @@ public func makeAttachmentFileControllerImpl( } ) - let recentDocuments: Signal<[Message]?, NoError> + let recentDocuments: Signal<[EngineRawMessage]?, NoError> let savedMusicContext: ProfileSavedMusicContext? - let savedMusic: Signal<[Message]?, NoError> + let savedMusic: Signal<[EngineRawMessage]?, NoError> switch mode { case .recent: recentDocuments = .single(nil) |> then( context.engine.messages.searchMessages(location: .sentMedia(tags: [.file]), query: "", state: nil) - |> map { result -> [Message]? in + |> map { result -> [EngineRawMessage]? in return result.0.messages } ) @@ -756,8 +755,8 @@ public func makeAttachmentFileControllerImpl( case .audio: recentDocuments = .single(nil) |> then( - context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil) - |> map { result -> [Message]? in + context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil) + |> map { result -> [EngineRawMessage]? in return result.0.messages } ) @@ -767,11 +766,11 @@ public func makeAttachmentFileControllerImpl( savedMusicContext!.state |> map { state in let peerId = context.account.peerId - var messages: [Message] = [] - let peers = SimpleDictionary() + var messages: [EngineRawMessage] = [] + let peers = EngineSimpleDictionary() for file in state.files { let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max)) - messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) + messages.append(EngineRawMessage(stableId: stableId, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) } return messages } @@ -783,7 +782,7 @@ public func makeAttachmentFileControllerImpl( let existingCloseButton = Atomic(value: nil) let existingSearchButton = Atomic(value: nil) - let previousRecentDocuments = Atomic<[Message]?>(value: nil) + let previousRecentDocuments = Atomic<[EngineRawMessage]?>(value: nil) let signal = combineLatest(queue: Queue.mainQueue(), presentationData, recentDocuments, @@ -960,7 +959,7 @@ public func makeAttachmentFileControllerImpl( controller.mulitpleCompletion = { sendMode, _, _, caption in let _ = stateValue.with({ state in if let selectedMessageIds = state.selectedMessageIds { - var remoteMessageIds: [MessageId] = [] + var remoteMessageIds: [EngineMessage.Id] = [] for id in selectedMessageIds { if let message = state.messageMap[id]?._asMessage() { if message.id.namespace == Namespaces.Message.Cloud { @@ -974,17 +973,17 @@ public func makeAttachmentFileControllerImpl( |> `catch` { _ in return .single(.result([])) } - |> mapToSignal { result -> Signal<[Message], NoError> in + |> mapToSignal { result -> Signal<[EngineRawMessage], NoError> in guard case let .result(result) = result else { return .complete() } return .single(result) } ).start(next: { peer, remoteMessages in - guard let peer, let peerReference = PeerReference(peer._asPeer()) else { + guard let peer, let peerReference = PeerReference(peer) else { return } - var messageMap: [MessageId: Message] = [:] + var messageMap: [EngineMessage.Id: EngineRawMessage] = [:] for message in remoteMessages { messageMap[message.id] = message } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 2e1463429e..23f1203c65 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import ItemListUI @@ -30,7 +29,7 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { let presentationData: PresentationData let focus: () -> Void let cancel: () -> Void - let send: (Message) -> Void + let send: (EngineRawMessage) -> Void let dismissInput: () -> Void let didPreviewAudio: () -> Void @@ -38,7 +37,7 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, cancel: @escaping () -> Void, send: @escaping (Message) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) { + init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, cancel: @escaping () -> Void, send: @escaping (EngineRawMessage) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) { self.context = context self.mode = mode self.presentationData = presentationData @@ -108,7 +107,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { private var validLayout: ContainerViewLayout? - init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, send: @escaping (Message) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) { + init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, focus: @escaping () -> Void, send: @escaping (EngineRawMessage) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void, didPreviewAudio: @escaping () -> Void) { self.context = context self.mode = mode self.presentationData = presentationData @@ -217,11 +216,11 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { private final class AttachmentFileSearchContainerInteraction { let context: AccountContext - let send: (Message) -> Void - let toggleMediaPlayback: (Message) -> Void + let send: (EngineRawMessage) -> Void + let toggleMediaPlayback: (EngineRawMessage) -> Void let expandSection: (Int32) -> Void - init(context: AccountContext, send: @escaping (Message) -> Void, toggleMediaPlayback: @escaping (Message) -> Void, expandSection: @escaping (Int32) -> Void) { + init(context: AccountContext, send: @escaping (EngineRawMessage) -> Void, toggleMediaPlayback: @escaping (EngineRawMessage) -> Void, expandSection: @escaping (Int32) -> Void) { self.context = context self.send = send self.toggleMediaPlayback = toggleMediaPlayback @@ -232,11 +231,11 @@ private final class AttachmentFileSearchContainerInteraction { private enum AttachmentFileSearchEntryId: Hashable { case header(Int32) case placeholder(Int32, Int32) - case message(Int32, MessageId) + case message(Int32, EngineMessage.Id) case showMore(Int32) } -private func areMessagesEqual(_ lhsMessage: Message?, _ rhsMessage: Message?) -> Bool { +private func areMessagesEqual(_ lhsMessage: EngineRawMessage?, _ rhsMessage: EngineRawMessage?) -> Bool { guard let lhsMessage = lhsMessage, let rhsMessage = rhsMessage else { return lhsMessage == nil && rhsMessage == nil } @@ -251,7 +250,7 @@ private func areMessagesEqual(_ lhsMessage: Message?, _ rhsMessage: Message?) -> private enum AttachmentFileSearchEntry: Comparable, Identifiable { case header(title: String, section: Int32) - case file(index: Int32, message: Message?, section: Int32) + case file(index: Int32, message: EngineRawMessage?, section: Int32) case showMore(text: String, section: Int32) var section: ItemListSectionId { @@ -334,7 +333,7 @@ private enum AttachmentFileSearchEntry: Comparable, Identifiable { let isStoryMusic = mode.isAudio let isDownloadList = mode.isAudio - return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: PeerId(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, isAttachMusic: true, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section) + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), systemStyle: .glass, context: interaction.context, chatLocation: .peer(id: EnginePeer.Id(0)), interaction: itemInteraction, message: message, selection: .none, displayHeader: false, isDownloadList: isDownloadList, isStoryMusic: isStoryMusic, isAttachMusic: true, displayFileInfo: displayFileInfo, displayBackground: true, style: .blocks, sectionId: section) case let .showMore(text, section): return ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), style: .blocks, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: section, editing: false, action: { interaction.expandSection(section) @@ -379,7 +378,7 @@ private func attachmentFileSearchContainerPreparedRecentTransition( public final class AttachmentFileSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext - private let send: (Message) -> Void + private let send: (EngineRawMessage) -> Void private let dimNode: ASDisplayNode private let backgroundNode: ASDisplayNode @@ -415,7 +414,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon return _hasDim } - public init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, send: @escaping (Message) -> Void, updateActivity: @escaping (Bool) -> Void, didPreviewAudio: @escaping () -> Void) { + public init(context: AccountContext, mode: AttachmentFileControllerMode, presentationData: PresentationData, send: @escaping (EngineRawMessage) -> Void, updateActivity: @escaping (Bool) -> Void, didPreviewAudio: @escaping () -> Void) { self.context = context self.send = send @@ -512,16 +511,16 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon let queryTokens = stringTokens(query.lowercased()) - let shared: Signal<[Message]?, NoError> - let savedMusic: Signal<[Message]?, NoError> - let globalMusic: Signal<[Message]?, NoError> + let shared: Signal<[EngineRawMessage]?, NoError> + let savedMusic: Signal<[EngineRawMessage]?, NoError> + let globalMusic: Signal<[EngineRawMessage]?, NoError> switch mode { case .recent: shared = .single(nil) |> then( context.engine.messages.searchMessages(location: .sentMedia(tags: [.file]), query: query, state: nil) |> delay(0.6, queue: Queue.mainQueue()) - |> map { result -> [Message]? in + |> map { result -> [EngineRawMessage]? in return result.0.messages } ) @@ -530,9 +529,9 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon case .audio: shared = .single(nil) |> then( - context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: query, state: nil) + context.engine.messages.searchMessages(location: .general(scope: .everywhere, groupId: nil, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: query, state: nil) |> delay(0.6, queue: Queue.mainQueue()) - |> map { result -> [Message]? in + |> map { result -> [EngineRawMessage]? in return result.0.messages.filter { !$0.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) } } ) @@ -542,8 +541,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon |> delay(0.6, queue: Queue.mainQueue()) |> map { state in let peerId = context.account.peerId - var messages: [Message] = [] - let peers = SimpleDictionary() + var messages: [EngineRawMessage] = [] + let peers = EngineSimpleDictionary() for file in state.files { var indexString = "" for attribute in file.attributes { @@ -564,7 +563,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon continue } let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max)) - messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) + messages.append(EngineRawMessage(stableId: stableId, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) } return messages } @@ -598,14 +597,14 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon return [] } let peerId = context.account.peerId - var messages: [Message] = [] - let peers = SimpleDictionary() + var messages: [EngineRawMessage] = [] + let peers = EngineSimpleDictionary() for result in results { switch result { case let .internalReference(internalReference): if let file = internalReference.file { let stableId = UInt32(clamping: file.fileId.id % Int64(Int32.max)) - messages.append(Message(stableId: stableId, stableVersion: 0, id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) + messages.append(EngineRawMessage(stableId: stableId, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Local, id: Int32(stableId)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [.music], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])) } default: break @@ -837,7 +836,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon insets.bottom += 60.0 let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { insets.left += inset insets.right += inset } @@ -900,20 +899,20 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } } -private func stringTokens(_ string: String) -> [ValueBoxKey] { +private func stringTokens(_ string: String) -> [EngineRawValueBoxKey] { let nsString = string.folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString let flag = UInt(kCFStringTokenizerUnitWord) let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent()) var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) - var tokens: [ValueBoxKey] = [] + var tokens: [EngineRawValueBoxKey] = [] - var addedTokens = Set() + var addedTokens = Set() while tokenType != [] { let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer) if currentTokenRange.location >= 0 && currentTokenRange.length != 0 { - let token = ValueBoxKey(length: currentTokenRange.length * 2) + let token = EngineRawValueBoxKey(length: currentTokenRange.length * 2) nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length)) if !addedTokens.contains(token) { tokens.append(token) @@ -926,7 +925,7 @@ private func stringTokens(_ string: String) -> [ValueBoxKey] { return tokens } -private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool { +private func matchStringTokens(_ tokens: [EngineRawValueBoxKey], with other: [EngineRawValueBoxKey]) -> Bool { if other.isEmpty { return false } else if other.count == 1 { diff --git a/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD b/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD index 4136ffb3e3..12bc07b85a 100644 --- a/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD +++ b/submodules/TelegramUI/Components/AuthConfirmationScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD index c8f00d99c9..e88140db3c 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 00063fed48..e9e7be9e0c 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import TelegramNotices import EntityKeyboard @@ -165,6 +164,7 @@ final class AvatarEditorScreenComponent: Component { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -211,6 +211,7 @@ final class AvatarEditorScreenComponent: Component { private var data: AvatarKeyboardInputData? private let emojiSearchDisposable = MetaDisposable() + private var stickerSearchContext: StickerSearchContext? private let emojiSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) { didSet { @@ -273,156 +274,173 @@ final class AvatarEditorScreenComponent: Component { switch query { case .none: + self.stickerSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.stickerSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { - var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) - if !languageCode.lowercased().hasPrefix("en") { - signal = signal - |> mapToSignal { keywords in - return .single(keywords) - |> then( - context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3) - |> map { englishKeywords in - return keywords + englishKeywords + self.stickerSearchContext = nil + + let emojiItemsSignal = context.engine.itemCollections.allItems(namespace: Namespaces.ItemCollection.CloudEmojiPacks) + |> take(1) + + let buildGroups: ([EngineRawItemCollectionItem], [String: String], StickerSearchContext.State, StickerSearchContext?) -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?) = { items, allEmoticons, stickerState, searchContext in + let hasPremium = true + + var emoji: [(String, TelegramMediaFile.Accessor?, String)] = [] + for rawItem in items { + guard let item = rawItem as? StickerPackItem else { + continue + } + if let alt = item.file.customEmojiAlt { + if !item.file.isPremiumEmoji || hasPremium { + if !alt.isEmpty, let keyword = allEmoticons[alt] { + emoji.append((alt, item.file, keyword)) + } else if alt == query { + emoji.append((alt, item.file, alt)) + } } + } + } + + var emojiItems: [EmojiPagerContentComponent.Item] = [] + var existingIds = Set() + for item in emoji { + if let itemFile = item.1 { + if existingIds.contains(itemFile.fileId) { + continue + } + existingIds.insert(itemFile.fileId) + let animationData = EntityKeyboardAnimationData(file: itemFile) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, + subgroupId: nil, + icon: .none, + tintMode: animationData.isTemplate ? .primary : .none + ) + emojiItems.append(item) + } + } + + var stickerItems: [EmojiPagerContentComponent.Item] = [] + for sticker in stickerState.items { + if existingIds.contains(sticker.file.fileId) { + continue + } + existingIds.insert(sticker.file.fileId) + let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(sticker.file)) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: TelegramMediaFile.Accessor(sticker.file), + subgroupId: nil, + icon: .none, + tintMode: .none + ) + stickerItems.append(item) + } + + var result: [EmojiPagerContentComponent.ItemGroup] = [] + if !emojiItems.isEmpty { + result.append( + EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "emoji", + title: "Emoji", + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: emojiItems + ) ) } + if !stickerItems.isEmpty { + result.append( + EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "stickers", + title: "Stickers", + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: stickerItems + ) + ) + } + return (result, stickerState.canLoadMore, stickerState.items.isEmpty && stickerState.isLoadingMore, searchContext) } - - let resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), - combineLatest(keywords.map { context.engine.stickers.searchStickers(query: query, emoticon: $0.emoticons, inputLanguageCode: languageCode) - |> map { items -> [FoundStickerItem] in - return items.items + + let resultSignal: Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?), NoError> + if query.isSingleEmoji { + let allEmoticons = [query.basicEmoji.0: query.basicEmoji.0] + let searchContext = context.engine.stickers.stickerSearchContext(query: nil, emoticon: [query.basicEmoji.0], inputLanguageCode: languageCode) + resultSignal = combineLatest(emojiItemsSignal, searchContext.state) + |> map { items, stickerState in + return buildGroups(items, allEmoticons, stickerState, searchContext) + } + } else { + var keywordsSignal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) + if !languageCode.lowercased().hasPrefix("en") { + keywordsSignal = keywordsSignal + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) } - }) - ) - |> map { view, stickers -> [EmojiPagerContentComponent.ItemGroup] in - let hasPremium = true - - var emoji: [(String, TelegramMediaFile.Accessor?, String)] = [] - - var existingEmoticons = Set() + } + + resultSignal = keywordsSignal + |> mapToSignal { keywords -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword - existingEmoticons.insert(emoticon) } } - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { - continue - } - if let alt = item.file.customEmojiAlt { - if !item.file.isPremiumEmoji || hasPremium { - if !alt.isEmpty, let keyword = allEmoticons[alt] { - emoji.append((alt, item.file, keyword)) - } else if alt == query { - emoji.append((alt, item.file, alt)) - } - } + let emoticon = Array(allEmoticons.keys) + guard !emoticon.isEmpty else { + return combineLatest(emojiItemsSignal, .single(StickerSearchContext.State(items: [], canLoadMore: false, isLoadingMore: false))) + |> map { items, stickerState in + return buildGroups(items, allEmoticons, stickerState, nil) } } - - var emojiItems: [EmojiPagerContentComponent.Item] = [] - - var existingIds = Set() - for item in emoji { - if let itemFile = item.1 { - if existingIds.contains(itemFile.fileId) { - continue - } - existingIds.insert(itemFile.fileId) - let animationData = EntityKeyboardAnimationData(file: itemFile) - let item = EmojiPagerContentComponent.Item( - animationData: animationData, - content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, - icon: .none, - tintMode: animationData.isTemplate ? .primary : .none - ) - emojiItems.append(item) - } + + let searchContext = context.engine.stickers.stickerSearchContext(query: query, emoticon: emoticon, inputLanguageCode: languageCode) + return combineLatest(emojiItemsSignal, searchContext.state) + |> map { items, stickerState in + return buildGroups(items, allEmoticons, stickerState, searchContext) } - - var stickerItems: [EmojiPagerContentComponent.Item] = [] - for stickerResult in stickers { - for sticker in stickerResult { - if existingIds.contains(sticker.file.fileId) { - continue - } - - existingIds.insert(sticker.file.fileId) - let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(sticker.file)) - let item = EmojiPagerContentComponent.Item( - animationData: animationData, - content: .animation(animationData), - itemFile: TelegramMediaFile.Accessor(sticker.file), - subgroupId: nil, - icon: .none, - tintMode: .none - ) - stickerItems.append(item) - } - } - - var result: [EmojiPagerContentComponent.ItemGroup] = [] - if !emojiItems.isEmpty { - result.append( - EmojiPagerContentComponent.ItemGroup( - supergroupId: "search", - groupId: "emoji", - title: "Emoji", - subtitle: nil, - badge: nil, - actionButtonTitle: nil, - isFeatured: false, - isPremiumLocked: false, - isEmbedded: false, - hasClear: false, - hasEdit: false, - collapsedLineCount: nil, - displayPremiumBadges: false, - headerItem: nil, - fillWithLoadingPlaceholders: false, - items: emojiItems - ) - ) - } - if !stickerItems.isEmpty { - result.append( - EmojiPagerContentComponent.ItemGroup( - supergroupId: "search", - groupId: "stickers", - title: "Stickers", - subtitle: nil, - badge: nil, - actionButtonTitle: nil, - isFeatured: false, - isPremiumLocked: false, - isEmbedded: false, - hasClear: false, - hasEdit: false, - collapsedLineCount: nil, - displayPremiumBadges: false, - headerItem: nil, - fillWithLoadingPlaceholders: false, - items: stickerItems - ) - ) - } - return result } } @@ -435,16 +453,18 @@ final class AvatarEditorScreenComponent: Component { return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.stickerSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.stickerSearchContext = nil let resultSignal = context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { continue @@ -515,11 +535,11 @@ final class AvatarEditorScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -539,18 +559,14 @@ final class AvatarEditorScreenComponent: Component { openSearch: { }, addGroupAction: { [weak self] groupId, isPremiumLocked, _ in - guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else { + guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? EngineItemCollectionId else { return } let context = controller.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + |> deliverOnMainQueue).start(next: { items in + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -559,7 +575,7 @@ final class AvatarEditorScreenComponent: Component { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -570,7 +586,7 @@ final class AvatarEditorScreenComponent: Component { } |> deliverOnMainQueue).start(completed: { }) - + break } } @@ -648,6 +664,9 @@ final class AvatarEditorScreenComponent: Component { } } }, + loadMore: { [weak self] in + self?.stickerSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -674,18 +693,14 @@ final class AvatarEditorScreenComponent: Component { openSearch: { }, addGroupAction: { [weak self] groupId, isPremiumLocked, _ in - guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? ItemCollectionId else { + guard let strongSelf = self, let controller = strongSelf.controller?(), let collectionId = groupId.base as? EngineItemCollectionId else { return } let context = controller.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + |> deliverOnMainQueue).start(next: { items in + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -694,7 +709,7 @@ final class AvatarEditorScreenComponent: Component { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -731,15 +746,11 @@ final class AvatarEditorScreenComponent: Component { ])]) context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + |> deliverOnMainQueue).start(next: { items in var stickerPackIds: [Int64] = [] - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { stickerPackIds.append(featuredStickerPack.info.id.id) } let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() @@ -779,6 +790,9 @@ final class AvatarEditorScreenComponent: Component { } } }, + loadMore: { [weak self] in + self?.stickerSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -919,17 +933,24 @@ final class AvatarEditorScreenComponent: Component { if let searchResult = emojiSearchState.result { let presentationData = context.sharedContext.currentPresentationData.with { $0 } var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? - if !searchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { + if !emojiSearchState.isSearching && !searchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { emptySearchResults = EmojiPagerContentComponent.EmptySearchResults( text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult, iconFile: nil ) } + let searchState: EmojiPagerContentComponent.SearchState = emojiSearchState.isSearching ? .searching : .active if state?.keyboardContentId == AnyHashable("emoji") { - data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: .active) + data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: searchState, canLoadMore: searchResult.canLoadMore) } else { - data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: .active) + data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? searchResult.groups, contentItemGroups: searchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: searchResult.id, version: searchResult.version), emptySearchResults: emptySearchResults, searchState: searchState, canLoadMore: searchResult.canLoadMore) + } + } else if emojiSearchState.isSearching { + if state?.keyboardContentId == AnyHashable("emoji") { + data.emoji = data.emoji.withUpdatedItemGroups(panelItemGroups: data.emoji.panelItemGroups, contentItemGroups: data.emoji.contentItemGroups, itemContentUniqueId: data.emoji.itemContentUniqueId, emptySearchResults: data.emoji.emptySearchResults, searchState: .searching) + } else { + data.stickers = data.stickers?.withUpdatedItemGroups(panelItemGroups: data.stickers?.panelItemGroups ?? [], contentItemGroups: data.stickers?.contentItemGroups ?? [], itemContentUniqueId: data.stickers?.itemContentUniqueId, emptySearchResults: data.stickers?.emptySearchResults, searchState: .searching) } } @@ -1501,7 +1522,7 @@ final class AvatarEditorScreenComponent: Component { } let values = MediaEditorValues( - peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), originalDimensions: PixelDimensions(size), cropOffset: .zero, cropRect: CGRect(origin: .zero, size: size), @@ -1518,6 +1539,7 @@ final class AvatarEditorScreenComponent: Component { videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift index 42c959c8a7..45256d2787 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarPreviewComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import AnimatedStickerNode import TelegramAnimatedStickerNode import StickerResources diff --git a/submodules/TelegramUI/Components/AvatarUploadToastScreen/BUILD b/submodules/TelegramUI/Components/AvatarUploadToastScreen/BUILD index a1ebb83491..cfb18fecd6 100644 --- a/submodules/TelegramUI/Components/AvatarUploadToastScreen/BUILD +++ b/submodules/TelegramUI/Components/AvatarUploadToastScreen/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Components/ViewControllerComponent", diff --git a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift index cbb7053aa4..a1e6aded48 100644 --- a/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift +++ b/submodules/TelegramUI/Components/BatchVideoRendering/Sources/BatchVideoRenderingContext.swift @@ -26,13 +26,19 @@ public final class BatchVideoRenderingContext { } deinit { - self.context?.targetRemoved(id: self.id) + // The handle can be released on any queue; hop to main so the context's + // non-thread-safe state (targetContexts, disposables, displayLink) is only + // mutated from one queue. + let context = self.context + let id = self.id + Queue.mainQueue().async { + context?.targetRemoved(id: id) + } } } public final class TargetState { private var lastRenderedFrame: (timestamp: Double, pts: Double, duration: Double)? - private var ptsOffset: Double = 0.0 private(set) var sampleBuffers: [CMSampleBuffer] = [] init() { @@ -124,6 +130,10 @@ public final class BatchVideoRenderingContext { case .endOfStream: self.reader = nil case .waitingForMoreData: + // Invariant: the reader is only constructed once the consumer's + // `data.isComplete == true` (see `BatchVideoRenderingContext.update`), + // so a `.file(...)` source should never report waitingForMoreData. + // Treat it as fatal rather than spin if the invariant is ever violated. self.isFailed = true break outer } @@ -187,7 +197,10 @@ public final class BatchVideoRenderingContext { } private func targetRemoved(id: Int) { - if self.targetContexts.removeValue(forKey: id) != nil { + if let removed = self.targetContexts.removeValue(forKey: id) { + // Clear the leftover TargetState so a recycled target doesn't start its + // next session by replaying our queued buffers and stale lastRenderedFrame. + removed.target?.batchVideoRenderingTargetState = nil self.update() } } @@ -205,12 +218,12 @@ public final class BatchVideoRenderingContext { ).startStrict() } if targetContext.dataDisposable == nil { - targetContext.dataDisposable = (self.context.account.postbox.mediaBox.resourceData(targetContext.file.media.resource) + targetContext.dataDisposable = (self.context.engine.resources.data(resource: EngineMediaResource(targetContext.file.media.resource)) |> deliverOnMainQueue).startStrict(next: { [weak self, weak targetContext] data in guard let self, let targetContext else { return } - if data.complete && targetContext.dataPath == nil { + if data.isComplete && targetContext.dataPath == nil { targetContext.dataPath = data.path self.update() } @@ -229,6 +242,10 @@ public final class BatchVideoRenderingContext { self.targetContexts.removeValue(forKey: id) } + self.updateDisplayLink() + } + + private func updateDisplayLink() { if !self.targetContexts.isEmpty { if self.displayLink == nil { self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] _ in @@ -242,7 +259,7 @@ public final class BatchVideoRenderingContext { self.displayLink = nil } } - + private func updateRendering() { if self.isRendering { return @@ -319,18 +336,7 @@ public final class BatchVideoRenderingContext { self.updateFrames() } - if !self.targetContexts.isEmpty { - if self.displayLink == nil { - self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] _ in - guard let self else { - return - } - self.updateRendering() - } - } - } else { - self.displayLink = nil - } + self.updateDisplayLink() } private func updateFrames() { diff --git a/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD b/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD index 43b5271ce3..37977a29be 100644 --- a/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/BottomButtonPanelComponent/BUILD @@ -16,7 +16,9 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/BottomButtonPanelComponent/Sources/BottomButtonPanelComponent.swift b/submodules/TelegramUI/Components/BottomButtonPanelComponent/Sources/BottomButtonPanelComponent.swift index 966c9230fd..97a11abe6c 100644 --- a/submodules/TelegramUI/Components/BottomButtonPanelComponent/Sources/BottomButtonPanelComponent.swift +++ b/submodules/TelegramUI/Components/BottomButtonPanelComponent/Sources/BottomButtonPanelComponent.swift @@ -5,7 +5,9 @@ import AsyncDisplayKit import ComponentFlow import ComponentDisplayAdapters import TelegramPresentationData +import AnimatedTextComponent import ButtonComponent +import EdgeEffect import MultilineTextComponent public final class BottomButtonPanelComponent: Component { @@ -58,20 +60,18 @@ public final class BottomButtonPanelComponent: Component { } public class View: UIView { - private let backgroundView: BlurredBackgroundView - private let separatorLayer: SimpleLayer + private let edgeEffectView: EdgeEffectView private let actionButton = ComponentView() - + private var component: BottomButtonPanelComponent? override public init(frame: CGRect) { - self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true) - self.separatorLayer = SimpleLayer() + self.edgeEffectView = EdgeEffectView() + self.edgeEffectView.isUserInteractionEnabled = false super.init(frame: frame) - self.addSubview(self.backgroundView) - self.layer.addSublayer(self.separatorLayer) + self.addSubview(self.edgeEffectView) } required public init?(coder: NSCoder) { @@ -79,7 +79,6 @@ public final class BottomButtonPanelComponent: Component { } func update(component: BottomButtonPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let themeUpdated = self.component?.theme !== component.theme self.component = component let topInset: CGFloat = 8.0 @@ -91,28 +90,34 @@ public final class BottomButtonPanelComponent: Component { bottomInset = component.insets.bottom + 10.0 } - let height: CGFloat = topInset + 50.0 + bottomInset + let buttonHeight: CGFloat = 52.0 + let height: CGFloat = topInset + buttonHeight + bottomInset + + let edgeEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: component.theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) - if themeUpdated { - self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - self.separatorLayer.backgroundColor = component.theme.rootController.navigationBar.separatorColor.cgColor - } - - - let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height)) - transition.setFrame(view: self.backgroundView, frame: backgroundFrame) - self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) - - transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel))) - var buttonTitleVStack: [AnyComponentWithIdentity] = [] - - let titleString = NSMutableAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) - buttonTitleVStack.append(AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(titleString))))) + buttonTitleVStack.append(AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 18.0, weight: .semibold, traits: .monospacedNumbers), + color: component.theme.list.itemCheckColors.foregroundColor, + items: [ + AnimatedTextComponent.Item(id: AnyHashable("title"), content: .text(component.title)) + ], + noDelay: true, + blur: false + )))) if let label = component.label { - let labelString = NSMutableAttributedString(string: label, font: Font.semibold(11.0), textColor: component.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center) - buttonTitleVStack.append(AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(labelString))))) + buttonTitleVStack.append(AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(AnimatedTextComponent( + font: Font.with(size: 11.0, weight: .semibold, traits: .monospacedNumbers), + color: component.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), + items: [ + AnimatedTextComponent.Item(id: AnyHashable("label"), content: .text(label)) + ], + noDelay: true, + blur: false + )))) } var buttonTitleContent: AnyComponent = AnyComponent(VStack(buttonTitleVStack, spacing: 1.0)) @@ -127,10 +132,10 @@ public final class BottomButtonPanelComponent: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: component.theme.list.itemCheckColors.fillColor, foreground: component.theme.list.itemCheckColors.foregroundColor, - pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) ), content: AnyComponentWithIdentity( id: 0, @@ -146,7 +151,7 @@ public final class BottomButtonPanelComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - component.insets.left - component.insets.right, height: 50.0) + containerSize: CGSize(width: availableSize.width - component.insets.left - component.insets.right, height: buttonHeight) ) if let actionButtonView = self.actionButton.view { if actionButtonView.superview == nil { diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index 8a14bf06a1..b62e67d0e4 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -282,7 +282,7 @@ public final class ButtonTextContentComponent: Component { } if let badgeSize, let badge = self.badge { - let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + 1.0), size: badgeSize) + let badgeFrame = CGRect(origin: CGPoint(x: contentFrame.minX + contentSize.width + badgeSpacing, y: floorToScreenPixels((size.height - badgeSize.height) * 0.5) + UIScreenPixel), size: badgeSize) if let badgeView = badge.view { var animateIn = false @@ -344,13 +344,31 @@ public final class ButtonComponent: Component { case actualGlass case legacy } - + + public struct Gradient: Equatable { + public enum Animation: Equatable { + case horizontalShift(duration: Double) + } + + public var colors: [UIColor] + public var animation: Animation + + public init( + colors: [UIColor], + animation: Animation = .horizontalShift(duration: 4.5) + ) { + self.colors = colors + self.animation = animation + } + } + public var style: Style public var color: UIColor public var foreground: UIColor public var pressedColor: UIColor public var cornerRadius: CGFloat public var isShimmering: Bool + public var gradient: Gradient? public init( style: Style = .legacy, @@ -358,7 +376,8 @@ public final class ButtonComponent: Component { foreground: UIColor, pressedColor: UIColor, cornerRadius: CGFloat = 10.0, - isShimmering: Bool = false + isShimmering: Bool = false, + gradient: Gradient? = nil ) { self.style = style self.color = color @@ -366,6 +385,7 @@ public final class ButtonComponent: Component { self.pressedColor = pressedColor self.cornerRadius = cornerRadius self.isShimmering = isShimmering + self.gradient = gradient } public func withIsShimmering(_ isShimmering: Bool) -> Background { @@ -375,7 +395,8 @@ public final class ButtonComponent: Component { foreground: self.foreground, pressedColor: self.pressedColor, cornerRadius: self.cornerRadius, - isShimmering: isShimmering + isShimmering: isShimmering, + gradient: self.gradient ) } } @@ -461,9 +482,14 @@ public final class ButtonComponent: Component { private var containerView: UIView private var glassContainerView: GlassBackgroundView? + private var glassShadowView: UIImageView? + private var glassShadowCornerRadius: CGFloat? + private var glassHighlightContainerView: UIView? private let button: HighlightTrackingButton + private let glassHighlightRecognizer: GlassHighlightGestureRecognizer private var shimmeringView: ButtonShimmeringView? + private var gradientBackgroundView: AnimatedGradientBackgroundView? private var chromeView: UIImageView? private var contentItem: ContentItem? @@ -476,6 +502,7 @@ public final class ButtonComponent: Component { self.containerView.isUserInteractionEnabled = false self.button = HighlightTrackingButton() + self.glassHighlightRecognizer = GlassHighlightGestureRecognizer(target: nil, action: nil) super.init(frame: frame) @@ -484,6 +511,8 @@ public final class ButtonComponent: Component { self.addSubview(self.containerView) self.addSubview(self.button) + self.addGestureRecognizer(self.glassHighlightRecognizer) + self.glassHighlightRecognizer.isEnabled = false self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) @@ -495,28 +524,6 @@ public final class ButtonComponent: Component { self.button.highligthedChanged = { [weak self] highlighted in if let self, let component = self.component, component.isEnabled { switch component.background.style { - case .glass: - let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) - if highlighted { - self.layer.shouldRasterize = true - - let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01) - transition.setBackgroundColor(view: self.containerView, color: highlightedColor) - transition.setScale(view: self.containerView, scale: 1.05, completion: { finished in - if finished { - self.layer.shouldRasterize = false - } - }) - } else { - self.layer.shouldRasterize = true - - transition.setBackgroundColor(view: self.containerView, color: component.background.color) - transition.setScale(view: self.containerView, scale: 1.0, completion: { finished in - if finished { - self.layer.shouldRasterize = false - } - }) - } case .legacy: if highlighted { self.containerView.layer.removeAnimation(forKey: "opacity") @@ -536,6 +543,112 @@ public final class ButtonComponent: Component { preconditionFailure() } + private func removeGlassEffect(transition: ComponentTransition) { + self.glassHighlightRecognizer.isEnabled = false + self.glassHighlightRecognizer.highlightContainerView = nil + + if let glassShadowView = self.glassShadowView, glassShadowView.superview != nil { + if transition.animation.isImmediate { + glassShadowView.removeFromSuperview() + } else { + transition.setAlpha(view: glassShadowView, alpha: 0.0, completion: { _ in + glassShadowView.removeFromSuperview() + }) + } + } + if let glassHighlightContainerView = self.glassHighlightContainerView, glassHighlightContainerView.superview != nil { + glassHighlightContainerView.removeFromSuperview() + } + self.glassShadowCornerRadius = nil + + self.layer.removeAnimation(forKey: "sublayerTransform") + self.layer.sublayerTransform = CATransform3DIdentity + } + + private func updateGlassEffect(component: ButtonComponent, size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) { + let shadowInset: CGFloat = 48.0 + + let glassShadowView: UIImageView + if let current = self.glassShadowView { + glassShadowView = current + } else { + glassShadowView = UIImageView() + glassShadowView.isUserInteractionEnabled = false + self.glassShadowView = glassShadowView + } + if glassShadowView.superview == nil { + self.insertSubview(glassShadowView, at: 0) + } else { + self.sendSubviewToBack(glassShadowView) + } + if self.glassShadowCornerRadius != cornerRadius || glassShadowView.image == nil { + glassShadowView.image = GlassBackgroundView.generateLegacyShadowImage(cornerRadius: cornerRadius, shadowInset: shadowInset, shadowIntensity: 0.18, shadowBlur: 64.0) + self.glassShadowCornerRadius = cornerRadius + } + transition.setFrame(view: glassShadowView, frame: CGRect(origin: .zero, size: size).insetBy(dx: -shadowInset, dy: -shadowInset)) + transition.setAlpha(view: glassShadowView, alpha: 1.0) + + let glassHighlightContainerView: UIView + if let current = self.glassHighlightContainerView { + glassHighlightContainerView = current + } else { + glassHighlightContainerView = UIView() + glassHighlightContainerView.isUserInteractionEnabled = false + glassHighlightContainerView.clipsToBounds = true + self.glassHighlightContainerView = glassHighlightContainerView + } + if glassHighlightContainerView.superview == nil { + self.insertSubview(glassHighlightContainerView, aboveSubview: self.containerView) + } else if self.button.superview === self { + self.insertSubview(glassHighlightContainerView, belowSubview: self.button) + } else { + self.bringSubviewToFront(glassHighlightContainerView) + } + transition.setFrame(view: glassHighlightContainerView, frame: CGRect(origin: .zero, size: size)) + transition.setCornerRadius(layer: glassHighlightContainerView.layer, cornerRadius: cornerRadius) + + self.glassHighlightRecognizer.highlightContainerView = glassHighlightContainerView + self.glassHighlightRecognizer.isEnabled = component.isEnabled && !component.displaysProgress + } + + private func updateGradientBackground(component: ButtonComponent, contentContainerView: UIView, size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) { + guard component.background.style != .actualGlass, let gradient = component.background.gradient, gradient.colors.count > 1 else { + if let gradientBackgroundView = self.gradientBackgroundView { + self.gradientBackgroundView = nil + if transition.animation.isImmediate { + gradientBackgroundView.removeFromSuperview() + } else { + gradientBackgroundView.layer.animateAlpha(from: gradientBackgroundView.alpha, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak gradientBackgroundView] _ in + gradientBackgroundView?.removeFromSuperview() + }) + } + } + return + } + + let gradientBackgroundView: AnimatedGradientBackgroundView + var gradientTransition = transition + if let current = self.gradientBackgroundView { + gradientBackgroundView = current + } else { + gradientTransition = .immediate + gradientBackgroundView = AnimatedGradientBackgroundView(frame: .zero) + gradientBackgroundView.alpha = 0.0 + self.gradientBackgroundView = gradientBackgroundView + } + + if gradientBackgroundView.superview !== contentContainerView { + gradientBackgroundView.removeFromSuperview() + contentContainerView.insertSubview(gradientBackgroundView, at: 0) + } else { + contentContainerView.sendSubviewToBack(gradientBackgroundView) + } + + gradientBackgroundView.update(size: size, gradient: gradient, cornerRadius: cornerRadius, transition: gradientTransition) + gradientTransition.setFrame(view: gradientBackgroundView, frame: CGRect(origin: .zero, size: size)) + transition.setAlpha(view: gradientBackgroundView, alpha: 1.0) + } + @objc private func pressed() { guard let component = self.component else { return @@ -641,6 +754,14 @@ public final class ButtonComponent: Component { transition.setCornerRadius(layer: self.containerView.layer, cornerRadius: cornerRadius) } + self.updateGradientBackground(component: component, contentContainerView: contentContainerView, size: size, cornerRadius: cornerRadius, transition: transition) + + if component.background.style == .glass, component.background.color.alpha > 1.0 - .ulpOfOne { + self.updateGlassEffect(component: component, size: size, cornerRadius: cornerRadius, transition: transition) + } else { + self.removeGlassEffect(transition: transition) + } + if let contentView = contentItem.view.view { var animateIn = false var contentTransition = transition @@ -714,7 +835,11 @@ public final class ButtonComponent: Component { shimmeringTransition = .immediate shimmeringView = ButtonShimmeringView(frame: .zero) self.shimmeringView = shimmeringView - contentContainerView.insertSubview(shimmeringView, at: 0) + if let gradientBackgroundView = self.gradientBackgroundView, gradientBackgroundView.superview === contentContainerView { + contentContainerView.insertSubview(shimmeringView, aboveSubview: gradientBackgroundView) + } else { + contentContainerView.insertSubview(shimmeringView, at: 0) + } } shimmeringView.update(size: size, background: component.background, cornerRadius: cornerRadius, transition: shimmeringTransition) shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: size)) @@ -736,13 +861,15 @@ public final class ButtonComponent: Component { self.chromeView = chromeView if let shimmeringView = self.shimmeringView { contentContainerView.insertSubview(chromeView, aboveSubview: shimmeringView) + } else if let gradientBackgroundView = self.gradientBackgroundView, gradientBackgroundView.superview === contentContainerView { + contentContainerView.insertSubview(chromeView, aboveSubview: gradientBackgroundView) } else { contentContainerView.insertSubview(chromeView, at: 0) } chromeView.layer.compositingFilter = "overlayBlendMode" chromeView.alpha = 0.8 - chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: component.background.color.lightness < 0.36, fillColor: .clear) + chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: size.height, height: size.height), isDark: component.background.color.lightness < 0.36, fillColor: .clear) } chromeTransition.setFrame(view: chromeView, frame: CGRect(origin: .zero, size: size)) } else if let chromeView = self.chromeView { @@ -770,6 +897,113 @@ public final class ButtonComponent: Component { } } +private final class AnimatedGradientBackgroundView: UIView { + private let backgroundView: UIImageView + private var animationView: UIImageView? + + private var currentGradient: ButtonComponent.Background.Gradient? + private var currentImageHeight: CGFloat? + + override init(frame: CGRect) { + self.backgroundView = UIImageView() + self.backgroundView.clipsToBounds = true + + super.init(frame: frame) + + self.clipsToBounds = true + self.isUserInteractionEnabled = false + + if #available(iOS 13.0, *) { + self.layer.cornerCurve = .continuous + self.backgroundView.layer.cornerCurve = .continuous + } + + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + func update(size: CGSize, gradient: ButtonComponent.Background.Gradient, cornerRadius: CGFloat, transition: ComponentTransition) { + let bounds = CGRect(origin: .zero, size: size) + + transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius) + transition.setCornerRadius(layer: self.backgroundView.layer, cornerRadius: cornerRadius) + transition.setFrame(view: self.backgroundView, frame: bounds) + + let imageHeight = max(1.0, size.height) + if self.currentGradient != gradient || self.currentImageHeight != imageHeight || self.backgroundView.image == nil { + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(gradient.colors.count - 1) + for i in 0 ..< gradient.colors.count { + locations.append(delta * CGFloat(i)) + } + + let image = generateGradientImage(size: CGSize(width: 200.0, height: imageHeight), colors: gradient.colors, locations: locations, direction: .horizontal) + self.backgroundView.image = image + self.animationView?.image = image + self.animationView?.layer.removeAnimation(forKey: "movement") + + self.currentGradient = gradient + self.currentImageHeight = imageHeight + } + + let animationView: UIImageView + if let current = self.animationView { + animationView = current + } else { + animationView = UIImageView() + animationView.image = self.backgroundView.image + self.animationView = animationView + self.backgroundView.addSubview(animationView) + } + + animationView.bounds = CGRect(origin: .zero, size: CGSize(width: size.width * 2.4, height: size.height)) + if animationView.layer.animation(forKey: "movement") == nil { + animationView.center = CGPoint(x: animationView.bounds.width / 2.0 - animationView.bounds.width * 0.35, y: size.height / 2.0) + } + self.setupGradientAnimations(size: size, gradient: gradient) + } + + private func setupGradientAnimations(size: CGSize, gradient: ButtonComponent.Background.Gradient) { + guard let animationView = self.animationView else { + return + } + + let duration: Double + switch gradient.animation { + case let .horizontalShift(value): + duration = value + } + + if animationView.layer.animation(forKey: "movement") == nil { + let offset = (animationView.bounds.width - size.width) / 2.0 + let previousValue = animationView.center.x + var newValue: CGFloat = offset + if offset - previousValue < animationView.bounds.width * 0.25 { + newValue -= animationView.bounds.width * 0.35 + } + animationView.center = CGPoint(x: newValue, y: animationView.bounds.height / 2.0) + + CATransaction.begin() + + let animation = CABasicAnimation(keyPath: "position.x") + animation.duration = duration + animation.fromValue = previousValue + animation.toValue = newValue + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + CATransaction.setCompletionBlock { [weak self] in + self?.setupGradientAnimations(size: size, gradient: gradient) + } + + animationView.layer.add(animation, forKey: "movement") + CATransaction.commit() + } + } +} + private class ButtonShimmeringView: UIView { private var shimmerView = ShimmerEffectForegroundView() private var borderView = UIView() @@ -813,7 +1047,11 @@ private class ButtonShimmeringView: UIView { compositingFilter = nil } - self.backgroundColor = background.color + if let gradient = background.gradient, gradient.colors.count > 1 { + self.backgroundColor = .clear + } else { + self.backgroundColor = background.color + } self.layer.cornerRadius = cornerRadius self.borderMaskView.layer.cornerRadius = cornerRadius diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index 86d49a667c..98e93cd59c 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -74,6 +74,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu public var shortName: String public var avatarImage: UIImage? public var audioOutput: AudioOutput + public var canSwitchAudioOutput: Bool public var isLocalAudioMuted: Bool public var isRemoteAudioMuted: Bool public var localVideo: VideoSource? @@ -90,6 +91,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu shortName: String, avatarImage: UIImage?, audioOutput: AudioOutput, + canSwitchAudioOutput: Bool, isLocalAudioMuted: Bool, isRemoteAudioMuted: Bool, localVideo: VideoSource?, @@ -105,6 +107,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.shortName = shortName self.avatarImage = avatarImage self.audioOutput = audioOutput + self.canSwitchAudioOutput = canSwitchAudioOutput self.isLocalAudioMuted = isLocalAudioMuted self.isRemoteAudioMuted = isRemoteAudioMuted self.localVideo = localVideo @@ -134,6 +137,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu if lhs.audioOutput != rhs.audioOutput { return false } + if lhs.canSwitchAudioOutput != rhs.canSwitchAudioOutput { + return false + } if lhs.isLocalAudioMuted != rhs.isLocalAudioMuted { return false } @@ -822,7 +828,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu self.flipCameraAction?() }), at: 0) } else { - buttons.insert(ButtonGroupView.Button(content: .speaker(audioOutput: params.state.audioOutput), isEnabled: !isTerminated, action: { [weak self] in + buttons.insert(ButtonGroupView.Button(content: .speaker(audioOutput: params.state.audioOutput), isEnabled: !isTerminated && params.state.canSwitchAudioOutput, action: { [weak self] in guard let self else { return } diff --git a/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal b/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal index 8834ab1672..0ccd808798 100644 --- a/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal +++ b/submodules/TelegramUI/Components/Calls/VoiceChatActionButton/Metal/VoiceChatActionButtonShaders.metal @@ -7,15 +7,6 @@ struct Rectangle { float2 size; }; -constant static float2 quadVertices[6] = { - float2(0.0, 0.0), - float2(1.0, 0.0), - float2(0.0, 1.0), - float2(1.0, 0.0), - float2(0.0, 1.0), - float2(1.0, 1.0) -}; - struct QuadVertexOut { float4 position [[position]]; float2 uv; diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift index d9d44d6857..29742fbe50 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import ComponentFlow -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -145,7 +144,7 @@ final class CameraLiveStreamComponent: Component { if let story = component.story { if self.storyContentDisposable == nil { - let storyContent = SingleStoryContentContextImpl(context: component.context, storyId: StoryId(peerId: component.peerId, id: story.id), storyItem: story, readGlobally: false) + let storyContent = SingleStoryContentContextImpl(context: component.context, storyId: EngineStoryId(peerId: component.peerId, id: story.id), storyItem: story, readGlobally: false) self.storyContent = storyContent self.storyContentDisposable = (storyContent.state |> deliverOnMainQueue).start(next: { [weak self] state in diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 456d01e1fc..9b09ee1d8c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1378,7 +1378,7 @@ private final class CameraScreenComponent: CombinedComponent { let previewHeight = floorToScreenPixels(availableSize.width * 1.77778) if !isTablet { if availableSize.height < previewHeight + 30.0 { - controlsBottomInset = -48.0 + controlsBottomInset = -70.0 } } @@ -1505,6 +1505,7 @@ private final class CameraScreenComponent: CombinedComponent { hasAppeared: component.hasAppeared && hasAllRequiredAccess, hasAccess: hasAllRequiredAccess, hideControls: component.cameraState.collageProgress > 1.0 - .ulpOfOne, + controlsBottomInset: controlsBottomInset, collageProgress: component.cameraState.collageProgress, collageCount: component.cameraState.isCollageEnabled ? component.cameraState.collageGrid.count : nil, tintColor: controlsTintColor, @@ -1957,7 +1958,7 @@ private final class CameraScreenComponent: CombinedComponent { availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) - if component.cameraState.isCollageEnabled { + if state.displayingCollageSelection { nextButtonX += 48.0 } var collageButtonX = nextButtonX @@ -2111,7 +2112,7 @@ private final class CameraScreenComponent: CombinedComponent { if isTablet { availableModeControlSize = CGSize(width: floor(panelWidth), height: 120.0) } else { - availableModeControlSize = availableSize + availableModeControlSize = CGSize(width: availableSize.width - 140.0, height: availableSize.height) } var availableModes: [CameraState.CameraMode] = [.photo, .video] diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift index c1d2974745..e102a7057b 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraVideoSource.swift @@ -34,7 +34,7 @@ final class CameraVideoSource: VideoSource { let index = self.onUpdatedListeners.add(f) return ActionDisposable { [weak self] in - DispatchQueue.main.async { + Queue.mainQueue().async { guard let self else { return } @@ -139,6 +139,7 @@ final class LiveStreamMediaSource { videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: true, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: 1.625, additionalVideoRotation: 0.0, @@ -252,7 +253,7 @@ final class LiveStreamMediaSource { let index = self.onVideoUpdatedListeners.add(f) return ActionDisposable { [weak self] in - DispatchQueue.main.async { + Queue.mainQueue().async { guard let self else { return } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 1fecc3d492..eadde87f2c 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -310,7 +310,7 @@ private final class ShutterButtonContentComponent: Component { glassAlpha = 0.0 chromeAlpha = 0.65 labelAlpha = progress ? 0.0 : 1.0 - chromeSize = CGSize(width: 326.0, height: 53.0 - UIScreenPixel) + chromeSize = CGSize(width: 297.0, height: 53.0 - UIScreenPixel) hasProgress = progress } @@ -683,6 +683,7 @@ final class CaptureControlsComponent: Component { let hasAppeared: Bool let hasAccess: Bool let hideControls: Bool + let controlsBottomInset: CGFloat let collageProgress: Float let collageCount: Int? let tintColor: UIColor @@ -712,6 +713,7 @@ final class CaptureControlsComponent: Component { hasAppeared: Bool, hasAccess: Bool, hideControls: Bool, + controlsBottomInset: CGFloat, collageProgress: Float, collageCount: Int?, tintColor: UIColor, @@ -740,6 +742,7 @@ final class CaptureControlsComponent: Component { self.hasAppeared = hasAppeared self.hasAccess = hasAccess self.hideControls = hideControls + self.controlsBottomInset = controlsBottomInset self.collageProgress = collageProgress self.collageCount = collageCount self.tintColor = tintColor @@ -783,6 +786,9 @@ final class CaptureControlsComponent: Component { if lhs.hideControls != rhs.hideControls { return false } + if lhs.controlsBottomInset != rhs.controlsBottomInset { + return false + } if lhs.collageProgress != rhs.collageProgress { return false } @@ -1223,6 +1229,13 @@ final class CaptureControlsComponent: Component { let hideControls = component.hideControls + var bottomButtonSideInset: CGFloat = 16.0 + var bottomButtonTopInset: CGFloat = 21.0 + if component.controlsBottomInset < 0.0 { + bottomButtonSideInset = 9.0 + bottomButtonTopInset = -2.0 - UIScreenPixel + } + let galleryButtonFrame: CGRect let lockReferenceFrame: CGRect let gallerySize: CGSize @@ -1268,7 +1281,7 @@ final class CaptureControlsComponent: Component { galleryButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - galleryButtonSize.width) / 2.0), y: size.height - galleryButtonSize.height - 56.0), size: galleryButtonSize) lockReferenceFrame = .zero } else { - galleryButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: size.height + 21.0), size: galleryButtonSize) + galleryButtonFrame = CGRect(origin: CGPoint(x: bottomButtonSideInset, y: size.height + bottomButtonTopInset), size: galleryButtonSize) lockReferenceFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: floorToScreenPixels((size.height - galleryButtonSize.height) / 2.0)), size: galleryButtonSize) } if let galleryButtonView = self.galleryButtonView.view as? CameraButton.View { @@ -1360,7 +1373,7 @@ final class CaptureControlsComponent: Component { environment: {}, containerSize: availableSize ) - let bottomFlipButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - bottomFlipButtonSize.width - 16.0, y: 21.0), size: bottomFlipButtonSize) + let bottomFlipButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - bottomFlipButtonSize.width - bottomButtonSideInset, y: bottomButtonTopInset), size: bottomFlipButtonSize) if let bottomFlipButtonView = self.bottomFlipButton.view { if bottomFlipButtonView.superview == nil { self.bottomContainerView.contentView.addSubview(bottomFlipButtonView) @@ -1399,13 +1412,13 @@ final class CaptureControlsComponent: Component { environment: {}, containerSize: availableSize ) - let bottomFlipButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 21.0), size: bottomSettingsButtonSize) + let bottomSettingsButtonFrame = CGRect(origin: CGPoint(x: bottomButtonSideInset, y: bottomButtonTopInset), size: bottomSettingsButtonSize) if let bottomSettingsButtonView = self.bottomSettingsButton.view { if bottomSettingsButtonView.superview == nil { self.bottomContainerView.contentView.addSubview(bottomSettingsButtonView) } - transition.setBounds(view: bottomSettingsButtonView, bounds: CGRect(origin: .zero, size: bottomFlipButtonFrame.size)) - transition.setPosition(view: bottomSettingsButtonView, position: bottomFlipButtonFrame.center) + transition.setBounds(view: bottomSettingsButtonView, bounds: CGRect(origin: .zero, size: bottomSettingsButtonFrame.size)) + transition.setPosition(view: bottomSettingsButtonView, position: bottomSettingsButtonFrame.center) transition.setScale(view: bottomSettingsButtonView, scale: !isLiveStream || isLiveActive || isRecording || isTransitioning || hideControls ? 0.01 : 1.0) transition.setAlpha(view: bottomSettingsButtonView, alpha: !isLiveStream || isLiveActive || isRecording || isTransitioning || hideControls ? 0.0 : 1.0) @@ -1461,7 +1474,7 @@ final class CaptureControlsComponent: Component { let shutterButtonFrame = CGRect(origin: CGPoint(x: (availableSize.width - shutterButtonSize.width) / 2.0, y: (size.height - shutterButtonSize.height) / 2.0), size: shutterButtonSize) let guideSpacing: CGFloat = 9.0 - let guideSize = CGSize(width: isHolding ? component.isTablet ? 84.0 : 60.0 : 0.0, height: 1.0 + UIScreenPixel) + let guideSize = CGSize(width: isHolding ? (component.isTablet ? 84.0 : 60.0) : 0.0, height: 1.0 + UIScreenPixel) let guideAlpha: CGFloat = isHolding ? 1.0 : 0.0 let leftGuideFrame = CGRect(origin: CGPoint(x: shutterButtonFrame.minX - guideSpacing - guideSize.width, y: floorToScreenPixels((size.height - guideSize.height) / 2.0)), size: guideSize) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift index fc56774d12..2bfcd3b081 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ModeComponent.swift @@ -70,7 +70,20 @@ final class ModeComponent: Component { return true } - final class View: UIView, ComponentTaggedView { + final class View: UIView, ComponentTaggedView, UIScrollViewDelegate, UIGestureRecognizerDelegate { + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + private struct LayoutData { + var containerSize: CGSize + var selectedFrame: CGRect + var cornerRadius: CGFloat? + var isTablet: Bool + } + private var component: ModeComponent? private var state: EmptyComponentState? @@ -107,6 +120,10 @@ final class ModeComponent: Component { private var backgroundContainer = GlassBackgroundContainerView() private var liquidLensView: LiquidLensView? + private let scrollView = ScrollView() + private let selectedScrollView = UIView() + private var ignoreScrolling = false + private var layoutData: LayoutData? private var itemViews: [AnyHashable: ItemView] = [:] private var selectedItemViews: [AnyHashable: ItemView] = [:] @@ -132,6 +149,27 @@ final class ModeComponent: Component { self.layer.allowsGroupOpacity = true + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = false + self.scrollView.scrollsToTop = false + self.scrollView.clipsToBounds = true + self.scrollView.delegate = self + self.scrollView.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in + guard let self else { + return false + } + return self.scrollView.contentOffset.x > .ulpOfOne + } + + self.selectedScrollView.clipsToBounds = true + self.selectedScrollView.isUserInteractionEnabled = false + self.addSubview(self.backgroundView) self.backgroundView.addSubview(self.backgroundContainer) } @@ -159,13 +197,14 @@ final class ModeComponent: Component { return self.backgroundView.frame.contains(point) } - private func item(at point: CGPoint) -> AnyHashable? { + private func item(at point: CGPoint, in view: UIView) -> AnyHashable? { var closestItem: (AnyHashable, CGFloat)? for (id, itemView) in self.itemViews { - if itemView.frame.contains(point) { + let itemFrame = itemView.convert(itemView.bounds, to: view) + if itemFrame.contains(point) { return id } else { - let distance = abs(point.x - itemView.center.x) + let distance = abs(point.x - itemFrame.midX) if let closestItemValue = closestItem { if closestItemValue.1 > distance { closestItem = (id, distance) @@ -178,22 +217,46 @@ final class ModeComponent: Component { return closestItem?.0 } - @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { - guard let component = self.component, let liquidLensView = self.liquidLensView else { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if self.ignoreScrolling { return } - let location = recognizer.location(in: liquidLensView.contentView) + self.updateScrolling(transition: .immediate) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer === self.tabSelectionRecognizer && otherGestureRecognizer === self.scrollView.panGestureRecognizer { + return true + } + if otherGestureRecognizer === self.tabSelectionRecognizer && gestureRecognizer === self.scrollView.panGestureRecognizer { + return true + } + return false + } + + @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) { + guard let component = self.component else { + return + } + let location = recognizer.location(in: self) switch recognizer.state { case .began: - if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] { + if let itemId = self.item(at: location, in: self), let itemView = self.itemViews[itemId] { let startX = itemView.frame.minX - 4.0 self.selectionGestureState = (startX, startX, itemId) self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) } case .changed: if var selectionGestureState = self.selectionGestureState { + let translation = recognizer.translation(in: self) + if !component.isTablet && self.scrollView.isScrollEnabled && abs(translation.x) > 6.0 && abs(translation.x) > abs(translation.y) { + self.selectionGestureState = nil + recognizer.state = .cancelled + self.state?.updated(transition: .spring(duration: 0.4), isLocal: true) + return + } selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x - if let itemId = self.item(at: location) { + if let itemId = self.item(at: location, in: self) { selectionGestureState.itemId = itemId } self.selectionGestureState = selectionGestureState @@ -214,8 +277,38 @@ final class ModeComponent: Component { break } } + + private func updateScrolling(transition: ComponentTransition) { + guard let component = self.component, let liquidLensView = self.liquidLensView, let layoutData = self.layoutData else { + return + } + + let contentOffsetX = layoutData.isTablet ? 0.0 : self.scrollView.bounds.minX + var lensSelection = (origin: layoutData.selectedFrame.origin, size: layoutData.selectedFrame.size) + if let selectionGestureState = self.selectionGestureState, !layoutData.isTablet { + lensSelection.origin = CGPoint(x: selectionGestureState.currentX, y: 0.0) + } + + if layoutData.isTablet { + lensSelection.size.width = layoutData.containerSize.width + } else { + lensSelection.origin.x -= contentOffsetX + lensSelection.origin.y = 0.0 + lensSelection.size.height = layoutData.containerSize.height + } + + let maxSelectionOriginX = max(0.0, layoutData.containerSize.width - lensSelection.size.width) + transition.setFrame(view: self.selectedScrollView, frame: CGRect(origin: .zero, size: layoutData.containerSize)) + transition.setBounds(view: self.selectedScrollView, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: layoutData.containerSize)) + + liquidLensView.update(size: layoutData.containerSize, cornerRadius: layoutData.cornerRadius, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.origin.x, maxSelectionOriginX)), y: lensSelection.origin.y), selectionSize: lensSelection.size, inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil && !layoutData.isTablet, isCollapsed: false, transition: transition) + self.backgroundContainer.update(size: layoutData.containerSize, isDark: true, transition: .immediate) + + self.scrollView.isScrollEnabled = !component.isTablet && self.scrollView.contentSize.width > self.scrollView.bounds.width + .ulpOfOne + } func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component self.component = component self.state = state @@ -228,16 +321,36 @@ final class ModeComponent: Component { liquidLensView = LiquidLensView(kind: isTablet ? .noContainer : .externalContainer) self.liquidLensView = liquidLensView self.backgroundContainer.contentView.addSubview(liquidLensView) + liquidLensView.contentView.addSubview(self.scrollView) + liquidLensView.selectedContentView.addSubview(self.selectedScrollView) let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:))) + tabSelectionRecognizer.delegate = self + tabSelectionRecognizer.cancelsTouchesInView = false self.tabSelectionRecognizer = tabSelectionRecognizer liquidLensView.addGestureRecognizer(tabSelectionRecognizer) } + if self.scrollView.superview == nil { + liquidLensView.contentView.addSubview(self.scrollView) + } + if self.selectedScrollView.superview == nil { + liquidLensView.selectedContentView.addSubview(self.selectedScrollView) + } self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11) - let inset: CGFloat = 23.0 - let spacing: CGFloat = isTablet ? 9.0 : 40.0 + var inset: CGFloat = 23.0 + let spacing: CGFloat + if isTablet { + spacing = 9.0 + } else { + if availableSize.width < 200.0 { + inset = 20.0 + spacing = 24.0 + } else { + spacing = 40.0 + } + } var i = 0 var itemFrame = CGRect(origin: isTablet ? .zero : CGPoint(x: inset, y: 0.0), size: buttonSize) @@ -257,12 +370,16 @@ final class ModeComponent: Component { itemView = ItemView() itemView.isUserInteractionEnabled = false self.itemViews[id] = itemView - liquidLensView.contentView.addSubview(itemView) selectedItemView = ItemView() selectedItemView.isUserInteractionEnabled = false self.selectedItemViews[id] = selectedItemView - liquidLensView.selectedContentView.addSubview(selectedItemView) + } + if itemView.superview !== self.scrollView { + self.scrollView.addSubview(itemView) + } + if selectedItemView.superview !== self.selectedScrollView { + self.selectedScrollView.addSubview(selectedItemView) } let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor) @@ -297,47 +414,68 @@ final class ModeComponent: Component { transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in itemView.removeFromSuperview() }) + + if let selectedItemView = self.selectedItemViews[id] { + transition.setAlpha(view: selectedItemView, alpha: 0.0, completion: { _ in + selectedItemView.removeFromSuperview() + }) + } } } for id in removeKeys { self.itemViews.removeValue(forKey: id) + self.selectedItemViews.removeValue(forKey: id) } let totalSize: CGSize let size: CGSize + let contentSize: CGSize var cornerRadius: CGFloat? if isTablet { totalSize = CGSize(width: availableSize.width, height: tabletButtonSize.height * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1)) size = CGSize(width: availableSize.width, height: availableSize.height) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: totalSize)) + contentSize = totalSize cornerRadius = 20.0 } else { size = CGSize(width: availableSize.width, height: buttonSize.height) totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - totalSize.width) / 2.0), y: 0.0), size: totalSize)) + let visibleSize = CGSize(width: min(availableSize.width, totalSize.width), height: totalSize.height) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - visibleSize.width) / 2.0), y: 0.0), size: visibleSize)) + contentSize = totalSize } let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size) transition.setFrame(view: self.backgroundContainer, frame: containerFrame) - - let selectionFrame = selectedFrame.insetBy(dx: -23.0, dy: 3.0) - var lensSelection: (origin: CGPoint, size: CGSize) - if let selectionGestureState = self.selectionGestureState, !isTablet { - lensSelection = (CGPoint(x: selectionGestureState.currentX, y: 0.0), selectionFrame.size) - } else { - lensSelection = (CGPoint(x: selectionFrame.minX, y: selectionFrame.minY), selectionFrame.size) - } - - if isTablet { - lensSelection.size.width = size.width - } else { - lensSelection.size.height = containerFrame.size.height - lensSelection.origin.y = 0.0 - } - transition.setFrame(view: liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size)) - liquidLensView.update(size: containerFrame.size, cornerRadius: cornerRadius, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.origin.x, containerFrame.size.width - lensSelection.size.width)), y: lensSelection.origin.y), selectionSize: lensSelection.size, inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil && !isTablet, isCollapsed: false, transition: transition) - self.backgroundContainer.update(size: containerFrame.size, isDark: true, transition: .immediate) + + let scrollViewFrame = CGRect(origin: .zero, size: containerFrame.size) + transition.setFrame(view: self.scrollView, frame: scrollViewFrame) + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + self.scrollView.isScrollEnabled = !isTablet && contentSize.width > scrollViewFrame.width + .ulpOfOne + + self.layoutData = LayoutData(containerSize: containerFrame.size, selectedFrame: selectedFrame.insetBy(dx: -inset, dy: 3.0), cornerRadius: cornerRadius, isTablet: isTablet) + + self.ignoreScrolling = true + var scrollViewBounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollViewFrame.size) + let maxContentOffsetX = max(0.0, contentSize.width - scrollViewFrame.width) + let shouldFocusOnSelectedItem = previousComponent?.currentMode != component.currentMode || previousComponent?.availableModes != component.availableModes || self.scrollView.bounds.size != scrollViewFrame.size + if self.scrollView.isScrollEnabled && shouldFocusOnSelectedItem { + let scrollLookahead = min(60.0, scrollViewBounds.width * 0.25) + if scrollViewBounds.minX + scrollViewBounds.width - scrollLookahead < selectedFrame.maxX { + scrollViewBounds.origin.x = selectedFrame.maxX - scrollViewBounds.width + scrollLookahead + } + if scrollViewBounds.minX > selectedFrame.minX - scrollLookahead { + scrollViewBounds.origin.x = selectedFrame.minX - scrollLookahead + } + } + scrollViewBounds.origin.x = max(0.0, min(scrollViewBounds.origin.x, maxContentOffsetX)) + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + self.ignoreScrolling = false + + self.updateScrolling(transition: transition) return size } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift index ed799fd9db..72289e0be8 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ShutterBlobView.swift @@ -43,7 +43,7 @@ final class ShutterBlobView: UIView { case .generic, .video, .transientToFlip: return CGSize(width: 0.63, height: 0.63) case .live: - return CGSize(width: 3.4, height: 0.55) + return CGSize(width: 3.1, height: 0.55) case .transientToLock, .lock, .stopVideo: return CGSize(width: 0.275, height: 0.275) } diff --git a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD index c60929eefc..00c812385b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift index cf87df8389..8e4bb123d8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAgeRestrictionAlertController/Sources/ChatAgeRestrictionAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD index 01012aac94..9d60cdcca9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AvatarNode", "//submodules/ContextUI", diff --git a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift index 4687fad8fe..27b644ffa8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatAvatarNavigationNode/Sources/ChatAvatarNavigationNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AvatarNode import ContextUI @@ -113,7 +112,18 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { self.avatarNode.isHidden = true } - public func setPeer(context: AccountContext, theme: PresentationTheme, peer: EnginePeer?, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, clipStyle: AvatarNodeClipStyle = .round, synchronousLoad: Bool = false, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), storeUnrounded: Bool = false) { + public func setPeer( + context: AccountContext, + theme: PresentationTheme, + peer: EnginePeer?, + authorOfMessage: MessageReference? = nil, + overrideImage: AvatarNodeImageOverride? = nil, + emptyColor: UIColor? = nil, + clipStyle: AvatarNodeClipStyle = .round, + synchronousLoad: Bool = false, + displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), + storeUnrounded: Bool = false + ) { self.context = context if let statusComponentView = self.statusView.view { @@ -163,6 +173,10 @@ public final class ChatAvatarNavigationNode: ASDisplayNode { profilePhoto = maybePhoto isKnown = true } + if profilePhoto == nil, case let .known(maybePhoto) = cachedPeerData.fallbackPhoto { + profilePhoto = maybePhoto + isKnown = true + } } if isKnown { diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD index 9ae227b70d..80c07a35a1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift index 39aec40872..3225921068 100644 --- a/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatBotInfoItem/Sources/ChatBotInfoItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TextFormat @@ -150,7 +149,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode { } let videoContent = NativeVideoContent( - id: .message(0, MediaId(namespace: 0, id: Int64.random(in: 0.. deliverOnMainQueue).startStandalone(next: { [weak self] peer in @@ -452,7 +452,7 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode, UIScrollViewDeleg for attribute in message.attributes { if let attribute = attribute as? ReplyMarkupMessageAttribute { if attribute.flags.contains(.once) { - self.controllerInteraction.dismissReplyMarkupMessage(message) + self.controllerInteraction.dismissReplyMarkupMessage(message._asMessage()) } break } diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD index 0d53fbe7c2..9271748454 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", "//submodules/AlertUI", diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index 6c92a201d3..6b7d770bab 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import AlertUI @@ -63,7 +62,7 @@ private func titleAndColorForAction(_ action: SubscriberAction, theme: Presentat } } -private func actionForPeer(context: AccountContext, peer: Peer, interfaceState: ChatPresentationInterfaceState, isJoining: Bool, isMuted: Bool) -> SubscriberAction? { +private func actionForPeer(context: AccountContext, peer: EnginePeer, interfaceState: ChatPresentationInterfaceState, isJoining: Bool, isMuted: Bool) -> SubscriberAction? { if case let .replyThread(message) = interfaceState.chatLocation, message.peerId == context.account.peerId { if let peer = interfaceState.savedMessagesTopicPeer { if case let .channel(channel) = peer { @@ -79,9 +78,9 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState: return .openChat } else if case .pinnedMessages = interfaceState.subject { var canManagePin = false - if let channel = peer as? TelegramChannel { + if case let .channel(channel) = peer { canManagePin = channel.hasPermission(.pinMessages) - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { switch group.role { case .creator, .admin: canManagePin = true @@ -92,7 +91,7 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState: canManagePin = true } } - } else if let _ = peer as? TelegramUser, interfaceState.explicitelyCanPinMessages { + } else if case .user = peer, interfaceState.explicitelyCanPinMessages { canManagePin = true } if canManagePin { @@ -101,7 +100,7 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState: return .hidePinnedMessages } } else { - if let channel = peer as? TelegramChannel { + if case let .channel(channel) = peer { if case .broadcast = channel.info, isJoining { if isMuted { return .unmuteNotifications @@ -174,7 +173,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { private var presentationInterfaceState: ChatPresentationInterfaceState? - private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)? + private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics, DeviceMetrics)? public override init() { super.init() @@ -214,9 +213,10 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { switch action { case .join, .joinGroup, .applyToJoin: self.isJoining = true - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics, deviceMetrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, deviceMetrics: deviceMetrics, force: true) } + var didJoin = false self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peer.id, hash: nil) |> afterDisposed { [weak self] in Queue.mainQueue().async { @@ -224,7 +224,19 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { strongSelf.isJoining = false } } - }).startStrict(error: { [weak self] error in + }).startStrict(next: { [weak self] result in + guard let strongSelf = self else { + return + } + switch result { + case .joined: + didJoin = true + case let .webView(webView): + if let controller = strongSelf.interfaceInteraction?.getNavigationController()?.viewControllers.last as? ViewController { + context.sharedContext.openJoinChatWebView(context: context, parentController: controller, updatedPresentationData: nil, webView: webView) + } + } + }, error: { [weak self] error in guard let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else { return } @@ -255,6 +267,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { guard let self else { return } + if !didJoin { + return + } Queue.mainQueue().after(0.5) { if let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer { var canEditRank = false @@ -292,8 +307,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } } - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false) + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { + return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, force: false) } private var displayedGiftOrSuggestTooltip = false @@ -389,9 +404,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { }) } - private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat { + private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, force: Bool) -> CGFloat { let isFirstTime = self.layoutData == nil - self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) + self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics, deviceMetrics) var transition = transition if !isFirstTime && !transition.isAnimated { @@ -401,7 +416,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.presentationInterfaceState = interfaceState var centerAction: (title: String, isAccent: Bool)? - if let context = self.context, let peer = interfaceState.renderedPeer?.peer, let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) { + if let context = self.context, let peer = interfaceState.renderedPeer?.peer, let action = actionForPeer(context: context, peer: EnginePeer(peer), interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) { self.action = action let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) @@ -433,10 +448,9 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { var leftInset = leftInset + 8.0 var rightInset = rightInset + 8.0 - if bottomInset <= 32.0 { - leftInset += 18.0 - rightInset += 18.0 - } + let compactBottomSideInset = self.compactBottomSideInset(bottomInset: bottomInset, deviceMetrics: deviceMetrics) + leftInset += compactBottomSideInset + rightInset += compactBottomSideInset var leftPanelItems: [GlassControlGroupComponent.Item] = [] if displaySuggestPost { diff --git a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD index 7dc886d4b5..f0837f9006 100644 --- a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/Display", "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/PhotoResources", diff --git a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift index 0b75b02a83..5db53b0421 100644 --- a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AVFoundation @@ -134,7 +133,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont imageDimensions = externalReference.content?.dimensions?.cgSize if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource , let dimensions = content.dimensions { - videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) + videoFileReference = .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) imageResource = nil } case let .internalReference(internalReference): @@ -197,7 +196,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont if updatedImageResource { if let imageResource = imageResource { let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: Int32(fittedImageDimensions.width * 2.0), height: Int32(fittedImageDimensions.height * 2.0)), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) updateImageSignal = chatMessagePhoto(postbox: self.context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage)) } else { updateImageSignal = .complete() diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD index 0d443277ba..e07177bd3a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/AppBundle", diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index bfee82ed6b..669604b158 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AppBundle @@ -252,10 +251,10 @@ public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNod ) inputNodeInteraction.displayStickerPlaceholder = false - let index = ItemCollectionItemIndex(index: 0, id: 0) - let collectionId = ItemCollectionId(namespace: 0, id: 0) + let index = EngineItemCollectionItemIndex(index: 0, id: 0) + let collectionId = EngineItemCollectionId(namespace: 0, id: 0) let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: []) - let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {}) + let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: EngineItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {}) strongSelf.stickerItem = item if isFirstTime { @@ -297,177 +296,6 @@ public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNod } } -public final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, ASGestureRecognizerDelegate { - private let context: AccountContext - private let interaction: ChatPanelInterfaceInteraction? - - private let titleNode: ImmediateTextNode - private let textNode: ImmediateTextNode - - private var stickerItem: ChatMediaInputStickerGridItem? - public let stickerNode: ChatMediaInputStickerGridItemNode - - private var currentTheme: PresentationTheme? - private var currentStrings: PresentationStrings? - - private var didSetupSticker = false - private let disposable = MetaDisposable() - - public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { - self.context = context - self.interaction = interaction - - self.titleNode = ImmediateTextNode() - self.titleNode.maximumNumberOfLines = 0 - self.titleNode.lineSpacing = 0.15 - self.titleNode.textAlignment = .center - self.titleNode.isUserInteractionEnabled = false - self.titleNode.displaysAsynchronously = false - - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 0 - self.textNode.lineSpacing = 0.15 - self.textNode.textAlignment = .center - self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = false - - self.stickerNode = ChatMediaInputStickerGridItemNode() - - super.init() - - self.addSubnode(self.titleNode) - self.addSubnode(self.textNode) - self.addSubnode(self.stickerNode) - } - - override public func didLoad() { - super.didLoad() - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:))) - tapRecognizer.delegate = self.wrappedGestureRecognizerDelegate - self.stickerNode.view.addGestureRecognizer(tapRecognizer) - } - - deinit { - self.disposable.dispose() - } - - public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true - } - - @objc private func stickerTapGesture(_ gestureRecognizer: UITapGestureRecognizer) { - guard let stickerItem = self.stickerItem else { - return - } - let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file._parse()), false, self.view, self.stickerNode.bounds, nil, []) - } - - public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { - if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { - self.currentTheme = interfaceState.theme - self.currentStrings = interfaceState.strings - - var displayName = "" - let distance = interfaceState.peerNearbyData?.distance ?? 0 - - if let renderedPeer = interfaceState.renderedPeer { - if let chatPeer = renderedPeer.chatOrMonoforumMainPeer { - displayName = EnginePeer(chatPeer).compactDisplayTitle - } - } - - let titleString = interfaceState.strings.Conversation_PeerNearbyTitle(displayName, shortStringForDistance(strings: interfaceState.strings, distance: distance)).string - let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - - self.titleNode.attributedText = NSAttributedString(string: titleString, font: titleFont, textColor: serviceColor.primaryText) - - self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_PeerNearbyText, font: messageFont, textColor: serviceColor.primaryText) - } - - let stickerSize = CGSize(width: 160.0, height: 160.0) - if let item = self.stickerItem { - self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true) - } else if !self.didSetupSticker { - let sticker: Signal - if let preloadedSticker = interfaceState.greetingData?.sticker { - sticker = preloadedSticker - } else { - sticker = self.context.engine.stickers.randomGreetingSticker() - |> map { item -> TelegramMediaFile? in - return item?.file - } - } - - self.didSetupSticker = true - self.disposable.set((sticker - |> deliverOnMainQueue).startStrict(next: { [weak self] sticker in - if let strongSelf = self, let sticker = sticker { - let inputNodeInteraction = ChatMediaInputNodeInteraction( - navigateToCollectionId: { _ in - }, - navigateBackToStickers: { - }, - setGifMode: { _ in - }, - openSettings: { - }, - openTrending: { _ in - }, - dismissTrendingPacks: { _ in - }, - toggleSearch: { _, _, _ in - }, - openPeerSpecificSettings: { - }, - dismissPeerSpecificSettings: { - }, - clearRecentlyUsedStickers: { - } - ) - inputNodeInteraction.displayStickerPlaceholder = false - - let index = ItemCollectionItemIndex(index: 0, id: 0) - let collectionId = ItemCollectionId(namespace: 0, id: 0) - let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: []) - let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {}) - strongSelf.stickerItem = item - strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true) - strongSelf.stickerNode.isVisibleInGrid = true - strongSelf.stickerNode.updateIsPanelVisible(true) - } - })) - } - - let insets = UIEdgeInsets(top: 15.0, left: 15.0, bottom: 15.0, right: 15.0) - let titleSpacing: CGFloat = 5.0 - let stickerSpacing: CGFloat = 5.0 - - var contentWidth: CGFloat = 210.0 - var contentHeight: CGFloat = 0.0 - - let titleSize = self.titleNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude)) - let textSize = self.textNode.updateLayout(CGSize(width: contentWidth, height: CGFloat.greatestFiniteMagnitude)) - - contentWidth = max(contentWidth, max(titleSize.width, textSize.width)) - - contentHeight += titleSize.height + titleSpacing + textSize.height + stickerSpacing + stickerSize.height - - let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight)) - - let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - titleSize.width) / 2.0), y: contentRect.minY), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let textFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - - let stickerFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - stickerSize.width) / 2.0), y: textFrame.maxY + stickerSpacing), size: stickerSize) - transition.updateFrame(node: self.stickerNode, frame: stickerFrame) - - return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size - } -} - private final class ChatEmptyNodeSecretChatContent: ASDisplayNode, ChatEmptyNodeContent { private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode @@ -1461,7 +1289,6 @@ private enum ChatEmptyNodeContentType: Equatable { case secret case group case cloud - case peerNearby case greeting case topic case premiumRequired @@ -1851,8 +1678,6 @@ public final class ChatEmptyNode: ASDisplayNode { contentType = .group } else if let channel = peer as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isCreator) && !channel.flags.contains(.isGigagroup) && !channel.isMonoForum { contentType = .group - } else if let _ = interfaceState.peerNearbyData { - contentType = .peerNearby } else if let peer = peer as? TelegramUser { if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil { contentType = .starsRequired(sendPaidMessageStars.value) @@ -1917,8 +1742,6 @@ public final class ChatEmptyNode: ASDisplayNode { }) interfaceInteraction.presentControllerInCurrent(controller, nil) } - case .peerNearby: - node = ChatEmptyNodeNearbyChatContent(context: self.context, interaction: self.interaction) case .greeting: node = ChatEmptyNodeGreetingChatContent(context: self.context, interaction: self.interaction) updateGreetingSticker = true @@ -1939,7 +1762,7 @@ public final class ChatEmptyNode: ASDisplayNode { } } switch contentType { - case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud: + case .greeting, .premiumRequired, .starsRequired, .cloud: self.isUserInteractionEnabled = true default: self.isUserInteractionEnabled = false diff --git a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift index ebea61edb7..4974eaedb1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Components/Chat/ChatHistoryEntry/Sources/ChatHistoryEntry.swift @@ -1,4 +1,3 @@ -import Postbox import TelegramCore import TelegramPresentationData import MergeLists @@ -18,10 +17,11 @@ public struct ChatMessageEntryAttributes: Equatable { public var updatingMedia: ChatUpdatingMessageMedia? public var isPlaying: Bool public var isCentered: Bool - public var authorStoryStats: PeerStoryStats? + public var authorStoryStats: EnginePeerStoryStats? public var displayContinueThreadFooter: Bool + public var pinToTop: Bool - public init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool, isCentered: Bool, authorStoryStats: PeerStoryStats?, displayContinueThreadFooter: Bool) { + public init(rank: CachedChannelAdminRank?, isContact: Bool, contentTypeHint: ChatMessageEntryContentType, updatingMedia: ChatUpdatingMessageMedia?, isPlaying: Bool, isCentered: Bool, authorStoryStats: EnginePeerStoryStats?, displayContinueThreadFooter: Bool, pinToTop: Bool) { self.rank = rank self.isContact = isContact self.contentTypeHint = contentTypeHint @@ -30,6 +30,7 @@ public struct ChatMessageEntryAttributes: Equatable { self.isCentered = isCentered self.authorStoryStats = authorStoryStats self.displayContinueThreadFooter = displayContinueThreadFooter + self.pinToTop = pinToTop } public init() { @@ -41,6 +42,7 @@ public struct ChatMessageEntryAttributes: Equatable { self.isCentered = false self.authorStoryStats = nil self.displayContinueThreadFooter = false + self.pinToTop = false } } @@ -51,10 +53,10 @@ public enum ChatInfoData: Equatable { } public enum ChatHistoryEntry: Identifiable, Comparable { - case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) - case MessageGroupEntry(Int64, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)], ChatPresentationData) - case UnreadEntry(MessageIndex, ChatPresentationData) - case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) + case MessageEntry(EngineRawMessage, ChatPresentationData, Bool, EngineMessageHistoryEntryLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) + case MessageGroupEntry(Int64, [(EngineRawMessage, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, EngineMessageHistoryEntryLocation?)], ChatPresentationData) + case UnreadEntry(EngineMessage.Index, ChatPresentationData) + case ReplyCountEntry(EngineMessage.Index, Bool, Int, ChatPresentationData) case ChatInfoEntry(ChatInfoData, ChatPresentationData) public var stableId: UInt64 { @@ -86,7 +88,7 @@ public enum ChatHistoryEntry: Identifiable, Comparable { } } - public var index: MessageIndex { + public var index: EngineMessage.Index { switch self { case let .MessageEntry(message, _, _, _, _, _): return message.index @@ -99,14 +101,14 @@ public enum ChatHistoryEntry: Identifiable, Comparable { case let .ChatInfoEntry(infoData, _): switch infoData { case .newThreadInfo: - return MessageIndex.absoluteUpperBound() + return EngineMessage.Index.absoluteUpperBound() default: - return MessageIndex.absoluteLowerBound() + return EngineMessage.Index.absoluteLowerBound() } } } - public var firstIndex: MessageIndex { + public var firstIndex: EngineMessage.Index { switch self { case let .MessageEntry(message, _, _, _, _, _): return message.index @@ -119,9 +121,9 @@ public enum ChatHistoryEntry: Identifiable, Comparable { case let .ChatInfoEntry(infoData, _): switch infoData { case .newThreadInfo: - return MessageIndex.absoluteUpperBound() + return EngineMessage.Index.absoluteUpperBound() default: - return MessageIndex.absoluteLowerBound() + return EngineMessage.Index.absoluteLowerBound() } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD index f192f2f409..4bba448043 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/MergeLists", diff --git a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift index c87f824712..c9ca8638b1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatHistorySearchContainerNode/Sources/ChatHistorySearchContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import MergeLists @@ -35,11 +34,11 @@ private extension ListMessageItemInteraction { } private enum ChatHistorySearchEntryStableId: Hashable { - case messageId(MessageId) + case messageId(EngineMessage.Id) } private enum ChatHistorySearchEntry: Comparable, Identifiable { - case message(Message, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationFontSize) + case message(EngineRawMessage, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationFontSize) var stableId: ChatHistorySearchEntryStableId { switch self { @@ -88,7 +87,7 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable { } } - func item(context: AccountContext, peerId: PeerId, interaction: ChatControllerInteraction) -> ListViewItem { + func item(context: AccountContext, peerId: EnginePeer.Id, interaction: ChatControllerInteraction) -> ListViewItem { switch self { case let .message(message, theme, strings, dateTimeFormat, fontSize): return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: .builtin(WallpaperSettings())), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(id: peerId), interaction: ListMessageItemInteraction(controllerInteraction: interaction), message: message, selection: .none, displayHeader: true) @@ -104,7 +103,7 @@ private struct ChatHistorySearchContainerTransition { let displayingResults: Bool } -private func chatHistorySearchContainerPreparedTransition(from fromEntries: [ChatHistorySearchEntry], to toEntries: [ChatHistorySearchEntry], query: String, displayingResults: Bool, context: AccountContext, peerId: PeerId, interaction: ChatControllerInteraction) -> ChatHistorySearchContainerTransition { +private func chatHistorySearchContainerPreparedTransition(from fromEntries: [ChatHistorySearchEntry], to toEntries: [ChatHistorySearchEntry], query: String, displayingResults: Bool, context: AccountContext, peerId: EnginePeer.Id, interaction: ChatControllerInteraction) -> ChatHistorySearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -126,7 +125,7 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten private var containerLayout: (ContainerViewLayout, CGFloat)? private var currentEntries: [ChatHistorySearchEntry]? - public var currentMessages: [MessageId: Message]? + public var currentMessages: [EngineMessage.Id: EngineRawMessage]? private var currentQuery: String? private let searchQuery = Promise() @@ -149,7 +148,7 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten return true } - public init(context: AccountContext, peerId: PeerId, threadId: Int64?, tagMask: MessageTags, interfaceInteraction: ChatControllerInteraction) { + public init(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64?, tagMask: EngineMessage.Tags, interfaceInteraction: ChatControllerInteraction) { self.context = context let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -194,14 +193,14 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten self.searchQueryDisposable.set((self.searchQuery.get() |> deliverOnMainQueue).startStrict(next: { [weak self] query in if let strongSelf = self { - let signal: Signal<([ChatHistorySearchEntry], [MessageId: Message])?, NoError> + let signal: Signal<([ChatHistorySearchEntry], [EngineMessage.Id: EngineRawMessage])?, NoError> if let query = query, !query.isEmpty { - let foundRemoteMessages: Signal<[Message], NoError> = context.engine.messages.searchMessages(location: .peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: threadId, minDate: nil, maxDate: nil), query: query, state: nil) + let foundRemoteMessages: Signal<[EngineRawMessage], NoError> = context.engine.messages.searchMessages(location: .peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: threadId, minDate: nil, maxDate: nil), query: query, state: nil) |> map { $0.0.messages } |> delay(0.2, queue: Queue.concurrentDefaultQueue()) signal = combineLatest(foundRemoteMessages, themeAndStringsPromise.get()) - |> map { messages, themeAndStrings -> ([ChatHistorySearchEntry], [MessageId: Message])? in + |> map { messages, themeAndStrings -> ([ChatHistorySearchEntry], [EngineMessage.Id: EngineRawMessage])? in if messages.isEmpty { return ([], [:]) } else { @@ -353,13 +352,13 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten } } - public func messageForGallery(_ id: MessageId) -> Message? { + public func messageForGallery(_ id: EngineMessage.Id) -> EngineMessage? { if let currentEntries = self.currentEntries { for entry in currentEntries { switch entry { case let .message(message, _, _, _, _): if message.id == id { - return message + return EngineMessage(message) } } } @@ -377,7 +376,7 @@ public final class ChatHistorySearchContainerNode: SearchDisplayControllerConten } } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index e81d670733..79e59151fc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -3,7 +3,6 @@ import ComponentFlow import Display import AsyncDisplayKit import TelegramCore -import Postbox import AccountContext import ChatListUI import MergeLists @@ -69,7 +68,7 @@ public final class ChatInlineSearchResultsListComponent: Component { public enum Contents: Equatable { case empty - case tag(MemoryBuffer) + case tag(EngineMemoryBuffer) case search(query: String, includeSavedPeers: Bool) case monoforumChats(query: String) } @@ -94,9 +93,9 @@ public final class ChatInlineSearchResultsListComponent: Component { public let initialScrollingState: ScrollingState? public let messageSelected: (EngineMessage) -> Void public let peerSelected: (EnginePeer) -> Void - public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal? + public let loadTagMessages: (EngineMemoryBuffer, EngineMessage.Index?) -> Signal? public let getSearchResult: () -> Signal? - public let getSavedPeers: (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>? + public let getSavedPeers: (String) -> Signal<[(EnginePeer, EngineMessage.Index?)], NoError>? public let getChats: (String) -> Signal? public let loadMoreSearchResults: () -> Void @@ -111,9 +110,9 @@ public final class ChatInlineSearchResultsListComponent: Component { initialScrollingState: ScrollingState?, messageSelected: @escaping (EngineMessage) -> Void, peerSelected: @escaping (EnginePeer) -> Void, - loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal?, + loadTagMessages: @escaping (EngineMemoryBuffer, EngineMessage.Index?) -> Signal?, getSearchResult: @escaping () -> Signal?, - getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?, + getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, EngineMessage.Index?)], NoError>?, getChats: @escaping (String) -> Signal?, loadMoreSearchResults: @escaping () -> Void ) { @@ -243,7 +242,7 @@ public final class ChatInlineSearchResultsListComponent: Component { private struct ContentsState: Equatable { enum ContentId: Equatable { case empty - case tag(MemoryBuffer) + case tag(EngineMemoryBuffer) case search(String) } @@ -274,8 +273,8 @@ public final class ChatInlineSearchResultsListComponent: Component { private let emptyResultsText = ComponentView() private let emptyResultsAnimation = ComponentView() - private var tagContents: (index: MessageIndex?, disposable: Disposable?)? - private var searchContents: (index: MessageIndex?, disposable: Disposable?)? + private var tagContents: (index: EngineMessage.Index?, disposable: Disposable?)? + private var searchContents: (index: EngineMessage.Index?, disposable: Disposable?)? private var nextContentsId: Int = 0 private var contentsState: ContentsState? @@ -453,7 +452,7 @@ public final class ChatInlineSearchResultsListComponent: Component { guard let visibleRange = displayedRange.visibleRange else { return } - var loadAroundIndex: MessageIndex? + var loadAroundIndex: EngineMessage.Index? if visibleRange.firstIndex <= 5 { if contentsState.hasLater { loadAroundIndex = contentsState.messages.first?.index @@ -615,7 +614,7 @@ public final class ChatInlineSearchResultsListComponent: Component { let disposable = MetaDisposable() self.searchContents = (nil, disposable) - let savedPeers: Signal<[(EnginePeer, MessageIndex?)], NoError> + let savedPeers: Signal<[(EnginePeer, EngineMessage.Index?)], NoError> if includeSavedPeers, !query.isEmpty, let savedPeersSignal = component.getSavedPeers(query) { savedPeers = savedPeersSignal } else { @@ -624,7 +623,7 @@ public final class ChatInlineSearchResultsListComponent: Component { if let historySignal = component.getSearchResult() { disposable.set((savedPeers - |> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in + |> mapToSignal { savedPeers -> Signal<([(EnginePeer, EngineMessage.Index?)], SearchMessagesResult?), NoError> in if savedPeers.isEmpty { return historySignal |> map { result in @@ -769,7 +768,7 @@ public final class ChatInlineSearchResultsListComponent: Component { /*if let historySignal = component.getSearchResult() { disposable.set((savedPeers - |> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in + |> mapToSignal { savedPeers -> Signal<([(EnginePeer, EngineMessage.Index?)], SearchMessagesResult?), NoError> in if savedPeers.isEmpty { return historySignal |> map { result in @@ -926,6 +925,8 @@ public final class ChatInlineSearchResultsListComponent: Component { }, performActiveSessionAction: { _, _ in }, + performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: { }, hideChatFolderUpdates: { @@ -1030,14 +1031,14 @@ public final class ChatInlineSearchResultsListComponent: Component { if let forwardInfo = message.forwardInfo { effectiveAuthor = forwardInfo.author.flatMap(EnginePeer.init) if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + effectiveAuthor = EnginePeer(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) } } if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo { if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] { effectiveAuthor = EnginePeer(peer) } else if let authorSignature = sourceAuthorInfo.originalAuthorName { - effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + effectiveAuthor = EnginePeer(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) } } if effectiveAuthor == nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel/Sources/ChatInputMessageAccessoryPanel.swift b/submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel/Sources/ChatInputMessageAccessoryPanel.swift index f3e4c1de58..28980b0af3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel/Sources/ChatInputMessageAccessoryPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel/Sources/ChatInputMessageAccessoryPanel.swift @@ -222,6 +222,7 @@ public final class ChatInputMessageAccessoryPanel: Component { let contents: Contents let chatPeerId: EnginePeer.Id? let action: ((UIView) -> Void)? + let longPressAction: ((UIView) -> Void)? let dismiss: (UIView) -> Void public init( @@ -229,12 +230,14 @@ public final class ChatInputMessageAccessoryPanel: Component { contents: Contents, chatPeerId: EnginePeer.Id?, action: ((UIView) -> Void)?, + longPressAction: ((UIView) -> Void)? = nil, dismiss: @escaping (UIView) -> Void ) { self.context = context self.contents = contents self.chatPeerId = chatPeerId self.action = action + self.longPressAction = longPressAction self.dismiss = dismiss } @@ -251,12 +254,16 @@ public final class ChatInputMessageAccessoryPanel: Component { if (lhs.action == nil) != (rhs.action == nil) { return false } + if (lhs.longPressAction == nil) != (rhs.longPressAction == nil) { + return false + } return true } public final class View: UIView, ChatInputAccessoryPanelView { private let closeButton: HighlightTrackingButton private let closeButtonIcon: GlassBackgroundView.ContentImageView + private let longPressGestureRecognizer: UILongPressGestureRecognizer private let lineView: UIImageView private let titleNode: CompositeTextNode @@ -295,6 +302,7 @@ public final class ChatInputMessageAccessoryPanel: Component { self.closeButton = HighlightTrackingButton() self.closeButtonIcon = GlassBackgroundView.ContentImageView() + self.longPressGestureRecognizer = UILongPressGestureRecognizer() self.lineView = UIImageView() self.titleNode = CompositeTextNode() @@ -311,6 +319,10 @@ public final class ChatInputMessageAccessoryPanel: Component { self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), for: .touchUpInside) self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + + self.longPressGestureRecognizer.addTarget(self, action: #selector(self.longPressGesture(_:))) + self.longPressGestureRecognizer.isEnabled = false + self.addGestureRecognizer(self.longPressGestureRecognizer) } required public init?(coder: NSCoder) { @@ -330,6 +342,15 @@ public final class ChatInputMessageAccessoryPanel: Component { } } + @objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) { + guard let component = self.component else { + return + } + if case .began = recognizer.state { + component.longPressAction?(self) + } + } + @objc private func closeButtonPressed() { guard let component = self.component else { return @@ -384,6 +405,7 @@ public final class ChatInputMessageAccessoryPanel: Component { self.component = component self.state = state self.environment = environment + self.longPressGestureRecognizer.isEnabled = component.longPressAction != nil if self.closeButtonIcon.image == nil { self.closeButtonIcon.image = generateCloseIcon() diff --git a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD index bc482b0ebb..db19e2ba85 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/ChatPresentationInterfaceState", diff --git a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift index 25a2afaf63..b3a4b9b000 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import AccountContext import ChatPresentationInterfaceState @@ -23,7 +22,15 @@ open class ChatInputPanelNode: ASDisplayNode { open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { } - open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + public final func compactBottomSideInset(bottomInset: CGFloat, deviceMetrics: DeviceMetrics) -> CGFloat { + if bottomInset <= 32.0 && deviceMetrics.screenCornerRadius > 0.0 { + return 18.0 + } else { + return 0.0 + } + } + + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { return 0.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift index 93b4efb11f..6313e641bb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputTextNode/Sources/ChatInputTextNode.swift @@ -1114,6 +1114,7 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate, super.init(frame: CGRect(), textContainer: self.displayInternal.textContainer, disableTiling: disableTiling) self.delegate = self + self.scrollsToTop = false if #available(iOS 18.0, *) { self.supportsAdaptiveImageGlyph = false diff --git a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift index bb9e9d0bd8..ecf7d27939 100644 --- a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import Display -import Postbox import TelegramCore import TelegramPresentationData import ActivityIndicator @@ -421,16 +420,16 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { case channel } private var chatType: ChatType = .channel - public func updatePresentationInterfaceState(renderedPeer: RenderedPeer?, chatLocation: ChatLocation) { + public func updatePresentationInterfaceState(renderedPeer: EngineRenderedPeer?, chatLocation: ChatLocation) { var chatType: ChatType = .channel if let peer = renderedPeer?.peer { - if peer is TelegramUser { + if case .user = peer { chatType = .user - } else if peer is TelegramGroup { + } else if case .legacyGroup = peer { chatType = .group - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if channel.isMonoForum { - if let mainChannel = renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + if case let .channel(mainChannel) = renderedPeer?.chatOrMonoforumMainPeer, mainChannel.hasPermission(.manageDirect) { if chatLocation.threadId == nil { chatType = .group } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD index 9c809d2612..3804ac777b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/StickerResources", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift index 8a888df6a5..4ce9960037 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import StickerResources import AccountContext @@ -21,7 +20,7 @@ public enum ChatMediaInputStickerGridSectionAccessory { } public final class ChatMediaInputStickerGridSection: GridSection { - public let collectionId: ItemCollectionId + public let collectionId: EngineItemCollectionId public let collectionInfo: StickerPackCollectionInfo? public let accessory: ChatMediaInputStickerGridSectionAccessory public let interaction: ChatMediaInputNodeInteraction @@ -32,7 +31,7 @@ public final class ChatMediaInputStickerGridSection: GridSection { return self.collectionId.hashValue } - public init(collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { + public init(collectionId: EngineItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { self.collectionId = collectionId self.collectionInfo = collectionInfo self.accessory = accessory @@ -118,7 +117,7 @@ public final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { public final class ChatMediaInputStickerGridItem: GridItem { public let context: AccountContext - public let index: ItemCollectionViewEntryIndex + public let index: EngineItemCollectionViewEntryIndex public let stickerItem: StickerPackItem public let selected: () -> Void public let interfaceInteraction: ChatControllerInteraction? @@ -129,7 +128,7 @@ public final class ChatMediaInputStickerGridItem: GridItem { public let section: GridSection? - public init(context: AccountContext, collectionId: ItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) { + public init(context: AccountContext, collectionId: EngineItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: EngineItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) { self.context = context self.index = index self.stickerItem = stickerItem diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD index e613fa4d19..c51712b5ac 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index 72528395d0..6f923f5d50 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -26,7 +25,7 @@ import ComponentFlow import ReactionSelectionNode import MultilineTextComponent -private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, messageCount: Int? = nil, accountPeerId: PeerId, forForumOverview: Bool) -> NSAttributedString? { +private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineRawMessage, messageCount: Int? = nil, accountPeerId: EnginePeer.Id, forForumOverview: Bool) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), messageCount: messageCount, accountPeerId: accountPeerId, forChatList: false, forForumOverview: forForumOverview) } @@ -108,7 +107,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { super.didLoad() } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let imageNode = self.imageNode, self.item?.message.id == messageId { return (imageNode, imageNode.bounds, { [weak self] in guard let strongSelf = self, let imageNode = strongSelf.imageNode else { @@ -137,9 +136,9 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false - var currentMedia: Media? + var currentMedia: EngineRawMedia? if let item = item { mediaLoop: for media in item.message.media { if let media = media as? TelegramMediaAction { @@ -552,7 +551,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.mediaBackgroundNode.image = backgroundImage if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD index 6256450503..f840e72c38 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramPresentationData", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 4ee6c88de2..21e0c26601 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import Display import TelegramPresentationData import AccountContext @@ -221,7 +220,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customInfo: ChatMessageActionButtonsNode.CustomInfo?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { + class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: EngineMessage, _ button: ReplyMarkupButton, _ customInfo: ChatMessageActionButtonsNode.CustomInfo?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode) return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customInfo, constrainedWidth, position in @@ -543,7 +542,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } var animationContent: EmojiStatusComponent.AnimationContent = .customEmoji(fileId: iconFileId) - if let file = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: iconFileId)] as? TelegramMediaFile { + if let file = message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: iconFileId)] as? TelegramMediaFile { animationContent = .file(file: file) } @@ -661,7 +660,7 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { } } - public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customInfos: [MemoryBuffer: CustomInfo], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customInfos: [EngineMemoryBuffer: CustomInfo], _ message: EngineMessage, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customInfos, message, constrainedWidth in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 00da8a1024..e9fd395a8e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -505,7 +505,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var emojiFile: TelegramMediaFile? var emojiString: String? - if messageIsEligibleForLargeCustomEmoji(item.message) || messageIsEligibleForLargeEmoji(item.message) { + if messageIsEligibleForLargeCustomEmoji(EngineMessage(item.message)) || messageIsEligibleForLargeEmoji(EngineMessage(item.message)) { emojiString = item.message.text } @@ -660,7 +660,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } let fittedSize = isEmoji ? dimensions.cgSize.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) let mode: AnimatedStickerMode = .direct(cachePathPrefix: pathPrefix) self.animationSize = fittedSize animationNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, isVideo: file.mimeType == "video/webm"), width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: mode) @@ -1152,7 +1152,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { tonAmount = stakeTonAmount } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular, associatedData: item.associatedData) var isReplyThread = false if case .replyThread = item.chatLocation { @@ -1168,7 +1168,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -1183,7 +1183,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { tonAmount: tonAmount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.message)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -1352,7 +1352,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } let availableWidth = max(60.0, availableContentWidth + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource.flatMap(EnginePeer.init), forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } var needsReplyBackground = false @@ -1370,7 +1370,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, baseWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { @@ -1419,7 +1419,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -1430,7 +1430,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } let reactions: ReactionsMessageAttribute - if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { + if shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) } else { reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) @@ -1447,7 +1447,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, - message: item.message, + message: EngineMessage(item.message), associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), @@ -1712,7 +1712,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf?.openQuickShare(node: node, gesture: gesture) } } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) animation.animator.updateFrame(layer: updatedShareButtonNode.layer, frame: CGRect(origin: CGPoint(x: !incoming ? updatedImageFrame.minX - buttonSize.width - 6.0 : updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0 + imageBottomPadding), size: buttonSize), completion: nil) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() @@ -2076,7 +2076,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: item.message) { + if canAddMessageReactions(message: EngineMessage(item.message)) { item.controllerInteraction.updateMessageReaction(item.message, .default, false, nil) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) @@ -2085,7 +2085,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } else if case .tap = gesture { item.controllerInteraction.clickThroughMessage(self.view, location) } else if case .doubleTap = gesture { - if canAddMessageReactions(message: item.message) { + if canAddMessageReactions(message: EngineMessage(item.message)) { item.controllerInteraction.updateMessageReaction(item.message, .default, false, nil) } } @@ -2257,7 +2257,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId) do { - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(resource.id)) let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 1.6), height: Int(animationSize.height * 1.6), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) var animationFrame: CGRect @@ -2404,10 +2404,10 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let peach = 0x1F351 let coffin = 0x26B0 - let appConfiguration = item.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = item.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } let text = item.message.text diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD index 4b6113af11..368374b99a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 4cb3db577c..6a190131c5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -61,7 +60,7 @@ public struct ChatMessageAttachedContentNodeMediaFlags: OptionSet { public final class ChatMessageAttachedContentNode: ASDisplayNode { private enum InlineMedia: Equatable { - case media(Media) + case media(EngineRawMedia) case peerAvatar(EnginePeer) static func ==(lhs: InlineMedia, rhs: InlineMedia) -> Bool { @@ -108,8 +107,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { private var linkHighlightingNode: LinkHighlightingNode? private var context: AccountContext? - private var message: Message? - private var media: Media? + private var message: EngineRawMessage? + private var media: EngineRawMedia? private var theme: ChatPresentationThemeData? private var mainColor: UIColor? @@ -168,7 +167,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.activateBadgeAction?() } - public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ titleBadge: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: ([Media], ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) + public typealias AsyncLayout = (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: MediaAutoDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ context: AccountContext, _ controllerInteraction: ChatControllerInteraction, _ message: EngineRawMessage, _ messageRead: Bool, _ chatLocation: ChatLocation, _ title: String?, _ titleBadge: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)?, _ mediaBadge: String?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ constrainedSize: CGSize, _ animationCache: AnimationCache, _ animationRenderer: MultiAnimationRenderer) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) public func makeProgress() -> Promise { let progress = Promise() @@ -236,7 +235,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if let peer = forwardInfo.author { author = peer } else if let authorSignature = forwardInfo.authorSignature { - author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + author = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } @@ -314,7 +313,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { insets.right += 6.0 } - var contentMediaValue: Media? + var contentMediaValue: EngineRawMedia? var contentFileValue: TelegramMediaFile? var contentAnimatedFilesValue: [TelegramMediaFile] = [] @@ -356,7 +355,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { contentMediaValue = file } else if file.isVideo { contentMediaValue = file - } else if file.isSticker || file.isAnimatedSticker { + } else if file.isSticker || file.isAnimatedSticker || file.isCustomEmoji { contentMediaValue = file } else { contentFileValue = file @@ -377,7 +376,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { if case .full = contentMediaAutomaticDownload { willDownloadOrLocal = true } else { - willDownloadOrLocal = context.account.postbox.mediaBox.completedResourcePath(file.resource) != nil + willDownloadOrLocal = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.resource.id)) != nil } if willDownloadOrLocal { contentMediaAutomaticPlayback = true @@ -714,7 +713,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, format: dateFormat, associatedData: associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: context.account.peerId, message: EngineMessage(message), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings, format: dateFormat, associatedData: associatedData) let statusType: ChatMessageDateAndStatusType if incoming { @@ -753,7 +752,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { type: statusType, layoutInput: .trailingContent( contentWidth: trailingContentWidth, - reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false) + reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: EngineMessage(message), isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions), preferAdditionalInset: false) ), constrainedSize: CGSize(width: maxStatusContentWidth, height: CGFloat.greatestFiniteMagnitude), availableReactions: associatedData.availableReactions, @@ -768,7 +767,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { starsCount: starsCount, isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(message)), animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer )) @@ -1115,7 +1114,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { inlineMedia.setSignal(updateInlineImageSignal) } case let .peerAvatar(peer): - if let peerReference = PeerReference(peer._asPeer()) { + if let peerReference = PeerReference(peer) { if let signal = peerAvatarImage(account: context.account, peerReference: peerReference, authorOfMessage: nil, representation: peer.largeProfileImage, displayDimensions: inlineMediaSize, clipStyle: .none, blurred: false, inset: 0.0, emptyColor: mainColor.withMultipliedAlpha(0.1), synchronousLoad: synchronousLoads, provideUnrounded: false) { let updateInlineImageSignal = signal |> map { images -> (TransformImageArguments) -> DrawingContext? in let image = images?.0 @@ -1563,7 +1562,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { pattern = MessageInlineBlockBackgroundView.Pattern( context: context, fileId: backgroundEmojiId, - file: message.associatedMedia[MediaId( + file: message.associatedMedia[EngineMedia.Id( namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId )] as? TelegramMediaFile @@ -1605,7 +1604,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { } } - public func updateHiddenMedia(_ media: [Media]?) -> Bool { + public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { if let currentMedia = self.media { if let media = media { var found = false @@ -1628,7 +1627,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { return false } - public func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(media: EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let contentImageNode = self.contentMedia, let image = self.media as? TelegramMediaImage, image.isEqual(to: media) { return (contentImageNode, contentImageNode.bounds, { [weak contentImageNode] in return (contentImageNode?.view.snapshotContentTree(unhide: true), nil) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD index ab37ef98ba..1050ac5b5d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift index f99ae32c5b..720cb37e88 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBirthdateSuggestionContentNode/Sources/ChatMessageBirthdateSuggestionContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD index 3869cfb7c9..1bd7f3cb75 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUIPreferences", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift index 01f95aa3ff..684c404552 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode/Sources/ChatMessageBubbleContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramUIPreferences import TelegramPresentationData @@ -144,14 +143,14 @@ public struct ChatMessageBubbleContentTapAction { case url(Url) case phone(String) case textMention(String) - case peerMention(peerId: PeerId, mention: String, openProfile: Bool) + case peerMention(peerId: EnginePeer.Id, mention: String, openProfile: Bool) case botCommand(String) case hashtag(String?, String) case instantPage case wallpaper case theme - case call(peerId: PeerId, isVideo: Bool) - case conferenceCall(message: Message) + case call(peerId: EnginePeer.Id, isVideo: Bool) + case conferenceCall(message: EngineRawMessage) case openMessage case timecode(Double, String) case tooltip(String, ASDisplayNode?, CGRect?) @@ -162,6 +161,7 @@ public struct ChatMessageBubbleContentTapAction { case date(Int32, String) case largeEmoji(String, String?, TelegramMediaFile) case customEmoji(TelegramMediaFile) + case externalInstantPage(url: Url, webpageId: EngineMedia.Id, anchor: String?) case custom(() -> Void) } @@ -181,8 +181,8 @@ public struct ChatMessageBubbleContentTapAction { public final class ChatMessageBubbleContentItem { public let context: AccountContext public let controllerInteraction: ChatControllerInteraction - public let message: Message - public let topMessage: Message + public let message: EngineRawMessage + public let topMessage: EngineRawMessage public let content: ChatMessageItemContent public let read: Bool public let chatLocation: ChatLocation @@ -192,7 +192,7 @@ public final class ChatMessageBubbleContentItem { public let isItemPinned: Bool public let isItemEdited: Bool - public init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: Message, topMessage: Message, content: ChatMessageItemContent, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes, isItemPinned: Bool, isItemEdited: Bool) { + public init(context: AccountContext, controllerInteraction: ChatControllerInteraction, message: EngineRawMessage, topMessage: EngineRawMessage, content: ChatMessageItemContent, read: Bool, chatLocation: ChatLocation, presentationData: ChatPresentationData, associatedData: ChatMessageItemAssociatedData, attributes: ChatMessageEntryAttributes, isItemPinned: Bool, isItemEdited: Bool) { self.context = context self.controllerInteraction = controllerInteraction self.message = message @@ -225,7 +225,7 @@ open class ChatMessageBubbleContentNode: ASDisplayNode { public var updateIsTextSelectionActive: ((Bool) -> Void)? public var requestInlineUpdate: (() -> Void)? - public var requestFullUpdate: (() -> Void)? + public var requestFullUpdate: ((ControlledTransition?) -> Void)? open var disablesClipping: Bool { return false @@ -257,15 +257,19 @@ open class ChatMessageBubbleContentNode: ASDisplayNode { }) } - open func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + open func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - - open func updateHiddenMedia(_ media: [Media]?) -> Bool { + + open func getAnchorRect(anchor: String) -> CGRect? { + return nil + } + + open func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return false } - open func updateSearchTextHighlightState(text: String?, messages: [MessageIndex]?) { + open func updateSearchTextHighlightState(text: String?, messages: [EngineMessage.Index]?) { } open func updateAutomaticMediaDownloadSettings(_ settings: MediaAutoDownloadSettings) { @@ -312,7 +316,7 @@ open class ChatMessageBubbleContentNode: ASDisplayNode { return nil } - open func targetForStoryTransition(id: StoryId) -> UIView? { + open func targetForStoryTransition(id: EngineStoryId) -> UIView? { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD index 21cd7ce809..a739a9a4e9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/BUILD @@ -96,6 +96,7 @@ swift_library( "//submodules/AvatarNode", "//submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode", "//submodules/TelegramUI/Components/PremiumAlertController", + "//submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index c36c5ab7d9..2079e6767c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -35,6 +35,7 @@ import ChatMessageDateAndStatusNode import ChatMessageBubbleContentNode import ChatHistoryEntry import ChatMessageTextBubbleContentNode +import ChatMessageRichDataBubbleContentNode import ChatMessageItemCommon import ChatMessageReplyInfoNode import ChatMessageCallBubbleContentNode @@ -257,7 +258,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ } else if case .copyProtectionRequest = action.action { result.append((message, ChatMessageDisableCopyProtectionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) } else { - if !canAddMessageReactions(message: message) { + if !canAddMessageReactions(message: EngineMessage(message)) { needReactions = false } result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) @@ -328,8 +329,16 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ if let updatingMedia = itemAttributes.updatingMedia { messageText = updatingMedia.text } + + var richText: RichTextMessageAttribute? + for attribute in item.message.attributes { + if let attribute = attribute as? RichTextMessageAttribute { + richText = attribute + break + } + } - if !messageText.isEmpty || isUnsupportedMedia || isStoryWithText { + if !messageText.isEmpty || (message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) && richText == nil) || isUnsupportedMedia || isStoryWithText { if !skipText { if case .group = item.content, !isFile { messageWithCaptionToAdd = (message, itemAttributes) @@ -390,6 +399,11 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ } } + if richText != nil && !skipText { + result.append((message, ChatMessageRichDataBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) + needReactions = false + } + if message.adAttribute != nil { result.removeAll() @@ -446,7 +460,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ let firstMessage = item.content.firstMessage let reactionsAreInline: Bool - reactionsAreInline = shouldDisplayInlineDateReactions(message: firstMessage, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) + reactionsAreInline = shouldDisplayInlineDateReactions(message: EngineMessage(firstMessage), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) if reactionsAreInline { needReactions = false } @@ -791,7 +805,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var currentInputParams: Params? private var currentApplyParams: ListViewItemApply? private var contentLayoutInsets = UIEdgeInsets() - + + private func isQuickReplyMessageInputCustomContents() -> Bool { + guard let item = self.item else { + return false + } + if case let .customChatContents(contents) = item.associatedData.subject, case .quickReplyMessageInput = contents.kind { + return true + } + return false + } + required public init(rotated: Bool) { self.mainContextSourceNode = ContextExtractedContentContainingNode() self.mainContainerNode = ContextControllerSourceNode() @@ -827,12 +851,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let item = strongSelf.item, item.controllerInteraction.focusedPollAddOptionMessageId != nil { return .none } - if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) { - if case let .action(action) = action, !action.contextMenuOnLongPress { - return .none - } - } - if let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + + let resolveLongTapAction: (InternalBubbleTapAction) -> ContextControllerSourceNode.ShouldBegin = { action in switch action { case .action: return .none @@ -850,6 +870,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } } + + let isQuickReplyMessageInputCustomContents = strongSelf.isQuickReplyMessageInputCustomContents() + if isQuickReplyMessageInputCustomContents, let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + return resolveLongTapAction(action) + } + if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) { + if case let .action(action) = action, !action.contextMenuOnLongPress { + return .none + } + } + if !isQuickReplyMessageInputCustomContents, let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + return resolveLongTapAction(action) + } return .default } @@ -1342,7 +1375,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI break case .ignore: return .fail - case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .date, .custom: + case .url, .phone, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .conferenceCall, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji, .date, .custom, .externalInstantPage: return .waitForSingleTap } } @@ -1561,9 +1594,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI rankBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), boostBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), - forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), + forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, EnginePeer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode), - actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), + actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [EngineMemoryBuffer: ChatMessageActionButtonsNode.CustomInfo], EngineMessage, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)), unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), @@ -1580,6 +1613,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let fontSize = floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0) let nameFont = Font.semibold(fontSize) + let regularFont = Font.regular(fontSize) let inlineBotPrefixFont = Font.regular(fontSize - 1.0) let boostBadgeFont = Font.regular(fontSize - 1.0) @@ -1619,6 +1653,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var allowFullWidth = false let chatLocationPeerId: PeerId = item.chatLocation.peerId ?? item.content.firstMessage.id.peerId + + /*let isInlinePage = false + for attribute in item.message.attributes { + if attribute is RichTextMessageAttribute { + allowFullWidth = true + isInlinePage = true + break + } + }*/ do { let peerId = chatLocationPeerId @@ -1659,6 +1702,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ignoreForward = true effectiveAuthor = author displayAuthorInfo = !mergedTop.merged && incoming + } else if let _ = item.content.firstMessage.guestChatAttribute { + effectiveAuthor = firstMessage.author + displayAuthorInfo = !mergedTop.merged && incoming + hasAvatar = true } else { effectiveAuthor = firstMessage.author @@ -1761,9 +1808,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser { for media in item.content.firstMessage.media { if let file = media as? TelegramMediaFile { - if file.isMusic { - ignoreForward = true - } else if file.isInstantVideo { + if file.isInstantVideo { isInstantVideo = true } break @@ -1885,6 +1930,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let subject = item.associatedData.subject, case .messageOptions = subject { needsShareButton = false } + + /*if isInlinePage { + needsShareButton = false + }*/ var tmpWidth: CGFloat if allowFullWidth { @@ -1892,7 +1941,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if (needsShareButton && !isSidePanelOpen) || isAd { tmpWidth -= 45.0 } else { - tmpWidth -= 4.0 + tmpWidth -= 3.0 } } else { tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth) @@ -2009,6 +2058,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI break } + var guestChatViaFromNameString: String? var inlineBotNameString: String? var replyMessage: Message? var replyForward: QuotedReplyMessageAttribute? @@ -2019,7 +2069,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var authorNameColor: UIColor? for attribute in firstMessage.attributes { - if let attribute = attribute as? InlineBotMessageAttribute { + if let attribute = attribute as? GuestChatMessageAttribute { + if let peer = firstMessage.peers[attribute.peerId] { + guestChatViaFromNameString = EnginePeer(peer).compactDisplayTitle + } + } else if let attribute = attribute as? InlineBotMessageAttribute { if let peerId = attribute.peerId, let bot = firstMessage.peers[peerId] as? TelegramUser { inlineBotNameString = bot.addressName } else { @@ -2254,7 +2308,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI bubbleReactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) } if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview { - bottomNodeMergeStatus = .Both + if incoming { + bottomNodeMergeStatus = .Left + } else { + bottomNodeMergeStatus = .Right + } } var currentCredibilityIcon: (EmojiStatusComponent.Content, UIColor?)? @@ -2398,6 +2456,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if authorNameString != nil { displayHeader = true } + if guestChatViaFromNameString != nil { + displayHeader = true + } if inlineBotNameString != nil { displayHeader = true } @@ -2501,7 +2562,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType if incoming { @@ -2528,7 +2589,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI impressionCount: !item.presentationData.isPreview ? viewCount : nil, dateText: dateText, type: statusType, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -2542,7 +2603,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI starsCount: starsCount, isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(message)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -2606,14 +2667,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI switch authorRank { case let .creator(rank): if let rank, !rank.isEmpty { - string = rank.trimmingEmojis + string = rank } else { string = item.presentationData.strings.Conversation_Owner } rankBadgeColor = UIColor(rgb: 0x956ac8) case let .admin(rank): if let rank, !rank.isEmpty { - string = rank.trimmingEmojis + string = rank } else { string = item.presentationData.strings.Conversation_Admin } @@ -2624,7 +2685,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI string = item.presentationData.strings.Chat_TagPlaceholder defaultRankColor = defaultRankColor.withMultipliedAlpha(0.5) } else { - string = rank.trimmingEmojis + string = rank } } else { string = "" @@ -2647,6 +2708,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI mutableString.append(botString) attributedString = mutableString viaSuffix = botString + } else if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let guestChatViaFromNameString = guestChatViaFromNameString { + let mutableString = NSMutableAttributedString(string: "\(authorNameString) ", attributes: [NSAttributedString.Key.font: nameFont, NSAttributedString.Key.foregroundColor: authorNameColor]) + let bodyAttributes = MarkdownAttributeSet(font: regularFont, textColor: authorNameColor) + let botString = addAttributesToStringWithRanges(item.presentationData.strings.Conversation_MessageGuestChatForUser(guestChatViaFromNameString)._tuple, body: bodyAttributes, argumentAttributes: [:]) + mutableString.append(botString) + attributedString = mutableString + viaSuffix = botString } else if let authorNameString = authorNameString, let authorNameColor = authorNameColor { attributedString = NSAttributedString(string: authorNameString, font: nameFont, textColor: authorNameColor) } else if let inlineBotNameString = inlineBotNameString { @@ -2764,7 +2832,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI forwardAuthorSignature = forwardInfo.authorSignature } } - let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource.flatMap(EnginePeer.init), forwardAuthorSignature, forwardPsaType, nil, CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) }) headerSize.height += 2.0 @@ -2792,7 +2860,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } - let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource, nil, nil, ChatMessageForwardInfoNode.StoryData(storyType: storyType), CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) + let sizeAndApply = forwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .bubble(incoming: incoming), forwardSource.flatMap(EnginePeer.init), nil, nil, ChatMessageForwardInfoNode.StoryData(storyType: storyType), CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude)) forwardInfoSizeApply = (sizeAndApply.0, { width in sizeAndApply.1(width) }) if storyType != .regular { @@ -2931,7 +2999,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), [:], item.message, maximumNodeWidth) + ), [:], EngineMessage(item.message), maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout @@ -2976,7 +3044,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout @@ -3017,7 +3085,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout @@ -3069,13 +3137,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout lastNodeTopPosition = .None(.Both) } else if let replyMarkup = replyMarkup, !item.presentationData.isPreview { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, maximumNodeWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], EngineMessage(item.message), maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } else if item.content.firstMessageAttributes.displayContinueThreadFooter { @@ -3103,7 +3171,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout @@ -3140,7 +3208,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: bubbleReactions, - message: item.message, + message: EngineMessage(item.message), associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: incoming, @@ -3466,7 +3534,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strings: item.presentationData.strings, context: item.context, controllerInteraction: item.controllerInteraction, - message: item.message, + message: EngineMessage(item.message), media: paidContent, constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, @@ -3477,7 +3545,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let sizeAndApply = mediaInfoLayout(ChatMessageStarsMediaInfoNode.Arguments( presentationData: item.presentationData, context: item.context, - message: item.message, + message: EngineMessage(item.message), media: paidContent, constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude), animationCache: item.controllerInteraction.presentationContext.animationCache, @@ -3777,7 +3845,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } var forceBackgroundSide = false - if actionButtonsSizeAndApply != nil || reactionButtonsSizeAndApply != nil { + if actionButtonsSizeAndApply != nil { forceBackgroundSide = true } else if case .semanticallyMerged = updatedMergedTop { forceBackgroundSide = true @@ -4599,12 +4667,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if strongSelf.selectionNode != nil { return false } - if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) { - if case .action = action { - return false - } - } - if let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + + let resolveLongTapAction: (InternalBubbleTapAction) -> Bool = { action in switch action { case .action, .optionalAction: return false @@ -4612,6 +4676,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return !openContextMenu.selectAll } } + + let isQuickReplyMessageInputCustomContents = strongSelf.isQuickReplyMessageInputCustomContents() + if isQuickReplyMessageInputCustomContents, let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + return resolveLongTapAction(action) + } + if let action = strongSelf.gestureRecognized(gesture: .tap, location: location, recognizer: nil) { + if case .action = action { + return false + } + } + if !isQuickReplyMessageInputCustomContents, let action = strongSelf.gestureRecognized(gesture: .longTap, location: location, recognizer: nil) { + return resolveLongTapAction(action) + } return true } containerNode.activated = { [weak strongSelf, weak containerNode] gesture, location in @@ -4779,12 +4856,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.internalUpdateLayout() } - contentNode.requestFullUpdate = { [weak strongSelf] in + contentNode.requestFullUpdate = { [weak strongSelf] customTransition in guard let strongSelf, let item = strongSelf.item else { return } - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, customTransition) } } } @@ -5217,7 +5294,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.messageAccessibilityArea.frame = backgroundFrame } if let summarizeButtonNode = strongSelf.summarizeButtonNode { - let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) + let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, disableComments: disablesComments, isSummarize: true) var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) @@ -5241,7 +5318,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI animation.animator.updateScale(layer: summarizeButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil) } if let shareButtonNode = strongSelf.shareButtonNode { - let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments) + let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, disableComments: disablesComments) var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) @@ -5275,7 +5352,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI }*/ strongSelf.messageAccessibilityArea.frame = backgroundFrame if let summarizeButtonNode = strongSelf.summarizeButtonNode { - let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments, isSummarize: true) + let buttonSize = summarizeButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, disableComments: disablesComments, isSummarize: true) var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.minY + 1.0), size: buttonSize) @@ -5299,7 +5376,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI animation.animator.updateScale(layer: summarizeButtonNode.layer, scale: (isCurrentlyPlayingMedia || isSidePanelOpen) ? 0.001 : 1.0, completion: nil) } if let shareButtonNode = strongSelf.shareButtonNode { - let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: disablesComments) + let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, disableComments: disablesComments) var buttonFrame = CGRect(origin: CGPoint(x: !incoming ? backgroundFrame.minX - buttonSize.width - 8.0 : backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) @@ -5383,7 +5460,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } } - if item.message.timestamp < 10 { + if item.message.timestamp < 10 && !strongSelf.isQuickReplyMessageInputCustomContents() { hasMenuGesture = false } strongSelf.mainContainerNode.isGestureEnabled = hasMenuGesture @@ -5464,7 +5541,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: openContextMenu.tapMessage) { + if canAddMessageReactions(message: EngineMessage(openContextMenu.tapMessage)) { item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default, false, nil) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) @@ -5473,7 +5550,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else if case .tap = gesture { item.controllerInteraction.clickThroughMessage(self.view, location) } else if case .doubleTap = gesture { - if canAddMessageReactions(message: item.message) { + if canAddMessageReactions(message: EngineMessage(item.message)) { item.controllerInteraction.updateMessageReaction(item.message, .default, false, nil) } } @@ -5599,7 +5676,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } if attribute.isQuote, !replyInfoNode.isQuoteExpanded { replyInfoNode.isQuoteExpanded = true - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) return } var progress: Promise? @@ -5631,7 +5708,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } if attribute.isQuote, !replyInfoNode.isQuoteExpanded { replyInfoNode.isQuoteExpanded = true - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) return } @@ -5754,6 +5831,27 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url.url, concealed: url.concealed, message: item.content.firstMessage, allowInlineWebpageResolution: url.allowInlineWebpageResolution, progress: tapAction.activate?())) }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) } + case let .externalInstantPage(url, webpageId, anchor): + if case .longTap = gesture, !tapAction.hasLongTapAction, let item = self.item { + let tapMessage = item.content.firstMessage + var subFrame = self.backgroundNode.frame + if case .group = item.content { + for contentNode in self.contentNodes { + if contentNode.item?.message.stableId == tapMessage.stableId { + subFrame = contentNode.frame.insetBy(dx: 0.0, dy: -4.0) + break + } + } + } + return .openContextMenu(InternalBubbleTapAction.OpenContextMenu(tapMessage: tapMessage, selectAll: false, subFrame: subFrame, disableDefaultPressAnimation: true)) + } else { + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item else { + return + } + item.controllerInteraction.openExternalInstantPage(ChatControllerInteraction.OpenInstantPage(webpageId: webpageId, url: url.url, anchor: anchor, concealed: true, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) + } case let .phone(number): return .action(InternalBubbleTapAction.Action({ [weak self] in guard let self, let item = self.item, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { @@ -5962,45 +6060,101 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } else { disableDefaultPressAnimation = true } + case let .externalInstantPage(url, webpageId, anchor): + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let item = self.item else { + return + } + let cleanUrl = url.url.replacingOccurrences(of: "mailto:", with: "") + guard let contentNode = self.contextContentNodeForLink(cleanUrl, rects: rects) else { + return + } + item.controllerInteraction.longTap(.url(url.url), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } + + if case .longTap = gesture, !tapAction.hasLongTapAction, let item = self.item { + let tapMessage = item.content.firstMessage + var subFrame = self.backgroundNode.frame + if case .group = item.content { + for contentNode in self.contentNodes { + if contentNode.item?.message.stableId == tapMessage.stableId { + subFrame = contentNode.frame.insetBy(dx: 0.0, dy: -4.0) + break + } + } + } + return .openContextMenu(InternalBubbleTapAction.OpenContextMenu(tapMessage: tapMessage, selectAll: false, subFrame: subFrame, disableDefaultPressAnimation: true)) + } else { + return .action(InternalBubbleTapAction.Action({ [weak self] in + guard let self, let item = self.item else { + return + } + item.controllerInteraction.openExternalInstantPage(ChatControllerInteraction.OpenInstantPage(webpageId: webpageId, url: url.url, anchor: anchor, concealed: true, progress: tapAction.activate?())) + }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) + } case let .phone(number): - return .action(InternalBubbleTapAction.Action({ [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { - return - } - item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }, contextMenuOnLongPress: !tapAction.hasLongTapAction)) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { + return + } + item.controllerInteraction.longTap(.phone(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .peerMention(peerId, mention, _): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else { - return - } - item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(mention, rects: rects) else { + return + } + item.controllerInteraction.longTap(.peerMention(peerId, mention), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .textMention(name): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else { - return - } - item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(name, rects: rects) else { + return + } + item.controllerInteraction.longTap(.mention(name), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .botCommand(command): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else { - return - } - item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(command, rects: rects) else { + return + } + item.controllerInteraction.longTap(.command(command), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case let .hashtag(peerName, hashtag): var fullHashtag = hashtag if let peerName { fullHashtag += "@\(peerName)" } - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else { - return - } - item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(fullHashtag, rects: rects) else { + return + } + item.controllerInteraction.longTap(.hashtag(fullHashtag), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .instantPage: break case .wallpaper: @@ -6014,21 +6168,29 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case .openMessage: break case let .timecode(timecode, text): - if let mediaMessage = mediaMessage { - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else { - return - } - item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if let mediaMessage { + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(text, rects: rects) else { + return + } + item.controllerInteraction.longTap(.timecode(timecode, text), ChatControllerInteraction.LongTapParams(message: mediaMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } } case let .bankCard(number): - return .action(InternalBubbleTapAction.Action { [weak self] in - guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { - return - } - item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + guard let self, let contentNode = self.contextContentNodeForLink(number, rects: rects) else { + return + } + item.controllerInteraction.longTap(.bankCard(number), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .tooltip: break case .openPollResults: @@ -6040,13 +6202,17 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI case .customEmoji: break case let .date(date, _): - return .action(InternalBubbleTapAction.Action { [weak self] in - let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) - guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else { - return - } - item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?())) - }) + if tapAction.hasLongTapAction { + return .action(InternalBubbleTapAction.Action({}, actionWithLongTapRecognizer: { [weak self] gesture in + let fullDate = stringForEntityFormattedDate(timestamp: date, format: .full(timeFormat: .short, dateFormat: .long, dayOfWeek: false), strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + guard let self, let contentNode = self.contextContentNodeForLink(fullDate, rects: rects) else { + return + } + item.controllerInteraction.longTap(.date(date), ChatControllerInteraction.LongTapParams(message: item.content.firstMessage, contentNode: contentNode, messageNode: self, progress: tapAction.activate?(), gesture: gesture)) + }, contextMenuOnLongPress: false)) + } else { + disableDefaultPressAnimation = true + } case .custom: break } @@ -6209,7 +6375,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if strongSelf.backgroundNode.supernode != nil, let backgroundView = strongSelf.backgroundNode.view.snapshotContentTree(unhide: true) { let backgroundContainer = UIView() - let backdropView = strongSelf.backgroundWallpaperNode.view.snapshotContentTree(unhide: true) + let backdropView = strongSelf.backgroundWallpaperNode.view.snapshotContentTree(unhide: true, keepPortals: true) if let backdropView = backdropView { let backdropFrame = strongSelf.backgroundWallpaperNode.layer.convert(strongSelf.backgroundWallpaperNode.bounds, to: strongSelf.backgroundNode.layer) backdropView.frame = backdropFrame @@ -7108,6 +7274,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI return nil } + override public func getAuthorNameNode() -> ASDisplayNode? { + guard let item = self.item, item.content.firstMessage.guestChatAttribute != nil else { + return nil + } + return self.nameNode + } + public func getQuoteRect(quote: String, offset: Int?) -> CGRect? { for contentNode in self.contentNodes { if let contentNode = contentNode as? ChatMessageTextBubbleContentNode { @@ -7118,7 +7291,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } return nil } - + + public func getAnchorRect(anchor: String) -> CGRect? { + for contentNode in self.contentNodes { + if let result = contentNode.getAnchorRect(anchor: anchor) { + return contentNode.view.convert(result, to: self.view) + } + } + return nil + } + public func getInnerReplySubjectRect(innerSubject: EngineMessageReplyInnerSubject) -> CGRect? { switch innerSubject { case let .todoItem(todoItemId): @@ -7240,10 +7422,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if item.controllerInteraction.summarizedMessageIds.contains(item.message.id) { item.controllerInteraction.summarizedMessageIds.remove(item.message.id) - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } else { item.controllerInteraction.summarizedMessageIds.insert(item.message.id) - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) let translateToLanguage = item.associatedData.translateToLanguage var requestSummary = true @@ -7258,7 +7440,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI |> deliverOnMainQueue).start(error: { error in if case .limitExceededPremium = error, let parentController = item.controllerInteraction.navigationController()?.topViewController as? ViewController { item.controllerInteraction.summarizedMessageIds.remove(item.message.id) - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) let controller = premiumAlertController( context: item.context, parentController: parentController, @@ -7298,7 +7480,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } for contentNode in self.contentNodes { - if contentNode is ChatMessageMediaBubbleContentNode || contentNode is ChatMessageGiftBubbleContentNode || contentNode is ChatMessageWebpageBubbleContentNode || contentNode is ChatMessageInvoiceBubbleContentNode || contentNode is ChatMessageGameBubbleContentNode || contentNode is ChatMessageInstantVideoBubbleContentNode { + if contentNode is ChatMessageMediaBubbleContentNode || contentNode is ChatMessageGiftBubbleContentNode || contentNode is ChatMessageWebpageBubbleContentNode || contentNode is ChatMessageInvoiceBubbleContentNode || contentNode is ChatMessageGameBubbleContentNode || contentNode is ChatMessageInstantVideoBubbleContentNode || contentNode is ChatMessageRichDataBubbleContentNode { contentNode.visibility = mapVisibility(effectiveMediaVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) } else { contentNode.visibility = mapVisibility(effectiveVisibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD index 4f7d632f42..098ce23e19 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AppBundle", "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift index 60cca730c9..d294658e77 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCallBubbleContentNode/Sources/ChatMessageCallBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AppBundle import ChatMessageBubbleContentNode @@ -108,7 +107,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing var peopleTextString: String? - var peopleAvatars: [Peer] = [] + var peopleAvatars: [EnginePeer] = [] var titleString: String? var callDuration: Int32? @@ -155,11 +154,11 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { if conferenceCall.otherParticipants.count > 0 { peopleTextString = item.presentationData.strings.Chat_CallMessage_GroupCallParticipantCount(Int32(conferenceCall.otherParticipants.count + 1)) if let peer = item.message.author { - peopleAvatars.append(peer) + peopleAvatars.append(EnginePeer(peer)) } for id in conferenceCall.otherParticipants { if let peer = item.message.peers[id] { - peopleAvatars.append(peer) + peopleAvatars.append(EnginePeer(peer)) } } } @@ -239,7 +238,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { } } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) var statusText: String if let callDuration = callDuration, callDuration > 1 { @@ -319,7 +318,7 @@ public class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.addSubnode(peopleAvatarsNode) } - let peopleAvatarsContent = peopleAvatarsContext.update(peers: peopleAvatars.prefix(3).map(EnginePeer.init), animated: false) + let peopleAvatarsContent = peopleAvatarsContext.update(peers: Array(peopleAvatars.prefix(3)), animated: false) let peopleAvatarsSize = peopleAvatarsNode.update(context: item.context, content: peopleAvatarsContent, itemSize: CGSize(width: peopleAvatarSize, height: peopleAvatarSize), customSpacing: peopleAvatarSize - peopleAvatarSpacing, font: avatarFont, animated: false, synchronousLoad: false) peopleAvatarsNode.frame = CGRect(origin: CGPoint(x: labelFrame.maxX + avatarsLeftInset, y: labelFrame.minY - 1.0), size: peopleAvatarsSize) } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD index 488db161a5..f50ac6e3b5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift index 99d4e8c196..ed68fc8ae6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageCommentFooterContentNode/Sources/ChatMessageCommentFooterContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -126,13 +125,13 @@ public final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContent let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right var dateReplies = 0 - var replyPeers: [Peer] = [] + var replyPeers: [EnginePeer] = [] var hasUnseenReplies = false for attribute in item.message.attributes { if let attribute = attribute as? ReplyThreadMessageAttribute { dateReplies = Int(attribute.count) - replyPeers = attribute.latestUsers.compactMap { peerId -> Peer? in - return item.message.peers[peerId] + replyPeers = attribute.latestUsers.compactMap { peerId -> EnginePeer? in + return item.message.peers[peerId].flatMap(EnginePeer.init) } if let maxMessageId = attribute.maxMessageId, let maxReadMessageId = attribute.maxReadMessageId { hasUnseenReplies = maxMessageId > maxReadMessageId @@ -358,7 +357,7 @@ public final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContent } } - let avatarContent = strongSelf.avatarsContext.update(peers: replyPeers.map(EnginePeer.init), animated: animation.isAnimated) + let avatarContent = strongSelf.avatarsContext.update(peers: replyPeers, animated: animation.isAnimated) let avatarsSize = strongSelf.avatarsNode.update(context: item.context, content: avatarContent, animated: animation.isAnimated, synchronousLoad: synchronousLoad) let iconAlpha: CGFloat = avatarsSize.width.isZero ? 1.0 : 0.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD index ce75d53b14..3f390d0119 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/AvatarNode", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index b668005ce7..b1a13bcd82 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AvatarNode @@ -112,9 +111,9 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } - var contactPeer: Peer? + var contactPeer: EnginePeer? if let peerId = selectedContact?.peerId, let peer = item.message.peers[peerId] { - contactPeer = peer + contactPeer = EnginePeer(peer) } let nameColors: PeerNameColors.Colors? @@ -260,7 +259,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { } } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -301,7 +300,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), + layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -315,7 +314,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -484,7 +483,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { pattern = MessageInlineBlockBackgroundView.Pattern( context: item.context, fileId: backgroundEmojiId, - file: item.message.associatedMedia[MediaId( + file: item.message.associatedMedia[EngineMedia.Id( namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId )] as? TelegramMediaFile diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD index 4bd08bf312..2dcdf04510 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index d537ad2a1c..9d9387274a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import SwiftSignalKit @@ -1471,6 +1470,6 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } } -public func shouldDisplayInlineDateReactions(message: Message, isPremium: Bool, forceInline: Bool) -> Bool { +public func shouldDisplayInlineDateReactions(message: EngineMessage, isPremium: Bool, forceInline: Bool) -> Bool { return false } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index eb5493531f..7e252d709c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -62,7 +61,7 @@ private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String } } -public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Message, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, associatedData: ChatMessageItemAssociatedData, ignoreAuthor: Bool = false) -> String { +public func stringForMessageTimestampStatus(accountPeerId: EnginePeer.Id, message: EngineMessage, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, associatedData: ChatMessageItemAssociatedData, ignoreAuthor: Bool = false) -> String { if let adAttribute = message.adAttribute { switch adAttribute.messageType { case .sponsored: @@ -155,11 +154,11 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess } var authorTitle: String? - if let author = message.author as? TelegramUser { + if let author = message.author, case .user = author { if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info { if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id, info.flags.contains(.messagesShouldHaveProfiles) { } else { - authorTitle = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder) + authorTitle = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } } else if let forwardInfo = message.forwardInfo, forwardInfo.sourceMessageId?.peerId.namespace == Namespaces.Peer.CloudChannel { authorTitle = forwardInfo.authorSignature diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD index 8ac301a11e..8ac49b13e7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift index d17580bb8f..ae9d2538c0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousDescriptionContentNode/Sources/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -50,7 +49,7 @@ public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessag } else { text = item.message.text } - let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil + let mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? = nil let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) @@ -103,11 +102,11 @@ public final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessag return ChatMessageBubbleContentTapAction(content: .none) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD index db002d4d41..41b7a09aed 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift index 0b420f0c7f..202bca13b2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousLinkContentNode/Sources/ChatMessageEventLogPreviousLinkContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -45,7 +44,7 @@ public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubble let title: String = item.message.text.contains("\n") ? item.presentationData.strings.Channel_AdminLog_MessagePreviousLinks : item.presentationData.strings.Channel_AdminLog_MessagePreviousLink let text: String = item.message.text - let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil + let mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? = nil let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) @@ -98,11 +97,11 @@ public final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubble return ChatMessageBubbleContentTapAction(content: .none) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD index 0a07f4940e..498ff8a205 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift index d3ec9d1355..773b6c1849 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageEventLogPreviousMessageContentNode/Sources/ChatMessageEventLogPreviousMessageContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -50,7 +49,7 @@ public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBub } else { text = item.message.text } - let mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil + let mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? = nil let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.attributes, item.context, item.controllerInteraction, item.message, true, .peer(id: item.message.id.peerId), title, nil, nil, text, messageEntities, mediaAndFlags, nil, nil, nil, true, layoutConstants, preparePosition, constrainedSize, item.controllerInteraction.presentationContext.animationCache, item.controllerInteraction.presentationContext.animationRenderer) @@ -105,11 +104,11 @@ public final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBub self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) }) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD index fc309e1bda..ad2af022dc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift index 33a6223ac6..6fa45a6da9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFactCheckBubbleContentNode/Sources/ChatMessageFactCheckBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -134,7 +133,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode guard let item = self.item else{ return } - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } public override func willUpdateIsExtractedToContextPreview(_ value: Bool) { @@ -326,7 +325,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -441,7 +440,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode impressionCount: !item.presentationData.isPreview ? viewCount : nil, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: nil, reactionSettings: item.presentationData.isPreview ? nil : ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), + layoutInput: .trailingContent(contentWidth: nil, reactionSettings: item.presentationData.isPreview ? nil : ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: EngineMessage(message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -455,7 +454,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD index a9382eddeb..367ca57c53 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUIPreferences", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index 0f8680dbaf..877c143a8c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import ComponentFlow @@ -54,7 +53,7 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { self.interactiveFileNode.requestUpdateLayout = { [weak self] _ in if let strongSelf = self, let item = strongSelf.item { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } @@ -201,7 +200,7 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return self.interactiveFileNode.transitionNode(media: media) } else { @@ -209,7 +208,7 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode { } } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.interactiveFileNode.updateHiddenMedia(media) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD index 9eb97d563e..5bedf2a16f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index 4afdb9e74d..d6070d30b8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData @@ -91,7 +90,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { private var linkProgressView: TextLoadingEffectView? private var linkProgressDisposable: Disposable? - private var previousPeer: Peer? + private var previousPeer: EnginePeer? public var openPsa: ((String, ASDisplayNode) -> Void)? @@ -279,7 +278,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { } } - public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: Peer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) { + public static func asyncLayout(_ maybeNode: ChatMessageForwardInfoNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ strings: PresentationStrings, _ type: ChatMessageForwardInfoType, _ peer: EnginePeer?, _ authorName: String?, _ psaType: String?, _ storyData: StoryData?, _ constrainedSize: CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode) { let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode) let nameNodeLayout = TextNode.asyncLayout(maybeNode?.nameNode) @@ -295,10 +294,10 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { let peerString: String if let peer = peer { - if let authorName = authorName, originalPeer === peer { - peerString = "\(EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))" + if let authorName = authorName, originalPeer == peer { + peerString = "\(peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) (\(authorName))" } else { - peerString = EnginePeer(peer).displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) + peerString = peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) } } else if let authorName = authorName { peerString = authorName @@ -409,7 +408,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { var currentCredibilityIconImage: UIImage? var highlight = true if let peer = peer { - if let channel = peer as? TelegramChannel, channel.addressName == nil { + if case let .channel(channel) = peer, channel.addressName == nil { if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) { } else if case .member = channel.participationStatus { } else { @@ -539,9 +538,9 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { avatarNode.updateSize(size: avatarSize) if let peer { if peer.smallProfileImage != nil { - avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize) + avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: avatarSize) } else { - avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize) + avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: peer, displayDimensions: avatarSize) } } else if let authorName, !authorName.isEmpty { avatarNode.setCustomLetters([String(authorName[authorName.startIndex])]) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD index 8f178acb21..cd30ed4ae4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index 647345b103..ca722d6fde 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -67,7 +66,7 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod var title: String? var text: String? - var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? + var mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? if let game = game { title = game.title @@ -132,11 +131,11 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod return ChatMessageBubbleContentTapAction(content: .none) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index f1263d1588..bf1b1cde37 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -308,7 +308,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { guard let item = self.item else{ return } - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } private func makeProgress() -> Promise { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD index 5fbdd265aa..9540dfa6d9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/AvatarNode", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index fbb56f38f3..c48d216161 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AvatarNode @@ -534,7 +533,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? switch position { @@ -568,7 +567,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, impressionCount: !item.presentationData.isPreview ? viewCount : nil, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), + layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: CGSize(width: constrainedSize.width - sideInsets, height: .greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -582,7 +581,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, starsCount: nil, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD index becb2bbd06..5ec7467b05 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUIPreferences", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift index b7b1f1fd10..1b6e4b11d6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import ComponentFlow @@ -94,7 +93,7 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN self.interactiveVideoNode.requestUpdateLayout = { [weak self] _ in if let strongSelf = self, let item = strongSelf.item { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } self.interactiveVideoNode.updateTranscriptionExpanded = { [weak self] state in @@ -102,13 +101,13 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN let previous = strongSelf.audioTranscriptionState strongSelf.audioTranscriptionState = state strongSelf.interactiveFileNode.audioTranscriptionState = state - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, state != .inProgress && previous != state) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, state != .inProgress && previous != state, nil) } } self.interactiveVideoNode.updateTranscriptionText = { [weak self] text in if let strongSelf = self, let item = strongSelf.item { strongSelf.interactiveFileNode.forcedAudioTranscriptionText = text - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } self.interactiveFileNode.updateTranscriptionExpanded = { [weak self] state in @@ -116,7 +115,7 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN let previous = strongSelf.audioTranscriptionState strongSelf.audioTranscriptionState = state strongSelf.interactiveVideoNode.audioTranscriptionState = state - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, previous != state) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, previous != state, nil) } } @@ -134,7 +133,7 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN self.interactiveFileNode.requestUpdateLayout = { [weak self] _ in if let strongSelf = self, let item = strongSelf.item { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } @@ -393,11 +392,11 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN } } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return false } @@ -465,7 +464,7 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN return nil } - override public func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: EngineStoryId) -> UIView? { return self.interactiveVideoNode.targetForStoryTransition(id: id) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD index 5716eabe19..1dd6a7760c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index e1a44d039f..f8c3faf696 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -48,7 +47,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco public var appliedParams: ListViewItemLayoutParams? public var appliedItem: ChatMessageItem? - public var appliedForwardInfo: (Peer?, String?)? + public var appliedForwardInfo: (EngineRawPeer?, String?)? public var appliedHasAvatar = false public var appliedCurrentlyPlaying: Bool? public var appliedAutomaticDownload = false @@ -461,11 +460,11 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco } } - var replyMessage: Message? + var replyMessage: EngineRawMessage? var replyForward: QuotedReplyMessageAttribute? var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyInnerSubject: EngineMessageReplyInnerSubject? - var replyStory: StoryId? + var replyStory: EngineStoryId? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { var inlineBotNameString: String? @@ -551,7 +550,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco let availableContentWidth = params.width - params.leftInset - params.rightInset - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left - var forwardSource: Peer? + var forwardSource: EngineRawPeer? var forwardAuthorSignature: String? var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)? @@ -578,7 +577,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco } } let availableWidth = max(60.0, availableContentWidth - normalDisplaySize.width + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource.flatMap(EnginePeer.init), forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { @@ -594,7 +593,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco var maxContentWidth = normalDisplaySize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], EngineMessage(item.message), maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -605,7 +604,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco } let reactions: ReactionsMessageAttribute - if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { + if shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) } else { reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) @@ -623,7 +622,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, - message: item.message, + message: EngineMessage(item.message), associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), @@ -706,7 +705,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco strongSelf?.shareButtonPressed() } } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: min(params.width - buttonSize.width - 8.0, videoFrame.maxX - 7.0), y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() @@ -1513,7 +1512,7 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco return nil } - override public func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: EngineStoryId) -> UIView? { guard let item = self.item else { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index cbff3c4238..6be3c27576 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import SwiftSignalKit import Display import TelegramCore @@ -47,8 +46,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { public let context: AccountContext public let presentationData: ChatPresentationData public let customTintColor: UIColor? - public let message: Message - public let topMessage: Message + public let message: EngineRawMessage + public let topMessage: EngineRawMessage public let associatedData: ChatMessageItemAssociatedData public let chatLocation: ChatLocation public let attributes: ChatMessageEntryAttributes @@ -71,8 +70,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { context: AccountContext, presentationData: ChatPresentationData, customTintColor: UIColor?, - message: Message, - topMessage: Message, + message: EngineRawMessage, + topMessage: EngineRawMessage, associatedData: ChatMessageItemAssociatedData, chatLocation: ChatLocation, attributes: ChatMessageEntryAttributes, @@ -181,7 +180,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { private let fetchControls = Atomic(value: nil) private var resourceStatus: FileMediaResourceStatus? - private var actualFetchStatus: MediaResourceStatus? + private var actualFetchStatus: EngineMediaResourceStatus? private let fetchDisposable = MetaDisposable() public var toggleSelection: (Bool) -> Void = { _ in } @@ -192,7 +191,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { public var updateTranscriptionExpanded: ((AudioTranscriptionButtonComponent.TranscriptionState) -> Void)? private var context: AccountContext? - private var message: Message? + private var message: EngineRawMessage? private var arguments: Arguments? private var presentationData: ChatPresentationData? private var file: TelegramMediaFile? @@ -360,7 +359,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 }) - let transcriptionText = self.forcedAudioTranscriptionText ?? transcribedText(message: message) + let transcriptionText = self.forcedAudioTranscriptionText ?? transcribedText(message: EngineMessage(message)) if transcriptionText == nil && !arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { if premiumConfiguration.audioTransciptionTrialCount > 0 { if !arguments.associatedData.isPremium { @@ -402,7 +401,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if case .expanded = self.audioTranscriptionState { shouldExpandNow = true } else { - if let result = transcribedText(message: message) { + if let result = transcribedText(message: EngineMessage(message)) { shouldExpandNow = true if case let .success(_, isPending) = result { @@ -431,10 +430,10 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { guard let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile else { return .single(nil) } - return context.account.postbox.mediaBox.resourceData(id: file.resource.id) + return context.engine.resources.data(id: EngineMediaResource.Id(file.resource.id)) |> take(1) |> mapToSignal { data -> Signal in - if !data.complete { + if !data.isComplete { return .single(nil) } return .single(data.path) @@ -445,7 +444,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { return .single(nil) } return convertOpusToAAC(sourcePath: result, allocateTempFile: { - return TempBox.shared.tempFile(fileName: "audio.m4a").path + return EngineTempBox.shared.tempFile(fileName: "audio.m4a").path }) } |> mapToSignal { result -> Signal in @@ -581,7 +580,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { let durationFont = Font.regular(floor(arguments.presentationData.fontSize.baseDisplaySize * 11.0 / 17.0)) var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedStatusSignal: Signal<(FileMediaResourceStatus, MediaResourceStatus?), NoError>? + var updatedStatusSignal: Signal<(FileMediaResourceStatus, EngineMediaResourceStatus?), NoError>? var updatedAudioLevelEventsSignal: Signal? var updatedPlaybackStatusSignal: Signal? var updatedFetchControls: FetchControls? @@ -622,13 +621,13 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if statusUpdated { if arguments.message.flags.isSending { updatedStatusSignal = combineLatest(messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions), messageMediaFileStatus(context: arguments.context, messageId: arguments.message.id, file: arguments.file)) - |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in + |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, EngineMediaResourceStatus?) in return (resourceStatus, actualFetchStatus) } updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false, isSavedMusic: false) } else { updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions) - |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in + |> map { resourceStatus -> (FileMediaResourceStatus, EngineMediaResourceStatus?) in return (resourceStatus, nil) } updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false, isSavedMusic: false) @@ -772,7 +771,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { displayTranscribe = false } else if arguments.message.id.peerId.namespace != Namespaces.Peer.SecretChat && !isViewOnceMessage && !arguments.presentationData.isPreview { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: arguments.context.currentAppConfiguration.with { $0 }) - if arguments.associatedData.isPremium { + if arguments.associatedData.isPremium || arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { displayTranscribe = true } else if premiumConfiguration.audioTransciptionTrialCount > 0 { if arguments.incoming { @@ -786,12 +785,10 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } else if arguments.incoming && isConsumed == false && arguments.associatedData.alwaysDisplayTranscribeButton.displayForNotConsumed { displayTranscribe = true } - } else if arguments.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { - displayTranscribe = true } } - let transcribedText = forcedAudioTranscriptionText ?? transcribedText(message: arguments.message) + let transcribedText = forcedAudioTranscriptionText ?? transcribedText(message: EngineMessage(arguments.message)) switch audioTranscriptionState { case .inProgress: @@ -933,9 +930,9 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: arguments.message, dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings, format: dateFormat, associatedData: arguments.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: arguments.context.account.peerId, message: EngineMessage(arguments.message), dateTimeFormat: arguments.presentationData.dateTimeFormat, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, strings: arguments.presentationData.strings, format: dateFormat, associatedData: arguments.associatedData) - let displayReactionsInline = shouldDisplayInlineDateReactions(message: arguments.message, isPremium: arguments.associatedData.isPremium, forceInline: arguments.associatedData.forceInlineReactions) + let displayReactionsInline = shouldDisplayInlineDateReactions(message: EngineMessage(arguments.message), isPremium: arguments.associatedData.isPremium, forceInline: arguments.associatedData.forceInlineReactions) var reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings? if displayReactionsInline || arguments.displayReactions { @@ -970,7 +967,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { starsCount: starsCount, isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode, hasAutoremove: arguments.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: arguments.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(arguments.topMessage)), animationCache: arguments.controllerInteraction.presentationContext.animationCache, animationRenderer: arguments.controllerInteraction.presentationContext.animationRenderer )) @@ -1427,7 +1424,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if var updatedStatusSignal = updatedStatusSignal { if strongSelf.file?.isInstantVideo == true { updatedStatusSignal = updatedStatusSignal - |> mapToThrottled { next -> Signal<(FileMediaResourceStatus, MediaResourceStatus?), NoError> in + |> mapToThrottled { next -> Signal<(FileMediaResourceStatus, EngineMediaResourceStatus?), NoError> in return .single(next) |> then(.complete() |> delay(0.1, queue: Queue.concurrentDefaultQueue())) } } @@ -1652,7 +1649,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { var playbackState: (position: Double, duration: Double, generationTimestamp: Double) = (0.0, 0.0, 0.0) if !isAudio { - var fetchStatus: MediaResourceStatus? + var fetchStatus: EngineMediaResourceStatus? if let actualFetchStatus = self.actualFetchStatus, message.forwardInfo != nil { fetchStatus = actualFetchStatus } else if case let .fetchStatus(status) = resourceStatus.mediaStatus { @@ -1815,7 +1812,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - image = playerAlbumArt(postbox: context.account.postbox, engine: context.engine, fileReference: .message(message: MessageReference(message), media: file), albumArt: .init(thumbnailResource: ExternalMusicAlbumArtResource(file: .message(message: MessageReference(message), media: file), title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(file: .message(message: MessageReference(message), media: file), title: title ?? "", performer: performer ?? "", isThumbnail: false)), thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), drawPlaceholderWhenEmpty: false, attemptSynchronously: !animated) + image = playerAlbumArt(engine: context.engine, fileReference: .message(message: MessageReference(message), media: file), albumArt: .init(thumbnailResource: ExternalMusicAlbumArtResource(file: .message(message: MessageReference(message), media: file), title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(file: .message(message: MessageReference(message), media: file), title: title ?? "", performer: performer ?? "", isThumbnail: false)), thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), drawPlaceholderWhenEmpty: false, attemptSynchronously: !animated) } } let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor) @@ -2081,7 +2078,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - public func transitionNode(media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(media: EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if let iconNode = self.iconNode, let file = self.file, file.isEqual(to: media) { return (iconNode, iconNode.bounds, { [weak iconNode] in return (iconNode?.view.snapshotContentTree(unhide: true), nil) @@ -2091,7 +2088,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { } } - public func updateHiddenMedia(_ media: [Media]?) -> Bool { + public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var isHidden = false if let file = self.file, let media = media { for m in media { @@ -2227,4 +2224,3 @@ public final class FileMessageSelectionNode: ASDisplayNode { self.checkNode.frame = CGRect(origin: checkOrigin, size: checkSize) } } - diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift index 70ac211208..e200998393 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveInstantVideoNode/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -455,7 +455,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } } let availableWidth: CGFloat = max(60.0, availableContentWidth - 220.0 + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource.flatMap(EnginePeer.init), forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) } var notConsumed = false @@ -552,7 +552,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData, ignoreAuthor: item.presentationData.isPreview) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData, ignoreAuthor: item.presentationData.isPreview) let maxDateAndStatusWidth: CGFloat if case .bubble = statusDisplayType { @@ -579,7 +579,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { impressionCount: !item.presentationData.isPreview ? viewCount : nil, dateText: dateText, type: statusType, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -593,7 +593,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -617,7 +617,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let result = ChatMessageInstantVideoItemLayoutResult(contentSize: contentSize, overflowLeft: 0.0, overflowRight: dateAndStatusOverflow ? 0.0 : (max(0.0, floorToScreenPixels(videoFrame.midX) + 55.0 + dateAndStatusSize.width - videoFrame.width))) var updatedAudioTranscriptionState: AudioTranscriptionButtonComponent.TranscriptionState? - let transcribedText = transcribedText(message: item.message) + let transcribedText = transcribedText(message: EngineMessage(item.message)) switch audioTranscriptionState { case .inProgress: @@ -703,7 +703,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if let updatedFile = updatedFile, updatedMedia { if let resource = updatedFile.previewRepresentations.first?.resource { - strongSelf.fetchedThumbnailDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .peer(item.message.id.peerId), userContentType: .video, reference: FileMediaReference.message(message: MessageReference(item.message), media: updatedFile).resourceReference(resource)).startStrict()) + strongSelf.fetchedThumbnailDisposable.set(item.context.engine.resources.fetch(reference: FileMediaReference.message(message: MessageReference(item.message), media: updatedFile).resourceReference(resource), userLocation: .peer(item.message.id.peerId), userContentType: .video).startStrict()) } else { strongSelf.fetchedThumbnailDisposable.set(nil) } @@ -763,7 +763,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } durationNode.defaultDuration = telegramFile.duration.flatMap(Double.init) - let streamVideo = automaticDownload && isMediaStreamable(message: item.message, media: telegramFile) && telegramFile.id?.namespace != Namespaces.Media.LocalFile + let streamVideo = automaticDownload && isMediaStreamable(message: EngineMessage(item.message), media: telegramFile) && telegramFile.id?.namespace != Namespaces.Media.LocalFile if let videoNode = strongSelf.videoNode { videoNode.layer.allowsGroupOpacity = true videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, delay: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in @@ -840,7 +840,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var displayTranscribe = false if item.message.id.peerId.namespace != Namespaces.Peer.SecretChat && statusDisplayType == .free && !isViewOnceMessage && !item.presentationData.isPreview { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - if item.associatedData.isPremium { + if item.associatedData.isPremium || item.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { displayTranscribe = true } else if premiumConfiguration.audioTransciptionTrialCount > 0 { if incoming { @@ -852,8 +852,6 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } else { displayTranscribe = false } - } else if item.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { - displayTranscribe = true } } @@ -1248,7 +1246,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { self.infoBackgroundNode.isHidden = isViewOnceMessage var isBuffering: Bool? - if let message = self.item?.message, let media = self.media, isMediaStreamable(message: message, media: media) && (self.automaticDownload ?? false) { + if let message = self.item?.message, let media = self.media, isMediaStreamable(message: EngineMessage(message), media: media) && (self.automaticDownload ?? false) { if let playerStatus = self.playerStatus, case .buffering = playerStatus.status { isBuffering = true } else { @@ -1833,7 +1831,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) - let transcriptionText = transcribedText(message: item.message) + let transcriptionText = transcribedText(message: EngineMessage(item.message)) if transcriptionText == nil && !item.associatedData.alwaysDisplayTranscribeButton.providedByGroupBoost { if premiumConfiguration.audioTransciptionTrialCount > 0 { if !item.associatedData.isPremium { @@ -1876,7 +1874,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { if case .expanded = self.audioTranscriptionState { shouldExpandNow = true } else { - if let result = transcribedText(message: item.message) { + if let result = transcribedText(message: EngineMessage(item.message)) { shouldExpandNow = true if case let .success(_, isPending) = result { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 9f9796ea95..f2177bb634 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1117,7 +1117,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr impressionCount: !presentationData.isPreview ? dateAndStatus.viewCount : nil, dateText: dateAndStatus.dateText, type: dateAndStatus.type, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: message, isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(message), isPremium: associatedData.isPremium, forceInline: associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: nativeSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude), availableReactions: associatedData.availableReactions, savedMessageTags: associatedData.savedMessageTags, @@ -1131,7 +1131,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr starsCount: dateAndStatus.starsCount, isPinned: dateAndStatus.isPinned, hasAutoremove: message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: message), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(message)), animationCache: presentationContext.animationCache, animationRenderer: presentationContext.animationRenderer )) @@ -1471,7 +1471,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } }, cancel: { if file.isAnimated { - context.account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) + context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(file.resource.id)) } else { messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) } @@ -1706,7 +1706,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } }, cancel: { if file.isAnimated { - context.account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) + context.engine.resources.cancelInteractiveResourceFetch(id: EngineMediaResource.Id(file.resource.id)) } else { messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) } @@ -1766,7 +1766,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr if NativeVideoContent.isHLSVideo(file: file), let minimizedQuality = HLSVideoContent.minimizedHLSQuality(file: .standalone(media: file), codecConfiguration: HLSCodecConfiguration(context: context)) { let postbox = context.account.postbox - let playlistStatusSignal = postbox.mediaBox.resourceStatus(minimizedQuality.playlist.media.resource) + let playlistStatusSignal = context.engine.resources.status(resource: EngineMediaResource(minimizedQuality.playlist.media.resource)) |> map { status -> MediaResourceStatus in switch status { case .Fetching, .Paused: @@ -1796,7 +1796,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr return .single((.Local, nil)) } - return postbox.mediaBox.resourceStatus(preloadData.0.media.resource) + return context.engine.resources.status(resource: EngineMediaResource(preloadData.0.media.resource)) |> map { status -> Bool in if case .Fetching = status { return true @@ -1806,7 +1806,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } |> distinctUntilChanged |> mapToSignal { isFetching -> Signal<(MediaResourceStatus, MediaResourceStatus?), NoError> in - return postbox.mediaBox.resourceRangesStatus(preloadData.0.media.resource) + return context.engine.resources.resourceRangesStatus(resource: EngineMediaResource(preloadData.0.media.resource)) |> map { status -> (MediaResourceStatus, MediaResourceStatus?) in let preloadRanges = RangeSet(preloadData.1) let intersection = status.intersection(preloadRanges) @@ -1985,7 +1985,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr strongSelf.videoNodeDecoration = decoration let mediaManager = context.sharedContext.mediaManager - let streamVideo = isMediaStreamable(message: message, media: updatedVideoFile) + let streamVideo = isMediaStreamable(message: EngineMessage(message), media: updatedVideoFile) let loopVideo = updatedVideoFile.isAnimated let videoContent: UniversalVideoContent @@ -2094,6 +2094,32 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } if currentReplaceAnimatedStickerNode, let updatedAnimatedStickerFile = updateAnimatedStickerFile { + var allowSticker = false + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + if updatedAnimatedStickerFile.fileId.namespace == Namespaces.Media.CloudFile { + var isValidated = false + for attribute in updatedAnimatedStickerFile.attributes { + if case .hintIsValidated = attribute { + isValidated = true + break + } + } + + inner: for attribute in updatedAnimatedStickerFile.attributes { + if case let .Sticker(_, packReference, _) = attribute { + if case .name = packReference { + allowSticker = true + } else if isValidated { + allowSticker = true + } + break inner + } + } + } + } else { + allowSticker = true + } + let animatedStickerNode = DefaultAnimatedStickerNodeImpl() animatedStickerNode.isUserInteractionEnabled = false animatedStickerNode.started = { @@ -2105,7 +2131,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr strongSelf.animatedStickerNode = animatedStickerNode let dimensions = updatedAnimatedStickerFile.dimensions ?? PixelDimensions(width: 512, height: 512) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) - animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: updatedAnimatedStickerFile.resource, isVideo: updatedAnimatedStickerFile.isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) + if allowSticker { + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: updatedAnimatedStickerFile.resource, isVideo: updatedAnimatedStickerFile.isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) + } strongSelf.pinchContainerNode.contentNode.insertSubnode(animatedStickerNode, aboveSubnode: strongSelf.imageNode) animatedStickerNode.visibility = strongSelf.visibility } @@ -2777,7 +2805,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } if let duration = file.duration, !message.flags.contains(.Unsent) { let durationString = file.isAnimated ? gifTitle : stringForDuration(playerDuration > 0 ? playerDuration : Int32(duration), position: playerPosition) - if isMediaStreamable(message: message, media: file) { + if isMediaStreamable(message: EngineMessage(message), media: file) { badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: muted, active: active) mediaDownloadState = .fetching(progress: automaticPlayback ? nil : adjustedProgress) if self.playerStatus?.status == .playing { @@ -2803,11 +2831,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: strings.Conversation_Processing, size: nil, muted: false, active: false) } } - if file.isAnimated && isMediaStreamable(message: message, media: file) { + if file.isAnimated && isMediaStreamable(message: EngineMessage(message), media: file) { state = automaticPlayback ? .none : state } } else { - if isMediaStreamable(message: message, media: file), let fileSize = file.size, fileSize > 0 && fileSize != .max { + if isMediaStreamable(message: EngineMessage(message), media: file), let fileSize = file.size, fileSize > 0 && fileSize != .max { let sizeString = "\(dataSizeString(Int64(Float(fileSize) * progress), forceDecimal: true, formatting: formatting)) / \(dataSizeString(fileSize, forceDecimal: true, formatting: formatting))" if message.flags.contains(.Unsent), let duration = file.duration { @@ -2883,7 +2911,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr do { let durationString = file.isAnimated ? gifTitle : stringForDuration(playerDuration > 0 ? playerDuration : (file.duration.flatMap { Int32(floor($0)) } ?? 0), position: playerPosition) if wideLayout { - if isMediaStreamable(message: message, media: file), let fileSize = file.size, fileSize > 0 && fileSize != .max { + if isMediaStreamable(message: EngineMessage(message), media: file), let fileSize = file.size, fileSize > 0 && fileSize != .max { state = automaticPlayback ? .none : .play(messageTheme.mediaOverlayControlColors.foregroundColor) badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(fileSize, formatting: formatting), muted: muted, active: true) mediaDownloadState = .remote @@ -2892,7 +2920,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr badgeContent = .mediaDownload(backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: muted, active: false) } } else { - if isMediaStreamable(message: message, media: file) { + if isMediaStreamable(message: EngineMessage(message), media: file) { state = automaticPlayback ? .none : .play(messageTheme.mediaOverlayControlColors.foregroundColor) badgeContent = .text(inset: 12.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: NSAttributedString(string: durationString), iconName: nil) mediaDownloadState = .compactRemote diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD index 78df073283..2d136e362f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift index 02855854c9..e6034a6550 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInvoiceBubbleContentNode/Sources/ChatMessageInvoiceBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -52,7 +51,7 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent var title: String? var subtitle: NSAttributedString? = nil var text: String? - var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? + var mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? var automaticDownloadSettings = item.controllerInteraction.automaticMediaDownloadSettings if let invoice = invoice { @@ -128,11 +127,11 @@ public final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContent return ChatMessageBubbleContentTapAction(content: .none) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD index 8c82069452..df02b6c4ad 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift index f736e8cbee..67ae7c2ef2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItem/Sources/ChatMessageItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import AsyncDisplayKit import Display import SwiftSignalKit @@ -12,10 +11,10 @@ import TelegramPresentationData import ChatMessageItemCommon public enum ChatMessageItemContent: Sequence { - case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) - case group(messages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)]) - - public func effectivelyIncoming(_ accountPeerId: PeerId, associatedData: ChatMessageItemAssociatedData? = nil) -> Bool { + case message(message: EngineRawMessage, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: EngineMessageHistoryEntryLocation?) + case group(messages: [(EngineRawMessage, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, EngineMessageHistoryEntryLocation?)]) + + public func effectivelyIncoming(_ accountPeerId: EnginePeer.Id, associatedData: ChatMessageItemAssociatedData? = nil) -> Bool { if let subject = associatedData?.subject, case let .messageOptions(_, _, info) = subject { if case .forward = info { return false @@ -31,7 +30,7 @@ public enum ChatMessageItemContent: Sequence { } } - public var index: MessageIndex { + public var index: EngineMessage.Index { switch self { case let .message(message, _, _, _, _): return message.index @@ -40,7 +39,7 @@ public enum ChatMessageItemContent: Sequence { } } - public var firstMessage: Message { + public var firstMessage: EngineRawMessage { switch self { case let .message(message, _, _, _, _): return message @@ -58,9 +57,9 @@ public enum ChatMessageItemContent: Sequence { } } - public func makeIterator() -> AnyIterator<(Message, ChatMessageEntryAttributes)> { + public func makeIterator() -> AnyIterator<(EngineRawMessage, ChatMessageEntryAttributes)> { var index = 0 - return AnyIterator { () -> (Message, ChatMessageEntryAttributes)? in + return AnyIterator { () -> (EngineRawMessage, ChatMessageEntryAttributes)? in switch self { case let .message(message, _, _, attributes, _): if index == 0 { @@ -84,10 +83,10 @@ public enum ChatMessageItemContent: Sequence { } public enum ChatMessageItemAdditionalContent { - case eventLogPreviousMessage(Message) - case eventLogPreviousDescription(Message) - case eventLogPreviousLink(Message) - case eventLogGroupedMessages([Message], Bool) + case eventLogPreviousMessage(EngineRawMessage) + case eventLogPreviousDescription(EngineRawMessage) + case eventLogPreviousLink(EngineRawMessage) + case eventLogGroupedMessages([EngineRawMessage], Bool) } public enum ChatMessageMerge: Int32 { @@ -131,12 +130,12 @@ public protocol ChatMessageItem: ListViewItem { var controllerInteraction: ChatControllerInteraction { get } var content: ChatMessageItemContent { get } var disableDate: Bool { get } - var effectiveAuthorId: PeerId? { get } + var effectiveAuthorId: EnginePeer.Id? { get } var additionalContent: ChatMessageItemAdditionalContent? { get } var headers: [ListViewItemHeader] { get } - var message: Message { get } + var message: EngineRawMessage { get } var read: Bool { get } var unsent: Bool { get } var sending: Bool { get } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD index 0d4b817002..a8832059b6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/Emoji", ], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index 7a0c2f956b..fc189879a3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import Emoji @@ -138,7 +137,7 @@ public struct ChatMessageItemLayoutConstants { public static var compact: ChatMessageItemLayoutConstants { let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 3.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)) let text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 11.0, bottom: 6.0 - UIScreenPixel, right: 11.0)) - let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 16.0, mergedCornerRadius: 8.0, contentMergedCornerRadius: 0.0, maxDimensions: CGSize(width: 300.0, height: 380.0), minDimensions: CGSize(width: 170.0, height: 74.0)) + let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 15.0, mergedCornerRadius: 7.0, contentMergedCornerRadius: 0.0, maxDimensions: CGSize(width: 300.0, height: 380.0), minDimensions: CGSize(width: 170.0, height: 74.0)) let video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0) let file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0)) let instantVideo = ChatMessageItemInstantVideoConstants(insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), dimensions: CGSize(width: 212.0, height: 212.0)) @@ -160,7 +159,7 @@ public struct ChatMessageItemLayoutConstants { } } -public func canViewMessageReactionList(message: Message) -> Bool { +public func canViewMessageReactionList(message: EngineMessage) -> Bool { var found = false var canViewList = false for attribute in message.attributes { @@ -209,7 +208,7 @@ public enum TranscribedText: Equatable { case error(AudioTranscriptionMessageAttribute.TranscriptionError) } -public func transcribedText(message: Message) -> TranscribedText? { +public func transcribedText(message: EngineMessage) -> TranscribedText? { for attribute in message.attributes { if let attribute = attribute as? AudioTranscriptionMessageAttribute { if !attribute.text.isEmpty { @@ -226,7 +225,7 @@ public func transcribedText(message: Message) -> TranscribedText? { return nil } -public func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) -> Bool { +public func isPollEffectivelyClosed(message: EngineMessage, poll: TelegramMediaPoll) -> Bool { if poll.isClosed { return true } else { @@ -235,12 +234,12 @@ public func isPollEffectivelyClosed(message: Message, poll: TelegramMediaPoll) - } public extension ChatReplyThreadMessage { - var effectiveTopId: MessageId { - return self.channelMessageId ?? MessageId(peerId: self.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: self.threadId)) + var effectiveTopId: EngineMessage.Id { + return self.channelMessageId ?? EngineMessage.Id(peerId: self.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: self.threadId)) } } -public func messageIsEligibleForLargeEmoji(_ message: Message) -> Bool { +public func messageIsEligibleForLargeEmoji(_ message: EngineMessage) -> Bool { if !message.text.isEmpty && message.text.containsOnlyEmoji { if !(message.textEntitiesAttribute?.entities.isEmpty ?? true) { return false @@ -251,7 +250,7 @@ public func messageIsEligibleForLargeEmoji(_ message: Message) -> Bool { } } -public func messageIsEligibleForLargeCustomEmoji(_ message: Message) -> Bool { +public func messageIsEligibleForLargeCustomEmoji(_ message: EngineMessage) -> Bool { let text = message.text.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "") guard !text.isEmpty && text.containsOnlyEmoji else { return false @@ -262,7 +261,7 @@ public func messageIsEligibleForLargeCustomEmoji(_ message: Message) -> Bool { } for entity in entities { if case let .CustomEmoji(_, fileId) = entity.type { - if let _ = message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { + if let _ = message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile { } else { return false @@ -274,7 +273,7 @@ public func messageIsEligibleForLargeCustomEmoji(_ message: Message) -> Bool { return true } -public func canAddMessageReactions(message: Message) -> Bool { +public func canAddMessageReactions(message: EngineMessage) -> Bool { if message.id.namespace != Namespaces.Message.Cloud { return false } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift index 42b4a3638f..bad3a3a22d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageDateHeader.swift @@ -1082,13 +1082,13 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat } if peer.isPremium && context.sharedContext.energyUsageSettings.autoplayVideo { - self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView in + self.cachedDataDisposable.set((context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CachedData(id: peer.id)) + |> deliverOnMainQueue).startStrict(next: { [weak self] cachedData in guard let strongSelf = self else { return } - - let cachedPeerData = peerView.cachedData as? CachedUserData + + let cachedPeerData = cachedData as? CachedUserData var personalPhoto: TelegramMediaImage? var profilePhoto: TelegramMediaImage? var isKnown = false @@ -1102,6 +1102,10 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat profilePhoto = maybePhoto isKnown = true } + if profilePhoto == nil, case let .known(maybePhoto) = cachedPeerData.fallbackPhoto { + profilePhoto = maybePhoto + isKnown = true + } } if isKnown { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index 881982906f..8499f95c2d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import AsyncDisplayKit import Display import SwiftSignalKit @@ -18,7 +17,7 @@ import ChatMessageStickerItemNode import ChatMessageAnimatedStickerItemNode import ChatMessageBubbleItemNode -private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { +private func mediaMergeableStyle(_ media: EngineRawMedia) -> ChatMessageMerge { if let story = media as? TelegramMediaStory, story.isMention { return .none } @@ -47,9 +46,22 @@ private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { return .fullyMerged } -private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs: Message) -> ChatMessageMerge { - var lhsEffectiveAuthor: Peer? = lhs.author - var rhsEffectiveAuthor: Peer? = rhs.author +private func anonymousGroupAdminSignature(message: EngineRawMessage, effectiveAuthor: EngineRawPeer?) -> String? { + guard let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info else { + return nil + } + guard effectiveAuthor?.id == channel.id else { + return nil + } + guard let signature = message.authorSignatureAttribute?.signature, !signature.isEmpty else { + return nil + } + return signature +} + +private func messagesShouldBeMerged(accountPeerId: EnginePeer.Id, _ lhs: EngineRawMessage, _ rhs: EngineRawMessage) -> ChatMessageMerge { + var lhsEffectiveAuthor: EngineRawPeer? = lhs.author + var rhsEffectiveAuthor: EngineRawPeer? = rhs.author for attribute in lhs.attributes { if let attribute = attribute as? SourceReferenceMessageAttribute { lhsEffectiveAuthor = lhs.peers[attribute.messageId.peerId] @@ -112,6 +124,14 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs sameAuthor = false } + if sameAuthor { + let lhsAnonymousAdminSignature = anonymousGroupAdminSignature(message: lhs, effectiveAuthor: lhsEffectiveAuthor) + let rhsAnonymousAdminSignature = anonymousGroupAdminSignature(message: rhs, effectiveAuthor: rhsEffectiveAuthor) + if lhsAnonymousAdminSignature != rhsAnonymousAdminSignature && (lhsAnonymousAdminSignature != nil || rhsAnonymousAdminSignature != nil) { + sameAuthor = false + } + } + var lhsEffectiveTimestamp = lhs.timestamp var rhsEffectiveTimestamp = rhs.timestamp @@ -223,7 +243,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible public let controllerInteraction: ChatControllerInteraction public let content: ChatMessageItemContent public let disableDate: Bool - public let effectiveAuthorId: PeerId? + public let effectiveAuthorId: EnginePeer.Id? public let additionalContent: ChatMessageItemAdditionalContent? let dateHeader: ChatMessageDateHeader @@ -232,7 +252,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible public let headers: [ListViewItemHeader] - public var message: Message { + public var message: EngineRawMessage { switch self.content { case let .message(message, _, _, _, _): return message @@ -270,10 +290,19 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible public var failed: Bool { switch self.content { - case let .message(message, _, _, _, _): - return message.flags.contains(.Failed) - case let .group(messages): - return messages[0].0.flags.contains(.Failed) + case let .message(message, _, _, _, _): + return message.flags.contains(.Failed) + case let .group(messages): + return messages[0].0.flags.contains(.Failed) + } + } + + public var pinToEdgeWithInset: Bool { + switch self.content { + case let .message(_, _, _, attributes, _): + return attributes.pinToTop + case let .group(messages): + return messages[0].3.pinToTop } } @@ -290,10 +319,10 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible var avatarHeader: ChatMessageAvatarHeader? let incoming = content.effectivelyIncoming(self.context.account.peerId) - var effectiveAuthor: Peer? + var effectiveAuthor: EngineRawPeer? var displayAuthorInfo: Bool - let messagePeerId: PeerId = chatLocation.peerId ?? content.firstMessage.id.peerId + let messagePeerId: EnginePeer.Id = chatLocation.peerId ?? content.firstMessage.id.peerId var headerSeparableThreadId: Int64? var headerDisplayPeer: ChatMessageDateHeader.HeaderData? @@ -303,14 +332,14 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible if let forwardInfo = content.firstMessage.forwardInfo { effectiveAuthor = forwardInfo.author if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + effectiveAuthor = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } if let sourceAuthorInfo = content.firstMessage.sourceAuthorInfo { if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = content.firstMessage.peers[originalAuthor] { effectiveAuthor = peer } else if let authorSignature = sourceAuthorInfo.originalAuthorName { - effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + effectiveAuthor = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } if peerId.isVerificationCodes && effectiveAuthor == nil { @@ -327,6 +356,10 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible } displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil + if let _ = content.firstMessage.guestChatAttribute { + displayAuthorInfo = true + } + if let chatPeer = content.firstMessage.peers[content.firstMessage.id.peerId], chatPeer.isForumOrMonoForum { if case .replyThread = chatLocation { if chatPeer.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() { @@ -347,7 +380,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .thread(id: threadId, info: threadInfo)) } else if content.firstMessage.threadId == EngineMessage.newTopicThreadId { headerSeparableThreadId = content.firstMessage.threadId - headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .thread(id: threadId, info: Message.AssociatedThreadInfo( + headerDisplayPeer = ChatMessageDateHeader.HeaderData(contents: .thread(id: threadId, info: EngineRawMessage.AssociatedThreadInfo( title: presentationData.strings.Chat_MessageHeaderBotNewThread, icon: nil, iconColor: 0, @@ -421,7 +454,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible if hasAvatar { if let effectiveAuthor = effectiveAuthor { - var storyStats: PeerStoryStats? + var storyStats: EnginePeerStoryStats? if case .peer(id: context.account.peerId) = chatLocation { } else { switch content { @@ -518,7 +551,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible } } - if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && self.message.media.isEmpty { + if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && self.message.media.isEmpty && !self.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { if case let .message(_, _, _, attributes, _) = self.content { switch attributes.contentTypeHint { case .largeEmoji: diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift index 4c393049f5..d4574e9b06 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatReplyCountItem.swift @@ -1,6 +1,6 @@ import Foundation import UIKit -import Postbox +import TelegramCore import AsyncDisplayKit import Display import SwiftSignalKit @@ -13,14 +13,14 @@ import ChatMessageItemCommon private let titleFont = UIFont.systemFont(ofSize: 13.0) public class ChatReplyCountItem: ListViewItem { - public let index: MessageIndex + public let index: EngineMessage.Index public let isComments: Bool public let count: Int public let presentationData: ChatPresentationData public let header: ChatMessageDateHeader public let controllerInteraction: ChatControllerInteraction - public init(index: MessageIndex, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction) { + public init(index: EngineMessage.Index, isComments: Bool, count: Int, presentationData: ChatPresentationData, context: AccountContext, controllerInteraction: ChatControllerInteraction) { self.index = index self.isComments = isComments self.count = count diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift index 63c5f26b05..d0f57ba5f5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatUnreadItem.swift @@ -1,6 +1,6 @@ import Foundation import UIKit -import Postbox +import TelegramCore import AsyncDisplayKit import Display import SwiftSignalKit @@ -13,12 +13,12 @@ import ChatMessageItemCommon private let titleFont = UIFont.systemFont(ofSize: 13.0) public class ChatUnreadItem: ListViewItem { - public let index: MessageIndex + public let index: EngineMessage.Index public let presentationData: ChatPresentationData public let controllerInteraction: ChatControllerInteraction public let header: ChatMessageDateHeader - public init(index: MessageIndex, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, context: AccountContext) { + public init(index: EngineMessage.Index, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, context: AccountContext) { self.index = index self.presentationData = presentationData self.controllerInteraction = controllerInteraction diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD index e935774197..eba7dfef21 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/LocalizedPeerData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index efcd885e4c..684c085925 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import AccountContext import LocalizedPeerData @@ -26,8 +25,8 @@ public func chatMessageItemLayoutConstants(_ constants: (ChatMessageItemLayoutCo } else { result = constants.0 } - result.image.defaultCornerRadius = presentationData.chatBubbleCorners.mainRadius - result.image.mergedCornerRadius = (presentationData.chatBubbleCorners.mergeBubbleCorners && result.image.defaultCornerRadius >= 10.0) ? presentationData.chatBubbleCorners.auxiliaryRadius : presentationData.chatBubbleCorners.mainRadius + result.image.defaultCornerRadius = max(0.0, presentationData.chatBubbleCorners.mainRadius - 1.0) + result.image.mergedCornerRadius = max(0.0, ((presentationData.chatBubbleCorners.mergeBubbleCorners && result.image.defaultCornerRadius >= 10.0) ? presentationData.chatBubbleCorners.auxiliaryRadius : presentationData.chatBubbleCorners.mainRadius) - 1.0) let minRadius: CGFloat = 4.0 let maxRadius: CGFloat = 16.0 let radiusTransition = (presentationData.chatBubbleCorners.mainRadius - minRadius) / (maxRadius - minRadius) @@ -105,7 +104,7 @@ public final class ChatMessageAccessibilityData { } } - let dataForMessage: (Message, Bool) -> (String, String) = { message, isReply -> (String, String) in + let dataForMessage: (EngineRawMessage, Bool) -> (String, String) = { message, isReply -> (String, String) in var label: String = "" var value: String = "" @@ -634,12 +633,12 @@ public enum InternalBubbleTapAction { } public struct OpenContextMenu { - public var tapMessage: Message + public var tapMessage: EngineRawMessage public var selectAll: Bool public var subFrame: CGRect public var disableDefaultPressAnimation: Bool - public init(tapMessage: Message, selectAll: Bool, subFrame: CGRect, disableDefaultPressAnimation: Bool = false) { + public init(tapMessage: EngineRawMessage, selectAll: Bool, subFrame: CGRect, disableDefaultPressAnimation: Bool = false) { self.tapMessage = tapMessage self.selectAll = selectAll self.subFrame = subFrame @@ -727,7 +726,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { } } - public func matchesMessage(id: MessageId) -> Bool { + public func matchesMessage(id: EngineMessage.Id) -> Bool { if let item = self.item { for (message, _) in item.content { if message.id == id { @@ -738,18 +737,18 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { return false } - public func messages() -> [Message] { + public func messages() -> [EngineRawMessage] { guard let item = self.item else { return [] } - var messages: [Message] = [] + var messages: [EngineRawMessage] = [] for (message, _) in item.content { messages.append(message) } return messages } - open func transitionNode(id: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + open func transitionNode(id: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } @@ -812,7 +811,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { if let item = self.item { switch button.action { case .text: - item.controllerInteraction.sendMessage(button.title) + item.controllerInteraction.sendMessage(button.title, item.message.id) case let .url(url): var concealed = true if url.hasPrefix("tg://") { @@ -820,15 +819,15 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { } item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, progress: progress)) case .requestMap: - item.controllerInteraction.shareCurrentLocation() + item.controllerInteraction.shareCurrentLocation(item.message.id) case .requestPhone: - item.controllerInteraction.shareAccountContact() + item.controllerInteraction.shareAccountContact(item.message.id) case .openWebApp: item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false, progress) case let .callback(requiresPassword, data): item.controllerInteraction.requestMessageActionCallback(item.message, data, false, requiresPassword, progress) case let .switchInline(samePeer, query, peerTypes): - var botPeer: Peer? + var botPeer: EngineRawPeer? var found = false for attribute in item.message.attributes { @@ -843,7 +842,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { botPeer = item.message.author } - var peerId: PeerId? + var peerId: EnginePeer.Id? if samePeer { peerId = item.message.id.peerId } @@ -895,13 +894,17 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { return nil } - open func targetForStoryTransition(id: StoryId) -> UIView? { + open func targetForStoryTransition(id: EngineStoryId) -> UIView? { return nil } open func getStatusNode() -> ASDisplayNode? { return nil } + + open func getAuthorNameNode() -> ASDisplayNode? { + return nil + } private var attachedAvatarNodeOffset: CGFloat = 0.0 private var attachedAvatarNodeIsHidden: Bool = false @@ -995,7 +998,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { return nil } - private func playEffectAnimation(resource: MediaResource) { + private func playEffectAnimation(resource: EngineRawMediaResource) { guard let item = self.item else { return } @@ -1021,7 +1024,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId) do { - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(resource.id)) let additionalAnimationNode: AnimatedStickerNode var effectiveScale: CGFloat = 1.0 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift index 0455a1c4a5..86131766f1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageJoinedChannelBubbleContentNode/Sources/ChatMessageJoinedChannelBubbleContentNode.swift @@ -337,7 +337,7 @@ public class ChatMessageJoinedChannelBubbleContentNode: ChatMessageBubbleContent jsonString += "}" if let data = jsonString.data(using: .utf8), let json = JSON(data: data) { - addAppLogEvent(postbox: item.context.account.postbox, type: "channels.open_recommended_channel", data: json) + item.context.engine.accountData.addAppLogEvent(type: "channels.open_recommended_channel", data: json) } item.controllerInteraction.openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) } else { @@ -777,7 +777,7 @@ private final class ChannelItemComponent: Component { self.mergedAvatarsNode = mergedAvatarsNode } - mergedAvatarsNode.update(context: component.context, peers: component.peers.map { $0._asPeer() }, synchronousLoad: false, imageSize: 60.0, imageSpacing: 10.0, borderWidth: 2.0, avatarFontSize: 26.0) + mergedAvatarsNode.update(context: component.context, peers: component.peers, synchronousLoad: false, imageSize: 60.0, imageSpacing: 10.0, borderWidth: 2.0, avatarFontSize: 26.0) let avatarsSize = CGSize(width: avatarSize.width + 20.0, height: avatarSize.height) mergedAvatarsNode.updateLayout(size: avatarsSize) mergedAvatarsNode.frame = CGRect(origin: CGPoint(x: avatarFrame.midX - avatarsSize.width / 2.0, y: avatarFrame.minY), size: avatarsSize) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD index 319e0fe189..efeef468cf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/LiveLocationTimerNode", "//submodules/PhotoResources", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift index 367234a0e1..e66c17d057 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMapBubbleContentNode/Sources/ChatMessageMapBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LiveLocationTimerNode import PhotoResources @@ -225,7 +224,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -279,7 +278,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { impressionCount: !item.presentationData.isPreview ? viewCount : nil, dateText: dateText, type: statusType, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -293,7 +292,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -439,7 +438,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { if let strongSelf = self { strongSelf.timeoutTimer?.0.invalidate() strongSelf.timeoutTimer = nil - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } }, queue: Queue.mainQueue()) strongSelf.timeoutTimer = (timer, timeoutDeadline) @@ -505,7 +504,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, let currentMedia = self.media, currentMedia.isEqual(to: media) { let imageNode = self.imageNode return (self.imageNode, self.imageNode.bounds, { [weak imageNode] in @@ -515,7 +514,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { return nil } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false if let currentMedia = self.media, let media = media { for item in media { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD index e5008a8e93..54f5c9efbb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUIPreferences", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index a979e9f00f..21feda7d24 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import TelegramPresentationData @@ -27,7 +26,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { private var selectionNode: GridMessageSelectionNode? private var highlightedState: Bool = false - private var media: Media? + private var media: EngineRawMedia? private var mediaIndex: Int? private var automaticPlayback: Bool? @@ -116,7 +115,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { let interactiveImageLayout = self.interactiveImageNode.asyncLayout() return { item, layoutConstants, preparePosition, selection, constrainedSize, _ in - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? var selectedMediaIndex: Int? var extendedMedia: TelegramExtendedMedia? var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none @@ -158,13 +157,13 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.context.sharedContext.energyUsageSettings.autoplayVideo { if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } } @@ -184,7 +183,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.context.sharedContext.energyUsageSettings.autoplayVideo { if let _ = telegramFile.videoCover { @@ -194,7 +193,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } } @@ -240,7 +239,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } } @@ -265,7 +264,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } else if (telegramFile.isVideo && !telegramFile.isAnimated) && item.context.sharedContext.energyUsageSettings.autoplayVideo { if let _ = telegramFile.videoCover { @@ -275,7 +274,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(telegramFile.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(telegramFile.resource.id)) != nil } } } @@ -378,7 +377,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -512,7 +511,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId, var currentMedia = self.media { if let invoice = currentMedia as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia, case let .full(fullMedia) = extendedMedia { currentMedia = fullMedia @@ -527,7 +526,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { return nil } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false var currentMedia = self.media diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD index 0b956ee90f..0b7384f174 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift index 0c2b0da404..cd776d9aa5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatCallNotificationItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift index 466f3f0e1f..506dd18540 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem/Sources/ChatMessageNotificationItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -24,8 +23,8 @@ public final class ChatMessageNotificationItem: NotificationItem { public let strings: PresentationStrings public let dateTimeFormat: PresentationDateTimeFormat public let nameDisplayOrder: PresentationPersonNameOrder - public let messages: [Message] - public let threadData: MessageHistoryThreadData? + public let messages: [EngineRawMessage] + public let threadData: EngineMessageHistoryThreadData? public let tapAction: () -> Bool public let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void @@ -33,7 +32,7 @@ public final class ChatMessageNotificationItem: NotificationItem { return messages.first?.id.peerId } - public init(context: AccountContext, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messages: [Message], threadData: MessageHistoryThreadData?, tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) { + public init(context: AccountContext, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messages: [EngineRawMessage], threadData: EngineMessageHistoryThreadData?, tapAction: @escaping () -> Bool, expandAction: @escaping (() -> (ASDisplayNode?, () -> Void)) -> Void) { self.context = context self.strings = strings self.dateTimeFormat = dateTimeFormat @@ -187,7 +186,7 @@ final class ChatMessageNotificationItemNode: NotificationItemNode { self.avatarNode.setPeer(context: item.context, theme: presentationData.theme, peer: avatarPeer, overrideImage: peer.id == item.context.account.peerId ? .savedMessagesIcon : nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor) } - var updatedMedia: Media? + var updatedMedia: EngineRawMedia? var imageDimensions: CGSize? var isRound = false var messageText: String diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD index d3f30ddc21..697ab7ecbb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index 5adf430902..b88a1e7ecd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import Display import ComponentFlow import ComponentDisplayAdapters -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD index 491db8f060..3e5dfa3db3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/BUILD @@ -36,6 +36,8 @@ swift_library( "//submodules/TelegramUI/Components/ChatControllerInteraction", "//submodules/RadialStatusNode", "//submodules/TelegramUI/Components/ComposePollScreen", + "//submodules/UndoUI", + "//submodules/TelegramStringFormatting", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift index 50a72cb79d..795171b893 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessagePollBubbleContentNode.swift @@ -29,6 +29,8 @@ import ComponentFlow import TextFieldComponent import PlainButtonComponent import LottieComponent +import UndoUI +import TelegramStringFormatting private final class ChatMessagePollOptionRadioNodeParameters: NSObject { let timestamp: Double @@ -483,6 +485,8 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { private let resultBarIconNode: ASImageNode private var mediaNode: TransformImageNode? private var mediaVideoIconNode: ASImageNode? + private var mediaWebpageIconNode: ASImageNode? + private var mediaWebpageOverlayNode: ASDisplayNode? private var stickerMediaLayer: InlineStickerItemLayer? private var mediaHidden = false private(set) var mediaFrame: CGRect? @@ -761,9 +765,73 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { let alpha: CGFloat = self.mediaHidden ? 0.0 : 1.0 self.mediaNode?.alpha = alpha self.mediaVideoIconNode?.alpha = alpha + self.mediaWebpageOverlayNode?.alpha = alpha + self.mediaWebpageIconNode?.alpha = alpha self.stickerMediaLayer?.opacity = Float(alpha) } + private func updateMediaWebpageOverlayNode(frame: CGRect) { + let mediaWebpageOverlayNode: ASDisplayNode + if let current = self.mediaWebpageOverlayNode { + mediaWebpageOverlayNode = current + } else { + let current = ASDisplayNode() + current.displaysAsynchronously = false + current.isUserInteractionEnabled = false + current.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) + current.cornerRadius = 10.0 + self.mediaWebpageOverlayNode = current + mediaWebpageOverlayNode = current + } + + if let mediaWebpageIconNode = self.mediaWebpageIconNode { + self.containerNode.insertSubnode(mediaWebpageOverlayNode, belowSubnode: mediaWebpageIconNode) + } else if let mediaNode = self.mediaNode { + self.containerNode.insertSubnode(mediaWebpageOverlayNode, aboveSubnode: mediaNode) + } else if mediaWebpageOverlayNode.supernode == nil { + self.containerNode.addSubnode(mediaWebpageOverlayNode) + } + mediaWebpageOverlayNode.frame = frame + mediaWebpageOverlayNode.alpha = self.mediaHidden ? 0.0 : 1.0 + } + + private func removeMediaWebpageOverlayNode() { + if let mediaWebpageOverlayNode = self.mediaWebpageOverlayNode { + mediaWebpageOverlayNode.removeFromSupernode() + self.mediaWebpageOverlayNode = nil + } + } + + private func updateMediaWebpageIconNode(frame: CGRect, tintColor: UIColor) { + let mediaWebpageIconNode: ASImageNode + if let current = self.mediaWebpageIconNode { + mediaWebpageIconNode = current + } else { + let current = ASImageNode() + current.displaysAsynchronously = false + self.mediaWebpageIconNode = current + mediaWebpageIconNode = current + } + + if let mediaWebpageOverlayNode = self.mediaWebpageOverlayNode { + self.containerNode.insertSubnode(mediaWebpageIconNode, aboveSubnode: mediaWebpageOverlayNode) + } else if mediaWebpageIconNode.supernode == nil { + self.containerNode.addSubnode(mediaWebpageIconNode) + } + mediaWebpageIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: tintColor) + let iconSize = mediaWebpageIconNode.image?.size ?? CGSize(width: 30.0, height: 30.0) + mediaWebpageIconNode.frame = CGRect(origin: CGPoint(x: frame.midX - iconSize.width * 0.5, y: frame.midY - iconSize.height * 0.5), size: iconSize) + mediaWebpageIconNode.isHidden = false + mediaWebpageIconNode.alpha = self.mediaHidden ? 0.0 : 1.0 + } + + private func removeMediaWebpageIconNode() { + if let mediaWebpageIconNode = self.mediaWebpageIconNode { + mediaWebpageIconNode.removeFromSupernode() + self.mediaWebpageIconNode = nil + } + } + func setMediaHidden(_ hidden: Bool) { if self.mediaHidden != hidden { self.mediaHidden = hidden @@ -852,9 +920,9 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { // } let rightInset: CGFloat = 10.0 + mediaInset - let recentVoterPeers: [Peer] + let recentVoterPeers: [EnginePeer] if let optionResult { - recentVoterPeers = optionResult.recentVoterPeerIds.compactMap { message.peers[$0] } + recentVoterPeers = optionResult.recentVoterPeerIds.compactMap { message.peers[$0] }.map(EnginePeer.init) } else { recentVoterPeers = [] } @@ -1164,6 +1232,8 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { stickerLayer.isVisibleForAnimations = true node.mediaNode?.removeFromSupernode() node.mediaNode = nil + node.removeMediaWebpageOverlayNode() + node.removeMediaWebpageIconNode() } else { if let stickerMediaLayer = node.stickerMediaLayer { stickerMediaLayer.removeFromSuperlayer() @@ -1187,11 +1257,14 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { mediaNode.frame = mediaFrame let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: media) + let mediaUpdated = previousMedia?.isEqual(to: media) != true var imageSize = ChatMessagePollOptionNode.mediaSize var isVideo = false + var mediaWebpageIconTintColor: UIColor? + var mediaWebpageHasImageThumbnail = false if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { imageSize = largest.dimensions.cgSize.aspectFilled(ChatMessagePollOptionNode.mediaSize) - if previousMedia?.isEqual(to: media) != true, let photoReference = mediaReference.concrete(TelegramMediaImage.self) { + if mediaUpdated, let photoReference = mediaReference.concrete(TelegramMediaImage.self) { mediaNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), photoReference: photoReference)) updatedFetchSignal = messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: largest.resource, storeToDownloadsPeerId: nil) } @@ -1199,7 +1272,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { if let dimensions = file.dimensions { imageSize = dimensions.cgSize.aspectFilled(ChatMessagePollOptionNode.mediaSize) } - if let fileReference = mediaReference.concrete(TelegramMediaFile.self), previousMedia?.isEqual(to: media) != true { + if let fileReference = mediaReference.concrete(TelegramMediaFile.self), mediaUpdated { if file.mimeType.hasPrefix("image/") { mediaNode.setSignal(instantPageImageFile(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, fetched: true)) } else { @@ -1207,8 +1280,53 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { } } isVideo = file.isVideo + } else if let webpage = media as? TelegramMediaWebpage { + let webpageReference = WebpageReference(webpage) + if case let .Loaded(content) = webpage.content, let image = content.image { + if let largest = largestImageRepresentation(image.representations) { + imageSize = largest.dimensions.cgSize.aspectFilled(ChatMessagePollOptionNode.mediaSize) + } + if mediaUpdated { + mediaNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), photoReference: .webPage(webPage: webpageReference, media: image))) + + if let smallest = smallestImageRepresentation(image.representations) { + updatedFetchSignal = fetchedMediaResource( + mediaBox: context.account.postbox.mediaBox, + userLocation: .peer(message.id.peerId), + userContentType: .image, + reference: .media(media: .webPage(webPage: webpageReference, media: image), resource: smallest.resource), + statsCategory: .image + ) + |> map { _ -> Void in + return Void() + } + |> `catch` { _ -> Signal in + return .complete() + } + } + } + mediaWebpageIconTintColor = .white + mediaWebpageHasImageThumbnail = true + } else { + let mediaAccentColor = incoming ? presentationData.theme.theme.chat.message.incoming.accentTextColor : presentationData.theme.theme.chat.message.outgoing.secondaryTextColor + if mediaUpdated || themeUpdated { + let backgroundColor = mediaAccentColor.withAlphaComponent(0.1) + mediaNode.setSignal(.single({ arguments in + let size = arguments.imageSize + let context = DrawingContext(size: size)! + context.withFlippedContext { context in + context.clear(CGRect(origin: .zero, size: size)) + context.setFillColor(backgroundColor.cgColor) + context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)) + context.fillPath() + } + return context + })) + } + mediaWebpageIconTintColor = mediaAccentColor + } } else if let map = media as? TelegramMediaMap { - if previousMedia?.isEqual(to: media) != true { + if mediaUpdated { let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(ChatMessagePollOptionNode.mediaSize.width), height: Int32(ChatMessagePollOptionNode.mediaSize.height)) mediaNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) } @@ -1224,6 +1342,18 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { )) apply() + if let mediaWebpageIconTintColor { + if mediaWebpageHasImageThumbnail { + node.updateMediaWebpageOverlayNode(frame: mediaFrame) + } else { + node.removeMediaWebpageOverlayNode() + } + node.updateMediaWebpageIconNode(frame: mediaFrame, tintColor: mediaWebpageIconTintColor) + } else { + node.removeMediaWebpageOverlayNode() + node.removeMediaWebpageIconNode() + } + if isVideo { let mediaVideoIconNode: ASImageNode if let current = node.mediaVideoIconNode { @@ -1245,6 +1375,8 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { } else if let mediaNode = node.mediaNode { mediaNode.removeFromSupernode() node.mediaNode = nil + node.removeMediaWebpageOverlayNode() + node.removeMediaWebpageIconNode() if let mediaVideoIconNode = node.mediaVideoIconNode { mediaVideoIconNode.removeFromSupernode() node.mediaVideoIconNode = nil @@ -1252,6 +1384,11 @@ private final class ChatMessagePollOptionNode: ASDisplayNode { } else if let mediaVideoIconNode = node.mediaVideoIconNode { mediaVideoIconNode.removeFromSupernode() node.mediaVideoIconNode = nil + node.removeMediaWebpageOverlayNode() + node.removeMediaWebpageIconNode() + } else { + node.removeMediaWebpageOverlayNode() + node.removeMediaWebpageIconNode() } node.setMediaHidden(node.mediaHidden) @@ -1384,7 +1521,10 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { private var animationLayer: InlineStickerItemLayer? private var statusNode: RadialStatusNode? private var videoIconView: UIImageView? + private var webpageOverlayView: UIView? + private var webpageIconView: UIImageView? private var appliedMedia: AnyMediaReference? + private var appliedWebpagePlaceholderColor: UIColor? private var currentFont: UIFont? private var currentTextColor: UIColor? @@ -1652,6 +1792,61 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { @objc private func attachButtonPressed() { self.attachPressed?() } + + private func updateWebpageIconView(frame: CGRect, tintColor: UIColor) { + let webpageIconView: UIImageView + if let current = self.webpageIconView { + webpageIconView = current + } else { + let current = UIImageView(image: UIImage(bundleImageName: "Chat/Context Menu/Link")?.withRenderingMode(.alwaysTemplate)) + current.isUserInteractionEnabled = false + self.view.addSubview(current) + self.webpageIconView = current + webpageIconView = current + } + + webpageIconView.tintColor = tintColor + let iconSize = webpageIconView.image?.size ?? CGSize(width: 30.0, height: 30.0) + webpageIconView.frame = CGRect(origin: CGPoint(x: frame.midX - iconSize.width * 0.5, y: frame.midY - iconSize.height * 0.5), size: iconSize) + webpageIconView.isHidden = false + self.view.bringSubviewToFront(webpageIconView) + } + + private func removeWebpageIconView() { + if let webpageIconView = self.webpageIconView { + self.webpageIconView = nil + webpageIconView.removeFromSuperview() + } + } + + private func updateWebpageOverlayView(frame: CGRect) { + let webpageOverlayView: UIView + if let current = self.webpageOverlayView { + webpageOverlayView = current + } else { + let current = UIView() + current.isUserInteractionEnabled = false + current.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) + current.layer.cornerRadius = 10.0 + current.clipsToBounds = true + self.view.addSubview(current) + self.webpageOverlayView = current + webpageOverlayView = current + } + + webpageOverlayView.frame = frame + webpageOverlayView.isHidden = false + if let webpageIconView = self.webpageIconView { + self.view.insertSubview(webpageOverlayView, belowSubview: webpageIconView) + } + } + + private func removeWebpageOverlayView() { + if let webpageOverlayView = self.webpageOverlayView { + self.webpageOverlayView = nil + webpageOverlayView.removeFromSuperview() + } + } private func updateModeSelectorLayout(size: CGSize, theme: PresentationTheme?, animated: Bool) { guard !size.width.isZero, !size.height.isZero, let theme = self.currentTheme else { @@ -1789,6 +1984,9 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { } self.imageButton.frame = imageNodeFrame self.imageButton.isHidden = false + self.removeWebpageOverlayView() + self.removeWebpageIconView() + self.appliedWebpagePlaceholderColor = nil } else if let animationLayer = self.animationLayer { self.animationLayer = nil animationLayer.removeFromSuperlayer() @@ -1815,11 +2013,14 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { } var isVideo = false + var webpageIconTintColor: UIColor? + var webpageHasImageThumbnail = false if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations), let photoReference = media.concrete(TelegramMediaImage.self) { imageSize = largest.dimensions.cgSize.aspectFilled(imageNodeSize) if updateMedia { imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: .other, photoReference: photoReference)) } + self.appliedWebpagePlaceholderColor = nil } else if let file = media.media as? TelegramMediaFile, let fileReference = media.concrete(TelegramMediaFile.self) { if let dimensions = file.dimensions { imageSize = dimensions.cgSize.aspectFilled(imageNodeSize) @@ -1834,12 +2035,50 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { } isVideo = true } + self.appliedWebpagePlaceholderColor = nil + } else if let webpage = media.media as? TelegramMediaWebpage { + let webpageReference = WebpageReference(webpage) + if case let .Loaded(content) = webpage.content, let image = content.image { + if let largest = largestImageRepresentation(image.representations) { + imageSize = largest.dimensions.cgSize.aspectFilled(imageNodeSize) + } + if updateMedia { + imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: .other, photoReference: .webPage(webPage: webpageReference, media: image))) + if let representation = smallestImageRepresentation(image.representations) { + let _ = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: .media(media: .webPage(webPage: webpageReference, media: image), resource: representation.resource)).startStandalone() + } + } + self.appliedWebpagePlaceholderColor = nil + webpageIconTintColor = .white + webpageHasImageThumbnail = true + } else { + let mediaAccentColor = self.currentIncoming ? theme.chat.message.incoming.accentTextColor : theme.chat.message.outgoing.secondaryTextColor + let backgroundColor = mediaAccentColor.withAlphaComponent(0.1) + if updateMedia || self.appliedWebpagePlaceholderColor?.isEqual(backgroundColor) != true { + self.appliedWebpagePlaceholderColor = backgroundColor + imageNode.setSignal(.single({ arguments in + let size = arguments.imageSize + let context = DrawingContext(size: size)! + context.withFlippedContext { context in + context.clear(CGRect(origin: .zero, size: size)) + context.setFillColor(backgroundColor.cgColor) + context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)) + context.fillPath() + } + return context + })) + } + webpageIconTintColor = mediaAccentColor + } } else if let map = media.media as? TelegramMediaMap { imageSize = imageNodeSize if updateMedia { let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height)) imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) } + self.appliedWebpagePlaceholderColor = nil + } else { + self.appliedWebpagePlaceholderColor = nil } let apply = imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 10.0), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: theme.list.mediaPlaceholderColor)) @@ -1864,10 +2103,23 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { statusNode.frame = imageNodeFrame.insetBy(dx: 4.0, dy: 4.0) statusNode.transitionToState(.progress(color: .white, lineWidth: 2.0, value: max(0.027, min(1.0, progress)), cancelEnabled: true, animateRotation: false)) isVideo = false + self.removeWebpageOverlayView() } else if let statusNode = self.statusNode { self.statusNode = nil statusNode.removeFromSupernode() } + + if attachment.progress == nil, let webpageIconTintColor { + if webpageHasImageThumbnail { + self.updateWebpageOverlayView(frame: imageNodeFrame) + } else { + self.removeWebpageOverlayView() + } + self.updateWebpageIconView(frame: imageNodeFrame, tintColor: webpageIconTintColor) + } else { + self.removeWebpageOverlayView() + self.removeWebpageIconView() + } if isVideo { let videoIconView: UIImageView @@ -1888,6 +2140,7 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { } } else { self.appliedMedia = nil + self.appliedWebpagePlaceholderColor = nil if let imageNode = self.imageNode { self.imageNode = nil imageNode.removeFromSupernode() @@ -1900,6 +2153,8 @@ private final class ChatMessagePollAddOptionNode: ASDisplayNode { self.videoIconView = nil videoIconView.removeFromSuperview() } + self.removeWebpageOverlayView() + self.removeWebpageIconView() self.imageButton.removeFromSupernode() } } @@ -2163,7 +2418,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { guard let item = self.item else { return } - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } private func updatePollAddOptionFocused(_ focus: Bool) { @@ -2239,7 +2494,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { context: item.context, updatedPresentationData: item.controllerInteraction.updatedPresentationData, subject: .option, - availableButtons: [.gallery, .sticker, .location], + availableButtons: [.gallery, .sticker, .location, .link], present: { [weak item] controller, _ in item?.controllerInteraction.navigationController()?.pushViewController(controller) }, @@ -2281,7 +2536,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { case let .result(result): if case let .media(resultMedia) = result { if let resultImage = resultMedia.media as? TelegramMediaImage, let resultLargest = largestImageRepresentation(resultImage.representations) { - item.context.account.postbox.mediaBox.moveResourceData(from: largest.resource.id, to: resultLargest.resource.id, synchronous: true) + item.context.engine.resources.moveResourceData(from: EngineMediaResource.Id(largest.resource.id), to: EngineMediaResource.Id(resultLargest.resource.id), synchronous: true) } media.media = resultMedia media.progress = nil @@ -2314,7 +2569,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { case let .result(result): if case let .media(resultMedia) = result { if let resultFile = resultMedia.media as? TelegramMediaFile { - item.context.account.postbox.mediaBox.moveResourceData(from: file.resource.id, to: resultFile.resource.id, synchronous: true) + item.context.engine.resources.moveResourceData(from: EngineMediaResource.Id(file.resource.id), to: EngineMediaResource.Id(resultFile.resource.id), synchronous: true) } media.media = resultMedia media.progress = nil @@ -2370,7 +2625,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { item.controllerInteraction.requestOpenMessagePollResults(item.message.id, pollId) case .anonymous: self.isPreviewingResults = !self.isPreviewingResults - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } } else if !selectedOpaqueIdentifiers.isEmpty { @@ -2412,21 +2667,21 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { return { item, layoutConstants, _, _, _, _ in let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) - + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in let message = item.message - + let incoming = item.message.effectivelyIncoming(item.context.account.peerId) var isBotChat: Bool = false if let peer = item.message.peers[item.message.id.peerId] as? TelegramUser, peer.botInfo != nil { isBotChat = true } - + let additionalTextRightInset: CGFloat = 24.0 - + let horizontalInset = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right let textConstrainedSize = CGSize(width: constrainedSize.width - horizontalInset - additionalTextRightInset, height: constrainedSize.height) - + var edited = false if item.attributes.updatingMedia != nil { edited = true @@ -2451,15 +2706,15 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { starsCount = attribute.stars.value } } - + let dateFormat: MessageTimestampStatusFormat if item.presentationData.isPreview { dateFormat = .full } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) - + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { statusType = nil @@ -2481,15 +2736,15 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { statusType = nil } } - + var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))? - + if let statusType = statusType { var isReplyThread = false if case .replyThread = item.chatLocation { isReplyThread = true } - + statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( context: item.context, presentationData: item.presentationData, @@ -2497,7 +2752,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), + layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -2511,12 +2766,12 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) } - + var poll: TelegramMediaPoll? for media in item.message.media { if let media = media as? TelegramMediaPoll { @@ -2524,13 +2779,13 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { break } } - + let messageTheme = incoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing - + var pollTitleText = poll?.text ?? "" var pollTitleEntities = poll?.textEntities ?? [] var pollOptions: [TranslationMessageAttribute.Additional] = [] - + var isTranslating = false if let poll, let translateToLanguage = item.associatedData.translateToLanguage, !poll.text.isEmpty && incoming { isTranslating = true @@ -2544,7 +2799,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } } - + let attributedText = stringWithAppliedEntities( pollTitleText, entities: pollTitleEntities, @@ -2559,22 +2814,22 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { blockQuoteFont: item.presentationData.messageBoldFont, message: message ) - + let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0) - + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let typeText: String - - var avatarPeers: [Peer] = [] + + var avatarPeers: [EnginePeer] = [] if let poll = poll { for peerId in poll.results.recentVoters { if let peer = item.message.peers[peerId] { - avatarPeers.append(peer) + avatarPeers.append(EnginePeer(peer)) } } } - - if let poll = poll, isPollEffectivelyClosed(message: message, poll: poll) { + + if let poll = poll, isPollEffectivelyClosed(message: EngineMessage(message), poll: poll) { typeText = item.presentationData.strings.MessagePoll_LabelClosed } else if let poll = poll { switch poll.kind { @@ -2597,9 +2852,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { typeText = item.presentationData.strings.MessagePoll_LabelAnonymous } let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - + let votersString: String? - + if isBotChat { votersString = nil } else if let poll = poll, let totalVoters = poll.results.totalVoters { @@ -2621,17 +2876,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { votersString = " " } let (votersLayout, votersApply) = makeVotersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: votersString ?? "", font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) - - var hasVoted = false - if let poll, let voters = poll.results.voters { - for voter in voters { - if voter.selected { - hasVoted = true - break - } - } - } - + let viewResultsString: String if let poll, let totalVoters = poll.results.totalVoters { if case .public = poll.publicity { @@ -2655,18 +2900,18 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let chevronImage = incoming ? PresentationResourcesChat.chatBubblePollChevronRightIncomingIcon(item.presentationData.theme.theme) : PresentationResourcesChat.chatBubblePollChevronRightOutgoingIcon(item.presentationData.theme.theme) viewResultsAttributedString.addAttribute(.attachment, value: chevronImage!, range: NSRange(range, in: viewResultsAttributedString.string)) } - + let (buttonSubmitInactiveTextLayout, buttonSubmitInactiveTextApply) = makeSubmitInactiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.accentControlDisabledColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let (buttonSubmitActiveTextLayout, buttonSubmitActiveTextApply) = makeSubmitActiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let (buttonSaveTextLayout, buttonSaveTextApply) = makeSaveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Common_Save, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: viewResultsAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) - + var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size) var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) - + textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top) - + var boundingSize: CGSize = textFrameWithoutInsets.size boundingSize.width += additionalTextRightInset boundingSize.width = max(boundingSize.width, typeLayout.size.width) @@ -2674,27 +2919,35 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { boundingSize.width = max(boundingSize.width, buttonSubmitInactiveTextLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/) boundingSize.width = max(boundingSize.width, buttonSaveTextLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/) boundingSize.width = max(boundingSize.width, buttonViewResultsTextLayout.size.width + 4.0/* + (statusSize?.width ?? 0.0)*/) - + if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0) } - + boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom - + let isClosed: Bool if let poll = poll { - isClosed = isPollEffectivelyClosed(message: message, poll: poll) + isClosed = isPollEffectivelyClosed(message: EngineMessage(message), poll: poll) } else { isClosed = false } - + var pollOptionsFinalizeLayouts: [(hasResult: Bool, layout: (CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))] = [] var addOptionFinalizeLayout: ((CGFloat) -> (CGSize, (Bool, Bool) -> ChatMessagePollAddOptionNode))? var orderedPollOptions: [(Int, TelegramMediaPollOption)] = [] + + var isRestricted = false if let poll = poll { + isRestricted = item.associatedData.isPollVotingRestricted( + poll: poll, + accountTestingEnvironment: item.context.account.testingEnvironment, + currentTimestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + ) + orderedPollOptions = resolvedOptionOrder(for: item) - + var optionVoterCount: [Int: Int32] = [:] var maxOptionVoterCount: Int32 = 0 var totalVoterCount: Int32 = 0 @@ -2714,12 +2967,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } totalVoterCount = totalVoters - if didVote || isClosed || isPreviewingResults { + if didVote || isClosed || isPreviewingResults || isRestricted { for i in 0 ..< poll.options.count { inner: for optionVoters in voters { if optionVoters.opaqueIdentifier == poll.options[i].opaqueIdentifier { optionVoterCount[i] = optionVoters.count - //TODO:correct maxOptionVoterCount = max(maxOptionVoterCount, optionVoters.count ?? 0) break inner } @@ -2727,16 +2979,16 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } } - + var optionVoterCounts: [Int] if totalVoterCount != 0 { optionVoterCounts = countNicePercent(votes: (0 ..< poll.options.count).map({ Int(optionVoterCount[$0] ?? 0) }), total: Int(totalVoterCount)) } else { optionVoterCounts = Array(repeating: 0, count: poll.options.count) } - + let hasAnyOptionMedia = orderedPollOptions.contains(where: { $0.1.media != nil }) - + for (i, option) in orderedPollOptions { let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ poll: TelegramMediaPoll, _ option: TelegramMediaPollOption, _ translation: TranslationMessageAttribute.Additional?, _ optionResult: ChatMessagePollOptionResult?, _ forceSelected: Bool?, _ hasAnyMedia: Bool, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode))) if let previous = previousOptionNodeLayouts[option.opaqueIdentifier] { @@ -2755,13 +3007,13 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if let count = optionVoterCount[i] { if maxOptionVoterCount != 0 && totalVoterCount != 0 { optionResult = ChatMessagePollOptionResult(normalized: CGFloat(count) / CGFloat(maxOptionVoterCount), percent: optionVoterCounts[i], count: count, recentVoterPeerIds: recentVoterPeerIds) - } else if isClosed { + } else if isClosed || isRestricted { optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0, recentVoterPeerIds: recentVoterPeerIds) } - } else if isClosed { + } else if isClosed || isRestricted { optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0, recentVoterPeerIds: recentVoterPeerIds) } - + var translation: TranslationMessageAttribute.Additional? if !pollOptions.isEmpty && i < pollOptions.count { translation = pollOptions[i] @@ -2771,12 +3023,12 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if !votedFor.isEmpty && optionResult == nil { forceSelected = votedFor.contains(option.opaqueIdentifier) } - + let result = makeLayout(item.context, item.presentationData, item.controllerInteraction.presentationContext, item.message, poll, option, translation, optionResult, forceSelected, hasAnyOptionMedia, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0) boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0) pollOptionsFinalizeLayouts.append((optionResult != nil, result.1)) } - + var maxPollOptions: Int = 20 if let data = item.context.currentAppConfiguration.with({ $0 }).data, let value = data["poll_answers_max"] as? Double { maxPollOptions = Int(value) @@ -2789,25 +3041,16 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { addOptionFinalizeLayout = addOptionResult.layout } } - - boundingSize.width = max(boundingSize.width, min(280.0, constrainedSize.width)) - - var canVote = false - if (item.message.id.namespace == Namespaces.Message.Cloud || Namespaces.Message.allNonRegular.contains(item.message.id.namespace)), let poll = poll, poll.pollId.namespace == Namespaces.Media.CloudPoll, !isClosed { - if !hasVoted { - canVote = true - } - } - let _ = canVote - + boundingSize.width = max(boundingSize.width, min(280.0, constrainedSize.width)) + return (boundingSize.width, { boundingWidth in var resultSize = CGSize(width: max(boundingSize.width, boundingWidth), height: boundingSize.height) - + let titleTypeSpacing: CGFloat = -4.0 let typeOptionsSpacing: CGFloat = 3.0 resultSize.height += titleTypeSpacing + typeLayout.size.height + typeOptionsSpacing - + var optionNodesSizesAndApply: [(CGSize, (Bool, Bool, Bool) -> ChatMessagePollOptionNode)] = [] for finalizeLayout in pollOptionsFinalizeLayouts { let result = finalizeLayout.layout(boundingWidth - layoutConstants.bubble.borderInset * 2.0) @@ -2826,7 +3069,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { resultSize.height += result.0.height addOptionSizeAndApply = result } - + let optionsVotersSpacing: CGFloat = 11.0 let optionsButtonSpacing: CGFloat = 9.0 let votersBottomSpacing: CGFloat = 11.0 @@ -2839,26 +3082,26 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { if let poll, case .poll = poll.kind, !poll.isClosed, let _ = poll.deadlineTimeout { resultSize.height += 6.0 } - + var statusSizeAndApply: (CGSize, (ListViewItemUpdateAnimation) -> Void)? if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { statusSizeAndApply = statusSuggestedWidthAndContinue.1(boundingWidth) } - + if let statusSizeAndApply = statusSizeAndApply { resultSize.height += statusSizeAndApply.0.height - 6.0 } - + let buttonSubmitInactiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitInactiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitInactiveTextLayout.size) let buttonSubmitActiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size) let buttonSaveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSaveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSaveTextLayout.size) let buttonViewResultsTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonViewResultsTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonViewResultsTextLayout.size) - + return (resultSize, { [weak self] animation, synchronousLoad, _ in if let strongSelf = self { strongSelf.item = item strongSelf.poll = poll - + let cachedLayout = strongSelf.textNode.textNode.cachedLayout if case .System = animation { if let cachedLayout = cachedLayout { @@ -2878,7 +3121,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } } - + let _ = textApply(TextNodeWithEntities.Arguments( context: item.context, cache: item.context.animationCache, @@ -2887,7 +3130,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { attemptSynchronous: synchronousLoad) ) let _ = typeApply() - + var verticalOffset = textFrame.maxY + titleTypeSpacing + typeLayout.size.height + typeOptionsSpacing var updatedOptionNodes: [ChatMessagePollOptionNode] = [] for i in 0 ..< optionNodesSizesAndApply.count { @@ -2905,17 +3148,25 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let option = optionNode.option optionNode.pressed = { [weak self] in - guard let self, let item = self.item, let option else { + guard let self, + let item = self.item, + let option else { return } item.controllerInteraction.requestSelectMessagePollOptions(item.message.id, [option.opaqueIdentifier]) } optionNode.resultPressed = { [weak self] in - guard let self, let item = self.item, let option else { + guard let self, + let item = self.item, + let option else { return } - if let poll, case .public = poll.publicity { - item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier) + if let poll { + if case .public = poll.publicity { + item.controllerInteraction.openMessagePollResults(item.message.id, option.opaqueIdentifier) + } else if isRestricted { + item.controllerInteraction.displayPollRestrictedToast(item.message.id) + } } } optionNode.selectionUpdated = { [weak self] in @@ -2925,7 +3176,9 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { self.updateSelection() } optionNode.longTapped = { [weak self] in - guard let self, let item = self.item, let option else { + guard let self, + let item = self.item, + let option else { return } item.controllerInteraction.pollOptionLongTap(option.opaqueIdentifier, ChatControllerInteraction.LongTapParams(message: item.message, contentNode: optionNode.contextSourceNode, messageNode: strongSelf, progress: nil)) @@ -2934,7 +3187,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else { animation.animator.updateFrame(layer: optionNode.layer, frame: optionNodeFrame, completion: nil) } - + if optionNode.currentResult != nil { verticalOffset += size.height - 7.0 } else { @@ -2943,7 +3196,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { updatedOptionNodes.append(optionNode) optionNode.isUserInteractionEnabled = !strongSelf.newOptionIsFocused && item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] == nil optionNode.alpha = strongSelf.newOptionIsFocused ? 0.5 : 1.0 - + if i > 0 { optionNode.previousOptionNode = updatedOptionNodes[i - 1] } else { @@ -2957,7 +3210,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } strongSelf.optionNodes = updatedOptionNodes strongSelf.updatePollOptionsInteraction(animated: animation.isAnimated) - + if let (size, apply) = addOptionSizeAndApply { let isRequesting = item.controllerInteraction.pollActionState.pollMessageIdsInProgress[item.message.id] != nil let addOptionNode = apply(animation.isAnimated, isRequesting) @@ -3000,11 +3253,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.addOptionNode = nil addOptionNode.removeFromSupernode() } - + if let poll = poll, let pendingNewOptionSubmissionText, let pendingNewOptionOptionCount = strongSelf.pendingNewOptionOptionCount, poll.options.count > pendingNewOptionOptionCount, poll.options.contains(where: { $0.text == pendingNewOptionSubmissionText }) { strongSelf.clearNewOptionInput() } - + if textLayout.hasRTL { strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: resultSize.width - textFrame.size.width - textInsets.left - layoutConstants.text.bubbleInsets.right - additionalTextRightInset, y: textFrame.origin.y), size: textFrame.size) } else { @@ -3012,11 +3265,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } let typeFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: textFrame.maxY + titleTypeSpacing), size: typeLayout.size) animation.animator.updateFrame(layer: strongSelf.typeNode.layer, frame: typeFrame, completion: nil) - + let deadlineTimeout = poll?.deadlineTimeout var displayDeadlineTimer = true var hasSelected = false - + if let poll { if let voters = poll.results.voters { for voter in voters { @@ -3034,7 +3287,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } var endDate: Int32? - if let deadlineTimeout, message.id.namespace == Namespaces.Message.Cloud { + if let deadlineTimeout, + message.id.namespace == Namespaces.Message.Cloud { let startDate: Int32 if let forwardInfo = message.forwardInfo { startDate = forwardInfo.date @@ -3064,12 +3318,13 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.timerNode = timerNode strongSelf.addSubnode(timerNode) timerNode.reachedTimeout = { - guard let strongSelf = self, let _ = strongSelf.item else { + guard let strongSelf = self, + let _ = strongSelf.item else { return } //item.controllerInteraction.requestMessageUpdate(item.message.id) } - + let timerTransition: ContainedViewLayoutTransition if animation.isAnimated { timerTransition = .animated(duration: 0.25, curve: .easeInOut) @@ -3087,7 +3342,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { timerNode.frame = CGRect(origin: CGPoint(x: resultSize.width - layoutConstants.text.bubbleInsets.right, y: typeFrame.minY), size: CGSize()) } else if let timerNode = strongSelf.timerNode { strongSelf.timerNode = nil - + let timerTransition: ContainedViewLayoutTransition if animation.isAnimated { timerTransition = .animated(duration: 0.25, curve: .easeInOut) @@ -3120,7 +3375,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { timerNode = DeadlineTimerNode() strongSelf.deadlineTimerNode = timerNode strongSelf.addSubnode(timerNode) - + let timerTransition: ContainedViewLayoutTransition if animation.isAnimated { timerTransition = .animated(duration: 0.25, curve: .easeInOut) @@ -3139,7 +3394,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { statusOffset += 6.0 } else if let timerNode = strongSelf.deadlineTimerNode { strongSelf.deadlineTimerNode = nil - + let timerTransition: ContainedViewLayoutTransition if animation.isAnimated { timerTransition = .animated(duration: 0.25, curve: .easeInOut) @@ -3151,11 +3406,11 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { }) timerTransition.updateTransformScale(node: timerNode, scale: 0.1) } - + let solutionButtonSize = CGSize(width: 32.0, height: 32.0) let solutionButtonFrame = CGRect(origin: CGPoint(x: resultSize.width - layoutConstants.text.bubbleInsets.right - solutionButtonSize.width + 5.0, y: typeFrame.minY - 16.0), size: solutionButtonSize) strongSelf.solutionButtonNode.frame = solutionButtonFrame - + if (strongSelf.timerNode == nil || !displayDeadlineTimer), let poll = poll, case .quiz = poll.kind, let _ = poll.results.solution, (isClosed || hasSelected) { if strongSelf.solutionButtonNode.alpha.isZero { let timerTransition: ContainedViewLayoutTransition @@ -3176,7 +3431,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } timerTransition.updateAlpha(node: strongSelf.solutionButtonNode, alpha: 0.0) } - + let avatarsFrame = CGRect(origin: CGPoint(x: typeFrame.maxX + 6.0, y: typeFrame.minY + floor((typeFrame.height - MergedAvatarsNode.defaultMergedImageSize) / 2.0)), size: CGSize(width: MergedAvatarsNode.defaultMergedImageSize + MergedAvatarsNode.defaultMergedImageSpacing * 2.0, height: MergedAvatarsNode.defaultMergedImageSize)) strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) @@ -3189,20 +3444,20 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else { alphaTransition = .immediate } - + let _ = votersApply() let votersFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - votersLayout.size.width) / 2.0), y: verticalOffset + optionsVotersSpacing), size: votersLayout.size) animation.animator.updateFrame(layer: strongSelf.votersNode.layer, frame: votersFrame, completion: nil) - + if animation.isAnimated, let previousPoll = previousPoll, let poll = poll { if previousPoll.results.totalVoters == nil && poll.results.totalVoters != nil { strongSelf.votersNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } } - + if let statusSizeAndApply = statusSizeAndApply { let statusFrame = CGRect(origin: CGPoint(x: resultSize.width - statusSizeAndApply.0.width - layoutConstants.text.bubbleInsets.right, y: votersFrame.maxY + statusOffset), size: statusSizeAndApply.0) - + if strongSelf.statusNode.supernode == nil { statusSizeAndApply.1(.None) strongSelf.statusNode.frame = statusFrame @@ -3214,22 +3469,22 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else if strongSelf.statusNode.supernode != nil { strongSelf.statusNode.removeFromSupernode() } - + let _ = buttonSubmitInactiveTextApply() strongSelf.buttonSubmitInactiveTextNode.frame = buttonSubmitInactiveTextFrame.offsetBy(dx: 0.0, dy: verticalOffset) - + let _ = buttonSubmitActiveTextApply() strongSelf.buttonSubmitActiveTextNode.frame = buttonSubmitActiveTextFrame.offsetBy(dx: 0.0, dy: verticalOffset) - + let _ = buttonSaveTextApply() strongSelf.buttonSaveTextNode.frame = buttonSaveTextFrame.offsetBy(dx: 0.0, dy: verticalOffset) - + let _ = buttonViewResultsTextApply() strongSelf.buttonViewResultsTextNode.frame = buttonViewResultsTextFrame.offsetBy(dx: 0.0, dy: verticalOffset) - + strongSelf.updateSelection() strongSelf.updatePollTooltipMessageState(animated: false) - + let buttonWidth: CGFloat = floor(max(strongSelf.buttonSaveTextNode.frame.width, max(strongSelf.buttonViewResultsTextNode.frame.width, strongSelf.buttonSubmitActiveTextNode.frame.width)) * 1.1) strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonWidth) / 2.0), y: verticalOffset), size: CGSize(width: buttonWidth, height: 44.0)) @@ -3322,7 +3577,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } } - let isClosed = isPollEffectivelyClosed(message: item.message, poll: poll) + let isClosed = isPollEffectivelyClosed(message: EngineMessage(item.message), poll: poll) let canAlwaysViewResults = !poll.hideResultsUntilClose && poll.isCreator var hasAnyVotes = false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessageQuizAnswerBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessageQuizAnswerBubbleContentNode.swift index 72d3794fbb..1573119fe6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessageQuizAnswerBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePollBubbleContentNode/Sources/ChatMessageQuizAnswerBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -48,7 +47,7 @@ public final class ChatMessageQuizAnswerBubbleContentNode: ChatMessageBubbleCont let title: String = item.presentationData.strings.MessagePoll_Explanation var text: String = "" var entities: [MessageTextEntity] = [] - var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? = nil + var mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? = nil var solution: TelegramMediaPollResults.Solution? if let poll = item.message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll, let solutionValue = poll.results.solution { text = solutionValue.text @@ -124,11 +123,11 @@ public final class ChatMessageQuizAnswerBubbleContentNode: ChatMessageBubbleCont self.contentNode.updateTouchesAtPoint(point.flatMap { $0.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY) }) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { return self.contentNode.updateHiddenMedia(media) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD index afa1e07539..cdec64f769 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index 11008ec607..04a68ef4f5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -95,7 +94,7 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont self.fetchDisposable.dispose() } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -110,9 +109,9 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont } } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false - var currentMedia: Media? + var currentMedia: EngineRawMedia? if let item = item { mediaLoop: for media in item.message.media { if let media = media as? TelegramMediaAction { @@ -218,7 +217,7 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont } if let photo = photo, let video = photo.videoRepresentations.last, let id = photo.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD index 5b4f65efeb..e4708fee31 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift index 12db046140..194c2467b9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -62,7 +61,7 @@ public final class MessageReactionButtonsNode: ASDisplayNode { savedMessageTags: SavedMessageTags?, reactions: ReactionsMessageAttribute, accountPeer: EnginePeer?, - message: Message, + message: EngineMessage, associatedData: ChatMessageItemAssociatedData, alignment: DisplayAlignment, constrainedWidth: CGFloat, @@ -632,7 +631,7 @@ public final class ChatMessageReactionsFooterContentNode: ChatMessageBubbleConte context: item.context, presentationData: item.presentationData, presentationContext: item.controllerInteraction.presentationContext, - availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactionsAttribute, accountPeer: item.associatedData.accountPeer, message: item.message, associatedData: item.associatedData, alignment: .left, constrainedWidth: constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing) + availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactionsAttribute, accountPeer: item.associatedData.accountPeer, message: EngineMessage(item.message), associatedData: item.associatedData, alignment: .left, constrainedWidth: constrainedSize.width - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, type: item.message.effectivelyIncoming(item.context.account.peerId) ? .incoming : .outgoing) return (layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right + buttonsUpdate.proposedWidth, { boundingWidth in var boundingSize = CGSize() @@ -712,13 +711,13 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { public let availableReactions: AvailableReactions? public let savedMessageTags: SavedMessageTags? public let reactions: ReactionsMessageAttribute - public let message: Message + public let message: EngineMessage public let associatedData: ChatMessageItemAssociatedData public let accountPeer: EnginePeer? public let isIncoming: Bool public let constrainedWidth: CGFloat public let centerAligned: Bool - + public init( context: AccountContext, presentationData: ChatPresentationData, @@ -726,7 +725,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode { availableReactions: AvailableReactions?, savedMessageTags: SavedMessageTags?, reactions: ReactionsMessageAttribute, - message: Message, + message: EngineMessage, associatedData: ChatMessageItemAssociatedData, accountPeer: EnginePeer?, isIncoming: Bool, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD index 9c5a9df3aa..7acafa371c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift index 8cf534d057..4aef7b4f1e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReplyInfoNode/Sources/ChatMessageReplyInfoNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import TelegramCore import SwiftSignalKit @@ -80,13 +79,13 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { public let strings: PresentationStrings public let context: AccountContext public let type: ChatMessageReplyInfoType - public let message: Message? + public let message: EngineRawMessage? public let replyForward: QuotedReplyMessageAttribute? public let quote: (quote: EngineMessageReplyQuote, isQuote: Bool)? public let innerSubject: EngineMessageReplyInnerSubject? - public let story: StoryId? + public let story: EngineStoryId? public let isSummarized: Bool - public let parentMessage: Message + public let parentMessage: EngineRawMessage public let constrainedSize: CGSize public let animationCache: AnimationCache? public let animationRenderer: MultiAnimationRenderer? @@ -97,13 +96,13 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { strings: PresentationStrings, context: AccountContext, type: ChatMessageReplyInfoType, - message: Message?, + message: EngineRawMessage?, replyForward: QuotedReplyMessageAttribute?, quote: (quote: EngineMessageReplyQuote, isQuote: Bool)?, innerSubject: EngineMessageReplyInnerSubject?, - story: StoryId?, + story: EngineStoryId?, isSummarized: Bool, - parentMessage: Message, + parentMessage: EngineRawMessage, constrainedSize: CGSize, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?, @@ -222,7 +221,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if let peer = forwardInfo.author { author = peer } else if let authorSignature = forwardInfo.authorSignature { - author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + author = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.Empty, id: EnginePeer.Id.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) } } @@ -430,8 +429,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } let textColor: UIColor - let iconColor: UIColor - switch arguments.type { case let .bubble(incoming): if isExpiredStory || isStory { @@ -441,10 +438,8 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { } else { textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor } - iconColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor case .standalone: textColor = titleColor - iconColor = titleColor } var textLeftInset: CGFloat = 0.0 @@ -872,6 +867,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 1.0, y: textFrame.minY + 3.0 + UIScreenPixel), size: imageSize) } } + expiredStoryIconView.tintColor = titleColor } else if let expiredStoryIconView = node.expiredStoryIconView { expiredStoryIconView.removeFromSuperview() } @@ -906,7 +902,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { pattern = MessageInlineBlockBackgroundView.Pattern( context: arguments.context, fileId: backgroundEmojiId, - file: arguments.parentMessage.associatedMedia[MediaId( + file: arguments.parentMessage.associatedMedia[EngineMedia.Id( namespace: Namespaces.Media.CloudFile, id: backgroundEmojiId )] as? TelegramMediaFile, @@ -951,7 +947,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode { if let todoItemCompleted { let checkLayerFrame = CGRect(origin: CGPoint(x: textFrame.minX - 16.0, y: textFrame.minY + 5.0), size: CGSize(width: 12.0, height: 12.0)) - let checkTheme = CheckNodeTheme(backgroundColor: iconColor, strokeColor: .clear, borderColor: iconColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) + let checkTheme = CheckNodeTheme(backgroundColor: titleColor, strokeColor: .clear, borderColor: titleColor, overlayBorder: false, hasInset: true, hasShadow: false, borderWidth: 1.0) let checkLayer: CheckLayer if let current = node.checkLayer { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD index a8bc3b4d00..84c6c61e86 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift index 5203e55540..f7c9e0a48b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRestrictedBubbleContentNode/Sources/ChatMessageRestrictedBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -77,7 +76,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod } } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -133,7 +132,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), + layoutInput: .trailingContent(contentWidth: textLayout.trailingLineWidth, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: EngineMessage(message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -147,7 +146,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/BUILD new file mode 100644 index 0000000000..d9cab4e388 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMessageRichDataBubbleContentNode", + module_name = "ChatMessageRichDataBubbleContentNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/InstantPageUI", + "//submodules/TextFormat:TextFormat", + "//submodules/TelegramUI/Components/Chat/ChatMessageBubbleContentNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode", + "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/TelegramUI/Components/TextLoadingEffect", + "//submodules/TelegramUIPreferences", + "//submodules/TextSelectionNode", + "//submodules/TelegramUI/Components/StreamingTextReveal:StreamingTextReveal", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift new file mode 100644 index 0000000000..74839546bd --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift @@ -0,0 +1,1554 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramCore +import SwiftSignalKit +import AccountContext +import ChatMessageBubbleContentNode +import ChatMessageDateAndStatusNode +import ChatMessageItemCommon +import ChatControllerInteraction +import InstantPageUI +import TextFormat +import TelegramUIPreferences +import TextLoadingEffect +import TextSelectionNode +import StreamingTextReveal + +public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode { + public final class ContainerNode: ASDisplayNode { + } + + private let containerNode: ContainerNode + public var statusNode: ChatMessageDateAndStatusNode? + // `init()` may run off the main thread; UIView construction must happen on the main thread. + // The page view is built lazily inside the apply closure (always main-thread) via ensurePageView(). + private var pageView: InstantPageV2View? + // Tracks the message (id + stableVersion) baked into the current pageView's render context. + // The synthesized webpage uses a sentinel id (namespace 0, id 0) shared across all richText + // messages, so we key cache invalidation on the message itself. When the bubble is recycled + // with a different message we must discard pageView (render context is constructor-fixed). + private var pageViewMessageKey: (id: EngineMessage.Id, stableVersion: UInt32, showMoreExpanded: Bool)? + // messageStableVersion is in the cache key because the synthesized instantPage content + // mutates between streamed AI message chunks (each chunk bumps stableVersion); without + // this, the cached layout would shadow newly-arrived content during streaming. + private var currentPageLayout: (boundingWidth: CGFloat, + presentationThemeIdentity: ObjectIdentifier, + expandedDetails: [Int: Bool], + messageStableVersion: UInt32, + showMoreExpanded: Bool, + layout: InstantPageV2Layout)? + private var currentExpandedDetails: [Int: Bool] = [:] + // Intra-message anchor scroll that is waiting on a collapsed
to expand + relayout. + private var pendingScrollAnchor: String? + // Progress guard: the details index expanded on the previous pending pass. + private var lastExpandedPendingDetailsIndex: Int? + private var linkProgressDisposable: Disposable? + private var linkProgressRects: [CGRect]? + private var linkHighlightingNode: LinkHighlightingNode? + private var linkProgressView: TextLoadingEffectView? + private var textSelectionAdapter: InstantPageMultiTextAdapter? + private var textSelectionNode: TextSelectionNode? + + private var textRevealController: TextRevealController? + private var textRevealLink: SharedDisplayLinkDriver.Link? + private var currentRevealCostMap: InstantPageV2RevealCostMap? + // Cursor value pushed into pageView.applyReveal on the prior tick. The display-link tick + // compares the revealed prefix's height at this cursor vs the new cursor to decide when + // to request a full bubble re-layout (so the bubble grows with the reveal). + private var lastAppliedRevealedCount: Int = 0 + private var displayContentsUnderSpoilers: Bool = false + private var relativeDateTimer: (timer: SwiftSignalKit.Timer, period: Int32)? + + // "Show more" affordance for partial rich messages (instantPage.isComplete == false). + // Managed inline, mirroring the statusNode pattern: a bubble-owned TextNode below the page + // content, with a TextLoadingEffectView shimmer while the full-text request is in flight. + private var showMoreTextNode: TextNode? + private var showMoreLoadingView: TextLoadingEffectView? + private var requestFullRichTextDisposable: Disposable? + private var requestFullRichTextMessageId: EngineMessage.Id? + // Transient per-message expand state. The full page is shown only after the user taps "Show + // more"; tagging it with the message id means any other message starts collapsed (partial) + // every time, even if its attribute already carries a cached fullInstantPage. + private var showMoreExpanded: (messageId: EngineMessage.Id, value: Bool)? + // The expand state actually applied on the previous layout pass, used to detect the + // collapse→expand transition so the bubble can grow downward in screen space (see the + // setInvertOffsetDirection call in the apply closure). nil until the first apply. + private var appliedShowMoreExpanded: Bool? + + override public var visibility: ListViewItemNodeVisibility { + didSet { + if oldValue != self.visibility { + self.updatePageViewVisibilityRect() + } + } + } + + // Pushes the current `visibility` sub-rect into `pageView.visibilityRect`, translated into the + // page view's coordinate space (the page view sits at the top of the bubble; no header offset). + // Re-invoked from the apply closure after `pageView.frame` is set, because the pageView's + // y-origin and size can change across streamed chunks (content growth) without a `visibility` + // change, which would otherwise leave the animation-gating rect stale. + private func updatePageViewVisibilityRect() { + guard let pageView = self.pageView else { + return + } + switch self.visibility { + case .none: + pageView.visibilityRect = nil + case let .visible(_, subRect): + var rect = subRect + rect.origin.x = 0.0 + rect.size.width = 10000.0 + rect.origin.y -= pageView.frame.minY + pageView.visibilityRect = rect + } + } + + required public init() { + self.containerNode = ContainerNode() + self.containerNode.clipsToBounds = true + + super.init() + + self.addSubnode(self.containerNode) + } + + /// Builds (or reuses) the V2View. Same-message stableVersion bumps (streamed AI chunks) reuse + /// the existing view, updating only the webpage content in place. The view is rebuilt only when + /// the bubble is recycled with a different message/webpage (different message id). + private func ensurePageView(item: ChatMessageBubbleContentItem, webpage: TelegramMediaWebpage, showMoreExpanded: Bool) -> InstantPageV2View { + let key = (id: item.message.id, stableVersion: item.message.stableVersion, showMoreExpanded: showMoreExpanded) + if let existing = self.pageView, let current = self.pageViewMessageKey, current.id == key.id { + if current.stableVersion == key.stableVersion && current.showMoreExpanded == key.showMoreExpanded { + return existing + } + // Same message, new chunk: reuse the view. Update only the content-bearing webpage on + // the existing render context; the subsequent pageView.update(layout:) call diffs item + // views by stable id (content blocks keep their ids, so their views and in-flight + // reveal state persist; only added/removed blocks change). This replaces the old + // wholesale rebuild and eliminates the per-chunk full-text-then-mask flash. + existing.renderContext?.updateContent(webpage: webpage) + self.pageViewMessageKey = key + return existing + } + self.pageView?.removeFromSuperview() + self.pageView = nil + + // Capture only the MessageReference (value type) — the closures are retained on the + // render context which is owned by the V2View, so we must avoid making them retain + // the bubble (`self`) or the message indirectly via `item`. + let messageReference = MessageReference(item.message) + let renderContext = InstantPageV2RenderContext( + context: item.context, + webpage: webpage, + sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel), + imageReference: { image in + return ImageMediaReference.message(message: messageReference, media: image) + }, + fileReference: { file in + return FileMediaReference.message(message: messageReference, media: file) + }, + present: { [weak self] controller, args in + self?.item?.controllerInteraction.presentController(controller, args) + }, + push: { [weak self] controller in + self?.item?.controllerInteraction.navigationController()?.pushViewController(controller) + }, + openUrl: { [weak self] urlItem in + self?.openInstantPageUrl(urlItem) + }, + baseNavigationController: { [weak self] in + self?.item?.controllerInteraction.navigationController() + }, + message: messageReference + ) + let view = InstantPageV2View(renderContext: renderContext) + self.pageView = view + self.pageViewMessageKey = key + self.containerNode.view.addSubview(view) + view.detailsTapped = { [weak self] index in + guard let self else { return } + let current = self.currentExpandedDetails[index] ?? self.defaultExpanded(forDetailsIndex: index) + self.currentExpandedDetails[index] = !current + if let item = self.item { + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) + } + } + return view + } + + private func defaultExpanded(forDetailsIndex index: Int) -> Bool { + guard let layout = self.currentPageLayout?.layout else { return false } + func search(_ items: [InstantPageV2LaidOutItem]) -> Bool? { + for item in items { + if case let .details(d) = item { + if d.index == index { + return d.defaultExpanded + } + // Recurse into an expanded parent's body so NESTED details indices resolve too; + // the flat top-level scan missed them, leaving the toggle's "current state" + // computation wrong for a nested details whose model default is expanded. + if let inner = d.innerLayout, let found = search(inner.items) { + return found + } + } + } + return nil + } + return search(layout.items) ?? false + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.linkProgressDisposable?.dispose() + self.relativeDateTimer?.timer.invalidate() + self.requestFullRichTextDisposable?.dispose() + } + + override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + let previousItem = self.item + let currentPageLayout = self.currentPageLayout + let currentExpandedDetails = self.currentExpandedDetails + let showMoreExpandedState = self.showMoreExpanded + let statusLayout = ChatMessageDateAndStatusNode.asyncLayout(self.statusNode) + let showMoreTextLayout = TextNode.asyncLayout(self.showMoreTextNode) + // Captured at main-thread, top of asyncLayoutContent. Mirrors TextBubble's + // `currentMaxGlyphCount` (TextBubble:313). The bubble's bounding size is sized + // to this revealed prefix during streaming, so it grows with the reveal rather + // than being final-sized from the first chunk. + let currentMaxGlyphCount: Int? = self.textRevealController?.currentGlyphCount + + return { [weak self] item, layoutConstants, _, _, _, _ in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let suggestedBoundingWidth: CGFloat = constrainedSize.width + + var boundingSize = CGSize(width: suggestedBoundingWidth, height: 0.0) + + var pageLayout: InstantPageV2Layout? + // Built alongside pageLayout so the apply closure can hand it to ensurePageView. + var pageWebpage: TelegramMediaWebpage? + + // Horizontal text inset baked into the InstantPage layout. The pageView sits at + // self-x 0 (containerNode at 1, pageView at -1 inside it), so the page's text + // left edge in the status node's coordinate space is exactly this value. Used as + // the status node's left edge + side inset, mirroring TextBubble's bubbleInsets. + let pageHorizontalInset: CGFloat = 11.0 + + let isDark = item.presentationData.theme.theme.overallDarkAppearance + let isIncoming = item.message.effectivelyIncoming(item.context.account.peerId) + let messageTheme = isIncoming ? item.presentationData.theme.theme.chat.message.incoming : item.presentationData.theme.theme.chat.message.outgoing + + var underlineLinks = true + if !messageTheme.primaryTextColor.isEqual(messageTheme.linkTextColor) { + underlineLinks = false + } + let _ = underlineLinks + + let author = item.message.author + let mainColor: UIColor + var secondaryColor: UIColor? = nil + var tertiaryColor: UIColor? = nil + + let nameColors: PeerNameColors.Colors? + switch author?.nameColor { + case let .preset(nameColor): + nameColors = item.context.peerNameColors.get(nameColor, dark: item.presentationData.theme.theme.overallDarkAppearance) + case let .collectible(collectibleColor): + nameColors = collectibleColor.peerNameColors(dark: item.presentationData.theme.theme.overallDarkAppearance) + default: + nameColors = nil + } + + let codeBlockBackgroundColor: UIColor + let codeBlockTitleColor: UIColor + let codeBlockAccentColor: UIColor + if !isIncoming { + mainColor = messageTheme.accentTextColor + if let _ = nameColors?.secondary { + secondaryColor = .clear + } + if let _ = nameColors?.tertiary { + tertiaryColor = .clear + } + + if item.presentationData.theme.theme.overallDarkAppearance { + codeBlockTitleColor = .white + codeBlockAccentColor = UIColor(white: 1.0, alpha: 0.5) + } else { + codeBlockTitleColor = mainColor + codeBlockAccentColor = mainColor + } + + codeBlockBackgroundColor = mainColor.withMultipliedAlpha(0.1) + } else { + let authorNameColor = nameColors?.main + secondaryColor = nameColors?.secondary + tertiaryColor = nameColors?.tertiary + + if let authorNameColor { + mainColor = authorNameColor + } else { + mainColor = messageTheme.accentTextColor + } + + codeBlockTitleColor = mainColor + codeBlockAccentColor = mainColor + + codeBlockBackgroundColor = mainColor.withMultipliedAlpha(0.1) + } + + let _ = secondaryColor + let _ = tertiaryColor + + let _ = codeBlockTitleColor + let _ = codeBlockAccentColor + + let textCategories = InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: messageTheme.secondaryTextColor), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: messageTheme.secondaryTextColor), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + ) + let pageTheme = InstantPageTheme( + type: isDark ? .dark : .light, + pageBackgroundColor: .clear, + textCategories: textCategories, + serif: false, + codeBlockBackgroundColor: codeBlockBackgroundColor, + linkColor: messageTheme.linkTextColor, + textHighlightColor: messageTheme.accentTextColor.withMultipliedAlpha(0.1), + linkHighlightColor: messageTheme.linkTextColor.withMultipliedAlpha(0.1), + markerColor: UIColor(rgb: 0xfef3bc), + panelBackgroundColor: messageTheme.accentControlColor.withMultipliedAlpha(0.1), + panelHighlightedBackgroundColor: messageTheme.accentControlColor.withMultipliedAlpha(0.25), + panelPrimaryColor: messageTheme.primaryTextColor, + panelSecondaryColor: messageTheme.secondaryTextColor, + panelAccentColor: messageTheme.accentTextColor, + tableBorderColor: isDark || !isIncoming ? messageTheme.accentControlColor.withMultipliedAlpha(0.25) : UIColor(white: 0.0, alpha: 0.1), + tableHeaderColor: isDark || !isIncoming ? messageTheme.accentControlColor.withMultipliedAlpha(0.1) : UIColor(white: 0.0, alpha: 0.05), + controlColor: messageTheme.accentControlColor, + imageTintColor: nil, + overlayPanelColor: isDark ? UIColor(white: 0.0, alpha: 0.13) : UIColor(white: 1.0, alpha: 0.13), + separatorColor: messageTheme.secondaryTextColor.mixedWith(mainColor.withMultipliedAlpha(0.2), alpha: 0.3), + secondaryControlColor: messageTheme.secondaryTextColor.mixedWith(mainColor.withMultipliedAlpha(0.2), alpha: 0.3) + ) + + var hasDraft = false + if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + hasDraft = true + } + var hadDraft = false + if let previousItem, previousItem.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + hadDraft = true + } + + // Resolve the node-local expand state for THIS message (collapsed for any other). + let showMoreExpanded = (showMoreExpandedState?.messageId == item.message.id) ? (showMoreExpandedState?.value ?? false) : false + + if let attribute = item.message.richText { + #if DEBUG && false + let instantPage = InstantPage(blocks: [.thinking(.concat([ + .textCustomEmoji(fileId: 5384559872899555845, alt: "a"), + .plain("Thinking...") + ]))], media: [:], isComplete: true, rtl: false, url: "", views: nil) + #else + // Show the full page only while expanded (after a "Show more" tap); otherwise the partial. + let instantPage = (showMoreExpanded ? attribute.fullInstantPage : nil) ?? attribute.instantPage + #endif + + let webpage = TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent( + url: "", + displayUrl: "", + hash: 0, + type: nil, + websiteName: nil, + title: nil, + text: nil, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: instantPage + ))) + pageWebpage = webpage + + let presentationThemeIdentity = ObjectIdentifier(item.presentationData.theme.theme) + let currentMessageStableVersion = item.message.stableVersion + if let current = currentPageLayout, + current.boundingWidth == suggestedBoundingWidth, + current.presentationThemeIdentity == presentationThemeIdentity, + current.expandedDetails == currentExpandedDetails, + current.showMoreExpanded == showMoreExpanded, + current.messageStableVersion == currentMessageStableVersion, + current.layout.formattedDateUpdatePeriod == nil { + // Reuse the cached layout only when it has no relative `textDate`. A relative + // date's formatted string ("N minutes ago") is baked into the laid-out text at + // layout time, and none of the cache-key inputs change as wall-clock advances — + // so reusing it would freeze the date and defeat the refresh timer (which fires + // `requestFullUpdate` precisely to re-run `layoutInstantPageV2` → `formatDate`). + // Forcing a recompute for relative-date pages keeps the timer's tick visible. + pageLayout = current.layout + } else { + #if DEBUG && false + let instantPage = InstantPage(blocks: [.thinking(.concat([ + .textCustomEmoji(fileId: 5384559872899555845, alt: "a"), + .plain("Thinking...") + ]))], media: [:], isComplete: true, rtl: false, url: "", views: nil) + #else + let instantPage = (showMoreExpanded ? attribute.fullInstantPage : nil) ?? attribute.instantPage + #endif + pageLayout = layoutInstantPageV2( + webpage: webpage, + instantPage: instantPage, + userLocation: .other, + boundingWidth: suggestedBoundingWidth - 2.0, + horizontalInset: pageHorizontalInset, + theme: pageTheme, + strings: item.presentationData.strings, + dateTimeFormat: item.presentationData.dateTimeFormat, + cachedMessageSyntaxHighlight: nil, + expandedDetails: currentExpandedDetails, + fitToWidth: true, + computeRevealCharacterRects: hasDraft || hadDraft + ) + } + } + + // Cost map computed here (not in apply) so we can size the bubble to the + // revealed prefix this layout pass. Mirrors TextBubble's clippedGlyphCountLayout. + let revealCostMap: InstantPageV2RevealCostMap? = (hasDraft || hadDraft) ? pageLayout?.computeRevealCostMap() : nil + let revealedGlyphCount: Int? = (hasDraft || hadDraft) ? (currentMaxGlyphCount ?? 0) : nil + + if let pageLayout { + let effectiveSize: CGSize + if let costMap = revealCostMap, let glyphCount = revealedGlyphCount { + effectiveSize = costMap.revealedContentSize(revealedCount: glyphCount, layout: pageLayout) + } else { + effectiveSize = pageLayout.contentSize + } + boundingSize.width = effectiveSize.width + boundingSize.height = effectiveSize.height + 2.0 + } + + // The hardcoded "Thinking…" header was removed in favor of server-sent + // InstantPageBlock.thinking blocks (rendered inside the pageView). There is no + // header strip anymore, so the page content starts at the top of the bubble. + let streamingHeaderOffset: CGFloat = 0.0 + + if hasDraft { + // The bubble's bottom inset is supplied by the `statusBottomEdge + 6.0` + // max() in the measure closure below — but that branch is gated by + // `!hasDraft`, so during streaming the bubble has only its 1pt bottom rim + // past `revealedContentSize.height` (= bounds.maxY + closingPad). Without + // this, descenders of the last revealed line sit cramped against the + // bubble's bottom edge and the bubble visibly grows by 6pt when streaming + // ends and the status node fades in. 6pt matches the constant inside the + // status max() (which itself tracks `TextBubble`'s `bubbleInsets.bottom`). + // `hadDraft && !hasDraft` (the finalize pass) doesn't need this because + // `!hasDraft` re-enables the status max(), which supplies the inset for it. + boundingSize.height += 6.0 + } + + let message = item.message + let incoming = isIncoming + + var edited = false + if item.attributes.updatingMedia != nil { + edited = true + } + var viewCount: Int? + var dateReplies = 0 + var starsCount: Int64? + var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage) + if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) { + dateReactionsAndPeers = ([], []) + } + + for attribute in item.message.attributes { + if let attribute = attribute as? EditedMessageAttribute { + edited = !attribute.isHidden + } else if let attribute = attribute as? ViewCountMessageAttribute { + viewCount = attribute.count + } else if let attribute = attribute as? ReplyThreadMessageAttribute, case .peer = item.chatLocation { + if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info { + dateReplies = Int(attribute.count) + } + } else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel { + starsCount = attribute.stars.value + } + } + + let dateFormat: MessageTimestampStatusFormat + if item.presentationData.isPreview { + dateFormat = .full + } else if let subject = item.associatedData.subject, case .messageOptions = subject { + dateFormat = .minimal + } else { + dateFormat = .regular + } + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + + let statusType: ChatMessageDateAndStatusType? + var displayStatus = false + switch position { + case let .linear(_, neighbor): + if case .None = neighbor { + displayStatus = true + } else if case .Neighbour(true, _, _) = neighbor { + displayStatus = true + } + default: + break + } + if case let .customChatContents(contents) = item.associatedData.subject { + if case .hashTagSearch = contents.kind { + displayStatus = true + } else { + displayStatus = false + } + } else if !item.presentationData.chatBubbleCorners.hasTails { + displayStatus = false + } else if case let .messageOptions(_, _, info) = item.associatedData.subject, case let .link(link) = info, link.isCentered { + displayStatus = false + } + + if displayStatus { + if incoming { + statusType = .BubbleIncoming + } else { + if message.flags.contains(.Failed) { + statusType = .BubbleOutgoing(.Failed) + } else if (message.flags.isSending && !message.isSentOrAcknowledged) || item.attributes.updatingMedia != nil { + statusType = .BubbleOutgoing(.Sending) + } else { + statusType = .BubbleOutgoing(.Sent(read: item.read)) + } + } + } else { + statusType = nil + } + + // Only trail the status inline with the last text line when the bottom-most page + // item is itself a text item; otherwise (table/image/etc. last) the status falls + // through to the contentSize.height anchor and sits below all content. + let lastTextLine = pageLayout.flatMap(InstantPageUI.lastTextLineFrameIfLastItemIsText(in:)) + var lastTextLineFrame: CGRect? = lastTextLine?.frame + // Baseline → visible-text-bottom compensation. Applied whether the date trails on + // the last line or wraps onto its own line below it (0 for attachment-inflated lines, + // whose maxY already sits at the visible bottom). + var lastTextLineTrailingPadding: CGFloat = lastTextLine?.trailingBottomPadding ?? 0.0 + + // "Show more" affordance for partial rich messages: laid out as a bubble-owned text + // node below the page content. Shown only when the page is incomplete AND the user + // has not expanded it yet (showMoreExpanded == false), the message is not streaming, + // it is a Cloud message (requestFullRichText is a no-op otherwise), and we are not in + // a preview / messageOptions context. When present, the date trails the link's line + // by substituting its frame for the last-text-line frame the status machinery consumes. + var showMore = false + if let attribute = item.message.richText, + !showMoreExpanded, + !attribute.instantPage.isComplete, + !hasDraft, + item.message.id.namespace == Namespaces.Message.Cloud, + !item.presentationData.isPreview { + if let subject = item.associatedData.subject, case .messageOptions = subject { + showMore = false + } else { + showMore = true + } + } + + var showMoreLayoutResult: (TextNodeLayout, () -> TextNode)? + var showMoreFramePageLocal: CGRect? + if showMore, let pageLayout { + let title = item.presentationData.strings.Chat_RichText_ShowMore + let attributedTitle = NSAttributedString(string: title, font: Font.regular(17.0), textColor: messageTheme.linkTextColor) + // The link only fits within the existing bubble width (it does not widen the + // bubble the way the status node does); the short fixed string never needs more, + // and `.end` truncation is a safe fallback for a pathologically narrow bubble. + let constrainedWidth = max(1.0, boundingSize.width - pageHorizontalInset * 2.0) + let layout = showMoreTextLayout(TextNodeLayoutArguments(attributedString: attributedTitle, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: constrainedWidth, height: 100.0))) + let showMoreTopSpacing: CGFloat = 2.0 + let frame = CGRect(origin: CGPoint(x: pageHorizontalInset, y: pageLayout.contentSize.height + showMoreTopSpacing), size: layout.0.size) + showMoreLayoutResult = layout + showMoreFramePageLocal = frame + // Date trails the link line (or wraps below it if it doesn't fit) — reuse the + // status machinery by substituting the link frame for the last-text-line frame. + lastTextLineFrame = frame + lastTextLineTrailingPadding = 0.0 + // Ensure the bubble contains the link even when the status node is hidden. The 1.0 + // is the content top rim; 6.0 the bottom breathing room used elsewhere in this file. + boundingSize.height = max(boundingSize.height, 1.0 + frame.maxY + 6.0) + } + + var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))? + if let statusType = statusType { + var isReplyThread = false + if case .replyThread = item.chatLocation { + isReplyThread = true + } + + // Measure trailing extent from the line's actual visible RIGHT EDGE (after + // alignment, in page coords) — not just its intrinsic width. A right-aligned + // or RTL last line has `lineWidth` worth of glyphs but sits all the way at + // the right text inset (lineFrame.maxX == text.frame.minX + textItem.width). + // Feeding the status node just `lineWidth` would let the trail/wrap decision + // place the date inline with the line — on top of it. `pageHorizontalInset` + // is the offset between page-coords and status-node-local coords (the status + // node sits at x=pageHorizontalInset in self, and pageView sits at self-x 0). + let trailingWidthToMeasure: CGFloat = lastTextLineFrame.map { $0.maxX - pageHorizontalInset } ?? 10000.0 + + let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)) + + statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( + context: item.context, + presentationData: item.presentationData, + edited: edited && !item.presentationData.isPreview, + impressionCount: !item.presentationData.isPreview ? viewCount : nil, + dateText: dateText, + type: statusType, + layoutInput: dateLayoutInput, + constrainedSize: CGSize(width: suggestedBoundingWidth, height: .greatestFiniteMagnitude), + availableReactions: item.associatedData.availableReactions, + savedMessageTags: item.associatedData.savedMessageTags, + reactions: item.presentationData.isPreview ? [] : dateReactionsAndPeers.reactions, + reactionPeers: dateReactionsAndPeers.peers, + displayAllReactionPeers: item.message.id.peerId.namespace == Namespaces.Peer.CloudUser, + areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId), + areStarReactionsEnabled: item.associatedData.areStarReactionsEnabled, + messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects), + replyCount: dateReplies, + starsCount: starsCount, + isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), + hasAutoremove: item.message.isSelfExpiring, + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), + animationCache: item.controllerInteraction.presentationContext.animationCache, + animationRenderer: item.controllerInteraction.presentationContext.animationRenderer + )) + } + + if let statusSuggestedWidthAndContinue, !hasDraft { + // Mirrors TextBubble: max(contentWidth, statusWidth + sideInsets), where + // sideInsets = left + right text inset (= pageHorizontalInset on each side). + boundingSize.width = max(boundingSize.width, statusSuggestedWidthAndContinue.0 + pageHorizontalInset * 2.0) + } + + return (boundingSize.width, { boundingWidth in + // Mirrors TextBubble's `boundingWidth - sideInsets` so the right-aligned date + // lands at the right text inset rather than past the bubble's right edge. + let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - pageHorizontalInset * 2.0) + if let statusSizeAndApply, !hasDraft { + // Status node anchor Y in the content node's space — mirrors the apply + // closure below. + let statusAnchorY: CGFloat + if let lastTextLineFrame { + // The renderer draws the baseline at the line frame's maxY, so the + // visible text sits `trailingBottomPadding` below it. Apply that pad + // whether the date trails on the line OR wraps onto its own line below: + // in both cases the date should reference the visible text bottom, not + // the baseline (mirrors TextBubble, whose status anchors at the text + // frame's maxY). Without it the wrapped date crowded the last line. + statusAnchorY = 1.0 + lastTextLineFrame.maxY + lastTextLineTrailingPadding + streamingHeaderOffset + } else if let pageLayout { + statusAnchorY = 1.0 + pageLayout.contentSize.height + streamingHeaderOffset + } else { + statusAnchorY = 1.0 + streamingHeaderOffset + } + // Date's bottom edge: a trailing date sits ~1pt below the anchor; a wrapped + // date extends `statusHeight` below it. Leave ~6pt to the bubble's bottom + // edge, matching TextBubble's bottom inset. + let statusBottomEdge = statusAnchorY + max(1.0, statusSizeAndApply.0.height) + boundingSize.height = max(boundingSize.height, statusBottomEdge + 6.0) + } + + return (boundingSize, { animation, _, info in + guard let self else { + return + } + self.item = item + + // If the bubble was recycled onto a different message while a full-text + // request was in flight, cancel it so this message never shows another's + // shimmer. + if let pendingId = self.requestFullRichTextMessageId, pendingId != item.message.id { + self.requestFullRichTextDisposable?.dispose() + self.requestFullRichTextDisposable = nil + self.requestFullRichTextMessageId = nil + self.updateShowMoreLoading(false) + } + + // On the collapse→expand transition (tapping "Show more"), grow the bubble + // downward in screen space (inverted list offset direction) instead of pushing + // earlier messages up — matching the audio-transcription expand. The ListView + // clamps this to what fits, so "if possible" is handled for us. Only fires on a + // change, and never on the first apply (appliedShowMoreExpanded is nil). + if let appliedShowMoreExpanded = self.appliedShowMoreExpanded, appliedShowMoreExpanded != showMoreExpanded { + info?.setInvertOffsetDirection() + } + self.appliedShowMoreExpanded = showMoreExpanded + + animation.animator.updateFrame(layer: self.containerNode.layer, frame: CGRect(origin: CGPoint(x: 1.0, y: 1.0), size: CGSize(width: boundingWidth - 2.0, height: boundingSize.height)), completion: nil) + self.containerNode.cornerRadius = layoutConstants.image.defaultCornerRadius + + if let statusSizeAndApply { + // Match TextBubble: anchor the status node's x at the fixed text-block + // left edge (not the last line's minX, which is large for nested + // content and shoves the right-aligned date off the bubble). The status + // node positions the date trailing/below relative to this origin. + let statusFrameY: CGFloat + if let lastTextLineFrame { + // Apply the text-rect pad (baseline → visible text bottom) for both + // the trailing and wrapped cases, so the date references the visible + // text bottom rather than the baseline. Mirrors the measure closure + // and TextBubble. Without it the wrapped date crowded the last line. + statusFrameY = 1.0 + lastTextLineFrame.maxY + lastTextLineTrailingPadding + } else if let pageLayout { + statusFrameY = 1.0 + pageLayout.contentSize.height + } else { + statusFrameY = 1.0 + } + let statusFrame = CGRect(origin: CGPoint(x: pageHorizontalInset, y: statusFrameY + streamingHeaderOffset), size: statusSizeAndApply.0) + let statusNode = statusSizeAndApply.1(self.statusNode == nil ? .None : animation) + + if self.statusNode !== statusNode { + self.statusNode?.removeFromSupernode() + self.statusNode = statusNode + + self.addSubnode(statusNode) + + statusNode.reactionSelected = { [weak self] _, value, sourceView in + guard let self, let item = self.item else { + return + } + item.controllerInteraction.updateMessageReaction(item.topMessage, .reaction(value), false, sourceView) + } + statusNode.openReactionPreview = { [weak self] gesture, sourceNode, value in + guard let self, let item = self.item else { + gesture?.cancel() + return + } + item.controllerInteraction.openMessageReactionContextMenu(item.topMessage, sourceNode, gesture, value) + } + statusNode.frame = statusFrame + } else { + animation.animator.updatePosition(layer: statusNode.layer, position: statusFrame.center, completion: nil) + animation.animator.updateBounds(layer: statusNode.layer, bounds: CGRect(origin: .zero, size: statusFrame.size), completion: nil) + } + } else if let statusNode = self.statusNode { + self.statusNode = nil + statusNode.removeFromSupernode() + } + + if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported), let statusNode = self.statusNode { + statusNode.pressed = { [weak self] in + guard let self, let statusNode = self.statusNode, let item = self.item else { + return + } + item.controllerInteraction.displayImportedMessageTooltip(statusNode) + } + } else { + self.statusNode?.pressed = nil + } + + if let pageLayout, let pageWebpage, let _ = item.message.richText { + self.currentPageLayout = ( + suggestedBoundingWidth, + ObjectIdentifier(item.presentationData.theme.theme), + self.currentExpandedDetails, + item.message.stableVersion, + showMoreExpanded, + pageLayout + ) + let pageView = self.ensurePageView(item: item, webpage: pageWebpage, showMoreExpanded: showMoreExpanded) + pageView.update(layout: pageLayout, theme: pageTheme, animation: animation) + pageView.frame = CGRect( + origin: CGPoint(x: -1.0, y: streamingHeaderOffset), + size: pageLayout.contentSize + ) + self.updatePageViewVisibilityRect() + if self.displayContentsUnderSpoilers { + pageView.setDisplayContentsUnderSpoilers(true, atLocation: nil, animated: false) + } + // Continue an in-flight anchor scroll that is waiting on a
+ // expansion to re-lay-out. This runs on EVERY apply pass (not only the + // expand-triggered one), but only does anything while a scroll is pending + // — and scrollToAnchor is idempotent: each invocation either resolves and + // scrolls (clearing pending) or expands the next collapsed level, and the + // progress guard guarantees termination. So an unrelated relayout (theme, + // width, reactions) that lands mid-expand simply advances/no-ops the loop. + // Deferred via justDispatch to avoid re-entering layout from this apply. + if let pendingAnchor = self.pendingScrollAnchor { + Queue.mainQueue().justDispatch { [weak self] in + guard let self, self.pendingScrollAnchor == pendingAnchor else { + return + } + self.scrollToAnchor(pendingAnchor) + } + } + } else { + self.currentPageLayout = nil + self.pageView?.update( + layout: InstantPageV2Layout(contentSize: .zero, items: [], detailsIndices: []), + theme: pageTheme, + animation: animation + ) + self.pageViewMessageKey = nil + } + + // "Show more" link node. + if let showMoreLayoutResult, let showMoreFramePageLocal { + let showMoreTextNode = showMoreLayoutResult.1() + if self.showMoreTextNode !== showMoreTextNode { + self.showMoreTextNode?.removeFromSupernode() + self.showMoreTextNode = showMoreTextNode + showMoreTextNode.isUserInteractionEnabled = false + self.addSubnode(showMoreTextNode) + } + // Self-coords: the 1.0 mirrors statusFrameY's container offset; the page + // content sits 1pt below the content-node top. + showMoreTextNode.frame = CGRect(origin: CGPoint(x: pageHorizontalInset, y: 1.0 + showMoreFramePageLocal.minY), size: showMoreFramePageLocal.size) + // Keep the shimmer alive across intervening relayouts while loading. + if self.requestFullRichTextDisposable != nil, self.requestFullRichTextMessageId == item.message.id { + self.updateShowMoreLoading(true) + } + } else { + if let showMoreTextNode = self.showMoreTextNode { + self.showMoreTextNode = nil + showMoreTextNode.removeFromSupernode() + } + self.updateShowMoreLoading(false) + } + + if let formattedDateUpdatePeriod = pageLayout?.formattedDateUpdatePeriod { + // Recreate the timer only when the period changes — unlike the TextBubble + // reference (ChatMessageTextBubbleContentNode), which rebuilds it every apply. + // The timer fires `requestFullUpdate`, which relays out and re-enters here; at + // a steady period this guard is false, so the running timer keeps its schedule + // instead of being reallocated (no per-apply churn, no firing-phase reset, no + // self-trigger loop). Do not "simplify" this to match the reference. + if self.relativeDateTimer?.period != formattedDateUpdatePeriod { + self.relativeDateTimer?.timer.invalidate() + let timer = SwiftSignalKit.Timer(timeout: Double(formattedDateUpdatePeriod), repeat: true, completion: { [weak self] in + self?.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) + }, queue: Queue.mainQueue()) + self.relativeDateTimer = (timer, formattedDateUpdatePeriod) + timer.start() + } + } else if let (timer, _) = self.relativeDateTimer { + self.relativeDateTimer = nil + timer.invalidate() + } + + // === Streaming state apply === + + // 1. Compute / cache the cost map. + // Reuse the cost map computed in the layout pass (the bubble's + // size depended on it) — don't recompute. Keep the previous map + // alive while a reveal/finalize is still in flight: on a post- + // streaming pass (hasDraft && hadDraft both false) revealCostMap is + // nil, and clobbering it would strand the display-link tick (whose + // guard requires a cost map), aborting the finalize before it can + // clear the mask and restore the status alpha. + if let revealCostMap { + self.currentRevealCostMap = revealCostMap + } else if self.textRevealController == nil { + self.currentRevealCostMap = nil + } + + // 2. Drive the reveal controller. + let previousAnimateGlyphCount: Int? = (hasDraft || hadDraft) ? (self.textRevealController?.currentGlyphCount ?? 0) : nil + if previousAnimateGlyphCount != nil || self.textRevealController != nil || hasDraft || hadDraft { + if hasDraft { + self.statusNode?.alpha = 0.0 + } + // Seed the (possibly freshly rebuilt) V2 view to the reveal cursor's + // current position so we don't flash full text. Use the live controller + // count rather than `previousAnimateGlyphCount`, which is nil — and would + // reset the reveal to 0 — on post-streaming finalize passes where the + // controller is still animating. + let seedCount = self.textRevealController?.currentGlyphCount ?? previousAnimateGlyphCount ?? 0 + self.pageView?.applyReveal(revealedCount: seedCount, + costMap: self.currentRevealCostMap, + animated: false) + self.lastAppliedRevealedCount = seedCount + self.updateTextRevealAnimation(previousGlyphCount: previousAnimateGlyphCount ?? 0, + hasDraft: hasDraft, + hadDraft: hadDraft) + } + }) + }) + }) + } + } + + private func updateTextRevealAnimation(previousGlyphCount: Int, hasDraft: Bool, hadDraft: Bool) { + let toCount = self.currentRevealCostMap?.total ?? 0 + let now = CACurrentMediaTime() + + if hasDraft, let controller = self.textRevealController, controller.isFinalizing { + self.textRevealController = nil + self.textRevealLink = nil + } + + if self.textRevealController == nil && (hasDraft || hadDraft) { + self.textRevealController = TextRevealController(initialRevealedCount: previousGlyphCount, initialLength: toCount, durationMultiplier: 10.0) + } + + guard let controller = self.textRevealController else { return } + + if hasDraft { + controller.observeUpdate(latestLength: toCount, at: now) + } else if hadDraft { + controller.finalize(finalLength: toCount) + } + + if controller.isFinalizing && controller.revealedCount >= Double(controller.latestLength) { + self.textRevealController = nil + self.textRevealLink = nil + self.pageView?.applyReveal(revealedCount: nil, costMap: nil, animated: false) + self.lastAppliedRevealedCount = 0 + // The cursor already caught up at finalize time, so the display-link `isComplete` + // branch (which normally restores the status alpha) will never run. Restore it + // here too, mirroring that branch. + if let item = self.item, let statusNode = self.statusNode, + !item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: statusNode, alpha: 1.0) + } + return + } + + guard toCount > 0 else { return } + + if self.textRevealLink == nil { + self.textRevealLink = SharedDisplayLinkDriver.shared.add { [weak self] _ in + guard let self else { return } + guard let item = self.item else { + self.textRevealController = nil + self.textRevealLink = nil + return + } + guard let controller = self.textRevealController, let costMap = self.currentRevealCostMap else { + self.textRevealLink = nil + return + } + let now = CACurrentMediaTime() + let (revealedGlyphCount, isComplete) = controller.tick(now: now) + + if isComplete { + self.textRevealController = nil + self.textRevealLink = nil + self.pageView?.applyReveal(revealedCount: nil, costMap: nil, animated: false) + self.lastAppliedRevealedCount = 0 + + if let statusNode = self.statusNode, + !item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: statusNode, alpha: 1.0) + } + self.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) + } else { + // If the revealed prefix's bottom y would change at the new cursor (i.e. + // crossing a line/item boundary), trigger a full bubble re-layout so the + // bubble grows with the reveal. Mirrors TextBubble's + // `cachedLayout.sizeForCharacterCount(...)` check at lines 1209-1216. + var requestUpdate = false + if let pageLayout = self.currentPageLayout?.layout, self.lastAppliedRevealedCount != revealedGlyphCount { + let prevHeight = costMap.revealedContentSize(revealedCount: self.lastAppliedRevealedCount, layout: pageLayout).height + let newHeight = costMap.revealedContentSize(revealedCount: revealedGlyphCount, layout: pageLayout).height + if prevHeight != newHeight { + requestUpdate = true + } + } + self.pageView?.applyReveal(revealedCount: revealedGlyphCount, costMap: costMap, animated: true) + self.lastAppliedRevealedCount = revealedGlyphCount + if requestUpdate { + self.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) + } + } + } + } + } + + override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2) + } + } + + override public func animateAdded(_ currentTimestamp: Double, duration: Double) { + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2) + } + } + + override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: statusNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + } + + override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if case .tap = gesture { + } else { + if let item = self.item, let subject = item.associatedData.subject, case .messageOptions = subject { + return ChatMessageBubbleContentTapAction(content: .none) + } + } + + if case .tap = gesture, let showMoreTextNode = self.showMoreTextNode, showMoreTextNode.frame.contains(point) { + // Highlight rect in containerNode-local coords (the highlight overlay lives inside + // containerNode, which sits at self (1, 1); the text node is on self). + let rects = [showMoreTextNode.frame.offsetBy(dx: -1.0, dy: -1.0)] + return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in + self?.activateShowMore() + }), rects: rects) + } + + if case .tap = gesture, !self.displayContentsUnderSpoilers, let entityHit = self.entityForTapLocation(point), entityHit.attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] != nil { + return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in + self?.revealSpoilers(atContentPoint: point) + })) + } + + guard let urlHit = self.urlForTapLocation(point) else { + if let entityHit = self.entityForTapLocation(point), let content = self.entityTapContent(entityHit.attributes) { + let rects = self.computeHighlightRects(item: entityHit.item, parentOffset: entityHit.parentOffset, localPoint: entityHit.localPoint) + return ChatMessageBubbleContentTapAction( + content: content, + rects: rects, + activate: self.makeActivate(item: entityHit.item, parentOffset: entityHit.parentOffset, localPoint: entityHit.localPoint) + ) + } + return ChatMessageBubbleContentTapAction(content: .none) + } + + let split = self.splitAnchor(urlHit.urlItem.url) + if split.base.isEmpty, let anchor = split.anchor { + // Don't accept intra-message anchor taps while the message is still streaming. + if let item = self.item, item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + return ChatMessageBubbleContentTapAction(content: .none) + } + let rects = self.computeHighlightRects(item: urlHit.item, parentOffset: urlHit.parentOffset, localPoint: urlHit.localPoint) + return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in + self?.scrollToAnchor(anchor) + }), rects: rects) + } + if let webpage = self.currentLoadedWebpage(), webpage.content.url == split.base, let anchor = split.anchor { + return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in + self?.scrollToAnchor(anchor) + })) + } + + // Default to concealed=true: InstantPageTextItem does not expose a clean + // "attribute substring with displayed range" API, so we cannot compare + // displayed text to the resolved URL the way the chat text bubble does. + // The chat URL handler will show a confirmation when concealed is true + // and the visible text differs from the destination — safer default. + let concealed = true + let url = ChatMessageBubbleContentTapAction.Url(url: urlHit.urlItem.url, concealed: concealed, allowInlineWebpageResolution: urlHit.urlItem.webpageId != nil) + let rects = self.computeHighlightRects(item: urlHit.item, parentOffset: urlHit.parentOffset, localPoint: urlHit.localPoint) + + if let webpageId = urlHit.urlItem.webpageId { + let split = self.splitAnchor(url.url) + return ChatMessageBubbleContentTapAction( + content: .externalInstantPage(url: url, webpageId: webpageId, anchor: split.anchor), + rects: rects, + activate: self.makeActivate(item: urlHit.item, parentOffset: urlHit.parentOffset, localPoint: urlHit.localPoint) + ) + } else { + return ChatMessageBubbleContentTapAction( + content: .url(url), + rects: rects, + activate: self.makeActivate(item: urlHit.item, parentOffset: urlHit.parentOffset, localPoint: urlHit.localPoint) + ) + } + } + + private func textItemAtLocation(_ location: CGPoint) -> (item: InstantPageTextItem, parentOffset: CGPoint)? { + guard let pageView = self.pageView else { return nil } + let local = self.view.convert(location, to: pageView) + return pageView.textItemAt(point: local) + } + + private func urlForTapLocation(_ point: CGPoint) -> (item: InstantPageTextItem, urlItem: InstantPageUrlItem, parentOffset: CGPoint, localPoint: CGPoint)? { + guard let pageView = self.pageView else { return nil } + let local = self.view.convert(point, to: pageView) + return pageView.urlItemAt(point: local).map { + (item: $0.item, urlItem: $0.urlItem, parentOffset: $0.parentOffset, localPoint: $0.localPoint) + } + } + + private func entityForTapLocation(_ point: CGPoint) -> (item: InstantPageTextItem, parentOffset: CGPoint, localPoint: CGPoint, attributes: [NSAttributedString.Key: Any])? { + guard let pageView = self.pageView else { return nil } + let local = self.view.convert(point, to: pageView) + guard let hit = pageView.textItemAt(point: local) else { return nil } + let localPoint = CGPoint(x: local.x - hit.parentOffset.x, y: local.y - hit.parentOffset.y) + guard let (_, attributes) = hit.item.attributesAtPoint(localPoint, orNearest: false) else { return nil } + return (item: hit.item, parentOffset: hit.parentOffset, localPoint: localPoint, attributes: attributes) + } + + private func revealSpoilers(atContentPoint point: CGPoint) { + guard !self.displayContentsUnderSpoilers, let pageView = self.pageView else { + return + } + self.displayContentsUnderSpoilers = true + let local = self.view.convert(point, to: pageView) + pageView.setDisplayContentsUnderSpoilers(true, atLocation: local, animated: true) + } + + private func entityTapContent(_ attributes: [NSAttributedString.Key: Any]) -> ChatMessageBubbleContentTapAction.Content? { + if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention { + return .peerMention(peerId: mention.peerId, mention: mention.mention, openProfile: false) + } else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String { + return .textMention(peerName) + } else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String { + return .botCommand(botCommand) + } else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag { + // Cashtags are carried as a Hashtag attribute (no dedicated cashtag key/tap-action exists); + // the leading "$" in the string distinguishes them, and the chat hashtag handler searches both. + return .hashtag(hashtag.peerName, hashtag.hashtag) + } else if let bankCard = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard)] as? String { + return .bankCard(bankCard) + } else if let date = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Date)] as? Int32 { + // The displayed string is unused downstream (ChatMessageBubbleItemNode matches `.date(date, _)`). + return .date(date, "") + } + return nil + } + + /// Bridges an InstantPageUrlItem (used by the gallery's caption URL handler) to the + /// chat layer's URL handler. `concealed: true` matches `tapActionAtPoint` for the same + /// reason: V2 cannot reliably compare displayed link text to the resolved URL. + private func openInstantPageUrl(_ url: InstantPageUrlItem) { + guard let item = self.item else { return } + item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl( + url: url.url, + concealed: true, + allowInlineWebpageResolution: url.webpageId != nil + )) + } + + private func computeHighlightRects(item: InstantPageTextItem, parentOffset: CGPoint, localPoint: CGPoint) -> [CGRect] { + // Text item returns rects in its local coords; translate back into containerNode-local coords. + // containerNode is offset by (1, 1) from the bubble-content-node, but the highlight overlay lives + // *inside* containerNode, so we use layout-coords (= containerNode-local) for the rects. + let originX = parentOffset.x + let originY = parentOffset.y + return item.linkSelectionRects(at: localPoint).map { rect in + rect.offsetBy(dx: originX, dy: originY) + } + } + + private func makeActivate(item: InstantPageTextItem, parentOffset: CGPoint, localPoint: CGPoint) -> (() -> Promise?)? { + return { [weak self, weak item] in + guard let self else { + return nil + } + let promise = Promise() + self.linkProgressDisposable?.dispose() + if self.linkProgressRects != nil { + self.linkProgressRects = nil + self.updateLinkProgressState() + } + self.linkProgressDisposable = (promise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + let updated: [CGRect]? + if value, let item { + updated = self.computeHighlightRects(item: item, parentOffset: parentOffset, localPoint: localPoint) + } else { + updated = nil + } + let changed: Bool + if let lhs = self.linkProgressRects, let rhs = updated { + changed = lhs != rhs + } else { + changed = (self.linkProgressRects == nil) != (updated == nil) + } + if changed { + self.linkProgressRects = updated + self.updateLinkProgressState() + } + }) + return promise + } + } + + private func updateLinkProgressState() { + guard let messageItem = self.item else { + return + } + if let rects = self.linkProgressRects, !rects.isEmpty { + let linkProgressView: TextLoadingEffectView + if let current = self.linkProgressView { + linkProgressView = current + } else { + linkProgressView = TextLoadingEffectView(frame: CGRect()) + self.linkProgressView = linkProgressView + self.containerNode.view.addSubview(linkProgressView) + } + linkProgressView.frame = self.containerNode.bounds + + let progressColor: UIColor = messageItem.message.effectivelyIncoming(messageItem.context.account.peerId) + ? messageItem.presentationData.theme.theme.chat.message.incoming.linkHighlightColor + : messageItem.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor + + linkProgressView.update(color: progressColor, size: self.containerNode.bounds.size, rects: rects) + } else if let linkProgressView = self.linkProgressView { + self.linkProgressView = nil + linkProgressView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linkProgressView] _ in + linkProgressView?.removeFromSuperview() + }) + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let statusNode = self.statusNode, statusNode.supernode != nil, let result = statusNode.hitTest(self.view.convert(point, to: statusNode.view), with: event) { + return result + } + return super.hitTest(point, with: event) + } + + override public func updateTouchesAtPoint(_ point: CGPoint?) { + guard let messageItem = self.item else { + return + } + + var rects: [CGRect]? + if let point { + if let showMoreTextNode = self.showMoreTextNode, showMoreTextNode.frame.contains(point) { + rects = [showMoreTextNode.frame.offsetBy(dx: -1.0, dy: -1.0)] + } else if let urlHit = self.urlForTapLocation(point) { + rects = self.computeHighlightRects(item: urlHit.item, parentOffset: urlHit.parentOffset, localPoint: urlHit.localPoint) + } else if let entityHit = self.entityForTapLocation(point), self.entityTapContent(entityHit.attributes) != nil { + rects = self.computeHighlightRects(item: entityHit.item, parentOffset: entityHit.parentOffset, localPoint: entityHit.localPoint) + } + } + + if let rects, !rects.isEmpty { + let highlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + highlightingNode = current + } else { + let color: UIColor = messageItem.message.effectivelyIncoming(messageItem.context.account.peerId) + ? messageItem.presentationData.theme.theme.chat.message.incoming.linkHighlightColor + : messageItem.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor + highlightingNode = LinkHighlightingNode(color: color) + highlightingNode.useModernPathCalculation = true + self.linkHighlightingNode = highlightingNode + self.containerNode.insertSubnode(highlightingNode, at: 0) + } + highlightingNode.frame = self.containerNode.bounds + highlightingNode.updateRects(rects) + } else if let highlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + highlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak highlightingNode] _ in + highlightingNode?.removeFromSupernode() + }) + } + } + + override public func updateSearchTextHighlightState(text: String?, messages: [EngineMessage.Index]?) { + } + + override public func willUpdateIsExtractedToContextPreview(_ value: Bool) { + if !value, let textSelectionNode = self.textSelectionNode { + self.textSelectionNode = nil + self.textSelectionAdapter = nil + textSelectionNode.highlightAreaNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + textSelectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSelectionNode] _ in + textSelectionNode?.highlightAreaNode.removeFromSupernode() + textSelectionNode?.removeFromSupernode() + }) + } + } + + override public func updateIsExtractedToContextPreview(_ value: Bool) { + guard value, self.textSelectionNode == nil, let messageItem = self.item, self.currentPageLayout?.layout != nil, let pageView = self.pageView, let rootNode = messageItem.controllerInteraction.chatControllerNode() else { + return + } + + // pageView sits at (-1, 0) inside containerNode; the adapter is placed at + // containerNode.bounds, so shift each item's page-space origin into + // containerNode-local coords for the adapter to operate in. + let pageOrigin = pageView.frame.origin + let entries = pageView.selectableTextItems() + .filter { $0.item.selectable && !$0.item.attributedString.string.isEmpty } + .map { entry in + InstantPageMultiTextAdapter.Entry( + item: entry.item, + frameOrigin: CGPoint( + x: entry.parentOffset.x + pageOrigin.x, + y: entry.parentOffset.y + pageOrigin.y + ) + ) + } + guard !entries.isEmpty else { + return + } + + let adapter = InstantPageMultiTextAdapter(entries: entries) + adapter.frame = self.containerNode.bounds + self.textSelectionAdapter = adapter + self.containerNode.addSubnode(adapter) + + let incoming = messageItem.message.effectivelyIncoming(messageItem.context.account.peerId) + let theme = messageItem.presentationData.theme.theme + let selectionColor = incoming ? theme.chat.message.incoming.textSelectionColor : theme.chat.message.outgoing.textSelectionColor + let knobColor = incoming ? theme.chat.message.incoming.textSelectionKnobColor : theme.chat.message.outgoing.textSelectionKnobColor + + let textSelectionNode = TextSelectionNode( + theme: TextSelectionTheme(selection: selectionColor, knob: knobColor, isDark: theme.overallDarkAppearance), + strings: messageItem.presentationData.strings, + textNodeOrView: .node(adapter), + updateIsActive: { _ in }, + present: { [weak self] c, a in + guard let self, let item = self.item else { + return + } + if let subject = item.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .reply = info { + item.controllerInteraction.presentControllerInCurrent(c, a) + } else { + item.controllerInteraction.presentGlobalOverlayController(c, a) + } + }, + rootView: { [weak rootNode] in + return rootNode?.view + }, + performAction: { [weak self] text, action in + guard let self, let item = self.item else { + return + } + if case .copy = action, + let range = self.textSelectionNode?.getSelection(), + range.length > 0, + let adapter = self.textSelectionAdapter { + let markdown = adapter.markdownForRange(range) + if !markdown.isEmpty { + item.controllerInteraction.performTextSelectionAction(item.message, true, NSAttributedString(string: markdown), nil, .copy) + return + } + } + item.controllerInteraction.performTextSelectionAction(item.message, true, text, nil, action) + } + ) + + let enableCopy = (!messageItem.associatedData.isCopyProtectionEnabled && !messageItem.message.isCopyProtected()) || messageItem.message.id.peerId.isVerificationCodes + textSelectionNode.enableCopy = enableCopy + + var enableOtherActions = true + if let subject = messageItem.associatedData.subject, case let .messageOptions(_, _, info) = subject, case .reply = info { + enableOtherActions = false + } + + textSelectionNode.enableQuote = false + textSelectionNode.enableTranslate = enableOtherActions + textSelectionNode.enableShare = enableOtherActions && enableCopy + textSelectionNode.enableLookup = true + textSelectionNode.menuSkipCoordnateConversion = !enableOtherActions + + textSelectionNode.frame = self.containerNode.bounds + textSelectionNode.highlightAreaNode.frame = self.containerNode.bounds + self.containerNode.insertSubnode(textSelectionNode.highlightAreaNode, at: 0) + self.containerNode.addSubnode(textSelectionNode) + self.textSelectionNode = textSelectionNode + } + + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + // V2 V0: media items render as gray placeholders; no transition node is exposed. + return nil + } + + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { + // V2 V0: media items render as gray placeholders; nothing to hide. + return false + } + + override public func getAnchorRect(anchor: String) -> CGRect? { + guard let pageView = self.pageView, let rect = pageView.anchorFrame(name: anchor) else { + return nil + } + // Small top breathing room so the target isn't flush against the content-area top + // (cf. V1 InstantPageControllerNode's -10 offset). The chat scroll consumes only the + // returned rect's minY (ChatController's scrollToMessageIdWithAnchor → .bottom(anchorY)), + // so pulling minY up by the margin is what lands the anchor below the top edge; the rect + // is grown to keep maxY stable should a future caller use the full rect (e.g. a highlight). + let topMargin: CGFloat = 8.0 + let adjusted = CGRect(x: rect.minX, y: max(0.0, rect.minY - topMargin), width: rect.width, height: rect.height + topMargin) + return self.view.convert(adjusted, from: pageView) + } + + override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? { + if let statusNode = self.statusNode, !statusNode.isHidden { + return statusNode.reactionView(value: value) + } + return nil + } + + override public func messageEffectTargetView() -> UIView? { + if let statusNode = self.statusNode, !statusNode.isHidden { + return statusNode.messageEffectTargetView() + } + return nil + } + + override public func getStatusNode() -> ASDisplayNode? { + return self.statusNode + } + + private func splitAnchor(_ url: String) -> (base: String, anchor: String?) { + if let anchorRange = url.range(of: "#") { + let anchor = String(url[anchorRange.upperBound...]).removingPercentEncoding + let base = String(url[.. TelegramMediaWebpage? { + return nil // V2 V0: media items are placeholders; no inline webpage resolution. + } + + private func scrollToAnchor(_ anchor: String) { + guard let item = self.item else { + return + } + // Empty fragment ("#") is a no-op. + if anchor.isEmpty { + self.clearPendingScroll() + return + } + // 1. Anchor is in the currently laid-out content → scroll now. + if self.pageView?.anchorFrame(name: anchor) != nil { + self.clearPendingScroll() + item.controllerInteraction.scrollToMessageIdWithAnchor(item.message.index, anchor) + return + } + // 2. Not laid out — it may be buried in a collapsed
. Find the path and expand + // the first collapsed details on it, then retry after the relayout (post-relayout hook). + let anchorExpanded = (self.showMoreExpanded?.messageId == item.message.id) ? (self.showMoreExpanded?.value ?? false) : false + guard let instantPage = item.message.richText.map({ (anchorExpanded ? $0.fullInstantPage : nil) ?? $0.instantPage }), + let path = instantPageAnchorPath(in: instantPage, name: anchor), + !path.isEmpty, + let collapsedIndex = self.pageView?.firstCollapsedDetails(forOrdinalPath: path) + else { + self.clearPendingScroll() + return + } + // Progress guard: if expanding this same index last pass didn't move us forward, stop. + if self.lastExpandedPendingDetailsIndex == collapsedIndex { + self.clearPendingScroll() + return + } + self.currentExpandedDetails[collapsedIndex] = true + self.pendingScrollAnchor = anchor + self.lastExpandedPendingDetailsIndex = collapsedIndex + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) + } + + private func clearPendingScroll() { + self.pendingScrollAnchor = nil + self.lastExpandedPendingDetailsIndex = nil + } + + // Fired by the "Show more" tap action. Expands this bubble to the full page: if the attribute + // already carries a cached fullInstantPage, expands immediately; otherwise fetches it (which + // persists it onto the message) while shimmering the link, then expands. Guards against a + // second request while one is in flight, and against re-expanding an already-expanded bubble. + private func activateShowMore() { + guard let item = self.item, let attribute = item.message.richText else { + return + } + let messageId = item.message.id + if let state = self.showMoreExpanded, state.messageId == messageId, state.value { + return + } + // Full page already cached on the attribute — expand immediately, no network, no shimmer. + if attribute.fullInstantPage != nil { + self.showMoreExpanded = (messageId, true) + item.controllerInteraction.requestMessageUpdate(messageId, false, nil) + return + } + // Otherwise fetch it; keep the link visible and shimmering until it arrives. + if self.requestFullRichTextDisposable != nil { + return + } + self.requestFullRichTextMessageId = messageId + self.updateShowMoreLoading(true) + self.requestFullRichTextDisposable = (item.context.engine.messages.requestFullRichText(id: messageId) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self else { + return + } + if result?.fullInstantPage != nil { + self.showMoreExpanded = (messageId, true) + } + self.finishShowMore() + if let item = self.item, item.message.id == messageId { + item.controllerInteraction.requestMessageUpdate(messageId, false, nil) + } + }, completed: { [weak self] in + self?.finishShowMore() + }) + } + + // Clears the in-flight request state and stops the shimmer. Invoked from both the request's + // `next` and `completed` handlers (the signal emits one value then completes); idempotent. + private func finishShowMore() { + self.requestFullRichTextDisposable?.dispose() + self.requestFullRichTextDisposable = nil + self.requestFullRichTextMessageId = nil + self.updateShowMoreLoading(false) + } + + // Shows/hides the shimmer over the "Show more" text node. The TextLoadingEffectView masks + // itself with the text node's own range rects, so it is placed at the text node's frame in + // self-coordinates (same parent). Removing the text node also removes the shimmer. + private func updateShowMoreLoading(_ loading: Bool) { + guard let item = self.item, let showMoreTextNode = self.showMoreTextNode else { + if let loadingView = self.showMoreLoadingView { + self.showMoreLoadingView = nil + loadingView.removeFromSuperview() + } + return + } + if loading { + let loadingView: TextLoadingEffectView + if let current = self.showMoreLoadingView { + loadingView = current + } else { + loadingView = TextLoadingEffectView(frame: CGRect()) + self.showMoreLoadingView = loadingView + self.view.addSubview(loadingView) + } + loadingView.frame = showMoreTextNode.frame + let color = item.message.effectivelyIncoming(item.context.account.peerId) + ? item.presentationData.theme.theme.chat.message.incoming.linkTextColor + : item.presentationData.theme.theme.chat.message.outgoing.linkTextColor + let title = item.presentationData.strings.Chat_RichText_ShowMore + loadingView.update(color: color, textNode: showMoreTextNode, range: NSRange(location: 0, length: (title as NSString).length)) + } else if let loadingView = self.showMoreLoadingView { + self.showMoreLoadingView = nil + loadingView.removeFromSuperview() + } + } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD index 159767d0a3..f05b8b8b4e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 6047a3099b..01c8b818d0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -136,21 +135,6 @@ private final class GlassButtonView: UIView { super.init(frame: frame) self.addSubview(self.backgroundView) - - if #available(iOS 26.0, *) { - } else { - self.button.highligthedChanged = { [weak self] highlighted in - guard let self else { - return - } - if highlighted && self.isEnabled && !self.isImplicitlyDisabled { - self.backgroundView.contentView.alpha = 0.6 - } else { - self.backgroundView.contentView.alpha = 1.0 - self.backgroundView.contentView.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) - } - } - } } required init?(coder: NSCoder) { @@ -177,7 +161,8 @@ private final class GlassButtonView: UIView { transition.setFrame(view: self.button, frame: CGRect(origin: CGPoint(), size: params.size)) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: params.size)) - self.backgroundView.update(size: params.size, cornerRadius: min(params.size.width, params.size.height) * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: params.preferClearGlass ? .clear : .panel), isInteractive: isEnabled, transition: transition) + self.backgroundView.update(size: params.size, cornerRadius: min(params.size.width, params.size.height) * 0.5, isDark: params.theme.overallDarkAppearance, tintColor: .init(kind: params.preferClearGlass ? .clear : .panel), isInteractive: true, transition: transition) + self.backgroundView.isUserInteractionEnabled = isEnabled self.iconView.alpha = isEnabled ? 1.0 : 0.5 self.iconView.tintMask.alpha = self.iconView.alpha @@ -202,7 +187,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent - private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)? + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool, deviceMetrics: DeviceMetrics)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var actions: ChatAvailableMessageActions? @@ -211,7 +196,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let canDeleteMessagesDisposable = MetaDisposable() - public var selectedMessages = Set() { + public var selectedMessages = Set() { didSet { if oldValue != self.selectedMessages { self.updateActions() @@ -288,8 +273,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { if self.selectedMessages.isEmpty { self.actions = nil - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } self.canDeleteMessagesDisposable.set(nil) } else if let context = self.context { @@ -297,8 +282,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { |> deliverOnMainQueue).startStrict(next: { [weak self] actions in if let strongSelf = self { strongSelf.actions = actions - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight: maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight: maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } } })) @@ -312,7 +297,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { } @objc private func deleteButtonPressed() { - self.interfaceInteraction?.deleteSelectedMessages() + self.interfaceInteraction?.deleteSelectedMessages(self.deleteButton) } @objc private func reportButtonPressed() { @@ -456,21 +441,20 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { } private func update(transition: ContainedViewLayoutTransition) { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } } - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { + self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) var leftInset = leftInset + 8.0 var rightInset = rightInset + 8.0 - if bottomInset <= 32.0 { - leftInset += 18.0 - rightInset += 18.0 - } + let compactBottomSideInset = self.compactBottomSideInset(bottomInset: bottomInset, deviceMetrics: deviceMetrics) + leftInset += compactBottomSideInset + rightInset += compactBottomSideInset let panelHeight = defaultHeight(metrics: metrics) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD index 70a8cb9400..cde8bb8e85 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/BUILD @@ -16,7 +16,6 @@ swift_library( "//submodules/TelegramUI/Components/ChatControllerInteraction", "//submodules/AccountContext", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/WallpaperBackgroundNode", "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/ContextUI", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift index 7e38d33ef3..3509bfad43 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageShareButton/Sources/ChatMessageShareButton.swift @@ -6,7 +6,6 @@ import TelegramPresentationData import ChatControllerInteraction import AccountContext import TelegramCore -import Postbox import WallpaperBackgroundNode import ChatMessageItemCommon import ContextUI @@ -107,7 +106,7 @@ public class ChatMessageShareButton: ASDisplayNode { self.morePressed?() } - public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false, isSummarize: Bool = false) -> CGSize { + public func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: EngineMessage, accountPeerId: EnginePeer.Id, disableComments: Bool = false, isSummarize: Bool = false) -> CGSize { var isReplies = false var isNavigate = false var replyCount = 0 @@ -176,7 +175,7 @@ public class ChatMessageShareButton: ASDisplayNode { updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else if isReplies { updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) - } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) { + } else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) { updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0) } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/BUILD index 85588cbecc..c238287b4f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift index 25836a51b2..be8ec426e9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStarsMediaInfoNode/Sources/ChatMessageStarsMediaInfoNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import TelegramCore import SwiftSignalKit @@ -178,7 +177,7 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode { public class Arguments { public let presentationData: ChatPresentationData public let context: AccountContext - public let message: Message + public let message: EngineMessage public let media: TelegramMediaPaidContent public let constrainedSize: CGSize public let animationCache: AnimationCache? @@ -187,7 +186,7 @@ public class ChatMessageStarsMediaInfoNode: ASDisplayNode { public init( presentationData: ChatPresentationData, context: AccountContext, - message: Message, + message: EngineMessage, media: TelegramMediaPaidContent, constrainedSize: CGSize, animationCache: AnimationCache?, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD index 93400be766..1c4c39b41e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index bbea729223..bcd759a779 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TextFormat @@ -75,7 +74,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { private var replyRecognizer: ChatSwipeToReplyRecognizer? private var currentSwipeAction: ChatControllerInteractionSwipeAction? - private var appliedForwardInfo: (Peer?, String?)? + private var appliedForwardInfo: (EngineRawPeer?, String?)? private var enableSynchronousImageApply: Bool = false @@ -229,7 +228,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } - if let item = strongSelf.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { + if let item = strongSelf.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(EngineMessage(item.message)) { if strongSelf.imageNode.frame.contains(point) { return .waitForDoubleTap } @@ -456,7 +455,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var textLayoutAndApply: (TextNodeLayout, () -> TextNode)? var isEmoji = false - if item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { + if item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(EngineMessage(item.message)) { let attributedText = NSAttributedString(string: item.message.text, font: item.presentationData.messageEmojiFont, textColor: .black) textLayoutAndApply = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width, height: 90.0), alignment: .natural)) @@ -643,7 +642,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) var isReplyThread = false if case .replyThread = item.chatLocation { @@ -657,7 +656,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), + layoutInput: .standalone(reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.StandaloneReactionSettings() : nil), constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -671,7 +670,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.message), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.message)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -703,11 +702,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } - var replyMessage: Message? + var replyMessage: EngineRawMessage? var replyForward: QuotedReplyMessageAttribute? var replyQuote: (quote: EngineMessageReplyQuote, isQuote: Bool)? var replyInnerSubject: EngineMessageReplyInnerSubject? - var replyStory: StoryId? + var replyStory: EngineStoryId? for attribute in item.message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { var inlineBotNameString: String? @@ -811,7 +810,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let contentHeight = max(imageSize.height, layoutConstants.image.minDimensions.height) - var forwardSource: Peer? + var forwardSource: EngineRawPeer? var forwardAuthorSignature: String? var forwardPsaType: String? @@ -839,7 +838,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } let availableForwardWidth = max(60.0, availableWidth + 6.0) - forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude)) + forwardInfoSizeApply = makeForwardInfoLayout(item.context, item.presentationData, item.presentationData.strings, .standalone, forwardSource.flatMap(EnginePeer.init), forwardAuthorSignature, forwardPsaType, nil, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude)) } var needsReplyBackground = false @@ -857,7 +856,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], item.message, baseWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, [:], EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { @@ -867,13 +866,13 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } var buttonDeclineValue: UInt8 = 0 - let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) + let buttonDecline = EngineMemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) var buttonApproveValue: UInt8 = 1 - let buttonApprove = MemoryBuffer(data: Data(bytes: &buttonApproveValue, count: 1)) + let buttonApprove = EngineMemoryBuffer(data: Data(bytes: &buttonApproveValue, count: 1)) var buttonSuggestChangesValue: UInt8 = 2 - let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) - - let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [ + let buttonSuggestChanges = EngineMemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) + + let customInfos: [EngineMemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [ buttonDecline: ChatMessageActionButtonsNode.CustomInfo( isEnabled: true, icon: .suggestedPostReject @@ -906,7 +905,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { ], flags: [], placeholder: nil - ), customInfos, item.message, baseWidth) + ), customInfos, EngineMessage(item.message), baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -917,7 +916,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } let reactions: ReactionsMessageAttribute - if shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { + if shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) { reactions = ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) } else { reactions = mergedMessageReactions(attributes: item.message.attributes, isTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId)) ?? ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [], recentPeers: [], topPeers: []) @@ -935,7 +934,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, reactions: reactions, - message: item.message, + message: EngineMessage(item.message), associatedData: item.associatedData, accountPeer: item.associatedData.accountPeer, isIncoming: item.message.effectivelyIncoming(item.context.account.peerId), @@ -1119,7 +1118,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf?.openQuickShare(node: node, gesture: gesture) } } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) let shareButtonFrame = CGRect(origin: CGPoint(x: baseShareButtonFrame.minX, y: baseShareButtonFrame.maxY - buttonSize.height), size: buttonSize) transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame) } else if let shareButtonNode = strongSelf.shareButtonNode { @@ -1512,7 +1511,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { case let .optionalAction(f): f() case let .openContextMenu(openContextMenu): - if canAddMessageReactions(message: item.message) { + if canAddMessageReactions(message: EngineMessage(item.message)) { item.controllerInteraction.updateMessageReaction(openContextMenu.tapMessage, .default, false, nil) } else { item.controllerInteraction.openMessageContextMenu(openContextMenu.tapMessage, openContextMenu.selectAll, self, openContextMenu.subFrame, nil, nil) @@ -1807,7 +1806,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) var isEmoji = false - if let item = self.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(item.message) { + if let item = self.item, item.presentationData.largeEmoji && messageIsEligibleForLargeEmoji(EngineMessage(item.message)) { isEmoji = true } @@ -2219,7 +2218,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { item.controllerInteraction.openMessageContextMenu(item.message, false, self, self.imageNode.frame, nil, nil) } - override public func targetForStoryTransition(id: StoryId) -> UIView? { + override public func targetForStoryTransition(id: EngineStoryId) -> UIView? { guard let item = self.item else { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD index 765105167f..75a659705d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift index 3e805b7cb6..4962128938 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStoryMentionContentNode/Sources/ChatMessageStoryMentionContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -95,7 +94,7 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { self.fetchDisposable.dispose() } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -110,9 +109,9 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { } } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false - var currentMedia: Media? + var currentMedia: EngineRawMedia? if let item = item { mediaLoop: for media in item.message.media { if let media = media as? TelegramMediaStory { @@ -159,7 +158,7 @@ public class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode { var story: Stories.Item? let storyMedia: TelegramMediaStory? = item.message.media.first(where: { $0 is TelegramMediaStory }) as? TelegramMediaStory - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let storyMedia, let storyItem = item.message.associatedStories[storyMedia.storyId], !storyItem.data.isEmpty, case let .item(storyValue) = storyItem.get(Stories.StoredItem.self) { selectedMedia = storyValue.media diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD index 703390463f..ea7eeb3c7e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 4d7b205375..afc62706e1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD index 5654796921..be9cffaee8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TextFormat", "//submodules/UrlEscaping", "//submodules/TelegramUniversalVideoContent", @@ -38,6 +37,8 @@ swift_library( "//submodules/TelegramUI/Components/TextLoadingEffect", "//submodules/TelegramUI/Components/ChatControllerInteraction", "//submodules/TelegramUI/Components/InteractiveTextComponent", + "//submodules/TelegramUI/Components/ShimmeringMask", + "//submodules/TelegramUI/Components/StreamingTextReveal:StreamingTextReveal", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index af00e36bc7..a1996db401 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TextFormat import UrlEscaping import TelegramUniversalVideoContent @@ -26,6 +25,8 @@ import ChatMessageItemCommon import TextLoadingEffect import ChatControllerInteraction import InteractiveTextComponent +import ShimmeringMask +import StreamingTextReveal private final class CachedChatMessageText { let text: String @@ -87,7 +88,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private let containerNode: ContainerNode private let textNode: InteractiveTextNodeWithEntities - + private let textAccessibilityOverlayNode: TextAccessibilityOverlayNode public var statusNode: ChatMessageDateAndStatusNode? private var linkHighlightingNode: LinkHighlightingNode? @@ -117,35 +118,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { private var displayContentsUnderSpoilers: (value: Bool, location: CGPoint?) = (false, nil) private var isSummaryApplied = false - - private final class TextRevealAnimationState { - let fromCount: Int - let toCount: Int - let startTimestamp: Double - let duration: Double - - init(fromCount: Int, toCount: Int, startTimestamp: Double, duration: Double) { - self.fromCount = fromCount - self.toCount = toCount - self.startTimestamp = startTimestamp - self.duration = duration - } - - func fraction(timestamp: Double) -> CGFloat { - var animationFraction = (timestamp - self.startTimestamp) / self.duration - animationFraction = max(0.0, min(1.0, animationFraction)) - return animationFraction - } - - func glyphCount(timestamp: Double) -> Int { - let animationFraction = self.fraction(timestamp: timestamp) - let glyphCount = (1.0 - animationFraction) * Double(self.fromCount) + animationFraction * Double(self.toCount) - return Int(glyphCount) - } - } - + private var textRevealLink: SharedDisplayLinkDriver.Link? - private var textRevealAnimationState: TextRevealAnimationState? + private var textRevealController: TextRevealController? private var relativeDateTimer: (timer: SwiftSignalKit.Timer, period: Int32)? @@ -197,7 +172,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else { self.expandedBlockIds.insert(blockId) } - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } self.textNode.textNode.requestDisplayContentsUnderSpoilers = { [weak self] location in guard let self else { @@ -246,14 +221,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let currentCachedChatMessageText = self.cachedChatMessageText let expandedBlockIds = self.expandedBlockIds let displayContentsUnderSpoilers = self.displayContentsUnderSpoilers - let currentMaxGlyphCount: Int? - if let textRevealAnimationState = self.textRevealAnimationState { - currentMaxGlyphCount = textRevealAnimationState.glyphCount(timestamp: CACurrentMediaTime()) - //print("currentMaxGlyphCount(\(textRevealAnimationState.fromCount) -> \(textRevealAnimationState.toCount)) fraction: \(textRevealAnimationState.fraction(timestamp: CACurrentMediaTime()))") - } else { - currentMaxGlyphCount = nil - } - let previousGlyphCount = self.textNode.textNode.getCharacterToGlyphMapping().count + let currentMaxGlyphCount: Int? = self.textRevealController?.currentGlyphCount + let previousGlyphCount = self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 return { item, layoutConstants, _, _, _, _ in let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) @@ -340,7 +309,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? var displayStatus = false @@ -655,7 +624,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let currentDict = updatedString.attributes(at: range.lowerBound, effectiveRange: nil) var updatedAttributes: [NSAttributedString.Key: Any] = currentDict - updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) + updatedAttributes[ChatTextInputAttributes.customEmoji] = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: item.message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile) let insertString = NSAttributedString(string: updatedString.attributedSubstring(from: range).string, attributes: updatedAttributes) updatedString.replaceCharacters(in: range, with: insertString) @@ -699,6 +668,15 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } + var hasDraft = false + if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + hasDraft = true + } + var hadDraft = false + if let previousItem, previousItem.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + hadDraft = true + } + let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) let (textLayout, textApply) = textLayout(InteractiveTextNodeLayoutArguments( attributedString: attributedText, @@ -712,25 +690,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { lineColor: messageTheme.accentControlColor, displayContentsUnderSpoilers: displayContentsUnderSpoilers.value, customTruncationToken: customTruncationToken, - expandedBlocks: expandedBlockIds + expandedBlocks: expandedBlockIds, + computeCharacterRects: true )) - var hasDraft = false - if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { - hasDraft = true - } - var hadDraft = false - if let previousItem, previousItem.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { - hadDraft = true - } - var maxGlyphCount = currentMaxGlyphCount if maxGlyphCount == nil && (hasDraft || hadDraft) { maxGlyphCount = previousGlyphCount } var clippedGlyphCountLayout: TextNodeLayout.LayoutInfo? if let maxGlyphCount { - clippedGlyphCountLayout = textLayout.layoutForGlyphCount(glyphCount: maxGlyphCount) + clippedGlyphCountLayout = textLayout.layoutForCharacterCount(characterCount: maxGlyphCount) } var statusSuggestedWidthAndContinue: (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode))? @@ -754,7 +724,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } let dateLayoutInput: ChatMessageDateAndStatusNode.LayoutInput - dateLayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)) + dateLayoutInput = .trailingContent(contentWidth: trailingWidthToMeasure, reactionSettings: ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions), preferAdditionalInset: false)) statusSuggestedWidthAndContinue = statusLayout(ChatMessageDateAndStatusNode.Arguments( context: item.context, @@ -777,14 +747,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread), hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) } var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size) - + var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) textFrame = textFrame.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: topInset) @@ -792,14 +762,13 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { if let clippedGlyphCountLayout { textFrame.size = clippedGlyphCountLayout.size - //print("currentMaxGlyphCount: \(currentMaxGlyphCount), size: \(textFrame.size.height)") textFrameWithoutInsets.size = CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom) } textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: topInset) var suggestedBoundingWidth: CGFloat = textFrameWithoutInsets.width - if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { + if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue, !hasDraft { suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0) } let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right @@ -811,10 +780,10 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets) boundingSize = textFrameWithoutInsets.size - if let statusSizeAndApply = statusSizeAndApply { + if let statusSizeAndApply = statusSizeAndApply, !hasDraft { boundingSize.height += statusSizeAndApply.0.height } - + boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right boundingSize.height += topInset + bottomInset @@ -828,12 +797,12 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var previousAnimateGlyphCount: Int? if hasDraft || hadDraft { - previousAnimateGlyphCount = strongSelf.textNode.textNode.getCharacterToGlyphMapping().count + previousAnimateGlyphCount = strongSelf.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 } strongSelf.textNode.textNode.displaysAsynchronously = !item.presentationData.isPreview - animation.animator.updateFrame(layer: strongSelf.containerNode.layer, frame: CGRect(origin: CGPoint(), size: boundingSize), completion: nil) - + animation.animator.updatePosition(layer: strongSelf.containerNode.layer, position: CGRect(origin: CGPoint(), size: boundingSize).center, completion: nil) + animation.animator.updateBounds(layer: strongSelf.containerNode.layer, bounds: CGRect(origin: CGPoint(), size: boundingSize), completion: nil) if let formattedDateUpdatePeriod { if strongSelf.relativeDateTimer?.period != formattedDateUpdatePeriod { @@ -841,7 +810,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.relativeDateTimer = nil } strongSelf.relativeDateTimer = (SwiftSignalKit.Timer(timeout: Double(formattedDateUpdatePeriod), repeat: true, completion: { [weak self] in - self?.requestFullUpdate?() + self?.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) }, queue: Queue.mainQueue()), formattedDateUpdatePeriod) strongSelf.relativeDateTimer?.timer.start() } else if let (timer, _) = strongSelf.relativeDateTimer { @@ -917,7 +886,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } ) )) - animation.animator.updateFrame(layer: strongSelf.textNode.textNode.layer, frame: realTextFrame, completion: nil) + animation.animator.updatePosition(layer: strongSelf.textNode.textNode.layer, position: realTextFrame.center, completion: nil) + animation.animator.updateBounds(layer: strongSelf.textNode.textNode.layer, bounds: CGRect(origin: CGPoint(), size: realTextFrame.size), completion: nil) + switch strongSelf.visibility { case .none: @@ -971,7 +942,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } statusNode.frame = statusFrame } else { - animation.animator.updateFrame(layer: statusNode.layer, frame: statusFrame, completion: nil) + animation.animator.updatePosition(layer: statusNode.layer, position: statusFrame.center, completion: nil) + animation.animator.updateBounds(layer: statusNode.layer, bounds: CGRect(origin: CGPoint(), size: statusFrame.size), completion: nil) } } else if let statusNode = strongSelf.statusNode { strongSelf.statusNode = nil @@ -1053,8 +1025,14 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { codeHighlightState.disposable.dispose() } - if previousAnimateGlyphCount != nil || strongSelf.textRevealAnimationState != nil || hadDraft { - strongSelf.updateTextRevealAnimation(previousGlyphCount: previousAnimateGlyphCount ?? 0) + if previousAnimateGlyphCount != nil || strongSelf.textRevealController != nil || hasDraft || hadDraft { + if strongSelf.textNode.textNode.revealCharacterCount == nil { + if hasDraft { + strongSelf.statusNode?.alpha = 0.0 + } + strongSelf.textNode.textNode.updateRevealCharacterCount(count: previousAnimateGlyphCount ?? 0, animated: false) + } + strongSelf.updateTextRevealAnimation(previousGlyphCount: previousAnimateGlyphCount ?? 0, hasDraft: hasDraft, hadDraft: hadDraft) } } }) @@ -1063,71 +1041,77 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } - private func updateTextRevealAnimation(previousGlyphCount: Int) { - var fromCount = previousGlyphCount - let toCount = self.textNode.textNode.getCharacterToGlyphMapping().count - let timestamp = CACurrentMediaTime() - if let textRevealAnimationState = self.textRevealAnimationState { - if textRevealAnimationState.toCount == toCount { - return - } - fromCount = textRevealAnimationState.glyphCount(timestamp: timestamp) + private func updateTextRevealAnimation(previousGlyphCount: Int, hasDraft: Bool, hadDraft: Bool) { + let toCount = self.textNode.textNode.cachedLayout?.attributedString?.length ?? 0 + let now = CACurrentMediaTime() + + if hasDraft, let controller = self.textRevealController, controller.isFinalizing { + self.textRevealController = nil + self.textRevealLink = nil } - if fromCount == toCount { - if self.textRevealAnimationState != nil { - self.textRevealAnimationState = nil - self.textRevealLink = nil - self.textNode.textNode.updateRevealGlyphCount(count: nil) - } + + if self.textRevealController == nil && (hasDraft || hadDraft) { + self.textRevealController = TextRevealController(initialRevealedCount: previousGlyphCount, initialLength: toCount) + } + + guard let controller = self.textRevealController else { return } - - var duration: Double = Double(toCount - fromCount) / 20.0 - duration = max(0.1, min(duration, 5.0)) - - self.textRevealAnimationState = TextRevealAnimationState( - fromCount: fromCount, - toCount: toCount, - startTimestamp: timestamp, - duration: duration - ) - if self.textRevealLink == nil, self.textRevealAnimationState != nil { - var lastLineUpdateTimestamp = timestamp + + if hasDraft { + controller.observeUpdate(latestLength: toCount, at: now) + } else if hadDraft { + controller.finalize(finalLength: toCount) + } + + if controller.isFinalizing && controller.revealedCount >= Double(controller.latestLength) { + self.textRevealController = nil + self.textRevealLink = nil + self.textNode.textNode.updateRevealCharacterCount(count: nil, animated: false) + return + } + + if self.textRevealLink == nil { self.textRevealLink = SharedDisplayLinkDriver.shared.add { [weak self] _ in guard let self else { return } - guard let textRevealAnimationState = self.textRevealAnimationState else { + guard let item = self.item else { + self.textRevealController = nil self.textRevealLink = nil return } - let timestamp = CACurrentMediaTime() - if textRevealAnimationState.fraction(timestamp: timestamp) >= 1.0 { - self.textRevealAnimationState = nil + guard let controller = self.textRevealController else { self.textRevealLink = nil - - self.textNode.textNode.updateRevealGlyphCount(count: nil) - self.requestFullUpdate?() + return + } + let now = CACurrentMediaTime() + let (revealedGlyphCount, isComplete) = controller.tick(now: now) + if isComplete { + self.textRevealController = nil + self.textRevealLink = nil + + self.textNode.textNode.updateRevealCharacterCount(count: nil, animated: false) + + if let statusNode = self.statusNode, !item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: statusNode, alpha: 1.0) + } + + self.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) } else { - let lineUpdateTimeout = timestamp - lastLineUpdateTimestamp - var requestUpdate = false - let glyphCount = textRevealAnimationState.glyphCount(timestamp: timestamp) - if let revealGlyphCount = self.textNode.textNode.revealGlyphCount, let cachedLayout = self.textNode.textNode.cachedLayout { - let previousLayout = cachedLayout.layoutForGlyphCount(glyphCount: revealGlyphCount) - let updatedLayout = cachedLayout.layoutForGlyphCount(glyphCount: glyphCount) - - if updatedLayout.size.height != previousLayout.size.height || abs(updatedLayout.size.width - previousLayout.size.width) > 8.0 { - if lineUpdateTimeout >= 0.0 { - lastLineUpdateTimestamp = timestamp + if let previousRevealGlyphCount = self.textNode.textNode.revealCharacterCount, previousRevealGlyphCount != revealedGlyphCount { + if let cachedLayout = self.textNode.textNode.cachedLayout { + if cachedLayout.sizeForCharacterCount(characterCount: previousRevealGlyphCount) != cachedLayout.sizeForCharacterCount(characterCount: revealedGlyphCount) { requestUpdate = true } + } else { + requestUpdate = true } + self.textNode.textNode.updateRevealCharacterCount(count: revealedGlyphCount, animated: true) } - self.textNode.textNode.updateRevealGlyphCount(count: glyphCount) - if requestUpdate { - self.requestFullUpdate?() + self.requestFullUpdate?(ControlledTransition(duration: 0.15, curve: .easeInOut, interactive: false)) } } } @@ -1136,17 +1120,23 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { override public func animateInsertion(_ currentTimestamp: Double, duration: Double) { self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2) + } } override public func animateAdded(_ currentTimestamp: Double, duration: Double) { self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.statusNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: 0.0, to: statusNode.alpha, duration: 0.2) + } } override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.textNode.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.statusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + if let statusNode = self.statusNode, statusNode.alpha != 0.0 { + statusNode.layer.animateAlpha(from: statusNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + } } override public func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { @@ -1401,7 +1391,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } } - override public func updateSearchTextHighlightState(text: String?, messages: [MessageIndex]?) { + override public func updateSearchTextHighlightState(text: String?, messages: [EngineMessage.Index]?) { guard let item = self.item else { return } @@ -1721,7 +1711,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { } self.displayContentsUnderSpoilers = (value, location) if let item = self.item { - item.controllerInteraction.requestMessageUpdate(item.message.id, false) + item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } @@ -1802,7 +1792,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { guard let textSelectionNode = self.textSelectionNode else { return nil } - guard let range = customRange ?? textSelectionNode.getSelection() else { + guard let rawRange = customRange ?? textSelectionNode.getSelection() else { return nil } guard let item = self.item else { @@ -1812,6 +1802,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return nil } + func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange { + let location = min(max(range.location, 0), length) + let upperBound = min(max(location, range.location + range.length), length) + return NSRange(location: location, length: upperBound - location) + } + + let range = normalizedSelectionRange(rawRange, length: string.length) + guard range.length > 0 else { + return nil + } + let nsString = string.string as NSString let substring = nsString.substring(with: range) let offset = range.location diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD index 6b225f349c..a5f51cc610 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift index 7f82b824f6..2bfce1647c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageThreadInfoNode/Sources/ChatMessageThreadInfoNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import TelegramCore import SwiftSignalKit @@ -187,7 +186,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { public let type: ChatMessageThreadInfoType public let peer: EnginePeer? public let threadId: Int64? - public let parentMessage: Message + public let parentMessage: EngineMessage public let constrainedSize: CGSize public let animationCache: AnimationCache? public let animationRenderer: MultiAnimationRenderer? @@ -200,7 +199,7 @@ public class ChatMessageThreadInfoNode: ASDisplayNode { type: ChatMessageThreadInfoType, peer: EnginePeer?, threadId: Int64?, - parentMessage: Message, + parentMessage: EngineMessage, constrainedSize: CGSize, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/BUILD index 0a6225a1f7..95017bfc93 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TextFormat", "//submodules/UrlEscaping", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index 92bd0fa3b8..566a097725 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TextFormat import UrlEscaping import SwiftSignalKit @@ -407,7 +406,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { let separatorNode: ASDisplayNode var context: AccountContext? - var message: Message? + var message: EngineRawMessage? var option: TelegramMediaTodo.Item? var pressed: (() -> Void)? @@ -727,7 +726,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { return ChatMessageBubbleContentTapAction(content: .none) } - static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) { + static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: EngineRawMessage, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) { let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode) let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode) @@ -769,10 +768,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { linkColor: messageTheme.linkTextColor, baseFont: presentationData.messageFont, linkFont: presentationData.messageFont, - boldFont: presentationData.messageFont, - italicFont: presentationData.messageFont, - boldItalicFont: presentationData.messageFont, - fixedFont: presentationData.messageFont, + boldFont: presentationData.messageBoldFont, + italicFont: presentationData.messageItalicFont, + boldItalicFont: presentationData.messageBoldItalicFont, + fixedFont: presentationData.messageFixedFont, blockQuoteFont: presentationData.messageFont, underlineLinks: underlineLinks, message: message @@ -1087,7 +1086,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { let makeViewResultsTextLayout = TextNode.asyncLayout(self.buttonViewResultsTextNode) let statusLayout = self.statusNode.asyncLayout() - var previousOptionNodeLayouts: [Int32: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ poll: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))] = [:] + var previousOptionNodeLayouts: [Int32: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: EngineRawMessage, _ poll: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))] = [:] for optionNode in self.optionNodes { if let option = optionNode.option { previousOptionNodeLayouts[option.id] = ChatMessageTodoItemNode.asyncLayout(optionNode) @@ -1138,7 +1137,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { } else { dateFormat = .regular } - let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) + let dateText = stringForMessageTimestampStatus(accountPeerId: item.context.account.peerId, message: EngineMessage(item.message), dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: dateFormat, associatedData: item.associatedData) let statusType: ChatMessageDateAndStatusType? if case .customChatContents = item.associatedData.subject { @@ -1177,7 +1176,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { impressionCount: viewCount, dateText: dateText, type: statusType, - layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: item.message, isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), + layoutInput: .trailingContent(contentWidth: 1000.0, reactionSettings: shouldDisplayInlineDateReactions(message: EngineMessage(item.message), isPremium: item.associatedData.isPremium, forceInline: item.associatedData.forceInlineReactions) ? ChatMessageDateAndStatusNode.TrailingReactionSettings(displayInline: true, preferAdditionalInset: false) : nil), constrainedSize: textConstrainedSize, availableReactions: item.associatedData.availableReactions, savedMessageTags: item.associatedData.savedMessageTags, @@ -1191,7 +1190,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { starsCount: starsCount, isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread, hasAutoremove: item.message.isSelfExpiring, - canViewReactionList: canViewMessageReactionList(message: item.topMessage), + canViewReactionList: canViewMessageReactionList(message: EngineMessage(item.topMessage)), animationCache: item.controllerInteraction.presentationContext.animationCache, animationRenderer: item.controllerInteraction.presentationContext.animationRenderer )) @@ -1293,7 +1292,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode { for i in 0 ..< todo.items.count { let todoItem = todo.items[i] - let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ item: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) + let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: EngineRawMessage, _ todo: TelegramMediaTodo, _ item: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) if let previous = previousOptionNodeLayouts[todoItem.id] { makeLayout = previous } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/BUILD index a031cda0e1..d932de0377 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/Display", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift index 91b51a70a9..232f625e96 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnlockMediaNode/Sources/ChatMessageUnlockMediaNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import Display import TelegramCore import SwiftSignalKit @@ -24,7 +23,7 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { public let strings: PresentationStrings public let context: AccountContext public let controllerInteraction: ChatControllerInteraction - public let message: Message + public let message: EngineMessage public let media: TelegramMediaPaidContent public let constrainedSize: CGSize public let animationCache: AnimationCache? @@ -35,7 +34,7 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { strings: PresentationStrings, context: AccountContext, controllerInteraction: ChatControllerInteraction, - message: Message, + message: EngineMessage, media: TelegramMediaPaidContent, constrainedSize: CGSize, animationCache: AnimationCache?, @@ -150,7 +149,7 @@ public class ChatMessageUnlockMediaNode: ASDisplayNode { } node.pressed = { - let _ = arguments.controllerInteraction.openMessage(arguments.message, OpenMessageParams(mode: .default)) + let _ = arguments.controllerInteraction.openMessage(arguments.message._asMessage(), OpenMessageParams(mode: .default)) } node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD index 87d31d072a..0b47a88771 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift index eb8e110712..588d57296b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageUnsupportedBubbleContentNode/Sources/ChatMessageUnsupportedBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD index 98d37bb582..ecd4515127 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift index ec9e6380ea..6aa35297cf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -135,7 +134,7 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode item.context.account.pendingPeerMediaUploadManager.cancel(peerId: item.message.id.peerId) } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id == messageId { return (self.imageNode, self.imageNode.bounds, { [weak self] in guard let strongSelf = self else { @@ -150,9 +149,9 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode } } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { var mediaHidden = false - var currentMedia: Media? + var currentMedia: EngineRawMedia? if let item = item { mediaLoop: for media in item.message.media { if let media = media as? TelegramMediaAction { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD index 61ab438674..6770d0c4a0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index 12e5aa8f2e..f4f93efff9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import Display import AsyncDisplayKit import SwiftSignalKit @@ -152,7 +151,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } self.contentNode.requestUpdateLayout = { [weak self] in if let strongSelf = self, let item = strongSelf.item { - let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false) + let _ = item.controllerInteraction.requestMessageUpdate(item.message.id, false, nil) } } self.contentNode.defaultContentAction = { [weak self] in @@ -225,7 +224,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent var text: String? var entities: [MessageTextEntity]? var titleBadge: String? - var mediaAndFlags: ([Media], ChatMessageAttachedContentNodeMediaFlags)? + var mediaAndFlags: ([EngineRawMedia], ChatMessageAttachedContentNodeMediaFlags)? var badge: String? var actionIcon: ChatMessageAttachedContentActionIcon? @@ -258,7 +257,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent entities = generateTextEntities(textValue, enabledTypes: entityTypes) } - var mainMedia: Media? + var mainMedia: EngineRawMedia? var automaticPlayback = false @@ -270,19 +269,19 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent if case .full = automaticDownload { automaticPlayback = true } else { - automaticPlayback = item.context.account.postbox.mediaBox.completedResourcePath(file.resource) != nil + automaticPlayback = item.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.resource.id)) != nil } } switch type { - case .instagram, .twitter: - if automaticPlayback { - mainMedia = webpage.story ?? webpage.file ?? webpage.image - } else { - mainMedia = webpage.story ?? webpage.image ?? webpage.file - } - default: + case .instagram, .twitter: + if automaticPlayback { mainMedia = webpage.story ?? webpage.file ?? webpage.image + } else { + mainMedia = webpage.story ?? webpage.image ?? webpage.file + } + default: + mainMedia = webpage.story ?? webpage.file ?? webpage.image } let themeMimeType = "application/x-tgtheme-ios" @@ -522,6 +521,16 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent actionTitle = item.presentationData.strings.Chat_ContactChannel case "telegram_newbot": actionTitle = item.presentationData.strings.Chat_CreateBotLink + case "telegram_aicomposetone": + actionTitle = "VIEW STYLE" + + for attribute in webpage.attributes { + if case let .aiTextStyle(aiTextStyle) = attribute { + if let file = item.message.associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: aiTextStyle.emojiFileId)] { + mediaAndFlags = ([file], [.preferMediaInline]) + } + } + } default: break } @@ -734,13 +743,13 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent return ChatMessageBubbleContentTapAction(content: .none) } - override public func updateHiddenMedia(_ media: [Media]?) -> Bool { + override public func updateHiddenMedia(_ media: [EngineRawMedia]?) -> Bool { if let media = media { var updatedMedia = media if let current = self.webPage, case let .Loaded(content) = current.content { for item in media { if let webpage = item as? TelegramMediaWebpage, webpage.id == current.id { - var mediaList: [Media] = [webpage] + var mediaList: [EngineRawMedia] = [webpage] if let image = content.image { mediaList.append(image) } @@ -752,7 +761,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } updatedMedia = mediaList } else if let id = item.id, content.file?.id == id || content.image?.id == id { - var mediaList: [Media] = [current] + var mediaList: [EngineRawMedia] = [current] if let image = content.image { mediaList.append(image) } @@ -772,7 +781,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } - override public func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + override public func transitionNode(messageId: EngineMessage.Id, media: EngineRawMedia, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { if self.item?.message.id != messageId { return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/BUILD index ee2fec7f98..2f4bd201dd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift index 9870951c4e..b821e104da 100644 --- a/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatNewThreadInfoItem/Sources/ChatNewThreadInfoItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TextFormat @@ -80,7 +79,7 @@ public final class ChatNewThreadInfoItemNode: ListViewItemNode, ASGestureRecogni public let offsetContainer: ASDisplayNode public let titleNode: TextNode public let subtitleNode: TextNode - let arrowView: UIImageView + var arrowView: UIImageView? let iconBackground: SimpleLayer var icon = ComponentView() @@ -106,8 +105,6 @@ public final class ChatNewThreadInfoItemNode: ListViewItemNode, ASGestureRecogni self.subtitleNode.isUserInteractionEnabled = false self.subtitleNode.displaysAsynchronously = false - self.arrowView = UIImageView() - super.init(layerBacked: false, rotated: true) self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) @@ -231,8 +228,14 @@ public final class ChatNewThreadInfoItemNode: ListViewItemNode, ASGestureRecogni if strongSelf.iconBackground.superlayer == nil { strongSelf.offsetContainer.layer.addSublayer(strongSelf.iconBackground) } - if strongSelf.arrowView.superview == nil { - strongSelf.offsetContainer.view.addSubview(strongSelf.arrowView) + + let arrowView: UIImageView + if let current = self?.arrowView { + arrowView = current + } else { + arrowView = UIImageView() + strongSelf.arrowView = arrowView + strongSelf.offsetContainer.view.addSubview(arrowView) } let iconComponent = AnyComponent(BundleIconComponent( @@ -265,15 +268,15 @@ public final class ChatNewThreadInfoItemNode: ListViewItemNode, ASGestureRecogni contentOriginY += subtitleLayout.size.height contentOriginY += 20.0 - if strongSelf.arrowView.image == nil { - strongSelf.arrowView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: .white)?.withRenderingMode(.alwaysTemplate) + if arrowView.image == nil { + arrowView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/DownButton"), color: .white)?.withRenderingMode(.alwaysTemplate) } - strongSelf.arrowView.tintColor = primaryTextColor.withMultipliedAlpha(0.5) - if let image = strongSelf.arrowView.image { + arrowView.tintColor = primaryTextColor.withMultipliedAlpha(0.5) + if let image = arrowView.image { let scaleFactor: CGFloat = 0.8 let imageSize = CGSize(width: floor(image.size.width * scaleFactor), height: floor(image.size.height * scaleFactor)) let arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + floor((backgroundSize.width - imageSize.width) / 2.0), y: backgroundFrame.minY + backgroundFrame.height - 8.0 - imageSize.height), size: imageSize) - strongSelf.arrowView.frame = arrowFrame + arrowView.frame = arrowFrame } if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD index b8e4d8a129..f489c8f76f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/AvatarNode", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift index d522989b4d..58ab8e009c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift @@ -3,7 +3,6 @@ import ComponentFlow import Display import AsyncDisplayKit import TelegramCore -import Postbox import AccountContext import AvatarNode import TextFormat diff --git a/submodules/TelegramUI/Components/Chat/ChatPanelsComponent/BUILD b/submodules/TelegramUI/Components/Chat/ChatPanelsComponent/BUILD index 49c5e938e3..c3e82286e8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatPanelsComponent/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatPanelsComponent/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/Display", "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD index 393edc5f09..a322282d44 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/BUILD @@ -16,11 +16,11 @@ swift_library( "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", - "//submodules/SolidRoundedButtonNode", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", "//submodules/TelegramNotices", - "//submodules/PresentationDataUtils", "//submodules/AnimationUI", "//submodules/MergeLists", "//submodules/MediaResources", @@ -45,6 +45,7 @@ swift_library( "//submodules/AnimatedCountLabelNode", "//submodules/HexColor", "//submodules/QrCodeUI", + "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/Components/BundleIconComponent", "//submodules/Components/SheetComponent", diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift index 3f0412ae82..b32cf1027d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift @@ -7,11 +7,9 @@ import Postbox import TelegramCore import SwiftSignalKit import AccountContext -import SolidRoundedButtonNode import TelegramPresentationData import TelegramUIPreferences import TelegramNotices -import PresentationDataUtils import AnimationUI import MergeLists import MediaResources @@ -36,6 +34,8 @@ import AnimatedCountLabelNode import HexColor import QrCodeUI import ComponentFlow +import ButtonComponent +import ComponentDisplayAdapters import GlassBarButtonComponent import SheetComponent import BundleIconComponent @@ -245,7 +245,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if let image = cachedBorderImages[key] { return image } else { - let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let image = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -271,7 +271,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec context.setLineWidth(lineWidth) context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) } - })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 16) cachedBorderImages[key] = image return image } @@ -314,7 +314,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.imageNode = TransformImageNode() self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0)) self.imageNode.isLayerBacked = true - self.imageNode.cornerRadius = 8.0 + //self.imageNode.cornerRadius = 16.0 self.imageNode.clipsToBounds = true self.overlayNode = ASImageNode() @@ -488,7 +488,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { let imageSize = CGSize(width: 82.0, height: 108.0) strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 6.0), size: imageSize) - let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) + let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) applyLayout() strongSelf.overlayNode.frame = strongSelf.imageNode.frame.insetBy(dx: -1.0, dy: -1.0) @@ -511,7 +511,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { } strongSelf.animatedStickerNode = animatedStickerNode strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) @@ -519,7 +519,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { animatedStickerNode.autoplay = true animatedStickerNode.visibility = strongSelf.visibilityStatus - strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).startStrict()) + strongSelf.stickerFetchedDisposable.set(item.context.engine.resources.fetch(reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource), userLocation: .other, userContentType: .sticker).startStrict()) let thumbnailDimensions = PixelDimensions(width: 512, height: 512) strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) @@ -571,7 +571,7 @@ public final class ChatQrCodeScreenImpl: ViewController, ChatQrCodeScreen { public static let themeCrossfadeDelay: Double = 0.05 public enum Subject { - case peer(peer: Peer, threadId: Int64?, temporary: Bool) + case peer(peer: EnginePeer, threadId: Int64?, temporary: Bool) case messages([Message]) public var fileName: String { @@ -580,7 +580,7 @@ public final class ChatQrCodeScreenImpl: ViewController, ChatQrCodeScreen { var result: String if let addressName = peer.addressName, !addressName.isEmpty { result = "t_me-\(peer.addressName ?? "")" - } else if let peer = peer as? TelegramUser { + } else if case let .user(peer) = peer { result = "t_me-\(peer.phone ?? "")" } else { result = "t_me-\(Int32.random(in: 0 ..< Int32.max))" @@ -770,8 +770,11 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg private let switchThemeButtonNode: HighlightTrackingButtonNode private let animationContainerNode: ASDisplayNode private var animationNode: AnimationNode - private let doneButton: SolidRoundedButtonNode - private let scanButton: SolidRoundedButtonNode + private let doneButton = ComponentView() + private let scanButton = ComponentView() + private let doneButtonTitle: String + private let scanButtonTitle: String + private let fileName: String private let listNode: ListView private var entries: [ThemeSettingsThemeEntry]? @@ -811,11 +814,20 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.context = context self.controller = controller self.presentationData = presentationData + self.fileName = controller.subject.fileName + self.scanButtonTitle = presentationData.strings.PeerInfo_QRCode_Scan + switch controller.subject { + case .peer: + self.doneButtonTitle = presentationData.strings.InviteLink_QRCode_Share + case .messages: + self.doneButtonTitle = presentationData.strings.Share_ShareMessage + } self.wrappingScrollNode = ASScrollNode() self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.scrollNodeContentNode = ASDisplayNode() self.scrollNodeContentNode.clipsToBounds = true @@ -870,19 +882,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false - - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), glass: true, height: 52.0, cornerRadius: 26.0) - switch controller.subject { - case .peer: - self.doneButton.title = self.presentationData.strings.InviteLink_QRCode_Share - case .messages: - self.doneButton.title = self.presentationData.strings.Share_ShareMessage - } - - self.scanButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor), font: .regular, height: 42.0, cornerRadius: 0.0) - self.scanButton.title = presentationData.strings.PeerInfo_QRCode_Scan - self.scanButton.icon = UIImage(bundleImageName: "Settings/ScanQr") - + self.listNode = ListViewImpl() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) @@ -903,8 +903,6 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.contentContainerNode.addSubnode(self.titleNode) self.contentContainerNode.addSubnode(self.segmentedNode) - self.contentContainerNode.addSubnode(self.doneButton) - self.contentContainerNode.addSubnode(self.scanButton) self.topContentContainerNode.addSubnode(self.animationContainerNode) self.animationContainerNode.addSubnode(self.animationNode) @@ -927,74 +925,6 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg } } - let fileName = controller.subject.fileName - self.doneButton.pressed = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.doneButton.isUserInteractionEnabled = false - - if strongSelf.segmentedNode.selectedIndex == 0 { - strongSelf.contentNode.generateVideo { [weak self] url in - if let strongSelf = self { - let tempFilePath = NSTemporaryDirectory() + "\(fileName).mp4" - try? FileManager.default.removeItem(atPath: tempFilePath) - let tempFileUrl = URL(fileURLWithPath: tempFilePath) - try? FileManager.default.moveItem(at: url, to: tempFileUrl) - - let activityController = UIActivityViewController(activityItems: [tempFileUrl], applicationActivities: [ShareToInstagramActivity(context: strongSelf.context)]) - activityController.completionWithItemsHandler = { [weak self] _, finished, _, _ in - if let strongSelf = self { - if finished { - strongSelf.completion?(strongSelf.selectedEmoticon) - } else { - strongSelf.doneButton.isUserInteractionEnabled = true - } - } - } - if let window = strongSelf.view.window { - activityController.popoverPresentationController?.sourceView = window - activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) - } - context.sharedContext.applicationBindings.presentNativeController(activityController) - } - } - } else { - strongSelf.contentNode.generateImage { [weak self] image in - if let strongSelf = self, let image = image, let jpgData = image.jpegData(compressionQuality: 0.9) { - let tempFilePath = NSTemporaryDirectory() + "\(fileName).jpg" - try? FileManager.default.removeItem(atPath: tempFilePath) - let tempFileUrl = URL(fileURLWithPath: tempFilePath) - try? jpgData.write(to: tempFileUrl) - - let activityController = UIActivityViewController(activityItems: [tempFileUrl], applicationActivities: [ShareToInstagramActivity(context: strongSelf.context)]) - activityController.completionWithItemsHandler = { [weak self] _, finished, _, _ in - if let strongSelf = self { - if finished { - strongSelf.completion?(strongSelf.selectedEmoticon) - } else { - strongSelf.doneButton.isUserInteractionEnabled = true - } - } - } - if let window = strongSelf.view.window { - activityController.popoverPresentationController?.sourceView = window - activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) - } - context.sharedContext.applicationBindings.presentNativeController(activityController) - } - } - } - } - - self.scanButton.pressed = { [weak self] in - guard let self else { - return - } - let controller = QrCodeScanScreen(context: self.context, subject: .peer) - self.controller?.push(controller) - } - let animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false) |> map { animatedEmoji -> [String: [StickerPackItem]] in var animatedEmojiStickers: [String: [StickerPackItem]] = [:] @@ -1259,8 +1189,6 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg } self.cancelButtonNode.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) - self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - self.scanButton.updateTheme(SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor)) let previousIconColors = iconColors(theme: previousTheme) let newIconColors = iconColors(theme: self.presentationData.theme) @@ -1299,6 +1227,67 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.cancel?() } + private func doneButtonPressed() { + self.doneButton.view?.isUserInteractionEnabled = false + + if self.segmentedNode.selectedIndex == 0 { + self.contentNode.generateVideo { [weak self] url in + if let strongSelf = self { + let tempFilePath = NSTemporaryDirectory() + "\(strongSelf.fileName).mp4" + try? FileManager.default.removeItem(atPath: tempFilePath) + let tempFileUrl = URL(fileURLWithPath: tempFilePath) + try? FileManager.default.moveItem(at: url, to: tempFileUrl) + + let activityController = UIActivityViewController(activityItems: [tempFileUrl], applicationActivities: [ShareToInstagramActivity(context: strongSelf.context)]) + activityController.completionWithItemsHandler = { [weak self] _, finished, _, _ in + if let strongSelf = self { + if finished { + strongSelf.completion?(strongSelf.selectedEmoticon) + } else { + strongSelf.doneButton.view?.isUserInteractionEnabled = true + } + } + } + if let window = strongSelf.view.window { + activityController.popoverPresentationController?.sourceView = window + activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) + } + strongSelf.context.sharedContext.applicationBindings.presentNativeController(activityController) + } + } + } else { + self.contentNode.generateImage { [weak self] image in + if let strongSelf = self, let image = image, let jpgData = image.jpegData(compressionQuality: 0.9) { + let tempFilePath = NSTemporaryDirectory() + "\(strongSelf.fileName).jpg" + try? FileManager.default.removeItem(atPath: tempFilePath) + let tempFileUrl = URL(fileURLWithPath: tempFilePath) + try? jpgData.write(to: tempFileUrl) + + let activityController = UIActivityViewController(activityItems: [tempFileUrl], applicationActivities: [ShareToInstagramActivity(context: strongSelf.context)]) + activityController.completionWithItemsHandler = { [weak self] _, finished, _, _ in + if let strongSelf = self { + if finished { + strongSelf.completion?(strongSelf.selectedEmoticon) + } else { + strongSelf.doneButton.view?.isUserInteractionEnabled = true + } + } + } + if let window = strongSelf.view.window { + activityController.popoverPresentationController?.sourceView = window + activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) + } + strongSelf.context.sharedContext.applicationBindings.presentNativeController(activityController) + } + } + } + } + + private func scanButtonPressed() { + let controller = QrCodeScanScreen(context: self.context, subject: .peer) + self.controller?.push(controller) + } + @objc private func switchThemePressed() { self.switchThemeButtonNode.isUserInteractionEnabled = false Queue.mainQueue().after(0.5) { @@ -1352,11 +1341,12 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg }) } -// Queue.mainQueue().after(ChatQrCodeScreenImpl.themeCrossfadeDelay) { -// let previousColor = self.contentBackgroundNode.backgroundColor ?? .clear -// self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor -// self.contentBackgroundNode.layer.animate(from: previousColor.cgColor, to: (self.contentBackgroundNode.backgroundColor ?? .clear).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: ChatQrCodeScreenImpl.themeCrossfadeDuration) -// } + if let snapshotView = self.contentBackgroundView.snapshotView(afterScreenUpdates: false) { + self.contentBackgroundView.superview?.insertSubview(snapshotView, aboveSubview: self.contentBackgroundView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatQrCodeScreenImpl.themeCrossfadeDuration, delay: ChatQrCodeScreenImpl.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) { snapshotView.frame = self.contentContainerNode.frame @@ -1435,13 +1425,13 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = (layout, navigationBarHeight) - var insets = layout.insets(options: [.statusBar, .input]) let cleanInsets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - let bottomInset: CGFloat = 10.0 + cleanInsets.bottom let titleHeight: CGFloat = 54.0 - let contentHeight = titleHeight + bottomInset + 188.0 + 52.0 + let buttonHeight: CGFloat = 52.0 + let buttonSpacing: CGFloat = 8.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: cleanInsets.bottom, innerDiameter: buttonHeight, sideInset: 30.0) + let contentHeight = titleHeight + 10.0 + 188.0 + buttonHeight + buttonSpacing + buttonInsets.bottom let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) @@ -1459,14 +1449,14 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.contentBackgroundView.update(size: contentFrame.size, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, topCornerRadius: 38.0, bottomCornerRadius: layout.deviceMetrics.screenCornerRadius - 2.0, transition: .immediate) transition.updateFrame(view: self.contentBackgroundView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - let barButtonSize = CGSize(width: 40.0, height: 40.0) + let barButtonSize = CGSize(width: 44.0, height: 44.0) let cancelButtonSize = self.cancelButton.update( transition: .immediate, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", @@ -1493,9 +1483,9 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg transition: .immediate, component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "switchTheme", component: AnyComponent( Rectangle(color: .clear) )), @@ -1517,7 +1507,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg } transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.scrollNodeContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height + 2000.0))) + transition.updateFrame(node: self.scrollNodeContentNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height))) let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight)) let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 36.0 - titleSize.height / 2.0), size: titleSize) @@ -1536,12 +1526,83 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize) transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) - let buttonInset: CGFloat = 30.0 - let scanButtonHeight = self.scanButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.scanButton, frame: CGRect(x: buttonInset, y: contentHeight - scanButtonHeight - insets.bottom, width: contentFrame.width, height: scanButtonHeight)) + let buttonTransition = ComponentTransition(transition) + let buttonWidth = contentFrame.width - buttonInsets.left - buttonInsets.right + let primaryBackgroundColor = self.presentationData.theme.list.itemCheckColors.fillColor + let primaryForegroundColor = self.presentationData.theme.list.itemCheckColors.foregroundColor + let doneButtonSize = self.doneButton.update( + transition: buttonTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: primaryBackgroundColor, + foreground: primaryForegroundColor, + pressedColor: primaryBackgroundColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + Text(text: self.doneButtonTitle, font: Font.semibold(17.0), color: primaryForegroundColor) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + self?.doneButtonPressed() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + + let secondaryForegroundColor = self.presentationData.theme.list.itemCheckColors.fillColor + let scanButtonContents: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: "icon", component: AnyComponent( + BundleIconComponent(name: "Settings/ScanQr", tintColor: secondaryForegroundColor) + )), + AnyComponentWithIdentity(id: "text", component: AnyComponent( + Text(text: self.scanButtonTitle, font: Font.semibold(17.0), color: secondaryForegroundColor) + )) + ] + let scanButtonSize = self.scanButton.update( + transition: buttonTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: secondaryForegroundColor.withMultipliedAlpha(0.1), + foreground: secondaryForegroundColor, + pressedColor: secondaryForegroundColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + HStack(scanButtonContents, spacing: 6.0) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + self?.scanButtonPressed() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - scanButtonHeight - 10.0 - insets.bottom, width: contentFrame.width, height: doneButtonHeight)) + let scanButtonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight - scanButtonSize.height - buttonInsets.bottom), size: scanButtonSize) + let doneButtonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: scanButtonFrame.minY - buttonSpacing - doneButtonSize.height), size: doneButtonSize) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.contentContainerNode.view.addSubview(doneButtonView) + } + doneButtonView.isAccessibilityElement = true + doneButtonView.accessibilityLabel = self.doneButtonTitle + doneButtonView.accessibilityTraits = [.button] + transition.updateFrame(view: doneButtonView, frame: doneButtonFrame) + } + if let scanButtonView = self.scanButton.view { + if scanButtonView.superview == nil { + self.contentContainerNode.view.addSubview(scanButtonView) + } + scanButtonView.isAccessibilityElement = true + scanButtonView.accessibilityLabel = self.scanButtonTitle + scanButtonView.accessibilityTraits = [.button] + transition.updateFrame(view: scanButtonView, frame: scanButtonFrame) + } transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) @@ -1576,7 +1637,7 @@ private protocol ContentNode: ASDisplayNode { private class QrContentNode: ASDisplayNode, ContentNode { private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer private let threadId: Int64? private let isStatic: Bool private let temporary: Bool @@ -1618,7 +1679,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { private var tokenUpdated = false var requestNextToken: () -> Void = {} - init(context: AccountContext, peer: Peer, threadId: Int64?, isStatic: Bool = false, temporary: Bool) { + init(context: AccountContext, peer: EnginePeer, threadId: Int64?, isStatic: Bool = false, temporary: Bool) { self.context = context self.peer = peer self.threadId = threadId @@ -1706,7 +1767,7 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.avatarNode = ImageNode() self.avatarNode.displaysAsynchronously = false - self.avatarNode.setSignal(peerAvatarCompleteImage(account: context.account, peer: EnginePeer(peer), size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)) + self.avatarNode.setSignal(peerAvatarCompleteImage(account: context.account, peer: peer, size: CGSize(width: 180.0, height: 180.0), font: avatarPlaceholderFont(size: 78.0), fullSize: true)) super.init() @@ -1739,9 +1800,9 @@ private class QrContentNode: ASDisplayNode, ContentNode { var codeLink: String if let addressName = peer.addressName, !addressName.isEmpty { codeLink = "https://t.me/\(peer.addressName ?? "")" - } else if let peer = peer as? TelegramUser { + } else if case let .user(peer) = peer { codeLink = "https://t.me/+\(peer.phone ?? "")" - } else if let _ = peer as? TelegramChannel { + } else if case .channel = peer { codeLink = "https://t.me/c/\(peer.id.id._internalGetInt64Value())" } else { codeLink = "" @@ -2502,9 +2563,9 @@ private enum RenderVideoResult { } private func renderVideo(context: AccountContext, backgroundImage: UIImage, userLocation: MediaResourceUserLocation, media: TelegramMediaFile, videoFrame: CGRect, completion: @escaping (URL?) -> Void) { - let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: userLocation, mediaReference: AnyMediaReference.standalone(media: media)) + let _ = (fetchMediaData(context: context, userLocation: userLocation, mediaReference: AnyMediaReference.standalone(media: media)) |> deliverOnMainQueue).startStandalone(next: { value, isImage in - guard case let .data(data) = value, data.complete else { + guard case let .data(data) = value, data.isComplete else { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift index 4bbf1b3ca0..694415b061 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRankInfoScreen/Sources/ChatRankInfoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -233,7 +232,7 @@ private final class ChatRankInfoSheetContent: CombinedComponent { let listItemParams = ListViewItemLayoutParams(width: previewWidth, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) - let emptyUser = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value(0)), accessHash: nil, firstName: "A", lastName: "", username: "", phone: "", photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + let emptyUser = TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: ._internalFromInt64Value(0)), accessHash: nil, firstName: "A", lastName: "", username: "", phone: "", photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) let memberMessageItem = RankChatPreviewItem.MessageItem( peer: .user(emptyUser), diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 21f7c80be9..79db3fa7b9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramBaseController @@ -20,8 +19,8 @@ public final class ChatRecentActionsController: TelegramBaseController { } private let context: AccountContext - private let peer: Peer - private let initialAdminPeerId: PeerId? + private let peer: EnginePeer + private let initialAdminPeerId: EnginePeer.Id? let starsState: StarsRevenueStats? private var presentationData: PresentationData @@ -39,7 +38,7 @@ public final class ChatRecentActionsController: TelegramBaseController { private var adminsDisposable: Disposable? - public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) { + public init(context: AccountContext, peer: EnginePeer, adminPeerId: EnginePeer.Id?, starsState: StarsRevenueStats?) { self.context = context self.peer = peer self.initialAdminPeerId = adminPeerId @@ -59,7 +58,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { + }, deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, reportMessages: { _, _ in }, blockMessageAuthor: { _, _ in @@ -140,7 +139,6 @@ public final class ChatRecentActionsController: TelegramBaseController { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { - }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { @@ -197,7 +195,7 @@ public final class ChatRecentActionsController: TelegramBaseController { let rightBarButton = ChatNavigationButton(action: .search(hasTags: false), buttonItem: UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.activateSearch))) self.rightBarButton = rightBarButton - self.titleView.title = CounterControllerTitle(title: EnginePeer(peer).compactDisplayTitle, counter: self.presentationData.strings.Channel_AdminLog_TitleAllEvents) + self.titleView.title = CounterControllerTitle(title: peer.compactDisplayTitle, counter: self.presentationData.strings.Channel_AdminLog_TitleAllEvents) let chatTheme = self.context.account.postbox.peerView(id: peer.id) |> map { view -> ChatTheme? in @@ -274,7 +272,7 @@ public final class ChatRecentActionsController: TelegramBaseController { } override public func loadDisplayNode() { - self.displayNode = ChatRecentActionsControllerNode(context: self.context, controller: self, peer: self.peer, presentationData: self.presentationData, pushController: { [weak self] c in + self.displayNode = ChatRecentActionsControllerNode(context: self.context, controller: self, peer: self.peer._asPeer(), presentationData: self.presentationData, pushController: { [weak self] c in (self?.navigationController as? NavigationController)?.pushViewController(c) }, presentController: { [weak self] c, t, a in self?.present(c, in: t, with: a, blockInteraction: true) @@ -332,7 +330,7 @@ public final class ChatRecentActionsController: TelegramBaseController { func openFilterSetup() { if self.adminsPromise == nil { self.adminsPromise = Promise() - let (disposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.peer.id) { membersState in + let (disposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, accountPeerId: self.context.account.peerId, peerId: self.peer.id) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { self.adminsPromise?.set(.single(nil)) } else { @@ -356,12 +354,12 @@ public final class ChatRecentActionsController: TelegramBaseController { var adminPeers: [EnginePeer] = [] if let result { for participant in result { - adminPeers.append(EnginePeer(participant.peer)) + adminPeers.append(participant.peer) } } let controller = RecentActionsSettingsSheet( context: self.context, - peer: EnginePeer(self.peer), + peer: self.peer, adminPeers: adminPeers, initialValue: RecentActionsSettingsSheet.Value( events: self.controllerNode.filter.events, @@ -380,7 +378,7 @@ public final class ChatRecentActionsController: TelegramBaseController { } private func updateTitle() { - let title = EnginePeer(self.peer).compactDisplayTitle + let title = self.peer.compactDisplayTitle let subtitle: String if self.controllerNode.filter.isEmpty { subtitle = self.presentationData.strings.Channel_AdminLog_TitleAllEvents diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index f21df27321..bdf4739e9b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import Display import TelegramPresentationData @@ -43,7 +42,7 @@ private final class ChatRecentActionsListOpaqueState { final class ChatRecentActionsControllerNode: ViewControllerTracingNode { private let context: AccountContext - private let peer: Peer + private let peer: EngineRawPeer private var presentationData: PresentationData private let pushController: (ViewController) -> Void @@ -95,20 +94,20 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { private let eventLogContext: ChannelAdminEventLogContext private var enqueuedTransitions: [(ChatRecentActionsHistoryTransition, Bool)] = [] - private var searchResultsState: (String, [MessageIndex])? + private var searchResultsState: (String, [EngineMessage.Index])? private var historyDisposable: Disposable? private let resolvePeerByNameDisposable = MetaDisposable() private var adminsDisposable: Disposable? private var adminsState: ChannelMemberListState? - private let banDisposables = DisposableDict() - private let reportFalsePositiveDisposables = DisposableDict() + private let banDisposables = DisposableDict() + private let reportFalsePositiveDisposables = DisposableDict() private weak var antiSpamTooltipController: UndoOverlayController? private weak var controller: ChatRecentActionsController? - init(context: AccountContext, controller: ChatRecentActionsController, peer: Peer, presentationData: PresentationData, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, PresentationContextType, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?) { + init(context: AccountContext, controller: ChatRecentActionsController, peer: EngineRawPeer, presentationData: PresentationData, pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController, PresentationContextType, Any?) -> Void, getNavigationController: @escaping () -> NavigationController?) { self.context = context self.controller = controller self.peer = peer @@ -158,7 +157,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.panelButtonNode.addTarget(self, action: #selector(self.settingsButtonPressed), forControlEvents: .touchUpInside) self.panelInfoButtonNode.addTarget(self, action: #selector(self.infoButtonPressed), forControlEvents: .touchUpInside) - let (adminsDisposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: context.account.peerId, peerId: self.peer.id, searchQuery: nil, updated: { [weak self] state in + let (adminsDisposable, _) = self.context.peerChannelMemberCategoriesContextsManager.admins(engine: self.context.engine, accountPeerId: context.account.peerId, peerId: self.peer.id, searchQuery: nil, updated: { [weak self] state in self?.adminsState = state }) self.adminsDisposable = adminsDisposable @@ -270,7 +269,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let strongSelf = self { strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).startStrict(next: { messageId in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] + var messageIdAndMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] if let messageId = messageId { messageIdAndMedia[messageId] = [media] @@ -314,7 +313,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let context = self?.context, let navigationController = self?.getNavigationController() { let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } - }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false + }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, editGif: { _, _ in }, requestMessageActionCallback: { [weak self] message, _, _, _, _ in guard let self else { @@ -327,7 +326,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in self?.openUrl(url.url, progress: url.progress) - }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in + }, openExternalInstantPage: { _ in + }, shareCurrentLocation: { _ in }, shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) { navigationController.pushViewController(controller) @@ -344,7 +344,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { guard let strongSelf = self else { return } - let resolveSignal: Signal + let resolveSignal: Signal if let peerName = peerName { resolveSignal = strongSelf.context.engine.peers.resolvePeerByName(name: peerName, referrer: nil) |> mapToSignal { result -> Signal in @@ -353,12 +353,19 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } return .single(result) } - |> mapToSignal { peer -> Signal in + |> mapToSignal { peer -> Signal in return .single(peer?._asPeer()) } } else { - resolveSignal = context.account.postbox.loadedPeerWithId(strongSelf.peer.id) - |> map(Optional.init) + resolveSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } strongSelf.resolvePeerByNameDisposable.set((resolveSignal |> deliverOnMainQueue).startStrict(next: { peer in @@ -604,7 +611,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, displaySwipeToReplyHint: { }, dismissReplyMarkupMessage: { _ in }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in + }, openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, displayPollSolution: { _, _ in }, displayPsa: { _, _ in @@ -646,10 +653,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, sendGift: { _ in }, openUniqueGift: { _ in }, openMessageFeeException: { - }, requestMessageUpdate: { _, _ in + }, requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { - }, scrollToMessageId: { _ in + }, scrollToMessageId: { _, _ in + }, scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, attemptedNavigationToPrivateQuote: { _ in }, forceUpdateWarpContents: { @@ -659,7 +667,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: self.backgroundNode)) self.controllerInteraction = controllerInteraction @@ -718,10 +729,10 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let toggledDeletedMessageIds = previousExpandedDeletedMessages.symmetricDifference(expandedDeletedMessages) - var searchResultsState: (String, [MessageIndex])? + var searchResultsState: (String, [EngineMessage.Index])? if update.3, let query = self?.filter.query { searchResultsState = (query, processedView.compactMap { entry in - return entry.entry.event.action.messageId.flatMap { MessageIndex(id: $0, timestamp: entry.entry.event.date) } + return entry.entry.event.action.messageId.flatMap { EngineMessage.Index(id: $0, timestamp: entry.entry.event.date) } }) } else { searchResultsState = nil @@ -742,7 +753,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let mediaManager = self.context.sharedContext.mediaManager self.galleryHiddenMesageAndMediaDisposable.set(mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - var messageIdAndMedia: [MessageId: [Media]] = [:] + var messageIdAndMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] for id in ids { if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { @@ -956,13 +967,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { func updateItemNodesSearchTextHighlightStates() { var searchString: String? - var resultsMessageIndices: [MessageIndex]? = nil + var resultsMessageIndices: [EngineMessage.Index]? = nil if let (query, indices) = self.searchResultsState { searchString = query resultsMessageIndices = indices } if searchString != self.controllerInteraction?.searchTextHighightState?.0 || resultsMessageIndices != self.controllerInteraction?.searchTextHighightState?.1 { - var searchTextHighightState: (String, [MessageIndex])? + var searchTextHighightState: (String, [EngineMessage.Index])? if let searchString = searchString, let resultsMessageIndices = resultsMessageIndices { searchTextHighightState = (searchString, resultsMessageIndices) } @@ -975,7 +986,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } } - func updateFilter(events: AdminLogEventsFlags, adminPeerIds: [PeerId]?) { + func updateFilter(events: AdminLogEventsFlags, adminPeerIds: [EnginePeer.Id]?) { self.filter = self.filter.withEvents(events).withAdminPeerIds(adminPeerIds) self.eventLogContext.setFilter(self.filter) } @@ -995,13 +1006,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { return false }), .window(.root), nil) } else { - let peerSignal: Signal = .single(peer._asPeer()) + let peerSignal: Signal = .single(peer._asPeer()) self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self, let peer = peer { if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), peekData: peekData, animated: true)) } else { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: EnginePeer(peer), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.pushController(infoController) } } @@ -1021,7 +1032,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self { if let peer = peer { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.pushController(infoController) } } @@ -1029,7 +1040,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { })) } - private func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) { + private func openMessageContextMenu(message: EngineRawMessage, selectAll: Bool, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) { guard let controller = self.controller else { return } @@ -1301,7 +1312,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { if let photoRepresentation = invite.photoRepresentation { photo.append(photoRepresentation) } - let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil) + let channel = TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil) let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount.value, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0) inputData.set(.single(BotCheckoutController.InputData( @@ -1472,7 +1483,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { break case .sendGift: break - case .chats, .contacts, .compose, .postStory, .settings, .unknownDeepLink, .oauth, .createBot: + case .chats, .contacts, .compose, .postStory, .settings, .unknownDeepLink, .oauth, .createBot, .textStyle: break } } @@ -1480,7 +1491,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } private func presentAutoremoveSetup() { - /*let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peer.id, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in + /*let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peer.id, style: .default, mode: .autoremove, currentTime: currentValue, completion: { [weak self] value in guard let strongSelf = self else { return } @@ -1537,14 +1548,14 @@ final class ChatRecentActionsMessageContextExtractedContentSource: ContextExtrac let blurBackground: Bool = true private weak var controllerNode: ChatRecentActionsControllerNode? - private let message: Message + private let message: EngineRawMessage private let selectAll: Bool var shouldBeDismissed: Signal { return .single(false) } - init(controllerNode: ChatRecentActionsControllerNode, message: Message, selectAll: Bool) { + init(controllerNode: ChatRecentActionsControllerNode, message: EngineRawMessage, selectAll: Bool) { self.controllerNode = controllerNode self.message = message self.selectAll = selectAll @@ -1606,7 +1617,7 @@ final class ChatMessageContextLocationContentSource: ContextLocationContentSourc } extension AdminLogEventAction { - var messageId: MessageId? { + var messageId: EngineMessage.Id? { switch self { case let .editMessage(_, new): return new.id diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift index 7a597f83b0..c85f7a8a68 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsFilterController.swift @@ -213,7 +213,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { peerText = strings.ChatAdmins_AdminLabel.lowercased() } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { arguments.toggleAdmin(participant.peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) @@ -427,7 +427,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update adminsPromise.set(.single(nil)) - let (membersDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peer.id) { membersState in + let (membersDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peer.id) { membersState in if case .loading = membersState.loadingState, membersState.list.isEmpty { adminsPromise.set(.single(nil)) } else { @@ -442,7 +442,7 @@ public func channelRecentActionsFilterController(context: AccountContext, update antiSpamBotPeerPromise.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: antiSpamBotId)) |> map { peer in if let peer = peer, case let .user(user) = peer { - return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: user) + return RenderedChannelParticipant(participant: .member(id: user.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil, subscriptionUntilDate: nil), peer: peer) } else { return nil } @@ -455,13 +455,13 @@ public func channelRecentActionsFilterController(context: AccountContext, update let signal = combineLatest(presentationData, statePromise.get(), adminsPromise.get(), antiSpamBotPeerPromise.get()) |> deliverOnMainQueue |> map { presentationData, state, admins, antiSpamBot -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) let doneEnabled = !state.events.isEmpty - let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: doneEnabled, action: { + let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: doneEnabled, action: { var resultState: ChatRecentActionsFilterControllerState? updateState { current in resultState = current @@ -501,4 +501,3 @@ public func channelRecentActionsFilterController(context: AccountContext, update } return controller } - diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index ce0cecf39f..929b524704 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -670,9 +670,9 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } peers[peer.id] = peer for (_, peer) in participant.peers { - peers[peer.id] = peer + peers[peer.id] = peer._asPeer() } - peers[participant.peer.id] = participant.peer + peers[participant.peer.id] = participant.peer._asPeer() let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) @@ -716,7 +716,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } if (prevBanInfo == nil || !prevBanInfo!.rights.flags.contains(.banReadMessages)) && newFlags.contains(.banReadMessages) { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageKickedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageKickedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -727,7 +727,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) text += "\n" } else if isBroadcast, newBanInfo == nil, prevBanInfo != nil { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageUnkickedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageUnkickedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageUnkickedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageUnkickedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -737,7 +737,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return result }, to: &text, entities: &entities) } else { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRestrictedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRestrictedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -776,6 +776,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { (.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs), (.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks), (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), + (.banSendReactions, self.presentationData.strings.Channel_AdminLog_SendReactions), (.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers), (.banEditRank, self.presentationData.strings.Channel_AdminLog_EditRankOwn), (.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages), @@ -825,7 +826,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { var entities: [MessageTextEntity] = [] if case .member = prev.participant, case .creator = new.participant { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageTransferedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageTransferedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageTransferedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageTransferedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -839,7 +840,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { if case let .creator(_, prevAdminInfo, prevRank) = prev.participant, case let .creator(_, newAdminInfo, newRank) = new.participant, (prevRank != newRank || prevAdminInfo?.rights.rights.contains(.canBeAnonymous) != newAdminInfo?.rights.rights.contains(.canBeAnonymous)) { if prevRank != newRank { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -867,7 +868,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { if prevAdminInfo?.rights.rights.contains(flag) != newAdminInfo?.rights.rights.contains(flag) { if !appendedRightsHeader { appendedRightsHeader = true - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -940,7 +941,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { if !appendedRightsHeader { appendedRightsHeader = true if prevAdminRights == nil { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageAddedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageAddedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageAddedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageAddedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -952,7 +953,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return result }, to: &text, entities: &entities) } else { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -970,7 +971,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { if !prevFlags.isEmpty && newFlags.isEmpty { if !appendedRightsHeader { appendedRightsHeader = true - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessageRemovedAdminNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -987,7 +988,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { if prevFlags.contains(flag) != newFlags.contains(flag) { if !appendedRightsHeader { appendedRightsHeader = true - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessagePromotedName(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)) : self.presentationData.strings.Channel_AdminLog_MessagePromotedNameUsername(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -1023,7 +1024,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return result }, to: &text, entities: &entities) } else { - appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(EnginePeer(new.peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in + appendAttributedText(text: new.peer.addressName == nil ? self.presentationData.strings.Channel_AdminLog_MessageRankNameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), newRank ?? "") : self.presentationData.strings.Channel_AdminLog_MessageRankUsernameNew(new.peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), "@" + new.peer.addressName!, newRank ?? ""), generateEntities: { index in var result: [MessageTextEntityType] = [] if index == 0 { result.append(.TextMention(peerId: new.peer.id)) @@ -1141,6 +1142,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { (.banSendStickers, self.presentationData.strings.Channel_AdminLog_BanSendStickersAndGifs), (.banEmbedLinks, self.presentationData.strings.Channel_AdminLog_BanEmbedLinks), (.banSendPolls, self.presentationData.strings.Channel_AdminLog_SendPolls), + (.banSendReactions, self.presentationData.strings.Channel_AdminLog_SendReactions), (.banAddMembers, self.presentationData.strings.Channel_AdminLog_AddMembers), (.banEditRank, self.presentationData.strings.Channel_AdminLog_EditRank), (.banPinMessages, self.presentationData.strings.Channel_AdminLog_PinMessages), @@ -2270,9 +2272,9 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } peers[peer.id] = peer for (_, peer) in new.peers { - peers[peer.id] = peer + peers[peer.id] = peer._asPeer() } - peers[new.peer.id] = new.peer + peers[new.peer.id] = new.peer._asPeer() var text: String = "" var entities: [MessageTextEntity] = [] @@ -2367,6 +2369,8 @@ private let deletedMessagesDisplayedLimit = 4 func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData, expandedDeletedMessages: Set, currentDeletedHeaderMessages: inout Set) -> [ChatRecentActionsEntry] { var result: [ChatRecentActionsEntry] = [] var deleteMessageEntries: [ChannelAdminEventLogEntry] = [] + let previousDeletedHeaderMessages = currentDeletedHeaderMessages + currentDeletedHeaderMessages = Set() func appendCurrentDeleteEntries() { if !deleteMessageEntries.isEmpty, let lastEntry = deleteMessageEntries.last, let lastMessageId = lastEntry.event.action.messageId { @@ -2394,9 +2398,16 @@ func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentation var skipAppendingGeneralEntry = false if case let .deleteMessage(message) = entry.event.action { var skipAppendingDeletionEntry = false - if currentDeleteMessageEvent == nil || (currentDeleteMessageEvent!.peerId == entry.event.peerId && abs(currentDeleteMessageEvent!.date - entry.event.date) < 5 && !currentDeletedHeaderMessages.contains(message.id)) { + let belongsToCurrentDeleteGroup: Bool + if let currentDeleteMessageEvent { + belongsToCurrentDeleteGroup = currentDeleteMessageEvent.peerId == entry.event.peerId && abs(currentDeleteMessageEvent.date - entry.event.date) < 5 } else { - if currentDeletedHeaderMessages.contains(message.id) { + belongsToCurrentDeleteGroup = true + } + let wasPreviousHeaderMessage = previousDeletedHeaderMessages.contains(message.id) + if currentDeleteMessageEvent == nil || (belongsToCurrentDeleteGroup && !wasPreviousHeaderMessage) { + } else { + if belongsToCurrentDeleteGroup && wasPreviousHeaderMessage { deleteMessageEntries.append(entry) skipAppendingDeletionEntry = true } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift index 6c6b603669..4f21496d02 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsSearchNavigationContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import SearchBarNode diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/BUILD index 8db571013a..1597e34a7d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", "//submodules/MediaPlayer:UniversalMediaPlayer", diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift index f8c3ce8074..56ca3620bf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import UniversalMediaPlayer @@ -392,7 +391,7 @@ public final class ChatRecordingPreviewInputPanelNodeImpl: ChatInputPanelNode { })*/ } - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { let waveformBackgroundFrame = CGRect(origin: CGPoint(x: 3.0, y: 3.0), size: CGSize(width: width - 3.0 * 2.0, height: 40.0 - 3.0 * 2.0)) if self.presentationInterfaceState != interfaceState { diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD index 69f3988816..aba5e2f5f4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/Display", "//submodules/TelegramPresentationData", "//submodules/ChatPresentationInterfaceState", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SearchBarNode", "//submodules/LocalizedPeerData", diff --git a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift index 20c1e1b8fe..5b255bed37 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode/Sources/ChatSearchNavigationContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import SearchBarNode diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD index 04c72e4f7b..4070685672 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AppBundle", "//submodules/ContextUI", diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift index a0552e2033..a1e5b6d07c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAsContextMenu/Sources/ChatSendAsPeerListContextItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AppBundle @@ -16,14 +15,14 @@ import ContextControllerImpl public final class ChatSendAsPeerListContextItem: ContextMenuCustomItem { let context: AccountContext - let chatPeerId: PeerId + let chatPeerId: EnginePeer.Id let peers: [SendAsPeer] - let selectedPeerId: PeerId? + let selectedPeerId: EnginePeer.Id? let isPremium: Bool let action: (EnginePeer) -> Void let presentToast: (EnginePeer) -> Void - public init(context: AccountContext, chatPeerId: PeerId, peers: [SendAsPeer], selectedPeerId: PeerId?, isPremium: Bool, action: @escaping (EnginePeer) -> Void, presentToast: @escaping (EnginePeer) -> Void) { + public init(context: AccountContext, chatPeerId: EnginePeer.Id, peers: [SendAsPeer], selectedPeerId: EnginePeer.Id?, isPremium: Bool, action: @escaping (EnginePeer) -> Void, presentToast: @escaping (EnginePeer) -> Void) { self.context = context self.chatPeerId = chatPeerId self.peers = peers @@ -70,8 +69,8 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel { - if case .broadcast = peer.info { + if case let .channel(channel) = peer.peer { + if case .broadcast = channel.info { subtitle = presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = presentationData.strings.VoiceChat_DiscussionGroup @@ -86,7 +85,7 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen selectedItemIndex = i } let extendedAvatarSize = CGSize(width: 35.0, height: 35.0) - let avatarSignal = peerAvatarCompleteImage(account: item.context.account, peer: EnginePeer(peer.peer), size: avatarSize) + let avatarSignal = peerAvatarCompleteImage(account: item.context.account, peer: peer.peer, size: avatarSize) |> map { image -> UIImage? in if isSelected, let image = image { return generateImage(extendedAvatarSize, rotatedContext: { size, context in @@ -107,18 +106,18 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen } } - let action = ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), textIcon: { theme in + let action = ContextMenuActionItem(text: peer.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), textIcon: { theme in return !item.isPremium && peer.isPremiumRequired ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.badgeInactiveFillColor) : nil }, action: { _, f in f(.default) if !item.isPremium && peer.isPremiumRequired { - item.presentToast(EnginePeer(peer.peer)) + item.presentToast(peer.peer) return } if peer.peer.id != item.selectedPeerId { - item.action(EnginePeer(peer.peer)) + item.action(peer.peer) } }) let actionNode = ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: {}, requestUpdateAction: { _, _ in @@ -153,6 +152,7 @@ private final class ChatSendAsPeerListContextItemNode: ASDisplayNode, ContextMen self.scrollNode.view.alwaysBounceVertical = false self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0) + self.scrollNode.view.scrollsToTop = false } func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index 07a25bb7cd..c38f82c1c6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -86,7 +86,7 @@ public final class ChatSendContactMessageContextPreview: UIView, ChatSendMessage for peer in self.contactPeers { switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -423,10 +423,11 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, navigateToThreadMessage: { _, _, _ in }, tapMessage: { _ in }, clickThroughMessage: { _, _ in - }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in + }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, editGif: { _, _ in - }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, updatePresentationState: { _ in }, openMessageShareMenu: { _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, openExternalInstantPage: { _ in + }, shareCurrentLocation: { _ in }, shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, updatePresentationState: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: { @@ -458,7 +459,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, displaySwipeToReplyHint: { }, dismissReplyMarkupMessage: { _ in }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in + }, openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, displayPollSolution: { _, _ in }, displayPsa: { _, _ in @@ -500,10 +501,11 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, sendGift: { _ in }, openUniqueGift: { _ in }, openMessageFeeException: { - }, requestMessageUpdate: { _, _ in + }, requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { - }, scrollToMessageId: { _ in + }, scrollToMessageId: { _, _ in + }, scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, attemptedNavigationToPrivateQuote: { _ in }, forceUpdateWarpContents: { @@ -513,7 +515,10 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: self.context, backgroundNode: self.wallpaperBackgroundNode)) let associatedData = ChatMessageItemAssociatedData( @@ -530,7 +535,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess accountPeer: nil ) - let entryAttributes = ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false) + let entryAttributes = ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false) let items = self.messages.map { message -> ChatMessageBubbleContentItem in return ChatMessageBubbleContentItem( diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 8942eafd60..329f038e4c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -2293,7 +2293,7 @@ private final class ChatSendStarsScreenComponent: Component { if let peerInfoController = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, diff --git a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD index c70d395426..3933f52171 100644 --- a/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatShareMessageTagView/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/UndoUI", diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/BUILD b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/BUILD index 626e7fbeba..5c9157c187 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/Display", "//submodules/AsyncDisplayKit", diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index 19fa39ba65..5317350d90 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import Display import AsyncDisplayKit @@ -941,8 +940,8 @@ public final class ChatSideTopicsPanel: Component { } let size = CGSize(width: contentSize, height: availableSize.height) - let iconFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - iconSize.height) * 0.5)), size: iconSize) - let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize) + let iconFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - iconSize.height) * 0.5) - 1.0), size: iconSize) + let titleFrame = CGRect(origin: CGPoint(x: iconFrame.maxX + spacing, y: floor((size.height - titleSize.height) * 0.5) - 1.0), size: titleSize) if let icon = self.icon { if let avatarNode = self.avatarNode { @@ -1422,7 +1421,7 @@ public final class ChatSideTopicsPanel: Component { let contentSize: CGFloat = leftInset + rightInset + titleSize.width let size = CGSize(width: contentSize, height: availableSize.height) - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - titleSize.height) * 0.5)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - titleSize.height) * 0.5) - 1.0), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { @@ -1502,7 +1501,6 @@ public final class ChatSideTopicsPanel: Component { self.scrollContainerView = UIView() self.scrollViewMask = UIImageView() self.scrollContainerView.mask = self.scrollViewMask - //self.scrollContainerView.addSubview(self.scrollViewMask) super.init(frame: frame) @@ -1536,7 +1534,7 @@ public final class ChatSideTopicsPanel: Component { public func topicIndex(threadId: Int64?) -> Int? { if let threadId { if let value = self.rawItems.firstIndex(where: { item in - if item.id == .chatList(PeerId(threadId)) { + if item.id == .chatList(EnginePeer.Id(threadId)) { return true } else if item.id == .forum(threadId) { return true @@ -1599,6 +1597,20 @@ public final class ChatSideTopicsPanel: Component { } } if isPinned { + if !seenPinnedItems { + switch component.location { + case .side: + beforePinnedItemsPosition = item.frame.minY + case .top, .bottom: + beforePinnedItemsPosition = item.frame.minX + } + switch component.location { + case .side: + afterPinnedItemsPosition = item.frame.maxY + case .top, .bottom: + afterPinnedItemsPosition = item.frame.maxX + } + } seenPinnedItems = true } else { if !seenPinnedItems { @@ -1885,7 +1897,7 @@ public final class ChatSideTopicsPanel: Component { itemFrame = CGRect(origin: CGPoint(x: 8.0 + 4.0, y: directionContainerInset + 6.0), size: itemSize) directionContainerInset += itemSize.height case .top, .bottom: - itemFrame = CGRect(origin: CGPoint(x: 12.0, y: 6.0), size: itemSize) + itemFrame = CGRect(origin: CGPoint(x: 12.0, y: 5.0), size: itemSize) directionContainerInset += itemSize.width - 20.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift index 0a453ead98..123c96a69f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift @@ -137,6 +137,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag public let micButtonBackgroundView: GlassBackgroundView public let micButtonTintMaskView: UIImageView public let micButton: ChatTextInputMediaRecordingButton + public let stopButtonIcon: GlassBackgroundView.ContentImageView public let sendContainerNode: ASDisplayNode public let sendButtonBackgroundView: UIImageView @@ -173,6 +174,10 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag public var customSendColor: UIColor? public var isSendDisabled: Bool = false + private var slowmodeProgressTimestamp: (duration: Int32, timestamp: Int32)? + private var slowmodeProgressTimer: Foundation.Timer? + private var slowmodeProgressLayer: SimpleShapeLayer? + public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) { self.context = context self.presentationContext = presentationContext @@ -189,6 +194,10 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag self.micButton.animationOutput = self.micButtonTintMaskView self.micButtonBackgroundView.maskContentView.addSubview(self.micButtonTintMaskView) + self.stopButtonIcon = GlassBackgroundView.ContentImageView() + self.micButtonBackgroundView.contentView.addSubview(self.stopButtonIcon) + self.stopButtonIcon.alpha = 0.0 + self.sendContainerNode = ASDisplayNode() self.sendContainerNode.layer.allowsGroupOpacity = true @@ -255,6 +264,10 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag } } + deinit { + self.slowmodeProgressTimer?.invalidate() + } + override public func didLoad() { super.didLoad() @@ -354,11 +367,94 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: defaultGlassTintColor, isInteractive: true, transition: ComponentTransition(transition)) - transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size)) + transition.updatePosition(layer: self.micButton.layer, position: CGRect(origin: CGPoint(), size: size).center) + transition.updateBounds(layer: self.micButton.layer, bounds: CGRect(origin: CGPoint(), size: size)) self.micButton.layoutItems() + if self.stopButtonIcon.image == nil { + self.stopButtonIcon.image = generateImage(CGSize(width: 14.0, height: 14.0), rotatedContext: { size, context in + UIGraphicsPushContext(context) + defer { + UIGraphicsPopContext() + } + + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + + UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 3.0).fill() + })?.withRenderingMode(.alwaysTemplate) + } + if let image = self.stopButtonIcon.image { + self.stopButtonIcon.tintColor = interfaceState.theme.chat.inputPanel.panelControlColor + transition.updateFrame(view: self.stopButtonIcon, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: size))) + } + + var sendSlowmodeTimerTimestamp: (duration: Int32, timestamp: Int32)? + if let slowmodeState = interfaceState.slowmodeState { + switch slowmodeState.variant { + case .pendingMessages: + break + case let .timestamp(timeoutTimestamp): + sendSlowmodeTimerTimestamp = (slowmodeState.timeout, timeoutTimestamp) + } + } + let sendButtonBackgroundFrame = CGRect(origin: CGPoint(), size: innerSize).insetBy(dx: 3.0, dy: 3.0) - transition.updateFrame(view: self.sendButtonBackgroundView, frame: sendButtonBackgroundFrame) + + let slowmodeInset: CGFloat = 4.0 + + self.slowmodeProgressTimestamp = sendSlowmodeTimerTimestamp + if sendSlowmodeTimerTimestamp != nil { + let slowmodeProgressLayer: SimpleShapeLayer + var slowmodeProgressTransition = transition + if let current = self.slowmodeProgressLayer { + slowmodeProgressLayer = current + } else { + slowmodeProgressTransition = .immediate + slowmodeProgressLayer = SimpleShapeLayer() + self.slowmodeProgressLayer = slowmodeProgressLayer + self.sendButtonBackgroundView.layer.superlayer?.insertSublayer(slowmodeProgressLayer, below: self.sendButtonBackgroundView.layer) + + slowmodeProgressLayer.fillColor = nil + slowmodeProgressLayer.lineWidth = 2.0 + slowmodeProgressLayer.lineCap = .round + } + + slowmodeProgressLayer.strokeColor = (self.customSendColor ?? interfaceState.theme.chat.inputPanel.panelControlAccentColor).cgColor + + if slowmodeProgressLayer.bounds.size != sendButtonBackgroundFrame.size { + let pathFrame = CGRect(origin: CGPoint(), size: sendButtonBackgroundFrame.size).insetBy(dx: 2.0, dy: 2.0) + slowmodeProgressLayer.path = UIBezierPath(roundedRect: pathFrame, cornerRadius: pathFrame.height * 0.5).cgPath + } + slowmodeProgressTransition.updateFrame(layer: slowmodeProgressLayer, frame: sendButtonBackgroundFrame) + + if self.slowmodeProgressTimer == nil { + self.slowmodeProgressTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 60.0, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + self.updateSlowmodeProgress() + }) + } + self.updateSlowmodeProgress() + } else { + if let slowmodeProgressLayer = self.slowmodeProgressLayer { + self.slowmodeProgressLayer = nil + slowmodeProgressLayer.removeFromSuperlayer() + } + if let slowmodeProgressTimer = self.slowmodeProgressTimer { + self.slowmodeProgressTimer = nil + slowmodeProgressTimer.invalidate() + } + } + + ComponentTransition(transition).setPosition(view: self.sendButtonBackgroundView, position: sendButtonBackgroundFrame.center) + ComponentTransition(transition).setBounds(view: self.sendButtonBackgroundView, bounds: CGRect(origin: CGPoint(), size: sendButtonBackgroundFrame.size)) + var sendButtonBackgroundScale: CGFloat = 1.0 + if sendSlowmodeTimerTimestamp != nil, let image = self.sendButtonBackgroundView.image { + sendButtonBackgroundScale = (image.size.height - slowmodeInset * 2.0) / image.size.height + } + transition.updateTransformScale(layer: self.sendButtonBackgroundView.layer, scale: sendButtonBackgroundScale) if self.isSendDisabled { transition.updateTintColor(view: self.sendButtonBackgroundView, color: interfaceState.theme.chat.inputPanel.panelControlAccentColor.withMultiplied(hue: 1.0, saturation: 0.0, brightness: 0.5).withMultipliedAlpha(0.25)) @@ -441,9 +537,25 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag return innerSize } + private func updateSlowmodeProgress() { + guard let slowmodeProgressLayer = self.slowmodeProgressLayer, let slowmodeProgressTimestamp = self.slowmodeProgressTimestamp else { + return + } + + let timestamp = Date().timeIntervalSince1970 + let timeout = max(0.0, Double(slowmodeProgressTimestamp.timestamp) - timestamp) + let fraction = timeout / max(0.1, Double(slowmodeProgressTimestamp.duration)) + + slowmodeProgressLayer.strokeEnd = CGFloat(fraction) + } + public func updateAccessibility() { self.accessibilityTraits = .button - if !self.micButton.alpha.isZero { + if !self.stopButtonIcon.alpha.isZero { + //TODO:localize + self.accessibilityLabel = "Stop" + self.accessibilityHint = nil + } else if !self.micButton.alpha.isZero { switch self.micButton.mode { case .audio: self.accessibilityLabel = self.strings.VoiceOver_Chat_RecordModeVoiceMessage diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD index 234f378b3e..905e14cb61 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index a9434330d9..580f8836c3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -407,7 +407,7 @@ public final class ChatTextInputPanelComponent: Component { }, cancelMessageSelection: { _ in }, - deleteSelectedMessages: { + deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, @@ -487,7 +487,6 @@ public final class ChatTextInputPanelComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.chatPeerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -665,8 +664,6 @@ public final class ChatTextInputPanelComponent: Component { }, openScheduledMessages: { }, - openPeersNearby: { - }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { @@ -799,7 +796,6 @@ public final class ChatTextInputPanelComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.chatPeerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -845,7 +841,7 @@ public final class ChatTextInputPanelComponent: Component { if let sendAsConfiguration = component.sendAsConfiguration { presentationInterfaceState = presentationInterfaceState.updatedSendAsPeers([SendAsPeer( - peer: sendAsConfiguration.currentPeer._asPeer(), + peer: sendAsConfiguration.currentPeer, subscribers: sendAsConfiguration.subscriberCount.flatMap(Int32.init(clamping:)), isPremiumRequired: sendAsConfiguration.isPremiumLocked )]).updatedShowSendAsPeers(sendAsConfiguration.isSelecting).updatedCurrentSendAsPeerId(sendAsConfiguration.currentPeer.id) @@ -1052,6 +1048,7 @@ public final class ChatTextInputPanelComponent: Component { transition: transition.containedViewLayoutTransition, interfaceState: presentationInterfaceState, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), + deviceMetrics: DeviceMetrics.iPhone16Pro, isMediaInputExpanded: false ) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index f0465c0afe..9d029e8080 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -4,7 +4,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import MobileCoreServices import TelegramPresentationData @@ -300,7 +299,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = [] - private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, LayoutMetrics, Bool, Bool)? + private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, LayoutMetrics, Bool, Bool, DeviceMetrics)? private var leftMenuInset: CGFloat = 0.0 private var rightSlowModeInset: CGFloat = 0.0 private var currentTextInputBackgroundWidthOffset: CGFloat = 0.0 @@ -713,6 +712,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.sendActionButtons.micButton.alpha = 0.0 self.sendActionButtons.micButtonTintMaskView.alpha = 0.0 self.sendActionButtons.expandMediaInputButtonBackgroundView.alpha = 0.0 + self.sendActionButtons.stopButtonIcon.alpha = 0.0 self.mediaActionButtons = ChatTextInputActionButtonsNode(context: context, presentationInterfaceState: presentationInterfaceState, presentationContext: presentationContext, presentController: presentController) self.mediaActionButtons.sendContainerNode.alpha = 0.0 @@ -872,15 +872,15 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } self.mediaActionButtons.micButton.offsetRecordingControls = { [weak self] in if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = strongSelf.validLayout { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } } } self.mediaActionButtons.micButton.updateCancelTranslation = { [weak self] in if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = strongSelf.validLayout { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } } } @@ -1412,10 +1412,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } public func requestLayout(transition: ContainedViewLayoutTransition = .immediate) { - guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { + guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = self.validLayout else { return } - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) } override public func updateLayout( @@ -1430,12 +1430,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, + deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool ) -> CGFloat { let isFirstTime = self.validLayout == nil let previousAdditionalSideInsets = self.validLayout?.4 - self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) + self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) let defaultGlassTintColor: GlassBackgroundView.TintColor let defaultGlassTintWithInnerColor: GlassBackgroundView.TintColor @@ -1450,10 +1451,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg var leftInset = leftInset var rightInset = rightInset - if bottomInset <= 32.0 { - leftInset += 18.0 - rightInset += 18.0 - } + let compactBottomSideInset = self.compactBottomSideInset(bottomInset: bottomInset, deviceMetrics: deviceMetrics) + leftInset += compactBottomSideInset + rightInset += compactBottomSideInset let placeholderColor: UIColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor @@ -1622,7 +1622,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg currentPeer = sendAsPeers.first?.peer } if let context = self.context, let peer = currentPeer { - self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: EnginePeer(peer), emptyColor: interfaceState.theme.list.mediaPlaceholderColor) + self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: peer, emptyColor: interfaceState.theme.list.mediaPlaceholderColor) } } else if let peer = interfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo, shouldDisplayMenuButton && interfaceState.editMessageState == nil { hasMenuButton = true @@ -2308,6 +2308,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } } + sendActionButtonsSize = self.sendActionButtons.updateLayout(size: CGSize(width: 40.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, showTitle: showTitle, currentMessageEffectId: presentationInterfaceState.interfaceState.sendMessageEffect, transition: transition, interfaceState: presentationInterfaceState) mediaActionButtonsSize = self.mediaActionButtons.updateLayout(size: CGSize(width: 40.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, showTitle: false, currentMessageEffectId: presentationInterfaceState.interfaceState.sendMessageEffect, transition: transition, interfaceState: presentationInterfaceState) } @@ -2897,7 +2898,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updateAlpha(layer: mediaPreviewPanelNode.tintMaskView.layer, alpha: 1.0) transition.updateFrame(view: mediaPreviewPanelNode.tintMaskView, frame: mediaPreviewPanelFrame) - let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, maxOverlayHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: false) + let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, maxOverlayHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: false) } else if let mediaPreviewPanelNode = self.mediaPreviewPanelNode { self.mediaPreviewPanelNode = nil let mediaPreviewPanelView = mediaPreviewPanelNode.view @@ -3103,17 +3104,18 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.textPlaceholderNode.view.setMonochromaticEffect(tintColor: placeholderColor) self.textInputNode?.textView.accessibilityHint = currentPlaceholder + + if transition.isAnimated, let snapshotView = self.textPlaceholderNode.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.textPlaceholderNode.frame + self.textPlaceholderNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.textPlaceholderNode.view) + snapshotView.layer.animateAlpha(from: snapshotView.alpha, to: 0.0, duration: 0.22, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + self.textPlaceholderNode.layer.animateAlpha(from: 0.0, to: self.textPlaceholderNode.alpha, duration: 0.18) + } let placeholderSize = self.textPlaceholderNode.updateLayout(CGSize(width: textPlaceholderMaxWidth, height: CGFloat.greatestFiniteMagnitude)) - if transition.isAnimated, let snapshotLayer = self.textPlaceholderNode.layer.snapshotContentTree() { - self.textPlaceholderNode.supernode?.layer.insertSublayer(snapshotLayer, above: self.textPlaceholderNode.layer) - snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.22, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in - snapshotLayer?.removeFromSuperlayer() - }) - self.textPlaceholderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) - } - textPlaceholderSize = placeholderSize } else { textPlaceholderSize = self.textPlaceholderNode.updateLayout(CGSize(width: textPlaceholderMaxWidth, height: CGFloat.greatestFiniteMagnitude)) @@ -3425,6 +3427,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } if let mediaDraftState = interfaceState.interfaceState.mediaDraftState, case .audio = mediaDraftState.contentType { + viewOnceIsVisible = true recordMoreIsVisible = true } @@ -3436,13 +3439,29 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updateSublayerTransformOffset(layer: self.clippingNode.layer, offset: CGPoint(x: 0.0, y: clippingDelta))*/ let viewOnceSize = self.viewOnceButton.update(theme: interfaceState.theme) - let viewOnceButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 50.0 - UIScreenPixel, y: -152.0), size: viewOnceSize) + + var viewOnceButtonY: CGFloat = -105.0 + if isRecording { + if accessoryPanel == nil { + viewOnceButtonY -= 49.0 + } + } + + let viewOnceButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 50.0 - UIScreenPixel, y: viewOnceButtonY), size: viewOnceSize) self.viewOnceButton.bounds = CGRect(origin: .zero, size: viewOnceButtonFrame.size) transition.updatePosition(node: self.viewOnceButton, position: viewOnceButtonFrame.center) if self.viewOnceButton.alpha.isZero && viewOnceIsVisible { self.viewOnceButton.update(isSelected: self.viewOnce, animated: false) } + + transition.updateAlpha(node: self.viewOnceButton, alpha: viewOnceIsVisible ? 1.0 : 0.0) + transition.updateTransformScale(node: self.viewOnceButton, scale: viewOnceIsVisible ? 1.0 : 0.01) + if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.id != interfaceState.accountPeerId && user.botInfo == nil && interfaceState.sendPaidMessageStars == nil { + self.viewOnceButton.isHidden = false + } else { + self.viewOnceButton.isHidden = true + } let recordMoreSize = self.recordMoreButton.update(theme: interfaceState.theme) let recordMoreButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 50.0 - UIScreenPixel, y: -52.0), size: recordMoreSize) @@ -3453,14 +3472,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.recordMoreButton.update(isSelected: false, animated: false) } - transition.updateAlpha(node: self.viewOnceButton, alpha: viewOnceIsVisible ? 1.0 : 0.0) - transition.updateTransformScale(node: self.viewOnceButton, scale: viewOnceIsVisible ? 1.0 : 0.01) - if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.id != interfaceState.accountPeerId && user.botInfo == nil && interfaceState.sendPaidMessageStars == nil { - self.viewOnceButton.isHidden = false - } else { - self.viewOnceButton.isHidden = true - } - transition.updateAlpha(node: self.recordMoreButton, alpha: recordMoreIsVisible ? 1.0 : 0.0) transition.updateTransformScale(node: self.recordMoreButton, scale: recordMoreIsVisible ? 1.0 : 0.01) @@ -4242,19 +4253,19 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let _ = file let _ = itemLayer - var collectionId: ItemCollectionId? + var collectionId: EngineItemCollectionId? for attribute in file.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute { switch packReference { case let .id(id, _): - collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + collectionId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) default: break } } } - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] if let collectionId { bubbleUpEmojiOrStickersets.append(collectionId) } @@ -4311,9 +4322,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg case let .CustomEmoji(_, _, displayText, stickerPackReference): text = displayText - var packId: ItemCollectionId? + var packId: EngineItemCollectionId? if case let .id(id, _) = stickerPackReference { - packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + packId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) } emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file) break loop @@ -4602,7 +4613,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg hideMicButton = true } + var displayStop = false if let interfaceState = self.presentationInterfaceState { + displayStop = interfaceState.canStopIncomingStreamingMessage if case let .customChatContents(customChatContents) = interfaceState.subject { switch customChatContents.kind { case .hashTagSearch: @@ -4615,13 +4628,33 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } - if hideMicButton || (mediaInputIsActive && !hideExpandMediaInput) { + if displayStop { + let alphaTransition = ComponentTransition(alphaTransition) + alphaTransition.setAlpha(view: self.mediaActionButtons.micButton, alpha: 0.0) + alphaTransition.setAlpha(view: self.mediaActionButtons.micButtonBackgroundView, alpha: 1.0) + alphaTransition.setAlpha(view: self.mediaActionButtons.micButtonTintMaskView, alpha: 0.0) + alphaTransition.setAlpha(view: self.mediaActionButtons.stopButtonIcon, alpha: 1.0) + + ComponentTransition(transition).setScale(view: self.mediaActionButtons.stopButtonIcon, scale: 1.0) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButton, scale: 0.001) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButtonTintMaskView, scale: 0.001) + } else if hideMicButton || (mediaInputIsActive && !hideExpandMediaInput) { + ComponentTransition(alphaTransition).setAlpha(view: self.mediaActionButtons.stopButtonIcon, alpha: 0.0) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.stopButtonIcon, scale: 0.001) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButton, scale: 1.0) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButtonTintMaskView, scale: 1.0) + if !self.mediaActionButtons.micButton.alpha.isZero { alphaTransition.updateAlpha(layer: self.mediaActionButtons.micButton.layer, alpha: 0.0) alphaTransition.updateAlpha(layer: self.mediaActionButtons.micButtonBackgroundView.layer, alpha: 0.0) alphaTransition.updateAlpha(layer: self.mediaActionButtons.micButtonTintMaskView.layer, alpha: 0.0) } } else { + ComponentTransition(alphaTransition).setAlpha(view: self.mediaActionButtons.stopButtonIcon, alpha: 0.0) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.stopButtonIcon, scale: 0.001) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButton, scale: 1.0) + ComponentTransition(transition).setScale(view: self.mediaActionButtons.micButtonTintMaskView, scale: 1.0) + let micAlpha: CGFloat = self.mediaActionButtons.micButton.fadeDisabled ? 0.5 : 1.0 if !self.mediaActionButtons.micButton.alpha.isEqual(to: micAlpha) { alphaTransition.updateAlpha(layer: self.mediaActionButtons.micButton.layer, alpha: micAlpha) @@ -4653,13 +4686,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } private func updateTextHeight(animated: Bool) { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, _, metrics, _, _) = self.validLayout, let interfaceState = self.presentationInterfaceState { + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, _, metrics, _, _, deviceMetrics) = self.validLayout, let interfaceState = self.presentationInterfaceState { var leftInset = leftInset var rightInset = rightInset - if bottomInset <= 32.0 { - leftInset += 18.0 - rightInset += 18.0 - } + let compactBottomSideInset = self.compactBottomSideInset(bottomInset: bottomInset, deviceMetrics: deviceMetrics) + leftInset += compactBottomSideInset + rightInset += compactBottomSideInset let baseWidth = width - leftInset - self.leftMenuInset - rightInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset - additionalSideInsets.right let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: baseWidth, sendActionControlsWidth: self.sendActionButtons.bounds.width, maxHeight: maxHeight, metrics: metrics, bottomInset: bottomInset, interfaceState: interfaceState) @@ -5269,7 +5301,20 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg attributedString = chatInputStateStringFromRTF(data, type: NSAttributedString.DocumentType.rtfd) } } - + + // Rich-message markdown copied to the clipboard is plain text containing + // `[](tg://emoji?id=)` emoji markers (no RTF/private type). + // Reattach those markers as live custom-emoji attributes so the field + // shows the animated emoji (matching the edit-load reconstruction). Only + // taken when a marker actually converted, so ordinary text pastes are + // unaffected and fall through to default paste. + if attributedString == nil, let plainText = pasteboard.string, plainText.contains("tg://emoji?id=") { + let reattached = chatInputTextWithReattachedCustomEmoji(plainText) + if reattached.string != plainText { + attributedString = reattached + } + } + if let attributedString = attributedString { self.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in if let inputText = current.inputText.mutableCopy() as? NSMutableAttributedString { @@ -5600,12 +5645,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } public func frameForInputActionButton() -> CGRect? { - if !self.mediaActionButtons.alpha.isZero { - if self.mediaActionButtons.micButton.alpha.isZero { - return self.mediaActionButtons.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: -3.0, dy: 0.0) - } else { - return self.mediaActionButtons.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: -3.0, dy: 0.0) - } + if !self.mediaActionButtons.alpha.isZero && self.mediaActionButtons.frame.minX < self.bounds.width { + return self.mediaActionButtons.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: -3.0, dy: 0.0) } return nil } diff --git a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/BUILD index bfdf93594b..f90aa0e729 100644 --- a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TextFormat", diff --git a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift index 3b80c51c1f..69498a7c91 100644 --- a/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatUserInfoItem/Sources/ChatUserInfoItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TextFormat @@ -114,7 +113,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe private var groupsInCommonContext: GroupsInCommonContext? private var groupsInCommonDisposable: Disposable? - private var groupsInCommon: [Peer] = [] + private var groupsInCommon: [EnginePeer] = [] private let disclaimerTextNode: TextNodeWithEntities @@ -436,7 +435,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe guard let self, let item = self.item else { return } - self.groupsInCommon = Array(state.peers.compactMap { $0.peer }.prefix(3)) + self.groupsInCommon = Array(state.peers.compactMap { $0.peer }.prefix(3)).map(EnginePeer.init) self.groupsAvatarsNode.update(context: item.context, peers: self.groupsInCommon, synchronousLoad: true, imageSize: avatarImageSize, imageSpacing: avatarSpacing, borderWidth: avatarBorder) }) } diff --git a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift index 1aca3bf9a1..61e5a4d228 100644 --- a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift @@ -229,6 +229,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.scrollNode = ASScrollNode() self.scrollNode.view.alwaysBounceVertical = false self.scrollNode.clipsToBounds = true + self.scrollNode.view.scrollsToTop = false self.placeholderNode = ASTextNode() self.placeholderNode.isUserInteractionEnabled = false @@ -236,6 +237,7 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: theme.list.itemPlaceholderTextColor) self.textFieldScrollNode = ASScrollNode() + self.textFieldScrollNode.view.scrollsToTop = false self.textFieldNode = TextFieldNode() self.textFieldNode.textField.font = Font.regular(15.0) diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD index 09c44b1067..217d311fe4 100644 --- a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift index fb4c4a6c0f..d3d836cb6c 100644 --- a/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/FactCheckAlertController/Sources/FactCheckAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD index 97ddeb8af6..6e77abca27 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift index 21ed1416f8..7e8ace96ef 100644 --- a/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode/Sources/ForwardAccessoryPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import Display import TelegramPresentationData @@ -23,7 +22,7 @@ import AppBundle import ComponentFlow import AlertComponent -func textStringForForwardedMessage(_ message: Message, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) { +func textStringForForwardedMessage(_ message: EngineRawMessage, strings: PresentationStrings) -> (text: String, entities: [MessageTextEntity], isMedia: Bool) { for media in message.media { switch media { case _ as TelegramMediaImage: @@ -89,8 +88,8 @@ func textStringForForwardedMessage(_ message: Message, strings: PresentationStri public final class ForwardAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() - public let messageIds: [MessageId] - private var messages: [Message] = [] + public let messageIds: [EngineMessage.Id] + private var messages: [EngineRawMessage] = [] let closeButton: HighlightableButtonNode let lineNode: ASImageNode @@ -110,7 +109,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)? - public init(context: AccountContext, messageIds: [MessageId], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, nameDisplayOrder: PresentationPersonNameOrder, forwardOptionsState: ChatInterfaceForwardOptionsState?, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { + public init(context: AccountContext, messageIds: [EngineMessage.Id], theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, nameDisplayOrder: PresentationPersonNameOrder, forwardOptionsState: ChatInterfaceForwardOptionsState?, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { self.context = context self.messageIds = messageIds self.theme = theme @@ -165,7 +164,10 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { ) } - self.messageDisposable.set((context.account.postbox.messagesAtIds(messageIds) + self.messageDisposable.set((context.engine.data.get(EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init))) + |> map { messageMap -> [EngineRawMessage] in + return messageIds.compactMap { messageMap[$0]??._asMessage() } + } |> deliverOnMainQueue).start(next: { [weak self] messages in if let strongSelf = self { if messages.isEmpty { @@ -295,7 +297,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { } var authors = "" - var uniquePeerIds = Set() + var uniquePeerIds = Set() var title = "" var text = NSMutableAttributedString(string: "") @@ -325,7 +327,7 @@ public final class ForwardAccessoryPanelNode: AccessoryPanelNode { case let .CustomEmoji(_, fileId): let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) if range.lowerBound >= 0 && range.upperBound <= additionalText.length { - additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range) + additionalText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: messages[0].associatedMedia[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile), range: range) } default: break diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD index a06f08dcc1..dc29ccd348 100644 --- a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/Display", "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift index d6e4efc807..ddfe1640ca 100644 --- a/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift +++ b/submodules/TelegramUI/Components/Chat/ManagedDiceAnimationNode/Sources/ManagedDiceAnimationNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -141,9 +140,9 @@ public final class ManagedDiceAnimationNode: ManagedAnimationNode { self.context = context self.emoji = emoji - self.configuration.set(self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.configuration.set(self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> InteractiveEmojiConfiguration? in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return InteractiveEmojiConfiguration.with(appConfiguration: appConfiguration) }) self.emojis.set(context.engine.stickers.loadedStickerPack(reference: .dice(emoji), forceActualized: false) diff --git a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD index 1328ab6234..4720629155 100644 --- a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/Display", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/Sources/MergedAvatarsNode.swift b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/Sources/MergedAvatarsNode.swift index 111a1d8640..6c6dda88c7 100644 --- a/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/Sources/MergedAvatarsNode.swift +++ b/submodules/TelegramUI/Components/Chat/MergedAvatarsNode/Sources/MergedAvatarsNode.swift @@ -4,16 +4,15 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData -import Postbox import TelegramCore import AvatarNode import AccountContext private enum PeerAvatarReference: Equatable { - case letters(PeerId, PeerColor?, [String]) + case letters(EnginePeer.Id, PeerColor?, [String]) case image(PeerReference, TelegramMediaImageRepresentation) - var peerId: PeerId { + var peerId: EnginePeer.Id { switch self { case let .letters(value, _, _): return value @@ -24,7 +23,7 @@ private enum PeerAvatarReference: Equatable { } private extension PeerAvatarReference { - init(peer: Peer) { + init(peer: EnginePeer) { if let photo = peer.smallProfileImage, let peerReference = PeerReference(peer) { self = .image(peerReference, photo) } else { @@ -35,13 +34,13 @@ private extension PeerAvatarReference { private final class MergedAvatarsNodeArguments: NSObject { let peers: [PeerAvatarReference] - let images: [PeerId: UIImage] + let images: [EnginePeer.Id: UIImage] let imageSize: CGFloat let imageSpacing: CGFloat let borderWidth: CGFloat let avatarFontSize: CGFloat - init(peers: [PeerAvatarReference], images: [PeerId: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat) { + init(peers: [PeerAvatarReference], images: [EnginePeer.Id: UIImage], imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat) { self.peers = peers self.images = images self.imageSize = imageSize @@ -62,8 +61,8 @@ public final class MergedAvatarsNode: ASDisplayNode { public static let defaultAvatarFontSize: CGFloat = 8.0 private var peers: [PeerAvatarReference] = [] - private var images: [PeerId: UIImage] = [:] - private var disposables: [PeerId: Disposable] = [:] + private var images: [EnginePeer.Id: UIImage] = [:] + private var disposables: [EnginePeer.Id: Disposable] = [:] private let buttonNode: HighlightTrackingButtonNode private var imageSize: CGFloat = defaultMergedImageSize private var imageSpacing: CGFloat = defaultMergedImageSpacing @@ -97,7 +96,7 @@ public final class MergedAvatarsNode: ASDisplayNode { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) } - public func update(context: AccountContext, peers: [Peer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat = 8.0) { + public func update(context: AccountContext, peers: [EnginePeer], synchronousLoad: Bool, imageSize: CGFloat, imageSpacing: CGFloat, borderWidth: CGFloat, avatarFontSize: CGFloat = 8.0) { self.imageSize = imageSize self.imageSpacing = imageSpacing self.borderWidthValue = borderWidth @@ -110,20 +109,20 @@ public final class MergedAvatarsNode: ASDisplayNode { if filteredPeers != self.peers { self.peers = filteredPeers - var validImageIds: [PeerId] = [] + var validImageIds: [EnginePeer.Id] = [] for peer in filteredPeers { if case .image = peer { validImageIds.append(peer.peerId) } } - var removedImageIds: [PeerId] = [] + var removedImageIds: [EnginePeer.Id] = [] for (id, _) in self.images { if !validImageIds.contains(id) { removedImageIds.append(id) } } - var removedDisposableIds: [PeerId] = [] + var removedDisposableIds: [EnginePeer.Id] = [] for (id, disposable) in self.disposables { if !validImageIds.contains(id) { disposable.dispose() diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD index beec526121..e610b4df40 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/Display", "//submodules/Components/HierarchyTrackingLayer", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramUI/Components/AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer", diff --git a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift index 7408d13b04..fc5366cc2b 100644 --- a/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift +++ b/submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView/Sources/MessageInlineBlockBackgroundView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import HierarchyTrackingLayer -import Postbox import TelegramCore import AnimationCache import MultiAnimationRenderer @@ -506,6 +505,8 @@ public final class MessageInlineBlockBackgroundView: UIView { thirdColor: params.thirdColor, backgroundColor: params.backgroundColor, pattern: params.pattern, + patternTopRightPosition: params.patternTopRightPosition, + patternAlpha: params.patternAlpha, animation: .None ) } diff --git a/submodules/TelegramUI/Components/Chat/QuickShareScreen/BUILD b/submodules/TelegramUI/Components/Chat/QuickShareScreen/BUILD index 1822500c99..8bf1f0dd79 100644 --- a/submodules/TelegramUI/Components/Chat/QuickShareScreen/BUILD +++ b/submodules/TelegramUI/Components/Chat/QuickShareScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareScreen.swift b/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareScreen.swift index d9043f109f..325da7d80c 100644 --- a/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareScreen.swift +++ b/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareScreen.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TextFormat import TelegramPresentationData @@ -218,19 +217,13 @@ private final class QuickShareScreenComponent: Component { if case let .peers(peers) = recentPeers, !peers.isEmpty { return .single(peers.map(EnginePeer.init)) } else { - return component.context.account.stateManager.postbox.tailChatListView( - groupId: .root, - count: 20, - summaryComponents: ChatListEntrySummaryComponents() - ) + return component.context.engine.messages.chatList(group: .root, count: 20) |> take(1) - |> map { view -> [EnginePeer] in + |> map { chatList -> [EnginePeer] in var peers: [EnginePeer] = [] - for entry in view.0.entries.reversed() { - if case let .MessageEntry(entryData) = entry { - if let user = entryData.renderedPeer.chatMainPeer as? TelegramUser, user.isGenericUser && user.id != component.context.account.peerId && !user.id.isSecretChat { - peers.append(EnginePeer(user)) - } + for item in chatList.items.reversed() { + if case let .user(user) = item.renderedPeer.chatMainPeer, user.isGenericUser && user.id != component.context.account.peerId && !user.id.isSecretChat { + peers.append(.user(user)) } } return peers diff --git a/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareToastScreen.swift b/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareToastScreen.swift index 723007aa78..baafd5d89b 100644 --- a/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareToastScreen.swift +++ b/submodules/TelegramUI/Components/Chat/QuickShareScreen/Sources/QuickShareToastScreen.swift @@ -254,16 +254,19 @@ private final class QuickShareToastScreenComponent: Component { let contentSize = self.content.update( transition: transition, - component: AnyComponent(MultilineTextComponent(text: .markdown( - text: tooltipText, - attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white), - bold: MarkdownAttributeSet(font: Font.semibold(14.0), textColor: environment.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)), - link: MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white), - linkAttribute: { _ in return nil }) - ))), + component: AnyComponent(MultilineTextComponent( + text: .markdown( + text: tooltipText, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white), + bold: MarkdownAttributeSet(font: Font.semibold(14.0), textColor: environment.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)), + link: MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white), + linkAttribute: { _ in return nil }) + ), + maximumNumberOfLines: 2 + )), environment: {}, - containerSize: CGSize(width: availableContentSize.width - contentInsets.left - contentInsets.right - spacing - iconSize.width - actionButtonSize.width - 16.0 - 4.0, height: availableContentSize.height) + containerSize: CGSize(width: availableContentSize.width - contentInsets.left - contentInsets.right - spacing - iconSize.width - actionButtonSize.width - 16.0 - 8.0, height: availableContentSize.height) ) var contentHeight: CGFloat = 0.0 @@ -313,7 +316,7 @@ private final class QuickShareToastScreenComponent: Component { self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.7), transition: transition.containedViewLayoutTransition) - self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 14.0, transition: transition.containedViewLayoutTransition) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, transition: transition.containedViewLayoutTransition) transition.setFrame(view: self.backgroundView, frame: backgroundFrame) transition.setFrame(view: self.contentView, frame: CGRect(origin: .zero, size: backgroundFrame.size)) diff --git a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD index d027a20de9..d9502ff521 100644 --- a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift index 44c99484a2..bde6b73843 100644 --- a/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ReplyAccessoryPanelNode/Sources/ReplyAccessoryPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import Display import TelegramPresentationData @@ -24,7 +23,7 @@ import CompositeTextNode public final class ReplyAccessoryPanelNode: AccessoryPanelNode { private let messageDisposable = MetaDisposable() public let chatPeerId: EnginePeer.Id - public let messageId: MessageId + public let messageId: EngineMessage.Id public let quote: EngineMessageReplyQuote? public let innerSubject: EngineMessageReplyInnerSubject? @@ -47,7 +46,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode { private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)? - public init(context: AccountContext, chatPeerId: EnginePeer.Id, messageId: MessageId, quote: EngineMessageReplyQuote?, innerSubject: EngineMessageReplyInnerSubject?, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { + public init(context: AccountContext, chatPeerId: EnginePeer.Id, messageId: EngineMessage.Id, quote: EngineMessageReplyQuote?, innerSubject: EngineMessageReplyInnerSubject?, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { self.chatPeerId = chatPeerId self.messageId = messageId self.quote = quote diff --git a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD index 24344ea62d..6ef0604ffa 100644 --- a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift index 23f7b9bf18..23a4c4e5bc 100644 --- a/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/SavedTagNameAlertController/Sources/SavedTagNameAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD index 885c055cd0..b1c21063e1 100644 --- a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift index dfe08a10b5..6a5973c86a 100644 --- a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import Display import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD index 554275c6a9..29c6d108f4 100644 --- a/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/ReactionSelectionNode", ], diff --git a/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift index 0f549ecfeb..5e99e36484 100644 --- a/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift +++ b/submodules/TelegramUI/Components/Chat/TopMessageReactions/Sources/TopMessageReactions.swift @@ -1,7 +1,6 @@ import Foundation import SwiftSignalKit import TelegramCore -import Postbox import AccountContext import ReactionSelectionNode @@ -10,7 +9,7 @@ public enum AllowedReactions { case all } -public func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal<(allowedReactions: AllowedReactions?, areStarsEnabled: Bool), NoError> { +public func peerMessageAllowedReactions(context: AccountContext, message: EngineRawMessage, ignoreDefault: Bool = false) -> Signal<(allowedReactions: AllowedReactions?, areStarsEnabled: Bool), NoError> { if message.id.peerId == context.account.peerId { return .single((.all, false)) } @@ -41,6 +40,10 @@ public func peerMessageAllowedReactions(context: AccountContext, message: Messag areStarsEnabled = value } + if let peer, !canSendReactionsToPeer(peer, ignoreDefault: ignoreDefault) { + return (nil, areStarsEnabled) + } + if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)), effectiveReactions.count >= maxReactionCount { return (.set(Set(effectiveReactions.map(\.value))), areStarsEnabled) } @@ -102,18 +105,11 @@ public func tagMessageReactions(context: AccountContext, subPeerId: EnginePeer.I return combineLatest( context.engine.stickers.availableReactions(), - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudDefaultTagReactions], namespaces: [ItemCollectionId.Namespace.max - 1], aroundIndex: nil, count: 10000000), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudDefaultTagReactions)), topTags ) |> take(1) - |> map { availableReactions, view, topTags -> [ReactionItem] in - var defaultTagReactions: OrderedItemListView? - for orderedView in view.orderedItemListsViews { - if orderedView.collectionId == Namespaces.OrderedItemList.CloudDefaultTagReactions { - defaultTagReactions = orderedView - } - } - + |> map { availableReactions, defaultTagReactionItems, topTags -> [ReactionItem] in var result: [ReactionItem] = [] var existingIds = Set() @@ -172,8 +168,8 @@ public func tagMessageReactions(context: AccountContext, subPeerId: EnginePeer.I } } - if let defaultTagReactions { - for item in defaultTagReactions.items { + do { + for item in defaultTagReactionItems { guard let topReaction = item.contents.get(RecentReactionItem.self) else { continue } @@ -256,7 +252,7 @@ public func tagMessageReactions(context: AccountContext, subPeerId: EnginePeer.I } } -public func topMessageReactions(context: AccountContext, message: Message, subPeerId: EnginePeer.Id?) -> Signal<[ReactionItem], NoError> { +public func topMessageReactions(context: AccountContext, message: EngineRawMessage, subPeerId: EnginePeer.Id?, ignoreDefault: Bool = false) -> Signal<[ReactionItem], NoError> { if message.id.peerId == context.account.peerId { var loadTags = false if let effectiveReactionsAttribute = message.effectiveReactionsAttribute(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) { @@ -275,18 +271,14 @@ public func topMessageReactions(context: AccountContext, message: Message, subPe } } - let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) - let topReactions = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> [RecentReactionItem] in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return [] - } - return view.items.compactMap { item -> RecentReactionItem? in + let topReactions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudTopReactions)) + |> map { items -> [RecentReactionItem] in + return items.compactMap { item -> RecentReactionItem? in return item.contents.get(RecentReactionItem.self) } } - let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> = peerMessageAllowedReactions(context: context, message: message) + let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> = peerMessageAllowedReactions(context: context, message: message, ignoreDefault: ignoreDefault) |> mapToSignal { allowedReactions, areStarsEnabled -> Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> in guard let allowedReactions = allowedReactions else { return .single(nil) diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD index 684e3ca458..43bbbfc9aa 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", "//submodules/TelegramUIPreferences:TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index ff9b4d23e4..d7b308ff93 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -1,9 +1,7 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import TelegramUIPreferences @@ -42,9 +40,9 @@ public struct ChatInterfaceHighlightedState: Equatable { } public struct ChatInterfacePollActionState: Equatable { - public var pollMessageIdsInProgress: [MessageId: [Data]] = [:] + public var pollMessageIdsInProgress: [EngineMessage.Id: [Data]] = [:] - public init(pollMessageIdsInProgress: [MessageId: [Data]] = [:]) { + public init(pollMessageIdsInProgress: [EngineMessage.Id: [Data]] = [:]) { self.pollMessageIdsInProgress = pollMessageIdsInProgress } } @@ -60,10 +58,10 @@ public enum ChatControllerInteractionReaction { } public struct UnreadMessageRangeKey: Hashable { - public var peerId: PeerId - public var namespace: MessageId.Namespace + public var peerId: EnginePeer.Id + public var namespace: Int32 - public init(peerId: PeerId, namespace: MessageId.Namespace) { + public init(peerId: EnginePeer.Id, namespace: Int32) { self.peerId = peerId self.namespace = namespace } @@ -138,18 +136,18 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public enum OpenPeerSource { case `default` case reaction - case groupParticipant(storyStats: PeerStoryStats?, avatarHeaderNode: ASDisplayNode?) + case groupParticipant(storyStats: EnginePeerStoryStats?, avatarHeaderNode: ASDisplayNode?) } public struct OpenUrl { public var url: String public var concealed: Bool public var external: Bool? - public var message: Message? + public var message: EngineRawMessage? public var allowInlineWebpageResolution: Bool public var progress: Promise? - public init(url: String, concealed: Bool, external: Bool? = nil, message: Message? = nil, allowInlineWebpageResolution: Bool = false, progress: Promise? = nil) { + public init(url: String, concealed: Bool, external: Bool? = nil, message: EngineRawMessage? = nil, allowInlineWebpageResolution: Bool = false, progress: Promise? = nil) { self.url = url self.concealed = concealed self.external = external @@ -159,14 +157,30 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol } } + public struct OpenInstantPage { + public var webpageId: EngineMedia.Id + public var url: String + public var anchor: String? + public var concealed: Bool + public var progress: Promise? + + public init(webpageId: EngineMedia.Id, url: String, anchor: String?, concealed: Bool, progress: Promise? = nil) { + self.webpageId = webpageId + self.url = url + self.anchor = anchor + self.concealed = concealed + self.progress = progress + } + } + public struct LongTapParams { - public var message: Message? + public var message: EngineRawMessage? public var contentNode: ContextExtractedContentContainingNode? public var messageNode: ASDisplayNode? public var progress: Promise? public var gesture: TapLongTapOrDoubleTapGestureRecognizer? - public init(message: Message? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise? = nil, gesture: TapLongTapOrDoubleTapGestureRecognizer? = nil) { + public init(message: EngineRawMessage? = nil, contentNode: ContextExtractedContentContainingNode? = nil, messageNode: ASDisplayNode? = nil, progress: Promise? = nil, gesture: TapLongTapOrDoubleTapGestureRecognizer? = nil) { self.message = message self.contentNode = contentNode self.messageNode = messageNode @@ -180,99 +194,100 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol case solution(TelegramMediaPollResults.Solution) } - public let openMessage: (Message, OpenMessageParams) -> Bool + public let openMessage: (EngineRawMessage, OpenMessageParams) -> Bool public let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void public let openPeerMention: (String, Promise?) -> Void - public let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void - public let updateMessageReaction: (Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void - public let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void + public let openMessageContextMenu: (EngineRawMessage, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void + public let updateMessageReaction: (EngineRawMessage, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void + public let openMessageReactionContextMenu: (EngineRawMessage, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void public let activateMessagePinch: (PinchSourceContainerNode) -> Void - public let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void - public let navigateToMessage: (MessageId, MessageId, NavigateToMessageParams) -> Void - public let navigateToMessageStandalone: (MessageId) -> Void - public let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void - public let tapMessage: ((Message) -> Void)? + public let openMessageContextActions: (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void + public let navigateToMessage: (EngineMessage.Id, EngineMessage.Id, NavigateToMessageParams) -> Void + public let navigateToMessageStandalone: (EngineMessage.Id) -> Void + public let navigateToThreadMessage: (EnginePeer.Id, Int64, EngineMessage.Id?) -> Void + public let tapMessage: ((EngineRawMessage) -> Void)? public let clickThroughMessage: (UIView?, CGPoint?) -> Void - public let toggleMessagesSelection: ([MessageId], Bool) -> Void + public let toggleMessagesSelection: ([EngineMessage.Id], Bool) -> Void public let sendCurrentMessage: (Bool, ChatSendMessageEffect?) -> Void - public let sendMessage: (String) -> Void - public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool + public let sendMessage: (String, EngineMessage.Id?) -> Void + public let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void public let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool public let editGif: (FileMediaReference, Bool) -> Void - public let requestMessageActionCallback: (Message, MemoryBuffer?, Bool, Bool, Promise?) -> Void + public let requestMessageActionCallback: (EngineRawMessage, EngineMemoryBuffer?, Bool, Bool, Promise?) -> Void public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void - public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void + public let activateSwitchInline: (EnginePeer.Id?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void public let openUrl: (OpenUrl) -> Void - public let shareCurrentLocation: () -> Void - public let shareAccountContact: () -> Void - public let sendBotCommand: (MessageId?, String) -> Void - public let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void - public let openWallpaper: (Message) -> Void - public let openTheme: (Message) -> Void + public let openExternalInstantPage: (OpenInstantPage) -> Void + public let shareCurrentLocation: (EngineMessage.Id?) -> Void + public let shareAccountContact: (EngineMessage.Id?) -> Void + public let sendBotCommand: (EngineMessage.Id?, String) -> Void + public let openInstantPage: (EngineRawMessage, ChatMessageItemAssociatedData?) -> Void + public let openWallpaper: (EngineRawMessage) -> Void + public let openTheme: (EngineRawMessage) -> Void public let openHashtag: (String?, String) -> Void public let updateInputState: ((ChatTextInputState) -> ChatTextInputState) -> Void public let updateInputMode: ((ChatInputMode) -> ChatInputMode) -> Void public let updatePresentationState: ((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void - public let openMessageShareMenu: (MessageId) -> Void + public let openMessageShareMenu: (EngineMessage.Id) -> Void public let presentController: (ViewController, Any?) -> Void public let presentControllerInCurrent: (ViewController, Any?) -> Void public let navigationController: () -> NavigationController? public let chatControllerNode: () -> ASDisplayNode? public let presentGlobalOverlayController: (ViewController, Any?) -> Void - public let callPeer: (PeerId, Bool) -> Void - public let openConferenceCall: (Message) -> Void + public let callPeer: (EnginePeer.Id, Bool) -> Void + public let openConferenceCall: (EngineRawMessage) -> Void public let longTap: (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void public let todoItemLongTap: (Int32, LongTapParams?) -> Void public let pollOptionLongTap: (Data, LongTapParams?) -> Void - public let openCheckoutOrReceipt: (MessageId, OpenMessageParams?) -> Void + public let openCheckoutOrReceipt: (EngineMessage.Id, OpenMessageParams?) -> Void public let openSearch: () -> Void - public let setupReply: (MessageId) -> Void - public let canSetupReply: (Message) -> ChatControllerInteractionSwipeAction + public let setupReply: (EngineMessage.Id) -> Void + public let canSetupReply: (EngineRawMessage) -> ChatControllerInteractionSwipeAction public let canSendMessages: () -> Bool public let navigateToFirstDateMessage: (Int32, Bool) -> Void - public let requestRedeliveryOfFailedMessages: (MessageId) -> Void + public let requestRedeliveryOfFailedMessages: (EngineMessage.Id) -> Void public let addContact: (String) -> Void - public let rateCall: (Message, CallId, Bool) -> Void - public let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void - public let requestAddMessagePollOption: (MessageId, String, [MessageTextEntity], Data, AnyMediaReference?) -> Void - public let requestOpenMessagePollResults: (MessageId, MediaId) -> Void + public let rateCall: (EngineRawMessage, CallId, Bool) -> Void + public let requestSelectMessagePollOptions: (EngineMessage.Id, [Data]) -> Void + public let requestAddMessagePollOption: (EngineMessage.Id, String, [MessageTextEntity], Data, AnyMediaReference?) -> Void + public let requestOpenMessagePollResults: (EngineMessage.Id, EngineMedia.Id) -> Void public let openAppStorePage: () -> Void - public let displayMessageTooltip: (MessageId, String, Bool, ASDisplayNode?, CGRect?) -> Void - public let seekToTimecode: (Message, Double, Bool) -> Void + public let displayMessageTooltip: (EngineMessage.Id, String, Bool, ASDisplayNode?, CGRect?) -> Void + public let seekToTimecode: (EngineRawMessage, Double, Bool) -> Void public let scheduleCurrentMessage: (ChatSendMessageActionSheetController.SendParameters?) -> Void - public let sendScheduledMessagesNow: ([MessageId]) -> Void - public let editScheduledMessagesTime: ([MessageId]) -> Void - public let performTextSelectionAction: (Message?, Bool, NSAttributedString, [MessageTextEntity]?, TextSelectionAction) -> Void + public let sendScheduledMessagesNow: ([EngineMessage.Id]) -> Void + public let editScheduledMessagesTime: ([EngineMessage.Id]) -> Void + public let performTextSelectionAction: (EngineRawMessage?, Bool, NSAttributedString, [MessageTextEntity]?, TextSelectionAction) -> Void public let displayImportedMessageTooltip: (ASDisplayNode) -> Void public let displaySwipeToReplyHint: () -> Void - public let dismissReplyMarkupMessage: (Message) -> Void - public let openMessagePollResults: (MessageId, Data) -> Void - public let openPollCreation: (Bool?) -> Void - public let openPollMedia: (Message, PollMediaSubject) -> Void + public let dismissReplyMarkupMessage: (EngineRawMessage) -> Void + public let openMessagePollResults: (EngineMessage.Id, Data) -> Void + public let openPollCreation: (EngineMessage.Id?, Bool?) -> Void + public let openPollMedia: (EngineRawMessage, PollMediaSubject) -> Void public let displayPollSolution: (TelegramMediaPollResults.Solution?, ASDisplayNode?) -> Void public let displayPsa: (String, ASDisplayNode) -> Void public let displayDiceTooltip: (TelegramMediaDice) -> Void public let animateDiceSuccess: (Bool, Bool) -> Void - public let displayPremiumStickerTooltip: (TelegramMediaFile, Message) -> Void - public let displayEmojiPackTooltip: (TelegramMediaFile, Message) -> Void - public let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void - public let openMessageReplies: (MessageId, Bool, Bool) -> Void - public let openReplyThreadOriginalMessage: (Message) -> Void - public let openMessageStats: (MessageId) -> Void - public let editMessageMedia: (MessageId, Bool) -> Void + public let displayPremiumStickerTooltip: (TelegramMediaFile, EngineRawMessage) -> Void + public let displayEmojiPackTooltip: (TelegramMediaFile, EngineRawMessage) -> Void + public let openPeerContextMenu: (EngineRawPeer, EngineMessage.Id?, ASDisplayNode, CGRect, ContextGesture?) -> Void + public let openMessageReplies: (EngineMessage.Id, Bool, Bool) -> Void + public let openReplyThreadOriginalMessage: (EngineRawMessage) -> Void + public let openMessageStats: (EngineMessage.Id) -> Void + public let editMessageMedia: (EngineMessage.Id, Bool) -> Void public let copyText: (String) -> Void public let displayUndo: (UndoOverlayContent) -> Void public let isAnimatingMessage: (UInt32) -> Bool public let getMessageTransitionNode: () -> ChatMessageTransitionProtocol? public let updateChoosingSticker: (Bool) -> Void - public let commitEmojiInteraction: (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void + public let commitEmojiInteraction: (EngineMessage.Id, String, EmojiInteraction, TelegramMediaFile) -> Void public let openLargeEmojiInfo: (String, String?, TelegramMediaFile) -> Void public let openJoinLink: (String) -> Void public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void public let activateAdAction: (EngineMessage.Id, Promise?, Bool, Bool) -> Void - public let adContextAction: (Message, ASDisplayNode, ContextGesture?) -> Void + public let adContextAction: (EngineRawMessage, ASDisplayNode, ContextGesture?) -> Void public let removeAd: (Data) -> Void public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void public let saveMediaToFiles: (EngineMessage.Id) -> Void @@ -283,43 +298,45 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void public let openStickerEditor: () -> Void - public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void - public let playMessageEffect: (Message) -> Void - public let editMessageFactCheck: (MessageId) -> Void + public let openAgeRestrictedMessageMedia: (EngineRawMessage, @escaping () -> Void) -> Void + public let playMessageEffect: (EngineRawMessage) -> Void + public let editMessageFactCheck: (EngineMessage.Id) -> Void public let sendGift: (EnginePeer.Id) -> Void public let openUniqueGift: (String) -> Void public let openMessageFeeException: () -> Void - public let requestMessageUpdate: (MessageId, Bool) -> Void + public let requestMessageUpdate: (EngineMessage.Id, Bool, ControlledTransition?) -> Void public let cancelInteractiveKeyboardGestures: () -> Void public let dismissTextInput: () -> Void - public let scrollToMessageId: (MessageIndex) -> Void - public let navigateToStory: (Message, StoryId) -> Void - public let attemptedNavigationToPrivateQuote: (Peer?) -> Void + public let scrollToMessageId: (EngineMessage.Index, CGFloat) -> Void + public let scrollToMessageIdWithAnchor: (EngineMessage.Index, String) -> Void + public let navigateToStory: (EngineRawMessage, EngineStoryId) -> Void + public let attemptedNavigationToPrivateQuote: (EngineRawPeer?) -> Void public let forceUpdateWarpContents: () -> Void public let playShakeAnimation: () -> Void - public let displayQuickShare: (MessageId, ASDisplayNode, ContextGesture) -> Void + public let displayQuickShare: (EngineMessage.Id, ASDisplayNode, ContextGesture) -> Void public let updateChatLocationThread: (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void - public let requestToggleTodoMessageItem: (MessageId, Int32, Bool) -> Void - public let displayTodoToggleUnavailable: (MessageId) -> Void + public let requestToggleTodoMessageItem: (EngineMessage.Id, Int32, Bool) -> Void + public let displayTodoToggleUnavailable: (EngineMessage.Id) -> Void public let openStarsPurchase: (Int64?) -> Void public let openRankInfo: (EnginePeer, ChatRankInfoScreenRole, String) -> Void public let openSetPeerAvatar: () -> Void + public let displayPollRestrictedToast: (EngineMessage.Id) -> Void public var canPlayMedia: Bool = false - public var hiddenMedia: [MessageId: [Media]] = [:] + public var hiddenMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] public var expandedTranslationMessageStableIds: Set = Set() public var selectionState: ChatInterfaceSelectionState? public var highlightedState: ChatInterfaceHighlightedState? public var contextHighlightedState: ChatInterfaceHighlightedState? public var automaticMediaDownloadSettings: MediaAutoDownloadSettings public var pollActionState: ChatInterfacePollActionState - public var currentPollMessageWithTooltip: MessageId? - public var currentPsaMessageWithTooltip: MessageId? + public var currentPollMessageWithTooltip: EngineMessage.Id? + public var currentPsaMessageWithTooltip: EngineMessage.Id? public var stickerSettings: ChatInterfaceStickerSettings - public var searchTextHighightState: (String, [MessageIndex])? - public var unreadMessageRange: [UnreadMessageRangeKey: Range] = [:] - public var seenOneTimeAnimatedMedia = Set() - public var currentMessageWithLoadingReplyThread: MessageId? + public var searchTextHighightState: (String, [EngineMessage.Index])? + public var unreadMessageRange: [UnreadMessageRangeKey: Range] = [:] + public var seenOneTimeAnimatedMedia = Set() + public var currentMessageWithLoadingReplyThread: EngineMessage.Id? public var updatedPresentationData: (initial: PresentationData, signal: Signal)? public let presentationContext: ChatPresentationContext public var playNextOutgoingGift: Bool = false @@ -327,9 +344,9 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var enableFullTranslucency: Bool = true public var chatIsRotated: Bool = true public var canReadHistory: Bool = false - public var summarizedMessageIds: Set = Set() + public var summarizedMessageIds: Set = Set() public var focusedTextInputIsMedia: Bool = false - public var focusedPollAddOptionMessageId: MessageId? + public var focusedPollAddOptionMessageId: EngineMessage.Id? private var isOpeningMediaValue: Bool = false public var isOpeningMedia: Bool { @@ -357,99 +374,100 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public var isSidePanelOpen: Bool = false public init( - openMessage: @escaping (Message, OpenMessageParams) -> Bool, + openMessage: @escaping (EngineRawMessage, OpenMessageParams) -> Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, OpenPeerSource) -> Void, openPeerMention: @escaping (String, Promise?) -> Void, - openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, - openMessageReactionContextMenu: @escaping (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void, - updateMessageReaction: @escaping (Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void, + openMessageContextMenu: @escaping (EngineRawMessage, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void, + openMessageReactionContextMenu: @escaping (EngineRawMessage, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void, + updateMessageReaction: @escaping (EngineRawMessage, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void, activateMessagePinch: @escaping (PinchSourceContainerNode) -> Void, - openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, - navigateToMessage: @escaping (MessageId, MessageId, NavigateToMessageParams) -> Void, - navigateToMessageStandalone: @escaping (MessageId) -> Void, - navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void, - tapMessage: ((Message) -> Void)?, + openMessageContextActions: @escaping (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void, + navigateToMessage: @escaping (EngineMessage.Id, EngineMessage.Id, NavigateToMessageParams) -> Void, + navigateToMessageStandalone: @escaping (EngineMessage.Id) -> Void, + navigateToThreadMessage: @escaping (EnginePeer.Id, Int64, EngineMessage.Id?) -> Void, + tapMessage: ((EngineRawMessage) -> Void)?, clickThroughMessage: @escaping (UIView?, CGPoint?) -> Void, - toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, + toggleMessagesSelection: @escaping ([EngineMessage.Id], Bool) -> Void, sendCurrentMessage: @escaping (Bool, ChatSendMessageEffect?) -> Void, - sendMessage: @escaping (String) -> Void, - sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool, + sendMessage: @escaping (String, EngineMessage.Id?) -> Void, + sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [EngineItemCollectionId]) -> Bool, sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool, editGif: @escaping (FileMediaReference, Bool) -> Void, - requestMessageActionCallback: @escaping (Message, MemoryBuffer?, Bool, Bool, Promise?) -> Void, + requestMessageActionCallback: @escaping (EngineRawMessage, EngineMemoryBuffer?, Bool, Bool, Promise?) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void, - activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void, + activateSwitchInline: @escaping (EnginePeer.Id?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void, openUrl: @escaping (OpenUrl) -> Void, - shareCurrentLocation: @escaping () -> Void, - shareAccountContact: @escaping () -> Void, - sendBotCommand: @escaping (MessageId?, String) -> Void, - openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, - openWallpaper: @escaping (Message) -> Void, - openTheme: @escaping (Message) -> Void, + openExternalInstantPage: @escaping (OpenInstantPage) -> Void, + shareCurrentLocation: @escaping (EngineMessage.Id?) -> Void, + shareAccountContact: @escaping (EngineMessage.Id?) -> Void, + sendBotCommand: @escaping (EngineMessage.Id?, String) -> Void, + openInstantPage: @escaping (EngineRawMessage, ChatMessageItemAssociatedData?) -> Void, + openWallpaper: @escaping (EngineRawMessage) -> Void, + openTheme: @escaping (EngineRawMessage) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, updatePresentationState: @escaping ((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void, - openMessageShareMenu: @escaping (MessageId) -> Void, + openMessageShareMenu: @escaping (EngineMessage.Id) -> Void, presentController: @escaping (ViewController, Any?) -> Void, presentControllerInCurrent: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, - callPeer: @escaping (PeerId, Bool) -> Void, - openConferenceCall: @escaping (Message) -> Void, + callPeer: @escaping (EnginePeer.Id, Bool) -> Void, + openConferenceCall: @escaping (EngineRawMessage) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, LongTapParams?) -> Void, todoItemLongTap: @escaping (Int32, LongTapParams?) -> Void, pollOptionLongTap: @escaping (Data, LongTapParams?) -> Void, - openCheckoutOrReceipt: @escaping (MessageId, OpenMessageParams?) -> Void, + openCheckoutOrReceipt: @escaping (EngineMessage.Id, OpenMessageParams?) -> Void, openSearch: @escaping () -> Void, - setupReply: @escaping (MessageId) -> Void, - canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, + setupReply: @escaping (EngineMessage.Id) -> Void, + canSetupReply: @escaping (EngineRawMessage) -> ChatControllerInteractionSwipeAction, canSendMessages: @escaping () -> Bool, navigateToFirstDateMessage: @escaping(Int32, Bool) ->Void, - requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, + requestRedeliveryOfFailedMessages: @escaping (EngineMessage.Id) -> Void, addContact: @escaping (String) -> Void, - rateCall: @escaping (Message, CallId, Bool) -> Void, - requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, - requestAddMessagePollOption: @escaping (MessageId, String, [MessageTextEntity], Data, AnyMediaReference?) -> Void, - requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, + rateCall: @escaping (EngineRawMessage, CallId, Bool) -> Void, + requestSelectMessagePollOptions: @escaping (EngineMessage.Id, [Data]) -> Void, + requestAddMessagePollOption: @escaping (EngineMessage.Id, String, [MessageTextEntity], Data, AnyMediaReference?) -> Void, + requestOpenMessagePollResults: @escaping (EngineMessage.Id, EngineMedia.Id) -> Void, openAppStorePage: @escaping () -> Void, - displayMessageTooltip: @escaping (MessageId, String, Bool, ASDisplayNode?, CGRect?) -> Void, - seekToTimecode: @escaping (Message, Double, Bool) -> Void, + displayMessageTooltip: @escaping (EngineMessage.Id, String, Bool, ASDisplayNode?, CGRect?) -> Void, + seekToTimecode: @escaping (EngineRawMessage, Double, Bool) -> Void, scheduleCurrentMessage: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, - sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, - editScheduledMessagesTime: @escaping ([MessageId]) -> Void, - performTextSelectionAction: @escaping (Message?, Bool, NSAttributedString, [MessageTextEntity]?, TextSelectionAction) -> Void, + sendScheduledMessagesNow: @escaping ([EngineMessage.Id]) -> Void, + editScheduledMessagesTime: @escaping ([EngineMessage.Id]) -> Void, + performTextSelectionAction: @escaping (EngineRawMessage?, Bool, NSAttributedString, [MessageTextEntity]?, TextSelectionAction) -> Void, displayImportedMessageTooltip: @escaping (ASDisplayNode) -> Void, displaySwipeToReplyHint: @escaping () -> Void, - dismissReplyMarkupMessage: @escaping (Message) -> Void, - openMessagePollResults: @escaping (MessageId, Data) -> Void, - openPollCreation: @escaping (Bool?) -> Void, - openPollMedia: @escaping (Message, PollMediaSubject) -> Void, + dismissReplyMarkupMessage: @escaping (EngineRawMessage) -> Void, + openMessagePollResults: @escaping (EngineMessage.Id, Data) -> Void, + openPollCreation: @escaping (EngineMessage.Id?, Bool?) -> Void, + openPollMedia: @escaping (EngineRawMessage, PollMediaSubject) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution?, ASDisplayNode?) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping (Bool, Bool) -> Void, - displayPremiumStickerTooltip: @escaping (TelegramMediaFile, Message) -> Void, - displayEmojiPackTooltip: @escaping (TelegramMediaFile, Message) -> Void, - openPeerContextMenu: @escaping (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void, - openMessageReplies: @escaping (MessageId, Bool, Bool) -> Void, - openReplyThreadOriginalMessage: @escaping (Message) -> Void, - openMessageStats: @escaping (MessageId) -> Void, - editMessageMedia: @escaping (MessageId, Bool) -> Void, + displayPremiumStickerTooltip: @escaping (TelegramMediaFile, EngineRawMessage) -> Void, + displayEmojiPackTooltip: @escaping (TelegramMediaFile, EngineRawMessage) -> Void, + openPeerContextMenu: @escaping (EngineRawPeer, EngineMessage.Id?, ASDisplayNode, CGRect, ContextGesture?) -> Void, + openMessageReplies: @escaping (EngineMessage.Id, Bool, Bool) -> Void, + openReplyThreadOriginalMessage: @escaping (EngineRawMessage) -> Void, + openMessageStats: @escaping (EngineMessage.Id) -> Void, + editMessageMedia: @escaping (EngineMessage.Id, Bool) -> Void, copyText: @escaping (String) -> Void, displayUndo: @escaping (UndoOverlayContent) -> Void, isAnimatingMessage: @escaping (UInt32) -> Bool, getMessageTransitionNode: @escaping () -> ChatMessageTransitionProtocol?, updateChoosingSticker: @escaping (Bool) -> Void, - commitEmojiInteraction: @escaping (MessageId, String, EmojiInteraction, TelegramMediaFile) -> Void, + commitEmojiInteraction: @escaping (EngineMessage.Id, String, EmojiInteraction, TelegramMediaFile) -> Void, openLargeEmojiInfo: @escaping (String, String?, TelegramMediaFile) -> Void, openJoinLink: @escaping (String) -> Void, openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void, activateAdAction: @escaping (EngineMessage.Id, Promise?, Bool, Bool) -> Void, - adContextAction: @escaping (Message, ASDisplayNode, ContextGesture?) -> Void, + adContextAction: @escaping (EngineRawMessage, ASDisplayNode, ContextGesture?) -> Void, removeAd: @escaping (Data) -> Void, openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void, saveMediaToFiles: @escaping (EngineMessage.Id) -> Void, @@ -460,27 +478,29 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void, openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void, openStickerEditor: @escaping () -> Void, - openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void, - playMessageEffect: @escaping (Message) -> Void, - editMessageFactCheck: @escaping (MessageId) -> Void, + openAgeRestrictedMessageMedia: @escaping (EngineRawMessage, @escaping () -> Void) -> Void, + playMessageEffect: @escaping (EngineRawMessage) -> Void, + editMessageFactCheck: @escaping (EngineMessage.Id) -> Void, sendGift: @escaping (EnginePeer.Id) -> Void, openUniqueGift: @escaping (String) -> Void, openMessageFeeException: @escaping () -> Void, - requestMessageUpdate: @escaping (MessageId, Bool) -> Void, + requestMessageUpdate: @escaping (EngineMessage.Id, Bool, ControlledTransition?) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, dismissTextInput: @escaping () -> Void, - scrollToMessageId: @escaping (MessageIndex) -> Void, - navigateToStory: @escaping (Message, StoryId) -> Void, - attemptedNavigationToPrivateQuote: @escaping (Peer?) -> Void, + scrollToMessageId: @escaping (EngineMessage.Index, CGFloat) -> Void, + scrollToMessageIdWithAnchor: @escaping (EngineMessage.Index, String) -> Void, + navigateToStory: @escaping (EngineRawMessage, EngineStoryId) -> Void, + attemptedNavigationToPrivateQuote: @escaping (EngineRawPeer?) -> Void, forceUpdateWarpContents: @escaping () -> Void, playShakeAnimation: @escaping () -> Void, - displayQuickShare: @escaping (MessageId, ASDisplayNode, ContextGesture) -> Void, + displayQuickShare: @escaping (EngineMessage.Id, ASDisplayNode, ContextGesture) -> Void, updateChatLocationThread: @escaping (Int64?, ChatControllerAnimateInnerChatSwitchDirection?) -> Void, - requestToggleTodoMessageItem: @escaping (MessageId, Int32, Bool) -> Void, - displayTodoToggleUnavailable: @escaping (MessageId) -> Void, + requestToggleTodoMessageItem: @escaping (EngineMessage.Id, Int32, Bool) -> Void, + displayTodoToggleUnavailable: @escaping (EngineMessage.Id) -> Void, openStarsPurchase: @escaping (Int64?) -> Void, openRankInfo: @escaping (EnginePeer, ChatRankInfoScreenRole, String) -> Void, openSetPeerAvatar: @escaping () -> Void, + displayPollRestrictedToast: @escaping (EngineMessage.Id) -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings, @@ -511,6 +531,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.requestMessageActionUrlAuth = requestMessageActionUrlAuth self.activateSwitchInline = activateSwitchInline self.openUrl = openUrl + self.openExternalInstantPage = openExternalInstantPage self.shareCurrentLocation = shareCurrentLocation self.shareAccountContact = shareAccountContact self.sendBotCommand = sendBotCommand @@ -600,6 +621,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures self.dismissTextInput = dismissTextInput self.scrollToMessageId = scrollToMessageId + self.scrollToMessageIdWithAnchor = scrollToMessageIdWithAnchor self.navigateToStory = navigateToStory self.attemptedNavigationToPrivateQuote = attemptedNavigationToPrivateQuote self.forceUpdateWarpContents = forceUpdateWarpContents @@ -611,6 +633,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol self.openStarsPurchase = openStarsPurchase self.openRankInfo = openRankInfo self.openSetPeerAvatar = openSetPeerAvatar + self.displayPollRestrictedToast = displayPollRestrictedToast self.automaticMediaDownloadSettings = automaticMediaDownloadSettings diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD index 18aedceb49..85a17851ad 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD @@ -17,13 +17,18 @@ swift_library( "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard", + "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/TextFormat:TextFormat", "//submodules/AppBundle:AppBundle", + "//submodules/SearchBarNode:SearchBarNode", "//submodules/TelegramUI/Components/ChatInputNode:ChatInputNode", "//submodules/Components/PagerComponent:PagerComponent", "//submodules/PremiumUI:PremiumUI", @@ -38,8 +43,8 @@ swift_library( "//submodules/TelegramUI/Components/ChatControllerInteraction:ChatControllerInteraction", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", "//submodules/TelegramUI/Components/EntityKeyboardGifContent:EntityKeyboardGifContent", + "//submodules/TelegramUI/Components/GlassControls", "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView", - "//submodules/AttachmentTextInputPanelNode", "//submodules/TelegramUI/Components/BatchVideoRendering", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/SubcodecAnimationCacheImpl:SubcodecAnimationCacheImpl", diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 2b3544686b..586c7e7219 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -30,7 +30,6 @@ import FeaturedStickersScreen import Pasteboard import EntityKeyboardGifContent import LegacyMessageInputPanelInputView -import AttachmentTextInputPanelNode import GlassBackgroundComponent private let keyboardCornerRadius: CGFloat = 30.0 @@ -64,7 +63,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let getNavigationController: () -> NavigationController? let requestLayout: (ContainedViewLayoutTransition) -> Void public var forceTheme: PresentationTheme? - + public init( sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool, sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, @@ -98,7 +97,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.getNavigationController = getNavigationController self.requestLayout = requestLayout } - + public init(chatControllerInteraction: ChatControllerInteraction, panelInteraction: ChatPanelInterfaceInteraction) { self.sendSticker = chatControllerInteraction.sendSticker self.sendEmoji = chatControllerInteraction.sendEmoji @@ -121,13 +120,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.requestLayout = panelInteraction.requestLayout } } - + public struct InputData: Equatable { public var emoji: EmojiPagerContentComponent? public var stickers: EmojiPagerContentComponent? public var gifs: EntityKeyboardGifContent? public var availableGifSearchEmojies: [EntityKeyboardComponent.GifSearchEmoji] - + public init( emoji: EmojiPagerContentComponent?, stickers: EmojiPagerContentComponent?, @@ -140,14 +139,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.availableGifSearchEmojies = availableGifSearchEmojies } } - + public final class StateContext { let emojiState = EmojiPagerContentComponent.StateContext() - + public init() { } } - + public static func hasPremium(context: AccountContext, chatPeerId: EnginePeer.Id?, premiumIfSavedMessages: Bool) -> Signal { let hasPremium: Signal if premiumIfSavedMessages, let chatPeerId = chatPeerId, chatPeerId == context.account.peerId { @@ -164,7 +163,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } return hasPremium } - + public static func inputData( context: AccountContext, chatPeerId: PeerId?, @@ -181,7 +180,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) -> Signal { let animationCache = context.animationCache let animationRenderer = context.animationRenderer - + let emojiItems = EmojiPagerContentComponent.emojiInputData( context: context, animationCache: animationCache, @@ -198,12 +197,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { hideBackground: hideBackground, maskEdge: maskEdge ) - + let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] let stickerOrderedItemListCollectionIds: [Int32] = [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers] - + let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings - + let stickerItems: Signal if hasStickers { stickerItems = EmojiPagerContentComponent.stickerInputData( @@ -226,20 +225,20 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else { stickerItems = .single(nil) } - + let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) |> map { appConfiguration -> [String] in let defaultReactions: [String] = ["👍", "👎", "😍", "😂", "😯", "😕", "😢", "😡", "💪", "👏", "🙈", "😒"] - + guard let data = appConfiguration.data, let emojis = data["gif_search_emojies"] as? [String] else { return defaultReactions } return emojis } |> distinctUntilChanged - + let animatedEmojiStickers: Signal<[String: [StickerPackItem]], NoError> - + if hasGifs { animatedEmojiStickers = context.engine.stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false) |> map { animatedEmoji -> [String: [StickerPackItem]] in @@ -263,7 +262,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else { animatedEmojiStickers = .single([:]) } - + let gifInputInteraction = GifPagerContentComponent.InputInteraction( performItemAction: { item, view, rect in if let sendGif { @@ -281,7 +280,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { hideBackground: hideBackground, hasSearch: hasSearch ) - + // We are going to subscribe to the actual data when the view is loaded let gifItems: Signal if hasGifs { @@ -304,7 +303,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else { gifItems = .single(nil) } - + return combineLatest(queue: .mainQueue(), emojiItems, stickerItems, @@ -341,15 +340,15 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { default: break } - + guard let title = title else { continue } - + availableGifSearchEmojies.append(EntityKeyboardComponent.GifSearchEmoji(emoji: reaction, file: file._parse(), title: title)) } } - + return InputData( emoji: emoji, stickers: stickers, @@ -358,11 +357,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) } } - + private let context: AccountContext private let stateContext: StateContext? private let entityKeyboardView: ComponentView - + private let defaultToEmojiTab: Bool private var stableReorderableGroupOrder: [EntityKeyboardComponent.ReorderCategory: [ItemCollectionId]] = [:] private var currentInputData: InputData @@ -371,32 +370,34 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { private let opaqueTopPanelBackground: Bool private let useOpaqueTheme: Bool private let displayBottomPanel: Bool - + private struct EmojiSearchResult { var groups: [EmojiPagerContentComponent.ItemGroup] var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } - + private struct EmojiSearchState { var result: EmojiSearchResult? var isSearching: Bool - + init(result: EmojiSearchResult?, isSearching: Bool) { self.result = result self.isSearching = isSearching } } - + private let emojiSearchDisposable = MetaDisposable() + private var emojiSearchContext: EmojiSearchContext? private let emojiSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) { didSet { self.emojiSearchState.set(.single(self.emojiSearchStateValue)) } } - + private let stickerSearchDisposable = MetaDisposable() private let stickerSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) private var stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false) { @@ -404,27 +405,27 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.stickerSearchState.set(.single(self.stickerSearchStateValue)) } } - + private let interaction: ChatEntityKeyboardInputNode.Interaction? private var inputNodeInteraction: ChatMediaInputNodeInteraction? - + private let trendingGifsPromise = Promise(nil) - + private var isMarkInputCollapsed: Bool = false - + private var isEmojiSearchActive: Bool = false { didSet { self.followsDefaultHeight = !self.isEmojiSearchActive } } - + public var clipContentToTopPanel: Bool = false - + public var externalTopPanelContainerImpl: PagerExternalTopPanelContainer? public override var externalTopPanelContainer: UIView? { return self.externalTopPanelContainerImpl } - + private let clippingView: UIView private var backgroundView: BlurredBackgroundView? private var backgroundTintView: UIImageView? @@ -432,14 +433,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { private var backgroundTintMaskView: UIView? private var backgroundTintMaskContentView: UIView? private var externalBackground: EmojiPagerContentComponent.ExternalBackground? - + public var switchToTextInput: (() -> Void)? - + private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? - + private var scheduledContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? private var scheduledInnerTransition: ComponentTransition? - + private var gifMode: GifPagerContentComponent.Subject? { didSet { if let gifMode = self.gifMode, gifMode != oldValue { @@ -447,7 +448,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } } - + public var canSwitchToTextInputAutomatically: Bool { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View, let centralId = pagerView.centralId { if centralId == AnyHashable("emoji") { @@ -456,9 +457,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } return true } - + public var useExternalSearchContainer: Bool = false - + private var gifContext: GifContext? { didSet { if let gifContext = self.gifContext { @@ -468,12 +469,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } private let gifComponent = Promise() private var gifInputInteraction: GifPagerContentComponent.InputInteraction? - + fileprivate var emojiInputInteraction: EmojiPagerContentComponent.InputInteraction? private var stickerInputInteraction: EmojiPagerContentComponent.InputInteraction? - + private weak var currentUndoOverlayController: UndoOverlayController? - + private var choosingStickerDisposable: Disposable? private var scrollingStickersGridPromise = Promise(false) private var previewingStickersPromise = ValuePromise(false) @@ -484,7 +485,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } |> distinctUntilChanged } - + public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, useOpaqueTheme: Bool = false, interaction: ChatEntityKeyboardInputNode.Interaction?, chatPeerId: PeerId?, stateContext: StateContext?, forceHasPremium: Bool = false, displayBottomPanel: Bool = true) { self.context = context self.currentInputData = currentInputData @@ -493,31 +494,31 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.useOpaqueTheme = useOpaqueTheme self.stateContext = stateContext self.displayBottomPanel = displayBottomPanel - + self.interaction = interaction - + self.clippingView = SparseContainerView() self.clippingView.clipsToBounds = true self.clippingView.layer.cornerRadius = keyboardCornerRadius - + self.entityKeyboardView = ComponentView() - + super.init() - + self.currentInputData = self.processInputData(inputData: self.currentInputData) - + self.topBackgroundExtension = 34.0 self.followsDefaultHeight = true - + if "".isEmpty { let backgroundView = BlurredBackgroundView(color: .black, enableBlur: true) self.backgroundView = backgroundView self.view.addSubview(backgroundView) - + let backgroundTintView = UIImageView() self.backgroundTintView = backgroundTintView self.view.addSubview(backgroundTintView) - + let backgroundTintMaskView = UIView() backgroundTintMaskView.backgroundColor = .white self.backgroundTintMaskView = backgroundTintMaskView @@ -525,31 +526,31 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { backgroundTintMaskView.layer.filters = [filter] } backgroundTintView.mask = backgroundTintMaskView - + let backgroundTintMaskContentView = UIView() backgroundTintMaskView.addSubview(backgroundTintMaskContentView) self.backgroundTintMaskContentView = backgroundTintMaskContentView - + let backgroundChromeView = UIImageView() self.backgroundChromeView = backgroundChromeView - + self.externalBackground = EmojiPagerContentComponent.ExternalBackground( effectContainerView: backgroundTintMaskContentView ) } - + self.view.addSubview(self.clippingView) - + if let backgroundChromeView = self.backgroundChromeView { self.view.addSubview(backgroundChromeView) } - + self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer() - + var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? if let interaction { let context = self.context - + stickerPeekBehavior = EmojiContentPeekBehaviorImpl( context: self.context, interaction: EmojiContentPeekBehaviorImpl.Interaction( @@ -561,7 +562,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { switch attribute { case let .CustomEmoji(_, _, displayText, stickerPackReference): text = displayText - + var packId: ItemCollectionId? if case let .id(id, _) = stickerPackReference { packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) @@ -572,7 +573,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { break } } - + if let emojiAttribute { interaction.sendEmoji(text, emojiAttribute, true) } @@ -582,16 +583,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return } let _ = strongSelf.context.engine.accountData.setEmojiStatus(file: file, expirationDate: nil).start() - + var animateInAsReplacement = false if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() strongSelf.currentUndoOverlayController = nil animateInAsReplacement = true } - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - + let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.EmojiStatus_AppliedText, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false }) strongSelf.currentUndoOverlayController = controller interaction.presentController(controller, nil) @@ -600,33 +601,33 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let strongSelf = self else { return } - + var text = "." var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? loop: for attribute in file.attributes { switch attribute { case let .CustomEmoji(_, _, displayText, _): text = displayText - + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file) break loop default: break } } - + if let _ = emojiAttribute { storeMessageTextInPasteboard(text, entities: [MessageTextEntity(range: 0 ..< (text as NSString).length, type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id))]) - + var animateInAsReplacement = false if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { currentUndoOverlayController.dismissWithCommitActionAndReplacementAnimation() strongSelf.currentUndoOverlayController = nil animateInAsReplacement = true } - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - + let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Conversation_EmojiCopied, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in return false }) strongSelf.currentUndoOverlayController = controller interaction.presentController(controller, nil) @@ -645,7 +646,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } ) } - + var premiumToastCounter = 0 self.emojiInputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self, weak interaction] groupId, item, _, _, _, _ in @@ -659,7 +660,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let strongSelf = self, let interaction else { return } - + if groupId == AnyHashable("featuredTop"), let file = item.itemFile { let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) let _ = (combineLatest( @@ -677,18 +678,18 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let self else { return } - + let _ = interaction - + var installedCollectionIds = Set() for (id, _, _) in emojiPacksView.collectionInfos { installedCollectionIds.insert(id) } - + let stickerPacks = view.items.map({ $0.contents.get(FeaturedStickerPackItem.self)! }).filter({ !installedCollectionIds.contains($0.info.id) }) - + for featuredStickerPack in stickerPacks { if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View, let emojiInputInteraction = self.emojiInputInteraction { @@ -713,7 +714,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { switch attribute { case let .CustomEmoji(_, _, displayText, _): text = displayText - + var packId: ItemCollectionId? if let id = groupId.base as? ItemCollectionId { packId = id @@ -724,7 +725,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { break } } - + if file.isPremiumEmoji && !hasPremium && groupId != AnyHashable("peerSpecific") && !forceHasPremium { var animateInAsReplacement = false if let currentUndoOverlayController = strongSelf.currentUndoOverlayController { @@ -732,9 +733,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.currentUndoOverlayController = nil animateInAsReplacement = true } - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - + premiumToastCounter += 1 var suggestSavedMessages = premiumToastCounter % 2 == 0 if chatPeerId == nil { @@ -749,19 +750,19 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { text = presentationData.strings.EmojiInput_PremiumEmojiToast_Text actionTitle = presentationData.strings.EmojiInput_PremiumEmojiToast_Action } - + let controller = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: text, undoText: actionTitle, customAction: { [weak interaction] in guard let interaction else { return } - + if suggestSavedMessages { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> deliverOnMainQueue).start(next: { peer in guard let peer = peer, let navigationController = interaction.getNavigationController() else { return } - + context.sharedContext.navigateToChatController(NavigateToChatControllerParams( navigationController: navigationController, chatController: nil, @@ -791,14 +792,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { interaction.presentController(controller, nil) return } - + if let emojiAttribute = emojiAttribute { AudioServicesPlaySystemSound(0x450) interaction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) + (strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels() } } else if case let .staticEmoji(staticEmoji) = item.content { AudioServicesPlaySystemSound(0x450) interaction.insertText(NSAttributedString(string: staticEmoji, attributes: [:])) + (strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels() } }) }, @@ -817,7 +820,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let interaction, let collectionId = groupId.base as? ItemCollectionId else { return } - + if isPremiumLocked { var replaceImpl: ((ViewController) -> Void)? let controller = PremiumDemoScreen(context: context, subject: .animatedEmoji, action: { @@ -828,24 +831,19 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { controller?.replace(with: c) } interaction.getNavigationController()?.pushViewController(controller) - + return } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()) + |> deliverOnMainQueue).start(next: { featuredEmojiPacks in + for featuredEmojiPack in featuredEmojiPacks { if featuredEmojiPack.info.id == collectionId { if let strongSelf = self { strongSelf.scheduledContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: scrollToGroup)) } let _ = context.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start() - + break } } @@ -874,15 +872,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ])]) interaction.presentController(actionSheet, nil) } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()) + |> deliverOnMainQueue).start(next: { featuredEmojiPacks in var emojiPackIds: [Int64] = [] - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredEmojiPack in featuredEmojiPacks { emojiPackIds.append(featuredEmojiPack.info.id.id) } let _ = ApplicationSpecificNotice.setDismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager, values: emojiPackIds).start() @@ -923,20 +916,23 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let self = self else { return } - + switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) - + if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { let context = self.context - + self.emojiSearchContext = nil + var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { signal = signal @@ -950,7 +946,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) } } - + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> Bool in guard case let .user(user) = peer else { @@ -959,43 +955,46 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return user.isPremium } |> distinctUntilChanged - + let resultSignal = combineLatest( signal, hasPremium ) - |> mapToSignal { keywords, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords, hasPremium -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword } } - let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> - let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> + + let emojiSearchContext: EmojiSearchContext? + let remoteSignal: Signal + let remotePacksSignal: Signal if hasPremium { - remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let currentEmojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + emojiSearchContext = currentEmojiSearchContext + remoteSignal = currentEmojiSearchContext.state remotePacksSignal = context.engine.stickers.searchEmojiSets(query: query) |> mapToSignal { localResult in - return .single((localResult, false)) + return .single(localResult) |> then( context.engine.stickers.searchEmojiSetsRemotely(query: query) |> map { remoteResult in - return (localResult.merge(with: remoteResult), true) + return localResult.merge(with: remoteResult) } ) } } else { - remoteSignal = .single(([], true)) - remotePacksSignal = .single((FoundStickerSets(), true)) + emojiSearchContext = nil + remoteSignal = .single(EmojiSearchContext.State(items: [], canLoadMore: false, isLoadingMore: false)) + remotePacksSignal = .single(FoundStickerSets()) } + return combineLatest(remoteSignal, remotePacksSignal) - |> mapToSignal { foundEmoji, foundPacks -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - if foundEmoji.items.isEmpty && !foundEmoji.isFinalResult { - return .complete() - } + |> map { foundEmoji, foundPacks -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in var items: [EmojiPagerContentComponent.Item] = [] - + let appendUnicodeEmoji = { for (_, list) in EmojiPagerContentComponent.staticEmojiMapping { for emojiString in list { @@ -1013,11 +1012,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } } - + if !hasPremium { appendUnicodeEmoji() } - + var existingIds = Set() for itemFile in foundEmoji.items { if existingIds.contains(itemFile.fileId) { @@ -1038,11 +1037,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) items.append(item) } - + if hasPremium { appendUnicodeEmoji() } - + var resultGroups: [EmojiPagerContentComponent.ItemGroup] = [] resultGroups.append(EmojiPagerContentComponent.ItemGroup( supergroupId: "search", @@ -1062,25 +1061,25 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { fillWithLoadingPlaceholders: false, items: items )) - - for (collectionId, info, _, _) in foundPacks.sets.infos { + + for (collectionId, info, _, _) in foundPacks.infos { if let info = info as? StickerPackCollectionInfo { var topItems: [StickerPackItem] = [] - for e in foundPacks.sets.entries { + for e in foundPacks.entries { if let item = e.item as? StickerPackItem { if e.index.collectionId == collectionId { topItems.append(item) } } } - + var groupItems: [EmojiPagerContentComponent.Item] = [] for item in topItems { var tintMode: EmojiPagerContentComponent.Item.TintMode = .none if item.file.isCustomTemplateEmoji { tintMode = .primary } - + let animationData = EntityKeyboardAnimationData(file: item.file) let resultItem = EmojiPagerContentComponent.Item( animationData: animationData, @@ -1090,10 +1089,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { icon: .none, tintMode: tintMode ) - + groupItems.append(resultItem) } - + resultGroups.append(EmojiPagerContentComponent.ItemGroup( supergroupId: AnyHashable(info.id), groupId: AnyHashable(info.id), @@ -1114,29 +1113,31 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { )) } } - - return .single(resultGroups) + + return (resultGroups, foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } - + var version = 0 self.emojiSearchStateValue.isSearching = true self.emojiSearchDisposable.set((resultSignal - |> delay(0.15, queue: .mainQueue()) + |> delay(0.25, queue: .mainQueue()) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return } - - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.emojiSearchContext = nil let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { @@ -1154,7 +1155,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) items.append(item) } - + return .single(([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", @@ -1174,14 +1175,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { items: items )], isFinalResult)) } - + var version = 0 self.emojiSearchDisposable.set((resultSignal |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return } - + guard let group = result.items.first else { return } @@ -1205,10 +1206,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -1216,6 +1217,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { updateScrollingToItemGroup: { }, onScroll: {}, + loadMore: { [weak self] in + self?.emojiSearchContext?.loadMore() + }, chatPeerId: chatPeerId, peekBehavior: stickerPeekBehavior, customLayout: nil, @@ -1227,7 +1231,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { stateContext: self.stateContext?.emojiState, addImage: nil ) - + self.stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak interaction] groupId, item, view, rect, layer, _ in let _ = (ChatEntityKeyboardInputNode.hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: false) |> take(1) |> deliverOnMainQueue).start(next: { hasPremium in @@ -1240,7 +1244,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } return } - + if groupId == AnyHashable("featuredTop") { let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) let _ = (context.account.postbox.combinedView(keys: [viewKey]) @@ -1265,7 +1269,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return interaction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, []) } )) - + break } } @@ -1274,14 +1278,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if file.isPremiumSticker && !hasPremium { let controller = PremiumIntroScreen(context: context, source: .stickers) interaction.getNavigationController()?.pushViewController(controller) - + return } var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] if let id = groupId.base as? ItemCollectionId, context.sharedContext.currentStickerSettings.with({ $0 }).dynamicPackOrder { bubbleUpEmojiOrStickersets.append(id) } - + let reference: FileMediaReference if groupId == AnyHashable("saved") { reference = .savedSticker(media: file) @@ -1333,22 +1337,17 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let interaction, let collectionId = groupId.base as? ItemCollectionId else { return } - + if isPremiumLocked { let controller = PremiumIntroScreen(context: context, source: .stickers) interaction.getNavigationController()?.pushViewController(controller) - + return } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedStickerPacks()) + |> deliverOnMainQueue).start(next: { featuredStickerPacks in + for featuredStickerPack in featuredStickerPacks { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -1357,7 +1356,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -1368,7 +1367,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } |> deliverOnMainQueue).start(completed: { }) - + break } } @@ -1397,15 +1396,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ])]) interaction.presentController(actionSheet, nil) } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedStickerPacks()) + |> deliverOnMainQueue).start(next: { featuredStickerPacks in var stickerPackIds: [Int64] = [] - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in featuredStickerPacks { stickerPackIds.append(featuredStickerPack.info.id.id) } let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() @@ -1470,7 +1464,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let strongSelf = self else { return } - + switch query { case .none: strongSelf.stickerSearchDisposable.set(nil) @@ -1482,7 +1476,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let resultSignal = strongSelf.context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote]) |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - + var existingIds = Set() for item in files.items { let itemFile = item.file @@ -1501,7 +1495,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { ) items.append(item) } - + return .single(([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", @@ -1521,7 +1515,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { items: items )], files.isFinalResult)) } - + var version = 0 strongSelf.stickerSearchDisposable.set((resultSignal |> deliverOnMainQueue).start(next: { result in @@ -1552,10 +1546,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -1574,7 +1568,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { stateContext: nil, addImage: nil ) - + self.inputDataDisposable = (combineLatest(queue: .mainQueue(), updatedInputData, .single(self.currentInputData.gifs) |> then(self.gifComponent.get() |> map(Optional.init)), @@ -1587,12 +1581,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } var inputData = inputData inputData.gifs = gifs - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - + if let emojiSearchResult = emojiSearchState.result { var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? - if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { + if !emojiSearchState.isSearching && !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { emptySearchResults = EmojiPagerContentComponent.EmptySearchResults( text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult, iconFile: nil @@ -1600,14 +1594,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } if let emoji = inputData.emoji { let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true) - inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState) + inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState, canLoadMore: emojiSearchResult.canLoadMore) } } else if emojiSearchState.isSearching { if let emoji = inputData.emoji { inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching) } } - + if let stickerSearchResult = stickerSearchState.result { var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults? if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { @@ -1618,17 +1612,17 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } if let stickers = inputData.stickers { let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true) - inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: stickerSearchState.isSearching ? .searching : defaultSearchState) + inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: stickerSearchState.isSearching ? .searching : defaultSearchState, canLoadMore: stickerSearchResult.canLoadMore) } } else if stickerSearchState.isSearching { if let stickers = inputData.stickers { inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickers.contentItemGroups, itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: stickers.emptySearchResults, searchState: .searching) } } - + var transition: ComponentTransition = .immediate var useAnimation = false - + if let pagerView = strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View, let centralId = pagerView.centralId { if centralId == AnyHashable("emoji") { useAnimation = strongSelf.currentInputData.emoji != inputData.emoji @@ -1636,7 +1630,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { useAnimation = strongSelf.currentInputData.stickers != inputData.stickers } } - + if useAnimation { let contentAnimation: EmojiPagerContentComponent.ContentAnimation if let scheduledContentAnimationHint = strongSelf.scheduledContentAnimationHint { @@ -1650,7 +1644,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.currentInputData = strongSelf.processInputData(inputData: inputData) strongSelf.performLayout(transition: transition) }) - + self.inputNodeInteraction = ChatMediaInputNodeInteraction( navigateToCollectionId: { _ in }, @@ -1673,7 +1667,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { clearRecentlyUsedStickers: { } ) - + self.trendingGifsPromise.set(.single(nil)) self.trendingGifsPromise.set(paneGifSearchForQuery(context: context, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil) |> map { items -> ChatMediaInputGifPaneTrendingState? in @@ -1683,13 +1677,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return nil } }) - + self.gifInputInteraction = GifPagerContentComponent.InputInteraction( performItemAction: { [weak interaction] item, view, rect in guard let interaction else { return } - + if let (collection, result) = item.contextResult { let _ = interaction.sendBotContextResultAsGif(collection, result, view, rect, false, false) } else { @@ -1726,25 +1720,25 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { hideBackground: currentInputData.gifs?.component.hideBackground ?? false, hasSearch: currentInputData.gifs?.component.inputInteraction.hasSearch ?? false ) - + self.switchToTextInput = { [weak self] in if let self { self.interaction?.switchToTextInput() } } - + if self.currentInputData.gifs != nil { let hasRecentGifs = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs)) |> map { savedGifs -> Bool in return !savedGifs.isEmpty } - + self.hasRecentGifsDisposable = (hasRecentGifs |> deliverOnMainQueue).start(next: { [weak self] hasRecentGifs in guard let strongSelf = self else { return } - + if let gifMode = strongSelf.gifMode { if !hasRecentGifs, case .recent = gifMode { strongSelf.gifMode = .trending @@ -1754,7 +1748,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } }) } - + self.choosingStickerDisposable = (self.choosingSticker |> deliverOnMainQueue).start(next: { [weak self] value in if let self { @@ -1762,7 +1756,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } }) } - + deinit { self.inputDataDisposable?.dispose() self.hasRecentGifsDisposable?.dispose() @@ -1770,17 +1764,17 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.stickerSearchDisposable.dispose() self.choosingStickerDisposable?.dispose() } - + private func reloadGifContext() { if let gifInputInteraction = self.gifInputInteraction, let gifMode = self.gifMode { self.gifContext = GifContext(context: self.context, subject: gifMode, gifInputInteraction: gifInputInteraction, trendingGifs: self.trendingGifsPromise.get()) } } - + public func markInputCollapsed() { self.isMarkInputCollapsed = true } - + private func performLayout(transition: ComponentTransition) { guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) = self.currentState else { return @@ -1788,14 +1782,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.scheduledInnerTransition = transition let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) } - + public func simulateUpdateLayout(isVisible: Bool) { guard let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, _, isExpanded) = self.currentState else { return } let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) } - + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if self.alpha.isZero || !self.view.isUserInteractionEnabled { return nil @@ -1805,7 +1799,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return result } } - + if let backgroundView = self.backgroundView, backgroundView.frame.contains(point) { for subview in self.view.subviews.reversed() { if let result = subview.hitTest(self.view.convert(point, to: subview), with: event) { @@ -1813,13 +1807,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } } - + return nil } - + public override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) - + let innerTransition: ComponentTransition if let scheduledInnerTransition = self.scheduledInnerTransition { self.scheduledInnerTransition = nil @@ -1827,40 +1821,40 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else { innerTransition = ComponentTransition(transition) } - + let wasMarkedInputCollapsed = self.isMarkInputCollapsed self.isMarkInputCollapsed = false - + var expandedHeight = standardInputHeight if self.isEmojiSearchActive && !isExpanded { expandedHeight += 118.0 } - + var hiddenInputHeight: CGFloat = 0.0 if self.hideInput && !self.adjustLayoutForHiddenInput { hiddenInputHeight = inputPanelHeight } - + let context = self.context let interaction = self.interaction let inputNodeInteraction = self.inputNodeInteraction! let trendingGifsPromise = self.trendingGifsPromise - + var mappedTransition = innerTransition - + if wasMarkedInputCollapsed || !isExpanded { mappedTransition = mappedTransition.withUserData(EntityKeyboardComponent.MarkInputCollapsed()) } - + var emojiContent: EmojiPagerContentComponent? = self.currentInputData.emoji var stickerContent: EmojiPagerContentComponent? = self.currentInputData.stickers var gifContent: EntityKeyboardGifContent? = self.currentInputData.gifs - + var stickersEnabled = true var emojiEnabled = true if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel { if let boostsToUnrestrict = interfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 { - + } else { if peer.hasBannedPermission(.banSendStickers) != nil { stickersEnabled = false @@ -1877,7 +1871,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { emojiEnabled = false } } - + if !stickersEnabled || interfaceState.interfaceState.editMessage != nil { stickerContent = nil gifContent = nil @@ -1896,16 +1890,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { gifContent = nil } } - + stickerContent?.inputInteractionHolder.inputInteraction = self.stickerInputInteraction self.currentInputData.emoji?.inputInteractionHolder.inputInteraction = self.emojiInputInteraction - + if let stickerInputInteraction = self.stickerInputInteraction { self.scrollingStickersGridPromise.set(stickerInputInteraction.scrollingStickersGridPromise.get()) } - + let startTime = CFAbsoluteTimeGetCurrent() - + var keyboardBottomInset = bottomInset if case .regular = layoutMetrics.widthClass, inputHeight > 0.0 && inputHeight < 100.0 { keyboardBottomInset = inputHeight + 15.0 @@ -1983,7 +1977,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { case .gifs: mappedMode = .gif } - + let searchContainerNode = PaneSearchContainerNode( context: context, theme: interfaceState.theme, @@ -2003,7 +1997,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } self.openGifContextMenu(file: item.file, contextResult: item.contextResult, sourceView: sourceNode.view, sourceRect: sourceRect, gesture: gesture, isSaved: isSaved) } - + return searchContainerNode }, contentIdUpdated: { _ in }, @@ -2018,26 +2012,26 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { environment: {}, containerSize: CGSize(width: width, height: expandedHeight) ) - + var clippingFrame = CGRect(origin: CGPoint(), size: entityKeyboardSize) clippingFrame.size.height += 32.0 - + var entityKeyboardSizeFrame = CGRect(origin: CGPoint(), size: entityKeyboardSize) if self.hideInput { clippingFrame.size.height += self.topBackgroundExtension clippingFrame.origin.y -= self.topBackgroundExtension entityKeyboardSizeFrame.origin.y += self.topBackgroundExtension } - + if let entityKeyboardComponentView = self.entityKeyboardView.view { if entityKeyboardComponentView.superview == nil { self.clippingView.addSubview(entityKeyboardComponentView) } transition.updateFrame(view: entityKeyboardComponentView, frame: entityKeyboardSizeFrame) } - + transition.updateFrame(view: self.clippingView, frame: clippingFrame) - + if let backgroundView = self.backgroundView, let backgroundTintView = self.backgroundTintView, let backgroundTintMaskView = self.backgroundTintMaskView, let backgroundTintMaskContentView = self.backgroundTintMaskContentView, let backgroundChromeView = self.backgroundChromeView { var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize) if self.hideInput { @@ -2045,7 +2039,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { backgroundFrame.origin.y -= self.topBackgroundExtension } backgroundFrame.size.height += 32.0 - + if backgroundChromeView.image == nil { backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: keyboardCornerRadius * 2.0, height: keyboardCornerRadius * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear) } @@ -2053,33 +2047,33 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { backgroundTintView.image = generateStretchableFilledCircleImage(diameter: keyboardCornerRadius * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) } backgroundTintView.tintColor = interfaceState.theme.chat.inputMediaPanel.backgroundColor - + transition.updateFrame(view: backgroundView, frame: backgroundFrame) backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate) backgroundView.update(size: backgroundFrame.size, cornerRadius: keyboardCornerRadius, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition) - + transition.updateFrame(view: backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0)) - + var backgroundTintMaskContentFrame = CGRect(origin: CGPoint(), size: backgroundFrame.size) if self.hideInput { backgroundTintMaskContentFrame.origin.y += self.topBackgroundExtension } transition.updateFrame(view: backgroundTintView, frame: backgroundFrame) - + transition.updateFrame(view: backgroundTintMaskView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) transition.updateFrame(view: backgroundTintMaskContentView, frame: backgroundTintMaskContentFrame) } - + let layoutTime = CFAbsoluteTimeGetCurrent() - startTime if layoutTime > 0.1 { #if DEBUG print("EntityKeyboard layout in \(layoutTime * 1000.0) ms") #endif } - + return (expandedHeight, 0.0) } - + private func processStableItemGroupList(category: EntityKeyboardComponent.ReorderCategory, itemGroups: [EmojiPagerContentComponent.ItemGroup]) -> [EmojiPagerContentComponent.ItemGroup] { let nextIds: [ItemCollectionId] = itemGroups.compactMap { group -> ItemCollectionId? in if group.isEmbedded { @@ -2094,11 +2088,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return nil } } - + let stableOrder = self.stableReorderableGroupOrder[category] ?? nextIds - + var updatedGroups: [EmojiPagerContentComponent.ItemGroup] = [] - + var staticIsFirst = false let topStaticGroups: [String] = [ "static", @@ -2120,7 +2114,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { break } } - + for group in itemGroups { if !(group.groupId.base is ItemCollectionId) { if group.groupId != AnyHashable("static") || staticIsFirst { @@ -2148,7 +2142,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { updatedGroups.append(group) } } - + let updatedIds = updatedGroups.compactMap { group -> ItemCollectionId? in if group.isEmbedded { return nil @@ -2164,10 +2158,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } self.stableReorderableGroupOrder[category] = updatedIds - + return updatedGroups } - + private func processInputData(inputData: InputData) -> InputData { return InputData( emoji: inputData.emoji.flatMap { emoji in @@ -2180,7 +2174,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { availableGifSearchEmojies: inputData.availableGifSearchEmojies ) } - + private func reorderItems(category: EntityKeyboardComponent.ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) { var currentIds: [ItemCollectionId] = [] for item in items { @@ -2198,9 +2192,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { case .masks: namespace = Namespaces.ItemCollection.CloudMaskPacks } - + self.stableReorderableGroupOrder.removeValue(forKey: category) - + let _ = (self.context.engine.stickers.reorderStickerPacks(namespace: namespace, itemIds: currentIds) |> deliverOnMainQueue).start(completed: { [weak self] in guard let strongSelf = self else { @@ -2208,19 +2202,19 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } strongSelf.performLayout(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) }) - + if self.context.sharedContext.currentStickerSettings.with({ $0 }).dynamicPackOrder { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.interaction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: presentationData.strings.StickerPacksSettings_DynamicOrderOff, text: presentationData.strings.StickerPacksSettings_DynamicOrderOffInfo, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in return false }), nil) - + let _ = updateStickerSettingsInteractively(accountManager: self.context.sharedContext.accountManager, { return $0.withUpdatedDynamicPackOrder(false) }).start() } } - + private func openGifContextMenu(file: FileMediaReference, contextResult: (ChatContextResultCollection, ChatContextResult)?, sourceView: UIView, sourceRect: CGRect, gesture: ContextGesture, isSaved: Bool) { let canSaveGif: Bool if file.media.fileId.namespace == Namespaces.Media.CloudFile { @@ -2228,7 +2222,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } else { canSaveGif = false } - + let _ = (self.context.engine.stickers.isGifSaved(id: file.media.fileId) |> deliverOnMainQueue).start(next: { [weak self] isGifSaved in guard let strongSelf = self else { @@ -2239,11 +2233,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { isGifSaved = false } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [file.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - + let gallery = strongSelf.context.sharedContext.makeGalleryController(context: strongSelf.context, source: .standaloneMessage(message, nil), streamSingleVideo: true, isPreview: true) - + var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Send, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor) @@ -2257,10 +2251,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } }))) - + if let currentState = strongSelf.currentState { let interfaceState = currentState.interfaceState - + var isScheduledMessages = false if case .scheduledMessages = interfaceState.subject { isScheduledMessages = true @@ -2281,7 +2275,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } }))) } - + if isSaved && interfaceState.sendPaidMessageStars == nil { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) @@ -2295,7 +2289,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } } - + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_Gif_AddCaption, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCaption"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in @@ -2304,7 +2298,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let _ = self.interaction?.editGif(file, true) } }))) - + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Preview_Gif_Edit, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in @@ -2313,13 +2307,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let _ = self.interaction?.editGif(file, false) } }))) - + if isSaved || isGifSaved { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) - + if let self { let _ = removeSavedGif(postbox: self.context.account.postbox, mediaId: file.media.fileId).start() } @@ -2329,11 +2323,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) - + guard let strongSelf = self else { return } - + let context = strongSelf.context let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (toggleGifSaved(account: context.account, fileReference: file, saved: true) @@ -2356,7 +2350,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { guard let strongSelf = self else { return false } - + if case .info = action { let controller = PremiumIntroScreen(context: context, source: .savedGifs) strongSelf.interaction?.getNavigationController()?.pushViewController(controller) @@ -2368,12 +2362,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { }) }))) } - + let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceView: sourceView, sourceRect: sourceRect)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) strongSelf.interaction?.presentGlobalOverlayController(contextController, nil) }) } - + public func scrollToGroupEmoji() { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View { pagerView.scrollToItemGroup(contentId: "emoji", groupId: "peerSpecific", subgroupId: nil) @@ -2385,17 +2379,17 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent let controller: ViewController weak var sourceView: UIView? let sourceRect: CGRect - + let navigationController: NavigationController? = nil - + let passthroughTouches: Bool = false - + init(controller: ViewController, sourceView: UIView?, sourceRect: CGRect) { self.controller = controller self.sourceView = sourceView self.sourceRect = sourceRect } - + func transitionInfo() -> ContextControllerTakeControllerInfo? { let sourceView = self.sourceView let sourceRect = self.sourceRect @@ -2407,7 +2401,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } }) } - + func animatedIn() { if let controller = self.controller as? GalleryControllerProtocol { controller.viewDidAppear(false) @@ -2415,19 +2409,19 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputView, LegacyMessageInputPanelInputView, UIInputViewAudioFeedback { +public final class EntityInputView: UIInputView, LegacyMessageInputPanelInputView, UIInputViewAudioFeedback { private let context: AccountContext - + public var insertText: ((NSAttributedString) -> Void)? public var deleteBackwards: (() -> Void)? public var switchToKeyboard: (() -> Void)? public var presentController: ((ViewController) -> Void)? - + private var presentationData: PresentationData private var inputNode: ChatEntityKeyboardInputNode? private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - + public init( context: AccountContext, isDark: Bool, @@ -2436,20 +2430,20 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi forceHasPremium: Bool = false ) { self.context = context - + self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } if isDark { self.presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme) } - + super.init(frame: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), inputViewStyle: .default) - + self.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.clipsToBounds = true - + let inputInteraction = EmojiPagerContentComponent.InputInteraction( performItemAction: { [weak self] groupId, item, _, _, _, _ in let hasPremium: Signal @@ -2462,7 +2456,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi guard let strongSelf = self else { return } - + if groupId == AnyHashable("featuredTop") { } else { if let file = item.itemFile?._parse() { @@ -2482,14 +2476,14 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi break } } - + if file.isPremiumEmoji && !hasPremium { let presentationData = context.sharedContext.currentPresentationData.with { $0 } strongSelf.presentController?(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.EmojiInput_PremiumEmojiToast_Text, undoText: presentationData.strings.EmojiInput_PremiumEmojiToast_Action, customAction: { guard let strongSelf = self else { return } - + var replaceImpl: ((ViewController) -> Void)? let controller = PremiumDemoScreen(context: strongSelf.context, subject: .animatedEmoji, action: { let controller = PremiumIntroScreen(context: strongSelf.context, source: .animatedEmoji) @@ -2503,7 +2497,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi controller.replace(with: c) } else { controller.dismiss() - + if let self { self.presentController?(c) } @@ -2513,7 +2507,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi }), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false })) return } - + if let emojiAttribute = emojiAttribute { AudioServicesPlaySystemSound(0x450) strongSelf.insertText?(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) @@ -2588,7 +2582,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi stateContext: nil, addImage: nil ) - + let semaphore = DispatchSemaphore(value: 0) var emojiComponent: EmojiPagerContentComponent? let _ = EmojiPagerContentComponent.emojiInputData( @@ -2597,7 +2591,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi animationRenderer: self.animationRenderer, isStandalone: true, subject: .generic, - hasTrending: false, + hasTrending: false, topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, @@ -2608,7 +2602,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi semaphore.signal() }) semaphore.wait() - + if let emojiComponent = emojiComponent { let inputNode = ChatEntityKeyboardInputNode( context: self.context, @@ -2625,7 +2619,7 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi isStandalone: true, subject: .generic, hasTrending: false, - topReactionItems: [], + topReactionItems: [], areUnicodeEmojiEnabled: true, areCustomEmojiEnabled: areCustomEmojiEnabled, chatPeerId: nil, @@ -2658,31 +2652,31 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi self.addSubnode(inputNode) } } - + required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public override func layoutSubviews() { super.layoutSubviews() - + guard let inputNode = self.inputNode else { return } - + for view in self.subviews { if view !== inputNode.view { view.isHidden = true } } - + let bottomInset: CGFloat if #available(iOS 11.0, *) { bottomInset = max(0.0, UIScreen.main.bounds.height - (self.window?.safeAreaLayoutGuide.layoutFrame.maxY ?? 10000.0)) } else { bottomInset = 0.0 } - + let presentationInterfaceState = ChatPresentationInterfaceState( chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, @@ -2697,7 +2691,6 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi mode: .standard(.default), chatLocation: .peer(id: self.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -2739,7 +2732,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { public let presentGlobalOverlayController: (ViewController, Any?) -> Void public let navigationController: () -> NavigationController? public let updateIsPreviewing: (Bool) -> Void - + public init(sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?, [ItemCollectionId]) -> Bool, sendEmoji: @escaping (TelegramMediaFile) -> Void, setStatus: @escaping (TelegramMediaFile) -> Void, copyEmoji: @escaping (TelegramMediaFile) -> Void, presentController: @escaping (ViewController, Any?) -> Void, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, updateIsPreviewing: @escaping (Bool) -> Void) { self.sendSticker = sendSticker self.sendEmoji = sendEmoji @@ -2751,26 +2744,26 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { self.updateIsPreviewing = updateIsPreviewing } } - + private final class ViewRecord { weak var view: UIView? let peekRecognizer: PeekControllerGestureRecognizer - + init(view: UIView, peekRecognizer: PeekControllerGestureRecognizer) { self.view = view self.peekRecognizer = peekRecognizer } } - + private let context: AccountContext private let forceTheme: PresentationTheme? private let interaction: Interaction? private let chatPeerId: EnginePeer.Id? private let present: (ViewController, Any?) -> Void - + private var viewRecords: [ViewRecord] = [] private weak var peekController: PeekController? - + public init(context: AccountContext, forceTheme: PresentationTheme? = nil, interaction: Interaction?, chatPeerId: EnginePeer.Id?, present: @escaping (ViewController, Any?) -> Void) { self.context = context self.forceTheme = forceTheme @@ -2778,12 +2771,76 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { self.chatPeerId = chatPeerId self.present = present } - + + private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult], interaction: Interaction) { + guard let action = actions.first else { + return + } + + var animateInAsReplacement = false + if let navigationController = interaction.navigationController() { + for controller in navigationController.overlayControllers { + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitActionAndReplacementAnimation() + animateInAsReplacement = true + } + } + } + + var presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if let forceTheme = self.forceTheme { + presentationData = presentationData.withUpdated(theme: forceTheme) + } + + let controller: UndoOverlayController + switch action.action { + case .add: + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_AddedTitle, + text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string, + undo: false, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { _ in + return true + } + ) + case let .remove(positionInList): + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_RemovedTitle, + text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string, + undo: true, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { [weak self] overlayAction in + if case .undo = overlayAction { + let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start() + } + return true + } + ) + } + + interaction.presentGlobalOverlayController(controller, nil) + } + public func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, CALayer, TelegramMediaFile)?) { self.viewRecords = self.viewRecords.filter({ $0.view != nil }) - + let viewRecord = self.viewRecords.first(where: { $0.view === view }) - + if let viewRecord = viewRecord { viewRecord.peekRecognizer.isEnabled = isEnabled } else { @@ -2794,19 +2851,19 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { guard let (groupId, itemLayer, file) = itemAtPoint(point) else { return nil } - + let context = strongSelf.context - + var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] if let id = groupId.base as? ItemCollectionId { if file.isCustomEmoji || context.sharedContext.currentStickerSettings.with({ $0 }).dynamicPackOrder { bubbleUpEmojiOrStickersets.append(id) } } - + let accountPeerId = context.account.peerId let chatPeerId = strongSelf.chatPeerId - + if file.isCustomEmoji { return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in var hasPremium = false @@ -2820,9 +2877,9 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { guard let strongSelf = self, let itemLayer = itemLayer else { return nil } - + var menuItems: [ContextMenuItem] = [] - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } var isLocked = false if !hasPremium { @@ -2831,7 +2888,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { isLocked = false } } - + if let interaction = strongSelf.interaction { let sendEmoji: (TelegramMediaFile) -> Void = { file in interaction.sendEmoji(file) @@ -2842,13 +2899,13 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { let copyEmoji: (TelegramMediaFile) -> Void = { file in interaction.copyEmoji(file) } - + if let _ = strongSelf.chatPeerId { menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.EmojiPreview_SendEmoji, icon: { theme in if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) { return generateImage(image.size, rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - + if let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) } @@ -2860,16 +2917,16 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { sendEmoji(file) f(.default) }))) - + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.EmojiPreview_SetAsStatus, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Smile"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.default) - + guard let strongSelf = self else { return } - + if hasPremium { setStatus(file) } else { @@ -2884,7 +2941,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { strongSelf.interaction?.navigationController()?.pushViewController(controller) } }))) - + menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.EmojiPreview_CopyEmoji, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in @@ -2893,14 +2950,14 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { }))) } } - + if menuItems.isEmpty { return nil } guard let view = view else { return nil } - + return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked, menu: menuItems, openPremiumIntro: { guard let strongSelf = self, let interaction = strongSelf.interaction else { return @@ -2916,7 +2973,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { } else { sendPaidMessageStars = .single(nil) } - + return combineLatest( context.engine.stickers.isStickerSaved(id: file.fileId), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in @@ -2934,15 +2991,15 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { return nil } var menuItems: [ContextMenuItem] = [] - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let isLocked = file.isPremiumSticker && !hasPremium - + if let interaction = strongSelf.interaction { let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView?, CGRect?, CALayer?) -> Void = { fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer in let _ = interaction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets) } - + if let chatPeerId = strongSelf.chatPeerId, !isLocked { if chatPeerId != strongSelf.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in @@ -2958,7 +3015,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { f(.default) }))) } - + if sendPaidMessageStars == nil { menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.actionSheet.primaryTextColor) @@ -2974,11 +3031,11 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { }))) } } - + menuItems.append( .action(ContextMenuActionItem(text: isStarred ? presentationData.strings.Stickers_RemoveFromFavorites : presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { _, f in f(.default) - + let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (context.engine.stickers.toggleStickerSaved(file: file, saved: !isStarred) |> deliverOnMainQueue).start(next: { result in @@ -3005,18 +3062,18 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { }) })) ) - + if !file.isPremiumSticker { menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Stickers_EditSticker, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Draw"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) - + var emoji: [String] = [] for attribute in file.attributes { if case let .Sticker(displayText, _, _) = attribute { emoji = [displayText] } } - + let controller = context.sharedContext.makeStickerEditorScreen( context: context, source: (file, emoji), @@ -3031,7 +3088,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { interaction.navigationController()?.pushViewController(controller) }))) } - + loop: for attribute in file.attributes { switch attribute { case let .CustomEmoji(_, _, _, packReference), let .Sticker(_, packReference, _): @@ -3041,16 +3098,21 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Sticker"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.default) - + guard let strongSelf = self else { return } - + let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil) return true - }, actionPerformed: nil) - + }, actionPerformed: { [weak self] actions in + guard let self else { + return + } + self.presentStickerPackActionOverlay(actions, interaction: interaction) + }) + interaction.navigationController()?.view.window?.endEditing(true) interaction.presentController(controller, nil) })) @@ -3061,31 +3123,31 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { break } } - + if groupId == AnyHashable("recent") { menuItems.append( .action(ContextMenuActionItem(text: presentationData.strings.Stickers_RemoveFromRecent, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { _, f in f(.default) - + guard let strongSelf = self else { return } - + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } interaction.presentGlobalOverlayController(UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, loop: true, title: nil, text: presentationData.strings.Conversation_StickerRemovedFromRecent, undoText: nil, customAction: nil), elevatedLayout: false, action: { _ in return false }), nil) - + strongSelf.context.engine.stickers.removeRecentlyUsedSticker(fileReference: .recentSticker(media: file)) })) ) } } - + guard let view = view else { return nil } - + return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(context: context, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: { guard let strongSelf = self, let interaction = strongSelf.interaction else { return @@ -3099,7 +3161,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { guard let strongSelf = self else { return nil } - + var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } if let forceTheme = strongSelf.forceTheme { presentationData = presentationData.withUpdated(theme: forceTheme) @@ -3120,7 +3182,7 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { guard let strongSelf = self else { return } - + let _ = strongSelf }) self.viewRecords.append(ViewRecord(view: view, peekRecognizer: peekRecognizer)) diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift index 29d2068b92..74b9ec72c8 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/GifPaneSearchContentNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift deleted file mode 100644 index e3e0272230..0000000000 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchBarNode.swift +++ /dev/null @@ -1,500 +0,0 @@ -import Foundation -import UIKit -import SwiftSignalKit -import AsyncDisplayKit -import Display -import TelegramPresentationData -import ActivityIndicator -import AppBundle -import FeaturedStickersScreen - -private func generateLoupeIcon(color: UIColor) -> UIImage? { - return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: color) -} - -private func generateClearIcon(color: UIColor) -> UIImage? { - return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color) -} - -private func generateBackground(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { - let diameter: CGFloat = 10.0 - return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in - context.setFillColor(backgroundColor.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(foregroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - }, opaque: true)?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0)) -} - -private class PaneSearchBarTextField: UITextField { - public var didDeleteBackwardWhileEmpty: (() -> Void)? - - let placeholderLabel: ImmediateTextNode - var placeholderString: NSAttributedString? { - didSet { - self.placeholderLabel.attributedText = self.placeholderString - } - } - - let prefixLabel: ASTextNode - var prefixString: NSAttributedString? { - didSet { - self.prefixLabel.attributedText = self.prefixString - } - } - - override init(frame: CGRect) { - self.placeholderLabel = ImmediateTextNode() - self.placeholderLabel.isUserInteractionEnabled = false - self.placeholderLabel.displaysAsynchronously = false - self.placeholderLabel.maximumNumberOfLines = 1 - - self.prefixLabel = ASTextNode() - self.prefixLabel.isUserInteractionEnabled = false - self.prefixLabel.displaysAsynchronously = false - - super.init(frame: frame) - - self.addSubnode(self.placeholderLabel) - self.addSubnode(self.prefixLabel) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var keyboardAppearance: UIKeyboardAppearance { - get { - return super.keyboardAppearance - } - set { - let resigning = self.isFirstResponder - if resigning { - self.resignFirstResponder() - } - super.keyboardAppearance = newValue - if resigning { - self.becomeFirstResponder() - } - } - } - - override func textRect(forBounds bounds: CGRect) -> CGRect { - if bounds.size.width.isZero { - return CGRect(origin: CGPoint(), size: CGSize()) - } - var rect = bounds.insetBy(dx: 4.0, dy: 4.0) - - let prefixSize = self.prefixLabel.measure(bounds.size) - if !prefixSize.width.isZero { - let prefixOffset = prefixSize.width - rect.origin.x += prefixOffset - rect.size.width -= prefixOffset - } - return rect - } - - override func editingRect(forBounds bounds: CGRect) -> CGRect { - return self.textRect(forBounds: bounds) - } - - override func layoutSubviews() { - super.layoutSubviews() - - let bounds = self.bounds - if bounds.size.width.isZero { - return - } - - let constrainedSize = self.textRect(forBounds: self.bounds).size - let labelSize = self.placeholderLabel.updateLayout(constrainedSize) - self.placeholderLabel.frame = CGRect(origin: CGPoint(x: self.textRect(forBounds: bounds).minX, y: self.textRect(forBounds: bounds).minY + 4.0), size: labelSize) - - let prefixSize = self.prefixLabel.measure(constrainedSize) - let prefixBounds = bounds.insetBy(dx: 4.0, dy: 4.0) - self.prefixLabel.frame = CGRect(origin: CGPoint(x: prefixBounds.minX, y: prefixBounds.minY + 1.0), size: prefixSize) - } - - override func deleteBackward() { - if self.text == nil || self.text!.isEmpty { - self.didDeleteBackwardWhileEmpty?() - } - super.deleteBackward() - } -} - -class PaneSearchBarNode: ASDisplayNode, UITextFieldDelegate { - var cancel: (() -> Void)? - var textUpdated: ((String, String) -> Void)? - var clearPrefix: (() -> Void)? - - private let backgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode - private let textBackgroundNode: ASImageNode - private var activityIndicator: ActivityIndicator? - private let iconNode: ASImageNode - private let textField: PaneSearchBarTextField - private let clearButton: HighlightableButtonNode - private let cancelButton: HighlightableButtonNode - - var placeholderString: NSAttributedString? { - get { - return self.textField.placeholderString - } set(value) { - self.textField.placeholderString = value - } - } - - var prefixString: NSAttributedString? { - get { - return self.textField.prefixString - } set(value) { - let previous = self.prefixString - let updated: Bool - if let previous = previous, let value = value { - updated = !previous.isEqual(to: value) - } else { - updated = (previous != nil) != (value != nil) - } - if updated { - self.textField.prefixString = value - self.textField.setNeedsLayout() - self.updateIsEmpty() - } - } - } - - var text: String { - get { - return self.textField.text ?? "" - } set(value) { - if self.textField.text ?? "" != value { - self.textField.text = value - self.textFieldDidChange(self.textField) - } - } - } - - var activity: Bool = false { - didSet { - if self.activity != oldValue { - if self.activity { - if self.activityIndicator == nil, let theme = self.theme { - let activityIndicator = ActivityIndicator(type: .custom(theme.chat.inputMediaPanel.stickersSearchControlColor, 13.0, 1.0, false)) - self.activityIndicator = activityIndicator - self.addSubnode(activityIndicator) - if let (boundingSize, leftInset, rightInset) = self.validLayout { - self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate) - } - } - } else if let activityIndicator = self.activityIndicator { - self.activityIndicator = nil - activityIndicator.removeFromSupernode() - } - self.iconNode.isHidden = self.activity - } - } - } - - private var validLayout: (CGSize, CGFloat, CGFloat)? - private var theme: PresentationTheme? - - override init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - self.textBackgroundNode = ASImageNode() - self.textBackgroundNode.isLayerBacked = false - self.textBackgroundNode.displaysAsynchronously = false - self.textBackgroundNode.displayWithoutProcessing = true - - self.iconNode = ASImageNode() - self.iconNode.isUserInteractionEnabled = false - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - - self.textField = PaneSearchBarTextField() - self.textField.accessibilityTraits = .searchField - self.textField.autocorrectionType = .no - self.textField.returnKeyType = .search - self.textField.font = Font.regular(17.0) - - self.clearButton = HighlightableButtonNode() - self.clearButton.imageNode.displaysAsynchronously = false - self.clearButton.imageNode.displayWithoutProcessing = true - self.clearButton.displaysAsynchronously = false - self.clearButton.isHidden = true - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) - self.cancelButton.displaysAsynchronously = false - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - - self.addSubnode(self.textBackgroundNode) - self.view.addSubview(self.textField) - self.addSubnode(self.iconNode) - self.addSubnode(self.clearButton) - self.addSubnode(self.cancelButton) - - self.textField.delegate = self - self.textField.addTarget(self, action: #selector(self.textFieldDidChange(_:)), for: .editingChanged) - - self.textField.didDeleteBackwardWhileEmpty = { [weak self] in - self?.clearPressed() - } - - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) - self.clearButton.addTarget(self, action: #selector(self.clearPressed), forControlEvents: .touchUpInside) - } - - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - self.theme = theme - - if let activityIndicator = self.activityIndicator { - activityIndicator.type = .custom(theme.chat.inputMediaPanel.stickersSearchControlColor, 13.0, 1.0, false) - } - self.separatorNode.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor - self.textBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 36.0, color: theme.chat.inputMediaPanel.stickersSearchBackgroundColor) - self.textField.textColor = theme.chat.inputMediaPanel.stickersSearchPrimaryColor - self.iconNode.image = generateLoupeIcon(color: theme.chat.inputMediaPanel.stickersSearchControlColor) - self.clearButton.setImage(generateClearIcon(color: theme.chat.inputMediaPanel.stickersSearchControlColor), for: []) - self.cancelButton.setAttributedTitle(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlAccentColor), for: []) - self.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance - self.textField.tintColor = theme.list.itemAccentColor - - if let (boundingSize, leftInset, rightInset) = self.validLayout { - self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate) - } - } - - func updateLayout(boundingSize: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { - self.validLayout = (boundingSize, leftInset, rightInset) - - self.backgroundNode.frame = self.bounds - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.bounds.size.height), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel))) - - let verticalOffset: CGFloat = -20.0 - - let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: boundingSize.width - leftInset - rightInset, height: boundingSize.height)) - - let cancelButtonSize = self.cancelButton.measure(CGSize(width: 100.0, height: CGFloat.infinity)) - transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 8.0 - cancelButtonSize.width, y: verticalOffset + 36.0), size: cancelButtonSize)) - - let textBackgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX + 8.0, y: verticalOffset + 28.0), size: CGSize(width: contentFrame.width - 16.0 - cancelButtonSize.width - 11.0, height: 36.0)) - transition.updateFrame(node: self.textBackgroundNode, frame: textBackgroundFrame) - - let textFrame = CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 27.0, y: textBackgroundFrame.minY), size: CGSize(width: max(1.0, textBackgroundFrame.size.width - 27.0 - 20.0), height: textBackgroundFrame.size.height)) - - if let iconImage = self.iconNode.image { - let iconSize = iconImage.size - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 5.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize)) - } - - if let activityIndicator = self.activityIndicator { - let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 11.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize)) - } - - let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize)) - - self.textField.frame = textFrame - self.textField.layoutSubviews() - } - - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - if let cancel = self.cancel { - cancel() - } - } - } - - func activate() { - self.textField.becomeFirstResponder() - } - - func animateIn(from node: PaneSearchBarPlaceholderNode, duration: Double, timingFunction: String, completion: @escaping () -> Void) { - let initialTextBackgroundFrame = node.view.convert(node.backgroundNode.frame, to: self.view) - - var backgroundCompleted = false - var separatorCompleted = false - var textBackgroundCompleted = false - let intermediateCompletion: () -> Void = { - if backgroundCompleted && separatorCompleted && textBackgroundCompleted { - completion() - } - } - - let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, initialTextBackgroundFrame.maxY + 8.0))) - if let fromBackgroundColor = node.backgroundColor, let toBackgroundColor = self.backgroundNode.backgroundColor { - self.backgroundNode.layer.animate(from: fromBackgroundColor.cgColor, to: toBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration * 0.7) - } else { - self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) - } - self.backgroundNode.layer.animateFrame(from: initialBackgroundFrame, to: self.backgroundNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in - backgroundCompleted = true - intermediateCompletion() - }) - - let initialSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, initialTextBackgroundFrame.maxY + 8.0)), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel)) - self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration) - self.separatorNode.layer.animateFrame(from: initialSeparatorFrame, to: self.separatorNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in - separatorCompleted = true - intermediateCompletion() - }) - - self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction, completion: { _ in - textBackgroundCompleted = true - intermediateCompletion() - }) - - let labelFrame = self.textField.placeholderLabel.frame - let initialLabelNodeFrame = CGRect(origin: node.labelNode.view.convert(node.labelNode.bounds, to: self.textField.superview).origin, size: labelFrame.size) - self.textField.layer.animateFrame(from: CGRect(origin: initialLabelNodeFrame.origin.offsetBy(dx: -labelFrame.minX, dy: -labelFrame.minY), size: self.textField.frame.size), to: self.textField.frame, duration: duration, timingFunction: timingFunction) - - let iconFrame = self.iconNode.frame - let initialIconFrame = CGRect(origin: node.iconNode.view.convert(node.iconNode.bounds, to: self.iconNode.view.superview).origin, size: iconFrame.size) - self.iconNode.layer.animateFrame(from: initialIconFrame, to: self.iconNode.frame, duration: duration, timingFunction: timingFunction) - - let cancelButtonFrame = self.cancelButton.frame - self.cancelButton.layer.animatePosition(from: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: initialTextBackgroundFrame.minY + 2.0 + cancelButtonFrame.size.height / 2.0), to: self.cancelButton.layer.position, duration: duration, timingFunction: timingFunction) - node.isHidden = true - } - - func deactivate(clear: Bool = true) { - self.textField.resignFirstResponder() - if clear { - self.textField.text = nil - self.textField.placeholderLabel.isHidden = false - } - } - - func transitionOut(to node: PaneSearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - let targetTextBackgroundFrame = node.view.convert(node.backgroundNode.view.frame, to: self.view) - - let duration: Double = 0.5 - let timingFunction = kCAMediaTimingFunctionSpring - - node.isHidden = true - self.clearButton.isHidden = true - self.textField.text = "" - - var backgroundCompleted = false - var separatorCompleted = false - var textBackgroundCompleted = false - let intermediateCompletion: () -> Void = { [weak node] in - if backgroundCompleted && separatorCompleted && textBackgroundCompleted { - completion() - node?.isHidden = false - } - } - - let targetBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.bounds.size.width, height: max(0.0, targetTextBackgroundFrame.maxY + 8.0))) - if let toBackgroundColor = node.backgroundColor, let fromBackgroundColor = self.backgroundNode.backgroundColor { - self.backgroundNode.layer.animate(from: fromBackgroundColor.cgColor, to: toBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: duration * 0.5, removeOnCompletion: false) - } else { - self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration / 2.0, removeOnCompletion: false) - } - self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: targetBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in - backgroundCompleted = true - intermediateCompletion() - }) - - let targetSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, targetTextBackgroundFrame.maxY + 8.0)), size: CGSize(width: self.bounds.size.width, height: UIScreenPixel)) - self.separatorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration / 2.0, removeOnCompletion: false) - self.separatorNode.layer.animateFrame(from: self.separatorNode.frame, to: targetSeparatorFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in - separatorCompleted = true - intermediateCompletion() - }) - - self.textBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { _ in - textBackgroundCompleted = true - intermediateCompletion() - }) - - let transitionBackgroundNode = ASImageNode() - transitionBackgroundNode.isLayerBacked = true - transitionBackgroundNode.displaysAsynchronously = false - transitionBackgroundNode.displayWithoutProcessing = true - transitionBackgroundNode.image = node.backgroundNode.image - self.insertSubnode(transitionBackgroundNode, aboveSubnode: self.textBackgroundNode) - transitionBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration / 2.0, removeOnCompletion: false) - transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - - let textFieldFrame = self.textField.frame - let targetLabelNodeFrame = CGRect(origin: node.labelNode.view.convert(node.labelNode.bounds, to: self.textField.superview).origin, size: textFieldFrame.size) - self.textField.layer.animateFrame(from: self.textField.frame, to: CGRect(origin: targetLabelNodeFrame.origin.offsetBy(dx: -self.textField.placeholderLabel.frame.minX, dy: -self.textField.placeholderLabel.frame.minY), size: self.textField.frame.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - if let snapshot = node.labelNode.layer.snapshotContentTree() { - snapshot.frame = CGRect(origin: self.textField.placeholderLabel.frame.origin, size: node.labelNode.frame.size) - self.textField.layer.addSublayer(snapshot) - snapshot.animateAlpha(from: 0.0, to: 1.0, duration: duration * 2.0 / 3.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue) - //self.textField.placeholderLabel.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 3.0 / 2.0, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) - - } - - let iconFrame = self.iconNode.frame - let targetIconFrame = CGRect(origin: node.iconNode.view.convert(node.iconNode.bounds, to: self.iconNode.view.superview).origin, size: iconFrame.size) - self.iconNode.image = node.iconNode.image - self.iconNode.layer.animateFrame(from: self.iconNode.frame, to: targetIconFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - - let cancelButtonFrame = self.cancelButton.frame - self.cancelButton.layer.animatePosition(from: self.cancelButton.layer.position, to: CGPoint(x: self.bounds.size.width + cancelButtonFrame.size.width / 2.0, y: targetTextBackgroundFrame.minY + 4.0 + cancelButtonFrame.size.height / 2.0), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) - } - - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if string.range(of: "\n") != nil { - return false - } - return true - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - self.textField.resignFirstResponder() - return false - } - - @objc func textFieldDidChange(_ textField: UITextField) { - self.updateIsEmpty() - if let textUpdated = self.textUpdated { - textUpdated(textField.text ?? "", self.textField.textInputMode?.primaryLanguage ?? "") - } - } - - private func updateIsEmpty() { - let isEmpty = !(textField.text?.isEmpty ?? true) - if isEmpty != self.textField.placeholderLabel.isHidden { - self.textField.placeholderLabel.isHidden = isEmpty - } - self.clearButton.isHidden = !isEmpty && self.prefixString == nil - } - - @objc func cancelPressed() { - if let cancel = self.cancel { - cancel() - } - } - - @objc func clearPressed() { - if (self.textField.text?.isEmpty ?? true) { - if self.prefixString != nil { - self.clearPrefix?() - } - } else { - self.textField.text = "" - self.textFieldDidChange(self.textField) - } - } - - func updateQuery(_ query: String) { - self.textField.text = query - self.textFieldDidChange(self.textField) - } -} diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift index 759f4b04cf..c50d6e40d6 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/PaneSearchContainerNode.swift @@ -2,34 +2,55 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import ComponentFlow +import SearchBarNode import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext import ChatPresentationInterfaceState import EntityKeyboard +import ContextUI +import GlassControls +import MultilineTextComponent import ChatControllerInteraction import MultiplexedVideoNode import FeaturedStickersScreen import StickerPeekUI import EntityKeyboardGifContent import BatchVideoRendering +import UndoUI -private let searchBarHeight: CGFloat = 52.0 +private let searchBarHeight: CGFloat = 76.0 +private let searchBarTopInset: CGFloat = 16.0 +private let searchBarFieldHeight: CGFloat = 44.0 + +private func paneSearchBarTheme(_ theme: PresentationTheme) -> SearchBarNodeTheme { + return SearchBarNodeTheme( + background: .clear, + separator: .clear, + inputFill: .clear, + primaryText: theme.chat.inputPanel.panelControlColor, + placeholder: theme.chat.inputPanel.inputPlaceholderColor, + inputIcon: theme.chat.inputPanel.inputControlColor, + inputClear: theme.chat.inputPanel.panelControlColor, + accent: theme.chat.inputPanel.panelControlAccentColor, + keyboard: theme.rootController.keyboardColor + ) +} public protocol PaneSearchContentNode { var ready: Signal { get } var deactivateSearchBar: (() -> Void)? { get set } var updateActivity: ((Bool) -> Void)? { get set } - + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) func updateText(_ text: String, languageCode: String?) func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) - + func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) func animateOut(transition: ContainedViewLayoutTransition) - + func updatePreviewing(animated: Bool) func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? } @@ -41,26 +62,34 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer private let interaction: ChatEntityKeyboardInputNode.Interaction private let inputNodeInteraction: ChatMediaInputNodeInteraction private let peekBehavior: EmojiContentPeekBehavior? - + private let backgroundNode: ASDisplayNode - private let searchBar: PaneSearchBarNode - - private var validLayout: CGSize? - + private let searchBar: SearchBarNode + private let navigationButtons = ComponentView() + private let selectedPackTitle = ComponentView() + + private var theme: PresentationTheme + private var strings: PresentationStrings + private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics)? + private weak var animatedPlaceholder: PaneSearchBarPlaceholderNode? + private var selectedStickerPack: StickerPaneSearchSelectedPack? + public var onCancel: (() -> Void)? - + public var openGifContextMenu: ((MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void)? - + public var ready: Signal { return self.contentNode.ready } - + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, batchVideoRenderingContext: BatchVideoRenderingContext?, stickerActionTitle: String? = nil, trendingGifsPromise: Promise, cancel: @escaping () -> Void, peekBehavior: EmojiContentPeekBehavior?) { self.context = context self.mode = mode self.interaction = interaction self.inputNodeInteraction = inputNodeInteraction self.peekBehavior = peekBehavior + self.theme = theme + self.strings = strings switch mode { case .gif: self.contentNode = GifPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, batchVideoRenderingContext: batchVideoRenderingContext ?? BatchVideoRenderingContext(context: context), trendingPromise: trendingGifsPromise) @@ -68,38 +97,43 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer self.contentNode = StickerPaneSearchContentNode(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction, stickerActionTitle: stickerActionTitle) } self.backgroundNode = ASDisplayNode() - - self.searchBar = PaneSearchBarNode() - + + self.searchBar = SearchBarNode( + theme: paneSearchBarTheme(theme), + presentationTheme: theme, + strings: strings, + fieldStyle: .glass, + displayBackground: false + ) + super.init() - + self.clipsToBounds = true - + self.addSubnode(self.backgroundNode) self.addSubnode(self.contentNode) self.addSubnode(self.searchBar) - + self.contentNode.deactivateSearchBar = { [weak self] in self?.searchBar.deactivate(clear: false) } self.contentNode.updateActivity = { [weak self] active in self?.searchBar.activity = active } - + self.searchBar.cancel = { [weak self] in + self?.searchBar.deactivate(clear: false) cancel() - - self?.searchBar.view.endEditing(true) self?.onCancel?() } self.searchBar.activate() - + self.searchBar.textUpdated = { [weak self] text, languageCode in self?.contentNode.updateText(text, languageCode: languageCode) } - + self.updateThemeAndStrings(theme: theme, strings: strings) - + if let contentNode = self.contentNode as? GifPaneSearchContentNode { contentNode.requestUpdateQuery = { [weak self] query in self?.updateQuery(query) @@ -108,7 +142,20 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer self?.openGifContextMenu?(file, node, rect, gesture, isSaved) } } - + + if let contentNode = self.contentNode as? StickerPaneSearchContentNode { + contentNode.selectedPackUpdated = { [weak self] pack in + guard let self else { + return + } + self.selectedStickerPack = pack + if pack != nil { + self.searchBar.deactivate(clear: false) + } + self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + if let contentNode = self.contentNode as? StickerPaneSearchContentNode, let peekBehavior = self.peekBehavior { peekBehavior.setGestureRecognizerEnabled(view: self.contentNode.view, isEnabled: true, itemAtPoint: { [weak contentNode] point in guard let contentNode else { @@ -117,7 +164,7 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer guard let (itemNode, item) = contentNode.itemAt(point: point) else { return nil } - + var maybeFile: TelegramMediaFile? if let item = item as? StickerPreviewPeekItem { switch item { @@ -132,26 +179,28 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer guard let file = maybeFile else { return nil } - + var groupId: AnyHashable = AnyHashable("search") for attribute in file.attributes { if case let .Sticker(_, packReference, _) = attribute { if case let .id(id, _) = packReference { - groupId = AnyHashable(ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) + groupId = AnyHashable(EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id)) } } } - + return (groupId, itemNode.layer, file) }) } } - + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings self.backgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0) self.contentNode.updateThemeAndStrings(theme: theme, strings: strings) - self.searchBar.updateThemeAndStrings(theme: theme, strings: strings) - + self.searchBar.updateThemeAndStrings(theme: paneSearchBarTheme(theme), presentationTheme: theme, strings: strings) + let placeholder: String switch mode { case .gif: @@ -159,76 +208,329 @@ public final class PaneSearchContainerNode: ASDisplayNode, EntitySearchContainer case .sticker, .trending: placeholder = strings.Stickers_Search } - self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.chat.inputMediaPanel.stickersSearchPlaceholderColor) + self.searchBar.placeholderString = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) } - + public func updateQuery(_ query: String) { - self.searchBar.updateQuery(query) + self.searchBar.text = query } - + public func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? { return self.contentNode.itemAt(point: CGPoint(x: point.x, y: point.y - searchBarHeight)) } - + + private func openMore() { + guard let selectedStickerPack = self.selectedStickerPack, let controlsView = self.navigationButtons.view as? GlassControlPanelComponent.View, let rightItemView = controlsView.rightItemView, let sourceView = rightItemView.itemView(id: AnyHashable("more")) else { + return + } + + let link = "https://t.me/addstickers/\(selectedStickerPack.info.shortName)" + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + let strings = self.strings + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: strings.StickerPack_Share, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + let shareController = self.context.sharedContext.makeShareController( + context: self.context, + params: ShareControllerParams( + subject: .url(link), + externalShare: false, + actionCompleted: { [weak self] in + guard let self else { + return + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + self.interaction.presentController(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return false + }), nil) + } + ) + ) + self.interaction.presentController(shareController, nil) + }))) + + items.append(.action(ContextMenuActionItem(text: strings.StickerPack_CopyLink, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + UIPasteboard.general.string = link + guard let self else { + return + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + self.interaction.presentController(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return false + }), nil) + }))) + + if selectedStickerPack.installed { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strings.StickerPack_RemoveStickerSet, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + let info = selectedStickerPack.info + let context = self.context + let _ = (context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) + |> deliverOnMainQueue).start(next: { [weak self] indexAndItems in + guard let self, let (positionInList, items) = indexAndItems else { + return + } + + let stickerItems = items.compactMap { $0 as? StickerPackItem } + if let contentNode = self.contentNode as? StickerPaneSearchContentNode { + contentNode.setPackInstalledState(id: info.id, installed: false) + } + + var animateInAsReplacement = false + if let navigationController = self.interaction.getNavigationController() { + for controller in navigationController.overlayControllers { + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitActionAndReplacementAnimation() + animateInAsReplacement = true + } + } + } + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + let undoController = UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: stickerItems.first, context: context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { [weak self] action in + if case .undo = action { + let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: stickerItems, positionInList: positionInList).start() + if let contentNode = self?.contentNode as? StickerPaneSearchContentNode { + contentNode.setPackInstalledState(id: info.id, installed: true) + } + } + return true + }) + if let navigationController = self.interaction.getNavigationController() { + navigationController.presentOverlay(controller: undoController) + } else { + self.interaction.presentController(undoController, nil) + } + }) + }))) + } + + let contextController = makeContextController( + presentationData: presentationData, + source: .reference(StickerPaneSearchHeaderContextReferenceContentSource(sourceView: sourceView)), + items: .single(ContextController.Items(content: .list(items))), + gesture: nil + ) + self.interaction.presentGlobalOverlayController(contextController, nil) + } + + private func requestLayout(transition: ContainedViewLayoutTransition) { + guard let (size, leftInset, rightInset, bottomInset, inputHeight, deviceMetrics) = self.validLayout else { + return + } + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) + } + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { - self.validLayout = size + self.validLayout = (size, leftInset, rightInset, bottomInset, inputHeight, deviceMetrics) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + + let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: searchBarTopInset), size: CGSize(width: size.width, height: searchBarFieldHeight)) + transition.updateFrame(node: self.searchBar, frame: searchBarFrame) + self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) + self.searchBar.isUserInteractionEnabled = self.selectedStickerPack == nil + transition.updateAlpha(node: self.searchBar, alpha: self.selectedStickerPack == nil ? 1.0 : 0.0) - transition.updateFrame(node: self.searchBar, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: searchBarHeight))) - self.searchBar.updateLayout(boundingSize: CGSize(width: size.width, height: searchBarHeight), leftInset: leftInset, rightInset: rightInset, transition: transition) + let componentTransition = ComponentTransition(transition) + let navigationButtonsFrame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: searchBarTopInset), size: CGSize(width: max(1.0, size.width - leftInset - rightInset - 16.0 * 2.0), height: 48.0)) + + let navigationButtonsSize = self.navigationButtons.update( + transition: componentTransition, + component: AnyComponent(GlassControlPanelComponent( + theme: self.theme, + leftItem: self.selectedStickerPack == nil ? nil : GlassControlPanelComponent.Item( + items: [ + GlassControlGroupComponent.Item( + id: AnyHashable("back"), + content: .icon("Navigation/Back"), + action: { [weak self] in + guard let self, let contentNode = self.contentNode as? StickerPaneSearchContentNode else { + return + } + contentNode.clearSelectedPack() + } + ) + ], + background: .panel + ), + centralItem: nil, + rightItem: self.selectedStickerPack == nil ? nil : GlassControlPanelComponent.Item( + items: [ + GlassControlGroupComponent.Item( + id: AnyHashable("more"), + content: .animation("anim_morewide"), + action: { [weak self] in + self?.openMore() + } + ) + ], + background: .panel + ), + centerAlignmentIfPossible: true, + isDark: self.theme.overallDarkAppearance + )), + environment: {}, + containerSize: navigationButtonsFrame.size + ) + if let navigationButtons = self.navigationButtons.view { + if navigationButtons.superview == nil { + self.view.addSubview(navigationButtons) + } + navigationButtons.isUserInteractionEnabled = self.selectedStickerPack != nil + componentTransition.setFrame(view: navigationButtons, frame: CGRect(origin: navigationButtonsFrame.origin, size: navigationButtonsSize)) + //componentTransition.setAlpha(view: navigationButtons, alpha: self.selectedStickerPack != nil ? 1.0 : 0.0) + } + + let title = self.selectedStickerPack?.info.title ?? "" + let titleSize = self.selectedPackTitle.update( + transition: componentTransition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.chat.inputPanel.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: max(1.0, size.width - leftInset - rightInset - 140.0), height: searchBarFieldHeight) + ) + if let titleView = self.selectedPackTitle.view { + if titleView.superview == nil { + self.view.addSubview(titleView) + } + titleView.isUserInteractionEnabled = false + let titleOrigin = CGPoint(x: leftInset + floor((size.width - leftInset - rightInset - titleSize.width) / 2.0), y: searchBarTopInset + floor((searchBarFieldHeight - titleSize.height) / 2.0)) + titleView.frame = CGRect(origin: titleOrigin, size: titleSize) + componentTransition.setAlpha(view: titleView, alpha: self.selectedStickerPack != nil ? 1.0 : 0.0) + } let contentFrame = CGRect(origin: CGPoint(x: leftInset, y: searchBarHeight), size: CGSize(width: size.width - leftInset - rightInset, height: size.height - searchBarHeight)) - transition.updateFrame(node: self.contentNode, frame: contentFrame) self.contentNode.updateLayout(size: contentFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) } - + public func deactivate() { + if let contentNode = self.contentNode as? StickerPaneSearchContentNode { + contentNode.clearSelectedPack() + } self.searchBar.deactivate(clear: true) } - + public func animateIn(from placeholder: PaneSearchBarPlaceholderNode?, anchorTop: CGPoint, anhorTopView: UIView, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { var verticalOrigin: CGFloat = anhorTopView.convert(anchorTop, to: self.view).y if let placeholder = placeholder { + self.animatedPlaceholder = placeholder + placeholder.isHidden = true + let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view) verticalOrigin = placeholderFrame.minY - 4.0 self.contentNode.animateIn(additivePosition: verticalOrigin, transition: transition) } else { self.contentNode.animateIn(additivePosition: 0.0, transition: transition) } - + + let searchBarFrame = self.searchBar.frame + let initialSearchBarFrame = CGRect(origin: CGPoint(x: searchBarFrame.minX, y: verticalOrigin), size: searchBarFrame.size) + switch transition { case let .animated(duration, curve): self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration / 2.0) - if let placeholder = placeholder { - self.searchBar.animateIn(from: placeholder, duration: duration, timingFunction: curve.timingFunction, completion: completion) - } else { - self.searchBar.alpha = 0.0 - transition.updateAlpha(node: self.searchBar, alpha: 1.0) - } - if let size = self.validLayout { - let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin), size: CGSize(width: size.width, height: max(0.0, size.height - verticalOrigin))) + + self.searchBar.alpha = 1.0 + self.searchBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction, completion: { _ in + completion() + }) + self.searchBar.layer.animateFrame(from: initialSearchBarFrame, to: searchBarFrame, duration: duration, timingFunction: curve.timingFunction) + + if let layout = self.validLayout { + let initialBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - verticalOrigin))) self.backgroundNode.layer.animateFrame(from: initialBackgroundFrame, to: self.backgroundNode.frame, duration: duration, timingFunction: curve.timingFunction) } case .immediate: + completion() break } } - + public func animateOut(to placeholder: PaneSearchBarPlaceholderNode, animateOutSearchBar: Bool, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - if case let .animated(duration, curve) = transition { - if let size = self.validLayout { - let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view) - let verticalOrigin = placeholderFrame.minY - 4.0 - self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin), size: CGSize(width: size.width, height: max(0.0, size.height - verticalOrigin))), duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false) + let finish: () -> Void = { [weak self] in + placeholder.isHidden = false + if let self, self.animatedPlaceholder === placeholder { + self.animatedPlaceholder = nil } + completion() } - self.searchBar.transitionOut(to: placeholder, transition: transition, completion: completion) + + if case let .animated(duration, curve) = transition { + let placeholderFrame = placeholder.view.convert(placeholder.bounds, to: self.view) + let verticalOrigin = placeholderFrame.minY - 4.0 + let targetSearchBarFrame = CGRect(origin: CGPoint(x: self.searchBar.frame.minX, y: verticalOrigin), size: self.searchBar.frame.size) + + if let layout = self.validLayout { + self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - verticalOrigin))), duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false) + } + + self.searchBar.layer.animateFrame(from: self.searchBar.frame, to: targetSearchBarFrame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false) + if animateOutSearchBar { + self.searchBar.alpha = 0.0 + self.searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false, completion: { _ in + finish() + }) + } else { + self.searchBar.layer.animateAlpha(from: self.searchBar.alpha, to: self.searchBar.alpha, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false, completion: { _ in + finish() + }) + } + } else { + if animateOutSearchBar { + self.searchBar.alpha = 0.0 + } + finish() + } + transition.updateAlpha(node: self.backgroundNode, alpha: 0.0) if animateOutSearchBar { transition.updateAlpha(node: self.searchBar, alpha: 0.0) } + let componentTransition = ComponentTransition(transition) + if let headerView = self.navigationButtons.view { + componentTransition.setAlpha(view: headerView, alpha: 0.0) + } + if let titleView = self.selectedPackTitle.view { + componentTransition.setAlpha(view: titleView, alpha: 0.0) + } self.contentNode.animateOut(transition: transition) self.deactivate() } } + +private final class StickerPaneSearchHeaderContextReferenceContentSource: ContextReferenceContentSource { + private weak var sourceView: UIView? + + init(sourceView: UIView) { + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + guard let sourceView = self.sourceView else { + return nil + } + return ContextControllerReferenceViewInfo(referenceView: sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift index f67543bd15..aad31d517d 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/StickerPaneSearchContentNode.swift @@ -2,8 +2,10 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import ButtonComponent +import ComponentFlow +import EdgeEffect import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import PresentationDataUtils @@ -19,93 +21,231 @@ import ChatControllerInteraction import FeaturedStickersScreen import ChatPresentationInterfaceState import StickerResources +import EntityKeyboard +import EmojiTextAttachmentView +import MultilineTextComponent +import TextFormat + +private let packPanelHeight: CGFloat = 76.0 +private let collapsedPackPanelHeight: CGFloat = 40.0 private enum StickerSearchEntryId: Equatable, Hashable { case sticker(String?, Int64) - case global(ItemCollectionId) } private enum StickerSearchEntry: Identifiable, Comparable { case sticker(index: Int, code: String?, stickerItem: FoundStickerItem, theme: PresentationTheme) - case global(index: Int, info: StickerPackCollectionInfo, topItems: [StickerPackItem], installed: Bool, topSeparator: Bool) - + var stableId: StickerSearchEntryId { switch self { case let .sticker(_, code, stickerItem, _): return .sticker(code, stickerItem.file.fileId.id) - case let .global(_, info, _, _, _): - return .global(info.id) } } - + static func ==(lhs: StickerSearchEntry, rhs: StickerSearchEntry) -> Bool { - switch lhs { - case let .sticker(lhsIndex, lhsCode, lhsStickerItem, lhsTheme): - if case let .sticker(rhsIndex, rhsCode, rhsStickerItem, rhsTheme) = rhs { - if lhsIndex != rhsIndex { - return false - } - if lhsCode != rhsCode { - return false - } - if lhsStickerItem != rhsStickerItem { - return false - } - if lhsTheme !== rhsTheme { - return false - } - return true - } else { + switch (lhs, rhs) { + case let (.sticker(lhsIndex, lhsCode, lhsStickerItem, lhsTheme), .sticker(rhsIndex, rhsCode, rhsStickerItem, rhsTheme)): + if lhsIndex != rhsIndex { return false } - case let .global(index, info, topItems, installed, topSeparator): - if case .global(index, info, topItems, installed, topSeparator) = rhs { - return true - } else { + if lhsCode != rhsCode { return false } + if lhsStickerItem != rhsStickerItem { + return false + } + if lhsTheme !== rhsTheme { + return false + } + return true } } - + static func <(lhs: StickerSearchEntry, rhs: StickerSearchEntry) -> Bool { - switch lhs { - case let .sticker(lhsIndex, _, _, _): - switch rhs { - case let .sticker(rhsIndex, _, _, _): - return lhsIndex < rhsIndex - default: - return true - } - case let .global(lhsIndex, _, _, _, _): - switch rhs { - case .sticker: - return false - case let .global(rhsIndex, _, _, _, _): - return lhsIndex < rhsIndex - } + switch (lhs, rhs) { + case let (.sticker(lhsIndex, _, _, _), .sticker(rhsIndex, _, _, _)): + return lhsIndex < rhsIndex } } - + func item(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> GridItem { switch self { case let .sticker(_, code, stickerItem, theme): return StickerPaneSearchStickerItem(context: context, theme: theme, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, selected: { node, layer, rect in interaction.sendSticker(.standalone(media: stickerItem.file), node.view, layer, rect) }) - case let .global(_, info, topItems, installed, topSeparator): - let itemContext = StickerPaneSearchGlobalItemContext() - itemContext.canPlayMedia = true - return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: false, info: StickerPackCollectionInfo.Accessor(info), topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: { - interaction.open(info) - }, install: { - interaction.install(info, topItems, !installed) - }, getItemIsPreviewed: { item in - return interaction.getItemIsPreviewed(item) - }, itemContext: itemContext) } } } +struct StickerPaneSearchSelectedPack { + let info: StickerPackCollectionInfo + let installed: Bool +} + +private struct StickerPaneSearchPack: Equatable { + let info: StickerPackCollectionInfo + let topItems: [StickerPackItem] + let installed: Bool +} + +private final class StickerSearchPackTopPanelItemComponent: Component { + typealias EnvironmentType = EntityKeyboardTopPanelItemEnvironment + + let context: AccountContext + let theme: PresentationTheme + let info: StickerPackCollectionInfo + let topItem: StickerPackItem? + let pressed: () -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + info: StickerPackCollectionInfo, + topItem: StickerPackItem?, + pressed: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.info = info + self.topItem = topItem + self.pressed = pressed + } + + static func ==(lhs: StickerSearchPackTopPanelItemComponent, rhs: StickerSearchPackTopPanelItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.info != rhs.info { + return false + } + if lhs.topItem != rhs.topItem { + return false + } + return true + } + + final class View: UIView { + private var itemLayer: InlineStickerItemLayer? + private var itemFileId: EngineMedia.Id? + private var titleView: ComponentView? + private var component: StickerSearchPackTopPanelItemComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.component?.pressed() + } + } + + func update(component: StickerSearchPackTopPanelItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let itemEnvironment = environment[EntityKeyboardTopPanelItemEnvironment.self].value + let file = component.topItem?.file._parse() + let fileId = file?.fileId + if self.itemFileId != fileId { + self.itemFileId = fileId + if let itemLayer = self.itemLayer { + self.itemLayer = nil + itemLayer.removeFromSuperlayer() + } + + if let file { + let itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) + let displaySize = itemDimensions.aspectFitted(CGSize(width: 44.0, height: 44.0)) + let itemLayer = InlineStickerItemLayer( + context: component.context, + userLocation: .other, + attemptSynchronousLoad: false, + emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file), + file: file, + cache: component.context.animationCache, + renderer: component.context.animationRenderer, + placeholderColor: component.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1), + pointSize: displaySize, + dynamicColor: .white + ) + self.itemLayer = itemLayer + self.layer.addSublayer(itemLayer) + } + } + + let iconFitSize: CGSize = itemEnvironment.isExpanded ? CGSize(width: 44.0, height: 44.0) : CGSize(width: 24.0, height: 24.0) + if let itemLayer = self.itemLayer, let file { + let itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) + let iconSize = itemDimensions.aspectFitted(iconFitSize) + let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) / 2.0), y: floor((iconFitSize.height - iconSize.height) / 2.0)), size: iconSize) + transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY)) + transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + itemLayer.isVisibleForAnimations = itemEnvironment.isContentInFocus && component.context.sharedContext.energyUsageSettings.loopStickers + } + + if itemEnvironment.isExpanded { + let titleView: ComponentView + if let current = self.titleView { + titleView = current + } else { + titleView = ComponentView() + self.titleView = titleView + } + let titleSize = titleView.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.info.title, font: Font.regular(10.0), textColor: component.theme.chat.inputPanel.primaryTextColor)), + insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) + )), + environment: {}, + containerSize: CGSize(width: 62.0, height: 100.0) + ) + if let view = titleView.view { + if view.superview == nil { + view.alpha = 0.0 + self.addSubview(view) + } + view.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: availableSize.height - titleSize.height - 1.0), size: titleSize) + transition.setAlpha(view: view, alpha: 1.0) + } + } else if let titleView = self.titleView { + self.titleView = nil + if let view = titleView.view { + if !transition.animation.isImmediate { + view.alpha = 0.0 + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, completion: { [weak view] _ in + view?.removeFromSuperview() + }) + } else { + view.removeFromSuperview() + } + } + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + private struct StickerPaneSearchGridTransition { let deletions: [Int] let insertions: [GridNodeInsertItem] @@ -114,23 +254,30 @@ private struct StickerPaneSearchGridTransition { let stationaryItems: GridNodeStationaryItems let scrollToItem: GridNodeScrollToItem? let animated: Bool + let crossfade: Bool } -private func preparedChatMediaInputGridEntryTransition(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [StickerSearchEntry], to toEntries: [StickerSearchEntry], interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> StickerPaneSearchGridTransition { +private struct StickerPaneSearchStickerState { + let context: StickerSearchContext? + let items: [FoundStickerItem] + let isLoadingMore: Bool +} + +private func preparedChatMediaInputGridEntryTransition(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, from fromEntries: [StickerSearchEntry], to toEntries: [StickerSearchEntry], interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, crossfade: Bool) -> StickerPaneSearchGridTransition { let stationaryItems: GridNodeStationaryItems = .none let scrollToItem: GridNodeScrollToItem? = nil var animated = false animated = true - + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - + let deletions = deleteIndices let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction), previousIndex: $0.2) } let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, theme: theme, strings: strings, interaction: interaction, inputNodeInteraction: inputNodeInteraction)) } - + let firstIndexInSectionOffset = 0 - - return StickerPaneSearchGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, animated: animated) + + return StickerPaneSearchGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, animated: animated, crossfade: crossfade) } final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { @@ -138,92 +285,131 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { private let interaction: ChatEntityKeyboardInputNode.Interaction private let inputNodeInteraction: ChatMediaInputNodeInteraction private var searchInteraction: StickerPaneSearchInteraction? - + private var theme: PresentationTheme private var strings: PresentationStrings - + private let trendingPane: ChatMediaInputTrendingPane private let gridNode: GridNode private let notFoundNode: ASImageNode private let notFoundLabel: ImmediateTextNode - - private var validLayout: CGSize? - + private let packPanel = ComponentView() + private let topEdgeEffectView = EdgeEffectView() + private let bottomEdgeEffectView = EdgeEffectView() + private let selectedPackAddButton = ComponentView() + private let packPanelVisibilityFractionUpdated = ActionSlot<(CGFloat, ComponentTransition)>() + private let packPanelActiveItemUpdated = ActionSlot<(AnyHashable, AnyHashable?, ComponentTransition)>() + + private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics)? + private var enqueuedTransitions: [StickerPaneSearchGridTransition] = [] - + private let searchDisposable = MetaDisposable() - + private let selectedPackDisposable = MetaDisposable() + private let queue = Queue() private let currentEntries = Atomic<[StickerSearchEntry]?>(value: nil) private let currentRemotePacks = Atomic(value: nil) - + private var currentSearchEntries: [StickerSearchEntry] = [] + private var currentPacks: [StickerPaneSearchPack] = [] + private var currentSearchIsFinal: Bool = false + private var searchIsActive: Bool = false + private var selectedPack: StickerPaneSearchPack? + private var isPackPanelExpanded: Bool = true + private var installedPackIds = Set() + private var stickerSearchContext: StickerSearchContext? + private var currentSearchStickerCount: Int = 0 + private var currentStickerCount: Int = 0 + private let _ready = Promise() var ready: Signal { return self._ready.get() } - + var deactivateSearchBar: (() -> Void)? var updateActivity: ((Bool) -> Void)? - + var selectedPackUpdated: ((StickerPaneSearchSelectedPack?) -> Void)? + private let installDisposable = MetaDisposable() - + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: ChatEntityKeyboardInputNode.Interaction, inputNodeInteraction: ChatMediaInputNodeInteraction, stickerActionTitle: String?) { self.context = context self.interaction = interaction self.inputNodeInteraction = inputNodeInteraction - + self.theme = theme self.strings = strings - + let trendingPaneInteraction = ChatMediaInputTrendingPane.Interaction( sendSticker: interaction.sendSticker, presentController: interaction.presentController, getNavigationController: interaction.getNavigationController ) - + self.trendingPane = ChatMediaInputTrendingPane(context: context, forceTheme: theme, interaction: trendingPaneInteraction, getItemIsPreviewed: { [weak inputNodeInteraction] item in return inputNodeInteraction?.previewedStickerPackItemFile?.id == item.file.id }, isPane: false) self.trendingPane.stickerActionTitle = stickerActionTitle - + self.gridNode = GridNode() - + self.notFoundNode = ASImageNode() self.notFoundNode.displayWithoutProcessing = true self.notFoundNode.displaysAsynchronously = false self.notFoundNode.clipsToBounds = false - + self.notFoundLabel = ImmediateTextNode() self.notFoundLabel.displaysAsynchronously = false self.notFoundLabel.isUserInteractionEnabled = false self.notFoundNode.addSubnode(self.notFoundLabel) - + self.gridNode.isHidden = true self.trendingPane.isHidden = false self.notFoundNode.isHidden = true - + self.topEdgeEffectView.isUserInteractionEnabled = false + self.bottomEdgeEffectView.isUserInteractionEnabled = false + self.bottomEdgeEffectView.alpha = 0.0 + super.init() - + self.addSubnode(self.trendingPane) self.addSubnode(self.gridNode) self.addSubnode(self.notFoundNode) - + self.view.addSubview(self.topEdgeEffectView) + self.view.addSubview(self.bottomEdgeEffectView) + self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollingInitiated = { [weak self] in self?.deactivateSearchBar?() } - + self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in + guard let self else { + return + } + self.updatePackPanelExpansionFromScroll() + + guard let (bottomVisible, _) = visibleItems.bottomVisible else { + return + } + guard self.selectedPack == nil, self.currentStickerCount != 0 else { + return + } + if bottomVisible >= max(0, self.currentStickerCount - 8) { + self.stickerSearchContext?.loadMore() + } + } + self.trendingPane.scrollingInitiated = { [weak self] in self?.deactivateSearchBar?() } - + self.searchInteraction = StickerPaneSearchInteraction(open: { [weak self] info in if let strongSelf = self { strongSelf.view.window?.endEditing(true) let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) - + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme) - + let controller = strongSelf.context.sharedContext.makeStickerPackScreen( context: strongSelf.context, updatedPresentationData: (presentationData, .single(presentationData)), @@ -241,7 +427,9 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { return false } }, - actionPerformed: nil + actionPerformed: { [weak self] actions in + self?.presentStickerPackActionOverlay(actions) + } ) strongSelf.interaction.presentController(controller, nil) } @@ -278,7 +466,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { return .complete() } |> deliverOnMainQueue - + let context = strongSelf.context var cancelImpl: (() -> Void)? let progressSignal = Signal { subscriber in @@ -296,7 +484,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { |> runOn(Queue.mainQueue()) |> delay(0.12, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - + installSignal = installSignal |> afterDisposed { Queue.mainQueue().async { @@ -306,24 +494,13 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { cancelImpl = { self?.installDisposable.set(nil) } - + strongSelf.installDisposable.set(installSignal.start(next: { info, items in guard let strongSelf = self else { return } - - var animateInAsReplacement = false - if let navigationController = strongSelf.interaction.getNavigationController() { - for controller in navigationController.overlayControllers { - if let controller = controller as? UndoOverlayController { - controller.dismissWithCommitActionAndReplacementAnimation() - animateInAsReplacement = true - } - } - } - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme) - strongSelf.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: strongSelf.context), elevatedLayout: false, animateInAsReplacement: animateInAsReplacement, action: { _ in + strongSelf.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: strongSelf.context), elevatedLayout: false, action: { _ in return true })) })) @@ -340,73 +517,138 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { }, getItemIsPreviewed: { item in return inputNodeInteraction.previewedStickerPackItemFile?.id == item.file.id }) - + self._ready.set(self.trendingPane.ready) self.trendingPane.activate() - + self.updateThemeAndStrings(theme: theme, strings: strings) } - + deinit { self.searchDisposable.dispose() + self.selectedPackDisposable.dispose() self.installDisposable.dispose() } - - func updateText(_ text: String, languageCode: String?) { - let signal: Signal<([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)?, NoError> - if !text.isEmpty { - let context = self.context - let stickers: Signal<([(String?, FoundStickerItem)], Bool), NoError> = Signal { subscriber in - var signals: Signal<[Signal<(String?, [FoundStickerItem], Bool), NoError>], NoError> = .single([]) - - let query = text.trimmingCharacters(in: .whitespacesAndNewlines) - if query.isSingleEmoji { - signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [text.basicEmoji.0]) - |> map { (nil, $0.items, $0.isFinalResult) }]) - } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { - var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) - if !languageCode.lowercased().hasPrefix("en") { - signal = signal - |> mapToSignal { keywords in - return .single(keywords) - |> then( - context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) - |> map { englishKeywords in - return keywords + englishKeywords - } - ) - } + + private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult]) { + guard let action = actions.first else { + return + } + + var animateInAsReplacement = false + if let navigationController = self.interaction.getNavigationController() { + for controller in navigationController.overlayControllers { + if let controller = controller as? UndoOverlayController { + controller.dismissWithCommitActionAndReplacementAnimation() + animateInAsReplacement = true + } + } + } + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + let controller: UndoOverlayController + switch action.action { + case .add: + self.setPackInstalledState(id: action.info.id, installed: true) + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_AddedTitle, + text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string, + undo: false, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { _ in + return true + } + ) + case let .remove(positionInList): + self.setPackInstalledState(id: action.info.id, installed: false) + controller = UndoOverlayController( + presentationData: presentationData, + content: .stickersModified( + title: presentationData.strings.StickerPackActionInfo_RemovedTitle, + text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string, + undo: true, + info: action.info, + topItem: action.items.first, + context: self.context + ), + elevatedLayout: false, + animateInAsReplacement: animateInAsReplacement, + action: { [weak self] overlayAction in + if case .undo = overlayAction { + let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start() + self?.setPackInstalledState(id: action.info.id, installed: true) } - signals = signal - |> map { keywords -> [Signal<(String?, [FoundStickerItem], Bool), NoError>] in - let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } - return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode) - |> map { (nil, $0.items, $0.isFinalResult) }] + return true + } + ) + } + + if let navigationController = self.interaction.getNavigationController() { + navigationController.presentOverlay(controller: controller) + } else { + self.interaction.presentController(controller, nil) + } + } + + func updateText(_ text: String, languageCode: String?) { + if self.selectedPack != nil { + self.clearSelectedPack(applySearchResults: false) + } + self.stickerSearchContext = nil + self.currentSearchStickerCount = 0 + self.currentStickerCount = 0 + self.isPackPanelExpanded = true + let _ = self.currentRemotePacks.swap(nil) + + let query = text.trimmingCharacters(in: .whitespacesAndNewlines) + let signal: Signal<(StickerPaneSearchStickerState, FoundStickerSets, Bool, FoundStickerSets?)?, NoError> + if query.isSingleEmoji || query.count >= 2 { + let context = self.context + let stickers: Signal + if query.isSingleEmoji { + let searchContext = context.engine.stickers.stickerSearchContext(query: nil, emoticon: [query.basicEmoji.0]) + stickers = searchContext.state + |> map { state -> StickerPaneSearchStickerState in + return StickerPaneSearchStickerState(context: searchContext, items: state.items, isLoadingMore: state.isLoadingMore) + } + } else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" { + var keywords = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) + if !languageCode.lowercased().hasPrefix("en") { + keywords = keywords + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) } } - - return (signals - |> mapToSignal { signals in - return combineLatest(signals) - }).start(next: { results in - var result: [(String?, FoundStickerItem)] = [] - var allAreFinal = true - for (emoji, stickers, isFinal) in results { - for sticker in stickers { - result.append((emoji, sticker)) - } - if !isFinal { - allAreFinal = false - } + stickers = .single(StickerPaneSearchStickerState(context: nil, items: [], isLoadingMore: true)) + |> then( + keywords + |> mapToSignal { keywords -> Signal in + let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } + let searchContext = context.engine.stickers.stickerSearchContext(query: query, emoticon: emoticon, inputLanguageCode: languageCode) + return searchContext.state + |> map { state -> StickerPaneSearchStickerState in + return StickerPaneSearchStickerState(context: searchContext, items: state.items, isLoadingMore: state.isLoadingMore) } - subscriber.putNext((result, allAreFinal)) - }, completed: { -// subscriber.putCompletion() }) + } else { + stickers = .single(StickerPaneSearchStickerState(context: nil, items: [], isLoadingMore: false)) } - - let local = context.engine.stickers.searchStickerSets(query: text) - let remote = context.engine.stickers.searchStickerSetsRemotely(query: text) + + let local = context.engine.stickers.searchStickerSets(query: query) + let remote = context.engine.stickers.searchStickerSetsRemotely(query: query) |> delay(0.2, queue: Queue.mainQueue()) let rawPacks = local |> mapToSignal { result -> Signal<(FoundStickerSets, Bool, FoundStickerSets?), NoError> in @@ -422,16 +664,12 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { } ) } - - let installedPackIds = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]) - |> map { view -> Set in - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - for entry in packsEntries { - installedPacks.insert(entry.id) - } - } + + let installedPackIds = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks)) + |> map { entries -> Set in + var installedPacks = Set() + for entry in entries { + installedPacks.insert(entry.id) } return installedPacks } @@ -439,14 +677,14 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { let packs = combineLatest(rawPacks, installedPackIds) |> map { packs, installedPackIds -> (FoundStickerSets, Bool, FoundStickerSets?) in var (localPacks, completed, remotePacks) = packs - + for i in 0 ..< localPacks.infos.count { let installed = installedPackIds.contains(localPacks.infos[i].0) if installed != localPacks.infos[i].3 { localPacks.infos[i].3 = installed } } - + if remotePacks != nil { for i in 0 ..< remotePacks!.infos.count { let installed = installedPackIds.contains(remotePacks!.infos[i].0) @@ -455,121 +693,390 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { } } } - + return (localPacks, completed, remotePacks) } - + signal = combineLatest(stickers, packs) - |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in - return (stickers.0, packs.0, packs.1 && stickers.1, packs.2) + |> map { stickers, packs -> (StickerPaneSearchStickerState, FoundStickerSets, Bool, FoundStickerSets?)? in + return (stickers, packs.0, packs.1 && !stickers.isLoadingMore, packs.2) } - self.updateActivity?(true) } else { signal = .single(nil) self.updateActivity?(false) } - + self.searchDisposable.set((signal |> deliverOn(self.queue)).start(next: { [weak self] result in Queue.mainQueue().async { guard let strongSelf = self, let interaction = strongSelf.searchInteraction else { return } - - var entries: [StickerSearchEntry] = [] + if let (stickers, packs, final, remote) = result { + strongSelf.stickerSearchContext = stickers.context + strongSelf.currentSearchStickerCount = stickers.items.count + strongSelf.currentStickerCount = stickers.items.count + strongSelf.updateActivity?(stickers.items.isEmpty && stickers.isLoadingMore) + if let remote = remote { let _ = strongSelf.currentRemotePacks.swap(remote) } strongSelf.gridNode.isHidden = false strongSelf.trendingPane.isHidden = true + + let previousPacks = strongSelf.currentPacks + let entries = strongSelf.entries(stickers: stickers.items) - if final { - strongSelf.updateActivity?(false) - } - - var index = 0 - var existingStickerIds = Set() - var previousCode: String? - for (code, sticker) in stickers { - if let id = sticker.file.id, !existingStickerIds.contains(id) { - entries.append(.sticker(index: index, code: code != previousCode ? code : nil, stickerItem: sticker, theme: strongSelf.theme)) - index += 1 - - previousCode = code - existingStickerIds.insert(id) - } - } - var isFirstGlobal = true - for (collectionId, info, _, installed) in packs.infos { - if let info = info as? StickerPackCollectionInfo { - var topItems: [StickerPackItem] = [] - for e in packs.entries { - if let item = e.item as? StickerPackItem { - if e.index.collectionId == collectionId { - topItems.append(item) - } - } + var packItems: [StickerPaneSearchPack] = strongSelf.packs(from: packs) + if !strongSelf.installedPackIds.isEmpty { + packItems = packItems.map { pack in + if strongSelf.installedPackIds.contains(pack.info.id) && !pack.installed { + return StickerPaneSearchPack(info: pack.info, topItems: pack.topItems, installed: true) + } else { + return pack } - entries.append(.global(index: index, info: info, topItems: topItems, installed: installed, topSeparator: !isFirstGlobal)) - isFirstGlobal = false - index += 1 } } - - if final || !entries.isEmpty { - strongSelf.notFoundNode.isHidden = !entries.isEmpty + + strongSelf.currentSearchEntries = entries + strongSelf.currentPacks = packItems + if let selectedPack = strongSelf.selectedPack, let updatedPack = packItems.first(where: { $0.info.id == selectedPack.info.id }), selectedPack.installed != updatedPack.installed { + strongSelf.setPackInstalledState(id: selectedPack.info.id, installed: updatedPack.installed, transition: .immediate) + } + strongSelf.currentSearchIsFinal = final + strongSelf.searchIsActive = true + + if strongSelf.selectedPack == nil { + strongSelf.enqueueEntries(entries, interaction: interaction) + strongSelf.updateNotFound() + } + if previousPacks != packItems { + strongSelf.requestLayout(transition: .immediate) } } else { let _ = strongSelf.currentRemotePacks.swap(nil) + strongSelf.stickerSearchContext = nil + strongSelf.currentSearchStickerCount = 0 + strongSelf.currentStickerCount = 0 + strongSelf.currentSearchEntries = [] + strongSelf.currentPacks = [] + strongSelf.currentSearchIsFinal = false + strongSelf.searchIsActive = false strongSelf.updateActivity?(false) strongSelf.gridNode.isHidden = true strongSelf.notFoundNode.isHidden = true strongSelf.trendingPane.isHidden = false + strongSelf.enqueueEntries([], interaction: interaction) + strongSelf.requestLayout(transition: .immediate) } - - let previousEntries = strongSelf.currentEntries.swap(entries) - let transition = preparedChatMediaInputGridEntryTransition(context: strongSelf.context, theme: strongSelf.theme, strings: strongSelf.strings, from: previousEntries ?? [], to: entries, interaction: interaction, inputNodeInteraction: strongSelf.inputNodeInteraction) - strongSelf.enqueueTransition(transition) } })) } + + private func entries(stickers: [FoundStickerItem]) -> [StickerSearchEntry] { + var entries: [StickerSearchEntry] = [] + var index = 0 + var existingStickerIds = Set() + for sticker in stickers { + if let id = sticker.file.id, !existingStickerIds.contains(id) { + entries.append(.sticker(index: index, code: nil, stickerItem: sticker, theme: self.theme)) + index += 1 + existingStickerIds.insert(id) + } + } + + return entries + } + + private func packs(from packs: FoundStickerSets) -> [StickerPaneSearchPack] { + var result: [StickerPaneSearchPack] = [] + var existingIds = Set() + for (collectionId, info, _, installed) in packs.infos { + guard !existingIds.contains(collectionId), let info = info as? StickerPackCollectionInfo else { + continue + } + existingIds.insert(collectionId) + + var topItems: [StickerPackItem] = [] + for entry in packs.entries { + if let item = entry.item as? StickerPackItem, entry.index.collectionId == collectionId { + topItems.append(item) + } + } + result.append(StickerPaneSearchPack(info: info, topItems: topItems, installed: installed)) + } + + return result + } + + private func entries(packItems: [StickerPackItem]) -> [StickerSearchEntry] { + var entries: [StickerSearchEntry] = [] + var existingStickerIds = Set() + var index = 0 + for item in packItems { + let file = item.file._parse() + if let id = file.id, !existingStickerIds.contains(id) { + entries.append(.sticker(index: index, code: nil, stickerItem: FoundStickerItem(file: file, stringRepresentations: item.getStringRepresentationsOfIndexKeys()), theme: self.theme)) + existingStickerIds.insert(id) + index += 1 + } + } + return entries + } + + private var shouldDisplayPackPanel: Bool { + return self.searchIsActive && !self.currentPacks.isEmpty + } + + private var currentPackPanelHeight: CGFloat { + guard self.shouldDisplayPackPanel else { + return 0.0 + } + return packPanelHeight + } + + private var currentVisiblePackPanelHeight: CGFloat { + guard self.shouldDisplayPackPanel else { + return 0.0 + } + return self.isPackPanelExpanded ? packPanelHeight : collapsedPackPanelHeight + } + + private var isInstallPackButtonVisible: Bool { + guard let selectedPack = self.selectedPack else { + return false + } + return !selectedPack.installed && !self.installedPackIds.contains(selectedPack.info.id) + } + func setPackInstalledState(id: EngineItemCollectionId, installed: Bool, transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)) { + if installed { + self.installedPackIds.insert(id) + } else { + self.installedPackIds.remove(id) + } + + var updatedSelectedPack: StickerPaneSearchPack? + if let selectedPack = self.selectedPack, selectedPack.info.id == id { + if selectedPack.installed != installed { + let pack = StickerPaneSearchPack(info: selectedPack.info, topItems: selectedPack.topItems, installed: installed) + self.selectedPack = pack + updatedSelectedPack = pack + } else { + updatedSelectedPack = selectedPack + } + } + + var updatedPacks = false + self.currentPacks = self.currentPacks.map { pack in + if pack.info.id == id && pack.installed != installed { + updatedPacks = true + return StickerPaneSearchPack(info: pack.info, topItems: pack.topItems, installed: installed) + } else { + return pack + } + } + + if let updatedSelectedPack { + self.selectedPackUpdated?(StickerPaneSearchSelectedPack(info: updatedSelectedPack.info, installed: updatedSelectedPack.installed)) + } + + if updatedSelectedPack != nil || updatedPacks { + self.requestLayout(transition: transition) + } + } + + private func updatePackPanelExpansionFromScroll() { + guard self.shouldDisplayPackPanel else { + return + } + + let contentOffsetY = self.gridNode.scrollView.contentOffset.y + let shouldExpand: Bool + if self.gridNode.scrollView.contentInset.top < 10.0 { + shouldExpand = true + } else { + shouldExpand = contentOffsetY <= -packPanelHeight + 20.0 + } + if self.isPackPanelExpanded != shouldExpand { + self.isPackPanelExpanded = shouldExpand + self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + private func resetGridScrollToTop() { + let scrollView = self.gridNode.scrollView + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + + private func enqueueEntries(_ entries: [StickerSearchEntry], interaction: StickerPaneSearchInteraction, crossfade: Bool = false) { + let previousEntries = self.currentEntries.swap(entries) + let transition = preparedChatMediaInputGridEntryTransition(context: self.context, theme: self.theme, strings: self.strings, from: previousEntries ?? [], to: entries, interaction: interaction, inputNodeInteraction: self.inputNodeInteraction, crossfade: crossfade) + self.enqueueTransition(transition) + } + + private func updateNotFound() { + if self.selectedPack != nil || !self.searchIsActive { + self.notFoundNode.isHidden = true + } else if self.currentSearchIsFinal || !self.currentSearchEntries.isEmpty || !self.currentPacks.isEmpty { + self.notFoundNode.isHidden = !(self.currentSearchEntries.isEmpty && self.currentPacks.isEmpty) + } else { + self.notFoundNode.isHidden = true + } + } + + private func selectPack(_ pack: StickerPaneSearchPack) { + guard let interaction = self.searchInteraction else { + return + } + + self.view.window?.endEditing(true) + self.deactivateSearchBar?() + + self.selectedPackDisposable.set(nil) + self.selectedPack = pack + self.currentStickerCount = 0 + self.notFoundNode.isHidden = true + self.gridNode.isHidden = false + self.trendingPane.isHidden = true + self.selectedPackUpdated?(StickerPaneSearchSelectedPack(info: pack.info, installed: pack.installed)) + + self.enqueueEntries(self.entries(packItems: pack.topItems), interaction: interaction, crossfade: true) + self.isPackPanelExpanded = true + self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + self.resetGridScrollToTop() + + let packId = pack.info.id + self.selectedPackDisposable.set((self.context.engine.stickers.loadedStickerPack(reference: .id(id: pack.info.id.id, accessHash: pack.info.accessHash), forceActualized: false) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self, let interaction = self.searchInteraction, self.selectedPack?.info.id == packId else { + return + } + switch result { + case let .result(_, items, _): + self.enqueueEntries(self.entries(packItems: items), interaction: interaction) + case .fetching, .none: + break + } + })) + } + + private func installSelectedStickerPack() { + guard let selectedPack = self.selectedPack, !selectedPack.installed, !self.installedPackIds.contains(selectedPack.info.id) else { + return + } + + let context = self.context + let packId = selectedPack.info.id + let accessHash = selectedPack.info.accessHash + + self.setPackInstalledState(id: packId, installed: true) + + let installSignal = (context.engine.stickers.loadedStickerPack(reference: .id(id: packId.id, accessHash: accessHash), forceActualized: false) + |> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in + switch result { + case let .result(info, items, installed): + let info = info._parse() + if installed { + return .single((info, items)) + } else { + return preloadedStickerPackThumbnail(account: context.account, info: StickerPackCollectionInfo.Accessor(info), items: items) + |> filter { $0 } + |> ignoreValues + |> then( + context.engine.stickers.addStickerPackInteractively(info: info, items: items) + |> ignoreValues + ) + |> mapToSignal { _ -> Signal<(StickerPackCollectionInfo, [StickerPackItem]), NoError> in + } + |> then(.single((info, items))) + } + case .fetching: + break + case .none: + break + } + return .complete() + } + |> deliverOnMainQueue) + + self.installDisposable.set(installSignal.start(next: { [weak self] info, items in + guard let self else { + return + } + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme) + self.interaction.getNavigationController()?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: self.context), elevatedLayout: false, action: { _ in + return true + })) + })) + } + + func clearSelectedPack(applySearchResults: Bool = true) { + guard self.selectedPack != nil else { + return + } + self.selectedPack = nil + self.selectedPackDisposable.set(nil) + + self.selectedPackUpdated?(nil) + + if applySearchResults, let interaction = self.searchInteraction { + self.currentStickerCount = self.currentSearchStickerCount + self.gridNode.isHidden = !self.searchIsActive + self.trendingPane.isHidden = self.searchIsActive + self.enqueueEntries(self.currentSearchEntries, interaction: interaction, crossfade: true) + self.updateNotFound() + self.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings self.notFoundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/StickersNotFoundIcon"), color: theme.list.freeMonoIconColor) self.notFoundLabel.attributedText = NSAttributedString(string: strings.Stickers_NoStickersFound, font: Font.medium(14.0), textColor: theme.list.freeTextColor) } - + private func enqueueTransition(_ transition: StickerPaneSearchGridTransition) { self.enqueuedTransitions.append(transition) - + if self.validLayout != nil { while !self.enqueuedTransitions.isEmpty { self.dequeueTransition() } } } - + private func dequeueTransition() { if let transition = self.enqueuedTransitions.first { self.enqueuedTransitions.remove(at: 0) + + if transition.crossfade, let snapshotView = self.gridNode.scrollView.snapshotContentTree() { + snapshotView.frame = self.gridNode.frame + self.gridNode.view.superview?.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + + self.gridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } let itemTransition: ContainedViewLayoutTransition = .immediate self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in }) } } - + func updatePreviewing(animated: Bool) { self.gridNode.forEachItemNode { itemNode in if let itemNode = itemNode as? StickerPaneSearchStickerItemNode { itemNode.updatePreviewing(animated: animated) - } else if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { - itemNode.updatePreviewing(animated: animated) } } self.trendingPane.updatePreviewing(animated: animated) } - + func itemAt(point: CGPoint) -> (ASDisplayNode, Any)? { if !self.trendingPane.isHidden { if let (itemNode, item) = self.trendingPane.itemAt(point: self.view.convert(point, to: self.trendingPane.view)) { @@ -579,36 +1086,165 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { if let itemNode = self.gridNode.itemNodeAtPoint(self.view.convert(point, to: self.gridNode.view)) { if let itemNode = itemNode as? StickerPaneSearchStickerItemNode, let stickerItem = itemNode.stickerItem { return (itemNode, StickerPreviewPeekItem.found(stickerItem)) - } else if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode { - if let (node, item) = itemNode.itemAt(point: self.view.convert(point, to: itemNode.view)) { - return (node, StickerPreviewPeekItem.pack(item.file._parse())) - } } } } return nil } + + private func updatePackButtonsLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + if self.shouldDisplayPackPanel { + let componentTransition = ComponentTransition(transition) + let panelSize = self.packPanel.update( + transition: componentTransition, + component: AnyComponent(EntityKeyboardTopPanelComponent( + id: AnyHashable("stickerSearchPacks"), + theme: self.theme, + customTintColor: nil, + items: self.currentPacks.map { pack in + return EntityKeyboardTopPanelComponent.Item( + id: AnyHashable(pack.info.id), + isReorderable: false, + content: AnyComponent(StickerSearchPackTopPanelItemComponent( + context: self.context, + theme: self.theme, + info: pack.info, + topItem: pack.topItems.first, + pressed: { [weak self] in + self?.selectPack(pack) + } + )) + ) + }, + containerSideInset: 0.0, + forceActiveItemId: self.selectedPack.flatMap { AnyHashable($0.info.id) }, + displayHighlightInExpanded: true, + automaticallySelectsFirstItem: false, + itemSpacing: 14.0, + activeContentItemIdUpdated: self.packPanelActiveItemUpdated, + reorderItems: { _ in } + )), + environment: { + EntityKeyboardTopContainerPanelEnvironment( + isContentInFocus: true, + height: collapsedPackPanelHeight, + visibilityFractionUpdated: self.packPanelVisibilityFractionUpdated, + isExpandedUpdated: { _, _ in } + ) + }, + containerSize: CGSize(width: size.width, height: self.currentVisiblePackPanelHeight) + ) + + if let view = self.packPanel.view { + if view.superview == nil { + self.view.addSubview(view) + } + componentTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: panelSize)) + } + } else if let view = self.packPanel.view { + view.removeFromSuperview() + } + + let isVisible = self.isInstallPackButtonVisible + + let componentTransition = ComponentTransition(transition) + let edgeEffectHeight: CGFloat = isVisible ? 88.0 + bottomInset : 0.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.bottomEdgeEffectView, frame: edgeEffectFrame) + self.bottomEdgeEffectView.update( + content: self.theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), + blur: true, + alpha: 1.0, + rect: edgeEffectFrame, + edge: .bottom, + edgeSize: min(edgeEffectFrame.height, 80.0), + transition: componentTransition + ) + transition.updateAlpha(layer: self.bottomEdgeEffectView.layer, alpha: isVisible ? 1.0 : 0.0) + + if isVisible, let selectedPack = self.selectedPack { + let buttonTitle = self.strings.StickerPack_AddStickerCount(selectedPack.info.count) + let buttonForegroundColor = self.theme.list.itemCheckColors.foregroundColor + let buttonBackgroundColor = self.theme.list.itemCheckColors.fillColor + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: bottomInset, innerDiameter: 52.0, sideInset: 30.0) + let buttonSize = self.selectedPackAddButton.update( + transition: componentTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .actualGlass, + color: buttonBackgroundColor, + foreground: buttonForegroundColor, + pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(buttonTitle), + component: AnyComponent(Text(text: buttonTitle, font: Font.semibold(17.0), color: buttonForegroundColor)) + ), + action: { [weak self] in + self?.installSelectedStickerPack() + } + )), + environment: {}, + containerSize: CGSize(width: max(0.0, size.width - buttonInsets.left - buttonInsets.right), height: 52.0) + ) + + if let buttonView = self.selectedPackAddButton.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + buttonView.isUserInteractionEnabled = true + buttonView.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - bottomInset - buttonInsets.bottom - buttonSize.height), size: buttonSize) + componentTransition.setAlpha(view: buttonView, alpha: 1.0) + } + } else if let buttonView = self.selectedPackAddButton.view { + buttonView.isUserInteractionEnabled = false + componentTransition.setAlpha(view: buttonView, alpha: 0.0) + } + } + func requestLayout(transition: ContainedViewLayoutTransition) { + guard let (size, leftInset, rightInset, bottomInset, inputHeight, deviceMetrics) = self.validLayout else { + return + } + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) + } + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, inputHeight: CGFloat, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition) { let firstLayout = self.validLayout == nil - self.validLayout = size - + self.validLayout = (size, leftInset, rightInset, bottomInset, inputHeight, deviceMetrics) + + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) + self.topEdgeEffectView.update( + content: self.theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), + blur: true, + alpha: 1.0, + rect: edgeEffectFrame, + edge: .top, + edgeSize: edgeEffectFrame.height, + transition: ComponentTransition(transition) + ) + transition.updateAlpha(layer: self.topEdgeEffectView.layer, alpha: self.shouldDisplayPackPanel ? 1.0 : 0.0) + self.updatePackButtonsLayout(size: size, bottomInset: bottomInset, transition: transition) + if let image = self.notFoundNode.image { - let areaHeight = size.height - inputHeight - + let areaHeight = max(0.0, size.height - inputHeight) + let labelSize = self.notFoundLabel.updateLayout(CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)) - + transition.updateFrame(node: self.notFoundNode, frame: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((areaHeight - image.size.height - labelSize.height) / 2.0)), size: image.size)) transition.updateFrame(node: self.notFoundLabel, frame: CGRect(origin: CGPoint(x: floor((image.size.width - labelSize.width) / 2.0), y: image.size.height + 8.0), size: labelSize)) } - + let contentFrame = CGRect(origin: CGPoint(), size: size) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0 + bottomInset, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + let gridTopInset: CGFloat = 4.0 + self.currentPackPanelHeight + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: contentFrame.size, insets: UIEdgeInsets(top: gridTopInset, left: 0.0, bottom: 4.0 + bottomInset + 64.0, right: 0.0), preloadSize: 300.0, type: .fixed(itemSize: CGSize(width: 75.0, height: 75.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.trendingPane, frame: contentFrame) self.trendingPane.updateLayout(size: contentFrame.size, topInset: 0.0, bottomInset: bottomInset, isExpanded: false, isVisible: true, deviceMetrics: deviceMetrics, transition: transition) - + transition.updateFrame(node: self.gridNode, frame: contentFrame) if firstLayout { while !self.enqueuedTransitions.isEmpty { @@ -616,23 +1252,43 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode { } } } - + func animateIn(additivePosition: CGFloat, transition: ContainedViewLayoutTransition) { self.gridNode.alpha = 0.0 transition.updateAlpha(node: self.gridNode, alpha: 1.0, completion: { _ in }) + if let view = self.packPanel.view { + view.alpha = 0.0 + ComponentTransition(transition).setAlpha(view: view, alpha: 1.0) + } + self.topEdgeEffectView.alpha = 0.0 + ComponentTransition(transition).setAlpha(view: self.topEdgeEffectView, alpha: self.shouldDisplayPackPanel ? 1.0 : 0.0) + self.bottomEdgeEffectView.alpha = 0.0 + ComponentTransition(transition).setAlpha(view: self.bottomEdgeEffectView, alpha: self.isInstallPackButtonVisible ? 1.0 : 0.0) + if let buttonView = self.selectedPackAddButton.view { + buttonView.alpha = 0.0 + ComponentTransition(transition).setAlpha(view: buttonView, alpha: self.isInstallPackButtonVisible ? 1.0 : 0.0) + } self.trendingPane.alpha = 0.0 transition.updateAlpha(node: self.trendingPane, alpha: 1.0, completion: { _ in }) - + if case let .animated(duration, curve) = transition { self.trendingPane.layer.animatePosition(from: CGPoint(x: 0.0, y: additivePosition), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, additive: true) } } - + func animateOut(transition: ContainedViewLayoutTransition) { transition.updateAlpha(node: self.gridNode, alpha: 0.0, completion: { _ in }) + if let view = self.packPanel.view { + ComponentTransition(transition).setAlpha(view: view, alpha: 0.0) + } + ComponentTransition(transition).setAlpha(view: self.topEdgeEffectView, alpha: 0.0) + ComponentTransition(transition).setAlpha(view: self.bottomEdgeEffectView, alpha: 0.0) + if let buttonView = self.selectedPackAddButton.view { + ComponentTransition(transition).setAlpha(view: buttonView, alpha: 0.0) + } transition.updateAlpha(node: self.trendingPane, alpha: 0.0, completion: { _ in }) transition.updateAlpha(node: self.notFoundNode, alpha: 0.0, completion: { _ in diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD index 664a0e0e73..44eacc4261 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", @@ -25,7 +24,10 @@ swift_library( "//submodules/TelegramStringFormatting", "//submodules/PresentationDataUtils", "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/Components/ResizableSheetComponent", + "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/AnimatedCounterComponent", "//submodules/AvatarNode", diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift index cd8b140f4d..2102d5a85a 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import MultilineTextWithEntitiesComponent -import SolidRoundedButtonComponent import PresentationDataUtils import Markdown import UndoUI @@ -22,28 +21,314 @@ import QrCodeUI import InviteLinksUI import PlainButtonComponent import AnimatedCounterComponent +import BundleIconComponent +import GlassBarButtonComponent +import ResizableSheetComponent -private final class ChatFolderLinkPreviewScreenComponent: Component { +private struct ChatFolderLinkPreviewResolvedData: Equatable { + let title: String + let topBadge: String? + let descriptionText: NSAttributedString + let listHeaderTitle: String + let listHeaderActionItems: [AnimatedCounterComponent.Item] + let showsListHeaderAction: Bool + let actionButtonTitle: String? + let actionButtonBadge: Int + let actionButtonEnabled: Bool + let allChatsAdded: Bool + let canAddChatCount: Int + let isLinkList: Bool + + static func ==(lhs: ChatFolderLinkPreviewResolvedData, rhs: ChatFolderLinkPreviewResolvedData) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.topBadge != rhs.topBadge { + return false + } + if !lhs.descriptionText.isEqual(to: rhs.descriptionText) { + return false + } + if lhs.listHeaderTitle != rhs.listHeaderTitle { + return false + } + if lhs.listHeaderActionItems != rhs.listHeaderActionItems { + return false + } + if lhs.showsListHeaderAction != rhs.showsListHeaderAction { + return false + } + if lhs.actionButtonTitle != rhs.actionButtonTitle { + return false + } + if lhs.actionButtonBadge != rhs.actionButtonBadge { + return false + } + if lhs.actionButtonEnabled != rhs.actionButtonEnabled { + return false + } + if lhs.allChatsAdded != rhs.allChatsAdded { + return false + } + if lhs.canAddChatCount != rhs.canAddChatCount { + return false + } + if lhs.isLinkList != rhs.isLinkList { + return false + } + return true + } +} + +private func chatFolderLinkPreviewResolvedData( + component: ChatFolderLinkPreviewScreenComponent, + theme: PresentationTheme, + strings: PresentationStrings, + selectedItems: Set +) -> ChatFolderLinkPreviewResolvedData { + let isLinkList: Bool + if case .linkList = component.subject { + isLinkList = true + } else { + isLinkList = false + } + + var allChatsAdded = false + var canAddChatCount = 0 + + let title: String + if isLinkList { + title = strings.FolderLinkPreview_TitleShare + } else if let linkContents = component.linkContents { + if case .remove = component.subject { + title = strings.FolderLinkPreview_TitleRemove + } else if linkContents.localFilterId != nil { + if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) { + allChatsAdded = true + } + canAddChatCount = linkContents.peers.count - linkContents.alreadyMemberPeerIds.count + + if allChatsAdded { + title = strings.FolderLinkPreview_TitleAddFolder + } else { + title = strings.FolderLinkPreview_TitleAddChats(Int32(canAddChatCount)) + } + } else { + title = strings.FolderLinkPreview_TitleAddFolder + } + } else { + title = " " + } + + let topBadge: String? + if isLinkList || allChatsAdded { + topBadge = nil + } else if case .remove = component.subject { + topBadge = nil + } else if let linkContents = component.linkContents, linkContents.localFilterId != nil, canAddChatCount != 0 { + topBadge = "+\(canAddChatCount)" + } else { + topBadge = nil + } + + let descriptionText: NSAttributedString + if isLinkList { + descriptionText = NSAttributedString(string: strings.FolderLinkPreview_TextLinkList) + } else if let linkContents = component.linkContents { + if case .remove = component.subject { + descriptionText = NSAttributedString(string: strings.FolderLinkPreview_TextRemoveFolder, font: Font.regular(15.0), textColor: theme.list.freeTextColor) + } else if allChatsAdded { + descriptionText = NSAttributedString(string: strings.FolderLinkPreview_TextAllAdded, font: Font.regular(15.0), textColor: theme.list.freeTextColor) + } else if linkContents.localFilterId == nil { + descriptionText = NSAttributedString(string: strings.FolderLinkPreview_TextAddFolder, font: Font.regular(15.0), textColor: theme.list.freeTextColor) + } else if let title = linkContents.title { + let chatCountString = strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount)) + + let textValue = NSMutableAttributedString(string: strings.FolderLinkPreview_TextAddChatsV2) + textValue.addAttributes([ + .font: Font.regular(15.0), + .foregroundColor: theme.list.freeTextColor + ], range: NSRange(location: 0, length: textValue.length)) + + let folderRange = (textValue.string as NSString).range(of: "{folder}") + if folderRange.location != NSNotFound { + textValue.replaceCharacters(in: folderRange, with: "") + textValue.insert(title.attributedString(font: Font.semibold(15.0), textColor: theme.list.freeTextColor), at: folderRange.location) + } + + let chatsRange = (textValue.string as NSString).range(of: "{chats}") + if chatsRange.location != NSNotFound { + textValue.replaceCharacters(in: chatsRange, with: "") + textValue.insert(NSAttributedString(string: chatCountString, font: Font.semibold(15.0), textColor: theme.list.freeTextColor), at: chatsRange.location) + } + + descriptionText = textValue + } else { + descriptionText = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: theme.list.freeTextColor) + } + } else { + descriptionText = NSAttributedString(string: " ") + } + + let listHeaderTitle: String + if isLinkList { + listHeaderTitle = strings.FolderLinkPreview_LinkSectionHeader + } else if let linkContents = component.linkContents { + if case .remove = component.subject { + listHeaderTitle = strings.FolderLinkPreview_RemoveSectionSelectedHeader(Int32(linkContents.peers.count)) + } else if allChatsAdded { + listHeaderTitle = strings.FolderLinkPreview_ChatSectionHeader(Int32(linkContents.peers.count)) + } else { + listHeaderTitle = strings.FolderLinkPreview_ChatSectionJoinHeader(Int32(linkContents.peers.count)) + } + } else { + listHeaderTitle = " " + } + + var listHeaderActionItems: [AnimatedCounterComponent.Item] = [] + if !isLinkList, let linkContents = component.linkContents { + let dynamicIndex = strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{dynamic}") + let staticIndex = strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{static}") + var headerActionItemIndices: [Int: Int] = [:] + if let dynamicIndex, let staticIndex { + if dynamicIndex.lowerBound < staticIndex.lowerBound { + headerActionItemIndices[0] = 0 + headerActionItemIndices[1] = 1 + } else { + headerActionItemIndices[0] = 1 + headerActionItemIndices[1] = 0 + } + } else if dynamicIndex != nil { + headerActionItemIndices[0] = 0 + } else if staticIndex != nil { + headerActionItemIndices[1] = 0 + } + + let dynamicItem: AnimatedCounterComponent.Item + let staticItem: AnimatedCounterComponent.Item + + if selectedItems.count == linkContents.peers.count { + dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartDeselect, numericValue: 0) + staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: strings.FolderLinkPreview_ListSelectionSelectAllStaticPartDeselect, numericValue: 1) + } else { + dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartSelect, numericValue: 1) + staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: strings.FolderLinkPreview_ListSelectionSelectAllStaticPartSelect, numericValue: 1) + } + + if let dynamicIndex = headerActionItemIndices[0], let staticIndex = headerActionItemIndices[1] { + if dynamicIndex < staticIndex { + listHeaderActionItems = [dynamicItem, staticItem] + } else { + listHeaderActionItems = [staticItem, dynamicItem] + } + } else if headerActionItemIndices[0] != nil { + listHeaderActionItems = [dynamicItem] + } else if headerActionItemIndices[1] != nil { + listHeaderActionItems = [staticItem] + } + } + + let showsListHeaderAction: Bool + if isLinkList { + showsListHeaderAction = false + } else if let linkContents = component.linkContents { + showsListHeaderAction = !allChatsAdded && linkContents.peers.count > 1 + } else { + showsListHeaderAction = false + } + + let actionButtonTitle: String? + let actionButtonBadge: Int + if isLinkList { + actionButtonTitle = nil + actionButtonBadge = 0 + } else if case .remove = component.subject { + actionButtonBadge = selectedItems.count + if selectedItems.isEmpty { + actionButtonTitle = strings.FolderLinkPreview_ButtonRemoveFolder + } else { + actionButtonTitle = strings.FolderLinkPreview_ButtonRemoveFolderAndChats + } + } else if allChatsAdded { + actionButtonBadge = 0 + actionButtonTitle = strings.Common_OK + } else if let linkContents = component.linkContents { + actionButtonBadge = max(0, selectedItems.count - (linkContents.peers.count - canAddChatCount)) + if linkContents.localFilterId != nil { + if actionButtonBadge == 0 { + actionButtonTitle = strings.FolderLinkPreview_ButtonDoNotJoinChats + } else { + actionButtonTitle = strings.FolderLinkPreview_ButtonJoinChats + } + } else { + actionButtonTitle = strings.FolderLinkPreview_ButtonAddFolder + } + } else { + actionButtonTitle = " " + actionButtonBadge = 0 + } + + return ChatFolderLinkPreviewResolvedData( + title: title, + topBadge: topBadge, + descriptionText: descriptionText, + listHeaderTitle: listHeaderTitle, + listHeaderActionItems: listHeaderActionItems, + showsListHeaderAction: showsListHeaderAction, + actionButtonTitle: actionButtonTitle, + actionButtonBadge: actionButtonBadge, + actionButtonEnabled: !selectedItems.isEmpty || component.linkContents?.localFilterId != nil, + allChatsAdded: allChatsAdded, + canAddChatCount: canAddChatCount, + isLinkList: isLinkList + ) +} + +private final class ChatFolderLinkPreviewContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment - + let context: AccountContext let subject: ChatFolderLinkPreviewScreen.Subject let linkContents: ChatFolderLinkContents? - let completion: (() -> Void)? - + let theme: PresentationTheme + let resolvedData: ChatFolderLinkPreviewResolvedData + let selectedItems: Set + let linkListItems: [ExportedChatFolderLink] + let peerAction: (EnginePeer) -> Void + let toggleAllSelection: () -> Void + let openCreateLink: () -> Void + let openLink: (ExportedChatFolderLink) -> Void + let openLinkContextAction: (ExportedChatFolderLink, ContextExtractedContentContainingView, ContextGesture?) -> Void + init( context: AccountContext, subject: ChatFolderLinkPreviewScreen.Subject, linkContents: ChatFolderLinkContents?, - completion: (() -> Void)? + theme: PresentationTheme, + resolvedData: ChatFolderLinkPreviewResolvedData, + selectedItems: Set, + linkListItems: [ExportedChatFolderLink], + peerAction: @escaping (EnginePeer) -> Void, + toggleAllSelection: @escaping () -> Void, + openCreateLink: @escaping () -> Void, + openLink: @escaping (ExportedChatFolderLink) -> Void, + openLinkContextAction: @escaping (ExportedChatFolderLink, ContextExtractedContentContainingView, ContextGesture?) -> Void ) { self.context = context self.subject = subject self.linkContents = linkContents - self.completion = completion + self.theme = theme + self.resolvedData = resolvedData + self.selectedItems = selectedItems + self.linkListItems = linkListItems + self.peerAction = peerAction + self.toggleAllSelection = toggleAllSelection + self.openCreateLink = openCreateLink + self.openLink = openLink + self.openLinkContextAction = openLinkContextAction } - - static func ==(lhs: ChatFolderLinkPreviewScreenComponent, rhs: ChatFolderLinkPreviewScreenComponent) -> Bool { + + static func ==(lhs: ChatFolderLinkPreviewContentComponent, rhs: ChatFolderLinkPreviewContentComponent) -> Bool { if lhs.context !== rhs.context { return false } @@ -53,396 +338,67 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { if lhs.linkContents !== rhs.linkContents { return false } + if lhs.theme !== rhs.theme { + return false + } + if lhs.resolvedData != rhs.resolvedData { + return false + } + if lhs.selectedItems != rhs.selectedItems { + return false + } + if lhs.linkListItems != rhs.linkListItems { + return false + } return true } - - private struct ItemLayout: Equatable { - var containerSize: CGSize - var containerInset: CGFloat - var bottomInset: CGFloat - var topInset: CGFloat - var contentHeight: CGFloat - - init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat, contentHeight: CGFloat) { - self.containerSize = containerSize - self.containerInset = containerInset - self.bottomInset = bottomInset - self.topInset = topInset - self.contentHeight = contentHeight - } - } - - private final class ScrollView: UIScrollView { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - - override func touchesShouldCancel(in view: UIView) -> Bool { - return true - } - } - - final class AnimationHint { - init() { - } - } - - final class View: UIView, UIScrollViewDelegate { - private let dimView: UIView - private let backgroundLayer: SimpleLayer - private let navigationBarContainer: SparseContainerView - private let scrollView: ScrollView - private let scrollContentClippingView: SparseContainerView - private let scrollContentView: UIView - private let bottomBackgroundLayer: SimpleLayer - private let bottomSeparatorLayer: SimpleLayer - + + final class View: UIView { private let topIcon = ComponentView() - - private let title = ComponentView() - private let leftButton = ComponentView() private let descriptionText = ComponentView() - private let actionButton = ComponentView() - private let listHeaderText = ComponentView() private let listHeaderAction = ComponentView() + private let itemContainerView: UIView private var items: [AnyHashable: ComponentView] = [:] - - private var selectedItems = Set() - - private var linkListItems: [ExportedChatFolderLink] = [] - - private let bottomOverscrollLimit: CGFloat - - private var ignoreScrolling: Bool = false - - private var component: ChatFolderLinkPreviewScreenComponent? + + private var component: ChatFolderLinkPreviewContentComponent? private weak var state: EmptyComponentState? - private var environment: ViewControllerComponentContainer.Environment? - private var itemLayout: ItemLayout? - - private var topOffsetDistance: CGFloat? - - private var joinDisposable: Disposable? - - private var inProgress: Bool = false - + private var environment: EnvironmentType? + override init(frame: CGRect) { - self.bottomOverscrollLimit = 200.0 - - self.dimView = UIView() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - self.backgroundLayer.cornerRadius = 10.0 - - self.navigationBarContainer = SparseContainerView() - - self.scrollView = ScrollView() - - self.scrollContentClippingView = SparseContainerView() - self.scrollContentClippingView.clipsToBounds = true - - self.scrollContentView = UIView() - self.itemContainerView = UIView() self.itemContainerView.clipsToBounds = true - self.itemContainerView.layer.cornerRadius = 10.0 - - self.bottomBackgroundLayer = SimpleLayer() - self.bottomSeparatorLayer = SimpleLayer() - + self.itemContainerView.layer.cornerRadius = 26.0 + super.init(frame: frame) - - self.addSubview(self.dimView) - self.layer.addSublayer(self.backgroundLayer) - - self.addSubview(self.navigationBarContainer) - - self.scrollView.delaysContentTouches = false - self.scrollView.canCancelContentTouches = true - self.scrollView.clipsToBounds = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.alwaysBounceVertical = true - self.scrollView.scrollsToTop = false - self.scrollView.delegate = self - self.scrollView.clipsToBounds = true - - self.addSubview(self.scrollContentClippingView) - self.scrollContentClippingView.addSubview(self.scrollView) - - self.scrollView.addSubview(self.scrollContentView) - - self.scrollContentView.addSubview(self.itemContainerView) - - self.layer.addSublayer(self.bottomBackgroundLayer) - self.layer.addSublayer(self.bottomSeparatorLayer) - - self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + + self.addSubview(self.itemContainerView) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - deinit { - self.joinDisposable?.dispose() - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateScrolling(transition: .immediate) - } - } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - guard let itemLayout = self.itemLayout, let topOffsetDistance = self.topOffsetDistance else { - return - } - - if scrollView.contentOffset.y <= -100.0 && velocity.y <= -2.0 { - self.environment?.controller()?.dismiss() - } else { - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - if topOffset > 0.0 { - topOffset = max(0.0, topOffset) - - if topOffset < topOffsetDistance { - targetContentOffset.pointee.y = scrollView.contentOffset.y - scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true) - } - } - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - if !self.backgroundLayer.frame.contains(point) { - return self.dimView - } - - if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { - return result - } - - let result = super.hitTest(point, with: event) - return result - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - guard let environment = self.environment, let controller = environment.controller() else { - return - } - controller.dismiss() - } - } - - private func updateScrolling(transition: ComponentTransition) { - guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { - return - } - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - topOffset = max(0.0, topOffset) - transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) - - let bottomDistance = itemLayout.contentHeight - self.scrollView.bounds.maxY - let bottomAlphaDistance: CGFloat = 30.0 - var bottomAlpha: CGFloat = bottomDistance / bottomAlphaDistance - bottomAlpha = max(0.0, min(1.0, bottomAlpha)) - - let bottomOverlayAlpha: CGFloat = bottomAlpha - transition.setAlpha(layer: self.bottomBackgroundLayer, alpha: bottomOverlayAlpha) - transition.setAlpha(layer: self.bottomSeparatorLayer, alpha: bottomOverlayAlpha) - - transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - - let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25)) - self.topOffsetDistance = topOffsetDistance - var topOffsetFraction = topOffset / topOffsetDistance - topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - - let transitionFactor: CGFloat = 1.0 - topOffsetFraction - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) - } - - func animateIn() { - self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.bottomBackgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.bottomSeparatorLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - } - - func animateOut(completion: @escaping () -> Void) { - if let controller = self.environment?.controller() { - controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut)) - } - - var animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - if self.scrollView.contentOffset.y < 0.0 { - animateOffset += -self.scrollView.contentOffset.y - } - - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.bottomBackgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.bottomSeparatorLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - if let actionButtonView = self.actionButton.view { - actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - } - } - - func update(component: ChatFolderLinkPreviewScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let animationHint = transition.userData(AnimationHint.self) - - var contentTransition = transition - if animationHint != nil { - contentTransition = .immediate - } - - let environment = environment[ViewControllerComponentContainer.Environment.self].value - let themeUpdated = self.environment?.theme !== environment.theme - - let resetScrolling = self.scrollView.bounds.width != availableSize.width - - let sideInset: CGFloat = 16.0 - - if self.component?.linkContents == nil, let linkContents = component.linkContents { - if case let .remove(_, defaultSelectedPeerIds) = component.subject { - for peer in linkContents.peers { - if defaultSelectedPeerIds.contains(peer.id) { - self.selectedItems.insert(peer.id) - } - } - } else { - for peer in linkContents.peers { - self.selectedItems.insert(peer.id) - } - } - } - - if self.component == nil, case let .linkList(_, initialLinks) = component.subject { - self.linkListItems = initialLinks - } - + + func update(component: ChatFolderLinkPreviewContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[EnvironmentType.self].value + self.component = component self.state = state self.environment = environment - - if themeUpdated { - self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor - self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor - self.bottomBackgroundLayer.backgroundColor = environment.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor - self.bottomSeparatorLayer.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor - } - - transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) - - var contentHeight: CGFloat = 0.0 - - let leftButtonSize = self.leftButton.update( - transition: contentTransition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), - action: { [weak self] in - guard let self, let controller = self.environment?.controller() else { - return - } - controller.dismiss() - } - ).minSize(CGSize(width: 44.0, height: 56.0))), - environment: {}, - containerSize: CGSize(width: 120.0, height: 100.0) - ) - let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize) - if let leftButtonView = self.leftButton.view { - if leftButtonView.superview == nil { - self.navigationBarContainer.addSubview(leftButtonView) - } - transition.setFrame(view: leftButtonView, frame: leftButtonFrame) - } - - let titleString: String - var allChatsAdded = false - var canAddChatCount = 0 - if case .linkList = component.subject { - titleString = environment.strings.FolderLinkPreview_TitleShare - } else if let linkContents = component.linkContents { - if case .remove = component.subject { - titleString = environment.strings.FolderLinkPreview_TitleRemove - } else if linkContents.localFilterId != nil { - if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) { - allChatsAdded = true - } - canAddChatCount = linkContents.peers.map(\.id).count - linkContents.alreadyMemberPeerIds.count - - if allChatsAdded { - titleString = environment.strings.FolderLinkPreview_TitleAddFolder - } else { - titleString = environment.strings.FolderLinkPreview_TitleAddChats(Int32(canAddChatCount)) - } - } else { - titleString = environment.strings.FolderLinkPreview_TitleAddFolder - } - } else { - titleString = " " - } - let titleSize = self.title.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) - ) - let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: 18.0), size: titleSize) - if let titleView = self.title.view { - if titleView.superview == nil { - self.navigationBarContainer.addSubview(titleView) - } - contentTransition.setFrame(view: titleView, frame: titleFrame) - } - - contentHeight += 44.0 - contentHeight += 14.0 - - var topBadge: String? - if case .linkList = component.subject { - } else if case .remove = component.subject { - } else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil, canAddChatCount != 0 { - topBadge = "+\(canAddChatCount)" - } - + self.itemContainerView.backgroundColor = component.theme.list.itemBlocksBackgroundColor + + let sideInset: CGFloat = 16.0 + var contentHeight: CGFloat = 58.0 + let topIconSize = self.topIcon.update( - transition: contentTransition, + transition: transition, component: AnyComponent(ChatFolderLinkHeaderComponent( context: component.context, - theme: environment.theme, + theme: component.theme, strings: environment.strings, title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true), - badge: topBadge + badge: component.resolvedData.topBadge )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset, height: 1000.0) @@ -450,62 +406,23 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let topIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - topIconSize.width) * 0.5), y: contentHeight), size: topIconSize) if let topIconView = self.topIcon.view { if topIconView.superview == nil { - self.scrollContentView.addSubview(topIconView) + self.addSubview(topIconView) } - contentTransition.setFrame(view: topIconView, frame: topIconFrame) + transition.setFrame(view: topIconView, frame: topIconFrame) topIconView.isHidden = component.linkContents == nil } - + contentHeight += topIconSize.height contentHeight += 20.0 - - let text: NSAttributedString - if case .linkList = component.subject { - text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextLinkList) - } else if let linkContents = component.linkContents { - if case .remove = component.subject { - text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextRemoveFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor) - } else if allChatsAdded { - text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAllAdded, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor) - } else if linkContents.localFilterId == nil { - text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAddFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor) - } else if let title = linkContents.title { - let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount)) - - let textValue = NSMutableAttributedString(string: environment.strings.FolderLinkPreview_TextAddChatsV2) - textValue.addAttributes([ - .font: Font.regular(15.0), - .foregroundColor: environment.theme.list.freeTextColor - ], range: NSRange(location: 0, length: textValue.length)) - - let folderRange = (textValue.string as NSString).range(of: "{folder}") - if folderRange.location != NSNotFound { - textValue.replaceCharacters(in: folderRange, with: "") - textValue.insert(title.attributedString(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: folderRange.location) - } - - let chatsRange = (textValue.string as NSString).range(of: "{chats}") - if chatsRange.location != NSNotFound { - textValue.replaceCharacters(in: chatsRange, with: "") - textValue.insert(NSAttributedString(string: chatCountString, font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: chatsRange.location) - } - - text = textValue - } else { - text = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor) - } - } else { - text = NSAttributedString(string: " ") - } - + let descriptionTextSize = self.descriptionText.update( - transition: .immediate, + transition: transition, component: AnyComponent(MultilineTextWithEntitiesComponent( context: component.context, animationCache: component.context.animationCache, animationRenderer: component.context.animationRenderer, - placeholderColor: environment.theme.list.freeTextColor.withMultipliedAlpha(0.1), - text: .plain(text), + placeholderColor: component.theme.list.freeTextColor.withMultipliedAlpha(0.1), + text: .plain(component.resolvedData.descriptionText), horizontalAlignment: .center, maximumNumberOfLines: 0 )), @@ -515,24 +432,23 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { let descriptionTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionTextSize.width) * 0.5), y: contentHeight), size: descriptionTextSize) if let descriptionTextView = self.descriptionText.view { if descriptionTextView.superview == nil { - self.scrollContentView.addSubview(descriptionTextView) + self.addSubview(descriptionTextView) } - descriptionTextView.bounds = CGRect(origin: CGPoint(), size: descriptionTextFrame.size) - contentTransition.setPosition(view: descriptionTextView, position: descriptionTextFrame.center) + transition.setFrame(view: descriptionTextView, frame: descriptionTextFrame) } - + contentHeight += descriptionTextFrame.height contentHeight += 39.0 - + var singleItemHeight: CGFloat = 0.0 - var itemsHeight: CGFloat = 0.0 var validIds: [AnyHashable] = [] - if case let .linkList(folderId, _) = component.subject { + + if case .linkList = component.subject { do { let id = AnyHashable("action") validIds.append(id) - + let item: ComponentView var itemTransition = transition if let current = self.items[id] { @@ -542,41 +458,41 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { item = ComponentView() self.items[id] = item } - + let itemSize = item.update( transition: itemTransition, component: AnyComponent(ActionListItemComponent( - theme: environment.theme, + theme: component.theme, sideInset: 0.0, iconName: "Contact List/LinkActionIcon", title: environment.strings.InviteLink_Create, - hasNext: !self.linkListItems.isEmpty, - action: { [weak self] in - self?.openCreateLink() + hasNext: !component.linkListItems.isEmpty, + action: { [weak component] in + component?.openCreateLink() } )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - + if let itemView = item.view { if itemView.superview == nil { self.itemContainerView.addSubview(itemView) } itemTransition.setFrame(view: itemView, frame: itemFrame) } - + itemsHeight += itemSize.height singleItemHeight = itemSize.height } - - for i in 0 ..< self.linkListItems.count { - let link = self.linkListItems[i] - + + for i in 0 ..< component.linkListItems.count { + let link = component.linkListItems[i] + let id = AnyHashable(link.link) validIds.append(id) - + let item: ComponentView var itemTransition = transition if let current = self.items[id] { @@ -586,94 +502,24 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { item = ComponentView() self.items[id] = item } - - let subtitle: String = environment.strings.ChatListFilter_LinkLabelChatCount(Int32(link.peerIds.count)) - + + let subtitle = environment.strings.ChatListFilter_LinkLabelChatCount(Int32(link.peerIds.count)) let itemComponent = LinkListItemComponent( - theme: environment.theme, + theme: component.theme, sideInset: 0.0, title: link.title.isEmpty ? link.link : link.title, link: link, label: subtitle, selectionState: .none, - hasNext: i != self.linkListItems.count - 1, - action: { [weak self] link in - guard let self else { - return - } - self.openLink(link: link) + hasNext: i != component.linkListItems.count - 1, + action: { [weak component] link in + component?.openLink(link) }, - contextAction: { [weak self] link, sourceView, gesture in - guard let self, let component = self.component, let environment = self.environment else { - return - } - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - - var itemList: [ContextMenuItem] = [] - - itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.default) - - UIPasteboard.general.string = link.link - - if let self, let component = self.component, let controller = self.environment?.controller() { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - } - }))) - - itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let self, let component = self.component, let controller = self.environment?.controller() { - controller.present(QrCodeScreen(context: component.context, updatedPresentationData: nil, subject: .chatFolder(slug: link.slug)), in: .window(.root)) - } - }))) - - itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - if let self, let component = self.component { - self.linkListItems.removeAll(where: { $0.link == link.link }) - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - - let context = component.context - let _ = (context.engine.peers.editChatFolderLink(filterId: folderId, link: link, title: nil, peerIds: nil, revoke: true) - |> deliverOnMainQueue).start(completed: { - let _ = (context.engine.peers.deleteChatFolderLink(filterId: folderId, link: link) - |> deliverOnMainQueue).start(completed: { - }) - }) - } - }))) - - let items = ContextController.Items(content: .list(itemList)) - - let controller = makeContextController( - presentationData: presentationData, - source: .extracted(LinkListContextExtractedContentSource(contentView: sourceView)), - items: .single(items), - recognizer: nil, - gesture: gesture - ) - - environment.controller()?.forEachController({ controller in - if let controller = controller as? UndoOverlayController { - controller.dismiss() - } - return true - }) - environment.controller()?.presentInGlobalOverlay(controller) + contextAction: { [weak component] link, sourceView, gesture in + component?.openLinkContextAction(link, sourceView, gesture) } ) - + let itemSize = item.update( transition: itemTransition, component: AnyComponent(itemComponent), @@ -681,24 +527,24 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - + if let itemView = item.view { if itemView.superview == nil { self.itemContainerView.addSubview(itemView) } itemTransition.setFrame(view: itemView, frame: itemFrame) } - + itemsHeight += itemSize.height singleItemHeight = itemSize.height } } else if let linkContents = component.linkContents { for i in 0 ..< linkContents.peers.count { let peer = linkContents.peers[i] - + let id = AnyHashable(peer.id) validIds.append(id) - + let item: ComponentView var itemTransition = transition if let current = self.items[id] { @@ -708,7 +554,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { item = ComponentView() self.items[id] = item } - + var subtitle: String? if case let .channel(channel) = peer, case .broadcast = channel.info { if linkContents.alreadyMemberPeerIds.contains(peer.id) { @@ -723,67 +569,40 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { subtitle = environment.strings.FolderLinkPreview_LabelPeerMembers(Int32(memberCount)) } } - + let itemSize = item.update( transition: itemTransition, component: AnyComponent(PeerListItemComponent( context: component.context, - theme: environment.theme, + theme: component.theme, strings: environment.strings, sideInset: 0.0, title: peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), peer: peer, subtitle: subtitle, - selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)), + selectionState: .editing(isSelected: component.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)), hasNext: i != linkContents.peers.count - 1, - action: { [weak self] peer in - guard let self, let component = self.component, let linkContents = component.linkContents, let controller = self.environment?.controller() else { - return - } - - if case .remove = component.subject { - if self.selectedItems.contains(peer.id) { - self.selectedItems.remove(peer.id) - } else { - self.selectedItems.insert(peer.id) - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } else if linkContents.alreadyMemberPeerIds.contains(peer.id) { - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let text: String - if case let .channel(channel) = peer, case .broadcast = channel.info { - text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberChannel - } else { - text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberGroup - } - controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: [peer], title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in true }), in: .current) - } else { - if self.selectedItems.contains(peer.id) { - self.selectedItems.remove(peer.id) - } else { - self.selectedItems.insert(peer.id) - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } + action: { [weak component] peer in + component?.peerAction(peer) } )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) ) let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - + if let itemView = item.view { if itemView.superview == nil { self.itemContainerView.addSubview(itemView) } itemTransition.setFrame(view: itemView, frame: itemFrame) } - + itemsHeight += itemSize.height singleItemHeight = itemSize.height } } - + var removeIds: [AnyHashable] = [] for (id, item) in self.items { if !validIds.contains(id) { @@ -794,71 +613,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { for id in removeIds { self.items.removeValue(forKey: id) } - - let listHeaderTitle: String - if case .linkList = component.subject { - listHeaderTitle = environment.strings.FolderLinkPreview_LinkSectionHeader - } else if let linkContents = component.linkContents { - if case .remove = component.subject { - listHeaderTitle = environment.strings.FolderLinkPreview_RemoveSectionSelectedHeader(Int32(linkContents.peers.count)) - } else if allChatsAdded { - listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionHeader(Int32(linkContents.peers.count)) - } else { - listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionJoinHeader(Int32(linkContents.peers.count)) - } - } else { - listHeaderTitle = " " - } - - var listHeaderActionItems: [AnimatedCounterComponent.Item] = [] - - let dynamicIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{dynamic}") - let staticIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{static}") - var headerActionItemIndices: [Int: Int] = [:] - if let dynamicIndex, let staticIndex { - if dynamicIndex.lowerBound < staticIndex.lowerBound { - headerActionItemIndices[0] = 0 - headerActionItemIndices[1] = 1 - } else { - headerActionItemIndices[0] = 1 - headerActionItemIndices[1] = 0 - } - } else if dynamicIndex != nil { - headerActionItemIndices[0] = 0 - } else if staticIndex != nil { - headerActionItemIndices[1] = 0 - } - - let dynamicItem: AnimatedCounterComponent.Item - let staticItem: AnimatedCounterComponent.Item - - if self.selectedItems.count == self.items.count { - dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartDeselect, numericValue: 0) - staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartDeselect, numericValue: 1) - } else { - dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartSelect, numericValue: 1) - staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartSelect, numericValue: 1) - } - - if let dynamicIndex = headerActionItemIndices[0], let staticIndex = headerActionItemIndices[1] { - if dynamicIndex < staticIndex { - listHeaderActionItems = [dynamicItem, staticItem] - } else { - listHeaderActionItems = [staticItem, dynamicItem] - } - } else if headerActionItemIndices[0] != nil { - listHeaderActionItems = [dynamicItem] - } else if headerActionItemIndices[1] != nil { - listHeaderActionItems = [staticItem] - } - - let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor) - + + let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: component.theme.list.freeTextColor) let listHeaderTextSize = self.listHeaderText.update( - transition: .immediate, + transition: transition, component: AnyComponent(MultilineTextComponent( text: .markdown( - text: listHeaderTitle, + text: component.resolvedData.listHeaderTitle, attributes: MarkdownAttributes( body: listHeaderBody, bold: listHeaderBody, @@ -872,40 +633,25 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { ) if let listHeaderTextView = self.listHeaderText.view { if listHeaderTextView.superview == nil { - listHeaderTextView.layer.anchorPoint = CGPoint() - self.scrollContentView.addSubview(listHeaderTextView) + self.addSubview(listHeaderTextView) } let listHeaderTextFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: listHeaderTextSize) - contentTransition.setPosition(view: listHeaderTextView, position: listHeaderTextFrame.origin) - listHeaderTextView.bounds = CGRect(origin: CGPoint(), size: listHeaderTextFrame.size) + transition.setFrame(view: listHeaderTextView, frame: listHeaderTextFrame) listHeaderTextView.isHidden = component.linkContents == nil } - + let listHeaderActionSize = self.listHeaderAction.update( transition: transition, component: AnyComponent(PlainButtonComponent( content: AnyComponent(AnimatedCounterComponent( font: Font.regular(13.0), - color: environment.theme.list.itemAccentColor, + color: component.theme.list.itemAccentColor, alignment: .right, - items: listHeaderActionItems + items: component.resolvedData.listHeaderActionItems )), effectAlignment: .right, - action: { [weak self] in - guard let self, let component = self.component, let linkContents = component.linkContents else { - return - } - if self.selectedItems.count != linkContents.peers.count { - for peer in linkContents.peers { - self.selectedItems.insert(peer.id) - } - } else { - self.selectedItems.removeAll() - for peerId in linkContents.alreadyMemberPeerIds { - self.selectedItems.insert(peerId) - } - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + action: { [weak component] in + component?.toggleAllSelection() } )), environment: {}, @@ -913,415 +659,544 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { ) if let listHeaderActionView = self.listHeaderAction.view { if listHeaderActionView.superview == nil { - listHeaderActionView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0) - self.scrollContentView.addSubview(listHeaderActionView) + self.addSubview(listHeaderActionView) } let listHeaderActionFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - 15.0 - listHeaderActionSize.width, y: contentHeight), size: listHeaderActionSize) - contentTransition.setFrame(view: listHeaderActionView, frame: listHeaderActionFrame) - - if let linkContents = component.linkContents, !allChatsAdded, linkContents.peers.count > 1 { - listHeaderActionView.isHidden = false - } else { - listHeaderActionView.isHidden = true - } + transition.setFrame(view: listHeaderActionView, frame: listHeaderActionFrame) + listHeaderActionView.isHidden = !component.resolvedData.showsListHeaderAction } - + contentHeight += listHeaderTextSize.height contentHeight += 6.0 - - contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight))) - - var initialContentHeight = contentHeight - initialContentHeight += min(itemsHeight, floor(singleItemHeight * 3.5)) - + + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight))) + contentHeight += itemsHeight - contentHeight += 24.0 - initialContentHeight += 24.0 - - let actionButtonTitle: String - var actionButtonBadge: Int = 0 - if case .remove = component.subject { - actionButtonBadge = self.selectedItems.count - if self.selectedItems.isEmpty { - actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolder - } else { - actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolderAndChats - } - } else if allChatsAdded { - actionButtonBadge = 0 - actionButtonTitle = environment.strings.Common_OK - } else if let linkContents = component.linkContents { - actionButtonBadge = max(0, self.selectedItems.count - (linkContents.peers.count - canAddChatCount)) - if linkContents.localFilterId != nil { - if actionButtonBadge == 0 { - actionButtonTitle = environment.strings.FolderLinkPreview_ButtonDoNotJoinChats - } else { - actionButtonTitle = environment.strings.FolderLinkPreview_ButtonJoinChats - } - } else { - actionButtonTitle = environment.strings.FolderLinkPreview_ButtonAddFolder - } - } else { - actionButtonTitle = " " + contentHeight += component.resolvedData.isLinkList ? 54.0 : 24.0 + + if itemsHeight == 0.0 && singleItemHeight == 0.0 { + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: 0.0))) } - let actionButtonSize = self.actionButton.update( - transition: transition, - component: AnyComponent(ButtonComponent( - background: ButtonComponent.Background( - color: environment.theme.list.itemCheckColors.fillColor, - foreground: environment.theme.list.itemCheckColors.foregroundColor, - pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) - ), - content: AnyComponentWithIdentity( - id: actionButtonTitle, - component: AnyComponent(ButtonTextContentComponent( - text: actionButtonTitle, - badge: actionButtonBadge, - textColor: environment.theme.list.itemCheckColors.foregroundColor, - badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, - badgeForeground: environment.theme.list.itemCheckColors.fillColor - )) - ), - isEnabled: !self.selectedItems.isEmpty || component.linkContents?.localFilterId != nil, - displaysProgress: self.inProgress, - action: { [weak self] in - guard let self, let component = self.component, let linkContents = component.linkContents, let controller = self.environment?.controller() else { - return + contentHeight += 52.0 + 3.0 + environment.safeInsets.bottom + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ChatFolderLinkPreviewScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: ChatFolderLinkPreviewScreen.Subject + let linkContents: ChatFolderLinkContents? + let completion: (() -> Void)? + + init( + context: AccountContext, + subject: ChatFolderLinkPreviewScreen.Subject, + linkContents: ChatFolderLinkContents?, + completion: (() -> Void)? + ) { + self.context = context + self.subject = subject + self.linkContents = linkContents + self.completion = completion + } + + static func ==(lhs: ChatFolderLinkPreviewScreenComponent, rhs: ChatFolderLinkPreviewScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + if lhs.linkContents !== rhs.linkContents { + return false + } + return true + } + + final class State: ComponentState { + var selectedItems = Set() + var didInitializeSelection = false + var linkListItems: [ExportedChatFolderLink] = [] + var didInitializeLinkList = false + var inProgress = false + var joinDisposable: Disposable? + + deinit { + self.joinDisposable?.dispose() + } + } + + func makeState() -> State { + return State() + } + + final class View: UIView { + private let sheet = ComponentView<(EnvironmentType, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var isDismissing = false + + private var component: ChatFolderLinkPreviewScreenComponent? + private weak var state: State? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func dismiss(controller: @escaping () -> ViewController?, animated: Bool) { + guard !self.isDismissing else { + return + } + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? ChatFolderLinkPreviewScreen { + controller.completePendingDismiss() + controller.dismiss(animated: false) + } else { + controller()?.dismiss(animated: false) + } + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() + } + } + + private func ensureInitializedState(component: ChatFolderLinkPreviewScreenComponent, state: State) { + if !state.didInitializeSelection, let linkContents = component.linkContents { + state.didInitializeSelection = true + if case let .remove(_, defaultSelectedPeerIds) = component.subject { + for peer in linkContents.peers { + if defaultSelectedPeerIds.contains(peer.id) { + state.selectedItems.insert(peer.id) } - - if case let .remove(folderId, _) = component.subject { - self.inProgress = true - self.state?.updated(transition: .immediate) - - component.completion?() - - let disposable = DisposableSet() - disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems))) - disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId])) - - let folderTitle: ChatFolderTitle - if let title = linkContents.title { - folderTitle = title - } else { - folderTitle = ChatFolderTitle(text: "", entities: [], enableAnimations: true) - } - - let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - - var additionalText: String? - if !self.selectedItems.isEmpty { - additionalText = presentationData.strings.FolderLinkPreview_ToastLeftChatsText(Int32(self.selectedItems.count)) - } - - var chatListController: ChatListController? - if let navigationController = controller.navigationController as? NavigationController { - for viewController in navigationController.viewControllers.reversed() { - if viewController is ChatFolderLinkPreviewScreen { - continue - } - - if let rootController = viewController as? TabBarController { - for c in rootController.controllers { - if let c = c as? ChatListController { - chatListController = c - break - } - } - } else if let c = viewController as? ChatListController { - chatListController = c - break - } - + } + } else { + for peer in linkContents.peers { + state.selectedItems.insert(peer.id) + } + } + } + + if !state.didInitializeLinkList, case let .linkList(_, initialLinks) = component.subject { + state.didInitializeLinkList = true + state.linkListItems = initialLinks + } + } + + private func toggleAllSelection() { + guard let component = self.component, let state = self.state, let linkContents = component.linkContents else { + return + } + if state.selectedItems.count != linkContents.peers.count { + for peer in linkContents.peers { + state.selectedItems.insert(peer.id) + } + } else { + state.selectedItems.removeAll() + for peerId in linkContents.alreadyMemberPeerIds { + state.selectedItems.insert(peerId) + } + } + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + } + + private func peerAction(peer: EnginePeer) { + guard let component = self.component, let state = self.state, let linkContents = component.linkContents, let controller = self.environment?.controller() else { + return + } + + if case .remove = component.subject { + if state.selectedItems.contains(peer.id) { + state.selectedItems.remove(peer.id) + } else { + state.selectedItems.insert(peer.id) + } + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + } else if linkContents.alreadyMemberPeerIds.contains(peer.id) { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let text: String + if case let .channel(channel) = peer, case .broadcast = channel.info { + text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberChannel + } else { + text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberGroup + } + controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: [peer], title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in true }), in: .current) + } else { + if state.selectedItems.contains(peer.id) { + state.selectedItems.remove(peer.id) + } else { + state.selectedItems.insert(peer.id) + } + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + } + } + + private func presentLinkContextAction(link: ExportedChatFolderLink, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?) { + guard let component = self.component, let environment = self.environment else { + return + } + guard case let .linkList(folderId, _) = component.subject else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + var itemList: [ContextMenuItem] = [] + + itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + UIPasteboard.general.string = link.link + + if let self, let component = self.component, let controller = self.environment?.controller() { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.InviteLink_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in false }), in: .window(.root)) + } + }))) + + itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + if let self, let component = self.component, let controller = self.environment?.controller() { + controller.present(QrCodeScreen(context: component.context, updatedPresentationData: nil, subject: .chatFolder(slug: link.slug)), in: .window(.root)) + } + }))) + + itemList.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + guard let self, let component = self.component, let state = self.state else { + return + } + + state.linkListItems.removeAll(where: { $0.link == link.link }) + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + + let context = component.context + let _ = (context.engine.peers.editChatFolderLink(filterId: folderId, link: link, title: nil, peerIds: nil, revoke: true) + |> deliverOnMainQueue).start(completed: { + let _ = (context.engine.peers.deleteChatFolderLink(filterId: folderId, link: link) + |> deliverOnMainQueue).start(completed: { + }) + }) + }))) + + let items = ContextController.Items(content: .list(itemList)) + let controller = makeContextController( + presentationData: presentationData, + source: .extracted(LinkListContextExtractedContentSource(contentView: sourceView)), + items: .single(items), + recognizer: nil, + gesture: gesture + ) + + environment.controller()?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + return true + }) + environment.controller()?.presentInGlobalOverlay(controller) + } + + private func performMainAction() { + guard let component = self.component, let state = self.state, let environment = self.environment, let controller = environment.controller() else { + return + } + guard let linkContents = component.linkContents else { + controller.dismiss() + return + } + + let resolvedData = chatFolderLinkPreviewResolvedData( + component: component, + theme: environment.theme.withModalBlocksBackground(), + strings: environment.strings, + selectedItems: state.selectedItems + ) + + if case let .remove(folderId, _) = component.subject { + state.inProgress = true + state.updated(transition: .immediate) + + component.completion?() + + let disposable = DisposableSet() + disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(state.selectedItems))) + disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId])) + + let folderTitle: ChatFolderTitle + if let title = linkContents.title { + folderTitle = title + } else { + folderTitle = ChatFolderTitle(text: "", entities: [], enableAnimations: true) + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + let additionalText: String? = state.selectedItems.isEmpty ? nil : presentationData.strings.FolderLinkPreview_ToastLeftChatsText(Int32(state.selectedItems.count)) + + var chatListController: ChatListController? + if let navigationController = controller.navigationController as? NavigationController { + for viewController in navigationController.viewControllers.reversed() { + if viewController is ChatFolderLinkPreviewScreen { + continue + } + + if let rootController = viewController as? TabBarController { + for controller in rootController.controllers { + if let controller = controller as? ChatListController { + chatListController = controller break } } - - let undoText = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastLeftTitleV2) - let folderRange = (undoText.string as NSString).range(of: "{folder}") - if folderRange.location != NSNotFound { - undoText.replaceCharacters(in: folderRange, with: "") - undoText.insert(folderTitle.rawAttributedString, at: folderRange.location) - } - - let context = component.context - let selectedItems = self.selectedItems - let undoOverlayController = UndoOverlayController( - presentationData: presentationData, - content: .removedChat(context: component.context, title: undoText, text: additionalText), - elevatedLayout: false, - action: { value in - if case .commit = value { - let _ = (context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(selectedItems)) - |> deliverOnMainQueue).start(completed: { - Queue.mainQueue().after(1.0, { - disposable.dispose() - }) - }) - return true - } else if case .undo = value { - disposable.dispose() - return true - } - return false - } - ) - - if let chatListController, chatListController.view.window != nil { - chatListController.present(undoOverlayController, in: .current) - } else { - controller.present(undoOverlayController, in: .window(.root)) - } - - controller.dismiss() - } else if allChatsAdded { - controller.dismiss() - } else if let _ = component.linkContents { - if self.joinDisposable == nil, !self.selectedItems.isEmpty { - let joinSignal: Signal - switch component.subject { - case .linkList, .remove: - return - case let .slug(slug): - joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(self.selectedItems)) - |> map(Optional.init) - case let .updates(updates): - var result: JoinChatFolderResult? - if let localFilterId = updates.chatFolderLinkContents.localFilterId, let title = updates.chatFolderLinkContents.title { - result = JoinChatFolderResult(folderId: localFilterId, title: title, newChatCount: self.selectedItems.count) - } - joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(self.selectedItems)) - |> map { _ -> JoinChatFolderResult? in - } - |> then(Signal.single(result)) - } - - self.inProgress = true - self.state?.updated(transition: .immediate) - - self.joinDisposable = (joinSignal - |> deliverOnMainQueue).start(next: { [weak self] result in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - if let result, let navigationController = controller.navigationController as? NavigationController { - var chatListController: ChatListController? - for viewController in navigationController.viewControllers { - if let rootController = viewController as? TabBarController { - for c in rootController.controllers { - if let c = c as? ChatListController { - chatListController = c - break - } - } - } else if let c = viewController as? ChatListController { - chatListController = c - break - } - } - - if let chatListController { - navigationController.popToRoot(animated: true) - let context = component.context - chatListController.navigateToFolder(folderId: result.folderId, completion: { [weak context, weak chatListController] in - guard let context, let chatListController else { - return - } - - let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) - - var isUpdates = false - if case .updates = component.subject { - isUpdates = true - } else { - if component.linkContents?.localFilterId != nil { - isUpdates = true - } - } - - if isUpdates { - let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2) - let folderRange = (titleString.string as NSString).range(of: "{folder}") - if folderRange.location != NSNotFound { - titleString.replaceCharacters(in: folderRange, with: "") - titleString.insert(result.title.rawAttributedString, at: folderRange.location) - } - - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) - } else if result.newChatCount != 0 { - let animationBackgroundColor: UIColor - if presentationData.theme.overallDarkAppearance { - animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor - } else { - animationBackgroundColor = UIColor(rgb: 0x474747) - } - - let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2) - let folderRange = (titleString.string as NSString).range(of: "{folder}") - if folderRange.location != NSNotFound { - titleString.replaceCharacters(in: folderRange, with: "") - titleString.insert(result.title.rawAttributedString, at: folderRange.location) - } - - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) - } else { - let animationBackgroundColor: UIColor - if presentationData.theme.overallDarkAppearance { - animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor - } else { - animationBackgroundColor = UIColor(rgb: 0x474747) - } - - let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitleV2) - let folderRange = (titleString.string as NSString).range(of: "{folder}") - if folderRange.location != NSNotFound { - titleString.replaceCharacters(in: folderRange, with: "") - titleString.insert(result.title.rawAttributedString, at: folderRange.location) - } - - chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: ""), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) - } - }) - } - } - - controller.dismiss() - }, error: { [weak self] error in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - let navigationController = controller.navigationController as? NavigationController - - switch error { - case .generic: - controller.dismiss() - case let .dialogFilterLimitExceeded(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .folders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in - guard let navigationController else { - return true - } - navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .folders)) - return true - }) - controller.push(limitController) - controller.dismiss() - case let .sharedFolderLimitExceeded(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in - guard let navigationController else { - return true - } - navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .membershipInSharedFolders)) - return true - }) - controller.push(limitController) - controller.dismiss() - case let .tooManyChannels(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .chatsPerFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in - guard let navigationController else { - return true - } - navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .chatsPerFolder)) - return true - }) - controller.push(limitController) - controller.dismiss() - case let .tooManyChannelsInAccount(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .channels, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in - guard let navigationController else { - return true - } - navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .groupsAndChannels)) - return true - }) - controller.push(limitController) - controller.dismiss() - } + } else if let controller = viewController as? ChatListController { + chatListController = controller + } + + break + } + } + + let undoText = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastLeftTitleV2) + let folderRange = (undoText.string as NSString).range(of: "{folder}") + if folderRange.location != NSNotFound { + undoText.replaceCharacters(in: folderRange, with: "") + undoText.insert(folderTitle.rawAttributedString, at: folderRange.location) + } + + let context = component.context + let selectedItems = state.selectedItems + let undoOverlayController = UndoOverlayController( + presentationData: presentationData, + content: .removedChat(context: component.context, title: undoText, text: additionalText), + elevatedLayout: false, + action: { value in + if case .commit = value { + let _ = (context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(selectedItems)) + |> deliverOnMainQueue).start(completed: { + Queue.mainQueue().after(1.0, { + disposable.dispose() }) - } else { - controller.dismiss() + }) + return true + } else if case .undo = value { + disposable.dispose() + return true + } + return false + } + ) + + if let chatListController, chatListController.view.window != nil { + chatListController.present(undoOverlayController, in: .current) + } else { + controller.present(undoOverlayController, in: .window(.root)) + } + + controller.dismiss() + return + } + + if resolvedData.allChatsAdded { + controller.dismiss() + return + } + + guard state.joinDisposable == nil, !state.selectedItems.isEmpty else { + controller.dismiss() + return + } + + let joinSignal: Signal + switch component.subject { + case .linkList, .remove: + return + case let .slug(slug): + joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(state.selectedItems)) + |> map(Optional.init) + case let .updates(updates): + var result: JoinChatFolderResult? + if let localFilterId = updates.chatFolderLinkContents.localFilterId, let title = updates.chatFolderLinkContents.title { + result = JoinChatFolderResult(folderId: localFilterId, title: title, newChatCount: state.selectedItems.count) + } + joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(state.selectedItems)) + |> map { _ -> JoinChatFolderResult? in + } + |> then(Signal.single(result)) + } + + state.inProgress = true + state.updated(transition: .immediate) + + state.joinDisposable = (joinSignal + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + if let result, let navigationController = controller.navigationController as? NavigationController { + var chatListController: ChatListController? + for viewController in navigationController.viewControllers { + if let rootController = viewController as? TabBarController { + for controller in rootController.controllers { + if let controller = controller as? ChatListController { + chatListController = controller + break + } } + } else if let controller = viewController as? ChatListController { + chatListController = controller + break } } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) - ) - - var bottomPanelHeight: CGFloat = 0.0 - - if case .linkList = component.subject { - bottomPanelHeight += 30.0 - } else { - bottomPanelHeight += 14.0 + environment.safeInsets.bottom + actionButtonSize.height - let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) - if let actionButtonView = self.actionButton.view { - if actionButtonView.superview == nil { - self.addSubview(actionButtonView) + + if let chatListController { + navigationController.popToRoot(animated: true) + let context = component.context + chatListController.navigateToFolder(folderId: result.folderId, completion: { [weak context, weak chatListController] in + guard let context, let chatListController else { + return + } + + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + + var isUpdates = false + if case .updates = component.subject { + isUpdates = true + } else if component.linkContents?.localFilterId != nil { + isUpdates = true + } + + if isUpdates { + let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2) + let folderRange = (titleString.string as NSString).range(of: "{folder}") + if folderRange.location != NSNotFound { + titleString.replaceCharacters(in: folderRange, with: "") + titleString.insert(result.title.rawAttributedString, at: folderRange.location) + } + + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + } else if result.newChatCount != 0 { + let animationBackgroundColor: UIColor + if presentationData.theme.overallDarkAppearance { + animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor + } else { + animationBackgroundColor = UIColor(rgb: 0x474747) + } + + let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2) + let folderRange = (titleString.string as NSString).range(of: "{folder}") + if folderRange.location != NSNotFound { + titleString.replaceCharacters(in: folderRange, with: "") + titleString.insert(result.title.rawAttributedString, at: folderRange.location) + } + + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + } else { + let animationBackgroundColor: UIColor + if presentationData.theme.overallDarkAppearance { + animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor + } else { + animationBackgroundColor = UIColor(rgb: 0x474747) + } + + let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitleV2) + let folderRange = (titleString.string as NSString).range(of: "{folder}") + if folderRange.location != NSNotFound { + titleString.replaceCharacters(in: folderRange, with: "") + titleString.insert(result.title.rawAttributedString, at: folderRange.location) + } + + chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: ""), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) + } + }) } - transition.setFrame(view: actionButtonView, frame: actionButtonFrame) } - - transition.setFrame(layer: self.bottomBackgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0), size: CGSize(width: availableSize.width, height: bottomPanelHeight))) - transition.setFrame(layer: self.bottomSeparatorLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight - 8.0 - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel))) - } - - if let controller = environment.controller() { - let subLayout = ContainerViewLayout( - size: availableSize, - metrics: environment.metrics, - deviceMetrics: environment.deviceMetrics, - intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset), - safeInsets: UIEdgeInsets(), - additionalInsets: UIEdgeInsets(), - statusBarHeight: nil, - inputHeight: nil, - inputHeightIsInteractivellyChanging: false, - inVoiceOver: false - ) - controller.presentationContext.containerLayoutUpdated(subLayout, transition: transition.containedViewLayoutTransition) - } - - contentHeight += bottomPanelHeight - initialContentHeight += bottomPanelHeight - - let containerInset: CGFloat = environment.statusBarHeight + 10.0 - let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) - - let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) - - self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset, contentHeight: scrollContentHeight) - - transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) - - transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) - - let scrollClippingFrame: CGRect - if case .linkList = component.subject { - scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - (containerInset + 56.0) + 1000.0)) - } else { - scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset + 56.0), size: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - bottomPanelHeight - 8.0 - (containerInset + 56.0))) - } - transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) - transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) - - self.ignoreScrolling = true - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) - let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) - if contentSize != self.scrollView.contentSize { - self.scrollView.contentSize = contentSize - } - if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) - } - self.ignoreScrolling = false - self.updateScrolling(transition: transition) - - return availableSize + + controller.dismiss() + }, error: { [weak self] error in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + let navigationController = controller.navigationController as? NavigationController + + switch error { + case .generic: + controller.dismiss() + case let .dialogFilterLimitExceeded(limit, _): + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .folders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + guard let navigationController else { + return true + } + navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .folders)) + return true + }) + controller.push(limitController) + controller.dismiss() + case let .sharedFolderLimitExceeded(limit, _): + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + guard let navigationController else { + return true + } + navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .membershipInSharedFolders)) + return true + }) + controller.push(limitController) + controller.dismiss() + case let .tooManyChannels(limit, _): + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .chatsPerFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + guard let navigationController else { + return true + } + navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .chatsPerFolder)) + return true + }) + controller.push(limitController) + controller.dismiss() + case let .tooManyChannelsInAccount(limit, _): + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .channels, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + guard let navigationController else { + return true + } + navigationController.pushViewController(PremiumIntroScreen(context: component.context, source: .groupsAndChannels)) + return true + }) + controller.push(limitController) + controller.dismiss() + } + }) } - + private func openLink(link: ExportedChatFolderLink) { guard let component = self.component else { return @@ -1329,7 +1204,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard case let .linkList(folderId, _) = component.subject else { return } - + let _ = (component.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { [weak self] filters in guard let self, let component = self.component else { @@ -1341,7 +1216,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard case let .filter(_, title, _, data) = filter else { return } - + let peerIds = data.includePeers.peers let _ = (component.context.engine.data.get( EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) @@ -1350,7 +1225,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard let self, let component = self.component, let controller = self.environment?.controller() else { return } - + let peers = peers.compactMap({ peer -> EnginePeer? in guard let peer else { return nil @@ -1360,16 +1235,17 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { } return peer }) - + let navigationController = controller.navigationController - controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in }, presentController: { [weak navigationController] c in - (navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root)) + controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in + }, presentController: { [weak navigationController] controller in + (navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root)) })) controller.dismiss() }) }) } - + private func openCreateLink() { guard let component = self.component else { return @@ -1377,7 +1253,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard case let .linkList(folderId, _) = component.subject else { return } - + let _ = (component.context.engine.peers.currentChatListFilters() |> deliverOnMainQueue).start(next: { [weak self] filters in guard let self, let component = self.component else { @@ -1389,7 +1265,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard case let .filter(_, title, _, data) = filter else { return } - + let peerIds = data.includePeers.peers let _ = (component.context.engine.data.get( EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) @@ -1398,7 +1274,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { guard let self, let component = self.component, let controller = self.environment?.controller() else { return } - + let peers = peers.compactMap({ peer -> EnginePeer? in guard let peer else { return nil @@ -1408,78 +1284,73 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { } return peer }) + if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) { let navigationController = controller.navigationController - controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: nil, linkUpdated: { _ in }, presentController: { [weak navigationController] c in - (navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root)) + controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: nil, linkUpdated: { _ in + }, presentController: { [weak navigationController] controller in + (navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root)) })) } else { var enabledPeerIds: [EnginePeer.Id] = [] - for peer in peers { - if canShareLinkToPeer(peer: peer) { - enabledPeerIds.append(peer.id) - } + for peer in peers where canShareLinkToPeer(peer: peer) { + enabledPeerIds.append(peer.id) } - + let _ = (component.context.engine.peers.exportChatFolder(filterId: folderId, title: "", peerIds: enabledPeerIds) |> deliverOnMainQueue).start(next: { [weak self] link in - guard let self, let component = self.component, let controller = self.environment?.controller() else { + guard let self, let component = self.component, let state = self.state, let controller = self.environment?.controller() else { return } - - self.linkListItems.insert(link, at: 0) - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - + + state.linkListItems.insert(link, at: 0) + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + let navigationController = controller.navigationController controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { [weak self] updatedLink in - guard let self else { + guard let self, let state = self.state else { return } - if let index = self.linkListItems.firstIndex(where: { $0.link == link.link }) { + if let index = state.linkListItems.firstIndex(where: { $0.link == link.link }) { if let updatedLink { - self.linkListItems[index] = updatedLink + state.linkListItems[index] = updatedLink } else { - self.linkListItems.remove(at: index) - } - } else { - if let updatedLink { - self.linkListItems.insert(updatedLink, at: 0) + state.linkListItems.remove(at: index) } + } else if let updatedLink { + state.linkListItems.insert(updatedLink, at: 0) } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - }, presentController: { [weak navigationController] c in - (navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root)) + state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + }, presentController: { [weak navigationController] controller in + (navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root)) })) - + controller.dismiss() }, error: { [weak self] error in guard let self, let component = self.component, let controller = self.environment?.controller() else { return } - + let context = component.context let navigationController = controller.navigationController as? NavigationController - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - + let text: String switch error { case .generic: text = presentationData.strings.ChatListFilter_CreateLinkUnknownError case let .sharedFolderLimitExceeded(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in guard let navigationController else { return true } navigationController.pushViewController(PremiumIntroScreen(context: context, source: .membershipInSharedFolders)) return true }) - controller.push(limitController) - return case let .limitExceeded(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .linksPerSharedFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .linksPerSharedFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in guard let navigationController else { return true } @@ -1487,10 +1358,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { return true }) controller.push(limitController) - return case let .tooManyChannels(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .chatsPerFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .chatsPerFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in guard let navigationController else { return true } @@ -1499,10 +1369,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { }) controller.push(limitController) controller.dismiss() - return case let .tooManyChannelsInAccount(limit, _): - let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .channels, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in + let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .channels, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in guard let navigationController else { return true } @@ -1511,24 +1380,160 @@ private final class ChatFolderLinkPreviewScreenComponent: Component { }) controller.push(limitController) controller.dismiss() - return case .someUserTooManyChannels: text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit } + controller.present(textAlertController(context: component.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }) } }) }) } + + func update(component: ChatFolderLinkPreviewScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + self.ensureInitializedState(component: component, state: state) + + self.component = component + self.state = state + + let environmentValue = environment[EnvironmentType.self].value + self.environment = environmentValue + let controller = environmentValue.controller + let theme = environmentValue.theme.withModalBlocksBackground() + + let dismiss: (Bool) -> Void = { [weak self] animated in + self?.dismiss(controller: controller, animated: animated) + } + + let resolvedData = chatFolderLinkPreviewResolvedData( + component: component, + theme: theme, + strings: environmentValue.strings, + selectedItems: state.selectedItems + ) + + let bottomItem: AnyComponent? + if let actionButtonTitle = resolvedData.actionButtonTitle { + bottomItem = AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: actionButtonTitle, + component: AnyComponent(ButtonTextContentComponent( + text: actionButtonTitle, + badge: resolvedData.actionButtonBadge, + textColor: theme.list.itemCheckColors.foregroundColor, + badgeBackground: theme.list.itemCheckColors.foregroundColor, + badgeForeground: theme.list.itemCheckColors.fillColor, + combinedAlignment: true + )) + ), + isEnabled: resolvedData.actionButtonEnabled, + displaysProgress: state.inProgress, + action: { [weak self] in + self?.performMainAction() + } + )) + } else { + bottomItem = nil + } + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(ChatFolderLinkPreviewContentComponent( + context: component.context, + subject: component.subject, + linkContents: component.linkContents, + theme: theme, + resolvedData: resolvedData, + selectedItems: state.selectedItems, + linkListItems: state.linkListItems, + peerAction: { [weak self] peer in + self?.peerAction(peer: peer) + }, + toggleAllSelection: { [weak self] in + self?.toggleAllSelection() + }, + openCreateLink: { [weak self] in + self?.openCreateLink() + }, + openLink: { [weak self] link in + self?.openLink(link: link) + }, + openLinkContextAction: { [weak self] link, sourceView, gesture in + self?.presentLinkContextAction(link: link, sourceView: sourceView, gesture: gesture) + } + )), + titleItem: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: resolvedData.title, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor)) + )), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + rightItem: nil, + hasTopEdgeEffect: false, + bottomItem: bottomItem, + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + animateOut: self.animateOut + )), + environment: { + environmentValue + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environmentValue.statusBarHeight, + safeInsets: environmentValue.safeInsets, + inputHeight: 0.0, + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: environmentValue.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + dismiss(animated) + } + ) + }, + containerSize: availableSize + ) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) + } + + return availableSize + } } - + func makeView() -> View { return View(frame: CGRect()) } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + + func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } @@ -1540,56 +1545,53 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer { case remove(folderId: Int32, defaultSelectedPeerIds: [EnginePeer.Id]) case linkList(folderId: Int32, initialLinks: [ExportedChatFolderLink]) } - + private let context: AccountContext private var linkContentsDisposable: Disposable? - - private var isDismissed: Bool = false - + + private var isDismissed = false + private var dismissCompletion: (() -> Void)? + public init(context: AccountContext, subject: Subject, contents: ChatFolderLinkContents, completion: (() -> Void)? = nil) { self.context = context - + super.init(context: context, component: ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: contents, completion: completion), navigationBarAppearance: .none) - + self.statusBar.statusBarStyle = .Ignore self.navigationPresentation = .flatModal self.blocksBackgroundWhenInOverlay = true - self.automaticallyControlPresentationContextLayout = false self.lockOrientation = true } - + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + deinit { self.linkContentsDisposable?.dispose() } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - } - + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - + self.view.disablesInteractiveModalDismiss = true - - if let componentView = self.node.hostView.componentView as? ChatFolderLinkPreviewScreenComponent.View { - componentView.animateIn() - } } - + + func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true - - if let componentView = self.node.hostView.componentView as? ChatFolderLinkPreviewScreenComponent.View { - componentView.animateOut(completion: { [weak self] in - completion?() - self?.dismiss(animated: false) - }) + self.dismissCompletion = completion + + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } else { + self.completePendingDismiss() self.dismiss(animated: false) } } @@ -1600,19 +1602,17 @@ private final class LinkListContextExtractedContentSource: ContextExtractedConte let keepInPlace: Bool = false let ignoreContentTouches: Bool = false let blurBackground: Bool = true - - //let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center - + private let contentView: ContextExtractedContentContainingView - + init(contentView: ContextExtractedContentContainingView) { self.contentView = contentView } - + func takeView() -> ContextControllerTakeViewInfo? { return ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: UIScreen.main.bounds) } - + func putBack() -> ContextControllerPutBackViewInfo? { return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } diff --git a/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift index d23452ebc4..859cc937b3 100644 --- a/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatList/ChatListFilterTabContainerNode/Sources/ChatListFilterTabContainerNode.swift @@ -559,6 +559,7 @@ public final class ChatListFilterTabContainerNode: ASDisplayNode { if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } + self.scrollNode.view.scrollsToTop = false self.backgroundView.contentView.addSubview(self.scrollNode.view) self.scrollNode.addSubnode(self.selectedBackgroundNode) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift index 7c7cb283e4..e0d642a4d7 100644 --- a/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift +++ b/submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent/Sources/ChatListNoticeItem.swift @@ -184,6 +184,8 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { var okButtonLayout: (TextNodeLayout, () -> TextNode)? var cancelButtonLayout: (TextNodeLayout, () -> TextNode)? var alignment: NSTextAlignment = .left + var textLinkAction: (([NSAttributedString.Key: Any], Int) -> Void)? + var textHighlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? switch item.notice { case let .clearStorage(sizeFraction): @@ -269,6 +271,54 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { textString = NSAttributedString(string: item.strings.ChatList_SessionReview_PanelText(newSessionReview.device, newSessionReview.location).string, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelConfirm, font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) + cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelReject, font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) + case let .reviewBotConnection(newBotConnectionReview, botUsername, totalCount): + spacing = 2.0 + alignment = .center + + var rawTitleString = item.strings.ChatList_BotConnectionReview_PanelTitle + if totalCount > 1 { + rawTitleString = "1/\(totalCount) \(rawTitleString)" + } + titleString = NSAttributedString(string: rawTitleString, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor) + + let formattedBotUsername = botUsername.isEmpty ? "" : "[@\(botUsername)](peer)" + let rawText = item.strings.ChatList_BotConnectionReview_PanelText(formattedBotUsername, newBotConnectionReview.device ?? "", newBotConnectionReview.location ?? "").string + textString = parseMarkdownIntoAttributedString(rawText, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor), + bold: MarkdownAttributeSet(font: textBoldFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor), + link: MarkdownAttributeSet(font: textFont, textColor: item.theme.rootController.navigationBar.accentTextColor), + linkAttribute: { _ in + return (TelegramTextAttributes.URL, "peer") + } + )) + textHighlightAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + } + textLinkAction = { [context = item.context] attributes, _ in + guard let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String else { + return + } + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: newBotConnectionReview.botId) + ) + |> deliverOnMainQueue).startStandalone(next: { peer in + guard let peer, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController { + navigationController.pushViewController(controller) + } else { + context.sharedContext.mainWindow?.present(controller, on: .root) + } + }) + } + okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelConfirm, font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelReject, font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) case let .starsSubscriptionLowBalance(amount, peers): @@ -331,15 +381,18 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { let _ = titleLayout.1(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: .white, attemptSynchronous: true)) if case .center = alignment { - strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.0.size.width) * 0.5), y: verticalInset), size: titleLayout.0.size) + strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.0.size.width) * 0.5), y: verticalInset + 1.0), size: titleLayout.0.size) } else { - strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.0.size) + strongSelf.titleNode.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 1.0), size: titleLayout.0.size) } let _ = textLayout.1(TextNodeWithEntities.Arguments(context: item.context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: .white, attemptSynchronous: true)) strongSelf.titleNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 1000000.0, height: 1000000.0)) strongSelf.textNode.visibilityRect = CGRect(origin: CGPoint(), size: CGSize(width: 1000000.0, height: 1000000.0)) + strongSelf.textNode.linkHighlightColor = item.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.12) + strongSelf.textNode.highlightAttributeAction = textHighlightAction + strongSelf.textNode.tapAttributeAction = textLinkAction if case .center = alignment { strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: strongSelf.titleNode.textNode.frame.maxY + spacing), size: textLayout.0.size) @@ -358,7 +411,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode { strongSelf.avatarsNode = avatarsNode } let avatarSize = CGSize(width: 30.0, height: 30.0) - avatarsNode.update(context: item.context, peers: avatarPeers.map { $0._asPeer() }, synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 16.0, borderWidth: 2.0 - UIScreenPixel, avatarFontSize: 10.0) + avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 16.0, borderWidth: 2.0 - UIScreenPixel, avatarFontSize: 10.0) let avatarsSize = CGSize(width: avatarSize.width + 16.0 * CGFloat(avatarPeers.count - 1), height: avatarSize.height) avatarsNode.updateLayout(size: avatarsSize) avatarsNode.frame = CGRect(origin: CGPoint(x: sideInset - 6.0, y: floor((layout.size.height - avatarsSize.height) / 2.0)), size: avatarsSize) diff --git a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD index 74b602e464..bffe09ad0b 100644 --- a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD +++ b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramUI/Components/GlassBackgroundComponent", diff --git a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift index e9839e633d..aff49df2a4 100644 --- a/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift +++ b/submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode/Sources/ChatListSearchFiltersContainerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -56,6 +55,7 @@ public final class ChatListSearchFiltersContainerNode: ASDisplayNode { self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.canCancelContentTouches = true self.scrollNode.view.contentInsetAdjustmentBehavior = .never + self.scrollNode.view.scrollsToTop = false self.view.addSubview(self.backgroundContainer) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift index 5ebc820020..55759caee5 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift @@ -116,7 +116,11 @@ public final class NavigationButtonComponent: Component { switch component.content { case let .text(title, isBold): - textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor) + if title == "___done" { + imageName = "Navigation/Done" + } else { + textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.medium(17.0), textColor: theme.chat.inputPanel.panelControlColor) + } case .more: isMore = true case let .icon(imageNameValue): diff --git a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/BUILD b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/BUILD index 4cc0f3a66f..981a7dd775 100644 --- a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift index 6bbc4ad256..4e3a5f6995 100644 --- a/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift +++ b/submodules/TelegramUI/Components/ChatParticipantRightsScreen/Sources/ChatParticipantRightsScreen.swift @@ -260,7 +260,7 @@ private final class ChatParticipantRightsContent: CombinedComponent { state.updated(transition: .easeInOut(duration: 0.2)) }, shouldUpdateText: { [weak state] text in - if text.containsEmoji { + if text.containsGraphicEmoji { state?.animateError() return false } diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD index 2c878a8ba4..90488ae6ce 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/TelegramPresentationData", "//submodules/TelegramStringFormatting", @@ -26,6 +25,7 @@ swift_library( "//submodules/TelegramUI/Components/ToastComponent", "//submodules/Markdown", "//submodules/UndoUI", + "//submodules/TooltipUI", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BundleIconComponent", @@ -37,6 +37,7 @@ swift_library( "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/Components/LottieAnimationComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift index e64e7b12b8..d3e5a6f3c7 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift index 30cfdd3ad1..9c9c5234a3 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramStringFormatting @@ -62,6 +61,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.wrappingScrollNode.view.alwaysBounceVertical = true self.wrappingScrollNode.view.delaysContentTouches = false self.wrappingScrollNode.view.canCancelContentTouches = true + self.wrappingScrollNode.view.scrollsToTop = false self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift index 782a1ff862..d459477356 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift @@ -13,10 +13,12 @@ import SheetComponent import ButtonComponent import PlainButtonComponent import BundleIconComponent +import LottieAnimationComponent import GlassBackgroundComponent import GlassBarButtonComponent import DatePickerNode import UndoUI +import TooltipUI private let calendar = Calendar(identifier: .gregorian) @@ -37,6 +39,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { let currentRepeatPeriod: Int32? let suggestedTime: Int32? let minimalTime: Int32? + let silentPosting: Bool let externalState: ExternalState let dismiss: () -> Void @@ -47,6 +50,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { currentRepeatPeriod: Int32?, suggestedTime: Int32?, minimalTime: Int32?, + silentPosting: Bool, externalState: ExternalState, dismiss: @escaping () -> Void ) { @@ -56,6 +60,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { self.currentRepeatPeriod = currentRepeatPeriod self.suggestedTime = suggestedTime self.minimalTime = minimalTime + self.silentPosting = silentPosting self.externalState = externalState self.dismiss = dismiss } @@ -66,6 +71,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component { final class View: UIView { private let cancel = ComponentView() + private let silent = ComponentView() + private let title = ComponentView() private let button = ComponentView() private let secondaryButton = ComponentView() @@ -93,13 +100,15 @@ private final class ChatScheduleTimeSheetContentComponent: Component { private var monthHeight: CGFloat? + private var isSilentPosting = false + private var date: Date? private var minDate: Date? private var maxDate: Date? private var isPickingTime = false private var isPickingRepeatPeriod = false - + private var repeatPeriod: Int32? private let dateFormatter: DateFormatter @@ -144,6 +153,70 @@ private final class ChatScheduleTimeSheetContentComponent: Component { } } + private func presentSilentPostingTooltip() { + guard let component = self.component, let sourceView = self.silent.view else { + return + } + + let peerId: EnginePeer.Id? + switch component.mode { + case let .scheduledMessages(peerIdValue, _): + peerId = peerIdValue + case .reminders: + peerId = component.context.account.peerId + default: + peerId = nil + } + guard let peerId else { + return + } + + let isSilentPosting = self.isSilentPosting + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer, let environment = self.environment, let controller = self.environment?.controller() else { + return + } + var isChannel = false + if case let .channel(channel) = peer, case .broadcast = channel.info { + isChannel = true + } + let text: String + if case let .user(user) = peer { + if user.id == component.context.account.peerId { + if isSilentPosting { + text = environment.strings.ScheduleMessage_SilentPosting_YouEnabled + } else { + text = environment.strings.ScheduleMessage_SilentPosting_YouDisabled + } + } else { + if isSilentPosting { + text = environment.strings.ScheduleMessage_SilentPosting_UserEnabled(peer.compactDisplayTitle).string + } else { + text = environment.strings.ScheduleMessage_SilentPosting_UserDisabled(peer.compactDisplayTitle).string + } + } + } else if isChannel { + if isSilentPosting { + text = environment.strings.ScheduleMessage_SilentPosting_ChannelEnabled + } else { + text = environment.strings.ScheduleMessage_SilentPosting_ChannelDisabled + } + } else { + if isSilentPosting { + text = environment.strings.ScheduleMessage_SilentPosting_GroupEnabled + } else { + text = environment.strings.ScheduleMessage_SilentPosting_GroupDisabled + } + } + + let sourceFrame = sourceView.convert(sourceView.bounds, to: nil) + controller.present(TooltipScreen(account: component.context.account, sharedContext: component.context.sharedContext, text: .plain(text: text), style: .default, icon: .none, location: .point(sourceFrame, .bottom), shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }), in: .window(.root)) + }) + } + func update(component: ChatScheduleTimeSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -157,6 +230,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { self.environment = environment if self.component == nil { + self.isSilentPosting = component.silentPosting switch component.mode { case .format, .search: self.minDate = Date(timeIntervalSince1970: 0.0) @@ -184,40 +258,6 @@ private final class ChatScheduleTimeSheetContentComponent: Component { var contentHeight: CGFloat = 0.0 contentHeight += 30.0 - let barButtonSize = CGSize(width: 44.0, height: 44.0) - let cancelSize = self.cancel.update( - transition: transition, - component: AnyComponent( - GlassBarButtonComponent( - size: barButtonSize, - backgroundColor: nil, - isDark: environment.theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "close", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Close", - tintColor: environment.theme.chat.inputPanel.panelControlColor - ) - )), - action: { [weak self] _ in - guard let self, let component = self.component else { - return - } - component.dismiss() - } - ) - ), - environment: {}, - containerSize: barButtonSize - ) - let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) - if let cancelView = self.cancel.view { - if cancelView.superview == nil { - self.addSubview(cancelView) - } - transition.setFrame(view: cancelView, frame: cancelFrame) - } - let title: String switch component.mode { case .scheduledMessages: @@ -229,8 +269,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { case .poll: title = strings.CreatePoll_Deadline_Title case .search: - //TODO:localize - title = "Search" + title = strings.Conversation_CalendarSearch_Title } let titleSize = self.title.update( transition: transition, @@ -318,75 +357,82 @@ private final class ChatScheduleTimeSheetContentComponent: Component { contentHeight += pickerHeight } - transition.setFrame(layer: self.topSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel))) - self.topSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor - if self.topSeparator.superlayer == nil { - self.layer.addSublayer(self.topSeparator) - } - - let timeTitleSize = self.timeTitle.update( - transition: transition, - component: AnyComponent( - Text(text: strings.ScheduleMessage_Time, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) - ), - environment: {}, - containerSize: availableSize - ) - let timeTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: timeTitleSize) - if let timeTitleView = self.timeTitle.view { - if timeTitleView.superview == nil { - self.addSubview(timeTitleView) - } - transition.setFrame(view: timeTitleView, frame: timeTitleFrame) - } - let date = self.date ?? Date() + var timeValueFrame: CGRect? - var t: time_t = Int(date.timeIntervalSince1970) - var timeinfo = tm() - localtime_r(&t, &timeinfo); - - let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: environment.dateTimeFormat) - let timeValueSize = self.timeValue.update( - transition: transition, - component: AnyComponent( - PlainButtonComponent( - content: AnyComponent( - ButtonContentComponent( - theme: environment.theme, - text: timeString, - isActive: self.isPickingTime, - isLocked: false - ) - ), - action: { [weak self] in - guard let self else { - return - } - if self.isPickingRepeatPeriod { - self.isPickingRepeatPeriod = false - } else { - self.isPickingTime = !self.isPickingTime - } - self.state?.updated() - }, - animateScale: false - ) - ), - environment: { - }, - containerSize: availableSize - ) - let timeValueFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - timeValueSize.width, y: contentHeight + 10.0), size: timeValueSize) - if let timeValueView = self.timeValue.view { - if timeValueView.superview == nil { - self.addSubview(timeValueView) + switch component.mode { + case .search: + break + default: + transition.setFrame(layer: self.topSeparator, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel))) + self.topSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor + if self.topSeparator.superlayer == nil { + self.layer.addSublayer(self.topSeparator) } - transition.setFrame(view: timeValueView, frame: timeValueFrame) + + let timeTitleSize = self.timeTitle.update( + transition: transition, + component: AnyComponent( + Text(text: strings.ScheduleMessage_Time, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) + ), + environment: {}, + containerSize: availableSize + ) + let timeTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: timeTitleSize) + if let timeTitleView = self.timeTitle.view { + if timeTitleView.superview == nil { + self.addSubview(timeTitleView) + } + transition.setFrame(view: timeTitleView, frame: timeTitleFrame) + } + + var t: time_t = Int(date.timeIntervalSince1970) + var timeinfo = tm() + localtime_r(&t, &timeinfo); + + let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: environment.dateTimeFormat) + let timeValueSize = self.timeValue.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + ButtonContentComponent( + theme: environment.theme, + text: timeString, + isActive: self.isPickingTime, + isLocked: false + ) + ), + action: { [weak self] in + guard let self else { + return + } + if self.isPickingRepeatPeriod { + self.isPickingRepeatPeriod = false + } else { + self.isPickingTime = !self.isPickingTime + } + self.state?.updated() + }, + animateScale: false + ) + ), + environment: { + }, + containerSize: availableSize + ) + let timeValueFrameValue = CGRect(origin: CGPoint(x: availableSize.width - sideInset - timeValueSize.width, y: contentHeight + 10.0), size: timeValueSize) + timeValueFrame = timeValueFrameValue + if let timeValueView = self.timeValue.view { + if timeValueView.superview == nil { + self.addSubview(timeValueView) + } + transition.setFrame(view: timeValueView, frame: timeValueFrameValue) + } + + contentHeight += 56.0 } - contentHeight += 56.0 - var repeatValueFrame = CGRect() if case .format = component.mode { contentHeight += 8.0 @@ -504,11 +550,10 @@ private final class ChatScheduleTimeSheetContentComponent: Component { case .poll: buttonTitle = strings.CreatePoll_Deadline_SetDeadline case .search: - //TODO:localize - buttonTitle = "Done" + buttonTitle = strings.Conversation_CalendarSearch_Done } - let buttonSideInset: CGFloat = 30.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( @@ -530,16 +575,17 @@ private final class ChatScheduleTimeSheetContentComponent: Component { controller.completion( ChatScheduleTimeScreen.Result( time: Int32(self.date?.timeIntervalSince1970 ?? 0), - repeatPeriod: self.repeatPeriod + repeatPeriod: self.repeatPeriod, + silentPosting: self.isSilentPosting ) ) component.dismiss() } )), environment: {}, - containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -562,12 +608,12 @@ private final class ChatScheduleTimeSheetContentComponent: Component { component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, - color: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.1), - foreground: environment.theme.list.itemDestructiveColor, - pressedColor: environment.theme.list.itemDestructiveColor.withMultipliedAlpha(0.8), + color: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.1), + foreground: environment.theme.list.itemAccentColor, + pressedColor: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.8), ), content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( - Text(text: strings.Conversation_FormatDate_RemoveDate, font: Font.semibold(17.0), color: environment.theme.list.itemDestructiveColor) + Text(text: strings.Conversation_FormatDate_RemoveDate, font: Font.semibold(17.0), color: environment.theme.list.itemAccentColor) )), isEnabled: true, displaysProgress: false, @@ -578,16 +624,17 @@ private final class ChatScheduleTimeSheetContentComponent: Component { controller.completion( ChatScheduleTimeScreen.Result( time: 0, - repeatPeriod: nil + repeatPeriod: nil, + silentPosting: false ) ) component.dismiss() } )), environment: {}, - containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize) if let buttonView = self.secondaryButton.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -595,7 +642,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component { transition.setFrame(view: buttonView, frame: buttonFrame) } contentHeight += buttonSize.height - } else if case .scheduledMessages(true) = component.mode { + } else if case .scheduledMessages(_, true) = component.mode { contentHeight += 8.0 let buttonSize = self.secondaryButton.update( @@ -619,16 +666,17 @@ private final class ChatScheduleTimeSheetContentComponent: Component { controller.completion( ChatScheduleTimeScreen.Result( time: scheduleWhenOnlineTimestamp, - repeatPeriod: nil + repeatPeriod: nil, + silentPosting: self.isSilentPosting ) ) component.dismiss() } )), environment: {}, - containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize) if let buttonView = self.secondaryButton.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -637,14 +685,11 @@ private final class ChatScheduleTimeSheetContentComponent: Component { } contentHeight += buttonSize.height } - - let bottomPanelPadding: CGFloat = 15.0 - let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding - contentHeight += bottomInset + contentHeight += buttonInsets.bottom let contentSize = CGSize(width: availableSize.width, height: contentHeight) - if self.isPickingTime { + if self.isPickingTime, let timeValueFrame { let _ = self.timePicker.update( transition: transition, component: AnyComponent( @@ -784,6 +829,87 @@ private final class ChatScheduleTimeSheetContentComponent: Component { component.externalState.repeatValueFrame = repeatValueFrame + let barButtonSize = CGSize(width: 44.0, height: 44.0) + let cancelSize = self.cancel.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + component.dismiss() + } + ) + ), + environment: {}, + containerSize: barButtonSize + ) + let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) + if let cancelView = self.cancel.view { + if cancelView.superview == nil { + self.addSubview(cancelView) + } + transition.setFrame(view: cancelView, frame: cancelFrame) + } + + switch component.mode { + case .scheduledMessages, .reminders: + let silentSize = self.silent.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "silent", component: AnyComponent( + LottieAnimationComponent( + animation: LottieAnimationComponent.AnimationItem( + name: self.isSilentPosting ? "NavigationMuteOn" : "NavigationMuteOff", + mode: !transition.animation.isImmediate ? .animating(loop: false) : .still(position: .end), + range: nil, + waitForCompletion: false + ), + colors: ["__allcolors__": environment.theme.chat.inputPanel.panelControlColor], + size: CGSize(width: 30.0, height: 30.0) + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + self.isSilentPosting = !self.isSilentPosting + self.state?.updated(transition: .easeInOut(duration: 0.2)) + + self.presentSilentPostingTooltip() + } + ) + ), + environment: {}, + containerSize: barButtonSize + ) + let silentFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - silentSize.width, y: 16.0), size: silentSize) + if let silentView = self.silent.view { + if silentView.superview == nil { + self.addSubview(silentView) + } + transition.setFrame(view: silentView, frame: silentFrame) + } + default: + break + } + return contentSize } } @@ -806,6 +932,7 @@ private final class ChatScheduleTimeScreenComponent: Component { let currentRepeatPeriod: Int32? let suggestedTime: Int32? let minimalTime: Int32? + let silentPosting: Bool init( context: AccountContext, @@ -813,7 +940,8 @@ private final class ChatScheduleTimeScreenComponent: Component { currentTime: Int32?, currentRepeatPeriod: Int32?, suggestedTime: Int32?, - minimalTime: Int32? + minimalTime: Int32?, + silentPosting: Bool ) { self.context = context self.mode = mode @@ -821,6 +949,7 @@ private final class ChatScheduleTimeScreenComponent: Component { self.currentRepeatPeriod = currentRepeatPeriod self.suggestedTime = suggestedTime self.minimalTime = minimalTime + self.silentPosting = silentPosting } static func ==(lhs: ChatScheduleTimeScreenComponent, rhs: ChatScheduleTimeScreenComponent) -> Bool { @@ -839,6 +968,9 @@ private final class ChatScheduleTimeScreenComponent: Component { if lhs.minimalTime != rhs.minimalTime { return false } + if lhs.silentPosting != rhs.silentPosting { + return false + } return true } @@ -893,6 +1025,7 @@ private final class ChatScheduleTimeScreenComponent: Component { currentRepeatPeriod: component.currentRepeatPeriod, suggestedTime: component.suggestedTime, minimalTime: component.minimalTime, + silentPosting: component.silentPosting, externalState: self.contentExternalState, dismiss: { [weak self] in guard let self else { @@ -958,7 +1091,7 @@ private final class ChatScheduleTimeScreenComponent: Component { public class ChatScheduleTimeScreen: ViewControllerComponentContainer { public enum Mode: Equatable { - case scheduledMessages(sendWhenOnlineAvailable: Bool) + case scheduledMessages(peerId: EnginePeer.Id, sendWhenOnlineAvailable: Bool) case reminders case format case poll @@ -968,6 +1101,13 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer { public struct Result { public let time: Int32 public let repeatPeriod: Int32? + public let silentPosting: Bool + + public init(time: Int32, repeatPeriod: Int32?, silentPosting: Bool = false) { + self.time = time + self.repeatPeriod = repeatPeriod + self.silentPosting = silentPosting + } } fileprivate let completion: (Result) -> Void @@ -979,6 +1119,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer { currentRepeatPeriod: Int32? = nil, suggestedTime: Int32? = nil, minimalTime: Int32? = nil, + silentPosting: Bool = false, isDark: Bool, completion: @escaping (Result) -> Void ) { @@ -990,7 +1131,8 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer { currentTime: currentTime, currentRepeatPeriod: currentRepeatPeriod, suggestedTime: suggestedTime, - minimalTime: minimalTime + minimalTime: minimalTime, + silentPosting: silentPosting ), navigationBarAppearance: .none, theme: isDark ? .dark : .default) self.statusBar.statusBarStyle = .Ignore diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD index 1f30dd2987..23187abd0d 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/TelegramPresentationData", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift index e64e7b12b8..d3e5a6f3c7 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/BUILD b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD index cb29f70424..e056bd9096 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD @@ -13,7 +13,11 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/SheetComponent", + "//submodules/Components/ViewControllerComponent", "//submodules/TelegramCore", "//submodules/AccountContext", "//submodules/ComponentFlow", @@ -21,13 +25,11 @@ swift_library( "//submodules/TelegramUIPreferences", "//submodules/PresentationDataUtils", "//submodules/TelegramNotices", - "//submodules/AnimationUI", "//submodules/MergeLists", "//submodules/MediaResources", "//submodules/StickerResources", "//submodules/WallpaperResources", "//submodules/TooltipUI", - "//submodules/SolidRoundedButtonNode", "//submodules/AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode", "//submodules/ShimmerEffect", @@ -36,6 +38,9 @@ swift_library( "//submodules/Markdown", "//submodules/AppBundle", "//submodules/ActivityIndicator", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/TelegramUI/Components/AlertComponent", "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent", diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift index d33e7e4012..ede779c011 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift @@ -2,16 +2,16 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox +import ComponentFlow +import ComponentDisplayAdapters +import ViewControllerComponent import TelegramCore import SwiftSignalKit import AccountContext -import SolidRoundedButtonNode import TelegramPresentationData import TelegramUIPreferences import TelegramNotices import PresentationDataUtils -import AnimationUI import MergeLists import MediaResources import StickerResources @@ -23,6 +23,12 @@ import ShimmerEffect import AttachmentUI import AvatarNode import AlertComponent +import SheetComponent +import ButtonComponent +import GlassBarButtonComponent +import BundleIconComponent +import LottieComponent +import MultilineTextComponent private struct ThemeSettingsThemeEntry: Comparable, Identifiable { let index: Int @@ -217,7 +223,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if let image = cachedBorderImages[key] { return image } else { - let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let image = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -243,7 +249,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec context.setLineWidth(lineWidth) context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) } - })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 16) cachedBorderImages[key] = image return image } @@ -289,7 +295,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { self.imageNode = TransformImageNode() self.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 82.0, height: 108.0)) self.imageNode.isLayerBacked = true - self.imageNode.cornerRadius = 8.0 + self.imageNode.cornerRadius = 16.0 self.imageNode.clipsToBounds = true self.imageNode.contentAnimations = [.subsequentUpdates] @@ -441,7 +447,11 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { } } if item.themeReference == nil { - strongSelf.imageNode.backgroundColor = item.theme.list.plainBackgroundColor + if item.theme.overallDarkAppearance { + strongSelf.imageNode.backgroundColor = item.theme.list.plainBackgroundColor + } else { + strongSelf.imageNode.backgroundColor = item.theme.rootController.navigationBar.segmentedForegroundColor + } } if updatedTheme || updatedSelected { @@ -492,7 +502,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { } strongSelf.animatedStickerNode = animatedStickerNode strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) @@ -500,7 +510,7 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { animatedStickerNode.autoplay = true animatedStickerNode.visibility = strongSelf.visibilityStatus - strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).startStrict()) + strongSelf.stickerFetchedDisposable.set(item.context.engine.resources.fetch(reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource), userLocation: .other, userContentType: .sticker).startStrict()) let thumbnailDimensions = PixelDimensions(width: 512, height: 512) strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) @@ -620,8 +630,8 @@ public final class ChatThemeScreen: ViewController { public static let themeCrossfadeDuration: Double = 0.3 public static let themeCrossfadeDelay: Double = 0.25 - private var controllerNode: ChatThemeScreenNode { - return self.displayNode as! ChatThemeScreenNode + private var controllerNode: ChatThemeSheetScreenNode { + return self.displayNode as! ChatThemeSheetScreenNode } private var animatedIn = false @@ -700,7 +710,7 @@ public final class ChatThemeScreen: ViewController { } override public func loadDisplayNode() { - self.displayNode = ChatThemeScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedTheme: self.initiallySelectedTheme, peerName: self.peerName) + self.displayNode = ChatThemeSheetScreenNode(context: self.context, presentationData: self.presentationData, controller: self, animatedEmojiStickers: self.animatedEmojiStickers, initiallySelectedTheme: self.initiallySelectedTheme, peerName: self.peerName) self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl self.controllerNode.previewTheme = { [weak self] chatTheme, dark in guard let strongSelf = self else { @@ -806,214 +816,418 @@ private func interpolateColors(from: [String: UIColor], to: [String: UIColor], f return colors } -private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelegate { - private let context: AccountContext - private var presentationData: PresentationData - private weak var controller: ChatThemeScreen? +private final class ChatThemeScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let topContentContainerNode: SparseNode - private let buttonsContentContainerNode: SparseNode - private let effectNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let textNode: ImmediateTextNode - private let cancelButtonNode: WebAppCancelButtonNode - private let switchThemeButton: HighlightTrackingButtonNode - private let animationContainerNode: ASDisplayNode - private var animationNode: AnimationNode - private let doneButton: SolidRoundedButtonNode - private let otherButton: HighlightableButtonNode + let context: AccountContext + let presentationData: PresentationData + let animatedEmojiStickers: [String: [StickerPackItem]] + let initiallySelectedTheme: ChatTheme? + let peerName: String + let canResetWallpaper: Bool + let present: (ViewController) -> Void + let presentInRoot: (ViewController) -> Void + let previewTheme: (ChatTheme?, Bool?) -> Void + let changeWallpaper: () -> Void + let resetWallpaper: () -> Void + let completion: (ChatTheme?) -> Void + let cancel: () -> Void - private let listNode: ListView - private var entries: [ThemeSettingsThemeEntry]? - private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] - private var initialized = false - - private let uniqueGiftChatThemesContext: UniqueGiftChatThemesContext - private var currentUniqueGiftChatThemesState: UniqueGiftChatThemesContext.State? - - private let peerName: String - - private let initiallySelectedTheme: ChatTheme? - private var selectedTheme: ChatTheme? { - didSet { - self.selectedThemePromise.set(self.selectedTheme) - } - } - private var selectedThemePromise: ValuePromise - - private var isDarkAppearancePromise: ValuePromise - private var isDarkAppearance: Bool = false { - didSet { - self.isDarkAppearancePromise.set(self.isDarkAppearance) - } - } - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - private let disposable = MetaDisposable() - - var present: ((ViewController) -> Void)? - var previewTheme: ((ChatTheme?, Bool?) -> Void)? - var completion: ((ChatTheme?) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, controller: ChatThemeScreen, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedTheme: ChatTheme?, peerName: String) { + init( + context: AccountContext, + presentationData: PresentationData, + animatedEmojiStickers: [String: [StickerPackItem]], + initiallySelectedTheme: ChatTheme?, + peerName: String, + canResetWallpaper: Bool, + present: @escaping (ViewController) -> Void, + presentInRoot: @escaping (ViewController) -> Void, + previewTheme: @escaping (ChatTheme?, Bool?) -> Void, + changeWallpaper: @escaping () -> Void, + resetWallpaper: @escaping () -> Void, + completion: @escaping (ChatTheme?) -> Void, + cancel: @escaping () -> Void + ) { self.context = context - self.controller = controller + self.presentationData = presentationData + self.animatedEmojiStickers = animatedEmojiStickers self.initiallySelectedTheme = initiallySelectedTheme self.peerName = peerName - self.selectedTheme = initiallySelectedTheme - self.selectedThemePromise = ValuePromise(initiallySelectedTheme) - self.presentationData = presentationData + self.canResetWallpaper = canResetWallpaper + self.present = present + self.presentInRoot = presentInRoot + self.previewTheme = previewTheme + self.changeWallpaper = changeWallpaper + self.resetWallpaper = resetWallpaper + self.completion = completion + self.cancel = cancel + } + + static func ==(lhs: ChatThemeScreenComponent, rhs: ChatThemeScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.presentationData.theme !== rhs.presentationData.theme { + return false + } + if lhs.presentationData.strings !== rhs.presentationData.strings { + return false + } + if lhs.initiallySelectedTheme != rhs.initiallySelectedTheme { + return false + } + if lhs.peerName != rhs.peerName { + return false + } + if lhs.canResetWallpaper != rhs.canResetWallpaper { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() - self.uniqueGiftChatThemesContext = UniqueGiftChatThemesContext(account: context.account) + private var component: ChatThemeScreenComponent? + private var environment: ViewControllerComponentContainer.Environment? - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true + override init(frame: CGRect) { + super.init(frame: frame) + } - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = .clear + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false + func update(component: ChatThemeScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environmentValue + + let sheetEnvironment = SheetComponentEnvironment( + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: false, + hasInputHeight: !environmentValue.inputHeight.isZero, + regularMetricsSize: nil, + dismiss: { _ in + component.cancel() + } + ) + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(ChatThemeSheetContentComponent( + context: component.context, + presentationData: component.presentationData, + animatedEmojiStickers: component.animatedEmojiStickers, + initiallySelectedTheme: component.initiallySelectedTheme, + peerName: component.peerName, + canResetWallpaper: component.canResetWallpaper, + present: component.present, + presentInRoot: component.presentInRoot, + previewTheme: component.previewTheme, + changeWallpaper: component.changeWallpaper, + resetWallpaper: component.resetWallpaper, + completion: component.completion, + cancel: component.cancel + )), + style: .glass, + backgroundColor: .color(environmentValue.theme.actionSheet.opaqueItemBackgroundColor), + clipsContent: true, + isScrollEnabled: false, + hasDimView: false, + animateOut: self.sheetAnimateOut + )), + environment: { + environmentValue + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: sheetSize)) + } + + return availableSize + } - self.topContentContainerNode = SparseNode() - self.topContentContainerNode.isOpaque = false + private func contentView() -> ChatThemeSheetContentComponent.View? { + guard let sheetView = self.sheet.view else { + return nil + } + return findTaggedComponentViewImpl(view: sheetView, tag: ChatThemeSheetContentComponent.Tag()) as? ChatThemeSheetContentComponent.View + } + + func containsContent(point: CGPoint) -> Bool { + guard let contentView = self.contentView() else { + return false + } + return contentView.bounds.contains(self.convert(point, to: contentView)) + } + + func dimTapped() { + self.contentView()?.dimTapped() + } + + func animateOut(completion: @escaping () -> Void) { + self.contentView()?.setAnimatedOut() + self.sheetAnimateOut.invoke(Action { _ in + completion() + }) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} - self.buttonsContentContainerNode = SparseNode() - self.buttonsContentContainerNode.isOpaque = false +private final class ChatThemeSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + final class Tag { + } + + let context: AccountContext + let presentationData: PresentationData + let animatedEmojiStickers: [String: [StickerPackItem]] + let initiallySelectedTheme: ChatTheme? + let peerName: String + let canResetWallpaper: Bool + let present: (ViewController) -> Void + let presentInRoot: (ViewController) -> Void + let previewTheme: (ChatTheme?, Bool?) -> Void + let changeWallpaper: () -> Void + let resetWallpaper: () -> Void + let completion: (ChatTheme?) -> Void + let cancel: () -> Void + + init( + context: AccountContext, + presentationData: PresentationData, + animatedEmojiStickers: [String: [StickerPackItem]], + initiallySelectedTheme: ChatTheme?, + peerName: String, + canResetWallpaper: Bool, + present: @escaping (ViewController) -> Void, + presentInRoot: @escaping (ViewController) -> Void, + previewTheme: @escaping (ChatTheme?, Bool?) -> Void, + changeWallpaper: @escaping () -> Void, + resetWallpaper: @escaping () -> Void, + completion: @escaping (ChatTheme?) -> Void, + cancel: @escaping () -> Void + ) { + self.context = context + self.presentationData = presentationData + self.animatedEmojiStickers = animatedEmojiStickers + self.initiallySelectedTheme = initiallySelectedTheme + self.peerName = peerName + self.canResetWallpaper = canResetWallpaper + self.present = present + self.presentInRoot = presentInRoot + self.previewTheme = previewTheme + self.changeWallpaper = changeWallpaper + self.resetWallpaper = resetWallpaper + self.completion = completion + self.cancel = cancel + } + + static func ==(lhs: ChatThemeSheetContentComponent, rhs: ChatThemeSheetContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.presentationData.theme !== rhs.presentationData.theme { + return false + } + if lhs.presentationData.strings !== rhs.presentationData.strings { + return false + } + if lhs.initiallySelectedTheme != rhs.initiallySelectedTheme { + return false + } + if lhs.peerName != rhs.peerName { + return false + } + if lhs.canResetWallpaper != rhs.canResetWallpaper { + return false + } + return true + } + + final class View: UIView, ComponentTaggedView { + private enum PrimaryAction: Equatable { + case chooseWallpaper + case resetTheme + case apply + } - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 + func matches(tag: Any) -> Bool { + return tag is Tag + } - self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance - self.isDarkAppearancePromise = ValuePromise(self.presentationData.theme.overallDarkAppearance) + private let title = ComponentView() + private let subtitle = ComponentView() + private let leftButton = ComponentView() + private let switchThemeButton = ComponentView() + private let primaryButton = ComponentView() + private var resetWallpaperButton: ComponentView? + private let switchThemePlayOnce = ActionSlot() - let backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - let textColor = self.presentationData.theme.actionSheet.primaryTextColor - let secondaryTextColor = self.presentationData.theme.actionSheet.secondaryTextColor - let blurStyle: UIBlurEffect.Style = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark + private let listNode: ListView + private var entries: [ThemeSettingsThemeEntry]? + private var enqueuedTransitions: [ThemeSettingsThemeItemNodeTransition] = [] + private var initialized = false - self.effectNode = ASDisplayNode(viewBlock: { - return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) - }) + private var component: ChatThemeSheetContentComponent? + private weak var state: EmptyComponentState? + private var environment: EnvironmentType? - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor + private var selectedTheme: ChatTheme? + private var isDarkAppearance: Bool = false + private var isSwitchThemeEnabled = true + private var isCompleting = false - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(17.0), textColor: textColor) + private var themes: [TelegramTheme] = [] + private var uniqueGiftChatThemesContext: UniqueGiftChatThemesContext? + private var currentUniqueGiftChatThemesState: UniqueGiftChatThemesContext.State? + private var uniqueGiftPeers: [EnginePeer.Id: EnginePeer] = [:] + private let disposable = MetaDisposable() - self.textNode = ImmediateTextNode() - self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(15.0), textColor: secondaryTextColor) - self.textNode.isHidden = true + private var themeSelectionsCount = 0 + private var displayedPreviewTooltip = false + private var animatedOut = false - self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings) - - self.switchThemeButton = HighlightTrackingButtonNode() - self.animationContainerNode = ASDisplayNode() - self.animationContainerNode.isUserInteractionEnabled = false - - self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) - self.animationNode.isUserInteractionEnabled = false - - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), glass: true, height: 52.0, cornerRadius: 26.0) - - self.otherButton = HighlightableButtonNode() - - self.listNode = ListViewImpl() - self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.updateButtons() - - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - self.wrappingScrollNode.addSubnode(self.topContentContainerNode) - self.wrappingScrollNode.addSubnode(self.buttonsContentContainerNode) - - self.backgroundNode.addSubnode(self.effectNode) - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.buttonsContentContainerNode.addSubnode(self.textNode) - self.buttonsContentContainerNode.addSubnode(self.doneButton) - self.buttonsContentContainerNode.addSubnode(self.otherButton) - - self.topContentContainerNode.addSubnode(self.animationContainerNode) - self.animationContainerNode.addSubnode(self.animationNode) - self.topContentContainerNode.addSubnode(self.switchThemeButton) - self.topContentContainerNode.addSubnode(self.listNode) - self.topContentContainerNode.addSubnode(self.cancelButtonNode) - - self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside) - self.cancelButtonNode.buttonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.doneButton.pressed = { [weak self] in - if let strongSelf = self { - if strongSelf.doneButton.font == .bold { - strongSelf.complete() - } else { - strongSelf.controller?.changeWallpaper() + override init(frame: CGRect) { + self.listNode = ListViewImpl() + self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) + + super.init(frame: frame) + + self.addSubview(self.listNode.view) + self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true + + self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in + guard let self, let state = self.currentUniqueGiftChatThemesState, case .ready(true) = state.dataState else { + return + } + if case let .known(value) = offset, value < 100.0 { + self.uniqueGiftChatThemesContext?.loadMore() } } } - self.otherButton.addTarget(self, action: #selector(self.otherButtonPressed), forControlEvents: .touchUpInside) - self.disposable.set(combineLatest( - queue: Queue.mainQueue(), - self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), - self.uniqueGiftChatThemesContext.state - |> mapToSignal { state -> Signal<(UniqueGiftChatThemesContext.State, [EnginePeer.Id: EnginePeer]), NoError> in - var peerIds: [EnginePeer.Id] = [] - for theme in state.themes { - if case let .gift(gift, _) = theme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId { - peerIds.append(themePeerId) - } - } - return combineLatest( - .single(state), - context.engine.data.get( - EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)) - ) |> map { peers in - var result: [EnginePeer.Id: EnginePeer] = [:] - for peerId in peerIds { - if let maybePeer = peers[peerId], let peer = maybePeer { - result[peerId] = peer - } - } - return result - } - ) - }, - self.selectedThemePromise.get(), - self.isDarkAppearancePromise.get() - ).startStrict(next: { [weak self] themes, uniqueGiftChatThemesStateAndPeers, selectedTheme, isDarkAppearance in - guard let strongSelf = self else { + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable.dispose() + } + + func setAnimatedOut() { + self.animatedOut = true + } + + private func setupIfNeeded(component: ChatThemeSheetContentComponent) { + guard self.uniqueGiftChatThemesContext == nil else { return } - let (uniqueGiftChatThemesState, peers) = uniqueGiftChatThemesStateAndPeers - strongSelf.currentUniqueGiftChatThemesState = uniqueGiftChatThemesState - - let isFirstTime = strongSelf.entries == nil - let presentationData = strongSelf.presentationData - + + self.selectedTheme = component.initiallySelectedTheme + self.isDarkAppearance = component.presentationData.theme.overallDarkAppearance + + let uniqueGiftChatThemesContext = UniqueGiftChatThemesContext(account: component.context.account) + self.uniqueGiftChatThemesContext = uniqueGiftChatThemesContext + + let context = component.context + self.disposable.set(combineLatest( + queue: Queue.mainQueue(), + context.engine.themes.getChatThemes(accountManager: context.sharedContext.accountManager), + uniqueGiftChatThemesContext.state + |> mapToSignal { state -> Signal<(UniqueGiftChatThemesContext.State, [EnginePeer.Id: EnginePeer]), NoError> in + var peerIds: [EnginePeer.Id] = [] + for theme in state.themes { + if case let .gift(gift, _) = theme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId { + peerIds.append(themePeerId) + } + } + return combineLatest( + .single(state), + context.engine.data.get( + EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)) + ) + |> map { peers in + var result: [EnginePeer.Id: EnginePeer] = [:] + for peerId in peerIds { + if let maybePeer = peers[peerId], let peer = maybePeer { + result[peerId] = peer + } + } + return result + } + ) + } + ).startStrict(next: { [weak self] themes, uniqueGiftChatThemesStateAndPeers in + guard let self else { + return + } + self.themes = themes + self.currentUniqueGiftChatThemesState = uniqueGiftChatThemesStateAndPeers.0 + self.uniqueGiftPeers = uniqueGiftChatThemesStateAndPeers.1 + self.rebuildEntries(crossfade: false) + })) + } + + private func hasChanges() -> Bool { + return self.selectedTheme?.id != self.component?.initiallySelectedTheme?.id + } + + private func primaryAction() -> PrimaryAction { + if self.selectedTheme?.id == self.component?.initiallySelectedTheme?.id { + return .chooseWallpaper + } else if self.selectedTheme == nil && self.component?.initiallySelectedTheme != nil { + return .resetTheme + } else { + return .apply + } + } + + private func primaryButtonTitle(strings: PresentationStrings) -> String { + switch self.primaryAction() { + case .chooseWallpaper: + if self.component?.canResetWallpaper == true { + return strings.Conversation_Theme_SetNewPhotoWallpaper + } else { + return strings.Conversation_Theme_SetPhotoWallpaper + } + case .resetTheme: + return strings.Conversation_Theme_Reset + case .apply: + return strings.Conversation_Theme_Apply + } + } + + private func rebuildEntries(crossfade: Bool) { + guard let component = self.component else { + return + } + + let presentationData = component.presentationData + let selectedTheme = self.selectedTheme + let isDarkAppearance = self.isDarkAppearance + var entries: [ThemeSettingsThemeEntry] = [] entries.append(ThemeSettingsThemeEntry( index: 0, @@ -1027,10 +1241,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega strings: presentationData.strings, wallpaper: nil )) - - var giftThemes = uniqueGiftChatThemesState.themes + + var giftThemes = self.currentUniqueGiftChatThemesState?.themes ?? [] var existingIds = Set() - if let initiallySelectedTheme, case .gift = initiallySelectedTheme { + if let initiallySelectedTheme = component.initiallySelectedTheme, case .gift = initiallySelectedTheme { let initialThemeIndex = giftThemes.firstIndex(where: { $0.id == initiallySelectedTheme.id }) if initialThemeIndex == nil || initialThemeIndex! > 50 { giftThemes.insert(initiallySelectedTheme, at: 0) @@ -1049,8 +1263,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega emojiFile = file } } - if let themePeerId = uniqueGift.themePeerId, theme.id != initiallySelectedTheme?.id { - peer = peers[themePeerId] + if let themePeerId = uniqueGift.themePeerId, theme.id != component.initiallySelectedTheme?.id { + peer = self.uniqueGiftPeers[themePeerId] } } let themeReference: PresentationThemeReference @@ -1077,15 +1291,16 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega existingIds.insert(theme.id) } - if uniqueGiftChatThemesState.themes.count == 0 || uniqueGiftChatThemesState.dataState == .ready(canLoadMore: false) { - for theme in themes { + let uniqueGiftThemesState = self.currentUniqueGiftChatThemesState + if uniqueGiftThemesState?.themes.count == 0 || uniqueGiftThemesState?.dataState == .ready(canLoadMore: false) { + for theme in self.themes { guard let emoticon = theme.emoticon else { continue } entries.append(ThemeSettingsThemeEntry( index: entries.count, chatTheme: .emoticon(emoticon), - emojiFile: animatedEmojiStickers[emoticon]?.first?.file._parse(), + emojiFile: component.animatedEmojiStickers[emoticon]?.first?.file._parse(), themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), peer: nil, nightMode: isDarkAppearance, @@ -1096,438 +1311,648 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega )) } } - + let action: (ChatTheme?) -> Void = { [weak self] chatTheme in - if let self, self.selectedTheme != chatTheme { - self.setChatTheme(chatTheme) - } - } - let previousEntries = strongSelf.entries ?? [] - //let crossfade = previousEntries.count != entries.count - let transition = preparedTransition(context: strongSelf.context, action: action, from: previousEntries, to: entries, crossfade: false) - strongSelf.enqueueTransition(transition) - - strongSelf.entries = entries - - if isFirstTime { - for theme in themes { - if let wallpaper = theme.settings?.first?.wallpaper, case let .file(file) = wallpaper { - let account = strongSelf.context.account - let accountManager = strongSelf.context.sharedContext.accountManager - let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation()) - if !FileManager.default.fileExists(atPath: path) { - let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in - let accountResource = account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), complete: false, fetch: true) - - let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file.file), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)) - let fetchedFullSizeDisposable = fetchedFullSize.start() - let fullSizeDisposable = accountResource.start(next: { next in - subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) - - if next.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedRead) { - accountManager.mediaBox.storeCachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), data: data) - } - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedFullSizeDisposable.dispose() - fullSizeDisposable.dispose() - } - } - let _ = accountFullSizeData.start() - } - } - } - } - })) - - self.switchThemeButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.animationContainerNode.layer.removeAnimation(forKey: "opacity") - strongSelf.animationContainerNode.alpha = 0.4 - } else { - strongSelf.animationContainerNode.alpha = 1.0 - strongSelf.animationContainerNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - - self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in - guard let self, let state = self.currentUniqueGiftChatThemesState, case .ready(true) = state.dataState else { - return - } - if case let .known(value) = offset, value < 100.0 { - self.uniqueGiftChatThemesContext.loadMore() - } - } - - self.updateCancelButton() - } - - deinit { - self.disposable.dispose() - } - - private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) { - self.enqueuedTransitions.append(transition) - - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - - private func dequeueTransition() { - guard let transition = self.enqueuedTransitions.first else { - return - } - self.enqueuedTransitions.remove(at: 0) - - var options = ListViewDeleteAndInsertOptions() - if self.initialized && transition.crossfade { - options.insert(.AnimateCrossfade) - } - options.insert(.Synchronous) - - var scrollToItem: ListViewScrollToItem? - if !self.initialized { - if let index = transition.entries.firstIndex(where: { entry in - return entry.chatTheme?.id == self.initiallySelectedTheme?.id - }) { - scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) - self.initialized = true - } - } - - self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in - }) - } - - private var skipButtonsUpdate = false - private func setChatTheme(_ chatTheme: ChatTheme?) { - self.animateCrossfade(animateIcon: true) - - self.skipButtonsUpdate = true - self.previewTheme?(chatTheme, self.isDarkAppearance) - self.selectedTheme = chatTheme - let _ = ensureThemeVisible(listNode: self.listNode, themeId: chatTheme?.id, animated: true) - - UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) { - self.updateButtons() - } - self.updateCancelButton() - self.skipButtonsUpdate = false - - self.themeSelectionsCount += 1 - if self.themeSelectionsCount == 2 { - self.maybePresentPreviewTooltip() - } - } - - private func updateButtons() { - let doneButtonTitle: String - var accentButtonTheme = true - var otherIsEnabled = false - if self.selectedTheme?.id == self.initiallySelectedTheme?.id { - otherIsEnabled = self.controller?.canResetWallpaper == true - doneButtonTitle = otherIsEnabled ? self.presentationData.strings.Conversation_Theme_SetNewPhotoWallpaper : self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper - accentButtonTheme = false - } else if self.selectedTheme?.id == nil && self.initiallySelectedTheme?.id != nil { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset - } else { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply - } - - let buttonTheme: SolidRoundedButtonTheme - if accentButtonTheme { - buttonTheme = SolidRoundedButtonTheme(theme: self.presentationData.theme) - } else { - buttonTheme = SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor) - } - UIView.performWithoutAnimation { - self.doneButton.title = doneButtonTitle - self.doneButton.font = accentButtonTheme ? .bold : .regular - } - self.doneButton.updateTheme(buttonTheme) - - self.otherButton.setTitle(self.presentationData.strings.Conversation_Theme_ResetWallpaper, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal) - self.otherButton.isHidden = !otherIsEnabled - self.textNode.isHidden = !accentButtonTheme || self.controller?.canResetWallpaper == false - - if let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - - private func updateCancelButton() { - var cancelButtonState: WebAppCancelButtonNode.State = .cancel - if self.selectedTheme?.id == self.initiallySelectedTheme?.id { - - } else if self.selectedTheme == nil && self.initiallySelectedTheme != nil { - cancelButtonState = .back - } else { - cancelButtonState = .back - } - self.cancelButtonNode.setState(cancelButtonState, animated: true) - } - - private var switchThemeIconAnimator: DisplayLinkAnimator? - func updatePresentationData(_ presentationData: PresentationData) { - guard !self.animatedOut else { - return - } - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - if let animatingCrossFade = self.animatingCrossFade { - Queue.mainQueue().after(!animatingCrossFade ? ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor() : 0.0, { - self.cancelButtonNode.setTheme(presentationData.theme, animated: true) - }) - } else { - self.cancelButtonNode.setTheme(presentationData.theme, animated: false) - } - - let previousIconColors = iconColors(theme: previousTheme) - let newIconColors = iconColors(theme: self.presentationData.theme) - - if !self.switchThemeButton.isUserInteractionEnabled { - Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) { - self.switchThemeIconAnimator = DisplayLinkAnimator(duration: ChatThemeScreen.themeCrossfadeDuration * UIView.animationDurationFactor(), from: 0.0, to: 1.0, update: { [weak self] value in - self?.animationNode.setColors(colors: interpolateColors(from: previousIconColors, to: newIconColors, fraction: value)) - }, completion: { [weak self] in - self?.switchThemeIconAnimator?.invalidate() - self?.switchThemeIconAnimator = nil - }) - - UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) { - self.updateButtons() - } - } - } else { - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: newIconColors) - if !self.skipButtonsUpdate { - self.updateButtons() - } - } - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.listNode.view.disablesInteractiveTransitionGestureRecognizer = true - } - - @objc func cancelButtonPressed() { - if self.cancelButtonNode.state == .back { - self.setChatTheme(self.initiallySelectedTheme) - } else { - self.cancel?() - } - } - - @objc func otherButtonPressed() { - if self.selectedTheme?.id != self.initiallySelectedTheme?.id { - self.setChatTheme(self.initiallySelectedTheme) - } else { - if self.controller?.canResetWallpaper == true { - self.controller?.resetWallpaper() - self.cancelButtonPressed() - } else { - self.cancelButtonPressed() - } - } - } - - func complete() { - let proceed = { - self.doneButton.isUserInteractionEnabled = false - self.completion?(self.selectedTheme) - } - if case let .gift(gift, _) = self.selectedTheme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId { - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: themePeerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer else { + guard let self, self.selectedTheme != chatTheme else { return } - let controller = giftThemeTransferAlertController( - context: self.context, - gift: uniqueGift, - previousPeer: peer, - commit: { - proceed() + self.setChatTheme(chatTheme) + } + let previousEntries = self.entries ?? [] + let transition = preparedTransition(context: component.context, action: action, from: previousEntries, to: entries, crossfade: crossfade) + self.enqueueTransition(transition) + + let isFirstTime = self.entries == nil + self.entries = entries + + if isFirstTime { + self.preloadWallpaperResources(themes: self.themes, context: component.context) + } + } + + private func preloadWallpaperResources(themes: [TelegramTheme], context: AccountContext) { + for theme in themes { + if let wallpaper = theme.settings?.first?.wallpaper, case let .file(file) = wallpaper { + let account = context.account + let accountManager = context.sharedContext.accountManager + let path = accountManager.mediaBox.cachedRepresentationCompletePath(file.file.resource.id, representation: CachedPreparedPatternWallpaperRepresentation()) + if !FileManager.default.fileExists(atPath: path) { + let accountFullSizeData = Signal<(Data?, Bool), NoError> { subscriber in + let accountResource = account.postbox.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), complete: false, fetch: true) + + let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: MediaResourceUserContentType(file: file.file), reference: .media(media: .standalone(media: file.file), resource: file.file.resource)) + let fetchedFullSizeDisposable = fetchedFullSize.start() + let fullSizeDisposable = accountResource.start(next: { next in + subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) + + if next.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedRead) { + accountManager.mediaBox.storeCachedResourceRepresentation(file.file.resource, representation: CachedPreparedPatternWallpaperRepresentation(), data: data) + } + }, error: subscriber.putError, completed: subscriber.putCompletion) + + return ActionDisposable { + fetchedFullSizeDisposable.dispose() + fullSizeDisposable.dispose() + } + } + let _ = accountFullSizeData.start() } - ) - self.controller?.present(controller, in: .window(.root)) - }) - } else { - proceed() - } - } - - func dimTapped() { - if self.selectedTheme?.id == self.initiallySelectedTheme?.id { - self.cancelButtonPressed() - } else { - let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in - if let self { - self.complete() - } - })], actionLayout: .horizontal, dismissOnOutsideTap: true) - self.present?(alertController) - } - } - - @objc func switchThemePressed() { - self.switchThemeButton.isUserInteractionEnabled = false - Queue.mainQueue().after(0.5) { - self.switchThemeButton.isUserInteractionEnabled = true - } - - self.animateCrossfade(animateIcon: false) - self.animationNode.setAnimation(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme)) - Queue.mainQueue().justDispatch { - self.animationNode.playOnce() - } - - let isDarkAppearance = !self.isDarkAppearance - self.previewTheme?(self.selectedTheme, isDarkAppearance) - self.isDarkAppearance = isDarkAppearance - - if isDarkAppearance { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone() - } else { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone() - } - } - - private var animatingCrossFade: Bool? - private func animateCrossfade(animateIcon: Bool) { - if animateIcon, let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.animationNode.frame - self.animationNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.animationNode.view) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - self.animatingCrossFade = animateIcon - Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) { - if let effectView = self.effectNode.view as? UIVisualEffectView { - UIView.animate(withDuration: ChatThemeScreen.themeCrossfadeDuration, delay: 0.0, options: .curveLinear) { - effectView.effect = UIBlurEffect(style: self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) - } completion: { _ in } } - - let previousColor = self.contentBackgroundNode.backgroundColor ?? .clear - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - self.contentBackgroundNode.layer.animate(from: previousColor.cgColor, to: (self.contentBackgroundNode.backgroundColor ?? .clear).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: ChatThemeScreen.themeCrossfadeDuration) - - self.animatingCrossFade = nil - } - - if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.contentContainerNode.frame - self.contentContainerNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.contentContainerNode.view) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) } - if !animateIcon, let snapshotView = self.otherButton.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.otherButton.frame - self.otherButton.view.superview?.insertSubview(snapshotView, aboveSubview: self.otherButton.view) + private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) { + self.enqueuedTransitions.append(transition) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: ChatThemeScreen.themeCrossfadeDuration, delay: ChatThemeScreen.themeCrossfadeDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - self.listNode.forEachVisibleItemNode { node in - if let node = node as? ThemeSettingsThemeItemIconNode { - node.crossfade() + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() } } - } - - private var animatedOut = false - func animateIn() { - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - let targetBounds = self.bounds - self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) - self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) - transition.animateView({ - self.bounds = targetBounds - self.dimNode.position = dimPosition - }) - } - - private var themeSelectionsCount = 0 - private var displayedPreviewTooltip = false - private func maybePresentPreviewTooltip() { - guard !self.displayedPreviewTooltip, !self.animatedOut else { - return + private func dequeueTransition() { + guard let transition = self.enqueuedTransitions.first else { + return + } + self.enqueuedTransitions.remove(at: 0) + + var options = ListViewDeleteAndInsertOptions() + if self.initialized && transition.crossfade { + options.insert(.AnimateCrossfade) + } + options.insert(.Synchronous) + + var scrollToItem: ListViewScrollToItem? + if !self.initialized { + if let index = transition.entries.firstIndex(where: { entry in + return entry.chatTheme?.id == self.component?.initiallySelectedTheme?.id + }) { + scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) + self.initialized = true + } + } + + self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in + }) } - let frame = self.switchThemeButton.view.convert(self.switchThemeButton.bounds, to: self.view) - let currentTimestamp = Int32(Date().timeIntervalSince1970) - - let isDark = self.presentationData.theme.overallDarkAppearance - - let signal: Signal<(Int32, Int32), NoError> - if isDark { - signal = ApplicationSpecificNotice.getChatSpecificThemeLightPreviewTip(accountManager: self.context.sharedContext.accountManager) - } else { - signal = ApplicationSpecificNotice.getChatSpecificThemeDarkPreviewTip(accountManager: self.context.sharedContext.accountManager) + private func setChatTheme(_ chatTheme: ChatTheme?) { + guard let component = self.component else { + return + } + + self.previewThemeChanged(chatTheme: chatTheme, dark: self.isDarkAppearance) + self.selectedTheme = chatTheme + self.rebuildEntries(crossfade: false) + let _ = ensureThemeVisible(listNode: self.listNode, themeId: chatTheme?.id, animated: true) + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: ChatThemeScreen.themeCrossfadeDuration, curve: .easeInOut))) + + self.themeSelectionsCount += 1 + if self.themeSelectionsCount == 2 { + self.maybePresentPreviewTooltip(component: component) + } } - let _ = (signal - |> deliverOnMainQueue).startStandalone(next: { [weak self] count, timestamp in - if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 { - strongSelf.displayedPreviewTooltip = true + private func previewThemeChanged(chatTheme: ChatTheme?, dark: Bool?) { + self.component?.previewTheme(chatTheme, dark) + self.listNode.forEachVisibleItemNode { node in + if let node = node as? ThemeSettingsThemeItemIconNode { + node.crossfade() + } + } + } + + private func closeOrBackPressed() { + if self.hasChanges() { + self.setChatTheme(self.component?.initiallySelectedTheme) + } else { + self.component?.cancel() + } + } + + private func resetWallpaperPressed() { + self.component?.resetWallpaper() + self.closeOrBackPressed() + } + + private func primaryPressed() { + switch self.primaryAction() { + case .chooseWallpaper: + self.component?.changeWallpaper() + case .resetTheme, .apply: + self.complete() + } + } + + private func complete() { + guard let component = self.component else { + return + } + let proceed = { [weak self] in + guard let self else { + return + } + self.isCompleting = true + self.state?.updated(transition: .immediate) + component.completion(self.selectedTheme) + } + if case let .gift(gift, _) = self.selectedTheme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId { + let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: themePeerId)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + let controller = giftThemeTransferAlertController( + context: component.context, + gift: uniqueGift, + previousPeer: peer, + commit: { + proceed() + } + ) + self.component?.presentInRoot(controller) + }) + } else { + proceed() + } + } + + func dimTapped() { + guard let component = self.component else { + return + } + if !self.hasChanges() { + self.closeOrBackPressed() + } else { + let alertController = textAlertController(context: component.context, updatedPresentationData: (component.presentationData, .single(component.presentationData)), title: nil, text: component.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: component.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: component.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in + self?.complete() + })], actionLayout: .horizontal, dismissOnOutsideTap: true) + component.present(alertController) + } + } + + private func switchThemePressed() { + guard self.isSwitchThemeEnabled, let component = self.component else { + return + } + self.isSwitchThemeEnabled = false + Queue.mainQueue().after(0.5) { [weak self] in + guard let self else { + return + } + self.isSwitchThemeEnabled = true + self.state?.updated(transition: .immediate) + } + + let isDarkAppearance = !self.isDarkAppearance + self.isDarkAppearance = isDarkAppearance + component.previewTheme(self.selectedTheme, isDarkAppearance) + self.rebuildEntries(crossfade: false) + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: ChatThemeScreen.themeCrossfadeDuration, curve: .easeInOut))) + Queue.mainQueue().justDispatch { [weak self] in + self?.switchThemePlayOnce.invoke(Void()) + } + + if isDarkAppearance { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: component.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone() + } else { + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: component.context.sharedContext.accountManager, count: 3, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone() + } + } + + private func maybePresentPreviewTooltip(component: ChatThemeSheetContentComponent) { + guard !self.displayedPreviewTooltip, !self.animatedOut, let switchThemeButtonView = self.switchThemeButton.view else { + return + } + + let frame = switchThemeButtonView.convert(switchThemeButtonView.bounds, to: self) + let currentTimestamp = Int32(Date().timeIntervalSince1970) + let isDark = component.presentationData.theme.overallDarkAppearance + + let signal: Signal<(Int32, Int32), NoError> + if isDark { + signal = ApplicationSpecificNotice.getChatSpecificThemeLightPreviewTip(accountManager: component.context.sharedContext.accountManager) + } else { + signal = ApplicationSpecificNotice.getChatSpecificThemeDarkPreviewTip(accountManager: component.context.sharedContext.accountManager) + } + + let _ = (signal + |> deliverOnMainQueue).startStandalone(next: { [weak self] count, timestamp in + guard let self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 else { + return + } + self.displayedPreviewTooltip = true - strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: .plain(text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _, _ in + component.present(TooltipScreen(account: component.context.account, sharedContext: component.context.sharedContext, text: .plain(text: isDark ? component.presentationData.strings.Conversation_Theme_PreviewLightShort : component.presentationData.strings.Conversation_Theme_PreviewDarkShort), style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _, _ in return .dismiss(consume: false) })) if isDark { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).startStandalone() + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeLightPreviewTip(accountManager: component.context.sharedContext.accountManager, timestamp: currentTimestamp).startStandalone() } else { - let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).startStandalone() + let _ = ApplicationSpecificNotice.incrementChatSpecificThemeDarkPreviewTip(accountManager: component.context.sharedContext.accountManager, timestamp: currentTimestamp).startStandalone() + } + }) + } + + func update(component: ChatThemeSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + self.state = state + let environmentValue = environment[EnvironmentType.self].value + self.environment = environmentValue + + self.setupIfNeeded(component: component) + + if let previousComponent, previousComponent.presentationData.theme !== component.presentationData.theme || previousComponent.presentationData.strings !== component.presentationData.strings { + self.rebuildEntries(crossfade: false) + } + + let width = availableSize.width + let topInset: CGFloat = 16.0 + let titleBarHeight: CGFloat = 44.0 + let sideInset: CGFloat = 16.0 + let buttonHeight: CGFloat = 52.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environmentValue.safeInsets.bottom, innerDiameter: buttonHeight, sideInset: 30.0) + let bottomInset: CGFloat = environmentValue.safeInsets.bottom.isZero ? 24.0 : environmentValue.safeInsets.bottom + 10.0 + + var contentHeight = topInset + + let leftButtonIconName = self.hasChanges() ? "Navigation/Back" : "Navigation/Close" + let leftButtonSize = self.leftButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: component.presentationData.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: leftButtonIconName, component: AnyComponent( + BundleIconComponent( + name: leftButtonIconName, + tintColor: component.presentationData.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in + self?.closeOrBackPressed() + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + if let leftButtonView = self.leftButton.view { + if leftButtonView.superview == nil { + self.addSubview(leftButtonView) + } + transition.setFrame(view: leftButtonView, frame: CGRect(origin: CGPoint(x: sideInset, y: topInset), size: leftButtonSize)) + } + + let switchButtonSize = self.switchThemeButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: component.presentationData.theme.overallDarkAppearance, + state: .glass, + isEnabled: self.isSwitchThemeEnabled, + component: AnyComponentWithIdentity(id: self.isDarkAppearance ? "night" : "day", component: AnyComponent( + LottieComponent( + content: LottieComponent.AppBundleContent(name: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun"), + color: component.presentationData.theme.chat.inputPanel.panelControlColor, + startingPosition: .end, + size: CGSize(width: 28.0, height: 28.0), + playOnce: self.switchThemePlayOnce + ) + )), + action: { [weak self] _ in + self?.switchThemePressed() + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + if let switchButtonView = self.switchThemeButton.view { + if switchButtonView.superview == nil { + self.addSubview(switchButtonView) + } + transition.setFrame(view: switchButtonView, frame: CGRect(origin: CGPoint(x: width - sideInset - switchButtonSize.width, y: topInset), size: switchButtonSize)) + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent(Text( + text: component.presentationData.strings.Conversation_Theme_Title, + font: Font.semibold(17.0), + color: component.presentationData.theme.actionSheet.primaryTextColor + )), + environment: {}, + containerSize: CGSize(width: width - 120.0, height: titleBarHeight) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - titleSize.width) * 0.5), y: topInset + floorToScreenPixels((titleBarHeight - titleSize.height) * 0.5)), size: titleSize)) + } + + contentHeight += titleBarHeight + 8.0 + + let listHeight: CGFloat = 120.0 + let listFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: width, height: listHeight)) + self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: listHeight, height: width) + self.listNode.position = CGPoint(x: listFrame.midX, y: listFrame.midY) + var listInsets = UIEdgeInsets() + listInsets.top += environmentValue.safeInsets.left + 12.0 + listInsets.bottom += environmentValue.safeInsets.right + 12.0 + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: listHeight, height: width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + contentHeight += listHeight + 12.0 + + let showSubtitle = self.primaryAction() != .chooseWallpaper && component.canResetWallpaper + if showSubtitle { + let subtitleSize = self.subtitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.presentationData.strings.Conversation_Theme_Subtitle(component.peerName).string, font: Font.regular(15.0), textColor: component.presentationData.theme.actionSheet.secondaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0 + )), + environment: {}, + containerSize: CGSize(width: width - 90.0, height: 100.0) + ) + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + transition.setAlpha(view: subtitleView, alpha: 1.0) + transition.setFrame(view: subtitleView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize)) + } + contentHeight += subtitleSize.height + 12.0 + } else if let subtitleView = self.subtitle.view { + transition.setAlpha(view: subtitleView, alpha: 0.0) + } + + let primaryAction = self.primaryAction() + let accentColor = component.presentationData.theme.actionSheet.controlAccentColor + let primaryTextColor: UIColor + let primaryBackground: ButtonComponent.Background + switch primaryAction { + case .chooseWallpaper: + primaryTextColor = accentColor + primaryBackground = ButtonComponent.Background( + style: .glass, + color: accentColor.withMultipliedAlpha(0.1), + foreground: accentColor, + pressedColor: accentColor.withMultipliedAlpha(0.2), + cornerRadius: 26.0 + ) + case .resetTheme, .apply: + primaryTextColor = component.presentationData.theme.list.itemCheckColors.foregroundColor + primaryBackground = ButtonComponent.Background( + style: .glass, + color: component.presentationData.theme.list.itemCheckColors.fillColor, + foreground: component.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 26.0 + ) + } + + let primaryButtonSize = self.primaryButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: primaryBackground, + content: AnyComponentWithIdentity( + id: AnyHashable(self.primaryButtonTitle(strings: component.presentationData.strings)), + component: AnyComponent(ButtonTextContentComponent( + text: self.primaryButtonTitle(strings: component.presentationData.strings), + badge: 0, + textColor: primaryTextColor, + badgeBackground: primaryTextColor, + badgeForeground: component.presentationData.theme.list.itemCheckColors.fillColor + )) + ), + isEnabled: !self.isCompleting, + displaysProgress: self.isCompleting, + action: { [weak self] in + self?.primaryPressed() + } + )), + environment: {}, + containerSize: CGSize(width: width - buttonInsets.left - buttonInsets.right, height: buttonHeight) + ) + if let primaryButtonView = self.primaryButton.view { + if primaryButtonView.superview == nil { + self.addSubview(primaryButtonView) + } + transition.setFrame(view: primaryButtonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: primaryButtonSize)) + } + contentHeight += primaryButtonSize.height + + let showResetWallpaper = component.canResetWallpaper && primaryAction == .chooseWallpaper + if showResetWallpaper { + contentHeight += 8.0 + let resetWallpaperButton: ComponentView + if let current = self.resetWallpaperButton { + resetWallpaperButton = current + } else { + resetWallpaperButton = ComponentView() + self.resetWallpaperButton = resetWallpaperButton + } + + let destructiveColor = component.presentationData.theme.actionSheet.destructiveActionTextColor + let resetButtonSize = resetWallpaperButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: destructiveColor.withMultipliedAlpha(0.1), + foreground: destructiveColor, + pressedColor: destructiveColor.withMultipliedAlpha(0.2) + ), + content: AnyComponentWithIdentity( + id: AnyHashable("resetWallpaper"), + component: AnyComponent(ButtonTextContentComponent( + text: component.presentationData.strings.Conversation_Theme_ResetWallpaper, + badge: 0, + textColor: destructiveColor, + badgeBackground: destructiveColor, + badgeForeground: component.presentationData.theme.actionSheet.itemBackgroundColor + )) + ), + action: { [weak self] in + self?.resetWallpaperPressed() + } + )), + environment: {}, + containerSize: CGSize(width: width - buttonInsets.left - buttonInsets.right, height: buttonHeight) + ) + if let resetButtonView = resetWallpaperButton.view { + if resetButtonView.superview == nil { + self.addSubview(resetButtonView) + } + transition.setAlpha(view: resetButtonView, alpha: 1.0) + transition.setFrame(view: resetButtonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: resetButtonSize)) + } + contentHeight += resetButtonSize.height + } else if let resetWallpaperButton = self.resetWallpaperButton { + self.resetWallpaperButton = nil + if let resetButtonView = resetWallpaperButton.view { + transition.setAlpha(view: resetButtonView, alpha: 0.0, completion: { _ in + resetButtonView.removeFromSuperview() + }) } } - }) + + contentHeight += bottomInset + + return CGSize(width: width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ChatThemeSheetScreenNode: ViewControllerTracingNode { + private let context: AccountContext + private var presentationData: PresentationData + private weak var controller: ChatThemeScreen? + + private let animatedEmojiStickers: [String: [StickerPackItem]] + private let initiallySelectedTheme: ChatTheme? + private let peerName: String + + private let hostView: ComponentHostView + private var containerLayout: (ContainerViewLayout, CGFloat)? + private var isDisplaying = false + private var animatedOut = false + + var present: ((ViewController) -> Void)? + var previewTheme: ((ChatTheme?, Bool?) -> Void)? + var completion: ((ChatTheme?) -> Void)? + var dismiss: (() -> Void)? + var cancel: (() -> Void)? + var passthroughHitTestImpl: ((CGPoint) -> UIView?)? + + init(context: AccountContext, presentationData: PresentationData, controller: ChatThemeScreen, animatedEmojiStickers: [String: [StickerPackItem]], initiallySelectedTheme: ChatTheme?, peerName: String) { + self.context = context + self.presentationData = presentationData + self.controller = controller + self.animatedEmojiStickers = animatedEmojiStickers + self.initiallySelectedTheme = initiallySelectedTheme + self.peerName = peerName + self.hostView = ComponentHostView() + + super.init() + + self.backgroundColor = nil + self.isOpaque = false + self.view.addSubview(self.hostView) + } + + private func update(transition: ComponentTransition) { + guard let (layout, navigationBarHeight) = self.containerLayout else { + return + } + + let environment = ViewControllerComponentContainer.Environment( + statusBarHeight: layout.statusBarHeight ?? 0.0, + navigationHeight: navigationBarHeight, + safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + additionalInsets: layout.additionalInsets, + inputHeight: layout.inputHeight ?? 0.0, + metrics: layout.metrics, + deviceMetrics: layout.deviceMetrics, + orientation: layout.metrics.orientation, + isVisible: self.isDisplaying, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + controller: { [weak self] in + return self?.controller + } + ) + + let component = ChatThemeScreenComponent( + context: self.context, + presentationData: self.presentationData, + animatedEmojiStickers: self.animatedEmojiStickers, + initiallySelectedTheme: self.initiallySelectedTheme, + peerName: self.peerName, + canResetWallpaper: self.controller?.canResetWallpaper == true, + present: { [weak self] controller in + self?.present?(controller) + }, + presentInRoot: { [weak self] controller in + self?.controller?.present(controller, in: .window(.root)) + }, + previewTheme: { [weak self] chatTheme, dark in + self?.previewTheme?(chatTheme, dark) + }, + changeWallpaper: { [weak self] in + self?.controller?.changeWallpaper() + }, + resetWallpaper: { [weak self] in + self?.controller?.resetWallpaper() + }, + completion: { [weak self] chatTheme in + self?.completion?(chatTheme) + }, + cancel: { [weak self] in + self?.cancel?() + } + ) + + let _ = self.hostView.update( + transition: transition, + component: AnyComponent(component), + environment: { + environment + }, + containerSize: layout.size + ) + transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size)) + } + + func updatePresentationData(_ presentationData: PresentationData) { + guard !self.animatedOut else { + return + } + self.presentationData = presentationData + self.update(transition: .immediate) + } + + func animateIn() { + self.isDisplaying = true + self.update(transition: ComponentTransition(animation: .none).withUserData(ViewControllerComponentContainer.AnimateInTransition())) } func animateOut(completion: (() -> Void)? = nil) { self.animatedOut = true - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - self.wrappingScrollNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.dismiss?() + if let rootView = self.hostView.componentView as? ChatThemeScreenComponent.View { + rootView.animateOut { [weak self] in + self?.dismiss?() completion?() } - }) + } else { + self.dismiss?() + completion?() + } + } + + func dimTapped() { + if let rootView = self.hostView.componentView as? ChatThemeScreenComponent.View { + rootView.dimTapped() + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = (layout, navigationBarHeight) + self.update(transition: ComponentTransition(transition)) } - var passthroughHitTestImpl: ((CGPoint) -> UIView?)? override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { var presentingAlertController = false self.controller?.forEachController({ c in @@ -1537,108 +1962,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega return true }) - if !presentingAlertController && self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - if let result = self.passthroughHitTestImpl?(point) { - return result - } else { - return nil - } + if !presentingAlertController && self.bounds.contains(point), let rootView = self.hostView.componentView as? ChatThemeScreenComponent.View, !rootView.containsContent(point: point) { + if let result = self.passthroughHitTestImpl?(point) { + return result + } else { + return nil } } return super.hitTest(point, with: event) } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - let cleanInsets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - - let bottomInset: CGFloat = 10.0 + cleanInsets.bottom - let titleHeight: CGFloat = 54.0 - var contentHeight = titleHeight + bottomInset + 168.0 - if self.controller?.canResetWallpaper == true { - contentHeight += 50.0 - } - if cleanInsets.bottom.isZero { - insets.bottom += 14.0 - contentHeight += 14.0 - } - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0 + UIScreenPixel), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let switchThemeSize = CGSize(width: 44.0, height: 44.0) - let switchThemeFrame = CGRect(origin: CGPoint(x: contentFrame.width - switchThemeSize.width - 3.0, y: 6.0), size: switchThemeSize) - transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame) - transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0)) - transition.updateFrameAsPositionAndBounds(node: self.animationNode, frame: CGRect(origin: .zero, size: self.animationContainerNode.frame.size)) - - let cancelSize = self.cancelButtonNode.calculateSizeThatFits(CGSize(width: layout.size.width, height: 56.0)) - let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: cancelSize) - transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) - - let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: layout.intrinsicInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) - let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInsets.left - buttonInsets.right, transition: transition) - var doneY = contentHeight - doneButtonHeight - 2.0 - insets.bottom - if self.controller?.canResetWallpaper == true { - doneY = contentHeight - doneButtonHeight - 52.0 - insets.bottom - } - transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInsets.left, y: doneY, width: contentFrame.width, height: doneButtonHeight)) - - let otherButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) - self.otherButton.frame = CGRect(origin: CGPoint(x: floor((contentFrame.width - otherButtonSize.width) / 2.0), y: contentHeight - otherButtonSize.height - insets.bottom - 15.0), size: otherButtonSize) - - let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight)) - let textFrame: CGRect - if self.controller?.canResetWallpaper == true { - textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 17.0), size: textSize) - } else { - textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 15.0), size: textSize) - } - transition.updateFrame(node: self.textNode, frame: textFrame) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) - transition.updateFrame(node: self.buttonsContentContainerNode, frame: contentContainerFrame) - - var listInsets = UIEdgeInsets() - listInsets.top += layout.safeInsets.left + 12.0 - listInsets.bottom += layout.safeInsets.right + 12.0 - - let contentSize = CGSize(width: contentFrame.width, height: 120.0) - - self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width) - self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight - 4.0) - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } } diff --git a/submodules/TelegramUI/Components/ChatTimerScreen/BUILD b/submodules/TelegramUI/Components/ChatTimerScreen/BUILD index a076943333..e7b96cf57c 100644 --- a/submodules/TelegramUI/Components/ChatTimerScreen/BUILD +++ b/submodules/TelegramUI/Components/ChatTimerScreen/BUILD @@ -11,15 +11,18 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/TelegramStringFormatting:TelegramStringFormatting", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/SheetComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift index f784eb74b2..8f5ba7ba58 100644 --- a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift +++ b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift @@ -1,14 +1,18 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import TelegramCore import SwiftSignalKit import AccountContext -import SolidRoundedButtonNode import TelegramPresentationData import PresentationDataUtils import TelegramStringFormatting +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import ButtonComponent +import BundleIconComponent +import GlassBarButtonComponent public enum ChatTimerScreenStyle { case `default` @@ -21,104 +25,10 @@ public enum ChatTimerScreenMode { case mute } -public final class ChatTimerScreen: ViewController { - private var controllerNode: ChatTimerScreenNode { - return self.displayNode as! ChatTimerScreenNode - } - - private var animatedIn = false - - private let context: AccountContext - private let style: ChatTimerScreenStyle - private let mode: ChatTimerScreenMode - private let currentTime: Int32? - private let dismissByTapOutside: Bool - private let completion: (Int32) -> Void - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode = .sendTimer, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { - self.context = context - self.style = style - self.mode = mode - self.currentTime = currentTime - self.dismissByTapOutside = dismissByTapOutside - self.completion = completion - - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - - self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.presentationData = presentationData - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - - self.statusBar.statusBarStyle = .Ignore - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDataDisposable?.dispose() - } - - override public func loadDisplayNode() { - self.displayNode = ChatTimerScreenNode(context: self.context, presentationData: presentationData, style: self.style, mode: self.mode, currentTime: self.currentTime, dismissByTapOutside: self.dismissByTapOutside) - self.controllerNode.completion = { [weak self] time in - guard let strongSelf = self else { - return - } - strongSelf.completion(time) - strongSelf.dismiss() - } - self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - self?.dismiss() - } - } - - override public func loadView() { - super.loadView() - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() - } - } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.controllerNode.animateOut(completion: completion) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } -} - private protocol TimerPickerView: UIView { - } -private class TimerCustomPickerView: UIPickerView, TimerPickerView { +private final class TimerCustomPickerView: UIPickerView, TimerPickerView { var selectorColor: UIColor? = nil { didSet { for subview in self.subviews { @@ -128,31 +38,27 @@ private class TimerCustomPickerView: UIPickerView, TimerPickerView { } } } - + override func didAddSubview(_ subview: UIView) { super.didAddSubview(subview) - - if let selectorColor = self.selectorColor { - if subview.bounds.height <= 1.0 { - subview.backgroundColor = selectorColor - } + + if let selectorColor = self.selectorColor, subview.bounds.height <= 1.0 { + subview.backgroundColor = selectorColor } } - + override func didMoveToWindow() { super.didMoveToWindow() - + if let selectorColor = self.selectorColor { - for subview in self.subviews { - if subview.bounds.height <= 1.0 { - subview.backgroundColor = selectorColor - } + for subview in self.subviews where subview.bounds.height <= 1.0 { + subview.backgroundColor = selectorColor } } } } -private class TimerDatePickerView: UIDatePicker, TimerPickerView { +private final class TimerDatePickerView: UIDatePicker, TimerPickerView { var selectorColor: UIColor? = nil { didSet { for subview in self.subviews { @@ -162,25 +68,21 @@ private class TimerDatePickerView: UIDatePicker, TimerPickerView { } } } - + override func didAddSubview(_ subview: UIView) { super.didAddSubview(subview) - - if let selectorColor = self.selectorColor { - if subview.bounds.height <= 1.0 { - subview.backgroundColor = selectorColor - } + + if let selectorColor = self.selectorColor, subview.bounds.height <= 1.0 { + subview.backgroundColor = selectorColor } } - + override func didMoveToWindow() { super.didMoveToWindow() - + if let selectorColor = self.selectorColor { - for subview in self.subviews { - if subview.bounds.height <= 1.0 { - subview.backgroundColor = selectorColor - } + for subview in self.subviews where subview.bounds.height <= 1.0 { + subview.backgroundColor = selectorColor } } } @@ -189,22 +91,22 @@ private class TimerDatePickerView: UIDatePicker, TimerPickerView { private let digitsCharacterSet = CharacterSet(charactersIn: "0123456789") private let nondigitsCharacterSet = CharacterSet(charactersIn: "0123456789").inverted -private class TimerPickerItemView: UIView { +private final class TimerPickerItemView: UIView { let valueLabel = UILabel() let unitLabel = UILabel() - + var textColor: UIColor? = nil { didSet { self.valueLabel.textColor = self.textColor self.unitLabel.textColor = self.textColor } } - + var value: (Int32, String)? { didSet { if let (value, string) = self.value { let components = string.components(separatedBy: " ") - if value == viewOnceTimeout { + if value == viewOnceTimeout || string.rangeOfCharacter(from: digitsCharacterSet) == nil { self.valueLabel.text = string self.unitLabel.text = "" } else if components.count > 1 { @@ -215,46 +117,64 @@ private class TimerPickerItemView: UIView { self.unitLabel.text = string.trimmingCharacters(in: digitsCharacterSet) } } - + self.setNeedsLayout() } } - + override init(frame: CGRect) { self.valueLabel.backgroundColor = nil self.valueLabel.isOpaque = false self.valueLabel.font = Font.regular(24.0) - + self.unitLabel.backgroundColor = nil self.unitLabel.isOpaque = false self.unitLabel.font = Font.medium(16.0) - + super.init(frame: frame) - + self.addSubview(self.valueLabel) self.addSubview(self.unitLabel) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() - + self.valueLabel.sizeToFit() self.unitLabel.sizeToFit() - - if let (value, _) = self.value, value == viewOnceTimeout { - self.valueLabel.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.frame.width - self.valueLabel.frame.size.width) / 2.0), y: floor((self.frame.height - self.valueLabel.frame.height) / 2.0)), size: self.valueLabel.frame.size) + + if self.unitLabel.text?.isEmpty ?? false { + self.valueLabel.frame = CGRect( + origin: CGPoint( + x: floorToScreenPixels((self.frame.width - self.valueLabel.frame.size.width) / 2.0), + y: floor((self.frame.height - self.valueLabel.frame.height) / 2.0) + ), + size: self.valueLabel.frame.size + ) } else { - self.valueLabel.frame = CGRect(origin: CGPoint(x: self.frame.width / 2.0 - 28.0 - self.valueLabel.frame.size.width, y: floor((self.frame.height - self.valueLabel.frame.height) / 2.0)), size: self.valueLabel.frame.size) - self.unitLabel.frame = CGRect(origin: CGPoint(x: self.frame.width / 2.0 - 20.0, y: floor((self.frame.height - self.unitLabel.frame.height) / 2.0) + 2.0), size: self.unitLabel.frame.size) + self.valueLabel.frame = CGRect( + origin: CGPoint( + x: self.frame.width / 2.0 - 28.0 - self.valueLabel.frame.size.width, + y: floor((self.frame.height - self.valueLabel.frame.height) / 2.0) + ), + size: self.valueLabel.frame.size + ) + self.unitLabel.frame = CGRect( + origin: CGPoint( + x: self.frame.width / 2.0 - 20.0, + y: floor((self.frame.height - self.unitLabel.frame.height) / 2.0) + 2.0 + ), + size: self.unitLabel.frame.size + ) } } } -private var timerValues: [Int32] = { +private let timerValues: [Int32] = { var values: [Int32] = [] for i in 1 ..< 20 { values.append(Int32(i)) @@ -265,514 +185,1027 @@ private var timerValues: [Int32] = { return values }() -class ChatTimerScreenNode: ViewControllerTracingNode, ASScrollViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate { - private let context: AccountContext - private let controllerStyle: ChatTimerScreenStyle - private var presentationData: PresentationData - private let dismissByTapOutside: Bool - private let mode: ChatTimerScreenMode - - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let effectNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let textNode: ImmediateTextNode - private let cancelButton: HighlightableButtonNode - private let doneButton: SolidRoundedButtonNode - - private let disableButton: HighlightableButtonNode - private let disableButtonTitle: ImmediateTextNode - - private var initialTime: Int32? - private var pickerView: TimerPickerView? - - private let autoremoveTimerValues: [Int32] - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var completion: ((Int32) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode, currentTime: Int32?, dismissByTapOutside: Bool) { - self.context = context - self.controllerStyle = style - self.presentationData = presentationData - self.dismissByTapOutside = dismissByTapOutside - self.mode = mode - self.initialTime = currentTime - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false +private let autoremoveTimerValues: [Int32] = [ + 1 * 24 * 60 * 60 as Int32, + 2 * 24 * 60 * 60 as Int32, + 3 * 24 * 60 * 60 as Int32, + 4 * 24 * 60 * 60 as Int32, + 5 * 24 * 60 * 60 as Int32, + 6 * 24 * 60 * 60 as Int32, + 1 * 7 * 24 * 60 * 60 as Int32, + 2 * 7 * 24 * 60 * 60 as Int32, + 3 * 7 * 24 * 60 * 60 as Int32, + 1 * 31 * 24 * 60 * 60 as Int32, + 2 * 30 * 24 * 60 * 60 as Int32, + 3 * 31 * 24 * 60 * 60 as Int32, + 4 * 30 * 24 * 60 * 60 as Int32, + 5 * 31 * 24 * 60 * 60 as Int32, + 6 * 30 * 24 * 60 * 60 as Int32, + 365 * 24 * 60 * 60 as Int32 +] - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 - - let backgroundColor: UIColor - let textColor: UIColor - let accentColor: UIColor - let blurStyle: UIBlurEffect.Style - switch style { +public final class ChatTimerPickerContentComponent: Component { + public typealias EnvironmentType = Empty + + public final class LeadingAction: Equatable { + public enum Icon { + case close + case back + } + + public let icon: Icon + public let action: () -> Void + + public init( + icon: Icon, + action: @escaping () -> Void + ) { + self.icon = icon + self.action = action + } + + public static func ==(lhs: LeadingAction, rhs: LeadingAction) -> Bool { + return lhs === rhs + } + } + + public let configuration: ChatTimerScreen.Configuration + public let theme: PresentationTheme + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let safeInsets: UIEdgeInsets + public let leadingAction: LeadingAction? + public let completion: (Int32?) -> Void + + public init( + configuration: ChatTimerScreen.Configuration, + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + safeInsets: UIEdgeInsets, + leadingAction: LeadingAction?, + completion: @escaping (Int32?) -> Void + ) { + self.configuration = configuration + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.safeInsets = safeInsets + self.leadingAction = leadingAction + self.completion = completion + } + + public static func ==(lhs: ChatTimerPickerContentComponent, rhs: ChatTimerPickerContentComponent) -> Bool { + return false + } + + public final class View: UIView, UIPickerViewDataSource, UIPickerViewDelegate { + private struct PickerConfigurationSignature: Equatable { + enum Picker: Equatable { + case timeOfDay + case date + case dateTime + case fixedValues(values: [Int32], selectionStrategy: ChatTimerScreen.Configuration.FixedSelectionStrategy) + } + + let picker: Picker + let currentValue: Int32? + let minimumDate: Date? + let maximumDate: Date? + let pickerValueMapping: ChatTimerScreen.Configuration.PickerValueMapping + } + + private let leadingButton = ComponentView() + private let title = ComponentView() + private let primaryButton = ComponentView() + private let secondaryButton = ComponentView() + + private var component: ChatTimerPickerContentComponent? + private weak var state: EmptyComponentState? + + private var pickerView: TimerPickerView? + private var isCompleting = false + + public override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func pickerTextColor(configuration: ChatTimerScreen.Configuration, theme: PresentationTheme) -> UIColor { + if let pickerTextColor = configuration.pickerTextColor { + return pickerTextColor(theme) + } + + switch configuration.style { case .default: - backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - textColor = self.presentationData.theme.actionSheet.primaryTextColor - accentColor = self.presentationData.theme.actionSheet.controlAccentColor - blurStyle = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark + return theme.actionSheet.primaryTextColor case .media: - backgroundColor = UIColor(rgb: 0x1c1c1e) - textColor = .white - accentColor = self.presentationData.theme.actionSheet.controlAccentColor - blurStyle = .dark - } - - self.effectNode = ASDisplayNode(viewBlock: { - return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) - }) - - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor - - let title: String - switch self.mode { - case .sendTimer: - title = self.presentationData.strings.Conversation_Timer_Title - case .autoremove: - title = self.presentationData.strings.Conversation_DeleteTimer_SetupTitle - case .mute: - title = self.presentationData.strings.Conversation_Mute_SetupTitle - } - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor) - - self.textNode = ImmediateTextNode() - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) - - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, isShimmering: false) - self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send - - self.disableButton = HighlightableButtonNode() - self.disableButtonTitle = ImmediateTextNode() - self.disableButton.addSubnode(self.disableButtonTitle) - self.disableButtonTitle.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_DeleteTimer_Disable, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemAccentColor) - self.disableButton.isHidden = true - - switch self.mode { - case .autoremove: - if self.initialTime != nil { - self.disableButton.isHidden = false + return .white } - default: - break } - - self.autoremoveTimerValues = [ - 1 * 24 * 60 * 60 as Int32, - 2 * 24 * 60 * 60 as Int32, - 3 * 24 * 60 * 60 as Int32, - 4 * 24 * 60 * 60 as Int32, - 5 * 24 * 60 * 60 as Int32, - 6 * 24 * 60 * 60 as Int32, - 1 * 7 * 24 * 60 * 60 as Int32, - 2 * 7 * 24 * 60 * 60 as Int32, - 3 * 7 * 24 * 60 * 60 as Int32, - 1 * 31 * 24 * 60 * 60 as Int32, - 2 * 30 * 24 * 60 * 60 as Int32, - 3 * 31 * 24 * 60 * 60 as Int32, - 4 * 30 * 24 * 60 * 60 as Int32, - 5 * 31 * 24 * 60 * 60 as Int32, - 6 * 30 * 24 * 60 * 60 as Int32, - 365 * 24 * 60 * 60 as Int32 - ] - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - - self.backgroundNode.addSubnode(self.effectNode) - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.cancelButton) - self.contentContainerNode.addSubnode(self.doneButton) - self.contentContainerNode.addSubnode(self.disableButton) - - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.doneButton.pressed = { [weak self] in - if let strongSelf = self, let pickerView = strongSelf.pickerView { - strongSelf.doneButton.isUserInteractionEnabled = false - if let pickerView = pickerView as? TimerCustomPickerView { - switch strongSelf.mode { - case .sendTimer: - let row = pickerView.selectedRow(inComponent: 0) - let value: Int32 - if row == 0 { - value = viewOnceTimeout - } else { - value = timerValues[row - 1] - } - strongSelf.completion?(value) - case .autoremove: - let timeInterval = strongSelf.autoremoveTimerValues[pickerView.selectedRow(inComponent: 0)] - strongSelf.completion?(Int32(timeInterval)) - case .mute: - break + + private func pickerConfigurationSignature(_ configuration: ChatTimerScreen.Configuration) -> PickerConfigurationSignature { + let picker: PickerConfigurationSignature.Picker + switch configuration.picker { + case .timeOfDay: + picker = .timeOfDay + case .date: + picker = .date + case .dateTime: + picker = .dateTime + case let .fixedValues(values, selectionStrategy, _): + picker = .fixedValues(values: values, selectionStrategy: selectionStrategy) + } + return PickerConfigurationSignature( + picker: picker, + currentValue: configuration.currentValue, + minimumDate: configuration.minimumDate, + maximumDate: configuration.maximumDate, + pickerValueMapping: configuration.pickerValueMapping + ) + } + + private func mapPickerTimestamp(_ timestamp: Int32, mapping: ChatTimerScreen.Configuration.PickerValueMapping) -> Int32 { + switch mapping { + case .rawTimestamp: + return timestamp + case .roundDateToDaysUTC: + return roundDateToDays(timestamp) + case .secondsFromMidnightGMT: + return timestamp + } + } + + private func selectedValue() -> Int32? { + guard let component = self.component, let pickerView = self.pickerView else { + return nil + } + + switch component.configuration.picker { + case let .fixedValues(values, _, _): + guard let pickerView = pickerView as? TimerCustomPickerView else { + return nil + } + let row = pickerView.selectedRow(inComponent: 0) + guard row >= 0, row < values.count else { + return nil + } + return values[row] + case .timeOfDay, .date, .dateTime: + guard let pickerView = pickerView as? TimerDatePickerView else { + return nil + } + return self.mapPickerTimestamp(Int32(pickerView.date.timeIntervalSince1970), mapping: component.configuration.pickerValueMapping) + } + } + + private func fixedValueSelectionIndex( + values: [Int32], + selectedValue: Int32, + strategy: ChatTimerScreen.Configuration.FixedSelectionStrategy + ) -> Int { + switch strategy { + case .exact: + return values.firstIndex(of: selectedValue) ?? 0 + case .closestLowerOrEqual: + var index = 0 + for i in 0 ..< values.count { + if values[i] <= selectedValue { + index = i } - } else if let pickerView = pickerView as? TimerDatePickerView { - switch strongSelf.mode { - case .mute: - let timeInterval = max(0, Int32(pickerView.date.timeIntervalSince1970) - Int32(Date().timeIntervalSince1970)) - strongSelf.completion?(timeInterval) - default: + } + return index + case .firstGreaterOrEqual: + var index = max(0, values.count - 1) + for i in 0 ..< values.count { + if selectedValue <= values[i] { + index = i break } } + return index } } - - self.disableButton.addTarget(self, action: #selector(self.disableButtonPressed), forControlEvents: .touchUpInside) - - self.setupPickerView(currentTime: currentTime) - } - - @objc private func disableButtonPressed() { - self.completion?(0) - } - - func setupPickerView(currentTime: Int32? = nil) { - if let pickerView = self.pickerView { - pickerView.removeFromSuperview() - } - - switch self.mode { - case .sendTimer: - let pickerView = TimerCustomPickerView() - pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18) - pickerView.dataSource = self - pickerView.delegate = self - - self.contentContainerNode.view.addSubview(pickerView) - self.pickerView = pickerView - case .autoremove: - let pickerView = TimerCustomPickerView() - pickerView.dataSource = self - pickerView.delegate = self - - pickerView.selectorColor = self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.18) - - self.contentContainerNode.view.addSubview(pickerView) - self.pickerView = pickerView - - if let value = self.initialTime { - var selectedRowIndex = 0 - for i in 0 ..< self.autoremoveTimerValues.count { - if self.autoremoveTimerValues[i] <= value { - selectedRowIndex = i - } + + private func setupPickerView(configuration: ChatTimerScreen.Configuration, theme: PresentationTheme, strings: PresentationStrings) { + let previousSelectedValue = self.selectedValue() + let previousDate = (self.pickerView as? TimerDatePickerView)?.date + + self.pickerView?.removeFromSuperview() + + switch configuration.picker { + case let .fixedValues(values, selectionStrategy, _): + let pickerView = TimerCustomPickerView() + pickerView.dataSource = self + pickerView.delegate = self + pickerView.selectorColor = self.pickerTextColor(configuration: configuration, theme: theme).withMultipliedAlpha(0.18) + self.addSubview(pickerView) + self.pickerView = pickerView + + if let selectedValue = previousSelectedValue ?? configuration.currentValue { + let index = self.fixedValueSelectionIndex(values: values, selectedValue: selectedValue, strategy: selectionStrategy) + pickerView.selectRow(index, inComponent: 0, animated: false) } - - pickerView.selectRow(selectedRowIndex, inComponent: 0, animated: false) - } - case .mute: - let pickerView = TimerDatePickerView() - pickerView.locale = localeWithStrings(self.presentationData.strings) - pickerView.datePickerMode = .dateAndTime - pickerView.minimumDate = Date() - if #available(iOS 13.4, *) { - pickerView.preferredDatePickerStyle = .wheels - } - pickerView.setValue(self.presentationData.theme.list.itemPrimaryTextColor, forKey: "textColor") - pickerView.setValue(false, forKey: "highlightsToday") - pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18) - pickerView.addTarget(self, action: #selector(self.dataPickerChanged), for: .valueChanged) - - self.contentContainerNode.view.addSubview(pickerView) - self.pickerView = pickerView - } - } - - @objc private func dataPickerChanged() { - if let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - - func numberOfComponents(in pickerView: UIPickerView) -> Int { - switch self.mode { - case .sendTimer: - return 1 - case .autoremove: - return 1 - case .mute: - return 0 - } - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - switch self.mode { - case .sendTimer: - return timerValues.count + 1 - case .autoremove: - return self.autoremoveTimerValues.count - case .mute: - return 0 - } - } - - func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { - switch self.mode { - case .sendTimer: - if row == 0 { - let string = self.presentationData.strings.MediaPicker_Timer_ViewOnce - if let view = view as? TimerPickerItemView { - view.value = (viewOnceTimeout, string) - return view + case .timeOfDay: + let pickerView = TimerDatePickerView() + pickerView.datePickerMode = .time + pickerView.timeZone = TimeZone(secondsFromGMT: 0) + pickerView.locale = Locale.current + if #available(iOS 13.4, *) { + pickerView.preferredDatePickerStyle = .wheels } - - let view = TimerPickerItemView() - view.value = (viewOnceTimeout, string) - view.textColor = .white - return view + pickerView.setValue(self.pickerTextColor(configuration: configuration, theme: theme), forKey: "textColor") + pickerView.selectorColor = self.pickerTextColor(configuration: configuration, theme: theme).withMultipliedAlpha(0.18) + pickerView.addTarget(self, action: #selector(self.datePickerChanged), for: .valueChanged) + let initialTimestamp: Int32 + if let currentValue = configuration.currentValue { + initialTimestamp = self.mapPickerTimestamp(currentValue, mapping: configuration.pickerValueMapping) + } else { + initialTimestamp = 0 + } + let date = previousDate ?? Date(timeIntervalSince1970: Double(initialTimestamp)) + pickerView.date = date + self.addSubview(pickerView) + self.pickerView = pickerView + case .date: + let pickerView = TimerDatePickerView() + pickerView.datePickerMode = .date + pickerView.timeZone = TimeZone(secondsFromGMT: 0) + pickerView.locale = localeWithStrings(strings) + if #available(iOS 13.4, *) { + pickerView.preferredDatePickerStyle = .wheels + } + pickerView.minimumDate = configuration.minimumDate + pickerView.maximumDate = configuration.maximumDate ?? Date(timeIntervalSince1970: Double(Int32.max - 1)) + pickerView.setValue(self.pickerTextColor(configuration: configuration, theme: theme), forKey: "textColor") + pickerView.selectorColor = self.pickerTextColor(configuration: configuration, theme: theme).withMultipliedAlpha(0.18) + pickerView.addTarget(self, action: #selector(self.datePickerChanged), for: .valueChanged) + let initialTimestamp: Int32 + if let currentValue = configuration.currentValue { + initialTimestamp = self.mapPickerTimestamp(currentValue, mapping: configuration.pickerValueMapping) + } else { + initialTimestamp = Int32(Date().timeIntervalSince1970) + } + var initialDate = previousDate ?? Date(timeIntervalSince1970: Double(initialTimestamp)) + if let minimumDate = pickerView.minimumDate, initialDate < minimumDate { + initialDate = minimumDate + } + if let maximumDate = pickerView.maximumDate, initialDate > maximumDate { + initialDate = maximumDate + } + pickerView.date = initialDate + self.addSubview(pickerView) + self.pickerView = pickerView + case .dateTime: + let pickerView = TimerDatePickerView() + pickerView.datePickerMode = .dateAndTime + pickerView.locale = localeWithStrings(strings) + pickerView.minimumDate = configuration.minimumDate ?? Date() + pickerView.maximumDate = configuration.maximumDate + if #available(iOS 13.4, *) { + pickerView.preferredDatePickerStyle = .wheels + } + pickerView.setValue(self.pickerTextColor(configuration: configuration, theme: theme), forKey: "textColor") + pickerView.setValue(false, forKey: "highlightsToday") + pickerView.selectorColor = self.pickerTextColor(configuration: configuration, theme: theme).withMultipliedAlpha(0.18) + pickerView.addTarget(self, action: #selector(self.datePickerChanged), for: .valueChanged) + + var date = previousDate ?? configuration.currentValue.flatMap { Date(timeIntervalSince1970: Double($0)) } ?? Date() + if let minimumDate = pickerView.minimumDate, date < minimumDate { + date = minimumDate + } + if let maximumDate = pickerView.maximumDate, date > maximumDate { + date = maximumDate + } + pickerView.date = date + self.addSubview(pickerView) + self.pickerView = pickerView + } + } + + @objc private func datePickerChanged() { + self.state?.updated(transition: .immediate) + } + + private func complete(selectedValue: Int32?) { + guard !self.isCompleting else { + return + } + self.isCompleting = true + + let transformedValue: Int32? + if let component = self.component { + transformedValue = component.configuration.completionValueTransform(selectedValue) } else { - let value = timerValues[row - 1] - let string = timeIntervalString(strings: self.presentationData.strings, value: value) - if let view = view as? TimerPickerItemView { - view.value = (value, string) - return view - } - - let view = TimerPickerItemView() - view.value = (value, string) - view.textColor = .white - return view + transformedValue = nil } - case .autoremove: + self.component?.completion(transformedValue) + } + + public func update( + component: ChatTimerPickerContentComponent, + availableSize: CGSize, + state: EmptyComponentState, + environment: Environment, + transition: ComponentTransition + ) -> CGSize { + let previousComponent = self.component + let themeUpdated = previousComponent?.theme !== component.theme + let stringsUpdated = previousComponent?.strings !== component.strings + let pickerConfigurationUpdated: Bool + if let previousConfiguration = previousComponent?.configuration { + pickerConfigurationUpdated = self.pickerConfigurationSignature(previousConfiguration) != self.pickerConfigurationSignature(component.configuration) + } else { + pickerConfigurationUpdated = true + } + + self.component = component + self.state = state + + if self.pickerView == nil || themeUpdated || stringsUpdated || pickerConfigurationUpdated { + self.setupPickerView(configuration: component.configuration, theme: component.theme, strings: component.strings) + } + + let titleColor: UIColor + switch component.configuration.style { + case .default: + titleColor = component.theme.actionSheet.primaryTextColor + case .media: + titleColor = .white + } + + let barButtonSize = CGSize(width: 44.0, height: 44.0) + if let leadingAction = component.leadingAction { + let iconName: String + switch leadingAction.icon { + case .close: + iconName = "Navigation/Close" + case .back: + iconName = "Navigation/Back" + } + let leadingButtonSize = self.leadingButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: nil, + isDark: component.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: iconName, component: AnyComponent( + BundleIconComponent( + name: iconName, + tintColor: component.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in + self?.component?.leadingAction?.action() + } + ) + ), + environment: {}, + containerSize: barButtonSize + ) + if let leadingButtonView = self.leadingButton.view { + if leadingButtonView.superview == nil { + self.addSubview(leadingButtonView) + } + transition.setFrame(view: leadingButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: leadingButtonSize)) + } + } else if let leadingButtonView = self.leadingButton.view, leadingButtonView.superview != nil { + leadingButtonView.removeFromSuperview() + } + + let titleText = component.configuration.title(component.strings) + if let titleText, !titleText.isEmpty { + let titleWidth = availableSize.width - (component.leadingAction != nil ? 120.0 : 60.0) + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + Text(text: titleText, font: Font.semibold(17.0), color: titleColor) + ), + environment: {}, + containerSize: CGSize(width: titleWidth, height: 44.0) + ) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame( + view: titleView, + frame: CGRect( + origin: CGPoint( + x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), + y: floorToScreenPixels(16.0 + (barButtonSize.height - titleSize.height) / 2.0) + ), + size: titleSize + ) + ) + } + } else if let titleView = self.title.view, titleView.superview != nil { + titleView.removeFromSuperview() + } + + var contentHeight: CGFloat = 68.0 + + let pickerHeight: CGFloat = 216.0 + if let pickerView = self.pickerView { + transition.setFrame(view: pickerView as UIView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: availableSize.width, height: pickerHeight))) + } + contentHeight += pickerHeight + contentHeight += 17.0 + + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: component.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) + let selectedValue = self.selectedValue() + let primaryButtonTitle = component.configuration.primaryActionTitle(component.strings, component.dateTimeFormat, selectedValue) + let primaryButtonSize = self.primaryButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(primaryButtonTitle), component: AnyComponent( + Text(text: primaryButtonTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + self?.complete(selectedValue: self?.selectedValue()) + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) + ) + if let primaryButtonView = self.primaryButton.view { + if primaryButtonView.superview == nil { + self.addSubview(primaryButtonView) + } + transition.setFrame(view: primaryButtonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: primaryButtonSize)) + } + contentHeight += primaryButtonSize.height + + if let secondaryAction = component.configuration.secondaryAction { + contentHeight += 8.0 + + let foregroundColor: UIColor + switch secondaryAction.style { + case .accent: + foregroundColor = component.theme.actionSheet.controlAccentColor + case .destructive: + foregroundColor = component.theme.list.itemDestructiveColor + } + let secondaryButtonTitle = secondaryAction.title(component.strings) + let secondaryButtonSize = self.secondaryButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: foregroundColor.withMultipliedAlpha(0.1), + foreground: foregroundColor, + pressedColor: foregroundColor.withMultipliedAlpha(0.2) + ), + content: AnyComponentWithIdentity(id: AnyHashable(secondaryButtonTitle), component: AnyComponent( + Text(text: secondaryButtonTitle, font: Font.semibold(17.0), color: foregroundColor) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + self?.complete(selectedValue: secondaryAction.value()) + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) + ) + if let secondaryButtonView = self.secondaryButton.view { + if secondaryButtonView.superview == nil { + self.addSubview(secondaryButtonView) + } + transition.setFrame(view: secondaryButtonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: secondaryButtonSize)) + } + contentHeight += secondaryButtonSize.height + } else if let secondaryButtonView = self.secondaryButton.view, secondaryButtonView.superview != nil { + secondaryButtonView.removeFromSuperview() + } + + contentHeight += buttonInsets.bottom + + return CGSize(width: availableSize.width, height: contentHeight) + } + + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + guard let configuration = self.component?.configuration else { + return 0 + } + + switch configuration.picker { + case let .fixedValues(values, _, _): + return values.count + case .timeOfDay, .date, .dateTime: + return 0 + } + } + + public func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent componentIndex: Int, reusing view: UIView?) -> UIView { + guard let component = self.component else { + return UIView() + } + let itemView: TimerPickerItemView if let current = view as? TimerPickerItemView { itemView = current } else { itemView = TimerPickerItemView() - itemView.textColor = self.presentationData.theme.list.itemPrimaryTextColor } - - let value = self.autoremoveTimerValues[row] - - let string: String - string = timeIntervalString(strings: self.presentationData.strings, value: value) - - itemView.value = (value, string) - + itemView.textColor = self.pickerTextColor(configuration: component.configuration, theme: component.theme) + + switch component.configuration.picker { + case let .fixedValues(values, _, formatter): + let value = values[row] + itemView.value = (value, formatter(component.strings, value)) + case .timeOfDay, .date, .dateTime: + break + } + return itemView - case .mute: - preconditionFailure() + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + self.state?.updated(transition: .immediate) } } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - self.dataPickerChanged() + + public func makeView() -> View { + return View(frame: CGRect()) } - - func updatePresentationData(_ presentationData: PresentationData) { - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - guard case .default = self.controllerStyle else { - return - } - - if let effectView = self.effectNode.view as? UIVisualEffectView { - effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark) - } - - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.setupPickerView() - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) - self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if self.dismissByTapOutside, case .ended = recognizer.state { - self.cancelButtonPressed() - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - let targetBounds = self.bounds - self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) - self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) - transition.animateView({ - self.bounds = targetBounds - self.dimNode.position = dimPosition - }) - } - - func animateOut(completion: (() -> Void)? = nil) { - var dimCompleted = false - var offsetCompleted = false - - let internalCompletion: () -> Void = { [weak self] in - if let strongSelf = self, dimCompleted && offsetCompleted { - strongSelf.dismiss?() - } - completion?() - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in - dimCompleted = true - internalCompletion() - }) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - offsetCompleted = true - internalCompletion() - }) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - return self.dimNode.view - } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - let cleanInsets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - - var buttonOffset: CGFloat = 0.0 - let bottomInset: CGFloat = 10.0 + cleanInsets.bottom - let titleHeight: CGFloat = 54.0 - var contentHeight = titleHeight + bottomInset + 52.0 + 17.0 - let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight) - - if !self.disableButton.isHidden { - buttonOffset += 52.0 - } - - contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight + buttonOffset - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight)) - let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - - let buttonInset: CGFloat = 16.0 - - switch self.mode { - case .sendTimer: - break - case .autoremove: - self.doneButton.title = self.presentationData.strings.Conversation_DeleteTimer_Apply - case .mute: - if let pickerView = self.pickerView as? TimerDatePickerView { - let timeInterval = max(0, Int32(pickerView.date.timeIntervalSince1970) - Int32(Date().timeIntervalSince1970)) - - if timeInterval > 0 { - let timeString = stringForPreciseRelativeTimestamp(strings: self.presentationData.strings, relativeTimestamp: Int32(pickerView.date.timeIntervalSince1970), relativeTo: Int32(Date().timeIntervalSince1970), dateTimeFormat: self.presentationData.dateTimeFormat) - - self.doneButton.title = self.presentationData.strings.Conversation_Mute_ApplyMuteUntil(timeString).string - } else { - self.doneButton.title = self.presentationData.strings.Common_Close - } - } else { - self.doneButton.title = self.presentationData.strings.Common_Close - } - } - - let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - let doneButtonFrame = CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight) - transition.updateFrame(node: self.doneButton, frame: doneButtonFrame) - - let disableButtonTitleSize = self.disableButtonTitle.updateLayout(CGSize(width: contentFrame.width, height: doneButtonHeight)) - let disableButtonFrame = CGRect(origin: CGPoint(x: doneButtonFrame.minX, y: doneButtonFrame.maxY), size: CGSize(width: contentFrame.width - buttonInset * 2.0, height: doneButtonHeight)) - transition.updateFrame(node: self.disableButton, frame: disableButtonFrame) - transition.updateFrame(node: self.disableButtonTitle, frame: CGRect(origin: CGPoint(x: floor((disableButtonFrame.width - disableButtonTitleSize.width) / 2.0), y: floor((disableButtonFrame.height - disableButtonTitleSize.height) / 2.0)), size: disableButtonTitleSize)) - - self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight)) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ChatTimerSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let configuration: ChatTimerScreen.Configuration + let dismiss: () -> Void + + init( + configuration: ChatTimerScreen.Configuration, + dismiss: @escaping () -> Void + ) { + self.configuration = configuration + self.dismiss = dismiss + } + + static func ==(lhs: ChatTimerSheetContentComponent, rhs: ChatTimerSheetContentComponent) -> Bool { + return lhs.configuration == rhs.configuration + } + + final class View: UIView { + private let content = ComponentView() + + private var component: ChatTimerSheetContentComponent? + private var environment: EnvironmentType? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update( + component: ChatTimerSheetContentComponent, + availableSize: CGSize, + state: EmptyComponentState, + environment: Environment, + transition: ComponentTransition + ) -> CGSize { + let environment = environment[EnvironmentType.self].value + + self.component = component + self.environment = environment + self.state = state + + self.content.parentState = state + let contentSize = self.content.update( + transition: transition, + component: AnyComponent(ChatTimerPickerContentComponent( + configuration: component.configuration, + theme: environment.theme, + strings: environment.strings, + dateTimeFormat: environment.dateTimeFormat, + safeInsets: environment.safeInsets, + leadingAction: ChatTimerPickerContentComponent.LeadingAction( + icon: .close, + action: { [weak self] in + self?.component?.dismiss() + } + ), + completion: { [weak self] value in + guard let self, let controller = self.environment?.controller() as? ChatTimerScreen else { + return + } + controller.completion(value) + self.component?.dismiss() + } + )), + environment: {}, + containerSize: availableSize + ) + if let contentView = self.content.view { + if contentView.superview == nil { + self.addSubview(contentView) + } + transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: contentSize)) + } + + return contentSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ChatTimerSheetComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let configuration: ChatTimerScreen.Configuration + + init(configuration: ChatTimerScreen.Configuration) { + self.configuration = configuration + } + + static func ==(lhs: ChatTimerSheetComponent, rhs: ChatTimerSheetComponent) -> Bool { + return lhs.configuration == rhs.configuration + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var component: ChatTimerSheetComponent? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func dismiss() { + self.sheetAnimateOut.invoke(Action { [weak self] _ in + guard let self, let controller = self.environment?.controller() else { + return + } + controller.dismiss(completion: nil) + }) + } + + func update(component: ChatTimerSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] _ in + self?.dismiss() + } + ) + + let backgroundColor: UIColor + switch component.configuration.style { + case .default: + backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor + case .media: + backgroundColor = UIColor(rgb: 0x1c1c1e) + } + + let _ = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(ChatTimerSheetContentComponent( + configuration: component.configuration, + dismiss: { [weak self] in + self?.dismiss() + } + )), + style: .glass, + backgroundColor: .color(backgroundColor), + followContentSizeChanges: true, + animateOut: self.sheetAnimateOut + )), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class ChatTimerScreen: ViewControllerComponentContainer { + public final class Configuration: Equatable { + public enum ActionStyle { + case accent + case destructive + } + + public enum PickerValueMapping { + case rawTimestamp + case roundDateToDaysUTC + case secondsFromMidnightGMT + } + + public enum FixedSelectionStrategy { + case exact + case closestLowerOrEqual + case firstGreaterOrEqual + } + + public final class SecondaryAction: Equatable { + public let title: (PresentationStrings) -> String + public let style: ActionStyle + public let value: () -> Int32? + + public init( + title: @escaping (PresentationStrings) -> String, + style: ActionStyle, + value: @escaping () -> Int32? + ) { + self.title = title + self.style = style + self.value = value + } + + public static func ==(lhs: SecondaryAction, rhs: SecondaryAction) -> Bool { + return lhs === rhs + } + } + + public enum PickerKind { + case timeOfDay + case date + case dateTime + case fixedValues( + values: [Int32], + selectionStrategy: FixedSelectionStrategy, + formatter: (PresentationStrings, Int32) -> String + ) + } + + public let style: ChatTimerScreenStyle + public let title: (PresentationStrings) -> String? + public let picker: PickerKind + public let currentValue: Int32? + public let minimumDate: Date? + public let maximumDate: Date? + public let pickerValueMapping: PickerValueMapping + public let primaryActionTitle: (PresentationStrings, PresentationDateTimeFormat, Int32?) -> String + public let secondaryAction: SecondaryAction? + public let completionValueTransform: (Int32?) -> Int32? + public let pickerTextColor: ((PresentationTheme) -> UIColor)? + + public init( + style: ChatTimerScreenStyle, + title: @escaping (PresentationStrings) -> String? = { _ in nil }, + picker: PickerKind, + currentValue: Int32?, + minimumDate: Date? = nil, + maximumDate: Date? = nil, + pickerValueMapping: PickerValueMapping, + primaryActionTitle: @escaping (PresentationStrings, PresentationDateTimeFormat, Int32?) -> String, + secondaryAction: SecondaryAction? = nil, + completionValueTransform: @escaping (Int32?) -> Int32? = { $0 }, + pickerTextColor: ((PresentationTheme) -> UIColor)? = nil + ) { + self.style = style + self.title = title + self.picker = picker + self.currentValue = currentValue + self.minimumDate = minimumDate + self.maximumDate = maximumDate + self.pickerValueMapping = pickerValueMapping + self.primaryActionTitle = primaryActionTitle + self.secondaryAction = secondaryAction + self.completionValueTransform = completionValueTransform + self.pickerTextColor = pickerTextColor + } + + public static func ==(lhs: Configuration, rhs: Configuration) -> Bool { + return lhs === rhs + } + } + + fileprivate let completion: (Int32?) -> Void + + private static func legacyConfiguration( + style: ChatTimerScreenStyle, + mode: ChatTimerScreenMode, + currentTime: Int32? + ) -> Configuration { + switch mode { + case .sendTimer: + return Configuration( + style: style, + title: { strings in + strings.Conversation_Timer_Title + }, + picker: .fixedValues( + values: [viewOnceTimeout] + timerValues, + selectionStrategy: .exact, + formatter: { strings, value in + if value == viewOnceTimeout { + return strings.MediaPicker_Timer_ViewOnce + } else { + return timeIntervalString(strings: strings, value: value) + } + } + ), + currentValue: currentTime ?? viewOnceTimeout, + pickerValueMapping: .rawTimestamp, + primaryActionTitle: { strings, _, _ in + strings.Conversation_Timer_Send + }, + pickerTextColor: { _ in + .white + } + ) + case .autoremove: + return Configuration( + style: style, + title: { strings in + strings.Conversation_DeleteTimer_SetupTitle + }, + picker: .fixedValues( + values: autoremoveTimerValues, + selectionStrategy: .closestLowerOrEqual, + formatter: { strings, value in + timeIntervalString(strings: strings, value: value) + } + ), + currentValue: currentTime, + pickerValueMapping: .rawTimestamp, + primaryActionTitle: { strings, _, _ in + strings.Conversation_DeleteTimer_Apply + }, + secondaryAction: currentTime != nil ? Configuration.SecondaryAction( + title: { strings in + strings.Conversation_DeleteTimer_Disable + }, + style: .destructive, + value: { + 0 + } + ) : nil, + pickerTextColor: { theme in + if case .media = style { + return .white + } else { + return theme.list.itemPrimaryTextColor + } + } + ) + case .mute: + return Configuration( + style: style, + title: { strings in + strings.Conversation_Mute_SetupTitle + }, + picker: .dateTime, + currentValue: currentTime, + minimumDate: Date(), + pickerValueMapping: .rawTimestamp, + primaryActionTitle: { strings, dateTimeFormat, selectedValue in + if let selectedValue { + let now = Int32(Date().timeIntervalSince1970) + let timeInterval = max(0, selectedValue - now) + if timeInterval > 0 { + let timeString = stringForPreciseRelativeTimestamp( + strings: strings, + relativeTimestamp: selectedValue, + relativeTo: now, + dateTimeFormat: dateTimeFormat + ) + return strings.Conversation_Mute_ApplyMuteUntil(timeString).string + } + } + return strings.Common_Close + }, + completionValueTransform: { selectedValue in + guard let selectedValue else { + return nil + } + return max(0, selectedValue - Int32(Date().timeIntervalSince1970)) + }, + pickerTextColor: { theme in + if case .media = style { + return .white + } else { + return theme.list.itemPrimaryTextColor + } + } + ) + } + } + + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + configuration: Configuration, + completion: @escaping (Int32?) -> Void + ) { + self.completion = completion + + super.init( + context: context, + component: ChatTimerSheetComponent(configuration: configuration), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: configuration.style == .media ? .dark : .default, + updatedPresentationData: updatedPresentationData + ) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + public convenience init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + style: ChatTimerScreenStyle, + mode: ChatTimerScreenMode = .sendTimer, + currentTime: Int32? = nil, + completion: @escaping (Int32) -> Void + ) { + let configuration = Self.legacyConfiguration(style: style, mode: mode, currentTime: currentTime) + self.init( + context: context, + updatedPresentationData: updatedPresentationData, + configuration: configuration, + completion: { value in + completion(value ?? 0) + } + ) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View { + view.dismissAnimated() + } } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/BUILD b/submodules/TelegramUI/Components/ChatTitleView/BUILD index aafb2e9ff0..4d9fc0c009 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/BUILD +++ b/submodules/TelegramUI/Components/ChatTitleView/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/LegacyComponents:LegacyComponents", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift index 64ed2c7941..db51166224 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import TelegramPresentationData import AccountContext import TelegramUIPreferences -import Postbox import TelegramCore import PeerPresenceStatusManager import ChatTitleActivityNode @@ -376,7 +375,7 @@ public final class ChatTitleComponent: Component { var titleStatusIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch component.content { - case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, isEnabledValue): + case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, hidePeerStatus, isEnabledValue): if peerView.peerId.isReplies { titleSegments = [AnimatedTextComponent.Item( id: AnyHashable(0), @@ -448,7 +447,7 @@ public final class ChatTitleComponent: Component { titleCredibilityIcon = .fake } else if peer.isScam { titleCredibilityIcon = .scam - } else if let emojiStatus = peer.emojiStatus { + } else if !hidePeerStatus, let emojiStatus = peer.emojiStatus { titleStatusIcon = .emojiStatus(emojiStatus) } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { titleCredibilityIcon = .premium @@ -612,7 +611,7 @@ public final class ChatTitleComponent: Component { var inputActivitiesAllowed = true switch component.content { - case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _): + case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _, _): if let peer = peerView.peer { if peer.id == component.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes { inputActivitiesAllowed = false @@ -710,7 +709,7 @@ public final class ChatTitleComponent: Component { } } else { switch component.content { - case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _, _): if let customSubtitle { let string = NSAttributedString(string: customSubtitle, font: subtitleFont, textColor: component.theme.chat.inputPanel.inputControlColor) state = .info(string, .generic) diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 58fffdcb90..f45f442812 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import LegacyComponents @@ -29,15 +28,15 @@ private let subtitleFont = Font.regular(13.0) public enum ChatTitleContent: Equatable { public struct PeerData: Equatable { - public var peerId: PeerId - public var peer: Peer? + public var peerId: EnginePeer.Id + public var peer: EngineRawPeer? public var isContact: Bool public var isSavedMessages: Bool public var notificationSettings: TelegramPeerNotificationSettings? - public var peerPresences: [PeerId: PeerPresence] - public var cachedData: CachedPeerData? - - public init(peerId: PeerId, peer: Peer?, isContact: Bool, isSavedMessages: Bool, notificationSettings: TelegramPeerNotificationSettings?, peerPresences: [PeerId: PeerPresence], cachedData: CachedPeerData?) { + public var peerPresences: [EnginePeer.Id: EngineRawPeerPresence] + public var cachedData: EngineCachedPeerData? + + public init(peerId: EnginePeer.Id, peer: EngineRawPeer?, isContact: Bool, isSavedMessages: Bool, notificationSettings: TelegramPeerNotificationSettings?, peerPresences: [EnginePeer.Id: EngineRawPeerPresence], cachedData: EngineCachedPeerData?) { self.peerId = peerId self.peer = peer self.isContact = isContact @@ -47,7 +46,7 @@ public enum ChatTitleContent: Equatable { self.cachedData = cachedData } - public init(peerView: PeerView) { + public init(peerView: EngineRawPeerView) { self.init(peerId: peerView.peerId, peer: peerViewMainPeer(peerView), isContact: peerView.peerIsContact, isSavedMessages: false, notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, peerPresences: peerView.peerPresences, cachedData: peerView.cachedData) } @@ -110,14 +109,14 @@ public enum ChatTitleContent: Equatable { } } - case peer(peerView: PeerData, customTitle: String?, customSubtitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) + case peer(peerView: PeerData, customTitle: String?, customSubtitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, hidePeerStatus: Bool, isEnabled: Bool) case replyThread(type: ReplyThreadType, count: Int) case custom(title: [TitleTextItem], subtitle: String?, isEnabled: Bool) public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { switch lhs { - case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled): - if case let .peer(rhsPeerView, rhsCustomTitle, rhsCustomSubtitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs { + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, hidePeerStatus, isEnabled): + if case let .peer(rhsPeerView, rhsCustomTitle, rhsCustomSubtitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsHidePeerStatus, rhsIsEnabled) = rhs { if peerView != rhsPeerView { return false } @@ -139,6 +138,9 @@ public enum ChatTitleContent: Equatable { if customMessageCount != rhsCustomMessageCount { return false } + if hidePeerStatus != rhsHidePeerStatus { + return false + } if isEnabled != rhsIsEnabled { return false } @@ -269,7 +271,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleStatusIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { - case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, isEnabledValue): + case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, hidePeerStatus, isEnabledValue): if peerView.peerId.isReplies { let typeText: String = self.strings.DialogList_Replies segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] @@ -306,7 +308,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleCredibilityIcon = .fake } else if peer.isScam { titleCredibilityIcon = .scam - } else if let emojiStatus = peer.emojiStatus { + } else if !hidePeerStatus, let emojiStatus = peer.emojiStatus { titleStatusIcon = .emojiStatus(emojiStatus) } else if peer.isPremium && !premiumConfiguration.isPremiumDisabled { titleCredibilityIcon = .premium @@ -476,8 +478,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var enableAnimation = false switch titleContent { - case let .peer(_, customTitle, _, _, _, _, _, _): - if case let .peer(_, previousCustomTitle, _, _, _, _, _, _) = oldValue { + case let .peer(_, customTitle, _, _, _, _, _, _, _): + if case let .peer(_, previousCustomTitle, _, _, _, _, _, _, _) = oldValue { if customTitle != previousCustomTitle { enableAnimation = false } @@ -503,7 +505,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _): + case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _, _): if let peer = peerView.peer { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes { inputActivitiesAllowed = false @@ -604,7 +606,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _, _): if let customSubtitle { let string = NSAttributedString(string: customSubtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) diff --git a/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD b/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD index 014f566a64..2256fb995e 100644 --- a/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/CocoonInfoScreen/BUILD @@ -14,7 +14,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/ComponentFlow", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift b/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift index ff119d1368..f7e1b6a566 100644 --- a/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift +++ b/submodules/TelegramUI/Components/CocoonInfoScreen/Sources/CocoonInfoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/ComposePollScreen/BUILD b/submodules/TelegramUI/Components/ComposePollScreen/BUILD index 358b7890a4..c261fedc22 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/BUILD +++ b/submodules/TelegramUI/Components/ComposePollScreen/BUILD @@ -60,6 +60,9 @@ swift_library( "//submodules/CounterControllerTitleView", "//submodules/TelegramUI/Components/EdgeEffect", "//submodules/ICloudResources", + "//submodules/CountrySelectionUI", + "//submodules/TelegramUI/Components/ShareWithPeersScreen", + "//submodules/ChatTextLinkEditUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift index 709e42b639..6f5e25f639 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift @@ -35,6 +35,9 @@ import ContextUI import StickerPeekUI import EdgeEffect import LocationUI +import CountrySelectionUI +import ShareWithPeersScreen +import ChatTextLinkEditUI public final class ComposedPoll { public struct Text { @@ -54,6 +57,8 @@ public final class ComposedPoll { public let revotingDisabled: Bool public let shuffleAnswers: Bool public let hideResultsUntilClose: Bool + public let restrictToSubscribers: Bool + public let limitToCountries: [String] public let text: Text public let description: Text @@ -72,6 +77,8 @@ public final class ComposedPoll { revotingDisabled: Bool, shuffleAnswers: Bool, hideResultsUntilClose: Bool, + restrictToSubscribers: Bool, + limitToCountries: [String], text: Text, description: Text, media: AnyMediaReference?, @@ -88,6 +95,8 @@ public final class ComposedPoll { self.revotingDisabled = revotingDisabled self.shuffleAnswers = shuffleAnswers self.hideResultsUntilClose = hideResultsUntilClose + self.restrictToSubscribers = restrictToSubscribers + self.limitToCountries = limitToCountries self.text = text self.description = description self.media = media @@ -139,6 +148,10 @@ final class ComposePollScreenComponent: Component { self.media = media } + deinit { + self.uploadDisposable?.dispose() + } + var requiresUpload: Bool { if let image = self.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations), !(largest.resource is CloudPhotoSizeMediaResource) { return true @@ -146,6 +159,9 @@ final class ComposePollScreenComponent: Component { if let file = self.media.media as? TelegramMediaFile, !(file.resource is CloudDocumentMediaResource) { return true } + if let webpage = self.media.media as? TelegramMediaWebpage, webpage.id?.namespace == Namespaces.Media.LocalFile { + return true + } return false } } @@ -229,11 +245,17 @@ final class ComposePollScreenComponent: Component { private var canAddOptions: Bool = false private var canRevote: Bool = false private var shuffleOptions: Bool = false - private var isQuiz: Bool = false + private(set) var isQuiz: Bool = false private var selectedQuizOptionIds = Set() private var limitDuration: Bool = false private var timeLimit: TimeLimit = .duration(24 * 60 * 60) private var hideResults: Bool = false + private var restrictToSubscribers: Bool = false + private var limitByCountry: Bool = false + private var limitToCountries: [String] = [] + + private var currentLocale: Locale? + private var didSetupCountries = false private var currentInputMode: ListComposePollOptionComponent.InputMode = .keyboard @@ -258,6 +280,8 @@ final class ComposePollScreenComponent: Component { private var cachedShuffleIcon: UIImage? private var cachedQuizIcon: UIImage? private var cachedDurationIcon: UIImage? + private var cachedSubscribersIcon: UIImage? + private var cachedCountryIcon: UIImage? private var cachedEmptyIcon: UIImage? private var reorderRecognizer: ReorderGestureRecognizer? @@ -460,6 +484,7 @@ final class ComposePollScreenComponent: Component { case questionNeeded case optionsNeeded case quizCorrectOptionNeeded + case countriesNeeded } var hasAnyData: Bool { @@ -490,14 +515,7 @@ final class ComposePollScreenComponent: Component { if self.pollTextInputState.text.length == 0 { return .questionNeeded } - - let mappedKind: TelegramMediaPollKind - if self.isQuiz { - mappedKind = .quiz(multipleAnswers: self.effectiveIsMultiAnswer) - } else { - mappedKind = .poll(multipleAnswers: self.effectiveIsMultiAnswer) - } - + var mappedOptions: [TelegramMediaPollOption] = [] var selectedQuizOptions: [Data] = [] for pollOption in self.pollOptions { @@ -532,10 +550,21 @@ final class ComposePollScreenComponent: Component { )) } - if self.isQuiz && mappedOptions.count < 2 { + let mappedKind: TelegramMediaPollKind + if self.isQuiz { + mappedKind = .quiz(multipleAnswers: self.effectiveIsMultiAnswer) + } else { + mappedKind = .poll(multipleAnswers: self.effectiveIsMultiAnswer && mappedOptions.count > 1) + } + + if mappedOptions.count < 1 { return .optionsNeeded } + if self.limitByCountry && self.limitToCountries.isEmpty { + return .countriesNeeded + } + var mappedCorrectAnswers: [Data]? if self.isQuiz { if !selectedQuizOptions.isEmpty { @@ -608,6 +637,8 @@ final class ComposePollScreenComponent: Component { revotingDisabled: !self.canRevote, shuffleAnswers: self.shuffleOptions, hideResultsUntilClose: self.hideResults, + restrictToSubscribers: self.restrictToSubscribers, + limitToCountries: self.limitByCountry ? self.limitToCountries : [], text: ComposedPoll.Text(string: self.pollTextInputState.text.string, entities: textEntities), description: ComposedPoll.Text(string: self.pollDescriptionInputState.text.string, entities: descriptionEntities), media: self.pollDescriptionMedia?.media, @@ -624,7 +655,8 @@ final class ComposePollScreenComponent: Component { media: mappedSolution.2?.media ) }, - hasUnseenVotes: false + hasUnseenVotes: false, + canViewStats: false ), deadlineTimeout: deadlineTimeout, deadlineDate: deadlineDate, @@ -733,7 +765,6 @@ final class ComposePollScreenComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -745,8 +776,6 @@ final class ComposePollScreenComponent: Component { businessIntro: nil ) - //self.inputMediaNodeBackground.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor - let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight < 100.0 ? inputHeight - bottomContainerInset : inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false) let inputNodeHeight = heightAndOverflow.0 let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight)) @@ -888,6 +917,11 @@ final class ComposePollScreenComponent: Component { self.deactivateInput() + if !replace, case .pollOption = subject, let webpage = self.attachedMedia(for: subject)?.media.media as? TelegramMediaWebpage, let link = webpage.content.url { + self.openAttachedLinkMedia(subject: subject, link: link) + return + } + guard replace || !self.openAttachMediaContextMenu(subject: subject) else { return } @@ -897,7 +931,7 @@ final class ComposePollScreenComponent: Component { case .description, .quizAnswer: availableButtons = [.gallery, .file, .location] default: - availableButtons = [.gallery, .sticker, .location] + availableButtons = [.gallery, .sticker, .location, .link] } let pollAttachmentSubject: PollAttachmentSubject @@ -937,11 +971,46 @@ final class ComposePollScreenComponent: Component { }) } + private func openAttachedLinkMedia(subject: MediaAttachSubject, link: String) { + guard let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = chatTextLinkEditController( + context: component.context, + updatedPresentationData: (presentationData, .never()), + text: presentationData.strings.CreatePoll_Link_Description, + link: link, + preview: true, + apply: { [weak self] link, webpage in + guard let self, let link else { + return + } + + if link.isEmpty { + self.setAttachedMedia(nil, for: subject) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + return + } + + let attachedMedia = AttachedMedia(media: .standalone(media: webpage ?? makePollAttachmentLinkWebpage(link: link))) + self.setAttachedMedia(attachedMedia, for: subject) + self.uploadAttachedMediaIfNeeded(attachedMedia) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + ) + (self.environment?.controller() as? ComposePollScreen)?.parentController()?.present(controller, in: .window(.root)) + } + private func uploadAttachedMediaIfNeeded(_ media: AttachedMedia) { guard let component = self.component, media.requiresUpload, media.uploadDisposable == nil else { return } - media.progress = 0.0 + if media.media.media is TelegramMediaWebpage { + } else { + media.progress = 0.0 + } if let image = media.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { media.uploadDisposable = (standaloneUploadedImage( @@ -964,7 +1033,7 @@ final class ComposePollScreenComponent: Component { switch result { case let .media(resultMedia): if let resultImage = resultMedia.media as? TelegramMediaImage, let resultLargest = largestImageRepresentation(resultImage.representations) { - component.context.account.postbox.mediaBox.moveResourceData(from: largest.resource.id, to: resultLargest.resource.id, synchronous: true) + component.context.engine.resources.moveResourceData(from: EngineMediaResource.Id(largest.resource.id), to: EngineMediaResource.Id(resultLargest.resource.id), synchronous: true) } media.media = resultMedia @@ -1003,7 +1072,7 @@ final class ComposePollScreenComponent: Component { switch result { case let .media(resultMedia): if let resultFile = resultMedia.media as? TelegramMediaFile { - component.context.account.postbox.mediaBox.moveResourceData(from: file.resource.id, to: resultFile.resource.id, synchronous: true) + component.context.engine.resources.moveResourceData(from: EngineMediaResource.Id(file.resource.id), to: EngineMediaResource.Id(resultFile.resource.id), synchronous: true) } media.media = resultMedia media.progress = nil @@ -1017,6 +1086,29 @@ final class ComposePollScreenComponent: Component { } }) } + if let webpage = media.media.media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + media.uploadDisposable = (webpagePreview(account: component.context.account, urls: [content.url]) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self else { + return + } + var transition: ComponentTransition = .immediate + switch result { + case let .result(result): + if let result { + media.media = .standalone(media: result.webpage) + } +// media.uploadDisposable?.dispose() +// media.uploadDisposable = nil + transition = .easeInOut(duration: 0.2) + default: + break + } + if !self.isUpdating { + self.state?.updated(transition: transition) + } + }) + } } private func openAttachMediaContextMenu(subject: MediaAttachSubject) -> Bool { @@ -1217,6 +1309,41 @@ final class ComposePollScreenComponent: Component { (self.environment?.controller() as? ComposePollScreen)?.parentController()?.push(controller) } + private func openCountriesSelection() { + guard let component = self.component else { + return + } + + let maxCount: Int32 + if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["poll_countries_max"] as? Double { + maxCount = Int32(value) + } else { + maxCount = 12 + } + + let stateContext = CountriesMultiselectionScreen.StateContext( + context: component.context, + subject: .countries, + maxCount: maxCount, + initialSelectedCountries: self.limitToCountries, + showFragment: true + ) + let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + let controller = CountriesMultiselectionScreen( + context: component.context, + stateContext: stateContext, + completion: { [weak self] countries in + guard let self else { + return + } + self.limitToCountries = countries + self.state?.updated() + } + ) + (self?.environment?.controller() as? ComposePollScreen)?.parentController()?.push(controller) + }) + } + func deactivateInput() { self.currentInputMode = .keyboard if hasFirstResponder(self) { @@ -1249,6 +1376,8 @@ final class ComposePollScreenComponent: Component { } if self.component == nil { + self.currentLocale = localeWithStrings(environment.strings) + self.isQuiz = component.isQuiz ?? false if !self.isQuiz { self.isMultiAnswer = true @@ -1415,6 +1544,8 @@ final class ComposePollScreenComponent: Component { self.cachedShuffleIcon = renderSettingsIcon(name: "Item List/Icons/Shuffle", backgroundColors: [UIColor(rgb: 0xAF52DE)]) self.cachedQuizIcon = renderSettingsIcon(name: "Item List/Icons/Checkbox", backgroundColors: [UIColor(rgb: 0x34C759)]) self.cachedDurationIcon = renderSettingsIcon(name: "Item List/Icons/Timer", backgroundColors: [UIColor(rgb: 0xFF453A)]) + self.cachedSubscribersIcon = renderSettingsIcon(name: "Item List/Icons/Profile", backgroundColors: [UIColor(rgb: 0x0A84FF)]) + self.cachedCountryIcon = renderSettingsIcon(name: "Item List/Icons/Flag", backgroundColors: [UIColor(rgb: 0xFF9F0A)]) self.cachedEmptyIcon = generateSingleColorImage(size: CGSize(width: 30.0, height: 30.0), color: .clear) @@ -1445,7 +1576,6 @@ final class ComposePollScreenComponent: Component { pollTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.pollTextInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, resetText: self.resetPollText.flatMap { resetText in @@ -1481,7 +1611,6 @@ final class ComposePollScreenComponent: Component { pollTextSectionItems.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ListComposePollOptionComponent( externalState: self.pollDescriptionInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, resetText: nil, @@ -1597,7 +1726,6 @@ final class ComposePollScreenComponent: Component { pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent( externalState: pollOption.textInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, resetText: pollOption.resetText.flatMap { resetText in @@ -2256,6 +2384,140 @@ final class ComposePollScreenComponent: Component { maximumNumberOfLines: 0 )) } + + if isChannel { + pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "subscribers", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_RestrictToSubscribers, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 2 + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_RestrictToSubscribersInfo, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 3, + lineSpacing: 0.1 + ))) + ], alignment: .left, spacing: 4.0)), + verticalAlignment: .middle, + contentInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent( + Image(image: self.cachedSubscribersIcon, size: CGSize(width: 30.0, height: 30.0)) + )), false), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.restrictToSubscribers, action: { [weak self] _ in + guard let self else { + return + } + self.restrictToSubscribers = !self.restrictToSubscribers + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + )))) + + pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "limitCountry", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_LimitCountry, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 2 + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_LimitCountryInfo, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), + textColor: theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 3, + lineSpacing: 0.1 + ))) + ], alignment: .left, spacing: 4.0)), + verticalAlignment: .middle, + contentInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent( + Image(image: self.cachedCountryIcon, size: CGSize(width: 30.0, height: 30.0)) + )), false), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.limitByCountry, action: { [weak self] _ in + guard let self else { + return + } + self.limitByCountry = !self.limitByCountry + self.state?.updated(transition: .spring(duration: 0.4)) + + if self.limitByCountry { + if !self.didSetupCountries { + let countriesConfiguration = component.context.currentCountriesConfiguration.with { $0 } + AuthorizationSequenceCountrySelectionController.setupCountryCodes(countries: countriesConfiguration.countries, codesByPrefix: countriesConfiguration.countriesByPrefix) + } + self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.scrollView.contentSize.height - self.scrollView.bounds.size.height), animated: true) + } + })), + action: nil + )))) + + if self.limitByCountry { + var value: String + if self.limitToCountries.count > 1 { + value = environment.strings.CreatePoll_AllowedCountries_Countries(Int32(self.limitToCountries.count)) + } else if self.limitToCountries.count == 1, let countryCode = self.limitToCountries.first { + if countryCode == "FT" { + value = "Fragment" + } else { + value = self.currentLocale?.localizedString(forRegionCode: countryCode) ?? countryCode + } + } else { + value = "" + } + + pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "countries", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_AllowedCountries, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 2 + ))) + ], alignment: .left, spacing: 4.0)), + verticalAlignment: .middle, + leftIcon: .custom(AnyComponentWithIdentity(id: 0, component: AnyComponent( + Image(image: self.cachedEmptyIcon, size: CGSize(width: 30.0, height: 30.0)) + )), false), + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: value, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )))), + accessory: .arrow, + action: { [weak self] view in + guard let self else { + return + } + self.deactivateInput() + self.openCountriesSelection() + } + )))) + } + } let pollSettingsSectionSize = self.pollSettingsSection.update( transition: transition, @@ -2316,7 +2578,6 @@ final class ComposePollScreenComponent: Component { AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.quizAnswerTextInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, resetText: self.resetQuizAnswerText.flatMap { resetText in @@ -2911,7 +3172,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont text = presentationData.strings.CreatePoll_QuestionNeeded case .optionsNeeded: title = nil - text = presentationData.strings.CreatePoll_OptionsNeeded + text = componentView.isQuiz ? presentationData.strings.CreatePoll_OptionsNeeded : presentationData.strings.CreatePoll_OptionsNeededOne case .quizCorrectOptionNeeded: title = nil if componentView.effectiveIsMultiAnswer { @@ -2919,6 +3180,9 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont } else { text = presentationData.strings.CreatePoll_QuizCorrectOptionNeeded } + case .countriesNeeded: + title = nil + text = presentationData.strings.CreatePoll_QuizCountryNeeded } let controller = UndoOverlayController( diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift index 876b0964dc..3945de4fc1 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import LegacyComponents @@ -16,6 +15,7 @@ import LocationUI import AttachmentFileController import ChatEntityKeyboardInputNode import ICloudResources +import ChatTextLinkEditUI public enum PollAttachmentSubject { case description @@ -23,6 +23,34 @@ public enum PollAttachmentSubject { case option } +func makePollAttachmentLinkWebpage(link: String) -> TelegramMediaWebpage { + let mediaId = Int64.random(in: Int64.min ... Int64.max) + return TelegramMediaWebpage( + webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: mediaId), + content: .Loaded(TelegramMediaWebpageLoadedContent( + url: link, + displayUrl: link, + hash: 0, + type: nil, + websiteName: nil, + title: nil, + text: nil, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: nil + )) + ) +} + public func presentPollAttachmentScreen( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, @@ -39,10 +67,7 @@ public func presentPollAttachmentScreen( chatLocation: nil, isScheduledMessages: false, buttons: availableButtons, - initialButton: .gallery, - makeEntityInputView: { - return nil - } + initialButton: .gallery ) let inputMediaNodeDataPromise = Promise(nil) if let inputMediaNodeData { @@ -64,7 +89,7 @@ public func presentPollAttachmentScreen( |> map(Optional.init) )) } - + attachmentController.requestController = { [weak attachmentController] type, controllerCompletion in let mediaPickerPollSubject: MediaPickerScreenImpl.Subject.AssetsMode.PollMode let filePickerPollSubject: AttachmentFileControllerSource.PollMode @@ -87,7 +112,7 @@ public func presentPollAttachmentScreen( locationPickerPollSubject = .option stickerPickerPollSubject = .option } - + switch type { case .gallery: let controller = MediaPickerScreenImpl( @@ -122,7 +147,7 @@ public func presentPollAttachmentScreen( bannedSendMedia: nil, presentGallery: { [weak attachmentController] in attachmentController?.dismiss(animated: true) - + let controller = MediaPickerScreenImpl( context: context, updatedPresentationData: updatedPresentationData, @@ -150,7 +175,7 @@ public func presentPollAttachmentScreen( }, presentFiles: { [weak attachmentController] in attachmentController?.dismiss(animated: true) - + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let controller = legacyICloudFilePicker(theme: presentationData.theme, documentTypes: ["public.item"], completion: { urls in guard let url = urls.first else { @@ -172,8 +197,8 @@ public func presentPollAttachmentScreen( if let audioMetadata = item.audioMetadata { attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) } - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) + + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) completion(.standalone(media: file)) }) }) @@ -236,6 +261,29 @@ public func presentPollAttachmentScreen( controllerCompletion(controller, controller.mediaPickerContext) }) return true + case .link: + attachmentController?.dismiss(animated: true) + + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + let controller = chatTextLinkEditController( + context: context, + updatedPresentationData: updatedPresentationData, + text: presentationData.strings.CreatePoll_Link_Description, + link: nil, + preview: true, + apply: { link, webpage in + guard let link else { + return + } + if let webpage { + completion(.standalone(media: webpage)) + return + } + completion(.standalone(media: makePollAttachmentLinkWebpage(link: link))) + } + ) + present(controller, false) + return false default: return false } diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift index 65fcfc9330..45a131ec30 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/StickerAttachmentScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import ComponentFlow @@ -88,6 +87,7 @@ final class StickerAttachmentScreenComponent: Component { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -107,6 +107,7 @@ final class StickerAttachmentScreenComponent: Component { self.emojiSearchState.set(.single(self.emojiSearchStateValue)) } } + private var emojiSearchContext: EmojiSearchContext? private let stickerSearchDisposable = MetaDisposable() private let stickerSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) @@ -236,26 +237,19 @@ final class StickerAttachmentScreenComponent: Component { return } - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) let _ = (combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), - context.account.postbox.combinedView(keys: [viewKey]) + context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackIds(namespace: Namespaces.ItemCollection.CloudEmojiPacks)), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)) ) |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] emojiPacksView, views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + |> deliverOnMainQueue).start(next: { [weak self] installedIds, items in guard let self else { return } - - var installedCollectionIds = Set() - for (id, _, _) in emojiPacksView.collectionInfos { - installedCollectionIds.insert(id) - } - - let stickerPacks = view.items.map({ $0.contents.get(FeaturedStickerPackItem.self)! }).filter({ + + let installedCollectionIds = Set(installedIds) + + let stickerPacks = items.map({ $0.contents.get(FeaturedStickerPackItem.self)! }).filter({ !installedCollectionIds.contains($0.info.id) }) @@ -287,18 +281,14 @@ final class StickerAttachmentScreenComponent: Component { openSearch: { }, addGroupAction: { [weak self] groupId, isPremiumLocked, _ in - guard let self, let component = self.component, let collectionId = groupId.base as? ItemCollectionId else { + guard let self, let component = self.component, let collectionId = groupId.base as? EngineItemCollectionId else { return } let context = component.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + |> deliverOnMainQueue).start(next: { items in + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -307,7 +297,7 @@ final class StickerAttachmentScreenComponent: Component { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -390,15 +380,18 @@ final class StickerAttachmentScreenComponent: Component { switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { + self.emojiSearchContext = nil var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { signal = signal @@ -418,24 +411,25 @@ final class StickerAttachmentScreenComponent: Component { signal, hasPremium ) - |> mapToSignal { keywords, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords, hasPremium -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword } } - let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> + let remoteSignal: Signal + let emojiSearchContext: EmojiSearchContext? if hasPremium { - remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let currentEmojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + emojiSearchContext = currentEmojiSearchContext + remoteSignal = currentEmojiSearchContext.state } else { - remoteSignal = .single(([], true)) + emojiSearchContext = nil + remoteSignal = .single(EmojiSearchContext.State(items: [], canLoadMore: false, isLoadingMore: false)) } return remoteSignal - |> mapToSignal { foundEmoji -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - if foundEmoji.items.isEmpty && !foundEmoji.isFinalResult { - return .complete() - } + |> map { foundEmoji -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in var items: [EmojiPagerContentComponent.Item] = [] let appendUnicodeEmoji = { @@ -460,7 +454,7 @@ final class StickerAttachmentScreenComponent: Component { appendUnicodeEmoji() } - var existingIds = Set() + var existingIds = Set() for itemFile in foundEmoji.items { if existingIds.contains(itemFile.fileId) { continue @@ -485,7 +479,7 @@ final class StickerAttachmentScreenComponent: Component { appendUnicodeEmoji() } - return .single([EmojiPagerContentComponent.ItemGroup( + return ([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", title: nil, @@ -502,7 +496,7 @@ final class StickerAttachmentScreenComponent: Component { headerItem: nil, fillWithLoadingPlaceholders: false, items: items - )]) + )], foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } @@ -515,16 +509,18 @@ final class StickerAttachmentScreenComponent: Component { return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.emojiSearchContext = nil let resultSignal = context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { continue @@ -592,10 +588,10 @@ final class StickerAttachmentScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -607,6 +603,9 @@ final class StickerAttachmentScreenComponent: Component { // self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) }, onScroll: {}, + loadMore: { [weak self] in + self?.emojiSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -638,14 +637,13 @@ final class StickerAttachmentScreenComponent: Component { let context = component.context let presentationData = context.sharedContext.currentPresentationData.with { $0 } if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] views in - guard let self, let controller = self.environment?.controller(), let view = views.views[viewKey] as? OrderedItemListView else { + |> deliverOnMainQueue).start(next: { [weak self] items in + guard let self, let controller = self.environment?.controller() else { return } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { controller.push(FeaturedStickersScreen( context: context, @@ -659,7 +657,7 @@ final class StickerAttachmentScreenComponent: Component { return true } )) - + break } } @@ -688,18 +686,14 @@ final class StickerAttachmentScreenComponent: Component { } }, addGroupAction: { [weak self] groupId, isPremiumLocked, _ in - guard let strongSelf = self, let component = strongSelf.component, let collectionId = groupId.base as? ItemCollectionId else { + guard let strongSelf = self, let component = strongSelf.component, let collectionId = groupId.base as? EngineItemCollectionId else { return } let context = component.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + |> deliverOnMainQueue).start(next: { items in + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -708,7 +702,7 @@ final class StickerAttachmentScreenComponent: Component { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -745,15 +739,11 @@ final class StickerAttachmentScreenComponent: Component { ])]) context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) + let _ = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + |> deliverOnMainQueue).start(next: { items in var stickerPackIds: [Int64] = [] - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { stickerPackIds.append(featuredStickerPack.info.id.id) } let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() @@ -797,7 +787,7 @@ final class StickerAttachmentScreenComponent: Component { |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for item in files.items { let itemFile = item.file if existingIds.contains(itemFile.fileId) { @@ -865,10 +855,10 @@ final class StickerAttachmentScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -926,7 +916,7 @@ final class StickerAttachmentScreenComponent: Component { } let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true) - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState, canLoadMore: emojiSearchResult.canLoadMore) } else if emojiSearchState.isSearching { emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiContent.contentItemGroups, itemContentUniqueId: emojiContent.itemContentUniqueId, emptySearchResults: emojiContent.emptySearchResults, searchState: .searching) } diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index 7a8f2eeadf..08b1aeff36 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import ComponentFlow @@ -205,7 +204,7 @@ final class ComposeTodoScreenComponent: Component { for (id, itemView) in self.todoItemsSectionContainer.itemViews { if let view = itemView.contents.view as? ListComposePollOptionComponent.View, !view.isRevealed && !view.currentText.isEmpty { let viewFrame = view.convert(view.bounds, to: self.todoItemsSectionContainer) - let iconFrame = CGRect(origin: CGPoint(x: viewFrame.maxX - 40.0, y: viewFrame.minY), size: CGSize(width: viewFrame.height, height: viewFrame.height)) + let iconFrame = CGRect(origin: CGPoint(x: viewFrame.minX, y: viewFrame.minY), size: CGSize(width: 50.0, height: viewFrame.height)) if iconFrame.contains(localPoint) { return (id, itemView.contents) } @@ -319,6 +318,32 @@ final class ComposeTodoScreenComponent: Component { } } + private static func limitedAttributedString(_ text: NSAttributedString, characterLimit: Int) -> NSAttributedString { + if text.string.count <= characterLimit { + return text + } + + let string = text.string + let endIndex = string.index(string.startIndex, offsetBy: characterLimit) + let length = NSRange(string.startIndex ..< endIndex, in: string).length + return text.attributedSubstring(from: NSRange(location: 0, length: length)) + } + + private static func attributedTodoItemLines(from text: NSAttributedString, characterLimit: Int) -> [NSAttributedString] { + var result: [NSAttributedString] = [] + + text.string.enumerateSubstrings(in: text.string.startIndex ..< text.string.endIndex, options: [.byLines, .substringNotRequired]) { _, substringRange, _, _ in + let range = NSRange(substringRange, in: text.string) + let line = text.attributedSubstring(from: range) + if line.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + return + } + result.append(Self.limitedAttributedString(line, characterLimit: characterLimit)) + } + + return result + } + func validatedInput() -> TelegramMediaTodo? { if self.todoTextInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 { return nil @@ -329,19 +354,10 @@ final class ComposeTodoScreenComponent: Component { if todoItem.textInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 { continue } - var entities: [MessageTextEntity] = [] - for entity in generateChatInputTextEntities(todoItem.textInputState.text) { - switch entity.type { - case .CustomEmoji: - entities.append(entity) - default: - break - } - } mappedItems.append( TelegramMediaTodo.Item( text: todoItem.textInputState.text.string, - entities: entities, + entities: generateChatInputTextEntities(todoItem.textInputState.text), id: todoItem.id ) ) @@ -351,16 +367,6 @@ final class ComposeTodoScreenComponent: Component { return nil } - var textEntities: [MessageTextEntity] = [] - for entity in generateChatInputTextEntities(self.todoTextInputState.text) { - switch entity.type { - case .CustomEmoji: - textEntities.append(entity) - default: - break - } - } - var flags: TelegramMediaTodo.Flags = [] if self.isCompletableByOthers { flags.insert(.othersCanComplete) @@ -372,7 +378,7 @@ final class ComposeTodoScreenComponent: Component { return TelegramMediaTodo( flags: flags, text: self.todoTextInputState.text.string, - textEntities: textEntities, + textEntities: generateChatInputTextEntities(self.todoTextInputState.text), items: mappedItems ) } @@ -471,7 +477,6 @@ final class ComposeTodoScreenComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -786,7 +791,6 @@ final class ComposeTodoScreenComponent: Component { todoTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.todoTextInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, isEnabled: canEdit, @@ -795,6 +799,7 @@ final class ComposeTodoScreenComponent: Component { }, assumeIsEditing: self.inputMediaNodeTargetTag === self.todoTextFieldTag, characterLimit: component.initialData.maxTodoTextLength, + formattingAvailable: true, emptyLineHandling: .allowed, returnKeyAction: { [weak self] in guard let self else { @@ -876,7 +881,6 @@ final class ComposeTodoScreenComponent: Component { todoItemsSectionItems.append(AnyComponentWithIdentity(id: todoItem.id, component: AnyComponent(ListComposePollOptionComponent( externalState: todoItem.textInputState, context: component.context, - style: .glass, theme: theme, strings: environment.strings, isEnabled: isEnabled, @@ -888,6 +892,7 @@ final class ComposeTodoScreenComponent: Component { hasLeftInset: true, canReorder: isEnabled, canAdd: isEnabled && i != 0 && i < component.initialData.maxTodoItemsCount, + formattingAvailable: true, emptyLineHandling: .notAllowed, returnKeyAction: { [weak self] in guard let self else { @@ -949,23 +954,19 @@ final class ComposeTodoScreenComponent: Component { return } if case let .text(text) = data { - let lines = text.string.components(separatedBy: "\n") + let lines = ComposeTodoScreenComponent.View.attributedTodoItemLines(from: text, characterLimit: component.initialData.maxTodoItemLength) if !lines.isEmpty { self.endEditing(true) var i = 0 for line in lines { - if line.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - continue - } - let line = String(line.prefix(component.initialData.maxTodoItemLength)) if i < self.todoItems.count { - self.todoItems[i].resetText = NSAttributedString(string: line) + self.todoItems[i].resetText = line } else { if self.todoItems.count < component.initialData.maxTodoItemsCount { let todoItem = ComposeTodoScreenComponent.TodoItem( id: self.nextTodoItemId ) - todoItem.resetText = NSAttributedString(string: line) + todoItem.resetText = line self.todoItems.append(todoItem) self.nextTodoItemId += 1 } @@ -976,6 +977,12 @@ final class ComposeTodoScreenComponent: Component { } } }, + present: { [weak self] c in + guard let controller = self?.environment?.controller() else { + return + } + controller.present(c, in: .window(.root)) + }, tag: todoItem.textFieldTag )))) @@ -1292,7 +1299,14 @@ final class ComposeTodoScreenComponent: Component { component: AnyComponent(ListSectionComponent( theme: theme, style: .glass, - header: nil, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_SettingsTitle, + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), footer: nil, items: todoSettingsSectionItems )), diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift index 34ae81f6c5..d74cdfc1cc 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import ComponentFlow @@ -252,7 +251,7 @@ final class NewContactScreenComponent: Component { let themeUpdated = self.environment?.theme !== environment.theme self.environment = environment - let theme = environment.theme + let theme = environment.theme.withModalBlocksBackground() let strings = environment.strings var initialCountryCode: Int32? @@ -583,7 +582,7 @@ final class NewContactScreenComponent: Component { return } if case let .peer(peer, _) = self.resolvedPeer { - if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { if let navigationController = component.context.sharedContext.mainWindow?.viewController as? NavigationController { navigationController.pushViewController(infoController) } @@ -716,7 +715,6 @@ final class NewContactScreenComponent: Component { ListComposePollOptionComponent( externalState: nil, context: component.context, - style: .glass, theme: theme, strings: strings, placeholder: NSAttributedString(string: strings.AddContact_NotePlaceholder, font: Font.regular(17.0), textColor: theme.list.itemPlaceholderTextColor), @@ -863,7 +861,7 @@ final class NewContactScreenComponent: Component { let edgeEffectHeight: CGFloat = 66.0 let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) let titleSize = self.title.update( transition: transition, diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift index 475a4d4f54..8eac5fce4c 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift @@ -246,7 +246,7 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { self.phoneInputNode.number = "+\(countryCodeAndId.0)" } - func processNumberChange(_ number: String) -> Bool { + func processNumberChange(_ number: String, notifyUpdated: Bool = true) -> Bool { guard let item = self.item else { return false } @@ -270,7 +270,9 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { self.phoneInputNode.mask = nil self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: item.strings.Login_PhonePlaceholder, font: Font.regular(17.0), textColor: item.theme.list.itemPlaceholderTextColor) } - item.updated(number, rawMask) + if notifyUpdated { + item.updated(number, rawMask) + } return true } else { @@ -297,7 +299,10 @@ final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { } func updateCountryCode(code: Int32, name: String, phoneNumber: String? = nil) { - self.phoneInputNode.codeAndNumber = (code, name, phoneNumber ?? self.phoneInputNode.codeAndNumber.2) + let targetNumber = phoneNumber ?? self.phoneInputNode.codeAndNumber.2 + let targetFullNumber = "+\(code)\(targetNumber)" + let _ = self.processNumberChange(targetFullNumber, notifyUpdated: false) + self.phoneInputNode.codeAndNumber = (code, name, targetNumber) let _ = self.processNumberChange(self.phoneInputNode.number) } diff --git a/submodules/TelegramUI/Components/ContentReportScreen/BUILD b/submodules/TelegramUI/Components/ContentReportScreen/BUILD index 4f552545f4..b96a7ef898 100644 --- a/submodules/TelegramUI/Components/ContentReportScreen/BUILD +++ b/submodules/TelegramUI/Components/ContentReportScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextActionsContainerNode.swift b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextActionsContainerNode.swift index 0ef201b886..93924c3791 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextActionsContainerNode.swift +++ b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextActionsContainerNode.swift @@ -424,6 +424,12 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { self.text = self.presentationData.strings.Camera_CollageReorderingInfo self.targetSelectionIndex = nil icon = UIImage(bundleImageName: "Chat/Context Menu/Tip") + case .deleteReaction: + self.action = nil + self.text = self.presentationData.strings.Chat_DeleteReactionInfo + self.targetSelectionIndex = nil + icon = nil + isUserInteractionEnabled = false } self.iconNode = ASImageNode() @@ -732,6 +738,7 @@ final class ContextActionsContainerNode: ASDisplayNode { if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } + self.scrollNode.view.scrollsToTop = false super.init() diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerExtractedPresentationNode.swift index 7c1fa64281..0f656f4597 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerExtractedPresentationNode.swift @@ -123,6 +123,128 @@ private extension ContextControllerTakeViewInfo.ContainingItem { } } +private final class PortalTransitionStaging { + enum SettleDestination { + case offsetContainer(ASDisplayNode) + case original + } + + weak var surface: UIView? + var wrapper: PortalSourceView? + var clone: PortalView? + var containingItem: ContextControllerTakeViewInfo.ContainingItem? + + /// Reparents the source's contentNode/contentView into a freshly-created + /// `PortalSourceView` inside `surface`, attaches a `PortalView(matchPosition: true)` + /// clone to `overlayHost`, sizes the wrapper so contentNode appears at + /// `targetScreenRect` in window coords, and returns the wrapper's layer. + /// + /// Returns nil if `PortalView(matchPosition:)` cannot be instantiated. In that + /// case staging is left empty and the caller takes the clipping fallback path. + func enter( + for containingItem: ContextControllerTakeViewInfo.ContainingItem, + in surface: UIView, + overlayHost: UIView, + targetScreenRect: CGRect + ) -> CALayer? { + guard let clone = PortalView(matchPosition: true) else { + return nil + } + + let wrapper = PortalSourceView() + + // Place wrapper so that the bubble (= containingItem.contentRect, in + // containingItem.view coords) lands at `targetScreenRect` on screen. + // + // After reparenting contentNode/contentView into wrapper (preserving its + // frame value), the bubble's rect in wrapper-local coords numerically + // equals containingItem.contentRect (since the bubble was at + // contentNode.frame.origin + bubbleOffsetInContentNode == contentRect.origin + // in the original parent). So: + // bubble.screen.origin = wrapper.screen.origin + contentRect.origin + // and we want bubble.screen.origin == targetScreenRect.origin, hence: + // wrapper.screen.origin = targetScreenRect.origin - contentRect.origin + // + // Hidden assumption: contentNode.frame.origin == (0, 0) within containingNode. + // Holds for every current ContextExtractedContentContainingNode adopter; if a + // future adopter offsets contentNode within its containing parent, the bubble + // will be mispositioned in the portal target by that delta. + let bubbleOffsetInContainer = containingItem.contentRect.origin + let wrapperOriginInWindow = CGPoint( + x: targetScreenRect.origin.x - bubbleOffsetInContainer.x, + y: targetScreenRect.origin.y - bubbleOffsetInContainer.y + ) + let wrapperFrameInWindow = CGRect(origin: wrapperOriginInWindow, size: containingItem.view.bounds.size) + wrapper.frame = surface.convert(wrapperFrameInWindow, from: nil) + surface.addSubview(wrapper) + + switch containingItem { + case let .node(containingNode): + wrapper.addSubview(containingNode.contentNode.view) + case let .view(containingView): + wrapper.addSubview(containingView.contentView) + } + + wrapper.addPortal(view: clone) + overlayHost.addSubview(clone.view) + + self.surface = surface + self.wrapper = wrapper + self.clone = clone + self.containingItem = containingItem + + return wrapper.layer + } + + /// Tears down staging. Reparents contentNode into the requested destination, + /// removes clone from its overlay host, removes wrapper from surface. + /// All operations are explicit; we rely on Telegram's manual-animation policy + /// (no implicit CALayer actions) — no CATransaction wrapping needed. + /// + /// `presentationScale` re-application is intentionally not handled here: the + /// portal path is gated on `contentNode.presentationScale == 1.0` in CCEPN's + /// animateIn/animateOut wiring (Tasks 3 and 4), so the scale compensation that + /// the design spec described at settle time is always identity in practice. + func settle(into destination: SettleDestination) { + guard let wrapper = self.wrapper, let containingItem = self.containingItem else { + return + } + + switch destination { + case let .offsetContainer(offsetContainerNode): + switch containingItem { + case let .node(containingNode): + offsetContainerNode.addSubnode(containingNode.contentNode) + case let .view(containingView): + offsetContainerNode.view.addSubview(containingView.contentView) + } + case .original: + // Reparent to the source's containing node/view (where ContextExtracted- + // ContentContainingNode/View added contentNode at init). Capturing + // contentNode.supernode at staging-enter time was overengineered: during + // animateOut staging that's offsetContainerNode (CCEPN's transient host), + // which gets torn down with CCEPN — leaving the bubble parentless. + switch containingItem { + case let .node(containingNode): + containingNode.addSubnode(containingNode.contentNode) + case let .view(containingView): + containingView.addSubview(containingView.contentView) + } + } + + if let clone = self.clone { + wrapper.removePortal(view: clone) + clone.view.removeFromSuperview() + } + wrapper.removeFromSuperview() + + self.surface = nil + self.wrapper = nil + self.clone = nil + self.containingItem = nil + } +} + final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextControllerPresentationNode, ASScrollViewDelegate { enum ContentSource { case location(ContextLocationContentSource) @@ -134,17 +256,20 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo private final class ItemContentNode: ASDisplayNode { let offsetContainerNode: ASDisplayNode var containingItem: ContextControllerTakeViewInfo.ContainingItem - + var animateClippingFromContentAreaInScreenSpace: CGRect? var storedGlobalFrame: CGRect? var storedGlobalBoundsFrame: CGRect? - + var presentationScale: CGFloat = 1.0 + var portalStaging: PortalTransitionStaging? + weak var sourceTransitionSurface: UIView? + init(containingItem: ContextControllerTakeViewInfo.ContainingItem) { self.offsetContainerNode = ASDisplayNode() self.containingItem = containingItem - + super.init() - + self.addSubnode(self.offsetContainerNode) } @@ -633,6 +758,22 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } let contentNodeValue = ItemContentNode(containingItem: takeInfo.containingItem) contentNodeValue.animateClippingFromContentAreaInScreenSpace = takeInfo.contentAreaInScreenSpace + contentNodeValue.sourceTransitionSurface = takeInfo.sourceTransitionSurface + + // Mirror any ancestor scale on the source (e.g. a sheet's container transform) onto the offset + // container so the extracted contents render at the same visual size as in-place — without this + // they pop to 1:1 when reparented into the unscaled overlay window. + let sourceView = takeInfo.containingItem.view + let modeledWidth = sourceView.bounds.width + if modeledWidth > 0.001 { + let visualWidth = sourceView.convert(sourceView.bounds, to: nil).width + let detectedScale = visualWidth / modeledWidth + if abs(detectedScale - 1.0) > 0.001 { + contentNodeValue.presentationScale = detectedScale + contentNodeValue.offsetContainerNode.layer.transform = CATransform3DMakeScale(detectedScale, detectedScale, 1.0) + } + } + self.scrollNode.insertSubnode(contentNodeValue, aboveSubnode: self.actionsContainerNode) self.itemContentNode = contentNodeValue itemContentNode = contentNodeValue @@ -818,7 +959,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let transitionInfo = reference.transitionInfo() { if let referenceView = transitionInfo.referenceView as? ContextExtractableContainer { if #available(iOS 26.2, *) { - if transitionInfo.referenceView.bounds.width == transitionInfo.referenceView.bounds.height { + if !"".isEmpty, transitionInfo.referenceView.bounds.width == transitionInfo.referenceView.bounds.height { contextExtractableContainer = (referenceView, convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view)) } } @@ -1165,6 +1306,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let contentNode = itemContentNode { var contentFrame = CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset + additionalVisibleOffsetY), size: contentNode.containingItem.view.bounds.size) + // contentRect.minY was derived from storedGlobalFrame.maxY (visual) minus contentRect.height (modeled); + // when an ancestor scale is in effect those don't cancel cleanly, leaving a (1 - scale) * (cy + ch) + // residue that pulls the content upward. Add it back so the content lands at the source's visual Y. + if contentNode.presentationScale != 1.0 { + let cr = contentNode.containingItem.contentRect + contentFrame.origin.y += (1.0 - contentNode.presentationScale) * (cr.minY + cr.height) + } if case let .extracted(extracted) = self.source { if extracted.adjustContentHorizontally { contentFrame.origin.x = combinedActionsFrame.minX @@ -1181,6 +1329,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } contentTransition.updateFrame(node: contentNode, frame: contentFrame, beginWithCurrentState: true) + + // Keep the staging wrapper's frame in sync with ItemContentNode's frame so + // chat-side relayouts that fire mid-animation (e.g. via layoutUpdated → + // requestUpdate after isExtractedToContextPreview = true) shift the portal + // source the same way ItemContentNode shifts. The additive position springs + // on wrapper.layer ("position.x" / "position.y") and this layout-pass + // "position" animation compose: visual = (interpolated layout) + (additive offset). + if let staging = contentNode.portalStaging, let wrapper = staging.wrapper, let surface = staging.surface { + let wrapperFrameInWindow = self.scrollNode.view.convert(contentFrame, to: nil) + let wrapperFrameInSurface = surface.convert(wrapperFrameInWindow, from: nil) + contentTransition.updateFrame(view: wrapper, frame: wrapperFrameInSurface, beginWithCurrentState: true) + } } if let contentNode = controllerContentNode { //TODO: @@ -1244,9 +1404,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo let actionsSize = self.actionsContainerNode.bounds.size if let contentNode = itemContentNode { - contentNode.takeContainingNode() + if contentNode.sourceTransitionSurface != nil && contentNode.presentationScale == 1.0 { + // Portal path: defer reparenting to the staging.enter call below. + } else { + contentNode.takeContainingNode() + } } - + let duration: Double = 0.42 let springDamping: CGFloat = 104.0 @@ -1255,11 +1419,70 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var animationInContentYDistance: CGFloat let currentContentScreenFrame: CGRect if let contentNode = itemContentNode { - if let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace { + let portalAnimationLayer: CALayer? + if let surface = contentNode.sourceTransitionSurface, contentNode.presentationScale == 1.0 { + let staging = PortalTransitionStaging() + // Use ItemContentNode.frame (already includes contentVerticalOffset and + // additionalVisibleOffsetY from the layout pass) plus + // containingItem.contentRect.origin to derive the bubble's actual rest + // window rect. Using bare `contentRect` skips those offsets — when reactions + // are visible (additionalVisibleOffsetY = reactionContextNode.visibleExtensionDistance) + // the wrapper would sit ~10pt above the bubble's true rest, visibly offsetting + // the portal mirror until staging.settle reparents into offsetContainerNode. + // Springs are additive: `from: -distance` at t=0 pulls the wrapper back to + // the chat-side source position; `to: 0` lands at rest. + let bubbleRestRectInScrollNode = CGRect( + x: contentNode.frame.minX + contentNode.containingItem.contentRect.minX, + y: contentNode.frame.minY + contentNode.containingItem.contentRect.minY, + width: contentNode.containingItem.contentRect.width, + height: contentNode.containingItem.contentRect.height + ) + let currentContentLocalFrameInWindow = self.scrollNode.view.convert(bubbleRestRectInScrollNode, to: nil) + if let layer = staging.enter( + for: contentNode.containingItem, + in: surface, + overlayHost: contentNode.offsetContainerNode.view, + targetScreenRect: currentContentLocalFrameInWindow + ) { + contentNode.portalStaging = staging + portalAnimationLayer = layer + + // Mid-animation crossfade: source (chat-tree wrapper, behind chrome) + // is visible at the chat-bubble slot; final (overlay clone, above + // chrome) is visible at the menu position. Final fade-in begins + // `crossfadeOverlap` seconds before source fade-out, so the final + // is already on screen before the source starts dropping out — no + // single-frame seam at the handoff. Each fade runs for a fixed 0.1s. + if let wrapper = staging.wrapper, let clone = staging.clone { + let sourceFadeDelay = 0.12 * duration + let crossfadeOverlap: Double = 0.3 + let crossfadeDuration: Double = 0.1 + let finalFadeDelay = max(0.0, sourceFadeDelay - crossfadeOverlap) + + // Final = clone (visible at end), fades in. + clone.view.alpha = 1.0 + clone.view.layer.allowsGroupOpacity = true + clone.view.layer.animateAlpha(from: 0.01, to: 1.0, duration: crossfadeDuration, delay: finalFadeDelay) + + // Source = wrapper (visible at start), fades out. + wrapper.alpha = 0.0 + wrapper.layer.allowsGroupOpacity = true + wrapper.layer.animateAlpha(from: 1.0, to: 0.0, duration: crossfadeDuration, delay: sourceFadeDelay) + } + } else { + // Staging refused (PortalView nil); fall back to clipping by reparenting now. + contentNode.takeContainingNode() + portalAnimationLayer = nil + } + } else { + portalAnimationLayer = nil + } + + if portalAnimationLayer == nil, let animateClippingFromContentAreaInScreenSpace = contentNode.animateClippingFromContentAreaInScreenSpace { self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 0.0, y: animateClippingFromContentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: animateClippingFromContentAreaInScreenSpace.height)), to: CGRect(origin: CGPoint(), size: layout.size), duration: 0.2) self.clippingNode.layer.animateBoundsOriginYAdditive(from: animateClippingFromContentAreaInScreenSpace.minY, to: 0.0, duration: 0.2) } - + currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) let currentContentLocalFrame = convertFrame(contentRect, from: self.scrollNode.view, to: self.view) animationInContentYDistance = currentContentLocalFrame.maxY - currentContentScreenFrame.maxY @@ -1275,7 +1498,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if actionsSize.height.isZero { var initialContentRect = contentRect initialContentRect.origin.y += extracted.initialAppearanceOffset.y - + let fixedContentY = floorToScreenPixels((layout.size.height - contentHeight) / 2.0) animationInContentYDistance = fixedContentY - initialContentRect.minY } else if contentX + contentWidth > layout.size.width / 2.0, actionsSize.height > 0.0 { @@ -1285,9 +1508,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } else { animationInContentXDistance = contentParentGlobalFrameOffsetX } - + + let animateLayer: CALayer = portalAnimationLayer ?? contentNode.layer + if animationInContentXDistance != 0.0 { - contentNode.layer.animateSpring( + animateLayer.animateSpring( from: -animationInContentXDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.x", duration: duration, @@ -1297,15 +1522,22 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo additive: true ) } - - contentNode.layer.animateSpring( + + animateLayer.animateSpring( from: -animationInContentYDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", duration: duration, delay: 0.0, initialVelocity: 0.0, damping: springDamping, - additive: true + additive: true, + completion: { [weak contentNode] _ in + guard let contentNode else { return } + if let staging = contentNode.portalStaging { + staging.settle(into: .offsetContainer(contentNode.offsetContainerNode)) + contentNode.portalStaging = nil + } + } ) if let reactionPreviewView = self.reactionPreviewView { @@ -1481,8 +1713,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } } + var portalAnimationLayer: CALayer? = nil + let currentContentScreenFrame: CGRect - + switch self.source { case let .location(location): if let putBackInfo = location.transitionInfo() { @@ -1504,12 +1738,55 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo } case let .extracted(source): let putBackInfo = source.putBack() - - if let putBackInfo = putBackInfo { + + if let putBackInfo = putBackInfo, + let surface = putBackInfo.sourceTransitionSurface, + let contentNode = itemContentNode, + contentNode.presentationScale == 1.0 + { + let preStagingScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) + let preStagingScreenFrameInWindow = self.view.convert(preStagingScreenFrame, to: nil) + let staging = PortalTransitionStaging() + if let layer = staging.enter( + for: contentNode.containingItem, + in: surface, + overlayHost: contentNode.offsetContainerNode.view, + targetScreenRect: preStagingScreenFrameInWindow + ) { + contentNode.portalStaging = staging + portalAnimationLayer = layer + + // Mid-dismiss crossfade: clone (overlay portal target, above chrome) + // is visible at the menu position; final (chat-tree wrapper, behind + // chrome) is visible once the bubble settles at the chat-bubble + // slot. Late handoff — clone carries the bubble through most of the + // dismiss; final picks up only as the bubble lands. Source fade-out + // is anchored at 80% of the spring duration; final fade-in starts + // `crossfadeOverlap` seconds earlier. Each fade runs for 0.1s. + if let wrapper = staging.wrapper, let clone = staging.clone { + let sourceFadeDelay = 0.5 * duration + let crossfadeOverlap: Double = 0.3 + let crossfadeDuration: Double = 0.12 + let finalFadeDelay = max(0.0, sourceFadeDelay - crossfadeOverlap) + + // Final = wrapper (visible at end), fades in. + wrapper.alpha = 1.0 + wrapper.layer.allowsGroupOpacity = true + wrapper.layer.animateAlpha(from: 0.01, to: 1.0, duration: crossfadeDuration, delay: finalFadeDelay) + + // Source = clone (visible at start), fades out. + clone.view.alpha = 0.0 + clone.view.layer.allowsGroupOpacity = true + clone.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: crossfadeDuration, delay: sourceFadeDelay) + } + } + } + + if portalAnimationLayer == nil, let putBackInfo = putBackInfo { self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) } - + if let contentNode = itemContentNode { currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view) if currentContentScreenFrame.origin.x < 0.0 { @@ -1542,6 +1819,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo switch result { case .default, .custom: animationInContentYDistance = currentContentLocalFrame.minY - currentContentScreenFrame.minY + // Same modeled-vs-visual mismatch as the static contentFrame compensation: contentRect.minY (used by + // currentContentLocalFrame) was derived with modeled height while the source-side reference uses visual + // height, leaving a `ch * (1 - scale)` residue that animates the content downward on dismiss. + if let contentNode = itemContentNode, contentNode.presentationScale != 1.0 { + animationInContentYDistance += contentNode.containingItem.contentRect.height * (1.0 - contentNode.presentationScale) + } case .dismissWithoutContent: animationInContentYDistance = 0.0 if let contentNode = itemContentNode { @@ -1611,8 +1894,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo animationInContentXDistance = -contentParentGlobalFrameOffsetX } + let animateLayer: CALayer = portalAnimationLayer ?? contentNode.offsetContainerNode.layer + if animationInContentXDistance != 0.0 { - contentNode.offsetContainerNode.layer.animate( + animateLayer.animate( from: -animationInContentXDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.x", @@ -1622,10 +1907,13 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo additive: true ) } - - contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: animationInContentXDistance, dy: -animationInContentYDistance) + + if portalAnimationLayer == nil { + contentNode.offsetContainerNode.position = contentNode.offsetContainerNode.position.offsetBy(dx: animationInContentXDistance, dy: -animationInContentYDistance) + } + let reactionContextNodeIsAnimatingOut = self.reactionContextNodeIsAnimatingOut - contentNode.offsetContainerNode.layer.animate( + animateLayer.animate( from: animationInContentYDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", @@ -1636,18 +1924,25 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo completion: { [weak self] _ in Queue.mainQueue().after(reactionContextNodeIsAnimatingOut ? 0.2 * UIView.animationDurationFactor() : 0.0, { if let strongSelf = self, let contentNode = strongSelf.itemContentNode { - switch contentNode.containingItem { - case let .node(containingNode): - containingNode.addSubnode(containingNode.contentNode) - case let .view(containingView): - containingView.addSubview(containingView.contentView) + if let staging = contentNode.portalStaging { + staging.settle(into: .original) + contentNode.portalStaging = nil + } else { + switch contentNode.containingItem { + case let .node(containingNode): + containingNode.addSubnode(containingNode.contentNode) + case let .view(containingView): + containingView.addSubview(containingView.contentView) + } } } - - contentNode.containingItem.isExtractedToContextPreview = false - contentNode.containingItem.isExtractedToContextPreviewUpdated?(false) - contentNode.containingItem.onDismiss?() - + + if let strongSelf = self, let contentNode = strongSelf.itemContentNode { + contentNode.containingItem.isExtractedToContextPreview = false + contentNode.containingItem.isExtractedToContextPreviewUpdated?(false) + contentNode.containingItem.onDismiss?() + } + restoreOverlayViews.forEach({ $0() }) completion() }) diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerImpl.swift b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerImpl.swift index 1bc22b48f5..940e6132f1 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerImpl.swift +++ b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerImpl.swift @@ -152,6 +152,7 @@ final class ContextControllerNode: ViewControllerTracingNode, ASScrollViewDelega if #available(iOS 11.0, *) { self.scrollNode.view.contentInsetAdjustmentBehavior = .never } + self.scrollNode.view.scrollsToTop = false self.contentContainerNode = ContextContentContainerNode() diff --git a/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD b/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD index 945235a576..45f9626c9b 100644 --- a/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD +++ b/submodules/TelegramUI/Components/EmojiGameStakeScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift b/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift index 2f6a008dc0..6cd8c4fd58 100644 --- a/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift +++ b/submodules/TelegramUI/Components/EmojiGameStakeScreen/Sources/EmojiGameStakeScreen.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import Markdown import TextFormat @@ -1009,7 +1008,7 @@ public final class AmountFieldComponent: Component { case .stars: text = "\(value)" case .ton: - text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: component.dateTimeFormat.decimalSeparator, groupingSeparator: component.dateTimeFormat.groupingSeparator), maxDecimalPositions: nil))" } self.textField.text = text self.placeholderView.view?.isHidden = !(self.textField.text ?? "").isEmpty @@ -1029,7 +1028,7 @@ public final class AmountFieldComponent: Component { case .stars: text = "\(value)" case .ton: - text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), maxDecimalPositions: nil))" + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: component.dateTimeFormat.decimalSeparator, groupingSeparator: component.dateTimeFormat.groupingSeparator), maxDecimalPositions: nil))" } self.textField.text = text self.placeholderView.view?.isHidden = !text.isEmpty diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD index 926aa3334d..495a15c0c6 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/ComponentFlow:ComponentFlow", @@ -25,7 +24,6 @@ swift_library( "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", "//submodules/Components/PagerComponent:PagerComponent", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", - "//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", "//submodules/lottie-ios:Lottie", @@ -34,6 +32,8 @@ swift_library( "//submodules/GZip:GZip", "//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/PresentationDataUtils:PresentationDataUtils", + "//submodules/TelegramUI/Components/ChatTimerScreen", + "//submodules/Components/SheetComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift index 48e767cecf..d2ea6a7413 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift @@ -10,8 +10,9 @@ import ComponentDisplayAdapters import MultilineTextComponent import EmojiStatusComponent import TelegramStringFormatting -import SolidRoundedButtonComponent import PresentationDataUtils +import ChatTimerScreen +import SheetComponent protocol ContextMenuItemWithAction: AnyObject { func performAction() -> ContextMenuPerformActionResult @@ -347,173 +348,116 @@ private final class ContextMenuActionsComponent: Component { private final class TimeSelectionControlComponent: Component { let theme: PresentationTheme let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat let bottomInset: CGFloat + let screenCornerRadius: CGFloat let apply: (Int32) -> Void let cancel: () -> Void init( theme: PresentationTheme, strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, bottomInset: CGFloat, + screenCornerRadius: CGFloat, apply: @escaping (Int32) -> Void, cancel: @escaping () -> Void ) { self.theme = theme self.strings = strings + self.dateTimeFormat = dateTimeFormat self.bottomInset = bottomInset + self.screenCornerRadius = screenCornerRadius self.apply = apply self.cancel = cancel } static func ==(lhs: TimeSelectionControlComponent, rhs: TimeSelectionControlComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.bottomInset != rhs.bottomInset { - return false - } - return true + return false } final class View: UIView { - private let backgroundView: BlurredBackgroundView - private let pickerView: UIDatePicker - private let titleView: ComponentView - private let leftButtonView: ComponentView - private let actionButtonView: ComponentView + private let backgroundView: SheetBackgroundView + private let contentView: ComponentView private var component: TimeSelectionControlComponent? override init(frame: CGRect) { - self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) - self.pickerView = UIDatePicker() - - self.titleView = ComponentView() - self.leftButtonView = ComponentView() - self.actionButtonView = ComponentView() + self.backgroundView = SheetBackgroundView() + self.contentView = ComponentView() super.init(frame: frame) self.addSubview(self.backgroundView) - - self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) - self.pickerView.datePickerMode = .countDownTimer - self.pickerView.datePickerMode = .dateAndTime - if #available(iOS 13.4, *) { - self.pickerView.preferredDatePickerStyle = .wheels - } - self.pickerView.minimumDate = Date(timeIntervalSince1970: Date().timeIntervalSince1970 + Double(TimeZone.current.secondsFromGMT())) - self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) - - self.addSubview(self.pickerView) - self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - @objc private func datePickerUpdated() { - } - func update(component: TimeSelectionControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - if self.component?.theme !== component.theme { - UILabel.setDateLabel(component.theme.list.itemPrimaryTextColor) - - self.pickerView.setValue(component.theme.list.itemPrimaryTextColor, forKey: "textColor") - - self.backgroundView.updateColor(color: component.theme.contextMenu.backgroundColor, transition: .immediate) - } - self.component = component - let topPanelHeight: CGFloat = 54.0 - let pickerSpacing: CGFloat = 10.0 - - let pickerSize = CGSize(width: availableSize.width, height: 216.0) - let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + pickerSpacing), size: pickerSize) - - let titleSize = self.titleView.update( + let glassInset: CGFloat = availableSize.width < availableSize.height ? 6.0 : 0.0 + let topCornerRadius: CGFloat = 38.0 + let bottomCornerRadius: CGFloat = max(24.0, component.screenCornerRadius) - 2.0 + + self.contentView.parentState = state + let contentSize = self.contentView.update( transition: transition, - component: AnyComponent(Text(text: component.strings.EmojiStatusSetup_SetUntil, font: Font.semibold(17.0), color: component.theme.list.itemPrimaryTextColor)), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 100.0) - ) - if let titleComponentView = self.titleView.view { - if titleComponentView.superview == nil { - self.addSubview(titleComponentView) - } - transition.setFrame(view: titleComponentView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((topPanelHeight - titleSize.height) / 2.0)), size: titleSize)) - } - - let leftButtonSize = self.leftButtonView.update( - transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text( - text: component.strings.Common_Cancel, - font: Font.regular(17.0), - color: component.theme.list.itemAccentColor - )), - action: { [weak self] in - self?.component?.cancel() - } - ).minSize(CGSize(width: 16.0, height: topPanelHeight))), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 100.0) - ) - if let leftButtonComponentView = self.leftButtonView.view { - if leftButtonComponentView.superview == nil { - self.addSubview(leftButtonComponentView) - } - transition.setFrame(view: leftButtonComponentView, frame: CGRect(origin: CGPoint(x: 16.0, y: floor((topPanelHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize)) - } - - let actionButtonSize = self.actionButtonView.update( - transition: transition, - component: AnyComponent(SolidRoundedButtonComponent( - title: component.strings.EmojiStatusSetup_SetUntil, - icon: nil, - theme: SolidRoundedButtonComponent.Theme(theme: component.theme), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: false, - action: { [weak self] in - guard let strongSelf = self, let component = strongSelf.component else { + component: AnyComponent(ChatTimerPickerContentComponent( + configuration: ChatTimerScreen.Configuration( + style: .default, + title: { strings in + strings.EmojiStatusSetup_SetUntil + }, + picker: .dateTime, + currentValue: nil, + minimumDate: Date(), + pickerValueMapping: .rawTimestamp, + primaryActionTitle: { strings, _, _ in + strings.EmojiStatusSetup_SetUntil + } + ), + theme: component.theme, + strings: component.strings, + dateTimeFormat: component.dateTimeFormat, + safeInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: component.bottomInset, right: 0.0), + leadingAction: ChatTimerPickerContentComponent.LeadingAction( + icon: .back, + action: { [weak self] in + self?.component?.cancel() + } + ), + completion: { [weak self] value in + guard let self, let value else { return } - - let timestamp = Int32(strongSelf.pickerView.date.timeIntervalSince1970 - Double(TimeZone.current.secondsFromGMT())) - component.apply(timestamp) + self.component?.apply(value) } )), environment: {}, - containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - glassInset * 2.0, height: availableSize.height) ) - let actionButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - actionButtonSize.width) / 2.0), y: pickerFrame.maxY + pickerSpacing), size: actionButtonSize) - if let actionButtonComponentView = self.actionButtonView.view { - if actionButtonComponentView.superview == nil { - self.addSubview(actionButtonComponentView) + let panelFrame = CGRect(origin: CGPoint(x: glassInset, y: 0.0), size: contentSize) + + self.backgroundView.update( + size: contentSize, + color: component.theme.actionSheet.opaqueItemBackgroundColor, + topCornerRadius: topCornerRadius, + bottomCornerRadius: bottomCornerRadius, + transition: transition + ) + transition.setFrame(view: self.backgroundView, frame: panelFrame) + + if let contentComponentView = self.contentView.view { + if contentComponentView.superview == nil { + self.addSubview(contentComponentView) } - transition.setFrame(view: actionButtonComponentView, frame: actionButtonFrame) + transition.setFrame(view: contentComponentView, frame: panelFrame) } - - self.pickerView.frame = pickerFrame - - var size = CGSize(width: availableSize.width, height: actionButtonFrame.maxY) - if component.bottomInset.isZero { - size.height += 10.0 - } else { - size.height += max(10.0, component.bottomInset) - } - - self.backgroundView.update(size: size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) - - return size + + return CGSize(width: availableSize.width, height: contentSize.height + glassInset) } } @@ -553,20 +497,26 @@ final class EmojiStatusPreviewScreenComponent: Component { let theme: PresentationTheme let strings: PresentationStrings + let dateTimeFormat: PresentationDateTimeFormat let bottomInset: CGFloat + let screenCornerRadius: CGFloat let item: EmojiStatusComponent let dismiss: (StatusResult?) -> Void init( theme: PresentationTheme, strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, bottomInset: CGFloat, + screenCornerRadius: CGFloat, item: EmojiStatusComponent, dismiss: @escaping (StatusResult?) -> Void ) { self.theme = theme self.strings = strings + self.dateTimeFormat = dateTimeFormat self.bottomInset = bottomInset + self.screenCornerRadius = screenCornerRadius self.item = item self.dismiss = dismiss } @@ -578,9 +528,15 @@ final class EmojiStatusPreviewScreenComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } if lhs.bottomInset != rhs.bottomInset { return false } + if lhs.screenCornerRadius != rhs.screenCornerRadius { + return false + } if lhs.item != rhs.item { return false } @@ -694,7 +650,9 @@ final class EmojiStatusPreviewScreenComponent: Component { component: AnyComponent(TimeSelectionControlComponent( theme: component.theme, strings: component.strings, + dateTimeFormat: component.dateTimeFormat, bottomInset: component.bottomInset, + screenCornerRadius: component.screenCornerRadius, apply: { [weak self] timestamp in guard let strongSelf = self, let component = strongSelf.component else { return diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 9c2f87ad96..72e5c128cd 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -18,7 +18,6 @@ import TextFormat import AppBundle import GZip import EmojiStatusComponent -import Postbox import TelegramNotices private func randomGenericReactionEffect(context: AccountContext) -> Signal { @@ -38,8 +37,8 @@ private func randomGenericReactionEffect(context: AccountContext) -> Signal filter(\.complete) + let dataDisposable = (context.engine.resources.data(resource: EngineMediaResource(file.resource)) + |> filter(\.isComplete) |> take(1)).start(next: { data in subscriber.putNext(data.path) subscriber.putCompletion() @@ -260,6 +259,7 @@ public final class EmojiStatusSelectionController: ViewController { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -297,6 +297,7 @@ public final class EmojiStatusSelectionController: ViewController { private var scheduledEmojiContentAnimationHint: EmojiPagerContentComponent.ContentAnimation? private let emojiSearchDisposable = MetaDisposable() + private var emojiSearchContext: EmojiSearchContext? private let emojiSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) private var emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) { didSet { @@ -339,24 +340,24 @@ public final class EmojiStatusSelectionController: ViewController { self.componentHost = ComponentView() self.componentShadowLayer = SimpleLayer() - self.componentShadowLayer.shadowOpacity = 0.12 + self.componentShadowLayer.shadowOpacity = 0.35 self.componentShadowLayer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor - self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 2.0) - self.componentShadowLayer.shadowRadius = 16.0 + self.componentShadowLayer.shadowOffset = CGSize(width: 0.0, height: 10.0) + self.componentShadowLayer.shadowRadius = 30.0 self.cloudLayer0 = SimpleLayer() self.cloudShadowLayer0 = SimpleLayer() - self.cloudShadowLayer0.shadowOpacity = 0.12 - self.cloudShadowLayer0.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor - self.cloudShadowLayer0.shadowOffset = CGSize(width: 0.0, height: 2.0) - self.cloudShadowLayer0.shadowRadius = 16.0 + self.cloudShadowLayer0.shadowOpacity = self.componentShadowLayer.shadowOpacity + self.cloudShadowLayer0.shadowColor = self.componentShadowLayer.shadowColor + self.cloudShadowLayer0.shadowOffset = self.componentShadowLayer.shadowOffset + self.cloudShadowLayer0.shadowRadius = self.componentShadowLayer.shadowRadius self.cloudLayer1 = SimpleLayer() self.cloudShadowLayer1 = SimpleLayer() - self.cloudShadowLayer1.shadowOpacity = 0.12 - self.cloudShadowLayer1.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor - self.cloudShadowLayer1.shadowOffset = CGSize(width: 0.0, height: 2.0) - self.cloudShadowLayer1.shadowRadius = 16.0 + self.cloudShadowLayer1.shadowOpacity = self.componentShadowLayer.shadowOpacity + self.cloudShadowLayer1.shadowColor = self.componentShadowLayer.shadowColor + self.cloudShadowLayer1.shadowOffset = self.componentShadowLayer.shadowOffset + self.cloudShadowLayer1.shadowRadius = self.componentShadowLayer.shadowRadius super.init() @@ -412,7 +413,7 @@ public final class EmojiStatusSelectionController: ViewController { } else { strongSelf.stableEmptyResultEmoji = nil } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active, canLoadMore: emojiSearchResult.canLoadMore) } else { strongSelf.stableEmptyResultEmoji = nil } @@ -437,7 +438,7 @@ public final class EmojiStatusSelectionController: ViewController { openSearch: { }, addGroupAction: { groupId, isPremiumLocked, _ in - guard let strongSelf = self, let collectionId = groupId.base as? ItemCollectionId else { + guard let strongSelf = self, let collectionId = groupId.base as? EngineItemCollectionId else { return } @@ -478,20 +479,23 @@ public final class EmojiStatusSelectionController: ViewController { guard let self = self else { return } - + switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) - + if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) } else { let context = self.context - + self.emojiSearchContext = nil + var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { signal = signal @@ -505,7 +509,7 @@ public final class EmojiStatusSelectionController: ViewController { ) } } - + let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> Bool in guard case let .user(user) = peer else { @@ -514,62 +518,60 @@ public final class EmojiStatusSelectionController: ViewController { return user.isPremium } |> distinctUntilChanged - - let resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), - context.engine.stickers.availableReactions(), - hasPremium - ) - |> take(1) - |> map { view, availableReactions, hasPremium -> [EmojiPagerContentComponent.ItemGroup] in - var result: [(String, TelegramMediaFile.Accessor?, String)] = [] - - var allEmoticons: [String: String] = [:] - for keyword in keywords { - for emoticon in keyword.emoticons { - allEmoticons[emoticon] = keyword.keyword - } + + let resultSignal = combineLatest( + signal, + hasPremium + ) + |> mapToSignal { keywords, hasPremium -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in + var allEmoticons: [String: String] = [:] + for keyword in keywords { + for emoticon in keyword.emoticons { + allEmoticons[emoticon] = keyword.keyword } - - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { + } + + let currentEmojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let emojiSearchContext: EmojiSearchContext? = currentEmojiSearchContext + let remoteSignal: Signal = currentEmojiSearchContext.state + let remotePacksSignal: Signal = context.engine.stickers.searchEmojiSets(query: query) + |> mapToSignal { localResult in + return .single(localResult) + |> then( + context.engine.stickers.searchEmojiSetsRemotely(query: query) + |> map { remoteResult in + return localResult.merge(with: remoteResult) + } + ) + } + + return combineLatest(remoteSignal, remotePacksSignal) + |> map { foundEmoji, foundPacks -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in + var items: [EmojiPagerContentComponent.Item] = [] + + var existingIds = Set() + for itemFile in foundEmoji.items { + if existingIds.contains(itemFile.fileId) { continue } - if let alt = item.file.customEmojiAlt { - if !item.file.isPremiumEmoji || hasPremium { - if !alt.isEmpty, let keyword = allEmoticons[alt] { - result.append((alt, item.file, keyword)) - } else if alt == query { - result.append((alt, item.file, alt)) - } - } + existingIds.insert(itemFile.fileId) + if itemFile.isPremiumEmoji && !hasPremium { + continue } + let animationData = EntityKeyboardAnimationData(file: TelegramMediaFile.Accessor(itemFile)) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: TelegramMediaFile.Accessor(itemFile), + subgroupId: nil, + icon: .none, + tintMode: animationData.isTemplate ? .primary : .none + ) + items.append(item) } - - var items: [EmojiPagerContentComponent.Item] = [] - - var existingIds = Set() - for item in result { - if let itemFile = item.1 { - if existingIds.contains(itemFile.fileId) { - continue - } - existingIds.insert(itemFile.fileId) - let animationData = EntityKeyboardAnimationData(file: itemFile) - let item = EmojiPagerContentComponent.Item( - animationData: animationData, - content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, - icon: .none, - tintMode: animationData.isTemplate ? .primary : .none - ) - items.append(item) - } - } - - return [EmojiPagerContentComponent.ItemGroup( + + var resultGroups: [EmojiPagerContentComponent.ItemGroup] = [] + resultGroups.append(EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", title: nil, @@ -586,10 +588,64 @@ public final class EmojiStatusSelectionController: ViewController { headerItem: nil, fillWithLoadingPlaceholders: false, items: items - )] + )) + + for (collectionId, info, _, _) in foundPacks.infos { + if let info = info as? StickerPackCollectionInfo { + var topItems: [StickerPackItem] = [] + for e in foundPacks.entries { + if let item = e.item as? StickerPackItem { + if e.index.collectionId == collectionId { + topItems.append(item) + } + } + } + + var groupItems: [EmojiPagerContentComponent.Item] = [] + for item in topItems { + var tintMode: EmojiPagerContentComponent.Item.TintMode = .none + if item.file.isCustomTemplateEmoji { + tintMode = .primary + } + + let animationData = EntityKeyboardAnimationData(file: item.file) + let resultItem = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: item.file, + subgroupId: nil, + icon: .none, + tintMode: tintMode + ) + + groupItems.append(resultItem) + } + + resultGroups.append(EmojiPagerContentComponent.ItemGroup( + supergroupId: AnyHashable(info.id), + groupId: AnyHashable(info.id), + title: info.title, + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: 3, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: groupItems + )) + } + } + + return (resultGroups, foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } - + var version = 0 self.emojiSearchStateValue.isSearching = true self.emojiSearchDisposable.set((resultSignal @@ -598,12 +654,14 @@ public final class EmojiStatusSelectionController: ViewController { guard let self else { return } - - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.emojiSearchContext = nil let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -677,11 +735,11 @@ public final class EmojiStatusSelectionController: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -689,6 +747,9 @@ public final class EmojiStatusSelectionController: ViewController { updateScrollingToItemGroup: { }, onScroll: {}, + loadMore: { + self?.emojiSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -973,16 +1034,12 @@ public final class EmojiStatusSelectionController: ViewController { if self.presentationData.theme.overallDarkAppearance { listBackgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor separatorColor = self.presentationData.theme.list.itemBlocksSeparatorColor - self.componentShadowLayer.shadowOpacity = 0.32 - self.cloudShadowLayer0.shadowOpacity = 0.32 - self.cloudShadowLayer1.shadowOpacity = 0.32 } else { listBackgroundColor = self.presentationData.theme.list.plainBackgroundColor separatorColor = self.presentationData.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5) - self.componentShadowLayer.shadowOpacity = 0.12 - self.cloudShadowLayer0.shadowOpacity = 0.12 - self.cloudShadowLayer1.shadowOpacity = 0.12 } + self.cloudShadowLayer0.shadowOpacity = self.componentShadowLayer.shadowOpacity + self.cloudShadowLayer1.shadowOpacity = self.componentShadowLayer.shadowOpacity self.cloudLayer0.backgroundColor = listBackgroundColor.cgColor self.cloudLayer1.backgroundColor = listBackgroundColor.cgColor @@ -1159,7 +1216,9 @@ public final class EmojiStatusSelectionController: ViewController { component: AnyComponent(EmojiStatusPreviewScreenComponent( theme: self.presentationData.theme, strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, bottomInset: layout.insets(options: []).bottom, + screenCornerRadius: layout.deviceMetrics.screenCornerRadius, item: EmojiStatusComponent( context: self.context, animationCache: self.context.animationCache, @@ -1207,10 +1266,10 @@ public final class EmojiStatusSelectionController: ViewController { for reaction in availableReactions.reactions { if case let .builtin(value) = reaction.value, value == emojiString { if let aroundAnimation = reaction.aroundAnimation?._parse() { - return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + return context.engine.resources.data(resource: EngineMediaResource(aroundAnimation.resource)) |> take(1) |> map { data -> String? in - if data.complete { + if data.isComplete { return data.path } else { return nil @@ -1396,10 +1455,10 @@ public final class EmojiStatusSelectionController: ViewController { for reaction in availableReactions.reactions { if case let .builtin(value) = reaction.value, value == emojiString { if let aroundAnimation = reaction.aroundAnimation?._parse() { - return context.account.postbox.mediaBox.resourceData(aroundAnimation.resource) + return context.engine.resources.data(resource: EngineMediaResource(aroundAnimation.resource)) |> take(1) |> map { data -> String? in - if data.complete { + if data.isComplete { return data.path } else { return nil @@ -1430,12 +1489,7 @@ public final class EmojiStatusSelectionController: ViewController { } } - public enum Mode { - case statusSelection - case backgroundSelection(completion: (TelegramMediaFile?) -> Void) - case customStatusSelection(completion: (TelegramMediaFile?, Int32?) -> Void) - case quickReactionSelection(completion: () -> Void) - } + public typealias Mode = EmojiStatusSelectionControllerMode private let context: AccountContext private weak var sourceView: UIView? diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD index 14b1bac8ef..6c78adcc19 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/BUILD @@ -17,7 +17,6 @@ swift_library( "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", "//submodules/AccountContext:AccountContext", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TextFormat:TextFormat", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/BUILD b/submodules/TelegramUI/Components/EntityKeyboard/BUILD index 1c0a044379..d82defe799 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboard/BUILD @@ -40,7 +40,7 @@ swift_library( "//submodules/AppBundle:AppBundle", "//submodules/UndoUI:UndoUI", "//submodules/Components/MultilineTextComponent:MultilineTextComponent", - "//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent", + "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/LocalizedPeerData:LocalizedPeerData", "//submodules/TelegramNotices:TelegramNotices", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 18695c713d..7e8a44a1a6 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import MultiAnimationRenderer import AnimationCache import AccountContext @@ -20,7 +19,8 @@ import StickerResources import AppBundle import UndoUI import AudioToolbox -import SolidRoundedButtonComponent +import ButtonComponent +import LottieComponent import EmojiTextAttachmentView import EmojiStatusComponent import TelegramNotices @@ -38,8 +38,8 @@ public struct EmojiComponentReactionItem: Equatable { public final class EntityKeyboardAnimationData: Equatable { public enum Id: Hashable { - case file(MediaId) - case stickerPackThumbnail(ItemCollectionId) + case file(EngineMedia.Id) + case stickerPackThumbnail(EngineItemCollectionId) case gift(String) } @@ -266,7 +266,8 @@ public final class EmojiPagerContentComponent: Component { public let updateScrollingToItemGroup: () -> Void public let externalCancel: (() -> Void)? public let onScroll: () -> Void - public let chatPeerId: PeerId? + public let loadMore: (() -> Void)? + public let chatPeerId: EnginePeer.Id? public let peekBehavior: EmojiContentPeekBehavior? public let customLayout: CustomLayout? public let externalBackground: ExternalBackground? @@ -296,7 +297,8 @@ public final class EmojiPagerContentComponent: Component { updateScrollingToItemGroup: @escaping () -> Void, externalCancel: (() -> Void)? = nil, onScroll: @escaping () -> Void, - chatPeerId: PeerId?, + loadMore: (() -> Void)? = nil, + chatPeerId: EnginePeer.Id?, peekBehavior: EmojiContentPeekBehavior?, customLayout: CustomLayout?, externalBackground: ExternalBackground?, @@ -324,6 +326,7 @@ public final class EmojiPagerContentComponent: Component { self.updateScrollingToItemGroup = updateScrollingToItemGroup self.externalCancel = externalCancel self.onScroll = onScroll + self.loadMore = loadMore self.chatPeerId = chatPeerId self.peekBehavior = peekBehavior self.customLayout = customLayout @@ -626,6 +629,7 @@ public final class EmojiPagerContentComponent: Component { public let contentItemGroups: [ItemGroup] public let itemLayoutType: ItemLayoutType public let itemContentUniqueId: ContentId? + public let canLoadMore: Bool public let searchState: SearchState public let warpContentsOnEdges: Bool public let hideBackground: Bool @@ -652,6 +656,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: [ItemGroup], itemLayoutType: ItemLayoutType, itemContentUniqueId: ContentId?, + canLoadMore: Bool = false, searchState: SearchState, warpContentsOnEdges: Bool, hideBackground: Bool, @@ -677,6 +682,7 @@ public final class EmojiPagerContentComponent: Component { self.contentItemGroups = contentItemGroups self.itemLayoutType = itemLayoutType self.itemContentUniqueId = itemContentUniqueId + self.canLoadMore = canLoadMore self.searchState = searchState self.warpContentsOnEdges = warpContentsOnEdges self.hideBackground = hideBackground @@ -693,7 +699,7 @@ public final class EmojiPagerContentComponent: Component { self.customTintColor = customTintColor } - public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: ContentId?, emptySearchResults: EmptySearchResults?, searchState: SearchState) -> EmojiPagerContentComponent { + public func withUpdatedItemGroups(panelItemGroups: [ItemGroup], contentItemGroups: [ItemGroup], itemContentUniqueId: ContentId?, emptySearchResults: EmptySearchResults?, searchState: SearchState, canLoadMore: Bool? = nil) -> EmojiPagerContentComponent { return EmojiPagerContentComponent( id: self.id, context: self.context, @@ -705,6 +711,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: contentItemGroups, itemLayoutType: self.itemLayoutType, itemContentUniqueId: itemContentUniqueId, + canLoadMore: canLoadMore ?? self.canLoadMore, searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, @@ -722,7 +729,7 @@ public final class EmojiPagerContentComponent: Component { ) } - public func withSelectedItems(_ selectedItems: Set) -> EmojiPagerContentComponent { + public func withSelectedItems(_ selectedItems: Set) -> EmojiPagerContentComponent { return EmojiPagerContentComponent( id: self.id, context: self.context, @@ -734,6 +741,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: contentItemGroups, itemLayoutType: self.itemLayoutType, itemContentUniqueId: itemContentUniqueId, + canLoadMore: self.canLoadMore, searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, @@ -763,6 +771,7 @@ public final class EmojiPagerContentComponent: Component { contentItemGroups: contentItemGroups, itemLayoutType: self.itemLayoutType, itemContentUniqueId: itemContentUniqueId, + canLoadMore: self.canLoadMore, searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, @@ -814,6 +823,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.itemContentUniqueId != rhs.itemContentUniqueId { return false } + if lhs.canLoadMore != rhs.canLoadMore { + return false + } if lhs.searchState != rhs.searchState { return false } @@ -1395,6 +1407,7 @@ public final class EmojiPagerContentComponent: Component { private var vibrancyClippingView: UIView private var vibrancyEffectView: UIView? public private(set) var mirrorContentClippingView: UIView? + private let mirrorScrollViewClippingView: UIView private let mirrorContentScrollView: UIView private var warpView: WarpView? private var mirrorContentWarpView: WarpView? @@ -1418,6 +1431,7 @@ public final class EmojiPagerContentComponent: Component { private var visibleGroupPremiumButtons: [AnyHashable: ComponentView] = [:] private var visibleGroupExpandActionButtons: [AnyHashable: GroupExpandActionButton] = [:] private var expandedGroupIds: Set = Set() + private var requestedLoadMoreContentId: ContentId? private var ignoreScrolling: Bool = false private var keepTopPanelVisibleUntilScrollingInput: Bool = false @@ -1445,6 +1459,13 @@ public final class EmojiPagerContentComponent: Component { private var tapRecognizer: UITapGestureRecognizer? private var longTapRecognizer: UILongPressGestureRecognizer? + private func hasSameContentId(_ lhs: ContentId?, _ rhs: ContentId?) -> Bool { + if let rhs, rhs.version < 2 { + return false + } + return lhs?.id == rhs?.id + } + override init(frame: CGRect) { self.backgroundView = BlurredBackgroundView(color: nil) self.backgroundTintView = UIView() @@ -1463,6 +1484,9 @@ public final class EmojiPagerContentComponent: Component { self.scrollViewClippingView = UIView() self.scrollViewClippingView.clipsToBounds = true + self.mirrorScrollViewClippingView = UIView() + self.mirrorScrollViewClippingView.clipsToBounds = true + self.mirrorContentScrollView = UIView() self.mirrorContentScrollView.layer.anchorPoint = CGPoint() self.mirrorContentScrollView.clipsToBounds = true @@ -1502,6 +1526,8 @@ public final class EmojiPagerContentComponent: Component { self.addSubview(self.scrollViewClippingView) self.scrollViewClippingView.addSubview(self.scrollView) + self.mirrorScrollViewClippingView.addSubview(self.mirrorContentScrollView) + self.scrollView.addSubview(self.placeholdersContainerView) let contextGesture = ContextGesture(target: self, action: #selector(self.tapGesture(_:))) @@ -1641,6 +1667,16 @@ public final class EmojiPagerContentComponent: Component { fatalError("init(coder:) has not been implemented") } + private var mirrorOverlayContainerView: UIView? { + if let mirrorContentClippingView = self.mirrorContentClippingView { + return mirrorContentClippingView + } else if let vibrancyEffectView = self.vibrancyEffectView { + return vibrancyEffectView + } else { + return nil + } + } + func updateIsWarpEnabled(isEnabled: Bool) { if isEnabled { if self.warpView == nil { @@ -1654,6 +1690,7 @@ public final class EmojiPagerContentComponent: Component { let mirrorContentWarpView = WarpView(frame: CGRect()) self.mirrorContentWarpView = mirrorContentWarpView + self.mirrorScrollViewClippingView.addSubview(mirrorContentWarpView) mirrorContentWarpView.contentView.addSubview(self.mirrorContentScrollView) } } else { @@ -1666,12 +1703,7 @@ public final class EmojiPagerContentComponent: Component { if let mirrorContentWarpView = self.mirrorContentWarpView { self.mirrorContentWarpView = nil - if let mirrorContentClippingView = self.mirrorContentClippingView { - mirrorContentClippingView.addSubview(self.mirrorContentScrollView) - } else if let vibrancyEffectView = self.vibrancyEffectView { - vibrancyEffectView.addSubview(self.mirrorContentScrollView) - } - + self.mirrorScrollViewClippingView.addSubview(self.mirrorContentScrollView) mirrorContentWarpView.removeFromSuperview() } } @@ -1714,7 +1746,7 @@ public final class EmojiPagerContentComponent: Component { } } - public func animateInReactionSelection(sourceItems: [MediaId: (frame: CGRect, cornerRadius: CGFloat, frameIndex: Int, placeholder: UIImage)]) { + public func animateInReactionSelection(sourceItems: [EngineMedia.Id: (frame: CGRect, cornerRadius: CGFloat, frameIndex: Int, placeholder: UIImage)]) { guard let component = self.component, let itemLayout = self.itemLayout else { return } @@ -3003,6 +3035,8 @@ public final class EmojiPagerContentComponent: Component { if let stateContext = self.component?.inputInteractionHolder.inputInteraction?.stateContext { stateContext.scrollPosition = scrollView.bounds.minY } + + self.maybeLoadMore() } public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { @@ -3321,23 +3355,40 @@ public final class EmojiPagerContentComponent: Component { gloss = false } + var groupPremiumButtonItems: [AnyComponentWithIdentity] = [] + groupPremiumButtonItems.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(ButtonTextContentComponent( + text: title, + badge: 0, + textColor: foregroundColor, + badgeBackground: foregroundColor, + badgeForeground: backgroundColor + )))) + if let animationName { + groupPremiumButtonItems.append(AnyComponentWithIdentity(id: "animation", component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: animationName), + color: foregroundColor, + startingPosition: .begin, + size: CGSize(width: 30.0, height: 30.0), + loop: true + )))) + } + let groupPremiumButtonSize = groupPremiumButton.update( transition: groupPremiumButtonTransition, - component: AnyComponent(SolidRoundedButtonComponent( - title: title, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: backgroundColor, - backgroundColors: backgroundColors, - foregroundColor: foregroundColor + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: backgroundColor, + foreground: foregroundColor, + pressedColor: backgroundColor.withMultipliedAlpha(0.8), + cornerRadius: groupBorderRadius, + isShimmering: gloss, + gradient: backgroundColors.count > 1 ? ButtonComponent.Background.Gradient(colors: backgroundColors) : nil + ), + content: AnyComponentWithIdentity( + id: AnyHashable("\(title)-\(animationName ?? "")"), + component: AnyComponent(HStack(groupPremiumButtonItems, spacing: 4.0)) ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: groupBorderRadius, - gloss: gloss, - animationName: animationName, - iconPosition: .right, - iconSpacing: 4.0, action: { [weak self] in guard let strongSelf = self, let component = strongSelf.component else { return @@ -3824,7 +3875,7 @@ public final class EmojiPagerContentComponent: Component { } var removedItemSelectionLayerIds: [EmojiKeyboardItemLayer.Key] = [] for (id, itemSelectionLayer) in self.visibleItemSelectionLayers { - var fileId: MediaId? + var fileId: EngineMedia.Id? switch id.itemId { case let .animation(id): switch id { @@ -4048,6 +4099,37 @@ public final class EmojiPagerContentComponent: Component { } } + private func maybeLoadMore() { + guard let component = self.component, !self.isUpdating else { + return + } + guard component.canLoadMore, let contentId = component.itemContentUniqueId else { + self.requestedLoadMoreContentId = nil + return + } + guard let loadMore = component.inputInteractionHolder.inputInteraction?.loadMore else { + return + } + + let loadMoreBoundary: CGFloat + if component.contentItemGroups.first?.groupId == AnyHashable("search"), let itemLayout = self.itemLayout, itemLayout.itemGroupLayouts.count > 1 { + loadMoreBoundary = itemLayout.itemGroupLayouts[1].frame.minY + } else { + loadMoreBoundary = self.scrollView.contentSize.height + } + + let remainingDistance = loadMoreBoundary - self.scrollView.bounds.maxY + if remainingDistance > 200.0 { + return + } + if self.requestedLoadMoreContentId == contentId { + return + } + + self.requestedLoadMoreContentId = contentId + loadMore() + } + private func expandGroup(groupId: AnyHashable) { self.expandedGroupIds.insert(groupId) @@ -4067,12 +4149,11 @@ public final class EmojiPagerContentComponent: Component { mirrorContentClippingView = UIView() mirrorContentClippingView.clipsToBounds = false self.mirrorContentClippingView = mirrorContentClippingView - - if let mirrorContentWarpView = self.mirrorContentWarpView { - mirrorContentClippingView.addSubview(mirrorContentWarpView) - } else { - mirrorContentClippingView.addSubview(self.mirrorContentScrollView) - } + } + if self.mirrorScrollViewClippingView.superview !== mirrorContentClippingView { + mirrorContentClippingView.insertSubview(self.mirrorScrollViewClippingView, at: 0) + } else { + mirrorContentClippingView.sendSubviewToBack(self.mirrorScrollViewClippingView) } let clippingFrame = CGRect(origin: CGPoint(x: 0.0, y: pagerEnvironment.containerInsets.top), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height)) @@ -4096,9 +4177,13 @@ public final class EmojiPagerContentComponent: Component { } self.vibrancyEffectView = vibrancyEffectView self.backgroundTintView.mask = vibrancyEffectView - self.vibrancyClippingView.addSubview(self.mirrorContentScrollView) vibrancyEffectView.addSubview(self.vibrancyClippingView) } + if self.mirrorScrollViewClippingView.superview !== self.vibrancyClippingView { + self.vibrancyClippingView.insertSubview(self.mirrorScrollViewClippingView, at: 0) + } else { + self.vibrancyClippingView.sendSubviewToBack(self.mirrorScrollViewClippingView) + } } if component.hideBackground { @@ -4283,7 +4368,7 @@ public final class EmojiPagerContentComponent: Component { var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:] var anchorItems: [EmojiKeyboardItemLayer.Key: CGRect] = [:] - if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.contentItemGroups != component.contentItemGroups, previousComponent.itemContentUniqueId == component.itemContentUniqueId { + if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.contentItemGroups != component.contentItemGroups, self.hasSameContentId(previousComponent.itemContentUniqueId, component.itemContentUniqueId) { if !transition.animation.isImmediate { var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:] for groupIndex in 0 ..< previousComponent.contentItemGroups.count { @@ -4405,7 +4490,7 @@ public final class EmojiPagerContentComponent: Component { if previousComponent == nil { isFirstUpdate = true } - if previousComponent?.itemContentUniqueId != component.itemContentUniqueId { + if !self.hasSameContentId(previousComponent?.itemContentUniqueId, component.itemContentUniqueId) { resetScrolling = true } if resetScrolling { @@ -4413,8 +4498,8 @@ public final class EmojiPagerContentComponent: Component { } var animateContentCrossfade = false - if let previousComponent, previousComponent.itemContentUniqueId != component.itemContentUniqueId, itemTransition.animation.isImmediate { - if !(previousComponent.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders }) && component.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders })) && previousComponent.itemContentUniqueId?.id != component.itemContentUniqueId?.id { + if let previousComponent, !self.hasSameContentId(previousComponent.itemContentUniqueId, component.itemContentUniqueId), itemTransition.animation.isImmediate { + if !(previousComponent.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders }) && component.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders })) { animateContentCrossfade = true } } @@ -4501,6 +4586,9 @@ public final class EmojiPagerContentComponent: Component { transition.setFrame(view: self.vibrancyClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize)) transition.setBounds(view: self.vibrancyClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize)) + transition.setFrame(view: self.mirrorScrollViewClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize)) + transition.setBounds(view: self.mirrorScrollViewClippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.isSearchActivated ? clippingTopInset : 0.0), size: availableSize)) + let previousSize = self.scrollView.bounds.size self.scrollView.bounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollSize) @@ -4590,10 +4678,12 @@ public final class EmojiPagerContentComponent: Component { } let previousBounds = self.scrollView.bounds - self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) let scrollOffset = previousBounds.minY - contentOffsetY - transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: scrollOffset), to: CGPoint(), additive: true) - animatedScrollOffset = scrollOffset + if abs(scrollOffset) > 0.5 { + self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffsetY), animated: false) + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: scrollOffset), to: CGPoint(), additive: true) + animatedScrollOffset = scrollOffset + } break outer } @@ -4662,11 +4752,9 @@ public final class EmojiPagerContentComponent: Component { if self.isSearchActivated { if visibleSearchHeader.superview != self { self.addSubview(visibleSearchHeader) - if self.mirrorContentClippingView != nil { - self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView) - } else { - self.mirrorContentScrollView.superview?.superview?.addSubview(visibleSearchHeader.tintContainerView) - } + } + if let mirrorOverlayContainerView = self.mirrorOverlayContainerView, visibleSearchHeader.tintContainerView.superview !== mirrorOverlayContainerView { + mirrorOverlayContainerView.addSubview(visibleSearchHeader.tintContainerView) } } else { /*if useOpaqueTheme { @@ -4727,7 +4815,7 @@ public final class EmojiPagerContentComponent: Component { self.visibleSearchHeader = visibleSearchHeader if self.isSearchActivated { self.addSubview(visibleSearchHeader) - self.mirrorContentClippingView?.addSubview(visibleSearchHeader.tintContainerView) + self.mirrorOverlayContainerView?.addSubview(visibleSearchHeader.tintContainerView) } else { self.scrollView.addSubview(visibleSearchHeader) self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index df4dd2efbb..c7b08f6fa2 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1241,6 +1241,9 @@ public extension EmojiPagerContentComponent { tintMode: tintMode ) case let .text(text): + if !areUnicodeEmojiEnabled { + continue + } resultItem = EmojiPagerContentComponent.Item( animationData: nil, content: .staticEmoji(text), diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 3967917a8b..0ab2b9c3df 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import TelegramPresentationData import TelegramCore -import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext @@ -42,7 +41,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode private let context: AccountContext private let forceTheme: PresentationTheme? - private var initialFocusId: ItemCollectionId? + private var initialFocusId: EngineItemCollectionId? private let hasPremiumForUse: Bool private let hasPremiumForInstallation: Bool private let parentInputInteraction: EmojiPagerContentComponent.InputInteraction @@ -73,7 +72,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode context: AccountContext, forceTheme: PresentationTheme?, items: [FeaturedStickerPackItem], - initialFocusId: ItemCollectionId?, + initialFocusId: EngineItemCollectionId?, hasPremiumForUse: Bool, hasPremiumForInstallation: Bool, parentInputInteraction: EmojiPagerContentComponent.InputInteraction @@ -263,7 +262,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for item in result { if let itemFile = item.1 { if existingIds.contains(itemFile.fileId) { @@ -321,7 +320,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { continue diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index d0839eceeb..c370c29594 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchStatusComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchStatusComponent.swift index 441b19ba9e..871678bb2c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchStatusComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchStatusComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index c525591808..e03665b427 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import BundleIconComponent import AudioToolbox import SwiftSignalKit @@ -1005,6 +1004,14 @@ public final class EntityKeyboardComponent: Component { pagerView.collapseTopPanel() } + public func revealHiddenPanels() { + guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent.View else { + return + } + + pagerView.revealHiddenPanels() + } + private func reorderPacks(category: ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) { self.component?.reorderItems(category, items) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift index 83c09abbe0..d5b92ee876 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox public final class EntityKeyboardTopContainerPanelEnvironment: Equatable { let isContentInFocus: Bool @@ -13,7 +12,7 @@ public final class EntityKeyboardTopContainerPanelEnvironment: Equatable { let visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)> let isExpandedUpdated: (Bool, ComponentTransition) -> Void - init( + public init( isContentInFocus: Bool, height: CGFloat, visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index a28088ad5d..5cfbcad852 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -6,7 +6,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext @@ -1218,11 +1217,14 @@ public final class EntityKeyboardTopPanelComponent: Component { let containerSideInset: CGFloat let defaultActiveItemId: AnyHashable? let forceActiveItemId: AnyHashable? + let displayHighlightInExpanded: Bool + let automaticallySelectsFirstItem: Bool + let itemSpacing: CGFloat let activeContentItemIdUpdated: ActionSlot<(AnyHashable, AnyHashable?, ComponentTransition)> let activeContentItemMapping: [AnyHashable: AnyHashable] let reorderItems: ([Item]) -> Void - init( + public init( id: AnyHashable, theme: PresentationTheme, customTintColor: UIColor?, @@ -1230,6 +1232,9 @@ public final class EntityKeyboardTopPanelComponent: Component { containerSideInset: CGFloat, defaultActiveItemId: AnyHashable? = nil, forceActiveItemId: AnyHashable? = nil, + displayHighlightInExpanded: Bool = false, + automaticallySelectsFirstItem: Bool = true, + itemSpacing: CGFloat = 8.0, activeContentItemIdUpdated: ActionSlot<(AnyHashable, AnyHashable?, ComponentTransition)>, activeContentItemMapping: [AnyHashable: AnyHashable] = [:], reorderItems: @escaping ([Item]) -> Void @@ -1241,6 +1246,9 @@ public final class EntityKeyboardTopPanelComponent: Component { self.containerSideInset = containerSideInset self.defaultActiveItemId = defaultActiveItemId self.forceActiveItemId = forceActiveItemId + self.displayHighlightInExpanded = displayHighlightInExpanded + self.automaticallySelectsFirstItem = automaticallySelectsFirstItem + self.itemSpacing = itemSpacing self.activeContentItemIdUpdated = activeContentItemIdUpdated self.activeContentItemMapping = activeContentItemMapping self.reorderItems = reorderItems @@ -1268,6 +1276,15 @@ public final class EntityKeyboardTopPanelComponent: Component { if lhs.forceActiveItemId != rhs.forceActiveItemId { return false } + if lhs.displayHighlightInExpanded != rhs.displayHighlightInExpanded { + return false + } + if lhs.automaticallySelectsFirstItem != rhs.automaticallySelectsFirstItem { + return false + } + if lhs.itemSpacing != rhs.itemSpacing { + return false + } if lhs.activeContentItemIdUpdated !== rhs.activeContentItemIdUpdated { return false } @@ -1393,7 +1410,7 @@ public final class EntityKeyboardTopPanelComponent: Component { let isExpanded: Bool let items: [Item] - init(isExpanded: Bool, containerSideInset: CGFloat, height: CGFloat, items: [ItemDescription]) { + init(isExpanded: Bool, containerSideInset: CGFloat, height: CGFloat, itemSpacing: CGFloat, items: [ItemDescription]) { self.sideInset = containerSideInset + 7.0 self.isExpanded = isExpanded @@ -1401,7 +1418,7 @@ public final class EntityKeyboardTopPanelComponent: Component { self.staticItemSize = self.itemSize self.staticExpandedItemSize = self.isExpanded ? self.staticItemSize : CGSize(width: 134.0, height: 28.0) self.innerItemSize = self.isExpanded ? CGSize(width: 50.0, height: 62.0) : CGSize(width: 24.0, height: 24.0) - self.itemSpacing = 8.0 + self.itemSpacing = itemSpacing var contentSize = CGSize(width: sideInset, height: height) var resultItems: [Item] = [] @@ -2002,6 +2019,9 @@ public final class EntityKeyboardTopPanelComponent: Component { if let forceActiveItemId = component.forceActiveItemId { self.activeContentItemId = forceActiveItemId + } else if component.defaultActiveItemId == nil && !component.automaticallySelectsFirstItem { + self.activeContentItemId = nil + self.activeSubcontentItemId = nil } else if self.activeContentItemId == nil, let defaultActiveItemId = component.defaultActiveItemId { self.activeContentItemId = defaultActiveItemId } @@ -2045,12 +2065,12 @@ public final class EntityKeyboardTopPanelComponent: Component { } self.items = items - if self.activeContentItemId == nil { + if self.activeContentItemId == nil && component.automaticallySelectsFirstItem { self.activeContentItemId = items.first?.id } let previousItemLayout = self.itemLayout - let itemLayout = ItemLayout(isExpanded: isExpanded, containerSideInset: component.containerSideInset, height: availableSize.height, items: self.items.map { item -> ItemLayout.ItemDescription in + let itemLayout = ItemLayout(isExpanded: isExpanded, containerSideInset: component.containerSideInset, height: availableSize.height, itemSpacing: component.itemSpacing, items: self.items.map { item -> ItemLayout.ItemDescription in let isStatic = item.id == AnyHashable("static") return ItemLayout.ItemDescription( isStatic: isStatic, @@ -2063,7 +2083,14 @@ public final class EntityKeyboardTopPanelComponent: Component { var updatedBounds: CGRect? if wasExpanded != isExpanded, let previousItemLayout = previousItemLayout { - if !isExpanded { + let keepInitialScrollOffset = component.displayHighlightInExpanded && self.scrollView.bounds.origin.x <= 1.0 + if keepInitialScrollOffset { + let maxContentOffsetX = max(0.0, itemLayout.contentSize.width - availableSize.width) + updatedBounds = CGRect( + origin: CGPoint(x: min(max(0.0, self.scrollView.bounds.origin.x), maxContentOffsetX), y: 0.0), + size: availableSize + ) + } else if !isExpanded { if let draggingEndOffset = self.draggingEndOffset { if abs(self.scrollView.contentOffset.x - draggingEndOffset) > 16.0 { self.draggingFocusItemIndex = nil @@ -2073,105 +2100,107 @@ public final class EntityKeyboardTopPanelComponent: Component { } } - var visibleBounds = self.scrollView.bounds - visibleBounds.origin.x -= 280.0 - visibleBounds.size.width += 560.0 - - let previousVisibleRange = previousItemLayout.visibleItemRange(for: visibleBounds) - if previousVisibleRange.minIndex <= previousVisibleRange.maxIndex { - var itemIndex = self.draggingFocusItemIndex ?? ((previousVisibleRange.minIndex + previousVisibleRange.maxIndex) / 2) - if !isExpanded { - if self.scrollView.bounds.maxX >= self.scrollView.contentSize.width { - itemIndex = component.items.count - 1 - } - if self.scrollView.bounds.minX <= 0.0 { - itemIndex = 0 - } - } + if !keepInitialScrollOffset { + var visibleBounds = self.scrollView.bounds + visibleBounds.origin.x -= 280.0 + visibleBounds.size.width += 560.0 - var previousItemFrame = previousItemLayout.containerFrame(at: itemIndex) - var updatedItemFrame = itemLayout.containerFrame(at: itemIndex) - - let previousDistanceToItem = (previousItemFrame.minX - self.scrollView.bounds.minX) - let previousDistanceToItemRight = (previousItemFrame.maxX - self.scrollView.bounds.maxX) - var newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.minX - previousDistanceToItem, y: 0.0), size: availableSize) - var useRightAnchor = false - if newBounds.minX > itemLayout.contentSize.width - self.scrollView.bounds.width { - newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width - itemIndex = component.items.count - 1 - useRightAnchor = true - } - if itemIndex == component.items.count - 1 { - useRightAnchor = true - } - if newBounds.minX < 0.0 { - newBounds.origin.x = 0.0 - itemIndex = 0 - useRightAnchor = false - } - - if useRightAnchor { - let _ = previousDistanceToItemRight - newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width - } - - previousItemFrame = previousItemLayout.containerFrame(at: itemIndex) - updatedItemFrame = itemLayout.containerFrame(at: itemIndex) - - self.draggingFocusItemIndex = itemIndex - - updatedBounds = newBounds - - var updatedVisibleBounds = newBounds - updatedVisibleBounds.origin.x -= 280.0 - updatedVisibleBounds.size.width += 560.0 - let updatedVisibleRange = itemLayout.visibleItemRange(for: updatedVisibleBounds) - - if useRightAnchor { - let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.maxX - previousItemFrame.width, y: previousItemFrame.minY), size: previousItemFrame.size) - for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex { - let indexDifference = index - itemIndex - if let itemView = self.itemViews[self.items[index].id] { - let itemContainerMaxX = baseFrame.maxX + CGFloat(indexDifference) * (previousItemLayout.itemSize.width + previousItemLayout.itemSpacing) - let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerMaxX - baseFrame.width, y: baseFrame.minY), size: baseFrame.size) - let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame) - - let itemSize = itemView.bounds.size - itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize) - - if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id { - self.highlightedIconBackgroundView.frame = itemOuterFrame - self.highlightedIconTintBackgroundView.frame = itemOuterFrame - } + let previousVisibleRange = previousItemLayout.visibleItemRange(for: visibleBounds) + if previousVisibleRange.minIndex <= previousVisibleRange.maxIndex { + var itemIndex = self.draggingFocusItemIndex ?? ((previousVisibleRange.minIndex + previousVisibleRange.maxIndex) / 2) + if !isExpanded { + if self.scrollView.bounds.maxX >= self.scrollView.contentSize.width { + itemIndex = component.items.count - 1 + } + if self.scrollView.bounds.minX <= 0.0 { + itemIndex = 0 } } - } else { - let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.minX, y: previousItemFrame.minY), size: previousItemFrame.size) - for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex { - let indexDifference = index - itemIndex - if let itemView = self.itemViews[self.items[index].id] { - var itemContainerOriginX = baseFrame.minX - if indexDifference > 0 { - for i in 0 ..< indexDifference { - itemContainerOriginX += previousItemLayout.itemSpacing - itemContainerOriginX += previousItemLayout.containerFrame(at: itemIndex + i).width - } - } else if indexDifference < 0 { - for i in 0 ..< (-indexDifference) { - itemContainerOriginX -= previousItemLayout.itemSpacing - itemContainerOriginX -= previousItemLayout.containerFrame(at: itemIndex - i - 1).width + + var previousItemFrame = previousItemLayout.containerFrame(at: itemIndex) + var updatedItemFrame = itemLayout.containerFrame(at: itemIndex) + + let previousDistanceToItem = (previousItemFrame.minX - self.scrollView.bounds.minX) + let previousDistanceToItemRight = (previousItemFrame.maxX - self.scrollView.bounds.maxX) + var newBounds = CGRect(origin: CGPoint(x: updatedItemFrame.minX - previousDistanceToItem, y: 0.0), size: availableSize) + var useRightAnchor = false + if newBounds.minX > itemLayout.contentSize.width - self.scrollView.bounds.width { + newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width + itemIndex = component.items.count - 1 + useRightAnchor = true + } + if itemIndex == component.items.count - 1 { + useRightAnchor = true + } + if newBounds.minX < 0.0 { + newBounds.origin.x = 0.0 + itemIndex = 0 + useRightAnchor = false + } + + if useRightAnchor { + let _ = previousDistanceToItemRight + newBounds.origin.x = itemLayout.contentSize.width - self.scrollView.bounds.width + } + + previousItemFrame = previousItemLayout.containerFrame(at: itemIndex) + updatedItemFrame = itemLayout.containerFrame(at: itemIndex) + + self.draggingFocusItemIndex = itemIndex + + updatedBounds = newBounds + + var updatedVisibleBounds = newBounds + updatedVisibleBounds.origin.x -= 280.0 + updatedVisibleBounds.size.width += 560.0 + let updatedVisibleRange = itemLayout.visibleItemRange(for: updatedVisibleBounds) + + if useRightAnchor { + let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.maxX - previousItemFrame.width, y: previousItemFrame.minY), size: previousItemFrame.size) + for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex { + let indexDifference = index - itemIndex + if let itemView = self.itemViews[self.items[index].id] { + let itemContainerMaxX = baseFrame.maxX + CGFloat(indexDifference) * (previousItemLayout.itemSize.width + previousItemLayout.itemSpacing) + let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerMaxX - baseFrame.width, y: baseFrame.minY), size: baseFrame.size) + let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame) + + let itemSize = itemView.bounds.size + itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize) + + if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id { + self.highlightedIconBackgroundView.frame = itemOuterFrame + self.highlightedIconTintBackgroundView.frame = itemOuterFrame } } - - let previousContainerFrame = previousItemLayout.containerFrame(at: index) - let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerOriginX, y: previousContainerFrame.minY), size: previousContainerFrame.size) - let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame) - - let itemSize = itemView.bounds.size - itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize) - - if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id { - self.highlightedIconBackgroundView.frame = itemOuterFrame + } + } else { + let baseFrame = CGRect(origin: CGPoint(x: updatedItemFrame.minX, y: previousItemFrame.minY), size: previousItemFrame.size) + for index in updatedVisibleRange.minIndex ... updatedVisibleRange.maxIndex { + let indexDifference = index - itemIndex + if let itemView = self.itemViews[self.items[index].id] { + var itemContainerOriginX = baseFrame.minX + if indexDifference > 0 { + for i in 0 ..< indexDifference { + itemContainerOriginX += previousItemLayout.itemSpacing + itemContainerOriginX += previousItemLayout.containerFrame(at: itemIndex + i).width + } + } else if indexDifference < 0 { + for i in 0 ..< (-indexDifference) { + itemContainerOriginX -= previousItemLayout.itemSpacing + itemContainerOriginX -= previousItemLayout.containerFrame(at: itemIndex - i - 1).width + } + } + + let previousContainerFrame = previousItemLayout.containerFrame(at: index) + let itemContainerFrame = CGRect(origin: CGPoint(x: itemContainerOriginX, y: previousContainerFrame.minY), size: previousContainerFrame.size) + let itemOuterFrame = previousItemLayout.contentFrame(index: index, containerFrame: itemContainerFrame) + + let itemSize = itemView.bounds.size + itemView.frame = CGRect(origin: CGPoint(x: itemOuterFrame.minX + floor((itemOuterFrame.width - itemSize.width) / 2.0), y: itemOuterFrame.minY + floor((itemOuterFrame.height - itemSize.height) / 2.0)), size: itemSize) + + if let activeContentItemId = self.activeContentItemId, activeContentItemId == self.items[index].id { + self.highlightedIconBackgroundView.frame = itemOuterFrame + } } } } @@ -2197,7 +2226,10 @@ public final class EntityKeyboardTopPanelComponent: Component { if let activeContentItemId = self.activeContentItemId { if let index = self.items.firstIndex(where: { $0.id == activeContentItemId }) { - let itemFrame = itemLayout.containerFrame(at: index) + var itemFrame = itemLayout.containerFrame(at: index) + if isExpanded && component.displayHighlightInExpanded { + itemFrame = CGRect(origin: CGPoint(x: itemFrame.midX - itemFrame.height / 2.0, y: itemFrame.minY), size: CGSize(width: itemFrame.height, height: itemFrame.height)) + } var highlightTransition = transition if self.highlightedIconBackgroundView.isHidden { @@ -2228,8 +2260,9 @@ public final class EntityKeyboardTopPanelComponent: Component { self.highlightedIconBackgroundView.isHidden = true self.highlightedIconTintBackgroundView.isHidden = true } - transition.setAlpha(view: self.highlightedIconBackgroundView, alpha: isExpanded ? 0.0 : 1.0) - transition.setAlpha(view: self.highlightedIconTintBackgroundView, alpha: isExpanded ? 0.0 : 1.0) + let highlightAlpha: CGFloat = isExpanded && !component.displayHighlightInExpanded ? 0.0 : 1.0 + transition.setAlpha(view: self.highlightedIconBackgroundView, alpha: highlightAlpha) + transition.setAlpha(view: self.highlightedIconTintBackgroundView, alpha: highlightAlpha) panelEnvironment.visibilityFractionUpdated.connect { [weak self] (fraction, transition) in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift index 05ba06d5b9..a1328d0d09 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntitySearchContentComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import AnimationCache import MultiAnimationRenderer import AccountContext diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index e21a915d81..d4a84ca4a8 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import MultiAnimationRenderer import AnimationCache import AccountContext @@ -248,7 +247,7 @@ public final class GifPagerContentComponent: Component { } fileprivate enum ItemKey: Hashable { - case media(MediaId) + case media(EngineMedia.Id) case placeholder(Int) } diff --git a/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD b/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD index cc15d62131..1888991dca 100644 --- a/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD +++ b/submodules/TelegramUI/Components/EntityKeyboardGifContent/BUILD @@ -19,7 +19,6 @@ swift_library( "//submodules/Components/BundleIconComponent:BundleIconComponent", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", "//submodules/YuvConversion:YuvConversion", diff --git a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift index b888b84842..c022173264 100644 --- a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift +++ b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import MultiplexedVideoNode @@ -96,7 +95,7 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset if let content = externalReference.content { imageResource = content.resource if let resource = content.resource as? WebFileReferenceMediaResource { - uniqueId = Int64(HashFunctions.murMurHash32(resource.url)) + uniqueId = Int64(EngineHashFunctions.murMurHash32(resource.url)) } } if let thumbnail = externalReference.thumbnail { @@ -126,7 +125,7 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset )) } } - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) } case let .internalReference(internalReference): @@ -339,7 +338,7 @@ public final class GifContext { gifItems = combineLatest(hasRecentGifs, paneGifSearchForQuery(context: context, query: query.joined(separator: ""), offset: token, incompleteResults: true, staleCachedResults: true, delayRequest: false, updateActivity: nil), searchCategories) |> map { hasRecentGifs, result, searchCategories -> EntityKeyboardGifContent in var items: [GifPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for item in componentValue.component.items { items.append(item) existingIds.insert(item.file.media.fileId) diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift index 92288fabc3..2745c356db 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerification.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import FileMediaResourceStatus @@ -74,7 +73,7 @@ public func ageVerificationAvailability(context: AccountContext) -> Signal take(1) |> mapToSignal { maybeFileAndMessage -> Signal in if let (file, message) = maybeFileAndMessage { - let fetchedData = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .file, reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource)) + let fetchedData = context.engine.resources.fetch(reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource), userLocation: .other, userContentType: .file) enum FetchStatus { case completed(String) @@ -84,8 +83,8 @@ public func ageVerificationAvailability(context: AccountContext) -> Signal { subscriber in let fetchedDisposable = fetchedData.start() - let resourceDataDisposable = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: false).start(next: { next in - if next.complete { + let resourceDataDisposable = context.engine.resources.data(resource: EngineMediaResource(file.resource)).start(next: { next in + if next.isComplete { SSZipArchive.unzipFile(atPath: next.path, toDestination: NSTemporaryDirectory()) subscriber.putNext(.completed(compiledModelPath)) subscriber.putCompletion() diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index 431f4cd4c1..b9d0243ea6 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -28,7 +28,7 @@ public func requireAgeVerification(context: AccountContext) -> Bool { } public func requireAgeVerification(context: AccountContext, peer: EnginePeer) -> Signal { - if requireAgeVerification(context: context), peer._asPeer().hasSensitiveContent(platform: "ios") { + if requireAgeVerification(context: context), peer.hasSensitiveContent(platform: "ios") { return context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.ContentSettings()) |> map { contentSettings in if !contentSettings.ignoreContentRestrictionReasons.contains("sensitive") { diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 0a0b5a4028..4eecc29534 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -15,7 +15,6 @@ import MultilineTextComponent import EmojiStatusComponent import PremiumUI import ProgressNavigationButtonNode -import Postbox import SwitchComponent private final class TitleFieldComponent: Component { @@ -898,21 +897,17 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { openSearch: { }, addGroupAction: { groupId, isPremiumLocked, _ in - guard let collectionId = groupId.base as? ItemCollectionId else { + guard let collectionId = groupId.base as? EngineItemCollectionId else { return } - - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (accountContext.account.postbox.combinedView(keys: [viewKey]) + + let _ = (accountContext.engine.data.subscribe(TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()) |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + |> deliverOnMainQueue).start(next: { items in + for featuredEmojiPack in items { if featuredEmojiPack.info.id == collectionId { let _ = accountContext.engine.stickers.addStickerPackInteractively(info: featuredEmojiPack.info._parse(), items: featuredEmojiPack.topItems).start() - + break } } @@ -1020,10 +1015,10 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { switch mode { case .create: title = presentationData.strings.CreateTopic_CreateTitle - doneTitle = presentationData.strings.CreateTopic_Create + doneTitle = "___done" case let .edit(threadId, topic, isHidden): title = presentationData.strings.CreateTopic_EditTitle - doneTitle = presentationData.strings.Common_Done + doneTitle = "___done" self.state = (topic.title, topic.icon, topic.iconColor, threadId == 1 ? isHidden : nil) } @@ -1032,7 +1027,7 @@ public class ForumCreateTopicScreen: ViewControllerComponentContainer { self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.doneBarItem = UIBarButtonItem(title: doneTitle, style: .done, target: self, action: #selector(self.createPressed)) self.navigationItem.rightBarButtonItem = self.doneBarItem diff --git a/submodules/TelegramUI/Components/ForumSettingsScreen/BUILD b/submodules/TelegramUI/Components/ForumSettingsScreen/BUILD index aca03b4f73..24513601ad 100644 --- a/submodules/TelegramUI/Components/ForumSettingsScreen/BUILD +++ b/submodules/TelegramUI/Components/ForumSettingsScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift index e458503b03..ee74f81b9f 100644 --- a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -205,7 +204,7 @@ final class ForumSettingsScreenComponent: Component { return context.engine.peers.setChannelForumMode(id: upgradedPeerId, isForum: true, displayForumAsTabs: mode == .tabs) }) |> map(Optional.init) - |> `catch` { [weak self] error -> Signal in + |> `catch` { [weak self] error -> Signal in guard let self, let controller = self.environment?.controller() else { return .single(nil) } @@ -221,7 +220,7 @@ final class ForumSettingsScreenComponent: Component { } return .single(nil) } - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in guard let upgradedPeerId = upgradedPeerId else { return .single(nil) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD index 9579870f15..1569acc58e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift index 35cd956873..f4f94e025f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftAnimationComponent/Sources/GiftCompositionComponent.swift @@ -226,7 +226,7 @@ public final class GiftCompositionComponent: Component { let node = DefaultAnimatedStickerNodeImpl() node.isUserInteractionEnabled = false - let pathPrefix = self.component!.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = self.component!.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) node.setup( source: AnimatedStickerResourceSource(account: self.component!.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(geom.iconSize.width * 1.6), @@ -345,7 +345,7 @@ public final class GiftCompositionComponent: Component { let node = DefaultAnimatedStickerNodeImpl() node.isUserInteractionEnabled = false - let pathPrefix = self.component!.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = self.component!.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) node.setup( source: AnimatedStickerResourceSource(account: self.component!.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(iconSize.width * 1.6), @@ -957,7 +957,7 @@ public final class GiftCompositionComponent: Component { node.isUserInteractionEnabled = false self.animationNode = node self.addSubview(node.view) - let pathPrefix = component.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = component.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) node.setup(source: AnimatedStickerResourceSource(account: component.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(iconSize.width * 1.6), height: Int(iconSize.height * 1.6), playbackMode: loop ? .loop : .once, diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD index d0a949560d..f662b2c9dd 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift index 600d686a89..700d57b821 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import AppBundle import LocalMediaResources @@ -367,7 +366,7 @@ private final class CraftGiftPageContent: Component { let anvilPath = getAppBundle().url(forResource: "Anvil", withExtension: "tgs")?.path ?? "" let anvilFile = TelegramMediaFile( - fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: -123456789), + fileId: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: -123456789), partialReference: nil, resource: BundleResource(name: "Anvil", path: anvilPath), previewRepresentations: [], diff --git a/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD index 152dfb21de..5804b8f474 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/Sources/GiftDemoScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/Sources/GiftDemoScreen.swift index 0935ae9f48..6601741a90 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/Sources/GiftDemoScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftDemoScreen/Sources/GiftDemoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD index fc62375e5c..729f8a2dc2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 840df9d0fb..73e329bf11 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -345,6 +345,7 @@ public final class GiftItemComponent: Component { private var selectionLayer: SimpleShapeLayer? private var checkLayer: CheckLayer? private var outlineLayer: SimpleLayer? + private var outlineRenderState: OutlineRenderState? private var animationFile: TelegramMediaFile? @@ -361,6 +362,14 @@ public final class GiftItemComponent: Component { private var giftAuctionTimer: SwiftSignalKit.Timer? + private struct OutlineRenderState: Equatable { + let size: CGSize + let cornerRadius: CGFloat + let outline: GiftItemComponent.Outline + let hasRibbon: Bool + let themeId: ObjectIdentifier + } + public var pattern: UIView? { if let view = self.patternView.view { return view @@ -1537,6 +1546,14 @@ public final class GiftItemComponent: Component { if let outline = component.outline { let lineWidth: CGFloat = 2.0 let outlineFrame = backgroundFrame + let hasRibbon = self.ribbon.layer.superlayer != nil + let outlineRenderState = OutlineRenderState( + size: outlineFrame.size, + cornerRadius: cornerRadius, + outline: outline, + hasRibbon: hasRibbon, + themeId: ObjectIdentifier(component.theme) + ) let outlineLayer: SimpleLayer if let current = self.outlineLayer { @@ -1544,12 +1561,15 @@ public final class GiftItemComponent: Component { } else { outlineLayer = SimpleLayer() self.outlineLayer = outlineLayer - if self.ribbon.layer.superlayer != nil { - self.layer.insertSublayer(outlineLayer, below: self.ribbon.layer) - } else { - self.layer.addSublayer(outlineLayer) - } - + } + + if hasRibbon { + self.layer.insertSublayer(outlineLayer, below: self.ribbon.layer) + } else if outlineLayer.superlayer == nil { + self.layer.addSublayer(outlineLayer) + } + + if self.outlineRenderState != outlineRenderState { let image = generateImage(outlineFrame.size, rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) @@ -1588,11 +1608,13 @@ public final class GiftItemComponent: Component { } }) outlineLayer.contents = image?.cgImage - - outlineLayer.frame = outlineFrame + self.outlineRenderState = outlineRenderState } + + transition.setFrame(layer: outlineLayer, frame: outlineFrame) } else if let outlineLayer = self.outlineLayer { self.outlineLayer = nil + self.outlineRenderState = nil outlineLayer.removeFromSuperlayer() } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD index 089497fb3f..1820be66b0 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 5dd8687f81..429cf39c84 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -927,7 +926,7 @@ final class GiftOptionsScreenComponent: Component { if let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .gifts, avatarInitiallyExpanded: false, fromChat: false, diff --git a/submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent/BUILD b/submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent/BUILD index 71148481be..1ea1bebc03 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index 56467283cd..a155663706 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -220,21 +219,21 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { var insets: UIEdgeInsets let separatorHeight = UIScreenPixel - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let chatPeerId = item.chatPeerId ?? peerId var items: [ListViewItem] = [] for _ in 0 ..< 1 { let authorPeerId = item.context.account.peerId - var peers = SimpleDictionary() - let messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + let messages = EngineSimpleDictionary() for peer in item.peers { peers[peer.id] = peer._asPeer() } - let media: [Media] + let media: [EngineRawMedia] switch item.subject { case let .premium(months, amount, currency): media = [ @@ -250,7 +249,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { ] } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 36637edf47..0db09aed45 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -104,6 +103,7 @@ private final class GiftSetupScreenComponent: Component { private let backgroundHandleView: UIImageView private let closeButton = ComponentView() + private var doneButton: ComponentView? private let remainingCount = ComponentView() private let auctionFooter = ComponentView() @@ -376,7 +376,7 @@ private final class GiftSetupScreenComponent: Component { let (currency, amount) = storeProduct.priceCurrencyAndAmount - addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_accept") + component.context.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_accept") var textInputText = NSAttributedString() if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View, case let .text(text) = inputPanelView.getSendMessageInput() { @@ -450,7 +450,7 @@ private final class GiftSetupScreenComponent: Component { } if let errorText { - addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_fail") + component.context.engine.accountData.addAppLogEvent(type: "premium_gift.promo_screen_fail") let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) controller.present(alertController, in: .window(.root)) @@ -766,7 +766,6 @@ private final class GiftSetupScreenComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -778,8 +777,6 @@ private final class GiftSetupScreenComponent: Component { businessIntro: nil ) - self.inputMediaNodeBackground.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor.cgColor - let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight < 100.0 ? inputHeight - bottomContainerInset : inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false) let inputNodeHeight = heightAndOverflow.0 let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight)) @@ -1150,6 +1147,65 @@ private final class GiftSetupScreenComponent: Component { transition.setFrame(view: closeButtonView, frame: closeButtonFrame) } + if environment.inputHeight > 0.0 { + var doneButtonTransition = transition + let doneButton: ComponentView + if let current = self.doneButton { + doneButton = current + } else { + doneButton = ComponentView() + self.doneButton = doneButton + doneButtonTransition = .immediate + } + + let doneButtonSize = doneButton.update( + transition: .immediate, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: environment.theme.list.itemCheckColors.fillColor, + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + self?.proceed() + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rawSideInset - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = doneButton.view { + if doneButtonView.superview == nil { + doneButtonView.layer.rasterizationScale = UIScreenScale + self.navigationBarContainer.addSubview(doneButtonView) + + if !transition.animation.isImmediate { + doneButtonView.layer.shouldRasterize = true + transition.animateScale(view: doneButtonView, from: 0.01, to: 1.0, completion: { _ in + doneButtonView.layer.shouldRasterize = false + }) + transition.animateAlpha(view: doneButtonView, from: 0.0, to: 1.0) + } + } + doneButtonTransition.setPosition(view: doneButtonView, position: doneButtonFrame.center) + doneButtonTransition.setBounds(view: doneButtonView, bounds: CGRect(origin: .zero, size: doneButtonFrame.size)) + } + } else if let doneButton = self.doneButton { + self.doneButton = nil + if let doneButtonView = doneButton.view { + doneButtonView.layer.shouldRasterize = true + doneButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + doneButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + doneButtonView.removeFromSuperview() + }) + } + } + let containerInset: CGFloat = environment.statusBarHeight + 10.0 var initialContentHeight = contentHeight @@ -1409,14 +1465,15 @@ private final class GiftSetupScreenComponent: Component { ) )), ], alignment: .left, spacing: 2.0)), - accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: presentationStringsFormattedNumber(Int32(availability.resale), environment.dateTimeFormat.groupingSeparator), font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: theme.list.itemSecondaryTextColor )), - maximumNumberOfLines: 0 - ))), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 16.0))), + maximumNumberOfLines: 1 + )))), + accessory: .arrow, action: { [weak self] _ in guard let self, let component = self.component, let controller = environment.controller() else { return diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD index 8c45e836c9..9eedd1221b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftAttributeListContextItem.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftAttributeListContextItem.swift index ac2c3e8a56..4a240c11c0 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftAttributeListContextItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftAttributeListContextItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -249,6 +248,7 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu self.scrollNode.view.alwaysBounceVertical = false self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0) + self.scrollNode.view.scrollsToTop = false } func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -518,20 +518,20 @@ private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenu } -private func stringTokens(_ string: String) -> [ValueBoxKey] { +private func stringTokens(_ string: String) -> [EngineDataBuffer] { let nsString = string.folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString let flag = UInt(kCFStringTokenizerUnitWord) let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent()) var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer) - var tokens: [ValueBoxKey] = [] + var tokens: [EngineDataBuffer] = [] - var addedTokens = Set() + var addedTokens = Set() while tokenType != [] { let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer) if currentTokenRange.location >= 0 && currentTokenRange.length != 0 { - let token = ValueBoxKey(length: currentTokenRange.length * 2) + let token = EngineDataBuffer(length: currentTokenRange.length * 2) nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length)) if !addedTokens.contains(token) { tokens.append(token) @@ -544,7 +544,7 @@ private func stringTokens(_ string: String) -> [ValueBoxKey] { return tokens } -private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool { +private func matchStringTokens(_ tokens: [EngineDataBuffer], with other: [EngineDataBuffer]) -> Bool { if other.isEmpty { return false } else if other.count == 1 { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index 2474a534a5..79e9e74846 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/SearchContextItem.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/SearchContextItem.swift index 2ce78ae467..ad05c448c1 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/SearchContextItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/SearchContextItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Gifts/GiftUnpinScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftUnpinScreen/BUILD index 87a0e849f6..0ff4e96a58 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftUnpinScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftUnpinScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index 3f8e64ed20..3dad3ead7d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift index 639cfdbca7..8233b0ae21 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift index 07fe56a270..ac8b784202 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift index 167c6b3cbe..6df1fa2907 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionWearPreviewScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift index 0c6c076076..3aa503fbd8 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift index 603c4f3692..0cae6f8d8a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftPurchaseAlertController.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift index f461e20c20..179a7259a3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftRemoveInfoAlertController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift index 9d26d18013..322655a19f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftSaleAlertController.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift index 1d3fae6d47..93fee375a2 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift index 053e762281..9da9a67308 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftValueScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index c2eb599ab8..3888683dc1 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -410,7 +409,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let profileController = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: peer.id == context.account.peerId ? .myProfileGifts : .gifts, avatarInitiallyExpanded: false, fromChat: false, @@ -558,7 +557,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let controller = self.context.sharedContext.makePeerInfoController( context: self.context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: giftsPeerId == self.context.account.peerId ? .myProfileGifts : .gifts, avatarInitiallyExpanded: false, fromChat: false, @@ -620,7 +619,7 @@ private final class GiftViewSheetContent: CombinedComponent { title: presentationData.strings.Gift_Convert_Title, text: text, actions: [ - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Gift_Convert_Convert, action: { [weak self, weak controller, weak navigationController] in guard let self else { return @@ -751,8 +750,8 @@ private final class GiftViewSheetContent: CombinedComponent { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift { let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo } - let updatedMedia: [Media] = [ - TelegramMediaAction( + let updatedMedia: [EngineMedia] = [ + .action(TelegramMediaAction( action: .starGiftUnique( gift: .unique(uniqueGift.withAttributes(updatedAttributes)), isUpgrade: isUpgrade, @@ -774,15 +773,15 @@ private final class GiftViewSheetContent: CombinedComponent { canCraftAt: canCraftAt, isCrafted: isCrafted ) - ) + )) ] - var mappedPeers: [PeerId: EnginePeer] = [:] + var mappedPeers: [EnginePeer.Id: EnginePeer] = [:] for (id, peer) in message.peers { mappedPeers[id] = EnginePeer(peer) } - var mappedAssociatedMessages: [MessageId: EngineMessage] = [:] + var mappedAssociatedMessages: [EngineMessage.Id: EngineMessage] = [:] for (id, message) in message.associatedMessages { mappedAssociatedMessages[id] = EngineMessage(message) } @@ -805,7 +804,7 @@ private final class GiftViewSheetContent: CombinedComponent { author: message.author, text: message.text, attributes: message.attributes, - media: updatedMedia.map { EngineMedia($0) }, + media: updatedMedia, peers: mappedPeers, associatedMessages: mappedAssociatedMessages, associatedMessageIds: message.associatedMessageIds, @@ -2249,7 +2248,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .upgradableGifts, avatarInitiallyExpanded: false, fromChat: false, @@ -5062,7 +5061,7 @@ private final class GiftViewSheetContent: CombinedComponent { delay = true } - let upgradeMessageId = MessageId(peerId: peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) + let upgradeMessageId = EngineMessage.Id(peerId: peerId, namespace: originalMessageId.namespace, id: upgradeMessageIdId) let buttonTitle = strings.Gift_View_ViewUpgraded buttonChild = button.update( component: ButtonComponent( diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift index e9621f1325..276c4b38b6 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftWithdrawAlertController.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD index 363e3ee0c3..9cf0a4c016 100644 --- a/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/PeerTableCellComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/ComponentFlow", "//submodules/TelegramPresentationData", "//submodules/TelegramCore", diff --git a/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD b/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD index 54dc0f8a77..d785b9a0f7 100644 --- a/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD +++ b/submodules/TelegramUI/Components/Gifts/TableComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/ComponentFlow", "//submodules/TelegramPresentationData", "//submodules/Components/MultilineTextComponent", diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 68bfbc9b92..39fa7c4fc4 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -270,12 +270,141 @@ public class GlassBackgroundView: UIView { } } + public struct CornerRadii: Equatable { + public let topLeft: CGFloat + public let topRight: CGFloat + public let bottomLeft: CGFloat + public let bottomRight: CGFloat + + public init(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) { + self.topLeft = topLeft + self.topRight = topRight + self.bottomLeft = bottomLeft + self.bottomRight = bottomRight + } + + public init(radius: CGFloat) { + self.init(topLeft: radius, topRight: radius, bottomLeft: radius, bottomRight: radius) + } + + fileprivate func insetBy(_ value: CGFloat) -> CornerRadii { + return CornerRadii( + topLeft: max(0.0, self.topLeft - value), + topRight: max(0.0, self.topRight - value), + bottomLeft: max(0.0, self.bottomLeft - value), + bottomRight: max(0.0, self.bottomRight - value) + ) + } + + fileprivate var maximum: CGFloat { + return max(max(self.topLeft, self.topRight), max(self.bottomLeft, self.bottomRight)) + } + } + public enum Shape: Equatable { case roundedRect(cornerRadius: CGFloat) + case customRoundedRect(cornerRadii: CornerRadii) + + fileprivate func cornerRadii(for size: CGSize) -> CornerRadii { + switch self { + case let .roundedRect(cornerRadius): + return GlassBackgroundView.clampedCornerRadii(size: size, cornerRadii: CornerRadii(radius: cornerRadius)) + case let .customRoundedRect(cornerRadii): + return GlassBackgroundView.clampedCornerRadii(size: size, cornerRadii: cornerRadii) + } + } + + func maximumCornerRadius(for size: CGSize) -> CGFloat { + return self.cornerRadii(for: size).maximum + } + } + + static func clampedCornerRadii(size: CGSize, cornerRadii: CornerRadii) -> CornerRadii { + let size = CGSize(width: max(0.0, size.width), height: max(0.0, size.height)) + var cornerRadii = CornerRadii( + topLeft: max(0.0, cornerRadii.topLeft), + topRight: max(0.0, cornerRadii.topRight), + bottomLeft: max(0.0, cornerRadii.bottomLeft), + bottomRight: max(0.0, cornerRadii.bottomRight) + ) + + func scaleFor(edgeLength: CGFloat, _ lhs: CGFloat, _ rhs: CGFloat) -> CGFloat { + let sum = lhs + rhs + if sum <= edgeLength || sum.isZero { + return 1.0 + } + return edgeLength / sum + } + + let scale = min( + 1.0, + scaleFor(edgeLength: size.width, cornerRadii.topLeft, cornerRadii.topRight), + scaleFor(edgeLength: size.width, cornerRadii.bottomLeft, cornerRadii.bottomRight), + scaleFor(edgeLength: size.height, cornerRadii.topLeft, cornerRadii.bottomLeft), + scaleFor(edgeLength: size.height, cornerRadii.topRight, cornerRadii.bottomRight) + ) + + if scale < 1.0 { + cornerRadii = CornerRadii( + topLeft: cornerRadii.topLeft * scale, + topRight: cornerRadii.topRight * scale, + bottomLeft: cornerRadii.bottomLeft * scale, + bottomRight: cornerRadii.bottomRight * scale + ) + } + + return cornerRadii + } + + static func generateRoundedRectPath(rect: CGRect, cornerRadii: CornerRadii) -> CGPath { + let cornerRadii = self.clampedCornerRadii(size: rect.size, cornerRadii: cornerRadii) + let path = CGMutablePath() + + func addCorner(tangent1End: CGPoint, tangent2End: CGPoint, radius: CGFloat) { + if radius > CGFloat.ulpOfOne { + path.addArc(tangent1End: tangent1End, tangent2End: tangent2End, radius: radius) + } else { + path.addLine(to: tangent1End) + path.addLine(to: tangent2End) + } + } + + path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadii.topLeft)) + addCorner( + tangent1End: CGPoint(x: rect.minX, y: rect.minY), + tangent2End: CGPoint(x: rect.minX + cornerRadii.topLeft, y: rect.minY), + radius: cornerRadii.topLeft + ) + path.addLine(to: CGPoint(x: rect.maxX - cornerRadii.topRight, y: rect.minY)) + addCorner( + tangent1End: CGPoint(x: rect.maxX, y: rect.minY), + tangent2End: CGPoint(x: rect.maxX, y: rect.minY + cornerRadii.topRight), + radius: cornerRadii.topRight + ) + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadii.bottomRight)) + addCorner( + tangent1End: CGPoint(x: rect.maxX, y: rect.maxY), + tangent2End: CGPoint(x: rect.maxX - cornerRadii.bottomRight, y: rect.maxY), + radius: cornerRadii.bottomRight + ) + path.addLine(to: CGPoint(x: rect.minX + cornerRadii.bottomLeft, y: rect.maxY)) + addCorner( + tangent1End: CGPoint(x: rect.minX, y: rect.maxY), + tangent2End: CGPoint(x: rect.minX, y: rect.maxY - cornerRadii.bottomLeft), + radius: cornerRadii.bottomLeft + ) + path.closeSubpath() + + return path + } + + static func generateRoundedRectPath(size: CGSize, cornerRadii: CornerRadii) -> CGPath { + return self.generateRoundedRectPath(rect: CGRect(origin: CGPoint(), size: size), cornerRadii: cornerRadii) } private final class ClippingShapeContext { let view: UIView + private var maskLayer: CAShapeLayer? private(set) var shape: Shape? @@ -288,7 +417,33 @@ public class GlassBackgroundView: UIView { switch shape { case let .roundedRect(cornerRadius): + self.maskLayer = nil + self.view.layer.mask = nil transition.setCornerRadius(layer: self.view.layer, cornerRadius: cornerRadius) + case let .customRoundedRect(cornerRadii): + transition.setCornerRadius(layer: self.view.layer, cornerRadius: 0.0) + if #available(iOS 26.0, *) { + transition.animateView { + self.view.cornerConfiguration = .corners( + topLeftRadius: .fixed(cornerRadii.topLeft), + topRightRadius: .fixed(cornerRadii.topRight), + bottomLeftRadius: .fixed(cornerRadii.bottomLeft), + bottomRightRadius: .fixed(cornerRadii.bottomRight) + ) + } + } else { + let maskLayer: CAShapeLayer + if let current = self.maskLayer { + maskLayer = current + } else { + maskLayer = CAShapeLayer() + maskLayer.fillColor = UIColor.black.cgColor + self.maskLayer = maskLayer + self.view.layer.mask = maskLayer + } + transition.setFrame(layer: maskLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setShapeLayerPath(layer: maskLayer, path: GlassBackgroundView.generateRoundedRectPath(size: size, cornerRadii: cornerRadii)) + } } } } @@ -311,6 +466,7 @@ public class GlassBackgroundView: UIView { private let legacyView: LegacyGlassView? private let legacyHighlightContainerView: UIView? + private let legacyHighlightClippingContext: ClippingShapeContext? private var glassHighlightRecognizer: GlassHighlightGestureRecognizer? private let nativeView: UIVisualEffectView? @@ -342,6 +498,7 @@ public class GlassBackgroundView: UIView { if #available(iOS 26.0, *), !GlassBackgroundView.useCustomGlassImpl { self.legacyView = nil self.legacyHighlightContainerView = nil + self.legacyHighlightClippingContext = nil let glassEffect = UIGlassEffect(style: .regular) glassEffect.isInteractive = false @@ -362,6 +519,7 @@ public class GlassBackgroundView: UIView { legacyHighlightContainerView.isUserInteractionEnabled = false legacyHighlightContainerView.clipsToBounds = true self.legacyHighlightContainerView = legacyHighlightContainerView + self.legacyHighlightClippingContext = ClippingShapeContext(view: legacyHighlightContainerView) self.nativeView = nil self.nativeViewClippingContext = nil self.nativeParamsView = nil @@ -438,13 +596,21 @@ public class GlassBackgroundView: UIView { public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) { let shape: Shape = .roundedRect(cornerRadius: cornerRadius) + self.update(size: size, shape: shape, isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, isVisible: isVisible, transition: transition) + } + + public func update(size: CGSize, cornerRadii: CornerRadii, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) { + let shape: Shape = .customRoundedRect(cornerRadii: cornerRadii) + self.update(size: size, shape: shape, isDark: isDark, tintColor: tintColor, isInteractive: isInteractive, isVisible: isVisible, transition: transition) + } + + func update(size: CGSize, shape: Shape, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, isVisible: Bool = true, transition: ComponentTransition) { if let glassHighlightRecognizer = self.glassHighlightRecognizer { glassHighlightRecognizer.isEnabled = isInteractive } - if let nativeView = self.nativeView, let nativeViewClippingContext = self.nativeViewClippingContext, (nativeView.bounds.size != size || nativeViewClippingContext.shape != shape) { - + if let nativeView = self.nativeView, let nativeViewClippingContext = self.nativeViewClippingContext, (nativeView.bounds.size != size || nativeViewClippingContext.shape != shape || (nativeView.overrideUserInterfaceStyle == .dark) != isDark) { nativeViewClippingContext.update(shape: shape, size: size, transition: transition) if transition.animation.isImmediate { nativeView.frame = CGRect(origin: CGPoint(), size: size) @@ -457,24 +623,21 @@ public class GlassBackgroundView: UIView { nativeView.overrideUserInterfaceStyle = isDark ? .dark : .light } if let legacyView = self.legacyView { - switch shape { - case let .roundedRect(cornerRadius): - let style: LegacyGlassView.Style - switch tintColor.kind { - case .panel: - style = .normal + let style: LegacyGlassView.Style + switch tintColor.kind { + case .panel: + style = .normal + case .clear: + style = .clear + case let .custom(styleValue, _): + switch styleValue { case .clear: style = .clear - case let .custom(styleValue, _): - switch styleValue { - case .clear: - style = .clear - case .default: - style = .normal - } + case .default: + style = .normal } - legacyView.update(size: size, cornerRadius: cornerRadius, style: style, transition: transition) } + legacyView.update(size: size, shape: shape, style: style, transition: transition) transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size)) transition.setAlpha(view: legacyView, alpha: isVisible ? 1.0 : 0.0) @@ -483,10 +646,7 @@ public class GlassBackgroundView: UIView { } if let legacyHighlightContainerView = self.legacyHighlightContainerView { transition.setFrame(view: legacyHighlightContainerView, frame: CGRect(origin: CGPoint(), size: size)) - switch shape { - case let .roundedRect(cornerRadius): - transition.setCornerRadius(layer: legacyHighlightContainerView.layer, cornerRadius: cornerRadius) - } + self.legacyHighlightClippingContext?.update(shape: shape, size: size, transition: transition) } let shadowInset: CGFloat = 32.0 @@ -534,25 +694,13 @@ public class GlassBackgroundView: UIView { if self.params != params { self.params = params - let outerCornerRadius: CGFloat - switch shape { - case let .roundedRect(cornerRadius): - outerCornerRadius = cornerRadius - } - if let shadowView = self.shadowView { - let shadowInnerInset: CGFloat = 0.5 - shadowView.image = generateImage(CGSize(width: shadowInset * 2.0 + outerCornerRadius * 2.0, height: shadowInset * 2.0 + outerCornerRadius * 2.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(UIColor.black.cgColor) - context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.04).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) - - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) - context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) - })?.stretchableImage(withLeftCapWidth: Int(shadowInset + outerCornerRadius), topCapHeight: Int(shadowInset + outerCornerRadius)) + switch shape { + case let .roundedRect(cornerRadius): + shadowView.image = Self.generateLegacyShadowImage(cornerRadius: cornerRadius, shadowInset: shadowInset) + case let .customRoundedRect(cornerRadii): + shadowView.image = Self.generateLegacyShadowImage(cornerRadii: GlassBackgroundView.clampedCornerRadii(size: size, cornerRadii: cornerRadii), shadowInset: shadowInset) + } transition.setAlpha(view: shadowView, alpha: isVisible ? 1.0 : 0.0) } @@ -579,7 +727,12 @@ public class GlassBackgroundView: UIView { borderWidthFactor = 1.0 } } - foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, borderWidthFactor: borderWidthFactor, isDark: isDark, fillColor: fillColor) + switch shape { + case let .roundedRect(cornerRadius): + foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), inset: shadowInset, borderWidthFactor: borderWidthFactor, isDark: isDark, fillColor: fillColor) + case let .customRoundedRect(cornerRadii): + foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(cornerRadii: GlassBackgroundView.clampedCornerRadii(size: size, cornerRadii: cornerRadii), inset: shadowInset, borderWidthFactor: borderWidthFactor, isDark: isDark, fillColor: fillColor) + } #if DEBUG //foregroundView.image = nil #endif @@ -617,7 +770,7 @@ public class GlassBackgroundView: UIView { glassEffectValue.tintColor = nil } } - glassEffectValue.isInteractive = params.isInteractive + glassEffectValue.isInteractive = isInteractive glassEffect = glassEffectValue } @@ -866,7 +1019,88 @@ private extension CGContext { } } +private struct LegacyResizableImageMetrics { + let imageSize: CGSize + let innerRect: CGRect + let leftCapWidth: Int + let topCapHeight: Int +} + +private func legacyResizableImageMetrics(cornerRadii: GlassBackgroundView.CornerRadii, inset: CGFloat) -> LegacyResizableImageMetrics { + let leftRadius = ceil(max(cornerRadii.topLeft, cornerRadii.bottomLeft)) + let rightRadius = ceil(max(cornerRadii.topRight, cornerRadii.bottomRight)) + let topRadius = ceil(max(cornerRadii.topLeft, cornerRadii.topRight)) + let bottomRadius = ceil(max(cornerRadii.bottomLeft, cornerRadii.bottomRight)) + + let innerSize = CGSize( + width: max(1.0, leftRadius + rightRadius + 1.0), + height: max(1.0, topRadius + bottomRadius + 1.0) + ) + let imageSize = CGSize(width: innerSize.width + inset * 2.0, height: innerSize.height + inset * 2.0) + + return LegacyResizableImageMetrics( + imageSize: imageSize, + innerRect: CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize), + leftCapWidth: Int(ceil(inset + leftRadius)), + topCapHeight: Int(ceil(inset + topRadius)) + ) +} + public extension GlassBackgroundView { + static func generateLegacyShadowImage(cornerRadius: CGFloat, shadowInset: CGFloat = 32.0, shadowIntensity: CGFloat = 0.04, shadowBlur: CGFloat = 40.0) -> UIImage? { + let shadowInnerInset: CGFloat = 0.5 + let diameter = max(1.0, cornerRadius * 2.0) + let size = CGSize(width: shadowInset * 2.0 + diameter, height: shadowInset * 2.0 + diameter) + + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let shadowRect = CGRect( + origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), + size: CGSize( + width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, + height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0 + ) + ) + + context.setFillColor(UIColor.black.cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: shadowBlur, color: UIColor(white: 0.0, alpha: shadowIntensity).cgColor) + context.fillEllipse(in: shadowRect) + + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.fillEllipse(in: shadowRect) + })?.stretchableImage( + withLeftCapWidth: Int(shadowInset + cornerRadius), + topCapHeight: Int(shadowInset + cornerRadius) + ) + } + + static func generateLegacyShadowImage(cornerRadii: CornerRadii, shadowInset: CGFloat = 32.0, shadowIntensity: CGFloat = 0.04, shadowBlur: CGFloat = 40.0) -> UIImage? { + let shadowInnerInset: CGFloat = 0.5 + let metrics = legacyResizableImageMetrics(cornerRadii: cornerRadii, inset: shadowInset) + + return generateImage(metrics.imageSize, rotatedContext: { _, context in + context.clear(CGRect(origin: CGPoint(), size: metrics.imageSize)) + + let shadowRect = metrics.innerRect.insetBy(dx: shadowInnerInset, dy: shadowInnerInset) + let shadowPath = GlassBackgroundView.generateRoundedRectPath(rect: shadowRect, cornerRadii: cornerRadii) + + context.setFillColor(UIColor.black.cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: shadowBlur, color: UIColor(white: 0.0, alpha: shadowIntensity).cgColor) + context.addPath(shadowPath) + context.fillPath() + + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.addPath(shadowPath) + context.fillPath() + })?.stretchableImage( + withLeftCapWidth: metrics.leftCapWidth, + topCapHeight: metrics.topCapHeight + ) + } + static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, borderWidthFactor: CGFloat = 1.0, isDark: Bool, fillColor: UIColor) -> UIImage { var size = size if size == .zero { @@ -1060,6 +1294,106 @@ public extension GlassBackgroundView { }.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5)) } + static func generateLegacyGlassImage(cornerRadii: CornerRadii, inset: CGFloat, borderWidthFactor: CGFloat = 1.0, isDark: Bool, fillColor: UIColor) -> UIImage { + let metrics = legacyResizableImageMetrics(cornerRadii: cornerRadii, inset: inset) + let size = metrics.imageSize + let innerRect = metrics.innerRect + + return UIGraphicsImageRenderer(size: size).image { ctx in + let context = ctx.cgContext + + context.clear(CGRect(origin: CGPoint(), size: size)) + + let addShadow: (CGContext, Bool, CGPoint, CGFloat, CGFloat, UIColor, CGBlendMode) -> Void = { context, isOuter, position, blur, spread, shadowColor, blendMode in + var blur = blur + + if isOuter { + blur += abs(spread) + + context.beginTransparencyLayer(auxiliaryInfo: nil) + context.saveGState() + defer { + context.restoreGState() + context.endTransparencyLayer() + } + + let spreadRect = innerRect.insetBy(dx: 0.25, dy: 0.25) + let spreadPath = GlassBackgroundView.generateRoundedRectPath(rect: spreadRect, cornerRadii: cornerRadii) + + context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor) + context.setFillColor(UIColor.black.withAlphaComponent(1.0).cgColor) + context.addPath(spreadPath) + context.fillPath() + + let cleanPath = GlassBackgroundView.generateRoundedRectPath(rect: innerRect, cornerRadii: cornerRadii) + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.addPath(cleanPath) + context.fillPath() + context.setBlendMode(.normal) + } else { + let image = UIGraphicsImageRenderer(size: size).image(actions: { ctx in + let context = ctx.cgContext + let spreadRect = innerRect.insetBy(dx: -spread - 0.33, dy: -spread - 0.33) + + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor) + context.setFillColor(shadowColor.cgColor) + context.addPath(UIBezierPath(rect: spreadRect.insetBy(dx: -10000.0, dy: -10000.0)).cgPath) + context.addPath(GlassBackgroundView.generateRoundedRectPath(rect: spreadRect, cornerRadii: cornerRadii)) + context.fillPath(using: .evenOdd) + }) + + UIGraphicsPushContext(context) + image.draw(in: CGRect(origin: .zero, size: size), blendMode: blendMode, alpha: 1.0) + UIGraphicsPopContext() + } + } + + addShadow(context, true, CGPoint(), 30.0, 0.0, UIColor(white: 0.0, alpha: 0.045), .normal) + addShadow(context, true, CGPoint(), 20.0, 0.0, UIColor(white: 0.0, alpha: 0.01), .normal) + + var a: CGFloat = 0.0 + var b: CGFloat = 0.0 + var s: CGFloat = 0.0 + fillColor.getHue(nil, saturation: &s, brightness: &b, alpha: &a) + + context.setFillColor(fillColor.cgColor) + context.addPath(GlassBackgroundView.generateRoundedRectPath(rect: innerRect, cornerRadii: cornerRadii)) + context.fillPath() + + let lineWidth: CGFloat = (isDark ? 0.8 : 0.8) * borderWidthFactor + let strokeColor: UIColor + let blendMode: CGBlendMode + let baseAlpha: CGFloat = isDark ? 0.3 : 0.6 + + if s == 0.0 && abs(a - 0.7) < 0.1 && !isDark { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: baseAlpha) + } else if s <= 0.3 && !isDark { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: 0.7 * baseAlpha) + } else if b >= 0.2 { + let maxAlpha: CGFloat = isDark ? 0.7 : 0.8 + blendMode = .overlay + strokeColor = UIColor(white: 1.0, alpha: max(0.5, min(1.0, maxAlpha * s)) * baseAlpha) + } else { + blendMode = .normal + strokeColor = UIColor(white: 1.0, alpha: 0.5 * baseAlpha) + } + + context.addPath(GlassBackgroundView.generateRoundedRectPath(rect: innerRect, cornerRadii: cornerRadii)) + context.clip() + context.setBlendMode(blendMode) + context.setLineWidth(lineWidth) + context.setStrokeColor(strokeColor.cgColor) + context.addPath(GlassBackgroundView.generateRoundedRectPath(rect: innerRect.insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadii: cornerRadii.insetBy(lineWidth * 0.5))) + context.strokePath() + context.resetClip() + context.setBlendMode(.normal) + }.stretchableImage(withLeftCapWidth: metrics.leftCapWidth, topCapHeight: metrics.topCapHeight) + } + static func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage { var size = size if size == .zero { @@ -1143,30 +1477,49 @@ public extension GlassBackgroundView { public final class GlassBackgroundComponent: Component { private let size: CGSize - private let cornerRadius: CGFloat + private let shape: GlassBackgroundView.Shape private let isDark: Bool private let tintColor: GlassBackgroundView.TintColor private let isInteractive: Bool + private let isVisible: Bool public init( size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor, - isInteractive: Bool = false + isInteractive: Bool = false, + isVisible: Bool = true ) { self.size = size - self.cornerRadius = cornerRadius + self.shape = .roundedRect(cornerRadius: cornerRadius) self.isDark = isDark self.tintColor = tintColor self.isInteractive = isInteractive + self.isVisible = isVisible + } + + public init( + size: CGSize, + cornerRadii: GlassBackgroundView.CornerRadii, + isDark: Bool, + tintColor: GlassBackgroundView.TintColor, + isInteractive: Bool = false, + isVisible: Bool = true + ) { + self.size = size + self.shape = .customRoundedRect(cornerRadii: cornerRadii) + self.isDark = isDark + self.tintColor = tintColor + self.isInteractive = isInteractive + self.isVisible = isVisible } public static func == (lhs: GlassBackgroundComponent, rhs: GlassBackgroundComponent) -> Bool { if lhs.size != rhs.size { return false } - if lhs.cornerRadius != rhs.cornerRadius { + if lhs.shape != rhs.shape { return false } if lhs.isDark != rhs.isDark { @@ -1178,12 +1531,15 @@ public final class GlassBackgroundComponent: Component { if lhs.isInteractive != rhs.isInteractive { return false } + if lhs.isVisible != rhs.isVisible { + return false + } return true } public final class View: GlassBackgroundView { func update(component: GlassBackgroundComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.update(size: component.size, cornerRadius: component.cornerRadius, isDark: component.isDark, tintColor: component.tintColor, isInteractive: component.isInteractive, transition: transition) + self.update(size: component.size, shape: component.shape, isDark: component.isDark, tintColor: component.tintColor, isInteractive: component.isInteractive, isVisible: component.isVisible, transition: transition) return component.size } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift index fd7a918447..7b5261d17c 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift @@ -88,17 +88,18 @@ final class LegacyGlassView: UIView { private struct Params: Equatable { let size: CGSize - let cornerRadius: CGFloat + let shape: GlassBackgroundView.Shape let style: Style - init(size: CGSize, cornerRadius: CGFloat, style: Style) { + init(size: CGSize, shape: GlassBackgroundView.Shape, style: Style) { self.size = size - self.cornerRadius = cornerRadius + self.shape = shape self.style = style } } private var params: Params? + private var maskLayer: CAShapeLayer? private let backdropLayer: CALayer? private let backdropLayerDelegate: BackdropLayerDelegate @@ -126,7 +127,11 @@ final class LegacyGlassView: UIView { } func update(size: CGSize, cornerRadius: CGFloat, style: Style, transition: ComponentTransition) { - let params = Params(size: size, cornerRadius: cornerRadius, style: style) + self.update(size: size, shape: .roundedRect(cornerRadius: cornerRadius), style: style, transition: transition) + } + + func update(size: CGSize, shape: GlassBackgroundView.Shape, style: Style, transition: ComponentTransition) { + let params = Params(size: size, shape: shape, style: style) let previousParams = self.params if self.params == params { return @@ -168,12 +173,31 @@ final class LegacyGlassView: UIView { } } - transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius) + switch shape { + case let .roundedRect(cornerRadius): + self.maskLayer = nil + self.layer.mask = nil + transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius) + case let .customRoundedRect(cornerRadii): + transition.setCornerRadius(layer: self.layer, cornerRadius: 0.0) + + let maskLayer: CAShapeLayer + if let current = self.maskLayer { + maskLayer = current + } else { + maskLayer = CAShapeLayer() + maskLayer.fillColor = UIColor.black.cgColor + self.maskLayer = maskLayer + self.layer.mask = maskLayer + } + transition.setFrame(layer: maskLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setShapeLayerPath(layer: maskLayer, path: GlassBackgroundView.generateRoundedRectPath(size: size, cornerRadii: cornerRadii)) + } transition.setFrame(layer: backdropLayer, frame: CGRect(origin: CGPoint(), size: size)) if #available(iOS 17.0, *), DeviceMetrics.performance.isGraphicallyCapable { let size = CGSize(width: max(1.0, size.width), height: max(1.0, size.height)) - let cornerRadius = min(min(size.width, size.height) * 0.5, cornerRadius) + let cornerRadius = min(min(size.width, size.height) * 0.5, shape.maximumCornerRadius(for: size)) let displacementMagnitudePoints: CGFloat = 20.0 let displacementMagnitudeU = displacementMagnitudePoints / size.width let displacementMagnitudeV = displacementMagnitudePoints / size.height diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift index 643b4086aa..f8bd84416b 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/TouchEffect.swift @@ -2,8 +2,8 @@ import Foundation import UIKit import Display -final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { - var highlightContainerView: UIView? +public final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + public weak var highlightContainerView: UIView? private var touchEffect: TouchEffect? private var initialTouchLocation: CGPoint? @@ -14,7 +14,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog } } - override init(target: Any?, action: Selector?) { + public override init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.delegate = self @@ -24,19 +24,19 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog self.requiresExclusiveTouchType = false } - override func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { + override public func canPrevent(_ preventedGestureRecognizer: UIGestureRecognizer) -> Bool { return false } - override func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { + override public func canBePrevented(by preventingGestureRecognizer: UIGestureRecognizer) -> Bool { return false } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - override func reset() { + override public func reset() { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } @@ -45,7 +45,7 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog self.initialTouchLocation = nil } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + override public func touchesBegan(_ touches: Set, with event: UIEvent) { if let view = self.touchEffectView ?? self.view, let touch = touches.first { let touchLocation = touch.location(in: view) let touchEffect = TouchEffect(view: view, highlightContainerView: self.highlightContainerView) @@ -60,21 +60,21 @@ final class GlassHighlightGestureRecognizer: UIGestureRecognizer, UIGestureRecog } } - override func touchesEnded(_ touches: Set, with event: UIEvent) { + override public func touchesEnded(_ touches: Set, with event: UIEvent) { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } self.touchEffect = nil } - override func touchesCancelled(_ touches: Set, with event: UIEvent) { + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { if let touchEffect = self.touchEffect { touchEffect.setIsTracking(false) } self.touchEffect = nil } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { guard let touchEffect = self.touchEffect, let view = self.touchEffectView ?? self.view, let touch = touches.first, diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift index 3efa665988..9d60cd341d 100644 --- a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -216,6 +216,7 @@ public final class GlassBarButtonComponent: Component { } } componentTransition.setFrame(view: view, frame: componentFrame) + componentTransition.setAlpha(view: view, alpha: component.isVisible ? 1.0 : 0.0) } let effectiveState: DisplayState = component.state ?? .glass diff --git a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift index ce0204d62b..531138fe3c 100644 --- a/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift +++ b/submodules/TelegramUI/Components/GlassControls/Sources/GlassControlGroup.swift @@ -15,7 +15,7 @@ public final class GlassControlGroupComponent: Component { case icon(String) case text(String) case animation(String) - case customIcon(id: AnyHashable, component: AnyComponent) + case customIcon(id: AnyHashable, component: AnyComponent, insets: UIEdgeInsets) enum Id: Hashable { case icon(String) @@ -32,7 +32,7 @@ public final class GlassControlGroupComponent: Component { return .text(text) case let .animation(animation): return .animation(animation) - case let .customIcon(id, _): + case let .customIcon(id, _, _): return .customIcon(id) } } @@ -216,8 +216,10 @@ public final class GlassControlGroupComponent: Component { size: CGSize(width: 32.0, height: 32.0), playOnce: playOnce )) - case let .customIcon(_, customIcon): + case let .customIcon(_, customIcon, insets): content = customIcon + itemInsets.left = insets.left + itemInsets.right = insets.right } var minItemWidth: CGFloat = availableSize.height @@ -285,8 +287,9 @@ public final class GlassControlGroupComponent: Component { let size = CGSize(width: contentsWidth, height: availableSize.height) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) - isInteractive = true - self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: isInteractive, transition: transition) + + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: true, transition: transition) + self.backgroundView.isUserInteractionEnabled = isInteractive return size } diff --git a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift index d42d3910c2..51a4d2a53e 100644 --- a/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift +++ b/submodules/TelegramUI/Components/GlobalControlPanelsContext/Sources/GlobalControlPanelsContext.swift @@ -76,6 +76,7 @@ public final class GlobalControlPanelsContext { case setupBirthday case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday]) case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int) + case reviewBotConnection(newBotConnectionReview: NewBotConnectionReview, botUsername: String, totalCount: Int) case premiumGrace case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer]) case setupPhoto(EnginePeer) @@ -316,9 +317,9 @@ public final class GlobalControlPanelsContext { if chatListNotices { let twoStepData: Signal = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init)) - let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let accountFreezeConfiguration = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged @@ -333,6 +334,7 @@ public final class GlobalControlPanelsContext { context.engine.notices.getServerDismissedSuggestions(), twoStepData, newSessionReviews(postbox: context.account.postbox), + newBotConnectionReviews(postbox: context.account.postbox), context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId) @@ -341,12 +343,24 @@ public final class GlobalControlPanelsContext { starsSubscriptionsContextPromise.get(), accountFreezeConfiguration ) - |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal in + |> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, newBotConnectionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal in let (accountPeer, birthday) = data if let newSessionReview = newSessionReviews.first { return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count)) } + if let newBotConnectionReview = newBotConnectionReviews.first { + return context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: newBotConnectionReview.botId) + ) + |> map { peer -> ChatListNotice? in + return .reviewBotConnection( + newBotConnectionReview: newBotConnectionReview, + botUsername: peer?.addressName ?? "", + totalCount: newBotConnectionReviews.count + ) + } + } if suggestions.contains(.setupPassword), let configuration { var notSet = false switch configuration { diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD index f3a92b4554..02bb123ece 100644 --- a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUIPreferences", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift index bd3d0802c4..062981395e 100644 --- a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallHeaderPanelComponent.swift @@ -8,7 +8,6 @@ import AccountContext import TelegramCore import GlobalControlPanelsContext import SwiftSignalKit -import Postbox import PresentationDataUtils public final class GroupCallHeaderPanelComponent: Component { diff --git a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift index 643569e238..d059a78b71 100644 --- a/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift +++ b/submodules/TelegramUI/Components/GroupCallHeaderPanelComponent/Sources/GroupCallNavigationAccessoryPanel.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import TelegramStringFormatting @@ -102,7 +101,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode { private let avatarsContext: AnimatedAvatarSetContext private var avatarsContent: AnimatedAvatarSetContext.Content? private let avatarsNode: AnimatedAvatarSetNode - private var audioLevelGenerators: [PeerId: FakeAudioLevelGenerator] = [:] + private var audioLevelGenerators: [EnginePeer.Id: FakeAudioLevelGenerator] = [:] private var audioLevelGeneratorTimer: SwiftSignalKit.Timer? private let backgroundNode: ASDisplayNode @@ -546,7 +545,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode { self.audioLevelGenerators[peerId] = FakeAudioLevelGenerator() } } - var removeGenerators: [PeerId] = [] + var removeGenerators: [EnginePeer.Id] = [] for peerId in self.audioLevelGenerators.keys { if !data.activeSpeakers.contains(peerId) { removeGenerators.append(peerId) @@ -571,7 +570,7 @@ final class GroupCallNavigationAccessoryPanel: ASDisplayNode { } private func sampleAudioGenerators() { - var levels: [PeerId: Float] = [:] + var levels: [EnginePeer.Id: Float] = [:] for (peerId, generator) in self.audioLevelGenerators { levels[peerId] = generator.get() } diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index 622412a738..edc3f81720 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -323,7 +322,7 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { } var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchSignal: Signal? + var updatedFetchSignal: Signal? if fileUpdated { if let file = file { updatedImageSignal = chatMessageSticker(account: item.account, userLocation: .other, file: file, small: false) diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift index 9c4eff3f94..433771e212 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -37,7 +36,7 @@ private enum GroupStickerPackSection: Int32 { private enum GroupStickerPackEntryId: Hashable { case index(Int32) - case pack(ItemCollectionId) + case pack(EngineItemCollectionId) static func ==(lhs: GroupStickerPackEntryId, rhs: GroupStickerPackEntryId) -> Bool { switch lhs { @@ -265,7 +264,7 @@ private struct GroupStickerPackSetupControllerState: Equatable { var searchingPacks: Bool } -private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, view: CombinedView, initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings, isEmoji: Bool) -> [GroupStickerPackEntry] { +private func groupStickerPackSetupControllerEntries(context: AccountContext, presentationData: PresentationData, searchText: String, packsEntries: [EngineRawItemCollectionInfoEntry], initialData: InitialStickerPackData?, searchState: GroupStickerPackSearchState, stickerSettings: StickerSettings, isEmoji: Bool) -> [GroupStickerPackEntry] { if initialData == nil { return [] } @@ -284,39 +283,34 @@ private func groupStickerPackSetupControllerEntries(context: AccountContext, pre } entries.append(.searchInfo(presentationData.theme, isEmoji ? presentationData.strings.Group_Emoji_Info : presentationData.strings.Channel_Stickers_CreateYourOwn)) - let namespace = isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { - if !packsEntries.isEmpty { - entries.append(.packsTitle(presentationData.theme, isEmoji ? presentationData.strings.Group_Emoji_YourEmoji : presentationData.strings.Channel_Stickers_YourStickers)) + if !packsEntries.isEmpty { + entries.append(.packsTitle(presentationData.theme, isEmoji ? presentationData.strings.Group_Emoji_YourEmoji : presentationData.strings.Channel_Stickers_YourStickers)) + } + + var index: Int32 = 0 + for entry in packsEntries { + if let info = entry.info as? StickerPackCollectionInfo { + var selected = false + if case let .found(found) = searchState { + selected = found.info.id == info.id } - - var index: Int32 = 0 - for entry in packsEntries { - if let info = entry.info as? StickerPackCollectionInfo { - var selected = false - if case let .found(found) = searchState { - selected = found.info.id == info.id - } - let count = info.count == 0 ? entry.count : info.count - - let thumbnail: StickerPackItem? - if let thumbnailRep = info.thumbnail { - thumbnail = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: thumbnailRep.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: info.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []), indexKeys: []) - } else { - thumbnail = entry.firstItem as? StickerPackItem - } - entries.append(.pack(index, presentationData.theme, presentationData.strings, info, thumbnail, isEmoji ? presentationData.strings.StickerPack_EmojiCount(count) : presentationData.strings.StickerPack_StickerCount(count), context.sharedContext.energyUsageSettings.loopStickers, selected)) - index += 1 - } + let count = info.count == 0 ? entry.count : info.count + + let thumbnail: StickerPackItem? + if let thumbnailRep = info.thumbnail { + thumbnail = StickerPackItem(index: EngineItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: thumbnailRep.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: info.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []), indexKeys: []) + } else { + thumbnail = entry.firstItem as? StickerPackItem } + entries.append(.pack(index, presentationData.theme, presentationData.strings, info, thumbnail, isEmoji ? presentationData.strings.StickerPack_EmojiCount(count) : presentationData.strings.StickerPack_StickerCount(count), context.sharedContext.energyUsageSettings.loopStickers, selected)) + index += 1 } } return entries } -public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, isEmoji: Bool = false, currentPackInfo: StickerPackCollectionInfo?, completion: ((StickerPackCollectionInfo?) -> Void)? = nil) -> ViewController { +public func groupStickerPackSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, isEmoji: Bool = false, currentPackInfo: StickerPackCollectionInfo?, completion: ((StickerPackCollectionInfo?) -> Void)? = nil) -> ViewController { let initialState = GroupStickerPackSetupControllerState(isSaving: false, searchingPacks: false) let statePromise = ValuePromise(initialState, ignoreRepeated: true) @@ -353,12 +347,12 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } } - let stickerPacks = Promise() - stickerPacks.set(context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks])])) + let stickerPacks = Promise<[EngineRawItemCollectionInfoEntry]>() + stickerPacks.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks))) let searchState = Promise<(String, GroupStickerPackSearchState)>() searchState.set(combineLatest(searchText.get(), initialData.get(), stickerPacks.get()) - |> mapToSignal { searchText, initialData, view -> Signal<(String, GroupStickerPackSearchState), NoError> in + |> mapToSignal { searchText, initialData, packsEntries -> Signal<(String, GroupStickerPackSearchState), NoError> in if let initialData = initialData { if searchText.isEmpty { return .single((searchText, .none)) @@ -368,15 +362,10 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } return .single((searchText, .found(StickerPackData(info: data.info, item: data.item)))) } else { - let namespace = isEmoji ? Namespaces.ItemCollection.CloudEmojiPacks : Namespaces.ItemCollection.CloudStickerPacks - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [namespace])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[namespace] { - for entry in packsEntries { - if let info = entry.info as? StickerPackCollectionInfo { - if info.shortName.lowercased() == searchText.lowercased() { - return .single((searchText, .found(StickerPackData(info: info, item: entry.firstItem as? StickerPackItem)))) - } - } + for entry in packsEntries { + if let info = entry.info as? StickerPackCollectionInfo { + if info.shortName.lowercased() == searchText.lowercased() { + return .single((searchText, .found(StickerPackData(info: info, item: entry.firstItem as? StickerPackItem)))) } } } @@ -405,7 +394,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres } }) - var navigateToChatControllerImpl: ((PeerId) -> Void)? + var navigateToChatControllerImpl: ((EnginePeer.Id) -> Void)? var dismissInputImpl: (() -> Void)? var dismissImpl: (() -> Void)? @@ -449,7 +438,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData let signal = combineLatest(presentationData, statePromise.get() |> deliverOnMainQueue, initialData.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, searchState.get() |> deliverOnMainQueue, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> deliverOnMainQueue) - |> map { presentationData, state, initialData, view, searchState, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, initialData, packsEntries, searchState, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in var stickerSettings = StickerSettings.defaultSettings if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { stickerSettings = value @@ -459,7 +448,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres if isEmoji { leftNavigationButton = nil } else { - leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) } @@ -489,7 +478,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres enabled = true info = data.info } - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: enabled, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: enabled, action: { if info?.id == currentPackInfo?.id { dismissImpl?() } else { @@ -539,7 +528,7 @@ public func groupStickerPackSetupController(context: AccountContext, updatedPres emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, view: view, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings, isEmoji: isEmoji), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: hasData && hadData) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStickerPackSetupControllerEntries(context: context, presentationData: presentationData, searchText: searchState.0, packsEntries: packsEntries, initialData: initialData, searchState: searchState.1, stickerSettings: stickerSettings, isEmoji: isEmoji), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: hasData && hadData) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift index e8224ba5ca..381166f2d7 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,15 +23,15 @@ private final class GroupStickerSearchContainerInteraction { private final class GroupStickerSearchEntry: Comparable, Identifiable { let index: Int let pack: StickerPackCollectionInfo - let topItem: ItemCollectionItem? + let topItem: EngineRawItemCollectionItem? - init(index: Int, pack: StickerPackCollectionInfo, topItem: ItemCollectionItem?) { + init(index: Int, pack: StickerPackCollectionInfo, topItem: EngineRawItemCollectionItem?) { self.index = index self.pack = pack self.topItem = topItem } - var stableId: ItemCollectionId { + var stableId: EngineItemCollectionId { return self.pack.id } diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift index 591d1b55dc..41816a0de4 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchNavigationContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift index c4ffa9609a..75006283d1 100644 --- a/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift +++ b/submodules/TelegramUI/Components/InteractiveTextComponent/Sources/InteractiveTextComponent.swift @@ -127,8 +127,9 @@ private final class InteractiveTextNodeLine { var embeddedItems: [InteractiveTextNodeEmbeddedItem] var attachments: [InteractiveTextNodeAttachment] let additionalTrailingLine: (CTLine, Double)? - - init(line: CTLine, constrainedWidth: CGFloat, frame: CGRect, intrinsicWidth: CGFloat, ascent: CGFloat, descent: CGFloat, range: NSRange?, isTruncated: Bool, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], underlines: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?) { + let characterRects: [CGRect]? + + init(line: CTLine, constrainedWidth: CGFloat, frame: CGRect, intrinsicWidth: CGFloat, ascent: CGFloat, descent: CGFloat, range: NSRange?, isTruncated: Bool, isRTL: Bool, strikethroughs: [InteractiveTextNodeStrikethrough], underlines: [InteractiveTextNodeStrikethrough], spoilers: [InteractiveTextNodeSpoiler], spoilerWords: [InteractiveTextNodeSpoiler], embeddedItems: [InteractiveTextNodeEmbeddedItem], attachments: [InteractiveTextNodeAttachment], additionalTrailingLine: (CTLine, Double)?, characterRects: [CGRect]?) { self.line = line self.constrainedWidth = constrainedWidth self.frame = frame @@ -145,6 +146,7 @@ private final class InteractiveTextNodeLine { self.embeddedItems = embeddedItems self.attachments = attachments self.additionalTrailingLine = additionalTrailingLine + self.characterRects = characterRects } } @@ -295,7 +297,9 @@ public final class InteractiveTextNodeLayoutArguments { public let displayContentsUnderSpoilers: Bool public let customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)? public let expandedBlocks: Set - + public let computeCharacterRects: Bool + public let minWidth: CGFloat? + public init( attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, @@ -314,7 +318,9 @@ public final class InteractiveTextNodeLayoutArguments { textStroke: (UIColor, CGFloat)? = nil, displayContentsUnderSpoilers: Bool = false, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)? = nil, - expandedBlocks: Set = Set() + expandedBlocks: Set = Set(), + computeCharacterRects: Bool = false, + minWidth: CGFloat? = nil ) { self.attributedString = attributedString self.backgroundColor = backgroundColor @@ -334,8 +340,10 @@ public final class InteractiveTextNodeLayoutArguments { self.displayContentsUnderSpoilers = displayContentsUnderSpoilers self.customTruncationToken = customTruncationToken self.expandedBlocks = expandedBlocks + self.computeCharacterRects = computeCharacterRects + self.minWidth = minWidth } - + public func withAttributedString(_ attributedString: NSAttributedString?) -> InteractiveTextNodeLayoutArguments { return InteractiveTextNodeLayoutArguments( attributedString: attributedString, @@ -355,7 +363,9 @@ public final class InteractiveTextNodeLayoutArguments { textStroke: self.textStroke, displayContentsUnderSpoilers: self.displayContentsUnderSpoilers, customTruncationToken: self.customTruncationToken, - expandedBlocks: self.expandedBlocks + expandedBlocks: self.expandedBlocks, + computeCharacterRects: self.computeCharacterRects, + minWidth: self.minWidth ) } } @@ -418,7 +428,7 @@ public final class InteractiveTextNodeLayout: NSObject { fileprivate let textStroke: (UIColor, CGFloat)? public let displayContentsUnderSpoilers: Bool fileprivate let expandedBlocks: Set - fileprivate var characterToGlyphMapping: [Int]? + public let minWidth: CGFloat? fileprivate init( attributedString: NSAttributedString?, @@ -442,7 +452,8 @@ public final class InteractiveTextNodeLayout: NSObject { textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, - expandedBlocks: Set + expandedBlocks: Set, + minWidth: CGFloat? ) { self.attributedString = attributedString self.maximumNumberOfLines = maximumNumberOfLines @@ -466,6 +477,7 @@ public final class InteractiveTextNodeLayout: NSObject { self.textStroke = textStroke self.displayContentsUnderSpoilers = displayContentsUnderSpoilers self.expandedBlocks = expandedBlocks + self.minWidth = minWidth } func withUpdatedDisplayContentsUnderSpoilers(_ displayContentsUnderSpoilers: Bool) -> InteractiveTextNodeLayout { @@ -491,7 +503,8 @@ public final class InteractiveTextNodeLayout: NSObject { textShadowBlur: self.textShadowBlur, textStroke: self.textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, - expandedBlocks: self.expandedBlocks + expandedBlocks: self.expandedBlocks, + minWidth: self.minWidth ) } @@ -1041,94 +1054,79 @@ public final class InteractiveTextNodeLayout: NSObject { return nil } - public func layoutForGlyphCount(glyphCount: Int) -> TextNodeLayout.LayoutInfo { + public func layoutForCharacterCount(characterCount: Int) -> TextNodeLayout.LayoutInfo { var height: CGFloat = 0.0 if !self.segments.isEmpty, let line = self.segments[0].lines.first { height = line.frame.maxY } - - var actualCount = 0 - var itemCount = 0 - for item in self.getCharacterToGlyphMapping() { - if itemCount >= glyphCount { - break - } - actualCount = item - itemCount += 1 - } - let glyphCount = actualCount - + var width: CGFloat = 0.0 - var count = 0 var trailingLineWidth: CGFloat = 0.0 var lineCount = 0 + var remainingCharacters = characterCount + for segment in self.segments { for line in segment.lines { - if count >= glyphCount { + if remainingCharacters <= 0 { break } - let lineWidth: CGFloat - let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray - var maxGlyphUpperIndex: CFIndex = 0 - for run in glyphRuns { - let run = run as! CTRun - let rangeGlyphCount = CTRunGetGlyphCount(run) - let stringRange = CTRunGetStringRange(run) - if count + Int(rangeGlyphCount) > glyphCount { - if count < glyphCount { - let remainingGlyphCount = stringRange.location + glyphCount - count - if remainingGlyphCount > 0 { - var indices: [CFIndex] = Array(repeating: 0, count: remainingGlyphCount) - CTRunGetStringIndices(run, CFRangeMake(stringRange.location, stringRange.location + glyphCount - count), &indices) - if let maxIndex = indices.max() { - maxGlyphUpperIndex = max(maxIndex, maxGlyphUpperIndex) - } - } else { - assertionFailure() - } - } - } else { - if stringRange.length != 0 { - maxGlyphUpperIndex = max(stringRange.location + stringRange.length, maxGlyphUpperIndex) - } - } - count += Int(rangeGlyphCount) - } - height = max(height, line.frame.maxY) - if maxGlyphUpperIndex != 0 { - let lineRange = CTLineGetStringRange(line.line) - if maxGlyphUpperIndex < lineRange.location + lineRange.length { - let rightOffset = CTLineGetOffsetForStringIndex(line.line, maxGlyphUpperIndex + 1, nil) - lineWidth = rightOffset - } else { - lineWidth = line.frame.width - } + + let lineCharCount: Int + if let characterRects = line.characterRects { + lineCharCount = characterRects.count + } else if let range = line.range { + lineCharCount = range.length } else { - lineWidth = line.frame.width + let ctRange = CTLineGetStringRange(line.line) + lineCharCount = ctRange.length } - + + let lineWidth: CGFloat + if remainingCharacters >= lineCharCount { + lineWidth = line.frame.width + } else { + let lineRange: NSRange + if let range = line.range { + lineRange = range + } else { + let ctRange = CTLineGetStringRange(line.line) + lineRange = NSRange(location: ctRange.location, length: ctRange.length) + } + let cutStringIndex = lineRange.location + remainingCharacters + lineWidth = CGFloat(CTLineGetOffsetForStringIndex(line.line, cutStringIndex, nil)) + } + + height = max(height, line.frame.maxY) width = max(width, lineWidth) trailingLineWidth = lineWidth + self.insets.left + self.insets.right + 4.0 lineCount += 1 + remainingCharacters -= lineCharCount } } + width = ceil(width) + self.insets.left + self.insets.right if lineCount > 1 { width = self.size.width } - + height += self.insets.top + self.insets.bottom + 2.0 + + if let minWidth = self.minWidth { + width = max(width, minWidth) + trailingLineWidth = max(trailingLineWidth, minWidth) + } + return TextNodeLayout.LayoutInfo( size: CGSize(width: width, height: ceil(height)), trailingLineWidth: trailingLineWidth ) } - - public func sizeForGlyphCount(glyphCount: Int) -> CGSize { - return self.layoutForGlyphCount(glyphCount: glyphCount).size + + public func sizeForCharacterCount(characterCount: Int) -> CGSize { + return self.layoutForCharacterCount(characterCount: characterCount).size } - public func getCharacterToGlyphMapping() -> [Int] { + /*public func getCharacterToGlyphMapping() -> [Int] { guard let attributedString = self.attributedString else { return [] } @@ -1182,7 +1180,7 @@ public final class InteractiveTextNodeLayout: NSObject { self.characterToGlyphMapping = result return result - } + }*/ } private func addSpoiler(line: InteractiveTextNodeLine, ascent: CGFloat, descent: CGFloat, startIndex: Int, endIndex: Int) { @@ -1257,6 +1255,52 @@ private func addAttachment(attachment: UIImage, line: InteractiveTextNodeLine, a line.attachments.append(InteractiveTextNodeAttachment(range: NSMakeRange(startIndex, endIndex - startIndex), frame: CGRect(x: min(leftOffset, rightOffset), y: descent - (ascent + descent), width: abs(rightOffset - leftOffset) + rightInset, height: ascent + descent), attachment: attachment)) } +private func computeCharacterRectsForLine(line: CTLine, lineRange: NSRange) -> [CGRect] { + var result = [CGRect](repeating: CGRect.zero, count: lineRange.length) + + let glyphRuns = CTLineGetGlyphRuns(line) as NSArray + for run in glyphRuns { + let run = run as! CTRun + let glyphCount = CTRunGetGlyphCount(run) + if glyphCount == 0 { + continue + } + + var glyphs = [CGGlyph](repeating: 0, count: glyphCount) + CTRunGetGlyphs(run, CFRangeMake(0, glyphCount), &glyphs) + + var positions = [CGPoint](repeating: CGPoint.zero, count: glyphCount) + CTRunGetPositions(run, CFRangeMake(0, glyphCount), &positions) + + var stringIndices = [CFIndex](repeating: 0, count: glyphCount) + CTRunGetStringIndices(run, CFRangeMake(0, glyphCount), &stringIndices) + + let attributes = CTRunGetAttributes(run) as NSDictionary + guard let font = attributes[kCTFontAttributeName] as! CTFont? else { + continue + } + + var boundingRects = [CGRect](repeating: CGRect.zero, count: glyphCount) + CTFontGetBoundingRectsForGlyphs(font, .default, &glyphs, &boundingRects, glyphCount) + + for i in 0 ..< glyphCount { + let charIndex = stringIndices[i] - lineRange.location + if charIndex >= 0 && charIndex < lineRange.length { + let pos = positions[i] + let bbox = boundingRects[i] + result[charIndex] = CGRect( + x: pos.x + bbox.origin.x, + y: pos.y + bbox.origin.y, + width: bbox.width, + height: bbox.height + ) + } + } + } + + return result +} + open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecognizerDelegate { public final class ApplyArguments { public let animation: ListViewItemUpdateAnimation @@ -1309,7 +1353,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn } public internal(set) var cachedLayout: InteractiveTextNodeLayout? - public internal(set) var revealGlyphCount: Int? + public internal(set) var revealCharacterCount: Int? public var renderContentTypes: RenderContentTypes = .all private var contentItemLayers: [Int: TextContentItemLayer] = [:] @@ -1443,7 +1487,9 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, - expandedBlocks: Set + expandedBlocks: Set, + computeCharacterRects: Bool = false, + minWidth: CGFloat? ) -> InteractiveTextNodeLayout { let blockQuoteLeftInset: CGFloat = 9.0 let blockQuoteRightInset: CGFloat = 0.0 @@ -1627,7 +1673,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn spoilerWords: [], embeddedItems: [], attachments: [], - additionalTrailingLine: nil + additionalTrailingLine: nil, + characterRects: nil ) additionalSegmentRightInset = 0.0 } @@ -1669,9 +1716,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn spoilerWords: [], embeddedItems: [], attachments: [], - additionalTrailingLine: nil + additionalTrailingLine: nil, + characterRects: computeCharacterRects ? computeCharacterRectsForLine(line: line, lineRange: NSRange(location: currentLineStartIndex, length: lineCharacterCount)) : nil )) - + remainingLines -= 1 if remainingLines <= 0 { break @@ -1728,7 +1776,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn spoilerWords: [], embeddedItems: [], attachments: [], - additionalTrailingLine: (truncationToken, 0.0) + additionalTrailingLine: (truncationToken, 0.0), + characterRects: computeCharacterRects ? computeCharacterRectsForLine(line: updatedLine, lineRange: lastLine.range ?? NSRange()) : nil ) } } @@ -1783,7 +1832,8 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn spoilerWords: [], embeddedItems: [], attachments: [], - additionalTrailingLine: (truncationToken, truncationTokenWidth) + additionalTrailingLine: (truncationToken, truncationTokenWidth), + characterRects: computeCharacterRects ? computeCharacterRectsForLine(line: updatedLine, lineRange: lastLine.range ?? NSRange()) : nil ) } } @@ -2011,6 +2061,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn size.width += insets.left + insets.right size.height += insets.top + insets.bottom + if let minWidth { + size.width = max(size.width, minWidth) + } + return InteractiveTextNodeLayout( attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, @@ -2033,16 +2087,17 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, - expandedBlocks: expandedBlocks + expandedBlocks: expandedBlocks, + minWidth: minWidth ) } - static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, expandedBlocks: Set) -> InteractiveTextNodeLayout { + static func calculateLayout(attributedString: NSAttributedString?, minimumNumberOfLines: Int, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, verticalAlignment: TextVerticalAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?, textShadowColor: UIColor?, textShadowBlur: CGFloat?, textStroke: (UIColor, CGFloat)?, displayContentsUnderSpoilers: Bool, customTruncationToken: ((UIFont, Bool) -> NSAttributedString?)?, expandedBlocks: Set, computeCharacterRects: Bool = false, minWidth: CGFloat? = nil) -> InteractiveTextNodeLayout { guard let attributedString else { - return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks) + return InteractiveTextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, explicitAlignment: alignment, resolvedAlignment: alignment, verticalAlignment: verticalAlignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), rawTextSize: CGSize(), truncated: false, firstLineOffset: 0.0, segments: [], backgroundColor: backgroundColor, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, expandedBlocks: expandedBlocks, minWidth: minWidth) } - return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks) + return calculateLayoutV2(attributedString: attributedString, minimumNumberOfLines: minimumNumberOfLines, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, backgroundColor: backgroundColor, constrainedSize: constrainedSize, alignment: alignment, verticalAlignment: verticalAlignment, lineSpacingFactor: lineSpacingFactor, cutout: cutout, insets: insets, lineColor: lineColor, textShadowColor: textShadowColor, textShadowBlur: textShadowBlur, textStroke: textStroke, displayContentsUnderSpoilers: displayContentsUnderSpoilers, customTruncationToken: customTruncationToken, expandedBlocks: expandedBlocks, computeCharacterRects: computeCharacterRects, minWidth: minWidth) } private func updateContentItems(arguments: ApplyArguments) { @@ -2187,7 +2242,7 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn return { arguments in var layout: InteractiveTextNodeLayout - if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks { + if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.explicitAlignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) && existingLayout.expandedBlocks == arguments.expandedBlocks && existingLayout.minWidth == arguments.minWidth { let stringMatch: Bool var colorMatch: Bool = true @@ -2215,10 +2270,10 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn layout = layout.withUpdatedDisplayContentsUnderSpoilers(arguments.displayContentsUnderSpoilers) } } else { - layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks) + layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects, minWidth: arguments.minWidth) } } else { - layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks) + layout = InteractiveTextNode.calculateLayout(attributedString: arguments.attributedString, minimumNumberOfLines: arguments.minimumNumberOfLines, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, verticalAlignment: arguments.verticalAlignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor, textShadowColor: arguments.textShadowColor, textShadowBlur: arguments.textShadowBlur, textStroke: arguments.textStroke, displayContentsUnderSpoilers: arguments.displayContentsUnderSpoilers, customTruncationToken: arguments.customTruncationToken, expandedBlocks: arguments.expandedBlocks, computeCharacterRects: arguments.computeCharacterRects, minWidth: arguments.minWidth) } let node = maybeNode ?? InteractiveTextNode() @@ -2255,58 +2310,42 @@ open class InteractiveTextNode: ASDisplayNode, TextNodeProtocol, UIGestureRecogn return count } - public func getCharacterToGlyphMapping() -> [Int] { - guard let cachedLayout = self.cachedLayout else { - return [] - } - return cachedLayout.getCharacterToGlyphMapping() - } - - public func updateRevealGlyphCount(count: Int?) { + public func updateRevealCharacterCount(count: Int?, animated: Bool) { + self.revealCharacterCount = count + guard let cachedLayout = self.cachedLayout else { return } - - self.revealGlyphCount = count - - if var count { - var actualCount = 0 - var itemCount = 0 - for item in self.getCharacterToGlyphMapping() { - if itemCount >= count { - break - } - actualCount = item - itemCount += 1 - } - count = actualCount - + + if let count { var nextItemId = 0 var currentCount = 0 for segment in cachedLayout.segments { let itemId = nextItemId nextItemId += 1 - - var segmentGlyphCount = 0 + + var segmentCharCount = 0 for line in segment.lines { - let glyphRuns = CTLineGetGlyphRuns(line.line) as NSArray - for run in glyphRuns { - let run = run as! CTRun - let glyphCount = CTRunGetGlyphCount(run) - segmentGlyphCount += Int(glyphCount) + if let characterRects = line.characterRects { + segmentCharCount += characterRects.count + } else if let range = line.range { + segmentCharCount += range.length + } else { + let ctRange = CTLineGetStringRange(line.line) + segmentCharCount += ctRange.length } } - - let segmentInnerCount = min(max(0, count - currentCount), segmentGlyphCount) + + let segmentInnerCount = min(max(0, count - currentCount), segmentCharCount) if let item = self.contentItemLayers[itemId] { - item.updateMaxGlyphDrawCount(value: segmentInnerCount) + item.updateMaxCharacterDrawCount(value: segmentInnerCount, animated: animated) } - - currentCount += segmentGlyphCount + + currentCount += segmentCharCount } } else { for (_, item) in self.contentItemLayers { - item.updateMaxGlyphDrawCount(value: nil) + item.updateMaxCharacterDrawCount(value: nil, animated: animated) } } } @@ -2390,14 +2429,12 @@ final class TextContentItemLayer: SimpleLayer { let size: CGSize let item: TextContentItem let mask: RenderMask? - let maxGlyphDrawCount: Int? - - init(size: CGSize, item: TextContentItem, mask: RenderMask?, maxGlyphDrawCount: Int?) { + + init(size: CGSize, item: TextContentItem, mask: RenderMask?) { self.size = size self.item = item self.mask = mask - self.maxGlyphDrawCount = maxGlyphDrawCount - + super.init() } } @@ -2476,8 +2513,6 @@ final class TextContentItemLayer: SimpleLayer { let offset = params.item.contentOffset let alignment: NSTextAlignment = .left - var drawnGlyphCount = 0 - for i in 0 ..< params.item.segment.lines.count { let line = params.item.segment.lines[i] @@ -2508,14 +2543,6 @@ final class TextContentItemLayer: SimpleLayer { let run = run as! CTRun let glyphCount = CTRunGetGlyphCount(run) - var runDrawGlyphCount = glyphCount - if let maxGlyphDrawCount = params.maxGlyphDrawCount { - if drawnGlyphCount >= maxGlyphDrawCount { - break - } - runDrawGlyphCount = CFIndex(max(0, min(Int(glyphCount), maxGlyphDrawCount - drawnGlyphCount))) - } - let attributes = CTRunGetAttributes(run) as NSDictionary if attributes["Attribute__EmbeddedItem"] != nil { continue @@ -2565,14 +2592,12 @@ final class TextContentItemLayer: SimpleLayer { let stringRange = CTRunGetStringRange(run) if line.attachments.contains(where: { $0.range.contains(stringRange.location) }) { } else { - CTRunDraw(run, context, CFRangeMake(0, runDrawGlyphCount)) + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) } } else { - CTRunDraw(run, context, CFRangeMake(0, runDrawGlyphCount)) + CTRunDraw(run, context, CFRangeMake(0, glyphCount)) } - - drawnGlyphCount += Int(glyphCount) - + if fixDoubleEmoji { context.setBlendMode(.normal) } @@ -2739,9 +2764,23 @@ final class TextContentItemLayer: SimpleLayer { } } - private(set) var params: Params? + private final class SnippetLayer: SimpleLayer { + let characterIndex: Int + + init(characterIndex: Int) { + self.characterIndex = characterIndex + super.init() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + private(set) var params: Params? + let renderNode: RenderNode + private let renderNodeContainer: SimpleLayer private var contentMaskNode: ASImageNode? private var overlayContentLayer: SimpleLayer? @@ -2755,21 +2794,28 @@ final class TextContentItemLayer: SimpleLayer { private var currentAnimationId: Int = 0 private var isAnimating: Bool = false private var currentContentMask: RenderMask? - - private var maxGlyphDrawCount: Int? - + private var revealMaskLayer: SimpleLayer? + private var revealLineMaskLayers: [SimpleLayer] = [] + + private var maxCharacterDrawCount: Int? + private var previousMaxCharacterDrawCount: Int = 0 + private var animatingSnippetLayers: [SnippetLayer] = [] + init(displaysAsynchronously: Bool) { self.renderNode = RenderNode() self.renderNode.displaysAsynchronously = displaysAsynchronously - + self.renderNodeContainer = SimpleLayer() + super.init() - - self.addSublayer(self.renderNode.layer) + + self.renderNodeContainer.addSublayer(self.renderNode.layer) + self.addSublayer(self.renderNodeContainer) } - + override init(layer: Any) { self.renderNode = RenderNode() - + self.renderNodeContainer = SimpleLayer() + super.init(layer: layer) } @@ -2777,17 +2823,294 @@ final class TextContentItemLayer: SimpleLayer { fatalError("init(coder:) has not been implemented") } - func updateMaxGlyphDrawCount(value: Int?) { - if self.maxGlyphDrawCount != value { - self.maxGlyphDrawCount = value - - if let renderParams = self.renderNode.params { - self.renderNode.params = RenderParams(size: renderParams.size, item: renderParams.item, mask: renderParams.mask, maxGlyphDrawCount: self.maxGlyphDrawCount) - self.renderNode.displayImmediately() + func updateMaxCharacterDrawCount(value: Int?, animated: Bool) { + if self.maxCharacterDrawCount == value { + return + } + self.maxCharacterDrawCount = value + self.updateRevealMask(animateNewSegments: animated) + } + + private struct RevealLineInfo { + let lineFrame: CGRect + let lineHeight: CGFloat + let revealedWidth: CGFloat + let isFull: Bool + let isRTL: Bool + } + + private func computeRevealedLines(lines: [InteractiveTextNodeLine], layerSize: CGSize, offset: CGPoint, characterLimit: Int) -> [RevealLineInfo] { + var result: [RevealLineInfo] = [] + var remainingCharacters = characterLimit + + for i in 0 ..< lines.count { + let line = lines[i] + + var lineFrame = line.frame + lineFrame.origin.y += offset.y + + if line.isRTL { + lineFrame.origin.x = offset.x + floor(layerSize.width - lineFrame.width) + lineFrame = displayLineFrame(frame: lineFrame, isRTL: true, boundingRect: CGRect(origin: CGPoint(), size: layerSize), cutout: nil) + } else { + lineFrame.origin.x += offset.x + } + + let lineHeight = line.ascent + line.descent + + guard let characterRects = line.characterRects else { + result.append(RevealLineInfo(lineFrame: lineFrame, lineHeight: lineHeight, revealedWidth: remainingCharacters > 0 ? lineFrame.width : 0.0, isFull: remainingCharacters > 0, isRTL: line.isRTL)) + continue + } + + if remainingCharacters <= 0 { + result.append(RevealLineInfo(lineFrame: lineFrame, lineHeight: lineHeight, revealedWidth: 0.0, isFull: false, isRTL: line.isRTL)) + continue + } + + let revealCount = min(characterRects.count, remainingCharacters) + var revealedWidth: CGFloat = 0.0 + if line.isRTL { + // Logical index 0 is the visually rightmost glyph in an RTL line, + // so the revealed extent grows leftward from the right edge. + var minX: CGFloat = .greatestFiniteMagnitude + for j in 0 ..< revealCount { + let rect = characterRects[j] + if !rect.isEmpty { + minX = min(minX, rect.minX) + } + } + if minX != .greatestFiniteMagnitude { + revealedWidth = ceil(lineFrame.width - minX) + } + } else { + for j in 0 ..< revealCount { + let rect = characterRects[j] + if !rect.isEmpty { + revealedWidth = max(revealedWidth, rect.maxX) + } + } + revealedWidth = ceil(revealedWidth) + } + + remainingCharacters -= characterRects.count + let isFull = remainingCharacters >= 0 + + result.append(RevealLineInfo(lineFrame: lineFrame, lineHeight: lineHeight, revealedWidth: revealedWidth, isFull: isFull, isRTL: line.isRTL)) + } + + return result + } + + private func updateRevealMask(animateNewSegments: Bool) { + guard let params = self.params else { + return + } + + let item = params.item + let lines = item.segment.lines + let layerSize = item.size + let offset = item.contentOffset + + let effectiveCharacterDrawCount: Int + if let maxCharacterDrawCount = self.maxCharacterDrawCount { + effectiveCharacterDrawCount = maxCharacterDrawCount + } else { + if self.previousMaxCharacterDrawCount > 0 || !self.animatingSnippetLayers.isEmpty { + // Reveal finished — compute total character count so mask and snippets + // can continue updating until all snippet animations complete + var totalCharCount = 0 + for line in lines { + if let characterRects = line.characterRects { + totalCharCount += characterRects.count + } else if let range = line.range { + totalCharCount += range.length + } else { + let ctRange = CTLineGetStringRange(line.line) + totalCharCount += ctRange.length + } + } + effectiveCharacterDrawCount = totalCharCount + } else { + // Nothing left to animate — remove the mask + if let _ = self.revealMaskLayer { + self.renderNodeContainer.mask = nil + self.revealMaskLayer = nil + self.revealLineMaskLayers.removeAll() + } + self.previousMaxCharacterDrawCount = 0 + return } } + + // Create or reuse the container mask layer + let revealMaskLayer: SimpleLayer + if let existing = self.revealMaskLayer { + revealMaskLayer = existing + } else { + revealMaskLayer = SimpleLayer() + revealMaskLayer.backgroundColor = UIColor.clear.cgColor + self.revealMaskLayer = revealMaskLayer + self.renderNodeContainer.mask = revealMaskLayer + } + revealMaskLayer.frame = CGRect(origin: CGPoint(), size: layerSize) + + // Compute current and previous reveal states + let currentLineInfos = self.computeRevealedLines(lines: lines, layerSize: layerSize, offset: offset, characterLimit: effectiveCharacterDrawCount) + + // Create snippet layers for newly revealed character rects + if self.previousMaxCharacterDrawCount < effectiveCharacterDrawCount, let contents = self.renderNode.layer.contents { + let containerOrigin = self.renderNodeContainer.frame.origin + + var previousRemaining = self.previousMaxCharacterDrawCount + var currentRemaining = effectiveCharacterDrawCount + var globalCharIndex = 0 + + for i in 0 ..< lines.count { + let line = lines[i] + let lineInfo = currentLineInfos[i] + + guard let characterRects = line.characterRects else { + continue + } + + let lineCharCount = characterRects.count + let prevCount = min(max(0, previousRemaining), lineCharCount) + let curCount = min(max(0, currentRemaining), lineCharCount) + + previousRemaining -= lineCharCount + currentRemaining -= lineCharCount + + if curCount <= prevCount { + globalCharIndex += lineCharCount + continue + } + + for j in prevCount ..< curCount { + let charRect = characterRects[j] + if charRect.isEmpty { + continue + } + + let snippetRect = CGRect( + x: lineInfo.lineFrame.minX + charRect.origin.x, + y: lineInfo.lineFrame.minY, + width: charRect.width, + height: lineInfo.lineHeight + ) + + if snippetRect.width < 0.5 { + continue + } + + let contentsRect = CGRect( + x: snippetRect.minX / layerSize.width, + y: snippetRect.minY / layerSize.height, + width: snippetRect.width / layerSize.width, + height: snippetRect.height / layerSize.height + ) + + let snippetLayer = SnippetLayer(characterIndex: globalCharIndex + j) + snippetLayer.contents = contents + snippetLayer.contentsRect = contentsRect + snippetLayer.contentsScale = self.renderNode.layer.contentsScale + snippetLayer.contentsGravity = self.renderNode.layer.contentsGravity + snippetLayer.frame = snippetRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y) + + self.addSublayer(snippetLayer) + self.animatingSnippetLayers.append(snippetLayer) + + ComponentTransition(animation: .curve(duration: 0.22, curve: .easeInOut)).animateBlur(layer: snippetLayer, fromRadius: 2.0, toRadius: 0.0) + snippetLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + snippetLayer.animatePosition(from: CGPoint(x: 0.0, y: 6.0), to: CGPoint(), duration: 0.2, additive: true) + snippetLayer.animateScale(from: 0.5, to: 1.0, duration: 0.2, completion: { [weak self, weak snippetLayer] _ in + guard let self, let snippetLayer else { + return + } + snippetLayer.removeFromSuperlayer() + self.animatingSnippetLayers.removeAll(where: { $0 === snippetLayer }) + self.updateRevealMask(animateNewSegments: false) + }) + } + globalCharIndex += lineCharCount + } + } + + // Build mask rects — use the lowest animating snippet index as the mask limit + // so the mask never extends past any character still being animated + let maskCharacterLimit: Int + if let lowestAnimating = self.animatingSnippetLayers.min(by: { $0.characterIndex < $1.characterIndex })?.characterIndex { + maskCharacterLimit = lowestAnimating + } else { + maskCharacterLimit = self.previousMaxCharacterDrawCount + } + let maskLineInfos = self.computeRevealedLines(lines: lines, layerSize: layerSize, offset: offset, characterLimit: maskCharacterLimit) + + var maskRects: [CGRect] = [] + var mergeStartY: CGFloat? + var mergeEndY: CGFloat? + + for info in maskLineInfos { + if info.revealedWidth <= 0.0 { + continue + } + + let maskFrame: CGRect + if info.isFull { + maskFrame = CGRect(x: 0.0, y: info.lineFrame.minY, width: layerSize.width, height: info.lineHeight) + } else if info.isRTL { + maskFrame = CGRect(x: info.lineFrame.maxX - info.revealedWidth, y: info.lineFrame.minY, width: info.revealedWidth, height: info.lineHeight) + } else { + maskFrame = CGRect(x: info.lineFrame.minX, y: info.lineFrame.minY, width: info.revealedWidth, height: info.lineHeight) + } + + if info.isFull { + if mergeStartY != nil { + mergeEndY = maskFrame.maxY + } else { + mergeStartY = maskFrame.minY + mergeEndY = maskFrame.maxY + } + } else { + if let startY = mergeStartY, let endY = mergeEndY { + maskRects.append(CGRect(x: 0.0, y: startY, width: layerSize.width, height: endY - startY)) + mergeStartY = nil + mergeEndY = nil + } + maskRects.append(maskFrame) + } + } + if let startY = mergeStartY, let endY = mergeEndY { + maskRects.append(CGRect(x: 0.0, y: startY, width: layerSize.width, height: endY - startY)) + } + + // Update mask child layers + while self.revealLineMaskLayers.count < maskRects.count { + let childLayer = SimpleLayer() + childLayer.backgroundColor = UIColor.white.cgColor + revealMaskLayer.addSublayer(childLayer) + self.revealLineMaskLayers.append(childLayer) + } + while self.revealLineMaskLayers.count > maskRects.count { + let removed = self.revealLineMaskLayers.removeLast() + removed.removeFromSuperlayer() + } + + for i in 0 ..< maskRects.count { + self.revealLineMaskLayers[i].frame = maskRects[i] + } + + self.previousMaxCharacterDrawCount = effectiveCharacterDrawCount + + // If maxCharacterDrawCount is nil and all snippet animations have finished, clean up + if self.maxCharacterDrawCount == nil && self.animatingSnippetLayers.isEmpty { + self.renderNodeContainer.mask = nil + self.revealMaskLayer = nil + self.revealLineMaskLayers.removeAll() + self.previousMaxCharacterDrawCount = 0 + } } - + func update( params: Params, animation: ListViewItemUpdateAnimation, @@ -2923,7 +3246,11 @@ final class TextContentItemLayer: SimpleLayer { } } - animation.animator.updateFrame(layer: self.renderNode.layer, frame: effectiveContentFrame, completion: nil) + animation.animator.updatePosition(layer: self.renderNodeContainer, position: effectiveContentFrame.center, completion: nil) + animation.animator.updateBounds(layer: self.renderNodeContainer, bounds: CGRect(origin: CGPoint(), size: effectiveContentFrame.size), completion: nil) + + animation.animator.updatePosition(layer: self.renderNode.layer, position: effectiveContentFrame.center, completion: nil) + animation.animator.updateBounds(layer: self.renderNode.layer, bounds: CGRect(origin: CGPoint(), size: effectiveContentFrame.size), completion: nil) var staticContentMask = contentMask if let contentMask, self.isAnimating { @@ -3040,10 +3367,11 @@ final class TextContentItemLayer: SimpleLayer { self.currentContentMask = contentMask - self.renderNode.params = RenderParams(size: contentFrame.size, item: params.item, mask: staticContentMask, maxGlyphDrawCount: self.maxGlyphDrawCount) + self.renderNode.params = RenderParams(size: contentFrame.size, item: params.item, mask: staticContentMask) + self.updateRevealMask(animateNewSegments: false) if synchronously { if let spoilerExpandRect, animation.isAnimated { - let localSpoilerExpandRect = spoilerExpandRect.offsetBy(dx: -self.renderNode.frame.minX, dy: -self.renderNode.frame.minY) + let localSpoilerExpandRect = spoilerExpandRect.offsetBy(dx: -self.renderNodeContainer.frame.minX, dy: -self.renderNodeContainer.frame.minY) let revealAnimationDuration: CGFloat = 0.55 @@ -3051,7 +3379,7 @@ final class TextContentItemLayer: SimpleLayer { let previousContents = self.renderNode.layer.contents let copyContentsLayer = SimpleLayer() - copyContentsLayer.frame = self.renderNode.frame + copyContentsLayer.frame = self.renderNodeContainer.frame copyContentsLayer.contents = previousContents copyContentsLayer.masksToBounds = self.renderNode.layer.masksToBounds copyContentsLayer.contentsGravity = self.renderNode.layer.contentsGravity @@ -3073,7 +3401,7 @@ final class TextContentItemLayer: SimpleLayer { copyContentsLayer.addSublayer(copySublayer) } - self.renderNode.layer.superlayer?.insertSublayer(copyContentsLayer, below: self.renderNode.layer) + self.renderNodeContainer.superlayer?.insertSublayer(copyContentsLayer, below: self.renderNodeContainer) self.renderNode.displayImmediately() diff --git a/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift b/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift index 7c60e22cc6..559b444c02 100644 --- a/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift +++ b/submodules/TelegramUI/Components/JoinSubjectScreen/Sources/JoinSubjectScreen.swift @@ -12,7 +12,6 @@ import BalancedTextComponent import ButtonComponent import BundleIconComponent import Markdown -import Postbox import TelegramCore import AvatarNode import TelegramStringFormatting @@ -317,7 +316,7 @@ private final class JoinSubjectScreenComponent: Component { if let peerInfoController = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, @@ -351,19 +350,31 @@ private final class JoinSubjectScreenComponent: Component { } self.joinDisposable = (component.context.engine.peers.joinChatInteractively(with: group.link) - |> deliverOnMainQueue).start(next: { [weak self] peer in + |> deliverOnMainQueue).start(next: { [weak self] result in guard let self, let component = self.component else { return } - if group.isRequest { + switch result { + case let .joined(peer): + if group.isRequest { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .inviteRequestSent(title: presentationData.strings.MemberRequests_RequestToJoinSent, text: group.isGroup ? presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } else { + if let peer { + self.navigateToPeer(peer: peer) + } + } + self.environment?.controller()?.dismiss() + case let .webView(webView): let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .inviteRequestSent(title: presentationData.strings.MemberRequests_RequestToJoinSent, text: group.isGroup ? presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - } else { - if let peer { - self.navigateToPeer(peer: peer) + if let controller = self.environment?.controller() { + component.context.sharedContext.openJoinChatWebView(context: component.context, parentController: controller, updatedPresentationData: (initial: presentationData, signal: component.context.sharedContext.presentationData), webView: webView) + } + self.isJoining = false + if !self.isUpdating { + self.state?.updated(transition: .immediate) } } - self.environment?.controller()?.dismiss() }, error: { [weak self] error in guard let self, let component = self.component else { return @@ -894,7 +905,7 @@ private final class JoinSubjectScreenComponent: Component { |> deliverOnMainQueue).startStandalone(next: { value in var value: PresentationGroupCallPersistentSettings = value?.get(PresentationGroupCallPersistentSettings.self) ?? PresentationGroupCallPersistentSettings.default value.isMicrophoneEnabledByDefault = callMicrophoneIsEnabled - if let entry = CodableEntry(value) { + if let entry = EngineCodableEntry(value) { context.engine.calls.setGroupCallPersistentSettings(callId: groupCall.id, value: entry) } }) diff --git a/submodules/TelegramUI/Components/LegacyCamera/BUILD b/submodules/TelegramUI/Components/LegacyCamera/BUILD index 7ba810fb22..50c465fdca 100644 --- a/submodules/TelegramUI/Components/LegacyCamera/BUILD +++ b/submodules/TelegramUI/Components/LegacyCamera/BUILD @@ -16,7 +16,6 @@ swift_library( "//submodules/LegacyComponents", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/ShareController", "//submodules/LegacyUI", diff --git a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift index 1b55e61013..613a72b300 100644 --- a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift @@ -3,7 +3,6 @@ import UIKit import LegacyComponents import Display import TelegramCore -import Postbox import SSignalKit import SwiftSignalKit import AccountContext @@ -11,7 +10,7 @@ import ShareController import LegacyUI import LegacyMediaPickerUI -public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendPaidMessageStars: Int64 = 0, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ChatSendMessageActionSheetController.SendParameters?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { +public func presentedLegacyCamera(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendPaidMessageStars: Int64 = 0, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ChatSendMessageActionSheetController.SendParameters?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, photoToolbarView: ((TGPhotoEditorBackButton, TGPhotoEditorDoneButton, Bool, Bool) -> (UIView & TGPhotoToolbarViewProtocol)?)? = nil, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) @@ -45,8 +44,8 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLoca } controller.presentScheduleController = { _, done in - presentSchedulePicker(true, { time in - done?(time) + presentSchedulePicker(true, { time, silentPosting in + done?(time, silentPosting) }) } controller.presentTimerController = { done in @@ -79,9 +78,19 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLoca } let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: presentationData, presentInGlobalOverlay: { [weak legacyController] controller in + if let legacyController { + legacyController.presentInGlobalOverlay(controller) + } else if let mainWindow = context.sharedContext.mainWindow { + mainWindow.presentInGlobalOverlay(controller) + } else { + context.sharedContext.presentGlobalController(controller, nil) + } + }) paintStickersContext.captionPanelView = { return getCaptionPanelView() } + paintStickersContext.photoToolbarView = photoToolbarView controller.stickersContext = paintStickersContext controller.isImportant = true @@ -92,9 +101,9 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLoca controller.inhibitDocumentCaptions = false if let peer { - controller.recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + controller.recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) if peer.id != context.account.peerId { - if peer is TelegramUser { + if case .user = peer { controller.hasTimer = hasSchedule } controller.hasSilentPosting = true diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD b/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD index 4ffab8aeb7..e44c649b1f 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", "//submodules/MediaResources", diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift index 798088a4d5..b36288d29a 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import MediaResources @@ -133,7 +132,7 @@ public func legacyInputMicPalette(from theme: PresentationTheme) -> TGModernConv return TGModernConversationInputMicPallete(dark: theme.overallDarkAppearance, buttonColor: inputPanelTheme.actionControlFillColor, iconColor: inputPanelTheme.actionControlForegroundColor, backgroundColor: theme.rootController.navigationBar.opaqueBackgroundColor, borderColor: inputPanelTheme.panelSeparatorColor, lock: inputPanelTheme.panelControlAccentColor, textColor: inputPanelTheme.primaryTextColor, secondaryTextColor: inputPanelTheme.secondaryTextColor, recording: inputPanelTheme.mediaRecordingDotColor) } -public func legacyInstantVideoController(theme: PresentationTheme, forStory: Bool, panelFrame: CGRect, context: AccountContext, peerId: PeerId, slowmodeState: ChatSlowmodeState?, hasSchedule: Bool, send: @escaping (InstantVideoController, EnqueueMessage?) -> Void, displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void) -> InstantVideoController { +public func legacyInstantVideoController(theme: PresentationTheme, forStory: Bool, panelFrame: CGRect, context: AccountContext, peerId: EnginePeer.Id, slowmodeState: ChatSlowmodeState?, hasSchedule: Bool, send: @escaping (InstantVideoController, EnqueueMessage?) -> Void, displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void, presentSchedulePicker: @escaping (@escaping (Int32, Bool) -> Void) -> Void) -> InstantVideoController { let isSecretChat = peerId.namespace == Namespaces.Peer.SecretChat let legacyController = InstantVideoController(presentation: .custom, theme: theme) @@ -166,8 +165,8 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo return node }, canSendSilently: !isSecretChat, canSchedule: hasSchedule, reminder: peerId == context.account.peerId)! controller.presentScheduleController = { done in - presentSchedulePicker { time in - done?(time) + presentSchedulePicker { time, silentPosting in + done?(time, silentPosting) } } controller.finishedWithVideo = { [weak legacyController] videoUrl, previewImage, _, duration, dimensions, liveUploadData, adjustments, isSilent, scheduleTimestamp in @@ -189,7 +188,7 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo let thumbnailSize = finalDimensions.aspectFitted(CGSize(width: 320.0, height: 320.0)) let thumbnailImage = TGScaleImageToPixelSize(previewImage, thumbnailSize)! if let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { - context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnailData) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: thumbnailData) previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } } @@ -203,8 +202,8 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo } if let dict = adjustments.dictionary(), let data = try? NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false) { - let adjustmentsData = MemoryBuffer(data: data) - let digest = MemoryBuffer(data: adjustmentsData.md5Digest()) + let adjustmentsData = EngineMemoryBuffer(data: data) + let digest = EngineMemoryBuffer(data: adjustmentsData.md5Digest()) resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest, isStory: false) } } @@ -216,22 +215,22 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo let resource: TelegramMediaResource if let liveUploadData = liveUploadData as? LegacyLiveUploadInterfaceResult, resourceAdjustments == nil, let data = try? Data(contentsOf: videoUrl) { resource = LocalFileMediaResource(fileId: liveUploadData.id) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) } else { resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: videoUrl.path, adjustments: resourceAdjustments) } if let previewImage = previewImage { - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } if let data = compressImageToJPEG(previewImage, quality: 0.7, tempFilePath: tempFile.path) { context.account.postbox.mediaBox.storeCachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), data: data) } } - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) var message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let scheduleTime: Int32? = scheduleTimestamp > 0 ? scheduleTimestamp : nil diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD index 97f4042d3c..a651b6703f 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/BUILD @@ -14,16 +14,16 @@ swift_library( "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/LegacyComponents", + "//submodules/ChatPresentationInterfaceState", "//submodules/ComponentFlow", "//submodules/TelegramPresentationData", "//submodules/ContextUI", "//submodules/TooltipUI", "//submodules/UndoUI", + "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", "//submodules/TelegramUI/Components/MessageInputPanelComponent", - "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView", "//submodules/TelegramNotices", "//submodules/TextFormat", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index 4801c4627f..c517dcedb9 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -4,21 +4,20 @@ import AsyncDisplayKit import LegacyComponents import Display import TelegramCore -import Postbox import SwiftSignalKit import AccountContext -import LegacyComponents import ComponentFlow import MessageInputPanelComponent import TelegramPresentationData import ContextUI import TooltipUI -import LegacyMessageInputPanelInputView import UndoUI import TelegramNotices import TextFormat import TelegramUIPreferences import Pasteboard +import ChatEntityKeyboardInputNode +import ChatPresentationInterfaceState public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let context: AccountContext @@ -26,78 +25,181 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { private let isScheduledMessages: Bool private let isFile: Bool private let hasTimer: Bool + private let customEmojiAvailable: Bool private let pushViewController: (ViewController) -> Void private let present: (ViewController) -> Void - private let presentInGlobalOverlay: (ViewController) -> Void - private let makeEntityInputView: () -> LegacyMessageInputPanelInputView? - + private let presentInGlobalOverlay: (ViewController) -> Void + private let getNavigationController: () -> NavigationController? + private let state = ComponentState() private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private let inputPanel = ComponentView() - + private var currentTimeout: Int32? private var currentIsEditing = false private var currentHeight: CGFloat? private var currentIsVideo = false private var currentIsCaptionAbove = false - + private var currentInputMode: MessageInputPanelComponent.InputMode = .text + private var currentAdditionalInputHeight: CGFloat = 0.0 + private var currentSafeAreaInset: UIEdgeInsets = .zero + private var currentContainerBottomInset: CGFloat = 0.0 + private var usesContainerLayout = false + private let hapticFeedback = HapticFeedback() - - private var inputView: LegacyMessageInputPanelInputView? - private var isEmojiKeyboardActive = false - + + private let inputMediaNodeDataPromise = Promise() + private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData? + private var inputMediaNodeDataDisposable: Disposable? + private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() + private var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction? + private var inputMediaNode: ChatEntityKeyboardInputNode? + public var sendPressed: ((NSAttributedString?) -> Void)? public var focusUpdated: ((Bool) -> Void)? public var heightUpdated: ((Bool) -> Void)? public var timerUpdated: ((NSNumber?) -> Void)? public var captionIsAboveUpdated: ((Bool) -> Void)? - + + public var additionalInputHeight: CGFloat { + return self.currentAdditionalInputHeight + } + private weak var undoController: UndoOverlayController? private weak var tooltipController: TooltipScreen? - + private var isAIEnabled: Bool = false - + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)? - + public init( context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, + customEmojiAvailable: Bool, pushViewController: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, - makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView? + getNavigationController: @escaping () -> NavigationController? ) { self.context = context self.chatLocation = chatLocation self.isScheduledMessages = isScheduledMessages self.isFile = isFile self.hasTimer = hasTimer + self.customEmojiAvailable = customEmojiAvailable self.pushViewController = pushViewController self.present = present self.presentInGlobalOverlay = presentInGlobalOverlay - self.makeEntityInputView = makeEntityInputView - + self.getNavigationController = getNavigationController + super.init() - + if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_disable_ai_attach"] as? Double, value == 1.0 { } else if let peerId = chatLocation.peerId, peerId.namespace != Namespaces.Peer.SecretChat { self.isAIEnabled = true } - + + self.inputMediaNodeDataPromise.set( + ChatEntityKeyboardInputNode.inputData( + context: context, + chatPeerId: nil, + areCustomEmojiEnabled: customEmojiAvailable, + hasTrending: false, + hasStickers: false, + hasGifs: false, + sendGif: nil + ) + ) + self.inputMediaNodeDataDisposable = (self.inputMediaNodeDataPromise.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let self else { + return + } + self.inputMediaNodeData = value + if case .emoji = self.currentInputMode { + self.update(transition: .immediate) + } + }) + + self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction( + sendSticker: { _, _, _, _, _, _, _, _, _ in + return false + }, + sendEmoji: { _, _, _ in + }, + sendGif: { _, _, _, _, _ in + return false + }, + sendBotContextResultAsGif: { _, _, _, _, _, _ in + return false + }, + editGif: { _, _ in + }, + updateChoosingSticker: { _ in + }, + switchToTextInput: { [weak self] in + self?.activateInput() + }, + dismissTextInput: { + }, + insertText: { [weak self] text in + self?.inputPanelExternalState.insertText(text) + }, + backwardsDeleteText: { [weak self] in + self?.inputPanelExternalState.deleteBackward() + }, + openStickerEditor: { + }, + presentController: { [weak self] controller, _ in + guard let self else { + return + } + self.prepareForPresentedController(controller) + self.present(controller) + }, + presentGlobalOverlayController: { [weak self] controller, _ in + guard let self else { + return + } + self.prepareForPresentedController(controller) + self.presentInGlobalOverlay(controller) + }, + getNavigationController: getNavigationController, + requestLayout: { [weak self] transition in + self?.update(transition: transition) + } + ) + self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme + self.state._updated = { [weak self] transition, _ in if let self { self.update(transition: transition.containedViewLayoutTransition) } } } - - public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat { - return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + + deinit { + self.inputMediaNodeDataDisposable?.dispose() } - + + public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat { + self.currentSafeAreaInset = .zero + self.currentContainerBottomInset = 0.0 + self.usesContainerLayout = false + return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + } + + @objc(updateContainerLayoutSize:safeAreaInset:bottomInset:keyboardHeight:animated:) + public func updateContainerLayoutSize(_ size: CGSize, safeAreaInset: UIEdgeInsets, bottomInset: CGFloat, keyboardHeight: CGFloat, animated: Bool) -> CGFloat { + self.currentSafeAreaInset = safeAreaInset + self.currentContainerBottomInset = bottomInset + self.usesContainerLayout = true + return self.updateLayout(width: size.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), isMediaInputExpanded: false) + } + public func caption() -> NSAttributedString { if let view = self.inputPanel.view as? MessageInputPanelComponent.View, case let .text(caption) = view.getSendMessageInput() { return caption @@ -105,7 +207,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { return NSAttributedString() } } - + private var scheduledMessageInput: MessageInputPanelComponent.SendMessageInput? public func setCaption(_ caption: NSAttributedString?) { let sendMessageInput = MessageInputPanelComponent.SendMessageInput.text(caption ?? NSAttributedString()) @@ -115,12 +217,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { self.scheduledMessageInput = sendMessageInput } } - + public func animate(_ view: UIView, frame: CGRect) { let transition = ComponentTransition.spring(duration: 0.4) transition.setFrame(view: view, frame: frame) } - + public func setTimeout(_ timeout: Int32, isVideo: Bool, isCaptionAbove: Bool) { self.dismissAllTooltips() var timeout: Int32? = timeout @@ -131,33 +233,56 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { self.currentIsVideo = isVideo self.currentIsCaptionAbove = isCaptionAbove } - + public func activateInput() { + let transition: ContainedViewLayoutTransition + if self.currentInputMode != .text { + transition = .animated(duration: 0.4, curve: .spring) + } else { + transition = .immediate + } + self.currentInputMode = .text + self.update(transition: transition) if let view = self.inputPanel.view as? MessageInputPanelComponent.View { view.activateInput() } } - + public func dismissInput() -> Bool { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { if view.canDeactivateInput() { - self.isEmojiKeyboardActive = false - self.inputView = nil - view.deactivateInput(force: true) + let inputModeTransition: ContainedViewLayoutTransition + if self.currentInputMode != .text { + self.currentInputMode = .text + inputModeTransition = .animated(duration: 0.4, curve: .spring) + } else { + inputModeTransition = .immediate + } + if view.isActive { + view.deactivateInput(force: true) + } + if !inputModeTransition.isAnimated { + return true + } + self.update(transition: inputModeTransition) return true } else { view.animateError() return false } } else { + if self.currentInputMode != .text { + self.currentInputMode = .text + self.update(transition: .animated(duration: 0.4, curve: .spring)) + } return true } } - + public func onAnimateOut() { self.dismissAllTooltips() } - + public func baseHeight() -> CGFloat { return 52.0 } @@ -165,25 +290,41 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + private func update(transition: ContainedViewLayoutTransition) { if let (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics) = self.validLayout { let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, metrics: metrics, isMediaInputExpanded: false) } } - - public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + + public func updateLayout( + width: CGFloat, + leftInset: CGFloat, + rightInset: CGFloat, + bottomInset: CGFloat, + keyboardHeight: CGFloat, + additionalSideInsets: UIEdgeInsets, + maxHeight: CGFloat, + isSecondary: Bool, + transition: ContainedViewLayoutTransition, + metrics: LayoutMetrics, + isMediaInputExpanded: Bool + ) -> CGFloat { let previousLayout = self.validLayout self.validLayout = (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics) - + var transition = transition if keyboardHeight.isZero, let previousKeyboardHeight = previousLayout?.keyboardHeight, previousKeyboardHeight > 0.0, !transition.isAnimated { transition = .animated(duration: 0.4, curve: .spring) } - + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let theme = defaultDarkColorPresentationTheme - + let isLandscape = width > maxHeight + let deviceMetrics = DeviceMetrics(screenSize: CGSize(width: width, height: maxHeight), scale: UIScreen.main.scale, statusBarHeight: 0.0, onScreenNavigationHeight: nil) + let standardInputHeight = deviceMetrics.standardInputHeight(inLandscape: isLandscape) + let keyboardWasHidden = self.inputPanelExternalState.isKeyboardHidden + var timeoutValue: String? var timeoutSelected = false if self.isFile { @@ -200,25 +341,34 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { timeoutValue = "1" } } - - var maxInputPanelHeight = maxHeight - if keyboardHeight.isZero { + + let reservedKeyboardHeight: CGFloat + if case .emoji = self.currentInputMode { + reservedKeyboardHeight = max(keyboardHeight, standardInputHeight) + } else if self.inputPanelExternalState.isEditing && keyboardHeight.isZero && keyboardWasHidden { + reservedKeyboardHeight = standardInputHeight + } else { + reservedKeyboardHeight = keyboardHeight + } + + let maxInputPanelHeight: CGFloat + if keyboardHeight.isZero, case .text = self.currentInputMode, !keyboardWasHidden { maxInputPanelHeight = 60.0 } else { - maxInputPanelHeight = maxHeight - keyboardHeight - 100.0 + maxInputPanelHeight = max(60.0, maxHeight - reservedKeyboardHeight - 100.0) } - + var resetInputContents: MessageInputPanelComponent.SendMessageInput? if let scheduledMessageInput = self.scheduledMessageInput { resetInputContents = scheduledMessageInput self.scheduledMessageInput = nil } - + var hasTimer = self.hasTimer && self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser && !self.isScheduledMessages if self.chatLocation.peerId?.isRepliesOrSavedMessages(accountPeerId: self.context.account.peerId) == true { hasTimer = false } - + self.inputPanel.parentState = self.state let inputPanelSize = self.inputPanel.update( transition: ComponentTransition(transition), @@ -236,9 +386,15 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { alwaysDarkWhenHasText: false, resetInputContents: resetInputContents, nextInputMode: { [weak self] _ in - if self?.isEmojiKeyboardActive == true { + guard let self else { + return .emoji + } + switch self.currentInputMode { + case .text: + return .emoji + case .emoji: return .text - } else { + default: return .emoji } }, @@ -258,31 +414,23 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { stopAndPreviewMediaRecording: nil, discardMediaRecordingPreview: nil, attachmentAction: { [weak self] in - if let self { - self.toggleIsCaptionAbove() - } + self?.toggleIsCaptionAbove() }, attachmentButtonMode: self.currentIsCaptionAbove ? .captionDown : .captionUp, myReaction: nil, likeAction: nil, likeOptionsAction: nil, inputModeAction: { [weak self] in - if let self { - self.toggleInputMode() - } + self?.toggleInputMode() }, timeoutAction: hasTimer ? { [weak self] sourceView, gesture in - if let self { - self.presentTimeoutSetup(sourceView: sourceView, gesture: gesture) - } + self?.presentTimeoutSetup(sourceView: sourceView, gesture: gesture) } : nil, forwardAction: nil, paidMessageAction: nil, moreAction: nil, presentCaptionPositionTooltip: { [weak self] sourceView in - if let self { - self.presentCaptionPositionTooltip(sourceView: sourceView) - } + self?.presentCaptionPositionTooltip(sourceView: sourceView) }, presentVoiceMessagesUnavailableTooltip: nil, presentTextLengthLimitTooltip: nil, @@ -300,63 +448,246 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { displayGradient: false, bottomInset: 0.0, isFormattingLocked: false, - hideKeyboard: false, - customInputView: self.inputView, - forceIsEditing: false, + hideKeyboard: self.currentInputMode == .emoji, + customInputView: nil, + forceIsEditing: self.currentInputMode == .emoji, disabledPlaceholder: nil, header: nil, isChannel: false, storyItem: nil, chatLocation: self.chatLocation, aiCompose: self.isAIEnabled ? { [weak self] in - guard let self else { - return - } - self.openAICompose() + self?.openAICompose() } : nil ) ), environment: {}, containerSize: CGSize(width: width, height: maxInputPanelHeight) ) + let inputPanelHeight = inputPanelSize.height - 8.0 + var totalHeight = inputPanelHeight + var inputMediaHeight: CGFloat = 0.0 + self.currentAdditionalInputHeight = 0.0 + var inputMediaNodeForLayout: ChatEntityKeyboardInputNode? + var isNewInputMediaNode = false + var retainedInputHeight = keyboardHeight + var shouldRetainHiddenInputHeight = false + + if case .emoji = self.currentInputMode, let inputData = self.inputMediaNodeData { + let inputMediaNode: ChatEntityKeyboardInputNode + if let current = self.inputMediaNode { + inputMediaNode = current + } else { + isNewInputMediaNode = true + inputMediaNode = ChatEntityKeyboardInputNode( + context: self.context, + currentInputData: inputData, + updatedInputData: self.inputMediaNodeDataPromise.get(), + defaultToEmojiTab: true, + opaqueTopPanelBackground: false, + useOpaqueTheme: false, + interaction: self.inputMediaInteraction, + chatPeerId: nil, + stateContext: self.inputMediaNodeStateContext + ) + inputMediaNode.clipsToBounds = true + inputMediaNode.externalTopPanelContainerImpl = nil + inputMediaNode.useExternalSearchContainer = true + self.inputMediaNode = inputMediaNode + } + + if inputMediaNode.view.superview == nil { + if let inputPanelView = self.inputPanel.view { + self.view.insertSubview(inputMediaNode.view, belowSubview: inputPanelView) + } else { + self.view.addSubview(inputMediaNode.view) + } + } + inputMediaNodeForLayout = inputMediaNode + + let inputPresentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) + let presentationInterfaceState = ChatPresentationInterfaceState( + chatWallpaper: .builtin(WallpaperSettings()), + theme: inputPresentationData.theme, + preferredGlassType: .default, + strings: inputPresentationData.strings, + dateTimeFormat: inputPresentationData.dateTimeFormat, + nameDisplayOrder: inputPresentationData.nameDisplayOrder, + limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, + fontSize: inputPresentationData.chatFontSize, + bubbleCorners: inputPresentationData.chatBubbleCorners, + accountPeerId: self.context.account.peerId, + mode: .standard(.default), + chatLocation: .peer(id: self.context.account.peerId), + subject: nil, + greetingData: nil, + pendingUnpinnedAllMessages: false, + activeGroupCallInfo: nil, + hasActiveGroupCall: false, + threadData: nil, + isGeneralThreadClosed: nil, + replyMessage: nil, + accountPeerColor: nil, + businessIntro: nil + ) + + let heightAndOverflow = inputMediaNode.updateLayout( + width: width, + leftInset: 0.0, + rightInset: 0.0, + bottomInset: 0.0, + standardInputHeight: standardInputHeight, + inputHeight: 0.0, + maximumHeight: maxHeight, + inputPanelHeight: 0.0, + transition: .immediate, + interfaceState: presentationInterfaceState, + layoutMetrics: metrics, + deviceMetrics: deviceMetrics, + isVisible: true, + isExpanded: false + ) + inputMediaHeight = heightAndOverflow.0 + self.currentAdditionalInputHeight = inputMediaHeight + totalHeight += inputMediaHeight + } else if let inputMediaNode = self.inputMediaNode { + self.inputMediaNode = nil + + if transition.isAnimated { + var dismissingInputHeight = keyboardHeight + if self.inputPanelExternalState.isEditing && (dismissingInputHeight.isZero && keyboardWasHidden) { + dismissingInputHeight = max(dismissingInputHeight, standardInputHeight) + } + let targetOriginY: CGFloat + if self.usesContainerLayout { + if dismissingInputHeight > 0.0 { + targetOriginY = maxHeight - dismissingInputHeight + } else { + targetOriginY = maxHeight + } + } else { + if dismissingInputHeight > 0.0 { + targetOriginY = inputPanelHeight + } else { + targetOriginY = inputPanelHeight + inputMediaNode.frame.height + } + } + let targetFrame = CGRect( + origin: CGPoint(x: inputMediaNode.frame.minX, y: targetOriginY), + size: inputMediaNode.frame.size + ) + transition.updateFrame(view: inputMediaNode.view, frame: targetFrame) + inputMediaNode.view.layer.animateAlpha(from: inputMediaNode.view.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak inputMediaNode] _ in + inputMediaNode?.view.removeFromSuperview() + }) + } else { + inputMediaNode.view.removeFromSuperview() + } + } + + if self.inputPanelExternalState.isEditing { + if case .emoji = self.currentInputMode { + retainedInputHeight = max(retainedInputHeight, standardInputHeight) + shouldRetainHiddenInputHeight = true + } else if retainedInputHeight.isZero && keyboardWasHidden { + retainedInputHeight = max(retainedInputHeight, standardInputHeight) + shouldRetainHiddenInputHeight = true + } + } + if self.currentAdditionalInputHeight.isZero && retainedInputHeight > 0.0 && shouldRetainHiddenInputHeight { + self.currentAdditionalInputHeight = retainedInputHeight + totalHeight += retainedInputHeight + } + + let isLandscapePhone = width > maxHeight && UIDevice.current.userInterfaceIdiom != .pad + let collapsedCaptionTopInset = self.currentSafeAreaInset.top + 48.0 + let expandedCaptionTopInset = self.currentSafeAreaInset.top + 8.0 + + var inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: inputPanelSize) + var inputMediaFrame = CGRect(origin: CGPoint(x: 0.0, y: inputPanelHeight), size: CGSize(width: width, height: inputMediaHeight)) + + if self.usesContainerLayout { + if isLandscapePhone { + inputPanelFrame.origin.y = maxHeight + 16.0 - 8.0 + inputMediaFrame.origin.y = maxHeight + 16.0 + } else if case .emoji = self.currentInputMode { + inputMediaFrame.origin.y = maxHeight - inputMediaHeight + if self.currentIsCaptionAbove { + inputPanelFrame.origin.y = expandedCaptionTopInset - 8.0 + } else { + inputPanelFrame.origin.y = inputMediaFrame.minY - inputPanelHeight - 8.0 + } + } else { + if self.currentIsCaptionAbove { + inputPanelFrame.origin.y = (retainedInputHeight > 0.0 ? expandedCaptionTopInset : collapsedCaptionTopInset) - 8.0 + } else { + let bottomOffset = max(self.currentContainerBottomInset, retainedInputHeight) + inputPanelFrame.origin.y = maxHeight - inputPanelHeight - bottomOffset - 8.0 + } + inputMediaFrame.origin.y = maxHeight + } + } + if let view = self.inputPanel.view { if view.superview == nil { self.view.addSubview(view) } - let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: inputPanelSize) transition.updateFrame(view: view, frame: inputPanelFrame) } - + + if let inputMediaNode = inputMediaNodeForLayout { + if isNewInputMediaNode && transition.isAnimated { + inputMediaNode.view.frame = inputMediaFrame.offsetBy(dx: 0.0, dy: inputMediaHeight) + inputMediaNode.view.alpha = 0.0 + inputMediaNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + inputMediaNode.view.alpha = 1.0 + transition.updateFrame(view: inputMediaNode.view, frame: inputMediaFrame) + } + if self.currentIsEditing != self.inputPanelExternalState.isEditing { self.currentIsEditing = self.inputPanelExternalState.isEditing self.focusUpdated?(self.currentIsEditing) } - - if self.currentHeight != inputPanelSize.height { - self.currentHeight = inputPanelSize.height + + if self.currentHeight != totalHeight { + self.currentHeight = totalHeight self.heightUpdated?(transition.isAnimated) } - - return inputPanelSize.height - 8.0 + + return totalHeight } - + + private func prepareForPresentedController(_ controller: ViewController) { + if controller is UndoOverlayController { + return + } + if let view = self.inputPanel.view as? MessageInputPanelComponent.View, view.isActive { + view.deactivateInput(force: true) + } + if self.currentInputMode != .text { + self.currentInputMode = .text + self.update(transition: .immediate) + } + } + private func openAICompose() { Task { @MainActor [weak self] in guard let self else { return } - + let effectiveInputText: NSAttributedString = self.caption() if effectiveInputText.length == 0 { return } - + let inputText = trimChatInputText(effectiveInputText) var entities: [MessageTextEntity] = [] if inputText.length != 0 { entities = generateTextEntities(inputText.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(inputText, maxAnimatedEmojisInText: 0)) } - + self.pushViewController(await self.context.sharedContext.makeTextProcessingScreen( context: self.context, theme: defaultDarkColorPresentationTheme, @@ -372,86 +703,57 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { sendContextActions: nil ), inputText: TextWithEntities(text: inputText.string, entities: entities), - copyResult: { [weak self] text in - guard let self else { - return - } - let _ = self + copyResult: { text in storeMessageTextInPasteboard(text.text, entities: text.entities) }, translateChat: nil )) } } - + private func toggleInputMode() { - self.isEmojiKeyboardActive = !self.isEmojiKeyboardActive - - if self.isEmojiKeyboardActive { - let inputView = self.makeEntityInputView() - inputView?.insertText = { [weak self] text in - if let self { - self.inputPanelExternalState.insertText(text) - } + switch self.currentInputMode { + case .text: + self.currentInputMode = .emoji + self.update(transition: .animated(duration: 0.4, curve: .spring)) + if let view = self.inputPanel.view as? MessageInputPanelComponent.View, !view.isActive { + view.activateInput() } - inputView?.deleteBackwards = { [weak self] in - if let self { - self.inputPanelExternalState.deleteBackward() - } - } - inputView?.switchToKeyboard = { [weak self] in - if let self { - self.isEmojiKeyboardActive = false - self.inputView = nil - self.update(transition: .immediate) - } - } - inputView?.presentController = { [weak self] c in - if let self { - if !(c is UndoOverlayController) { - self.isEmojiKeyboardActive = false - if let view = self.inputPanel.view as? MessageInputPanelComponent.View { - view.deactivateInput(force: true) - } - } - self.present(c) - } - } - self.inputView = inputView - self.update(transition: .immediate) - } else { - self.inputView = nil - self.update(transition: .immediate) + case .emoji: + self.activateInput() + default: + self.currentInputMode = .emoji + self.update(transition: .animated(duration: 0.4, curve: .spring)) } } - + private func toggleIsCaptionAbove() { self.currentIsCaptionAbove = !self.currentIsCaptionAbove self.captionIsAboveUpdated?(self.currentIsCaptionAbove) self.update(transition: .animated(duration: 0.3, curve: .spring)) - + self.dismissAllTooltips() - + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - + let title = self.currentIsCaptionAbove ? presentationData.strings.MediaPicker_InvertCaption_Updated_Up_Title : presentationData.strings.MediaPicker_InvertCaption_Updated_Down_Title let text = self.currentIsCaptionAbove ? presentationData.strings.MediaPicker_InvertCaption_Updated_Up_Text : presentationData.strings.MediaPicker_InvertCaption_Updated_Down_Text let animationName = self.currentIsCaptionAbove ? "message_preview_sort_above" : "message_preview_sort_below" - + let controller = UndoOverlayController( presentationData: presentationData, content: .universal(animation: animationName, scale: 1.0, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: 2.0), elevatedLayout: false, position: self.currentIsCaptionAbove ? .bottom : .top, - action: { _ in return false } + action: { _ in return false } ) self.present(controller) self.undoController = controller } - + private func presentTimeoutSetup(sourceView: UIView, gesture: ContextGesture?) { self.hapticFeedback.impact(.light) - + var items: [ContextMenuItem] = [] let updateTimeout: (Int32?) -> Void = { [weak self] timeout in @@ -465,46 +767,46 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { } } } - + let currentValue = self.currentTimeout let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme) let title = presentationData.strings.MediaPicker_Timer_Description let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil - - items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction))) + + items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in nil }, action: emptyAction))) items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_ViewOnce, icon: { theme in return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage() - }, action: { _, a in - a(.default) - + }, action: { _, action in + action(.default) + updateTimeout(viewOnceTimeout) }))) - + let values: [Int32] = [3, 10, 30] - + for value in values { items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_Seconds(value), icon: { theme in return currentValue == value ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage() - }, action: { _, a in - a(.default) - + }, action: { _, action in + action(.default) + updateTimeout(value) }))) } - + items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_DoNotDelete, icon: { theme in return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage() - }, action: { _, a in - a(.default) - + }, action: { _, action in + action(.default) + updateTimeout(nil) }))) - + let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView, position: self.currentIsCaptionAbove ? .bottom : .top)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) - self.present(contextController) + self.presentInGlobalOverlay(contextController) } - + private func dismissAllTooltips() { if let undoController = self.undoController { self.undoController = nil @@ -515,17 +817,17 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { tooltipController.dismiss() } } - + private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) { guard let superview = self.view.superview?.superview else { return } self.dismissAllTooltips() - + let parentFrame = superview.convert(superview.bounds, to: nil) let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize()) - + let isVideo = self.currentIsVideo let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let text: String @@ -540,7 +842,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { text = isVideo ? presentationData.strings.MediaPicker_Timer_Video_KeepTooltip : presentationData.strings.MediaPicker_Timer_Photo_KeepTooltip iconName = "anim_autoremove_off" } - + let tooltipController = TooltipScreen( account: self.context.account, sharedContext: self.context.sharedContext, @@ -557,16 +859,15 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { } ) self.tooltipController = tooltipController - self.present(tooltipController) + self.presentInGlobalOverlay(tooltipController) } - + private func presentCaptionPositionTooltip(sourceView: UIView) { guard let superview = self.view.superview?.superview else { return } self.dismissAllTooltips() - - + let _ = (ApplicationSpecificNotice.getCaptionAboveMediaTooltip(accountManager: self.context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] count in guard let self else { @@ -575,13 +876,13 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { if count > 2 { return } - + let parentFrame = superview.convert(superview.bounds, to: nil) let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX + 2.0, y: absoluteFrame.minY + 6.0), size: CGSize()) - + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - + let tooltipController = TooltipScreen( account: self.context.account, sharedContext: self.context.sharedContext, @@ -599,17 +900,23 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { } ) self.tooltipController = tooltipController - self.present(tooltipController) - + self.presentInGlobalOverlay(tooltipController) + let _ = ApplicationSpecificNotice.incrementCaptionAboveMediaTooltip(accountManager: self.context.sharedContext.accountManager).start() }) } - + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let result = super.hitTest(point, with: event) if let view = self.inputPanel.view, let panelResult = view.hitTest(self.view.convert(point, to: view), with: event) { return panelResult } + if let inputMediaNode = self.inputMediaNode, let inputMediaResult = inputMediaNode.view.hitTest(self.view.convert(point, to: inputMediaNode.view), with: event) { + return inputMediaResult + } + let result = super.hitTest(point, with: event) + if result === self.view { + return nil + } return result } } @@ -619,7 +926,7 @@ private final class HeaderContextReferenceContentSource: ContextReferenceContent var keepInPlace: Bool { return true } - + let position: ContextControllerReferenceViewInfo.ActionsPosition init(sourceView: UIView, position: ContextControllerReferenceViewInfo.ActionsPosition) { diff --git a/submodules/TelegramUI/Components/LensTransition/Sources/LensTransitionContainer.swift b/submodules/TelegramUI/Components/LensTransition/Sources/LensTransitionContainer.swift index 6d364b774e..d25f67c5d0 100644 --- a/submodules/TelegramUI/Components/LensTransition/Sources/LensTransitionContainer.swift +++ b/submodules/TelegramUI/Components/LensTransition/Sources/LensTransitionContainer.swift @@ -1481,26 +1481,28 @@ private final class LensTransitionContainerFallbackImpl: UIView, LensTransitionC override init(frame: CGRect) { self.backgroundView = GlassBackgroundView() - + super.init(frame: frame) - + + self.backgroundView.contentView.clipsToBounds = true self.addSubview(self.backgroundView) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + func animateIn(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView) { } - + func animateOut(fromRect: CGRect, toRect: CGRect, fromCornerRadius: CGFloat, toCornerRadius: CGFloat, isDark: Bool, sourceEffectView: LensTransitionContainerEffectView) { } - + func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, transition: ComponentTransition) { transition.setBounds(view: self.backgroundView, bounds: CGRect(origin: .zero, size: size)) transition.setPosition(view: self.backgroundView, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5)) self.backgroundView.update(size: size, cornerRadius: cornerRadius, isDark: isDark, tintColor: .init(kind: .panel), transition: transition) + transition.setCornerRadius(layer: self.backgroundView.contentView.layer, cornerRadius: cornerRadius) } } diff --git a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift index d4ca89a2b5..f16b416515 100644 --- a/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift +++ b/submodules/TelegramUI/Components/ListActionItemComponent/Sources/ListActionItemComponent.swift @@ -93,15 +93,23 @@ public final class ListActionItemComponent: Component { public enum LeftIcon: Equatable { public final class Check: Equatable { + public enum Style { + case round + case rectangle + } + + public let style: Style public let isSelected: Bool public let isEnabled: Bool public let toggle: (() -> Void)? public init( + style: Style = .round, isSelected: Bool, isEnabled: Bool = true, toggle: (() -> Void)? ) { + self.style = style self.isSelected = isSelected self.isEnabled = isEnabled self.toggle = toggle @@ -111,6 +119,9 @@ public final class ListActionItemComponent: Component { if lhs === rhs { return true } + if lhs.style != rhs.style { + return false + } if lhs.isSelected != rhs.isSelected { return false } @@ -318,12 +329,12 @@ public final class ListActionItemComponent: Component { self.action?() } - func update(size: CGSize, theme: PresentationTheme, isSelected: Bool, transition: ComponentTransition) { + func update(size: CGSize, theme: PresentationTheme, isSelected: Bool, isRectangle: Bool = false, transition: ComponentTransition) { let checkLayer: CheckLayer if let current = self.checkLayer { checkLayer = current } else { - checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check(isRectangle: false)) + checkLayer = CheckLayer(theme: CheckNodeTheme(theme: theme, style: .plain), content: .check(isRectangle: isRectangle)) self.checkLayer = checkLayer self.layer.addSublayer(checkLayer) } @@ -508,6 +519,8 @@ public final class ListActionItemComponent: Component { case .custom: contentLeftInset += 46.0 } + } else { + contentLeftInset += component.contentInsets.left } let titleSize = self.title.update( @@ -625,11 +638,11 @@ public final class ListActionItemComponent: Component { leftCheckView.frame = CGRect(origin: CGPoint(x: -checkSize.width, y: self.bounds.height == 0.0 ? checkFrame.minY : floor((self.bounds.height - checkSize.height) * 0.5)), size: checkFrame.size) transition.setPosition(view: leftCheckView, position: checkFrame.center) transition.setBounds(view: leftCheckView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size)) - leftCheckView.update(size: checkFrame.size, theme: component.theme, isSelected: check.isSelected, transition: .immediate) + leftCheckView.update(size: checkFrame.size, theme: component.theme, isSelected: check.isSelected, isRectangle: check.style == .rectangle, transition: .immediate) } else { transition.setPosition(view: leftCheckView, position: checkFrame.center) transition.setBounds(view: leftCheckView, bounds: CGRect(origin: CGPoint(), size: checkFrame.size)) - leftCheckView.update(size: checkFrame.size, theme: component.theme, isSelected: check.isSelected, transition: transition) + leftCheckView.update(size: checkFrame.size, theme: component.theme, isSelected: check.isSelected, isRectangle: check.style == .rectangle, transition: transition) } case let .custom(customLeftIcon, adjustLeftInset): var resetLeftIcon = false @@ -716,7 +729,8 @@ public final class ListActionItemComponent: Component { } } - if case .arrow = component.accessory { + switch component.accessory { + case .arrow: let arrowView: UIImageView var arrowTransition = transition if let current = self.arrowView { @@ -735,14 +749,7 @@ public final class ListActionItemComponent: Component { let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 7.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size) arrowTransition.setFrame(view: arrowView, frame: arrowFrame) } - } else { - if let arrowView = self.arrowView { - self.arrowView = nil - arrowView.removeFromSuperview() - } - } - - if case .expandArrows = component.accessory { + case .expandArrows: let arrowView: UIImageView var arrowTransition = transition if let current = self.arrowView { @@ -761,7 +768,7 @@ public final class ListActionItemComponent: Component { let arrowFrame = CGRect(origin: CGPoint(x: availableSize.width - 16.0 - image.size.width, y: floor((contentHeight - image.size.height) * 0.5)), size: image.size) arrowTransition.setFrame(view: arrowView, frame: arrowFrame) } - } else { + default: if let arrowView = self.arrowView { self.arrowView = nil arrowView.removeFromSuperview() diff --git a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift index 8ca1510769..6da3970763 100644 --- a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift +++ b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift @@ -22,11 +22,6 @@ import EmojiTextAttachmentView import TextFormat public final class ListComposePollOptionComponent: Component { - public enum Style { - case glass - case legacy - } - public final class ResetText: Equatable { public let value: NSAttributedString @@ -117,7 +112,6 @@ public final class ListComposePollOptionComponent: Component { public let externalState: TextFieldComponent.ExternalState? public let context: AccountContext - public let style: Style public let theme: PresentationTheme public let strings: PresentationStrings public let placeholder: NSAttributedString? @@ -130,6 +124,7 @@ public final class ListComposePollOptionComponent: Component { public let canReorder: Bool public let canAdd: Bool public let attachment: Attachment? + public let formattingAvailable: Bool public let emptyLineHandling: TextFieldComponent.EmptyLineHandling public let returnKeyType: UIReturnKeyType public let returnKeyAction: (() -> Void)? @@ -141,12 +136,12 @@ public final class ListComposePollOptionComponent: Component { public let attachAction: (() -> Void)? public let deleteAction: (() -> Void)? public let paste: ((TextFieldComponent.PasteData) -> Void)? + public let present: ((ViewController) -> Void)? public let tag: AnyObject? public init( externalState: TextFieldComponent.ExternalState?, context: AccountContext, - style: Style = .legacy, theme: PresentationTheme, strings: PresentationStrings, placeholder: NSAttributedString? = nil, @@ -159,6 +154,7 @@ public final class ListComposePollOptionComponent: Component { canReorder: Bool = false, canAdd: Bool = false, attachment: Attachment? = nil, + formattingAvailable: Bool = false, emptyLineHandling: TextFieldComponent.EmptyLineHandling, returnKeyType: UIReturnKeyType = .next, returnKeyAction: (() -> Void)? = nil, @@ -170,11 +166,11 @@ public final class ListComposePollOptionComponent: Component { attachAction: (() -> Void)? = nil, deleteAction: (() -> Void)? = nil, paste: ((TextFieldComponent.PasteData) -> Void)? = nil, + present: ((ViewController) -> Void)? = nil, tag: AnyObject? = nil ) { self.externalState = externalState self.context = context - self.style = style self.theme = theme self.strings = strings self.placeholder = placeholder @@ -187,6 +183,7 @@ public final class ListComposePollOptionComponent: Component { self.canReorder = canReorder self.canAdd = canAdd self.attachment = attachment + self.formattingAvailable = formattingAvailable self.emptyLineHandling = emptyLineHandling self.returnKeyType = returnKeyType self.returnKeyAction = returnKeyAction @@ -198,6 +195,7 @@ public final class ListComposePollOptionComponent: Component { self.attachAction = attachAction self.deleteAction = deleteAction self.paste = paste + self.present = present self.tag = tag } @@ -208,9 +206,6 @@ public final class ListComposePollOptionComponent: Component { if lhs.context !== rhs.context { return false } - if lhs.style != rhs.style { - return false - } if lhs.theme !== rhs.theme { return false } @@ -247,6 +242,9 @@ public final class ListComposePollOptionComponent: Component { if lhs.attachment != rhs.attachment { return false } + if lhs.formattingAvailable != rhs.formattingAvailable { + return false + } if lhs.emptyLineHandling != rhs.emptyLineHandling { return false } @@ -475,6 +473,8 @@ public final class ListComposePollOptionComponent: Component { private var statusNode: RadialStatusNode? private var animationLayer: InlineStickerItemLayer? private var videoIconView: UIImageView? + private var webpageOverlayView: UIView? + private var webpageIconView: UIImageView? private let imageButton = HighlightTrackingButton() private var checkView: CheckView? @@ -670,6 +670,92 @@ public final class ListComposePollOptionComponent: Component { } component.attachAction?() } + + private func updateWebpageIconView(frame: CGRect, tintColor: UIColor, transition: ComponentTransition) { + var webpageIconTransition = transition + let webpageIconView: UIImageView + if let current = self.webpageIconView { + webpageIconView = current + } else { + webpageIconTransition = webpageIconTransition.withAnimation(.none) + webpageIconView = UIImageView(image: UIImage(bundleImageName: "Chat/Context Menu/Link")?.withRenderingMode(.alwaysTemplate)) + webpageIconView.isUserInteractionEnabled = false + self.webpageIconView = webpageIconView + self.addSubview(webpageIconView) + + if !transition.animation.isImmediate { + let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2) + alphaTransition.animateAlpha(view: webpageIconView, from: 0.0, to: 1.0) + alphaTransition.animateScale(view: webpageIconView, from: 0.01, to: 1.0) + } + } + webpageIconView.tintColor = tintColor + + let iconSize = webpageIconView.image?.size ?? CGSize(width: 30.0, height: 30.0) + let iconFrame = CGRect(origin: CGPoint(x: frame.center.x - iconSize.width * 0.5, y: frame.center.y - iconSize.height * 0.5), size: iconSize) + webpageIconTransition.setPosition(view: webpageIconView, position: iconFrame.center) + webpageIconTransition.setBounds(view: webpageIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + self.bringSubviewToFront(webpageIconView) + } + + private func removeWebpageIconView(transition: ComponentTransition) { + if let webpageIconView = self.webpageIconView { + self.webpageIconView = nil + if !transition.animation.isImmediate { + let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2) + alphaTransition.setAlpha(view: webpageIconView, alpha: 0.0, completion: { [weak webpageIconView] _ in + webpageIconView?.removeFromSuperview() + }) + alphaTransition.setScale(view: webpageIconView, scale: 0.001) + } else { + webpageIconView.removeFromSuperview() + } + } + } + + private func updateWebpageOverlayView(frame: CGRect, transition: ComponentTransition) { + var webpageOverlayTransition = transition + let webpageOverlayView: UIView + if let current = self.webpageOverlayView { + webpageOverlayView = current + } else { + webpageOverlayTransition = webpageOverlayTransition.withAnimation(.none) + webpageOverlayView = UIView() + webpageOverlayView.isUserInteractionEnabled = false + webpageOverlayView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.3) + webpageOverlayView.layer.cornerRadius = 10.0 + webpageOverlayView.clipsToBounds = true + self.webpageOverlayView = webpageOverlayView + self.addSubview(webpageOverlayView) + + if !transition.animation.isImmediate { + let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2) + alphaTransition.animateAlpha(view: webpageOverlayView, from: 0.0, to: 1.0) + alphaTransition.animateScale(view: webpageOverlayView, from: 0.01, to: 1.0) + } + } + + if let webpageIconView = self.webpageIconView { + self.insertSubview(webpageOverlayView, belowSubview: webpageIconView) + } + webpageOverlayTransition.setPosition(view: webpageOverlayView, position: frame.center) + webpageOverlayTransition.setBounds(view: webpageOverlayView, bounds: CGRect(origin: CGPoint(), size: frame.size)) + } + + private func removeWebpageOverlayView(transition: ComponentTransition) { + if let webpageOverlayView = self.webpageOverlayView { + self.webpageOverlayView = nil + if !transition.animation.isImmediate { + let alphaTransition: ComponentTransition = .easeInOut(duration: 0.2) + alphaTransition.setAlpha(view: webpageOverlayView, alpha: 0.0, completion: { [weak webpageOverlayView] _ in + webpageOverlayView?.removeFromSuperview() + }) + alphaTransition.setScale(view: webpageOverlayView, scale: 0.001) + } else { + webpageOverlayView.removeFromSuperview() + } + } + } func update(component: ListComposePollOptionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true @@ -681,10 +767,7 @@ public final class ListComposePollOptionComponent: Component { self.component = component self.state = state - var verticalInset: CGFloat = 12.0 - if case .glass = component.style { - verticalInset = 16.0 - } + let verticalInset: CGFloat = 16.0 var leftInset: CGFloat = 16.0 var rightInset: CGFloat = 16.0 let modeSelectorSize = CGSize(width: 32.0, height: 32.0) @@ -732,11 +815,15 @@ public final class ListComposePollOptionComponent: Component { enableInlineAnimations: component.enableInlineAnimations, emptyLineHandling: component.emptyLineHandling, externalHandlingForMultilinePaste: true, - formatMenuAvailability: .none, + formatMenuAvailability: component.formattingAvailable ? .available([.bold, .italic, .strikethrough, .underline, .monospace, .spoiler, .link]) : .none, returnKeyType: component.returnKeyType, lockedFormatAction: { }, - present: { _ in + present: { [weak self] c in + guard let self, let component = self.component else { + return + } + component.present?(c) }, paste: { [weak self] data in guard let self, let component = self.component else { @@ -1048,13 +1135,15 @@ public final class ListComposePollOptionComponent: Component { imageNodeTransition.setBounds(view: imageNode.view, bounds: CGRect(origin: CGPoint(), size: imageNodeFrame.size)) var imageSize = imageNodeSize - var updateMedia = false - if self.appliedMedia != media { + let emptyColor = component.theme.list.mediaPlaceholderColor + let updateMedia = self.appliedMedia != media || previousComponent?.theme !== component.theme + if updateMedia { self.appliedMedia = media - updateMedia = true } var isVideo = false + var webpageIconTintColor: UIColor? + var webpageHasImageThumbnail = false if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations), let photoReference = media.concrete(TelegramMediaImage.self) { imageSize = largest.dimensions.cgSize.aspectFilled(imageNodeSize) @@ -1093,6 +1182,35 @@ public final class ListComposePollOptionComponent: Component { return context })) } + } else if let webpage = media.media as? TelegramMediaWebpage { + if case let .Loaded(content) = webpage.content, let image = content.image { + if let largest = largestImageRepresentation(image.representations) { + imageSize = largest.dimensions.cgSize.aspectFilled(imageNodeSize) + } + if updateMedia { + imageNode.setSignal(chatMessagePhoto(postbox: component.context.account.postbox, userLocation: .other, photoReference: .webPage(webPage: WebpageReference(webpage), media: image))) + if let representation = smallestImageRepresentation(image.representations) { + let _ = fetchedMediaResource(mediaBox: component.context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: .media(media: .webPage(webPage: WebpageReference(webpage), media: image), resource: representation.resource)).startStandalone() + } + } + webpageIconTintColor = .white + webpageHasImageThumbnail = true + } else { + if updateMedia { + imageNode.setSignal(.single({ arguments in + let size = arguments.imageSize + let context = DrawingContext(size: size)! + context.withFlippedContext { context in + context.clear(CGRect(origin: .zero, size: size)) + context.setFillColor(component.theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor) + context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)) + context.fillPath() + } + return context + })) + } + webpageIconTintColor = component.theme.list.itemAccentColor + } } else if let map = media.media as? TelegramMediaMap { imageSize = CGSize(width: 40.0, height: 40.0) if updateMedia { @@ -1104,7 +1222,7 @@ public final class ListComposePollOptionComponent: Component { let cornerRadius: CGFloat = 10.0 let makeLayout = imageNode.asyncLayout() Queue.concurrentDefaultQueue().async { - let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor)) + let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: emptyColor)) Queue.mainQueue().async { apply() } @@ -1131,6 +1249,8 @@ public final class ListComposePollOptionComponent: Component { statusNode.transitionToState(.progress(color: .white, lineWidth: 2.0 - UIScreenPixel, value: max(0.027, min(1.0, progress)), cancelEnabled: true, animateRotation: true)) isVideo = false + self.removeWebpageIconView(transition: transition) + self.removeWebpageOverlayView(transition: transition) } else if let statusNode = self.statusNode { self.statusNode = nil if !transition.animation.isImmediate { @@ -1144,6 +1264,20 @@ public final class ListComposePollOptionComponent: Component { } } + if attachment.progress == nil { + if let webpageIconTintColor { + if webpageHasImageThumbnail { + self.updateWebpageOverlayView(frame: imageNodeFrame, transition: transition) + } else { + self.removeWebpageOverlayView(transition: transition) + } + self.updateWebpageIconView(frame: imageNodeFrame, tintColor: webpageIconTintColor, transition: transition) + } else { + self.removeWebpageIconView(transition: transition) + self.removeWebpageOverlayView(transition: transition) + } + } + if isVideo { let videoIconView: UIImageView if let current = self.videoIconView { @@ -1186,6 +1320,8 @@ public final class ListComposePollOptionComponent: Component { imageNode.view.removeFromSuperview() } self.imageButton.removeFromSuperview() + self.removeWebpageIconView(transition: transition) + self.removeWebpageOverlayView(transition: transition) if let videoIconView = self.videoIconView { self.videoIconView = nil @@ -1212,6 +1348,9 @@ public final class ListComposePollOptionComponent: Component { statusNode.view.removeFromSuperview() } } + } else { + self.removeWebpageIconView(transition: transition) + self.removeWebpageOverlayView(transition: transition) } if let inputMode = component.inputMode { @@ -1359,10 +1498,7 @@ public final class ListComposePollOptionComponent: Component { return } - var verticalInset: CGFloat = 12.0 - if case .glass = component.style { - verticalInset = 16.0 - } + let verticalInset: CGFloat = 16.0 var leftInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0 diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift index 8be6a995f9..ca3ab4dff2 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift @@ -89,6 +89,7 @@ public final class ListMultilineTextFieldItemComponent: Component { public let initialText: String public let resetText: ResetText? public let placeholder: String + public let placeholderDefinesMinHeight: Bool public let autocapitalizationType: UITextAutocapitalizationType public let autocorrectionType: UITextAutocorrectionType public let keyboardType: UIKeyboardType @@ -117,6 +118,7 @@ public final class ListMultilineTextFieldItemComponent: Component { initialText: String, resetText: ResetText? = nil, placeholder: String, + placeholderDefinesMinHeight: Bool = false, autocapitalizationType: UITextAutocapitalizationType = .sentences, autocorrectionType: UITextAutocorrectionType = .default, keyboardType: UIKeyboardType = .default, @@ -144,6 +146,7 @@ public final class ListMultilineTextFieldItemComponent: Component { self.initialText = initialText self.resetText = resetText self.placeholder = placeholder + self.placeholderDefinesMinHeight = placeholderDefinesMinHeight self.autocapitalizationType = autocapitalizationType self.autocorrectionType = autocorrectionType self.keyboardType = keyboardType @@ -186,6 +189,9 @@ public final class ListMultilineTextFieldItemComponent: Component { if lhs.placeholder != rhs.placeholder { return false } + if lhs.placeholderDefinesMinHeight != rhs.placeholderDefinesMinHeight { + return false + } if lhs.autocapitalizationType != rhs.autocapitalizationType { return false } @@ -469,7 +475,20 @@ public final class ListMultilineTextFieldItemComponent: Component { containerSize: CGSize(width: availableSize.width - textFieldRightInset, height: availableSize.height) ) - let size = CGSize(width: availableSize.width, height: textFieldSize.height - 1.0) + let placeholderSize = self.placeholder.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.placeholder.isEmpty ? " " : component.placeholder, font: Font.regular(17.0), textColor: component.theme.list.itemPlaceholderTextColor)), + maximumNumberOfLines: component.placeholderDefinesMinHeight ? 0 : 1 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) + ) + + var size = CGSize(width: availableSize.width, height: textFieldSize.height - 1.0) + if component.placeholderDefinesMinHeight { + size.height = max(size.height, placeholderSize.height + verticalInset * 2.0 - 1.0) + } let textFieldFrame = CGRect(origin: CGPoint(), size: textFieldSize) if let textFieldView = self.textField.view { @@ -480,14 +499,6 @@ public final class ListMultilineTextFieldItemComponent: Component { transition.setFrame(view: textFieldView, frame: textFieldFrame) } - let placeholderSize = self.placeholder.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.placeholder.isEmpty ? " " : component.placeholder, font: Font.regular(17.0), textColor: component.theme.list.itemPlaceholderTextColor)) - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) - ) let placeholderFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: placeholderSize) if let placeholderView = self.placeholder.view { if placeholderView.superview == nil { diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD index fa7ab63ca6..61888dcbd4 100644 --- a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUIPreferences", "//submodules/SSignalKit/SwiftSignalKit", diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift index 9ec0dfbfad..837de7f552 100644 --- a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LiveLocationHeaderPanelComponent.swift @@ -8,10 +8,9 @@ import AccountContext import TelegramCore import GlobalControlPanelsContext import SwiftSignalKit -import Postbox import PresentationDataUtils -private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) { +private func presentLiveLocationController(context: AccountContext, peerId: EnginePeer.Id, controller: ViewController) { let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in if let message = message, let strongController = controller { let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: { @@ -142,7 +141,7 @@ public final class LiveLocationHeaderPanelComponent: Component { } if let beginTimeAndTimeout { - items.append(LocationBroadcastActionSheetItem(context: component.context, peer: peer, title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { [weak self] in + items.append(LocationBroadcastActionSheetItem(context: component.context, peer: EnginePeer(peer), title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { [weak self] in dismissAction() guard let self, let component = self.component, let controller = component.controller() else { @@ -175,7 +174,7 @@ public final class LiveLocationHeaderPanelComponent: Component { presentLiveLocationController(context: component.context, peerId: peerId, controller: controller) } }, - close: { [weak self] in + close: { [weak self] _ in guard let self, let component = self.component, let controller = component.controller() else { return } diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift index 30bdac8506..105be70e18 100644 --- a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastActionSheetItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AccountContext import LiveLocationTimerNode @@ -11,14 +10,14 @@ import AvatarNode public class LocationBroadcastActionSheetItem: ActionSheetItem { public let context: AccountContext - public let peer: Peer + public let peer: EnginePeer public let title: String public let beginTimestamp: Double public let timeout: Double public let strings: PresentationStrings public let action: () -> Void - public init(context: AccountContext, peer: Peer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) { + public init(context: AccountContext, peer: EnginePeer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) { self.context = context self.peer = peer self.title = title @@ -105,7 +104,7 @@ public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode { let textColor: UIColor = self.theme.primaryTextColor self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: textColor) - self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(item.peer)) + self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: item.peer) self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: Int32(item.timeout) == liveLocationIndefinitePeriod ? -1.0 : item.timeout, strings: item.strings) } diff --git a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift index c0f86a677d..ac9479580c 100644 --- a/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift +++ b/submodules/TelegramUI/Components/LiveLocationHeaderPanelComponent/Sources/LocationBroadcastNavigationAccessoryPanel.swift @@ -25,7 +25,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode { private var nameDisplayOrder: PresentationPersonNameOrder private let tapAction: () -> Void - private let close: () -> Void + private let close: (UIView) -> Void private let contentNode: ASDisplayNode @@ -38,7 +38,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode { private var validLayout: (CGSize, CGFloat, CGFloat)? private var peersAndMode: ([EnginePeer], LocationBroadcastNavigationAccessoryPanelMode, Bool)? - init(accountPeerId: EnginePeer.Id, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, tapAction: @escaping () -> Void, close: @escaping () -> Void) { + init(accountPeerId: EnginePeer.Id, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, tapAction: @escaping () -> Void, close: @escaping (UIView) -> Void) { self.accountPeerId = accountPeerId self.theme = theme self.strings = strings @@ -178,19 +178,19 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode { let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0 - let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size) - let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size) + let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 6.0 + minimizedTitleOffset), size: titleLayout.size) + let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 22.0), size: subtitleLayout.size) if let image = self.iconNode.image { - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: 7.0 + leftInset, y: 9.0), size: image.size)) - transition.updateFrame(node: self.wavesNode, frame: CGRect(origin: CGPoint(x: -2.0 + leftInset, y: -3.0), size: CGSize(width: 48.0, height: 48.0))) + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: 9.0 + leftInset, y: 10.0), size: image.size)) + transition.updateFrame(node: self.wavesNode, frame: CGRect(origin: CGPoint(x: leftInset, y: -3.0), size: CGSize(width: 48.0, height: 48.0))) } transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame) transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame) let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - rightInset, y: minimizedTitleFrame.minY + 10.0), size: closeButtonSize)) + transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: size.width - 18.0 - closeButtonSize.width - rightInset, y: floorToScreenPixels((size.height - closeButtonSize.height) / 2.0)), size: closeButtonSize)) } func update(peers: [EnginePeer], mode: LocationBroadcastNavigationAccessoryPanelMode, canClose: Bool) { @@ -201,7 +201,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode { } @objc func closePressed() { - self.close() + self.close(self.closeButton.view) } @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD index 0fc27dc460..190756ccf1 100644 --- a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift index 7c1cb21b1e..6a91bf131a 100644 --- a/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift +++ b/submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation/Sources/ImageObjectSeparation.swift @@ -6,7 +6,6 @@ import CoreImage import CoreImage.CIFilterBuiltins import VideoToolbox import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import FileMediaResourceStatus @@ -66,7 +65,7 @@ public func cutoutAvailability(context: AccountContext) -> Signal take(1) |> mapToSignal { maybeFileAndMessage -> Signal in if let (file, message) = maybeFileAndMessage { - let fetchedData = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .file, reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource)) + let fetchedData = context.engine.resources.fetch(reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource), userLocation: .other, userContentType: .file) enum FetchStatus { case completed(String) @@ -76,8 +75,8 @@ public func cutoutAvailability(context: AccountContext) -> Signal { subscriber in let fetchedDisposable = fetchedData.start() - let resourceDataDisposable = context.account.postbox.mediaBox.resourceData(file.resource, attemptSynchronously: false).start(next: { next in - if next.complete { + let resourceDataDisposable = context.engine.resources.data(resource: EngineMediaResource(file.resource)).start(next: { next in + if next.isComplete { SSZipArchive.unzipFile(atPath: next.path, toDestination: NSTemporaryDirectory()) subscriber.putNext(.completed(compiledModelPath)) subscriber.putCompletion() diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLinkEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLinkEntity.swift index 684aa37810..69bab76f69 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLinkEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLinkEntity.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TextFormat -import Postbox import TelegramCore public final class DrawingLinkEntity: DrawingEntity, Codable { @@ -121,7 +120,7 @@ public final class DrawingLinkEntity: DrawingEntity, Codable { self.style = try container.decode(Style.self, forKey: .style) if let webpageData = try container.decodeIfPresent(Data.self, forKey: .webpage) { - self.webpage = PostboxDecoder(buffer: MemoryBuffer(data: webpageData)).decodeRootObject() as? TelegramMediaWebpage + self.webpage = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: webpageData)).decodeRootObject() as? TelegramMediaWebpage } else { self.webpage = nil } @@ -157,7 +156,7 @@ public final class DrawingLinkEntity: DrawingEntity, Codable { try container.encodeIfPresent(self.largeMedia, forKey: .largeMedia) if let webpage = self.webpage { - let encoder = PostboxEncoder() + let encoder = EnginePostboxEncoder() encoder.encodeRootObject(webpage) let webpageData = encoder.makeData() try container.encode(webpageData, forKey: .webpage) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift index d9eb6d0ea4..5e144c7115 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingLocationEntity.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TextFormat -import Postbox import TelegramCore public final class DrawingLocationEntity: DrawingEntity, Codable { @@ -106,13 +105,13 @@ public final class DrawingLocationEntity: DrawingEntity, Codable { self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false if let locationData = try container.decodeIfPresent(Data.self, forKey: .location) { - self.location = PostboxDecoder(buffer: MemoryBuffer(data: locationData)).decodeRootObject() as! TelegramMediaMap + self.location = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: locationData)).decodeRootObject() as! TelegramMediaMap } else { fatalError() } if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) { - self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile + self.icon = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile } self.queryId = try container.decodeIfPresent(Int64.self, forKey: .queryId) @@ -136,13 +135,13 @@ public final class DrawingLocationEntity: DrawingEntity, Codable { try container.encode(self.color, forKey: .color) try container.encode(self.hasCustomColor, forKey: .hasCustomColor) - var encoder = PostboxEncoder() + var encoder = EnginePostboxEncoder() encoder.encodeRootObject(self.location) let locationData = encoder.makeData() try container.encode(locationData, forKey: .location) if let icon = self.icon { - encoder = PostboxEncoder() + encoder = EnginePostboxEncoder() encoder.encodeRootObject(icon) let iconData = encoder.makeData() try container.encode(iconData, forKey: .icon) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index 13ce46aedd..a30694ea60 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AccountContext -import Postbox import TelegramCore func entitiesPath() -> String { @@ -38,7 +37,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case animatedImage(Data, UIImage) case video(TelegramMediaFile) case dualVideoReference(Bool) - case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?) + case message([EngineMessage.Id], CGSize, TelegramMediaFile?, CGRect?, CGFloat?) case gift(StarGift.UniqueGift, CGSize) public static func == (lhs: Content, rhs: Content) -> Bool { @@ -249,7 +248,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { if let gift = try container.decodeIfPresent(StarGift.UniqueGift.self, forKey: .gift) { let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero self.content = .gift(gift, size) - } else if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) { + } else if let messageIds = try container.decodeIfPresent([EngineMessage.Id].self, forKey: .messageIds) { let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile) let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift index ed4f63ef73..cfdaef8cb8 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingWeatherEntity.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import TextFormat -import Postbox import TelegramCore public final class DrawingWeatherEntity: DrawingEntity, Codable { @@ -101,7 +100,7 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable { self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) { - self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile + self.icon = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile } self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize) @@ -123,9 +122,9 @@ public final class DrawingWeatherEntity: DrawingEntity, Codable { try container.encode(self.color, forKey: .color) try container.encode(self.hasCustomColor, forKey: .hasCustomColor) - var encoder = PostboxEncoder() + var encoder = EnginePostboxEncoder() if let icon = self.icon { - encoder = PostboxEncoder() + encoder = EnginePostboxEncoder() encoder.encodeRootObject(icon) let iconData = encoder.makeData() try container.encode(iconData, forKey: .icon) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index e0d6fa0fbd..38447c846e 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -83,7 +82,7 @@ public final class DrawingWallpaperRenderer { public final class DrawingMessageRenderer { final class ContainerNode: ASDisplayNode { private let context: AccountContext - private let messages: [Message] + private let messages: [EngineMessage] private let isNight: Bool private let isOverlay: Bool private let isLink: Bool @@ -96,7 +95,7 @@ public final class DrawingMessageRenderer { init( context: AccountContext, - messages: [Message], + messages: [EngineMessage], isNight: Bool = false, isOverlay: Bool = false, isLink: Bool = false, @@ -223,17 +222,17 @@ public final class DrawingMessageRenderer { let avatarHeaderItem: ListViewItemHeader? if let author = self.messages.first?.author { - let avatarPeer: Peer - if let peer = self.messages.first!.peers[author.id] { + let avatarPeer: EnginePeer + if let peer = self.messages.first!.enginePeers[author.id] { avatarPeer = peer } else { avatarPeer = author } - avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: avatarPeer, message: self.messages.first!, theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder) + avatarHeaderItem = self.context.sharedContext.makeChatMessageAvatarHeaderItem(context: self.context, timestamp: self.messages.first?.timestamp ?? 0, peer: avatarPeer._asPeer(), message: self.messages.first!._asMessage(), theme: theme, strings: presentationData.strings, wallpaper: presentationData.chatWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder) } else { avatarHeaderItem = nil } - let items: [ListViewItem] = [self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: self.messages, theme: theme, strings: presentationData.strings, wallpaper: presentationData.theme.chat.defaultWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: nil, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)] + let items: [ListViewItem] = [self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: self.messages.map { $0._asMessage() }, theme: theme, strings: presentationData.strings, wallpaper: presentationData.theme.chat.defaultWallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: chatBubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: nil, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)] let inset: CGFloat = 16.0 var leftInset: CGFloat = 37.0 @@ -356,15 +355,15 @@ public final class DrawingMessageRenderer { } private let context: AccountContext - private let messages: [Message] - + private let messages: [EngineMessage] + private let dayContainerNode: ContainerNode private let nightContainerNode: ContainerNode private let overlayContainerNode: ContainerNode - + public init( context: AccountContext, - messages: [Message], + messages: [EngineMessage], parentView: UIView, isLink: Bool = false, isGift: Bool = false, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 190e1983e7..3209e5c488 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -6,7 +6,6 @@ import Vision import Photos import SwiftSignalKit import Display -import Postbox import TelegramCore import TelegramPresentationData import FastBlur @@ -167,7 +166,7 @@ public final class MediaEditor { case videoCollage([VideoCollageItem]) case asset(PHAsset) case draft(MediaEditorDraft) - case message(MessageId) + case message(EngineMessage.Id) case gift(StarGift.UniqueGift) case sticker(TelegramMediaFile) @@ -417,11 +416,11 @@ public final class MediaEditor { return (artist: artist, title: title) } - func playerAndThumbnails(_ signal: Signal, mirror: Bool = false) -> Signal<(AVPlayer, [UIImage], Double)?, NoError> { + func playerAndThumbnails(_ signal: Signal, mirroringChanges: [VideoMirroringChange] = []) -> Signal<(AVPlayer, [UIImage], Double)?, NoError> { return signal |> mapToSignal { player -> Signal<(AVPlayer, [UIImage], Double)?, NoError> in if let player, let asset = player.currentItem?.asset { - return videoFrames(asset: asset, count: framesCount, mirror: mirror) + return videoFrames(asset: asset, count: framesCount, mirroringChanges: mirroringChanges) |> map { framesAndUpdateTimestamp in return (player, framesAndUpdateTimestamp.0, framesAndUpdateTimestamp.1) } @@ -435,7 +434,8 @@ public final class MediaEditor { playerAndThumbnails(self.playerPromise.get()), self.additionalPlayersPromise.get() |> mapToSignal { players in - return combineLatest(players.compactMap { playerAndThumbnails(.single($0), mirror: true) }) + let mirroringChanges = self.values.additionalVideoMirroringChanges + return combineLatest(players.compactMap { playerAndThumbnails(.single($0), mirroringChanges: mirroringChanges) }) }, self.audioPlayerPromise.get(), self.valuesPromise.get(), @@ -567,6 +567,7 @@ public final class MediaEditor { videoVolume: 1.0, additionalVideoPath: nil, additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, @@ -630,9 +631,7 @@ public final class MediaEditor { return } let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) } - if mirror { - self.renderer.videoFinishPass.additionalTextureRotation = .rotate0DegreesMirrored - } + self.renderer.videoFinishPass.additionalTextureRotation = mirror ? .rotate0DegreesMirrored : .rotate0Degrees let hasTransparency = imageHasTransparency(image) self.renderer.consume(main: .texture(texture, time, hasTransparency, nil, 1.0, .zero), additionals: additionalTexture.flatMap { [.texture($0, time, false, nil, 1.0, .zero)] } ?? [], render: true, displayEnabled: false) } @@ -837,7 +836,7 @@ public final class MediaEditor { |> mapToSignal { message in var player: AVPlayer? if let message, !"".isEmpty { - if let maybeFile = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let path = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: "mp4") { + if let maybeFile = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let path = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(maybeFile.resource.id), pathExtension: "mp4") { let asset = AVURLAsset(url: URL(fileURLWithPath: path)) player = self.makePlayer(asset: asset) } @@ -1822,9 +1821,9 @@ public final class MediaEditor { self.updateAdditionalVideoPlaybackRange() } - public func setAdditionalVideo(_ path: String?, isDual: Bool = false, positionChanges: [VideoPositionChange]) { + public func setAdditionalVideo(_ path: String?, isDual: Bool = false, mirroringChanges: [VideoMirroringChange] = [], positionChanges: [VideoPositionChange]) { self.updateValues(mode: .skipRendering) { values in - var values = values.withUpdatedAdditionalVideo(path: path, isDual: isDual, positionChanges: positionChanges) + var values = values.withUpdatedAdditionalVideo(path: path, isDual: isDual, mirroringChanges: mirroringChanges, positionChanges: positionChanges) if path == nil { values = values.withUpdatedAdditionalVideoOffset(nil).withUpdatedAdditionalVideoTrimRange(nil).withUpdatedAdditionalVideoVolume(nil) } @@ -2415,7 +2414,29 @@ public final class MediaEditor { } -public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage? = nil, initialTimestamp: Double? = nil, mirror: Bool = false) -> Signal<([UIImage], Double), NoError> { +private func videoFrameMirroring(at timestamp: Double, changes: [VideoMirroringChange]) -> Bool { + guard let firstChange = changes.first else { + return false + } + var isMirrored = firstChange.isMirrored + for change in changes { + if timestamp >= change.timestamp { + isMirrored = change.isMirrored + } else { + break + } + } + return isMirrored +} + +private func mirroredVideoFrame(_ image: UIImage, mirrored: Bool) -> UIImage { + guard mirrored, let cgImage = image.cgImage else { + return image + } + return UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored) +} + +public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage? = nil, initialTimestamp: Double? = nil, mirroringChanges: [VideoMirroringChange] = []) -> Signal<([UIImage], Double), NoError> { func blurredImage(_ image: UIImage) -> UIImage? { guard let image = image.cgImage else { return nil @@ -2482,35 +2503,46 @@ public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage } else { firstFrame = generateSingleColorImage(size: CGSize(width: 24.0, height: 36.0), color: .black)! } + firstFrame = mirroredVideoFrame(firstFrame, mirrored: videoFrameMirroring(at: 0.0, changes: mirroringChanges)) if let asset { return Signal { subscriber in subscriber.putNext((Array(repeating: firstFrame, count: count), initialTimestamp ?? CACurrentMediaTime())) var timestamps: [NSValue] = [] + var requestedTimes: [CMTime] = [] let duration = asset.duration.seconds let interval = duration / Double(count) for i in 0 ..< count { - timestamps.append(NSValue(time: CMTime(seconds: Double(i) * interval, preferredTimescale: CMTimeScale(1000)))) + let requestedTime = CMTime(seconds: Double(i) * interval, preferredTimescale: CMTimeScale(1000)) + requestedTimes.append(requestedTime) + timestamps.append(NSValue(time: requestedTime)) } - var updatedFrames: [UIImage] = [] - imageGenerator?.generateCGImagesAsynchronously(forTimes: timestamps) { _, image, _, _, _ in + var updatedFrames = Array(repeating: firstFrame, count: count) + var remainingFrames = count + imageGenerator?.generateCGImagesAsynchronously(forTimes: timestamps) { requestedTime, image, actualTime, _, _ in + guard let frameIndex = requestedTimes.firstIndex(where: { CMTimeCompare($0, requestedTime) == 0 }) else { + return + } if let image { - updatedFrames.append(UIImage(cgImage: image, scale: 1.0, orientation: mirror ? .upMirrored : .up)) - if updatedFrames.count == count { + let frameTimestamp = actualTime.seconds.isFinite ? actualTime.seconds : requestedTime.seconds + updatedFrames[frameIndex] = mirroredVideoFrame( + UIImage(cgImage: image), + mirrored: videoFrameMirroring(at: frameTimestamp, changes: mirroringChanges) + ) + remainingFrames -= 1 + if remainingFrames == 0 { subscriber.putNext((updatedFrames, CACurrentMediaTime())) subscriber.putCompletion() } else { - var tempFrames = updatedFrames - for _ in 0 ..< count - updatedFrames.count { - tempFrames.append(firstFrame) - } - subscriber.putNext((tempFrames, CACurrentMediaTime())) + subscriber.putNext((updatedFrames, CACurrentMediaTime())) } - } else { - if let previous = updatedFrames.last { - updatedFrames.append(previous) + } else if remainingFrames > 0 { + remainingFrames -= 1 + if remainingFrames == 0 { + subscriber.putNext((updatedFrames, CACurrentMediaTime())) + subscriber.putCompletion() } } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift index fbb97794e7..7c1791dbf3 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorDraft.swift @@ -5,7 +5,6 @@ import CoreLocation import TelegramCore import TelegramUIPreferences import PersistentStringHash -import Postbox import AccountContext public struct MediaEditorResultPrivacy: Codable, Equatable { @@ -98,12 +97,12 @@ public final class MediaEditorDraft: Codable, Equatable { public let values: MediaEditorValues public let caption: NSAttributedString public let privacy: MediaEditorResultPrivacy? - public let forwardInfo: StoryId? + public let forwardInfo: EngineStoryId? public let timestamp: Int32 public let location: CLLocationCoordinate2D? public let expiresOn: Int32? - public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, duration: Double?, values: MediaEditorValues, caption: NSAttributedString, privacy: MediaEditorResultPrivacy?, forwardInfo: StoryId?, timestamp: Int32, location: CLLocationCoordinate2D?, expiresOn: Int32?) { + public init(path: String, isVideo: Bool, thumbnail: UIImage, dimensions: PixelDimensions, duration: Double?, values: MediaEditorValues, caption: NSAttributedString, privacy: MediaEditorResultPrivacy?, forwardInfo: EngineStoryId?, timestamp: Int32, location: CLLocationCoordinate2D?, expiresOn: Int32?) { self.path = path self.isVideo = isVideo self.thumbnail = thumbnail @@ -148,7 +147,7 @@ public final class MediaEditorDraft: Codable, Equatable { self.privacy = nil } - self.forwardInfo = try container.decodeIfPresent(StoryId.self, forKey: .forwardInfo) + self.forwardInfo = try container.decodeIfPresent(EngineStoryId.self, forKey: .forwardInfo) self.timestamp = try container.decodeIfPresent(Int32.self, forKey: .timestamp) ?? 1688909663 @@ -203,7 +202,7 @@ public final class MediaEditorDraft: Codable, Equatable { } private struct MediaEditorDraftItemId { - public let rawValue: MemoryBuffer + public let rawValue: EngineMemoryBuffer var value: Int64 { return self.rawValue.makeData().withUnsafeBytes { buffer -> Int64 in @@ -214,18 +213,18 @@ private struct MediaEditorDraftItemId { } } - init(_ rawValue: MemoryBuffer) { + init(_ rawValue: EngineMemoryBuffer) { self.rawValue = rawValue } init(_ value: Int64) { var value = value - self.rawValue = MemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) + self.rawValue = EngineMemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) } init(_ value: UInt64) { var value = Int64(bitPattern: value) - self.rawValue = MemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) + self.rawValue = EngineMemoryBuffer(data: Data(bytes: &value, count: MemoryLayout.size(ofValue: value))) } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index dc285429ae..f5a5aee9bc 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -22,7 +22,7 @@ public enum EditorToolKey: Int32, CaseIterable { case blur case curves case stickerOutline - + static let adjustmentToolsKeys: [EditorToolKey] = [ .enhance, .brightness, @@ -44,7 +44,7 @@ public struct VideoPositionChange: Codable, Equatable { case translationFrom case timestamp } - + public let additional: Bool public let translationFrom: CGPoint? public let timestamp: Double @@ -60,6 +60,24 @@ public struct VideoPositionChange: Codable, Equatable { } } +public struct VideoMirroringChange: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case isMirrored + case timestamp + } + + public let isMirrored: Bool + public let timestamp: Double + + public init( + isMirrored: Bool, + timestamp: Double + ) { + self.isMirrored = isMirrored + self.timestamp = timestamp + } +} + public struct MediaAudioTrack: Codable, Equatable { private enum CodingKeys: String, CodingKey { case path @@ -291,6 +309,9 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl if lhs.additionalVideoIsDual != rhs.additionalVideoIsDual { return false } + if lhs.additionalVideoMirroringChanges != rhs.additionalVideoMirroringChanges { + return false + } if lhs.additionalVideoPosition != rhs.additionalVideoPosition { return false } @@ -402,6 +423,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl case videoVolume case additionalVideoPath case additionalVideoIsDual + case additionalVideoMirroringChanges case additionalVideoPosition case additionalVideoScale case additionalVideoRotation @@ -591,6 +613,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl public let additionalVideoPath: String? public let additionalVideoIsDual: Bool + public let additionalVideoMirroringChanges: [VideoMirroringChange] public let additionalVideoPosition: CGPoint? public let additionalVideoScale: CGFloat? public let additionalVideoRotation: CGFloat? @@ -660,6 +683,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl videoVolume: CGFloat?, additionalVideoPath: String?, additionalVideoIsDual: Bool, + additionalVideoMirroringChanges: [VideoMirroringChange], additionalVideoPosition: CGPoint?, additionalVideoScale: CGFloat?, additionalVideoRotation: CGFloat?, @@ -700,6 +724,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl self.videoVolume = videoVolume self.additionalVideoPath = additionalVideoPath self.additionalVideoIsDual = additionalVideoIsDual + self.additionalVideoMirroringChanges = additionalVideoMirroringChanges self.additionalVideoPosition = additionalVideoPosition self.additionalVideoScale = additionalVideoScale self.additionalVideoRotation = additionalVideoRotation @@ -755,6 +780,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl self.additionalVideoPath = try container.decodeIfPresent(String.self, forKey: .additionalVideoPath) self.additionalVideoIsDual = try container.decodeIfPresent(Bool.self, forKey: .additionalVideoIsDual) ?? false + self.additionalVideoMirroringChanges = try container.decodeIfPresent([VideoMirroringChange].self, forKey: .additionalVideoMirroringChanges) ?? [] self.additionalVideoPosition = try container.decodeIfPresent(CGPoint.self, forKey: .additionalVideoPosition) self.additionalVideoScale = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoScale) self.additionalVideoRotation = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoRotation) @@ -829,6 +855,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl try container.encodeIfPresent(self.additionalVideoPath, forKey: .additionalVideoPath) try container.encodeIfPresent(self.additionalVideoIsDual, forKey: .additionalVideoIsDual) + try container.encodeIfPresent(self.additionalVideoMirroringChanges, forKey: .additionalVideoMirroringChanges) try container.encodeIfPresent(self.additionalVideoPosition, forKey: .additionalVideoPosition) try container.encodeIfPresent(self.additionalVideoScale, forKey: .additionalVideoScale) try container.encodeIfPresent(self.additionalVideoRotation, forKey: .additionalVideoRotation) @@ -869,125 +896,125 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl } public func makeCopy() -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } - func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + func withUpdatedAdditionalVideo(path: String?, isDual: Bool, mirroringChanges: [VideoMirroringChange], positionChanges: [VideoPositionChange]) -> MediaEditorValues { + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoMirroringChanges: mirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCollage(_ collage: [VideoCollageItem]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedVideoTrimRange(_ videoTrimRange: Range) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedCollageTrackSamples(_ collageTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedCoverDimensions(_ coverDimensions: CGSize?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset) } public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset) } public var resultDimensions: PixelDimensions { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift index 92b590f103..4c622874ad 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoExport.swift @@ -485,7 +485,7 @@ public final class MediaEditorVideoExport { } else if let additionalPath = self.configuration.values.additionalVideoPath { let asset = AVURLAsset(url: URL(fileURLWithPath: additionalPath)) additionalAsset = asset - signals = [.single(.video(asset: asset, rect: nil, scale: 1.0, offset: .zero, rotation: textureRotatonForAVAsset(asset, mirror: true), duration: asset.duration.seconds, trimRange: nil, trimOffset: nil, volume: nil))] + signals = [.single(.video(asset: asset, rect: nil, scale: 1.0, offset: .zero, rotation: textureRotatonForAVAsset(asset, mirror: false), duration: asset.duration.seconds, trimRange: nil, trimOffset: nil, volume: nil))] } var audioAsset: AVAsset? diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift index 9f9557616c..9f59ca4ac7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorVideoFFMpegWriter.swift @@ -105,7 +105,9 @@ final class MediaEditorVideoFFMpegWriter: MediaEditorVideoExportWriter { let height = CVPixelBufferGetHeight(buffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer) - let frame = FFMpegAVFrame(pixelFormat: .YUVA, width: Int32(width), height: Int32(height)) + guard let frame = FFMpegAVFrame(pixelFormat: .YUVA, width: Int32(width), height: Int32(height)) else { + return false + } CVPixelBufferLockBaseAddress(buffer, CVPixelBufferLockFlags.readOnly) let src = CVPixelBufferGetBaseAddress(buffer) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift index bf17eb05e1..90daf25142 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift @@ -264,8 +264,7 @@ private class VideoInputContext: NSObject, InputContext, AVPlayerItemOutputPullD super.init() - //TODO: mirror if self.additionalPlayer == nil && self.mirror - self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: rect == nil ? additional : false) + self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: false) let colorProperties: [String: Any] = [ AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2, diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift index 65206a070c..240c81a650 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift @@ -265,7 +265,7 @@ private func lookupSpringValue(_ t: CGFloat) -> CGFloat { for i in 0 ..< table.count - 2 { let lhs = table[i] let rhs = table[i + 1] - + if t >= lhs.0 && t <= rhs.0 { let fraction = (t - lhs.0) / (rhs.0 - lhs.0) let value = lhs.1 + fraction * (rhs.1 - lhs.1) @@ -289,7 +289,7 @@ struct VideoEncodeParameters { final class VideoFinishPass: RenderPass { private var cachedTexture: MTLTexture? - + var gradientPipelineState: MTLRenderPipelineState? var mainPipelineState: MTLRenderPipelineState? @@ -444,9 +444,8 @@ final class VideoFinishPass: RenderPass { if let position = values.additionalVideoPosition, let scale = values.additionalVideoScale, let rotation = values.additionalVideoRotation { self.additionalPosition = VideoFinishPass.VideoPosition(position: position, size: CGSize(width: 1080.0 / 4.0, height: 1440.0 / 4.0), scale: scale, rotation: rotation, mirroring: false, baseScale: self.additionalPosition.baseScale) } - if !values.additionalVideoPositionChanges.isEmpty { - self.videoPositionChanges = values.additionalVideoPositionChanges - } + self.videoPositionChanges = values.additionalVideoPositionChanges + self.additionalVideoMirroringChanges = values.additionalVideoMirroringChanges self.videoDuration = videoDuration self.additionalVideoDuration = additionalVideoDuration self.videoRange = values.videoTrimRange @@ -486,12 +485,30 @@ final class VideoFinishPass: RenderPass { private var isSticker = true private var coverDimensions: CGSize? private var videoPositionChanges: [VideoPositionChange] = [] + private var additionalVideoMirroringChanges: [VideoMirroringChange] = [] private var videoDuration: Double? private var additionalVideoDuration: Double? private var videoRange: Range? private var additionalVideoRange: Range? private var additionalVideoOffset: Double? + private func additionalVideoMirroring(at timestamp: Double) -> Bool { + guard let firstChange = self.additionalVideoMirroringChanges.first else { + return false + } + let assetTimestamp = timestamp + (self.additionalVideoOffset ?? 0.0) + + var isMirrored = firstChange.isMirrored + for change in self.additionalVideoMirroringChanges { + if assetTimestamp >= change.timestamp { + isMirrored = change.isMirrored + } else { + break + } + } + return isMirrored + } + enum VideoType { case main case additional @@ -566,6 +583,7 @@ final class VideoFinishPass: RenderPass { var mainPosition = self.mainPosition var additionalPosition = self.additionalPosition + additionalPosition = VideoPosition(position: additionalPosition.position, size: additionalPosition.size, scale: additionalPosition.scale, rotation: additionalPosition.rotation, mirroring: self.additionalVideoMirroring(at: timestamp), baseScale: additionalPosition.baseScale) var disappearingPosition = self.mainPosition var transitionFraction = 1.0 @@ -588,7 +606,7 @@ final class VideoFinishPass: RenderPass { backgroundTexture = additionalInput backgroundTextureRotation = self.additionalTextureRotation - mainPosition = VideoPosition(position: mainPosition.position, size: CGSize(width: 1440.0, height: 1920.0), scale: mainPosition.scale, rotation: mainPosition.rotation, mirroring: mainPosition.mirroring, baseScale: mainPosition.baseScale) + mainPosition = VideoPosition(position: mainPosition.position, size: CGSize(width: 1440.0, height: 1920.0), scale: mainPosition.scale, rotation: mainPosition.rotation, mirroring: additionalPosition.mirroring, baseScale: mainPosition.baseScale) additionalPosition = VideoPosition(position: additionalPosition.position, size: CGSize(width: 1080.0 / 4.0, height: 1920.0 / 4.0), scale: additionalPosition.scale, rotation: additionalPosition.rotation, mirroring: additionalPosition.mirroring, baseScale: additionalPosition.baseScale) foregroundTexture = mainInput diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index 229c8257ae..f99112df82 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -44,6 +44,7 @@ swift_library( "//submodules/ChatPresentationInterfaceState", "//submodules/DeviceAccess", "//submodules/LocationUI", + "//submodules/Weather", "//submodules/TelegramUI/Components/AudioWaveformComponent", "//submodules/ReactionSelectionNode", "//submodules/TelegramUI/Components/VolumeSliderContextItem", @@ -63,7 +64,6 @@ swift_library( "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/WebsiteType", "//submodules/UrlEscaping", - "//submodules/DeviceLocationManager", "//submodules/TelegramUI/Components/SaveProgressScreen", "//submodules/TelegramUI/Components/MediaAssetsContext", "//submodules/CheckNode", @@ -71,6 +71,7 @@ swift_library( "//submodules/TelegramUI/Components/AttachmentFileController", "//submodules/SaveToCameraRoll", "//submodules/TelegramUI/Components/ContextControllerImpl", + "//submodules/TelegramUI/Components/SliderComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift index 0b23bc037d..8d75439a5b 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift @@ -2,11 +2,10 @@ import Foundation import UIKit import Display import ComponentFlow -import SwiftSignalKit -import LegacyComponents import MediaEditor +import SliderComponent -final class AdjustmentSliderComponent: Component { +private final class AdjustmentSliderRowComponent: Component { typealias EnvironmentType = Empty let title: String @@ -44,7 +43,7 @@ final class AdjustmentSliderComponent: Component { self.isTrackingUpdated = isTrackingUpdated } - static func ==(lhs: AdjustmentSliderComponent, rhs: AdjustmentSliderComponent) -> Bool { + static func ==(lhs: AdjustmentSliderRowComponent, rhs: AdjustmentSliderRowComponent) -> Bool { if lhs.title != rhs.title { return false } @@ -72,12 +71,12 @@ final class AdjustmentSliderComponent: Component { return true } - final class View: UIView, UITextFieldDelegate { + final class View: UIView { private let title = ComponentView() private let value = ComponentView() - private var sliderView: TGPhotoEditorSliderView? + private let slider = ComponentView() - private var component: AdjustmentSliderComponent? + private var component: AdjustmentSliderRowComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { @@ -88,22 +87,15 @@ final class AdjustmentSliderComponent: Component { fatalError("init(coder:) has not been implemented") } - func update(component: AdjustmentSliderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + func update(component: AdjustmentSliderRowComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.state = state var internalIsTrackingUpdated: ((Bool) -> Void)? if let isTrackingUpdated = component.isTrackingUpdated { internalIsTrackingUpdated = { [weak self] isTracking in + isTrackingUpdated(isTracking) if let self { - if isTracking { - self.sliderView?.bordered = true - } else { - Queue.mainQueue().after(0.1) { - self.sliderView?.bordered = false - } - } - isTrackingUpdated(isTracking) let transition: ComponentTransition if isTracking { transition = .immediate @@ -119,52 +111,27 @@ final class AdjustmentSliderComponent: Component { } } } - - let sliderView: TGPhotoEditorSliderView - if let current = self.sliderView { - sliderView = current - sliderView.value = CGFloat(component.value) - } else { - sliderView = TGPhotoEditorSliderView() - sliderView.backgroundColor = .clear - sliderView.startColor = UIColor(rgb: 0xffffff) - sliderView.enablePanHandling = true - sliderView.trackCornerRadius = 1.0 - sliderView.lineSize = 2.0 - sliderView.minimumValue = CGFloat(component.minValue) - sliderView.maximumValue = CGFloat(component.maxValue) - sliderView.startValue = CGFloat(component.startValue) - sliderView.value = CGFloat(component.value) - sliderView.disablesInteractiveTransitionGestureRecognizer = true - sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) - sliderView.layer.allowsGroupOpacity = true - self.sliderView = sliderView - self.addSubview(sliderView) - } - sliderView.interactionBegan = { - internalIsTrackingUpdated?(true) - } - sliderView.interactionEnded = { - internalIsTrackingUpdated?(false) - } - if component.isEnabled { - sliderView.alpha = 1.3 - sliderView.trackColor = component.trackColor ?? UIColor(rgb: 0xffffff) - sliderView.isUserInteractionEnabled = true + var hasValue = false + let valueText: String + if component.displayValue { + if component.value > 0.005 { + valueText = String(format: "+%.2f", component.value) + hasValue = true + } else if component.value < -0.005 { + valueText = String(format: "%.2f", component.value) + hasValue = true + } else { + valueText = "" + } } else { - sliderView.trackColor = UIColor(rgb: 0xffffff) - sliderView.alpha = 0.3 - sliderView.isUserInteractionEnabled = false + valueText = "" } - transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 22.0, y: 7.0), size: CGSize(width: availableSize.width - 22.0 * 2.0, height: 44.0))) - sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) - let titleSize = self.title.update( transition: .immediate, component: AnyComponent( - Text(text: component.title, font: Font.regular(14.0), color: UIColor(rgb: 0x808080)) + Text(text: component.title, font: Font.bold(13.0), color: hasValue ? UIColor(rgb: 0xffffff) : UIColor(rgb: 0x808080)) ), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -173,26 +140,13 @@ final class AdjustmentSliderComponent: Component { if titleView.superview == nil { self.addSubview(titleView) } - transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: 21.0, y: 0.0), size: titleSize)) - } - - let valueText: String - if component.displayValue { - if component.value > 0.005 { - valueText = String(format: "+%.2f", component.value) - } else if component.value < -0.005 { - valueText = String(format: "%.2f", component.value) - } else { - valueText = "" - } - } else { - valueText = "" + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: 30.0, y: 0.0), size: titleSize)) } let valueSize = self.value.update( transition: .immediate, component: AnyComponent( - Text(text: valueText, font: Font.with(size: 14.0, traits: .monospacedNumbers), color: UIColor(rgb: 0xffd300)) + Text(text: valueText, font: Font.with(size: 13.0, weight: .medium, traits: .monospacedNumbers), color: UIColor(rgb: 0xffd300)) ), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -201,17 +155,44 @@ final class AdjustmentSliderComponent: Component { if valueView.superview == nil { self.addSubview(valueView) } - transition.setFrame(view: valueView, frame: CGRect(origin: CGPoint(x: availableSize.width - 21.0 - valueSize.width, y: 0.0), size: valueSize)) + transition.setFrame(view: valueView, frame: CGRect(origin: CGPoint(x: availableSize.width - 30.0 - valueSize.width, y: 0.0), size: valueSize)) } - return CGSize(width: availableSize.width, height: 52.0) - } - - @objc private func sliderValueChanged() { - guard let component = self.component, let sliderView = self.sliderView else { - return + let sliderSize = self.slider.update( + transition: transition, + component: AnyComponent( + SliderComponent( + content: .continuous(SliderComponent.Continuous( + value: CGFloat(component.value), + range: CGFloat(component.minValue) ... CGFloat(component.maxValue), + startValue: CGFloat(component.startValue), + valueUpdated: { [weak self] value in + guard let self, let component = self.component else { + return + } + component.valueUpdated(Float(value)) + } + )), + useNative: true, + trackBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), + trackForegroundColor: component.isEnabled ? (component.trackColor ?? UIColor(rgb: 0xffffff)) : UIColor(rgb: 0xffffff), + isEnabled: component.isEnabled, + trackHeight: 6.0, + displaysBorderOnTracking: component.isTrackingUpdated != nil, + isTrackingUpdated: internalIsTrackingUpdated + ) + ), + environment: {}, + containerSize: CGSize(width: max(0.0, availableSize.width - 28.0 * 2.0), height: 44.0) + ) + if let sliderView = self.slider.view { + if sliderView.superview == nil { + self.addSubview(sliderView) + } + transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 28.0, y: 7.0), size: sliderSize)) } - component.valueUpdated(Float(sliderView.value)) + + return CGSize(width: availableSize.width, height: 52.0) } } @@ -325,14 +306,14 @@ final class AdjustmentsComponent: Component { let size = componentView.update( transition: transition, component: AnyComponent( - AdjustmentSliderComponent( + AdjustmentSliderRowComponent( title: tool.title, value: value, minValue: tool.minValue, maxValue: tool.maxValue, startValue: tool.startValue, isEnabled: true, - trackColor: nil, + trackColor: UIColor(rgb: 0xffd300), displayValue: true, valueUpdated: { value in var updatedValue = value @@ -363,7 +344,7 @@ final class AdjustmentsComponent: Component { } transition.setFrame(view: view, frame: CGRect(origin: origin, size: size)) } - origin = origin.offsetBy(dx: 0.0, dy: size.height) + origin = origin.offsetBy(dx: 0.0, dy: size.height + 8.0) } let size = CGSize(width: availableSize.width, height: 180.0) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift index 3cf59203b7..47019ceb17 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift @@ -2,9 +2,9 @@ import Foundation import UIKit import Display import ComponentFlow -import LegacyComponents import MediaEditor import TelegramPresentationData +import SliderComponent private final class BlurModeComponent: Component { typealias EnvironmentType = Empty @@ -73,7 +73,7 @@ private final class BlurModeComponent: Component { component: AnyComponent( Text( text: component.title, - font: Font.regular(14.0), + font: Font.bold(13.0), color: component.isSelected ? UIColor(rgb: 0xffd300) : UIColor(rgb: 0x808080) ) ), @@ -198,7 +198,7 @@ final class BlurComponent: Component { component: AnyComponent( Text( text: component.strings.Story_Editor_Blur_Title, - font: Font.regular(14.0), + font: Font.bold(13.0), color: UIColor(rgb: 0x808080) ) ), @@ -331,27 +331,30 @@ final class BlurComponent: Component { let sliderSize = self.slider.update( transition: transition, component: AnyComponent( - AdjustmentSliderComponent( - title: "", - value: state.value.intensity, - minValue: 0.0, - maxValue: 1.0, - startValue: 0.0, - isEnabled: state.value.mode != .off, - trackColor: nil, - displayValue: false, - valueUpdated: { [weak state] value in - if let state { - valueUpdated(state.value.withUpdatedIntensity(value)) + SliderComponent( + content: .continuous(SliderComponent.Continuous( + value: CGFloat(state.value.intensity), + range: 0.0 ... 1.0, + startValue: 0.0, + valueUpdated: { [weak state] value in + if let state { + valueUpdated(state.value.withUpdatedIntensity(Float(value))) + } } - }, + )), + useNative: true, + trackBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), + trackForegroundColor: state.value.mode != .off ? UIColor(rgb: 0xffd300) : UIColor(rgb: 0xffffff), + isEnabled: state.value.mode != .off, + trackHeight: 6.0, + displaysBorderOnTracking: true, isTrackingUpdated: { isTracking in isTrackingUpdated(isTracking) } ) ), environment: {}, - containerSize: availableSize + containerSize: CGSize(width: max(0.0, availableSize.width - 28.0 * 2.0), height: 44.0) ) var buttons = [self.offButton, self.radialButton, self.linearButton] @@ -375,7 +378,8 @@ final class BlurComponent: Component { } let verticalSpacing: CGFloat = -5.0 - let sliderFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + offButtonSize.height + verticalSpacing), size: sliderSize) + let sliderRowHeight: CGFloat = 52.0 + let sliderFrame = CGRect(origin: CGPoint(x: 28.0, y: topInset + offButtonSize.height + verticalSpacing + 7.0), size: sliderSize) if let view = self.slider.view { if view.superview == nil { self.addSubview(view) @@ -383,7 +387,7 @@ final class BlurComponent: Component { transition.setFrame(view: view, frame: sliderFrame) } - return CGSize(width: availableSize.width, height: topInset + offButtonSize.height + verticalSpacing + sliderSize.height + 6.0) + return CGSize(width: availableSize.width, height: topInset + offButtonSize.height + verticalSpacing + sliderRowHeight + 6.0) } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkOptions.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkOptions.swift index 6e33cd4f93..e19044ac62 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkOptions.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkOptions.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import ContextUI import ChatPresentationInterfaceState @@ -36,7 +35,7 @@ func presentLinkOptionsController(context: AccountContext, selfController: Creat } private func linkOptions(context: AccountContext, selfController: CreateLinkScreen, snapshotImage: UIImage?, isDark: Bool, sourceNode: ASDisplayNode, url: String, text: String, positionBelowText: Bool, largeMedia: Bool?, webPage: TelegramMediaWebpage, completion: @escaping (Bool, Bool?) -> Void, remove: @escaping () -> Void) -> ContextController.Source? { - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) let initialUrlPreview = ChatPresentationInterfaceState.UrlPreview(url: url, webPage: webPage, positionBelowText: positionBelowText, largeMedia: largeMedia) @@ -90,7 +89,7 @@ private func linkOptions(context: AccountContext, selfController: CreateLinkScre if let image = snapshotImage { let wallpaperResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) if let wallpaperData = image.jpegData(compressionQuality: 0.87) { - context.account.postbox.mediaBox.storeResourceData(wallpaperResource.id, data: wallpaperData, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(wallpaperResource.id), data: wallpaperData, synchronous: true) } let wallpaperRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: wallpaperResource, progressiveSizes: [], immediateThumbnailData: nil) wallpaper = .image([wallpaperRepresentation], WallpaperSettings()) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift index 32b6146976..84788e3fc0 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CreateLinkScreen.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import Markdown import TextFormat @@ -486,18 +485,18 @@ private final class CreateLinkSheetComponent: CombinedComponent { } } - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] attributes.append(TextEntitiesMessageAttribute(entities: [.init(range: 0 ..< (text as NSString).length, type: .Url)])) if !self.dismissed { attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !self.positionBelowText, forceLargeMedia: self.largeMedia ?? webpageHasLargeMedia, isManuallyAdded: false, isSafe: true)) } - - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: text, attributes: attributes, media: effectiveMedia.flatMap { [$0] } ?? [], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: text, attributes: attributes, media: effectiveMedia.flatMap { [$0] } ?? [], peers: EngineSimpleDictionary(), associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let completion = controller.completion - let renderer = DrawingMessageRenderer(context: self.context, messages: [message], parentView: controller.view, isLink: true) + let renderer = DrawingMessageRenderer(context: self.context, messages: [EngineMessage(message)], parentView: controller.view, isLink: true) renderer.render(completion: { result in completion( CreateLinkScreen.Result( diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift index 924459dc45..7311d63ff8 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/CurvesComponent.swift @@ -153,7 +153,7 @@ final class CurvesComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Curves_All, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .all ? .white : UIColor(rgb: 0x808080) ) ), @@ -181,7 +181,7 @@ final class CurvesComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Curves_Red, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .red ? .white : UIColor(rgb: 0x808080) ) ), @@ -209,7 +209,7 @@ final class CurvesComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Curves_Green, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .green ? .white : UIColor(rgb: 0x808080) ) ), @@ -237,7 +237,7 @@ final class CurvesComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Curves_Blue, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .blue ? .white : UIColor(rgb: 0x808080) ) ), @@ -536,7 +536,7 @@ final class CurvesScreenComponent: Component { text: .plain( NSAttributedString( string: String(format: "%.2f", value.blacks), - font: Font.regular(14.0), + font: Font.medium(13.0), textColor: UIColor(rgb: 0xffffff) ) ), @@ -563,7 +563,7 @@ final class CurvesScreenComponent: Component { text: .plain( NSAttributedString( string: String(format: "%.2f", value.shadows), - font: Font.regular(14.0), + font: Font.medium(13.0), textColor: UIColor(rgb: 0xffffff) ) ), @@ -590,7 +590,7 @@ final class CurvesScreenComponent: Component { text: .plain( NSAttributedString( string: String(format: "%.2f", value.midtones), - font: Font.regular(14.0), + font: Font.medium(13.0), textColor: UIColor(rgb: 0xffffff) ) ), @@ -617,7 +617,7 @@ final class CurvesScreenComponent: Component { text: .plain( NSAttributedString( string: String(format: "%.2f", value.highlights), - font: Font.regular(14.0), + font: Font.medium(13.0), textColor: UIColor(rgb: 0xffffff) ) ), @@ -644,7 +644,7 @@ final class CurvesScreenComponent: Component { text: .plain( NSAttributedString( string: String(format: "%.2f", value.whites), - font: Font.regular(14.0), + font: Font.medium(13.0), textColor: UIColor(rgb: 0xffffff) ) ), diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift index e8cc73e516..471c075009 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TextFormat @@ -24,7 +23,7 @@ public extension MediaEditorScreenImpl { willDismiss: @escaping () -> Void = {}, update: @escaping (Disposable?) -> Void ) -> MediaEditorScreenImpl? { - guard let peerReference = PeerReference(peer._asPeer()) else { + guard let peerReference = PeerReference(peer) else { return nil } let subject: Signal @@ -34,9 +33,9 @@ public extension MediaEditorScreenImpl { return .single(.draft(source, Int64(storyItem.id))) } else { let media = storyItem.media._asMedia() - return fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: storyItem.id, media: media)) + return fetchMediaData(context: context, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: storyItem.id, media: media)) |> mapToSignal { (value, isImage) -> Signal in - guard case let .data(data) = value, data.complete else { + guard case let .data(data) = value, data.isComplete else { return .complete() } if let image = UIImage(contentsOfFile: data.path) { @@ -51,7 +50,7 @@ public extension MediaEditorScreenImpl { duration = file.duration } let symlinkPath = data.path + ".mp4" - if fileSize(symlinkPath) == nil { + if engineFileSize(symlinkPath) == nil { let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) } return .single(nil) @@ -183,9 +182,9 @@ public extension MediaEditorScreenImpl { case let .image(image, dimensions): updateProgressImpl?(0.0) - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) { update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .image(dimensions: dimensions, data: imageData, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) @@ -208,8 +207,8 @@ public extension MediaEditorScreenImpl { updateProgressImpl?(0.0) if let valuesData = try? JSONEncoder().encode(values) { - let data = MemoryBuffer(data: valuesData) - let digest = MemoryBuffer(data: data.md5Digest()) + let data = EngineMemoryBuffer(data: valuesData) + let digest = EngineMemoryBuffer(data: data.md5Digest()) let adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true) let resource: TelegramMediaResource @@ -222,13 +221,13 @@ public extension MediaEditorScreenImpl { resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments)) } - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } let firstFrameImageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) } - let firstFrameFile = firstFrameImageData.flatMap { data -> TempBoxFile? in - let file = TempBox.shared.tempFile(fileName: "image.jpg") + let firstFrameFile = firstFrameImageData.flatMap { data -> EngineTempBoxFile? in + let file = EngineTempBox.shared.tempFile(fileName: "image.jpg") if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) { return file } else { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift index 1b958dfa1b..f58ce5a41f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift @@ -230,6 +230,7 @@ private final class MediaCoverScreenComponent: Component { component: AnyComponent( ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) @@ -261,7 +262,7 @@ private final class MediaCoverScreenComponent: Component { ) ), environment: {}, - containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) ) let doneButtonFrame = CGRect( origin: CGPoint(x: floor((availableSize.width - doneButtonSize.width) / 2.0), y: min(buttonsContainerFrame.minY, availableSize.height - doneButtonSize.height - buttonSideInset)), diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index f9f3e01ef5..9e8d198831 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -52,6 +52,9 @@ import SaveProgressScreen import TelegramNotices import AttachmentFileController import SaveToCameraRoll +import GlassBarButtonComponent +import GlassBackgroundComponent +import Weather private let playbackButtonTag = GenericComponentViewTag() private let muteButtonTag = GenericComponentViewTag() @@ -198,7 +201,7 @@ final class MediaEditorScreenComponent: Component { case .sticker: image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/AddSticker"), color: .white)! case .tools: - image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Tools"), color: .white)! + image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Adjustments"), color: .white)! case .rotate: image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Rotate"), color: .white)! case .flip: @@ -300,6 +303,8 @@ final class MediaEditorScreenComponent: Component { public final class View: UIView { private let cancelButton = ComponentView() private let doneButton = ComponentView() + + private let buttonsBackgroundView = GlassBackgroundView() private let drawButton = ComponentView() private let textButton = ComponentView() private let stickerButton = ComponentView() @@ -675,9 +680,6 @@ final class MediaEditorScreenComponent: Component { } func animateOutToTool(inPlace: Bool, transition: ComponentTransition) { - if let view = self.cancelButton.view { - view.alpha = 0.0 - } let buttons = [ self.drawButton, self.textButton, @@ -686,12 +688,12 @@ final class MediaEditorScreenComponent: Component { ] for button in buttons { if let view = button.view { - if !inPlace { - view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -44.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) } } + if let view = self.cancelButton.view { + transition.setScale(view: view, scale: 0.1) + } if let view = self.doneButton.view { transition.setScale(view: view, scale: 0.1) } @@ -704,12 +706,6 @@ final class MediaEditorScreenComponent: Component { } func animateInFromTool(inPlace: Bool, transition: ComponentTransition) { - if let view = self.cancelButton.view { - view.alpha = 1.0 - } - if let buttonView = self.cancelButton.view as? Button.View, let view = buttonView.content as? LottieAnimationComponent.View { - view.playOnce() - } let buttons = [ self.drawButton, self.textButton, @@ -718,12 +714,12 @@ final class MediaEditorScreenComponent: Component { ] for button in buttons { if let view = button.view { - if !inPlace { - view.layer.animatePosition(from: CGPoint(x: 0.0, y: -44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) } } + if let view = self.cancelButton.view { + transition.setScale(view: view, scale: 1.0) + } if let view = self.doneButton.view { transition.setScale(view: view, scale: 1.0) } @@ -821,10 +817,17 @@ final class MediaEditorScreenComponent: Component { buttonSideInset = 30.0 } else { previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778)) - buttonSideInset = 10.0 + if availableSize.height < previewSize.height + 30.0 { topInset = 0.0 - controlsBottomInset = -50.0 + controlsBottomInset = -62.0 + buttonSideInset = 9.0 + } else { + if availableSize.width > 320.0 { + buttonSideInset = 16.0 + } else { + buttonSideInset = 9.0 + } } } var previewFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: topInset), size: previewSize) @@ -835,30 +838,36 @@ final class MediaEditorScreenComponent: Component { let bottomButtonsAlpha: CGFloat = isRecordingAdditionalVideo ? 0.3 : 1.0 let buttonsAreHidden = component.isDisplayingTool != nil || component.isDismissing || component.isInteractingWithEntities + if self.buttonsBackgroundView.superview == nil { + self.addSubview(self.buttonsBackgroundView) + } + let cancelButtonSize = self.cancelButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent( - LottieAnimationComponent( - animation: LottieAnimationComponent.AnimationItem( - name: "media_backToCancel", - mode: .still(position: .end), - range: (0.5, 1.0) - ), - colors: ["__allcolors__": .white], - size: CGSize(width: 33.0, height: 33.0) - ) - ), - action: { [weak controller] in - guard let controller else { - return + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + isVisible: !buttonsAreHidden && bottomButtonsAlpha > 0.0, + component: AnyComponentWithIdentity( + id: "close", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Back", tintColor: .white) + ) + ), + action: { [weak controller] _ in + guard let controller else { + return + } + guard !controller.node.recording.isActive else { + return + } + controller.maybePresentDiscardAlert() } - guard !controller.node.recording.isActive else { - return - } - controller.maybePresentDiscardAlert() - } - )), + ) + ), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) @@ -872,35 +881,83 @@ final class MediaEditorScreenComponent: Component { } transition.setPosition(view: cancelButtonView, position: cancelButtonFrame.center) transition.setBounds(view: cancelButtonView, bounds: CGRect(origin: .zero, size: cancelButtonFrame.size)) - transition.setAlpha(view: cancelButtonView, alpha: buttonsAreHidden ? 0.0 : bottomButtonsAlpha) } - var doneButtonTitle: String? - var doneButtonIcon: UIImage? + let doneButtonFont = Font.with(size: 16.0, design: .round, weight: .semibold) + var doneButtonItems: [AnyComponentWithIdentity] = [] + var doneButtonExplicitSize: CGSize? switch controller.mode { case .storyEditor: - doneButtonTitle = isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased() - doneButtonIcon = UIImage(bundleImageName: "Media Editor/Next")! + if availableSize.width > 320.0 { + doneButtonItems = [ + AnyComponentWithIdentity( + id: "label", + component: AnyComponent( + Text(text: isEditingStory ? environment.strings.Story_Editor_Done.uppercased() : environment.strings.Story_Editor_Next.uppercased(), font: doneButtonFont, color: .white) + ) + ), + AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + BundleIconComponent(name: "Media Editor/Next", tintColor: .white) + ) + ) + ] + } else { + doneButtonExplicitSize = CGSize(width: 44.0, height: 44.0) + doneButtonItems = [ + AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Back", tintColor: .white, flipHorizontally: true) + ) + ) + ] + } case .stickerEditor, .avatarEditor, .coverEditor: - doneButtonTitle = nil - doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! + doneButtonExplicitSize = CGSize(width: 44.0, height: 44.0) + doneButtonItems = [ + AnyComponentWithIdentity( + id: "icon", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Done", tintColor: .white) + ) + ) + ] case .botPreview: - doneButtonTitle = environment.strings.Story_Editor_Add.uppercased() - doneButtonIcon = nil + doneButtonItems = [ + AnyComponentWithIdentity( + id: "label", + component: AnyComponent( + Text(text: environment.strings.Story_Editor_Add.uppercased(), font: doneButtonFont, color: .white) + ) + ) + ] } let doneButtonSize = self.doneButton.update( transition: transition, - component: AnyComponent(PlainButtonComponent( - content: AnyComponent(DoneButtonContentComponent( + component: AnyComponent( + GlassBarButtonComponent( + size: doneButtonExplicitSize, backgroundColor: UIColor(rgb: 0x0088ff), - icon: doneButtonIcon, - title: doneButtonTitle)), - effectAlignment: .center, - action: { [weak controller] in - controller?.node.requestCompletion() - } - )), + isDark: true, + state: .tintedGlass, + isVisible: !buttonsAreHidden && bottomButtonsAlpha > 0.0, + component: AnyComponentWithIdentity( + id: "done", + component: AnyComponent( + HStack( + doneButtonItems, + spacing: 5.0 + ) + ) + ), + action: { [weak controller] _ in + controller?.node.requestCompletion() + } + ) + ), environment: {}, containerSize: CGSize(width: availableSize.width, height: 44.0) ) @@ -914,19 +971,21 @@ final class MediaEditorScreenComponent: Component { } transition.setPosition(view: doneButtonView, position: doneButtonFrame.center) transition.setBounds(view: doneButtonView, bounds: CGRect(origin: .zero, size: doneButtonFrame.size)) - transition.setAlpha(view: doneButtonView, alpha: buttonsAreHidden ? 0.0 : bottomButtonsAlpha) } - - let buttonsAvailableWidth: CGFloat - let buttonsLeftOffset: CGFloat - if isTablet { - buttonsAvailableWidth = previewSize.width + 180.0 - buttonsLeftOffset = floorToScreenPixels((availableSize.width - buttonsAvailableWidth) / 2.0) - } else { - buttonsAvailableWidth = floor(availableSize.width - cancelButtonSize.width * 0.66 - (doneButtonSize.width - cancelButtonSize.width * 0.33) - buttonSideInset * 2.0) - buttonsLeftOffset = floorToScreenPixels(buttonSideInset + cancelButtonSize.width * 0.66) + + let buttonSize = CGSize(width: 44.0, height: 44.0) + let buttonSpacing: CGFloat = 10.0 + var buttonCount = 4 + if let subject = controller.node.subject, case .empty = subject { + buttonCount = 3 } + let buttonsTotalWidth: CGFloat = buttonSize.width * CGFloat(buttonCount) + buttonSpacing * CGFloat(buttonCount - 1) + let buttonsBackgroundFrame = CGRect(x: cancelButtonFrame.maxX + floorToScreenPixels(((doneButtonFrame.minX - cancelButtonFrame.maxX) - buttonsTotalWidth) / 2.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset, width: buttonsTotalWidth, height: buttonSize.height) + transition.setFrame(view: self.buttonsBackgroundView, frame: buttonsBackgroundFrame) + self.buttonsBackgroundView.update(size: buttonsBackgroundFrame.size, cornerRadius: buttonsBackgroundFrame.size.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !buttonsAreHidden && bottomButtonsAlpha > 0.0, transition: transition) + + var buttonOriginX: CGFloat = 0.0 let drawButtonSize = self.drawButton.update( transition: transition, component: AnyComponent(ContextReferenceButtonComponent( @@ -935,7 +994,7 @@ final class MediaEditorScreenComponent: Component { size: CGSize(width: 30.0, height: 30.0) )), tag: drawButtonTag, - minSize: CGSize(width: 30.0, height: 30.0), + minSize: buttonSize, action: { [weak controller] _, _ in guard let controller else { return @@ -947,13 +1006,14 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) var drawButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 - drawButtonSize.width / 2.0 - 3.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: drawButtonSize - ) - + ) + buttonOriginX += drawButtonSize.width + buttonSpacing + let textButtonSize = self.textButton.update( transition: transition, component: AnyComponent(ContextReferenceButtonComponent( @@ -962,7 +1022,7 @@ final class MediaEditorScreenComponent: Component { size: CGSize(width: 30.0, height: 30.0) )), tag: textButtonTag, - minSize: CGSize(width: 30.0, height: 30.0), + minSize: buttonSize, action: { [weak controller] _, _ in guard let controller else { return @@ -974,12 +1034,13 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) var textButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 2.0 - textButtonSize.width / 2.0 - 1.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: textButtonSize ) + buttonOriginX += textButtonSize.width + buttonSpacing let stickerButtonSize = self.stickerButton.update( transition: transition, @@ -989,7 +1050,7 @@ final class MediaEditorScreenComponent: Component { size: CGSize(width: 30.0, height: 30.0) )), tag: stickerButtonTag, - minSize: CGSize(width: 30.0, height: 30.0), + minSize: buttonSize, action: { [weak controller] view, gesture in guard let controller else { return @@ -1005,12 +1066,13 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) var stickerButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 3.0 - stickerButtonSize.width / 2.0 + 1.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: stickerButtonSize ) + buttonOriginX += drawButtonSize.width + buttonSpacing let rotateButtonSize = self.rotateButton.update( transition: transition, @@ -1020,7 +1082,7 @@ final class MediaEditorScreenComponent: Component { size: CGSize(width: 30.0, height: 30.0) )), tag: textButtonTag, - minSize: CGSize(width: 30.0, height: 30.0), + minSize: buttonSize, action: { [weak controller, weak mediaEditor] _, _ in guard let controller, let mediaEditor else { return @@ -1037,10 +1099,10 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let rotateButtonFrame = CGRect( - origin: CGPoint(x: drawButtonFrame.origin.x, y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), + origin: CGPoint(x: drawButtonFrame.origin.x, y: 0.0), size: rotateButtonSize ) @@ -1052,7 +1114,7 @@ final class MediaEditorScreenComponent: Component { size: CGSize(width: 30.0, height: 30.0) )), tag: textButtonTag, - minSize: CGSize(width: 30.0, height: 30.0), + minSize: buttonSize, action: { [weak controller, weak mediaEditor] _, _ in guard let controller, let mediaEditor else { return @@ -1069,10 +1131,10 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let flipButtonFrame = CGRect( - origin: CGPoint(x: textButtonFrame.origin.x, y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 2.0), + origin: CGPoint(x: textButtonFrame.origin.x, y: 0.0), size: flipButtonSize ) @@ -1089,7 +1151,7 @@ final class MediaEditorScreenComponent: Component { if let rotateButtonView = self.rotateButton.view { if rotateButtonView.superview == nil { - self.addSubview(rotateButtonView) + self.buttonsBackgroundView.contentView.addSubview(rotateButtonView) } transition.setPosition(view: rotateButtonView, position: rotateButtonFrame.center) transition.setBounds(view: rotateButtonView, bounds: CGRect(origin: .zero, size: rotateButtonFrame.size)) @@ -1100,7 +1162,7 @@ final class MediaEditorScreenComponent: Component { if let flipButtonView = self.flipButton.view { if flipButtonView.superview == nil { - self.addSubview(flipButtonView) + self.buttonsBackgroundView.contentView.addSubview(flipButtonView) } transition.setPosition(view: flipButtonView, position: flipButtonFrame.center) transition.setBounds(view: flipButtonView, bounds: CGRect(origin: .zero, size: flipButtonFrame.size)) @@ -1117,7 +1179,7 @@ final class MediaEditorScreenComponent: Component { if let drawButtonView = self.drawButton.view { if drawButtonView.superview == nil { - self.addSubview(drawButtonView) + self.buttonsBackgroundView.contentView.addSubview(drawButtonView) } transition.setPosition(view: drawButtonView, position: drawButtonFrame.center) transition.setBounds(view: drawButtonView, bounds: CGRect(origin: .zero, size: drawButtonFrame.size)) @@ -1128,7 +1190,7 @@ final class MediaEditorScreenComponent: Component { if !isAvatarEditor && !isCoverEditor, let textButtonView = self.textButton.view { if textButtonView.superview == nil { - self.addSubview(textButtonView) + self.buttonsBackgroundView.contentView.addSubview(textButtonView) } transition.setPosition(view: textButtonView, position: textButtonFrame.center) transition.setBounds(view: textButtonView, bounds: CGRect(origin: .zero, size: textButtonFrame.size)) @@ -1139,7 +1201,7 @@ final class MediaEditorScreenComponent: Component { if !isAvatarEditor && !isCoverEditor, let stickerButtonView = self.stickerButton.view { if stickerButtonView.superview == nil { - self.addSubview(stickerButtonView) + self.buttonsBackgroundView.contentView.addSubview(stickerButtonView) } transition.setPosition(view: stickerButtonView, position: stickerButtonFrame.center) transition.setBounds(view: stickerButtonView, bounds: CGRect(origin: .zero, size: stickerButtonFrame.size)) @@ -1155,12 +1217,14 @@ final class MediaEditorScreenComponent: Component { } else { let toolsButtonSize = self.toolsButton.update( transition: transition, - component: AnyComponent(Button( + component: AnyComponent(ContextReferenceButtonComponent( content: AnyComponent(Image( image: state.image(.tools), size: CGSize(width: 30.0, height: 30.0) )), - action: { [weak controller] in + tag: textButtonTag, + minSize: buttonSize, + action: { [weak controller] _, _ in guard let controller else { return } @@ -1171,15 +1235,15 @@ final class MediaEditorScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let toolsButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 5.0 * 4.0 - toolsButtonSize.width / 2.0 + 3.0), y: availableSize.height - environment.safeInsets.bottom + buttonBottomInset + controlsBottomInset + 1.0), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: toolsButtonSize ) if let toolsButtonView = self.toolsButton.view { if toolsButtonView.superview == nil { - self.addSubview(toolsButtonView) + self.buttonsBackgroundView.contentView.addSubview(toolsButtonView) } transition.setPosition(view: toolsButtonView, position: toolsButtonFrame.center) transition.setBounds(view: toolsButtonView, bounds: CGRect(origin: .zero, size: toolsButtonFrame.size)) @@ -1257,7 +1321,6 @@ final class MediaEditorScreenComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -1644,31 +1707,37 @@ final class MediaEditorScreenComponent: Component { let saveButtonSize = self.saveButton.update( transition: transition, - component: AnyComponent(CameraButton( - content: saveContentComponent, - action: { [weak self, weak controller] in - guard let self, let controller else { - return + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + isVisible: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities && topButtonsAlpha > 0.0, + component: saveContentComponent, + action: { [weak self, weak controller] _ in + guard let self, let controller else { + return + } + guard !controller.node.recording.isActive else { + return + } + if let view = self.saveButton.findTaggedView(tag: saveButtonTag) as? LottieAnimationComponent.View { + view.playOnce() + } + controller.requestSave() } - guard !controller.node.recording.isActive else { - return - } - if let view = self.saveButton.findTaggedView(tag: saveButtonTag) as? LottieAnimationComponent.View { - view.playOnce() - } - controller.requestSave() - } - )), + ) + ), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) let saveButtonFrame = CGRect( - origin: CGPoint(x: availableSize.width - 20.0 - saveButtonSize.width, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), + origin: CGPoint(x: availableSize.width - 16.0 - saveButtonSize.width, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), size: saveButtonSize ) if let saveButtonView = self.saveButton.view { if saveButtonView.superview == nil { - setupButtonShadow(saveButtonView) self.addSubview(saveButtonView) } @@ -1678,7 +1747,7 @@ final class MediaEditorScreenComponent: Component { buttonTransition.setPosition(view: saveButtonView, position: saveButtonFrame.center) buttonTransition.setBounds(view: saveButtonView, bounds: CGRect(origin: .zero, size: saveButtonFrame.size)) transition.setScale(view: saveButtonView, scale: displayTopButtons ? 1.0 : 0.01) - transition.setAlpha(view: saveButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? saveButtonAlpha : 0.0) + transition.setAlpha(view: saveButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities && saveButtonAlpha > 0.0 ? saveButtonAlpha : 1.0) } var topButtonOffsetX: CGFloat = 0.0 @@ -3750,7 +3819,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return } var messageFile: TelegramMediaFile? - if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.account.postbox.mediaBox.completedResourcePath(maybeFile.resource, pathExtension: nil) { + if let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, maybeFile.isVideo, let _ = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(maybeFile.resource.id), pathExtension: nil) { messageFile = maybeFile } if "".isEmpty { @@ -3786,7 +3855,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID guard let self else { return } - let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift, wallpaperDayColor: wallpaperColors.0, wallpaperNightColor: wallpaperColors.1) + let renderer = DrawingMessageRenderer(context: self.context, messages: messages.map(EngineMessage.init), parentView: self.view, isGift: isGift, wallpaperDayColor: wallpaperColors.0, wallpaperNightColor: wallpaperColors.1) renderer.render(completion: { result in if isDraft, let existingEntityView = self.entitiesView.getView(where: { entityView in if let stickerEntityView = entityView as? DrawingStickerEntityView { @@ -5135,7 +5204,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } let _ = (fetchMediaData( context: self.context, - postbox: self.context.account.postbox, userLocation: .other, mediaReference: file ) |> deliverOnMainQueue).start(next: { [weak self] state, _ in @@ -7733,10 +7801,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID let imagesReady = ValuePromise(false, ignoreRepeated: true) Queue.concurrentDefaultQueue().async { if !isVideo, let data = try? WebP.convert(toWebP: image, quality: 90.0) { - self.context.account.postbox.mediaBox.storeResourceData(isVideo ? thumbnailResource.id : resource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(isVideo ? thumbnailResource.id : resource.id), data: data, synchronous: true) } if let thumbnailImage = generateScaledImage(image: image, size: CGSize(width: 320.0, height: 320.0), opaque: false, scale: 1.0), let data = try? WebP.convert(toWebP: thumbnailImage, quality: 90.0) { - self.context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: data, synchronous: true) } imagesReady.set(true) } @@ -8095,15 +8163,16 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID return .single((.progress(progress * 0.5), nil)) case let .complete(resource): if let resource = resource as? CloudDocumentMediaResource { - return .single((.progress(1.0), nil)) |> then(.single((.complete(resource, mimeType), nil))) + return .single((.progress(1.0), nil)) |> then(.single((.complete(EngineMediaResource(resource), mimeType), nil))) } else { - return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, thumbnail: file.previewRepresentations.first?.resource, alt: "", dimensions: dimensions, duration: duration, mimeType: mimeType) + return context.engine.stickers.uploadSticker(peer: peer, resource: EngineMediaResource(resource), thumbnail: file.previewRepresentations.first.flatMap { EngineMediaResource($0.resource) }, alt: "", dimensions: dimensions, duration: duration, mimeType: mimeType) |> mapToSignal { status -> Signal<(UploadStickerStatus, (StickerPackReference, String)?), UploadStickerError> in switch status { case let .progress(progress): return .single((.progress(isVideo ? 0.5 + progress * 0.5 : progress), nil)) case let .complete(resource, _): - let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: file.size ?? 0, dimensions: dimensions, duration: file.duration, isVideo: isVideo) + let rawResource = resource._asResource() as! TelegramMediaResource + let file = stickerFile(resource: rawResource, thumbnailResource: file.previewRepresentations.first?.resource, size: file.size ?? 0, dimensions: dimensions, duration: file.duration, isVideo: isVideo) switch action { case .send: return .single((status, nil)) @@ -8117,7 +8186,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } case let .createStickerPack(title): let sticker = ImportSticker( - resource: .standalone(resource: resource), + resource: .standalone(resource: rawResource), emojis: emojis, dimensions: dimensions, duration: duration, @@ -8137,7 +8206,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID } case let .addToStickerPack(pack, title): let sticker = ImportSticker( - resource: .standalone(resource: resource), + resource: .standalone(resource: rawResource), emojis: emojis, dimensions: dimensions, duration: duration, @@ -8175,14 +8244,15 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self?.stickerUploadDisposable.set(nil) }) case let .complete(resource, _): + let rawResource = resource._asResource() as! TelegramMediaResource let navigationController = self.effectiveNavigationController as? NavigationController - + let result: MediaEditorScreenImpl.Result switch action { case .update: result = MediaEditorScreenImpl.Result(media: .sticker(file: file, emoji: emojis)) case .upload, .send: - let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: resource.size ?? 0, dimensions: dimensions, duration: self.preferredStickerDuration(), isVideo: isVideo) + let file = stickerFile(resource: rawResource, thumbnailResource: file.previewRepresentations.first?.resource, size: rawResource.size ?? 0, dimensions: dimensions, duration: self.preferredStickerDuration(), isVideo: isVideo) result = MediaEditorScreenImpl.Result(media: .sticker(file: file, emoji: emojis)) default: result = MediaEditorScreenImpl.Result() @@ -8443,7 +8513,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.videoExport = nil if let toStickerResource { if let data = try? Data(contentsOf: URL(fileURLWithPath: outputPath)) { - self.context.account.postbox.mediaBox.storeResourceData(toStickerResource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(toStickerResource.id), data: data, synchronous: true) } } else { saveToPhotos(outputPath, true) @@ -9044,13 +9114,9 @@ func hasFirstResponder(_ view: UIView) -> Bool { } private func allowedStoryReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { - let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) - let topReactions = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> [RecentReactionItem] in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return [] - } - return view.items.compactMap { item -> RecentReactionItem? in + let topReactions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudTopReactions)) + |> map { items -> [RecentReactionItem] in + return items.compactMap { item -> RecentReactionItem? in return item.contents.get(RecentReactionItem.self) } } @@ -9234,3 +9300,31 @@ private struct MediaEditorConfiguration { } } } + +private func getWeather(context: AccountContext, load: Bool) -> Signal { + return Weather.getWeatherData(context: context, load: load) + |> map { state -> StickerPickerScreen.Weather in + switch state { + case .none: + return .none + case .notDetermined: + return .notDetermined + case .notAllowed: + return .notAllowed + case .notPreloaded: + return .notPreloaded + case .fetching: + return .fetching + case let .loaded(weather): + if let match = context.animatedEmojiStickersValue[weather.emoji]?.first { + return .loaded(StickerPickerScreen.Weather.LoadedWeather( + emoji: weather.emoji, + emojiFile: match.file._parse(), + temperature: weather.temperature + )) + } else { + return .none + } + } + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreenDrafts.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreenDrafts.swift index 7ede825219..8ba1f35f43 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreenDrafts.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreenDrafts.swift @@ -3,7 +3,6 @@ import UIKit import Display import CoreLocation import Photos -import Postbox import TelegramCore import AccountContext import MediaEditor @@ -182,7 +181,7 @@ extension MediaEditorScreenImpl { values: values, caption: caption, privacy: privacy, - forwardInfo: forwardSource.flatMap { StoryId(peerId: $0.0.id, id: $0.1.id) }, + forwardInfo: forwardSource.flatMap { EngineStoryId(peerId: $0.0.id, id: $0.1.id) }, timestamp: timestamp, location: location, expiresOn: expiresOn diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoryCompletion.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoryCompletion.swift index 0f51bd6e6a..b3344e2200 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoryCompletion.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorStoryCompletion.swift @@ -9,6 +9,21 @@ import Photos import MediaEditor import DrawingUI +private func additionalVideoMirroring(at timestamp: Double, changes: [VideoMirroringChange]) -> Bool { + guard let firstChange = changes.first else { + return false + } + var isMirrored = firstChange.isMirrored + for change in changes { + if timestamp >= change.timestamp { + isMirrored = change.isMirrored + } else { + break + } + } + return isMirrored +} + extension MediaEditorScreenImpl { func requestStoryCompletion(animated: Bool) { guard let mediaEditor = self.node.mediaEditor, !self.didComplete else { @@ -161,6 +176,10 @@ extension MediaEditorScreenImpl { } else { firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60)) } + let additionalFirstFrameTime = CMTime( + seconds: max(0.0, firstFrameTime.seconds + (mediaEditor.values.additionalVideoOffset ?? 0.0)), + preferredTimescale: firstFrameTime.timescale + ) let videoResult: Signal var videoIsMirrored = false let duration: Double @@ -210,7 +229,7 @@ extension MediaEditorScreenImpl { let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath)) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) avAssetGenerator.appliesPreferredTrackTransform = true - avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in + avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in if let additionalCGImage { subscriber.putNext((UIImage(cgImage: cgImage), UIImage(cgImage: additionalCGImage))) subscriber.putCompletion() @@ -302,7 +321,7 @@ extension MediaEditorScreenImpl { let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath)) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) avAssetGenerator.appliesPreferredTrackTransform = true - avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in + avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in if let additionalCGImage { subscriber.putNext((UIImage(cgImage: cgImage), UIImage(cgImage: additionalCGImage))) subscriber.putCompletion() @@ -328,7 +347,7 @@ extension MediaEditorScreenImpl { let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath)) let avAssetGenerator = AVAssetImageGenerator(asset: avAsset) avAssetGenerator.appliesPreferredTrackTransform = true - avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in + avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in if let additionalCGImage { subscriber.putNext((image, UIImage(cgImage: additionalCGImage))) subscriber.putCompletion() @@ -437,7 +456,15 @@ extension MediaEditorScreenImpl { let (image, additionalImage) = images var currentImage = mediaEditor.resultImage if let image { - mediaEditor.replaceSource(image, additionalImage: additionalImage, time: firstFrameTime, mirror: true) + mediaEditor.replaceSource( + image, + additionalImage: additionalImage, + time: firstFrameTime, + mirror: additionalVideoMirroring( + at: additionalFirstFrameTime.seconds, + changes: mediaEditor.values.additionalVideoMirroringChanges + ) + ) if let updatedImage = mediaEditor.getResultImage(mirror: videoIsMirrored) { currentImage = updatedImage } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index ec53f4d4eb..acfb68e235 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -13,8 +13,10 @@ import MultilineTextComponent import DrawingUI import MediaEditor import Photos -import LottieAnimationComponent import MessageInputPanelComponent +import BundleIconComponent +import GlassBarButtonComponent +import GlassBackgroundComponent private enum MediaToolsSection: Equatable { case adjustments @@ -54,7 +56,6 @@ private final class ToolIconComponent: Component { } final class View: UIView { - private let selection = SimpleShapeLayer() private let icon = ComponentView() private var component: ToolIconComponent? @@ -62,10 +63,6 @@ private final class ToolIconComponent: Component { override init(frame: CGRect) { super.init(frame: frame) - - self.selection.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)), cornerRadius: 10.0).cgPath - self.selection.fillColor = UIColor(rgb: 0xd1d1d1).cgColor - self.layer.addSublayer(self.selection) } required init?(coder: NSCoder) { @@ -105,8 +102,6 @@ private final class ToolIconComponent: Component { transition.setFrame(view: view, frame: iconFrame) } - self.selection.isHidden = !component.isSelected - return size } } @@ -163,7 +158,6 @@ private final class MediaToolsScreenComponent: Component { case tint case blur case curves - case done } private var cachedImages: [ImageKey: UIImage] = [:] func image(_ key: ImageKey) -> UIImage { @@ -173,15 +167,13 @@ private final class MediaToolsScreenComponent: Component { var image: UIImage switch key { case .adjustments: - image = UIImage(bundleImageName: "Media Editor/Tools")! + image = UIImage(bundleImageName: "Media Editor/Adjustments")! case .tint: image = UIImage(bundleImageName: "Media Editor/Tint")! case .blur: image = UIImage(bundleImageName: "Media Editor/Blur")! case .curves: image = UIImage(bundleImageName: "Media Editor/Curves")! - case .done: - image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)! } cachedImages[key] = image return image @@ -220,7 +212,8 @@ private final class MediaToolsScreenComponent: Component { public final class View: UIView { private let buttonsContainerView = UIView() - private let buttonsBackgroundView = UIView() + private let buttonsBackgroundView = GlassBackgroundView() + private let selectedToolBackgroundLayer = SimpleShapeLayer() private let cancelButton = ComponentView() private let adjustmentsButton = ComponentView() private let tintButton = ComponentView() @@ -248,7 +241,7 @@ private final class MediaToolsScreenComponent: Component { self.optionsBackgroundView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.9) super.init(frame: frame) - + self.backgroundColor = .clear self.addSubview(self.previewContainerView) @@ -256,6 +249,10 @@ private final class MediaToolsScreenComponent: Component { self.previewContainerView.addSubview(self.optionsContainerView) self.optionsContainerView.addSubview(self.optionsBackgroundView) self.buttonsContainerView.addSubview(self.buttonsBackgroundView) + + self.selectedToolBackgroundLayer.path = UIBezierPath(roundedRect: CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)), cornerRadius: 16.5).cgPath + self.selectedToolBackgroundLayer.fillColor = UIColor(rgb: 0xd1d1d1).cgColor + self.buttonsBackgroundView.contentView.layer.insertSublayer(self.selectedToolBackgroundLayer, at: 0) } required init?(coder: NSCoder) { @@ -269,20 +266,17 @@ private final class MediaToolsScreenComponent: Component { self.blurButton, self.curvesButton ] - - var delay: Double = 0.0 for button in buttons { if let view = button.view { - view.alpha = 0.0 - Queue.mainQueue().after(delay, { - view.alpha = 1.0 - view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.0) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: 0.0) - }) - delay += 0.03 + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.0) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: 0.0) } } + + if let view = self.cancelButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } if let view = self.doneButton.view { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -297,9 +291,7 @@ private final class MediaToolsScreenComponent: Component { private var animatingOut = false func animateOutToEditor(completion: @escaping () -> Void) { self.animatingOut = true - - self.cancelButton.view?.isHidden = true - + let buttons = [ self.adjustmentsButton, self.tintButton, @@ -309,17 +301,21 @@ private final class MediaToolsScreenComponent: Component { for button in buttons { if let view = button.view { - view.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true, completion: { _ in + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in completion() }) - view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) } } + if let view = self.cancelButton.view { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) + } + if let view = self.doneButton.view { view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, removeOnCompletion: false) } if let view = self.toolScreen?.view { @@ -364,12 +360,17 @@ private final class MediaToolsScreenComponent: Component { buttonSideInset = 30.0 } else { previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778)) - buttonSideInset = 10.0 + if availableSize.height < previewSize.height + 30.0 { topInset = 0.0 controlsBottomInset = -75.0 + buttonSideInset = 9.0 } else { - self.buttonsBackgroundView.backgroundColor = .clear + if availableSize.width > 320.0 { + buttonSideInset = 16.0 + } else { + buttonSideInset = 9.0 + } } } @@ -378,25 +379,26 @@ private final class MediaToolsScreenComponent: Component { let cancelButtonSize = self.cancelButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent( - LottieAnimationComponent( - animation: LottieAnimationComponent.AnimationItem( - name: "media_backToCancel", - mode: .animating(loop: false), - range: self.animatingOut ? (0.5, 1.0) : (0.0, 0.5) - ), - colors: ["__allcolors__": .white], - size: CGSize(width: 33.0, height: 33.0) - ) - ), - action: { - guard let controller = environment.controller() as? MediaToolsScreen else { - return + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: true, + state: .glass, + component: AnyComponentWithIdentity( + id: "close", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Back", tintColor: .white) + ) + ), + action: { _ in + guard let controller = environment.controller() as? MediaToolsScreen else { + return + } + controller.requestDismiss(reset: true, animated: true) } - controller.requestDismiss(reset: true, animated: true) - } - )), + ) + ), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) @@ -413,18 +415,26 @@ private final class MediaToolsScreenComponent: Component { let doneButtonSize = self.doneButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Image( - image: state.image(.done), - size: CGSize(width: 33.0, height: 33.0) - )), - action: { - guard let controller = environment.controller() as? MediaToolsScreen else { - return + component: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: UIColor(rgb: 0x0088ff), + isDark: true, + state: .tintedGlass, + component: AnyComponentWithIdentity( + id: "done", + component: AnyComponent( + BundleIconComponent(name: "Navigation/Done", tintColor: .white) + ) + ), + action: { _ in + guard let controller = environment.controller() as? MediaToolsScreen else { + return + } + controller.requestDismiss(reset: false, animated: true) } - controller.requestDismiss(reset: false, animated: true) - } - )), + ) + ), environment: {}, containerSize: CGSize(width: 44.0, height: 44.0) ) @@ -439,15 +449,19 @@ private final class MediaToolsScreenComponent: Component { transition.setFrame(view: doneButtonView, frame: doneButtonFrame) } - let buttonsAvailableWidth: CGFloat - let buttonsLeftOffset: CGFloat - if isTablet { - buttonsAvailableWidth = previewSize.width + 260.0 - buttonsLeftOffset = floorToScreenPixels((availableSize.width - buttonsAvailableWidth) / 2.0) - } else { - buttonsAvailableWidth = availableSize.width - buttonsLeftOffset = 0.0 - } + let buttonSize = CGSize(width: 44.0, height: 44.0) + let buttonSpacing: CGFloat = 10.0 + let buttonsTotalWidth = buttonSize.width * 4.0 + buttonSpacing * 3.0 + let buttonsBackgroundFrame = CGRect( + x: cancelButtonFrame.maxX + floorToScreenPixels((doneButtonFrame.minX - cancelButtonFrame.maxX - buttonsTotalWidth) / 2.0), + y: buttonBottomInset, + width: buttonsTotalWidth, + height: buttonSize.height + ) + transition.setFrame(view: self.buttonsBackgroundView, frame: buttonsBackgroundFrame) + self.buttonsBackgroundView.update(size: buttonsBackgroundFrame.size, cornerRadius: buttonsBackgroundFrame.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: transition) + + var buttonOriginX: CGFloat = 0.0 let adjustmentsButtonSize = self.adjustmentsButton.update( transition: transition, @@ -460,17 +474,18 @@ private final class MediaToolsScreenComponent: Component { action: { sectionUpdated(.adjustments) } - )), + ).minSize(buttonSize)), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let adjustmentsButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 4.0 - 3.0 - adjustmentsButtonSize.width / 2.0), y: buttonBottomInset), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: adjustmentsButtonSize ) + buttonOriginX += adjustmentsButtonSize.width + buttonSpacing if let adjustmentsButtonView = self.adjustmentsButton.view { if adjustmentsButtonView.superview == nil { - self.buttonsContainerView.addSubview(adjustmentsButtonView) + self.buttonsBackgroundView.contentView.addSubview(adjustmentsButtonView) } transition.setFrame(view: adjustmentsButtonView, frame: adjustmentsButtonFrame) } @@ -486,17 +501,18 @@ private final class MediaToolsScreenComponent: Component { action: { sectionUpdated(.tint) } - )), + ).minSize(buttonSize)), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let tintButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 2.5 + 5.0 - tintButtonSize.width / 2.0), y: buttonBottomInset), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: tintButtonSize ) + buttonOriginX += tintButtonSize.width + buttonSpacing if let tintButtonView = self.tintButton.view { if tintButtonView.superview == nil { - self.buttonsContainerView.addSubview(tintButtonView) + self.buttonsBackgroundView.contentView.addSubview(tintButtonView) } transition.setFrame(view: tintButtonView, frame: tintButtonFrame) } @@ -512,17 +528,18 @@ private final class MediaToolsScreenComponent: Component { action: { sectionUpdated(.blur) } - )), + ).minSize(buttonSize)), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let blurButtonFrame = CGRect( - origin: CGPoint(x: floorToScreenPixels(availableSize.width - buttonsLeftOffset - buttonsAvailableWidth / 2.5 - 5.0 - blurButtonSize.width / 2.0), y: buttonBottomInset), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: blurButtonSize ) + buttonOriginX += blurButtonSize.width + buttonSpacing if let blurButtonView = self.blurButton.view { if blurButtonView.superview == nil { - self.buttonsContainerView.addSubview(blurButtonView) + self.buttonsBackgroundView.contentView.addSubview(blurButtonView) } transition.setFrame(view: blurButtonView, frame: blurButtonFrame) } @@ -538,21 +555,42 @@ private final class MediaToolsScreenComponent: Component { action: { sectionUpdated(.curves) } - )), + ).minSize(buttonSize)), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: buttonSize ) let curvesButtonFrame = CGRect( - origin: CGPoint(x: buttonsLeftOffset + floorToScreenPixels(buttonsAvailableWidth / 4.0 * 3.0 + 3.0 - curvesButtonSize.width / 2.0), y: buttonBottomInset), + origin: CGPoint(x: buttonOriginX, y: 0.0), size: curvesButtonSize ) if let curvesButtonView = self.curvesButton.view { if curvesButtonView.superview == nil { - self.buttonsContainerView.addSubview(curvesButtonView) + self.buttonsBackgroundView.contentView.addSubview(curvesButtonView) } transition.setFrame(view: curvesButtonView, frame: curvesButtonFrame) } + let selectedButtonFrame: CGRect + switch component.section { + case .adjustments: + selectedButtonFrame = adjustmentsButtonFrame + case .tint: + selectedButtonFrame = tintButtonFrame + case .blur: + selectedButtonFrame = blurButtonFrame + case .curves: + selectedButtonFrame = curvesButtonFrame + } + let selectedToolBackgroundSize = CGSize(width: 33.0, height: 33.0) + let selectedToolBackgroundFrame = CGRect( + origin: CGPoint( + x: floorToScreenPixels(selectedButtonFrame.midX - selectedToolBackgroundSize.width / 2.0), + y: floorToScreenPixels(selectedButtonFrame.midY - selectedToolBackgroundSize.height / 2.0) + ), + size: selectedToolBackgroundSize + ) + transition.setFrame(layer: self.selectedToolBackgroundLayer, frame: selectedToolBackgroundFrame) + var sectionChanged = false if previousSection != component.section { sectionChanged = true @@ -938,7 +976,6 @@ private final class MediaToolsScreenComponent: Component { transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame) - transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size)) return availableSize } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift index a9e3991b4c..ea4942a2e6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerPackListContextItem.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -62,7 +61,7 @@ private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCu if let resource = thumbnailResource as? CloudDocumentMediaResource { resourceId = resource.fileId } - let thumbnailFile = topItemFile ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [], alternativeRepresentations: []) + let thumbnailFile = topItemFile ?? TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [], alternativeRepresentations: []) let _ = freeMediaFileInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start() thumbnailIconSource = ContextMenuActionItemIconSource( @@ -111,6 +110,7 @@ private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCu self.scrollNode.view.alwaysBounceVertical = false self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0) + self.scrollNode.view.scrollsToTop = false } func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift index bc7c5b5fdc..8c4c21630c 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StorySource.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import MediaEditor diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/TintComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/TintComponent.swift index 0f6a0afce0..89dc8bbf4b 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/TintComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/TintComponent.swift @@ -2,9 +2,9 @@ import Foundation import UIKit import Display import ComponentFlow -import LegacyComponents import MediaEditor import TelegramPresentationData +import SliderComponent private final class TintColorComponent: Component { typealias EnvironmentType = Empty @@ -193,7 +193,7 @@ final class TintComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Tint_Shadows, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .shadows ? .white : UIColor(rgb: 0x808080) ) ), @@ -221,7 +221,7 @@ final class TintComponent: Component { content: AnyComponent( Text( text: component.strings.Story_Editor_Tint_Highlights, - font: Font.regular(14.0), + font: Font.bold(13.0), color: state.section == .highlights ? .white : UIColor(rgb: 0x808080) ) ), @@ -339,32 +339,35 @@ final class TintComponent: Component { let sliderSize = self.slider.update( transition: transition, component: AnyComponent( - AdjustmentSliderComponent( - title: "", - value: state.section == .shadows ? component.shadowsValue.intensity : component.highlightsValue.intensity, - minValue: 0.0, - maxValue: 1.0, - startValue: 0.0, - isEnabled: currentColor != .clear, - trackColor: currentColor != .clear ? currentColor : .white, - displayValue: false, - valueUpdated: { [weak state] value in - if let state { - switch state.section { - case .shadows: - shadowsValueUpdated(state.shadowsValue.withUpdatedIntensity(value)) - case .highlights: - highlightsValueUpdated(state.highlightsValue.withUpdatedIntensity(value)) + SliderComponent( + content: .continuous(SliderComponent.Continuous( + value: CGFloat(state.section == .shadows ? component.shadowsValue.intensity : component.highlightsValue.intensity), + range: 0.0 ... 1.0, + startValue: 0.0, + valueUpdated: { [weak state] value in + if let state { + switch state.section { + case .shadows: + shadowsValueUpdated(state.shadowsValue.withUpdatedIntensity(Float(value))) + case .highlights: + highlightsValueUpdated(state.highlightsValue.withUpdatedIntensity(Float(value))) + } } } - }, + )), + useNative: true, + trackBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), + trackForegroundColor: currentColor != .clear ? currentColor : .white, + isEnabled: currentColor != .clear, + trackHeight: 6.0, + displaysBorderOnTracking: true, isTrackingUpdated: { isTracking in isTrackingUpdated(isTracking) } ) ), environment: {}, - containerSize: availableSize + containerSize: CGSize(width: max(0.0, availableSize.width - 28.0 * 2.0), height: 44.0) ) let colorsVerticalSpacing: CGFloat = 9.0 @@ -387,7 +390,8 @@ final class TintComponent: Component { } let verticalSpacing: CGFloat = 3.0 - let sliderFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + highlightsButtonSize.height + verticalSpacing + sizes.first!.height + verticalSpacing), size: sliderSize) + let sliderRowHeight: CGFloat = 52.0 + let sliderFrame = CGRect(origin: CGPoint(x: 28.0, y: topInset + highlightsButtonSize.height + verticalSpacing + sizes.first!.height + verticalSpacing + 7.0), size: sliderSize) if let view = self.slider.view { if view.superview == nil { self.addSubview(view) @@ -395,7 +399,7 @@ final class TintComponent: Component { transition.setFrame(view: view, frame: sliderFrame) } - return CGSize(width: availableSize.width, height: topInset + highlightsButtonSize.height + colorsVerticalSpacing + sizes.first!.height + verticalSpacing + sliderSize.height) + return CGSize(width: availableSize.width, height: topInset + highlightsButtonSize.height + colorsVerticalSpacing + sizes.first!.height + verticalSpacing + sliderRowHeight) } } @@ -407,4 +411,3 @@ final class TintComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } - diff --git a/submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist/Sources/PeerMessagesMediaPlaylist.swift index 00da2e081c..bea6e89f13 100644 --- a/submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist/Sources/PeerMessagesMediaPlaylist.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -10,10 +9,10 @@ import MusicAlbumArtResources import TextFormat private enum PeerMessagesMediaPlaylistLoadAnchor { - case messageId(MessageId) - case index(MessageIndex) - - var id: MessageId { + case messageId(EngineMessage.Id) + case index(EngineMessage.Index) + + var id: EngineMessage.Id { switch self { case let .messageId(id): return id @@ -33,9 +32,9 @@ struct MessageMediaPlaylistItemStableId: Hashable { let stableId: UInt32 } -private func extractFileMedia(_ message: Message) -> TelegramMediaFile? { +private func extractFileMedia(_ message: EngineRawMessage) -> TelegramMediaFile? { var file: TelegramMediaFile? - for media in message.media { + for media in message.effectiveMedia { if let media = media as? TelegramMediaFile { file = media break @@ -52,10 +51,10 @@ private func extractFileMedia(_ message: Message) -> TelegramMediaFile? { public final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { public let id: SharedMediaPlaylistItemId - public let message: Message + public let message: EngineRawMessage public let isSavedMusic: Bool - - public init(message: Message, isSavedMusic: Bool) { + + public init(message: EngineRawMessage, isSavedMusic: Bool) { self.id = PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index) self.message = message self.isSavedMusic = isSavedMusic @@ -162,11 +161,11 @@ private enum NavigatedMessageFromViewPosition { case exact } -private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: MessageIndex) -> [Message] { +private func aroundMessagesFromMessages(_ messages: [EngineRawMessage], centralIndex: EngineMessage.Index) -> [EngineRawMessage] { guard let index = messages.firstIndex(where: { $0.index.id == centralIndex.id }) else { return [] } - var result: [Message] = [] + var result: [EngineRawMessage] = [] if index != 0 { for i in (0 ..< index).reversed() { result.append(messages[i]) @@ -182,7 +181,7 @@ private func aroundMessagesFromMessages(_ messages: [Message], centralIndex: Mes return result } -private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: MessageIndex) -> [Message] { +private func aroundMessagesFromView(view: EngineRawMessageHistoryView, centralIndex: EngineMessage.Index) -> [EngineRawMessage] { let filteredEntries = view.entries.filter { entry in if entry.message.minAutoremoveOrClearTimeout == viewOnceTimeout { return false @@ -194,7 +193,7 @@ private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: Mess guard let index = filteredEntries.firstIndex(where: { $0.index.id == centralIndex.id }) else { return [] } - var result: [Message] = [] + var result: [EngineRawMessage] = [] if index != 0 { for i in (0 ..< index).reversed() { result.append(filteredEntries[i].message) @@ -210,7 +209,7 @@ private func aroundMessagesFromView(view: MessageHistoryView, centralIndex: Mess return result } -private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition) -> (message: Message, around: [Message], exact: Bool)? { +private func navigatedMessageFromMessages(_ messages: [EngineRawMessage], anchorIndex: EngineMessage.Index, position: NavigatedMessageFromViewPosition) -> (message: EngineRawMessage, around: [EngineRawMessage], exact: Bool)? { var index = 0 for message in messages { if message.index.id == anchorIndex.id { @@ -249,7 +248,7 @@ private func navigatedMessageFromMessages(_ messages: [Message], anchorIndex: Me } } -private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: MessageIndex, position: NavigatedMessageFromViewPosition, reversed: Bool) -> (message: Message, around: [Message], exact: Bool)? { +private func navigatedMessageFromView(_ view: EngineRawMessageHistoryView, anchorIndex: EngineMessage.Index, position: NavigatedMessageFromViewPosition, reversed: Bool) -> (message: EngineRawMessage, around: [EngineRawMessage], exact: Bool)? { var index = 0 let filteredEntries = view.entries.filter { entry in @@ -348,10 +347,10 @@ private func navigatedMessageFromView(_ view: MessageHistoryView, anchorIndex: M } private struct PlaybackStack { - var ids: [MessageId] = [] - var set: Set = [] + var ids: [EngineMessage.Id] = [] + var set: Set = [] - mutating func resetToId(_ id: MessageId) { + mutating func resetToId(_ id: EngineMessage.Id) { if self.set.contains(id) { if let index = self.ids.firstIndex(of: id) { for i in (index + 1) ..< self.ids.count { @@ -369,7 +368,7 @@ private struct PlaybackStack { } } - mutating func push(_ id: MessageId) { + mutating func push(_ id: EngineMessage.Id) { if self.set.contains(id) { if let index = self.ids.firstIndex(of: id) { self.ids.remove(at: index) @@ -379,7 +378,7 @@ private struct PlaybackStack { self.set.insert(id) } - mutating func pop() -> MessageId? { + mutating func pop() -> EngineMessage.Id? { if !self.ids.isEmpty { let id = self.ids.removeLast() self.set.remove(id) @@ -411,8 +410,8 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { private var playbackStack = PlaybackStack() - private var currentItem: (current: Message, around: [Message])? - private var currentlyObservedMessageId: MessageId? + private var currentItem: (current: EngineRawMessage, around: [EngineRawMessage])? + private var currentlyObservedMessageId: EngineMessage.Id? private let currentlyObservedMessageDisposable = MetaDisposable() private var loadingItem: Bool = false private var loadingMore: Bool = false @@ -564,7 +563,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { self.loadingItem = true self.updateState() - let namespaces: MessageIdNamespaces + let namespaces: EngineMessageIdNamespaces if Namespaces.Message.allScheduled.contains(anchor.id.namespace) { namespaces = .just(Namespaces.Message.allScheduled) } else if Namespaces.Message.allQuickReply.contains(anchor.id.namespace) { @@ -579,13 +578,13 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { case let .messages(chatLocation, tagMask, _): let historySignal = self.context.account.postbox.messageAtId(messageId) |> take(1) - |> mapToSignal { message -> Signal<(Message, [Message])?, NoError> in + |> mapToSignal { message -> Signal<(EngineRawMessage, [EngineRawMessage])?, NoError> in guard let message = message else { return .single(nil) } return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic(value: nil)), anchor: .index(message.index), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: .tag(tagMask), appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: []) - |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + |> mapToSignal { view -> Signal<(EngineRawMessage, [EngineRawMessage])?, NoError> in if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: message.index, position: .exact, reversed: reversed) { return .single((message, aroundMessages)) } else { @@ -658,7 +657,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { case let .index(index): switch self.messagesLocation.effectiveLocation(context: self.context) { case let .messages(chatLocation, tagMask, _): - var inputIndex: Signal? + var inputIndex: Signal? let looping = self.looping switch self.order { case .regular, .reversed: @@ -693,12 +692,12 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { } } let historySignal = (inputIndex ?? .single(nil)) - |> mapToSignal { inputIndex -> Signal<(Message, [Message])?, NoError> in + |> mapToSignal { inputIndex -> Signal<(EngineRawMessage, [EngineRawMessage])?, NoError> in guard let inputIndex = inputIndex else { return .single(nil) } return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic(value: nil)), anchor: .index(inputIndex), ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: .tag(tagMask), appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: []) - |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + |> mapToSignal { view -> Signal<(EngineRawMessage, [EngineRawMessage])?, NoError> in let position: NavigatedMessageFromViewPosition switch navigation { case .later: @@ -721,14 +720,14 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { } if case .all = looping { - let viewIndex: HistoryViewInputAnchor + let viewIndex: EngineHistoryViewInputAnchor if case .earlier = navigation { viewIndex = .upperBound } else { viewIndex = .lowerBound } return self.context.account.postbox.aroundMessageHistoryViewForLocation(self.context.chatLocationInput(for: chatLocation, contextHolder: self.chatLocationContextHolder ?? Atomic(value: nil)), anchor: viewIndex, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: .tag(tagMask), appendMessagesFromTheSameGroup: false, namespaces: namespaces, orderStatistics: []) - |> mapToSignal { view -> Signal<(Message, [Message])?, NoError> in + |> mapToSignal { view -> Signal<(EngineRawMessage, [EngineRawMessage])?, NoError> in let position: NavigatedMessageFromViewPosition switch navigation { case .later, .random: @@ -736,7 +735,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { case .earlier: position = .later } - if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: MessageIndex.absoluteLowerBound(), position: position, reversed: reversed) { + if let (message, aroundMessages, _) = navigatedMessageFromView(view.0, anchorIndex: EngineMessage.Index.absoluteLowerBound(), position: position, reversed: reversed) { return .single((message, aroundMessages)) } else { return .single(nil) @@ -791,7 +790,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { case .savedMusic: fatalError() case let .custom(messages, _, _, loadMore, _): - let inputIndex: Signal + let inputIndex: Signal let looping = self.looping switch self.order { case .regular, .reversed: @@ -800,7 +799,7 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { var playbackStack = self.playbackStack inputIndex = messages |> take(1) - |> map { messages, _, _ -> MessageIndex in + |> map { messages, _, _ -> EngineMessage.Index in if case let .random(previous) = navigation, previous { let _ = playbackStack.pop() while true { @@ -817,10 +816,10 @@ public final class PeerMessagesMediaPlaylist: SharedMediaPlaylist { } } let historySignal = inputIndex - |> mapToSignal { inputIndex -> Signal<((Message, [Message])?, Int, Bool), NoError> in + |> mapToSignal { inputIndex -> Signal<((EngineRawMessage, [EngineRawMessage])?, Int, Bool), NoError> in return messages |> take(1) - |> mapToSignal { messages, _, hasMore -> Signal<((Message, [Message])?, Int, Bool), NoError> in + |> mapToSignal { messages, _, hasMore -> Signal<((EngineRawMessage, [EngineRawMessage])?, Int, Bool), NoError> in let position: NavigatedMessageFromViewPosition switch navigation { case .later: diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD index 4fc1322a18..9f85342356 100644 --- a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/BUILD @@ -28,7 +28,6 @@ swift_library( "//submodules/TelegramUI/Components/GlobalControlPanelsContext", "//submodules/UndoUI", "//submodules/OverlayStatusController", - "//submodules/Postbox", "//submodules/PresentationDataUtils", ], visibility = [ diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift index 21b4d08ef8..2099084833 100644 --- a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaNavigationAccessoryHeaderNode.swift @@ -257,7 +257,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi self.closeButton.displaysAsynchronously = false self.rateButton = AudioRateButton() - self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -4.0) + self.rateButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -4.0, bottom: -8.0, right: -8.0) self.rateButton.displaysAsynchronously = false self.accessibilityAreaNode = AccessibilityAreaNode() @@ -280,7 +280,6 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi self.scrollNode.addSubnode(self.previousItemNode) self.scrollNode.addSubnode(self.nextItemNode) - self.addSubnode(self.rateButton) self.addSubnode(self.accessibilityAreaNode) self.actionButton.addSubnode(self.playPauseIconNode) @@ -297,6 +296,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi self.addSubnode(self.actionButton) self.addSubnode(self.closeButton) + self.addSubnode(self.rateButton) self.actionButton.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { @@ -351,6 +351,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false + self.scrollNode.view.scrollsToTop = false let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.tapRecognizer = tapRecognizer diff --git a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift index 3899788a34..c6b7ba1ebe 100644 --- a/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/MediaPlaybackHeaderPanelComponent/Sources/MediaPlaybackHeaderPanelComponent.swift @@ -12,7 +12,6 @@ import UniversalMediaPlayer import UndoUI import OverlayStatusController import TelegramUIPreferences -import Postbox import PresentationDataUtils public final class MediaPlaybackHeaderPanelComponent: Component { @@ -21,19 +20,22 @@ public final class MediaPlaybackHeaderPanelComponent: Component { public let strings: PresentationStrings public let data: GlobalControlPanelsContext.MediaPlayback public let controller: () -> ViewController? + public let shouldPerformAction: ((@escaping () -> Void) -> Void)? public init( context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, data: GlobalControlPanelsContext.MediaPlayback, - controller: @escaping () -> ViewController? + controller: @escaping () -> ViewController?, + shouldPerformAction: ((@escaping () -> Void) -> Void)? = nil ) { self.context = context self.theme = theme self.strings = strings self.data = data self.controller = controller + self.shouldPerformAction = shouldPerformAction } public static func ==(lhs: MediaPlaybackHeaderPanelComponent, rhs: MediaPlaybackHeaderPanelComponent) -> Bool { @@ -71,6 +73,17 @@ public final class MediaPlaybackHeaderPanelComponent: Component { deinit { self.playlistPreloadDisposable?.dispose() } + + private func performAction(_ action: @escaping () -> Void) { + guard let component = self.component else { + return + } + if let shouldPerformAction = component.shouldPerformAction { + shouldPerformAction(action) + } else { + action() + } + } func update(component: MediaPlaybackHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let themeUpdated = self.component?.theme !== component.theme @@ -148,7 +161,7 @@ public final class MediaPlaybackHeaderPanelComponent: Component { let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) + return EnginePreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) }) return rate } @@ -220,102 +233,110 @@ public final class MediaPlaybackHeaderPanelComponent: Component { }) } panel.togglePlayPause = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: component.data.kind) + }) } panel.playPrevious = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.next, type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.next, type: component.data.kind) + }) } panel.playNext = { [weak self] in - guard let self, let component = self.component else { - return - } - component.context.sharedContext.mediaManager.playlistControl(.previous, type: component.data.kind) + self?.performAction({ [weak self] in + guard let self, let component = self.component else { + return + } + component.context.sharedContext.mediaManager.playlistControl(.previous, type: component.data.kind) + }) } panel.tapAction = { [weak self] in - guard let self, let component = self.component, let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else { - return - } - - if let id = component.data.item.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = component.data.playlistLocation as? PeerMessagesPlaylistLocation { - if case .music = component.data.kind { - switch playlistLocation { - case .custom, .savedMusic: - let controllerContext: AccountContext - if component.data.account.id == component.context.account.id { - controllerContext = component.context - } else { - controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) - } - let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController) - self.window?.endEditing(true) - playerController.navigationPresentation = .flatModal - controller.push(playerController) - case let .messages(chatLocation, _, _): - let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) - - var cancelImpl: (() -> Void)? - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let progressSignal = Signal { [weak self] subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { - cancelImpl?() - })) - self?.component?.controller()?.present(controller, in: .window(.root)) - return ActionDisposable { [weak controller] in - Queue.mainQueue().async() { - controller?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = MetaDisposable() - var progressStarted = false - self.playlistPreloadDisposable?.dispose() - self.playlistPreloadDisposable = (signal - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - |> deliverOnMainQueue).start(next: { [weak self] index in - guard let self, let component = self.component else { - return - } - if let _ = index.0 { - let controllerContext: AccountContext - if component.data.account.id == component.context.account.id { - controllerContext = component.context - } else { - controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) - } - let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController) - self.window?.endEditing(true) - playerController.navigationPresentation = .flatModal - controller.push(playerController) - } else if index.1 { - if !progressStarted { - progressStarted = true - progressDisposable.set(progressSignal.start()) - } - } - }, completed: { - }) - cancelImpl = { [weak self] in - self?.playlistPreloadDisposable?.dispose() - } - default: - break - } - } else { - component.context.sharedContext.navigateToChat(accountId: component.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + self?.performAction({ [weak self] in + guard let self, let component = self.component, let controller = component.controller(), let navigationController = controller.navigationController as? NavigationController else { + return } - } + + if let id = component.data.item.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = component.data.playlistLocation as? PeerMessagesPlaylistLocation { + if case .music = component.data.kind { + switch playlistLocation { + case .custom, .savedMusic: + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: .peer(id: id.messageId.peerId), type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: playlistLocation, parentNavigationController: navigationController) + self.window?.endEditing(true) + playerController.navigationPresentation = .flatModal + controller.push(playerController) + case let .messages(chatLocation, _, _): + let signal = component.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: component.context, chatLocation: chatLocation, subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(EngineMessage.Tags.music)) + + var cancelImpl: (() -> Void)? + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.component?.controller()?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = MetaDisposable() + var progressStarted = false + self.playlistPreloadDisposable?.dispose() + self.playlistPreloadDisposable = (signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + |> deliverOnMainQueue).start(next: { [weak self] index in + guard let self, let component = self.component else { + return + } + if let _ = index.0 { + let controllerContext: AccountContext + if component.data.account.id == component.context.account.id { + controllerContext = component.context + } else { + controllerContext = component.context.sharedContext.makeTempAccountContext(account: component.data.account) + } + let playerController = component.context.sharedContext.makeOverlayAudioPlayerController(context: controllerContext, chatLocation: chatLocation, type: component.data.kind, initialMessageId: id.messageId, initialOrder: component.data.playbackOrder, playlistLocation: nil, parentNavigationController: navigationController) + self.window?.endEditing(true) + playerController.navigationPresentation = .flatModal + controller.push(playerController) + } else if index.1 { + if !progressStarted { + progressStarted = true + progressDisposable.set(progressSignal.start()) + } + } + }, completed: { + }) + cancelImpl = { [weak self] in + self?.playlistPreloadDisposable?.dispose() + } + default: + break + } + } else { + component.context.sharedContext.navigateToChat(accountId: component.context.account.id, peerId: id.messageId.peerId, messageId: id.messageId) + } + } + }) } } diff --git a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD index 1791648614..5f9887c618 100644 --- a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/BUILD @@ -17,7 +17,6 @@ swift_library( "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", "//submodules/PresentationDataUtils", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift index a4a3262b68..2fda8c1f76 100644 --- a/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift +++ b/submodules/TelegramUI/Components/MessageFeeHeaderPanelComponent/Sources/ChatFeePanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift index 13cfb235cb..bfc4b5bec8 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift @@ -7,7 +7,6 @@ import ComponentFlow import SwiftSignalKit import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import AvatarNode import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index a5c7b4d4b3..7ee5afa086 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -82,9 +82,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha signal = .single({ _ in return .stickers([]) }) } - let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let stickerConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> StickersSearchConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return StickersSearchConfiguration.with(appConfiguration: appConfiguration) } let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 6a5074fdc5..5ad64b0bbb 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -1188,13 +1188,18 @@ public final class MessageInputPanelComponent: Component { let previousPlaceholder = self.component?.placeholder - let defaultInsets = UIEdgeInsets(top: 14.0, left: 9.0, bottom: 6.0, right: 41.0) + let defaultSideInset: CGFloat = component.style == .media ? 16.0 : 9.0 + let defaultInsets = UIEdgeInsets(top: 14.0, left: defaultSideInset, bottom: 6.0, right: 41.0) var insets = defaultInsets let layoutFromTop = component.attachmentButtonMode == .captionDown if let _ = component.attachmentAction { - insets.left = 41.0 + if case .media = component.style { + insets.left = 54.0 + } else { + insets.left = 41.0 + } } if let _ = component.setMediaRecordingActive { insets.right = 41.0 @@ -1202,7 +1207,9 @@ public final class MessageInputPanelComponent: Component { let textFieldSideInset: CGFloat switch component.style { - case .media, .videoChat, .gift: + case .media: + textFieldSideInset = 16.0 + case .videoChat, .gift: textFieldSideInset = 8.0 default: textFieldSideInset = 9.0 @@ -1326,7 +1333,7 @@ public final class MessageInputPanelComponent: Component { let placeholderTransition: ComponentTransition = (previousPlaceholder != nil && previousPlaceholder != component.placeholder) ? ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)) : .immediate let placeholderSize: CGSize - var placeholderColor = UIColor(rgb: 0xffffff, alpha: 0.4) + var placeholderColor = UIColor(rgb: 0xffffff, alpha: 0.35) if case .gift = component.style { placeholderColor = component.theme.chat.inputPanel.inputPlaceholderColor } @@ -1483,11 +1490,9 @@ public final class MessageInputPanelComponent: Component { } var fieldBackgroundFrame: CGRect - if hasMediaRecording { + if hasMediaRecording || [.videoChat, .gift].contains(component.style) { fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: fieldFrame.height)) - } else if [.videoChat, .gift].contains(component.style) { - fieldBackgroundFrame = CGRect(origin: CGPoint(x: mediaInsets.left, y: insets.top), size: CGSize(width: availableSize.width - mediaInsets.left - mediaInsets.right, height: fieldFrame.height)) - } else if isEditing || component.style == .editor || component.style == .media { + } else if isEditing || [.editor, .media].contains(component.style) { fieldBackgroundFrame = fieldFrame } else { if component.forwardAction != nil && component.likeAction != nil { @@ -1502,10 +1507,8 @@ public final class MessageInputPanelComponent: Component { let rawFieldBackgroundFrame = fieldBackgroundFrame fieldBackgroundFrame.size.height += headerHeight - //transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size)) - switch component.style { - case .gift: + case .media, .editor, .gift, .videoChat: if self.fieldGlassBackgroundView == nil { let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame) self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView) @@ -1515,20 +1518,14 @@ public final class MessageInputPanelComponent: Component { self.fieldBackgroundTint.isHidden = true } if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { - fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition) - transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) - } - case .videoChat: - if self.fieldGlassBackgroundView == nil { - let fieldGlassBackgroundView = GlassBackgroundView(frame: fieldBackgroundFrame) - self.insertSubview(fieldGlassBackgroundView, aboveSubview: self.fieldBackgroundView) - self.fieldGlassBackgroundView = fieldGlassBackgroundView - - self.fieldBackgroundView.isHidden = true - self.fieldBackgroundTint.isHidden = true - } - if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { - fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: .init(kind: .custom(style: .default, color: UIColor(rgb: 0x25272e, alpha: 0.72))), transition: transition) + let tintColor: GlassBackgroundView.TintColor + switch component.style { + case .videoChat: + tintColor = .init(kind: .custom(style: .default, color: UIColor(rgb: 0x25272e, alpha: 0.72))) + default: + tintColor = .init(kind: .panel) + } + fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, transition: transition) transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) } default: @@ -1540,7 +1537,6 @@ public final class MessageInputPanelComponent: Component { transition.setFrame(view: self.fieldBackgroundTint, frame: fieldBackgroundFrame) transition.setFrame(view: self.mediaRecordingVibrancyContainer, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size)) - //self.fieldBackgroundTint.backgroundColor = .blue transition.setCornerRadius(layer: self.fieldBackgroundTint.layer, cornerRadius: headerHeight > 0.0 ? 18.0 : baseFieldHeight * 0.5) var textClippingFrame = rawFieldBackgroundFrame.offsetBy(dx: 0.0, dy: headerHeight) @@ -1559,9 +1555,9 @@ public final class MessageInputPanelComponent: Component { if isEditing || component.style == .story || component.style == .videoChat || component.style == .gift { placeholderOriginX = 16.0 } else { - placeholderOriginX = floorToScreenPixels(fieldBackgroundFrame.minX + (fieldBackgroundFrame.width - placeholderSize.width) / 2.0) + placeholderOriginX = floorToScreenPixels((fieldBackgroundFrame.width - placeholderSize.width) / 2.0) } - let placeholderFrame = CGRect(origin: CGPoint(x: placeholderOriginX, y: headerHeight + floor((rawFieldBackgroundFrame.height - placeholderSize.height) * 0.5)), size: placeholderSize) + let placeholderFrame = CGRect(origin: CGPoint(x: placeholderOriginX, y: headerHeight + floorToScreenPixels((rawFieldBackgroundFrame.height - placeholderSize.height) * 0.5)), size: placeholderSize) if let placeholderView = self.placeholder.view, let vibrancyPlaceholderView = self.vibrancyPlaceholder.view { if vibrancyPlaceholderView.superview == nil { vibrancyPlaceholderView.layer.anchorPoint = CGPoint() @@ -1900,6 +1896,7 @@ public final class MessageInputPanelComponent: Component { transition: transition, component: AnyComponent(MessageInputActionButtonComponent( mode: attachmentButtonMode, + style: attachmentButtonMode != .attach ? .glass(isTinted: false) : .legacy, storyId: component.storyItem?.id, action: { [weak self] mode, action, sendAction in guard let self, let component = self.component, case .up = action else { @@ -1950,7 +1947,7 @@ public final class MessageInputPanelComponent: Component { } else { attachmentButtonPosition = size.height - insets.bottom - baseFieldHeight + attachmentButtonPosition } - let attachmentButtonFrame = CGRect(origin: CGPoint(x: floor((insets.left - attachmentButtonSize.width) * 0.5) + (fieldBackgroundFrame.minX - fieldFrame.minX), y: attachmentButtonPosition), size: attachmentButtonSize) + let attachmentButtonFrame = CGRect(origin: CGPoint(x: floor((insets.left - attachmentButtonSize.width) * 0.5) + (fieldBackgroundFrame.minX - fieldFrame.minX) - 2.0, y: attachmentButtonPosition), size: attachmentButtonSize) transition.setPosition(view: attachmentButtonView, position: attachmentButtonFrame.center) transition.setBounds(view: attachmentButtonView, bounds: CGRect(origin: CGPoint(), size: attachmentButtonFrame.size)) transition.setAlpha(view: attachmentButtonView, alpha: attachmentVisible ? 1.0 : 0.0) diff --git a/submodules/TelegramUI/Components/MiniAppListScreen/BUILD b/submodules/TelegramUI/Components/MiniAppListScreen/BUILD index 02745a260c..abf9388755 100644 --- a/submodules/TelegramUI/Components/MiniAppListScreen/BUILD +++ b/submodules/TelegramUI/Components/MiniAppListScreen/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/TelegramUIPreferences", diff --git a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift index 7e7f049436..b8a7460f99 100644 --- a/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift +++ b/submodules/TelegramUI/Components/MiniAppListScreen/Sources/MiniAppListScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -227,7 +226,7 @@ final class MiniAppListScreenComponent: Component { return } - if let peerInfoScreen = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let peerInfoScreen = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { peerInfoScreen.navigationPresentation = .modal controller.push(peerInfoScreen) } @@ -504,7 +503,9 @@ final class MiniAppListScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift index 71f461ab79..5395153fb8 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift @@ -330,6 +330,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll private var expandedTapGestureRecoginzer: UITapGestureRecognizer? private var currentTransition: Transition? + private var pendingAction: PendingAction? private var isApplyingTransition = false private var validLayout: ContainerViewLayout? @@ -446,6 +447,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll return } if let result = self.scrollView.hitTest(gestureRecognizer.location(in: self.scrollView), with: nil), result === self.scrollView { + guard self.canStartMutatingTransition() else { + return + } self.collapse() } } @@ -596,6 +600,62 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } } + private enum PendingAction: Equatable { + case maximize(itemId: AnyHashable) + } + + private func canStartMutatingTransition() -> Bool { + return self.currentTransition == nil && !self.isApplyingTransition + } + + private func performMaximize(for item: Item) { + guard let navigationController = self.navigationController else { + return + } + item.beforeMaximize(navigationController, { [weak self] in + self?.navigationController?.maximizeViewController(item.controller, animated: true) + }) + } + + private func requestOrQueueMaximize(for item: Item) { + if self.canStartMutatingTransition() { + self.performMaximize(for: item) + return + } + if case let .dismiss(dismissingItemId)? = self.currentTransition, dismissingItemId != item.id { + self.pendingAction = .maximize(itemId: item.id) + } + } + + private func drainPendingAction() { + guard self.canStartMutatingTransition(), let pendingAction = self.pendingAction else { + return + } + self.pendingAction = nil + + switch pendingAction { + case let .maximize(itemId): + guard let item = self.items.first(where: { $0.id == itemId }) else { + return + } + self.performMaximize(for: item) + } + } + + private func completeTransition( + _ completedTransition: Transition, + completion: @escaping (Transition) -> Void, + cleanup: () -> Void = {} + ) { + self.isApplyingTransition = false + if self.currentTransition == completedTransition { + self.currentTransition = nil + } + cleanup() + completion(completedTransition) + self.drainPendingAction() + } + public func maximizeController(_ viewController: MinimizableController, animated: Bool, completion: @escaping (Bool) -> Void) { guard let item = self.items.first(where: { $0.controller === viewController }) else { completion(self.items.count == 0) @@ -634,11 +694,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } if self.items.count == 1, let item = self.items.first { - if let navigationController = self.navigationController { - item.beforeMaximize(navigationController, { [weak self] in - self?.navigationController?.maximizeViewController(item.controller, animated: true) - }) - } + self.performMaximize(for: item) } else { let contentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.bounds.height) self.scrollView.contentOffset = CGPoint(x: 0.0, y: contentOffset) @@ -662,6 +718,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll return } self.requestUpdate(transition: .immediate) + guard self.canStartMutatingTransition() else { + return + } let contentOffset = scrollView.contentOffset if scrollView.contentOffset.y < -64.0, let lastItemId = self.items.last?.id, let itemNode = self.itemNodes[lastItemId] { @@ -798,6 +857,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll return } if self.isExpanded { + guard self.canStartMutatingTransition() else { + return + } let proceed = { [weak self] in guard let self else { return @@ -864,13 +926,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll return } if self.isExpanded, let itemNode { - if let navigationController = self.navigationController { - itemNode.item.beforeMaximize(navigationController, { [weak self, weak itemNode] in - if let item = itemNode?.item { - self?.navigationController?.maximizeViewController(item.controller, animated: true) - } - }) - } + self.requestOrQueueMaximize(for: itemNode.item) } else { self.expand() } @@ -1032,11 +1088,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } transition.animatePosition(node: itemNode, from: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + initialOffset), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - completion(currentTransition) + self.completeTransition(currentTransition, completion: completion) }) case let .maximize(itemId): let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) @@ -1075,31 +1127,26 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } transition.updatePosition(node: itemNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + maximizeTopInset + self.scrollView.contentOffset.y), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - - completion(currentTransition) - - if let _ = itemNode.snapshotView { - let snapshotContainerView = itemNode.snapshotContainerView - snapshotContainerView.isUserInteractionEnabled = true - snapshotContainerView.layer.allowsGroupOpacity = true - snapshotContainerView.center = CGPoint(x: itemNode.item.controller.displayNode.view.bounds.width / 2.0, y: snapshotContainerView.bounds.height / 2.0) - itemNode.item.controller.displayNode.view.addSubview(snapshotContainerView) - Queue.mainQueue().after(0.35, { - snapshotContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - snapshotContainerView.removeFromSuperview() + self.completeTransition(currentTransition, completion: completion, cleanup: { + if let _ = itemNode.snapshotView { + let snapshotContainerView = itemNode.snapshotContainerView + snapshotContainerView.isUserInteractionEnabled = true + snapshotContainerView.layer.allowsGroupOpacity = true + snapshotContainerView.center = CGPoint(x: itemNode.item.controller.displayNode.view.bounds.width / 2.0, y: snapshotContainerView.bounds.height / 2.0) + itemNode.item.controller.displayNode.view.addSubview(snapshotContainerView) + Queue.mainQueue().after(0.35, { + snapshotContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotContainerView.removeFromSuperview() + }) }) - }) - } - - self.itemNodes[itemId] = nil - itemNode.removeFromSupernode() - dimView.removeFromSuperview() - - self.requestUpdate(transition: .immediate) + } + + self.itemNodes[itemId] = nil + itemNode.removeFromSupernode() + dimView.removeFromSuperview() + + self.requestUpdate(transition: .immediate) + }) }) case let .dismiss(itemId): let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) @@ -1129,18 +1176,15 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } transition.updatePosition(node: itemNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + topInset + self.scrollView.contentOffset.y), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - completion(currentTransition) - self.itemNodes[itemId] = nil - itemNode.removeFromSupernode() - dimView.removeFromSuperview() - - self.navigationController?.maximizeViewController(itemNode.item.controller, animated: false) - - self.requestUpdate(transition: .immediate) + self.completeTransition(currentTransition, completion: completion, cleanup: { + self.itemNodes[itemId] = nil + itemNode.removeFromSupernode() + dimView.removeFromSuperview() + + self.navigationController?.maximizeViewController(itemNode.item.controller, animated: false) + + self.requestUpdate(transition: .immediate) + }) }) }) } @@ -1148,18 +1192,14 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll } else { let isLast = self.items.isEmpty transition.updatePosition(node: dismissedItemNode, position: CGPoint(x: -layout.size.width, y: dismissedItemNode.position.y), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - completion(currentTransition) - - self.itemNodes[itemId] = nil - dismissedItemNode.removeFromSupernode() - - if isLast { - self.didDismiss?(self) - } + self.completeTransition(currentTransition, completion: completion, cleanup: { + self.itemNodes[itemId] = nil + dismissedItemNode.removeFromSupernode() + + if isLast { + self.didDismiss?(self) + } + }) }) if isLast { let dismissOffset = collapsedHeight(layout: layout) @@ -1169,20 +1209,12 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll case .dismissAll: let dismissOffset = collapsedHeight(layout: layout) transition.updatePosition(layer: self.bottomEdgeView.layer, position: self.bottomEdgeView.layer.position.offsetBy(dx: 0.0, dy: dismissOffset), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - completion(currentTransition) + self.completeTransition(currentTransition, completion: completion) }) transition.updatePosition(layer: self.scrollView.layer, position: self.scrollView.center.offsetBy(dx: 0.0, dy: dismissOffset)) case .collapse: transition.updateBounds(layer: self.scrollView.layer, bounds: CGRect(origin: .zero, size: self.scrollView.bounds.size), completion: { _ in - self.isApplyingTransition = false - if self.currentTransition == currentTransition { - self.currentTransition = nil - } - completion(currentTransition) + self.completeTransition(currentTransition, completion: completion) }) default: break diff --git a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift index 6879454e18..5d175befa2 100644 --- a/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift +++ b/submodules/TelegramUI/Components/MoreHeaderButton/Sources/MoreHeaderButton.swift @@ -86,7 +86,7 @@ public final class MoreHeaderButton: HighlightableButtonNode { transition: .immediate, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent( - name: "anim_moredots" + name: "anim_morewide" ), color: self.color )), diff --git a/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD index e987d0cb71..e3cf1c57ae 100644 --- a/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/ContextUI:ContextUI", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift index 1d2511cf75..c02b17c648 100644 --- a/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/MultiplexedVideoNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramCore import AVFoundation import ContextUI @@ -59,8 +58,8 @@ private final class MultiplexedVideoTrackingNode: ASDisplayNode { private final class VisibleVideoItem { enum Id: Equatable, Hashable { - case saved(MediaId) - case trending(MediaId) + case saved(EngineMedia.Id) + case trending(EngineMedia.Id) } let id: Id let file: MultiplexedVideoNodeFile @@ -317,6 +316,7 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate { } self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) self.view.addGestureRecognizer(recognizer) @@ -766,7 +766,7 @@ public final class MultiplexedVideoNode: ASDisplayNode, ASScrollViewDelegate { } } - public func frameForItem(_ id: MediaId) -> CGRect? { + public func frameForItem(_ id: EngineMedia.Id) -> CGRect? { for item in self.displayItems { if item.file.file.media.fileId == id { return item.frame diff --git a/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift index bf26679418..026641a2a6 100644 --- a/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift +++ b/submodules/TelegramUI/Components/MultiplexedVideoNode/Sources/SoftwareVideoThumbnailLayer.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import PhotoResources diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift index 8639fa514d..bb653def06 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationBarImpl.swift @@ -12,8 +12,6 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { return 38.0 } - public static let thinBackArrowImage = generateTintedImage(image: UIImage(bundleImageName: "Navigation/BackArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) - public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) var presentationData: NavigationBarPresentationData @@ -583,11 +581,14 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { public var secondaryContentHeight: CGFloat private var edgeEffectExtension: CGFloat = 0.0 - private var edgeEffectView: EdgeEffectView? + private var edgeEffectViewImpl: EdgeEffectView? + public var edgeEffectView: UIView? { + return self.edgeEffectViewImpl + } private var backgroundContainer: GlassBackgroundContainerView? public var backgroundView: UIView { - if let edgeEffectView = self.edgeEffectView { + if let edgeEffectView = self.edgeEffectViewImpl { return edgeEffectView } else { return self.backgroundNode.view @@ -631,7 +632,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } self.buttonsContainerNode = SparseNode() - self.buttonsContainerNode.clipsToBounds = true + //self.buttonsContainerNode.clipsToBounds = true self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor @@ -654,7 +655,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { if case .glass = presentationData.theme.style { let edgeEffectView = EdgeEffectView() edgeEffectView.isUserInteractionEnabled = false - self.edgeEffectView = edgeEffectView + self.edgeEffectViewImpl = edgeEffectView self.view.addSubview(edgeEffectView) let backgroundContainer = GlassBackgroundContainerView() @@ -721,6 +722,12 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } } + self.leftButtonNodeImpl.requestUpdate = { [weak self] in + guard let self else { + return + } + self.requestLayout() + } self.rightButtonNodeImpl.pressed = { [weak self] index in if let item = self?.item { @@ -733,6 +740,12 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } } } + self.rightButtonNodeImpl.requestUpdate = { [weak self] in + guard let self else { + return + } + self.requestLayout() + } } public var isBackgroundVisible: Bool { @@ -806,7 +819,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { backgroundContainer.update(size: backgroundContainerFrame.size, isDark: self.presentationData.theme.overallDarkAppearance, transition: ComponentTransition(transition)) } - if let edgeEffectView = self.edgeEffectView { + if let edgeEffectView = self.edgeEffectViewImpl { if let edgeEffectColor = self.presentationData.theme.edgeEffectColor, edgeEffectColor.alpha == 0.0 { edgeEffectView.isHidden = true } else { @@ -906,6 +919,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { case .accent: self.leftButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5) + case .accentDisabled: + self.leftButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor + self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5) case .generic: self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor @@ -946,6 +962,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { case .accent: self.rightButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5) + case .accentDisabled: + self.rightButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor + self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5) case .generic: self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor @@ -994,6 +1013,8 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { switch self.leftButtonNodeImpl.commonContentType { case .accent: leftButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentButtonColor)) + case .accentDisabled: + leftButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentDisabledButtonColor)) case .generic: break } @@ -1028,6 +1049,8 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { switch self.rightButtonNodeImpl.commonContentType { case .accent: rightButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentButtonColor)) + case .accentDisabled: + rightButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentDisabledButtonColor)) case .generic: break } @@ -1048,17 +1071,18 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } if self.titleNode.view.superview != nil { - let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) + var transition = transition + if self.titleNode.frame.width.isZero { + transition = .immediate + } + self.titleNode.alpha = 1.0 - do { - var transition = transition - if self.titleNode.frame.width.isZero { - transition = .immediate - } - self.titleNode.alpha = 1.0 - - let titleOffset: CGFloat = 0.0 - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) + let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight)) + + if titleSize.width <= size.width - max(leftTitleInset, rightTitleInset) * 2.0 { + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) + } else { + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftTitleInset + floor((size.width - leftTitleInset - rightTitleInset - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) } } @@ -1081,6 +1105,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { titleViewTransition.updateFrame(view: titleView, frame: titleFrame) } else { + leftTitleInset = max(leftTitleInset, rightTitleInset) + rightTitleInset = leftTitleInset + let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight) var titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset { @@ -1109,7 +1136,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar { } private func applyEdgeEffectExtension(transition: ContainedViewLayoutTransition) { - if let edgeEffectView = self.edgeEffectView { + if let edgeEffectView = self.edgeEffectViewImpl { transition.updateTransform(layer: edgeEffectView.layer, transform: CATransform3DMakeTranslation(0.0, max(0.0, min(20.0, self.edgeEffectExtension)), 0.0)) } } diff --git a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift index 5c5982341f..9970050187 100644 --- a/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift +++ b/submodules/TelegramUI/Components/NavigationBarImpl/Sources/NavigationButtonNode.swift @@ -211,6 +211,7 @@ private final class ItemComponent: Component { private final class NavigationButtonItemNode: ImmediateTextNode { private let isGlass: Bool + var requestUpdate: (() -> Void)? private func fontForCurrentState() -> UIFont { return self.bold ? Font.semibold(17.0) : Font.medium(17.0) @@ -235,7 +236,13 @@ private final class NavigationButtonItemNode: ImmediateTextNode { if let item = self.item { self.setEnabledListener = item.addSetEnabledListener { [weak self] value in - self?.isEnabled = value + guard let self else { + return + } + if self.isEnabled != value { + self.isEnabled = value + self.requestUpdate?() + } } self.accessibilityHint = item.accessibilityHint self.accessibilityLabel = item.accessibilityLabel @@ -528,6 +535,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode { public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode { enum ContentType { case accent + case accentDisabled case generic } @@ -537,6 +545,8 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga private var items: [UIBarButtonItem] = [] private var nodes: [NavigationButtonItemNode] = [] + var requestUpdate: (() -> Void)? + private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = [] public var singleCustomNode: ASDisplayNode? { @@ -633,6 +643,9 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga } } } + node.requestUpdate = { [weak self] in + self?.requestUpdate?() + } self.nodes.append(node) self.addSubnode(node) } @@ -680,6 +693,9 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga } } } + node.requestUpdate = { [weak self] in + self?.requestUpdate?() + } self.nodes.append(node) self.addSubnode(node) } @@ -812,7 +828,11 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga if item.title == "___close" { } else if item.title == "___clear" { } else if item.title == "___done" { - nodeContentType = .accent + if item.isEnabled { + nodeContentType = .accent + } else { + nodeContentType = .accentDisabled + } } if commonType == nil { diff --git a/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD b/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD index c561d70b86..742d73bcfe 100644 --- a/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD +++ b/submodules/TelegramUI/Components/NotificationExceptionsScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift index 8b20e10e96..e33c33a6fa 100644 --- a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift +++ b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -614,7 +613,7 @@ public func threadNotificationExceptionsScreen(context: AccountContext, peerId: if !state.notificationExceptions.isEmpty { if state.editing { leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}) - rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { + rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: { updateState { value in var value = value value.editing = false diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD b/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD index a7990a3978..c4c42c4916 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index c9fcea3e0a..7df38817e7 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -1006,11 +1006,11 @@ public func notificationPeerExceptionController( let signal = combineLatest(queue: .mainQueue(), (updatedPresentationData?.signal ?? context.sharedContext.presentationData), context.engine.peers.notificationSoundList(), statePromise.get() |> distinctUntilChanged) |> map { presentationData, notificationSoundList, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { arguments.cancel() }) - let rightNavigationButton = ItemListNavigationButton(content: .text(state.canRemove || edit ? presentationData.strings.Common_Done : presentationData.strings.Notification_Exceptions_Add), style: .bold, enabled: true, action: { + let rightNavigationButton = ItemListNavigationButton(content: state.canRemove || edit ? .icon(.done) : .text(presentationData.strings.Notification_Exceptions_Add), style: .bold, enabled: true, action: { arguments.complete() }) @@ -1116,7 +1116,7 @@ public func notificationPeerExceptionController( break } }), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { }) ], parseMarkdown: true), in: .window(.root)) } diff --git a/submodules/TelegramUI/Components/OpenUserGeneratedUrl/BUILD b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/BUILD new file mode 100644 index 0000000000..524bf1429b --- /dev/null +++ b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "OpenUserGeneratedUrl", + module_name = "OpenUserGeneratedUrl", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/ComponentFlow", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/OverlayStatusController", + "//submodules/UrlWhitelist", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/AlertComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent", + "//submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent", + "//submodules/UrlHandling", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/PresentationDataUtils/Sources/OpenUrl.swift b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift similarity index 71% rename from submodules/PresentationDataUtils/Sources/OpenUrl.swift rename to submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift index aa2dc5d708..51d69bcbb9 100644 --- a/submodules/PresentationDataUtils/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift @@ -1,27 +1,46 @@ import Foundation +import UIKit import Display import SwiftSignalKit -import Postbox +import ComponentFlow +import TelegramCore import AccountContext import OverlayStatusController import UrlWhitelist import TelegramPresentationData import AlertComponent +import AlertCheckComponent +import AlertWebpagePreviewComponent import UrlHandling -public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, forceDark: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise? = nil, alertDisplayUpdated: ((ViewController?) -> Void)? = nil) -> Disposable { +public func openUserGeneratedUrl( + context: AccountContext, + peerId: EnginePeer.Id?, + url: String, + webpage: TelegramMediaWebpage? = nil, + concealed: Bool, + forceConcealed: Bool = false, + skipUrlAuth: Bool = false, + skipConcealedAlert: Bool = false, + forceDark: Bool = false, + present: @escaping (ViewController) -> Void, + openResolved: @escaping (ResolvedUrl) -> Void, + progress: Promise? = nil, + alertDisplayUpdated: ((ViewController?) -> Void)? = nil, + concealedAlertOption: OpenUserGeneratedUrlConcealedAlertOption? = nil +) -> Disposable { var concealed = concealed - + var presentationData = context.sharedContext.currentPresentationData.with { $0 } if forceDark { presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) } - + let openImpl: () -> Disposable = { let disposable = MetaDisposable() var cancelImpl: (() -> Void)? let progressSignal: Signal - + if let progress { progressSignal = Signal { subscriber in progress.set(.single(true)) @@ -32,7 +51,7 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: |> runOn(Queue.mainQueue()) } else { progressSignal = Signal { subscriber in - let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + let controller = OverlayStatusController(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark, type: .loading(cancelled: { cancelImpl?() })) present(controller) @@ -46,17 +65,17 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: } let progressDisposable = MetaDisposable() var didStartProgress = false - + cancelImpl = { disposable.dispose() } - + var resolveSignal: Signal resolveSignal = context.sharedContext.resolveUrlWithProgress(context: context, peerId: peerId, url: url, skipUrlAuth: skipUrlAuth) #if DEBUG //resolveSignal = .single(.progress) |> then(resolveSignal |> delay(2.0, queue: .mainQueue())) #endif - + disposable.set((resolveSignal |> afterDisposed { Queue.mainQueue().async { @@ -75,44 +94,83 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: openResolved(result) } })) - + return ActionDisposable { cancelImpl?() } } - + let (parsedString, parsedConcealed) = parseUrl(url: url, wasConcealed: concealed) concealed = parsedConcealed - - if let parsedUrl = parseInternalUrl(sharedContext: context.sharedContext, context: context, query: url) { + + if forceConcealed { + concealed = true + } else if let parsedUrl = parseInternalUrl(sharedContext: context.sharedContext, context: context, query: url) { if case .proxy = parsedUrl { concealed = true } } - + if concealed && !skipConcealedAlert { var rawDisplayUrl: String = parsedString let maxLength = 180 if rawDisplayUrl.count > maxLength { rawDisplayUrl = String(rawDisplayUrl[..] = [] + content.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(AlertTitleComponent(title: presentationData.strings.OpenLinkConfirmation_Title)))) + content.append(AnyComponentWithIdentity(id: "text", component: AnyComponent(AlertTextComponent( + content: .plain(displayUrl), + alignment: .center, + color: .primary, + style: .background(.default), + insets: UIEdgeInsets(top: 9.0, left: 4.0, bottom: 0.0, right: 4.0) + )))) + if let webpage, case .Loaded = webpage.content { + content.append(AnyComponentWithIdentity(id: "webpagePreview", component: AnyComponent(AlertWebpagePreviewComponent( + context: context, + presentationData: presentationData, + webpage: webpage + )))) } - present(alertController) - alertDisplayUpdated?(alertController) + if let concealedAlertOption, let checkState { + content.append(AnyComponentWithIdentity(id: "check", component: AnyComponent(AlertCheckComponent( + title: concealedAlertOption.title, + initialValue: false, + externalState: checkState + )))) + } + + var updatedPresentationDataSignal = context.sharedContext.presentationData + if forceDark { + updatedPresentationDataSignal = .single(presentationData) + } + let controller = AlertScreen( + content: content, + actions: [ + .init(title: presentationData.strings.Common_Cancel), + .init(title: presentationData.strings.OpenLinkConfirmation_Open, type: .default, action: { + if checkState?.value == true { + concealedAlertOption?.action() + } + disposable.set(openImpl()) + }), + ], + updatedPresentationData: (presentationData, updatedPresentationDataSignal) + ) + controller.dismissed = { _ in + alertDisplayUpdated?(nil) + } + present(controller) + alertDisplayUpdated?(controller) return disposable } else { return openImpl() diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD index 12a157c72b..0f5f193a9a 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/BUILD @@ -18,7 +18,6 @@ swift_library( "//submodules/Components/ViewControllerComponent", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/TelegramUI/Components/SwitchComponent", diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift index a432b7cef8..52985f43f6 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/PeerAllowedReactionsScreen.swift @@ -8,7 +8,6 @@ import AppBundle import ViewControllerComponent import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import EntityKeyboard import MultilineTextComponent @@ -59,6 +58,7 @@ final class PeerAllowedReactionsScreenComponent: Component { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -111,6 +111,7 @@ final class PeerAllowedReactionsScreenComponent: Component { self.emojiSearchState.set(.single(self.emojiSearchStateValue)) } } + private var emojiSearchContext: EmojiSearchContext? private var emptyResultEmojis: [TelegramMediaFile] = [] private var stableEmptyResultEmoji: TelegramMediaFile? @@ -471,7 +472,7 @@ final class PeerAllowedReactionsScreenComponent: Component { } else { self.stableEmptyResultEmoji = nil } - emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active) + emojiContent = emojiContent.withUpdatedItemGroups(panelItemGroups: emojiContent.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : .active, canLoadMore: emojiSearchResult.canLoadMore) } else { self.stableEmptyResultEmoji = nil @@ -598,17 +599,20 @@ final class PeerAllowedReactionsScreenComponent: Component { switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { let context = component.context let isEmojiOnly = !"".isEmpty + self.emojiSearchContext = nil var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { @@ -633,7 +637,7 @@ final class PeerAllowedReactionsScreenComponent: Component { } |> distinctUntilChanged - let resultSignal: Signal<[EmojiPagerContentComponent.ItemGroup], NoError> + let resultSignal: Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> do { let remotePacksSignal: Signal<(sets: FoundStickerSets, isFinalResult: Bool), NoError> = .single((FoundStickerSets(), false)) |> then( @@ -644,7 +648,7 @@ final class PeerAllowedReactionsScreenComponent: Component { let localPacksSignal: Signal = context.engine.stickers.searchEmojiSets(query: query) resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { @@ -687,19 +691,20 @@ final class PeerAllowedReactionsScreenComponent: Component { fillWithLoadingPlaceholders: false, items: items )) - return .single(resultGroups) + return .single((resultGroups, false, false, nil)) } else { - let remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let emojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let remoteSignal = emojiSearchContext.state return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1), + context.engine.itemCollections.allItems(namespace: Namespaces.ItemCollection.CloudEmojiPacks) |> take(1), context.engine.stickers.availableReactions() |> take(1), hasPremium |> take(1), remotePacksSignal, remoteSignal, localPacksSignal ) - |> map { view, availableReactions, hasPremium, foundPacks, foundEmoji, foundLocalPacks -> [EmojiPagerContentComponent.ItemGroup] in + |> map { rawItems, availableReactions, hasPremium, foundPacks, foundEmoji, foundLocalPacks -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in var result: [(String, TelegramMediaFile.Accessor?, String)] = [] var allEmoticons: [String: String] = [:] @@ -724,8 +729,8 @@ final class PeerAllowedReactionsScreenComponent: Component { } } - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { + for rawItem in rawItems { + guard let item = rawItem as? StickerPackItem else { continue } if !item.file.isPremiumEmoji { @@ -741,7 +746,7 @@ final class PeerAllowedReactionsScreenComponent: Component { var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for item in result { if let itemFile = item.1 { if existingIds.contains(itemFile.fileId) { @@ -784,7 +789,7 @@ final class PeerAllowedReactionsScreenComponent: Component { combinedSets = foundLocalPacks combinedSets = combinedSets.merge(with: foundPacks.sets) - var existingCollectionIds = Set() + var existingCollectionIds = Set() for (collectionId, info, _, _) in combinedSets.infos { if !existingCollectionIds.contains(collectionId) { existingCollectionIds.insert(collectionId) @@ -842,7 +847,7 @@ final class PeerAllowedReactionsScreenComponent: Component { )) } } - return resultGroups + return (resultGroups, foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } } @@ -857,18 +862,20 @@ final class PeerAllowedReactionsScreenComponent: Component { return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.emojiSearchContext = nil let resultSignal: Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> do { resultSignal = component.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { continue @@ -938,11 +945,11 @@ final class PeerAllowedReactionsScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -950,6 +957,9 @@ final class PeerAllowedReactionsScreenComponent: Component { updateScrollingToItemGroup: { }, onScroll: {}, + loadMore: { [weak self] in + self?.emojiSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -1807,10 +1817,10 @@ public class PeerAllowedReactionsScreen: ViewControllerComponentContainer { public static func content(context: AccountContext, peerId: EnginePeer.Id) -> Signal { return combineLatest( context.engine.stickers.availableReactions(), - context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)]) + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CachedData(id: peerId)) ) - |> mapToSignal { availableReactions, combinedView -> Signal in - guard let cachedDataView = combinedView.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView, let cachedData = cachedDataView.cachedPeerData as? CachedChannelData else { + |> mapToSignal { availableReactions, cachedPeerData -> Signal in + guard let cachedData = cachedPeerData as? CachedChannelData else { return .complete() } diff --git a/submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem/BUILD b/submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem/BUILD index 074697e8d6..b7264d4587 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/AccountPeerContextItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/PresentationDataUtils", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD index f1e7a53a96..888a999769 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift index deb9abef7b..4e64d422d5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift @@ -8,7 +8,6 @@ import AppBundle import ViewControllerComponent import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import MultilineTextComponent import ButtonComponent diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift index 67084d3172..10d595552b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/JoinAffiliateProgramScreen.swift @@ -8,7 +8,6 @@ import AppBundle import ViewControllerComponent import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import MultilineTextComponent import ButtonComponent @@ -20,6 +19,7 @@ import ContextUI import AvatarNode import PlainButtonComponent import ToastComponent +import TextFormat private final class JoinAffiliateProgramScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -875,7 +875,7 @@ private final class JoinAffiliateProgramScreenComponent: Component { guard let infoController = component.context.sharedContext.makePeerInfoController( context: component.context, updatedPresentationData: nil, - peer: component.sourcePeer._asPeer(), + peer: component.sourcePeer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, @@ -1221,13 +1221,28 @@ private final class JoinAffiliateProgramScreenComponent: Component { body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemSecondaryTextColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), - linkAttribute: { url in - return ("URL", url) + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) } ) ), horizontalAlignment: .center, - maximumNumberOfLines: 0 + maximumNumberOfLines: 0, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak self] attributes, _ in + guard let environment = self?.environment, let controller = environment.controller(), let navigationController = controller.navigationController as? NavigationController else { + return + } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: environment.strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + } )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD index 78ff393298..c6a470262e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/PresentationDataUtils", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen/Sources/PeerCopyProtectionInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen/Sources/PeerCopyProtectionInfoScreen.swift index ed1a58fd27..69c09e989e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen/Sources/PeerCopyProtectionInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerCopyProtectionInfoScreen/Sources/PeerCopyProtectionInfoScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD index 586c694ff3..2cbff8a410 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift index 16a1c5d10e..7c2686c79b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode/Sources/PeerInfoChatListPaneNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -488,7 +487,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS } } - public func ensureMessageIsVisible(id: MessageId) { + public func ensureMessageIsVisible(id: EngineMessage.Id) { } public func scrollToTop() -> Bool { @@ -508,7 +507,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS public func brieflyDisableTouchActions() { } - public func findLoadedMessage(id: MessageId) -> Message? { + public func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -521,7 +520,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS public func cancelPreviewGestures() { } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD index 067d915018..fe815d3255 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift index de84f76dcd..b78538b44d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoChatPaneNode/Sources/PeerInfoChatPaneNode.swift @@ -3,7 +3,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import TelegramStringFormatting @@ -141,7 +140,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - public init(context: AccountContext, chatLocation: ChatLocation, tag: MessageTags?, navigationController: @escaping () -> NavigationController?) { + public init(context: AccountContext, chatLocation: ChatLocation, tag: EngineMessage.Tags?, navigationController: @escaping () -> NavigationController?) { self.context = context self.navigationController = navigationController self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -224,7 +223,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro self.presentationDataDisposable?.dispose() } - public func ensureMessageIsVisible(id: MessageId) { + public func ensureMessageIsVisible(id: EngineMessage.Id) { } public func scrollToTop() -> Bool { @@ -238,7 +237,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro public func brieflyDisableTouchActions() { } - public func findLoadedMessage(id: MessageId) -> Message? { + public func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -254,7 +253,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScro public func cancelPreviewGestures() { } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD index 903ae1e85b..c4f5d3e07d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift index 06d3b681fe..141796f01a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift @@ -71,7 +71,7 @@ public final class PeerInfoCoverComponent: Component { func colors(context: AccountContext, isDark: Bool) -> (UIColor, UIColor)? { switch self { case let .peer(peer): - if let colors = peer._asPeer().profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: isDark) }) { + if let colors = peer.profileColor.flatMap({ context.peerNameColors.getProfile($0, dark: isDark) }) { let backgroundColor = colors.main let secondaryBackgroundColor = colors.secondary ?? colors.main return (backgroundColor, secondaryBackgroundColor) @@ -79,7 +79,7 @@ public final class PeerInfoCoverComponent: Component { return nil } case let .managedBot(peer): - if let color = peer._asPeer().nameColor { + if let color = peer.nameColor { let colors = calculateAvatarColors(context: context, explicitColorIndex: nil, peerId: peer.id, nameColor: color, icon: .none, theme: nil) if colors.count == 2 { return (colors[0], colors[1]) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD index 1e8570712a..3bd9654bb7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/AsyncDisplayKit", "//submodules/Display", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift index 964e7ba8b2..9fa63eaa2c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode/Sources/PeerInfoPaneNode.swift @@ -1,5 +1,4 @@ import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import UIKit @@ -109,12 +108,12 @@ public protocol PeerInfoPaneNode: ASDisplayNode { func scrollToTop() -> Bool func transferVelocity(_ velocity: CGFloat) func cancelPreviewGestures() - func findLoadedMessage(id: MessageId) -> Message? - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? func addToTransitionSurface(view: UIView) func updateHiddenMedia() func updateSelectedMessages(animated: Bool) - func ensureMessageIsVisible(id: MessageId) + func ensureMessageIsVisible(id: EngineMessage.Id) } public extension PeerInfoPaneNode { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift index ba7530fe28..01da82422c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent/Sources/PeerInfoRatingComponent.swift @@ -343,7 +343,7 @@ public final class PeerInfoRatingComponent: Component { } if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_outer", withExtension: "svg"), let data = try? Data(contentsOf: url) { - if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.borderColor) { + if let image = generateTintedImage(image: drawSvgImage(data: data, size: size, backgroundColor: nil, foregroundColor: nil, scale: 0.0, opaque: false), color: component.borderColor) { image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0) } } @@ -372,7 +372,7 @@ public final class PeerInfoRatingComponent: Component { } if let url = Bundle.main.url(forResource: "profile_level\(levelIndex)_inner", withExtension: "svg"), let data = try? Data(contentsOf: url) { - if let image = generateTintedImage(image: drawSvgImage(data, size, nil, nil, 0.0, false), color: component.backgroundColor) { + if let image = generateTintedImage(image: drawSvgImage(data: data, size: size, backgroundColor: nil, foregroundColor: nil, scale: 0.0, opaque: false), color: component.backgroundColor) { image.draw(in: CGRect(origin: CGPoint(x: 0.0, y: backgroundOffsetsY[levelIndex] ?? 0.0), size: size), blendMode: .normal, alpha: 1.0) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index c35771e65c..9bc36133b3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -31,6 +31,7 @@ swift_library( "//submodules/ChatListUI", "//submodules/TelegramUI/Components/Chat/ChatMessageItemView", "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/ChatScheduleTimeController", "//submodules/TelegramUI/Components/ChatTimerScreen", "//submodules/TelegramUI/Components/ChatTitleView", "//submodules/Components/ComponentDisplayAdapters", @@ -121,7 +122,6 @@ swift_library( "//submodules/TranslateUI", "//submodules/UndoUI", "//submodules/MediaPlayer:UniversalMediaPlayer", - "//submodules/WebSearchUI", "//submodules/WebUI", "//submodules/TelegramUI/Components/MultiScaleTextNode", "//submodules/GridMessageSelectionNode", @@ -142,6 +142,7 @@ swift_library( "//submodules/TelegramUI/Components/Settings/PeerNameColorItem", "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/TelegramUI/Components/TextLoadingEffect", + "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen", "//submodules/TelegramUI/Components/Settings/PeerSelectionScreen", "//submodules/TelegramUI/Components/ButtonComponent", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift index f85773787d..7f987fbf21 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCallListItem.swift @@ -6,17 +6,17 @@ import TelegramPresentationData import ItemListAddressItem import SwiftSignalKit import AccountContext -import Postbox +import TelegramCore import PeerInfoUI import ItemListUI final class PeerInfoScreenCallListItem: PeerInfoScreenItem { let id: AnyHashable - let messages: [Message] - + let messages: [EngineMessage] + init( id: AnyHashable, - messages: [Message] + messages: [EngineMessage] ) { self.id = id self.messages = messages diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift index d81eca2ccd..17a891e9f2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenCommentItem.swift @@ -167,7 +167,11 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { let height = textSize.height + verticalInset * 2.0 - transition.updateFrame(node: self.textNode, frame: textFrame) + if self.textNode.frame.size != textFrame.size { + self.textNode.frame = textFrame + } else { + transition.updateFrame(node: self.textNode, frame: textFrame) + } self.activateArea.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureEncryptionKeyItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureEncryptionKeyItem.swift index ed92e4af43..4a942dc0be 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureEncryptionKeyItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureEncryptionKeyItem.swift @@ -98,9 +98,9 @@ private final class PeerInfoScreenDisclosureEncryptionKeyItemNode: PeerInfoScree let arrowInset: CGFloat = 18.0 - let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 12.0), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 16.0), size: textSize) - let height = textSize.height + 24.0 + let height = textSize.height + 32.0 if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) { self.arrowNode.image = arrowImage @@ -118,7 +118,7 @@ private final class PeerInfoScreenDisclosureEncryptionKeyItemNode: PeerInfoScree let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift index 9dab9b2ccc..341aa2690a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift @@ -6,7 +6,6 @@ import TelegramPresentationData import ItemListPeerItem import SwiftSignalKit import AccountContext -import Postbox import TelegramCore import ItemListUI diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift index bfbc701147..b6527b86b1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift @@ -6,7 +6,6 @@ import TelegramPresentationData import ItemListPeerItem import SwiftSignalKit import AccountContext -import Postbox import TelegramCore import ItemListUI @@ -20,7 +19,7 @@ enum PeerInfoScreenMemberItemAction { final class PeerInfoScreenMemberItem: PeerInfoScreenItem { let id: AnyHashable let context: ItemListPeerItem.Context - let enclosingPeer: Peer? + let enclosingPeer: EnginePeer? let member: PeerInfoMember let badge: String? let isAccount: Bool @@ -31,7 +30,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem { init( id: AnyHashable, context: ItemListPeerItem.Context, - enclosingPeer: Peer?, + enclosingPeer: EnginePeer?, member: PeerInfoMember, badge: String? = nil, isAccount: Bool, @@ -149,9 +148,9 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { case .member: var canEditRank = false if item.member.id == item.context.accountPeerId { - if let channel = item.enclosingPeer as? TelegramChannel, channel.hasPermission(.editRank) { + if case let .channel(channel) = item.enclosingPeer, channel.hasPermission(.editRank) { canEditRank = true - } else if let group = item.enclosingPeer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { + } else if case let .legacyGroup(group) = item.enclosingPeer, !group.hasBannedPermission(.banEditRank) { canEditRank = true } } @@ -178,13 +177,13 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.accountPeerId, peer: item.enclosingPeer, member: item.member) var options: [ItemListPeerItemRevealOption] = [] - if actions.contains(.promote) && item.enclosingPeer is TelegramChannel { + if actions.contains(.promote), case .channel = item.enclosingPeer { options.append(ItemListPeerItemRevealOption(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: { item.action?(.promote) })) } if actions.contains(.restrict) { - if item.enclosingPeer is TelegramChannel { + if case .channel = item.enclosingPeer { options.append(ItemListPeerItemRevealOption(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: { item.action?(.restrict) })) @@ -220,7 +219,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { itemText = .presence } - let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: EnginePeer(item.member.peer), height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, animateFirstAvatarTransition: !item.isAccount, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in + let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: item.member.peer, height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, animateFirstAvatarTransition: !item.isAccount, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in }, removePeer: { _ in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 02862baafc..1bf164df4e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -9,7 +9,6 @@ import TelegramStringFormatting import ContextUI import TelegramCore import ChatListUI -import Postbox import StoryContainerScreen import AvatarNode @@ -182,7 +181,8 @@ public final class LoadingOverlayNode: ASDisplayNode { let interaction = ChatListNodeInteraction(context: context, animationCache: context.animationCache, animationRenderer: context.animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() - }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in }, editPeer: { _ in }, openWebApp: { _ in }, openPhotoSetup: { @@ -522,6 +522,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod }, performActiveSessionAction: { _, _ in }, + performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: { }, hideChatFolderUpdates: { @@ -560,10 +562,10 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod let isLoading = item.data.isLoading if !isLoading, !item.data.topMessages.isEmpty { - index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: item.data.topMessages[0].index)) + index = EngineChatList.Item.Index.chatList(EngineChatListIndex(pinningIndex: nil, messageIndex: item.data.topMessages[0].index)) messages = item.data.topMessages } else { - index = EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: item.data.peer.peerId, namespace: Namespaces.Message.Cloud, id: 1), timestamp: 0))) + index = EngineChatList.Item.Index.chatList(EngineChatListIndex(pinningIndex: nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: item.data.peer.peerId, namespace: Namespaces.Message.Cloud, id: 1), timestamp: 0))) messages = [] } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift index 45d5961580..5365b2f894 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGifPaneNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -25,17 +24,17 @@ private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6) private let mediaBadgeTextColor = UIColor.white private final class VisualMediaItemInteraction { - let openMessage: (Message) -> Void - let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void - let toggleSelection: (MessageId, Bool) -> Void - - var hiddenMedia: [MessageId: [Media]] = [:] - var selectedMessageIds: Set? - + let openMessage: (EngineRawMessage) -> Void + let openMessageContextActions: (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void + let toggleSelection: (EngineMessage.Id, Bool) -> Void + + var hiddenMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] + var selectedMessageIds: Set? + init( - openMessage: @escaping (Message) -> Void, - openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, - toggleSelection: @escaping (MessageId, Bool) -> Void + openMessage: @escaping (EngineRawMessage) -> Void, + openMessageContextActions: @escaping (EngineRawMessage, ASDisplayNode, CGRect, ContextGesture?) -> Void, + toggleSelection: @escaping (EngineMessage.Id, Bool) -> Void ) { self.openMessage = openMessage self.openMessageContextActions = openMessageContextActions @@ -58,9 +57,9 @@ private final class VisualMediaItemNode: ASDisplayNode { private let fetchStatusDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable() - private var resourceStatus: MediaResourceStatus? - - private var item: (VisualMediaItem, Media?, CGSize, CGSize?)? + private var resourceStatus: EngineMediaResourceStatus? + + private var item: (VisualMediaItem, EngineRawMedia?, CGSize, CGSize?)? private var theme: PresentationTheme? private var hasVisibility: Bool = false @@ -118,8 +117,8 @@ private final class VisualMediaItemNode: ASDisplayNode { if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { if case .tap = gesture { if let (item, _, _, _) = self.item { - var media: Media? - for value in item.message.media { + var media: EngineRawMedia? + for value in item.message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -131,7 +130,7 @@ private final class VisualMediaItemNode: ASDisplayNode { if let media = media { if let file = media as? TelegramMediaFile { - if isMediaStreamable(message: item.message, media: file) { + if isMediaStreamable(message: EngineMessage(item.message), media: file) { self.interaction.openMessage(item.message) } else { self.progressPressed() @@ -150,9 +149,9 @@ private final class VisualMediaItemNode: ASDisplayNode { guard let message = self.item?.0.message else { return } - - var media: Media? - for value in message.media { + + var media: EngineRawMedia? + for value in message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -183,8 +182,8 @@ private final class VisualMediaItemNode: ASDisplayNode { return } self.theme = theme - var media: Media? - for value in item.message.media { + var media: EngineRawMedia? + for value in item.message.effectiveMedia { if let image = value as? TelegramMediaImage { media = image break @@ -235,7 +234,7 @@ private final class VisualMediaItemNode: ASDisplayNode { if let strongSelf = self, let (item, _, _, _) = strongSelf.item { strongSelf.resourceStatus = status - let isStreamable = isMediaStreamable(message: item.message, media: file) + let isStreamable = isMediaStreamable(message: EngineMessage(item.message), media: file) var statusState: RadialStatusNodeState = .none if isStreamable || file.isAnimated { @@ -417,16 +416,16 @@ private final class VisualMediaItemNode: ASDisplayNode { } private final class VisualMediaItem { - let message: Message + let message: EngineRawMessage let dimensions: CGSize let aspectRatio: CGFloat - - init(message: Message) { + + init(message: EngineRawMessage) { self.message = message var aspectRatio: CGFloat = 1.0 var dimensions = CGSize(width: 100.0, height: 100.0) - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if let dimensionsValue = file.dimensions, dimensions.height > 1 { dimensions = dimensionsValue.cgSize @@ -488,7 +487,7 @@ private final class FloatingHeaderNode: ASDisplayNode { } } -private func tagMaskForType(_ type: PeerInfoGifPaneNode.ContentType) -> MessageTags { +private func tagMaskForType(_ type: PeerInfoGifPaneNode.ContentType) -> EngineMessage.Tags { switch type { case .photoOrVideo: return .photoOrVideo @@ -616,7 +615,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe } private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let chatLocation: ChatLocation private let chatLocationContextHolder: Atomic private let chatControllerInteraction: ChatControllerInteraction @@ -651,7 +650,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:] private var numberOfItemsToRequest: Int = 50 - private var currentView: MessageHistoryView? + private var currentView: EngineRawMessageHistoryView? private var isRequestingView: Bool = false private var isFirstHistoryView: Bool = true @@ -667,7 +666,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe return 0.0 } - init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, contentType: ContentType) { + init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: EnginePeer.Id, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, contentType: ContentType) { self.context = context self.peerId = peerId self.chatLocation = chatLocation @@ -704,6 +703,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe } self.scrollNode.view.scrollsToTop = false self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false self.addSubnode(self.scrollNode) self.addSubnode(self.floatingHeaderNode) @@ -714,7 +714,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe guard let strongSelf = self else { return } - var hiddenMedia: [MessageId: [Media]] = [:] + var hiddenMedia: [EngineMessage.Id: [EngineRawMedia]] = [:] for id in ids { if case let .chat(accountId, messageId, media) = id, accountId == strongSelf.context.account.id { hiddenMedia[messageId] = [media] @@ -751,7 +751,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe self.hiddenMediaDisposable?.dispose() } - func ensureMessageIsVisible(id: MessageId) { + func ensureMessageIsVisible(id: EngineMessage.Id) { let activeRect = self.scrollNode.bounds for item in self.mediaItems { if item.message.id == id { @@ -781,7 +781,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe })) } - private func updateHistory(view: MessageHistoryView, updateType: ViewUpdateType) { + private func updateHistory(view: EngineRawMessageHistoryView, updateType: EngineViewUpdateType) { self.currentView = view switch updateType { @@ -816,10 +816,10 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe } } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { for item in self.mediaItems { if item.message.id == id { - return item.message + return EngineMessage(item.message) } } return nil @@ -881,7 +881,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe } } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for item in self.mediaItems { if item.message.id == messageId { if let itemNode = self.visibleMediaItems[item.message.stableId] { @@ -988,7 +988,7 @@ final class PeerInfoGifPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScrollViewDe let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) - var headerItem: Message? + var headerItem: EngineRawMessage? var validIds = Set() if minVisibleIndex <= maxVisibleIndex { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift index b1600c7312..cb27d285c6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -25,23 +24,23 @@ private struct GroupsInCommonListTransaction { private struct GroupsInCommonListEntry: Comparable, Identifiable { var index: Int - var peer: Peer - - var stableId: PeerId { + var peer: EnginePeer + + var stableId: EnginePeer.Id { return self.peer.id } - + static func ==(lhs: GroupsInCommonListEntry, rhs: GroupsInCommonListEntry) -> Bool { - return lhs.peer.isEqual(rhs.peer) + return lhs.peer == rhs.peer } static func <(lhs: GroupsInCommonListEntry, rhs: GroupsInCommonListEntry) -> Bool { return lhs.index < rhs.index } - func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) -> ListViewItem { let peer = self.peer - return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: EnginePeer(self.peer), presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: { + return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: self.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: { openPeer(peer) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in @@ -51,7 +50,7 @@ private struct GroupsInCommonListEntry: Comparable, Identifiable { } } -private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to toEntries: [GroupsInCommonListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> GroupsInCommonListTransaction { +private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to toEntries: [GroupsInCommonListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) -> GroupsInCommonListTransaction { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -63,9 +62,9 @@ private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let chatControllerInteraction: ChatControllerInteraction - private let openPeerContextAction: (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void + private let openPeerContextAction: (Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void private let groupsInCommonContext: GroupsInCommonContext weak var parentController: ViewController? @@ -106,7 +105,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { private var disposable: Disposable? - init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void, groupsInCommonContext: GroupsInCommonContext) { + init(context: AccountContext, peerId: EnginePeer.Id, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void, groupsInCommonContext: GroupsInCommonContext) { self.context = context self.peerId = peerId self.chatControllerInteraction = chatControllerInteraction @@ -180,7 +179,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { self.disposable?.dispose() } - func ensureMessageIsVisible(id: MessageId) { + func ensureMessageIsVisible(id: EngineMessage.Id) { } func scrollToTop() -> Bool { @@ -229,11 +228,11 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { var entries: [GroupsInCommonListEntry] = [] for peer in state.peers { if let peer = peer.peer { - entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer)) + entries.append(GroupsInCommonListEntry(index: entries.count, peer: EnginePeer(peer))) } } let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in - self?.chatControllerInteraction.openPeer(EnginePeer(peer), .default, nil, .default) + self?.chatControllerInteraction.openPeer(peer, .default, nil, .default) }, openPeerContextAction: { [weak self] peer, node, gesture in self?.openPeerContextAction(false, peer, node, gesture) }) @@ -292,7 +291,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { transition.updateFrame(view: self.listMaskView, frame: listMaskFrame) } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -308,7 +307,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { func cancelPreviewGestures() { } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift index 5faf856184..84d5d770e4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoListPaneNode.swift @@ -3,7 +3,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import PresentationDataUtils import AccountContext @@ -23,7 +22,7 @@ import PeerInfoPaneNode final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let chatLocation: ChatLocation private let chatLocationContextHolder: Atomic private let chatControllerInteraction: ChatControllerInteraction @@ -40,8 +39,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { return self.ready.get() } - private let selectedMessagesPromise = Promise?>(nil) - private var selectedMessages: Set? { + private let selectedMessagesPromise = Promise?>(nil) + private var selectedMessages: Set? { didSet { if self.selectedMessages != oldValue { self.selectedMessagesPromise.set(.single(self.selectedMessages)) @@ -69,7 +68,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { return 0.0 } - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: MessageTags) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatControllerInteraction: ChatControllerInteraction, peerId: EnginePeer.Id, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, tagMask: EngineMessage.Tags) { self.context = context self.peerId = peerId self.chatLocation = chatLocation @@ -166,15 +165,15 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { let presentationData = context.sharedContext.currentPresentationData.with { $0 } switch tagMask { - case MessageTags.file: + case EngineMessage.Tags.file: return PeerInfoStatusData(text: presentationData.strings.SharedMedia_FileCount(Int32(count)), isActivity: false, key: .files) - case MessageTags.music: + case EngineMessage.Tags.music: return PeerInfoStatusData(text: presentationData.strings.SharedMedia_MusicCount(Int32(count)), isActivity: false, key: .music) - case MessageTags.voiceOrInstantVideo: + case EngineMessage.Tags.voiceOrInstantVideo: return PeerInfoStatusData(text: presentationData.strings.SharedMedia_VoiceMessageCount(Int32(count)), isActivity: false, key: .voice) - case MessageTags.webPage: + case EngineMessage.Tags.webPage: return PeerInfoStatusData(text: presentationData.strings.SharedMedia_LinkCount(Int32(count)), isActivity: false, key: .links) - case MessageTags.polls: + case EngineMessage.Tags.polls: return PeerInfoStatusData(text: presentationData.strings.SharedMedia_PollCount(Int32(count)), isActivity: false, key: .links) default: return nil @@ -188,7 +187,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.playlistPreloadDisposable?.dispose() } - func ensureMessageIsVisible(id: MessageId) { + func ensureMessageIsVisible(id: EngineMessage.Id) { } @@ -276,7 +275,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { let settings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings)?.get(MusicPlaybackSettings.self) ?? MusicPlaybackSettings.defaultSettings transaction.updateSharedData(ApplicationSpecificSharedDataKeys.musicPlaybackSettings, { _ in - return PreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) + return EnginePreferencesEntry(settings.withUpdatedVoicePlaybackRate(rate)) }) return rate } @@ -371,7 +370,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { } if let id = state.id as? PeerMessagesMediaPlaylistItemId, let playlistLocation = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .messages(chatLocation, _, _) = playlistLocation { if type == .music { - let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: strongSelf.context, chatLocation: .peer(id: id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(MessageTags.music)) + let signal = strongSelf.context.sharedContext.messageFromPreloadedChatHistoryViewForLocation(id: id.messageId, location: ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: .id(id.messageId)), count: 60, highlight: true, setupReply: false), id: 0), context: strongSelf.context, chatLocation: .peer(id: id.messageId.peerId), subject: nil, chatLocationContextHolder: Atomic(value: nil), tag: .tag(EngineMessage.Tags.music)) var cancelImpl: (() -> Void)? let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } @@ -482,7 +481,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { self.listNode.scrollEnabled = !isScrollingLockedAtTop } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { self.listNode.messageInCurrentHistoryView(id) } @@ -503,11 +502,12 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode { func cancelPreviewGestures() { } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + let rawMedia = media._asMedia() self.listNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ListMessageNode { - if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { + if let result = itemNode.transitionNode(id: messageId, media: rawMedia, adjustRect: false) { transitionNode = result } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift index 7ba0773cb5..82f9ed6b50 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoMembersPane.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -37,7 +36,7 @@ enum PeerMembersListAction { private enum PeerMembersListEntryStableId: Hashable { case addMember - case peer(PeerId) + case peer(EnginePeer.Id) } private enum PeerMembersListEntry: Comparable, Identifiable { @@ -89,7 +88,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: PresentationData, enclosingPeer: Peer, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, contextAction: ((PeerInfoMember, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, enclosingPeer: EnginePeer, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, contextAction: ((PeerInfoMember, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { switch self { case let .addMember(_, text): return ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: PresentationResourcesItemList.addPersonIcon(presentationData.theme), title: text, alwaysPlain: true, sectionId: 0, height: .compactPeerList, color: .accent, editing: false, action: { @@ -110,9 +109,9 @@ private enum PeerMembersListEntry: Comparable, Identifiable { case .member: var canEditRank = false if member.id == context.account.peerId { - if let channel = enclosingPeer as? TelegramChannel, channel.hasPermission(.editRank) { + if case let .channel(channel) = enclosingPeer, channel.hasPermission(.editRank) { canEditRank = true - } else if let group = enclosingPeer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) { + } else if case let .legacyGroup(group) = enclosingPeer, !group.hasBannedPermission(.banEditRank) { canEditRank = true } } @@ -139,13 +138,13 @@ private enum PeerMembersListEntry: Comparable, Identifiable { let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: enclosingPeer, member: member) var options: [ItemListPeerItemRevealOption] = [] - if actions.contains(.promote) && enclosingPeer is TelegramChannel { + if actions.contains(.promote), case .channel = enclosingPeer { options.append(ItemListPeerItemRevealOption(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: { action(member, .promote) })) } if actions.contains(.restrict) { - if enclosingPeer is TelegramChannel { + if case .channel = enclosingPeer { options.append(ItemListPeerItemRevealOption(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: { action(member, .restrict) })) @@ -165,7 +164,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { } var status: ContactsPeerItemStatus = .presence(presence, presentationData.dateTimeFormat) - if let user = member.peer as? TelegramUser, let botInfo = user.botInfo { + if case let .user(user) = member.peer, let botInfo = user.botInfo { let botStatus: String if botInfo.flags.contains(.hasAccessToChatHistory) { botStatus = presentationData.strings.Bot_GroupStatusReadsHistory @@ -188,7 +187,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { displayOrder: presentationData.nameDisplayOrder, context: context, peerMode: .memberList, - peer: .peer(peer: EnginePeer(member.peer), chatPeer: EnginePeer(member.peer)), + peer: .peer(peer: member.peer, chatPeer: member.peer), status: status, rightLabelText: label.flatMap { .init(text: $0, color: labelColor, hasBackground: labelBackground) }, enabled: true, @@ -268,7 +267,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable { } } -private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toEntries: [PeerMembersListEntry], context: AccountContext, presentationData: PresentationData, enclosingPeer: Peer, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, contextAction: ((PeerInfoMember, ASDisplayNode, ContextGesture?) -> Void)?) -> PeerMembersListTransaction { +private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toEntries: [PeerMembersListEntry], context: AccountContext, presentationData: PresentationData, enclosingPeer: EnginePeer, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, contextAction: ((PeerInfoMember, ASDisplayNode, ContextGesture?) -> Void)?) -> PeerMembersListTransaction { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -290,7 +289,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { private let listMaskView: UIImageView private let listNode: ListView private var currentEntries: [PeerMembersListEntry] = [] - private var enclosingPeer: Peer? + private var enclosingPeer: EnginePeer? private var currentState: PeerInfoMembersState? private var canLoadMore: Bool = false private var enqueuedTransactions: [PeerMembersListTransaction] = [] @@ -317,7 +316,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { private var disposable: Disposable? - init(context: AccountContext, peerId: PeerId, membersContext: PeerInfoMembersContext, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, membersContext: PeerInfoMembersContext, addMemberAction: @escaping () -> Void, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void) { self.context = context self.membersContext = membersContext self.addMemberAction = addMemberAction @@ -358,9 +357,9 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { return } - strongSelf.enclosingPeer = enclosingPeer._asPeer() + strongSelf.enclosingPeer = enclosingPeer strongSelf.currentState = state - strongSelf.updateState(enclosingPeer: enclosingPeer._asPeer(), state: state, presentationData: presentationData) + strongSelf.updateState(enclosingPeer: enclosingPeer, state: state, presentationData: presentationData) }) self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in @@ -394,7 +393,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { self.disposable?.dispose() } - func ensureMessageIsVisible(id: MessageId) { + func ensureMessageIsVisible(id: EngineMessage.Id) { } func scrollToTop() -> Bool { @@ -439,7 +438,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { } } - private func updateState(enclosingPeer: Peer, state: PeerInfoMembersState, presentationData: PresentationData) { + private func updateState(enclosingPeer: EnginePeer, state: PeerInfoMembersState, presentationData: PresentationData) { var entries: [PeerMembersListEntry] = [] if state.canAddMembers { entries.append(.addMember(presentationData.theme, presentationData.strings.GroupInfo_AddParticipant)) @@ -542,7 +541,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { transition.updateFrame(view: self.listMaskView, frame: listMaskFrame) } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -558,7 +557,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { func cancelPreviewGestures() { } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedPeersPane.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedPeersPane.swift index 31e13a2fd3..c518841d85 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedPeersPane.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/Panes/PeerInfoRecommendedPeersPane.swift @@ -5,7 +5,6 @@ import Display import ComponentFlow import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -31,7 +30,7 @@ private struct RecommendedPeersListTransaction { private enum RecommendedPeersListEntryStableId: Hashable { case addMember - case peer(PeerId) + case peer(EnginePeer.Id) } private enum RecommendedPeersListEntry: Comparable, Identifiable { @@ -65,7 +64,7 @@ private enum RecommendedPeersListEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: PresentationData, action: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, action: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) -> ListViewItem { switch self { case let .peer(_, _, peer, subscribers): let text: ItemListPeerItemText @@ -85,13 +84,13 @@ private enum RecommendedPeersListEntry: Comparable, Identifiable { }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, contextAction: { node, gesture in - openPeerContextAction(peer._asPeer(), node, gesture) + openPeerContextAction(peer, node, gesture) }, hasTopStripe: false, noInsets: true, noCorners: true, style: .plain, disableInteractiveTransitionIfNecessary: true) } } } -private func preparedTransition(from fromEntries: [RecommendedPeersListEntry], to toEntries: [RecommendedPeersListEntry], context: AccountContext, presentationData: PresentationData, action: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) -> RecommendedPeersListTransaction { +private func preparedTransition(from fromEntries: [RecommendedPeersListEntry], to toEntries: [RecommendedPeersListEntry], context: AccountContext, presentationData: PresentationData, action: @escaping (EnginePeer) -> Void, openPeerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void) -> RecommendedPeersListTransaction { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -116,7 +115,7 @@ extension RecommendedBots: RecommendedPeers { final class PeerInfoRecommendedPeersPaneNode: ASDisplayNode, PeerInfoPaneNode { private let context: AccountContext private let chatControllerInteraction: ChatControllerInteraction - private let openPeerContextAction: (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void + private let openPeerContextAction: (Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void weak var parentController: ViewController? @@ -152,7 +151,7 @@ final class PeerInfoRecommendedPeersPaneNode: ASDisplayNode, PeerInfoPaneNode { private var disposable: Disposable? - init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, chatControllerInteraction: ChatControllerInteraction, openPeerContextAction: @escaping (Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void) { self.context = context self.chatControllerInteraction = chatControllerInteraction self.openPeerContextAction = openPeerContextAction @@ -208,7 +207,7 @@ final class PeerInfoRecommendedPeersPaneNode: ASDisplayNode, PeerInfoPaneNode { self.disposable?.dispose() } - func ensureMessageIsVisible(id: MessageId) { + func ensureMessageIsVisible(id: EngineMessage.Id) { } func scrollToTop() -> Bool { @@ -455,7 +454,7 @@ final class PeerInfoRecommendedPeersPaneNode: ASDisplayNode, PeerInfoPaneNode { }) } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -471,7 +470,7 @@ final class PeerInfoRecommendedPeersPaneNode: ASDisplayNode, PeerInfoPaneNode { func cancelPreviewGestures() { } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarListNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarListNode.swift index 00af60cf77..cb5c9f4df8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarListNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import TelegramPresentationData import PeerInfoAvatarListNode import SwiftSignalKit -import Postbox import TelegramCore import ContextUI import AccountContext @@ -23,7 +22,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { let isReady = Promise() - var arguments: (Peer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? + var arguments: (EnginePeer?, Int64?, EngineMessageHistoryThread.Info?, PresentationTheme, CGFloat, Bool)? var item: PeerInfoAvatarListItem? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? @@ -128,7 +127,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { } } - func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: Peer?, isForum: Bool, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, avatarSize: CGFloat, isExpanded: Bool, peer: EnginePeer?, isForum: Bool, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded) self.maskNode.isForum = isForum self.pinchSourceNode.update(size: size, transition: transition) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift index aaf95233c6..89dc09d0d0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift @@ -13,7 +13,6 @@ import AvatarVideoNode import SwiftSignalKit import TelegramUniversalVideoContent import PeerInfoAvatarListNode -import Postbox import TelegramCore import EmojiStatusComponent import GalleryUI @@ -101,7 +100,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { self.playbackStartDisposable.dispose() } - func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme, peer: Peer?) { + func updateStoryView(transition: ContainedViewLayoutTransition, theme: PresentationTheme, peer: EnginePeer?) { var colors = AvatarNode.Colors(theme: theme) let regularNavigationContentsSecondaryColor: UIColor @@ -160,7 +159,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } var isForum = false - if let peer, let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if let peer, case let .channel(channel) = peer, channel.isForumOrMonoForum { isForum = true } @@ -210,7 +209,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } private struct Params { - let peer: Peer? + let peer: EnginePeer? let threadId: Int64? let threadInfo: EngineMessageHistoryThread.Info? let item: PeerInfoAvatarListItem? @@ -219,7 +218,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { let isExpanded: Bool let isSettings: Bool - init(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { + init(peer: EnginePeer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { self.peer = peer self.threadId = threadId self.threadInfo = threadInfo @@ -251,7 +250,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { ) } - func update(peer: Peer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { + func update(peer: EnginePeer?, threadId: Int64?, threadInfo: EngineMessageHistoryThread.Info?, item: PeerInfoAvatarListItem?, theme: PresentationTheme, avatarSize: CGFloat, isExpanded: Bool, isSettings: Bool) { self.params = Params(peer: peer, threadId: threadId, threadInfo: threadInfo, item: item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: isSettings) if let peer = peer { @@ -282,7 +281,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { } self.avatarNode.imageNode.animateFirstTransition = !isSettings - self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) + self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true) if let threadInfo = threadInfo { self.avatarNode.isHidden = true @@ -327,7 +326,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { var isForum = false let avatarCornerRadius: CGFloat - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if case let .channel(channel) = peer, channel.isForumOrMonoForum { avatarCornerRadius = floor(avatarSize * 0.25) isForum = true } else { @@ -402,7 +401,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0)) markupNode.updateVisibility(true) } else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoBirthdayOverlay.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoBirthdayOverlay.swift index d6e4eec46c..16fdcde910 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoBirthdayOverlay.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoBirthdayOverlay.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import ConfettiEffect @@ -98,7 +97,7 @@ final class PeerInfoBirthdayOverlay: ASDisplayNode { let animationNode = DefaultAnimatedStickerNodeImpl() let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.media.resource, fitzModifier: nil) - let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.media.resource.id) + let pathPrefix = self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.media.resource.id)) animationNode.setup(source: source, width: Int(pixelSize.width), height: Int(pixelSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) self.addSubnode(animationNode) @@ -132,7 +131,7 @@ final class PeerInfoBirthdayOverlay: ASDisplayNode { let animationNode = DefaultAnimatedStickerNodeImpl() let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.media.resource, fitzModifier: nil) - let pathPrefix: String? = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.media.resource.id) + let pathPrefix: String? = self.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.media.resource.id)) animationNode.setup(source: source, width: Int(pixelSize.width), height: Int(pixelSize.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) self.addSubnode(animationNode) @@ -179,7 +178,7 @@ final class PeerInfoBirthdayOverlay: ASDisplayNode { context.engine.stickers.loadedStickerPack(reference: .name("FestiveFontEmoji"), forceActualized: false) ) |> mapToSignal { animatedEmoji, numbers -> Signal in - var signals: [Signal] = [] + var signals: [Signal] = [] if case let .result(_, items, _) = animatedEmoji { for item in items { let indexKeys = item.getStringRepresentationsOfIndexKeys() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 9a6e073c4e..96e55f3fa1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -383,9 +383,9 @@ final class PeerInfoPersonalChannelData: Equatable { } final class PeerInfoScreenData { - let peer: Peer? - let chatPeer: Peer? - let savedMessagesPeer: Peer? + let peer: EnginePeer? + let chatPeer: EnginePeer? + let savedMessagesPeer: EnginePeer? let cachedData: CachedPeerData? let status: PeerInfoStatusData? let peerNotificationSettings: TelegramPeerNotificationSettings? @@ -393,8 +393,8 @@ final class PeerInfoScreenData { let globalNotificationSettings: EngineGlobalNotificationSettings? let availablePanes: [PeerInfoPaneKey] let groupsInCommon: GroupsInCommonContext? - let linkedDiscussionPeer: Peer? - let linkedMonoforumPeer: Peer? + let linkedDiscussionPeer: EnginePeer? + let linkedMonoforumPeer: EnginePeer? let members: PeerInfoMembersData? let storyListContext: StoryListContext? let storyArchiveListContext: StoryListContext? @@ -425,6 +425,7 @@ final class PeerInfoScreenData { let savedMusicContext: ProfileSavedMusicContext? let savedMusicState: ProfileSavedMusicContext.State? let managedByBot: EnginePeer? + let businessConnectedBot: EnginePeer? let _isContact: Bool var forceIsContact: Bool = false @@ -438,9 +439,9 @@ final class PeerInfoScreenData { } init( - peer: Peer?, - chatPeer: Peer?, - savedMessagesPeer: Peer?, + peer: EnginePeer?, + chatPeer: EnginePeer?, + savedMessagesPeer: EnginePeer?, cachedData: CachedPeerData?, status: PeerInfoStatusData?, peerNotificationSettings: TelegramPeerNotificationSettings?, @@ -449,8 +450,8 @@ final class PeerInfoScreenData { isContact: Bool, availablePanes: [PeerInfoPaneKey], groupsInCommon: GroupsInCommonContext?, - linkedDiscussionPeer: Peer?, - linkedMonoforumPeer: Peer?, + linkedDiscussionPeer: EnginePeer?, + linkedMonoforumPeer: EnginePeer?, members: PeerInfoMembersData?, storyListContext: StoryListContext?, storyArchiveListContext: StoryListContext?, @@ -480,7 +481,8 @@ final class PeerInfoScreenData { webAppPermissions: WebAppPermissionsState?, savedMusicContext: ProfileSavedMusicContext?, savedMusicState: ProfileSavedMusicContext.State?, - managedByBot: EnginePeer? + managedByBot: EnginePeer?, + businessConnectedBot: EnginePeer? ) { self.peer = peer self.chatPeer = chatPeer @@ -525,6 +527,7 @@ final class PeerInfoScreenData { self.savedMusicContext = savedMusicContext self.savedMusicState = savedMusicState self.managedByBot = managedByBot + self.businessConnectedBot = businessConnectedBot } } @@ -931,6 +934,20 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId) + let businessConnectedBot = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: context.account.peerId) + ) + |> map { bot -> EnginePeer.Id? in + return bot?.id + } + |> distinctUntilChanged + |> mapToSignal { botPeerId -> Signal in + guard let botPeerId else { + return .single(nil) + } + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId)) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), accountsAndPeers, @@ -940,7 +957,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, combineLatest(notificationExceptions, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get()), combineLatest(context.account.viewTracker.featuredStickerPacks(), archivedStickerPacks), hasPassport, - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)), context.engine.notices.getServerProvidedSuggestions(), context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), @@ -956,9 +973,10 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, bots, peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: true), starsState, - tonState + tonState, + businessConnectedBot ) - |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState, tonState -> PeerInfoScreenData in + |> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState, tonState, businessConnectedBot -> PeerInfoScreenData in let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications let (featuredStickerPacks, archivedStickerPacks) = stickerPacks @@ -970,7 +988,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, }) var enableQRLogin = false - let appConfiguration = accountPreferences.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) + let appConfiguration = accountPreferences?.get(AppConfiguration.self) if let appConfiguration, let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR { enableQRLogin = true } @@ -1006,8 +1024,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, ) return PeerInfoScreenData( - peer: peer, - chatPeer: peer, + peer: peer.flatMap(EnginePeer.init), + chatPeer: peer.flatMap(EnginePeer.init), savedMessagesPeer: nil, cachedData: peerView.cachedData, status: nil, @@ -1048,7 +1066,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, webAppPermissions: nil, savedMusicContext: nil, savedMusicState: nil, - managedByBot: nil + managedByBot: nil, + businessConnectedBot: businessConnectedBot ) } } @@ -1120,7 +1139,8 @@ func peerInfoScreenData( webAppPermissions: nil, savedMusicContext: nil, savedMusicState: nil, - managedByBot: nil + managedByBot: nil, + businessConnectedBot: nil )) case let .user(userPeerId, secretChatId, kind): let groupsInCommon: GroupsInCommonContext? @@ -1131,14 +1151,7 @@ func peerInfoScreenData( } else { groupsInCommon = nil } - - let recommendedBots: Signal - if case .bot = kind { - recommendedBots = context.engine.peers.recommendedBots(peerId: userPeerId) - } else { - recommendedBots = .single(nil) - } - + let premiumGiftOptions: Signal<[PremiumGiftCodeOption], NoError> let profileGiftsContext: ProfileGiftsContext? let profileGiftsCollectionsContext: ProfileGiftsCollectionsContext? @@ -1260,7 +1273,7 @@ func peerInfoScreenData( |> distinctUntilChanged var secretChatKeyFingerprint: Signal = .single(nil) - if let secretChatId = secretChatId { + if let secretChatId { secretChatKeyFingerprint = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.SecretChatKeyFingerprint(id: secretChatId)) } @@ -1290,21 +1303,7 @@ func peerInfoScreenData( } else { hasStoryArchive = .single(false) } - - var botPreviewStoryListContext: StoryListContext? - let hasBotPreviewItems: Signal - if case .bot = kind { - let botPreviewStoryListContextValue = BotPreviewStoryListContext(account: context.account, engine: context.engine, peerId: peerId, language: nil, assumeEmpty: false) - botPreviewStoryListContext = botPreviewStoryListContextValue - hasBotPreviewItems = botPreviewStoryListContextValue.state - |> map { state in - return !state.items.isEmpty - } - |> distinctUntilChanged - } else { - hasBotPreviewItems = .single(false) - } - + let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> Bool in return peer?.isPremium ?? false @@ -1387,66 +1386,109 @@ func peerInfoScreenData( hasSavedMessageTags = .single(false) } - let starsRevenueContextAndState = combineLatest( - context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> distinctUntilChanged, - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId)) - |> distinctUntilChanged - ) - |> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in - var canViewStarsRevenue = canViewRevenue - if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests { - canViewStarsRevenue = true - } - #if DEBUG - canViewStarsRevenue = peerId != context.account.peerId - #endif + let recommendedBots: Signal + var botPreviewStoryListContext: StoryListContext? + let hasBotPreviewItems: Signal + let starsRevenueContextAndState: Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> + let revenueContextAndState: Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> + let webAppPermissions: Signal + if case .bot = kind { + recommendedBots = context.engine.peers.recommendedBots(peerId: userPeerId) - guard canViewStarsRevenue else { - return .single((nil, nil)) + let botPreviewStoryListContextValue = BotPreviewStoryListContext(account: context.account, engine: context.engine, peerId: peerId, language: nil, assumeEmpty: false) + botPreviewStoryListContext = botPreviewStoryListContextValue + hasBotPreviewItems = botPreviewStoryListContextValue.state + |> map { state in + return !state.items.isEmpty } - let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: false) - return starsRevenueStatsContext.state - |> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in - return (starsRevenueStatsContext, state.stats) - } - } - - let revenueContextAndState = combineLatest( - context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> distinctUntilChanged, - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId)) |> distinctUntilChanged - ) - |> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in - var canViewRevenue = canViewRevenue - if let peer, case let .user(user) = peer, let _ = user.botInfo, context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests { - canViewRevenue = true + + starsRevenueContextAndState = combineLatest( + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> distinctUntilChanged, + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId)) + |> distinctUntilChanged + ) + |> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in + var canViewStarsRevenue = canViewRevenue + if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests { + canViewStarsRevenue = true + } + #if DEBUG + canViewStarsRevenue = peerId != context.account.peerId + #endif + + guard canViewStarsRevenue else { + return .single((nil, nil)) + } + let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: false) + return starsRevenueStatsContext.state + |> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in + return (starsRevenueStatsContext, state.stats) + } } - #if DEBUG - canViewRevenue = peerId != context.account.peerId - #endif - guard canViewRevenue else { - return .single((nil, nil)) + + revenueContextAndState = combineLatest( + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> distinctUntilChanged, + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId)) + |> distinctUntilChanged + ) + |> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in + var canViewRevenue = canViewRevenue + if let peer, case let .user(user) = peer, let _ = user.botInfo, context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests { + canViewRevenue = true + } + #if DEBUG + canViewRevenue = peerId != context.account.peerId + #endif + guard canViewRevenue else { + return .single((nil, nil)) + } + let revenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: true) + return revenueStatsContext.state + |> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in + return (revenueStatsContext, state.stats) + } } - let revenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: true) - return revenueStatsContext.state - |> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in - return (revenueStatsContext, state.stats) + + webAppPermissions = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer, case let .user(user) = peer, let _ = user.botInfo { + return webAppPermissionsState(context: context, peerId: peerId) + } else { + return .single(nil) + } } + } else { + recommendedBots = .single(nil) + hasBotPreviewItems = .single(false) + starsRevenueContextAndState = .single((nil, nil)) + revenueContextAndState = .single((nil, nil)) + webAppPermissions = .single(nil) } - - let webAppPermissions: Signal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) - |> mapToSignal { peer -> Signal in - if let peer, case let .user(user) = peer, let _ = user.botInfo { - return webAppPermissionsState(context: context, peerId: peerId) - } else { - return .single(nil) - } - } - + let savedMusicContext = ProfileSavedMusicContext(account: context.account, peerId: peerId) - + + let businessConnectedBot: Signal + if isMyProfile { + businessConnectedBot = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: context.account.peerId) + ) + |> map { bot -> EnginePeer.Id? in + return bot?.id + } + |> distinctUntilChanged + |> mapToSignal { botPeerId -> Signal in + guard let botPeerId else { + return .single(nil) + } + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId)) + } + } else { + businessConnectedBot = .single(nil) + } + return combineLatest( context.account.viewTracker.peerView(peerId, updateData: true), peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: sharedMediaFromForumTopic), @@ -1468,9 +1510,10 @@ func peerInfoScreenData( revenueContextAndState, premiumGiftOptions, webAppPermissions, - savedMusicContext.state + savedMusicContext.state, + businessConnectedBot ) - |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, recommendedBots, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions, savedMusicState -> PeerInfoScreenData in + |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, recommendedBots, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions, savedMusicState, businessConnectedBot -> PeerInfoScreenData in var availablePanes = availablePanes if isMyProfile { availablePanes?.insert(.stories, at: 0) @@ -1574,9 +1617,9 @@ func peerInfoScreenData( } return PeerInfoScreenData( - peer: peer, - chatPeer: peerView.peers[peerId], - savedMessagesPeer: savedMessagesPeer?._asPeer(), + peer: peer.flatMap(EnginePeer.init), + chatPeer: peerView.peers[peerId].flatMap(EnginePeer.init), + savedMessagesPeer: savedMessagesPeer, cachedData: peerView.cachedData, status: status, peerNotificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings, @@ -1616,7 +1659,8 @@ func peerInfoScreenData( webAppPermissions: webAppPermissions, savedMusicContext: savedMusicContext, savedMusicState: savedMusicState, - managedByBot: managedByBot + managedByBot: managedByBot, + businessConnectedBot: businessConnectedBot ) } case .channel: @@ -1789,14 +1833,14 @@ func peerInfoScreenData( } } - var discussionPeer: Peer? + var discussionPeer: EnginePeer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { - discussionPeer = peer + discussionPeer = EnginePeer(peer) } - - var monoforumPeer: Peer? + + var monoforumPeer: EnginePeer? if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { - monoforumPeer = peerView.peers[linkedMonoforumId] + monoforumPeer = peerView.peers[linkedMonoforumId].flatMap(EnginePeer.init) } var canManageInvitations = false @@ -1820,8 +1864,8 @@ func peerInfoScreenData( } return PeerInfoScreenData( - peer: peerView.peers[peerId], - chatPeer: peerView.peers[peerId], + peer: peerView.peers[peerId].flatMap(EnginePeer.init), + chatPeer: peerView.peers[peerId].flatMap(EnginePeer.init), savedMessagesPeer: nil, cachedData: peerView.cachedData, status: status, @@ -1862,7 +1906,8 @@ func peerInfoScreenData( webAppPermissions: nil, savedMusicContext: nil, savedMusicState: nil, - managedByBot: nil + managedByBot: nil, + businessConnectedBot: nil ) } case let .group(groupId): @@ -1891,7 +1936,7 @@ func peerInfoScreenData( return (nil, value) } } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId) |> map { value -> (total: Int32?, recent: Int32?) in return (value.total, value.recent) } @@ -1992,14 +2037,7 @@ func peerInfoScreenData( let threadData: Signal if case let .replyThread(message) = chatLocation { let threadId = message.threadId - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadData = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> MessageHistoryThreadData? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - return view.info?.data.get(MessageHistoryThreadData.self) - } + threadData = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: threadId)) } else { threadData = .single(nil) } @@ -2074,7 +2112,7 @@ func peerInfoScreenData( requestsStatePromise.get(), hasStories, threadData, - context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)), accountIsPremium, hasSavedMessages, hasSavedMessagesChats, @@ -2083,14 +2121,14 @@ func peerInfoScreenData( starsRevenueContextAndState ) |> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState -> Signal in - var discussionPeer: Peer? + var discussionPeer: EnginePeer? if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] { - discussionPeer = peer + discussionPeer = EnginePeer(peer) } - - var monoforumPeer: Peer? + + var monoforumPeer: EnginePeer? if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { - monoforumPeer = peerView.peers[linkedMonoforumId] + monoforumPeer = peerView.peers[linkedMonoforumId].flatMap(EnginePeer.init) } var availablePanes = availablePanes @@ -2154,11 +2192,11 @@ func peerInfoScreenData( let peerNotificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings let threadNotificationSettings = threadData?.notificationSettings - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return .single(PeerInfoScreenData( - peer: peerView.peers[groupId], - chatPeer: peerView.peers[groupId], + peer: peerView.peers[groupId].flatMap(EnginePeer.init), + chatPeer: peerView.peers[groupId].flatMap(EnginePeer.init), savedMessagesPeer: nil, cachedData: peerView.cachedData, status: status, @@ -2199,7 +2237,8 @@ func peerInfoScreenData( webAppPermissions: nil, savedMusicContext: nil, savedMusicState: nil, - managedByBot: nil + managedByBot: nil, + businessConnectedBot: nil )) } } @@ -2216,13 +2255,13 @@ func peerInfoIsCopyProtected(data: PeerInfoScreenData) -> Bool { return isCopyProtected } -func canEditPeerInfo(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?) -> Bool { +func canEditPeerInfo(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?) -> Bool { if context.account.peerId == peer?.id { return true } - if let user = peer as? TelegramUser, let botInfo = user.botInfo { + if case let .user(user) = peer, let botInfo = user.botInfo { return botInfo.flags.contains(.canEdit) - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if let threadData = threadData { if chatLocation.threadId == 1 { return false @@ -2238,7 +2277,7 @@ func canEditPeerInfo(context: AccountContext, peer: Peer?, chatLocation: ChatLoc return true } } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { switch group.role { case .admin, .creator: return true @@ -2265,23 +2304,23 @@ struct PeerInfoMemberActions: OptionSet { static let editRank = PeerInfoMemberActions(rawValue: 1 << 3) } -func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: PeerInfoMember) -> PeerInfoMemberActions { +func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: EnginePeer?, member: PeerInfoMember) -> PeerInfoMemberActions { var result: PeerInfoMemberActions = [] - + if peer == nil { result.insert(.logout) } else if member.id == accountPeerId { - if let channel = peer as? TelegramChannel { + if case let .channel(channel) = peer { if channel.hasPermission(.editRank) { result.insert(.editRank) } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { if !group.hasBannedPermission(.banEditRank) { result.insert(.editRank) } } } else { - if let channel = peer as? TelegramChannel { + if case let .channel(channel) = peer { if channel.flags.contains(.isCreator) { if !channel.flags.contains(.isGigagroup) { result.insert(.restrict) @@ -2325,7 +2364,7 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: break } } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { switch group.role { case .creator: result.insert(.restrict) @@ -2333,11 +2372,13 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: result.insert(.editRank) case .admin: switch member { - case let .legacyGroupMember(_, _, invitedBy, _, _, _): - result.insert(.restrict) - if invitedBy == accountPeerId { - result.insert(.promote) - result.insert(.editRank) + case let .legacyGroupMember(_, role, invitedBy, _, _, _): + if case .member = role { + result.insert(.restrict) + if invitedBy == accountPeerId { + result.insert(.promote) + result.insert(.editRank) + } } case .channelMember: break @@ -2346,8 +2387,8 @@ func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer?, member: } case .member: switch member { - case let .legacyGroupMember(_, _, invitedBy, _, _, _): - if invitedBy == accountPeerId { + case let .legacyGroupMember(_, role, invitedBy, _, _, _): + if case .member = role, invitedBy == accountPeerId { result.insert(.restrict) result.insert(.editRank) } @@ -2383,9 +2424,9 @@ func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKe return hiddenWhileExpanded } -func peerInfoHeaderActionButtons(peer: Peer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] { +func peerInfoHeaderActionButtons(peer: EnginePeer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] { var result: [PeerInfoHeaderButtonKey] = [] - if !isContact && !isSecretChat, let user = peer as? TelegramUser, user.botInfo == nil { + if !isContact && !isSecretChat, case let .user(user) = peer, user.botInfo == nil { result = [.message, .addContact] } @@ -2396,9 +2437,9 @@ func peerInfoHeaderActionButtons(peer: Peer?, isSecretChat: Bool, isContact: Boo return result } -func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] { +func peerInfoHeaderButtons(peer: EnginePeer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] { var result: [PeerInfoHeaderButtonKey] = [] - if let user = peer as? TelegramUser { + if case let .user(user) = peer { if !isOpenedFromChat { result.append(.message) } @@ -2432,7 +2473,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro } else { result.append(.more) } - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if let _ = threadInfo { result.append(.mute) result.append(.search) @@ -2507,7 +2548,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro } } } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { let hasVoiceChat = group.flags.contains(.hasVoiceChat) let canStartVoiceChat: Bool @@ -2534,8 +2575,8 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro return result } -func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool { - if let user = peer as? TelegramUser { +func peerInfoCanEdit(peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool { + if case let .user(user) = peer { if user.isDeleted { return false } @@ -2546,7 +2587,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag return false } return true - } else if let peer = peer as? TelegramChannel { + } else if case let .channel(peer) = peer { if peer.isForumOrMonoForum, let threadData = threadData { if peer.flags.contains(.isCreator) { return true @@ -2567,7 +2608,7 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag } return false } - } else if let peer = peer as? TelegramGroup { + } else if case let .legacyGroup(peer) = peer { if case .creator = peer.role { return true } else if case let .admin(rights, _) = peer.role { @@ -2582,19 +2623,19 @@ func peerInfoCanEdit(peer: Peer?, chatLocation: ChatLocation, threadData: Messag return false } -func peerInfoIsChatMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool { - func isPeerMuted(peer: Peer?, peerNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool { +func peerInfoIsChatMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool { + func isPeerMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool { var peerIsMuted = false if let peerNotificationSettings { if case .muted = peerNotificationSettings.muteState { peerIsMuted = true } else if case .default = peerNotificationSettings.muteState, let globalNotificationSettings { if let peer { - if peer is TelegramUser { + if case .user = peer { peerIsMuted = !globalNotificationSettings.privateChats.enabled - } else if peer is TelegramGroup { + } else if case .legacyGroup = peer { peerIsMuted = !globalNotificationSettings.groupChats.enabled - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { switch channel.info { case .group: peerIsMuted = !globalNotificationSettings.groupChats.enabled diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift index 245df71b38..98fc8ba70d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift @@ -9,7 +9,6 @@ import PeerInfoAvatarListNode import AvatarVideoNode import TelegramUniversalVideoContent import SwiftSignalKit -import Postbox import TelegramCore import Display import GalleryUI @@ -58,7 +57,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { } var removedPhotoResourceIds = Set() - func update(peer: Peer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: AvatarUploadProgress?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { + func update(peer: EnginePeer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: AvatarUploadProgress?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { guard let peer = peer else { return } @@ -85,12 +84,12 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { overrideImage = item == nil && canEdit ? .editAvatarIcon(forceNone: true) : nil } self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0)) - self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) + self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) var isForum = false let avatarCornerRadius: CGFloat - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if case let .channel(channel) = peer, channel.isForumOrMonoForum { isForum = true avatarCornerRadius = floor(avatarSize * 0.25) } else { @@ -162,7 +161,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { markupNode.removeFromSupernode() } - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift index 91d9455b87..439b373642 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarOverlayNode.swift @@ -5,7 +5,6 @@ import TelegramPresentationData import AccountContext import Display import RadialStatusNode -import Postbox import TelegramCore import PeerInfoAvatarListNode import AvatarNode @@ -60,7 +59,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode { transition.updateAlpha(node: self, alpha: 1.0 - fraction) } - func update(peer: Peer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: AvatarUploadProgress?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { + func update(peer: EnginePeer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: AvatarUploadProgress?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) { guard let peer = peer else { return } @@ -71,7 +70,7 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear) let clipStyle: AvatarNodeClipStyle - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if case let .channel(channel) = peer, channel.isForumOrMonoForum { clipStyle = .roundedRect } else { clipStyle = .round diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift index bc3b966244..e9598a7c68 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import TelegramPresentationData import AccountContext import Display -import Postbox import TelegramCore import SwiftSignalKit @@ -49,7 +48,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { self.itemNodes[key]?.layer.addShakeAnimation() } - func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, peer: Peer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, cachedData: CachedPeerData?, isContact: Bool, isSettings: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { + func update(width: CGFloat, safeInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, peer: EnginePeer?, threadData: MessageHistoryThreadData?, chatLocation: ChatLocation, cachedData: EngineCachedPeerData?, isContact: Bool, isSettings: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat { let avatarSize: CGFloat = isModalOverlay ? 200.0 : 100.0 let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize)) transition.updateFrameAdditiveToCenter(node: self.avatarNode, frame: CGRect(origin: avatarFrame.center, size: CGSize())) @@ -70,11 +69,11 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { } var isEditableBot = false - if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { isEditableBot = true } var fieldKeys: [PeerInfoHeaderTextFieldNodeKey] = [] - if let user = peer as? TelegramUser { + if case let .user(user) = peer { if !user.isDeleted { fieldKeys.append(.firstName) if isEditableBot { @@ -83,12 +82,12 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { fieldKeys.append(.lastName) } } - } else if let _ = peer as? TelegramGroup { + } else if case .legacyGroup = peer { fieldKeys.append(.title) if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) { fieldKeys.append(.description) } - } else if let _ = peer as? TelegramChannel { + } else if case .channel = peer { fieldKeys.append(.title) if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) { fieldKeys.append(.description) @@ -104,15 +103,19 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { var isMultiline = false switch key { case .firstName: - if let peer = peer as? TelegramUser { + if case let .user(user) = peer { if let editableBotInfo = (cachedData as? CachedUserData)?.editableBotInfo { updateText = editableBotInfo.name } else { - updateText = peer.firstName ?? "" + updateText = user.firstName ?? "" } } case .lastName: - updateText = (peer as? TelegramUser)?.lastName ?? "" + if case let .user(user) = peer { + updateText = user.lastName ?? "" + } else { + updateText = "" + } case .title: updateText = peer?.debugDisplayTitle ?? "" case .description: @@ -151,7 +154,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { placeholder = presentationData.strings.UserInfo_LastNamePlaceholder isEnabled = isContact || isSettings case .title: - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { placeholder = presentationData.strings.GroupInfo_ChannelListNamePlaceholder } else { placeholder = presentationData.strings.GroupInfo_GroupNamePlaceholder diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift index f42fd8cfd0..fe4c0b06a0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButton.swift @@ -13,23 +13,6 @@ private enum MoreIconNodeState: Equatable { case moreToSearch(Float) } -private let glassBackArrowImage: UIImage? = { - let imageSize = CGSize(width: 44.0, height: 44.0) - let topRightPoint = CGPoint(x: 24.6, y: 14.0) - let centerPoint = CGPoint(x: 17.0, y: imageSize.height * 0.5) - return generateImage(imageSize, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor.white.cgColor) - context.setLineWidth(2.0) - context.setLineCap(.round) - context.setLineJoin(.round) - context.move(to: topRightPoint) - context.addLine(to: centerPoint) - context.addLine(to: CGPoint(x: topRightPoint.x, y: size.height - topRightPoint.y)) - context.strokePath() - })?.withRenderingMode(.alwaysTemplate) -}() - private final class MoreIconNode: ManagedAnimationNode { private let duration: Double = 0.21 private var iconState: MoreIconNodeState = .more @@ -233,7 +216,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .back: text = "" accessibilityText = presentationData.strings.Common_Back - icon = glassBackArrowImage + icon = PresentationResourcesRootController.navigationBackIcon(presentationData.theme) case .edit: text = presentationData.strings.Common_Edit accessibilityText = text @@ -271,7 +254,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { case .more: text = "" accessibilityText = presentationData.strings.Common_More - icon = nil// PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) + icon = nil isGestureEnabled = true isAnimation = true animationState = .more diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index c392a2381d..b0d90b7108 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import AvatarNode import AccountContext @@ -89,7 +88,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private weak var controller: PeerInfoScreenImpl? private var presentationData: PresentationData? private var state: PeerInfoState? - private var peer: Peer? + private var peer: EnginePeer? private var threadData: MessageHistoryThreadData? private var isSearching: Bool = false private var avatarSize: CGFloat? @@ -493,7 +492,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { private var currentStatusIcon: CredibilityIcon? private var currentPanelStatusData: PeerInfoStatusData? - func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, screenData: PeerInfoScreenData?, isSearching: Bool, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { + func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: EnginePeer?, cachedData: EngineCachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, profileGiftsContext: ProfileGiftsContext?, screenData: PeerInfoScreenData?, isSearching: Bool, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { if self.appliedCustomNavigationContentNode !== self.customNavigationContentNode { if let previous = self.appliedCustomNavigationContentNode { ComponentTransition(transition).setAlpha(view: previous.view, alpha: 0.0, completion: { [weak previous] _ in @@ -521,7 +520,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.peer = peer self.threadData = threadData self.isSearching = isSearching - self.avatarListNode.listContainerNode.peer = peer.flatMap(EnginePeer.init) + self.avatarListNode.listContainerNode.peer = peer let isFirstTime = self.validLayout == nil self.validLayout = (width, statusBarHeight, deviceMetrics) @@ -568,7 +567,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } hasBackground = true } else if let peer { - backgroundCoverSubject = .peer(EnginePeer(peer)) + backgroundCoverSubject = .peer(peer) if peer.effectiveProfileColor != nil { hasBackground = true } @@ -619,7 +618,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } var isForum = false - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if case let .channel(channel) = peer, channel.isForumOrMonoForum { isForum = true } @@ -1215,14 +1214,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { } else if let threadData = threadData { title = threadData.info.title } else { - title = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } title = title.replacingOccurrences(of: "\u{1160}", with: "").replacingOccurrences(of: "\u{3164}", with: "") if title.replacingOccurrences(of: "\u{fe0e}", with: "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { title = "" //"\u{00A0}" } if title.isEmpty { - if let peer = peer as? TelegramUser, let phone = peer.phone { + if case let .user(user) = peer, let phone = user.phone { title = formatPhoneNumber(context: self.context, number: phone) } else if let addressName = peer.addressName { title = "@\(addressName)" @@ -1235,7 +1234,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { titleAttributes = MultiScaleTextState.Attributes(font: Font.medium(28.0), color: .white) smallTitleAttributes = MultiScaleTextState.Attributes(font: Font.medium(28.0), color: .white, shadowColor: titleShadowColor) - if self.isSettings, let user = peer as? TelegramUser { + if self.isSettings, case let .user(user) = peer { var subtitle = formatPhoneNumber(context: self.context, number: user.phone ?? "") if let mainUsername = user.addressName, !mainUsername.isEmpty { @@ -1267,11 +1266,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { panelSubtitleString = (panelStatusData.text, MultiScaleTextState.Attributes(font: Font.regular(17.0), color: subtitleColor)) } } else if let _ = threadData { - let subtitleColor: UIColor - subtitleColor = UIColor.white + let subtitleColor: UIColor = .white let statusText: String - if let user = peer as? TelegramUser, user.isForum { + if case let .user(user) = peer, user.isForum { statusText = " " } else { statusText = peer.debugDisplayTitle @@ -1351,7 +1349,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { let textSideInset: CGFloat = 36.0 let expandedAvatarHeight: CGFloat = expandedAvatarListSize.height - let titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude) + var titleConstrainedSize = CGSize(width: width - textSideInset * 2.0 - (isPremium || isVerified || isFake ? 20.0 : 0.0), height: .greatestFiniteMagnitude) + if self.navigationButtonContainer.rightButtonNodes.count > 1 { + titleConstrainedSize.width -= 60.0 + } let titleNodeLayout = self.titleNode.updateLayout(text: titleStringText, states: [ TitleNodeStateRegular: MultiScaleTextState(attributes: titleAttributes, constrainedSize: titleConstrainedSize), @@ -1364,6 +1365,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { ], mainState: TitleNodeStateRegular) self.subtitleNode.accessibilityLabel = subtitleStringText + var subtitleButtonHorizontalOffset: CGFloat = 0.0 if subtitleIsButton { let subtitleBackgroundNode: ASDisplayNode if let current = self.subtitleBackgroundNode { @@ -1400,20 +1402,21 @@ final class PeerInfoHeaderNode: ASDisplayNode { let subtitleArrowNode: ASImageNode if let current = self.subtitleArrowNode { subtitleArrowNode = current - if themeUpdated { - subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) - } } else { subtitleArrowNode = ASImageNode() self.subtitleArrowNode = subtitleArrowNode self.subtitleNode.insertSubnode(subtitleArrowNode, at: 1) - - subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate) } - subtitleBackgroundNode.backgroundColor = .white.withMultipliedAlpha(0.1) + if subtitleArrowNode.image == nil || themeUpdated { + subtitleArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: presentationData.theme.list.itemSecondaryTextColor) + } + self.subtitleNode.updateTintColor(color: presentationData.theme.list.itemSecondaryTextColor, transition: navigationTransition) + + transition.updateBackgroundColor(node: subtitleBackgroundNode, color: contentButtonBackgroundColor) let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size - var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -6.0, dy: -4.0) + var subtitleBackgroundFrame = CGRect(origin: CGPoint(), size: subtitleSize).offsetBy(dx: -subtitleSize.width * 0.5, dy: -subtitleSize.height * 0.5).insetBy(dx: -8.0, dy: -4.0) subtitleBackgroundFrame.size.width += 12.0 + subtitleButtonHorizontalOffset = subtitleBackgroundFrame.midX transition.updateFrame(node: subtitleBackgroundNode, frame: subtitleBackgroundFrame) transition.updateCornerRadius(node: subtitleBackgroundNode, cornerRadius: subtitleBackgroundFrame.height * 0.5) @@ -1620,7 +1623,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { titleOffset = -min(titleCollapseOffset, contentOffset) titleCollapseFraction = max(0.0, min(1.0, contentOffset / titleCollapseOffset)) - subtitleFrame = CGRect(origin: CGPoint(x: 16.0, y: minTitleFrame.maxY + 2.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: 16.0 - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: minTitleFrame.maxY + 2.0), size: subtitleSize) if self.subtitleRating != nil { subtitleFrame.origin.x += 22.0 } @@ -1642,10 +1645,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { let totalSubtitleWidth = effectiveSubtitleWidth + usernameSpacing + usernameSize.width if usernameSize.width == 0.0 { - subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - effectiveSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - effectiveSubtitleWidth) / 2.0) - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - usernameSize.width) / 2.0), y: subtitleFrame.maxY + 1.0), size: usernameSize) } else { - subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0), y: titleFrame.maxY + 1.0), size: subtitleSize) + subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - totalSubtitleWidth) / 2.0) - subtitleButtonHorizontalOffset * (1.0 - titleCollapseFraction), y: titleFrame.maxY + 1.0), size: subtitleSize) usernameFrame = CGRect(origin: CGPoint(x: subtitleFrame.maxX + usernameSpacing, y: titleFrame.maxY + 1.0), size: usernameSize) } } @@ -1960,7 +1963,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.avatarListNode.containerNode.view.mask = nil } - self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer.flatMap(EnginePeer.init), isExpanded: self.isAvatarExpanded, transition: transition) + self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition) if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse { self.avatarListNode.avatarContainerNode.canAttachVideo = false } @@ -2050,7 +2053,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } self.controller?.push(ProfileLevelInfoScreen( context: self.context, - peer: EnginePeer(peer), + peer: peer, starRating: currentStarRating, pendingStarRating: self.currentPendingStarRating, customTheme: self.presentationData?.theme @@ -2350,7 +2353,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { buttonText = presentationData.strings.PeerInfo_ButtonVideoCall buttonIcon = .videoCall case .voiceChat: - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { buttonText = presentationData.strings.PeerInfo_ButtonLiveStream } else { buttonText = presentationData.strings.PeerInfo_ButtonVoiceChat @@ -2859,4 +2862,3 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint) } } - diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift index 0d0bdf5c72..fc9b68ca01 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoInteraction.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import SwiftSignalKit import AccountContext import StatisticsUI @@ -23,6 +22,7 @@ final class PeerInfoInteraction { let openAddContact: () -> Void let updateBlocked: (Bool) -> Void let openReport: (PeerInfoReportType) -> Void + let openDeleteReaction: (EngineMessage.Id) -> Void let openShareBot: () -> Void let openAddBotToGroup: () -> Void let performBotCommand: (PeerInfoBotCommand) -> Void @@ -41,8 +41,7 @@ final class PeerInfoInteraction { let editingOpenAutoremoveMesages: () -> Void let openPermissions: () -> Void let openLocation: () -> Void - let editingOpenSetupLocation: () -> Void - let openPeerInfo: (Peer, Bool) -> Void + let openPeerInfo: (EnginePeer, Bool) -> Void let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void @@ -83,6 +82,7 @@ final class PeerInfoInteraction { let editingOpenVerifyAccounts: () -> Void let editingToggleAutoTranslate: (Bool) -> Void let displayAutoTranslateLocked: () -> Void + let editingOpenBusinessChatBots: () -> Void let getController: () -> ViewController? init( @@ -100,6 +100,7 @@ final class PeerInfoInteraction { openAddContact: @escaping () -> Void, updateBlocked: @escaping (Bool) -> Void, openReport: @escaping (PeerInfoReportType) -> Void, + openDeleteReaction: @escaping (EngineMessage.Id) -> Void, openShareBot: @escaping () -> Void, openAddBotToGroup: @escaping () -> Void, performBotCommand: @escaping (PeerInfoBotCommand) -> Void, @@ -118,8 +119,7 @@ final class PeerInfoInteraction { editingOpenAutoremoveMesages: @escaping () -> Void, openPermissions: @escaping () -> Void, openLocation: @escaping () -> Void, - editingOpenSetupLocation: @escaping () -> Void, - openPeerInfo: @escaping (Peer, Bool) -> Void, + openPeerInfo: @escaping (EnginePeer, Bool) -> Void, performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void, openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void, performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, @@ -160,6 +160,7 @@ final class PeerInfoInteraction { editingOpenVerifyAccounts: @escaping () -> Void, editingToggleAutoTranslate: @escaping (Bool) -> Void, displayAutoTranslateLocked: @escaping () -> Void, + editingOpenBusinessChatBots: @escaping () -> Void, getController: @escaping () -> ViewController? ) { self.openUsername = openUsername @@ -176,6 +177,7 @@ final class PeerInfoInteraction { self.openAddContact = openAddContact self.updateBlocked = updateBlocked self.openReport = openReport + self.openDeleteReaction = openDeleteReaction self.openShareBot = openShareBot self.openAddBotToGroup = openAddBotToGroup self.performBotCommand = performBotCommand @@ -194,7 +196,6 @@ final class PeerInfoInteraction { self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages self.openPermissions = openPermissions self.openLocation = openLocation - self.editingOpenSetupLocation = editingOpenSetupLocation self.openPeerInfo = openPeerInfo self.performMemberAction = performMemberAction self.openPeerInfoContextMenu = openPeerInfoContextMenu @@ -236,6 +237,7 @@ final class PeerInfoInteraction { self.editingOpenVerifyAccounts = editingOpenVerifyAccounts self.editingToggleAutoTranslate = editingToggleAutoTranslate self.displayAutoTranslateLocked = displayAutoTranslateLocked + self.editingOpenBusinessChatBots = editingOpenBusinessChatBots self.getController = getController } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift index 5146ee4244..d19e07f18f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoMembers.swift @@ -14,8 +14,8 @@ enum PeerInfoMemberRole { enum PeerInfoMember: Equatable { case channelMember(participant: RenderedChannelParticipant, storyStats: PeerStoryStats?) case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?, storyStats: PeerStoryStats?, rank: String?) - case account(peer: RenderedPeer) - + case account(peer: EngineRenderedPeer) + var id: PeerId { switch self { case let .channelMember(participant, _): @@ -26,15 +26,15 @@ enum PeerInfoMember: Equatable { return peer.peerId } } - - var peer: Peer { + + var peer: EnginePeer { switch self { case let .channelMember(participant, _): return participant.peer case let .legacyGroupMember(peer, _, _, _, _, _): - return peer.peers[peer.peerId]! + return EnginePeer(peer.peers[peer.peerId]!) case let .account(peer): - return peer.peers[peer.peerId]! + return peer.peer! } } @@ -162,7 +162,7 @@ private final class PeerInfoMembersContextImpl { self.pushState() if peerId.namespace == Namespaces.Peer.CloudChannel { - let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, requestUpdate: true, updated: { [weak self] state in + let (disposable, control) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, requestUpdate: true, updated: { [weak self] state in queue.async { guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 385fd4dbc7..97789b516e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData -import Postbox import TelegramCore import AccountContext import ContextUI @@ -408,10 +407,10 @@ private final class PeerInfoPendingPane { updatedPresentationData: (initial: PresentationData, signal: Signal)?, chatControllerInteraction: ChatControllerInteraction, data: PeerInfoScreenData, - openPeerContextAction: @escaping (Bool, Peer, ASDisplayNode, ContextGesture?) -> Void, + openPeerContextAction: @escaping (Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void, openAddMemberAction: @escaping () -> Void, requestPerformPeerMemberAction: @escaping (PeerInfoMember, PeerMembersListAction) -> Void, - peerId: PeerId, + peerId: EnginePeer.Id, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, @@ -462,7 +461,7 @@ private final class PeerInfoPendingPane { if let cachedUserData = data.cachedData as? CachedUserData, cachedUserData.disallowedGifts == .All { canGift = false } - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { if channel.hasPermission(.sendSomething) { canManage = true } @@ -476,7 +475,7 @@ private final class PeerInfoPendingPane { if let peer = data.peer { if peer.id == context.account.peerId { canManage = true - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if channel.hasPermission(.editStories) { canManage = true } @@ -493,7 +492,7 @@ private final class PeerInfoPendingPane { scope = .botPreview(id: peerId) if let peer = data.peer { - if let user = peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { canManage = true } } @@ -582,7 +581,7 @@ private final class PeerInfoPendingPane { final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegate { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let chatLocation: ChatLocation private let chatLocationContextHolder: Atomic private let isMediaOnly: Bool @@ -637,7 +636,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat var selectionPanelNode: PeerInfoSelectionPanelNode? var chatControllerInteraction: ChatControllerInteraction? - var openPeerContextAction: ((Bool, Peer, ASDisplayNode, ContextGesture?) -> Void)? + var openPeerContextAction: ((Bool, EnginePeer, ASDisplayNode, ContextGesture?) -> Void)? var openAddMemberAction: (() -> Void)? var requestPerformPeerMemberAction: ((PeerInfoMember, PeerMembersListAction) -> Void)? @@ -657,7 +656,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat private let initialPaneKey: PeerInfoPaneKey? - init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: PeerId, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?, initialStoryFolderId: Int64?, initialGiftCollectionId: Int64?, switchToMediaTarget: PeerInfoSwitchToMediaTarget?) { + init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, chatLocation: ChatLocation, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?, chatLocationContextHolder: Atomic, isMediaOnly: Bool, initialPaneKey: PeerInfoPaneKey?, initialStoryFolderId: Int64?, initialGiftCollectionId: Int64?, switchToMediaTarget: PeerInfoSwitchToMediaTarget?) { self.context = context self.updatedPresentationData = updatedPresentationData self.peerId = peerId @@ -823,7 +822,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat } } - func findLoadedMessage(id: MessageId) -> Message? { + func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return self.currentPane?.node.findLoadedMessage(id: id) } @@ -831,11 +830,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat self.currentPane?.node.updateHiddenMedia() } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return self.currentPane?.node.transitionNodeForGallery(messageId: messageId, media: media) } - func updateSelectedMessageIds(_ selectedMessageIds: Set?, animated: Bool) { + func updateSelectedMessageIds(_ selectedMessageIds: Set?, animated: Bool) { for (_, pane) in self.currentPanes { pane.node.updateSelectedMessages(animated: animated) } @@ -1249,7 +1248,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat if let peer = data?.peer { if peer.id == self.context.account.peerId { canManageTabs = true - } else if let channel = data?.peer as? TelegramChannel, case .broadcast = channel.info { + } else if case let .channel(channel) = data?.peer, case .broadcast = channel.info { if channel.hasPermission(.changeInfo) { canManageTabs = true } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift index d24b90a11c..1ccdf29d21 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoProfileItems.swift @@ -4,7 +4,6 @@ import Display import AccountContext import TelegramPresentationData import TelegramCore -import Postbox import PeerInfoUI import TextFormat import PhoneNumberFormat @@ -36,7 +35,18 @@ enum InfoSection: Int, CaseIterable { case botAffiliateProgram } -func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] { +func infoItems( + data: PeerInfoScreenData?, + context: AccountContext, + presentationData: PresentationData, + interaction: PeerInfoInteraction, + reactionSourceMessageId: EngineMessage.Id?, + canDeleteReaction: Bool, + callMessages: [EngineMessage], + chatLocation: ChatLocation, + isOpenedFromChat: Bool, + isMyProfile: Bool +) -> [(AnyHashable, [PeerInfoScreenItem])] { guard let data = data else { return [] } @@ -67,7 +77,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD interaction.openBirthdayContextMenu(node, gesture) } - if let user = data.peer as? TelegramUser { + if case let .user(user) = data.peer { let ItemCallList = 1000 let ItemPersonalChannelHeader = 2000 let ItemPersonalChannel = 2001 @@ -81,9 +91,9 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD let ItemAffiliateInfo = 4001 let ItemBusinessHours = 5000 let ItemLocation = 5001 - let ItemSendMessage = 6000 - let ItemReport = 6001 - let ItemAddToContacts = 6002 + let ItemAddToContacts = 6000 + let ItemDeleteReaction = 6001 + let ItemReport = 6002 let ItemBlock = 6003 let ItemEncryptionKey = 6004 let ItemBalanceHeader = 7000 @@ -359,31 +369,22 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD } if !isMyProfile { - if let reactionSourceMessageId = reactionSourceMessageId, !data.isContact { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { - interaction.openChat(nil) + if !data.isContact, user.botInfo == nil { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: { + interaction.openAddContact() })) - + } + + if let reactionSourceMessageId = reactionSourceMessageId { + if canDeleteReaction { + items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemDeleteReaction, text: presentationData.strings.PeerInfo_DeleteReaction, color: .destructive, action: { + interaction.openDeleteReaction(reactionSourceMessageId) + })) + } items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_BanAndReport, color: .destructive, action: { interaction.openReport(.reaction(reactionSourceMessageId)) })) - } else if let _ = nearbyPeerDistance { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemSendMessage, text: presentationData.strings.UserInfo_SendMessage, action: { - interaction.openChat(nil) - })) - - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemReport, text: presentationData.strings.ReportPeer_Report, color: .destructive, action: { - interaction.openReport(.user) - })) } else { - if !data.isContact { - if user.botInfo == nil { - items[currentPeerInfoSection]!.append(PeerInfoScreenActionItem(id: ItemAddToContacts, text: presentationData.strings.PeerInfo_AddToContacts, action: { - interaction.openAddContact() - })) - } - } - var isBlocked = false if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked { isBlocked = true @@ -519,7 +520,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD if let managedByBot = data.managedByBot { items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, icon: .managedBot, text: presentationData.strings.PeerInfo_ManagedBotFooter(managedByBot.compactDisplayTitle).string, linkAction: { _ in - interaction.openPeerInfo(managedByBot._asPeer(), false) + interaction.openPeerInfo(managedByBot, false) })) } else { items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, text: presentationData.strings.Bot_AddToChatInfo)) @@ -527,7 +528,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD } } } - } else if let channel = data.peer as? TelegramChannel { + } else if case let .channel(channel) = data.peer { let ItemUsername = 1 let ItemUsernameInfo = 2 let ItemAbout = 3 @@ -793,7 +794,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD } } } - } else if let group = data.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = data.peer { if let cachedData = data.cachedData as? CachedGroupData { let aboutText: String? if group.isFake { @@ -818,7 +819,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD if let peer = data.peer, let members = data.members, case let .shortList(_, memberList) = members { var canAddMembers = false - if let group = data.peer as? TelegramGroup { + if case let .legacyGroup(group) = data.peer { switch group.role { case .admin, .creator: canAddMembers = true @@ -828,7 +829,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD if !group.hasBannedPermission(.banAddMembers) { canAddMembers = true } - } else if let channel = data.peer as? TelegramChannel { + } else if case let .channel(channel) = data.peer { switch channel.info { case .broadcast: break @@ -901,7 +902,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s } if let data = data { - if let user = data.peer as? TelegramUser { + if case let .user(user) = data.peer { let ItemNote: AnyHashable = AnyHashable("note_edit") let ItemNoteInfo = 1 @@ -942,7 +943,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s } else { programTitleValue = .text(presentationData.strings.PeerInfo_ItemAffiliateProgram_ValueOff) } - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, text: presentationData.strings.PeerInfo_ItemAffiliateProgram_Title, icon: PresentationResourcesSettings.affiliateProgram, action: { interaction.editingOpenAffiliateProgram() })) } @@ -1045,7 +1046,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s interaction.requestDeleteContact() })) } - } else if let channel = data.peer as? TelegramChannel { + } else if case let .channel(channel) = data.peer { switch channel.info { case .broadcast: let ItemUsername = 1 @@ -1097,7 +1098,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s if let addressName = peer.addressName, !addressName.isEmpty { discussionGroupTitle = "@\(addressName)" } else { - discussionGroupTitle = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + discussionGroupTitle = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } } else { discussionGroupTitle = presentationData.strings.Channel_DiscussionGroupAdd @@ -1152,24 +1153,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s var boostIcon: UIImage? if let approximateBoostLevel = channel.approximateBoostLevel, approximateBoostLevel < 1 { boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) - } else { - /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) - let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) - let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) - boostIcon = generateImage(badgeSize, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) - - context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) - context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillPath() - - UIGraphicsPushContext(context) - labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) - UIGraphicsPopContext() - })*/ } items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: PresentationResourcesSettings.chatAppearance, action: { interaction.editingOpenNameColorSetup() @@ -1192,7 +1175,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) { let labelString: NSAttributedString if channel.linkedMonoforumId != nil { - if let monoforumPeer = data.linkedMonoforumPeer as? TelegramChannel { + if case let .channel(monoforumPeer) = data.linkedMonoforumPeer { if let sendPaidMessageStars = monoforumPeer.sendPaidMessageStars { let formattedLabel = formatStarsAmountText(sendPaidMessageStars, dateTimeFormat: presentationData.dateTimeFormat) let smallLabelFont = Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize / 17.0 * 13.0)) @@ -1225,7 +1208,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s labelString = NSAttributedString(string: presentationData.strings.PeerInfo_AllowChannelMessages_Off, font: labelFont, textColor: labelColor) } - items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .attributedText(labelString), additionalBadgeLabel: presentationData.strings.Settings_New, text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: { + items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .attributedText(labelString), text: presentationData.strings.PeerInfo_AllowChannelMessages, icon: PresentationResourcesSettings.channelMessages, action: { interaction.editingOpenPostSuggestionsSetup() })) @@ -1331,7 +1314,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s let ItemRecentActions = 111 let ItemLocationHeader = 112 let ItemLocation = 113 - let ItemLocationSetup = 114 let ItemDeleteGroup = 115 let ItemReactions = 116 let ItemTopics = 117 @@ -1355,11 +1337,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s interaction.openLocation() } )) - if cachedData.flags.contains(.canChangePeerGeoLocation) { - items[.groupLocation]!.append(PeerInfoScreenActionItem(id: ItemLocationSetup, text: presentationData.strings.Group_Location_ChangeLocation, action: { - interaction.editingOpenSetupLocation() - })) - } } if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) { @@ -1404,7 +1381,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s if let addressName = linkedDiscussionPeer.addressName, !addressName.isEmpty { peerTitle = "@\(addressName)" } else { - peerTitle = EnginePeer(linkedDiscussionPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + peerTitle = linkedDiscussionPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemLinkedChannel, label: .text(peerTitle), text: presentationData.strings.Group_LinkedChannel, icon: PresentationResourcesSettings.channels, action: { @@ -1466,23 +1443,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s boostIcon = generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Info_BoostLevelPlusBadge("1").string) } else { boostIcon = nil - /*let labelText = NSAttributedString(string: presentationData.strings.Settings_New, font: Font.medium(11.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - let labelBounds = labelText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil) - let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height)) - let badgeSize = CGSize(width: labelSize.width + 8.0, height: labelSize.height + 2.0 + 1.0) - boostIcon = generateImage(badgeSize, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let rect = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height - UIScreenPixel * 2.0)) - - context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 5.0).cgPath) - context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) - context.fillPath() - - UIGraphicsPushContext(context) - labelText.draw(at: CGPoint(x: 4.0, y: 1.0 + UIScreenPixel)) - UIGraphicsPopContext() - })*/ } items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAppearance, label: .image(colorImage, colorImage.size), additionalBadgeIcon: boostIcon, text: presentationData.strings.Channel_Info_AppearanceItem, icon: PresentationResourcesSettings.chatAppearance, action: { interaction.editingOpenNameColorSetup() @@ -1589,7 +1549,7 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s } } } - } else if let group = data.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = data.peer { let ItemUsername = 101 let ItemInviteLinks = 102 let ItemPreHistory = 103 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index e7c811e0b7..51cc9be59f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -29,7 +29,6 @@ import GalleryUI import LegacyUI import MapResourceToAvatarSizes import LegacyComponents -import WebSearchUI import LocationResources import LocationUI import Geocoding @@ -209,7 +208,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let peerId: PeerId let isOpenedFromChat: Bool let videoCallsEnabled: Bool - let callMessages: [Message] + let callMessages: [EngineMessage] let chatLocation: ChatLocation let chatLocationContextHolder: Atomic let switchToStoryFolder: Int64? @@ -281,8 +280,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro personalChannels: nil ) var forceIsContactPromise = ValuePromise(false) - let nearbyPeerDistance: Int32? let reactionSourceMessageId: MessageId? + let sourceMessageId: MessageId? + var canDeleteReaction = false var dataDisposable: Disposable? let activeActionDisposable = MetaDisposable() @@ -347,15 +347,38 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private var didSetReady = false - init(controller: PeerInfoScreenImpl, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], isSettings: Bool, isMyProfile: Bool, hintGroupInCommon: PeerId?, requestsContext: PeerInvitationImportersContext?, profileGiftsContext: ProfileGiftsContext?, starsContext: StarsContext?, tonContext: StarsContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic, switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?, switchToStoryFolder: Int64?, switchToMediaTarget: PeerInfoSwitchToMediaTarget?, initialPaneKey: PeerInfoPaneKey?, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) { + init( + controller: PeerInfoScreenImpl, + context: AccountContext, + peerId: PeerId, + avatarInitiallyExpanded: Bool, + isOpenedFromChat: Bool, + reactionSourceMessageId: MessageId?, + sourceMessageId: MessageId?, + callMessages: [EngineMessage], + isSettings: Bool, + isMyProfile: Bool, + hintGroupInCommon: PeerId?, + requestsContext: PeerInvitationImportersContext?, + profileGiftsContext: ProfileGiftsContext?, + starsContext: StarsContext?, + tonContext: StarsContext?, + chatLocation: ChatLocation, + chatLocationContextHolder: Atomic, + switchToGiftsTarget: PeerInfoSwitchToGiftsTarget?, + switchToStoryFolder: Int64?, + switchToMediaTarget: PeerInfoSwitchToMediaTarget?, + initialPaneKey: PeerInfoPaneKey?, + sharedMediaFromForumTopic: (EnginePeer.Id, Int64)? + ) { self.controller = controller self.context = context self.peerId = peerId self.isOpenedFromChat = isOpenedFromChat self.videoCallsEnabled = true self.presentationData = controller.presentationData - self.nearbyPeerDistance = nearbyPeerDistance self.reactionSourceMessageId = reactionSourceMessageId + self.sourceMessageId = sourceMessageId self.callMessages = callMessages self.isSettings = isSettings self.isMyProfile = isMyProfile @@ -437,6 +460,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openReport: { [weak self] type in self?.openReport(type: type, contextController: nil, backAction: nil) }, + openDeleteReaction: { [weak self] messageId in + self?.openDeleteReaction(messageId: messageId) + }, openShareBot: { [weak self] in self?.openShareBot() }, @@ -491,9 +517,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro openLocation: { [weak self] in self?.openLocation() }, - editingOpenSetupLocation: { [weak self] in - self?.editingOpenSetupLocation() - }, openPeerInfo: { [weak self] peer, isMember in self?.openPeerInfo(peer: peer, isMember: isMember) }, @@ -682,6 +705,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } self.displayAutoTranslateLocked() + }, editingOpenBusinessChatBots: { [weak self] in + guard let self else { + return + } + self.editingOpenBusinessChatBots() }, getController: { [weak self] in return self?.controller @@ -732,14 +760,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId { + if case let .channel(channel) = currentPeer, channel.isForumOrMonoForum, let threadId = message.threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { targetLocation = .replyThread(message) } else { - targetLocation = .peer(EnginePeer(currentPeer)) + targetLocation = .peer(currentPeer) } let currentPeerId = strongSelf.peerId @@ -899,14 +927,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in c?.dismiss(completion: { if let strongSelf = self, let currentPeer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - if let channel = currentPeer as? TelegramChannel, channel.isForumOrMonoForum, let threadId = message.threadId { + if case let .channel(channel) = currentPeer, channel.isForumOrMonoForum, let threadId = message.threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: currentPeer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone() } else { let targetLocation: NavigateToChatControllerParams.Location if case let .replyThread(message) = strongSelf.chatLocation { targetLocation = .replyThread(message) } else { - targetLocation = .peer(EnginePeer(currentPeer)) + targetLocation = .peer(currentPeer) } let currentPeerId = strongSelf.peerId @@ -1060,7 +1088,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } strongSelf.paneContainerNode.updateSelectedMessageIds(strongSelf.state.selectedMessageIds, animated: true) }, sendCurrentMessage: { _, _ in - }, sendMessage: { _ in + }, sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in @@ -1077,8 +1105,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } strongSelf.openUrl(url: url.url, concealed: url.concealed, external: url.external ?? false) - }, shareCurrentLocation: { - }, shareAccountContact: { + }, openExternalInstantPage: { _ in + }, shareCurrentLocation: { _ in + }, shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in guard let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController else { @@ -1087,11 +1116,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var foundGalleryMessage: Message? if let searchContentNode = strongSelf.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { if let galleryMessage = searchContentNode.messageForGallery(message.id) { - strongSelf.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [EngineMessage(galleryMessage)]) - foundGalleryMessage = galleryMessage + strongSelf.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [galleryMessage]) + foundGalleryMessage = galleryMessage._asMessage() } } - if foundGalleryMessage == nil, let galleryMessage = strongSelf.paneContainerNode.findLoadedMessage(id: message.id) { + if foundGalleryMessage == nil, let galleryMessage = strongSelf.paneContainerNode.findLoadedMessage(id: message.id)?._asMessage() { foundGalleryMessage = galleryMessage } @@ -1133,14 +1162,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro actionSheet?.dismissAnimated() if let strongSelf = self { if canOpenIn { - let actionSheet = OpenInActionSheetController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { }) } }) strongSelf.view.endEditing(true) - strongSelf.controller?.present(actionSheet, in: .window(.root)) + strongSelf.controller?.push(actionSheet) } else { strongSelf.context.sharedContext.applicationBindings.openUrl(url) } @@ -1193,7 +1222,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, displaySwipeToReplyHint: { }, dismissReplyMarkupMessage: { _ in }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in + }, openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, displayPollSolution: { _, _ in }, displayPsa: { _, _ in @@ -1239,10 +1268,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.openPremiumGift() }, openUniqueGift: { _ in }, openMessageFeeException: { - }, requestMessageUpdate: { _, _ in + }, requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { - }, scrollToMessageId: { _ in + }, scrollToMessageId: { _, _ in + }, scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, attemptedNavigationToPrivateQuote: { _ in }, forceUpdateWarpContents: { @@ -1252,7 +1282,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().startStrict(next: { [weak self] ids in guard let strongSelf = self else { @@ -1300,7 +1333,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro items = [ .action(ContextMenuActionItem(text: presentationData.strings.Conversation_LinkDialogOpen, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ImageEnlarge"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) - self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, .default) + self?.chatInterfaceInteraction.openPeer(peer, .default, nil, .default) })), .action(ContextMenuActionItem(text: presentationData.strings.Chat_SimilarChannels_Join, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) @@ -1308,14 +1341,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - self.joinChannel(peer: EnginePeer(peer)) + self.joinChannel(peer: peer) })) ] } else { items = [ .action(ContextMenuActionItem(text: presentationData.strings.Conversation_LinkDialogOpen, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ImageEnlarge"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.dismissWithoutContent) - self?.chatInterfaceInteraction.openPeer(EnginePeer(peer), .default, nil, .default) + self?.chatInterfaceInteraction.openPeer(peer, .default, nil, .default) })) ] } @@ -1530,7 +1563,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } - strongSelf.openAvatarGallery(peer: EnginePeer(peer), entries: entries, centralEntry: centralEntry, animateTransition: true) + strongSelf.openAvatarGallery(peer: peer, entries: entries, centralEntry: centralEntry, animateTransition: true) Queue.mainQueue().after(0.4) { strongSelf.resetHeaderExpansion() @@ -1628,7 +1661,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) return } - if let peer = data.peer as? TelegramUser { + if case let .user(peer) = data.peer { if strongSelf.isSettings || strongSelf.isMyProfile, let cachedData = data.cachedData as? CachedUserData { let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? "" let lastName = strongSelf.headerNode.editingContentNode.editingTextForKey(.lastName) ?? "" @@ -1874,7 +1907,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else { strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) } - } else if let group = data.peer as? TelegramGroup, canEditPeerInfo(context: strongSelf.context, peer: group, chatLocation: chatLocation, threadData: data.threadData) { + } else if case let .legacyGroup(group) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: chatLocation, threadData: data.threadData) { let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? "" let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? "" @@ -1930,7 +1963,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) })) } - } else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(context: strongSelf.context, peer: channel, chatLocation: strongSelf.chatLocation, threadData: data.threadData) { + } else if case let .channel(channel) = data.peer, canEditPeerInfo(context: strongSelf.context, peer: data.peer, chatLocation: strongSelf.chatLocation, threadData: data.threadData) { let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? "" let description = strongSelf.headerNode.editingContentNode.editingTextForKey(.description) ?? "" @@ -2101,7 +2134,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in - guard let strongSelf = self, let data = strongSelf.data, let user = data.peer as? TelegramUser else { + guard let strongSelf = self, let data = strongSelf.data, case let .user(user) = data.peer else { return } var actions: [ContextMenuAction] = [] @@ -2317,12 +2350,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] c, f in if let strongSelf = self, let parent = strongSelf.controller { - presentPeerReportOptions(context: context, parent: parent, contextController: c, subject: .profilePhoto(peer.id, 0), completion: { _, _ in }) + presentPeerReportOptions(context: context, parent: parent, contextController: c, subject: .profilePhoto(peer.id, 0, sourceMessageId: strongSelf.sourceMessageId), completion: { _, _ in }) } })) ] - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) @@ -2374,9 +2407,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self, let navigationController = self.controller?.navigationController as? NavigationController, let peer = self.data?.peer else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) } - + if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) { self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId)) } @@ -2457,14 +2490,37 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self?.updateNavigation(transition: .immediate, additive: true, animateHeader: true) } + let reactionSourceMessage: Signal + if let reactionSourceMessageId = self.reactionSourceMessageId { + reactionSourceMessage = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: reactionSourceMessageId)) + } else { + reactionSourceMessage = .single(nil) + } + self.dataDisposable = combineLatest( queue: Queue.mainQueue(), screenData, - self.forceIsContactPromise.get() - ).startStrict(next: { [weak self] data, forceIsContact in + self.forceIsContactPromise.get(), + reactionSourceMessage + ).startStrict(next: { [weak self] data, forceIsContact, reactionSourceMessage in guard let strongSelf = self else { return } + + var canDeleteReaction = false + if let sourceMessage = reactionSourceMessage?._asMessage(), let reactionPeerId = data.peer?.id, let channel = sourceMessage.peers[sourceMessage.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) { + for attribute in sourceMessage.attributes { + guard let attribute = attribute as? ReactionsMessageAttribute else { + continue + } + if attribute.recentPeers.contains(where: { $0.peerId == reactionPeerId }) || attribute.topPeers.contains(where: { $0.peerId == reactionPeerId }) { + canDeleteReaction = true + break + } + } + } + strongSelf.canDeleteReaction = canDeleteReaction + if data.isContact && forceIsContact { strongSelf.forceIsContactPromise.set(false) } else { @@ -2474,12 +2530,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.cachedDataPromise.set(.single(data.cachedData)) }) - if let _ = nearbyPeerDistance { - self.preloadHistoryDisposable.set(self.context.account.addAdditionalPreloadHistoryPeerId(peerId: peerId)) - - self.context.prefetchManager?.prepareNextGreetingSticker() - } - self.customStatusDisposable = (self.customStatusPromise.get() |> deliverOnMainQueue).startStrict(next: { [weak self] value in guard let strongSelf = self else { @@ -2549,7 +2599,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if self.headerNode.avatarListNode.avatarContainerNode.storyProgress != mappedValue { self.headerNode.avatarListNode.avatarContainerNode.storyProgress = mappedValue - self.headerNode.avatarListNode.avatarContainerNode.updateStoryView(transition: .immediate, theme: self.presentationData.theme, peer: peer?._asPeer()) + self.headerNode.avatarListNode.avatarContainerNode.updateStoryView(transition: .immediate, theme: self.presentationData.theme, peer: peer) } }) self.expiringStoryListDisposable = (combineLatest(queue: .mainQueue(), @@ -2695,7 +2745,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } } - if let channel = data.peer as? TelegramChannel, channel.isForumOrMonoForum, self.chatLocation.threadId == nil { + if case let .channel(channel) = data.peer, channel.isForumOrMonoForum, self.chatLocation.threadId == nil { if self.forumTopicNotificationExceptionsDisposable == nil { self.forumTopicNotificationExceptionsDisposable = (self.context.engine.peers.forumChannelTopicNotificationExceptions(id: channel.id) |> deliverOnMainQueue).startStrict(next: { [weak self] list in @@ -2744,10 +2794,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var previousPhotoIsPersonal: Bool? var currentPhotoIsPersonal: Bool? - if let previousUser = previousData?.peer as? TelegramUser { + if case let .user(previousUser) = previousData?.peer { previousPhotoIsPersonal = previousUser.profileImageRepresentations.first?.isPersonal == true } - if let user = data.peer as? TelegramUser { + if case let .user(user) = data.peer { currentPhotoIsPersonal = user.profileImageRepresentations.first?.isPersonal == true } @@ -2906,7 +2956,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.controller?.push(controller) openUrlImpl = { [weak self, weak controller] url, concealed, forceUpdate, commit in - let _ = openUserGeneratedUrl(context: context, peerId: peerId, url: url, concealed: concealed, present: { [weak self] c in + let _ = context.sharedContext.openUserGeneratedUrl(context: context, peerId: peerId, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { [weak self] c in self?.controller?.present(c, in: .window(.root)) }, openResolved: { result in var navigationController: NavigationController? @@ -2932,7 +2982,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, dismissInput: { context.sharedContext.mainWindow?.viewController?.view.endEditing(false) }, contentContext: nil, progress: nil, completion: nil) - }) + }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil) } presentImpl = { [weak controller] c, a in controller?.present(c, in: .window(.root), with: a) @@ -3034,7 +3084,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func openAutoremove(currentValue: Int32?) { - let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in + let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .autoremove, currentTime: currentValue, completion: { [weak self] value in guard let strongSelf = self else { return } @@ -3063,7 +3113,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func openCustomMute() { - let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in + let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .mute, currentTime: nil, completion: { [weak self] value in guard let strongSelf = self, let peer = strongSelf.data?.peer else { return } @@ -3188,7 +3238,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) } - func openClearHistory(contextController: ContextControllerProtocol, clearPeerHistory: ClearPeerHistory, peer: Peer, chatPeer: Peer) { + func openClearHistory(contextController: ContextControllerProtocol, clearPeerHistory: ClearPeerHistory, peer: Peer, chatPeer: EnginePeer) { var subItems: [ContextMenuItem] = [] subItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in @@ -3205,9 +3255,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let title: String switch anyType { case .user, .secretChat, .savedMessages: - title = self.presentationData.strings.PeerInfo_ClearConfirmationUser(EnginePeer(chatPeer).compactDisplayTitle).string + title = self.presentationData.strings.PeerInfo_ClearConfirmationUser(chatPeer.compactDisplayTitle).string case .group, .channel: - title = self.presentationData.strings.PeerInfo_ClearConfirmationGroup(EnginePeer(chatPeer).compactDisplayTitle).string + title = self.presentationData.strings.PeerInfo_ClearConfirmationGroup(chatPeer.compactDisplayTitle).string } subItems.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in @@ -3226,7 +3276,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let text: String switch canClearForEveryone { case .user, .secretChat, .savedMessages: - text = self.presentationData.strings.Conversation_DeleteMessagesFor(EnginePeer(chatPeer).compactDisplayTitle).string + text = self.presentationData.strings.Conversation_DeleteMessagesFor(chatPeer.compactDisplayTitle).string case .channel, .group: text = self.presentationData.strings.Conversation_DeleteMessagesForEveryone } @@ -3244,7 +3294,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let text: String switch canClearForMyself { case .secretChat: - text = self.presentationData.strings.Conversation_DeleteMessagesFor(EnginePeer(chatPeer).compactDisplayTitle).string + text = self.presentationData.strings.Conversation_DeleteMessagesFor(chatPeer.compactDisplayTitle).string default: text = self.presentationData.strings.Conversation_DeleteMessagesForMe } @@ -3384,7 +3434,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro |> deliverOnMainQueue).startStrict(completed: { [weak self] in if let strongSelf = self, let peer = strongSelf.data?.peer { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }) + let controller = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_DeletedFromContacts(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }) controller.keepOnParentDismissal = true strongSelf.controller?.present(controller, in: .window(.root)) @@ -3430,7 +3480,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let presentationData = strongSelf.presentationData if case let .user(peer) = peer, let _ = peer.botInfo { - strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: block).startStrict()) + strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: block, sourceMessageId: strongSelf.sourceMessageId).startStrict()) if !block { let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: [.message(text: "/start", attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() if let navigationController = strongSelf.controller?.navigationController as? NavigationController { @@ -3455,12 +3505,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } - strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: true).startStrict()) + strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: true, sourceMessageId: strongSelf.sourceMessageId).startStrict()) if deleteChat { let _ = strongSelf.context.engine.peers.removePeerChat(peerId: strongSelf.peerId, reportChatSpam: reportSpam).startStandalone() (strongSelf.controller?.navigationController as? NavigationController)?.popToRoot(animated: true) } else if reportSpam { - let _ = strongSelf.context.engine.peers.reportPeer(peerId: strongSelf.peerId, reason: .spam, message: "").startStandalone() + let _ = strongSelf.context.engine.peers.reportPeer(peerId: strongSelf.peerId, reason: .spam, message: "", sourceMessageId: strongSelf.sourceMessageId).startStandalone() } deleteSendMessageIntents(peerId: strongSelf.peerId) @@ -3481,7 +3531,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let strongSelf = self else { return } - strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: block).startStrict()) + strongSelf.activeActionDisposable.set(strongSelf.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peer.id, isBlocked: block, sourceMessageId: strongSelf.sourceMessageId).startStrict()) })]), in: .window(.root)) } } @@ -3499,7 +3549,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.view.endEditing(true) let statsController: ViewController - if let channel = peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = peer, case .group = channel.info { if case .monetization = section { statsController = channelStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id, section: section, existingStarsRevenueContext: data.starsRevenueStatsContext, boostStatus: boostStatus) } else { @@ -3512,7 +3562,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func openBoost() { - guard let peer = self.data?.peer, let channel = peer as? TelegramChannel, let controller = self.controller else { + guard case let .channel(channel) = self.data?.peer, let controller = self.controller else { return } @@ -3562,7 +3612,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - self.activeActionDisposable.set(self.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: self.peerId, isBlocked: true).startStrict()) + self.activeActionDisposable.set(self.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: self.peerId, isBlocked: true, sourceMessageId: sourceMessageId).startStrict()) self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .emoji(name: "PoliceCar", text: self.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) }), ActionSheetButtonItem(title: presentationData.strings.ReportPeer_ReportReaction_Report, action: { [weak self] in @@ -3585,7 +3635,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro default: contextController?.dismiss() - self.context.sharedContext.makeContentReportScreen(context: self.context, subject: .peer(self.peerId), forceDark: false, present: { [weak self] controller in + let reportSubject = ReportContentSubject.peer(self.peerId, sourceMessageId: self.sourceMessageId) + self.context.sharedContext.makeContentReportScreen(context: self.context, subject: reportSubject, forceDark: false, present: { [weak self] controller in self?.controller?.push(controller) }, completion: { }, requestSelectMessages: { [weak self] title, option, message in @@ -3598,7 +3649,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let data = self.data, let peer = data.peer, let encryptionKeyFingerprint = data.encryptionKeyFingerprint else { return } - self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: EnginePeer(peer))) + self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: peer)) } func openShareLink(url: String) { @@ -3697,7 +3748,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } func performBotCommand(command: PeerInfoBotCommand) { - let _ = (self.context.account.postbox.loadedPeerWithId(peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in guard let strongSelf = self else { return @@ -3714,7 +3772,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let _ = enqueueMessages(account: strongSelf.context.account, peerId: peer.id, messages: [.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() if let peer = strongSelf.data?.peer, let navigationController = strongSelf.controller?.navigationController as? NavigationController { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)))) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer))) } }) } @@ -3738,7 +3796,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func editingOpenPublicLinkSetup() { - if let peer = self.data?.peer as? TelegramUser, peer.botInfo != nil { + if case let .user(peer) = self.data?.peer, peer.botInfo != nil { let controller = usernameSetupController(context: self.context, mode: .bot(self.peerId)) self.controller?.push(controller) } else { @@ -3758,7 +3816,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } private func editingOpenAffiliateProgram() { - if let peer = self.data?.peer as? TelegramUser, let botInfo = peer.botInfo { + if case let .user(peer) = self.data?.peer, let botInfo = peer.botInfo { if botInfo.flags.contains(.canEdit) { let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id, mode: .editProgram) |> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in @@ -3948,7 +4006,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } if self.peerId == self.context.account.peerId { controller.push(UserAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData)) - } else if let peer = self.data?.peer, peer is TelegramChannel { + } else if case .channel = self.data?.peer { controller.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, boostStatus: self.boostStatus)) } } @@ -3986,6 +4044,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro })) }) } + + private func editingOpenBusinessChatBots() { + let _ = (self.context.sharedContext.makeChatbotSetupScreenInitialData(context: self.context) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] initialData in + guard let self else { + return + } + self.controller?.push(self.context.sharedContext.makeChatbotSetupScreen(context: self.context, initialData: initialData)) + }) + } private func editingOpenInviteLinksSetup() { self.controller?.push(inviteLinkListController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, admin: nil)) @@ -4035,7 +4104,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let data = self.data, let peer = data.peer else { return } - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { let subscription = Promise() subscription.set(PeerAllowedReactionsScreen.content(context: self.context, peerId: peer.id)) let _ = (subscription.get() |> take(1) |> deliverOnMainQueue).start(next: { [weak self] content in @@ -4089,7 +4158,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let data = self.data, let peer = data.peer else { return } - if peer is TelegramGroup { + if case .legacyGroup = peer { if isEnabled { let context = self.context let signal: Signal = self.context.engine.peers.convertGroupToSupergroup(peerId: self.peerId, additionalProcessing: { upgradedPeerId -> Signal in @@ -4148,9 +4217,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro case .members: self.controller?.push(channelMembersController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId)) case .admins: - if peer is TelegramGroup { + if case .legacyGroup = peer { self.controller?.push(channelAdminsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId)) - } else if peer is TelegramChannel { + } else if case .channel = peer { self.controller?.push(channelAdminsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId)) } case .banned: @@ -4212,7 +4281,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let context = self.context let presentationData = self.presentationData - let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, venue: MapVenue(title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil) + let map = TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, heading: nil, accuracyRadius: nil, venue: MapVenue(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), address: location.address, provider: nil, id: nil, type: nil), liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil) let controllerParams = LocationViewParams(sendLiveLocation: { _ in }, stopLiveLocation: { _ in @@ -4221,50 +4290,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }, openPeer: { _ in }, showAll: false) - let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer, text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peer._asPeer(), text: "", attributes: [], media: [map], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let controller = LocationViewController(context: context, updatedPresentationData: self.controller?.updatedPresentationData, subject: EngineMessage(message), params: controllerParams) self.controller?.push(controller) } - - private func editingOpenSetupLocation() { - guard let data = self.data, let peer = data.peer else { - return - } - let controller = LocationPickerController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, mode: .pick, completion: { [weak self] location, _, _, address, _ in - guard let strongSelf = self else { - return - } - let addressSignal: Signal - if let address = address { - addressSignal = .single(address) - } else { - addressSignal = reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude) - |> map { placemark in - if let placemark = placemark { - return placemark.fullAddress - } else { - return "\(location.latitude), \(location.longitude)" - } - } - } - - let context = strongSelf.context - let _ = (addressSignal - |> mapToSignal { address -> Signal in - return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peer.id, coordinate: (location.latitude, location.longitude), address: address) - } - |> deliverOnMainQueue).startStandalone() - }) - self.controller?.push(controller) - } - - private func openPeerInfo(peer: Peer, isMember: Bool) { - var mode: PeerInfoControllerMode = .generic - if isMember { - mode = .group(self.peerId) - } + private func openPeerInfo(peer: EnginePeer, isMember: Bool) { + let mode: PeerInfoControllerMode = .generic if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { (self.controller?.navigationController as? NavigationController)?.pushViewController(infoController) } @@ -4461,7 +4494,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro ActionSheetTextItem(title: text), ActionSheetButtonItem(title: title, color: .destructive, action: { dismissAction() - self?.deletePeerChat(peer: peer._asPeer(), globally: true) + self?.deletePeerChat(peer: peer, globally: true) }), ]), ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) @@ -4515,32 +4548,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro strongSelf.controller?.present(textAlertController(context: strongSelf.context, title: title, text: text, actions: [ TextAlertAction(type: .destructiveAction, title: actionText, action: { - self?.deletePeerChat(peer: peer._asPeer(), globally: delete) + self?.deletePeerChat(peer: peer, globally: delete) }), - TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }) ], parseMarkdown: true), in: .window(.root)) }) } - private func deletePeerChat(peer: Peer, globally: Bool) { + private func deletePeerChat(peer: EnginePeer, globally: Bool) { guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { return } - guard let tabController = navigationController.viewControllers.first as? TabBarController else { + guard let navigationTarget = resolveChatListNavigationTarget(navigationController: navigationController, excluding: controller) else { return } - for childController in tabController.controllers { - if let chatListController = childController as? ChatListController { - chatListController.maybeAskForPeerChatRemoval(peer: EngineRenderedPeer(peer: EnginePeer(peer)), joined: false, deleteGloballyIfPossible: globally, completion: { [weak navigationController] deleted in - if deleted { - navigationController?.popToRoot(animated: true) - } - }, removed: { - }) - break + navigationTarget.chatListController.maybeAskForPeerChatRemoval(peer: EngineRenderedPeer(peer: peer), joined: false, deleteGloballyIfPossible: globally, completion: { [weak navigationController] deleted in + guard deleted, let navigationController = navigationController else { + return } - } + if let popToController = navigationTarget.popToController { + let _ = navigationController.popToViewController(popToController, animated: true) + } else { + navigationController.popToRoot(animated: true) + } + }, removed: { + }) } func deleteProfilePhoto(_ item: PeerInfoAvatarListItem) { @@ -4669,7 +4702,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } self.postingAvailabilityDisposable?.dispose() - if let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo { + if let data = self.data, case let .user(user) = data.peer, let botInfo = user.botInfo { if !botInfo.flags.contains(.canEdit) { return } @@ -4788,7 +4821,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return nil } - if let data = self.data, let user = data.peer as? TelegramUser, let _ = user.botInfo { + if let data = self.data, case let .user(user) = data.peer, let _ = user.botInfo { if let pane = self.paneContainerNode.currentPane?.node as? PeerInfoStoryPaneNode, let transitionView = pane.extractPendingStoryTransitionView() { return StoryCameraTransitionOut( destinationView: transitionView, @@ -5161,7 +5194,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro navigationController: navigationController, chatController: nil, context: strongSelf.context, - chatLocation: .peer(EnginePeer(peer)), + chatLocation: .peer(peer), subject: .message(id: .id(index.id), highlight: nil, timecode: nil, setupReply: false), botStart: nil, updateTextInputState: nil, @@ -5171,7 +5204,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro scrollToEndIfExists: false, activateMessageSearch: nil, peekData: nil, - peerNearbyData: nil, reportReason: nil, animated: true, options: [], @@ -5258,7 +5290,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let peer = self.data?.peer else { return } - let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.UserInfo_ResetToOriginalAlertText(EnginePeer(peer).compactDisplayTitle).string, actions: [ + let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.UserInfo_ResetToOriginalAlertText(peer.compactDisplayTitle).string, actions: [ TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { }), @@ -5267,7 +5299,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return } strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in - mapResourceToAvatarSizes(postbox: strongSelf.context.account.postbox, resource: resource, representations: representations) + mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations) }) |> deliverOnMainQueue).startStrict(next: { [weak self] _ in guard let strongSelf = self else { @@ -5352,7 +5384,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro var contentHeight: CGFloat = 0.0 let sectionInset: CGFloat - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { sectionInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) } else { sectionInset = 0.0 @@ -5377,7 +5409,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro insets.left += sectionInset insets.right += sectionInset - let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat, isMyProfile: self.isMyProfile) + let items = self.isSettings ? settingsItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, isExpanded: self.headerNode.isAvatarExpanded) : infoItems(data: self.data, context: self.context, presentationData: self.presentationData, interaction: self.interaction, reactionSourceMessageId: self.reactionSourceMessageId, canDeleteReaction: self.canDeleteReaction, callMessages: self.callMessages, chatLocation: self.chatLocation, isOpenedFromChat: self.isOpenedFromChat, isMyProfile: self.isMyProfile) contentHeight += headerHeight if !((self.isSettings || self.isMyProfile) && self.state.isEditing) { @@ -5598,13 +5630,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let type: PeerType if isBot { type = .bot - } else if let user = peer as? TelegramUser { + } else if case let .user(user) = peer { if user.botInfo != nil && !user.id.isVerificationCodes { type = .bot } else { type = .user } - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + } else if case let .channel(channel) = peer, case .broadcast = channel.info { type = .channel } else { type = .group @@ -5758,7 +5790,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro if !additive { let sectionInset: CGFloat - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { sectionInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) } else { sectionInset = 0.0 @@ -5820,7 +5852,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro } else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) } - if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, let channel = data.peer as? TelegramChannel, channel.hasPermission(.postStories) { + if let data = self.data, !data.isPremiumRequiredForStoryPosting || data.accountIsPremium, case let .channel(channel) = data.peer, channel.hasPermission(.postStories) { rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0) } else if self.isMyProfile { rightNavigationButtons.insert(PeerInfoHeaderNavigationButtonSpec(key: .postStory, isForExpandedView: false), at: 0) @@ -5847,7 +5879,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro case .media: rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) case .botPreview: - if let data = self.data, data.hasBotPreviewItems, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { + if let data = self.data, data.hasBotPreviewItems, case let .user(user) = data.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) } case .stories: @@ -6107,14 +6139,19 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.joinChannelDisposable.set(( self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peer.id, hash: nil) |> deliverOnMainQueue - |> afterCompleted { [weak self] in - Queue.mainQueue().async { - if let self { - self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - } + ).startStrict(next: { [weak self] result in + guard let self else { + return + } + switch result { + case .joined: + self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + case let .webView(webView): + if let controller = self.controller { + self.context.sharedContext.openJoinChatWebView(context: self.context, parentController: controller, updatedPresentationData: self.controller?.updatedPresentationData, webView: webView) } } - ).startStrict(error: { [weak self] error in + }, error: { [weak self] error in guard let self else { return } @@ -6289,9 +6326,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc public let peerId: PeerId private let avatarInitiallyExpanded: Bool private let isOpenedFromChat: Bool - private let nearbyPeerDistance: Int32? private let reactionSourceMessageId: MessageId? - private let callMessages: [Message] + private let sourceMessageId: MessageId? + private let callMessages: [EngineMessage] let isSettings: Bool let isMyProfile: Bool private let hintGroupInCommon: PeerId? @@ -6377,9 +6414,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc peerId: PeerId, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, - nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, - callMessages: [Message], + sourceMessageId: MessageId? = nil, + callMessages: [EngineMessage], isSettings: Bool = false, isMyProfile: Bool = false, hintGroupInCommon: PeerId? = nil, @@ -6398,8 +6435,8 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc self.peerId = peerId self.avatarInitiallyExpanded = avatarInitiallyExpanded self.isOpenedFromChat = isOpenedFromChat - self.nearbyPeerDistance = nearbyPeerDistance self.reactionSourceMessageId = reactionSourceMessageId + self.sourceMessageId = sourceMessageId self.callMessages = callMessages self.isSettings = isSettings self.isMyProfile = isMyProfile @@ -6459,6 +6496,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc badgeStrokeColor: baseNavigationBarPresentationData.theme.badgeStrokeColor, badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor, accentButtonColor: baseNavigationBarPresentationData.theme.accentButtonColor, + accentDisabledButtonColor: baseNavigationBarPresentationData.theme.accentDisabledButtonColor, accentForegroundColor: baseNavigationBarPresentationData.theme.accentForegroundColor ), strings: baseNavigationBarPresentationData.strings)) @@ -6526,7 +6564,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc if let primary = primary { let size = CGSize(width: 31.0, height: 31.0) let inset: CGFloat = 3.0 - if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1._asPeer()), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) { + if let signal = peerAvatarImage(account: primary.0, peerReference: PeerReference(primary.1), authorOfMessage: nil, representation: primary.1.profileImageRepresentations.first, displayDimensions: size, inset: 3.0, emptyColor: nil, synchronousLoad: false) { return signal |> map { imageVersions -> (UIImage, UIImage)? in if let image = imageVersions?.0 { @@ -6735,6 +6773,19 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc }) self.updateTabBarSearchState(ViewController.TabBarSearchState(isActive: false), transition: .immediate) + + if let sourceMessageId { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> deliverOnMainQueue).start(next: { peer in + guard case let .user(user) = peer else { + return + } + if case .personal = user.accessHash { + } else { + let _ = context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId, sourceMessageId: sourceMessageId).startStandalone() + } + }) + } } required init(coder aDecoder: NSCoder) { @@ -6766,7 +6817,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc initialPaneKey = .files } } - self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, nearbyPeerDistance: self.nearbyPeerDistance, reactionSourceMessageId: self.reactionSourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, switchToGiftsTarget: self.switchToGiftsTarget, switchToStoryFolder: self.switchToStoryFolder, switchToMediaTarget: self.switchToMediaTarget, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic) + self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, isOpenedFromChat: self.isOpenedFromChat, reactionSourceMessageId: self.reactionSourceMessageId, sourceMessageId: self.sourceMessageId, callMessages: self.callMessages, isSettings: self.isSettings, isMyProfile: self.isMyProfile, hintGroupInCommon: self.hintGroupInCommon, requestsContext: self.requestsContext, profileGiftsContext: self.profileGiftsContext, starsContext: self.starsContext, tonContext: self.tonContext, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, switchToGiftsTarget: self.switchToGiftsTarget, switchToStoryFolder: self.switchToStoryFolder, switchToMediaTarget: self.switchToMediaTarget, initialPaneKey: initialPaneKey, sharedMediaFromForumTopic: self.sharedMediaFromForumTopic) self.controllerNode.accountsAndPeers.set(self.accountsAndPeers.get() |> map { $0.1 }) self.controllerNode.activeSessionsContextAndCount.set(self.activeSessionsContextAndCount.get()) self.cachedDataPromise.set(self.controllerNode.cachedDataPromise.get()) @@ -6874,7 +6925,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: subject, keepStack: .always, peekData: peekData)) case .info: if peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(infoController) } } @@ -7177,7 +7228,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc }) { let _ = navigationController.popToViewController(infoController, animated: false) } else { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.replaceController(sourceController, with: infoController, animated: false) } } @@ -7375,16 +7426,16 @@ struct ClearPeerHistory { var canClearForMyself: ClearType? = nil var canClearForEveryone: ClearType? = nil - init(context: AccountContext, peer: Peer, chatPeer: Peer, cachedData: CachedPeerData?) { + init(context: AccountContext, peer: Peer, chatPeer: EnginePeer, cachedData: CachedPeerData?) { if peer.id == context.account.peerId { canClearCache = false canClearForMyself = .savedMessages canClearForEveryone = nil - } else if chatPeer is TelegramSecretChat { + } else if case .secretChat = chatPeer { canClearCache = false canClearForMyself = .secretChat canClearForEveryone = nil - } else if let group = chatPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = chatPeer { canClearCache = false switch group.role { @@ -7395,7 +7446,7 @@ struct ClearPeerHistory { canClearForMyself = .group canClearForEveryone = nil } - } else if let channel = chatPeer as? TelegramChannel { + } else if case let .channel(channel) = chatPeer { var canDeleteHistory = false if let cachedData = cachedData as? CachedChannelData { canDeleteHistory = cachedData.flags.contains(.canDeleteHistory) @@ -7423,7 +7474,7 @@ struct ClearPeerHistory { canClearCache = false canClearForMyself = .user - if let user = chatPeer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = chatPeer, user.botInfo != nil { canClearForEveryone = nil } else { canClearForEveryone = .user diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 0c83494846..754acfe19a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -25,7 +25,7 @@ private func sharedSetupProfilePhotoUpload(context: AccountContext, image: UIIma } let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom) let _ = representation @@ -73,7 +73,7 @@ public extension PeerInfoScreenImpl { var avatarPickerHolder: Any? let _ = avatarPickerHolder - let (mainController, pickerHolder) = context.sharedContext.makeAvatarMediaPickerScreen(context: context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { + let (mainController, pickerHolder) = context.sharedContext.makeAvatarMediaPickerScreen(context: context, peerType: PeerType.getType(for: peer), getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { }, completion: { [weak parentController] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in avatarPickerHolder = nil @@ -245,11 +245,10 @@ public extension PeerInfoScreenImpl { return } - let postbox = context.account.postbox let signal: Signal - - signal = context.engine.peers.updatePeerPhoto(peerId: peer.id, photo: context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + + signal = context.engine.peers.updatePeerPhoto(peerId: peer.id, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) var dismissStatus: (() -> Void)? @@ -409,8 +408,8 @@ public extension PeerInfoScreenImpl { let updateAvatarDisposable = MetaDisposable() updateAvatarDisposable.set((videoResource |> mapToSignal { videoResource -> Signal in - return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } |> deliverOnMainQueue).startStandalone(next: { result in @@ -420,7 +419,7 @@ public extension PeerInfoScreenImpl { case let .progress(value): uploadStatus?.set(.single(.progress(value))) } - + if case .complete = result { dismissStatus?() } @@ -440,7 +439,7 @@ extension PeerInfoScreenImpl { let peerId = self.peerId var isForum = false - if let peer = peer as? TelegramChannel, peer.isForumOrMonoForum { + if case let .channel(peer) = peer, peer.isForumOrMonoForum { isForum = true } @@ -512,7 +511,7 @@ extension PeerInfoScreenImpl { let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController var dismissImpl: (() -> Void)? - let (mainController, pickerHolder) = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in + let (mainController, pickerHolder) = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, peerType: PeerType.getType(for: peer), getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in self?.openAvatarRemoval(mode: mode, peer: peer, item: item) }, completion: { [weak self] result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in guard let self else { @@ -666,7 +665,11 @@ extension PeerInfoScreenImpl { } if mainController is ActionSheetController { - self.present(mainController, in: .window(.root)) + if let navigationController = self.navigationController, let topController = navigationController.topViewController as? ViewController { + topController.present(mainController, in: .window(.root)) + } else { + self.present(mainController, in: .window(.root)) + } } else { mainController.navigationPresentation = .flatModal mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) @@ -699,11 +702,10 @@ extension PeerInfoScreenImpl { } } } - let postbox = strongSelf.context.account.postbox let signal: Signal if case .custom = mode { signal = strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + return mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations) }) } else if case .fallback = mode { signal = strongSelf.context.engine.accountData.removeFallbackPhoto(reference: nil) @@ -713,7 +715,7 @@ extension PeerInfoScreenImpl { } } else { signal = strongSelf.context.engine.peers.updatePeerPhoto(peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + return mapResourceToAvatarSizes(engine: strongSelf.context.engine, resource: resource, representations: representations) }) } strongSelf.controllerNode.updateAvatarDisposable.set((signal @@ -766,7 +768,7 @@ extension PeerInfoScreenImpl { self.controllerNode.scrollNode.view.setContentOffset(CGPoint(), animated: false) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom) if [.suggest, .fallback].contains(mode) { @@ -794,29 +796,28 @@ extension PeerInfoScreenImpl { return } - let postbox = self.context.account.postbox let signal: Signal if self.isSettings || self.isMyProfile { if case .fallback = mode { - signal = self.context.engine.accountData.updateFallbackPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + signal = self.context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations) }) } else { - signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + signal = self.context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations) }) } } else if case .custom = mode { - signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations) }) } else if case .suggest = mode { - signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: EngineMediaResource(resource), videoResource: nil, videoStartTimestamp: nil, markup: nil, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations) }) } else { - signal = self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: resource), mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) + signal = self.context.engine.peers.updatePeerPhoto(peerId: self.peerId, photo: self.context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: self.context.engine, resource: resource, representations: representations) }) } @@ -1020,25 +1021,25 @@ extension PeerInfoScreenImpl { |> mapToSignal { videoResource -> Signal in if isSettings || isMyProfile { if case .fallback = mode { - return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else { - return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } } else if case .custom = mode { - return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else if case .suggest = mode { - return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else { - return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } } @@ -1234,25 +1235,25 @@ extension PeerInfoScreenImpl { |> mapToSignal { videoResource -> Signal in if isSettings || isMyProfile { if case .fallback = mode { - return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.accountData.updateFallbackPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else { - return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.accountData.updateAccountPhoto(resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } } else if case .custom = mode { - return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else if case .suggest = mode { - return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: EngineMediaResource(photoResource), videoResource: videoResource.flatMap(EngineMediaResource.init), videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } else { - return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource($0)) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift index a2d2af6767..75cac3afb1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenBusinessActions.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import ContextUI diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift index f27cded7ca..807e27b106 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenCallActions.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import ContextUI @@ -20,7 +19,7 @@ extension PeerInfoScreenNode { return } let peerId = self.peerId - let requestCall: (PeerId?, EngineGroupCallDescription?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in + let requestCall: (EnginePeer.Id?, EngineGroupCallDescription?) -> Void = { [weak self] defaultJoinAsPeerId, activeCall in if let activeCall = activeCall { self?.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { completion in if let defaultJoinAsPeerId = defaultJoinAsPeerId { @@ -45,7 +44,7 @@ extension PeerInfoScreenNode { return } - guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { + guard case let .user(peer) = self.data?.peer, let cachedUserData = self.data?.cachedData as? CachedUserData else { return } if cachedUserData.callsPrivate { @@ -74,7 +73,7 @@ extension PeerInfoScreenNode { self.controller?.push(CreateExternalMediaStreamScreen(context: self.context, peerId: self.peerId, credentialsPromise: credentialsPromise, mode: .create(liveStream: false))) } - func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { + func createAndJoinGroupCall(peerId: EnginePeer.Id, joinAsPeerId: EnginePeer.Id?) { guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } @@ -128,7 +127,7 @@ extension PeerInfoScreenNode { case .generic, .scheduledTooLate: text = strongSelf.presentationData.strings.Login_UnknownError case .anonymousNotAllowed: - if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = strongSelf.data?.peer, case .broadcast = channel.info { text = strongSelf.presentationData.strings.LiveStream_AnonymousDisabledAlertText } else { text = strongSelf.presentationData.strings.VoiceChat_AnonymousDisabledAlertText @@ -142,9 +141,16 @@ extension PeerInfoScreenNode { } } - func openVoiceChatDisplayAsPeerSelection(completion: @escaping (PeerId) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { + func openVoiceChatDisplayAsPeerSelection(completion: @escaping (EnginePeer.Id) -> Void, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil, result: ((ContextMenuActionResult) -> Void)? = nil, backAction: ((ContextControllerProtocol) -> Void)? = nil) { let dismissOnSelection = contextController == nil - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -165,10 +171,10 @@ extension PeerInfoScreenNode { var isGroup = false for peer in peers { - if peer.peer is TelegramGroup { + if case .legacyGroup = peer.peer { isGroup = true break - } else if let peer = peer.peer as? TelegramChannel, case .group = peer.info { + } else if case let .channel(channel) = peer.peer, case .group = channel.info { isGroup = true break } @@ -183,7 +189,7 @@ extension PeerInfoScreenNode { if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount } else if let subscribers = peer.subscribers { - if let peer = peer.peer as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(channel) = peer.peer, case .broadcast = channel.info { subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) } else { subtitle = strongSelf.presentationData.strings.Conversation_StatusMembers(subscribers) @@ -191,8 +197,8 @@ extension PeerInfoScreenNode { } let avatarSize = CGSize(width: 28.0, height: 28.0) - let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize) - items.append(.action(ContextMenuActionItem(text: EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize) + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in if dismissOnSelection { f(.dismissWithoutContent) } @@ -239,14 +245,21 @@ extension PeerInfoScreenNode { }) } - func openVoiceChatOptions(defaultJoinAsPeerId: PeerId?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { + func openVoiceChatOptions(defaultJoinAsPeerId: EnginePeer.Id?, gesture: ContextGesture? = nil, contextController: ContextControllerProtocol? = nil) { guard let chatPeer = self.data?.peer else { return } let context = self.context let peerId = self.peerId let defaultJoinAsPeerId = defaultJoinAsPeerId ?? self.context.account.peerId - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return [FoundPeer(peer: peer, subscribers: nil)] } @@ -271,7 +284,7 @@ extension PeerInfoScreenNode { } if let peer = selectedPeer { let avatarSize = CGSize(width: 28.0, height: 28.0) - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(EnginePeer(peer.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: EnginePeer(peer.peer), size: avatarSize)), action: { c, f in + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize)), action: { c, f in guard let strongSelf = self else { return } @@ -290,7 +303,7 @@ extension PeerInfoScreenNode { let createVoiceChatTitle: String let scheduleVoiceChatTitle: String - if let channel = strongSelf.data?.peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = strongSelf.data?.peer, case .broadcast = channel.info { createVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_CreateLiveStream scheduleVoiceChatTitle = strongSelf.presentationData.strings.ChannelInfo_ScheduleLiveStream } else { @@ -313,11 +326,11 @@ extension PeerInfoScreenNode { var credentialsPromise: Promise? var canCreateStream = false switch chatPeer { - case let group as TelegramGroup: + case let .legacyGroup(group): if case .creator = group.role { canCreateStream = true } - case let channel as TelegramChannel: + case let .channel(channel): if channel.hasPermission(.manageCalls) { canCreateStream = true credentialsPromise = Promise() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift index b43ac185cc..5881cb69cc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayGiftsContextMenu.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import ContextUI import PeerInfoVisualMediaPaneNode @@ -26,7 +25,7 @@ extension PeerInfoScreenNode { let giftsContext = pane.giftsContext var hasVisibility = false - if let channel = data.peer as? TelegramChannel, channel.hasPermission(.sendSomething) { + if case let .channel(channel) = data.peer, channel.hasPermission(.sendSomething) { hasVisibility = true } else if data.peer?.id == self.context.account.peerId { hasVisibility = true diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift index c4a0ff8b21..2b0a724c45 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenDisplayMediaGalleryContextMenu.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import ContextUI import PeerInfoVisualMediaPaneNode @@ -30,7 +29,7 @@ extension PeerInfoScreenNode { } if case .botPreview = pane.scope { - guard let data = self.data, let user = data.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else { + guard let data = self.data, case let .user(user) = data.peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) else { return } @@ -141,7 +140,7 @@ extension PeerInfoScreenNode { self.mediaGalleryContextMenu = contextController controller.presentInGlobalOverlay(contextController) } else if case .peer = pane.scope { - guard let data = self.data, let user = data.peer as? TelegramUser else { + guard let data = self.data, case let .user(user) = data.peer else { return } let _ = user @@ -281,7 +280,7 @@ extension PeerInfoScreenNode { return } - var mediaCount: [MessageTags: Int32] = [:] + var mediaCount: [EngineMessage.Tags: Int32] = [:] for (key, count) in messageCounts { mediaCount[key.tag] = count.flatMap(Int32.init) ?? 0 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenFrozenAccount.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenFrozenAccount.swift index 9638abbe96..1466e1c280 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenFrozenAccount.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenFrozenAccount.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift index 54143e7daf..9ff46141a8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMessageActions.swift @@ -3,14 +3,13 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import TextFormat import UndoUI import ChatInterfaceState extension PeerInfoScreenNode { - func deleteMessages(messageIds: Set?) { + func deleteMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(engine: self.context.engine, accountPeerId: self.context.account.peerId, messageIds: messageIds, keepUpdated: false) |> deliverOnMainQueue).startStrict(next: { [weak self] actions in @@ -19,9 +18,9 @@ extension PeerInfoScreenNode { var items: [ActionSheetItem] = [] var personalPeerName: String? var isChannel = false - if let user = peer as? TelegramUser { - personalPeerName = EnginePeer(user).compactDisplayTitle - } else if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case .user = peer { + personalPeerName = peer.compactDisplayTitle + } else if case let .channel(channel) = peer, case .broadcast = channel.info { isChannel = true } @@ -71,7 +70,7 @@ extension PeerInfoScreenNode { } } - func forwardMessages(messageIds: Set?) { + func forwardMessages(messageIds: Set?) { if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty { let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, multipleSelection: true, selectForumThreads: true)) peerSelectionController.multiplePeersSelected = { [weak self, weak peerSelectionController] peers, peerMap, messageText, mode, forwardOptions, _ in @@ -85,7 +84,7 @@ extension PeerInfoScreenNode { let inputText = convertMarkdownToAttributes(messageText) for text in breakChatInputText(trimChatInputText(inputText)) { if text.length != 0 { - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) @@ -95,7 +94,7 @@ extension PeerInfoScreenNode { } } - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) result.append(contentsOf: messageIds.map { messageId -> EnqueueMessage in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift index 3b789a30a7..909dba2e7a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBio.swift @@ -3,12 +3,13 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramUIPreferences import ContextUI import TranslateUI +import TextProcessingScreen +import Pasteboard import UndoUI extension PeerInfoScreenNode { @@ -54,7 +55,7 @@ extension PeerInfoScreenNode { UIPasteboard.general.string = bioText let toastText: String - if let _ = self.data?.peer as? TelegramUser { + if case .user = self.data?.peer { toastText = self.presentationData.strings.MyProfile_ToastBioCopied } else { toastText = self.presentationData.strings.ChannelProfile_ToastAboutCopied @@ -88,7 +89,7 @@ extension PeerInfoScreenNode { } let copyText: String - if let _ = self.data?.peer as? TelegramUser { + if case .user = self.data?.peer { copyText = self.presentationData.strings.MyProfile_BioActionCopy } else { copyText = self.presentationData.strings.ChannelProfile_AboutActionCopy @@ -106,16 +107,24 @@ extension PeerInfoScreenNode { guard let self else { return } - - let controller = TranslateScreen(context: self.context, text: bioText, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - controller.pushController = { [weak self] c in - (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true - self?.controller?.push(c) + + Task { @MainActor [weak self] in + guard let self, let parentController = self.controller else { + return + } + let presentationData = self.presentationData + let controller = await TextProcessingScreen( + context: self.context, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: bioText, entities: []), + copyResult: { [weak parentController] text in + storeMessageTextInPasteboard(text.text, entities: text.entities) + parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }, + translateChat: nil + ) + parentController.present(controller, in: .window(.root)) } - controller.presentController = { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - } - self.controller?.present(controller, in: .window(.root)) } }))) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift index 5aab30eef2..d728aa07f5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenBirthday.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramStringFormatting diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift index 824bd0ce0b..aca8dd3fa1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenChat.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore extension PeerInfoScreenNode { @@ -22,25 +21,14 @@ extension PeerInfoScreenNode { navigationController.setViewControllers(viewControllers, animated: true) current.activateSearch(domain: .everything, query: "") } else if let peer = self.data?.chatPeer { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, activateMessageSearch: (.everything, ""), peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, activateMessageSearch: (.everything, ""))) } } } func openChatForReporting(title: String, option: Data, message: String?) { if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - if let channel = peer as? TelegramChannel, channel.isForumOrMonoForum { + if case let .channel(channel) = peer, channel.isForumOrMonoForum { //let _ = self.context.engine.peers.reportPeer(peerId: peer.id, reason: reason, message: "").startStandalone() //self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .emoji(name: "PoliceCar", text: self.presentationData.strings.Report_Succeed), elevatedLayout: false, action: { _ in return false }), in: .current) } else { @@ -48,11 +36,9 @@ extension PeerInfoScreenNode { NavigateToChatControllerParams( navigationController: navigationController, context: self.context, - chatLocation: .peer(EnginePeer(peer)), + chatLocation: .peer(peer), keepStack: .default, - reportReason: NavigateToChatControllerParams.ReportReason(title: title, option: option, message: message), - completion: { _ in - } + reportReason: NavigateToChatControllerParams.ReportReason(title: title, option: option, message: message) ) ) } @@ -61,15 +47,13 @@ extension PeerInfoScreenNode { func openChatForThemeChange() { if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: true, completion: { _ in - })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, changeColors: true)) } } - + func openChatForTranslation() { if let peer = self.data?.peer, let navigationController = (self.controller?.navigationController as? NavigationController) { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, changeColors: false, completion: { _ in - })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, changeColors: false)) } } @@ -92,45 +76,66 @@ extension PeerInfoScreenNode { } if let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) } } + func openDeleteReaction(messageId: EngineMessage.Id) { + guard let authorPeer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController else { + return + } + + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId), + TelegramEngine.EngineData.Item.Messages.Message(id: messageId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] sourcePeer, sourceMessage in + guard let self, let sourcePeer, let sourceMessage = sourceMessage?._asMessage() else { + return + } + guard let channel = sourceMessage.peers[sourceMessage.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) else { + return + } + + var hasReaction = false + for attribute in sourceMessage.attributes { + guard let attribute = attribute as? ReactionsMessageAttribute else { + continue + } + if attribute.recentPeers.contains(where: { $0.peerId == authorPeer.id }) || attribute.topPeers.contains(where: { $0.peerId == authorPeer.id }) { + hasReaction = true + break + } + } + guard hasReaction else { + return + } + + let chatLocation: NavigateToChatControllerParams.Location + if case let .channel(channel) = sourcePeer, channel.isForumOrMonoForum, let threadId = sourceMessage.threadId { + chatLocation = .replyThread(ChatReplyThreadMessage(peerId: sourcePeer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: channel.isMonoForum, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) + } else { + chatLocation = .peer(sourcePeer) + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), keepStack: .always, useExisting: true, completion: { chatController in + chatController.presentReactionDeletionOptions(author: authorPeer, messageId: messageId) + })) + }) + } + func openChatWithClearedHistory(type: InteractiveHistoryClearingType) { guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), setupController: { controller in + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, setupController: { controller in controller.beginClearHistory(type: type) - }, completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - - navigationController.setViewControllers(viewControllers, animated: false) - } })) } func openChannelMessages() { - guard let channel = self.data?.peer as? TelegramChannel, let linkedMonoforumId = channel.linkedMonoforumId else { + guard case let .channel(channel) = self.data?.peer, let linkedMonoforumId = channel.linkedMonoforumId else { return } let _ = (self.context.engine.data.get( diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift index ef1ebc78ce..90734a0c9f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMember.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramStringFormatting @@ -25,7 +24,7 @@ extension PeerInfoScreenNode { guard let self, let navigationController = self.controller?.navigationController as? NavigationController else { return } - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(member.peer)))) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(member.peer))) } }))) } @@ -51,7 +50,7 @@ extension PeerInfoScreenNode { }))) } - if actions.contains(.promote) && enclosingPeer is TelegramChannel { + if actions.contains(.promote), case .channel = enclosingPeer { var actionTitle: String = self.presentationData.strings.GroupInfo_ActionPromote if case .admin = member.role { actionTitle = self.presentationData.strings.GroupInfo_ActionEditAdmin @@ -67,7 +66,7 @@ extension PeerInfoScreenNode { } if actions.contains(.restrict) { - if enclosingPeer is TelegramChannel { + if case .channel = enclosingPeer { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.GroupInfo_ActionRestrict, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in c?.dismiss { guard let self else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift index ebaea8f357..6419048931 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenMessage.swift @@ -3,35 +3,144 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import LegacyMediaPickerUI +import MediaPickerUI import ChatHistorySearchContainerNode +import ChatScheduleTimeController import MediaResources import TelegramUIPreferences extension PeerInfoScreenNode { - func openMessage(id: MessageId) -> Bool { + private func presentMediaScheduleTimePicker(completion: @escaping (Int32, Bool) -> Void) { + guard let peerId = self.chatLocation.peerId else { + return + } + let _ = (self.context.account.viewTracker.peerView(peerId) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in + guard let self, let controller = self.controller, let peer = peerViewMainPeer(peerView) else { + return + } + + var sendWhenOnlineAvailable = false + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case .present = presence.status { + sendWhenOnlineAvailable = true + } + if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { + sendWhenOnlineAvailable = false + } + + let mode: ChatScheduleTimeScreen.Mode + if peerId == self.context.account.peerId { + mode = .reminders + } else { + mode = .scheduledMessages(peerId: peer.id, sendWhenOnlineAvailable: sendWhenOnlineAvailable) + } + + let scheduleController = ChatScheduleTimeScreen( + context: self.context, + mode: mode, + currentTime: nil, + currentRepeatPeriod: nil, + minimalTime: nil, + silentPosting: false, + isDark: true, + completion: { result in + completion(result.time, result.silentPosting) + } + ) + self.view.endEditing(true) + controller.present(scheduleController, in: .window(.root)) + }) + } + + private func openScheduledMessages() { + guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { + return + } + + var mappedChatLocation = self.chatLocation + if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId { + mappedChatLocation = .peer(id: self.context.account.peerId) + } + + let scheduledController = self.context.sharedContext.makeChatController( + context: self.context, + chatLocation: mappedChatLocation, + subject: .scheduledMessages, + botStart: nil, + mode: .standard(.default), + params: nil + ) + scheduledController.navigationPresentation = .modal + navigationController.pushViewController(scheduledController) + } + + private func transformEditedMediaMessages(_ messages: [EnqueueMessage], replyToMessageId: EngineMessage.Id, silentPosting: Bool, scheduleTime: Int32?) -> [EnqueueMessage] { + let replySubject = EngineMessageReplySubject(messageId: replyToMessageId, quote: nil, innerSubject: nil) + let defaultThreadId: Int64? + if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId { + defaultThreadId = replyThreadMessage.threadId + } else { + defaultThreadId = nil + } + + return messages.map { message in + var message = message.withUpdatedReplyToMessageId(replySubject) + + if let defaultThreadId { + var updateThreadId = false + switch message { + case let .message(_, _, _, _, threadId, _, _, _, _, _): + updateThreadId = threadId == nil + case let .forward(_, threadId, _, _, _): + updateThreadId = threadId == nil + } + if updateThreadId { + message = message.withUpdatedThreadId(defaultThreadId) + } + } + + return message.withUpdatedAttributes { attributes in + var attributes = attributes + for i in (0 ..< attributes.count).reversed() { + if attributes[i] is NotificationInfoMessageAttribute || attributes[i] is OutgoingScheduleInfoMessageAttribute { + attributes.remove(at: i) + } + } + if silentPosting { + attributes.append(NotificationInfoMessageAttribute(flags: .muted)) + } + if let scheduleTime { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) + } + return attributes + } + } + } + + func openMessage(id: EngineMessage.Id) -> Bool { guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { return false } - var foundGalleryMessage: Message? + var foundGalleryMessage: EngineMessage? if let searchContentNode = self.searchDisplayController?.contentNode as? ChatHistorySearchContainerNode { if let galleryMessage = searchContentNode.messageForGallery(id) { - self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [EngineMessage(galleryMessage)]) + self.context.engine.messages.ensureMessagesAreLocallyAvailable(messages: [galleryMessage]) foundGalleryMessage = galleryMessage } } if foundGalleryMessage == nil, let galleryMessage = self.paneContainerNode.findLoadedMessage(id: id) { foundGalleryMessage = galleryMessage } - + guard let galleryMessage = foundGalleryMessage else { return false } self.view.endEditing(true) - - return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in + + return self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: self.chatLocationContextHolder, message: galleryMessage._asMessage(), standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: { [weak self] in self?.view.endEditing(true) }, present: { [weak self] c, a, _ in self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true) @@ -39,7 +148,7 @@ extension PeerInfoScreenNode { guard let strongSelf = self else { return nil } - return strongSelf.paneContainerNode.transitionNodeForGallery(messageId: messageId, media: media) + return strongSelf.paneContainerNode.transitionNodeForGallery(messageId: messageId, media: EngineMedia(media)) }, addToTransitionSurface: { [weak self] view in guard let strongSelf = self else { return @@ -105,25 +214,42 @@ extension PeerInfoScreenNode { } var mediaReference: AnyMediaReference? - for media in message.media { - if let image = media as? TelegramMediaImage { + for media in message.engineMedia { + if case let .image(image) = media { mediaReference = AnyMediaReference.standalone(media: image) - } else if let file = media as? TelegramMediaFile { + } else if case let .file(file) = media { mediaReference = AnyMediaReference.standalone(media: file) } } - + if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + let hasSilentPosting = peer.id != strongSelf.context.account.peerId + let hasSchedule = peer.id.namespace != Namespaces.Peer.SecretChat + legacyMediaEditor(context: strongSelf.context, peer: EnginePeer(peer), threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + transitionCompletion() }, getCaptionPanelView: { return nil - }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in + }, photoToolbarView: { [context = strongSelf.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) + }, hasSilentPosting: hasSilentPosting, hasSchedule: hasSchedule, reminder: peer.id == strongSelf.context.account.peerId, presentSchedulePicker: { [weak self] _, done in + self?.presentMediaScheduleTimePicker(completion: { time, silentPosting in + done(time, silentPosting) + }) + }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, _ in if let strongSelf = self { strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!) |> deliverOnMainQueue).startStrict(next: { [weak self] messages in if let strongSelf = self { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages.map { $0.message.withUpdatedReplyToMessageId(.init(messageId: message.id, quote: nil, innerSubject: nil)) }).startStandalone() + let effectiveScheduleTime = scheduleTime == 0 ? nil : scheduleTime + let mappedMessages = strongSelf.transformEditedMediaMessages(messages.map(\.message), replyToMessageId: message.id, silentPosting: silentPosting, scheduleTime: effectiveScheduleTime) + let _ = (enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: mappedMessages) + |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in + guard let self, let effectiveScheduleTime, effectiveScheduleTime != scheduleWhenOnlineTimestamp else { + return + } + self.openScheduledMessages() + }) } })) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift index 2db0ca69fe..fff66b0adb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenNote.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import ContextUI diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift index b810ae587a..11135c8597 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPeerInfoContextMenu.swift @@ -3,11 +3,12 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import UndoUI import TranslateUI +import TextProcessingScreen +import Pasteboard import TelegramStringFormatting import TelegramUIPreferences @@ -71,16 +72,22 @@ extension PeerInfoScreenNode { let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages) if canTranslate { actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in - - let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - controller.pushController = { [weak self] c in - (self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true - self?.controller?.push(c) + Task { @MainActor [weak self] in + guard let self, let parentController = self.controller else { + return + } + let controller = await TextProcessingScreen( + context: context, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: text, entities: []), + copyResult: { [weak parentController] text in + storeMessageTextInPasteboard(text.text, entities: text.entities) + parentController?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }, + translateChat: nil + ) + parentController.present(controller, in: .window(.root)) } - controller.presentController = { [weak self] c in - self?.controller?.present(c, in: .window(.root)) - } - self?.controller?.present(controller, in: .window(.root)) })) } @@ -123,7 +130,7 @@ extension PeerInfoScreenNode { text = customLink content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) } else if let addressName = peer.addressName { - if peer is TelegramChannel { + if case .channel = peer { text = "https://t.me/\(addressName)" content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift index 9cfa3a0f54..c27895619e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenPhone.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import ContextUI diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift index f9b34f3961..858c399463 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenURL.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import OpenInExternalAppUI import PresentationDataUtils @@ -25,7 +24,7 @@ extension PeerInfoScreenNode { strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: subject, updateTextInputState: inputState, activateInput: inputState != nil ? .text : nil, keepStack: .always, peekData: peekData)) case .info: if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { strongSelf.controller?.push(infoController) } } @@ -53,7 +52,7 @@ extension PeerInfoScreenNode { } func openUrl(url: String, concealed: Bool, external: Bool, forceExternal: Bool = false, commit: @escaping () -> Void = {}) { - let _ = openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, concealed: concealed, present: { [weak self] c in + let _ = self.context.sharedContext.openUserGeneratedUrl(context: self.context, peerId: self.peerId, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { [weak self] c in self?.controller?.present(c, in: .window(.root)) }, openResolved: { [weak self] tempResolved in guard let strongSelf = self else { @@ -77,17 +76,17 @@ extension PeerInfoScreenNode { }, dismissInput: { self?.view.endEditing(true) }, contentContext: nil, progress: nil, completion: nil) - }) + }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil) } func openUrlIn(_ url: String) { - let actionSheet = OpenInActionSheetController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { }) } }) - self.controller?.present(actionSheet, in: .window(.root)) + self.controller?.push(actionSheet) } func openPeerMention(_ name: String, navigation: ChatControllerInteractionNavigateToPeer = .default) { @@ -134,19 +133,16 @@ extension PeerInfoScreenNode { } disposable.set((resolveSignal |> take(1) - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { if let peer = peer { var navigation = navigation if case .default = navigation { - if let peer = peer as? TelegramUser, peer.botInfo != nil { + if case let .user(user) = peer, user.botInfo != nil { navigation = .chat(textInputState: nil, subject: nil, peekData: nil) } } - strongSelf.openResolved(.peer(peer, navigation)) + strongSelf.openResolved(.peer(peer._asPeer(), navigation)) } else { strongSelf.controller?.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } @@ -158,7 +154,7 @@ extension PeerInfoScreenNode { if self.resolvePeerByNameDisposable == nil { self.resolvePeerByNameDisposable = MetaDisposable() } - var resolveSignal: Signal + var resolveSignal: Signal if let peerName = peerName { resolveSignal = self.context.engine.peers.resolvePeerByName(name: peerName, referrer: nil) |> mapToSignal { result -> Signal in @@ -167,12 +163,16 @@ extension PeerInfoScreenNode { } return .single(result) } - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } } else { - resolveSignal = self.context.account.postbox.loadedPeerWithId(self.peerId) - |> map(Optional.init) + resolveSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0) } } var cancelImpl: (() -> Void)? let presentationData = self.presentationData @@ -203,7 +203,7 @@ extension PeerInfoScreenNode { self.resolvePeerByNameDisposable?.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self, !hashtag.isEmpty { - let searchController = HashtagSearchController(context: strongSelf.context, peer: peer.flatMap(EnginePeer.init), query: hashtag) + let searchController = HashtagSearchController(context: strongSelf.context, peer: peer, query: hashtag) strongSelf.controller?.push(searchController) } })) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift index 94896590ed..c66c500b38 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenOpenUsername.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import ContextUI diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift index d5f1c3f837..7f9c0cba09 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenPerformButtonAction.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import UndoUI import ContextUI @@ -22,31 +21,20 @@ extension PeerInfoScreenNode { switch key { case .message: if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer { - if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { + if case let .channel(channel) = peer, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { Task { @MainActor [weak self] in guard let self else { return } - + guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else { return } - + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) } } else { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true - } - navigationController.setViewControllers(viewControllers, animated: false) - } - })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) } } case .discussion: @@ -75,7 +63,7 @@ extension PeerInfoScreenNode { } else { displayCustomNotificationSettings = true } - if self.data?.threadData == nil, let channel = self.data?.peer as? TelegramChannel, channel.isForumOrMonoForum { + if self.data?.threadData == nil, case let .channel(channel) = self.data?.peer, channel.isForumOrMonoForum { displayCustomNotificationSettings = true } @@ -204,42 +192,42 @@ extension PeerInfoScreenNode { let threadId = strongSelf.chatLocation.threadId let context = strongSelf.context - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue } - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + let updatePeerNotificationInterval: (EnginePeer.Id, Int32?) -> Signal = { peerId, muteInterval in return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue } - let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { + let updatePeerDisplayPreviews: (EnginePeer.Id, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue } - let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { + let updatePeerStoriesMuted: (EnginePeer.Id, PeerStoryNotificationSettings.Mute) -> Signal = { peerId, mute in return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue } - let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { + let updatePeerStoriesHideSender: (EnginePeer.Id, PeerStoryNotificationSettings.HideSender) -> Signal = { peerId, hideSender in return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue } - let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerStorySound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue } let mode: NotificationExceptionMode let defaultSound: PeerMessageSound - if let _ = peer as? TelegramUser { + if case .user = peer { mode = .users([:]) defaultSound = globalSettings.privateChats.sound._asMessageSound() - } else if let _ = peer as? TelegramSecretChat { + } else if case .secretChat = peer { mode = .users([:]) defaultSound = globalSettings.privateChats.sound._asMessageSound() - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if case .broadcast = channel.info { mode = .channels([:]) defaultSound = globalSettings.channels.sound._asMessageSound() @@ -255,7 +243,7 @@ extension PeerInfoScreenNode { let canRemove = false - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: EnginePeer(peer), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: globalSettings.privateChats.storySettings.sound, edit: true, updatePeerSound: { peerId, sound in let _ = (updatePeerSound(peer.id, sound) |> deliverOnMainQueue).startStandalone(next: { _ in }) @@ -436,18 +424,18 @@ extension PeerInfoScreenNode { var canSetupAutoremoveTimeout = false - if let secretChat = chatPeer as? TelegramSecretChat { + if case let .secretChat(secretChat) = chatPeer { currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout canSetupAutoremoveTimeout = false - } else if let group = chatPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = chatPeer { if !group.hasBannedPermission(.banChangeInfo) { canSetupAutoremoveTimeout = true } - } else if let user = chatPeer as? TelegramUser { + } else if case let .user(user) = chatPeer { if user.id != strongSelf.context.account.peerId { canSetupAutoremoveTimeout = true } - } else if let channel = chatPeer as? TelegramChannel { + } else if case let .channel(channel) = chatPeer { if channel.hasPermission(.changeInfo) { canSetupAutoremoveTimeout = true } @@ -471,7 +459,7 @@ extension PeerInfoScreenNode { } var hasDiscussion = false - if let channel = chatPeer as? TelegramChannel { + if case let .channel(channel) = chatPeer { switch channel.info { case let .broadcast(info): hasDiscussion = info.flags.contains(.hasDiscussionGroup) @@ -488,7 +476,7 @@ extension PeerInfoScreenNode { }))) } - if let user = peer as? TelegramUser { + if case let .user(user) = peer { if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted { items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor) @@ -575,7 +563,7 @@ extension PeerInfoScreenNode { } } - if user.botInfo == nil && data.isContact, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone { + if user.botInfo == nil && data.isContact, case let .user(peer) = strongSelf.data?.peer, let phone = peer.phone { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in @@ -782,7 +770,7 @@ extension PeerInfoScreenNode { }))) } - if let user = data.peer as? TelegramUser, let cachedData = data.cachedData as? CachedUserData, user.botInfo == nil && !user.flags.contains(.isSupport) && user.id != strongSelf.context.account.peerId && strongSelf.peerId.namespace != Namespaces.Peer.SecretChat { + if case let .user(user) = data.peer, let cachedData = data.cachedData as? CachedUserData, user.botInfo == nil && !user.flags.contains(.isSupport) && user.id != strongSelf.context.account.peerId && strongSelf.peerId.namespace != Namespaces.Peer.SecretChat { let copyProtectionEnabled = cachedData.flags.contains(.myCopyProtectionEnabled) || cachedData.flags.contains(.copyProtectionEnabled) items.append(.action(ContextMenuActionItem(text: !copyProtectionEnabled ? strongSelf.presentationData.strings.PeerInfo_DisableSharing : strongSelf.presentationData.strings.PeerInfo_EnableSharing, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: !copyProtectionEnabled ? "Chat/Context Menu/ForwardDisable" : "Chat/Context Menu/ForwardEnable"), color: theme.contextMenu.primaryColor) @@ -812,7 +800,7 @@ extension PeerInfoScreenNode { } let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: true).start() - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, peerNearbyData: nil, completion: { _ in })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in })) } let _ = (ApplicationSpecificNotice.getCopyProtectionTips(accountManager: self.context.sharedContext.accountManager) |> deliverOnMainQueue).start(next: { [weak self] count in @@ -838,7 +826,7 @@ extension PeerInfoScreenNode { let action = { let _ = self.context.engine.peers.toggleMessageCopyProtection(peerId: user.id, enabled: false).start() - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: .default, scrollToEndIfExists: true, completion: { _ in })) + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default, scrollToEndIfExists: true, completion: { _ in })) } let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) @@ -846,7 +834,7 @@ extension PeerInfoScreenNode { if let cachedUserData = self.data?.cachedData as? CachedUserData, !cachedUserData.flags.contains(.copyProtectionEnabled), let date = cachedUserData.myCopyProtectionEnableDate, currentTime < date + timeout { action() } else { - let peerName = self.data?.peer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" + let peerName = self.data?.peer?.compactDisplayTitle ?? "" let alertController = AlertScreen(context: self.context, configuration: .init(actionAlignment: .vertical), title: self.presentationData.strings.EnableSharing_Title, text: self.presentationData.strings.EnableSharing_Text(peerName).string, actions: [ .init(title: self.presentationData.strings.EnableSharing_SendRequest, type: .default, action: { action() @@ -865,7 +853,7 @@ extension PeerInfoScreenNode { generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: EnginePeer(user)) } }))) } @@ -901,7 +889,7 @@ extension PeerInfoScreenNode { if finalItemsCount > itemsCount { items.insert(.separator, at: itemsCount) } - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { if let cachedData = strongSelf.data?.cachedData as? CachedChannelData { if case .broadcast = channel.info, cachedData.flags.contains(.starGiftsAvailable) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_SendGift, badge: nil, icon: { theme in @@ -1075,13 +1063,13 @@ extension PeerInfoScreenNode { }))) } - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: channel, chatPeer: channel, cachedData: strongSelf.data?.cachedData) + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: channel, chatPeer: EnginePeer(channel), cachedData: strongSelf.data?.cachedData) if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: channel) + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: channel, chatPeer: EnginePeer(channel)) } }))) } @@ -1123,7 +1111,7 @@ extension PeerInfoScreenNode { } } } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { if canSetupAutoremoveTimeout { let strings = strongSelf.presentationData.strings items.append(.action(ContextMenuActionItem(text: currentAutoremoveTimeout == nil ? strongSelf.presentationData.strings.PeerInfo_EnableAutoDelete : strongSelf.presentationData.strings.PeerInfo_AdjustAutoDelete, icon: { theme in @@ -1242,13 +1230,13 @@ extension PeerInfoScreenNode { }))) } - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: group, chatPeer: group, cachedData: strongSelf.data?.cachedData) + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: group, chatPeer: EnginePeer(group), cachedData: strongSelf.data?.cachedData) if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) }, action: { c, _ in if let c { - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: group) + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: group, chatPeer: EnginePeer(group)) } }))) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift index 0e64cec1d7..806e652ac2 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenSettingsActions.swift @@ -3,7 +3,6 @@ import UIKit import Display import AccountContext import SwiftSignalKit -import Postbox import TelegramCore import SettingsUI import PeerInfoStoryGridScreen @@ -58,7 +57,6 @@ extension PeerInfoScreenNode { peerId: self.context.account.peerId, avatarInitiallyExpanded: false, isOpenedFromChat: false, - nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], isMyProfile: true, @@ -174,7 +172,7 @@ extension PeerInfoScreenNode { case .watch: push(watchSettingsController(context: self.context)) case .support: - let supportPeer = Promise() + let supportPeer = Promise() supportPeer.set(context.engine.peers.supportPeerId()) self.controller?.present(textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: self.presentationData.strings.Settings_FAQ_Intro, actions: [ @@ -198,7 +196,7 @@ extension PeerInfoScreenNode { guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else { return } - if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { + if case let .user(user) = self.data?.peer, let phoneNumber = user.phone { let introController = PrivacyIntroController(context: self.context, mode: .changePhoneNumber(phoneNumber), proceedAction: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { navigationController.replaceTopController(ChangePhoneNumberController(context: strongSelf.context), animated: true) @@ -251,7 +249,7 @@ extension PeerInfoScreenNode { } }) case .logout: - if let user = self.data?.peer as? TelegramUser, let phoneNumber = user.phone { + if case let .user(user) = self.data?.peer, let phoneNumber = user.phone { if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { self.controller?.push(logoutOptionsController(context: self.context, navigationController: navigationController, canAddAccounts: true, phoneNumber: phoneNumber)) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift index 78af5c091e..4a341bba9c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSelectionPanelNode.swift @@ -47,7 +47,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { + }, deleteSelectedMessages: { _ in deleteMessages() }, reportSelectedMessages: { reportMessages() @@ -132,7 +132,6 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { _, _ in }, openScheduledMessages: { - }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { @@ -198,8 +197,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, preferredGlassType: .default, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) - let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, preferredGlassType: .default, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: false) transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight))) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift index d4554dfec9..c5f58f4bb5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoSettingsItems.swift @@ -4,7 +4,6 @@ import Display import AccountContext import TelegramPresentationData import TelegramCore -import Postbox import PhoneNumberFormat import ItemListUI import SwiftSignalKit @@ -48,7 +47,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat var setStatusTitle: String = "" let displaySetStatus: Bool var hasEmojiStatus = false - if let peer = data.peer as? TelegramUser, peer.isPremium { + if case let .user(peer) = data.peer, peer.isPremium { if peer.emojiStatus != nil { hasEmojiStatus = true setStatusTitle = presentationData.strings.PeerInfo_ChangeEmojiStatus @@ -86,7 +85,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat items[.phone]!.append(PeerInfoScreenActionItem(id: 1, text: "Restore Subscription", action: { interaction.openSettings(.premiumManagement) })) - } else if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser { + } else if settings.suggestPhoneNumberConfirmation, case let .user(peer) = data.peer { let phoneNumber = formatPhoneNumber(context: context, number: peer.phone ?? "") items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).string, text: .markdown(presentationData.strings.Settings_CheckPhoneNumberText), linkAction: { link in if case .tap = link { @@ -120,8 +119,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat for (peerAccountContext, peer, badgeCount) in settings.accountsAndPeers { let mappedContext = ItemListPeerItem.Context.custom(ItemListPeerItem.Context.Custom( accountPeerId: peerAccountContext.account.peerId, - postbox: peerAccountContext.account.postbox, - network: peerAccountContext.account.network, + engine: peerAccountContext.engine, animationCache: context.animationCache, animationRenderer: context.animationRenderer, isPremiumDisabled: false, @@ -129,7 +127,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat return context.engine.stickers.resolveInlineStickers(fileIds: fileIds) } )) - let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer._asPeer())) + let member: PeerInfoMember = .account(peer: EngineRenderedPeer(peer: peer)) items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: mappedContext, enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, isAccount: true, action: { action in switch action { case .open: @@ -175,7 +173,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat if let settings = data.globalSettings { for bot in settings.bots { let iconSignal: Signal - if let peer = PeerReference(bot.peer._asPeer()), let icon = bot.icons[.iOSSettingsStatic] { + if let peer = PeerReference(bot.peer), let icon = bot.icons[.iOSSettingsStatic] { let fileReference: FileMediaReference = .attachBot(peer: peer, media: icon) iconSignal = instantPageImageFile(account: context.account, userLocation: .other, fileReference: fileReference, fetched: true) |> map { generator -> UIImage? in @@ -370,21 +368,36 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte let ItemBirthdayRemove = 11 let ItemBirthdayHelp = 12 let ItemPeerPersonalChannel = 13 + let ItemPeerChatAutomation = 14 + let ItemPeerChatAutomationHelp = 15 items[.help]!.append(PeerInfoScreenCommentItem(id: ItemNameHelp, text: presentationData.strings.EditProfile_NameAndPhotoOrVideoHelp)) if let cachedData = data.cachedData as? CachedUserData { - items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: state.updatingBio ?? (cachedData.about ?? ""), placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in + let currentBio = state.updatingBio ?? (cachedData.about ?? "") + items[.bio]!.append(PeerInfoScreenMultilineInputItem(id: ItemBio, text: currentBio, placeholder: presentationData.strings.UserInfo_About_Placeholder, textUpdated: { updatedText in interaction.updateBio(updatedText) }, action: { interaction.dismissInput() }, maxLength: Int(data.globalSettings?.userLimits.maxAboutLength ?? 70))) - items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: presentationData.strings.Settings_About_PrivacyHelp, linkAction: { _ in + + + var bioPrivacyInfo = presentationData.strings.Settings_About_PrivacyHelpEmpty + if let bioPrivacy = data.globalSettings?.privacySettings?.bio, !currentBio.isEmpty { + switch bioPrivacy { + case .enableEveryone: + bioPrivacyInfo = presentationData.strings.Settings_About_PrivacyHelpEveryone + case .enableContacts: + bioPrivacyInfo = presentationData.strings.Settings_About_PrivacyHelpContacts + case .disableEveryone: + bioPrivacyInfo = presentationData.strings.Settings_About_PrivacyHelpNobody + } + } + items[.bio]!.append(PeerInfoScreenCommentItem(id: ItemBioHelp, text: bioPrivacyInfo, linkAction: { _ in interaction.openBioPrivacy() })) } - var birthday: TelegramBirthday? if let updatingBirthDate = state.updatingBirthDate { birthday = updatingBirthDate @@ -400,7 +413,7 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte } let isEditingBirthDate = state.isEditingBirthDate - items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: nil, hasArrow: false, action: { + items[.birthday]!.append(PeerInfoScreenDisclosureItem(id: ItemBirthday, label: .coloredText(birthDateString, isEditingBirthDate ? .accent : .generic), text: presentationData.strings.Settings_Birthday, icon: PresentationResourcesSettings.birthday, hasArrow: false, action: { interaction.updateIsEditingBirthdate(!isEditingBirthDate) })) if isEditingBirthDate, let birthday { @@ -413,17 +426,25 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte })) } - - var birthdayIsForContactsOnly = false - if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday, case .enableContacts = birthdayPrivacy { - birthdayIsForContactsOnly = true + var birthdayPrivacyInfo = "" + if let birthdayPrivacy = data.globalSettings?.privacySettings?.birthday { + switch birthdayPrivacy { + case .enableEveryone: + birthdayPrivacyInfo = presentationData.strings.Settings_Birthday_PrivacyHelpEveryone + case .enableContacts: + birthdayPrivacyInfo = presentationData.strings.Settings_Birthday_PrivacyHelpContacts + case .disableEveryone: + birthdayPrivacyInfo = presentationData.strings.Settings_Birthday_PrivacyHelpNobody + } + } + if !birthdayPrivacyInfo.isEmpty { + items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayPrivacyInfo, linkAction: { _ in + interaction.openBirthdatePrivacy() + })) } - items[.birthday]!.append(PeerInfoScreenCommentItem(id: ItemBirthdayHelp, text: birthdayIsForContactsOnly ? presentationData.strings.Settings_Birthday_ContactsHelp : presentationData.strings.Settings_Birthday_Help, linkAction: { _ in - interaction.openBirthdatePrivacy() - })) - if let user = data.peer as? TelegramUser { - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, action: { + if case let .user(user) = data.peer { + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPhoneNumber, label: .text(user.phone.flatMap({ formatPhoneNumber(context: context, number: $0) }) ?? ""), text: presentationData.strings.Settings_PhoneNumber, icon: PresentationResourcesSettings.recentCalls, action: { interaction.openSettings(.phoneNumber) })) } @@ -431,11 +452,11 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte if let addressName = data.peer?.addressName, !addressName.isEmpty { username = "@\(addressName)" } - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(username), text: presentationData.strings.Settings_Username, action: { + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text(username), text: presentationData.strings.Settings_Username, icon: PresentationResourcesSettings.email, action: { interaction.openSettings(.username) })) - if let peer = data.peer as? TelegramUser { + if case let .user(peer) = data.peer { var colors: [PeerNameColors.Colors] = [] if let nameColor = peer.nameColor { let nameColors: PeerNameColors.Colors @@ -452,7 +473,7 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte } let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Settings_YourColor, icon: nil, action: { + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerColor, label: .image(colorImage, colorImage.size), text: presentationData.strings.Settings_YourColor, icon: PresentationResourcesSettings.yourColor, action: { interaction.editingOpenNameColorSetup() })) @@ -468,12 +489,24 @@ func settingsEditingItems(data: PeerInfoScreenData?, state: PeerInfoState, conte personalChannelTitle = peer.compactDisplayTitle } - items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: nil, action: { + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerPersonalChannel, label: .text(personalChannelTitle ?? presentationData.strings.Settings_PersonalChannelEmptyValue), text: presentationData.strings.Settings_PersonalChannelItem, icon: PresentationResourcesSettings.channels, action: { interaction.editingOpenPersonalChannel() })) } } + let automationBotTitle: String + if let botPeer = data.businessConnectedBot { + let _ = botPeer + automationBotTitle = "@\(botPeer.compactDisplayTitle)" + } else { + automationBotTitle = presentationData.strings.Settings_ChatAutomationOff + } + items[.info]!.append(PeerInfoScreenDisclosureItem(id: ItemPeerChatAutomation, label: .text(automationBotTitle), additionalBadgeLabel: nil, text: presentationData.strings.Settings_ChatAutomation, icon: PresentationResourcesSettings.aiTools, action: { + interaction.editingOpenBusinessChatBots() + })) + items[.info]!.append(PeerInfoScreenCommentItem(id: ItemPeerChatAutomationHelp, text: presentationData.strings.Settings_ChatAutomationInfo)) + items[.account]!.append(PeerInfoScreenActionItem(id: ItemAddAccount, text: presentationData.strings.Settings_AddAnotherAccount, alignment: .center, action: { interaction.openSettings(.addAccount) })) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift index ce30f6f28a..3408599eae 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PhotoUpdateConfirmationController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift index af50c523e1..98af424b97 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PresentAddMembers.swift @@ -4,15 +4,14 @@ import Display import AccountContext import TelegramPresentationData import SwiftSignalKit -import Postbox import TelegramCore import InviteLinksUI import SendInviteLinkScreen import UndoUI import PresentationDataUtils -public func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { - let members: Promise<[PeerId]> = Promise() +public func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: EnginePeer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { + let members: Promise<[EnginePeer.Id]> = Promise() if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { /*var membersDisposable: Disposable? let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in @@ -29,13 +28,13 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa |> take(1) |> deliverOnMainQueue).startStandalone(next: { [weak parentController] recentIds in var createInviteLinkImpl: (() -> Void)? - var confirmationImpl: ((PeerId) -> Signal)? + var confirmationImpl: ((EnginePeer.Id) -> Signal)? let _ = confirmationImpl var options: [ContactListAdditionalOption] = [] let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } var canCreateInviteLink = false - if let group = groupPeer as? TelegramGroup { + if case let .legacyGroup(group) = groupPeer { switch group.role { case .creator: canCreateInviteLink = true @@ -44,7 +43,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa default: break } - } else if let channel = groupPeer as? TelegramChannel, (channel.addressName?.isEmpty ?? true) { + } else if case let .channel(channel) = groupPeer, (channel.addressName?.isEmpty ?? true) { if channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) { canCreateInviteLink = true } @@ -60,13 +59,20 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa contactsController.navigationPresentation = .modal confirmationImpl = { [weak contactsController] peerId in - return context.account.postbox.loadedPeerWithId(peerId) + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue |> mapToSignal { peer in let result = ValuePromise() let presentationData = context.sharedContext.currentPresentationData.with { $0 } if let contactsController = contactsController { - let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [ + let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: { result.set(false) }), @@ -81,8 +87,8 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa } } - let addMembers: ([ContactListPeerId]) -> Signal<[(PeerId, AddChannelMemberError)], NoError> = { members -> Signal<[(PeerId, AddChannelMemberError)], NoError> in - let memberIds = members.compactMap { contact -> PeerId? in + let addMembers: ([ContactListPeerId]) -> Signal<[(EnginePeer.Id, AddChannelMemberError)], NoError> = { members -> Signal<[(EnginePeer.Id, AddChannelMemberError)], NoError> in + let memberIds = members.compactMap { contact -> EnginePeer.Id? in switch contact { case let .peer(peerId): return peerId @@ -93,23 +99,23 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa return context.account.postbox.multiplePeersView(memberIds) |> take(1) |> deliverOnMainQueue - |> mapToSignal { view -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + |> mapToSignal { view -> Signal<[(EnginePeer.Id, AddChannelMemberError)], NoError> in if groupPeer.id.namespace == Namespaces.Peer.CloudChannel { if memberIds.count == 1 { return context.peerChannelMemberCategoriesContextsManager.addMember(engine: context.engine, peerId: groupPeer.id, memberId: memberIds[0]) - |> map { _ -> [(PeerId, AddChannelMemberError)] in + |> map { _ -> [(EnginePeer.Id, AddChannelMemberError)] in } - |> then(Signal<[(PeerId, AddChannelMemberError)], AddChannelMemberError>.single([])) - |> `catch` { error -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + |> then(Signal<[(EnginePeer.Id, AddChannelMemberError)], AddChannelMemberError>.single([])) + |> `catch` { error -> Signal<[(EnginePeer.Id, AddChannelMemberError)], NoError> in return .single([(memberIds[0], error)]) } } else { return context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: groupPeer.id, memberIds: memberIds) } } else { - var signals: [Signal<(PeerId, AddChannelMemberError)?, NoError>] = [] + var signals: [Signal<(EnginePeer.Id, AddChannelMemberError)?, NoError>] = [] for memberId in memberIds { - let signal: Signal<(PeerId, AddChannelMemberError)?, NoError> = context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId) + let signal: Signal<(EnginePeer.Id, AddChannelMemberError)?, NoError> = context.engine.peers.addGroupMember(peerId: groupPeer.id, memberId: memberId) |> mapError { error -> AddChannelMemberError in switch error { case .generic: @@ -125,16 +131,16 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa } } |> ignoreValues - |> map { _ -> (PeerId, AddChannelMemberError)? in + |> map { _ -> (EnginePeer.Id, AddChannelMemberError)? in } - |> then(Signal<(PeerId, AddChannelMemberError)?, AddChannelMemberError>.single(nil)) - |> `catch` { error -> Signal<(PeerId, AddChannelMemberError)?, NoError> in + |> then(Signal<(EnginePeer.Id, AddChannelMemberError)?, AddChannelMemberError>.single(nil)) + |> `catch` { error -> Signal<(EnginePeer.Id, AddChannelMemberError)?, NoError> in return .single((memberId, error)) } signals.append(signal) } return combineLatest(signals) - |> map { values -> [(PeerId, AddChannelMemberError)] in + |> map { values -> [(EnginePeer.Id, AddChannelMemberError)] in return values.compactMap { $0 } } } @@ -200,7 +206,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa if !failedPeers.isEmpty, let contactsController, let navigationController = contactsController.navigationController as? NavigationController { var viewControllers = navigationController.viewControllers if let index = viewControllers.firstIndex(where: { $0 === contactsController }) { - let inviteScreen = SendInviteLinkScreen(context: context, subject: .chat(peer: EnginePeer(groupPeer), link: exportedInvitation?.link), peers: failedPeers) + let inviteScreen = SendInviteLinkScreen(context: context, subject: .chat(peer: groupPeer, link: exportedInvitation?.link), peers: failedPeers) viewControllers.remove(at: index) viewControllers.append(inviteScreen) navigationController.setViewControllers(viewControllers, animated: true) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD index b26f1644a1..220a3538e5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift index 3b3a3c0082..e6443db7b5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/PeerInfoStoryGridScreen.swift @@ -242,7 +242,7 @@ final class PeerInfoStoryGridScreenComponent: Component { guard let self, let component = self.component, let peer else { return } - guard let peerReference = PeerReference(peer._asPeer()) else { + guard let peerReference = PeerReference(peer) else { return } @@ -265,7 +265,7 @@ final class PeerInfoStoryGridScreenComponent: Component { for (_, item) in sortedItems { let itemOffset = progressStart progressStart += valueNorm - signals.append(saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .other, mediaReference: .story(peer: peerReference, id: item.id, media: item.media._asMedia())) + signals.append(saveToCameraRoll(context: component.context, userLocation: .other, mediaReference: .story(peer: peerReference, id: item.id, media: item.media._asMedia())) |> map { progress -> Float in return itemOffset + progress * valueNorm }) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift index b3d43bb29d..b51b155ebe 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen/Sources/StorySearchGridScreen.swift @@ -251,12 +251,12 @@ public final class StorySearchGridScreen: ViewControllerComponentContainer { } self.present(self.context.sharedContext.makeShareController(context: self.context, params: ShareControllerParams(subject: .mapMedia(locationMap), externalShare: true)), in: .window(.root), with: nil) }) - self.present(OpenInActionSheetController(context: self.context, updatedPresentationData: nil, item: .location(location: locationMap, directions: nil), additionalAction: shareAction, openUrl: { [weak self] url in + self.push(OpenInOptionsScreen(context: self.context, updatedPresentationData: nil, item: .location(location: locationMap, directions: nil), additionalAction: shareAction, openUrl: { [weak self] url in guard let self else { return } self.context.sharedContext.applicationBindings.openUrl(url) - }), in: .window(.root), with: nil) + })) } func updateTitle() { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift index dad0464fe4..fe41b91c02 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/AddGiftsScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -12,9 +11,7 @@ import AccountContext import ComponentFlow import ViewControllerComponent import BundleIconComponent -import MultilineTextComponent -import ButtonComponent -import BlurredBackgroundComponent +import BottomButtonPanelComponent import ContextUI final class AddGiftsScreenComponent: Component { @@ -56,9 +53,8 @@ final class AddGiftsScreenComponent: Component { private var giftsListView: GiftsListView? - private let buttonBackground = ComponentView() - private let buttonSeparator = SimpleLayer() - private let button = ComponentView() + private let buttonPanel = ComponentView() + private var bottomPanelHeight: CGFloat = 68.0 private var isUpdating: Bool = false @@ -114,7 +110,7 @@ final class AddGiftsScreenComponent: Component { var contentSize = CGSize(width: self.scrollView.bounds.width, height: contentHeight) contentSize.height += environment.safeInsets.bottom contentSize.height = max(contentSize.height, self.scrollView.bounds.size.height) - contentSize.height += 50.0 + 24.0 + contentSize.height += self.bottomPanelHeight transition.setFrame(view: giftsListView, frame: CGRect(origin: CGPoint(), size: contentSize)) if self.scrollView.contentSize != contentSize { @@ -161,47 +157,15 @@ final class AddGiftsScreenComponent: Component { self.state = state let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let buttonHeight: CGFloat = 50.0 - let bottomPanelPadding: CGFloat = 12.0 - let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding - let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset - - let bottomPanelOffset: CGFloat = giftsListView.selectedItems.count > 0 ? 0.0 : bottomPanelHeight - - let buttonString = environment.strings.AddGifts_AddGifts(Int32(giftsListView.selectedItems.count)) - let bottomPanelSize = self.buttonBackground.update( + let buttonString = environment.strings.AddGifts_AddGifts(Int32(max(1, giftsListView.selectedItems.count))) + let bottomPanelSize = self.buttonPanel.update( transition: transition, - component: AnyComponent(BlurredBackgroundComponent( - color: environment.theme.rootController.tabBar.backgroundColor - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: bottomPanelHeight) - ) - self.buttonSeparator.backgroundColor = environment.theme.rootController.tabBar.separatorColor.cgColor - - if let view = self.buttonBackground.view { - if view.superview == nil { - self.addSubview(view) - self.layer.addSublayer(self.buttonSeparator) - } - transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize)) - transition.setFrame(layer: self.buttonSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: CGSize(width: availableSize.width, height: UIScreenPixel))) - } - - let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) - let buttonSize = self.button.update( - transition: transition, - component: AnyComponent(ButtonComponent( - background: ButtonComponent.Background( - color: environment.theme.list.itemCheckColors.fillColor, - foreground: environment.theme.list.itemCheckColors.foregroundColor, - pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 - ), - content: AnyComponentWithIdentity( - id: AnyHashable(buttonAttributedString.string), - component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) - ), + component: AnyComponent(BottomButtonPanelComponent( + theme: environment.theme, + title: buttonString, + label: nil, + isEnabled: true, + insets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: environment.safeInsets.bottom, right: sideInset), action: { [weak self] in guard let self, let controller = self.environment?.controller() as? AddGiftsScreen, let giftsListView = self.giftsListView else { return @@ -211,18 +175,21 @@ final class AddGiftsScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight) + containerSize: availableSize ) - if let buttonView = self.button.view { - if buttonView.superview == nil { - self.addSubview(buttonView) + self.bottomPanelHeight = bottomPanelSize.height + + let bottomPanelOffset: CGFloat = giftsListView.selectedItems.count > 0 ? 0.0 : bottomPanelSize.height + if let buttonPanelView = self.buttonPanel.view { + if buttonPanelView.superview == nil { + self.addSubview(buttonPanelView) } - transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - bottomPanelHeight + bottomPanelPadding + bottomPanelOffset), size: buttonSize)) + transition.setFrame(view: buttonPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height + bottomPanelOffset), size: bottomPanelSize)) } let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) - let _ = giftsListView.update(size: availableSize, sideInset: 0.0, bottomInset: max(environment.safeInsets.bottom, bottomPanelHeight), deviceMetrics: environment.deviceMetrics, visibleHeight: availableSize.height, isScrollingLockedAtTop: false, expandProgress: 0.0, presentationData: presentationData, synchronous: false, visibleBounds: visibleBounds, transition: transition.containedViewLayoutTransition) + let _ = giftsListView.update(size: availableSize, sideInset: 0.0, bottomInset: max(environment.safeInsets.bottom, bottomPanelSize.height), deviceMetrics: environment.deviceMetrics, visibleHeight: availableSize.height, isScrollingLockedAtTop: false, expandProgress: 0.0, presentationData: presentationData, synchronous: false, visibleBounds: visibleBounds, transition: transition.containedViewLayoutTransition) transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: availableSize)) self.backgroundView.backgroundColor = environment.theme.list.blocksBackgroundColor @@ -299,7 +266,7 @@ public final class AddGiftsScreen: ViewControllerComponentContainer { } self.filterButton.addTarget(self, action: #selector(self.filterPressed), forControlEvents: .touchUpInside) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.filterButton) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index 21b5567114..e1c98a1ac3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import ContextUI @@ -30,7 +29,7 @@ import ContextUI final class GiftsListView: UIView { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id let profileGifts: ProfileGiftsContext private let giftsCollections: ProfileGiftsCollectionsContext? @@ -127,7 +126,7 @@ final class GiftsListView: UIView { var contextAction: ((ProfileGiftsContext.State.StarGift, UIView, ContextGesture) -> Void)? var addToCollection: (() -> Void)? - init(context: AccountContext, peerId: PeerId, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil, remainingSelectionCount: Int32 = 0) { + init(context: AccountContext, peerId: EnginePeer.Id, profileGifts: ProfileGiftsContext, giftsCollections: ProfileGiftsCollectionsContext?, canSelect: Bool, ignoreCollection: Int32? = nil, remainingSelectionCount: Int32 = 0) { self.context = context self.peerId = peerId self.profileGifts = profileGifts @@ -820,12 +819,13 @@ final class GiftsListView: UIView { environment: {}, containerSize: CGSize(width: params.size.width - sideInset * 2.0, height: params.size.height) ) - let buttonAttributedString = NSAttributedString(string: presentationData.strings.PeerInfo_Gifts_EmptyCollection_Action, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) + let buttonAttributedString = NSAttributedString(string: presentationData.strings.PeerInfo_Gifts_EmptyCollection_Action, font: Font.semibold(17.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let emptyResultsActionSize = self.emptyResultsAction.update( transition: .immediate, component: AnyComponent( ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: presentationData.theme.list.itemCheckColors.fillColor, foreground: presentationData.theme.list.itemCheckColors.foregroundColor, pressedColor: presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -841,7 +841,7 @@ final class GiftsListView: UIView { ) ), environment: {}, - containerSize: CGSize(width: 240.0, height: 50.0) + containerSize: CGSize(width: 240.0, height: 52.0) ) let emptyTotalHeight = emptyResultsTitleSize.height + emptyTextSpacing + emptyResultsTextSize.height + emptyTextSpacing + emptyResultsActionSize.height diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index bab566ae72..ee9ea2d9fc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import PresentationDataUtils import AccountContext @@ -68,7 +67,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let profileGiftsCollections: ProfileGiftsCollectionsContext private let profileGifts: ProfileGiftsContext private let canManage: Bool @@ -132,7 +131,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr private let collectionsMaxCount: Int - public init(context: AccountContext, peerId: PeerId, chatControllerInteraction: ChatControllerInteraction, profileGiftsCollections: ProfileGiftsCollectionsContext, profileGifts: ProfileGiftsContext, canManage: Bool, canGift: Bool, initialGiftCollectionId: Int64?) { + public init(context: AccountContext, peerId: EnginePeer.Id, chatControllerInteraction: ChatControllerInteraction, profileGiftsCollections: ProfileGiftsCollectionsContext, profileGifts: ProfileGiftsContext, canManage: Bool, canGift: Bool, initialGiftCollectionId: Int64?) { self.context = context self.peerId = peerId self.chatControllerInteraction = chatControllerInteraction @@ -213,6 +212,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.scrollNode.view.contentInsetAdjustmentBehavior = .never self.scrollNode.view.delegate = self + self.scrollNode.view.scrollsToTop = false if let tabSelectorView = self.tabSelector.view { self.scrollNode.view.insertSubview(self.giftsListView, aboveSubview: tabSelectorView) @@ -347,7 +347,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } } - public func ensureMessageIsVisible(id: MessageId) { + public func ensureMessageIsVisible(id: EngineMessage.Id) { } public func scrollToTop() -> Bool { @@ -784,7 +784,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr buttonTitle = params.presentationData.strings.PeerInfo_Gifts_SendGift } - let buttonAttributedString = NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) + let buttonAttributedString = NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) var buttonTitleContent: AnyComponent = AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) if let buttonIconName { buttonTitleContent = AnyComponent(HStack([ @@ -1456,7 +1456,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr self.updateScrolling(transition: ComponentTransition(transition)) } - public func findLoadedMessage(id: MessageId) -> Message? { + public func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -1472,7 +1472,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr public func cancelPreviewGestures() { } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 9f1663f763..921303febf 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -4,7 +4,6 @@ import UIKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import PresentationDataUtils import AccountContext @@ -56,7 +55,7 @@ private final class VisualMediaItemInteraction { let openItemContextActions: (EngineStoryItem, ASDisplayNode, CGRect, ContextGesture?) -> Void let toggleSelection: (Int32, Bool) -> Void - var hiddenStories = Set() + var hiddenStories = Set() var selectedIds: Set? init( @@ -71,7 +70,7 @@ private final class VisualMediaItemInteraction { } private final class VisualMediaHoleAnchor: SparseItemGrid.HoleAnchor { - let storyId: StoryId + let storyId: EngineStoryId override var id: AnyHashable { return AnyHashable(self.storyId) } @@ -86,7 +85,7 @@ private final class VisualMediaHoleAnchor: SparseItemGrid.HoleAnchor { return self.localMonthTimestamp } - init(index: Int, storyId: StoryId, localMonthTimestamp: Int32) { + init(index: Int, storyId: EngineStoryId, localMonthTimestamp: Int32) { self.indexValue = index self.storyId = storyId self.localMonthTimestamp = localMonthTimestamp @@ -103,7 +102,7 @@ private final class VisualMediaItem: SparseItemGrid.Item { } let localMonthTimestamp: Int32 let peer: PeerReference - let storyId: StoryId + let storyId: EngineStoryId let story: EngineStoryItem let authorPeer: EnginePeer? let isPinned: Bool @@ -122,7 +121,7 @@ private final class VisualMediaItem: SparseItemGrid.Item { return VisualMediaHoleAnchor(index: self.index, storyId: self.storyId, localMonthTimestamp: self.localMonthTimestamp) } - init(index: Int, peer: PeerReference, storyId: StoryId, story: EngineStoryItem, authorPeer: EnginePeer?, isPinned: Bool, localMonthTimestamp: Int32, isReorderable: Bool, isEnabled: Bool) { + init(index: Int, peer: PeerReference, storyId: EngineStoryId, story: EngineStoryItem, authorPeer: EnginePeer?, isPinned: Bool, localMonthTimestamp: Int32, isReorderable: Bool, isEnabled: Bool) { self.indexValue = index self.peer = peer self.storyId = storyId @@ -489,7 +488,7 @@ private final class DurationLayer: SimpleLayer { }) self.contents = image?.cgImage - if let smallProfileImage = author.smallProfileImage, let peerReference = PeerReference(author._asPeer()) { + if let smallProfileImage = author.smallProfileImage, let peerReference = PeerReference(author) { if let result = directMediaImageCache.getAvatarImage(peer: peerReference, resource: MediaResourceReference.avatar(peer: peerReference, resource: smallProfileImage.resource), immediateThumbnail: smallProfileImage.immediateThumbnailData, size: 24, includeBlurred: true, synchronous: synchronous == .full) { if let image = result.image { avatarLayer.contents = image.cgImage @@ -855,7 +854,7 @@ private final class ItemLayer: CALayer, SparseItemGridLayer { binding.bindLayers(items: [item], layers: [displayItem], size: size, insets: insets, synchronous: .none) } else { if let layer = displayItem.layer as? ItemLayer { - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let image = item.story.media._asMedia() as? TelegramMediaImage { selectedMedia = image } else if let file = item.story.media._asMedia() as? TelegramMediaFile { @@ -1124,7 +1123,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { var updateShimmerLayersImpl: ((SparseItemGridDisplayItem) -> Void)? var reorderIfPossibleImpl: ((SparseItemGrid.Item, Int) -> Void)? - var revealedSpoilerMessageIds = Set() + var revealedSpoilerMessageIds = Set() private var shimmerImages: [CGFloat: UIImage] = [:] @@ -1211,7 +1210,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { let hasSpoiler = false layer.updateHasSpoiler(hasSpoiler: hasSpoiler) - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let image = story.media._asMedia() as? TelegramMediaImage { selectedMedia = image } else if let file = story.media._asMedia() as? TelegramMediaFile { @@ -1322,7 +1321,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding { } } - func updateLayerData(story: EngineStoryItem, item: VisualMediaItem, selectedMedia: Media, layer: ItemLayer, synchronous: SparseItemGrid.Synchronous) { + func updateLayerData(story: EngineStoryItem, item: VisualMediaItem, selectedMedia: EngineRawMedia, layer: ItemLayer, synchronous: SparseItemGrid.Synchronous) { var viewCount: Int32? if let value = story.views?.seenCount { viewCount = Int32(value) @@ -1527,7 +1526,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr var address: String? var distance: Double? var drivingTime: ExpectedTravelTime - var transitTime: ExpectedTravelTime var walkingTime: ExpectedTravelTime var hasEta: Bool @@ -1536,7 +1534,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr address: String?, distance: Double?, drivingTime: ExpectedTravelTime, - transitTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, hasEta: Bool ) { @@ -1544,7 +1541,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.address = address self.distance = distance self.drivingTime = drivingTime - self.transitTime = transitTime self.walkingTime = walkingTime self.hasEta = hasEta } @@ -1562,7 +1558,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private let contextGestureContainerNode: ContextControllerSourceNode - private var mapOptionsNode: LocationOptionsNode? private var mapNode: LocationMapHeaderNode? private var mapDisposable: Disposable? @@ -1587,7 +1582,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr private let directMediaImageCache: DirectMediaImageCache private var items: SparseItemGrid.Items? private var pinnedIds: Set = Set() - private var reorderedIds: [StoryId]? + private var reorderedIds: [EngineStoryId]? private var itemCount: Int? private var didUpdateItemsOnce: Bool = false private var itemTabId: AnyHashable? @@ -2192,7 +2187,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if case .location = scope { let mapNode = LocationMapHeaderNode( presentationData: self.presentationData, - glass: false, + glass: true, toggleMapModeSelection: { [weak self] in guard let self else { return @@ -2202,7 +2197,15 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr state.displayingMapModeOptions = !state.displayingMapModeOptions self.locationViewState = state }, - updateMapMode: { _ in + updateMapMode: { [weak self] mode in + guard let self else { + return + } + + var state = self.locationViewState + state.mapMode = mode + state.displayingMapModeOptions = false + self.locationViewState = state }, goToUserLocation: { [weak self] in guard let self else { @@ -2334,7 +2337,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr strongSelf.itemGridBinding.updatePresentationData(presentationData: presentationData) strongSelf.itemGrid.updatePresentationData(theme: presentationData.theme) - strongSelf.mapOptionsNode?.updatePresentationData(presentationData) }) self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false) @@ -2355,24 +2357,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr throttledUserLocation(mapNode.mapNode.userLocation) ) - var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating, .calculating)) + var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating)) var address: Signal = .single(nil) let locale = localeWithStrings(self.presentationData.strings) - eta = .single((.calculating, .calculating, .calculating)) - |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .automobile), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .transit), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .walking)) - |> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in + eta = .single((.calculating, .calculating)) + |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .automobile), getExpectedTravelTime(coordinate: locationCoordinate, transportType: .walking)) + |> mapToSignal { drivingTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> in if case .calculating = drivingTime { return .complete() } - if case .calculating = transitTime { - return .complete() - } if case .calculating = walkingTime { return .complete() } - return .single((drivingTime, transitTime, walkingTime)) + return .single((drivingTime, walkingTime)) }) /*if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty { @@ -2417,8 +2416,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr address: address, distance: distance, drivingTime: eta.0, - transitTime: eta.1, - walkingTime: eta.2, + walkingTime: eta.1, hasEta: false ) @@ -2556,7 +2554,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if let story = folderPreview.item { var imageSignal: Signal? - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let image = story.media._asMedia() as? TelegramMediaImage { selectedMedia = image } else if let file = story.media._asMedia() as? TelegramMediaFile { @@ -2766,12 +2764,12 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr guard let self else { return } - guard let peer, let peerReference = PeerReference(peer._asPeer()) else { + guard let peer, let peerReference = PeerReference(peer) else { return } let shareController = self.context.sharedContext.makeShareController(context: self.context, params: ShareControllerParams( - subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: StoryId(peerId: peer.id, id: item.id), isMention: false)), nil), + subject: .media(.story(peer: peerReference, id: item.id, media: TelegramMediaStory(storyId: EngineStoryId(peerId: peer.id, id: item.id), isMention: false)), nil), externalShare: false )) self.parentController?.present(shareController, in: .window(.root)) @@ -2881,7 +2879,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr } } - public func ensureMessageIsVisible(id: MessageId) { + public func ensureMessageIsVisible(id: EngineMessage.Id) { } private func requestHistoryAroundVisiblePosition(synchronous: Bool, reloadAtTop: Bool, animated: Bool = true) { @@ -3038,7 +3036,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if let reorderedIds = self.reorderedIds { var fixedStateItems: [StoryListContext.State.Item] = [] - var seenIds = Set() + var seenIds = Set() for id in reorderedIds { if let index = stateItems.firstIndex(where: { $0.id == id }) { seenIds.insert(id) @@ -3060,7 +3058,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if let value = state.peerReference { peerReference = value } else if let peer = item.peer { - peerReference = PeerReference(peer._asPeer()) + peerReference = PeerReference(peer) } guard let peerReference else { continue @@ -3100,7 +3098,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr )) } if mappedItems.count < state.totalCount, let lastItem = state.items.last, let _ = state.loadMoreToken { - mappedHoles.append(VisualMediaHoleAnchor(index: mappedItems.count, storyId: StoryId(peerId: context.account.peerId, id: Int32.max), localMonthTimestamp: Month(localTimestamp: lastItem.storyItem.timestamp + timezoneOffset).packedValue)) + mappedHoles.append(VisualMediaHoleAnchor(index: mappedItems.count, storyId: EngineStoryId(peerId: context.account.peerId, id: Int32.max), localMonthTimestamp: Month(localTimestamp: lastItem.storyItem.timestamp + timezoneOffset).packedValue)) } totalCount = state.totalCount totalCount = max(mappedItems.count, totalCount) @@ -3262,7 +3260,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr return } - var ids = items.items.compactMap { item -> StoryId? in + var ids = items.items.compactMap { item -> EngineStoryId? in return (item as? VisualMediaItem)?.storyId } @@ -3299,7 +3297,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.itemGrid.brieflyDisableTouchActions() } - public func findLoadedMessage(id: MessageId) -> Message? { + public func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { return nil } @@ -3313,7 +3311,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr public func cancelPreviewGestures() { } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { return nil } @@ -3605,9 +3603,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr controller?.dismissAnimated() } - var mappedMedia: [Media] = [] + var mappedMedia: [EngineRawMedia] = [] if let items = self.items { - mappedMedia = items.items.compactMap { item -> Media? in + mappedMedia = items.items.compactMap { item -> EngineRawMedia? in guard let item = item as? VisualMediaItem else { return nil } @@ -3756,12 +3754,10 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr address: addressString, distance: distanceString, drivingTime: mapInfoData.drivingTime, - transitTime: mapInfoData.transitTime, walkingTime: mapInfoData.walkingTime, hasEta: mapInfoData.hasEta, action: {}, drivingAction: {}, - transitAction: {}, walkingAction: {} ) let (mapInfoLayout, mapInfoReadyAndApply) = mapInfoNode.asyncLayout()( @@ -4201,30 +4197,6 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if self.mapNode != nil { self.updateMapLayout(size: size, topInset: topInset, bottomInset: bottomInset, deviceMetrics: deviceMetrics, transition: transition) gridTopInset += self.effectiveMapHeight - - let mapOptionsNode: LocationOptionsNode - if let current = self.mapOptionsNode { - mapOptionsNode = current - } else { - mapOptionsNode = LocationOptionsNode(presentationData: self.presentationData, hasBackground: false, updateMapMode: { [weak self] mode in - guard let self else { - return - } - - var state = self.locationViewState - state.mapMode = mode - state.displayingMapModeOptions = false - self.locationViewState = state - }) - mapOptionsNode.clipsToBounds = true - self.mapOptionsNode = mapOptionsNode - self.parentController?.navigationBar?.additionalContentNode.addSubnode(mapOptionsNode) - } - - let mapOptionsFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset - self.additionalNavigationHeight), size: CGSize(width: size.width, height: self.additionalNavigationHeight)) - transition.updatePosition(node: mapOptionsNode, position: mapOptionsFrame.center) - transition.updateBounds(node: mapOptionsNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 38.0 - self.additionalNavigationHeight), size: mapOptionsFrame.size)) - mapOptionsNode.updateLayout(size: mapOptionsFrame.size, leftInset: sideInset, rightInset: sideInset, transition: transition) } var hasBarBackground = false @@ -5177,7 +5149,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr public func presentDeleteBotPreviewLanguage() { self.parentController?.present(textAlertController(context: self.context, title: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Title, text: self.presentationData.strings.BotPreviews_DeleteTranslationAlert_Text, actions: [ - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: { + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.Common_OK, action: { [weak self] in guard let self else { @@ -5223,9 +5195,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr return } - var mappedMedia: [Media] = [] + var mappedMedia: [EngineRawMedia] = [] if let items = self.items { - mappedMedia = items.items.compactMap { item -> Media? in + mappedMedia = items.items.compactMap { item -> EngineRawMedia? in guard let item = item as? VisualMediaItem else { return nil } @@ -5321,7 +5293,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr self.reorderedIds = nil if case .botPreview = self.scope, let listSource = self.listSource as? BotPreviewStoryListContext { if let items = self.items { - var reorderedMedia: [Media] = [] + var reorderedMedia: [EngineRawMedia] = [] for id in reorderedIds { if let item = items.items.first(where: { ($0 as? VisualMediaItem)?.storyId == id }) as? VisualMediaItem { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index 3497f3aa66..40b4fd0bf5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -899,7 +899,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding, ListShimme layer.updateHasSpoiler(hasSpoiler: hasSpoiler) var selectedMedia: Media? - for media in message.media { + for media in message.effectiveMedia { if let image = media as? TelegramMediaImage { selectedMedia = image break @@ -1777,7 +1777,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, let _ = updateVisualMediaStoredState(engine: self.context.engine, peerId: self.peerId, messageTag: self.stateTag, state: VisualMediaStoredState(zoomLevel: level.rawValue)).start() } - public func ensureMessageIsVisible(id: MessageId) { + public func ensureMessageIsVisible(id: EngineMessage.Id) { } private func requestHistoryAroundVisiblePosition(synchronous: Bool, reloadAtTop: Bool) { @@ -1880,7 +1880,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, self.itemGrid.brieflyDisableTouchActions() } - public func findLoadedMessage(id: MessageId) -> Message? { + public func findLoadedMessage(id: EngineMessage.Id) -> EngineMessage? { guard let items = self.items else { return nil } @@ -1889,7 +1889,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, continue } if item.message.id == id { - return item.message + return EngineMessage(item.message) } } return nil @@ -1929,7 +1929,7 @@ public final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, } } - public func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { var foundItemLayer: SparseItemGridLayer? self.itemGrid.forEachVisibleItem { item in guard let itemLayer = item.layer as? ItemLayer else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD index d0bc74abb5..5ee6d7fdf5 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/BUILD @@ -18,7 +18,6 @@ swift_library( "//submodules/Components/ViewControllerComponent", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/AccountContext", "//submodules/TelegramUI/Components/SwitchComponent", "//submodules/Components/MultilineTextComponent", diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index 0158733e80..dcd3c16db3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift index 09747a6867..7d549c6c61 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen/Sources/ProfileLevelInfoScreen.swift @@ -102,8 +102,7 @@ private final class SheetContent: Component { self.environment = environment var contentHeight: CGFloat = 0.0 - - let titleString: String = environment.strings.ProfileLevelInfo_Title + let descriptionTextString: String var secondaryDescriptionTextString: String? if component.peer.id == component.context.account.peerId { @@ -178,7 +177,6 @@ private final class SheetContent: Component { )) } - let _ = titleString let titleSize = self.title.update( transition: transition, component: AnyComponent(AnimatedTextComponent( diff --git a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD index b61bba252c..1bc81a7320 100644 --- a/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/ProfileLevelRatingBarComponent/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/BUILD b/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/BUILD index 788a843cf0..3944bcd19e 100644 --- a/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/Sources/VerifyAlertController.swift b/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/Sources/VerifyAlertController.swift index d807a4efb0..9ea1e37f24 100644 --- a/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/Sources/VerifyAlertController.swift +++ b/submodules/TelegramUI/Components/PeerInfo/VerifyAlertController/Sources/VerifyAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/BUILD b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/BUILD index b4afc963ea..620a7dd779 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/BUILD +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/PresentationDataUtils", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift index 743d9178cb..4a3f69ff6c 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift @@ -45,7 +45,7 @@ func localizedOldChannelDate(peer: InactiveChannel, strings: PresentationStrings string = strings.OldChannels_InactiveYear(Int32(dif)) } - if let channel = peer.peer as? TelegramChannel, case .group = channel.info { + if case let .channel(channel) = peer.peer, case .group = channel.info { if let participantsCount = peer.participantsCount, participantsCount != 0 { string = strings.OldChannels_GroupFormat(participantsCount) + ", " + string } else { @@ -172,7 +172,7 @@ private enum OldChannelsEntry: ItemListNodeEntry { case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): - return ContactsPeerItem(presentationData: presentationData, style: .blocks, systemStyle: .glass, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: presentationData, style: .blocks, systemStyle: .glass, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in arguments.togglePeer(peer.peer.id, true) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) } @@ -307,7 +307,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa ) ) |> map { presentationData, state, peers, limits -> (ItemListControllerState, (ItemListNodeState, Any)) in - let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { + let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: { dismissImpl?() }) let (accountPeer, limits, premiumLimits) = limits @@ -386,7 +386,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa let peers = peers ?? [] let ensureStoredPeers = peers.map { $0.peer }.filter { state.selectedPeers.contains($0.id) } - let ensureStoredPeersSignal: Signal = context.engine.peers.ensurePeersAreLocallyAvailable(peers: ensureStoredPeers.map(EnginePeer.init)) + let ensureStoredPeersSignal: Signal = context.engine.peers.ensurePeersAreLocallyAvailable(peers: ensureStoredPeers) return ensureStoredPeersSignal |> then(context.engine.peers.removePeerChats(peerIds: Array(ensureStoredPeers.map(\.id)))) diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift index 8259a5ab74..2b85b10410 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift @@ -154,7 +154,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem { switch self { case let .peer(_, peer, selected): - return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in interaction.togglePeer(peer.peer.id) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) } diff --git a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD index 0afb500308..499b18092a 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD +++ b/submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/PresentationDataUtils", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/PeerReportScreen/BUILD b/submodules/TelegramUI/Components/PeerReportScreen/BUILD index 95a0b40747..4b5c1ff8bc 100644 --- a/submodules/TelegramUI/Components/PeerReportScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerReportScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift b/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift index cdae86c7b0..d4cef5ddbe 100644 --- a/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift +++ b/submodules/TelegramUI/Components/PeerReportScreen/Sources/PeerReportScreen.swift @@ -18,7 +18,7 @@ import Markdown public enum PeerReportSubject { case peer(EnginePeer.Id) case messages([EngineMessage.Id]) - case profilePhoto(EnginePeer.Id, Int64) + case profilePhoto(EnginePeer.Id, Int64, sourceMessageId: EngineMessage.Id?) case story(EnginePeer.Id, Int32) } @@ -149,8 +149,8 @@ public func presentPeerReportOptions( displaySuccess() completion(nil, false) }) - case let .profilePhoto(peerId, _): - let _ = (context.engine.peers.reportPeerPhoto(peerId: peerId, reason: reportReason, message: "") + case let .profilePhoto(peerId, _, sourceMessageId): + let _ = (context.engine.peers.reportPeerPhoto(peerId: peerId, reason: reportReason, message: "", sourceMessageId: sourceMessageId) |> deliverOnMainQueue).start(completed: { displaySuccess() completion(nil, false) @@ -285,8 +285,8 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe displaySuccess() completion(nil, true) }) - case let .profilePhoto(peerId, _): - let _ = (context.engine.peers.reportPeerPhoto(peerId: peerId, reason: reportReason, message: message) + case let .profilePhoto(peerId, _, sourceMessageId): + let _ = (context.engine.peers.reportPeerPhoto(peerId: peerId, reason: reportReason, message: message, sourceMessageId: sourceMessageId) |> deliverOnMainQueue).start(completed: { displaySuccess() completion(nil, true) diff --git a/submodules/TelegramUI/Components/PeerSelectionController/BUILD b/submodules/TelegramUI/Components/PeerSelectionController/BUILD index f3d3fd0e3f..ec8167231b 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/BUILD +++ b/submodules/TelegramUI/Components/PeerSelectionController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/ProgressNavigationButtonNode", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift index 860ba6ad33..dcbe7fe0c6 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import Display import TelegramCore -import Postbox import TelegramPresentationData import ProgressNavigationButtonNode import AccountContext @@ -356,7 +355,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon self.peerSelectionNode.requestOpenPeerFromSearch = { [weak self] peer, threadId in if let strongSelf = self { - strongSelf.openMessageFromSearchDisposable.set((_internal_storedMessageFromSearchPeer(postbox: strongSelf.context.account.postbox, peer: peer._asPeer()) + strongSelf.openMessageFromSearchDisposable.set((_internal_storedMessageFromSearchPeer(postbox: strongSelf.context.account.postbox, peer: peer) |> deliverOnMainQueue).start(completed: { [weak strongSelf] in if let strongSelf = strongSelf, let peerSelected = strongSelf.peerSelected { if case let .channel(peer) = peer, peer.isForumOrMonoForum, threadId == nil, strongSelf.selectForumThreads { diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index abc5f036a2..0c327ec041 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -42,16 +41,16 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let hasTypeHeaders: Bool private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private let suggestedPeers: [EnginePeer] - + private var presentationInterfaceState: ChatPresentationInterfaceState private let presentationInterfaceStatePromise = ValuePromise() - + private var interfaceInteraction: ChatPanelInterfaceInteraction? - + var inProgress: Bool = false - + var navigationBar: NavigationBar? - + private let requirementsBackgroundNode: NavigationBackgroundNode? private let requirementsSeparatorNode: ASDisplayNode? private let requirementsTextNode: ImmediateTextNode? @@ -61,28 +60,28 @@ final class PeerSelectionControllerNode: ASDisplayNode { private let emptyTitleNode: ImmediateTextNode private let emptyTextNode: ImmediateTextNode private let emptyButtonNode: SolidRoundedButtonNode - + private let bottomEdgeEffectView: EdgeEffectView? private let segmentedControl: ComponentView? private var segmentedControlItems: [String]? private var segmentedControlSelectedIndex: Int = 0 - + private var textInputPanelNode: AttachmentTextInputPanelNode? private var forwardAccessoryPanelNode: ForwardAccessoryPanelNode? - + var contactListNode: ContactListNode? let chatListNode: ChatListNode? let mainContainerNode: ChatListContainerNode? - + private var contactListActive = false - + private var searchDisplayController: SearchDisplayController? - + private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)? - + var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? var contentScrollingEnded: ((ListView) -> Bool)? - + var requestActivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)? var requestOpenPeer: ((EnginePeer, Int64?) -> Void)? @@ -90,32 +89,32 @@ final class PeerSelectionControllerNode: ASDisplayNode { var requestOpenPeerFromSearch: ((EnginePeer, Int64?) -> Void)? var requestOpenMessageFromSearch: ((EnginePeer, Int64?, EngineMessage.Id) -> Void)? var requestSend: (([EnginePeer], [EnginePeer.Id: EnginePeer], NSAttributedString, AttachmentTextInputPanelSendMode, ChatInterfaceForwardOptionsState?, ChatSendMessageActionSheetController.SendParameters?) -> Void)? - + private var presentationData: PresentationData { didSet { self.presentationDataPromise.set(.single(self.presentationData)) } } private var presentationDataPromise = Promise() - + private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - + private var countPanelNode: PeersCountPanelNode? - + weak var pushedController: ViewController? - + private var readyValue = Promise() var ready: Signal { return self.readyValue.get() } - + private var isEmpty = false - + private var updatedPresentationData: (initial: PresentationData, signal: Signal) { return (self.presentationData, self.presentationDataPromise.get()) } - + init(context: AccountContext, controller: PeerSelectionControllerImpl, presentationData: PresentationData, filter: ChatListNodePeersFilter, forumPeerId: (id: EnginePeer.Id, isMonoforum: Bool)?, hasFilters: Bool, hasChatListSelector: Bool, hasContactSelector: Bool, hasGlobalSearch: Bool, forwardedMessageIds: [EngineMessage.Id], hasTypeHeaders: Bool, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, hasCreation: Bool, createNewGroup: (() -> Void)?, suggestedPeers: [EnginePeer], present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void) { self.context = context self.controller = controller @@ -129,17 +128,17 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.hasTypeHeaders = hasTypeHeaders self.requestPeerType = requestPeerType self.suggestedPeers = suggestedPeers - + self.presentationData = presentationData - + self.animationCache = context.animationCache self.animationRenderer = context.animationRenderer - - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) - + + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: EnginePeer.Id(0)), subject: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = self.presentationInterfaceState.updatedInterfaceState { $0.withUpdatedForwardMessageIds(forwardedMessageIds) } self.presentationInterfaceStatePromise.set(self.presentationInterfaceState) - + if let _ = self.requestPeerType { self.requirementsBackgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor) self.requirementsSeparatorNode = ASDisplayNode() @@ -152,34 +151,34 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.requirementsSeparatorNode = nil self.requirementsTextNode = nil } - + self.emptyTitleNode = ImmediateTextNode() self.emptyTitleNode.displaysAsynchronously = false self.emptyTitleNode.maximumNumberOfLines = 0 self.emptyTitleNode.isHidden = true self.emptyTitleNode.textAlignment = .center self.emptyTitleNode.lineSpacing = 0.25 - + self.emptyTextNode = ImmediateTextNode() self.emptyTextNode.displaysAsynchronously = false self.emptyTextNode.maximumNumberOfLines = 0 self.emptyTextNode.isHidden = true self.emptyTextNode.lineSpacing = 0.25 - + self.emptyAnimationNode = DefaultAnimatedStickerNodeImpl() self.emptyAnimationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListNoResults"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.emptyAnimationNode.isHidden = true self.emptyAnimationSize = CGSize(width: 120.0, height: 120.0) - + self.emptyButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), cornerRadius: 11.0, isShimmering: true) self.emptyButtonNode.isHidden = true self.emptyButtonNode.pressed = { createNewGroup?() } - + if hasChatListSelector && hasContactSelector { self.bottomEdgeEffectView = EdgeEffectView() - + self.segmentedControl = ComponentView() self.segmentedControlItems = [ self.presentationData.strings.DialogList_TabTitle, @@ -189,13 +188,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.bottomEdgeEffectView = nil self.segmentedControl = nil } - + var chatListCategories: [ChatListNodeAdditionalCategory] = [] - + if let _ = createNewGroup { chatListCategories.append(ChatListNodeAdditionalCategory(id: 0, icon: PresentationResourcesItemList.createGroupIcon(self.presentationData.theme), smallIcon: nil, title: self.presentationData.strings.PeerSelection_ImportIntoNewGroup, appearance: .action)) } - + let chatListLocation: ChatListControllerLocation if let (forumPeerId, isMonoforum) = self.forumPeerId { if isMonoforum { @@ -206,14 +205,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { chatListLocation = .chatList(groupId: .root) } - + let chatListMode: ChatListNodeMode if let requestPeerType = self.requestPeerType { chatListMode = .peerType(type: requestPeerType, hasCreate: hasCreation) } else { chatListMode = .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, topPeers: self.suggestedPeers, chatListFilters: nil, displayAutoremoveTimeout: false, displayPresence: false) } - + if hasFilters { self.mainContainerNode = ChatListContainerNode(context: context, controller: nil, location: chatListLocation, chatListMode: chatListMode, previewing: false, controlsHistoryPreload: false, isInlineMode: false, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in @@ -228,38 +227,38 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.chatListNode?.selectionLimit = multipleSelectionLimit } } - + super.init() - + self.setViewBlock({ return UITracingLayerView() }) - + self.chatListNode?.additionalCategorySelected = { _ in createNewGroup?() } - + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - + self.chatListNode?.selectionCountChanged = { [weak self] count in self?.textInputPanelNode?.updateSendButtonEnabled(count > 0, animated: true) } self.chatListNode?.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } - + self.chatListNode?.activateSearch = { [weak self] in self?.requestActivateSearch?() } self.mainContainerNode?.activateSearch = { [weak self] in self?.requestActivateSearch?() } - + self.chatListNode?.peerSelected = { [weak self] peer, threadId, _, _, _ in guard let self else { return } - + if let (peerId, isMonoforum) = self.forumPeerId, isMonoforum { let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) @@ -280,7 +279,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { guard let self else { return } - + if let (peerId, isMonoforum) = self.forumPeerId, isMonoforum { let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) @@ -299,14 +298,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.requestOpenPeer?(peer, threadId) } } - + self.chatListNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in self?.requestOpenDisabledPeer?(peer, threadId, reason) } self.mainContainerNode?.disabledPeerSelected = { [weak self] peer, threadId, reason in self?.requestOpenDisabledPeer?(peer, threadId, reason) } - + self.chatListNode?.contentOffsetChanged = { [weak self] offset in guard let strongSelf = self else { return @@ -315,7 +314,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.contentOffsetChanged?(offset) } } - + self.mainContainerNode?.contentOffsetChanged = { [weak self] offset, _ in guard let strongSelf = self else { return @@ -324,11 +323,11 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.contentOffsetChanged?(offset) } } - + self.chatListNode?.contentScrollingEnded = { [weak self] listView in return self?.contentScrollingEnded?(listView) ?? false } - + self.chatListNode?.isEmptyUpdated = { [weak self] state, _, _ in guard let strongSelf = self else { return @@ -339,7 +338,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) } } - + if let mainContainerNode = self.mainContainerNode { mainContainerNode.displayFilterLimit = { [weak self] in guard let strongSelf = self else { @@ -362,33 +361,33 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let chatListNode = self.chatListNode { self.addSubnode(chatListNode) } - + if hasChatListSelector && hasContactSelector { if let bottomEdgeEffectView = self.bottomEdgeEffectView { self.view.addSubview(bottomEdgeEffectView) } } - + if let requirementsBackgroundNode = self.requirementsBackgroundNode, let requirementsSeparatorNode = self.requirementsSeparatorNode, let requirementsTextNode = self.requirementsTextNode { self.chatListNode?.addSubnode(requirementsBackgroundNode) self.chatListNode?.addSubnode(requirementsSeparatorNode) self.chatListNode?.addSubnode(requirementsTextNode) - + self.addSubnode(self.emptyAnimationNode) self.addSubnode(self.emptyTitleNode) self.addSubnode(self.emptyTextNode) self.addSubnode(self.emptyButtonNode) } - + if !hasChatListSelector && hasContactSelector { self.indexChanged(1) } - + self.interfaceInteraction = ChatPanelInterfaceInteraction(setupReplyMessage: { _, _, _ in }, setupEditMessage: { _, _ in }, beginMessageSelection: { _, _ in }, cancelMessageSelection: { _ in - }, deleteSelectedMessages: { + }, deleteSelectedMessages: { _ in }, reportSelectedMessages: { }, reportMessages: { _, _ in }, blockMessageAuthor: { _, _ in @@ -407,16 +406,16 @@ final class PeerSelectionControllerNode: ASDisplayNode { } let presentationData = strongSelf.presentationData - + let peerIds = strongSelf.selectedPeers.0.map { $0.id } - + let forwardOptions: Signal forwardOptions = strongSelf.presentationInterfaceStatePromise.get() |> map { state -> ChatControllerSubject.ForwardOptions in return ChatControllerSubject.ForwardOptions(hideNames: state.interfaceState.forwardOptionsState?.hideNames ?? false, hideCaptions: state.interfaceState.forwardOptionsState?.hideCaptions ?? false) } |> distinctUntilChanged - + let chatController = strongSelf.context.sharedContext.makeChatController( context: strongSelf.context, chatLocation: .peer(id: strongSelf.context.account.peerId), @@ -426,7 +425,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { params: nil ) chatController.canReadHistory.set(false) - + let messageIds = strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [] let messagesCount: Signal if messageIds.count > 1 { @@ -443,17 +442,18 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { messagesCount = .single(1) } - + let accountPeerId = strongSelf.context.account.peerId let items = combineLatest(forwardOptions, strongSelf.context.account.postbox.messagesAtIds(messageIds), messagesCount) |> map { forwardOptions, messages, messagesCount -> [ContextMenuItem] in var items: [ContextMenuItem] = [] - + var hasCaptions = false - var uniquePeerIds = Set() - + var uniquePeerIds = Set() + var hasOther = false var hasNotOwnMessages = false + var hasRichMessages = false for message in messages { if let author = message.effectiveAuthor { if !uniquePeerIds.contains(author.id) { @@ -464,15 +464,12 @@ final class PeerSelectionControllerNode: ASDisplayNode { hasNotOwnMessages = true } } - + var isDice = false - var isMusic = false for media in message.media { - if let media = media as? TelegramMediaFile, media.isMusic { - isMusic = true - } else if media is TelegramMediaDice { + if media is TelegramMediaDice { isDice = true - } else { + } else if (media as? TelegramMediaFile)?.isMusic != true { if !message.text.isEmpty { if media is TelegramMediaImage || media is TelegramMediaFile { hasCaptions = true @@ -480,16 +477,19 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } } - if !isDice && !isMusic { + if !isDice { hasOther = true } + if message.richText != nil { + hasRichMessages = true + } } - - let canHideNames = hasNotOwnMessages && hasOther - + + let canHideNames = hasNotOwnMessages && hasOther && !hasRichMessages + let hideNames = forwardOptions.hideNames let hideCaptions = forwardOptions.hideCaptions - + if !"".isEmpty { // check if seecret chat } else { if canHideNames { @@ -508,7 +508,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return updated }) }))) - + items.append(.action(ContextMenuActionItem(text: uniquePeerIds.count == 1 ? presentationData.strings.Conversation_ForwardOptions_HideSendersName : presentationData.strings.Conversation_ForwardOptions_HideSendersNames, icon: { theme in if hideNames { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) @@ -523,10 +523,10 @@ final class PeerSelectionControllerNode: ASDisplayNode { return updated }) }))) - + items.append(.separator) } - + if hasCaptions { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowCaption, icon: { theme in if hideCaptions { @@ -545,7 +545,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return updated }) }))) - + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_HideCaption, icon: { theme in if hideCaptions { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) @@ -563,11 +563,11 @@ final class PeerSelectionControllerNode: ASDisplayNode { return updated }) }))) - + items.append(.separator) } } - + items.append(.action(ContextMenuActionItem(text: messagesCount == 1 ? presentationData.strings.Conversation_ForwardOptions_SendMessage : presentationData.strings.Conversation_ForwardOptions_SendMessages, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak chatController] c, f in guard let strongSelf = self else { return @@ -581,7 +581,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { f(.default) }))) - + return items } @@ -685,7 +685,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { var selectionRange: Range? var text: NSAttributedString? var inputMode: ChatInputMode? - + strongSelf.updateChatPresentationInterfaceState(animated: true, { state in selectionRange = state.interfaceState.effectiveInputState.selectionRange if let selectionRange = selectionRange { @@ -694,7 +694,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { inputMode = state.inputMode return state }) - + var link: String? if let text { text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in @@ -703,8 +703,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } } - - let controller = chatTextLinkEditController(context: context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in + + let controller = chatTextLinkEditController(context: context, updatedPresentationData: (presentationData, .never()), text: presentationData.strings.TextFormat_AddLinkText(text?.string ?? "").string, link: link, apply: { [weak self] link, _ in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link = link { strongSelf.updateChatPresentationInterfaceState(animated: true, { state in @@ -725,13 +725,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { }) strongSelf.present(controller, nil) } - }, openDateEditing: { + }, openDateEditing: { }, displaySlowmodeTooltip: { _, _ in }, displaySendMessageOptions: { [weak self] node, gesture in guard let strongSelf = self else { return } - + let _ = (ChatSendMessageContextScreen.initialData(context: strongSelf.context, currentMessageEffectId: nil) |> deliverOnMainQueue).start(next: { initialData in guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else { @@ -741,12 +741,12 @@ final class PeerSelectionControllerNode: ASDisplayNode { guard let textInputNode = textInputPanelNode.textInputNode else { return } - + var hasEntityKeyboard = false if case .media = strongSelf.presentationInterfaceState.inputMode { hasEntityKeyboard = true } - + let controller = makeChatSendMessageActionSheetController( initialData: initialData, context: strongSelf.context, @@ -796,7 +796,6 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.presentInGlobalOverlay(controller, nil) }) }, openScheduledMessages: { - }, openPeersNearby: { }, displaySearchResultsTooltip: { _, _ in }, unarchivePeer: { }, scrollToTop: { @@ -847,15 +846,15 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, chatController: { return nil }, statuses: nil) - + if let chatListNode = self.chatListNode { self.readyValue.set(chatListNode.ready) } } - + override func didLoad() { super.didLoad() - + if let controller = self.controller, controller.immediatelySwitchToContacts { self.segmentedControlSelectedIndex = 1 if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { @@ -864,33 +863,33 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.indexChanged(1) } } - + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData self.updateThemeAndStrings() self.mainContainerNode?.updatePresentationData(presentationData) } - + private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, f, completion: completion) } - + private func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) { let presentationInterfaceState = f(self.presentationInterfaceState) let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState - + self.presentationInterfaceState = presentationInterfaceState self.presentationInterfaceStatePromise.set(presentationInterfaceState) - + if let textInputPanelNode = self.textInputPanelNode, updateInputTextState { textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated) } - + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: transition) } } - + private var selectedPeers: ([EnginePeer], [EnginePeer.Id: EnginePeer]) { if self.contactListActive { let selectedContactPeers = self.contactListNode?.selectedPeers ?? [] @@ -899,8 +898,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { var selectedPeerMap: [EnginePeer.Id: EnginePeer] = [:] for contactPeer in selectedContactPeers { if case let .peer(peer, _, _) = contactPeer { - selectedPeers.append(EnginePeer(peer)) - selectedPeerMap[peer.id] = EnginePeer(peer) + selectedPeers.append(peer) + selectedPeerMap[peer.id] = peer } } return (selectedPeers, selectedPeerMap) @@ -930,7 +929,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return (selectedPeers, selectedPeerMap) } } - + func beginSelection() { guard let controller = self.controller else { return @@ -947,7 +946,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }) self.addSubnode(countPanelNode) self.countPanelNode = countPanelNode - + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) } @@ -958,33 +957,50 @@ final class PeerSelectionControllerNode: ASDisplayNode { forwardAccessoryPanelNode.interfaceInteraction = self.interfaceInteraction self.addSubnode(forwardAccessoryPanelNode) self.forwardAccessoryPanelNode = forwardAccessoryPanelNode - - let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, presentController: { [weak self] c in self?.present(c, nil) }, makeEntityInputView: { - return nil - }) + + let textInputPanelNode = AttachmentTextInputPanelNode( + context: self.context, + presentationInterfaceState: self.presentationInterfaceState, + customEmojiAvailable: self.presentationInterfaceState.customEmojiAvailable, + presentController: { [weak self] c in + self?.present(c, nil) + }, + presentInGlobalOverlay: { [weak self] c in + self?.presentInGlobalOverlay(c, nil) + }, + getNavigationController: { [weak self] in + return self?.controller?.navigationController as? NavigationController + } + ) textInputPanelNode.interfaceInteraction = self.interfaceInteraction textInputPanelNode.sendMessage = { [weak self] mode, messageEffect in guard let strongSelf = self else { return } - + let effectiveInputText = strongSelf.presentationInterfaceState.interfaceState.composeInputState.inputText let forwardOptionsState = strongSelf.presentationInterfaceState.interfaceState.forwardOptionsState - + let (selectedPeers, selectedPeerMap) = strongSelf.selectedPeers if !selectedPeers.isEmpty { strongSelf.requestSend?(selectedPeers, selectedPeerMap, effectiveInputText, mode, forwardOptionsState, messageEffect) } } + textInputPanelNode.updateHeight = { [weak self] _ in + guard let self, let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout else { + return + } + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) + } self.addSubnode(textInputPanelNode) self.textInputPanelNode = textInputPanelNode - + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) } } } - + if self.contactListActive { self.contactListNode?.multipleSelection = true self.contactListNode?.updateSelectionState({ _ in @@ -1009,11 +1025,11 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.countPanelNode?.buttonTitle = self.presentationData.strings.ShareMenu_Send } self.countPanelNode?.count = count - + if let titleView = self.controller?.titleView, let maxCount = self.controller?.multipleSelectionLimit { titleView.title = CounterControllerTitle(title: titleView.title.title, counter: "\(count)/\(maxCount)") } - + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) } @@ -1027,49 +1043,46 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } } - + private func updateThemeAndStrings() { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.searchDisplayController?.updatePresentationData(self.presentationData) self.chatListNode?.updateThemeAndStrings(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.updateChatPresentationInterfaceState({ $0.updatedTheme(self.presentationData.theme) }) - + self.requirementsBackgroundNode?.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate) - + if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .immediate) } } - + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = (layout, navigationBarHeight, actualNavigationBarHeight) - + let cleanInsets = layout.insets(options: []) var insets = layout.insets(options: [.input]) - + var toolbarHeight: CGFloat = cleanInsets.bottom var textPanelHeight: CGFloat? var accessoryHeight: CGFloat = 0.0 - + if let forwardAccessoryPanelNode = self.forwardAccessoryPanelNode { let size = forwardAccessoryPanelNode.calculateSizeThatFits(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)) accessoryHeight = size.height } - + if let textInputPanelNode = self.textInputPanelNode { var panelTransition = transition if textInputPanelNode.frame.width.isZero { panelTransition = .immediate } - var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) - if self.searchDisplayController == nil { - panelHeight += insets.bottom - } else { - panelHeight += cleanInsets.bottom - } + var panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.safeInsets.bottom, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), textFieldMaxHeight: layout.size.height / 2.0, availableHeight: layout.size.height, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let effectiveBottomInset = textInputPanelNode.additionalInputHeight > 0.0 ? 0.0 : (self.searchDisplayController == nil ? insets.bottom : cleanInsets.bottom) + panelHeight += effectiveBottomInset textPanelHeight = panelHeight - + let panelFrame = CGRect(x: 0.0, y: layout.size.height - panelHeight, width: layout.size.width, height: panelHeight) if textInputPanelNode.frame.width.isZero { var initialPanelFrame = panelFrame @@ -1078,13 +1091,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { } transition.updateFrame(node: textInputPanelNode, frame: panelFrame) } - + if let forwardAccessoryPanelNode = self.forwardAccessoryPanelNode { let size = forwardAccessoryPanelNode.calculateSizeThatFits(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)) forwardAccessoryPanelNode.updateState(size: size, inset: layout.safeInsets.left, interfaceState: self.presentationInterfaceState) forwardAccessoryPanelNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, forwardOptionsState: self.presentationInterfaceState.interfaceState.forwardOptionsState) let panelFrame = CGRect(x: 0.0, y: layout.size.height - (textPanelHeight ?? 0.0) - size.height, width: size.width, height: size.height) - + accessoryHeight = size.height if forwardAccessoryPanelNode.frame.width.isZero { var initialPanelFrame = panelFrame @@ -1093,7 +1106,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } transition.updateFrame(node: forwardAccessoryPanelNode, frame: panelFrame) } - + if let countPanelNode = self.countPanelNode { let countPanelHeight = countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition) if countPanelNode.count == 0 { @@ -1108,11 +1121,11 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { toolbarHeight += 44.0 } - + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight - 10.0), size: CGSize(width: layout.size.width, height: toolbarHeight + 10.0)) transition.updateFrame(view: bottomEdgeEffectView, frame: edgeEffectFrame) bottomEdgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, blur: true, alpha: 0.3, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(30.0, edgeEffectFrame.height), transition: ComponentTransition(transition)) - + let controlSize = segmentedControl.update( transition: ComponentTransition(transition), component: AnyComponent(SegmentControlComponent( @@ -1132,7 +1145,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { if let (layout, navigationBarHeight, actualNavigationBarHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, actualNavigationBarHeight: actualNavigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) } - + self.indexChanged(index) } )), @@ -1148,28 +1161,28 @@ final class PeerSelectionControllerNode: ASDisplayNode { transition.updateFrame(view: segmentedControlView, frame: segmentedControlFrame) } } - + insets.top += navigationBarHeight insets.bottom = max(insets.bottom, toolbarHeight) insets.left += layout.safeInsets.left insets.right += layout.safeInsets.right - + var headerInsets = layout.insets(options: [.input]) headerInsets.top += actualNavigationBarHeight headerInsets.bottom = max(headerInsets.bottom, cleanInsets.bottom) headerInsets.left += layout.safeInsets.left headerInsets.right += layout.safeInsets.right - + if let chatListNode = self.chatListNode { chatListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) chatListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) } - + if let mainContainerNode = self.mainContainerNode { transition.updateFrame(node: mainContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) mainContainerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: actualNavigationBarHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: navigationBarHeight, insets: insets, isReorderingFilters: false, isEditing: false, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0, storiesInset: 0.0, transition: transition) } - + if let requestPeerTypes = self.requestPeerType, let requestPeerType = requestPeerTypes.first { if self.isEmpty { self.chatListNode?.isHidden = true @@ -1177,7 +1190,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.requirementsTextNode?.isHidden = true self.requirementsSeparatorNode?.isHidden = true self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) - + var emptyTitle: String var emptyText: String var emptyButtonText: String @@ -1219,14 +1232,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { emptyText = "" emptyButtonText = "" } - + self.emptyTitleNode.attributedText = NSAttributedString(string: emptyTitle, font: Font.semibold(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor) self.emptyTextNode.attributedText = NSAttributedString(string: emptyText, font: Font.regular(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor) - + let padding: CGFloat = 44.0 let emptyTitleSize = self.emptyTitleNode.updateLayout(CGSize(width: layout.size.width - insets.left * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) let emptyTextSize = self.emptyTextNode.updateLayout(CGSize(width: layout.size.width - insets.left * 2.0 - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) - + let emptyAnimationHeight = self.emptyAnimationSize.height let emptyAnimationSpacing: CGFloat = 12.0 let emptyTextSpacing: CGFloat = 17.0 @@ -1238,7 +1251,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing + emptyButtonSpacing + emptyButtonHeight let emptyAnimationY = floorToScreenPixels((layout.size.height - emptyTotalHeight) / 2.0) - + if !emptyButtonText.isEmpty { let buttonPadding: CGFloat = 30.0 self.emptyButtonNode.title = emptyButtonText @@ -1249,13 +1262,13 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { self.emptyButtonNode.isHidden = true } - + let textTransition = ContainedViewLayoutTransition.immediate textTransition.updateFrame(node: self.emptyAnimationNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - self.emptyAnimationSize.width) / 2.0), y: emptyAnimationY), size: self.emptyAnimationSize)) textTransition.updateFrame(node: self.emptyTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - emptyTitleSize.width) / 2.0), y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing), size: emptyTitleSize)) textTransition.updateFrame(node: self.emptyTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - emptyTextSize.width) / 2.0), y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) self.emptyAnimationNode.updateLayout(size: self.emptyAnimationSize) - + self.emptyAnimationNode.isHidden = false self.emptyTitleNode.isHidden = false self.emptyTextNode.isHidden = false @@ -1263,54 +1276,54 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else if let requirementsBackgroundNode = self.requirementsBackgroundNode, let requirementsSeparatorNode = self.requirementsSeparatorNode, let requirementsTextNode = self.requirementsTextNode, let requirementsText = stringForRequestPeerType(strings: self.presentationData.strings, peerType: requestPeerType, offset: true) { let requirements = NSMutableAttributedString(string: self.presentationData.strings.RequestPeer_Requirements + "\n", font: Font.semibold(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor) requirements.append(NSAttributedString(string: requirementsText, font: Font.regular(13.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor)) - + requirementsTextNode.attributedText = requirements let sideInset: CGFloat = 16.0 let verticalInset: CGFloat = 11.0 let requirementsSize = requirementsTextNode.updateLayout(CGSize(width: layout.size.width - insets.left - insets.right - sideInset * 2.0, height: .greatestFiniteMagnitude)) - + let requirementsBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: actualNavigationBarHeight), size: CGSize(width: layout.size.width, height: requirementsSize.height + verticalInset * 2.0)) insets.top += requirementsBackgroundFrame.height - + requirementsBackgroundNode.update(size: requirementsBackgroundFrame.size, transition: transition) transition.updateFrame(node: requirementsBackgroundNode, frame: requirementsBackgroundFrame) - + transition.updateFrame(node: requirementsSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: requirementsBackgroundFrame.maxY - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - + requirementsTextNode.frame = CGRect(origin: CGPoint(x: insets.left + sideInset, y: requirementsBackgroundFrame.minY + verticalInset), size: requirementsSize) } } - + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve) - + if let chatListNode = self.chatListNode { chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, originalTopInset: updateSizeAndInsets.insets.top, storiesInset: 0.0, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0) } - + if let contactListNode = self.contactListNode { contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) contactListNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - + contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition) } - + if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } } - + func activateSearch(placeholderNode: SearchBarPlaceholderNode) { guard let (containerLayout, navigationBarHeight, _) = self.containerLayout, let navigationBar = self.navigationBar else { return } - + self.navigationBar?.setSecondaryContentNode(nil, animated: true) - + if self.chatListNode?.supernode != nil || self.mainContainerNode?.supernode != nil { self.chatListNode?.accessibilityElementsHidden = true self.mainContainerNode?.accessibilityElementsHidden = true - + let chatListLocation: ChatListControllerLocation if let (forumPeerId, isMonoforum) = self.forumPeerId { if isMonoforum { @@ -1321,7 +1334,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } else { chatListLocation = .chatList(groupId: EngineChatList.Group(.root)) } - + self.searchDisplayController = SearchDisplayController( presentationData: self.presentationData, contentNode: ChatListSearchContainerNode( @@ -1341,14 +1354,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { } var updated = false var count = 0 - + let chatListNode: ChatListNode? if let mainContainerNode = strongSelf.mainContainerNode { chatListNode = mainContainerNode.currentItemNode } else { chatListNode = strongSelf.chatListNode } - + chatListNode?.updateState { state in if state.editing { updated = true @@ -1419,7 +1432,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } }, fieldStyle: placeholderNode.fieldStyle ) - + self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in if let strongSelf = self, let strongPlaceholderNode = placeholderNode { @@ -1430,10 +1443,10 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } }, placeholder: placeholderNode) - + } else if let contactListNode = self.contactListNode, contactListNode.supernode != nil { contactListNode.accessibilityElementsHidden = true - + var categories: ContactsSearchCategories = [.cloudContacts] if self.hasGlobalSearch { categories.insert(.global) @@ -1465,7 +1478,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { return nil } } - + if updated { strongSelf.textInputPanelNode?.updateSendButtonEnabled(count > 0, animated: true) strongSelf.requestDeactivateSearch?() @@ -1493,7 +1506,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { requestDeactivateSearch() } }, fieldStyle: placeholderNode.fieldStyle) - + self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate) self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in if let strongSelf = self, let strongPlaceholderNode = placeholderNode { @@ -1506,30 +1519,30 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, placeholder: placeholderNode) } } - + func deactivateSearch(placeholderNode: SearchBarPlaceholderNode) { if let searchDisplayController = self.searchDisplayController { if self.chatListNode?.supernode != nil || self.mainContainerNode?.supernode != nil { self.chatListNode?.accessibilityElementsHidden = false self.mainContainerNode?.accessibilityElementsHidden = false - + if let controller = self.controller, controller.chatListFiltersNonEmpty { self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: true) } self.controller?.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) - + searchDisplayController.deactivate(placeholder: placeholderNode) self.searchDisplayController = nil } else if let contactListNode = self.contactListNode, contactListNode.supernode != nil { contactListNode.accessibilityElementsHidden = false - + self.controller?.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) searchDisplayController.deactivate(placeholder: placeholderNode) self.searchDisplayController = nil } } } - + func scrollToTop() { if self.mainContainerNode?.supernode != nil { self.mainContainerNode?.scrollToTop(animated: true, adjustForTempInset: false) @@ -1539,7 +1552,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { //contactListNode.scrollToTop() } } - + private func indexChanged(_ index: Int) { let contactListActive = index == 1 if contactListActive != self.contactListActive { @@ -1556,7 +1569,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } self.recursivelyEnsureDisplaySynchronously(true) contactListNode.enableUpdates = true - + if let (layout, _, _) = self.containerLayout { self.controller?.containerLayoutUpdated(layout, transition: .immediate) } @@ -1575,7 +1588,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { contactListNode.openPeer = { [weak self] peer, _, _, _ in if case let .peer(peer, _, _) = peer { self?.contactListNode?.listNode.clearHighlightAnimated(true) - self?.requestOpenPeer?(EnginePeer(peer), nil) + self?.requestOpenPeer?(peer, nil) } } contactListNode.openDisabledPeer = { [weak self] peer, reason in @@ -1599,14 +1612,14 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.contentOffsetChanged?(offset) } } - + contactListNode.contentScrollingEnded = { [weak self] listView in return self?.contentScrollingEnded?(listView) ?? false } - + if let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, actualNavigationBarHeight: actualNavigationHeight, transition: .immediate) - + let _ = (contactListNode.ready |> deliverOnMainQueue).start(next: { [weak self] _ in if let strongSelf = self { strongSelf.navigationBar?.setSecondaryContentNode(nil, animated: false) @@ -1620,7 +1633,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } } strongSelf.recursivelyEnsureDisplaySynchronously(true) - + if let (layout, _, _) = strongSelf.containerLayout { strongSelf.controller?.containerLayoutUpdated(layout, transition: .immediate) } @@ -1636,7 +1649,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { mainContainerNode.removeFromSupernode() } self.recursivelyEnsureDisplaySynchronously(true) - + if let (layout, _, _) = self.containerLayout { self.controller?.containerLayoutUpdated(layout, transition: .immediate) } @@ -1647,7 +1660,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.navigationBar?.setSecondaryContentNode(self.controller?.tabContainerNode, animated: false) } contactListNode.enableUpdates = false - + if let mainContainerNode = self.mainContainerNode { self.insertSubnode(mainContainerNode, aboveSubnode: contactListNode) } @@ -1655,7 +1668,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.insertSubnode(chatListNode, aboveSubnode: contactListNode) } contactListNode.removeFromSupernode() - + if let (layout, _, _) = self.containerLayout { self.controller?.containerLayoutUpdated(layout, transition: .immediate) } @@ -1669,7 +1682,7 @@ public func stringForAdminRights(strings: PresentationStrings, adminRights: Tele func append(_ string: String) { rights.append("• \(string)") } - + if isChannel { if adminRights.rights.contains(.canChangeInfo) { append(strings.RequestPeer_Requirement_Channel_Rights_Info) @@ -1745,7 +1758,7 @@ public func stringForAdminRights(strings: PresentationStrings, adminRights: Tele private func stringForRequestPeerType(strings: PresentationStrings, peerType: ReplyMarkupButtonRequestPeerType, offset: Bool) -> String? { var lines: [String] = [] - + func append(_ string: String) { if offset { lines.append(" • \(string)") @@ -1753,7 +1766,7 @@ private func stringForRequestPeerType(strings: PresentationStrings, peerType: Re lines.append("• \(string)") } } - + switch peerType { case let .user(user): if let isPremium = user.isPremium { @@ -1886,25 +1899,25 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent let controller: ViewController weak var sourceView: UIView? let sourceRect: CGRect? - + let navigationController: NavigationController? = nil - + let passthroughTouches: Bool - + init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect? = nil, passthroughTouches: Bool) { self.controller = controller self.sourceView = sourceNode?.view self.sourceRect = sourceRect self.passthroughTouches = passthroughTouches } - + init(controller: ViewController, sourceView: UIView?, sourceRect: CGRect? = nil, passthroughTouches: Bool) { self.controller = controller self.sourceView = sourceView self.sourceRect = sourceRect self.passthroughTouches = passthroughTouches } - + func transitionInfo() -> ContextControllerTakeControllerInfo? { let sourceView = self.sourceView let sourceRect = self.sourceRect @@ -1916,7 +1929,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } }) } - + func animatedIn() { } } @@ -1924,60 +1937,60 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent private final class PeersCountPanelNode: ASDisplayNode { private let theme: PresentationTheme private let strings: PresentationStrings - + private let separatorNode: ASDisplayNode private let button: SolidRoundedButtonNode - + private var validLayout: (CGFloat, CGFloat, CGFloat)? - + var buttonTitle: String = "" var count: Int = 0 { didSet { if self.count != oldValue && self.count > 0 { self.button.title = self.buttonTitle self.button.badge = "\(self.count)" - + if let (width, sideInset, bottomInset) = self.validLayout { let _ = self.updateLayout(width: width, sideInset: sideInset, bottomInset: bottomInset, transition: .immediate) } } } } - + init(theme: PresentationTheme, strings: PresentationStrings, action: @escaping () -> Void) { self.theme = theme self.strings = strings self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = theme.rootController.navigationBar.separatorColor - + self.button = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 48.0, cornerRadius: 10.0) - + super.init() - + self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor - + self.addSubnode(self.button) self.addSubnode(self.separatorNode) - + self.button.pressed = { action() } } - + func updateLayout(width: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { self.validLayout = (width, sideInset, bottomInset) let topInset: CGFloat = 9.0 var bottomInset = bottomInset bottomInset += topInset - (bottomInset.isZero ? 0.0 : 4.0) - + let buttonInset: CGFloat = 16.0 + sideInset let buttonWidth = width - buttonInset * 2.0 let buttonHeight = self.button.updateLayout(width: buttonWidth, transition: transition) transition.updateFrame(node: self.button, frame: CGRect(x: buttonInset, y: topInset, width: buttonWidth, height: buttonHeight)) - + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) - + return topInset + buttonHeight + bottomInset } } diff --git a/submodules/TelegramUI/Components/PollStatsScreen/BUILD b/submodules/TelegramUI/Components/PollStatsScreen/BUILD new file mode 100644 index 0000000000..6cd172cdc4 --- /dev/null +++ b/submodules/TelegramUI/Components/PollStatsScreen/BUILD @@ -0,0 +1,32 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PollStatsScreen", + module_name = "PollStatsScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/SheetComponent", + "//submodules/ItemListUI", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/StatisticsUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/PollStatsScreen/Sources/PollStatsScreen.swift b/submodules/TelegramUI/Components/PollStatsScreen/Sources/PollStatsScreen.swift new file mode 100644 index 0000000000..03be0edc4b --- /dev/null +++ b/submodules/TelegramUI/Components/PollStatsScreen/Sources/PollStatsScreen.swift @@ -0,0 +1,342 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import TelegramCore +import AccountContext +import TelegramPresentationData +import ComponentFlow +import ViewControllerComponent +import SheetComponent +import MultilineTextComponent +import BundleIconComponent +import GlassBarButtonComponent +import ListSectionComponent +import ListItemComponentAdaptor +import StatisticsUI +import ItemListUI + +private final class PollStatsSheetContent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let messageId: EngineMessage.Id + let animateOut: ActionSlot> + let getController: () -> ViewController? + + init( + context: AccountContext, + messageId: EngineMessage.Id, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.context = context + self.messageId = messageId + self.animateOut = animateOut + self.getController = getController + } + + static func ==(lhs: PollStatsSheetContent, rhs: PollStatsSheetContent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.messageId != rhs.messageId { + return false + } + return true + } + + final class State: ComponentState { + let pollStatsContext: PollStatsContext + + private let animateOut: ActionSlot> + private let getController: () -> ViewController? + private let stateDisposable = MetaDisposable() + + var stats: PollStats? + private var loadedVotesGraphToken: String? + + init( + context: AccountContext, + messageId: EngineMessage.Id, + animateOut: ActionSlot>, + getController: @escaping () -> ViewController? + ) { + self.pollStatsContext = PollStatsContext(account: context.account, messageId: messageId) + self.animateOut = animateOut + self.getController = getController + + super.init() + + self.stateDisposable.set((self.pollStatsContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.stats = state.stats + if let stats = state.stats, case let .OnDemand(token) = stats.votesGraph, !token.isEmpty, self.loadedVotesGraphToken != token { + self.loadedVotesGraphToken = token + self.pollStatsContext.loadVotesGraph() + } + self.updated(transition: .immediate) + })) + } + + deinit { + self.stateDisposable.dispose() + } + + func dismiss(animated: Bool) { + guard let controller = self.getController() else { + return + } + if animated { + self.animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } else { + controller.dismiss(animated: false) + } + } + } + + func makeState() -> State { + return State( + context: self.context, + messageId: self.messageId, + animateOut: self.animateOut, + getController: self.getController + ) + } + + static var body: Body { + let closeButton = Child(GlassBarButtonComponent.self) + let title = Child(MultilineTextComponent.self) + let section = Child(ListSectionComponent.self) + + return { context in + let environment = context.environment[EnvironmentType.self].value + let component = context.component + let state = context.state + + let theme = environment.theme + let strings = environment.strings + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let sideInset: CGFloat = 16.0 + let sectionWidth = context.availableSize.width - sideInset * 2.0 + let graph = state.stats?.votesGraph ?? .OnDemand(token: "") + + var contentSize = CGSize(width: context.availableSize.width, height: 16.0) + + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + state.dismiss(animated: true) + } + ), + availableSize: CGSize(width: 44.0, height: 44.0), + transition: .immediate + ) + context.add(closeButton.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: contentSize.height + closeButton.size.height / 2.0))) + + let title = title.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.PollStats_Title, + font: Font.semibold(17.0), + textColor: theme.actionSheet.primaryTextColor + )), + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - 120.0, height: 44.0), + transition: .immediate + ) + context.add(title.position(CGPoint(x: context.availableSize.width / 2.0, y: 16.0 + 22.0))) + contentSize.height += 44.0 + contentSize.height += 19.0 + + let section = section.update( + component: ListSectionComponent( + theme: theme, + style: .glass, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: strings.PollStats_GraphHeader.uppercased(), + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: nil, + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( + itemGenerator: StatsGraphItem( + presentationData: ItemListPresentationData(presentationData), + systemStyle: .glass, + graph: graph, + type: .lines5Min, + getDetailsData: { date, completion in + let _ = state.pollStatsContext.loadDetailedGraph(graph, x: Int64(date.timeIntervalSince1970) * 1000).start(next: { detailedGraph in + if let detailedGraph, case let .Loaded(_, data) = detailedGraph { + completion(data) + } else { + completion(nil) + } + }) + }, + sectionId: 0, + style: .blocks + ), + params: ListViewItemLayoutParams( + width: sectionWidth, + leftInset: 0.0, + rightInset: 0.0, + availableHeight: 10000.0, + isStandalone: true + ) + ))), + ], + displaySeparators: false + ), + availableSize: CGSize(width: sectionWidth, height: context.availableSize.height), + transition: context.transition + ) + context.add(section.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + section.size.height / 2.0))) + contentSize.height += section.size.height + contentSize.height += 39.0 + + return contentSize + } + } +} + +private final class PollStatsSheetComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let messageId: EngineMessage.Id + + init( + context: AccountContext, + messageId: EngineMessage.Id + ) { + self.context = context + self.messageId = messageId + } + + static func ==(lhs: PollStatsSheetComponent, rhs: PollStatsSheetComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.messageId != rhs.messageId { + return false + } + return true + } + + static var body: Body { + let sheet = Child(SheetComponent.self) + let animateOut = StoredActionSlot(Action.self) + let sheetExternalState = SheetComponent.ExternalState() + + return { context in + let environment = context.environment[EnvironmentType.self] + let controller = environment.controller + + let dismiss: (Bool) -> Void = { animated in + guard let controller = controller() else { + return + } + if animated { + animateOut.invoke(Action { [weak controller] _ in + controller?.dismiss(completion: nil) + }) + } else { + controller.dismiss(completion: nil) + } + } + + let sheet = sheet.update( + component: SheetComponent( + content: AnyComponent(PollStatsSheetContent( + context: context.component.context, + messageId: context.component.messageId, + animateOut: animateOut, + getController: controller + )), + style: .glass, + backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), + followContentSizeChanges: true, + clipsContent: true, + autoAnimateOut: false, + externalState: sheetExternalState, + animateOut: animateOut, + onPan: {}, + willDismiss: {} + ), + environment: { + environment + SheetComponentEnvironment( + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.value.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { animated in + dismiss(animated) + } + ) + }, + availableSize: context.availableSize, + transition: context.transition + ) + + context.add(sheet.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))) + + return context.availableSize + } + } +} + +public final class PollStatsScreen: ViewControllerComponentContainer { + public init( + context: AccountContext, + messageId: EngineMessage.Id + ) { + super.init( + context: context, + component: PollStatsSheetComponent( + context: context, + messageId: messageId + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: .default + ) + + self.navigationPresentation = .flatModal + self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + self.view.disablesInteractiveModalDismiss = true + } +} diff --git a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift index d0a78d1da8..a5ac5752eb 100644 --- a/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Premium/PremiumStarComponent/Sources/GiftAvatarComponent.swift @@ -394,7 +394,7 @@ public final class GiftAvatarComponent: Component { self.mergedAvatarsNode = mergedAvatarsNode } - mergedAvatarsNode.update(context: component.context, peers: Array(component.peers.map { $0._asPeer() }.prefix(3)), synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 30.0, borderWidth: 2.0, avatarFontSize: 26.0) + mergedAvatarsNode.update(context: component.context, peers: Array(component.peers.prefix(3)), synchronousLoad: false, imageSize: avatarSize.width, imageSpacing: 30.0, borderWidth: 2.0, avatarFontSize: 26.0) let avatarsSize = CGSize(width: avatarSize.width + 30.0 * CGFloat(min(3, component.peers.count) - 1), height: avatarSize.height) mergedAvatarsNode.updateLayout(size: avatarsSize) mergedAvatarsNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - avatarsSize.width) / 2.0), y: 113.0 - avatarSize.height / 2.0), size: avatarsSize) diff --git a/submodules/TelegramUI/Components/PremiumAlertController/BUILD b/submodules/TelegramUI/Components/PremiumAlertController/BUILD index 25d0e52f0d..6e388d0d47 100644 --- a/submodules/TelegramUI/Components/PremiumAlertController/BUILD +++ b/submodules/TelegramUI/Components/PremiumAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift b/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift index 001ca763bb..6cf81be6df 100644 --- a/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift +++ b/submodules/TelegramUI/Components/PremiumAlertController/Sources/PremiumAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD index 69caa55031..58b2aaff5f 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/ProxyServerPreviewScreen/BUILD b/submodules/TelegramUI/Components/ProxyServerPreviewScreen/BUILD index a74ad1276b..e1f2a486e7 100644 --- a/submodules/TelegramUI/Components/ProxyServerPreviewScreen/BUILD +++ b/submodules/TelegramUI/Components/ProxyServerPreviewScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/ProxyServerPreviewScreen/Sources/ProxyServerPreviewScreen.swift b/submodules/TelegramUI/Components/ProxyServerPreviewScreen/Sources/ProxyServerPreviewScreen.swift index 4d43050f2d..7751f63cfc 100644 --- a/submodules/TelegramUI/Components/ProxyServerPreviewScreen/Sources/ProxyServerPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ProxyServerPreviewScreen/Sources/ProxyServerPreviewScreen.swift @@ -20,22 +20,25 @@ import OverlayStatusController private final class ProxyServerPreviewSheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment - let context: AccountContext + let sharedContext: SharedAccountContext + let network: Network let server: ProxyServerSettings let cancel: (Bool) -> Void init( - context: AccountContext, + sharedContext: SharedAccountContext, + network: Network, server: ProxyServerSettings, cancel: @escaping (Bool) -> Void ) { - self.context = context + self.sharedContext = sharedContext + self.network = network self.server = server self.cancel = cancel } static func ==(lhs: ProxyServerPreviewSheetContent, rhs: ProxyServerPreviewSheetContent) -> Bool { - if lhs.context !== rhs.context { + if lhs.sharedContext !== rhs.sharedContext { return false } if lhs.server != rhs.server { @@ -45,7 +48,8 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { } final class State: ComponentState { - private let context: AccountContext + private let sharedContext: SharedAccountContext + private let network: Network private let server: ProxyServerSettings private var disposable = MetaDisposable() @@ -59,8 +63,9 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { private var revertSettings: ProxySettings? - init(context: AccountContext, server: ProxyServerSettings) { - self.context = context + init(sharedContext: SharedAccountContext, network: Network, server: ProxyServerSettings) { + self.sharedContext = sharedContext + self.network = network self.server = server super.init() @@ -71,7 +76,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { self.statusDisposable.dispose() if let revertSettings = self.revertSettings { - let _ = updateProxySettingsInteractively(accountManager: self.context.sharedContext.accountManager, { _ in + let _ = updateProxySettingsInteractively(accountManager: self.sharedContext.accountManager, { _ in return revertSettings }) } @@ -91,7 +96,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { return } - let statusesContext = ProxyServersStatuses(network: self.context.account.network, servers: .single([self.server])) + let statusesContext = ProxyServersStatuses(network: self.network, servers: .single([self.server])) self.statusesContext = statusesContext self.status = .checking @@ -114,13 +119,13 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { return } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let presentationData = self.sharedContext.currentPresentationData.with { $0 } self.displayWarningIfNeeded { [weak self] in guard let self else { return } - let accountManager = self.context.sharedContext.accountManager + let accountManager = self.sharedContext.accountManager let proxyServerSettings = self.server let _ = (accountManager.transaction { transaction -> ProxySettings in var currentSettings: ProxySettings? @@ -145,7 +150,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { self.inProgress = true self.updated() - let signal = self.context.account.network.connectionStatus + let signal = self.network.connectionStatus |> filter { status in switch status { case let .online(proxyAddress): @@ -181,7 +186,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { let _ = updateProxySettingsInteractively(accountManager: accountManager, { _ in return previousSettings }).start() - self.controller?.present(textAlertController(sharedContext: self.context.sharedContext, title: nil, text: presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.controller?.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } } })) @@ -195,9 +200,9 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { commit() return } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let presentationData = self.sharedContext.currentPresentationData.with { $0 } let alertController = textAlertController( - context: context, + sharedContext: self.sharedContext, title: presentationData.strings.SocksProxySetup_Warning_Title, text: presentationData.strings.SocksProxySetup_Warning_Text, actions: [ @@ -212,7 +217,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, server: self.server) + return State(sharedContext: self.sharedContext, network: self.network, server: self.server) } static var body: Body { @@ -414,19 +419,22 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent { private final class ProxyServerPreviewSheetComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment - let context: AccountContext + let sharedContext: SharedAccountContext + let network: Network let server: ProxyServerSettings init( - context: AccountContext, + sharedContext: SharedAccountContext, + network: Network, server: ProxyServerSettings ) { - self.context = context + self.sharedContext = sharedContext + self.network = network self.server = server } static func ==(lhs: ProxyServerPreviewSheetComponent, rhs: ProxyServerPreviewSheetComponent) -> Bool { - if lhs.context !== rhs.context { + if lhs.sharedContext !== rhs.sharedContext { return false } if lhs.server != rhs.server { @@ -446,7 +454,8 @@ private final class ProxyServerPreviewSheetComponent: CombinedComponent { let sheet = sheet.update( component: SheetComponent( content: AnyComponent(ProxyServerPreviewSheetContent( - context: context.component.context, + sharedContext: context.component.sharedContext, + network: context.component.network, server: context.component.server, cancel: { animate in if animate { @@ -504,18 +513,15 @@ private final class ProxyServerPreviewSheetComponent: CombinedComponent { } public class ProxyServerPreviewScreen: ViewControllerComponentContainer { - private let context: AccountContext - public init( context: AccountContext, server: ProxyServerSettings ) { - self.context = context - super.init( context: context, component: ProxyServerPreviewSheetComponent( - context: context, + sharedContext: context.sharedContext, + network: context.account.network, server: server ), navigationBarAppearance: .none, @@ -526,6 +532,28 @@ public class ProxyServerPreviewScreen: ViewControllerComponentContainer { self.navigationPresentation = .flatModal } + public init( + sharedContext: SharedAccountContext, + network: Network, + updatedPresentationData: (initial: PresentationData, signal: Signal), + server: ProxyServerSettings + ) { + super.init( + component: ProxyServerPreviewSheetComponent( + sharedContext: sharedContext, + network: network, + server: server + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + presentationMode: .default, + theme: .default, + updatedPresentationData: updatedPresentationData + ) + + self.navigationPresentation = .flatModal + } + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/submodules/TelegramUI/Components/RankChatPreviewItem/Sources/RankChatPreviewItem.swift b/submodules/TelegramUI/Components/RankChatPreviewItem/Sources/RankChatPreviewItem.swift index 4f917066be..19c0f7c10a 100644 --- a/submodules/TelegramUI/Components/RankChatPreviewItem/Sources/RankChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/RankChatPreviewItem/Sources/RankChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -20,9 +19,14 @@ public final class RankChatPreviewItem: ListViewItem, ItemListItem, ListItemComp if lhs.text != rhs.text { return false } - if areMediaArraysEqual(lhs.media, rhs.media) { + if lhs.media.count != rhs.media.count { return false } + for i in 0 ..< lhs.media.count { + if !lhs.media[i].isEqual(to: rhs.media[i]) { + return false + } + } if lhs.rank != rhs.rank { return false } @@ -32,11 +36,11 @@ public final class RankChatPreviewItem: ListViewItem, ItemListItem, ListItemComp let peer: EnginePeer let text: String let entities: TextEntitiesMessageAttribute? - let media: [Media] + let media: [EngineRawMedia] let rank: String let rankRole: ChatRankInfoScreenRole - public init(peer: EnginePeer, text: String, entities: TextEntitiesMessageAttribute?, media: [Media], rank: String, rankRole: ChatRankInfoScreenRole) { + public init(peer: EnginePeer, text: String, entities: TextEntitiesMessageAttribute?, media: [EngineRawMedia], rank: String, rankRole: ChatRankInfoScreenRole) { self.peer = peer self.text = text self.entities = entities @@ -234,29 +238,29 @@ final class RankChatPreviewItemNode: ListViewItemNode { for messageItem in item.messageItems.reversed() { var userPeer = messageItem.peer._asPeer() - let updatedId = PeerId.Id._internalFromInt64Value(userPeer.id.id._internalGetInt64Value() - 7) - let authorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: updatedId) - let groupPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)) - let groupPeer = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil) + let updatedId = EnginePeer.Id.Id._internalFromInt64Value(userPeer.id.id._internalGetInt64Value() - 7) + let authorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: updatedId) + let groupPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(0)) + let groupPeer = TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil) if let user = userPeer as? TelegramUser { userPeer = TelegramUser(id: authorPeerId, accessHash: user.accessHash, firstName: user.firstName, lastName: user.lastName, username: "", phone: user.phone, photo: user.photo, botInfo: user.botInfo, restrictionInfo: user.restrictionInfo, flags: user.flags, emojiStatus: user.emojiStatus, usernames: user.usernames, storiesHidden: user.storiesHidden, nameColor: user.nameColor, backgroundEmojiId: user.backgroundEmojiId, profileColor: user.profileColor, profileBackgroundEmojiId: user.profileBackgroundEmojiId, subscriberCount: user.subscriberCount, verificationIconFileId: user.verificationIconFileId) } - var peers = SimpleDictionary() - let messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + let messages = EngineSimpleDictionary() peers[authorPeerId] = userPeer peers[groupPeerId] = groupPeer let media = messageItem.media - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let entities = messageItem.entities { attributes.append(entities) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: groupPeerId, namespace: Namespaces.Message.Local, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 20460, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: groupPeerId, namespace: Namespaces.Message.Local, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 20460, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: item.containerWidth != nil, isPreview: true, isStandalone: false, rank: messageItem.rank, rankRole: messageItem.rankRole)) } diff --git a/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/BUILD b/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/BUILD index 24183c1036..54d18e0f46 100644 --- a/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/BUILD +++ b/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", "//submodules/LegacyComponents", diff --git a/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/Sources/FetchAudioMediaResource.swift b/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/Sources/FetchAudioMediaResource.swift index 97acda1ddb..c451ecab49 100644 --- a/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/Sources/FetchAudioMediaResource.swift +++ b/submodules/TelegramUI/Components/Resources/FetchAudioMediaResource/Sources/FetchAudioMediaResource.swift @@ -1,14 +1,13 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import TelegramCore import FFMpegBinding import LocalMediaResources -public func fetchLocalFileAudioMediaResource(postbox: Postbox, resource: LocalFileAudioMediaResource) -> Signal { +public func fetchLocalFileAudioMediaResource(resource: LocalFileAudioMediaResource) -> Signal { let tempFile = EngineTempBox.shared.tempFile(fileName: "audio.ogg") FFMpegOpusTrimmer.trim(resource.path, to: tempFile.path, start: resource.trimRange?.lowerBound ?? 0.0, end: resource.trimRange?.upperBound ?? 1.0) - + return .single(.moveTempFile(file: tempFile)) } diff --git a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift index 86a9eb018f..10b8e29b91 100644 --- a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift @@ -892,6 +892,7 @@ private extension MediaEditorValues { videoVolume: 1.0, additionalVideoPath: nil, additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, @@ -1041,6 +1042,7 @@ private extension MediaEditorValues { videoVolume: 1.0, additionalVideoPath: nil, additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, diff --git a/submodules/TelegramUI/Components/SaveProgressScreen/BUILD b/submodules/TelegramUI/Components/SaveProgressScreen/BUILD index e9de562525..9d05ce1532 100644 --- a/submodules/TelegramUI/Components/SaveProgressScreen/BUILD +++ b/submodules/TelegramUI/Components/SaveProgressScreen/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/Components/BundleIconComponent", "//submodules/Components/LottieAnimationComponent", "//submodules/AccountContext", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD b/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD index 8606deae33..04c2c1a374 100644 --- a/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD +++ b/submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/Display", "//submodules/TelegramCore", - "//submodules/Postbox", "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/SegmentControlComponent/Sources/SegmentControlComponent.swift b/submodules/TelegramUI/Components/SegmentControlComponent/Sources/SegmentControlComponent.swift index 012de5aafb..f6bd7fe4e9 100644 --- a/submodules/TelegramUI/Components/SegmentControlComponent/Sources/SegmentControlComponent.swift +++ b/submodules/TelegramUI/Components/SegmentControlComponent/Sources/SegmentControlComponent.swift @@ -8,6 +8,57 @@ import TelegramPresentationData import SegmentedControlNode public final class SegmentControlComponent: Component { + public final class Theme: Equatable { + public let backgroundColor: UIColor + public let legacyBackgroundColor: UIColor + public let foregroundColor: UIColor + public let textColor: UIColor + public let dividerColor: UIColor + + public init( + backgroundColor: UIColor, + legacyBackgroundColor: UIColor, + foregroundColor: UIColor, + textColor: UIColor, + dividerColor: UIColor + ) { + self.backgroundColor = backgroundColor + self.legacyBackgroundColor = legacyBackgroundColor + self.foregroundColor = foregroundColor + self.textColor = textColor + self.dividerColor = dividerColor + } + + public convenience init(theme: PresentationTheme) { + self.init( + backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor, + legacyBackgroundColor: theme.overallDarkAppearance ? theme.list.itemBlocksBackgroundColor : theme.rootController.navigationBar.segmentedBackgroundColor, + foregroundColor: theme.rootController.navigationBar.segmentedForegroundColor, + textColor: theme.rootController.navigationBar.segmentedTextColor, + dividerColor: theme.rootController.navigationBar.segmentedDividerColor + ) + } + + public static func ==(lhs: Theme, rhs: Theme) -> Bool { + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.legacyBackgroundColor != rhs.legacyBackgroundColor { + return false + } + if lhs.foregroundColor != rhs.foregroundColor { + return false + } + if lhs.textColor != rhs.textColor { + return false + } + if lhs.dividerColor != rhs.dividerColor { + return false + } + return true + } + } + public struct Item: Equatable { var id: AnyHashable var title: String @@ -18,13 +69,13 @@ public final class SegmentControlComponent: Component { } } - let theme: PresentationTheme + let theme: Theme let items: [Item] let selectedId: AnyHashable? let action: (AnyHashable) -> Void - + public init( - theme: PresentationTheme, + theme: Theme, items: [Item], selectedId: AnyHashable?, action: @escaping (AnyHashable) -> Void @@ -34,9 +85,23 @@ public final class SegmentControlComponent: Component { self.selectedId = selectedId self.action = action } + + public convenience init( + theme: PresentationTheme, + items: [Item], + selectedId: AnyHashable?, + action: @escaping (AnyHashable) -> Void + ) { + self.init( + theme: Theme(theme: theme), + items: items, + selectedId: selectedId, + action: action + ) + } public static func ==(lhs: SegmentControlComponent, rhs: SegmentControlComponent) -> Bool { - if lhs.theme !== rhs.theme { + if lhs.theme != rhs.theme { return false } if lhs.items != rhs.items { @@ -102,7 +167,7 @@ public final class SegmentControlComponent: Component { } func update(component: SegmentControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let themeUpdated = self.component?.theme !== component.theme + let themeUpdated = self.component?.theme != component.theme self.component = component @@ -127,13 +192,12 @@ public final class SegmentControlComponent: Component { } if themeUpdated { - let backgroundColor = component.theme.rootController.navigationBar.segmentedBackgroundColor segmentedView.setTitleTextAttributes([ .font: Font.semibold(14.0), - .foregroundColor: component.theme.rootController.navigationBar.segmentedTextColor + .foregroundColor: component.theme.textColor ], for: .normal) - segmentedView.foregroundColor = component.theme.rootController.navigationBar.segmentedForegroundColor - segmentedView.backgroundColor = backgroundColor + segmentedView.foregroundColor = component.theme.foregroundColor + segmentedView.backgroundColor = component.theme.backgroundColor } controlSize = segmentedView.sizeThatFits(availableSize) @@ -146,16 +210,14 @@ public final class SegmentControlComponent: Component { segmentedNode = current if themeUpdated { - let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor - let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor) + let controlTheme = SegmentedControlTheme(backgroundColor: component.theme.legacyBackgroundColor, foregroundColor: component.theme.foregroundColor, shadowColor: .clear, textColor: component.theme.textColor, dividerColor: component.theme.dividerColor) segmentedNode.updateTheme(controlTheme) } } else { let mappedItems: [SegmentedControlItem] = component.items.map { item -> SegmentedControlItem in return SegmentedControlItem(title: item.title) } - let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor - let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor) + let controlTheme = SegmentedControlTheme(backgroundColor: component.theme.legacyBackgroundColor, foregroundColor: component.theme.foregroundColor, shadowColor: .clear, textColor: component.theme.textColor, dividerColor: component.theme.dividerColor) segmentedNode = SegmentedControlNode(theme: controlTheme, items: mappedItems, selectedIndex: component.items.firstIndex(where: { $0.id == component.selectedId }) ?? 0, cornerRadius: 18.0) self.legacySegmentedNode = segmentedNode self.addSubnode(segmentedNode) diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD index e95817f52a..6fec00f75a 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", @@ -24,7 +23,10 @@ swift_library( "//submodules/AppBundle", "//submodules/TelegramStringFormatting", "//submodules/PresentationDataUtils", - "//submodules/Components/SolidRoundedButtonComponent", + "//submodules/Components/ResizableSheetComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/AvatarNode", "//submodules/CheckNode", "//submodules/Markdown", diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift index 04691de605..a4e86a5cbe 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift @@ -1,16 +1,13 @@ import Foundation import UIKit import Display -import AsyncDisplayKit import ComponentFlow import SwiftSignalKit import ViewControllerComponent -import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import MultilineTextComponent -import SolidRoundedButtonComponent +import ResizableSheetComponent import PresentationDataUtils import Markdown import UndoUI @@ -18,31 +15,64 @@ import AnimatedAvatarSetNode import AvatarNode import TelegramStringFormatting import ChatMessagePaymentAlertController +import ResizableSheetComponent +import ButtonComponent +import BundleIconComponent +import GlassBarButtonComponent +import MultilineTextComponent -private final class SendInviteLinkScreenComponent: Component { +private func sendInviteLinkHasInviteSection(subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer]) -> Bool { + let premiumRestrictedUsers = peers.filter { peer in + return peer.canInviteWithPremium + } + + switch subject { + case let .chat(_, link): + if premiumRestrictedUsers.count == peers.count && link == nil { + return false + } else if link != nil && !premiumRestrictedUsers.isEmpty && peers.allSatisfy({ $0.premiumRequiredToContact }) { + return false + } else { + return true + } + case .groupCall: + return true + } +} + +private final class SendInviteLinkContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext let subject: SendInviteLinkScreenSubject let peers: [TelegramForbiddenInvitePeer] let peerPresences: [EnginePeer.Id: EnginePeer.Presence] - let sendPaidMessageStars: [EnginePeer.Id: StarsAmount] + let selectedItems: Set + let theme: PresentationTheme + let toggleSelection: (EnginePeer.Id) -> Void + let openPremium: () -> Void init( context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], peerPresences: [EnginePeer.Id: EnginePeer.Presence], - sendPaidMessageStars: [EnginePeer.Id: StarsAmount] + selectedItems: Set, + theme: PresentationTheme, + toggleSelection: @escaping (EnginePeer.Id) -> Void, + openPremium: @escaping () -> Void ) { self.context = context self.subject = subject self.peers = peers self.peerPresences = peerPresences - self.sendPaidMessageStars = sendPaidMessageStars + self.selectedItems = selectedItems + self.theme = theme + self.toggleSelection = toggleSelection + self.openPremium = openPremium } - static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool { + static func ==(lhs: SendInviteLinkContentComponent, rhs: SendInviteLinkContentComponent) -> Bool { if lhs.context !== rhs.context { return false } @@ -52,40 +82,16 @@ private final class SendInviteLinkScreenComponent: Component { if lhs.peerPresences != rhs.peerPresences { return false } - if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + if lhs.selectedItems != rhs.selectedItems { + return false + } + if lhs.theme !== rhs.theme { return false } return true } - private struct ItemLayout: Equatable { - var containerSize: CGSize - var containerInset: CGFloat - var bottomInset: CGFloat - var topInset: CGFloat - - init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) { - self.containerSize = containerSize - self.containerInset = containerInset - self.bottomInset = bottomInset - self.topInset = topInset - } - } - - private final class ScrollView: UIScrollView { - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - } - - final class View: UIView, UIScrollViewDelegate { - private let dimView: UIView - private let backgroundLayer: SimpleLayer - private let navigationBarContainer: SparseContainerView - private let scrollView: ScrollView - private let scrollContentClippingView: SparseContainerView - private let scrollContentView: UIView - + final class View: UIView { private var avatarsNode: AnimatedAvatarSetNode? private let avatarsContext = AnimatedAvatarSetContext() @@ -96,275 +102,46 @@ private final class SendInviteLinkScreenComponent: Component { private var premiumSeparatorRight: SimpleLayer? private var premiumSeparatorText: ComponentView? - private let leftButton = ComponentView() - private var title: ComponentView? private var descriptionText: ComponentView? - private var actionButton: ComponentView? private let itemContainerView: UIView private var items: [AnyHashable: ComponentView] = [:] - private var selectedItems = Set() - - private let bottomOverscrollLimit: CGFloat - - private var ignoreScrolling: Bool = false - - private var component: SendInviteLinkScreenComponent? + private var component: SendInviteLinkContentComponent? private weak var state: EmptyComponentState? - private var environment: ViewControllerComponentContainer.Environment? - private var itemLayout: ItemLayout? - - private var topOffsetDistance: CGFloat? - - private var createCallDisposable: Disposable? - private var isInProgress: Bool = false override init(frame: CGRect) { - self.bottomOverscrollLimit = 200.0 - - self.dimView = UIView() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - self.backgroundLayer.cornerRadius = 10.0 - - self.navigationBarContainer = SparseContainerView() - - self.scrollView = ScrollView() - - self.scrollContentClippingView = SparseContainerView() - self.scrollContentClippingView.clipsToBounds = true - - self.scrollContentView = UIView() - self.itemContainerView = UIView() self.itemContainerView.clipsToBounds = true - self.itemContainerView.layer.cornerRadius = 10.0 + self.itemContainerView.layer.cornerRadius = 26.0 super.init(frame: frame) - self.addSubview(self.dimView) - self.layer.addSublayer(self.backgroundLayer) - - self.addSubview(self.navigationBarContainer) - - self.scrollView.delaysContentTouches = true - self.scrollView.canCancelContentTouches = true - self.scrollView.clipsToBounds = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.scrollView.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false - } - self.scrollView.showsVerticalScrollIndicator = false - self.scrollView.showsHorizontalScrollIndicator = false - self.scrollView.alwaysBounceHorizontal = false - self.scrollView.alwaysBounceVertical = true - self.scrollView.scrollsToTop = false - self.scrollView.delegate = self - self.scrollView.clipsToBounds = true - - self.addSubview(self.scrollContentClippingView) - self.scrollContentClippingView.addSubview(self.scrollView) - - self.scrollView.addSubview(self.scrollContentView) - - self.scrollContentView.addSubview(self.itemContainerView) - - self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + self.addSubview(self.itemContainerView) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - deinit { - self.createCallDisposable?.dispose() - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateScrolling(transition: .immediate) - } - } - - func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - guard let itemLayout = self.itemLayout, let topOffsetDistance = self.topOffsetDistance else { - return - } - - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - topOffset = max(0.0, topOffset) - - if topOffset < topOffsetDistance { - targetContentOffset.pointee.y = scrollView.contentOffset.y - scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true) - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if !self.bounds.contains(point) { - return nil - } - if !self.backgroundLayer.frame.contains(point) { - return self.dimView - } - - if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) { - return result - } - - let result = super.hitTest(point, with: event) - return result - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - guard let environment = self.environment, let controller = environment.controller() else { - return - } - controller.dismiss() - } - } - - private func updateScrolling(transition: ComponentTransition) { - guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else { - return - } - var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset - topOffset = max(0.0, topOffset) - transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0)) - - transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset)) - - let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25)) - self.topOffsetDistance = topOffsetDistance - var topOffsetFraction = topOffset / topOffsetDistance - topOffsetFraction = max(0.0, min(1.0, topOffsetFraction)) - - let transitionFactor: CGFloat = 1.0 - topOffsetFraction - controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition) - } - - func animateIn() { - self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let actionButtonView = self.actionButton?.view { - actionButtonView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - } - - func animateOut(completion: @escaping () -> Void) { - let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - if let actionButtonView = self.actionButton?.view { - actionButtonView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - } - } - - private func presentPaidMessageAlertIfNeeded(peers: [EngineRenderedPeer], requiresStars: [EnginePeer.Id: StarsAmount], completion: @escaping () -> Void) { - guard let component = self.component else { - completion() - return - } - var totalAmount: StarsAmount = .zero - for peer in peers { - if let amount = requiresStars[peer.peerId] { - totalAmount = totalAmount + amount - } - } - if totalAmount.value > 0 { - let controller = chatMessagePaymentAlertController( - context: component.context, - presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, - updatedPresentationData: nil, - peers: peers, - count: 1, - amount: totalAmount, - totalAmount: totalAmount, - hasCheck: false, - navigationController: self.environment?.controller()?.navigationController as? NavigationController, - completion: { _ in - completion() - } - ) - self.environment?.controller()?.present(controller, in: .window(.root)) - } else { - completion() - } - } - - func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - let environment = environment[ViewControllerComponentContainer.Environment.self].value - let themeUpdated = self.environment?.theme !== environment.theme - - let resetScrolling = self.scrollView.bounds.width != availableSize.width - - let sideInset: CGFloat = 16.0 - - if self.component == nil { - for peer in component.peers { - switch component.subject { - case let .chat(_, link): - if link != nil && !peer.premiumRequiredToContact { - self.selectedItems.insert(peer.peer.id) - } - case .groupCall: - self.selectedItems.insert(peer.peer.id) - } - } - } + func update(component: SendInviteLinkContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[EnvironmentType.self].value + let theme = component.theme self.component = component self.state = state - self.environment = environment + + let sideInset: CGFloat = 16.0 + self.itemContainerView.backgroundColor = theme.list.itemBlocksBackgroundColor let premiumRestrictedUsers = component.peers.filter { peer in return peer.canInviteWithPremium } - var hasInviteLink = true - switch component.subject { - case let .chat(_, link): - if premiumRestrictedUsers.count == component.peers.count && link == nil { - hasInviteLink = false - } else if link != nil && !premiumRestrictedUsers.isEmpty && component.peers.allSatisfy({ $0.premiumRequiredToContact }) { - hasInviteLink = false - } - case .groupCall: - hasInviteLink = true - } - - if themeUpdated { - self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - self.backgroundLayer.backgroundColor = environment.theme.list.blocksBackgroundColor.cgColor - self.itemContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor - - var locations: [NSNumber] = [] - var colors: [CGColor] = [] - let numStops = 6 - for i in 0 ..< numStops { - let step = CGFloat(i) / CGFloat(numStops - 1) - locations.append(step as NSNumber) - colors.append(environment.theme.list.blocksBackgroundColor.withAlphaComponent(1.0 - step * step).cgColor) - } - } - - transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) + let hasInviteSection = sendInviteLinkHasInviteSection(subject: component.subject, peers: component.peers) var contentHeight: CGFloat = 0.0 - contentHeight += 102.0 + contentHeight += 120.0 let avatarsNode: AnimatedAvatarSetNode if let current = self.avatarsNode { @@ -372,7 +149,7 @@ private final class SendInviteLinkScreenComponent: Component { } else { avatarsNode = AnimatedAvatarSetNode() self.avatarsNode = avatarsNode - self.scrollContentView.addSubview(avatarsNode.view) + self.addSubview(avatarsNode.view) } let avatarPeers: [EnginePeer] @@ -385,37 +162,15 @@ private final class SendInviteLinkScreenComponent: Component { let avatarsSize = avatarsNode.update( context: component.context, content: avatarsContent, - itemSize: CGSize(width: 60.0, height: 60.0), + itemSize: CGSize(width: 64.0, height: 64.0), customSpacing: 30.0, font: avatarPlaceholderFont(size: 28.0), animated: false, synchronousLoad: true ) - let avatarsFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - avatarsSize.width) * 0.5), y: 26.0), size: avatarsSize) + let avatarsFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - avatarsSize.width) * 0.5), y: 48.0), size: avatarsSize) transition.setFrame(view: avatarsNode.view, frame: avatarsFrame) - let leftButtonSize = self.leftButton.update( - transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), - action: { [weak self] in - guard let self, let controller = self.environment?.controller() else { - return - } - controller.dismiss() - } - ).minSize(CGSize(width: 44.0, height: 56.0))), - environment: {}, - containerSize: CGSize(width: 120.0, height: 100.0) - ) - let leftButtonFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: leftButtonSize) - if let leftButtonView = self.leftButton.view { - if leftButtonView.superview == nil { - self.navigationBarContainer.addSubview(leftButtonView) - } - transition.setFrame(view: leftButtonView, frame: leftButtonFrame) - } - if !premiumRestrictedUsers.isEmpty { var premiumItemsTransition = transition @@ -447,15 +202,15 @@ private final class SendInviteLinkScreenComponent: Component { let premiumTitleSize = premiumTitle.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.SendInviteLink_TitleUpgradeToPremium, font: Font.semibold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: environment.strings.SendInviteLink_TitleUpgradeToPremium, font: Font.semibold(24.0), textColor: theme.list.itemPrimaryTextColor)) )), environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) ) let premiumTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - premiumTitleSize.width) * 0.5), y: contentHeight), size: premiumTitleSize) if let premiumTitleView = premiumTitle.view { if premiumTitleView.superview == nil { - self.scrollContentView.addSubview(premiumTitleView) + self.addSubview(premiumTitleView) } transition.setFrame(view: premiumTitleView, frame: premiumTitleFrame) } @@ -474,7 +229,6 @@ private final class SendInviteLinkScreenComponent: Component { } } else { let extraCount = premiumRestrictedUsers.count - 3 - var peersTextArray: [String] = [] for i in 0 ..< min(3, premiumRestrictedUsers.count) { peersTextArray.append("**\(premiumRestrictedUsers[i].peer.compactDisplayTitle)**") @@ -516,7 +270,6 @@ private final class SendInviteLinkScreenComponent: Component { text = environment.strings.SendInviteLink_TextCallsRestrictedOneUser(premiumRestrictedUsers[0].peer.compactDisplayTitle).string } else { let extraCount = premiumRestrictedUsers.count - 3 - var peersTextArray: [String] = [] for i in 0 ..< min(3, premiumRestrictedUsers.count) { peersTextArray.append("**\(premiumRestrictedUsers[i].peer.compactDisplayTitle)**") @@ -547,8 +300,8 @@ private final class SendInviteLinkScreenComponent: Component { } } - let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) - let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor) + let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: theme.list.itemPrimaryTextColor) let premiumTextSize = premiumText.update( transition: .immediate, @@ -568,7 +321,7 @@ private final class SendInviteLinkScreenComponent: Component { let premiumTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - premiumTextSize.width) * 0.5), y: contentHeight), size: premiumTextSize) if let premiumTextView = premiumText.view { if premiumTextView.superview == nil { - self.scrollContentView.addSubview(premiumTextView) + self.addSubview(premiumTextView) } transition.setFrame(view: premiumTextView, frame: premiumTextFrame) } @@ -576,57 +329,50 @@ private final class SendInviteLinkScreenComponent: Component { contentHeight += premiumTextSize.height contentHeight += 22.0 - let premiumButtonTitle = environment.strings.SendInviteLink_SubscribeToPremiumButton + let premiumButtonGradientColors = [ + UIColor(rgb: 0x0077ff), + UIColor(rgb: 0x6b93ff), + UIColor(rgb: 0x8878ff), + UIColor(rgb: 0xe46ace) + ] let premiumButtonSize = premiumButton.update( - transition: transition, - component: AnyComponent(SolidRoundedButtonComponent( - title: premiumButtonTitle, - badge: nil, - theme: SolidRoundedButtonComponent.Theme( - backgroundColor: .black, - backgroundColors: [ - UIColor(rgb: 0x0077ff), - UIColor(rgb: 0x6b93ff), - UIColor(rgb: 0x8878ff), - UIColor(rgb: 0xe46ace) - ], - foregroundColor: .white + transition: premiumItemsTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: premiumButtonGradientColors[0], + foreground: .white, + pressedColor: premiumButtonGradientColors[0], + isShimmering: false, + gradient: ButtonComponent.Background.Gradient(colors: premiumButtonGradientColors) ), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 11.0, - gloss: false, - animationName: nil, - iconPosition: .right, - iconSpacing: 4.0, - action: { [weak self] in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - let navigationController = controller.navigationController as? NavigationController - - controller.dismiss() - - let premiumController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .settings, forceDark: false, dismissed: nil) - navigationController?.pushViewController(premiumController) + content: AnyComponentWithIdentity( + id: AnyHashable(environment.strings.SendInviteLink_SubscribeToPremiumButton), + component: AnyComponent(ButtonTextContentComponent( + text: environment.strings.SendInviteLink_SubscribeToPremiumButton, + badge: 0, + textColor: .white, + badgeBackground: .white, + badgeForeground: premiumButtonGradientColors[0] + )) + ), + action: { + component.openPremium() } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0) ) - let premiumButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumButtonSize) if let premiumButtonView = premiumButton.view { if premiumButtonView.superview == nil { - self.scrollContentView.addSubview(premiumButtonView) + self.addSubview(premiumButtonView) } - transition.setFrame(view: premiumButtonView, frame: premiumButtonFrame) + premiumItemsTransition.setFrame(view: premiumButtonView, frame: premiumButtonFrame) } contentHeight += premiumButtonSize.height - if hasInviteLink { + if hasInviteSection { let premiumSeparatorText: ComponentView if let current = self.premiumSeparatorText { premiumSeparatorText = current @@ -641,7 +387,7 @@ private final class SendInviteLinkScreenComponent: Component { } else { premiumSeparatorLeft = SimpleLayer() self.premiumSeparatorLeft = premiumSeparatorLeft - self.scrollContentView.layer.addSublayer(premiumSeparatorLeft) + self.layer.addSublayer(premiumSeparatorLeft) } let premiumSeparatorRight: SimpleLayer @@ -650,33 +396,32 @@ private final class SendInviteLinkScreenComponent: Component { } else { premiumSeparatorRight = SimpleLayer() self.premiumSeparatorRight = premiumSeparatorRight - self.scrollContentView.layer.addSublayer(premiumSeparatorRight) + self.layer.addSublayer(premiumSeparatorRight) } - premiumSeparatorLeft.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor - premiumSeparatorRight.backgroundColor = environment.theme.list.itemPlainSeparatorColor.cgColor + premiumSeparatorLeft.backgroundColor = theme.list.itemPlainSeparatorColor.cgColor + premiumSeparatorRight.backgroundColor = theme.list.itemPlainSeparatorColor.cgColor contentHeight += 19.0 let premiumSeparatorTextSize = premiumSeparatorText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.SendInviteLink_PremiumOrSendSectionSeparator, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)) + text: .plain(NSAttributedString(string: environment.strings.SendInviteLink_PremiumOrSendSectionSeparator, font: Font.regular(15.0), textColor: theme.list.itemSecondaryTextColor)) )), environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) ) let premiumSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - premiumSeparatorTextSize.width) * 0.5), y: contentHeight), size: premiumSeparatorTextSize) if let premiumSeparatorTextView = premiumSeparatorText.view { if premiumSeparatorTextView.superview == nil { - self.scrollContentView.addSubview(premiumSeparatorTextView) + self.addSubview(premiumSeparatorTextView) } transition.setFrame(view: premiumSeparatorTextView, frame: premiumSeparatorTextFrame) } let separatorWidth: CGFloat = 72.0 let separatorSpacing: CGFloat = 10.0 - transition.setFrame(layer: premiumSeparatorLeft, frame: CGRect(origin: CGPoint(x: premiumSeparatorTextFrame.minX - separatorSpacing - separatorWidth, y: premiumSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))) transition.setFrame(layer: premiumSeparatorRight, frame: CGRect(origin: CGPoint(x: premiumSeparatorTextFrame.maxX + separatorSpacing, y: premiumSeparatorTextFrame.midY + 1.0), size: CGSize(width: separatorWidth, height: UIScreenPixel))) @@ -712,12 +457,7 @@ private final class SendInviteLinkScreenComponent: Component { } } - let containerInset: CGFloat = environment.statusBarHeight + 10.0 - - var initialContentHeight = contentHeight - let clippingY: CGFloat - - if hasInviteLink { + if hasInviteSection { let title: ComponentView if let current = self.title { title = current @@ -734,14 +474,6 @@ private final class SendInviteLinkScreenComponent: Component { self.descriptionText = descriptionText } - let actionButton: ComponentView - if let current = self.actionButton { - actionButton = current - } else { - actionButton = ComponentView() - self.actionButton = actionButton - } - let titleText: String switch component.subject { case let .chat(_, link): @@ -753,15 +485,15 @@ private final class SendInviteLinkScreenComponent: Component { let titleSize = title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: titleText, font: Font.semibold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)) + text: .plain(NSAttributedString(string: titleText, font: Font.semibold(24.0), textColor: theme.list.itemPrimaryTextColor)) )), environment: {}, - containerSize: CGSize(width: availableSize.width - leftButtonFrame.maxX * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) ) let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize) if let titleView = title.view { if titleView.superview == nil { - self.scrollContentView.addSubview(titleView) + self.addSubview(titleView) } transition.setFrame(view: titleView, frame: titleFrame) } @@ -775,27 +507,21 @@ private final class SendInviteLinkScreenComponent: Component { if !premiumRestrictedUsers.isEmpty { if link != nil { text = environment.strings.SendInviteLink_TextSendInviteLink + } else if component.peers.count == 1 { + text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string } else { - if component.peers.count == 1 { - text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string - } else { - text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) - } + text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) } + } else if link != nil { + if component.peers.count == 1 { + text = environment.strings.SendInviteLink_TextAvailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string + } else { + text = environment.strings.SendInviteLink_TextAvailableMultipleUsers(Int32(component.peers.count)) + } + } else if component.peers.count == 1 { + text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string } else { - if link != nil { - if component.peers.count == 1 { - text = environment.strings.SendInviteLink_TextAvailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string - } else { - text = environment.strings.SendInviteLink_TextAvailableMultipleUsers(Int32(component.peers.count)) - } - } else { - if component.peers.count == 1 { - text = environment.strings.SendInviteLink_TextUnavailableSingleUser(component.peers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)).string - } else { - text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) - } - } + text = environment.strings.SendInviteLink_TextUnavailableMultipleUsers(Int32(component.peers.count)) } case let .groupCall(groupCall): switch groupCall { @@ -811,8 +537,8 @@ private final class SendInviteLinkScreenComponent: Component { } } - let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) - let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor) + let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: theme.list.itemPrimaryTextColor) let descriptionTextSize = descriptionText.update( transition: .immediate, @@ -824,7 +550,8 @@ private final class SendInviteLinkScreenComponent: Component { linkAttribute: { _ in nil } )), horizontalAlignment: .center, - maximumNumberOfLines: 0 + maximumNumberOfLines: 0, + lineSpacing: 0.2 )), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 16.0 * 2.0, height: 1000.0) @@ -832,96 +559,80 @@ private final class SendInviteLinkScreenComponent: Component { let descriptionTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionTextSize.width) * 0.5), y: contentHeight), size: descriptionTextSize) if let descriptionTextView = descriptionText.view { if descriptionTextView.superview == nil { - self.scrollContentView.addSubview(descriptionTextView) + self.addSubview(descriptionTextView) } transition.setFrame(view: descriptionTextView, frame: descriptionTextFrame) } contentHeight += descriptionTextFrame.height contentHeight += 22.0 - initialContentHeight = contentHeight - - var singleItemHeight: CGFloat = 0.0 var itemsHeight: CGFloat = 0.0 var validIds: [AnyHashable] = [] if case .chat = component.subject { for i in 0 ..< component.peers.count { let peer = component.peers[i] + let id = AnyHashable(peer.peer.id) + validIds.append(id) - for _ in 0 ..< 1 { - //let id: AnyHashable = AnyHashable("\(peer.id)_\(j)") - let id = AnyHashable(peer.peer.id) - validIds.append(id) - - let item: ComponentView - var itemTransition = transition - if let current = self.items[id] { - item = current - } else { - itemTransition = .immediate - item = ComponentView() - self.items[id] = item - } - - let itemSubtitle: PeerListItemComponent.Subtitle - let canBeSelected : Bool - switch component.subject { - case let .chat(_, link): - canBeSelected = link != nil && !peer.premiumRequiredToContact - case .groupCall: - canBeSelected = true - } - if peer.premiumRequiredToContact { - itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) - } else { - itemSubtitle = .presence(component.peerPresences[peer.peer.id]) - } - - let itemSize = item.update( - transition: itemTransition, - component: AnyComponent(PeerListItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - sideInset: 0.0, - title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), - subtitle: itemSubtitle, - peer: peer.peer, - selectionState: !canBeSelected ? .none : .editing(isSelected: self.selectedItems.contains(peer.peer.id)), - hasNext: i != component.peers.count - 1, - action: { [weak self] peer in - guard let self else { - return - } - if !canBeSelected { - return - } - if self.selectedItems.contains(peer.id) { - self.selectedItems.remove(peer.id) - } else { - self.selectedItems.insert(peer.id) - } - self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) - ) - let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) - - if let itemView = item.view { - if itemView.superview == nil { - self.itemContainerView.addSubview(itemView) - } - itemTransition.setFrame(view: itemView, frame: itemFrame) - } - - itemsHeight += itemSize.height - singleItemHeight = itemSize.height + let item: ComponentView + var itemTransition = transition + if let current = self.items[id] { + item = current + } else { + itemTransition = .immediate + item = ComponentView() + self.items[id] = item } + + let canBeSelected: Bool + switch component.subject { + case let .chat(_, link): + canBeSelected = link != nil && !peer.premiumRequiredToContact + case .groupCall: + canBeSelected = true + } + + let itemSubtitle: PeerListItemComponent.Subtitle + if peer.premiumRequiredToContact { + itemSubtitle = .text(text: environment.strings.SendInviteLink_StatusAvailableToPremiumOnly, icon: .lock) + } else { + itemSubtitle = .presence(component.peerPresences[peer.peer.id]) + } + + let itemSize = item.update( + transition: itemTransition, + component: AnyComponent(PeerListItemComponent( + context: component.context, + theme: theme, + strings: environment.strings, + sideInset: 0.0, + title: peer.peer.displayTitle(strings: environment.strings, displayOrder: .firstLast), + subtitle: itemSubtitle, + peer: peer.peer, + selectionState: !canBeSelected ? .none : .editing(isSelected: component.selectedItems.contains(peer.peer.id)), + hasNext: i != component.peers.count - 1, + action: { peer in + if canBeSelected { + component.toggleSelection(peer.id) + } + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: itemsHeight), size: itemSize) + if let itemView = item.view { + if itemView.superview == nil { + self.itemContainerView.addSubview(itemView) + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + + itemsHeight += itemSize.height } } + var removeIds: [AnyHashable] = [] for (id, item) in self.items { if !validIds.contains(id) { @@ -932,182 +643,17 @@ private final class SendInviteLinkScreenComponent: Component { for id in removeIds { self.items.removeValue(forKey: id) } - transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight))) - initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5)) + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight))) if itemsHeight != 0.0 { contentHeight += itemsHeight contentHeight += 24.0 - initialContentHeight += 24.0 } else { contentHeight += 4.0 } - let actionButtonTitle: String - let actionButtonBadge: String? - switch component.subject { - case let.chat(_, link): - if link != nil { - actionButtonTitle = self.selectedItems.isEmpty ? environment.strings.SendInviteLink_ActionSkip : environment.strings.SendInviteLink_ActionInvite - } else { - actionButtonTitle = environment.strings.SendInviteLink_ActionClose - } - actionButtonBadge = (self.selectedItems.isEmpty || link == nil) ? nil : "\(self.selectedItems.count)" - case .groupCall: - actionButtonTitle = environment.strings.SendInviteLink_ActionInvite - actionButtonBadge = nil - } - let actionButtonSize = actionButton.update( - transition: transition, - component: AnyComponent(SolidRoundedButtonComponent( - title: actionButtonTitle, - badge: actionButtonBadge, - theme: SolidRoundedButtonComponent.Theme(theme: environment.theme), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 11.0, - gloss: false, - animationName: nil, - iconPosition: .right, - iconSpacing: 4.0, - isLoading: self.isInProgress, - action: { [weak self] in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - let link: String? - switch component.subject { - case let .chat(_, linkValue): - link = linkValue - case let .groupCall(groupCall): - switch groupCall { - case .create: - self.isInProgress = true - self.state?.updated(transition: .immediate) - - self.createCallDisposable = (component.context.engine.calls.createConferenceCall() - |> deliverOnMainQueue).startStrict(next: { [weak self] call in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - if self.selectedItems.isEmpty { - controller.dismiss() - } else { - let link = call.link - let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } - - self.presentPaidMessageAlertIfNeeded( - peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) }, - requiresStars: component.sendPaidMessageStars, - completion: { [weak self] in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - for peerId in Array(self.selectedItems) { - var messageAttributes: [EngineMessage.Attribute] = [] - if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { - messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) - } - let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() - } - - let text: String - if selectedPeers.count == 1 { - text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else if selectedPeers.count == 2 { - text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else { - text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string - } - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) - - let navigationController = controller.navigationController as? NavigationController - - let context = component.context - controller.dismiss(completion: { [weak navigationController] in - if let navigationController, let peer = selectedPeers.first?.peer { - context.sharedContext.navigateToChatController(NavigateToChatControllerParams( - navigationController: navigationController, - context: context, - chatLocation: .peer(peer) - )) - } - }) - } - ) - } - }) - - return - case let .existing(linkValue): - link = linkValue - } - } - - if self.selectedItems.isEmpty { - controller.dismiss() - } else if let link { - let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } - - self.presentPaidMessageAlertIfNeeded( - peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) }, - requiresStars: component.sendPaidMessageStars, - completion: { [weak self] in - guard let self, let component = self.component, let controller = self.environment?.controller() else { - return - } - - for peerId in Array(self.selectedItems) { - var messageAttributes: [EngineMessage.Attribute] = [] - if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { - messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) - } - let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() - } - - let text: String - if selectedPeers.count == 1 { - text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else if selectedPeers.count == 2 { - text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string - } else { - text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string - } - - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) - - controller.dismiss() - } - ) - } else { - controller.dismiss() - } - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) - ) - let bottomPanelHeight = 15.0 + environment.safeInsets.bottom + actionButtonSize.height - let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) - if let actionButtonView = actionButton.view { - if actionButtonView.superview == nil { - self.addSubview(actionButtonView) - } - transition.setFrame(view: actionButtonView, frame: actionButtonFrame) - } - - contentHeight += bottomPanelHeight - initialContentHeight += bottomPanelHeight - - clippingY = actionButtonFrame.minY - 24.0 + contentHeight += 84.0 } else { if let title = self.title { self.title = nil @@ -1117,44 +663,502 @@ private final class SendInviteLinkScreenComponent: Component { self.descriptionText = nil descriptionText.view?.removeFromSuperview() } - if let actionButton = self.actionButton { - self.actionButton = nil - actionButton.view?.removeFromSuperview() + + for (_, item) in self.items { + item.view?.removeFromSuperview() } + self.items.removeAll() + transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: 0.0))) - initialContentHeight += environment.safeInsets.bottom - - clippingY = availableSize.height + contentHeight += 24.0 + environment.safeInsets.bottom } - let topInset: CGFloat = max(0.0, availableSize.height - containerInset - initialContentHeight) + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class SendInviteLinkActionButtonComponent: Component { + let theme: PresentationTheme + let title: String + let badge: Int? + let displaysProgress: Bool + let action: () -> Void + + init( + theme: PresentationTheme, + title: String, + badge: Int?, + displaysProgress: Bool, + action: @escaping () -> Void + ) { + self.theme = theme + self.title = title + self.badge = badge + self.displaysProgress = displaysProgress + self.action = action + } + + static func ==(lhs: SendInviteLinkActionButtonComponent, rhs: SendInviteLinkActionButtonComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.badge != rhs.badge { + return false + } + if lhs.displaysProgress != rhs.displaysProgress { + return false + } + return true + } + + final class View: UIView { + private let button = ComponentView() + private var component: SendInviteLinkActionButtonComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: SendInviteLinkActionButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component - let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) - - self.scrollContentClippingView.layer.cornerRadius = 10.0 - - self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset) - - transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight))) - - transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)) - transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize)) - - let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset)) - transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center) - transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size)) - - self.ignoreScrolling = true - transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) - let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight) - if contentSize != self.scrollView.contentSize { - self.scrollView.contentSize = contentSize + let content: AnyComponentWithIdentity + if let badge = component.badge { + content = AnyComponentWithIdentity(id: "badge-\(component.title)-\(badge)", component: AnyComponent(ButtonTextContentComponent( + text: component.title, + badge: badge, + textColor: component.theme.list.itemCheckColors.foregroundColor, + badgeBackground: component.theme.list.itemCheckColors.foregroundColor, + badgeForeground: component.theme.list.itemCheckColors.fillColor + ))) + } else { + content = AnyComponentWithIdentity(id: "title-\(component.title)", component: AnyComponent(Text( + text: component.title, + font: Font.semibold(17.0), + color: component.theme.list.itemCheckColors.foregroundColor + ))) } - if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize) + + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: content, + isEnabled: true, + displaysProgress: component.displaysProgress, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.action() + } + )), + environment: {}, + containerSize: availableSize + ) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: CGRect(origin: .zero, size: buttonSize)) + } + + return buttonSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class SendInviteLinkScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: SendInviteLinkScreenSubject + let peers: [TelegramForbiddenInvitePeer] + let peerPresences: [EnginePeer.Id: EnginePeer.Presence] + let sendPaidMessageStars: [EnginePeer.Id: StarsAmount] + + init( + context: AccountContext, + subject: SendInviteLinkScreenSubject, + peers: [TelegramForbiddenInvitePeer], + peerPresences: [EnginePeer.Id: EnginePeer.Presence], + sendPaidMessageStars: [EnginePeer.Id: StarsAmount] + ) { + self.context = context + self.subject = subject + self.peers = peers + self.peerPresences = peerPresences + self.sendPaidMessageStars = sendPaidMessageStars + } + + static func ==(lhs: SendInviteLinkScreenComponent, rhs: SendInviteLinkScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peers != rhs.peers { + return false + } + if lhs.peerPresences != rhs.peerPresences { + return false + } + if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(EnvironmentType, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var selectedItems = Set() + private var didInitializeSelection = false + private var isInProgress = false + private var isDismissing = false + + private var component: SendInviteLinkScreenComponent? + private weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private var createCallDisposable: Disposable? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.createCallDisposable?.dispose() + } + + private func dismiss(controller: @escaping () -> ViewController?, animated: Bool) { + guard !self.isDismissing else { + return + } + self.isDismissing = true + + let performDismiss: () -> Void = { + if let controller = controller() as? SendInviteLinkScreen { + controller.completePendingDismiss() + controller.dismiss(animated: false) + } else { + controller()?.dismiss(animated: false) + } + } + + if animated { + self.animateOut.invoke(Action { _ in + performDismiss() + }) + } else { + performDismiss() + } + } + + private func presentPaidMessageAlertIfNeeded(peers: [EngineRenderedPeer], requiresStars: [EnginePeer.Id: StarsAmount], completion: @escaping () -> Void) { + guard let component = self.component else { + completion() + return + } + var totalAmount: StarsAmount = .zero + for peer in peers { + if let amount = requiresStars[peer.peerId] { + totalAmount = totalAmount + amount + } + } + if totalAmount.value > 0 { + let controller = chatMessagePaymentAlertController( + context: component.context, + presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, + updatedPresentationData: nil, + peers: peers, + count: 1, + amount: totalAmount, + totalAmount: totalAmount, + hasCheck: false, + navigationController: self.environment?.controller()?.navigationController as? NavigationController, + completion: { _ in + completion() + } + ) + self.environment?.controller()?.present(controller, in: .window(.root)) + } else { + completion() + } + } + + private func sendInviteLink(link: String, selectedPeers: [TelegramForbiddenInvitePeer], completion: (() -> Void)? = nil) { + guard let component = self.component, let environment = self.environment else { + return + } + self.presentPaidMessageAlertIfNeeded( + peers: selectedPeers.map { EngineRenderedPeer(peer: $0.peer) }, + requiresStars: component.sendPaidMessageStars, + completion: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + for peerId in Array(self.selectedItems) { + var messageAttributes: [EngineMessage.Attribute] = [] + if let sendPaidMessageStars = component.sendPaidMessageStars[peerId] { + messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) + } + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: [.message(text: link, attributes: messageAttributes, inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).startStandalone() + } + + let text: String + if selectedPeers.count == 1 { + text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else if selectedPeers.count == 2 { + text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } else { + text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root)) + + completion?() + } + ) + } + + private func performMainAction() { + guard let component = self.component, let controller = self.environment?.controller() else { + return + } + + let link: String? + switch component.subject { + case let .chat(_, linkValue): + link = linkValue + case let .groupCall(groupCall): + switch groupCall { + case .create: + self.isInProgress = true + self.state?.updated(transition: .immediate) + + self.createCallDisposable = (component.context.engine.calls.createConferenceCall() + |> deliverOnMainQueue).startStrict(next: { [weak self] call in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + + if self.selectedItems.isEmpty { + controller.dismiss() + } else { + let link = call.link + let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } + self.sendInviteLink(link: link, selectedPeers: selectedPeers, completion: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + let navigationController = controller.navigationController as? NavigationController + let context = component.context + controller.dismiss(completion: { [weak navigationController] in + if let navigationController, let peer = selectedPeers.first?.peer { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + context: context, + chatLocation: .peer(peer) + )) + } + }) + }) + } + }) + return + case let .existing(linkValue): + link = linkValue + } + } + + if self.selectedItems.isEmpty { + controller.dismiss() + } else if let link { + let selectedPeers = component.peers.filter { self.selectedItems.contains($0.peer.id) } + self.sendInviteLink(link: link, selectedPeers: selectedPeers, completion: { [weak self] in + self?.environment?.controller()?.dismiss() + }) + } else { + controller.dismiss() + } + } + + func update(component: SendInviteLinkScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if !self.didInitializeSelection { + self.didInitializeSelection = true + for peer in component.peers { + switch component.subject { + case let .chat(_, link): + if link != nil && !peer.premiumRequiredToContact { + self.selectedItems.insert(peer.peer.id) + } + case .groupCall: + self.selectedItems.insert(peer.peer.id) + } + } + } + + self.component = component + self.state = state + + let environmentValue = environment[EnvironmentType.self].value + self.environment = environmentValue + let controller = environmentValue.controller + let theme = environmentValue.theme.withModalBlocksBackground() + let hasInviteSection = sendInviteLinkHasInviteSection(subject: component.subject, peers: component.peers) + + let dismiss: (Bool) -> Void = { [weak self] animated in + self?.dismiss(controller: controller, animated: animated) + } + + let actionTitle: String + let actionBadge: Int? + switch component.subject { + case let .chat(_, link): + if link != nil { + actionTitle = self.selectedItems.isEmpty ? environmentValue.strings.SendInviteLink_ActionSkip : environmentValue.strings.SendInviteLink_ActionInvite + } else { + actionTitle = environmentValue.strings.SendInviteLink_ActionClose + } + actionBadge = (self.selectedItems.isEmpty || link == nil) ? nil : self.selectedItems.count + case .groupCall: + actionTitle = environmentValue.strings.SendInviteLink_ActionInvite + actionBadge = nil + } + + let bottomItem: AnyComponent? + if hasInviteSection { + bottomItem = AnyComponent(SendInviteLinkActionButtonComponent( + theme: theme, + title: actionTitle, + badge: actionBadge, + displaysProgress: self.isInProgress, + action: { [weak self] in + self?.performMainAction() + } + )) + } else { + bottomItem = nil + } + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(SendInviteLinkContentComponent( + context: component.context, + subject: component.subject, + peers: component.peers, + peerPresences: component.peerPresences, + selectedItems: self.selectedItems, + theme: theme, + toggleSelection: { [weak self] peerId in + guard let self else { + return + } + if self.selectedItems.contains(peerId) { + self.selectedItems.remove(peerId) + } else { + self.selectedItems.insert(peerId) + } + self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut))) + }, + openPremium: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() else { + return + } + let navigationController = controller.navigationController as? NavigationController + controller.dismiss() + let premiumController = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .settings, forceDark: false, dismissed: nil) + navigationController?.pushViewController(premiumController) + } + )), + titleItem: nil, + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + rightItem: nil, + hasTopEdgeEffect: false, + bottomItem: bottomItem, + backgroundColor: .color(theme.list.modalBlocksBackgroundColor), + defaultHeight: 540.0, + animateOut: self.animateOut + )), + environment: { + environmentValue + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environmentValue.statusBarHeight, + safeInsets: environmentValue.safeInsets, + inputHeight: 0.0, + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: environmentValue.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated) + } + ) + }, + containerSize: availableSize + ) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) } - self.ignoreScrolling = false - self.updateScrolling(transition: transition) return availableSize } @@ -1164,141 +1168,31 @@ private final class SendInviteLinkScreenComponent: Component { return View(frame: CGRect()) } - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + public class SendInviteLinkScreen: ViewControllerComponentContainer { - private let context: AccountContext - private let peers: [TelegramForbiddenInvitePeer] - private var isDismissed: Bool = false + private var dismissCompletion: (() -> Void)? private var presenceDisposable: Disposable? public init(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme? = nil) { - self.context = context - - #if DEBUG && false - var peers = peers - - if !"".isEmpty { - enum TestConfiguration: CaseIterable { - case singlePeerNoPremiumLink - case singlePeerPremiumLink - case singlePeerNoPremiumNoLink - case singlePeerPremiumNoLink - case somePeersNoPremiumLink - case somePeersOnePremiumLink - case somePeersAllPremiumLink - case somePeersNoPremiumNoLink - case somePeersOnePremiumNoLink - case somePeersAllPremiumNoLink - case morePeersNoPremiumLink - case morePeersOnePremiumLink - case morePeersAllPremiumLink - case morePeersNoPremiumNoLink - case morePeersOnePremiumNoLink - case morePeersAllPremiumNoLink - } - - var nextPeerId: Int64 = 1 - let makePeer: (Bool, Bool) -> TelegramForbiddenInvitePeer = { canInviteWithPremium, premiumRequiredToContact in - guard case let .user(user) = peers[0].peer else { - preconditionFailure() - } - let id = nextPeerId - nextPeerId += 1 - return TelegramForbiddenInvitePeer( - peer: .user(TelegramUser( - id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id)), - accessHash: user.accessHash, - firstName: user.firstName, - lastName: user.lastName, - username: user.username, - phone: user.phone, - photo: user.photo, - botInfo: user.botInfo, - restrictionInfo: user.restrictionInfo, - flags: user.flags, - emojiStatus: user.emojiStatus, - usernames: user.usernames, - storiesHidden: user.storiesHidden, - nameColor: user.nameColor, - backgroundEmojiId: user.backgroundEmojiId, - profileColor: user.profileColor, - profileBackgroundEmojiId: user.profileBackgroundEmojiId, - subscriberCount: user.subscriberCount, - verificationIconFileId: user.verificationIconFileId - )), - canInviteWithPremium: canInviteWithPremium, - premiumRequiredToContact: premiumRequiredToContact - ) - } - - let caseIndex = 9 - let configuration = TestConfiguration.allCases[caseIndex] - do { - switch configuration { - case .singlePeerNoPremiumLink: - peers = [makePeer(false, false)] - link = "abcd" - case .singlePeerPremiumLink: - peers = [makePeer(true, false)] - link = "abcd" - case .singlePeerNoPremiumNoLink: - peers = [makePeer(false, false)] - link = nil - case .singlePeerPremiumNoLink: - peers = [makePeer(true, false)] - link = nil - case .somePeersNoPremiumLink: - peers = (0 ..< 3).map { _ in makePeer(false, false) } - link = "abcd" - case .somePeersOnePremiumLink: - peers = [ - makePeer(false, false), - makePeer(true, true), - makePeer(false, false) - ] - link = "abcd" - case .somePeersAllPremiumLink: - peers = (0 ..< 3).map { _ in makePeer(true, false) } - link = "abcd" - case .somePeersNoPremiumNoLink: - peers = (0 ..< 3).map { _ in makePeer(false, false) } - link = nil - case .somePeersOnePremiumNoLink: - peers = [ - makePeer(false, false), - makePeer(true, false), - makePeer(false, false) - ] - link = nil - case .somePeersAllPremiumNoLink: - peers = (0 ..< 3).map { _ in makePeer(true, false) } - link = nil - case .morePeersNoPremiumLink: - preconditionFailure() - case .morePeersOnePremiumLink: - preconditionFailure() - case .morePeersAllPremiumLink: - preconditionFailure() - case .morePeersNoPremiumNoLink: - preconditionFailure() - case .morePeersOnePremiumNoLink: - preconditionFailure() - case .morePeersAllPremiumNoLink: - preconditionFailure() - } - } - } - #endif - - self.peers = peers - - super.init(context: context, component: SendInviteLinkScreenComponent(context: context, subject: subject, peers: peers, peerPresences: [:], sendPaidMessageStars: [:]), navigationBarAppearance: .none, theme: theme.flatMap { .custom($0) } ?? .default) + super.init( + context: context, + component: SendInviteLinkScreenComponent( + context: context, + subject: subject, + peers: peers, + peerPresences: [:], + sendPaidMessageStars: [:] + ), + navigationBarAppearance: .none, + theme: theme.flatMap { .custom($0) } ?? .default + ) self.statusBar.statusBarStyle = .Ignore self.navigationPresentation = .flatModal @@ -1328,7 +1222,16 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { parsedSendPaidMessageStars[id] = sendPaidMessageStars } } - self.updateComponent(component: AnyComponent(SendInviteLinkScreenComponent(context: context, subject: subject, peers: peers, peerPresences: parsedPresences, sendPaidMessageStars: parsedSendPaidMessageStars)), transition: .immediate) + self.updateComponent( + component: AnyComponent(SendInviteLinkScreenComponent( + context: context, + subject: subject, + peers: peers, + peerPresences: parsedPresences, + sendPaidMessageStars: parsedSendPaidMessageStars + )), + transition: .immediate + ) }) } @@ -1344,22 +1247,23 @@ public class SendInviteLinkScreen: ViewControllerComponentContainer { super.viewDidAppear(animated) self.view.disablesInteractiveModalDismiss = true - - if let componentView = self.node.hostView.componentView as? SendInviteLinkScreenComponent.View { - componentView.animateIn() - } } + func completePendingDismiss() { + let dismissCompletion = self.dismissCompletion + self.dismissCompletion = nil + dismissCompletion?() + } + override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true + self.dismissCompletion = completion - if let componentView = self.node.hostView.componentView as? SendInviteLinkScreenComponent.View { - componentView.animateOut(completion: { [weak self] in - completion?() - self?.dismiss(animated: false) - }) + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() } else { + self.completePendingDismiss() self.dismiss(animated: false) } } diff --git a/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/BUILD index 0f7c19aaa1..b8ba016275 100644 --- a/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD index 4d6f471d37..1c9cc083ac 100644 --- a/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ArchiveInfoScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD index 671515c2ed..311910ad39 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD @@ -42,9 +42,8 @@ swift_library( "//submodules/ItemListPeerActionItem", "//submodules/ItemListUI", "//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController", - "//submodules/DateSelectionUI", "//submodules/TelegramStringFormatting", - "//submodules/TelegramUI/Components/TimeSelectionActionSheet", + "//submodules/TelegramUI/Components/ChatTimerScreen", "//submodules/TelegramUI/Components/ChatListHeaderComponent", "//submodules/AttachmentUI", "//submodules/SearchBarNode", diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 12d3e1bca0..5de66a15b3 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -7,7 +7,6 @@ import TelegramPresentationData import AppBundle import ChatListUI import AccountContext -import Postbox import TelegramCore final class GreetingMessageListItemComponent: Component { @@ -197,6 +196,8 @@ final class GreetingMessageListItemComponent: Component { }, performActiveSessionAction: { _, _ in }, + performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: { }, hideChatFolderUpdates: { @@ -226,7 +227,7 @@ final class GreetingMessageListItemComponent: Component { context: component.context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: component.message.index)), + index: EngineChatList.Item.Index.chatList(EngineChatListIndex(pinningIndex: nil, messageIndex: component.message.index)), content: .peer(ChatListItemContent.PeerData( messages: [component.message], peer: EngineRenderedPeer(peer: component.accountPeer), diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift index c8c10bdc99..4d15e5e1da 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift @@ -1,14 +1,13 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtocol { private final class PendingMessageContext { let disposable = MetaDisposable() - var message: Message? + var message: EngineRawMessage? init() { } @@ -21,13 +20,13 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco private var shortcut: String private var shortcutId: Int32? - private(set) var mergedHistoryView: MessageHistoryView? - private var sourceHistoryView: MessageHistoryView? - + private(set) var mergedHistoryView: EngineRawMessageHistoryView? + private var sourceHistoryView: EngineRawMessageHistoryView? + private var pendingMessages: [PendingMessageContext] = [] private var historyViewDisposable: Disposable? private var pendingHistoryViewDisposable: Disposable? - let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>() + let historyViewStream = ValuePipe<(EngineRawMessageHistoryView, EngineViewUpdateType)>() private var nextUpdateIsHoleFill: Bool = false init(queue: Queue, context: AccountContext, shortcut: String, shortcutId: Int32?) { @@ -98,17 +97,17 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco } } - private func updateHistoryView(updateType: ViewUpdateType) { + private func updateHistoryView(updateType: EngineViewUpdateType) { var entries = self.sourceHistoryView?.entries ?? [] for pendingMessage in self.pendingMessages { if let message = pendingMessage.message { if !entries.contains(where: { $0.message.stableId == message.stableId }) { - entries.append(MessageHistoryEntry( + entries.append(EngineRawMessageHistoryEntry( message: message, isRead: true, location: nil, monthLocation: nil, - attributes: MutableMessageHistoryEntryAttributes( + attributes: EngineRawMutableMessageHistoryEntryAttributes( authorIsContact: false ) )) @@ -116,8 +115,8 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco } } entries.sort(by: { $0.message.index < $1.message.index }) - - let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: entries, holeEarlier: false, holeLater: false, isLoading: false) + + let mergedHistoryView = EngineRawMessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: entries, holeEarlier: false, holeLater: false, isLoading: false) self.mergedHistoryView = mergedHistoryView self.historyViewStream.putNext((mergedHistoryView, updateType)) @@ -184,7 +183,7 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco var kind: ChatCustomContentsKind - var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { + var historyView: Signal<(EngineRawMessageHistoryView, EngineViewUpdateType), NoError> { return self.impl.signalWith({ impl, subscriber in if let mergedHistoryView = impl.mergedHistoryView { subscriber.putNext((mergedHistoryView, .Initial)) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index fe66d1577d..7c6418a7dd 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,11 +23,10 @@ import Markdown import PeerListItemComponent import AvatarNode import ListItemSliderSelectorComponent -import DateSelectionUI import PlainButtonComponent import TelegramStringFormatting import TextFormat -import TimeSelectionActionSheet +import ChatTimerScreen private let checkIcon: UIImage = { return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in @@ -506,14 +504,19 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { return } - let controller = DateSelectionActionSheetController( + let controller = ChatTimerScreen( context: component.context, - title: nil, - currentValue: Int32(clippedDate.timeIntervalSince1970), - minimumDate: nil, - maximumDate: nil, - emptyTitle: nil, - applyValue: { [weak self] value in + configuration: ChatTimerScreen.Configuration( + style: .default, + title: { strings in return isStartTime ? strings.BusinessMessageSetup_ScheduleStartTime : strings.BusinessMessageSetup_ScheduleEndTime }, + picker: .date, + currentValue: Int32(clippedDate.timeIntervalSince1970), + pickerValueMapping: .roundDateToDaysUTC, + primaryActionTitle: { strings, _, _ in + strings.Wallpaper_Set + } + ), + completion: { [weak self] value in guard let self else { return } @@ -545,33 +548,44 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { let hour = components.hour ?? 0 let minute = components.minute ?? 0 - let controller = TimeSelectionActionSheet(context: component.context, currentValue: Int32(hour * 60 * 60 + minute * 60), applyValue: { [weak self] value in - guard let self else { - return - } - guard let value else { - return - } - - let updatedHour = value / (60 * 60) - let updatedMinute = (value % (60 * 60)) / 60 - - let calendar = Calendar.current - var updatedComponents = calendar.dateComponents([.year, .month, .day], from: currentValue) - updatedComponents.hour = Int(updatedHour) - updatedComponents.minute = Int(updatedMinute) - - guard let updatedClippedDate = calendar.date(from: updatedComponents) else { - return - } - - if isStartTime { - self.customScheduleStart = updatedClippedDate - } else { - self.customScheduleEnd = updatedClippedDate - } - self.state?.updated(transition: .immediate) - }) + let controller = ChatTimerScreen( + context: component.context, + configuration: ChatTimerScreen.Configuration( + style: .default, + picker: .timeOfDay, + currentValue: Int32(hour * 60 * 60 + minute * 60), + pickerValueMapping: .secondsFromMidnightGMT, + primaryActionTitle: { strings, _, _ in + strings.Wallpaper_Set + } + ), + completion: { [weak self] value in + guard let self else { + return + } + guard let value else { + return + } + + let updatedHour = value / (60 * 60) + let updatedMinute = (value % (60 * 60)) / 60 + + let calendar = Calendar.current + var updatedComponents = calendar.dateComponents([.year, .month, .day], from: currentValue) + updatedComponents.hour = Int(updatedHour) + updatedComponents.minute = Int(updatedMinute) + + guard let updatedClippedDate = calendar.date(from: updatedComponents) else { + return + } + + if isStartTime { + self.customScheduleStart = updatedClippedDate + } else { + self.customScheduleEnd = updatedClippedDate + } + self.state?.updated(transition: .immediate) + }) self.environment?.controller()?.present(controller, in: .window(.root)) } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift index 13f07d581f..2cf3abe444 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkChatContents.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext @@ -30,8 +29,8 @@ final class BusinessLinkChatContents: ChatCustomContentsProtocol { var kind: ChatCustomContentsKind - var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> { - let view = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: [], holeEarlier: false, holeLater: false, isLoading: false) + var historyView: Signal<(EngineRawMessageHistoryView, EngineViewUpdateType), NoError> { + let view = EngineRawMessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: [], holeEarlier: false, holeLater: false, isLoading: false) return .single((view, .Initial)) } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkListItemComponent.swift index e1f7fd4b1f..0c8224d4f7 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinkListItemComponent.swift @@ -6,7 +6,6 @@ import ListSectionComponent import TelegramPresentationData import AppBundle import AccountContext -import Postbox import TelegramCore import TextNodeWithEntities import MultilineTextComponent diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index f9a8a6cdf4..2923c0bd2e 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -212,6 +211,8 @@ final class QuickReplySetupScreenComponent: Component { }, performActiveSessionAction: { _, _ in }, + performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: { }, hideChatFolderUpdates: { @@ -256,7 +257,7 @@ final class QuickReplySetupScreenComponent: Component { context: listNode.context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: listNode.context.account.peerId, namespace: 0, id: 0), timestamp: 0))), + index: EngineChatList.Item.Index.chatList(EngineChatListIndex(pinningIndex: nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: listNode.context.account.peerId, namespace: 0, id: 0), timestamp: 0))), content: .peer(ChatListItemContent.PeerData( messages: [item.topMessage], peer: EngineRenderedPeer(peer: accountPeer), @@ -768,20 +769,13 @@ final class QuickReplySetupScreenComponent: Component { titleText = strings.QuickReply_Title } - let closeTitle: String - switch component.mode { - case .manage: - closeTitle = strings.Common_Close - case .select: - closeTitle = strings.Common_Cancel - } let headerContent: ChatListHeaderComponent.Content? = ChatListHeaderComponent.Content( title: titleText, navigationBackTitle: nil, titleComponent: nil, chatListTitle: nil, leftButton: isModal ? AnyComponentWithIdentity(id: "close", component: AnyComponent(NavigationButtonComponent( - content: .text(title: closeTitle, isBold: false), + content: .icon(imageName: "Navigation/Close"), pressed: { [weak self] _ in guard let self else { return @@ -1064,7 +1058,9 @@ final class QuickReplySetupScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD index d025efe999..6bedd804ae 100644 --- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD index dfe7c400e0..80e1d2d757 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", @@ -36,7 +35,7 @@ swift_library( "//submodules/TextFormat", "//submodules/UIKitRuntimeUtils", "//submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen", - "//submodules/TelegramUI/Components/TimeSelectionActionSheet", + "//submodules/TelegramUI/Components/ChatTimerScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift index 6266befb8c..553c126ca8 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessDaySetupScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -22,7 +21,7 @@ import LocationUI import TelegramStringFormatting import TextFormat import PlainButtonComponent -import TimeSelectionActionSheet +import ChatTimerScreen func clipMinutes(_ value: Int) -> Int { return value % (24 * 60) @@ -181,31 +180,42 @@ final class BusinessDaySetupScreenComponent: Component { return } - let controller = TimeSelectionActionSheet(context: component.context, currentValue: Int32(isStartTime ? clipMinutes(range.startMinute) : clipMinutes(range.endMinute)) * 60, applyValue: { [weak self] value in - guard let self else { - return - } - guard let value else { - return - } - if let index = self.ranges.firstIndex(where: { $0.id == rangeId }) { - var startMinute = range.startMinute - var endMinute = range.endMinute - if isStartTime { - startMinute = Int(value) / 60 - } else { - endMinute = Int(value) / 60 + let controller = ChatTimerScreen( + context: component.context, + configuration: ChatTimerScreen.Configuration( + style: .default, + picker: .timeOfDay, + currentValue: Int32(isStartTime ? clipMinutes(range.startMinute) : clipMinutes(range.endMinute)) * 60, + pickerValueMapping: .secondsFromMidnightGMT, + primaryActionTitle: { strings, _, _ in + strings.Wallpaper_Set } - if endMinute < startMinute { - endMinute = endMinute + 24 * 60 + ), + completion: { [weak self] value in + guard let self else { + return } - self.ranges[index].startMinute = startMinute - self.ranges[index].endMinute = endMinute - self.validateRanges() - - self.state?.updated(transition: .immediate) - } - }) + guard let value else { + return + } + if let index = self.ranges.firstIndex(where: { $0.id == rangeId }) { + var startMinute = range.startMinute + var endMinute = range.endMinute + if isStartTime { + startMinute = Int(value) / 60 + } else { + endMinute = Int(value) / 60 + } + if endMinute < startMinute { + endMinute = endMinute + 24 * 60 + } + self.ranges[index].startMinute = startMinute + self.ranges[index].endMinute = endMinute + self.validateRanges() + + self.state?.updated(transition: .immediate) + } + }) self.environment?.controller()?.present(controller, in: .window(.root)) } diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift index 6eecea9155..1eafa17d0a 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift index 683f23d097..86cc6ae0c0 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -56,6 +55,7 @@ final class BusinessIntroSetupScreenComponent: Component { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -100,6 +100,7 @@ final class BusinessIntroSetupScreenComponent: Component { private var stickerContent: EmojiPagerContentComponent? private var stickerContentDisposable: Disposable? private let stickerSearchDisposable = MetaDisposable() + private var stickerSearchContext: StickerSearchContext? private var stickerSearchState = EmojiSearchState(result: nil, isSearching: false) private var displayStickerInput: Bool = false @@ -309,6 +310,7 @@ final class BusinessIntroSetupScreenComponent: Component { self.stickerFile = itemFile._parse() self.displayStickerInput = false + self.stickerSearchContext = nil self.stickerSearchDisposable.set(nil) self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false) @@ -351,6 +353,7 @@ final class BusinessIntroSetupScreenComponent: Component { switch query { case .none: + self.stickerSearchContext = nil self.stickerSearchDisposable.set(nil) self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false) if !self.isUpdating { @@ -360,54 +363,49 @@ final class BusinessIntroSetupScreenComponent: Component { let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.stickerSearchContext = nil self.stickerSearchDisposable.set(nil) self.stickerSearchState = EmojiSearchState(result: nil, isSearching: false) self.state?.updated(transition: .immediate) } else { let context = component.context + self.stickerSearchContext = nil - let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in - var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) - - if query.isSingleEmoji { - signals = .single([context.engine.stickers.searchStickers(query: nil, emoticon: [query.basicEmoji.0]) - |> map { (nil, $0.items) }]) - } else if query.count > 1, !languageCode.isEmpty && languageCode != "emoji" { - var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) - if !languageCode.lowercased().hasPrefix("en") { - signal = signal - |> mapToSignal { keywords in - return .single(keywords) - |> then( - context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) - |> map { englishKeywords in - return keywords + englishKeywords - } - ) - } - } - signals = signal - |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in - let emoticon = keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 } - return [context.engine.stickers.searchStickers(query: query, emoticon: emoticon, inputLanguageCode: languageCode) - |> map { (nil, $0.items) }] + let stickers: Signal<(items: [(String?, FoundStickerItem)], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?), NoError> + if query.isSingleEmoji { + let searchContext = context.engine.stickers.stickerSearchContext(query: nil, emoticon: [query.basicEmoji.0]) + stickers = searchContext.state + |> map { state -> (items: [(String?, FoundStickerItem)], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?) in + return (state.items.map { (nil, $0) }, state.canLoadMore, state.items.isEmpty && state.isLoadingMore, searchContext) + } + } else if query.count > 1, !languageCode.isEmpty && languageCode != "emoji" { + var keywordsSignal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) + if !languageCode.lowercased().hasPrefix("en") { + keywordsSignal = keywordsSignal + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) } } - - return (signals - |> mapToSignal { signals in - return combineLatest(signals) - }).start(next: { results in - var result: [(String?, FoundStickerItem)] = [] - for (emoji, stickers) in results { - for sticker in stickers { - result.append((emoji, sticker)) - } + stickers = keywordsSignal + |> mapToSignal { keywords -> Signal<(items: [(String?, FoundStickerItem)], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?), NoError> in + let emoticon = Array(Set(keywords.flatMap { $0.emoticons }.map { $0.basicEmoji.0 })) + guard !emoticon.isEmpty else { + return .single(([], false, false, nil)) } - subscriber.putNext(result) - }, completed: { - subscriber.putCompletion() - }) + let searchContext = context.engine.stickers.stickerSearchContext(query: query, emoticon: emoticon, inputLanguageCode: languageCode) + return searchContext.state + |> map { state -> (items: [(String?, FoundStickerItem)], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?) in + return (state.items.map { (nil, $0) }, state.canLoadMore, state.items.isEmpty && state.isLoadingMore, searchContext) + } + } + } else { + stickers = .single(([], false, false, nil)) } let currentRemotePacks = Atomic(value: nil) @@ -430,15 +428,11 @@ final class BusinessIntroSetupScreenComponent: Component { ) } - let installedPackIds = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]) - |> map { view -> Set in - var installedPacks = Set() - if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { - if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { - for entry in packsEntries { - installedPacks.insert(entry.id) - } - } + let installedPackIds = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCollections.InstalledPackInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks)) + |> map { entries -> Set in + var installedPacks = Set() + for entry in entries { + installedPacks.insert(entry.id) } return installedPacks } @@ -467,21 +461,14 @@ final class BusinessIntroSetupScreenComponent: Component { } let signal = combineLatest(stickers, packs) - |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in - return (stickers, packs.0, packs.1, packs.2) - } - - let resultSignal: Signal<[EmojiPagerContentComponent.ItemGroup], NoError> = signal - |> mapToSignal { result in - guard let result else { - return .complete() - } - - let (foundItems, localSets, complete, remoteSets) = result + |> map { stickers, packs -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: StickerSearchContext?) in + let foundItems = stickers.items + let localSets = packs.0 + let remoteSets = packs.2 var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for (_, entry) in foundItems { let itemFile = entry.file @@ -529,11 +516,7 @@ final class BusinessIntroSetupScreenComponent: Component { items.append(item) } - if items.isEmpty && !complete { - return .complete() - } - - return .single([EmojiPagerContentComponent.ItemGroup( + let groups = [EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", title: nil, @@ -550,31 +533,34 @@ final class BusinessIntroSetupScreenComponent: Component { headerItem: nil, fillWithLoadingPlaceholders: false, items: items - )]) + )] + return (groups, stickers.canLoadMore, stickers.isSearching, stickers.searchContext) } var version = 0 self.stickerSearchState.isSearching = true self.state?.updated(transition: .immediate) - self.stickerSearchDisposable.set((resultSignal + self.stickerSearchDisposable.set((signal |> delay(0.15, queue: .mainQueue()) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self else { return } - self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.stickerSearchContext = result.searchContext + self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 self.state?.updated(transition: .immediate) })) } case let .category(value): + self.stickerSearchContext = nil let resultSignal = component.context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote]) |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for item in files.items { let itemFile = item.file if existingIds.contains(itemFile.fileId) { @@ -642,13 +628,13 @@ final class BusinessIntroSetupScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) if !self.isUpdating { self.state?.updated(transition: .immediate) } return } - self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 if !self.isUpdating { self.state?.updated(transition: .immediate) @@ -659,6 +645,9 @@ final class BusinessIntroSetupScreenComponent: Component { updateScrollingToItemGroup: { }, onScroll: {}, + loadMore: { [weak self] in + self?.stickerSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -1026,7 +1015,7 @@ final class BusinessIntroSetupScreenComponent: Component { stickerSelectionControl = ComponentView() self.stickerSelectionControl = stickerSelectionControl } - var selectedItems = Set() + var selectedItems = Set() if let stickerFile = self.stickerFile { selectedItems.insert(stickerFile.fileId) } @@ -1036,14 +1025,14 @@ final class BusinessIntroSetupScreenComponent: Component { if let stickerSearchResult = self.stickerSearchState.result { var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults? - if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { + if !self.stickerSearchState.isSearching && !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults( text: environment.strings.Stickers_NoStickersFound, iconFile: nil ) } let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true) - stickerContent = stickerContent.withUpdatedItemGroups(panelItemGroups: stickerContent.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: self.stickerSearchState.isSearching ? .searching : defaultSearchState) + stickerContent = stickerContent.withUpdatedItemGroups(panelItemGroups: stickerContent.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: self.stickerSearchState.isSearching ? .searching : defaultSearchState, canLoadMore: stickerSearchResult.canLoadMore) } else if self.stickerSearchState.isSearching { stickerContent = stickerContent.withUpdatedItemGroups(panelItemGroups: stickerContent.panelItemGroups, contentItemGroups: stickerContent.contentItemGroups, itemContentUniqueId: stickerContent.itemContentUniqueId, emptySearchResults: stickerContent.emptySearchResults, searchState: .searching) } diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift index e1a568e5da..0674297168 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift @@ -122,7 +122,6 @@ final class ChatIntroItemComponent: Component { mode: .standard(.default), chatLocation: .peer(id: component.context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, diff --git a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD index dd67a3b410..4772e4f748 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift index 8a531ac3a3..23e0f9387c 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLinkNameAlertController/Sources/BusinessLinkNameAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD index 83cfb9c9c7..5bde7cdc39 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift index 6f0654f867..3e66115baf 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -220,7 +219,13 @@ final class BusinessLocationSetupScreenComponent: Component { guard let component = self.component else { return } - let controller = LocationPickerController(context: component.context, updatedPresentationData: nil, mode: .pick, initialLocation: initialLocation, completion: { [weak self] location, _, _, address, _ in + let controller = LocationPickerController( + context: component.context, + style: .glass, + updatedPresentationData: nil, + mode: .pick, + initialLocation: initialLocation, + completion: { [weak self] location, _, _, address, _ in guard let self else { return } @@ -522,6 +527,7 @@ final class BusinessLocationSetupScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -610,7 +616,7 @@ final class BusinessLocationSetupScreenComponent: Component { if let current = self.applyButtonItem { applyButtonItem = current } else { - applyButtonItem = UIBarButtonItem(title: environment.strings.Common_Save, style: .done, target: self, action: #selector(self.savePressed)) + applyButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.savePressed)) } if controller.navigationItem.rightBarButtonItem !== applyButtonItem { controller.navigationItem.setRightBarButton(applyButtonItem, animated: true) diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD index 210ac682a5..cb4272bce9 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", @@ -36,6 +35,7 @@ swift_library( "//submodules/TelegramUI/Components/Stories/PeerListItemComponent", "//submodules/TelegramUI/Components/AlertComponent", "//submodules/ShimmerEffect", + "//submodules/UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift index 17f2df3631..7e56f86bef 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift @@ -655,7 +655,31 @@ final class BusinessRecipientListScreenComponent: Component { } } -final class BusinessRecipientListScreen: ViewControllerComponentContainer { +public final class BusinessRecipientListScreen: ViewControllerComponentContainer { + public struct ResolvedPeer { + public let peer: EnginePeer + public let isContact: Bool + + public init(peer: EnginePeer, isContact: Bool) { + self.peer = peer + self.isContact = isContact + } + } + + public struct PeerListState { + public let categories: TelegramBusinessRecipients.Categories + public let peers: [ResolvedPeer] + public let excludePeers: [ResolvedPeer] + public let excludeByDefault: Bool + + public init(categories: TelegramBusinessRecipients.Categories, peers: [ResolvedPeer], excludePeers: [ResolvedPeer], excludeByDefault: Bool) { + self.categories = categories + self.peers = peers + self.excludePeers = excludePeers + self.excludeByDefault = excludeByDefault + } + } + final class InitialData { fileprivate let peerList: BusinessRecipientListScreenComponent.PeerList @@ -666,7 +690,7 @@ final class BusinessRecipientListScreen: ViewControllerComponentContainer { } } - enum Mode { + public enum Mode { case includeExceptions case excludeExceptions case excludeUsers @@ -711,6 +735,121 @@ final class BusinessRecipientListScreen: ViewControllerComponentContainer { deinit { } + public static func openSetupFlow(context: AccountContext, from controller: ViewController, state: PeerListState, isExclude: Bool, update: @escaping (PeerListState) -> Void) { + let mode: BusinessRecipientListScreen.Mode + if isExclude { + mode = .excludeUsers + } else if state.excludeByDefault { + mode = .excludeExceptions + } else { + mode = .includeExceptions + } + + let mappedPeerList = BusinessRecipientListScreenComponent.PeerList( + categories: isExclude ? Set() : self.mapCategories(state.categories), + peers: (isExclude ? state.excludePeers : state.peers).map { peer in + BusinessRecipientListScreenComponent.PeerList.Peer( + peer: peer.peer, + isContact: peer.isContact + ) + } + ) + + let applyUpdate: (BusinessRecipientListScreenComponent.PeerList) -> PeerListState = { updatedPeerList in + var updatedState = state + + switch mode { + case .excludeExceptions, .includeExceptions: + updatedState = PeerListState( + categories: self.mapCategories(updatedPeerList.categories), + peers: updatedPeerList.peers.map { peer in + ResolvedPeer(peer: peer.peer, isContact: peer.isContact) + }, + excludePeers: state.excludePeers.filter { excludePeer in + !updatedPeerList.peers.contains(where: { $0.peer.id == excludePeer.peer.id }) + }, + excludeByDefault: state.excludeByDefault + ) + case .excludeUsers: + let excludePeers = updatedPeerList.peers.map { peer in + ResolvedPeer(peer: peer.peer, isContact: peer.isContact) + } + updatedState = PeerListState( + categories: state.categories, + peers: state.peers.filter { peer in + !excludePeers.contains(where: { $0.peer.id == peer.peer.id }) + }, + excludePeers: excludePeers, + excludeByDefault: state.excludeByDefault + ) + } + + return updatedState + } + + if mappedPeerList.categories.isEmpty && mappedPeerList.peers.isEmpty { + let setupController = BusinessRecipientListScreenComponent.View.makePeerListSetupScreen( + context: context, + mode: mode, + initialPeerList: mappedPeerList, + completion: { peerList in + controller.push(BusinessRecipientListScreen( + context: context, + peerList: peerList, + mode: mode, + update: { updatedPeerList in + update(applyUpdate(updatedPeerList)) + } + )) + } + ) + controller.push(setupController) + } else { + controller.push(BusinessRecipientListScreen( + context: context, + peerList: mappedPeerList, + mode: mode, + update: { updatedPeerList in + update(applyUpdate(updatedPeerList)) + } + )) + } + } + + private static func mapCategories(_ categories: TelegramBusinessRecipients.Categories) -> Set { + var mappedCategories = Set() + if categories.contains(.existingChats) { + mappedCategories.insert(.existingChats) + } + if categories.contains(.newChats) { + mappedCategories.insert(.newChats) + } + if categories.contains(.contacts) { + mappedCategories.insert(.contacts) + } + if categories.contains(.nonContacts) { + mappedCategories.insert(.nonContacts) + } + return mappedCategories + } + + private static func mapCategories(_ categories: Set) -> TelegramBusinessRecipients.Categories { + var mappedCategories: TelegramBusinessRecipients.Categories = [] + if categories.contains(.existingChats) { + mappedCategories.insert(.existingChats) + } + if categories.contains(.newChats) { + mappedCategories.insert(.newChats) + } + if categories.contains(.contacts) { + mappedCategories.insert(.contacts) + } + if categories.contains(.nonContacts) { + mappedCategories.insert(.nonContacts) + } + return mappedCategories + } + @objc private func cancelPressed() { self.dismiss() } diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift index bbe37bf01b..ecda61297b 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSearchResultItemComponent.swift @@ -208,7 +208,11 @@ final class ChatbotSearchResultItemComponent: Component { case let .found(peer, _): isTextVisible = true titleValue = peer.displayTitle(strings: component.strings, displayOrder: .firstLast) - subtitleValue = component.strings.Bot_GenericBotStatus + if let addressName = peer.addressName { + subtitleValue = "@\(addressName)" + } else { + subtitleValue = component.strings.Bot_GenericBotStatus + } } let titleSize = self.titleLabel.update( diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index f6807785f5..0b14eda092 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -15,6 +14,7 @@ import ViewControllerComponent import MultilineTextComponent import BalancedTextComponent import BackButtonComponent +import EdgeEffect import ListSectionComponent import ListActionItemComponent import ListTextFieldItemComponent @@ -24,6 +24,7 @@ import Markdown import PeerListItemComponent import AvatarNode import AlertComponent +import UndoUI private let checkIcon: UIImage = { return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in @@ -144,6 +145,7 @@ final class ChatbotSetupScreenComponent: Component { private let scrollView: ScrollView private let navigationTitle = ComponentView() + private let titleTransformContainer: UIView private let icon = ComponentView() private let subtitle = ComponentView() private let nameSection = ComponentView() @@ -190,6 +192,9 @@ final class ChatbotSetupScreenComponent: Component { } self.scrollView.alwaysBounceVertical = true + self.titleTransformContainer = UIView() + self.titleTransformContainer.isUserInteractionEnabled = false + super.init(frame: frame) self.scrollView.delegate = self @@ -203,6 +208,7 @@ final class ChatbotSetupScreenComponent: Component { } deinit { + self.titleTransformContainer.removeFromSuperview() } func scrollToTop() { @@ -210,10 +216,21 @@ final class ChatbotSetupScreenComponent: Component { } func attemptNavigation(complete: @escaping () -> Void) -> Bool { - guard let component = self.component else { + guard let component = self.component, let environemnt = self.environment else { return true } + if let botResolutionState = self.botResolutionState, case let .found(_, isInstalled) = botResolutionState.state, !isInstalled { + let alertController = textAlertController(context: component.context, title: environemnt.strings.ChatbotSetup_SetupNotCompleted_Title, text: environemnt.strings.ChatbotSetup_SetupNotCompleted_Text, actions: [ + TextAlertAction(type: .defaultAction, title: environemnt.strings.Common_Cancel, action: {}), + TextAlertAction(type: .genericAction, title: environemnt.strings.ChatbotSetup_SetupNotCompleted_Leave, action: { + complete() + }) + ]) + environemnt.controller()?.present(alertController, in: .window(.root)) + return false + } + var mappedCategories: TelegramBusinessRecipients.Categories = [] if self.additionalPeerList.categories.contains(.existingChats) { mappedCategories.insert(.existingChats) @@ -251,33 +268,29 @@ final class ChatbotSetupScreenComponent: Component { self.updateScrolling(transition: .immediate) } - var scrolledUp = true private func updateScrolling(transition: ComponentTransition) { - let navigationRevealOffsetY: CGFloat = 0.0 - - let navigationAlphaDistance: CGFloat = 16.0 - let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance)) - if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { - transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha) - transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha) + guard let environment = self.environment else { + return } - var scrolledUp = false - if navigationAlpha < 0.5 { - scrolledUp = true - } else if navigationAlpha > 0.5 { - scrolledUp = false - } + let titleCenterY: CGFloat = environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) * 0.5 + let titleTransformDistance: CGFloat = 20.0 + let titleY: CGFloat = max(titleCenterY, self.titleTransformContainer.center.y - self.scrollView.contentOffset.y) - if self.scrolledUp != scrolledUp { - self.scrolledUp = scrolledUp - if !self.isUpdating { - self.state?.updated() - } - } + transition.setSublayerTransform(view: self.titleTransformContainer, transform: CATransform3DMakeTranslation(0.0, titleY - self.titleTransformContainer.center.y, 0.0)) + let titleYDistance: CGFloat = titleY - titleCenterY + let titleTransformFraction: CGFloat = 1.0 - max(0.0, min(1.0, titleYDistance / titleTransformDistance)) + let titleMinScale: CGFloat = 17.0 / 24.0 + let titleScale: CGFloat = 1.0 * (1.0 - titleTransformFraction) + titleMinScale * titleTransformFraction if let navigationTitleView = self.navigationTitle.view { - transition.setAlpha(view: navigationTitleView, alpha: 1.0) + transition.setScale(view: navigationTitleView, scale: titleScale) + } + + if let controller = environment.controller(), let navigationBar = controller.navigationBar, let edgeEffectView = navigationBar.edgeEffectView { + let edgeEffectAlphaDistance = max(1.0, self.titleTransformContainer.center.y - titleCenterY) + let edgeEffectAlpha = max(0.0, min(1.0, self.scrollView.contentOffset.y / edgeEffectAlphaDistance)) + transition.setAlpha(view: edgeEffectView, alpha: edgeEffectAlpha) } } @@ -645,20 +658,41 @@ final class ChatbotSetupScreenComponent: Component { let navigationTitleSize = self.navigationTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.ChatbotSetup_TitleItem, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + text: .plain(NSAttributedString(string: environment.strings.ChatbotSetup_TitleItem, font: Font.bold(24.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), horizontalAlignment: .center )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) ) - let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) - if let navigationTitleView = self.navigationTitle.view { - if navigationTitleView.superview == nil { - if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { - navigationBar.view.addSubview(navigationTitleView) + let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.navigationHeight + 76.0), size: navigationTitleSize) + let overlaySuperview: UIView? + if let controller = environment.controller(), let navigationBar = controller.navigationBar, let navigationBarSuperview = navigationBar.view.superview { + overlaySuperview = navigationBarSuperview + } else { + overlaySuperview = self + } + if let overlaySuperview { + if self.titleTransformContainer.superview !== overlaySuperview { + self.titleTransformContainer.removeFromSuperview() + if let controller = environment.controller(), let navigationBar = controller.navigationBar, overlaySuperview === navigationBar.view.superview { + overlaySuperview.insertSubview(self.titleTransformContainer, aboveSubview: navigationBar.view) + } else { + overlaySuperview.addSubview(self.titleTransformContainer) } } - transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) + } + if let navigationTitleView = self.navigationTitle.view { + if navigationTitleView.superview !== self.titleTransformContainer { + navigationTitleView.removeFromSuperview() + self.titleTransformContainer.addSubview(navigationTitleView) + } + transition.setPosition(view: self.titleTransformContainer, position: navigationTitleFrame.center) + transition.setBounds(view: self.titleTransformContainer, bounds: CGRect(origin: CGPoint(), size: navigationTitleFrame.size)) + transition.setBounds(view: navigationTitleView, bounds: CGRect(origin: CGPoint(), size: navigationTitleFrame.size)) + transition.setPosition(view: navigationTitleView, position: CGPoint( + x: navigationTitleFrame.size.width * 0.5, + y: navigationTitleFrame.size.height * 0.5 + )) } let bottomContentInset: CGFloat = 24.0 @@ -675,13 +709,13 @@ final class ChatbotSetupScreenComponent: Component { let iconSize = self.icon.update( transition: .immediate, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "BotEmoji"), + content: LottieComponent.AppBundleContent(name: "ChatAutomation"), loop: false )), environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) + containerSize: CGSize(width: 140.0, height: 140.0) ) - let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight + 8.0), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight - 50.0), size: iconSize) if let iconView = self.icon.view as? LottieComponent.View { if iconView.superview == nil { self.scrollView.addSubview(iconView) @@ -691,11 +725,11 @@ final class ChatbotSetupScreenComponent: Component { iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) } - contentHeight += 129.0 + contentHeight += 115.0 let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.ChatbotSetup_Text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor), - bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), + body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor), link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor), linkAttribute: { attributes in return ("URL", "") @@ -732,7 +766,7 @@ final class ChatbotSetupScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 64.0, height: 1000.0) ) let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize) if let subtitleView = self.subtitle.view { @@ -780,7 +814,7 @@ final class ChatbotSetupScreenComponent: Component { strings: environment.strings, content: mappedContent, installAction: { [weak self] in - guard let self else { + guard let self, let component = self.component, let environment = self.environment, let controller = self.environment?.controller() else { return } self.endEditing(true) @@ -791,6 +825,15 @@ final class ChatbotSetupScreenComponent: Component { self.botResolutionState = botResolutionState self.botRights = [.reply, .readMessages, .deleteSentMessages, .deleteReceivedMessages] self.state?.updated(transition: .spring(duration: 0.3)) + + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .actionSucceeded(title: nil, text: environment.strings.ChatbotSetup_BotInstalled(peer.compactDisplayTitle).string, cancel: nil, destructive: false), + elevatedLayout: false, + position: .bottom, + animateInAsReplacement: false, + action: { _ in return true } + ), in: .current) } else { self.environment?.controller()?.present(textAlertController(context: component.context, title: nil, text: presentationData.strings.ChatbotSetup_ErrorBotNotBusinessCapable, actions: [ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { diff --git a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD index 142a36bbaa..ae1ca4f927 100644 --- a/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/BUILD b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/BUILD index 802cc7a542..c3ff6dded8 100644 --- a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/Display", "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift index e563f3385a..12946d256d 100644 --- a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift index 13761c553c..3de44cb9bf 100644 --- a/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift +++ b/submodules/TelegramUI/Components/Settings/LanguageSelectionScreen/Sources/LanguageSelectionScreenNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -452,7 +451,7 @@ final class LanguageSelectionScreenNode: ViewControllerTracingNode { var listInsets = layout.insets(options: [.input]) listInsets.top += navigationBarHeight - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) listInsets.left += inset listInsets.right += inset diff --git a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD index 2fdef7364d..2fc20a7fe0 100644 --- a/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/NewSessionInfoScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/PasskeysScreen/BUILD b/submodules/TelegramUI/Components/Settings/PasskeysScreen/BUILD index 38a7d38057..6d5ffb2ae0 100644 --- a/submodules/TelegramUI/Components/Settings/PasskeysScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PasskeysScreen/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 8959e21dda..904dcee8e4 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -98,7 +97,7 @@ final class ChannelAppearanceScreenComponent: Component { TelegramEngine.EngineData.Item.Peer.StickerPack(id: peerId), TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId) ), - telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) + context.engine.themes.themes(accountManager: context.sharedContext.accountManager) ) |> map { peerData, cloudThemes -> ContentsData in let (peer, subscriberCount, emojiPack, canSetStickerPack, stickerPack, wallpaper) = peerData @@ -170,7 +169,8 @@ final class ChannelAppearanceScreenComponent: Component { } final class View: UIView, UIScrollViewDelegate { - private let edgeEffectView: EdgeEffectView + private let topEdgeEffectView: EdgeEffectView + private let bottomEdgeEffectView: EdgeEffectView private let topOverscrollLayer = SimpleLayer() private let scrollView: ScrollView private let actionButton = ComponentView() @@ -226,7 +226,8 @@ final class ChannelAppearanceScreenComponent: Component { private weak var emojiStatusSelectionController: ViewController? override init(frame: CGRect) { - self.edgeEffectView = EdgeEffectView() + self.topEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView = EdgeEffectView() self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true @@ -247,7 +248,8 @@ final class ChannelAppearanceScreenComponent: Component { self.scrollView.layer.addSublayer(self.topOverscrollLayer) - self.addSubview(self.edgeEffectView) + self.addSubview(self.topEdgeEffectView) + self.addSubview(self.bottomEdgeEffectView) } required init?(coder: NSCoder) { @@ -340,7 +342,7 @@ final class ChannelAppearanceScreenComponent: Component { transition.setAlpha(view: navigationTitleView, alpha: navigationAlpha) } - //transition.setAlpha(view: self.edgeEffectView, alpha: navigationAlpha) + transition.setAlpha(view: self.topEdgeEffectView, alpha: navigationAlpha) } private func resolveState() -> ResolvedState? { @@ -695,9 +697,9 @@ final class ChannelAppearanceScreenComponent: Component { self.previousEmojiSetupTimestamp = currentTimestamp self.emojiStatusSelectionController?.dismiss() - var selectedItems = Set() + var selectedItems = Set() if let currentFileId { - selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: currentFileId)) + selectedItems.insert(EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: currentFileId)) } let mappedSubject: EmojiPagerContentComponent.Subject @@ -929,7 +931,7 @@ final class ChannelAppearanceScreenComponent: Component { if let temporaryPeerWallpaper = self.temporaryPeerWallpaper { resolvedWallpaper = .single(temporaryPeerWallpaper) } else if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: component.context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: component.context.engine, network: component.context.account.network, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper } @@ -1303,7 +1305,7 @@ final class ChannelAppearanceScreenComponent: Component { var emojiPackFile: TelegramMediaFile? if let thumbnail = emojiPack?.thumbnail { - emojiPackFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []) + emojiPackFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []) } let emojiPackSectionSize = self.emojiPackSection.update( @@ -1435,7 +1437,7 @@ final class ChannelAppearanceScreenComponent: Component { var stickerPackFile: TelegramMediaFile? if let peerStickerPack = contentsData.peerStickerPack, let thumbnail = peerStickerPack.thumbnail { - stickerPackFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: peerStickerPack.id.id), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []) + stickerPackFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: peerStickerPack.id.id), partialReference: nil, resource: thumbnail.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: thumbnail.immediateThumbnailData, mimeType: "", size: nil, attributes: [], alternativeRepresentations: []) } let stickerPackSectionSize = self.stickerPackSection.update( @@ -1505,7 +1507,7 @@ final class ChannelAppearanceScreenComponent: Component { if !isGroup { let messageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: false, - peerId: EnginePeer.Id(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), + peerId: EnginePeer.Id(namespace: peer.id.namespace, id: EnginePeer.Id.Id._internalFromInt64Value(0)), author: peer.compactDisplayTitle, photo: peer.profileImageRepresentations, nameColor: .preset(resolvedState.nameColor), @@ -1644,7 +1646,7 @@ final class ChannelAppearanceScreenComponent: Component { if isGroup { let incomingMessageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: false, - peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)), + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)), author: environment.strings.Group_Appearance_PreviewAuthor, photo: [], nameColor: .preset(.red), @@ -1656,7 +1658,7 @@ final class ChannelAppearanceScreenComponent: Component { let outgoingMessageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: true, - peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), + peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), author: peer.compactDisplayTitle, photo: peer.profileImageRepresentations, nameColor: .preset(.blue), @@ -1824,10 +1826,15 @@ final class ChannelAppearanceScreenComponent: Component { transition.setAlpha(view: buttonView, alpha: 1.0) } - let edgeEffectHeight: CGFloat = availableSize.height - buttonY + 36.0 - let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) - transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + let topEdgeEffectHeight: CGFloat = environment.navigationHeight + 24.0 + let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topEdgeEffectHeight)) + transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) + + let bottomEdgeEffectHeight: CGFloat = availableSize.height - buttonY + 36.0 + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomEdgeEffectHeight), size: CGSize(width: availableSize.width, height: bottomEdgeEffectHeight)) + transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition) let previousBounds = self.scrollView.bounds diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift index 1758ade1a1..1e923dfeee 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -215,32 +214,32 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { var insets: UIEdgeInsets let separatorHeight = UIScreenPixel - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(1)) var items: [ListViewItem] = [] for messageItem in item.messageItems.reversed() { let authorPeerId = messageItem.peerId - let replyAuthorPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(10)) + let replyAuthorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(10)) - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: messageItem.author, lastName: "", username: nil, phone: nil, photo: messageItem.photo, botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: messageItem.nameColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) + let replyMessageId = EngineMessage.Id(peerId: peerId, namespace: 0, id: 3) if let (replyAuthor, text, replyColor) = messageItem.reply { peers[replyAuthorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: replyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: replyColor, backgroundEmojiId: messageItem.backgroundEmojiId, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[replyAuthorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + messages[replyMessageId] = EngineRawMessage(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[replyAuthorPeerId], text: text, attributes: [], media: [], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) } - var media: [Media] = [] + var media: [EngineRawMedia] = [] if let (site, title, text) = messageItem.linkPreview, params.width > 320.0 { - media.append(TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))) + media.append(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent(url: "", displayUrl: "", hash: 0, type: nil, websiteName: site, title: title, text: text, embedUrl: nil, embedType: nil, embedSize: nil, duration: nil, author: nil, isMediaLargeByDefault: nil, imageIsVideoCover: false, image: nil, file: nil, story: nil, attributes: [], instantPage: nil)))) } - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)] : [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: messageItem.outgoing ? [] : [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: messageItem.reply != nil ? [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)] : [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift index dd7f756419..a857c63436 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorProfilePreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index c4ec862dc0..b3a1c22249 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -4,7 +4,6 @@ import Photos import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -31,7 +30,6 @@ import BundleIconComponent import Markdown import PeerNameColorItem import EmojiActionIconComponent -import TabSelectorComponent import WallpaperResources import EdgeEffect import TextFormat @@ -66,14 +64,11 @@ final class UserAppearanceScreenComponent: Component { } let context: AccountContext - let overNavigationContainer: UIView init( - context: AccountContext, - overNavigationContainer: UIView + context: AccountContext ) { self.context = context - self.overNavigationContainer = overNavigationContainer } static func ==(lhs: UserAppearanceScreenComponent, rhs: UserAppearanceScreenComponent) -> Bool { @@ -176,15 +171,27 @@ final class UserAppearanceScreenComponent: Component { private let scrollView: ScrollView private let actionButton = ComponentView() private let edgeEffectView: EdgeEffectView - - private let tabSelector = ComponentView() + enum Section: Int32 { case profile case name + + init(segmentIndex: Int) { + self = segmentIndex == 1 ? .name : .profile + } + + var segmentIndex: Int { + switch self { + case .profile: + return 0 + case .name: + return 1 + } + } } private var currentSection: Section = .profile - private let previewShadowView = UIImageView(image: generatePreviewShadowImage()) + private let previewEdgeEffectView = EdgeEffectView() private let profilePreview = ComponentView() private let profileColorSection = ComponentView() @@ -259,6 +266,7 @@ final class UserAppearanceScreenComponent: Component { self.edgeEffectView = EdgeEffectView() self.edgeEffectView.isUserInteractionEnabled = false + self.previewEdgeEffectView.isUserInteractionEnabled = false super.init(frame: frame) @@ -269,7 +277,7 @@ final class UserAppearanceScreenComponent: Component { self.scrollView.layer.addSublayer(self.topOverscrollLayer) - self.containerView.addSubview(self.previewShadowView) + self.containerView.addSubview(self.previewEdgeEffectView) self.addSubview(self.edgeEffectView) } @@ -329,6 +337,31 @@ final class UserAppearanceScreenComponent: Component { return true } + private func prepareForSectionSwitch(to updatedSection: Section) { + if (updatedSection == .name && self.selectedProfileGift != nil) || (updatedSection == .profile && self.selectedNameGift != nil) { + switch updatedSection { + case .profile: + self.selectedNameGift = nil + self.updatedPeerNameColor = nil + self.updatedPeerNameEmoji = nil + case .name: + self.selectedProfileGift = nil + self.updatedPeerProfileColor = nil + self.updatedPeerProfileEmoji = nil + self.updatedPeerStatus = nil + } + } + } + + func switchToSection(_ updatedSection: Section, transition: ComponentTransition = .easeInOut(duration: 0.3)) { + if self.currentSection == updatedSection { + return + } + self.prepareForSectionSwitch(to: updatedSection) + self.currentSection = updatedSection + self.state?.updated(transition: transition.withUserData(TransitionHint(animateTabChange: true))) + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { self.updateScrolling(transition: .immediate) } @@ -860,9 +893,9 @@ final class UserAppearanceScreenComponent: Component { self.previousEmojiSetupTimestamp = currentTimestamp self.emojiStatusSelectionController?.dismiss() - var selectedItems = Set() + var selectedItems = Set() if let currentFileId { - selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: currentFileId)) + selectedItems.insert(EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: currentFileId)) } let mappedSubject: EmojiPagerContentComponent.Subject @@ -1008,26 +1041,18 @@ final class UserAppearanceScreenComponent: Component { self.environment = environment if self.component == nil { - if let controller = environment.controller() as? UserAppearanceScreen, let focusOnItemTag = controller.focusOnItemTag { - switch focusOnItemTag { - case .profile: - self.currentSection = .profile - case .profileAddIcons: - self.currentSection = .profile - Queue.mainQueue().after(0.1) { - self.openEmojiSetup() + if let controller = environment.controller() as? UserAppearanceScreen { + self.currentSection = controller.selectedSection + + if let focusOnItemTag = controller.focusOnItemTag { + switch focusOnItemTag { + case .profileAddIcons, .nameAddIcons: + Queue.mainQueue().after(0.1) { + self.openEmojiSetup() + } + default: + break } - case .profileUseGift: - self.currentSection = .profile - case .name: - self.currentSection = .name - case .nameAddIcons: - self.currentSection = .name - Queue.mainQueue().after(0.1) { - self.openEmojiSetup() - } - case .nameUseGift: - self.currentSection = .name } } } @@ -1035,7 +1060,9 @@ final class UserAppearanceScreenComponent: Component { self.component = component self.state = state - transition.setFrame(view: component.overNavigationContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: environment.navigationHeight))) + if let controller = environment.controller() as? UserAppearanceScreen { + controller.updateSegmentedTitleView(theme: environment.theme, strings: environment.strings) + } let theme = environment.theme @@ -1113,7 +1140,7 @@ final class UserAppearanceScreenComponent: Component { if let presentationTheme { let resolvedWallpaper: Signal if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: component.context.account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: component.context.engine, network: component.context.account.network, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper } @@ -1165,61 +1192,6 @@ final class UserAppearanceScreenComponent: Component { previewTransition = .immediate } - let tabSelectorSize = self.tabSelector.update( - transition: transition, - component: AnyComponent( - TabSelectorComponent( - colors: TabSelectorComponent.Colors( - foreground: environment.theme.list.itemAccentColor, - selection: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.1), - normal: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.78), - simple: true - ), - theme: environment.theme, - customLayout: TabSelectorComponent.CustomLayout(font: Font.semibold(16.0)), - items: [ - TabSelectorComponent.Item(id: Section.profile.rawValue, title: environment.strings.ProfileColorSetup_TitleProfile), - TabSelectorComponent.Item(id: Section.name.rawValue, title: environment.strings.ProfileColorSetup_TitleName) - ], - selectedId: self.currentSection.rawValue, - setSelectedId: { [weak self] value in - guard let self else { - return - } - if let intValue = value.base as? Int32 { - let updatedSection = Section(rawValue: intValue) ?? .profile - if self.currentSection != updatedSection { - if (updatedSection == .name && self.selectedProfileGift != nil) || (updatedSection == .profile && self.selectedNameGift != nil) { - switch updatedSection { - case .profile: - self.selectedNameGift = nil - self.updatedPeerNameColor = nil - self.updatedPeerNameEmoji = nil - case .name: - self.selectedProfileGift = nil - self.updatedPeerProfileColor = nil - self.updatedPeerProfileEmoji = nil - self.updatedPeerStatus = nil - } - } - self.currentSection = updatedSection - self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true))) - } - } - } - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 44.0) - ) - let tabSelectorFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - tabSelectorSize.width) / 2.0), y: environment.statusBarHeight + 2.0 + floorToScreenPixels((environment.navigationHeight - environment.statusBarHeight - tabSelectorSize.height) / 2.0)), size: tabSelectorSize) - if let tabSelectorView = self.tabSelector.view { - if tabSelectorView.superview == nil { - component.overNavigationContainer.addSubview(tabSelectorView) - } - transition.setFrame(view: tabSelectorView, frame: tabSelectorFrame) - } - let bottomContentInset: CGFloat = 24.0 let bottomInset: CGFloat = 8.0 let sideInset: CGFloat = 16.0 + environment.safeInsets.left @@ -1230,8 +1202,7 @@ final class UserAppearanceScreenComponent: Component { var contentHeight: CGFloat = 0.0 let itemCornerRadius: CGFloat = 26.0 - - transition.setTintColor(view: self.previewShadowView, color: environment.theme.list.itemBlocksBackgroundColor) + let previewEdgeEffectHeight: CGFloat = 72.0 switch self.currentSection { case .profile: @@ -1282,8 +1253,10 @@ final class UserAppearanceScreenComponent: Component { } contentHeight += profilePreviewSize.height - 38.0 - transition.setFrame(view: self.previewShadowView, frame: profilePreviewFrame.insetBy(dx: -45.0, dy: -45.0)) - previewTransition.setAlpha(view: self.previewShadowView, alpha: !self.scrolledUp ? 1.0 : 0.0) + let previewEdgeEffectFrame = CGRect(origin: CGPoint(x: profilePreviewFrame.minX, y: profilePreviewFrame.maxY - 35.0), size: CGSize(width: profilePreviewFrame.width, height: previewEdgeEffectHeight)) + previewTransition.setFrame(view: self.previewEdgeEffectView, frame: previewEdgeEffectFrame) + self.previewEdgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: previewEdgeEffectFrame, edge: .top, edgeSize: previewEdgeEffectFrame.height, transition: previewTransition) + previewTransition.setAlpha(view: self.previewEdgeEffectView, alpha: !self.scrolledUp ? 1.0 : 0.0) var profileLogoContents: [AnyComponentWithIdentity] = [] profileLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( @@ -1303,7 +1276,8 @@ final class UserAppearanceScreenComponent: Component { return (TelegramTextAttributes.URL, contents) } ) - let previewFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.ProfileColorSetup_ProfileColorPreviewInfo, attributes: footerAttributes)) + let previewFooterRawText = environment.strings.ProfileColorSetup_ProfileColorPreviewInfo.replacingOccurrences(of: " >]", with: "\u{00A0}>]") + let previewFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(previewFooterRawText, attributes: footerAttributes)) if let range = previewFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 { previewFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: previewFooterText.string)) } @@ -1336,8 +1310,11 @@ final class UserAppearanceScreenComponent: Component { self.updatedPeerProfileEmoji = nil self.updatedPeerStatus = nil } - self.currentSection = .name - self.state?.updated(transition: .easeInOut(duration: 0.3).withUserData(TransitionHint(animateTabChange: true))) + if let controller = self.environment?.controller() as? UserAppearanceScreen { + controller.switchToSection(.name) + } else { + self.switchToSection(.name) + } } )), items: [ @@ -1596,7 +1573,7 @@ final class UserAppearanceScreenComponent: Component { let messageItem = PeerNameColorChatPreviewItem.MessageItem( outgoing: false, - peerId: EnginePeer.Id(namespace: peer.id.namespace, id: PeerId.Id._internalFromInt64Value(0)), + peerId: EnginePeer.Id(namespace: peer.id.namespace, id: EnginePeer.Id.Id._internalFromInt64Value(0)), author: peer.compactDisplayTitle, photo: peer.profileImageRepresentations, nameColor: resolvedState.nameColor, @@ -1656,8 +1633,10 @@ final class UserAppearanceScreenComponent: Component { } contentHeight += namePreviewSize.height - 38.0 - transition.setFrame(view: self.previewShadowView, frame: namePreviewFrame.insetBy(dx: -45.0, dy: -45.0)) - previewTransition.setAlpha(view: self.previewShadowView, alpha: !self.scrolledUp ? 1.0 : 0.0) + let previewEdgeEffectFrame = CGRect(origin: CGPoint(x: namePreviewFrame.minX, y: namePreviewFrame.maxY - UIScreenPixel), size: CGSize(width: namePreviewFrame.width, height: previewEdgeEffectHeight)) + previewTransition.setFrame(view: self.previewEdgeEffectView, frame: previewEdgeEffectFrame) + self.previewEdgeEffectView.update(content: environment.theme.list.itemBlocksBackgroundColor, alpha: 1.0, rect: previewEdgeEffectFrame, edge: .top, edgeSize: previewEdgeEffectFrame.height, transition: previewTransition) + previewTransition.setAlpha(view: self.previewEdgeEffectView, alpha: !self.scrolledUp ? 1.0 : 0.0) let nameColorSectionSize = self.nameColorSection.update( transition: transition, @@ -1998,11 +1977,20 @@ final class UserAppearanceScreenComponent: Component { public class UserAppearanceScreen: ViewControllerComponentContainer { private let context: AccountContext fileprivate let focusOnItemTag: UserAppearanceEntryTag? - - private let overNavigationContainer: UIView + fileprivate var selectedSection: UserAppearanceScreenComponent.View.Section + private let segmentedTitleView: ItemListControllerSegmentedTitleView private var didSetReady: Bool = false + private static func initialSection(for focusOnItemTag: UserAppearanceEntryTag?) -> UserAppearanceScreenComponent.View.Section { + switch focusOnItemTag { + case .name, .nameAddIcons, .nameUseGift: + return .name + case .profile, .profileAddIcons, .profileUseGift, nil: + return .profile + } + } + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, @@ -2010,21 +1998,34 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { ) { self.context = context self.focusOnItemTag = focusOnItemTag - - self.overNavigationContainer = SparseContainerView() - + + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + let initialSection = UserAppearanceScreen.initialSection(for: focusOnItemTag) + self.selectedSection = initialSection + self.segmentedTitleView = ItemListControllerSegmentedTitleView( + theme: presentationData.theme, + segments: [ + presentationData.strings.ProfileColorSetup_TitleProfile, + presentationData.strings.ProfileColorSetup_TitleName + ], + selectedIndex: initialSection.segmentIndex + ) + super.init(context: context, component: UserAppearanceScreenComponent( - context: context, - overNavigationContainer: self.overNavigationContainer + context: context ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) - + self.automaticallyControlPresentationContextLayout = false - + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.title = "" self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.titleView = self.segmentedTitleView + + self.segmentedTitleView.indexUpdated = { [weak self] index in + self?.switchToSection(UserAppearanceScreenComponent.View.Section(segmentIndex: index)) + } self.ready.set(.never()) @@ -2042,9 +2043,25 @@ public class UserAppearanceScreen: ViewControllerComponentContainer { return componentView.attemptNavigation(complete: complete) } + } + + fileprivate func updateSegmentedTitleView(theme: PresentationTheme, strings: PresentationStrings) { + self.segmentedTitleView.theme = theme + self.segmentedTitleView.segments = [ + strings.ProfileColorSetup_TitleProfile, + strings.ProfileColorSetup_TitleName + ] + self.segmentedTitleView.index = self.selectedSection.segmentIndex + } + + fileprivate func switchToSection(_ section: UserAppearanceScreenComponent.View.Section) { + self.selectedSection = section + self.segmentedTitleView.index = section.segmentIndex - if let navigationBar = self.navigationBar { - navigationBar.customOverBackgroundContentView.insertSubview(self.overNavigationContainer, at: 0) + if let componentView = self.node.hostView.componentView as? UserAppearanceScreenComponent.View { + componentView.switchToSection(section) + } else { + self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3)) } } @@ -2161,32 +2178,3 @@ final class TopBottomCornersComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } - -private func generatePreviewShadowImage() -> UIImage { - let cornerRadius: CGFloat = 26.0 - let shadowInset: CGFloat = 45.0 - - let side = (cornerRadius + 5.0) * 2.0 - let fullSide = shadowInset * 2.0 + side - - return generateImage(CGSize(width: fullSide, height: fullSide), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let edgeHeight = shadowInset + cornerRadius + 11.0 - context.clip(to: CGRect(x: shadowInset, y: size.height - edgeHeight, width: side, height: edgeHeight)) - - let rect = CGRect(origin: .zero, size: CGSize(width: fullSide, height: fullSide)).insetBy(dx: shadowInset + 1.0, dy: shadowInset + 2.0) - let path = CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil) - - let drawShadow = { - context.addPath(path) - context.setShadow(offset: CGSize(), blur: 80.0, color: UIColor.black.cgColor) - context.setFillColor(UIColor.black.cgColor) - context.fillPath() - } - - drawShadow() - drawShadow() - drawShadow() - })!.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius + 5), topCapHeight: Int(shadowInset + cornerRadius + 5)).withRenderingMode(.alwaysTemplate) -} diff --git a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift index 0cb04d686f..d0fa4a8c28 100644 --- a/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerSelectionScreen/Sources/PeerSelectionScreen.swift @@ -522,7 +522,9 @@ final class PeerSelectionScreenComponent: Component { let timingFunction: String switch curve { case .easeInOut: - timingFunction = CAMediaTimingFunctionName.easeOut.rawValue + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case .easeIn: + timingFunction = CAMediaTimingFunctionName.easeIn.rawValue case .linear: timingFunction = CAMediaTimingFunctionName.linear.rawValue case .spring: diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift index a155b1c6f0..c4247157e5 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramPresentationData -import Postbox import TelegramCore import ItemListUI import EmojiStatusComponent @@ -332,7 +331,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 1.0), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame var animationContent: EmojiStatusComponent.AnimationContent? diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift index 697a6a5d6e..2cb32a6233 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -58,7 +57,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry { } case demoHeader(String) - case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: Peer?) + case demoMessage(wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: EnginePeer?) case demoDescription(String) case quickReaction(String, MessageReaction.Reaction, AvailableReactions) case quickReactionDescription(String) @@ -160,7 +159,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry { nameDisplayOrder: nameDisplayOrder, availableReactions: availableReactions, reaction: reaction, - accountPeer: accountPeer, + accountPeer: accountPeer?._asPeer(), toggleReaction: { arguments.toggleReaction() } @@ -187,7 +186,7 @@ private func quickReactionSetupControllerEntries( reactionSettings: ReactionSettings, state: QuickReactionSetupControllerState, isPremium: Bool, - accountPeer: Peer? + accountPeer: EnginePeer? ) -> [QuickReactionSetupControllerEntry] { var entries: [QuickReactionSetupControllerEntry] = [] @@ -245,10 +244,10 @@ public func quickReactionSetupController( } ) - let settings = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + let settings = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) |> map { preferencesView -> ReactionSettings in let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default @@ -275,7 +274,7 @@ public func quickReactionSetupController( reactionSettings: settings, state: state, isPremium: isPremium, - accountPeer: accountPeer?._asPeer() + accountPeer: accountPeer ) let controllerState = ItemListControllerState( @@ -310,7 +309,7 @@ public func quickReactionSetupController( ) |> take(1) |> deliverOnMainQueue).start(next: { settings, availableReactions in - var currentSelectedFileId: MediaId? + var currentSelectedFileId: EngineMedia.Id? switch settings.quickReaction { case .builtin: if let availableReactions = availableReactions { @@ -320,7 +319,7 @@ public func quickReactionSetupController( } } case let .custom(fileId): - currentSelectedFileId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + currentSelectedFileId = EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId) case .stars: if let availableReactions = availableReactions { if let reaction = availableReactions.reactions.first(where: { $0.value == settings.quickReaction }) { @@ -330,7 +329,7 @@ public func quickReactionSetupController( } } - var selectedItems = Set() + var selectedItems = Set() if let currentSelectedFileId = currentSelectedFileId { selectedItems.insert(currentSelectedFileId) } diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift index 48eb6f1d4d..7b7523dfee 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -27,10 +26,10 @@ class ReactionChatPreviewItem: ListViewItem, ItemListItem { let nameDisplayOrder: PresentationPersonNameOrder let availableReactions: AvailableReactions? let reaction: MessageReaction.Reaction? - let accountPeer: Peer? + let accountPeer: EngineRawPeer? let toggleReaction: () -> Void - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: Peer?, toggleReaction: @escaping () -> Void) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: EngineRawPeer?, toggleReaction: @escaping () -> Void) { self.context = context self.theme = theme self.strings = strings @@ -301,17 +300,17 @@ class ReactionChatPreviewItemNode: ListViewItemNode { let separatorHeight = UIScreenPixel let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 - let userPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)) + let userPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)) let chatPeerId = userPeerId - var peers = SimpleDictionary() - let messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + let messages = EngineSimpleDictionary() peers[userPeerId] = TelegramUser(id: userPeerId, accessHash: nil, firstName: item.strings.Settings_QuickReactionSetup_DemoMessageAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) let messageText = item.strings.Settings_QuickReactionSetup_DemoMessageText - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let reaction = item.reaction { var recentPeers: [ReactionsMessageAttribute.RecentPeer] = [] if let accountPeer = item.accountPeer { @@ -321,7 +320,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { attributes.append(ReactionsMessageAttribute(canViewList: false, isTags: false, reactions: [MessageReaction(value: reaction, count: 1, chosenOrder: 0)], recentPeers: recentPeers, topPeers: [])) } - let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[userPeerId], text: messageText, attributes: attributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: item.availableReactions, accountPeer: item.accountPeer, isCentered: true, isPreview: true, isStandalone: false, rank: nil, rankRole: nil) + let messageItem = item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: chatPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[userPeerId], text: messageText, attributes: attributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])], theme: item.theme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: item.availableReactions, accountPeer: item.accountPeer, isCentered: true, isPreview: item.reaction == nil, isStandalone: false, rank: nil, rankRole: nil) var node: ListViewItemNode? if let current = currentNode { diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD index 99a8b1bbc7..63ff3980c5 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift index b78cc739f1..466a1b8bd8 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -50,7 +49,7 @@ public func quickReplyNameAlertController(context: AccountContext, updatedPresen component: AnyComponent( AlertInputFieldComponent( context: context, - initialValue: nil, + initialValue: value, placeholder: strings.QuickReply_ShortcutPlaceholder, characterLimit: characterLimit, hasClearButton: false, @@ -59,6 +58,21 @@ public func quickReplyNameAlertController(context: AccountContext, updatedPresen autocorrectionType: .no, isInitiallyFocused: true, externalState: inputState, + shouldChangeText: { updatedText in + if updatedText.isEmpty { + return true + } + for scalar in updatedText.unicodeScalars { + if scalar.value == 0x5f || scalar.value == 0x200c || scalar.value == 0xb7 || (scalar.value >= 0xd80 && scalar.value <= 0xdff) { + continue + } + if CharacterSet.letters.contains(scalar) || CharacterSet.decimalDigits.contains(scalar) { + continue + } + return false + } + return true + }, returnKeyAction: { applyImpl?() } diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD index d6fd0655ff..7f23c3678e 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/GradientBackground", diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift index 6af509eee1..86a9ecb6ff 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import AccountContext @@ -233,7 +232,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { switch wallpaper { case .builtin: self.imageNode.alpha = 1.0 - self.imageNode.setSignal(settingsBuiltinWallpaperImage(account: context.account, thumbnail: true)) + self.imageNode.setSignal(settingsBuiltinWallpaperImage(thumbnail: true)) let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets())) apply() self.isLoadedDisposable.set(nil) @@ -282,8 +281,8 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { } let anyStatus = combineLatest(queue: .mainQueue(), - context.account.postbox.mediaBox.resourceStatus(convertedFullRepresentations[0].reference.resource, approximateSynchronousValue: true), - context.sharedContext.accountManager.mediaBox.resourceStatus(convertedFullRepresentations[0].reference.resource, approximateSynchronousValue: true) + context.engine.resources.status(resource: EngineMediaResource(convertedFullRepresentations[0].reference.resource), approximateSynchronousValue: true), + context.sharedContext.accountManager.resources.status(resource: EngineMediaResource(convertedFullRepresentations[0].reference.resource), approximateSynchronousValue: true) ) |> map { a, b -> Bool in switch a { @@ -368,7 +367,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { } self.animatedStickerNode = animatedStickerNode self.emojiContainerNode.addSubnode(animatedStickerNode) - let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) @@ -376,7 +375,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { animatedStickerNode.autoplay = true animatedStickerNode.visibility = true - self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + self.stickerFetchedDisposable.set(context.engine.resources.fetch(reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource), userLocation: .other, userContentType: .other).start()) // let thumbnailDimensions = PixelDimensions(width: 512, height: 512) // self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD index 49b5ed0d22..98a1be1ec5 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD @@ -17,6 +17,7 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramPresentationData", "//submodules/AccountContext", + "//submodules/ItemListUI", "//submodules/PresentationDataUtils", "//submodules/WallpaperBackgroundNode", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift index 762ea62da1..854e812593 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift @@ -1,13 +1,13 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore import TelegramPresentationData import TelegramUIPreferences import AccountContext +import ItemListUI import PresentationDataUtils import MediaResources import WallpaperGalleryScreen @@ -58,7 +58,7 @@ public final class ThemeAccentColorController: ViewController { return self._ready } - private let segmentedTitleView: ThemeColorSegmentedTitleView + private let segmentedTitleView: ItemListControllerSegmentedTitleView private var applyDisposable = MetaDisposable() @@ -73,7 +73,11 @@ public final class ThemeAccentColorController: ViewController { let section: ThemeColorSection = .background self.section = section - self.segmentedTitleView = ThemeColorSegmentedTitleView(theme: self.presentationData.theme, strings: self.presentationData.strings, selectedSection: section) + self.segmentedTitleView = ItemListControllerSegmentedTitleView(theme: self.presentationData.theme, segments: [ + self.presentationData.strings.Theme_Colors_Background, + self.presentationData.strings.Theme_Colors_Accent, + self.presentationData.strings.Theme_Colors_Messages + ], selectedIndex: section.rawValue) if case .background = mode { self.initialBackgroundColor = randomBackgroundColors.randomElement().flatMap { UIColor(rgb: UInt32(bitPattern: $0)) } @@ -81,38 +85,43 @@ public final class ThemeAccentColorController: ViewController { self.initialBackgroundColor = nil } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)) + super.init(navigationBarPresentationData: nil) //NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) self.navigationPresentation = .modal + self._hasGlassStyle = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - self.segmentedTitleView.sectionUpdated = { [weak self] section in - if let strongSelf = self { - strongSelf.controllerNode.updateSection(section) - } - } - - self.segmentedTitleView.shouldUpdateSection = { [weak self] section, f in - guard let strongSelf = self else { - f(false) + self.segmentedTitleView.indexUpdated = { [weak self] index in + guard let strongSelf = self, let section = ThemeColorSection(rawValue: index) else { return } + if strongSelf.segmentedTitleView.index == index { + return + } + + let updateSection: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.segmentedTitleView.index = index + strongSelf.controllerNode.updateSection(section) + } + guard section == .background else { - f(true) + updateSection() return } if strongSelf.controllerNode.requiresWallpaperChange { let controller = textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Theme_Colors_ColorWallpaperWarning, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - f(false) }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Theme_Colors_ColorWallpaperWarningProceed, action: { - f(true) + updateSection() })]) strongSelf.present(controller, in: .window(.root)) } else { - f(true) + updateSection() } } @@ -120,8 +129,7 @@ public final class ThemeAccentColorController: ViewController { self.title = self.presentationData.strings.Wallpaper_Title self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } else { - self.navigationItem.titleView = self.segmentedTitleView - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + //self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) } } @@ -187,14 +195,14 @@ public final class ThemeAccentColorController: ViewController { let resource = file.file.resource var data: Data? - if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { data = maybeData - } else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + } else if let path = strongSelf.context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { data = maybeData } - + if let data = data { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) prepareWallpaper = .complete() } else { prepareWallpaper = .complete() @@ -374,19 +382,18 @@ public final class ThemeAccentColorController: ViewController { }, ready: self._ready) self.controllerNode.themeUpdated = { [weak self] theme in if let strongSelf = self { - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationTheme: theme, presentationStrings: strongSelf.presentationData.strings), transition: .immediate) strongSelf.segmentedTitleView.theme = theme } } self.controllerNode.requestSectionUpdate = { [weak self] section in if let strongSelf = self { - strongSelf.segmentedTitleView.setIndex(section.rawValue, animated: true) + strongSelf.segmentedTitleView.index = section.rawValue } } let _ = (combineLatest( self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) |> take(1), - telegramWallpapers(postbox: context.account.postbox, network: context.account.network) |> take(1) + context.engine.themes.wallpapers() |> take(1) ) |> deliverOnMainQueue).start(next: { [weak self] sharedData, wallpapers in guard let strongSelf = self else { @@ -635,6 +642,25 @@ public final class ThemeAccentColorController: ViewController { public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + let navigationLayout = self.navigationLayout(layout: layout) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout.navigationFrame.maxY, transition: transition) + + if case .background = self.mode { + if self.segmentedTitleView.superview != nil { + self.segmentedTitleView.removeFromSuperview() + } + } else { + if self.segmentedTitleView.superview == nil { + self.view.addSubview(self.segmentedTitleView) + } + + let segmentedTitleFrame = CGRect( + x: layout.safeInsets.left, + y: navigationLayout.navigationFrame.maxY - navigationLayout.defaultContentHeight, + width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, + height: navigationLayout.defaultContentHeight + ) + transition.updateFrame(view: self.segmentedTitleView, frame: segmentedTitleFrame) + } } } diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 75b2184144..6a0523e9bd 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -361,6 +360,9 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate self.backgroundContainerNode.addSubnode(self.backgroundWrapperNode) self.backgroundWrapperNode.addSubnode(self.backgroundNode) + self.patternButtonNode.dark = true + self.colorsButtonNode.dark = true + self.patternButtonNode.addTarget(self, action: #selector(self.togglePattern), forControlEvents: .touchUpInside) self.colorsButtonNode.addTarget(self, action: #selector(self.toggleColors), forControlEvents: .touchUpInside) self.playButtonNode.addTarget(self, action: #selector(self.playPressed), forControlEvents: .touchUpInside) @@ -602,8 +604,6 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate if let strongSelf = self { strongSelf.patternPanelNode.serviceBackgroundColor = color strongSelf.pageControlBackgroundNode.backgroundColor = color - strongSelf.patternButtonNode.buttonColor = color - strongSelf.colorsButtonNode.buttonColor = color strongSelf.playButtonBackgroundNode.updateColor(color: color, transition: .immediate) } }) @@ -626,6 +626,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.isPagingEnabled = true self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false + self.pageControlNode.setPage(0.0) self.colorPanelNode.view.disablesInteractiveTransitionGestureRecognizer = true self.patternPanelNode.view.disablesInteractiveTransitionGestureRecognizer = true @@ -868,6 +870,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in }, openStarsTopup: { _ in @@ -895,7 +898,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate context: self.context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: .chatList(ChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: MessageIndex(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), + index: .chatList(EngineChatListIndex(pinningIndex: isPinned ? 0 : nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), timestamp: timestamp))), content: .peer(ChatListItemContent.PeerData( messages: [ EngineMessage( @@ -961,11 +964,11 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate } let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) - let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer1: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer2: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer3: EnginePeer = .channel(TelegramChannel(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil, verificationIconFileId: nil, sendPaidMessageStars: nil, linkedMonoforumId: nil)) + let peer3Author: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) + let peer4: EnginePeer = .user(TelegramUser(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) let timestamp = self.referenceTimestamp @@ -1049,43 +1052,43 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate let headerItem = self.context.sharedContext.makeChatMessageDateHeaderItem(context: self.context, timestamp: self.referenceTimestamp, theme: self.theme, strings: self.presentationData.strings, wallpaper: self.wallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder) var items: [ListViewItem] = [] - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[otherPeerId] = TelegramUser(id: otherPeerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) - var sampleMessages: [Message] = [] + var sampleMessages: [EngineRawMessage] = [] - let message1 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message1 = EngineRawMessage(stableId:1, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_4_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message1) - let message2 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = EngineRawMessage(stableId:2, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_5_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message2) - let message3 = Message(stableId: 3, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message3 = EngineRawMessage(stableId:3, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:3), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_6_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message3) - let message4 = Message(stableId: 4, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message4 = EngineRawMessage(stableId:4, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:4), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66003, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_7_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message4.id] = message4 sampleMessages.append(message4) - let message5 = Message(stableId: 5, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message5 = EngineRawMessage(stableId:5, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:5), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66004, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_1_Text, attributes: [ReplyMessageAttribute(messageId: message4.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messages[message5.id] = message5 sampleMessages.append(message5) let waveformBase64 = "DAAOAAkACQAGAAwADwAMABAADQAPABsAGAALAA0AGAAfABoAHgATABgAGQAYABQADAAVABEAHwANAA0ACQAWABkACQAOAAwACQAfAAAAGQAVAAAAEwATAAAACAAfAAAAHAAAABwAHwAAABcAGQAAABQADgAAABQAHwAAAB8AHwAAAAwADwAAAB8AEwAAABoAFwAAAB8AFAAAAAAAHwAAAAAAHgAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAHwAAAAAAAAA=" let voiceAttributes: [TelegramMediaFileAttribute] = [.Audio(isVoice: true, duration: 23, title: nil, performer: nil, waveform: Data(base64Encoded: waveformBase64)!)] - let voiceMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) + let voiceMedia = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: voiceAttributes, alternativeRepresentations: []) - let message6 = Message(stableId: 6, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message6 = EngineRawMessage(stableId:6, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:6), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66005, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [voiceMedia], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message6) - let message7 = Message(stableId: 7, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message7 = EngineRawMessage(stableId:7, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id:7), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66006, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_2_Text, attributes: [ReplyMessageAttribute(messageId: message5.id, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message7) - let message8 = Message(stableId: 8, stableVersion: 0, id: MessageId(peerId: otherPeerId, namespace: 0, id: 8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message8 = EngineRawMessage(stableId:8, stableVersion: 0, id: EngineMessage.Id(peerId: otherPeerId, namespace: 0, id:8), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66007, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: self.presentationData.strings.Appearance_ThemePreview_Chat_3_Text, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) sampleMessages.append(message8) items = sampleMessages.reversed().map { message in @@ -1147,7 +1150,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate self.messageNodes = messageNodes } - let toolbarHeight = 49.0 + layout.intrinsicInsets.bottom + let toolbarHeight = 52.0 + layout.intrinsicInsets.bottom var relativeOffset: CGFloat = 0.0 if !self.state.colorPanelCollapsed && self.state.section == .messages { relativeOffset = (CGFloat(self.state.selectedColor) / CGFloat(max(1, self.state.messagesColors.count))) * (self.colorPanelNode.frame.height + toolbarHeight + 144.0) * -1.0 @@ -1208,13 +1211,13 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate if case let .peer(peer) = self.resultMode, case .user = peer, !self.state.displayPatternPanel { toolbarBottomInset += 58.0 } - let toolbarHeight = 49.0 + toolbarBottomInset + let toolbarHeight = 52.0 + toolbarBottomInset transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight))) - self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition) + self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: 52.0), layout: layout, transition: transition) var bottomInset = toolbarHeight - let standardInputHeight = layout.deviceMetrics.keyboardHeight(inLandscape: false) - let inputFieldPanelHeight: CGFloat = 47.0 + let standardInputHeight = layout.deviceMetrics.keyboardHeight(inLandscape: false) + 17.0 + let inputFieldPanelHeight = WallpaperColorPanelNode.topControlsHeight(for: layout.size.width) let colorPanelHeight = max(standardInputHeight, layout.inputHeight ?? 0.0) - bottomInset + inputFieldPanelHeight var colorPanelOffset: CGFloat = 0.0 @@ -1250,7 +1253,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate transition.updateBounds(node: self.messagesContainerNode, bounds: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)) transition.updatePosition(node: self.messagesContainerNode, position: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height).center) - let backgroundSize = CGSize(width: bounds.width, height: bounds.height - (colorPanelHeight - colorPanelOffset)) + let backgroundSize = CGSize(width: bounds.width, height: bounds.height - (colorPanelHeight - colorPanelOffset) + 20.0) transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(), size: backgroundSize)) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size)) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeColorSegmentedTitleView.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeColorSegmentedTitleView.swift deleted file mode 100644 index a8d182994d..0000000000 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeColorSegmentedTitleView.swift +++ /dev/null @@ -1,67 +0,0 @@ -import Foundation -import UIKit -import SegmentedControlNode -import TelegramPresentationData - -final class ThemeColorSegmentedTitleView: UIView { - private let segmentedControlNode: SegmentedControlNode - - var theme: PresentationTheme { - didSet { - self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) - } - } - - var index: Int { - get { - return self.segmentedControlNode.selectedIndex - } - set { - self.segmentedControlNode.selectedIndex = newValue - } - } - - func setIndex(_ index: Int, animated: Bool) { - self.segmentedControlNode.setSelectedIndex(index, animated: animated) - } - - var sectionUpdated: ((ThemeColorSection) -> Void)? - var shouldUpdateSection: ((ThemeColorSection, @escaping (Bool) -> Void) -> Void)? - - init(theme: PresentationTheme, strings: PresentationStrings, selectedSection: ThemeColorSection) { - self.theme = theme - - let sections = [strings.Theme_Colors_Background, strings.Theme_Colors_Accent, strings.Theme_Colors_Messages] - self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: sections.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedSection.rawValue) - - super.init(frame: CGRect()) - - self.segmentedControlNode.selectedIndexChanged = { [weak self] index in - if let section = ThemeColorSection(rawValue: index) { - self?.sectionUpdated?(section) - } - } - - self.segmentedControlNode.selectedIndexShouldChange = { [weak self] index, f in - if let section = ThemeColorSection(rawValue: index) { - self?.shouldUpdateSection?(section, f) - } else { - f(false) - } - } - - self.addSubnode(self.segmentedControlNode) - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - - let size = self.bounds.size - let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width + 20.0), transition: .immediate) - self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: floor((size.height - controlSize.height) / 2.0)), size: controlSize) - } -} diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD index 7e7c767224..24d0726a8a 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/MergeLists", diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift index c3fda9e89b..ed537cfedb 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import MergeLists @@ -462,7 +461,7 @@ private final class ThemeCarouselThemeItemIconNode: ListViewItemNode { } strongSelf.animatedStickerNode = animatedStickerNode strongSelf.emojiContainerNode.insertSubnode(animatedStickerNode, belowSubnode: strongSelf.placeholderNode) - let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + let pathPrefix = item.context.engine.resources.shortLivedResourceCachePathPrefix(id: EngineMediaResource.Id(file.resource.id)) animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: item.context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) @@ -470,7 +469,7 @@ private final class ThemeCarouselThemeItemIconNode: ListViewItemNode { animatedStickerNode.autoplay = true animatedStickerNode.visibility = strongSelf.visibilityStatus - strongSelf.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: item.context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + strongSelf.stickerFetchedDisposable.set(item.context.engine.resources.fetch(reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource), userLocation: .other, userContentType: .sticker).start()) let thumbnailDimensions = PixelDimensions(width: 512, height: 512) strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) diff --git a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD index f0350af4f8..1c8a5b72df 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD +++ b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/Display", "//submodules/AsyncDisplayKit", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", "//submodules/MergeLists", diff --git a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift index 4b8a806e25..3c2f83417d 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem/Sources/ThemeSettingsThemeItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import MergeLists @@ -660,7 +659,7 @@ public class ThemeSettingsThemeItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD index 38fdb23587..e3713f6ab9 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/Display", "//submodules/AsyncDisplayKit", - "//submodules/Postbox", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/TelegramCore", "//submodules/TelegramPresentationData", diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift index 28aee7a2b2..28ae629e05 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift index f67d1f3b13..8ae722c397 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD index 45b7b88b4e..9737db2810 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD @@ -32,6 +32,9 @@ swift_library( "//submodules/LegacyMediaPickerUI", "//submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode", "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassControls", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPanelNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPanelNode.swift index b4f8d02740..35fbca796e 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPanelNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPanelNode.swift @@ -3,31 +3,25 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import Display +import ComponentFlow import TelegramPresentationData import HexColor +import GlassBackgroundComponent -private var currentTextInputBackgroundImage: (UIColor, UIColor, CGFloat, UIImage)? -private func textInputBackgroundImage(fieldColor: UIColor, strokeColor: UIColor, diameter: CGFloat) -> UIImage? { - if let current = currentTextInputBackgroundImage { - if current.0.isEqual(fieldColor) && current.1.isEqual(strokeColor) && current.2.isEqual(to: diameter) { - return current.3 - } +private final class ColorPanelGlassBackgroundNode: ASDisplayNode { + override init() { + super.init() + + self.setViewBlock({ + return GlassBackgroundView() + }) + self.isUserInteractionEnabled = false } - - let image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in - context.clear(CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) - context.setFillColor(fieldColor.cgColor) - context.fillEllipse(in: CGRect(x: 0.0, y: 0.0, width: diameter, height: diameter)) - context.setStrokeColor(strokeColor.cgColor) - let strokeWidth: CGFloat = 1.0 - context.setLineWidth(strokeWidth) - context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: diameter - strokeWidth, height: diameter - strokeWidth)) - })?.stretchableImage(withLeftCapWidth: Int(diameter) / 2, topCapHeight: Int(diameter) / 2) - if let image = image { - currentTextInputBackgroundImage = (fieldColor, strokeColor, diameter, image) - return image - } else { - return nil + + func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, isInteractive: Bool = false, transition: ContainedViewLayoutTransition) { + if let view = self.view as? GlassBackgroundView { + view.update(size: size, cornerRadius: cornerRadius, isDark: isDark, tintColor: .init(kind: .panel), isInteractive: isInteractive, transition: ComponentTransition(transition)) + } } } @@ -37,22 +31,23 @@ private func generateSwatchBorderImage(theme: PresentationTheme) -> UIImage? { private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { private var theme: PresentationTheme - + private let swatchNode: ASDisplayNode + private let swatchBackgroundNode: ColorPanelGlassBackgroundNode private let borderNode: ASImageNode private let removeButton: HighlightableButtonNode - private let textBackgroundNode: ASImageNode + private let textBackgroundNode: ColorPanelGlassBackgroundNode private let selectionNode: ASDisplayNode let textFieldNode: TextFieldNode private let measureNode: ImmediateTextNode private let prefixNode: ASTextNode - + private var gestureRecognizer: UITapGestureRecognizer? - + var colorChanged: ((UIColor, Bool) -> Void)? var colorRemoved: (() -> Void)? var colorSelected: (() -> Void)? - + private var color: UIColor? private var isDefault = false { @@ -60,69 +55,70 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.updateSelectionVisibility() } } - + var isRemovable: Bool = false { didSet { self.removeButton.isUserInteractionEnabled = self.isRemovable } } - + private var previousIsDefault: Bool? private var previousColor: UIColor? private var validLayout: (CGSize, Bool)? - + private var skipEndEditing = false private let displaySwatch: Bool - + init(theme: PresentationTheme, displaySwatch: Bool = true) { self.theme = theme self.displaySwatch = displaySwatch - - self.textBackgroundNode = ASImageNode() - self.textBackgroundNode.image = textInputBackgroundImage(fieldColor: theme.chat.inputPanel.inputBackgroundColor, strokeColor: theme.chat.inputPanel.inputStrokeColor, diameter: 33.0) - self.textBackgroundNode.displayWithoutProcessing = true - self.textBackgroundNode.displaysAsynchronously = false - + + self.textBackgroundNode = ColorPanelGlassBackgroundNode() + self.selectionNode = ASDisplayNode() self.selectionNode.backgroundColor = theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.2) self.selectionNode.cornerRadius = 3.0 self.selectionNode.isUserInteractionEnabled = false - + self.textFieldNode = TextFieldNode() self.measureNode = ImmediateTextNode() - + self.prefixNode = ASTextNode() self.prefixNode.attributedText = NSAttributedString(string: "#", font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputTextColor) - + self.swatchNode = ASDisplayNode() self.swatchNode.cornerRadius = 10.5 - + self.swatchNode.clipsToBounds = true + + self.swatchBackgroundNode = ColorPanelGlassBackgroundNode() + self.borderNode = ASImageNode() self.borderNode.displaysAsynchronously = false self.borderNode.displayWithoutProcessing = true self.borderNode.image = generateSwatchBorderImage(theme: theme) - + self.removeButton = HighlightableButtonNode() self.removeButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRemoveIcon"), color: theme.chat.inputPanel.inputControlColor), for: .normal) - + super.init() - + self.addSubnode(self.textBackgroundNode) self.addSubnode(self.selectionNode) self.addSubnode(self.textFieldNode) self.addSubnode(self.prefixNode) + self.addSubnode(self.swatchBackgroundNode) self.addSubnode(self.swatchNode) self.addSubnode(self.borderNode) self.addSubnode(self.removeButton) - + self.removeButton.addTarget(self, action: #selector(self.removePressed), forControlEvents: .touchUpInside) } - + override func didLoad() { super.didLoad() - + self.textFieldNode.textField.font = Font.regular(17.0) self.textFieldNode.textField.textColor = self.theme.chat.inputPanel.inputTextColor self.textFieldNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance @@ -134,26 +130,24 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.textFieldNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged) self.textFieldNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) self.textFieldNode.textField.tintColor = self.theme.list.itemAccentColor - + let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapped(_:))) self.view.addGestureRecognizer(gestureRecognizer) self.gestureRecognizer = gestureRecognizer } - + func updateTheme(_ theme: PresentationTheme) { self.theme = theme - - self.textBackgroundNode.image = textInputBackgroundImage(fieldColor: self.theme.chat.inputPanel.inputBackgroundColor, strokeColor: self.theme.chat.inputPanel.inputStrokeColor, diameter: 33.0) - + self.textFieldNode.textField.textColor = self.isDefault ? self.theme.chat.inputPanel.inputPlaceholderColor : self.theme.chat.inputPanel.inputTextColor self.textFieldNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance self.textFieldNode.textField.tintColor = self.theme.list.itemAccentColor - + self.selectionNode.backgroundColor = theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.2) self.borderNode.image = generateSwatchBorderImage(theme: theme) self.updateBorderVisibility() } - + func setColor(_ color: UIColor, isDefault: Bool = false, update: Bool = true, ended: Bool = true) { self.color = color self.isDefault = isDefault @@ -169,7 +163,7 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.swatchNode.backgroundColor = color self.updateBorderVisibility() } - + private func updateBorderVisibility() { guard let color = self.swatchNode.backgroundColor else { return @@ -181,21 +175,21 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.borderNode.alpha = 0.0 } } - + @objc private func removePressed() { if self.textFieldNode.textField.isFirstResponder { self.skipEndEditing = true } - + self.colorRemoved?() } - + @objc private func tapped(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.colorSelected?() } } - + @objc internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { var updated = textField.text ?? "" updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string) @@ -205,28 +199,28 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil { textField.text = updated.uppercased() textField.textColor = self.theme.chat.inputPanel.inputTextColor - + if updated.count == 6, let color = UIColor(hexString: updated) { self.setColor(color) } - + if let (size, _) = self.validLayout { self.updateSelectionLayout(size: size, transition: .immediate) } } return false } - + @objc func textFieldTextChanged(_ sender: UITextField) { if let color = self.colorFromCurrentText() { self.setColor(color) } - + if let (size, _) = self.validLayout { self.updateSelectionLayout(size: size, transition: .immediate) } } - + @objc func textFieldShouldReturn(_ textField: UITextField) -> Bool { self.skipEndEditing = true if let color = self.colorFromCurrentText() { @@ -237,7 +231,7 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { self.textFieldNode.textField.resignFirstResponder() return false } - + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { self.skipEndEditing = false self.previousColor = self.color @@ -247,7 +241,7 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { return true } - + @objc func textFieldDidEndEditing(_ textField: UITextField) { if !self.skipEndEditing { if let color = self.colorFromCurrentText() { @@ -257,13 +251,13 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { } } } - + func setSkipEndEditingIfNeeded() { if self.textFieldNode.textField.isFirstResponder && self.colorFromCurrentText() != nil { self.skipEndEditing = true } } - + private func colorFromCurrentText() -> UIColor? { if let text = self.textFieldNode.textField.text, text.count == 6, let color = UIColor(hexString: text) { return color @@ -271,43 +265,56 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate { return nil } } - + private func updateSelectionLayout(size: CGSize, transition: ContainedViewLayoutTransition) { self.measureNode.attributedText = NSAttributedString(string: self.textFieldNode.textField.text ?? "", font: self.textFieldNode.textField.font) let size = self.measureNode.updateLayout(size) transition.updateFrame(node: self.selectionNode, frame: CGRect(x: self.textFieldNode.frame.minX, y: 6.0, width: max(0.0, size.width), height: 20.0)) } - + private func updateSelectionVisibility() { self.selectionNode.isHidden = true } - + func updateLayout(size: CGSize, condensed: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = (size, condensed) - - let swatchFrame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 21.0, height: 21.0)) + + let isDark = self.theme.overallDarkAppearance + transition.updateFrame(node: self.textBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + self.textBackgroundNode.update(size: size, cornerRadius: size.height * 0.5, isDark: isDark, transition: transition) + + let swatchInset: CGFloat = max(5.0, floor(size.height * 0.16)) + let swatchSize = CGSize(width: size.height - swatchInset * 2.0, height: size.height - swatchInset * 2.0) + let swatchFrame = CGRect(origin: CGPoint(x: swatchInset, y: swatchInset), size: swatchSize) + transition.updateFrame(node: self.swatchBackgroundNode, frame: swatchFrame) + self.swatchBackgroundNode.update(size: swatchFrame.size, cornerRadius: swatchFrame.height * 0.5, isDark: isDark, transition: transition) transition.updateFrame(node: self.swatchNode, frame: swatchFrame) transition.updateFrame(node: self.borderNode, frame: swatchFrame) + self.swatchNode.cornerRadius = swatchFrame.height * 0.5 + self.swatchBackgroundNode.isHidden = !self.displaySwatch self.swatchNode.isHidden = !self.displaySwatch - + self.borderNode.isHidden = !self.displaySwatch + let textPadding: CGFloat if self.displaySwatch { - textPadding = condensed ? 31.0 : 37.0 + textPadding = swatchFrame.maxX + (condensed ? 7.0 : 10.0) } else { - textPadding = 12.0 + textPadding = condensed ? 6.0 : 12.0 } - - transition.updateFrame(node: self.textBackgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) - transition.updateFrame(node: self.textFieldNode, frame: CGRect(x: textPadding + 10.0, y: 1.0, width: size.width - (21.0 + textPadding), height: size.height - 2.0)) - + + self.textFieldNode.textField.font = Font.regular(17.0) + self.prefixNode.attributedText = NSAttributedString(string: "#", font: Font.regular(17.0), textColor: self.theme.chat.inputPanel.inputTextColor) + + let removeSize = CGSize(width: size.height, height: size.height) + let removeOffset: CGFloat = condensed ? 2.0 : 0.0 + transition.updateFrame(node: self.textFieldNode, frame: CGRect(x: textPadding + 10.0, y: 1.0, width: max(1.0, size.width - textPadding - removeSize.width - 14.0), height: size.height - 2.0)) + self.updateSelectionLayout(size: size, transition: transition) - + let prefixSize = self.prefixNode.measure(size) - transition.updateFrame(node: self.prefixNode, frame: CGRect(origin: CGPoint(x: textPadding - UIScreenPixel, y: 6.0), size: prefixSize)) - - let removeSize = CGSize(width: 33.0, height: 33.0) - let removeOffset: CGFloat = condensed ? 3.0 : 0.0 + transition.updateFrame(node: self.prefixNode, frame: CGRect(origin: CGPoint(x: textPadding - UIScreenPixel, y: floor((size.height - prefixSize.height) / 2.0)), size: prefixSize)) + transition.updateFrame(node: self.removeButton, frame: CGRect(origin: CGPoint(x: size.width - removeSize.width + removeOffset, y: 0.0), size: removeSize)) self.removeButton.alpha = self.isRemovable ? 1.0 : 0.0 } @@ -322,7 +329,7 @@ public struct WallpaperColorPanelNodeState: Equatable { public var preview: Bool public var simpleGradientGeneration: Bool public var suggestedNewColor: HSBColor? - + public init(selection: Int? = nil, colors: [HSBColor], maximumNumberOfColors: Int, rotateAvailable: Bool, rotation: Int32, preview: Bool, simpleGradientGeneration: Bool, suggestedNewColor: HSBColor? = nil) { self.selection = selection self.colors = colors @@ -390,16 +397,14 @@ private final class ColorSampleItemNode: ASImageNode { public final class WallpaperColorPanelNode: ASDisplayNode { private var theme: PresentationTheme - + private var state: WallpaperColorPanelNodeState - - private let backgroundNode: NavigationBackgroundNode - private let topSeparatorNode: ASDisplayNode - private let bottomSeparatorNode: ASDisplayNode + private let rotateButton: HighlightableButtonNode private let swapButton: HighlightableButtonNode + private let sampleItemsBackgroundNode: ColorPanelGlassBackgroundNode + private let addButtonBackgroundNode: ColorPanelGlassBackgroundNode private let addButton: HighlightableButtonNode - private let doneButton: HighlightableButtonNode private let colorPickerNode: WallpaperColorPickerNode private var sampleItemNodes: [ColorSampleItemNode] = [] @@ -408,36 +413,32 @@ public final class WallpaperColorPanelNode: ASDisplayNode { public var colorsChanged: (([HSBColor], Int, Bool) -> Void)? public var colorSelected: (() -> Void)? public var rotate: (() -> Void)? - + public var colorAdded: (() -> Void)? public var colorRemoved: (() -> Void)? - + private var validLayout: (CGSize, CGFloat)? + public static func topControlsHeight(for width: CGFloat) -> CGFloat { + return 52.0 + } + public init(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme - - self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor) - - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor - self.bottomSeparatorNode = ASDisplayNode() - self.bottomSeparatorNode.backgroundColor = theme.chat.inputPanel.panelSeparatorColor - - self.doneButton = HighlightableButtonNode() - self.doneButton.setImage(PresentationResourcesChat.chatInputPanelApplyButtonImage(theme), for: .normal) - + self.colorPickerNode = WallpaperColorPickerNode(strings: strings) - + self.rotateButton = HighlightableButtonNode() self.rotateButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) self.swapButton = HighlightableButtonNode() self.swapButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorSwapIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) + self.sampleItemsBackgroundNode = ColorPanelGlassBackgroundNode() + self.addButtonBackgroundNode = ColorPanelGlassBackgroundNode() self.addButton = HighlightableButtonNode() self.addButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorAddIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) self.multiColorFieldNode = ColorInputFieldNode(theme: theme, displaySwatch: false) - + self.state = WallpaperColorPanelNodeState( selection: 0, colors: [], @@ -447,22 +448,20 @@ public final class WallpaperColorPanelNode: ASDisplayNode { preview: false, simpleGradientGeneration: false ) - + super.init() - - self.backgroundColor = .white - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.topSeparatorNode) - self.addSubnode(self.bottomSeparatorNode) + + self.backgroundColor = .clear + + self.addSubnode(self.sampleItemsBackgroundNode) self.addSubnode(self.multiColorFieldNode) - self.addSubnode(self.doneButton) self.addSubnode(self.colorPickerNode) - + self.addSubnode(self.rotateButton) self.addSubnode(self.swapButton) + self.addSubnode(self.addButtonBackgroundNode) self.addSubnode(self.addButton) - + self.rotateButton.addTarget(self, action: #selector(self.rotatePressed), forControlEvents: .touchUpInside) self.swapButton.addTarget(self, action: #selector(self.swapPressed), forControlEvents: .touchUpInside) self.addButton.addTarget(self, action: #selector(self.addPressed), forControlEvents: .touchUpInside) @@ -496,7 +495,7 @@ public final class WallpaperColorPanelNode: ASDisplayNode { }, animated: strongSelf.state.colors.count >= 2) } } - + self.colorPickerNode.colorChanged = { [weak self] color in if let strongSelf = self { strongSelf.updateState({ current in @@ -522,22 +521,22 @@ public final class WallpaperColorPanelNode: ASDisplayNode { } } } - + public func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.backgroundNode.updateColor(color: self.theme.chat.inputPanel.panelBackgroundColor, transition: .immediate) - self.topSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor - self.bottomSeparatorNode.backgroundColor = self.theme.chat.inputPanel.panelSeparatorColor self.multiColorFieldNode.updateTheme(theme) + self.rotateButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) + self.swapButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorSwapIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) + self.addButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorAddIcon"), color: theme.chat.inputPanel.panelControlColor), for: .normal) } - + public func updateState(_ f: (WallpaperColorPanelNodeState) -> WallpaperColorPanelNodeState, updateLayout: Bool = true, animated: Bool = true) { var updateLayout = updateLayout let previousColors = self.state.colors let previousPreview = self.state.preview let previousSelection = self.state.selection self.state = f(self.state) - + let colorWasRemovable = self.multiColorFieldNode.isRemovable self.multiColorFieldNode.isRemovable = self.state.colors.count > 1 if colorWasRemovable != self.multiColorFieldNode.isRemovable { @@ -549,7 +548,7 @@ public final class WallpaperColorPanelNode: ASDisplayNode { self.colorPickerNode.color = self.state.colors[index] } } - + if updateLayout, let (size, bottomInset) = self.validLayout { self.updateLayout(size: size, bottomInset: bottomInset, transition: animated ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) } @@ -570,36 +569,30 @@ public final class WallpaperColorPanelNode: ASDisplayNode { self.colorsChanged?(self.state.colors, self.state.selection ?? 0, !self.state.preview) } } - + public func updateLayout(size: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (size, bottomInset) - - let condensedLayout = size.width < 375.0 - let separatorHeight = UIScreenPixel - let topPanelHeight: CGFloat = 47.0 - transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: topPanelHeight)) - self.backgroundNode.update(size: self.backgroundNode.bounds.size, transition: transition) - transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: separatorHeight)) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(x: 0.0, y: topPanelHeight, width: size.width, height: separatorHeight)) - - let fieldHeight: CGFloat = 33.0 - let leftInset: CGFloat - let rightInset: CGFloat - if condensedLayout { - leftInset = 6.0 - rightInset = 6.0 - } else { - leftInset = 15.0 - rightInset = 15.0 - } - - let buttonSize = CGSize(width: 26.0, height: 26.0) - let canAddColors = self.state.colors.count < self.state.maximumNumberOfColors - transition.updateFrame(node: self.addButton, frame: CGRect(origin: CGPoint(x: size.width - rightInset - buttonSize.width, y: floor((topPanelHeight - buttonSize.height) / 2.0)), size: buttonSize)) + let condensedLayout = size.width < 375.0 + let topPanelHeight = WallpaperColorPanelNode.topControlsHeight(for: size.width) + let controlsSize: CGFloat = 40.0 + let fieldHeight = controlsSize + let leftInset: CGFloat = 8.0 + let rightInset: CGFloat = leftInset + let sampleItemSpacing: CGFloat = 6.0 + + let buttonSize = CGSize(width: controlsSize, height: controlsSize) + let canAddColors = self.state.colors.count < self.state.maximumNumberOfColors + let addButtonFrame = CGRect(origin: CGPoint(x: size.width - rightInset - buttonSize.width, y: floor((topPanelHeight - buttonSize.height) / 2.0)), size: buttonSize) + + transition.updateFrame(node: self.addButtonBackgroundNode, frame: addButtonFrame) + self.addButtonBackgroundNode.update(size: buttonSize, cornerRadius: buttonSize.height * 0.5, isDark: self.theme.overallDarkAppearance, transition: transition) + transition.updateAlpha(node: self.addButtonBackgroundNode, alpha: canAddColors ? 1.0 : 0.0) + transition.updateSublayerTransformScale(node: self.addButtonBackgroundNode, scale: canAddColors ? 1.0 : 0.1) + transition.updateFrame(node: self.addButton, frame: addButtonFrame) transition.updateAlpha(node: self.addButton, alpha: canAddColors ? 1.0 : 0.0) transition.updateSublayerTransformScale(node: self.addButton, scale: canAddColors ? 1.0 : 0.1) - + func degreesToRadians(_ degrees: CGFloat) -> CGFloat { var degrees = degrees if degrees >= 270.0 { @@ -614,10 +607,12 @@ public final class WallpaperColorPanelNode: ASDisplayNode { self.swapButton.isHidden = true self.multiColorFieldNode.isHidden = false - let sampleItemSize: CGFloat = 32.0 - let sampleItemSpacing: CGFloat = 15.0 + let sampleCount = self.state.colors.count + let reservedAddWidth = canAddColors ? buttonSize.width + sampleItemSpacing : 0.0 + let sampleItemSize: CGFloat = 34.0 + let sampleItemsInset = floor((controlsSize - sampleItemSize) / 2.0) - var nextSampleX = leftInset + var nextSampleX = leftInset + sampleItemsInset for i in 0 ..< self.state.colors.count { var animateIn = false @@ -644,7 +639,7 @@ public final class WallpaperColorPanelNode: ASDisplayNode { if i != 0 { nextSampleX += sampleItemSpacing } - itemNode.frame = CGRect(origin: CGPoint(x: nextSampleX, y: (topPanelHeight - sampleItemSize) / 2.0), size: CGSize(width: sampleItemSize, height: sampleItemSize)) + itemNode.frame = CGRect(origin: CGPoint(x: nextSampleX, y: floor((topPanelHeight - sampleItemSize) / 2.0)), size: CGSize(width: sampleItemSize, height: sampleItemSize)) nextSampleX += sampleItemSize itemNode.update(size: itemNode.bounds.size, color: self.state.colors[i].color, isSelected: self.state.selection == i) @@ -665,17 +660,28 @@ public final class WallpaperColorPanelNode: ASDisplayNode { self.sampleItemNodes.removeSubrange(self.state.colors.count ..< self.sampleItemNodes.count) } - let fieldX = nextSampleX + sampleItemSpacing + if sampleCount > 0 { + let samplesBackgroundFrame = CGRect(x: leftInset, y: floor((topPanelHeight - controlsSize) / 2.0), width: max(controlsSize, nextSampleX - leftInset + sampleItemsInset), height: controlsSize) + transition.updateFrame(node: self.sampleItemsBackgroundNode, frame: samplesBackgroundFrame) + self.sampleItemsBackgroundNode.update(size: samplesBackgroundFrame.size, cornerRadius: controlsSize * 0.5, isDark: self.theme.overallDarkAppearance, transition: transition) + transition.updateAlpha(node: self.sampleItemsBackgroundNode, alpha: 1.0) + } else { + transition.updateFrame(node: self.sampleItemsBackgroundNode, frame: CGRect(x: leftInset, y: floor((topPanelHeight - controlsSize) / 2.0), width: controlsSize, height: controlsSize)) + transition.updateAlpha(node: self.sampleItemsBackgroundNode, alpha: 0.0) + } - let fieldFrame = CGRect(x: fieldX, y: (topPanelHeight - fieldHeight) / 2.0, width: size.width - fieldX - leftInset - (canAddColors ? (buttonSize.width + sampleItemSpacing) : 0.0), height: fieldHeight) + let fieldSpacing = sampleItemSpacing + 3.0 + let fieldX = nextSampleX + (sampleCount > 0 ? fieldSpacing : 0.0) + + let fieldFrame = CGRect(x: fieldX, y: floor((topPanelHeight - fieldHeight) / 2.0), width: max(1.0, size.width - fieldX - rightInset - reservedAddWidth), height: fieldHeight) transition.updateFrame(node: self.multiColorFieldNode, frame: fieldFrame) - self.multiColorFieldNode.updateLayout(size: fieldFrame.size, condensed: false, transition: transition) - - let colorPickerSize = CGSize(width: size.width, height: size.height - topPanelHeight - separatorHeight) - transition.updateFrame(node: self.colorPickerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + separatorHeight), size: colorPickerSize)) - self.colorPickerNode.updateLayout(size: colorPickerSize, transition: transition) + self.multiColorFieldNode.updateLayout(size: fieldFrame.size, condensed: condensedLayout, transition: transition) + + let colorPickerSize = CGSize(width: size.width, height: size.height - topPanelHeight) + transition.updateFrame(node: self.colorPickerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: colorPickerSize.width, height: colorPickerSize.height + bottomInset))) + self.colorPickerNode.updateLayout(size: colorPickerSize, bottomInset: bottomInset, transition: transition) } - + @objc private func rotatePressed() { self.rotate?() self.updateState({ current in @@ -688,7 +694,7 @@ public final class WallpaperColorPanelNode: ASDisplayNode { return updated }) } - + @objc private func swapPressed() { /*self.updateState({ current in var updated = current @@ -699,7 +705,7 @@ public final class WallpaperColorPanelNode: ASDisplayNode { return updated })*/ } - + @objc private func addPressed() { self.colorSelected?() self.colorAdded?() diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPickerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPickerNode.swift index ec5198f61f..244b49cb9d 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPickerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperColorPickerNode.swift @@ -21,7 +21,7 @@ private let knobBackgroundImage: UIImage? = { }() private let pointerImage: UIImage? = { - return generateImage(CGSize(width: 12.0, height: 55.0), opaque: false, scale: nil, rotatedContext: { size, context in + return generateImage(CGSize(width: 12.0, height: 60.0), opaque: false, scale: nil, rotatedContext: { size, context in context.setBlendMode(.clear) context.setFillColor(UIColor.clear.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) @@ -49,19 +49,6 @@ private let pointerImage: UIImage? = { }) }() -private let brightnessMaskImage: UIImage? = { - return generateImage(CGSize(width: 36.0, height: 36.0), opaque: false, scale: nil, rotatedContext: { size, context in - let bounds = CGRect(origin: CGPoint(), size: size) - - context.setFillColor(UIColor.white.cgColor) - context.fill(bounds) - - context.setBlendMode(.clear) - context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: bounds) - })?.stretchableImage(withLeftCapWidth: 18, topCapHeight: 18) -}() - private let brightnessGradientImage: UIImage? = { return generateImage(CGSize(width: 160.0, height: 1.0), opaque: false, scale: nil, rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) @@ -227,7 +214,6 @@ private final class WallpaperColorHueSaturationNode: ASDisplayNode { private final class WallpaperColorBrightnessNode: ASDisplayNode { private let gradientNode: ASImageNode - private let maskNode: ASImageNode var hsb: (CGFloat, CGFloat, CGFloat) = (0.0, 1.0, 1.0) { didSet { @@ -244,25 +230,17 @@ private final class WallpaperColorBrightnessNode: ASDisplayNode { self.gradientNode.displayWithoutProcessing = true self.gradientNode.image = brightnessGradientImage self.gradientNode.contentMode = .scaleToFill - - self.maskNode = ASImageNode() - self.maskNode.displaysAsynchronously = false - self.maskNode.displayWithoutProcessing = true - self.maskNode.image = brightnessMaskImage - self.maskNode.contentMode = .scaleToFill - + super.init() self.isOpaque = true self.addSubnode(self.gradientNode) - self.addSubnode(self.maskNode) } override func layout() { super.layout() self.gradientNode.frame = self.bounds - self.maskNode.frame = self.bounds } } @@ -335,21 +313,23 @@ final class WallpaperColorPickerNode: ASDisplayNode { init(strings: PresentationStrings) { self.brightnessNode = WallpaperColorBrightnessNode() + self.brightnessNode.clipsToBounds = true self.brightnessNode.hitTestSlop = UIEdgeInsets(top: -16.0, left: -16.0, bottom: -16.0, right: -16.0) self.brightnessKnobNode = ASImageNode() self.brightnessKnobNode.image = pointerImage self.brightnessKnobNode.isUserInteractionEnabled = false self.colorNode = WallpaperColorHueSaturationNode() self.colorNode.hitTestSlop = UIEdgeInsets(top: -16.0, left: -16.0, bottom: -16.0, right: -16.0) + self.colorNode.clipsToBounds = true self.colorKnobNode = WallpaperColorKnobNode() super.init() - self.backgroundColor = .white + self.backgroundColor = .clear + self.addSubnode(self.colorNode) self.addSubnode(self.brightnessNode) self.addSubnode(self.brightnessKnobNode) - self.addSubnode(self.colorNode) self.addSubnode(self.colorKnobNode) self.update() @@ -432,7 +412,7 @@ final class WallpaperColorPickerNode: ASDisplayNode { } private func update() { - self.backgroundColor = .white + self.backgroundColor = .clear self.colorNode.value = self.color.brightness self.brightnessNode.hsb = self.color.values self.colorKnobNode.color = self.color @@ -452,20 +432,29 @@ final class WallpaperColorPickerNode: ASDisplayNode { colorKnobFrame.origin = origin transition.updateFrame(node: self.colorKnobNode, frame: colorKnobFrame) - let inset: CGFloat = 15.0 - let brightnessKnobSize = CGSize(width: 12.0, height: 55.0) + let inset: CGFloat = 16.0 + let brightnessKnobSize = CGSize(width: 12.0, height: 60.0) let brightnessKnobFrame = CGRect(x: inset - brightnessKnobSize.width / 2.0 + (size.width - inset * 2.0) * (1.0 - self.color.brightness), y: size.height - 65.0, width: brightnessKnobSize.width, height: brightnessKnobSize.height) transition.updateFrame(node: self.brightnessKnobNode, frame: brightnessKnobFrame) } - func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, bottomInset: CGFloat = 0.0, transition: ContainedViewLayoutTransition) { self.validLayout = size let colorHeight = size.height - 66.0 - transition.updateFrame(node: self.colorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: colorHeight)) + let colorVisualHeight = colorHeight + bottomInset + transition.updateFrame(node: self.colorNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: colorVisualHeight)) + self.colorNode.cornerRadius = min(32.0, colorVisualHeight * 0.5) + if #available(iOS 11.0, *) { + self.colorNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } + if #available(iOS 13.0, *) { + self.colorNode.layer.cornerCurve = .continuous + } - let inset: CGFloat = 15.0 - transition.updateFrame(node: self.brightnessNode, frame: CGRect(x: inset, y: size.height - 55.0, width: size.width - inset * 2.0, height: 35.0)) + let inset: CGFloat = 16.0 + transition.updateFrame(node: self.brightnessNode, frame: CGRect(x: inset, y: size.height - 55.0, width: size.width - inset * 2.0, height: 40.0)) + self.brightnessNode.cornerRadius = 20.0 self.updateKnobLayout(size: size, panningColor: false, transition: .immediate) } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperCropNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperCropNode.swift index ca30053e7f..780aab8723 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperCropNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperCropNode.swift @@ -41,6 +41,7 @@ final class WallpaperCropNode: ASDisplayNode, ASScrollViewDelegate { self.scrollNode.view.scrollsToTop = false self.scrollNode.view.delaysContentTouches = false self.scrollNode.view.decelerationRate = UIScrollView.DecelerationRate.fast + self.scrollNode.view.scrollsToTop = false self.addSubnode(self.scrollNode) } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift index 40883a64c5..beacb3441d 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import QuickLook -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -25,14 +24,14 @@ public enum WallpaperListType { public enum WallpaperListSource { case list(wallpapers: [TelegramWallpaper], central: TelegramWallpaper, type: WallpaperListType) - case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?, [UInt32], Int32?, Int32?, Message?) - case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, [UInt32], Int32?, Int32?, Message?) + case wallpaper(TelegramWallpaper, WallpaperPresentationOptions?, [UInt32], Int32?, Int32?, EngineRawMessage?) + case slug(String, TelegramMediaFile?, WallpaperPresentationOptions?, [UInt32], Int32?, Int32?, EngineRawMessage?) case asset(PHAsset) case contextResult(ChatContextResult) case customColor(UInt32?) } -private func areMessagesEqual(_ lhsMessage: Message?, _ rhsMessage: Message?) -> Bool { +private func areMessagesEqual(_ lhsMessage: EngineRawMessage?, _ rhsMessage: EngineRawMessage?) -> Bool { if lhsMessage == nil && rhsMessage == nil { return true } @@ -49,7 +48,7 @@ private func areMessagesEqual(_ lhsMessage: Message?, _ rhsMessage: Message?) -> } public enum WallpaperGalleryEntry: Equatable { - case wallpaper(TelegramWallpaper, Message?) + case wallpaper(TelegramWallpaper, EngineRawMessage?) case asset(PHAsset) case contextResult(ChatContextResult) @@ -170,12 +169,16 @@ class WallpaperGalleryInteraction { let beginTransitionToEditor: () -> Void let beginTransitionFromEditor: (Bool) -> Void let finishTransitionFromEditor: () -> Void + let toolbarCancel: () -> Void + let toolbarDone: (Bool) -> Void - init(editMedia: @escaping (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void, beginTransitionToEditor: @escaping () -> Void, beginTransitionFromEditor: @escaping (Bool) -> Void, finishTransitionFromEditor: @escaping () -> Void) { + init(editMedia: @escaping (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void, beginTransitionToEditor: @escaping () -> Void, beginTransitionFromEditor: @escaping (Bool) -> Void, finishTransitionFromEditor: @escaping () -> Void, toolbarCancel: @escaping () -> Void, toolbarDone: @escaping (Bool) -> Void) { self.editMedia = editMedia self.beginTransitionToEditor = beginTransitionToEditor self.beginTransitionFromEditor = beginTransitionFromEditor self.finishTransitionFromEditor = finishTransitionFromEditor + self.toolbarCancel = toolbarCancel + self.toolbarDone = toolbarDone } } @@ -215,14 +218,12 @@ public class WallpaperGalleryController: ViewController { private var previousCentralEntryIndex: Int? private let centralItemSubtitle = Promise() - private let centralItemStatus = Promise() private let centralItemAction = Promise() private let centralItemAttributesDisposable = DisposableSet(); private var validLayout: (ContainerViewLayout, CGFloat)? private var overlayNode: WallpaperGalleryOverlayNode? - private var toolbarNode: WallpaperGalleryToolbarNode? private var patternPanelNode: WallpaperPatternPanelNode? private var colorsPanelNode: WallpaperColorPanelNode? @@ -232,6 +233,8 @@ public class WallpaperGalleryController: ViewController { private var savedPatternWallpaper: TelegramWallpaper? private var savedPatternIntensity: Int32? + private var toolbarDoneIsSolid = false + private var toolbarDoneDismissed = false public var requiredLevel: Int? @@ -244,9 +247,10 @@ public class WallpaperGalleryController: ViewController { super.init(navigationBarPresentationData: nil) self.navigationPresentation = .modal + self._hasGlassStyle = true self.title = self.presentationData.strings.WallpaperPreview_Title - //self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.interaction = WallpaperGalleryInteraction(editMedia: { [weak self] asset, image, cropRect, adjustments, referenceView, apply, fullSizeApply in @@ -267,22 +271,11 @@ public class WallpaperGalleryController: ViewController { self.present(c, in: .window(.root)) } }) - }, beginTransitionToEditor: { [weak self] in - guard let self else { - return - } - let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - if let toolbarNode = self.toolbarNode { - transition.updateAlpha(node: toolbarNode, alpha: 0.0) - } + }, beginTransitionToEditor: { }, beginTransitionFromEditor: { [weak self] saving in guard let self else { return } - let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - if let toolbarNode = self.toolbarNode { - transition.updateAlpha(node: toolbarNode, alpha: 1.0) - } if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { centralItemNode.beginTransitionFromEditor(saving: saving) } @@ -293,6 +286,10 @@ public class WallpaperGalleryController: ViewController { if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { centralItemNode.finishTransitionFromEditor() } + }, toolbarCancel: { [weak self] in + self?.dismiss(forceAway: true) + }, toolbarDone: { [weak self] forBoth in + self?.toolbarDonePressed(forBoth: forBoth) }) var entries: [WallpaperGalleryEntry] = [] @@ -360,19 +357,6 @@ public class WallpaperGalleryController: ViewController { } })) - self.centralItemAttributesDisposable.add(self.centralItemStatus.get().start(next: { [weak self] status in - if let strongSelf = self { - let enabled: Bool - switch status { - case .Local: - enabled = true - default: - enabled = false - } - strongSelf.toolbarNode?.setDoneEnabled(enabled) - } - })) - self.centralItemAttributesDisposable.add(self.centralItemAction.get().start(next: { [weak self] barButton in if let strongSelf = self { strongSelf.navigationItem.rightBarButtonItem = barButton @@ -396,7 +380,7 @@ public class WallpaperGalleryController: ViewController { } self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData), transition: .immediate) - self.toolbarNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.updateVisibleItemToolbars(layout: self.validLayout?.0, transition: .immediate) self.patternPanelNode?.updateTheme(self.presentationData.theme) self.colorsPanelNode?.updateTheme(self.presentationData.theme) @@ -409,6 +393,293 @@ public class WallpaperGalleryController: ViewController { self.presentingViewController?.dismiss(animated: true, completion: nil) //self.galleryNode.modalAnimateOut(completion: completion) } + + private func toolbarHeight() -> CGFloat { + var toolbarHeight: CGFloat = 66.0 + if case let .peer(peer, _) = self.mode, case .user = peer { + toolbarHeight += 58.0 + } + return toolbarHeight + } + + private func toolbarDoneButtonType() -> WallpaperGalleryToolbarDoneButtonType { + var doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set + switch self.source { + case let .wallpaper(wallpaper, _, _, _, _, _): + switch wallpaper { + case let .file(file): + if file.id == 0 { + doneButtonType = .none + } + default: + break + } + default: + break + } + if case let .peer(peer, _) = self.mode { + if case .user = peer { + doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium) + } else { + doneButtonType = .setChannel + } + } + return doneButtonType + } + + private func itemArguments(colorPreview: Bool = false, isColorsList: Bool = false, patternEnabled: Bool = false) -> WallpaperGalleryItemArguments { + return WallpaperGalleryItemArguments( + colorPreview: colorPreview, + isColorsList: isColorsList, + patternEnabled: patternEnabled, + toolbarHeight: self.toolbarHeight(), + toolbarDoneButtonType: self.toolbarDoneButtonType(), + toolbarRequiredLevel: self.requiredLevel + ) + } + + private func updateVisibleItemToolbars(layout: ContainerViewLayout?, transition: ContainedViewLayoutTransition) { + guard self.isNodeLoaded else { + return + } + let toolbarHeight = self.toolbarHeight() + self.galleryNode.pager.forEachItemNode { itemNode in + guard let itemNode = itemNode as? WallpaperGalleryItemNode else { + return + } + itemNode.updateToolbarThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + itemNode.setToolbarDoneIsSolid(self.toolbarDoneIsSolid, transition: transition) + if let layout { + itemNode.updateToolbarLayout(layout: layout, toolbarHeight: toolbarHeight, transition: transition) + } + } + } + + private func toolbarDonePressed(forBoth: Bool) { + guard !self.toolbarDoneDismissed else { + return + } + + let strongSelf = self + if forBoth && !strongSelf.context.isPremium { + let context = strongSelf.context + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .wallpapers, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .wallpapers, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + strongSelf.push(controller) + + return + } + + if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { + if centralItemNode.cropNode.scrollNode.view.isDecelerating { + return + } + strongSelf.toolbarDoneDismissed = true + let options = centralItemNode.options + if !strongSelf.entries.isEmpty { + let entry = strongSelf.entries[centralItemNode.index] + let apply = strongSelf.apply + if case .peer = strongSelf.mode { + if case let .wallpaper(wallpaper, _) = entry, options.contains(.blur) { + var resource: TelegramMediaResource? + switch wallpaper { + case let .file(file): + resource = file.file.resource + case let .image(representations, _): + if let largestSize = largestImageRepresentation(representations) { + resource = largestSize.resource + } + default: + break + } + if let resource = resource { + let representation = CachedBlurredWallpaperRepresentation() + var data: Data? + if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } else if let path = strongSelf.context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } + + if let data = data { + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + let _ = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) + |> filter({ $0.complete }) + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + apply?(entry, options, nil, nil, centralItemNode.brightness, forBoth) + }) + } + } + } else { + apply?(entry, options, centralItemNode.editedFullSizeImage, centralItemNode.editedCropRect, centralItemNode.brightness, forBoth) + } + return + } + + switch entry { + case let .wallpaper(wallpaper, _): + var resource: TelegramMediaResource? + switch wallpaper { + case let .file(file): + resource = file.file.resource + case let .image(representations, _): + if let largestSize = largestImageRepresentation(representations) { + resource = largestSize.resource + } + default: + break + } + + let completion: (TelegramWallpaper) -> Void = { wallpaper in + let baseSettings = wallpaper.settings + let updatedSettings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), colors: baseSettings?.colors ?? [], intensity: baseSettings?.intensity, rotation: baseSettings?.rotation) + let wallpaper = wallpaper.withUpdatedSettings(updatedSettings) + + let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered + let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + let wallpaper = wallpaper.isBasicallyEqual(to: strongSelf.presentationData.theme.chat.defaultWallpaper) ? nil : wallpaper + let themeReference: PresentationThemeReference + if autoNightModeTriggered { + themeReference = current.automaticThemeSwitchSetting.theme + } else { + themeReference = current.theme + } + let accentColor = current.themeSpecificAccentColors[themeReference.index] + if let accentColor = accentColor, accentColor.baseColor == .custom { + themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = wallpaper + } else { + themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = nil + themeSpecificChatWallpapers[themeReference.index] = wallpaper + } + return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) + }) |> deliverOnMainQueue).start(completed: { + strongSelf.dismiss(animated: true) + }) + + switch strongSelf.source { + case .wallpaper, .slug: + let _ = saveWallpaper(account: strongSelf.context.account, wallpaper: wallpaper).start() + default: + break + } + let _ = installWallpaper(account: strongSelf.context.account, wallpaper: wallpaper).start() + let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction in + WallpapersState.update(transaction: transaction, { state in + var state = state + if let index = state.wallpapers.firstIndex(where: { + $0.isBasicallyEqual(to: wallpaper) + }) { + state.wallpapers.remove(at: index) + } + state.wallpapers.insert(wallpaper, at: 0) + return state + }) + }).start() + } + + let applyWallpaper: (TelegramWallpaper) -> Void = { wallpaper in + if options.contains(.blur) { + if let resource = resource { + let representation = CachedBlurredWallpaperRepresentation() + var data: Data? + if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } else if let path = strongSelf.context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } + + if let data = data { + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + let _ = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) + |> filter({ $0.complete }) + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + completion(wallpaper) + }) + } + } + } else if case let .file(file) = wallpaper, let resource = resource { + if wallpaper.isPattern, !file.settings.colors.isEmpty, let _ = file.settings.intensity { + var data: Data? + var thumbnailData: Data? + if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } else if let path = strongSelf.context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + data = maybeData + } + + let thumbnailResource = file.file.previewRepresentations.first?.resource + + if let resource = thumbnailResource { + if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + thumbnailData = maybeData + } else if let path = strongSelf.context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(resource)), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + thumbnailData = maybeData + } + } + + if let data = data { + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + if let thumbnailResource = thumbnailResource, let thumbnailData = thumbnailData { + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) + } + completion(wallpaper) + } + } else if let path = strongSelf.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.file.resource.id)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(file.file.resource.id), data: data) + completion(wallpaper) + } + } else { + completion(wallpaper) + } + } + + if case let .image(currentRepresentations, currentSettings) = wallpaper { + let _ = (strongSelf.context.wallpaperUploadManager!.stateSignal() + |> take(1) + |> deliverOnMainQueue).start(next: { status in + switch status { + case let .uploaded(uploadedWallpaper, resultWallpaper): + if case let .image(uploadedRepresentations, _) = uploadedWallpaper, uploadedRepresentations == currentRepresentations { + let updatedWallpaper = resultWallpaper.withUpdatedSettings(currentSettings) + applyWallpaper(updatedWallpaper) + return + } + case let .uploading(uploadedWallpaper, _): + if case let .image(uploadedRepresentations, uploadedSettings) = uploadedWallpaper, uploadedRepresentations == currentRepresentations, uploadedSettings != currentSettings { + let updatedWallpaper = uploadedWallpaper.withUpdatedSettings(currentSettings) + applyWallpaper(updatedWallpaper) + return + } + default: + break + } + applyWallpaper(wallpaper) + }) + } else { + var updatedWallpaper = wallpaper + if var settings = wallpaper.settings { + settings.motion = options.contains(.motion) + updatedWallpaper = updatedWallpaper.withUpdatedSettings(settings) + } + applyWallpaper(updatedWallpaper) + } + default: + break + } + + strongSelf.apply?(entry, options, nil, centralItemNode.cropRect, centralItemNode.brightness, forBoth) + } + } + } private func updateTransaction(entries: [WallpaperGalleryEntry], arguments: WallpaperGalleryItemArguments) -> GalleryPagerTransaction { var i: Int = 0 @@ -480,271 +751,10 @@ public class WallpaperGalleryController: ViewController { break } - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let overlayNode = WallpaperGalleryOverlayNode() self.overlayNode = overlayNode self.galleryNode.overlayNode = overlayNode self.galleryNode.addSubnode(overlayNode) - - var doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set - switch self.source { - case let .wallpaper(wallpaper, _, _, _, _, _): - switch wallpaper { - case let .file(file): - if file.id == 0 { - doneButtonType = .none - } - default: - break - } - default: - break - } - if case let .peer(peer, _) = self.mode { - if case .user = peer { - doneButtonType = .setPeer(peer.compactDisplayTitle, self.context.isPremium) - } else { - doneButtonType = .setChannel - } - } - - let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType) - toolbarNode.requiredLevel = self.requiredLevel - switch self.source { - case .asset, .contextResult: - toolbarNode.dark = false - default: - toolbarNode.dark = true - } - self.toolbarNode = toolbarNode - overlayNode.addSubnode(toolbarNode) - - toolbarNode.cancel = { [weak self] in - self?.dismiss(forceAway: true) - } - var dismissed = false - toolbarNode.done = { [weak self] forBoth in - if let strongSelf = self, !dismissed { - if forBoth && !strongSelf.context.isPremium { - let context = strongSelf.context - var replaceImpl: ((ViewController) -> Void)? - let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .wallpapers, forceDark: false, action: { - let controller = context.sharedContext.makePremiumIntroController(context: context, source: .wallpapers, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) - } - strongSelf.push(controller) - - return - } - - if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { - if centralItemNode.cropNode.scrollNode.view.isDecelerating { - return - } - dismissed = true - let options = centralItemNode.options - if !strongSelf.entries.isEmpty { - let entry = strongSelf.entries[centralItemNode.index] - let apply = strongSelf.apply - if case .peer = strongSelf.mode { - if case let .wallpaper(wallpaper, _) = entry, options.contains(.blur) { - var resource: MediaResource? - switch wallpaper { - case let .file(file): - resource = file.file.resource - case let .image(representations, _): - if let largestSize = largestImageRepresentation(representations) { - resource = largestSize.resource - } - default: - break - } - if let resource = resource { - let representation = CachedBlurredWallpaperRepresentation() - var data: Data? - if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } - - if let data = data { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - let _ = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) - |> filter({ $0.complete }) - |> take(1) - |> deliverOnMainQueue).start(next: { _ in - apply?(entry, options, nil, nil, centralItemNode.brightness, forBoth) - }) - } - } - } else { - apply?(entry, options, centralItemNode.editedFullSizeImage, centralItemNode.editedCropRect, centralItemNode.brightness, forBoth) - } - return - } - - switch entry { - case let .wallpaper(wallpaper, _): - var resource: MediaResource? - switch wallpaper { - case let .file(file): - resource = file.file.resource - case let .image(representations, _): - if let largestSize = largestImageRepresentation(representations) { - resource = largestSize.resource - } - default: - break - } - - let completion: (TelegramWallpaper) -> Void = { wallpaper in - let baseSettings = wallpaper.settings - let updatedSettings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), colors: baseSettings?.colors ?? [], intensity: baseSettings?.intensity, rotation: baseSettings?.rotation) - let wallpaper = wallpaper.withUpdatedSettings(updatedSettings) - - let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered - let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - let wallpaper = wallpaper.isBasicallyEqual(to: strongSelf.presentationData.theme.chat.defaultWallpaper) ? nil : wallpaper - let themeReference: PresentationThemeReference - if autoNightModeTriggered { - themeReference = current.automaticThemeSwitchSetting.theme - } else { - themeReference = current.theme - } - let accentColor = current.themeSpecificAccentColors[themeReference.index] - if let accentColor = accentColor, accentColor.baseColor == .custom { - themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = wallpaper - } else { - themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = nil - themeSpecificChatWallpapers[themeReference.index] = wallpaper - } - return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) - }) |> deliverOnMainQueue).start(completed: { - self?.dismiss(animated: true) - }) - - switch strongSelf.source { - case .wallpaper, .slug: - let _ = saveWallpaper(account: strongSelf.context.account, wallpaper: wallpaper).start() - default: - break - } - let _ = installWallpaper(account: strongSelf.context.account, wallpaper: wallpaper).start() - let _ = (strongSelf.context.sharedContext.accountManager.transaction { transaction in - WallpapersState.update(transaction: transaction, { state in - var state = state - if let index = state.wallpapers.firstIndex(where: { - $0.isBasicallyEqual(to: wallpaper) - }) { - state.wallpapers.remove(at: index) - } - state.wallpapers.insert(wallpaper, at: 0) - return state - }) - }).start() - } - - let applyWallpaper: (TelegramWallpaper) -> Void = { wallpaper in - if options.contains(.blur) { - if let resource = resource { - let representation = CachedBlurredWallpaperRepresentation() - var data: Data? - if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } - - if let data = data { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - let _ = (strongSelf.context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true) - |> filter({ $0.complete }) - |> take(1) - |> deliverOnMainQueue).start(next: { _ in - completion(wallpaper) - }) - } - } - } else if case let .file(file) = wallpaper, let resource = resource { - if wallpaper.isPattern, !file.settings.colors.isEmpty, let _ = file.settings.intensity { - var data: Data? - var thumbnailData: Data? - if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - data = maybeData - } - - let thumbnailResource = file.file.previewRepresentations.first?.resource - - if let resource = thumbnailResource { - if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - thumbnailData = maybeData - } else if let path = strongSelf.context.sharedContext.accountManager.mediaBox.completedResourcePath(resource), let maybeData = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - thumbnailData = maybeData - } - } - - if let data = data { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - if let thumbnailResource = thumbnailResource, let thumbnailData = thumbnailData { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) - } - completion(wallpaper) - } - } else if let path = strongSelf.context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) - completion(wallpaper) - } - } else { - completion(wallpaper) - } - } - - if case let .image(currentRepresentations, currentSettings) = wallpaper { - let _ = (strongSelf.context.wallpaperUploadManager!.stateSignal() - |> take(1) - |> deliverOnMainQueue).start(next: { status in - switch status { - case let .uploaded(uploadedWallpaper, resultWallpaper): - if case let .image(uploadedRepresentations, _) = uploadedWallpaper, uploadedRepresentations == currentRepresentations { - let updatedWallpaper = resultWallpaper.withUpdatedSettings(currentSettings) - applyWallpaper(updatedWallpaper) - return - } - case let .uploading(uploadedWallpaper, _): - if case let .image(uploadedRepresentations, uploadedSettings) = uploadedWallpaper, uploadedRepresentations == currentRepresentations, uploadedSettings != currentSettings { - let updatedWallpaper = uploadedWallpaper.withUpdatedSettings(currentSettings) - applyWallpaper(updatedWallpaper) - return - } - default: - break - } - applyWallpaper(wallpaper) - }) - } else { - var updatedWallpaper = wallpaper - if var settings = wallpaper.settings { - settings.motion = options.contains(.motion) - updatedWallpaper = updatedWallpaper.withUpdatedSettings(settings) - } - applyWallpaper(updatedWallpaper) - } - default: - break - } - - strongSelf.apply?(entry, options, nil, centralItemNode.cropRect, centralItemNode.brightness, forBoth) - } - } - } - } } private func currentEntry() -> WallpaperGalleryEntry? { @@ -771,11 +781,11 @@ public class WallpaperGalleryController: ViewController { private func bindCentralItemNode(animated: Bool, updated: Bool) { if let node = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { self.centralItemSubtitle.set(node.subtitle.get()) - self.centralItemStatus.set(node.status.get()) self.centralItemAction.set(node.actionButton.get()) node.action = { [weak self] in self?.actionPressed() } + self.updateVisibleItemToolbars(layout: self.validLayout?.0, transition: .immediate) node.requestPatternPanel = { [weak self] enabled, initialWallpaper in if let strongSelf = self, let (layout, _) = strongSelf.validLayout { strongSelf.colorsPanelEnabled = false @@ -902,7 +912,7 @@ public class WallpaperGalleryController: ViewController { entries[centralEntryIndex] = currentEntry self.entries = entries - self.galleryNode.pager.transaction(self.updateTransaction(entries: entries, arguments: WallpaperGalleryItemArguments(colorPreview: preview, isColorsList: false, patternEnabled: self.patternPanelEnabled))) + self.galleryNode.pager.transaction(self.updateTransaction(entries: entries, arguments: self.itemArguments(colorPreview: preview, isColorsList: false, patternEnabled: self.patternPanelEnabled))) } private func updateEntries(wallpaper: TelegramWallpaper, preview: Bool = false) { @@ -921,7 +931,7 @@ public class WallpaperGalleryController: ViewController { entries[centralEntryIndex] = currentEntry self.entries = entries - self.galleryNode.pager.transaction(self.updateCurrentEntryTransaction(entry: currentEntry, arguments: WallpaperGalleryItemArguments(colorPreview: preview, isColorsList: false, patternEnabled: self.patternPanelEnabled), index: centralEntryIndex)) + self.galleryNode.pager.transaction(self.updateCurrentEntryTransaction(entry: currentEntry, arguments: self.itemArguments(colorPreview: preview, isColorsList: false, patternEnabled: self.patternPanelEnabled), index: centralEntryIndex)) } private func updateEntries(pattern: TelegramWallpaper?, intensity: Int32? = nil, preview: Bool = false) { @@ -956,7 +966,7 @@ public class WallpaperGalleryController: ViewController { } self.entries = updatedEntries - self.galleryNode.pager.transaction(self.updateTransaction(entries: updatedEntries, arguments: WallpaperGalleryItemArguments(colorPreview: preview, isColorsList: true, patternEnabled: self.patternPanelEnabled))) + self.galleryNode.pager.transaction(self.updateTransaction(entries: updatedEntries, arguments: self.itemArguments(colorPreview: preview, isColorsList: true, patternEnabled: self.patternPanelEnabled))) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -964,7 +974,9 @@ public class WallpaperGalleryController: ViewController { super.containerLayoutUpdated(layout, transition: transition) - let panelHeight: CGFloat = 235.0 + let panelHeight: CGFloat = 188.0 + WallpaperColorPanelNode.topControlsHeight(for: layout.size.width) + let toolbarHeight = self.toolbarHeight() + self.toolbarDoneIsSolid = self.patternPanelEnabled || self.colorsPanelEnabled var pagerLayout = layout if self.patternPanelEnabled || self.colorsPanelEnabled { @@ -975,13 +987,7 @@ public class WallpaperGalleryController: ViewController { self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size) self.galleryNode.containerLayoutUpdated(pagerLayout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.overlayNode?.frame = self.galleryNode.bounds - - var toolbarHeight: CGFloat = 66.0 - if case let .peer(peer, _) = self.mode, case .user = peer { - toolbarHeight += 58.0 - } - transition.updateFrame(node: self.toolbarNode!, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom))) - self.toolbarNode!.updateLayout(size: CGSize(width: layout.size.width, height: toolbarHeight), layout: layout, transition: transition) + self.updateVisibleItemToolbars(layout: layout, transition: transition) var bottomInset = toolbarHeight + layout.intrinsicInsets.bottom @@ -1026,7 +1032,7 @@ public class WallpaperGalleryController: ViewController { } self.patternPanelNode = patternPanelNode currentPatternPanelNode = patternPanelNode - self.overlayNode?.insertSubnode(patternPanelNode, belowSubnode: self.toolbarNode!) + self.overlayNode?.addSubnode(patternPanelNode) } let currentColorsPanelNode: WallpaperColorPanelNode @@ -1036,7 +1042,7 @@ public class WallpaperGalleryController: ViewController { let colorsPanelNode = WallpaperColorPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings) self.colorsPanelNode = colorsPanelNode currentColorsPanelNode = colorsPanelNode - self.overlayNode?.insertSubnode(colorsPanelNode, belowSubnode: self.toolbarNode!) + self.overlayNode?.addSubnode(colorsPanelNode) colorsPanelNode.colorsChanged = { [weak self] colors, _, _ in guard let strongSelf = self else { @@ -1056,7 +1062,6 @@ public class WallpaperGalleryController: ViewController { } } - let originalBottomInset = bottomInset var patternPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight) if self.patternPanelEnabled { patternPanelFrame.origin = CGPoint(x: 0.0, y: layout.size.height - max((layout.inputHeight ?? 0.0) - panelHeight + 44.0, bottomInset) - panelHeight) @@ -1064,7 +1069,7 @@ public class WallpaperGalleryController: ViewController { } transition.updateFrame(node: currentPatternPanelNode, frame: patternPanelFrame) - currentPatternPanelNode.updateLayout(size: patternPanelFrame.size, bottomInset: originalBottomInset, transition: transition) + currentPatternPanelNode.updateLayout(size: patternPanelFrame.size, bottomInset: 0.0, transition: transition) var colorsPanelFrame = CGRect(x: 0.0, y: layout.size.height, width: layout.size.width, height: panelHeight) if self.colorsPanelEnabled { @@ -1072,11 +1077,9 @@ public class WallpaperGalleryController: ViewController { bottomInset += panelHeight } - transition.updateFrame(node: currentColorsPanelNode, frame: CGRect(origin: colorsPanelFrame.origin, size: CGSize(width: colorsPanelFrame.width, height: colorsPanelFrame.height + originalBottomInset))) - currentColorsPanelNode.updateLayout(size: colorsPanelFrame.size, bottomInset: originalBottomInset, transition: transition) + transition.updateFrame(node: currentColorsPanelNode, frame: colorsPanelFrame) + currentColorsPanelNode.updateLayout(size: colorsPanelFrame.size, bottomInset: 0.0, transition: transition) - self.toolbarNode?.setDoneIsSolid(self.patternPanelEnabled || self.colorsPanelEnabled, transition: transition) - bottomInset += toolbarHeight self.validLayout = (layout, bottomInset) @@ -1086,11 +1089,12 @@ public class WallpaperGalleryController: ViewController { colors = true } - self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode, interaction: self.interaction!) }), centralItemIndex: self.centralEntryIndex) + self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: self.itemArguments(isColorsList: colors), source: self.source, mode: self.mode, interaction: self.interaction!) }), centralItemIndex: self.centralEntryIndex) if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { itemNode.options = initialOptions } + self.updateVisibleItemToolbars(layout: layout, transition: .immediate) } } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 7bd2d9037d..7e36181a6d 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -21,16 +20,24 @@ import WallpaperBackgroundNode import TextFormat import TooltipUI import TelegramNotices +import ComponentFlow +import GlassControls struct WallpaperGalleryItemArguments { let colorPreview: Bool let isColorsList: Bool let patternEnabled: Bool + let toolbarHeight: CGFloat + let toolbarDoneButtonType: WallpaperGalleryToolbarDoneButtonType + let toolbarRequiredLevel: Int? - init(colorPreview: Bool = false, isColorsList: Bool = false, patternEnabled: Bool = false) { + init(colorPreview: Bool = false, isColorsList: Bool = false, patternEnabled: Bool = false, toolbarHeight: CGFloat = 66.0, toolbarDoneButtonType: WallpaperGalleryToolbarDoneButtonType = .set, toolbarRequiredLevel: Int? = nil) { self.colorPreview = colorPreview self.isColorsList = isColorsList self.patternEnabled = patternEnabled + self.toolbarHeight = toolbarHeight + self.toolbarDoneButtonType = toolbarDoneButtonType + self.toolbarRequiredLevel = toolbarRequiredLevel } } @@ -78,7 +85,7 @@ class WallpaperGalleryItem: GalleryItem { private let progressDiameter: CGFloat = 50.0 private let motionAmount: CGFloat = 32.0 -private func reference(for resource: MediaResource, media: Media, message: Message?, slug: String?) -> MediaResourceReference { +private func reference(for resource: TelegramMediaResource, media: EngineRawMedia, message: EngineRawMessage?, slug: String?) -> MediaResourceReference { if let message = message { return .media(media: .message(message: MessageReference(message), media: media), resource: resource) } @@ -106,10 +113,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let blurredNode: BlurredImageNode let cropNode: WallpaperCropNode - private let cancelButtonNode: WallpaperNavigationButtonNode - private let shareButtonNode: WallpaperNavigationButtonNode - private let dayNightButtonNode: WallpaperNavigationButtonNode - private let editButtonNode: WallpaperNavigationButtonNode + private let topButtons: ComponentView + private var topCanShare = false + private var topCanSwitchTheme = false + private var topCanEdit = false + private var controlsGlassIsDark = true private let buttonsContainerNode: SparseNode private let blurButtonNode: WallpaperOptionButtonNode @@ -118,6 +126,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let colorsButtonNode: WallpaperOptionButtonNode private let playButtonNode: WallpaperNavigationButtonNode private let sliderNode: WallpaperSliderNode + private let toolbarNode: WallpaperGalleryToolbarNode + private var toolbarDoneEnabled = true private let messagesContainerNode: ASDisplayNode private var messageNodes: [ListViewItemNode]? @@ -131,7 +141,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { private let colorDisposable = MetaDisposable() let subtitle = Promise(nil) - let status = Promise(.Local) + let status = Promise(.Local) let actionButton = Promise(nil) var action: (() -> Void)? var requestPatternPanel: ((Bool, TelegramWallpaper) -> Void)? @@ -139,6 +149,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var requestRotateGradient: ((Int32) -> Void)? private var validLayout: (ContainerViewLayout, CGFloat)? + private var validToolbarLayout: (ContainerViewLayout, CGFloat)? private var validOffset: CGFloat? private var initialWallpaper: TelegramWallpaper? @@ -177,6 +188,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) self.messagesContainerNode.isUserInteractionEnabled = false + self.topButtons = ComponentView() self.buttonsContainerNode = SparseNode() self.blurButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Blurred, value: .check(false)) self.blurButtonNode.setEnabled(false) @@ -193,16 +205,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { }) self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear])) + self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.presentationData.theme, strings: self.presentationData.strings) - self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: true) - self.cancelButtonNode.enableSaturation = true - self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: true) - self.shareButtonNode.enableSaturation = true - self.dayNightButtonNode = WallpaperNavigationButtonNode(content: .dayNight(isNight: self.isDarkAppearance), dark: true) - self.dayNightButtonNode.enableSaturation = true - self.editButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Settings/WallpaperAdjustments"), size: CGSize(width: 28.0, height: 28.0)), dark: true) - self.editButtonNode.enableSaturation = true - self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.white.cgColor) @@ -265,10 +269,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.buttonsContainerNode.addSubnode(self.colorsButtonNode) self.buttonsContainerNode.addSubnode(self.playButtonNode) self.buttonsContainerNode.addSubnode(self.sliderNode) - self.buttonsContainerNode.addSubnode(self.cancelButtonNode) - self.buttonsContainerNode.addSubnode(self.shareButtonNode) - self.buttonsContainerNode.addSubnode(self.dayNightButtonNode) - self.buttonsContainerNode.addSubnode(self.editButtonNode) + self.buttonsContainerNode.addSubnode(self.toolbarNode) self.imageNode.addSubnode(self.brightnessNode) @@ -277,10 +278,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.addTarget(self, action: #selector(self.togglePattern), forControlEvents: .touchUpInside) self.colorsButtonNode.addTarget(self, action: #selector(self.toggleColors), forControlEvents: .touchUpInside) self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside) - self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) - self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside) - self.dayNightButtonNode.addTarget(self, action: #selector(self.dayNightPressed), forControlEvents: .touchUpInside) - self.editButtonNode.addTarget(self, action: #selector(self.editPressed), forControlEvents: .touchUpInside) sliderValueChangedImpl = { [weak self] value in if let self { @@ -460,7 +457,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { @objc private func dayNightPressed() { self.isDarkAppearance = !self.isDarkAppearance - self.dayNightButtonNode.setIsNight(self.isDarkAppearance) if let layout = self.validLayout?.0 { let offset = CGPoint(x: self.validOffset ?? 0.0, y: 0.0) @@ -595,6 +591,20 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.source = source self.mode = mode self.interaction = interaction + + self.toolbarNode.doneButtonType = arguments.toolbarDoneButtonType + self.toolbarNode.requiredLevel = arguments.toolbarRequiredLevel + self.updateToolbarThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + self.toolbarNode.cancel = { [weak self] in + if let interaction = self?.interaction { + interaction.toolbarCancel() + } + } + self.toolbarNode.done = { [weak self] forBoth in + if let interaction = self?.interaction { + interaction.toolbarDone(forBoth) + } + } if self.arguments.colorPreview != previousArguments.colorPreview { if self.arguments.colorPreview { @@ -614,8 +624,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let imagePromise = Promise() let signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> - let fetchSignal: Signal - let statusSignal: Signal + let fetchSignal: Signal + let statusSignal: Signal let subtitleSignal: Signal var actionSignal: Signal = .single(nil) var colorSignal: Signal = serviceColor(from: imagePromise.get()) @@ -697,16 +707,25 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.playButtonNode.setIcon(self.playButtonRotateImage) } - self.cancelButtonNode.enableSaturation = isColor - self.dayNightButtonNode.enableSaturation = isColor - self.editButtonNode.enableSaturation = isColor - self.shareButtonNode.enableSaturation = isColor self.patternButtonNode.backgroundNode.enableSaturation = isColor self.blurButtonNode.backgroundNode.enableSaturation = isColor self.motionButtonNode.backgroundNode.enableSaturation = isColor self.colorsButtonNode.backgroundNode.enableSaturation = isColor self.playButtonNode.enableSaturation = isColor - + + let controlsGlassIsDark = self.presentationData.theme.overallDarkAppearance //!isColor + self.controlsGlassIsDark = controlsGlassIsDark + self.patternButtonNode.backgroundNode.isDark = controlsGlassIsDark + self.blurButtonNode.backgroundNode.isDark = controlsGlassIsDark + self.motionButtonNode.backgroundNode.isDark = controlsGlassIsDark + self.colorsButtonNode.backgroundNode.isDark = controlsGlassIsDark + self.playButtonNode.dark = controlsGlassIsDark + self.toolbarNode.dark = controlsGlassIsDark + self.blurButtonNode.dark = controlsGlassIsDark + self.motionButtonNode.dark = controlsGlassIsDark + self.patternButtonNode.dark = controlsGlassIsDark + self.colorsButtonNode.dark = controlsGlassIsDark + var canShare = false var canSwitchTheme = false var canEdit = false @@ -719,7 +738,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { case .builtin, .emoticon: displaySize = CGSize(width: 1308.0, height: 2688.0).fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor contentSize = displaySize - signal = settingsBuiltinWallpaperImage(account: self.context.account) + signal = settingsBuiltinWallpaperImage() fetchSignal = .complete() statusSignal = .single(.Local) subtitleSignal = .single(nil) @@ -794,15 +813,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: fileReference, representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false) } - fetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: convertedRepresentations[convertedRepresentations.count - 1].reference) - let account = self.context.account - statusSignal = self.context.sharedContext.accountManager.mediaBox.resourceStatus(file.file.resource) + fetchSignal = context.engine.resources.fetch(reference: convertedRepresentations[convertedRepresentations.count - 1].reference, userLocation: .other, userContentType: .other) + let engine = self.context.engine + statusSignal = self.context.sharedContext.accountManager.resources.status(resource: EngineMediaResource(file.file.resource)) |> take(1) - |> mapToSignal { status -> Signal in + |> mapToSignal { status -> Signal in if case .Local = status { return .single(status) } else { - return account.postbox.mediaBox.resourceStatus(file.file.resource) + return engine.resources.status(resource: EngineMediaResource(file.file.resource)) } } if let fileSize = file.file.size { @@ -826,18 +845,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode { signal = wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, alwaysShowThumbnailFirst: true, autoFetchFullSize: false) if let largestIndex = convertedRepresentations.firstIndex(where: { $0.representation == largestSize }) { - fetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: convertedRepresentations[largestIndex].reference) + fetchSignal = context.engine.resources.fetch(reference: convertedRepresentations[largestIndex].reference, userLocation: .other, userContentType: .other) } else { fetchSignal = .complete() } - let account = context.account - statusSignal = context.sharedContext.accountManager.mediaBox.resourceStatus(largestSize.resource) + let engine = context.engine + statusSignal = context.sharedContext.accountManager.resources.status(resource: EngineMediaResource(largestSize.resource)) |> take(1) - |> mapToSignal { status -> Signal in + |> mapToSignal { status -> Signal in if case .Local = status { return .single(status) } else { - return account.postbox.mediaBox.resourceStatus(largestSize.resource) + return engine.resources.status(resource: EngineMediaResource(largestSize.resource)) } } if let fileSize = largestSize.resource.size { @@ -875,7 +894,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let dimensions = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) contentSize = dimensions displaySize = dimensions.aspectFittedOrSmaller(CGSize(width: 2048.0, height: 2048.0)) - signal = photoWallpaper(postbox: context.account.postbox, photoLibraryResource: PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max))) + signal = photoWallpaper(photoLibraryResource: PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier, uniqueId: Int64.random(in: Int64.min ... Int64.max))) fetchSignal = .complete() statusSignal = .single(.Local) subtitleSignal = .single(nil) @@ -923,11 +942,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) signal = chatMessagePhoto(postbox: context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage)) - fetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .media(media: .standalone(media: tmpImage), resource: imageResource)) - statusSignal = context.account.postbox.mediaBox.resourceStatus(imageResource) + fetchSignal = context.engine.resources.fetch(reference: .media(media: .standalone(media: tmpImage), resource: imageResource), userLocation: .other, userContentType: .other) + statusSignal = context.engine.resources.status(resource: EngineMediaResource(imageResource)) } else { displaySize = CGSize(width: 1.0, height: 1.0) contentSize = displaySize @@ -949,14 +968,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { canSwitchTheme = true } - if canSwitchTheme { - self.dayNightButtonNode.isHidden = false - self.shareButtonNode.isHidden = true - } else { - self.dayNightButtonNode.isHidden = true - self.shareButtonNode.isHidden = !canShare - } - self.editButtonNode.isHidden = !canEdit + self.topCanSwitchTheme = canSwitchTheme + self.topCanShare = canShare + self.topCanEdit = canEdit if self.cropNode.supernode == nil { self.imageNode.contentMode = .scaleAspectFill @@ -986,7 +1000,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if case .asset = entry, let image, let data = image.jpegData(compressionQuality: 0.5) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + strongSelf.context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], WallpaperSettings()) strongSelf.nativeNode.update(wallpaper: wallpaper, animated: false) @@ -1016,6 +1030,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { strongSelf.blurButtonNode.setEnabled(local) strongSelf.motionButtonNode.setEnabled(local) strongSelf.patternButtonNode.setEnabled(local) + strongSelf.setToolbarDoneEnabled(local) } })) @@ -1029,10 +1044,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode { return } strongSelf.statusNode.backgroundNodeColor = color - strongSelf.patternButtonNode.buttonColor = color - strongSelf.blurButtonNode.buttonColor = color - strongSelf.motionButtonNode.buttonColor = color - strongSelf.colorsButtonNode.buttonColor = color })) } else if self.arguments.patternEnabled != previousArguments.patternEnabled { self.patternButtonNode.isSelected = self.arguments.patternEnabled @@ -1067,6 +1078,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: .immediate) self.updateMessagesLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition:.immediate) } + if let (layout, toolbarHeight) = self.validToolbarLayout { + self.updateToolbarLayout(layout: layout, toolbarHeight: toolbarHeight, offset: CGPoint(x: offset, y: 0.0), transition: .immediate) + } } func updateDismissTransition(_ value: CGFloat) { @@ -1074,6 +1088,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateButtonsLayout(layout: layout, offset: CGPoint(x: 0.0, y: value), transition: .immediate) self.updateMessagesLayout(layout: layout, offset: CGPoint(x: 0.0, y: value), transition: .immediate) } + if let (layout, toolbarHeight) = self.validToolbarLayout { + self.updateToolbarLayout(layout: layout, toolbarHeight: toolbarHeight, offset: CGPoint(x: self.validOffset ?? 0.0, y: value), transition: .immediate) + } } var options: WallpaperPresentationOptions { @@ -1297,6 +1314,34 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } transition.updatePosition(node: self.wrapperNode, position: CGPoint(x: layout.size.width / 2.0 + appliedOffset, y: layout.size.height / 2.0)) } + + func setToolbarDoneEnabled(_ enabled: Bool) { + self.toolbarDoneEnabled = enabled + self.toolbarNode.setDoneEnabled(enabled) + } + + func setToolbarDoneIsSolid(_ isSolid: Bool, transition: ContainedViewLayoutTransition) { + self.toolbarNode.setDoneIsSolid(isSolid, transition: transition) + } + + func updateToolbarThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.toolbarNode.updateThemeAndStrings(theme: theme, strings: strings) + self.toolbarNode.setDoneEnabled(self.toolbarDoneEnabled) + } + + func updateToolbarLayout(layout: ContainerViewLayout, toolbarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validToolbarLayout = (layout, toolbarHeight) + self.updateToolbarLayout(layout: layout, toolbarHeight: toolbarHeight, offset: CGPoint(x: self.validOffset ?? 0.0, y: 0.0), transition: transition) + } + + private func updateToolbarLayout(layout: ContainerViewLayout, toolbarHeight: CGFloat, offset: CGPoint, transition: ContainedViewLayoutTransition) { + let toolbarFrame = CGRect( + origin: CGPoint(x: offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom + offset.y), + size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom) + ) + transition.updateFrame(node: self.toolbarNode, frame: toolbarFrame) + self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: toolbarHeight), layout: layout, transition: transition) + } func updateButtonsLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { let patternButtonSize = self.patternButtonNode.measure(layout.size) @@ -1329,10 +1374,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let buttonSpacing: CGFloat = 18.0 - var toolbarHeight: CGFloat = 66.0 - if let mode = self.mode, case let .peer(peer, _) = mode, case .user = peer { - toolbarHeight += 58.0 - } + let toolbarHeight = self.arguments.toolbarHeight let leftButtonFrame = CGRect(origin: CGPoint(x: floor(layout.size.width / 2.0 - buttonSize.width - buttonSpacing) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize) let centerButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 54.0 + offset.y + additionalYOffset), size: buttonSize) @@ -1363,11 +1405,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } else { sliderFrame = sliderFrame.offsetBy(dx: 0.0, dy: 22.0) } - - let cancelSize = self.cancelButtonNode.measure(layout.size) - let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize) - let shareFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x, y: 16.0), size: CGSize(width: 28.0, height: 28.0)) - let editFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x - 46.0, y: 16.0), size: CGSize(width: 28.0, height: 28.0)) + + var topCanShare = self.topCanShare let centerOffset: CGFloat = 32.0 @@ -1461,7 +1500,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorsAlpha = 0.0 blurAlpha = 0.0 playAlpha = 0.0 - self.shareButtonNode.isHidden = true + topCanShare = false } blurFrame = centerButtonFrame } @@ -1487,10 +1526,70 @@ final class WallpaperGalleryItemNode: GalleryItemNode { transition.updateTransformScale(node: self.sliderNode, scale: sliderScale) self.sliderNode.updateLayout(size: sliderFrame.size) - transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame) - transition.updateFrame(node: self.shareButtonNode, frame: shareFrame) - transition.updateFrame(node: self.dayNightButtonNode, frame: shareFrame) - transition.updateFrame(node: self.editButtonNode, frame: editFrame) + let leftControlItems: [GlassControlGroupComponent.Item] = [ + GlassControlGroupComponent.Item( + id: AnyHashable("close"), + content: .icon("Navigation/Close"), + action: { [weak self] in + self?.cancelPressed() + } + ) + ] + var rightControlItems: [GlassControlGroupComponent.Item] = [] + if self.topCanEdit { + rightControlItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("edit"), + content: .icon("Settings/WallpaperAdjustments"), + action: { [weak self] in + self?.editPressed() + } + )) + } + if self.topCanSwitchTheme { + rightControlItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("dayNight"), + content: .animation(self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun"), + action: { [weak self] in + self?.dayNightPressed() + } + )) + } else if topCanShare { + rightControlItems.append(GlassControlGroupComponent.Item( + id: AnyHashable("share"), + content: .icon("Navigation/Share"), + action: { [weak self] in + self?.actionPressed() + } + )) + } + + let topButtonSideInset: CGFloat = 16.0 + let topButtonsSize = self.topButtons.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassControlPanelComponent( + theme: self.controlsGlassIsDark ? defaultDarkPresentationTheme : self.presentationData.theme, + leftItem: GlassControlPanelComponent.Item( + items: leftControlItems, + background: .panel + ), + centralItem: nil, + rightItem: rightControlItems.isEmpty ? nil : GlassControlPanelComponent.Item( + items: rightControlItems, + background: .panel + ), + centerAlignmentIfPossible: true, + isDark: self.controlsGlassIsDark + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - topButtonSideInset * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: 44.0) + ) + let topButtonsFrame = CGRect(origin: CGPoint(x: topButtonSideInset + layout.safeInsets.left + offset.x, y: topButtonSideInset), size: topButtonsSize) + if let topButtonsView = self.topButtons.view { + if topButtonsView.superview == nil { + self.buttonsContainerNode.view.addSubview(topButtonsView) + } + transition.updateFrame(view: topButtonsView, frame: topButtonsFrame) + } } private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) { @@ -1506,24 +1605,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } var items: [ListViewItem] = [] - var peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + var peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) let otherPeerId = self.context.account.peerId - var peers = SimpleDictionary() - var messages = SimpleDictionary() + var peers = EngineSimpleDictionary() + var messages = EngineSimpleDictionary() let replyAuthor = self.presentationData.strings.Appearance_ThemePreview_Chat_2_ReplyName - var messageAuthor: Peer = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) + var messageAuthor: EngineRawPeer = TelegramUser(id: peerId, accessHash: nil, firstName: self.presentationData.strings.Appearance_PreviewReplyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) let otherAuthor = TelegramUser(id: otherPeerId, accessHash: nil, firstName: replyAuthor, lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: .preset(.blue), backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[otherPeerId] = otherAuthor - var messageAttributes: [MessageAttribute] = [] + var messageAttributes: [EngineMessage.Attribute] = [] if let mode = self.mode, case let .peer(peer, _) = mode, case .channel = peer { peerId = peer.id messageAuthor = peer._asPeer() - let replyMessageId = MessageId(peerId: peerId, namespace: 0, id: 3) - messages[replyMessageId] = Message(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: messageAuthor, text: self.presentationData.strings.WallpaperPreview_ChannelReplyText, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let replyMessageId = EngineMessage.Id(peerId: peerId, namespace: 0, id: 3) + messages[replyMessageId] = EngineRawMessage(stableId: 3, stableVersion: 0, id: replyMessageId, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: messageAuthor, text: self.presentationData.strings.WallpaperPreview_ChannelReplyText, attributes: [], media: [], peers: peers, associatedMessages: EngineSimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) messageAttributes = [ReplyMessageAttribute(messageId: replyMessageId, threadMessageId: nil, quote: nil, isQuote: false, innerSubject: nil)] } @@ -1626,19 +1725,19 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let theme = self.presentationData.theme if !bottomMessageText.isEmpty { - let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message1 = EngineRawMessage(stableId: 2, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } - let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: messageAttributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message2 = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: messageAttributes, media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) if let serviceMessageText { let attributedText = convertMarkdownToAttributes(NSAttributedString(string: serviceMessageText)) let entities = generateChatInputTextEntities(attributedText) - let message3 = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: attributedText.string, entities: entities, additionalAttributes: nil))], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message3 = EngineRawMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: attributedText.string, entities: entities, additionalAttributes: nil))], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } @@ -1751,6 +1850,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.updateButtonsLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: transition) self.updateMessagesLayout(layout: layout, offset: CGPoint(x: offset, y: 0.0), transition: transition) + self.updateToolbarLayout(layout: layout, toolbarHeight: self.arguments.toolbarHeight, transition: transition) self.validLayout = (layout, navigationBarHeight) } @@ -1769,7 +1869,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { return } - let frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.bounds, to: self.view) + guard let controlsView = self.topButtons.view as? GlassControlPanelComponent.View, let dayNightView = controlsView.rightItemView?.itemView(id: AnyHashable("dayNight")) else { + return + } + + let frame = dayNightView.convert(dayNightView.bounds, to: self.view) let currentTimestamp = Int32(Date().timeIntervalSince1970) let isDark = self.isDarkAppearance diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift index 0ce64b7b9c..2c1575bd40 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift @@ -6,6 +6,7 @@ import TelegramPresentationData import ManagedAnimationNode import ComponentFlow import PremiumLockButtonSubtitleComponent +import ButtonComponent public enum WallpaperGalleryToolbarCancelButtonType { case cancel @@ -24,169 +25,129 @@ public enum WallpaperGalleryToolbarDoneButtonType { public protocol WallpaperGalleryToolbar: ASDisplayNode { var cancelButtonType: WallpaperGalleryToolbarCancelButtonType { get set } var doneButtonType: WallpaperGalleryToolbarDoneButtonType { get set } - + var cancel: (() -> Void)? { get set } var done: ((Bool) -> Void)? { get set } - + func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) - + func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) } public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryToolbar { class ButtonNode: ASDisplayNode { private let strings: PresentationStrings - + private let doneButton = HighlightTrackingButtonNode() - private var doneButtonBackgroundNode: ASDisplayNode + private var doneButtonBackgroundNode: WallpaperOptionBackgroundNode private let doneButtonTitleNode: ImmediateTextNode private var doneButtonSubtitle: ComponentView? - + private let doneButtonSolidBackgroundNode: ASDisplayNode private let doneButtonSolidTitleNode: ImmediateTextNode - + private let animationNode: SimpleAnimationNode - + var action: () -> Void = {} - + var isLocked: Bool = false { didSet { self.animationNode.isHidden = !self.isLocked } } - + var requiredLevel: Int? - + init(strings: PresentationStrings) { self.strings = strings - - self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() - self.doneButtonBackgroundNode.cornerRadius = 14.0 - + + self.doneButtonBackgroundNode = WallpaperOptionBackgroundNode(isDark: false) + self.doneButtonTitleNode = ImmediateTextNode() self.doneButtonTitleNode.displaysAsynchronously = false self.doneButtonTitleNode.isUserInteractionEnabled = false - + self.doneButtonSolidBackgroundNode = ASDisplayNode() self.doneButtonSolidBackgroundNode.alpha = 0.0 self.doneButtonSolidBackgroundNode.clipsToBounds = true - self.doneButtonSolidBackgroundNode.layer.cornerRadius = 14.0 + self.doneButtonSolidBackgroundNode.layer.cornerRadius = 26.0 if #available(iOS 13.0, *) { self.doneButtonSolidBackgroundNode.layer.cornerCurve = .continuous } self.doneButtonSolidBackgroundNode.isUserInteractionEnabled = false - + self.doneButtonSolidTitleNode = ImmediateTextNode() self.doneButtonSolidTitleNode.alpha = 0.0 self.doneButtonSolidTitleNode.displaysAsynchronously = false self.doneButtonSolidTitleNode.isUserInteractionEnabled = false - + self.animationNode = SimpleAnimationNode(animationName: "premium_unlock", size: CGSize(width: 30.0, height: 30.0)) self.animationNode.customColor = .white self.animationNode.isHidden = true - + super.init() - + self.doneButton.isExclusiveTouch = true self.addSubnode(self.doneButtonBackgroundNode) - self.addSubnode(self.doneButtonTitleNode) - - self.addSubnode(self.doneButtonSolidBackgroundNode) - self.addSubnode(self.doneButtonSolidTitleNode) - + self.doneButtonBackgroundNode.contentView.addSubnode(self.doneButtonTitleNode) + + self.doneButtonBackgroundNode.contentView.addSubnode(self.doneButtonSolidBackgroundNode) + self.doneButtonBackgroundNode.contentView.addSubnode(self.doneButtonSolidTitleNode) + self.addSubnode(self.animationNode) - - self.addSubnode(self.doneButton) - - self.doneButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if strongSelf.isSolid { - strongSelf.doneButtonSolidBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonSolidBackgroundNode.alpha = 0.55 - strongSelf.doneButtonSolidTitleNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonSolidTitleNode.alpha = 0.55 - } else { - strongSelf.doneButtonBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonBackgroundNode.alpha = 0.55 - strongSelf.doneButtonTitleNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonTitleNode.alpha = 0.55 - - strongSelf.doneButtonSubtitle?.view?.layer.removeAnimation(forKey: "opacity") - strongSelf.doneButtonSubtitle?.view?.alpha = 0.55 - } - } else { - if strongSelf.isSolid { - strongSelf.doneButtonSolidBackgroundNode.alpha = 1.0 - strongSelf.doneButtonSolidBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - strongSelf.doneButtonSolidTitleNode.alpha = 1.0 - strongSelf.doneButtonSolidTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - } else { - strongSelf.doneButtonBackgroundNode.alpha = 1.0 - strongSelf.doneButtonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - strongSelf.doneButtonTitleNode.alpha = 1.0 - strongSelf.doneButtonTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - - strongSelf.doneButtonSubtitle?.view?.alpha = 1.0 - strongSelf.doneButtonSubtitle?.view?.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) - } - } - } - } - + + self.doneButtonBackgroundNode.contentView.addSubview(self.doneButton.view) + self.doneButton.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) } - + func setEnabled(_ enabled: Bool) { self.doneButton.alpha = enabled ? 1.0 : 0.4 self.doneButton.isUserInteractionEnabled = enabled } - + private var isSolid = false func setIsSolid(_ isSolid: Bool, transition: ContainedViewLayoutTransition) { guard self.isSolid != isSolid else { return } self.isSolid = isSolid - + transition.updateAlpha(node: self.doneButtonBackgroundNode, alpha: isSolid ? 0.0 : 1.0) transition.updateAlpha(node: self.doneButtonSolidBackgroundNode, alpha: isSolid ? 1.0 : 0.0) transition.updateAlpha(node: self.doneButtonTitleNode, alpha: isSolid ? 0.0 : 1.0) transition.updateAlpha(node: self.doneButtonSolidTitleNode, alpha: isSolid ? 1.0 : 0.0) } - + func updateTitle(_ title: String, theme: PresentationTheme) { - self.doneButtonTitleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: .white) - + self.doneButtonTitleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.dark ? .white : .black) + self.doneButtonSolidBackgroundNode.backgroundColor = theme.list.itemCheckColors.fillColor self.doneButtonSolidTitleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor) } - + func updateSize(_ size: CGSize) { let bounds = CGRect(origin: .zero, size: size) self.doneButtonBackgroundNode.frame = bounds - if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperOptionBackgroundNode { - backgroundNode.updateLayout(size: size) - } else if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperLightButtonBackgroundNode { - backgroundNode.updateLayout(size: size) - } + self.doneButtonBackgroundNode.updateLayout(size: size) self.doneButtonSolidBackgroundNode.frame = bounds - + self.doneButtonSolidBackgroundNode.layer.cornerRadius = size.height * 0.5 + let constrainedSize = CGSize(width: size.width - 44.0, height: size.height) let iconSize = CGSize(width: 30.0, height: 30.0) let doneTitleSize = self.doneButtonTitleNode.updateLayout(constrainedSize) - + var totalWidth = doneTitleSize.width if self.isLocked { totalWidth += iconSize.width + 1.0 } let titleOriginX = floorToScreenPixels((bounds.width - totalWidth) / 2.0) - + self.animationNode.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels((bounds.height - iconSize.height) / 2.0)), size: iconSize) - + var titleFrame = CGRect(origin: CGPoint(x: titleOriginX + totalWidth - doneTitleSize.width, y: floorToScreenPixels((bounds.height - doneTitleSize.height) / 2.0)), size: doneTitleSize).offsetBy(dx: bounds.minX, dy: bounds.minY) - + if let requiredLevel = self.requiredLevel { let subtitle: ComponentView if let current = self.doneButtonSubtitle { @@ -195,7 +156,7 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT subtitle = ComponentView() self.doneButtonSubtitle = subtitle } - + let subtitleSize = subtitle.update( transition: .immediate, component: AnyComponent( @@ -208,43 +169,36 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT environment: {}, containerSize: size ) - + if let view = subtitle.view { if view.superview == nil { view.isUserInteractionEnabled = false self.view.addSubview(view) } - + titleFrame.origin.y -= 8.0 - + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 3.0), size: subtitleSize) view.frame = subtitleFrame } } - + self.doneButtonTitleNode.frame = titleFrame - + let _ = self.doneButtonSolidTitleNode.updateLayout(constrainedSize) self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame - + self.doneButton.frame = bounds } - + var dark: Bool = false { didSet { if self.dark != oldValue { - self.doneButtonBackgroundNode.removeFromSupernode() - if self.dark { - self.doneButtonBackgroundNode = WallpaperOptionBackgroundNode(enableSaturation: true) - } else { - self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() - } - self.doneButtonBackgroundNode.cornerRadius = 14.0 - self.insertSubnode(self.doneButtonBackgroundNode, at: 0) + self.doneButtonBackgroundNode.isDark = self.dark } } } - + private var previousActionTime: Double? @objc func pressed() { let currentTime = CACurrentMediaTime() @@ -255,10 +209,10 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT self.action() } } - + private var theme: PresentationTheme private let strings: PresentationStrings - + public var cancelButtonType: WallpaperGalleryToolbarCancelButtonType { didSet { self.updateThemeAndStrings(theme: self.theme, strings: self.strings) @@ -269,44 +223,42 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT self.updateThemeAndStrings(theme: self.theme, strings: self.strings) } } - + public var dark: Bool = false { didSet { self.applyButton.dark = self.dark self.applyForBothButton.dark = self.dark } } - + private let applyButton: ButtonNode private let applyForBothButton: ButtonNode - + public var cancel: (() -> Void)? public var done: ((Bool) -> Void)? - + var requiredLevel: Int? { didSet { self.applyButton.requiredLevel = self.requiredLevel } } - + public init(theme: PresentationTheme, strings: PresentationStrings, cancelButtonType: WallpaperGalleryToolbarCancelButtonType = .cancel, doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set) { self.theme = theme self.strings = strings self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType - + self.applyButton = ButtonNode(strings: strings) self.applyForBothButton = ButtonNode(strings: strings) - + super.init() - + self.addSubnode(self.applyButton) - if case .setPeer = doneButtonType { - self.addSubnode(self.applyForBothButton) - } - + self.addSubnode(self.applyForBothButton) + self.updateThemeAndStrings(theme: theme, strings: strings) - + self.applyButton.action = { [weak self] in if let self { self.done?(false) @@ -318,26 +270,27 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT } } } - + public func setDoneEnabled(_ enabled: Bool) { self.applyButton.setEnabled(enabled) self.applyForBothButton.setEnabled(enabled) } - + private var isSolid = false public func setDoneIsSolid(_ isSolid: Bool, transition: ContainedViewLayoutTransition) { guard self.isSolid != isSolid else { return } - + self.isSolid = isSolid self.applyButton.setIsSolid(isSolid, transition: transition) self.applyForBothButton.setIsSolid(isSolid, transition: transition) } - + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme - + self.applyButton.isUserInteractionEnabled = true + let applyTitle: String var applyForBothTitle: String? = nil var applyForBothLocked = false @@ -358,35 +311,36 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT applyTitle = "" self.applyButton.isUserInteractionEnabled = false } - + self.applyButton.updateTitle(applyTitle, theme: theme) if let applyForBothTitle { self.applyForBothButton.updateTitle(applyForBothTitle, theme: theme) } self.applyForBothButton.isLocked = applyForBothLocked } - + public func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - let inset: CGFloat = 16.0 - let buttonHeight: CGFloat = 50.0 - + let inset: CGFloat = 30.0 + let buttonHeight: CGFloat = 52.0 + let spacing: CGFloat = 8.0 - + let applyFrame = CGRect(origin: CGPoint(x: inset, y: 2.0), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight)) let applyForBothFrame = CGRect(origin: CGPoint(x: inset, y: applyFrame.maxY + spacing), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight)) - + var showApplyForBothButton = false if case .setPeer = self.doneButtonType { showApplyForBothButton = true } transition.updateAlpha(node: self.applyForBothButton, alpha: showApplyForBothButton ? 1.0 : 0.0) - + self.applyForBothButton.isUserInteractionEnabled = showApplyForBothButton + self.applyButton.frame = applyFrame self.applyButton.updateSize(applyFrame.size) self.applyForBothButton.frame = applyForBothFrame self.applyForBothButton.updateSize(applyForBothFrame.size) } - + @objc func cancelPressed() { self.cancel?() } @@ -395,7 +349,7 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT public final class WallpaperGalleryOldToolbarNode: ASDisplayNode, WallpaperGalleryToolbar { private var theme: PresentationTheme private let strings: PresentationStrings - + public var cancelButtonType: WallpaperGalleryToolbarCancelButtonType { didSet { self.updateThemeAndStrings(theme: self.theme, strings: self.strings) @@ -406,119 +360,135 @@ public final class WallpaperGalleryOldToolbarNode: ASDisplayNode, WallpaperGalle self.updateThemeAndStrings(theme: self.theme, strings: self.strings) } } - - private let cancelButton = HighlightTrackingButtonNode() - private let cancelHighlightBackgroundNode = ASDisplayNode() - private let doneButton = HighlightTrackingButtonNode() - private let doneHighlightBackgroundNode = ASDisplayNode() - private let backgroundNode = NavigationBackgroundNode(color: .clear) - private let separatorNode = ASDisplayNode() - private let topSeparatorNode = ASDisplayNode() - + + private let cancelButton = ComponentView() + private let doneButton = ComponentView() + + private var cancelTitle: String = "" + private var doneTitle: String = "" + private var doneEnabled: Bool = true + private var validLayout: (CGSize, ContainerViewLayout)? + public var cancel: (() -> Void)? public var done: ((Bool) -> Void)? - + public init(theme: PresentationTheme, strings: PresentationStrings, cancelButtonType: WallpaperGalleryToolbarCancelButtonType = .cancel, doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set) { self.theme = theme self.strings = strings self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType - - self.cancelHighlightBackgroundNode.alpha = 0.0 - self.doneHighlightBackgroundNode.alpha = 0.0 - + super.init() - self.addSubnode(self.backgroundNode) - self.addSubnode(self.cancelHighlightBackgroundNode) - self.addSubnode(self.cancelButton) - self.addSubnode(self.doneHighlightBackgroundNode) - self.addSubnode(self.doneButton) - self.addSubnode(self.separatorNode) - self.addSubnode(self.topSeparatorNode) - self.updateThemeAndStrings(theme: theme, strings: strings) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.cancelHighlightBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.cancelHighlightBackgroundNode.alpha = 1.0 - } else { - strongSelf.cancelHighlightBackgroundNode.alpha = 0.0 - strongSelf.cancelHighlightBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - - self.doneButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.doneHighlightBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.doneHighlightBackgroundNode.alpha = 1.0 - } else { - strongSelf.doneHighlightBackgroundNode.alpha = 0.0 - strongSelf.doneHighlightBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) - self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside) } - + public func setDoneEnabled(_ enabled: Bool) { - self.doneButton.alpha = enabled ? 1.0 : 0.4 - self.doneButton.isUserInteractionEnabled = enabled + self.doneEnabled = enabled + if let (size, layout) = self.validLayout { + self.updateLayout(size: size, layout: layout, transition: .immediate) + } } - + public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme - self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate) - self.separatorNode.backgroundColor = theme.rootController.tabBar.separatorColor - self.topSeparatorNode.backgroundColor = theme.rootController.tabBar.separatorColor - self.cancelHighlightBackgroundNode.backgroundColor = theme.list.itemHighlightedBackgroundColor - self.doneHighlightBackgroundNode.backgroundColor = theme.list.itemHighlightedBackgroundColor - - let cancelTitle: String + switch self.cancelButtonType { case .cancel: - cancelTitle = strings.Common_Cancel + self.cancelTitle = strings.Common_Cancel case .discard: - cancelTitle = strings.WallpaperPreview_PatternPaternDiscard + self.cancelTitle = strings.WallpaperPreview_PatternPaternDiscard } - let doneTitle: String switch self.doneButtonType { case .set, .setPeer, .setChannel: - doneTitle = strings.Wallpaper_Set + self.doneTitle = strings.Wallpaper_Set case .proceed: - doneTitle = strings.Theme_Colors_Proceed + self.doneTitle = strings.Theme_Colors_Proceed case .apply: - doneTitle = strings.WallpaperPreview_PatternPaternApply + self.doneTitle = strings.WallpaperPreview_PatternPaternApply case .none: - doneTitle = "" - self.doneButton.isUserInteractionEnabled = false + self.doneTitle = "" + } + + if let (size, layout) = self.validLayout { + self.updateLayout(size: size, layout: layout, transition: .immediate) } - self.cancelButton.setTitle(cancelTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: []) - self.doneButton.setTitle(doneTitle, with: Font.regular(17.0), with: theme.list.itemPrimaryTextColor, for: []) } - + public func updateLayout(size: CGSize, layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.cancelButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: floor(size.width / 2.0), height: size.height)) - self.cancelHighlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: floor(size.width / 2.0), height: size.height)) - self.doneButton.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: size.width - floor(size.width / 2.0), height: size.height)) - self.doneHighlightBackgroundNode.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: size.width - floor(size.width / 2.0), height: size.height)) - self.separatorNode.frame = CGRect(origin: CGPoint(x: floor(size.width / 2.0), y: 0.0), size: CGSize(width: UIScreenPixel, height: size.height + layout.intrinsicInsets.bottom)) - self.topSeparatorNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: UIScreenPixel)) - self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundNode.update(size: CGSize(width: size.width, height: size.height + layout.intrinsicInsets.bottom), transition: .immediate) - } - - @objc func cancelPressed() { - self.cancel?() - } - - @objc func donePressed() { - self.done?(false) + self.validLayout = (size, layout) + + let sideInset: CGFloat = 30.0 + let spacing: CGFloat = 24.0 + let buttonHeight: CGFloat = 52.0 + let buttonWidth = floor(max(1.0, size.width - sideInset * 2.0 - spacing) / 2.0) + let buttonY = floor((size.height - buttonHeight) / 2.0) + let cancelFrame = CGRect(x: sideInset, y: buttonY, width: buttonWidth, height: buttonHeight) + let doneFrame = CGRect(x: cancelFrame.maxX + spacing, y: buttonY, width: max(1.0, size.width - sideInset - cancelFrame.maxX - spacing), height: buttonHeight) + + let glassColor = UIColor(white: 1.0, alpha: 0.0) + let foregroundColor = self.theme.list.itemPrimaryTextColor + let componentTransition = ComponentTransition(transition) + + let cancelSize = self.cancelButton.update( + transition: componentTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .actualGlass, + color: glassColor, + foreground: foregroundColor, + pressedColor: glassColor, + cornerRadius: buttonHeight * 0.5 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(self.cancelTitle), + component: AnyComponent(Text(text: self.cancelTitle, font: Font.semibold(17.0), color: foregroundColor)) + ), + action: { [weak self] in + self?.cancel?() + } + )), + environment: {}, + containerSize: cancelFrame.size + ) + if let cancelView = self.cancelButton.view { + if cancelView.superview == nil { + self.view.addSubview(cancelView) + } + transition.updateFrame(view: cancelView, frame: CGRect(origin: cancelFrame.origin, size: cancelSize)) + } + + let doneIsVisible = !self.doneTitle.isEmpty + let doneSize = self.doneButton.update( + transition: componentTransition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .actualGlass, + color: glassColor, + foreground: foregroundColor, + pressedColor: glassColor, + cornerRadius: buttonHeight * 0.5 + ), + content: AnyComponentWithIdentity( + id: AnyHashable(self.doneTitle), + component: AnyComponent(Text(text: self.doneTitle, font: Font.semibold(17.0), color: foregroundColor)) + ), + isEnabled: self.doneEnabled && doneIsVisible, + tintWhenDisabled: false, + action: { [weak self] in + self?.done?(false) + } + )), + environment: {}, + containerSize: doneFrame.size + ) + if let doneView = self.doneButton.view { + if doneView.superview == nil { + self.view.addSubview(doneView) + } + transition.updateFrame(view: doneView, frame: CGRect(origin: doneFrame.origin, size: doneSize)) + componentTransition.setAlpha(view: doneView, alpha: doneIsVisible ? (self.doneEnabled ? 1.0 : 0.4) : 0.0) + doneView.isUserInteractionEnabled = self.doneEnabled && doneIsVisible + } } } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperOptionButtonNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperOptionButtonNode.swift index 4129277be3..2b9aa4b32a 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperOptionButtonNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperOptionButtonNode.swift @@ -5,6 +5,8 @@ import AsyncDisplayKit import SwiftSignalKit import CheckNode import AnimationUI +import ComponentFlow +import GlassBackgroundComponent public enum WallpaperOptionButtonValue { case check(Bool) @@ -68,31 +70,43 @@ final class WallpaperLightButtonBackgroundNode: ASDisplayNode { } final class WallpaperOptionBackgroundNode: ASDisplayNode { - private let backgroundNode: NavigationBackgroundNode - var enableSaturation: Bool { didSet { - self.backgroundNode.updateColor(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: self.enableSaturation, transition: .immediate) } } - init(enableSaturation: Bool = false) { + var isDark: Bool { + didSet { + if self.isDark != oldValue, let validSize = self.validSize { + self.updateLayout(size: validSize) + } + } + } + + private var validSize: CGSize? + + init(enableSaturation: Bool = false, isDark: Bool = true) { self.enableSaturation = enableSaturation - self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: enableSaturation) + self.isDark = isDark super.init() - - self.clipsToBounds = true - self.isUserInteractionEnabled = false - - self.addSubnode(self.backgroundNode) + + self.setViewBlock({ + return GlassBackgroundView() + }) + } + + private var glassView: GlassBackgroundView { + return self.view as! GlassBackgroundView } - func updateLayout(size: CGSize) { - let frame = CGRect(origin: .zero, size: size) - self.backgroundNode.frame = frame - - self.backgroundNode.update(size: size, transition: .immediate) + var contentView: UIView { + return self.glassView.contentView + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + self.validSize = size + self.glassView.update(size: size, cornerRadius: size.height * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel), isInteractive: true, transition: ComponentTransition(transition)) } } @@ -105,9 +119,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { var enableSaturation: Bool = false { didSet { - if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode { - backgroundNode.enableSaturation = self.enableSaturation - } + self.backgroundNode.enableSaturation = self.enableSaturation } } @@ -117,105 +129,70 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { if self.dark != oldValue { self.backgroundNode.removeFromSupernode() if self.dark { - self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation) + self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation, isDark: true) } else { - self.backgroundNode = WallpaperLightButtonBackgroundNode() + self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation, isDark: false) } self.insertSubnode(self.backgroundNode, at: 0) } } } - private var backgroundNode: ASDisplayNode + private var backgroundNode: WallpaperOptionBackgroundNode private let iconNode: ASImageNode private let textNode: ImmediateTextNode private var animationNode: AnimationNode? func setIcon(_ image: UIImage?) { - self.iconNode.image = generateTintedImage(image: image, color: .white) + self.iconNode.image = generateTintedImage(image: image, color: self.dark ? .white : .black) } init(content: Content, dark: Bool) { self.content = content self.dark = dark - if dark { - self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation) - } else { - self.backgroundNode = WallpaperLightButtonBackgroundNode() - } + self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation, isDark: dark) self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false self.iconNode.contentMode = .center + let iconColor: UIColor = dark ? .white : .black + var title: String switch content { case let .text(text): title = text case let .icon(icon, _): title = "" - self.iconNode.image = generateTintedImage(image: icon, color: .white) + self.iconNode.image = generateTintedImage(image: icon, color: .black) case let .dayNight(isNight): title = "" - let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: [:], scale: 1.0) + let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: ["__allcolors__": iconColor], scale: 1.0) animationNode.speed = 1.66 animationNode.isUserInteractionEnabled = false self.animationNode = animationNode } self.textNode = ImmediateTextNode() - self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: .white) + self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: iconColor) super.init() self.isExclusiveTouch = true self.addSubnode(self.backgroundNode) - self.addSubnode(self.iconNode) - self.addSubnode(self.textNode) + self.backgroundNode.contentView.addSubnode(self.iconNode) + self.backgroundNode.contentView.addSubnode(self.textNode) if let animationNode = self.animationNode { - self.addSubnode(animationNode) - } - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.backgroundNode.alpha = 0.4 - - strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") - strongSelf.iconNode.alpha = 0.4 - - strongSelf.textNode.layer.removeAnimation(forKey: "opacity") - strongSelf.textNode.alpha = 0.4 - -// if let animationNode = strongSelf.animationNode { -// animationNode.layer.removeAnimation(forKey: "opacity") -// animationNode.alpha = 0.4 -// } - } else { - strongSelf.backgroundNode.alpha = 1.0 - strongSelf.backgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - strongSelf.iconNode.alpha = 1.0 - strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - strongSelf.textNode.alpha = 1.0 - strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - -// if let animationNode = strongSelf.animationNode { -// animationNode.alpha = 1.0 -// animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) -// } - } - } + self.backgroundNode.contentView.addSubnode(animationNode) } } func setIsNight(_ isNight: Bool) { - self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:]) + let iconColor: UIColor = self.dark ? .white : .black + self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: ["__allcolors__": iconColor]) self.animationNode?.speed = 1.66 self.animationNode?.playOnce() @@ -225,11 +202,6 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { } } - var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { - didSet { - } - } - private var textSize: CGSize? override func measure(_ constrainedSize: CGSize) -> CGSize { switch self.content { @@ -249,11 +221,7 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { let size = self.bounds.size self.backgroundNode.frame = self.bounds - if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode { - backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) - } else if let backgroundNode = self.backgroundNode as? WallpaperLightButtonBackgroundNode { - backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) - } + self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size) self.backgroundNode.cornerRadius = size.height / 2.0 self.iconNode.frame = self.bounds @@ -303,7 +271,16 @@ public final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { public var title: String { didSet { - self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white) + self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: self.dark ? .white : .black) + } + } + + public var dark: Bool = false { + didSet { + let color: UIColor = self.dark ? .white : .black + self.checkNode.theme = CheckNodeTheme(backgroundColor: color, strokeColor: .clear, borderColor: color, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5) + self.textNode.attributedText = NSAttributedString(string: self.title, font: Font.medium(13), textColor: self.dark ? .white : .black) + let _ = self.textNode.updateLayout(CGSize(width: 160.0, height: 40.0)) } } @@ -324,8 +301,6 @@ public final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { super.init() - self.clipsToBounds = true - self.cornerRadius = 14.0 self.isExclusiveTouch = true switch value { @@ -345,32 +320,9 @@ public final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { self.addSubnode(self.backgroundNode) - self.addSubnode(self.checkNode) - self.addSubnode(self.textNode) - self.addSubnode(self.colorNode) - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.backgroundNode.alpha = 0.4 - - strongSelf.colorNode.layer.removeAnimation(forKey: "opacity") - strongSelf.colorNode.alpha = 0.4 - } else { - strongSelf.backgroundNode.alpha = 1.0 - strongSelf.backgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - - strongSelf.colorNode.alpha = 1.0 - strongSelf.colorNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - - public var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { - didSet { - } + self.backgroundNode.contentView.addSubnode(self.checkNode) + self.backgroundNode.contentView.addSubnode(self.textNode) + self.backgroundNode.contentView.addSubnode(self.colorNode) } public var color: UIColor? { diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift index 4e6e46b1db..73238caf47 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperPatternPanelNode.swift @@ -8,7 +8,6 @@ import TelegramPresentationData import LegacyComponents import AccountContext import MergeLists -import Postbox import SettingsThemeWallpaperNode private let itemSize = CGSize(width: 88.0, height: 88.0) @@ -256,9 +255,9 @@ public final class WallpaperPatternPanelNode: ASDisplayNode { self.addSubnode(self.titleNode) self.addSubnode(self.labelNode) - self.disposable = ((telegramWallpapers(postbox: context.account.postbox, network: context.account.network) + self.disposable = ((context.engine.themes.wallpapers() |> map { wallpapers -> [TelegramWallpaper] in - var existingIds = Set() + var existingIds = Set() return wallpapers.filter { wallpaper in if case let .file(file) = wallpaper, wallpaper.isPattern, file.file.mimeType != "image/webp" { @@ -294,6 +293,7 @@ public final class WallpaperPatternPanelNode: ASDisplayNode { self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.showsVerticalScrollIndicator = false self.scrollNode.view.alwaysBounceHorizontal = true + self.scrollNode.view.scrollsToTop = false let sliderView = TGPhotoEditorSliderView() sliderView.disableSnapToPositions = true diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 0871ba3e2e..6f7d3abdad 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -108,7 +108,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina public enum Mode { case `default` case peer(EnginePeer) - + var galleryMode: WallpaperGalleryController.Mode { switch self { case .default: @@ -117,7 +117,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina return .peer(peer, false) } } - + var colorPickerMode: ThemeAccentColorController.ResultMode { switch self { case .default: @@ -127,61 +127,61 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } } } - + private var controllerNode: ThemeColorsGridControllerNode { return self.displayNode as! ThemeColorsGridControllerNode } - + private let _ready = Promise() public override var ready: Promise { return self._ready } - + private let context: AccountContext let mode: Mode - + private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - + private var validLayout: ContainerViewLayout? - + private var previousContentOffset: GridNodeVisibleContentOffset? - + fileprivate var mainButtonState: AttachmentMainButtonState? - + var pushController: (ViewController) -> Void = { _ in } var dismissControllers: (() -> Void)? var openGallery: (() -> Void)? - + public init(context: AccountContext, mode: Mode = .default, canDelete: Bool = false) { self.context = context self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)) - + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - + self.scrollToTop = { [weak self] in if let strongSelf = self { strongSelf.controllerNode.scrollToTop() } } - + self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { let previousTheme = strongSelf.presentationData.theme let previousStrings = strongSelf.presentationData.strings - + strongSelf.presentationData = presentationData - + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { strongSelf.updateThemeAndStrings() } } }) - + if case .peer = mode { self.title = self.presentationData.strings.Conversation_Theme_ChooseColorTitle self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) @@ -189,40 +189,40 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } else { self.title = self.presentationData.strings.WallpaperColors_Title } - + self.pushController = { [weak self] controller in self?.push(controller) } - + self.mainButtonState = AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false) } - + required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + deinit { self.presentationDataDisposable?.dispose() } - + @objc private func cancelPressed() { self.dismiss() } - + @objc private func customPressed() { self.presentColorPicker() } - + private func updateThemeAndStrings() { self.title = self.presentationData.strings.WallpaperColors_Title self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData, style: .glass), transition: .immediate) - + if self.isNodeLoaded { self.controllerNode.updatePresentationData(self.presentationData) } } - + private func presentColorPicker() { let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) |> take(1) @@ -231,7 +231,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina return } let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings - + let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered let themeReference: PresentationThemeReference if autoNightModeTriggered { @@ -239,7 +239,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina } else { themeReference = settings.theme } - + let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode) controller.completion = { [weak self, weak controller] in if let strongSelf = self { @@ -268,7 +268,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina strongSelf.pushController(controller) }) } - + public override func loadDisplayNode() { var dark = false if case .default = self.mode { @@ -285,7 +285,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina strongSelf.presentColorPicker() } }) - + let transitionOffset: CGFloat switch self.mode { case .default: @@ -293,7 +293,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina case .peer: transitionOffset = 2.0 } - + self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in if let strongSelf = self { var previousContentOffsetValue: CGFloat? @@ -312,29 +312,29 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina case .unknown, .none: strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) } - + strongSelf.previousContentOffset = offset } } - + self._ready.set(self.controllerNode.ready.get()) - + self.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) - + self.displayNodeDidLoad() } - + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } - + @objc fileprivate func mainButtonPressed() { self.dismiss(animated: true) self.openGallery?() } - + public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var parentController: () -> ViewController? = { @@ -346,7 +346,7 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } public var isMinimized: Bool = false - + public var mediaPickerContext: AttachmentMediaPickerContext? { return ThemeColorsGridContext(controller: self) } @@ -354,15 +354,15 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina private final class ThemeColorsGridContext: AttachmentMediaPickerContext { private weak var controller: ThemeColorsGridController? - + public var mainButtonState: Signal { return .single(self.controller?.mainButtonState) } - + init(controller: ThemeColorsGridController) { self.controller = controller } - + func mainButtonAction() { self.controller?.mainButtonPressed() } @@ -376,9 +376,7 @@ public func standaloneColorPickerController( push: @escaping (ViewController) -> Void, openGallery: @escaping () -> Void ) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { - return nil - }) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false) controller.requestController = { _, present in let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer)) colorPickerController.pushController = { controller in diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift index 242cffb640..c6ff0f4561 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift @@ -49,12 +49,16 @@ final class ThemeColorsGridControllerItemNode: GridItemNode { self.wallpaperNode = SettingsThemeWallpaperNode(displayLoading: false) super.init() + self.clipsToBounds = true + self.addSubnode(self.wallpaperNode) } override func didLoad() { super.didLoad() + self.view.layer.cornerRadius = 16.0 + self.view.isExclusiveTouch = true self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift index 315d529ec3..4bed625ea8 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift @@ -74,7 +74,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let ready = ValuePromise() private var topBackgroundNode: ASDisplayNode - private var separatorNode: ASDisplayNode + private let maskNode: ASImageNode private let customColorItemNode: ItemListActionItemNode private var customColorItem: ItemListActionItem @@ -105,8 +105,8 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.topBackgroundNode = ASDisplayNode() self.topBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false self.customColorItemNode = ItemListActionItemNode() self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { @@ -124,12 +124,13 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { if case .default = controller.mode { self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor self.gridNode.addSubnode(self.topBackgroundNode) - self.gridNode.addSubnode(self.separatorNode) self.gridNode.addSubnode(self.customColorItemNode) } else { self.backgroundColor = presentationData.theme.list.plainBackgroundColor } self.addSubnode(self.gridNode) + self.gridNode.addSubnode(self.maskNode) + self.maskNode.image = PresentationResourcesItemList.cornersImage(presentationData.theme, top: true, bottom: true, glass: true) let previousEntries = Atomic<[ThemeColorsGridControllerEntry]?>(value: nil) @@ -227,6 +228,27 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { } } self.gridNode.view.addGestureRecognizer(tapRecognizer) + + self.gridNode.presentationLayoutUpdated = { [weak self] gridLayout, transition in + if let strongSelf = self, let (layout, _) = strongSelf.validLayout { + let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) + let maskSideInset: CGFloat = layout.size.width >= 320.0 ? sideInset : 0.0 + + let maskY: CGFloat + if let controller = strongSelf.controller, case .default = controller.mode { + let buttonTopInset: CGFloat = 32.0 + let buttonHeight: CGFloat = 44.0 + let buttonBottomInset: CGFloat = 35.0 + let buttonInset = buttonTopInset + buttonHeight + buttonBottomInset + let buttonOffset = buttonInset + 10.0 + maskY = -buttonOffset + buttonInset + } else { + maskY = 0.0 + } + + transition.updateFrame(node: strongSelf.maskNode, frame: CGRect(origin: CGPoint(x: maskSideInset, y: maskY), size: CGSize(width: layout.size.width - maskSideInset * 2.0, height: gridLayout.contentSize.height + 10.0))) + } + } } @@ -263,7 +285,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor self.topBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor - self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.maskNode.image = PresentationResourcesItemList.cornersImage(presentationData.theme, top: true, bottom: true, glass: true) self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentColorPicker() @@ -297,6 +319,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { return } let hadValidLayout = self.validLayout != nil + self.validLayout = (layout, navigationBarHeight) var insets = layout.insets(options: [.input]) insets.top += navigationBarHeight @@ -304,42 +327,23 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { insets.right = layout.safeInsets.right let scrollIndicatorInsets = insets - let itemsPerRow: Int - if case .compact = layout.metrics.widthClass { - switch layout.orientation { - case .portrait: - itemsPerRow = 3 - case .landscape: - itemsPerRow = 5 - } - } else { - itemsPerRow = 3 - } + let padding: CGFloat = 12.0 + let minSpacing: CGFloat = 6.0 let referenceImageSize: CGSize let screenWidth = min(layout.size.width, layout.size.height) - if screenWidth >= 375.0 { + if screenWidth >= 390.0 { referenceImageSize = CGSize(width: 108.0, height: 108.0) } else { referenceImageSize = CGSize(width: 91.0, height: 91.0) } - let width = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - let imageSize: CGSize - let spacing: CGFloat - var fillWidth: Bool? - if case .peer = controller.mode { - spacing = 1.0 - - let itemWidth = floorToScreenPixels((width - spacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) - imageSize = CGSize(width: itemWidth, height: itemWidth) - fillWidth = true - } else { - let minSpacing = 8.0 - - imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((width - CGFloat(itemsPerRow + 1) * minSpacing) / CGFloat(itemsPerRow)), height: referenceImageSize.height)) - spacing = floor((width - CGFloat(itemsPerRow) * imageSize.width) / CGFloat(itemsPerRow + 1)) - } + let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) + let gridWidth = layout.size.width - sideInset * 2.0 + let imageCount = max(2, Int((gridWidth - padding * 2.0) / referenceImageSize.width)) + let itemWidth = floorToScreenPixels((gridWidth - padding * 2.0 - CGFloat(imageCount - 1) * minSpacing) / CGFloat(imageCount)) + let imageSize = CGSize(width: itemWidth, height: itemWidth) + let spacing = floorToScreenPixels((gridWidth - padding * 2.0 - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount - 1)) let buttonTopInset: CGFloat = 32.0 let buttonHeight: CGFloat = 44.0 @@ -349,26 +353,27 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { var buttonOffset = buttonInset + 10.0 var listInsets = insets - if case .default = controller.mode { - if layout.size.width >= 375.0 { - let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - listInsets.left += inset - listInsets.right += inset - - if self.leftOverlayNode.supernode == nil { - self.gridNode.addSubnode(self.leftOverlayNode) - } - if self.rightOverlayNode.supernode == nil { - self.gridNode.addSubnode(self.rightOverlayNode) - } - } else { - if self.leftOverlayNode.supernode != nil { - self.leftOverlayNode.removeFromSupernode() - } - if self.rightOverlayNode.supernode != nil { - self.rightOverlayNode.removeFromSupernode() - } + if layout.size.width >= 320.0 { + listInsets.left = sideInset + listInsets.right = sideInset + + if self.leftOverlayNode.supernode == nil { + self.gridNode.addSubnode(self.leftOverlayNode) } + if self.rightOverlayNode.supernode == nil { + self.gridNode.addSubnode(self.rightOverlayNode) + } + } else { + if self.leftOverlayNode.supernode != nil { + self.leftOverlayNode.removeFromSupernode() + } + if self.rightOverlayNode.supernode != nil { + self.rightOverlayNode.removeFromSupernode() + } + } + + if case .default = controller.mode { + self.customColorItemNode.isHidden = false } else { self.customColorItemNode.isHidden = true buttonOffset = 0.0 @@ -381,19 +386,19 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { colorApply(false) transition.updateFrame(node: self.topBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) transition.updateFrame(node: self.customColorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset), size: colorLayout.contentSize)) - self.leftOverlayNode.frame = CGRect(x: 0.0, y: -buttonOffset, width: listInsets.left, height: buttonTopInset + colorLayout.contentSize.height + UIScreenPixel) - self.rightOverlayNode.frame = CGRect(x: layout.size.width - listInsets.right, y: -buttonOffset, width: listInsets.right, height: buttonTopInset + colorLayout.contentSize.height + UIScreenPixel) + self.leftOverlayNode.frame = CGRect(x: 0.0, y: -buttonOffset, width: listInsets.left, height: buttonTopInset + colorLayout.contentSize.height + 10000.0) + self.rightOverlayNode.frame = CGRect(x: layout.size.width - listInsets.right, y: -buttonOffset, width: listInsets.right, height: buttonTopInset + colorLayout.contentSize.height + 10000.0) insets.top += spacing + buttonInset - - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: fillWidth, lineSpacing: spacing, itemSpacing: fillWidth != nil ? spacing : nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + listInsets.top = insets.top + listInsets.left += 3.0 + listInsets.right += 3.0 self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: listInsets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - self.validLayout = (layout, navigationBarHeight) if !hadValidLayout { self.dequeueTransitions() } @@ -406,7 +411,6 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) self.topBackgroundNode.layer.animatePosition(from: self.topBackgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.topBackgroundNode.layer.position, duration: duration) - self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration) self.customColorItemNode.layer.animatePosition(from: self.customColorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.customColorItemNode.layer.position, duration: duration) } } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index 1fda3c4cb0..da424a986a 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import LegacyComponents @@ -116,7 +115,7 @@ public final class ThemeGridController: ViewController { if let isEmpty = self.isEmpty, isEmpty { } else { if self.editingMode { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) } else { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) } @@ -360,7 +359,7 @@ public final class ThemeGridController: ViewController { }) }).start() - let _ = (telegramWallpapers(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network) + let _ = (strongSelf.context.engine.themes.wallpapers() |> deliverOnMainQueue).start(completed: { [weak self, weak controller] in controller?.dismiss() if let strongSelf = self { @@ -479,7 +478,7 @@ public final class ThemeGridController: ViewController { @objc func editPressed() { self.editingMode = true - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)) self.controllerNode.updateState { state in var state = state state.editing = true diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift index c65730aeb2..6cd5b48ced 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import AccountContext import GridMessageSelectionNode import SettingsThemeWallpaperNode @@ -16,7 +15,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if let image = cachedBorderImages[key] { return image } else { - let image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in + let image = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -42,7 +41,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec context.setLineWidth(lineWidth) context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) } - })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10) + })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 16) cachedBorderImages[key] = image return image } @@ -114,7 +113,7 @@ final class ThemeGridControllerItemNode: GridItemNode { override func didLoad() { super.didLoad() - self.view.layer.cornerRadius = 10.0 + self.view.layer.cornerRadius = 16.0 self.view.isExclusiveTouch = true self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 5d59dd666e..4ca22beb39 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import UniversalMediaPlayer @@ -321,7 +320,7 @@ final class ThemeGridControllerNode: ASDisplayNode { self.addSubnode(self.gridNode) self.gridNode.addSubnode(self.maskNode) - self.maskNode.image = PresentationResourcesItemList.cornersImage(presentationData.theme, top: true, bottom: true) + self.maskNode.image = PresentationResourcesItemList.cornersImage(presentationData.theme, top: true, bottom: true, glass: true) let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil) let interaction = ThemeGridControllerInteraction(openWallpaper: { [weak self] wallpaper in @@ -578,7 +577,7 @@ final class ThemeGridControllerNode: ASDisplayNode { let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) var listInsets = layout.safeInsets - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { listInsets.left = sideInset listInsets.right = sideInset } @@ -597,7 +596,7 @@ final class ThemeGridControllerNode: ASDisplayNode { transition.updateFrame(node: strongSelf.resetDescriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0 + resetLayout.contentSize.height), size: resetDescriptionLayout.contentSize)) let maskSideInset = strongSelf.leftOverlayNode.frame.maxX - strongSelf.maskNode.frame = CGRect(origin: CGPoint(x: maskSideInset, y: strongSelf.separatorNode.frame.minY + UIScreenPixel + 4.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: gridLayout.contentSize.height + 6.0)) + strongSelf.maskNode.frame = CGRect(origin: CGPoint(x: maskSideInset, y: strongSelf.separatorNode.frame.minY + UIScreenPixel), size: CGSize(width: layout.size.width - sideInset * 2.0, height: gridLayout.contentSize.height + 10.0)) } } } @@ -634,7 +633,7 @@ final class ThemeGridControllerNode: ASDisplayNode { switch self.mode { case .generic: self.wallpapersPromise.set(combineLatest(queue: .mainQueue(), - telegramWallpapers(postbox: self.context.account.postbox, network: self.context.account.network), + self.context.engine.themes.wallpapers(), self.context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.wallapersState]) ) |> map { remoteWallpapers, sharedData -> [Wallpaper] in @@ -767,22 +766,23 @@ final class ThemeGridControllerNode: ASDisplayNode { var scrollIndicatorInsets = insets + let padding: CGFloat = 12.0 let minSpacing: CGFloat = 6.0 + let referenceImageSize: CGSize let screenWidth = min(layout.size.width, layout.size.height) if screenWidth >= 390.0 { - referenceImageSize = CGSize(width: 112.0, height: 150.0) + referenceImageSize = CGSize(width: 110.0, height: 150.0) } else { - referenceImageSize = CGSize(width: 91.0, height: 161.0) + referenceImageSize = CGSize(width: 90.0, height: 150.0) } - let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) let gridWidth = layout.size.width - sideInset * 2.0 - let imageCount = Int((gridWidth - minSpacing * 2.0) / (referenceImageSize.width)) - let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((gridWidth - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) - let spacing = floor((gridWidth - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) + let imageCount = Int((gridWidth - padding * 2.0) / (referenceImageSize.width)) + let imageSize = referenceImageSize.aspectFilled(CGSize(width: floorToScreenPixels((gridWidth - padding * 2.0 - CGFloat(imageCount - 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) + let spacing = floorToScreenPixels((gridWidth - padding * 2.0 - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount - 1)) let makeColorLayout = self.colorItemNode.asyncLayout() let makeGalleryLayout = (self.galleryItemNode as? ItemListActionItemNode)?.asyncLayout() @@ -791,7 +791,7 @@ final class ThemeGridControllerNode: ASDisplayNode { let makeDescriptionLayout = self.descriptionItemNode.asyncLayout() var listInsets = insets - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { listInsets.left = sideInset listInsets.right = sideInset if self.leftOverlayNode.supernode == nil { @@ -851,10 +851,10 @@ final class ThemeGridControllerNode: ASDisplayNode { } let buttonOffset = buttonInset + 10.0 - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 504.0))) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - var originY = -buttonOffset + buttonTopInset + var originY = -buttonOffset + buttonTopInset - 4.0 if !isChannel { transition.updateFrame(node: self.colorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: colorLayout.contentSize)) originY += colorLayout.contentSize.height @@ -877,7 +877,9 @@ final class ThemeGridControllerNode: ASDisplayNode { self.rightOverlayNode.frame = CGRect(x: layout.size.width - listInsets.right, y: -buttonOffset, width: listInsets.right, height: buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height + 10000.0) insets.top += spacing + buttonInset - listInsets.top = insets.top + listInsets.top = insets.top + 4.0 + listInsets.left += 3.0 + listInsets.right += 3.0 if self.currentState.editing { let panelHeight: CGFloat diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift deleted file mode 100644 index 842c90037f..0000000000 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift +++ /dev/null @@ -1,258 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Postbox -import Display -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import ListSectionHeaderNode - -private class ThemeGridColorNode: HighlightableButtonNode { - let action: () -> Void - - init(color: WallpaperSearchColor, strokeColor: UIColor, dark: Bool, action: @escaping (WallpaperSearchColor) -> Void) { - self.action = { - action(color) - } - - super.init() - - let image: UIImage? - if color == .white && !dark { - image = generateFilledCircleImage(diameter: 42.0, color: .white, strokeColor: strokeColor, strokeWidth: 1.0) - } else if color == .black && dark { - image = generateFilledCircleImage(diameter: 42.0, color: .black, strokeColor: strokeColor, strokeWidth: 1.0) - } else { - image = generateFilledCircleImage(diameter: 42.0, color: color.displayColor) - } - self.setImage(image, for: .normal) - } - - override func didLoad() { - super.didLoad() - - self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) - } - - @objc func pressed() { - self.action() - } -} - -private let inset: CGFloat = 15.0 -private let diameter: CGFloat = 42.0 - -final class ThemeGridSearchColorsNode: ASDisplayNode { - private var theme: PresentationTheme - private var strings: PresentationStrings - private let sectionHeaderNode: ListSectionHeaderNode - private let scrollNode: ASScrollNode - - private let colorSelected: (WallpaperSearchColor) -> Void - - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, colorSelected: @escaping (WallpaperSearchColor) -> Void) { - self.theme = theme - self.strings = strings - - self.colorSelected = colorSelected - - self.sectionHeaderNode = ListSectionHeaderNode(theme: theme) - self.sectionHeaderNode.title = strings.WallpaperSearch_ColorTitle.uppercased() - - self.scrollNode = ASScrollNode() - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.showsVerticalScrollIndicator = false - self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true - - super.init() - - self.addSubnode(self.sectionHeaderNode) - self.addSubnode(self.scrollNode) - - self.scrollNode.view.contentSize = CGSize(width: (inset + diameter) * CGFloat(WallpaperSearchColor.allCases.count) + inset, height: 71.0) - - for color in WallpaperSearchColor.allCases { - let colorNode = ThemeGridColorNode(color: color, strokeColor: theme.list.controlSecondaryColor, dark: theme.overallDarkAppearance, action: colorSelected) - self.scrollNode.addSubnode(colorNode) - } - } - - func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { - if self.theme !== theme || self.strings !== strings { - self.theme = theme - self.strings = strings - - self.sectionHeaderNode.title = strings.WallpaperSearch_ColorTitle.uppercased() - self.sectionHeaderNode.updateTheme(theme: theme) - } - } - - override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: constrainedSize.width, height: 100.0) - } - - private var validLayout: (CGSize, CGFloat, CGFloat)? - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { - let hadLayout = self.validLayout != nil - self.validLayout = (size, leftInset, rightInset) - - self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 29.0)) - self.sectionHeaderNode.updateLayout(size: CGSize(width: size.width, height: 29.0), leftInset: leftInset, rightInset: rightInset) - - var insets = UIEdgeInsets() - insets.left += leftInset - insets.right += rightInset - - self.scrollNode.frame = CGRect(x: 0.0, y: 29.0, width: size.width, height: size.height - 29.0) - self.scrollNode.view.contentInset = insets - if !hadLayout { - self.scrollNode.view.contentOffset = CGPoint(x: -leftInset, y: 0.0) - } - - var offset: CGFloat = inset - if let subnodes = self.scrollNode.subnodes { - for node in subnodes { - node.frame = CGRect(x: offset, y: inset, width: diameter, height: diameter) - offset += diameter + inset - } - } - } -} - - -class ThemeGridSearchColorsItem: ListViewItem { - let account: Account - let theme: PresentationTheme - let strings: PresentationStrings - let colorSelected: (WallpaperSearchColor) -> Void - - let header: ListViewItemHeader? - - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, colorSelected: @escaping (WallpaperSearchColor) -> Void) { - self.account = account - self.theme = theme - self.strings = strings - self.colorSelected = colorSelected - self.header = nil - } - - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = ThemeGridSearchColorsItemNode() - let makeLayout = node.asyncLayout() - let (nodeLayout, nodeApply) = makeLayout(self, params, nextItem != nil) - node.contentSize = nodeLayout.contentSize - node.insets = nodeLayout.insets - - completion(node, nodeApply) - } - } - - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - if let nodeValue = node() as? ThemeGridSearchColorsItemNode { - let layout = nodeValue.asyncLayout() - async { - let (nodeLayout, apply) = layout(self, params, nextItem != nil) - Queue.mainQueue().async { - completion(nodeLayout, { info in - apply().1(info) - }) - } - } - } - } - } -} - -class ThemeGridSearchColorsItemNode: ListViewItemNode { - private let backgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode - private var colorsNode: ThemeGridSearchColorsNode? - - private var item: ThemeGridSearchColorsItem? - - required init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - super.init(layerBacked: false) - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - } - - override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { - if let item = self.item { - let makeLayout = self.asyncLayout() - let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil) - self.contentSize = nodeLayout.contentSize - self.insets = nodeLayout.insets - let _ = nodeApply() - } - } - - func asyncLayout() -> (_ item: ThemeGridSearchColorsItem, _ params: ListViewItemLayoutParams, _ last: Bool) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { - let currentItem = self.item - - return { [weak self] item, params, last in - let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 101.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)) - - return (nodeLayout, { [weak self] in - var updatedTheme: PresentationTheme? - if currentItem?.theme !== item.theme { - updatedTheme = item.theme - } - - return (nil, { _ in - if let strongSelf = self { - strongSelf.item = item - - if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor - } - - let colorsNode: ThemeGridSearchColorsNode - if let currentColorsNode = strongSelf.colorsNode { - colorsNode = currentColorsNode - colorsNode.updateThemeAndStrings(theme: item.theme, strings: item.strings) - } else { - colorsNode = ThemeGridSearchColorsNode(account: item.account, theme: item.theme, strings: item.strings, colorSelected: item.colorSelected) - strongSelf.colorsNode = colorsNode - strongSelf.addSubnode(colorsNode) - } - - colorsNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize) - colorsNode.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - - let separatorHeight = UIScreenPixel - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) - strongSelf.separatorNode.isHidden = true - } - }) - }) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) - } - - override public func headers() -> [ListViewItemHeader]? { - if let item = self.item { - return item.header.flatMap { [$0] } - } else { - return nil - } - } -} diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchContentNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchContentNode.swift deleted file mode 100644 index 17074511e3..0000000000 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchContentNode.swift +++ /dev/null @@ -1,834 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import SwiftSignalKit -import Postbox -import TelegramCore -import TelegramPresentationData -import MergeLists -import AccountContext -import SearchUI -import ChatListSearchItemHeader -import WebSearchUI -import SearchBarNode - -enum WallpaperSearchColor: CaseIterable { - case blue - case red - case orange - case yellow - case green - case teal - case purple - case pink - case brown - case black - case gray - case white - - var string: String { - switch self { - case .blue: - return "Blue" - case .red: - return "Red" - case .orange: - return "Orange" - case .yellow: - return "Yellow" - case .green: - return "Green" - case .teal: - return "Teal" - case .purple: - return "Purple" - case .pink: - return "Pink" - case .brown: - return "Brown" - case .black: - return "Black" - case .gray: - return "Gray" - case .white: - return "White" - } - } - - var displayColor: UIColor { - switch self { - case .blue: - return UIColor(rgb: 0x0076ff) - case .red: - return UIColor(rgb: 0xff0000) - case .orange: - return UIColor(rgb: 0xff8a00) - case .yellow: - return UIColor(rgb: 0xffca00) - case .green: - return UIColor(rgb: 0x00e432) - case .teal: - return UIColor(rgb: 0x1fa9ab) - case .purple: - return UIColor(rgb: 0x7300aa) - case .pink: - return UIColor(rgb: 0xf9bec5) - case .brown: - return UIColor(rgb: 0x734021) - case .black: - return UIColor(rgb: 0x000000) - case .gray: - return UIColor(rgb: 0x5c585f) - case .white: - return UIColor(rgb: 0xffffff) - } - } - - func localizedString(strings: PresentationStrings) -> String { - switch self { - case .blue: - return strings.WallpaperSearch_ColorBlue - case .red: - return strings.WallpaperSearch_ColorRed - case .orange: - return strings.WallpaperSearch_ColorOrange - case .yellow: - return strings.WallpaperSearch_ColorYellow - case .green: - return strings.WallpaperSearch_ColorGreen - case .teal: - return strings.WallpaperSearch_ColorTeal - case .purple: - return strings.WallpaperSearch_ColorPurple - case .pink: - return strings.WallpaperSearch_ColorPink - case .brown: - return strings.WallpaperSearch_ColorBrown - case .black: - return strings.WallpaperSearch_ColorBlack - case .gray: - return strings.WallpaperSearch_ColorGray - case .white: - return strings.WallpaperSearch_ColorWhite - } - } -} - -enum WallpaperSearchQuery: Equatable { - case generic(String) - case color(WallpaperSearchColor, String) - - var botQuery: String { - switch self { - case let .generic(query): - return query - case let .color(color, query): - return "#color\(color.string) \(query)" - } - } - - var query: String { - switch self { - case let .generic(query), let .color(_, query): - return query - } - } - - func updatedWithText(_ text: String) -> WallpaperSearchQuery { - switch self { - case .generic: - return .generic(text) - case let .color(color, _): - return .color(color, text) - } - } - - func updatedWithColor(_ color: WallpaperSearchColor?) -> WallpaperSearchQuery { - if let color = color { - switch self { - case let .generic(text): - return .color(color, text) - case let .color(_, text): - return .color(color, text) - } - } else { - switch self { - case .generic: - return self - case let .color(_, text): - return .generic(text) - } - } - } -} - -final class ThemeGridSearchInteraction { - let openResult: (ChatContextResult) -> Void - let selectColor: (WallpaperSearchColor) -> Void - let setSearchQuery: (WallpaperSearchQuery) -> Void - let deleteRecentQuery: (String) -> Void - - init(openResult: @escaping (ChatContextResult) -> Void, selectColor: @escaping (WallpaperSearchColor) -> Void, setSearchQuery: @escaping (WallpaperSearchQuery) -> Void, deleteRecentQuery: @escaping (String) -> Void) { - self.openResult = openResult - self.selectColor = selectColor - self.setSearchQuery = setSearchQuery - self.deleteRecentQuery = deleteRecentQuery - } -} - -private enum ThemeGridRecentEntryStableId: Hashable { - case colors - case query(String) -} - -private enum ThemeGridRecentEntry: Comparable, Identifiable { - case colors(PresentationTheme, PresentationStrings) - case query(Int, String) - - var stableId: ThemeGridRecentEntryStableId { - switch self { - case .colors: - return .colors - case let .query(_, query): - return .query(query) - } - } - - static func ==(lhs: ThemeGridRecentEntry, rhs: ThemeGridRecentEntry) -> Bool { - switch lhs { - case let .colors(lhsTheme, lhsStrings): - if case let .colors(rhsTheme, rhsStrings) = rhs { - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } - return true - } else { - return false - } - case let .query(lhsIndex, lhsQuery): - if case .query(lhsIndex, lhsQuery) = rhs { - return true - } else { - return false - } - } - } - - static func <(lhs: ThemeGridRecentEntry, rhs: ThemeGridRecentEntry) -> Bool { - switch lhs { - case .colors: - return true - case let .query(lhsIndex, _): - switch rhs { - case .colors: - return false - case let .query(rhsIndex, _): - return lhsIndex <= rhsIndex - } - } - } - - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ThemeGridSearchInteraction, header: ListViewItemHeader) -> ListViewItem { - switch self { - case let .colors(theme, strings): - return ThemeGridSearchColorsItem(account: account, theme: theme, strings: strings, colorSelected: { color in - interaction.selectColor(color) - }) - case let .query(_, query): - return WebSearchRecentQueryItem(account: account, theme: theme, strings: strings, query: query, tapped: { query in - interaction.setSearchQuery(.generic(query)) - }, deleted: { query in - interaction.deleteRecentQuery(query) - }, header: header) - } - } -} - -private struct ThemeGridSearchContainerRecentTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] -} - -private struct ThemeGridSearchEntry: Comparable, Identifiable { - let index: Int - let result: ChatContextResult - - static func ==(lhs: ThemeGridSearchEntry, rhs: ThemeGridSearchEntry) -> Bool { - return lhs.index == rhs.index && lhs.result == rhs.result - } - - static func <(lhs: ThemeGridSearchEntry, rhs: ThemeGridSearchEntry) -> Bool { - return lhs.index < rhs.index - } - - var stableId: Int { - return self.index - } - - func item(account: Account, theme: PresentationTheme, interaction: ThemeGridSearchInteraction) -> ThemeGridSearchItem { - return ThemeGridSearchItem(account: account, theme: theme, result: self.result, interaction: interaction) - } -} - -struct ThemeGridSearchContainerTransition { - let deletions: [Int] - let insertions: [GridNodeInsertItem] - let updates: [GridNodeUpdateItem] - let displayingResults: Bool - let isEmpty: Bool - let query: String -} - -private func themeGridSearchContainerPreparedRecentTransition(from fromEntries: [ThemeGridRecentEntry], to toEntries: [ThemeGridRecentEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, interaction: ThemeGridSearchInteraction, header: ListViewItemHeader) -> ThemeGridSearchContainerRecentTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, header: header), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, interaction: interaction, header: header), directionHint: nil) } - - return ThemeGridSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates) -} - -private func themeGridSearchContainerPreparedTransition(from fromEntries: [ThemeGridSearchEntry], to toEntries: [ThemeGridSearchEntry], displayingResults: Bool, account: Account, theme: PresentationTheme, isEmpty: Bool, query: String, interaction: ThemeGridSearchInteraction) -> ThemeGridSearchContainerTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices - let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, interaction: interaction), previousIndex: $0.2) } - let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, interaction: interaction)) } - - return ThemeGridSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, query: query) -} - -private struct ThemeGridSearchResult { - let query: String - let collection: ChatContextResultCollection - let items: [ChatContextResult] - let nextOffset: String? -} - -private struct ThemeGridSearchContext { - let result: ThemeGridSearchResult - let loadMoreIndex: String? -} - -final class ThemeGridSearchContentNode: SearchDisplayControllerContentNode { - private let context: AccountContext - - private let recentListNode: ListView - private let gridNode: GridNode - private let dimNode: ASDisplayNode - - private let emptyResultsTitleNode: ImmediateTextNode - private let emptyResultsTextNode: ImmediateTextNode - - private var enqueuedRecentTransitions: [(ThemeGridSearchContainerRecentTransition, Bool)] = [] - private var enqueuedTransitions: [(ThemeGridSearchContainerTransition, Bool)] = [] - private var validLayout: (ContainerViewLayout, CGFloat)? - - private var queryValue: WallpaperSearchQuery = .generic("") - private let queryPromise: Promise - private let searchDisposable = MetaDisposable() - private var recentDisposable: Disposable? - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - private let presentationDataPromise: Promise - - private let _isSearching = ValuePromise(false, ignoreRepeated: true) - override var isSearching: Signal { - return self._isSearching.get() - } - - init(context: AccountContext, openResult: @escaping (ChatContextResult) -> Void) { - self.context = context - self.queryPromise = Promise(self.queryValue) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.presentationData = presentationData - self.presentationDataPromise = Promise(self.presentationData) - - self.dimNode = ASDisplayNode() - self.recentListNode = ListViewImpl() - self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor - self.recentListNode.accessibilityPageScrolledString = { row, count in - return presentationData.strings.VoiceOver_ScrollStatus(row, count).string - } - - self.gridNode = GridNode() - - self.emptyResultsTitleNode = ImmediateTextNode() - self.emptyResultsTitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.SharedMedia_SearchNoResults, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.freeTextColor) - self.emptyResultsTitleNode.textAlignment = .center - self.emptyResultsTitleNode.isHidden = true - - self.emptyResultsTextNode = ImmediateTextNode() - self.emptyResultsTextNode.maximumNumberOfLines = 0 - self.emptyResultsTextNode.textAlignment = .center - self.emptyResultsTextNode.isHidden = true - - super.init() - - self.dimNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor - - self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - - self.addSubnode(self.dimNode) - self.addSubnode(self.recentListNode) - self.addSubnode(self.gridNode) - - self.addSubnode(self.emptyResultsTitleNode) - self.addSubnode(self.emptyResultsTextNode) - - let searchContext = Promise(nil) - let searchContextValue = Atomic(value: nil) - let updateSearchContext: ((ThemeGridSearchContext?) -> (ThemeGridSearchContext?, Bool)) -> Void = { f in - var shouldUpdate = false - let updated = searchContextValue.modify { current in - let (u, s) = f(current) - shouldUpdate = s - if s { - return u - } else { - return current - } - } - if shouldUpdate { - searchContext.set(.single(updated)) - } - } - - self.gridNode.isHidden = true - self.gridNode.visibleItemsUpdated = { visibleItems in - if let bottom = visibleItems.bottom { - if let context = searchContextValue.with({ $0 }), bottom.0 >= context.result.items.count - 8 { - updateSearchContext { previous in - guard let previous = previous else { - return (nil, false) - } - if previous.loadMoreIndex != nil { - return (previous, false) - } - guard let _ = previous.result.items.last else { - return (previous, false) - } - return (ThemeGridSearchContext(result: previous.result, loadMoreIndex: previous.result.nextOffset), true) - } - } - } - } - self.recentListNode.isHidden = false - - let previousSearchItems = Atomic<[ThemeGridSearchEntry]?>(value: nil) - - let interaction = ThemeGridSearchInteraction(openResult: { [weak self] result in - openResult(result) - if let strongSelf = self { - strongSelf.dismissInput?() - - let query = strongSelf.queryValue.query - if !query.isEmpty { - let _ = addRecentWallpaperSearchQuery(engine: strongSelf.context.engine, string: query).start() - } - } - }, selectColor: { [weak self] color in - self?.updateQuery({ $0.updatedWithColor(color) }, updateInterface: true) - }, setSearchQuery: { [weak self] query in - self?.dismissInput?() - self?.updateQuery({ _ in - return query - }, updateInterface: true) - }, deleteRecentQuery: { query in - let _ = removeRecentWallpaperSearchQuery(engine: context.engine, string: query).start() - }) - - let configuration = self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - - let foundItems = self.queryPromise.get() - |> mapToSignal { query -> Signal<([ThemeGridSearchEntry], Bool)?, NoError> in - let query = query.botQuery - guard !query.isEmpty else { - return .single(nil) - } - - let wallpaperQuery = "#wallpaper \(query)" - updateSearchContext { _ in - return (nil, true) - } - - return .single(([], true)) - |> then( - configuration - |> mapToSignal { configuration -> Signal in - guard let name = configuration.imageBotUsername else { - return .single(nil) - } - return context.engine.peers.resolvePeerByName(name: name, referrer: nil) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - |> mapToSignal { peer -> Signal in - if let peer = peer { - return .single(peer._asPeer()) - } else { - return .single(nil) - } - } - } - |> mapToSignal { peer -> Signal<([ThemeGridSearchEntry], Bool)?, NoError> in - if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let loadMore = searchContext.get() - |> mapToSignal { searchContext -> Signal<([ThemeGridSearchEntry], Bool)?, NoError> in - if let searchContext = searchContext { - if let _ = searchContext.loadMoreIndex, let nextOffset = searchContext.result.nextOffset { - let collection = searchContext.result.collection - let geoPoint = collection.geoPoint.flatMap { geoPoint -> (Double, Double) in - return (geoPoint.latitude, geoPoint.longitude) - } - return self.context.engine.messages.requestChatContextResults(botId: collection.botId, peerId: collection.peerId, query: searchContext.result.query, location: .single(geoPoint), offset: nextOffset) - |> map { results -> ChatContextResultCollection? in - return results?.results - } - |> `catch` { error -> Signal in - return .single(nil) - } - |> map { nextResults -> (ChatContextResultCollection, String?) in - var results: [ChatContextResult] = [] - var existingIds = Set() - for result in searchContext.result.items { - results.append(result) - existingIds.insert(result.id) - } - var nextOffset: String? - if let nextResults = nextResults { - for result in nextResults.results { - if !existingIds.contains(result.id) { - results.append(result) - existingIds.insert(result.id) - } - } - if let newNextOffset = nextResults.nextOffset, !newNextOffset.isEmpty { - nextOffset = newNextOffset - } - } - let merged = ChatContextResultCollection(botId: collection.botId, peerId: collection.peerId, query: collection.query, geoPoint: collection.geoPoint, queryId: nextResults?.queryId ?? collection.queryId, nextOffset: nextOffset ?? "", presentation: collection.presentation, switchPeer: collection.switchPeer, webView: collection.webView, results: results, cacheTimeout: collection.cacheTimeout) - return (merged, nextOffset) - } - |> mapToSignal { newCollection, nextOffset -> Signal<([ThemeGridSearchEntry], Bool)?, NoError> in - updateSearchContext { previous in - return (ThemeGridSearchContext(result: ThemeGridSearchResult(query: searchContext.result.query, collection: newCollection, items: newCollection.results, nextOffset: nextOffset), loadMoreIndex: nil), true) - } - return .complete() - } - } else { - var entries: [ThemeGridSearchEntry] = [] - var i = 0 - for result in searchContext.result.items { - entries.append(ThemeGridSearchEntry(index: i, result: result)) - i += 1 - } - return .single((entries, false)) - } - } else { - return .complete() - } - } - - - return (.complete() |> delay(0.1, queue: Queue.concurrentDefaultQueue())) - |> then( - requestContextResults(engine: context.engine, botId: user.id, query: wallpaperQuery, peerId: context.account.peerId, limit: 16) - |> map { results -> ChatContextResultCollection? in - return results?.results - } - |> map { collection -> ([ThemeGridSearchEntry], Bool)? in - guard let collection = collection else { - return nil - } - var entries: [ThemeGridSearchEntry] = [] - var i = 0 - for result in collection.results { - entries.append(ThemeGridSearchEntry(index: i, result: result)) - i += 1 - } - updateSearchContext { _ in - return (ThemeGridSearchContext(result: ThemeGridSearchResult(query: wallpaperQuery, collection: collection, items: collection.results, nextOffset: collection.nextOffset), loadMoreIndex: nil), true) - } - return (entries, false) - } - |> delay(0.2, queue: Queue.concurrentDefaultQueue()) - |> then(loadMore) - ) - } else { - return .single(nil) - } - } - ) - } - - let previousRecentItems = Atomic<[ThemeGridRecentEntry]?>(value: nil) - self.recentDisposable = (combineLatest(wallpaperSearchRecentQueries(engine: self.context.engine), self.presentationDataPromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] queries, presentationData in - if let strongSelf = self { - var entries: [ThemeGridRecentEntry] = [] - - entries.append(.colors(presentationData.theme, presentationData.strings)) - for i in 0 ..< queries.count { - entries.append(.query(i, queries[i])) - } - - let header = ChatListSearchItemHeader(type: .recentPeers, theme: presentationData.theme, strings: presentationData.strings, actionTitle: presentationData.strings.WebSearch_RecentSectionClear, action: { _ in - let _ = clearRecentWallpaperSearchQueries(engine: strongSelf.context.engine).start() - }) - - let previousEntries = previousRecentItems.swap(entries) - let transition = themeGridSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, account: context.account, theme: presentationData.theme, strings: presentationData.strings, interaction: interaction, header: header) - strongSelf.enqueueRecentTransition(transition, firstTime: previousEntries == nil) - } - }) - - self.searchDisposable.set((combineLatest(foundItems, self.presentationDataPromise.get(), self.queryPromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] entriesAndFlags, presentationData, query in - if let strongSelf = self { - strongSelf._isSearching.set(entriesAndFlags?.1 ?? false) - - let previousEntries = previousSearchItems.swap(entriesAndFlags?.0) - - var isEmpty = false - if let entriesAndFlags = entriesAndFlags { - isEmpty = entriesAndFlags.0.isEmpty && !entriesAndFlags.1 - } - - let firstTime = previousEntries == nil - let transition = themeGridSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, account: context.account, theme: presentationData.theme, isEmpty: isEmpty, query: query.query, interaction: interaction) - strongSelf.enqueueTransition(transition, firstTime: firstTime) - } - })) - - self.presentationDataDisposable = (context.sharedContext.presentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - - strongSelf.presentationData = presentationData - strongSelf.presentationDataPromise.set(.single(presentationData)) - - if previousTheme !== presentationData.theme { - strongSelf.updateTheme(theme: presentationData.theme) - } - } - }) - - self.recentListNode.beganInteractiveDragging = { [weak self] _ in - self?.dismissInput?() - } - - self.gridNode.scrollingInitiated = { [weak self] in - self?.dismissInput?() - } - } - - override func didLoad() { - super.didLoad() - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancel?() - } - } - - deinit { - self.searchDisposable.dispose() - self.recentDisposable?.dispose() - self.presentationDataDisposable?.dispose() - } - - private func updateTheme(theme: PresentationTheme) { - self.backgroundColor = theme.chatList.backgroundColor - self.dimNode.backgroundColor = theme.chatList.backgroundColor - self.recentListNode.verticalScrollIndicatorColor = theme.list.scrollIndicatorColor - } - - private func updateQuery(_ f: (WallpaperSearchQuery) -> (WallpaperSearchQuery), updateInterface: Bool = false) { - let query = f(self.queryValue) - if query != self.queryValue { - self.queryValue = query - self.queryPromise.set(.single(query)) - - if updateInterface { - let tokens: [SearchBarToken] - let text: String - let placeholder: String - switch query { - case let .generic(query): - tokens = [] - text = query - placeholder = self.presentationData.strings.Wallpaper_Search - case let .color(color, query): - let backgroundColor = color.displayColor - let foregroundColor: UIColor - let strokeColor: UIColor - if color == .white { - foregroundColor = .black - strokeColor = self.presentationData.theme.rootController.navigationSearchBar.inputClearButtonColor - } else { - foregroundColor = .white - strokeColor = color.displayColor - } - tokens = [SearchBarToken(id: 0, icon: UIImage(bundleImageName: "Settings/WallpaperSearchColorIcon"), title: color.localizedString(strings: self.presentationData.strings), style: SearchBarToken.Style(backgroundColor: backgroundColor, foregroundColor: foregroundColor, strokeColor: strokeColor), permanent: false)] - text = query - placeholder = self.presentationData.strings.Wallpaper_SearchShort - } - self.setQuery?(nil, tokens, text) - self.setPlaceholder?(placeholder) - } - } - } - - override func searchTextUpdated(text: String) { - self.updateQuery({ $0.updatedWithText(text) }) - } - - override func searchTextClearPrefix() { - self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) - } - - override func searchTextClearTokens() { - self.updateQuery({ $0.updatedWithColor(nil) }, updateInterface: true) - } - - private func enqueueRecentTransition(_ transition: ThemeGridSearchContainerRecentTransition, firstTime: Bool) { - self.enqueuedRecentTransitions.append((transition, firstTime)) - - if self.validLayout != nil { - while !self.enqueuedRecentTransitions.isEmpty { - self.dequeueRecentTransition() - } - } - } - - private func dequeueRecentTransition() { - if let (transition, firstTime) = self.enqueuedRecentTransitions.first { - self.enqueuedRecentTransitions.remove(at: 0) - - var options = ListViewDeleteAndInsertOptions() - if firstTime { - options.insert(.PreferSynchronousDrawing) - } else { - options.insert(.AnimateInsertion) - } - - self.recentListNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in - }) - } - } - - private func enqueueTransition(_ transition: ThemeGridSearchContainerTransition, firstTime: Bool) { - self.enqueuedTransitions.append((transition, firstTime)) - - if self.validLayout != nil { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - } - - private func dequeueTransition() { - if let (transition, _) = self.enqueuedTransitions.first { - self.enqueuedTransitions.remove(at: 0) - - let displayingResults = transition.displayingResults - self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.gridNode.isHidden = !displayingResults - strongSelf.recentListNode.isHidden = displayingResults - strongSelf.dimNode.isHidden = displayingResults - strongSelf.backgroundColor = strongSelf.presentationData.theme.chatList.backgroundColor - - strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.WebSearch_SearchNoResultsDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) - - let emptyResults = displayingResults && transition.isEmpty - strongSelf.emptyResultsTitleNode.isHidden = !emptyResults - strongSelf.emptyResultsTextNode.isHidden = !emptyResults - - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - }) - } - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - - let hadValidLayout = self.validLayout != nil - self.validLayout = (layout, navigationBarHeight) - - let minSpacing: CGFloat = 8.0 - let referenceImageSize: CGSize - let screenWidth = min(layout.size.width, layout.size.height) - if screenWidth >= 390.0 { - referenceImageSize = CGSize(width: 108.0, height: 230.0) - } else { - referenceImageSize = CGSize(width: 91.0, height: 161.0) - } - let imageCount = Int((layout.size.width - minSpacing * 2.0) / (referenceImageSize.width + minSpacing)) - let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) - let spacing = floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) - - let topInset = navigationBarHeight - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - - self.recentListNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight + spacing, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - let padding: CGFloat = 16.0 - let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) - let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) - - let insets = layout.insets(options: [.input]) - let emptyTextSpacing: CGFloat = 8.0 - let emptyTotalHeight = emptyTitleSize.height + emptyTextSize.height + emptyTextSpacing - let emptyTitleY = navigationBarHeight + floorToScreenPixels((layout.size.height - navigationBarHeight - max(insets.bottom, layout.intrinsicInsets.bottom) - emptyTotalHeight) / 2.0) - - transition.updateFrame(node: self.emptyResultsTitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTitleSize.width) / 2.0, y: emptyTitleY), size: emptyTitleSize)) - transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + padding + (layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyTitleY + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) - - if !hadValidLayout { - while !self.enqueuedRecentTransitions.isEmpty { - self.dequeueRecentTransition() - } - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - } - - private func clearRecentSearch() { - let _ = (self.context.engine.peers.clearRecentlySearchedPeers() |> deliverOnMainQueue).start() - } - - override func scrollToTop() { - if !self.gridNode.isHidden { - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - } else { - self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } - } -} diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchItem.swift deleted file mode 100644 index 12e53a36db..0000000000 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchItem.swift +++ /dev/null @@ -1,155 +0,0 @@ -import Foundation -import UIKit -import Display -import TelegramCore -import SwiftSignalKit -import AsyncDisplayKit -import Postbox -import TelegramPresentationData -import PhotoResources - -final class ThemeGridSearchItem: GridItem { - let account: Account - let theme: PresentationTheme - let result: ChatContextResult - let interaction: ThemeGridSearchInteraction - - let section: GridSection? = nil - - init(account: Account, theme: PresentationTheme, result: ChatContextResult, interaction: ThemeGridSearchInteraction) { - self.account = account - self.theme = theme - self.result = result - self.interaction = interaction - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = ThemeGridSearchItemNode() - node.setup(item: self) - return node - } - - func update(node: GridItemNode) { - guard let node = node as? ThemeGridSearchItemNode else { - assertionFailure() - return - } - node.setup(item: self) - } -} - -final class ThemeGridSearchItemNode: GridItemNode { - private let imageNode: TransformImageNode - - private(set) var item: ThemeGridSearchItem? - private var currentDimensions: CGSize? - - override init() { - self.imageNode = TransformImageNode() - self.imageNode.contentAnimations = [.subsequentUpdates] - self.imageNode.displaysAsynchronously = false - - super.init() - - self.addSubnode(self.imageNode) - } - - override func didLoad() { - super.didLoad() - - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) - } - - func setup(item: ThemeGridSearchItem) { - if self.item !== item { - var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - - var thumbnailDimensions: CGSize? - var thumbnailResource: TelegramMediaResource? - var imageResource: TelegramMediaResource? - var imageDimensions: CGSize? - var immediateThumbnailData: Data? - switch item.result { - case let .externalReference(externalReference): - if let content = externalReference.content, externalReference.type != "gif" { - imageResource = content.resource - } else if let thumbnail = externalReference.thumbnail { - imageResource = thumbnail.resource - } - imageDimensions = externalReference.content?.dimensions?.cgSize - case let .internalReference(internalReference): - if let image = internalReference.image { - immediateThumbnailData = image.immediateThumbnailData - if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 321, height: 321)) { - imageResource = representation.resource - imageDimensions = representation.dimensions.cgSize - } - if let file = internalReference.file { - if let thumbnailRepresentation = smallestImageRepresentation(file.previewRepresentations) { - thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize - thumbnailResource = thumbnailRepresentation.resource - } - } else { - if let thumbnailRepresentation = smallestImageRepresentation(image.representations) { - thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize - thumbnailResource = thumbnailRepresentation.resource - } - } - } else if let file = internalReference.file { - immediateThumbnailData = file.immediateThumbnailData - if let dimensions = file.dimensions { - imageDimensions = dimensions.cgSize - } else if let largestRepresentation = largestImageRepresentation(file.previewRepresentations) { - imageDimensions = largestRepresentation.dimensions.cgSize - } - imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource - } - } - - var representations: [TelegramMediaImageRepresentation] = [] - if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions { - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - if let imageResource = imageResource, let imageDimensions = imageDimensions { - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - if !representations.isEmpty { - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - updateImageSignal = mediaGridMessagePhoto(account: item.account, userLocation: .other, photoReference: .standalone(media: tmpImage), fullRepresentationSize: CGSize(width: 512, height: 512)) - } else { - updateImageSignal = .complete() - } - - if let updateImageSignal = updateImageSignal { - self.imageNode.setSignal(updateImageSignal) - } - - self.currentDimensions = imageDimensions - if let _ = imageDimensions { - self.setNeedsLayout() - } - } - - self.item = item - } - - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - if let item = self.item { - item.interaction.openResult(item.result) - } - } - } - - override func layout() { - super.layout() - - let bounds = self.bounds - self.imageNode.frame = bounds - - if let item = self.item, let dimensions = self.currentDimensions { - let imageSize = dimensions.aspectFilled(bounds.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: bounds.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))() - } - } -} diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift index 856ae1ae6e..3430c4001a 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperSearchRecentQueries.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperSearchRecentQueries.swift deleted file mode 100644 index 20201b9cd9..0000000000 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperSearchRecentQueries.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramUIPreferences - -private struct WallpaperSearchRecentQueryItemId { - public let rawValue: MemoryBuffer - - var value: String { - return String(data: self.rawValue.makeData(), encoding: .utf8) ?? "" - } - - init(_ rawValue: MemoryBuffer) { - self.rawValue = rawValue - } - - init?(_ value: String) { - if let data = value.data(using: .utf8) { - self.rawValue = MemoryBuffer(data: data) - } else { - return nil - } - } -} - -public final class RecentWallpaperSearchQueryItem: Codable { - public init() { - } - - public init(from decoder: Decoder) throws { - } - - public func encode(to encoder: Encoder) throws { - } -} - -func addRecentWallpaperSearchQuery(engine: TelegramEngine, string: String) -> Signal { - if let itemId = WallpaperSearchRecentQueryItemId(string) { - return engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.wallpaperSearchRecentQueries, id: itemId.rawValue, item: RecentWallpaperSearchQueryItem(), removeTailIfCountExceeds: 100) - } else { - return .complete() - } -} - -func removeRecentWallpaperSearchQuery(engine: TelegramEngine, string: String) -> Signal { - if let itemId = WallpaperSearchRecentQueryItemId(string) { - return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.wallpaperSearchRecentQueries, id: itemId.rawValue) - } else { - return .complete() - } -} - -func clearRecentWallpaperSearchQueries(engine: TelegramEngine) -> Signal { - return engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.wallpaperSearchRecentQueries) -} - -func wallpaperSearchRecentQueries(engine: TelegramEngine) -> Signal<[String], NoError> { - return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.wallpaperSearchRecentQueries)) - |> map { items -> [String] in - var result: [String] = [] - for item in items { - let value = WallpaperSearchRecentQueryItemId(item.id).value - result.append(value) - } - return result - } -} diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift index fafe52e6e9..b60bd7f395 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import MediaResources import LocalMediaResources @@ -18,16 +17,16 @@ public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperG case let .wallpaper(wallpaper, _): switch wallpaper { case let .file(file): - if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) + if let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.file.resource.id)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(file.file.resource.id), data: data) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() } case let .image(representations, _): for representation in representations { let resource = representation.resource - if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + if let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() } } @@ -64,7 +63,7 @@ public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperG } if let imageResource = imageResource { - imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + imageSignal = .single(context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(imageResource.id))) |> mapToSignal { path -> Signal in if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { return .single(image) @@ -100,18 +99,18 @@ public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperG if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) let autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered let accountManager = context.sharedContext.accountManager let account = context.account let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in - var resource: MediaResource? + var resource: TelegramMediaResource? if case let .image(representations, _) = wallpaper, let representation = largestImageRepresentation(representations) { resource = representation.resource } else if case let .file(file) = wallpaper { @@ -198,7 +197,7 @@ public func getTemporaryCustomPeerWallpaper(context: AccountContext, wallpaper: } if let imageResource = imageResource { - imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + imageSignal = .single(context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(imageResource.id))) |> mapToSignal { path -> Signal in if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { return .single(image) @@ -234,12 +233,12 @@ public func getTemporaryCustomPeerWallpaper(context: AccountContext, wallpaper: if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() @@ -257,15 +256,15 @@ public func getTemporaryCustomPeerWallpaper(context: AccountContext, wallpaper: } } -public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, forBoth: Bool, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, peerId: EnginePeer.Id, forBoth: Bool, completion: @escaping () -> Void) { var imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): imageSignal = .complete() switch wallpaper { case let .file(file): - if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true) + if let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(file.file.resource.id)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) { + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(file.file.resource.id), data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() @@ -274,8 +273,8 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa case let .image(representations, _): for representation in representations { let resource = representation.resource - if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + if let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(resource.id)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() } } @@ -311,7 +310,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa } if let imageResource = imageResource { - imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + imageSignal = .single(context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(imageResource.id))) |> mapToSignal { path -> Signal in if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { return .single(image) @@ -347,12 +346,12 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData, synchronous: true) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/BUILD b/submodules/TelegramUI/Components/ShareExtensionContext/BUILD index 47e6c76e66..1b1e2be9d1 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/BUILD +++ b/submodules/TelegramUI/Components/ShareExtensionContext/BUILD @@ -33,6 +33,7 @@ swift_library( "//submodules/ActivityIndicator", "//submodules/DebugSettingsUI", "//submodules/ManagedFile", + "//submodules/ContextUI", "//submodules/TelegramUI/Components/TelegramUIDeclareEncodables", "//submodules/TelegramUI/Components/AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer", @@ -41,6 +42,7 @@ swift_library( "//submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods", "//submodules/TelegramUI/Components/PeerSelectionController", "//submodules/TelegramUI/Components/ContextMenuScreen", + "//submodules/TelegramUI/Components/ContextControllerImpl", "//submodules/TelegramUI/Components/NavigationBarImpl", ], visibility = [ diff --git a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift index c3b242c393..e7b05cc9f1 100644 --- a/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Components/ShareExtensionContext/Sources/ShareExtensionContext.swift @@ -34,6 +34,8 @@ import TelegramAccountAuxiliaryMethods import PeerSelectionController import ContextMenuScreen import NavigationBarImpl +import ContextUI +import ContextControllerImpl private var installedSharedLogger = false @@ -201,6 +203,18 @@ public class ShareRootControllerImpl { defaultNavigationBarImpl = { presentationData in return NavigationBarImpl(presentationData: presentationData) } + makeContextControllerImpl = { context, presentationData, configuration, recognizer, gesture, workaroundUseLegacyImplementation, disableScreenshots, hideReactionPanelTail in + return ContextControllerImpl( + context: context, + presentationData: presentationData, + configuration: configuration, + recognizer: recognizer, + gesture: gesture, + workaroundUseLegacyImplementation: workaroundUseLegacyImplementation, + disableScreenshots: disableScreenshots, + hideReactionPanelTail: hideReactionPanelTail + ) + } } deinit { @@ -412,7 +426,7 @@ public class ShareRootControllerImpl { let accountData: Signal<(ShareControllerEnvironment, ShareControllerAccountContext, [ShareControllerSwitchableAccount]), NoError> = accountManager.accountRecords() |> take(1) |> mapToSignal { view -> Signal<(ShareControllerEnvironment, ShareControllerAccountContext, [ShareControllerSwitchableAccount]), NoError> in - var signals: [Signal<(AccountRecordId, AccountStateManager, Peer)?, NoError>] = [] + var signals: [Signal<(AccountRecordId, AccountStateManager, EnginePeer)?, NoError>] = [] for record in view.records { if record.attributes.contains(where: { attribute in if case .loggedOut = attribute { @@ -436,14 +450,14 @@ public class ShareRootControllerImpl { rootPath: rootPath, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(uploadInBackground: nil) ) - |> mapToSignal { result -> Signal<(AccountRecordId, AccountStateManager, Peer)?, NoError> in + |> mapToSignal { result -> Signal<(AccountRecordId, AccountStateManager, EnginePeer)?, NoError> in if let result { - return result.postbox.transaction { transaction -> (AccountRecordId, AccountStateManager, Peer)? in + return result.postbox.transaction { transaction -> (AccountRecordId, AccountStateManager, EnginePeer)? in guard let peer = transaction.getPeer(result.accountPeerId) else { return nil } - - return (record.id, result, peer) + + return (record.id, result, EnginePeer(peer)) } } else { return .single(nil) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift index ddb1aafd17..20f40edbe2 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CountriesMultiselectionScreen.swift @@ -9,7 +9,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import PresentationDataUtils import ButtonComponent @@ -19,6 +18,8 @@ import TelegramStringFormatting import LottieComponent import UndoUI import CountrySelectionUI +import GlassBarButtonComponent +import BundleIconComponent final class CountriesMultiselectionScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -239,8 +240,8 @@ final class CountriesMultiselectionScreenComponent: Component { self.navigationContainerView.addSubview(self.navigationBackgroundView) self.navigationContainerView.layer.addSublayer(self.navigationSeparatorLayer) - self.containerView.addSubview(self.bottomBackgroundView) - self.containerView.layer.addSublayer(self.bottomSeparatorLayer) + //self.containerView.addSubview(self.bottomBackgroundView) + //self.containerView.layer.addSublayer(self.bottomSeparatorLayer) self.containerView.addSubnode(self.indexNode) @@ -463,8 +464,7 @@ final class CountriesMultiselectionScreenComponent: Component { update() } - let limit = component.context.userLimits.maxGiveawayCountriesCount - if self.selectedCountries.count >= limit, index == nil { + if let limit = component.stateContext.maxCount, self.selectedCountries.count >= limit, index == nil { self.hapticFeedback.error() let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } @@ -751,7 +751,7 @@ final class CountriesMultiselectionScreenComponent: Component { if let searchStateContext = self.searchStateContext, searchStateContext.subject == .countriesSearch(query: self.navigationTextFieldState.text) { } else { self.searchStateDisposable?.dispose() - let searchStateContext = CountriesMultiselectionScreen.StateContext(context: component.context, subject: .countriesSearch(query: self.navigationTextFieldState.text)) + let searchStateContext = CountriesMultiselectionScreen.StateContext(context: component.context, subject: .countriesSearch(query: self.navigationTextFieldState.text), showFragment: component.stateContext.showFragment) var applyState = false self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in guard let self else { @@ -788,7 +788,6 @@ final class CountriesMultiselectionScreenComponent: Component { var sections: [ItemLayout.Section] = [] if let stateValue = self.effectiveStateValue { - var id: Int = 0 for (_, countries) in stateValue.sections { sections.append(ItemLayout.Section( @@ -803,21 +802,30 @@ final class CountriesMultiselectionScreenComponent: Component { let containerInset: CGFloat = environment.statusBarHeight - var navigationHeight: CGFloat = 56.0 + var navigationHeight: CGFloat = 76.0 let navigationSideInset: CGFloat = 16.0 var navigationButtonsWidth: CGFloat = 0.0 let navigationLeftButtonSize = self.navigationLeftButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), - action: { [weak self] in + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { [weak self] _ in guard let self, let environment = self.environment, let controller = environment.controller() as? CountriesMultiselectionScreen else { return } controller.requestDismiss() } - ).minSize(CGSize(width: navigationHeight, height: navigationHeight))), + )), environment: {}, containerSize: CGSize(width: availableSize.width, height: navigationHeight) ) @@ -830,21 +838,25 @@ final class CountriesMultiselectionScreenComponent: Component { } navigationButtonsWidth += navigationLeftButtonSize.width + navigationSideInset - let actionButtonTitle = environment.strings.CountriesList_SaveCountries - let title = environment.strings.CountriesList_SelectCountries - let subtitle = environment.strings.CountriesList_SelectUpTo(component.context.userLimits.maxGiveawayCountriesCount) + let actionButtonTitle = environment.strings.CountriesList_SaveCountries + var titleComponents: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity( + id: "title", + component: AnyComponent(Text(text: environment.strings.CountriesList_SelectCountries, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.primaryTextColor)) + ) + ] - let titleComponent = AnyComponent( - List([ - AnyComponentWithIdentity( - id: "title", - component: AnyComponent(Text(text: title, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.primaryTextColor)) - ), + if let maxCount = component.stateContext.maxCount { + titleComponents.append( AnyComponentWithIdentity( id: "subtitle", - component: AnyComponent(Text(text: subtitle, font: Font.regular(13.0), color: environment.theme.rootController.navigationBar.secondaryTextColor)) + component: AnyComponent(Text(text: environment.strings.CountriesList_SelectUpTo(maxCount), font: Font.regular(13.0), color: environment.theme.rootController.navigationBar.secondaryTextColor)) ) - ], + ) + } + + let titleComponent = AnyComponent( + List(titleComponents, centerAlignment: true) ) @@ -884,10 +896,12 @@ final class CountriesMultiselectionScreenComponent: Component { let badge = self.selectedCountries.count + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let actionButtonSize = self.actionButton.update( transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) @@ -917,15 +931,15 @@ final class CountriesMultiselectionScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: containerWidth - navigationSideInset * 2.0, height: 50.0) + containerSize: CGSize(width: containerWidth - buttonInsets.left - buttonInsets.right, height: 52.0) ) - if environment.inputHeight != 0.0 { bottomPanelHeight += environment.inputHeight + 8.0 + actionButtonSize.height } else { - bottomPanelHeight += 10.0 + environment.safeInsets.bottom + actionButtonSize.height + bottomPanelHeight += actionButtonSize.height + bottomPanelHeight += buttonInsets.bottom } - let actionButtonFrame = CGRect(origin: CGPoint(x: containerSideInset + navigationSideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) + let actionButtonFrame = CGRect(origin: CGPoint(x: containerSideInset + buttonInsets.left, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) if let actionButtonView = self.actionButton.view { if actionButtonView.superview == nil { self.containerView.addSubview(actionButtonView) @@ -1104,7 +1118,9 @@ public extension CountriesMultiselectionScreen { var stateValue: State? public let subject: Subject + public let maxCount: Int32? public let initialSelectedCountries: [String] + public let showFragment: Bool private var stateDisposable: Disposable? private let stateSubject = Promise() @@ -1120,15 +1136,23 @@ public extension CountriesMultiselectionScreen { public init( context: AccountContext, subject: Subject = .countries, - initialSelectedCountries: [String] = [] + maxCount: Int32? = nil, + initialSelectedCountries: [String] = [], + showFragment: Bool = false ) { self.subject = subject + self.maxCount = maxCount self.initialSelectedCountries = initialSelectedCountries + self.showFragment = showFragment let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let countries = localizedCountryNamesAndCodes(strings: presentationData.strings).sorted { lhs, rhs in + let countryList = localizedCountryNamesAndCodes(strings: presentationData.strings) + var countries = countryList.sorted { lhs, rhs in return lhs.0.1.lowercased() < rhs.0.1.lowercased() } + if showFragment, let index = countries.firstIndex(where: { $0.1 == "FR" }) { + countries.insert((("Fragment", "Fragment"), "FT", [888]), at: index) + } switch subject { case .countries: @@ -1137,8 +1161,8 @@ public extension CountriesMultiselectionScreen { var currentSection: String? var currentCountries: [CountryItem] = [] for country in countries { - let section = String(country.0.1.prefix(1)) - if currentSection != section { + let section = String(country.0.1.prefix(1)).uppercased() + if currentSection != section && country.1 != "FT" { if let currentSection { sections.append((currentSection, currentCountries)) } diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift index 3a3a03ca1c..2720fbbacd 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/LiveStreamSettingsScreen.swift @@ -9,7 +9,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import PresentationDataUtils import ButtonComponent diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 120b2b2f5c..848ae4f389 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -768,16 +768,16 @@ final class ShareWithPeersScreenComponent: Component { }) } else if peer.id.namespace == Namespaces.Peer.CloudChannel { let participants: Signal<[EnginePeer], NoError> = Signal { subscriber in - let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peer.id, requestUpdate: true, count: 200, updated: { list in + let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peer.id, requestUpdate: true, count: 200, updated: { list in var peers: [EnginePeer] = [] for item in list.list { if item.peer.id == context.account.peerId { continue } - if let user = item.peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = item.peer, user.botInfo != nil { continue } - peers.append(EnginePeer(item.peer)) + peers.append(item.peer) } if !list.list.isEmpty { subscriber.putNext(peers) @@ -2765,10 +2765,10 @@ final class ShareWithPeersScreenComponent: Component { let navigationLeftButtonSize = self.navigationLeftButton.update( transition: transition, component: AnyComponent(GlassBarButtonComponent( - size: CGSize(width: 40.0, height: 40.0), - backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, isDark: environment.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "close", component: AnyComponent( BundleIconComponent( name: "Navigation/Close", diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift index 5bc9be69da..3c9f8ed80b 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreenState.swift @@ -4,7 +4,6 @@ import TelegramCore import AccountContext import TelegramUIPreferences import TemporaryCachedPeerDataManager -import Postbox public extension ShareWithPeersScreen { final class State { @@ -524,11 +523,11 @@ public extension ShareWithPeersScreen { let contactsState = Promise() let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) - disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in + disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in membersState.set(.single(state)) }) - let contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in + let contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in contactsState.set(.single(state)) }) @@ -555,7 +554,7 @@ public extension ShareWithPeersScreen { continue } - peers.append(EnginePeer(participant.peer)) + peers.append(participant.peer) existingPeersIds.insert(participant.peer.id) } @@ -563,7 +562,7 @@ public extension ShareWithPeersScreen { if participant.peer.isDeleted || existingPeersIds.contains(participant.peer.id) || participant.participant.adminInfo != nil { continue } - if let user = participant.peer as? TelegramUser, user.botInfo != nil { + if case let .user(user) = participant.peer, user.botInfo != nil { continue } @@ -573,7 +572,7 @@ public extension ShareWithPeersScreen { continue } - peers.append(EnginePeer(participant.peer)) + peers.append(participant.peer) } let state = State( @@ -650,9 +649,9 @@ public extension ShareWithPeersScreen { } } - let queryTokens = stringIndexTokens(searchQuery ?? "", transliteration: .combined) - func peerMatchesTokens(peer: EnginePeer, tokens: [ValueBoxKey]) -> Bool { - if matchStringIndexTokens(peer.indexName._asIndexName().indexTokens, with: queryTokens) { + let queryTokens = context.engine.peers.tokenizeSearchString(string: searchQuery ?? "", transliteration: .combined) + func peerMatchesTokens(peer: EnginePeer, tokens: [EngineDataBuffer]) -> Bool { + if context.engine.peers.matchSearchTokens(peer.indexName._asIndexName().indexTokens, with: queryTokens) { return true } return false diff --git a/submodules/GlassButtonNode/BUILD b/submodules/TelegramUI/Components/ShimmeringMask/BUILD similarity index 58% rename from submodules/GlassButtonNode/BUILD rename to submodules/TelegramUI/Components/ShimmeringMask/BUILD index bab4d6718e..8aaeac03c3 100644 --- a/submodules/GlassButtonNode/BUILD +++ b/submodules/TelegramUI/Components/ShimmeringMask/BUILD @@ -1,8 +1,8 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") swift_library( - name = "GlassButtonNode", - module_name = "GlassButtonNode", + name = "ShimmeringMask", + module_name = "ShimmeringMask", srcs = glob([ "Sources/**/*.swift", ]), @@ -10,8 +10,9 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", + "//submodules/ComponentFlow", + "//submodules/Display", + "//submodules/Components/HierarchyTrackingLayer", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ShimmeringMask/Sources/ShimmeringMaskView.swift b/submodules/TelegramUI/Components/ShimmeringMask/Sources/ShimmeringMaskView.swift new file mode 100644 index 0000000000..7da9d6d331 --- /dev/null +++ b/submodules/TelegramUI/Components/ShimmeringMask/Sources/ShimmeringMaskView.swift @@ -0,0 +1,129 @@ +import Foundation +import UIKit +import ComponentFlow +import Display +import HierarchyTrackingLayer + +public final class ShimmeringMaskView: UIView { + private struct Params: Equatable { + var size: CGSize + var containerWidth: CGFloat + var offsetX: CGFloat + var gradientWidth: CGFloat + } + + public let contentView: UIView + + private let peakAlpha: CGFloat + private let duration: Double + + private let hierarchyTrackingLayer: HierarchyTrackingLayer + private let maskLayer: CAGradientLayer + + private var params: Params? + + public init(peakAlpha: CGFloat, duration: Double) { + self.peakAlpha = peakAlpha + self.duration = duration + + self.contentView = UIView() + + self.hierarchyTrackingLayer = HierarchyTrackingLayer() + + self.maskLayer = CAGradientLayer() + self.maskLayer.startPoint = CGPoint(x: 0.0, y: 0.5) + self.maskLayer.endPoint = CGPoint(x: 1.0, y: 0.5) + self.maskLayer.colors = [ + UIColor(white: 1.0, alpha: 1.0).cgColor, + UIColor(white: 1.0, alpha: self.peakAlpha).cgColor, + UIColor(white: 1.0, alpha: 1.0).cgColor + ] + self.maskLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5) + + super.init(frame: CGRect()) + + self.addSubview(self.contentView) + self.contentView.layer.mask = self.maskLayer + + self.layer.addSublayer(self.hierarchyTrackingLayer) + self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in + guard let self else { + return + } + self.updateAnimations() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateAnimations() { + guard let params = self.params else { + return + } + if self.maskLayer.animation(forKey: "shimmer") != nil { + return + } + let travelDelta = params.containerWidth + params.gradientWidth + let animation = self.maskLayer.makeAnimation( + from: 0.0 as NSNumber, + to: travelDelta as NSNumber, + keyPath: "position.x", + timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, + duration: self.duration, + delay: 0.0, + mediaTimingFunction: nil, + removeOnCompletion: true, + additive: true + ) + animation.repeatCount = Float.infinity + self.maskLayer.add(animation, forKey: "shimmer") + } + + public func update( + size: CGSize, + containerWidth: CGFloat, + offsetX: CGFloat, + gradientWidth: CGFloat, + transition: ComponentTransition + ) { + let params = Params( + size: size, + containerWidth: containerWidth, + offsetX: offsetX, + gradientWidth: gradientWidth + ) + if self.params == params { + return + } + self.params = params + + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) + + let travelDistance = containerWidth + gradientWidth + let maskWidth = size.width + 2.0 * travelDistance + + let dipHalfFraction: CGFloat + if maskWidth > 0.0 { + dipHalfFraction = (gradientWidth * 0.5) / maskWidth + } else { + dipHalfFraction = 0.0 + } + self.maskLayer.locations = [ + (0.5 - dipHalfFraction) as NSNumber, + 0.5 as NSNumber, + (0.5 + dipHalfFraction) as NSNumber + ] + + let maskBounds = CGRect(origin: CGPoint(), size: CGSize(width: maskWidth, height: size.height)) + let staticPositionX = -gradientWidth * 0.5 - offsetX + let maskPosition = CGPoint(x: staticPositionX, y: size.height * 0.5) + + transition.setBounds(layer: self.maskLayer, bounds: maskBounds) + transition.setPosition(layer: self.maskLayer, position: maskPosition) + + self.maskLayer.removeAnimation(forKey: "shimmer") + self.updateAnimations() + } +} diff --git a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift index d9cffb9fe4..204e0d7fa3 100644 --- a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift +++ b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift @@ -42,11 +42,21 @@ public final class SliderComponent: Component { public final class Continuous: Equatable { public let value: CGFloat public let minValue: CGFloat? + public let range: ClosedRange + public let startValue: CGFloat public let valueUpdated: (CGFloat) -> Void - public init(value: CGFloat, minValue: CGFloat? = nil, valueUpdated: @escaping (CGFloat) -> Void) { + public init( + value: CGFloat, + minValue: CGFloat? = nil, + range: ClosedRange = 0.0 ... 1.0, + startValue: CGFloat = 0.0, + valueUpdated: @escaping (CGFloat) -> Void + ) { self.value = value self.minValue = minValue + self.range = range + self.startValue = startValue self.valueUpdated = valueUpdated } @@ -57,6 +67,12 @@ public final class SliderComponent: Component { if lhs.minValue != rhs.minValue { return false } + if lhs.range != rhs.range { + return false + } + if lhs.startValue != rhs.startValue { + return false + } return true } } @@ -73,6 +89,10 @@ public final class SliderComponent: Component { public let minTrackForegroundColor: UIColor? public let knobSize: CGFloat? public let knobColor: UIColor? + public let isEnabled: Bool + public let trackHeight: CGFloat? + public let displaysBorderOnTracking: Bool + public let useLegacyKnob: Bool public let isTrackingUpdated: ((Bool) -> Void)? public init( @@ -83,6 +103,10 @@ public final class SliderComponent: Component { minTrackForegroundColor: UIColor? = nil, knobSize: CGFloat? = nil, knobColor: UIColor? = nil, + isEnabled: Bool = true, + trackHeight: CGFloat? = nil, + displaysBorderOnTracking: Bool = false, + useLegacyKnob: Bool = false, isTrackingUpdated: ((Bool) -> Void)? = nil ) { self.content = content @@ -92,6 +116,10 @@ public final class SliderComponent: Component { self.minTrackForegroundColor = minTrackForegroundColor self.knobSize = knobSize self.knobColor = knobColor + self.isEnabled = isEnabled + self.trackHeight = trackHeight + self.displaysBorderOnTracking = displaysBorderOnTracking + self.useLegacyKnob = useLegacyKnob self.isTrackingUpdated = isTrackingUpdated } @@ -114,6 +142,18 @@ public final class SliderComponent: Component { if lhs.knobColor != rhs.knobColor { return false } + if lhs.isEnabled != rhs.isEnabled { + return false + } + if lhs.trackHeight != rhs.trackHeight { + return false + } + if lhs.displaysBorderOnTracking != rhs.displaysBorderOnTracking { + return false + } + if lhs.useLegacyKnob != rhs.useLegacyKnob { + return false + } return true } @@ -123,6 +163,8 @@ public final class SliderComponent: Component { public final class View: UIView { private var nativeSliderView: SliderView? + private let nativeTrackBackgroundView = UIView() + private let nativeTrackForegroundView = UIView() private var sliderView: TGPhotoEditorSliderView? private var component: SliderComponent? @@ -139,6 +181,12 @@ public final class SliderComponent: Component { required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + private static let legacyKnobImage: UIImage = generateImage(CGSize(width: 21.0, height: 21.0), rotatedContext: { _, context in + context.setShadow(offset: CGSize(width: 0.0, height: 0.5), blur: 1.5, color: UIColor(white: 0.0, alpha: 0.5).cgColor) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(x: 2.0, y: 2.0, width: 17.0, height: 17.0)) + })! public func cancelGestures() { if let sliderView = self.sliderView, let gestureRecognizers = sliderView.gestureRecognizers { @@ -158,6 +206,11 @@ public final class SliderComponent: Component { let size = CGSize(width: availableSize.width, height: 44.0) if #available(iOS 26.0, *), component.useNative { + if let sliderView = self.sliderView { + self.sliderView = nil + sliderView.removeFromSuperview() + } + let sliderView: SliderView if let current = self.nativeSliderView { sliderView = current @@ -167,13 +220,15 @@ public final class SliderComponent: Component { sliderView.addTarget(self, action: #selector(self.sliderValueChanged), for: .valueChanged) sliderView.layer.allowsGroupOpacity = true + self.addSubview(self.nativeTrackBackgroundView) + self.addSubview(self.nativeTrackForegroundView) self.addSubview(sliderView) self.nativeSliderView = sliderView switch component.content { case let .continuous(continuous): - sliderView.minimumValue = Float(continuous.minValue ?? 0.0) - sliderView.maximumValue = 1.0 + sliderView.minimumValue = Float(continuous.minValue ?? continuous.range.lowerBound) + sliderView.maximumValue = Float(continuous.range.upperBound) case let .discrete(discrete): sliderView.minimumValue = 0.0 sliderView.maximumValue = Float(discrete.valueCount - 1) @@ -182,20 +237,69 @@ public final class SliderComponent: Component { } switch component.content { case let .continuous(continuous): + sliderView.minimumValue = Float(continuous.minValue ?? continuous.range.lowerBound) + sliderView.maximumValue = Float(continuous.range.upperBound) sliderView.value = Float(continuous.value) case let .discrete(discrete): + sliderView.minimumValue = 0.0 + sliderView.maximumValue = Float(discrete.valueCount - 1) sliderView.value = Float(discrete.value) } - sliderView.minimumTrackTintColor = component.trackForegroundColor - sliderView.maximumTrackTintColor = component.trackBackgroundColor + + let useCenteredNativeTrack: Bool + if case let .continuous(continuous) = component.content, continuous.range.lowerBound < continuous.startValue && continuous.startValue < continuous.range.upperBound { + useCenteredNativeTrack = true + } else { + useCenteredNativeTrack = false + } + + if useCenteredNativeTrack { + sliderView.minimumTrackTintColor = UIColor.clear + sliderView.maximumTrackTintColor = UIColor.clear + + let trackHeight = component.trackHeight ?? 4.0 + let trackFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - trackHeight) * 0.5)), size: CGSize(width: availableSize.width, height: trackHeight)) + self.nativeTrackBackgroundView.backgroundColor = component.trackBackgroundColor + self.nativeTrackBackgroundView.layer.cornerRadius = trackHeight * 0.5 + self.nativeTrackBackgroundView.alpha = component.isEnabled ? 1.0 : 0.3 + transition.setFrame(view: self.nativeTrackBackgroundView, frame: trackFrame) + + if case let .continuous(continuous) = component.content { + let rangeDistance = max(continuous.range.upperBound - continuous.range.lowerBound, CGFloat.ulpOfOne) + let startPosition = min(max((continuous.startValue - continuous.range.lowerBound) / rangeDistance, 0.0), 1.0) * trackFrame.width + let valuePosition = min(max((continuous.value - continuous.range.lowerBound) / rangeDistance, 0.0), 1.0) * trackFrame.width + let foregroundFrame = CGRect( + origin: CGPoint(x: trackFrame.minX + min(startPosition, valuePosition), y: trackFrame.minY), + size: CGSize(width: abs(valuePosition - startPosition), height: trackFrame.height) + ) + self.nativeTrackForegroundView.backgroundColor = component.trackForegroundColor + self.nativeTrackForegroundView.layer.cornerRadius = trackHeight * 0.5 + self.nativeTrackForegroundView.alpha = component.isEnabled ? 1.0 : 0.3 + transition.setFrame(view: self.nativeTrackForegroundView, frame: foregroundFrame) + } + } else { + sliderView.minimumTrackTintColor = component.trackForegroundColor + sliderView.maximumTrackTintColor = component.trackBackgroundColor + self.nativeTrackBackgroundView.frame = CGRect() + self.nativeTrackForegroundView.frame = CGRect() + } + sliderView.isEnabled = component.isEnabled + sliderView.alpha = component.isEnabled ? 1.0 : 0.3 transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: 44.0))) } else { + self.nativeTrackBackgroundView.frame = CGRect() + self.nativeTrackForegroundView.frame = CGRect() + if let nativeSliderView = self.nativeSliderView { + self.nativeSliderView = nil + nativeSliderView.removeFromSuperview() + } + var internalIsTrackingUpdated: ((Bool) -> Void)? if let isTrackingUpdated = component.isTrackingUpdated { internalIsTrackingUpdated = { [weak self] isTracking in if let self { - if !"".isEmpty { + if component.displaysBorderOnTracking { if isTracking { self.sliderView?.bordered = true } else { @@ -215,51 +319,20 @@ public final class SliderComponent: Component { } else { sliderView = TGPhotoEditorSliderView() sliderView.enablePanHandling = true - if let knobSize = component.knobSize { - sliderView.lineSize = knobSize + 4.0 - } else { - sliderView.lineSize = 4.0 - } - sliderView.trackCornerRadius = sliderView.lineSize * 0.5 sliderView.dotSize = 5.0 - sliderView.minimumValue = 0.0 - sliderView.startValue = 0.0 sliderView.disablesInteractiveTransitionGestureRecognizer = true switch component.content { case let .discrete(discrete): - sliderView.maximumValue = CGFloat(discrete.valueCount - 1) sliderView.positionsCount = discrete.valueCount sliderView.useLinesForPositions = true sliderView.markPositions = discrete.markPositions case .continuous: - sliderView.maximumValue = 1.0 + break } sliderView.backgroundColor = nil sliderView.isOpaque = false - sliderView.backColor = component.trackBackgroundColor - sliderView.startColor = component.trackBackgroundColor - sliderView.trackColor = component.trackForegroundColor - if let knobSize = component.knobSize { - sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) - if let knobColor = component.knobColor { - context.setFillColor(knobColor.cgColor) - } else { - context.setFillColor(UIColor.white.cgColor) - } - context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - knobSize) * 0.5), y: floor((size.width - knobSize) * 0.5)), size: CGSize(width: knobSize, height: knobSize))) - }) - } else { - sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) - context.setFillColor(UIColor.white.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) - }) - } sliderView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size) sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) @@ -271,22 +344,66 @@ public final class SliderComponent: Component { self.sliderView = sliderView self.addSubview(sliderView) } + if let trackHeight = component.trackHeight { + sliderView.lineSize = trackHeight + } else if let knobSize = component.knobSize { + sliderView.lineSize = knobSize + 4.0 + } else { + sliderView.lineSize = 4.0 + } + sliderView.trackCornerRadius = sliderView.lineSize * 0.5 + sliderView.backColor = component.trackBackgroundColor + sliderView.startColor = component.useLegacyKnob ? UIColor(rgb: 0xffffff) : component.trackBackgroundColor + sliderView.trackColor = component.trackForegroundColor + sliderView.isUserInteractionEnabled = component.isEnabled + sliderView.alpha = component.isEnabled ? (component.useLegacyKnob ? 1.3 : 1.0) : 0.3 + if let knobSize = component.knobSize { + sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + if let knobColor = component.knobColor { + context.setFillColor(knobColor.cgColor) + } else { + context.setFillColor(UIColor.white.cgColor) + } + context.fillEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - knobSize) * 0.5), y: floor((size.width - knobSize) * 0.5)), size: CGSize(width: knobSize, height: knobSize))) + }) + } else if component.useLegacyKnob { + sliderView.knobImage = View.legacyKnobImage + } else { + sliderView.knobImage = generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 12.0, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) + }) + } sliderView.lowerBoundTrackColor = component.minTrackForegroundColor switch component.content { case let .discrete(discrete): - sliderView.value = CGFloat(discrete.value) + sliderView.minimumValue = 0.0 + sliderView.maximumValue = CGFloat(discrete.valueCount - 1) + sliderView.startValue = 0.0 + sliderView.positionsCount = discrete.valueCount + sliderView.useLinesForPositions = true + sliderView.markPositions = discrete.markPositions if let minValue = discrete.minValue { sliderView.lowerBoundValue = CGFloat(minValue) } else { sliderView.lowerBoundValue = 0.0 } + sliderView.value = CGFloat(discrete.value) case let .continuous(continuous): - sliderView.value = continuous.value + sliderView.minimumValue = continuous.range.lowerBound + sliderView.maximumValue = continuous.range.upperBound + sliderView.startValue = continuous.startValue + sliderView.positionsCount = 0 if let minValue = continuous.minValue { sliderView.lowerBoundValue = minValue } else { sliderView.lowerBoundValue = 0.0 } + sliderView.value = continuous.value } sliderView.interactionBegan = { internalIsTrackingUpdated?(true) diff --git a/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD index e75b7cea6e..40ecdc513c 100644 --- a/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD +++ b/submodules/TelegramUI/Components/Stars/ItemShimmeringLoadingComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/ComponentFlow", "//submodules/AppBundle", "//submodules/Components/HierarchyTrackingLayer", diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/BUILD b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/BUILD index 8e587337b5..9938b25de3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index ab62c323c2..225a6e2e3d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import ComponentFlow import TelegramPresentationData @@ -23,7 +22,7 @@ public final class StarsAvatarComponent: Component { let theme: PresentationTheme let peer: StarsAvatarComponent.Peer? let photo: TelegramMediaWebFile? - let media: [Media] + let media: [EngineRawMedia] let gift: StarGift? let backgroundColor: UIColor let size: CGSize? @@ -33,7 +32,7 @@ public final class StarsAvatarComponent: Component { theme: PresentationTheme, peer: StarsAvatarComponent.Peer?, photo: TelegramMediaWebFile?, - media: [Media], + media: [EngineRawMedia], gift: StarGift?, backgroundColor: UIColor, size: CGSize? = nil @@ -61,7 +60,7 @@ public final class StarsAvatarComponent: Component { if lhs.photo != rhs.photo { return false } - if !areMediaArraysEqual(lhs.media, rhs.media) { + if !engineAreMediaArraysEqual(lhs.media, rhs.media) { return false } if lhs.gift != rhs.gift { diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD index b71d4ed3c0..33526bb75c 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 1aea568c66..13d3ae0af9 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import ComponentFlow import TelegramPresentationData @@ -272,7 +271,7 @@ public final class StarsImageComponent: Component { return false } case let .media(lhsMedia): - if case let .media(rhsMedia) = rhs, areMediaArraysEqual(lhsMedia.map { $0.media }, rhsMedia.map { $0.media }) { + if case let .media(rhsMedia) = rhs, engineAreMediaArraysEqual(lhsMedia.map { $0.media }, rhsMedia.map { $0.media }) { return true } else { return false @@ -323,7 +322,7 @@ public final class StarsImageComponent: Component { public let backgroundColor: UIColor public let icon: Icon? public let value: Int64? - public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? + public let action: ((@escaping (EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? public init( context: AccountContext, @@ -333,7 +332,7 @@ public final class StarsImageComponent: Component { backgroundColor: UIColor, icon: Icon? = nil, value: Int64? = nil, - action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil + action: ((@escaping (EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil ) { self.context = context self.subject = subject @@ -401,7 +400,7 @@ public final class StarsImageComponent: Component { private let fetchDisposable = MetaDisposable() private var hiddenMediaDisposable: Disposable? - private var hiddenMedia: [Media] = [] + private var hiddenMedia: [EngineRawMedia] = [] public override init(frame: CGRect) { super.init(frame: frame) @@ -432,7 +431,7 @@ public final class StarsImageComponent: Component { }) } - public func transitionNode(_ transitionMedia: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + public func transitionNode(_ transitionMedia: EngineRawMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { guard let component = self.component, let containerNode = self.containerNode else { return nil } @@ -644,7 +643,7 @@ public final class StarsImageComponent: Component { let media: TelegramMediaImage switch extendedMedia.first { case let .preview(imageDimensions, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let thumbnailMedia = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) media = thumbnailMedia if let imageDimensions { dimensions = imageDimensions.cgSize.aspectFilled(imageSize) @@ -686,7 +685,7 @@ public final class StarsImageComponent: Component { let media: TelegramMediaImage switch extendedMedia[1] { case let .preview(imageDimensions, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let thumbnailMedia = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) media = thumbnailMedia if let imageDimensions { secondDimensions = imageDimensions.cgSize.aspectFilled(imageSize) @@ -1038,7 +1037,7 @@ public final class StarsImageComponent: Component { guard let self, let component = self.component else { return } - var hiddenMedia: [Media] = [] + var hiddenMedia: [EngineRawMedia] = [] for id in ids { if case let .chat(accountId, _, media) = id, accountId == component.context.account.id { hiddenMedia.append(media) diff --git a/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD index 61bc2e9d45..1e5fadadde 100644 --- a/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsIntroScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD index 8eea622fc1..b1e85960ed 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index a733e61b22..9e0f9fb370 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import PresentationDataUtils import ViewControllerComponent @@ -187,6 +186,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { let component = context.component let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller let state = context.state state.products = component.products @@ -476,7 +476,10 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { } }, tapAction: { attributes, _ in - component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: nil, dismissInput: {}) + guard let controller = controller(), let navigationController = controller.navigationController as? NavigationController else { + return + } + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Purchase_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) } ), environment: {}, @@ -1034,10 +1037,8 @@ public final class StarsPurchaseScreen: ViewControllerComponentContainer { completionImpl?(stars) } ), navigationBarAppearance: .default, presentationMode: .modal, theme: customTheme.flatMap { .custom($0) } ?? .default) - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - let cancelItem = UIBarButtonItem(title: presentationData.strings.Common_Close, style: .plain, target: self, action: #selector(self.cancelPressed)) + + let cancelItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed)) self.navigationItem.setLeftBarButton(cancelItem, animated: false) self.navigationPresentation = .modal diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index a7531e23af..a034ae91d7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -277,8 +277,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { descriptionText = "" count = CurrencyAmount(amount: subscription.pricing.amount, currency: .stars) date = subscription.untilDate - if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 { - additionalDate = creationDate + if case let .channel(channel) = subscription.peer, channel.creationDate > 0 { + additionalDate = channel.creationDate } else { additionalDate = nil } @@ -1955,7 +1955,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { return } if isProfile { - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller) } } else { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD index d71aef4f9c..066e81e6de 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index c4207b6e35..ed818d6790 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -9,7 +9,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import BalancedTextComponent import Markdown diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 91d7256e8f..87c58b5f20 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -9,7 +9,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import BalancedTextComponent import Markdown @@ -634,7 +633,7 @@ final class StarsTransactionsScreenComponent: Component { starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame) } - contentHeight += 181.0 + contentHeight += 197.0 let descriptionSize = self.descriptionView.update( transition: .immediate, @@ -1285,7 +1284,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { var hasLeft = false var isKicked = false - if let channel = subscription.peer._asPeer() as? TelegramChannel { + if case let .channel(channel) = subscription.peer { switch channel.participationStatus { case .left: hasLeft = true diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD index 06a63c3c2b..0b89fa7a6c 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD index 01c08ce49c..abebdf3c0b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index c439add8e4..6e5685b939 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import Markdown import TextFormat diff --git a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift index 4b3791a899..c19e1ab7e9 100644 --- a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift +++ b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift @@ -482,6 +482,7 @@ public class StickerPickerScreen: ViewController { var id: AnyHashable var version: Int var isPreset: Bool + var canLoadMore: Bool } private struct EmojiSearchState { @@ -501,6 +502,7 @@ public class StickerPickerScreen: ViewController { self.emojiSearchState.set(.single(self.emojiSearchStateValue)) } } + private var emojiSearchContext: EmojiSearchContext? private let stickerSearchDisposable = MetaDisposable() private let stickerSearchState = Promise(EmojiSearchState(result: nil, isSearching: false)) @@ -691,7 +693,7 @@ public class StickerPickerScreen: ViewController { } let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true) - inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState) + inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState, canLoadMore: emojiSearchResult.canLoadMore) } else if emojiSearchState.isSearching { inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching) } @@ -942,14 +944,9 @@ public class StickerPickerScreen: ViewController { return } let context = controller.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks()) + |> deliverOnMainQueue).start(next: { featuredEmojiPacks in + for featuredStickerPack in featuredEmojiPacks { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -958,7 +955,7 @@ public class StickerPickerScreen: ViewController { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -1044,15 +1041,18 @@ public class StickerPickerScreen: ViewController { switch query { case .none: + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { + self.emojiSearchContext = nil self.emojiSearchDisposable.set(nil) - self.emojiSearchState.set(.single(EmojiSearchState(result: nil, isSearching: false))) + self.emojiSearchStateValue = EmojiSearchState(result: nil, isSearching: false) } else { + self.emojiSearchContext = nil var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: false) if !languageCode.lowercased().hasPrefix("en") { signal = signal @@ -1072,24 +1072,25 @@ public class StickerPickerScreen: ViewController { signal, hasPremium ) - |> mapToSignal { keywords, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + |> mapToSignal { keywords, hasPremium -> Signal<(groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?), NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword } } - let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> + let remoteSignal: Signal + let emojiSearchContext: EmojiSearchContext? if hasPremium { - remoteSignal = context.engine.stickers.searchEmoji(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + let currentEmojiSearchContext = context.engine.stickers.emojiSearchContext(query: query, emoticon: Array(allEmoticons.keys), inputLanguageCode: languageCode) + emojiSearchContext = currentEmojiSearchContext + remoteSignal = currentEmojiSearchContext.state } else { - remoteSignal = .single(([], true)) + emojiSearchContext = nil + remoteSignal = .single(EmojiSearchContext.State(items: [], canLoadMore: false, isLoadingMore: false)) } return remoteSignal - |> mapToSignal { foundEmoji -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in - if foundEmoji.items.isEmpty && !foundEmoji.isFinalResult { - return .complete() - } + |> map { foundEmoji -> (groups: [EmojiPagerContentComponent.ItemGroup], canLoadMore: Bool, isSearching: Bool, searchContext: EmojiSearchContext?) in var items: [EmojiPagerContentComponent.Item] = [] let appendUnicodeEmoji = { @@ -1139,7 +1140,7 @@ public class StickerPickerScreen: ViewController { appendUnicodeEmoji() } - return .single([EmojiPagerContentComponent.ItemGroup( + return ([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", title: nil, @@ -1156,7 +1157,7 @@ public class StickerPickerScreen: ViewController { headerItem: nil, fillWithLoadingPlaceholders: false, items: items - )]) + )], foundEmoji.canLoadMore, foundEmoji.items.isEmpty && foundEmoji.isLoadingMore, emojiSearchContext) } } @@ -1169,11 +1170,13 @@ public class StickerPickerScreen: ViewController { return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result, id: AnyHashable(query), version: version, isPreset: false), isSearching: false) + self.emojiSearchContext = result.searchContext + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.groups, id: AnyHashable(query), version: version, isPreset: false, canLoadMore: result.canLoadMore), isSearching: result.isSearching) version += 1 })) } case let .category(value): + self.emojiSearchContext = nil let resultSignal = context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -1246,10 +1249,10 @@ public class StickerPickerScreen: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } @@ -1261,6 +1264,9 @@ public class StickerPickerScreen: ViewController { self?.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) }, onScroll: {}, + loadMore: { [weak self] in + self?.emojiSearchContext?.loadMore() + }, chatPeerId: nil, peekBehavior: nil, customLayout: nil, @@ -1299,14 +1305,12 @@ public class StickerPickerScreen: ViewController { } let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (controller.context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] views in - guard let self, let controller = self.controller, let view = views.views[viewKey] as? OrderedItemListView else { + let _ = (controller.context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedStickerPacks()) + |> deliverOnMainQueue).start(next: { [weak self] featuredStickerPacks in + guard let self, let controller = self.controller else { return } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in featuredStickerPacks { if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { controller.pushController(FeaturedStickersScreen( context: controller.context, @@ -1357,14 +1361,9 @@ public class StickerPickerScreen: ViewController { return } let context = controller.context - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedStickerPacks()) + |> deliverOnMainQueue).start(next: { featuredStickerPacks in + for featuredStickerPack in featuredStickerPacks { if featuredStickerPack.info.id == collectionId { let _ = (context.engine.stickers.loadedStickerPack(reference: .id(id: featuredStickerPack.info.id.id, accessHash: featuredStickerPack.info.accessHash), forceActualized: false) |> mapToSignal { result -> Signal in @@ -1373,7 +1372,7 @@ public class StickerPickerScreen: ViewController { if installed { return .complete() } else { - return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) + return context.engine.stickers.addStickerPackInteractively(info: info._parse(), items: items) |> map { _ in return Void() } } case .fetching: break @@ -1413,15 +1412,10 @@ public class StickerPickerScreen: ViewController { ])]) context.sharedContext.mainWindow?.presentInGlobalOverlay(actionSheet) } else if groupId == AnyHashable("featuredTop") { - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) - let _ = (context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return - } + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Collections.FeaturedStickerPacks()) + |> deliverOnMainQueue).start(next: { featuredStickerPacks in var stickerPackIds: [Int64] = [] - for featuredStickerPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredStickerPack in featuredStickerPacks { stickerPackIds.append(featuredStickerPack.info.id.id) } let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: stickerPackIds).start() @@ -1533,10 +1527,10 @@ public class StickerPickerScreen: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) return } - strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true, canLoadMore: false), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD index 137ec4fb05..e1a4e986f4 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/BUILD +++ b/submodules/TelegramUI/Components/StorageUsageScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/ComponentFlow:ComponentFlow", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index 7b5f78fb54..c6cf49f953 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -911,7 +911,7 @@ final class DataUsageScreenComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(value: .modeChanged))) })), environment: {}, - containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 16.0 + 44.0 + 10.0) * 2.0, height: 100.0) + containerSize: CGSize(width: availableSize.width - (environment.safeInsets.left + 36.0) * 2.0, height: 100.0) ) let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize) if let segmentedControlComponentView = self.segmentedControlView.view { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift index 89a1494604..f42ef91240 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageFileListPanelComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode import AvatarNode @@ -102,35 +101,22 @@ private let smallExtensionFont = Font.with(size: 12.0, design: .round, weight: . private final class FileListItemComponent: Component { enum Icon: Equatable { case fileExtension(String) - case media(Media, TelegramMediaImageRepresentation) + case mediaFile(TelegramMediaFile, TelegramMediaImageRepresentation) + case mediaImage(TelegramMediaImage, TelegramMediaImageRepresentation) case audio - + static func ==(lhs: Icon, rhs: Icon) -> Bool { - switch lhs { - case let .fileExtension(value): - if case .fileExtension(value) = rhs { - return true - } else { - return false - } - case let .media(media, representation): - if case let .media(rhsMedia, rhsRepresentation) = rhs { - if media.id != rhsMedia.id { - return false - } - if representation != rhsRepresentation { - return false - } - return true - } else { - return false - } - case .audio: - if case .audio = rhs { - return true - } else { - return false - } + switch (lhs, rhs) { + case let (.fileExtension(l), .fileExtension(r)): + return l == r + case let (.mediaFile(lFile, lRepresentation), .mediaFile(rFile, rRepresentation)): + return lFile.fileId == rFile.fileId && lRepresentation == rRepresentation + case let (.mediaImage(lImage, lRepresentation), .mediaImage(rImage, rRepresentation)): + return lImage.imageId == rImage.imageId && lRepresentation == rRepresentation + case (.audio, .audio): + return true + default: + return false } } } @@ -445,25 +431,34 @@ private final class FileListItemComponent: Component { } } - if case let .media(media, representation) = component.icon { + let mediaRepresentation: TelegramMediaImageRepresentation? + switch component.icon { + case let .mediaFile(_, representation), let .mediaImage(_, representation): + mediaRepresentation = representation + default: + mediaRepresentation = nil + } + + if let representation = mediaRepresentation { var resetImage = false - + let iconImageNode: TransformImageNode if let current = self.iconImageNode { iconImageNode = current } else { resetImage = true - + iconImageNode = TransformImageNode() self.iconImageNode = iconImageNode self.containerButton.addSubview(iconImageNode.view) } - + let iconSize = CGSize(width: 40.0, height: 40.0) let imageSize: CGSize = representation.dimensions.cgSize - + if resetImage { - if let file = media as? TelegramMediaFile { + switch component.icon { + case let .mediaFile(file, _): iconImageNode.setSignal(chatWebpageSnippetFile( account: component.context.account, userLocation: .peer(component.messageId.peerId), @@ -471,19 +466,21 @@ private final class FileListItemComponent: Component { representation: representation, automaticFetch: false )) - } else if let image = media as? TelegramMediaImage { + case let .mediaImage(image, _): iconImageNode.setSignal(mediaGridMessagePhoto( account: component.context.account, userLocation: .peer(component.messageId.peerId), photoReference: ImageMediaReference.standalone(media: image), automaticFetch: false )) + default: + break } } - + let iconFrame = CGRect(origin: CGPoint(x: iconLeftInset + floor((leftInset - iconLeftInset - iconSize.width) / 2.0), y: floor((height - verticalInset * 2.0 - iconSize.height) / 2.0)), size: iconSize) transition.setFrame(view: iconImageNode.view, frame: iconFrame) - + let iconImageLayout = iconImageNode.asyncLayout() let iconImageApply = iconImageLayout(TransformImageArguments( corners: ImageCorners(radius: 8.0), @@ -631,11 +628,11 @@ final class StorageFileListPanelComponent: Component { typealias EnvironmentType = StorageUsagePanelEnvironment final class Item: Equatable { - let message: Message + let message: EngineMessage let size: Int64 - + init( - message: Message, + message: EngineMessage, size: Int64 ) { self.message = message @@ -982,14 +979,14 @@ final class StorageFileListPanelComponent: Component { extensionIconValue = fileExtension if let representation = smallestImageRepresentation(file.previewRepresentations) { - imageIconValue = .media(file, representation) + imageIconValue = .mediaFile(file, representation) } } } else if let image = media as? TelegramMediaImage { title = environment.strings.Message_Photo if let representation = largestImageRepresentation(image.representations) { - imageIconValue = .media(image, representation) + imageIconValue = .mediaImage(image, representation) } } } @@ -1062,7 +1059,7 @@ final class StorageFileListPanelComponent: Component { component: AnyComponent(FileListItemComponent( context: component.context, theme: environment.theme, - messageId: EngineMessage.Id(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), namespace: 0, id: 0), + messageId: EngineMessage.Id(peerId: component.context.account.peerId, namespace: 0, id: 0), title: "ABCDEF", subtitle: "ABCDEF", label: "1000", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index a7c10155d9..463115e109 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -9,7 +9,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import EmojiStatusComponent import Markdown @@ -223,7 +222,7 @@ final class StorageUsageScreenComponent: Component { return true } - func togglePeer(id: EnginePeer.Id, availableMessages: [EngineMessage.Id: Message]) -> SelectionState { + func togglePeer(id: EnginePeer.Id, availableMessages: [EngineMessage.Id: EngineMessage]) -> SelectionState { var selectedPeers = self.selectedPeers var selectedMessages = self.selectedMessages @@ -351,32 +350,32 @@ final class StorageUsageScreenComponent: Component { let peerId: EnginePeer.Id? let stats: AllStorageUsageStats let contextStats: StorageUsageStats - let messages: [MessageId: Message] - + let messages: [EngineMessage.Id: EngineMessage] + var isSelectingPeers: Bool = false private(set) var selectionState: SelectionState - + let existingCategories: Set private(set) var selectedCategories: Set - + let peerItems: StoragePeerListPanelComponent.Items? let imageItems: StorageMediaGridPanelComponent.Items? let fileItems: StorageFileListPanelComponent.Items? let musicItems: StorageFileListPanelComponent.Items? - + private let allPhotos: Set private let allVideos: Set private let allFiles: Set private let allMusic: Set - + private(set) var selectedSize: Int64 = 0 - private(set) var clearIncludeMessages: [Message] = [] - private(set) var clearExcludeMessages: [Message] = [] - + private(set) var clearIncludeMessages: [EngineMessage] = [] + private(set) var clearExcludeMessages: [EngineMessage] = [] + init( peerId: EnginePeer.Id?, stats: AllStorageUsageStats, - messages: [MessageId: Message], + messages: [EngineMessage.Id: EngineMessage], peerItems: StoragePeerListPanelComponent.Items?, imageItems: StorageMediaGridPanelComponent.Items?, fileItems: StorageFileListPanelComponent.Items?, @@ -581,16 +580,16 @@ final class StorageUsageScreenComponent: Component { } } - var clearIncludeMessages: [Message] = [] - var clearExcludeMessages: [Message] = [] - + var clearIncludeMessages: [EngineMessage] = [] + var clearExcludeMessages: [EngineMessage] = [] + if self.selectedCategories.contains(.photos) { let deselectedPhotos = self.allPhotos.subtracting(self.selectionState.selectedMessages) if !deselectedPhotos.isEmpty, let imageItems = self.imageItems { for item in imageItems.items { if deselectedPhotos.contains(item.message.id) { selectedSize -= item.size - clearExcludeMessages.append(item.message._asMessage()) + clearExcludeMessages.append(item.message) } } } @@ -600,19 +599,19 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if selectedPhotos.contains(item.message.id) { selectedSize += item.size - clearIncludeMessages.append(item.message._asMessage()) + clearIncludeMessages.append(item.message) } } } } - + if self.selectedCategories.contains(.videos) { let deselectedVideos = self.allVideos.subtracting(self.selectionState.selectedMessages) if !deselectedVideos.isEmpty, let imageItems = self.imageItems { for item in imageItems.items { if deselectedVideos.contains(item.message.id) { selectedSize -= item.size - clearExcludeMessages.append(item.message._asMessage()) + clearExcludeMessages.append(item.message) } } } @@ -622,12 +621,12 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if selectedVideos.contains(item.message.id) { selectedSize += item.size - clearIncludeMessages.append(item.message._asMessage()) + clearIncludeMessages.append(item.message) } } } } - + if self.selectedCategories.contains(.files) { let deselectedFiles = self.allFiles.subtracting(self.selectionState.selectedMessages) if !deselectedFiles.isEmpty, let fileItems = self.fileItems { @@ -649,7 +648,7 @@ final class StorageUsageScreenComponent: Component { } } } - + if self.selectedCategories.contains(.music) { let deselectedMusic = self.allMusic.subtracting(self.selectionState.selectedMessages) if !deselectedMusic.isEmpty, let musicItems = self.musicItems { @@ -1044,17 +1043,11 @@ final class StorageUsageScreenComponent: Component { if self.statsDisposable == nil { let context = component.context - let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings])) - let cacheSettingsExceptionCount: Signal<[CacheStorageSettings.PeerStorageCategory: Int32], NoError> = component.context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> AccountSpecificCacheStorageSettings in - let cacheSettings: AccountSpecificCacheStorageSettings - if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) { - cacheSettings = value - } else { - cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings - } - - return cacheSettings + let cacheSettingsExceptionCount: Signal<[CacheStorageSettings.PeerStorageCategory: Int32], NoError> = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.accountSpecificCacheStorageSettings) + ) + |> map { preferencesEntry -> AccountSpecificCacheStorageSettings in + return preferencesEntry?.get(AccountSpecificCacheStorageSettings.self) ?? AccountSpecificCacheStorageSettings.defaultSettings } |> distinctUntilChanged |> mapToSignal { accountSpecificSettings -> Signal<[CacheStorageSettings.PeerStorageCategory: Int32], NoError> in @@ -2082,7 +2075,7 @@ final class StorageUsageScreenComponent: Component { let peerInfoController = component.context.sharedContext.makePeerInfoController( context: component.context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, @@ -2388,20 +2381,20 @@ final class StorageUsageScreenComponent: Component { } class RenderResult { - var messages: [MessageId: Message] = [:] + var messages: [EngineMessage.Id: EngineMessage] = [:] var imageItems: [StorageMediaGridPanelComponent.Item] = [] var fileItems: [StorageFileListPanelComponent.Item] = [] var musicItems: [StorageFileListPanelComponent.Item] = [] } - + self.messagesDisposable = (component.context.engine.resources.renderStorageUsageStatsMessages(stats: contextStats, categories: [.files, .photos, .videos, .music], existingMessages: self.aggregatedData?.messages ?? [:]) |> deliverOn(Queue()) |> map { messages -> RenderResult in let result = RenderResult() - + result.messages = messages - var mergedMedia: [MessageId: Int64] = [:] + var mergedMedia: [EngineMessage.Id: Int64] = [:] if let categoryStats = contextStats.categories[.photos] { mergedMedia = categoryStats.messages } @@ -2429,7 +2422,7 @@ final class StorageUsageScreenComponent: Component { if matches { result.imageItems.append(StorageMediaGridPanelComponent.Item( - message: EngineMessage(message), + message: message, size: messageSize )) } @@ -2565,7 +2558,7 @@ final class StorageUsageScreenComponent: Component { chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: nil, - message: message, + message: message._asMessage(), standalone: true, reverseMessageGalleryOrder: false, navigationController: self.controller?()?.navigationController as? NavigationController @@ -2764,25 +2757,21 @@ final class StorageUsageScreenComponent: Component { self.controller?()?.presentInGlobalOverlay(controller) } - private func openMessage(message: Message) { + private func openMessage(message: EngineMessage) { guard let component = self.component else { return } guard let controller = self.controller?(), let navigationController = controller.navigationController as? NavigationController else { return } - let foundGalleryMessage: Message? = message - guard let galleryMessage = foundGalleryMessage else { - return - } self.endEditing(true) - + let _ = component.context.sharedContext.openChatMessage(OpenChatMessageParams( context: component.context, chatLocation: .peer(id: message.id.peerId), chatFilterTag: nil, chatLocationContextHolder: nil, - message: galleryMessage, + message: message._asMessage(), standalone: true, reverseMessageGalleryOrder: true, navigationController: navigationController, @@ -3067,9 +3056,9 @@ final class StorageUsageScreenComponent: Component { } } - var includeMessages: [Message] = [] - var excludeMessages: [Message] = [] - + var includeMessages: [EngineMessage] = [] + var excludeMessages: [EngineMessage] = [] + for (id, message) in aggregatedData.messages { if aggregatedData.selectionState.selectedPeers.contains(id.peerId) { if !aggregatedData.selectionState.selectedMessages.contains(id) { @@ -3081,7 +3070,7 @@ final class StorageUsageScreenComponent: Component { } } } - + let _ = (component.context.engine.resources.clearStorage(peerIds: aggregatedData.selectionState.selectedPeers, includeMessages: includeMessages, excludeMessages: excludeMessages) |> deliverOnMainQueue).start(next: { [weak self] progress in guard let self else { @@ -3132,69 +3121,52 @@ final class StorageUsageScreenComponent: Component { self.controller?()?.presentInGlobalOverlay(c, with: nil) } - let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.accountSpecificCacheStorageSettings])) - let accountSpecificSettings: Signal = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> AccountSpecificCacheStorageSettings in - let cacheSettings: AccountSpecificCacheStorageSettings - if let view = views.views[viewKey] as? PreferencesView, let value = view.values[PreferencesKeys.accountSpecificCacheStorageSettings]?.get(AccountSpecificCacheStorageSettings.self) { - cacheSettings = value - } else { - cacheSettings = AccountSpecificCacheStorageSettings.defaultSettings - } - - return cacheSettings + let accountSpecificSettings: Signal = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.accountSpecificCacheStorageSettings) + ) + |> map { preferencesEntry -> AccountSpecificCacheStorageSettings in + return preferencesEntry?.get(AccountSpecificCacheStorageSettings.self) ?? AccountSpecificCacheStorageSettings.defaultSettings } |> distinctUntilChanged - - let peerExceptions: Signal<[(peer: FoundPeer, value: Int32)], NoError> = accountSpecificSettings - |> mapToSignal { accountSpecificSettings -> Signal<[(peer: FoundPeer, value: Int32)], NoError> in - return context.account.postbox.transaction { transaction -> [(peer: FoundPeer, value: Int32)] in - var result: [(peer: FoundPeer, value: Int32)] = [] - + + let peerExceptions: Signal<[(peer: EnginePeer, value: Int32)], NoError> = accountSpecificSettings + |> mapToSignal { accountSpecificSettings -> Signal<[(peer: EnginePeer, value: Int32)], NoError> in + return context.engine.data.get( + EngineDataMap(accountSpecificSettings.peerStorageTimeoutExceptions.map(\.key).map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))) + ) + |> map { peers -> [(peer: EnginePeer, value: Int32)] in + var result: [(peer: EnginePeer, value: Int32)] = [] + for item in accountSpecificSettings.peerStorageTimeoutExceptions { - let peerId = item.key - let value = item.value - - guard let peer = transaction.getPeer(peerId) else { + guard let peer = peers[item.key] ?? nil else { continue } let peerCategory: CacheStorageSettings.PeerStorageCategory - var subscriberCount: Int32? - if peer is TelegramUser { + switch peer { + case .user, .secretChat: peerCategory = .privateChats - } else if peer is TelegramGroup { + case .legacyGroup: peerCategory = .groups - - if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData { - subscriberCount = (cachedData.participants?.participants.count).flatMap(Int32.init) - } - } else if let channel = peer as? TelegramChannel { + case let .channel(channel): if case .group = channel.info { peerCategory = .groups } else { peerCategory = .channels } - if peerCategory == mappedCategory { - if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData { - subscriberCount = cachedData.participantsSummary.memberCount - } - } - } else { - continue } - + if peerCategory != mappedCategory { continue } - - result.append((peer: FoundPeer(peer: peer, subscribers: subscriberCount), value: value)) + + result.append((peer: peer, value: item.value)) } - + return result.sorted(by: { lhs, rhs in if lhs.value != rhs.value { return lhs.value < rhs.value } - return lhs.peer.peer.debugDisplayTitle < rhs.peer.peer.debugDisplayTitle + return lhs.peer.debugDisplayTitle < rhs.peer.debugDisplayTitle }) } } @@ -3289,7 +3261,7 @@ final class StorageUsageScreenComponent: Component { } }))) } else { - subItems.append(.custom(MultiplePeerAvatarsContextItem(context: context, peers: peerExceptions.prefix(3).map { EnginePeer($0.peer.peer) }, totalCount: peerExceptions.count, action: { c, _ in + subItems.append(.custom(MultiplePeerAvatarsContextItem(context: context, peers: peerExceptions.prefix(3).map { $0.peer }, totalCount: peerExceptions.count, action: { c, _ in c.dismiss(completion: { }) @@ -3351,6 +3323,7 @@ public final class StorageUsageScreen: ViewControllerComponentContainer { if peer != nil { self.navigationPresentation = .modal + self._hasGlassStyle = true } self.readyValue.set(componentReady.get() |> timeout(0.3, queue: .mainQueue(), alternate: .single(true))) diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index 3e4d303edd..82bb8586e2 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -7,7 +7,6 @@ import ComponentFlow import SwiftSignalKit import AccountContext import TelegramCore -import Postbox import MultilineTextComponent import AvatarNode import TelegramPresentationData @@ -229,7 +228,7 @@ public final class PeerListItemComponent: Component { let avatar: Avatar? let avatarComponent: AnyComponent? let peer: EnginePeer? - let storyStats: PeerStoryStats? + let storyStats: EnginePeerStoryStats? let subtitle: Subtitle? let subtitleComponent: AnyComponent? let subtitleAccessory: SubtitleAccessory @@ -260,7 +259,7 @@ public final class PeerListItemComponent: Component { avatar: Avatar? = nil, avatarComponent: AnyComponent? = nil, peer: EnginePeer?, - storyStats: PeerStoryStats? = nil, + storyStats: EnginePeerStoryStats? = nil, subtitle: Subtitle?, subtitleComponent: AnyComponent? = nil, subtitleAccessory: SubtitleAccessory, @@ -1320,11 +1319,11 @@ public final class PeerListItemComponent: Component { var mediaReference: AnyMediaReference? - if let peer = component.peer, let peerReference = PeerReference(peer._asPeer()) { + if let peer = component.peer, let peerReference = PeerReference(peer) { if let story = component.story { mediaReference = .story(peer: peerReference, id: story.id, media: story.media._asMedia()) } else if let message = component.message { - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? for media in message.media { if let image = media as? TelegramMediaImage { selectedMedia = image diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 3dfa33e15c..f9476bb580 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -36,7 +36,6 @@ swift_library( "//submodules/PhoneNumberFormat", "//submodules/TelegramIntents", "//submodules/LegacyUI", - "//submodules/WebSearchUI", "//submodules/PremiumUI", "//submodules/ICloudResources", "//submodules/LegacyComponents", @@ -80,6 +79,7 @@ swift_library( "//submodules/TextSelectionNode", "//submodules/Pasteboard", "//submodules/Speak", + "//submodules/TelegramUI/Components/TextProcessingScreen", "//submodules/TranslateUI", "//submodules/TelegramNotices", "//submodules/MediaPlayer:UniversalMediaPlayer", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/LiveChatReactionStreamView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/LiveChatReactionStreamView.swift index c80f66635f..03d040b559 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/LiveChatReactionStreamView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/LiveChatReactionStreamView.swift @@ -107,12 +107,12 @@ private func makePeerBadgeImage(engine: TelegramEngine, peer: EnginePeer, count: textSize.height = ceil(textSize.height) var avatarSourceImage: UIImage? - if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let peerReference = PeerReference(peer._asPeer()) { - let disposable = fetchedMediaResource(mediaBox: engine.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .avatar, reference: .avatar(peer: peerReference, resource: resource)).startStrict() - let signal = engine.account.postbox.mediaBox.resourceData(resource) - |> filter { $0.complete } + if let resource = smallestImageRepresentation(peer.profileImageRepresentations)?.resource, let peerReference = PeerReference(peer) { + let disposable = engine.resources.fetch(reference: .avatar(peer: peerReference, resource: resource), userLocation: .peer(peer.id), userContentType: .avatar).startStrict() + let signal = engine.resources.data(resource: EngineMediaResource(resource)) + |> filter { $0.isComplete } |> map { value -> Data? in - if value.complete { + if value.isComplete { return try? Data(contentsOf: URL(fileURLWithPath: value.path)) } else { return nil diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/OpenStories.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/OpenStories.swift index 242839a492..71192f8763 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/OpenStories.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/OpenStories.swift @@ -4,7 +4,6 @@ import Display import AccountContext import SwiftSignalKit import TelegramCore -import Postbox import AvatarNode public extension StoryContainerScreen { @@ -170,7 +169,7 @@ public extension StoryContainerScreen { parentController: ViewController, transitionIn: @escaping () -> StoryContainerScreen.TransitionIn?, transitionOut: @escaping (EnginePeer.Id) -> StoryContainerScreen.TransitionOut?, - setFocusedItem: @escaping (Signal) -> Void, + setFocusedItem: @escaping (Signal) -> Void, setProgress: @escaping (Signal) -> Void, completion: @escaping (StoryContainerScreen) -> Void = { _ in } ) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 9bcaa14158..fbda84417d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -207,9 +207,9 @@ public final class StoryContentContextImpl: StoryContentContext { if let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData { var isMuted = false if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings, topSearchPeers: []) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings, topSearchPeers: []) } else { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil, topSearchPeers: []) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: nil, topSearchPeers: []) } additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -1010,7 +1010,7 @@ public final class StoryContentContextImpl: StoryContentContext { for i in 0 ..< min(possibleItems.count, 3) { let peer = possibleItems[i].0 let item = possibleItems[i].1 - if let peerReference = PeerReference(peer._asPeer()), let mediaId = item.media.id { + if let peerReference = PeerReference(peer), let mediaId = item.media.id { var reactions: [MessageReaction.Reaction] = [] for mediaArea in item.mediaAreas { if case let .reaction(_, reaction, _) = mediaArea { @@ -1284,7 +1284,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext { return } - let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -1596,7 +1596,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { let stateValue: StoryContentContextState if let focusedIndex, let (peer, presence, areVoiceMessagesAvailable, canViewStats, notificationSettings, globalNotificationSettings, isPremiumRequiredForMessaging, boostsToUnrestrict, appliedBoosts, sendPaidMessageStars) = data?.data, let peer { - let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: []) let additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, areVoiceMessagesAvailable: areVoiceMessagesAvailable, @@ -1714,7 +1714,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { for i in 0 ..< min(possibleItems.count, 3) { let peer = possibleItems[i].0 let item = possibleItems[i].1 - if let peerReference = PeerReference(peer._asPeer()), let mediaId = item.storyItem.media.id { + if let peerReference = PeerReference(peer), let mediaId = item.storyItem.media.id { var reactions: [MessageReaction.Reaction] = [] for mediaArea in item.storyItem.mediaAreas { if case let .reaction(_, reaction, _) = mediaArea { @@ -1916,13 +1916,13 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - return combineLatest(files.map { file -> Signal in return Signal { subscriber in - let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + let loadSignal = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker) |> ignoreValues |> `catch` { _ -> Signal in return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -1963,13 +1963,13 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - return combineLatest(files.map { file -> Signal in return Signal { subscriber in - let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + let loadSignal = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker) |> ignoreValues |> `catch` { _ -> Signal in return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -2028,7 +2028,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine guard let peerValue else { return .complete() } - guard let peer = PeerReference(peerValue._asPeer()) else { + guard let peer = PeerReference(peerValue) else { return .complete() } @@ -2065,9 +2065,9 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine case let .image(image): if let representation = largestImageRepresentation(image.representations) { statusSignals.append( - context.account.postbox.mediaBox.resourceData(representation.resource) + context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> filter { data in - return data.complete + return data.isComplete } |> take(1) |> ignoreValues @@ -2091,7 +2091,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine } statusSignals.append( - context.account.postbox.mediaBox.resourceRangesStatus(file.resource) + context.engine.resources.resourceRangesStatus(resource: EngineMediaResource(file.resource)) |> filter { ranges in if let fetchRange { return ranges.isSuperset(of: RangeSet(fetchRange.0)) @@ -2152,13 +2152,13 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine return combineLatest(files.map { file -> Signal in return Signal { subscriber in - let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + let loadSignal = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker) |> ignoreValues |> `catch` { _ -> Signal in return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -2201,13 +2201,13 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine return combineLatest(files.map { file -> Signal in return Signal { subscriber in - let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + let loadSignal = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker) |> ignoreValues |> `catch` { _ -> Signal in return .complete() } - let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + let statusSignal = context.engine.resources.status(resource: EngineMediaResource(file.resource)) |> filter { status in if case .Local = status { return true @@ -2487,9 +2487,9 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { if let cachedUserData = cachedPeerDataView.cachedPeerData as? CachedUserData { var isMuted = false if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings, topSearchPeers: []) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: notificationSettings, topSearchPeers: []) } else { - isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: nil, topSearchPeers: []) + isMuted = resolvedAreStoriesMuted(globalSettings: globalNotificationSettings._asGlobalNotificationSettings(), peer: peer, peerSettings: nil, topSearchPeers: []) } additionalPeerData = StoryContentContextState.AdditionalPeerData( isMuted: isMuted, @@ -2974,7 +2974,7 @@ public final class RepostStoriesContentContextImpl: StoryContentContext { for i in 0 ..< min(possibleItems.count, 3) { let peer = possibleItems[i].0 let item = possibleItems[i].1 - if let peerReference = PeerReference(peer._asPeer()), let mediaId = item.media.id { + if let peerReference = PeerReference(peer), let mediaId = item.media.id { var reactions: [MessageReaction.Reaction] = [] for mediaArea in item.mediaAreas { if case let .reaction(_, reaction, _) = mediaArea { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index e48abd47d2..314cc72cc6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -8,7 +8,6 @@ import SwiftSignalKit import AppBundle import MessageInputPanelComponent import TelegramCore -import Postbox import UndoUI import ReactionSelectionNode import EntityKeyboard @@ -276,14 +275,14 @@ private final class StoryContainerScreenComponent: Component { let context: AccountContext let content: StoryContentContext - let focusedItemPromise: Promise + let focusedItemPromise: Promise let transitionIn: StoryContainerScreen.TransitionIn? let transitionOut: (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut? init( context: AccountContext, content: StoryContentContext, - focusedItemPromise: Promise, + focusedItemPromise: Promise, transitionIn: StoryContainerScreen.TransitionIn?, transitionOut: @escaping (EnginePeer.Id, Int32) -> StoryContainerScreen.TransitionOut? ) { @@ -370,7 +369,7 @@ private final class StoryContainerScreenComponent: Component { private let backgroundLayer: SimpleLayer private let backgroundEffectView: BlurredBackgroundView - private let focusedItem = ValuePromise(nil, ignoreRepeated: true) + private let focusedItem = ValuePromise(nil, ignoreRepeated: true) private var stateValue: StoryContentContextState? private var contentUpdatedDisposable: Disposable? @@ -424,7 +423,7 @@ private final class StoryContainerScreenComponent: Component { var longPressRecognizer: StoryLongPressRecognizer? - private var pendingNavigationToItemId: StoryId? + private var pendingNavigationToItemId: EngineStoryId? private let interactionGuide = ComponentView() private var isDisplayingInteractionGuide: Bool = false @@ -982,6 +981,9 @@ private final class StoryContainerScreenComponent: Component { if let environment = self.environment, case .regular = environment.metrics.widthClass { if result.isDescendant(of: self.backgroundEffectView) { if let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] { + if point.x < itemSetView.frame.minX || point.x > itemSetView.frame.maxX { + return result + } return itemSetView.view.view } } @@ -1229,7 +1231,7 @@ private final class StoryContainerScreenComponent: Component { self.commitHorizontalPan(velocity: CGPoint(x: 200.0, y: 0.0)) } } else { - var mappedId: StoryId? + var mappedId: EngineStoryId? switch direction { case .previous: mappedId = slice.previousItemId @@ -1245,6 +1247,13 @@ private final class StoryContainerScreenComponent: Component { } } } + + fileprivate func navigateWithKeyShortcut(direction: StoryItemSetContainerComponent.NavigationDirection) { + guard !hasFirstResponder(self) else { + return + } + self.navigate(direction: direction) + } func presentExternalTooltip(_ tooltipScreen: UndoOverlayController) { guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else { @@ -1367,10 +1376,10 @@ private final class StoryContainerScreenComponent: Component { let stateValue = component.content.stateValue - var focusedItemId: StoryId? + var focusedItemId: EngineStoryId? var isVideo = false if let slice = stateValue?.slice { - focusedItemId = StoryId(peerId: slice.peer.id, id: slice.item.storyItem.id) + focusedItemId = EngineStoryId(peerId: slice.peer.id, id: slice.item.storyItem.id) if case .file = slice.item.storyItem.media { isVideo = true } @@ -2012,7 +2021,7 @@ private final class StoryContainerScreenComponent: Component { } } -public class StoryContainerScreen: ViewControllerComponentContainer { +public class StoryContainerScreen: ViewControllerComponentContainer, KeyShortcutResponder { public struct TransitionState: Equatable { public var sourceSize: CGSize public var destinationSize: CGSize @@ -2093,8 +2102,8 @@ public class StoryContainerScreen: ViewControllerComponentContainer { private var didAnimateIn: Bool = false private var isDismissed: Bool = false - private let focusedItemPromise = Promise() - public var focusedItem: Signal { + private let focusedItemPromise = Promise() + public var focusedItem: Signal { return self.focusedItemPromise.get() } @@ -2152,6 +2161,56 @@ public class StoryContainerScreen: ViewControllerComponentContainer { } } } + + public var keyShortcuts: [KeyShortcut] { + if self.isViewLoaded, hasFirstResponder(self.view) { + return [] + } + var keyShortcuts: [KeyShortcut] = [] + keyShortcuts.append( + KeyShortcut( + title: "", + input: UIKeyCommand.inputUpArrow, + modifiers: [.command], + action: { [weak self] in + self?.dismiss() + } + ) + ) + keyShortcuts.append( + KeyShortcut( + title: "", + input: "W", + modifiers: [.command], + action: { [weak self] in + self?.dismiss() + } + ) + ) + keyShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputLeftArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.navigateWithKeyShortcut(direction: .previous) + } + } + ) + ) + keyShortcuts.append( + KeyShortcut( + input: UIKeyCommand.inputRightArrow, + modifiers: [], + action: { [weak self] in + if let componentView = self?.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.navigateWithKeyShortcut(direction: .next) + } + } + ) + ) + return keyShortcuts + } public func presentExternalTooltip(_ tooltipScreen: UndoOverlayController) { if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { @@ -2232,13 +2291,9 @@ public class StoryContainerScreen: ViewControllerComponentContainer { } func allowedStoryReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { - let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) - let topReactions = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> [RecentReactionItem] in - guard let view = views.views[viewKey] as? OrderedItemListView else { - return [] - } - return view.items.compactMap { item -> RecentReactionItem? in + let topReactions = context.engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: Namespaces.OrderedItemList.CloudTopReactions)) + |> map { items -> [RecentReactionItem] in + return items.compactMap { item -> RecentReactionItem? in return item.contents.get(RecentReactionItem.self) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index c4ba0356a8..2530a4d73e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData public final class StoryContentItem: Equatable { @@ -30,7 +29,7 @@ public final class StoryContentItem: Equatable { } public final class SharedState { - public var replyDrafts: [StoryId: NSAttributedString] = [:] + public var replyDrafts: [EngineStoryId: NSAttributedString] = [:] public var baseRate: Double = 1.0 public init() { @@ -71,7 +70,7 @@ public final class StoryContentItem: Equatable { public let containerInsets: UIEdgeInsets public let presentationProgressUpdated: (Double, Bool, Bool) -> Void public let customItemSubtitleUpdated: () -> Void - public let markAsSeen: (StoryId) -> Void + public let markAsSeen: (EngineStoryId) -> Void public init( externalState: ExternalState, @@ -80,7 +79,7 @@ public final class StoryContentItem: Equatable { containerInsets: UIEdgeInsets, presentationProgressUpdated: @escaping (Double, Bool, Bool) -> Void, customItemSubtitleUpdated: @escaping () -> Void, - markAsSeen: @escaping (StoryId) -> Void + markAsSeen: @escaping (EngineStoryId) -> Void ) { self.externalState = externalState self.sharedState = sharedState @@ -108,7 +107,7 @@ public final class StoryContentItem: Equatable { } } - public let id: StoryId + public let id: EngineStoryId public let position: Int? public let dayCounters: DayCounters? public let peerId: EnginePeer.Id? @@ -117,7 +116,7 @@ public final class StoryContentItem: Equatable { public let itemPeer: EnginePeer? public init( - id: StoryId, + id: EngineStoryId, position: Int?, dayCounters: DayCounters?, peerId: EnginePeer.Id?, @@ -231,10 +230,10 @@ public final class StoryContentContextState { public let additionalPeerData: AdditionalPeerData public let item: StoryContentItem public let totalCount: Int - public let previousItemId: StoryId? - public let nextItemId: StoryId? + public let previousItemId: EngineStoryId? + public let nextItemId: EngineStoryId? public let allItems: [StoryContentItem] - public let forwardInfoStories: [StoryId: Promise] + public let forwardInfoStories: [EngineStoryId: Promise] var effectivePeer: EnginePeer { return self.item.itemPeer ?? self.peer @@ -245,10 +244,10 @@ public final class StoryContentContextState { additionalPeerData: AdditionalPeerData, item: StoryContentItem, totalCount: Int, - previousItemId: StoryId?, - nextItemId: StoryId?, + previousItemId: EngineStoryId?, + nextItemId: EngineStoryId?, allItems: [StoryContentItem], - forwardInfoStories: [StoryId: Promise] + forwardInfoStories: [EngineStoryId: Promise] ) { self.peer = peer self.additionalPeerData = additionalPeerData @@ -305,7 +304,7 @@ public enum StoryContentContextNavigation { public enum ItemDirection { case previous case next - case id(StoryId) + case id(EngineStoryId) } public enum PeerDirection { @@ -324,5 +323,5 @@ public protocol StoryContentContext: AnyObject { func resetSideStates() func navigate(navigation: StoryContentContextNavigation) - func markAsSeen(id: StoryId) + func markAsSeen(id: EngineStoryId) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 25cefe535d..7dbb255eae 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import MultilineTextComponent import AccountContext -import Postbox import TelegramCore import TextNodeWithEntities import TextFormat diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift index dd9701b798..f7ef92e112 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentLiveChatComponent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import MultilineTextComponent import AccountContext -import Postbox import TelegramCore import TelegramPresentationData import SwiftSignalKit @@ -367,7 +366,7 @@ final class StoryContentLiveChatComponent: Component { rank: nil, subscriptionUntilDate: nil ), - peer: author._asPeer() + peer: author )], mode: .liveStream( messageCount: 1, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 75fe51d2a7..b476f088d6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import AccountContext import TelegramCore -import Postbox import AsyncDisplayKit import PhotoResources import SwiftSignalKit @@ -32,7 +31,7 @@ final class StoryItemContentComponent: Component { let peer: EnginePeer let item: EngineStoryItem let availableReactions: StoryAvailableReactions? - let entityFiles: [MediaId: TelegramMediaFile] + let entityFiles: [EngineMedia.Id: TelegramMediaFile] let audioMode: StoryContentItem.AudioMode let baseRate: Double let isVideoBuffering: Bool @@ -44,7 +43,7 @@ final class StoryItemContentComponent: Component { let activateReaction: (UIView, MessageReaction.Reaction) -> Void let controller: () -> ViewController? - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, isUIHidden: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, canManageLiveChatMessagesFromPeers: Set, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void, controller: @escaping () -> ViewController?) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [EngineMedia.Id: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, baseRate: Double, isVideoBuffering: Bool, isCurrent: Bool, isUIHidden: Bool, preferHighQuality: Bool, isEmbeddedInCamera: Bool, canManageLiveChatMessagesFromPeers: Set, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void, controller: @escaping () -> ViewController?) { self.context = context self.strings = strings self.peer = peer @@ -335,7 +334,7 @@ final class StoryItemContentComponent: Component { return } - if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer._asPeer()) { + if case let .file(file) = currentMessageMedia, let peerReference = PeerReference(component.peer) { if self.videoNode == nil { let videoNode = UniversalVideoNode( context: component.context, @@ -604,7 +603,7 @@ final class StoryItemContentComponent: Component { if !self.markedAsSeen { self.markedAsSeen = true if let component = self.component { - self.environment?.markAsSeen(StoryId(peerId: component.peer.id, id: component.item.id)) + self.environment?.markAsSeen(EngineStoryId(peerId: component.peer.id, id: component.item.id)) } } @@ -729,7 +728,7 @@ final class StoryItemContentComponent: Component { if !self.markedAsSeen { self.markedAsSeen = true if let component = self.component { - self.environment?.markAsSeen(StoryId(peerId: component.peer.id, id: component.item.id)) + self.environment?.markAsSeen(EngineStoryId(peerId: component.peer.id, id: component.item.id)) } } @@ -843,7 +842,7 @@ final class StoryItemContentComponent: Component { let startTime = CFAbsoluteTimeGetCurrent() - let peerReference = PeerReference(component.peer._asPeer()) + let peerReference = PeerReference(component.peer) let selectedMedia: EngineMedia var messageMedia: EngineMedia? diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift index a5dcac6959..adf39cf105 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemImageView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import ComponentFlow import TinyThumbnail @@ -84,7 +83,7 @@ final class StoryItemImageView: UIView { dimensions = representation.dimensions.cgSize if isMediaUpdated { - if attemptSynchronous, let path = context.account.postbox.mediaBox.completedResourcePath(id: representation.resource.id, pathExtension: nil) { + if attemptSynchronous, let path = context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(representation.resource.id), pathExtension: nil) { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: path)?.preparingForDisplay() { self.updateImage(image: image, isCaptureProtected: isCaptureProtected) @@ -103,12 +102,12 @@ final class StoryItemImageView: UIView { } } - if let peerReference = PeerReference(peer._asPeer()) { + if let peerReference = PeerReference(peer) { self.fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(peer.id), userContentType: .story, reference: .media(media: .story(peer: peerReference, id: storyId, media: media._asMedia()), resource: representation.resource), ranges: nil).start() } - self.disposable = (context.account.postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: false)) + self.disposable = (context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> map { result -> UIImage? in - if result.complete { + if result.isComplete { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: result.path)?.preparingForDisplay() { return image @@ -165,9 +164,9 @@ final class StoryItemImageView: UIView { } let fullSize = context.account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedVideoFirstFrameRepresentation(), complete: true, fetch: true, attemptSynchronously: false) - var previewSize: Signal = .single(nil) + var previewSize: Signal = .single(nil) if let representation = file.previewRepresentations.first { - previewSize = context.account.postbox.mediaBox.resourceData(representation.resource, option: .complete(waitUntilFetchStatus: false)) + previewSize = context.engine.resources.data(resource: EngineMediaResource(representation.resource)) |> map(Optional.init) } @@ -190,7 +189,7 @@ final class StoryItemImageView: UIView { return nil } } - } else if let previewResult, previewResult.complete { + } else if let previewResult, previewResult.isComplete { if #available(iOS 15.0, *) { if let image = UIImage(contentsOfFile: previewResult.path)?.preparingForDisplay() { return image diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift index 72f8685b71..c3481c92a8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import ComponentFlow import TinyThumbnail @@ -307,7 +306,7 @@ final class StoryItemOverlaysView: UIView { flags: MediaArea.ReactionFlags, counter: Int, availableReactions: StoryAvailableReactions?, - entityFiles: [MediaId: TelegramMediaFile], + entityFiles: [EngineMedia.Id: TelegramMediaFile], synchronous: Bool, size: CGSize, isActive: Bool @@ -347,7 +346,7 @@ final class StoryItemOverlaysView: UIView { case let .custom(fileId): if let resolvedFile = self.resolvedFile, resolvedFile.fileId.id == fileId { file = resolvedFile - } else if let value = entityFiles[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] { + } else if let value = entityFiles[EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)] { file = value } else { if self.requestStickerDisposable == nil { @@ -425,7 +424,7 @@ final class StoryItemOverlaysView: UIView { self.directStickerView = directStickerView self.customEmojiLoadDisposable?.dispose() - self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start() + self.customEmojiLoadDisposable = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker).start() } var color: UIColor? if file.isCustomTemplateEmoji { @@ -474,7 +473,7 @@ final class StoryItemOverlaysView: UIView { customEmojiView.updateTextColor(flags.contains(.isDark) ? .white : .black) self.customEmojiLoadDisposable?.dispose() - self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start() + self.customEmojiLoadDisposable = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker).start() customEmojiView.isUserInteractionEnabled = false self.customEmojiView = customEmojiView @@ -594,7 +593,7 @@ final class StoryItemOverlaysView: UIView { self.file = file self.customEmojiLoadDisposable?.dispose() - self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start() + self.customEmojiLoadDisposable = context.engine.resources.fetch(reference: .standalone(resource: file.resource), userLocation: .other, userContentType: .sticker).start() let _ = self.directStickerView.update( transition: .immediate, @@ -701,7 +700,7 @@ final class StoryItemOverlaysView: UIView { peer: EnginePeer, story: EngineStoryItem, availableReactions: StoryAvailableReactions?, - entityFiles: [MediaId: TelegramMediaFile], + entityFiles: [EngineMedia.Id: TelegramMediaFile], size: CGSize, isCaptureProtected: Bool, attemptSynchronous: Bool, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index fa1efdfe9e..8e9b7278b6 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -14,7 +14,6 @@ import LegacyInstantVideoController import UndoUI import ContextUI import TelegramCore -import Postbox import AvatarNode import MediaEditorScreen import ImageCompression @@ -80,7 +79,7 @@ public final class StoryItemSetContainerComponent: Component { public enum NavigationDirection { case previous case next - case id(StoryId) + case id(EngineStoryId) } public struct PinchState: Equatable { @@ -122,7 +121,7 @@ public final class StoryItemSetContainerComponent: Component { public let close: () -> Void public let navigate: (NavigationDirection) -> Void public let delete: () -> Void - public let markAsSeen: (StoryId) -> Void + public let markAsSeen: (EngineStoryId) -> Void public let reorder: () -> Void public let createToFolder: (String, [EngineStoryItem]) -> Void public let addToFolder: (Int64) -> Void @@ -163,7 +162,7 @@ public final class StoryItemSetContainerComponent: Component { close: @escaping () -> Void, navigate: @escaping (NavigationDirection) -> Void, delete: @escaping () -> Void, - markAsSeen: @escaping (StoryId) -> Void, + markAsSeen: @escaping (EngineStoryId) -> Void, reorder: @escaping () -> Void, createToFolder: @escaping (String, [EngineStoryItem]) -> Void, addToFolder: @escaping (Int64) -> Void, @@ -363,11 +362,11 @@ public final class StoryItemSetContainerComponent: Component { } final class CaptionItem { - let itemId: StoryId + let itemId: EngineStoryId let externalState = StoryContentCaptionComponent.ExternalState() let view = ComponentView() - init(itemId: StoryId) { + init(itemId: EngineStoryId) { self.itemId = itemId } } @@ -471,7 +470,7 @@ public final class StoryItemSetContainerComponent: Component { var isSearchActive: Bool = false - var viewLists: [StoryId: ViewList] = [:] + var viewLists: [EngineStoryId: ViewList] = [:] let viewListsContainer: UIView var isEditingStory: Bool = false @@ -479,8 +478,8 @@ public final class StoryItemSetContainerComponent: Component { var itemLayout: ItemLayout? var ignoreScrolling: Bool = false - var visibleItems: [StoryId: VisibleItem] = [:] - var trulyValidIds: [StoryId] = [] + var visibleItems: [EngineStoryId: VisibleItem] = [:] + var trulyValidIds: [EngineStoryId] = [] var reactionContextNode: ReactionContextNode? weak var disappearingReactionContextNode: ReactionContextNode? @@ -506,8 +505,8 @@ public final class StoryItemSetContainerComponent: Component { let transitionCloneContainerView: UIView - private var awaitingSwitchToId: (from: StoryId, to: StoryId)? - private var animateNextNavigationId: StoryId? + private var awaitingSwitchToId: (from: EngineStoryId, to: EngineStoryId)? + private var animateNextNavigationId: EngineStoryId? private var initializedOffset: Bool = false private var viewListPanState: PanState? @@ -1498,8 +1497,8 @@ public final class StoryItemSetContainerComponent: Component { hintAllowSynchronousLoads = hint.allowSynchronousLoads } - var validIds: [StoryId] = [] - var trulyValidIds: [StoryId] = [] + var validIds: [EngineStoryId] = [] + var trulyValidIds: [EngineStoryId] = [] let centralItemX = itemLayout.contentFrame.center.x @@ -1980,7 +1979,7 @@ public final class StoryItemSetContainerComponent: Component { self.trulyValidIds = trulyValidIds - var removeIds: [StoryId] = [] + var removeIds: [EngineStoryId] = [] for (id, visibleItem) in self.visibleItems { if !validIds.contains(id) { removeIds.append(id) @@ -2001,7 +2000,7 @@ public final class StoryItemSetContainerComponent: Component { return } let progressMode = self.itemProgressMode() - var centralId: StoryId? + var centralId: EngineStoryId? if let component = self.component { centralId = component.slice.item.id } @@ -3066,7 +3065,7 @@ public final class StoryItemSetContainerComponent: Component { sendAsConfiguration = sendAsPeer.flatMap { value in return MessageInputPanelComponent.SendAsConfiguration( - currentPeer: EnginePeer(value.peer), + currentPeer: value.peer, subscriberCount: value.subscribers.flatMap(Int.init), isPremiumLocked: value.isPremiumRequired, isSelecting: self.sendMessageContext.isSelectingSendAsPeer, @@ -3442,7 +3441,7 @@ public final class StoryItemSetContainerComponent: Component { let minimizedHeight = max(100.0, availableSize.height - (325.0 + 12.0)) let defaultHeight = 60.0 + component.safeInsets.bottom + 1.0 - var validViewListIds: [StoryId] = [] + var validViewListIds: [EngineStoryId] = [] var displayViewLists = false if case .liveStream = component.slice.item.storyItem.media { @@ -3455,7 +3454,7 @@ public final class StoryItemSetContainerComponent: Component { var viewListHeightMidFraction: CGFloat = 0.0 if displayViewLists, let currentIndex = component.slice.allItems.firstIndex(where: { $0.id == component.slice.item.id }) { - var visibleViewListIds: [StoryId] = [component.slice.item.id] + var visibleViewListIds: [EngineStoryId] = [component.slice.item.id] if self.viewListDisplayState != .hidden, let viewListPanState = self.viewListPanState { if currentIndex != 0 { if viewListPanState.fraction > 0.0 { @@ -3469,7 +3468,7 @@ public final class StoryItemSetContainerComponent: Component { } } - var preloadViewListIds: [(StoryId, EngineStoryItem.Views)] = [] + var preloadViewListIds: [(EngineStoryId, EngineStoryItem.Views)] = [] if let views = component.slice.item.storyItem.views { preloadViewListIds.append((component.slice.item.id, views)) } @@ -3507,7 +3506,7 @@ public final class StoryItemSetContainerComponent: Component { } var fixedAnimationOffset: CGFloat = 0.0 - var applyFixedAnimationOffsetIds: [StoryId] = [] + var applyFixedAnimationOffsetIds: [EngineStoryId] = [] let normalCollapsedContentAreaHeight: CGFloat = availableSize.height - minimizedHeight @@ -3929,7 +3928,7 @@ public final class StoryItemSetContainerComponent: Component { } else { self.viewListMetrics = nil } - var removeViewListIds: [StoryId] = [] + var removeViewListIds: [EngineStoryId] = [] for (id, viewList) in self.viewLists { if !validViewListIds.contains(id) { removeViewListIds.append(id) @@ -4544,7 +4543,7 @@ public final class StoryItemSetContainerComponent: Component { } var forwardInfoStory: Signal? = nil if let forwardInfo = component.slice.item.storyItem.forwardInfo, case let .known(peer, id, _) = forwardInfo { - forwardInfoStory = component.slice.forwardInfoStories[StoryId(peerId: peer.id, id: id)]?.get() + forwardInfoStory = component.slice.forwardInfoStories[EngineStoryId(peerId: peer.id, id: id)]?.get() } let captionSize = captionItem.view.update( transition: captionItemTransition, @@ -4566,7 +4565,7 @@ public final class StoryItemSetContainerComponent: Component { } switch action { case let .url(url, concealed): - let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak self] c in + let _ = component.context.sharedContext.openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak self] c in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -4576,13 +4575,13 @@ public final class StoryItemSetContainerComponent: Component { return } self.sendMessageContext.openResolved(view: self, result: resolved, forceExternal: false, concealed: concealed) - }, alertDisplayUpdated: { [weak self] alertController in + }, progress: nil, alertDisplayUpdated: { [weak self] alertController in guard let self else { return } self.sendMessageContext.statusController = alertController self.updateIsProgressPaused() - }) + }, concealedAlertOption: nil) case let .textMention(value): self.sendMessageContext.openPeerMention(view: self, name: value) case let .peerMention(peerId, _): @@ -4600,7 +4599,7 @@ public final class StoryItemSetContainerComponent: Component { return } self.sendMessageContext.presentTextEntityActions(view: self, action: action, openUrl: { [weak self] url, concealed in - let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, concealed: concealed, skipUrlAuth: false, skipConcealedAlert: false, present: { [weak self] c in + let _ = component.context.sharedContext.openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { [weak self] c in guard let self, let component = self.component, let controller = component.controller() else { return } @@ -4610,7 +4609,7 @@ public final class StoryItemSetContainerComponent: Component { return } self.sendMessageContext.openResolved(view: self, result: resolved, forceExternal: false, concealed: concealed) - }) + }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil) }) }, textSelectionAction: { [weak self] text, action in @@ -4830,7 +4829,7 @@ public final class StoryItemSetContainerComponent: Component { } } case let .custom(fileId): - selectedItems.insert(AnyHashable(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId))) + selectedItems.insert(AnyHashable(EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId))) } } @@ -4969,8 +4968,8 @@ public final class StoryItemSetContainerComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))) var text = "" - var messageAttributes: [MessageAttribute] = [] - var inlineStickers: [MediaId : Media] = [:] + var messageAttributes: [EngineMessage.Attribute] = [] + var inlineStickers: [EngineMedia.Id : EngineRawMedia] = [:] switch updateReaction { case let .builtin(textValue): text = textValue @@ -5626,7 +5625,7 @@ public final class StoryItemSetContainerComponent: Component { } navigationController.setViewControllers(currentViewControllers, animated: true) } else { - guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + guard let chatController = component.context.sharedContext.makePeerInfoController(context: component.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } @@ -5654,7 +5653,7 @@ public final class StoryItemSetContainerComponent: Component { return } - let storyId = StoryId(peerId: peer.id, id: id) + let storyId = EngineStoryId(peerId: peer.id, id: id) guard let viewList = self.viewLists[component.slice.item.id], let viewListView = viewList.view.view as? StoryItemSetViewListComponent.View, let viewListContext = viewListView.currentViewList else { return @@ -5681,7 +5680,7 @@ public final class StoryItemSetContainerComponent: Component { transitionIn: transitionIn, transitionOut: { [weak sourceView, weak viewListView] peerId, storyIdValue in var destinationView: UIView? - if let view = viewListView?.sourceView(storyId: StoryId(peerId: peerId, id: storyIdValue as? Int32 ?? 0)) { + if let view = viewListView?.sourceView(storyId: EngineStoryId(peerId: peerId, id: storyIdValue as? Int32 ?? 0)) { destinationView = view } else { destinationView = sourceView @@ -5967,7 +5966,7 @@ public final class StoryItemSetContainerComponent: Component { } private func requestSave() { - guard let component = self.component, let peerReference = PeerReference(component.slice.effectivePeer._asPeer()) else { + guard let component = self.component, let peerReference = PeerReference(component.slice.effectivePeer) else { return } @@ -5977,7 +5976,7 @@ public final class StoryItemSetContainerComponent: Component { let stringSaving = component.strings.Story_TooltipSaving let stringSaved = component.strings.Story_TooltipSaved - let disposable = (saveToCameraRoll(context: component.context, postbox: component.context.account.postbox, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia())) + let disposable = (saveToCameraRoll(context: component.context, userLocation: .peer(peerReference.id), customUserContentType: .story, mediaReference: .story(peer: peerReference, id: component.slice.item.storyItem.id, media: component.slice.item.storyItem.media._asMedia())) |> deliverOnMainQueue).start(next: { [weak saveScreen] progress in guard let saveScreen else { return @@ -6243,7 +6242,7 @@ public final class StoryItemSetContainerComponent: Component { } } - if !emojiFileIds.isEmpty || hasLinkedStickers, let peerReference = PeerReference(component.slice.effectivePeer._asPeer()) { + if !emojiFileIds.isEmpty || hasLinkedStickers, let peerReference = PeerReference(component.slice.effectivePeer) { let context = component.context tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil) @@ -6478,7 +6477,7 @@ public final class StoryItemSetContainerComponent: Component { if let story = folderPreview.item { var imageSignal: Signal? - var selectedMedia: Media? + var selectedMedia: EngineRawMedia? if let image = story.media._asMedia() as? TelegramMediaImage { selectedMedia = image } else if let file = story.media._asMedia() as? TelegramMediaFile { @@ -6697,7 +6696,7 @@ public final class StoryItemSetContainerComponent: Component { } } - if component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + if component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer.usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -6953,7 +6952,7 @@ public final class StoryItemSetContainerComponent: Component { }))) } - if component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer._asPeer().usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { + if component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer.usernames.isEmpty) && (component.slice.item.storyItem.expirationTimestamp > Int32(Date().timeIntervalSince1970) || component.slice.item.storyItem.isPinned) { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -7221,7 +7220,7 @@ public final class StoryItemSetContainerComponent: Component { items.append(.separator) } - let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.effectivePeer._asPeer(), peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.effectivePeer, peerSettings: settings._asNotificationSettings(), topSearchPeers: topSearchPeers) if !component.slice.effectivePeer.isService && isContact { items.append(.action(ContextMenuActionItem(text: isMuted ? component.strings.StoryFeed_ContextNotifyOn : component.strings.StoryFeed_ContextNotifyOff, icon: { theme in @@ -7271,7 +7270,7 @@ public final class StoryItemSetContainerComponent: Component { }))) } - if !component.slice.effectivePeer.isService && component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer._asPeer().usernames.isEmpty) { + if !component.slice.effectivePeer.isService && component.slice.item.storyItem.isPublic && (component.slice.effectivePeer.addressName != nil || !component.slice.effectivePeer.usernames.isEmpty) { items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, a in @@ -7548,7 +7547,7 @@ public final class StoryItemSetContainerComponent: Component { } private func performMusicAction(file: TelegramMediaFile, sourceView: UIView, gesture: ContextGesture?) { - guard let component = self.component, let controller = component.controller(), let peer = PeerReference(component.slice.peer._asPeer()) else { + guard let component = self.component, let controller = component.controller(), let peer = PeerReference(component.slice.peer) else { return } @@ -7616,7 +7615,7 @@ public final class StoryItemSetContainerComponent: Component { if let controller = component.context.sharedContext.makePeerInfoController( context: component.context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .myProfile, avatarInitiallyExpanded: false, fromChat: false, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index ed20ffc4e6..f9b11322c8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -17,7 +17,6 @@ import TextFormat import PhoneNumberFormat import TelegramIntents import LegacyUI -import WebSearchUI import ChatTimerScreen import PremiumUI import ICloudResources @@ -28,7 +27,6 @@ import TelegramPresentationData import LegacyInstantVideoController import TelegramPresentationData import ChatPresentationInterfaceState -import Postbox import OverlayStatusController import PresentationDataUtils import TextFieldComponent @@ -41,6 +39,8 @@ import ChatScheduleTimeController import StoryStealthModeSheetScreen import Speak import TranslateUI +import TextProcessingScreen +import Pasteboard import TelegramNotices import ObjectiveC import LocationUI @@ -62,11 +62,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case text case media } - + private var context: AccountContext? private weak var view: StoryItemSetContainerComponent.View? private var inputPanelExternalState: MessageInputPanelComponent.ExternalState? - + weak var attachmentController: AttachmentController? weak var shareController: ViewController? weak var tooltipScreen: ViewController? @@ -76,59 +76,59 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { weak var menuController: ViewController? var progressPauseContext = StoryProgressPauseContext() var isViewingAttachedStickers = false - + var currentTooltipUpdateTimer: Foundation.Timer? - + var currentInputMode: InputMode = .text private var needsInputActivation = false - + var audioRecorderValue: ManagedAudioRecorder? var audioRecorder = Promise() var recordedAudioPreview: ChatRecordedMediaPreview? - + var videoRecorderValue: InstantVideoController? var videoRecorder = Promise() var hasRecordedVideoPreview = false - + var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData? var inputMediaNodeDataDisposable: Disposable? var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() var inputMediaInteraction: ChatEntityKeyboardInputNode.Interaction? var inputMediaNode: ChatEntityKeyboardInputNode? - + let controllerNavigationDisposable = MetaDisposable() let enqueueMediaMessageDisposable = MetaDisposable() let navigationActionDisposable = MetaDisposable() let resolvePeerByNameDisposable = MetaDisposable() - + var currentSpeechHolder: SpeechSynthesizerHolder? - + var currentLiveStreamMessageStars: StarsAmount? weak var currentSendStarsUndoController: UndoOverlayController? var currentLiveStreamStarsIsActive: Bool = false var currentLiveStreamStarsIsActiveTimer: Foundation.Timer? - + struct SendAsData: Equatable { var isPremium: Bool var availablePeers: [SendAsPeer] - + init(isPremium: Bool, availablePeers: [SendAsPeer]) { self.isPremium = isPremium self.availablePeers = availablePeers } } - + var sendAsData: SendAsData? var currentSendAsPeer: SendAsPeer? var isSelectingSendAsPeer: Bool = false var sendAsDisposable: Disposable? - + private(set) var isMediaRecordingLocked: Bool = false var wasRecordingDismissed: Bool = false - + init() { } - + deinit { self.controllerNavigationDisposable.dispose() self.enqueueMediaMessageDisposable.dispose() @@ -138,12 +138,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.currentTooltipUpdateTimer?.invalidate() self.sendAsDisposable?.dispose() } - + func setup(component: StoryItemSetContainerComponent, view: StoryItemSetContainerComponent.View, inputPanelExternalState: MessageInputPanelComponent.ExternalState, keyboardInputData: Signal) { self.context = component.context self.inputPanelExternalState = inputPanelExternalState self.view = view - + if self.inputMediaNodeDataDisposable == nil { self.inputMediaNodeDataDisposable = (keyboardInputData |> deliverOnMainQueue).start(next: { [weak self] value in @@ -153,7 +153,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.inputMediaNodeData = value }) } - + self.inputMediaInteraction = ChatEntityKeyboardInputNode.Interaction( sendSticker: { [weak self] fileReference, _, _, _, _, _, _, _, _ in if let self, let view = self.view { @@ -192,7 +192,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }, dismissTextInput: { - + }, insertText: { [weak self] text in if let self { @@ -229,7 +229,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } ) self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme - + if let peerId = component.slice.item.peerId { self.sendAsDisposable = combineLatest( queue: Queue.mainQueue(), @@ -241,12 +241,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let accountPeer else { return } - + let isPremium = accountPeer.isPremium - + var availablePeers: [SendAsPeer] = [] availablePeers.append(SendAsPeer( - peer: accountPeer._asPeer(), + peer: accountPeer, subscribers: nil, isPremiumRequired: false )) @@ -256,7 +256,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } availablePeers.append(peer) } - + let sendAsData = SendAsData( isPremium: isPremium, availablePeers: availablePeers @@ -270,7 +270,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) } } - + func toggleInputMode() { guard let view = self.view else { return @@ -284,12 +284,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.currentInputMode = .text } } - + func updateInputMediaNode(view: StoryItemSetContainerComponent.View, availableSize: CGSize, bottomInset: CGFloat, bottomContainerInset: CGFloat, inputHeight: CGFloat, effectiveInputHeight: CGFloat, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ComponentTransition) -> CGFloat { guard let context = self.context, let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return 0.0 } - + var height: CGFloat = 0.0 if let component = self.view?.component, case .media = self.currentInputMode, let inputData = self.inputMediaNodeData { var updatedInputData = inputData @@ -297,7 +297,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if case .liveStream = component.slice.item.storyItem.media { isLiveStream = true } - + if isLiveStream { updatedInputData = ChatEntityKeyboardInputNode.InputData( emoji: updatedInputData.emoji, @@ -306,7 +306,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { availableGifSearchEmojies: [] ) } - + var animateIn = false let inputMediaNode: ChatEntityKeyboardInputNode if let current = self.inputMediaNode { @@ -341,7 +341,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } self.inputMediaNode = inputMediaNode } - + let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) let presentationInterfaceState = ChatPresentationInterfaceState( chatWallpaper: .builtin(WallpaperSettings()), @@ -357,7 +357,6 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, - peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, @@ -368,22 +367,22 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { accountPeerColor: nil, businessIntro: nil ) - + let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight < 100.0 ? inputHeight - bottomContainerInset : inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false) let inputNodeHeight = heightAndOverflow.0 let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight)) - + if animateIn { let inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight) ComponentTransition.immediate.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame) } - + transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame) - + height = heightAndOverflow.0 } else if let inputMediaNode = self.inputMediaNode { self.inputMediaNode = nil - + var targetFrame = inputMediaNode.frame targetFrame.origin.y = availableSize.height transition.setFrame(view: inputMediaNode.view, frame: targetFrame, completion: { [weak inputMediaNode] _ in @@ -396,17 +395,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }) } - + if self.needsInputActivation { self.needsInputActivation = false Queue.mainQueue().justDispatch { inputPanelView.activateInput() } } - + return height } - + func animateOut(bounds: CGRect) { if let inputMediaNode = self.inputMediaNode { inputMediaNode.layer.animatePosition( @@ -420,20 +419,20 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { inputMediaNode.layer.animateAlpha(from: inputMediaNode.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) } } - + private func presentMessageSentTooltip(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messageId: EngineMessage.Id?, isScheduled: Bool = false) { guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else { return } - + if let tooltipScreen = self.tooltipScreen { tooltipScreen.dismiss(animated: true) } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - + let text = isScheduled ? presentationData.strings.Story_TooltipMessageScheduled : presentationData.strings.Story_TooltipMessageSent - + let tooltipScreen = UndoOverlayController( presentationData: presentationData, content: .actionSucceeded(title: "", text: text, cancel: messageId != nil ? presentationData.strings.Story_ToastViewInChat : "", destructive: false), @@ -451,31 +450,31 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.present(tooltipScreen, in: .current) self.tooltipScreen = tooltipScreen view.updateIsProgressPaused() - + HapticFeedback().success() } - + func presentSendMessageOptions(view: StoryItemSetContainerComponent.View, sourceView: UIView, gesture: ContextGesture?) { guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else { return } - + view.dismissAllTooltips() - + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) var items: [ContextMenuItem] = [] - + if case .liveStream = component.slice.item.storyItem.media { items.append(.action(ContextMenuActionItem(text: self.currentLiveStreamMessageStars != nil ? presentationData.strings.LiveStream_InputContextMenuEditStars : presentationData.strings.LiveStream_InputContextMenuAddStars, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSuggestPost"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak view] _, a in a(.default) - + guard let self, let view else { return } self.performPaidMessageAction(view: view) }))) - + var canRemoveStars = self.currentLiveStreamMessageStars != nil if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View { if let liveChatStateValue = visibleItemView.liveChatState { @@ -484,12 +483,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + if canRemoveStars { items.append(.action(ContextMenuActionItem(text: presentationData.strings.LiveStream_InputContextMenuRemoveStars, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/RemovePrice"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak view] _, a in a(.default) - + guard let self, let view else { return } @@ -502,34 +501,34 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if let presence = component.slice.additionalPeerData.presence, case .present = presence.status { sendWhenOnlineAvailable = true } - + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak view] _, a in a(.default) - + guard let self, let view else { return } self.performSendMessageAction(view: view, silentPosting: true) }))) - + if sendWhenOnlineAvailable { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak view] _, a in a(.default) - + guard let self, let view else { return } self.performSendMessageAction(view: view, scheduleTime: scheduleWhenOnlineTimestamp) }))) } - + if component.slice.additionalPeerData.sendPaidMessageStars == nil { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor) }, action: { [weak self, weak view] _, a in a(.default) - + guard let self, let view else { return } @@ -537,9 +536,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }))) } } - + let contextItems = ContextController.Items(content: .list(items)) - + let contextController = makeContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .top)), items: .single(contextItems), gesture: gesture) contextController.dismissed = { [weak view] in guard let view else { @@ -552,7 +551,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { view.updateIsProgressPaused() controller.present(contextController, in: .window(.root)) } - + func presentScheduleTimePicker( view: StoryItemSetContainerComponent.View ) { @@ -565,12 +564,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } let _ = peerId let controller = component.controller() as? StoryContainerScreen - + var sendWhenOnlineAvailable = false if let presence = component.slice.additionalPeerData.presence, case .present = presence.status { sendWhenOnlineAvailable = true } - + let timeController = ChatScheduleTimeController(context: component.context, updatedPresentationData: nil, mode: .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable), style: .media, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self, weak view] time in guard let self, let view else { return @@ -586,18 +585,18 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } view.endEditing(true) controller?.present(timeController, in: .window(.root)) - + self.actionSheet = timeController view.updateIsProgressPaused() } - + func presentPaidMessageAlertIfNeeded(view: StoryItemSetContainerComponent.View, completion: @escaping () -> Void) { guard let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars else { completion() return } let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) - + let controller = chatMessagePaymentAlertController( context: component.context, presentationData: presentationData, @@ -614,13 +613,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { ) component.controller()?.present(controller, in: .window(.root)) } - + func performWithPossibleStealthModeConfirmation(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { guard let component = view.component, component.stealthModeTimeout != nil else { action() return } - + let _ = (combineLatest( component.context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState() @@ -629,16 +628,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { ) |> deliverOnMainQueue).start(next: { [weak self, weak view] data, noticeCount in let config = data - + guard let self, let view, let component = view.component else { return } - + let timestamp = Int32(Date().timeIntervalSince1970) if noticeCount < 1, let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp { let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - + let alertController = AlertScreen( title: component.strings.Story_AlertStealthModeActiveTitle, text: component.strings.Story_AlertStealthModeActiveText, @@ -659,9 +658,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } self.actionSheet = alertController view.updateIsProgressPaused() - + component.controller()?.presentInGlobalOverlay(alertController) - + #if DEBUG #else let _ = ApplicationSpecificNotice.incrementStoryStealthModeReplyCount(accountManager: component.context.sharedContext.accountManager).start() @@ -671,7 +670,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }) } - + func performSendMessageAction( view: StoryItemSetContainerComponent.View, silentPosting: Bool = false, @@ -682,7 +681,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } if case .liveStream = component.slice.item.storyItem.media { if let visibleItem = view.visibleItems[component.slice.item.id], let itemView = visibleItem.view.view as? StoryItemContentComponent.View { - + var sendPaidMessageStars = self.currentLiveStreamMessageStars var isAdmin = false var sendAsPeer: SendAsPeer? @@ -697,7 +696,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { sendPaidMessageStars = StarsAmount(value: minMessagePrice, nanos: 0) } } - + if let currentSendAsPeer = self.currentSendAsPeer { sendAsPeer = currentSendAsPeer } else { @@ -705,7 +704,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return self.sendAsData?.availablePeers.first(where: { $0.peer.id == defaultSendAs }) } } - + if liveChatStateValue.isAdmin { if let sendAsPeer { isAdmin = sendAsPeer.peer.id == component.context.account.peerId @@ -715,7 +714,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + if let call = itemView.mediaStreamCall { let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { @@ -724,7 +723,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return } - + switch inputPanelView.getSendMessageInput() { case let .text(text): if !text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { @@ -734,7 +733,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let params = GroupCallMessagesContext.getStarAmountParamMapping(params: paramSets, value: isAdmin ? 1000000000 : sendPaidMessageStars?.value ?? 0) maxInputLength = params.maxLength maxEmojiCount = params.emojiCount - + var sendDisabledMinStars: Int64? if let maxInputLength, text.string.count > maxInputLength { sendDisabledMinStars = paramSets.paramSets.sorted(by: { $0.minStars < $1.minStars }).first(where: { $0.maxMessageLength >= text.string.count })?.minStars ?? 1000000000 @@ -763,36 +762,36 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if emojiCount > absMaxEmojiCount { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) view.component?.controller()?.present(textAlertController(context: component.context, updatedPresentationData: (presentationData, .single(presentationData)), title: nil, text: presentationData.strings.LiveStream_ErrorMaxAllowedEmoji_Text(Int32(absMaxEmojiCount)), actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - + return } - + sendDisabledMinStars = paramSets.paramSets.sorted(by: { $0.minStars < $1.minStars }).first(where: { $0.maxEmojiCount >= emojiCount })?.minStars ?? 1000000000 } } - + if let sendDisabledMinStars { self.performPaidMessageAction(view: view, minStars: Int(sendDisabledMinStars)) return } - + if let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View { if !(visibleItemView.liveChatState?.isExpanded ?? true) { visibleItemView.toggleLiveChatExpanded() } visibleItemView.scheduleScrollLiveChatToBottom() } - + let entities = generateChatInputTextEntities(text) - + call.sendMessage(fromId: sendAsPeer?.peer.id, isAdmin: isAdmin, text: text.string, entities: entities, paidStars: sendPaidMessageStars?.value) - - component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id)) + + component.storyItemSharedState.replyDrafts.removeValue(forKey: EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id)) inputPanelView.clearSendMessageInput(updateState: true) - + self.currentLiveStreamMessageStars = nil view.state?.updated(transition: .spring(duration: 0.4)) - + let controller = component.controller() as? StoryContainerScreen controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) } @@ -801,7 +800,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } return } - + self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in guard let self, let view else { return @@ -809,37 +808,37 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let component = view.component else { return } - + let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { return } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return } let peer = component.slice.effectivePeer - + let controller = component.controller() as? StoryContainerScreen - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in guard let self, let view else { return } if let recordedAudioPreview = self.recordedAudioPreview, case let .audio(audio) = recordedAudioPreview { self.recordedAudioPreview = nil - + let waveformBuffer = audio.waveform.makeBitstream() - - var messageAttributes: [MessageAttribute] = [] + + var messageAttributes: [EngineMessage.Attribute] = [] if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) } - + let messages: [EnqueueMessage] = [.message(text: "", attributes: messageAttributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: audio.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: Int(audio.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] - + let _ = enqueueMessages(account: component.context.account, peerId: peerId, messages: messages).start() - + view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) } else if self.hasRecordedVideoPreview, let videoRecorderValue = self.videoRecorderValue { videoRecorderValue.send() @@ -866,9 +865,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } }) - component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id)) + component.storyItemSharedState.replyDrafts.removeValue(forKey: EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id)) inputPanelView.clearSendMessageInput(updateState: true) - + self.currentInputMode = .text if hasFirstResponder(view) { view.endEditing(true) @@ -882,7 +881,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - + func performSendStickerAction(view: StoryItemSetContainerComponent.View, fileReference: FileMediaReference) { self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in guard let self, let view else { @@ -895,11 +894,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) let peer = component.slice.effectivePeer - + let controller = component.controller() as? StoryContainerScreen - + if let navigationController = controller?.navigationController as? NavigationController { var controllers = navigationController.viewControllers for controller in controllers.reversed() { @@ -910,14 +909,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } navigationController.setViewControllers(controllers, animated: true) - + controller?.window?.forEachController({ controller in if let controller = controller as? StickerPackScreenImpl { controller.dismiss() } }) } - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in guard let self, let view else { return @@ -935,7 +934,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } }) - + self.currentInputMode = .text if hasFirstResponder(view) { view.endEditing(true) @@ -946,8 +945,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - - func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) { + + func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult, silentPosting: Bool = false, scheduleTime: Int32? = nil) { guard let component = view.component else { return } @@ -955,11 +954,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) let peer = component.slice.effectivePeer - + let controller = component.controller() as? StoryContainerScreen - + if let navigationController = controller?.navigationController as? NavigationController { var controllers = navigationController.viewControllers for controller in controllers.reversed() { @@ -970,14 +969,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } navigationController.setViewControllers(controllers, animated: true) - + controller?.window?.forEachController({ controller in if let controller = controller as? StickerPackScreenImpl { controller.dismiss() } }) } - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self, weak view] in guard let self, let view else { return @@ -987,6 +986,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { replyTo: nil, storyId: focusedStoryId, content: .contextResult(results, result), + silentPosting: silentPosting, + scheduleTime: scheduleTime, sendPaidMessageStars: component.slice.additionalPeerData.sendPaidMessageStars ) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in Queue.mainQueue().after(0.3) { @@ -995,7 +996,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } }) - + self.currentInputMode = .text if hasFirstResponder(view) { view.endEditing(true) @@ -1005,7 +1006,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring)) }) } - + func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) { guard let component = view.component else { return @@ -1022,15 +1023,15 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - + func enqueueStickerImage(view: StoryItemSetContainerComponent.View, image: UIImage, isMemoji: Bool) { guard let component = view.component else { return } let peer = component.slice.effectivePeer - + let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0)) - + func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? { let format = UIGraphicsImageRendererFormat() format.scale = 1.0 @@ -1045,13 +1046,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if let targetSize = targetSize, let scaledImage = scaleImage(image, size: targetSize, boundiingSize: targetSize) { image = scaledImage } - + return Signal { subscriber in if let data = try? WebP.convert(toWebP: image, quality: quality * 100.0) { subscriber.putNext(data) } subscriber.putCompletion() - + return EmptyDisposable } |> runOn(Queue.concurrentDefaultQueue()) } @@ -1059,16 +1060,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let _ = (convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).start(next: { [weak self, weak view] data in if let self, let view, !data.isEmpty { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - component.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - + component.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) + var fileAttributes: [TelegramMediaFileAttribute] = [] fileAttributes.append(.FileName(fileName: "sticker.webp")) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) fileAttributes.append(.ImageSize(size: PixelDimensions(size))) - - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) + + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in guard let self else { return @@ -1078,7 +1079,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }) } - + func setMediaRecordingActive( view: StoryItemSetContainerComponent.View, isActive: Bool, @@ -1086,7 +1087,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { sendAction: Bool ) { self.isMediaRecordingLocked = false - + guard let component = view.component else { return } @@ -1094,7 +1095,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) @@ -1102,7 +1103,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let view, let component = view.component, let peer else { return } - + if isActive { if isVideo { if self.videoRecorderValue == nil { @@ -1140,8 +1141,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, completion: { time, repeatPeriod in - done(time) + self.presentScheduleTimePicker(view: view, peer: peer, completion: { time, repeatPeriod, silentPosting in + done(time, silentPosting) }) }))) } @@ -1159,24 +1160,24 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let component = view.component else { return } - + self.wasRecordingDismissed = !sendAction self.audioRecorder.set(.single(nil)) - + guard let data else { return } - + if data.duration < 0.5 || !sendAction { HapticFeedback().error() } else { let randomId = Int64.random(in: Int64.min ... Int64.max) - + let resource = LocalFileMediaResource(fileId: randomId) - component.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) - + component.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data.compressedData) + let waveformBuffer: Data? = data.waveform - + self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak self, weak view] in guard let self, let view else { return @@ -1186,7 +1187,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } self.sendMessages(view: view, peer: peer, messages: [.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) - + HapticFeedback().tap() }) }) @@ -1194,24 +1195,24 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) } else if let videoRecorderValue = self.videoRecorderValue { self.wasRecordingDismissed = !sendAction - + if sendAction { videoRecorderValue.completeVideo() } else { self.videoRecorder.set(.single(nil)) } self.hasRecordedVideoPreview = false - + view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) } } }) } - + func lockMediaRecording() { self.isMediaRecordingLocked = true } - + func stopMediaRecording(view: StoryItemSetContainerComponent.View) { if let audioRecorderValue = self.audioRecorderValue { let _ = (audioRecorderValue.takenRecordedData() |> deliverOnMainQueue).start(next: { [weak self, weak view] data in @@ -1219,7 +1220,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } self.audioRecorder.set(.single(nil)) - + guard let data else { return } @@ -1227,8 +1228,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { HapticFeedback().error() } else if let waveform = data.waveform { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.compressedData.count)) - - component.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) + + component.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data.compressedData) self.recordedAudioPreview = .audio(ChatRecordedMediaPreview.Audio(resource: resource, fileSize: Int32(data.compressedData.count), duration: Int32(data.duration), waveform: AudioWaveform(bitstream: waveform, bitsPerSample: 5))) view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) } @@ -1242,7 +1243,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + func discardMediaRecordingPreview(view: StoryItemSetContainerComponent.View) { if self.recordedAudioPreview != nil { self.recordedAudioPreview = nil @@ -1255,7 +1256,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { view.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring))) } } - + func performShareAction(view: StoryItemSetContainerComponent.View) { guard let component = view.component else { return @@ -1267,16 +1268,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - + if focusedItem.storyItem.isForwardingDisabled { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) let actionSheet = ActionSheetController(presentationData: presentationData) - + actionSheet.setItemGroups([ ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Story_Context_CopyLink, color: .accent, action: { [weak self, weak view, weak actionSheet] in actionSheet?.dismissAnimated() - + guard let self, let view else { return } @@ -1289,7 +1290,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) ]) ]) - + actionSheet.dismissed = { [weak self, weak view] _ in guard let self, let view else { return @@ -1299,7 +1300,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } self.actionSheet = actionSheet view.updateIsProgressPaused() - + component.presentController(actionSheet, nil) } else { var preferredAction: ShareControllerPreferredAction? @@ -1309,7 +1310,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { |> deliverOnMainQueue).start(next: { link in if let link { UIPasteboard.general.string = link - + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) component.presentController(UndoOverlayController( presentationData: presentationData, @@ -1322,9 +1323,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) })) } - + let shareController = component.context.sharedContext.makeShareController(context: component.context, params: ShareControllerParams( - subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: StoryId(peerId: peerId, id: focusedItem.storyItem.id), isMention: false)), nil), + subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id), isMention: false)), nil), preferredAction: preferredAction ?? .default, externalShare: false, immediateExternalShare: false, @@ -1419,7 +1420,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.present(shareController, in: .window(.root)) } } - + func performPaidMessageAction(view: StoryItemSetContainerComponent.View, minStars: Int? = nil) { Task { @MainActor [weak view] in guard let view else { @@ -1438,22 +1439,22 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - + var inputText = NSAttributedString(string: "") switch inputPanelView.getSendMessageInput() { case let .text(text): inputText = text } - + guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else { return } - + var minAmount: Int64 = 1 if let minMessagePrice = visibleItemView.liveChatState?.minMessagePrice { minAmount = minMessagePrice } - + var currentAmount: Int? = (self.currentLiveStreamMessageStars?.value).flatMap { Int($0) } if let minStars { if let currentAmountValue = currentAmount { @@ -1462,7 +1463,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { currentAmount = minStars } } - + let initialData = await ChatSendStarsScreen.initialDataLiveStreamMessage( context: component.context, peerId: peerId, @@ -1473,7 +1474,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view else { return } - + self.currentLiveStreamMessageStars = StarsAmount(value: amount, nanos: 0) view.state?.updated(transition: .spring(duration: 0.4)) } @@ -1487,7 +1488,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + func performShareTextAction(view: StoryItemSetContainerComponent.View, text: String) { guard let component = view.component else { return @@ -1495,10 +1496,10 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let controller = component.controller() else { return } - + let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - + let shareController = component.context.sharedContext.makeShareController(context: component.context, params: ShareControllerParams(subject: .text(text), externalShare: true, immediateExternalShare: false, updatedPresentationData: updatedPresentationData, dismissed: { [weak self, weak view] _ in guard let self, let view else { return @@ -1512,12 +1513,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.present(shareController, in: .window(.root)) } - + func performTranslateTextAction(view: StoryItemSetContainerComponent.View, text: String, entities: [MessageTextEntity]) { guard let component = view.component else { return } - + let _ = (component.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) |> take(1) |> deliverOnMainQueue).start(next: { [weak self, weak view] sharedData in @@ -1525,54 +1526,61 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } let peer = component.slice.effectivePeer - + let _ = self - + let translationSettings: TranslationSettings if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { translationSettings = current } else { translationSettings = TranslationSettings.defaultSettings } - + var showTranslateIfTopical = false if case let .channel(channel) = peer, !(channel.addressName ?? "").isEmpty { showTranslateIfTopical = true } - + let (_, language) = canTranslateText(context: component.context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages) - + let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start() - - let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, entities: entities, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages) - translateController.pushController = { [weak view] c in - guard let view, let component = view.component else { + + Task { @MainActor [weak self, weak view] in + guard let self, let view, let component = view.component else { return } - component.controller()?.push(c) - } - translateController.presentController = { [weak view] c in - guard let view, let component = view.component else { - return - } - component.controller()?.present(c, in: .window(.root)) - } - - self.actionSheet = translateController - view.updateIsProgressPaused() - - translateController.wasDismissed = { [weak self, weak view] in - guard let self, let view else { - return - } - self.actionSheet = nil + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let translateController = await TextProcessingScreen( + context: component.context, + theme: defaultDarkPresentationTheme, + mode: .translate(fromLanguage: language, applyResult: nil), + inputText: TextWithEntities(text: text, entities: entities), + copyResult: { [weak view] text in + guard let component = view?.component else { + return + } + storeMessageTextInPasteboard(text.text, entities: text.entities) + component.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }, + translateChat: nil + ) + + self.actionSheet = translateController view.updateIsProgressPaused() + + translateController.wasDismissed = { [weak self, weak view] in + guard let self, let view else { + return + } + self.actionSheet = nil + view.updateIsProgressPaused() + } + + component.controller()?.present(translateController, in: .window(.root)) } - - component.controller()?.present(translateController, in: .window(.root)) }) } - + func performLookupTextAction(view: StoryItemSetContainerComponent.View, text: String) { guard let component = view.component else { return @@ -1582,38 +1590,38 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.popoverPresentationController?.sourceView = window controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) window.rootViewController?.present(controller, animated: true) - + final class DeinitWatcher: NSObject { let f: () -> Void - + init(_ f: @escaping () -> Void) { self.f = f } - + deinit { f() } } - + self.lookupController = controller view.updateIsProgressPaused() - + objc_setAssociatedObject(controller, &ObjCKey_DeinitWatcher, DeinitWatcher { [weak self, weak view] in guard let self, let view else { return } - + self.lookupController = nil view.updateIsProgressPaused() }, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } - + func performCopyLinkAction(view: StoryItemSetContainerComponent.View) { guard let component = view.component else { return } - + let _ = (component.context.engine.messages.exportStoryLink(peerId: component.slice.effectivePeer.id, id: component.slice.item.storyItem.id) |> deliverOnMainQueue).start(next: { [weak view] link in guard let view, let component = view.component else { @@ -1621,7 +1629,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } if let link { UIPasteboard.general.string = link - + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) component.presentController(UndoOverlayController( presentationData: presentationData, @@ -1633,13 +1641,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }) } - + private func clearInputText(view: StoryItemSetContainerComponent.View) { guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return } inputPanelView.clearSendMessageInput(updateState: true) - + guard let component = view.component else { return } @@ -1647,13 +1655,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - component.storyItemSharedState.replyDrafts.removeValue(forKey: StoryId(peerId: peerId, id: focusedItem.storyItem.id)) + component.storyItemSharedState.replyDrafts.removeValue(forKey: EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id)) } - + enum AttachMenuSubject { case `default` } - + func presentAttachmentMenu( view: StoryItemSetContainerComponent.View, subject: AttachMenuSubject @@ -1665,17 +1673,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) + let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return } - + var inputText = NSAttributedString(string: "") switch inputPanelView.getSendMessageInput() { case let .text(text): inputText = text } - + let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) @@ -1686,19 +1694,19 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peer else { return } - + let inputIsActive = !"".isEmpty - + self.currentInputMode = .text view.endEditing(true) - + var banSendText: (Int32, Bool)? var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? var bannedSendFiles: (Int32, Bool)? - + let _ = bannedSendFiles - + var canSendPolls = true if case let .user(peer) = peer, peer.botInfo == nil { canSendPolls = false @@ -1737,7 +1745,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { canSendPolls = false } } - + var availableButtons: [AttachmentButtonType] = [.gallery, .file] if banSendText == nil { availableButtons.append(.location) @@ -1746,9 +1754,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if canSendPolls { availableButtons.insert(.poll, at: max(0, availableButtons.count - 1)) } - + let isScheduledMessages = !"".isEmpty - + var peerType: AttachMenuBots.Bot.PeerFlags = [] if case let .user(user) = peer { if let _ = user.botInfo { @@ -1765,7 +1773,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { peerType = .group } } - + let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError> if !isScheduledMessages { buttons = component.context.engine.messages.attachMenuBots() @@ -1781,7 +1789,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case .gift: initialButton = .gift*/ } - + if !"".isEmpty { for bot in attachMenuBots.reversed() { var peerType = peerType @@ -1792,7 +1800,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let button: AttachmentButtonType = .app(bot) if !bot.peerTypes.intersection(peerType).isEmpty { buttons.insert(button, at: 1) - + /*if case let .bot(botId, _, _) = subject { if initialButton == nil && bot.peer.id == botId { initialButton = button @@ -1802,18 +1810,18 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { allButtons.insert(button, at: 1) } } - + return (buttons, allButtons, initialButton) } } else { buttons = .single((availableButtons, availableButtons, .gallery)) } - + let dataSettings = component.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) return entry ?? GeneratedMediaStoreSettings.defaultSettings } - + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) let premiumGiftOptions: [CachedPremiumGiftOption] if !premiumConfiguration.isPremiumDisabled && premiumConfiguration.showPremiumGiftInAttachMenu, case let .user(user) = peer, !user.isPremium && !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { @@ -1822,45 +1830,36 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } else { premiumGiftOptions = [] } - + let _ = combineLatest(queue: Queue.mainQueue(), buttons, dataSettings).start(next: { [weak self, weak view] buttonsAndInitialButton, dataSettings in guard let self, let view, let component = view.component else { return } - + var (buttons, allButtons, initialButton) = buttonsAndInitialButton if !premiumGiftOptions.isEmpty { buttons.insert(.gift, at: 1) } let _ = allButtons - + guard let initialButton = initialButton else { return } - + let currentMediaController = Atomic(value: nil) let currentFilesController = Atomic(value: nil) let currentLocationController = Atomic(value: nil) - + let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - + let attachmentController = AttachmentController( context: component.context, updatedPresentationData: updatedPresentationData, + style: .glass, chatLocation: .peer(id: peer.id), buttons: buttons, - initialButton: initialButton, - makeEntityInputView: { [weak view] in - guard let view, let component = view.component else { - return nil - } - return EntityInputView( - context: component.context, - isDark: true, - areCustomEmojiEnabled: true //TODO:check custom emoji - ) - } + initialButton: initialButton ) attachmentController.didDismiss = { [weak self, weak view] in guard let self, let view else { @@ -1931,7 +1930,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - + let controller = component.context.sharedContext.makeAttachmentFileController(context: component.context, updatedPresentationData: updatedPresentationData, audio: false, bannedSendMedia: bannedSendFiles, presentGallery: { [weak self, weak view, weak attachmentController] in guard let self, let view else { return @@ -1948,7 +1947,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let view, let component = view.component else { return } - var messageAttributes: [MessageAttribute] = [] + var messageAttributes: [EngineMessage.Attribute] = [] if let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) } @@ -1991,7 +1990,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != component.context.account.peerId let theme = component.theme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - let controller = LocationPickerController(context: component.context, updatedPresentationData: updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self, weak view] location, _, _, _, _ in + let controller = LocationPickerController(context: component.context, style: .glass, updatedPresentationData: updatedPresentationData, mode: .share(peer: peer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self, weak view] location, _, _, _, _ in guard let self, let view else { return } @@ -2004,7 +2003,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) completion(controller, controller.mediaPickerContext) - + let _ = currentLocationController.swap(controller) }) return true @@ -2027,9 +2026,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let (peers, _, silent, scheduleTime, text, _) = peers else { return } - + let targetPeer = peer - + var textEnqueueMessage: EnqueueMessage? if let text = text, text.length > 0 { var attributes: [EngineMessage.Attribute] = [] @@ -2048,11 +2047,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) case let .deviceContact(_, basicData): @@ -2060,17 +2059,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { continue } let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) } - + if let media = media { let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) enqueueMessages.append(message) } } - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in guard let self else { return @@ -2081,7 +2080,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") @@ -2099,7 +2098,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + if let stableId = stableId { return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) |> take(1) @@ -2130,7 +2129,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { enqueueMessages.append(textEnqueueMessage) } enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in guard let self else { return @@ -2138,7 +2137,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime) }) } else { - let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in + let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: component.context), environment: ShareControllerAppEnvironment(sharedContext: component.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in guard let self, let view else { return } @@ -2148,13 +2147,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let phone = contactData.basicData.phoneNumbers[0].value if let vCardData = contactData.serializedVCard() { let media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: peer?.id, vCardData: vCardData) - + var enqueueMessages: [EnqueueMessage] = [] if let textEnqueueMessage = textEnqueueMessage { enqueueMessages.append(textEnqueueMessage) } enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])) - + self.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in guard let self else { return @@ -2170,58 +2169,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { })) return true case .gift: - /*let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions - if !premiumGiftOptions.isEmpty { - let controller = PremiumGiftScreen(context: context, peerId: peer.id, options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in - if let strongSelf = self { - strongSelf.push(c) - } - }, completion: { [weak self] in - if let strongSelf = self { - strongSelf.hintPlayNextOutgoingGift() - strongSelf.attachmentController?.dismiss(animated: true) - } - }) - completion(controller, controller.mediaPickerContext) - strongSelf.controllerNavigationDisposable.set(nil) - - let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id).start() - }*/ - //TODO:gift controller break case let .app(bot): let _ = bot -// let params = WebAppParameters(source: .attachMenu, peerId: peer.id, botId: bot.peer.id, botName: bot.shortName, botVerified: bot.peer.isVerified, url: nil, queryId: nil, payload: nil, buttonText: nil, keepAliveSignal: nil, forceHasSettings: false, fullSize: true) -// let theme = component.theme -// let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) -// let controller = WebAppController(context: component.context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: nil) -// controller.openUrl = { [weak self] url, _, _, _ in -// guard let self else { -// return -// } -// let _ = self -// //self?.openUrl(url, concealed: true, forceExternal: true) -// } -// controller.getNavigationController = { [weak view] in -// guard let view, let controller = view.component?.controller() else { -// return nil -// } -// return controller.navigationController as? NavigationController -// } -// controller.completion = { [weak self] in -// guard let self else { -// return -// } -// let _ = self -// /*if let strongSelf = self { -// strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { -// $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil) } -// }) -// strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() -// }*/ -// } -// completion(controller, controller.mediaPickerContext) -// self.controllerNavigationDisposable.set(nil) default: break } @@ -2236,7 +2186,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.attachmentController = attachmentController view.updateIsProgressPaused() } - + if inputIsActive { Queue.mainQueue().after(0.15, { present() @@ -2247,12 +2197,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - + private func presentMediaPicker( view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, - replyToStoryId: StoryId?, + replyToStoryId: EngineStoryId?, subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, @@ -2265,7 +2215,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } let theme = component.theme - let controller = MediaPickerScreenImpl(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peer: peer, threadTitle: nil, chatLocation: .peer(id: peer.id), bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos) + let controller = MediaPickerScreenImpl(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), style: .glass, peer: peer, threadTitle: nil, chatLocation: .peer(id: peer.id), bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, subject: subject, saveEditedPhotos: saveEditedPhotos) let mediaPickerContext = controller.mediaPickerContext controller.openCamera = { [weak self, weak view] cameraView in guard let self, let view else { @@ -2275,35 +2225,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.openCamera(view: view, peer: peer, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, cameraView: cameraView) } } - controller.presentWebSearch = { [weak self, weak view, weak controller] mediaGroups, activateOnDisplay in - guard let self, let view, let controller else { - return - } - self.presentWebSearch(view: view, activateOnDisplay: activateOnDisplay, present: { [weak controller] c, a in - controller?.present(c, in: .current) - if let webSearchController = c as? WebSearchController { - webSearchController.searchingUpdated = { [weak mediaGroups] searching in - if let mediaGroups = mediaGroups, mediaGroups.isNodeLoaded { - let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(node: mediaGroups.displayNode, alpha: searching ? 0.0 : 1.0) - mediaGroups.displayNode.isUserInteractionEnabled = !searching - } - } - webSearchController.present(mediaGroups, in: .current) - webSearchController.dismissed = { - updateMediaPickerContext(mediaPickerContext) - } - controller?.webSearchController = webSearchController - updateMediaPickerContext(webSearchController.mediaPickerContext) - } - }) - } controller.presentSchedulePicker = { [weak self, weak view] media, done in guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod in - done(time) + self.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod, silentPosting in + done(time, silentPosting) }) } controller.presentTimerPicker = { [weak self, weak view] done in @@ -2325,8 +2252,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } present(controller, mediaPickerContext) } - - private func presentOldMediaPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, fileMode: Bool, editingMedia: Bool, push: @escaping (ViewController) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) { + + private func presentOldMediaPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: EngineStoryId?, fileMode: Bool, editingMedia: Bool, push: @escaping (ViewController) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) { guard let component = view.component else { return } @@ -2338,11 +2265,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case let .text(text): inputText = text } - + let engine = component.context.engine let _ = (component.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, EngineConfiguration.SearchBots), NoError> in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - + return engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) |> map { configuration -> (GeneratedMediaStoreSettings, EngineConfiguration.SearchBots) in return (entry ?? GeneratedMediaStoreSettings.defaultSettings, configuration) @@ -2359,13 +2286,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { selectionLimit = 10 slowModeEnabled = true } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - + let _ = legacyAssetPicker(context: component.context, presentationData: presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer._asPeer(), threadTitle: nil, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).start(next: { [weak self, weak view] generator in if let view, let component = view.component, let controller = component.controller() { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - + let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: presentationData.theme, initialLayout: controller.currentlyAppliedLayout) legacyController.navigationPresentation = .modal legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style @@ -2374,44 +2301,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { legacyController?.view.disablesInteractiveModalDismiss = true } let controller = generator(legacyController.context) - + legacyController.bind(controller: controller) legacyController.deferScreenEdgeGestures = [.top] - - configureLegacyAssetPicker(controller, context: component.context, peer: peer._asPeer(), chatLocation: .peer(id: peer.id), initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak view, weak legacyController] in - if let view, let component = view.component { - let theme = component.theme - let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - let controller = WebSearchController(context: component.context, updatedPresentationData: updatedPresentationData, peer: peer, chatLocation: .peer(id: peer.id), configuration: searchBotsConfiguration, mode: .media(attachment: false, completion: { [weak view] results, selectionState, editingState, silentPosting in - if let legacyController = legacyController { - legacyController.dismiss() - } - guard let view else { - return - } - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak view] result in - if let strongSelf = self, let view { - strongSelf.enqueueChatContextResult(view: view, peer: peer, replyMessageId: replyMessageId, storyId: replyToStoryId, results: results, result: result, hideVia: true) - } - }, enqueueMediaMessages: { [weak view] signals in - if let strongSelf = self, let view { - if editingMedia { - strongSelf.editMessageMediaWithLegacySignals(view: view, signals: signals) - } else { - strongSelf.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: replyMessageId, replyToStoryId: replyToStoryId, signals: signals, silentPosting: silentPosting) - } - } - }) - })) - controller.getCaptionPanelView = { [weak view] in - guard let self, let view else { - return nil - } - return self.getCaptionPanelView(view: view, peer: peer) - } - component.controller()?.push(controller) - } - }, presentSelectionLimitExceeded: { [weak view] in + + configureLegacyAssetPicker(controller, context: component.context, peer: peer._asPeer(), chatLocation: .peer(id: peer.id), initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: nil, presentSelectionLimitExceeded: { [weak view] in guard let view, let component = view.component else { return } @@ -2424,8 +2318,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { component.controller()?.present(textAlertController(context: component.context, updatedPresentationData: (presentationData, .single(presentationData)), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak view] media, done in if let strongSelf = self, let view { - strongSelf.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod in - done(time) + strongSelf.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod, silentPosting in + done(time, silentPosting) }) } }, presentTimerPicker: { [weak view] done in @@ -2458,8 +2352,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - - private func presentFileGallery(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, editingMessage: Bool = false) { + + private func presentFileGallery(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: EngineStoryId?, editingMessage: Bool = false) { self.presentOldMediaPicker(view: view, peer: peer, replyMessageId: replyMessageId, replyToStoryId: replyToStoryId, fileMode: true, editingMedia: editingMessage, push: { [weak view] c in view?.component?.controller()?.push(c) }, completion: { [weak self, weak view] signals, silentPosting, scheduleTime in @@ -2473,8 +2367,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } }) } - - private func presentICloudFileGallery(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?) { + + private func presentICloudFileGallery(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: EngineStoryId?) { guard let component = view.component else { return } @@ -2489,9 +2383,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } let (accountPeer, limits, premiumLimits) = result let isPremium = accountPeer?.isPremium ?? false - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - + component.controller()?.present(legacyICloudFilePicker(theme: presentationData.theme, hasMultiselection: true, completion: { [weak self, weak view] urls in if let strongSelf = self, let view, !urls.isEmpty { var signals: [Signal] = [] @@ -2524,7 +2418,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + var groupingKey: Int64? var fileTypes: (music: Bool, other: Bool) = (false, false) if results.count > 1 { @@ -2542,14 +2436,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if fileTypes.music != fileTypes.other { groupingKey = Int64.random(in: Int64.min ... Int64.max) } - + var messages: [EnqueueMessage] = [] for item in results { if let item = item { let fileId = Int64.random(in: Int64.min ... Int64.max) let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { + if mimeType.hasPrefix("image/") || mimeType == "application/pdf" || item.audioMetadata?.hasAudioArtwork == true { previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } var attributes: [TelegramMediaFileAttribute] = [] @@ -2557,7 +2451,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if let audioMetadata = item.audioMetadata { attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) } - + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: replyMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) }, replyToStoryId: replyToStoryId, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []) messages.append(message) @@ -2566,7 +2460,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { groupingKey = Int64.random(in: Int64.min ... Int64.max) } } - + if !messages.isEmpty { strongSelf.presentPaidMessageAlertIfNeeded(view: view, completion: { [weak self] in guard let strongSelf = self else { @@ -2581,7 +2475,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }), in: .window(.root)) }) } - + func presentMediaPasteboard(view: StoryItemSetContainerComponent.View, subjects: [MediaPickerScreenImpl.Subject.Media]) { // guard let component = view.component else { // return @@ -2590,17 +2484,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { // guard let peerId = focusedItem.peerId else { // return // } -// let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id) +// let focusedStoryId = EngineStoryId(peerId: peerId, id: focusedItem.storyItem.id) // guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { // return // } -// +// // var inputText = NSAttributedString(string: "") // switch inputPanelView.getSendMessageInput() { // case let .text(text): // inputText = text // } -// +// // let peer = component.slice.effectivePeer // let theme = defaultDarkPresentationTheme // let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) @@ -2649,12 +2543,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { // controller.navigationPresentation = .flatModal // component.controller()?.push(controller) } - - private func enqueueChatContextResult(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, storyId: StoryId?, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) { - if !canSendMessagesToPeer(peer._asPeer()) { + + private func enqueueChatContextResult(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, storyId: EngineStoryId?, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, resetTextInputState: Bool = true) { + if !canSendMessagesToPeer(peer) { return } - + let sendMessage: (Int32?) -> Void = { [weak self, weak view] scheduleTime in guard let self, let view, let component = view.component else { return @@ -2673,50 +2567,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { postpone: false ) { } - + if let attachmentController = self.attachmentController { attachmentController.dismiss(animated: true) } } - - sendMessage(nil) - } - - private func presentWebSearch(view: StoryItemSetContainerComponent.View, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { - guard let component = view.component else { - return - } - let context = component.context - let peer = component.slice.effectivePeer - let storyId = component.slice.item.storyItem.id - - let theme = component.theme - let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> deliverOnMainQueue).start(next: { [weak self, weak view] configuration in - if let self { - let controller = WebSearchController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, chatLocation: .peer(id: peer.id), configuration: configuration, mode: .media(attachment: true, completion: { [weak self] results, selectionState, editingState, silentPosting in - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self, weak view] result in - if let self, let view { - self.performSendContextResultAction(view: view, results: results, result: result) - } - }, enqueueMediaMessages: { [weak self, weak view] signals in - if let self, let view, !signals.isEmpty { - self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: StoryId(peerId: peer.id, id: storyId), signals: signals, silentPosting: false) - } - }) - }), activateOnDisplay: activateOnDisplay) - controller.getCaptionPanelView = { [weak self, weak view] in - if let view { - return self?.getCaptionPanelView(view: view, peer: peer) - } else { - return nil - } - } - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - }) + + sendMessage(scheduleTime) } private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer, mediaPicker: MediaPickerScreenImpl? = nil) -> TGCaptionPanelView? { @@ -2754,28 +2611,33 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { view.updateIsProgressPaused() } view.component?.controller()?.push(c) - + view.updateIsProgressPaused() } else { view.component?.controller()?.presentInGlobalOverlay(c) } + }, getNavigationController: { [weak view] in + guard let controller = view?.component?.controller() else { + return nil + } + return controller.navigationController as? NavigationController }) as? TGCaptionPanelView } - - private func openCamera(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, cameraView: TGAttachmentCameraView? = nil) { + + private func openCamera(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: EngineStoryId?, cameraView: TGAttachmentCameraView? = nil) { guard let component = view.component else { return } guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else { return } - + var inputText = NSAttributedString(string: "") switch inputPanelView.getSendMessageInput() { case let .text(text): inputText = text } - + let _ = (component.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) return entry ?? GeneratedMediaStoreSettings.defaultSettings @@ -2784,17 +2646,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let component = view.component, let parentController = component.controller() else { return } - + var enablePhoto = true var enableVideo = true - + if let callManager = component.context.sharedContext.callManager, callManager.hasActiveCall { enableVideo = false } - + var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? - + if case let .channel(channel) = peer { if let value = channel.hasBannedPermission(.banSendPhotos) { bannedSendPhotos = value @@ -2810,17 +2672,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { bannedSendVideos = (Int32.max, false) } } - + if bannedSendPhotos != nil { enablePhoto = false } if bannedSendVideos != nil { enableVideo = false } - + let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat - - presentedLegacyCamera(context: component.context, peer: peer._asPeer(), chatLocation: .peer(id: peer.id), cameraView: cameraView, menuController: nil, parentController: parentController, attachmentController: self.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters in + + presentedLegacyCamera(context: component.context, peer: peer, chatLocation: .peer(id: peer.id), cameraView: cameraView, menuController: nil, parentController: parentController, attachmentController: self.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self, weak view] signals, silentPosting, scheduleTime, parameters in guard let self, let view else { return } @@ -2838,8 +2700,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, style: .media, completion: { time, repeatPeriod in - done(time) + self.presentScheduleTimePicker(view: view, peer: peer, style: .media, completion: { time, repeatPeriod, silentPosting in + done(time, silentPosting) }) }, presentTimerPicker: { [weak self, weak view] done in guard let self, let view else { @@ -2853,6 +2715,8 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return nil } return self.getCaptionPanelView(view: view, peer: peer) + }, photoToolbarView: { [context = component.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) }, dismissedWithResult: { [weak self] in guard let self else { return @@ -2866,14 +2730,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) }) } - + private func presentScheduleTimePicker( view: StoryItemSetContainerComponent.View, peer: EnginePeer, style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, - completion: @escaping (Int32, Int32?) -> Void + completion: @escaping (Int32, Int32?, Bool) -> Void ) { guard let component = view.component else { return @@ -2885,7 +2749,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let view, let component = view.component else { return } - + var sendWhenOnlineAvailable = false if let presence, case .present = presence.status { sendWhenOnlineAvailable = true @@ -2893,43 +2757,52 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { sendWhenOnlineAvailable = false } - - let mode: ChatScheduleTimeControllerMode + + let mode: ChatScheduleTimeScreen.Mode //ChatScheduleTimeControllerMode if peer.id == component.context.account.peerId { mode = .reminders } else { - mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) + mode = .scheduledMessages(peerId: peer.id, sendWhenOnlineAvailable: sendWhenOnlineAvailable) } - let theme = component.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in - completion(time, nil) - }) + let controller = ChatScheduleTimeScreen( + context: component.context, + mode: mode, + currentTime: selectedTime, + currentRepeatPeriod: nil, + suggestedTime: nil, + minimalTime: nil, + silentPosting: false, + isDark: style == .media, + completion: { result in + completion(result.time, nil, result.silentPosting) + } + ) view.endEditing(true) view.component?.controller()?.present(controller, in: .window(.root)) }) } - - private func presentTimerPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + + private func presentTimerPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, completion: @escaping (Int32) -> Void) { guard let component = view.component else { return } let theme = component.theme - let controller = ChatTimerScreen(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatTimerScreen(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), style: style, currentTime: selectedTime, completion: { time in completion(time) }) view.endEditing(true) component.controller()?.present(controller, in: .window(.root)) } - + private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] { - var focusedStoryId: StoryId? + var focusedStoryId: EngineStoryId? if let component = view.component, let peerId = component.slice.item.peerId { - focusedStoryId = StoryId(peerId: peerId, id: component.slice.item.storyItem.id) + focusedStoryId = EngineStoryId(peerId: peerId, id: component.slice.item.storyItem.id) } - + return messages.map { message in var message = message - + if let focusedStoryId { switch message { case let .message(text, attributes, inlineStickers, mediaReference, threadId, replyToMessageId, _, localGroupingKey, correlationId, bubbleUpEmojiOrStickersets): @@ -2940,7 +2813,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { break } } - + return message.withUpdatedAttributes { attributes in var attributes = attributes if silentPosting || scheduleTime != nil { @@ -2958,7 +2831,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) } } - var messageAttributes: [MessageAttribute] = [] + var messageAttributes: [EngineMessage.Attribute] = [] if let component = view.component, let sendPaidMessageStars = component.slice.additionalPeerData.sendPaidMessageStars { messageAttributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) } @@ -2966,7 +2839,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], silentPosting: Bool = false, scheduleTime: Int32? = nil) { guard let component = view.component else { return @@ -2979,29 +2852,29 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } }) - + donateSendMessageIntent(account: component.context.account, sharedContext: component.context.sharedContext, intentContext: .chat, peerIds: [peer.id]) - + if let attachmentController = self.attachmentController { attachmentController.dismiss(animated: true) } } - - private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { + + private func enqueueMediaMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyToMessageId: EngineMessage.Id?, replyToStoryId: EngineStoryId?, signals: [Any]?, silentPosting: Bool, scheduleTime: Int32? = nil, parameters: ChatSendMessageActionSheetController.SendParameters? = nil, getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {}) { guard let component = view.component else { return } - + self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals!) |> deliverOnMainQueue).start(next: { [weak self, weak view] items in if let strongSelf = self, let view { var mappedMessages: [EnqueueMessage] = [] var addedTransitions: [(Int64, [String], () -> Void)] = [] - + var groupedCorrelationIds: [Int64: Int64] = [:] - + var skipAddingTransitions = false - + for item in items { var message = item.message if message.groupingKey != nil { @@ -3011,7 +2884,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } else if items.count > 3 { skipAddingTransitions = true } - + if let uniqueId = item.uniqueId, !item.isFile && !skipAddingTransitions { let correlationId: Int64 var addTransition = scheduleTime == nil @@ -3058,12 +2931,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } strongSelf.sendMessages(view: view, peer: peer, messages: mappedMessages.map { $0.withUpdatedReplyToMessageId(replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) }).withUpdatedReplyToStoryId(replyToStoryId) }, silentPosting: silentPosting, scheduleTime: scheduleTime) - + completion() } })) } - + private func editMessageMediaWithLegacySignals(view: StoryItemSetContainerComponent.View, signals: [Any]) { guard let component = view.component else { return @@ -3071,12 +2944,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let _ = (legacyAssetPickerEnqueueMessages(context: component.context, account: component.context.account, signals: signals) |> deliverOnMainQueue).start() } - + func openResolved(view: StoryItemSetContainerComponent.View, result: ResolvedUrl, forceExternal: Bool = false, concealed: Bool = false, completion: (() -> Void)? = nil) { guard let component = view.component, let navigationController = component.controller()?.navigationController as? NavigationController else { return } - + self.progressPauseContext.update = { [weak self, weak view] controller in guard let self, let view else { return @@ -3084,7 +2957,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.progressPauseContext.externalController = controller view.updateIsProgressPaused() } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) let peerId = component.slice.effectivePeer.id @@ -3113,7 +2986,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { })) } case .info: - let _ = (context.account.postbox.loadedPeerWithId(peerId.id) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).start(next: { [weak navigationController] peer in if peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil { @@ -3142,7 +3022,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } controller.present(c, in: .window(.root), with: a) - }, + }, dismissInput: { [weak view] in guard let view else { return @@ -3154,7 +3034,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { completion: completion ) } - + func navigateToMessage(view: StoryItemSetContainerComponent.View, messageId: EngineMessage.Id, completion: (() -> Void)?) { guard let component = view.component else { return @@ -3170,14 +3050,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { completion?() }) } - - func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, sourceMessageId: MessageId? = nil) { + + func openPeerMention(view: StoryItemSetContainerComponent.View, name: String, sourceMessageId: EngineMessage.Id? = nil) { guard let component = view.component, let parentController = component.controller() else { return } let disposable = self.resolvePeerByNameDisposable var resolveSignal = component.context.engine.peers.resolvePeerByName(name: name, referrer: nil, ageLimit: 10) - + var cancelImpl: (() -> Void)? let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let progressSignal = Signal { [weak self, weak view, weak parentController] subscriber in @@ -3185,14 +3065,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { cancelImpl?() })) parentController?.present(controller, in: .window(.root)) - + self?.statusController = controller view?.updateIsProgressPaused() - + return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() - + self?.statusController = nil view?.updateIsProgressPaused() } @@ -3201,7 +3081,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - + resolveSignal = resolveSignal |> afterDisposed { Queue.mainQueue().async { @@ -3221,16 +3101,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } return .single(result) } - |> mapToSignal { peer -> Signal in - return .single(peer?._asPeer()) - } - |> deliverOnMainQueue).start(next: { [weak view] peer in + |> deliverOnMainQueue).start(next: { [weak view] enginePeer in guard let view, let component = view.component else { return } - if let peer = peer { + if let enginePeer = enginePeer { + let peer = enginePeer._asPeer() var navigation: ChatControllerInteractionNavigateToPeer - if let peer = peer as? TelegramUser, peer.botInfo == nil { + if case let .user(user) = enginePeer, user.botInfo == nil { navigation = .info(nil) } else { navigation = .chat(textInputState: nil, subject: nil, peekData: nil) @@ -3242,15 +3120,15 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } })) } - + func openHashtag(view: StoryItemSetContainerComponent.View, hashtag: String, peerName: String?) { guard let component = view.component, let parentController = component.controller() else { return } - + let peerId = component.slice.effectivePeer.id - - var resolveSignal: Signal + + var resolveSignal: Signal if let peerName = peerName { resolveSignal = component.context.engine.peers.resolvePeerByName(name: peerName, referrer: nil) |> mapToSignal { result -> Signal in @@ -3259,16 +3137,15 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } return .single(result) } - |> mapToSignal { peer -> Signal in - if let peer = peer { - return .single(peer._asPeer()) + } else { + resolveSignal = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) } else { - return .single(nil) + return .never() } } - } else { - resolveSignal = component.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) } var cancelImpl: (() -> Void)? let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } @@ -3277,14 +3154,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { cancelImpl?() })) parentController?.present(controller, in: .window(.root)) - + self?.statusController = controller view?.updateIsProgressPaused() - + return ActionDisposable { [weak controller] in Queue.mainQueue().async() { controller?.dismiss() - + self?.statusController = nil view?.updateIsProgressPaused() } @@ -3293,7 +3170,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - + resolveSignal = resolveSignal |> afterDisposed { Queue.mainQueue().async { @@ -3319,13 +3196,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let searchController = component.context.sharedContext.makeStorySearchController(context: component.context, scope: .query(nil, hashtag), listContext: nil) navigationController.pushViewController(searchController) } else { - let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer.flatMap(EnginePeer.init), query: hashtag, stories: true, forceDark: true) + let searchController = component.context.sharedContext.makeHashtagSearchController(context: component.context, peer: peer, query: hashtag, stories: true, forceDark: true) navigationController.pushViewController(searchController) } } })) } - + func openPeerMention(view: StoryItemSetContainerComponent.View, peerId: EnginePeer.Id) { guard let component = view.component else { return @@ -3338,13 +3215,21 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.openPeer(view: view, peer: peer) }) } - + func openPeer(view: StoryItemSetContainerComponent.View, peer: EnginePeer, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil) { guard let component = view.component else { return } - - let peerSignal: Signal = component.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) + + let peerSignal: Signal = component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map(Optional.init) self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak view] peer in guard let view, let component = view.component, let peer else { return @@ -3362,16 +3247,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } })) } - + func presentTextEntityActions(view: StoryItemSetContainerComponent.View, action: StoryContentCaptionComponent.Action, openUrl: @escaping (String, Bool) -> Void) { guard let component = view.component else { return } - + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: component.theme, fontSize: .regular), allowInputInset: false) - + var canOpenIn = false - + let title: String let value: String var openAction: String? = component.strings.Conversation_LinkDialogOpen @@ -3401,10 +3286,10 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case .customEmoji: return } - + var items: [ActionSheetItem] = [] items.append(ActionSheetTextItem(title: title)) - + if let openAction { items.append(ActionSheetButtonItem(title: openAction, color: .accent, action: { [weak self, weak view, weak actionSheet] in actionSheet?.dismissAnimated() @@ -3413,12 +3298,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case let .url(url, concealed): if canOpenIn { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let actionSheet = OpenInActionSheetController(context: component.context, item: .url(url: url), openUrl: { url in + let actionSheet = OpenInOptionsScreen(context: component.context, item: .url(url: url), openUrl: { url in if let navigationController = component.controller()?.navigationController as? NavigationController { component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) } }) - component.controller()?.present(actionSheet, in: .window(.root)) + component.controller()?.push(actionSheet) } else { openUrl(url, concealed) } @@ -3434,19 +3319,19 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } })) } - + items.append(ActionSheetButtonItem(title: copyAction, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() UIPasteboard.general.string = value })) - + if case let .url(url, _) = action, let link = URL(string: url) { items.append(ActionSheetButtonItem(title: component.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) })) } - + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: component.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in actionSheet?.dismissAnimated() @@ -3459,18 +3344,18 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.actionSheet = nil view.updateIsProgressPaused() } - + component.controller()?.present(actionSheet, in: .window(.root)) - + self.actionSheet = actionSheet view.updateIsProgressPaused() } - + func openAttachedStickers(view: StoryItemSetContainerComponent.View, packs: Signal<[StickerPackReference], NoError>) { guard let component = view.component else { return } - + guard let parentController = component.controller() as? StoryContainerScreen else { return } @@ -3479,14 +3364,14 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { let progressSignal = Signal { [weak parentController, weak self, weak view] subscriber in let progressController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) parentController?.present(progressController, in: .window(.root), with: nil) - + self?.statusController = progressController view?.updateIsProgressPaused() - + return ActionDisposable { [weak progressController] in Queue.mainQueue().async() { progressController?.dismiss() - + self?.statusController = nil view?.updateIsProgressPaused() } @@ -3495,7 +3380,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { |> runOn(Queue.mainQueue()) |> delay(0.15, queue: Queue.mainQueue()) let progressDisposable = progressSignal.start() - + let signal = packs |> afterDisposed { Queue.mainQueue().async { @@ -3533,16 +3418,16 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }) parentController?.present(controller, in: .window(.root), with: nil) }) - + self.isViewingAttachedStickers = true view.updateIsProgressPaused() } - + func requestStealthMode(view: StoryItemSetContainerComponent.View) { guard let component = view.component else { return } - + let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState(), TelegramEngine.EngineData.Item.Configuration.App() @@ -3551,11 +3436,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let component = view.component, let controller = component.controller() else { return } - + let timestamp = Int32(Date().timeIntervalSince1970) if let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp { let remainingActiveSeconds = activeUntilTimestamp - timestamp - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) let text = component.strings.Story_ToastStealthModeActiveText(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)).string let tooltipScreen = UndoOverlayController( @@ -3579,24 +3464,24 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.currentTooltipUpdateTimer = nil return } - + let timestamp = Int32(Date().timeIntervalSince1970) let remainingActiveSeconds = max(1, activeUntilTimestamp - timestamp) - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) let text = component.strings.Story_ToastStealthModeActiveText(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)).string tooltipScreenValue.content = .actionSucceeded(title: component.strings.Story_ToastStealthModeActiveTitle, text: text, cancel: "", destructive: false) }) - + self.tooltipScreen?.dismiss(animated: true) self.tooltipScreen = tooltipScreen controller.present(tooltipScreen, in: .current) - + view.updateIsProgressPaused() - + return } - + let pastPeriod: Int32 let futurePeriod: Int32 if let data = appConfig.data, let futurePeriodF = data["stories_stealth_future_period"] as? Double, let pastPeriodF = data["stories_stealth_past_period"] as? Double { @@ -3606,7 +3491,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { pastPeriod = 5 * 60 futurePeriod = 25 * 60 } - + let sheet = StoryStealthModeSheetScreen( context: component.context, mode: .control(external: false, cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp), @@ -3617,13 +3502,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let component = view.component else { return } - + let _ = (component.context.engine.messages.enableStoryStealthMode() |> deliverOnMainQueue).start(completed: { [weak self, weak view] in guard let self, let view, let component = view.component, let controller = component.controller() else { return } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) let text = component.strings.Story_ToastStealthModeActivatedText(timeIntervalString(strings: presentationData.strings, value: pastPeriod), timeIntervalString(strings: presentationData.strings, value: futurePeriod)).string let tooltipScreen = UndoOverlayController( @@ -3638,9 +3523,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.tooltipScreen?.dismiss(animated: true) self.tooltipScreen = tooltipScreen controller.present(tooltipScreen, in: .current) - + view.updateIsProgressPaused() - + HapticFeedback().success() }) } @@ -3657,12 +3542,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.push(sheet) }) } - + func presentStealthModeUpgrade(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { guard let component = view.component else { return } - + let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState(), TelegramEngine.EngineData.Item.Configuration.App() @@ -3671,7 +3556,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let self, let view, let component = view.component, let controller = component.controller() else { return } - + let pastPeriod: Int32 let futurePeriod: Int32 if let data = appConfig.data, let futurePeriodF = data["stories_stealth_future_period"] as? Double, let pastPeriodF = data["stories_stealth_past_period"] as? Double { @@ -3681,7 +3566,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { pastPeriod = 5 * 60 futurePeriod = 25 * 60 } - + let sheet = StoryStealthModeSheetScreen( context: component.context, mode: .upgrade, @@ -3704,12 +3589,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { controller.push(sheet) }) } - + func presentQualityUpgrade(view: StoryItemSetContainerComponent.View, action: @escaping () -> Void) { guard let component = view.component, let controller = component.controller() else { return } - + let sheet = StoryQualityUpgradeSheetScreen( context: component.context, buttonAction: { @@ -3727,20 +3612,20 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { view.updateIsProgressPaused() controller.push(sheet) } - + private var selectedMediaArea: MediaArea? func activateMediaArea(view: StoryItemSetContainerComponent.View, mediaArea: MediaArea, position: CGPoint? = nil, immediate: Bool = false) { guard let component = view.component, let controller = component.controller() else { return } - + let theme = defaultDarkColorPresentationTheme let updatedPresentationData: (initial: PresentationData, signal: Signal) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }) - + let context = component.context - + var useGesturePosition = false - + var actions: [ContextMenuAction] = [] switch mediaArea { case let .venue(coordinates, venue): @@ -3759,9 +3644,9 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { useGesturePosition = true let action = { [weak self, weak view, weak controller] in let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: true)) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in if case let .result(messages) = result { - return .single(messages.first) + return .single(messages.first.flatMap(EngineMessage.init)) } else { return .complete() } @@ -3809,20 +3694,20 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return case let .link(_, url): let action = { - let _ = openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, concealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in + let _ = component.context.sharedContext.openUserGeneratedUrl(context: component.context, peerId: component.slice.effectivePeer.id, url: url, webpage: nil, concealed: false, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in controller?.present(c, in: .window(.root)) }, openResolved: { [weak self, weak view] resolved in guard let self, let view else { return } self.openResolved(view: view, result: resolved, forceExternal: false, concealed: false) - }, alertDisplayUpdated: { [weak self, weak view] alertController in + }, progress: nil, alertDisplayUpdated: { [weak self, weak view] alertController in guard let self, let view else { return } self.statusController = alertController view.updateIsProgressPaused() - }) + }, concealedAlertOption: nil) } if immediate { action() @@ -3836,20 +3721,20 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { case let .starGift(_, slug): useGesturePosition = true let action = { - let _ = openUserGeneratedUrl(context: component.context, peerId: nil, url: "https://t.me/nft/\(slug)", concealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in + let _ = component.context.sharedContext.openUserGeneratedUrl(context: component.context, peerId: nil, url: "https://t.me/nft/\(slug)", webpage: nil, concealed: false, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: true, present: { [weak controller] c in controller?.present(c, in: .window(.root)) }, openResolved: { [weak self, weak view] resolved in guard let self, let view else { return } self.openResolved(view: view, result: resolved, forceExternal: false, concealed: false) - }, alertDisplayUpdated: { [weak self, weak view] alertController in + }, progress: nil, alertDisplayUpdated: { [weak self, weak view] alertController in guard let self, let view else { return } self.statusController = alertController view.updateIsProgressPaused() - }) + }, concealedAlertOption: nil) } if immediate { action() @@ -3859,18 +3744,18 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { action() })) } - + self.selectedMediaArea = mediaArea - + let referenceSize = view.controlsContainerView.frame.size let size = CGSize(width: 16.0, height: mediaArea.coordinates.height / 100.0 * referenceSize.height * 1.1) var frame = CGRect(x: mediaArea.coordinates.x / 100.0 * referenceSize.width - size.width / 2.0, y: mediaArea.coordinates.y / 100.0 * referenceSize.height - size.height / 2.0, width: size.width, height: size.height) frame = view.controlsContainerView.convert(frame, to: nil) - + if useGesturePosition, let position { frame = CGRect(origin: position.offsetBy(dx: 0.0, dy: 44.0), size: .zero) } - + let node = controller.displayNode let menuController = makeContextMenuController(actions: actions, blurred: true) menuController.centerHorizontally = true @@ -3901,17 +3786,17 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.menuController = menuController view.updateIsProgressPaused() } - + func activateInlineReaction(view: StoryItemSetContainerComponent.View, reactionView: UIView, reaction: MessageReaction.Reaction) { guard let component = view.component else { return } - + let animateWithReactionItem: (ReactionItem) -> Void = { [weak self, weak view] reactionItem in guard let self, let view else { return } - + self.performWithPossibleStealthModeConfirmation(view: view, action: { [weak view] in guard let view, let component = view.component else { return @@ -3919,26 +3804,26 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { if component.slice.effectivePeer.id != component.context.account.peerId { let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.effectivePeer.id, id: component.slice.item.storyItem.id, reaction: reaction).start() } - + let targetFrame = reactionView.convert(reactionView.bounds, to: view) - + let targetView = UIView(frame: targetFrame) targetView.isUserInteractionEnabled = false view.addSubview(targetView) - + let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false) view.componentContainerView.addSubview(standaloneReactionAnimation.view) - + if let standaloneReactionAnimation = view.standaloneReactionAnimation { view.standaloneReactionAnimation = nil - + let standaloneReactionAnimationView = standaloneReactionAnimation.view standaloneReactionAnimation.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak standaloneReactionAnimationView] _ in standaloneReactionAnimationView?.removeFromSuperview() }) } view.standaloneReactionAnimation = standaloneReactionAnimation - + standaloneReactionAnimation.frame = view.bounds standaloneReactionAnimation.animateReactionSelection( context: component.context, @@ -3954,13 +3839,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let view else { return } - + if let standaloneReactionAnimation = view.standaloneReactionAnimation { view.standaloneReactionAnimation = nil standaloneReactionAnimation.view.removeFromSuperview() } view.standaloneReactionAnimation = standaloneReactionAnimation - + standaloneReactionAnimation.frame = view.bounds view.componentContainerView.addSubview(standaloneReactionAnimation.view) }, @@ -3971,7 +3856,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { ) }) } - + switch reaction { case .builtin: if let availableReactions = component.availableReactions { @@ -4011,7 +3896,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + func openSendStars(view: StoryItemSetContainerComponent.View) { Task { @MainActor [weak view] in guard let view else { @@ -4027,7 +3912,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let peerId = focusedItem.peerId else { return } - + var topPeers: [ReactionsMessageAttribute.TopPeer] = [] var minAmount: Int64 = 1 var sendAsPeer: SendAsPeer? @@ -4043,11 +3928,11 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { ) } } - + if let minMessagePrice = visibleItemView.liveChatState?.minMessagePrice { minAmount = minMessagePrice } - + if let currentSendAsPeer = self.currentSendAsPeer { sendAsPeer = currentSendAsPeer } else { @@ -4056,12 +3941,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + let initialData = await ChatSendStarsScreen.initialData( context: component.context, peerId: peerId, - myPeer: (sendAsPeer?.peer).flatMap(EnginePeer.init), - reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ EnginePeer($0.peer) }) ?? []), isDisplayOnly: component.isEmbeddedInCamera), + myPeer: sendAsPeer?.peer, + reactSubject: .liveStream(peerId: peerId, storyId: focusedItem.storyItem.id, minAmount: Int(minAmount), liveChatMessageParams: LiveChatMessageParams(appConfig: component.context.currentAppConfiguration.with({ $0 })), availableSendAsPeers: component.isEmbeddedInCamera ? [] : (self.sendAsData?.availablePeers.map({ $0.peer }) ?? []), isDisplayOnly: component.isEmbeddedInCamera), topPeers: topPeers, completion: { [weak self, weak view] amount, privacy, _, _ in guard let self, let view else { @@ -4086,30 +3971,30 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + func performSendStars(view: StoryItemSetContainerComponent.View, buttonView: UIView?, count: Int, isFromExpandedView: Bool) { Task { @MainActor [weak self, weak view] in guard let self, let view, let component = view.component else { return } - + guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else { return } - + if isFromExpandedView { if let currentSendStarsUndoController = self.currentSendStarsUndoController { self.currentSendStarsUndoController = nil currentSendStarsUndoController.dismiss() } - + self.commitSendStars(view: view, count: count, delay: false) } else { let starsContextState = await component.context.starsContext?.state.get() guard let balance = starsContextState?.balance else { return } - + var totalExpectedStars = count if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars, pendingMyStars > 0 { totalExpectedStars += Int(pendingMyStars) @@ -4121,7 +4006,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let targetPeerId = component.slice.item.peerId else { return } - + let customTheme = component.theme let options = await component.context.engine.payments.starsTopUpOptions().get() let controller = component.context.sharedContext.makeStarsPurchaseScreen( @@ -4134,10 +4019,10 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { completion: { _ in } ) navigationController.pushViewController(controller) - + return } - + var reactionItem: ReactionItem? if let availableReactions = await component.context.availableReactions.get() { for item in availableReactions.reactions { @@ -4148,7 +4033,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let aroundAnimation = item.aroundAnimation else { continue } - + reactionItem = ReactionItem( reaction: ReactionItem.Reaction(rawValue: item.value), appearAnimation: item.appearAnimation, @@ -4163,27 +4048,27 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + if let reactionItem, let buttonView { let targetFrame = buttonView.convert(buttonView.bounds, to: view) - + let targetView = UIView(frame: targetFrame) targetView.isUserInteractionEnabled = false view.addSubview(targetView) - + let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: nil, useDirectRendering: false) view.componentContainerView.addSubview(standaloneReactionAnimation.view) - + if let standaloneReactionAnimation = view.standaloneReactionAnimation { view.standaloneReactionAnimation = nil - + let standaloneReactionAnimationView = standaloneReactionAnimation.view standaloneReactionAnimation.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak standaloneReactionAnimationView] _ in standaloneReactionAnimationView?.removeFromSuperview() }) } view.standaloneReactionAnimation = standaloneReactionAnimation - + standaloneReactionAnimation.frame = view.bounds standaloneReactionAnimation.animateReactionSelection( context: component.context, @@ -4199,13 +4084,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let view else { return } - + if let standaloneReactionAnimation = view.standaloneReactionAnimation { view.standaloneReactionAnimation = nil standaloneReactionAnimation.view.removeFromSuperview() } view.standaloneReactionAnimation = standaloneReactionAnimation - + standaloneReactionAnimation.frame = view.bounds view.componentContainerView.addSubview(standaloneReactionAnimation.view) }, @@ -4215,7 +4100,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } ) } - + self.currentLiveStreamStarsIsActive = true self.currentLiveStreamStarsIsActiveTimer?.invalidate() self.currentLiveStreamStarsIsActiveTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self, weak view] _ in @@ -4225,7 +4110,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { self.currentLiveStreamStarsIsActive = false view.state?.updated(transition: .spring(duration: 0.4)) }) - + var totalStars = 0 if let pendingMyStars = visibleItemView.liveChatState?.starStats?.pendingMyStars, pendingMyStars > 0 { totalStars += count @@ -4236,10 +4121,10 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { var count = count count = max(Int(minAmount), count) totalStars += count - + self.commitSendStars(view: view, count: count, delay: true) } - + let title: String /*if case .anonymous = privacy { title = self.presentationData.strings.Chat_ToastStarsSent_AnonymousTitle(Int32(self.currentSendStarsUndoCount)) @@ -4249,12 +4134,12 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } else*/ do { title = component.strings.Chat_ToastStarsSent_Title(Int32(totalStars)) } - + let textItems = AnimatedTextComponent.extractAnimatedTextString(string: component.strings.Chat_ToastStarsSent_Text("", ""), id: "text", mapping: [ 0: .number(totalStars, minDigits: 1), 1: .text(component.strings.Chat_ToastStarsSent_TextStarAmount(Int32(totalStars))) ]) - + if let current = self.currentSendStarsUndoController { current.content = .starsSent(context: component.context, title: title, text: textItems, hasUndo: true) } else { @@ -4286,7 +4171,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + private func commitSendStars(view: StoryItemSetContainerComponent.View, count: Int, delay: Bool) { guard let component = view.component else { return @@ -4310,7 +4195,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return self.sendAsData?.availablePeers.first(where: { $0.peer.id == defaultSendAs }) } } - + if liveChatStateValue.isAdmin { if let sendAsPeer { isAdmin = sendAsPeer.peer.id == component.context.account.peerId @@ -4319,10 +4204,10 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } } } - + call.sendStars(fromId: sendAsPeer?.peer.id, isAdmin: isAdmin, amount: Int64(count), delay: delay) } - + func openSendAsSelection(view: StoryItemSetContainerComponent.View, sourceView: UIView, gesture: ContextGesture?) { guard let component = view.component, let sendAsData = self.sendAsData, let controller = component.controller() else { return @@ -4330,7 +4215,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { guard let visibleItemView = view.visibleItems[component.slice.item.id]?.view.view as? StoryItemContentComponent.View else { return } - + var currentSendAsPeer: SendAsPeer? if let currentSendAsPeerValue = self.currentSendAsPeer { currentSendAsPeer = currentSendAsPeerValue @@ -4339,13 +4224,13 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return self.sendAsData?.availablePeers.first(where: { $0.peer.id == defaultSendAs }) } } - + let focusedItem = component.slice.item guard let peerId = focusedItem.peerId else { return } let isPremium = sendAsData.isPremium - + var items: [ContextMenuItem] = [] items.append(.custom(ChatSendAsPeerTitleContextItem(text: component.strings.Conversation_SendMesageAs.uppercased()), false)) items.append(.custom(ChatSendAsPeerListContextItem( @@ -4368,7 +4253,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { return } HapticFeedback().impact() - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) controller.present(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: component.context, peer: peer, title: nil, text: presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak view] action in guard let view, let component = view.component, let controller = component.controller() else { @@ -4376,7 +4261,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } if case .undo = action { view.endEditing(true) - + let introScreen = PremiumIntroScreen(context: component.context, source: .settings) controller.push(introScreen) } @@ -4385,25 +4270,25 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { }), false )) - + final class ReferenceSource: ContextReferenceContentSource { let controller: ViewController let sourceView: UIView let insets: UIEdgeInsets let contentInsets: UIEdgeInsets - + init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { self.controller = controller self.sourceView = sourceView self.insets = insets self.contentInsets = contentInsets } - + func transitionInfo() -> ContextControllerReferenceViewInfo? { return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets, actionsPosition: .top) } } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceSource(controller: controller, sourceView: sourceView, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), contentInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))), items: .single(ContextController.Items(content: .list(items))), gesture: gesture, workaroundUseLegacyImplementation: false) contextController.dismissed = { [weak self, weak view] in @@ -4414,19 +4299,19 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { view.state?.updated(transition: .spring(duration: 0.4)) } controller.presentInGlobalOverlay(contextController) - + self.isSelectingSendAsPeer = true view.state?.updated(transition: .spring(duration: 0.4)) } - + func displayLiveStreamSettings(view: StoryItemSetContainerComponent.View) { Task { @MainActor [weak self, weak view] in guard let self, let view, let component = view.component, let controller = component.controller(), let mediaStreamCall = view.mediaStreamCall else { return } - + let callState = await (mediaStreamCall.state |> take(1)).get() - + let stateContext = LiveStreamSettingsScreen.StateContext( context: component.context, mode: .edit( @@ -4463,7 +4348,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { public class StoryProgressPauseContext { fileprivate weak var externalController: ViewController? public fileprivate(set) var update: (ViewController?) -> Void = { _ in } - + var hasExternalController: Bool { return self.externalController != nil } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 1a1ff8c8e4..7eb6970858 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import MultilineTextComponent import TelegramCore -import Postbox import TelegramPresentationData import ComponentDisplayAdapters import AccountContext @@ -46,7 +45,7 @@ public final class StoryItemSetViewListComponent: Component { } public final class SharedListsContext { - var viewLists: [StoryId: EngineStoryViewListContext] = [:] + var viewLists: [EngineStoryId: EngineStoryViewListContext] = [:] public init() { } @@ -294,7 +293,7 @@ public final class StoryItemSetViewListComponent: Component { var requestedLoadMoreToken: EngineStoryViewListContext.LoadMoreToken? private var previewedItemDisposable: Disposable? - private var previewedItemId: StoryId? + private var previewedItemId: EngineStoryId? var eventCycleState: EventCycleState? @@ -345,7 +344,7 @@ public final class StoryItemSetViewListComponent: Component { self.previewedItemDisposable?.dispose() } - func setPreviewedItem(signal: Signal) { + func setPreviewedItem(signal: Signal) { self.previewedItemDisposable?.dispose() self.previewedItemDisposable = (signal |> distinctUntilChanged |> deliverOnMainQueue).start(next: { [weak self] previewedItemId in guard let self else { @@ -369,7 +368,7 @@ public final class StoryItemSetViewListComponent: Component { }) } - func sourceView(storyId: StoryId) -> UIView? { + func sourceView(storyId: EngineStoryId) -> UIView? { for (itemId, visibleItem) in self.visibleItems { if let itemView = visibleItem.view as? PeerListItemComponent.View { if itemId.peerId == storyId.peerId && itemId.storyId == storyId.id { @@ -808,11 +807,11 @@ public final class StoryItemSetViewListComponent: Component { } if self.configuration == ContentConfigurationKey(listMode: .everyone, sortMode: defaultSortMode) { let viewList: EngineStoryViewListContext - if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] { + if let current = component.sharedListsContext.viewLists[EngineStoryId(peerId: component.peerId, id: component.storyItem.id)] { viewList = current } else { viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: defaultSortMode.sortMode) - component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList + component.sharedListsContext.viewLists[EngineStoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList } self.viewList = viewList } else { @@ -823,7 +822,7 @@ public final class StoryItemSetViewListComponent: Component { case .contacts: mappedListMode = .contacts } - self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: self.configuration.sortMode.sortMode, parentSource: component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)]) + self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: self.configuration.sortMode.sortMode, parentSource: component.sharedListsContext.viewLists[EngineStoryId(peerId: component.peerId, id: component.storyItem.id)]) } } } @@ -1352,11 +1351,11 @@ public final class StoryItemSetViewListComponent: Component { return super.hitTest(point, with: event) } - public func setPreviewedItem(signal: Signal) { + public func setPreviewedItem(signal: Signal) { self.currentContentView?.setPreviewedItem(signal: signal) } - public func sourceView(storyId: StoryId) -> UIView? { + public func sourceView(storyId: EngineStoryId) -> UIView? { self.currentContentView?.sourceView(storyId: storyId) } @@ -1505,11 +1504,11 @@ public final class StoryItemSetViewListComponent: Component { if let views = component.storyItem.views { let viewList: EngineStoryViewListContext - if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] { + if let current = component.sharedListsContext.viewLists[EngineStoryId(peerId: component.peerId, id: component.storyItem.id)] { viewList = current } else { viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst) - component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList + component.sharedListsContext.viewLists[EngineStoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList } self.mainViewList = viewList self.mainViewListDisposable = (viewList.state diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index 3b0074726e..0b19bc56fc 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -6,7 +6,6 @@ import AppBundle import BundleIconComponent import AccountContext import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import StoryContainerScreen @@ -377,6 +376,7 @@ public final class StoryPeerListComponent: Component { self.scrollView.alwaysBounceVertical = false self.scrollView.alwaysBounceHorizontal = true self.scrollView.clipsToBounds = false + self.scrollView.scrollsToTop = false self.scrollContainerView = UIView() self.scrollContainerView.clipsToBounds = true @@ -443,7 +443,7 @@ public final class StoryPeerListComponent: Component { } } - public func setPreviewedItem(signal: Signal) { + public func setPreviewedItem(signal: Signal) { self.previewedItemDisposable?.dispose() self.previewedItemDisposable = (signal |> map(\.?.peerId) |> distinctUntilChanged |> deliverOnMainQueue).start(next: { [weak self] itemId in guard let self, let component = self.component else { diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift index cc012a79f3..99d913e031 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListItemComponent.swift @@ -965,7 +965,7 @@ public final class StoryPeerListItemComponent: Component { var titleTransition = transition if let previousAnimation = previousComponent?.ringAnimation, case .progress = previousAnimation, component.ringAnimation == nil { if let titleView = self.title.view, let snapshotView = titleView.snapshotView(afterScreenUpdates: false) { - self.button.addSubview(snapshotView) + self.titleContainer.addSubview(snapshotView) snapshotView.frame = titleView.frame snapshotView.layer.animateAlpha(from: component.expandedAlphaFraction, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() diff --git a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD index 49b18603a1..2ec4c22c32 100644 --- a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AvatarNode", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/PhotoResources", "//submodules/AccountContext", diff --git a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift index 2ff507bc33..29f49ec090 100644 --- a/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StorySetIndicatorComponent/Sources/StorySetIndicatorComponent.swift @@ -4,7 +4,6 @@ import Display import ComponentFlow import TelegramPresentationData import TelegramCore -import Postbox import SwiftSignalKit import AccountContext import PhotoResources @@ -183,7 +182,7 @@ public final class StorySetIndicatorComponent: Component { init(context: AccountContext, item: StorySetIndicatorComponent.Item, displayAvatars: Bool, updated: @escaping () -> Void) { self.updated = updated - let peerReference = PeerReference(item.peer._asPeer()) + let peerReference = PeerReference(item.peer) var messageMedia: EngineMedia? switch item.storyItem.media { @@ -204,7 +203,7 @@ public final class StorySetIndicatorComponent: Component { var fetchSignal: Signal? if displayAvatars { - imageSignal = peerAvatarCompleteImage(postbox: context.account.postbox, network: context.account.network, peer: item.peer, forceProvidedRepresentation: false, representation: nil, size: CGSize(width: 26.0, height: 26.0), round: true, font: avatarPlaceholderFont(size: 13.0), drawLetters: true, fullSize: false, blurred: false) + imageSignal = peerAvatarCompleteImage(postbox: context.account.postbox, peer: item.peer, forceProvidedRepresentation: false, representation: nil, size: CGSize(width: 26.0, height: 26.0), round: true, font: avatarPlaceholderFont(size: 13.0), drawLetters: true, fullSize: false, blurred: false) } else { switch messageMedia { case let .image(image): diff --git a/submodules/SvgRendering/BUILD b/submodules/TelegramUI/Components/StreamingTextReveal/BUILD similarity index 60% rename from submodules/SvgRendering/BUILD rename to submodules/TelegramUI/Components/StreamingTextReveal/BUILD index 20e1b4b487..6318cdd2e7 100644 --- a/submodules/SvgRendering/BUILD +++ b/submodules/TelegramUI/Components/StreamingTextReveal/BUILD @@ -1,13 +1,15 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") swift_library( - name = "SvgRendering", - module_name = "SvgRendering", + name = "StreamingTextReveal", + module_name = "StreamingTextReveal", srcs = glob([ "Sources/**/*.swift", ]), - deps = [ + copts = [ + "-warnings-as-errors", ], + deps = [], visibility = [ "//visibility:public", ], diff --git a/submodules/TelegramUI/Components/StreamingTextReveal/Sources/TextRevealController.swift b/submodules/TelegramUI/Components/StreamingTextReveal/Sources/TextRevealController.swift new file mode 100644 index 0000000000..47e0a38908 --- /dev/null +++ b/submodules/TelegramUI/Components/StreamingTextReveal/Sources/TextRevealController.swift @@ -0,0 +1,90 @@ +import Foundation + +public final class TextRevealController { + private static let velocityTau: Double = 0.12 + private static let gapEwmaAlpha: Double = 0.4 + private static let initialGap: Double = 0.5 + private static let stallFloor: Double = 0.10 + private static let finalizeTime: Double = 0.3 + private static let frameDtCap: Double = 0.05 + private static let initialInputRate: Double = 40.0 + + public private(set) var revealedCount: Double + private var velocity: Double = 0.0 + private var avgInterArrival: Double = TextRevealController.initialGap + private var lastSampleTime: Double? + private var lastSampleLength: Int? + private var predictedNextArrivalTime: Double? + private var chunkCount: Int = 0 + public private(set) var latestLength: Int + public private(set) var isFinalizing: Bool = false + private var lastFrameTime: Double? + public var durationMultiplier: Double + + public init(initialRevealedCount: Int, initialLength: Int, durationMultiplier: Double = 1.0) { + self.revealedCount = Double(initialRevealedCount) + self.latestLength = initialLength + self.durationMultiplier = max(0.0001, durationMultiplier) + } + + public var currentGlyphCount: Int { + return Int(self.revealedCount) + } + + public func observeUpdate(latestLength: Int, at now: Double) { + if let lastLen = self.lastSampleLength { + if latestLength > lastLen { + if let lastTime = self.lastSampleTime { + let interArrival = max(now - lastTime, 0.001) + self.avgInterArrival = TextRevealController.gapEwmaAlpha * interArrival + + (1.0 - TextRevealController.gapEwmaAlpha) * self.avgInterArrival + } + self.lastSampleTime = now + self.lastSampleLength = latestLength + self.predictedNextArrivalTime = now + self.avgInterArrival + self.chunkCount += 1 + } else if latestLength < lastLen { + self.lastSampleLength = latestLength + } + } else { + self.lastSampleTime = now + self.lastSampleLength = latestLength + self.predictedNextArrivalTime = now + self.avgInterArrival + self.chunkCount += 1 + } + self.latestLength = latestLength + if self.revealedCount > Double(latestLength) { + self.revealedCount = Double(latestLength) + } + } + + public func finalize(finalLength: Int) { + self.latestLength = finalLength + self.isFinalizing = true + if self.revealedCount > Double(finalLength) { + self.revealedCount = Double(finalLength) + } + } + + public func tick(now: Double) -> (revealedGlyphCount: Int, isComplete: Bool) { + let dt = min(now - (self.lastFrameTime ?? now), TextRevealController.frameDtCap) + let lag = max(0.0, Double(self.latestLength) - self.revealedCount) + let targetVelocity: Double + if self.isFinalizing { + targetVelocity = max(self.velocity, lag / TextRevealController.finalizeTime) + } else if self.chunkCount < 2 { + targetVelocity = lag > 0.0 ? TextRevealController.initialInputRate : 0.0 + } else if let predNext = self.predictedNextArrivalTime { + let timeToNext = max(TextRevealController.stallFloor, predNext - now) + targetVelocity = lag / timeToNext + } else { + targetVelocity = lag > 0.0 ? TextRevealController.initialInputRate : 0.0 + } + let smoothing = min(1.0, dt / TextRevealController.velocityTau) + self.velocity += (targetVelocity - self.velocity) * smoothing + self.revealedCount = min(Double(self.latestLength), self.revealedCount + self.velocity * dt / self.durationMultiplier) + self.lastFrameTime = now + let isComplete = self.isFinalizing && self.revealedCount >= Double(self.latestLength) + return (Int(self.revealedCount), isComplete) + } +} diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift index 0ebf550a13..d2ad61f9a6 100644 --- a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -791,7 +791,11 @@ public final class TabBarComponent: Component { var itemFrame = CGRect(origin: CGPoint(x: nextItemX, y: floor((tabsSize.height - itemSize.height) * 0.5)), size: itemSize) nextItemX += itemSize.width if isItemSelected { - selectionFrame = itemFrame + if itemFrame.size.width < itemFrame.size.height { + selectionFrame = itemFrame.insetBy(dx: floor((itemFrame.size.height * 1.2 - itemFrame.size.width) * -0.5), dy: 0.0) + } else { + selectionFrame = itemFrame + } } if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View { diff --git a/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/Sources/TelegramAccountAuxiliaryMethods.swift index 3f6203887f..976b942ba6 100644 --- a/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods/Sources/TelegramAccountAuxiliaryMethods.swift @@ -46,7 +46,7 @@ public func makeTelegramAccountAuxiliaryMethods(uploadInBackground: ((Postbox, M return fetchLocalFileVideoMediaResource(postbox: postbox, resource: resource, alwaysUseModernPipeline: useModernPipeline) } } else if let resource = resource as? LocalFileAudioMediaResource { - return fetchLocalFileAudioMediaResource(postbox: postbox, resource: resource) + return fetchLocalFileAudioMediaResource(resource: resource) } else if let resource = resource as? LocalFileGifMediaResource { return fetchLocalFileGifMediaResource(resource: resource) } else if let photoLibraryResource = resource as? PhotoLibraryMediaResource { diff --git a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD index 5faf3562fe..233f4b8e1a 100644 --- a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD +++ b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/BUILD @@ -10,14 +10,13 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", + "//submodules/TelegramCore", "//submodules/TemporaryCachedPeerDataManager", "//submodules/TelegramUIPreferences", "//submodules/TelegramNotices", "//submodules/InstantPageUI", "//submodules/AccountContext", "//submodules/LocalMediaResources", - "//submodules/WebSearchUI", "//submodules/InstantPageCache", "//submodules/SettingsUI", "//submodules/WallpaperResources", diff --git a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/Sources/TelegramUIDeclareEncodables.swift b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/Sources/TelegramUIDeclareEncodables.swift index 461dc1999b..9e0b0cb81f 100644 --- a/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/Sources/TelegramUIDeclareEncodables.swift +++ b/submodules/TelegramUI/Components/TelegramUIDeclareEncodables/Sources/TelegramUIDeclareEncodables.swift @@ -1,11 +1,10 @@ -import Postbox +import TelegramCore import TemporaryCachedPeerDataManager import TelegramUIPreferences import TelegramNotices import InstantPageUI import AccountContext import LocalMediaResources -import WebSearchUI import InstantPageCache import SettingsUI import WallpaperResources @@ -15,12 +14,12 @@ import ChatInterfaceState import ICloudResources private var telegramUIDeclaredEncodables: Void = { - declareEncodable(VideoLibraryMediaResource.self, f: { VideoLibraryMediaResource(decoder: $0) }) - declareEncodable(LocalFileVideoMediaResource.self, f: { LocalFileVideoMediaResource(decoder: $0) }) - declareEncodable(LocalFileAudioMediaResource.self, f: { LocalFileAudioMediaResource(decoder: $0) }) - declareEncodable(LocalFileGifMediaResource.self, f: { LocalFileGifMediaResource(decoder: $0) }) - declareEncodable(PhotoLibraryMediaResource.self, f: { PhotoLibraryMediaResource(decoder: $0) }) - declareEncodable(ICloudFileResource.self, f: { ICloudFileResource(decoder: $0) }) + engineDeclareEncodable(VideoLibraryMediaResource.self, f: { VideoLibraryMediaResource(decoder: $0) }) + engineDeclareEncodable(LocalFileVideoMediaResource.self, f: { LocalFileVideoMediaResource(decoder: $0) }) + engineDeclareEncodable(LocalFileAudioMediaResource.self, f: { LocalFileAudioMediaResource(decoder: $0) }) + engineDeclareEncodable(LocalFileGifMediaResource.self, f: { LocalFileGifMediaResource(decoder: $0) }) + engineDeclareEncodable(PhotoLibraryMediaResource.self, f: { PhotoLibraryMediaResource(decoder: $0) }) + engineDeclareEncodable(ICloudFileResource.self, f: { ICloudFileResource(decoder: $0) }) return }() diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 5bb7677ff5..591b0f708c 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -1035,7 +1035,7 @@ public final class TextFieldComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme) let updatedPresentationData: (initial: PresentationData, signal: Signal) = (presentationData, .single(presentationData)) - let controller = component.context.sharedContext.makeLinkEditController(context: component.context, updatedPresentationData: updatedPresentationData, text: text.string, link: link, apply: { [weak self] link in + let controller = component.context.sharedContext.makeLinkEditController(context: component.context, updatedPresentationData: updatedPresentationData, text: text.string, link: link, apply: { [weak self] link, _ in if let self { if let link { if !link.isEmpty { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD index 547c1c3acc..01ac090ee0 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/BUILD +++ b/submodules/TelegramUI/Components/TextProcessingScreen/BUILD @@ -38,6 +38,9 @@ swift_library( "//submodules/TelegramUI/Components/ToastComponent", "//submodules/TelegramUI/Components/InteractiveTextComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent", + "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/TelegramNotices", "//submodules/Markdown", "//submodules/TelegramUIPreferences", @@ -45,6 +48,7 @@ swift_library( "//submodules/TextSelectionNode", "//submodules/Pasteboard", "//submodules/Speak", + "//submodules/ContextUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift index 24243a125e..76e631c9b8 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingLanguageSelectionComponent.swift @@ -32,7 +32,7 @@ final class TextProcessingLanguageSelectionComponent: Component { let selectedLanguageCode: String let currentStyle: TelegramComposeAIMessageMode.StyleId let displayStyles: [TextProcessingScreen.Style]? - let completion: (String, TelegramComposeAIMessageMode.StyleId) -> Void + let completion: (String, TelegramComposeAIMessageMode.StyleReference) -> Void let dismissed: () -> Void let inputHeight: CGFloat @@ -45,7 +45,7 @@ final class TextProcessingLanguageSelectionComponent: Component { selectedLanguageCode: String, currentStyle: TelegramComposeAIMessageMode.StyleId, displayStyles: [TextProcessingScreen.Style]?, - completion: @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void, + completion: @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void, dismissed: @escaping () -> Void, inputHeight: CGFloat ) { @@ -271,7 +271,18 @@ final class TextProcessingLanguageSelectionComponent: Component { } if case .ended = recognizer.state { if component.displayStyles != nil, let updatedStyle = self.updatedStyle { - component.completion(component.selectedLanguageCode, updatedStyle) + let mappedStyle: TelegramComposeAIMessageMode.StyleReference + switch updatedStyle { + case .neutral: + mappedStyle = .neutral + case let .style(style): + if let styleValue = component.displayStyles?.first(where: { $0.reference.id == style }) { + mappedStyle = .style(styleValue.reference) + } else { + return + } + } + component.completion(component.selectedLanguageCode, mappedStyle) } self.animateOut() } @@ -298,7 +309,19 @@ final class TextProcessingLanguageSelectionComponent: Component { return } if self.updatedLanguage != nil || self.updatedStyle != nil { - component.completion(self.updatedLanguage ?? component.selectedLanguageCode, self.updatedStyle ?? component.currentStyle) + let styleId = self.updatedStyle ?? component.currentStyle + let mappedStyle: TelegramComposeAIMessageMode.StyleReference + switch styleId { + case .neutral: + mappedStyle = .neutral + case let .style(style): + if let styleValue = component.displayStyles?.first(where: { $0.reference.id == style }) { + mappedStyle = .style(styleValue.reference) + } else { + return + } + } + component.completion(self.updatedLanguage ?? component.selectedLanguageCode, mappedStyle) } self.animateOut() } @@ -469,7 +492,7 @@ final class TextProcessingLanguageSelectionComponent: Component { var styleData: [(id: TelegramComposeAIMessageMode.StyleId, iconFileId: Int64?, iconFile: TelegramMediaFile?, title: String)] = [] styleData.append((.neutral, nil, nil, component.strings.TextProcessingStyle_Neutral)) for item in displayStyles { - styleData.append((item.id, item.emojiFileId, item.emojiFile, item.title)) + styleData.append((.style(item.reference.id), item.emojiFileId, item.emojiFile, item.title)) } let stylesItemSize = CGSize(width: 82.0, height: 60.0) diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift index c078d33c57..9b0017373f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingScreen.swift @@ -24,6 +24,8 @@ import TelegramNotices import Markdown import TelegramUIPreferences import ChatSendMessageActionUI +import ContextUI +import EmojiStatusComponent final class TextProcessingContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -40,6 +42,7 @@ final class TextProcessingContentComponent: Component { let externalState: ExternalState let context: AccountContext let mode: TextProcessingScreen.Mode + let previewIconFile: TelegramMediaFile? let styles: [TextProcessingScreen.Style] let inputText: TextWithEntities let initialEditState: TextProcessingScreen.EditState? @@ -47,12 +50,18 @@ final class TextProcessingContentComponent: Component { let shouldDisplayStyleNotice: Bool let copyCurrentResult: (() -> Void)? let translateChat: ((String) -> Void)? - let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void) -> Void + let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void) -> Void + let newStyleAdded: (TelegramComposeAIMessageMode.CloudStyle) -> Void + let styleUpdated: (TelegramComposeAIMessageMode.CloudStyle) -> Void + let styleDeleted: (TelegramComposeAIMessageMode.StyleId) -> Void + let displayToast: (String) -> Void + let dismiss: (@escaping () -> Void) -> Void init( externalState: ExternalState, context: AccountContext, mode: TextProcessingScreen.Mode, + previewIconFile: TelegramMediaFile?, styles: [TextProcessingScreen.Style], inputText: TextWithEntities, initialEditState: TextProcessingScreen.EditState?, @@ -60,12 +69,18 @@ final class TextProcessingContentComponent: Component { shouldDisplayStyleNotice: Bool, copyCurrentResult: (() -> Void)?, translateChat: ((String) -> Void)?, - displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void) -> Void + displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void) -> Void, + newStyleAdded: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void, + styleUpdated: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void, + styleDeleted: @escaping (TelegramComposeAIMessageMode.StyleId) -> Void, + displayToast: @escaping (String) -> Void, + dismiss: @escaping (@escaping () -> Void) -> Void ) { self.externalState = externalState self.styles = styles self.context = context self.mode = mode + self.previewIconFile = previewIconFile self.inputText = inputText self.initialEditState = initialEditState self.ignoredTranslationLanguages = ignoredTranslationLanguages @@ -73,9 +88,17 @@ final class TextProcessingContentComponent: Component { self.copyCurrentResult = copyCurrentResult self.translateChat = translateChat self.displayLanguageSelectionMenu = displayLanguageSelectionMenu + self.newStyleAdded = newStyleAdded + self.styleUpdated = styleUpdated + self.styleDeleted = styleDeleted + self.displayToast = displayToast + self.dismiss = dismiss } static func ==(lhs: TextProcessingContentComponent, rhs: TextProcessingContentComponent) -> Bool { + if lhs.styles != rhs.styles { + return false + } return true } @@ -94,8 +117,14 @@ final class TextProcessingContentComponent: Component { private let modeTabs = ComponentView() private let actionsSection = ComponentView() + private var previewIcon: ComponentView? + private var previewTitle: ComponentView? + private var previewDescription: ComponentView? + private let currentContentBackground: UIImageView private let currentContentContainer: UIView + private var currentContentHeader: (id: AnyHashable, view: ComponentView)? + private var currentContentFooter: (id: AnyHashable, view: ComponentView)? private let translateState = TextProcessingTranslateContentComponent.ExternalState() private let stylizeState = TextProcessingTranslateContentComponent.ExternalState() @@ -105,6 +134,10 @@ final class TextProcessingContentComponent: Component { private var currentMode: Mode = .translate + private var isRequestingStylePreview: Bool = false + private var currentStylePreview: AIMessageStylePreview? + private var currentStylePreviewDisposable: Disposable? + override init(frame: CGRect) { self.currentContentBackground = UIImageView() self.currentContentContainer = UIView() @@ -148,6 +181,10 @@ final class TextProcessingContentComponent: Component { preconditionFailure() } + deinit { + self.currentStylePreviewDisposable?.dispose() + } + private func externalStatesUpdated() { guard let component = self.component else { return @@ -195,6 +232,235 @@ final class TextProcessingContentComponent: Component { }) } } + + private func requestShareStyle(id: TelegramComposeAIMessageMode.StyleReference) { + guard let component = self.component else { + return + } + guard let style = component.styles.first(where: { .style($0.reference.id) == id.id }) else { + return + } + guard let slug = style.slug else { + return + } + let shareController = component.context.sharedContext.makeShareController(context: component.context, params: ShareControllerParams( + subject: .url("https://t.me/addstyle/\(slug)"), + completed: { [weak self] peerIds in + Task { @MainActor in + guard let self, let component = self.component, let environment = self.environment, let peerId = peerIds.first else { + return + } + let text: String + if peerId == component.context.account.peerId { + text = environment.strings.WebBrowser_LinkForwardTooltip_SavedMessages_One + } else { + guard let peer = await component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)).get() else { + return + } + text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string + } + component.displayToast(text) + } + } + )) + self.environment?.controller()?.present(shareController, in: .window(.root)) + } + + private func requestEditStyle(id: TelegramComposeAIMessageMode.StyleReference) { + Task { @MainActor [weak self] in + guard let self else { + return + } + guard let component = self.component, let environment = self.environment else { + return + } + guard let style = component.styles.first(where: { .style($0.reference.id) == id.id }) else { + return + } + environment.controller()?.push(await TextStyleEditScreen( + context: component.context, + mode: .edit(style.cloudStyle), + completion: { [weak self] style in + guard let self, let component = self.component else { + return + } + component.styleUpdated(style) + }, + styleDeleted: { [weak self] in + guard let self, let component = self.component else { + return + } + component.styleDeleted(style.id.id) + } + )) + } + } + + private func requestDeleteStyle(id: TelegramComposeAIMessageMode.StyleReference) { + guard let component = self.component, let environment = self.environment else { + return + } + guard let style = component.styles.first(where: { .style($0.reference.id) == id.id }) else { + return + } + guard case let .custom(style) = style.cloudStyle.content else { + return + } + + if style.isCreator { + environment.controller()?.push(textAlertController( + context: component.context, + title: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Text, + actions: [ + TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in + guard let self, let component = self.component else { + return + } + let _ = component.context.engine.messages.deleteAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone() + component.styleDeleted(id.id) + }), + ] + )) + } else { + environment.controller()?.push(textAlertController( + context: component.context, + title: environment.strings.TextProcessing_AlertDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertDeleteStyle_Text, + actions: [ + TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in + guard let self, let component = self.component else { + return + } + let _ = component.context.engine.messages.unsaveAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone() + component.styleDeleted(id.id) + }), + ] + )) + } + } + + private func openStyleContextMenu(id: TelegramComposeAIMessageMode.StyleReference, gesture: ContextGesture, sourceView: ContextExtractedContentContainingView) { + guard let component = self.component, let environment = self.environment else { + return + } + + guard let style = component.styles.first(where: { .style($0.reference.id) == id.id }) else { + return + } + + var items: [ContextMenuItem] = [] + if style.isAuthor { + items.append(.action(ContextMenuActionItem( + text: environment.strings.TextProcessing_StyleMenu_Edit, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, + action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.requestEditStyle(id: id) + }) + }) + )) + } + items.append(.action(ContextMenuActionItem( + text: environment.strings.TextProcessing_StyleMenu_Share, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) + }, + action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.requestShareStyle(id: id) + }) + }) + )) + items.append(.action(ContextMenuActionItem( + text: environment.strings.TextProcessing_StyleMenu_Delete, + textColor: .destructive, + icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, + action: { [weak self] c, _ in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + self.requestDeleteStyle(id: id) + }) + }) + )) + + final class ContextExtractedContentSourceImpl: ContextExtractedContentSource { + let keepInPlace: Bool = false + let ignoreContentTouches: Bool = false + let blurBackground: Bool = false + let actionsHorizontalAlignment: ContextActionsHorizontalAlignment = .center + + private let contentView: ContextExtractedContentContainingView + + init(contentView: ContextExtractedContentContainingView) { + self.contentView = contentView + } + + func takeView() -> ContextControllerTakeViewInfo? { + return ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: UIScreen.main.bounds) + } + + func putBack() -> ContextControllerPutBackViewInfo? { + return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) + } + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + let controller = makeContextController( + presentationData: presentationData, + source: .extracted(ContextExtractedContentSourceImpl(contentView: sourceView)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture + ) + environment.controller()?.presentInGlobalOverlay(controller) + } + + private func requestStylePreview() { + guard let component = self.component else { + return + } + guard case let .preview(style, _, _, _, _) = component.mode else { + return + } + + var index = 0 + if let currentStylePreview = self.currentStylePreview, let currentIndex = currentStylePreview.index { + var maxPreviewCount = 3 + if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["aicompose_tone_examples_num"] as? Double { + maxPreviewCount = max(1, Int(value)) + } + index = (currentIndex + 1) % maxPreviewCount + } + + self.isRequestingStylePreview = true + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + self.currentStylePreviewDisposable?.dispose() + self.currentStylePreviewDisposable = (component.context.engine.messages.requestAIMessageStylePreview(reference: TelegramComposeAIMessageMode.CloudStyle(content: .custom(style)).reference, index: index) |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self, let result else { + return + } + self.isRequestingStylePreview = false + self.currentStylePreview = result + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + } func update(component: TextProcessingContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true @@ -205,25 +471,134 @@ final class TextProcessingContentComponent: Component { let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) let environment = environment[ViewControllerComponentContainer.Environment.self].value + let theme = environment.theme.withModalBlocksBackground() - if self.component == nil { - self.stylizeState.displayStyleTooltip = component.shouldDisplayStyleNotice - switch component.mode { - case .edit: - self.currentMode = .stylize - case .translate: - self.currentMode = .translate - } - } + let isFirstTime = self.component == nil self.component = component self.environment = environment self.state = state + if isFirstTime { + self.stylizeState.displayStyleTooltip = component.shouldDisplayStyleNotice + switch component.mode { + case .edit: + self.currentMode = .stylize + case let .preview(_, _, initialPreview, _, _): + self.currentMode = .stylize + self.currentStylePreview = initialPreview + self.requestStylePreview() + case .translate: + self.currentMode = .translate + } + } + let sideInset: CGFloat = 16.0 var contentHeight: CGFloat = 0.0 - contentHeight += 82.0 + + if case let .preview(style, _, _, _, _) = component.mode { + contentHeight += 40.0 + if let previewIconFile = component.previewIconFile { + let previewIcon: ComponentView + if let current = self.previewIcon { + previewIcon = current + } else { + previewIcon = ComponentView() + self.previewIcon = previewIcon + } + let previewIconSize = CGSize(width: 60.0, height: 60.0) + let _ = previewIcon.update( + transition: transition, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: .animation( + content: .file(file: previewIconFile), + size: previewIconSize, + placeholderColor: theme.list.mediaPlaceholderColor, + themeColor: theme.list.itemPrimaryTextColor, + loopMode: .count(1) + ), + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: previewIconSize + ) + let previewIconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - previewIconSize.width) * 0.5), y: contentHeight), size: previewIconSize) + if let previewIconView = previewIcon.view { + if previewIconView.superview == nil { + self.addSubview(previewIconView) + previewIconView.isUserInteractionEnabled = false + } + transition.setFrame(view: previewIconView, frame: previewIconFrame) + } + contentHeight += previewIconSize.height + contentHeight += 10.0 + } + + let previewTitle: ComponentView + if let current = self.previewTitle { + previewTitle = current + } else { + previewTitle = ComponentView() + self.previewTitle = previewTitle + } + + let previewDescription: ComponentView + if let current = self.previewDescription { + previewDescription = current + } else { + previewDescription = ComponentView() + self.previewDescription = previewDescription + } + + let titleSize = previewTitle.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: style.title, font: Font.bold(30.0), textColor: theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: contentHeight), size: titleSize) + if let previewTitleView = previewTitle.view { + if previewTitleView.superview == nil { + previewTitleView.isUserInteractionEnabled = false + self.addSubview(previewTitleView) + } + transition.setPosition(view: previewTitleView, position: titleFrame.center) + previewTitleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + } + contentHeight += titleSize.height + 5.0 + + let descriptionSize = previewDescription.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.TextProcessing_StylePreview_Subtitle, font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor)), + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.12 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let descriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionSize.width) * 0.5), y: contentHeight), size: descriptionSize) + if let previewDescriptionView = previewDescription.view { + if previewDescriptionView.superview == nil { + previewDescriptionView.isUserInteractionEnabled = false + self.addSubview(previewDescriptionView) + } + transition.setPosition(view: previewDescriptionView, position: descriptionFrame.center) + previewDescriptionView.bounds = CGRect(origin: CGPoint(), size: descriptionFrame.size) + } + contentHeight += descriptionSize.height + contentHeight += 30.0 + } else { + contentHeight += 82.0 + } switch component.mode { case .edit: @@ -309,7 +684,7 @@ final class TextProcessingContentComponent: Component { let modeTabsSize = self.modeTabs.update( transition: transition, component: AnyComponent(TabBarComponent( - theme: environment.theme, + theme: theme, tintSelectedItem: false, isLiftedStateEnabled: false, strings: environment.strings, @@ -331,7 +706,7 @@ final class TextProcessingContentComponent: Component { } contentHeight += modeTabsSize.height contentHeight += 24.0 - case .translate: + case .translate, .preview: break } @@ -344,12 +719,14 @@ final class TextProcessingContentComponent: Component { self.currentContent = nil } + let contentExternalState: TextProcessingTranslateContentComponent.ExternalState let contentComponent: AnyComponent switch self.currentMode { case .translate: + contentExternalState = self.translateState contentComponent = AnyComponent(TextProcessingTranslateContentComponent( context: component.context, - theme: environment.theme, + theme: theme, strings: environment.strings, styles: component.styles, externalState: self.translateState, @@ -357,35 +734,166 @@ final class TextProcessingContentComponent: Component { mode: .translate(ignoredLanguages: component.ignoredTranslationLanguages), copyAction: component.copyCurrentResult, displayLanguageSelectionMenu: component.displayLanguageSelectionMenu, + createStyle: { + }, + openStyleContextMenu: { _, _, _ in + }, present: { [weak self] c, a in self?.environment?.controller()?.present(c, in: .window(.root), with: a) }, rootViewForTextSelection: { [weak self] in return self?.environment?.controller()?.view + }, + openPeer: { _ in + }, + requestAnotherPreviewExample: { } )) case .stylize: + var inputText = component.inputText + var isPreview = false + var fromText: TextWithEntities? + var toText: TextWithEntities? + var isRequestingPreview: Bool = false + var authorPeer: EnginePeer? + var userCount: Int = 0 + if case let .preview(style, authorPeerValue, _, _, _) = component.mode { + isPreview = true + inputText = TextWithEntities(text: "", entities: []) + authorPeer = authorPeerValue + userCount = style.userCount ?? 0 + isRequestingPreview = self.isRequestingStylePreview + if let currentStylePreview = self.currentStylePreview { + fromText = currentStylePreview.from + toText = currentStylePreview.to + } + } + + contentExternalState = self.stylizeState contentComponent = AnyComponent(TextProcessingTranslateContentComponent( context: component.context, - theme: environment.theme, + theme: theme, strings: environment.strings, styles: component.styles, externalState: self.stylizeState, - inputText: component.inputText, - mode: .stylize, + inputText: inputText, + mode: isPreview ? .preview(from: fromText, to: toText, authorPeer: authorPeer, userCount: userCount, isRequesting: isRequestingPreview) : .stylize, copyAction: component.copyCurrentResult, displayLanguageSelectionMenu: component.displayLanguageSelectionMenu, + createStyle: { [weak self] in + Task { @MainActor in + guard let self else { + return + } + guard let component = self.component, let environment = self.environment else { + return + } + + let hasPremium = await (component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId) + ) + |> map { peer -> Bool in + guard case let .user(user) = peer else { + return false + } + return user.isPremium + }).get() + let userLimits = await component.context.engine.data.get( + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: hasPremium) + ).get() + + let maxStyles = Int(userLimits.maxOwnedAITextStyles) + + if component.styles.filter({ $0.isAuthor }).count >= maxStyles { + if !hasPremium { + let context = component.context + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .aiTools, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesStealthMode, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak self, weak controller] c in + controller?.dismiss(animated: true, completion: { + guard let self else { + return + } + self.environment?.controller()?.push(c) + }) + } + environment.controller()?.push(controller) + } else { + environment.controller()?.push(textAlertController( + context: component.context, + title: environment.strings.TextProcessing_AlertTooManyStyles_Title, + text: environment.strings.TextProcessing_AlertTooManyStyles_Text, + actions: [ + TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {}), + ] + )) + } + return + } + + environment.controller()?.push(await TextStyleEditScreen( + context: component.context, + mode: .create, + completion: { [weak self] style in + guard let self, let component = self.component else { + return + } + + if let contentComponentView = self.currentContent?.view.view as? TextProcessingTranslateContentComponent.View { + contentComponentView.scrollStylesToStart() + } + + component.newStyleAdded(style) + }, + styleDeleted: { + } + )) + } + }, + openStyleContextMenu: { [weak self] styleId, gesture, sourceView in + guard let self else { + return + } + self.openStyleContextMenu(id: styleId, gesture: gesture, sourceView: sourceView) + }, present: { [weak self] c, a in self?.environment?.controller()?.present(c, in: .window(.root), with: a) }, rootViewForTextSelection: { [weak self] in return self?.environment?.controller()?.view + }, + openPeer: { [weak self] peer in + guard let self, let component = self.component, let environment = self.environment else { + return + } + guard let navigationController = environment.controller()?.navigationController as? NavigationController else { + return + } + let context = component.context + component.dismiss({ [weak context, weak navigationController] in + guard let context, let navigationController else { + return + } + if let peerInfoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + navigationController.pushViewController(peerInfoController) + } + }) + }, + requestAnotherPreviewExample: { [weak self] in + guard let self else { + return + } + self.requestStylePreview() } )) case .fix: + contentExternalState = self.fixState contentComponent = AnyComponent(TextProcessingTranslateContentComponent( context: component.context, - theme: environment.theme, + theme: theme, strings: environment.strings, styles: component.styles, externalState: self.fixState, @@ -393,15 +901,72 @@ final class TextProcessingContentComponent: Component { mode: .fix, copyAction: component.copyCurrentResult, displayLanguageSelectionMenu: component.displayLanguageSelectionMenu, + createStyle: { + }, + openStyleContextMenu: { _, _, _ in + }, present: { [weak self] c, a in self?.environment?.controller()?.present(c, in: .window(.root), with: a) }, rootViewForTextSelection: { [weak self] in return self?.environment?.controller()?.view + }, + openPeer: { _ in + }, + requestAnotherPreviewExample: { } )) } + if let sectionHeader = contentExternalState.sectionHeader { + let headerView: ComponentView + var headerTransition = transition + if let current = self.currentContentHeader, current.id == sectionHeader.id { + headerView = current.view + } else { + headerTransition = headerTransition.withAnimation(.none) + + if let currentContentHeader = self.currentContentHeader { + self.currentContentHeader = nil + if let componentView = currentContentHeader.view.view { + alphaTransition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in + componentView?.removeFromSuperview() + }) + } + } + + headerView = ComponentView() + self.currentContentHeader = (sectionHeader.id, headerView) + } + let headerSideInset: CGFloat = sideInset + 16.0 + let headerSize = headerView.update( + transition: .immediate, + component: sectionHeader.component, + environment: {}, + containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: 10000.0) + ) + let headerFrame = CGRect(origin: CGPoint(x: headerSideInset, y: contentHeight), size: headerSize) + if let headerComponentView = headerView.view { + if headerComponentView.superview == nil { + headerComponentView.layer.anchorPoint = CGPoint() + self.addSubview(headerComponentView) + alphaTransition.animateAlpha(view: headerComponentView, from: 0.0, to: 1.0) + } + headerTransition.setPosition(view: headerComponentView, position: headerFrame.origin) + headerComponentView.bounds = CGRect(origin: CGPoint(), size: headerFrame.size) + } + contentHeight += headerSize.height + 7.0 + } else { + if let currentContentHeader = self.currentContentHeader { + self.currentContentHeader = nil + if let componentView = currentContentHeader.view.view { + alphaTransition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in + componentView?.removeFromSuperview() + }) + } + } + } + let content: ComponentView var contentTransition = transition if let current = self.currentContent { @@ -433,28 +998,79 @@ final class TextProcessingContentComponent: Component { if self.currentContentBackground.image == nil { self.currentContentBackground.image = generateStretchableFilledCircleImage(diameter: 60.0, color: .white)?.withRenderingMode(.alwaysTemplate) } - self.currentContentBackground.tintColor = environment.theme.list.itemBlocksBackgroundColor + self.currentContentBackground.tintColor = theme.list.itemBlocksBackgroundColor transition.setFrame(view: self.currentContentBackground, frame: contentFrame) - contentHeight += contentSize.height + if let sectionFooter = contentExternalState.sectionFooter { + let footerView: ComponentView + var footerTransition = transition + if let current = self.currentContentFooter, current.id == sectionFooter.id { + footerView = current.view + } else { + footerTransition = footerTransition.withAnimation(.none) + + if let currentContentFooter = self.currentContentFooter { + self.currentContentFooter = nil + if let componentView = currentContentFooter.view.view { + alphaTransition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in + componentView?.removeFromSuperview() + }) + } + } + + footerView = ComponentView() + self.currentContentFooter = (sectionFooter.id, footerView) + } + let headerSideInset: CGFloat = sideInset + 16.0 + let footerSize = footerView.update( + transition: .immediate, + component: sectionFooter.component, + environment: {}, + containerSize: CGSize(width: availableSize.width - headerSideInset * 2.0, height: 10000.0) + ) + if contentHeight != 0.0 { + contentHeight += 8.0 - UIScreenPixel + } + let footerFrame = CGRect(origin: CGPoint(x: headerSideInset, y: contentHeight), size: footerSize) + if let footerComponentView = footerView.view { + if footerComponentView.superview == nil { + footerComponentView.layer.anchorPoint = CGPoint() + self.addSubview(footerComponentView) + alphaTransition.animateAlpha(view: footerComponentView, from: 0.0, to: 1.0) + } + footerTransition.setPosition(view: footerComponentView, position: footerFrame.origin) + footerComponentView.bounds = CGRect(origin: CGPoint(), size: footerFrame.size) + } + contentHeight += footerSize.height + } else { + if let currentContentFooter = self.currentContentFooter { + self.currentContentFooter = nil + if let componentView = currentContentFooter.view.view { + alphaTransition.setAlpha(view: componentView, alpha: 0.0, completion: { [weak componentView] _ in + componentView?.removeFromSuperview() + }) + } + } + } + var actionsSectionItems: [AnyComponentWithIdentity] = [] if case .translate = component.mode { if let copyTranslation = component.copyCurrentResult { actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent( - theme: environment.theme, + theme: theme, style: .glass, title: AnyComponent( MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Translate_CopyTranslation, font: Font.regular(17.0), - textColor: environment.theme.list.itemAccentColor + textColor: theme.list.itemAccentColor )), maximumNumberOfLines: 1 ) ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: environment.theme.list.itemAccentColor))), false), + leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor))), false), action: { _ in copyTranslation() } @@ -462,19 +1078,19 @@ final class TextProcessingContentComponent: Component { } if let translateChat = component.translateChat { actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent( - theme: environment.theme, + theme: theme, style: .glass, title: AnyComponent( MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Localization_TranslateEntireChat, font: Font.regular(17.0), - textColor: environment.theme.list.itemAccentColor + textColor: theme.list.itemAccentColor )), maximumNumberOfLines: 1 ) ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: environment.theme.list.itemAccentColor))), false), + leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: theme.list.itemAccentColor))), false), action: { [weak self] _ in guard let self, let language = self.translateState.result?.language else { return @@ -490,7 +1106,7 @@ final class TextProcessingContentComponent: Component { let actionsSectionSize = self.actionsSection.update( transition: transition, component: AnyComponent(ListSectionComponent( - theme: environment.theme, + theme: theme, style: .glass, header: nil, footer: nil, @@ -531,10 +1147,11 @@ private final class TextProcessingSheetComponent: Component { let context: AccountContext let mode: TextProcessingScreen.Mode let ignoredTranslationLanguages: [String] - let styles: [TextProcessingScreen.Style] + let initialStyles: [TextProcessingScreen.Style] let inputText: TextWithEntities let initialEditState: TextProcessingScreen.EditState? let shouldDisplayStyleNotice: Bool + let previewIconFile: TelegramMediaFile? let copyCurrentResult: ((TextWithEntities) -> Void)? let translateChat: ((String) -> Void)? @@ -542,20 +1159,22 @@ private final class TextProcessingSheetComponent: Component { context: AccountContext, mode: TextProcessingScreen.Mode, ignoredTranslationLanguages: [String], - styles: [TextProcessingScreen.Style], + initialStyles: [TextProcessingScreen.Style], inputText: TextWithEntities, initialEditState: TextProcessingScreen.EditState?, shouldDisplayStyleNotice: Bool, + previewIconFile: TelegramMediaFile?, copyCurrentResult: ((TextWithEntities) -> Void)?, translateChat: ((String) -> Void)? ) { self.context = context self.mode = mode self.ignoredTranslationLanguages = ignoredTranslationLanguages - self.styles = styles + self.initialStyles = initialStyles self.inputText = inputText self.initialEditState = initialEditState self.shouldDisplayStyleNotice = shouldDisplayStyleNotice + self.previewIconFile = previewIconFile self.copyCurrentResult = copyCurrentResult self.translateChat = translateChat } @@ -567,18 +1186,19 @@ private final class TextProcessingSheetComponent: Component { final class View: UIView { private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() private var toast: ComponentView? - private var languageSelectionMenu: ComponentView? private let animateOut = ActionSlot>() private let contentExternalState = TextProcessingContentComponent.ExternalState() + + private var styles: [TextProcessingScreen.Style] = [] private final class LanguageSelectionMenuData { let sourceView: UIView let currentLanguage: String let currentStyle: TelegramComposeAIMessageMode.StyleId let displayStyle: Bool - let completion: (String, TelegramComposeAIMessageMode.StyleId) -> Void + let completion: (String, TelegramComposeAIMessageMode.StyleReference) -> Void - init(sourceView: UIView, currentLanguage: String, currentStyle: TelegramComposeAIMessageMode.StyleId, displayStyle: Bool, completion: @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void) { + init(sourceView: UIView, currentLanguage: String, currentStyle: TelegramComposeAIMessageMode.StyleId, displayStyle: Bool, completion: @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void) { self.sourceView = sourceView self.currentLanguage = currentLanguage self.currentStyle = currentStyle @@ -587,10 +1207,21 @@ private final class TextProcessingSheetComponent: Component { } } private var languageSelectionMenuData: LanguageSelectionMenuData? + private var languageSelectionMenu: ComponentView? + + private var styleCreatedToastData: (timer: Foundation.Timer, emojiFile: TelegramMediaFile, style: TelegramComposeAIMessageMode.CloudStyle.Custom)? + private var styleCreatedToast: ComponentView? + + private var customToastData: (timer: Foundation.Timer, text: String)? + private var customToast: ComponentView? private var component: TextProcessingSheetComponent? private var environment: ViewControllerComponentContainer.Environment? private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private var actionDisposable: Disposable? + private var isPerformingMainAction: Bool = false override init(frame: CGRect) { super.init(frame: frame) @@ -600,6 +1231,12 @@ private final class TextProcessingSheetComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.actionDisposable?.dispose() + self.styleCreatedToastData?.timer.invalidate() + self.customToastData?.timer.invalidate() + } + private func displayLongPressSendMenu(sourceSendButton: UIView) { Task { @MainActor [weak self, weak sourceSendButton] in guard let self, let sourceSendButton, let component = self.component, case let .edit(_, _, _, sendContextActions) = component.mode, let sendContextActions else { @@ -665,7 +1302,7 @@ private final class TextProcessingSheetComponent: Component { currentPrice: nil, hasTimers: false, sendPaidMessageStars: nil, - isMonoforum: peer._asPeer().isMonoForum + isMonoforum: peer.isMonoForum )), hasEntityKeyboard: false, gesture: nil, @@ -717,13 +1354,22 @@ private final class TextProcessingSheetComponent: Component { } func update(component: TextProcessingSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if self.component == nil { + self.styles = component.initialStyles + } + self.component = component self.state = state let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value self.environment = environmentValue let controller = environmentValue.controller - let theme = environmentValue.theme + let theme = environmentValue.theme.withModalBlocksBackground() let dismiss: (Bool) -> Void = { [weak self] animated in if animated { @@ -782,11 +1428,53 @@ private final class TextProcessingSheetComponent: Component { } dismiss(true) } - case .translate: - actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose - isMainActionEnabled = true - performMainAction = { - dismiss(true) + case let .translate(_, applyResult): + if let applyResult { + actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply + isMainActionEnabled = !self.contentExternalState.isProcessing && self.contentExternalState.result != nil + performMainAction = { [weak self] in + guard let self, let result = self.contentExternalState.result else { + return + } + applyResult(result) + dismiss(true) + } + } else { + actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose + isMainActionEnabled = true + performMainAction = { + dismiss(true) + } + } + case let .preview(style, _, _, isAlreadyAdded, added): + actionButtonTitle = isAlreadyAdded ? environmentValue.strings.TextProcessing_StyleMenu_ButtonClose : environmentValue.strings.TextProcessing_StyleMenu_ButtonAdd + isMainActionEnabled = !self.isPerformingMainAction + performMainAction = { [weak self] in + guard let self, let component = self.component else { + return + } + self.isPerformingMainAction = true + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + if isAlreadyAdded { + dismiss(true) + } else { + self.actionDisposable?.dispose() + self.actionDisposable = (component.context.engine.messages.installAIMessageStyle(style: style) |> deliverOnMainQueue).startStrict(error: { [weak self] _ in + guard let self else { + return + } + self.isPerformingMainAction = false + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }, completed: { + dismiss(true) + added() + }) + } } } } @@ -801,13 +1489,18 @@ private final class TextProcessingSheetComponent: Component { } } + var displayInfoButton = true let titleString: String switch component.mode { case .edit: titleString = environmentValue.strings.TextProcessing_TitleEdit case .translate: titleString = environmentValue.strings.TextProcessing_TitleTranslate + case .preview: + titleString = "" + displayInfoButton = false } + let sheetSize = self.sheet.update( transition: transition, @@ -816,7 +1509,8 @@ private final class TextProcessingSheetComponent: Component { externalState: self.contentExternalState, context: component.context, mode: component.mode, - styles: component.styles, + previewIconFile: component.previewIconFile, + styles: self.styles, inputText: component.inputText, initialEditState: component.initialEditState, ignoredTranslationLanguages: component.ignoredTranslationLanguages, @@ -831,12 +1525,93 @@ private final class TextProcessingSheetComponent: Component { } }, displayLanguageSelectionMenu: { [weak self] sourceView, currentLanguage, currentStyle, displayStyle, completion in - guard let self else { return } + guard let self else { + return + } self.languageSelectionMenuData = LanguageSelectionMenuData(sourceView: sourceView, currentLanguage: currentLanguage, currentStyle: currentStyle, displayStyle: displayStyle, completion: completion) self.state?.updated(transition: .immediate) + }, + newStyleAdded: { [weak self] style in + Task { @MainActor in + guard let self, let component = self.component else { + return + } + var authorPeer: EnginePeer? + if case let .custom(style) = style.content, let authorId = style.authorId { + authorPeer = await component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: authorId) + ).get() + } + + if case let .custom(style) = style.content, let emojiFileId = style.emojiFileId { + let emojiFile = await component.context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get().first?.value + + if let emojiFile { + self.styleCreatedToastData = (Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + self.styleCreatedToastData = nil + self.state?.updated(transition: .spring(duration: 0.4)) + }), emojiFile, style) + } + } + + self.styles.insert(TextProcessingScreen.Style(cloudStyle: style, authorPeer: authorPeer), at: 0) + self.state?.updated(transition: .spring(duration: 0.4)) + } + }, + styleUpdated: { [weak self] style in + Task { @MainActor in + guard let self, let component = self.component else { + return + } + guard let index = self.styles.firstIndex(where: { $0.id.id == style.id }) else { + return + } + var authorPeer: EnginePeer? + if case let .custom(style) = style.content, let authorId = style.authorId { + authorPeer = await component.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: authorId) + ).get() + } + self.styles[index] = TextProcessingScreen.Style(cloudStyle: style, authorPeer: authorPeer) + self.state?.updated(transition: .immediate) + } + }, + styleDeleted: { [weak self] id in + guard let self else { + return + } + guard let index = self.styles.firstIndex(where: { $0.id.id == id }) else { + return + } + self.styles.remove(at: index) + self.state?.updated(transition: .spring(duration: 0.4)) + }, + displayToast: { [weak self] text in + guard let self else { + return + } + self.customToastData = (Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in + guard let self else { + return + } + self.customToastData = nil + self.state?.updated(transition: .spring(duration: 0.4)) + }), text) + self.state?.updated(transition: .spring(duration: 0.4)) + }, + dismiss: { [weak self] completion in + self?.animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + completion() + }) } )), - titleItem: AnyComponent(TitleComponent( + titleItem: titleString.isEmpty ? nil : AnyComponent(TitleComponent( theme: theme, title: titleString, isProcessing: self.contentExternalState.isProcessing @@ -858,7 +1633,7 @@ private final class TextProcessingSheetComponent: Component { } ) ), - rightItem: AnyComponent( + rightItem: displayInfoButton ? AnyComponent( GlassBarButtonComponent( size: CGSize(width: 44.0, height: 44.0), backgroundColor: nil, @@ -877,7 +1652,8 @@ private final class TextProcessingSheetComponent: Component { environment.controller()?.push(component.context.sharedContext.makeCocoonInfoScreen(context: component.context)) } ) - ), + ) : nil, + hasTopEdgeEffect: displayInfoButton, bottomItem: AnyComponent( ActionButtonsComponent( theme: theme, @@ -997,6 +1773,130 @@ private final class TextProcessingSheetComponent: Component { } } } + + if let styleCreatedToastData = self.styleCreatedToastData, !self.contentExternalState.nonPremiumFloodTriggered { + let sideInset: CGFloat = 8.0 + + let styleCreatedToast: ComponentView + var styleCreatedToastTransition = transition + if let current = self.styleCreatedToast { + styleCreatedToast = current + } else { + styleCreatedToastTransition = styleCreatedToastTransition.withAnimation(.none) + styleCreatedToast = ComponentView() + self.styleCreatedToast = styleCreatedToast + } + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let styleCreatedToastSize = styleCreatedToast.update( + transition: styleCreatedToastTransition, + component: AnyComponent(ToastContentComponent( + icon: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: .animation( + content: .file(file: styleCreatedToastData.emojiFile), + size: CGSize(width: 32.0, height: 32.0), + placeholderColor: environmentValue.theme.list.mediaPlaceholderColor, + themeColor: environmentValue.theme.list.itemPrimaryTextColor, + loopMode: .count(1) + ), + size: CGSize(width: 32.0, height: 32.0), + isVisibleForAnimations: true, + action: nil + )), + content: AnyComponent(VStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: environmentValue.strings.TextProcessing_ToastStyleCreated_Title(styleCreatedToastData.style.title).string, font: Font.semibold(14.0), textColor: .white)), + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( + text: .markdown(text: environmentValue.strings.TextProcessing_ToastStyleCreated_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + maximumNumberOfLines: 0 + ))) + ], alignment: .left, spacing: 6.0)), + insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0), + iconSpacing: 12.0 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + if let styleCreatedToastView = styleCreatedToast.view { + if styleCreatedToastView.superview == nil, let sheetView = self.sheet.view as? ResizableSheetComponent.View { + sheetView.containerView.addSubview(styleCreatedToastView) + if !transition.animation.isImmediate { + styleCreatedToastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + styleCreatedToastTransition.setFrame(view: styleCreatedToastView, frame: CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - 94.0 - styleCreatedToastSize.height), size: styleCreatedToastSize)) + } + } else { + if let styleCreatedToast = self.styleCreatedToast { + self.styleCreatedToast = nil + if let styleCreatedToastView = styleCreatedToast.view { + styleCreatedToastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak styleCreatedToastView] _ in + styleCreatedToastView?.removeFromSuperview() + }) + } + } + } + + if let customToastData = self.customToastData, !self.contentExternalState.nonPremiumFloodTriggered, self.styleCreatedToastData == nil { + let sideInset: CGFloat = 8.0 + + let customToast: ComponentView + var customToastTransition = transition + if let current = self.customToast { + customToast = current + } else { + customToastTransition = customToastTransition.withAnimation(.none) + customToast = ComponentView() + self.customToast = customToast + } + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + let playOnce = ActionSlot() + let customToastSize = customToast.update( + transition: customToastTransition, + component: AnyComponent(ToastContentComponent( + icon: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_infotip"), + startingPosition: .begin, + size: CGSize(width: 32.0, height: 32.0), + playOnce: playOnce + )), + content: AnyComponent(VStack([ + AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( + text: .markdown(text: customToastData.text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + maximumNumberOfLines: 0 + ))) + ], alignment: .left, spacing: 6.0)), + insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0), + iconSpacing: 12.0 + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height) + ) + if let customToastView = customToast.view { + if customToastView.superview == nil, let sheetView = self.sheet.view as? ResizableSheetComponent.View { + sheetView.containerView.addSubview(customToastView) + if !transition.animation.isImmediate { + customToastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + playOnce.invoke(()) + } + customToastTransition.setFrame(view: customToastView, frame: CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - 94.0 - customToastSize.height), size: customToastSize)) + } + } else { + if let customToast = self.customToast { + self.customToast = nil + if let customToastView = customToast.view { + customToastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak customToastView] _ in + customToastView?.removeFromSuperview() + }) + } + } + } if let languageSelectionMenuDataValue = self.languageSelectionMenuData { let languageSelectionMenu: ComponentView @@ -1017,10 +1917,12 @@ private final class TextProcessingSheetComponent: Component { topLanguages: [], selectedLanguageCode: languageSelectionMenuDataValue.currentLanguage, currentStyle: languageSelectionMenuDataValue.currentStyle, - displayStyles: languageSelectionMenuDataValue.displayStyle ? component.styles : nil, + displayStyles: languageSelectionMenuDataValue.displayStyle ? self.styles : nil, completion: languageSelectionMenuDataValue.completion, dismissed: { [weak self] in - guard let self else { return } + guard let self else { + return + } self.languageSelectionMenuData = nil self.state?.updated(transition: .immediate) }, @@ -1067,21 +1969,75 @@ public class TextProcessingScreen: ViewControllerComponentContainer { } } - public struct Style: Equatable { - public let type: String + public final class Style: Equatable { + public let reference: TelegramComposeAIMessageMode.CloudStyle.Reference public let title: String public let emojiFileId: Int64? public let emojiFile: TelegramMediaFile? + public let isAuthor: Bool + public let slug: String? + public let cloudStyle: TelegramComposeAIMessageMode.CloudStyle + public let authorPeer: EnginePeer? - public var id: TelegramComposeAIMessageMode.StyleId { - return .style(self.type) + public var id: TelegramComposeAIMessageMode.StyleReference { + return .style(self.reference) } - public init(type: String, title: String, emojiFileId: Int64?, emojiFile: TelegramMediaFile?) { - self.type = type + public init(reference: TelegramComposeAIMessageMode.CloudStyle.Reference, title: String, emojiFileId: Int64?, emojiFile: TelegramMediaFile?, isAuthor: Bool, slug: String?, cloudStyle: TelegramComposeAIMessageMode.CloudStyle, authorPeer: EnginePeer?) { + self.reference = reference self.title = title self.emojiFileId = emojiFileId self.emojiFile = emojiFile + self.isAuthor = isAuthor + self.slug = slug + self.cloudStyle = cloudStyle + self.authorPeer = authorPeer + } + + convenience init(cloudStyle: TelegramComposeAIMessageMode.CloudStyle, authorPeer: EnginePeer?) { + let title: String + let emojiFileId: Int64? + var isAuthor = false + var slug: String? + switch cloudStyle.content { + case let .standard(standard): + title = standard.title + emojiFileId = standard.emojiFileId + case let .custom(custom): + title = custom.title + emojiFileId = custom.emojiFileId + isAuthor = custom.isCreator + slug = custom.slug + } + self.init( + reference: cloudStyle.reference, + title: title, + emojiFileId: emojiFileId, + emojiFile: nil, + isAuthor: isAuthor, + slug: slug, + cloudStyle: cloudStyle, + authorPeer: authorPeer + ) + } + + public static func ==(lhs: Style, rhs: Style) -> Bool { + if lhs.reference != rhs.reference { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.emojiFileId != rhs.emojiFileId { + return false + } + if lhs.emojiFile != rhs.emojiFile { + return false + } + if lhs.cloudStyle != rhs.cloudStyle { + return false + } + return true } } @@ -1099,17 +2055,56 @@ public class TextProcessingScreen: ViewControllerComponentContainer { let rawStyles = await context.engine.messages.composeAIMessageStyles().get() var styles: [Style] = [] - let resolvedEmojiFiles: [Int64: TelegramMediaFile] = await context.engine.stickers.resolveInlineStickersLocal(fileIds: Array(Set(rawStyles.compactMap({ $0.emojiFileId })))).get() + let resolvedEmojiFiles: [Int64: TelegramMediaFile] = await context.engine.stickers.resolveInlineStickersLocal(fileIds: Array(Set(rawStyles.compactMap({ style in + switch style.content { + case let .standard(standard): + return standard.emojiFileId + case let .custom(custom): + return custom.emojiFileId + } + })))).get() for value in rawStyles { + let title: String + let emojiFileId: Int64? + var isAuthor = false + var slug: String? + var authorPeer: EnginePeer? + switch value.content { + case let .standard(standard): + title = standard.title + emojiFileId = standard.emojiFileId + case let .custom(custom): + title = custom.title + emojiFileId = custom.emojiFileId + isAuthor = custom.isCreator + slug = custom.slug + if let authorId = custom.authorId { + authorPeer = await context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: authorId) + ).get() + } + } styles.append(Style( - type: value.type, - title: value.title, - emojiFileId: value.emojiFileId, - emojiFile: value.emojiFileId.flatMap { resolvedEmojiFiles[$0] } + reference: value.reference, + title: title, + emojiFileId: emojiFileId, + emojiFile: emojiFileId.flatMap { resolvedEmojiFiles[$0] }, + isAuthor: isAuthor, + slug: slug, + cloudStyle: value, + authorPeer: authorPeer )) } - let shouldDisplayStyleNotice = await ApplicationSpecificNotice.getAITextProcessingStyleSelection(accountManager: context.sharedContext.accountManager).get() < 3 + var shouldDisplayStyleNotice = false + var previewIconFile: TelegramMediaFile? + if case let .preview(style, _, _, _, _) = mode { + if let emojiFileId = style.emojiFileId { + previewIconFile = await context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get().first?.value + } + } else { + shouldDisplayStyleNotice = await ApplicationSpecificNotice.getAITextProcessingStyleSelection(accountManager: context.sharedContext.accountManager).get() < 3 + } var initialEditState: EditState? if case let .edit(saveRestoreStateId, _, _, _) = mode, let saveRestoreStateId { @@ -1132,10 +2127,11 @@ public class TextProcessingScreen: ViewControllerComponentContainer { context: context, mode: mode, ignoredTranslationLanguages: translationSettings.ignoredLanguages ?? [], - styles: styles, + initialStyles: styles, inputText: inputText, initialEditState: initialEditState, shouldDisplayStyleNotice: shouldDisplayStyleNotice, + previewIconFile: previewIconFile, copyCurrentResult: copyResult, translateChat: translateChat ), diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift index 7cdaccce1b..e265719ceb 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingStyleSelectionComponent.swift @@ -7,6 +7,9 @@ import MultilineTextComponent import TelegramCore import EmojiStatusComponent import AccountContext +import BundleIconComponent +import GlassBackgroundComponent +import ComponentDisplayAdapters final class TextProcessingStyleSelectionComponent: Component { let context: AccountContext @@ -14,7 +17,9 @@ final class TextProcessingStyleSelectionComponent: Component { let strings: PresentationStrings let styles: [TextProcessingScreen.Style] let selectedStyle: TelegramComposeAIMessageMode.StyleId - let updateStyle: (TelegramComposeAIMessageMode.StyleId) -> Void + let updateStyle: (TelegramComposeAIMessageMode.StyleReference) -> Void + let createStyle: () -> Void + let openStyleContextMenu: (TelegramComposeAIMessageMode.StyleReference, ContextGesture, ContextExtractedContentContainingView) -> Void init( context: AccountContext, @@ -22,7 +27,9 @@ final class TextProcessingStyleSelectionComponent: Component { strings: PresentationStrings, styles: [TextProcessingScreen.Style], selectedStyle: TelegramComposeAIMessageMode.StyleId, - updateStyle: @escaping (TelegramComposeAIMessageMode.StyleId) -> Void + updateStyle: @escaping (TelegramComposeAIMessageMode.StyleReference) -> Void, + createStyle: @escaping () -> Void, + openStyleContextMenu: @escaping (TelegramComposeAIMessageMode.StyleReference, ContextGesture, ContextExtractedContentContainingView) -> Void ) { self.context = context self.theme = theme @@ -30,6 +37,8 @@ final class TextProcessingStyleSelectionComponent: Component { self.styles = styles self.selectedStyle = selectedStyle self.updateStyle = updateStyle + self.createStyle = createStyle + self.openStyleContextMenu = openStyleContextMenu } static func ==(lhs: TextProcessingStyleSelectionComponent, rhs: TextProcessingStyleSelectionComponent) -> Bool { @@ -59,11 +68,19 @@ final class TextProcessingStyleSelectionComponent: Component { private weak var state: EmptyComponentState? private var isUpdating: Bool = false + private let contextGestureContainerView: ContextControllerSourceView private let scrollView: ScrollView private var itemViews: [TelegramComposeAIMessageMode.StyleId: ComponentView] = [:] + private let createStyleItemView = ComponentView() private let selectedBackgroundView: UIImageView + + private var itemWithActiveContextGesture: TelegramComposeAIMessageMode.StyleId? override init(frame: CGRect) { + self.contextGestureContainerView = ContextControllerSourceView() + self.contextGestureContainerView.isGestureEnabled = true + self.contextGestureContainerView.useSublayerTransformForActivation = true + self.scrollView = ScrollView() self.selectedBackgroundView = UIImageView() self.selectedBackgroundView.isHidden = true @@ -81,16 +98,82 @@ final class TextProcessingStyleSelectionComponent: Component { self.scrollView.alwaysBounceVertical = false self.scrollView.scrollsToTop = false self.scrollView.clipsToBounds = false - self.addSubview(self.scrollView) - + self.addSubview(self.contextGestureContainerView) + self.scrollView.addSubview(self.selectedBackgroundView) + self.contextGestureContainerView.addSubview(self.scrollView) self.scrollView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + + self.contextGestureContainerView.shouldBegin = { [weak self] point in + guard let self, let component = self.component else { + return false + } + guard let (itemId, itemView) = self.item(at: point) else { + return false + } + guard let item = component.styles.first(where: { .style($0.reference.id) == itemId }) else { + return false + } + guard case .custom = item.reference else { + return false + } + guard let itemComponentView = itemView.view as? ItemComponent.View else { + return false + } + + self.itemWithActiveContextGesture = itemId + self.contextGestureContainerView.targetLayerForActivationProgress = itemComponentView.containerView.layer + + let startPoint = point + self.contextGestureContainerView.contextGesture?.externalUpdated = { [weak self] _, point in + guard let self else { + return + } + + let dist = sqrt(pow(startPoint.x - point.x, 2.0) + pow(startPoint.y - point.y, 2.0)) + if dist > 10.0 { + self.contextGestureContainerView.contextGesture?.cancel() + } + } + + return true + } + self.contextGestureContainerView.activated = { [weak self] gesture, _ in + guard let self, let component = self.component else { + return + } + guard let itemWithActiveContextGesture = self.itemWithActiveContextGesture else { + return + } + + var itemView: ItemComponent.View? + itemView = self.itemViews[itemWithActiveContextGesture]?.view as? ItemComponent.View + + guard let itemView else { + return + } + guard let item = component.styles.first(where: { .style($0.reference.id) == itemWithActiveContextGesture }) else { + return + } + component.openStyleContextMenu(item.id, gesture, itemView.contextContainerView) + } } required init?(coder: NSCoder) { preconditionFailure() } + + private func item(at point: CGPoint) -> (id: TelegramComposeAIMessageMode.StyleId, itemView: ComponentView)? { + for (id, itemView) in self.itemViews { + if let itemComponentView = itemView.view { + if itemComponentView.bounds.contains(self.scrollView.convert(self.convert(point, to: self.scrollView), to: itemComponentView)) { + return (id, itemView) + } + } + } + return nil + } @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { guard let component = self.component else { @@ -103,16 +186,28 @@ final class TextProcessingStyleSelectionComponent: Component { if component.selectedStyle == id { component.updateStyle(.neutral) } else { - component.updateStyle(id) + if let style = component.styles.first(where: { .style($0.reference.id) == id }) { + component.updateStyle(.style(style.reference)) + } } self.scrollView.scrollRectToVisible(itemComponentView.frame.insetBy(dx: -100.0, dy: 0.0), animated: true) break } } } + if let itemComponentView = self.createStyleItemView.view { + if itemComponentView.bounds.contains(self.scrollView.convert(recognizer.location(in: self.scrollView), to: itemComponentView)) { + component.createStyle() + } + } } } + func scrollToStart() { + let transition: ComponentTransition = .spring(duration: 0.4) + transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(), size: self.scrollView.bounds.size)) + } + func update(component: TextProcessingStyleSelectionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -135,12 +230,12 @@ final class TextProcessingStyleSelectionComponent: Component { let style = component.styles[i] let itemView: ComponentView var itemTransition = transition - if let current = self.itemViews[style.id] { + if let current = self.itemViews[.style(style.reference.id)] { itemView = current } else { itemTransition = itemTransition.withAnimation(.none) itemView = ComponentView() - self.itemViews[style.id] = itemView + self.itemViews[.style(style.reference.id)] = itemView } let measuredSize = itemView.update( transition: itemTransition, @@ -154,18 +249,30 @@ final class TextProcessingStyleSelectionComponent: Component { environment: {}, containerSize: CGSize(width: maxItemWidth, height: availableSize.height) ) - itemSizes[style.id] = measuredSize + itemSizes[.style(style.reference.id)] = measuredSize } + + let createStyleItemSize: CGSize = self.createStyleItemView.update( + transition: transition, + component: AnyComponent(CreateItemComponent( + theme: component.theme, + strings: component.strings + )), + environment: {}, + containerSize: CGSize(width: maxItemWidth, height: availableSize.height) + ) // Compute uniform slot width from largest item var largestItemWidth: CGFloat = 0.0 for (_, size) in itemSizes { largestItemWidth = max(largestItemWidth, size.width) } + largestItemWidth = max(largestItemWidth, createStyleItemSize.width) + let contentBasedWidth = min(maxSlotWidth, max(minSlotWidth, largestItemWidth + itemPadding)) let slotWidth: CGFloat - if CGFloat(component.styles.count) * contentBasedWidth <= availableSize.width { - slotWidth = floor(availableSize.width / CGFloat(component.styles.count)) + if CGFloat(component.styles.count + 1) * contentBasedWidth <= availableSize.width { + slotWidth = floor(availableSize.width / CGFloat(component.styles.count + 1)) } else { var resolved: CGFloat = contentBasedWidth var targetVisible: CGFloat = min(7.5, floor((availableSize.width + 16.0) / (contentBasedWidth + 10.0)) + 0.5) @@ -179,39 +286,62 @@ final class TextProcessingStyleSelectionComponent: Component { } slotWidth = resolved } - let contentWidth = slotWidth * CGFloat(component.styles.count) + let contentWidth = slotWidth * CGFloat(component.styles.count + 1) self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) self.scrollView.contentSize = CGSize(width: contentWidth, height: availableSize.height) self.scrollView.alwaysBounceHorizontal = contentWidth > availableSize.width + self.contextGestureContainerView.frame = CGRect(origin: CGPoint(), size: availableSize) // Second pass: position items centered in their slots var selectedItemFrame: CGRect? for i in 0 ..< component.styles.count { let style = component.styles[i] - guard let itemView = self.itemViews[style.id], - let naturalSize = itemSizes[style.id] else { + guard let itemView = self.itemViews[.style(style.reference.id)], + let naturalSize = itemSizes[.style(style.reference.id)] else { continue } - let slotOriginX = CGFloat(i) * slotWidth + let itemSize = CGSize(width: slotWidth, height: naturalSize.height) + let itemFrame = CGRect(origin: CGPoint(x: CGFloat(i) * slotWidth, y: 0.0), size: itemSize) + if let itemComponentView = itemView.view as? ItemComponent.View { + var itemTransition = transition + if itemComponentView.superview == nil { + self.scrollView.addSubview(itemComponentView) + itemTransition = itemTransition.withAnimation(.none) + transition.animateScale(view: itemComponentView, from: 0.001, to: 1.0) + transition.animateAlpha(view: itemComponentView, from: 0.0, to: 1.0) + } + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + itemComponentView.applySize(measuredSize: naturalSize, size: itemSize, transition: itemTransition) + } + if .style(style.reference.id) == component.selectedStyle { + selectedItemFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: -5.0), size: CGSize(width: slotWidth, height: availableSize.height + 5.0 + 3.0)) + } + } + + do { + let naturalSize = createStyleItemSize + let slotOriginX = CGFloat(component.styles.count) * slotWidth let itemX = slotOriginX + floor((slotWidth - naturalSize.width) * 0.5) let itemFrame = CGRect(origin: CGPoint(x: itemX, y: 0.0), size: naturalSize) - if let itemComponentView = itemView.view { + if let itemComponentView = self.createStyleItemView.view { if itemComponentView.superview == nil { self.scrollView.addSubview(itemComponentView) } transition.setFrame(view: itemComponentView, frame: itemFrame) } - if style.id == component.selectedStyle { - selectedItemFrame = CGRect(origin: CGPoint(x: slotOriginX, y: -5.0), size: CGSize(width: slotWidth, height: availableSize.height + 5.0 + 3.0)) - } } var removedIds: [TelegramComposeAIMessageMode.StyleId] = [] for (id, itemView) in self.itemViews { - if !component.styles.contains(where: { $0.id == id }) { + if !component.styles.contains(where: { .style($0.reference.id) == id }) { removedIds.append(id) - itemView.view?.removeFromSuperview() + if let itemComponentView = itemView.view { + transition.setAlpha(view: itemComponentView, alpha: 0.0, completion: { [weak itemComponentView] _ in + itemComponentView?.removeFromSuperview() + }) + transition.setScale(view: itemComponentView, scale: 0.001) + } } } for id in removedIds { @@ -293,6 +423,11 @@ private final class ItemComponent: Component { } final class View: UIView { + let contextContainerView: ContextExtractedContentContainingView + let containerView: UIView + private let backgroundContainer: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private var imageIcon: ComponentView? private let title = ComponentView() @@ -300,13 +435,55 @@ private final class ItemComponent: Component { private weak var state: EmptyComponentState? override init(frame: CGRect) { + self.contextContainerView = ContextExtractedContentContainingView() + self.containerView = UIView() + + self.backgroundContainer = GlassBackgroundContainerView() + self.backgroundContainer.alpha = 0.0 + self.backgroundView = GlassBackgroundView() + self.backgroundContainer.contentView.addSubview(self.backgroundView) + super.init(frame: frame) + + self.addSubview(self.contextContainerView) + self.contextContainerView.contentView.addSubview(self.backgroundContainer) + self.contextContainerView.contentView.addSubview(self.containerView) + + self.contextContainerView.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in + guard let self else { + return + } + let transition: ComponentTransition = transition.isAnimated ? .easeInOut(duration: 0.25) : .immediate + if isExtracted { + transition.setAlpha(view: self.backgroundContainer, alpha: 1.0) + } else { + transition.setAlpha(view: self.backgroundContainer, alpha: 0.001) + } + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + func applySize(measuredSize: CGSize, size: CGSize, transition: ComponentTransition) { + guard let component = self.component else { + return + } + + let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - measuredSize.width) * 0.5), y: floor((measuredSize.height - size.height) * 0.5)), size: measuredSize) + let contentRect = CGRect(origin: CGPoint(x: 0.0, y: -5.0 - 4.0), size: CGSize(width: size.width + 0.0, height: size.height + 5.0 + 3.0 + 6.0)) + transition.setFrame(view: self.backgroundContainer, frame: contentRect) + self.backgroundContainer.update(size: contentRect.size, isDark: component.theme.overallDarkAppearance, transition: transition) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: contentRect.size)) + self.backgroundView.update(size: contentRect.size, cornerRadius: 20.0, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition) + + transition.setFrame(view: self.containerView, frame: containerFrame) + transition.setFrame(view: self.contextContainerView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: self.contextContainerView.contentView, frame: CGRect(origin: CGPoint(), size: size)) + self.contextContainerView.contentRect = contentRect.insetBy(dx: -2.0, dy: -2.0) + } + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { let previousComponent = self.component self.component = component @@ -381,11 +558,105 @@ private final class ItemComponent: Component { let iconFrame = CGRect(origin: CGPoint(x: floor((contentWidth - iconSize.width) * 0.5), y: -3.0), size: iconSize) if let imageIconView = imageIcon.view { if imageIconView.superview == nil { - self.addSubview(imageIconView) + self.containerView.addSubview(imageIconView) } iconTransition.setFrame(view: imageIconView, frame: iconFrame) } + let titleFrame = CGRect(origin: CGPoint(x: floor((contentWidth - titleSize.width) * 0.5), y: availableSize.height - 5.0 - titleSize.height), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.containerView.addSubview(titleView) + } + titleView.frame = titleFrame + } + + let size = CGSize(width: contentWidth, height: availableSize.height) + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class CreateItemComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + + init( + theme: PresentationTheme, + strings: PresentationStrings + ) { + self.theme = theme + self.strings = strings + } + + static func ==(lhs: CreateItemComponent, rhs: CreateItemComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + return true + } + + final class View: UIView { + private let icon = ComponentView() + private let title = ComponentView() + + private var component: CreateItemComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: CreateItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let iconTintColor = component.theme.list.itemPrimaryTextColor + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: "TextProcessing/NewStyle", + tintColor: iconTintColor + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.strings.TextProcessing_StyleList_Add, font: Font.medium(10.0), textColor: iconTintColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + + let contentWidth = max(iconSize.width, titleSize.width) + + let iconFrame = CGRect(origin: CGPoint(x: floor((contentWidth - iconSize.width) * 0.5), y: -3.0), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + let titleFrame = CGRect(origin: CGPoint(x: floor((contentWidth - titleSize.width) * 0.5), y: availableSize.height - 5.0 - titleSize.height), size: titleSize) if let titleView = self.title.view { if titleView.superview == nil { diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift index 845b487e72..186e69904f 100644 --- a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextProcessingTranslateContentComponent.swift @@ -11,6 +11,8 @@ import BundleIconComponent import TelegramCore import TranslateUI import TooltipComponent +import Markdown +import PlainButtonComponent private let languageRecognizer = NLLanguageRecognizer() @@ -54,10 +56,13 @@ final class TextProcessingTranslateContentComponent: Component { case translate(ignoredLanguages: [String]) case stylize case fix + case preview(from: TextWithEntities?, to: TextWithEntities?, authorPeer: EnginePeer?, userCount: Int, isRequesting: Bool) } final class ExternalState { fileprivate(set) var sourceLanguage: String? + fileprivate(set) var sectionHeader: AnyComponentWithIdentity? + fileprivate(set) var sectionFooter: AnyComponentWithIdentity? fileprivate(set) var result: (language: String, text: TextWithEntities?, textCorrectionRanges: [Range])? = nil { didSet { @@ -70,7 +75,7 @@ final class TextProcessingTranslateContentComponent: Component { fileprivate(set) var emojify: Bool = false fileprivate(set) var isSourceTextExpanded: Bool = false - fileprivate(set) var style: TelegramComposeAIMessageMode.StyleId = .neutral + fileprivate(set) var style: TelegramComposeAIMessageMode.StyleReference = .neutral var displayStyleTooltip: Bool = false fileprivate(set) var isProcessing: Bool = false { @@ -103,9 +108,13 @@ final class TextProcessingTranslateContentComponent: Component { let externalState: ExternalState let mode: Mode let copyAction: (() -> Void)? - let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void) -> Void + let displayLanguageSelectionMenu: (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void) -> Void + let createStyle: () -> Void + let openStyleContextMenu: (TelegramComposeAIMessageMode.StyleReference, ContextGesture, ContextExtractedContentContainingView) -> Void let present: (ViewController, Any?) -> Void let rootViewForTextSelection: () -> UIView? + let openPeer: (EnginePeer) -> Void + let requestAnotherPreviewExample: () -> Void init( context: AccountContext, @@ -116,9 +125,13 @@ final class TextProcessingTranslateContentComponent: Component { inputText: TextWithEntities, mode: Mode, copyAction: (() -> Void)?, - displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleId) -> Void) -> Void, + displayLanguageSelectionMenu: @escaping (UIView, String, TelegramComposeAIMessageMode.StyleId, Bool, @escaping (String, TelegramComposeAIMessageMode.StyleReference) -> Void) -> Void, + createStyle: @escaping () -> Void, + openStyleContextMenu: @escaping (TelegramComposeAIMessageMode.StyleReference, ContextGesture, ContextExtractedContentContainingView) -> Void, present: @escaping (ViewController, Any?) -> Void, - rootViewForTextSelection: @escaping () -> UIView? + rootViewForTextSelection: @escaping () -> UIView?, + openPeer: @escaping (EnginePeer) -> Void, + requestAnotherPreviewExample: @escaping () -> Void ) { self.context = context self.theme = theme @@ -129,8 +142,12 @@ final class TextProcessingTranslateContentComponent: Component { self.mode = mode self.copyAction = copyAction self.displayLanguageSelectionMenu = displayLanguageSelectionMenu + self.createStyle = createStyle + self.openStyleContextMenu = openStyleContextMenu self.present = present self.rootViewForTextSelection = rootViewForTextSelection + self.openPeer = openPeer + self.requestAnotherPreviewExample = requestAnotherPreviewExample } static func ==(lhs: TextProcessingTranslateContentComponent, rhs: TextProcessingTranslateContentComponent) -> Bool { @@ -222,6 +239,8 @@ final class TextProcessingTranslateContentComponent: Component { } else { mappedMode = .stylize(emojify: component.externalState.emojify, style: component.externalState.style) } + case .preview: + mappedMode = nil case .fix: mappedMode = .proofread } @@ -255,6 +274,12 @@ final class TextProcessingTranslateContentComponent: Component { } } } + + func scrollStylesToStart() { + if let styleSelectionView = self.styleSelection.view as? TextProcessingStyleSelectionComponent.View { + styleSelectionView.scrollToStart() + } + } func update(component: TextProcessingTranslateContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true @@ -318,6 +343,8 @@ final class TextProcessingTranslateContentComponent: Component { case .fix: component.externalState.result = ("", nil, []) self.beginTranslationIfNecessary(reset: false) + case .preview: + component.externalState.result = ("", component.inputText, []) } } @@ -354,6 +381,138 @@ final class TextProcessingTranslateContentComponent: Component { toFormat = component.strings.TextProcessing_ResultBadge } toTitle = "" + case .preview: + fromFormat = component.strings.TextProcessing_StylePreview_Before + toFormat = component.strings.TextProcessing_StylePreview_After + toTitle = "" + } + + var fromText: TextWithEntities? = component.inputText + var fromTextMeasurementString: String? + var toTextMeasurementString: String? + var toText: TextWithEntities? = component.externalState.result?.text + var isPreview = false + if case let .preview(from, to, _, _, isRequesting) = component.mode { + isPreview = true + if isRequesting { + fromText = nil + toText = nil + } else { + fromText = from + toText = to + } + fromTextMeasurementString = from?.text + toTextMeasurementString = to?.text + } + + if case .stylize = component.mode { + if case .style = component.externalState.style, let style = component.styles.first(where: { $0.id == component.externalState.style }), let authorPeer = style.authorPeer { + let footerText: String + if let addressName = authorPeer.addressName { + footerText = component.strings.TextProcessing_StyleFooterAuthor("@" + addressName).string + } else { + footerText = component.strings.TextProcessing_StyleFooterAuthor(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast)).string + } + component.externalState.sectionFooter = AnyComponentWithIdentity(id: "style_by_\(authorPeer.id.toInt64())", component: AnyComponent(MultilineTextComponent( + text: .markdown(text: footerText, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: component.theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor), + linkAttribute: { url in + return ("URL", url) + } + )), + highlightColor: component.theme.list.itemAccentColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, + tapAction: { [weak self] attributes, _ in + guard let self, let component = self.component else { + return + } + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] as? String { + component.openPeer(authorPeer) + } + } + ))) + } else { + component.externalState.sectionFooter = nil + } + } else if case let .preview(_, _, authorPeer, userCount, _) = component.mode { + component.externalState.sectionHeader = AnyComponentWithIdentity(id: "preview", component: AnyComponent(HStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .markdown(text: component.strings.TextProcessing_StylePreview_ExampleHeader, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: component.theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor), + linkAttribute: { url in + return ("URL", url) + } + )) + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(PlainButtonComponent( + content: AnyComponent(HStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(BundleIconComponent( + name: "Settings/Refresh", + tintColor: component.theme.list.itemAccentColor + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.strings.TextProcessing_StylePreview_ExampleHeaderRefresh, font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor)) + ))) + ], spacing: 2.0)), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.requestAnotherPreviewExample() + }, + animateScale: false, + animateContents: false + ))), + ], spacing: 8.0, alignment: .alternatingLeftRight))) + + let userCountString = component.strings.TextProcessing_StyleFooterUserCount(Int32(userCount)) + + let footerText: String + if let authorPeer { + if let addressName = authorPeer.addressName { + footerText = component.strings.TextProcessing_StyleFooterCreatedByFormat(userCountString, component.strings.TextProcessing_StyleFooterCreatedBy("@" + addressName).string).string + } else { + footerText = component.strings.TextProcessing_StyleFooterCreatedByFormat(userCountString, component.strings.TextProcessing_StyleFooterCreatedBy(authorPeer.displayTitle(strings: component.strings, displayOrder: .firstLast)).string).string + } + } else { + footerText = component.strings.TextProcessing_StyleFooterCreatedBySimpleFormat(userCountString).string + } + component.externalState.sectionFooter = AnyComponentWithIdentity(id: "style_by_\(authorPeer?.id.toInt64() ?? 0)", component: AnyComponent(MultilineTextComponent( + text: .markdown(text: footerText, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: component.theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: component.theme.list.itemAccentColor), + linkAttribute: { url in + return ("URL", url) + } + )), + highlightColor: component.theme.list.itemAccentColor.withAlphaComponent(0.1), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + }, + tapAction: { [weak self] attributes, _ in + guard let self, let component = self.component else { + return + } + if let authorPeer, let _ = attributes[NSAttributedString.Key(rawValue: "URL")] as? String { + component.openPeer(authorPeer) + } + } + ))) } contentHeight += topInset @@ -365,7 +524,7 @@ final class TextProcessingTranslateContentComponent: Component { theme: component.theme, strings: component.strings, styles: component.styles, - selectedStyle: component.externalState.style, + selectedStyle: component.externalState.style.id, updateStyle: { [weak self] style in guard let self, let component = self.component else { return @@ -381,6 +540,18 @@ final class TextProcessingTranslateContentComponent: Component { } } } + }, + createStyle: { [weak self] in + guard let self, let component = self.component else { + return + } + component.createStyle() + }, + openStyleContextMenu: { [weak self] styleId, gesture, sourceView in + guard let self, let component = self.component else { + return + } + component.openStyleContextMenu(styleId, gesture, sourceView) } )), environment: {}, @@ -407,7 +578,7 @@ final class TextProcessingTranslateContentComponent: Component { titleFormat: fromFormat, title: fromTitle, titleAction: nil, - isExpanded: ( + isExpanded: isPreview ? nil : ( component.externalState.isSourceTextExpanded, { [weak self] in guard let self, let component = self.component else { @@ -421,8 +592,8 @@ final class TextProcessingTranslateContentComponent: Component { ), copyAction: nil, emojify: nil, - text: component.inputText, - loadingStateMeasuringText: nil, + text: fromText, + loadingStateMeasuringText: fromTextMeasurementString, textCorrectionRanges: [], present: component.present, rootViewForTextSelection: component.rootViewForTextSelection @@ -462,7 +633,7 @@ final class TextProcessingTranslateContentComponent: Component { guard let self, let component = self.component, let result = component.externalState.result else { return } - component.displayLanguageSelectionMenu(sourceView, result.language, component.externalState.style, true, { [weak self] language, style in + component.displayLanguageSelectionMenu(sourceView, result.language, component.externalState.style.id, true, { [weak self] language, style in guard let self, let component = self.component else { return } @@ -499,8 +670,8 @@ final class TextProcessingTranslateContentComponent: Component { } } ) : nil, - text: component.externalState.result?.text, - loadingStateMeasuringText: component.inputText.text, + text: toText, + loadingStateMeasuringText: toTextMeasurementString ?? component.inputText.text, textCorrectionRanges: component.mode == .fix ? (component.externalState.result?.textCorrectionRanges ?? []) : [], present: component.present, rootViewForTextSelection: component.rootViewForTextSelection diff --git a/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift new file mode 100644 index 0000000000..ae218bcc95 --- /dev/null +++ b/submodules/TelegramUI/Components/TextProcessingScreen/Sources/TextStyleEditScreen.swift @@ -0,0 +1,1108 @@ +import Foundation +import UIKit +import SwiftSignalKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AccountContext +import ViewControllerComponent +import MultilineTextComponent +import ButtonComponent +import BundleIconComponent +import TelegramCore +import PresentationDataUtils +import ResizableSheetComponent +import GlassBarButtonComponent +import ListSectionComponent +import Markdown +import TelegramUIPreferences +import ListMultilineTextFieldItemComponent +import TextFieldComponent +import ListActionItemComponent +import CheckComponent +import PlainButtonComponent +import EntityKeyboard +import EmojiStatusComponent + +final class TextStyleEditContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + final class ExternalState { + let titleInputState = ListMultilineTextFieldItemComponent.ExternalState() + let textInputState = ListMultilineTextFieldItemComponent.ExternalState() + var isLinkToProfileEnabled: Bool = false + var emojiFile: TelegramMediaFile? + } + + let externalState: ExternalState + let context: AccountContext + let mode: TextStyleEditScreen.Mode + let styleDeleted: () -> Void + + init( + externalState: ExternalState, + context: AccountContext, + mode: TextStyleEditScreen.Mode, + styleDeleted: @escaping () -> Void + ) { + self.externalState = externalState + self.context = context + self.mode = mode + self.styleDeleted = styleDeleted + } + + static func ==(lhs: TextStyleEditContentComponent, rhs: TextStyleEditContentComponent) -> Bool { + return true + } + + private enum Mode { + case translate + case stylize + case fix + } + + final class View: UIView { + private var component: TextStyleEditContentComponent? + private var environment: ViewControllerComponentContainer.Environment? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + private var previousEnvironment: ViewControllerComponentContainer.Environment? + + private let iconBackground = ComponentView() + private let emptyIcon = ComponentView() + private var emojiIcon: ComponentView? + private let titleSection = ComponentView() + private let textSection = ComponentView() + private let deleteSection = ComponentView() + private let linkOption = ComponentView() + + private let titleFieldTag = ListMultilineTextFieldItemComponent.Tag() + private let textFieldTag = ListMultilineTextFieldItemComponent.Tag() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + preconditionFailure() + } + + private func recenterCaret(hintView: UIView, transition: ComponentTransition) { + var fieldView: ListMultilineTextFieldItemComponent.View? + var ancestor: UIView? = hintView + while let current = ancestor { + if let candidate = current as? ListMultilineTextFieldItemComponent.View { + fieldView = candidate + break + } + ancestor = current.superview + } + guard let fieldView else { + return + } + if !(fieldView.matches(tag: self.titleFieldTag) || fieldView.matches(tag: self.textFieldTag)) { + return + } + guard let inputTextView = fieldView.textFieldView?.inputTextView else { + return + } + let caretPosition = inputTextView.selectedTextRange?.end ?? inputTextView.endOfDocument + let caretRect = inputTextView.caretRect(for: caretPosition) + if caretRect.isNull || caretRect.isInfinite { + return + } + + var scrollAncestor: UIView? = self.superview + var scrollView: UIScrollView? + while let current = scrollAncestor { + if let candidate = current as? UIScrollView { + scrollView = candidate + break + } + scrollAncestor = current.superview + } + guard let scrollView, let environment = self.environment else { + return + } + + let caretInScroll = inputTextView.convert(caretRect, to: scrollView) + + // ResizableSheetComponent bottom action button (52pt) + gap above it (8pt). + let bottomActionAreaHeight: CGFloat = 60.0 + let caretTopInset: CGFloat = 24.0 + let caretBottomInset: CGFloat = 24.0 + let visibleTop = scrollView.bounds.minY + caretTopInset + let visibleBottom = scrollView.bounds.maxY - environment.inputHeight - bottomActionAreaHeight - caretBottomInset + + let previousBounds = scrollView.bounds + var newBounds = previousBounds + if caretInScroll.maxY > visibleBottom { + newBounds.origin.y += (caretInScroll.maxY - visibleBottom) + } else if caretInScroll.minY < visibleTop { + newBounds.origin.y -= (visibleTop - caretInScroll.minY) + } + let maxOriginY = max(0.0, scrollView.contentSize.height - scrollView.bounds.height) + newBounds.origin.y = min(max(0.0, newBounds.origin.y), maxOriginY) + + if newBounds != previousBounds { + scrollView.bounds = newBounds + if !transition.animation.isImmediate { + let offsetY = previousBounds.origin.y - newBounds.origin.y + transition.animateBoundsOrigin(view: scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + } + + func activateEmojiSelection() { + guard let component = self.component else { + return + } + guard let iconBackgroundView = self.iconBackground.view else { + return + } + self.environment?.controller()?.present(component.context.sharedContext.makeEmojiStatusSelectionController( + context: component.context, + mode: .backgroundSelection(completion: { [weak self] file in + guard let self, let component = self.component else { + return + } + component.externalState.emojiFile = file + self.state?.updated(transition: .immediate) + DispatchQueue.main.async { [weak self] in + guard let self else { + return + } + self.state?.updated(transition: .immediate) + } + }), + sourceView: iconBackgroundView, + emojiContent: EmojiPagerContentComponent.emojiInputData( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + isStandalone: false, + subject: .emoji, + hasTrending: false, + topReactionItems: [], + areUnicodeEmojiEnabled: false, + areCustomEmojiEnabled: true, + chatPeerId: component.context.account.peerId, + selectedItems: Set() + ) |> map { $0 }, + currentSelection: nil, + color: nil, + destinationItemView: { [weak self] in + guard let self else { + return nil + } + return self.emojiIcon?.view + } + ), in: .window(.root)) + } + + func update(component: TextStyleEditContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2) + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let previousEnvironment = self.previousEnvironment + self.previousEnvironment = environment + + var resetTitle: String? + var resetText: String? + if self.component == nil { + resetTitle = "" + if case let .edit(style) = component.mode, case let .custom(style) = style.content { + resetTitle = style.title + resetText = style.prompt ?? "" + component.externalState.isLinkToProfileEnabled = style.authorId != nil + } + } + + self.component = component + self.environment = environment + self.state = state + + let sideInset: CGFloat = 16.0 + let sectionSpacing: CGFloat = 24.0 + let iconSpacing: CGFloat = 24.0 + + var contentHeight: CGFloat = 0.0 + contentHeight += 70.0 + + let iconBackgroundSize = CGSize(width: 100.0, height: 100.0) + let _ = self.iconBackground.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(FilledRoundedRectangleComponent( + color: environment.theme.list.itemBlocksBackgroundColor, + cornerRadius: .minEdge, + smoothCorners: false + )), + action: { [weak self] in + guard let self else { + return + } + self.activateEmojiSelection() + }, + animateAlpha: false, + animateScale: false, + )), + environment: {}, + containerSize: iconBackgroundSize + ) + let iconBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconBackgroundSize.width) * 0.5), y: contentHeight), size: iconBackgroundSize) + if let iconBackgroundView = self.iconBackground.view { + if iconBackgroundView.superview == nil { + self.addSubview(iconBackgroundView) + } + transition.setFrame(view: iconBackgroundView, frame: iconBackgroundFrame) + } + + let emptyIconSize = self.emptyIcon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: "TextProcessing/EditEmojiPlaceholder", + tintColor: environment.theme.list.controlSecondaryColor + )), + environment: {}, + containerSize: iconBackgroundFrame.size + ) + let emptyIconFrame = emptyIconSize.centered(in: iconBackgroundFrame) + if let emptyIconView = self.emptyIcon.view { + if emptyIconView.superview == nil { + self.addSubview(emptyIconView) + emptyIconView.isUserInteractionEnabled = false + } + transition.setFrame(view: emptyIconView, frame: emptyIconFrame) + transition.setAlpha(view: emptyIconView, alpha: component.externalState.emojiFile == nil ? 1.0 : 0.0) + } + + if let emojiFile = component.externalState.emojiFile { + var emojiIconTransition = transition + let emojiIcon: ComponentView + if let current = self.emojiIcon { + emojiIcon = current + } else { + emojiIconTransition = emojiIconTransition.withAnimation(.none) + emojiIcon = ComponentView() + self.emojiIcon = emojiIcon + } + let emojiSize = CGSize(width: 70.0, height: 70.0) + let emojiIconSize = emojiIcon.update( + transition: emojiIconTransition, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: .animation( + content: .file(file: emojiFile), + size: emojiSize, + placeholderColor: environment.theme.list.mediaPlaceholderColor, + themeColor: environment.theme.list.itemPrimaryTextColor, + loopMode: .count(1) + ), + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: emojiSize + ) + let emojiIconFrame = emojiIconSize.centered(in: iconBackgroundFrame) + if let emojiIconView = emojiIcon.view { + if emojiIconView.superview == nil { + self.addSubview(emojiIconView) + emojiIconView.isUserInteractionEnabled = false + } + emojiIconTransition.setFrame(view: emojiIconView, frame: emojiIconFrame) + } + } else { + if let emojiIcon = self.emojiIcon { + self.emojiIcon = nil + if let emojiIconView = emojiIcon.view { + transition.setAlpha(view: emojiIconView, alpha: 0.0, completion: { [weak emojiIconView] _ in + emojiIconView?.removeFromSuperview() + }) + transition.setScale(view: emojiIconView, scale: 0.001) + } + } + } + + contentHeight += iconBackgroundSize.height + iconSpacing + + var titleSectionItems: [AnyComponentWithIdentity] = [] + titleSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListMultilineTextFieldItemComponent( + externalState: component.externalState.titleInputState, + style: .glass, + context: component.context, + theme: environment.theme, + strings: environment.strings, + initialText: "", + resetText: resetTitle.flatMap { resetTitle in + return ListMultilineTextFieldItemComponent.ResetText(value: resetTitle) + }, + placeholder: environment.strings.TextProcessing_EditStyle_NamePlaceholder, + autocapitalizationType: .sentences, + autocorrectionType: .default, + characterLimit: 12, + emptyLineHandling: .notAllowed, + updated: nil, + textUpdateTransition: .spring(duration: 0.4), + tag: self.titleFieldTag + )))) + let titleSectionSize = self.titleSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + style: .glass, + header: nil, + footer: nil, + items: titleSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let titleSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titleSectionSize) + if let titleSectionView = self.titleSection.view { + if titleSectionView.superview == nil { + self.addSubview(titleSectionView) + self.titleSection.parentState = state + } + transition.setFrame(view: titleSectionView, frame: titleSectionFrame) + } + contentHeight += titleSectionSize.height + contentHeight += sectionSpacing + + var textSectionItems: [AnyComponentWithIdentity] = [] + textSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListMultilineTextFieldItemComponent( + externalState: component.externalState.textInputState, + style: .glass, + context: component.context, + theme: environment.theme, + strings: environment.strings, + initialText: "", + resetText: resetText.flatMap { resetText in + return ListMultilineTextFieldItemComponent.ResetText(value: resetText) + }, + placeholder: environment.strings.TextProcessing_EditStyle_TextPlaceholder, + placeholderDefinesMinHeight: true, + autocapitalizationType: .sentences, + autocorrectionType: .default, + characterLimit: 1024, + emptyLineHandling: .allowed, + updated: nil, + textUpdateTransition: .spring(duration: 0.4), + tag: self.textFieldTag + )))) + let textSectionSize = self.textSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + style: .glass, + header: nil, + footer: nil, + items: textSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let textSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: textSectionSize) + if let textSectionView = self.textSection.view { + if textSectionView.superview == nil { + self.addSubview(textSectionView) + self.textSection.parentState = state + } + transition.setFrame(view: textSectionView, frame: textSectionFrame) + } + contentHeight += textSectionSize.height + + if case let .edit(style) = component.mode, case let .custom(style) = style.content { + contentHeight += sectionSpacing + + let deleteSectionSize = self.deleteSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + style: .glass, + header: nil, + footer: nil, + items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.TextProcessing_EditStyle_Delete, + font: Font.regular(17.0), + textColor: environment.theme.list.itemDestructiveColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .center, spacing: 2.0, fillWidth: true)), + accessory: nil, + action: { [weak self] _ in + guard let self, let component = self.component, let environment = self.environment else { + return + } + environment.controller()?.push(textAlertController( + context: component.context, + title: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Title, + text: environment.strings.TextProcessing_AlertCreatorDeleteStyle_Text, + actions: [ + TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in + guard let self, let component = self.component else { + return + } + let _ = component.context.engine.messages.deleteAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone() + component.styleDeleted() + }), + ] + )) + } + )))] + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let deleteSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: deleteSectionSize) + if let deleteSectionView = self.deleteSection.view { + if deleteSectionView.superview == nil { + self.addSubview(deleteSectionView) + self.deleteSection.parentState = state + } + transition.setFrame(view: deleteSectionView, frame: deleteSectionFrame) + } + contentHeight += deleteSectionSize.height + } + + contentHeight += 23.0 + + let checkTheme = CheckComponent.Theme( + backgroundColor: environment.theme.list.itemCheckColors.fillColor, + strokeColor: environment.theme.list.itemCheckColors.foregroundColor, + borderColor: environment.theme.list.itemCheckColors.strokeColor, + overlayBorder: false, + hasInset: false, + hasShadow: false + ) + let linkOptionSize = self.linkOption.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent( + theme: checkTheme, + size: CGSize(width: 18.0, height: 18.0), + selected: component.externalState.isLinkToProfileEnabled + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: environment.strings.TextProcessing_EditStyle_AddLink, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)) + ))) + ], spacing: 10.0)), + effectAlignment: .center, + action: { [weak self] in + guard let self else { + return + } + component.externalState.isLinkToProfileEnabled = !component.externalState.isLinkToProfileEnabled + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }, + animateAlpha: false, + animateScale: false + )), + environment: { + }, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0) + ) + let linkOptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - linkOptionSize.width) * 0.5), y: contentHeight), size: linkOptionSize) + if let linkOptionView = self.linkOption.view { + if linkOptionView.superview == nil { + self.addSubview(linkOptionView) + } + transition.setFrame(view: linkOptionView, frame: linkOptionFrame) + } + contentHeight += linkOptionSize.height + + contentHeight += 104.0 + + let _ = alphaTransition + + if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let hintView = hint.view { + switch hint.kind { + case .textChanged: + self.recenterCaret(hintView: hintView, transition: transition) + default: + break + } + } + if let previousEnvironment { + var targetView: UIView? + if component.externalState.titleInputState.isEditing { + if let view = self.titleSection.findTaggedView(tag: self.titleFieldTag) as? ListMultilineTextFieldItemComponent.View { + targetView = view + } + } else if component.externalState.textInputState.isEditing { + if let view = self.textSection.findTaggedView(tag: self.textFieldTag) as? ListMultilineTextFieldItemComponent.View { + targetView = view + } + } + + if let targetView { + if (environment.inputHeight == 0.0) != (previousEnvironment.inputHeight == 0.0) { + DispatchQueue.main.async { [weak self] in + guard let self else { + return + } + self.recenterCaret(hintView: targetView, transition: transition) + } + } + } + } + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class TextStyleEditSheetComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let mode: TextStyleEditScreen.Mode + let initialEmojiFile: TelegramMediaFile? + let completion: (TelegramComposeAIMessageMode.CloudStyle) -> Void + let styleDeleted: () -> Void + + init( + context: AccountContext, + mode: TextStyleEditScreen.Mode, + initialEmojiFile: TelegramMediaFile?, + completion: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void, + styleDeleted: @escaping () -> Void + ) { + self.context = context + self.mode = mode + self.initialEmojiFile = initialEmojiFile + self.completion = completion + self.styleDeleted = styleDeleted + } + + static func ==(lhs: TextStyleEditSheetComponent, rhs: TextStyleEditSheetComponent) -> Bool { + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>() + private let animateOut = ActionSlot>() + + private var component: TextStyleEditSheetComponent? + private var environment: ViewControllerComponentContainer.Environment? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + private let contentState = TextStyleEditContentComponent.ExternalState() + + private var isActionInProgress: Bool = false + private var createDisposable: Disposable? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.contentState.titleInputState.updated = { [weak self] in + DispatchQueue.main.async { + guard let self else { + return + } + self.state?.updated(transition: .spring(duration: 0.4)) + } + } + self.contentState.textInputState.updated = self.contentState.titleInputState.updated + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.createDisposable?.dispose() + } + + private func performCreateStyle() { + guard let component = self.component else { + return + } + if self.contentState.titleInputState.text.string.isEmpty || self.contentState.textInputState.text.string.isEmpty { + return + } + guard let emojiFile = self.contentState.emojiFile else { + if let sheetView = self.sheet.view as? ResizableSheetComponent.View { + if let contentView = sheetView.contentViewValue as? TextStyleEditContentComponent.View { + contentView.activateEmojiSelection() + } + } + return + } + + self.createDisposable?.dispose() + + switch component.mode { + case .create: + self.isActionInProgress = true + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + + self.createDisposable = (component.context.engine.messages.createAITextStyle( + displayAuthor: self.contentState.isLinkToProfileEnabled, + emojiFileId: emojiFile.fileId.id, + title: self.contentState.titleInputState.text.string, + prompt: self.contentState.textInputState.text.string + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self, let component = self.component, let environment = self.environment else { + return + } + let controller = environment.controller + + self.animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + + component.completion(result) + }, error: { [weak self] error in + guard let self else { + return + } + + self.isActionInProgress = false + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + case let .edit(style): + guard case let .custom(style) = style.content else { + return + } + self.isActionInProgress = true + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + self.createDisposable = (component.context.engine.messages.editAITextStyle( + id: style.id, + accessHash: style.accessHash, + displayAuthor: self.contentState.isLinkToProfileEnabled, + emojiFileId: emojiFile.fileId.id, + title: self.contentState.titleInputState.text.string, + prompt: self.contentState.textInputState.text.string + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self, let component = self.component, let environment = self.environment else { + return + } + let controller = environment.controller + + self.animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + + component.completion(result) + }, error: { [weak self] error in + guard let self else { + return + } + + self.isActionInProgress = false + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.4)) + } + }) + } + } + + func update(component: TextStyleEditSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + if self.component == nil { + self.contentState.emojiFile = component.initialEmojiFile + } + + self.component = component + self.state = state + + let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environmentValue + let controller = environmentValue.controller + let theme = environmentValue.theme + + let dismiss: (Bool) -> Void = { [weak self] animated in + if animated { + self?.animateOut.invoke(Action { _ in + if let controller = controller() { + controller.dismiss(completion: nil) + } + }) + } else { + if let controller = controller() { + controller.dismiss(completion: nil) + } + } + } + + let performMainAction: () -> Void = { [weak self] in + guard let self else { + return + } + self.performCreateStyle() + } + let isMainActionEnabled = !self.contentState.titleInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !self.contentState.textInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !self.isActionInProgress + let actionButtonTitle: String + let titleString: String + switch component.mode { + case .create: + titleString = environmentValue.strings.TextProcessing_EditStyle_TitleCreate + actionButtonTitle = environmentValue.strings.TextProcessing_EditStyle_ActionCreate + case .edit: + titleString = environmentValue.strings.TextProcessing_EditStyle_TitleEdit + actionButtonTitle = environmentValue.strings.TextProcessing_EditStyle_ActionEdit + } + + let sheetSize = self.sheet.update( + transition: transition, + component: AnyComponent(ResizableSheetComponent( + content: AnyComponent(TextStyleEditContentComponent( + externalState: self.contentState, + context: component.context, + mode: component.mode, + styleDeleted: component.styleDeleted + )), + titleItem: AnyComponent(TitleComponent( + theme: theme, + title: titleString + )), + leftItem: AnyComponent( + GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + dismiss(true) + } + ) + ), + rightItem: nil, + bottomItem: AnyComponent( + ActionButtonsComponent( + theme: theme, + strings: environmentValue.strings, + actionTitle: actionButtonTitle, + displayProgress: self.isActionInProgress, + action: isMainActionEnabled ? performMainAction : nil + ) + ), + backgroundColor: .color(theme.list.blocksBackgroundColor), + animateOut: self.animateOut + )), + environment: { + environmentValue + ResizableSheetComponentEnvironment( + theme: theme, + statusBarHeight: environmentValue.statusBarHeight, + safeInsets: environmentValue.safeInsets, + inputHeight: environmentValue.inputHeight, + metrics: environmentValue.metrics, + deviceMetrics: environmentValue.deviceMetrics, + isDisplaying: environmentValue.isVisible, + isCentered: environmentValue.metrics.widthClass == .regular, + screenSize: availableSize, + regularMetricsSize: nil, + dismiss: { animated in + dismiss(animated) + } + ) + }, + containerSize: availableSize + ) + self.sheet.parentState = state + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class TextStyleEditScreen: ViewControllerComponentContainer { + public enum Mode { + case create + case edit(TelegramComposeAIMessageMode.CloudStyle) + } + + private let context: AccountContext + + public init( + context: AccountContext, + theme: PresentationTheme? = nil, + mode: Mode, + completion: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void, + styleDeleted: @escaping () -> Void + ) async { + self.context = context + + var initialEmojiFile: TelegramMediaFile? + if case let .edit(style) = mode, case let .custom(style) = style.content, let emojiFileId = style.emojiFileId { + initialEmojiFile = await context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get()[emojiFileId] + } + + super.init( + context: context, + component: TextStyleEditSheetComponent( + context: context, + mode: mode, + initialEmojiFile: initialEmojiFile, + completion: completion, + styleDeleted: styleDeleted + ), + navigationBarAppearance: .none, + statusBarStyle: .ignore, + theme: theme.flatMap({ .custom($0) }) ?? .default + ) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public func dismissAnimated() { + if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { + view.dismissAnimated() + } + } +} + +private final class TitleComponent: Component { + let theme: PresentationTheme + let title: String + + init( + theme: PresentationTheme, + title: String + ) { + self.theme = theme + self.title = title + } + + static func ==(lhs: TitleComponent, rhs: TitleComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + return true + } + + final class View: UIView { + private let title = ComponentView() + + private var component: TitleComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: TitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + + return titleSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ActionButtonsComponent: Component { + let theme: PresentationTheme + let strings: PresentationStrings + let actionTitle: String + let displayProgress: Bool + let action: (() -> Void)? + + init( + theme: PresentationTheme, + strings: PresentationStrings, + actionTitle: String, + displayProgress: Bool, + action: (() -> Void)? + ) { + self.theme = theme + self.strings = strings + self.actionTitle = actionTitle + self.displayProgress = displayProgress + self.action = action + } + + static func ==(lhs: ActionButtonsComponent, rhs: ActionButtonsComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.actionTitle != rhs.actionTitle { + return false + } + if lhs.displayProgress != rhs.displayProgress { + return false + } + if (lhs.action == nil) != (rhs.action == nil) { + return false + } + return true + } + + final class View: UIView { + private let actionButton = ComponentView() + + private var component: ActionButtonsComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ActionButtonsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.state = state + + let actionButtonWidth: CGFloat = availableSize.width + + var actionButtonContents: [AnyComponentWithIdentity] = [] + actionButtonContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.actionTitle, font: Font.semibold(17.0), textColor: component.theme.list.itemCheckColors.foregroundColor)) + )))) + + let actionButtonSize = self.actionButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(HStack( + actionButtonContents, + spacing: 6.0 + )) + ), + isEnabled: component.action != nil, + displaysProgress: component.displayProgress, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.action?() + } + )), + environment: {}, + containerSize: CGSize(width: actionButtonWidth, height: availableSize.height) + ) + let actionButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: actionButtonSize) + if let actionButtonView = self.actionButton.view { + if actionButtonView.superview == nil { + self.addSubview(actionButtonView) + } + transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + } + + return CGSize(width: availableSize.width, height: actionButtonSize.height) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/TimeSelectionActionSheet/Sources/TimeSelectionActionSheet.swift b/submodules/TelegramUI/Components/TimeSelectionActionSheet/Sources/TimeSelectionActionSheet.swift deleted file mode 100644 index 8ec3ff48af..0000000000 --- a/submodules/TelegramUI/Components/TimeSelectionActionSheet/Sources/TimeSelectionActionSheet.swift +++ /dev/null @@ -1,132 +0,0 @@ -import Foundation -import Display -import AsyncDisplayKit -import UIKit -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import TelegramStringFormatting -import AccountContext -import UIKitRuntimeUtils - -public final class TimeSelectionActionSheet: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override public var ready: Promise { - return self._ready - } - - public init(context: AccountContext, currentValue: Int32, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - - self._ready.set(.single(true)) - - var updatedValue = currentValue - var items: [ActionSheetItem] = [] - items.append(TimeSelectionActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in - updatedValue = value - })) - if let emptyTitle = emptyTitle { - items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in - self?.dismissAnimated() - applyValue(nil) - })) - } - items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in - self?.dismissAnimated() - applyValue(updatedValue) - })) - self.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in - self?.dismissAnimated() - }), - ]) - ]) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class TimeSelectionActionSheetItem: ActionSheetItem { - let strings: PresentationStrings - - let currentValue: Int32 - let valueChanged: (Int32) -> Void - - init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.strings = strings - self.currentValue = currentValue - self.valueChanged = valueChanged - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return TimeSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private final class TimeSelectionActionSheetItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - - private let valueChanged: (Int32) -> Void - private let pickerView: UIDatePicker - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme - self.strings = strings - self.valueChanged = valueChanged - - UILabel.setDateLabel(theme.primaryTextColor) - - self.pickerView = UIDatePicker() - self.pickerView.datePickerMode = .countDownTimer - self.pickerView.datePickerMode = .time - self.pickerView.timeZone = TimeZone(secondsFromGMT: 0) - self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue)) - self.pickerView.locale = Locale.current - if #available(iOS 13.4, *) { - self.pickerView.preferredDatePickerStyle = .wheels - } - self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor") - - super.init(theme: theme) - - self.view.addSubview(self.pickerView) - self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged) - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 216.0) - - self.pickerView.frame = CGRect(origin: CGPoint(), size: size) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } - - @objc private func datePickerUpdated() { - self.valueChanged(Int32(self.pickerView.date.timeIntervalSince1970)) - } -} - diff --git a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift index 03e2207b99..4b8f594141 100644 --- a/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/TokenListTextField/Sources/EditableTokenListNode.swift @@ -304,6 +304,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.scrollNode = ASScrollNode() self.scrollNode.view.alwaysBounceVertical = true + self.scrollNode.view.scrollsToTop = false self.placeholderNode = ASTextNode() self.placeholderNode.isUserInteractionEnabled = false @@ -312,6 +313,7 @@ final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: theme.placeholderTextColor) self.textFieldScrollNode = ASScrollNode() + self.textFieldScrollNode.view.scrollsToTop = false self.textFieldNode = TextFieldNode() self.textFieldNode.textField.font = Font.regular(15.0) diff --git a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD index 7ed891c752..1d3dbb87da 100644 --- a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/LocalizedPeerData", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift index cf04513bd7..a2ad5a9382 100644 --- a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift +++ b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/ChatTranslationPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData @@ -394,7 +393,7 @@ final class ChatTranslationPanelNode: ASDisplayNode { let cocoonPath = getAppBundle().url(forResource: "Cocoon", withExtension: "tgs")?.path ?? "" let cocoonFile = TelegramMediaFile( - fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: -123456789), + fileId: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: -123456789), partialReference: nil, resource: BundleResource(name: "Cocoon", path: cocoonPath), previewRepresentations: [], diff --git a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/TranslateHeaderPanelComponent.swift b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/TranslateHeaderPanelComponent.swift index a684a8b9ac..8a0cb46451 100644 --- a/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/TranslateHeaderPanelComponent.swift +++ b/submodules/TelegramUI/Components/TranslateHeaderPanelComponent/Sources/TranslateHeaderPanelComponent.swift @@ -7,7 +7,6 @@ import ComponentDisplayAdapters import AccountContext import TelegramCore import SwiftSignalKit -import Postbox import PresentationDataUtils public final class TranslateHeaderPanelComponent: Component { diff --git a/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/BUILD b/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/BUILD index c56da66785..1fd642ae5a 100644 --- a/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/BUILD +++ b/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/BUILD @@ -15,7 +15,6 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/AccountContext", "//submodules/SSignalKit/SwiftSignalKit", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/LocalizedPeerData", "//submodules/TelegramStringFormatting", diff --git a/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/Sources/TranslationLanguagesContextMenuContent.swift b/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/Sources/TranslationLanguagesContextMenuContent.swift index 020a6f1f6b..3dc429d3ac 100644 --- a/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/Sources/TranslationLanguagesContextMenuContent.swift +++ b/submodules/TelegramUI/Components/TranslationLanguagesContextMenuContent/Sources/TranslationLanguagesContextMenuContent.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramStringFormatting @@ -261,6 +260,7 @@ public final class TranslationLanguagesContextMenuContent: ContextControllerItem self.scrollNode.view.contentInsetAdjustmentBehavior = .never } self.scrollNode.clipsToBounds = false + self.scrollNode.view.scrollsToTop = false super.init() diff --git a/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift b/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift index 0b735bed62..e65b81b067 100644 --- a/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift +++ b/submodules/TelegramUI/Components/Utils/AnimatableProperty/Sources/AnimatableProperty.swift @@ -74,6 +74,8 @@ public final class AnimatableProperty { break case .easeInOut: t = listViewAnimationCurveEaseInOut(t) + case .easeIn: + t = listViewAnimationCurveEaseIn(t) case .spring: t = lookupSpringValue(t) case let .custom(x1, y1, x2, y2): diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD index 3a2073718b..85935d5916 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/BUILD @@ -12,7 +12,6 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit", "//submodules/Display", - "//submodules/Postbox", "//submodules/TelegramCore", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/ComponentFlow", diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 4cc16219bc..cf28d4c811 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -8,7 +8,6 @@ import ViewControllerComponent import ComponentDisplayAdapters import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import PresentationDataUtils import Camera @@ -441,7 +440,7 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { } }, error: { [weak self] _ in if let self, let controller = self.getController() { - controller.completion(nil, nil, nil) + controller.completion(nil, nil, nil, nil) } })) } @@ -536,7 +535,7 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { let availableSize = context.component.containerSize var sideInset: CGFloat = 8.0 - if component.safeInsets.bottom <= 32.0 { + if component.safeInsets.bottom <= 32.0 && environment.inputHeight == 0.0 { sideInset += 18.0 } @@ -575,14 +574,18 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { if !component.isPreviewing { if case .on = component.cameraState.flashMode, case .front = component.cameraState.position { + let frontFlashPosition = CGPoint(x: component.previewFrame.midX, y: component.previewFrame.midY) + let frontFlashSize = CGSize( + width: max(abs(frontFlashPosition.x), abs(context.availableSize.width - frontFlashPosition.x)) * 2.0, + height: max(abs(frontFlashPosition.y), abs(context.availableSize.height - frontFlashPosition.y)) * 2.0 + ) let frontFlash = frontFlash.update( - component: Image(image: state.image(.flashImage, theme: environment.theme), tintColor: component.cameraState.flashTint.color), - availableSize: context.availableSize, + component: Image(image: state.image(.flashImage, theme: environment.theme), tintColor: component.cameraState.flashTint.color, contentMode: .scaleAspectFill), + availableSize: frontFlashSize, transition: .easeInOut(duration: 0.2) ) - let frontFlashFrame = CGRect(origin: CGPoint(), size: context.availableSize) context.add(frontFlash - .position(frontFlashFrame.center) + .position(frontFlashPosition) .scale(1.5 - component.cameraState.flashTintSize * 0.5) .appear(.default(alpha: true)) .disappear(ComponentTransition.Disappear({ view, transition, completion in @@ -1272,7 +1275,7 @@ public class VideoMessageCameraScreen: ViewController { let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileReferenceMediaResource(localFilePath: path, randomId: id) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: Int64(data.count), attributes: [.FileName(fileName: "video.mp4")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: Int64(data.count), attributes: [.FileName(fileName: "video.mp4")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: self.context.engine.account, peerId: self.context.engine.account.peerId, messages: [message]).startStandalone() @@ -1641,7 +1644,7 @@ public class VideoMessageCameraScreen: ViewController { fileprivate var allowLiveUpload: Bool fileprivate var viewOnceAvailable: Bool - fileprivate let completion: (EnqueueMessage?, Bool?, Int32?) -> Void + fileprivate let completion: (EnqueueMessage?, Bool?, Int32?, Int32?) -> Void private var audioSessionDisposable: Disposable? @@ -1794,7 +1797,7 @@ public class VideoMessageCameraScreen: ViewController { viewOnceAvailable: Bool, inputPanelFrame: (CGRect, Bool), chatNode: ASDisplayNode?, - completion: @escaping (EnqueueMessage?, Bool?, Int32?) -> Void + completion: @escaping (EnqueueMessage?, Bool?, Int32?, Int32?) -> Void ) { self.context = context self.updatedPresentationData = updatedPresentationData @@ -1833,7 +1836,7 @@ public class VideoMessageCameraScreen: ViewController { fileprivate var didSend = false fileprivate var lastActionTimestamp: Double? fileprivate var isSendingImmediately = false - public func sendVideoRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, messageEffect: ChatSendMessageEffect? = nil) { + public func sendVideoRecording(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, messageEffect: ChatSendMessageEffect? = nil) { guard !self.didSend else { return } @@ -1845,7 +1848,7 @@ public class VideoMessageCameraScreen: ViewController { } if case .none = self.cameraState.recording, self.node.results.isEmpty { - self.completion(nil, nil, nil) + self.completion(nil, nil, nil, nil) return } @@ -1859,7 +1862,7 @@ public class VideoMessageCameraScreen: ViewController { self.waitingForNextResult = true self.node.stopRecording.invoke(Void()) } else { - self.completion(nil, nil, nil) + self.completion(nil, nil, nil, nil) return } } @@ -1889,7 +1892,7 @@ public class VideoMessageCameraScreen: ViewController { } if duration < 1.0 { - self.completion(nil, nil, nil) + self.completion(nil, nil, nil, nil) return } @@ -1937,12 +1940,53 @@ public class VideoMessageCameraScreen: ViewController { guard let self else { return } - let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, collage: [], nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, collageTrackSamples: nil, coverImageTimestamp: nil, coverDimensions: nil, qualityPreset: .videoMessage) + let values = MediaEditorValues( + peerId: self.context.account.peerId, + originalDimensions: dimensions, + cropOffset: .zero, + cropRect: CGRect(origin: .zero, size: dimensions.cgSize), + cropScale: 1.0, + cropRotation: 0.0, + cropMirroring: false, + cropOrientation: nil, + gradientColors: nil, + videoTrimRange: self.node.previewState?.trimRange, + videoBounce: false, + videoIsMuted: false, + videoIsFullHd: false, + videoIsMirrored: false, + videoVolume: nil, + additionalVideoPath: nil, + additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], + additionalVideoPosition: nil, + additionalVideoScale: nil, + additionalVideoRotation: nil, + additionalVideoPositionChanges: [], + additionalVideoTrimRange: nil, + additionalVideoOffset: nil, + additionalVideoVolume: nil, + collage: [], + nightTheme: false, + drawing: nil, + maskDrawing: nil, + entities: [], + toolValues: [:], + audioTrack: nil, + audioTrackTrimRange: nil, + audioTrackOffset: nil, + audioTrackVolume: nil, + audioTrackSamples: nil, + collageTrackSamples: nil, + coverImageTimestamp: nil, + coverDimensions: nil, + qualityPreset: .videoMessage + ) var resourceAdjustments: VideoMediaResourceAdjustments? = nil if let valuesData = try? JSONEncoder().encode(values) { - let data = MemoryBuffer(data: valuesData) - let digest = MemoryBuffer(data: data.md5Digest()) + let data = EngineMemoryBuffer(data: valuesData) + let digest = EngineMemoryBuffer(data: data.md5Digest()) resourceAdjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: false) } @@ -1955,7 +1999,7 @@ public class VideoMessageCameraScreen: ViewController { } if !hasAdjustments, let liveUploadData, let data = try? Data(contentsOf: URL(fileURLWithPath: video.videoPath)) { resource = LocalFileMediaResource(fileId: liveUploadData.id) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) } else { resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), paths: videoPaths, adjustments: resourceAdjustments) } @@ -1965,21 +2009,21 @@ public class VideoMessageCameraScreen: ViewController { let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) let thumbnailSize = video.dimensions.cgSize.aspectFitted(CGSize(width: 320.0, height: 320.0)) if let thumbnailData = scaleImageToPixelSize(image: thumbnailImage, size: thumbnailSize)?.jpegData(compressionQuality: 0.4) { - self.context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(thumbnailResource.id), data: thumbnailData) previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } - let tempFile = TempBox.shared.tempFile(fileName: "file") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file") defer { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } if let data = compressImageToJPEG(thumbnailImage, quality: 0.7, tempFilePath: tempFile.path) { context.account.postbox.mediaBox.storeCachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), data: data) } - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if self.cameraState.isViewOnceEnabled { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) } @@ -1998,7 +2042,7 @@ public class VideoMessageCameraScreen: ViewController { localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [] - ), silentPosting, scheduleTime) + ), silentPosting, scheduleTime, repeatPeriod) }) }) } diff --git a/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD index 2bbdb275af..8d26154271 100644 --- a/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/TelegramCore", ], visibility = [ diff --git a/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift index f2863df6c2..bbd149be09 100644 --- a/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/Components/WallpaperPreviewMedia/Sources/WallpaperPreviewMedia.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore public enum WallpaperPreviewMediaContent: Equatable { @@ -12,11 +11,11 @@ public enum WallpaperPreviewMediaContent: Equatable { case emoticon(String) } -public final class WallpaperPreviewMedia: Media { - public var id: MediaId? { +public final class WallpaperPreviewMedia: EngineRawMedia { + public var id: EngineMedia.Id? { return nil } - public let peerIds: [PeerId] = [] + public let peerIds: [EnginePeer.Id] = [] public let content: WallpaperPreviewMediaContent @@ -24,14 +23,14 @@ public final class WallpaperPreviewMedia: Media { self.content = content } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.content = .color(.clear) } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { } - public func isEqual(to other: Media) -> Bool { + public func isEqual(to other: EngineRawMedia) -> Bool { guard let other = other as? WallpaperPreviewMedia else { return false } @@ -43,7 +42,7 @@ public final class WallpaperPreviewMedia: Media { return true } - public func isSemanticallyEqual(to other: Media) -> Bool { + public func isSemanticallyEqual(to other: EngineRawMedia) -> Bool { return self.isEqual(to: other) } } @@ -73,11 +72,11 @@ public extension WallpaperPreviewMedia { } } -public final class UniqueGiftPreviewMedia: Media { - public var id: MediaId? { +public final class UniqueGiftPreviewMedia: EngineRawMedia { + public var id: EngineMedia.Id? { return nil } - public let peerIds: [PeerId] = [] + public let peerIds: [EnginePeer.Id] = [] public let content: StarGift.UniqueGift? @@ -85,14 +84,14 @@ public final class UniqueGiftPreviewMedia: Media { self.content = content } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.content = nil } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { } - public func isEqual(to other: Media) -> Bool { + public func isEqual(to other: EngineRawMedia) -> Bool { guard let other = other as? UniqueGiftPreviewMedia else { return false } @@ -104,17 +103,17 @@ public final class UniqueGiftPreviewMedia: Media { return true } - public func isSemanticallyEqual(to other: Media) -> Bool { + public func isSemanticallyEqual(to other: EngineRawMedia) -> Bool { return self.isEqual(to: other) } } -public final class GiftAuctionPreviewMedia: Media { - public var id: MediaId? { +public final class GiftAuctionPreviewMedia: EngineRawMedia { + public var id: EngineMedia.Id? { return nil } - public let peerIds: [PeerId] = [] + public let peerIds: [EnginePeer.Id] = [] public let content: StarGift.Gift? public let endTime: Int32 @@ -124,15 +123,15 @@ public final class GiftAuctionPreviewMedia: Media { self.endTime = endTime } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.content = nil self.endTime = 0 } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { } - public func isEqual(to other: Media) -> Bool { + public func isEqual(to other: EngineRawMedia) -> Bool { guard let other = other as? GiftAuctionPreviewMedia else { return false } @@ -144,7 +143,7 @@ public final class GiftAuctionPreviewMedia: Media { return true } - public func isSemanticallyEqual(to other: Media) -> Bool { + public func isSemanticallyEqual(to other: EngineRawMedia) -> Bool { return self.isEqual(to: other) } } diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Avatar8.pdf b/submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Avatar8.pdf deleted file mode 100644 index 6c97d03a30..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Avatar8.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@2x.png deleted file mode 100644 index 1d51eb1725..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@3x.png deleted file mode 100644 index 95e99b4db2..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/CallCancelIcon@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/Contents.json deleted file mode 100644 index 313c758607..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Call/CallCancelButton.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "CallCancelIcon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "CallCancelIcon@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@2x.png b/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@2x.png deleted file mode 100644 index 55a0de9e65..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@3x.png b/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@3x.png deleted file mode 100644 index 0f680c2fe6..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/CallRouteBluetooth@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/Contents.json deleted file mode 100644 index d221f407ce..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Call/CallRouteBluetooth.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "CallRouteBluetooth@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "CallRouteBluetooth@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/ic_calls_speaker.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/ic_calls_speaker.pdf deleted file mode 100644 index 22af6e39c6..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/ic_calls_speaker.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/Contents.json deleted file mode 100644 index 2ef9a3ee1a..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@2x_Before_b542dc3fd7b381de80e554286a94ccd5b8d02154.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@3x_Before_c8c1c96f16a3977d2a6d1956a0aeb31768c6bb23.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@2x_Before_b542dc3fd7b381de80e554286a94ccd5b8d02154.png b/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@2x_Before_b542dc3fd7b381de80e554286a94ccd5b8d02154.png deleted file mode 100644 index 9b5e566eb4..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@2x_Before_b542dc3fd7b381de80e554286a94ccd5b8d02154.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@3x_Before_c8c1c96f16a3977d2a6d1956a0aeb31768c6bb23.png b/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@3x_Before_c8c1c96f16a3977d2a6d1956a0aeb31768c6bb23.png deleted file mode 100644 index 0026e6063d..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/LegacyCallRouteSpeaker.imageset/submodules_TelegramUI_Images.xcassets_Call_CallRouteSpeaker.imageset_CallRouteSpeaker@3x_Before_c8c1c96f16a3977d2a6d1956a0aeb31768c6bb23.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/ic_voicevolumeoff.pdf b/submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/ic_voicevolumeoff.pdf deleted file mode 100644 index bcc068ce30..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/ic_voicevolumeoff.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/ic_cam_flip.pdf b/submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/ic_cam_flip.pdf deleted file mode 100644 index 5084218a29..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/ic_cam_flip.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/Contents.json similarity index 74% rename from submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/Contents.json index d0d69abee5..da60b22b56 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_calls_speaker.pdf", + "filename" : "incoming_call_20.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/incoming_call_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/incoming_call_20.pdf new file mode 100644 index 0000000000..af90dd7613 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/CallIncomingIcon.imageset/incoming_call_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/Contents.json new file mode 100644 index 0000000000..9b101ab4da --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "outgoing_call_20.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/outgoing_call_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/outgoing_call_20.pdf new file mode 100644 index 0000000000..61af8fc2bb Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/CallOutgoingIcon.imageset/outgoing_call_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/Contents.json similarity index 71% rename from submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/Contents.json index 95ca49b9d3..c409ed977a 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/StatusVolumeOff.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_voicevolumeoff.pdf", + "filename" : "incoming_video_call_20.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/incoming_video_call_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/incoming_video_call_20.pdf new file mode 100644 index 0000000000..979cb9e3af Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoIncomingIcon.imageset/incoming_video_call_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/Contents.json new file mode 100644 index 0000000000..01d2bc12c2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "outgoing_video_call_20.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/outgoing_video_call_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/outgoing_video_call_20.pdf new file mode 100644 index 0000000000..bb7773265c Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/CallVideoOutgoingIcon.imageset/outgoing_video_call_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/Contents.json new file mode 100644 index 0000000000..7375ad0121 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gamepad_20.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/gamepad_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/gamepad_20.pdf new file mode 100644 index 0000000000..7304fb98ad Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/GameIcon.imageset/gamepad_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/Contents.json new file mode 100644 index 0000000000..d2f8bb359c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "checklist_20.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/checklist_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/checklist_20.pdf new file mode 100644 index 0000000000..63fe6b59e9 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/TodoIcon.imageset/checklist_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/Contents.json new file mode 100644 index 0000000000..75aa24b443 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voice_message_20.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/voice_message_20.pdf b/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/voice_message_20.pdf new file mode 100644 index 0000000000..4d0f9fb5dc Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat List/VoiceMessageIcon.imageset/voice_message_20.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/Contents.json new file mode 100644 index 0000000000..c2b74e56fe --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "linktab_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/linktab_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/linktab_30.pdf new file mode 100644 index 0000000000..acf61dd694 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Link.imageset/linktab_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/Contents.json similarity index 77% rename from submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/Contents.json index b48dcd4e15..fb844031d6 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "18off_24.pdf", + "filename" : "addmusic.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/addmusic.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/addmusic.pdf new file mode 100644 index 0000000000..6939e1fdab Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AddMusic.imageset/addmusic.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf deleted file mode 100644 index 356e934807..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/18on_24.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf deleted file mode 100644 index 974496474d..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeUnmark.imageset/18off_24.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/Contents.json deleted file mode 100644 index 1caab208d5..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "hidecaption_24.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/hidecaption_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/hidecaption_24.pdf deleted file mode 100644 index 3d86f95ada..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionHide.imageset/hidecaption_24.pdf +++ /dev/null @@ -1,177 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 3.334961 3.205170 cm -0.000000 0.000000 0.000000 scn -1.135289 17.265064 m -0.875590 17.524763 0.454536 17.524763 0.194837 17.265064 c --0.064862 17.005365 -0.064862 16.584312 0.194837 16.324614 c -16.194838 0.324612 l -16.454536 0.064913 16.875591 0.064913 17.135290 0.324612 c -17.394989 0.584311 17.394989 1.005365 17.135290 1.265064 c -15.270563 3.129791 l -15.665111 3.129791 l -16.032377 3.129791 16.330109 3.427522 16.330109 3.794791 c -16.330109 4.162061 16.032377 4.459791 15.665111 4.459791 c -13.940563 4.459791 l -7.330003 11.070351 l -7.330003 12.694838 l -7.330004 12.718859 l -7.330019 12.978289 7.330032 13.211649 7.314171 13.405788 c -7.297186 13.613665 7.258753 13.834405 7.148529 14.050732 c -6.988900 14.364021 6.734188 14.618734 6.420897 14.778363 c -6.204570 14.888588 5.983830 14.927021 5.775954 14.944005 c -5.581833 14.959866 5.348500 14.959853 5.089100 14.959841 c -5.089095 14.959841 l -5.089057 14.959841 l -5.089017 14.959841 l -5.065003 14.959841 l -3.440514 14.959841 l -1.135289 17.265064 l -h -4.770516 13.629837 m -6.000003 12.400351 l -6.000003 12.694838 l -6.000003 12.985837 5.999486 13.164092 5.988588 13.297483 c -5.983510 13.359637 5.977127 13.397297 5.971728 13.420219 c -5.969160 13.431128 5.967000 13.437864 5.965689 13.441540 c -5.964900 13.443751 5.964303 13.445174 5.963926 13.446011 c -5.963701 13.446509 5.963554 13.446799 5.963490 13.446924 c -5.931373 13.509958 5.880125 13.561207 5.817090 13.593325 c -5.816755 13.593495 5.815236 13.594263 5.811705 13.595523 c -5.808031 13.596835 5.801293 13.598994 5.790386 13.601562 c -5.767462 13.606961 5.729803 13.613344 5.667649 13.618422 c -5.534258 13.629320 5.356003 13.629837 5.065003 13.629837 c -4.770516 13.629837 l -h -0.181478 14.050732 m -0.267737 14.220026 0.381761 14.372214 0.517790 14.501538 c -1.459741 13.559587 l -1.420951 13.529512 1.389032 13.491115 1.366516 13.446924 c -1.366346 13.446590 1.365578 13.445070 1.364318 13.441540 c -1.363006 13.437864 1.360847 13.431128 1.358278 13.420219 c -1.352880 13.397297 1.346497 13.359637 1.341419 13.297483 c -1.330521 13.164092 1.330003 12.985837 1.330003 12.694838 c -1.330003 9.894837 l -1.330003 9.603838 1.330521 9.425583 1.341419 9.292192 c -1.346497 9.230038 1.352880 9.192378 1.358278 9.169455 c -1.360847 9.158547 1.363006 9.151810 1.364318 9.148135 c -1.365578 9.144605 1.366346 9.143085 1.366516 9.142751 c -1.398634 9.079716 1.449882 9.028467 1.512917 8.996350 c -1.513251 8.996180 1.514771 8.995412 1.518302 8.994151 c -1.521976 8.992840 1.528713 8.990681 1.539621 8.988112 c -1.562544 8.982714 1.600204 8.976331 1.662358 8.971252 c -1.795749 8.960355 1.974004 8.959837 2.265003 8.959837 c -5.065003 8.959837 l -5.356003 8.959837 5.534258 8.960355 5.667649 8.971252 c -5.729803 8.976331 5.767462 8.982714 5.790386 8.988112 c -5.801293 8.990681 5.808031 8.992840 5.811705 8.994151 c -5.815236 8.995412 5.816755 8.996180 5.817090 8.996350 c -5.861280 9.018866 5.899678 9.050784 5.929753 9.089574 c -6.871704 8.147623 l -6.742380 8.011595 6.590191 7.897571 6.420897 7.811312 c -6.204570 7.701087 5.983830 7.662654 5.775953 7.645670 c -5.581820 7.629809 5.348469 7.629822 5.089050 7.629836 c -5.089025 7.629836 l -5.065003 7.629837 l -2.265003 7.629837 l -2.240981 7.629836 l -2.240957 7.629836 l -1.981538 7.629822 1.748187 7.629809 1.554053 7.645670 c -1.346177 7.662654 1.125436 7.701087 0.909109 7.811312 c -0.595819 7.970941 0.341107 8.225653 0.181478 8.538943 c -0.071253 8.755270 0.032820 8.976010 0.015836 9.183887 c --0.000024 9.378011 -0.000013 9.611347 0.000001 9.870750 c -0.000001 9.870787 l -0.000001 9.870823 l -0.000001 9.894837 l -0.000001 12.694838 l -0.000001 12.718851 l -0.000001 12.718889 l -0.000001 12.718924 l --0.000013 12.978327 -0.000024 13.211664 0.015836 13.405788 c -0.032820 13.613665 0.071253 13.834405 0.181478 14.050732 c -h -1.665110 4.459791 m -10.559536 4.459791 l -11.889536 3.129791 l -1.665110 3.129791 l -1.297841 3.129791 1.000110 3.427522 1.000110 3.794791 c -1.000110 4.162061 1.297841 4.459791 1.665110 4.459791 c -h -10.665111 14.459792 m -10.297841 14.459792 10.000111 14.162062 10.000111 13.794792 c -10.000111 13.427523 10.297841 13.129791 10.665111 13.129791 c -15.665111 13.129791 l -16.032377 13.129791 16.330109 13.427523 16.330109 13.794792 c -16.330109 14.162062 16.032377 14.459792 15.665111 14.459792 c -10.665111 14.459792 l -h -12.665111 9.459791 m -12.297841 9.459791 12.000111 9.162061 12.000111 8.794791 c -12.000111 8.427522 12.297841 8.129791 12.665111 8.129791 c -15.665111 8.129791 l -16.032377 8.129791 16.330109 8.427522 16.330109 8.794791 c -16.330109 9.162061 16.032377 9.459791 15.665111 9.459791 c -12.665111 9.459791 l -h -f* -n -Q - -endstream -endobj - -3 0 obj - 4618 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000004708 00000 n -0000004731 00000 n -0000004904 00000 n -0000004978 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -5037 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/Contents.json deleted file mode 100644 index 8b6179bcaf..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "showcaption_24.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/showcaption_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/showcaption_24.pdf deleted file mode 100644 index 174e22bccd..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/CaptionShow.imageset/showcaption_24.pdf +++ /dev/null @@ -1,169 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 3.334961 6.334900 cm -0.000000 0.000000 0.000000 scn -2.265003 11.830077 m -2.240989 11.830077 l -2.240949 11.830077 l -1.981532 11.830091 1.748184 11.830104 1.554052 11.814242 c -1.346176 11.797258 1.125435 11.758825 0.909108 11.648602 c -0.595819 11.488972 0.341106 11.234260 0.181477 10.920970 c -0.071253 10.704642 0.032820 10.483902 0.015835 10.276026 c --0.000026 10.081894 -0.000013 9.848544 0.000001 9.589127 c -0.000001 9.589089 l -0.000001 9.565076 l -0.000001 6.765075 l -0.000001 6.741061 l -0.000001 6.741024 l --0.000013 6.481606 -0.000026 6.248257 0.015835 6.054125 c -0.032820 5.846249 0.071253 5.625508 0.181477 5.409181 c -0.341106 5.095891 0.595819 4.841178 0.909108 4.681550 c -1.125435 4.571325 1.346176 4.532892 1.554052 4.515908 c -1.748192 4.500046 1.981551 4.500060 2.240980 4.500074 c -2.265002 4.500075 l -5.065003 4.500075 l -5.089025 4.500074 l -5.348454 4.500060 5.581814 4.500046 5.775953 4.515908 c -5.983829 4.532892 6.204570 4.571325 6.420897 4.681550 c -6.734187 4.841178 6.988900 5.095891 7.148529 5.409181 c -7.258753 5.625508 7.297186 5.846249 7.314170 6.054125 c -7.330032 6.248264 7.330019 6.481624 7.330004 6.741053 c -7.330003 6.765075 l -7.330003 9.565075 l -7.330004 9.589098 l -7.330019 9.848527 7.330032 10.081886 7.314170 10.276026 c -7.297186 10.483902 7.258753 10.704642 7.148529 10.920970 c -6.988900 11.234260 6.734187 11.488972 6.420897 11.648602 c -6.204570 11.758825 5.983829 11.797258 5.775953 11.814242 c -5.581822 11.830104 5.348474 11.830091 5.089057 11.830077 c -5.089017 11.830077 l -5.065003 11.830077 l -2.265003 11.830077 l -h -1.512916 10.463563 m -1.513250 10.463733 1.514770 10.464500 1.518301 10.465761 c -1.521975 10.467072 1.528712 10.469232 1.539620 10.471801 c -1.562544 10.477199 1.600203 10.483582 1.662357 10.488660 c -1.795748 10.499558 1.974003 10.500075 2.265003 10.500075 c -5.065003 10.500075 l -5.356002 10.500075 5.534257 10.499558 5.667649 10.488660 c -5.729803 10.483582 5.767462 10.477199 5.790385 10.471801 c -5.801293 10.469232 5.808030 10.467072 5.811704 10.465761 c -5.815235 10.464500 5.816755 10.463733 5.817090 10.463563 c -5.880124 10.431445 5.931373 10.380197 5.963490 10.317163 c -5.963660 10.316828 5.964428 10.315308 5.965689 10.311777 c -5.967000 10.308103 5.969159 10.301366 5.971728 10.290458 c -5.977127 10.267534 5.983509 10.229876 5.988587 10.167721 c -5.999485 10.034330 6.000003 9.856074 6.000003 9.565075 c -6.000003 6.765075 l -6.000003 6.474076 5.999485 6.295821 5.988587 6.162429 c -5.983509 6.100276 5.977127 6.062616 5.971728 6.039693 c -5.969159 6.028785 5.967000 6.022048 5.965689 6.018374 c -5.964428 6.014843 5.963660 6.013323 5.963490 6.012989 c -5.931373 5.949954 5.880124 5.898705 5.817090 5.866588 c -5.816755 5.866418 5.815235 5.865650 5.811705 5.864389 c -5.808030 5.863078 5.801293 5.860919 5.790385 5.858350 c -5.767462 5.852952 5.729803 5.846569 5.667649 5.841491 c -5.534257 5.830593 5.356002 5.830075 5.065003 5.830075 c -2.265002 5.830075 l -1.974003 5.830075 1.795748 5.830593 1.662357 5.841491 c -1.600203 5.846569 1.562544 5.852952 1.539620 5.858350 c -1.528712 5.860919 1.521975 5.863078 1.518301 5.864389 c -1.514770 5.865650 1.513250 5.866418 1.512916 5.866588 c -1.449882 5.898705 1.398633 5.949954 1.366516 6.012989 c -1.366345 6.013323 1.365577 6.014843 1.364317 6.018373 c -1.363005 6.022048 1.360846 6.028785 1.358277 6.039693 c -1.352879 6.062616 1.346496 6.100276 1.341418 6.162429 c -1.330520 6.295821 1.330003 6.474076 1.330003 6.765075 c -1.330003 9.565076 l -1.330003 9.856075 1.330520 10.034330 1.341418 10.167721 c -1.346496 10.229876 1.352879 10.267534 1.358277 10.290458 c -1.360846 10.301366 1.363005 10.308103 1.364317 10.311777 c -1.365577 10.315308 1.366345 10.316828 1.366516 10.317163 c -1.398633 10.380197 1.449882 10.431445 1.512916 10.463563 c -h -10.665110 11.330029 m -10.297840 11.330029 10.000110 11.032299 10.000110 10.665030 c -10.000110 10.297760 10.297840 10.000030 10.665110 10.000030 c -15.665110 10.000030 l -16.032377 10.000030 16.330109 10.297760 16.330109 10.665030 c -16.330109 11.032299 16.032377 11.330029 15.665110 11.330029 c -10.665110 11.330029 l -h -10.665110 6.330029 m -10.297840 6.330029 10.000110 6.032299 10.000110 5.665030 c -10.000110 5.297760 10.297840 5.000030 10.665110 5.000030 c -15.665110 5.000030 l -16.032377 5.000030 16.330109 5.297760 16.330109 5.665030 c -16.330109 6.032299 16.032377 6.330029 15.665110 6.330029 c -10.665110 6.330029 l -h -1.000109 0.665030 m -1.000109 1.032299 1.297840 1.330029 1.665109 1.330029 c -15.665110 1.330029 l -16.032377 1.330029 16.330109 1.032299 16.330109 0.665030 c -16.330109 0.297760 16.032377 0.000030 15.665110 0.000030 c -1.665109 0.000030 l -1.297840 0.000030 1.000109 0.297760 1.000109 0.665030 c -h -f* -n -Q - -endstream -endobj - -3 0 obj - 4706 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000004796 00000 n -0000004819 00000 n -0000004992 00000 n -0000005066 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -5125 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/Contents.json deleted file mode 100644 index 97b7ab0446..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_lt_darkmode.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/ic_lt_darkmode.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/ic_lt_darkmode.pdf deleted file mode 100644 index 98a41e4986..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DarkMode.imageset/ic_lt_darkmode.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/Contents.json similarity index 77% rename from submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/Contents.json index fb10bc0705..ceda3a93f4 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "shrink_24.pdf", + "filename" : "globe (1).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/globe (1).pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/globe (1).pdf new file mode 100644 index 0000000000..152dee90c7 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Globe.imageset/globe (1).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/shrink_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/shrink_24.pdf deleted file mode 100644 index 0b4710ee5d..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ImageShrink.imageset/shrink_24.pdf +++ /dev/null @@ -1,169 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 3.835083 3.834961 cm -0.000000 0.000000 0.000000 scn -5.436178 16.330017 m -5.465000 16.330017 l -10.865000 16.330017 l -10.893822 16.330017 l -11.709461 16.330023 12.362126 16.330027 12.889546 16.286936 c -13.430926 16.242702 13.898637 16.149773 14.328876 15.930555 c -15.018489 15.579180 15.579163 15.018506 15.930539 14.328892 c -16.149757 13.898653 16.242687 13.430943 16.286919 12.889563 c -16.330009 12.362154 16.330006 11.709505 16.330000 10.893888 c -16.330000 10.893824 l -16.330000 10.865017 l -16.330000 5.465017 l -16.330000 5.436212 l -16.330000 5.436146 l -16.330006 4.620529 16.330009 3.967880 16.286919 3.440471 c -16.242687 2.899091 16.149757 2.431380 15.930539 2.001142 c -15.579163 1.311528 15.018489 0.750854 14.328876 0.399478 c -13.898637 0.180260 13.430927 0.087330 12.889546 0.043098 c -12.362138 0.000008 11.709489 0.000011 10.893871 0.000017 c -10.893806 0.000017 l -10.865000 0.000017 l -5.465000 0.000017 l -5.436194 0.000017 l -5.436130 0.000017 l -4.620512 0.000011 3.967863 0.000008 3.440454 0.043098 c -2.899074 0.087330 2.431364 0.180260 2.001125 0.399478 c -1.311511 0.750854 0.750837 1.311528 0.399461 2.001142 c -0.180244 2.431380 0.087314 2.899090 0.043081 3.440471 c --0.000010 3.967890 -0.000006 4.620555 0.000000 5.436195 c -0.000000 5.465017 l -0.000000 10.865017 l -0.000000 10.893839 l --0.000006 11.709478 -0.000010 12.362144 0.043081 12.889563 c -0.087314 13.430943 0.180244 13.898653 0.399461 14.328892 c -0.750837 15.018506 1.311511 15.579180 2.001125 15.930555 c -2.431364 16.149773 2.899074 16.242702 3.440454 16.286936 c -3.967873 16.330027 4.620538 16.330023 5.436178 16.330017 c -h -3.548759 14.961352 m -3.089627 14.923841 2.816429 14.853280 2.604933 14.745517 c -2.165574 14.521652 1.808364 14.164443 1.584500 13.725084 c -1.476737 13.513588 1.406177 13.240391 1.368664 12.781259 c -1.330518 12.314363 1.330000 11.716068 1.330000 10.865017 c -1.330000 5.465017 l -1.330000 4.613965 1.330518 4.015671 1.368664 3.548775 c -1.406177 3.089643 1.476737 2.816445 1.584500 2.604949 c -1.808364 2.165591 2.165574 1.808381 2.604933 1.584517 c -2.816429 1.476754 3.089627 1.406194 3.548759 1.368681 c -4.015654 1.330534 4.613948 1.330017 5.465000 1.330017 c -10.865000 1.330017 l -11.716052 1.330017 12.314346 1.330534 12.781242 1.368681 c -13.240374 1.406194 13.513572 1.476754 13.725068 1.584517 c -14.164426 1.808381 14.521636 2.165591 14.745501 2.604949 c -14.853263 2.816445 14.923823 3.089643 14.961336 3.548776 c -14.999483 4.015671 15.000000 4.613965 15.000000 5.465017 c -15.000000 10.865017 l -15.000000 11.716068 14.999483 12.314363 14.961336 12.781259 c -14.923823 13.240391 14.853263 13.513588 14.745501 13.725084 c -14.521636 14.164443 14.164426 14.521652 13.725068 14.745517 c -13.513572 14.853280 13.240374 14.923841 12.781241 14.961352 c -12.314346 14.999499 11.716052 15.000017 10.865000 15.000017 c -5.465000 15.000017 l -4.613948 15.000017 4.015654 14.999499 3.548759 14.961352 c -h -7.829878 12.164894 m -7.829878 12.532164 7.532147 12.829895 7.164878 12.829895 c -6.797609 12.829895 6.499878 12.532164 6.499878 12.164894 c -6.499878 10.770347 l -4.635104 12.635120 l -4.375406 12.894819 3.954351 12.894819 3.694653 12.635120 c -3.434954 12.375422 3.434954 11.954367 3.694653 11.694669 c -5.559426 9.829895 l -4.164878 9.829895 l -3.797609 9.829895 3.499878 9.532164 3.499878 9.164894 c -3.499878 8.797626 3.797609 8.499895 4.164878 8.499895 c -7.148878 8.499895 l -7.153147 8.499901 l -7.157188 8.499939 l -7.329931 8.497953 7.503299 8.562863 7.635104 8.694669 c -7.766910 8.826473 7.831820 8.999842 7.829834 9.172585 c -7.829872 9.176626 l -7.829878 9.180895 l -7.829878 12.164894 l -h -9.164833 3.499878 m -8.797565 3.499878 8.499834 3.797609 8.499834 4.164879 c -8.499834 7.148878 l -8.499840 7.153147 l -8.499878 7.157188 l -8.497892 7.329931 8.562802 7.503300 8.694608 7.635104 c -8.826412 7.766910 8.999781 7.831820 9.172523 7.829834 c -9.176565 7.829872 l -9.180834 7.829878 l -12.164833 7.829878 l -12.532103 7.829878 12.829834 7.532147 12.829834 7.164879 c -12.829834 6.797609 12.532103 6.499878 12.164833 6.499878 c -10.770286 6.499878 l -12.635059 4.635104 l -12.894758 4.375405 12.894758 3.954351 12.635059 3.694653 c -12.375360 3.434954 11.954307 3.434954 11.694608 3.694653 c -9.829834 5.559426 l -9.829834 4.164879 l -9.829834 3.797609 9.532103 3.499878 9.164833 3.499878 c -h -f* -n -Q - -endstream -endobj - -3 0 obj - 4327 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000004417 00000 n -0000004440 00000 n -0000004613 00000 n -0000004687 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -4746 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/Contents.json deleted file mode 100644 index 5b28aa91ea..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_mute2hours.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/ic_mute2hours.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/ic_mute2hours.pdf deleted file mode 100644 index 25545ba34e..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Mute2h.imageset/ic_mute2hours.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json deleted file mode 100644 index 96ba4365cd..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "Size=24px, Type=Crossed Out.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Size=24px, Type=Crossed Out.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Size=24px, Type=Crossed Out.pdf deleted file mode 100644 index 0b20e7ad20..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unstar.imageset/Size=24px, Type=Crossed Out.pdf +++ /dev/null @@ -1,165 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 4.834961 4.361450 cm -0.000000 0.000000 0.000000 scn -0.294029 13.569108 m -0.148725 13.203500 0.079447 12.808213 0.043083 12.363134 c --0.000009 11.835724 -0.000005 11.183071 0.000001 10.367449 c -0.000001 10.367412 l -0.000001 10.338589 l -0.000001 2.887971 l -0.000001 2.855927 l -0.000001 2.855909 l --0.000013 2.361413 -0.000024 1.944829 0.029606 1.619491 c -0.058879 1.298060 0.124788 0.933573 0.357819 0.638192 c -0.669126 0.243593 1.142026 0.010736 1.644602 0.004579 c -2.020809 -0.000031 2.349897 0.169960 2.622518 0.342736 c -2.898462 0.517618 3.228661 0.771631 3.620616 1.073153 c -3.645996 1.092677 l -6.594922 3.361081 l -6.992292 3.666750 7.044882 3.691571 7.078142 3.700500 c -7.135041 3.715775 7.194962 3.715775 7.251861 3.700500 c -7.285121 3.691571 7.337711 3.666750 7.735081 3.361081 c -10.684008 1.092677 l -10.709353 1.073179 l -11.101323 0.771646 11.431534 0.517624 11.707485 0.342736 c -11.980106 0.169960 12.309195 -0.000031 12.685401 0.004579 c -13.008502 0.008537 13.319336 0.106193 13.582854 0.280283 c -12.355464 1.507673 l -12.146183 1.646584 11.878798 1.851580 11.494923 2.146868 c -8.545997 4.415272 l -8.477973 4.467776 l -8.477962 4.467785 l -8.199948 4.682729 7.921870 4.897722 7.596705 4.985017 c -7.313910 5.060937 7.016093 5.060937 6.733298 4.985017 c -6.408127 4.897721 6.130046 4.682723 5.852028 4.467772 c -5.784007 4.415271 l -2.835081 2.146868 l -2.410956 1.820618 2.129028 1.604588 1.910557 1.466129 c -1.735109 1.354937 1.664445 1.337146 1.654627 1.334675 c -1.654414 1.334620 l -1.557878 1.337669 1.467334 1.382254 1.406059 1.456912 c -1.405972 1.457113 l -1.401946 1.466402 1.372963 1.533260 1.354124 1.740117 c -1.330665 1.997703 1.330002 2.352881 1.330002 2.887971 c -1.330002 10.338589 l -1.330002 11.189641 1.330519 11.787935 1.368666 12.254830 c -1.375014 12.332534 1.382310 12.404911 1.390571 12.472566 c -0.294029 13.569108 l -h -12.999968 2.744075 m -12.999996 2.790545 13.000002 2.838489 13.000002 2.887970 c -13.000002 10.338589 l -13.000002 11.189640 12.999485 11.787935 12.961338 12.254830 c -12.923825 12.713963 12.853265 12.987160 12.745502 13.198656 c -12.521638 13.638015 12.164428 13.995224 11.725070 14.219089 c -11.513574 14.326852 11.240376 14.397411 10.781243 14.434924 c -10.314348 14.473071 9.716053 14.473588 8.865002 14.473588 c -5.465002 14.473588 l -4.613950 14.473588 4.015655 14.473071 3.548759 14.434924 c -3.089628 14.397411 2.816430 14.326852 2.604934 14.219089 c -2.372593 14.100705 2.163225 13.945032 1.984401 13.759640 c -1.043852 14.700190 l -1.320836 14.983611 1.643658 15.221989 2.001126 15.404127 c -2.431365 15.623345 2.899075 15.716275 3.440455 15.760508 c -3.967874 15.803599 4.620536 15.803595 5.436175 15.803589 c -5.436180 15.803589 l -5.465002 15.803589 l -8.865002 15.803589 l -8.893824 15.803589 l -9.709463 15.803595 10.362128 15.803599 10.889548 15.760508 c -11.430928 15.716275 11.898639 15.623345 12.328877 15.404127 c -13.018491 15.052752 13.579165 14.492078 13.930541 13.802464 c -14.149758 13.372225 14.242688 12.904515 14.286921 12.363134 c -14.330012 11.835714 14.330008 11.183048 14.330002 10.367406 c -14.330002 10.367395 l -14.330002 10.338589 l -14.330002 2.887970 l -14.330002 2.855918 l -14.330016 2.361417 14.330028 1.944830 14.300398 1.619491 c -14.295680 1.567688 14.290010 1.514769 14.282844 1.461199 c -12.999968 2.744075 l -h -f* -n -Q -q -1.000000 0.000000 -0.000000 1.000000 4.500000 3.540039 cm -0.000000 0.000000 0.000000 scn -0.470226 17.930187 m -0.210527 18.189886 -0.210527 18.189886 -0.470226 17.930187 c --0.729925 17.670488 -0.729925 17.249434 -0.470226 16.989735 c -0.470226 17.930187 l -h -15.529774 0.989735 m -15.789473 0.730036 16.210527 0.730036 16.470226 0.989735 c -16.729925 1.249434 16.729925 1.670488 16.470226 1.930187 c -15.529774 0.989735 l -h --0.470226 16.989735 m -15.529774 0.989735 l -16.470226 1.930187 l -0.470226 17.930187 l --0.470226 16.989735 l -h -f -n -Q - -endstream -endobj - -3 0 obj - 3871 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000003961 00000 n -0000003984 00000 n -0000004157 00000 n -0000004231 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -4290 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/Contents.json deleted file mode 100644 index ab416f72c8..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "hideforward_24.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/hideforward_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/hideforward_24.pdf deleted file mode 100644 index b05af4f661..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/UserCrossed.imageset/hideforward_24.pdf +++ /dev/null @@ -1,112 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 3.334961 1.270081 cm -0.000000 0.000000 0.000000 scn -6.330028 15.229959 m -6.330028 16.519543 7.375443 17.564959 8.665028 17.564959 c -9.954613 17.564959 11.000028 16.519543 11.000028 15.229959 c -11.000028 13.940373 9.954613 12.894958 8.665028 12.894958 c -7.375443 12.894958 6.330028 13.940373 6.330028 15.229959 c -h -8.665028 18.894958 m -6.640904 18.894958 5.000028 17.254082 5.000028 15.229959 c -5.000028 13.205835 6.640904 11.564959 8.665028 11.564959 c -10.689151 11.564959 12.330028 13.205835 12.330028 15.229959 c -12.330028 17.254082 10.689151 18.894958 8.665028 18.894958 c -h -2.847365 5.874736 m -3.182212 6.620716 3.727596 7.408447 4.622032 8.012360 c -5.052609 8.303081 5.574740 8.558434 6.209703 8.744807 c -5.164166 9.790345 l -4.686711 9.600591 4.259488 9.372349 3.877790 9.114632 c -2.736064 8.343752 2.047248 7.340033 1.633996 6.419378 c -1.238339 5.537922 1.434021 4.662781 1.966020 4.035870 c -2.481754 3.428125 3.295742 3.064959 4.165028 3.064959 c -11.889552 3.064959 l -10.559552 4.394958 l -4.165028 4.394958 l -3.653601 4.394958 3.222711 4.610523 2.980097 4.896420 c -2.753749 5.163151 2.677613 5.496559 2.847365 5.874736 c -h -14.796587 3.538785 m -17.135227 1.200146 l -17.394926 0.940447 17.394926 0.519392 17.135227 0.259693 c -16.875528 -0.000006 16.454473 -0.000006 16.194775 0.259693 c -0.194774 16.259693 l --0.064925 16.519392 -0.064925 16.940447 0.194774 17.200146 c -0.454473 17.459845 0.875527 17.459845 1.135226 17.200146 c -7.960464 10.374907 l -8.188262 10.388149 8.423051 10.394958 8.665028 10.394958 c -10.754458 10.394958 12.307954 9.887258 13.452266 9.114632 c -14.593992 8.343751 15.282808 7.340032 15.696060 6.419377 c -16.091717 5.537921 15.896035 4.662780 15.364036 4.035870 c -15.201860 3.844761 15.010192 3.677837 14.796587 3.538785 c -h -13.815245 4.520127 m -14.036389 4.610582 14.220131 4.743431 14.349958 4.896420 c -14.576306 5.163150 14.652442 5.496557 14.482691 5.874735 c -14.147844 6.620715 13.602460 7.408447 12.708024 8.012359 c -11.919037 8.545074 10.822649 8.959038 9.287930 9.047441 c -13.815245 4.520127 l -h -f* -n -Q - -endstream -endobj - -3 0 obj - 2105 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000002195 00000 n -0000002218 00000 n -0000002391 00000 n -0000002465 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -2524 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/Contents.json deleted file mode 100644 index a2cead8511..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "premiumlockedchat.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/premiumlockedchat.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/premiumlockedchat.pdf deleted file mode 100644 index 56f0ca020c..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Empty Chat/PremiumRequiredIcon.imageset/premiumlockedchat.pdf +++ /dev/null @@ -1,188 +0,0 @@ -%PDF-1.7 - -1 0 obj - << /Type /XObject - /Length 2 0 R - /Group << /Type /Group - /S /Transparency - >> - /Subtype /Form - /Resources << >> - /BBox [ 0.000000 0.000000 120.000000 120.000000 ] - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 26.666748 23.947998 cm -0.000000 0.000000 0.000000 scn -33.333332 67.118652 m -51.742825 67.118652 66.666664 53.551525 66.666664 36.815620 c -66.666664 20.079723 51.742825 6.512592 33.333332 6.512592 c -30.510870 6.512592 27.770340 6.831497 25.152962 7.431835 c -24.657856 7.545395 24.095694 7.026123 23.157236 6.159271 c -22.022297 5.110928 20.337004 3.554222 17.554379 1.993690 c -13.999221 -0.000084 9.073529 0.172890 8.239902 0.522552 c -7.440670 0.857796 8.055842 1.524185 9.139198 2.697731 c -9.888692 3.509621 10.862268 4.564251 11.746614 5.919903 c -13.909238 9.235085 13.015616 13.181854 12.045396 13.892178 c -4.458577 19.446659 0.000000 27.270615 0.000000 36.815620 c -0.000000 53.551525 14.923841 67.118652 33.333332 67.118652 c -h -25.627058 49.241592 m -26.466211 54.000668 31.004473 57.178391 35.763550 56.339237 c -40.522621 55.500084 43.700348 50.961823 42.861195 46.202747 c -41.652504 39.347935 l -42.190289 39.251141 42.644272 39.108437 43.057198 38.898041 c -44.186165 38.322800 45.104050 37.404915 45.679291 36.275944 c -46.333252 34.992474 46.333252 33.312317 46.333252 29.952000 c -46.333252 28.152000 l -46.333252 24.791687 46.333252 23.111526 45.679291 21.828056 c -45.104050 20.699085 44.186165 19.781200 43.057198 19.205959 c -41.773727 18.552002 40.093567 18.552002 36.733253 18.552002 c -29.933254 18.552002 l -26.572937 18.552002 24.892780 18.552002 23.609308 19.205959 c -22.480337 19.781200 21.562452 20.699085 20.987213 21.828056 c -20.333252 23.111526 20.333252 24.791687 20.333252 28.152000 c -20.333252 29.952000 l -20.333252 33.312317 20.333252 34.992474 20.987213 36.275944 c -21.562452 37.404915 22.480337 38.322800 23.609308 38.898041 c -24.892780 39.552002 26.572937 39.552002 29.933252 39.552002 c -36.733253 39.552002 l -37.045204 39.552002 37.342670 39.552002 37.626686 39.551476 c -38.921963 46.897339 l -39.377502 49.480839 37.652451 51.944466 35.068954 52.400005 c -32.485458 52.855545 30.021830 51.130497 29.566288 48.546997 c -29.436054 47.808395 l -29.244247 46.720604 28.206930 45.994267 27.119141 46.186073 c -26.031353 46.377880 25.305016 47.415199 25.496822 48.502983 c -25.627058 49.241592 l -h -f* -n -Q - -endstream -endobj - -2 0 obj - 2182 -endobj - -3 0 obj - << /Type /XObject - /Length 4 0 R - /Group << /Type /Group - /S /Transparency - >> - /Subtype /Form - /Resources << >> - /BBox [ 0.000000 0.000000 120.000000 120.000000 ] - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 10.000000 9.000000 cm -0.000000 0.000000 0.000000 scn -0.000000 62.666668 m -0.000000 75.734558 0.000000 82.268509 2.543181 87.259781 c -4.780226 91.650230 8.349772 95.219772 12.740221 97.456818 c -17.731495 100.000000 24.265440 100.000000 37.333332 100.000000 c -62.666668 100.000000 l -75.734558 100.000000 82.268509 100.000000 87.259781 97.456818 c -91.650230 95.219772 95.219772 91.650230 97.456818 87.259781 c -100.000000 82.268509 100.000000 75.734558 100.000000 62.666668 c -100.000000 37.333332 l -100.000000 24.265442 100.000000 17.731491 97.456818 12.740219 c -95.219772 8.349770 91.650230 4.780228 87.259781 2.543182 c -82.268509 0.000000 75.734558 0.000000 62.666668 0.000000 c -37.333332 0.000000 l -24.265440 0.000000 17.731495 0.000000 12.740221 2.543182 c -8.349772 4.780228 4.780226 8.349770 2.543181 12.740219 c -0.000000 17.731491 0.000000 24.265442 0.000000 37.333332 c -0.000000 62.666668 l -h -f -n -Q - -endstream -endobj - -4 0 obj - 969 -endobj - -5 0 obj - << /XObject << /X1 1 0 R >> - /ExtGState << /E1 << /SMask << /Type /Mask - /G 3 0 R - /S /Alpha - >> - /Type /ExtGState - >> >> - >> -endobj - -6 0 obj - << /Length 7 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -/E1 gs -/X1 Do -Q - -endstream -endobj - -7 0 obj - 46 -endobj - -8 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 120.000000 120.000000 ] - /Resources 5 0 R - /Contents 6 0 R - /Parent 9 0 R - >> -endobj - -9 0 obj - << /Kids [ 8 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -10 0 obj - << /Pages 9 0 R - /Type /Catalog - >> -endobj - -xref -0 11 -0000000000 65535 f -0000000010 00000 n -0000002442 00000 n -0000002465 00000 n -0000003684 00000 n -0000003706 00000 n -0000004004 00000 n -0000004106 00000 n -0000004127 00000 n -0000004302 00000 n -0000004376 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 10 0 R - /Size 11 ->> -startxref -4436 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/search_24.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/search_24.svg deleted file mode 100644 index aebf081900..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/search_24.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/Contents.json deleted file mode 100644 index dcbff79c27..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "InputReactionIcon.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/InputReactionIcon.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/InputReactionIcon.svg deleted file mode 100644 index 0e358991b8..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconReaction.imageset/InputReactionIcon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png deleted file mode 100644 index 7546315a2c..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Slice 1.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/Contents.json deleted file mode 100644 index 740a85579e..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "addlink_16.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/addlink_16.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/addlink_16.pdf deleted file mode 100644 index 0eb367086f..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExternalLink.imageset/addlink_16.pdf +++ /dev/null @@ -1,236 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 8.500000 7.040154 cm -0.000000 0.000000 0.000000 scn --0.470226 1.930072 m --0.729925 1.670374 -0.729925 1.249319 -0.470226 0.989621 c --0.210527 0.729922 0.210527 0.729922 0.470226 0.989621 c --0.470226 1.930072 l -h -6.470226 6.989621 m -6.729925 7.249319 6.729925 7.670374 6.470226 7.930072 c -6.210527 8.189772 5.789473 8.189772 5.529774 7.930072 c -6.470226 6.989621 l -h -0.470226 0.989621 m -6.470226 6.989621 l -5.529774 7.930072 l --0.470226 1.930072 l -0.470226 0.989621 l -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 9.500000 8.170006 cm -0.000000 0.000000 0.000000 scn -0.000000 6.994994 m --0.367269 6.994994 -0.665000 6.697264 -0.665000 6.329994 c --0.665000 5.962725 -0.367269 5.664994 0.000000 5.664994 c -0.000000 6.994994 l -h -5.000000 6.329994 m -5.665000 6.329994 l -5.665000 6.697264 5.367270 6.994994 5.000000 6.994994 c -5.000000 6.329994 l -h -4.335000 1.329994 m -4.335000 0.962725 4.632730 0.664994 5.000000 0.664994 c -5.367270 0.664994 5.665000 0.962725 5.665000 1.329994 c -4.335000 1.329994 l -h -0.000000 5.664994 m -5.000000 5.664994 l -5.000000 6.994994 l -0.000000 6.994994 l -0.000000 5.664994 l -h -4.335000 6.329994 m -4.335000 1.329994 l -5.665000 1.329994 l -5.665000 6.329994 l -4.335000 6.329994 l -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 3.000000 1.668968 cm -0.000000 0.000000 0.000000 scn -4.000000 10.666032 m -4.367270 10.666032 4.665000 10.963762 4.665000 11.331032 c -4.665000 11.698301 4.367270 11.996032 4.000000 11.996032 c -4.000000 10.666032 l -h -10.665000 5.331032 m -10.665000 5.698301 10.367270 5.996032 10.000000 5.996032 c -9.632730 5.996032 9.335000 5.698301 9.335000 5.331032 c -10.665000 5.331032 l -h -8.907981 1.549019 m -9.209885 0.956499 l -8.907981 1.549019 l -h -9.782013 2.423051 m -10.374533 2.121147 l -9.782013 2.423051 l -h -1.092019 11.113045 m -0.790115 11.705564 l -1.092019 11.113045 l -h -0.217987 10.239013 m --0.374532 10.540916 l -0.217987 10.239013 l -h -4.000000 11.996032 m -3.200000 11.996032 l -3.200000 10.666032 l -4.000000 10.666032 l -4.000000 11.996032 l -h --0.665000 8.131032 m --0.665000 4.531032 l -0.665000 4.531032 l -0.665000 8.131032 l --0.665000 8.131032 l -h -3.200000 0.666032 m -6.800000 0.666032 l -6.800000 1.996032 l -3.200000 1.996032 l -3.200000 0.666032 l -h -10.665000 4.531032 m -10.665000 5.331032 l -9.335000 5.331032 l -9.335000 4.531032 l -10.665000 4.531032 l -h -6.800000 0.666032 m -7.349080 0.666032 7.800883 0.665515 8.167748 0.695489 c -8.542377 0.726097 8.886601 0.791779 9.209885 0.956499 c -8.606077 2.141538 l -8.501536 2.088272 8.351824 2.044960 8.059443 2.021071 c -7.759301 1.996549 7.371026 1.996032 6.800000 1.996032 c -6.800000 0.666032 l -h -9.335000 4.531032 m -9.335000 3.960006 9.334483 3.571731 9.309960 3.271588 c -9.286072 2.979208 9.242760 2.829495 9.189494 2.724955 c -10.374533 2.121147 l -10.539253 2.444430 10.604935 2.788655 10.635543 3.163283 c -10.665517 3.530149 10.665000 3.981952 10.665000 4.531032 c -9.335000 4.531032 l -h -9.209885 0.956499 m -9.711337 1.212002 10.119030 1.619695 10.374533 2.121147 c -9.189494 2.724955 l -9.061502 2.473758 8.857274 2.269529 8.606077 2.141538 c -9.209885 0.956499 l -h --0.665000 4.531032 m --0.665000 3.981952 -0.665517 3.530149 -0.635543 3.163283 c --0.604935 2.788655 -0.539253 2.444430 -0.374532 2.121147 c -0.810506 2.724955 l -0.757240 2.829495 0.713928 2.979208 0.690040 3.271588 c -0.665517 3.571731 0.665000 3.960006 0.665000 4.531032 c --0.665000 4.531032 l -h -3.200000 1.996032 m -2.628974 1.996032 2.240699 1.996549 1.940556 2.021071 c -1.648176 2.044960 1.498463 2.088272 1.393923 2.141538 c -0.790115 0.956499 l -1.113398 0.791779 1.457623 0.726097 1.832252 0.695489 c -2.199117 0.665515 2.650921 0.666032 3.200000 0.666032 c -3.200000 1.996032 l -h --0.374532 2.121147 m --0.119030 1.619695 0.288663 1.212002 0.790115 0.956499 c -1.393923 2.141538 l -1.142726 2.269529 0.938497 2.473758 0.810506 2.724955 c --0.374532 2.121147 l -h -3.200000 11.996032 m -2.650921 11.996032 2.199117 11.996549 1.832252 11.966575 c -1.457623 11.935966 1.113398 11.870285 0.790115 11.705564 c -1.393923 10.520525 l -1.498463 10.573792 1.648176 10.617104 1.940556 10.640992 c -2.240699 10.665515 2.628974 10.666032 3.200000 10.666032 c -3.200000 11.996032 l -h -0.665000 8.131032 m -0.665000 8.702057 0.665517 9.090332 0.690040 9.390476 c -0.713928 9.682856 0.757240 9.832568 0.810506 9.937109 c --0.374532 10.540916 l --0.539253 10.217633 -0.604935 9.873408 -0.635543 9.498780 c --0.665517 9.131915 -0.665000 8.680111 -0.665000 8.131032 c -0.665000 8.131032 l -h -0.790115 11.705564 m -0.288663 11.450062 -0.119030 11.042369 -0.374532 10.540916 c -0.810506 9.937109 l -0.938497 10.188306 1.142726 10.392534 1.393923 10.520525 c -0.790115 11.705564 l -h -f -n -Q - -endstream -endobj - -3 0 obj - 4659 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 16.000000 16.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000004749 00000 n -0000004772 00000 n -0000004945 00000 n -0000005019 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -5078 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/Contents.json deleted file mode 100644 index 6b7b65b4e5..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "peoplenearbyon_24.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/peoplenearbyon_24.pdf b/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/peoplenearbyon_24.pdf deleted file mode 100644 index 1eada00e63..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Contact List/Context Menu/PeopleNearby.imageset/peoplenearbyon_24.pdf +++ /dev/null @@ -1,145 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 2.934998 1.735001 cm -0.000000 0.000000 0.000000 scn -7.330000 17.464998 m -7.330000 18.423212 8.106786 19.199999 9.065001 19.199999 c -10.023214 19.199999 10.800000 18.423212 10.800000 17.464998 c -10.800000 16.506784 10.023214 15.729999 9.065001 15.729999 c -8.106786 15.729999 7.330000 16.506784 7.330000 17.464998 c -h -9.065001 20.529999 m -7.372247 20.529999 6.000000 19.157751 6.000000 17.464998 c -6.000000 15.772245 7.372247 14.399999 9.065001 14.399999 c -10.757753 14.399999 12.130000 15.772245 12.130000 17.464998 c -12.130000 19.157751 10.757753 20.529999 9.065001 20.529999 c -h -1.330000 3.865000 m -1.330000 4.026144 1.400150 4.232668 1.634496 4.483692 c -1.872207 4.738321 2.249572 5.004240 2.774642 5.255713 c -3.677844 5.688284 4.928888 6.035789 6.400000 6.230032 c -6.400001 4.564999 l -6.400001 3.755901 7.055903 3.099998 7.865001 3.099998 c -10.265000 3.099998 l -11.074098 3.099998 11.730000 3.755901 11.730000 4.564999 c -11.730000 6.230032 l -13.201113 6.035789 14.452158 5.688284 15.355359 5.255713 c -15.880429 5.004240 16.257793 4.738321 16.495506 4.483692 c -16.729851 4.232668 16.800003 4.026144 16.800003 3.865000 c -16.800003 3.677490 16.702744 3.422382 16.359842 3.113541 c -16.017097 2.804838 15.483717 2.496361 14.767962 2.223692 c -13.341063 1.680111 11.324945 1.330000 9.065001 1.330000 c -6.805056 1.330000 4.788938 1.680111 3.362040 2.223692 c -2.646284 2.496361 2.112905 2.804838 1.770159 3.113541 c -1.427257 3.422382 1.330000 3.677490 1.330000 3.865000 c -h -2.200152 6.455237 m -3.300211 6.982090 4.758119 7.368791 6.400000 7.570784 c -6.400000 8.099998 l -5.859936 8.099998 l -5.076447 8.099998 4.338713 8.757976 4.479389 9.657219 c -4.588190 10.352705 4.856467 11.405879 5.540887 12.298394 c -6.249626 13.222622 7.373408 13.929998 9.065001 13.929998 c -10.756596 13.929998 11.880377 13.222621 12.589115 12.298393 c -13.273535 11.405878 13.541812 10.352703 13.650612 9.657217 c -13.791287 8.757974 13.053553 8.099998 12.270063 8.099998 c -11.730000 8.099998 l -11.730000 7.570784 l -13.371881 7.368791 14.829790 6.982090 15.929850 6.455237 c -16.545578 6.160346 17.079411 5.807216 17.467701 5.391293 c -17.859352 4.971766 18.130001 4.456234 18.130001 3.865000 c -18.130001 3.168854 17.757156 2.582132 17.249931 2.125290 c -16.742554 1.668306 16.045780 1.287239 15.241435 0.980824 c -13.628130 0.366230 11.444248 0.000000 9.065001 0.000000 c -6.685753 0.000000 4.501871 0.366230 2.888566 0.980824 c -2.084221 1.287239 1.387449 1.668306 0.880069 2.125290 c -0.372844 2.582132 0.000000 3.168854 0.000000 3.865000 c -0.000000 4.456234 0.270649 4.971766 0.662301 5.391293 c -1.050590 5.807216 1.584423 6.160346 2.200152 6.455237 c -h -6.596293 11.489061 m -6.105442 10.848969 5.886520 10.046863 5.793407 9.451655 c -5.793330 9.451145 l -5.800257 9.444448 5.821226 9.429998 5.859936 9.429998 c -7.065000 9.429998 l -7.432269 9.429998 7.730000 9.132269 7.730000 8.764999 c -7.730000 4.564999 l -7.730000 4.490440 7.790442 4.429998 7.865001 4.429998 c -8.533002 4.429998 l -8.533002 6.764999 l -8.533002 7.058815 8.771186 7.296999 9.065001 7.296999 c -9.358817 7.296999 9.597001 7.058815 9.597001 6.764999 c -9.597001 4.429998 l -10.265000 4.429998 l -10.339560 4.429998 10.400000 4.490440 10.400000 4.564999 c -10.400000 8.764999 l -10.400000 8.941368 10.470061 9.110514 10.594772 9.235225 c -10.719484 9.359937 10.888630 9.429998 11.064999 9.429998 c -12.270063 9.429998 l -12.308775 9.429998 12.329742 9.444448 12.336671 9.451145 c -12.336594 9.451655 l -12.243481 10.046862 12.024561 10.848969 11.533709 11.489061 c -11.067177 12.097442 10.327614 12.599998 9.065001 12.599998 c -7.802389 12.599998 7.062826 12.097442 6.596293 11.489061 c -h -f* -n -Q - -endstream -endobj - -3 0 obj - 3674 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000003764 00000 n -0000003787 00000 n -0000003960 00000 n -0000004034 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -4093 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/Contents.json deleted file mode 100644 index 7abcd976aa..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_stopshowme.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/ic_stopshowme.pdf b/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/ic_stopshowme.pdf deleted file mode 100644 index 9cc4c3d65a..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Contact List/MakeInvisibleIcon.imageset/ic_stopshowme.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/Contents.json deleted file mode 100644 index 97cc9be019..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_showme.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/ic_showme.pdf b/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/ic_showme.pdf deleted file mode 100644 index 0c5a6e2133..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Contact List/MakeVisibleIcon.imageset/ic_showme.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/Contents.json deleted file mode 100644 index 77dd2fca4e..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "ic_location.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/ic_location.pdf b/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/ic_location.pdf deleted file mode 100644 index 45afa9b5f7..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Contact List/PeopleNearbyIcon.imageset/ic_location.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/Contents.json deleted file mode 100644 index f6826555b5..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ic_ivclosesmall.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/ic_ivclosesmall.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/ic_ivclosesmall.pdf deleted file mode 100644 index 04f8b15429..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/ic_ivclosesmall.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/Contents.json new file mode 100644 index 0000000000..2cc650de69 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "birthday.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/birthday.pdf b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/birthday.pdf new file mode 100644 index 0000000000..c77d724ac1 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Cake.imageset/birthday.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/Contents.json similarity index 78% rename from submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/Contents.json index 3b6cd02302..43174ee790 100644 --- a/submodules/TelegramUI/Images.xcassets/Avatar/SampleAvatar1.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Avatar8.pdf", + "filename" : "flag.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/flag.pdf b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/flag.pdf new file mode 100644 index 0000000000..7b9163fe79 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Item List/Icons/Flag.imageset/flag.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/Contents.json similarity index 78% rename from submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/Contents.json index eda08930c8..d4f692b39a 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/AgeMark.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "18on_24.pdf", + "filename" : "loc8.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/loc8.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/loc8.pdf new file mode 100644 index 0000000000..c701eec262 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAmerica.imageset/loc8.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/Contents.json similarity index 78% rename from submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/Contents.json index 037cefa85b..b96e295d1a 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryPlaceholder.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Slice 1.png", + "filename" : "loc9.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/loc9.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/loc9.pdf new file mode 100644 index 0000000000..12da3dbe05 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeAsia.imageset/loc9.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/Contents.json new file mode 100644 index 0000000000..4b76f1f60f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "loc7.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/loc7.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/loc7.pdf new file mode 100644 index 0000000000..e511bf1e9a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionGlobeEurope.imageset/loc7.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/Contents.json new file mode 100644 index 0000000000..47751e58bd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "loc11.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/loc11.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/loc11.pdf new file mode 100644 index 0000000000..43ec75dc46 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionHybrid.imageset/loc11.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json index 2468a09a36..0ddc25e04e 100644 --- a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "headerlocation_30.pdf", + "filename" : "loc1.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/loc1.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/loc1.pdf new file mode 100644 index 0000000000..df05f9d099 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/loc1.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/Contents.json new file mode 100644 index 0000000000..2836db899e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "loc2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/loc2.pdf similarity index 59% rename from submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf rename to submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/loc2.pdf index 070e541975..788817e753 100644 Binary files a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf and b/submodules/TelegramUI/Images.xcassets/Location/OptionLocating.imageset/loc2.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json index b67dccfbe4..8980d96ec7 100644 --- a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "headermap_30.pdf", + "filename" : "loc5.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/loc5.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/loc5.pdf new file mode 100644 index 0000000000..4d604fb8c0 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/loc5.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/Contents.json new file mode 100644 index 0000000000..a347dfb45c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "loc4.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/loc4.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/loc4.pdf new file mode 100644 index 0000000000..e06bf01f7a Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Location/OptionTracking.imageset/loc4.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/Contents.json deleted file mode 100644 index 3b8e5cbe3c..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "LocationPinIcon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "LocationPinIcon@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@2x.png deleted file mode 100644 index ab6fd29ccb..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@3x.png deleted file mode 100644 index a57e0d4252..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Location/PinIcon.imageset/LocationPinIcon@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/Contents.json similarity index 83% rename from submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/Contents.json index ee96d83980..b3a12bd66e 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSearchIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "search_24.svg", + "filename" : "tools_30 (4).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/tools_30 (4).pdf similarity index 72% rename from submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf rename to submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/tools_30 (4).pdf index 2bdf30a259..6a93dd33d3 100644 Binary files a/submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf and b/submodules/TelegramUI/Images.xcassets/Media Editor/Adjustments.imageset/tools_30 (4).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/Contents.json similarity index 76% rename from submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/Contents.json index e6f1b93d3e..a75bdb1b8d 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/SwitchCameraIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "ic_cam_flip.pdf", + "filename" : "crop_30 (4).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/crop_30 (4).pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/crop_30 (4).pdf new file mode 100644 index 0000000000..c55d00ce13 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/Crop.imageset/crop_30 (4).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/Contents.json index 6074e4a7f0..912db07593 100644 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "pencil_30.pdf", + "filename" : "pencil_30 (4).pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30 (4).pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30 (4).pdf new file mode 100644 index 0000000000..211becd4a7 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30 (4).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30.pdf deleted file mode 100644 index 20a9307e93..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Pencil.imageset/pencil_30.pdf +++ /dev/null @@ -1,93 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 4.170021 4.169979 cm -1.000000 1.000000 1.000000 scn -1.660000 10.830000 m -1.660000 15.894451 5.765549 20.000000 10.830000 20.000000 c -15.894451 20.000000 20.000000 15.894451 20.000000 10.830000 c -20.000000 7.821688 18.551388 5.151716 16.313671 3.479595 c -14.640235 11.010053 l -14.555845 11.389809 14.219020 11.660000 13.830000 11.660000 c -13.553333 11.660000 l -11.778684 16.983950 l -11.474785 17.895645 10.185216 17.895649 9.881317 16.983952 c -8.106667 11.660000 l -7.830000 11.660000 l -7.440980 11.660000 7.104155 11.389809 7.019765 11.010053 c -5.346330 3.479595 l -3.108612 5.151716 1.660000 7.821688 1.660000 10.830000 c -h -13.164198 10.000000 m -14.815517 2.569059 l -13.610337 1.986547 12.258304 1.660000 10.830000 1.660000 c -9.401696 1.660000 8.049662 1.986547 6.844482 2.569059 c -8.495803 10.000000 l -13.164198 10.000000 l -h -10.830000 21.660000 m -4.848756 21.660000 0.000000 16.811243 0.000000 10.830000 c -0.000000 4.848755 4.848756 0.000000 10.830000 0.000000 c -16.811245 0.000000 21.660000 4.848755 21.660000 10.830000 c -21.660000 16.811243 16.811245 21.660000 10.830000 21.660000 c -h -f* -n -Q - -endstream -endobj - -3 0 obj - 1162 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000001252 00000 n -0000001275 00000 n -0000001448 00000 n -0000001522 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -1581 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/Contents.json deleted file mode 100644 index 71cf2c0e13..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "undo.png", - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/undo.png b/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/undo.png deleted file mode 100644 index 1dc7913d2b..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Media Editor/Redo.imageset/undo.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json new file mode 100644 index 0000000000..bbc3fe0aeb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attachlogo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf new file mode 100644 index 0000000000..b222371180 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Media Editor/Send.imageset/attachlogo.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/Contents.json deleted file mode 100644 index cb8e30c043..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "tools_30.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/tools_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/tools_30.pdf deleted file mode 100644 index 25301318c7..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Tools.imageset/tools_30.pdf +++ /dev/null @@ -1,101 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 4.170021 4.169979 cm -1.000000 1.000000 1.000000 scn -5.830000 21.660000 m -6.288396 21.660000 6.660000 21.288397 6.660000 20.830000 c -6.660000 17.660000 l -20.830000 17.660000 l -21.288397 17.660000 21.660000 17.288397 21.660000 16.830000 c -21.660000 16.371603 21.288397 16.000000 20.830000 16.000000 c -6.660000 16.000000 l -6.660000 12.830000 l -6.660000 12.371604 6.288396 12.000000 5.830000 12.000000 c -5.371603 12.000000 5.000000 12.371604 5.000000 12.830000 c -5.000000 16.000000 l -0.830000 16.000000 l -0.371603 16.000000 0.000000 16.371603 0.000000 16.830000 c -0.000000 17.288397 0.371603 17.660000 0.830000 17.660000 c -5.000000 17.660000 l -5.000000 20.830000 l -5.000000 21.288397 5.371603 21.660000 5.830000 21.660000 c -h -20.830000 3.999998 m -21.288397 3.999998 21.660000 4.371601 21.660000 4.829998 c -21.660000 5.288395 21.288397 5.659998 20.830000 5.659998 c -16.660000 5.659998 l -16.660000 8.830000 l -16.660000 9.288396 16.288397 9.660000 15.830000 9.660000 c -15.371604 9.660000 15.000000 9.288396 15.000000 8.830000 c -15.000000 5.659998 l -0.830000 5.659998 l -0.371603 5.659998 0.000000 5.288395 0.000000 4.829998 c -0.000000 4.371601 0.371603 3.999998 0.830000 3.999998 c -15.000000 3.999998 l -15.000000 0.829998 l -15.000000 0.371603 15.371604 0.000000 15.830000 0.000000 c -16.288397 0.000000 16.660000 0.371603 16.660000 0.829998 c -16.660000 3.999998 l -20.830000 3.999998 l -h -f* -n -Q - -endstream -endobj - -3 0 obj - 1452 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000001542 00000 n -0000001565 00000 n -0000001738 00000 n -0000001812 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -1871 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/Contents.json index 71cf2c0e13..7a253e3f25 100644 --- a/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/Contents.json @@ -1,17 +1,8 @@ { "images" : [ { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "filename" : "undo.png", - "idiom" : "universal", - "scale" : "3x" + "filename" : "undo_30 (4).pdf", + "idiom" : "universal" } ], "info" : { diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo.png b/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo.png deleted file mode 100644 index c2b1063a39..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo_30 (4).pdf similarity index 66% rename from submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf rename to submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo_30 (4).pdf index 3ed9097bd4..deba9dc00c 100644 Binary files a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf and b/submodules/TelegramUI/Images.xcassets/Media Editor/Undo.imageset/undo_30 (4).pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/Contents.json deleted file mode 100644 index e2fa1abb90..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "videosettingsauto_30.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/videosettingsauto_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/videosettingsauto_30.pdf deleted file mode 100644 index d36b2d7686..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQAuto.imageset/videosettingsauto_30.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/Contents.json deleted file mode 100644 index 1f7bfc2bb2..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "videosettingshd_30.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/videosettingshd_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/videosettingshd_30.pdf deleted file mode 100644 index 735db423a8..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQHD.imageset/videosettingshd_30.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/Contents.json deleted file mode 100644 index 8f71976b5f..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "videosettingssd_30.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/videosettingssd_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/videosettingssd_30.pdf deleted file mode 100644 index c77db5874e..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Media Gallery/NavigationSettingsQSD.imageset/videosettingssd_30.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/BackArrow.svg b/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/BackArrow.svg deleted file mode 100644 index 46caf41fd7..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/BackArrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/Contents.json deleted file mode 100644 index 000c70dfaf..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Navigation/BackArrow.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "BackArrow.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/Contents.json new file mode 100644 index 0000000000..e30ddbbd0e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "sharenewgroup.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/sharenewgroup.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/sharenewgroup.pdf new file mode 100644 index 0000000000..6f8900ccef Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Navigation/CreateGroup.imageset/sharenewgroup.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/Contents.json new file mode 100644 index 0000000000..dc5cf9d4a0 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "shareshare.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/shareshare.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/shareshare.pdf new file mode 100644 index 0000000000..81a1664bf5 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Navigation/Share.imageset/shareshare.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/200x200bb-75.jpg b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/200x200bb-75.jpg new file mode 100644 index 0000000000..0249b126a8 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/200x200bb-75.jpg differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Contents.json index d46b1751fe..0fcdd8c48e 100644 --- a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Contents.json @@ -6,17 +6,16 @@ }, { "idiom" : "universal", - "filename" : "Maps@2x.png", "scale" : "2x" }, { + "filename" : "200x200bb-75.jpg", "idiom" : "universal", - "filename" : "Maps@3x.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@2x.png b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@2x.png deleted file mode 100644 index bfccc77337..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@3x.png b/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@3x.png deleted file mode 100644 index 29e2387b08..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Open In/Maps.imageset/Maps@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/200x200bb-100.png b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/200x200bb-100.png new file mode 100644 index 0000000000..baf0736c4b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/200x200bb-100.png differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Contents.json index be50f3de3e..78579e21eb 100644 --- a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Contents.json @@ -6,17 +6,16 @@ }, { "idiom" : "universal", - "filename" : "Safari@2x.png", "scale" : "2x" }, { + "filename" : "200x200bb-100.png", "idiom" : "universal", - "filename" : "Safari@3x.png", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@2x.png b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@2x.png deleted file mode 100644 index a833be5d05..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@3x.png b/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@3x.png deleted file mode 100644 index 9de9152228..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Open In/Safari.imageset/Safari@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/Contents.json deleted file mode 100644 index 6a062695f3..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "username.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/username.pdf b/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/username.pdf deleted file mode 100644 index 548f72b27c..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Peer Info/CollectibleUsernameInfoTitleIcon.imageset/username.pdf +++ /dev/null @@ -1,100 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 19.254883 19.005127 cm -0.000000 0.000000 0.000000 scn -0.000000 25.994989 m -0.000000 40.351631 11.638357 51.989990 25.995001 51.989990 c -40.351643 51.989990 51.989998 40.351631 51.989998 25.994989 c -51.989998 19.994989 l -51.989998 15.579475 48.410515 11.999992 43.994999 11.999992 c -40.680130 11.999992 37.836452 14.017384 36.624741 16.891380 c -34.058002 13.897156 30.248056 11.999989 25.995001 11.999989 c -18.265776 11.999989 12.000000 18.265766 12.000000 25.994989 c -12.000000 33.724213 18.265776 39.989990 25.995001 39.989990 c -29.919403 39.989990 33.466534 38.374702 36.007809 35.772713 c -36.097725 36.791267 36.953087 37.589989 37.994999 37.589989 c -39.096806 37.589989 39.989998 36.696800 39.989998 35.594986 c -39.989998 26.005434 l -39.990002 25.994989 l -39.989998 25.984545 l -39.989998 19.994995 l -39.989998 17.783092 41.783100 15.989990 43.994999 15.989990 c -46.206898 15.989990 48.000000 17.783092 48.000000 19.994989 c -48.000000 25.994989 l -48.000000 38.148018 38.148026 47.999989 25.995001 47.999989 c -13.841973 47.999989 3.990000 38.148018 3.990000 25.994989 c -3.990000 25.793646 3.992699 25.592970 3.998061 25.392994 c -4.317001 13.518284 14.043282 3.989990 25.995001 3.989990 c -29.449270 3.989990 32.712318 4.784481 35.616467 6.198845 c -36.607044 6.681271 37.801151 6.269333 38.283577 5.278755 c -38.766006 4.288174 38.354069 3.094070 37.363487 2.611641 c -33.926983 0.938011 30.067709 -0.000008 25.995001 -0.000008 c -11.638357 -0.000008 0.000000 11.638348 0.000000 25.994989 c -h -25.995001 35.999992 m -31.520609 35.999992 35.999996 31.520597 35.999996 25.994989 c -35.999996 20.469381 31.520609 15.989994 25.995001 15.989994 c -20.469393 15.989994 15.990000 20.469381 15.990000 25.994989 c -15.990000 31.520597 20.469393 35.999992 25.995001 35.999992 c -h -f* -n -Q - -endstream -endobj - -3 0 obj - 1836 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 90.000000 90.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000001926 00000 n -0000001949 00000 n -0000002122 00000 n -0000002196 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -2255 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json deleted file mode 100644 index dc0972e257..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "replacedboost_30.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf deleted file mode 100644 index d6cbcced6a..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/BoostReplaceIcon.imageset/replacedboost_30.pdf +++ /dev/null @@ -1,273 +0,0 @@ -%PDF-1.7 - -1 0 obj - << >> -endobj - -2 0 obj - << /Length 3 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 4.877197 15.157974 cm -0.000000 0.000000 0.000000 scn -5.552186 8.359201 m -5.243867 8.359201 5.008486 8.634665 5.056572 8.939211 c -5.727019 13.185373 l -5.810456 13.713807 5.119398 13.988482 4.817303 13.546960 c -0.088512 6.635651 l --0.139333 6.302646 0.099122 5.850563 0.502614 5.850563 c -2.581887 5.850563 l -2.890206 5.850563 3.125587 5.575099 3.077501 5.270554 c -2.407054 1.024391 l -2.323617 0.495958 3.014675 0.221282 3.316770 0.662805 c -8.045561 7.574114 l -8.273406 7.907119 8.034951 8.359201 7.631459 8.359201 c -5.552186 8.359201 l -h -f* -n -Q -q -1.000000 0.000000 -0.000000 1.000000 16.877197 1.157974 cm -0.000000 0.000000 0.000000 scn -5.552186 8.359201 m -5.243867 8.359201 5.008486 8.634665 5.056572 8.939211 c -5.727019 13.185373 l -5.810456 13.713807 5.119398 13.988482 4.817303 13.546960 c -0.088512 6.635651 l --0.139333 6.302646 0.099122 5.850563 0.502614 5.850563 c -2.581887 5.850563 l -2.890206 5.850563 3.125587 5.575099 3.077501 5.270554 c -2.407054 1.024391 l -2.323617 0.495958 3.014675 0.221282 3.316770 0.662805 c -8.045561 7.574114 l -8.273406 7.907119 8.034951 8.359201 7.631459 8.359201 c -5.552186 8.359201 l -h -f* -n -Q -q -1.000000 0.000000 -0.000000 1.000000 20.000000 15.177917 cm -0.000000 0.000000 0.000000 scn -6.586899 4.235184 m -6.911034 4.559319 6.911034 5.084846 6.586899 5.408981 c -6.262764 5.733116 5.737236 5.733116 5.413101 5.408981 c -6.586899 4.235184 l -h -3.000000 1.822083 m -2.413101 1.235184 l -2.737236 0.911049 3.262764 0.911049 3.586899 1.235184 c -3.000000 1.822083 l -h -0.586899 5.408981 m -0.262763 5.733116 -0.262763 5.733116 -0.586899 5.408981 c --0.911034 5.084846 -0.911034 4.559319 -0.586899 4.235184 c -0.586899 5.408981 l -h -5.413101 5.408981 m -2.413101 2.408981 l -3.586899 1.235184 l -6.586899 4.235184 l -5.413101 5.408981 l -h -3.586899 2.408981 m -0.586899 5.408981 l --0.586899 4.235184 l -2.413101 1.235184 l -3.586899 2.408981 l -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 16.000000 15.339355 cm -0.000000 0.000000 0.000000 scn -0.000000 10.490644 m --0.458396 10.490644 -0.830000 10.119040 -0.830000 9.660645 c --0.830000 9.202249 -0.458396 8.830645 0.000000 8.830645 c -0.000000 10.490644 l -h -6.170000 1.660645 m -6.170000 1.202249 6.541604 0.830645 7.000000 0.830645 c -7.458396 0.830645 7.830000 1.202249 7.830000 1.660645 c -6.170000 1.660645 l -h -6.727516 8.295621 m -7.467052 8.672433 l -6.727516 8.295621 l -h -0.000000 8.830645 m -3.000000 8.830645 l -3.000000 10.490644 l -0.000000 10.490644 l -0.000000 8.830645 l -h -6.170000 5.660645 m -6.170000 1.660645 l -7.830000 1.660645 l -7.830000 5.660645 l -6.170000 5.660645 l -h -3.000000 8.830645 m -3.713761 8.830645 4.199165 8.829999 4.574407 8.799340 c -4.939959 8.769474 5.127283 8.715313 5.258164 8.648625 c -6.011788 10.127696 l -5.607891 10.333492 5.177792 10.415573 4.709584 10.453828 c -4.251065 10.491290 3.686370 10.490644 3.000000 10.490644 c -3.000000 8.830645 l -h -7.830000 5.660645 m -7.830000 6.347014 7.830646 6.911709 7.793183 7.370228 c -7.754929 7.838436 7.672848 8.268535 7.467052 8.672433 c -5.987981 7.918809 l -6.054668 7.787928 6.108829 7.600603 6.138696 7.235051 c -6.169354 6.859809 6.170000 6.374406 6.170000 5.660645 c -7.830000 5.660645 l -h -5.258164 8.648625 m -5.572395 8.488517 5.827872 8.233040 5.987981 7.918809 c -7.467052 8.672433 l -7.147793 9.299013 6.638368 9.808438 6.011788 10.127696 c -5.258164 8.648625 l -h -f -n -Q -q --1.000000 -0.000000 -0.000000 -1.000000 10.000000 14.822083 cm -0.000000 0.000000 0.000000 scn -6.586899 4.235184 m -6.911034 4.559319 6.911034 5.084846 6.586899 5.408981 c -6.262764 5.733116 5.737236 5.733116 5.413101 5.408981 c -6.586899 4.235184 l -h -3.000000 1.822083 m -2.413101 1.235184 l -2.737236 0.911049 3.262764 0.911049 3.586899 1.235184 c -3.000000 1.822083 l -h -0.586899 5.408981 m -0.262763 5.733116 -0.262763 5.733116 -0.586899 5.408981 c --0.911034 5.084846 -0.911034 4.559319 -0.586899 4.235184 c -0.586899 5.408981 l -h -5.413101 5.408981 m -2.413101 2.408981 l -3.586899 1.235184 l -6.586899 4.235184 l -5.413101 5.408981 l -h -3.586899 2.408981 m -0.586899 5.408981 l --0.586899 4.235184 l -2.413101 1.235184 l -3.586899 2.408981 l -h -f -n -Q -q --1.000000 -0.000000 -0.000000 -1.000000 14.000000 13.660645 cm -0.000000 0.000000 0.000000 scn -0.000000 9.490644 m --0.458396 9.490644 -0.830000 9.119040 -0.830000 8.660645 c --0.830000 8.202249 -0.458396 7.830645 0.000000 7.830645 c -0.000000 9.490644 l -h -6.170000 1.660645 m -6.170000 1.202248 6.541604 0.830645 7.000000 0.830645 c -7.458396 0.830645 7.830000 1.202248 7.830000 1.660645 c -6.170000 1.660645 l -h -6.727516 7.295621 m -7.467052 7.672433 l -6.727516 7.295621 l -h -0.000000 7.830645 m -3.000000 7.830645 l -3.000000 9.490644 l -0.000000 9.490644 l -0.000000 7.830645 l -h -6.170000 4.660645 m -6.170000 1.660645 l -7.830000 1.660645 l -7.830000 4.660645 l -6.170000 4.660645 l -h -3.000000 7.830645 m -3.713761 7.830645 4.199165 7.829999 4.574407 7.799341 c -4.939959 7.769474 5.127283 7.715313 5.258164 7.648625 c -6.011788 9.127696 l -5.607891 9.333492 5.177792 9.415573 4.709584 9.453828 c -4.251065 9.491290 3.686370 9.490644 3.000000 9.490644 c -3.000000 7.830645 l -h -7.830000 4.660645 m -7.830000 5.347014 7.830646 5.911709 7.793183 6.370228 c -7.754929 6.838436 7.672848 7.268535 7.467052 7.672433 c -5.987981 6.918809 l -6.054668 6.787928 6.108829 6.600603 6.138696 6.235051 c -6.169354 5.859809 6.170000 5.374406 6.170000 4.660645 c -7.830000 4.660645 l -h -5.258164 7.648625 m -5.572395 7.488517 5.827872 7.233039 5.987981 6.918809 c -7.467052 7.672433 l -7.147793 8.299013 6.638368 8.808438 6.011788 9.127696 c -5.258164 7.648625 l -h -f -n -Q - -endstream -endobj - -3 0 obj - 5530 -endobj - -4 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] - /Resources 1 0 R - /Contents 2 0 R - /Parent 5 0 R - >> -endobj - -5 0 obj - << /Kids [ 4 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -6 0 obj - << /Pages 5 0 R - /Type /Catalog - >> -endobj - -xref -0 7 -0000000000 65535 f -0000000010 00000 n -0000000034 00000 n -0000005620 00000 n -0000005643 00000 n -0000005816 00000 n -0000005890 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 6 0 R - /Size 7 ->> -startxref -5949 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/Contents.json deleted file mode 100644 index c60ea81e07..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "addreactions2_32.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/addreactions2_32.pdf b/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/addreactions2_32.pdf deleted file mode 100644 index a10889d1e3..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Premium/ForegroundIcon.imageset/addreactions2_32.pdf and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json deleted file mode 100644 index 27e29557d3..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "giftcrash.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/Contents.json deleted file mode 100644 index 5898847ff8..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "ReadTime.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/ReadTime.pdf b/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/ReadTime.pdf deleted file mode 100644 index 25f40eeac6..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Premium/PrivacyReadTime.imageset/ReadTime.pdf +++ /dev/null @@ -1,277 +0,0 @@ -%PDF-1.7 - -1 0 obj - << /Type /XObject - /Length 2 0 R - /Group << /Type /Group - /S /Transparency - >> - /Subtype /Form - /Resources << >> - /BBox [ 0.000000 0.000000 90.000000 90.000000 ] - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -0.707107 0.707107 -0.707107 0.707107 67.034660 -18.373894 cm -0.000000 0.000000 0.000000 scn -10.453167 41.350018 m -9.512874 42.238548 8.030319 42.196587 7.141789 41.256294 c -6.253258 40.316002 6.295220 38.833447 7.235513 37.944916 c -10.453167 41.350018 l -h -0.095610 63.883827 m --2.239701 64.066391 l -0.095610 63.883827 l -h --2.342436 54.749527 m --2.342436 53.455833 -1.293692 52.407089 0.000000 52.407089 c -1.293692 52.407089 2.342436 53.455833 2.342436 54.749527 c --2.342436 54.749527 l -h -7.235513 37.944916 m -13.076076 32.425873 20.978649 29.037750 29.669981 29.037750 c -29.669981 33.722622 l -22.218493 33.722622 15.455904 36.622681 10.453167 41.350018 c -7.235513 37.944916 l -h -29.669981 29.037750 m -47.645660 29.037750 62.242207 43.525253 62.242207 61.426483 c -57.557335 61.426483 l -57.557335 46.139488 45.085209 33.722622 29.669981 33.722622 c -29.669981 29.037750 l -h -62.242207 61.426483 m -62.242207 79.327713 47.645660 93.815216 29.669981 93.815216 c -29.669981 89.130341 l -45.085209 89.130341 57.557335 76.713478 57.557335 61.426483 c -62.242207 61.426483 l -h -29.669981 93.815216 m -12.540254 93.815216 -0.940433 80.686134 -2.239701 64.066391 c -2.430921 63.701260 l -3.546472 77.970970 15.073609 89.130341 29.669981 89.130341 c -29.669981 93.815216 l -h --2.239701 64.066391 m --2.312374 63.136772 -2.342436 55.500645 -2.342436 54.749527 c -2.342436 54.749527 l -2.342436 55.154526 2.350449 57.228703 2.366412 59.305004 c -2.374389 60.342529 2.384313 61.375381 2.396135 62.197342 c -2.402054 62.608826 2.408379 62.962509 2.415047 63.235344 c -2.418387 63.371964 2.421699 63.483559 2.424903 63.569450 c -2.426500 63.612274 2.427957 63.645615 2.429212 63.670444 c -2.430558 63.697090 2.431254 63.705528 2.430921 63.701260 c --2.239701 64.066391 l -h -f -n -Q -q -0.707107 0.707107 -0.707107 0.707107 35.092972 1.912642 cm -0.000000 0.000000 0.000000 scn -9.400195 9.052296 m -16.491901 18.012711 l -17.065374 18.737297 16.942873 19.789585 16.218287 20.363058 c -15.921938 20.597603 15.554913 20.724905 15.176980 20.724241 c -1.113485 20.699522 l -0.497442 20.698439 -0.001081 20.198158 0.000002 19.582117 c -0.000426 19.340685 0.079173 19.105904 0.224414 18.913044 c -7.634510 9.073509 l -8.005109 8.581406 8.704469 8.482906 9.196571 8.853506 c -9.272655 8.910804 9.341086 8.977612 9.400195 9.052296 c -h -f* -n -Q -q -1.000000 0.000000 -0.000000 1.000000 37.394165 30.785156 cm -0.000000 0.000000 0.000000 scn -25.237368 22.854145 m -26.030727 23.803909 25.903934 25.216991 24.954170 26.010351 c -24.004406 26.803711 22.591324 26.676918 21.797964 25.727154 c -25.237368 22.854145 l -h -7.466430 5.075024 m -9.186129 3.638515 l -9.186131 3.638519 l -7.466430 5.075024 l -h -7.322968 5.062153 m -5.886413 3.342493 l -5.886472 3.342443 l -7.322968 5.062153 l -h -7.306202 5.079990 m -5.500865 3.752708 l -5.500870 3.752703 l -7.306202 5.079990 l -h -1.805337 16.344992 m -1.072299 17.342052 -0.330222 17.556084 -1.327282 16.823048 c --2.324342 16.090010 -2.538375 14.687489 -1.805337 13.690428 c -1.805337 16.344992 l -h -21.797964 25.727154 m -5.746728 6.511530 l -9.186131 3.638519 l -25.237368 22.854145 l -21.797964 25.727154 l -h -5.746732 6.511532 m -6.504047 7.418142 7.852895 7.539131 8.759465 6.781860 c -5.886472 3.342443 l -6.879384 2.513050 8.356690 2.645563 9.186129 3.638515 c -5.746732 6.511532 l -h -8.759524 6.781811 m -8.891400 6.671646 9.009609 6.545914 9.111534 6.407280 c -5.500870 3.752703 l -5.612496 3.600872 5.741967 3.463160 5.886413 3.342493 c -8.759524 6.781811 l -h -9.111539 6.407272 m -1.805337 16.344992 l --1.805337 13.690428 l -5.500865 3.752708 l -9.111539 6.407272 l -h -f -n -Q -q -1.000000 0.000000 -0.000000 1.000000 29.174744 29.887207 cm -0.000000 0.000000 0.000000 scn -5.315279 3.728403 m -6.056771 2.737616 7.461063 2.535522 8.451851 3.277015 c -9.442638 4.018508 9.644732 5.422799 8.903239 6.413588 c -5.315279 3.728403 l -h -1.793980 15.913027 m -1.052487 16.903814 -0.351804 17.105907 -1.342592 16.364414 c --2.333380 15.622922 -2.535474 14.218631 -1.793980 13.227842 c -1.793980 15.913027 l -h -8.903239 6.413588 m -1.793980 15.913027 l --1.793980 13.227842 l -5.315279 3.728403 l -8.903239 6.413588 l -h -f -n -Q - -endstream -endobj - -2 0 obj - 4078 -endobj - -3 0 obj - << /Type /XObject - /Length 4 0 R - /Group << /Type /Group - /S /Transparency - >> - /Subtype /Form - /Resources << >> - /BBox [ 0.000000 0.000000 90.000000 90.000000 ] - >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm -0.000000 0.000000 0.000000 scn -0.000000 80.000000 m -80.000000 80.000000 l -80.000000 0.000000 l -0.000000 0.000000 l -0.000000 80.000000 l -h -f -n -Q - -endstream -endobj - -4 0 obj - 232 -endobj - -5 0 obj - << /XObject << /X1 1 0 R >> - /ExtGState << /E1 << /SMask << /Type /Mask - /G 3 0 R - /S /Alpha - >> - /Type /ExtGState - >> >> - >> -endobj - -6 0 obj - << /Length 7 0 R >> -stream -/DeviceRGB CS -/DeviceRGB cs -q -/E1 gs -/X1 Do -Q - -endstream -endobj - -7 0 obj - 46 -endobj - -8 0 obj - << /Annots [] - /Type /Page - /MediaBox [ 0.000000 0.000000 90.000000 90.000000 ] - /Resources 5 0 R - /Contents 6 0 R - /Parent 9 0 R - >> -endobj - -9 0 obj - << /Kids [ 8 0 R ] - /Count 1 - /Type /Pages - >> -endobj - -10 0 obj - << /Pages 9 0 R - /Type /Catalog - >> -endobj - -xref -0 11 -0000000000 65535 f -0000000010 00000 n -0000004336 00000 n -0000004359 00000 n -0000004839 00000 n -0000004861 00000 n -0000005159 00000 n -0000005261 00000 n -0000005282 00000 n -0000005455 00000 n -0000005529 00000 n -trailer -<< /ID [ (some) (id) ] - /Root 10 0 R - /Size 11 ->> -startxref -5589 -%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/Contents.json deleted file mode 100644 index 2c2f33f0da..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "clearpasscode@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "clearpasscode@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@2x.png b/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@2x.png deleted file mode 100644 index 4fd57e8830..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@3x.png b/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@3x.png deleted file mode 100644 index fc6549cc0c..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/PasscodeClearIcon.imageset/clearpasscode@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/Contents.json deleted file mode 100644 index 3dd535cbed..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/Contents.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "PasswordPlaceholderIcon@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "PasswordPlaceholderIcon@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@2x.png deleted file mode 100644 index c9cd47a84c..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@2x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@3x.png deleted file mode 100644 index 8ce614090f..0000000000 Binary files a/submodules/TelegramUI/Images.xcassets/Settings/PasswordIntroIcon.imageset/PasswordPlaceholderIcon@3x.png and /dev/null differ diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/Contents.json deleted file mode 100644 index 6e965652df..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/Contents.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "provides-namespace" : true - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/Contents.json deleted file mode 100644 index f42e8f2075..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconBackgroundTime.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/PowerIconBackgroundTime.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/PowerIconBackgroundTime.svg deleted file mode 100644 index d4e4739bc1..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconBackgroundTime.imageset/PowerIconBackgroundTime.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/Contents.json deleted file mode 100644 index 82b6ec3e70..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconEffects.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/PowerIconEffects.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/PowerIconEffects.svg deleted file mode 100644 index 417e451cca..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEffects.imageset/PowerIconEffects.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/Contents.json deleted file mode 100644 index fd88972363..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconEmoji.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/PowerIconEmoji.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/PowerIconEmoji.svg deleted file mode 100644 index c8cc8922a1..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconEmoji.imageset/PowerIconEmoji.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/Contents.json deleted file mode 100644 index c3d709d9e8..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconGif.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/PowerIconGif.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/PowerIconGif.svg deleted file mode 100644 index adaed14d56..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconGif.imageset/PowerIconGif.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/Contents.json deleted file mode 100644 index 6e83cc8223..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconMedia.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/PowerIconMedia.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/PowerIconMedia.svg deleted file mode 100644 index 4189aec595..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconMedia.imageset/PowerIconMedia.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/Contents.json deleted file mode 100644 index 8e14ecc776..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconStickers.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/PowerIconStickers.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/PowerIconStickers.svg deleted file mode 100644 index d825813fc7..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconStickers.imageset/PowerIconStickers.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/Contents.json deleted file mode 100644 index d47d2a6972..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "filename" : "PowerIconVideo.svg", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/PowerIconVideo.svg b/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/PowerIconVideo.svg deleted file mode 100644 index 0a3bb1baba..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Settings/Power/PowerIconVideo.imageset/PowerIconVideo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/Contents.json new file mode 100644 index 0000000000..9d000d735b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "setemoji.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/setemoji.pdf b/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/setemoji.pdf new file mode 100644 index 0000000000..92143fac3b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/TextProcessing/EditEmojiPlaceholder.imageset/setemoji.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/Contents.json new file mode 100644 index 0000000000..d313636cad --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "addstyle.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/addstyle.pdf b/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/addstyle.pdf new file mode 100644 index 0000000000..3565e78849 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/TextProcessing/NewStyle.imageset/addstyle.pdf differ diff --git a/submodules/TelegramUI/Resources/Animations/BotEmoji.tgs b/submodules/TelegramUI/Resources/Animations/BotEmoji.tgs deleted file mode 100644 index 0076344f4c..0000000000 Binary files a/submodules/TelegramUI/Resources/Animations/BotEmoji.tgs and /dev/null differ diff --git a/submodules/TelegramUI/Resources/Animations/ChatAutomation.tgs b/submodules/TelegramUI/Resources/Animations/ChatAutomation.tgs new file mode 100644 index 0000000000..3e386fd63e Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/ChatAutomation.tgs differ diff --git a/submodules/TelegramUI/Resources/Animations/NavigationMuteOff.tgs b/submodules/TelegramUI/Resources/Animations/NavigationMuteOff.tgs new file mode 100644 index 0000000000..0fd0c192a5 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/NavigationMuteOff.tgs differ diff --git a/submodules/TelegramUI/Resources/Animations/NavigationMuteOn.tgs b/submodules/TelegramUI/Resources/Animations/NavigationMuteOn.tgs new file mode 100644 index 0000000000..7d3d18b9fe Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/NavigationMuteOn.tgs differ diff --git a/submodules/TelegramUI/Resources/Animations/anim_hand5.json b/submodules/TelegramUI/Resources/Animations/anim_hand5.json deleted file mode 100644 index e90dc525a4..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_hand5.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.7.6","fr":60,"ip":0,"op":240,"w":100,"h":100,"nm":"hand_5 BIG 1x","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL CONTROL","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":30,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":36,"s":[5.537,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":43,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":52,"s":[5.537,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":68,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":87,"s":[5.537,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.13,"y":1},"o":{"x":0.174,"y":0},"t":115,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.34,"y":1},"o":{"x":0.54,"y":0},"t":157,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.54,"y":0},"t":168,"s":[6.742,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":172,"s":[1.942,-42.622,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":178,"s":[13.542,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":184,"s":[9.142,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.29,"y":1},"o":{"x":0.167,"y":0},"t":190,"s":[9.542,-39.422,0],"to":[0,0,0],"ti":[0,0,0]},{"t":199,"s":[6.742,-39.422,0]}],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"head","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.781],"y":[1]},"o":{"x":[0.412],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":30,"s":[21]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":54,"s":[21]},{"i":{"x":[0.13],"y":[1]},"o":{"x":[0.811],"y":[0]},"t":71,"s":[21]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.54],"y":[0]},"t":157,"s":[0.516]},{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":162,"s":[61.167]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.54],"y":[0]},"t":169,"s":[0.516]},{"i":{"x":[0.637],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":177,"s":[-21.833]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":183,"s":[0]},{"t":218,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.396,"y":1},"o":{"x":0.683,"y":0},"t":1,"s":[81.289,125.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":11,"s":[81.289,176.385,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":30,"s":[115.289,114.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.487,"y":0},"t":54,"s":[115.289,148.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.7,"y":0},"t":71,"s":[115.289,141.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[133.289,230.475,0],"to":[0,0,0],"ti":[0.667,1.167,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":110,"s":[129.289,223.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.13,"y":1},"o":{"x":0.181,"y":0},"t":117,"s":[129.289,223.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.34,"y":1},"o":{"x":0.54,"y":0},"t":157,"s":[145.289,244.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.54,"y":0},"t":169,"s":[153.289,246.475,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":183,"s":[70.289,86.475,0],"to":[0,0,0],"ti":[0,0,0]},{"t":218,"s":[81.289,125.475,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":1,"s":[500,500,100]},{"i":{"x":[0.425,0.425,0.425],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":11,"s":[500,500,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":16,"s":[520,484,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":54,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.7],"y":[0,0,0]},"t":71,"s":[500,500,100]},{"i":{"x":[0.13,0.13,0.13],"y":[1,1,1]},"o":{"x":[0.176,0.176,0.176],"y":[0,0,0]},"t":97,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.54,0.54,0.54],"y":[0,0,0]},"t":157,"s":[500,500,100]},{"i":{"x":[0.34,0.34,0.34],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":162,"s":[477,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.54,0.54,0.54],"y":[0,0,0]},"t":169,"s":[500,500,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":175,"s":[469,533,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":183,"s":[485,515,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":198,"s":[527,484,100]},{"t":218,"s":[500,500,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":0,"s":[{"i":[[-6.341,0],[0,-6.341],[6.341,0],[0,6.341]],"o":[[6.341,0],[0,6.341],[-6.341,0],[0,-6.341]],"v":[[0,-11.482],[11.482,0],[0,11.482],[-11.482,0]],"c":true}]},{"i":{"x":0.13,"y":1},"o":{"x":0.167,"y":0},"t":117,"s":[{"i":[[-6.395,0],[0,-6.395],[6.395,0],[0,6.395]],"o":[[6.395,0],[0,6.395],[-6.395,0],[0,-6.395]],"v":[[0,-11.579],[11.579,0],[0,11.579],[-11.579,0]],"c":true}]},{"i":{"x":0.34,"y":1},"o":{"x":0.54,"y":0},"t":157,"s":[{"i":[[-6.395,0],[0,-6.395],[6.395,0],[0,6.395]],"o":[[6.395,0],[0,6.395],[-6.395,0],[0,-6.395]],"v":[[0,-11.579],[11.579,0],[0,11.579],[-11.579,0]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.54,"y":0},"t":169,"s":[{"i":[[-6.395,0],[0,-6.395],[6.395,0],[0,6.395]],"o":[[6.395,0],[0,6.395],[-6.395,0],[0,-6.395]],"v":[[0,-11.579],[11.579,0],[0,11.579],[-11.579,0]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":183,"s":[{"i":[[-6.395,0],[0,-6.395],[6.395,0],[0,6.395]],"o":[[6.395,0],[0,6.395],[-6.395,0],[0,-6.395]],"v":[[0,-11.579],[11.579,0],[0,11.579],[-11.579,0]],"c":true}]},{"t":218,"s":[{"i":[[-6.395,0],[0,-6.395],[6.395,0],[0,6.395]],"o":[[6.395,0],[0,6.395],[-6.395,0],[0,-6.395]],"v":[[0,-11.579],[11.579,0],[0,11.579],[-11.579,0]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Oval","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"hands","parent":6,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":28,"s":[8]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.487],"y":[0]},"t":49,"s":[8]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":66,"s":[8]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":95,"s":[15]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.6],"y":[0]},"t":172,"s":[15]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":183,"s":[0]},{"t":215,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.538,"y":1},"o":{"x":0.525,"y":0},"t":0,"s":[-2.341,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[-2.341,-12.256,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":0.3},"o":{"x":0.167,"y":0.167},"t":28,"s":[-1.541,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.487,"y":0},"t":49,"s":[-1.541,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.5,"y":0},"t":66,"s":[-1.541,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.5,"y":0.5},"t":95,"s":[1.459,-10.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.6,"y":0},"t":172,"s":[1.459,-10.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":183,"s":[-2.341,-24.177,0],"to":[0,0,0],"ti":[0,0,0]},{"t":215,"s":[-2.341,-17.777,0]}],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"hands 3","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[0,0],[-1.37,-5.996],[-12.799,0.954],[-5.623,-0.366]],"o":[[-2.582,5.43],[1.226,5.005],[4.037,-0.259],[1.184,0.071]],"v":[[-21.167,-23.136],[-27.242,-4.081],[-7.372,3.34],[9.979,3.037]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":24,"s":[{"i":[[0,0],[-0.633,-3.903],[-5.394,-1.449],[-8.926,0.862]],"o":[[1.012,6.936],[0.998,6.158],[5.589,1.532],[1.18,-0.114]],"v":[[-18.065,-33.976],[-19.359,-12.251],[-7.484,2.834],[8.267,2.627]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":30,"s":[{"i":[[0,0],[-1.834,-3.522],[-4.71,-2.732],[-8.926,0.862]],"o":[[4.406,7.624],[2.894,5.558],[5.028,2.917],[1.18,-0.114]],"v":[[-29.396,-35.058],[-19.39,-14.698],[-8.387,2.178],[8.267,2.627]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":37,"s":[{"i":[[0,0],[-0.633,-3.903],[-5.394,-1.449],[-8.926,0.862]],"o":[[1.012,6.936],[0.998,6.158],[5.589,1.532],[1.18,-0.114]],"v":[[-18.065,-33.976],[-19.359,-12.251],[-7.484,2.834],[8.267,2.627]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":46,"s":[{"i":[[0,0],[-0.961,-3.853],[-4.984,-1.753],[-8.926,0.862]],"o":[[1.597,6.857],[1.517,6.08],[5.484,1.929],[1.18,-0.114]],"v":[[-23.166,-35.108],[-20.844,-12.632],[-7.906,2.713],[8.267,2.627]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":62,"s":[{"i":[[0,0],[-0.633,-3.903],[-5.394,-1.449],[-8.926,0.862]],"o":[[1.012,6.936],[0.998,6.158],[5.589,1.532],[1.18,-0.114]],"v":[[-18.065,-33.976],[-19.359,-12.251],[-7.484,2.834],[8.371,3.018]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":83,"s":[{"i":[[0,0],[-0.961,-3.853],[-4.906,-1.962],[-7.467,0.538]],"o":[[1.597,6.857],[1.517,6.08],[4.479,1.791],[1.183,-0.085]],"v":[[-23.166,-35.108],[-20.844,-12.632],[-7.906,2.713],[8.484,3.913]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":110,"s":[{"i":[[0,0],[-0.26,-3.59],[-3.384,-0.79],[-7.192,0.386]],"o":[[0.455,6.368],[0.41,5.665],[5.588,1.254],[1.184,-0.062]],"v":[[-19.285,-31.287],[-20.744,-10.988],[-7.332,2.936],[7.831,4.696]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":159,"s":[{"i":[[0,0],[-0.365,-3.581],[-5.898,-2.25],[-7.859,-0.922]],"o":[[0.643,6.352],[0.576,5.651],[5.898,2.25],[1.177,0.138]],"v":[[-19.77,-28.6],[-20.63,-8.268],[-7.332,2.936],[8.229,6.66]],"c":false}]},{"i":{"x":0.33,"y":1},"o":{"x":0.167,"y":0},"t":170,"s":[{"i":[[0,0],[-0.365,-3.581],[-5.898,-2.25],[-7.859,-0.922]],"o":[[0.643,6.352],[0.576,5.651],[5.898,2.25],[1.177,0.138]],"v":[[-17.58,-28.152],[-18.943,-8.927],[-7.087,3.077],[8.229,6.66]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":172,"s":[{"i":[[0,0],[-0.365,-3.581],[-5.898,-2.25],[-7.859,-0.922]],"o":[[0.643,6.352],[0.576,5.651],[5.898,2.25],[1.177,0.138]],"v":[[-17.58,-28.152],[-18.943,-8.927],[-7.087,3.077],[8.229,6.66]],"c":false}]},{"i":{"x":0.33,"y":1},"o":{"x":0.167,"y":0.167},"t":177,"s":[{"i":[[0,0],[-0.54,-4.745],[-7.23,-2.167],[-5.398,-0.759]],"o":[[-4.605,4.425],[0.657,5.889],[5.467,1.861],[1.174,0.151]],"v":[[-13.923,-29.232],[-18.91,-12.686],[-7.227,2.305],[7.477,5.457]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":183,"s":[{"i":[[0,0],[-0.757,-6.182],[-8.875,-2.065],[-4.049,-0.033]],"o":[[1.716,5.76],[0.757,6.182],[5.804,0.802],[1.183,0.01]],"v":[[-20.544,-37.522],[-16.784,-18.145],[-6.731,2.836],[6.768,3.871]],"c":false}]},{"i":{"x":0.22,"y":1},"o":{"x":0.167,"y":0.167},"t":197,"s":[{"i":[[0,0],[-1.231,-6.629],[-10.082,-1.99],[-3.033,-0.134]],"o":[[-2.133,4.972],[1.242,6.689],[4.542,1.029],[1.183,0.052]],"v":[[-21.494,-33.181],[-22.796,-14.58],[-6.487,2.795],[6.539,3.624]],"c":false}]},{"t":230,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"hands 2","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[-2.886,-0.073],[-2.347,-0.053],[-2.059,-3.86],[-0.222,-4.947]],"o":[[2.847,0.072],[11.634,0.272],[2.05,3.678],[0,0]],"v":[[2.805,2.87],[11.318,3.002],[28.486,9.829],[31.187,24.214]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":29,"s":[{"i":[[-2.805,0.395],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.23,-0.595],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.696,3.339],[12.911,2.962],[23.56,19.096],[31.538,32.046]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.4,"y":0},"t":49,"s":[{"i":[[-2.805,0.396],[-2.168,-0.779],[-1.2,-3.73],[-2.259,-5.223]],"o":[[3.592,-0.507],[6.085,2.162],[1.511,4.741],[0,0]],"v":[[2.591,3.352],[12.843,2.769],[22.316,18.867],[27.587,32.67]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":56,"s":[{"i":[[-2.809,0.363],[-2.164,-0.79],[-1.265,-3.731],[-2.492,-5.262]],"o":[[3.71,-0.472],[5.954,2.15],[1.585,4.718],[0,0]],"v":[[2.587,3.473],[12.901,3.159],[22.53,18.907],[28.267,32.563]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":71,"s":[{"i":[[-2.826,0.202],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.274,-0.305],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.755,3.732],[13.24,3.821],[23.56,19.096],[31.538,32.046]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":77,"s":[{"i":[[-3.178,0.215],[-2.041,-0.845],[-2.499,-1.941],[-4.429,-3.642]],"o":[[4.522,-0.369],[5.066,2.097],[2.711,2.446],[0,0]],"v":[[2.574,4.136],[12.86,4.148],[22.716,18.561],[31.303,23.734]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83,"s":[{"i":[[-3.695,-0.034],[-1.897,-0.845],[-3.783,0.554],[-5.561,-1.128]],"o":[[4.929,-0.055],[4.71,2.097],[3.783,-0.554],[0,0]],"v":[[2.262,4.258],[12.224,3.929],[21.543,17.816],[30.975,12.182]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":89,"s":[{"i":[[-3.987,-0.175],[-1.816,-0.845],[-2.558,0.538],[-4.267,2.804]],"o":[[4.392,0.151],[4.508,2.097],[4.029,-0.892],[0,0]],"v":[[0.773,4.323],[12.105,4.679],[20.881,17.396],[23.392,2.697]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":97,"s":[{"i":[[-4.068,-0.214],[-1.793,-0.845],[-2.221,0.534],[-3.91,3.887]],"o":[[3.583,0.188],[4.452,2.097],[4.097,-0.985],[0,0]],"v":[[1.673,4.518],[12.072,4.886],[20.698,17.28],[21.304,0.085]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":110,"s":[{"i":[[-4.068,-0.214],[-1.793,-0.845],[-2.221,0.534],[-1.866,5.72]],"o":[[3.583,0.188],[4.452,2.097],[4.097,-0.985],[0,0]],"v":[[1.647,4.421],[11.286,4.268],[20.698,17.28],[20.124,-0.841]],"c":false}]},{"i":{"x":0.13,"y":1},"o":{"x":0.167,"y":0},"t":117,"s":[{"i":[[-4.068,-0.214],[-1.793,-0.845],[-2.221,0.534],[-1.866,5.72]],"o":[[3.583,0.188],[4.452,2.097],[4.097,-0.985],[0,0]],"v":[[1.647,4.421],[11.286,4.268],[20.698,17.28],[20.124,-0.841]],"c":false}]},{"i":{"x":0.47,"y":1},"o":{"x":0.54,"y":0},"t":159,"s":[{"i":[[-3.882,-1.235],[-1.793,-0.845],[-2.236,0.469],[-6.732,3.4]],"o":[[6.962,2.214],[4.452,2.097],[6.913,-1.449],[0,0]],"v":[[-2.326,4.761],[11.274,7.998],[20.698,17.28],[22.603,3.774]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.53,"y":0},"t":170,"s":[{"i":[[-3.882,-1.235],[-1.793,-0.845],[-3.475,0.534],[-2.888,6.054]],"o":[[6.962,2.214],[4.452,2.097],[6.411,-0.985],[0,0]],"v":[[-2.326,4.761],[12.099,7.984],[24.802,16.49],[25.028,3.271]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":174,"s":[{"i":[[-3.711,-1.045],[-1.877,-0.707],[-3.424,-0.312],[-3.136,3.726]],"o":[[6.283,1.865],[4.802,1.755],[5.881,-0.065],[0,0]],"v":[[-1.494,4.593],[11.879,7.297],[24.69,16.195],[35.961,7.352]],"c":false}]},{"i":{"x":0.33,"y":1},"o":{"x":0.167,"y":0.167},"t":177,"s":[{"i":[[-3.396,-0.694],[-2.033,-0.452],[-3.328,-1.877],[-3.596,-0.579]],"o":[[5.027,1.218],[5.448,1.122],[4.899,1.636],[0,0]],"v":[[0.045,4.282],[11.473,6.026],[24.482,15.649],[36.718,19.295]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.6,"y":0},"t":186,"s":[{"i":[[-2.838,-0.073],[-2.309,0],[-3.159,-4.652],[-4.411,-8.209]],"o":[[2.799,0.072],[6.593,0],[3.159,4.652],[0,0]],"v":[[2.773,3.73],[10.753,3.773],[24.114,14.681],[34.584,32.542]],"c":false}]},{"t":222,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"body","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.538,"y":1},"o":{"x":0.525,"y":0},"t":0,"s":[262.842,433.748,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[262.842,450.324,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":28,"s":[262.842,403.748,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":0.3},"o":{"x":0.167,"y":0.167},"t":49,"s":[262.842,433.748,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":66,"s":[262.842,433.748,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.6,"y":0.6},"t":172,"s":[262.842,433.748,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":0.2},"o":{"x":0.167,"y":0.167},"t":183,"s":[262.842,433.748,0],"to":[0,0,0],"ti":[0,0,0]},{"t":215,"s":[262.842,433.748,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,14.211,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":0,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":3,"s":[515,485,100]},{"i":{"x":[0.566,0.566,0.566],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.484,0.484,0.484],"y":[0,0,0]},"t":10,"s":[489.464,510.536,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":15,"s":[515,485,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":28,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":37,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":49,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":57,"s":[515,485,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":78,"s":[485,515,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":95,"s":[500,500,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":172,"s":[500,500,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":183,"s":[500,500,100]},{"t":215,"s":[500,500,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[{"i":[[0,0],[0,0],[-1.991,-0.83],[-3.105,0],[-2.6,1.063],[-0.091,2.131],[0,0]],"o":[[0,0],[0.148,2.059],[2.638,1.099],[3.053,0],[2.064,-0.844],[0,0],[0,0]],"v":[[-14.565,-8.333],[-13.375,7.863],[-9.896,12.562],[0.353,14.211],[10.467,12.616],[13.992,7.749],[14.426,-7.239]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":24,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.292,-16.725],[-11.425,7.863],[-8.369,12.562],[0.392,14.211],[9.036,12.616],[12.131,7.749],[13.652,-10.268]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.74,-17.039],[-11.432,7.863],[-8.377,12.562],[0.392,14.211],[9.044,12.616],[12.139,7.749],[13.308,-10.843]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":37,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.292,-16.725],[-11.425,7.863],[-8.369,12.562],[0.392,14.211],[9.036,12.616],[12.131,7.749],[13.652,-10.268]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":46,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.74,-17.039],[-11.432,7.863],[-8.377,12.562],[0.392,14.211],[9.044,12.616],[12.139,7.749],[13.308,-10.843]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":57,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.292,-16.725],[-11.425,7.863],[-8.369,12.562],[0.392,14.211],[9.036,12.616],[12.131,7.749],[13.652,-10.268]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.5,"y":0},"t":66,"s":[{"i":[[0.942,-11.03],[0,0],[-1.748,-0.83],[-2.726,0],[-2.283,1.063],[-0.08,2.131],[-1.162,6.43]],"o":[[-0.818,9.587],[0.13,2.059],[2.317,1.099],[2.681,0],[1.812,-0.844],[0,0],[0.041,-0.17]],"v":[[-11.74,-17.039],[-11.432,7.863],[-8.377,12.562],[0.392,14.211],[9.044,12.616],[12.139,7.749],[13.308,-10.843]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.5,"y":0},"t":95,"s":[{"i":[[7.932,-7.722],[-0.296,-1.817],[-2.373,-1.147],[-2.726,0.008],[-2.179,1.263],[-0.336,1.409],[-1.022,3.53]],"o":[[-7.574,7.373],[0.296,1.817],[2.309,1.116],[2.681,-0.008],[2.218,-1.285],[0.336,-1.409],[0.041,-0.17]],"v":[[-11.795,-13.512],[-17.064,5.317],[-13.796,10.808],[-2.427,12.362],[5.214,10.746],[8.296,5.87],[10.454,-3.469]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.6,"y":0},"t":172,"s":[{"i":[[7.932,-7.722],[-0.296,-1.817],[-2.373,-1.147],[-2.726,0.008],[-2.179,1.263],[-0.336,1.409],[-1.022,3.53]],"o":[[-7.574,7.373],[0.296,1.817],[2.309,1.116],[2.681,-0.008],[2.218,-1.285],[0.336,-1.409],[0.041,-0.17]],"v":[[-11.795,-13.512],[-17.064,5.317],[-13.796,10.808],[-2.427,12.362],[5.214,10.746],[8.296,5.87],[10.454,-3.469]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":183,"s":[{"i":[[0,0],[0,0],[-1.638,-0.83],[-2.555,0],[-2.139,1.063],[-0.075,2.131],[0,0]],"o":[[0,0],[0.122,2.059],[2.171,1.099],[2.512,0],[1.698,-0.844],[0,0],[0,0]],"v":[[-11.432,-21.961],[-10.647,7.863],[-7.784,12.562],[0.304,14.211],[8.281,12.616],[11.182,7.749],[11.432,-19.411]],"c":false}]},{"t":215,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":244,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"hand_5","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.75,47.75,0],"ix":2,"l":2},"a":{"a":0,"k":[256,256,0],"ix":1,"l":2},"s":{"a":0,"k":[11,11,100],"ix":6,"l":2}},"ao":0,"w":512,"h":512,"ip":0,"op":240,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_hand6.json b/submodules/TelegramUI/Resources/Animations/anim_hand6.json deleted file mode 100644 index 1864a8703b..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_hand6.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.7.6","fr":60,"ip":0,"op":240,"w":100,"h":100,"nm":"hand_6 BIG 1x","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256.989,275.127,0],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":243,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"head","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.6],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":23.514,"s":[21]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.17],"y":[0]},"t":159,"s":[21]},{"i":{"x":[0.836],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":169,"s":[-18]},{"i":{"x":[0.14],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":181,"s":[0]},{"t":202,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":1,"s":[122.706,17.15,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":11,"s":[122.706,68.999,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.589,"y":1},"o":{"x":0.275,"y":0},"t":23.514,"s":[150.706,5.999,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.206,"y":0},"t":36.148,"s":[150.706,32.05,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.29,"y":1},"o":{"x":0.295,"y":0},"t":55.52,"s":[150.706,27.67,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.71,"y":0.684},"o":{"x":0.75,"y":0},"t":72,"s":[150.706,31.062,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.205,"y":0.943},"o":{"x":0.097,"y":0.337},"t":100,"s":[158.75,127.141,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.205,"y":0.901},"o":{"x":0.328,"y":0.041},"t":123,"s":[140.564,110.895,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.205,"y":0.205},"o":{"x":0.328,"y":0.328},"t":147,"s":[148.731,122.942,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.71,"y":0},"t":159,"s":[148.731,122.942,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.14,"y":1},"o":{"x":0.167,"y":0},"t":181,"s":[122.706,1.199,0],"to":[0,0,0],"ti":[0,0,0]},{"t":202,"s":[122.706,17.15,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":1,"s":[500,500,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":11,"s":[525,475,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":16,"s":[485,515,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":23.514,"s":[500,500,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":36,"s":[500,500,100]},{"i":{"x":[0.605,0.605,0.605],"y":[1,1,1]},"o":{"x":[0.709,0.709,0.709],"y":[0,0,0]},"t":55.52,"s":[500,500,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":72,"s":[500,500,100]},{"i":{"x":[0.605,0.605,0.605],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":84,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.195,0.195,0.195],"y":[0,0,0]},"t":100,"s":[531,465,100]},{"i":{"x":[0.666,0.666,0.666],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":111,"s":[508,482,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":123,"s":[531,465,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":147,"s":[531,465,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.71,0.71,0.71],"y":[0,0,0]},"t":159,"s":[531,465,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":169,"s":[485,515,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":181,"s":[500,500,100]},{"i":{"x":[0.14,0.14,0.14],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":190,"s":[515,485,100]},{"t":202,"s":[500,500,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.341,0],[0,-6.341],[6.341,0],[0,6.341]],"o":[[6.341,0],[0,6.341],[-6.341,0],[0,-6.341]],"v":[[0,-11.482],[11.482,0],[0,11.482],[-11.482,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Oval","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"NULL CONTROL","parent":7,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-1.541,-17.777,0],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":3,"nm":"hands","parent":3,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":23.514,"s":[8]},{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.71],"y":[0]},"t":167,"s":[8]},{"t":187,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[111,115,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[111,155,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":23.514,"s":[115,115,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":32.309,"s":[115,121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":48.244,"s":[115,115,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":0.5},"o":{"x":0.167,"y":0.167},"t":73.717,"s":[115,121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":0.5},"o":{"x":0.167,"y":0.167},"t":104,"s":[115,121,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.71,"y":0},"t":167,"s":[115,121,0],"to":[0,0,0],"ti":[0,0,0]},{"t":187,"s":[111,115,0]}],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"hands 3","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[0,0],[-0.71,-5.814],[-13.22,1.67],[-6.666,-0.138]],"o":[[-3.232,5.731],[0.566,4.637],[3.994,-0.505],[1.186,0.024]],"v":[[-18.219,-21.464],[-27.079,-2.224],[-7.644,3.484],[11.485,2.829]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":23.514,"s":[{"i":[[0,0],[-0.961,-3.853],[-4.491,-2.782],[-8.28,0.661]],"o":[[1.597,6.857],[1.517,6.08],[4.565,2.828],[1.182,-0.094]],"v":[[-20.247,-37.336],[-17.926,-14.859],[-7.359,2.636],[10.724,2.534]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":27.281,"s":[{"i":[[0,0],[-0.511,-3.901],[-4.724,-3.504],[-8.291,0.526]],"o":[[2.238,7.884],[0.807,6.156],[4.331,3.14],[1.184,-0.075]],"v":[[-18.139,-37.479],[-16.265,-15.877],[-7.359,2.636],[10.941,2.657]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":32.309,"s":[{"i":[[0,0],[0.125,-3.969],[-5.052,-4.524],[-8.435,1.023]],"o":[[-2.798,7.352],[-0.197,6.263],[4,3.582],[1.177,-0.143]],"v":[[-6.969,-38.346],[-13.919,-17.314],[-7.359,2.636],[9.48,3.009]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":39.988,"s":[{"i":[[0,0],[-0.447,-3.908],[-4.757,-3.608],[-8.213,0.625]],"o":[[-4.119,5.691],[0.705,6.167],[4.297,3.185],[1.182,-0.09]],"v":[[-10.167,-36.731],[-16.027,-16.023],[-7.359,2.636],[11.49,2.929]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":48,"s":[{"i":[[0,0],[-0.961,-3.853],[-4.491,-2.782],[-8.28,0.661]],"o":[[1.597,6.857],[1.517,6.08],[4.565,2.828],[1.182,-0.094]],"v":[[-20.247,-37.336],[-17.926,-14.859],[-7.359,2.636],[10.724,2.534]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":59,"s":[{"i":[[0,0],[-0.745,-3.894],[-5.61,-1.137],[-7.557,0.15]],"o":[[0.04,7.276],[1.176,6.145],[4.685,1.226],[2.785,0.43]],"v":[[-21.221,-29.776],[-21.735,-7.592],[-7.832,2.454],[9.503,3.125]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.7,"y":0},"t":77,"s":[{"i":[[0,0],[-0.534,-3.934],[-6.701,0.467],[-7.857,-1.7]],"o":[[-1.477,7.685],[0.843,6.208],[4.802,-0.335],[4.346,0.94]],"v":[[-22.17,-22.408],[-25.448,-0.509],[-8.293,2.277],[8.34,3.892]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":83,"s":[{"i":[[0,0],[-1.283,-3.822],[-6.716,0.378],[-7.766,-1.789]],"o":[[-1.159,7.502],[1.533,5.663],[4.777,-0.271],[4.347,1.005]],"v":[[-23.938,-21.192],[-25.341,0.043],[-8.232,2.315],[8.497,4.584]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":89,"s":[{"i":[[0,0],[-4.461,-3.345],[-6.782,-0.001],[-7.382,-2.168]],"o":[[0.19,6.726],[4.461,3.345],[4.673,0.001],[4.35,1.277]],"v":[[-31.448,-16.026],[-24.888,2.391],[-7.974,2.476],[8.356,5.481]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[0,0],[-5.006,-0.871],[-6.739,0.618],[-3.595,-1.191]],"o":[[3.138,5.078],[7.808,0.677],[3.051,-0.206],[4.086,1.935]],"v":[[-35.589,-2.901],[-23.582,6.559],[-7.395,2.829],[5.349,5.214]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[0,0],[-5.097,-0.459],[-6.732,0.721],[-1.487,-0.398]],"o":[[3.63,4.803],[8.366,0.233],[2.78,-0.24],[4.048,2.025]],"v":[[-36.279,-0.714],[-23.364,7.254],[-7.298,2.887],[-0.817,3.327]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[0,0],[-5.278,0.366],[-6.718,0.928],[-1.571,-0.06]],"o":[[4.612,4.253],[9.482,-0.657],[2.24,-0.309],[3.971,2.206]],"v":[[-37.66,3.661],[-22.928,8.643],[-7.105,3.005],[-2.464,2.943]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[0,0],[-3.051,3.557],[-6.774,-0.323],[-0.439,-0.145]],"o":[[6.234,-0.011],[3.051,-3.557],[1.059,0.05],[4.488,-0.165]],"v":[[-32.128,21.461],[-18.515,15.867],[-6.941,3.526],[-5.387,3.41]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104,"s":[{"i":[[0,0],[-1.97,4.904],[-6.781,-0.025],[-0.04,0.149]],"o":[[5.985,-2.499],[1.97,-4.904],[0.507,0.073],[4.271,1.301]],"v":[[-26.349,30.241],[-16.309,19.479],[-6.859,3.786],[-6.722,3.667]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":106,"s":[{"i":[[0,0],[-0.723,4.978],[-6.358,0.441],[-0.086,-0.015]],"o":[[3.449,-4.195],[0.723,-4.978],[0.651,-0.017],[4.461,-0.533]],"v":[[-21.092,35.493],[-14.798,22.142],[-6.816,3.923],[-6.291,3.85]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":116,"s":[{"i":[[0,0],[0.27,3.962],[-5.659,-0.43],[0,0]],"o":[[-2.38,-4.193],[-0.461,-6.764],[0.813,0.105],[0,0]],"v":[[-6.789,40.46],[-11.955,22.079],[-6.74,4.165],[-6.541,4.123]],"c":false}]},{"i":{"x":0.56,"y":1},"o":{"x":0.167,"y":0},"t":132,"s":[{"i":[[0,0],[0.028,3.971],[-6.776,0.271],[0,0]],"o":[[0.92,-6.575],[-0.044,-6.266],[0.437,0.042],[0,0]],"v":[[-14.554,41.248],[-13.978,22.06],[-6.74,4.165],[-6.04,4.181]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.44,"y":0},"t":149,"s":[{"i":[[0,0],[0.028,3.971],[-6.696,-1.076],[0,0]],"o":[[-1.06,-6.297],[-0.044,-6.266],[0.486,0.034],[0,0]],"v":[[-11.825,41.269],[-13.468,22.09],[-6.74,4.165],[-4.523,4.686]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":163,"s":[{"i":[[0,0],[0.028,3.971],[-6.696,-1.076],[0,0]],"o":[[-1.06,-6.297],[-0.044,-6.266],[0.486,0.034],[0,0]],"v":[[-11.825,41.269],[-13.468,22.09],[-6.74,4.165],[-4.523,4.686]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":167,"s":[{"i":[[0,0],[0.028,3.971],[-6.774,0.312],[0.114,0.062]],"o":[[-1.06,-6.297],[-0.044,-6.266],[0.175,-0.024],[0.128,0.161]],"v":[[-11.825,41.269],[-13.468,22.09],[-6.74,4.165],[-3.2,4.045]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[0,0],[-0.332,3.858],[-6.615,-0.065],[-6.227,-2.487]],"o":[[-1.197,-5.882],[0.411,-5.901],[4.645,0.044],[4.032,1.605]],"v":[[-13.447,39.061],[-15.196,20.338],[-6.713,4.082],[8.845,6.116]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":171,"s":[{"i":[[0,0],[-2.006,3.333],[-5.838,-0.364],[-5.143,-1.039]],"o":[[-1.832,-3.948],[2.53,-4.202],[4.517,0.245],[3.33,0.673]],"v":[[-20.998,28.78],[-23.239,12.181],[-6.64,3.301],[7.921,5.13]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":172,"s":[{"i":[[0,0],[-3.833,2.656],[-5.544,-0.477],[-4.639,-1.02]],"o":[[-0.634,-3.479],[4.095,-3.091],[4.469,0.321],[2.949,0.736]],"v":[[-27.21,21.956],[-23.39,8.31],[-6.628,2.86],[7.594,4.819]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":173,"s":[{"i":[[0,0],[-5.661,1.98],[-5.25,-0.59],[-4.136,-1]],"o":[[0.565,-3.01],[5.66,-1.98],[4.421,0.397],[2.568,0.798]],"v":[[-33.422,15.133],[-23.541,4.439],[-6.54,3.01],[7.266,4.508]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":174,"s":[{"i":[[0,0],[-9.318,0.062],[-4.956,-0.704],[-4.288,-0.672]],"o":[[-0.353,0.511],[6.341,-0.042],[4.372,0.473],[2.252,0.625]],"v":[[-39.783,7.131],[-23.543,1.746],[-6.473,2.566],[6.939,4.148]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":175,"s":[{"i":[[0,0],[-7.373,-0.661],[-5.987,-0.908],[-3.473,-0.494]],"o":[[4.936,-1.112],[5.389,0.674],[4.345,0.516],[2.073,0.526]],"v":[[-41.655,1.633],[-24.332,-0.148],[-6.488,2.688],[6.748,3.903]],"c":false}]},{"i":{"x":0.1,"y":1},"o":{"x":0.167,"y":0.167},"t":177,"s":[{"i":[[0,0],[-3.484,-2.106],[-8.05,-1.318],[-3.2,-0.264]],"o":[[2.562,1.927],[3.484,2.106],[4.29,0.602],[1.716,0.329]],"v":[[-36.386,-11.472],[-25.909,-3.937],[-6.358,2.513],[6.464,3.595]],"c":false}]},{"t":195,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"hands 2","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[-2.832,-0.073],[-2.303,-0.066],[-1.926,-3.085],[-0.239,-5.607]],"o":[[2.794,0.072],[11.894,0.339],[2.109,3.377],[0,0]],"v":[[2.758,2.83],[11.206,2.761],[28.065,7.836],[30.818,22.809]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":23.514,"s":[{"i":[[-2.816,0.314],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.14,-0.462],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.456,3.576],[12.827,2.873],[22.174,19.291],[30.152,32.241]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":34,"s":[{"i":[[-2.805,0.396],[-2.168,-0.779],[-1.2,-3.73],[-2.259,-5.223]],"o":[[3.592,-0.507],[6.085,2.162],[1.511,4.741],[0,0]],"v":[[2.725,4.04],[12.869,3.17],[22.316,18.867],[27.587,32.67]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":48.244,"s":[{"i":[[-2.816,0.314],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.14,-0.462],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.456,3.576],[12.827,2.873],[22.174,19.291],[30.152,32.241]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":59,"s":[{"i":[[-2.816,0.149],[-2.272,-0.949],[-1.475,-3.735],[-3.255,-5.39]],"o":[[4.388,-0.192],[5.461,2.271],[1.826,4.641],[0,0]],"v":[[2.315,3.354],[12.923,3.555],[22.3,19.809],[29.56,32.985]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":77,"s":[{"i":[[-2.816,-0.309],[-2.628,-1.238],[-1.2,-3.73],[-2.259,-5.223]],"o":[[5.074,0.557],[5.841,2.753],[1.511,4.741],[0,0]],"v":[[1.925,2.738],[13.189,5.447],[22.65,21.244],[27.921,35.047]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[-0.598,-0.181],[-0.539,-0.561],[-0.196,-2.606],[1.687,-4.266]],"o":[[4.741,1.436],[3.565,3.814],[0.23,3.032],[0,0]],"v":[[5.057,5.135],[15.215,9.739],[20.248,24.608],[18.597,38.923]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":96,"s":[{"i":[[-0.457,-0.364],[-0.52,-0.555],[-0.187,-2.596],[1.723,-4.257]],"o":[[3.097,0.541],[3.544,3.823],[0.218,3.016],[0,0]],"v":[[9.643,6.899],[15.234,9.778],[20.225,24.639],[18.511,38.959]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":98,"s":[{"i":[[-0.369,-0.478],[-0.508,-0.551],[-0.181,-2.589],[1.746,-4.251]],"o":[[0.369,0.484],[3.531,3.83],[0.21,3.006],[0,0]],"v":[[14.421,8.736],[15.245,9.803],[20.211,24.659],[18.457,38.982]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":100,"s":[{"i":[[0,0],[-0.074,-0.046],[-0.361,-3.217],[1.172,-4.139]],"o":[[0,0],[3.532,3.723],[0.33,3.294],[0,0]],"v":[[15.242,9.767],[15.359,9.86],[20.37,24.574],[19.626,38.792]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":102,"s":[{"i":[[-0.412,-0.138],[-0.318,-0.426],[-0.593,-4.024],[0.433,-3.996]],"o":[[0.167,0.033],[3.533,3.586],[0.484,3.664],[0,0]],"v":[[15.365,9.779],[15.505,9.932],[20.575,24.466],[21.13,38.547]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":116,"s":[{"i":[[0,0],[-1.009,-0.802],[-1.352,-6.671],[-1.989,-3.524]],"o":[[0,0],[4.03,3.256],[0.988,4.877],[0,0]],"v":[[12.859,7.391],[15.063,8.718],[21.245,24.11],[26.059,37.745]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[0,0],[-1.097,-1.149],[-0.799,-5.031],[-0.839,-3.94]],"o":[[0,0],[3.609,3.446],[0.71,4.75],[0,0]],"v":[[12.902,6.938],[15.063,8.718],[20.372,24.232],[22.707,38.343]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":137,"s":[{"i":[[0,0],[-0.388,-0.392],[-0.475,-4.068],[-0.163,-4.185]],"o":[[0,0],[3.092,3.858],[0.546,4.676],[0,0]],"v":[[14.961,8.53],[15.063,8.718],[19.859,24.304],[20.739,38.695]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":157,"s":[{"i":[[0,0],[-0.58,-0.466],[-1.352,-6.671],[-0.912,-4.483]],"o":[[0,0],[3.124,3.255],[0.988,4.877],[0,0]],"v":[[14.98,9.235],[15.19,9.407],[20.156,24.263],[23.242,38.545]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":165,"s":[{"i":[[-3.102,-0.816],[-1.57,-1.341],[-1.352,-6.671],[-0.912,-4.483]],"o":[[0.548,0.101],[3.658,3.223],[0.988,4.877],[0,0]],"v":[[12.399,7.597],[15.19,9.407],[20.156,24.263],[23.242,38.545]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":167,"s":[{"i":[[-3.663,-0.964],[-1.749,-1.499],[-1.352,-6.671],[-0.912,-4.483]],"o":[[0.647,0.119],[3.754,3.217],[0.988,4.877],[0,0]],"v":[[8.772,6.017],[15.19,9.407],[20.156,24.263],[23.242,38.545]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":168,"s":[{"i":[[-3.718,-0.617],[-1.769,-1.459],[-1.384,-6.679],[-0.892,-4.426]],"o":[[6.299,1.053],[3.934,3.132],[1.01,4.878],[0,0]],"v":[[1.967,4.432],[15.1,9.259],[20.419,24.094],[23.49,38.317]],"c":false}]},{"t":189,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":244,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"body","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[116.171,151.524,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.723,"y":1},"o":{"x":0.395,"y":0},"t":21,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.361,"y":0},"t":32.309,"s":[116.171,147.924,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.723,"y":1},"o":{"x":0.395,"y":0},"t":48.244,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.723,"y":0.723},"o":{"x":0.167,"y":0.167},"t":73.717,"s":[116.171,147.924,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.71,"y":0},"t":167,"s":[116.171,147.924,0],"to":[0,0,0],"ti":[0,0,0]},{"t":187,"s":[116.171,146.724,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,14.211,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.484,0.484,0.484],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.486,0.486,0.486],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":15,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":21,"s":[100,100,100]},{"i":{"x":[0.486,0.486,0.486],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":26.025,"s":[98,102,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":32.309,"s":[100,100,100]},{"i":{"x":[0.486,0.486,0.486],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":39.988,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.48,0.48,0.48],"y":[0,0,0]},"t":48.244,"s":[100,100,100]},{"i":{"x":[0.471,0.471,0.471],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":59.158,"s":[98,102,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":73.717,"s":[100,100,100]},{"i":{"x":[0.471,0.471,0.471],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":89,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":104,"s":[100,100,100]},{"i":{"x":[0.471,0.471,0.471],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":114,"s":[98,102,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":133,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.71,0.71,0.71],"y":[0,0,0]},"t":167,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":175,"s":[98,102,100]},{"t":187,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[{"i":[[0,0],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[0,0]],"o":[[0,0],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,0]],"v":[[-14.632,-6.25],[-13.927,7.863],[-10.625,12.562],[0.35,14.211],[11.198,12.616],[14.544,7.749],[14.632,-5.171]],"c":false}]},{"i":{"x":0.833,"y":0.913},"o":{"x":0.5,"y":0},"t":24,"s":[{"i":[[2.01,-10.893],[-0.221,-1.818],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[-2.532,4.395]],"o":[[-1.585,8.587],[0.221,1.818],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0.024,-0.319]],"v":[[-11.288,-18.4],[-12.135,7.863],[-8.833,12.562],[0.35,14.211],[9.406,12.616],[12.752,7.749],[13.837,-10.133]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.087},"t":32,"s":[{"i":[[3.168,-10.612],[-0.338,-1.852],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[-3.171,3.957]],"o":[[-2.669,8.935],[0.338,1.852],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0.078,-0.146]],"v":[[-11.587,-18.638],[-12.157,7.863],[-8.855,12.562],[0.35,14.211],[9.428,12.616],[12.773,7.749],[14.172,-9.711]],"c":false}]},{"i":{"x":0.833,"y":0.954},"o":{"x":0.5,"y":0},"t":48,"s":[{"i":[[2.01,-10.893],[-0.209,-1.898],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[-2.532,4.395]],"o":[[-1.585,8.587],[0.209,1.898],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0.024,-0.319]],"v":[[-11.08,-17.817],[-12.127,7.863],[-8.825,12.562],[0.35,14.211],[9.398,12.616],[12.744,7.749],[13.83,-9.727]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":77,"s":[{"i":[[3.203,-10.603],[-0.305,-1.276],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[-0.275,6.886]],"o":[[-2.702,8.946],[0.305,1.276],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0.08,-0.14]],"v":[[-11.404,-15.827],[-12.123,7.863],[-8.82,12.562],[0.35,14.211],[9.394,12.616],[12.739,7.749],[14.048,-5.48]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":95,"s":[{"i":[[5.646,-9.501],[-0.274,-1.374],[-1.915,-1.173],[-2.813,0],[-2.356,1.063],[-0.75,2.369],[-0.131,5.096]],"o":[[-5.397,9.039],[0.274,1.374],[1.958,1.192],[2.767,0],[1.87,-0.844],[0.743,-2.219],[0.08,-0.14]],"v":[[-10.068,-13.558],[-13.242,7.677],[-9.903,12.562],[-0.168,14.211],[8.516,12.616],[12.267,7.934],[13.587,-2.439]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":98,"s":[{"i":[[5.771,-9.444],[-0.346,-1.217],[-1.916,-1.191],[-2.806,0],[-2.35,1.063],[-0.784,2.382],[-0.124,5.004]],"o":[[-5.535,9.043],[0.346,1.217],[1.93,1.197],[2.76,0],[1.866,-0.844],[0.782,-2.332],[0.08,-0.14]],"v":[[-9.999,-13.441],[-13.299,7.667],[-9.958,12.562],[-0.195,14.211],[8.471,12.616],[12.243,7.944],[13.58,-2.197]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":104,"s":[{"i":[[5.832,-9.417],[-0.459,-1.398],[-1.917,-1.199],[-2.803,0],[-2.348,1.063],[-0.8,2.388],[-0.12,4.96]],"o":[[-5.602,9.046],[0.459,1.398],[1.917,1.199],[2.757,0],[1.863,-0.844],[0.8,-2.388],[0.08,-0.14]],"v":[[-9.966,-13.385],[-13.327,7.663],[-9.985,12.562],[-0.208,14.211],[8.449,12.616],[12.232,7.949],[13.872,-2.099]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":117,"s":[{"i":[[5.832,-9.417],[-0.392,-1.279],[-1.917,-1.199],[-2.803,0],[-2.348,1.063],[-0.8,2.388],[0.974,5.882]],"o":[[-5.602,9.046],[0.392,1.279],[1.917,1.199],[2.757,0],[1.863,-0.844],[0.8,-2.388],[0.08,-0.14]],"v":[[-8.982,-13.385],[-12.343,7.663],[-9.001,12.562],[-0.208,14.211],[8.058,12.616],[11.841,7.949],[13.276,-4.427]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":124,"s":[{"i":[[5.832,-9.417],[-0.23,-1.373],[-1.917,-1.199],[-2.803,0],[-2.348,1.063],[-0.8,2.388],[0.177,5.21]],"o":[[-5.602,9.046],[0.23,1.373],[1.917,1.199],[2.757,0],[1.863,-0.844],[0.8,-2.388],[0.08,-0.14]],"v":[[-9.699,-13.385],[-13.06,7.663],[-9.718,12.562],[-0.208,14.211],[8.343,12.616],[12.126,7.949],[13.7,-2.8]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":134,"s":[{"i":[[5.832,-9.417],[-0.359,-1.248],[-1.917,-1.199],[-2.803,0],[-2.348,1.063],[-0.8,2.388],[-0.12,4.96]],"o":[[-5.602,9.046],[0.359,1.248],[1.917,1.199],[2.757,0],[1.863,-0.844],[0.8,-2.388],[0.08,-0.14]],"v":[[-9.966,-13.385],[-13.327,7.663],[-9.985,12.562],[-0.208,14.211],[8.449,12.616],[12.232,7.949],[13.652,-2.058]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.71,"y":0},"t":167,"s":[{"i":[[5.832,-9.417],[-0.259,-1.248],[-1.917,-1.199],[-2.803,0],[-2.348,1.063],[-0.8,2.388],[-0.12,4.96]],"o":[[-5.602,9.046],[0.259,1.248],[1.917,1.199],[2.757,0],[1.863,-0.844],[0.8,-2.388],[0.08,-0.14]],"v":[[-9.966,-13.385],[-13.327,7.663],[-9.985,12.562],[-0.208,14.211],[8.449,12.616],[12.232,7.949],[13.852,-2.099]],"c":false}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0.167},"t":171,"s":[{"i":[[4.926,-9.164],[-0.379,-1.503],[-1.916,-1.181],[-2.81,0],[-2.354,1.063],[-0.764,2.375],[-0.114,4.713]],"o":[[-4.766,9.084],[0.379,1.503],[1.946,1.194],[2.764,0],[1.868,-0.844],[0.76,-2.269],[0.076,-0.133]],"v":[[-10.024,-13.626],[-12.944,7.673],[-9.604,12.562],[-0.18,14.211],[8.309,12.616],[12.07,7.939],[13.516,-5.476]],"c":false}]},{"t":187,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":244,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"hand_6","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.75,47.75,0],"ix":2,"l":2},"a":{"a":0,"k":[256,256,0],"ix":1,"l":2},"s":{"a":0,"k":[11,11,100],"ix":6,"l":2}},"ao":0,"w":512,"h":512,"ip":0,"op":240,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_hand7.json b/submodules/TelegramUI/Resources/Animations/anim_hand7.json deleted file mode 100644 index f8f9d1967d..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_hand7.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.7.6","fr":60,"ip":0,"op":180,"w":100,"h":100,"nm":"hand_7 BIG 1x","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"handle","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.6],"y":[0]},"t":10,"s":[174]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":16,"s":[147]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.213],"y":[0]},"t":23,"s":[-17]},{"i":{"x":[0.61],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":27,"s":[-35.589]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.5],"y":[0]},"t":32,"s":[27.015]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[47.22]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0]},"t":42,"s":[-18]},{"i":{"x":[0.61],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":48,"s":[-35.589]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.5],"y":[0]},"t":54,"s":[27.015]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":61,"s":[47.22]},{"i":{"x":[0.785],"y":[-2.394]},"o":{"x":[0.518],"y":[0]},"t":73,"s":[-18]},{"i":{"x":[0.312],"y":[1]},"o":{"x":[0.204],"y":[0.31]},"t":81,"s":[-9.175]},{"t":97,"s":[174]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":10,"s":[-9.96,1.125,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":16,"s":[-16.661,-20.671,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":23,"s":[-36.588,-27.368,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":27,"s":[-30.271,-30.836,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":32,"s":[-11.215,-32.394,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":37,"s":[-16.116,-31.018,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":42,"s":[-36.588,-27.368,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[-30.271,-30.836,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":54,"s":[-11.215,-32.394,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":61,"s":[-16.116,-31.018,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":73,"s":[-36.588,-27.368,0],"to":[0,0,0],"ti":[0,0,0]},{"t":97,"s":[-9.802,-0.869,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-215.5,-116,0],"ix":1,"l":2},"s":{"a":0,"k":[20.019,19.981,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-215.146,-127.132]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-210.023,-182.103]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-207.5,-198.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-207.5,-198.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":87,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-211.874,-179.057]],"c":false}]},{"t":89,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-215.5,-116],[-215.991,-154.088]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":92,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"flag","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":92.982,"ix":10},"p":{"a":0,"k":[-204.72,-162.79,0],"ix":2,"l":2},"a":{"a":0,"k":[-122.092,14.474,0],"ix":1,"l":2},"s":{"a":0,"k":[99.986,100.014,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":12,"s":[{"i":[[0.317,-0.377],[9.871,0.008],[-5.426,0.66]],"o":[[-6.226,-0.015],[-1.013,-0.074],[4.743,0.541]],"v":[[-5.96,154.637],[-50.486,154.578],[-27.743,151.11]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":14,"s":[{"i":[[0.317,-1.093],[9.871,0.023],[-3.056,2.72]],"o":[[-6.226,-0.044],[-1.013,-0.215],[7.435,1.476]],"v":[[-5.96,154.637],[-28.783,155.672],[-28.323,148.174]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0.317,-4.427],[9.871,0.094],[-7.661,10.615]],"o":[[-6.226,-0.178],[-1.013,-0.869],[16.085,12.234]],"v":[[-5.96,154.637],[-39.634,154.55],[-66.371,118.451]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[-21.058,28.062]],"o":[[-6.226,-0.312],[-1.013,-1.524],[1.096,39.632]],"v":[[-5.96,154.637],[-50.486,153.428],[-40.692,84.982]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[-6.509,17.627]],"o":[[-6.226,-0.312],[-1.013,-1.524],[5.307,24.241]],"v":[[-5.96,154.637],[-50.486,153.428],[-25.785,87.237]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[-32.021,13.36]],"o":[[-6.226,-0.312],[-1.013,-1.524],[-16.69,26.199]],"v":[[-5.96,154.637],[-50.486,153.428],[15.356,105.805]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":25,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[-5.426,13.583]],"o":[[-6.226,-0.312],[-1.013,-1.524],[4.743,11.147]],"v":[[-5.96,154.637],[-50.486,153.428],[-27.743,82.045]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[12.378,29.044]],"o":[[-6.226,-0.312],[-1.013,-1.524],[29.83,8.678]],"v":[[-5.96,154.637],[-50.486,153.428],[-51.505,85]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":32,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-31.325,-39.218]],"o":[[-6.226,0.289],[-1.013,1.414],[-3.169,-41.969]],"v":[[-5.83,154.152],[-51.499,151.85],[-15.606,221.238]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":34,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-5.426,-12.606]],"o":[[-6.226,0.289],[-1.013,1.414],[4.743,-10.345]],"v":[[-5.83,154.152],[-51.499,151.85],[-27.874,222.489]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":37,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[11.573,-55.334]],"o":[[-6.226,0.289],[-1.013,1.414],[39.449,-25.407]],"v":[[-5.83,154.152],[-51.499,151.85],[-52.752,224.049]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":42,"s":[{"i":[[0.317,-6.39],[9.871,0.136],[-11.942,3.44]],"o":[[-6.226,-0.257],[-1.013,-1.254],[-1.26,15.905]],"v":[[-5.948,154.592],[-50.579,153.284],[-20.684,99.413]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":45,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[12.378,29.044]],"o":[[-6.226,-0.312],[-1.013,-1.524],[29.83,8.678]],"v":[[-5.96,154.637],[-50.486,153.428],[-51.505,85]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":53,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-31.325,-39.218]],"o":[[-6.226,0.289],[-1.013,1.414],[-3.169,-41.969]],"v":[[-5.83,154.152],[-51.499,151.85],[-15.606,221.238]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":55.842,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-5.426,-12.606]],"o":[[-6.226,0.289],[-1.013,1.414],[4.743,-10.345]],"v":[[-5.83,154.152],[-51.499,151.85],[-27.874,222.489]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":63,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[11.573,-55.334]],"o":[[-6.226,0.289],[-1.013,1.414],[39.449,-25.407]],"v":[[-5.83,154.152],[-51.499,151.85],[-52.752,224.049]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":71,"s":[{"i":[[0.317,-5.893],[9.871,0.126],[-21.71,21.684]],"o":[[-6.226,-0.237],[-1.013,-1.157],[-2.05,24.036]],"v":[[-5.944,154.576],[-50.612,153.231],[-14.362,101.878]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":75,"s":[{"i":[[0.317,-7.761],[9.871,0.165],[-5.426,13.583]],"o":[[-6.226,-0.312],[-1.013,-1.524],[4.743,11.147]],"v":[[-5.96,154.637],[-50.486,153.428],[-27.743,82.045]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":81,"s":[{"i":[[0.317,5.961],[9.871,-0.127],[30.989,-21.541]],"o":[[-6.226,0.24],[-1.013,1.17],[42.685,-3.8]],"v":[[-5.841,154.192],[-51.415,151.981],[-117.05,184.128]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":83,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-22.34,-40.1]],"o":[[-6.226,0.289],[-1.013,1.414],[1.595,-36.326]],"v":[[-5.83,154.152],[-51.499,151.85],[-28.747,222.978]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":86,"s":[{"i":[[0.139,7.208],[9.871,0.091],[-5.112,-12.737]],"o":[[-6.231,0.135],[-1.048,1.388],[4.997,-10.225]],"v":[[-5.313,153.232],[-48.29,151.152],[-29.044,221.002]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0},"t":87,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-5.426,-12.606]],"o":[[-13.386,-2.417],[-1.013,1.414],[4.743,-10.345]],"v":[[-4.828,156.44],[-48.115,167.327],[-27.874,222.489]],"c":true}]},{"t":89,"s":[{"i":[[0.317,7.203],[9.871,-0.153],[-0.737,-10.604]],"o":[[-6.226,0.289],[-1.013,1.414],[4.743,-10.345]],"v":[[-5.83,154.152],[-15.405,184.967],[-16.93,216.13]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-113.297,-133.109],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":14,"op":90,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256.989,275.127,0],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":182,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"head","parent":8,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.6],"y":[0]},"t":1,"s":[0]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":11,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":23,"s":[-13]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.16],"y":[0]},"t":71,"s":[-13]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0]},"t":98.215,"s":[0]},{"i":{"x":[0.09],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":106.715,"s":[7]},{"t":127.357421875,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":1,"s":[0,-37.327,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":11,"s":[0,-26.827,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":23,"s":[-4.4,-39.427,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":36,"s":[-2.401,-30.424,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":50,"s":[-4.4,-39.427,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.315,"y":0},"t":71,"s":[-2.401,-30.424,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.496,"y":1},"o":{"x":0.6,"y":0},"t":98,"s":[5.201,-26.827,0],"to":[0,0,0],"ti":[0,0,0]},{"t":127.357421875,"s":[0,-37.327,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":1,"s":[100,100,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":11,"s":[105,95,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":16,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":23,"s":[105,95,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":28,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":36,"s":[100,100,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":44,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":50,"s":[105,95,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":58,"s":[97,103,100]},{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.16,0.16,0.16],"y":[0,0,0]},"t":71,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.6,0.6,0.6],"y":[0,0,0]},"t":98.215,"s":[105,95,100]},{"i":{"x":[0.09,0.09,0.09],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":106.715,"s":[95,105,100]},{"t":127.357421875,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-6.341,0],[0,-6.341],[6.341,0],[0,6.341]],"o":[[6.341,0],[0,6.341],[-6.341,0],[0,-6.341]],"v":[[0,-11.482],[11.482,0],[0,11.482],[-11.482,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Oval","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":183,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":3,"nm":"hands","parent":8,"sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":21,"s":[-10]},{"i":{"x":[0.17],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":70,"s":[-10]},{"t":128.572265625,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[-2.341,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[-2.341,-9.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":21,"s":[-3.741,-17.977,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":35,"s":[-1.541,-11.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.5,"y":0},"t":49,"s":[-1.541,-17.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.544,"y":0},"t":70,"s":[-1.541,-11.777,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.134,"y":1},"o":{"x":0.5,"y":0},"t":97,"s":[-2.341,-9.777,0],"to":[0,0,0],"ti":[0,0,0]},{"t":128.572265625,"s":[-2.341,-17.777,0]}],"ix":2,"l":2},"a":{"a":0,"k":[115,115,0],"ix":1,"l":2},"s":{"a":0,"k":[20,20,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":183,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"hands 3","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[0,0],[-0.71,-5.814],[-13.22,1.67],[-6.659,-0.445]],"o":[[-4.86,-9.215],[0.566,4.637],[3.994,-0.505],[1.183,0.079]],"v":[[-9.125,0.522],[-27.079,-2.224],[-7.644,3.484],[11.49,2.929]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":16,"s":[{"i":[[0,0],[-1.902,-5.873],[-9.833,-0.14],[-7.372,0.373]],"o":[[-4.328,1.758],[1.817,5.11],[4.766,0.217],[1.182,0.009]],"v":[[-17.041,-20.099],[-26.446,-5.406],[-7.078,3.541],[9.615,2.763]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":23,"s":[{"i":[[0,0],[-4.205,-5.986],[-9.957,-1.926],[-4.458,0.474]],"o":[[5.753,4.944],[4.232,6.025],[3.773,0.73],[1.179,-0.125]],"v":[[-37.604,-27.175],[-25.224,-11.553],[-5.844,2.761],[7.055,3.069]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":27,"s":[{"i":[[0,0],[-1.728,-7.979],[-9.811,-4.073],[-4.458,0.474]],"o":[[3.018,5.449],[1.024,4.728],[4.574,1.385],[1.179,-0.125]],"v":[[-30.389,-31.532],[-24.556,-12.392],[-6.174,2.474],[7.055,3.069]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":32,"s":[{"i":[[0,0],[-0.133,-5.267],[-9.077,-3.752],[-4.458,0.474]],"o":[[-4.804,4.722],[0.193,7.66],[5.825,2.408],[1.179,-0.125]],"v":[[-10.804,-33.837],[-18.834,-15.906],[-6.69,2.025],[7.055,3.069]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":37,"s":[{"i":[[0,0],[-0.271,-4.621],[-9.517,-2.839],[-4.458,0.474]],"o":[[-3.394,4.297],[0.242,4.13],[4.799,1.569],[1.179,-0.125]],"v":[[-15.907,-30.468],[-22.029,-13.729],[-6.267,2.393],[7.055,3.069]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":42,"s":[{"i":[[0,0],[-3.928,-6.171],[-9.957,-1.926],[-4.563,0.793]],"o":[[5.522,5.2],[3.954,6.211],[3.773,0.73],[1.168,-0.203]],"v":[[-36.207,-29.267],[-24.551,-13.099],[-5.703,2.786],[7.055,3.069]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":48,"s":[{"i":[[0,0],[-1.728,-7.979],[-9.811,-4.073],[-4.458,0.474]],"o":[[3.018,5.449],[1.024,4.728],[4.574,1.385],[1.179,-0.125]],"v":[[-30.389,-31.532],[-24.556,-12.392],[-6.174,2.474],[7.055,3.069]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":54,"s":[{"i":[[0,0],[-0.133,-5.267],[-9.319,-3.103],[-4.568,0.674]],"o":[[-4.804,4.722],[0.193,7.66],[5.735,1.909],[1.173,-0.173]],"v":[[-10.804,-33.837],[-18.834,-15.906],[-6.656,1.832],[7.072,2.972]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":61,"s":[{"i":[[0,0],[-0.271,-4.621],[-9.517,-2.839],[-4.458,0.474]],"o":[[-3.394,4.297],[0.242,4.13],[4.799,1.569],[1.179,-0.125]],"v":[[-15.907,-30.468],[-22.029,-13.729],[-6.267,2.393],[7.055,3.069]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.5,"y":0},"t":73,"s":[{"i":[[0,0],[-4.205,-5.986],[-9.957,-1.926],[-4.458,0.474]],"o":[[5.753,4.944],[4.232,6.025],[3.773,0.73],[1.179,-0.125]],"v":[[-37.604,-27.175],[-25.224,-11.553],[-5.844,2.761],[7.055,3.069]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.6,"y":0},"t":97,"s":[{"i":[[0,0],[-0.71,-5.814],[-13.22,1.67],[-6.659,-0.445]],"o":[[-4.86,-9.215],[0.566,4.637],[3.994,-0.505],[1.183,0.079]],"v":[[-9.125,0.522],[-27.079,-2.224],[-7.644,3.484],[11.49,2.929]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":110,"s":[{"i":[[0,0],[-3.924,-6.477],[-10.094,-0.98],[-3.546,0.075]],"o":[[4.365,3.67],[3.808,6.286],[3.961,0.384],[1.183,-0.028]],"v":[[-37.672,-26.337],[-25.271,-10.97],[-5.942,2.371],[7.258,2.943]],"c":false}]},{"t":131,"s":[{"i":[[0,0],[-4.026,-6.73],[-10.797,-1.273],[-2.119,-0.01]],"o":[[0.65,4.552],[3.882,6.488],[3.499,0.413],[1.186,0.005]],"v":[[-34.027,-30.664],[-27.902,-11.553],[-6.28,2.761],[4.392,3.022]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":183,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"hands 2","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[115,115,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[500,500,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":10,"s":[{"i":[[-2.832,-0.073],[-2.303,-0.066],[-1.926,-3.085],[-0.239,-5.607]],"o":[[2.794,0.072],[11.894,0.339],[2.109,3.377],[0,0]],"v":[[2.763,2.93],[11.324,2.869],[28.065,7.836],[30.818,22.809]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":22,"s":[{"i":[[-2.833,0.019],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.963,-0.034],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.632,3.384],[12.827,2.873],[22.174,19.291],[30.152,32.241]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":36,"s":[{"i":[[-2.83,-0.135],[-3.536,-1.048],[-1.592,-3.476],[-1.09,-6.235]],"o":[[7.035,0.337],[5.485,1.626],[1.67,3.647],[0,0]],"v":[[1.498,3.488],[12.683,2.542],[23.022,15.874],[26.398,31.323]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":50,"s":[{"i":[[-2.829,0.15],[-2.144,-0.845],[-1.574,-3.736],[-3.615,-5.451]],"o":[[4.712,-0.25],[5.323,2.097],[1.94,4.605],[0,0]],"v":[[2.417,3.447],[12.827,2.873],[22.174,19.291],[30.152,32.241]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.4,"y":0},"t":71,"s":[{"i":[[-2.833,0.023],[-3.532,-0.206],[-1.416,-3.799],[-0.329,-6.321]],"o":[[4.317,-0.036],[4.623,0.27],[1.401,3.758],[0,0]],"v":[[1.983,3.325],[12.861,3.014],[25.932,11.288],[27.784,26.515]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.6,"y":0},"t":97,"s":[{"i":[[-2.832,-0.073],[-2.303,-0.066],[-1.926,-3.085],[-0.239,-5.607]],"o":[[2.794,0.072],[11.894,0.339],[2.109,3.377],[0,0]],"v":[[2.763,2.93],[11.324,2.869],[28.065,7.836],[30.818,22.809]],"c":false}]},{"t":124.927734375,"s":[{"i":[[-3.103,0.058],[-2.523,0.098],[-2.257,-5.922],[-0.05,-3.076]],"o":[[2.726,-0.051],[10.848,-0.42],[1.636,4.291],[0,0]],"v":[[3.047,3.014],[11.974,3.008],[30.43,17.71],[32.973,31.114]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":183,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"body","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[116.171,151.524,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":21,"s":[116.171,140.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":35,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.5,"y":0},"t":49,"s":[116.171,143.924,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.544,"y":0},"t":70,"s":[116.171,146.724,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.134,"y":1},"o":{"x":0.5,"y":0},"t":97,"s":[116.171,151.524,0],"to":[0,0,0],"ti":[0,0,0]},{"t":128.572265625,"s":[116.171,146.724,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,14.211,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":5,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.484,0.484,0.484],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.486,0.486,0.486],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":15,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":21,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":27,"s":[97,103,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":35,"s":[100,100,100]},{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":42,"s":[103,97,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.5,0.5,0.5],"y":[0,0,0]},"t":49,"s":[100,100,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":56,"s":[97,103,100]},{"i":{"x":[0.5,0.5,0.5],"y":[1,1,1]},"o":{"x":[0.422,0.422,0.422],"y":[0,0,0]},"t":70,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,1]},"o":{"x":[0.484,0.484,0.484],"y":[0,0,0]},"t":97,"s":[100,100,100]},{"i":{"x":[0.17,0.17,0.17],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":109.143,"s":[97,103,100]},{"t":128.572265625,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":0,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.5,"y":0},"t":10,"s":[{"i":[[0,0],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[0,0]],"o":[[0,0],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,0]],"v":[[-14.032,-4.05],[-13.127,7.863],[-9.825,12.562],[0.35,14.211],[10.398,12.616],[13.744,7.749],[14.232,-7.571]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.5,"y":0},"t":21,"s":[{"i":[[-1.863,-5.271],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[1.3,6.472]],"o":[[1.863,5.271],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[-0.223,-1.111]],"v":[[-14.632,-13.411],[-12.127,7.863],[-8.825,12.562],[0.35,14.211],[9.398,12.616],[12.744,7.749],[11.232,-16.011]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0},"t":35,"s":[{"i":[[-1.922,-5.439],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[2.094,6.249]],"o":[[1.922,5.439],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,-0.323]],"v":[[-15.346,-10.627],[-12.527,7.863],[-9.225,12.562],[0.35,14.211],[9.798,12.616],[13.144,7.749],[11.974,-10.116]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0.167},"t":42,"s":[{"i":[[-1.882,-5.326],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[1.559,6.399]],"o":[[1.882,5.326],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[-0.197,-0.919]],"v":[[-14.481,-12.569],[-12.098,7.863],[-8.796,12.562],[0.35,14.211],[9.369,12.616],[12.715,7.749],[11.484,-13.946]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":49,"s":[{"i":[[-1.863,-5.271],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[1.3,6.472]],"o":[[1.863,5.271],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[-0.223,-1.111]],"v":[[-14.732,-16.111],[-12.127,7.863],[-8.825,12.562],[0.35,14.211],[9.398,12.616],[12.744,7.749],[11.532,-15.511]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":54,"s":[{"i":[[-1.872,-5.295],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[1.413,6.44]],"o":[[1.872,5.295],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[-0.191,-0.998]],"v":[[-14.729,-16.667],[-12.144,7.863],[-8.842,12.562],[0.35,14.211],[9.415,12.616],[12.761,7.749],[11.555,-14.741]],"c":false}]},{"i":{"x":0.833,"y":1},"o":{"x":0.167,"y":0.167},"t":57,"s":[{"i":[[-1.882,-5.326],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[1.559,6.399]],"o":[[1.882,5.326],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,0.274]],"v":[[-14.818,-14.61],[-12.155,7.863],[-8.853,12.562],[0.35,14.211],[9.426,12.616],[12.771,7.749],[11.18,-14.235]],"c":false}]},{"i":{"x":0.5,"y":1},"o":{"x":0.167,"y":0},"t":70,"s":[{"i":[[-1.922,-5.439],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[2.094,6.249]],"o":[[1.922,5.439],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,-0.323]],"v":[[-15.324,-9.227],[-12.527,7.863],[-9.225,12.562],[0.35,14.211],[9.798,12.616],[13.144,7.749],[11.974,-10.116]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.5,"y":0},"t":97,"s":[{"i":[[0,0],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[0,0]],"o":[[0,0],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,0]],"v":[[-14.032,-4.05],[-13.127,7.863],[-9.825,12.562],[0.35,14.211],[10.398,12.616],[13.744,7.749],[14.232,-7.571]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":104,"s":[{"i":[[-0.837,-2.368],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[0.912,2.72]],"o":[[0.837,2.368],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,-0.141]],"v":[[-13.849,-7.596],[-12.139,7.863],[-8.836,12.562],[0.35,14.211],[9.41,12.616],[12.755,7.749],[12.383,-8.209]],"c":false}]},{"i":{"x":0.17,"y":1},"o":{"x":0.167,"y":0.167},"t":112,"s":[{"i":[[0,-1.426],[0,0],[-1.89,-0.83],[-2.946,0],[-2.468,1.063],[-0.086,2.131],[0.292,0.87]],"o":[[0,0.803],[0.14,2.059],[2.504,1.099],[2.898,0],[1.959,-0.844],[0,0],[0,-0.045]],"v":[[-13.276,-13.757],[-12.148,7.863],[-8.845,12.562],[0.35,14.211],[9.419,12.616],[12.764,7.749],[12.791,-11.807]],"c":false}]},{"t":128.572265625,"s":[{"i":[[0,0],[-0.741,-5.903],[-1.922,-0.751],[-2.946,0],[-2.468,1.063],[-0.228,3.004],[0,0]],"o":[[0,0],[0.328,2.608],[2.556,0.999],[2.898,0],[1.959,-0.844],[0.638,-8.388],[0,0]],"v":[[-12.432,-14.575],[-11.977,7.814],[-8.487,12.849],[0.4,13.948],[9.223,12.916],[12.656,7.499],[12.482,-13.225]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":183,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"hand_7","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50.75,47.75,0],"ix":2,"l":2},"a":{"a":0,"k":[256,256,0],"ix":1,"l":2},"s":{"a":0,"k":[11,11,100],"ix":6,"l":2}},"ao":0,"w":512,"h":512,"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_moretosort_r.json b/submodules/TelegramUI/Resources/Animations/anim_moretosort_r.json deleted file mode 100644 index c1466c90e7..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_moretosort_r.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.12.1","fr":60,"ip":0,"op":90,"w":512,"h":512,"nm":"more_to_sort_cw","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"1","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[101,150,0],"to":[2.333,0,0],"ti":[-2.333,0,0]},{"t":89,"s":[115,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[0,-0.823],[-0.823,0],[0,0],[0,0.823],[0.812,0]],"o":[[-0.823,0],[0,0.823],[0,0],[0.812,0],[0,-0.823],[0,0]],"v":[[-0.01,-1.5],[-1.5,0],[-0.01,1.5],[0.01,1.5],[1.5,0],[0.01,-1.5]],"c":true}]},{"t":89,"s":[{"i":[[0,0],[0,-0.367],[-0.367,0],[0,0],[0,0.367],[0.367,0]],"o":[[-0.367,0],[0,0.367],[0,0],[0.367,0],[0,-0.367],[0,0]],"v":[[-6,-0.665],[-6.665,0],[-6,0.665],[6,0.665],[6.665,0],[6,-0.665]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[1000,1000],"ix":3},"r":{"a":0,"k":-90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202 (Stroke)","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"2","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[150.5,150,0],"to":[0.75,0,0],"ti":[-0.75,0,0]},{"t":89,"s":[155,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[0,-0.823],[-0.823,0],[0,0],[0,0.823],[0.812,0]],"o":[[-0.823,0],[0,0.823],[0,0],[0.812,0],[0,-0.823],[0,0]],"v":[[-0.01,-1.5],[-1.5,0],[-0.01,1.5],[0.01,1.5],[1.5,0],[0.01,-1.5]],"c":true}]},{"t":89,"s":[{"i":[[0,0],[0,-0.367],[-0.367,0],[0,0],[0,0.367],[0.367,0]],"o":[[-0.367,0],[0,0.367],[0,0],[0.367,0],[0,-0.367],[0,0]],"v":[[-4,-0.665],[-4.665,0],[-4,0.665],[4,0.665],[4.665,0],[4,-0.665]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[1000,1000],"ix":3},"r":{"a":0,"k":-90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202 (Stroke)","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"3","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[200,150,0],"to":[-0.833,0,0],"ti":[0.833,0,0]},{"t":89,"s":[195,150,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[0,0],[0,-0.823],[-0.823,0],[0,0],[0,0.823],[0.812,0]],"o":[[-0.823,0],[0,0.823],[0,0],[0.812,0],[0,-0.823],[0,0]],"v":[[-0.01,-1.5],[-1.5,0],[-0.01,1.5],[0.01,1.5],[1.5,0],[0.01,-1.5]],"c":true}]},{"t":89,"s":[{"i":[[0,0],[0,-0.367],[-0.367,0],[0,0],[0,0.367],[0.367,0]],"o":[[-0.367,0],[0,0.367],[0,0],[0.367,0],[0,-0.367],[0,0]],"v":[[-2,-0.665],[-2.665,0],[-2,0.665],[2,0.665],[2.665,0],[2,-0.665]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[1000,1000],"ix":3},"r":{"a":0,"k":-90,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202 (Stroke)","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":90,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"more and search","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.6],"y":[0]},"t":0,"s":[0]},{"t":89,"s":[90]}],"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2,"l":2},"a":{"a":0,"k":[150,150,0],"ix":1,"l":2},"s":{"a":0,"k":[170.667,170.667,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.6,"y":0},"t":0,"s":[{"i":[[-55.228,0],[0,55.229],[55.229,0],[0,-55.228]],"o":[[55.229,0],[0,-55.228],[-55.228,0],[0,55.229]],"v":[[23.5,123.5],[123.5,23.5],[23.5,-76.5],[-76.5,23.5]],"c":true}]},{"t":102,"s":[{"i":[[-55.228,0],[0,55.229],[55.229,0],[0,-55.228]],"o":[[55.229,0],[0,-55.228],[-55.228,0],[0,55.229]],"v":[[23.5,123.5],[123.5,23.5],[23.5,-76.5],[-76.5,23.5]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":13.33,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[126.5,126.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"c","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-10,"op":92,"st":-10,"ct":1,"bm":0}],"markers":[],"props":{}} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_notificationsoundprogress.json b/submodules/TelegramUI/Resources/Animations/anim_notificationsoundprogress.json deleted file mode 100644 index 7c93f3b7b7..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_notificationsoundprogress.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"4.8.0","meta":{"g":"LottieFiles AE ","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":60,"w":512,"h":512,"nm":"Sound Download","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL ALL","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.1,"y":0},"t":-1,"s":[256,56,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.7,"y":0},"t":11,"s":[256,322,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":25,"s":[256,228,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.41,"y":1},"o":{"x":0.3,"y":0},"t":39,"s":[256,288,0],"to":[0,0,0],"ti":[0,0,0]},{"t":59,"s":[256,256,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.359,0.359,0.115],"y":[0,0,0]},"t":0,"s":[20,60,100]},{"i":{"x":[0.54,0.54,0.738],"y":[1,1,1]},"o":{"x":[0.1,0.1,0.1],"y":[0,0,0]},"t":11,"s":[102,70,100]},{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":25,"s":[90,110,100]},{"i":{"x":[0.41,0.41,0.41],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":39,"s":[105,95,100]},{"t":59,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":1,"op":179,"st":-1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Arrow 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.7,"y":0},"t":8,"s":[387,21.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.4,"y":1},"o":{"x":0.3,"y":0},"t":22,"s":[389,181.333,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.41,"y":1},"o":{"x":0.3,"y":0},"t":36,"s":[399,111.333,0],"to":[0,0,0],"ti":[0,0,0]},{"t":59,"s":[395,141.333,0]}],"ix":2},"a":{"a":0,"k":[139,-114.667,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[112.991,-95.024],[139.219,-96.402],[166.009,-94.955]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[112.991,-101.972],[139.219,-36.333],[166.009,-105.232]],"c":false}]},{"i":{"x":0.71,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[68,-84],[139.173,-36.333],[210,-84]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.29,"y":0},"t":36,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[86.5,-84],[139.137,-36.333],[193,-84.5]],"c":false}]},{"t":59,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[70.667,-86.5],[139.167,-36.333],[207.333,-86.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[138.582,-93.931],[139.036,-96.361]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[138.582,-154],[139.036,-38.319]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":19,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[138.145,-207.143],[139.051,-37.851]],"c":false}]},{"i":{"x":0.71,"y":1},"o":{"x":0.167,"y":0.167},"t":22,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[138.063,-170],[139.063,-37.5]],"c":false}]},{"i":{"x":0.4,"y":1},"o":{"x":0.29,"y":0},"t":36,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[139.063,-193],[139.051,-37.5]],"c":false}]},{"t":59,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[139.063,-193],[139.063,-37.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":12,"op":179,"st":-1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"R","parent":5,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[88.174,82.369,0],"ix":2},"a":{"a":0,"k":[88.174,82.369,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[-0.626,-19.83],[0,-0.716],[39.672,0],[0,30.227],[-39.672,0],[-10.43,-4.012],[0,0],[-5.043,-3.301]],"o":[[0,0],[0,30.227],[-39.672,0],[0,-30.227],[12.077,0],[0,0],[3.48,-0.35],[11.788,7.716]],"v":[[159.476,-150.419],[160.007,103.453],[84.753,158.183],[16.341,103.453],[84.753,48.723],[118.958,55.005],[118.979,-191.031],[139.841,-186.806]],"c":true}]},{"i":{"x":0.517,"y":1},"o":{"x":0.589,"y":0},"t":22,"s":[{"i":[[-4.753,-0.752],[0,-0.716],[39.672,0],[0,30.227],[-39.672,0],[-10.43,-4.012],[0,0],[-5.911,-1.178]],"o":[[0,0],[0,30.227],[-39.672,0],[0,-30.227],[12.077,0],[0,0],[5.313,2.033],[9.821,1.957]],"v":[[159.61,61.441],[160.007,103.453],[84.753,158.183],[16.341,103.453],[84.753,48.723],[118.958,55.005],[119.173,53.964],[136.554,58.5]],"c":true}]},{"i":{"x":0.41,"y":1},"o":{"x":0.433,"y":0},"t":36,"s":[{"i":[[-6.917,3.879],[0,-0.716],[39.672,0],[0,30.227],[-39.672,0],[-10.43,-4.012],[0,0],[-6.028,0]],"o":[[0,0],[0,30.227],[-39.672,0],[0,-30.227],[12.077,0],[0,0],[5.313,2.033],[8.494,0]],"v":[[159.348,-48.29],[160.007,103.453],[84.753,158.183],[16.341,103.453],[84.753,48.723],[118.958,55.005],[118.851,-45.346],[135.959,-42.2]],"c":true}]},{"t":59,"s":[{"i":[[-6.917,3.879],[0,-0.716],[39.672,0],[0,30.227],[-39.672,0],[-10.43,-4.012],[0,0],[-6.028,0]],"o":[[0,0],[0,30.227],[-39.672,0],[0,-30.227],[12.077,0],[0,0],[5.313,2.033],[8.494,0]],"v":[[159.452,6.555],[160.007,103.453],[84.753,158.183],[16.341,103.453],[84.753,48.723],[118.958,55.005],[118.955,9.499],[136.063,12.646]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":181,"st":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"L","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-58.912,2.378,0],"ix":2},"a":{"a":0,"k":[-58.912,2.378,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":0.751},"o":{"x":0.05,"y":0},"t":13,"s":[{"i":[[0,0],[0,0],[0,0],[0.171,-0.721],[0,-0.687],[39.672,0],[0,30.227],[-39.672,0],[-9.268,-3.321],[0,0],[-19.193,4.657]],"o":[[0,0],[0,0],[-0.752,0.152],[0,0],[0,30.227],[-39.672,0],[0,-30.227],[10.92,0],[0,0],[0,-19.896],[0,0]],"v":[[118.79,-191.298],[119.935,-149.636],[-61.03,-115.682],[-62.53,-114.233],[-62.334,137.659],[-134.167,192.389],[-206,137.659],[-134.167,82.929],[-103.627,88.107],[-103.629,-113.786],[-70.799,-155.552]],"c":true}]},{"i":{"x":0.564,"y":1},"o":{"x":0.514,"y":0.128},"t":32,"s":[{"i":[[0,0],[0,0],[0,0],[0.171,-0.721],[0,-0.687],[39.672,0],[0,30.227],[-39.672,0],[-9.268,-3.322],[0,0],[-19.192,4.657]],"o":[[0,0],[0,0],[-0.752,0.152],[0,0],[0,30.227],[-39.672,0],[0,-30.227],[10.92,0],[0,0],[0,-19.896],[0,0]],"v":[[54.288,-183.407],[54.277,-141.529],[-61.03,-115.682],[-62.53,-114.233],[-62.334,137.659],[-134.167,192.389],[-206,137.659],[-134.167,82.929],[-103.627,88.107],[-103.629,-113.786],[-70.799,-155.552]],"c":true}]},{"t":59,"s":[{"i":[[0,0],[0,0],[0,0],[0.171,-0.721],[0,-0.687],[39.672,0],[0,30.227],[-39.672,0],[-9.268,-3.322],[0,0],[-19.193,4.657]],"o":[[0,0],[0,0],[-0.752,0.152],[0,0],[0,30.227],[-39.672,0],[0,-30.227],[10.92,0],[0,0],[0,-19.896],[0,0]],"v":[[88.175,-187.632],[88.165,-145.754],[-61.03,-115.682],[-62.53,-114.233],[-62.334,137.659],[-134.167,192.389],[-206,137.659],[-134.167,82.929],[-103.628,88.107],[-103.629,-113.786],[-70.799,-155.552]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":179,"st":-1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_voicemute.json b/submodules/TelegramUI/Resources/Animations/anim_voicemute.json deleted file mode 100644 index a37a9c8609..0000000000 --- a/submodules/TelegramUI/Resources/Animations/anim_voicemute.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.5.9","fr":60,"ip":0,"op":20,"w":72,"h":72,"nm":"ic_mute","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[34.5,37.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.5,-8.5],[8.5,8.5]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Обрезать контуры 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path 5","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[34.5,37.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.5,-8.5],[8.5,8.5]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.109803922474,0.109803922474,0.117647059262,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Обводка 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Обрезать контуры 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Icon","tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[36,37.2,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.36,0],[0,-0.36],[3.81,-0.34],[0,0],[0.37,0],[0.04,0.32],[0,0],[0,0],[0,3.9],[-0.36,0],[0,-0.36],[-3.38,0],[0,3.39]],"o":[[0.37,0],[0,3.9],[0,0],[0,0.37],[-0.33,0],[0,0],[0,0],[-3.81,-0.34],[0,-0.36],[0.37,0],[0,3.39],[3.39,0],[0,-0.36]],"v":[[6.796,-1.464],[7.466,-0.804],[0.666,6.636],[0.666,9.196],[-0.004,9.866],[-0.654,9.286],[-0.664,9.196],[-0.664,6.636],[-7.464,-0.804],[-6.804,-1.464],[-6.134,-0.804],[-0.004,5.336],[6.136,-0.804]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-2.35,0],[-0.09,-2.28],[0,0],[0,0],[2.36,0],[0.1,2.27],[0,0],[0,0]],"o":[[2.3,0],[0,0],[0,0],[0,2.36],[-2.29,0],[0,0],[0,0],[0,-2.35]],"v":[[-0.004,-9.864],[4.256,-5.774],[4.266,-5.604],[4.266,-0.804],[-0.004,3.466],[-4.264,-0.624],[-4.264,-0.804],[-4.264,-5.604]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[1.62,0],[0.09,-1.55],[0,0],[0,0],[-1.63,0],[-0.08,1.55],[0,0],[0,0]],"o":[[-1.57,0],[0,0],[0,0],[0,1.62],[1.57,0],[0,0],[0,0],[0,-1.63]],"v":[[0.004,-8.536],[-2.936,-5.756],[-2.936,-5.596],[-2.936,-0.796],[0.004,2.134],[2.934,-0.646],[2.934,-0.796],[2.934,-5.596]],"c":true},"ix":2},"nm":"Контур 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":20,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/device_edge.json b/submodules/TelegramUI/Resources/Animations/device_edge.json deleted file mode 100644 index 487209ad55..0000000000 --- a/submodules/TelegramUI/Resources/Animations/device_edge.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.7.4","fr":60,"ip":0,"op":120,"w":30,"h":30,"nm":"edge_30","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Union","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0.003,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.697,0],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[0.958,0.002],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.84,1.691],[1.9,1.628],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[7.228,5.252],[8.051,4.885],[8.251,4.849],[8.43,4.943],[8.514,5.128],[8.467,5.325],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[9.361,0.912],[7.622,2.661],[5.258,3.314],[5.256,3.314],[2.011,2.635],[1.642,2.071]],"c":true}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.279,0.493],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[1.152,-0.173],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.319,2.836],[1.379,2.774],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[6.189,5.293],[7.114,4.78],[7.313,4.745],[7.493,4.839],[7.577,5.024],[7.529,5.221],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[8.84,2.058],[7.102,3.807],[4.737,4.459],[4.735,4.459],[1.49,3.781],[1.121,3.216]],"c":true}]},{"t":50,"s":[{"i":[[-0.082,0.078],[-0.014,0.019],[0,0.42],[0.001,0.035],[0,0],[0,0],[0,0.009],[0.002,0.03],[0.293,0.391],[0.348,0.176],[0.39,0.003],[0.247,-0.117],[0,0],[0,0],[0.009,-0.004],[0.226,-0.375],[0.005,-0.438],[-3.697,0],[-0.897,0.336],[-0.267,0.139],[-0.069,-0.011],[-0.048,-0.051],[-0.007,-0.069],[0.037,-0.059],[2.152,-0.744],[0,0],[0.253,-0.054],[-0.228,0.072],[2.252,0.966],[1.04,2.219],[-0.014,1.51],[-0.03,0.216],[0.003,-0.209],[-1.868,1.842],[-2.628,0],[-1.655,-3.238],[-0.012,-0.024],[-0.003,-0.006],[0.019,-1.66],[0.417,-0.725],[0.722,-0.422],[0.832,-0.002],[0.001,0],[1.105,0.768],[0,0.204]],"o":[[0.024,-0.023],[0.395,-0.514],[0,-0.029],[0,0],[0,0],[0,-0.009],[-0.001,-0.027],[-0.029,-0.486],[-0.233,-0.313],[-0.348,-0.176],[-0.533,-0.01],[0,0],[0,0],[-0.009,0.004],[-0.384,0.209],[-0.226,0.375],[0,3.263],[0.958,0.002],[0.282,-0.106],[0.061,-0.034],[0.069,0.011],[0.048,0.051],[0.007,0.069],[-1.215,1.926],[0,0],[-0.193,0.061],[0.234,-0.044],[-2.325,0.775],[-2.252,-0.966],[-0.636,-1.37],[0,-0.213],[-0.033,0.203],[0.041,-2.622],[1.871,-1.845],[3.886,0],[0.011,0.022],[0.003,0.006],[0.283,0.552],[-0.002,0.836],[-0.417,0.725],[-0.716,0.425],[0,0],[-0.073,0.003],[-0.236,-0.165],[0,-0.191]],"v":[[1.84,1.691],[1.9,1.628],[2.509,0.073],[2.508,-0.023],[2.508,-0.022],[2.508,-0.021],[2.507,-0.048],[2.504,-0.134],[2.011,-1.479],[1.127,-2.223],[0.004,-2.495],[-1.181,-2.196],[-1.181,-2.196],[-1.183,-2.195],[-1.21,-2.182],[-2.142,-1.29],[-2.493,-0.049],[4.423,5.757],[7.228,5.252],[8.051,4.885],[8.251,4.849],[8.43,4.943],[8.514,5.128],[8.467,5.325],[3.264,9.45],[3.162,9.484],[2.478,9.66],[3.171,9.487],[-3.935,9.19],[-9.045,4.245],[-9.991,-0.13],[-9.945,-0.774],[-10,-0.156],[-7.022,-7.122],[-0.001,-10.001],[9.114,-4.9],[9.149,-4.832],[9.157,-4.815],[10,-1.47],[9.361,0.912],[7.622,2.661],[5.258,3.314],[5.256,3.314],[2.011,2.635],[1.642,2.071]],"c":true}]}],"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0.005]],"o":[[0,-0.005],[0,0]],"v":[[-10,-0.142],[-10,-0.156]],"c":false},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[600,600],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Union","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":-60,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Ellipse 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[1090]},{"t":50,"s":[1080]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[16.667,16.667,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[11.667,11.667,100]},{"t":40,"s":[16.667,16.667,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":120,"st":-60,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Fonts/SFCompactRounded-Semibold.otf b/submodules/TelegramUI/Resources/Fonts/SFCompactRounded-Semibold.otf deleted file mode 100644 index 100e58bff7..0000000000 Binary files a/submodules/TelegramUI/Resources/Fonts/SFCompactRounded-Semibold.otf and /dev/null differ diff --git a/submodules/TelegramUI/Resources/Fonts/latinmodern-math.otf b/submodules/TelegramUI/Resources/Fonts/latinmodern-math.otf new file mode 100644 index 0000000000..a7347598ae Binary files /dev/null and b/submodules/TelegramUI/Resources/Fonts/latinmodern-math.otf differ diff --git a/submodules/TelegramUI/Resources/Fonts/latinmodern-math.plist b/submodules/TelegramUI/Resources/Fonts/latinmodern-math.plist new file mode 100755 index 0000000000..c9240d8b76 Binary files /dev/null and b/submodules/TelegramUI/Resources/Fonts/latinmodern-math.plist differ diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 419d1a1e27..dcbf7c913f 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -329,9 +329,9 @@ public final class AccountContextImpl: AccountContext { self.animationRenderer = DCTMultiAnimationRendererImpl() (self.animationRenderer as? DCTMultiAnimationRendererImpl)?.useYuvA = sharedContext.immediateExperimentalUISettings.compressedEmojiCache - let updatedLimitsConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.limitsConfiguration]) + let updatedLimitsConfiguration = self.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.limitsConfiguration)) |> map { preferences -> LimitsConfiguration in - return preferences.values[PreferencesKeys.limitsConfiguration]?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue + return preferences?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue } self.currentLimitsConfiguration = Atomic(value: limitsConfiguration) @@ -353,7 +353,7 @@ public final class AccountContextImpl: AccountContext { let _ = currentContentSettings.swap(value) }) - let updatedAppConfiguration = getAppConfiguration(postbox: account.postbox) + let updatedAppConfiguration = getAppConfiguration(engine: self.engine) self.currentAppConfiguration = Atomic(value: appConfiguration) self._appConfiguration.set(.single(appConfiguration) |> then(updatedAppConfiguration)) @@ -378,22 +378,24 @@ public final class AccountContextImpl: AccountContext { }).start() } }) + + let queue = Queue() + self.deviceSpecificContactImportContexts = QueueLocalObject(queue: queue, generate: { + return DeviceSpecificContactImportContexts(queue: queue) + }) let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes())) if !temp { let currentCountriesConfiguration = self.currentCountriesConfiguration self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: langCode) - |> deliverOnMainQueue).start(next: { value in - let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value)) + |> deliverOnMainQueue).start(next: { [weak self] value in + let configuration = CountriesConfiguration(countries: value) + let _ = currentCountriesConfiguration.swap(configuration) + self?._countriesConfiguration.set(.single(configuration)) }) } - let queue = Queue() - self.deviceSpecificContactImportContexts = QueueLocalObject(queue: queue, generate: { - return DeviceSpecificContactImportContexts(queue: queue) - }) - if let contactDataManager = sharedContext.contactDataManager { let deviceSpecificContactImportContexts = self.deviceSpecificContactImportContexts self.managedAppSpecificContactsDisposable = (contactDataManager.appSpecificReferences() @@ -558,11 +560,10 @@ public final class AccountContextImpl: AccountContext { return .single(nil) case let .replyThread(data): if data.isForumPost, let peerId = location.peerId { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.peerId, threadId: data.threadId) - return self.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> MessageId? in - if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.data.get(MessageHistoryThreadData.self) { - return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: data.maxOutgoingReadId) + return self.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: data.peerId, threadId: data.threadId)) + |> map { threadData -> MessageId? in + if let threadData { + return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: threadData.maxOutgoingReadId) } else { return nil } @@ -581,26 +582,13 @@ public final class AccountContextImpl: AccountContext { public func chatLocationUnreadCount(for location: ChatLocation, contextHolder: Atomic) -> Signal { switch location { case let .peer(peerId): - let unreadCountsKey: PostboxViewKey = .unreadCounts(items: [.peer(id: peerId, handleThreads: false), .total(nil)]) - return self.account.postbox.combinedView(keys: [unreadCountsKey]) - |> map { views in - var unreadCount: Int32 = 0 - - if let view = views.views[unreadCountsKey] as? UnreadMessageCountsView { - if let count = view.count(for: .peer(id: peerId, handleThreads: false)) { - unreadCount = count - } - } - - return Int(unreadCount) - } + return self.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId, handleThreads: false)) case let .replyThread(data): if data.isForumPost { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: data.peerId, threadId: data.threadId) - return self.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> Int in - if let threadInfo = views.views[viewKey] as? MessageHistoryThreadInfoView, let data = threadInfo.info?.data.get(MessageHistoryThreadData.self) { - return Int(data.incomingUnreadCount) + return self.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: data.peerId, threadId: data.threadId)) + |> map { threadData -> Int in + if let threadData { + return Int(threadData.incomingUnreadCount) } else { return 0 } @@ -921,10 +909,10 @@ private final class ChatLocationReplyContextHolderImpl: ChatLocationContextHolde } } -func getAppConfiguration(postbox: Postbox) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +func getAppConfiguration(engine: TelegramEngine) -> Signal { + return engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { view -> AppConfiguration in - let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let appConfiguration: AppConfiguration = view?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue return appConfiguration } |> distinctUntilChanged diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 3746158718..b1ea96b8da 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -45,6 +45,7 @@ import RecaptchaEnterprise import NavigationBarImpl import ContextUI import ContextControllerImpl +import ProxyServerPreviewScreen #if canImport(AppCenter) import AppCenter @@ -1687,7 +1688,7 @@ private func extractAccountManagerState(records: AccountRecordsView Bool = { allowExpansion in + if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded { + minimizedContainer.collapse() + } else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController { + topContoller.minimizeIfNeeded() + } else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen { + topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil) + } + + for controller in strongSelf.rootController.viewControllers { + if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) { + if allowExpansion { + return true + } else { + strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) + + let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: chatLocation.asChatLocation, mode: .overlay(strongSelf.rootController)) + let presentationArguments = ChatControllerOverlayPresentationData(expandData: (nil, {})) + chatController.presentationArguments = presentationArguments + (strongSelf.rootController.viewControllers.last as? ViewController)?.present(chatController, in: .window(.root), with: presentationArguments) + return false + } + } + } + + strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) + + var processed = false + for media in firstMessage.media { + if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action { + strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController) + processed = true + break + } + } + + if !processed { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation)) + } + + return false } - - for controller in strongSelf.rootController.viewControllers { - if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) { - return true + + if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl { + let didPresentAlert = topController.presentVoiceMessageDiscardAlert(action: { + let _ = proceedAction(false) + }, discardIfVideo: true, performAction: false) + if didPresentAlert { + return false } } - strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) - - var processed = false - for media in firstMessage.media { - if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action { - strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController) - processed = true - break - } - } - - if !processed { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation)) - } + return proceedAction(true) } return false }, expandAction: { expandData in @@ -628,8 +653,6 @@ final class AuthorizedApplicationContext { if case .requestable = required.3.status { requestedPermissions.append((required.3, false)) } - default: - break } i += 1 } @@ -711,8 +734,6 @@ final class AuthorizedApplicationContext { }, { result in permissionsPosition.set(position + 1) }) - default: - break } } } @@ -755,9 +776,9 @@ final class AuthorizedApplicationContext { }) let importableContacts = self.context.sharedContext.contactDataManager?.importable() ?? .single([:]) - let optionalImportableContacts = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.contactsSettings]) + let optionalImportableContacts = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.contactsSettings)) |> mapToSignal { preferences -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> in - let settings: ContactsSettings = preferences.values[PreferencesKeys.contactsSettings]?.get(ContactsSettings.self) ?? .defaultSettings + let settings: ContactsSettings = preferences?.get(ContactsSettings.self) ?? .defaultSettings if settings.synchronizeContacts { return importableContacts } else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerEditGif.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerEditGif.swift index 0a9d87e4b2..8adadf8b13 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerEditGif.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerEditGif.swift @@ -1,22 +1,24 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display import AccountContext import ChatControllerInteraction import LegacyMediaPickerUI +import MediaPickerUI extension ChatControllerImpl { func openGifEditing(file: FileMediaReference, addCaption: Bool) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } + let hasSilentPosting = peer.id != self.context.account.peerId + let hasSchedule = self.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat && self.presentationInterfaceState.sendPaidMessageStars == nil legacyMediaEditor( context: self.context, - peer: peer, + peer: EnginePeer(peer), threadTitle: nil, media: file.abstract, mode: addCaption ? .caption : .default, @@ -27,7 +29,27 @@ extension ChatControllerImpl { getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false, hasTimer: false) }, - sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in + photoToolbarView: { [context = self.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) + }, + hasSilentPosting: hasSilentPosting, + hasSchedule: hasSchedule, + reminder: peer.id == self.context.account.peerId, + presentSchedulePicker: { [weak self] _, done in + guard let self else { + return + } + self.presentScheduleTimePicker(style: .media, completion: { [weak self] result in + guard let self else { + return + } + done(result.time, result.silentPosting) + if self.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { + self.openScheduledMessages() + } + }) + }, + sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, isCaptionAbove in guard let self else { return } @@ -39,8 +61,8 @@ extension ChatControllerImpl { fromGallery: false, signals: signals, originalMediaReference: file.abstract, - silentPosting: false, - scheduleTime: nil, + silentPosting: silentPosting, + scheduleTime: scheduleTime == 0 ? nil : scheduleTime, replyToSubject: nil, parameters: parameters, getAnimatedTransitionSource: nil, @@ -48,7 +70,8 @@ extension ChatControllerImpl { ) }, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) + c.navigationPresentation = .flatModal + self?.push(c) } ) } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerEditSticker.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerEditSticker.swift index 6387cf9113..7b812b9607 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerEditSticker.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerEditSticker.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerFrozenAccount.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerFrozenAccount.swift index 5a0366e74a..1152571b03 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerFrozenAccount.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerFrozenAccount.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 748ae33859..9934503998 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -15,6 +15,7 @@ import DeviceAccess import TextFormat import TelegramBaseController import AccountContext +import BrowserUI import TelegramStringFormatting import OverlayStatusController import DeviceLocationManager @@ -58,6 +59,7 @@ import Markdown import TelegramPermissionsUI import Speak import TranslateUI +import TextProcessingScreen import UniversalMediaPlayer import WallpaperBackgroundNode import ChatListUI @@ -119,7 +121,6 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen import PostSuggestionsSettingsScreen import ChatSendStarsScreen import ChatSendAsContextMenu @@ -381,6 +382,9 @@ extension ChatControllerImpl { if previousState.slowmodeState != contentData.state.slowmodeState || previousState.boostsToUnrestrict != contentData.state.boostsToUnrestrict { animated = true } + if previousState.canStopIncomingStreamingMessage != contentData.state.canStopIncomingStreamingMessage { + animated = true + } var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate if let forceAnimationTransition { @@ -445,6 +449,7 @@ extension ChatControllerImpl { presentationInterfaceState = presentationInterfaceState.updatedRemovePaidMessageFeeData(contentData.state.removePaidMessageFeeData) presentationInterfaceState = presentationInterfaceState.updatedViewForumAsMessages(contentData.state.viewForumAsMessages) presentationInterfaceState = presentationInterfaceState.updatedHasTopics(contentData.state.hasTopics) + presentationInterfaceState = presentationInterfaceState.updatedCanStopIncomingStreamingMessage(contentData.state.canStopIncomingStreamingMessage) presentationInterfaceState = presentationInterfaceState.updatedTitlePanelContext({ context in if contentData.state.pinnedMessageId != nil { @@ -530,6 +535,8 @@ extension ChatControllerImpl { let initialInterfaceState = contentData.initialInterfaceState contentData.initialInterfaceState = nil + let initialTextInputState = self.initialTextInputState + self.initialTextInputState = nil if !self.didInitializePersistentPeerInterfaceData, let initialPersistentPeerData = contentData.initialPersistentPeerData { self.didInitializePersistentPeerInterfaceData = true @@ -541,6 +548,9 @@ extension ChatControllerImpl { if let initialInterfaceState { interfaceState = initialInterfaceState.interfaceState } + if let initialTextInputState { + interfaceState = interfaceState.withUpdatedComposeInputState(initialTextInputState) + } if let channel = contentData.state.renderedPeer?.peer as? TelegramChannel { if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { @@ -760,7 +770,14 @@ extension ChatControllerImpl { }) } - let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let currentAccountPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> map { peer in return SendAsPeer(peer: peer, subscribers: nil, isPremiumRequired: false) } @@ -795,7 +812,7 @@ extension ChatControllerImpl { } } if !hasAnonymousPeer { - allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0) + allPeers?.insert(SendAsPeer(peer: EnginePeer(channel), subscribers: 0, isPremiumRequired: false), at: 0) } } else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, (info.flags.contains(.messagesShouldHaveSignatures) || info.flags.contains(.messagesShouldHaveProfiles)) { allPeers = peers @@ -813,7 +830,7 @@ extension ChatControllerImpl { allPeers?.insert(currentAccountPeer, at: 0) } if !hasAnonymousPeer { - allPeers?.insert(SendAsPeer(peer: channel, subscribers: 0, isPremiumRequired: false), at: 0) + allPeers?.insert(SendAsPeer(peer: EnginePeer(channel), subscribers: 0, isPremiumRequired: false), at: 0) } } else { allPeers = peers.filter { $0.peer.id != peerViewMainPeer(peerView)?.id } @@ -866,6 +883,12 @@ extension ChatControllerImpl { self?.requestLayout(transition: transition) } + var enableSendAnimationV2 = false + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_send_animation_v2"] { + } else { + enableSendAnimationV2 = true + } + self.chatDisplayNode.setupSendActionOnViewUpdate = { [weak self] f, messageCorrelationId in //print("setup layoutActionOnViewTransition") @@ -874,6 +897,11 @@ extension ChatControllerImpl { } self.layoutActionOnViewTransitionAction = f + if !enableSendAnimationV2 { + self.chatDisplayNode.historyNode.pinToTopStableId = nil + } else if !self.chatDisplayNode.historyNode.isStrictlyScrolledToPinToEdgeItem() { + self.chatDisplayNode.historyNode.pinToTopStableId = nil + } self.chatDisplayNode.historyNode.layoutActionOnViewTransition = ({ [weak self] transition in f() if let strongSelf = self, let validLayout = strongSelf.validLayout { @@ -893,8 +921,8 @@ extension ChatControllerImpl { let shouldUseFastMessageSendAnimation = strongSelf.chatDisplayNode.shouldUseFastMessageSendAnimation - strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationLayout(layout: validLayout).navigationFrame.maxY, transition: .animated(duration: duration, curve: curve), listViewTransaction: { updateSizeAndInsets, _, _, _ in - + strongSelf.chatDisplayNode.containerLayoutUpdated(validLayout, navigationBarHeight: strongSelf.navigationLayout(layout: validLayout).navigationFrame.maxY, transition: .animated(duration: duration, curve: curve), listViewTransaction: { [weak strongSelf] + updateSizeAndInsets, _, _, _ in var options = transition.options let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) @@ -924,10 +952,15 @@ extension ChatControllerImpl { insertItems.append(ListViewInsertItem(index: item.index, previousIndex: item.previousIndex, item: item.item, directionHint: item.directionHint == .Down ? .Up : nil)) } - if isScheduledMessages, let insertedIndex = insertedIndex { + if isScheduledMessages, let insertedIndex { scrollToItem = ListViewScrollToItem(index: insertedIndex, position: .visible, animated: true, curve: .Custom(duration: duration, controlPoints.0, controlPoints.1, controlPoints.2, controlPoints.3), directionHint: .Down) } else if transition.historyView.originalView.laterId == nil { scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Custom(duration: duration, controlPoints.0, controlPoints.1, controlPoints.2, controlPoints.3), directionHint: .Up) + if enableSendAnimationV2, Thread.isMainThread, let strongSelf { + if strongSelf.chatDisplayNode.historyNode.isStrictlyScrolledToPinToEdgeItem() { + scrollToItem = nil + } + } } if let maxInsertedItem = maxInsertedItem { @@ -1006,7 +1039,8 @@ extension ChatControllerImpl { } } - let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting ?? false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) + let effectiveSilentPosting = silentPosting ?? strongSelf.presentationInterfaceState.interfaceState.silentPosting + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: effectiveSilentPosting, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) var forwardedMessages: [[EnqueueMessage]] = [] var forwardSourcePeerIds = Set() @@ -1171,7 +1205,7 @@ extension ChatControllerImpl { } return updatedState }) - self.searchResult.set(.single((results, state, .general(scope: .channels, tags: nil, minDate: nil, maxDate: nil, folderId: nil)))) + self.searchResult.set(.single((results, state, .general(scope: .channels, groupId: nil, tags: nil, minDate: nil, maxDate: nil, folderId: nil)))) } } @@ -1662,7 +1696,7 @@ extension ChatControllerImpl { if canSendMessagesHere { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(ChatInterfaceState.ReplyMessageSubject( messageId: message.id, @@ -1727,7 +1761,7 @@ extension ChatControllerImpl { return } let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in var entities: [MessageTextEntity] = [] for attribute in message.attributes { @@ -1746,7 +1780,12 @@ extension ChatControllerImpl { } } - let inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities) + let inputText: NSAttributedString + if let richTextAttribute = message.attributes.first(where: { $0 is RichTextMessageAttribute }) as? RichTextMessageAttribute { + inputText = chatInputTextWithReattachedCustomEmoji(markdownStringFromInstantPage(richTextAttribute.instantPage)) + } else { + inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities) + } var disableUrlPreviews: [String] = [] if webpageUrl == nil { disableUrlPreviews = detectUrls(inputText) @@ -1803,7 +1842,7 @@ extension ChatControllerImpl { return } self.updateChatPresentationInterfaceState(transition: transition, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) - }, deleteSelectedMessages: { [weak self] in + }, deleteSelectedMessages: { [weak self] sourceView in if let strongSelf = self { if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty { strongSelf.messageContextDisposable.set((strongSelf.context.sharedContext.chatAvailableMessageActions(engine: strongSelf.context.engine, accountPeerId: strongSelf.context.account.peerId, messageIds: messageIds, keepUpdated: false) @@ -1817,7 +1856,7 @@ extension ChatControllerImpl { if actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty { strongSelf.presentClearCacheSuggestion() } else { - strongSelf.presentDeleteMessageOptions(messageIds: messageIds, options: actions.options, contextController: nil, completion: { _ in }) + strongSelf.presentDeleteMessageOptions(messageIds: messageIds, options: actions.options, contextController: nil, sourceView: sourceView, completion: { _ in }) } } } @@ -1961,7 +2000,7 @@ extension ChatControllerImpl { } self.beginDeleteMessagesWithUndo(messageIds: Set(messages.map({ $0.id })), type: .forEveryone) }), - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}) + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}) ], actionLayout: .vertical ) @@ -2160,20 +2199,31 @@ extension ChatControllerImpl { } var invertedMediaAttribute: InvertMediaMessageAttribute? - if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { - invertedMediaAttribute = attribute as? InvertMediaMessageAttribute - } - - if let mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove { - if mediaCaptionIsAbove { - invertedMediaAttribute = InvertMediaMessageAttribute() - } else { - invertedMediaAttribute = nil + if webpagePreviewAttribute == nil { + if let attribute = message.attributes.first(where: { $0 is InvertMediaMessageAttribute }) { + invertedMediaAttribute = attribute as? InvertMediaMessageAttribute + } + + if let mediaCaptionIsAbove = editMessage.mediaCaptionIsAbove { + if mediaCaptionIsAbove { + invertedMediaAttribute = InvertMediaMessageAttribute() + } else { + invertedMediaAttribute = nil + } } } let text = trimChatInputText(convertMarkdownToAttributes(expandedInputStateAttributedString(editMessage.inputState.inputText))) - + + var isSpecialChatContents = false + if case .customChatContents = strongSelf.presentationInterfaceState.subject { + isSpecialChatContents = true + } + var richTextAttribute: RichTextMessageAttribute? + if !isSpecialChatContents { + richTextAttribute = richMarkdownAttributeIfNeeded(context: strongSelf.context, attributedText: expandedInputStateAttributedString(editMessage.inputState.inputText)) + } + let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) var entitiesAttribute: TextEntitiesMessageAttribute? if !entities.isEmpty { @@ -2220,7 +2270,7 @@ extension ChatControllerImpl { if text.length == 0 { if strongSelf.presentationInterfaceState.editMessageState?.mediaReference != nil { - } else if message.media.contains(where: { media in + } else if message.effectiveMedia.contains(where: { media in switch media { case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaMap: return true @@ -2255,9 +2305,17 @@ extension ChatControllerImpl { if let currentMessage = currentMessage { let currentEntities = currentMessage.textEntitiesAttribute?.entities ?? [] let currentWebpagePreviewAttribute = currentMessage.webpagePreviewAttribute ?? WebpagePreviewMessageAttribute(leadingPreview: false, forceLargeMedia: nil, isManuallyAdded: true, isSafe: false) - - if currentMessage.text != text.string || currentEntities != entities || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { - strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertedMediaAttribute, disableUrlPreview: disableUrlPreview) + let currentRichText = currentMessage.attributes.first(where: { $0 is RichTextMessageAttribute }) as? RichTextMessageAttribute + + if let richTextAttribute = richTextAttribute { + // Rich edit: empty text, no entities, carry the rich attribute. + if currentRichText != richTextAttribute || !currentMessage.text.isEmpty || updatingMedia { + strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: "", media: media, entities: nil, richText: richTextAttribute, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertedMediaAttribute, disableUrlPreview: disableUrlPreview) + } + } else { + if currentMessage.text != text.string || currentEntities != entities || currentRichText != nil || updatingMedia || webpagePreviewAttribute != currentWebpagePreviewAttribute || disableUrlPreview { + strongSelf.context.account.pendingUpdateMessageManager.add(messageId: editMessage.messageId, text: text.string, media: media, entities: entitiesAttribute, richText: nil, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertedMediaAttribute, disableUrlPreview: disableUrlPreview) + } } } @@ -3016,11 +3074,42 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.dismissInput() if let peer = peer as? TelegramSecretChat { - let controller = ChatSecretAutoremoveTimerActionSheetController(context: strongSelf.context, currentValue: peer.messageAutoremoveTimeout == nil ? 0 : peer.messageAutoremoveTimeout!, applyValue: { value in - if let strongSelf = self { + let timeoutValues: [Int32] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 30, + 1 * 60, + 1 * 60 * 60, + 24 * 60 * 60, + 7 * 24 * 60 * 60 + ] + let currentValue = peer.messageAutoremoveTimeout ?? 0 + let controller = ChatTimerScreen( + context: strongSelf.context, + configuration: ChatTimerScreen.Configuration( + style: .default, + picker: .fixedValues( + values: timeoutValues, + selectionStrategy: .firstGreaterOrEqual, + formatter: { strings, value in + if value == 0 { + return strings.Profile_MessageLifetimeForever + } else { + return timeIntervalString(strings: strings, value: value) + } + } + ), + currentValue: currentValue > 0 ? currentValue : 7, + pickerValueMapping: .rawTimestamp, + primaryActionTitle: { strings, _, _ in + strings.Common_Done + } + ), + completion: { value in + guard let strongSelf = self, let value else { + return + } let _ = strongSelf.context.engine.peers.setChatMessageAutoremoveTimeoutInteractively(peerId: peer.id, timeout: value == 0 ? nil : value).startStandalone() } - }) + ) strongSelf.present(controller, in: .window(.root)) } } else { @@ -3454,7 +3543,7 @@ extension ChatControllerImpl { strongSelf.controllerInteraction?.callPeer(peerId, isVideo) } }, toggleMessageStickerStarred: { [weak self] messageId in - if let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { var stickerFile: TelegramMediaFile? for media in message.media { if let file = media as? TelegramMediaFile, file.isSticker { @@ -3616,7 +3705,7 @@ extension ChatControllerImpl { self.selectPollOptionFeedback?.success() }), forKey: id) }, requestStopPollInMessage: { [weak self] id in - guard let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else { + guard let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id)?._asMessage() else { return } @@ -3721,7 +3810,7 @@ extension ChatControllerImpl { } } - let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: text?.string ?? "", link: link, apply: { [weak self] link in + let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, text: strongSelf.presentationData.strings.TextFormat_AddLinkText(text?.string ?? "").string, link: link, apply: { [weak self] link, _ in if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange { if let link { if !link.isEmpty { @@ -3850,12 +3939,6 @@ extension ChatControllerImpl { if let strongSelf = self { strongSelf.openScheduledMessages() } - }, openPeersNearby: { [weak self] in - if let strongSelf = self { - let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context) - controller.navigationPresentation = .master - strongSelf.effectiveNavigationController?.pushViewController(controller, animated: true, completion: { }) - } }, displaySearchResultsTooltip: { [weak self] node, nodeRect in if let strongSelf = self { strongSelf.searchResultsTooltipController?.dismiss() @@ -3992,7 +4075,7 @@ extension ChatControllerImpl { if !(peer is TelegramGroup || peer is TelegramChannel) { return } - presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable) + presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: EnginePeer(peer), selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable) }, presentGigagroupHelp: { [weak self] in if let strongSelf = self { strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current) @@ -4348,7 +4431,7 @@ extension ChatControllerImpl { if let controller = self.context.sharedContext.makePeerInfoController( context: self.context, updatedPresentationData: nil, - peer: peer, + peer: EnginePeer(peer), mode: .gifts, avatarInitiallyExpanded: false, fromChat: false, @@ -4542,27 +4625,56 @@ extension ChatControllerImpl { return } let (_, language) = canTranslateText(context: context, text: text.string, showTranslate: true, ignoredLanguages: nil) - + let entities = generateChatInputTextEntities(text) - presentTranslateScreen( - context: self.context, - text: text.string, - entities: entities, - canCopy: true, - fromLanguage: language, - replaceText: { text, entities in - replace(chatInputStateStringWithAppliedEntities(text, entities: entities)) - }, - pushController: { [weak self] c in - self?.push(c) - }, - presentController: { [weak self] c in - self?.present(c, in: .window(.root)) - }, - display: { [weak self] c in - self?.push(c) + + let translationConfiguration = TranslationConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) + var useSystemTranslation = false + switch translationConfiguration.manual { + case .system: + if #available(iOS 18.0, *) { + useSystemTranslation = true } - ) + default: + break + } + + if useSystemTranslation { + presentTranslateScreen( + context: self.context, + text: text.string, + entities: entities, + canCopy: true, + fromLanguage: language, + replaceText: { text, entities in + replace(chatInputStateStringWithAppliedEntities(text, entities: entities)) + }, + pushController: { [weak self] c in + self?.push(c) + }, + presentController: { [weak self] c in + self?.present(c, in: .window(.root)) + }, + display: { [weak self] c in + self?.push(c) + } + ) + } else { + Task { @MainActor [weak self] in + guard let self else { + return + } + self.push(await TextProcessingScreen( + context: self.context, + mode: .translate(fromLanguage: language, applyResult: { text in + replace(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities)) + }), + inputText: TextWithEntities(text: text.string, entities: entities), + copyResult: nil, + translateChat: nil + )) + } + } }, sendEmoji: { [weak self] text, attribute, immediately in guard let self else { return @@ -4956,7 +5068,7 @@ extension ChatControllerImpl { } let engine = self.context.engine - let previousPeerCache = Atomic<[PeerId: Peer]>(value: [:]) + let previousPeerCache = Atomic<[PeerId: EnginePeer]>(value: [:]) let activitySpace: PeerActivitySpace? switch self.chatLocation { @@ -4971,9 +5083,9 @@ extension ChatControllerImpl { if let activitySpace = activitySpace, let peerId = peerId { self.peerInputActivitiesDisposable?.dispose() self.peerInputActivitiesDisposable = (self.context.account.peerInputActivities(peerId: activitySpace) - |> mapToSignal { activities -> Signal<[(Peer, PeerInputActivity)], NoError> in + |> mapToSignal { activities -> Signal<[(EnginePeer, PeerInputActivity)], NoError> in var foundAllPeers = true - var cachedResult: [(Peer, PeerInputActivity)] = [] + var cachedResult: [(EnginePeer, PeerInputActivity)] = [] previousPeerCache.with { dict -> Void in for (peerId, activity) in activities { if let peer = dict[peerId] { @@ -4990,13 +5102,13 @@ extension ChatControllerImpl { return engine.data.get(EngineDataMap( activities.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0.0) } )) - |> map { peerMap -> [(Peer, PeerInputActivity)] in - var result: [(Peer, PeerInputActivity)] = [] - var peerCache: [PeerId: Peer] = [:] + |> map { peerMap -> [(EnginePeer, PeerInputActivity)] in + var result: [(EnginePeer, PeerInputActivity)] = [] + var peerCache: [PeerId: EnginePeer] = [:] for (peerId, activity) in activities { if let maybePeer = peerMap[peerId], let peer = maybePeer { - result.append((peer._asPeer(), activity)) - peerCache[peerId] = peer._asPeer() + result.append((peer, activity)) + peerCache[peerId] = peer } } let _ = previousPeerCache.swap(peerCache) @@ -5019,7 +5131,7 @@ extension ChatControllerImpl { peerId: peerId, items: displayActivities.map { item -> ChatTitleComponent.Activities.Item in return ChatTitleComponent.Activities.Item( - peer: EnginePeer(item.0), + peer: item.0, activity: item.1 ) } @@ -5112,56 +5224,68 @@ extension ChatControllerImpl { } } })) + + let isScheduledMessages: Bool + if case .scheduledMessages = self.presentationInterfaceState.subject { + isScheduledMessages = true + } else { + isScheduledMessages = false + } - self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) + self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId, isScheduled: isScheduledMessages) |> deliverOnMainQueue).startStrict(next: { [weak self] reason in - if let strongSelf = self, strongSelf.currentFailedMessagesAlertController == nil { - let text: String - var title: String? - let moreInfo: Bool - switch reason { - case .flood: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood - moreInfo = true - case .sendingTooFast: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooFast - title = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooFastTitle - moreInfo = false - case .publicBan: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted - moreInfo = true - case .mediaRestricted: - text = strongSelf.restrictedSendingContentsText() - moreInfo = false - case .slowmodeActive: - text = strongSelf.presentationData.strings.Chat_SlowmodeSendError - moreInfo = false - case .tooMuchScheduled: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled - moreInfo = false - case .voiceMessagesForbidden: - strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .alert) - return - case .nonPremiumMessagesForbidden: - if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorNonPremiumForbidden(EnginePeer(peer).compactDisplayTitle).string - moreInfo = false - } else { - return - } - } - let actions: [TextAlertAction] - if moreInfo { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { - self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) - }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] - } else { - actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] - } - let controller = textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: title, text: text, actions: actions) - strongSelf.currentFailedMessagesAlertController = controller - strongSelf.present(controller, in: .window(.root)) + guard let strongSelf = self else { + return } + guard strongSelf.currentFailedMessagesAlertController == nil else { + return + } + + let text: String + var title: String? + let moreInfo: Bool + switch reason { + case .flood: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + moreInfo = true + case .sendingTooFast: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooFast + title = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooFastTitle + moreInfo = false + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + moreInfo = true + case .mediaRestricted: + text = strongSelf.restrictedSendingContentsText() + moreInfo = false + case .slowmodeActive: + text = strongSelf.presentationData.strings.Chat_SlowmodeSendError + moreInfo = false + case .tooMuchScheduled: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorTooMuchScheduled + moreInfo = false + case .voiceMessagesForbidden: + strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .alert) + return + case .nonPremiumMessagesForbidden: + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer { + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorNonPremiumForbidden(EnginePeer(peer).compactDisplayTitle).string + moreInfo = false + } else { + return + } + } + let actions: [TextAlertAction] + if moreInfo { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Generic_ErrorMoreInfo, action: { + self?.openPeerMention("spambot", navigation: .chat(textInputState: nil, subject: nil, peekData: nil)) + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } else { + actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})] + } + let controller = textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: title, text: text, actions: actions) + strongSelf.currentFailedMessagesAlertController = controller + strongSelf.present(controller, in: .window(.root)) })) self.sentPeerMediaMessageEventsDisposable.dispose() @@ -5242,7 +5366,7 @@ extension ChatControllerImpl { } } - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(mappedId) { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(mappedId)?._asMessage() { if toSubject.setupReply { Queue.mainQueue().after(0.1) { strongSelf.interfaceInteraction?.setupReplyMessage(mappedId, toSubject.subject, { _, f in f() }) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index 662ce26d34..2e4d308a10 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -120,7 +119,6 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen extension ChatControllerImpl { func requestAudioRecorder(beginWithTone: Bool, existingDraft: ChatInterfaceMediaDraftState.Audio? = nil) { @@ -131,7 +129,7 @@ extension ChatControllerImpl { } var resumeData: AudioRecorderResumeData? - if let existingDraft, let path = self.context.account.postbox.mediaBox.completedResourcePath(existingDraft.resource), let compressedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let recorderResumeData = existingDraft.resumeData { + if let existingDraft, let path = self.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(existingDraft.resource.id)), let compressedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let recorderResumeData = existingDraft.resumeData { resumeData = AudioRecorderResumeData(compressedData: compressedData, resumeData: recorderResumeData) } @@ -182,7 +180,7 @@ extension ChatControllerImpl { viewOnceAvailable: viewOnceAvailable, inputPanelFrame: (currentInputPanelFrame, self.chatDisplayNode.inputNode != nil), chatNode: self.chatDisplayNode.historyNode, - completion: { [weak self] message, silentPosting, scheduleTime in + completion: { [weak self] message, silentPosting, scheduleTime, repeatPeriod in guard let self, let videoController = self.videoRecorderValue else { return } @@ -230,14 +228,8 @@ extension ChatControllerImpl { }, usedCorrelationId ? correlationId : nil) let messages = [message] - let transformedMessages: [EnqueueMessage] - if let silentPosting { - transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting) - } else if let scheduleTime { - transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime) - } else { - transformedMessages = self.transformEnqueueMessages(messages) - } + let effectiveSilentPosting = silentPosting ?? self.presentationInterfaceState.interfaceState.silentPosting + let transformedMessages = self.transformEnqueueMessages(messages, silentPosting: effectiveSilentPosting, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod) self.sendMessages(transformedMessages) } @@ -311,7 +303,7 @@ extension ChatControllerImpl { } else if let waveform = data.waveform { if resource == nil { resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max), size: Int64(data.compressedData.count)) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource!.id, data: data.compressedData) + strongSelf.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource!.id), data: data.compressedData) } let audioWaveform: AudioWaveform @@ -361,7 +353,7 @@ extension ChatControllerImpl { let randomId = Int64.random(in: Int64.min ... Int64.max) let resource = LocalFileMediaResource(fileId: randomId) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data.compressedData) + strongSelf.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data.compressedData) let waveformBuffer: Data? = data.waveform @@ -395,12 +387,12 @@ extension ChatControllerImpl { } }, usedCorrelationId ? correlationId : nil) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if viewOnce { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) } - strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) + strongSelf.sendMessages([.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: randomId), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(data.compressedData.count), attributes: [.Audio(isVoice: true, duration: Int(data.duration), title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: [])]) strongSelf.recorderFeedback?.tap() strongSelf.recorderFeedback = nil @@ -477,6 +469,9 @@ extension ChatControllerImpl { if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { self.dismissMediaRecorder(pause ? .pause : .preview) } else { + if case .video = self.presentationInterfaceState.interfaceState.mediaDraftState { + return + } self.videoRecorder.set(.single(nil)) } } @@ -495,6 +490,10 @@ extension ChatControllerImpl { }.updatedInterfaceState { $0.withUpdatedMediaDraftState(nil) } }) } else { + if case .video = self.presentationInterfaceState.interfaceState.mediaDraftState { + return + } + let proceed = { self.withAudioRecorder(resuming: true, { audioRecorder in audioRecorder.resume() @@ -606,7 +605,11 @@ extension ChatControllerImpl { } } - let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0, y: layout.size.height - insets.bottom - 128.0), size: CGSize()) + var sideOffset: CGFloat = 18.0 + if let inputHeight = layout.inputHeight, inputHeight > 0.0 { + sideOffset = 0.0 + } + let location = CGRect(origin: CGPoint(x: screenWidth - layout.safeInsets.right - 50.0 - sideOffset, y: layout.size.height - insets.bottom - 128.0), size: CGSize()) let tooltipController = TooltipScreen( account: self.context.account, @@ -721,7 +724,7 @@ extension ChatControllerImpl { } }, nil) - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if viewOnce { attributes.append(AutoremoveTimeoutMessageAttribute(timeout: viewOnceTimeout, countdownBeginTime: nil)) } @@ -736,7 +739,7 @@ extension ChatControllerImpl { let randomId = Int64.random(in: Int64.min ... Int64.max) let tempPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).ogg" resource = LocalFileAudioMediaResource(randomId: randomId, path: tempPath, trimRange: trimRange) - self.context.account.postbox.mediaBox.moveResourceData(audio.resource.id, toTempPath: tempPath) + self.context.engine.resources.moveResourceData(id: EngineMediaResource.Id(audio.resource.id), toTempPath: tempPath) waveform = waveform.subwaveform(from: trimRange.lowerBound / Double(audio.duration), to: trimRange.upperBound / Double(audio.duration)) finalDuration = Int(trimRange.upperBound - trimRange.lowerBound) } else { @@ -745,16 +748,10 @@ extension ChatControllerImpl { let waveformBuffer = waveform.makeBitstream() - let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: finalDuration, title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] + let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: Int64(audio.fileSize), attributes: [.Audio(isVoice: true, duration: finalDuration, title: nil, performer: nil, waveform: waveformBuffer)], alternativeRepresentations: [])), threadId: self.chatLocation.threadId, replyToMessageId: self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])] - let transformedMessages: [EnqueueMessage] - if let silentPosting = silentPosting { - transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone) - } else if let scheduleTime = scheduleTime { - transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) - } else { - transformedMessages = self.transformEnqueueMessages(messages) - } + let effectiveSilentPosting = silentPosting ?? self.presentationInterfaceState.interfaceState.silentPosting + let transformedMessages = self.transformEnqueueMessages(messages, silentPosting: effectiveSilentPosting, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) guard let peerId = self.chatLocation.peerId else { return @@ -769,7 +766,10 @@ extension ChatControllerImpl { donateSendMessageIntent(account: self.context.account, sharedContext: self.context.sharedContext, intentContext: .chat, peerIds: [peerId]) case .video: - self.videoRecorderValue?.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, messageEffect: messageEffect) + guard let videoRecorderValue = self.videoRecorderValue else { + return + } + videoRecorderValue.sendVideoRecording(silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, messageEffect: messageEffect) } } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift index 815a39f0e5..969ff0a103 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -14,8 +13,8 @@ import UndoUI extension ChatControllerImpl { func navigateToMessage( - fromId: MessageId, - id: MessageId, + fromId: EngineMessage.Id, + id: EngineMessage.Id, params: NavigateToMessageParams ) { var id = id @@ -67,7 +66,7 @@ extension ChatControllerImpl { } func navigateToMessage( - from fromId: MessageId?, + from fromId: EngineMessage.Id?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, @@ -84,9 +83,9 @@ extension ChatControllerImpl { completion?() return } - var fromIndex: MessageIndex? + var fromIndex: EngineMessage.Index? - var fromMessage: Message? + var fromMessage: EngineMessage? if let fromId = fromId, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(fromId) { fromIndex = message.index fromMessage = message @@ -114,8 +113,8 @@ extension ChatControllerImpl { if isPinnedMessages || forceNew, let messageId = messageLocation.messageId { let peerSignal: Signal - if forceNew, let fromMessage, let peer = fromMessage.peers[fromMessage.id.peerId] { - peerSignal = .single(EnginePeer(peer)) + if forceNew, let fromMessage, let peer = fromMessage.enginePeers[fromMessage.id.peerId] { + peerSignal = .single(peer) } else { peerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) } @@ -125,11 +124,11 @@ extension ChatControllerImpl { |> `catch` { _ in return .single(.result([])) } - |> mapToSignal { result -> Signal<[Message], NoError> in + |> mapToSignal { result -> Signal<[EngineMessage], NoError> in guard case let .result(result) = result else { return .complete() } - return .single(result) + return .single(result.map(EngineMessage.init)) } ) |> deliverOnMainQueue).startStandalone(next: { [weak self] peer, messages in @@ -143,7 +142,7 @@ extension ChatControllerImpl { self.dismiss() let navigateToLocation: NavigateToChatControllerParams.Location - if let message = messages.first, let threadId = message.threadId, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum { + if let message = messages.first, let threadId = message.threadId, case let .channel(channel) = message.enginePeers[message.id.peerId], channel.isForumOrMonoForum { navigateToLocation = .replyThread(ChatReplyThreadMessage(peerId: peer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: false,maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)) } else { navigateToLocation = .peer(peer) @@ -198,7 +197,7 @@ extension ChatControllerImpl { case let .index(index): searchLocation = .index(index) case .upperBound: - searchLocation = .index(MessageIndex.upperBound(peerId: chatLocation.peerId)) + searchLocation = .index(EngineMessage.Index.upperBound(peerId: chatLocation.peerId)) } var historyView: Signal @@ -206,9 +205,9 @@ extension ChatControllerImpl { historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation), count: 50, highlight: true, setupReply: false), id: 0), context: self.context, chatLocation: preloadChatLocation, subject: subject, chatLocationContextHolder: Atomic(value: nil), fixedCombinedReadStates: nil, tag: nil, additionalData: []) - var signal: Signal<(MessageIndex?, Bool), NoError> + var signal: Signal<(EngineMessage.Index?, Bool), NoError> signal = historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + |> mapToSignal { historyView -> Signal<(EngineMessage.Index?, Bool), NoError> in switch historyView { case .Loading: return .single((nil, true)) @@ -320,7 +319,7 @@ extension ChatControllerImpl { self.contentData?.historyNavigationStack.add(fromIndex) } - let scrollFromIndex: MessageIndex? + let scrollFromIndex: EngineMessage.Index? if let fromIndex = fromIndex { scrollFromIndex = fromIndex } else if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage() { @@ -361,7 +360,7 @@ extension ChatControllerImpl { } if case let .id(_, params) = messageLocation, let timecode = params.timestamp { - let _ = self.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(timecode))) + let _ = self.controllerInteraction?.openMessage(message._asMessage(), OpenMessageParams(mode: .timecode(timecode))) } } else if case let .index(index) = messageLocation, index.id.id == 0, index.timestamp > 0, case .scheduledMessages = self.presentationInterfaceState.subject { self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: index, animated: animated, scrollPosition: scrollPosition) @@ -393,7 +392,7 @@ extension ChatControllerImpl { searchLocation = .index(index) case .upperBound: if let peerId = self.chatLocation.peerId { - searchLocation = .index(MessageIndex.upperBound(peerId: peerId)) + searchLocation = .index(EngineMessage.Index.upperBound(peerId: peerId)) } else { searchLocation = .index(.absoluteUpperBound()) } @@ -401,9 +400,9 @@ extension ChatControllerImpl { var historyView: Signal historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation), count: 50, highlight: true, setupReply: setupReply), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tag: nil, additionalData: []) - var signal: Signal<(MessageIndex?, Bool), NoError> + var signal: Signal<(EngineMessage.Index?, Bool), NoError> signal = historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + |> mapToSignal { historyView -> Signal<(EngineMessage.Index?, Bool), NoError> in switch historyView { case .Loading: return .single((nil, true)) @@ -521,9 +520,9 @@ extension ChatControllerImpl { } let historyView = preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: .InitialSearch(subject: MessageHistoryInitialSearchSubject(location: searchLocation, quote: quote.flatMap { quote in MessageHistoryInitialSearchSubject.Quote(string: quote.string, offset: quote.offset) }, subject: subject), count: 50, highlight: true, setupReply: setupReply), id: 0), context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tag: nil, additionalData: []) - var signal: Signal + var signal: Signal signal = historyView - |> mapToSignal { historyView -> Signal in + |> mapToSignal { historyView -> Signal in switch historyView { case .Loading: return .complete() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift index c2d0e1bb68..90e5a23d7e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigationButtonAction.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -119,7 +118,6 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen extension ChatControllerImpl { func navigationButtonAction(_ action: ChatNavigationButtonAction) { @@ -438,7 +436,7 @@ extension ChatControllerImpl { if peer.restrictionText(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) == nil && !self.presentationInterfaceState.isNotAccessible { if peer.id == self.context.account.peerId { - if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { + if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer, let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), mode: .generic, avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { self.effectiveNavigationController?.pushViewController(infoController) } } else { @@ -458,17 +456,17 @@ extension ChatControllerImpl { default: mode = .generic } - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), mode: mode, avatarInitiallyExpanded: expandAvatar, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { self.effectiveNavigationController?.pushViewController(infoController) } } - + let _ = self.dismissPreviewing?(false) } })) case .replyThread: if let peer = self.presentationInterfaceState.renderedPeer?.peer, case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId { - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .forumTopic(thread: replyThreadMessage), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), mode: .forumTopic(thread: replyThreadMessage), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { self.effectiveNavigationController?.pushViewController(infoController) } } else if let monoforumPeer = self.presentationInterfaceState.renderedPeer?.peer, case let .replyThread(replyThreadMessage) = self.chatLocation, monoforumPeer.isMonoForum { @@ -476,20 +474,20 @@ extension ChatControllerImpl { if #available(iOS 13.0, *) { Task { @MainActor [weak self] in guard let peer = await context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(replyThreadMessage.threadId)) + TelegramEngine.EngineData.Item.Peer.Peer(id: EnginePeer.Id(replyThreadMessage.threadId)) ).get() else { return } guard let self else { return } - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer._asPeer(), mode: .monoforum(monoforumPeer.id), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: peer, mode: .monoforum(monoforumPeer.id), avatarInitiallyExpanded: false, fromChat: true, requestsContext: nil) { self.effectiveNavigationController?.pushViewController(infoController) } } } } else if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, case let .replyThread(message) = self.chatLocation { - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: channel, mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(channel), mode: .forumTopic(thread: message), avatarInitiallyExpanded: false, fromChat: true, requestsContext: self.contentData?.inviteRequestsContext) { self.effectiveNavigationController?.pushViewController(infoController) } } @@ -623,7 +621,7 @@ extension ChatControllerImpl { } else { items.append(ActionSheetButtonItem(title: presentationData.strings.Cache_Clear("\(dataSizeString(totalSize, formatting: DataSizeStringFormatting(presentationData: presentationData)))").string, action: { let clearCategories = sizeIndex.keys.filter({ sizeIndex[$0]!.0 }) - var clearMediaIds = Set() + var clearMediaIds = Set() var media = stats.media if var categories = media[peerId] { @@ -639,11 +637,11 @@ extension ChatControllerImpl { media[peerId] = categories } - var clearResourceIds = Set() + var clearResourceIds = Set() for id in clearMediaIds { if let ids = stats.mediaResourceIds[id] { for resourceId in ids { - clearResourceIds.insert(resourceId) + clearResourceIds.insert(EngineMediaResource.Id(resourceId)) } } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift index 0b4e34a494..ecf806cfc1 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenBankCardContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -31,15 +30,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture - - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil + + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift index bddb29baca..4a4a8e8081 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenCommandContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -31,15 +30,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift index 6eb88e48b4..1860fcceea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDateContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -35,15 +34,10 @@ extension ChatControllerImpl: EKEventEditViewDelegate { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture - - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil + + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] items.append( @@ -102,7 +96,7 @@ extension ChatControllerImpl: EKEventEditViewDelegate { guard let self else { return } - let attributes: [MessageAttribute] = [ + let attributes: [EngineMessage.Attribute] = [ OutgoingScheduleInfoMessageAttribute(scheduleTime: result.time, repeatPeriod: result.repeatPeriod) ] let forwardMessage: EnqueueMessage = .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDocumentScanner.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDocumentScanner.swift index d90e71ce45..eade7a3d98 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDocumentScanner.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenDocumentScanner.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -49,12 +48,12 @@ extension ChatControllerImpl: VNDocumentCameraViewControllerDelegate { arc4random_buf(&randomId, 8) let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) var previewResource: LocalFileMediaResource? if let image = generatePdfPreviewImage(data: data, size: CGSize(width: 256, height: 256.0)), let jpegData = image.jpegData(compressionQuality: 0.5) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: jpegData, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: jpegData, synchronous: true) previewResource = resource } @@ -65,7 +64,7 @@ extension ChatControllerImpl: VNDocumentCameraViewControllerDelegate { let image = scan.imageOfPage(at: i) if let data = image.jpegData(compressionQuality: 0.87) { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + self.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data, synchronous: true) var fileTitle = title if scan.pageCount > 1 { @@ -93,7 +92,7 @@ extension ChatControllerImpl: VNDocumentCameraViewControllerDelegate { var attributes: [TelegramMediaFileAttribute] = [] attributes.append(.FileName(fileName: item.fileName)) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: item.resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: item.mimeType, size: item.size, attributes: attributes, alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: item.resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: item.mimeType, size: item.size, attributes: attributes, alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: self.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []) messages.append(message) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift index 4cdca9f8bb..caee4eac16 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenHashtagContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -31,15 +30,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index cc9acdf90f..63ae79c6ea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -1,36 +1,223 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display import ContextUI import UndoUI import AccountContext +import BrowserUI import ChatMessageItemView import ChatMessageItemCommon import MessageUI import ChatControllerInteraction +import TelegramUIPreferences +import UrlEscaping import UrlWhitelist import OpenInExternalAppUI import SafariServices +private struct ChatLinkOpenMode { + let shouldOpenInApp: Bool +} + +private enum ChatLinkReverseOpenTarget { + case inApp + case externalBrowser + + var checkboxTitle: String { + switch self { + case .inApp: + return "Always open this site in-app" + case .externalBrowser: + return "Always open this site in browser" + } + } + + var openExternalBrowser: Bool { + switch self { + case .inApp: + return false + case .externalBrowser: + return true + } + } +} + +private func chatLinkContextMenuCanonicalUrl(from url: String) -> URL? { + var urlWithScheme = url + if !url.contains("://") && !url.hasPrefix("mailto:") { + urlWithScheme = "http://" + url + } + if let parsed = URL(string: urlWithScheme) { + return parsed + } else if let encoded = (urlWithScheme as NSString).addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) { + return URL(string: encoded) + } + return nil +} + +private func chatLinkContextMenuOpenMode(context: AccountContext, url: String) -> Signal { + let lowercasedUrl = url.lowercased() + if lowercasedUrl.hasPrefix("mailto:") || lowercasedUrl.hasPrefix("tel:") || lowercasedUrl.hasPrefix("calshow:") { + return .single(nil) + } + + guard let parsedUrl = chatLinkContextMenuCanonicalUrl(from: url) else { + return .single(nil) + } + + let scheme = (parsedUrl.scheme ?? "").lowercased() + guard scheme == "http" || scheme == "https" || scheme == "tonsite" else { + return .single(nil) + } + + let host = parsedUrl.host?.lowercased() ?? "" + if host.isEmpty { + return .single(nil) + } + if host == "t.me" || host == "telegram.me" || host == "telegram.dog" { + return .single(nil) + } + if host.hasSuffix(".ton") || scheme.hasPrefix("tonsite") { + return .single(nil) + } + + return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.webBrowserSettings)) + |> take(1) + |> map { accountSettingsEntry -> ChatLinkOpenMode? in + let accountSettings = accountSettingsEntry?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + let normalizedHost = ".\(host)" + let exceptions = accountSettings.openExternalBrowser ? accountSettings.inAppExceptions : accountSettings.externalExceptions + var isExceptedDomain = false + for exception in exceptions { + if normalizedHost.hasSuffix(".\(exception.domain.lowercased())") { + isExceptedDomain = true + break + } + } + + let shouldOpenInApp: Bool + if accountSettings.openExternalBrowser { + shouldOpenInApp = isExceptedDomain + } else { + shouldOpenInApp = !isExceptedDomain + } + return ChatLinkOpenMode(shouldOpenInApp: shouldOpenInApp) + } +} + extension ChatControllerImpl { + private func presentOpenLinkConfirmation(_ url: String, target: ChatLinkReverseOpenTarget) { + var exceptionAdded = false + let disposable = self.context.sharedContext.openUserGeneratedUrl( + context: self.context, + peerId: self.contentData?.state.peerView?.peerId, + url: url, + webpage: nil, + concealed: false, + forceConcealed: true, + skipUrlAuth: false, + skipConcealedAlert: false, + forceDark: false, + present: { [weak self] c in + self?.present(c, in: .window(.root)) + }, + openResolved: { [weak self] result in + guard let self else { + return + } + switch target { + case .inApp: + if case let .externalUrl(resolvedUrl) = result, let navigationController = self.effectiveNavigationController { + self.chatDisplayNode.dismissInput() + let controller = BrowserScreen(context: self.context, subject: .webPage(url: resolvedUrl)) + navigationController.pushViewController(controller) + + if exceptionAdded { + Queue.mainQueue().after(0.5) { + let tooltipScreen = UndoOverlayController( + presentationData: self.presentationData, + content: .actionSucceeded(title: "Exception Added", text: "This site will always open in-app.", cancel: "", destructive: false), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in + return false + } + ) + controller.present(tooltipScreen, in: .current) + } + } + } else { + self.openResolved(result: result, sourceMessageId: nil, forceExternal: false, concealed: true) + } + case .externalBrowser: + if case .externalUrl = result { + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]) + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak self] sharedData in + guard let self else { + return + } + self.chatDisplayNode.dismissInput() + + let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings + var defaultWebBrowser = settings.defaultWebBrowser + if defaultWebBrowser == nil || defaultWebBrowser == "inApp" || defaultWebBrowser == "inAppSafari" { + defaultWebBrowser = "safari" + } + + let targetUrl = chatLinkContextMenuCanonicalUrl(from: url)?.absoluteString ?? url + let openInOptions = availableOpenInOptions(context: self.context, item: .url(url: targetUrl)) + if let option = openInOptions.first(where: { $0.identifier == defaultWebBrowser }) { + if case let .openUrl(openInUrl) = option.action() { + self.context.sharedContext.applicationBindings.openUrl(openInUrl) + } else { + self.context.sharedContext.applicationBindings.openUrl(targetUrl) + } + } else { + self.context.sharedContext.applicationBindings.openUrl(targetUrl) + } + }) + } else { + self.openResolved(result: result, sourceMessageId: nil, forceExternal: true, concealed: false) + } + } + }, + progress: nil, + alertDisplayUpdated: nil, + concealedAlertOption: OpenUserGeneratedUrlConcealedAlertOption(title: target.checkboxTitle, action: { [weak self] in + guard let self else { + return + } + let _ = toggleWebBrowserSettingsException( + postbox: self.context.account.postbox, + network: self.context.account.network, + openExternalBrowser: target.openExternalBrowser, + delete: false, + url: url + ).startStandalone() + + exceptionAdded = true + }) + ) + self.navigationActionDisposable.set(disposable) + } + func openLinkContextMenu(url: String, params: ChatControllerInteraction.LongTapParams) -> Void { guard let message = params.message, let contentNode = params.contentNode else { var (cleanUrl, _) = parseUrl(url: url, wasConcealed: false) var canAddToReadingList = true - var canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url)).count > 1 let mailtoString = "mailto:" let telString = "tel:" var openText = self.presentationData.strings.Conversation_LinkDialogOpen var phoneNumber: String? - + var isPhoneNumber = false var isEmail = false var hasOpenAction = true - + if cleanUrl.hasPrefix(mailtoString) { canAddToReadingList = false cleanUrl = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: mailtoString.distance(from: mailtoString.startIndex, to: mailtoString.endIndex))...]) @@ -40,81 +227,94 @@ extension ChatControllerImpl { phoneNumber = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: telString.distance(from: telString.startIndex, to: telString.endIndex))...]) cleanUrl = phoneNumber! openText = self.presentationData.strings.UserInfo_PhoneCall - canOpenIn = false isPhoneNumber = true - + if cleanUrl.hasPrefix("+888") { hasOpenAction = false } - } else if canOpenIn { - openText = self.presentationData.strings.Conversation_FileOpenIn } - - let actionSheet = ActionSheetController(presentationData: self.presentationData) - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: cleanUrl)) - if hasOpenAction { - items.append(ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - if canOpenIn { - strongSelf.openUrlIn(url) - } else { - strongSelf.openUrl(url, concealed: false) - } - } - })) - } - if let phoneNumber = phoneNumber { - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddContact, color: .accent, action: { [weak self, weak actionSheet] in - actionSheet?.dismissAnimated() - if let strongSelf = self { - strongSelf.controllerInteraction?.addContact(phoneNumber) - } - })) - } - items.append(ActionSheetButtonItem(title: canAddToReadingList ? self.presentationData.strings.ShareMenu_CopyShareLink : self.presentationData.strings.Conversation_ContextMenuCopy, color: .accent, action: { [weak actionSheet, weak self] in - actionSheet?.dismissAnimated() + + let _ = (chatLinkContextMenuOpenMode(context: self.context, url: url) + |> deliverOnMainQueue).startStandalone(next: { [weak self] openMode in guard let self else { return } - UIPasteboard.general.string = cleanUrl - - let content: UndoOverlayContent - if isPhoneNumber { - content = .copy(text: self.presentationData.strings.Conversation_PhoneCopied) - } else if isEmail { - content = .copy(text: self.presentationData.strings.Conversation_EmailCopied) - } else if canAddToReadingList { - content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) - } else { - content = .copy(text: self.presentationData.strings.Conversation_TextCopied) - } - self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })) - if canAddToReadingList { - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - if let link = URL(string: url) { - let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + + let actionSheet = ActionSheetController(presentationData: self.presentationData) + var items: [ActionSheetItem] = [] + items.append(ActionSheetTextItem(title: cleanUrl)) + if hasOpenAction { + items.append(ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + self?.openUrl(url, concealed: false) + })) + + if let openMode { + let reverseText = openMode.shouldOpenInApp ? self.presentationData.strings.Chat_ContextMenu_OpenInBrowser : self.presentationData.strings.Chat_ContextMenu_OpenInApp + items.append(ActionSheetButtonItem(title: reverseText, color: .accent, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + guard let self else { + return + } + if openMode.shouldOpenInApp { + self.presentOpenLinkConfirmation(url, target: .externalBrowser) + } else { + self.presentOpenLinkConfirmation(url, target: .inApp) + } + })) } - })) - } - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + } + if let phoneNumber = phoneNumber { + items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddContact, color: .accent, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + if let strongSelf = self { + strongSelf.controllerInteraction?.addContact(phoneNumber) + } + })) + } + items.append(ActionSheetButtonItem(title: canAddToReadingList ? self.presentationData.strings.ShareMenu_CopyShareLink : self.presentationData.strings.Conversation_ContextMenuCopy, color: .accent, action: { [weak actionSheet, weak self] in actionSheet?.dismissAnimated() - }) - ])]) - self.chatDisplayNode.dismissInput() - self.present(actionSheet, in: .window(.root)) - + guard let self else { + return + } + UIPasteboard.general.string = cleanUrl + + let content: UndoOverlayContent + if isPhoneNumber { + content = .copy(text: self.presentationData.strings.Conversation_PhoneCopied) + } else if isEmail { + content = .copy(text: self.presentationData.strings.Conversation_EmailCopied) + } else if canAddToReadingList { + content = .linkCopied(title: nil, text: self.presentationData.strings.Conversation_LinkCopied) + } else { + content = .copy(text: self.presentationData.strings.Conversation_TextCopied) + } + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })) + if canAddToReadingList { + items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + if let link = URL(string: url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + })) + } + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + self.chatDisplayNode.dismissInput() + self.present(actionSheet, in: .window(.root)) + }) + return } - + guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(message.id) else { return } - + var updatedMessages = messages for i in 0 ..< updatedMessages.count { if updatedMessages[i].id == message.id { @@ -123,85 +323,100 @@ extension ChatControllerImpl { break } } - + var (cleanUrl, _) = parseUrl(url: url, wasConcealed: false) var canAddToReadingList = true - let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url)).count > 1 - + var isEmail = false let mailtoString = "mailto:" - var openText = self.presentationData.strings.Conversation_LinkDialogOpen + let openText = self.presentationData.strings.Conversation_LinkDialogOpen var copyText = self.presentationData.strings.Conversation_ContextMenuCopyLink if cleanUrl.hasPrefix(mailtoString) { canAddToReadingList = false cleanUrl = String(cleanUrl[cleanUrl.index(cleanUrl.startIndex, offsetBy: mailtoString.distance(from: mailtoString.startIndex, to: mailtoString.endIndex))...]) copyText = self.presentationData.strings.Conversation_ContextMenuCopyEmail isEmail = true - } else if canOpenIn { - openText = self.presentationData.strings.Conversation_FileOpenIn } - + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture - - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } - - var items: [ContextMenuItem] = [] - - items.append( - .action(ContextMenuActionItem(text: openText, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - guard let self else { - return - } - f(.default) - - if canOpenIn { - self.openUrlIn(url) - } - else { - self.openUrl(url, concealed: false) - } - })) - ) - - items.append( - .action(ContextMenuActionItem(text: copyText, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) + let gesture: ContextGesture? = nil - guard let self else { - return - } - - UIPasteboard.general.string = cleanUrl + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) + + let itemsSignal = chatLinkContextMenuOpenMode(context: self.context, url: url) + |> deliverOnMainQueue + |> map { [weak self] openMode -> ContextController.Items in + guard let self else { + return ContextController.Items(content: .list([])) + } + + var items: [ContextMenuItem] = [] - self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: isEmail ? presentationData.strings.Conversation_EmailCopied : presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) - })) - ) - - if canAddToReadingList { items.append( - .action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_AddToReadingList, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadingList"), color: theme.contextMenu.primaryColor) }, action: { _, f in + .action(ContextMenuActionItem(text: openText, icon: { theme in return generateTintedImage(image: openMode?.shouldOpenInApp == true ? UIImage(bundleImageName: "Chat/Context Menu/Browser") : UIImage(bundleImageName: "Chat/Context Menu/Globe"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) - - if let link = URL(string: url) { - let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + + guard let self else { + return } + self.openUrl(url, concealed: false) })) ) + + if let openMode { + let reverseText = openMode.shouldOpenInApp ? "Open in Browser" : "Open In-App" + items.append( + .action(ContextMenuActionItem(text: reverseText, icon: { theme in return generateTintedImage(image: openMode.shouldOpenInApp ? UIImage(bundleImageName: "Chat/Context Menu/Globe") : UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + if openMode.shouldOpenInApp { + self.presentOpenLinkConfirmation(url, target: .externalBrowser) + } else { + self.presentOpenLinkConfirmation(url, target: .inApp) + } + })) + ) + } + + items.append( + .action(ContextMenuActionItem(text: copyText, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + UIPasteboard.general.string = cleanUrl + + self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: isEmail ? presentationData.strings.Conversation_EmailCopied : presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })) + ) + + if canAddToReadingList { + items.append( + .action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_AddToReadingList, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReadingList"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + + if let link = URL(string: url) { + let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil) + } + })) + ) + } + + return ContextController.Items(content: .list(items)) } - + self.canReadHistory.set(false) - - let controller = makeContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))), recognizer: recognizer, gesture: gesture, disableScreenshots: false) + + let controller = makeContextController(presentationData: self.presentationData, source: source, items: itemsSignal, recognizer: recognizer, gesture: gesture, disableScreenshots: false) controller.dismissed = { [weak self] in self?.canReadHistory.set(true) } - + self.window?.presentInGlobalOverlay(controller) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index 0080cb5043..e6eff30b34 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -18,9 +17,10 @@ import TooltipUI import TopMessageReactions import TelegramNotices import PresentationDataUtils +import ChatPresentationInterfaceState extension ChatControllerImpl { - func openMessageContextMenu(message: Message, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { + func openMessageContextMenu(message: EngineMessage, selectAll: Bool, node: ASDisplayNode, frame: CGRect, anyRecognizer: UIGestureRecognizer?, location: CGPoint?) -> Void { if self.presentationInterfaceState.interfaceState.selectionState != nil { return } @@ -45,13 +45,15 @@ extension ChatControllerImpl { guard let topMessage = messages.first else { return } - + + let canBypassReactionRestrictions = canBypassRestrictions(chatPresentationInterfaceState: self.presentationInterfaceState) + let _ = combineLatest(queue: .mainQueue(), self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)), contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: updatedMessages, controllerInteraction: self.controllerInteraction, selectAll: selectAll, interfaceInteraction: self.interfaceInteraction, messageNode: node as? ChatMessageItemView), - peerMessageAllowedReactions(context: self.context, message: topMessage), - peerMessageSelectedReactions(context: self.context, message: topMessage), - topMessageReactions(context: self.context, message: topMessage, subPeerId: self.chatLocation.threadId.flatMap(EnginePeer.Id.init)), + peerMessageAllowedReactions(context: self.context, message: topMessage, ignoreDefault: canBypassReactionRestrictions), + peerMessageSelectedReactions(context: self.context, message: EngineMessage(topMessage)), + topMessageReactions(context: self.context, message: topMessage, subPeerId: self.chatLocation.threadId.flatMap(EnginePeer.Id.init), ignoreDefault: canBypassReactionRestrictions), ApplicationSpecificNotice.getChatTextSelectionTips(accountManager: self.context.sharedContext.accountManager) ).startStandalone(next: { [weak self] peer, actions, allowedReactionsAndStars, selectedReactions, topReactions, chatTextSelectionTips in guard let self else { @@ -126,7 +128,7 @@ extension ChatControllerImpl { actions.context = self.context actions.animationCache = self.controllerInteraction?.presentationContext.animationCache - if canAddMessageReactions(message: topMessage), let allowedReactions = allowedReactions, !topReactions.isEmpty { + if canAddMessageReactions(message: EngineMessage(topMessage)), let allowedReactions = allowedReactions, !topReactions.isEmpty { actions.reactionItems = topReactions.map { ReactionContextItem.reaction(item: $0, icon: .none) } actions.selectedReactionItems = selectedReactions.reactions if message.areReactionsTags(accountPeerId: self.context.account.peerId) { @@ -166,12 +168,8 @@ extension ChatControllerImpl { allReactionsAreAvailable = false } - let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - if premiumConfiguration.isPremiumDisabled { - allReactionsAreAvailable = false - } - if allReactionsAreAvailable { + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) actions.getEmojiContent = { [weak self] animationCache, animationRenderer in guard let self else { preconditionFailure() @@ -186,7 +184,7 @@ extension ChatControllerImpl { hasTrending: false, topReactionItems: reactionItems, areUnicodeEmojiEnabled: false, - areCustomEmojiEnabled: true, + areCustomEmojiEnabled: !premiumConfiguration.isPremiumDisabled, chatPeerId: self.chatLocation.peerId, selectedItems: selectedReactions.files ) @@ -370,13 +368,24 @@ extension ChatControllerImpl { controller?.view.endEditing(true) if case .stars = chosenUpdatedReaction.reaction { + if !canSendReactionsToChat(self.presentationInterfaceState) { + if let controller { + controller.dismiss(completion: { [weak self] in + self?.displaySendReactionRestrictedToast() + }) + } else { + self.displaySendReactionRestrictedToast() + } + return + } + if isLarge { if let controller { controller.dismiss(completion: { [weak self] in guard let self else { return } - self.openMessageSendStarsScreen(message: message) + self.openMessageSendStarsScreen(message: EngineMessage(message)) }) } return @@ -505,6 +514,17 @@ extension ChatControllerImpl { isFirst = !currentReactions.contains(where: { $0.value == chosenReaction }) } + if removedReaction == nil && !canSendReactionsToChat(self.presentationInterfaceState) { + if let controller { + controller.dismiss(completion: { [weak self] in + self?.displaySendReactionRestrictedToast() + }) + } else { + self.displaySendReactionRestrictedToast() + } + return + } + if message.areReactionsTags(accountPeerId: self.context.account.peerId) { if removedReaction == nil, !topReactions.contains(where: { $0.reaction.rawValue == chosenReaction }) { if !self.presentationInterfaceState.isPremium { @@ -667,15 +687,17 @@ final class ChatControllerContextReferenceContentSource: ContextReferenceContent let sourceView: UIView let insets: UIEdgeInsets let contentInsets: UIEdgeInsets + let actionsOnTop: Bool - init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets()) { + init(controller: ViewController, sourceView: UIView, insets: UIEdgeInsets, contentInsets: UIEdgeInsets = UIEdgeInsets(), actionsOnTop: Bool = false) { self.controller = controller self.sourceView = sourceView self.insets = insets self.contentInsets = contentInsets + self.actionsOnTop = actionsOnTop } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds.inset(by: self.insets), insets: self.contentInsets, actionsPosition: self.actionsOnTop ? .top : .bottom) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift index ca5ac6c4eb..ce3600bcee 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageFactCheck.swift @@ -4,7 +4,7 @@ import FactCheckAlertController extension ChatControllerImpl { func openEditMessageFactCheck(messageId: EngineMessage.Id) { - guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) else { + guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() else { return } var currentText: String = "" diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift index 4a364bda5a..a48582f554 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPeer.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -119,12 +118,11 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen import FaceScanScreen import ForumCreateTopicScreen extension ChatControllerImpl { - func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil, skipAgeVerification: Bool = false) { + func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: EngineMessage.Id? = nil, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil, skipAgeVerification: Bool = false) { let _ = self.presentVoiceMessageDiscardAlert(action: { if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId { switch navigation { @@ -166,7 +164,7 @@ extension ChatControllerImpl { } else { if let peer = peer { do { - var chatPeerId: PeerId? + var chatPeerId: EnginePeer.Id? if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramGroup { chatPeerId = peer.id } else if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel, case .group = peer.info, case .member = peer.participationStatus { @@ -175,17 +173,18 @@ extension ChatControllerImpl { switch navigation { case .info, .default: - let peerSignal: Signal + let peerSignal: Signal if let messageId = fromMessage?.id { peerSignal = loadedPeerFromMessage(account: self.context.account, peerId: peer.id, messageId: messageId) + |> map { peer in peer.flatMap(EnginePeer.init) } } else { - peerSignal = self.context.account.postbox.loadedPeerWithId(peer.id) |> map(Optional.init) + peerSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) } self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self, let peer = peer { var mode: PeerInfoControllerMode = .generic - if let _ = fromMessage, let chatPeerId = chatPeerId { - mode = .group(chatPeerId) + if let messageId = fromMessage?.id, chatPeerId != nil { + mode = .group(sourceMessageId: messageId) } if let fromReactionMessageId = fromReactionMessageId { mode = .reaction(fromReactionMessageId) @@ -347,8 +346,8 @@ extension ChatControllerImpl { guard let self, let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer else { return } - - guard let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + + guard let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: EnginePeer(peer), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { return } (self.navigationController as? NavigationController)?.pushViewController(controller) @@ -409,39 +408,45 @@ extension ChatControllerImpl { } }))) } else { - items.append(.action(ContextMenuActionItem(text: strings.Chat_CreateTopic, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] action in - guard let self else { - return - } - - action.dismissWithResult(.default) - - let controller = ForumCreateTopicScreen(context: self.context, peerId: peerId, mode: .create) - controller.navigationPresentation = .modal - - controller.completion = { [weak self, weak controller] title, fileId, iconColor, _ in - controller?.isInProgress = true - controller?.view.endEditing(true) - + var canCreateTopics = false + if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser, let botInfo = peer.botInfo, botInfo.flags.contains(.forumManagedByUser) { + canCreateTopics = true + } + if canCreateTopics { + items.append(.action(ContextMenuActionItem(text: strings.Chat_CreateTopic, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] action in guard let self else { return } - let _ = (self.context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) - |> deliverOnMainQueue).startStandalone(next: { [weak self, weak controller] topicId in + action.dismissWithResult(.default) + + let controller = ForumCreateTopicScreen(context: self.context, peerId: peerId, mode: .create) + controller.navigationPresentation = .modal + + controller.completion = { [weak self, weak controller] title, fileId, iconColor, _ in + controller?.isInProgress = true + controller?.view.endEditing(true) + guard let self else { return } - self.updateChatLocationThread(threadId: topicId) - controller?.dismiss() - }, error: { _ in - controller?.isInProgress = false - }) - } - self.push(controller) - }))) + + let _ = (self.context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak controller] topicId in + guard let self else { + return + } + self.updateChatLocationThread(threadId: topicId) + controller?.dismiss() + }, error: { _ in + controller?.isInProgress = false + }) + } + self.push(controller) + }))) + } } let presentationData = self.presentationData diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift index 4441bc44f7..b7dd5f35fc 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPhoneContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -35,15 +34,10 @@ extension ChatControllerImpl: MFMessageComposeViewControllerDelegate { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift index fe2a3f3ddc..ec1ea41250 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift @@ -1,13 +1,13 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display import ContextUI import UndoUI import AccountContext +import ChatMessageBubbleContentNode import ChatMessageItemView import ChatMessageItemCommon import ChatControllerInteraction @@ -43,18 +43,28 @@ extension ChatControllerImpl { if let peerId = pollOption.addedBy { addedByPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) } - + + let messageItemView = params.messageNode as? ChatMessageItemView + let associatedData = (params.messageNode as? ChatMessageBubbleContentNode)?.item?.associatedData ?? messageItemView?.item?.associatedData + let _ = combineLatest( queue: Queue.mainQueue(), - contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView), + contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: messageItemView), addedByPeer ).start(next: { [weak self] actions, addedByPeer in guard let self else { return } - + var items: [ContextMenuItem] = [] - if !poll.isClosed && (selectedOptions.isEmpty || !poll.revotingDisabled) { + + let isRestricted = associatedData?.isPollVotingRestricted( + poll: poll, + accountTestingEnvironment: self.context.account.testingEnvironment, + currentTimestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + ) ?? false + let canVote = poll.pollId.namespace == Namespaces.Media.CloudPoll && !Namespaces.Message.allNonRegular.contains(message.id.namespace) && !isPollEffectivelyClosed(message: EngineMessage(message), poll: poll) && !isRestricted && (selectedOptions.isEmpty || !poll.revotingDisabled) + if canVote { if selectedOptions.contains(pollOption.opaqueIdentifier) { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_Poll_RetractOptionVote, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Unvote"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in guard let self else { @@ -126,7 +136,7 @@ extension ChatControllerImpl { guard let self else { return } - var threadMessageId: MessageId? + var threadMessageId: EngineMessage.Id? if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation { threadMessageId = replyThreadMessage.effectiveMessageId } @@ -254,7 +264,7 @@ extension ChatControllerImpl { ) ) - let messageContentSource = ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, snapshot: true) + let messageContentSource = ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: EngineMessage(message), selectAll: false, snapshot: true) sources.append( ContextController.Source( diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollOptionMedia.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollOptionMedia.swift index 7989c608ac..09c907b4b8 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollOptionMedia.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollOptionMedia.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -15,25 +14,26 @@ import TelegramStringFormatting import TelegramPresentationData import StickerPeekUI import StickerPackPreviewUI +import PresentationDataUtils extension ChatControllerImpl { - func openPollMedia(message: Message, subject: ChatControllerInteraction.PollMediaSubject) -> Void { + func openPollMedia(message: EngineMessage, subject: ChatControllerInteraction.PollMediaSubject) -> Void { let mediaSubject: GalleryMediaSubject - var media: Media? + let media: EngineMedia? switch subject { case let .option(option): - media = option.media + media = option.media.flatMap(EngineMedia.init) mediaSubject = .pollOption(option.opaqueIdentifier) case let .solution(solution): - media = solution.media + media = solution.media.flatMap(EngineMedia.init) mediaSubject = .pollSolution } - + guard let media else { return } - - if let file = media as? TelegramMediaFile, file.isSticker || file.isCustomEmoji { + + if case let .file(file) = media, file.isSticker || file.isCustomEmoji { let _ = (self.context.engine.stickers.isStickerSaved(id: file.fileId) |> deliverOnMainQueue).start(next: { [weak self] isStarred in guard let self else { @@ -105,6 +105,41 @@ extension ChatControllerImpl { self.presentInGlobalOverlay(peekController) }) + } else if case let .webpage(webpage) = media { + guard let url = webpage.content.url else { + return + } + let _ = self.context.sharedContext.openUserGeneratedUrl( + context: self.context, + peerId: nil, + url: url, + webpage: webpage, + concealed: true, + forceConcealed: true, + skipUrlAuth: true, + skipConcealedAlert: false, + forceDark: false, + present: { [weak self] c in + self?.present(c, in: .window(.root)) + }, + openResolved: { [weak self] result in + guard let self, let navigationController = self.effectiveNavigationController else { + return + } + let context = self.context + context.sharedContext.openResolvedUrl(result, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in + + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: { _, _, _ in + }, present: { [weak self] c, a in + self?.present(c, in: .window(.root), with: a) + }, dismissInput: { + context.sharedContext.mainWindow?.viewController?.view.endEditing(false) + }, contentContext: nil, progress: nil, completion: nil) + }, + progress: nil, + alertDisplayUpdated: nil, + concealedAlertOption: nil + ) } else { let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams( context: self.context, @@ -112,7 +147,7 @@ extension ChatControllerImpl { chatLocation: self.chatLocation, chatFilterTag: nil, chatLocationContextHolder: nil, - message: message, + message: message._asMessage(), mediaSubject: mediaSubject, standalone: true, reverseMessageGalleryOrder: false, @@ -195,7 +230,7 @@ extension ChatControllerImpl { }, chatAvatarHiddenMedia: { _, _ in }, - gallerySource: .standaloneMessage(message, mediaSubject) + gallerySource: .standaloneMessage(message._asMessage(), mediaSubject) )) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift index 086d87ae1a..a94fc35d22 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTimecodeContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -31,15 +30,10 @@ extension ChatControllerImpl { } } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) var items: [ContextMenuItem] = [] diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift index 8d88365bd7..363fd5a46a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenTodoContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -169,7 +168,7 @@ extension ChatControllerImpl { guard let self else { return } - var threadMessageId: MessageId? + var threadMessageId: EngineMessage.Id? if case let .replyThread(replyThreadMessage) = self.presentationInterfaceState.chatLocation { threadMessageId = replyThreadMessage.effectiveMessageId } @@ -232,6 +231,7 @@ extension ChatControllerImpl { text: "", media: .update(.standalone(media: updatedTodo)), entities: nil, + richText: nil, inlineStickers: [:] ).start() }))) @@ -251,7 +251,7 @@ extension ChatControllerImpl { ) ) - let messageContentSource = ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, snapshot: true) + let messageContentSource = ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: EngineMessage(message), selectAll: false, snapshot: true) sources.append( ContextController.Source( diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift index 61b871b9bd..1672d9755a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenUsernameContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -19,15 +18,10 @@ extension ChatControllerImpl { return } - let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil// anyRecognizer as? TapLongTapOrDoubleTapGestureRecognizer - let gesture: ContextGesture? = nil // anyRecognizer as? ContextGesture + let recognizer: TapLongTapOrDoubleTapGestureRecognizer? = params.gesture + let gesture: ContextGesture? = nil - let source: ContextContentSource -// if let location = location { -// source = .location(ChatMessageContextLocationContentSource(controller: self, location: messageNode.view.convert(messageNode.bounds, to: nil).origin.offsetBy(dx: location.x, dy: location.y))) -// } else { - source = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) -// } + let source: ContextContentSource = .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)) params.progress?.set(.single(true)) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenViewOnceMediaMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenViewOnceMediaMessage.swift index ec6308a9f7..a40fe91b84 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenViewOnceMediaMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenViewOnceMediaMessage.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -119,10 +118,9 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen extension ChatControllerImpl { - func openViewOnceMediaMessage(_ message: Message) { + func openViewOnceMediaMessage(_ message: EngineMessage) { if self.screenCaptureManager?.isRecordingActive == true { let controller = textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Chat_PlayOnceMesasge_DisableScreenCapture, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { })]) @@ -174,7 +172,7 @@ extension ChatControllerImpl { contextController?.present(c, in: .current) } - let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in + let _ = self.context.sharedContext.openChatMessage(OpenChatMessageParams(context: self.context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _, _ in }, transitionNode: { _, _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, openConferenceCall: { _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .singleMessage(message.id))) } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 42fd82e66f..1c6b716c02 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -268,7 +268,7 @@ func openWebAppImpl( } else { source = url.isEmpty ? .generic : .simple } - let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), sameOrigin: result.flags.contains(.sameOrigin), appSettings: appSettings) let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in presentImpl?(c, a) @@ -317,11 +317,13 @@ func openWebAppImpl( } var presentImpl: ((ViewController, Any?) -> Void)? - let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: hasWebApp ? "" : nil, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let params = WebAppParameters(source: .button, peerId: chatPeer?.id ?? botPeer.id, botId: botPeer.id, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: hasWebApp ? "" : nil, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), sameOrigin: result.flags.contains(.sameOrigin), appSettings: appSettings) let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botPeer.id, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in presentImpl?(c, a) }, commit: commit) + }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: chatPeer?.id ?? botPeer.id, botAddress: botPeer.addressName ?? "", query: query, chatTypes: chatTypes, completion: completion) }, completion: { [weak parentController] in if let parentController = parentController as? ChatControllerImpl { parentController.chatDisplayNode.historyNode.scrollToEndOfHistory() @@ -398,6 +400,129 @@ func openWebAppImpl( }) } +private final class JoinChatWebViewControllerHolder { + weak var controller: WebAppController? +} + +private func presentJoinChatWebViewDecisionToast(context: AccountContext, parentController: ViewController, presentationData: PresentationData, decision: JoinChatWebViewDecision) { + let present: (EnginePeer?) -> Void = { [weak parentController] peer in + let isBroadcast: Bool + if let peer, case let .channel(channel) = peer, case .broadcast = channel.info { + isBroadcast = true + } else { + isBroadcast = false + } + + let content: UndoOverlayContent + switch decision.result { + case .approved: + if isBroadcast, let peer { + content = .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil) + } else { + content = .succeed(text: presentationData.strings.Chat_JoinedGroup_Text, timeout: nil, customUndoText: nil) + } + case .declined: + content = .info(title: nil, text: isBroadcast ? presentationData.strings.Channel_ErrorAccessDenied : presentationData.strings.Group_ErrorAccessDenied, timeout: nil, customUndoText: nil) + case .queued: + content = .inviteRequestSent(title: presentationData.strings.MemberRequests_RequestToJoinSent, text: isBroadcast ? presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel : presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup) + case .webView: + return + } + + parentController?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } + + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: decision.peerId)) + |> deliverOnMainQueue).startStandalone(next: { peer in + present(peer) + }) +} + +func openJoinChatWebViewImpl( + context: AccountContext, + parentController: ViewController, + updatedPresentationData: (initial: PresentationData, signal: Signal)?, + webView: JoinChatWebView +) { + if context.isFrozen { + parentController.push(context.sharedContext.makeAccountFreezeInfoScreen(context: context)) + return + } + + let presentationData: PresentationData + if let parentController = parentController as? ChatControllerImpl { + presentationData = parentController.presentationData + } else { + presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + } + + let peerId = webView.peerId ?? webView.botPeer.id + let botName = webView.botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let params = WebAppParameters(source: .button, peerId: peerId, botId: webView.botPeer.id, botName: botName, botVerified: webView.botPeer.isVerified, botAddress: webView.botPeer.addressName ?? "", appName: nil, url: webView.url, queryId: webView.queryId, payload: nil, buttonText: "", keepAliveSignal: nil, forceHasSettings: false, fullSize: false) + + let decisionDisposable = MetaDisposable() + let webAppControllerHolder = JoinChatWebViewControllerHolder() + var pendingUrl: String? + var didHandleTerminalResult = false + var presentImpl: ((ViewController, Any?) -> Void)? + + let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: nil, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in + ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in + presentImpl?(c, a) + }, commit: commit) + }, requestSwitchInline: { [weak parentController] query, chatTypes, completion in + ChatControllerImpl.botRequestSwitchInline(context: context, controller: parentController as? ChatControllerImpl, peerId: peerId, botAddress: webView.botPeer.addressName ?? "", query: query, chatTypes: chatTypes, completion: completion) + }, didDismiss: { + decisionDisposable.dispose() + }, getNavigationController: { [weak parentController] in + var navigationController: NavigationController? + if let parentController = parentController as? ChatControllerImpl { + navigationController = parentController.effectiveNavigationController + } + return navigationController ?? (context.sharedContext.mainWindow?.viewController as? NavigationController) + }, onControllerCreated: { controller in + webAppControllerHolder.controller = controller + if let pendingUrl { + controller.loadExternal(url: pendingUrl) + } + pendingUrl = nil + }) + controller.navigationPresentation = .flatModal + + presentImpl = { [weak controller] c, a in + controller?.present(c, in: .window(.root), with: a) + } + + decisionDisposable.set((context.account.stateManager.joinChatWebViewDecisions + |> deliverOnMainQueue).start(next: { [weak controller, weak parentController] decisions in + for decision in decisions { + guard decision.queryId == webView.queryId else { + continue + } + switch decision.result { + case let .webView(url): + if let webAppController = webAppControllerHolder.controller { + webAppController.loadExternal(url: url) + } else { + pendingUrl = url + } + case .approved, .declined, .queued: + if didHandleTerminalResult { + return + } + didHandleTerminalResult = true + decisionDisposable.set(nil) + controller?.dismiss(animated: true) + if let parentController { + presentJoinChatWebViewDecisionToast(context: context, parentController: parentController, presentationData: presentationData, decision: decision) + } + } + } + })) + + parentController.push(controller) +} + public extension ChatControllerImpl { func openWebApp(buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { @@ -482,7 +607,7 @@ public extension ChatControllerImpl { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: subject, keepStack: .always, peekData: peekData)) case .info: if peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(infoController) } } @@ -500,7 +625,7 @@ public extension ChatControllerImpl { if let controller { controller.openUrl(url, concealed: concealed, forceExternal: true, commit: commit) } else { - let _ = openUserGeneratedUrl(context: context, peerId: peerId, url: url, concealed: concealed, present: { c in + let _ = context.sharedContext.openUserGeneratedUrl(context: context, peerId: peerId, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { c in present(c, nil) }, openResolved: { result in var navigationController: NavigationController? @@ -522,7 +647,7 @@ public extension ChatControllerImpl { context.sharedContext.mainWindow?.viewController?.view.endEditing(false) }, contentContext: nil, progress: nil, completion: nil) } - }) + }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil) } } @@ -607,7 +732,7 @@ public extension ChatControllerImpl { updateProgress() }) |> deliverOnMainQueue).startStandalone(next: { [weak parentController, weak chatController] result in - let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, botAddress: botPeer.addressName ?? "", appName: botApp.shortName, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings) + let params = WebAppParameters(source: .generic, peerId: peerId, botId: botPeer.id, botName: botApp.title, botVerified: botPeer.isVerified, botAddress: botPeer.addressName ?? "", appName: botApp.shortName, url: result.url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, forceHasSettings: botApp.flags.contains(.hasSettings), fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), sameOrigin: result.flags.contains(.sameOrigin), appSettings: appSettings) var presentImpl: ((ViewController, Any?) -> Void)? let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { url, concealed, forceUpdate, commit in ChatControllerImpl.botOpenUrl(context: context, peerId: peerId, controller: chatController, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift index ab1b663e74..4af9e943d6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaidMessage.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -120,7 +119,7 @@ extension ChatControllerImpl { return false } if case .undo = action { - var messageIds: [MessageId] = [] + var messageIds: [EngineMessage.Id] = [] self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemNodeProtocol { for message in itemNode.messages() { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index 5d489d7069..ff9467e7ef 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -10,7 +9,6 @@ import MediaPickerUI import MediaPasteboardUI import LegacyMediaPickerUI import MediaEditor -import ChatEntityKeyboardInputNode extension ChatControllerImpl { func displayPasteMenu(_ subjects: [MediaPickerScreenImpl.Subject.Media]) { @@ -24,6 +22,7 @@ extension ChatControllerImpl { if strongSelf.presentationInterfaceState.interfaceState.postSuggestionState != nil { enableMultiselection = false } + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText strongSelf.chatDisplayNode.dismissInput() let controller = mediaPasteboardScreen( @@ -33,18 +32,21 @@ extension ChatControllerImpl { subjects: subjects, presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in if let strongSelf = self { - strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in + strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: { controller, mediaPickerContext in + if !inputText.string.isEmpty { + mediaPickerContext?.setCaption(inputText) + } + present(controller, mediaPickerContext) + }, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in + if !inputText.string.isEmpty { + self?.clearInputText() + } self?.enqueueMediaMessages(fromGallery: fromGallery, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) }) } }, getSourceRect: nil, - makeEntityInputView: { [weak self] in - guard let self else { - return nil - } - return EntityInputView(context: self.context, isDark: false, areCustomEmojiEnabled: self.presentationInterfaceState.customEmojiAvailable) - } + customEmojiAvailable: strongSelf.presentationInterfaceState.customEmojiAvailable ) controller.navigationPresentation = .flatModal strongSelf.push(controller) @@ -103,14 +105,14 @@ extension ChatControllerImpl { self.enqueueMediaMessageDisposable.set((convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).startStrict(next: { [weak self] data in if let strongSelf = self, !data.isEmpty { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - strongSelf.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + strongSelf.context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) var fileAttributes: [TelegramMediaFileAttribute] = [] fileAttributes.append(.FileName(fileName: "sticker.webp")) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) fileAttributes.append(.ImageSize(size: PixelDimensions(size))) - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) + let media = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes, alternativeRepresentations: []) let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in @@ -154,7 +156,7 @@ extension ChatControllerImpl { Queue.mainQueue().after(3.0) { if let message = self.chatDisplayNode.historyNode.lastVisbleMesssage(), let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile, file.isSticker { - self.context.engine.stickers.addRecentlyUsedSticker(fileReference: .message(message: MessageReference(message), media: file)) + self.context.engine.stickers.addRecentlyUsedSticker(fileReference: .message(message: MessageReference(message._asMessage()), media: file)) } } }) @@ -202,6 +204,7 @@ extension ChatControllerImpl { videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, + additionalVideoMirroringChanges: [], additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, @@ -251,9 +254,9 @@ extension ChatControllerImpl { let previewRepresentations: [TelegramMediaImageRepresentation] = [] let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - self.context.account.postbox.mediaBox.copyResourceData(resource.id, fromTempPath: path) + self.context.engine.resources.copyResourceData(id: EngineMediaResource.Id(resource.id), fromTempPath: path) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/webm", size: 0, attributes: fileAttributes, alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/webm", size: 0, attributes: fileAttributes, alternativeRepresentations: []) self.enqueueStickerFile(file) default: break diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift index 62cbe695e2..2c740bda67 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPlayMessageEffect.swift @@ -2,14 +2,13 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext import ChatMessageItemView extension ChatControllerImpl { - func playMessageEffect(message: Message) { + func playMessageEffect(message: EngineMessage) { var messageItemNode: ChatMessageItemView? self.chatDisplayNode.historyNode.forEachVisibleMessageItemNode { itemNode in if let item = itemNode.item, item.message.id == message.id { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerReport.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerReport.swift index a08513ec6c..149167ad39 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerReport.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerReport.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -119,7 +118,6 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen extension ChatControllerImpl { func unblockPeer() { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index d3665dee65..fb6e199ca2 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit @@ -119,7 +118,6 @@ import AudioWaveform import PeerNameColorScreen import ChatEmptyNode import ChatMediaInputStickerGridItem -import AdsInfoScreen import Photos import ChatThemeScreen diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift index f2ddc3926b..50dca82959 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift @@ -1,15 +1,91 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AsyncDisplayKit import Display import UndoUI import AccountContext import ChatControllerInteraction +import TelegramStringFormatting extension ChatControllerImpl { + func displaySendReactionRestrictedToast() { + self.controllerInteraction?.presentControllerInCurrent(UndoOverlayController( + presentationData: self.presentationData, + content: .banned(text: self.presentationData.strings.Chat_SendReactionRestricted), + elevatedLayout: false, + position: .bottom, + animateInAsReplacement: false, + action: { _ in return true } + ), nil) + } + + func displayPollRestrictedToast(messageId: EngineMessage.Id) { + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Messages.Message(id: messageId), + TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] message, peer in + guard let self, let message, let peer else { + return + } + let peerName = peer.compactDisplayTitle + guard let poll = message.media.first(where: { $0 is TelegramMediaPoll }) as? TelegramMediaPoll else { + return + } + + var accountCountry = "" + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let country = data["phone_country_iso2"] as? String { + accountCountry = (country) + } + var text = "" + if !poll.countries.isEmpty && !poll.countries.contains(accountCountry) { + let locale = localeWithStrings(self.presentationData.strings) + let countryNames = poll.countries.map { id in + if id == "FT" { + return "Fragment" + } else if let countryName = locale.localizedString(forRegionCode: id) { + return countryName + } else { + return id + } + } + var countries: String = "" + if countryNames.count == 1, let country = countryNames.first { + countries = "**\(country)**" + } else { + for i in 0 ..< countryNames.count { + countries.append("**\(countryNames[i])**") + if i == countryNames.count - 2 { + countries.append(self.presentationData.strings.Chat_Poll_Restriction_Country_CountriesLastDelimiter) + } else if i < countryNames.count - 2 { + countries.append(self.presentationData.strings.Chat_Poll_Restriction_Country_CountriesDelimiter) + } + } + } + if poll.restrictToSubscribers { + text = self.presentationData.strings.Chat_Poll_Restriction_SubscribersCountry(peerName, countries).string + } else { + text = self.presentationData.strings.Chat_Poll_Restriction_Country(countries).string + } + } else { + if case let .channel(channel) = peer, case .member = channel.participationStatus { + text = self.presentationData.strings.Chat_Poll_Restriction_Subscribers_TimeLimit + } else { + text = self.presentationData.strings.Chat_Poll_Restriction_Subscribers(peerName).string + } + } + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .banned(text: text), + position: .bottom, + action: { _ in return true } + ) + self.controllerInteraction?.presentControllerInCurrent(controller, nil) + }) + } + func displayPostedScheduledMessagesToast(ids: [EngineMessage.Id]) { let timestamp = CFAbsoluteTimeGetCurrent() if self.lastPostedScheduledMessagesToastTimestamp + 0.4 >= timestamp { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index fc854dfdb9..bc930d7f0d 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -27,6 +26,17 @@ private enum OptionsId: Hashable { case link } +private func chatLocationMatchesDestination(_ chatLocation: ChatLocation, peerId: EnginePeer.Id, threadId: Int64?) -> Bool { + switch chatLocation { + case let .peer(id): + return id == peerId && threadId == nil + case let .replyThread(replyThreadMessage): + return replyThreadMessage.peerId == peerId && replyThreadMessage.threadId == threadId + case .customChatContents: + return false + } +} + private func presentChatInputOptions(selfController: ChatControllerImpl, sourceView: UIView, initialId: OptionsId) { var getContextController: (() -> ContextController?)? @@ -130,11 +140,12 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceView: var items: [ContextMenuItem] = [] var hasCaptions = false - var uniquePeerIds = Set() + var uniquePeerIds = Set() var hasOther = false var hasNotOwnMessages = false var hasPaid = false + var hasRichMessages = false for message in messages { if let author = message.effectiveAuthor { if !uniquePeerIds.contains(author.id) { @@ -147,10 +158,8 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceView: } var isDice = false - var isMusic = false for media in message.media { if let media = media as? TelegramMediaFile, media.isMusic { - isMusic = true if !message.text.isEmpty { hasCaptions = true } @@ -164,12 +173,15 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceView: hasPaid = true } } - if !isDice && !isMusic { + if !isDice { hasOther = true } + if message.richText != nil { + hasRichMessages = true + } } - var canHideNames = hasNotOwnMessages && hasOther + var canHideNames = hasNotOwnMessages && hasOther && !hasRichMessages if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { canHideNames = false } @@ -564,7 +576,7 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj var suggestedPeers: [EnginePeer] = [] if let message = await selfController.context.engine.data.get( TelegramEngine.EngineData.Item.Messages.Message(id: replySubject.messageId) - ).get(), case let .user(author) = message.author { + ).get(), case let .user(author) = message.author, author.id != selfController.context.account.peerId { suggestedPeers.append(.user(author)) } @@ -623,14 +635,13 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj return } let peerId = peer.id - //let accountPeerId = selfController.context.account.peerId var isPinnedMessages = false if case .pinnedMessages = selfController.presentationInterfaceState.subject { isPinnedMessages = true } - if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages { + if chatLocationMatchesDestination(selfController.chatLocation, peerId: peerId, threadId: threadId), selfController.parentController == nil, !isPinnedMessages { selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) }) selfController.updateItemNodesSearchTextHighlightStates() selfController.searchResultsController = nil @@ -651,7 +662,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id, if let navigationController = selfController.effectiveNavigationController { for controller in navigationController.viewControllers { if let maybeChat = controller as? ChatControllerImpl { - if case .peer(peerId) = maybeChat.chatLocation { + if chatLocationMatchesDestination(maybeChat.chatLocation, peerId: peerId, threadId: threadId) { var isChatPinnedMessages = false if case .pinnedMessages = maybeChat.presentationInterfaceState.subject { isChatPinnedMessages = true diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index b16df87b7e..2bca0dbd4e 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import ContextUI import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramNotices import ChatSendMessageActionUI @@ -13,6 +12,7 @@ import TopMessageReactions import ReactionSelectionNode import ChatControllerInteraction import ChatSendAudioMessageContextPreview +import BrowserUI extension ChatSendMessageEffect { convenience init(_ effect: ChatSendMessageActionSheetController.SendParameters.Effect) { @@ -212,6 +212,14 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no } } + var richTextPreview: ChatSendMessageContextScreenRichTextPreview? + if case .customChatContents = selfController.presentationInterfaceState.subject { + } else if mediaPreview == nil, + let attributedText = textInputView.attributedText, + let attribute = richMarkdownAttributeIfNeeded(context: selfController.context, attributedText: attributedText) { + richTextPreview = ChatSendMessageRichTextPreview(context: selfController.context, instantPage: attribute.instantPage) + } + let controller = makeChatSendMessageActionSheetController( initialData: initialData, context: selfController.context, @@ -287,7 +295,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no }, reactionItems: (!textInputView.text.isEmpty || mediaPreview != nil) ? effectItems : nil, availableMessageEffects: availableMessageEffects, - isPremium: hasPremium + isPremium: hasPremium, + richTextPreview: richTextPreview ) selfController.sendMessageActionsController = controller if layout.isNonExclusive { diff --git a/submodules/TelegramUI/Sources/Chat/ChatSendMessageRichTextPreview.swift b/submodules/TelegramUI/Sources/Chat/ChatSendMessageRichTextPreview.swift new file mode 100644 index 0000000000..4a12867f5a --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatSendMessageRichTextPreview.swift @@ -0,0 +1,152 @@ +import Foundation +import UIKit +import Display +import TelegramCore +import AccountContext +import TelegramPresentationData +import ComponentFlow +import InstantPageUI +import TelegramUIPreferences +import ChatSendMessageActionUI + +final class ChatSendMessageRichTextPreview: ChatSendMessageContextScreenRichTextPreview { + private let context: AccountContext + private let instantPage: InstantPage + private let webpage: TelegramMediaWebpage + private let pageView: InstantPageV2View + + private var cachedBoundingWidth: CGFloat? + private var cachedThemeIdentity: ObjectIdentifier? + private var cachedContentSize: CGSize = .zero + + var view: UIView { + return self.pageView + } + + init(context: AccountContext, instantPage: InstantPage) { + self.context = context + self.instantPage = instantPage + + let webpage = TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: 0, id: 0), content: .Loaded(TelegramMediaWebpageLoadedContent( + url: "", + displayUrl: "", + hash: 0, + type: nil, + websiteName: nil, + title: nil, + text: nil, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: instantPage + ))) + self.webpage = webpage + + let renderContext = InstantPageV2RenderContext( + context: context, + webpage: webpage, + sourceLocation: InstantPageSourceLocation(userLocation: .other, peerType: .channel), + imageReference: { image in + return ImageMediaReference.standalone(media: image) + }, + fileReference: { file in + return FileMediaReference.standalone(media: file) + }, + present: { _, _ in }, + push: { _ in }, + openUrl: { _ in }, + baseNavigationController: { return nil }, + message: nil + ) + self.pageView = InstantPageV2View(renderContext: renderContext) + self.pageView.isUserInteractionEnabled = false + } + + func update(boundingWidth: CGFloat, presentationData: PresentationData, transition: ComponentTransition) -> CGSize { + let themeIdentity = ObjectIdentifier(presentationData.theme) + if self.cachedBoundingWidth == boundingWidth, self.cachedThemeIdentity == themeIdentity { + return self.cachedContentSize + } + + // Combined with MessageItemView's 1pt left border this yields a 10pt left text inset. + let pageHorizontalInset: CGFloat = 9.0 + let isDark = presentationData.theme.overallDarkAppearance + let messageTheme = presentationData.theme.chat.message.outgoing + let mainColor = messageTheme.accentTextColor + + let codeBlockBackgroundColor: UIColor + if isDark { + codeBlockBackgroundColor = UIColor(white: 0.0, alpha: 0.25) + } else { + codeBlockBackgroundColor = mainColor.withMultipliedAlpha(0.1) + } + + let textCategories = InstantPageTextCategories( + kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 24.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + subheader: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), + paragraph: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 17.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + caption: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: messageTheme.secondaryTextColor), + credit: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 13.0, lineSpacingFactor: 1.0), color: messageTheme.secondaryTextColor), + table: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + article: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 18.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor), + codeBlock: InstantPageTextAttributes(font: InstantPageFont(style: .monospace, size: 14.0, lineSpacingFactor: 1.0), color: messageTheme.primaryTextColor) + ) + let pageTheme = InstantPageTheme( + type: isDark ? .dark : .light, + pageBackgroundColor: .clear, + textCategories: textCategories, + serif: false, + codeBlockBackgroundColor: codeBlockBackgroundColor, + linkColor: messageTheme.linkTextColor, + textHighlightColor: messageTheme.accentTextColor.withMultipliedAlpha(0.1), + linkHighlightColor: messageTheme.linkTextColor.withMultipliedAlpha(0.1), + markerColor: UIColor(rgb: 0xfef3bc), + panelBackgroundColor: messageTheme.accentControlColor.withMultipliedAlpha(0.1), + panelHighlightedBackgroundColor: messageTheme.accentControlColor.withMultipliedAlpha(0.25), + panelPrimaryColor: messageTheme.primaryTextColor, + panelSecondaryColor: messageTheme.secondaryTextColor, + panelAccentColor: messageTheme.accentTextColor, + // Preview is always outgoing, so these match the reference bubble's + // `isDark || !isIncoming` (always-true) branch. + tableBorderColor: messageTheme.accentControlColor.withMultipliedAlpha(0.25), + tableHeaderColor: messageTheme.accentControlColor.withMultipliedAlpha(0.1), + controlColor: messageTheme.accentControlColor, + imageTintColor: nil, + overlayPanelColor: messageTheme.accentControlColor.withMultipliedAlpha(0.25), + separatorColor: messageTheme.accentControlColor.withMultipliedAlpha(0.25), + secondaryControlColor: messageTheme.secondaryTextColor + ) + + let layout = layoutInstantPageV2( + webpage: self.webpage, + instantPage: self.instantPage, + userLocation: .other, + boundingWidth: boundingWidth, + horizontalInset: pageHorizontalInset, + theme: pageTheme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + cachedMessageSyntaxHighlight: nil, + expandedDetails: [:], + fitToWidth: true + ) + self.pageView.update(layout: layout, theme: pageTheme, animation: .None) + // The parent (MessageItemView) owns and sets `pageView`'s frame; `update` only + // rebuilds content and reports the size. Rendering is static (.None) — the screen + // drives the size/crossfade transition. + + self.cachedBoundingWidth = boundingWidth + self.cachedThemeIdentity = themeIdentity + self.cachedContentSize = layout.contentSize + return layout.contentSize + } +} diff --git a/submodules/TelegramUI/Sources/Chat/PeerMessageSelectedReactions.swift b/submodules/TelegramUI/Sources/Chat/PeerMessageSelectedReactions.swift index 7a30ad19f8..94a81cfaf9 100644 --- a/submodules/TelegramUI/Sources/Chat/PeerMessageSelectedReactions.swift +++ b/submodules/TelegramUI/Sources/Chat/PeerMessageSelectedReactions.swift @@ -1,17 +1,16 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext -func peerMessageSelectedReactions(context: AccountContext, message: Message) -> Signal<(reactions: Set, files: Set), NoError> { +func peerMessageSelectedReactions(context: AccountContext, message: EngineMessage) -> Signal<(reactions: Set, files: Set), NoError> { return context.engine.stickers.availableReactions() |> take(1) - |> map { availableReactions -> (reactions: Set, files: Set) in - var result = Set() + |> map { availableReactions -> (reactions: Set, files: Set) in + var result = Set() var reactions = Set() - + if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) { for reaction in effectiveReactions { if !reaction.isSelected { @@ -27,11 +26,11 @@ func peerMessageSelectedReactions(context: AccountContext, message: Message) -> result.insert(availableReaction.selectAnimation.fileId) } case let .custom(fileId): - result.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) + result.insert(EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId)) } } } - + return (reactions, result) } } diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index acf7b004a7..e71da9520a 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -28,7 +27,7 @@ func updateChatPresentationInterfaceStateImpl( completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void ) { var transition = transition - if !selfController.didAppear { + if !selfController.enableAnimations { transition = .immediate } @@ -94,7 +93,7 @@ func updateChatPresentationInterfaceStateImpl( guard let selfController, value else { return } - selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_Cancel, action: { + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .genericAction, title: selfController.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_OK, action: { [weak selfController] in guard let selfController else { return @@ -151,7 +150,7 @@ func updateChatPresentationInterfaceStateImpl( case .generic: break case let .inlineBotLocationRequest(peerId): - selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .defaultAction, title: selfController.presentationData.strings.Common_Cancel, action: { [weak selfController] in + selfController.present(textAlertController(context: selfController.context, updatedPresentationData: selfController.updatedPresentationData, title: nil, text: selfController.presentationData.strings.Conversation_ShareInlineBotLocationConfirmation, actions: [TextAlertAction(type: .genericAction, title: selfController.presentationData.strings.Common_Cancel, action: { [weak selfController] in guard let selfController else { return } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a36a485982..70f4ac306d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -212,7 +212,7 @@ func isTopmostChatController(_ controller: ChatControllerImpl) -> Bool { return true } -func calculateSlowmodeActiveUntilTimestamp(account: Account, untilTimestamp: Int32?) -> Int32? { +func calculateSlowmodeActiveUntilTimestamp(untilTimestamp: Int32?) -> Int32? { guard let untilTimestamp = untilTimestamp else { return nil } @@ -252,6 +252,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let context: AccountContext public internal(set) var chatLocation: ChatLocation public internal(set) var subject: ChatControllerSubject? + var initialTextInputState: ChatTextInputState? var botStart: ChatControllerInitialBotStart? var attachBotStart: ChatControllerInitialAttachBotStart? @@ -386,7 +387,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var chatUnreadMentionCountDisposable: Disposable? var peerInputActivitiesDisposable: Disposable? - var peerInputActivitiesPromise = Promise<[(Peer, PeerInputActivity)]>() + var peerInputActivitiesPromise = Promise<[(EnginePeer, PeerInputActivity)]>() var interactiveEmojiSyncDisposable = MetaDisposable() var recentlyUsedInlineBotsValue: [Peer] = [] @@ -458,6 +459,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var willAppear = false var didAppear = false + var enableAnimations = false var scheduledActivateInput: ChatControllerActivateInput? var raiseToListen: RaiseToListenManager? @@ -482,6 +484,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G weak var emojiPackTooltipController: TooltipScreen? weak var birthdayTooltipController: TooltipScreen? weak var scheduledVideoProcessingTooltipController: TooltipScreen? + weak var guestChatMessageTooltipController: TooltipScreen? weak var slowmodeTooltipController: ChatSlowmodeHintController? @@ -638,10 +641,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G botAppStart: ChatControllerInitialBotAppStart? = nil, mode: ChatControllerPresentationMode = .standard(.default), peekData: ChatPeekTimeout? = nil, - peerNearbyData: ChatPeerNearbyData? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], customChatNavigationStack: [EnginePeer.Id]? = nil, + initialTextInputState: ChatTextInputState? = nil, params: ChatControllerParams? = nil ) { self.initTimestamp = CFAbsoluteTimeGetCurrent() @@ -654,6 +657,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatLocation = chatLocation self.chatLocationContextHolder = chatLocationContextHolder self.subject = subject + self.initialTextInputState = initialTextInputState self.botStart = botStart self.attachBotStart = attachBotStart self.botAppStart = botAppStart @@ -689,7 +693,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.stickerSettings = ChatInterfaceStickerSettings() - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, greetingData: context.prefetchManager?.preloadedGreetingSticker, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) if case let .customChatContents(customChatContents) = subject { switch customChatContents.kind { @@ -843,7 +847,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in - guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else { + guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id)?._asMessage() else { return false } @@ -901,7 +905,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if (file.isVoice || file.isInstantVideo) && message.minAutoremoveOrClearTimeout == viewOnceTimeout { - self.openViewOnceMediaMessage(message) + self.openViewOnceMediaMessage(EngineMessage(message)) return false } } else if file.isVideo { @@ -1585,17 +1589,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { - legacyMediaEditor(context: self.context, peer: peer, threadTitle: self.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { + let hasSilentPosting = peer.id != self.context.account.peerId + let hasSchedule = self.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat && self.presentationInterfaceState.sendPaidMessageStars == nil + legacyMediaEditor(context: self.context, peer: EnginePeer(peer), threadTitle: self.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: { transitionCompletion() }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false) - }, sendMessagesWithSignals: { [weak self] signals, _, _, isCaptionAbove in + }, photoToolbarView: { [context = self.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) + }, hasSilentPosting: hasSilentPosting, hasSchedule: hasSchedule, reminder: peer.id == self.context.account.peerId, presentSchedulePicker: { [weak self] _, done in + guard let self else { + return + } + self.presentScheduleTimePicker(style: .media, presentInOverlay: true, completion: { [weak self] result in + guard let self else { + return + } + done(result.time, result.silentPosting) + if self.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { + self.openScheduledMessages() + } + }) + }, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, isCaptionAbove in if let self { var parameters: ChatSendMessageActionSheetController.SendParameters? if isCaptionAbove { parameters = ChatSendMessageActionSheetController.SendParameters(effect: nil, textIsAboveMedia: true) } - self.enqueueMediaMessages(signals: signals, silentPosting: false, replyToSubject: .init(messageId: message.id, quote: nil, innerSubject: nil), parameters: parameters) + self.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime == 0 ? nil : scheduleTime, replyToSubject: .init(messageId: message.id, quote: nil, innerSubject: nil), parameters: parameters) } }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) @@ -1656,13 +1677,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self, self.isNodeLoaded else { return } - self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location) + self.openMessageContextMenu(message: EngineMessage(message), selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location) }, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in guard let self else { return } - self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value) + self.openMessageReactionContextMenu(message: EngineMessage(message), sourceView: sourceView, gesture: gesture, value: value) }, updateMessageReaction: { [weak self] initialMessage, reaction, force, sourceView in guard let strongSelf = self else { return @@ -1735,7 +1756,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let tag = ReactionsMessageAttribute.messageTag(reaction: chosenReaction) if strongSelf.presentationInterfaceState.historyFilter?.customTag == tag { if let sourceView { - strongSelf.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: nil, value: chosenReaction) + strongSelf.openMessageReactionContextMenu(message: EngineMessage(message), sourceView: sourceView, gesture: nil, value: chosenReaction) } } else { strongSelf.chatDisplayNode.historyNode.frozenMessageForScrollingReset = message.id @@ -1747,7 +1768,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message) + let _ = (peerMessageAllowedReactions(context: strongSelf.context, message: message, ignoreDefault: canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState)) |> deliverOnMainQueue).startStandalone(next: { allowedReactions, _ in guard let strongSelf = self else { return @@ -1791,6 +1812,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if case .stars = chosenReaction { + if !canSendReactionsToChat(strongSelf.presentationInterfaceState) { + strongSelf.displaySendReactionRestrictedToast() + return + } + if strongSelf.selectPollOptionFeedback == nil { strongSelf.selectPollOptionFeedback = HapticFeedback() } @@ -1957,7 +1983,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if removedReaction == nil { - if !canAddMessageReactions(message: message) { + if !canSendReactionsToChat(strongSelf.presentationInterfaceState) { + strongSelf.displaySendReactionRestrictedToast() + return + } + + if !canAddMessageReactions(message: EngineMessage(message)) { itemNode.openMessageContextMenu() return } @@ -2069,7 +2100,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts(itemNode: itemNode) - if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: message, isPremium: strongSelf.presentationInterfaceState.isPremium, forceInline: false) { + if let removedReaction = removedReaction, let targetView = itemNode.targetReactionView(value: removedReaction), shouldDisplayInlineDateReactions(message: EngineMessage(message), isPremium: strongSelf.presentationInterfaceState.isPremium, forceInline: false) { var hideRemovedReaction: Bool = false if let reactions = mergedMessageReactions(attributes: message.attributes, isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) { for reaction in reactions.reactions { @@ -2206,7 +2237,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } } - }, sendMessage: { [weak self] text in + }, sendMessage: { [weak self] text, sourceMessageId in guard let strongSelf = self, canSendMessagesToChat(strongSelf.presentationInterfaceState) else { return } @@ -2234,12 +2265,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } + let replyMessageSubject = sourceMessageId.flatMap { + EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) + } ?? strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel let peerId = strongSelf.chatLocation.peerId if peerId?.namespace != Namespaces.Peer.SecretChat, let interactiveEmojis = strongSelf.chatDisplayNode.interactiveEmojis, interactiveEmojis.emojis.contains(text) { - strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: text)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: AnyMediaReference.standalone(media: TelegramMediaDice(emoji: text)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } else { - strongSelf.sendMessages([.message(text: text, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + strongSelf.sendMessages([.message(text: text, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } }, sendSticker: { [weak self] fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets in guard let strongSelf = self else { @@ -2281,87 +2315,203 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf else { return } - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in - var current = current - current = current.updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) - if clearInput { - interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) - } - return interfaceState - }.updatedInputMode { current in - if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil, focused: focused) - } - return current - } - - return current - }) - } - }, shouldAnimateMessageTransition ? correlationId : nil) - if shouldAnimateMessageTransition { - if let sourceNode = sourceView?.asyncdisplaykit_node as? ChatMediaInputStickerGridItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: { - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in - var current = current - current = current.updatedInputMode { current in - if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil, focused: focused) - } - return current + let addToTransitionNodeIfNeeded: () -> Void = { + guard let strongSelf = self else { + return + } + if shouldAnimateMessageTransition { + if let sourceNode = sourceView?.asyncdisplaykit_node as? ChatMediaInputStickerGridItemNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: { + guard let strongSelf = self else { + return } - - return current - }) - }) - } else if let sourceNode = sourceView?.asyncdisplaykit_node as? HorizontalStickerGridItemNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) - } else if let sourceNode = sourceView?.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .emptyPanel(itemNode: sourceNode), replyPanel: nil), initiated: {}) - } else if let sourceLayer, let sourceView, let sourceRect { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .universal(sourceContainerView: sourceView, sourceRect: sourceRect, sourceLayer: sourceLayer), replyPanel: replyPanel), initiated: { - guard let strongSelf = self else { - return - } - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in - var current = current - current = current.updatedInputMode { current in - if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { - return .media(mode: mode, expanded: nil, focused: focused) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInputMode { current in + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) + } + return current } + return current - } - - return current + }) }) - }) + } else if let sourceNode = sourceView?.asyncdisplaykit_node as? HorizontalStickerGridItemNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {}) + } else if let sourceNode = sourceView?.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .emptyPanel(itemNode: sourceNode), replyPanel: nil), initiated: {}) + } else if let sourceLayer, let sourceView, let sourceRect { + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .universal(sourceContainerView: sourceView, sourceRect: sourceRect, sourceLayer: sourceLayer), replyPanel: replyPanel), initiated: { + guard let strongSelf = self else { + return + } + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInputMode { current in + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) + } + return current + } + + return current + }) + }) + } } } - let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)] + let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + + let messages: [EnqueueMessage] = [.message(text: "", attributes: attributes, inlineStickers: [:], mediaReference: fileReference.abstract, threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)] if silentPosting { + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) + if clearInput { + interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) + } + return interfaceState + }.updatedInputMode { current in + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) + } + return current + } + + return current + }) + } + }, shouldAnimateMessageTransition ? correlationId : nil) + + addToTransitionNodeIfNeeded() let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone) strongSelf.sendMessages(transformedMessages) } else if schedule { - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) + if clearInput { + interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) + } + return interfaceState + }.updatedInputMode { current in + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) + } + return current + } + + return current + }) + } + }, shouldAnimateMessageTransition ? correlationId : nil) + + strongSelf.presentScheduleTimePicker(completion: { [weak self] result in + if let strongSelf = self { + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: result.silentPosting, scheduleTime: result.time, repeatPeriod: result.repeatPeriod, postpone: postpone) strongSelf.sendMessages(transformedMessages) } }) } else { - let transformedMessages = strongSelf.transformEnqueueMessages(messages, postpone: postpone) - strongSelf.sendMessages(transformedMessages) + let messages = strongSelf.transformEnqueueMessages(messages, postpone: postpone) + + var targetThreadId: Int64? + var clearMainThreadForward = false + if strongSelf.chatLocation.threadId == nil, let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasForum), botInfo.flags.contains(.forumManagedByUser) { + if let message = messages.first { + switch message { + case let .message(_, _, _, _, _, replyToMessageId, _, _, _, _): + if let _ = replyToMessageId { + if let replyMessage = strongSelf.presentationInterfaceState.replyMessage { + targetThreadId = replyMessage.threadId + } + } else { + targetThreadId = EngineMessage.newTopicThreadId + } + case let .forward(_, threadId, _, _, _): + if let threadId { + targetThreadId = threadId + } else { + targetThreadId = EngineMessage.newTopicThreadId + clearMainThreadForward = true + } + } + } + } + + let doSend: (Int64?) -> Void = { [weak self] overrideThreadId in + guard let strongSelf = self else { + return + } + + var messages = messages + if let overrideThreadId { + messages = messages.map { message in + return message.withUpdatedThreadId(overrideThreadId) + } + } + + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() + + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) + if clearInput { + interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) + } + return interfaceState + }.updatedInputMode { current in + if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil { + return .media(mode: mode, expanded: nil, focused: focused) + } + return current + } + + return current + }) + } + }, shouldAnimateMessageTransition ? correlationId : nil) + + addToTransitionNodeIfNeeded() + + strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }) + } + + if let targetThreadId { + strongSelf.chatDisplayNode.historyNode.stopHistoryUpdates() + strongSelf.updateChatLocationThread(threadId: targetThreadId, animationDirection: .right, transferInputState: true, completion: { [weak strongSelf] in + guard let strongSelf else { + return + } + doSend(targetThreadId) + if clearMainThreadForward, let peerId = strongSelf.chatLocation.peerId { + let _ = ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: nil, { current in + return current.withUpdatedForwardMessageIds(nil) + }).startStandalone() + } + }) + } else { + doSend(nil) + } } }) return true @@ -2441,9 +2591,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G messages = strongSelf.transformEnqueueMessages(messages, silentPosting: true) strongSelf.sendMessages(messages) } else if schedule { - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in + strongSelf.presentScheduleTimePicker(completion: { [weak self] result in if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod) + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: result.silentPosting, scheduleTime: result.time, repeatPeriod: result.repeatPeriod) strongSelf.sendMessages(transformedMessages) } }) @@ -2933,7 +3083,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return $0 } }) - strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId), strongSelf.context.engine.messages.requestMessageActionUrlAuth(subject: subject) |> afterDisposed { + let urlAuthAccountPeer = strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + strongSelf.messageActionUrlAuthDisposable.set(((combineLatest(urlAuthAccountPeer, strongSelf.context.engine.messages.requestMessageActionUrlAuth(subject: subject) |> afterDisposed { Queue.mainQueue().async { if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -2961,7 +3119,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .default: strongSelf.openUrl(defaultUrl, concealed: false, skipUrlAuth: true) case let .request(domain, bot, _, flags, _, _): - let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: bot, requestWriteAccess: flags.contains(.requestWriteAccess), displayName: EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in + let controller = chatMessageActionUrlAuthController(context: strongSelf.context, defaultUrl: defaultUrl, domain: domain, bot: EnginePeer(bot), requestWriteAccess: flags.contains(.requestWriteAccess), displayName: peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), open: { [weak self] authorize, allowWriteAccess in if let strongSelf = self { if authorize { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { @@ -3084,7 +3242,67 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { strongSelf.openUrl(url, concealed: concealed, forceExternal: forceExternal, skipConcealedAlert: skipConcealedAlert, message: message, allowInlineWebpageResolution: urlData.allowInlineWebpageResolution, progress: progress) } - }, shareCurrentLocation: { [weak self] in + }, openExternalInstantPage: { [weak self] instantPage in + guard let self else { + return + } + let _ = (webpagePreviewWithProgress(account: self.context.account, urls: [instantPage.url], webpageId: instantPage.webpageId) + |> deliverOnMainQueue).startStandalone(next: { [weak self] result in + Task { @MainActor in + guard let self else { + return + } + switch result { + case let .result(webpageResult): + instantPage.progress?.set(.single(false)) + + guard let webpageResult else { + self.openUrl(instantPage.url, concealed: true) + return + } + + if case .Loaded = webpageResult.webpage.content { + let sourceLocation: InstantPageSourceLocation + + if let peerId = self.chatLocation.peerId { + let (peer, isContact) = await self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), + TelegramEngine.EngineData.Item.Peer.IsContact(id: peerId) + ).get() + + if let peer { + let peerType: MediaAutoDownloadPeerType + switch peer { + case .user: + peerType = isContact ? .contact : .otherPrivate + case let .channel(channel): + if case .group = channel.info { + peerType = .group + } else { + peerType = .channel + } + case .legacyGroup: + peerType = .group + case .secretChat: + return + } + let peerLocation = MediaResourceUserLocation.peer(peer.id) + sourceLocation = InstantPageSourceLocation(userLocation: peerLocation, peerType: peerType) + } else { + sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel) + } + } else { + sourceLocation = InstantPageSourceLocation(userLocation: .other, peerType: .channel) + } + + self.push(makeInstantPageControllerImpl(context: self.context, webPage: webpageResult.webpage, anchor: instantPage.anchor, sourceLocation: sourceLocation)) + } + case .progress: + instantPage.progress?.set(.single(true)) + } + } + }) + }, shareCurrentLocation: { [weak self] sourceMessageId in if let strongSelf = self { if case .pinnedMessages = strongSelf.presentationInterfaceState.subject { return @@ -3099,7 +3317,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> deliverOnMainQueue).startStandalone(next: { coordinate in if let strongSelf = self { if let coordinate = coordinate { - strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + let replyMessageSubject = sourceMessageId.flatMap { + EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) + } + strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil)), threadId: nil, replyToMessageId: replyMessageSubject, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } else { strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root)) } @@ -3108,7 +3329,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } })]), in: .window(.root)) } - }, shareAccountContact: { [weak self] in + }, shareAccountContact: { [weak self] sourceMessageId in if let strongSelf = self { if case .pinnedMessages = strongSelf.presentationInterfaceState.subject { return @@ -3120,10 +3341,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmationTitle, text: strongSelf.presentationData.strings.Conversation_ShareBotContactConfirmation, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { if let strongSelf = self { - let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId) + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { peer in - if let peer = peer as? TelegramUser, let phone = peer.phone, !phone.isEmpty { - strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) + if case let .user(user) = peer, let phone = user.phone, !phone.isEmpty { + let replyMessageSubject = sourceMessageId.flatMap { + EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) + } + strongSelf.sendMessages([.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id, vCardData: nil)), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } }) } @@ -3145,7 +3376,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G postAsReply = true } - if let messageId = messageId, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let messageId = messageId, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { if let author = message.author as? TelegramUser, author.botInfo != nil { } else { postAsReply = false @@ -3174,7 +3405,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.sendMessages([.message(text: command, attributes: attributes, inlineStickers: [:], mediaReference: nil, threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyToMessageId, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]) } }, openInstantPage: { [weak self] message, associatedData in - if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { + if let strongSelf = self, strongSelf.isNodeLoaded, let navigationController = strongSelf.effectiveNavigationController, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id)?._asMessage() { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) { @@ -3186,7 +3417,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }, openWallpaper: { [weak self] message in - if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { + if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id)?._asMessage() { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() strongSelf.context.sharedContext.openChatWallpaper(context: strongSelf.context, message: message, present: { [weak self] c, a in @@ -3195,7 +3426,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } }, openTheme: { [weak self] message in - if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { + if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id)?._asMessage() { let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { strongSelf.chatDisplayNode.dismissInput() openChatTheme(context: strongSelf.context, message: message, pushController: { [weak self] c in @@ -3452,6 +3683,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return .none } } + if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject, case .quickReplyMessageInput = customChatContents.kind { + return .none + } if canReplyInChat(strongSelf.presentationInterfaceState, accountPeerId: strongSelf.context.account.peerId) { return .reply @@ -3519,7 +3753,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - let controller = makeContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message._asMessage(), selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) + let controller = makeContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) strongSelf.currentContextController = controller strongSelf.forEachController({ controller in if let controller = controller as? TooltipScreen { @@ -3607,7 +3841,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts() - let controller = makeContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: topMessage, selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) + let controller = makeContextController(presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatController: strongSelf, chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: EngineMessage(topMessage), selectAll: true)), items: .single(ContextController.Items(content: .list(actions))), recognizer: nil) strongSelf.currentContextController = controller strongSelf.forEachController({ controller in if let controller = controller as? TooltipScreen { @@ -3654,7 +3888,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id), controllerInteraction.pollActionState.pollMessageIdsInProgress[id] == nil { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id)?._asMessage(), controllerInteraction.pollActionState.pollMessageIdsInProgress[id] == nil { controllerInteraction.pollActionState.pollMessageIdsInProgress[id] = opaqueIdentifiers strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) let disposables: DisposableDict @@ -3683,7 +3917,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let resultPoll = resultPoll else { return } - guard let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else { + guard let _ = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id)?._asMessage() else { return } @@ -3752,20 +3986,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ) strongSelf.present(controller, in: .current) } - }, error: { _ in + }, error: { error in guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { return } if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) } + + switch error { + case .restrictedToSubscribers: + strongSelf.displayPollRestrictedToast(messageId: id) + default: + break + } }, completed: { guard let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction else { return } if controllerInteraction.pollActionState.pollMessageIdsInProgress.removeValue(forKey: id) != nil { Queue.mainQueue().after(1.0, { - strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) }) } @@ -3901,7 +4141,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let forwardInfo = message.forwardInfo, let sourceMessageId = forwardInfo.sourceMessageId, case let .replyThread(threadMessage) = strongSelf.chatLocation, threadMessage.isChannelPost { messageId = sourceMessageId } - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { let _ = strongSelf.controllerInteraction?.openMessage(message, OpenMessageParams(mode: .timecode(Double(timestamp)))) } else { strongSelf.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: Double(timestamp), quote: nil)), animated: true, forceInCurrentChat: true) @@ -3915,15 +4155,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard !self.presentAccountFrozenInfoIfNeeded(delay: true) else { return } - self.presentScheduleTimePicker(completion: { [weak self] time, repeatPeriod in + self.presentScheduleTimePicker(completion: { [weak self] result in if let strongSelf = self { if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { - strongSelf.sendMediaRecording(scheduleTime: time, messageEffect: (params?.effect).flatMap { + strongSelf.sendMediaRecording(silentPosting: result.silentPosting, scheduleTime: result.time, repeatPeriod: result.repeatPeriod, messageEffect: (params?.effect).flatMap { return ChatSendMessageEffect(id: $0.id) }) } else { - let silentPosting = strongSelf.presentationInterfaceState.interfaceState.silentPosting - strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, scheduleTime: time, repeatPeriod: repeatPeriod, messageEffect: (params?.effect).flatMap { + strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: result.silentPosting, scheduleTime: result.time, repeatPeriod: result.repeatPeriod, messageEffect: (params?.effect).flatMap { return ChatSendMessageEffect(id: $0.id) }) { [weak self] in if let strongSelf = self { @@ -3931,7 +4170,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil).withUpdatedForwardMessageIds(nil).withUpdatedForwardOptionsState(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } }) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -3966,13 +4205,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.presentScheduleTimePicker(selectedTime: message.timestamp, selectedRepeatPeriod: message._asMessage().scheduleRepeatPeriod, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { var entities: TextEntitiesMessageAttribute? + var richText: RichTextMessageAttribute? for attribute in message.attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { entities = attribute - break + } else if let attribute = attribute as? RichTextMessageAttribute { + richText = attribute } } - strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: [:], webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute(scheduleTime: time, repeatPeriod: repeatPeriod)) |> deliverOnMainQueue).startStrict(next: { result in + strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, richText: richText, inlineStickers: [:], webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute(scheduleTime: time, repeatPeriod: repeatPeriod)) |> deliverOnMainQueue).startStrict(next: { result in }, error: { error in })) } @@ -4087,7 +4328,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { self.push(await TextProcessingScreen( context: self.context, - mode: .translate(fromLanguage: language), + mode: .translate(fromLanguage: language, applyResult: nil), inputText: TextWithEntities(text: text.string, entities: entities ?? []), copyResult: canCopy ? { [weak self] text in guard let self else { @@ -4133,7 +4374,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } - if let messageId = message?.id, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) ?? message { + if let messageId = message?.id, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() ?? message { var quoteData: EngineMessageReplyQuote? let nsRange = NSRange(location: range.lowerBound, length: range.upperBound - range.lowerBound) @@ -4211,12 +4452,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) }) - }, openPollCreation: { [weak self] isQuiz in + }, openPollCreation: { [weak self] sourceMessageId, isQuiz in guard let self else { return } let _ = self.presentVoiceMessageDiscardAlert(action: { - if let controller = self.configurePollCreation(isQuiz: isQuiz) { + if let controller = self.configurePollCreation(sourceMessageId: sourceMessageId, isQuiz: isQuiz) { controller.navigationPresentation = .modal self.effectiveNavigationController?.pushViewController(controller) } @@ -4225,7 +4466,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - self.openPollMedia(message: message, subject: subject) + self.openPollMedia(message: EngineMessage(message), subject: subject) }, displayPollSolution: { [weak self] solution, sourceNode in guard let self else { return @@ -4234,7 +4475,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.displayPollSolution(solution: solution, sourceNode: sourceNode, isAutomatic: false) } else if let messageId = self.controllerInteraction?.currentPollMessageWithTooltip { self.controllerInteraction?.currentPollMessageWithTooltip = nil - self.controllerInteraction?.requestMessageUpdate(messageId, false) + self.controllerInteraction?.requestMessageUpdate(messageId, false, nil) } }, displayPsa: { [weak self] type, sourceNode in self?.displayPsa(type: type, sourceNode: sourceNode, isAutomatic: false) @@ -4436,8 +4677,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] { let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in + legacyMediaEditor(context: strongSelf.context, peer: EnginePeer(peer), threadTitle: strongSelf.contentData?.state.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: true) + }, photoToolbarView: { [context = strongSelf.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) }, sendMessagesWithSignals: { [weak self] signals, _, _, _ in if let strongSelf = self { strongSelf.interfaceInteraction?.setupEditMessage(messageId, { _ in }) @@ -4581,7 +4824,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } var message: Message? - if let historyMessage = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let historyMessage = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { message = historyMessage } else if let panelMessage = self.chatDisplayNode.adPanelMessage, panelMessage.id == messageId { message = panelMessage @@ -4613,7 +4856,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = AdsInfoScreen( context: context, mode: isBot ? .bot : .channel, - message: message + message: EngineMessage(message) ) controller.removeAd = { [weak self] opaqueId in self?.removeAd(opaqueId: opaqueId) @@ -5003,14 +5246,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.joinChannelDisposable.set(( self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peer.id, hash: nil) |> deliverOnMainQueue - |> afterCompleted { [weak self] in - Queue.mainQueue().async { - if let self { - self.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { _ in return false }), in: .current) - } - } + ).startStrict(next: { [weak self] result in + guard let self else { + return } - ).startStrict(error: { [weak self] error in + switch result { + case .joined: + self.present(UndoOverlayController(presentationData: presentationData, content: .succeed(text: presentationData.strings.Chat_SimilarChannels_JoinedChannel(peer.compactDisplayTitle).string, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { _ in return false }), in: .current) + case let .webView(webView): + self.context.sharedContext.openJoinChatWebView(context: self.context, parentController: self, updatedPresentationData: self.updatedPresentationData, webView: webView) + } + }, error: { [weak self] error in guard let self else { return } @@ -5087,7 +5333,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - self.playMessageEffect(message: message) + self.playMessageEffect(message: EngineMessage(message)) }, editMessageFactCheck: { [weak self] messageId in guard let self else { return @@ -5174,9 +5420,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(controller, in: .window(.root)) }) } - }, requestMessageUpdate: { [weak self] id, scroll in + }, requestMessageUpdate: { [weak self] id, scroll, customTransition in if let self { - self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll) + self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll, customTransition: customTransition) } }, cancelInteractiveKeyboardGestures: { [weak self] in if let self { @@ -5185,8 +5431,38 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, dismissTextInput: { [weak self] in self?.chatDisplayNode.dismissTextInput() - }, scrollToMessageId: { [weak self] index in - self?.chatDisplayNode.historyNode.scrollToMessage(index: index) + }, scrollToMessageId: { [weak self] index, offset in + self?.chatDisplayNode.historyNode.scrollToMessage(index: index, offset: offset) + }, scrollToMessageIdWithAnchor: { [weak self] index, anchor in + guard let self else { + return + } + // Find the bubble for this message (it must be at least partially + // visible — we got here from a tap inside it) and compute the + // anchor's y in item-local coords. .bottom(anchorY) places the item + // so the anchor lands at the visual top of the rotated chat list's + // content area; .center(.custom) is bypassed for short items, so it + // can't position the anchor uniformly. + var anchorY: CGFloat? + self.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in + guard anchorY == nil else { + return + } + if let itemNode = itemNode as? ChatMessageBubbleItemNode, + itemNode.item?.message.id == index.id, + let rect = itemNode.getAnchorRect(anchor: anchor) { + anchorY = rect.minY + } + } + if let anchorY { + self.chatDisplayNode.historyNode.scrollToMessage( + from: index, to: index, + animated: true, highlight: false, + scrollPosition: .bottom(anchorY) + ) + } else { + self.chatDisplayNode.historyNode.scrollToMessage(index: index) + } }, navigateToStory: { [weak self] message, storyId in guard let self else { return @@ -5373,7 +5649,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.dismissAllTooltips() - if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let _ = message.forwardInfo { + if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage(), let _ = message.forwardInfo { let controller = UndoOverlayController( presentationData: self.presentationData, content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: self.presentationData.strings.Chat_Todo_CompletionLimitedForward, customUndoText: nil, timeout: nil), @@ -5405,7 +5681,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } ) self.present(controller, in: .current) - } else if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + } else if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { var peerName = "" if let author = message.author { peerName = EnginePeer(author).compactDisplayTitle @@ -5521,6 +5797,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } self.interfaceInteraction?.openSetPeerAvatar() + }, displayPollRestrictedToast: { [weak self] messageId in + guard let self else { + return + } + self.displayPollRestrictedToast(messageId: messageId) }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: self.stickerSettings, presentationContext: ChatPresentationContext(context: context, backgroundNode: self.chatBackgroundNode)) controllerInteraction.enableFullTranslucency = context.sharedContext.energyUsageSettings.fullTranslucency @@ -7117,6 +7398,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + DispatchQueue.main.async { [weak self] in + guard let self else { + return + } + self.enableAnimations = true + } + if case let .replyThread(message) = self.chatLocation, message.isForumPost { if self.keepMessageCountersSyncrhonizedDisposable == nil { self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: message.peerId, threadId: message.threadId).startStrict() @@ -7490,12 +7778,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.peekTimerDisposable.set( (strongSelf.context.engine.peers.joinChatInteractively(with: peekData.linkData) - |> deliverOnMainQueue).startStrict(next: { peerId in + |> deliverOnMainQueue).startStrict(next: { [weak self] result in guard let strongSelf = self else { return } - if peerId == nil { - strongSelf.dismiss() + switch result { + case let .joined(peer): + if peer == nil { + strongSelf.dismiss() + } + case let .webView(webView): + strongSelf.context.sharedContext.openJoinChatWebView(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: strongSelf.updatedPresentationData, webView: webView) } }, error: { _ in guard let strongSelf = self else { @@ -8062,7 +8355,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let messageId = item.message.id self.controllerInteraction?.currentPollMessageWithTooltip = messageId - self.controllerInteraction?.requestMessageUpdate(messageId, false) + self.controllerInteraction?.requestMessageUpdate(messageId, false, nil) } public func displayPromoAnnouncement(text: String) { @@ -8563,9 +8856,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } } else { - self.presentScheduleTimePicker(style: media ? .media : .default, dismissByTapOutside: false, completion: { [weak self] time, repeatPeriod in + self.presentScheduleTimePicker(style: media ? .media : .default, dismissByTapOutside: false, completion: { [weak self] result in if let strongSelf = self { - strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: time, repeatPeriod: repeatPeriod, postpone: postpone), commit: true) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: result.silentPosting, scheduleTime: result.time, repeatPeriod: result.repeatPeriod, postpone: postpone), commit: true) } }) } @@ -8603,6 +8896,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G getAnimatedTransitionSource: ((String) -> UIView?)? = nil, completion: @escaping () -> Void = {} ) { + var animateTransition = true + if let validLayout = self.chatDisplayNode.validLayout?.0 { + if validLayout.metrics.widthClass != .compact { + animateTransition = false + } + } + self.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: self.context, account: self.context.account, signals: signals!, originalMediaReference: originalMediaReference) |> deliverOnMainQueue).startStrict(next: { [weak self] items in guard let strongSelf = self else { @@ -8629,6 +8929,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if shouldDivert { skipAddingTransitions = true } + if !animateTransition { + skipAddingTransitions = true + } for item in items { var message = item.message @@ -8689,10 +8992,58 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G mappedMessages.append(message) } + + let messages = strongSelf.transformEnqueueMessages(mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime, postpone: postpone) + let replyMessageSubject = replyToSubject ?? strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject + + var targetThreadId: Int64? + var clearMainThreadForward = false + if strongSelf.chatLocation.threadId == nil, let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasForum), botInfo.flags.contains(.forumManagedByUser) { + if let message = messages.first { + switch message { + case let .message(_, _, _, _, _, replyToMessageId, _, _, _, _): + if let _ = replyToMessageId { + if let replyMessage = strongSelf.presentationInterfaceState.replyMessage { + targetThreadId = replyMessage.threadId + } + } else { + targetThreadId = EngineMessage.newTopicThreadId + } + case let .forward(_, threadId, _, _, _): + if let threadId { + targetThreadId = threadId + } else { + targetThreadId = EngineMessage.newTopicThreadId + clearMainThreadForward = true + } + } + } + } - if addedTransitions.count > 1 { - var transitions: [(Int64, ChatMessageTransitionNodeImpl.Source, () -> Void)] = [] - for (correlationId, uniqueIds, initiated) in addedTransitions { + let addTransitionNodes: () -> Void = { + guard let strongSelf = self else { + return + } + + if addedTransitions.count > 1 { + var transitions: [(Int64, ChatMessageTransitionNodeImpl.Source, () -> Void)] = [] + for (correlationId, uniqueIds, initiated) in addedTransitions { + var source: ChatMessageTransitionNodeImpl.Source? + if uniqueIds.count > 1 { + source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: { + return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) }) + })) + } else if let uniqueId = uniqueIds.first { + source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: { + return getAnimatedTransitionSource?(uniqueId) + })) + } + if let source = source { + transitions.append((correlationId, source, initiated)) + } + } + strongSelf.chatDisplayNode.messageTransitionNode.add(grouped: transitions) + } else if let (correlationId, uniqueIds, initiated) = addedTransitions.first { var source: ChatMessageTransitionNodeImpl.Source? if uniqueIds.count > 1 { source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: { @@ -8704,26 +9055,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } if let source = source { - transitions.append((correlationId, source, initiated)) + strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: { + initiated() + }) } } - strongSelf.chatDisplayNode.messageTransitionNode.add(grouped: transitions) - } else if let (correlationId, uniqueIds, initiated) = addedTransitions.first { - var source: ChatMessageTransitionNodeImpl.Source? - if uniqueIds.count > 1 { - source = .groupedMediaInput(ChatMessageTransitionNodeImpl.Source.GroupedMediaInput(extractSnapshots: { - return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) }) - })) - } else if let uniqueId = uniqueIds.first { - source = .mediaInput(ChatMessageTransitionNodeImpl.Source.MediaInput(extractSnapshot: { - return getAnimatedTransitionSource?(uniqueId) - })) - } - if let source = source { - strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: { - initiated() - }) - } } if case let .customChatContents(customChatContents) = strongSelf.presentationInterfaceState.subject, let messageLimit = customChatContents.messageLimit { @@ -8740,21 +9076,50 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } } - - let messages = strongSelf.transformEnqueueMessages(mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime, postpone: postpone) - let replyMessageSubject = replyToSubject ?? strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ - if let strongSelf = self { - strongSelf.chatDisplayNode.collapseInput() - - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } - }) + + let doSend: (Int64?) -> Void = { [weak strongSelf] overrideThreadId in + guard let strongSelf else { + return } - completionImpl?() - }, usedCorrelationId) + + var messages = messages + if let overrideThreadId { + messages = messages.map { message in + return message.withUpdatedThreadId(overrideThreadId) + } + } + + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ + if let strongSelf = self { + strongSelf.chatDisplayNode.collapseInput() - strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }, media: true) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } + }) + } + completionImpl?() + }, usedCorrelationId) + + addTransitionNodes() + strongSelf.sendMessages(messages.map { $0.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel) }, media: true) + } + + if let targetThreadId { + strongSelf.chatDisplayNode.historyNode.stopHistoryUpdates() + strongSelf.updateChatLocationThread(threadId: targetThreadId, animationDirection: .right, transferInputState: true, completion: { [weak strongSelf] in + guard let strongSelf else { + return + } + doSend(targetThreadId) + if clearMainThreadForward, let peerId = strongSelf.chatLocation.peerId { + let _ = ChatInterfaceState.update(engine: strongSelf.context.engine, peerId: peerId, threadId: nil, { current in + return current.withUpdatedForwardMessageIds(nil) + }).startStandalone() + } + }) + } else { + doSend(nil) + } if let _ = scheduleTime { completion() @@ -8763,7 +9128,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true, postpone: Bool = false) { + func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, resetTextInputState: Bool = true, postpone: Bool = false) { if !canSendMessagesToChat(self.presentationInterfaceState) { return } @@ -8777,7 +9142,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G isScheduledMessages = true } - let sendMessage: (Int32?) -> Void = { [weak self] scheduleTime in + let sendMessage: (Int32?, Bool) -> Void = { [weak self] scheduleTime, silentPosting in guard let self else { return } @@ -8813,12 +9178,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - if isScheduledMessages { - self.presentScheduleTimePicker(style: .default, dismissByTapOutside: false, completion: { time, repeatPeriod in - sendMessage(time) + if let scheduleTime { + sendMessage(scheduleTime, silentPosting) + } else if isScheduledMessages { + self.presentScheduleTimePicker(style: .default, dismissByTapOutside: false, completion: { result in + sendMessage(result.time, result.silentPosting) }) } else { - sendMessage(nil) + sendMessage(nil, silentPosting) } } @@ -9009,8 +9376,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } else if let peerId = self.chatLocation.peerId { - resolveSignal = self.context.account.postbox.loadedPeerWithId(peerId) - |> map(Optional.init) + resolveSignal = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { Optional($0._asPeer()) } } else { resolveSignal = .single(nil) } @@ -9061,47 +9435,50 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func shareAccountContact() { - let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> deliverOnMainQueue).startStandalone(next: { [weak self] accountPeer in guard let strongSelf = self else { return } - guard let user = accountPeer as? TelegramUser, let phoneNumber = user.phone else { + guard case let .user(user) = accountPeer, let phoneNumber = user.phone else { return } guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser else { return } - - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - var items: [ActionSheetItem] = [] - items.append(ActionSheetTextItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumberConfirmation(formatPhoneNumber(context: strongSelf.context, number: phoneNumber), EnginePeer(peer).compactDisplayTitle).string)) - items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.engine.contacts.acceptAndShareContact(peerId: peer.id) - |> deliverOnMainQueue).startStandalone(error: { _ in - guard let strongSelf = self else { - return - } - strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - }, completed: { - guard let strongSelf = self else { - return - } - strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .genericSuccess(strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), in: .window(.root)) - }) - })) - - actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ])]) - strongSelf.chatDisplayNode.dismissInput() - strongSelf.present(actionSheet, in: .window(.root)) + let alertController = textAlertController( + context: strongSelf.context, + title: nil, + text: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumberConfirmation(formatPhoneNumber(context: strongSelf.context, number: phoneNumber), EnginePeer(peer).compactDisplayTitle).string, + actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber, action: { + guard let strongSelf = self else { + return + } + let _ = (strongSelf.context.engine.contacts.acceptAndShareContact(peerId: peer.id) + |> deliverOnMainQueue).startStandalone(error: { _ in + guard let strongSelf = self else { + return + } + strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + }, completed: { + guard let strongSelf = self else { + return + } + strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .genericSuccess(strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber_StatusSuccess(EnginePeer(peer).compactDisplayTitle).string, true)), in: .window(.root)) + }) + }) + ] + ) + strongSelf.present(alertController, in: .window(.root)) }) } @@ -9146,10 +9523,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard case let .peer(peerId) = self.chatLocation else { return } + let navigationTarget = self.effectiveNavigationController.flatMap { navigationController in + resolveChatListNavigationTarget(navigationController: navigationController, excluding: self) + } self.commitPurposefulAction() self.chatDisplayNode.historyNode.disconnect() let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: reportChatSpam).startStandalone() - self.effectiveNavigationController?.popToRoot(animated: true) + if let navigationController = self.effectiveNavigationController { + if let navigationTarget, let popToController = navigationTarget.popToController { + let _ = navigationController.popToViewController(popToController, animated: true) + } else { + navigationController.popToRoot(animated: true) + } + } let _ = self.context.engine.privacy.requestUpdatePeerIsBlocked(peerId: peerId, isBlocked: true).startStandalone() } @@ -9173,7 +9559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, progress: Promise? = nil, forceExternal: Bool = false, concealed: Bool = false, commit: @escaping () -> Void = {}) { let urlContext: OpenURLContext - let message = sourceMessageId.flatMap { self.chatDisplayNode.historyNode.messageInCurrentHistoryView($0) } + let message = sourceMessageId.flatMap { self.chatDisplayNode.historyNode.messageInCurrentHistoryView($0)?._asMessage() } if let peerId = self.chatLocation.peerId { urlContext = .chat(peerId: peerId, message: message, updatedPresentationData: self.updatedPresentationData) } else { @@ -9208,7 +9594,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G commit() case .info: dismissWebAppControllers() - strongSelf.navigationActionDisposable.set((strongSelf.context.account.postbox.loadedPeerWithId(peerId.id) + strongSelf.navigationActionDisposable.set((strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] peer in if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil { @@ -9332,17 +9725,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - let disposable = openUserGeneratedUrl(context: self.context, peerId: self.contentData?.state.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in + let disposable = self.context.sharedContext.openUserGeneratedUrl(context: self.context, peerId: self.contentData?.state.peerView?.peerId, url: url, webpage: nil, concealed: concealed, forceConcealed: false, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, forceDark: false, present: { [weak self] c in self?.present(c, in: .window(.root)) }, openResolved: { [weak self] resolved in self?.openResolved(result: resolved, sourceMessageId: message?.id, progress: progress, forceExternal: forceExternal, concealed: concealed, commit: commit) - }, progress: progress) + }, progress: progress, alertDisplayUpdated: nil, concealedAlertOption: nil) self.navigationActionDisposable.set(disposable) }, performAction: true) } func openUrlIn(_ url: String) { - let actionSheet = OpenInActionSheetController(context: self.context, updatedPresentationData: self.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in + let actionSheet = OpenInOptionsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, item: .url(url: url), openUrl: { [weak self] url in if let strongSelf = self, let navigationController = strongSelf.effectiveNavigationController { strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.presentationData, navigationController: navigationController, dismissInput: { self?.chatDisplayNode.dismissInput() @@ -9350,7 +9743,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }) self.chatDisplayNode.dismissInput() - self.present(actionSheet, in: .window(.root)) + self.push(actionSheet) } @available(iOSApplicationExtension 11.0, iOS 11.0, *) @@ -9475,7 +9868,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let tooltipController = self.mediaRecordingModeTooltipController { tooltipController.updateContent(.text(text), animated: true, extendTimer: true) - } else if let rect = rect { + } else if let rect { let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, padding: 2.0) self.mediaRecordingModeTooltipController = tooltipController tooltipController.dismissed = { [weak self, weak tooltipController] _ in @@ -9792,6 +10185,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.mediaRestrictedTooltipController?.dismiss() self.checksTooltipController?.dismiss() self.copyProtectionTooltipController?.dismiss() + self.guestChatMessageTooltipController?.dismiss() self.window?.forEachController({ controller in if let controller = controller as? UndoOverlayController { @@ -9836,11 +10230,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - public func getTransitionInfo(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? { + public func getTransitionInfo(messageId: EngineMessage.Id, media: EngineMedia) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? { var selectedNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? + let rawMedia = media._asMedia() self.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { - if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) { + if let result = itemNode.transitionNode(id: messageId, media: rawMedia, adjustRect: false) { selectedNode = result } } @@ -10070,7 +10465,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ) } - func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, selectedRepeatPeriod: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32, Int32?) -> Void) { + func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, selectedRepeatPeriod: Int32? = nil, dismissByTapOutside: Bool = true, presentInOverlay: Bool = false, completion: @escaping (Int32, Int32?) -> Void) { + self.presentScheduleTimePicker(style: style, selectedTime: selectedTime, selectedRepeatPeriod: selectedRepeatPeriod, dismissByTapOutside: dismissByTapOutside, presentInOverlay: presentInOverlay, completion: { result in + completion(result.time, result.repeatPeriod) + }) + } + + func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, selectedRepeatPeriod: Int32? = nil, dismissByTapOutside: Bool = true, presentInOverlay: Bool = false, completion: @escaping (ChatScheduleTimeScreen.Result) -> Void) { guard let peerId = self.chatLocation.peerId else { return } @@ -10092,7 +10493,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if peerId == strongSelf.context.account.peerId { mode = .reminders } else { - mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) + mode = .scheduledMessages(peerId: peer.id, sendWhenOnlineAvailable: sendWhenOnlineAvailable) } let controller = ChatScheduleTimeScreen( @@ -10101,42 +10502,61 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G currentTime: selectedTime, currentRepeatPeriod: selectedRepeatPeriod, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, + silentPosting: strongSelf.presentationInterfaceState.interfaceState.silentPosting, isDark: style == .media, completion: { result in - completion(result.time, result.repeatPeriod) + completion(result) } ) strongSelf.chatDisplayNode.dismissInput() - strongSelf.push(controller) + if presentInOverlay || strongSelf.videoRecorderValue != nil { + strongSelf.present(controller, in: .window(.root)) + } else { + strongSelf.push(controller) + } }) } - func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, completion: @escaping (Int32) -> Void) { guard case .peer = self.chatLocation else { return } - let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in + let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: style, currentTime: selectedTime, completion: { time in completion(time) }) self.chatDisplayNode.dismissInput() self.present(controller, in: .window(.root)) } - func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool { - if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { + func presentVoiceMessageDiscardAlert( + action: @escaping () -> Void = {}, + alertAction: (() -> Void)? = nil, + discardIfVideo: Bool = false, + delay: Bool = false, + performAction: Bool = true + ) -> Bool { + if let mediaRecordingState = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { + var discard = false + if discardIfVideo, case .video = mediaRecordingState { + discard = true + } alertAction?() Queue.mainQueue().after(delay ? 0.2 : 0.0) { let alertController = textAlertController( context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, - text: self.presentationData.strings.Conversation_StopVoiceMessageDescription, + text: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription : self.presentationData.strings.Conversation_StopVoiceMessageDescription, actions: [ TextAlertAction( type: .defaultAction, - title: self.presentationData.strings.Conversation_StopVoiceMessagePauseAction, + title: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction : self.presentationData.strings.Conversation_StopVoiceMessagePauseAction, action: { [weak self] in - self?.stopMediaRecorder(pause: true) + if discard { + self?.dismissMediaRecorder(.dismiss) + } else { + self?.stopMediaRecorder(pause: true) + } Queue.mainQueue().after(0.1) { action() } @@ -10161,32 +10581,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return false } - - func presentRecordedVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool { - if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState { - alertAction?() - Queue.mainQueue().after(delay ? 0.2 : 0.0) { - self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction, action: { [weak self] in - self?.stopMediaRecorder() - Queue.mainQueue().after(0.1) { - action() - } - })]), in: .window(.root)) - } - - return true - } else if performAction { - action() - } - return false - } - + func presentAutoremoveSetup() { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } - let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in + let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, completion: { [weak self] value in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index a8834e5866..9e70344c1b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -1,7 +1,7 @@ import Foundation +import UIKit import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -21,7 +21,7 @@ fileprivate struct InitialBannedRights { } extension ChatControllerImpl { - fileprivate func applyAdminUserActionsResult(messageIds: Set, result: AdminUserActionsSheet.ChatResult, initialUserBannedRights: [EnginePeer.Id: InitialBannedRights]) { + fileprivate func applyAdminUserActionsResult(messageIds: Set, reactionPeerId: EnginePeer.Id? = nil, result: AdminUserActionsSheet.ChatResult, initialUserBannedRights: [EnginePeer.Id: InitialBannedRights]) { guard let messagesPeerId = self.chatLocation.peerId else { return } @@ -30,7 +30,12 @@ extension ChatControllerImpl { } - var title: String? = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleMultiple + var title: String? + if let _ = reactionPeerId { + title = self.presentationData.strings.Chat_AdminAction_ToastReactionsDeletedTitleSingle + } else { + title = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleMultiple + } if !result.deleteAllFromPeers.isEmpty { title = self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleMultiple } @@ -67,13 +72,24 @@ extension ChatControllerImpl { } do { - let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() + if let reactionPeerId { + if let messageId = messageIds.first { + let _ = self.context.engine.messages.deleteReaction(messageId: messageId, authorId: reactionPeerId).startStandalone() + } + } else { + let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone() + } for authorId in result.deleteAllFromPeers { let _ = self.context.engine.messages.deleteAllMessagesWithAuthor(peerId: messagesPeerId, authorId: authorId, namespace: Namespaces.Message.Cloud).startStandalone() let _ = self.context.engine.messages.clearAuthorHistory(peerId: messagesPeerId, memberId: authorId).startStandalone() } + let aroundMessageId = messageIds.count == 1 ? messageIds.first : nil + for authorId in result.deleteAllReactionsFromPeers { + let _ = self.context.engine.messages.deleteAllReactionsWithAuthor(peerId: messagesPeerId, authorId: authorId, aroundMessageId: aroundMessageId).startStandalone() + } + for authorId in result.reportSpamPeers { let _ = self.context.engine.peers.reportPeer(peerId: authorId, reason: .spam, message: "").startStandalone() } @@ -88,9 +104,17 @@ extension ChatControllerImpl { } if text.isEmpty { - text = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextMultiple - if !result.deleteAllFromPeers.isEmpty { + if let _ = reactionPeerId { + text = self.presentationData.strings.Chat_AdminAction_ToastReactionsDeletedTextSingle + } else { + text = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextMultiple + } + if !result.deleteAllFromPeers.isEmpty && !result.deleteAllReactionsFromPeers.isEmpty { + text = self.presentationData.strings.Chat_AdminAction_ToastMessagesAndReactionsDeletedText + } else if !result.deleteAllFromPeers.isEmpty { text = self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextMultiple + } else if !result.deleteAllReactionsFromPeers.isEmpty { + text = self.presentationData.strings.Chat_AdminAction_ToastReactionsDeletedTextMultiple } title = nil } @@ -124,7 +148,7 @@ extension ChatControllerImpl { self.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) } - func presentMultiBanMessageOptions(accountPeerId: PeerId, authors: [Peer], messageIds: Set, options: ChatAvailableMessageActionOptions) { + func presentMultiBanMessageOptions(accountPeerId: EnginePeer.Id, authors: [EnginePeer], messageIds: Set, options: ChatAvailableMessageActionOptions) { guard let peerId = self.chatLocation.peerId else { return } @@ -139,7 +163,7 @@ extension ChatControllerImpl { var signal = combineLatest(authors.map { author in self.context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: author.id) - |> map { result -> (Peer, ChannelParticipant?) in + |> map { result -> (EnginePeer, ChannelParticipant?) in return (author, result) } }) @@ -203,10 +227,9 @@ extension ChatControllerImpl { ), rank: nil, subscriptionUntilDate: nil) } - let peer = author renderedParticipants.append(RenderedChannelParticipant( participant: participant, - peer: peer + peer: author )) switch participant { case .creator: @@ -238,7 +261,31 @@ extension ChatControllerImpl { })) } - func presentBanMessageOptions(accountPeerId: PeerId, author: Peer, messageIds: Set, options: ChatAvailableMessageActionOptions) { + public func presentReactionDeletionOptions(author: EnginePeer, messageId: EngineMessage.Id) { + guard self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudChannel, author.id != self.context.account.peerId else { + return + } + let _ = (self.context.sharedContext.chatAvailableMessageActions( + engine: self.context.engine, + accountPeerId: self.context.account.peerId, + messageIds: Set([messageId]), + keepUpdated: false + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] actions in + guard let self, !actions.options.isEmpty else { + return + } + self.presentBanMessageOptions( + accountPeerId: self.context.account.peerId, + author: author, + messageIds: Set([messageId]), + options: actions.options, + reaction: true + ) + }) + } + + func presentBanMessageOptions(accountPeerId: EnginePeer.Id, author: EnginePeer, messageIds: Set, options: ChatAvailableMessageActionOptions, reaction: Bool = false) { guard let peerId = self.chatLocation.peerId else { return } @@ -325,14 +372,17 @@ extension ChatControllerImpl { initialUserBannedRights[participant.peerId] = InitialBannedRights(value: nil) } } - self.push(AdminUserActionsSheet( - context: self.context, - chatPeer: chatPeer, - peers: [RenderedChannelParticipant( - participant: participant, - peer: authorPeer._asPeer() - )], - mode: .chat( + + let mode: AdminUserActionsSheet.Mode + if reaction { + mode = .chatReaction(completion: { [weak self] result in + guard let self else { + return + } + self.applyAdminUserActionsResult(messageIds: messageIds, reactionPeerId: authorPeer.id, result: result, initialUserBannedRights: initialUserBannedRights) + }) + } else { + mode = .chat( messageCount: messageIds.count, deleteAllMessageCount: deleteAllMessageCount, completion: { [weak self] result in @@ -342,12 +392,22 @@ extension ChatControllerImpl { self.applyAdminUserActionsResult(messageIds: messageIds, result: result, initialUserBannedRights: initialUserBannedRights) } ) + } + + self.push(AdminUserActionsSheet( + context: self.context, + chatPeer: chatPeer, + peers: [RenderedChannelParticipant( + participant: participant, + peer: authorPeer + )], + mode: mode )) }) })) } - func beginDeleteMessagesWithUndo(messageIds: Set, type: InteractiveMessagesDeletionType) { + func beginDeleteMessagesWithUndo(messageIds: Set, type: InteractiveMessagesDeletionType) { var deleteImmediately = false if case .forEveryone = type { deleteImmediately = true @@ -380,7 +440,7 @@ extension ChatControllerImpl { }), in: .current) } - func presentDeleteMessageOptions(messageIds: Set, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, completion: @escaping (ContextMenuActionResult) -> Void) { + func presentDeleteMessageOptions(messageIds: Set, options: ChatAvailableMessageActionOptions, contextController: ContextControllerProtocol?, sourceView: UIView? = nil, completion: @escaping (ContextMenuActionResult) -> Void) { let _ = (self.context.engine.data.get( EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:))) ) @@ -415,7 +475,7 @@ extension ChatControllerImpl { } self.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone) }), - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}) + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}) ], actionLayout: .vertical, parseMarkdown: true @@ -460,14 +520,16 @@ extension ChatControllerImpl { return } var entities: TextEntitiesMessageAttribute? + var richText: RichTextMessageAttribute? for attribute in message.attributes { if let attribute = attribute as? TextEntitiesMessageAttribute { entities = attribute - break + } else if let attribute = attribute as? RichTextMessageAttribute { + richText = attribute } } let scheduleTime = message.timestamp + repeatAttribute.repeatPeriod - self.editMessageDisposable.set((self.context.engine.messages.requestEditMessage(messageId: message.id, text: message.text, media: .keep, entities: entities, inlineStickers: [:], webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: repeatAttribute.repeatPeriod)) |> deliverOnMainQueue).startStrict(next: { result in + self.editMessageDisposable.set((self.context.engine.messages.requestEditMessage(messageId: message.id, text: message.text, media: .keep, entities: entities, richText: richText, inlineStickers: [:], webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: repeatAttribute.repeatPeriod)) |> deliverOnMainQueue).startStrict(next: { result in }, error: { error in })) }), @@ -477,7 +539,7 @@ extension ChatControllerImpl { } self.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone) }), - TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}) + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}) ], actionLayout: .vertical, parseMarkdown: true @@ -503,7 +565,17 @@ extension ChatControllerImpl { isChannel = true } + var contextItems: [ContextMenuItem] = [] + var canDisplayContextMenu = true + if options.contains(.cancelSending) { + contextItems.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuCancelSending, textColor: .destructive, icon: { _ in nil }, action: { [weak self] _, f in + f(.dismissWithoutContent) + if let strongSelf = self { + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) + strongSelf.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone) + } + }))) items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ContextMenuCancelSending, color: .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() if let strongSelf = self { @@ -513,9 +585,6 @@ extension ChatControllerImpl { })) } - var contextItems: [ContextMenuItem] = [] - var canDisplayContextMenu = true - var unsendPersonalMessages = false if options.contains(.unsendPersonal) { canDisplayContextMenu = false @@ -536,7 +605,7 @@ extension ChatControllerImpl { if let strongSelf = self { var giveaway: TelegramMediaGiveaway? for messageId in messageIds { - if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { + if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage() { if let media = message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway { giveaway = media break @@ -555,7 +624,7 @@ extension ChatControllerImpl { let dateString = stringForDate(timestamp: giveaway.untilDate, timeZone: .current, strings: strongSelf.presentationData.strings) strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Title, text: strongSelf.presentationData.strings.Chat_Giveaway_DeleteConfirmation_Text(dateString).string, actions: [TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.Common_Delete, action: { commit() - }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { })], parseMarkdown: true), in: .window(.root)) } f(.default) @@ -644,6 +713,16 @@ extension ChatControllerImpl { if canDisplayContextMenu, let contextController = contextController { contextController.setItems(.single(ContextController.Items(content: .list(contextItems))), minHeight: nil, animated: true) + } else if canDisplayContextMenu, let sourceView = sourceView { + let contextController = makeContextController( + presentationData: self.presentationData, + source: .reference(ChatControllerContextReferenceContentSource(controller: self, sourceView: sourceView, insets: .zero, actionsOnTop: true)), + items: .single(ContextController.Items(content: .list(contextItems))), + gesture: nil + ) + self.chatDisplayNode.dismissInput() + self.presentInGlobalOverlay(contextController) + completion(.default) } else { actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in @@ -724,7 +803,7 @@ extension ChatControllerImpl { chatPeer: chatPeer, peers: [RenderedChannelParticipant( participant: participant, - peer: authorPeer._asPeer() + peer: authorPeer )], mode: .monoforum(completion: { [weak self] result in guard let self else { diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index 0d6d1b2b6d..8e437535ed 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -147,6 +147,7 @@ extension ChatControllerImpl { var removePaidMessageFeeData: ChatPresentationInterfaceState.RemovePaidMessageFeeData? var viewForumAsMessages: Bool = false var hasTopics: Bool = false + var canStopIncomingStreamingMessage: Bool = false var preloadNextChatPeerId: EnginePeer.Id? } @@ -319,7 +320,7 @@ extension ChatControllerImpl { return (nil, value) } } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId) |> map { value -> (total: Int32?, recent: Int32?) in return (value.total, value.recent) } @@ -602,18 +603,17 @@ extension ChatControllerImpl { notificationSettings: nil, peerPresences: [:], cachedData: nil - ), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true) + ), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, hidePeerStatus: false, isEnabled: true) } else { strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(channel.debugDisplayTitle))], subtitle: nil, isEnabled: true) } } else { var customSubtitle: String? if savedMessagesChatsTip { - //TODO:localize - customSubtitle = "Tap to view as chats" + customSubtitle = strings.Chat_SavedMessagesStatusViewAsChats } - strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, hidePeerStatus: false, isEnabled: hasPeerInfo) let imageOverride: AvatarNodeImageOverride? if context.account.peerId == peer.id { @@ -652,16 +652,9 @@ extension ChatControllerImpl { let threadInfo: Signal if let threadId = chatLocation.threadId { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadInfo = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> EngineMessageHistoryThread.Info? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { - return nil - } - return data.info + threadInfo = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: threadId)) + |> map { threadData -> EngineMessageHistoryThread.Info? in + return threadData?.info } |> distinctUntilChanged } else { @@ -712,6 +705,21 @@ extension ChatControllerImpl { } let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) + + let canStopIncomingStreamingMessage: Signal = .single(false) + /*if let peerId = chatLocation.peerId { + let key = PeerAndThreadId(peerId: peerId, threadId: chatLocation.threadId) + canStopIncomingStreamingMessage = context.account.postbox.combinedView(keys: [PostboxViewKey.typingDrafts(key)]) + |> map { views -> Bool in + guard let view = views.views[PostboxViewKey.typingDrafts(key)] as? TypingDraftsView else { + return false + } + return view.typingDraft != nil + } + |> distinctUntilChanged + } else { + canStopIncomingStreamingMessage = .single(false) + }*/ self.peerDisposable = combineLatest( queue: Queue.mainQueue(), @@ -728,8 +736,9 @@ extension ChatControllerImpl { managingBot, adMessage, displayedPeerVerification, - globalPrivacySettings - ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, hasTopics, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, adMessage, displayedPeerVerification, globalPrivacySettings in + globalPrivacySettings, + canStopIncomingStreamingMessage + ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, hasTopics, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, adMessage, displayedPeerVerification, globalPrivacySettings, canStopIncomingStreamingMessage in guard let strongSelf = self else { return } @@ -739,6 +748,7 @@ extension ChatControllerImpl { if strongSelf.state.peerView === peerView && strongSelf.state.hasScheduledMessages == hasScheduledMessages && strongSelf.state.hasTopics == hasTopics + && strongSelf.state.canStopIncomingStreamingMessage == canStopIncomingStreamingMessage && strongSelf.state.threadInfo == threadInfo && strongSelf.state.hasSearchTags == hasSearchTags && strongSelf.state.hasSavedChats == hasSavedChats @@ -750,6 +760,7 @@ extension ChatControllerImpl { strongSelf.state.hasScheduledMessages = hasScheduledMessages strongSelf.state.hasTopics = hasTopics + strongSelf.state.canStopIncomingStreamingMessage = canStopIncomingStreamingMessage var upgradedToPeerId: PeerId? var movedToForumTopics = false @@ -793,8 +804,8 @@ extension ChatControllerImpl { strongSelf.state.threadInfo = threadInfo if wasGroupChannel != isGroupChannel { if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) let disposable = DisposableSet() disposable.add(recentDisposable) disposable.add(adminsDisposable) @@ -1058,6 +1069,7 @@ extension ChatControllerImpl { strongSelf.state.explicitelyCanPinMessages = explicitelyCanPinMessages strongSelf.state.hasScheduledMessages = hasScheduledMessages strongSelf.state.hasTopics = hasTopics + strongSelf.state.canStopIncomingStreamingMessage = canStopIncomingStreamingMessage strongSelf.state.autoremoveTimeout = autoremoveTimeout strongSelf.state.currentSendAsPeerId = currentSendAsPeerId strongSelf.state.copyProtectionEnabled = copyProtectionEnabled @@ -1225,26 +1237,24 @@ extension ChatControllerImpl { guard let replyThreadId = replyThreadId else { return .single((message, nil, 0)) } - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: replyThreadId) - let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Cloud, customTag: nil) - let localCountViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Local, customTag: nil) - return context.account.postbox.combinedView(keys: [viewKey, countViewKey, localCountViewKey]) - |> map { views -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return (message, nil, 0) - } + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: replyThreadId), + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: replyThreadId, tag: MessageTags()), + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: replyThreadId, tag: MessageTags(), namespace: Namespaces.Message.Local) + ) + |> map { threadData, cloudCount, localCount -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in var messageCount = 0 - if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { + if let count = cloudCount { if replyThreadId == 1 { - messageCount += Int(count) + messageCount += count } else { - messageCount += max(Int(count) - 1, 0) + messageCount += max(count - 1, 0) } } - if let summaryView = views.views[localCountViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { - messageCount += Int(count) + if let count = localCount { + messageCount += count } - return (message, view.info?.data.get(MessageHistoryThreadData.self), messageCount) + return (message, threadData, messageCount) } } @@ -1258,29 +1268,16 @@ extension ChatControllerImpl { let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?, isMonoforumFeeRemoved: Bool)?, NoError> if let savedMessagesPeerId { let threadPeerId = savedMessagesPeerId - let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: []) - let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil) - let threadInfoKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: savedMessagesPeerId.toInt64()) - - savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey, threadInfoKey]) - |> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?, isMonoforumFeeRemoved: Bool)? in - var peer: EnginePeer? - var presence: EnginePeer.Presence? - if let peerView = views.views[basicPeerKey] as? PeerView { - peer = peerViewMainPeer(peerView).flatMap(EnginePeer.init) - presence = peerView.peerPresences[threadPeerId].flatMap(EnginePeer.Presence.init) - } - - var messageCount = 0 - if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count { - messageCount += Int(count) - } - - var isMonoforumFeeRemoved = false - if let threadInfoView = views.views[threadInfoKey] as? MessageHistoryThreadInfoView, let threadInfo = threadInfoView.info?.data.get(MessageHistoryThreadData.self) { - isMonoforumFeeRemoved = threadInfo.isMessageFeeRemoved - } - + + savedMessagesPeer = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.MainPeer(id: threadPeerId), + TelegramEngine.EngineData.Item.Peer.Presence(id: threadPeerId), + TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: peerId, threadId: savedMessagesPeerId.toInt64(), tag: MessageTags()), + TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: savedMessagesPeerId.toInt64()) + ) + |> map { peer, presence, count, threadInfo -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?, isMonoforumFeeRemoved: Bool)? in + let messageCount = count.flatMap(Int.init) ?? 0 + let isMonoforumFeeRemoved = threadInfo?.isMessageFeeRemoved ?? false return (peer, messageCount, presence, isMonoforumFeeRemoved) } |> distinctUntilChanged(isEqual: { lhs, rhs in @@ -1361,7 +1358,7 @@ extension ChatControllerImpl { return (nil, value) } } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerId) |> map { value -> (total: Int32?, recent: Int32?) in return (value.total, value.recent) } @@ -1403,6 +1400,21 @@ extension ChatControllerImpl { let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy()) + let canStopIncomingStreamingMessage: Signal = .single(false) + /*if let peerId = chatLocation.peerId { + let key = PeerAndThreadId(peerId: peerId, threadId: chatLocation.threadId) + canStopIncomingStreamingMessage = context.account.postbox.combinedView(keys: [PostboxViewKey.typingDrafts(key)]) + |> map { views -> Bool in + guard let view = views.views[PostboxViewKey.typingDrafts(key)] as? TypingDraftsView else { + return false + } + return view.typingDraft != nil + } + |> distinctUntilChanged + } else { + canStopIncomingStreamingMessage = .single(false) + }*/ + self.peerDisposable = (combineLatest(queue: Queue.mainQueue(), peerView, messageAndTopic, @@ -1413,9 +1425,10 @@ extension ChatControllerImpl { hasSavedChats, isPremiumRequiredForMessaging, managingBot, - globalPrivacySettings + globalPrivacySettings, + canStopIncomingStreamingMessage ) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings, canStopIncomingStreamingMessage in guard let strongSelf = self else { return } @@ -1491,6 +1504,7 @@ extension ChatControllerImpl { } strongSelf.state.hasTopics = true + strongSelf.state.canStopIncomingStreamingMessage = canStopIncomingStreamingMessage if let savedMessagesPeerId { var peerPresences: [PeerId: PeerPresence] = [:] @@ -1519,7 +1533,7 @@ extension ChatControllerImpl { customMessageCount = savedMessagesPeer?.messageCount ?? 0 } - strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true) + strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, hidePeerStatus: false, isEnabled: true) strongSelf.state.peerView = peerView @@ -1613,7 +1627,7 @@ extension ChatControllerImpl { } } - strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, hidePeerStatus: true, isEnabled: true) let avatarContent: EmojiStatusComponent.Content if chatLocation.threadId == 1 { @@ -1658,8 +1672,8 @@ extension ChatControllerImpl { if wasGroupChannel != isGroupChannel { if let isGroupChannel = isGroupChannel, isGroupChannel { - let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) - let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) + let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in }) let disposable = DisposableSet() disposable.add(recentDisposable) disposable.add(adminsDisposable) @@ -1908,7 +1922,7 @@ extension ChatControllerImpl { canBypassRestrictions = true } if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { - if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { + if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(untilTimestamp: cachedData.slowModeValidUntilTimestamp) { slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) } } @@ -2098,13 +2112,9 @@ extension ChatControllerImpl { let threadData: Signal let forumTopicData: Signal if let threadId = chatLocation.threadId { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId) - threadData = context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> ChatPresentationInterfaceState.ThreadData? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { + threadData = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: threadId)) + |> map { threadData -> ChatPresentationInterfaceState.ThreadData? in + guard let data = threadData else { return nil } return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) @@ -2115,13 +2125,9 @@ extension ChatControllerImpl { forumTopicData = isForum |> mapToSignal { isForum -> Signal in if isForum { - let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: 1) - return context.account.postbox.combinedView(keys: [viewKey]) - |> map { views -> ChatPresentationInterfaceState.ThreadData? in - guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else { - return nil - } - guard let data = view.info?.data.get(MessageHistoryThreadData.self) else { + return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.ThreadInfo(peerId: peerId, threadId: 1)) + |> map { threadData -> ChatPresentationInterfaceState.ThreadData? in + guard let data = threadData else { return nil } return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed) @@ -2262,7 +2268,7 @@ extension ChatControllerImpl { } else { cachedData = .single((nil, [:])) } - + self.cachedDataDisposable?.dispose() self.cachedDataDisposable = combineLatest(queue: .mainQueue(), cachedData, @@ -2314,7 +2320,7 @@ extension ChatControllerImpl { if let channel = strongSelf.state.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { if hasPendingMessages { slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages) - } else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) { + } else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(untilTimestamp: cachedData.slowModeValidUntilTimestamp) { slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerDisplayBusinessBotMessageTooltip.swift b/submodules/TelegramUI/Sources/ChatControllerDisplayBusinessBotMessageTooltip.swift index a08466b392..e28d67da18 100644 --- a/submodules/TelegramUI/Sources/ChatControllerDisplayBusinessBotMessageTooltip.swift +++ b/submodules/TelegramUI/Sources/ChatControllerDisplayBusinessBotMessageTooltip.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift b/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift index 6b59404eac..39c29d2f35 100644 --- a/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift +++ b/submodules/TelegramUI/Sources/ChatControllerDisplayDiceTooltip.swift @@ -1,6 +1,5 @@ import Foundation import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/TelegramUI/Sources/ChatControllerDisplayGuestChatMessageTooltip.swift b/submodules/TelegramUI/Sources/ChatControllerDisplayGuestChatMessageTooltip.swift new file mode 100644 index 0000000000..adcaf3a277 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerDisplayGuestChatMessageTooltip.swift @@ -0,0 +1,58 @@ +import Foundation +import TelegramPresentationData +import AccountContext +import TelegramCore +import SwiftSignalKit +import Display +import PresentationDataUtils +import ChatMessageItemView +import TelegramNotices +import TooltipUI + +extension ChatControllerImpl { + func displayGuestChatMessageTooltip(itemNode: ChatMessageItemView) { + let _ = (ApplicationSpecificNotice.getGuestChatMessageTooltip(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak itemNode] value in + guard let self, let itemNode else { + return + } + + if value >= 2 { + return + } + + guard let sourceNode = itemNode.getAuthorNameNode() else { + return + } + + Queue.mainQueue().after(0.5) { + let sourceRect = sourceNode.view.convert(sourceNode.view.bounds, to: nil) + + self.messageTooltipController?.dismiss() + self.guestChatMessageTooltipController?.dismiss() + + let tooltipScreen = TooltipScreen( + account: self.context.account, + sharedContext: self.context.sharedContext, + text: .plain(text: self.presentationData.strings.Chat_GuestChatMessageTooltip), + balancedTextLayout: true, + location: .point(sourceRect, .bottom), + displayDuration: .custom(3.5), + shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + } + ) + self.guestChatMessageTooltipController = tooltipScreen + tooltipScreen.becameDismissed = { [weak self, weak tooltipScreen] _ in + if let strongSelf = self, let tooltipScreen, strongSelf.guestChatMessageTooltipController === tooltipScreen { + strongSelf.guestChatMessageTooltipController = nil + } + } + + self.present(tooltipScreen, in: .current) + + let _ = ApplicationSpecificNotice.incrementGuestChatMessageTooltip(accountManager: self.context.sharedContext.accountManager).startStandalone() + } + }) + } +} diff --git a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift index c1ffb6bcd6..ae9fc6719c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift +++ b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 6913d4cbb1..ededfd221f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -16,7 +15,7 @@ import TopMessageReactions import ChatMessagePaymentAlertController extension ChatControllerImpl { - func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) { + func forwardMessages(messageIds: [EngineMessage.Id], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) { let _ = (self.context.engine.data.get(EngineDataMap( messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init) )) @@ -28,7 +27,7 @@ extension ChatControllerImpl { }) } - func forwardMessages(messages: [Message], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool) { + func forwardMessages(messages: [EngineRawMessage], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool) { let _ = self.presentVoiceMessageDiscardAlert(action: { var filter: ChatListNodePeersFilter = [.onlyWriteable, .excludeDisabled, .doNotSearchMessages] var hasPublicPolls = false @@ -143,7 +142,7 @@ extension ChatControllerImpl { let inputText = convertMarkdownToAttributes(messageText) for text in breakChatInputText(trimChatInputText(inputText)) { if text.length != 0 { - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) @@ -153,7 +152,7 @@ extension ChatControllerImpl { } } - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true)) result.append(contentsOf: messages.map { message -> EnqueueMessage in @@ -317,14 +316,14 @@ extension ChatControllerImpl { let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) commit(transformedMessages) case .schedule: - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in + strongSelf.presentScheduleTimePicker(completion: { [weak self] timeResult in if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod) + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: timeResult.silentPosting, scheduleTime: timeResult.time, repeatPeriod: timeResult.repeatPeriod) commit(transformedMessages) } }) case .whenOnline: - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleWhenOnlineTimestamp) + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: strongSelf.presentationInterfaceState.interfaceState.silentPosting, scheduleTime: scheduleWhenOnlineTimestamp) commit(transformedMessages) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerKeyShortcuts.swift b/submodules/TelegramUI/Sources/ChatControllerKeyShortcuts.swift index c52cd0b417..20a936bf7c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerKeyShortcuts.swift +++ b/submodules/TelegramUI/Sources/ChatControllerKeyShortcuts.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import ChatInterfaceState import TelegramCore import SwiftSignalKit diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index fb25659d4f..049bd55562 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -67,6 +67,7 @@ import PresentationDataUtils import TextProcessingScreen import Pasteboard import UndoUI +import BrowserUI final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -183,6 +184,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var scrollContainerNode: ScrollContainerNode? private var containerNode: ASDisplayNode? private var overlayNavigationBar: ChatOverlayNavigationBar? + private var contextTransitionContainer: UIView? var overlayTitle: String? { didSet { @@ -385,7 +387,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } else { loadingPlaceholderNode = ChatLoadingPlaceholderNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners, backgroundNode: self.backgroundNode) - loadingPlaceholderNode.updatePresentationInterfaceState(renderedPeer: self.chatPresentationInterfaceState.renderedPeer, chatLocation: self.chatLocation) + loadingPlaceholderNode.updatePresentationInterfaceState(renderedPeer: self.chatPresentationInterfaceState.renderedPeer.flatMap(EngineRenderedPeer.init), chatLocation: self.chatLocation) self.backgroundNode.supernode?.insertSubnode(loadingPlaceholderNode, aboveSubnode: self.backgroundNode) self.loadingPlaceholderNode = loadingPlaceholderNode @@ -493,7 +495,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if case let .messageOptions(_, messageIds, info) = subject { switch info { case let .forward(forward): - let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId), forward.options) + let accountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), accountPeerSignal, forward.options) |> map { messages, accountPeer, options -> ([Message], Int32, Bool) in var messages = messages let forwardedMessageIds = Set(messages.map { $0.id }) @@ -555,7 +566,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if let poll = media as? TelegramMediaPoll { var updatedMedia = message.media.filter { !($0 is TelegramMediaPoll) } - updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, textEntities: poll.textEntities, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil, hasUnseenVotes: false), isClosed: false, deadlineTimeout: nil, deadlineDate: nil, pollHash: poll.pollHash)) + updatedMedia.append(TelegramMediaPoll(pollId: poll.pollId, publicity: poll.publicity, kind: poll.kind, text: poll.text, textEntities: poll.textEntities, options: poll.options, correctAnswers: poll.correctAnswers, results: TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil, hasUnseenVotes: false, canViewStats: false), isClosed: false, deadlineTimeout: nil, deadlineDate: nil, pollHash: poll.pollHash)) messageMedia = updatedMedia } if let _ = media as? TelegramMediaDice { @@ -582,7 +593,16 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } source = .custom(messages: messages, messageId: MessageId(peerId: PeerId(0), namespace: 0, id: 0), quote: nil, isSavedMusic: false, canReorder: false, loadMore: nil) case let .reply(reply): - let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), context.account.postbox.loadedPeerWithId(context.account.peerId)) + let replyAccountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } + let messages = combineLatest(context.account.postbox.messagesAtIds(messageIds), replyAccountPeerSignal) |> map { messages, accountPeer -> ([Message], Int32, Bool) in var messages = messages messages.sort(by: { lhsMessage, rhsMessage in @@ -611,10 +631,19 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { stories = .single([:]) } + let linkAccountPeerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } + |> map { $0._asPeer() } if let replyMessageId = options.replyMessageId { return combineLatest( context.account.postbox.messagesAtIds([replyMessageId]), - context.account.postbox.loadedPeerWithId(context.account.peerId), + linkAccountPeerSignal, stories ) |> map { messages, peer, stories -> (ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]) in @@ -622,7 +651,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } else { return combineLatest( - context.account.postbox.loadedPeerWithId(context.account.peerId), + linkAccountPeerSignal, stories ) |> map { peer, stories -> (ChatControllerSubject.LinkOptions, Peer, Message?, [StoryId: CodableEntry]) in @@ -833,9 +862,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.setupHistoryNode() - self.interactiveEmojisDisposable = (self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + self.interactiveEmojisDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> InteractiveEmojiConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return InteractiveEmojiConfiguration.with(appConfiguration: appConfiguration) } |> deliverOnMainQueue).startStrict(next: { [weak self] emojis in @@ -1395,6 +1424,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { data: mediaPlayback, controller: { [weak self] in return self?.controller + }, + shouldPerformAction: { [weak self] action in + guard let controller = self?.controller else { + action() + return + } + let _ = controller.presentVoiceMessageDiscardAlert(action: action) } ))) ) @@ -1648,6 +1684,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else if let headerPanelsView = self.headerPanelsView { self.headerPanelsView = nil if let headerPanelsComponentView = headerPanelsView.view { + transition.updateTransformScale(layer: headerPanelsComponentView.layer, scale: 0.001) transition.updateAlpha(layer: headerPanelsComponentView.layer, alpha: 0.0, completion: { [weak headerPanelsComponentView] _ in headerPanelsComponentView?.removeFromSuperview() }) @@ -1756,7 +1793,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if inputTextPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) } - let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) } if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) { inputPanelNodeHandlesTransition = true @@ -1768,7 +1805,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { dismissedInputPanelNode = self.inputPanelNode } - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.inputPanelNode = inputPanelNode if inputPanelNode.supernode !== self { @@ -1779,7 +1816,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -1790,7 +1827,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing { if secondaryInputPanelNode !== self.secondaryInputPanelNode { dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.secondaryInputPanelNode = secondaryInputPanelNode if secondaryInputPanelNode.supernode == nil { @@ -1801,7 +1838,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: inputPanelBottomInset, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -2051,6 +2088,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { messageTransitionNode.transform = previousMessageTransitionNode.transform previousMessageTransitionNode.supernode?.insertSubnode(self.messageTransitionNode, aboveSubnode: previousMessageTransitionNode) + previousMessageTransitionNode.overlayContainerNode.supernode?.insertSubnode( self.messageTransitionNode.overlayContainerNode, aboveSubnode: previousMessageTransitionNode.overlayContainerNode) self.emptyType = nil self.isLoadingValue = false @@ -2224,7 +2262,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { additionalOffset = 80.0 } if let _ = inputPanelSize { - inputPanelHideOffset += -48.0 - additionalOffset + inputPanelHideOffset += -56.0 - additionalOffset } if let accessoryPanelSize = accessoryPanelSize { inputPanelHideOffset += -accessoryPanelSize.height - additionalOffset @@ -2458,6 +2496,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { listInsets.left += floatingTopicsPanelInsets.left listInsets.bottom += floatingTopicsPanelInsets.top + childContentInsets.top = listInsets.bottom + var emptyNodeInsets = insets emptyNodeInsets.bottom += inputPanelsHeight self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets, listInsets.left, listInsets.right) @@ -2645,6 +2685,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var headerPanelsTransition = ComponentTransition(transition) if headerPanelsComponentView.superview == nil { headerPanelsTransition.animateAlpha(view: headerPanelsComponentView, from: 0.0, to: 1.0) + headerPanelsTransition.animateScale(view: headerPanelsComponentView, from: 0.001, to: 1.0) headerPanelsTransition = headerPanelsTransition.withAnimation(.none) self.floatingTopicsPanelContainer.view.addSubview(headerPanelsComponentView) } @@ -3489,7 +3530,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } self.derivedLayoutState = ChatControllerNodeDerivedLayoutState(inputContextPanelsFrame: inputContextPanelsFrame, inputContextPanelsOverMainPanelFrame: inputContextPanelsOverMainPanelFrame, inputNodeHeight: inputNodeHeightAndOverflow?.0, inputNodeAdditionalHeight: inputNodeHeightAndOverflow?.1, upperInputPositionBound: inputNodeHeightAndOverflow?.0 != nil ? self.upperInputPositionBound : nil) - + + if let contextTransitionContainer = self.contextTransitionContainer { + contextTransitionContainer.frame = self.view.convert(self.frameForVisibleArea(), to: self.contentContainerNode.contentNode.view) + } + //self.notifyTransitionCompletionListeners(transition: transition) } @@ -3591,7 +3636,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) if self.pendingSwitchToChatLocation == nil { - self.loadingPlaceholderNode?.updatePresentationInterfaceState(renderedPeer: chatPresentationInterfaceState.renderedPeer, chatLocation: self.chatLocation) + self.loadingPlaceholderNode?.updatePresentationInterfaceState(renderedPeer: chatPresentationInterfaceState.renderedPeer.flatMap(EngineRenderedPeer.init), chatLocation: self.chatLocation) } var updatedInputFocus = self.chatPresentationInterfaceStateRequiresInputFocus(self.chatPresentationInterfaceState) != self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState) @@ -3996,6 +4041,37 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.textInputPanelNode?.isMediaDeleted = isDeleted } + func ensureContextTransitionContainer() -> UIView? { + // Frames are expressed in self.view coords by frameForVisibleArea(), but the + // container lives inside self.contentContainerNode.contentNode.view (the + // direct parent of historyNodeContainer) so chat-side ancestor clipping + // applies and chrome above contentContainerNode (input panel, nav, etc.) + // renders over it. Convert at the boundary. + // + // In overlay chat mode (self.containerNode != nil) historyNodeContainer is + // reparented out of contentContainerNode (see line ~1299), making the + // `aboveSubview: historyNodeContainer.view` insertion invalid. Return nil + // so callers fall back to CCEPN's clipping path — portal-style transitions + // are not supported in overlay mode. + guard self.containerNode == nil else { return nil } + let parent = self.contentContainerNode.contentNode.view + let frame = self.view.convert(self.frameForVisibleArea(), to: parent) + if let existing = self.contextTransitionContainer { + existing.frame = frame + return existing + } + let container = UIView() + // No clipsToBounds: the source-side wrapper is faded out via alpha during the + // crossfade, so we don't rely on clipping to hide it. Clipping the wrapper + // would also clip the iOS portal mirror (which reflects ancestor clipping), + // producing visibly clipped pixels in the clone at intermediate positions. + container.isUserInteractionEnabled = false + container.frame = frame + parent.insertSubview(container, aboveSubview: self.historyNodeContainer.view) + self.contextTransitionContainer = container + return container + } + func frameForVisibleArea() -> CGRect { var rect = CGRect(origin: CGPoint(x: self.visibleAreaInset.left, y: self.visibleAreaInset.top), size: CGSize(width: self.bounds.size.width - self.visibleAreaInset.left - self.visibleAreaInset.right, height: self.bounds.size.height - self.visibleAreaInset.top - self.visibleAreaInset.bottom)) if let inputContextPanelNode = self.inputContextPanelNode, let topItemFrame = inputContextPanelNode.topItemFrame { @@ -4019,8 +4095,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { func frameForInputActionButton() -> CGRect? { if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode { - return textInputPanelNode.frameForInputActionButton().flatMap { - return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY) + return textInputPanelNode.frameForInputActionButton().flatMap { rect in + return self.view.convert(rect, from: textInputPanelNode.view) } } return nil @@ -4771,8 +4847,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let editMessageState = effectivePresentationInterfaceState.editMessageState, let mediaReferenceValue = editMessageState.mediaReference { mediaReference = mediaReferenceValue } else { - if let message = self.historyNode.messageInCurrentHistoryView(editingOriginalMessageId) { - for media in message.media { + if let message = self.historyNode.messageInCurrentHistoryView(editingOriginalMessageId)?._asMessage() { + for media in message.effectiveMedia { if media is TelegramMediaFile || media is TelegramMediaImage { mediaReference = .message(message: MessageReference(message), media: media) } @@ -4781,44 +4857,63 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - for text in breakChatInputText(trimChatInputText(inputText)) { - if text.length != 0 { - var attributes: [MessageAttribute] = [] - let entities: [MessageTextEntity] - if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject, case .businessLinkSetup = customChatContents.kind { - entities = generateChatInputTextEntities(text, generateLinks: false) - } else { - entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: 0)) + var isSpecialChatContents = false + if case .customChatContents = self.chatPresentationInterfaceState.subject { + isSpecialChatContents = true + } + if !"".isEmpty, !isSpecialChatContents, let attribute = richMarkdownAttributeIfNeeded(context: self.context, attributedText: effectiveInputText) { + let attributes: [MessageAttribute] = [attribute] + var richBubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + for (_, packId) in bubbleUpEmojiOrStickersetsById { + if !richBubbleUpEmojiOrStickersets.contains(packId) { + richBubbleUpEmojiOrStickersets.append(packId) } - if !entities.isEmpty { - attributes.append(TextEntitiesMessageAttribute(entities: entities)) - } - - if let urlPreview = self.chatPresentationInterfaceState.urlPreview { - if self.chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) { - attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews])) + } + if richBubbleUpEmojiOrStickersets.count > 1 { + richBubbleUpEmojiOrStickersets.removeAll() + } + messages.append(.message(text: "", attributes: attributes, inlineStickers: inlineStickers, mediaReference: nil, threadId: self.chatLocation.threadId, replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: richBubbleUpEmojiOrStickersets)) + mediaReference = nil + } else { + for text in breakChatInputText(trimChatInputText(inputText)) { + if text.length != 0 { + var attributes: [MessageAttribute] = [] + let entities: [MessageTextEntity] + if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject, case .businessLinkSetup = customChatContents.kind { + entities = generateChatInputTextEntities(text, generateLinks: false) } else { - attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true, isSafe: false)) + entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text, maxAnimatedEmojisInText: 0)) } - } - - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] - for entity in entities { - if case let .CustomEmoji(_, fileId) = entity.type { - if let packId = bubbleUpEmojiOrStickersetsById[fileId] { - if !bubbleUpEmojiOrStickersets.contains(packId) { - bubbleUpEmojiOrStickersets.append(packId) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } + + if let urlPreview = self.chatPresentationInterfaceState.urlPreview { + if self.chatPresentationInterfaceState.interfaceState.composeDisableUrlPreviews.contains(urlPreview.url) { + attributes.append(OutgoingContentInfoMessageAttribute(flags: [.disableLinkPreviews])) + } else { + attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !urlPreview.positionBelowText, forceLargeMedia: urlPreview.largeMedia, isManuallyAdded: true, isSafe: false)) + } + } + + var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + for entity in entities { + if case let .CustomEmoji(_, fileId) = entity.type { + if let packId = bubbleUpEmojiOrStickersetsById[fileId] { + if !bubbleUpEmojiOrStickersets.contains(packId) { + bubbleUpEmojiOrStickersets.append(packId) + } } } } + + if bubbleUpEmojiOrStickersets.count > 1 { + bubbleUpEmojiOrStickersets.removeAll() + } + + messages.append(.message(text: text.string, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: self.chatLocation.threadId, replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) + mediaReference = nil } - - if bubbleUpEmojiOrStickersets.count > 1 { - bubbleUpEmojiOrStickersets.removeAll() - } - - messages.append(.message(text: text.string, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: self.chatLocation.threadId, replyToMessageId: self.chatPresentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) - mediaReference = nil } } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index e8ba9b0947..2588a90e7a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -21,7 +20,6 @@ import MediaPickerUI import LegacyCamera import LegacyMediaPickerUI import LocationUI -import WebSearchUI import WebUI import UndoUI import ICloudResources @@ -42,32 +40,32 @@ extension ChatControllerImpl { enum AttachMenuSubject { case `default` case edit(mediaOptions: MessageMediaEditingOptions, mediaReference: AnyMediaReference) - case bot(id: PeerId, payload: String?, justInstalled: Bool) + case bot(id: EnginePeer.Id, payload: String?, justInstalled: Bool) case gift } - + func presentAttachmentMenu(subject: AttachMenuSubject) { guard self.audioRecorderValue == nil && self.videoRecorderValue == nil else { return } - + let context = self.context let inputIsActive = self.presentationInterfaceState.inputMode == .text - + self.chatDisplayNode.dismissInput() - + let canByPassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: self.presentationInterfaceState) - + var banSendText: (Int32, Bool)? var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? var bannedSendFiles: (Int32, Bool)? - + var enableMultiselection = true if self.presentationInterfaceState.interfaceState.postSuggestionState != nil { enableMultiselection = false } - + var canSendPolls = true var canSendTodos = true if let peer = self.presentationInterfaceState.renderedPeer?.peer { @@ -117,37 +115,37 @@ extension ChatControllerImpl { } else { canSendPolls = false } - + var availableButtons: [AttachmentButtonType] = [.gallery, .file] if banSendText == nil { availableButtons.append(.location) availableButtons.append(.contact) } - + if canSendPolls { availableButtons.insert(.poll, at: max(0, availableButtons.count - 1)) } - + if canSendTodos { availableButtons.insert(.todo, at: max(0, availableButtons.count - 1)) } - + if "".isEmpty { availableButtons.insert(.audio, at: max(0, availableButtons.count - 1)) } - + let presentationData = self.presentationData - + var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true } - + var isPaidMessages = false if let _ = self.presentationInterfaceState.sendPaidMessageStars { isPaidMessages = true } - + var peerType: AttachMenuBots.Bot.PeerFlags = [] if let peer = self.presentationInterfaceState.renderedPeer?.peer { if let user = peer as? TelegramUser { @@ -166,7 +164,7 @@ extension ChatControllerImpl { } } } - + let buttons: Signal<([AttachmentButtonType], [AttachmentButtonType], AttachmentButtonType?), NoError> if let peer = self.presentationInterfaceState.renderedPeer?.peer, !isScheduledMessages, !peer.isDeleted { buttons = combineLatest( @@ -187,7 +185,7 @@ extension ChatControllerImpl { default: break } - + for bot in attachMenuBots.reversed() { var peerType = peerType if bot.peer.id == peer.id { @@ -197,7 +195,7 @@ extension ChatControllerImpl { let button: AttachmentButtonType = .app(bot) if !bot.peerTypes.intersection(peerType).isEmpty { buttons.insert(button, at: 1) - + if case let .bot(botId, _, _) = subject { if initialButton == nil && bot.peer.id == botId { initialButton = button @@ -206,7 +204,7 @@ extension ChatControllerImpl { } allButtons.insert(button, at: 1) } - + if !isPaidMessages { if context.isPremium, shortcutMessageList.items.count > 0, let user = peer as? TelegramUser, user.botInfo == nil { if let index = buttons.firstIndex(where: { $0 == .location }) { @@ -221,21 +219,21 @@ extension ChatControllerImpl { } } } - + return (buttons, allButtons, initialButton) } } else { buttons = .single((availableButtons, availableButtons, .gallery)) } - + let dataSettings = self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) return entry ?? GeneratedMediaStoreSettings.defaultSettings } - + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) let premiumGiftOptions: [CachedPremiumGiftOption] - + var showPremiumGift = false if !premiumConfiguration.isPremiumDisabled && self.presentationInterfaceState.disallowedGifts != TelegramDisallowedGifts.All { if self.presentationInterfaceState.alwaysShowGiftButton { @@ -246,23 +244,23 @@ extension ChatControllerImpl { showPremiumGift = true } } - + if let peer = self.presentationInterfaceState.renderedPeer?.peer, showPremiumGift, let user = peer as? TelegramUser, !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) { premiumGiftOptions = self.presentationInterfaceState.premiumGiftOptions } else { premiumGiftOptions = [] } - + let _ = combineLatest(queue: Queue.mainQueue(), buttons, dataSettings).startStandalone(next: { [weak self] buttonsAndInitialButton, dataSettings in guard let strongSelf = self else { return } - + var (buttons, allButtons, initialButton) = buttonsAndInitialButton if !premiumGiftOptions.isEmpty { buttons.insert(.gift, at: 1) } - + guard let initialButton = initialButton else { if case let .bot(botId, botPayload, botJustInstalled) = subject { if let button = allButtons.first(where: { button in @@ -289,7 +287,7 @@ extension ChatControllerImpl { let controller = webAppTermsAlertController(context: context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { allowWrite in let _ = (context.engine.messages.addBotToAttachMenu(botId: botId, allowWrite: allowWrite) |> deliverOnMainQueue).startStandalone(error: { _ in - + }, completed: { strongSelf.presentAttachmentBot(botId: botId, payload: botPayload, justInstalled: true) }) @@ -302,22 +300,17 @@ extension ChatControllerImpl { } return } - + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - + let currentMediaController = Atomic(value: nil) let currentFilesController = Atomic(value: nil) let currentAudioController = Atomic(value: nil) let currentLocationController = Atomic(value: nil) - + strongSelf.canReadHistory.set(false) - - let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, style: .glass, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in - guard let strongSelf = self else { - return nil - } - return EntityInputView(context: strongSelf.context, isDark: false, areCustomEmojiEnabled: strongSelf.presentationInterfaceState.customEmojiAvailable) - }) + + let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, style: .glass, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, customEmojiAvailable: strongSelf.presentationInterfaceState.customEmojiAvailable) attachmentController.attachmentButton = strongSelf.chatDisplayNode.getAttachmentButton() attachmentController.shouldMinimizeOnSwipe = { [weak attachmentController] button in if case .app = button { @@ -390,8 +383,8 @@ extension ChatControllerImpl { if mediaReferences.count > 1 { groupingKey = Int64.random(in: .min ..< .max) } - - var attributes: [MessageAttribute] = [] + + var attributes: [EngineMessage.Attribute] = [] var text = "" if let caption { text = caption.string @@ -400,13 +393,13 @@ extension ChatControllerImpl { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } } - + for mediaReference in mediaReferences { if messages.count == 10 { groupingKey = Int64.random(in: .min ..< .max) } let isLast = mediaReference == mediaReferences.last - + messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) } messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false) @@ -440,8 +433,8 @@ extension ChatControllerImpl { if mediaReferences.count > 1 { groupingKey = Int64.random(in: .min ..< .max) } - - var attributes: [MessageAttribute] = [] + + var attributes: [EngineMessage.Attribute] = [] var text = "" if let caption { text = caption.string @@ -450,13 +443,13 @@ extension ChatControllerImpl { attributes.append(TextEntitiesMessageAttribute(entities: entities)) } } - + for mediaReference in mediaReferences { if messages.count == 10 { groupingKey = Int64.random(in: .min ..< .max) } let isLast = mediaReference == mediaReferences.last - + messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) } messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false) @@ -477,7 +470,7 @@ extension ChatControllerImpl { controller.prepareForReuse() return true } - let selfPeerId: PeerId + let selfPeerId: EnginePeer.Id if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { if let peer = peer as? TelegramChannel, case .broadcast = peer.info { selfPeerId = peer.id @@ -507,7 +500,7 @@ extension ChatControllerImpl { } let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: location), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - + strongSelf.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in guard let strongSelf = self else { return @@ -515,7 +508,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -525,7 +518,7 @@ extension ChatControllerImpl { }) }) completion(controller, controller.mediaPickerContext) - + let _ = currentLocationController.swap(controller) }) return true @@ -533,7 +526,9 @@ extension ChatControllerImpl { let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, style: .glass, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always, requirePhoneNumbers: true)) contactsController.presentScheduleTimePicker = { [weak self] completion in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(completion: completion) + strongSelf.presentScheduleTimePicker(completion: { result in + completion(result.time, result.repeatPeriod, result.silentPosting) + }) } } contactsController.navigationPresentation = .modal @@ -543,7 +538,7 @@ extension ChatControllerImpl { if let strongSelf = self, let (peers, _, silent, scheduleTime, text, parameters) = peers { var textEnqueueMessage: EnqueueMessage? if let text = text, text.length > 0 { - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] let entities = generateTextEntities(text.string, enabledTypes: .all, currentEntities: generateChatInputTextEntities(text)) if !entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: entities)) @@ -559,11 +554,11 @@ extension ChatControllerImpl { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) case let .deviceContact(_, basicData): @@ -571,17 +566,17 @@ extension ChatControllerImpl { continue } let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) } - + if let media = media { let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -609,17 +604,17 @@ extension ChatControllerImpl { strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)) }) } else if let peer = peers.first { - let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> + let dataSignal: Signal<(EngineRawPeer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") let context = strongSelf.context dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1) - |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in + |> mapToSignal { basicData -> Signal<(EngineRawPeer?, DeviceContactExtendedData?), NoError> in var stableId: String? let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) outer: for (id, data) in basicData { @@ -630,11 +625,11 @@ extension ChatControllerImpl { } } } - + if let stableId = stableId { return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + |> map { extendedData -> (EngineRawPeer?, DeviceContactExtendedData?) in return (contact, extendedData) } } else { @@ -644,7 +639,7 @@ extension ChatControllerImpl { case let .deviceContact(id, _): dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + |> map { extendedData -> (EngineRawPeer?, DeviceContactExtendedData?) in return (nil, extendedData) } } @@ -658,18 +653,18 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) - + var enqueueMessages: [EnqueueMessage] = [] if let textEnqueueMessage = textEnqueueMessage { enqueueMessages.append(textEnqueueMessage) } - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let parameters { if let effect = parameters.effect { attributes.append(EffectMessageAttribute(id: effect.id)) @@ -683,7 +678,7 @@ extension ChatControllerImpl { strongSelf.sendMessages(strongSelf.transformEnqueueMessages(enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime, postpone: postpone), postpone: postpone) }) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0.flatMap(EnginePeer.init), contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -694,13 +689,13 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) - + var enqueueMessages: [EnqueueMessage] = [] if let textEnqueueMessage = textEnqueueMessage { enqueueMessages.append(textEnqueueMessage) @@ -763,7 +758,7 @@ extension ChatControllerImpl { }) completion(controller, controller.mediaPickerContext) strongSelf.controllerNavigationDisposable.set(nil) - + let _ = ApplicationSpecificNotice.incrementDismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peer.id, timestamp: Int32(Date().timeIntervalSince1970)).startStandalone() } } @@ -795,7 +790,7 @@ extension ChatControllerImpl { } completion(controller, controller.mediaPickerContext) strongSelf.controllerNavigationDisposable.set(nil) - + if bot.flags.contains(.notActivated) { let alertController = webAppTermsAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, bot: bot, completion: { [weak self] allowWrite in guard let self else { @@ -824,7 +819,7 @@ extension ChatControllerImpl { guard let strongSelf else { return } - + let controller = QuickReplySetupScreen(context: strongSelf.context, initialData: initialData as! QuickReplySetupScreen.InitialData, mode: .select(completion: { [weak strongSelf] shortcutId in guard let strongSelf else { return @@ -845,7 +840,7 @@ extension ChatControllerImpl { attachmentController.navigationPresentation = .flatModal strongSelf.push(attachmentController) strongSelf.attachmentController = attachmentController - + if case let .bot(botId, _, botJustInstalled) = subject, botJustInstalled { if let button = allButtons.first(where: { button in if case let .app(bot) = button, bot.peer.id == botId { @@ -866,7 +861,7 @@ extension ChatControllerImpl { } } } - + if inputIsActive { Queue.mainQueue().after(0.15, { present() @@ -876,7 +871,7 @@ extension ChatControllerImpl { } }) } - + func presentEditingAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) { let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) @@ -907,7 +902,7 @@ extension ChatControllerImpl { } } } - + if editMediaOptions == nil, let (untilDate, personal) = bannedSendMedia { let banDescription: String if untilDate != 0 && untilDate != Int32.max { @@ -917,7 +912,7 @@ extension ChatControllerImpl { } else { banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia } - + let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) var items: [ActionSheetItem] = [] items.append(ActionSheetTextItem(title: banDescription)) @@ -943,10 +938,10 @@ extension ChatControllerImpl { }) ])]) strongSelf.present(actionSheet, in: .window(.root)) - + return } - + let legacyController = LegacyController(presentation: .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) legacyController.blocksBackgroundWhenInOverlay = true legacyController.acceptsFocusWhenInOverlay = true @@ -954,14 +949,14 @@ extension ChatControllerImpl { legacyController.controllerLoaded = { [weak legacyController] in legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true } - + let emptyController = LegacyEmptyController(context: legacyController.context)! let navigationController = makeLegacyNavigationController(rootController: emptyController) navigationController.setNavigationBarHidden(true, animated: false) legacyController.bind(controller: navigationController) - + legacyController.enableSizeClassSignal = true - + let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText let menuEditMediaOptions = editMediaOptions.flatMap { options -> LegacyAttachmentMenuMediaEditing in var result: LegacyAttachmentMenuMediaEditing = .none @@ -970,7 +965,7 @@ extension ChatControllerImpl { } return result } - + var slowModeEnabled = false var hasSchedule = false if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { @@ -979,10 +974,10 @@ extension ChatControllerImpl { } hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat && strongSelf.presentationInterfaceState.sendPaidMessageStars == nil } - + let controller = legacyAttachmentMenu( context: strongSelf.context, - peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, + peer: strongSelf.presentationInterfaceState.renderedPeer?.peer.flatMap(EnginePeer.init), threadTitle: strongSelf.contentData?.state.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, addingMedia: editMediaOptions == nil, @@ -992,7 +987,7 @@ extension ChatControllerImpl { canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, - recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, + recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue.map(EnginePeer.init), initialCaption: inputText, openGallery: { self?.presentOldMediaPicker(fileMode: false, editingMedia: true, completion: { signals, silentPosting, scheduleTime in @@ -1005,14 +1000,14 @@ extension ChatControllerImpl { if let strongSelf = self { var enablePhoto = true var enableVideo = true - + if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { enableVideo = false } - + var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? - + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { if let channel = peer as? TelegramChannel { if let value = channel.hasBannedPermission(.banSendPhotos) { @@ -1030,26 +1025,27 @@ extension ChatControllerImpl { } } } - + if bannedSendPhotos != nil { enablePhoto = false } if bannedSendVideos != nil { enableVideo = false } - + var storeCapturedPhotos = false var hasSchedule = false if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { storeCapturedPhotos = peer.id.namespace != Namespaces.Peer.SecretChat - + hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat && strongSelf.presentationInterfaceState.sendPaidMessageStars == nil } - - presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: storeCapturedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, _, _, _ in + + let cameraPeer: EnginePeer? = strongSelf.presentationInterfaceState.renderedPeer?.peer.map { EnginePeer($0) } + presentedLegacyCamera(context: strongSelf.context, peer: cameraPeer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: storeCapturedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, _, _, _ in if let strongSelf = self { strongSelf.editMessageMediaWithLegacySignals(signals!) - + if !inputText.string.isEmpty { strongSelf.clearInputText() } @@ -1062,10 +1058,10 @@ extension ChatControllerImpl { } }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] result in if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + done(result.time, result.silentPosting) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -1079,14 +1075,13 @@ extension ChatControllerImpl { } }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false) + }, photoToolbarView: { [context = strongSelf.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) }) } }, openFileGallery: { self?.presentFileMediaPickerOptions(editingMessage: true) - }, openWebSearch: { [weak self] in - self?.presentWebSearch(editingMessage: editMediaOptions != nil, attachment: false, present: { [weak self] c, a in - self?.present(c, in: .window(.root), with: a) - }) + }, openWebSearch: { }, openMap: { self?.presentLocationPicker() }, openContacts: { @@ -1119,10 +1114,10 @@ extension ChatControllerImpl { })], actionLayout: .vertical), in: .window(.root)) }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] result in if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + done(result.time, result.silentPosting) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -1164,11 +1159,11 @@ extension ChatControllerImpl { controller.customRemoveFromParentViewController = { [weak legacyController] in legacyController?.dismiss() } - + legacyController.blocksBackgroundWhenInOverlay = true strongSelf.present(legacyController, in: .window(.root)) controller.present(in: emptyController, sourceView: nil, animated: true) - + let presentationDisposable = strongSelf.updatedPresentationData.1.startStrict(next: { [weak controller] presentationData in if let controller = controller { controller.pallete = legacyMenuPaletteFromTheme(presentationData.theme, forceDark: false) @@ -1177,7 +1172,7 @@ extension ChatControllerImpl { legacyController.disposables.add(presentationDisposable) }) } - + func presentFileGallery(editingMessage: Bool = false) { self.presentOldMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { [weak self] signals, silentPosting, scheduleTime in if editingMessage { @@ -1187,7 +1182,7 @@ extension ChatControllerImpl { } }) } - + func presentICloudFileGallery(editingMessage: Bool = false, documentTypes: [String] = ["public.item"]) { let _ = (self.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), @@ -1200,7 +1195,7 @@ extension ChatControllerImpl { } let (accountPeer, limits, premiumLimits) = result let isPremium = accountPeer?.isPremium ?? false - + strongSelf.present(legacyICloudFilePicker(theme: strongSelf.presentationData.theme, hasMultiselection: true, documentTypes: documentTypes, completion: { [weak self] urls in if let strongSelf = self, !urls.isEmpty { var signals: [Signal] = [] @@ -1211,7 +1206,7 @@ extension ChatControllerImpl { |> deliverOnMainQueue).startStrict(next: { results in if let strongSelf = self { let replyMessageSubject = strongSelf.presentationInterfaceState.interfaceState.replyMessageSubject - + for item in results { if let item = item { if item.fileSize > Int64(premiumLimits.maxUploadFileParts) * 512 * 1024 { @@ -1235,7 +1230,7 @@ extension ChatControllerImpl { } } } - + var groupingKey: Int64? var fileTypes: (music: Bool, other: Bool) = (false, false) if results.count > 1 { @@ -1253,14 +1248,14 @@ extension ChatControllerImpl { if fileTypes.music != fileTypes.other { groupingKey = Int64.random(in: Int64.min ... Int64.max) } - + var messages: [EnqueueMessage] = [] for item in results { if let item = item { let fileId = Int64.random(in: Int64.min ... Int64.max) let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { + if mimeType.hasPrefix("image/") || mimeType == "application/pdf" || item.audioMetadata?.hasAudioArtwork == true { previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } var attributes: [TelegramMediaFileAttribute] = [] @@ -1268,8 +1263,8 @@ extension ChatControllerImpl { if let audioMetadata = item.audioMetadata { attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) } - - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) + + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: strongSelf.chatLocation.threadId, replyToMessageId: replyMessageSubject?.subjectModel, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: []) messages.append(message) } @@ -1277,7 +1272,7 @@ extension ChatControllerImpl { groupingKey = Int64.random(in: Int64.min ... Int64.max) } } - + if !messages.isEmpty { if editingMessage { strongSelf.editMessageMediaWithMessages(messages) @@ -1285,7 +1280,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -1305,7 +1300,7 @@ extension ChatControllerImpl { }), in: .window(.root)) }) } - + func presentFileMediaPickerOptions(editingMessage: Bool) { let actionSheet = ActionSheetController(presentationData: self.presentationData) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ @@ -1329,7 +1324,7 @@ extension ChatControllerImpl { self.chatDisplayNode.dismissInput() self.present(actionSheet, in: .window(.root)) } - + func presentMediaPicker(subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, enableMultiselection: Bool, present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping (Bool, [Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { @@ -1346,7 +1341,7 @@ extension ChatControllerImpl { peer: (self.presentationInterfaceState.renderedPeer?.peer).flatMap(EnginePeer.init), threadTitle: self.contentData?.state.threadInfo?.title, chatLocation: self.chatLocation, - isScheduledMessages: isScheduledMessages, + isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, @@ -1372,10 +1367,10 @@ extension ChatControllerImpl { } controller.presentSchedulePicker = { [weak self] media, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] result in if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + done(result.time, result.silentPosting) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -1409,7 +1404,7 @@ extension ChatControllerImpl { } else { return } - + let editorController = MediaEditorScreenImpl( context: self.context, mode: .coverEditor(dimensions: dimensions), @@ -1443,7 +1438,7 @@ extension ChatControllerImpl { } self.push(editorController) }, dismissed: { - + } ) (self.navigationController as? NavigationController)?.pushViewController(mainController, animated: true) @@ -1460,12 +1455,12 @@ extension ChatControllerImpl { } present(controller, mediaPickerContext) } - + func presentOldMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool, Int32) -> Void) { let engine = self.context.engine let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, EngineConfiguration.SearchBots), NoError> in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) - + return engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) |> map { configuration -> (GeneratedMediaStoreSettings, EngineConfiguration.SearchBots) in return (entry ?? GeneratedMediaStoreSettings.defaultSettings, configuration) @@ -1483,7 +1478,7 @@ extension ChatControllerImpl { selectionLimit = 10 slowModeEnabled = true } - + let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, threadTitle: strongSelf.contentData?.state.threadInfo?.title, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, selectionLimit: selectionLimit).startStandalone(next: { generator in if let strongSelf = self { let legacyController = LegacyController(presentation: fileMode ? .navigation : .custom, theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout) @@ -1494,54 +1489,29 @@ extension ChatControllerImpl { legacyController?.view.disablesInteractiveModalDismiss = true } let controller = generator(legacyController.context) - + legacyController.bind(controller: controller) legacyController.deferScreenEdgeGestures = [.top] - - configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: editingMedia ? nil : { [weak self, weak legacyController] in - if let strongSelf = self { - let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: searchBotsConfiguration, mode: .media(attachment: false, completion: { results, selectionState, editingState, silentPosting in - if let legacyController = legacyController { - legacyController.dismiss() - } - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { result in - if let strongSelf = self { - strongSelf.enqueueChatContextResult(results, result, hideVia: true) - } - }, enqueueMediaMessages: { signals in - if let strongSelf = self { - if editingMedia { - strongSelf.editMessageMediaWithLegacySignals(signals) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) - } - } - }) - })) - controller.getCaptionPanelView = { [weak self] in - return self?.getCaptionPanelView(isFile: fileMode) - } - strongSelf.effectiveNavigationController?.pushViewController(controller) - } - }, presentSelectionLimitExceeded: { + + configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, presentWebSearch: nil, presentSelectionLimitExceeded: { guard let strongSelf = self else { return } - + let text: String if slowModeEnabled { text = strongSelf.presentationData.strings.Chat_SlowmodeAttachmentLimitReached } else { text = strongSelf.presentationData.strings.Chat_AttachmentLimitReached } - + strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak self] media, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] result in if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + done(result.time, result.silentPosting) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -1574,126 +1544,12 @@ extension ChatControllerImpl { }) }) } - - func presentWebSearch(editingMessage: Bool, attachment: Bool, activateOnDisplay: Bool = true, present: @escaping (ViewController, Any?) -> Void) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - - let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots()) - |> deliverOnMainQueue).startStandalone(next: { [weak self] configuration in - if let strongSelf = self { - let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), chatLocation: strongSelf.chatLocation, configuration: configuration, mode: .media(attachment: attachment, completion: { [weak self] results, selectionState, editingState, silentPosting in - self?.attachmentController?.dismiss(animated: true, completion: nil) - legacyEnqueueWebSearchMessages(selectionState, editingState, enqueueChatContextResult: { [weak self] result in - if let strongSelf = self { - strongSelf.enqueueChatContextResult(results, result, hideVia: true) - } - }, enqueueMediaMessages: { [weak self] signals in - if let strongSelf = self, !signals.isEmpty { - if editingMessage { - strongSelf.editMessageMediaWithLegacySignals(signals) - } else { - strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting) - } - } - }) - }), activateOnDisplay: activateOnDisplay) - controller.attemptItemSelection = { [weak strongSelf] item in - guard let strongSelf, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return false - } - - enum ItemType { - case gif - case image - case video - } - - var itemType: ItemType? - switch item { - case let .internalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - case let .externalReference(reference): - if reference.type == "gif" { - itemType = .gif - } else if reference.type == "photo" { - itemType = .image - } else if reference.type == "video" { - itemType = .video - } - } - - var bannedSendPhotos: (Int32, Bool)? - var bannedSendVideos: (Int32, Bool)? - var bannedSendGifs: (Int32, Bool)? - - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - if let value = channel.hasBannedPermission(.banSendGifs) { - bannedSendGifs = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendGifs) { - bannedSendGifs = (Int32.max, false) - } - } - - if let itemType { - switch itemType { - case .image: - if bannedSendPhotos != nil { - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .video: - if bannedSendVideos != nil { - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - case .gif: - if bannedSendGifs != nil { - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.restrictedSendingContentsText(), actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - - return false - } - } - } - - return true - } - controller.getCaptionPanelView = { [weak strongSelf] in - return strongSelf?.getCaptionPanelView(isFile: false) - } - present(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } - }) - } - + func presentLocationPicker() { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } - let selfPeerId: PeerId + let selfPeerId: EnginePeer.Id if let peer = peer as? TelegramChannel, case .broadcast = peer.info { selfPeerId = peer.id } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.hasPermission(.canBeAnonymous) { @@ -1707,7 +1563,7 @@ extension ChatControllerImpl { return } let hasLiveLocation = peer.id.namespace != Namespaces.Peer.SecretChat && peer.id != strongSelf.context.account.peerId && strongSelf.presentationInterfaceState.subject != .scheduledMessages - let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in + let controller = LocationPickerController(context: strongSelf.context, style: .glass, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: EnginePeer(peer), selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { [weak self] location, _, _, _, _ in guard let strongSelf = self else { return } @@ -1720,7 +1576,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -1733,7 +1589,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.dismissInput() }) } - + func presentContactPicker() { let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always)) contactsController.navigationPresentation = .modal @@ -1748,11 +1604,11 @@ extension ChatControllerImpl { var media: TelegramMediaContact? switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { continue } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: contact.id, vCardData: nil) case let .deviceContact(_, basicData): @@ -1760,7 +1616,7 @@ extension ChatControllerImpl { continue } let contactData = DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") - + let phone = contactData.basicData.phoneNumbers[0].value media = TelegramMediaContact(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumber: phone, peerId: nil, vCardData: nil) } @@ -1770,7 +1626,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -1787,17 +1643,17 @@ extension ChatControllerImpl { strongSelf.sendMessages(enqueueMessages, postpone: postpone) }) } else if let peer = peers.first { - let dataSignal: Signal<(Peer?, DeviceContactExtendedData?), NoError> + let dataSignal: Signal<(EngineRawPeer?, DeviceContactExtendedData?), NoError> switch peer { case let .peer(contact, _, _): - guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { + guard case let .user(contact) = contact, let phoneNumber = contact.phone else { return } let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName ?? "", lastName: contact.lastName ?? "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") let context = strongSelf.context dataSignal = (strongSelf.context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1) - |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactExtendedData?), NoError> in + |> mapToSignal { basicData -> Signal<(EngineRawPeer?, DeviceContactExtendedData?), NoError> in var stableId: String? let queryPhoneNumber = formatPhoneNumber(context: context, number: phoneNumber) outer: for (id, data) in basicData { @@ -1808,11 +1664,11 @@ extension ChatControllerImpl { } } } - + if let stableId = stableId { return (context.sharedContext.contactDataManager?.extendedData(stableId: stableId) ?? .single(nil)) |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + |> map { extendedData -> (EngineRawPeer?, DeviceContactExtendedData?) in return (contact, extendedData) } } else { @@ -1822,7 +1678,7 @@ extension ChatControllerImpl { case let .deviceContact(id, _): dataSignal = (strongSelf.context.sharedContext.contactDataManager?.extendedData(stableId: id) ?? .single(nil)) |> take(1) - |> map { extendedData -> (Peer?, DeviceContactExtendedData?) in + |> map { extendedData -> (EngineRawPeer?, DeviceContactExtendedData?) in return (nil, extendedData) } } @@ -1836,7 +1692,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -1850,7 +1706,7 @@ extension ChatControllerImpl { strongSelf.sendMessages([message], postpone: postpone) }) } else { - let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0, contactId: nil, contactData: contactData, completion: { peer, contactData in + let contactController = strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .filter(peer: peerAndContactData.0.flatMap(EnginePeer.init), contactId: nil, contactData: contactData, completion: { peer, contactData in guard let strongSelf = self, !contactData.basicData.phoneNumbers.isEmpty else { return } @@ -1861,7 +1717,7 @@ extension ChatControllerImpl { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() - + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -1884,7 +1740,7 @@ extension ChatControllerImpl { } })) } - + func getCaptionPanelView(isFile: Bool, hasTimer: Bool = true) -> TGCaptionPanelView? { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { @@ -1899,9 +1755,11 @@ extension ChatControllerImpl { return } self.presentInGlobalOverlay(c) + }, getNavigationController: { [weak self] in + return self?.navigationController as? NavigationController }) as? TGCaptionPanelView } - + func openCamera(cameraView: TGAttachmentCameraView? = nil) { let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) @@ -1911,17 +1769,17 @@ extension ChatControllerImpl { guard let strongSelf = self else { return } - + var enablePhoto = true var enableVideo = true - + if let callManager = strongSelf.context.sharedContext.callManager as? PresentationCallManagerImpl, callManager.hasActiveCall { enableVideo = false } - + var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? - + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { if let channel = peer as? TelegramChannel { if let value = channel.hasBannedPermission(.banSendPhotos) { @@ -1939,14 +1797,14 @@ extension ChatControllerImpl { } } } - + if bannedSendPhotos != nil { enablePhoto = false } if bannedSendVideos != nil { enableVideo = false } - + var storeCapturedMedia = false var hasSchedule = false if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { @@ -1954,8 +1812,9 @@ extension ChatControllerImpl { hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat && strongSelf.presentationInterfaceState.sendPaidMessageStars == nil } let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - - presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendPaidMessageStars: strongSelf.presentationInterfaceState.sendPaidMessageStars?.value ?? 0, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, parameters in + + let cameraPeer: EnginePeer? = strongSelf.presentationInterfaceState.renderedPeer?.peer.map { EnginePeer($0) } + presentedLegacyCamera(context: strongSelf.context, peer: cameraPeer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendPaidMessageStars: strongSelf.presentationInterfaceState.sendPaidMessageStars?.value ?? 0, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, parameters in if let strongSelf = self { strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil, parameters: parameters) if !inputText.string.isEmpty { @@ -1970,10 +1829,10 @@ extension ChatControllerImpl { } }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in + strongSelf.presentScheduleTimePicker(style: .media, presentInOverlay: true, completion: { [weak self] result in if let strongSelf = self { - done(time) - if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { + done(result.time, result.silentPosting) + if strongSelf.presentationInterfaceState.subject != .scheduledMessages && result.time != scheduleWhenOnlineTimestamp { strongSelf.openScheduledMessages() } } @@ -1987,6 +1846,8 @@ extension ChatControllerImpl { } }, getCaptionPanelView: { [weak self] in return self?.getCaptionPanelView(isFile: false) + }, photoToolbarView: { [context = strongSelf.context] backButton, doneButton, solidBackground, hasSendStarsButton in + return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton) }, dismissedWithResult: { [weak self] in self?.attachmentController?.dismiss(animated: false, completion: nil) }, finishedTransitionIn: { [weak self] in @@ -1994,10 +1855,10 @@ extension ChatControllerImpl { }) }) } - + func openStickerEditor() { self.chatDisplayNode.dismissInput() - + var dismissImpl: (() -> Void)? let mainController = self.context.sharedContext.makeStickerMediaPickerScreen( context: self.context, @@ -2026,7 +1887,7 @@ extension ChatControllerImpl { } else { subject = .single(.empty(PixelDimensions(width: 1080, height: 1920))) } - + let editorController = MediaEditorScreenImpl( context: self.context, mode: .stickerEditor(mode: .generic(canSend: true)), @@ -2050,7 +1911,7 @@ extension ChatControllerImpl { }, completion: { [weak self] results, commit in dismissImpl?() self?.chatDisplayNode.dismissInput() - + Queue.mainQueue().after(0.1) { commit({}) if case let .sticker(file, _) = results.first?.media { @@ -2082,8 +1943,8 @@ extension ChatControllerImpl { mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.push(mainController) } - - func configurePollCreation(isQuiz: Bool? = nil) -> ViewController? { + + func configurePollCreation(sourceMessageId: EngineMessage.Id? = nil, isQuiz: Bool? = nil) -> ViewController? { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return nil } @@ -2100,28 +1961,30 @@ extension ChatControllerImpl { guard let self else { return } - let replyMessageSubject = self.presentationInterfaceState.interfaceState.replyMessageSubject + let replyMessageSubject = sourceMessageId.flatMap { + EngineMessageReplySubject(messageId: $0, quote: nil, innerSubject: nil) + } ?? self.presentationInterfaceState.interfaceState.replyMessageSubject?.subjectModel self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let self { self.chatDisplayNode.collapseInput() - + self.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) } }, nil) - - var attributes: [MessageAttribute] = [] + + var attributes: [EngineMessage.Attribute] = [] if !poll.description.entities.isEmpty { attributes.append(TextEntitiesMessageAttribute(entities: poll.description.entities)) } - + let message: EnqueueMessage = .message( text: poll.description.string, attributes: attributes, inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaPoll( - pollId: MediaId(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)), + pollId: EngineMedia.Id(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min...Int64.max)), publicity: poll.publicity, kind: poll.kind, text: poll.text.string, @@ -2137,7 +2000,10 @@ extension ChatControllerImpl { revotingDisabled: poll.revotingDisabled, shuffleAnswers: poll.shuffleAnswers, hideResultsUntilClose: poll.hideResultsUntilClose, - attachedMedia: poll.media?.media + isCreator: true, + attachedMedia: poll.media?.media, + restrictToSubscribers: poll.restrictToSubscribers, + countries: poll.limitToCountries )), threadId: self.chatLocation.threadId, replyToMessageId: nil, @@ -2146,12 +2012,12 @@ extension ChatControllerImpl { correlationId: nil, bubbleUpEmojiOrStickersets: [] ) - self.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject?.subjectModel)]) + self.sendMessages([message.withUpdatedReplyToMessageId(replyMessageSubject)]) }) } ) } - + func configureTodoCreation() -> ViewController? { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return nil @@ -2174,7 +2040,7 @@ extension ChatControllerImpl { self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let self { self.chatDisplayNode.collapseInput() - + self.updateChatPresentationInterfaceState(animated: true, interactive: false, { $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedSendMessageEffect(nil).withUpdatedPostSuggestionState(nil) } }) @@ -2197,15 +2063,15 @@ extension ChatControllerImpl { } ) } - + func openTodoEditing(messageId: EngineMessage.Id, itemId: Int32?, append: Bool) { - guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let peer = self.presentationInterfaceState.renderedPeer?.peer else { + guard let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)?._asMessage(), let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } guard let existingTodo = message.media.first(where: { $0 is TelegramMediaTodo }) as? TelegramMediaTodo else { return } - + guard self.context.isPremium else { let context = self.context var replaceImpl: ((ViewController) -> Void)? @@ -2219,9 +2085,9 @@ extension ChatControllerImpl { self.push(demoController) return } - + let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message) - + let controller = ComposeTodoScreen( context: self.context, initialData: ComposeTodoScreen.initialData( @@ -2247,13 +2113,14 @@ extension ChatControllerImpl { } return true } - + if canEdit && !areItemsOnlyAppended(existing: existingTodo.items, updated: todo.items) { let _ = self.context.engine.messages.requestEditMessage( messageId: messageId, text: "", media: .update(.standalone(media: todo)), entities: nil, + richText: nil, inlineStickers: [:] ).start() } else { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift index 821488a047..659b44954f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenCalendarSearch.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import CalendarMessageScreen @@ -52,7 +51,7 @@ extension ChatControllerImpl { strongSelf.loadingMessage.set(.single(.generic)) - let peerId: PeerId + let peerId: EnginePeer.Id let threadId: Int64? switch strongSelf.chatLocation { case let .peer(peerIdValue): @@ -89,7 +88,7 @@ extension ChatControllerImpl { strongSelf.loadingMessage.set(.single(.generic)) - let peerId: PeerId + let peerId: EnginePeer.Id let threadId: Int64? switch strongSelf.chatLocation { case let .peer(peerIdValue): @@ -210,7 +209,7 @@ extension ChatControllerImpl { self.loadingMessage.set(.single(.generic)) - let peerId: PeerId + let peerId: EnginePeer.Id let threadId: Int64? switch self.chatLocation { case let .peer(peerIdValue): diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index ee684b7ffc..3f47be584c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import ContextUI @@ -36,7 +35,7 @@ extension ChatControllerImpl { self.push(controller) } - func openMessageReactionContextMenu(message: Message, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?, value: MessageReaction.Reaction) { + func openMessageReactionContextMenu(message: EngineMessage, sourceView: ContextExtractedContentContainingView, gesture: ContextGesture?, value: MessageReaction.Reaction) { if message.areReactionsTags(accountPeerId: self.context.account.peerId) { if !self.presentationInterfaceState.isPremium { self.presentTagPremiumPaywall() @@ -146,7 +145,7 @@ extension ChatControllerImpl { guard let self else { return } - self.controllerInteraction?.updateMessageReaction(message, .reaction(value), true, nil) + self.controllerInteraction?.updateMessageReaction(message._asMessage(), .reaction(value), true, nil) }))) self.canReadHistory.set(false) @@ -188,25 +187,49 @@ extension ChatControllerImpl { } var dismissController: ((@escaping () -> Void) -> Void)? + let canDeleteReactions: Bool + if case let .channel(channel) = message.enginePeers[message.id.peerId] { + canDeleteReactions = channel.hasPermission(.deleteAllMessages) + } else { + canDeleteReactions = false + } + let canViewReactions = canViewMessageReactionList(message: message) + let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? + if canDeleteReactions && canViewReactions { + deleteReaction = { [weak self] peer, _ in + dismissController?({ [weak self] in + guard let self else { + return + } + self.presentReactionDeletionOptions(author: peer, messageId: message.id) + }) + } + } else { + deleteReaction = nil + } var items: ContextController.Items - if canViewMessageReactionList(message: message) { + if canViewReactions { items = ContextController.Items(content: .custom(ReactionListContextMenuContent( context: self.context, displayReadTimestamps: true, availableReactions: availableReactions, animationCache: self.controllerInteraction!.presentationContext.animationCache, animationRenderer: self.controllerInteraction!.presentationContext.animationRenderer, - message: EngineMessage(message), - reaction: value, readStats: nil, back: nil, openPeer: { peer, hasReaction in + message: message, + reaction: value, + readStats: nil, + back: nil, + openPeer: { peer, hasReaction in dismissController?({ [weak self] in guard let self else { return } - self.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message), fromReactionMessageId: hasReaction ? message.id : nil) + self.openPeer(peer: peer, navigation: .default, fromMessage: MessageReference(message._asMessage()), fromReactionMessageId: hasReaction ? message.id : nil) }) - } + }, + deleteReaction: deleteReaction ))) } else { items = ContextController.Items(content: .list([])) @@ -369,7 +392,11 @@ extension ChatControllerImpl { } } - func openMessageSendStarsScreen(message: Message) { + func openMessageSendStarsScreen(message: EngineMessage) { + guard canSendReactionsToChat(self.presentationInterfaceState) else { + return + } + if let current = self.currentSendStarsUndoController { self.currentSendStarsUndoController = nil current.dismiss() diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReplies.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReplies.swift index f5cb3b3137..1657d4ffd8 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReplies.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReplies.swift @@ -1,14 +1,13 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import PresentationDataUtils import Display extension ChatControllerImpl { - func openMessageReplies(messageId: MessageId, displayProgressInMessage: MessageId?, isChannelPost: Bool, atMessage atMessageId: MessageId?, displayModalProgress: Bool) { + func openMessageReplies(messageId: EngineMessage.Id, displayProgressInMessage: EngineMessage.Id?, isChannelPost: Bool, atMessage atMessageId: EngineMessage.Id?, displayModalProgress: Bool) { guard let navigationController = self.effectiveNavigationController else { return } @@ -58,7 +57,7 @@ extension ChatControllerImpl { }) } - static func openMessageReplies(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: MessageId, isChannelPost: Bool, atMessage atMessageId: MessageId?, displayModalProgress: Bool) -> Signal { + static func openMessageReplies(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, navigationController: NavigationController, present: @escaping (ViewController, Any?) -> Void, messageId: EngineMessage.Id, isChannelPost: Bool, atMessage atMessageId: EngineMessage.Id?, displayModalProgress: Bool) -> Signal { return Signal { subscriber in let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift index ea565b6fe6..01c28ff53b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageShareMenu.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import ContextUI @@ -92,6 +91,59 @@ func chatShareToSavedMessagesAdditionalView(_ chatController: ChatControllerImpl } extension ChatControllerImpl { +// func openMessageShareMenu(id: EngineMessage.Id) { +// guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let message = messages.first else { +// return +// } +// +// let chatPresentationInterfaceState = self.presentationInterfaceState +// var warnAboutPrivate = false +// var canShareToStory = false +// if case .peer = chatPresentationInterfaceState.chatLocation, let channel = message.peers[message.id.peerId] as? TelegramChannel { +// if case .broadcast = channel.info { +// canShareToStory = true +// if let message = messages.first, message.media.contains(where: { media in +// if media is TelegramMediaContact || media is TelegramMediaPoll || media is TelegramMediaTodo { +// return true +// } else if let file = media as? TelegramMediaFile, file.isSticker || file.isAnimatedSticker || file.isVideoSticker { +// return true +// } else { +// return false +// } +// }) { +// canShareToStory = false +// } +// if message.text.containsOnlyEmoji { +// canShareToStory = false +// } +// } +// if channel.addressName == nil { +// warnAboutPrivate = true +// } +// } +// +// let _ = warnAboutPrivate +// +//// let shareScreen = self.context.sharedContext.makeShareController( +//// context: self.context, +//// subject: .messages(messages), +//// forceExternal: false, +//// shareStory: canShareToStory ? { [weak self] in +//// guard let self else { +//// return +//// } +//// Queue.mainQueue().after(0.15) { +//// let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .messages(messages), parentController: self) +//// self.push(controller) +//// } +//// } : nil, +//// enqueued: nil, +//// actionCompleted: nil +//// ) +// self.chatDisplayNode.dismissInput() +// self.push(shareScreen) +// } + func openMessageShareMenu(id: EngineMessage.Id) { guard let messages = self.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let message = messages.first else { return diff --git a/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift index 60fc23d926..086744b174 100644 --- a/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift +++ b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift @@ -1,7 +1,6 @@ import Foundation import TelegramPresentationData import AccountContext -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/TelegramUI/Sources/ChatControllerScrollToPointInHistory.swift b/submodules/TelegramUI/Sources/ChatControllerScrollToPointInHistory.swift index 31220d20c2..26d6eb6965 100644 --- a/submodules/TelegramUI/Sources/ChatControllerScrollToPointInHistory.swift +++ b/submodules/TelegramUI/Sources/ChatControllerScrollToPointInHistory.swift @@ -4,7 +4,6 @@ import AccountContext import Display import SwiftSignalKit import TelegramCore -import Postbox import UIKit import OverlayStatusController import PresentationDataUtils @@ -15,7 +14,7 @@ extension ChatControllerImpl { let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tag: nil, additionalData: []) let signal = historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + |> mapToSignal { historyView -> Signal<(EngineMessage.Index?, Bool), NoError> in switch historyView { case .Loading: return .single((nil, true)) @@ -79,7 +78,7 @@ extension ChatControllerImpl { let historyView = preloadedChatHistoryViewForLocation(locationInput, context: self.context, chatLocation: self.chatLocation, subject: self.subject, chatLocationContextHolder: self.chatLocationContextHolder, fixedCombinedReadStates: nil, tag: nil, additionalData: []) let signal = historyView - |> mapToSignal { historyView -> Signal<(MessageIndex?, Bool), NoError> in + |> mapToSignal { historyView -> Signal<(EngineMessage.Index?, Bool), NoError> in switch historyView { case .Loading: return .single((nil, true)) @@ -146,9 +145,9 @@ extension ChatControllerImpl { } var kind: PeerInfoControllerMode.PeerInfoMediaKind? - if message.tags.contains(MessageTags.photoOrVideo) { + if message.tags.contains(EngineMessage.Tags.photoOrVideo) { kind = .photoVideo - } else if message.tags.contains(MessageTags.file) { + } else if message.tags.contains(EngineMessage.Tags.file) { kind = .file } @@ -159,7 +158,7 @@ extension ChatControllerImpl { let peerInfoController = self.context.sharedContext.makePeerInfoController( context: self.context, updatedPresentationData: self.updatedPresentationData, - peer: peer._asPeer(), + peer: peer, mode: .media(kind: kind, messageIndex: message.index), avatarInitiallyExpanded: false, fromChat: true, diff --git a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift index 07cd1d573d..925b489de3 100644 --- a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift @@ -3,7 +3,6 @@ import TelegramPresentationData import AccountContext import ChatPresentationInterfaceState import SwiftSignalKit -import Postbox import TelegramCore extension ChatControllerImpl { @@ -106,7 +105,7 @@ extension ChatControllerImpl { return } strongSelf.searchResultsCount.set(results.totalCount) - var navigateIndex: MessageIndex? + var navigateIndex: EngineMessage.Index? strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { current in if let data = current.search { let messageIndices = results.messages.map({ $0.index }).sorted() diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 0322eba0b9..6cac18a9de 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -59,7 +59,8 @@ func chatHistoryEntriesForView( cachedData: CachedPeerData?, adMessage: Message?, dynamicAdMessages: [Message], - isMusicPlaylist: Bool + isMusicPlaylist: Bool, + pinToTopStableId: EngineMessage.StableId? ) -> ([ChatHistoryEntry], ChatHistoryEntriesForViewState) { var currentState = currentState @@ -144,6 +145,11 @@ func chatHistoryEntriesForView( var message = entry.message var isRead = entry.isRead + var pinToTop = false + if message.stableId == pinToTopStableId { + pinToTop = true + } + if pendingRemovedMessages.contains(message.id) { continue } @@ -235,11 +241,11 @@ func chatHistoryEntriesForView( } if presentationData.largeEmoji, message.media.isEmpty { - if messageIsEligibleForLargeCustomEmoji(message) { + if messageIsEligibleForLargeCustomEmoji(EngineMessage(message)) { contentTypeHint = .animatedEmoji } else if stickersEnabled && message.text.count == 1, let _ = associatedData.animatedEmojiStickers[message.text.basicEmoji.0], (message.textEntitiesAttribute?.entities.isEmpty ?? true) { contentTypeHint = .animatedEmoji - } else if messageIsEligibleForLargeEmoji(message) { + } else if messageIsEligibleForLargeEmoji(EngineMessage(message)) { contentTypeHint = .animatedEmoji } } @@ -258,7 +264,7 @@ func chatHistoryEntriesForView( isCentered = link.isCentered } - let attributes = ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: isCentered, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false) + let attributes = ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: isCentered, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false, pinToTop: pinToTop) let groupStableId = currentState.messageGroupStableId(messageStableId: message.stableId, groupId: messageGroupingKey, isLocal: Namespaces.Message.allLocal.contains(message.id.namespace)) var found = false @@ -304,7 +310,7 @@ func chatHistoryEntriesForView( isCentered = link.isCentered } - entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: isCentered, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: isCentered, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false, pinToTop: pinToTop))) } } else { let selection: ChatHistoryMessageSelection @@ -314,7 +320,7 @@ func chatHistoryEntriesForView( selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false, pinToTop: pinToTop))) } } @@ -388,7 +394,7 @@ func chatHistoryEntriesForView( associatedThreadInfo: nil, associatedStories: [:] ) - entries.insert(.MessageEntry(serviceMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)), at: index) + entries.insert(.MessageEntry(serviceMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false)), at: index) } for i in (0 ..< entries.count).reversed() { @@ -428,7 +434,7 @@ func chatHistoryEntriesForView( } } if let insertAtPosition { - entries.insert(.MessageEntry(joinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)), at: insertAtPosition) + entries.insert(.MessageEntry(joinMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false)), at: insertAtPosition) } } } @@ -483,11 +489,11 @@ func chatHistoryEntriesForView( var contentTypeHint: ChatMessageEntryContentType = .generic if presentationData.largeEmoji, topMessage.media.isEmpty { - if messageIsEligibleForLargeCustomEmoji(topMessage) { + if messageIsEligibleForLargeCustomEmoji(EngineMessage(topMessage)) { contentTypeHint = .animatedEmoji } else if stickersEnabled && topMessage.text.count == 1, let _ = associatedData.animatedEmojiStickers[topMessage.text.basicEmoji.0] { contentTypeHint = .animatedEmoji - } else if messageIsEligibleForLargeEmoji(topMessage) { + } else if messageIsEligibleForLargeEmoji(EngineMessage(topMessage)) { contentTypeHint = .animatedEmoji } } @@ -496,12 +502,12 @@ func chatHistoryEntriesForView( if messages.count > 1, let groupingKey = messages[0].groupingKey { var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = [] for message in messages { - groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false), nil)) + groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false, isCentered: false, authorStoryStats: message.author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false, pinToTop: false), nil)) } entries.insert(.MessageGroupEntry(groupingKey, groupMessages, presentationData), at: 0) } else { if !hasTopicCreated { - entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false, isCentered: false, authorStoryStats: messages[0].author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false)), at: 0) + entries.insert(.MessageEntry(messages[0], presentationData, false, nil, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[messages[0].id], isPlaying: false, isCentered: false, authorStoryStats: messages[0].author.flatMap { view.peerStoryStats[$0.id] }, displayContinueThreadFooter: false, pinToTop: false)), at: 0) } } @@ -582,7 +588,7 @@ func chatHistoryEntriesForView( associatedThreadInfo: nil, associatedStories: [:] ) - entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)), at: 0) + entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false)), at: 0) } if let chatPeer, let nameChangeDate = peerStatusSettings.nameChangeDate, nameChangeDate > 0 { @@ -622,7 +628,7 @@ func chatHistoryEntriesForView( associatedThreadInfo: nil, associatedStories: [:] ) - entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)), at: 0) + entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false)), at: 0) } if let peer = chatPeer.flatMap(EnginePeer.init) { @@ -675,7 +681,7 @@ func chatHistoryEntriesForView( if !dynamicAdMessages.isEmpty { assert(entries.sorted() == entries) for message in dynamicAdMessages { - entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false))) + entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false))) } entries.sort() } @@ -710,7 +716,7 @@ func chatHistoryEntriesForView( associatedStories: message.associatedStories ) nextAdMessageId += 1 - entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false))) + entries.append(.MessageEntry(updatedMessage, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false))) } } } @@ -772,7 +778,7 @@ func chatHistoryEntriesForView( associatedThreadInfo: nil, associatedStories: [:] ) - entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false))) + entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false))) } } @@ -829,7 +835,7 @@ func chatHistoryEntriesForView( associatedThreadInfo: nil, associatedStories: [:] ) - entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)), at: 0) + entries.insert(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false, pinToTop: false)), at: 0) } } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 1348dedfb8..8180d4f054 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1,11 +1,9 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import MediaResources @@ -38,6 +36,8 @@ import DustEffect import UrlHandling import TextFormat import ChatNewThreadInfoItem +import PhoneNumberFormat +import Postbox struct ChatTopVisibleMessageRange: Equatable { var lowerBound: MessageIndex @@ -366,12 +366,15 @@ private func extractAssociatedData( deviceContactsNumbers: Set, isInline: Bool, showSensitiveContent: Bool, - isSuspiciousPeer: Bool + isSuspiciousPeer: Bool, + accountCountry: String? ) -> ChatMessageItemAssociatedData { var automaticDownloadPeerId: EnginePeer.Id? var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel var contactsPeerIds: Set = Set() var channelDiscussionGroup: ChatMessageItemAssociatedData.ChannelDiscussionGroupStatus = .unknown + var isParticipant = false + var invitedOn: Int32? if case let .peer(peerId) = chatLocation { automaticDownloadPeerId = peerId @@ -395,11 +398,15 @@ private func extractAssociatedData( } else if peerId.namespace == Namespaces.Peer.CloudChannel { for entry in view.additionalData { if case let .peer(_, value) = entry { - if let channel = value as? TelegramChannel, case .group = channel.info { - automaticMediaDownloadPeerType = .group + if let channel = value as? TelegramChannel { + if case .group = channel.info { + automaticMediaDownloadPeerType = .group + } + isParticipant = channel.participationStatus == .member } } else if case let .cachedPeerData(dataPeerId, cachedData) = entry, dataPeerId == peerId { if let cachedData = cachedData as? CachedChannelData { + invitedOn = cachedData.invitedOn switch cachedData.linkedDiscussionPeerId { case let .known(value): channelDiscussionGroup = .known(value) @@ -421,7 +428,7 @@ private func extractAssociatedData( automaticDownloadPeerId = message.peerId } - return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, areStarReactionsEnabled: areStarReactionsEnabled, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, isSuspiciousPeer: isSuspiciousPeer) + return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, areStarReactionsEnabled: areStarReactionsEnabled, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, isSuspiciousPeer: isSuspiciousPeer, accountCountry: accountCountry, isParticipant: isParticipant, invitedOn: invitedOn) } private extension ChatHistoryLocationInput { @@ -754,6 +761,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH var frozenMessageForScrollingReset: EngineMessage.Id? private var hasDisplayedBusinessBotMessageTooltip: Bool = false + private var hasDisplayedGuestChatMessageTooltip: Bool = false private let _isReady = ValuePromise(false, ignoreRepeated: true) public var isReady: Signal { @@ -763,6 +771,8 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH private let initTimestamp: Double + var pinToTopStableId: EngineMessage.StableId? + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), @@ -1169,10 +1179,13 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH }).startStrict() self.beganInteractiveDragging = { [weak self] _ in - self?.isInteractivelyScrollingValue = true - self?.isInteractivelyScrollingPromise.set(true) - self?.beganDragging?() - //self?.updateHistoryScrollingArea(transition: .immediate) + guard let self else { + return + } + self.isInteractivelyScrollingValue = true + self.isInteractivelyScrollingPromise.set(true) + //self.pinToTopStableId = nil + self.beganDragging?() } self.endedInteractiveDragging = { [weak self] _ in @@ -1682,16 +1695,16 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) ), peerReactionSettings, - self.context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.reactionSettings)) ) |> map { peer, peerReactionSettings, preferencesView -> (MessageReaction.Reaction?, Bool) in var areStarsEnabled: Bool = false if let peerReactionSettings, let value = peerReactionSettings.knownValue?.starsAllowed { areStarsEnabled = value } - + let reactionSettings: ReactionSettings - if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + if let entry = preferencesView, let value = entry.get(ReactionSettings.self) { reactionSettings = value } else { reactionSettings = .default @@ -1711,6 +1724,33 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH return peer } |> distinctUntilChanged + + let accountCountry: Signal + if let data = context.currentAppConfiguration.with({ $0 }).data, let country = data["phone_country_iso2"] as? String { + accountCountry = .single(country) + } else { + accountCountry = .single(nil) + |> then( + combineLatest( + accountPeer + |> map { peer -> String? in + if case let .user(user) = peer { + return user.phone + } else { + return nil + } + } + |> distinctUntilChanged, + (context as! AccountContextImpl).countriesConfiguration + ) + |> map { phone, countriesConfiguration in + guard let phone, let (country, _) = lookupCountryIdByNumber(phone, configuration: countriesConfiguration) else { + return nil + } + return country.id + } + ) + } let topicAuthorId: Signal if let peerId = chatLocation.peerId, let threadId = chatLocation.threadId { @@ -1853,6 +1893,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH savedMessageTags |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_savedMessageTags"), defaultReaction |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_defaultReaction"), accountPeer |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_accountPeer"), + accountCountry |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_accountCountry"), audioTranscriptionSuggestion |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_audioTranscriptionSuggestion"), promises |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_promises"), topicAuthorId |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_topicAuthorId"), @@ -1863,7 +1904,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH chatThemes |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_chatThemes"), deviceContactsNumbers |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_deviceContactsNumbers"), contentSettings |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_contentSettings") - ) |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_firstChatHistoryTransition")).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in + ) |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_firstChatHistoryTransition")).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, accountCountry, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises if measure_isFirstTime { @@ -2107,12 +2148,35 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH isSuspiciousPeer = true } - let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction.0, areStarReactionsEnabled: defaultReaction.1, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage?.toLang, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive"), isSuspiciousPeer: isSuspiciousPeer) + let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction.0, areStarReactionsEnabled: defaultReaction.1, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage?.toLang, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive"), isSuspiciousPeer: isSuspiciousPeer, accountCountry: accountCountry) var includeEmbeddedSavedChatInfo = false if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated { includeEmbeddedSavedChatInfo = true } + + var pinToTopStableId: EngineMessage.StableId? + var scrollToPinToTopStableId = false + if let strongSelf = self { + for i in (0 ..< view.entries.count).reversed() { + let message = view.entries[i].message + if message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + for j in (0 ..< i).reversed() { + let otherMessage = view.entries[j].message + if !otherMessage.effectivelyIncoming(context.account.peerId) && otherMessage.id.namespace == Namespaces.Message.Cloud { + if strongSelf.pinToTopStableId != otherMessage.stableId { + strongSelf.pinToTopStableId = otherMessage.stableId + scrollToPinToTopStableId = true + } + } + break + } + break + } + } + + pinToTopStableId = strongSelf.pinToTopStableId + } let previousChatHistoryEntriesForViewState = chatHistoryEntriesForViewState.with({ $0 }) let (filteredEntries, updatedChatHistoryEntriesForViewState) = chatHistoryEntriesForView( @@ -2141,7 +2205,8 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH cachedData: data.cachedData, adMessage: allAdMessages.fixed, dynamicAdMessages: allAdMessages.opportunistic, - isMusicPlaylist: isMusicPlaylist + isMusicPlaylist: isMusicPlaylist, + pinToTopStableId: pinToTopStableId ) let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0 let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4) @@ -2365,6 +2430,17 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH keyboardButtonsMessage = nil } + if scrollToPinToTopStableId, let strongSelf = self, let pinToTopStableId = strongSelf.pinToTopStableId { + for entry in processedView.filteredEntries.reversed() { + if case let .MessageEntry(message, _, _, _, _, _) = entry { + if message.stableId == pinToTopStableId { + updatedScrollPosition = .index(subject: MessageHistoryScrollToSubject(index: .message(message.index), quote: nil), position: .top(0.0), directionHint: .Up, animated: true, highlight: false, displayLink: false, setupReply: false) + break + } + } + } + } + let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: !isSavedMusic || forceUpdateAll) var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition, systemStyle: systemStyle) @@ -2465,10 +2541,10 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } private func beginPresentationDataManagement(updated: Signal) { - let appConfiguration = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } var didSetPresentationData = false @@ -2834,6 +2910,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH var visibleAdOpaqueIds: [Data] = [] var peerIdsWithRefreshStories: [PeerId] = [] var visibleBusinessBotMessageId: EngineMessage.Id? + var visibleGuestChatMessageId: EngineMessage.Id? if indexRange.0 <= indexRange.1 { for i in (indexRange.0 ... indexRange.1) { @@ -2885,7 +2962,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } - for media in message.media { + for media in message.effectiveMedia { if let _ = media as? TelegramMediaUnsupported { contentRequiredValidation = true } else if message.flags.contains(.Incoming), let media = media as? TelegramMediaMap, let liveBroadcastingTimeout = media.liveBroadcastingTimeout { @@ -3052,6 +3129,15 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH visibleBusinessBotMessageId = message.id } } + if message.id.namespace == Namespaces.Message.Cloud, message.flags.contains(.Incoming), message.guestChatAttribute != nil { + if let visibleGuestChatMessageIdValue = visibleGuestChatMessageId { + if visibleGuestChatMessageIdValue < message.id { + visibleGuestChatMessageId = message.id + } + } else { + visibleGuestChatMessageId = message.id + } + } switch message.id.peerId.namespace { case Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel: messageIdsWithPossibleReactions.append(message.id) @@ -3069,6 +3155,15 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH visibleBusinessBotMessageId = message.id } } + if message.id.namespace == Namespaces.Message.Cloud, message.flags.contains(.Incoming), message.guestChatAttribute != nil { + if let visibleGuestChatMessageIdValue = visibleGuestChatMessageId { + if visibleGuestChatMessageIdValue < message.id { + visibleGuestChatMessageId = message.id + } + } else { + visibleGuestChatMessageId = message.id + } + } switch message.id.peerId.namespace { case Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel: messageIdsWithPossibleReactions.append(message.id) @@ -3265,6 +3360,23 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } } + + if let visibleGuestChatMessageId, !self.hasDisplayedGuestChatMessageTooltip { + var foundItemNode: ChatMessageItemView? + self.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, item.message.id == visibleGuestChatMessageId, itemNode.getAuthorNameNode() != nil { + foundItemNode = itemNode + } + } + + if let foundItemNode { + self.hasDisplayedGuestChatMessageTooltip = true + + if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl { + chatController.displayGuestChatMessageTooltip(itemNode: foundItemNode) + } + } + } } if !self.isSettingTopReplyThreadMessageShown { @@ -3337,6 +3449,11 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH if self.chatHistoryLocationValue?.content != locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) } + } else if historyView.originalView.holeEarlier, case let .custom(_, _, _, _, _, loadMore) = self.source, let loadMore { + if self.chatHistoryLocationValue?.content != locationInput { + self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) + loadMore() + } } else if case let .customChatContents(customChatContents) = self.subject, case .hashTagSearch = customChatContents.kind { if self.chatHistoryLocationValue?.content != locationInput { self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId()) @@ -3506,22 +3623,22 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Scroll(subject: MessageHistoryScrollToSubject(index: .message(toIndex), quote: quote.flatMap { quote in MessageHistoryScrollToSubject.Quote(string: quote.string, offset: quote.offset) }, subject: subject, setupReply: setupReply), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated, highlight: highlight, setupReply: setupReply), id: self.takeNextHistoryLocationId()) } - public func anchorMessageInCurrentHistoryView() -> Message? { + public func anchorMessageInCurrentHistoryView() -> EngineMessage? { if let historyView = self.historyView { if let visibleRange = self.displayedItemRange.visibleRange { var index = 0 for entry in historyView.filteredEntries.reversed() { if index >= visibleRange.firstIndex && index <= visibleRange.lastIndex { if case let .MessageEntry(message, _, _, _, _, _) = entry { - return message + return EngineMessage(message) } } index += 1 } } - + for case let .MessageEntry(message, _, _, _, _, _) in historyView.filteredEntries { - return message + return EngineMessage(message) } } return nil @@ -3547,24 +3664,24 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } - public func latestMessageInCurrentHistoryView() -> Message? { + public func latestMessageInCurrentHistoryView() -> EngineMessage? { if let historyView = self.historyView { if historyView.originalView.laterId == nil, let firstEntry = historyView.filteredEntries.last { if case let .MessageEntry(message, _, _, _, _, _) = firstEntry { - return message + return EngineMessage(message) } } } return nil } - - public func firstMessageForEditInCurrentHistoryView() -> Message? { + + public func firstMessageForEditInCurrentHistoryView() -> EngineMessage? { if let historyView = self.historyView { if historyView.originalView.laterId == nil { for entry in historyView.filteredEntries.reversed() { if case let .MessageEntry(message, _, _, _, _, _) = entry { if canEditMessage(context: context, limitsConfiguration: context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message) { - return message + return EngineMessage(message) } } } @@ -3573,45 +3690,45 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH return nil } - public func messageInCurrentHistoryView(after messageId: MessageId) -> Message? { + public func messageInCurrentHistoryView(after messageId: EngineMessage.Id) -> EngineMessage? { if let historyView = self.historyView { if let index = historyView.filteredEntries.firstIndex(where: { $0.firstIndex.id == messageId }), index < historyView.filteredEntries.count - 1 { let nextEntry = historyView.filteredEntries[index + 1] if case let .MessageEntry(message, _, _, _, _, _) = nextEntry { - return message + return EngineMessage(message) } else if case let .MessageGroupEntry(_, messages, _) = nextEntry, let firstMessage = messages.first { - return firstMessage.0 + return EngineMessage(firstMessage.0) } } } return nil } - - public func messageInCurrentHistoryView(before messageId: MessageId) -> Message? { + + public func messageInCurrentHistoryView(before messageId: EngineMessage.Id) -> EngineMessage? { if let historyView = self.historyView { if let index = historyView.filteredEntries.firstIndex(where: { $0.firstIndex.id == messageId }), index > 0 { let nextEntry = historyView.filteredEntries[index - 1] if case let .MessageEntry(message, _, _, _, _, _) = nextEntry { - return message + return EngineMessage(message) } else if case let .MessageGroupEntry(_, messages, _) = nextEntry, let firstMessage = messages.first { - return firstMessage.0 + return EngineMessage(firstMessage.0) } } } return nil } - public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { + public func messageInCurrentHistoryView(_ id: EngineMessage.Id) -> EngineMessage? { if let historyView = self.historyView { for entry in historyView.filteredEntries { if case let .MessageEntry(message, _, _, _, _, _) = entry { if message.id == id { - return message + return EngineMessage(message) } } else if case let .MessageGroupEntry(_, messages, _) = entry { for (message, _, _, _, _) in messages { if message.id == id { - return message + return EngineMessage(message) } } } @@ -4324,16 +4441,16 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH if foundCorrelationMessage { self.layoutActionOnViewTransition = nil let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition) - self.transaction(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options, scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in + self.transaction(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options.union(.Synchronous), scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in completion(true, result) }) } else { - self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in + self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options.union(.Synchronous), scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in completion(false, result) }) } } else { - self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in + self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options.union(.Synchronous), scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: { result in completion(false, result) }) } @@ -4557,7 +4674,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } - func lastVisbleMesssage() -> Message? { + func lastVisbleMesssage() -> EngineMessage? { var currentMessage: Message? if let historyView = self.historyView { if let visibleRange = self.displayedItemRange.visibleRange { @@ -4576,7 +4693,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } } - return currentMessage + return currentMessage.flatMap(EngineMessage.init) } func immediateScrollState() -> ChatInterfaceHistoryScrollState? { @@ -4669,7 +4786,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } - func requestMessageUpdate(_ id: MessageId, andScrollToItem scroll: Bool = false) { + func requestMessageUpdate(_ id: MessageId, andScrollToItem scroll: Bool = false, customTransition: ControlledTransition? = nil) { if let historyView = self.historyView { var messageItem: ChatMessageItem? self.forEachItemNode({ itemNode in @@ -4715,7 +4832,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) } - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, customAnimationTransition: customTransition, updateOpaqueState: nil, completion: { _ in }) break loop } case let .MessageGroupEntry(_, messages, presentationData): @@ -4736,7 +4853,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH scrollToItem = ListViewScrollToItem(index: index, position: .center(.top), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true) } - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: scrollToItem, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, customAnimationTransition: customTransition, updateOpaqueState: nil, completion: { _ in }) break loop } default: @@ -4787,7 +4904,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH item = ListMessageItem(presentationData: presentationData, systemStyle: self.systemStyle, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch) } let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil) - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion, .Synchronous], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) break loop } default: @@ -4984,7 +5101,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH } } - func scrollToMessage(index: MessageIndex) { + func scrollToMessage(index: MessageIndex, offset: CGFloat = 0.0) { self.appliedScrollToMessageId = nil self.scrollToMessageIdPromise.set(.single(index)) } diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationStack.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationStack.swift index a4d8aa1a72..80cc6569fd 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationStack.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationStack.swift @@ -1,15 +1,15 @@ import Foundation import UIKit -import Postbox +import TelegramCore struct ChatHistoryNavigationStack { - private var messageIndices: [MessageIndex] = [] + private var messageIndices: [EngineMessage.Index] = [] - mutating func add(_ index: MessageIndex) { + mutating func add(_ index: EngineMessage.Index) { self.messageIndices.append(index) } - mutating func removeLast() -> MessageIndex? { + mutating func removeLast() -> EngineMessage.Index? { if messageIndices.isEmpty { return nil } @@ -20,7 +20,7 @@ struct ChatHistoryNavigationStack { return self.messageIndices.isEmpty } - mutating func filterOutIndicesLessThan(_ index: MessageIndex) { + mutating func filterOutIndicesLessThan(_ index: EngineMessage.Index) { for i in (0 ..< self.messageIndices.count).reversed() { if self.messageIndices[i] <= index { self.messageIndices.remove(at: i) diff --git a/submodules/TelegramUI/Sources/ChatHistoryNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNode.swift index 6d8721b3b8..83885d27b0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNode.swift @@ -1,11 +1,11 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import SwiftSignalKit import Display import ChatPresentationInterfaceState import AccountContext +import TelegramCore public enum ChatHistoryNodeLoadState: Equatable { public enum EmptyType: Equatable { @@ -15,7 +15,7 @@ public enum ChatHistoryNodeLoadState: Equatable { case topic case botInfo } - + case loading(Bool) case empty(EmptyType) case messages @@ -24,11 +24,11 @@ public enum ChatHistoryNodeLoadState: Equatable { public protocol ChatHistoryNode: AnyObject { var historyState: ValuePromise { get } var preloadPages: Bool { get set } - + var loadState: ChatHistoryNodeLoadState? { get } func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) - - func messageInCurrentHistoryView(_ id: MessageId) -> Message? + + func messageInCurrentHistoryView(_ id: EngineMessage.Id) -> EngineMessage? func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) func forEachItemNode(_ f: (ASDisplayNode) -> Void) func disconnect() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index 0944f199b8..66ccd16977 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -27,6 +27,17 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult } } +private func hasBannedInlineContent(chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel { + let canBypass = canBypassRestrictions(chatPresentationInterfaceState: chatPresentationInterfaceState) + return channel.hasBannedPermission(.banSendInline, ignoreDefault: canBypass) != nil + } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup { + return group.hasBannedPermission(.banSendInline) + } else { + return false + } +} + func textInputContextPanel(context: AccountContext, chatPresentationInterfaceState: ChatPresentationInterfaceState, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, currentPanel: ChatInputContextPanelNode?) -> ChatInputContextPanelNode? { guard let controllerInteraction else { return nil @@ -45,15 +56,8 @@ func textInputContextPanel(context: AccountContext, chatPresentationInterfaceSta }).first else { return nil } - - var hasBannedInlineContent = false - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendInline) != nil { - hasBannedInlineContent = true - } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendInline) { - hasBannedInlineContent = true - } - - if hasBannedInlineContent { + + if hasBannedInlineContent(chatPresentationInterfaceState: chatPresentationInterfaceState) { switch inputQueryResult { case .stickers, .contextRequestResult: if let currentPanel = currentPanel as? DisabledContextResultsChatInputContextPanelNode { @@ -183,15 +187,8 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa }).first else { return nil } - - var hasBannedInlineContent = false - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendInline) != nil { - hasBannedInlineContent = true - } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendInline) { - hasBannedInlineContent = true - } - - if hasBannedInlineContent { + + if hasBannedInlineContent(chatPresentationInterfaceState: chatPresentationInterfaceState) { switch inputQueryResult { case .stickers, .contextRequestResult: if let currentPanel = currentPanel as? DisabledContextResultsChatInputContextPanelNode { @@ -305,4 +302,3 @@ func chatOverlayContextPanelForChatPresentationIntefaceState(_ chatPresentationI return nil } - diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 023a5d3e98..481efce7f5 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import Display import AccountContext import Emoji @@ -221,7 +220,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte var stickersEnabled = true var stickersAreEmoji = !isTextEmpty if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel { - if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(peer) { + if isTextEmpty, case .broadcast = peer.info, canSendMessagesToPeer(EnginePeer(peer)) { accessoryItems.append(.silentPost(chatPresentationInterfaceState.interfaceState.silentPosting)) } if let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift index 68e8f2aa23..a7483d8662 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputNodes.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import AccountContext import ChatPresentationInterfaceState import ChatControllerInteraction diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index a40d494a9c..2cdd37cd44 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -230,6 +230,9 @@ func textInputAccessoryPanel( previousTapTimestamp = CFAbsoluteTimeGetCurrent() interfaceInteraction?.presentReplyOptions(sourceView) }, + longPressAction: { _ in + interfaceInteraction?.navigateToMessage(replyMessageSubject.messageId, false, true, ChatLoadingMessageSubject.generic) + }, dismiss: { _ in interfaceInteraction?.setupReplyMessage(nil, nil, { _, f in f() }) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 798844b9d7..e58c57e392 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import Display @@ -29,6 +28,7 @@ import TranslateUI import DebugSettingsUI import ChatPresentationInterfaceState import Pasteboard +import BrowserUI import SettingsUI import TextNodeWithEntities import ChatControllerInteraction @@ -44,15 +44,15 @@ private struct MessageContextMenuData { let canPin: Bool let canEdit: Bool let canSelect: Bool - let resourceStatus: MediaResourceStatus? + let resourceStatus: EngineMediaResource.FetchStatus? let messageActions: ChatAvailableMessageActions } -func canEditMessage(context: AccountContext, limitsConfiguration: EngineConfiguration.Limits, message: Message) -> Bool { +func canEditMessage(context: AccountContext, limitsConfiguration: EngineConfiguration.Limits, message: EngineRawMessage) -> Bool { return canEditMessage(accountPeerId: context.account.peerId, limitsConfiguration: limitsConfiguration, message: message) } -private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineConfiguration.Limits, message: Message, reschedule: Bool = false) -> Bool { +private func canEditMessage(accountPeerId: EnginePeer.Id, limitsConfiguration: EngineConfiguration.Limits, message: EngineRawMessage, reschedule: Bool = false) -> Bool { var hasEditRights = false var unlimitedInterval = reschedule @@ -190,7 +190,7 @@ private func canEditFactCheck(appConfig: AppConfiguration) -> Bool { return false } -private func canViewReadStats(message: Message, participantCount: Int?, isMessageRead: Bool, isPremium: Bool, appConfig: AppConfiguration) -> Bool { +private func canViewReadStats(message: EngineRawMessage, participantCount: Int?, isMessageRead: Bool, isPremium: Bool, appConfig: AppConfiguration) -> Bool { guard let peer = message.peers[message.id.peerId] else { return false } @@ -285,7 +285,7 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag return true } -func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { +func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: EnginePeer.Id) -> Bool { if case let .customChatContents(contents) = chatPresentationInterfaceState.subject, case .hashTagSearch = contents.kind { return true } @@ -383,7 +383,7 @@ enum ChatMessageContextMenuAction { case sheet(ChatMessageContextMenuSheetAction) } -func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions { +func messageMediaEditingOptions(message: EngineRawMessage) -> MessageMediaEditingOptions { if message.id.peerId.namespace == Namespaces.Peer.SecretChat { return [] } @@ -436,7 +436,7 @@ func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions return options } -func updatedChatEditInterfaceMessageState(context: AccountContext, state: ChatPresentationInterfaceState, message: Message) -> (ChatPresentationInterfaceState, (UrlPreviewState?, Disposable)?) { +func updatedChatEditInterfaceMessageState(context: AccountContext, state: ChatPresentationInterfaceState, message: EngineRawMessage) -> (ChatPresentationInterfaceState, (UrlPreviewState?, Disposable)?) { var updated = state for media in message.media { if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { @@ -480,23 +480,35 @@ func updatedChatEditInterfaceMessageState(context: AccountContext, state: ChatPr ) } -func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [Message], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?, readStats: MessageReadStats? = nil, messageNode: ChatMessageItemView? = nil) -> Signal { +func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, messages: [EngineRawMessage], controllerInteraction: ChatControllerInteraction?, selectAll: Bool, interfaceInteraction: ChatPanelInterfaceInteraction?, readStats: MessageReadStats? = nil, messageNode: ChatMessageItemView? = nil) -> Signal { guard let interfaceInteraction = interfaceInteraction, let controllerInteraction = controllerInteraction else { return .single(ContextController.Items(content: .list([]))) } - if let message = messages.first, message.id.namespace < 0 { - return .single(ContextController.Items(content: .list([]))) + if let message = messages.first { + if message.id.namespace < 0 { + return .single(ContextController.Items(content: .list([]))) + } + if message.id.namespace == Namespaces.Message.Local && message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { + return .single(ContextController.Items(content: .list([]))) + } } var isEmbeddedMode = false if case .standard(.embedded) = chatPresentationInterfaceState.mode { isEmbeddedMode = true } - + if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject, case .hashTagSearch = customChatContents.kind { isEmbeddedMode = true } - + + let isSharedMediaPolls: Bool + if isEmbeddedMode, case let .tag(tag)? = chatPresentationInterfaceState.subject, tag == .polls { + isSharedMediaPolls = true + } else { + isSharedMediaPolls = false + } + var hasExpandedAudioTranscription = false if let messageNode = messageNode as? ChatMessageBubbleItemNode { hasExpandedAudioTranscription = messageNode.hasExpandedAudioTranscription() @@ -690,13 +702,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return .single(ContextController.Items(content: .list(actions))) } - var loadStickerSaveStatus: MediaId? - var loadCopyMediaResource: MediaResource? + var loadStickerSaveStatus: EngineMedia.Id? + var loadCopyMediaResource: TelegramMediaResource? var isAction = false var isGiveawayServiceMessage = false var diceEmoji: String? if messages.count == 1 { - for media in messages[0].media { + for media in messages[0].effectiveMedia { if let file = media as? TelegramMediaFile { if file.isSticker { loadStickerSaveStatus = file.fileId @@ -808,9 +820,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState |> map(Optional.init) } - var loadResourceStatusSignal: Signal = .single(nil) + var loadResourceStatusSignal: Signal = .single(nil) if let loadCopyMediaResource = loadCopyMediaResource { - loadResourceStatusSignal = context.account.postbox.mediaBox.resourceStatus(loadCopyMediaResource) + loadResourceStatusSignal = context.engine.resources.status(resource: EngineMediaResource(loadCopyMediaResource)) |> take(1) |> map(Optional.init) } @@ -867,7 +879,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState let isScheduled = chatPresentationInterfaceState.subject == .scheduledMessages - let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest( + let dataSignal: Signal<(MessageContextMenuData, [EngineMessage.Id: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest( loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, @@ -882,7 +894,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState context.engine.peers.notificationSoundList() |> take(1), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) ) - |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, isMessageRead, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in + |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, isMessageRead, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [EngineMessage.Id: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in let (limitsConfiguration, appConfig) = limitsAndAppConfig var canEdit = false if !isAction { @@ -937,9 +949,36 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState |> deliverOnMainQueue |> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer -> ContextController.Items in let isPremium = accountPeer?.isPremium ?? false - + var actions: [ContextMenuItem] = [] - + + if isSharedMediaPolls && messages.count == 1 { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.SharedMedia_ViewInChat, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + guard let navigationController = controllerInteraction.navigationController() else { + return + } + guard let peer = message.peers[message.id.peerId].flatMap(EnginePeer.init) else { + return + } + if case let .channel(channel) = peer, channel.isForumOrMonoForum, let threadId = message.threadId { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peer.id, threadId: threadId, messageId: message.id, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .default, animated: true).startStandalone() + } else { + let targetLocation: NavigateToChatControllerParams.Location + if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { + targetLocation = .replyThread(replyThreadMessage) + } else { + targetLocation = .peer(peer) + } + + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: targetLocation, subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false), keepStack: .always, useExisting: true)) + } + }) + }))) + } + var isPinnedMessages = false if case .pinnedMessages = chatPresentationInterfaceState.subject { isPinnedMessages = true @@ -998,7 +1037,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState strongController.dismiss() let id = Int64.random(in: Int64.min ... Int64.max) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: logPath, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: "CallStats.log")], alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: logPath, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: "CallStats.log")], alternativeRepresentations: []) let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).startStandalone() @@ -1049,7 +1088,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if !hasRateTranscription && message.minAutoremoveOrClearTimeout == nil { - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile, let size = file.size, size < 1 * 1024 * 1024, let duration = file.duration, duration < 60, (["audio/mpeg", "audio/mp3", "audio/mpeg3", "audio/ogg"] as [String]).contains(file.mimeType.lowercased()) { let fileName = file.fileName ?? "Tone" @@ -1104,7 +1143,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if !isPremium && isDownloading { var isLargeFile = false - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if let size = file.size, size >= 150 * 1024 * 1024 { isLargeFile = true @@ -1250,9 +1289,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } let message = messages[0] + var richMessageMarkdown: String? + if let richTextAttribute = message.attributes.first(where: { $0 is RichTextMessageAttribute }) as? RichTextMessageAttribute { + let markdown = markdownStringFromInstantPage(richTextAttribute.instantPage) + if !markdown.isEmpty { + richMessageMarkdown = markdown + } + } var isExpired = false var isImage = false - for media in message.media { + for media in message.effectiveMedia { if let _ = media as? TelegramMediaExpiredContent { isExpired = true } @@ -1262,7 +1308,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } let isCopyProtected = chatPresentationInterfaceState.copyProtectionEnabled || message.isCopyProtected() - if !messageText.isEmpty || (resourceAvailable && isImage) || diceEmoji != nil { + if !messageText.isEmpty || richMessageMarkdown != nil || (resourceAvailable && isImage) || diceEmoji != nil { if !isExpired { if !isPoll { if !isCopyProtected { @@ -1273,6 +1319,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState UIPasteboard.general.string = diceEmoji } else { let copyTextWithEntities = { + if let richMessageMarkdown { + storeMessageTextInPasteboard(richMessageMarkdown, entities: nil) + Queue.mainQueue().after(0.2, { + let content: UndoOverlayContent = .copy(text: chatPresentationInterfaceState.strings.Conversation_MessageCopied) + controllerInteraction.displayUndo(content) + }) + return + } var messageText = message.text var messageEntities: [MessageTextEntity]? var restrictedText: String? @@ -1310,12 +1364,12 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState }) } if resourceAvailable { - for media in message.media { + for media in message.effectiveMedia { if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { - let _ = (context.account.postbox.mediaBox.resourceData(largest.resource, option: .incremental(waitUntilFetchStatus: false)) + let _ = (context.engine.resources.data(resource: EngineMediaResource(largest.resource), incremental: true) |> take(1) |> deliverOnMainQueue).startStandalone(next: { data in - if data.complete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if data.isComplete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = UIImage(data: imageData) { if !messageText.isEmpty { copyTextWithEntities() @@ -1354,7 +1408,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState showTranslateIfTopical = true } - let (canTranslate, _) = canTranslateText(context: context, text: messageText, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages) + var (canTranslate, _) = canTranslateText(context: context, text: messageText, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: showTranslateIfTopical, ignoredLanguages: translationSettings.ignoredLanguages) + if let peerId = chatPresentationInterfaceState.chatLocation.peerId, peerId.namespace == Namespaces.Peer.SecretChat { + canTranslate = false + } if canTranslate { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuTranslate, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.actionSheet.primaryTextColor) @@ -1397,7 +1454,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if resourceAvailable, !message.containsSecretMedia && !isCopyProtected { var mediaReference: AnyMediaReference? var isVideo = false - for media in message.media { + for media in message.effectiveMedia { if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) { mediaReference = ImageMediaReference.standalone(media: image).abstract break @@ -1411,7 +1468,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: isVideo ? chatPresentationInterfaceState.strings.Gallery_SaveVideo : chatPresentationInterfaceState.strings.Gallery_SaveImage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: mediaReference) + let _ = (saveToCameraRoll(context: context, userLocation: .peer(message.id.peerId), mediaReference: mediaReference) |> deliverOnMainQueue).startStandalone(completed: { Queue.mainQueue().after(0.2) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -1424,7 +1481,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } var downloadableMediaResourceInfos: [String] = [] - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if let info = extractMediaResourceDebugInfo(resource: file.resource) { downloadableMediaResourceInfos.append(info) @@ -1439,7 +1496,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if !isCopyProtected { - for media in message.media { + for media in message.effectiveMedia { if let file = media as? TelegramMediaFile { if file.isMusic { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveToFiles, icon: { theme in @@ -1516,7 +1573,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var activeTodo: TelegramMediaTodo? for media in message.media { if let poll = media as? TelegramMediaPoll, !poll.isClosed, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll { - if !isPollEffectivelyClosed(message: message, poll: poll) { + if !isPollEffectivelyClosed(message: EngineMessage(message), poll: poll) { activePoll = poll } } else if let todo = media as? TelegramMediaTodo { @@ -1632,7 +1689,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if canPin { - var pinnedSelectedMessageId: MessageId? + var pinnedSelectedMessageId: EngineMessage.Id? for message in messages { if message.tags.contains(.pinned) { pinnedSelectedMessageId = message.id @@ -1655,7 +1712,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - if let activePoll = activePoll, messages[0].forwardInfo == nil { + if let activePoll, messages[0].forwardInfo == nil { var canStopPoll = false if !messages[0].flags.contains(.Incoming) { canStopPoll = true @@ -1701,7 +1758,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in - var threadMessageId: MessageId? + var threadMessageId: EngineMessage.Id? if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { threadMessageId = replyThreadMessage.effectiveMessageId } @@ -1862,6 +1919,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } var clearCacheAsDelete = false + var hasViewStats = false if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, !isMigrated { var views: Int = 0 var forwards: Int = 0 @@ -1882,11 +1940,28 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState controllerInteraction.openMessageStats(messages[0].id) }) }))) + hasViewStats = true } clearCacheAsDelete = true } + if !hasViewStats, messages[0].forwardInfo == nil { + for media in message.media { + if let poll = media as? TelegramMediaPoll, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll, poll.results.canViewStats { + actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ViewPollStats, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + let controller = context.sharedContext.makePollStatsScreen(context: context, messageId: messages[0].id) + controllerInteraction.navigationController()?.pushViewController(controller) + }) + }))) + break + } + } + } + if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, canEditFactCheck(appConfig: appConfig) { var canAddFactCheck = true if message.media.contains(where: { $0 is TelegramMediaAction || $0 is TelegramMediaGiveaway }) { @@ -2147,6 +2222,20 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState displayReadTimestamps = true } + let deleteReaction: ((EnginePeer, MessageReaction.Reaction) -> Void)? + if let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.hasPermission(.deleteAllMessages) { + deleteReaction = { [weak c] peer, _ in + c?.dismiss(completion: { + guard let chatController = interfaceInteraction.chatController() as? ChatController else { + return + } + chatController.presentReactionDeletionOptions(author: peer, messageId: message.id) + }) + } + } else { + deleteReaction = nil + } + c.pushItems(items: .single(ContextController.Items(content: .custom(ReactionListContextMenuContent( context: context, displayReadTimestamps: displayReadTimestamps, @@ -2163,7 +2252,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState c?.dismiss(completion: { controllerInteraction.openPeer(peer, .default, MessageReference(message), hasReaction ? .reaction : .default) }) - } + }, + deleteReaction: deleteReaction )), tip: tip))) } else { f(.default) @@ -2261,11 +2351,60 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } + for media in message.media { + if let poll = media as? TelegramMediaPoll, message.id.namespace == Namespaces.Message.Cloud, poll.pollId.namespace == Namespaces.Media.CloudPoll { + var restrictionText: String = "" + let peerName: String = chatPresentationInterfaceState.renderedPeer?.peer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" + + if !poll.countries.isEmpty { + let locale = localeWithStrings(chatPresentationInterfaceState.strings) + let countryNames = poll.countries.map { id in + if id == "FT" { + return "Fragment" + } else if let countryName = locale.localizedString(forRegionCode: id) { + return countryName + } else { + return id + } + } + var countries: String = "" + if countryNames.count == 1, let country = countryNames.first { + countries = "**\(country)**" + } else { + for i in 0 ..< countryNames.count { + countries.append("**\(countryNames[i])**") + if i == countryNames.count - 2 { + countries.append(chatPresentationInterfaceState.strings.Chat_Poll_Restriction_Country_CountriesLastDelimiter) + } else if i < countryNames.count - 2 { + countries.append(chatPresentationInterfaceState.strings.Chat_Poll_Restriction_Country_CountriesDelimiter) + } + } + } + if poll.restrictToSubscribers { + restrictionText = chatPresentationInterfaceState.strings.Chat_Poll_Restriction_SubscribersCountry(peerName, countries).string + } else { + restrictionText = chatPresentationInterfaceState.strings.Chat_Poll_Restriction_Country(countries).string + } + } else if poll.restrictToSubscribers { + restrictionText = chatPresentationInterfaceState.strings.Chat_Poll_Restriction_Subscribers(peerName).string + } + + if !restrictionText.isEmpty { + actions.append(.separator) + let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil + actions.append( + .action(ContextMenuActionItem(text: restrictionText, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in return nil }, action: noAction)) + ) + } + break + } + } + return ContextController.Items(content: .list(actions), tip: nil) } } -func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId: PeerId, message: Message, unlimitedInterval: Bool) -> Bool { +func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId: EnginePeer.Id, message: EngineRawMessage, unlimitedInterval: Bool) -> Bool { if message.id.peerId == accountPeerId { return true } @@ -2282,7 +2421,7 @@ func canPerformEditingActions(limits: LimitsConfiguration, accountPeerId: PeerId return false } -private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId: PeerId, message: Message) -> Bool { +private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId: EnginePeer.Id, message: EngineRawMessage) -> Bool { if message.id.peerId == accountPeerId { return true } @@ -2306,7 +2445,7 @@ private func canPerformDeleteActions(limits: LimitsConfiguration, accountPeerId: return false } -func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: PeerId, messageIds: Set, messages: [MessageId: Message] = [:], peers: [PeerId: Peer] = [:], keepUpdated: Bool) -> Signal { +func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: EnginePeer.Id, messageIds: Set, messages: [EngineMessage.Id: EngineRawMessage] = [:], peers: [EnginePeer.Id: EngineRawPeer] = [:], keepUpdated: Bool) -> Signal { return engine.data.subscribe( TelegramEngine.EngineData.Item.Configuration.Limits(), EngineDataMap(Set(messageIds.map(\.peerId)).map(TelegramEngine.EngineData.Item.Peer.Peer.init)), @@ -2324,9 +2463,9 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer isPremium = false } - var optionsMap: [MessageId: ChatAvailableMessageActionOptions] = [:] - var banPeer: Peer? - var banPeers: [Peer] = [] + var optionsMap: [EngineMessage.Id: ChatAvailableMessageActionOptions] = [:] + var banPeer: EngineRawPeer? + var banPeers: [EngineRawPeer] = [] var hadPersonalIncoming = false var hadBanPeerId = false var disableDelete = false @@ -2337,7 +2476,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer var setTag = false var commonTags: Set? - func getPeer(_ peerId: PeerId) -> Peer? { + func getPeer(_ peerId: EnginePeer.Id) -> EngineRawPeer? { if let maybePeer = peerMap[peerId], let peer = maybePeer { return peer._asPeer() } else if let peer = peers[peerId] { @@ -2347,7 +2486,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer } } - func getMessage(_ messageId: MessageId) -> Message? { + func getMessage(_ messageId: EngineMessage.Id) -> EngineRawMessage? { if let maybeMessage = messageMap[messageId], let message = maybeMessage { return message._asMessage() } else if let message = messages[messageId] { @@ -2357,7 +2496,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer } } - func isPeerCopyProtected(_ peerId: PeerId) -> Bool? { + func isPeerCopyProtected(_ peerId: EnginePeer.Id) -> Bool? { let copyProtection = copyProtectionMap[peerId] let myCopyProtection = myCopyProtectionMap[peerId] if copyProtection == true || myCopyProtection == true { @@ -2620,7 +2759,7 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer commonTags = nil } - return ChatAvailableMessageActions(options: reducedOptions, banAuthor: banPeer, banAuthors: banPeers, disableDelete: disableDelete, isCopyProtected: isCopyProtected, setTag: setTag, editTags: commonTags ?? Set()) + return ChatAvailableMessageActions(options: reducedOptions, banAuthor: banPeer.flatMap(EnginePeer.init), banAuthors: banPeers.map(EnginePeer.init), disableDelete: disableDelete, isCopyProtected: isCopyProtected, setTag: setTag, editTags: commonTags ?? Set()) } else { return ChatAvailableMessageActions(options: [], banAuthor: nil, banAuthors: [], disableDelete: false, isCopyProtected: isCopyProtected, setTag: false, editTags: Set()) } @@ -2824,10 +2963,10 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu final class ChatMessageAuthorContextItem: ContextMenuCustomItem { fileprivate let context: AccountContext - fileprivate let message: Message + fileprivate let message: EngineRawMessage fileprivate let action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, EnginePeer) -> Void)? - init(context: AccountContext, message: Message, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, EnginePeer) -> Void)?) { + init(context: AccountContext, message: EngineRawMessage, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, EnginePeer) -> Void)?) { self.context = context self.message = message self.action = action @@ -3068,13 +3207,13 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu final class ChatReadReportContextItem: ContextMenuCustomItem { fileprivate let context: AccountContext - fileprivate let message: Message + fileprivate let message: EngineRawMessage fileprivate let hasReadReports: Bool fileprivate let isEdit: Bool fileprivate let stats: MessageReadStats? fileprivate let action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, MessageReadStats?, [StickerPackCollectionInfo], TelegramMediaFile?) -> Void)? - init(context: AccountContext, message: Message, hasReadReports: Bool, isEdit: Bool, stats: MessageReadStats?, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, MessageReadStats?, [StickerPackCollectionInfo], TelegramMediaFile?) -> Void)?) { + init(context: AccountContext, message: EngineRawMessage, hasReadReports: Bool, isEdit: Bool, stats: MessageReadStats?, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void, MessageReadStats?, [StickerPackCollectionInfo], TelegramMediaFile?) -> Void)?) { self.context = context self.message = message self.hasReadReports = hasReadReports @@ -3656,10 +3795,10 @@ private func stringForRemainingTime(_ duration: Int32, strings: PresentationStri final class ChatRateTranscriptionContextItem: ContextMenuCustomItem { fileprivate let context: AccountContext - fileprivate let message: Message + fileprivate let message: EngineRawMessage fileprivate let action: (Bool) -> Void - init(context: AccountContext, message: Message, action: @escaping (Bool) -> Void) { + init(context: AccountContext, message: EngineRawMessage, action: @escaping (Bool) -> Void) { self.context = context self.message = message self.action = action diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index 06271060c2..2b3f3484ea 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramUIPreferences import LegacyComponents import TextFormat @@ -14,7 +13,7 @@ import TelegramNotices import ChatPresentationInterfaceState import ChatContextQuery -func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)], requestBotLocationStatus: @escaping (PeerId) -> Void) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] { +func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)], requestBotLocationStatus: @escaping (EnginePeer.Id) -> Void) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] { let inputQueries = inputContextQueriesForChatPresentationIntefaceState(chatPresentationInterfaceState).filter({ query in if chatPresentationInterfaceState.editMessageState != nil { switch query { @@ -54,7 +53,7 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation return updates } -private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (PeerId) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> { +private func updatedContextQueryResultStateForQuery(context: AccountContext, peer: EngineRawPeer?, chatLocation: ChatLocation, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?, requestBotLocationStatus: @escaping (EnginePeer.Id) -> Void) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> { switch inputQuery { case let .emoji(query): var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete() @@ -69,9 +68,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee signal = .single({ _ in return .stickers([]) }) } - let stickerConfiguration = context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let stickerConfiguration = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> map { preferencesView -> StickersSearchConfiguration in - let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue return StickersSearchConfiguration.with(appConfiguration: appConfiguration) } let stickerSettings = context.sharedContext.accountManager.transaction { transaction -> StickerSettings in @@ -375,14 +374,14 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee if query.isSingleEmoji { return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), + context.engine.itemCollections.allItems(namespace: Namespaces.ItemCollection.CloudEmojiPacks), hasPremium ) - |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in + |> map { items, hasPremium -> [(String, TelegramMediaFile?, String)] in var result: [(String, TelegramMediaFile?, String)] = [] - - for entry in view.entries { - guard let item = entry.item as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else { + + for entry in items { + guard let item = entry as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else { continue } let stringRepresentations = item.getStringRepresentationsOfIndexKeys() @@ -418,21 +417,21 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee |> castError(ChatContextQueryError.self) |> mapToSignal { keywords -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in return combineLatest( - context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000), + context.engine.itemCollections.allItems(namespace: Namespaces.ItemCollection.CloudEmojiPacks), hasPremium ) - |> map { view, hasPremium -> [(String, TelegramMediaFile?, String)] in + |> map { items, hasPremium -> [(String, TelegramMediaFile?, String)] in var result: [(String, TelegramMediaFile?, String)] = [] - + var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword } } - - for entry in view.entries { - guard let item = entry.item as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else { + + for entry in items { + guard let item = entry as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else { continue } let stringRepresentations = item.getStringRepresentationsOfIndexKeys() @@ -556,7 +555,7 @@ struct UrlPreviewState { var detectedUrls: [String] } -func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?, forPeerId: PeerId?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? { +func urlPreviewStateForInputText(_ inputText: NSAttributedString?, context: AccountContext, currentQuery: UrlPreviewState?, forPeerId: EnginePeer.Id?) -> (UrlPreviewState?, Signal<(TelegramMediaWebpage?) -> (TelegramMediaWebpage, String)?, NoError>)? { guard let _ = inputText else { if currentQuery != nil { return (nil, .single({ _ in return nil })) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 803f089285..c5e4ae8176 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -47,12 +46,12 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha } if canClear { - let buttonItem = UIBarButtonItem(title: "___clear", style: .plain, target: target, action: selector) + let buttonItem = UIBarButtonItem(title: strings.Conversation_ClearAll, style: .plain, target: target, action: selector) buttonItem.accessibilityLabel = title return ChatNavigationButton(action: .clearHistory, buttonItem: buttonItem) } else { title = strings.Conversation_ClearCache - let buttonItem = UIBarButtonItem(title: "___clear", style: .plain, target: target, action: selector) + let buttonItem = UIBarButtonItem(title: strings.Conversation_ClearCache, style: .plain, target: target, action: selector) buttonItem.accessibilityLabel = title return ChatNavigationButton(action: .clearCache, buttonItem: buttonItem) } @@ -67,7 +66,7 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha if let currentButton = currentButton, currentButton.action == .dismiss { return currentButton } else { - let buttonItem = UIBarButtonItem(title: strings.Common_Close, style: .plain, target: target, action: selector) + let buttonItem = UIBarButtonItem(title: "___close", style: .plain, target: target, action: selector) buttonItem.accessibilityLabel = strings.Common_Close return ChatNavigationButton(action: .dismiss, buttonItem: buttonItem) } @@ -78,6 +77,9 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha } func rightNavigationButtonForChatInterfaceState(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, strings: PresentationStrings, currentButton: ChatNavigationButton?, target: Any?, selector: Selector?, chatInfoNavigationButton: ChatNavigationButton?, moreInfoNavigationButton: ChatNavigationButton?) -> ChatNavigationButton? { + if case .standard(.previewing) = presentationInterfaceState.mode { + return nil + } var hasMessages = false if let chatHistoryState = presentationInterfaceState.chatHistoryState { if case .loaded(false, _) = chatHistoryState { diff --git a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift index b80d3cdb95..47ac7716c8 100644 --- a/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatInviteRequestsTitlePanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData @@ -13,101 +12,6 @@ import AccountContext import ChatPresentationInterfaceState import LegacyChatHeaderPanelComponent -private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { - private var theme: PresentationTheme? - - private let labelNode: ImmediateTextNode - private let filledBackgroundNode: LinkHighlightingNode - - private let openPeersNearby: () -> Void - - init(openPeersNearby: @escaping () -> Void) { - self.openPeersNearby = openPeersNearby - - self.labelNode = ImmediateTextNode() - self.labelNode.maximumNumberOfLines = 1 - self.labelNode.textAlignment = .center - - self.filledBackgroundNode = LinkHighlightingNode(color: .clear) - - super.init() - - self.addSubnode(self.filledBackgroundNode) - self.addSubnode(self.labelNode) - } - - override func didLoad() { - super.didLoad() - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.view.addGestureRecognizer(tapRecognizer) - } - - @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { - self.openPeersNearby() - } - - func update(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, chatPeer: Peer, distance: Int32, transition: ContainedViewLayoutTransition) -> CGFloat { - let primaryTextColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper).primaryText - - if self.theme !== theme { - self.theme = theme - - self.labelNode.linkHighlightColor = primaryTextColor.withAlphaComponent(0.3) - } - - let topInset: CGFloat = 6.0 - let bottomInset: CGFloat = 6.0 - let sideInset: CGFloat = 16.0 - - let stringAndRanges = strings.Conversation_PeerNearbyDistance(EnginePeer(chatPeer).compactDisplayTitle, shortStringForDistance(strings: strings, distance: distance)) - - let attributedString = NSMutableAttributedString(string: stringAndRanges.string, font: Font.regular(13.0), textColor: primaryTextColor) - - let boldAttributes = [NSAttributedString.Key.font: Font.semibold(13.0), NSAttributedString.Key(rawValue: "_Link"): true as NSNumber] - for range in stringAndRanges.ranges.prefix(1) { - attributedString.addAttributes(boldAttributes, range: range.range) - } - - self.labelNode.attributedText = attributedString - let labelLayout = self.labelNode.updateLayoutFullInfo(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) - - var labelRects = labelLayout.linesRects() - if labelRects.count > 1 { - let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) - for i in 0 ..< sortedIndices.count { - let index = sortedIndices[i] - for j in -1 ... 1 { - if j != 0 && index + j >= 0 && index + j < sortedIndices.count { - if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { - labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) - labelRects[index].size.width = labelRects[index + j].size.width - } - } - } - } - } - for i in 0 ..< labelRects.count { - labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0)) - labelRects[i].size.height = 20.0 - labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) - } - - let backgroundLayout = self.filledBackgroundNode.asyncLayout() - let serviceColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper) - let backgroundApply = backgroundLayout(serviceColor.fill, labelRects, 10.0, 10.0, 0.0) - backgroundApply() - - let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) - - let labelFrame = CGRect(origin: CGPoint(x: floor((width - labelLayout.size.width) / 2.0), y: topInset + floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) - self.labelNode.frame = labelFrame - self.filledBackgroundNode.frame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) - - return topInset + backgroundSize.height + bottomInset - } -} - final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { private final class Params { let width: CGFloat @@ -139,7 +43,7 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { private var theme: PresentationTheme? - private var peerId: PeerId? + private var peerId: EnginePeer.Id? private var peers: [EnginePeer] = [] private var count: Int32 = 0 @@ -184,7 +88,7 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { } - func update(peerId: PeerId, peers: [EnginePeer], count: Int32) { + func update(peerId: EnginePeer.Id, peers: [EnginePeer], count: Int32) { self.peerId = peerId self.peers = peers self.count = count @@ -202,7 +106,18 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { if interfaceState.theme !== self.theme { self.theme = interfaceState.theme - self.closeButton.setImage(PresentationResourcesChat.chatInputPanelEncircledCloseIconImage(interfaceState.theme), for: []) + self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(interfaceState.theme.chat.inputPanel.panelControlColor.cgColor) + context.setLineWidth(1.33) + context.setLineCap(.round) + context.move(to: CGPoint(x: 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 1.0, y: 1.0)) + context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0)) + context.strokePath() + }), for: []) self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor } @@ -211,14 +126,14 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { let contentRightInset: CGFloat = 14.0 + rightInset let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0)) - transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)) + transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: width - contentRightInset - closeButtonSize.width - 3.0, y: floorToScreenPixels((panelHeight - closeButtonSize.height) / 2.0)), size: closeButtonSize)) - self.buttonTitle.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RequestsToJoin(self.count), font: Font.regular(16.0), textColor: interfaceState.theme.rootController.navigationBar.accentTextColor) + self.buttonTitle.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_RequestsToJoin(self.count), font: Font.medium(15.0), textColor: interfaceState.theme.chat.inputPanel.panelControlColor) transition.updateFrame(node: self.button, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: panelHeight))) let titleSize = self.buttonTitle.updateLayout(CGSize(width: width - leftInset - 90.0 - contentRightInset, height: 100.0)) - var buttonTitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - titleSize.width) * 0.5), y: floor((panelHeight - titleSize.height) * 0.5)), size: titleSize) + var buttonTitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - titleSize.width) * 0.5), y: floor((panelHeight - titleSize.height) * 0.5) + 1.0), size: titleSize) buttonTitleFrame.origin.x = max(buttonTitleFrame.minX, leftInset + 90.0) transition.updatePosition(node: self.buttonTitle, position: buttonTitleFrame.origin) self.buttonTitle.bounds = CGRect(origin: CGPoint(), size: buttonTitleFrame.size) @@ -228,7 +143,7 @@ final class ChatInviteRequestsTitlePanelNode: ChatTitleAccessoryPanelNode { if let avatarsContent = self.avatarsContent { let avatarsSize = self.avatarsNode.update(context: self.context, content: avatarsContent, itemSize: CGSize(width: 32.0, height: 32.0), animated: true, synchronousLoad: true) - transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: leftInset + 8.0, y: floor((panelHeight - avatarsSize.height) / 2.0)), size: avatarsSize)) + transition.updateFrame(node: self.avatarsNode, frame: CGRect(origin: CGPoint(x: leftInset + 4.0, y: floor((panelHeight - avatarsSize.height) / 2.0)), size: avatarsSize)) } self.activateAreaNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: panelHeight)) diff --git a/submodules/TelegramUI/Sources/ChatLinkPreview.swift b/submodules/TelegramUI/Sources/ChatLinkPreview.swift index 7dd4ac4884..643451dd21 100644 --- a/submodules/TelegramUI/Sources/ChatLinkPreview.swift +++ b/submodules/TelegramUI/Sources/ChatLinkPreview.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramNotices diff --git a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift index c9dae58747..1467fa6d33 100644 --- a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift @@ -97,9 +97,9 @@ private final class ChatManagingBotTitlePanelComponent: Component { let topInset: CGFloat = 6.0 let bottomInset: CGFloat = 6.0 let avatarDiameter: CGFloat = 36.0 - let avatarTextSpacing: CGFloat = 10.0 + let avatarTextSpacing: CGFloat = 8.0 let titleTextSpacing: CGFloat = 1.0 - let leftInset: CGFloat = component.insets.left + 12.0 + let leftInset: CGFloat = component.insets.left + 6.0 let rightInset: CGFloat = component.insets.right + 10.0 let actionAndSettingsButtonsSpacing: CGFloat = 8.0 @@ -134,7 +134,7 @@ private final class ChatManagingBotTitlePanelComponent: Component { component: AnyComponent(PlainButtonComponent( content: AnyComponent(BundleIconComponent( name: "Chat/Context Menu/Customize", - tintColor: component.theme.rootController.navigationBar.controlColor + tintColor: component.theme.chat.inputPanel.panelControlColor )), effectAlignment: .center, minSize: CGSize(width: 1.0, height: 40.0), @@ -164,7 +164,7 @@ private final class ChatManagingBotTitlePanelComponent: Component { let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)) + text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.chat.inputPanel.panelControlColor)) )), environment: {}, containerSize: CGSize(width: maxTextWidth, height: 100.0) @@ -178,7 +178,7 @@ private final class ChatManagingBotTitlePanelComponent: Component { let textSize = self.text.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: textValue, font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor)) + text: .plain(NSAttributedString(string: textValue, font: Font.regular(14.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor)) )), environment: {}, containerSize: CGSize(width: maxTextWidth, height: 100.0) @@ -353,7 +353,7 @@ final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode { guard let navigationController = chatController.navigationController as? NavigationController else { return } - if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { navigationController.pushViewController(controller) } }) diff --git a/submodules/TelegramUI/Sources/ChatMessageActionUrlAuthController.swift b/submodules/TelegramUI/Sources/ChatMessageActionUrlAuthController.swift index 4e65670b29..1b29e65e76 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionUrlAuthController.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionUrlAuthController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,7 +23,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode { private let nameDisplayOrder: PresentationPersonNameOrder private let defaultUrl: String private let domain: String - private let bot: Peer + private let bot: EnginePeer private let displayName: String private let titleNode: ASTextNode @@ -62,7 +61,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode { } } - init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, defaultUrl: String, domain: String, bot: Peer, requestWriteAccess: Bool, displayName: String, actions: [TextAlertAction]) { + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, defaultUrl: String, domain: String, bot: EnginePeer, requestWriteAccess: Bool, displayName: String, actions: [TextAlertAction]) { self.strings = strings self.nameDisplayOrder = nameDisplayOrder self.defaultUrl = defaultUrl @@ -163,7 +162,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode { self.textNode.attributedText = formattedText(strings.Conversation_OpenBotLinkText(self.defaultUrl).string, color: theme.primaryColor, textAlignment: .center) self.authorizeLabelNode.attributedText = formattedText(strings.Conversation_OpenBotLinkLogin(self.domain, self.displayName).string, color: theme.primaryColor) - self.allowWriteLabelNode.attributedText = formattedText(strings.Conversation_OpenBotLinkAllowMessages(EnginePeer(self.bot).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)).string, color: theme.primaryColor) + self.allowWriteLabelNode.attributedText = formattedText(strings.Conversation_OpenBotLinkAllowMessages(self.bot.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)).string, color: theme.primaryColor) self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { @@ -301,7 +300,7 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode { } } -func chatMessageActionUrlAuthController(context: AccountContext, defaultUrl: String, domain: String, bot: Peer, requestWriteAccess: Bool, displayName: String, open: @escaping (Bool, Bool) -> Void) -> AlertController { +func chatMessageActionUrlAuthController(context: AccountContext, defaultUrl: String, domain: String, bot: EnginePeer, requestWriteAccess: Bool, displayName: String, open: @escaping (Bool, Bool) -> Void) -> AlertController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let theme = presentationData.theme let strings = presentationData.strings diff --git a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift index ddc1729688..71d1ba707b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift +++ b/submodules/TelegramUI/Sources/ChatMessageContextControllerContentSource.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import ContextUI -import Postbox import TelegramCore import SwiftSignalKit import ChatMessageItemView @@ -38,7 +37,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou private weak var chatController: ChatControllerImpl? private weak var chatNode: ChatControllerNode? private let engine: TelegramEngine - private let message: Message + private let message: EngineMessage private let selectAll: Bool private let snapshot: Bool @@ -61,7 +60,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou |> distinctUntilChanged } - init(chatController: ChatControllerImpl, chatNode: ChatControllerNode, engine: TelegramEngine, message: Message, selectAll: Bool, centerVertically: Bool = false, keepDefaultContentTouches: Bool = false, snapshot: Bool = false) { + init(chatController: ChatControllerImpl, chatNode: ChatControllerNode, engine: TelegramEngine, message: EngineMessage, selectAll: Bool, centerVertically: Bool = false, keepDefaultContentTouches: Bool = false, snapshot: Bool = false) { self.chatController = chatController self.chatNode = chatNode self.engine = engine @@ -88,8 +87,14 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) { - result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) - + let sourceTransitionSurface: UIView? + if self.snapshot { + sourceTransitionSurface = nil + } else { + sourceTransitionSurface = chatNode.ensureContextTransitionContainer() + } + result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface) + if self.snapshot, let snapshotView = contentNode.contentNode.view.snapshotContentTree(unhide: false, keepPortals: true, keepTransform: true) { contentNode.view.superview?.addSubview(snapshotView) self.snapshotView = snapshotView @@ -113,10 +118,16 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }) { - result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + let sourceTransitionSurface: UIView? + if self.snapshot { + sourceTransitionSurface = nil + } else { + sourceTransitionSurface = chatNode.ensureContextTransitionContainer() + } + result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: sourceTransitionSurface) } } - + return result } } @@ -134,7 +145,7 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo private weak var chatNode: ChatControllerNode? private weak var backgroundNode: WallpaperBackgroundNode? private let engine: TelegramEngine - private let message: Message + private let message: EngineMessage private let present: (ViewController) -> Void private var messageNodeCopy: ChatMessageItemView? @@ -167,7 +178,7 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo ) } - init(context: AccountContext, presentationData: PresentationData, chatNode: ChatControllerNode, backgroundNode: WallpaperBackgroundNode, engine: TelegramEngine, message: Message, present: @escaping (ViewController) -> Void) { + init(context: AccountContext, presentationData: PresentationData, chatNode: ChatControllerNode, backgroundNode: WallpaperBackgroundNode, engine: TelegramEngine, message: EngineMessage, present: @escaping (ViewController) -> Void) { self.context = context self.presentationData = presentationData self.chatNode = chatNode @@ -220,7 +231,7 @@ final class ChatViewOnceMessageContextExtractedContentSource: ContextExtractedCo if (isIncoming || "".isEmpty) { let messageItem = self.context.sharedContext.makeChatMessagePreviewItem( context: self.context, - messages: [self.message], + messages: [self.message._asMessage()], theme: self.presentationData.theme, strings: self.presentationData.strings, wallpaper: self.presentationData.chatWallpaper, @@ -424,7 +435,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo private weak var chatNode: ChatControllerNode? private let engine: TelegramEngine - private let message: Message + private let message: EngineMessage private let contentView: ContextExtractedContentContainingView var shouldBeDismissed: Signal { @@ -443,7 +454,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo |> distinctUntilChanged } - init(chatNode: ChatControllerNode, engine: TelegramEngine, message: Message, contentView: ContextExtractedContentContainingView) { + init(chatNode: ChatControllerNode, engine: TelegramEngine, message: EngineMessage, contentView: ContextExtractedContentContainingView) { self.chatNode = chatNode self.engine = engine self.message = message @@ -464,17 +475,17 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }) { - result = ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + result = ContextControllerTakeViewInfo(containingItem: .view(self.contentView), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer()) } } return result } - + func putBack() -> ContextControllerPutBackViewInfo? { guard let chatNode = self.chatNode else { return nil } - + var result: ContextControllerPutBackViewInfo? chatNode.historyNode.forEachItemNode { itemNode in guard let itemNode = itemNode as? ChatMessageItemView else { @@ -484,7 +495,7 @@ final class ChatMessageReactionContextExtractedContentSource: ContextExtractedCo return } if item.content.contains(where: { $0.0.stableId == self.message.stableId }) { - result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil)) + result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil), sourceTransitionSurface: chatNode.ensureContextTransitionContainer()) } } return result diff --git a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift index 48f96157ca..88854db52e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -22,7 +21,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode { private var strings: PresentationStrings private let peerMedia: Bool - var selectedMessages = Set() { + var selectedMessages = Set() { didSet { if oldValue != self.selectedMessages { self.reportButton.isEnabled = self.selectedMessages.count != 0 @@ -72,7 +71,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode { } } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift index a29c5cb6f1..825dfc9748 100644 --- a/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift +++ b/submodules/TelegramUI/Sources/ChatMessageThrottledProcessingManager.swift @@ -1,7 +1,7 @@ import Foundation import UIKit -import Postbox import SwiftSignalKit +import TelegramCore final class ChatMessageThrottledProcessingManager { private let queue = Queue.mainQueue() @@ -9,25 +9,25 @@ final class ChatMessageThrottledProcessingManager { private let delay: Double private let submitInterval: Double? - var process: ((Set) -> Void)? + var process: ((Set) -> Void)? private var timer: SwiftSignalKit.Timer? - private var processedList: [MessageAndThreadId] = [] - private var processed: [MessageAndThreadId: Double] = [:] - private var buffer = Set() + private var processedList: [EngineMessageAndThreadId] = [] + private var processed: [EngineMessageAndThreadId: Double] = [:] + private var buffer = Set() init(delay: Double = 1.0, submitInterval: Double? = nil) { self.delay = delay self.submitInterval = submitInterval } - func setProcess(process: @escaping (Set) -> Void) { + func setProcess(process: @escaping (Set) -> Void) { self.queue.async { self.process = process } } - func add(_ messageIds: [MessageAndThreadId]) { + func add(_ messageIds: [EngineMessageAndThreadId]) { self.queue.async { let timestamp = CFAbsoluteTimeGetCurrent() @@ -80,9 +80,9 @@ final class ChatMessageVisibleThrottledProcessingManager { private let interval: Double - private var currentIds = Set() + private var currentIds = Set() - var process: ((Set) -> Void)? + var process: ((Set) -> Void)? private let timer: SwiftSignalKit.Timer @@ -107,13 +107,13 @@ final class ChatMessageVisibleThrottledProcessingManager { self.timer.invalidate() } - func setProcess(process: @escaping (Set) -> Void) { + func setProcess(process: @escaping (Set) -> Void) { self.queue.async { self.process = process } } - func update(_ ids: Set) { + func update(_ ids: Set) { self.queue.async { if self.currentIds != ids { self.currentIds = ids diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 82217835b6..2074c849b6 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -6,7 +6,6 @@ import ContextUI import AnimatedStickerNode import SwiftSignalKit import ContextUI -import Postbox import TelegramCore import ReactionSelectionNode import ChatControllerInteraction @@ -31,6 +30,64 @@ private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView } } +private func pendingAdditiveSublayerTranslation(_ layer: CALayer) -> CGPoint { + guard let keys = layer.animationKeys() else { return .zero } + var result = CGPoint.zero + for key in keys { + guard let anim = layer.animation(forKey: key) as? CABasicAnimation, + anim.keyPath == "sublayerTransform", + anim.isAdditive, + let fromValue = anim.fromValue as? NSValue else { + continue + } + let t = fromValue.caTransform3DValue + result.x += t.m41 + result.y += t.m42 + } + return result +} + +/// Convert a rect expressed in window coordinates to `toView`'s local coordinates, +/// accounting for pending additive `sublayerTransform` animations on any ancestor +/// of `toView`. Returns the position in `toView.bounds` that will render at +/// `windowRect` visually at t=0 of the pending animations (once CA commits them). +/// +/// Standard `toView.layer.convert(windowRect, from: nil)` reads model transforms +/// only, so it yields the position that will render at `windowRect` *at t=end* of +/// any pending additive animations (since additive animations don't change model +/// values). For source-side morph calibration, where the snapshot was captured at +/// pre-animation state, the t=0 position is what we want. +/// +/// Walks the layer chain top-down from the root to `toView`. At each parent→child +/// step it subtracts the parent's pending additive `sublayerTransform` translation +/// *in the parent's own bounds coord space*, then does the standard one-step +/// `convert(_:to:)` into the child. Applying the correction at the right level +/// (rather than flat-summing the translations in the destination space) lets +/// `CALayer.convert` propagate each correction through any remaining +/// transforms — child `transform`, further ancestors' own model +/// `sublayerTransform`, etc. — so the result is correct even when the chain +/// contains non-translation transforms (rotations, scales). +private func convertAnimatingSourceRectFromWindow(_ windowRect: CGRect, toView: UIView) -> CGRect { + var chain: [CALayer] = [] + var layer: CALayer? = toView.layer + while let cur = layer { + chain.append(cur) + layer = cur.superlayer + } + chain.reverse() + + var r = windowRect + for i in 0..<(chain.count - 1) { + let parent = chain[i] + let child = chain[i + 1] + + let pending = pendingAdditiveSublayerTranslation(parent) + let adjustedR = r.offsetBy(dx: -pending.x, dy: -pending.y) + r = parent.convert(adjustedR, to: child) + } + return r +} + private final class OverlayTransitionContainerNode: ViewControllerTracingNode { override init() { super.init() @@ -388,9 +445,11 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran func beginAnimation() { if let portalTargetView = self.portalTargetView { portalTargetView.view.alpha = 0.0 - portalTargetView.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15) + portalTargetView.view.layer.allowsGroupOpacity = true + portalTargetView.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.14, delay: 0.14) - self.portalSourceView.layer.animateAlpha(from: 0.01, to: 1.0, duration: 0.12) + self.portalSourceView.layer.allowsGroupOpacity = true + self.portalSourceView.layer.animateAlpha(from: 0.01, to: 1.0, duration: 0.1, delay: 0.12) } let verticalDuration: Double = ChatMessageTransitionNodeImpl.animationDuration @@ -428,12 +487,11 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: self.view) - let sourceRect = self.view.convert(initialTextInput.sourceRect, from: nil) + let sourceRect = convertAnimatingSourceRectFromWindow(initialTextInput.sourceRect, toView: self.view) let sourceBackgroundAbsoluteRect = initialTextInput.backgroundView.frame.offsetBy(dx: sourceRect.minX, dy: sourceRect.minY) let sourceAbsoluteRect = CGRect(origin: CGPoint(x: sourceBackgroundAbsoluteRect.minX, y: sourceBackgroundAbsoluteRect.maxY - self.contextSourceNode.contentRect.height), size: self.contextSourceNode.contentRect.size) let textInput = ChatMessageTransitionNodeImpl.Source.TextInput(backgroundView: initialTextInput.backgroundView, contentView: initialTextInput.contentView, sourceRect: sourceRect, scrollOffset: initialTextInput.scrollOffset) - textInput.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: sourceAbsoluteRect.height - sourceBackgroundAbsoluteRect.height), size: textInput.backgroundView.bounds.size) textInput.contentView.frame = textInput.contentView.frame.offsetBy(dx: 0.0, dy: sourceAbsoluteRect.height - sourceBackgroundAbsoluteRect.height) @@ -443,7 +501,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran var replySourceAbsoluteFrame: CGRect if let storedFrameBeforeDismissed = replyPanel.storedFrameBeforeDismissed { - replySourceAbsoluteFrame = self.view.convert(storedFrameBeforeDismissed, from: nil) + replySourceAbsoluteFrame = convertAnimatingSourceRectFromWindow(storedFrameBeforeDismissed, toView: self.view) } else { replySourceAbsoluteFrame = replyPanelParentView.convert(replyPanelFrame, to: self.view) } @@ -477,6 +535,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size) + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: verticalCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in guard let strongSelf = self else { return @@ -484,6 +543,7 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran strongSelf.endAnimation() }) self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: horizontalCurve.mediaTimingFunction, additive: true) + self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), horizontalCurve, horizontalDuration) self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), verticalCurve, verticalDuration) @@ -1098,8 +1158,10 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran overlayController.displayNode.addSubnode(animatingItemNode) animatingItemNode.overlayController = overlayController self.listNode.context.sharedContext.mainWindow?.presentInGlobalOverlay(overlayController) + animatingItemNode.frame = self.bounds default: - self.addSubnode(animatingItemNode) + animatingItemNode.frame = CGRect() + itemNode.addSubnode(animatingItemNode) } animatingItemNode.animationEnded = { [weak self, weak animatingItemNode] in @@ -1120,7 +1182,6 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - animatingItemNode.frame = self.bounds animatingItemNode.beginAnimation() self.onTransitionEvent(.animated(duration: ChatMessageTransitionNodeImpl.animationDuration, curve: ChatMessageTransitionNodeImpl.verticalAnimationCurve)) @@ -1149,15 +1210,15 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } } - func addMessageContextController(messageId: MessageId, contextController: ContextController) { + func addMessageContextController(messageId: EngineMessage.Id, contextController: ContextController) { self.addMessageReactionContextContext(messageId: messageId, contextController: contextController, standaloneReactionAnimation: nil) } - func addMessageStandaloneReactionAnimation(messageId: MessageId, standaloneReactionAnimation: StandaloneReactionAnimation) { + func addMessageStandaloneReactionAnimation(messageId: EngineMessage.Id, standaloneReactionAnimation: StandaloneReactionAnimation) { self.addMessageReactionContextContext(messageId: messageId, contextController: nil, standaloneReactionAnimation: standaloneReactionAnimation) } - private func addMessageReactionContextContext(messageId: MessageId, contextController: ContextController?, standaloneReactionAnimation: StandaloneReactionAnimation?) { + private func addMessageReactionContextContext(messageId: EngineMessage.Id, contextController: ContextController?, standaloneReactionAnimation: StandaloneReactionAnimation?) { self.removeEmptyMessageReactionContexts() var messageItemNode: ListViewItemNode? @@ -1182,9 +1243,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } func addExternalOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, itemNode: ListViewItemNode?, isRotated: Bool) { - for animatingItemNode in self.animatingItemNodes { + /*for animatingItemNode in self.animatingItemNodes { animatingItemNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) - } + }*/ if itemNode == nil { for decorationItemNode in self.decorationItemNodes { decorationItemNode.addExternalOffset(offset: offset, transition: transition) @@ -1205,9 +1266,9 @@ public final class ChatMessageTransitionNodeImpl: ASDisplayNode, ChatMessageTran } func addContentOffset(offset: CGFloat, itemNode: ListViewItemNode?) { - for animatingItemNode in self.animatingItemNodes { + /*for animatingItemNode in self.animatingItemNodes { animatingItemNode.addContentOffset(offset: offset, itemNode: itemNode) - } + }*/ if itemNode == nil { for decorationItemNode in self.decorationItemNodes { decorationItemNode.addContentOffset(offset: offset) diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 534654c109..b4bc3caa51 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -570,7 +569,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } } - private func enqueueTransition(width: CGFloat, panelHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) { + private func enqueueTransition(width: CGFloat, panelHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) { let message = pinnedMessage.message var animationTransition: ContainedViewLayoutTransition = .immediate @@ -661,7 +660,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first { switch firstMedia { case let .preview(dimensions, immediateThumbnailData, _): - let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + let thumbnailMedia = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) if let dimensions { imageDimensions = dimensions.cgSize } @@ -722,7 +721,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedFetchMediaSignal: Signal? + var updatedFetchMediaSignal: Signal? if mediaUpdated { if let updatedMediaReference = updatedMediaReference, imageDimensions != nil { if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { @@ -735,7 +734,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { if fileReference.media.isAnimatedSticker { let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512) updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))) - updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource)) + updatedFetchMediaSignal = context.engine.resources.fetch(reference: fileReference.resourceReference(fileReference.media.resource), userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media)) } else if fileReference.media.isVideo || fileReference.media.isAnimated { updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { @@ -797,7 +796,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor) } - let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude) + let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset - 10.0, height: CGFloat.greatestFiniteMagnitude) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0))) let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)? @@ -935,7 +934,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { let button = attribute.rows[0].buttons[0] switch button.action { case .text: - controllerInteraction.sendMessage(button.title) + controllerInteraction.sendMessage(button.title, message.id) return case let .url(url): var isConcealed = true @@ -945,10 +944,10 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed, progress: Promise())) return case .requestMap: - controllerInteraction.shareCurrentLocation() + controllerInteraction.shareCurrentLocation(message.id) return case .requestPhone: - controllerInteraction.shareAccountContact() + controllerInteraction.shareAccountContact(message.id) return case .openWebApp: let progressPromise = Promise() @@ -976,22 +975,22 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { }) return case let .switchInline(samePeer, query, peerTypes): - var botPeer: Peer? - + var botPeer: EnginePeer? + var found = false for attribute in message.attributes { if let attribute = attribute as? InlineBotMessageAttribute { if let peerId = attribute.peerId { - botPeer = message.peers[peerId] + botPeer = message.peers[peerId].flatMap(EnginePeer.init) found = true } } } if !found { - botPeer = message.author + botPeer = message.author.flatMap(EnginePeer.init) } - - var peerId: PeerId? + + var peerId: EnginePeer.Id? if samePeer { peerId = message.id.peerId } diff --git a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift index 9cd52cba92..c4568f583a 100644 --- a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramNotices import TelegramPresentationData @@ -30,9 +29,10 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode { var isSecondary: Bool var interfaceState: ChatPresentationInterfaceState var metrics: LayoutMetrics + var deviceMetrics: DeviceMetrics var isMediaInputExpanded: Bool - init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { + init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) { self.width = width self.leftInset = leftInset self.rightInset = rightInset @@ -43,6 +43,7 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode { self.isSecondary = isSecondary self.interfaceState = interfaceState self.metrics = metrics + self.deviceMetrics = deviceMetrics self.isMediaInputExpanded = isMediaInputExpanded } } @@ -74,8 +75,8 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode { deinit { } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { + let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) if let currentLayout = self.currentLayout, currentLayout.params == params { return currentLayout.height } diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index 9dcafa3e61..93d51a53b5 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -184,24 +183,24 @@ private final class ChatInfoTitlePanelInviteInfoNode: ASDisplayNode { return result } - func update(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, chatPeer: Peer, invitedBy: Peer, transition: ContainedViewLayoutTransition) -> CGFloat { + func update(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, chatPeer: EnginePeer, invitedBy: EnginePeer, transition: ContainedViewLayoutTransition) -> CGFloat { let primaryTextColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper).primaryText - + if self.theme !== theme { self.theme = theme - + self.labelNode.linkHighlightColor = primaryTextColor.withAlphaComponent(0.3) } - + let topInset: CGFloat = 6.0 let bottomInset: CGFloat = 6.0 let sideInset: CGFloat = 16.0 - + let stringAndRanges: PresentationStrings.FormattedString - if let channel = chatPeer as? TelegramChannel, case .broadcast = channel.info { - stringAndRanges = strings.Conversation_NoticeInvitedByInChannel(EnginePeer(invitedBy).compactDisplayTitle) + if case let .channel(channel) = chatPeer, case .broadcast = channel.info { + stringAndRanges = strings.Conversation_NoticeInvitedByInChannel(invitedBy.compactDisplayTitle) } else { - stringAndRanges = strings.Conversation_NoticeInvitedByInGroup(EnginePeer(invitedBy).compactDisplayTitle) + stringAndRanges = strings.Conversation_NoticeInvitedByInGroup(invitedBy.compactDisplayTitle) } let attributedString = NSMutableAttributedString(string: stringAndRanges.string, font: Font.regular(13.0), textColor: primaryTextColor) @@ -249,101 +248,6 @@ private final class ChatInfoTitlePanelInviteInfoNode: ASDisplayNode { } } -private final class ChatInfoTitlePanelPeerNearbyInfoNode: ASDisplayNode { - private var theme: PresentationTheme? - - private let labelNode: ImmediateTextNode - private let filledBackgroundNode: LinkHighlightingNode - - private let openPeersNearby: () -> Void - - init(openPeersNearby: @escaping () -> Void) { - self.openPeersNearby = openPeersNearby - - self.labelNode = ImmediateTextNode() - self.labelNode.maximumNumberOfLines = 1 - self.labelNode.textAlignment = .center - - self.filledBackgroundNode = LinkHighlightingNode(color: .clear) - - super.init() - - self.addSubnode(self.filledBackgroundNode) - self.addSubnode(self.labelNode) - } - - override func didLoad() { - super.didLoad() - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.view.addGestureRecognizer(tapRecognizer) - } - - @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) { - self.openPeersNearby() - } - - func update(width: CGFloat, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, chatPeer: Peer, distance: Int32, transition: ContainedViewLayoutTransition) -> CGFloat { - let primaryTextColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper).primaryText - - if self.theme !== theme { - self.theme = theme - - self.labelNode.linkHighlightColor = primaryTextColor.withAlphaComponent(0.3) - } - - let topInset: CGFloat = 6.0 - let bottomInset: CGFloat = 6.0 - let sideInset: CGFloat = 16.0 - - let stringAndRanges = strings.Conversation_PeerNearbyDistance(EnginePeer(chatPeer).compactDisplayTitle, shortStringForDistance(strings: strings, distance: distance)) - - let attributedString = NSMutableAttributedString(string: stringAndRanges.string, font: Font.regular(13.0), textColor: primaryTextColor) - - let boldAttributes = [NSAttributedString.Key.font: Font.semibold(13.0), NSAttributedString.Key(rawValue: "_Link"): true as NSNumber] - for range in stringAndRanges.ranges.prefix(1) { - attributedString.addAttributes(boldAttributes, range: range.range) - } - - self.labelNode.attributedText = attributedString - let labelLayout = self.labelNode.updateLayoutFullInfo(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)) - - var labelRects = labelLayout.linesRects() - if labelRects.count > 1 { - let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) - for i in 0 ..< sortedIndices.count { - let index = sortedIndices[i] - for j in -1 ... 1 { - if j != 0 && index + j >= 0 && index + j < sortedIndices.count { - if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 { - labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width) - labelRects[index].size.width = labelRects[index + j].size.width - } - } - } - } - } - for i in 0 ..< labelRects.count { - labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0)) - labelRects[i].size.height = 20.0 - labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0) - } - - let backgroundLayout = self.filledBackgroundNode.asyncLayout() - let serviceColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper) - let backgroundApply = backgroundLayout(serviceColor.fill, labelRects, 10.0, 10.0, 0.0) - backgroundApply() - - let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) - - let labelFrame = CGRect(origin: CGPoint(x: floor((width - labelLayout.size.width) / 2.0), y: topInset + floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) - self.labelNode.frame = labelFrame - self.filledBackgroundNode.frame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) - - return topInset + backgroundSize.height + bottomInset - } -} - final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { private let context: AccountContext private let animationCache: AnimationCache @@ -361,7 +265,6 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { private var presentationInterfaceState: ChatPresentationInterfaceState? private var inviteInfoNode: ChatInfoTitlePanelInviteInfoNode? - private var peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode? private var cachedChevronImage: (UIImage, PresentationTheme)? @@ -738,12 +641,13 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { panelInset += 40.0 } - var chatPeer: Peer? + var chatPeer: EnginePeer? if let renderedPeer = interfaceState.renderedPeer { - chatPeer = renderedPeer.peers[renderedPeer.peerId] + chatPeer = renderedPeer.peers[renderedPeer.peerId].flatMap(EnginePeer.init) } var hitTestSlop: CGFloat = 0.0 - if let chatPeer = chatPeer, (updatedButtons.contains(.block) || updatedButtons.contains(.reportSpam) || updatedButtons.contains(.reportUserSpam)), let invitedBy = interfaceState.contactStatus?.invitedBy { + if let chatPeer = chatPeer, (updatedButtons.contains(.block) || updatedButtons.contains(.reportSpam) || updatedButtons.contains(.reportUserSpam)), let invitedByPeer = interfaceState.contactStatus?.invitedBy { + let invitedBy = EnginePeer(invitedByPeer) var inviteInfoTransition = transition let inviteInfoNode: ChatInfoTitlePanelInviteInfoNode if let current = self.inviteInfoNode { @@ -771,34 +675,6 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { inviteInfoNode?.removeFromSupernode() }) } - - if let chatPeer = chatPeer, let distance = interfaceState.contactStatus?.peerStatusSettings?.geoDistance { - var peerNearbyInfoTransition = transition - let peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode - if let current = self.peerNearbyInfoNode { - peerNearbyInfoNode = current - } else { - peerNearbyInfoTransition = .immediate - peerNearbyInfoNode = ChatInfoTitlePanelPeerNearbyInfoNode(openPeersNearby: { [weak self] in - self?.interfaceInteraction?.openPeersNearby() - }) - self.addSubnode(peerNearbyInfoNode) - self.peerNearbyInfoNode = peerNearbyInfoNode - peerNearbyInfoNode.alpha = 0.0 - transition.updateAlpha(node: peerNearbyInfoNode, alpha: 1.0) - } - - if let peerNearbyInfoNode = self.peerNearbyInfoNode { - let peerNearbyHeight = peerNearbyInfoNode.update(width: width, theme: interfaceState.theme, strings: interfaceState.strings, wallpaper: interfaceState.chatWallpaper, chatPeer: chatPeer, distance: distance, transition: peerNearbyInfoTransition) - peerNearbyInfoTransition.updateFrame(node: peerNearbyInfoNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight + panelInset), size: CGSize(width: width, height: peerNearbyHeight))) - panelHeight += peerNearbyHeight - } - } else if let peerNearbyInfoNode = self.peerNearbyInfoNode { - self.peerNearbyInfoNode = nil - transition.updateAlpha(node: peerNearbyInfoNode, alpha: 0.0, completion: { [weak peerNearbyInfoNode] _ in - peerNearbyInfoNode?.removeFromSupernode() - }) - } return LayoutResult(backgroundHeight: initialPanelHeight, insetHeight: panelHeight + panelInset, hitTestSlop: hitTestSlop) } diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index 9f76e4bc68..22e43345cf 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramStringFormatting import ChatPresentationInterfaceState @@ -81,7 +80,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.openBoostToUnrestrict() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState } diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index be9444e201..b29ac66d01 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -288,6 +288,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe }, openActiveSessions: { }, openBirthdaySetup: { }, performActiveSessionAction: { _, _ in + }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: { }, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift index c686bf1d6d..9e21477601 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsController.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/TelegramUI/Sources/ChatSearchState.swift b/submodules/TelegramUI/Sources/ChatSearchState.swift index bf886b8000..5b402de82e 100644 --- a/submodules/TelegramUI/Sources/ChatSearchState.swift +++ b/submodules/TelegramUI/Sources/ChatSearchState.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore struct ChatSearchState: Equatable { diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index e404568775..05079585e8 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -10,7 +10,6 @@ import MultilineTextComponent import PlainButtonComponent import UIKitRuntimeUtils import TelegramCore -import Postbox import EmojiStatusComponent import SwiftSignalKit import ContextUI @@ -446,7 +445,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat private var itemsDisposable: Disposable? - private var appliedScrollToTag: MemoryBuffer? + private var appliedScrollToTag: EngineMemoryBuffer? init(context: AccountContext, chatLocation: ChatLocation) { self.context = context diff --git a/submodules/TelegramUI/Sources/ChatSecretAutoremoveTimerActionSheet.swift b/submodules/TelegramUI/Sources/ChatSecretAutoremoveTimerActionSheet.swift deleted file mode 100644 index aa1d97af6b..0000000000 --- a/submodules/TelegramUI/Sources/ChatSecretAutoremoveTimerActionSheet.swift +++ /dev/null @@ -1,183 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import UIKit -import TelegramCore -import SwiftSignalKit -import Photos -import TelegramPresentationData -import AccountContext - -final class ChatSecretAutoremoveTimerActionSheetController: ActionSheetController { - private var presentationDisposable: Disposable? - - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - - init(context: AccountContext, currentValue: Int32, availableValues: [Int32]? = nil, applyValue: @escaping (Int32) -> Void) { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let strings = presentationData.strings - - super.init(theme: ActionSheetControllerTheme(presentationData: presentationData)) - - self.presentationDisposable = context.sharedContext.presentationData.startStrict(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData) - } - }) - - self._ready.set(.single(true)) - - var updatedValue: Int32 - if currentValue > 0 { - updatedValue = currentValue - } else { - if let availableValues = availableValues { - updatedValue = availableValues[0] - } else { - updatedValue = 7 - } - } - self.setItemGroups([ - ActionSheetItemGroup(items: [ - AutoremoveTimeoutSelectorItem(strings: strings, currentValue: updatedValue, availableValues: availableValues, valueChanged: { value in - updatedValue = value - }), - ActionSheetButtonItem(title: strings.Common_Done, font: .bold, action: { [weak self] in - self?.dismissAnimated() - applyValue(updatedValue) - }) - ]), - ]) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDisposable?.dispose() - } -} - -private final class AutoremoveTimeoutSelectorItem: ActionSheetItem { - let strings: PresentationStrings - - let currentValue: Int32 - let availableValues: [Int32]? - let valueChanged: (Int32) -> Void - - init(strings: PresentationStrings, currentValue: Int32, availableValues: [Int32]?, valueChanged: @escaping (Int32) -> Void) { - self.strings = strings - self.currentValue = currentValue - self.availableValues = availableValues - self.valueChanged = valueChanged - } - - func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return AutoremoveTimeoutSelectorItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, availableValues: self.availableValues, valueChanged: self.valueChanged) - } - - func updateNode(_ node: ActionSheetItemNode) { - } -} - -private let defaultTimeoutValues: [Int32] = [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 30, - 1 * 60, - 1 * 60 * 60, - 24 * 60 * 60, - 7 * 24 * 60 * 60 -] - -private final class AutoremoveTimeoutSelectorItemNode: ActionSheetItemNode, UIPickerViewDelegate, UIPickerViewDataSource { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings - - private let timeoutValues: [Int32] - - private let valueChanged: (Int32) -> Void - private let pickerView: UIPickerView - - init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, availableValues: [Int32]?, valueChanged: @escaping (Int32) -> Void) { - self.theme = theme - self.strings = strings - self.valueChanged = valueChanged - - self.pickerView = UIPickerView() - - if let availableValues = availableValues { - self.timeoutValues = [0] + availableValues.filter({ $0 > 0 }) - } else { - self.timeoutValues = defaultTimeoutValues - } - - super.init(theme: theme) - - self.pickerView.delegate = self - self.pickerView.dataSource = self - self.view.addSubview(self.pickerView) - - self.pickerView.reloadAllComponents() - var index: Int = 0 - for i in 0 ..< self.timeoutValues.count { - if currentValue <= self.timeoutValues[i] { - index = i - break - } - } - self.pickerView.selectRow(index, inComponent: 0, animated: false) - } - - func numberOfComponents(in pickerView: UIPickerView) -> Int { - return 1 - } - - func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { - return self.timeoutValues.count - } - - func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { - return 40.0 - } - - func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { - if self.timeoutValues[row] == 0 { - return NSAttributedString(string: self.strings.Profile_MessageLifetimeForever, font: Font.medium(15.0), textColor: self.theme.primaryTextColor) - } else { - return NSAttributedString(string: timeIntervalString(strings: self.strings, value: self.timeoutValues[row]), font: Font.medium(15.0), textColor: self.theme.primaryTextColor) - } - } - - func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - self.valueChanged(self.timeoutValues[row]) - } - - public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { - let size = CGSize(width: constrainedSize.width, height: 180.0) - - self.pickerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 180.0)) - - self.updateInternalLayout(size, constrainedSize: constrainedSize) - return size - } -} diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index 3aa6cd9d48..ce48f9c2a6 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramNotices import TelegramPresentationData @@ -32,9 +31,10 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { var isSecondary: Bool var interfaceState: ChatPresentationInterfaceState var metrics: LayoutMetrics + var deviceMetrics: DeviceMetrics var isMediaInputExpanded: Bool - init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { + init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) { self.width = width self.leftInset = leftInset self.rightInset = rightInset @@ -45,6 +45,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { self.isSecondary = isSecondary self.interfaceState = interfaceState self.metrics = metrics + self.deviceMetrics = deviceMetrics self.isMediaInputExpanded = isMediaInputExpanded } } @@ -73,7 +74,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { private var currentLayout: Layout? - private var tagMessageCount: (tag: MemoryBuffer, count: Int?, disposable: Disposable?)? + private var tagMessageCount: (tag: EngineMemoryBuffer, count: Int?, disposable: Disposable?)? private var totalMessageCount: Int? private var totalMessageCountDisposable: Disposable? @@ -110,16 +111,15 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { self.totalMessageCountDisposable?.dispose() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { var leftInset = leftInset + 8.0 var rightInset = rightInset + 8.0 - if bottomInset <= 32.0 { - leftInset += 18.0 - rightInset += 18.0 - } + let compactBottomSideInset = self.compactBottomSideInset(bottomInset: bottomInset, deviceMetrics: deviceMetrics) + leftInset += compactBottomSideInset + rightInset += compactBottomSideInset - let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, deviceMetrics: deviceMetrics, isMediaInputExpanded: isMediaInputExpanded) if let currentLayout = self.currentLayout, currentLayout.params == params { return currentLayout.height } @@ -130,7 +130,7 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { return height } - func prepareSwitchToFilter(tag: MemoryBuffer, count: Int) { + func prepareSwitchToFilter(tag: EngineMemoryBuffer, count: Int) { self.tagMessageCount?.disposable?.dispose() self.tagMessageCount = (tag, count, nil) } diff --git a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift index a344369297..8cce468833 100644 --- a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import ChatPresentationInterfaceState @@ -91,7 +90,7 @@ final class ChatUnblockInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState } diff --git a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift index 5cd2fb706d..0a6a56d831 100644 --- a/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatVerifiedPeerTitlePanelNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import LocalizedPeerData @@ -78,7 +77,7 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor } - var panelHeight: CGFloat = 8.0 + var panelHeight: CGFloat = 12.0 if let peer = interfaceState.renderedPeer?.peer, let verification = interfaceState.peerVerification { if isFirstTime { @@ -94,6 +93,7 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode { let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: plainText, font: Font.regular(12.0), textColor: interfaceState.theme.rootController.navigationBar.secondaryTextColor, paragraphAlignment: .center)) attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: emojiStatus.fileId, file: nil), range: NSMakeRange(0, 1)) + attributedText.addAttribute(.baselineOffset, value: 1.0, range: NSMakeRange(0, 1)) if let entity = entities.first { let range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) attributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: interfaceState.theme.rootController.navigationBar.accentTextColor, range: range) @@ -125,8 +125,8 @@ final class ChatVerifiedPeerTitlePanelNode: ChatTitleAccessoryPanelNode { placeholderColor: interfaceState.theme.list.mediaPlaceholderColor, attemptSynchronous: false )) - transition.updateFrame(node: emojiStatusTextNode.textNode, frame: CGRect(origin: CGPoint(x: floor((width - emojiStatusLayout.size.width) / 2.0), y: panelHeight), size: emojiStatusLayout.size)) - panelHeight += emojiStatusLayout.size.height + 8.0 + transition.updateFrame(node: emojiStatusTextNode.textNode, frame: CGRect(origin: CGPoint(x: floor((width - emojiStatusLayout.size.width) / 2.0), y: panelHeight + 1.0), size: emojiStatusLayout.size)) + panelHeight += emojiStatusLayout.size.height + 12.0 emojiStatusTextNode.visibilityRect = .infinite } diff --git a/submodules/TelegramUI/Sources/ClearNotificationsManager.swift b/submodules/TelegramUI/Sources/ClearNotificationsManager.swift index bcc43996e6..0993c3fa63 100644 --- a/submodules/TelegramUI/Sources/ClearNotificationsManager.swift +++ b/submodules/TelegramUI/Sources/ClearNotificationsManager.swift @@ -1,12 +1,12 @@ import Foundation import SwiftSignalKit -import Postbox +import TelegramCore private let messageNotificationKeyExpr = try? NSRegularExpression(pattern: "m([-\\d]+):([-\\d]+):([-\\d]+)_?", options: []) public enum NotificationManagedNotificationRequestId: Hashable { - case messageId(MessageId) - case globallyUniqueId(Int64, PeerId?) + case messageId(EngineMessage.Id) + case globallyUniqueId(Int64, EnginePeer.Id?) public init?(string: String) { if string.hasPrefix("m") { @@ -26,7 +26,7 @@ public enum NotificationManagedNotificationRequestId: Hashable { guard let id = Int32(idString) else { return nil } - self = .messageId(MessageId(peerId: PeerId(peerId), namespace: namespace, id: id)) + self = .messageId(EngineMessage.Id(peerId: EnginePeer.Id(peerId), namespace: namespace, id: id)) return } } @@ -48,7 +48,7 @@ public final class ClearNotificationsManager { private let removeNotificationIds: ([String]) -> Void private let removePendingNotificationIds: ([String]) -> Void - private var ids: [PeerId: MessageId] = [:] + private var ids: [EnginePeer.Id: EngineMessage.Id] = [:] private var timer: SwiftSignalKit.Timer? @@ -91,7 +91,7 @@ public final class ClearNotificationsManager { }) } - public func append(_ id: MessageId) { + public func append(_ id: EngineMessage.Id) { if let current = self.ids[id.peerId] { if current < id { self.ids[id.peerId] = id diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index aef09615e3..2accdfc015 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import TelegramPresentationData @@ -167,6 +166,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { }, performActiveSessionAction: { _, _ in }, + performBotConnectionReviewAction: { _, _ in + }, openChatFolderUpdates: { }, hideChatFolderUpdates: { @@ -211,7 +212,7 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, - index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: context.account.peerId, namespace: 0, id: 0), timestamp: 0))), + index: EngineChatList.Item.Index.chatList(EngineChatListIndex(pinningIndex: nil, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: context.account.peerId, namespace: 0, id: 0), timestamp: 0))), content: .peer(ChatListItemContent.PeerData( messages: [shortcut.topMessage], peer: renderedPeer, @@ -368,7 +369,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { switch command { case let .command(command): if sendImmediately { - interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text) + interfaceInteraction.sendBotCommand(command.peer._asPeer(), "/" + command.command.text) } else { interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in var commandQueryRange: NSRange? diff --git a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift index 8d27ec66e3..e345014502 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import AvatarNode @@ -141,7 +140,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { let leftInset: CGFloat = 55.0 + params.leftInset let rightInset: CGFloat = 10.0 + params.rightInset - let peerName = EnginePeer(item.command.peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + let peerName = item.command.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) let commandString = NSMutableAttributedString() commandString.append(NSAttributedString(string: "/" + item.command.command.text, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)) @@ -166,7 +165,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { strongSelf.arrowNode.setImage(iconImage, for: []) - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.command.peer), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor) + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.command.peer, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor) let _ = textApply() diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift index d424abb064..6c54824092 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import Display @@ -75,7 +74,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { private let disposable = MetaDisposable() - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId, chatPresentationContext: ChatPresentationContext) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: EnginePeer.Id, chatPresentationContext: ChatPresentationContext) { self.backgroundView = GlassBackgroundView() self.backgroundView.layer.anchorPoint = CGPoint() @@ -152,7 +151,7 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { let transition = preparedTransition(from: from ?? [], to: to, context: self.context, fontSize: self.fontSize, commandSelected: { [weak self] command, sendImmediately in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if sendImmediately { - interfaceInteraction.sendBotCommand(command.peer, "/" + command.command.text) + interfaceInteraction.sendBotCommand(command.peer._asPeer(), "/" + command.command.text) } else { interfaceInteraction.updateShowCommands { _ in return false } interfaceInteraction.updateTextInputStateAndMode { textInputState, inputMode in diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift index e27f05e592..1e823f781d 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index e5788e4079..5cfef2a4fd 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -22,8 +21,6 @@ public class ComposeControllerImpl: ViewController, ComposeController { return self.displayNode as! ComposeControllerNode } - private let index: PeerNameIndex = .lastNameFirst - private var _ready = Promise() override public var ready: Promise { return self._ready @@ -226,7 +223,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { DeviceAccess.authorizeAccess(to: .contacts) default: let presentationData = strongSelf.presentationData - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) } @@ -312,7 +309,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { } } - private func openPeer(peerId: PeerId) { + private func openPeer(peerId: EnginePeer.Id) { (self.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: self.context, chatLocation: .peer(id: peerId)), animated: true) } diff --git a/submodules/TelegramUI/Sources/ComposeControllerNode.swift b/submodules/TelegramUI/Sources/ComposeControllerNode.swift index 574b49755c..1a17e59426 100644 --- a/submodules/TelegramUI/Sources/ComposeControllerNode.swift +++ b/submodules/TelegramUI/Sources/ComposeControllerNode.swift @@ -1,7 +1,6 @@ import Display import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -22,7 +21,7 @@ final class ComposeControllerNode: ASDisplayNode { var navigationBar: NavigationBar? var requestDeactivateSearch: (() -> Void)? - var requestOpenPeerFromSearch: ((PeerId) -> Void)? + var requestOpenPeerFromSearch: ((EnginePeer.Id) -> Void)? var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? var openCreateNewGroup: (() -> Void)? diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 3f2a25e270..908f39f5e6 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -18,13 +17,13 @@ import PremiumUI import UndoUI import ContextUI -private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String { +private func peerTokenTitle(accountPeerId: EnginePeer.Id, peer: EnginePeer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String { if peer.id == accountPeerId { return strings.DialogList_SavedMessages } else if peer.id.isReplies { return strings.DialogList_Replies } else { - return EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder) + return peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder) } } @@ -43,8 +42,6 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection var dismissed: (() -> Void)? - private let index: PeerNameIndex = .lastNameFirst - private var _ready = Promise() private var _limitsReady = Promise() private var _peersReady = Promise() @@ -168,7 +165,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } } strongSelf.contactsNode.editableTokens.append(contentsOf: peers.map { peer -> EditableTokenListToken in - return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) + return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) }) strongSelf._peersReady.set(.single(true)) if strongSelf.isNodeLoaded { @@ -198,7 +195,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } let peers = peerList.compactMap { $0 } strongSelf.contactsNode.editableTokens.append(contentsOf: peers.map { peer -> EditableTokenListToken in - return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer._asPeer(), strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) + return EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: params.context.account.peerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) }) strongSelf._peersReady.set(.single(true)) if strongSelf.isNodeLoaded { @@ -301,17 +298,17 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection case .peerSelection: self.titleView.title = CounterControllerTitle(title: self.params.title ?? self.presentationData.strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder, counter: "") if self.rightNavigationButton == nil { - let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) + let rightNavigationButton = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) self.rightNavigationButton = rightNavigationButton - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(cancelPressed)) self.navigationItem.rightBarButtonItem = self.rightNavigationButton } case let .chatSelection(chatSelection): self.titleView.title = CounterControllerTitle(title: self.params.title ?? chatSelection.title, counter: "") if self.rightNavigationButton == nil { - let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) + let rightNavigationButton = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) self.rightNavigationButton = rightNavigationButton - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(cancelPressed)) self.navigationItem.rightBarButtonItem = self.rightNavigationButton } } @@ -383,7 +380,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection displayCountAlert = true updatedState = updatedState.withToggledPeerId(.peer(peer.id)) } else { - addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(EnginePeer(peer))) + addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) } } updatedCount = updatedState.selectedPeerIndices.count @@ -400,7 +397,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection state.selectedPeerIds.remove(peer.id) removedTokenId = peer.id } else { - addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(EnginePeer(peer))) + addedToken = EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: accountPeerId, peer: peer, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer)) state.selectedPeerIds.insert(peer.id) } updatedCount = state.selectedPeerIds.count @@ -448,7 +445,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection if !self.params.initialSelectedPeers.isEmpty { for peer in self.params.initialSelectedPeers { - self.contactsNode.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + self.contactsNode.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil)) } /*if case let .contacts(contactsNode) = self.contactsNode.contentNode { contactsNode.updateSelectionState { state in @@ -478,7 +475,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection a(.default) if let self { - self.params.sendMessage?(EnginePeer(peer)) + self.params.sendMessage?(peer) } }))) @@ -488,7 +485,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection a(.default) if let self { - self.params.openProfile?(EnginePeer(peer)) + self.params.openProfile?(peer) } }))) @@ -745,7 +742,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } for peer in peers { if !existingPeerIds.contains(peer.id) { - tokens.append(EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: self.context.account.peerId, peer: peer._asPeer(), strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))) + tokens.append(EditableTokenListToken(id: peer.id, title: peerTokenTitle(accountPeerId: self.context.account.peerId, peer: peer, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder), fixedPosition: nil, subject: .peer(peer))) } } } else { diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index ba2ffa3754..881bfd5b67 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -1,7 +1,6 @@ import Display import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -20,16 +19,16 @@ import CheckComponent private struct SearchResultEntry: Identifiable { let index: Int - let peer: Peer - + let peer: EnginePeer + var stableId: Int64 { return self.peer.id.toInt64() } - + static func ==(lhs: SearchResultEntry, rhs: SearchResultEntry) -> Bool { - return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer) + return lhs.index == rhs.index && lhs.peer == rhs.peer } - + static func <(lhs: SearchResultEntry, rhs: SearchResultEntry) -> Bool { return lhs.index < rhs.index } @@ -314,7 +313,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } case let .chats(chatsNode): chatsNode.peerSelected = { [weak self] peer, _, _, _, _ in - self?.openPeer?(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + self?.openPeer?(.peer(peer: peer, isGlobal: false, participantCount: nil)) } chatsNode.additionalCategorySelected = { [weak self] id in guard let strongSelf = self else { @@ -327,7 +326,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { let searchText = ValuePromise() self.tokenListNode.deleteToken = { [weak self] id in - if let id = id as? PeerId { + if let id = id as? EnginePeer.Id { self?.removeSelectedPeer?(ContactListPeerId.peer(id)) } else if let id = id as? Int { self?.removeSelectedCategory?(id) @@ -489,7 +488,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } else if count <= 1 { let callTitle: String if case let .contacts(contactListNode) = self.contentNode, let peer = contactListNode.selectedPeers.first, case let .peer(peer, _, _) = peer { - callTitle = self.presentationData.strings.NewCall_ActionCallSingle(EnginePeer(peer).compactDisplayTitle).string + callTitle = self.presentationData.strings.NewCall_ActionCallSingle(peer.compactDisplayTitle).string } else { callTitle = self.presentationData.strings.NewCall_ActionCallMultiple } diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index 047253a186..a475db9a80 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData @@ -39,7 +38,6 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } } - private let index: PeerNameIndex = .lastNameFirst private let titleProducer: (PresentationStrings) -> String private let options: Signal<[ContactListAdditionalOption], NoError> private let displayDeviceContacts: Bool @@ -70,7 +68,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private let isPeerEnabled: (ContactListPeer) -> Bool var dismissed: (() -> Void)? - var presentScheduleTimePicker: (@escaping (Int32, Int32?) -> Void) -> Void = { _ in } + var presentScheduleTimePicker: (@escaping (Int32, Int32?, Bool) -> Void) -> Void = { _ in } private let createActionDisposable = MetaDisposable() private let confirmationDisposable = MetaDisposable() @@ -514,7 +512,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController a(.default) if let self { - self.sendMessage?(EnginePeer(peer)) + self.sendMessage?(peer) } }))) @@ -524,7 +522,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController a(.default) if let self { - self.openProfile?(EnginePeer(peer)) + self.openProfile?(peer) } }))) @@ -646,8 +644,8 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { } func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - self.controller?.presentScheduleTimePicker ({ time, repeatPeriod in - self.controller?.contactsNode.requestMultipleAction?(false, time, parameters) + self.controller?.presentScheduleTimePicker ({ time, repeatPeriod, silentPosting in + self.controller?.contactsNode.requestMultipleAction?(silentPosting, time, parameters) }) } diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 8b22952450..6c958947a2 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -1,7 +1,6 @@ import Display import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -158,7 +157,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { onlyWriteable: false, isGroupInvitation: false, isPeerEnabled: { peer in - return isPeerEnabled(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + return isPeerEnabled(.peer(peer: peer, isGlobal: false, participantCount: nil)) }, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in @@ -228,7 +227,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { strongSelf.contactListNode.updateSelectionState { state in let peerId = ContactListPeerId.peer(peer.id) let state = state ?? ContactListNodeGroupSelectionState() - return state.withToggledPeerId(peerId).withSelectedPeerMap([peerId: ContactListPeer.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)]) + return state.withToggledPeerId(peerId).withSelectedPeerMap([peerId: ContactListPeer.peer(peer: peer, isGlobal: false, participantCount: nil)]) } } } @@ -281,7 +280,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { } let inset: CGFloat - if layout.size.width >= 375.0 { + if layout.size.width >= 320.0 { inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) } else { inset = 0.0 diff --git a/submodules/TelegramUI/Sources/CreateChannelController.swift b/submodules/TelegramUI/Sources/CreateChannelController.swift index b855091607..a8deff185a 100644 --- a/submodules/TelegramUI/Sources/CreateChannelController.swift +++ b/submodules/TelegramUI/Sources/CreateChannelController.swift @@ -3,7 +3,6 @@ import UIKit import Display import SSignalKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import LegacyComponents @@ -14,13 +13,16 @@ import AlertUI import PresentationDataUtils import LegacyUI import ItemListAvatarAndNameInfoItem -import WebSearchUI import PeerInfoUI import MapResourceToAvatarSizes import LegacyMediaPickerUI import TextFormat +import MediaEditor +import MediaEditorScreen +import CameraScreen import AvatarEditorScreen import OldChannelsController +import Photos import AVFoundation private struct CreateChannelArguments { @@ -68,7 +70,7 @@ private enum CreateChannelEntryTag: ItemListItemTag { } private enum CreateChannelEntry: ItemListNodeEntry { - case channelInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) + case channelInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?) case setProfilePhoto(PresentationTheme, String) case descriptionSetup(PresentationTheme, String, String) @@ -124,11 +126,7 @@ private enum CreateChannelEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } - if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer { - if !lhsPeer.isEqual(rhsPeer) { - return false - } - } else if (lhsPeer != nil) != (rhsPeer != nil) { + if lhsPeer != rhsPeer { return false } if lhsEditingState != rhsEditingState { @@ -194,7 +192,7 @@ private enum CreateChannelEntry: ItemListNodeEntry { let arguments = arguments as! CreateChannelArguments switch self { case let .channelInfo(_, _, dateTimeFormat, peer, state, avatar): - return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer.flatMap(EnginePeer.init), presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(itemContext: .accountContext(arguments.context), presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, mode: .editSettings, peer: peer, presence: nil, memberCount: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false, withExtendedBottomInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, editingNameCompleted: { arguments.focusOnDescription() @@ -282,9 +280,9 @@ private func CreateChannelEntries(presentationData: PresentationData, state: Cre let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil) - let peer = TelegramGroup(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), title: state.editingName.composedTitle, photo: [], participantCount: 0, role: .creator(rank: nil), membership: .Member, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0) - - entries.append(.channelInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar)) + let peer = TelegramGroup(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), title: state.editingName.composedTitle, photo: [], participantCount: 0, role: .creator(rank: nil), membership: .Member, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0) + + entries.append(.channelInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, EnginePeer(peer), groupInfoState, state.avatar)) entries.append(.descriptionSetup(presentationData.theme, presentationData.strings.Channel_Edit_AboutItem, state.editingDescriptionText)) entries.append(.descriptionInfo(presentationData.theme, presentationData.strings.Channel_About_Help)) @@ -346,7 +344,7 @@ public enum CreateChannelMode { case requestPeer(ReplyMarkupButtonRequestPeerType.Channel) } -public func createChannelController(context: AccountContext, mode: CreateChannelMode = .generic, willComplete: @escaping (String, @escaping () -> Void) -> Void = { _, complete in complete() }, completion: ((PeerId, @escaping () -> Void) -> Void)? = nil) -> ViewController { +public func createChannelController(context: AccountContext, mode: CreateChannelMode = .generic, willComplete: @escaping (String, @escaping () -> Void) -> Void = { _, complete in complete() }, completion: ((EnginePeer.Id, @escaping () -> Void) -> Void)? = nil) -> ViewController { let initialState = CreateChannelState(creating: false, editingName: ItemListAvatarAndNameInfoItemName.title(title: "", type: .channel), editingDescriptionText: "", avatar: nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -363,10 +361,29 @@ public func createChannelController(context: AccountContext, mode: CreateChannel let actionsDisposable = DisposableSet() - let currentAvatarMixin = Atomic(value: nil) - - let uploadedAvatar = Promise() - var uploadedVideoAvatar: (Promise, Double?)? = nil + var avatarPickerHolder: Any? + var pendingAvatar: CreatePendingPeerAvatar? + let applyPendingAvatar: (CreatePendingPeerAvatar) -> Void = { avatar in + pendingAvatar = avatar + updateState { current in + var current = current + current.avatar = avatar.updatingAvatar + return current + } + } + let updatePendingAvatarIfCurrent: (CreatePendingPeerAvatar) -> Void = { avatar in + if pendingAvatar?.previewRepresentation.resource.id == avatar.previewRepresentation.resource.id { + applyPendingAvatar(avatar) + } + } + let clearPendingAvatar: () -> Void = { + pendingAvatar = nil + updateState { current in + var current = current + current.avatar = nil + return current + } + } let checkAddressNameDisposable = MetaDisposable() actionsDisposable.add(checkAddressNameDisposable) @@ -408,7 +425,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel endEditingImpl?() - var createSignal: Signal = context.engine.peers.createChannel(title: title, description: description.isEmpty ? nil : description) + var createSignal: Signal = context.engine.peers.createChannel(title: title, description: description.isEmpty ? nil : description) if case .requestPeer = mode { if let publicLink, !publicLink.isEmpty { createSignal = createSignal @@ -434,12 +451,9 @@ public func createChannelController(context: AccountContext, mode: CreateChannel } } }).start(next: { peerId in - let updatingAvatar = stateValue.with { - return $0.avatar - } - if let _ = updatingAvatar { - let _ = context.engine.peers.updatePeerPhoto(peerId: peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + if let pendingAvatar { + let _ = context.engine.peers.updatePeerPhoto(peerId: peerId, photo: pendingAvatar.uploadedPhoto, video: pendingAvatar.uploadedVideo, videoStartTimestamp: pendingAvatar.videoStartTimestamp, markup: pendingAvatar.markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }).start() } @@ -474,216 +488,127 @@ public func createChannelController(context: AccountContext, mode: CreateChannel }, changeProfilePhoto: { endEditingImpl?() - let title = stateValue.with { state -> String in - return state.editingName.composedTitle - } + let keyboardInputData = Promise() + keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true)) - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), - TelegramEngine.EngineData.Item.Configuration.SearchBots() - ) - |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var dismissPickerImpl: (() -> Void)? + let (mainController, pickerHolder) = context.sharedContext.makeAvatarMediaPickerScreen(context: context, peerType: .channel, getSourceRect: { return nil }, canDelete: pendingAvatar != nil, performDelete: { + clearPendingAvatar() + }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, _, cancelled in + avatarPickerHolder = nil - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.statusBar.statusBarStyle = .Ignore - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) - - legacyController.bind(controller: navigationController) - - endEditingImpl?() - presentControllerImpl?(legacyController, nil) - - let completedChannelPhotoImpl: (UIImage) -> Void = { image in - if let data = image.jpegData(compressionQuality: 0.6) { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource)) - uploadedVideoAvatar = nil - updateState { current in - var current = current - current.avatar = .image(representation, false) - return current - } + let applyPhoto: (UIImage) -> Void = { image in + if let avatar = CreatePeerAvatarSetup.photo(context: context, image: image) { + applyPendingAvatar(avatar) + } + } + let applyVideo: (UIImage, MediaEditorScreenImpl.MediaResult.VideoResult?, MediaEditorValues?, UploadPeerPhotoMarkup?) -> Void = { image, video, values, markup in + if let avatar = CreatePeerAvatarSetup.video(context: context, image: image, video: video, values: values, markup: markup, didCompleteLoadingPreview: { avatar in + updatePendingAvatarIfCurrent(avatar) + }) { + applyPendingAvatar(avatar) } } - let completedChannelVideoImpl: (UIImage, Any?, TGVideoEditAdjustments?) -> Void = { image, asset, adjustments in - if let data = image.jpegData(compressionQuality: 0.6) { - let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - updateState { state in - var state = state - state.avatar = .image(representation, true) - return state + let subject: Signal + if let asset = result as? PHAsset { + subject = .single(.asset(asset)) + } else if let image = result as? UIImage { + subject = .single(.image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .bottomRight, fromCamera: false)) + } else if let result = result as? Signal { + subject = result + |> map { value -> MediaEditorScreenImpl.Subject? in + switch value { + case .pendingImage: + return nil + case let .image(image): + return .image(image: image.image, dimensions: PixelDimensions(image.image.size), additionalImage: nil, additionalImagePosition: .topLeft, fromCamera: false) + case let .video(video): + return .video(videoPath: video.videoPath, thumbnail: video.coverImage, mirror: video.mirror, additionalVideoPath: nil, additionalThumbnail: nil, dimensions: video.dimensions, duration: video.duration, videoPositionChanges: [], additionalVideoPosition: .topLeft, fromCamera: false) + default: + return nil } - - var videoStartTimestamp: Double? = nil - if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { - videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue - } - - let signal = Signal { subscriber in - let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in - if let paintingData = adjustments.paintingData, paintingData.hasAnimation { - return LegacyPaintEntityRenderer(postbox: context.account.postbox, adjustments: adjustments) - } else { - return nil - } - } - - let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") - let uploadInterface = LegacyLiveUploadInterface(context: context) - let signal: SSignal - if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer { - let durationSignal: SSignal = SSignal(generator: { subscriber in - let disposable = (entityRenderer.duration()).start(next: { duration in - subscriber.putNext(duration) - subscriber.putCompletion() - }) - - return SBlockDisposable(block: { - disposable.dispose() - }) - }) - signal = durationSignal.map(toSignal: { duration -> SSignal in - if let duration = duration as? Double { - return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)! - } else { - return SSignal.single(nil) - } - }) - - } else if let asset = asset as? AVAsset { - signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)! - } else { - signal = SSignal.complete() - } - - let signalDisposable = signal.start(next: { next in - if let result = next as? TGMediaVideoConversionResult { - if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - } - - if let timestamp = videoStartTimestamp { - videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05)) - } - - var value = stat() - if stat(result.fileURL.path, &value) == 0 { - if let data = try? Data(contentsOf: result.fileURL) { - let resource: TelegramMediaResource - if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { - resource = LocalFileMediaResource(fileId: liveUploadData.id) - } else { - resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - } - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - subscriber.putNext(resource) - - EngineTempBox.shared.dispose(tempFile) - } - } - subscriber.putCompletion() - } - }, error: { _ in - }, completed: nil) - - let disposable = ActionDisposable { - signalDisposable?.dispose() - } - - return ActionDisposable { - disposable.dispose() - } - } - - uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource)) - - let promise = Promise() - promise.set(signal - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { resource -> Signal in - if let resource = resource { - return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init) - } else { - return .single(nil) - } - } |> afterNext { next in - if let next = next, next.isCompleted { - updateState { state in - var state = state - state.avatar = .image(representation, false) - return state - } - } - }) - uploadedVideoAvatar = (promise, videoStartTimestamp) } + } else { + let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .channel, markup: nil) + controller.imageCompletion = { image, commit in + applyPhoto(image) + commit() + } + controller.videoCompletion = { image, _, _, markup, commit in + applyVideo(image, nil, nil, markup) + commit() + } + pushControllerImpl?(controller) + return } - let keyboardInputData = Promise() - keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true)) - - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)! - mixin.stickersContext = LegacyPaintStickersContext(context: context) - let _ = currentAvatarMixin.swap(mixin) - mixin.requestSearchController = { assetsController in - let controller = WebSearchController(context: context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: title, completion: { result in - assetsController?.dismiss() - completedChannelPhotoImpl(result) - })) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } -// mixin.requestAvatarEditor = { imageCompletion, videoCompletion in -// guard let imageCompletion, let videoCompletion else { -// return -// } -// let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .channel, markup: nil) -// controller.imageCompletion = imageCompletion -// controller.videoCompletion = videoCompletion -// pushControllerImpl?(controller) -// } - mixin.didFinishWithImage = { image in - if let image = image { - completedChannelPhotoImpl(image) - } - } - mixin.didFinishWithVideo = { image, asset, adjustments in - if let image = image, let asset = asset { - completedChannelVideoImpl(image, asset, adjustments) - } - } - if stateValue.with({ $0.avatar }) != nil { - mixin.didFinishWithDelete = { - updateState { current in - var current = current - current.avatar = nil - return current + let editorController = MediaEditorScreenImpl( + context: context, + mode: .avatarEditor, + subject: subject, + transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery( + MediaEditorScreenImpl.TransitionIn.GalleryTransitionIn( + sourceView: $0, + sourceRect: transitionRect, + sourceImage: transitionImage + ) + ) }), + transitionOut: { finished, _ in + if !finished, let transitionView { + return MediaEditorScreenImpl.TransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: 0.0 + ) } - uploadedAvatar.set(.never()) - } - } - mixin.didDismiss = { [weak legacyController] in - let _ = currentAvatarMixin.swap(nil) - legacyController?.dismiss() - } - let menuController = mixin.present() - if let menuController = menuController { - menuController.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } + return nil + }, + completion: { results, commit in + guard let result = results.first else { + return + } + switch result.media { + case let .image(image, _): + applyPhoto(image) + commit({}) + case let .video(video, coverImage, values, _, _): + if let coverImage { + applyVideo(coverImage, video, values, nil) + } + commit({}) + default: + break + } + dismissPickerImpl?() + } as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void + ) + editorController.cancelled = { _ in + cancelled() } + pushControllerImpl?(editorController) + }, dismissed: { + avatarPickerHolder = nil }) + avatarPickerHolder = pickerHolder + if let mainController { + dismissPickerImpl = { [weak mainController] in + if let mainController, let navigationController = mainController.navigationController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + return !(controller is CameraScreen) && controller !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) + } + } + if mainController is ActionSheetController { + presentControllerImpl?(mainController, nil) + } else { + mainController.navigationPresentation = .flatModal + mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + pushControllerImpl?(mainController) + } + } }, focusOnDescription: { focusOnDescriptionImpl?() }, updatePublicLinkText: { text in @@ -702,7 +627,7 @@ public func createChannelController(context: AccountContext, mode: CreateChannel return updated } - checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .peer(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(0))), name: text) + checkAddressNameDisposable.set((context.engine.peers.validateAddressNameInteractive(domain: .peer(EnginePeer.Id(namespace: Namespaces.Peer.CloudGroup, id: EnginePeer.Id.Id._internalFromInt64Value(0))), name: text) |> deliverOnMainQueue).start(next: { result in updateState { state in var updated = state @@ -741,6 +666,8 @@ public func createChannelController(context: AccountContext, mode: CreateChannel return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() + + let _ = avatarPickerHolder } let controller = ItemListController(context: context, state: signal) diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 12295436ea..5acbb90d51 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -20,7 +20,6 @@ import LegacyUI import LocationUI import ItemListPeerItem import ItemListAvatarAndNameInfoItem -import WebSearchUI import Geocoding import PeerInfoUI import MapResourceToAvatarSizes @@ -34,6 +33,10 @@ import TextFormat import AvatarEditorScreen import SendInviteLinkScreen import OldChannelsController +import MediaEditor +import MediaEditorScreen +import CameraScreen +import Photos import AVFoundation private struct CreateGroupArguments { @@ -563,10 +566,29 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] let checkAddressNameDisposable = MetaDisposable() actionsDisposable.add(checkAddressNameDisposable) - let currentAvatarMixin = Atomic(value: nil) - - let uploadedAvatar = Promise() - var uploadedVideoAvatar: (Promise, Double?)? = nil + var avatarPickerHolder: Any? + var pendingAvatar: CreatePendingPeerAvatar? + let applyPendingAvatar: (CreatePendingPeerAvatar) -> Void = { avatar in + pendingAvatar = avatar + updateState { current in + var current = current + current.avatar = avatar.updatingAvatar + return current + } + } + let updatePendingAvatarIfCurrent: (CreatePendingPeerAvatar) -> Void = { avatar in + if pendingAvatar?.previewRepresentation.resource.id == avatar.previewRepresentation.resource.id { + applyPendingAvatar(avatar) + } + } + let clearPendingAvatar: () -> Void = { + pendingAvatar = nil + updateState { current in + var current = current + current.avatar = nil + return current + } + } if initialTitle == nil && peerIds.count > 0 && peerIds.count < 5 { let _ = (context.engine.data.get( @@ -793,19 +815,15 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] let _ = createSignal let _ = replaceControllerImpl let _ = dismissImpl - let _ = uploadedVideoAvatar actionsDisposable.add((createSignal |> mapToSignal { result -> Signal in guard let result = result else { return .single(nil) } - let updatingAvatar = stateValue.with { - return $0.avatar - } - if let _ = updatingAvatar { - return context.engine.peers.updatePeerPhoto(peerId: result.peerId, photo: uploadedAvatar.get(), video: uploadedVideoAvatar?.0.get(), videoStartTimestamp: uploadedVideoAvatar?.1, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations) + if let pendingAvatar { + return context.engine.peers.updatePeerPhoto(peerId: result.peerId, photo: pendingAvatar.uploadedPhoto, video: pendingAvatar.uploadedVideo, videoStartTimestamp: pendingAvatar.videoStartTimestamp, markup: pendingAvatar.markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(engine: context.engine, resource: resource, representations: representations) }) |> ignoreValues |> `catch` { _ -> Signal in @@ -893,245 +911,163 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] }, changeProfilePhoto: { endEditingImpl?() - let title = stateValue.with { state -> String in - return state.editingName.composedTitle + let peerType: AvatarEditorScreen.PeerType + if case let .requestPeer(group) = mode, group.isForum == true { + peerType = .forum + } else { + peerType = .group } - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), - TelegramEngine.EngineData.Item.Configuration.SearchBots() - ) - |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in - let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let keyboardInputData = Promise() + keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true)) + + var dismissPickerImpl: (() -> Void)? + let (mainController, pickerHolder) = context.sharedContext.makeAvatarMediaPickerScreen(context: context, peerType: .group, getSourceRect: { return nil }, canDelete: pendingAvatar != nil, performDelete: { + clearPendingAvatar() + }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, _, cancelled in + avatarPickerHolder = nil - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) - legacyController.statusBar.statusBarStyle = .Ignore - - let emptyController = LegacyEmptyController(context: legacyController.context)! - let navigationController = makeLegacyNavigationController(rootController: emptyController) - navigationController.setNavigationBarHidden(true, animated: false) - navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) - - legacyController.bind(controller: navigationController) - - endEditingImpl?() - presentControllerImpl?(legacyController, nil) - - let completedGroupPhotoImpl: (UIImage) -> Void = { image in - if let data = image.jpegData(compressionQuality: 0.6) { - let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: resource)) - uploadedVideoAvatar = nil - updateState { current in - var current = current - current.avatar = .image(representation, false) - return current - } + let applyPhoto: (UIImage) -> Void = { image in + if let avatar = CreatePeerAvatarSetup.photo(context: context, image: image) { + applyPendingAvatar(avatar) + } + } + let applyVideo: (UIImage, MediaEditorScreenImpl.MediaResult.VideoResult?, MediaEditorValues?, UploadPeerPhotoMarkup?) -> Void = { image, video, values, markup in + if let avatar = CreatePeerAvatarSetup.video(context: context, image: image, video: video, values: values, markup: markup, didCompleteLoadingPreview: { avatar in + updatePendingAvatarIfCurrent(avatar) + }) { + applyPendingAvatar(avatar) } } - let completedGroupVideoImpl: (UIImage, Any?, TGVideoEditAdjustments?) -> Void = { image, asset, adjustments in - if let data = image.jpegData(compressionQuality: 0.6) { - let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - updateState { state in - var state = state - state.avatar = .image(representation, true) - return state + let subject: Signal + if let asset = result as? PHAsset { + subject = .single(.asset(asset)) + } else if let image = result as? UIImage { + subject = .single(.image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .bottomRight, fromCamera: false)) + } else if let result = result as? Signal { + subject = result + |> map { value -> MediaEditorScreenImpl.Subject? in + switch value { + case .pendingImage: + return nil + case let .image(image): + return .image(image: image.image, dimensions: PixelDimensions(image.image.size), additionalImage: nil, additionalImagePosition: .topLeft, fromCamera: false) + case let .video(video): + return .video(videoPath: video.videoPath, thumbnail: video.coverImage, mirror: video.mirror, additionalVideoPath: nil, additionalThumbnail: nil, dimensions: video.dimensions, duration: video.duration, videoPositionChanges: [], additionalVideoPosition: .topLeft, fromCamera: false) + default: + return nil } - - var videoStartTimestamp: Double? = nil - if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { - videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue - } - - let signal = Signal { subscriber in - let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in - if let paintingData = adjustments.paintingData, paintingData.hasAnimation { - return LegacyPaintEntityRenderer(postbox: context.account.postbox, adjustments: adjustments) - } else { - return nil - } - } - - let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") - let uploadInterface = LegacyLiveUploadInterface(context: context) - let signal: SSignal - if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer { - let durationSignal: SSignal = SSignal(generator: { subscriber in - let disposable = (entityRenderer.duration()).start(next: { duration in - subscriber.putNext(duration) - subscriber.putCompletion() - }) - - return SBlockDisposable(block: { - disposable.dispose() - }) - }) - signal = durationSignal.map(toSignal: { duration -> SSignal in - if let duration = duration as? Double { - return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)! - } else { - return SSignal.single(nil) - } - }) - - } else if let asset = asset as? AVAsset { - signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)! - } else { - signal = SSignal.complete() - } - - let signalDisposable = signal.start(next: { next in - if let result = next as? TGMediaVideoConversionResult { - if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { - context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) - } - - if let timestamp = videoStartTimestamp { - videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05)) - } - - var value = stat() - if stat(result.fileURL.path, &value) == 0 { - if let data = try? Data(contentsOf: result.fileURL) { - let resource: TelegramMediaResource - if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { - resource = LocalFileMediaResource(fileId: liveUploadData.id) - } else { - resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) - } - context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - subscriber.putNext(resource) - - EngineTempBox.shared.dispose(tempFile) - } - } - subscriber.putCompletion() - } - }, error: { _ in - }, completed: nil) - - let disposable = ActionDisposable { - signalDisposable?.dispose() - } - - return ActionDisposable { - disposable.dispose() - } - } - - uploadedAvatar.set(context.engine.peers.uploadedPeerPhoto(resource: photoResource)) - - let promise = Promise() - promise.set(signal - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { resource -> Signal in - if let resource = resource { - return context.engine.peers.uploadedPeerVideo(resource: resource) |> map(Optional.init) - } else { - return .single(nil) - } - } |> afterNext { next in - if let next = next, next.isCompleted { - updateState { state in - var state = state - state.avatar = .image(representation, false) - return state - } - } - }) - uploadedVideoAvatar = (promise, videoStartTimestamp) } + } else { + let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: peerType, markup: nil) + controller.imageCompletion = { image, commit in + applyPhoto(image) + commit() + } + controller.videoCompletion = { image, _, _, markup, commit in + applyVideo(image, nil, nil, markup) + commit() + } + pushImpl?(controller) + return } - let keyboardInputData = Promise() - keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true)) - - let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)! - mixin.stickersContext = LegacyPaintStickersContext(context: context) - let _ = currentAvatarMixin.swap(mixin) - mixin.requestSearchController = { assetsController in - let controller = WebSearchController(context: context, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: title, completion: { result in - assetsController?.dismiss() - completedGroupPhotoImpl(result) - })) - presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) - } -// mixin.requestAvatarEditor = { imageCompletion, videoCompletion in -// guard let imageCompletion, let videoCompletion else { -// return -// } -// let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: .group, markup: nil) -// controller.imageCompletion = imageCompletion -// controller.videoCompletion = videoCompletion -// pushImpl?(controller) -// } - mixin.didFinishWithImage = { image in - if let image = image { - completedGroupPhotoImpl(image) - } - } - mixin.didFinishWithVideo = { image, asset, adjustments in - if let image = image, let asset = asset { - completedGroupVideoImpl(image, asset, adjustments) - } - } - if stateValue.with({ $0.avatar }) != nil { - mixin.didFinishWithDelete = { - updateState { current in - var current = current - current.avatar = nil - return current + let editorController = MediaEditorScreenImpl( + context: context, + mode: .avatarEditor, + subject: subject, + transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery( + MediaEditorScreenImpl.TransitionIn.GalleryTransitionIn( + sourceView: $0, + sourceRect: transitionRect, + sourceImage: transitionImage + ) + ) }), + transitionOut: { finished, _ in + if !finished, let transitionView { + return MediaEditorScreenImpl.TransitionOut( + destinationView: transitionView, + destinationRect: transitionView.bounds, + destinationCornerRadius: 0.0 + ) } - uploadedAvatar.set(.never()) - } - } - mixin.didDismiss = { [weak legacyController] in - let _ = currentAvatarMixin.swap(nil) - legacyController?.dismiss() - } - let menuController = mixin.present() - if let menuController = menuController { - menuController.customRemoveFromParentViewController = { [weak legacyController] in - legacyController?.dismiss() - } + return nil + }, + completion: { results, commit in + guard let result = results.first else { + return + } + switch result.media { + case let .image(image, _): + applyPhoto(image) + commit({}) + case let .video(video, coverImage, values, _, _): + if let coverImage { + applyVideo(coverImage, video, values, nil) + } + commit({}) + default: + break + } + dismissPickerImpl?() + } as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void + ) + editorController.cancelled = { _ in + cancelled() } + pushImpl?(editorController) + }, dismissed: { + avatarPickerHolder = nil }) + avatarPickerHolder = pickerHolder + if let mainController { + dismissPickerImpl = { [weak mainController] in + if let mainController, let navigationController = mainController.navigationController { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + return !(controller is CameraScreen) && controller !== mainController + } + navigationController.setViewControllers(viewControllers, animated: false) + } + } + if mainController is ActionSheetController { + presentControllerImpl?(mainController, nil) + } else { + mainController.navigationPresentation = .flatModal + mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + pushImpl?(mainController) + } + } }, changeLocation: { endEditingImpl?() - let controller = LocationPickerController(context: context, mode: .pick, completion: { location, _, _, address, _ in - let addressSignal: Signal - if let address = address { - addressSignal = .single(address) - } else { - addressSignal = reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude) - |> map { placemark in - if let placemark = placemark { - return placemark.fullAddress - } else { - return "\(location.latitude), \(location.longitude)" - } - } - } - - let _ = (addressSignal - |> deliverOnMainQueue).start(next: { address in - addressPromise.set(.single(address)) - updateState { current in - var current = current - current.location = PeerGeoLocation(latitude: location.latitude, longitude: location.longitude, address: address) - return current - } - }) - }) - pushImpl?(controller) + let controller = LocationPickerController(context: context, style: .glass, mode: .pick, completion: { location, _, _, address, _ in + let addressSignal: Signal + if let address = address { + addressSignal = .single(address) + } else { + addressSignal = reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude) + |> map { placemark in + if let placemark = placemark { + return placemark.fullAddress + } else { + return "\(location.latitude), \(location.longitude)" + } + } + } + + let _ = (addressSignal + |> deliverOnMainQueue).start(next: { address in + addressPromise.set(.single(address)) + updateState { current in + var current = current + current.location = PeerGeoLocation(latitude: location.latitude, longitude: location.longitude, address: address) + return current + } + }) + }) + pushImpl?(controller) }, updateWithVenue: { venue in guard let venueData = venue.venue else { return @@ -1221,7 +1157,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] }, action: { _, f in f(.default) - let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in + let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, completion: { value in applyValue(value) }) endEditingImpl?() @@ -1313,6 +1249,8 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId] } |> afterDisposed { actionsDisposable.dispose() + + let _ = avatarPickerHolder } let controller = ItemListController(context: context, state: signal) diff --git a/submodules/TelegramUI/Sources/CreatePeerAvatarSetup.swift b/submodules/TelegramUI/Sources/CreatePeerAvatarSetup.swift new file mode 100644 index 0000000000..603e188909 --- /dev/null +++ b/submodules/TelegramUI/Sources/CreatePeerAvatarSetup.swift @@ -0,0 +1,216 @@ +import Foundation +import UIKit +import SwiftSignalKit +import TelegramCore +import AccountContext +import MediaEditor +import MediaEditorScreen +import ItemListAvatarAndNameInfoItem +import Photos +import AVFoundation + +struct CreatePendingPeerAvatar { + let previewRepresentation: TelegramMediaImageRepresentation + let isLoadingPreview: Bool + let uploadedPhoto: Signal + let uploadedVideo: Signal? + let videoStartTimestamp: Double? + let markup: UploadPeerPhotoMarkup? + + var updatingAvatar: ItemListAvatarAndNameInfoItemUpdatingAvatar { + return .image(self.previewRepresentation, self.isLoadingPreview) + } +} + +enum CreatePeerAvatarSetup { + private static func makePhotoRepresentation(context: AccountContext, image: UIImage) -> (LocalFileMediaResource, TelegramMediaImageRepresentation)? { + guard let data = image.jpegData(compressionQuality: 0.6) else { + return nil + } + + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + context.engine.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) + let representation = TelegramMediaImageRepresentation( + dimensions: PixelDimensions(width: 640, height: 640), + resource: resource, + progressiveSizes: [], + immediateThumbnailData: nil, + hasVideo: false, + isPersonal: false + ) + return (resource, representation) + } + + static func photo(context: AccountContext, image: UIImage) -> CreatePendingPeerAvatar? { + guard let (resource, representation) = self.makePhotoRepresentation(context: context, image: image) else { + return nil + } + + return CreatePendingPeerAvatar( + previewRepresentation: representation, + isLoadingPreview: false, + uploadedPhoto: context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(resource)), + uploadedVideo: nil, + videoStartTimestamp: nil, + markup: nil + ) + } + + static func video( + context: AccountContext, + image: UIImage, + video: MediaEditorScreenImpl.MediaResult.VideoResult?, + values: MediaEditorValues?, + markup: UploadPeerPhotoMarkup?, + didCompleteLoadingPreview: @escaping (CreatePendingPeerAvatar) -> Void = { _ in } + ) -> CreatePendingPeerAvatar? { + var shouldUploadVideo = true + if markup != nil { + if let data = context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue { + shouldUploadVideo = true + } else { + shouldUploadVideo = false + } + } + + guard let (photoResource, representation) = self.makePhotoRepresentation(context: context, image: image) else { + return nil + } + + let uploadedPhoto = context.engine.peers.uploadedPeerPhoto(resource: EngineMediaResource(photoResource)) + + var videoStartTimestamp: Double? = nil + if let values, let coverImageTimestamp = values.coverImageTimestamp, coverImageTimestamp > 0.0 { + videoStartTimestamp = coverImageTimestamp - (values.videoTrimRange?.lowerBound ?? 0.0) + } + + let hasVideoUpload = shouldUploadVideo && video != nil && values != nil + guard hasVideoUpload, let video, let values else { + return CreatePendingPeerAvatar( + previewRepresentation: representation, + isLoadingPreview: false, + uploadedPhoto: uploadedPhoto, + uploadedVideo: nil, + videoStartTimestamp: videoStartTimestamp, + markup: markup + ) + } + + let account = context.account + let videoResource: Signal + + var exportSubject: Signal<(MediaEditorVideoExport.Subject, Double), NoError>? + switch video { + case let .imageFile(path): + if let image = UIImage(contentsOfFile: path) { + exportSubject = .single((.image(image: image), 3.0)) + } + case let .videoFile(path): + let asset = AVURLAsset(url: NSURL(fileURLWithPath: path) as URL) + exportSubject = .single((.video(asset: asset, isStory: false), asset.duration.seconds)) + case let .asset(localIdentifier): + exportSubject = Signal { subscriber in + let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil) + if fetchResult.count != 0 { + let asset = fetchResult.object(at: 0) + if asset.mediaType == .video { + PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in + if let avAsset { + subscriber.putNext((.video(asset: avAsset, isStory: true), avAsset.duration.seconds)) + subscriber.putCompletion() + } + } + } else { + let options = PHImageRequestOptions() + options.deliveryMode = .highQualityFormat + PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in + if let image { + subscriber.putNext((.image(image: image), 3.0)) + subscriber.putCompletion() + } + } + } + } + return EmptyDisposable + } + } + + guard let exportSubject else { + return CreatePendingPeerAvatar( + previewRepresentation: representation, + isLoadingPreview: false, + uploadedPhoto: uploadedPhoto, + uploadedVideo: nil, + videoStartTimestamp: videoStartTimestamp, + markup: markup + ) + } + + videoResource = exportSubject + |> castError(UploadPeerPhotoError.self) + |> mapToSignal { exportSubject, duration in + return Signal { subscriber in + let configuration = recommendedVideoExportConfiguration(values: values, duration: duration, forceFullHd: true, frameRate: 60.0, isAvatar: true) + let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") + let videoExport = MediaEditorVideoExport(postbox: context.account.postbox, subject: exportSubject, configuration: configuration, outputPath: tempFile.path, textScale: 2.0) + let _ = (videoExport.status + |> deliverOnMainQueue).startStandalone(next: { status in + switch status { + case .completed: + if let data = try? Data(contentsOf: URL(fileURLWithPath: tempFile.path), options: .mappedIfSafe) { + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + subscriber.putNext(resource) + subscriber.putCompletion() + } + EngineTempBox.shared.dispose(tempFile) + case .progress: + break + default: + break + } + }) + + return EmptyDisposable + } + } + + var completedAvatar: CreatePendingPeerAvatar? + let uploadedVideo = (videoResource + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { resource -> Signal in + if let resource { + return context.engine.peers.uploadedPeerVideo(resource: EngineMediaResource(resource)) + |> map(Optional.init) + } else { + return .single(nil) + } + } + |> afterNext { next in + if let next, next.isCompleted, let completedAvatar { + didCompleteLoadingPreview(completedAvatar) + } + }) + + let pendingAvatar = CreatePendingPeerAvatar( + previewRepresentation: representation, + isLoadingPreview: true, + uploadedPhoto: uploadedPhoto, + uploadedVideo: uploadedVideo, + videoStartTimestamp: videoStartTimestamp, + markup: markup + ) + completedAvatar = CreatePendingPeerAvatar( + previewRepresentation: representation, + isLoadingPreview: false, + uploadedPhoto: uploadedPhoto, + uploadedVideo: uploadedVideo, + videoStartTimestamp: videoStartTimestamp, + markup: markup + ) + + return pendingAvatar + } +} diff --git a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift index f40a2c3a50..a4d0f4a610 100644 --- a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import ChatPresentationInterfaceState import ChatInputPanelNode @@ -36,7 +35,7 @@ final class DeleteChatInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.deleteChat() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/DeviceContactDataManager.swift b/submodules/TelegramUI/Sources/DeviceContactDataManager.swift index 576b6d6f64..864f858896 100644 --- a/submodules/TelegramUI/Sources/DeviceContactDataManager.swift +++ b/submodules/TelegramUI/Sources/DeviceContactDataManager.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import Contacts import TelegramUIPreferences @@ -16,7 +15,7 @@ private protocol DeviceContactDataContext { func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? func createContactWithData(_ contactData: DeviceContactExtendedData) -> (DeviceContactStableId, DeviceContactExtendedData)? - func deleteContactWithAppSpecificReference(peerId: PeerId) + func deleteContactWithAppSpecificReference(peerId: EnginePeer.Id) } private final class DeviceContactDataModernContext: DeviceContactDataContext { @@ -25,11 +24,11 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { let store = CNContactStore() var updateHandle: NSObjectProtocol? var currentContacts: [DeviceContactStableId: DeviceContactBasicData] = [:] - var currentAppSpecificReferences: [PeerId: DeviceContactBasicDataWithReference] = [:] + var currentAppSpecificReferences: [EnginePeer.Id: DeviceContactBasicDataWithReference] = [:] private var retrieveContactsDisposable: Disposable? - init(queue: Queue, accountManager: AccountManager, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void, appSpecificReferencesUpdated: @escaping ([PeerId: DeviceContactBasicDataWithReference]) -> Void) { + init(queue: Queue, accountManager: AccountManager, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void, appSpecificReferencesUpdated: @escaping ([EnginePeer.Id: DeviceContactBasicDataWithReference]) -> Void) { self.queue = queue self.accountManager = accountManager @@ -78,12 +77,12 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { } } - private func retrieveContacts() -> Signal<([DeviceContactStableId: DeviceContactBasicData], [PeerId: DeviceContactBasicDataWithReference]), NoError> { + private func retrieveContacts() -> Signal<([DeviceContactStableId: DeviceContactBasicData], [EnginePeer.Id: DeviceContactBasicDataWithReference]), NoError> { return self.accountManager.transaction { transaction -> DeviceContactDataState? in return transaction.getSharedData(SharedDataKeys.deviceContacts)?.get(DeviceContactDataState.self) } |> deliverOn(self.queue) - |> map { currentData -> ([DeviceContactStableId: DeviceContactBasicData], [PeerId: DeviceContactBasicDataWithReference]) in + |> map { currentData -> ([DeviceContactStableId: DeviceContactBasicData], [EnginePeer.Id: DeviceContactBasicDataWithReference]) in if let currentData, let stateToken = currentData.stateToken, !sharedDisableDeviceContactDataDiffing { final class ChangeVisitor: NSObject, CNChangeHistoryEventVisitor { let onDropEverything: () -> Void @@ -126,8 +125,8 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { ] var contacts: [DeviceContactStableId: DeviceContactBasicData] = currentData.contacts - var telegramReferences: [PeerId: String] = currentData.telegramReferences - var reverseTelegramReferences: [String: Set] = [:] + var telegramReferences: [EnginePeer.Id: String] = currentData.telegramReferences + var reverseTelegramReferences: [String: Set] = [:] for (peerId, id) in telegramReferences { if reverseTelegramReferences[id] == nil { reverseTelegramReferences[id] = Set() @@ -183,12 +182,12 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { let _ = self.accountManager.transaction({ transaction -> Void in transaction.updateSharedData(SharedDataKeys.deviceContacts, { _ in - return PreferencesEntry(DeviceContactDataState( + return EnginePreferencesEntry(DeviceContactDataState( contacts: contacts, telegramReferences: telegramReferences, stateToken: resultState?.stateToken)) }) }).startStandalone() - var references: [PeerId: DeviceContactBasicDataWithReference] = [:] + var references: [EnginePeer.Id: DeviceContactBasicDataWithReference] = [:] for (peerId, id) in telegramReferences { if let basicData = contacts[id] { references[peerId] = DeviceContactBasicDataWithReference(stableId: id, basicData: basicData) @@ -220,12 +219,12 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { let _ = self.accountManager.transaction({ transaction -> Void in transaction.updateSharedData(SharedDataKeys.deviceContacts, { _ in - return PreferencesEntry(DeviceContactDataState( + return EnginePreferencesEntry(DeviceContactDataState( contacts: contacts, telegramReferences: telegramReferences, stateToken: resultState?.stateToken)) }) }).startStandalone() - var references: [PeerId: DeviceContactBasicDataWithReference] = [:] + var references: [EnginePeer.Id: DeviceContactBasicDataWithReference] = [:] for (peerId, id) in telegramReferences { if let basicData = contacts[id] { references[peerId] = DeviceContactBasicDataWithReference(stableId: id, basicData: basicData) @@ -455,7 +454,7 @@ private final class DeviceContactDataModernContext: DeviceContactDataContext { return (mutableContact.identifier, contactData) } - func deleteContactWithAppSpecificReference(peerId: PeerId) { + func deleteContactWithAppSpecificReference(peerId: EnginePeer.Id) { guard let reference = self.currentAppSpecificReferences[peerId] else { return } @@ -495,8 +494,8 @@ private final class DeviceContactDataManagerPrivateImpl { private var stableIdToBasicContactData: [DeviceContactStableId: DeviceContactBasicData] = [:] private var normalizedPhoneNumberToStableId: [DeviceContactNormalizedPhoneNumber: [DeviceContactStableId]] = [:] - private var appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference] = [:] - private var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:] + private var appSpecificReferences: [EnginePeer.Id: DeviceContactBasicDataWithReference] = [:] + private var stableIdToAppSpecificReference: [DeviceContactStableId: EnginePeer.Id] = [:] private var importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData] = [:] @@ -506,7 +505,7 @@ private final class DeviceContactDataManagerPrivateImpl { private let basicDataSubscribers = Bag<([DeviceContactStableId: DeviceContactBasicData]) -> Void>() private var basicDataForNormalizedNumberContexts: [DeviceContactNormalizedPhoneNumber: BasicDataForNormalizedNumberContext] = [:] private let importableContactsSubscribers = Bag<([DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]) -> Void>() - private let appSpecificReferencesSubscribers = Bag<([PeerId: DeviceContactBasicDataWithReference]) -> Void>() + private let appSpecificReferencesSubscribers = Bag<([EnginePeer.Id: DeviceContactBasicDataWithReference]) -> Void>() init(queue: Queue, accountManager: AccountManager) { self.queue = queue @@ -620,9 +619,9 @@ private final class DeviceContactDataManagerPrivateImpl { } } - private func updateAppSpecificReferences(appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference]) { + private func updateAppSpecificReferences(appSpecificReferences: [EnginePeer.Id: DeviceContactBasicDataWithReference]) { self.appSpecificReferences = appSpecificReferences - var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:] + var stableIdToAppSpecificReference: [DeviceContactStableId: EnginePeer.Id] = [:] for (peerId, value) in appSpecificReferences { stableIdToAppSpecificReference[value.stableId] = peerId } @@ -712,7 +711,7 @@ private final class DeviceContactDataManagerPrivateImpl { } } - func appSpecificReferences(updated: @escaping ([PeerId: DeviceContactBasicDataWithReference]) -> Void) -> Disposable { + func appSpecificReferences(updated: @escaping ([EnginePeer.Id: DeviceContactBasicDataWithReference]) -> Void) -> Disposable { let queue = self.queue let index = self.appSpecificReferencesSubscribers.add({ data in @@ -732,9 +731,9 @@ private final class DeviceContactDataManagerPrivateImpl { } } - func search(query: String, updated: @escaping ([DeviceContactStableId: (DeviceContactBasicData, PeerId?)]) -> Void) -> Disposable { + func search(query: String, updated: @escaping ([DeviceContactStableId: (DeviceContactBasicData, EnginePeer.Id?)]) -> Void) -> Disposable { let normalizedQuery = query.lowercased() - var result: [DeviceContactStableId: (DeviceContactBasicData, PeerId?)] = [:] + var result: [DeviceContactStableId: (DeviceContactBasicData, EnginePeer.Id?)] = [:] for (stableId, basicData) in self.stableIdToBasicContactData { if basicData.firstName.lowercased().hasPrefix(normalizedQuery) || basicData.lastName.lowercased().hasPrefix(normalizedQuery) { result[stableId] = (basicData, self.stableIdToAppSpecificReference[stableId]) @@ -759,7 +758,7 @@ private final class DeviceContactDataManagerPrivateImpl { completion(result) } - func deleteContactWithAppSpecificReference(peerId: PeerId, completion: @escaping () -> Void) { + func deleteContactWithAppSpecificReference(peerId: EnginePeer.Id, completion: @escaping () -> Void) { self.dataContext?.deleteContactWithAppSpecificReference(peerId: peerId) completion() } @@ -836,7 +835,7 @@ public final class DeviceContactDataManagerImpl: DeviceContactDataManager { } } - public func appSpecificReferences() -> Signal<[PeerId: DeviceContactBasicDataWithReference], NoError> { + public func appSpecificReferences() -> Signal<[EnginePeer.Id: DeviceContactBasicDataWithReference], NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with({ impl in @@ -848,7 +847,7 @@ public final class DeviceContactDataManagerImpl: DeviceContactDataManager { } } - public func search(query: String) -> Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError> { + public func search(query: String) -> Signal<[DeviceContactStableId: (DeviceContactBasicData, EnginePeer.Id?)], NoError> { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with({ impl in @@ -900,7 +899,7 @@ public final class DeviceContactDataManagerImpl: DeviceContactDataManager { } } - public func deleteContactWithAppSpecificReference(peerId: PeerId) -> Signal { + public func deleteContactWithAppSpecificReference(peerId: EnginePeer.Id) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with({ impl in diff --git a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift index 69025bf1fe..f86bc5defd 100644 --- a/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/DisabledContextResultsChatInputContextPanelNode.swift @@ -43,7 +43,8 @@ final class DisabledContextResultsChatInputContextPanelNode: ChatInputContextPan self.containerNode.backgroundColor = interfaceState.theme.list.plainBackgroundColor self.separatorNode.backgroundColor = interfaceState.theme.list.itemPlainSeparatorColor - guard let (untilDate, personal) = (interfaceState.renderedPeer?.peer as? TelegramChannel)?.hasBannedPermission(.banSendInline) else { + let canBypass = canBypassRestrictions(chatPresentationInterfaceState: interfaceState) + guard let (untilDate, personal) = (interfaceState.renderedPeer?.peer as? TelegramChannel)?.hasBannedPermission(.banSendInline, ignoreDefault: canBypass) else { return } let banDescription: String diff --git a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift index c85b9b2421..e54a1ed650 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import TelegramPresentationData @@ -25,7 +24,7 @@ import ChatInputContextPanelNode private enum EmojisChatInputContextPanelEntryStableId: Hashable, Equatable { case symbol(String) - case media(MediaId) + case media(EngineMedia.Id) } private func backgroundCenterImage(_ theme: PresentationTheme) -> UIImage? { @@ -236,19 +235,19 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { let _ = file let _ = itemLayer - var collectionId: ItemCollectionId? + var collectionId: EngineItemCollectionId? for attribute in file.attributes { if case let .CustomEmoji(_, _, _, packReference) = attribute { switch packReference { case let .id(id, _): - collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + collectionId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) default: break } } } - var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var bubbleUpEmojiOrStickersets: [EngineItemCollectionId] = [] if let collectionId { bubbleUpEmojiOrStickersets.append(collectionId) } @@ -309,9 +308,9 @@ final class EmojisChatInputContextPanelNode: ChatInputContextPanelNode { case let .CustomEmoji(_, _, displayText, stickerPackReference): text = displayText - var packId: ItemCollectionId? + var packId: EngineItemCollectionId? if case let .id(id, _) = stickerPackReference { - packId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) + packId = EngineItemCollectionId(namespace: Namespaces.ItemCollection.CloudEmojiPacks, id: id) } emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: packId, fileId: file.fileId.id, file: file) break loop diff --git a/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift b/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift index 99908fef41..5b3c76031d 100644 --- a/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/EmojisChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AnimationCache import MultiAnimationRenderer diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index 08ad7c9271..1aa4751861 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -18,7 +18,7 @@ import WallpaperResources import GZip import TelegramUniversalVideoContent import GradientBackground -import Svg +import LegacyImpl import UniversalMediaPlayer import RangeSet @@ -29,7 +29,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR if !data.complete { return .complete() } - return fetchCachedStickerAJpegRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + return fetchCachedStickerAJpegRepresentation(resource: resource, resourceData: data, representation: representation) } } else if let representation = representation as? CachedScaledImageRepresentation { return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) @@ -130,7 +130,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) |> mapToSignal { data -> Signal in if data.complete, let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { - return fetchCachedAlbumArtworkRepresentation(account: account, resource: resource, data: fileData, representation: representation) + return fetchCachedAlbumArtworkRepresentation(resource: resource, data: fileData, representation: representation) |> `catch` { _ -> Signal in return .complete() } @@ -138,7 +138,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR return account.postbox.mediaBox.resourceData(resource, size: size, in: 0 ..< min(size, 256 * 1024)) |> mapToSignal { result -> Signal in let (data, _) = result - return fetchCachedAlbumArtworkRepresentation(account: account, resource: resource, data: data, representation: representation) + return fetchCachedAlbumArtworkRepresentation(resource: resource, data: data, representation: representation) |> `catch` { error -> Signal in switch error { case let .moreDataNeeded(targetSize): @@ -146,7 +146,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR |> mapToSignal { result -> Signal in let (data, _) = result - return fetchCachedAlbumArtworkRepresentation(account: account, resource: resource, data: data, representation: representation) + return fetchCachedAlbumArtworkRepresentation(resource: resource, data: data, representation: representation) |> `catch` { error -> Signal in return .complete() } @@ -164,7 +164,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR if !data.complete { return .complete() } - return fetchAnimatedStickerRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + return fetchAnimatedStickerRepresentation(resource: resource, resourceData: data, representation: representation) } } else if let representation = representation as? CachedVideoStickerRepresentation { return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) @@ -172,7 +172,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR if !data.complete { return .complete() } - return fetchVideoStickerRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + return fetchVideoStickerRepresentation(resource: resource, resourceData: data, representation: representation) } } else if let representation = representation as? CachedAnimatedStickerFirstFrameRepresentation { return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) @@ -180,7 +180,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR if !data.complete { return .complete() } - return fetchAnimatedStickerFirstFrameRepresentation(account: account, resource: resource, resourceData: data, representation: representation) + return fetchAnimatedStickerFirstFrameRepresentation(resource: resource, resourceData: data, representation: representation) } } else if let resource = resource as? YoutubeEmbedStoryboardMediaResource, let _ = representation as? YoutubeEmbedStoryboardMediaResourceRepresentation { return fetchYoutubeEmbedStoryboardResource(resource: resource) @@ -210,7 +210,7 @@ private func videoFirstFrameData(account: Account, resource: MediaResource, chun |> mapToSignal { _ -> Signal in return account.postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: false), attemptSynchronously: false) |> mapToSignal { data -> Signal in - return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data) + return fetchCachedVideoFirstFrameRepresentation(resource: resource, resourceData: data) |> `catch` { _ -> Signal in if chunkSize > size { return .complete() @@ -225,7 +225,7 @@ private func videoFirstFrameData(account: Account, resource: MediaResource, chun } } -private func fetchCachedStickerAJpegRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal { +private func fetchCachedStickerAJpegRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedStickerAJpegRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { var image: UIImage? @@ -371,7 +371,7 @@ public enum FetchVideoFirstFrameError { case generic } -private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData) -> Signal { +private func fetchCachedVideoFirstFrameRepresentation(resource: MediaResource, resourceData: MediaResourceData) -> Signal { return Signal { subscriber in let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" do { @@ -499,38 +499,11 @@ public func fetchCachedSharedResourceRepresentation(accountManager: AccountManag } } -private func fetchCachedBlurredWallpaperRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedBlurredWallpaperRepresentation) -> Signal { - return Signal({ subscriber in - if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { - if let image = UIImage(data: data) { - let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" - let url = URL(fileURLWithPath: path) - - if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { - CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary) - - let colorQuality: Float = 0.5 - - let options = NSMutableDictionary() - options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString) - - CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary) - if CGImageDestinationFinalize(colorDestination) { - subscriber.putNext(.temporaryPath(path)) - subscriber.putCompletion() - } - } - } - } - return EmptyDisposable - }) |> runOn(Queue.concurrentDefaultQueue()) -} - public enum FetchAlbumArtworkError { case moreDataNeeded(Int) } -private func fetchCachedAlbumArtworkRepresentation(account: Account, resource: MediaResource, data: Data, representation: CachedAlbumArtworkRepresentation) -> Signal { +private func fetchCachedAlbumArtworkRepresentation(resource: MediaResource, data: Data, representation: CachedAlbumArtworkRepresentation) -> Signal { return Signal({ subscriber in let result = readAlbumArtworkData(data) switch result { @@ -573,7 +546,7 @@ private func fetchCachedAlbumArtworkRepresentation(account: Account, resource: M }) |> runOn(Queue.concurrentDefaultQueue()) } -private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal { +private func fetchAnimatedStickerFirstFrameRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.stringRepresentation)-\(representation.uniqueId)").start(next: { file in @@ -587,7 +560,7 @@ private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, reso |> runOn(Queue.concurrentDefaultQueue()) } -private func fetchAnimatedStickerRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerRepresentation) -> Signal { +private func fetchAnimatedStickerRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerRepresentation) -> Signal { return Signal({ subscriber in if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) { return cacheAnimatedStickerFrames(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.stringRepresentation)-\(representation.uniqueId)").start(next: { value in @@ -602,7 +575,7 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi |> runOn(Queue.concurrentDefaultQueue()) } -private func fetchVideoStickerRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedVideoStickerRepresentation) -> Signal { +private func fetchVideoStickerRepresentation(resource: MediaResource, resourceData: MediaResourceData, representation: CachedVideoStickerRepresentation) -> Signal { return Signal({ subscriber in return cacheVideoStickerFrames(path: resourceData.path, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.stringRepresentation)-\(representation.uniqueId)").start(next: { value in subscriber.putNext(value) diff --git a/submodules/TelegramUI/Sources/FetchResource.swift b/submodules/TelegramUI/Sources/FetchResource.swift deleted file mode 100644 index 0210791a26..0000000000 --- a/submodules/TelegramUI/Sources/FetchResource.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation -import Postbox -import TelegramCore -import SwiftSignalKit - -func fetchResource(account: Account, resource: MediaResource, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>) -> Signal? { - return nil -} - diff --git a/submodules/TelegramUI/Sources/GalleryHiddenMediaManager.swift b/submodules/TelegramUI/Sources/GalleryHiddenMediaManager.swift index ae494a4432..43e16e1fc1 100644 --- a/submodules/TelegramUI/Sources/GalleryHiddenMediaManager.swift +++ b/submodules/TelegramUI/Sources/GalleryHiddenMediaManager.swift @@ -1,6 +1,6 @@ import Foundation import UIKit -import Postbox +import TelegramCore import SwiftSignalKit import AsyncDisplayKit import AccountContext @@ -144,7 +144,7 @@ final class GalleryHiddenMediaManagerImpl: GalleryHiddenMediaManager { } } - func findTarget(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? { + func findTarget(messageId: EngineMessage.Id, media: EngineMedia) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? { for i in (0 ..< self.targets.count).reversed() { if let holderTarget = self.targets[i].target { if let result = holderTarget.getTransitionInfo(messageId: messageId, media: media) { diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index 5a57356c20..fdd72a5cd6 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import TelegramPresentationData diff --git a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift index 9dfef707b0..84715bc9dc 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -272,7 +271,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { strongSelf.activateAreaNode.accessibilityLabel = item.title strongSelf.activateAreaNode.frame = CGRect(origin: .zero, size: nodeLayout.size) - strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]) + strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]) strongSelf.setRevealOptionsOpened(item.revealed, animated: animation.isAnimated) } }) diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index c76495b3ec..76fcf7bc4b 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import SwiftSignalKit @@ -19,6 +18,9 @@ import ChatControllerInteraction import ChatContextResultPeekContent import ChatInputContextPanelNode import BatchVideoRendering +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters private struct ChatContextResultStableId: Hashable { let result: ChatContextResult @@ -83,6 +85,9 @@ private func preparedTransition(from fromEntries: [HorizontalListContextResultsC } final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { + private let backgroundContainerView: GlassBackgroundContainerView + private let backgroundView: GlassBackgroundView + private let listClippingView: UIView private let listView: ListView private var currentExternalResults: ChatContextResultCollection? private var currentProcessedResults: ChatContextResultCollection? @@ -96,9 +101,14 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont private let batchVideoContext: QueueLocalObject override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) { + self.backgroundContainerView = GlassBackgroundContainerView() + self.backgroundView = GlassBackgroundView() + self.backgroundContainerView.contentView.addSubview(self.backgroundView) + self.listClippingView = UIView() + self.listClippingView.clipsToBounds = true + self.listView = ListViewImpl() - self.listView.isOpaque = true - self.listView.backgroundColor = theme.list.plainBackgroundColor + self.listView.isOpaque = false self.listView.transform = CATransform3DMakeRotation(-CGFloat(CGFloat.pi / 2.0), 0.0, 0.0, 1.0) self.listView.isHidden = true self.listView.accessibilityPageScrolledString = { row, count in @@ -112,9 +122,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext) self.isOpaque = false - self.clipsToBounds = true + self.clipsToBounds = false + self.layer.allowsGroupOpacity = true - self.addSubnode(self.listView) + self.view.addSubview(self.backgroundContainerView) + self.listClippingView.addSubview(self.listView.view) + self.backgroundView.contentView.addSubview(self.listClippingView) self.listView.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in if let strongSelf = self, let state = opaqueTransactionState as? HorizontalListContextResultsOpaqueState { @@ -362,12 +375,26 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { let listHeight: CGFloat = 105.0 + let sideInset: CGFloat = 8.0 + let innerInset: CGFloat = 4.0 + let cornerRadius: CGFloat = 8.0 + let innerRadius: CGFloat = cornerRadius - innerInset - self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: listHeight, height: size.width) + let listFrame = CGRect(x: sideInset, y: size.height - bottomInset - 8.0 - listHeight, width: size.width - sideInset * 2.0, height: listHeight) + let transformedListFrame = CGSize(width: listFrame.height, height: listFrame.width).centered(in: listFrame) + self.listView.bounds = CGRect(origin: CGPoint(), size: transformedListFrame.size) + transition.updatePosition(node: self.listView, position: CGRect(origin: CGPoint(x: -innerInset, y: -innerInset), size: listFrame.size).center) - //transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + transition.updateFrame(view: self.listClippingView, frame: CGRect(origin: CGPoint(), size: listFrame.size).insetBy(dx: innerInset, dy: innerInset)) + self.listClippingView.layer.cornerRadius = innerRadius - transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - bottomInset - 8.0 - listHeight / 2.0)) + let backgroundContainerInset: CGFloat = 32.0 + let backgroundContainerFrame = listFrame.insetBy(dx: -backgroundContainerInset, dy: -backgroundContainerInset) + transition.updateFrame(view: self.backgroundContainerView, frame: backgroundContainerFrame) + self.backgroundContainerView.update(size: backgroundContainerFrame.size, isDark: interfaceState.theme.overallDarkAppearance, transition: ComponentTransition(transition)) + + transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: listFrame.size).offsetBy(dx: backgroundContainerInset, dy: backgroundContainerInset)) + self.backgroundView.update(size: listFrame.size, cornerRadius: cornerRadius, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: ComponentTransition(transition)) var insets = UIEdgeInsets() insets.top = leftInset @@ -392,22 +419,18 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont } override func animateOut(completion: @escaping () -> Void) { - /*let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + self.listView.bounds.size.width), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - completion() - })*/ - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + ComponentTransition.easeInOut(duration: 0.3).setAlpha(view: self.backgroundContainerView, alpha: 0.01, completion: { _ in completion() }) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let listViewBounds = self.listView.bounds - let listViewPosition = self.listView.position - let listViewFrame = CGRect(origin: CGPoint(x: listViewPosition.x - listViewBounds.height / 2.0, y: listViewPosition.y - listViewBounds.width / 2.0), size: CGSize(width: listViewBounds.height, height: listViewBounds.width)) - if !listViewFrame.contains(point) { + guard let result = super.hitTest(point, with: event) else { return nil } - return super.hitTest(point, with: event) + if result === self.view { + return nil + } + return result } } diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index ed79bfdbf4..4def8c5536 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import AVFoundation import RadialStatusNode import StickerResources @@ -97,7 +96,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode private var currentImageResource: TelegramMediaResource? private var currentVideoFile: TelegramMediaFile? private var currentAnimatedStickerFile: TelegramMediaFile? - private var resourceStatus: MediaResourceStatus? + private var resourceStatus: EngineMediaResource.FetchStatus? private(set) var item: HorizontalListContextResultsChatInputPanelItem? private var statusDisposable = MetaDisposable() private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) @@ -202,7 +201,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode let sideInset: CGFloat = 4.0 var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - var updatedStatusSignal: Signal? + var updatedStatusSignal: Signal? var imageResource: TelegramMediaResource? var stickerFile: TelegramMediaFile? @@ -218,14 +217,14 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } imageDimensions = externalReference.content?.dimensions?.cgSize if externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let content = externalReference.content, let dimensions = content.dimensions { - videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) + videoFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: []) imageResource = nil } if let file = videoFile { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(file.resource)) } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } case let .internalReference(internalReference): if let image = internalReference.image { @@ -254,12 +253,12 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode if file.isVideo && file.isAnimated { videoFile = file imageResource = nil - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(file.resource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(file.resource)) } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } } else if let imageResource = imageResource { - updatedStatusSignal = item.context.account.postbox.mediaBox.resourceStatus(imageResource) + updatedStatusSignal = item.context.engine.resources.status(resource: EngineMediaResource(imageResource)) } } @@ -312,7 +311,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode updateImageSignal = chatMessageSticker(account: item.context.account, userLocation: .other, file: stickerFile, small: false, fetched: true) } else { let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(CGSize(width: fittedImageDimensions.width * 2.0, height: fittedImageDimensions.height * 2.0)), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) - let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) + let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) updateImageSignal = chatMessagePhoto(postbox: item.context.account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), synchronousLoad: true) } } else { diff --git a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift index 3d15ecc22a..bc2da20523 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickerGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import StickerResources import AccountContext import AnimatedStickerNode @@ -81,7 +80,7 @@ final class HorizontalStickerGridItemNode: GridItemNode { var stickerItem: StickerPackItem? { if let (_, item, _) = self.currentState { - return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.file, indexKeys: []) + return StickerPackItem(index: EngineItemCollectionItemIndex(index: 0, id: 0), file: item.file, indexKeys: []) } else { return nil } @@ -256,7 +255,7 @@ final class HorizontalStickerGridItemNode: GridItemNode { placeholderNode.frame = bounds if let context = self.currentState?.0, let theme = self.currentState?.1.theme, let file = self.currentState?.1.file { - placeholderNode.update(backgroundColor: theme.list.plainBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor.mixedWith(theme.list.plainBackgroundColor, alpha: 0.4), shimmeringColor: theme.list.mediaPlaceholderColor.withAlphaComponent(0.3), data: file.immediateThumbnailData, size: bounds.size, enableEffect: context.sharedContext.energyUsageSettings.fullTranslucency) + placeholderNode.update(backgroundColor: nil, foregroundColor: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), shimmeringColor: theme.list.mediaPlaceholderColor.withAlphaComponent(0.2), data: file.immediateThumbnailData, size: bounds.size, enableEffect: context.sharedContext.energyUsageSettings.fullTranslucency) } } diff --git a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift index 71d8698f71..ceca474702 100755 --- a/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalStickersChatContextPanelNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import Display import SwiftSignalKit @@ -59,7 +58,7 @@ private struct StickerEntry: Identifiable, Comparable { let index: Int let file: TelegramMediaFile - var stableId: MediaId { + var stableId: EngineMedia.Id { return self.file.fileId } diff --git a/submodules/TelegramUI/Sources/ID3ArtworkReader.swift b/submodules/TelegramUI/Sources/ID3ArtworkReader.swift index 0fdc8aef04..29ea8dc742 100644 --- a/submodules/TelegramUI/Sources/ID3ArtworkReader.swift +++ b/submodules/TelegramUI/Sources/ID3ArtworkReader.swift @@ -4,35 +4,37 @@ private enum ID3Tag: CaseIterable { case v2 case v3 - static var headerLength = 5 + static let headerLength = 10 + static let versionHeaderLength = 5 + var header: Data { switch self { case .v2: - return Data([ 0x49, 0x44, 0x33, 0x02, 0x00 ]) + return Data([0x49, 0x44, 0x33, 0x02, 0x00]) case .v3: - return Data([ 0x49, 0x44, 0x33, 0x03, 0x00 ]) + return Data([0x49, 0x44, 0x33, 0x03, 0x00]) } } var artworkHeader: Data { switch self { case .v2: - return Data([ 0x50, 0x49, 0x43 ]) + return Data([0x50, 0x49, 0x43]) case .v3: - return Data([ 0x41, 0x50, 0x49, 0x43 ]) + return Data([0x41, 0x50, 0x49, 0x43]) } } - var frameSizeOffset: Int { + var frameIdentifierLength: Int { switch self { case .v2: - return 2 + return 3 case .v3: return 4 } } - var frameOffset: Int { + var frameHeaderLength: Int { switch self { case .v2: return 6 @@ -41,25 +43,30 @@ private enum ID3Tag: CaseIterable { } } - func frameSize(_ value: Int32) -> Int { + var unsupportedFlagsMask: UInt8 { + switch self { + case .v2, .v3: + return 0xc0 + } + } + + func frameDataSize(in data: Data, at frameOffset: Int) -> Int? { switch self { case .v2: - return Int(value & 0x00ffffff) + self.frameOffset + guard let range = makeRange(start: frameOffset + self.frameIdentifierLength, length: 3, upperBound: data.count) else { + return nil + } + let bytes = data[range] + return Int(UInt32(bytes[bytes.startIndex]) << 16 | UInt32(bytes[bytes.startIndex + 1]) << 8 | UInt32(bytes[bytes.startIndex + 2])) case .v3: - return Int(value) + self.frameOffset + guard let value = readBigEndianUInt32(in: data, at: frameOffset + self.frameIdentifierLength) else { + return nil + } + return Int(value) } } } -private let sizeOffset = 6 -private let tagOffset = 10 -private let artOffset = 10 - -private let v2FrameOffset: UInt32 = 6 -private let v3FrameOffset: UInt32 = 10 - -private let tagEnding = Data([ 0x00, 0x00, 0x00 ]) - private enum ID3ArtworkFormat: CaseIterable { case jpg case png @@ -67,77 +74,103 @@ private enum ID3ArtworkFormat: CaseIterable { var magic: Data { switch self { case .jpg: - return Data([ 0xff, 0xd8, 0xff ]) + return Data([0xff, 0xd8, 0xff]) case .png: - return Data([ 0x89, 0x50, 0x4e, 0x47 ]) + return Data([0x89, 0x50, 0x4e, 0x47]) } } } -private class DataStream { - private let data: Data - private(set) var position = 0 - var reachedEnd: Bool { - return self.position >= self.data.count +private enum ID3ArtworkReaderLimits { + static let maximumTagSize = 16 * 1024 * 1024 + static let maximumFrameSize = 16 * 1024 * 1024 + static let maximumArtworkSize = 16 * 1024 * 1024 +} + +private let id3Prefix = Data([0x49, 0x44, 0x33]) +private let tagEnding = Data([0x00, 0x00, 0x00]) +private let jpegEndMarker = Data([0xff, 0xd9]) +private let pngEndMarker = Data([0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]) + +private func makeRange(start: Int, length: Int, upperBound: Int) -> Range? { + guard start >= 0, length >= 0 else { + return nil } - - init(_ data: Data) { - self.data = data + let (end, overflow) = start.addingReportingOverflow(length) + guard !overflow, end <= upperBound else { + return nil } - - func next() -> UInt8? { - guard !self.reachedEnd else { - return nil - } - let byte = self.data[self.position] - self.position += 1 - return byte + return start ..< end +} + +private func checkedAdd(_ lhs: Int, _ rhs: Int) -> Int? { + let (result, overflow) = lhs.addingReportingOverflow(rhs) + return overflow ? nil : result +} + +private func readBigEndianUInt32(in data: Data, at offset: Int) -> UInt32? { + guard let range = makeRange(start: offset, length: 4, upperBound: data.count) else { + return nil } - - func nextValue() -> T? { - let count = MemoryLayout.size - guard self.position + count <= self.data.count else { - return nil - } - let value = self.data.subdata(in: self.position ..< self.position + count).withUnsafeBytes { (pointer: UnsafeRawBufferPointer) -> T in - return pointer.baseAddress!.assumingMemoryBound(to: T.self).pointee - } - self.position += count - return value + let bytes = data[range] + return UInt32(bytes[bytes.startIndex]) << 24 + | UInt32(bytes[bytes.startIndex + 1]) << 16 + | UInt32(bytes[bytes.startIndex + 2]) << 8 + | UInt32(bytes[bytes.startIndex + 3]) +} + +private func decodeSynchsafeUInt32(_ value: UInt32) -> Int? { + guard value & 0x80808080 == 0 else { + return nil } + let b1 = Int((value >> 24) & 0x7f) + let b2 = Int((value >> 16) & 0x7f) + let b3 = Int((value >> 8) & 0x7f) + let b4 = Int(value & 0x7f) + return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4 +} + +private func extractArtworkData(from data: Data, frameDataRange: Range) -> Data? { + let frameData = data.subdata(in: frameDataRange) - func next(_ count: Int, offset: Int = 0, peek: Bool = false) -> Data? { - guard self.position + offset + count <= self.data.count else { - return nil + var bestMatch: (format: ID3ArtworkFormat, range: Range)? + for format in ID3ArtworkFormat.allCases { + guard let range = frameData.range(of: format.magic) else { + continue } - let subdata = self.data.subdata(in: self.position + offset ..< self.position + offset + count) - if !peek { - self.position += count + offset - } - return subdata - } - - func upTo(_ marker: UInt8) -> Data? { - if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) { - let upTo = self.next(end - self.position) - self.skip() - return upTo + if let current = bestMatch { + if range.lowerBound < current.range.lowerBound { + bestMatch = (format, range) + } } else { - return nil + bestMatch = (format, range) } } - func skip(_ count: Int = 1) { - self.position += count + guard let match = bestMatch else { + return nil } - func skipThrough(_ marker: UInt8) { - if let end = (self.position ..< self.data.count).firstIndex( where: { self.data[$0] == marker } ) { - self.position = end + 1 - } else { - self.position = self.data.count - } + let payload: Data + switch match.format { + case .jpg: + if let endMarkerRange = frameData[match.range.lowerBound ..< frameData.endIndex].range(of: jpegEndMarker) { + payload = frameData.subdata(in: match.range.lowerBound ..< endMarkerRange.upperBound) + } else { + payload = frameData.subdata(in: match.range.lowerBound ..< frameData.endIndex) + } + case .png: + if let endMarkerRange = frameData[match.range.lowerBound ..< frameData.endIndex].range(of: pngEndMarker) { + payload = frameData.subdata(in: match.range.lowerBound ..< endMarkerRange.upperBound) + } else { + payload = frameData.subdata(in: match.range.lowerBound ..< frameData.endIndex) + } } + + guard payload.count <= ID3ArtworkReaderLimits.maximumArtworkSize else { + return nil + } + return payload } enum ID3ArtworkResult { @@ -147,12 +180,17 @@ enum ID3ArtworkResult { } func readAlbumArtworkData(_ data: Data) -> ID3ArtworkResult { - guard data.count >= 4 else { - return .notFound + if data.count < id3Prefix.count { + return id3Prefix.starts(with: data) ? .moreDataNeeded(ID3Tag.headerLength) : .notFound + } + if data.count < ID3Tag.headerLength { + return data.starts(with: id3Prefix) ? .moreDataNeeded(ID3Tag.headerLength) : .notFound } - let stream = DataStream(data) - let versionHeader = stream.next(ID3Tag.headerLength) + guard let versionHeaderRange = makeRange(start: 0, length: ID3Tag.versionHeaderLength, upperBound: data.count) else { + return .notFound + } + let versionHeader = data.subdata(in: versionHeaderRange) var version: ID3Tag? for tag in ID3Tag.allCases { @@ -165,79 +203,57 @@ func readAlbumArtworkData(_ data: Data) -> ID3ArtworkResult { return .notFound } - stream.skip() - guard let value: UInt32 = stream.nextValue() else { + let flags = data[5] + guard flags & id3Tag.unsupportedFlagsMask == 0 else { return .notFound } - let size = CFSwapInt32HostToBig(value) - let b1 = (size & 0x7f000000) >> 3 - let b2 = (size & 0x007f0000) >> 2 - let b3 = (size & 0x00007f00) >> 1 - let b4 = size & 0x0000007f - let tagSize = Int(b1 + b2 + b3 + b4) - while !stream.reachedEnd { - guard let frameHeader = stream.next(4, peek: true) else { - return .moreDataNeeded(tagSize) - } - - stream.skip(id3Tag.frameSizeOffset) - guard let value: UInt32 = stream.nextValue() else { - return .moreDataNeeded(tagSize) - } - let val = CFSwapInt32HostToBig(value) - if val > Int32.max { - return .notFound - } - let frameSize = id3Tag.frameSize(Int32(val)) - let bytesLeft = frameSize - id3Tag.frameSizeOffset - 4 - - if frameHeader == id3Tag.artworkHeader { - var image: (ID3ArtworkFormat, Int)? - outer: for i in 0 ..< frameSize - 4 { - if let head = stream.next(4, offset: i, peek: true) { - for format in ID3ArtworkFormat.allCases { - if head.prefix(format.magic.count) == format.magic { - image = (format, i) - break outer - } - } - } - } - - if let (format, offset) = image { - stream.skip(offset) - - switch format { - case .jpg: - var data = Data(capacity: frameSize + 1024) - var previousByte: UInt8 = 0xff - - let limit = Int(Double(frameSize - offset) * 0.8) - for _ in 0 ..< frameSize - offset { - if let byte: UInt8 = stream.nextValue() { - data.append(byte) - if byte == 0xd9 && previousByte == 0xff && data.count > limit { - break - } - previousByte = byte - } else { - return .moreDataNeeded(tagSize) - } - } - return .artworkData(data) - case .png: - if let data = stream.next(frameSize - offset) { - return .artworkData(data) - } else { - return .moreDataNeeded(tagSize) - } - } - } - } else if frameHeader.prefix(3) == tagEnding { - return .notFound - } - stream.skip(bytesLeft) + guard let rawTagSize = readBigEndianUInt32(in: data, at: 6), let tagPayloadSize = decodeSynchsafeUInt32(rawTagSize) else { + return .notFound } + guard let totalTagSize = checkedAdd(ID3Tag.headerLength, tagPayloadSize), totalTagSize <= ID3ArtworkReaderLimits.maximumTagSize else { + return .notFound + } + + let availableTagEnd = min(totalTagSize, data.count) + var frameOffset = ID3Tag.headerLength + + while frameOffset < availableTagEnd { + let remainingTagBytes = availableTagEnd - frameOffset + if remainingTagBytes < id3Tag.frameHeaderLength { + return totalTagSize > data.count ? .moreDataNeeded(totalTagSize) : .notFound + } + + guard let frameIdentifierRange = makeRange(start: frameOffset, length: id3Tag.frameIdentifierLength, upperBound: data.count) else { + return totalTagSize > data.count ? .moreDataNeeded(totalTagSize) : .notFound + } + let frameIdentifier = data.subdata(in: frameIdentifierRange) + if frameIdentifier.prefix(3) == tagEnding { + return .notFound + } + + guard let framePayloadSize = id3Tag.frameDataSize(in: data, at: frameOffset), framePayloadSize <= ID3ArtworkReaderLimits.maximumFrameSize else { + return .notFound + } + guard let frameSize = checkedAdd(id3Tag.frameHeaderLength, framePayloadSize), let frameEnd = checkedAdd(frameOffset, frameSize) else { + return .notFound + } + guard frameEnd <= totalTagSize else { + return .notFound + } + if frameEnd > data.count { + return .moreDataNeeded(totalTagSize) + } + + if frameIdentifier == id3Tag.artworkHeader { + let frameDataStart = frameOffset + id3Tag.frameHeaderLength + if let artworkData = extractArtworkData(from: data, frameDataRange: frameDataStart ..< frameEnd) { + return .artworkData(artworkData) + } + } + + frameOffset = frameEnd + } + return .notFound } diff --git a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift index b96bef3b55..16bec6dad9 100644 --- a/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift +++ b/submodules/TelegramUI/Sources/InlineReactionSearchPanel.swift @@ -4,7 +4,6 @@ import SwiftSignalKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext @@ -34,13 +33,13 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, ASScrollVie private let context: AccountContext private var theme: PresentationTheme private var strings: PresentationStrings - private let peerId: PeerId? + private let peerId: EnginePeer.Id? private let scrollNode: ASScrollNode private var items: [TelegramMediaFile] = [] private var displayItems: [DisplayItem] = [] private var topInset: CGFloat? - private var itemNodes: [MediaId: HorizontalStickerGridItemNode] = [:] + private var itemNodes: [EngineMedia.Id: HorizontalStickerGridItemNode] = [:] private var validLayout: (size: CGSize, bottomInset: CGFloat)? fileprivate weak var currentInterfaceState: ChatPresentationInterfaceState? @@ -66,7 +65,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, ASScrollVie |> distinctUntilChanged } - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peerId: PeerId?) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peerId: EnginePeer.Id?) { self.context = context self.theme = theme self.strings = strings @@ -83,6 +82,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, ASScrollVie self.scrollNode.view.showsVerticalScrollIndicator = false self.scrollNode.view.showsHorizontalScrollIndicator = false self.scrollNode.view.delegate = self.wrappedScrollViewDelegate + self.scrollNode.view.scrollsToTop = false self.addSubnode(self.scrollNode) } @@ -425,7 +425,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, ASScrollVie minVisibleY -= 200.0 maxVisibleY += 200.0 - var validIds = Set() + var validIds = Set() for i in 0 ..< self.displayItems.count { let item = self.displayItems[i] @@ -469,7 +469,7 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, ASScrollVie } } - var removeIds: [MediaId] = [] + var removeIds: [EngineMedia.Id] = [] for (id, itemNode) in self.itemNodes { if !validIds.contains(id) { removeIds.append(id) @@ -495,7 +495,7 @@ final class InlineReactionSearchPanel: ChatInputContextPanelNode { private var choosingStickerDisposable: Disposable? - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId?, chatPresentationContext: ChatPresentationContext) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: EnginePeer.Id?, chatPresentationContext: ChatPresentationContext) { self.containerNode = ASDisplayNode() self.backgroundView = GlassBackgroundView() diff --git a/submodules/TelegramUI/Sources/LargeEmojiActionSheetItem.swift b/submodules/TelegramUI/Sources/LargeEmojiActionSheetItem.swift index d16fd2d030..de88ceab55 100644 --- a/submodules/TelegramUI/Sources/LargeEmojiActionSheetItem.swift +++ b/submodules/TelegramUI/Sources/LargeEmojiActionSheetItem.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/TelegramUI/Sources/MakeTempAccountContext.swift b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift index 670abb783b..7f594c5863 100644 --- a/submodules/TelegramUI/Sources/MakeTempAccountContext.swift +++ b/submodules/TelegramUI/Sources/MakeTempAccountContext.swift @@ -1,7 +1,6 @@ import Foundation import SwiftSignalKit import TelegramCore -import Postbox import AccountContext import BuildConfig import TelegramPresentationData @@ -14,7 +13,7 @@ public func makeTempContext( appGroupPath: String, accountManager: AccountManager, appLockContext: AppLockContext, - encryptionParameters: ValueBoxEncryptionParameters, + encryptionParameters: EngineValueBoxEncryptionParameters, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, diff --git a/submodules/TelegramUI/Sources/MediaManager.swift b/submodules/TelegramUI/Sources/MediaManager.swift index f838410fdf..6d9006381d 100644 --- a/submodules/TelegramUI/Sources/MediaManager.swift +++ b/submodules/TelegramUI/Sources/MediaManager.swift @@ -3,7 +3,6 @@ import SwiftSignalKit import AVFoundation import MobileCoreServices import Display -import Postbox import TelegramCore import MediaPlayer import TelegramAudio @@ -78,7 +77,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { didSet { if self.voiceMediaPlayer !== oldValue { if let voiceMediaPlayer = self.voiceMediaPlayer { - let account = voiceMediaPlayer.account + let account = voiceMediaPlayer.engine.account self.voiceMediaPlayerStateDisposable.set((voiceMediaPlayer.playbackState |> deliverOnMainQueue).startStrict(next: { [weak self, weak voiceMediaPlayer] state in guard let strongSelf = self else { @@ -117,7 +116,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { if self.musicMediaPlayer !== oldValue { if let musicMediaPlayer = self.musicMediaPlayer { let type = musicMediaPlayer.type - let account = musicMediaPlayer.account + let account = musicMediaPlayer.engine.account self.musicMediaPlayerStateValue.set(musicMediaPlayer.playbackState |> map { state -> (Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)? in guard let state = state else { @@ -331,7 +330,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { |> distinctUntilChanged(isEqual: { $0?.0 === $1?.0 && $0?.1 == $1?.1 }) |> mapToSignal { value -> Signal in if let (account, value) = value { - return playerAlbumArt(postbox: account.postbox, engine: TelegramEngine(account: account), fileReference: value.fullSizeResource.file, albumArt: value, thumbnail: false) + return playerAlbumArt(engine: TelegramEngine(account: account), fileReference: value.fullSizeResource.file, albumArt: value, thumbnail: false) |> map { generator -> UIImage? in let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: 640, height: 640), boundingSize: CGSize(width: 640, height: 640), intrinsicInsets: .zero) return generator(arguments)?.generateImage() @@ -524,7 +523,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { controlPlaybackWithProximity = playlist.context.sharedContext.currentMediaInputSettings.with({ $0.enableRaiseToSpeak }) } - let voiceMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: context.account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) + let voiceMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, engine: context.engine, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: .reversed, initialLooping: .none, initialPlaybackRate: settings.voicePlaybackRate, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: controlPlaybackWithProximity, type: type, continueInstantVideoLoopAfterFinish: continueInstantVideoLoopAfterFinish) strongSelf.voiceMediaPlayer = voiceMediaPlayer voiceMediaPlayer.playedToEnd = { [weak voiceMediaPlayer] in if let strongSelf = self, let voiceMediaPlayer = voiceMediaPlayer, voiceMediaPlayer === strongSelf.voiceMediaPlayer { @@ -558,7 +557,7 @@ public final class MediaManagerImpl: NSObject, MediaManager { strongSelf.musicMediaPlayer?.control(control) } else { strongSelf.musicMediaPlayer?.stop() - let musicMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, account: context.account, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: storedState?.playbackRate ?? .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false, type: type, continueInstantVideoLoopAfterFinish: true) + let musicMediaPlayer = SharedMediaPlayer(context: context, mediaManager: strongSelf, inForeground: strongSelf.inForeground, engine: context.engine, audioSession: strongSelf.audioSession, overlayMediaManager: strongSelf.overlayMediaManager, playlist: playlist, initialOrder: settings.order, initialLooping: settings.looping, initialPlaybackRate: storedState?.playbackRate ?? .x1, playerIndex: nextPlayerIndex, controlPlaybackWithProximity: false, type: type, continueInstantVideoLoopAfterFinish: true) strongSelf.musicMediaPlayer = musicMediaPlayer strongSelf.musicListenTracker = MusicListenTracker(engine: context.engine) musicMediaPlayer.cancelled = { [weak musicMediaPlayer] in diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index c0d7a4c6ca..41ee283032 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -36,7 +36,7 @@ private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { } func item(context: AccountContext, presentationData: PresentationData, inverted: Bool, setPeerIdRevealed: @escaping (EnginePeer.Id?) -> Void, peerSelected: @escaping (EnginePeer) -> Void, removeRequested: @escaping (EnginePeer.Id) -> Void) -> ListViewItem { - return MentionChatInputPanelItem(context: context, presentationData: ItemListPresentationData(presentationData), inverted: inverted, peer: self.peer._asPeer(), revealed: self.revealed, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested) + return MentionChatInputPanelItem(context: context, presentationData: ItemListPresentationData(presentationData), inverted: inverted, peer: self.peer, revealed: self.revealed, setPeerIdRevealed: setPeerIdRevealed, peerSelected: peerSelected, removeRequested: removeRequested) } } diff --git a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift index 928dba8aa7..b30613f214 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import AvatarNode @@ -16,14 +15,14 @@ final class MentionChatInputPanelItem: ListViewItem { fileprivate let presentationData: ItemListPresentationData fileprivate let revealed: Bool fileprivate let inverted: Bool - fileprivate let peer: Peer + fileprivate let peer: EnginePeer private let peerSelected: (EnginePeer) -> Void fileprivate let setPeerIdRevealed: (EnginePeer.Id?) -> Void fileprivate let removeRequested: (EnginePeer.Id) -> Void - + let selectable: Bool = true - - public init(context: AccountContext, presentationData: ItemListPresentationData, inverted: Bool, peer: Peer, revealed: Bool, setPeerIdRevealed: @escaping (PeerId?) -> Void, peerSelected: @escaping (EnginePeer) -> Void, removeRequested: @escaping (PeerId) -> Void) { + + public init(context: AccountContext, presentationData: ItemListPresentationData, inverted: Bool, peer: EnginePeer, revealed: Bool, setPeerIdRevealed: @escaping (EnginePeer.Id?) -> Void, peerSelected: @escaping (EnginePeer) -> Void, removeRequested: @escaping (EnginePeer.Id) -> Void) { self.context = context self.presentationData = presentationData self.inverted = inverted @@ -85,7 +84,7 @@ final class MentionChatInputPanelItem: ListViewItem { if self.revealed { self.setPeerIdRevealed(nil) } else { - self.peerSelected(EnginePeer(self.peer)) + self.peerSelected(self.peer) } } } @@ -174,7 +173,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { } - let title = EnginePeer(item.peer).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) + let title = item.peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) var username: String? let string = NSMutableAttributedString() string.append(NSAttributedString(string: title, font: primaryFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)) @@ -204,7 +203,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor) + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor) let _ = textApply() @@ -221,8 +220,8 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { strongSelf.activateAreaNode.accessibilityValue = username strongSelf.activateAreaNode.frame = CGRect(origin: .zero, size: nodeLayout.size) - if let peer = item.peer as? TelegramUser, let _ = peer.botInfo { - strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]) + if case let .user(peer) = item.peer, let _ = peer.botInfo { + strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]) strongSelf.setRevealOptionsOpened(item.revealed, animated: animation.isAnimated) } else { strongSelf.setRevealOptions([]) diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 74ca2b1aa1..4a2555811b 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -3,7 +3,6 @@ import UIKit import Display import SwiftSignalKit import TelegramCore -import Postbox import AccountContext import GalleryUI import InstantPageUI @@ -51,13 +50,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam if channel.flags.contains(.displayForumAsTabs) { viewForumAsMessages = .single(true) } else { - viewForumAsMessages = params.context.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peer.id)]) + viewForumAsMessages = params.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CachedData(id: peer.id)) |> take(1) - |> map { combinedView in - guard let cachedDataView = combinedView.views[.cachedPeerData(peerId: peer.id)] as? CachedPeerDataView else { - return false - } - if let cachedData = cachedDataView.cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages { + |> map { cachedPeerData in + if let cachedData = cachedPeerData as? CachedChannelData, case let .known(viewForumAsMessages) = cachedData.viewForumAsMessages, viewForumAsMessages { return true } else { return false @@ -140,7 +136,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam } if !params.forceOpenChat, !viewForumAsMessages, params.subject == nil, case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId { - if let controller = params.context.sharedContext.makePeerInfoController(context: params.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = params.context.sharedContext.makePeerInfoController(context: params.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { params.navigationController.pushViewController(controller, animated: params.animated, completion: { }) return @@ -182,6 +178,7 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam controller.navigateToMessage(messageLocation: .id(messageId, NavigateToMessageParams(timestamp: timecode, quote: (highlight?.quote).flatMap { quote in NavigateToMessageParams.Quote(string: quote.string, offset: quote.offset) }, subject: highlight?.subject, setupReply: setupReply)), animated: isFirst || params.forceAnimatedScroll, completion: { [weak navigationController, weak controller] in if let navigationController = navigationController, let controller = controller { let _ = navigationController.popToViewController(controller, animated: animated) + params.completion(controller) } }, customPresentProgress: { [weak navigationController] c, a in (navigationController?.viewControllers.last as? ViewController)?.present(c, in: .window(.root), with: a) @@ -252,8 +249,14 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, mode: botAppStart.mode) } } + + if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) { + if let updateTextInputState = params.updateTextInputState { + controller.updateTextInputState(updateTextInputState) + } + } } else { - controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack, customChatNavigationStack: params.customChatNavigationStack) + controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack, customChatNavigationStack: params.customChatNavigationStack, initialTextInputState: params.updateTextInputState) if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation { Queue.mainQueue().after(0.1) { @@ -262,14 +265,6 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam } } - if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) { - if let updateTextInputState = params.updateTextInputState { - Queue.mainQueue().after(0.1) { - controller.updateTextInputState(updateTextInputState) - } - } - } - controller.purposefulAction = params.purposefulAction if let search = params.activateMessageSearch { controller.activateSearch(domain: search.0, query: search.1) @@ -416,7 +411,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll } public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, scrollToEndIfExists: Bool, keepStack: NavigateToChatKeepStack, animated: Bool) -> Signal { - return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) + return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: messageId, preload: false) |> deliverOnMainQueue |> beforeNext { [weak context, weak navigationController] result in guard let context = context, let navigationController = navigationController else { @@ -448,7 +443,7 @@ public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePee } } -public func chatControllerForForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64) -> Signal { +public func chatControllerForForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, initialTextInputState: ChatTextInputState? = nil) -> Signal { return context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) ) @@ -476,10 +471,11 @@ public func chatControllerForForumThreadImpl(context: AccountContext, peerId: En initialAnchor: .automatic, isNotAvailable: false )), - chatLocationContextHolder: Atomic(value: nil) + chatLocationContextHolder: Atomic(value: nil), + initialTextInputState: initialTextInputState )) } else { - return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil, preload: false) + return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil, preload: false) |> deliverOnMainQueue |> `catch` { _ -> Signal in return .complete() @@ -488,7 +484,8 @@ public func chatControllerForForumThreadImpl(context: AccountContext, peerId: En return ChatControllerImpl( context: context, chatLocation: .replyThread(message: result.message), - chatLocationContextHolder: result.contextHolder + chatLocationContextHolder: result.contextHolder, + initialTextInputState: initialTextInputState ) } } diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift index 5fa8c6edf8..022b47055b 100644 --- a/submodules/TelegramUI/Sources/NotificationContentContext.swift +++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift @@ -4,7 +4,6 @@ import UserNotificationsUI import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext @@ -87,7 +86,7 @@ public final class NotificationViewControllerImpl { let rootPath = rootPathForBasePath(self.initializationData.appGroupPath) performAppGroupUpgrades(appGroupPath: self.initializationData.appGroupPath, rootPath: rootPath) - TempBox.initializeShared(basePath: rootPath, processType: "notification-content", launchSpecificId: Int64.random(in: Int64.min ... Int64.max)) + EngineTempBox.initializeShared(basePath: rootPath, processType: "notification-content", launchSpecificId: Int64.random(in: Int64.min ... Int64.max)) let logsPath = rootPath + "/logs/notificationcontent-logs" let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) @@ -140,7 +139,7 @@ public final class NotificationViewControllerImpl { return nil }) - sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), externalRecaptchaRequestVerification: { _, _ in return .never() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _, _ in }, appDelegate: nil) + sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: EngineValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: EngineValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: EngineValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, notificationController: nil, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), externalRequestVerificationStream: .never(), externalRecaptchaRequestVerification: { _, _ in return .never() }, autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _, _ in }, appDelegate: nil) presentationDataPromise.set(sharedAccountContext!.presentationData) } @@ -179,7 +178,7 @@ public final class NotificationViewControllerImpl { return } - let messageId = MessageId(peerId: PeerId(peerIdValue), namespace: messageIdNamespace, id: messageIdId) + let messageId = EngineMessage.Id(peerId: EnginePeer.Id(peerIdValue), namespace: messageIdNamespace, id: messageIdId) if let image = media as? TelegramMediaImage, let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 120, height: 120)), let largestRepresentation = largestImageRepresentation(image.representations) { let dimensions = largestRepresentation.dimensions diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index d71ffc1abe..859791713b 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -25,7 +25,7 @@ func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: S shareViaException: false, completion: { peer, stableId, contactData in if let peer = peer { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushController(infoController) } } else if let stableId, let contactData { @@ -39,7 +39,7 @@ func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: S DeviceAccess.authorizeAccess(to: .contacts) default: let presentationData = context.sharedContext.currentPresentationData.with { $0 } - present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { context.sharedContext.applicationBindings.openSettings() })]), nil) } diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index a75cc7b7c3..db5e01219c 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -137,13 +137,13 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.openUrl(url) return true case let .pass(file): - let _ = (params.context.account.postbox.mediaBox.resourceData(file.resource, option: .complete(waitUntilFetchStatus: true)) + let _ = (params.context.engine.resources.data(resource: EngineMediaResource(file.resource), waitUntilFetchStatus: true) |> take(1) |> deliverOnMainQueue).startStandalone(next: { data in guard let navigationController = params.navigationController else { return } - if data.complete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if data.isComplete, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let pass = try? PKPass(data: content), let controller = PKAddPassesViewController(pass: pass) { if let window = navigationController.view.window { controller.popoverPresentationController?.sourceView = window @@ -256,11 +256,14 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } } + let fileReference: FileMediaReference = .message(message: MessageReference(params.message), media: file) let subject: BrowserScreen.Subject - if file.mimeType == "application/pdf" { - subject = .pdfDocument(file: .message(message: MessageReference(params.message), media: file), canShare: canShare) + if file.mimeType.contains("markdown") { + subject = .markdownDocument(file: fileReference, canShare: canShare) + } else if file.mimeType.contains("pdf") { + subject = .pdfDocument(file: fileReference, canShare: canShare) } else { - subject = .document(file: .message(message: MessageReference(params.message), media: file), canShare: canShare) + subject = .document(file: fileReference, canShare: canShare) } let controller = BrowserScreen(context: params.context, subject: subject) controller.openDocument = { [weak controller] file, canShare in @@ -327,8 +330,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } |> take(1) |> timeout(1.0, queue: .mainQueue(), alternate: .single(false)) |> deliverOnMainQueue).startStandalone(next: { value in if value { let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } - //TODO:localize - let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "Device is muted.", timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + let toastController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Chat_ToastVoiceMessageDeviceMuted, timeout: 4.0, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }) params.present(toastController, nil, .current) @@ -437,7 +439,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } else { contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contact.firstName, lastName: contact.lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: contact.phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") } - let controller = deviceContactInfoController(context: ShareControllerAppAccountContext(context: params.context), environment: ShareControllerAppEnvironment(sharedContext: params.context.sharedContext), updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer?._asPeer(), nil, contactData), completed: nil, cancelled: nil) + let controller = deviceContactInfoController(context: ShareControllerAppAccountContext(context: params.context), environment: ShareControllerAppEnvironment(sharedContext: params.context.sharedContext), updatedPresentationData: params.updatedPresentationData, subject: .vcard(peer, nil, contactData), completed: nil, cancelled: nil) params.navigationController?.pushViewController(controller) }) return true @@ -460,7 +462,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { }), .window(.root)) case let .theme(media): params.dismissInput() - let path = params.context.account.postbox.mediaBox.completedResourcePath(media.resource) + let path = params.context.engine.resources.completedResourcePath(id: EngineMediaResource.Id(media.resource.id)) var previewTheme: PresentationTheme? if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) { previewTheme = makePresentationTheme(data: data) @@ -476,15 +478,15 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { return false } -func makeInstantPageControllerImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? { - guard let (webpage, anchor) = instantPageAndAnchor(message: message) else { +public func makeInstantPageControllerImpl(context: AccountContext, message: Message, sourcePeerType: MediaAutoDownloadPeerType?) -> ViewController? { + guard let (webpage, anchor) = instantPageAndAnchor(message: EngineMessage(message)) else { return nil } let sourceLocation = InstantPageSourceLocation(userLocation: .peer(message.id.peerId), peerType: sourcePeerType ?? .channel) return makeInstantPageControllerImpl(context: context, webPage: webpage, anchor: anchor, sourceLocation: sourceLocation) } -func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController { +public func makeInstantPageControllerImpl(context: AccountContext, webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation) -> ViewController { return BrowserScreen(context: context, subject: .instantPage(webPage: webPage, anchor: anchor, sourceLocation: sourceLocation, preloadedResources: nil)) } @@ -540,7 +542,7 @@ func openChatTheme(context: AccountContext, message: Message, pushController: @e } if case let .theme(slug) = resolvedUrl { if let file = file { - if let path = context.sharedContext.accountManager.mediaBox.completedResourcePath(file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + if let path = context.sharedContext.accountManager.resources.completedResourcePath(resource: EngineMediaResource(file.resource)), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { if let theme = makePresentationTheme(data: data) { let controller = ThemePreviewController(context: context, previewTheme: theme, source: .slug(slug, file)) pushController(controller) diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 2231f78cbd..7ed2e227bf 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -20,7 +20,6 @@ import JoinLinkPreviewUI import LanguageLinkPreviewUI import SettingsUI import UrlHandling -import ChatInterfaceState import TelegramCallsUI import UndoUI import ImportStickerPackUI @@ -120,6 +119,15 @@ func openResolvedUrlImpl( } ) } + let isStartGroup: Bool + switch peerType { + case .group?: + isStartGroup = true + default: + isStartGroup = false + } + let shouldForceStartGroupStartFlow = isStartGroup && !payload.isEmpty && adminRights == nil + let shouldCheckExistingGroupAdmin = isStartGroup && !payload.isEmpty && adminRights != nil var filter: ChatListNodePeersFilter = [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages] var title: String = presentationData.strings.Bot_AddToChat_Title @@ -198,53 +206,108 @@ func openResolvedUrlImpl( } }), TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {}) - ] + ], + actionLayout: .vertical ) present(alertController, nil) } + let openAdminControllerImpl: (TelegramChatAdminRightsFlags?) -> Void = { initialAdminRights in + let adminController = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in + if shouldCheckExistingGroupAdmin { + Queue.mainQueue().after(0.1) { + addMemberImpl() + } + } else { + controller?.dismiss() + } + }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) + navigationController?.pushViewController(adminController) + } + let openAdminControllerWithResolvedRightsImpl: (Bool) -> Void = { isGroup in + if adminRights == nil { + let _ = (defaultAdminRights.get() + |> take(1) + |> deliverOnMainQueue).start(next: { defaultAdminRights in + let initialAdminRights = isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights + openAdminControllerImpl(initialAdminRights) + }) + } else { + openAdminControllerImpl(adminRights?.chatAdminRights) + } + } if case let .channel(peer) = peer { var isGroup = false if case .group = peer.info { isGroup = true } - if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true { - let _ = (defaultAdminRights.get() - |> take(1) - |> deliverOnMainQueue).start(next: { defaultAdminRights in - let initialAdminRights = adminRights?.chatAdminRights ?? (isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights) - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in - controller?.dismiss() - }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) - navigationController?.pushViewController(controller) - }) + if shouldForceStartGroupStartFlow { + addMemberImpl() + } else if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true { + if shouldCheckExistingGroupAdmin && isGroup { + let _ = (context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: botPeerId) + |> deliverOnMainQueue).start(next: { participant in + let isBotAlreadyAdmin: Bool + if let participant = participant { + switch participant { + case .creator: + isBotAlreadyAdmin = true + case let .member(_, _, adminInfo, _, _, _): + isBotAlreadyAdmin = adminInfo != nil + } + } else { + isBotAlreadyAdmin = false + } + if isBotAlreadyAdmin { + addMemberImpl() + } else { + openAdminControllerWithResolvedRightsImpl(isGroup) + } + }) + } else { + openAdminControllerWithResolvedRightsImpl(isGroup) + } } else { addMemberImpl() } } else if case let .legacyGroup(peer) = peer { - if case .member = peer.role { + if shouldForceStartGroupStartFlow { addMemberImpl() - } else { - let _ = (defaultAdminRights.get() - |> take(1) - |> deliverOnMainQueue).start(next: { defaultAdminRights in - let initialAdminRights = adminRights?.chatAdminRights ?? defaultAdminRights?.group?.rights - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in - controller?.dismiss() - }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) - navigationController?.pushViewController(controller) + } else if case .member = peer.role { + addMemberImpl() + } else if shouldCheckExistingGroupAdmin { + let _ = (context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId) + |> mapToSignal { _ in + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peerId)) + } + |> deliverOnMainQueue).start(next: { participants in + let isBotAlreadyAdmin: Bool + if let participant = participants.knownValue?.first(where: { $0.peerId == botPeerId }) { + switch participant { + case .creator, .admin: + isBotAlreadyAdmin = true + case .member: + isBotAlreadyAdmin = false + } + } else { + isBotAlreadyAdmin = false + } + if isBotAlreadyAdmin { + addMemberImpl() + } else { + openAdminControllerWithResolvedRightsImpl(true) + } }) + } else { + openAdminControllerWithResolvedRightsImpl(true) } } } dismissInput() navigationController?.pushViewController(controller) case let .gameStart(botPeerId, game): - let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.Bot_AddToChat_Title, selectForumThreads: true)) - controller.peerSelected = { [weak controller] peer, _ in - let _ = peer.id - let _ = botPeerId - let _ = game + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, title: presentationData.strings.ShareMenu_SelectChats, selectForumThreads: true)) + controller.peerSelected = { [weak controller] peer, threadId in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text: String if case .user = peer { @@ -254,6 +317,20 @@ func openResolvedUrlImpl( } let alertController = textAlertController(context: context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.RequestPeer_SelectionConfirmationSend, action: { + controller?.inProgress = true + let _ = (context.engine.messages.sendBotGame(botPeerId: botPeerId, game: game, to: peer.id, threadId: threadId) + |> deliverOnMainQueue).startStandalone(error: { _ in + controller?.inProgress = false + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + }) + if let navigationController { + if let threadId { + let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: true, keepStack: .always, animated: true).startStandalone() + } else { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, useExisting: true, scrollToEndIfExists: true)) + } + } controller?.dismiss() }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { })]) @@ -553,16 +630,16 @@ func openResolvedUrlImpl( } let chatController: Signal if let threadId { - chatController = chatControllerForForumThreadImpl(context: context, peerId: peerId, threadId: threadId) + chatController = chatControllerForForumThreadImpl(context: context, peerId: peerId, threadId: threadId, initialTextInputState: textInputState) } else { - chatController = .single(ChatControllerImpl(context: context, chatLocation: .peer(id: peerId))) + chatController = .single(ChatControllerImpl(context: context, chatLocation: .peer(id: peerId), initialTextInputState: textInputState)) } - + let _ = (chatController |> deliverOnMainQueue).start(next: { [weak navigationController] chatController in guard let navigationController else { return - } + } var controllers = navigationController.viewControllers.filter { controller in if controller is PeerSelectionController { return false @@ -573,17 +650,8 @@ func openResolvedUrlImpl( navigationController.setViewControllers(controllers, animated: true) }) } - - if let textInputState = textInputState { - let _ = (ChatInterfaceState.update(engine: context.engine, peerId: peerId, threadId: threadId, { currentState in - return currentState.withUpdatedComposeInputState(textInputState) - }) - |> deliverOnMainQueue).startStandalone(completed: { - updateControllers() - }) - } else { - updateControllers() - } + + updateControllers() } if let to = to { @@ -629,7 +697,7 @@ func openResolvedUrlImpl( present(shareController, nil) context.sharedContext.applicationBindings.dismissNativeController() } else { - let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled], selectForumThreads: true)) + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled], hasFilters: true, selectForumThreads: true)) controller.peerSelected = { peer, threadId in continueWithPeer(peer.id, threadId) } @@ -678,18 +746,18 @@ func openResolvedUrlImpl( subscriber.putNext((nil, settings, themeInfo)) subscriber.putCompletion() } else if let resource = themeInfo.file?.resource { - disposables.add(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .standalone(resource: resource)).start()) + disposables.add(context.engine.resources.fetch(reference: .standalone(resource: resource), userLocation: .other, userContentType: .other).start()) - let maybeFetched = context.sharedContext.accountManager.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) + let maybeFetched = context.sharedContext.accountManager.resources.data(resource: EngineMediaResource(resource)) |> mapToSignal { maybeData -> Signal in - if maybeData.complete { + if maybeData.isComplete { let loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single(loadedData) } else { - return context.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: false) + return context.engine.resources.data(resource: EngineMediaResource(resource)) |> map { next -> Data? in - if next.size > 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + if next.availableSize > 0, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []) { + context.sharedContext.accountManager.resources.storeResourceData(id: EngineMediaResource.Id(resource.id), data: data) return data } else { return nil @@ -788,7 +856,7 @@ func openResolvedUrlImpl( navigationController?.pushViewController(controller) default: let presentationData = context.sharedContext.currentPresentationData.with { $0 } - present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { context.sharedContext.applicationBindings.openSettings() })]), nil) } @@ -1740,7 +1808,7 @@ func openResolvedUrlImpl( guard let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: updatedPresentationData, - peer: peer._asPeer(), + peer: peer, mode: .storyAlbum(id: id), avatarInitiallyExpanded: false, fromChat: false, @@ -1761,7 +1829,7 @@ func openResolvedUrlImpl( guard let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: updatedPresentationData, - peer: peer._asPeer(), + peer: peer, mode: .giftCollection(id: id), avatarInitiallyExpanded: false, fromChat: false, @@ -1876,6 +1944,8 @@ func openResolvedUrlImpl( browserIdentifier = "duckDuckGo" } else if browser.hasPrefix("Alook") { browserIdentifier = "alook" + } else if browser.hasPrefix("Vivaldi") { + browserIdentifier = "vivaldi" } } @@ -1971,5 +2041,27 @@ func openResolvedUrlImpl( } navigationController?.pushViewController(controller) } + case let .textStyle(style, initialPreview): + Task { @MainActor in + var authorPeer: EnginePeer? + if let authorId = style.authorId { + authorPeer = await context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: authorId) + ).get() + } + let isAlreadyAdded = await context.engine.messages.composeAIMessageStyles().get().contains(where: { $0.id == .style(.custom(style.id)) }) + + let controller = await context.sharedContext.makeTextProcessingScreen(context: context, theme: nil, mode: .preview(style: style, authorPeer: authorPeer, initialPreview: initialPreview, isAlreadyAdded: isAlreadyAdded, added: { + Task { @MainActor in + guard let emojiFileId = style.emojiFileId, let file = await context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]).get().first?.value else { + return + } + present(UndoOverlayController(presentationData: presentationData, content: .customEmoji(context: context, file: file, loop: false, title: presentationData.strings.TextProcessing_ToastStyleAdded_Title, text: presentationData.strings.TextProcessing_ToastStyleAdded_Text(style.title).string, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in + return true + }), nil) + } + }), inputText: TextWithEntities(text: "", entities: []), copyResult: nil, translateChat: nil) + navigationController?.pushViewController(controller) + } } } diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index 0ab56ceb80..6c964bad99 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -1,8 +1,6 @@ import Foundation import Display -import SafariServices import TelegramCore -import Postbox import SwiftSignalKit import MtProtoKit import TelegramPresentationData @@ -17,7 +15,7 @@ import OverlayStatusController import PresentationDataUtils public struct ParsedSecureIdUrl { - public let peerId: PeerId + public let peerId: EnginePeer.Id public let scope: String public let publicKey: String public let callbackUrl: String @@ -100,7 +98,7 @@ public func parseSecureIdUrl(_ url: URL) -> ParsedSecureIdUrl? { return nil } - return ParsedSecureIdUrl(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce) + return ParsedSecureIdUrl(peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(botId)), scope: scope, publicKey: publicKey, callbackUrl: callbackUrl, opaquePayload: opaquePayload, opaqueNonce: opaqueNonce) } } @@ -172,7 +170,7 @@ private func makeResolvedUrlHandler( openPeer: { peer, navigation in switch navigation { case .info: - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { context.sharedContext.applicationBindings.dismissNativeController() navigationController?.pushViewController(infoController) } @@ -255,27 +253,22 @@ private func handleInternetUrl( if let host = parsedUrl.host, telegramMeHosts.contains(host) { handleInternalUrl(parsedUrl.absoluteString) } else { - let settings = combineLatest(context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]), context.sharedContext.accountManager.accessChallengeData()) + let settings = combineLatest( + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.webBrowserSettings)) + ) |> take(1) - |> map { sharedData, accessChallengeData -> WebBrowserSettings in - let passcodeSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationPasscodeSettings]?.get(PresentationPasscodeSettings.self) ?? PresentationPasscodeSettings.defaultSettings - - var settings: WebBrowserSettings - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) { - settings = current - } else { - settings = .defaultSettings - } - if accessChallengeData.data.isLockable { - if passcodeSettings.autolockTimeout != nil && settings.defaultWebBrowser == "inApp" { - settings = WebBrowserSettings(defaultWebBrowser: "safari", exceptions: []) - } - } - return settings + |> map { sharedData, accountSettingsEntry -> (WebBrowserSettings, AccountWebBrowserSettings) in + let localSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings + let accountSettings = accountSettingsEntry?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings + return (localSettings, accountSettings) } let _ = (settings |> deliverOnMainQueue).startStandalone(next: { settings in + let localSettings = settings.0 + let accountSettings = settings.1 + var isTonSite = false if let host = parsedUrl.host, host.lowercased().hasSuffix(".ton") { isTonSite = true @@ -283,9 +276,35 @@ private func handleInternetUrl( isTonSite = true } - if let defaultWebBrowser = settings.defaultWebBrowser, defaultWebBrowser != "inApp" && !isTonSite { + var isExceptedDomain = false + let host = ".\((parsedUrl.host ?? "").lowercased())" + let exceptions = accountSettings.openExternalBrowser ? accountSettings.inAppExceptions : accountSettings.externalExceptions + for exception in exceptions { + if host.hasSuffix(".\(exception.domain.lowercased())") { + isExceptedDomain = true + break + } + } + + let shouldOpenInApp: Bool + if isTonSite { + shouldOpenInApp = true + } else if accountSettings.openExternalBrowser { + shouldOpenInApp = isExceptedDomain + } else { + shouldOpenInApp = !isExceptedDomain + } + + if shouldOpenInApp { + let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString)) + navigationController?.pushViewController(controller) + } else { let openInOptions = availableOpenInOptions(context: context, item: .url(url: originalUrl)) - if let option = openInOptions.first(where: { $0.identifier == settings.defaultWebBrowser }) { + var defaultWebBrowser = localSettings.defaultWebBrowser + if defaultWebBrowser == nil || defaultWebBrowser == "inApp" || defaultWebBrowser == "inAppSafari" { + defaultWebBrowser = "safari" + } + if let option = openInOptions.first(where: { $0.identifier == defaultWebBrowser }) { if case let .openUrl(openInUrl) = option.action() { context.sharedContext.applicationBindings.openUrl(openInUrl) } else { @@ -294,29 +313,6 @@ private func handleInternetUrl( } else { context.sharedContext.applicationBindings.openUrl(originalUrl) } - } else { - var isExceptedDomain = false - let host = ".\((parsedUrl.host ?? "").lowercased())" - for exception in settings.exceptions { - if host.hasSuffix(".\(exception.domain)") { - isExceptedDomain = true - break - } - } - - if (settings.defaultWebBrowser == nil && !isExceptedDomain) || isTonSite { - let controller = BrowserScreen(context: context, subject: .webPage(url: parsedUrl.absoluteString)) - navigationController?.pushViewController(controller) - } else { - if let window = navigationController?.view.window, !isExceptedDomain { - let controller = SFSafariViewController(url: parsedUrl) - controller.preferredBarTintColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor - controller.preferredControlTintColor = presentationData.theme.rootController.navigationBar.accentTextColor - window.rootViewController?.present(controller, animated: true) - } else { - context.sharedContext.applicationBindings.openUrl(parsedUrl.absoluteString) - } - } } }) } @@ -436,7 +432,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur switch host { case "localpeer": if let peerIdValue = params["id"].flatMap(Int64.init), let accountId = params["accountId"].flatMap(Int64.init) { - let peerId = PeerId(peerIdValue) + let peerId = EnginePeer.Id(peerIdValue) context.sharedContext.applicationBindings.dismissNativeController() context.sharedContext.navigateToChat(accountId: AccountRecordId(rawValue: accountId), peerId: peerId, messageId: nil) } @@ -527,12 +523,12 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur } case "user": if let idValue = params["id"].flatMap(Int64.init), idValue > 0 { - let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(idValue)))) + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(idValue)))) |> deliverOnMainQueue).startStandalone(next: { peer in if let peer = peer, let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, @@ -625,15 +621,6 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let parameter = params["slug"] { convertedUrl = makeTelegramUrl("/m/\(parameter)") } - case "hostoverride": - if let override = params["host"] { - let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in - var settings = settings - settings.backupHostOverride = override - return settings - }).startStandalone() - return - } case "premium_offer": let reference = params["ref"] handleResolvedUrl(.premiumOffer(reference: reference)) @@ -670,7 +657,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur case "send_gift": if let recipient = params["to"] { if let id = Int64(recipient) { - handleResolvedUrl(.sendGift(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)))) + handleResolvedUrl(.sendGift(peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(id)))) } else { let _ = (context.engine.peers.resolvePeerByName(name: recipient, referrer: nil) |> deliverOnMainQueue).start(next: { result in @@ -693,6 +680,10 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur handleResolvedUrl(.createBot(parentBot: peer.id, username: params["username"], title: params["name"])) }) } + case "textStyle": + if let slug = params["slug"] { + convertedUrl = makeTelegramUrl("/addstyle/\(slug)") + } default: break } @@ -1014,7 +1005,16 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur if let host = parsedUrl.host, telegramMeHosts.contains(host) { continueHandling() } else { - if isTelegraPhLink(parsedUrl.absoluteString) { + if isTelegramWebShortLink(parsedUrl.absoluteString) { + handleInternetUrl( + parsedUrl: parsedUrl, + originalUrl: url, + context: context, + presentationData: presentationData, + navigationController: navigationController, + handleInternalUrl: handleInternalUrl + ) + } else if isTelegraPhLink(parsedUrl.absoluteString) { continueHandling() } else { context.sharedContext.applicationBindings.openUniversalUrl(url, TelegramApplicationOpenUrlCompletion(completion: { success in diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift index 6240209a75..c7fef41747 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import Display import SwiftSignalKit import TelegramUIPreferences @@ -15,7 +14,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer private let context: AccountContext let chatLocation: ChatLocation let type: MediaManagerPlayerType - let initialMessageId: MessageId + let initialMessageId: EngineMessage.Id let initialOrder: MusicPlaybackSettingsOrder let playlistLocation: SharedMediaPlaylistLocation? @@ -33,7 +32,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, - initialMessageId: MessageId, + initialMessageId: EngineMessage.Id, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation? = nil, parentNavigationController: NavigationController? @@ -180,7 +179,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer let fileId = Int64.random(in: Int64.min ... Int64.max) let mimeType = guessMimeTypeByFileExtension((item.fileName as NSString).pathExtension) var previewRepresentations: [TelegramMediaImageRepresentation] = [] - if mimeType.hasPrefix("image/") || mimeType == "application/pdf" { + if mimeType.hasPrefix("image/") || mimeType == "application/pdf" || item.audioMetadata?.hasAudioArtwork == true { previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 320, height: 320), resource: ICloudFileResource(urlData: item.urlData, thumbnail: true), progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) } var attributes: [TelegramMediaFileAttribute] = [] @@ -189,7 +188,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer attributes.append(.Audio(isVoice: false, duration: audioMetadata.duration, title: audioMetadata.title, performer: audioMetadata.performer, waveform: nil)) } - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) + let file = TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: fileId), partialReference: nil, resource: ICloudFileResource(urlData: item.urlData, thumbnail: false), previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: mimeType, size: Int64(item.fileSize), attributes: attributes, alternativeRepresentations: []) let _ = (standaloneUploadedFile( postbox: self.context.account.postbox, @@ -211,7 +210,7 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer switch result { case let .media(resultMedia): if let resultFile = resultMedia.media as? TelegramMediaFile { - self.context.account.postbox.mediaBox.moveResourceData(from: file.resource.id, to: resultFile.resource.id, synchronous: true) + self.context.engine.resources.moveResourceData(from: EngineMediaResource.Id(file.resource.id), to: EngineMediaResource.Id(resultFile.resource.id), synchronous: true) self.controllerNode.addToSavedMusic(file: .standalone(media: file)) } } diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 9c050cddf5..6ca193c38b 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -86,7 +85,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu context: AccountContext, chatLocation: ChatLocation, type: MediaManagerPlayerType, - initialMessageId: MessageId, + initialMessageId: EngineMessage.Id, initialOrder: MusicPlaybackSettingsOrder, playlistLocation: SharedMediaPlaylistLocation?, requestDismiss: @escaping () -> Void, @@ -120,8 +119,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu self.currentIsReversed = true } - var openMessageImpl: ((MessageId) -> Bool)? - var openMessageContextMenuImpl: ((Message, ASDisplayNode, CGRect, Any?) -> Void)? + var openMessageImpl: ((EngineMessage.Id) -> Bool)? + var openMessageContextMenuImpl: ((EngineRawMessage, ASDisplayNode, CGRect, Any?) -> Void)? self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in if let openMessageImpl = openMessageImpl { return openMessageImpl(message.id) @@ -142,7 +141,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in - }, sendMessage: { _ in + }, sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in @@ -155,8 +154,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in - }, shareCurrentLocation: { - }, shareAccountContact: { + }, openExternalInstantPage: { _ in + }, shareCurrentLocation: { _ in + }, shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in @@ -203,7 +203,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, displaySwipeToReplyHint: { }, dismissReplyMarkupMessage: { _ in }, openMessagePollResults: { _, _ in - }, openPollCreation: { _ in + }, openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, displayPollSolution: { _, _ in }, displayPsa: { _, _ in @@ -245,10 +245,11 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, sendGift: { _ in }, openUniqueGift: { _ in }, openMessageFeeException: { - }, requestMessageUpdate: { _, _ in + }, requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { - }, scrollToMessageId: { _ in + }, scrollToMessageId: { _, _ in + }, scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, attemptedNavigationToPrivateQuote: { _ in }, forceUpdateWarpContents: { @@ -258,7 +259,10 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu }, requestToggleTodoMessageItem: { _, _, _ in }, displayTodoToggleUnavailable: { _ in }, openStarsPurchase: { _ in - }, openRankInfo: { _, _, _ in }, openSetPeerAvatar: {}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) + }, openRankInfo: { _, _, _ in + }, openSetPeerAvatar: { + }, displayPollRestrictedToast: { _ in + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil)) self.dimNode = ASDisplayNode() self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -301,7 +305,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu self.historyFrameTopMaskNode.image = generateCornersImage(theme: self.presentationData.theme) self.historyFrameTopMaskNode.isUserInteractionEnabled = false - let tagMask: MessageTags + let tagMask: EngineMessage.Tags switch type { case .music: tagMask = .music @@ -387,7 +391,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu self.currentAlbumArt = fileReferenceAndAlbumArt if let (fileReference, albumArt) = fileReferenceAndAlbumArt { - self.albumArtNode.setSignal(playerAlbumArt(postbox: self.context.account.postbox, engine: self.context.engine, fileReference: fileReference, albumArt: albumArt, thumbnail: false)) + self.albumArtNode.setSignal(playerAlbumArt(engine: self.context.engine, fileReference: fileReference, albumArt: albumArt, thumbnail: false)) } self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut)) @@ -466,7 +470,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } openMessageImpl = { [weak self] id in - if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id) { + if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.historyNode.messageInCurrentHistoryView(id)?._asMessage() { var playlistLocation: PeerMessagesPlaylistLocation? if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation { if case let .custom(messages, canReorder, _, loadMore, hidePanel) = location { @@ -600,7 +604,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: savedMusicContext.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self, let peer = peer.flatMap({ PeerReference($0._asPeer()) }) else { + guard let self, let peer = peer.flatMap({ PeerReference($0) }) else { return } @@ -736,7 +740,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu if let controller = self.context.sharedContext.makePeerInfoController( context: self.context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .myProfile, avatarInitiallyExpanded: false, fromChat: false, @@ -840,7 +844,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu containerTransform = CATransform3DScale(containerTransform, scale, scale, scale) transition.updateTransform(layer: self.containerContainingNode.layer, transform: containerTransform) - transition.updateCornerRadius(node: self.containerContainingNode, cornerRadius: layout.deviceMetrics.screenCornerRadius) + + let containerCornerRadius = max(22.0, layout.deviceMetrics.screenCornerRadius) + transition.updateCornerRadius(node: self.containerContainingNode, cornerRadius: containerCornerRadius) } private var effectiveHeaderHeight: CGFloat { @@ -1188,8 +1194,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } } - private func transitionToUpdatedHistoryNode(atMessage messageId: MessageId) { - let tagMask: MessageTags + private func transitionToUpdatedHistoryNode(atMessage messageId: EngineMessage.Id) { + let tagMask: EngineMessage.Tags switch self.type { case .music: tagMask = .music @@ -1359,8 +1365,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } } - private func openMessageContextMenu(message: Message, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) { - guard let node = node as? ContextExtractedContentContainingNode, let peer = message.peers[message.id.peerId].flatMap({ PeerReference($0) }), let file = message.media.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile else { + private func openMessageContextMenu(message: EngineRawMessage, node: ASDisplayNode, frame: CGRect, recognizer: TapLongTapOrDoubleTapGestureRecognizer? = nil, gesture: ContextGesture? = nil, location: CGPoint? = nil) { + guard let node = node as? ContextExtractedContentContainingNode, let peer = message.peers[message.id.peerId].flatMap({ PeerReference($0) }), let file = message.effectiveMedia.first(where: { $0 is TelegramMediaFile}) as? TelegramMediaFile else { return } let context = self.context diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift index fb1bb8ed30..b40f5419d0 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControlsNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -453,7 +452,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode { case let .telegramFile(fileReference, _, _): strongSelf.currentFileReference = fileReference if let size = fileReference.media.size { - strongSelf.scrubberNode.bufferingStatus = strongSelf.account.postbox.mediaBox.resourceRangesStatus(fileReference.media.resource) + strongSelf.scrubberNode.bufferingStatus = strongSelf.engine.resources.resourceRangesStatus(resource: EngineMediaResource(fileReference.media.resource)) |> map { ranges -> (RangeSet, Int64) in return (ranges, size) } @@ -759,7 +758,7 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode { if self.currentAlbumArt != albumArt || !self.currentAlbumArtInitialized { self.currentAlbumArtInitialized = true self.currentAlbumArt = albumArt - self.albumArtNode.setSignal(playerAlbumArt(postbox: self.account.postbox, engine: self.engine, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: true)) + self.albumArtNode.setSignal(playerAlbumArt(engine: self.engine, fileReference: self.currentFileReference, albumArt: albumArt, thumbnail: true)) self.requestAlbumArtDisplay?(nil) } } @@ -829,8 +828,14 @@ final class OverlayAudioPlayerControlsNode: ASDisplayNode { } func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, maxHeight: CGFloat, savedMusic: Bool?, transition: ContainedViewLayoutTransition) -> CGFloat { + let previousWidth = self.validLayout?.width self.validLayout = (width, leftInset, rightInset, bottomInset, maxHeight, savedMusic) + var transition = transition + if previousWidth == nil || previousWidth?.isZero == true { + transition = .immediate + } + let finalPanelHeight = OverlayAudioPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, maxHeight: maxHeight, isExpanded: self.isExpanded, savedMusic: savedMusic) let panelHeight = OverlayAudioPlayerControlsNode.heightForLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, maxHeight: maxHeight, isExpanded: self.isExpanded, savedMusic: nil) diff --git a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift index e4c763d9ca..e341ec78b0 100644 --- a/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/OverlayInstantVideoNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import SwiftSignalKit import Display import TelegramCore -import Postbox import TelegramPresentationData import UniversalMediaPlayer import TelegramUIPreferences @@ -40,14 +39,14 @@ final class OverlayInstantVideoNode: OverlayMediaItemNode { var playbackEnded: (() -> Void)? - init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, close: @escaping () -> Void) { + init(context: AccountContext, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, close: @escaping () -> Void) { self.close = close self.content = content var togglePlayPauseImpl: (() -> Void)? let decoration = OverlayInstantVideoDecoration(tapped: { togglePlayPauseImpl?() }) - self.videoNode = UniversalVideoNode(context: context, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .secondaryOverlay, snapshotContentWhenGone: true) + self.videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .secondaryOverlay, snapshotContentWhenGone: true) self.decoration = decoration super.init() diff --git a/submodules/TelegramUI/Sources/OverlayMediaController.swift b/submodules/TelegramUI/Sources/OverlayMediaController.swift index cbed0ab07e..1dddfaadc4 100644 --- a/submodules/TelegramUI/Sources/OverlayMediaController.swift +++ b/submodules/TelegramUI/Sources/OverlayMediaController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import AccountContext import AVKit diff --git a/submodules/TelegramUI/Sources/OverlayMediaControllerNode.swift b/submodules/TelegramUI/Sources/OverlayMediaControllerNode.swift index 0e2cc2d901..e5b60ac8fc 100644 --- a/submodules/TelegramUI/Sources/OverlayMediaControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayMediaControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import AccountContext private final class OverlayMediaControllerNodeView: UITracingLayerView { diff --git a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift index 5ef8ed2ef9..6b54a65b86 100644 --- a/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift +++ b/submodules/TelegramUI/Sources/PeerMediaCollectionInterfaceState.swift @@ -1,5 +1,5 @@ import Foundation -import Postbox +import TelegramCore import TelegramPresentationData import ChatInterfaceState @@ -11,12 +11,12 @@ enum PeerMediaCollectionMode: Int32 { } struct PeerMediaCollectionInterfaceState: Equatable { - let peer: Peer? + let peer: EnginePeer? let selectionState: ChatInterfaceSelectionState? let mode: PeerMediaCollectionMode let theme: PresentationTheme let strings: PresentationStrings - + init(theme: PresentationTheme, strings: PresentationStrings) { self.theme = theme self.strings = strings @@ -24,21 +24,17 @@ struct PeerMediaCollectionInterfaceState: Equatable { self.selectionState = nil self.mode = .photoOrVideo } - - init(peer: Peer?, selectionState: ChatInterfaceSelectionState?, mode: PeerMediaCollectionMode, theme: PresentationTheme, strings: PresentationStrings) { + + init(peer: EnginePeer?, selectionState: ChatInterfaceSelectionState?, mode: PeerMediaCollectionMode, theme: PresentationTheme, strings: PresentationStrings) { self.peer = peer self.selectionState = selectionState self.mode = mode self.theme = theme self.strings = strings } - + static func ==(lhs: PeerMediaCollectionInterfaceState, rhs: PeerMediaCollectionInterfaceState) -> Bool { - if let peer = lhs.peer { - if rhs.peer == nil || !peer.isEqual(rhs.peer!) { - return false - } - } else if let _ = rhs.peer { + if lhs.peer != rhs.peer { return false } @@ -61,8 +57,8 @@ struct PeerMediaCollectionInterfaceState: Equatable { return true } - func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> PeerMediaCollectionInterfaceState { - var selectedIds = Set() + func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> PeerMediaCollectionInterfaceState { + var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } @@ -72,8 +68,8 @@ struct PeerMediaCollectionInterfaceState: Equatable { return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), mode: self.mode, theme: self.theme, strings: self.strings) } - func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> PeerMediaCollectionInterfaceState { - var selectedIds = Set() + func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> PeerMediaCollectionInterfaceState { + var selectedIds = Set() if let selectionState = self.selectionState { selectedIds.formUnion(selectionState.selectedIds) } @@ -95,7 +91,7 @@ struct PeerMediaCollectionInterfaceState: Equatable { return PeerMediaCollectionInterfaceState(peer: self.peer, selectionState: nil, mode: self.mode, theme: self.theme, strings: self.strings) } - func withUpdatedPeer(_ peer: Peer?) -> PeerMediaCollectionInterfaceState { + func withUpdatedPeer(_ peer: EnginePeer?) -> PeerMediaCollectionInterfaceState { return PeerMediaCollectionInterfaceState(peer: peer, selectionState: self.selectionState, mode: self.mode, theme: self.theme, strings: self.strings) } diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index fb9234290f..4dd03bc17f 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -448,7 +448,7 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess }) }, openPeer: { peer in if let peer = peer.peers[peer.peerId] { - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } } diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 1ab0ba75e7..9e45dce177 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -52,10 +52,10 @@ private final class PrefetchManagerInnerImpl { } |> distinctUntilChanged - let appConfiguration = account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) + let appConfiguration = engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)) |> take(1) |> map { view in - return view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue + return view?.get(AppConfiguration.self) ?? .defaultValue } let orderedPreloadMedia = combineLatest(account.viewTracker.orderedPreloadMedia, TelegramEngine(account: account).stickers.loadedStickerPack(reference: .animatedEmoji, forceActualized: false), appConfiguration) diff --git a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift index cfdec9f045..85bab95ea4 100644 --- a/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift +++ b/submodules/TelegramUI/Sources/PreparedChatHistoryViewTransition.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import Display import MergeLists @@ -9,7 +8,7 @@ import ChatControllerInteraction import ChatHistoryEntry import ChatMessageBubbleItemNode -func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, source: ChatHistoryListSource, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: InitialMessageHistoryData?, keyboardButtonsMessage: Message?, cachedData: CachedPeerData?, cachedDataMessages: [MessageId: Message]?, readStateData: [PeerId: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNodeImpl?, allUpdated: Bool) -> ChatHistoryViewTransition { +func preparedChatHistoryViewTransition(from fromView: ChatHistoryView?, to toView: ChatHistoryView, reason: ChatHistoryViewTransitionReason, reverse: Bool, chatLocation: ChatLocation, source: ChatHistoryListSource, controllerInteraction: ChatControllerInteraction, scrollPosition: ChatHistoryViewScrollPosition?, scrollAnimationCurve: ListViewAnimationCurve?, initialData: EngineInitialMessageHistoryData?, keyboardButtonsMessage: EngineRawMessage?, cachedData: EngineCachedPeerData?, cachedDataMessages: [EngineMessage.Id: EngineRawMessage]?, readStateData: [EnginePeer.Id: ChatHistoryCombinedInitialReadStateData]?, flashIndicators: Bool, updatedMessageSelection: Bool, messageTransitionNode: ChatMessageTransitionNodeImpl?, allUpdated: Bool) -> ChatHistoryViewTransition { var mergeResult: (deleteIndices: [Int], indicesAndItems: [(Int, ChatHistoryEntry, Int?)], updateIndices: [(Int, ChatHistoryEntry, Int)]) let allUpdated = allUpdated || (fromView?.associatedData != toView.associatedData) if reverse { diff --git a/submodules/TelegramUI/Sources/SaveMediaToFiles.swift b/submodules/TelegramUI/Sources/SaveMediaToFiles.swift index 7ad5919923..ef91d38ffd 100644 --- a/submodules/TelegramUI/Sources/SaveMediaToFiles.swift +++ b/submodules/TelegramUI/Sources/SaveMediaToFiles.swift @@ -2,7 +2,6 @@ import Foundation import AVFoundation import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import OverlayStatusController @@ -24,7 +23,7 @@ func saveMediaToFiles(context: AccountContext, fileReference: FileMediaReference } } - var signal = fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: fileReference.abstract) + var signal = fetchMediaData(context: context, userLocation: .other, mediaReference: fileReference.abstract) var cancelImpl: (() -> Void)? let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -60,11 +59,9 @@ func saveMediaToFiles(context: AccountContext, fileReference: FileMediaReference case .progress: break case let .data(data): - if data.complete { + if data.isComplete { var symlinkPath = data.path + ".mp3" - if fileSize(symlinkPath) != nil { - try? FileManager.default.removeItem(atPath: symlinkPath) - } + try? FileManager.default.removeItem(atPath: symlinkPath) let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) let audioUrl = URL(fileURLWithPath: symlinkPath) diff --git a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift index b8ae461975..be6ae06001 100644 --- a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import SwiftSignalKit import LocalizedPeerData import ChatPresentationInterfaceState @@ -53,7 +52,7 @@ final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isMediaInputExpanded: Bool) -> CGFloat { self.presentationInterfaceState = interfaceState var text: String? diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index dd5d28ed28..6b701ebe86 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -13,7 +13,6 @@ import DeviceLocationManager import ItemListUI import LegacyUI import ChatListUI -import PeersNearbyUI import PeerInfoUI import SettingsUI import UrlHandling @@ -22,6 +21,7 @@ import LocalMediaResources import OverlayStatusController import AlertUI import PresentationDataUtils +import OpenUserGeneratedUrl import LocationUI import AppLock import WallpaperBackgroundNode @@ -99,14 +99,17 @@ import GiftCraftScreen import ChatParticipantRightsScreen import PeerCopyProtectionInfoScreen import ChatRankInfoScreen +import PollStatsScreen import RankChatPreviewItem import TextProcessingScreen import CreateBotScreen +import EmojiStatusSelectionComponent +import EntityKeyboard private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() let tokens = Bag() - + var isEmpty: Bool { return self.tokens.isEmpty && self.subscribers.isEmpty } @@ -292,7 +295,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, notificationController: NotificationContainerController?, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, hasInAppPurchases: Bool, rootPath: String, legacyBasePath: String?, apsNotificationToken: Signal, voipNotificationToken: Signal, firebaseSecretStream: Signal<[String: String], NoError>, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?, Bool) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }, appDelegate: AppDelegate?, testingEnvironment: Bool = false) { assert(Queue.mainQueue().isCurrent()) - + precondition(!testHasInstance) testHasInstance = true @@ -805,7 +808,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { guard let peer = peer else { return nil } - return AccountWithInfo(account: context.account, peer: peer._asPeer()) + return AccountWithInfo(account: context.account, peer: peer) } |> distinctUntilChanged }) @@ -1931,7 +1934,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { handleTextLinkActionImpl(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink) } - public func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? { + public func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController? { let controller = peerInfoControllerImpl(context: context, updatedPresentationData: updatedPresentationData, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: fromChat) controller?.navigationPresentation = .modalInLargeLayout return controller @@ -2241,15 +2244,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?) { openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, forceExternal: forceExternal, forceUpdate: forceUpdate, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, sendEmoji: sendEmoji, requestMessageActionUrlAuth: requestMessageActionUrlAuth, joinVoiceChat: joinVoiceChat, present: present, dismissInput: dismissInput, contentContext: contentContext, progress: progress, completion: completion) } - + + public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, webpage: TelegramMediaWebpage?, concealed: Bool, forceConcealed: Bool, skipUrlAuth: Bool, skipConcealedAlert: Bool, forceDark: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise?, alertDisplayUpdated: ((ViewController?) -> Void)?, concealedAlertOption: OpenUserGeneratedUrlConcealedAlertOption?) -> Disposable { + return OpenUserGeneratedUrl.openUserGeneratedUrl(context: context, peerId: peerId, url: url, webpage: webpage, concealed: concealed, forceConcealed: forceConcealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, forceDark: forceDark, present: present, openResolved: openResolved, progress: progress, alertDisplayUpdated: alertDisplayUpdated, concealedAlertOption: concealedAlertOption) + } + public func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { return deviceContactInfoController(context: context, environment: environment, subject: subject, completed: completed, cancelled: cancelled) } - - public func makePeersNearbyController(context: AccountContext) -> ViewController { - return peersNearbyController(context: context) - } - + public func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode, params: ChatControllerParams?) -> ChatController { return ChatControllerImpl(context: context, chatLocation: chatLocation, subject: subject, botStart: botStart, mode: mode, params: params) } @@ -2295,7 +2298,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return nil } - public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController { + public func makeChatRecentActionsController(context: AccountContext, peer: EnginePeer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController { return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId, starsState: starsState) } @@ -2343,7 +2346,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return PeerSelectionControllerImpl(params) } - public func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { + public func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, parentController: ViewController, groupPeer: EnginePeer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) { return presentAddMembersImpl(context: context, updatedPresentationData: updatedPresentationData, parentController: parentController, groupPeer: groupPeer, selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable) } @@ -2376,7 +2379,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, - sendMessage: { _ in }, + sendMessage: { _, _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, @@ -2389,8 +2392,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, - shareCurrentLocation: {}, - shareAccountContact: {}, + openExternalInstantPage: { _ in + }, + shareCurrentLocation: { _ in }, + shareAccountContact: { _ in }, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, @@ -2469,7 +2474,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openMessagePollResults: { _, _ in }, - openPollCreation: { _ in + openPollCreation: { _, _ in }, openPollMedia: { _, _ in }, @@ -2551,13 +2556,15 @@ public final class SharedAccountContextImpl: SharedAccountContext { }, openMessageFeeException: { }, - requestMessageUpdate: { _, _ in + requestMessageUpdate: { _, _, _ in }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { }, - scrollToMessageId: { _ in + scrollToMessageId: { _, _ in + }, + scrollToMessageIdWithAnchor: { _, _ in }, navigateToStory: { _, _ in }, @@ -2578,7 +2585,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { openStarsPurchase: { _ in }, openRankInfo: { _, _, _ in - }, openSetPeerAvatar: { + }, + openSetPeerAvatar: { + }, + displayPollRestrictedToast: { _ in }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), @@ -2723,7 +2733,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { return recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: context.engine.privacy.webSessions(), websitesOnly: false) } - public func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController { + public func makeChatQrCodeScreen(context: AccountContext, peer: EnginePeer, threadId: Int64?, temporary: Bool) -> ViewController { return ChatQrCodeScreenImpl(context: context, subject: .peer(peer: peer, threadId: threadId, temporary: temporary)) } @@ -2756,19 +2766,18 @@ public final class SharedAccountContextImpl: SharedAccountContext { return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(.chat) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send) } - public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, pushViewController: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? { + public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, pushViewController: @escaping (ViewController) -> Void, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void, getNavigationController: @escaping () -> NavigationController?) -> NSObject? { let inputPanelNode = LegacyMessageInputPanelNode( context: context, chatLocation: chatLocation, isScheduledMessages: isScheduledMessages, isFile: isFile, hasTimer: hasTimer, + customEmojiAvailable: customEmojiAvailable, pushViewController: pushViewController, present: present, presentInGlobalOverlay: presentInGlobalOverlay, - makeEntityInputView: { - return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable) - } + getNavigationController: getNavigationController ) return inputPanelNode } @@ -2973,8 +2982,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .copyProtection case .aiTools: mappedSource = .aiTools - case let .auth(price): - mappedSource = .auth(price) + case let .auth(price, days): + mappedSource = .auth(price, days) case let .premiumGift(file): mappedSource = .premiumGift(file) } @@ -3292,12 +3301,12 @@ public final class SharedAccountContextImpl: SharedAccountContext { )).startStandalone(next: { [weak controller] result, options in if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext { if case .starGiftTransfer = source { - presentTransferAlertImpl?(EnginePeer(peer)) + presentTransferAlertImpl?(peer) } else { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.DisallowedGifts(id: peer.id)) |> deliverOnMainQueue).start(next: { disallowedGifts in if let disallowedGifts, disallowedGifts == TelegramDisallowedGifts.All && peer.id != context.account.peerId { - let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Gift_Send_GiftsDisallowed(EnginePeer(peer).compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) + let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Gift_Send_GiftsDisallowed(peer.compactDisplayTitle).string, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]) controller?.present(alertController, in: .window(.root)) return } @@ -3330,7 +3339,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let infoController = self.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: peer.smallProfileImage != nil, fromChat: false, @@ -3478,7 +3487,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let controller = context.sharedContext.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .gifts, avatarInitiallyExpanded: false, fromChat: false, @@ -3710,16 +3719,21 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } - public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController { + public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: (([StickerPackScreenActionResult]) -> Void)?) -> ViewController { return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, actionTitle: actionTitle, isEditing: isEditing, expandIfNeeded: expandIfNeeded, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: { actions in - if let (_, _, action) = actions.first { + guard let actionPerformed = actionPerformed else { + return + } + actionPerformed(actions.map { info, items, action in + let mappedAction: StickerPackScreenActionKind switch action { case .add: - actionPerformed?(true) - case .remove: - actionPerformed?(false) + mappedAction = .add + case let .remove(positionInList): + mappedAction = .remove(positionInList: positionInList) } - } + return StickerPackScreenActionResult(info: info, items: items, action: mappedAction) + }) }) } @@ -3953,8 +3967,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return stickerMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed) } - public func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) { - return avatarMediaPickerController(context: context, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed) + public func makeAvatarMediaPickerScreen(context: AccountContext, peerType: PeerType, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?) { + return avatarMediaPickerController(context: context, peerType: peerType, getSourceRect: getSourceRect, canDelete: canDelete, performDelete: performDelete, completion: completion, dismissed: dismissed) } public func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController { @@ -3969,7 +3983,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController { - return proxySettingsController(accountManager: sharedContext.accountManager, sharedContext: sharedContext, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) + return proxySettingsController(accountManager: sharedContext.accountManager, sharedContext: sharedContext, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) } public func makeDataAndStorageController(context: AccountContext, sensitiveContent: Bool) -> ViewController { @@ -3988,6 +4002,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { return messageStatsController(context: context, updatedPresentationData: updatedPresentationData, subject: .message(id: messageId)) } + public func makePollStatsScreen(context: AccountContext, messageId: EngineMessage.Id) -> ViewController { + return PollStatsScreen(context: context, messageId: messageId) + } public func makeStoryStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peerId: EnginePeer.Id, storyId: Int32, storyItem: EngineStoryItem, fromStory: Bool) -> ViewController { return messageStatsController(context: context, updatedPresentationData: updatedPresentationData, subject: .story(peerId: peerId, id: storyId, item: storyItem, fromStory: fromStory)) } @@ -4008,7 +4025,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { if let infoController = self.makePeerInfoController( context: context, updatedPresentationData: nil, - peer: peer._asPeer(), + peer: peer, mode: .generic, avatarInitiallyExpanded: peer.smallProfileImage != nil, fromChat: false, @@ -4253,6 +4270,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { openWebAppImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, botPeer: botPeer, chatPeer: chatPeer, threadId: threadId, buttonText: buttonText, url: url, simple: simple, source: source, skipTermsOfService: skipTermsOfService, payload: payload, verifyAgeCompletion: verifyAgeCompletion) } + public func openJoinChatWebView(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, webView: JoinChatWebView) { + openJoinChatWebViewImpl(context: context, parentController: parentController, updatedPresentationData: updatedPresentationData, webView: webView) + } + public func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal { return AffiliateProgramSetupScreen.content(context: context, peerId: peerId, mode: mode) } @@ -4294,8 +4315,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { return CocoonInfoScreen(context: context) } - public func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController { - return chatTextLinkEditController(context: context, updatedPresentationData: updatedPresentationData, text: text, link: link, apply: apply) + public func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?, TelegramMediaWebpage?) -> Void) -> ViewController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + return chatTextLinkEditController(context: context, updatedPresentationData: updatedPresentationData, text: presentationData.strings.TextFormat_AddLinkText(text).string, link: link, apply: apply) } @available(iOS 13.0, *) @@ -4412,9 +4434,21 @@ public final class SharedAccountContextImpl: SharedAccountContext { completion: completion ) } + + public func makeEmojiStatusSelectionController(context: AccountContext, mode: EmojiStatusSelectionControllerMode, sourceView: UIView, emojiContent: Signal, currentSelection: Int64?, color: UIColor?, destinationItemView: @escaping () -> UIView?) -> ViewController { + return EmojiStatusSelectionController( + context: context, + mode: mode, + sourceView: sourceView, + emojiContent: emojiContent |> map { $0 as! EmojiPagerContentComponent }, + currentSelection: currentSelection, + color: color, + destinationItemView: destinationItemView + ) + } } -private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { +private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { var switchToMediaTarget: PeerInfoSwitchToMediaTarget? switch mode { case let .media(kind, index): @@ -4430,15 +4464,18 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation break } - if let _ = peer as? TelegramGroup { - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget) - } else if let _ = peer as? TelegramChannel { + if case .legacyGroup = peer { + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget) + } else if case .channel = peer { + var sourceMessageId: MessageId? var forumTopicThread: ChatReplyThreadMessage? var switchToRecommendedChannels = false var switchToGiftsTarget: PeerInfoSwitchToGiftsTarget? var switchToGroupsInCommon = false var switchToStoryFolder: Int64? switch mode { + case let .group(messageId): + sourceMessageId = messageId case let .forumTopic(thread): forumTopicThread = thread case .recommendedChannels: @@ -4454,11 +4491,11 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation default: break } - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget) - } else if peer is TelegramUser { - var nearbyPeerDistance: Int32? + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, reactionSourceMessageId: nil, sourceMessageId: sourceMessageId, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget) + } else if case .user = peer { var reactionSourceMessageId: MessageId? - var callMessages: [Message] = [] + var sourceMessageId: MessageId? + var callMessages: [EngineMessage] = [] var hintGroupInCommon: PeerId? var forumTopicThread: ChatReplyThreadMessage? var sharedMediaFromForumTopic: (EnginePeer.Id, Int64)? @@ -4468,16 +4505,16 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation var switchToStoryFolder: Int64? switch mode { - case let .nearbyPeer(distance): - nearbyPeerDistance = distance case let .calls(messages): callMessages = messages case .generic: break - case let .group(id): - hintGroupInCommon = id + case let .group(messageId): + hintGroupInCommon = messageId.peerId + sourceMessageId = messageId case let .reaction(messageId): reactionSourceMessageId = messageId + sourceMessageId = messageId case let .forumTopic(thread): forumTopicThread = thread case .myProfile: @@ -4507,9 +4544,9 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation default: break } - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget) - } else if peer is TelegramSecretChat { - return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget) + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, reactionSourceMessageId: reactionSourceMessageId, sourceMessageId: sourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget) + } else if case .secretChat = peer { + return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget) } return nil } diff --git a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift index 7d48f091fa..8e69e29324 100644 --- a/submodules/TelegramUI/Sources/SharedMediaPlayer.swift +++ b/submodules/TelegramUI/Sources/SharedMediaPlayer.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import UniversalMediaPlayer @@ -114,7 +113,7 @@ private enum SharedMediaPlaybackItem: Equatable { final class SharedMediaPlayer { private weak var context: AccountContext? private weak var mediaManager: MediaManager? - let account: Account + let engine: TelegramEngine private let audioSession: ManagedAudioSession private let overlayMediaManager: OverlayMediaManager private let playerIndex: Int32 @@ -181,10 +180,10 @@ final class SharedMediaPlayer { let type: MediaManagerPlayerType - init(context: AccountContext, mediaManager: MediaManager, inForeground: Signal, account: Account, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool, type: MediaManagerPlayerType, continueInstantVideoLoopAfterFinish: Bool) { + init(context: AccountContext, mediaManager: MediaManager, inForeground: Signal, engine: TelegramEngine, audioSession: ManagedAudioSession, overlayMediaManager: OverlayMediaManager, playlist: SharedMediaPlaylist, initialOrder: MusicPlaybackSettingsOrder, initialLooping: MusicPlaybackSettingsLooping, initialPlaybackRate: AudioPlaybackRate, playerIndex: Int32, controlPlaybackWithProximity: Bool, type: MediaManagerPlayerType, continueInstantVideoLoopAfterFinish: Bool) { self.context = context self.mediaManager = mediaManager - self.account = account + self.engine = engine self.audioSession = audioSession self.overlayMediaManager = overlayMediaManager playlist.setOrder(initialOrder) @@ -233,13 +232,13 @@ final class SharedMediaPlayer { case .voice, .music: switch playbackData.source { case let .telegramFile(fileReference, _, _): - strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.account.postbox, userLocation: .other, userContentType: .audio, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music ? .conservative : .none, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity, isAudioVideoMessage: playbackData.type == .voice)) + strongSelf.playbackItem = .audio(MediaPlayer(audioSessionManager: strongSelf.audioSession, postbox: strongSelf.engine.account.postbox, userLocation: .other, userContentType: .audio, resourceReference: fileReference.resourceReference(fileReference.media.resource), streamable: playbackData.type == .music ? .conservative : .none, video: false, preferSoftwareDecoding: false, enableSound: true, baseRate: rateValue, fetchAutomatically: true, playAndRecord: controlPlaybackWithProximity, isAudioVideoMessage: playbackData.type == .voice)) } case .instantVideo: if let mediaManager = strongSelf.mediaManager, let context = strongSelf.context, let item = item as? MessageMediaPlaylistItem { switch playbackData.source { case let .telegramFile(fileReference, _, _): - let videoNode = OverlayInstantVideoNode(context: context, postbox: strongSelf.account.postbox, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.stableId, fileReference.media.fileId), userLocation: .peer(item.message.id.peerId), fileReference: fileReference, enableSound: false, baseRate: rateValue, isAudioVideoMessage: true, captureProtected: item.message.isCopyProtected(), storeAfterDownload: nil), close: { [weak mediaManager] in + let videoNode = OverlayInstantVideoNode(context: context, audioSession: strongSelf.audioSession, manager: mediaManager.universalVideoManager, content: NativeVideoContent(id: .message(item.message.stableId, fileReference.media.fileId), userLocation: .peer(item.message.id.peerId), fileReference: fileReference, enableSound: false, baseRate: rateValue, isAudioVideoMessage: true, captureProtected: item.message.isCopyProtected(), storeAfterDownload: nil), close: { [weak mediaManager] in mediaManager?.setPlaylist(nil, type: .voice, control: .playback(.pause)) }) strongSelf.playbackItem = .instantVideo(videoNode) @@ -497,9 +496,9 @@ final class SharedMediaPlayer { let fetchedNextSignal: Signal switch current { case let .telegramFile(file, _, _): - fetchedCurrentSignal = self.account.postbox.mediaBox.resourceData(file.media.resource) + fetchedCurrentSignal = self.engine.resources.data(resource: EngineMediaResource(file.media.resource)) |> mapToSignal { data -> Signal in - if data.complete { + if data.isComplete { return .single(Void()) } else { return .complete() @@ -510,7 +509,7 @@ final class SharedMediaPlayer { } switch next { case let .telegramFile(file, _, _): - fetchedNextSignal = fetchedMediaResource(mediaBox: self.account.postbox.mediaBox, userLocation: .other, userContentType: .audio, reference: file.resourceReference(file.media.resource)) + fetchedNextSignal = self.engine.resources.fetch(reference: file.resourceReference(file.media.resource), userLocation: .other, userContentType: .audio) |> ignoreValues |> `catch` { _ -> Signal in return .complete() diff --git a/submodules/TelegramUI/Sources/SharedNotificationManager.swift b/submodules/TelegramUI/Sources/SharedNotificationManager.swift index c2a945ef66..7ce3502e36 100644 --- a/submodules/TelegramUI/Sources/SharedNotificationManager.swift +++ b/submodules/TelegramUI/Sources/SharedNotificationManager.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import UserNotifications import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -129,7 +128,7 @@ public final class SharedNotificationManager { } let previousDisposable = context.disposable context.disposable = (account.stateManager.pollStateUpdateCompletion() - |> mapToSignal { messageIds -> Signal<[MessageId], NoError> in + |> mapToSignal { messageIds -> Signal<[EngineMessage.Id], NoError> in return .single(messageIds) |> delay(1.0, queue: Queue.mainQueue()) } @@ -218,7 +217,7 @@ public final class SharedNotificationManager { let aps = payload["aps"] as? [AnyHashable: Any] - var readMessageId: MessageId? + var readMessageId: EngineMessage.Id? var isForcedLogOut = false var isCall = false var isAnnouncement = false @@ -229,7 +228,7 @@ public final class SharedNotificationManager { var body: String? var apnsSound: String? var configurationUpdate: (Int32, String, Int32, Data?)? - var messagesDeleted: [MessageId] = [] + var messagesDeleted: [EngineMessage.Id] = [] if let aps = aps, let alert = aps["alert"] as? String { if let range = alert.range(of: ": ") { title = String(alert[.. TempBoxFile? in - let file = TempBox.shared.tempFile(fileName: "image.jpg") + let firstFrameFile = imageData.flatMap { data -> EngineTempBoxFile? in + let file = EngineTempBox.shared.tempFile(fileName: "image.jpg") if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) { return file } else { diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index b6e53647a7..de144e980d 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -18,12 +18,15 @@ import UndoUI import BrowserUI func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem) { + let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) + let presentImpl: (ViewController, Any?) -> Void = { controllerToPresent, _ in controller.present(controllerToPresent, in: .window(.root)) } let openResolvedPeerImpl: (EnginePeer?, ChatControllerInteractionNavigateToPeer) -> Void = { [weak controller] peer, navigation in - guard let peer = peer else { + guard let peer else { + controller?.present(textAlertController(context: context, updatedPresentationData: nil, title: nil, text: presentationData.strings.Resolve_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return } context.sharedContext.openResolvedUrl(.peer(peer._asPeer(), navigation), context: context, urlContext: .generic, navigationController: (controller?.navigationController as? NavigationController), forceExternal: false, forceUpdate: false, openPeer: { (peer, navigation) in @@ -39,7 +42,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n ) navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in if let controller = controller, let peer = peer { - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { (controller.navigationController as? NavigationController)?.pushViewController(infoController) } } @@ -73,7 +76,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n if let controller = controller { switch result { case let .externalUrl(url): - context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: context.sharedContext.currentPresentationData.with({ $0 }), navigationController: controller.navigationController as? NavigationController, dismissInput: { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: false, presentationData: presentationData, navigationController: controller.navigationController as? NavigationController, dismissInput: { }) case let .peer(peer, navigation): openResolvedPeerImpl(peer.flatMap(EnginePeer.init), navigation) @@ -100,7 +103,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n let sourceLocation = InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group) let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation) (controller.navigationController as? NavigationController)?.pushViewController(browserController, animated: true) - case .boost, .chatFolder, .join, .invoice: + case .boost, .chatFolder, .join, .invoice, .proxy: if let navigationController = controller.navigationController as? NavigationController { openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, message: nil, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigateToPeer in openResolvedPeerImpl(peer, navigateToPeer) @@ -128,7 +131,6 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n })) } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } switch action { case .tap: switch itemLink { @@ -156,10 +158,17 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n openPeerMentionImpl(mention) case let .hashtag(_, hashtag): if let peerId = peerId { - let peerSignal = context.account.postbox.loadedPeerWithId(peerId) + let peerSignal = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if let peer { + return .single(peer) + } else { + return .never() + } + } let _ = (peerSignal |> deliverOnMainQueue).start(next: { peer in - let searchController = HashtagSearchController(context: context, peer: EnginePeer(peer), query: hashtag) + let searchController = HashtagSearchController(context: context, peer: peer, query: hashtag) (controller.navigationController as? NavigationController)?.pushViewController(searchController) }) } diff --git a/submodules/TelegramUI/Sources/ThemeUpdateManager.swift b/submodules/TelegramUI/Sources/ThemeUpdateManager.swift index c157d3ef20..e7baecc1e7 100644 --- a/submodules/TelegramUI/Sources/ThemeUpdateManager.swift +++ b/submodules/TelegramUI/Sources/ThemeUpdateManager.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -91,7 +90,7 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager { let resolvedWallpaper: Signal if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { - resolvedWallpaper = cachedWallpaper(account: account, slug: file.slug, settings: file.settings) + resolvedWallpaper = cachedWallpaper(engine: TelegramEngine(account: account), network: account.network, slug: file.slug, settings: file.settings) |> map { wallpaper in return wallpaper?.wallpaper } @@ -109,7 +108,7 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager { guard complete, let fullSizeData = fullSizeData else { return .complete() } - accountManager.mediaBox.storeResourceData(file.file.resource.id, data: fullSizeData, synchronous: true) + accountManager.resources.storeResourceData(id: EngineMediaResource.Id(file.file.resource.id), data: fullSizeData, synchronous: true) return .single((.cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: wallpaper, creatorAccountId: theme.isCreator ? account.id : nil)), presentationTheme)) } } else { @@ -138,7 +137,7 @@ final class ThemeUpdateManagerImpl: ThemeUpdateManager { theme = updatedTheme } - return PreferencesEntry(PresentationThemeSettings(theme: theme, themePreferredBaseTheme: current.themePreferredBaseTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, reduceMotion: current.reduceMotion)) + return EnginePreferencesEntry(PresentationThemeSettings(theme: theme, themePreferredBaseTheme: current.themePreferredBaseTheme, themeSpecificAccentColors: current.themeSpecificAccentColors, themeSpecificChatWallpapers: current.themeSpecificChatWallpapers, useSystemFont: current.useSystemFont, fontSize: current.fontSize, listsFontSize: current.listsFontSize, chatBubbleSettings: current.chatBubbleSettings, automaticThemeSwitchSetting: automaticThemeSwitchSetting, largeEmoji: current.largeEmoji, reduceMotion: current.reduceMotion)) }) }).start() } diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift index 5ef372684d..a352a13a08 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift @@ -100,12 +100,12 @@ private enum VerticalListContextResultsChatInputContextPanelEntry: Comparable, I } } - func item(account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem { + func item(engine: TelegramEngine, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> ListViewItem { switch self { case let .action(theme, title): return VerticalListContextResultsChatInputPanelButtonItem(theme: theme, title: title, pressed: actionSelected) case let .result(_, theme, result): - return VerticalListContextResultsChatInputPanelItem(account: account, theme: theme, result: result, resultSelected: resultSelected) + return VerticalListContextResultsChatInputPanelItem(engine: engine, theme: theme, result: result, resultSelected: resultSelected) } } } @@ -116,12 +116,12 @@ private struct VerticalListContextResultsChatInputContextPanelTransition { let updates: [ListViewUpdateItem] } -private func preparedTransition(from fromEntries: [VerticalListContextResultsChatInputContextPanelEntry], to toEntries: [VerticalListContextResultsChatInputContextPanelEntry], account: Account, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> VerticalListContextResultsChatInputContextPanelTransition { +private func preparedTransition(from fromEntries: [VerticalListContextResultsChatInputContextPanelEntry], to toEntries: [VerticalListContextResultsChatInputContextPanelEntry], engine: TelegramEngine, actionSelected: @escaping () -> Void, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) -> VerticalListContextResultsChatInputContextPanelTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - + let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, actionSelected: actionSelected, resultSelected: resultSelected), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, actionSelected: actionSelected, resultSelected: resultSelected), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, actionSelected: actionSelected, resultSelected: resultSelected), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, actionSelected: actionSelected, resultSelected: resultSelected), directionHint: nil) } return VerticalListContextResultsChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates) } @@ -239,7 +239,7 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex private func prepareTransition(from: [VerticalListContextResultsChatInputContextPanelEntry]?, to: [VerticalListContextResultsChatInputContextPanelEntry], results: ChatContextResultCollection) { let firstTime = self.currentEntries == nil - let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, actionSelected: { [weak self] in + let transition = preparedTransition(from: from ?? [], to: to, engine: self.context.engine, actionSelected: { [weak self] in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { if let switchPeer = results.switchPeer { interfaceInteraction.botSwitchChatWithPayload(results.botId, switchPeer.startParam) diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift index 86c40fa8a4..60c4255d33 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift @@ -10,15 +10,15 @@ import PhotoResources import StickerResources final class VerticalListContextResultsChatInputPanelItem: ListViewItem { - fileprivate let account: Account + fileprivate let engine: TelegramEngine fileprivate let theme: PresentationTheme fileprivate let result: ChatContextResult fileprivate let resultSelected: (ChatContextResult, ASDisplayNode, CGRect) -> Bool - + let selectable: Bool = true - - public init(account: Account, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) { - self.account = account + + public init(engine: TelegramEngine, theme: PresentationTheme, result: ChatContextResult, resultSelected: @escaping (ChatContextResult, ASDisplayNode, CGRect) -> Bool) { + self.engine = engine self.theme = theme self.result = result self.resultSelected = resultSelected @@ -226,8 +226,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) iconImageApply = iconImageLayout(arguments) - updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource) - |> map(EngineMediaResource.FetchStatus.init) + updatedStatusSignal = item.engine.resources.status(resource: EngineMediaResource(imageResource)) } var updatedIconImageResource = false @@ -242,11 +241,11 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { if updatedIconImageResource { if let imageResource = imageResource { if let stickerFile = stickerFile { - updateIconImageSignal = chatMessageSticker(account: item.account, userLocation: .other, file: stickerFile, small: false, fetched: true) + updateIconImageSignal = chatMessageSticker(account: item.engine.account, userLocation: .other, file: stickerFile, small: false, fetched: true) } else { let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 55, height: 55), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false) let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [tmpRepresentation], immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: []) - updateIconImageSignal = chatWebpageSnippetPhoto(account: item.account, userLocation: .other, photoReference: .standalone(media: tmpImage)) + updateIconImageSignal = chatWebpageSnippetPhoto(account: item.engine.account, userLocation: .other, photoReference: .standalone(media: tmpImage)) } } else { updateIconImageSignal = .complete() diff --git a/submodules/TelegramUI/Sources/WallpaperUploadManager.swift b/submodules/TelegramUI/Sources/WallpaperUploadManager.swift index 5b20cb4bdf..7c732002ad 100644 --- a/submodules/TelegramUI/Sources/WallpaperUploadManager.swift +++ b/submodules/TelegramUI/Sources/WallpaperUploadManager.swift @@ -95,7 +95,7 @@ final class WallpaperUploadManagerImpl: WallpaperUploadManager { switch result { case let .complete(wallpaper): if case let .file(file) = wallpaper { - sharedContext.accountManager.mediaBox.moveResourceData(from: currentResource.id, to: file.file.resource.id) + sharedContext.accountManager.resources.moveResourceData(from: EngineMediaResource.Id(currentResource.id), to: EngineMediaResource.Id(file.file.resource.id)) account.postbox.mediaBox.moveResourceData(from: currentResource.id, to: file.file.resource.id) } default: diff --git a/submodules/TelegramUI/Sources/WidgetDataContext.swift b/submodules/TelegramUI/Sources/WidgetDataContext.swift index 31069a815d..288e3235d4 100644 --- a/submodules/TelegramUI/Sources/WidgetDataContext.swift +++ b/submodules/TelegramUI/Sources/WidgetDataContext.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import WidgetItems import TelegramPresentationData @@ -101,11 +100,11 @@ final class WidgetDataContext { self.reloadManager = WidgetReloadManager(inForeground: inForeground) let queue = Queue() - let updatedAdditionalPeerIds: Signal<[AccountRecordId: Set], NoError> = Signal { subscriber in + let updatedAdditionalPeerIds: Signal<[AccountRecordId: Set], NoError> = Signal { subscriber in if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { #if arch(arm64) || arch(x86_64) WidgetCenter.shared.getCurrentConfigurations { result in - var peerIds: [AccountRecordId: Set] = [:] + var peerIds: [AccountRecordId: Set] = [:] func processFriend(_ item: Friend) { guard let identifier = item.identifier else { @@ -121,7 +120,7 @@ final class WidgetDataContext { return } let accountId = AccountRecordId(rawValue: accountIdValue) - let peerId = PeerId(peerIdValue) + let peerId = EnginePeer.Id(peerIdValue) if peerIds[accountId] == nil { peerIds[accountId] = Set() } @@ -162,7 +161,7 @@ final class WidgetDataContext { } |> runOn(queue) |> then( - Signal<[AccountRecordId: Set], NoError>.complete() + Signal<[AccountRecordId: Set], NoError>.complete() |> delay(10.0, queue: queue) ) |> restart diff --git a/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift b/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift index 073d9b3620..9fd127ea11 100644 --- a/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ContactSynchronizationSettings.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -54,7 +53,7 @@ public func updateContactSettingsInteractively(accountManager: AccountManager InstantPagePresentationSettings { - return InstantPagePresentationSettings(themeType: themeType, fontSize: self.fontSize, forceSerif: self.forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) + return InstantPagePresentationSettings(themeType: themeType, fontSize: self.fontSize, lineSpacingFactor: self.lineSpacingFactor, forceSerif: self.forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) } public func withUpdatedFontSize(_ fontSize: InstantPagePresentationFontSize) -> InstantPagePresentationSettings { - return InstantPagePresentationSettings(themeType: self.themeType, fontSize: fontSize, forceSerif: self.forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) + return InstantPagePresentationSettings(themeType: self.themeType, fontSize: fontSize, lineSpacingFactor: self.lineSpacingFactor, forceSerif: self.forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) } public func withUpdatedForceSerif(_ forceSerif: Bool) -> InstantPagePresentationSettings { - return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, forceSerif: forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) + return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, lineSpacingFactor: self.lineSpacingFactor, forceSerif: forceSerif, autoNightMode: self.autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) } public func withUpdatedAutoNightMode(_ autoNightMode: Bool) -> InstantPagePresentationSettings { - return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, forceSerif: self.forceSerif, autoNightMode: autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) + return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, lineSpacingFactor: self.lineSpacingFactor, forceSerif: self.forceSerif, autoNightMode: autoNightMode, ignoreAutoNightModeUntil: self.ignoreAutoNightModeUntil) } public func withUpdatedIgnoreAutoNightModeUntil(_ ignoreAutoNightModeUntil: Int32) -> InstantPagePresentationSettings { - return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, forceSerif: self.forceSerif, autoNightMode: autoNightMode, ignoreAutoNightModeUntil: ignoreAutoNightModeUntil) + return InstantPagePresentationSettings(themeType: self.themeType, fontSize: self.fontSize, lineSpacingFactor: self.lineSpacingFactor, forceSerif: self.forceSerif, autoNightMode: autoNightMode, ignoreAutoNightModeUntil: ignoreAutoNightModeUntil) } } @@ -106,7 +112,7 @@ public func updateInstantPagePresentationSettingsInteractively(accountManager: A } else { currentSettings = InstantPagePresentationSettings.defaultSettings } - return PreferencesEntry(f(currentSettings)) + return EnginePreferencesEntry(f(currentSettings)) }) } } diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift index d6f3aa3b6f..8961fa49e0 100644 --- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift +++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift @@ -1,6 +1,5 @@ import Foundation import TelegramCore -import Postbox private enum ApplicationSpecificPreferencesKeyValues: Int32 { case voipDerivedState = 16 @@ -13,15 +12,15 @@ private enum ApplicationSpecificPreferencesKeyValues: Int32 { } public struct ApplicationSpecificPreferencesKeys { - public static let voipDerivedState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voipDerivedState.rawValue) - public static let chatArchiveSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatArchiveSettings.rawValue) - public static let chatListFilterSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatListFilterSettings.rawValue) - public static let widgetSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.widgetSettings.rawValue) - public static let mediaAutoSaveSettings = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.mediaAutoSaveSettings.rawValue) - public static let ageVerificationState = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.ageVerificationState.rawValue) + public static let voipDerivedState: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.voipDerivedState.rawValue) + public static let chatArchiveSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatArchiveSettings.rawValue) + public static let chatListFilterSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.chatListFilterSettings.rawValue) + public static let widgetSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.widgetSettings.rawValue) + public static let mediaAutoSaveSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.mediaAutoSaveSettings.rawValue) + public static let ageVerificationState: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificPreferencesKeyValues.ageVerificationState.rawValue) - public static func textProcessingEditingState(peerId: PeerId) -> ValueBoxKey { - let key = ValueBoxKey(length: 4 + 8) + public static func textProcessingEditingState(peerId: EnginePeer.Id) -> EngineDataBuffer { + let key = EngineDataBuffer(length: 4 + 8) key.setInt32(0, value: ApplicationSpecificPreferencesKeyValues.textProcessingEditingState.rawValue) key.setInt64(4, value: peerId.toInt64()) return key @@ -55,29 +54,29 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 { } public struct ApplicationSpecificSharedDataKeys { - public static let inAppNotificationSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.inAppNotificationSettings.rawValue) - public static let presentationPasscodeSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.presentationPasscodeSettings.rawValue) - public static let automaticMediaDownloadSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.automaticMediaDownloadSettings.rawValue) - public static let generatedMediaStoreSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.generatedMediaStoreSettings.rawValue) - public static let voiceCallSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.voiceCallSettings.rawValue) - public static let presentationThemeSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.presentationThemeSettings.rawValue) - public static let instantPagePresentationSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.instantPagePresentationSettings.rawValue) - public static let callListSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.callListSettings.rawValue) - public static let experimentalSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.experimentalSettings.rawValue) - public static let musicPlaybackSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.musicPlaybackSettings.rawValue) - public static let mediaInputSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.mediaInputSettings.rawValue) - public static let experimentalUISettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.experimentalUISettings.rawValue) - public static let stickerSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.stickerSettings.rawValue) - public static let watchPresetSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.watchPresetSettings.rawValue) - public static let webSearchSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.webSearchSettings.rawValue) - public static let contactSynchronizationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.contactSynchronizationSettings.rawValue) - public static let webBrowserSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.webBrowserSettings.rawValue) - public static let intentsSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue) - public static let translationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue) - public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue) - public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue) - public static let updateSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue) - public static let chatSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue) + public static let inAppNotificationSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.inAppNotificationSettings.rawValue) + public static let presentationPasscodeSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.presentationPasscodeSettings.rawValue) + public static let automaticMediaDownloadSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.automaticMediaDownloadSettings.rawValue) + public static let generatedMediaStoreSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.generatedMediaStoreSettings.rawValue) + public static let voiceCallSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.voiceCallSettings.rawValue) + public static let presentationThemeSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.presentationThemeSettings.rawValue) + public static let instantPagePresentationSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.instantPagePresentationSettings.rawValue) + public static let callListSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.callListSettings.rawValue) + public static let experimentalSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.experimentalSettings.rawValue) + public static let musicPlaybackSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.musicPlaybackSettings.rawValue) + public static let mediaInputSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.mediaInputSettings.rawValue) + public static let experimentalUISettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.experimentalUISettings.rawValue) + public static let stickerSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.stickerSettings.rawValue) + public static let watchPresetSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.watchPresetSettings.rawValue) + public static let webSearchSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.webSearchSettings.rawValue) + public static let contactSynchronizationSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.contactSynchronizationSettings.rawValue) + public static let webBrowserSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.webBrowserSettings.rawValue) + public static let intentsSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue) + public static let translationSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue) + public static let drawingSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue) + public static let mediaDisplaySettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue) + public static let updateSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue) + public static let chatSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue) } private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 { @@ -112,8 +111,6 @@ public struct ApplicationSpecificItemCacheCollectionId { } private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { - case webSearchRecentQueries = 0 - case wallpaperSearchRecentQueries = 1 case localThemes = 3 case storyDrafts = 4 case storySources = 5 @@ -123,8 +120,6 @@ private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 { } public struct ApplicationSpecificOrderedItemListCollectionId { - public static let webSearchRecentQueries = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.webSearchRecentQueries.rawValue) - public static let wallpaperSearchRecentQueries = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.wallpaperSearchRecentQueries.rawValue) public static let settingsSearchRecentItems = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.settingsSearchRecentItems.rawValue) public static let localThemes = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.localThemes.rawValue) public static let storyDrafts = applicationSpecificOrderedItemListCollectionId(ApplicationSpecificOrderedItemListCollectionIdValues.storyDrafts.rawValue) diff --git a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index 3e7f2c5484..5f2f06d4d7 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import SwiftSignalKit @@ -58,7 +57,7 @@ public struct WallpaperPresentationOptions: OptionSet { public static let blur = WallpaperPresentationOptions(rawValue: 1 << 1) } -public struct PresentationLocalTheme: PostboxCoding, Equatable { +public struct PresentationLocalTheme: EnginePostboxCoding, Equatable { public let title: String public let resource: LocalFileMediaResource public let resolvedWallpaper: TelegramWallpaper? @@ -69,13 +68,13 @@ public struct PresentationLocalTheme: PostboxCoding, Equatable { self.resolvedWallpaper = resolvedWallpaper } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.title = decoder.decodeStringForKey("title", orElse: "") self.resource = decoder.decodeObjectForKey("resource", decoder: { LocalFileMediaResource(decoder: $0) }) as! LocalFileMediaResource self.resolvedWallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wallpaper")?.value } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.title, forKey: "title") encoder.encodeObject(self.resource, forKey: "resource") if let resolvedWallpaper = self.resolvedWallpaper { @@ -137,12 +136,12 @@ public struct PresentationCloudTheme: Codable, Equatable { } } -public enum PresentationThemeReference: PostboxCoding, Equatable { +public enum PresentationThemeReference: EnginePostboxCoding, Equatable { case builtin(PresentationBuiltinThemeReference) case local(PresentationLocalTheme) case cloud(PresentationCloudTheme) - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { case 0: self = .builtin(PresentationBuiltinThemeReference(rawValue: decoder.decodeInt32ForKey("t", orElse: 0))!) @@ -166,7 +165,7 @@ public enum PresentationThemeReference: PostboxCoding, Equatable { } } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { switch self { case let .builtin(reference): encoder.encodeInt32(0, forKey: "v") @@ -369,8 +368,8 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable { self.force = try container.decodeIfPresent(Bool.self, forKey: "force") ?? false self.trigger = try container.decode(AutomaticThemeSwitchTrigger.self, forKey: "trigger") - if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") { - self.theme = PresentationThemeReference(decoder: PostboxDecoder(buffer: MemoryBuffer(data: themeData.data))) + if let themeData = try container.decodeIfPresent(EngineAdaptedPostboxDecoder.RawObjectData.self, forKey: "theme_v2") { + self.theme = PresentationThemeReference(decoder: EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: themeData.data))) } else { self.theme = .builtin(.night) } @@ -382,7 +381,7 @@ public struct AutomaticThemeSwitchSetting: Codable, Equatable { try container.encode(self.force, forKey: "force") try container.encode(self.trigger, forKey: "trigger") - let themeData = PostboxEncoder().encodeObjectToRawData(self.theme) + let themeData = EnginePostboxEncoder().encodeObjectToRawData(self.theme) try container.encode(themeData, forKey: "theme_v2") } } @@ -435,7 +434,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable { } } -public struct PresentationThemeAccentColor: PostboxCoding, Equatable { +public struct PresentationThemeAccentColor: EnginePostboxCoding, Equatable { public static func == (lhs: PresentationThemeAccentColor, rhs: PresentationThemeAccentColor) -> Bool { return lhs.index == rhs.index && lhs.baseColor == rhs.baseColor && lhs.accentColor == rhs.accentColor && lhs.bubbleColors == rhs.bubbleColors } @@ -476,7 +475,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { self.themeIndex = themeIndex } - public init(decoder: PostboxDecoder) { + public init(decoder: EnginePostboxDecoder) { self.index = decoder.decodeInt32ForKey("i", orElse: -1) self.baseColor = PresentationThemeBaseColor(rawValue: decoder.decodeInt32ForKey("b", orElse: 0)) ?? .blue self.accentColor = decoder.decodeOptionalInt32ForKey("c").flatMap { UInt32(bitPattern: $0) } @@ -500,7 +499,7 @@ public struct PresentationThemeAccentColor: PostboxCoding, Equatable { self.themeIndex = decoder.decodeOptionalInt64ForKey("t") } - public func encode(_ encoder: PostboxEncoder) { + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeInt32(self.index, forKey: "i") encoder.encodeInt32(self.baseColor.rawValue, forKey: "b") if let value = self.accentColor { @@ -617,12 +616,12 @@ public struct PresentationThemeSettings: Codable { public var largeEmoji: Bool public var reduceMotion: Bool - private func wallpaperResources(_ wallpaper: TelegramWallpaper) -> [MediaResourceId] { + private func wallpaperResources(_ wallpaper: TelegramWallpaper) -> [EngineRawMediaResourceId] { switch wallpaper { case let .image(representations, _): return representations.map { $0.resource.id } case let .file(file): - var resources: [MediaResourceId] = [] + var resources: [EngineRawMediaResourceId] = [] resources.append(file.file.resource.id) resources.append(contentsOf: file.file.previewRepresentations.map { $0.resource.id }) return resources @@ -631,8 +630,8 @@ public struct PresentationThemeSettings: Codable { } } - public var relatedResources: [MediaResourceId] { - var resources: [MediaResourceId] = [] + public var relatedResources: [EngineRawMediaResourceId] { + var resources: [EngineRawMediaResourceId] = [] for (_, chatWallpaper) in self.themeSpecificChatWallpapers { resources.append(contentsOf: wallpaperResources(chatWallpaper)) } @@ -673,8 +672,8 @@ public struct PresentationThemeSettings: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) - if let themeData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "t") { - self.theme = PresentationThemeReference(decoder: PostboxDecoder(buffer: MemoryBuffer(data: themeData.data))) + if let themeData = try container.decodeIfPresent(EngineAdaptedPostboxDecoder.RawObjectData.self, forKey: "t") { + self.theme = PresentationThemeReference(decoder: EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: themeData.data))) } else { self.theme = .builtin(.dayClassic) } @@ -695,10 +694,10 @@ public struct PresentationThemeSettings: Codable { } self.themeSpecificChatWallpapers = mappedThemeSpecificChatWallpapers - let themeSpecificAccentColorsDict = try container.decode([DictionaryKey: AdaptedPostboxDecoder.RawObjectData].self, forKey: "themeSpecificAccentColors") + let themeSpecificAccentColorsDict = try container.decode([DictionaryKey: EngineAdaptedPostboxDecoder.RawObjectData].self, forKey: "themeSpecificAccentColors") var mappedThemeSpecificAccentColors: [Int64: PresentationThemeAccentColor] = [:] for (key, value) in themeSpecificAccentColorsDict { - let innerDecoder = PostboxDecoder(buffer: MemoryBuffer(data: value.data)) + let innerDecoder = EnginePostboxDecoder(buffer: EngineMemoryBuffer(data: value.data)) mappedThemeSpecificAccentColors[key.key] = PresentationThemeAccentColor(decoder: innerDecoder) } self.themeSpecificAccentColors = mappedThemeSpecificAccentColors @@ -719,7 +718,7 @@ public struct PresentationThemeSettings: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) - try container.encode(PostboxEncoder().encodeObjectToRawData(self.theme), forKey: "t") + try container.encode(EnginePostboxEncoder().encodeObjectToRawData(self.theme), forKey: "t") var mappedThemePreferredBaseTheme: [Int64: Int64] = [:] for (key, value) in self.themePreferredBaseTheme { @@ -727,9 +726,9 @@ public struct PresentationThemeSettings: Codable { } try container.encode(mappedThemePreferredBaseTheme, forKey: "themePreferredBaseTheme") - var mappedThemeSpecificAccentColors: [DictionaryKey: AdaptedPostboxEncoder.RawObjectData] = [:] + var mappedThemeSpecificAccentColors: [DictionaryKey: EngineAdaptedPostboxEncoder.RawObjectData] = [:] for (key, value) in self.themeSpecificAccentColors { - mappedThemeSpecificAccentColors[DictionaryKey(key)] = PostboxEncoder().encodeObjectToRawData(value) + mappedThemeSpecificAccentColors[DictionaryKey(key)] = EnginePostboxEncoder().encodeObjectToRawData(value) } try container.encode(mappedThemeSpecificAccentColors, forKey: "themeSpecificAccentColors") @@ -802,7 +801,7 @@ public func updatePresentationThemeSettingsInteractively(accountManager: Account } else { currentSettings = PresentationThemeSettings.defaultSettings } - return PreferencesEntry(f(currentSettings)) + return EnginePreferencesEntry(f(currentSettings)) }) } } diff --git a/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift b/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift index 613b51444e..233adf2e18 100644 --- a/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift +++ b/submodules/TelegramUIPreferences/Sources/RenderedTotalUnreadCount.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -8,12 +7,7 @@ public enum RenderedTotalUnreadCountType { case filtered } -public func renderedTotalUnreadCount(inAppNotificationSettings: InAppNotificationSettings, transaction: Transaction) -> (Int32, RenderedTotalUnreadCountType) { - let totalUnreadState = transaction.getTotalUnreadState(groupId: .root) - return renderedTotalUnreadCount(inAppSettings: inAppNotificationSettings, totalUnreadState: totalUnreadState) -} - -public func renderedTotalUnreadCount(inAppSettings: InAppNotificationSettings, totalUnreadState: ChatListTotalUnreadState) -> (Int32, RenderedTotalUnreadCountType) { +public func renderedTotalUnreadCount(inAppSettings: InAppNotificationSettings, totalUnreadState: EngineChatListTotalUnreadState) -> (Int32, RenderedTotalUnreadCountType) { let type: RenderedTotalUnreadCountType switch inAppSettings.totalUnreadCountDisplayStyle { case .filtered: diff --git a/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift b/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift index 99ff7f799e..c20bdfdb9a 100644 --- a/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WatchPresetSettings.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -59,7 +58,7 @@ public func updateWatchPresetSettingsInteractively(accountManager: AccountManage } else { currentSettings = WatchPresetSettings.defaultSettings } - return PreferencesEntry(f(currentSettings)) + return EnginePreferencesEntry(f(currentSettings)) }) } } diff --git a/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift b/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift index 68a6760183..16e25305c8 100644 --- a/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WebBrowserSettings.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -90,7 +89,7 @@ public func updateWebBrowserSettingsInteractively(accountManager: AccountManager } else { currentSettings = WebBrowserSettings.defaultSettings } - return PreferencesEntry(f(currentSettings)) + return EnginePreferencesEntry(f(currentSettings)) }) } } diff --git a/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift b/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift index 09debb4d1c..4267ad5123 100644 --- a/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/WebSearchSettings.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -41,7 +40,7 @@ public func updateWebSearchSettingsInteractively(accountManager: AccountManager< } else { currentSettings = .defaultSettings } - return PreferencesEntry(f(currentSettings)) + return EnginePreferencesEntry(f(currentSettings)) }) } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift index 0da0605cdc..c9f68f5850 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/OverlayUniversalVideoNode.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import SwiftSignalKit import Display import TelegramCore -import Postbox import TelegramAudio import AccountContext import AVKit @@ -41,7 +40,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP private var statusDisposable: Disposable? private var status: MediaPlayerStatus? - public init(context: AccountContext, postbox: Postbox, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) { + public init(context: AccountContext, audioSession: ManagedAudioSession, manager: UniversalVideoManager, content: UniversalVideoContent, shouldBeDismissed: Signal = .single(false), expand: @escaping () -> Void, close: @escaping () -> Void) { self.content = content self.defaultExpand = expand @@ -62,7 +61,7 @@ public final class OverlayUniversalVideoNode: OverlayMediaItemNode, AVPictureInP }, controlsAreShowingUpdated: { value in controlsAreShowingUpdatedImpl?(value) }) - self.videoNode = UniversalVideoNode(context: context, postbox: postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay) + self.videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: audioSession, manager: manager, decoration: decoration, content: content, priority: .overlay) self.decoration = decoration super.init() diff --git a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift index 753ea23172..452764e542 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift @@ -1,6 +1,5 @@ import Foundation import Display -import Postbox import TelegramCore import AccountContext import WebKit @@ -417,23 +416,23 @@ public class YoutubeEmbedStoryboardMediaResource: TelegramMediaResource { self.url = url } - public required init(decoder: PostboxDecoder) { + public required init(decoder: EnginePostboxDecoder) { self.videoId = decoder.decodeStringForKey("v", orElse: "") self.storyboardId = decoder.decodeInt32ForKey("i", orElse: 0) self.url = decoder.decodeStringForKey("u", orElse: "") } - - public func encode(_ encoder: PostboxEncoder) { + + public func encode(_ encoder: EnginePostboxEncoder) { encoder.encodeString(self.videoId, forKey: "v") encoder.encodeInt32(self.storyboardId, forKey: "i") encoder.encodeString(self.url, forKey: "u") } - - public var id: MediaResourceId { - return MediaResourceId(YoutubeEmbedStoryboardMediaResourceId(videoId: self.videoId, storyboardId: self.storyboardId).uniqueId) + + public var id: EngineRawMediaResourceId { + return EngineRawMediaResourceId(YoutubeEmbedStoryboardMediaResourceId(videoId: self.videoId, storyboardId: self.storyboardId).uniqueId) } - - public func isEqual(to: MediaResource) -> Bool { + + public func isEqual(to: EngineRawMediaResource) -> Bool { if let to = to as? YoutubeEmbedStoryboardMediaResource { return self.videoId == to.videoId && self.storyboardId == to.storyboardId && self.url == to.url } else { @@ -442,17 +441,17 @@ public class YoutubeEmbedStoryboardMediaResource: TelegramMediaResource { } } -public final class YoutubeEmbedStoryboardMediaResourceRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived - +public final class YoutubeEmbedStoryboardMediaResourceRepresentation: EngineRawCachedMediaResourceRepresentation { + public let keepDuration: EngineCachedMediaRepresentationKeepDuration = .shortLived + public var uniqueId: String { return "cached" } - + public init() { } - - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { + + public func isEqual(to: EngineRawCachedMediaResourceRepresentation) -> Bool { if to is YoutubeEmbedStoryboardMediaResourceRepresentation { return true } else { @@ -461,14 +460,14 @@ public final class YoutubeEmbedStoryboardMediaResourceRepresentation: CachedMedi } } -public func fetchYoutubeEmbedStoryboardResource(resource: YoutubeEmbedStoryboardMediaResource) -> Signal { +public func fetchYoutubeEmbedStoryboardResource(resource: YoutubeEmbedStoryboardMediaResource) -> Signal { return Signal { subscriber in subscriber.putNext(.reset) let disposable = MetaDisposable() disposable.set(fetchHttpResource(url: resource.url).start(next: { next in if case let .dataPart(_, data, _, complete) = next, complete { - let tempFile = TempBox.shared.tempFile(fileName: "image.jpg") + let tempFile = EngineTempBox.shared.tempFile(fileName: "image.jpg") if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) { subscriber.putNext(.tempFile(tempFile)) subscriber.putCompletion() diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 59f69e97fd..87c742a812 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -512,6 +512,7 @@ public final class OngoingGroupCallContext { enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, + useReferenceImpl: Bool, prioritizeVP8: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, @@ -663,7 +664,8 @@ public final class OngoingGroupCallContext { return encryptionContext.decrypt(message: data, userId: userId) } } - } + }, + useReferenceImpl: useReferenceImpl ) #else self.context = GroupCallThreadLocalContext( @@ -1223,10 +1225,10 @@ public final class OngoingGroupCallContext { } } - public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, prioritizeVP8: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, isConference: Bool, audioIsActiveByDefault: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?, encryptionContext: OngoingGroupCallEncryptionContext?) { + public init(inputDeviceId: String = "", outputDeviceId: String = "", audioSessionActive: Signal, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, enableSystemMute: Bool, useReferenceImpl: Bool, prioritizeVP8: Bool, logPath: String, onMutedSpeechActivityDetected: @escaping (Bool) -> Void, isConference: Bool, audioIsActiveByDefault: Bool, isStream: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice?, encryptionContext: OngoingGroupCallEncryptionContext?) { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, prioritizeVP8: prioritizeVP8, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, isConference: isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: isStream, sharedAudioDevice: sharedAudioDevice, encryptionContext: encryptionContext) + return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, audioSessionActive: audioSessionActive, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, enableSystemMute: enableSystemMute, useReferenceImpl: useReferenceImpl, prioritizeVP8: prioritizeVP8, logPath: logPath, onMutedSpeechActivityDetected: onMutedSpeechActivityDetected, isConference: isConference, audioIsActiveByDefault: audioIsActiveByDefault, isStream: isStream, sharedAudioDevice: sharedAudioDevice, encryptionContext: encryptionContext) }) } diff --git a/submodules/TelegramVoip/Sources/WrappedMediaStreamingContext.swift b/submodules/TelegramVoip/Sources/WrappedMediaStreamingContext.swift index 640e984bf5..27b72f8605 100644 --- a/submodules/TelegramVoip/Sources/WrappedMediaStreamingContext.swift +++ b/submodules/TelegramVoip/Sources/WrappedMediaStreamingContext.swift @@ -3,7 +3,6 @@ import SwiftSignalKit import TgVoipWebrtc import TelegramCore import Network -import Postbox import FFMpegBinding import ManagedFile @@ -275,7 +274,7 @@ public final class ExternalMediaStreamingContext: SharedHLSServerSource { } } - func fileData(id: Int64, range: Range) -> Signal<(TempBoxFile, Range, Int)?, NoError> { + func fileData(id: Int64, range: Range) -> Signal<(EngineTempBoxFile, Range, Int)?, NoError> { return .never() } } @@ -331,7 +330,7 @@ public final class ExternalMediaStreamingContext: SharedHLSServerSource { } } - public func fileData(id: Int64, range: Range) -> Signal<(TempBoxFile, Range, Int)?, NoError> { + public func fileData(id: Int64, range: Range) -> Signal<(EngineTempBoxFile, Range, Int)?, NoError> { return self.impl.signalWith { impl, subscriber in impl.fileData(id: id, range: range).start(next: subscriber.putNext) } @@ -519,7 +518,7 @@ public protocol SharedHLSServerSource: AnyObject { func masterPlaylistData() -> Signal func playlistData(quality: Int) -> Signal func partData(index: Int, quality: Int) -> Signal - func fileData(id: Int64, range: Range) -> Signal<(TempBoxFile, Range, Int)?, NoError> + func fileData(id: Int64, range: Range) -> Signal<(EngineTempBoxFile, Range, Int)?, NoError> func arbitraryFileData(path: String) -> Signal<(data: Data, contentType: String)?, NoError> } @@ -778,11 +777,11 @@ public final class SharedHLSServer { } if let result { - let sourceTempFile = TempBox.shared.tempFile(fileName: "part.mp4") - let tempFile = TempBox.shared.tempFile(fileName: "part.ts") + let sourceTempFile = EngineTempBox.shared.tempFile(fileName: "part.mp4") + let tempFile = EngineTempBox.shared.tempFile(fileName: "part.ts") defer { - TempBox.shared.dispose(sourceTempFile) - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(sourceTempFile) + EngineTempBox.shared.dispose(tempFile) } guard let _ = try? result.write(to: URL(fileURLWithPath: sourceTempFile.path)) else { @@ -875,7 +874,7 @@ public final class SharedHLSServer { }) } - private static func sendRemainingFileRange(queue: Queue, connection: NWConnection, tempFile: TempBoxFile, managedFile: ManagedFile, remainingRange: Range, fileSize: Int) -> Void { + private static func sendRemainingFileRange(queue: Queue, connection: NWConnection, tempFile: EngineTempBoxFile, managedFile: ManagedFile, remainingRange: Range, fileSize: Int) -> Void { let blockSize = 256 * 1024 let clippedLowerBound = min(remainingRange.lowerBound, fileSize) @@ -883,7 +882,7 @@ public final class SharedHLSServer { clippedUpperBound = min(clippedUpperBound, clippedLowerBound + blockSize) if clippedUpperBound == clippedLowerBound { - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) connection.cancel() } else { let _ = managedFile.seek(position: Int64(clippedLowerBound)) @@ -895,7 +894,7 @@ public final class SharedHLSServer { if let error { Logger.shared.log("SharedHLSServer", "Failed to send response: \(error)") connection.cancel() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) } else { sendRemainingFileRange(queue: queue, connection: connection, tempFile: tempFile, managedFile: managedFile, remainingRange: nextRange, fileSize: fileSize) } @@ -904,12 +903,12 @@ public final class SharedHLSServer { } } - private func sendResponseFileAndClose(connection: NWConnection, file: TempBoxFile, fileRange: Range, range: Range, totalSize: Int) { + private func sendResponseFileAndClose(connection: NWConnection, file: EngineTempBoxFile, fileRange: Range, range: Range, totalSize: Int) { let queue = self.queue guard let managedFile = ManagedFile(queue: nil, path: file.path, mode: .read), let fileSize = managedFile.getSize() else { self.sendErrorAndClose(connection: connection, error: .internalServerError) - TempBox.shared.dispose(file) + EngineTempBox.shared.dispose(file) return } diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/CachedChannelAdmins.swift b/submodules/TemporaryCachedPeerDataManager/Sources/CachedChannelAdmins.swift index 931d93ab03..a09fb7db18 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/CachedChannelAdmins.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/CachedChannelAdmins.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit @@ -43,16 +42,16 @@ public enum CachedChannelAdminRank: Codable, Equatable { public final class CachedChannelAdminRanks: Codable { private struct DictionaryKey: Codable, Hashable { - var key: PeerId + var key: EnginePeer.Id - init(_ key: PeerId) { + init(_ key: EnginePeer.Id) { self.key = key } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) - self.key = PeerId(try container.decode(Int64.self, forKey: "k")) + self.key = EnginePeer.Id(try container.decode(Int64.self, forKey: "k")) } func encode(to encoder: Encoder) throws { @@ -61,18 +60,18 @@ public final class CachedChannelAdminRanks: Codable { try container.encode(self.key.toInt64(), forKey: "k") } } - - public let ranks: [PeerId: CachedChannelAdminRank] - - public init(ranks: [PeerId: CachedChannelAdminRank]) { + + public let ranks: [EnginePeer.Id: CachedChannelAdminRank] + + public init(ranks: [EnginePeer.Id: CachedChannelAdminRank]) { self.ranks = ranks } - + public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) let dict = try container.decode([DictionaryKey: CachedChannelAdminRank].self, forKey: "ranks") - var mappedDict: [PeerId: CachedChannelAdminRank] = [:] + var mappedDict: [EnginePeer.Id: CachedChannelAdminRank] = [:] for (key, value) in dict { mappedDict[key.key] = value } @@ -89,17 +88,17 @@ public final class CachedChannelAdminRanks: Codable { try container.encode(mappedDict, forKey: "ranks") } - public static func cacheKey(peerId: PeerId) -> ValueBoxKey { - let key = ValueBoxKey(length: 8) + public static func cacheKey(peerId: EnginePeer.Id) -> EngineDataBuffer { + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: peerId.toInt64()) return key } } -public func cachedChannelAdminRanksEntryId(peerId: PeerId) -> ItemCacheEntryId { - return ItemCacheEntryId(collectionId: 100, key: CachedChannelAdminRanks.cacheKey(peerId: peerId)) +public func cachedChannelAdminRanksEntryId(peerId: EnginePeer.Id) -> EngineItemCacheEntryId { + return EngineItemCacheEntryId(collectionId: 100, key: CachedChannelAdminRanks.cacheKey(peerId: peerId)) } -func updateCachedChannelAdminRanks(engine: TelegramEngine, peerId: PeerId, ranks: Dictionary) -> Signal { +func updateCachedChannelAdminRanks(engine: TelegramEngine, peerId: EnginePeer.Id, ranks: Dictionary) -> Signal { return engine.itemCache.put(collectionId: 100, id: CachedChannelAdminRanks.cacheKey(peerId: peerId), item: CachedChannelAdminRanks(ranks: ranks)) } diff --git a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift index 9bc2c0c03c..61bfacc05c 100644 --- a/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift +++ b/submodules/TemporaryCachedPeerDataManager/Sources/PeerChannelMemberCategoriesContextsManager.swift @@ -327,43 +327,43 @@ public final class PeerChannelMemberCategoriesContextsManager { } } - public func recent(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, count: Int32? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + public func recent(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, count: Int32? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { let key: PeerChannelMemberContextKey if let searchQuery = searchQuery { key = .recentSearch(searchQuery) } else { key = .recent } - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, count: count, updated: updated) + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, count: count, updated: updated) } - - public func mentions(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: MessageId?, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + + public func mentions(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, threadMessageId: MessageId?, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { let key: PeerChannelMemberContextKey = .mentions(threadId: threadMessageId, query: searchQuery) - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated) } - - public func admins(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) + + public func admins(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated) } - - public func contacts(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .contacts(searchQuery), requestUpdate: true, updated: updated) + + public func contacts(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .contacts(searchQuery), requestUpdate: true, updated: updated) } - - public func bots(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .bots(searchQuery), requestUpdate: true, updated: updated) + + public func bots(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .bots(searchQuery), requestUpdate: true, updated: updated) } - - public func restricted(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restricted(searchQuery), requestUpdate: true, updated: updated) + + public func restricted(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .restricted(searchQuery), requestUpdate: true, updated: updated) } - - public func banned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .banned(searchQuery), requestUpdate: true, updated: updated) + + public func banned(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .banned(searchQuery), requestUpdate: true, updated: updated) } - - public func restrictedAndBanned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { - return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated) + + public func restrictedAndBanned(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) { + return self.getContext(engine: engine, postbox: engine.account.postbox, network: engine.account.network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated) } public func updateMemberBannedRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChatBannedRights?) -> Signal { @@ -447,21 +447,27 @@ public final class PeerChannelMemberCategoriesContextsManager { } } - public func join(engine: TelegramEngine, peerId: PeerId, hash: String?) -> Signal { + public func join(engine: TelegramEngine, peerId: PeerId, hash: String?) -> Signal { return engine.peers.joinChannel(peerId: peerId, hash: hash) |> deliverOnMainQueue |> beforeNext { [weak self] result in - if let strongSelf = self, let updated = result { - strongSelf.impl.with { impl in + if let self { + self.impl.with { impl in for (contextPeerId, context) in impl.contexts { if peerId == contextPeerId { - context.replayUpdates([(nil, updated, nil)]) + switch result { + case let .joined(participant): + if let participant { + context.replayUpdates([(nil, participant, nil)]) + } + case .webView: + break + } } } } } } - |> ignoreValues } public func addMember(engine: TelegramEngine, peerId: PeerId, memberId: PeerId) -> Signal { @@ -569,11 +575,11 @@ public final class PeerChannelMemberCategoriesContextsManager { |> runOn(Queue.mainQueue()) } - public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<(total: Int32, recent: Int32), NoError> { + public func recentOnlineSmall(engine: TelegramEngine, accountPeerId: PeerId, peerId: PeerId) -> Signal<(total: Int32, recent: Int32), NoError> { return Signal { [weak self] subscriber in var previousIds: Set? let statusesDisposable = MetaDisposable() - let disposableAndControl = self?.recent(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { state in + let disposableAndControl = self?.recent(engine: engine, accountPeerId: accountPeerId, peerId: peerId, updated: { state in var idList: [PeerId] = [] for item in state.list { idList.append(item.peer.id) diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index 61dde36ecb..9de9c1b5b0 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import TelegramPresentationData import Emoji @@ -317,9 +316,9 @@ public func textAttributedStringForStateText(context: AnyObject, stateText: NSAt } public final class ChatTextInputTextMentionAttribute: NSObject { - public let peerId: PeerId + public let peerId: EnginePeer.Id - public init(peerId: PeerId) { + public init(peerId: EnginePeer.Id) { self.peerId = peerId super.init() @@ -434,13 +433,13 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { case dice } - public let interactivelySelectedFromPackId: ItemCollectionId? + public let interactivelySelectedFromPackId: EngineItemCollectionId? public let fileId: Int64 public let file: TelegramMediaFile? public let custom: Custom? public let enableAnimation: Bool - public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil, enableAnimation: Bool = true) { + public init(interactivelySelectedFromPackId: EngineItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil, enableAnimation: Bool = true) { self.interactivelySelectedFromPackId = interactivelySelectedFromPackId self.fileId = fileId self.file = file @@ -452,7 +451,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.interactivelySelectedFromPackId = try container.decodeIfPresent(ItemCollectionId.self, forKey: .interactivelySelectedFromPackId) + self.interactivelySelectedFromPackId = try container.decodeIfPresent(EngineItemCollectionId.self, forKey: .interactivelySelectedFromPackId) self.fileId = try container.decode(Int64.self, forKey: .fileId) self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) self.custom = nil @@ -604,17 +603,11 @@ private func refreshTextMentions(text: NSString, initialAttributedText: NSAttrib } } -private let textUrlEdgeCharacters: CharacterSet = { - var set: CharacterSet = .alphanumerics - set.formUnion(.symbols) - set.formUnion(.punctuationCharacters) - set.remove("(") - set.remove(")") - return set -}() +private let textUrlEdgeCharacters = CharacterSet.alphanumerics private let textUrlCharacters: CharacterSet = { - var set: CharacterSet = textUrlEdgeCharacters + var set = textUrlEdgeCharacters + set.insert(charactersIn: "@_") set.formUnion(.whitespacesAndNewlines) return set }() diff --git a/submodules/TextFormat/Sources/CustomEmojiMarkdownMarker.swift b/submodules/TextFormat/Sources/CustomEmojiMarkdownMarker.swift new file mode 100644 index 0000000000..18b0800c30 --- /dev/null +++ b/submodules/TextFormat/Sources/CustomEmojiMarkdownMarker.swift @@ -0,0 +1,84 @@ +import Foundation + +/// The private markdown-link URL that carries a custom emoji's file id between +/// the send path and the rich-message renderer. Shared by the forward +/// (compose/send) path and the reverse (copy/edit) converters so the encode and +/// decode cannot drift. Format: a markdown link `[](tg://emoji?id=)`. +public func customEmojiMarkdownURL(fileId: Int64) -> String { + return "tg://emoji?id=\(fileId)" +} + +/// Backslash-escapes only the characters that would break a marker link's +/// display text (the `alt`): backslash, the link-text brackets, and newline. +/// Minimal by design — the forward CommonMark parser unescapes these, so the +/// alt round-trips. Shared by every site that emits a `[](tg://emoji?id=…)` +/// marker so the escaping cannot drift between encoders. +public func escapeCustomEmojiMarkdownAlt(_ string: String) -> String { + var result = "" + result.reserveCapacity(string.count) + for character in string { + switch character { + case "\\", "[", "]", "\n": + result.append("\\") + result.append(character) + default: + result.append(character) + } + } + return result +} + +/// Parses the file id out of a `tg://emoji?id=` marker URL. +/// Returns nil for any other URL (ordinary links flow through unchanged). +public func parseCustomEmojiFileId(fromMarkdownURL url: String) -> Int64? { + let prefix = "tg://emoji?id=" + guard url.hasPrefix(prefix) else { + return nil + } + return Int64(url.dropFirst(prefix.count)) +} + +/// Regex matching an emitted marker link: `[](tg://emoji?id=)`. +/// `alt` is captured as group 1 (any run of non-`]` chars), the file id as group 2. +private let customEmojiMarkerRegex = try? NSRegularExpression( + pattern: "\\[([^\\]]*)\\]\\(tg://emoji\\?id=(-?\\d+)\\)", + options: [] +) + +/// Reverse of the forward normalization: takes reconstructed markdown source +/// (e.g. from `markdownStringFromInstantPage`) and returns an attributed string +/// where each `tg://emoji?id=` marker link has been turned back into a live +/// `ChatTextInputAttributes.customEmoji` run (the alt text carrying a +/// `ChatTextInputTextCustomEmojiAttribute`). Everything else stays verbatim +/// markdown text. Used to populate the edit compose field so it shows the +/// animated emoji; on re-save the forward path reads the attribute's fileId back. +/// +/// `file` is left nil — the renderer resolves the emoji lazily from `fileId`, +/// and the send path only needs the fileId. Known limitation: an alt containing +/// a literal `]` is not matched (emoji alts do not contain brackets). +public func chatInputTextWithReattachedCustomEmoji(_ markdown: String) -> NSAttributedString { + let result = NSMutableAttributedString(string: markdown) + guard let regex = customEmojiMarkerRegex else { + return result + } + let matches = regex.matches(in: markdown, options: [], range: NSRange(markdown.startIndex..., in: markdown)) + // Replace from last match to first so the earlier NSRanges stay valid. + for match in matches.reversed() { + guard match.numberOfRanges == 3 else { + continue + } + guard let altRange = Range(match.range(at: 1), in: markdown), + let idRange = Range(match.range(at: 2), in: markdown), + let fileId = Int64(markdown[idRange]) else { + continue + } + let alt = String(markdown[altRange]) + // The attribute must ride on at least one character; use a space if the + // alt was emitted empty (selection-copy with no alt available). + let displayText = alt.isEmpty ? " " : alt + let attribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil) + let replacement = NSAttributedString(string: displayText, attributes: [ChatTextInputAttributes.customEmoji: attribute]) + result.replaceCharacters(in: match.range, with: replacement) + } + return result +} diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index 35cd855bfb..c84cd84767 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import Display import libprisma @@ -83,7 +82,51 @@ public func chatInputStateStringWithAppliedEntities(_ text: String, entities: [M private let syntaxHighlighter = Syntaxer() -public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], strings: PresentationStrings? = nil, dateTimeFormat: PresentationDateTimeFormat? = nil, baseColor: UIColor, linkColor: UIColor, baseQuoteTintColor: UIColor? = nil, baseQuoteSecondaryTintColor: UIColor? = nil, baseQuoteTertiaryTintColor: UIColor? = nil, codeBlockTitleColor: UIColor? = nil, codeBlockAccentColor: UIColor? = nil, codeBlockBackgroundColor: UIColor? = nil, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: Message?, entityFiles: [MediaId: TelegramMediaFile] = [:], adjustQuoteFontSize: Bool = false, cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil, paragraphAlignment: NSTextAlignment? = nil) -> NSAttributedString { +private func isValidMessageSyntaxHighlightRange(_ range: Range, expectedLength: Int) -> Bool { + return range.lowerBound >= 0 && range.upperBound <= expectedLength && range.lowerBound < range.upperBound +} + +private func validatedCachedMessageSyntaxHighlight(_ highlight: MessageSyntaxHighlight, expectedLength: Int, language: String) -> MessageSyntaxHighlight? { + for entity in highlight.entities { + if !isValidMessageSyntaxHighlightRange(entity.range, expectedLength: expectedLength) { + return nil + } + } + return highlight +} + +private func generateMessageSyntaxHighlight(spec: CachedMessageSyntaxHighlight.Spec, theme: SyntaxterTheme) -> MessageSyntaxHighlight { + let expectedLength = (spec.text as NSString).length + guard let syntaxHighlighter else { + return MessageSyntaxHighlight(entities: []) + } + guard let highlightedString = syntaxHighlighter.syntax(spec.text, language: spec.language, theme: theme) else { + return MessageSyntaxHighlight(entities: []) + } + guard highlightedString.length == expectedLength else { + return MessageSyntaxHighlight(entities: []) + } + + var entities: [MessageSyntaxHighlight.Entity] = [] + var hasInvalidRange = false + highlightedString.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: highlightedString.length), using: { value, subRange, stop in + let range = subRange.lowerBound ..< subRange.upperBound + if !isValidMessageSyntaxHighlightRange(range, expectedLength: expectedLength) { + hasInvalidRange = true + stop.pointee = true + return + } + if let value = value as? UIColor, value != .black { + entities.append(MessageSyntaxHighlight.Entity(color: Int32(bitPattern: value.rgb), range: range)) + } + }) + if hasInvalidRange { + return MessageSyntaxHighlight(entities: []) + } + return MessageSyntaxHighlight(entities: entities) +} + +public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], strings: PresentationStrings? = nil, dateTimeFormat: PresentationDateTimeFormat? = nil, baseColor: UIColor, linkColor: UIColor, baseQuoteTintColor: UIColor? = nil, baseQuoteSecondaryTintColor: UIColor? = nil, baseQuoteTertiaryTintColor: UIColor? = nil, codeBlockTitleColor: UIColor? = nil, codeBlockAccentColor: UIColor? = nil, codeBlockBackgroundColor: UIColor? = nil, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false, message: EngineRawMessage?, entityFiles: [EngineMedia.Id: TelegramMediaFile] = [:], adjustQuoteFontSize: Bool = false, cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil, paragraphAlignment: NSTextAlignment? = nil) -> NSAttributedString { let baseQuoteTintColor = baseQuoteTintColor ?? baseColor var nsString: NSString? @@ -336,7 +379,7 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti addFontAttributes(range, .smaller) } case let .CustomEmoji(_, fileId): - let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + let mediaId = EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: fileId) var emojiFile: TelegramMediaFile? if let file = message?.associatedMedia[mediaId] as? TelegramMediaFile { emojiFile = file @@ -399,8 +442,10 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti let codeText = (string.string as NSString).substring(with: range) if let cachedMessageSyntaxHighlight, let entry = cachedMessageSyntaxHighlight.values[CachedMessageSyntaxHighlight.Spec(language: language, text: codeText)] { - for entity in entry.entities { - string.addAttribute(.foregroundColor, value: UIColor(rgb: UInt32(bitPattern: entity.color)), range: NSRange(location: range.location + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)) + if let validatedEntry = validatedCachedMessageSyntaxHighlight(entry, expectedLength: range.length, language: language) { + for entity in validatedEntry.entities { + string.addAttribute(.foregroundColor, value: UIColor(rgb: UInt32(bitPattern: entity.color)), range: NSRange(location: range.location + entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)) + } } } }) @@ -588,8 +633,18 @@ public func extractMessageSyntaxHighlightSpecs(text: String, entities: [MessageT private let internalFixedCodeFont = Font.regular(17.0) public func asyncUpdateMessageSyntaxHighlight(engine: TelegramEngine, messageId: EngineMessage.Id, current: CachedMessageSyntaxHighlight?, specs: [CachedMessageSyntaxHighlight.Spec]) -> Signal { - if let current, !specs.contains(where: { current.values[$0] == nil }) { - return .complete() + if let current { + var hasMissingOrInvalidSpec = false + for spec in specs { + let expectedLength = (spec.text as NSString).length + guard let value = current.values[spec], validatedCachedMessageSyntaxHighlight(value, expectedLength: expectedLength, language: spec.language) != nil else { + hasMissingOrInvalidSpec = true + break + } + } + if !hasMissingOrInvalidSpec { + return .complete() + } } return Signal { subscriber in @@ -598,26 +653,15 @@ public func asyncUpdateMessageSyntaxHighlight(engine: TelegramEngine, messageId: let theme = SyntaxterTheme(dark: false, textColor: .black, textFont: internalFixedCodeFont, italicFont: internalFixedCodeFont, mediumFont: internalFixedCodeFont) for spec in specs { + let expectedLength = (spec.text as NSString).length if let value = current?.values[spec] { - updated[spec] = value - } else { - var entities: [MessageSyntaxHighlight.Entity] = [] - - if let syntaxHighlighter { - if let highlightedString = syntaxHighlighter.syntax(spec.text, language: spec.language, theme: theme) { - highlightedString.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: highlightedString.length), using: { value, subRange, _ in - if let value = value as? UIColor, value != .black { - entities.append(MessageSyntaxHighlight.Entity(color: Int32(bitPattern: value.rgb), range: subRange.lowerBound ..< subRange.upperBound)) - } - }) - } - } - - updated[spec] = MessageSyntaxHighlight(entities: entities) + updated[spec] = validatedCachedMessageSyntaxHighlight(value, expectedLength: expectedLength, language: spec.language) ?? MessageSyntaxHighlight(entities: []) + } else if let theme { + updated[spec] = generateMessageSyntaxHighlight(spec: spec, theme: theme) } } - if let entry = CodableEntry(CachedMessageSyntaxHighlight(values: updated)) { + if let entry = EngineCodableEntry(CachedMessageSyntaxHighlight(values: updated)) { return engine.messages.storeLocallyDerivedData(messageId: messageId, data: ["code": entry]).start(completed: { subscriber.putCompletion() }) @@ -629,8 +673,18 @@ public func asyncUpdateMessageSyntaxHighlight(engine: TelegramEngine, messageId: } public func asyncStanaloneSyntaxHighlight(current: CachedMessageSyntaxHighlight?, specs: [CachedMessageSyntaxHighlight.Spec]) -> Signal { - if let current, !specs.contains(where: { current.values[$0] == nil }) { - return .single(current) + if let current { + var hasMissingOrInvalidSpec = false + for spec in specs { + let expectedLength = (spec.text as NSString).length + guard let value = current.values[spec], validatedCachedMessageSyntaxHighlight(value, expectedLength: expectedLength, language: spec.language) != nil else { + hasMissingOrInvalidSpec = true + break + } + } + if !hasMissingOrInvalidSpec { + return .single(current) + } } return Signal { subscriber in @@ -639,22 +693,11 @@ public func asyncStanaloneSyntaxHighlight(current: CachedMessageSyntaxHighlight? let theme = SyntaxterTheme(dark: false, textColor: .black, textFont: internalFixedCodeFont, italicFont: internalFixedCodeFont, mediumFont: internalFixedCodeFont) for spec in specs { + let expectedLength = (spec.text as NSString).length if let value = current?.values[spec] { - updated[spec] = value - } else { - var entities: [MessageSyntaxHighlight.Entity] = [] - - if let syntaxHighlighter { - if let highlightedString = syntaxHighlighter.syntax(spec.text, language: spec.language, theme: theme) { - highlightedString.enumerateAttribute(.foregroundColor, in: NSRange(location: 0, length: highlightedString.length), using: { value, subRange, _ in - if let value = value as? UIColor, value != .black { - entities.append(MessageSyntaxHighlight.Entity(color: Int32(bitPattern: value.rgb), range: subRange.lowerBound ..< subRange.upperBound)) - } - }) - } - } - - updated[spec] = MessageSyntaxHighlight(entities: entities) + updated[spec] = validatedCachedMessageSyntaxHighlight(value, expectedLength: expectedLength, language: spec.language) ?? MessageSyntaxHighlight(entities: []) + } else if let theme { + updated[spec] = generateMessageSyntaxHighlight(spec: spec, theme: theme) } } diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index a779d44732..f2a0ffffeb 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -531,7 +531,13 @@ public final class TextSelectionNode: ASDisplayNode { }) } - return adjustedRange + func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange { + let location = min(max(range.location, 0), length) + let upperBound = min(max(location, range.location + range.length), length) + return NSRange(location: location, length: upperBound - location) + } + + return normalizedSelectionRange(adjustedRange, length: attributedString.length) } private func convertSelectionFromOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange { @@ -646,7 +652,7 @@ public final class TextSelectionNode: ASDisplayNode { highlightOverlay.innerRadius = 2.0 highlightOverlay.outerRadius = 2.0 highlightOverlay.inset = 1.0 - highlightOverlay.useModernPathCalculation = false + highlightOverlay.useModernPathCalculation = true self.highlightOverlay = highlightOverlay self.highlightAreaNode.addSubnode(highlightOverlay) diff --git a/submodules/TgVoipWebrtc/BUILD b/submodules/TgVoipWebrtc/BUILD index 14cc452523..c5e0acf5d0 100644 --- a/submodules/TgVoipWebrtc/BUILD +++ b/submodules/TgVoipWebrtc/BUILD @@ -7,6 +7,11 @@ config_setting( }, ) +config_setting( + name = "is_macos", + constraint_values = ["@platforms//os:macos"], +) + optimization_flags = select({ ":debug_build": [ ], @@ -107,6 +112,7 @@ sources = glob([ "tgcalls/tgcalls/group/AudioStreamingPartPersistentDecoder.cpp", "tgcalls/tgcalls/group/AVIOContextImpl.cpp", "tgcalls/tgcalls/group/GroupInstanceCustomImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceReferenceImpl.cpp", "tgcalls/tgcalls/group/GroupJoinPayloadInternal.cpp", "tgcalls/tgcalls/group/GroupNetworkManager.cpp", "tgcalls/tgcalls/group/StreamingMediaContext.cpp", @@ -116,6 +122,7 @@ sources = glob([ "tgcalls/tgcalls/v2/DirectNetworkingImpl.cpp", "tgcalls/tgcalls/v2/ExternalSignalingConnection.cpp", "tgcalls/tgcalls/v2/InstanceV2Impl.cpp", + "tgcalls/tgcalls/v2/InstanceV2CompatImpl.cpp", "tgcalls/tgcalls/v2/InstanceV2ReferenceImpl.cpp", "tgcalls/tgcalls/v2/NativeNetworkingImpl.cpp", "tgcalls/tgcalls/v2/RawTcpSocket.cpp", @@ -125,6 +132,7 @@ sources = glob([ "tgcalls/tgcalls/v2/SignalingConnection.cpp", "tgcalls/tgcalls/v2/SignalingEncryption.cpp", "tgcalls/tgcalls/v2/SignalingSctpConnection.cpp", + "tgcalls/tgcalls/v2/CustomDcSctpSocket.cpp", ] objc_library( @@ -189,3 +197,105 @@ objc_library( "//visibility:public", ], ) + +# Pure C++ core library for non-iOS targets (e.g. CLI tools). +# Uses FakeInterface instead of darwin platform files. +cc_library( + name = "tgcalls_core", + srcs = glob([ + "tgcalls/tgcalls/**/*.h", + "tgcalls/tgcalls/**/*.hpp", + "tgcalls/tgcalls/platform/fake/**/*.cpp", + "tgcalls/tgcalls/third-party/**/*.cpp", + "tgcalls/tgcalls/utils/**/*.cpp", + ], allow_empty=True, exclude = [ + "tgcalls/tgcalls/legacy/**", + "tgcalls/tgcalls/platform/tdesktop/**", + "tgcalls/tgcalls/platform/android/**", + "tgcalls/tgcalls/platform/windows/**", + "tgcalls/tgcalls/platform/uwp/**", + "tgcalls/tgcalls/platform/darwin/**", + "tgcalls/tgcalls/desktop_capturer/**", + ]) + [ + "tgcalls/tgcalls/Manager.cpp", + "tgcalls/tgcalls/MediaManager.cpp", + "tgcalls/tgcalls/AudioDeviceHelper.cpp", + "tgcalls/tgcalls/ChannelManager.cpp", + "tgcalls/tgcalls/CodecSelectHelper.cpp", + "tgcalls/tgcalls/CryptoHelper.cpp", + "tgcalls/tgcalls/EncryptedConnection.cpp", + "tgcalls/tgcalls/FakeAudioDeviceModule.cpp", + "tgcalls/tgcalls/FakeVideoTrackSource.cpp", + "tgcalls/tgcalls/FieldTrialsConfig.cpp", + "tgcalls/tgcalls/Instance.cpp", + "tgcalls/tgcalls/InstanceImpl.cpp", + "tgcalls/tgcalls/LogSinkImpl.cpp", + "tgcalls/tgcalls/Message.cpp", + "tgcalls/tgcalls/NetworkManager.cpp", + "tgcalls/tgcalls/SctpDataChannelProviderInterfaceImpl.cpp", + "tgcalls/tgcalls/StaticThreads.cpp", + "tgcalls/tgcalls/ThreadLocalObject.cpp", + "tgcalls/tgcalls/TurnCustomizerImpl.cpp", + "tgcalls/tgcalls/VideoCaptureInterface.cpp", + "tgcalls/tgcalls/VideoCaptureInterfaceImpl.cpp", + + "tgcalls/tgcalls/group/AudioStreamingPart.cpp", + "tgcalls/tgcalls/group/AudioStreamingPartInternal.cpp", + "tgcalls/tgcalls/group/AudioStreamingPartPersistentDecoder.cpp", + "tgcalls/tgcalls/group/AVIOContextImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceCustomImpl.cpp", + "tgcalls/tgcalls/group/GroupInstanceReferenceImpl.cpp", + "tgcalls/tgcalls/group/GroupJoinPayloadInternal.cpp", + "tgcalls/tgcalls/group/GroupNetworkManager.cpp", + "tgcalls/tgcalls/group/StreamingMediaContext.cpp", + "tgcalls/tgcalls/group/VideoStreamingPart.cpp", + + "tgcalls/tgcalls/v2/ContentNegotiation.cpp", + "tgcalls/tgcalls/v2/DirectNetworkingImpl.cpp", + "tgcalls/tgcalls/v2/ExternalSignalingConnection.cpp", + "tgcalls/tgcalls/v2/InstanceV2Impl.cpp", + "tgcalls/tgcalls/v2/InstanceV2CompatImpl.cpp", + "tgcalls/tgcalls/v2/InstanceV2ReferenceImpl.cpp", + "tgcalls/tgcalls/v2/NativeNetworkingImpl.cpp", + "tgcalls/tgcalls/v2/RawTcpSocket.cpp", + "tgcalls/tgcalls/v2/ReflectorPort.cpp", + "tgcalls/tgcalls/v2/ReflectorRelayPortFactory.cpp", + "tgcalls/tgcalls/v2/Signaling.cpp", + "tgcalls/tgcalls/v2/SignalingConnection.cpp", + "tgcalls/tgcalls/v2/SignalingEncryption.cpp", + "tgcalls/tgcalls/v2/SignalingSctpConnection.cpp", + "tgcalls/tgcalls/v2/SignalingTranslator.cpp", + "tgcalls/tgcalls/v2/CustomDcSctpSocket.cpp", + ], + copts = [ + "-I{}/tgcalls/tgcalls".format(package_name()), + "-Ithird-party/webrtc/webrtc", + "-Ithird-party/webrtc/dependencies", + "-Ithird-party/webrtc/absl", + "-Ithird-party/libyuv", + "-DRTC_ENABLE_VP9", + "-DTGVOIP_NAMESPACE=tgvoip_webrtc", + "-std=c++17", + "-w", + ] + select({ + "@platforms//os:linux": ["-DWEBRTC_LINUX", "-DWEBRTC_POSIX"], + "//conditions:default": ["-DWEBRTC_MAC", "-DWEBRTC_POSIX"], + }) + optimization_flags, + deps = [ + "//third-party/webrtc", + "//third-party/boringssl:crypto", + "//third-party/boringssl:ssl", + "//third-party/ogg:ogg", + "//third-party/opusfile:opusfile", + "//submodules/ffmpeg:ffmpeg", + "//third-party/rnnoise:rnnoise", + "//third-party/libyuv", + ] + select({ + ":is_macos": ["//third-party/webrtc:webrtc_platform_helpers"], + "//conditions:default": [], + }), + linkopts = ["-lz"], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TgVoipWebrtc/CLAUDE.md b/submodules/TgVoipWebrtc/CLAUDE.md new file mode 100644 index 0000000000..27994a67c2 --- /dev/null +++ b/submodules/TgVoipWebrtc/CLAUDE.md @@ -0,0 +1,298 @@ +# tgcalls Library + +The tgcalls VoIP library source. See the root `CLAUDE.md` for build instructions and the project overview. + +## macOS Build Support + +This repo has been patched to support native macOS arm64 builds (`darwin_arm64` CPU) in addition to the original iOS targets. Changes made: +- `third-party/webrtc/BUILD` — added `@platforms//os:linux` to `arch_specific_cflags` select (fixes macOS getting Linux flags via `//conditions:default`); moved `cocoa_threading.mm` from `cc_library` to `webrtc_platform_helpers` `objc_library` (Bazel 8 rejects `.mm` in `cc_library`); replaced UIKit with AppKit for macOS +- `third-party/openh264/BUILD` — added `//conditions:default` to `select()` statements +- `third-party/webrtc/absl/absl/base/attributes.h` — disabled `ABSL_ATTRIBUTE_LIFETIME_BOUND` (newer Xcode clang rejects it on void-returning functions) +- 8 third-party BUILD files + 8 build shell scripts — added `darwin_arm64 -> macos_arm64` architecture support (opus, libvpx, ffmpeg, dav1d, mozjpeg, webp, libjxl, td) + +## Linux Build Support + +The repo supports native Linux arm64 and x86_64 builds. Key changes from the iOS/macOS-only baseline: +- `.bazelrc` — Apple toolchain settings under `build:macos`, Linux uses default CC toolchain via `build:linux` (auto-selected by `--enable_platform_specific_config`) +- `build-system/BUILD` — `linux_arm64` and `linux_x86_64` config_settings +- `objc_library` → `cc_library` conversions for pure C/C++ targets (ogg, opusfile, rnnoise, opus, libvpx, dav1d, ffmpeg wrappers, WebRTC main target) +- WebRTC BUILD — platform flags via `select()` (`-DWEBRTC_LINUX` vs `-DWEBRTC_MAC`), stdlib task queue instead of GCD on Linux, macOS-only sources excluded +- Third-party genrule build scripts — Linux architecture cases added (libvpx, dav1d, ffmpeg), system cmake/meson/ninja used instead of downloaded macOS binaries +- BoringSSL — `_Generic` C11 guarded for C++ mode (GCC compatibility) +- tgcalls headers — `#include ` added for GCC 15 strictness + +## SCTP Signaling + +### Writable Gate (role-based handshake ordering) + +tgcalls uses a custom SCTP association (via dc-sctp) over the signaling channel for reliable message delivery. `SignalingSctpConnection` wraps `DcSctpTransport` with a `SignalingPacketTransport` shim. + +The SCTP handshake is ordered using DcSctpTransport's writable gate (`MaybeConnectSocket()`), mirroring how WebRTC PeerConnection uses DTLS writable state to control SCTP connection timing: + +- **Caller** (`isOutgoing=true`): `SignalingPacketTransport` starts writable → `Connect()` fires immediately → sends INIT +- **Callee** (`isOutgoing=false`): starts not-writable → `Connect()` deferred → on first `receiveExternal()`, `setWritable(true)` fires `SignalWritableState` → `MaybeConnectSocket()` → `Connect()` + +The callee's `Connect()` and processing of the caller's INIT happen synchronously within the same `BlockingCall` on the network thread (RFC 4960 §5.2.1 simultaneous-open). + +Key files: +- `SignalingSctpConnection.cpp` — `SignalingPacketTransport` writable state, `setWritable()`, constructor takes `isInitiator` +- `InstanceV2Impl.cpp` / `InstanceV2ReferenceImpl.cpp` — pass `_encryptionKey.isOutgoing` as `isInitiator` +- `third-party/webrtc/webrtc/media/sctp/dcsctp_transport.cc:662-667` — `MaybeConnectSocket()` gate (unmodified) + +### Timer Tuning (CustomDcSctpSocket) + +WebRTC's stock `DcSctpSocket` has a bug: `max_timer_backoff_duration` is wired to the T3-rtx (data retransmission) timer but **not** to the t1_init and t1_cookie (handshake) timers. The handshake timers use unlimited exponential backoff (1000, 2000, 4000, 8000ms...), causing the SCTP handshake to stall for 20+ seconds under packet loss with simultaneous-open (both sides call `Connect()`). + +Fix: `CustomDcSctpSocket` (in `tgcalls/v2/`) is a copy of `DcSctpSocket` with the 6-line fix that passes `max_timer_backoff_duration` to the t1_init and t1_cookie timer constructors. A `CustomDcSctpSocketFactory` in `SignalingSctpConnection.cpp` creates it instead of the stock socket, with configurable timer overrides. WebRTC source is **untouched**. + +Default signaling SCTP timer values (set in `SignalingSctpConnection::Options`): + +| Setting | WebRTC Default | Signaling Override | +|---|---|---| +| `t1_init_timeout` | 1000ms | 400ms | +| `t1_cookie_timeout` | 1000ms | 400ms | +| `max_timer_backoff_duration` | 3000ms | 750ms | +| `max_init_retransmits` | 8 | unlimited (from `DcSctpTransport::Start`) | + +Retry pattern: 400ms, 750ms, 750ms, 750ms... (~18 attempts in 15s). At 30% loss, 100% success rate over 5000 runs. + +These values are configurable via JSON custom parameters (passed to `InstanceV2Impl` via `config.customParameters`): +- `network_sctp_t1_init_ms` — T1-init timeout (0 = use default 400ms) +- `network_sctp_t1_cookie_ms` — T1-cookie timeout (0 = use default 400ms) +- `network_sctp_max_backoff_ms` — max timer backoff cap (0 = use default 750ms) + +Key files: +- `tgcalls/v2/CustomDcSctpSocket.h/.cpp` — patched `DcSctpSocket` copy +- `tgcalls/v2/SignalingSctpConnection.cpp` — `CustomDcSctpSocketFactory`, timer option plumbing +- `tgcalls/v2/InstanceV2Impl.cpp` — reads JSON params, passes `Options` to `SignalingSctpConnection` + +## InstanceV2CompatImpl (version 14.0.0) + +A cross-version interop implementation that uses WebRTC PeerConnection internally (like InstanceV2ReferenceImpl) but speaks V2Impl's signaling protocol (`InitialSetupMessage`, `NegotiateChannelsMessage`, `CandidatesMessage`). This enables bidirectional calls between PeerConnection-based clients and V2Impl-based clients (versions 7.0.0–13.0.0). + +### Architecture + +``` +PeerConnection <-> SignalingTranslator <-> EncryptedConnection <-> SignalingSctpConnection +``` + +- **SignalingTranslator** (`tgcalls/v2/SignalingTranslator.h/.cpp`): Converts between `cricket::SessionDescription` (PeerConnection's internal format) and V2Impl signaling messages. Uses `JsepSessionDescription` programmatic API — no SDP string round-trips. +- **Outbound**: PeerConnection generates offer/answer → SignalingTranslator extracts `InitialSetupMessage` (transport params) + `NegotiateChannelsMessage` (media contents) +- **Inbound**: Buffers both messages until complete → builds `cricket::SessionDescription` → wraps in `JsepSessionDescription` → `SetRemoteDescription` + +### Key Design Decisions + +- **No data channel with V2Impl peers**: WebRTC data channel requires PeerConnection on both sides. V2Impl uses NativeNetworkingImpl (no PeerConnection). When paired with V2Impl, the data channel m-line is padded as `rejected` in the remote answer so PeerConnection accepts it. For CompatImpl↔CompatImpl calls, the data channel works normally. +- **Caller-only renegotiation**: Only the outgoing side triggers offers from `onRenegotiationNeeded` to prevent unsolicited offer storms. +- **MediaState via signaling**: `MediaStateMessage` sent over the SCTP signaling channel (not data channel), ensuring it works with both V2Impl and CompatImpl peers. +- **Sequential content IDs**: Uses "0", "1", ... as m-line mids, matching PeerConnection's default scheme. +- **Shared conversion functions**: `convertContentInfoToSignalingContent()` and `convertSignalingContentToContentInfo()` extracted to `Signaling.h/.cpp` for use by both `ContentNegotiationContext` (V2Impl) and `SignalingTranslator` (CompatImpl). + +### Cross-Version Testing + +```bash +# CompatImpl caller → V2Impl callee +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 14.0.0 --version2 13.0.0 --duration 10 --quiet + +# V2Impl caller → CompatImpl callee +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 13.0.0 --version2 14.0.0 --duration 10 --quiet + +# With lossy signaling +./bazel-bin/tools/tgcalls_cli/tgcalls_cli --mode p2p --version 14.0.0 --version2 13.0.0 --duration 15 --drop-rate 0.3 --delay 50-200 --quiet +``` + +100% success rate at 30% loss in both directions (tested with 50 sequential + 20 parallel runs each direction). + +Key files: +- `tgcalls/v2/InstanceV2CompatImpl.h/.cpp` — main implementation +- `tgcalls/v2/SignalingTranslator.h/.cpp` — cricket↔signaling conversion +- `tgcalls/v2/Signaling.h/.cpp` — shared conversion functions (`convertContentInfoToSignalingContent`, `convertSignalingContentToContentInfo`) + +## GroupInstanceCustomImpl (Group Calls) + +The group call implementation in `tgcalls/group/GroupInstanceCustomImpl.cpp` (~4700 lines). Uses a client-server model with an SFU, unlike 1:1 calls which are peer-to-peer. + +### Protocol Stack +- **Join signaling**: JSON over application layer (`emitJoinPayload` → app sends to SFU → `setJoinResponsePayload`) +- **Transport**: ICE + DTLS-SRTP over UDP (standard WebRTC transport, NOT PeerConnection) +- **Media**: RTP/RTCP with Opus audio (48kHz, 2ch, 32kbps), optional VP8/H264/VP9 video +- **Control**: SCTP data channel over DTLS for Colibri protocol (video constraints, debug messages) + +### Join Flow +1. Client calls `emitJoinPayload()` → generates JSON with audio SSRC, ICE ufrag/pwd, DTLS fingerprint +2. Application sends JSON to SFU server +3. Server responds with its ICE candidates, DTLS fingerprint, video codec info +4. Client calls `setJoinResponsePayload(json)` → ICE/DTLS negotiation begins +5. On connection: `networkStateUpdated` callback fires + +### Participant Discovery +- Unknown SSRC arrives in RTP → `receiveUnknownSsrcPacket()` → `maybeRequestUnknownSsrc(ssrc)` +- App's `requestMediaChannelDescriptions` callback queries server for SSRC→participant mapping +- `addIncomingAudioChannel(ssrc, userId)` creates decoder channel + +### Colibri Data Channel Messages +```json +// SFU → Client +{"colibriClass": "SenderVideoConstraints", "videoConstraints": {"idealHeight": 360}} + +// Client → SFU +{"colibriClass": "ReceiverVideoConstraints", "defaultConstraints": {"maxHeight": 0}, + "constraints": {"endpoint1": {"minHeight": 720, "maxHeight": 720}}} +``` + +### Key Files +- `tgcalls/group/GroupInstanceCustomImpl.h/.cpp` — main implementation +- `tgcalls/group/GroupNetworkManager.h/.cpp` — ICE/DTLS/SRTP transport +- `tgcalls/group/GroupJoinPayloadInternal.h/.cpp` — join JSON serialization + +## GroupInstanceReferenceImpl (PeerConnection-based Group Calls) + +An alternative group call implementation that uses standard WebRTC PeerConnection instead of the manual ICE/DTLS/SRTP management in `GroupInstanceCustomImpl`. Supports both audio and video (H264 simulcast). Implements the same `GroupInstanceInterface`. + +### Architecture + +``` +GroupInstanceReferenceImpl + └── PeerConnection (single, to SFU) + ├── sendrecv audio transceiver (outgoing audio) + ├── sendonly video transceiver (outgoing H264 simulcast, SDP-munged SSRCs) + ├── recvonly audio transceivers (one per remote SSRC, added dynamically) + ├── recvonly video transceivers (one per remote endpoint, added dynamically) + └── data channel ("data", for ActiveVideoSsrcs and Colibri video constraints) +``` + +### How It Differs from CustomImpl + +| Aspect | CustomImpl | ReferenceImpl | +|--------|-----------|---------------| +| Transport | Manual ICE/DTLS/SRTP via GroupNetworkManager | WebRTC PeerConnection | +| SDP | None (custom JSON protocol) | Local SDP construction, translates to/from JSON | +| SSRC discovery | `unknownSsrcPacketReceived` on raw RTP | Audio: `GRAudioFrameTransformer` on mid=0's unsignaled receiver. Video: `ActiveVideoSsrcs` data channel message from SFU | +| Audio channels | Manual `IncomingAudioChannel` per SSRC | PeerConnection recvonly transceivers | +| Audio levels | RTP header extension parsing | Per-receiver `GRAudioLevelSink` reading real PCM levels | +| Video outgoing | Manual `cricket::VideoChannel` with direct SSRC control | PeerConnection sendonly transceiver + SDP munging for simulcast SSRCs | +| Video incoming | Manual `IncomingVideoChannel` per endpoint | PeerConnection recvonly transceivers with SSRCs in answer | +| Video decode | Manual decoder lifecycle | PeerConnection handles internally | +| Code size | ~4700 lines | ~1500 lines | + +### Join Flow (SDP Translation) + +1. Create PeerConnection with Opus audio transceiver, sendonly video transceiver (no track), and data channel +2. `createOffer` → munge video SSRCs (replace PeerConnection's auto-generated SSRCs with pre-allocated simulcast SSRCs) → `SetLocalDescription` → extract ICE/DTLS params from local SDP +3. Serialize as JSON (same format as CustomImpl): `{ssrc, ufrag, pwd, fingerprints, ssrc-groups}` +4. Parse SFU response JSON → construct `JsepSessionDescription("answer")` programmatically via `cricket::SessionDescription` API (no SDP string parsing) +5. `SetRemoteDescription` → ICE/DTLS connects via PeerConnection internals +6. Add remote ICE candidates via `AddIceCandidate` after `SetRemoteDescription` +7. Activate outgoing video: attach `FakeVideoTrackSource` track to the existing sendonly transceiver via `sender()->SetTrack()` — no renegotiation needed + +### Dynamic Participant Handling + +**Audio (per-receiver frame transformer):** +1. The first packet for an unknown SSRC X reaches mid=0's receiver — PeerConnection's catch-all for unsignaled audio. The voice channel creates a `WebRtcAudioReceiveStream` for X and attaches `GRAudioFrameTransformer` (registered as `unsignaled_frame_transformer_` on mid=0). +2. The transformer's `Transform(frame)` reads `frame->GetSsrc() = X`, sees X for the first time, **buffers the frame** in a per-SSRC FIFO, and posts the SSRC to the media thread. +3. `handleDiscoveredAudioSsrc(X)` inserts X into `_remoteSsrcs` with a fresh mid, fires `_requestMediaChannelDescriptions({X}, ...)` (matches CustomImpl's contract), and calls `scheduleDiscoveryRenegotiation()` (250 ms debounce). +4. After the debounce, `renegotiate()` adds a recvonly audio transceiver bound to mid=`_nextMid++` for every entry in `_remoteSsrcs` that doesn't have one. `buildRemoteAnswer` includes X on the new m-line; `WebRtcVoiceReceiveChannel::AddRecvStream(X)` PROMOTES the existing unsignaled stream in place (`webrtc_voice_engine.cc:2258-2266`) — the transformer stays attached. +5. `onRenegotiationComplete` runs `wireRemoteAudioLevelSinks()` (attaches `GRAudioLevelSink` per receiver), then calls `_audioFrameTransformer->releaseSsrc(ssrc)` for every SSRC whose transceiver now has a mid. The transformer drains the per-SSRC FIFO via `OnTransformedFrame` so the buffered audio plays without a startup gap. +6. Subsequent live frames for X take the transformer's `kDrained` branch (immediate passthrough). The per-receiver `GRAudioLevelSink` reads real PCM levels. + +The `colibriClass=ActiveAudioSsrcs` data-channel mechanism (test-SFU only) was removed; the tap is the single audio-discovery path. Removed-SSRC handling is the same as CustomImpl: stale recvonly transceivers stay in the SDP indefinitely; participant departures are tracked at the application layer (MTProto). + +The transformer is installed once on mid=0's receiver only — re-installing on the recvonly receivers triggers `Register{Sink,}TransformedFrameCallback` re-runs that overwrite valid registrations and misroute frames. Stream promotion keeps the single instance valid for every signaled SSRC. + +**Video:** +1. SFU sends `ActiveVideoSsrcs` over data channel → forwarded to app via `dataChannelMessageReceived` +2. App calls `setRequestedVideoChannels()` → adds recvonly video transceivers, sends `ReceiverVideoConstraints` over data channel +3. Renegotiate: new offer → munge outgoing video SSRCs → `SetLocalDescription` → build answer with incoming video SSRCs → `SetRemoteDescription` +4. `wirePendingVideoSinks()`: attach `FakeVideoSink` to the recvonly transceiver's receiver track after `SetRemoteDescription` completes +5. Renegotiations are serialized (`_isRenegotiating` / `_pendingRenegotiation` flags) to prevent overlapping offer/answer cycles + +### Outgoing Video: SDP Munging for Simulcast + +PeerConnection's API doesn't support SSRC-based simulcast directly (only RID-based, which doesn't put SSRCs in the SDP). The workaround: + +1. Pre-allocate 6 random video SSRCs at construction: 3 layers × (primary + RTX) +2. Add a sendonly video transceiver in `start()` with no track +3. Before `SetLocalDescription`, `mungeVideoSsrcsInOffer()` replaces the video m-line's auto-generated `StreamParams` with our pre-allocated SSRCs + SIM + FID groups +4. `UpdateLocalStreams_w()` in WebRTC's `channel.cc` sees SSRCs already present and skips generation +5. Later, `setVideoSource()` just calls `sender()->SetTrack()` — no renegotiation + +### Incoming Video: SSRC-Based Demux + +The answer for incoming video m-lines includes remote SSRCs from `VideoChannelDescription.ssrcGroups`. This is required because CustomImpl sets the `WebRTC-Video-DiscardPacketsWithUnknownSsrc` field trial process-wide, which disables unsignaled stream creation. Without explicit SSRCs, PeerConnection drops incoming video packets in mixed groups. + +### Key Implementation Details + +- **ICE roles**: PeerConnection uses standard ICE (full agent, controlling when remote is ICE-lite). The SFU uses `Accept` for PeerConnection clients vs `Dial` for CustomImpl clients. +- **Loopback**: `PeerConnectionFactory::Options::network_ignore_mask = 0` enables loopback interface gathering for localhost SFU +- **MID exclusion**: The `buildRemoteAnswer()` excludes the `urn:ietf:params:rtp-hdrext:sdes:mid` RTP header extension from ALL m-lines (audio and video). The SFU forwards raw RTP with the sender's MID value, which would cause the BUNDLE demuxer to route packets to the wrong channel. Without MID, PeerConnection falls back to SSRC/PT-based routing. +- **RTP header extensions**: Copied from the local offer per m-line (minus MID), ensuring BUNDLE-safe IDs. Hardcoding IDs risks collisions across the BUNDLE group. +- **SDP mid matching**: During renegotiation, the constructed remote answer mirrors the local offer's m-line structure and mids exactly. Mismatched mids cause `SetRemoteDescription` to fail. +- **Audio level reporting**: Uses synthetic levels (0.1) for all known remote SSRCs, since the SFU forwards RTP with extension IDs that may not match PeerConnection's negotiated mapping +- **Video sink wiring**: `OnTrack` doesn't fire for locally-created recvonly transceivers. Sinks are wired explicitly in `wirePendingVideoSinks()` after `SetRemoteDescription` completes, and also in `addIncomingVideoOutput()` if the track already exists. +- **H264 codec in answer**: PT 104 (primary) + PT 105 (RTX, apt=104), matching WebRTC's `assignPayloadTypes` order. RTCP feedback: nack, nack pli, ccm fir, goog-remb, transport-cc. +- **Renegotiation serialization**: Only one offer/answer cycle runs at a time. Deferred renegotiations only fire if there are unnegotiated transceivers (no mid assigned yet), avoiding redundant cycles. + +### Key Files +- `tgcalls/group/GroupInstanceReferenceImpl.h/.cpp` — implementation +- `tgcalls/group/GroupInstanceImpl.h` — shared `GroupInstanceInterface` + +## Video Support Pitfalls + +Critical findings from implementing video in the test SFU — relevant for anyone working on group video: + +### H264 Decoder Requires Two Build Flags +The WebRTC BUILD needs BOTH `-DWEBRTC_USE_H264` (encoder, OpenH264) AND `-DWEBRTC_USE_H264_DECODER` (decoder, FFmpeg). Without the decoder flag, `H264Decoder::Create()` returns nullptr and WebRTC silently falls back to `NullVideoDecoder` which accepts frames but never decodes them — no error logged. The encoder works fine without the decoder flag, making this easy to miss. + +### FFmpeg 7+ Removed `reordered_opaque` +`h264_decoder_impl.cc` uses `AVCodecContext::reordered_opaque` and `AVFrame::reordered_opaque` for passing timestamps through the decode pipeline. FFmpeg 7+ removed this field. The fix uses `AVPacket::pts` instead. IMPORTANT: `AVCodecContext::opaque` is already used to store the `H264DecoderImpl*` pointer (line 74 of `AVGetBuffer2`) — do NOT use it for timestamps. + +### Outgoing Video Channel Steals Incoming RTP +`GroupInstanceCustomImpl` creates separate `cricket::VideoChannel` objects for outgoing and incoming video, all sharing the same `RtpTransport`. The outgoing channel's `WebRtcVideoReceiveChannel` has an "unsignalled SSRC" handler that creates default receive streams for unknown SSRCs. When video RTP from other participants arrives before `IncomingVideoChannel` registers its SSRCs, the outgoing channel intercepts the packets permanently. Fix: enable the `WebRTC-Video-DiscardPacketsWithUnknownSsrc` field trial in the field trial string. + +### Video Channel Setup Is Reactive, Not Pre-Registered +Video channels are set up reactively when `ActiveVideoSsrcs` arrives via the data channel — same as the real Telegram app. The `dataChannelMessageReceived` callback in `GroupInstanceDescriptor` forwards Colibri messages to the app, which calls `setRequestedVideoChannels`. The `DiscardPacketsWithUnknownSsrc` field trial prevents the outgoing channel from stealing RTP packets for SSRCs not yet registered. The SFU sends proactive PLI after constraints arrive, ensuring keyframes are produced after the incoming channel is ready. + +### SFU Must Send Proactive PLI +WebRTC's `VideoReceiveStream2` doesn't immediately request a keyframe when a new receive stream is created — it waits until it detects missing packets or a timeout. The SFU must proactively send PLI to the sender when a receiver first requests video via `ReceiverVideoConstraints`. Without this, the decoder waits indefinitely for a keyframe. + +### RTP/RTCP Demux: Marker Bit False Positives +RFC 5761 demux by second byte: RTCP types are 200-211. But RTP with Marker=1 and dynamic PT ≥ 96 gives byte[1] ≥ 224. Using `byte[1] >= 200` falsely classifies H264 RTP (PT=104, M=1 → byte[1]=232) as RTCP. Correct range: `byte[1] >= 200 && byte[1] < 224`. + +### SRTCP Requires Separate Contexts from SRTP +Pion's `SessionSRTP` and `SessionSRTCP` can't share the same `net.Conn` (both start read loops that fight for packets). The solution: demux RTCP at the transport level (in `PacketDemux`), create separate `srtp.Context` instances for SRTCP decrypt/encrypt using the same DTLS-extracted keys, and handle RTCP manually without `SessionSRTCP`. + +### PeerConnection Simulcast SSRCs Require SDP Munging +PeerConnection's API doesn't support SSRC-based simulcast (only RID-based). With RID-based simulcast, SSRCs are NOT in the `createOffer` SDP — they're generated internally during `SetLocalDescription` and not accessible via `sender->GetParameters()` (only primary SSRCs, not RTX). The workaround: add a single-encoding transceiver (no RIDs), then replace the auto-generated `StreamParams` in the offer with pre-allocated SSRCs + SIM + FID groups before calling `SetLocalDescription`. `UpdateLocalStreams_w()` skips generation when SSRCs already exist. IMPORTANT: `transceiver->mid()` is `nullopt` before `SetLocalDescription` — match by content direction, not mid. + +### MID RTP Header Extension Causes Wrong Channel Routing in SFU +The SFU forwards raw RTP packets including all header extensions. If the sender's video RTP includes a MID extension (e.g., MID="1"), the receiver's PeerConnection BUNDLE demuxer routes the packet to its own mid=1 channel — which is the outgoing video, not the incoming video transceiver. Fix: exclude `urn:ietf:params:rtp-hdrext:sdes:mid` from ALL m-lines in `buildRemoteAnswer()`. Without MID negotiated, PeerConnection falls back to SSRC/PT-based routing. This must be done for ALL m-lines (including audio) because the BUNDLE transport shares the extension map across all channels. + +### `DiscardPacketsWithUnknownSsrc` Is Process-Wide +CustomImpl calls `field_trial::InitFieldTrialsFromString(...)` which sets `WebRTC-Video-DiscardPacketsWithUnknownSsrc/Enabled/` globally for the process. In mixed groups, this prevents ReferenceImpl's PeerConnection from creating unsignaled receive streams for incoming video. Fix: include explicit remote video SSRCs in the `buildRemoteAnswer()` for incoming video m-lines, so PeerConnection registers SSRC-based demux entries instead of relying on unsignaled stream handling. + +### `OnTrack` Doesn't Fire for Locally-Created Recvonly Transceivers +When you call `AddTransceiver(MEDIA_TYPE_VIDEO, {direction=recvonly})`, PeerConnection creates the transceiver and its receiver track immediately. `OnTrack` only fires when a REMOTE-initiated track is added. For locally-created recvonly transceivers, you must wire sinks explicitly after `SetRemoteDescription` completes — don't wait for `OnTrack`. + +### SSRC Parsing: json11 int_value() Overflows for uint32 > INT_MAX +`GoSfu_QueryVideoSsrcs` returns SSRCs as `uint32` in JSON. For values > 2^31, json11's `int_value()` (which returns `int`) overflows to `INT_MAX` (2147483647). Fix: use `number_value()` (returns `double`) and cast via `int64_t` to `uint32_t`. + +### Join Payload JSON Field Name: `"sources"` Not `"ssrcs"` +tgcalls serializes video SSRC groups in `GroupJoinInternalPayload::serialize()` using the key `"sources"` (not `"ssrcs"`). The Go SFU's JSON struct tags must match: `Sources []int32 \`json:"sources"\``. + +### Simulcast Max Layers Depends on Source Resolution, Not Bitrate +WebRTC's `kSimulcastFormats` table in `video/config/simulcast.cc` hardcodes `max_layers` per resolution: 640x360 → 2 layers, 960x540 → 3 layers, 1280x720 → 3 layers. The `SimulcastEncoderAdapter` uses this to cap the number of encoders regardless of available bitrate. If you need 3 simulcast layers, the source must be at least 960x540. The `FakeVideoTrackSource` uses 1280x720 for this reason. With 1280x720 and scale factors /4, /2, /1, the layers are 320x180, 640x360, 1280x720. + +### SFU Must Rewrite SSRCs When Switching Simulcast Layers +CustomImpl's `IncomingVideoChannel` calls `SetSink(_mainVideoSsrc, ...)` where `_mainVideoSsrc` is the first SSRC in the SIM group (layer 0). The video sink only receives decoded frames from that specific SSRC's receive stream. When the SFU forwards a higher layer's packets, it must rewrite bytes 8-11 of the RTP header to the primary (layer 0) SSRC. RTX packets must similarly be rewritten to the layer 0 FID SSRC. Without this, higher-layer packets are delivered to the wrong receive stream and produce zero decoded frames. This is standard SFU behavior for simulcast — Jitsi and mediasoup do the same. + +### Sender BWE Start Bitrate Determines Initial Layer Count +`adjustBitratePreferences` sets `start_bitrate_bps = max(min_bitrate_bps, 400k)`. At 400kbps start, the `BitrateAllocator` gives L0 (60k) + L1 (110k) = 170k, leaving only 230k for L2 which needs min 300k. Layer 2 is disabled until the GCC ramps up. The SFU's transport-cc feedback enables this ramp-up. The `UpdateAllocationLimits` log shows `total_requested_max_bitrate` — if this is below the sum of all layers' min bitrates, some layers are excluded. + +### `assignPayloadTypes` Codec Ordering +WebRTC's `assignPayloadTypes` assigns dynamic PTs starting at 100 in order: VP8 (100/101), VP9 (102/103), H264 (104/105). Both sender and receiver call this independently with the same codec list, so PTs match. The SFU's join response codec PTs (100 for H264 in our case) are used by `configureVideoParams` to SELECT which codec to use, but the actual PT assignment comes from `assignPayloadTypes`. + +## Known Issues +- `ThreadLocalObject::~ThreadLocalObject()` posts fire-and-forget cleanup tasks to the tgcalls media thread. If the process does orderly static destruction, the static thread pool may be torn down while these tasks are still executing, causing "pure virtual function called". The CLI tool uses `_exit()` to avoid this. This is not a problem in the real Telegram app. +- `SignalingSctpConnection::OnReadyToSend()` had a missing `break` after the first send failure in its pending-data flush loop. This could cause application-level message reordering (though the application handles it gracefully via `_pendingIceCandidates` buffering). Fixed in our fork. +- `InstanceV2ReferenceImpl::writeStateLogRecords()` had a use-after-free: it captured a raw `Call*` pointer on the media thread and posted it to the worker thread. If `stop()` called `_peerConnection->Close()` (which destroys `Call`) between the post and worker thread execution, the worker thread would dereference a dangling pointer. The `call_ptr_` field in WebRTC's `PeerConnection` is `Call* const` and is never nulled, so the existing null check didn't catch this. Fixed with an `_isStopped` atomic flag checked in the worker thread lambda before accessing `call`. Manifested as ~2% segfault rate under 250-process parallel load; 100% pass rate after fix (5000/5000). +- WebRTC's `RTC_LOG` writes to stdout, not stderr. There is no way to separate it from application output within a single process. The local mass test harness (`run-local-test.sh`) works around this by using separate processes and checking exit codes rather than parsing output. diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 67406bd78d..5b51961b9f 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -458,7 +458,8 @@ onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDet audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice isConference:(bool)isConference isActiveByDefault:(bool)isActiveByDefault -encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool, int32_t))encryptDecrypt; +encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool, int32_t))encryptDecrypt +useReferenceImpl:(bool)useReferenceImpl; - (void)stop:(void (^ _Nullable)())completion; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 5e912dbcaa..5f67764d10 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -34,6 +34,7 @@ #import "group/GroupInstanceImpl.h" #import "group/GroupInstanceCustomImpl.h" +#import "group/GroupInstanceReferenceImpl.h" #import "VideoCaptureInterfaceImpl.h" @@ -2388,7 +2389,8 @@ onMutedSpeechActivityDetected:(void (^ _Nullable)(bool))onMutedSpeechActivityDet audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice isConference:(bool)isConference isActiveByDefault:(bool)isActiveByDefault -encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool, int32_t))encryptDecrypt { +encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, bool, int32_t))encryptDecrypt +useReferenceImpl:(bool)useReferenceImpl { self = [super init]; if (self != nil) { _queue = queue; @@ -2471,7 +2473,7 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, boo } __weak GroupCallThreadLocalContext *weakSelf = self; - _instance.reset(new tgcalls::GroupInstanceCustomImpl((tgcalls::GroupInstanceDescriptor){ + tgcalls::GroupInstanceDescriptor descriptor = (tgcalls::GroupInstanceDescriptor){ .threads = tgcalls::StaticThreads::getThreads(), .config = config, .statsLogPath = statsLogPathValue, @@ -2699,7 +2701,12 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, int64_t, boo }, .e2eEncryptDecrypt = mappedEncryptDecrypt, .isConference = isConference - })); + }; + if (useReferenceImpl) { + _instance.reset(new tgcalls::GroupInstanceReferenceImpl(std::move(descriptor))); + } else { + _instance.reset(new tgcalls::GroupInstanceCustomImpl(std::move(descriptor))); + } } return self; } diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 8099768559..2caf643db0 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 8099768559edb0efd2d1b300090c18141226e9a8 +Subproject commit 2caf643db02d2a6d862a27a81801271aac94af94 diff --git a/submodules/TonBinding/BUILD b/submodules/TonBinding/BUILD deleted file mode 100644 index e32788b00b..0000000000 --- a/submodules/TonBinding/BUILD +++ /dev/null @@ -1,26 +0,0 @@ -objc_library( - name = "TonBinding", - module_name = "TonBinding", - enable_modules = True, - srcs = glob([ - "Sources/**/*.m", - "Sources/**/*.mm", - ]), - hdrs = glob([ - "Sources/**/*.h", - ]), - copts = [ - "-std=c++14", - ], - includes = [ - "Sources", - ], - deps = [ - "//submodules/SSignalKit/SSignalKit:SSignalKit", - "//third-party/boringssl:crypto", - "//submodules/ton:ton", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/TonBinding/Sources/TON.h b/submodules/TonBinding/Sources/TON.h deleted file mode 100644 index 31c0dc812f..0000000000 --- a/submodules/TonBinding/Sources/TON.h +++ /dev/null @@ -1,190 +0,0 @@ -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface TONError : NSObject - -@property (nonatomic, strong, readonly) NSString *text; - -@end - -@interface TONKey : NSObject - -@property (nonatomic, strong, readonly) NSString *publicKey; -@property (nonatomic, strong, readonly) NSData *secret; - -- (instancetype)initWithPublicKey:(NSString *)publicKey secret:(NSData *)secret; - -@end - -@interface TONTransactionId : NSObject - -@property (nonatomic, readonly) int64_t lt; -@property (nonatomic, strong, readonly) NSData * _Nonnull transactionHash; - -- (instancetype)initWithLt:(int64_t)lt transactionHash:(NSData * _Nonnull)transactionHash; - -@end - -@interface TONAccountState : NSObject - -@property (nonatomic, readonly) bool isInitialized; -@property (nonatomic, readonly) bool isRWallet; -@property (nonatomic, readonly) int64_t balance; -@property (nonatomic, readonly) int64_t unlockedBalance; - -@property (nonatomic, readonly) int32_t seqno; -@property (nonatomic, strong, readonly) TONTransactionId * _Nullable lastTransactionId; -@property (nonatomic, readonly) int64_t syncUtime; - -- (instancetype)initWithIsInitialized:(bool)isInitialized isRWallet:(bool)isRWallet balance:(int64_t)balance unlockedBalance:(int64_t)unlockedBalance seqno:(int32_t)seqno lastTransactionId:(TONTransactionId * _Nullable)lastTransactionId syncUtime:(int64_t)syncUtime; - -@end - -@protocol TONTransactionMessageContents - -@end - -@interface TONTransactionMessageContentsRawData : NSObject - -@property (nonatomic, strong, readonly) NSData * _Nonnull data; - -- (instancetype)initWithData:(NSData * _Nonnull)data; - -@end - -@interface TONTransactionMessageContentsPlainText : NSObject - -@property (nonatomic, strong, readonly) NSString * _Nonnull text; - -- (instancetype)initWithText:(NSString * _Nonnull)text; - -@end - -@interface TONEncryptedData : NSObject - -@property (nonatomic, strong, readonly) NSString * _Nonnull sourceAddress; -@property (nonatomic, strong, readonly) NSData * _Nonnull data; - -- (instancetype)initWithSourceAddress:(NSString * _Nonnull)sourceAddress data:(NSData * _Nonnull)data; - -@end - -@interface TONTransactionMessageContentsEncryptedText : NSObject - -@property (nonatomic, strong, readonly) TONEncryptedData * _Nonnull encryptedData; - -- (instancetype)initWithEncryptedData:(TONEncryptedData * _Nonnull)encryptedData; - -@end - -@interface TONTransactionMessage : NSObject - -@property (nonatomic, readonly) int64_t value; -@property (nonatomic, strong, readonly) NSString * _Nonnull source; -@property (nonatomic, strong, readonly) NSString * _Nonnull destination; -@property (nonatomic, strong, readonly) id _Nonnull contents; -@property (nonatomic, strong, readonly) NSData * _Nonnull bodyHash; - -- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination contents:(id _Nonnull)contents bodyHash:(NSData * _Nonnull)bodyHash; - -@end - -@interface TONTransaction : NSObject - -@property (nonatomic, strong, readonly) NSData * _Nonnull data; -@property (nonatomic, strong, readonly) TONTransactionId * _Nonnull transactionId; -@property (nonatomic, readonly) int64_t timestamp; -@property (nonatomic, readonly) int64_t storageFee; -@property (nonatomic, readonly) int64_t otherFee; -@property (nonatomic, strong, readonly) TONTransactionMessage * _Nullable inMessage; -@property (nonatomic, strong, readonly) NSArray * _Nonnull outMessages; - -- (instancetype)initWithData:(NSData * _Nonnull)data transactionId:(TONTransactionId * _Nonnull)transactionId timestamp:(int64_t)timestamp storageFee:(int64_t)storageFee otherFee:(int64_t)otherFee inMessage:(TONTransactionMessage * _Nullable)inMessage outMessages:(NSArray * _Nonnull)outMessages; - -@end - -@interface TONExternalRequest : NSObject - -@property (nonatomic, strong, readonly) NSData * _Nonnull data; -@property (nonatomic, copy, readonly) void (^onResult)(NSData * _Nullable, NSString * _Nullable); - -- (instancetype)initWithData:(NSData * _Nonnull)data onResult:(void (^)(NSData * _Nullable, NSString * _Nullable))onResult; - -@end - -@interface TONFees : NSObject - -@property (nonatomic, readonly) int64_t inFwdFee; -@property (nonatomic, readonly) int64_t storageFee; -@property (nonatomic, readonly) int64_t gasFee; -@property (nonatomic, readonly) int64_t fwdFee; - -- (instancetype)initWithInFwdFee:(int64_t)inFwdFee storageFee:(int64_t)storageFee gasFee:(int64_t)gasFee fwdFee:(int64_t)fwdFee; - -@end - -@interface TONSendGramsQueryFees : NSObject - -@property (nonatomic, strong, readonly) TONFees * _Nonnull sourceFees; -@property (nonatomic, strong, readonly) NSArray * _Nonnull destinationFees; - -- (instancetype)initWithSourceFees:(TONFees * _Nonnull)sourceFees destinationFees:(NSArray * _Nonnull)destinationFees; - -@end - -@interface TONPreparedSendGramsQuery : NSObject - -@property (nonatomic, readonly) int64_t queryId; -@property (nonatomic, readonly) int64_t validUntil; -@property (nonatomic, strong, readonly) NSData * _Nonnull bodyHash; - -- (instancetype)initWithQueryId:(int64_t)queryId validUntil:(int64_t)validUntil bodyHash:(NSData *)bodyHash; - -@end - -@interface TONSendGramsResult : NSObject - -@property (nonatomic, readonly) int64_t sentUntil; -@property (nonatomic, strong, readonly) NSData * _Nonnull bodyHash; - -- (instancetype)initWithSentUntil:(int64_t)sentUntil bodyHash:(NSData *)bodyHash; - -@end - -@interface TONValidatedConfig : NSObject - -@property (nonatomic, readonly) int64_t defaultWalletId; - -- (instancetype)initWithDefaultWalletId:(int64_t)defaultWalletId; - -@end - -@interface TON : NSObject - -- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests syncStateUpdated:(void (^)(float))syncStateUpdated; - -- (SSignal *)updateConfig:(NSString *)config blockchainName:(NSString *)blockchainName; -- (SSignal *)validateConfig:(NSString *)config blockchainName:(NSString *)blockchainName; - -- (SSignal *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword; -- (SSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey initialWalletId:(int64_t)initialWalletId rwalletInitialPublicKey:(NSString * _Nullable)rwalletInitialPublicKey; -- (SSignal *)getAccountStateWithAddress:(NSString *)accountAddress; -- (SSignal *)generateSendGramsQueryFromKey:(TONKey *)key localPassword:(NSData *)localPassword fromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount comment:(NSData *)comment encryptComment:(bool)encryptComment forceIfDestinationNotInitialized:(bool)forceIfDestinationNotInitialized timeout:(int32_t)timeout randomId:(int64_t)randomId; -- (SSignal *)generateFakeSendGramsQueryFromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount comment:(NSData *)comment encryptComment:(bool)encryptComment forceIfDestinationNotInitialized:(bool)forceIfDestinationNotInitialized timeout:(int32_t)timeout; -- (SSignal *)estimateSendGramsQueryFees:(TONPreparedSendGramsQuery *)preparedQuery; -- (SSignal *)commitPreparedSendGramsQuery:(TONPreparedSendGramsQuery *)preparedQuery; -- (SSignal *)exportKey:(TONKey *)key localPassword:(NSData *)localPassword; -- (SSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray *)wordList; -- (SSignal *)deleteKey:(TONKey *)key; -- (SSignal *)deleteAllKeys; -- (SSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash; -- (SSignal *)decryptMessagesWithKey:(TONKey * _Nonnull)key localPassword:(NSData * _Nonnull)localPassword messages:(NSArray * _Nonnull)messages; - -- (NSData *)encrypt:(NSData *)decryptedData secret:(NSData *)data; -- (NSData * __nullable)decrypt:(NSData *)encryptedData secret:(NSData *)data; - -@end - -NS_ASSUME_NONNULL_END diff --git a/submodules/TonBinding/Sources/TON.mm b/submodules/TonBinding/Sources/TON.mm deleted file mode 100644 index 1ff7dc656d..0000000000 --- a/submodules/TonBinding/Sources/TON.mm +++ /dev/null @@ -1,1227 +0,0 @@ -#import "TON.h" - -#import "tonlib/Client.h" - -static td::SecureString makeSecureString(NSData * _Nonnull data) { - if (data == nil || data.length == 0) { - return td::SecureString(); - } else { - return td::SecureString((const char *)data.bytes, (size_t)data.length); - } -} - -static std::string makeString(NSData * _Nonnull data) { - if (data == nil || data.length == 0) { - return std::string(); - } else { - return std::string((const char *)data.bytes, ((const char *)data.bytes) + data.length); - } -} - -static NSData * _Nonnull makeData(std::string &string) { - if (string.size() == 0) { - return [NSData data]; - } else { - return [[NSData alloc] initWithBytes:string.data() length:string.size()]; - } -} - -static NSString * _Nullable readString(std::string &string) { - if (string.size() == 0) { - return @""; - } else { - return [[NSString alloc] initWithBytes:string.data() length:string.size() encoding:NSUTF8StringEncoding]; - } -} - -static NSData * _Nullable readSecureString(td::SecureString &string) { - if (string.size() == 0) { - return [NSData data]; - } else { - return [[NSData alloc] initWithBytes:string.data() length:string.size()]; - } -} - -static TONTransactionMessage * _Nullable parseTransactionMessage(tonlib_api::object_ptr &message) { - if (message == nullptr) { - return nil; - } - NSString *source = readString(message->source_->account_address_); - NSString *destination = readString(message->destination_->account_address_); - - id contents = nil; - if (message->msg_data_->get_id() == tonlib_api::msg_dataRaw::ID) { - auto msgData = tonlib_api::move_object_as(message->msg_data_); - contents = [[TONTransactionMessageContentsRawData alloc] initWithData:makeData(msgData->body_)]; - } else if (message->msg_data_->get_id() == tonlib_api::msg_dataText::ID) { - auto msgData = tonlib_api::move_object_as(message->msg_data_); - NSString *text = readString(msgData->text_); - if (text == nil) { - contents = [[TONTransactionMessageContentsPlainText alloc] initWithText:@""]; - } else { - contents = [[TONTransactionMessageContentsPlainText alloc] initWithText:text]; - } - } else if (message->msg_data_->get_id() == tonlib_api::msg_dataDecryptedText::ID) { - auto msgData = tonlib_api::move_object_as(message->msg_data_); - NSString *text = readString(msgData->text_); - if (text == nil) { - contents = [[TONTransactionMessageContentsPlainText alloc] initWithText:@""]; - } else { - contents = [[TONTransactionMessageContentsPlainText alloc] initWithText:text]; - } - } else if (message->msg_data_->get_id() == tonlib_api::msg_dataEncryptedText::ID) { - auto msgData = tonlib_api::move_object_as(message->msg_data_); - TONEncryptedData *encryptedData = [[TONEncryptedData alloc] initWithSourceAddress:source data:makeData(msgData->text_)]; - contents = [[TONTransactionMessageContentsEncryptedText alloc] initWithEncryptedData:encryptedData]; - } else { - contents = [[TONTransactionMessageContentsRawData alloc] initWithData:[NSData data]]; - } - - if (source == nil || destination == nil) { - return nil; - } - return [[TONTransactionMessage alloc] initWithValue:message->value_ source:source destination:destination contents:contents bodyHash:makeData(message->body_hash_)]; -} - -@implementation TONKey - -- (instancetype)initWithPublicKey:(NSString *)publicKey secret:(NSData *)secret { - self = [super init]; - if (self != nil) { - _publicKey = publicKey; - _secret = secret; - } - return self; -} - -@end - -@implementation TONAccountState - -- (instancetype)initWithIsInitialized:(bool)isInitialized isRWallet:(bool)isRWallet balance:(int64_t)balance unlockedBalance:(int64_t)unlockedBalance seqno:(int32_t)seqno lastTransactionId:(TONTransactionId * _Nullable)lastTransactionId syncUtime:(int64_t)syncUtime { - self = [super init]; - if (self != nil) { - _isInitialized = isInitialized; - _isRWallet = isRWallet; - _balance = balance; - _unlockedBalance = unlockedBalance; - _seqno = seqno; - _lastTransactionId = lastTransactionId; - _syncUtime = syncUtime; - } - return self; -} - -@end - -@implementation TONTransactionId - -- (instancetype)initWithLt:(int64_t)lt transactionHash:(NSData *)transactionHash { - self = [super init]; - if (self != nil) { - _lt = lt; - _transactionHash = transactionHash; - } - return self; -} - -@end - -@implementation TONTransactionMessageContentsRawData - -- (instancetype)initWithData:(NSData * _Nonnull)data { - self = [super init]; - if (self != nil) { - _data = data; - } - return self; -} - -@end - -@implementation TONTransactionMessageContentsPlainText - -- (instancetype)initWithText:(NSString * _Nonnull)text { - self = [super init]; - if (self != nil) { - _text = text; - } - return self; -} - -@end - -@implementation TONTransactionMessageContentsEncryptedText - -- (instancetype)initWithEncryptedData:(TONEncryptedData * _Nonnull)encryptedData { - self = [super init]; - if (self != nil) { - _encryptedData = encryptedData; - } - return self; -} - -@end - -@implementation TONTransactionMessage - -- (instancetype)initWithValue:(int64_t)value source:(NSString * _Nonnull)source destination:(NSString * _Nonnull)destination contents:(id _Nonnull)contents bodyHash:(NSData * _Nonnull)bodyHash { - self = [super init]; - if (self != nil) { - _value = value; - _source = source; - _destination = destination; - _contents = contents; - _bodyHash = bodyHash; - } - return self; -} - -@end - -@implementation TONTransaction - -- (instancetype)initWithData:(NSData * _Nonnull)data transactionId:(TONTransactionId * _Nonnull)transactionId timestamp:(int64_t)timestamp storageFee:(int64_t)storageFee otherFee:(int64_t)otherFee inMessage:(TONTransactionMessage * _Nullable)inMessage outMessages:(NSArray * _Nonnull)outMessages { - self = [super init]; - if (self != nil) { - _data = data; - _transactionId = transactionId; - _timestamp = timestamp; - _storageFee = storageFee; - _otherFee = otherFee; - _inMessage = inMessage; - _outMessages = outMessages; - } - return self; -} - -@end - -@implementation TONExternalRequest - -- (instancetype)initWithData:(NSData * _Nonnull)data onResult:(void (^)(NSData * _Nullable, NSString * _Nullable))onResult { - self = [super init]; - if (self != nil) { - _data = data; - _onResult = [onResult copy]; - } - return self; -} - -@end - -@implementation TONFees - -- (instancetype)initWithInFwdFee:(int64_t)inFwdFee storageFee:(int64_t)storageFee gasFee:(int64_t)gasFee fwdFee:(int64_t)fwdFee { - self = [super init]; - if (self != nil) { - _inFwdFee = inFwdFee; - _storageFee = storageFee; - _gasFee = gasFee; - _fwdFee = fwdFee; - } - return self; -} - -@end - -@implementation TONSendGramsQueryFees - -- (instancetype)initWithSourceFees:(TONFees * _Nonnull)sourceFees destinationFees:(NSArray * _Nonnull)destinationFees { - self = [super init]; - if (self != nil) { - _sourceFees = sourceFees; - _destinationFees = destinationFees; - } - return self; -} - -@end - -@implementation TONPreparedSendGramsQuery - -- (instancetype)initWithQueryId:(int64_t)queryId validUntil:(int64_t)validUntil bodyHash:(NSData *)bodyHash { - self = [super init]; - if (self != nil) { - _queryId = queryId; - _validUntil = validUntil; - _bodyHash = bodyHash; - } - return self; -} - -@end - -@implementation TONSendGramsResult - -- (instancetype)initWithSentUntil:(int64_t)sentUntil bodyHash:(NSData *)bodyHash { - self = [super init]; - if (self != nil) { - _sentUntil = sentUntil; - _bodyHash = bodyHash; - } - return self; -} - -@end - -@implementation TONValidatedConfig - -- (instancetype)initWithDefaultWalletId:(int64_t)defaultWalletId { - self = [super init]; - if (self != nil) { - _defaultWalletId = defaultWalletId; - } - return self; -} - -@end - -@implementation TONEncryptedData - -- (instancetype)initWithSourceAddress:(NSString * _Nonnull)sourceAddress data:(NSData * _Nonnull)data { - self = [super init]; - if (self != nil) { - _sourceAddress = sourceAddress; - _data = data; - } - return self; -} - -@end - -using tonlib_api::make_object; - -@interface TONReceiveThreadParams : NSObject - -@property (nonatomic, readonly) std::shared_ptr client; -@property (nonatomic, copy, readonly) void (^received)(tonlib::Client::Response &); - -@end - -@implementation TONReceiveThreadParams - -- (instancetype)initWithClient:(std::shared_ptr)client received:(void (^)(tonlib::Client::Response &))received { - self = [super init]; - if (self != nil) { - _client = client; - _received = [received copy]; - } - return self; -} - -@end - -@interface TONRequestHandler : NSObject - -@property (nonatomic, copy, readonly) void (^completion)(tonlib_api::object_ptr &); - -@end - -@implementation TONRequestHandler - -- (instancetype)initWithCompletion:(void (^)(tonlib_api::object_ptr &))completion { - self = [super init]; - if (self != nil) { - _completion = [completion copy]; - } - return self; -} - -@end - -@implementation TONError - -- (instancetype)initWithText:(NSString *)text { - self = [super init]; - if (self != nil) { - _text = text; - } - return self; -} - -@end - -typedef enum { - TONInitializationStatusInitializing, - TONInitializationStatusReady, - TONInitializationStatusError -} TONInitializationStatus; - -@interface TON () { - std::shared_ptr _client; - uint64_t _nextRequestId; - NSLock *_requestHandlersLock; - NSMutableDictionary *_requestHandlers; - SPipe *_initializedStatus; - NSMutableSet *_sendGramRandomIds; - SQueue *_queue; - bool _enableExternalRequests; -} - -@end - -@implementation TON - -+ (void)receiveThread:(TONReceiveThreadParams *)params { - while (true) { - auto response = params.client->receive(1000); - if (response.object) { - params.received(response); - } - } -} - -- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests syncStateUpdated:(void (^)(float))syncStateUpdated { - self = [super init]; - if (self != nil) { - _queue = [SQueue mainQueue]; - _requestHandlersLock = [[NSLock alloc] init]; - _requestHandlers = [[NSMutableDictionary alloc] init]; - _initializedStatus = [[SPipe alloc] initWithReplay:true]; - _initializedStatus.sink(@(TONInitializationStatusInitializing)); - _nextRequestId = 1; - _sendGramRandomIds = [[NSMutableSet alloc] init]; - _enableExternalRequests = enableExternalRequests; - - _client = std::make_shared(); - - [self setupLogging]; - - std::weak_ptr weakClient = _client; - - NSLock *requestHandlersLock = _requestHandlersLock; - NSMutableDictionary *requestHandlers = _requestHandlers; - NSThread *thread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(receiveThread:) object:[[TONReceiveThreadParams alloc] initWithClient:_client received:^(tonlib::Client::Response &response) { - if (response.object->get_id() == tonlib_api::updateSendLiteServerQuery::ID) { - auto result = tonlib_api::move_object_as(response.object); - int64_t requestId = result->id_; - NSData *data = makeData(result->data_); - if (performExternalRequest) { - performExternalRequest([[TONExternalRequest alloc] initWithData:data onResult:^(NSData * _Nullable result, NSString * _Nullable error) { - auto strongClient = weakClient.lock(); - if (strongClient != nullptr) { - if (result != nil) { - auto query = make_object( - requestId, - makeString(result) - ); - strongClient->send({ 1, std::move(query) }); - } else if (error != nil) { - auto query = make_object( - requestId, - make_object( - 400, - error.UTF8String - ) - ); - strongClient->send({ 1, std::move(query) }); - } - } - }]); - } - return; - } else if (response.object->get_id() == tonlib_api::updateSyncState::ID) { - auto result = tonlib_api::move_object_as(response.object); - if (result->sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) { - auto syncStateInProgress = tonlib_api::move_object_as(result->sync_state_); - int32_t currentDelta = syncStateInProgress->current_seqno_ - syncStateInProgress->from_seqno_; - int32_t fullDelta = syncStateInProgress->to_seqno_ - syncStateInProgress->from_seqno_; - if (currentDelta > 0 && fullDelta > 0) { - float progress = ((float)currentDelta) / ((float)fullDelta); - syncStateUpdated(progress); - } else { - syncStateUpdated(0.0f); - } - } else if (result->sync_state_->get_id() == tonlib_api::syncStateDone::ID) { - syncStateUpdated(1.0f); - } - return; - } - NSNumber *requestId = @(response.id); - [requestHandlersLock lock]; - TONRequestHandler *handler = requestHandlers[requestId]; - [requestHandlers removeObjectForKey:requestId]; - [requestHandlersLock unlock]; - if (handler != nil) { - handler.completion(response.object); - } - }]]; - [thread start]; - - [[NSFileManager defaultManager] createDirectoryAtPath:keystoreDirectory withIntermediateDirectories:true attributes:nil error:nil]; - - SPipe *initializedStatus = _initializedStatus; - [[self requestInitWithConfigString:config blockchainName:blockchainName keystoreDirectory:keystoreDirectory enableExternalRequests:enableExternalRequests] startWithNext:nil error:^(id error) { - NSString *errorText = @"Unknown error"; - if ([error isKindOfClass:[TONError class]]) { - errorText = ((TONError *)error).text; - } - initializedStatus.sink(@(TONInitializationStatusError)); - } completed:^{ - initializedStatus.sink(@(TONInitializationStatusReady)); - }]; - } - return self; -} - -- (void)setupLogging { -#if DEBUG - auto query = make_object( - make_object() - ); - _client->execute({ INT16_MAX + 1, std::move(query) }); -#else - auto query = make_object( - make_object() - ); - _client->execute({ INT16_MAX + 1, std::move(query) }); -#endif -} - -- (NSData * __nullable)decrypt:(NSData *)encryptedData secret:(NSData *)data { - auto query = make_object(makeSecureString(encryptedData), makeSecureString(data)); - tonlib_api::object_ptr result = _client->execute({ INT16_MAX + 1, std::move(query) }).object; - - if (result->get_id() == tonlib_api::error::ID) { - return nil; - } else { - tonlib_api::object_ptr value = tonlib_api::move_object_as(result); - return readSecureString(value->bytes_); - } -} -- (NSData *)encrypt:(NSData *)decryptedData secret:(NSData *)data { - auto query = make_object(makeSecureString(decryptedData), makeSecureString(data)); - tonlib_api::object_ptr result = _client->execute({ INT16_MAX + 1, std::move(query) }).object; - - tonlib_api::object_ptr value = tonlib_api::move_object_as(result); - - return readSecureString(value->bytes_); -} - -- (SSignal *)requestInitWithConfigString:(NSString *)configString blockchainName:(NSString *)blockchainName keystoreDirectory:(NSString *)keystoreDirectory enableExternalRequests:(bool)enableExternalRequests { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else { - [subscriber putCompletion]; - } - }]; - - bool ignoreCache = false; - #if DEBUG - ignoreCache = true; - #endif - - auto query = make_object(make_object( - make_object( - configString.UTF8String, - blockchainName.UTF8String, - enableExternalRequests, - ignoreCache - ), - make_object( - keystoreDirectory.UTF8String - ) - )); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)updateConfig:(NSString *)config blockchainName:(NSString *)blockchainName { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else { - [subscriber putCompletion]; - } - }]; - - auto query = make_object( - make_object( - config.UTF8String, - blockchainName.UTF8String, - _enableExternalRequests, - false - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)validateConfig:(NSString *)config blockchainName:(NSString *)blockchainName { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::options_configInfo::ID) { - auto result = tonlib_api::move_object_as(object); - [subscriber putNext:[[TONValidatedConfig alloc] initWithDefaultWalletId:result->default_wallet_id_]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object( - make_object( - config.UTF8String, - blockchainName.UTF8String, - _enableExternalRequests, - false - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)createKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::key::ID) { - auto result = tonlib_api::move_object_as(object); - NSString *publicKey = [[NSString alloc] initWithData:[[NSData alloc] initWithBytes:result->public_key_.data() length:result->public_key_.length()] encoding:NSUTF8StringEncoding]; - if (publicKey == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error decoding UTF8 string in createKeyWithLocalPassword"]]; - return; - } - NSData *secret = [[NSData alloc] initWithBytes:result->secret_.data() length:result->secret_.length()]; - [subscriber putNext:[[TONKey alloc] initWithPublicKey:publicKey secret:secret]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object( - makeSecureString(localPassword), - makeSecureString(mnemonicPassword), - td::SecureString() - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)getWalletAccountAddressWithPublicKey:(NSString *)publicKey initialWalletId:(int64_t)initialWalletId rwalletInitialPublicKey:(NSString * _Nullable)rwalletInitialPublicKey { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - NSData *publicKeyData = [publicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (publicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in getWalletAccountAddressWithPublicKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - NSData *rwalletInitialPublicKeyData = nil; - if (rwalletInitialPublicKey != nil) { - rwalletInitialPublicKeyData = [rwalletInitialPublicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (rwalletInitialPublicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string for rwalletInitialPublicKey in getWalletAccountAddressWithPublicKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::accountAddress::ID) { - auto result = tonlib_api::move_object_as(object); - [subscriber putNext:[[NSString alloc] initWithUTF8String:result->account_address_.c_str()]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - tonlib_api::object_ptr initialAccountState; - - std::int32_t revision; - if (rwalletInitialPublicKey != nil) { - initialAccountState = make_object( - makeString(rwalletInitialPublicKeyData), - makeString(publicKeyData), - initialWalletId - ); - revision = -1; - } else { - initialAccountState = tonlib_api::move_object_as(make_object( - makeString(publicKeyData), - initialWalletId - )); - revision = 1; - } - - auto query = make_object( - tonlib_api::move_object_as(initialAccountState), - revision - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)getAccountStateWithAddress:(NSString *)accountAddress { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::fullAccountState::ID) { - auto fullAccountState = tonlib_api::move_object_as(object); - int32_t seqNo = -1; - - bool isRWallet = false; - int64_t unlockedBalance = INT64_MAX; - - if (fullAccountState->account_state_->get_id() == tonlib_api::uninited_accountState::ID) { - seqNo = -1; - } else if (fullAccountState->account_state_->get_id() == tonlib_api::wallet_v3_accountState::ID) { - auto v3AccountState = tonlib_api::move_object_as(fullAccountState->account_state_); - seqNo = v3AccountState->seqno_; - } else if (fullAccountState->account_state_->get_id() == tonlib_api::rwallet_accountState::ID) { - auto rwalletAccountState = tonlib_api::move_object_as(fullAccountState->account_state_); - isRWallet = true; - unlockedBalance = rwalletAccountState->unlocked_balance_; - seqNo = rwalletAccountState->seqno_; - }else { - [subscriber putError:[[TONError alloc] initWithText:@"Unknown type"]]; - return; - } - - TONTransactionId *lastTransactionId = nil; - if (fullAccountState->last_transaction_id_ != nullptr) { - lastTransactionId = [[TONTransactionId alloc] initWithLt:fullAccountState->last_transaction_id_->lt_ transactionHash:makeData(fullAccountState->last_transaction_id_->hash_)]; - } - [subscriber putNext:[[TONAccountState alloc] initWithIsInitialized:false isRWallet:isRWallet balance:fullAccountState->balance_ unlockedBalance:unlockedBalance seqno:-1 lastTransactionId:lastTransactionId syncUtime:fullAccountState->sync_utime_]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object(make_object(accountAddress.UTF8String)); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)generateSendGramsQueryFromKey:(TONKey *)key localPassword:(NSData *)localPassword fromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount comment:(NSData *)comment encryptComment:(bool)encryptComment forceIfDestinationNotInitialized:(bool)forceIfDestinationNotInitialized timeout:(int32_t)timeout randomId:(int64_t)randomId { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - if ([_sendGramRandomIds containsObject:@(randomId)]) { - [_sendGramRandomIds addObject:@(randomId)]; - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - } - - NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (publicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in sendGramsFromKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - __weak TON *weakSelf = self; - SQueue *queue = _queue; - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - [queue dispatch:^{ - __strong TON *strongSelf = weakSelf; - if (strongSelf != nil) { - [_sendGramRandomIds removeObject:@(randomId)]; - } - }]; - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::query_info::ID) { - auto result = tonlib_api::move_object_as(object); - TONPreparedSendGramsQuery *preparedQuery = [[TONPreparedSendGramsQuery alloc] initWithQueryId:result->id_ validUntil:result->valid_until_ bodyHash:makeData(result->body_hash_)]; - [subscriber putNext:preparedQuery]; - [subscriber putCompletion]; - } else { - [subscriber putCompletion]; - } - }]; - - tonlib_api::object_ptr inputMessageData; - if (encryptComment && comment.length != 0) { - inputMessageData = make_object( - makeString(comment) - ); - } else { - inputMessageData = make_object( - makeString(comment) - ); - } - std::vector > inputMessages; - inputMessages.push_back(make_object( - make_object(address.UTF8String), - makeString([NSData data]), - amount, - tonlib_api::move_object_as(inputMessageData) - )); - auto inputAction = make_object( - std::move(inputMessages), - forceIfDestinationNotInitialized - ); - - auto query = make_object( - make_object( - make_object( - makeString(publicKeyData), - makeSecureString(key.secret) - ), - makeSecureString(localPassword) - ), - make_object(fromAddress.UTF8String), - timeout, - tonlib_api::move_object_as(inputAction), - nil - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)generateFakeSendGramsQueryFromAddress:(NSString *)fromAddress toAddress:(NSString *)address amount:(int64_t)amount comment:(NSData *)comment encryptComment:(bool)encryptComment forceIfDestinationNotInitialized:(bool)forceIfDestinationNotInitialized timeout:(int32_t)timeout { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::query_info::ID) { - auto result = tonlib_api::move_object_as(object); - TONPreparedSendGramsQuery *preparedQuery = [[TONPreparedSendGramsQuery alloc] initWithQueryId:result->id_ validUntil:result->valid_until_ bodyHash:makeData(result->body_hash_)]; - [subscriber putNext:preparedQuery]; - [subscriber putCompletion]; - } else { - [subscriber putCompletion]; - } - }]; - - tonlib_api::object_ptr inputMessageData; - if (encryptComment && comment.length != 0) { - inputMessageData = make_object( - makeString(comment) - ); - } else { - inputMessageData = make_object( - makeString(comment) - ); - } - std::vector > inputMessages; - inputMessages.push_back(make_object( - make_object(address.UTF8String), - makeString([NSData data]), - amount, - tonlib_api::move_object_as(inputMessageData) - )); - auto inputAction = make_object( - std::move(inputMessages), - forceIfDestinationNotInitialized - ); - - auto query = make_object( - make_object(), - make_object(fromAddress.UTF8String), - timeout, - tonlib_api::move_object_as(inputAction), - nil - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)estimateSendGramsQueryFees:(TONPreparedSendGramsQuery *)preparedQuery { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::query_fees::ID) { - auto result = tonlib_api::move_object_as(object); - TONFees *sourceFees = [[TONFees alloc] initWithInFwdFee:result->source_fees_->in_fwd_fee_ storageFee:result->source_fees_->storage_fee_ gasFee:result->source_fees_->gas_fee_ fwdFee:result->source_fees_->fwd_fee_]; - NSMutableArray *destinationFees = [[NSMutableArray alloc] init]; - for (auto &fee : result->destination_fees_) { - TONFees *destinationFee = [[TONFees alloc] initWithInFwdFee:fee->in_fwd_fee_ storageFee:fee->storage_fee_ gasFee:fee->gas_fee_ fwdFee:fee->fwd_fee_]; - [destinationFees addObject:destinationFee]; - } - - [subscriber putNext:[[TONSendGramsQueryFees alloc] initWithSourceFees:sourceFees destinationFees:destinationFees]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object( - preparedQuery.queryId, - true - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)commitPreparedSendGramsQuery:(TONPreparedSendGramsQuery *)preparedQuery { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::ok::ID) { - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object( - preparedQuery.queryId - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)exportKey:(TONKey *)key localPassword:(NSData *)localPassword { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (publicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in exportKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::exportedKey::ID) { - auto result = tonlib_api::move_object_as(object); - NSMutableArray *wordList = [[NSMutableArray alloc] init]; - for (auto &it : result->word_list_) { - NSString *string = [[NSString alloc] initWithData:[[NSData alloc] initWithBytes:it.data() length:it.size()] encoding:NSUTF8StringEncoding]; - if (string == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error decoding UTF8 string in exportedKey::word_list"]]; - return; - } - [wordList addObject:string]; - } - [subscriber putNext:wordList]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - auto query = make_object( - make_object( - make_object( - makeString(publicKeyData), - makeSecureString(key.secret) - ), - makeSecureString(localPassword) - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)importKeyWithLocalPassword:(NSData *)localPassword mnemonicPassword:(NSData *)mnemonicPassword wordList:(NSArray *)wordList { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::key::ID) { - auto result = tonlib_api::move_object_as(object); - NSString *publicKey = [[NSString alloc] initWithData:[[NSData alloc] initWithBytes:result->public_key_.data() length:result->public_key_.length()] encoding:NSUTF8StringEncoding]; - if (publicKey == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error decoding UTF8 string in importKeyWithLocalPassword"]]; - return; - } - NSData *secret = [[NSData alloc] initWithBytes:result->secret_.data() length:result->secret_.length()]; - [subscriber putNext:[[TONKey alloc] initWithPublicKey:publicKey secret:secret]]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - std::vector wordVector; - for (NSString *word in wordList) { - NSData *wordData = [word dataUsingEncoding:NSUTF8StringEncoding]; - wordVector.push_back(makeSecureString(wordData)); - } - - auto query = make_object( - makeSecureString(localPassword), - makeSecureString(mnemonicPassword), - make_object(std::move(wordVector))); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)deleteKey:(TONKey *)key { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (publicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in deleteKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else { - [subscriber putCompletion]; - } - }]; - - auto query = make_object( - make_object( - makeString(publicKeyData), - makeSecureString(key.secret) - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)deleteAllKeys { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else { - [subscriber putCompletion]; - } - }]; - - auto query = make_object(); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)getTransactionListWithAddress:(NSString * _Nonnull)address lt:(int64_t)lt hash:(NSData * _Nonnull)hash { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - NSData *addressData = [address dataUsingEncoding:NSUTF8StringEncoding]; - if (addressData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in getTransactionListWithAddress"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::raw_transactions::ID) { - auto result = tonlib_api::move_object_as(object); - NSMutableArray *transactions = [[NSMutableArray alloc] init]; - for (auto &it : result->transactions_) { - TONTransactionId *transactionId = [[TONTransactionId alloc] initWithLt:it->transaction_id_->lt_ transactionHash:makeData(it->transaction_id_->hash_)]; - TONTransactionMessage *inMessage = parseTransactionMessage(it->in_msg_); - NSMutableArray * outMessages = [[NSMutableArray alloc] init]; - for (auto &messageIt : it->out_msgs_) { - TONTransactionMessage *outMessage = parseTransactionMessage(messageIt); - if (outMessage != nil) { - [outMessages addObject:outMessage]; - } - } - [transactions addObject:[[TONTransaction alloc] initWithData:makeData(it->data_) transactionId:transactionId timestamp:it->utime_ storageFee:it->storage_fee_ otherFee:it->other_fee_ inMessage:inMessage outMessages:outMessages]]; - } - [subscriber putNext:transactions]; - [subscriber putCompletion]; - } else { - assert(false); - } - }]; - - auto query = make_object( - make_object(), - make_object( - makeString(addressData) - ), - make_object( - lt, - makeString(hash) - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -- (SSignal *)decryptMessagesWithKey:(TONKey * _Nonnull)key localPassword:(NSData * _Nonnull)localPassword messages:(NSArray * _Nonnull)messages { - return [[[[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) { - NSData *publicKeyData = [key.publicKey dataUsingEncoding:NSUTF8StringEncoding]; - if (publicKeyData == nil) { - [subscriber putError:[[TONError alloc] initWithText:@"Error encoding UTF8 string in decryptMessagesWithKey"]]; - return [[SBlockDisposable alloc] initWithBlock:^{}]; - } - - uint64_t requestId = _nextRequestId; - _nextRequestId += 1; - - _requestHandlers[@(requestId)] = [[TONRequestHandler alloc] initWithCompletion:^(tonlib_api::object_ptr &object) { - if (object->get_id() == tonlib_api::error::ID) { - auto error = tonlib_api::move_object_as(object); - [subscriber putError:[[TONError alloc] initWithText:[[NSString alloc] initWithUTF8String:error->message_.c_str()]]]; - } else if (object->get_id() == tonlib_api::msg_dataDecryptedArray::ID) { - auto result = tonlib_api::move_object_as(object); - if (result->elements_.size() != messages.count) { - [subscriber putError:[[TONError alloc] initWithText:@"API interaction error"]]; - } else { - NSMutableArray > *resultMessages = [[NSMutableArray alloc] init]; - int index = 0; - for (auto &it : result->elements_) { - if (it->data_->get_id() == tonlib_api::msg_dataDecryptedText::ID) { - auto dataDecryptedText = tonlib_api::move_object_as(it->data_); - NSString *decryptedString = readString(dataDecryptedText->text_); - if (decryptedString != nil) { - [resultMessages addObject:[[TONTransactionMessageContentsPlainText alloc] initWithText:decryptedString]]; - } else { - [resultMessages addObject:[[TONTransactionMessageContentsEncryptedText alloc] initWithEncryptedData:messages[index]]]; - } - } else { - [resultMessages addObject:[[TONTransactionMessageContentsEncryptedText alloc] initWithEncryptedData:messages[index]]]; - } - index++; - } - [subscriber putNext:resultMessages]; - [subscriber putCompletion]; - } - } else { - assert(false); - } - }]; - - std::vector> inputData; - for (TONEncryptedData *message in messages) { - NSData *sourceAddressData = [message.sourceAddress dataUsingEncoding:NSUTF8StringEncoding]; - if (sourceAddressData == nil) { - continue; - } - - inputData.push_back(make_object( - make_object( - makeString(sourceAddressData) - ), - make_object( - makeString(message.data) - ) - )); - } - - auto query = make_object( - make_object( - make_object( - makeString(publicKeyData), - makeSecureString(key.secret) - ), - makeSecureString(localPassword) - ), - make_object( - std::move(inputData) - ) - ); - _client->send({ requestId, std::move(query) }); - - return [[SBlockDisposable alloc] initWithBlock:^{ - }]; - }] startOn:[SQueue mainQueue]] deliverOn:[SQueue mainQueue]]; -} - -@end diff --git a/submodules/TranslateUI/BUILD b/submodules/TranslateUI/BUILD index b559ed1036..dfe9b76ada 100644 --- a/submodules/TranslateUI/BUILD +++ b/submodules/TranslateUI/BUILD @@ -13,7 +13,6 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/Display:Display", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", diff --git a/submodules/TranslateUI/Sources/LanguageSelectionController.swift b/submodules/TranslateUI/Sources/LanguageSelectionController.swift deleted file mode 100644 index a9d2e4ea86..0000000000 --- a/submodules/TranslateUI/Sources/LanguageSelectionController.swift +++ /dev/null @@ -1,197 +0,0 @@ -import Foundation -import UIKit -import Display -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import TelegramUIPreferences -import ItemListUI -import PresentationDataUtils -import TelegramStringFormatting -import AccountContext - -private final class LanguageSelectionControllerArguments { - let context: AccountContext - let updateLanguageSelected: (String) -> Void - - init(context: AccountContext, updateLanguageSelected: @escaping (String) -> Void) { - self.context = context - self.updateLanguageSelected = updateLanguageSelected - } -} - -private enum LanguageSelectionControllerSection: Int32 { - case languages -} - -private enum LanguageSelectionControllerEntry: ItemListNodeEntry { - case language(Int32, PresentationTheme, String, String, Bool, String) - - var section: ItemListSectionId { - switch self { - case .language: - return LanguageSelectionControllerSection.languages.rawValue - } - } - - var stableId: Int32 { - switch self { - case let .language(index, _, _, _, _, _): - return index - } - } - - static func ==(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool { - switch lhs { - case let .language(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsValue, lhsCode): - if case let .language(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsValue, rhsCode) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsValue == rhsValue, lhsCode == rhsCode { - return true - } else { - return false - } - } - } - - static func <(lhs: LanguageSelectionControllerEntry, rhs: LanguageSelectionControllerEntry) -> Bool { - return lhs.stableId < rhs.stableId - } - - func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { - let arguments = arguments as! LanguageSelectionControllerArguments - switch self { - case let .language(_, _, title, subtitle, value, code): - return LocalizationListItem(presentationData: presentationData, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: { - arguments.updateLanguageSelected(code) - }, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }) - } - } -} - -private func languageSelectionControllerEntries(theme: PresentationTheme, strings: PresentationStrings, selectedLanguage: String, languages: [(String, String, String)]) -> [LanguageSelectionControllerEntry] { - var entries: [LanguageSelectionControllerEntry] = [] - - var index: Int32 = 0 - for (code, title, subtitle) in languages { - entries.append(.language(index, theme, title, subtitle, code == selectedLanguage, code)) - index += 1 - } - - return entries -} - -private struct LanguageSelectionControllerState: Equatable { - enum Section { - case original - case translation - } - - var section: Section - var fromLanguage: String - var toLanguage: String -} - -public func languageSelectionController(context: AccountContext, forceTheme: PresentationTheme? = nil, fromLanguage: String, toLanguage: String, completion: @escaping (String, String) -> Void) -> ViewController { - let statePromise = ValuePromise(LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage), ignoreRepeated: true) - let stateValue = Atomic(value: LanguageSelectionControllerState(section: .translation, fromLanguage: fromLanguage, toLanguage: toLanguage)) - let updateState: ((LanguageSelectionControllerState) -> LanguageSelectionControllerState) -> Void = { f in - statePromise.set(stateValue.modify { f($0) }) - } - - let actionsDisposable = DisposableSet() - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let interfaceLanguageCode = presentationData.strings.baseLanguageCode - - var dismissImpl: (() -> Void)? - - let arguments = LanguageSelectionControllerArguments(context: context, updateLanguageSelected: { code in - updateState { current in - var updated = current - switch updated.section { - case .original: - updated.fromLanguage = code - case .translation: - updated.toLanguage = code - } - return updated - } - }) - - let enLocale = Locale(identifier: "en") - var languages: [(String, String, String)] = [] - var addedLanguages = Set() - for code in popularTranslationLanguages { - if let title = enLocale.localizedString(forLanguageCode: code) { - let languageLocale = Locale(identifier: code) - let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title - let value = (code, title.capitalized, subtitle.capitalized) - if code == interfaceLanguageCode { - languages.insert(value, at: 0) - } else { - languages.append(value) - } - addedLanguages.insert(code) - } - } - - for code in supportedTranslationLanguages { - if !addedLanguages.contains(code), let title = enLocale.localizedString(forLanguageCode: code) { - let languageLocale = Locale(identifier: code) - let subtitle = languageLocale.localizedString(forLanguageCode: code) ?? title - let value = (code, title.capitalized, subtitle.capitalized) - if code == interfaceLanguageCode { - languages.insert(value, at: 0) - } else { - languages.append(value) - } - } - } - - let signal = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, statePromise.get()) - |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in - var presentationData = presentationData - if let forceTheme { - presentationData = presentationData.withUpdated(theme: forceTheme) - } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .sectionControl([presentationData.strings.Translate_Languages_Original, presentationData.strings.Translate_Languages_Translation], 1), leftNavigationButton: ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {}), rightNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { - completion(state.fromLanguage, state.toLanguage) - dismissImpl?() - }), backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) - - let selectedLanguage: String - switch state.section { - case.original: - selectedLanguage = state.fromLanguage - case .translation: - selectedLanguage = state.toLanguage - } - - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: languageSelectionControllerEntries(theme: presentationData.theme, strings: presentationData.strings, selectedLanguage: selectedLanguage, languages: languages), style: .blocks, animateChanges: false) - - return (controllerState, (listState, arguments)) - } - |> afterDisposed { - actionsDisposable.dispose() - } - - let controller = ItemListController(context: context, state: signal) - controller.titleControlValueChanged = { value in - updateState { current in - var updated = current - if value == 0 { - updated.section = .original - } else { - updated.section = .translation - } - return updated - } - } - controller.alwaysSynchronous = true - controller.navigationPresentation = .modal - - dismissImpl = { [weak controller] in - controller?.dismiss(animated: true, completion: nil) - } - - return controller -} diff --git a/submodules/TranslateUI/Sources/LocalizationListItem.swift b/submodules/TranslateUI/Sources/LocalizationListItem.swift index 07ba6eba56..a8ac41666d 100644 --- a/submodules/TranslateUI/Sources/LocalizationListItem.swift +++ b/submodules/TranslateUI/Sources/LocalizationListItem.swift @@ -383,7 +383,7 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) if item.editing.editable, item.removeItem != nil { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)])) } else { strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift b/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift deleted file mode 100644 index 561a0feda1..0000000000 --- a/submodules/TranslateUI/Sources/PlayPauseIconComponent.swift +++ /dev/null @@ -1,120 +0,0 @@ -import Foundation -import UIKit -import ComponentFlow -import ManagedAnimationNode - -enum PlayPauseIconNodeState: Equatable { - case play - case pause -} - -private final class PlayPauseIconNode: ManagedAnimationNode { - private let duration: Double = 0.35 - private var iconState: PlayPauseIconNodeState = .play - - init() { - super.init(size: CGSize(width: 40.0, height: 40.0)) - - self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01)) - } - - func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) { - guard self.iconState != state else { - return - } - - let previousState = self.iconState - self.iconState = state - - switch previousState { - case .pause: - switch state { - case .play: - if animated { - self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration)) - } else { - self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01)) - } - case .pause: - break - } - case .play: - switch state { - case .pause: - if animated { - self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration)) - } else { - self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01)) - } - case .play: - break - } - } - } -} - -final class PlayPauseIconComponent: Component { - let state: PlayPauseIconNodeState - let tintColor: UIColor? - let size: CGSize - - init(state: PlayPauseIconNodeState, tintColor: UIColor?, size: CGSize) { - self.state = state - self.tintColor = tintColor - self.size = size - } - - static func ==(lhs: PlayPauseIconComponent, rhs: PlayPauseIconComponent) -> Bool { - if lhs.state != rhs.state { - return false - } - if lhs.tintColor != rhs.tintColor { - return false - } - if lhs.size != rhs.size { - return false - } - return true - } - - final class View: UIView { - private var component: PlayPauseIconComponent? - private var animationNode: PlayPauseIconNode - - override init(frame: CGRect) { - self.animationNode = PlayPauseIconNode() - - super.init(frame: frame) - - self.addSubview(self.animationNode.view) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: PlayPauseIconComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - if self.component?.state != component.state { - self.component = component - - self.animationNode.enqueueState(component.state, animated: true) - } - - self.animationNode.customColor = component.tintColor - - let animationSize = component.size - let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height)) - self.animationNode.view.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize) - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/TranslateUI/Sources/Translate.swift b/submodules/TranslateUI/Sources/Translate.swift index f587da8b5d..38152a40bc 100644 --- a/submodules/TranslateUI/Sources/Translate.swift +++ b/submodules/TranslateUI/Sources/Translate.swift @@ -86,6 +86,7 @@ public var supportedTranslationLanguages = [ "fa", "pl", "pt", + "pt-BR", "pa", "ro", "ru", @@ -132,7 +133,7 @@ public var popularTranslationLanguages = [ "it", "ja", "ko", - "pt", + "pt-BR", "ru", "es", "uk" diff --git a/submodules/TranslateUI/Sources/TranslateButtonComponent.swift b/submodules/TranslateUI/Sources/TranslateButtonComponent.swift deleted file mode 100644 index d00837baae..0000000000 --- a/submodules/TranslateUI/Sources/TranslateButtonComponent.swift +++ /dev/null @@ -1,184 +0,0 @@ -import Foundation -import UIKit -import Display -import ComponentFlow -import TelegramPresentationData -import BundleIconComponent - -private final class TranslateButtonContentComponent: CombinedComponent { - let theme: PresentationTheme - let title: String - let icon: String - - init( - theme: PresentationTheme, - title: String, - icon: String - ) { - self.theme = theme - self.title = title - self.icon = icon - } - - static func ==(lhs: TranslateButtonContentComponent, rhs: TranslateButtonContentComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.title != rhs.title { - return false - } - if lhs.icon != rhs.icon { - return false - } - return true - } - - static var body: Body { - let title = Child(Text.self) - let icon = Child(BundleIconComponent.self) - - return { context in - let component = context.component - - let icon = icon.update( - component: BundleIconComponent( - name: component.icon, - tintColor: component.theme.list.itemPrimaryTextColor - ), - availableSize: CGSize(width: 30.0, height: 30.0), - transition: context.transition - ) - - let title = title.update( - component: Text( - text: component.title, - font: Font.regular(17.0), - color: component.theme.list.itemPrimaryTextColor - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let sideInset: CGFloat = 16.0 - let textSideInset: CGFloat = 60.0 - - context.add(title - .position(CGPoint(x: textSideInset + title.size.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - context.add(icon - .position(CGPoint(x: sideInset + icon.size.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - return context.availableSize - } - } -} - -final class TranslateButtonComponent: Component { - private let content: TranslateButtonContentComponent - private let theme: PresentationTheme - private let isEnabled: Bool - private let action: () -> Void - - init( - theme: PresentationTheme, - title: String, - icon: String, - isEnabled: Bool, - action: @escaping () -> Void - ) { - self.content = TranslateButtonContentComponent(theme: theme, title: title, icon: icon) - self.isEnabled = isEnabled - self.theme = theme - self.action = action - } - - static func ==(lhs: TranslateButtonComponent, rhs: TranslateButtonComponent) -> Bool { - if lhs.theme !== rhs.theme { - return false - } - if lhs.content !== rhs.content { - return false - } - if lhs.isEnabled != rhs.isEnabled { - return false - } - return true - } - - final class View: HighlightTrackingButton { - private let backgroundView: UIView - private let centralContentView: ComponentHostView - - private var component: TranslateButtonComponent? - - override init(frame: CGRect) { - self.backgroundView = UIView() - self.backgroundView.isUserInteractionEnabled = false - - self.centralContentView = ComponentHostView() - self.centralContentView.isUserInteractionEnabled = false - - super.init(frame: frame) - - self.backgroundView.clipsToBounds = true - - self.addSubview(self.backgroundView) - self.addSubview(self.centralContentView) - - self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, let component = strongSelf.component { - if highlighted { - strongSelf.backgroundView.backgroundColor = component.theme.list.itemHighlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor - }) - } - } - } - - self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc func pressed() { - if let component = self.component { - component.action() - } - } - - public func update(component: TranslateButtonComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { - self.component = component - - self.backgroundView.backgroundColor = component.theme.list.itemBlocksBackgroundColor - self.backgroundView.layer.cornerRadius = 26.0 - - let _ = self.centralContentView.update( - transition: transition, - component: AnyComponent(component.content), - environment: {}, - containerSize: availableSize - ) - transition.setFrame(view: self.centralContentView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) - - self.centralContentView.alpha = component.isEnabled ? 1.0 : 0.4 - self.isUserInteractionEnabled = component.isEnabled - - return availableSize - } - } - - func makeView() -> View { - return View() - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - return view.update(component: self, availableSize: availableSize, transition: transition) - } -} diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index b3f965462e..8cbde2fedb 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -5,1175 +5,8 @@ import AsyncDisplayKit import TelegramCore import SwiftSignalKit import AccountContext -import TelegramPresentationData -import PresentationDataUtils -import Speak -import ComponentFlow -import ViewControllerComponent -import MultilineTextComponent -import MultilineTextWithEntitiesComponent -import BundleIconComponent -import UndoUI import SwiftUI -import ResizableSheetComponent -import GlassBarButtonComponent -import ListSectionComponent -import ListActionItemComponent -import PlainButtonComponent -import ButtonComponent -import TextFormat -import Pasteboard -import ContextUI -import TranslationLanguagesContextMenuContent import TelegramUIPreferences -import Markdown - -private let translateToTag = GenericComponentViewTag() - -private func generateExpandBackground(size: CGSize, color: UIColor) -> UIImage { - return generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - var locations: [CGFloat] = [0.0, 1.0] - let colors: [CGColor] = [color.withAlphaComponent(0.0).cgColor, color.cgColor] - - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 40.0, y: size.height), options: CGGradientDrawingOptions()) - context.setFillColor(color.cgColor) - context.fill(CGRect(origin: CGPoint(x: 40.0, y: 0.0), size: CGSize(width: size.width - 40.0, height: size.height))) - })! -} - -private final class SheetContent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - let context: AccountContext - let text: String - let entities: [MessageTextEntity] - let fromLanguage: String? - let toLanguage: String - let copyTranslation: ((String, [MessageTextEntity]) -> Void)? - let replaceText: ((String, [MessageTextEntity]) -> Void)? - let translateChat: ((String, String) -> Void)? - let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void - let expand: () -> Void - let dismiss: () -> Void - - init( - context: AccountContext, - text: String, - entities: [MessageTextEntity], - fromLanguage: String?, - toLanguage: String, - copyTranslation: ((String, [MessageTextEntity]) -> Void)?, - replaceText: ((String, [MessageTextEntity]) -> Void)?, - translateChat: ((String, String) -> Void)?, - changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, - expand: @escaping () -> Void, - dismiss: @escaping () -> Void - ) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.copyTranslation = copyTranslation - self.replaceText = replaceText - self.translateChat = translateChat - self.changeLanguage = changeLanguage - self.expand = expand - self.dismiss = dismiss - } - - static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.entities != rhs.entities { - return false - } - if lhs.fromLanguage != rhs.fromLanguage { - return false - } - if lhs.toLanguage != rhs.toLanguage { - return false - } - return true - } - - final class State: ComponentState { - private let context: AccountContext - - var fromLanguage: String? - let text: String - let entities: [MessageTextEntity] - var textExpanded: Bool = false - - var toLanguage: String - var tone: TranslationTone = .neutral - - var translatedText: (String, [MessageTextEntity])? - - private let expand: () -> Void - - private var translationDisposable = MetaDisposable() - - fileprivate var isSpeakingOriginalText: Bool = false - fileprivate var isSpeakingTranslatedText: Bool = false - private var speechHolder: SpeechSynthesizerHolder? - fileprivate var availableSpeakLanguages: Set - - fileprivate var moreBackgroundImage: (CGSize, UIImage, UIColor)? - - private let useAlternativeTranslation: Bool - - weak var controller: TranslateScreen? - - init(context: AccountContext, fromLanguage: String?, text: String, entities: [MessageTextEntity], toLanguage: String, expand: @escaping () -> Void) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.expand = expand - self.availableSpeakLanguages = supportedSpeakLanguages() - - let translationConfiguration = TranslationConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) - var useAlternativeTranslation = false - switch translationConfiguration.manual { - case .alternative: - useAlternativeTranslation = true - default: - break - } - self.useAlternativeTranslation = useAlternativeTranslation - - super.init() - - self.translationDisposable.set((self.translate(text: text, entities: entities, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - deinit { - self.speechHolder?.stop() - self.translationDisposable.dispose() - } - - func translate(text: String, entities: [MessageTextEntity], fromLang: String?, toLang: String) -> Signal<(String, [MessageTextEntity])?, TranslationError> { - if self.useAlternativeTranslation { - return alternativeTranslateText(text: text, fromLang: fromLang, toLang: toLang) - } else { - return self.context.engine.messages.translate(text: text, toLang: toLang, entities: entities, tone: self.tone) - } - } - - func changeTone(_ tone: TranslationTone) { - guard self.tone != tone else { - return - } - self.tone = tone - self.translatedText = nil - self.updated(transition: .immediate) - - self.translationDisposable.set((self.translate(text: self.text, entities: self.entities, fromLang: self.fromLanguage, toLang: self.toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - func changeLanguage(fromLanguage: String, toLanguage: String) { - guard self.fromLanguage != fromLanguage || self.toLanguage != toLanguage else { - return - } - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.translatedText = nil - self.updated(transition: .immediate) - - self.translationDisposable.set((self.translate(text: self.text, entities: self.entities, fromLang: fromLanguage, toLang: toLanguage) |> deliverOnMainQueue).start(next: { [weak self] text in - guard let strongSelf = self else { - return - } - strongSelf.translatedText = text - strongSelf.updated(transition: .immediate) - }, error: { error in - - })) - } - - func expandText() { - self.textExpanded = true - self.updated(transition: .immediate) - - self.expand() - } - - func speakOriginalText() { - if let speechHolder = self.speechHolder { - self.speechHolder = nil - speechHolder.stop() - } - - if self.isSpeakingOriginalText { - self.isSpeakingOriginalText = false - } else { - self.isSpeakingTranslatedText = false - - self.isSpeakingOriginalText = true - self.speechHolder = speakText(context: self.context, text: self.text) - self.speechHolder?.completion = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isSpeakingOriginalText = false - strongSelf.updated(transition: .immediate) - } - } - self.updated(transition: .immediate) - } - - func speakTranslatedText() { - guard let translatedText = self.translatedText else { - return - } - - if let speechHolder = self.speechHolder { - self.speechHolder = nil - speechHolder.stop() - } - - if self.isSpeakingTranslatedText { - self.isSpeakingTranslatedText = false - } else { - self.isSpeakingOriginalText = false - - self.isSpeakingTranslatedText = true - self.speechHolder = speakText(context: self.context, text: translatedText.0) - self.speechHolder?.completion = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.isSpeakingTranslatedText = false - strongSelf.updated(transition: .immediate) - } - } - self.updated(transition: .immediate) - } - - func presentLanguageSelection() { - guard let controller = self.controller else { - return - } - - guard let sourceView = controller.node.hostView.findTaggedView(tag: translateToTag) else { - return - } - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - var languageCode = presentationData.strings.baseLanguageCode - let rawSuffix = "-raw" - if languageCode.hasSuffix(rawSuffix) { - languageCode = String(languageCode.dropLast(rawSuffix.count)) - } - - let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] sharedData in - guard let self else { - return - } - let settings: TranslationSettings - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) { - settings = current - } else { - settings = TranslationSettings.defaultSettings - } - - var addedLanguages = Set() - - var topLanguages: [String] = [] - let langCode = normalizeTranslationLanguage(languageCode) - - topLanguages.append("tone_formal") - topLanguages.append("tone_neutral") - topLanguages.append("tone_casual") - - topLanguages.append("") - - var ignoredLanguages: Set - if let current = settings.ignoredLanguages { - ignoredLanguages = Set(current) - } else { - ignoredLanguages = Set([langCode]) - for language in systemLanguageCodes() { - ignoredLanguages.insert(language) - } - } - for code in supportedTranslationLanguages { - if ignoredLanguages.contains(code) { - topLanguages.append(code) - } - } - - topLanguages.append(" ") - - var languages: [(String, String)] = [] - let languageLocale = Locale(identifier: langCode) - - for code in topLanguages { - if !addedLanguages.contains(code) { - let displayTitle: String - if code.hasPrefix("tone_") { - switch code { - case "tone_formal": - displayTitle = "Formal" - case "tone_neutral": - displayTitle = "Neutral" - case "tone_casual": - displayTitle = "Casual" - default: - displayTitle = "" - } - } else { - displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" - } - - let value = (code, displayTitle) - if code == languageCode { - languages.insert(value, at: 4) - } else { - languages.append(value) - } - addedLanguages.insert(code) - } - } - - for code in supportedTranslationLanguages { - if !addedLanguages.contains(code) { - let displayTitle = languageLocale.localizedString(forLanguageCode: code) ?? "" - let value = (code, displayTitle) - if code == languageCode { - languages.insert(value, at: 4) - } else { - languages.append(value) - } - addedLanguages.insert(code) - } - } - - var selectedLanguages = Set() - selectedLanguages.insert("tone_\(self.tone.rawValue)") - selectedLanguages.insert(self.toLanguage) - - var dismissImpl: (() -> Void)? - let items = ContextController.Items( - content: .custom( - TranslationLanguagesContextMenuContent( - context: self.context, - languages: languages, - selectedLanguages: selectedLanguages, - back: nil, - selectLanguage: { [weak self] language in - guard let self else { - return - } - if language.hasPrefix("tone_") { - let tone = String(language.dropFirst(5)) - self.changeTone(TranslationTone(rawValue: tone)!) - } else { - self.changeLanguage(fromLanguage: self.fromLanguage ?? "", toLanguage: language) - } - dismissImpl?() - } - ) - ) - ) - - let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(items), gesture: nil) - controller.presentInGlobalOverlay(contextController) - - dismissImpl = { [weak contextController] in - contextController?.dismiss() - } - }) - } - } - - func makeState() -> State { - return State(context: self.context, fromLanguage: self.fromLanguage, text: self.text, entities: self.entities, toLanguage: self.toLanguage, expand: self.expand) - } - - static var body: Body { - let textBackground = Child(RoundedRectangle.self) - - let originalTitle = Child(PlainButtonComponent.self) - let originalText = Child(MultilineTextComponent.self) - - let originalMoreBackground = Child(Image.self) - let originalMoreButton = Child(Button.self) - - let originalSpeakButton = Child(Button.self) - - let translationTitle = Child(PlainButtonComponent.self) - let translationText = Child(MultilineTextComponent.self) - let translationPlaceholder = Child(RoundedRectangle.self) - let translationSpeakButton = Child(Button.self) - - let textStripe = Child(Rectangle.self) -// let textSection = Child(ListSectionComponent.self) - let actionsSection = Child(ListSectionComponent.self) - - return { context in - let environment = context.environment[ViewControllerComponentContainer.Environment.self].value - let state = context.state - let theme = environment.theme.withModalBlocksBackground() - let strings = environment.strings - let presentationData = context.component.context.sharedContext.currentPresentationData.with { $0 } - let component = context.component - - if state.controller == nil { - state.controller = environment.controller() as? TranslateScreen - } - - let sideInset: CGFloat = 16.0 - - - let textTopInset: CGFloat = 16.0 - let textSpacing: CGFloat = 5.0 - let itemSpacing: CGFloat = 20.0 - let textSideInset: CGFloat = 16.0 - - - var contentHeight: CGFloat = 82.0 - - let textFont = Font.regular(20.0) - let boldTextFont = Font.semibold(20.0) - let italicTextFont = Font.with(size: 20.0, weight: .regular, traits: .italic) - let boldItalicTextFont = Font.semiboldItalic(20.0) - let fixedTextFont = Font.with(size: 20.0, design: .monospace) - - var languageCode = environment.strings.baseLanguageCode - let rawSuffix = "-raw" - if languageCode.hasSuffix(rawSuffix) { - languageCode = String(languageCode.dropLast(rawSuffix.count)) - } - let locale = Locale(identifier: languageCode) - let fromLanguage: String - if let languageCode = state.fromLanguage { - fromLanguage = locale.localizedString(forLanguageCode: languageCode) ?? "" - } else { - fromLanguage = "" - } - - let _ = sideInset - let _ = fromLanguage - - let originalTitleString = parseMarkdownIntoAttributedString("Detected as **\(fromLanguage)**", attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemSecondaryTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemAccentColor), link: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.list.itemPrimaryTextColor), linkAttribute: { _ in return nil })) - - let originalTitle = originalTitle.update( - component: PlainButtonComponent( - content: AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "label", component: AnyComponent( - MultilineTextComponent( - text: .plain(originalTitleString), - horizontalAlignment: .natural, - maximumNumberOfLines: 1 - ) - )), - AnyComponentWithIdentity(id: "icon", component: AnyComponent( - BundleIconComponent(name: "Item List/ContextDisclosureArrow", tintColor: theme.list.itemAccentColor, maxSize: CGSize(width: 8.0, height: 11.0)) - )) - ], spacing: 3.0) - ), - action: { - component.changeLanguage(state.fromLanguage ?? "", state.toLanguage, { fromLang, toLang in - state.changeLanguage(fromLanguage: fromLang, toLanguage: toLang) - }) - }, - animateScale: false - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let originalAttributedText = stringWithAppliedEntities(state.text, entities: state.entities, baseColor: theme.list.itemPrimaryTextColor, linkColor: theme.list.itemPrimaryTextColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) - - let originalText = originalText.update( - component: MultilineTextComponent( - text: .plain(originalAttributedText), - horizontalAlignment: .natural, - maximumNumberOfLines: state.textExpanded ? 0 : 1, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - (state.textExpanded ? 30.0 : 0.0), height: context.availableSize.height), - transition: .immediate - ) - - var toLanguage = locale.localizedString(forLanguageCode: state.toLanguage) ?? "" - if state.tone != .neutral { - toLanguage += " (\(state.tone.rawValue.capitalized))" - } - let translationTitle = translationTitle.update( - component: PlainButtonComponent( - content: AnyComponent( - HStack([ - AnyComponentWithIdentity(id: "label", component: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: toLanguage, font: Font.semibold(13.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .natural)), - horizontalAlignment: .natural, - maximumNumberOfLines: 1 - ) - )), - AnyComponentWithIdentity(id: "icon", component: AnyComponent( - BundleIconComponent(name: "Item List/ContextDisclosureArrow", tintColor: theme.list.itemAccentColor, maxSize: CGSize(width: 8.0, height: 11.0)) - )) - ], spacing: 3.0) - ), - action: { [weak state] in - state?.presentLanguageSelection() -// component.changeLanguage(state.fromLanguage ?? "", state.toLanguage, { fromLang, toLang in -// state.changeLanguage(fromLanguage: fromLang, toLanguage: toLang) -// }) - }, - animateScale: false, - tag: translateToTag - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: CGFloat.greatestFiniteMagnitude), - transition: .immediate - ) - - let translationTextHeight: CGFloat - - var maybeTranslationText: _UpdatedChildComponent? = nil - var maybeTranslationPlaceholder: _UpdatedChildComponent? = nil - if let translatedText = state.translatedText { - let attributedText = stringWithAppliedEntities(translatedText.0, entities: translatedText.1, baseColor: theme.list.itemAccentColor, linkColor: theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: boldTextFont, italicFont: italicTextFont, boldItalicFont: boldItalicTextFont, fixedFont: fixedTextFont, blockQuoteFont: textFont, message: nil) - - maybeTranslationText = translationText.update( - component: MultilineTextComponent( - text: .plain(attributedText), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.1 - ), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - 30.0, height: context.availableSize.height), - transition: .immediate - ) - translationTextHeight = maybeTranslationText?.size.height ?? 0.0 - } else { - maybeTranslationPlaceholder = translationPlaceholder.update( - component: RoundedRectangle(color: theme.list.itemAccentColor.withAlphaComponent(0.17), cornerRadius: 6.0), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0 - 42.0, height: 12.0), - transition: .immediate - ) - translationTextHeight = 22.0 - } - - let topInset = contentHeight - let textBackgroundOrigin = CGPoint(x: sideInset, y: topInset) - - let textStripe = textStripe.update( - component: Rectangle(color: theme.list.itemPlainSeparatorColor), - availableSize: CGSize(width: context.availableSize.width - (sideInset + textSideInset) * 2.0, height: UIScreenPixel), - transition: .immediate - ) - - let textBackgroundSize = CGSize(width: context.availableSize.width - sideInset * 2.0, height: textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationTextHeight + itemSpacing) - - let textBackground = textBackground.update( - component: RoundedRectangle(color: theme.list.itemBlocksBackgroundColor, cornerRadius: 26.0), - availableSize: textBackgroundSize, - transition: context.transition - ) - - context.add(textBackground - .position(CGPoint(x: textBackgroundOrigin.x + textBackgroundSize.width / 2.0, y: topInset + textBackgroundSize.height / 2.0)) - ) - - context.add(textStripe - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + textStripe.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing)) - ) - - context.add(originalTitle - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + originalTitle.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height / 2.0)) - ) - context.add(originalText - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + originalText.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height / 2.0)) - ) - - if state.textExpanded { - if let fromLanguage = state.fromLanguage, state.availableSpeakLanguages.contains(fromLanguage) { - var checkColor = theme.list.itemCheckColors.foregroundColor - if checkColor.rgb == theme.list.itemPrimaryTextColor.rgb { - checkColor = theme.list.plainBackgroundColor - } - - let originalSpeakButton = originalSpeakButton.update( - component: Button( - content: AnyComponent(ZStack([ - AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle( - fillColor: theme.list.itemPrimaryTextColor, - size: CGSize(width: 26.0, height: 26.0) - ))), - AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( - state: state.isSpeakingOriginalText ? .pause : .play, - tintColor: checkColor, - size: CGSize(width: 20.0, height: 20.0) - ))), - ])), - action: { [weak state] in - guard let state = state else { - return - } - state.speakOriginalText() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: CGSize(width: 26.0, height: 26.0), - transition: .immediate - ) - - context.add(originalSpeakButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalSpeakButton.size.width / 2.0 + 9.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height - originalSpeakButton.size.height / 2.0 - 2.0 + 12.0)) - ) - } - } else { - let originalMoreButton = originalMoreButton.update( - component: Button( - content: AnyComponent(Text(text: strings.PeerInfo_BioExpand, font: Font.regular(17.0), color: theme.list.itemAccentColor)), - action: { [weak state] in - guard let state = state else { - return - } - state.expandText() - } - ), - availableSize: context.availableSize, - transition: .immediate - ) - - let originalMoreBackgroundSize = CGSize(width: originalMoreButton.size.width + 50.0, height: originalMoreButton.size.height) - let originalMoreBackgroundImage: UIImage - let backgroundColor = theme.list.itemBlocksBackgroundColor - if let (size, image, color) = state.moreBackgroundImage, size == originalMoreBackgroundSize && color == backgroundColor { - originalMoreBackgroundImage = image - } else { - originalMoreBackgroundImage = generateExpandBackground(size: originalMoreBackgroundSize, color: backgroundColor) - state.moreBackgroundImage = (originalMoreBackgroundSize, originalMoreBackgroundImage, backgroundColor) - } - let originalMoreBackground = originalMoreBackground.update( - component: Image(image: originalMoreBackgroundImage, tintColor: backgroundColor), - availableSize: originalMoreBackgroundSize, - transition: .immediate - ) - - context.add(originalMoreBackground - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalMoreBackground.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalMoreBackground.size.height / 2.0 + 2.0)) - ) - - context.add(originalMoreButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - originalMoreButton.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height / 2.0 + 1.0 - UIScreenPixel)) - ) - } - - context.add(translationTitle - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationTitle.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height / 2.0)) - ) - - if let translationText = maybeTranslationText { - context.add(translationText - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationText.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationText.size.height / 2.0)) - ) - - if state.availableSpeakLanguages.contains(state.toLanguage) { - let translationSpeakButton = translationSpeakButton.update( - component: Button( - content: AnyComponent(ZStack([ - AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle( - fillColor: theme.list.itemAccentColor, - size: CGSize(width: 26.0, height: 26.0) - ))), - AnyComponentWithIdentity(id: "a", component: AnyComponent(PlayPauseIconComponent( - state: state.isSpeakingTranslatedText ? .pause : .play, - tintColor: theme.list.itemCheckColors.foregroundColor, - size: CGSize(width: 20.0, height: 20.0) - ))), - ])), - action: { [weak state] in - guard let state = state else { - return - } - state.speakTranslatedText() - } - ).minSize(CGSize(width: 44.0, height: 44.0)), - availableSize: CGSize(width: 26.0, height: 26.0), - transition: .immediate - ) - - context.add(translationSpeakButton - .position(CGPoint(x: context.availableSize.width - sideInset - textSideInset - translationSpeakButton.size.width / 2.0 + 9.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationTextHeight - translationSpeakButton.size.height / 2.0 - 2.0 + 12.0)) - .appear(.default()) - .disappear(.default()) - ) - } - } else if let translationPlaceholder = maybeTranslationPlaceholder { - context.add(translationPlaceholder - .position(CGPoint(x: textBackgroundOrigin.x + textSideInset + translationPlaceholder.size.width / 2.0, y: textBackgroundOrigin.y + textTopInset + originalTitle.size.height + textSpacing + originalText.size.height + itemSpacing + textTopInset + translationTitle.size.height + textSpacing + translationPlaceholder.size.height / 2.0 + 4.0)) - ) - } - - contentHeight += textBackgroundSize.height - contentHeight += 24.0 - -// let textSectionItems: [AnyComponentWithIdentity] = [] -// let textSection = textSection.update( -// component: ListSectionComponent( -// theme: theme, -// style: .glass, -// header: nil, -// footer: nil, -// items: textSectionItems -// ), -// availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), -// transition: context.transition -// ) -// context.add(textSection -// .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + textSection.size.height / 2.0)) -// ) -// contentHeight += textSection.size.height -// contentHeight += 24.0 - - var actionsSectionItems: [AnyComponentWithIdentity] = [] - if let replaceText = component.replaceText { - actionsSectionItems.append(AnyComponentWithIdentity(id: "replace", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Replace with Translation", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Replace", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - replaceText(state.translatedText?.0 ?? state.text, state.translatedText?.1 ?? state.entities) - component.dismiss() - } - )))) - } - if let copyTranslation = component.copyTranslation { - actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: strings.Translate_CopyTranslation, - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - copyTranslation(state.translatedText?.0 ?? "", state.translatedText?.1 ?? []) - } - )))) - } - if let translateChat = component.translateChat { - actionsSectionItems.append(AnyComponentWithIdentity(id: "translate", component: AnyComponent(ListActionItemComponent( - theme: theme, - style: .glass, - title: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString( - string: "Translate Entire Chat", - font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), - textColor: theme.list.itemAccentColor - )), - maximumNumberOfLines: 1 - ) - ), - leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Translate", tintColor: theme.list.itemAccentColor))), false), - action: { [weak state] _ in - guard let state else { - return - } - translateChat(state.fromLanguage ?? "", state.toLanguage) - component.dismiss() - } - )))) - } - - if !actionsSectionItems.isEmpty { - let actionsSection = actionsSection.update( - component: ListSectionComponent( - theme: theme, - style: .glass, - header: nil, - footer: nil, - items: actionsSectionItems - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), - transition: context.transition - ) - context.add(actionsSection - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentHeight + actionsSection.size.height / 2.0)) - ) - contentHeight += actionsSection.size.height - } - - contentHeight += 32.0 - contentHeight += 52.0 + 30.0 - - return CGSize(width: context.availableSize.width, height: contentHeight) - } - } -} - -private final class TranslateSheetComponent: CombinedComponent { - typealias EnvironmentType = ViewControllerComponentContainer.Environment - - private let context: AccountContext - private let text: String - private let entities: [MessageTextEntity] - private let fromLanguage: String? - private let toLanguage: String - private let copyTranslation: ((String, [MessageTextEntity]) -> Void)? - private let replaceText: ((String, [MessageTextEntity]) -> Void)? - private let translateChat: ((String, String) -> Void)? - private let changeLanguage: (String, String, @escaping (String, String) -> Void) -> Void - private let openCocoonInfo: () -> Void - - init( - context: AccountContext, - text: String, - entities: [MessageTextEntity], - fromLanguage: String?, - toLanguage: String, - copyTranslation: ((String, [MessageTextEntity]) -> Void)?, - replaceText: ((String, [MessageTextEntity]) -> Void)?, - translateChat: ((String, String) -> Void)? = nil, - changeLanguage: @escaping (String, String, @escaping (String, String) -> Void) -> Void, - openCocoonInfo: @escaping () -> Void - ) { - self.context = context - self.text = text - self.entities = entities - self.fromLanguage = fromLanguage - self.toLanguage = toLanguage - self.copyTranslation = copyTranslation - self.replaceText = replaceText - self.translateChat = translateChat - self.changeLanguage = changeLanguage - self.openCocoonInfo = openCocoonInfo - } - - static func ==(lhs: TranslateSheetComponent, rhs: TranslateSheetComponent) -> Bool { - return true - } - - static var body: Body { - let sheet = Child(ResizableSheetComponent<(EnvironmentType)>.self) - let animateOut = StoredActionSlot(Action.self) - - return { context in - let environment = context.environment[EnvironmentType.self] - - let controller = environment.controller - - let dismiss: (Bool) -> Void = { animated in - if animated { - animateOut.invoke(Action { _ in - if let controller = controller() { - controller.dismiss(completion: nil) - } - }) - } else { - if let controller = controller() { - controller.dismiss(completion: nil) - } - } - } - - let theme = environment.theme.withModalBlocksBackground() - let strings = environment.strings - - let openCocoonInfo = context.component.openCocoonInfo - - let sheet = sheet.update( - component: ResizableSheetComponent( - content: AnyComponent(SheetContent( - context: context.component.context, - text: context.component.text, - entities: context.component.entities, - fromLanguage: context.component.fromLanguage, - toLanguage: context.component.toLanguage, - copyTranslation: context.component.copyTranslation, - replaceText: context.component.replaceText, - translateChat: context.component.translateChat, - changeLanguage: context.component.changeLanguage, - expand: {}, - dismiss: { - dismiss(true) - } - )), - titleItem: AnyComponent( - VStack([ - AnyComponentWithIdentity(id: "title", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "Translation", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor))) - )), - AnyComponentWithIdentity(id: "subtitle", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "powered by Cocoon", font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor))) - )) - ], spacing: 1.0) - ), - leftItem: AnyComponent( - GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "close", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Close", - tintColor: theme.chat.inputPanel.panelControlColor - ) - )), - action: { _ in - dismiss(true) - } - ) - ), - rightItem: AnyComponent( - GlassBarButtonComponent( - size: CGSize(width: 44.0, height: 44.0), - backgroundColor: nil, - isDark: theme.overallDarkAppearance, - state: .glass, - component: AnyComponentWithIdentity(id: "info", component: AnyComponent( - BundleIconComponent( - name: "Navigation/Question", - tintColor: theme.chat.inputPanel.panelControlColor - ) - )), - action: { _ in - openCocoonInfo() - } - ) - ), - bottomItem: AnyComponent( - ButtonComponent( - background: ButtonComponent.Background( - style: .glass, - color: theme.list.itemCheckColors.fillColor, - foreground: theme.list.itemCheckColors.foregroundColor, - pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) - ), - content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent( - ButtonTextContentComponent( - text: strings.Common_OK, - badge: 0, - textColor: theme.list.itemCheckColors.foregroundColor, - badgeBackground: theme.list.itemCheckColors.foregroundColor, - badgeForeground: theme.list.itemCheckColors.fillColor - ) - ) - ), - action: { - dismiss(true) - } - ) - ), - backgroundColor: .color(theme.list.modalBlocksBackgroundColor), - animateOut: animateOut - ), - environment: { - environment - ResizableSheetComponentEnvironment( - theme: theme, - statusBarHeight: environment.statusBarHeight, - safeInsets: environment.safeInsets, - inputHeight: 0.0, - metrics: environment.metrics, - deviceMetrics: environment.deviceMetrics, - isDisplaying: environment.value.isVisible, - isCentered: environment.metrics.widthClass == .regular, - screenSize: context.availableSize, - regularMetricsSize: CGSize(width: 430.0, height: 900.0), - dismiss: { animated in - dismiss(animated) - } - ) - }, - availableSize: context.availableSize, - transition: context.transition - ) - - context.add(sheet - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) - ) - - return context.availableSize - } - } -} - -public final class TranslateScreen: ViewControllerComponentContainer { - private let context: AccountContext - - public var pushController: (ViewController) -> Void = { _ in } - public var presentController: (ViewController) -> Void = { _ in } - - public init( - context: AccountContext, - forceTheme: PresentationTheme? = nil, - text: String, - entities: [MessageTextEntity] = [], - canCopy: Bool, - fromLanguage: String?, - toLanguage: String? = nil, - ignoredLanguages: [String]? = nil, - replaceText: ((String, [MessageTextEntity]) -> Void)? = nil, - translateChat: ((String, String) -> Void)? = nil - ) { - self.context = context - - let theme: ViewControllerComponentContainer.Theme - if let forceTheme { - theme = .custom(forceTheme) - } else { - theme = .default - } - - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - var baseLanguageCode = presentationData.strings.baseLanguageCode - let rawSuffix = "-raw" - if baseLanguageCode.hasSuffix(rawSuffix) { - baseLanguageCode = String(baseLanguageCode.dropLast(rawSuffix.count)) - } - - let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages) - - var toLanguage = toLanguage ?? baseLanguageCode - if toLanguage == fromLanguage { - if fromLanguage == "en" { - toLanguage = dontTranslateLanguages.first(where: { $0 != "en" }) ?? "en" - } else { - toLanguage = "en" - } - if toLanguage == "en" && fromLanguage == "en" { - if let anyOtherLanguage = NSLocale.preferredLanguages.first(where: { !$0.hasPrefix("en-") }) { - toLanguage = anyOtherLanguage - } - } - } - - toLanguage = normalizeTranslationLanguage(toLanguage) - - var copyTranslationImpl: ((String, [MessageTextEntity]) -> Void)? - var changeLanguageImpl: ((String, String, @escaping (String, String) -> Void) -> Void)? - var openCocoonInfoImpl: (() -> Void)? - - super.init( - context: context, - component: TranslateSheetComponent( - context: context, - text: text, - entities: entities, - fromLanguage: fromLanguage, - toLanguage: toLanguage, - copyTranslation: !canCopy ? nil : { text, entities in - copyTranslationImpl?(text, entities) - }, - replaceText: replaceText, - translateChat: translateChat, - changeLanguage: { fromLang, toLang, completion in - changeLanguageImpl?(fromLang, toLang, completion) - }, - openCocoonInfo: { - openCocoonInfoImpl?() - } - ), - navigationBarAppearance: .none, - statusBarStyle: .ignore, - theme: theme - ) - - self.statusBar.statusBarStyle = .Ignore - self.navigationPresentation = .flatModal - self.blocksBackgroundWhenInOverlay = true - self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) - - copyTranslationImpl = { [weak self] text, entities in - storeMessageTextInPasteboard(text, entities: entities) - - let content = UndoOverlayContent.copy(text: presentationData.strings.Conversation_TextCopied) - self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - self?.dismissAnimated() - } - - changeLanguageImpl = { [weak self] fromLang, toLang, completion in - let pushController = self?.pushController - let presentController = self?.presentController - let controller = languageSelectionController(context: context, forceTheme: forceTheme, fromLanguage: fromLang, toLanguage: toLang, completion: { fromLang, toLang in - let controller = TranslateScreen(context: context, forceTheme: forceTheme, text: text, entities: entities, canCopy: canCopy, fromLanguage: fromLang, toLanguage: toLang, ignoredLanguages: ignoredLanguages, replaceText: replaceText) - controller.pushController = pushController ?? { _ in } - controller.presentController = presentController ?? { _ in } - presentController?(controller) - }) - - self?.dismissAnimated() - - pushController?(controller) - } - - openCocoonInfoImpl = { [weak self] in - let controller = context.sharedContext.makeCocoonInfoScreen(context: context) - self?.pushController(controller) - } - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func dismissAnimated() { - if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View { - view.dismissAnimated() - } - } -} public func presentTranslateScreen( context: AccountContext, @@ -1205,11 +38,6 @@ public func presentTranslateScreen( if useSystemTranslation { presentSystemTranslateScreen(context: context, text: text) } else { - let controller = TranslateScreen(context: context, text: text, entities: entities, canCopy: canCopy, fromLanguage: fromLanguage, toLanguage: toLanguage, ignoredLanguages: ignoredLanguages, replaceText: replaceText, translateChat: translateChat) - controller.pushController = pushController - controller.presentController = presentController - controller.wasDismissed = wasDismissed - display(controller) } } @@ -1258,17 +86,3 @@ struct TranslateScreenHostingView: View { } } } - -private final class GiftViewContextReferenceContentSource: ContextReferenceContentSource { - private let controller: ViewController - private let sourceView: UIView - - init(controller: ViewController, sourceView: UIView) { - self.controller = controller - self.sourceView = sourceView - } - - func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) - } -} diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 47f9cd7ab1..bc0e2e3aba 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -361,14 +361,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil - self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0) + self.animationNode = AnimationNode(animation: "anim_banned", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 0.9) self.animatedStickerNode = nil let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText - self.textNode.maximumNumberOfLines = 2 + self.textNode.maximumNumberOfLines = 5 displayUndo = false self.originalRemainingSeconds = 5 case let .importedMessage(text): @@ -597,7 +597,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true) } if let resourceReference = resourceReference { - updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference) + updatedFetchSignal = context.engine.resources.fetch(reference: resourceReference, userLocation: .other, userContentType: .other) |> mapError { _ -> EngineMediaResource.Fetch.Error in return .generic } @@ -914,7 +914,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { updatedImageSignal = chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: resource._asResource(), animated: true) } if let resourceReference = resourceReference { - updatedFetchSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: resourceReference) + updatedFetchSignal = context.engine.resources.fetch(reference: resourceReference, userLocation: .other, userContentType: .other) |> mapError { _ -> EngineMediaResource.Fetch.Error in return .generic } diff --git a/submodules/UrlHandling/BUILD b/submodules/UrlHandling/BUILD index 4d6dcfb0a7..f1ecafd4b7 100644 --- a/submodules/UrlHandling/BUILD +++ b/submodules/UrlHandling/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/MtProtoKit:MtProtoKit", "//submodules/AccountContext:AccountContext", diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 34257ee68c..5a92356531 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import MtProtoKit import TelegramPresentationData @@ -9,7 +8,15 @@ import TelegramUIPreferences import TelegramNotices import AccountContext -private let baseTelegramMePaths = ["telegram.me", "t.me", "telegram.dog"] +private let baseTelegramMePaths = [ + "telegram.me", + "t.me", "telegram.dog" +] +private let telegramWebShortLinkHosts = [ + "a.t.me", + "k.t.me", + "z.t.me" +] private let baseTelegraPhPaths = [ "telegra.ph/", "te.legra.ph/", @@ -19,6 +26,20 @@ private let baseTelegraPhPaths = [ "telegram.org/tour/" ] +public func isTelegramWebShortLink(_ url: String) -> Bool { + var urlWithScheme = url + if !urlWithScheme.contains("://") { + urlWithScheme = "https://\(urlWithScheme)" + } + guard let parsedUrl = URL(string: urlWithScheme), let host = parsedUrl.host?.lowercased() else { + return false + } + if let scheme = parsedUrl.scheme?.lowercased(), scheme != "http" && scheme != "https" { + return false + } + return telegramWebShortLinkHosts.contains(host) +} + extension ResolvedBotAdminRights { init?(_ string: String) { var rawValue: UInt32 = 0 @@ -54,6 +75,9 @@ extension ResolvedBotAdminRights { if components.contains("manage_chat") { rawValue |= ResolvedBotAdminRights.manageChat.rawValue } + if components.contains("manage_topics") { + rawValue |= ResolvedBotAdminRights.manageTopics.rawValue + } if components.contains("anonymous") { rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue } @@ -94,7 +118,7 @@ public enum ParsedInternalPeerUrlParameter { public enum ParsedInternalUrl { public enum UrlPeerReference { case name(String) - case id(PeerId) + case id(EnginePeer.Id) } public enum UrlMessageSubject { @@ -104,8 +128,8 @@ public enum ParsedInternalUrl { } case peer(UrlPeerReference, ParsedInternalPeerUrlParameter?) - case peerId(PeerId) - case privateMessage(messageId: MessageId, threadId: Int32?, subject: UrlMessageSubject?) + case peerId(EnginePeer.Id) + case privateMessage(messageId: EngineMessage.Id, threadId: Int32?, subject: UrlMessageSubject?) case stickerPack(name: String, type: StickerPackUrlType) case invoice(String) case join(String) @@ -128,6 +152,7 @@ public enum ParsedInternalUrl { case auction(slug: String) case oauth(url: String) case createBot(parentBot: String, username: String?, title: String?) + case textStyle(slug: String) case externalUrl(url: String) } @@ -274,7 +299,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } else if peerName == "boost" { for queryItem in queryItems { if queryItem.name == "c", let value = queryItem.value, let channelId = Int64(value), channelId > 0 { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(channelId)) return .peer(.id(peerId), .boost) } } @@ -442,7 +467,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } } } else if pathComponents[0].hasPrefix(phonebookUsernamePathPrefix), let idValue = Int64(String(pathComponents[0][pathComponents[0].index(pathComponents[0].startIndex, offsetBy: phonebookUsernamePathPrefix.count)...])) { - return .peerId(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(idValue))) + return .peerId(EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(idValue))) } else if pathComponents[0].hasPrefix("$") || pathComponents[0].hasPrefix("%24") { var component = pathComponents[0].replacingOccurrences(of: "%24", with: "$") if component.hasPrefix("$") { @@ -615,6 +640,8 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } } return .createBot(parentBot: parentBot, username: username, title: title) + } else if pathComponents.count >= 2 && pathComponents[0] == "addstyle" { + return .textStyle(slug: pathComponents[1]) } else if pathComponents[0] == "m" { return .messageLink(slug: pathComponents[1]) } else if pathComponents.count == 3 && pathComponents[0] == "c" { @@ -643,7 +670,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } } } - return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, subject: subject) + return .privateMessage(messageId: EngineMessage.Id(peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, subject: subject) } else { return nil } @@ -682,7 +709,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } } } - return .privateMessage(messageId: MessageId(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, subject: subject) + return .privateMessage(messageId: EngineMessage.Id(peerId: EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(channelId)), namespace: Namespaces.Message.Cloud, id: messageId), threadId: threadId, subject: subject) } else { return nil } @@ -706,7 +733,7 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, context: Accou } } - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + let peerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(channelId)) if boost { return .peer(.id(peerId), .boost) } else if let threadId = threadId { @@ -954,7 +981,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } case let .channelMessage(id, timecode): if case let .channel(channel) = peer, channel.isForumOrMonoForum { - let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) + let messageId = EngineMessage.Id(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) |> `catch` { _ in return .single(.result([])) @@ -984,10 +1011,10 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } } else { - return .single(.result(.channelMessage(peer: peer._asPeer(), messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))) + return .single(.result(.channelMessage(peer: peer._asPeer(), messageId: EngineMessage.Id(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), timecode: timecode))) } case let .replyThread(id, replyId): - let replyThreadMessageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) + let replyThreadMessageId = EngineMessage.Id(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id) if case let .channel(channel) = peer, channel.isForumOrMonoForum { return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(replyThreadMessageId.id)) @@ -997,7 +1024,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) return .progress case let .result(info): if let _ = info { - return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(peerId: channel.id, threadId: Int64(replyThreadMessageId.id), channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId))) + return .result(.replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(peerId: channel.id, threadId: Int64(replyThreadMessageId.id), channelMessageId: nil, isChannelPost: false, isForumPost: true, isMonoforumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: EngineMessage.Id(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: replyId))) } else { return .result(.peer(peer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))) } @@ -1013,7 +1040,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) guard let result = result else { return .result(.channelMessage(peer: peer._asPeer(), messageId: replyThreadMessageId, timecode: nil)) } - return .result(.replyThreadMessage(replyThreadMessage: result, messageId: MessageId(peerId: result.peerId, namespace: Namespaces.Message.Cloud, id: replyId))) + return .result(.replyThreadMessage(replyThreadMessage: result, messageId: EngineMessage.Id(peerId: result.peerId, namespace: Namespaces.Message.Cloud, id: replyId))) }) } case let .voiceChat(invite): @@ -1154,7 +1181,7 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } } else if let threadId = threadId { - let replyThreadMessageId = MessageId(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) + let replyThreadMessageId = EngineMessage.Id(peerId: foundPeer.id, namespace: Namespaces.Message.Cloud, id: threadId) return .single(.progress) |> then(context.engine.messages.fetchChannelReplyThreadMessage(messageId: replyThreadMessageId, atMessageId: nil) |> map(Optional.init) |> `catch` { _ -> Signal in @@ -1338,6 +1365,19 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } } + case let .textStyle(slug): + let signal: Signal = context.engine.messages.requestAIMessageStyle(slug: slug) + |> map { result -> ResolveInternalUrlResult in + guard let result else { + return .result(nil) + } + guard case let .custom(style) = result.style.content else { + return .result(nil) + } + return .result(.textStyle(style: style, initialPreview: result.initialPreview)) + } + return .single(.progress) + |> then(signal) } } @@ -1490,7 +1530,7 @@ private struct UrlHandlingConfiguration { } } -public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal { +public func resolveUrlImpl(context: AccountContext, peerId: EnginePeer.Id?, url: String, skipUrlAuth: Bool) -> Signal { let schemes = ["http://", "https://", ""] return ApplicationSpecificNotice.getSecretChatLinkPreviews(accountManager: context.sharedContext.accountManager) @@ -1530,6 +1570,10 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String } } + if isTelegramWebShortLink(url) { + return .single(.result(.externalUrl(url))) + } + for basePath in baseTelegramMePaths { for scheme in schemes { let basePrefix = scheme + basePath + "/" diff --git a/submodules/WallpaperResources/BUILD b/submodules/WallpaperResources/BUILD index 8fcb8fa8c2..96000b45f6 100644 --- a/submodules/WallpaperResources/BUILD +++ b/submodules/WallpaperResources/BUILD @@ -11,7 +11,6 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", "//submodules/LocalMediaResources:LocalMediaResources", @@ -21,7 +20,7 @@ swift_library( "//submodules/PhotoResources:PhotoResources", "//submodules/PersistentStringHash:PersistentStringHash", "//submodules/AppBundle:AppBundle", - "//submodules/Svg:Svg", + "//submodules/Svg/LegacyImpl", "//submodules/GZip:GZip", "//submodules/GradientBackground:GradientBackground", "//submodules/TelegramPresentationData:TelegramPresentationData", diff --git a/submodules/WallpaperResources/Sources/WallpaperCache.swift b/submodules/WallpaperResources/Sources/WallpaperCache.swift index 12d999635e..dffd5045ea 100644 --- a/submodules/WallpaperResources/Sources/WallpaperCache.swift +++ b/submodules/WallpaperResources/Sources/WallpaperCache.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramApi import TelegramCore import TelegramUIPreferences @@ -27,10 +26,8 @@ public final class CachedWallpaper: Codable { } } -public func cachedWallpaper(account: Account, slug: String, settings: WallpaperSettings?, update: Bool = false) -> Signal { - let engine = TelegramEngine(account: account) - - let slugKey = ValueBoxKey(length: 8) +public func cachedWallpaper(engine: TelegramEngine, network: Network, slug: String, settings: WallpaperSettings?, update: Bool = false) -> Signal { + let slugKey = EngineDataBuffer(length: 8) slugKey.setInt64(0, value: Int64(bitPattern: slug.persistentHashValue)) return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.cachedWallpapers, id: slugKey)) |> mapToSignal { entry -> Signal in @@ -41,13 +38,13 @@ public func cachedWallpaper(account: Account, slug: String, settings: WallpaperS return .single(entry) } } else { - return getWallpaper(network: account.network, slug: slug) + return getWallpaper(network: network, slug: slug) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) } |> mapToSignal { wallpaper -> Signal in - let slugKey = ValueBoxKey(length: 8) + let slugKey = EngineDataBuffer(length: 8) slugKey.setInt64(0, value: Int64(bitPattern: slug.persistentHashValue)) if var wallpaper = wallpaper { @@ -81,7 +78,7 @@ public func updateCachedWallpaper(engine: TelegramEngine, wallpaper: TelegramWal guard case let .file(file) = wallpaper, file.id != 0 else { return } - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: Int64(bitPattern: file.slug.persistentHashValue)) let _ = engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.cachedWallpapers, id: key, item: CachedWallpaper(wallpaper: wallpaper)).start() diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 588c1cc851..490a1d162f 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import Display import CoreImage -import Postbox import TelegramCore import MediaResources import ImageBlur @@ -14,24 +13,24 @@ import LocalMediaResources import TelegramPresentationData import TelegramUIPreferences import AppBundle -import Svg +import LegacyImpl import GradientBackground import GZip public func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, onlyFullSize: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) { - let maybeFullSize: Signal + let maybeFullSize: Signal if thumbnail, let file = fileReference?.media { maybeFullSize = combineLatest(accountManager.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false, attemptSynchronously: synchronousLoad), account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false, attemptSynchronously: synchronousLoad)) - |> mapToSignal { maybeSharedData, maybeData -> Signal in + |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return .single(maybeSharedData) } else if maybeData.complete { return .single(maybeData) } else { return combineLatest(accountManager.mediaBox.resourceData(file.resource), account.postbox.mediaBox.resourceData(file.resource)) - |> mapToSignal { maybeSharedData, maybeData -> Signal in + |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return accountManager.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: true) } @@ -46,7 +45,7 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager mapToSignal { maybeSharedData, maybeData -> Signal in + |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return .single(maybeSharedData) } else if maybeData.complete { @@ -57,7 +56,7 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager map { sharedData, data -> MediaResourceData in + |> map { sharedData, data -> EngineRawMediaResourceData in if sharedData.complete && data.complete { if sharedData.size > data.size { return sharedData @@ -87,7 +86,7 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager + let fetchedThumbnail: Signal fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: representations[smallestIndex].reference) let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: representations[largestIndex].reference) @@ -839,7 +838,7 @@ private func builtinWallpaperData() -> Signal { } |> runOn(Queue.concurrentDefaultQueue()) } -public func settingsBuiltinWallpaperImage(account: Account, thumbnail: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func settingsBuiltinWallpaperImage(thumbnail: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return builtinWallpaperData() |> map { fullSizeImage in return { arguments in guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { @@ -877,7 +876,7 @@ public func settingsBuiltinWallpaperImage(account: Account, thumbnail: Bool = fa } } -public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func photoWallpaper(photoLibraryResource: PhotoLibraryMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let thumbnail = fetchPhotoLibraryImage(localIdentifier: photoLibraryResource.localIdentifier, thumbnail: true) let fullSize = fetchPhotoLibraryImage(localIdentifier: photoLibraryResource.localIdentifier, thumbnail: false) @@ -954,11 +953,11 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM } public func telegramThemeData(account: Account, accountManager: AccountManager, reference: MediaResourceReference, synchronousLoad: Bool = false) -> Signal { - let maybeFetched = accountManager.mediaBox.resourceData(reference.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad) + let maybeFetched = accountManager.resources.data(resource: EngineMediaResource(reference.resource), attemptSynchronously: synchronousLoad) return maybeFetched |> take(1) |> mapToSignal { maybeData in - if maybeData.complete { + if maybeData.isComplete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single(loadedData) } else { @@ -970,7 +969,7 @@ public func telegramThemeData(account: Account, accountManager: AccountManager take(1) |> mapToSignal { maybeData -> Signal<(PresentationTheme?, Data?), NoError> in - if maybeData.complete && isSupportedTheme { + if maybeData.isComplete && isSupportedTheme { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((loadedData.flatMap { makePresentationTheme(data: $0) }, nil)) } else { let decodedThumbnailData = fileReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let previewRepresentation = fileReference.media.previewRepresentations.first - let fetchedThumbnail: Signal + let fetchedThumbnail: Signal if let previewRepresentation = previewRepresentation { fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: fileReference.resourceReference(previewRepresentation.resource)) } else { @@ -1211,7 +1210,7 @@ public func themeImage(account: Account, accountManager: AccountManager mapToSignal { (theme, thumbnailData) -> Signal<(PresentationTheme?, WallpaperImage?, Data?), NoError> in if let theme = theme { if case let .file(file) = theme.chat.defaultWallpaper { - return cachedWallpaper(account: account, slug: file.slug, settings: file.settings) + return cachedWallpaper(engine: TelegramEngine(account: account), network: account.network, slug: file.slug, settings: file.settings) |> mapToSignal { wallpaper -> Signal<(PresentationTheme?, WallpaperImage?, Data?), NoError> in if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { var convertedRepresentations: [ImageRepresentationWithReference] = [] @@ -1257,13 +1256,13 @@ public func themeImage(account: Account, accountManager: AccountManager mapToSignal { data in - if data.complete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if data.isComplete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { return .single((theme, .pattern(data: imageData, colors: file.settings.colors, intensity: intensity), thumbnailData)) } else { return .complete() @@ -1360,7 +1359,7 @@ public func themeImage(account: Account, accountManager: AccountManager mapToSignal { wallpaper in if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { var effectiveBackgroundColor = backgroundColor @@ -1520,7 +1519,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager Signal { +public enum WeatherDataState { + case none + case notDetermined + case notAllowed + case notPreloaded + case fetching + case loaded(WeatherData) +} + +public func getWeatherData(context: AccountContext, load: Bool) -> Signal { + if !load { + return .single(.notPreloaded) + } + + guard let locationManager = context.sharedContext.locationManager else { + return .single(.none) + } + + return DeviceAccess.authorizationStatus(subject: .location(.send)) + |> mapToSignal { status in + switch status { + case .notDetermined: + return .single(.notDetermined) + case .denied, .restricted, .unreachable, .limited: + return .single(.notAllowed) + case .allowed: + return .single(.fetching) + |> then( + currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0) + |> mapToSignal { location in + guard let location else { + return .single(.none) + } + + return requestWeatherData(context: context, location: location) + |> map { weather in + if let weather { + return .loaded(weather) + } else { + return .none + } + } + } + ) + } + } +} + +public func requestWeatherData(context: AccountContext, location: CLLocationCoordinate2D) -> Signal { let appConfiguration = context.currentAppConfiguration.with { $0 } let botConfiguration = WeatherBotConfiguration.with(appConfiguration: appConfiguration) @@ -20,7 +72,7 @@ private func getWeatherData(context: AccountContext, location: CLLocationCoordin return context.engine.peers.resolvePeerByName(name: botUsername, referrer: nil) |> mapToSignal { result -> Signal in guard case let .result(result) = result else { - return .complete() + return .single(nil) } return .single(result) } @@ -36,65 +88,18 @@ private func getWeatherData(context: AccountContext, location: CLLocationCoordin return .single(nil) } } - |> map { contextResult -> StoryWeather? in + |> map { contextResult -> WeatherData? in guard let contextResult, let result = contextResult.results.first, let emoji = result.title, let temperature = result.description.flatMap(Double.init) else { return nil } - return StoryWeather(emoji: emoji, temperature: temperature) + let effectiveEmoji = emojiFor(for: strippedEmoji(emoji), date: Date(), location: location) + return WeatherData(emoji: effectiveEmoji, temperature: temperature) } } else { return .single(nil) } } -func getWeather(context: AccountContext, load: Bool) -> Signal { - guard let locationManager = context.sharedContext.locationManager else { - return .single(.none) - } - - return DeviceAccess.authorizationStatus(subject: .location(.send)) - |> mapToSignal { status in - switch status { - case .notDetermined: - return .single(.notDetermined) - case .denied, .restricted, .unreachable, .limited: - return .single(.notAllowed) - case .allowed: - if load { - return .single(.fetching) - |> then( - currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0) - |> mapToSignal { location in - if let location { - return getWeatherData(context: context, location: location) - |> mapToSignal { weather in - if let weather { - let effectiveEmoji = emojiFor(for: weather.emoji.strippedEmoji, date: Date(), location: location) - if let match = context.animatedEmojiStickersValue[effectiveEmoji]?.first { - return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather( - emoji: effectiveEmoji, - emojiFile: match.file._parse(), - temperature: weather.temperature - ))) - } else { - return .single(.none) - } - } else { - return .single(.none) - } - } - } else { - return .single(.none) - } - } - ) - } else { - return .single(.notPreloaded) - } - } - } -} - private struct WeatherBotConfiguration { static var defaultValue: WeatherBotConfiguration { return WeatherBotConfiguration(botName: "izweatherbot") @@ -118,6 +123,16 @@ private struct WeatherBotConfiguration { private let J1970: Double = 2440588.0 private let moonEmojis = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘", "🌑"] +private func strippedEmoji(_ emoji: String) -> String { + var string = "" + for scalar in emoji.unicodeScalars { + if scalar.value != 0xfe0f { + string.unicodeScalars.append(scalar) + } + } + return string +} + private func emojiFor(for emoji: String, date: Date, location: CLLocationCoordinate2D) -> String { var emoji = emoji if !"".isEmpty, ["☀️", "🌤️"].contains(emoji) && !isDay(latitude: location.latitude, longitude: location.longitude, dateTime: date) { diff --git a/submodules/WebSearchUI/BUILD b/submodules/WebSearchUI/BUILD deleted file mode 100644 index e8b8681c49..0000000000 --- a/submodules/WebSearchUI/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "WebSearchUI", - module_name = "WebSearchUI", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/AccountContext:AccountContext", - "//submodules/LegacyComponents:LegacyComponents", - "//submodules/TelegramUIPreferences:TelegramUIPreferences", - "//submodules/MergeLists:MergeLists", - "//submodules/GalleryUI:GalleryUI", - "//submodules/ChatListSearchItemHeader:ChatListSearchItemHeader", - "//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent", - "//submodules/CheckNode:CheckNode", - "//submodules/PhotoResources:PhotoResources", - "//submodules/SearchBarNode:SearchBarNode", - "//submodules/ItemListUI:ItemListUI", - "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", - "//submodules/SegmentedControlNode:SegmentedControlNode", - "//submodules/AppBundle:AppBundle", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/AttachmentUI:AttachmentUI", - "//submodules/RadialStatusNode:RadialStatusNode", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift deleted file mode 100644 index 7649f89917..0000000000 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchEditor.swift +++ /dev/null @@ -1,97 +0,0 @@ -import Foundation -import UIKit -import LegacyComponents -import SwiftSignalKit -import TelegramCore -import SSignalKit -import Display -import TelegramPresentationData -import AccountContext -import LegacyUI -import LegacyMediaPickerUI - -func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTheme, result: ChatContextResult, initialLayout: ContainerViewLayout?, updateHiddenMedia: @escaping (String?) -> Void, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (UIImage) -> Void, present: @escaping (ViewController, Any?) -> Void) { - guard let item = legacyWebSearchItem(account: context.account, result: result) else { - return - } - - var screenImage: Signal = .single(nil) - if let resource = item.thumbnailResource { - screenImage = context.account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: true) - |> map { maybeData -> UIImage? in - if maybeData.complete { - if let loadedData = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []), let image = UIImage(data: loadedData) { - return image - } - } - return nil - } - } - - let _ = (screenImage - |> take(1) - |> deliverOnMainQueue).start(next: { screenImage in - let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout) - legacyController.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style - - let paintStickersContext = LegacyPaintStickersContext(context: context) - - let controller = TGPhotoEditorController(context: legacyController.context, item: item, intent: TGPhotoEditorControllerAvatarIntent, adjustments: nil, caption: nil, screenImage: screenImage ?? UIImage(), availableTabs: TGPhotoEditorController.defaultTabs(forAvatarIntent: true), selectedTab: .cropTab)! - controller.stickersContext = paintStickersContext - legacyController.bind(controller: controller) - - controller.editingContext = TGMediaEditingContext() - controller.didFinishEditing = { [weak controller] _, result, _, hasChanges, commit in - if !hasChanges { - return - } - if let result = result { - completed(result) - } - commit?() - controller?.dismiss(animated: true) - } - controller.requestThumbnailImage = { _ -> SSignal in - return item.thumbnailImageSignal() - } - controller.requestOriginalScreenSizeImage = { _, position -> SSignal in - return item.screenImageSignal(position) - } - controller.requestOriginalFullSizeImage = { _, position -> SSignal in - return item.originalImageSignal(position) - } - - let fromView = transitionView(result)! - let transition = TGMediaAvatarEditorTransition(controller: controller, from: fromView)! - transition.transitionHostView = transitionHostView() - transition.referenceFrame = { - return fromView.frame - } - transition.referenceImageSize = { - return item.dimensions - } - transition.referenceScreenImageSignal = { - return item.screenImageSignal(0.0) - } - transition.imageReady = { - updateHiddenMedia(result.id) - } - - controller.beginCustomTransitionOut = { [weak legacyController] outFrame, outView, completion in - transition.outReferenceFrame = outFrame - transition.repView = outView - transition.dismiss(animated: true, completion: { - updateHiddenMedia(nil) - if let completion = completion { - DispatchQueue.main.async { - completion() - } - } - legacyController?.dismiss() - }) - } - - present(legacyController, nil) - transition.present(animated: true) - }) -} diff --git a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift b/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift deleted file mode 100644 index 29f65144f9..0000000000 --- a/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift +++ /dev/null @@ -1,475 +0,0 @@ -import Foundation -import UIKit -import LegacyComponents -import SwiftSignalKit -import TelegramCore -import SSignalKit -import UIKit -import Display -import TelegramPresentationData -import AccountContext -import PhotoResources -import LegacyUI -import LegacyMediaPickerUI -import Postbox - -class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem { - var isVideo: Bool { - return false - } - - var uniqueIdentifier: String! { - return self.result.id - } - - let result: ChatContextResult - private(set) var thumbnailResource: TelegramMediaResource? - private(set) var imageResource: TelegramMediaResource? - let dimensions: CGSize - let thumbnailImage: Signal - let originalImage: Signal - let progress: Signal - - init(result: ChatContextResult) { - self.result = result - self.dimensions = CGSize() - self.thumbnailImage = .complete() - self.originalImage = .complete() - self.progress = .complete() - } - - init(result: ChatContextResult, thumbnailResource: TelegramMediaResource?, imageResource: TelegramMediaResource?, dimensions: CGSize, thumbnailImage: Signal, originalImage: Signal, progress: Signal) { - self.result = result - self.thumbnailResource = thumbnailResource - self.imageResource = imageResource - self.dimensions = dimensions - self.thumbnailImage = thumbnailImage - self.originalImage = originalImage - self.progress = progress - } - - var originalSize: CGSize { - return self.dimensions - } - - func thumbnailImageSignal() -> SSignal! { - return SSignal(generator: { subscriber -> SDisposable? in - let disposable = self.thumbnailImage.start(next: { image in - subscriber.putNext(image) - subscriber.putCompletion() - }) - - return SBlockDisposable(block: { - disposable.dispose() - }) - }) - } - - func screenImageAndProgressSignal() -> SSignal { - return SSignal { subscriber in - let imageDisposable = self.originalImage.start(next: { image in - if !image.degraded() { - subscriber.putNext(1.0) - } - subscriber.putNext(image) - if !image.degraded() { - subscriber.putCompletion() - } - }) - - let progressDisposable = (self.progress - |> deliverOnMainQueue).start(next: { next in - subscriber.putNext(next) - }) - - return SBlockDisposable { - imageDisposable.dispose() - progressDisposable.dispose() - } - } - } - - func screenImageSignal(_ position: TimeInterval) -> SSignal! { - return self.originalImageSignal(position) - } - - func originalImageSignal(_ position: TimeInterval) -> SSignal! { - return SSignal(generator: { subscriber -> SDisposable? in - let disposable = self.originalImage.start(next: { image in - subscriber.putNext(image) - if !image.degraded() { - subscriber.putCompletion() - } - }) - - return SBlockDisposable(block: { - disposable.dispose() - }) - }) - } -} - -private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGalleryEditableItem, TGModernGallerySelectableItem { - var selectionContext: TGMediaSelectionContext! - var editingContext: TGMediaEditingContext! - var stickersContext: TGPhotoPaintStickersContext! - let item: LegacyWebSearchItem - - init(item: LegacyWebSearchItem) { - self.item = item - super.init() - } - - func editableMediaItem() -> TGMediaEditableItem! { - return self.item - } - - func selectableMediaItem() -> TGMediaSelectableItem! { - return self.item - } - - func toolbarTabs() -> TGPhotoEditorTab { - return [.cropTab, .paintTab, .toolsTab] - } - - func uniqueId() -> String! { - return self.item.uniqueIdentifier - } - - override func viewClass() -> AnyClass! { - return LegacyWebSearchGalleryItemView.self - } - - override func isEqual(_ object: Any?) -> Bool { - if let item = object as? LegacyWebSearchGalleryItem { - return item.item.result.id == self.item.result.id - } - return false - } -} - -private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView { - private let readyForTransition = SVariable() - - @objc func setHiddenAsBeingEdited(_ hidden: Bool) { - self.imageView.isHidden = hidden - } - - @objc func singleTap() { - if let item = item as? LegacyWebSearchGalleryItem, let selectionContext = item.selectionContext { - selectionContext.toggleItemSelection(item.selectableMediaItem(), success: nil) - } - } - - override func readyForTransitionIn() -> SSignal! { - return self.readyForTransition.signal().take(1) - } - - override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) { - if let item = item as? LegacyWebSearchGalleryItem { - self._setItem(item) - self.imageSize = TGFitSize(item.editableMediaItem().originalSize!, CGSize(width: 1600, height: 1600)) - - let signal = item.editingContext.imageSignal(for: item.editableMediaItem())?.map(toSignal: { result -> SSignal in - if let image = result as? UIImage { - return SSignal.single(image) - } else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem { - return mediaItem.screenImageAndProgressSignal() - } else { - return SSignal.complete() - } - }) - - self.imageView.setSignal(signal?.deliver(on: SQueue.main()).afterNext({ [weak self] next in - if let strongSelf = self, let image = next as? UIImage { - strongSelf.imageSize = image.size - strongSelf.reset() - strongSelf.readyForTransition.set(SSignal.single(true)) - } - })) - - self.reset() - } else { - self.imageView.setSignal(nil) - super.setItem(item, synchronously: synchronously) - } - } - - override func contentView() -> UIView! { - return self.imageView - } - - override func transitionContentView() -> UIView! { - return self.contentView() - } - - override func transitionViewContentRect() -> CGRect { - let contentView = self.transitionContentView()! - return contentView.convert(contentView.bounds, to: self.transitionView()) - } -} - -func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyWebSearchItem? { - var thumbnailDimensions: CGSize? - var thumbnailResource: TelegramMediaResource? - var imageResource: TelegramMediaResource? - var imageDimensions = CGSize() - var immediateThumbnailData: Data? - - let thumbnailSignal: Signal - let originalSignal: Signal - - switch result { - case let .externalReference(externalReference): - if let content = externalReference.content { - imageResource = content.resource - } - if let thumbnail = externalReference.thumbnail { - thumbnailResource = thumbnail.resource - thumbnailDimensions = thumbnail.dimensions?.cgSize - } - if let dimensions = externalReference.content?.dimensions { - imageDimensions = dimensions.cgSize - } - case let .internalReference(internalReference): - immediateThumbnailData = internalReference.image?.immediateThumbnailData - if let image = internalReference.image { - if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { - imageDimensions = imageRepresentation.dimensions.cgSize - imageResource = imageRepresentation.resource - } - if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 200, height: 100)) { - thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize - thumbnailResource = thumbnailRepresentation.resource - } - } - } - - if let imageResource = imageResource { - let progressSignal = account.postbox.mediaBox.resourceStatus(imageResource) - |> map { status -> Float in - switch status { - case .Local: - return 1.0 - case .Remote, .Paused: - return 0.027 - case let .Fetching(_, progress): - return max(progress, 0.1) - } - } - - var representations: [TelegramMediaImageRepresentation] = [] - if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions { - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false) - |> mapToSignal { value -> Signal in - let thumbnailData = value._0 - if let data = thumbnailData, let image = UIImage(data: data) { - return .single(image) - } else { - return .complete() - } - } - originalSignal = chatMessagePhotoDatas(postbox: account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true) - |> mapToSignal { value -> Signal in - let thumbnailData = value._0 - let fullSizeData = value._1 - let fullSizeComplete = value._3 - - if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) { - return .single(image) - } else if let data = thumbnailData, let image = UIImage(data: data) { - image.setDegraded(true) - return .single(image) - } else { - return .complete() - } - } - - return LegacyWebSearchItem(result: result, thumbnailResource: thumbnailResource, imageResource: imageResource, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal, progress: progressSignal) - } else { - return nil - } -} - -private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) { - var focusItem: TGModernGalleryItem? - var galleryItems: [TGModernGalleryItem] = [] - for result in results { - if let item = legacyWebSearchItem(account: account, result: result) { - let galleryItem = LegacyWebSearchGalleryItem(item: item) - galleryItem.selectionContext = selectionContext - galleryItem.editingContext = editingContext - if result.id == current.id { - focusItem = galleryItem - } - galleryItems.append(galleryItem) - } - } - return (galleryItems, focusItem) -} - -func presentLegacyWebSearchGallery(context: AccountContext, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, presentationData: PresentationData, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: (ViewController, Any?) -> Void) { - let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) - legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style - - let recipientName: String? - if let threadTitle { - recipientName = threadTitle - } else { - if peer?.id == context.account.peerId { - recipientName = presentationData.strings.DialogList_SavedMessages - } else { - recipientName = peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - } - } - - let paintStickersContext = LegacyPaintStickersContext(context: context) - paintStickersContext.captionPanelView = { - return getCaptionPanelView() - } - - let controller = TGModernGalleryController(context: legacyController.context)! - controller.asyncTransitionIn = true - legacyController.bind(controller: controller) - - let (items, focusItem) = galleryItems(account: context.account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext) - - let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: false, allowCaptionEntities: true, hasTimer: false, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: false, hasCamera: false, recipientName: recipientName, isScheduledMessages: false, hasCoverButton: false)! - model.stickersContext = paintStickersContext - controller.model = model - model.controller = controller - model.useGalleryImageAsEditableItemImage = true - model.storeOriginalImageForItem = { item, image in - editingContext.setOriginalImage(image, for: item, synchronous: false) - } - model.willFinishEditingItem = { item, adjustments, representation, hasChanges in - if hasChanges { - editingContext.setAdjustments(adjustments, for: item) - } - editingContext.setTemporaryRep(representation, for: item) - if let selectionContext = selectionContext, adjustments != nil, let item = item as? TGMediaSelectableItem { - selectionContext.setItem(item, selected: true) - } - } - model.didFinishEditingItem = { item, adjustments, result, thumbnail in - editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true) - } - model.saveItemCaption = { item, caption in - editingContext.setCaption(caption, for: item) - if let selectionContext = selectionContext, let caption = caption, caption.length > 0, let item = item as? TGMediaSelectableItem { - selectionContext.setItem(item, selected: true) - } - } - if let selectionContext = selectionContext { - model.interfaceView.updateSelectionInterface(selectionContext.count(), counterVisible: selectionContext.count() > 0, animated: false) - } - model.interfaceView.donePressed = { item in - if let item = item as? LegacyWebSearchGalleryItem { - controller.dismissWhenReady(animated: true) - completed(item.item.result) - } - } - controller.transitionHost = { - return transitionHostView() - } - var transitionedIn = false - controller.itemFocused = { item in - if let item = item as? LegacyWebSearchGalleryItem, transitionedIn { - updateHiddenMedia(item.item.result.id) - } - } - controller.beginTransitionIn = { item, _ in - if let item = item as? LegacyWebSearchGalleryItem { - return transitionView(item.item.result) - } else { - return nil - } - } - controller.startedTransitionIn = { - transitionedIn = true - updateHiddenMedia(current.id) - } - controller.beginTransitionOut = { item, _ in - if let item = item as? LegacyWebSearchGalleryItem { - return transitionView(item.item.result) - } else { - return nil - } - } - controller.completedTransitionOut = { [weak legacyController] in - updateHiddenMedia(nil) - legacyController?.dismiss() - } - present(legacyController, nil) -} - -public func legacyEnqueueWebSearchMessages(_ selectionState: TGMediaSelectionContext, _ editingState: TGMediaEditingContext, enqueueChatContextResult: (ChatContextResult) -> Void, enqueueMediaMessages: ([Any]) -> Void) -{ - var results: [ChatContextResult] = [] - for item in selectionState.selectedItems() { - if let item = item as? LegacyWebSearchItem { - results.append(item.result) - } - } - - if !results.isEmpty { - var signals: [Any] = [] - for result in results { - let editableItem = LegacyWebSearchItem(result: result) - if let adjustments = editingState.adjustments(for: editableItem) { - let animated = adjustments.paintingData?.hasAnimation ?? false - if let imageSignal = editingState.imageSignal(for: editableItem) { - let signal = imageSignal.map { image -> Any in - if let image = image as? UIImage { - var dict: [AnyHashable: Any] = [ - "type": "editedPhoto", - "image": image - ] - - if animated { - dict["isAnimation"] = true - if let photoEditorValues = adjustments as? PGPhotoEditorValues { - dict["adjustments"] = TGVideoEditAdjustments(photoEditorValues: photoEditorValues, preset: TGMediaVideoConversionPresetAnimation) - } - - let filePath = NSTemporaryDirectory().appending("/gifvideo_\(arc4random()).jpg") - let data = image.jpegData(compressionQuality: 0.8) - if let data = data { - let _ = try? data.write(to: URL(fileURLWithPath: filePath), options: []) - } - dict["url"] = NSURL(fileURLWithPath: filePath) - - if adjustments.cropApplied(forAvatar: false) || adjustments.hasPainting() || adjustments.toolsApplied() { - var paintingImage: UIImage? = adjustments.paintingData?.stillImage - if paintingImage == nil { - paintingImage = adjustments.paintingData?.image - } - - let thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSize(width: 512.0, height: 512.0)), adjustments.originalSize, true, true, true, false) - if let thumbnailImage = thumbnailImage { - dict["previewImage"] = thumbnailImage - } - } - } - - return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) as Any - } else { - return SSignal.complete() - } - } - signals.append(signal as Any) - } - } else { - enqueueChatContextResult(result) - } - } - - if !signals.isEmpty { - enqueueMediaMessages(signals) - } - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchBadgeNode.swift b/submodules/WebSearchUI/Sources/WebSearchBadgeNode.swift deleted file mode 100644 index c52090e332..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchBadgeNode.swift +++ /dev/null @@ -1,96 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramPresentationData - -final class WebSearchBadgeNode: ASDisplayNode { - private var fillColor: UIColor - private var strokeColor: UIColor - private var textColor: UIColor - - private let textNode: ASTextNode - private let backgroundNode: ASImageNode - - private let font: UIFont = Font.with(size: 17.0, design: .round, weight: .bold) - - var text: String = "" { - didSet { - self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) - self.invalidateCalculatedLayout() - } - } - - convenience init(theme: PresentationTheme) { - self.init(fillColor: theme.list.itemCheckColors.fillColor, strokeColor: theme.list.itemCheckColors.fillColor, textColor: theme.list.itemCheckColors.foregroundColor) - } - - init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { - self.fillColor = fillColor - self.strokeColor = strokeColor - self.textColor = textColor - - self.textNode = ASTextNode() - self.textNode.isUserInteractionEnabled = false - self.textNode.displaysAsynchronously = false - - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 22.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) - - super.init() - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.textNode) - } - - func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { - self.fillColor = fillColor - self.strokeColor = strokeColor - self.textColor = textColor - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 22.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) - self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) - } - - func animateBump(incremented: Bool) { - if incremented { - let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) - firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.2) - firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.2, completion: { finished in - if finished { - let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) - secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0) - secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0) - } - }) - } else { - let firstTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) - firstTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 0.8) - firstTransition.updateTransformScale(layer: self.textNode.layer, scale: 0.8, completion: { finished in - if finished { - let secondTransition = ContainedViewLayoutTransition.animated(duration: 0.1, curve: .easeInOut) - secondTransition.updateTransformScale(layer: self.backgroundNode.layer, scale: 1.0) - secondTransition.updateTransformScale(layer: self.textNode.layer, scale: 1.0) - } - }) - } - } - - func animateOut() { - let timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue - self.backgroundNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) - self.textNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, delay: 0.0, timingFunction: timingFunction, removeOnCompletion: true, completion: nil) - } - - override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - let badgeSize = self.textNode.measure(constrainedSize) - let backgroundSize = CGSize(width: max(22.0, badgeSize.width + 12.0), height: 22.0) - let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) - self.backgroundNode.frame = backgroundFrame - self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) - UIScreenPixel), size: badgeSize) - - return backgroundSize - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift deleted file mode 100644 index 9e960e9a63..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ /dev/null @@ -1,608 +0,0 @@ -import Foundation -import UIKit -import SwiftSignalKit -import Display -import AsyncDisplayKit -import TelegramCore -import LegacyComponents -import TelegramUIPreferences -import TelegramPresentationData -import AccountContext -import AttachmentUI - -public enum WebSearchMode { - case media - case avatar -} - -public enum WebSearchControllerMode { - case media(attachment: Bool, completion: (ChatContextResultCollection, TGMediaSelectionContext, TGMediaEditingContext, Bool) -> Void) - case editor(completion: (UIImage) -> Void) - case avatar(initialQuery: String?, completion: (UIImage) -> Void) - - var mode: WebSearchMode { - switch self { - case .media, .editor: - return .media - case .avatar: - return .avatar - } - } -} - -final class WebSearchControllerInteraction { - let openResult: (ChatContextResult) -> Void - let setSearchQuery: (String) -> Void - let deleteRecentQuery: (String) -> Void - let toggleSelection: (ChatContextResult, Bool) -> Bool - let sendSelected: (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void - let schedule: (ChatSendMessageActionSheetController.SendParameters?) -> Void - let avatarCompleted: (UIImage) -> Void - let selectionState: TGMediaSelectionContext? - let editingState: TGMediaEditingContext - var hiddenMediaId: String? - - init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Bool, sendSelected: @escaping (ChatContextResult?, Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { - self.openResult = openResult - self.setSearchQuery = setSearchQuery - self.deleteRecentQuery = deleteRecentQuery - self.toggleSelection = toggleSelection - self.sendSelected = sendSelected - self.schedule = schedule - self.avatarCompleted = avatarCompleted - self.selectionState = selectionState - self.editingState = editingState - } -} - -private func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal { - return Signal { subscriber in - let disposable = selectionState.selectionChangedSignal()?.start(next: { next in - subscriber.putNext(Void()) - }, completed: {}) - return ActionDisposable { - disposable?.dispose() - } - } -} - -public struct WebSearchConfiguration: Equatable { - public let gifProvider: String? - - public init(appConfiguration: AppConfiguration) { - var gifProvider: String? = nil - if let data = appConfiguration.data, let value = data["gif_search_branding"] as? String { - gifProvider = value - } - self.gifProvider = gifProvider - } -} - -public final class WebSearchController: ViewController { - private var validLayout: ContainerViewLayout? - - private let context: AccountContext - let mode: WebSearchControllerMode - private let peer: EnginePeer? - private let chatLocation: ChatLocation? - private let configuration: EngineConfiguration.SearchBots - private let activateOnDisplay: Bool - - private var controllerNode: WebSearchControllerNode { - return self.displayNode as! WebSearchControllerNode - } - - private var _ready = Promise() - override public var ready: Promise { - return self._ready - } - - private var didPlayPresentationAnimation = false - - private var controllerInteraction: WebSearchControllerInteraction? - private var interfaceState: WebSearchInterfaceState - private let interfaceStatePromise = ValuePromise() - - private var disposable: Disposable? - private let resultsDisposable = MetaDisposable() - private var selectionDisposable: Disposable? - - private var navigationContentNode: WebSearchNavigationContentNode? - - public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } { - didSet { - self.controllerNode.getCaptionPanelView = self.getCaptionPanelView - } - } - - public var presentSchedulePicker: (Bool, @escaping (Int32) -> Void) -> Void = { _, _ in } - - public var dismissed: () -> Void = { } - - public var searchingUpdated: (Bool) -> Void = { _ in } - - public var attemptItemSelection: (ChatContextResult) -> Bool = { _ in return true } - - private var searchQueryPromise = ValuePromise() - private var searchQueryDisposable: Disposable? - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: EngineConfiguration.SearchBots, mode: WebSearchControllerMode, activateOnDisplay: Bool = true) { - self.context = context - self.mode = mode - self.peer = peer - self.chatLocation = chatLocation - self.configuration = configuration - self.activateOnDisplay = activateOnDisplay - - let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - self.interfaceState = WebSearchInterfaceState(presentationData: presentationData) - - var searchQuery: String? - if case let .avatar(initialQuery, _) = mode, let query = initialQuery { - searchQuery = query - self.interfaceState = self.interfaceState.withUpdatedQuery(query) - } - - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme).withUpdatedSeparatorColor(presentationData.theme.list.plainBackgroundColor).withUpdatedBackgroundColor(presentationData.theme.list.plainBackgroundColor), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) - self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style - - self.scrollToTop = { [weak self] in - if let strongSelf = self { - strongSelf.controllerNode.scrollToTop(animated: true) - } - } - - let settings = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webSearchSettings]) - |> map { sharedData -> WebSearchSettings in - if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.webSearchSettings]?.get(WebSearchSettings.self) { - return current - } else { - return WebSearchSettings.defaultSettings - } - } - - let gifProvider = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) - |> map { view -> String? in - guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else { - return nil - } - let configuration = WebSearchConfiguration(appConfiguration: appConfiguration) - return configuration.gifProvider - } - |> distinctUntilChanged - - self.disposable = ((combineLatest(settings, (updatedPresentationData?.signal ?? context.sharedContext.presentationData), gifProvider)) - |> deliverOnMainQueue).start(next: { [weak self] settings, presentationData, gifProvider in - guard let strongSelf = self else { - return - } - strongSelf.updateInterfaceState { current -> WebSearchInterfaceState in - var updated = current - if case .media = mode, current.state?.scope != settings.scope { - updated = updated.withUpdatedScope(settings.scope) - } - if current.presentationData !== presentationData { - updated = updated.withUpdatedPresentationData(presentationData) - } - if current.gifProvider != gifProvider { - updated = updated.withUpdatedGifProvider(gifProvider) - } - return updated - } - }) - - var attachment = false - if case let .media(attachmentValue, _) = mode { - attachment = attachmentValue - } else if case .editor = mode { - attachment = true - } - let navigationContentNode = WebSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, attachment: attachment) - self.navigationContentNode = navigationContentNode - navigationContentNode.setQueryUpdated { [weak self] query in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.searchQueryPromise.set(query) - strongSelf.searchingUpdated(!query.isEmpty) - } - } - navigationContentNode.cancel = { [weak self] in - if let strongSelf = self { - strongSelf.cancel() - } - } - self.navigationBar?.setContentNode(navigationContentNode, animated: false) - if let query = searchQuery { - navigationContentNode.setQuery(query) - } - - let selectionState: TGMediaSelectionContext? - switch self.mode { - case .media: - selectionState = TGMediaSelectionContext() - case .avatar: - selectionState = nil - case .editor: - selectionState = nil - } - let editingState = TGMediaEditingContext() - self.controllerInteraction = WebSearchControllerInteraction(openResult: { [weak self] result in - if let strongSelf = self { - strongSelf.controllerNode.openResult(currentResult: result, present: { [weak self] viewController, arguments in - if let strongSelf = self { - strongSelf.present(viewController, in: .window(.root), with: arguments, blockInteraction: true) - } - }) - } - }, setSearchQuery: { [weak self] query in - if let strongSelf = self { - strongSelf.navigationContentNode?.setQuery(query) - strongSelf.updateSearchQuery(query) - strongSelf.navigationContentNode?.deactivate() - } - }, deleteRecentQuery: { [weak self] query in - if let strongSelf = self { - let _ = removeRecentWebSearchQuery(engine: strongSelf.context.engine, string: query).start() - } - }, toggleSelection: { [weak self] result, value in - if let strongSelf = self { - if !strongSelf.attemptItemSelection(result) { - return false - } - let item = LegacyWebSearchItem(result: result) - strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value) - return true - } else { - return false - } - }, sendSelected: { [weak self] current, silently, scheduleTime, messageEffect in - if let selectionState = selectionState, let results = self?.controllerNode.currentExternalResults { - if let current = current { - let currentItem = LegacyWebSearchItem(result: current) - selectionState.setItem(currentItem, selected: true) - } - if case let .media(_, sendSelected) = mode { - sendSelected(results, selectionState, editingState, false) - } - } - }, schedule: { [weak self] messageEffect in - if let strongSelf = self { - strongSelf.presentSchedulePicker(false, { [weak self] time in - self?.controllerInteraction?.sendSelected(nil, false, time, nil) - }) - } - }, avatarCompleted: { result in - if case let .avatar(_, avatarCompleted) = mode { - avatarCompleted(result) - } - }, selectionState: selectionState, editingState: editingState) - - selectionState?.attemptSelectingItem = { [weak self] item in - guard let self else { - return false - } - - if let item = item as? LegacyWebSearchItem { - return self.attemptItemSelection(item.result) - } - - return true - } - - if let selectionState = selectionState { - self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState) - |> deliverOnMainQueue).start(next: { [weak self] _ in - if let strongSelf = self { - strongSelf.controllerNode.updateSelectionState(animated: true) - } - }) - } - - let throttledSearchQuery = self.searchQueryPromise.get() - |> mapToSignal { query -> Signal in - if !query.isEmpty { - return (.complete() |> delay(1.0, queue: Queue.mainQueue())) - |> then(.single(query)) - } else { - return .single(query) - } - } - - self.searchQueryDisposable = (throttledSearchQuery - |> deliverOnMainQueue).start(next: { [weak self] query in - if let self { - self.updateSearchQuery(query) - } - }) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.disposable?.dispose() - self.resultsDisposable.dispose() - self.selectionDisposable?.dispose() - self.searchQueryDisposable?.dispose() - } - - public func cancel() { - self.controllerNode.dismissInput?() - self.controllerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in - self?.dismissed() - self?.dismiss() - }) - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments, !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - if case .modalSheet = presentationArguments.presentationAnimation { - self.controllerNode.animateIn() - } - } - } - - private var didActivateSearch = false - override public func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - var select = false - if case let .avatar(initialQuery, _) = self.mode, let _ = initialQuery { - select = true - } - if case let .media(attachment, _) = self.mode, attachment && !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - self.controllerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } else if case .editor = self.mode, !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - self.controllerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - if !self.didActivateSearch && self.activateOnDisplay { - self.didActivateSearch = true - self.navigationContentNode?.activate(select: select) - } - } - - override public func loadDisplayNode() { - var attachment: Bool = false - if case let .media(attachmentValue, _) = self.mode, attachmentValue { - attachment = true - } - self.displayNode = WebSearchControllerNode(controller: self, context: self.context, presentationData: self.interfaceState.presentationData, controllerInteraction: self.controllerInteraction!, peer: self.peer, chatLocation: self.chatLocation, mode: self.mode.mode, attachment: attachment) - self.controllerNode.requestUpdateInterfaceState = { [weak self] animated, f in - if let strongSelf = self { - strongSelf.updateInterfaceState(f) - } - } - self.controllerNode.cancel = { [weak self] in - if let strongSelf = self { - strongSelf.dismiss() - } - } - self.controllerNode.dismissInput = { [weak self] in - if let strongSelf = self { - strongSelf.navigationContentNode?.deactivate() - } - } - self.controllerNode.updateInterfaceState(self.interfaceState, animated: false) - - self._ready.set(.single(true)) - self.displayNodeDidLoad() - } - - private func updateInterfaceState(animated: Bool = true, _ f: (WebSearchInterfaceState) -> WebSearchInterfaceState) { - let previousInterfaceState = self.interfaceState - let previousTheme = self.interfaceState.presentationData.theme - let previousStrings = self.interfaceState.presentationData.theme - let previousGifProvider = self.interfaceState.gifProvider - - let updatedInterfaceState = f(self.interfaceState) - self.interfaceState = updatedInterfaceState - self.interfaceStatePromise.set(updatedInterfaceState) - - if self.isNodeLoaded { - if previousTheme !== updatedInterfaceState.presentationData.theme || previousStrings !== updatedInterfaceState.presentationData.strings || previousGifProvider != updatedInterfaceState.gifProvider { - self.controllerNode.updatePresentationData(theme: updatedInterfaceState.presentationData.theme, strings: updatedInterfaceState.presentationData.strings) - } - if previousInterfaceState != self.interfaceState { - self.controllerNode.updateInterfaceState(self.interfaceState, animated: animated) - } - } - } - - private func updateSearchQuery(_ query: String) { - let scope: Signal - switch self.mode { - case .media: - scope = self.interfaceStatePromise.get() - |> map { state -> WebSearchScope? in - return state.state?.scope - } - |> distinctUntilChanged - case .avatar, .editor: - scope = .single(.images) - } - - self.updateInterfaceState { $0.withUpdatedQuery(query) } - - let scopes: [WebSearchScope: Promise<((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, Bool)>] = [.images: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .images) - |> mapToSignal { result -> Signal<((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, Bool), NoError> in - return .single((result, false)) - |> then(.single((result, true))) - }), .gifs: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .gifs) - |> mapToSignal { result -> Signal<((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, Bool), NoError> in - return .single((result, false)) - |> then(.single((result, true))) - })] - - var results = scope - |> mapToSignal { scope -> (Signal<((ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, Bool), NoError>) in - if let scope = scope, let scopeResults = scopes[scope] { - return scopeResults.get() - } else { - return .complete() - } - } - - if query.isEmpty { - results = .single(({ _ in return nil}, false)) - self.navigationContentNode?.setActivity(false) - } - - let previousResults = Atomic<(ChatContextResultCollection, Bool)?>(value: nil) - self.resultsDisposable.set((results - |> deliverOnMainQueue).start(next: { [weak self] result, immediate in - if let strongSelf = self { - if let result = result(nil), case let .contextRequestResult(_, results) = result { - if let results = results { - let previous = previousResults.swap((results, immediate)) - if let previous = previous, previous.0.queryId == results.queryId && !previous.1 { - } else { - strongSelf.controllerNode.updateResults(results, immediate: immediate) - } - } - } else { - strongSelf.controllerNode.updateResults(nil) - } - } - })) - } - - private func signalForQuery(_ query: String, scope: WebSearchScope) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> { - let delayRequest = true - let signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ _ in return .contextRequestResult(nil, nil) }) - - let peerId = self.peer?.id ?? self.context.account.peerId - - let botName: String? - switch scope { - case .images: - botName = self.configuration.imageBotUsername - case .gifs: - botName = self.configuration.gifBotUsername - } - guard let name = botName else { - return .single({ _ in return .contextRequestResult(nil, nil) }) - } - - let context = self.context - let contextBot = self.context.engine.peers.resolvePeerByName(name: name, referrer: nil) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - |> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in - if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder { - let results = requestContextResults(engine: context.engine, botId: user.id, query: query, peerId: peerId, limit: 64) - |> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - return { _ in - return .contextRequestResult(.user(user), results?.results) - } - } - - let botResult: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ previousResult in - var passthroughPreviousResult: ChatContextResultCollection? - if let previousResult = previousResult { - if case let .contextRequestResult(previousUser, previousResults) = previousResult { - if previousUser?.id == user.id { - passthroughPreviousResult = previousResults - } - } - } - return .contextRequestResult(nil, passthroughPreviousResult) - }) - - let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> - if delayRequest { - maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue()) - } else { - maybeDelayedContextResults = results - } - - return botResult |> then(maybeDelayedContextResults) - } else { - return .single({ _ in return nil }) - } - } - return (signal |> then(contextBot)) - |> deliverOnMainQueue - |> beforeStarted { [weak self] in - if let strongSelf = self { - strongSelf.navigationContentNode?.setActivity(true) - } - } - |> afterCompleted { [weak self] in - if let strongSelf = self { - strongSelf.navigationContentNode?.setActivity(false) - } - } - } - - public var mediaPickerContext: WebSearchPickerContext? { - if let interaction = self.controllerInteraction { - return WebSearchPickerContext(interaction: interaction) - } else { - return nil - } - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.validLayout = layout - - let navigationBarHeight = self.navigationLayout(layout: layout).navigationFrame.maxY - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - } -} - -public class WebSearchPickerContext: AttachmentMediaPickerContext { - private weak var interaction: WebSearchControllerInteraction? - - public var selectionCount: Signal { - return Signal { [weak self] subscriber in - let disposable = self?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in - subscriber.putNext(Int(self?.interaction?.selectionState?.count() ?? 0)) - }, error: { _ in }, completed: { }) - return ActionDisposable { - disposable?.dispose() - } - } - } - - public var caption: Signal { - return Signal { [weak self] subscriber in - let disposable = self?.interaction?.editingState.forcedCaption().start(next: { caption in - if let caption = caption as? NSAttributedString { - subscriber.putNext(caption) - } else { - subscriber.putNext(nil) - } - }, error: { _ in }, completed: { }) - return ActionDisposable { - disposable?.dispose() - } - } - } - - init(interaction: WebSearchControllerInteraction) { - self.interaction = interaction - } - - public func setCaption(_ caption: NSAttributedString) { - self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) - } - - public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - self.interaction?.sendSelected(nil, mode == .silently, nil, parameters) - } - - public func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - self.interaction?.schedule(parameters) - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift b/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift deleted file mode 100644 index 75d2142a62..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchControllerNode.swift +++ /dev/null @@ -1,829 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import SwiftSignalKit -import Display -import TelegramCore -import LegacyComponents -import TelegramPresentationData -import TelegramUIPreferences -import MergeLists -import AccountContext -import GalleryUI -import ChatListSearchItemHeader -import SegmentedControlNode -import AppBundle - -private struct WebSearchContextResultStableId: Hashable { - let result: ChatContextResult - - func hash(into hasher: inout Hasher) { - hasher.combine(result.id) - } - - static func ==(lhs: WebSearchContextResultStableId, rhs: WebSearchContextResultStableId) -> Bool { - return lhs.result == rhs.result - } -} - -private struct WebSearchEntry: Comparable, Identifiable { - let index: Int - let result: ChatContextResult - - var stableId: WebSearchContextResultStableId { - return WebSearchContextResultStableId(result: self.result) - } - - static func ==(lhs: WebSearchEntry, rhs: WebSearchEntry) -> Bool { - return lhs.index == rhs.index && lhs.result == rhs.result - } - - static func <(lhs: WebSearchEntry, rhs: WebSearchEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(account: Account, theme: PresentationTheme, interfaceState: WebSearchInterfaceState, controllerInteraction: WebSearchControllerInteraction) -> GridItem { - return WebSearchItem(account: account, theme: theme, interfaceState: interfaceState, result: self.result, controllerInteraction: controllerInteraction) - } -} - -private struct WebSearchTransition { - let deleteItems: [Int] - let insertItems: [GridNodeInsertItem] - let updateItems: [GridNodeUpdateItem] - let entryCount: Int - let hasMore: Bool -} - -private func preparedTransition(from fromEntries: [WebSearchEntry], to toEntries: [WebSearchEntry], hasMore: Bool, account: Account, theme: PresentationTheme, interfaceState: WebSearchInterfaceState, controllerInteraction: WebSearchControllerInteraction) -> WebSearchTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, theme: theme, interfaceState: interfaceState, controllerInteraction: controllerInteraction), previousIndex: $0.2) } - let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, interfaceState: interfaceState, controllerInteraction: controllerInteraction)) } - - return WebSearchTransition(deleteItems: deleteIndices, insertItems: insertions, updateItems: updates, entryCount: toEntries.count, hasMore: hasMore) -} - -private func gridNodeLayoutForContainerLayout(_ layout: ContainerViewLayout) -> GridNodeLayoutType { - let itemsPerRow: Int - if case .compact = layout.metrics.widthClass { - switch layout.orientation { - case .portrait: - itemsPerRow = 3 - case .landscape: - itemsPerRow = 5 - } - } else { - itemsPerRow = 3 - } - - let side = floorToScreenPixels((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow)) - return .fixed(itemSize: CGSize(width: side, height: side), fillWidth: true, lineSpacing: 1.0, itemSpacing: 1.0) -} - - -private struct WebSearchRecentQueryStableId: Hashable { - let query: String -} - -private struct WebSearchRecentQueryEntry: Comparable, Identifiable { - let index: Int - let query: String - - var stableId: WebSearchRecentQueryStableId { - return WebSearchRecentQueryStableId(query: self.query) - } - - static func ==(lhs: WebSearchRecentQueryEntry, rhs: WebSearchRecentQueryEntry) -> Bool { - return lhs.index == rhs.index && lhs.query == rhs.query - } - - static func <(lhs: WebSearchRecentQueryEntry, rhs: WebSearchRecentQueryEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction, header: ListViewItemHeader) -> ListViewItem { - return WebSearchRecentQueryItem(account: account, theme: theme, strings: strings, query: self.query, tapped: { query in - controllerInteraction.setSearchQuery(query) - }, deleted: { query in - controllerInteraction.deleteRecentQuery(query) - }, header: header) - } -} - -private struct WebSearchRecentTransition { - let deletions: [ListViewDeleteItem] - let insertions: [ListViewInsertItem] - let updates: [ListViewUpdateItem] -} - -private func preparedWebSearchRecentTransition(from fromEntries: [WebSearchRecentQueryEntry], to toEntries: [WebSearchRecentQueryEntry], account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction, header: ListViewItemHeader) -> WebSearchRecentTransition { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) - - let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, controllerInteraction: controllerInteraction, header: header), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, theme: theme, strings: strings, controllerInteraction: controllerInteraction, header: header), directionHint: nil) } - - return WebSearchRecentTransition(deletions: deletions, insertions: insertions, updates: updates) -} - -class WebSearchControllerNode: ASDisplayNode { - private weak var controller: WebSearchController? - private let context: AccountContext - private let peer: EnginePeer? - private let chatLocation: ChatLocation? - private var theme: PresentationTheme - private var strings: PresentationStrings - private var presentationData: PresentationData - private let mode: WebSearchMode - private let attachment: Bool - - private let controllerInteraction: WebSearchControllerInteraction - private var webSearchInterfaceState: WebSearchInterfaceState - private let webSearchInterfaceStatePromise: ValuePromise - - private let segmentedContainerNode: ASDisplayNode - private let segmentedBackgroundNode: ASDisplayNode - private let segmentedSeparatorNode: ASDisplayNode - private let segmentedControlNode: SegmentedControlNode - - private let toolbarBackgroundNode: ASDisplayNode - private let toolbarSeparatorNode: ASDisplayNode - private let cancelButton: HighlightableButtonNode - private let sendButton: HighlightableButtonNode - private let badgeNode: WebSearchBadgeNode - - private let attributionNode: ASImageNode - - private let recentQueriesNode: ListView - private var enqueuedRecentTransitions: [(WebSearchRecentTransition, Bool)] = [] - - private var gridNode: GridNode - private var enqueuedTransitions: [(WebSearchTransition, Bool)] = [] - private var dequeuedInitialTransitionOnLayout = false - - private(set) var currentExternalResults: ChatContextResultCollection? - private var currentProcessedResults: ChatContextResultCollection? - private var currentEntries: [WebSearchEntry]? - private var hasMore = false - private var isLoadingMore = false - - private let hiddenMediaId = Promise(nil) - private var hiddenMediaDisposable: Disposable? - - private let results = ValuePromise(nil, ignoreRepeated: true) - - private let disposable = MetaDisposable() - private let loadMoreDisposable = MetaDisposable() - - private var recentDisposable: Disposable? - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var requestUpdateInterfaceState: (Bool, (WebSearchInterfaceState) -> WebSearchInterfaceState) -> Void = { _, _ in } - var cancel: (() -> Void)? - var dismissInput: (() -> Void)? - - var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } - - init(controller: WebSearchController, context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchControllerInteraction, peer: EnginePeer?, chatLocation: ChatLocation?, mode: WebSearchMode, attachment: Bool) { - self.controller = controller - self.context = context - self.theme = presentationData.theme - self.strings = presentationData.strings - self.presentationData = presentationData - self.controllerInteraction = controllerInteraction - self.peer = peer - self.chatLocation = chatLocation - self.mode = mode - self.attachment = attachment - - self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: context.sharedContext.currentPresentationData.with { $0 }) - self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true) - - self.segmentedContainerNode = ASDisplayNode() - self.segmentedContainerNode.clipsToBounds = true - - self.segmentedBackgroundNode = ASDisplayNode() - self.segmentedSeparatorNode = ASDisplayNode() - - let items = [ - strings.WebSearch_Images, - strings.WebSearch_GIFs - ] - self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: items.map { SegmentedControlItem(title: $0) }, selectedIndex: 0) - - self.toolbarBackgroundNode = ASDisplayNode() - self.toolbarSeparatorNode = ASDisplayNode() - - self.attributionNode = ASImageNode() - - self.cancelButton = HighlightableButtonNode() - self.sendButton = HighlightableButtonNode() - - self.badgeNode = WebSearchBadgeNode(theme: theme) - - self.gridNode = GridNode() - self.gridNode.backgroundColor = theme.list.plainBackgroundColor - - self.recentQueriesNode = ListViewImpl() - self.recentQueriesNode.backgroundColor = theme.list.plainBackgroundColor - self.recentQueriesNode.accessibilityPageScrolledString = { row, count in - return presentationData.strings.VoiceOver_ScrollStatus(row, count).string - } - - super.init() - - self.setViewBlock({ - return UITracingLayerView() - }) - - self.addSubnode(self.gridNode) - if !attachment { - self.addSubnode(self.recentQueriesNode) - } - self.addSubnode(self.segmentedContainerNode) - self.segmentedContainerNode.addSubnode(self.segmentedBackgroundNode) - self.segmentedContainerNode.addSubnode(self.segmentedSeparatorNode) -// if case .media = mode { -// self.segmentedContainerNode.addSubnode(self.segmentedControlNode) -// } - if !attachment { - self.addSubnode(self.toolbarBackgroundNode) - self.addSubnode(self.toolbarSeparatorNode) - self.addSubnode(self.cancelButton) - self.addSubnode(self.sendButton) - self.addSubnode(self.attributionNode) - self.addSubnode(self.badgeNode) - } - - self.segmentedControlNode.selectedIndexChanged = { [weak self] index in - if let strongSelf = self, let scope = WebSearchScope(rawValue: Int32(index)) { - let _ = updateWebSearchSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager) { _ -> WebSearchSettings in - return WebSearchSettings(scope: scope) - }.start() - strongSelf.requestUpdateInterfaceState(true) { current in - return current.withUpdatedScope(scope) - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) - self.sendButton.addTarget(self, action: #selector(self.sendPressed), forControlEvents: .touchUpInside) - - self.applyPresentationData() - - self.disposable.set((combineLatest(self.results.get(), self.webSearchInterfaceStatePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] results, interfaceState in - if let strongSelf = self { - strongSelf.updateInternalResults(results, interfaceState: interfaceState) - } - })) - - if !attachment { - let previousRecentItems = Atomic<[WebSearchRecentQueryEntry]?>(value: nil) - self.recentDisposable = (combineLatest(webSearchRecentQueries(engine: self.context.engine), self.webSearchInterfaceStatePromise.get()) - |> deliverOnMainQueue).start(next: { [weak self] queries, interfaceState in - if let strongSelf = self { - var entries: [WebSearchRecentQueryEntry] = [] - for i in 0 ..< queries.count { - entries.append(WebSearchRecentQueryEntry(index: i, query: queries[i])) - } - - let header = ChatListSearchItemHeader(type: .recentPeers, theme: interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, actionTitle: interfaceState.presentationData.strings.WebSearch_RecentSectionClear, action: { _ in - let _ = clearRecentWebSearchQueries(engine: strongSelf.context.engine).start() - }) - - let previousEntries = previousRecentItems.swap(entries) - - let transition = preparedWebSearchRecentTransition(from: previousEntries ?? [], to: entries, account: strongSelf.context.account, theme: interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, header: header) - strongSelf.enqueueRecentTransition(transition, firstTime: previousEntries == nil) - } - }) - } - - self.recentQueriesNode.beganInteractiveDragging = { [weak self] _ in - self?.dismissInput?() - } - - self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries { - if bottom.0 >= entries.count - 8 { - strongSelf.loadMore() - } - } - } - self.gridNode.scrollingInitiated = { [weak self] in - self?.dismissInput?() - } - - self.sendButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, strongSelf.badgeNode.alpha > 0.0 { - if highlighted { - strongSelf.badgeNode.layer.removeAnimation(forKey: "opacity") - strongSelf.badgeNode.alpha = 0.4 - } else { - strongSelf.badgeNode.alpha = 1.0 - strongSelf.badgeNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - - self.hiddenMediaDisposable = (self.hiddenMediaId.get() - |> deliverOnMainQueue).start(next: { [weak self] id in - if let strongSelf = self { - strongSelf.controllerInteraction.hiddenMediaId = id - - strongSelf.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? WebSearchItemNode { - itemNode.updateHiddenMedia() - } - } - } - }) - } - - deinit { - self.disposable.dispose() - self.recentDisposable?.dispose() - self.loadMoreDisposable.dispose() - self.hiddenMediaDisposable?.dispose() - } - - func updatePresentationData(theme: PresentationTheme, strings: PresentationStrings) { - let themeUpdated = theme !== self.theme - self.theme = theme - self.strings = strings - - self.applyPresentationData(themeUpdated: themeUpdated) - } - - func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { - self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: transition) - transition.updateAlpha(node: self.segmentedBackgroundNode, alpha: alpha) - } - - func applyPresentationData(themeUpdated: Bool = true) { - self.cancelButton.setTitle(self.strings.Common_Cancel, with: Font.regular(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal) - - if let selectionState = self.controllerInteraction.selectionState { - let sendEnabled = selectionState.count() > 0 - let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor - self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal) - } - - if themeUpdated { - self.gridNode.backgroundColor = self.theme.list.plainBackgroundColor - self.segmentedBackgroundNode.backgroundColor = self.theme.list.plainBackgroundColor - self.segmentedSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor - self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) - self.toolbarBackgroundNode.backgroundColor = self.theme.rootController.navigationBar.opaqueBackgroundColor - self.toolbarSeparatorNode.backgroundColor = self.theme.rootController.navigationBar.separatorColor - } - - let gifProviderImage: UIImage? - if let gifProvider = self.webSearchInterfaceState.gifProvider { - switch gifProvider { - case "tenor": - gifProviderImage = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Tenor"), color: self.theme.list.itemSecondaryTextColor) - case "giphy": - gifProviderImage = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Giphy"), color: self.theme.list.itemSecondaryTextColor) - default: - gifProviderImage = nil - } - } else { - gifProviderImage = nil - } - let previousGifProviderImage = self.attributionNode.image - self.attributionNode.image = gifProviderImage - - if previousGifProviderImage == nil, let validLayout = self.containerLayout { - self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate) - } - } - - func animateIn(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - } - - func animateOut(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in - completion?() - }) - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.input]) - insets.top += navigationBarHeight - - let hasQuery = !(self.webSearchInterfaceState.state?.query ?? "").isEmpty - - let segmentedHeight: CGFloat = self.segmentedControlNode.supernode != nil ? 44.0 : 5.0 - let panelY: CGFloat = insets.top - UIScreenPixel - 4.0 - - transition.updateSublayerTransformOffset(layer: self.segmentedContainerNode.layer, offset: CGPoint(x: 0.0, y: !hasQuery ? -44.0 : 0.0), completion: nil) - transition.updateFrame(node: self.segmentedContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight))) - - transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: segmentedHeight))) - transition.updateFrame(node: self.segmentedSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: segmentedHeight - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - - let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 10.0 * 2.0), transition: transition) - transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layout.size.width - layout.safeInsets.left - layout.safeInsets.right - controlSize.width) / 2.0), y: 5.0), size: controlSize)) - - insets.top -= 4.0 - - let toolbarHeight: CGFloat = 44.0 - let toolbarY = layout.size.height - toolbarHeight - insets.bottom - transition.updateFrame(node: self.toolbarBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: toolbarY), size: CGSize(width: layout.size.width, height: toolbarHeight + insets.bottom))) - transition.updateFrame(node: self.toolbarSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: toolbarY), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - - if let image = self.attributionNode.image { - self.attributionNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - image.size.width) / 2.0), y: toolbarY + floor((toolbarHeight - image.size.height) / 2.0)), size: image.size) - transition.updateAlpha(node: self.attributionNode, alpha: self.webSearchInterfaceState.state?.scope == .gifs ? 1.0 : 0.0) - } - - let toolbarPadding: CGFloat = 10.0 - let cancelSize = self.cancelButton.measure(CGSize(width: layout.size.width, height: toolbarHeight)) - transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: toolbarPadding + layout.safeInsets.left, y: toolbarY), size: CGSize(width: cancelSize.width, height: toolbarHeight))) - - let sendSize = self.sendButton.measure(CGSize(width: layout.size.width, height: toolbarHeight)) - let sendFrame = CGRect(origin: CGPoint(x: layout.size.width - toolbarPadding - layout.safeInsets.right - sendSize.width, y: toolbarY), size: CGSize(width: sendSize.width, height: toolbarHeight)) - transition.updateFrame(node: self.sendButton, frame: sendFrame) - - if let selectionState = self.controllerInteraction.selectionState { - self.sendButton.isHidden = false - - let previousSendEnabled = self.sendButton.isEnabled - let sendEnabled = selectionState.count() > 0 - self.sendButton.isEnabled = sendEnabled - if sendEnabled != previousSendEnabled { - let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor - self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal) - } - - let selectedCount = selectionState.count() - let badgeText = String(selectedCount) - if selectedCount > 0 && (self.badgeNode.text != badgeText || self.badgeNode.alpha < 1.0) { - if transition.isAnimated { - var incremented = true - if let previousCount = Int(self.badgeNode.text) { - incremented = selectedCount > previousCount || self.badgeNode.alpha < 1.0 - } - self.badgeNode.animateBump(incremented: incremented) - } - self.badgeNode.text = badgeText - - let badgeSize = self.badgeNode.measure(layout.size) - transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: CGPoint(x: sendFrame.minX - badgeSize.width - 6.0, y: toolbarY + 11.0), size: badgeSize)) - transition.updateAlpha(node: self.badgeNode, alpha: 1.0) - } else if selectedCount == 0 { - if transition.isAnimated { - self.badgeNode.animateOut() - } - let badgeSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: CGPoint(x: sendFrame.minX - badgeSize.width - 6.0, y: toolbarY + 11.0), size: badgeSize)) - transition.updateAlpha(node: self.badgeNode, alpha: 0.0) - } - } else { - self.sendButton.isHidden = true - } - - let previousBounds = self.gridNode.bounds - self.gridNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height) - self.gridNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - - insets.top += segmentedHeight - insets.bottom += toolbarHeight - - let gridInsets = UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: insets.bottom, right: layout.safeInsets.right) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: gridInsets, preloadSize: 400.0, type: gridNodeLayoutForContainerLayout(layout)), transition: .immediate), itemTransition: .immediate, stationaryItems: .none,updateFirstIndexInSectionOffset: nil), completion: { _ in }) - - let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) - - self.recentQueriesNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.recentQueriesNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - - if !self.dequeuedInitialTransitionOnLayout { - self.dequeuedInitialTransitionOnLayout = true - self.dequeueTransition() - } - } - - func updateInterfaceState(_ interfaceState: WebSearchInterfaceState, animated: Bool) { - let previousGifProvider = self.webSearchInterfaceState.gifProvider - self.webSearchInterfaceState = interfaceState - self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState) - - if let state = interfaceState.state { - self.segmentedControlNode.selectedIndex = Int(state.scope.rawValue) - } - - if previousGifProvider != interfaceState.gifProvider { - self.applyPresentationData(themeUpdated: false) - } - - if let validLayout = self.containerLayout { - self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) - } - } - - func updateSelectionState(animated: Bool) { - self.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? WebSearchItemNode { - itemNode.updateSelectionState(animated: animated) - } - } - - if let validLayout = self.containerLayout { - self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) - } - } - - func updateResults(_ results: ChatContextResultCollection?, immediate: Bool = false) { - if self.currentExternalResults == results { - return - } - let previousResults = self.currentExternalResults - self.currentExternalResults = results - self.currentProcessedResults = results - - self.isLoadingMore = false - self.loadMoreDisposable.set(nil) - - if immediate && previousResults?.query == results?.query && previousResults?.botId != results?.botId { - let previousNode = self.gridNode - - let gridNode = GridNode() - gridNode.backgroundColor = theme.list.plainBackgroundColor - gridNode.frame = previousNode.frame - - gridNode.visibleItemsUpdated = { [weak self] visibleItems in - if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries { - if bottom.0 >= entries.count - 8 { - strongSelf.loadMore() - } - } - } - gridNode.scrollingInitiated = { [weak self] in - self?.dismissInput?() - } - - if self.recentQueriesNode.supernode != nil { - self.insertSubnode(gridNode, belowSubnode: self.recentQueriesNode) - } else { - self.insertSubnode(gridNode, aboveSubnode: previousNode) - } - self.gridNode = gridNode - self.currentEntries = nil - let directionMultiplier: CGFloat - if let state = self.webSearchInterfaceState.state { - switch state.scope { - case .images: - directionMultiplier = 1.0 - case .gifs: - directionMultiplier = -1.0 - } - } else { - directionMultiplier = 1.0 - } - - previousNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -directionMultiplier * self.bounds.width, y: 0.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak previousNode] _ in - previousNode?.removeFromSupernode() - }) - gridNode.layer.animatePosition(from: CGPoint(x: directionMultiplier * bounds.width, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } else if previousResults?.botId != results?.botId || previousResults?.query != results?.query { - self.scrollToTop() - } - - self.results.set(results) - } - - func clearResults() { - self.results.set(nil) - } - - private func loadMore() { - guard !self.isLoadingMore, let currentProcessedResults = self.currentProcessedResults, currentProcessedResults.results.count > 55, let nextOffset = currentProcessedResults.nextOffset else { - return - } - self.isLoadingMore = true - let geoPoint = currentProcessedResults.geoPoint.flatMap { geoPoint -> (Double, Double) in - return (geoPoint.latitude, geoPoint.longitude) - } - self.loadMoreDisposable.set((self.context.engine.messages.requestChatContextResults(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, location: .single(geoPoint), offset: nextOffset) - |> deliverOnMainQueue).start(next: { [weak self] nextResults in - guard let strongSelf = self, let nextResults = nextResults else { - return - } - strongSelf.isLoadingMore = false - var results: [ChatContextResult] = [] - var existingIds = Set() - for result in currentProcessedResults.results { - results.append(result) - existingIds.insert(result.id) - } - for result in nextResults.results.results { - if !existingIds.contains(result.id) { - results.append(result) - existingIds.insert(result.id) - } - } - let mergedResults = ChatContextResultCollection(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, geoPoint: currentProcessedResults.geoPoint, queryId: nextResults.results.queryId, nextOffset: nextResults.results.nextOffset, presentation: currentProcessedResults.presentation, switchPeer: currentProcessedResults.switchPeer, webView: currentProcessedResults.webView, results: results, cacheTimeout: currentProcessedResults.cacheTimeout) - strongSelf.currentProcessedResults = mergedResults - strongSelf.results.set(mergedResults) - })) - } - - private func updateInternalResults(_ results: ChatContextResultCollection?, interfaceState: WebSearchInterfaceState) { - var entries: [WebSearchEntry] = [] - var hasMore = false - if let state = interfaceState.state, state.query.isEmpty { - } else if let results = results { - hasMore = results.nextOffset != nil - - var index = 0 - var resultIds = Set() - for result in results.results { - let entry = WebSearchEntry(index: index, result: result) - if resultIds.contains(entry.stableId) { - continue - } else { - resultIds.insert(entry.stableId) - } - entries.append(entry) - index += 1 - } - } - - let firstTime = self.currentEntries == nil - let transition = preparedTransition(from: self.currentEntries ?? [], to: entries, hasMore: hasMore, account: self.context.account, theme: interfaceState.presentationData.theme, interfaceState: interfaceState, controllerInteraction: self.controllerInteraction) - self.currentEntries = entries - - self.enqueueTransition(transition, firstTime: firstTime) - } - - private func enqueueTransition(_ transition: WebSearchTransition, firstTime: Bool) { - self.enqueuedTransitions.append((transition, firstTime)) - - if self.containerLayout != nil { - while !self.enqueuedTransitions.isEmpty { - self.dequeueTransition() - } - } - } - - private func dequeueTransition() { - if let (transition, _) = self.enqueuedTransitions.first { - self.enqueuedTransitions.remove(at: 0) - - if let state = self.webSearchInterfaceState.state { - self.recentQueriesNode.isHidden = !state.query.isEmpty - } - - self.hasMore = transition.hasMore - self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deleteItems, insertItems: transition.insertItems, updateItems: transition.updateItems, scrollToItem: nil, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: true), completion: { _ in }) - } - } - - private func enqueueRecentTransition(_ transition: WebSearchRecentTransition, firstTime: Bool) { - enqueuedRecentTransitions.append((transition, firstTime)) - - if self.containerLayout != nil { - while !self.enqueuedRecentTransitions.isEmpty { - self.dequeueRecentTransition() - } - } - } - - private func dequeueRecentTransition() { - if let (transition, firstTime) = self.enqueuedRecentTransitions.first { - self.enqueuedRecentTransitions.remove(at: 0) - - var options = ListViewDeleteAndInsertOptions() - if firstTime { - options.insert(.PreferSynchronousDrawing) - } else { - options.insert(.AnimateInsertion) - } - - self.recentQueriesNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in - }) - } - } - - @objc private func cancelPressed() { - self.cancel?() - } - - @objc private func sendPressed() { - self.controllerInteraction.sendSelected(nil, false, nil, nil) - - self.cancel?() - } - - func scrollToTop(animated: Bool = false) { - self.gridNode.scrollView.setContentOffset(CGPoint(x: 0.0, y: -self.gridNode.scrollView.contentInset.top), animated: animated) - } - - func openResult(currentResult: ChatContextResult, present: @escaping (ViewController, Any?) -> Void) { - self.view.endEditing(true) - - if self.controllerInteraction.selectionState != nil { - if let state = self.webSearchInterfaceState.state, state.scope == .images { - if let results = self.currentProcessedResults?.results { - presentLegacyWebSearchGallery(context: self.context, peer: self.peer, threadTitle: nil, chatLocation: self.chatLocation, presentationData: self.presentationData, results: results, current: currentResult, selectionContext: self.controllerInteraction.selectionState, editingContext: self.controllerInteraction.editingState, updateHiddenMedia: { [weak self] id in - self?.hiddenMediaId.set(.single(id)) - }, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in - return self?.gridNode.view - }, transitionView: { [weak self] result in - return self?.transitionNode(for: result)?.transitionView() - }, completed: { [weak self] result in - if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) - strongSelf.cancel?() - } - }, getCaptionPanelView: self.getCaptionPanelView, present: present) - } - } else { - if let results = self.currentProcessedResults?.results { - var entries: [WebSearchGalleryEntry] = [] - var centralIndex: Int = 0 - for i in 0 ..< results.count { - entries.append(WebSearchGalleryEntry(index: entries.count, result: results[i])) - if results[i] == currentResult { - centralIndex = i - } - } - - let controller = WebSearchGalleryController(context: self.context, peer: self.peer, selectionState: self.controllerInteraction.selectionState, editingState: self.controllerInteraction.editingState, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in - - }, baseNavigationController: nil, sendCurrent: { [weak self] result in - if let strongSelf = self { - strongSelf.controllerInteraction.sendSelected(result, false, nil, nil) - strongSelf.cancel?() - } - }) - self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue) - |> map { entry in - return entry?.result.id - }) - present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in - if let strongSelf = self { - var transitionNode: WebSearchItemNode? - strongSelf.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id { - transitionNode = itemNode - } - } - if let transitionNode = transitionNode { - return GalleryTransitionArguments(transitionNode: (transitionNode, transitionNode.bounds, { [weak transitionNode] in - return (transitionNode?.transitionView().snapshotContentTree(unhide: true), nil) - }), addToTransitionSurface: { view in - if let strongSelf = self { - strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view) - } - }) - } - } - return nil - })) - } - } - } else { - if let mode = self.controller?.mode, case let .editor(completion) = mode { - if let item = legacyWebSearchItem(account: self.context.account, result: currentResult) { - let _ = (item.originalImage - |> deliverOnMainQueue).start(next: { image in - if !image.degraded() { - completion(image) - } - }) - } - } else { - presentLegacyWebSearchEditor(context: self.context, theme: self.theme, result: currentResult, initialLayout: self.containerLayout?.0, updateHiddenMedia: { [weak self] id in - self?.hiddenMediaId.set(.single(id)) - }, transitionHostView: { [weak self] in - return self?.gridNode.view - }, transitionView: { [weak self] result in - return self?.transitionNode(for: result)?.transitionView() - }, completed: { [weak self] result in - if let strongSelf = self { - strongSelf.controllerInteraction.avatarCompleted(result) - strongSelf.cancel?() - } - }, present: present) - } - } - } - - private func transitionNode(for result: ChatContextResult) -> WebSearchItemNode? { - var transitionNode: WebSearchItemNode? - self.gridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == result.id { - transitionNode = itemNode - } - } - return transitionNode - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift deleted file mode 100644 index 48266cf949..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift +++ /dev/null @@ -1,354 +0,0 @@ -import Foundation -import UIKit -import Display -import QuickLook -import SwiftSignalKit -import AsyncDisplayKit -import TelegramCore -import LegacyComponents -import TelegramPresentationData -import AccountContext -import GalleryUI -import TelegramUniversalVideoContent - -final class WebSearchGalleryControllerInteraction { - let dismiss: (Bool) -> Void - let send: (ChatContextResult) -> Void - let selectionState: TGMediaSelectionContext? - let editingState: TGMediaEditingContext - - init(dismiss: @escaping (Bool) -> Void, send: @escaping (ChatContextResult) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) { - self.dismiss = dismiss - self.send = send - self.selectionState = selectionState - self.editingState = editingState - } -} - -struct WebSearchGalleryEntry: Equatable { - let index: Int - let result: ChatContextResult - - static func ==(lhs: WebSearchGalleryEntry, rhs: WebSearchGalleryEntry) -> Bool { - return lhs.result == rhs.result - } - - func item(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchGalleryControllerInteraction?) -> GalleryItem { - switch self.result { - case let .externalReference(externalReference): - if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let dimensions = content.dimensions { - let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil, videoCodec: nil)], alternativeRepresentations: [])) - return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), userLocation: .other, fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true, storeAfterDownload: nil), controllerInteraction: controllerInteraction) - } - case let .internalReference(internalReference): - if let file = internalReference.file { - return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), userLocation: .other, fileReference: .standalone(media: file), loopVideo: true, enableSound: false, fetchAutomatically: true, storeAfterDownload: nil), controllerInteraction: controllerInteraction) - } - } - preconditionFailure() - } -} - -final class WebSearchGalleryControllerPresentationArguments { - let animated: Bool - let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments? - - init(animated: Bool = true, transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) { - self.animated = animated - self.transitionArguments = transitionArguments - } -} - -class WebSearchGalleryController: ViewController { - private static let navigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: .white, accentForegroundColor: .black) - - private var galleryNode: GalleryControllerNode { - return self.displayNode as! GalleryControllerNode - } - - private let context: AccountContext - private var presentationData: PresentationData - private var controllerInteraction: WebSearchGalleryControllerInteraction? - - private let _ready = Promise() - override var ready: Promise { - return self._ready - } - private var didSetReady = false - - private let disposable = MetaDisposable() - - private var entries: [WebSearchGalleryEntry] = [] - private var centralEntryIndex: Int? - - private let centralItemTitle = Promise() - private let centralItemTitleContent = Promise() - private let centralItemNavigationStyle = Promise() - private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() - private let centralItemAttributesDisposable = DisposableSet() - - private let titleView: GalleryTitleView - - private let checkedDisposable = MetaDisposable() - private var checkNode: GalleryNavigationCheckNode? - - private let _hiddenMedia = Promise(nil) - var hiddenMedia: Signal { - return self._hiddenMedia.get() - } - - private let replaceRootController: (ViewController, Promise?) -> Void - private let baseNavigationController: NavigationController? - - init(context: AccountContext, peer: EnginePeer?, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, Promise?) -> Void, baseNavigationController: NavigationController?, sendCurrent: @escaping (ChatContextResult) -> Void) { - self.context = context - self.replaceRootController = replaceRootController - self.baseNavigationController = baseNavigationController - - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - self.titleView = GalleryTitleView(context: context, presentationData: self.presentationData) - - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: WebSearchGalleryController.navigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) - - self.controllerInteraction = WebSearchGalleryControllerInteraction(dismiss: { [weak self] animated in - self?.dismiss(forceAway: false) - }, send: { [weak self] current in - sendCurrent(current) - self?.dismiss(forceAway: true) - }, selectionState: selectionState, editingState: editingState) - - if let title = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) { - let recipientNode = GalleryNavigationRecipientNode(color: .white, title: title) - let leftItem = UIBarButtonItem(customDisplayNode: recipientNode) - self.navigationItem.leftBarButtonItem = leftItem - } - - let checkNode = GalleryNavigationCheckNode(theme: self.presentationData.theme) - checkNode.addTarget(target: self, action: #selector(self.checkPressed)) - let rightItem = UIBarButtonItem(customDisplayNode: checkNode) - self.navigationItem.rightBarButtonItem = rightItem - self.checkNode = checkNode - - self.statusBar.statusBarStyle = .White - - let entriesSignal: Signal<[WebSearchGalleryEntry], NoError> = .single(entries) - - self.disposable.set((entriesSignal |> deliverOnMainQueue).start(next: { [weak self] entries in - if let strongSelf = self { - strongSelf.entries = entries - strongSelf.centralEntryIndex = centralIndex - if strongSelf.isViewLoaded { - strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ - $0.item(context: context, presentationData: strongSelf.presentationData, controllerInteraction: strongSelf.controllerInteraction) - }), centralItemIndex: centralIndex) - - let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in - strongSelf?.didSetReady = true - } - strongSelf._ready.set(ready |> map { true }) - } - } - })) - - self.centralItemAttributesDisposable.add(self.centralItemTitle.get().start(next: { [weak self] title in - self?.navigationItem.title = title - })) - - self.centralItemAttributesDisposable.add(self.centralItemTitleContent.get().start(next: { [weak self] titleContent in - self?.titleView.setContent(content: titleContent) - })) - - self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, _ in - self?.galleryNode.updatePresentationState({ - $0.withUpdatedFooterContentNode(footerContentNode) - }, transition: .immediate) - })) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.disposable.dispose() - self.checkedDisposable.dispose() - self.centralItemAttributesDisposable.dispose() - } - - @objc func checkPressed() { - if let checkNode = self.checkNode, let controllerInteraction = self.controllerInteraction, let centralItemNode = self.galleryNode.pager.centralItemNode() as? WebSearchVideoGalleryItemNode, let item = centralItemNode.item { - let legacyItem = LegacyWebSearchItem(result: item.result) - - controllerInteraction.selectionState?.setItem(legacyItem, selected: checkNode.isChecked) - } - } - - private func dismiss(forceAway: Bool, animated: Bool = true) { - var animatedOutNode = true - var animatedOutInterface = false - - let completion = { [weak self] in - if animatedOutNode && animatedOutInterface { - self?._hiddenMedia.set(.single(nil)) - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - } - - if animated { - if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments { - if !self.entries.isEmpty { - if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway { - animatedOutNode = false - centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: { - animatedOutNode = true - completion() - }) - } - } - } - - self.galleryNode.animateOut(animateContent: animatedOutNode, completion: { - animatedOutInterface = true - completion() - }) - } else { - animatedOutInterface = true - completion() - } - } - - override func loadDisplayNode() { - let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in - if let strongSelf = self { - strongSelf.present(controller, in: .window(.root), with: arguments, blockInteraction: true) - } - }, pushController: { _ in - }, dismissController: { [weak self] in - self?.dismiss(forceAway: true) - }, replaceRootController: { [weak self] controller, ready in - if let strongSelf = self { - strongSelf.replaceRootController(controller, ready) - } - }, editMedia: { _ in - }, controller: { [weak self] in - return self - }, currentItemNode: { [weak self] in - return self?.galleryNode.pager.centralItemNode() - }) - self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: self.titleView) - self.displayNodeDidLoad() - - self.galleryNode.statusBar = self.statusBar - self.galleryNode.navigationBar = self.navigationBar - - self.galleryNode.transitionDataForCentralItem = { [weak self] in - if let strongSelf = self { - if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? WebSearchGalleryControllerPresentationArguments { - if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) { - return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface) - } - } - } - return nil - } - self.galleryNode.dismiss = { [weak self] in - self?._hiddenMedia.set(.single(nil)) - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - - self.galleryNode.pager.replaceItems(self.entries.map({ - $0.item(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction) - }), centralItemIndex: self.centralEntryIndex) - - self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in - if let strongSelf = self { - var item: WebSearchGalleryEntry? - if let index = index { - item = strongSelf.entries[index] - - if let node = strongSelf.galleryNode.pager.centralItemNode() { - strongSelf.centralItemTitle.set(node.title()) - strongSelf.centralItemTitleContent.set(node.titleContent()) - strongSelf.centralItemNavigationStyle.set(node.navigationStyle()) - strongSelf.centralItemFooterContentNode.set(node.footerContent()) - } - - if let checkNode = strongSelf.checkNode, let controllerInteraction = strongSelf.controllerInteraction, let selectionState = controllerInteraction.selectionState, let item = item { - checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: false) - } - } - if strongSelf.didSetReady { - strongSelf._hiddenMedia.set(.single(item)) - } - } - } - - let selectionState = self.controllerInteraction?.selectionState - let selectionUpdated = Signal { subscriber in - if let selectionState = selectionState { - let disposable = selectionState.selectionChangedSignal()!.start(next: { _ in - subscriber.putNext(Void()) - }, error: { _ in }, completed: {})! - return ActionDisposable { - disposable.dispose() - } - } else { - subscriber.putCompletion() - return EmptyDisposable - } - } - self.checkedDisposable.set((selectionUpdated - |> deliverOnMainQueue).start(next: { [weak self] _ in - if let strongSelf = self, let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() { - let item = strongSelf.entries[centralItemNode.index] - if let checkNode = strongSelf.checkNode, let controllerInteraction = strongSelf.controllerInteraction, let selectionState = controllerInteraction.selectionState { - checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: true) - } - } - })) - - let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in - self?.didSetReady = true - } - self._ready.set(ready |> map { true }) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - var nodeAnimatesItself = false - - if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments { - self.centralItemTitle.set(centralItemNode.title()) - self.centralItemTitleContent.set(centralItemNode.titleContent()) - self.centralItemNavigationStyle.set(centralItemNode.navigationStyle()) - self.centralItemFooterContentNode.set(centralItemNode.footerContent()) - - let item = self.entries[centralItemNode.index] - if let transitionArguments = presentationArguments.transitionArguments(item) { - nodeAnimatesItself = true - centralItemNode.activateAsInitial() - - if presentationArguments.animated { - centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {}) - } - - if let checkNode = self.checkNode, let controllerInteraction = self.controllerInteraction, let selectionState = controllerInteraction.selectionState { - checkNode.setIsChecked(selectionState.isIdentifierSelected(item.result.id), animated: false) - } - - self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) - } - } - - self.galleryNode.animateIn(animateContent: !nodeAnimatesItself, useSimpleAnimation: false) - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.galleryNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchGalleryFooterContentNode.swift b/submodules/WebSearchUI/Sources/WebSearchGalleryFooterContentNode.swift deleted file mode 100644 index 8b355c575e..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchGalleryFooterContentNode.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import SwiftSignalKit -import LegacyComponents -import TelegramPresentationData -import AccountContext -import GalleryUI - -final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode { - private let context: AccountContext - private var theme: PresentationTheme - private var strings: PresentationStrings - - private let cancelButton: HighlightableButtonNode - private let sendButton: HighlightableButtonNode - - var cancel: (() -> Void)? - var send: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData) { - self.context = context - self.theme = presentationData.theme - self.strings = presentationData.strings - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setImage(TGComponentsImageNamed("PhotoPickerBackIcon"), for: [.normal]) - self.sendButton = HighlightableButtonNode() - self.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.theme), for: [.normal]) - - super.init() - - self.addSubnode(self.cancelButton) - self.addSubnode(self.sendButton) - - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside) - } - - func setCaption(_ caption: String) { - - } - - override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> LayoutInfo { - let width = size.width - let panelSize: CGFloat = 49.0 - var panelHeight: CGFloat = panelSize + bottomInset - panelHeight += contentInset - - self.cancelButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize)) - self.sendButton.frame = CGRect(origin: CGPoint(x: width - panelSize - rightInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize)) - - return LayoutInfo(height: panelHeight, needsShadow: false) - } - - override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) { - self.cancelButton.alpha = 1.0 - self.sendButton.alpha = 1.0 - } - - override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { - self.cancelButton.alpha = 0.0 - self.sendButton.alpha = 0.0 - completion() - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func sendButtonPressed() { - self.send?() - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchInterfaceState.swift b/submodules/WebSearchUI/Sources/WebSearchInterfaceState.swift deleted file mode 100644 index ddeb5e1684..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchInterfaceState.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import UIKit -import TelegramPresentationData -import TelegramUIPreferences - -struct WebSearchInterfaceInnerState: Equatable { - let scope: WebSearchScope - let query: String -} - -struct WebSearchInterfaceState: Equatable { - let state: WebSearchInterfaceInnerState? - let presentationData: PresentationData - let gifProvider: String? - - init (presentationData: PresentationData) { - self.state = nil - self.presentationData = presentationData - self.gifProvider = nil - } - - init(state: WebSearchInterfaceInnerState?, presentationData: PresentationData, gifProvider: String? = nil) { - self.state = state - self.presentationData = presentationData - self.gifProvider = gifProvider - } - - func withUpdatedScope(_ scope: WebSearchScope) -> WebSearchInterfaceState { - return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: scope, query: self.state?.query ?? ""), presentationData: self.presentationData, gifProvider: self.gifProvider) - } - - func withUpdatedQuery(_ query: String) -> WebSearchInterfaceState { - return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: self.state?.scope ?? .images, query: query), presentationData: self.presentationData, gifProvider: self.gifProvider) - } - - func withUpdatedPresentationData(_ presentationData: PresentationData) -> WebSearchInterfaceState { - return WebSearchInterfaceState(state: self.state, presentationData: presentationData, gifProvider: self.gifProvider) - } - - func withUpdatedGifProvider(_ gifProvider: String?) -> WebSearchInterfaceState { - return WebSearchInterfaceState(state: self.state, presentationData: self.presentationData, gifProvider: gifProvider) - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchItem.swift b/submodules/WebSearchUI/Sources/WebSearchItem.swift deleted file mode 100644 index cc3b7f9587..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchItem.swift +++ /dev/null @@ -1,316 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import SwiftSignalKit -import TelegramPresentationData -import CheckNode -import PhotoResources -import RadialStatusNode - -final class WebSearchItem: GridItem { - var section: GridSection? - - let account: Account - let theme: PresentationTheme - let interfaceState: WebSearchInterfaceState - let result: ChatContextResult - let controllerInteraction: WebSearchControllerInteraction - - public init(account: Account, theme: PresentationTheme, interfaceState: WebSearchInterfaceState, result: ChatContextResult, controllerInteraction: WebSearchControllerInteraction) { - self.account = account - self.theme = theme - self.result = result - self.interfaceState = interfaceState - self.controllerInteraction = controllerInteraction - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = WebSearchItemNode() - node.setup(item: self, synchronousLoad: synchronousLoad) - return node - } - - func update(node: GridItemNode) { - guard let node = node as? WebSearchItemNode else { - assertionFailure() - return - } - node.setup(item: self, synchronousLoad: false) - } -} - -final class WebSearchItemNode: GridItemNode { - private let imageNodeBackground: ASDisplayNode - private let imageNode: TransformImageNode - private var checkNode: CheckNode? - private var statusNode: RadialStatusNode? - - private(set) var item: WebSearchItem? - private var currentDimensions: CGSize? - - private let fetchStatusDisposable = MetaDisposable() - private let fetchDisposable = MetaDisposable() - private var resourceStatus: EngineMediaResource.FetchStatus? - - override init() { - self.imageNodeBackground = ASDisplayNode() - self.imageNodeBackground.isLayerBacked = true - - self.imageNode = TransformImageNode() - self.imageNode.contentAnimations = [.subsequentUpdates] - self.imageNode.displaysAsynchronously = false - - super.init() - - self.addSubnode(self.imageNodeBackground) - self.addSubnode(self.imageNode) - } - - deinit { - self.fetchStatusDisposable.dispose() - self.fetchDisposable.dispose() - } - - override func didLoad() { - super.didLoad() - - let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) - recognizer.tapActionAtPoint = { _ in - return .waitForSingleTap - } - self.imageNode.view.addGestureRecognizer(recognizer) - } - - func updateProgress(_ value: Float?, animated: Bool) { - if let value { - let statusNode: RadialStatusNode - if let current = self.statusNode { - statusNode = current - } else { - statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6)) - statusNode.isUserInteractionEnabled = false - self.addSubnode(statusNode) - self.statusNode = statusNode - } - let adjustedProgress = max(0.027, CGFloat(value)) - let state: RadialStatusNodeState = .progress(color: .white, lineWidth: nil, value: adjustedProgress, cancelEnabled: true, animateRotation: true) - statusNode.transitionToState(state) - } else if let statusNode = self.statusNode { - self.statusNode = nil - if animated { - statusNode.transitionToState(.none, animated: true, completion: { [weak statusNode] in - statusNode?.removeFromSupernode() - }) - } else { - statusNode.removeFromSupernode() - } - } - } - - func setup(item: WebSearchItem, synchronousLoad: Bool) { - if self.item !== item { - var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - - var thumbnailDimensions: CGSize? - var thumbnailResource: TelegramMediaResource? - var imageResource: TelegramMediaResource? - var imageDimensions: CGSize? - var immediateThumbnailData: Data? - - switch item.result { - case let .externalReference(externalReference): - if let content = externalReference.content, externalReference.type != "gif" { - imageResource = content.resource - } else if let thumbnail = externalReference.thumbnail { - imageResource = thumbnail.resource - } - imageDimensions = externalReference.content?.dimensions?.cgSize - case let .internalReference(internalReference): - if let image = internalReference.image { - immediateThumbnailData = image.immediateThumbnailData - if let largestRepresentation = largestImageRepresentation(image.representations) { - imageDimensions = largestRepresentation.dimensions.cgSize - } - imageResource = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 200, height: 100))?.resource - if let file = internalReference.file { - if let thumbnailRepresentation = smallestImageRepresentation(file.previewRepresentations) { - thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize - thumbnailResource = thumbnailRepresentation.resource - } - } else { - if let thumbnailRepresentation = smallestImageRepresentation(image.representations) { - thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize - thumbnailResource = thumbnailRepresentation.resource - } - } - } else if let file = internalReference.file { - immediateThumbnailData = file.immediateThumbnailData - if let dimensions = file.dimensions { - imageDimensions = dimensions.cgSize - } else if let largestRepresentation = largestImageRepresentation(file.previewRepresentations) { - imageDimensions = largestRepresentation.dimensions.cgSize - } - imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource - } - } - - var representations: [TelegramMediaImageRepresentation] = [] - if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions { - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - if let imageResource = imageResource, let imageDimensions = imageDimensions { - representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) - } - if !representations.isEmpty { - let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) - updateImageSignal = mediaGridMessagePhoto(account: item.account, userLocation: .other, photoReference: .standalone(media: tmpImage)) - } else { - updateImageSignal = .complete() - } - - if let updateImageSignal = updateImageSignal { - let editingContext = item.controllerInteraction.editingState - let editableItem = LegacyWebSearchItem(result: item.result) - let editedImageSignal = Signal { subscriber in - if let signal = editingContext.thumbnailImageSignal(for: editableItem) { - let disposable = signal.start(next: { next in - if let image = next as? UIImage { - subscriber.putNext(image) - } else { - subscriber.putNext(nil) - } - }, error: { _ in - }, completed: nil)! - - return ActionDisposable { - disposable.dispose() - } - } else { - return EmptyDisposable - } - } - let editedSignal: Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> = editedImageSignal - |> map { image in - if let image = image { - return { arguments in - guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { - return nil - } - let drawingRect = arguments.drawingRect - let imageSize = image.size - let fittedSize = imageSize.aspectFilled(arguments.boundingSize).fitted(imageSize) - let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) - - context.withFlippedContext { c in - c.setBlendMode(.copy) - if let cgImage = image.cgImage { - drawImage(context: c, image: cgImage, orientation: .up, in: fittedRect) - } - } - return context - } - } else { - return nil - } - } - - let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> = editedSignal - |> mapToSignal { result in - if result != nil { - return .single(result!) - } else { - return updateImageSignal - } - } - self.imageNode.setSignal(imageSignal) - } - - self.currentDimensions = imageDimensions - if let _ = imageDimensions { - self.setNeedsLayout() - } - self.updateHiddenMedia() - } - - self.item = item - self.updateSelectionState(animated: false) - } - - func updateSelectionState(animated: Bool) { - if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState { - let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: item.theme, style: .overlay)) - checkNode.valueChanged = { [weak self] value in - guard let self else { - return - } - if !item.controllerInteraction.toggleSelection(item.result, value) { - self.checkNode?.setSelected(false, animated: false) - } - } - self.addSubnode(checkNode) - self.checkNode = checkNode - self.setNeedsLayout() - } - - if let item = self.item { - if let selectionState = item.controllerInteraction.selectionState { - let selected = selectionState.isIdentifierSelected(item.result.id) - self.checkNode?.setSelected(selected, animated: animated) - } - } - } - - func updateHiddenMedia() { - if let item = self.item { - let wasHidden = self.isHidden - self.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id - if !self.isHidden && wasHidden { - self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } - - func transitionView() -> UIView { - let view = self.imageNode.view.snapshotContentTree(unhide: true, keepTransform: true)! - view.frame = self.convert(self.bounds, to: nil) - return view - } - - override func layout() { - super.layout() - - let imageFrame = self.bounds - self.imageNode.frame = imageFrame - - if let item = self.item, let dimensions = self.currentDimensions { - let imageSize = dimensions.aspectFilled(imageFrame.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))() - } - - let checkSize = CGSize(width: 28.0, height: 28.0) - self.checkNode?.frame = CGRect(origin: CGPoint(x: imageFrame.width - checkSize.width - 2.0, y: 2.0), size: checkSize) - } - - @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { - guard let item = self.item else { - return - } - - switch recognizer.state { - case .ended: - if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { - switch gesture { - case .tap: - item.controllerInteraction.openResult(item.result) - default: - break - } - } - default: - break - } - } -} - diff --git a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift b/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift deleted file mode 100644 index f7af067849..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchNavigationContentNode.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import TelegramPresentationData -import SearchBarNode - -private let searchBarFont = Font.regular(17.0) - -final class WebSearchNavigationContentNode: NavigationBarContentNode { - private let theme: PresentationTheme - private let strings: PresentationStrings - - private let searchBar: SearchBarNode - - private var queryUpdated: ((String) -> Void)? - - var cancel: (() -> Void)? - - init(theme: PresentationTheme, strings: PresentationStrings, attachment: Bool) { - self.theme = theme - self.strings = strings - - self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern, displayBackground: !attachment) - self.searchBar.hasCancelButton = attachment - self.searchBar.placeholderString = NSAttributedString(string: attachment ? strings.Attachment_SearchWeb : strings.Common_Search, font: searchBarFont, textColor: theme.rootController.navigationSearchBar.inputPlaceholderTextColor) - - super.init() - - self.addSubnode(self.searchBar) - - self.searchBar.textUpdated = { [weak self] query, _ in - self?.queryUpdated?(query) - } - self.searchBar.cancel = { [weak self] in - self?.cancel?() - } - } - - func setQueryUpdated(_ f: @escaping (String) -> Void) { - self.queryUpdated = f - } - - func setActivity(_ activity: Bool) { - self.searchBar.activity = activity - } - - func setQuery(_ query: String) { - self.searchBar.text = query - } - - override var nominalHeight: CGFloat { - return 56.0 - } - - override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize { - let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 56.0)) - self.searchBar.frame = searchBarFrame - self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) - - return size - } - - func activate(select: Bool = false) { - self.searchBar.activate() - self.searchBar.selectAll() - } - - func deactivate() { - self.searchBar.deactivate(clear: false) - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchRecentQueries.swift b/submodules/WebSearchUI/Sources/WebSearchRecentQueries.swift deleted file mode 100644 index 22f85a37c9..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchRecentQueries.swift +++ /dev/null @@ -1,69 +0,0 @@ -import Foundation -import UIKit -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramUIPreferences - -private struct WebSearchRecentQueryItemId { - public let rawValue: MemoryBuffer - - var value: String { - return String(data: self.rawValue.makeData(), encoding: .utf8) ?? "" - } - - init(_ rawValue: MemoryBuffer) { - self.rawValue = rawValue - } - - init?(_ value: String) { - if let data = value.data(using: .utf8) { - self.rawValue = MemoryBuffer(data: data) - } else { - return nil - } - } -} - -public final class RecentWebSearchQueryItem: Codable { - init() { - } - - public init(from decoder: Decoder) throws { - } - - public func encode(to encoder: Encoder) throws { - } -} - -func addRecentWebSearchQuery(engine: TelegramEngine, string: String) -> Signal { - if let itemId = WebSearchRecentQueryItemId(string) { - return engine.orderedLists.addOrMoveToFirstPosition(collectionId: ApplicationSpecificOrderedItemListCollectionId.webSearchRecentQueries, id: itemId.rawValue, item: RecentWebSearchQueryItem(), removeTailIfCountExceeds: 100) - } else { - return .complete() - } -} - -func removeRecentWebSearchQuery(engine: TelegramEngine, string: String) -> Signal { - if let itemId = WebSearchRecentQueryItemId(string) { - return engine.orderedLists.removeItem(collectionId: ApplicationSpecificOrderedItemListCollectionId.webSearchRecentQueries, id: itemId.rawValue) - } else { - return .complete() - } -} - -func clearRecentWebSearchQueries(engine: TelegramEngine) -> Signal { - return engine.orderedLists.clear(collectionId: ApplicationSpecificOrderedItemListCollectionId.webSearchRecentQueries) -} - -func webSearchRecentQueries(engine: TelegramEngine) -> Signal<[String], NoError> { - return engine.data.subscribe(TelegramEngine.EngineData.Item.OrderedLists.ListItems(collectionId: ApplicationSpecificOrderedItemListCollectionId.webSearchRecentQueries)) - |> map { items -> [String] in - var result: [String] = [] - for item in items { - let value = WebSearchRecentQueryItemId(item.id).value - result.append(value) - } - return result - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift b/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift deleted file mode 100644 index a6f0d79fac..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchRecentQueryItem.swift +++ /dev/null @@ -1,233 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData -import ItemListUI -import PresentationDataUtils - -private enum RevealOptionKey: Int32 { - case delete -} - -public class WebSearchRecentQueryItem: ListViewItem { - let theme: PresentationTheme - let strings: PresentationStrings - let account: Account - let query: String - let tapped: (String) -> Void - let deleted: (String) -> Void - - let header: ListViewItemHeader? - - public init(account: Account, theme: PresentationTheme, strings: PresentationStrings, query: String, tapped: @escaping (String) -> Void, deleted: @escaping (String) -> Void, header: ListViewItemHeader) { - self.theme = theme - self.strings = strings - self.account = account - self.query = query - self.tapped = tapped - self.deleted = deleted - self.header = header - } - - public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { - async { - let node = WebSearchRecentQueryItemNode() - let makeLayout = node.asyncLayout() - let (nodeLayout, nodeApply) = makeLayout(self, params, nextItem == nil, !(previousItem is WebSearchRecentQueryItem)) - node.contentSize = nodeLayout.contentSize - node.insets = nodeLayout.insets - - completion(node, nodeApply) - } - } - - public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { - Queue.mainQueue().async { - if let nodeValue = node() as? WebSearchRecentQueryItemNode { - let layout = nodeValue.asyncLayout() - async { - let (nodeLayout, apply) = layout(self, params, nextItem == nil, !(previousItem is WebSearchRecentQueryItem)) - Queue.mainQueue().async { - completion(nodeLayout, { info in - apply().1(info) - }) - } - } - } - } - } - - public var selectable: Bool { - return true - } - - public func selected(listView: ListView) { - listView.clearHighlightAnimated(true) - self.tapped(self.query) - } -} - -class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode { - private let backgroundNode: ASDisplayNode - private let separatorNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private var textNode: TextNode? - - private var item: WebSearchRecentQueryItem? - private var layoutParams: ListViewItemLayoutParams? - - required init() { - self.backgroundNode = ASDisplayNode() - self.backgroundNode.isLayerBacked = true - - self.separatorNode = ASDisplayNode() - self.separatorNode.isLayerBacked = true - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.isLayerBacked = true - - super.init(layerBacked: false, rotated: false, seeThrough: false) - - self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) - } - - override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { - if let item = self.item { - let makeLayout = self.asyncLayout() - let (nodeLayout, nodeApply) = makeLayout(item, params, nextItem == nil, previousItem == nil) - self.contentSize = nodeLayout.contentSize - self.insets = nodeLayout.insets - let _ = nodeApply() - } - } - - override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { - super.setHighlighted(highlighted, at: point, animated: animated) - - if highlighted { - self.highlightedBackgroundNode.alpha = 1.0 - if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) - } - } else { - if self.highlightedBackgroundNode.supernode != nil { - if animated { - self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in - if let strongSelf = self { - if completed { - strongSelf.highlightedBackgroundNode.removeFromSupernode() - } - } - }) - self.highlightedBackgroundNode.alpha = 0.0 - } else { - self.highlightedBackgroundNode.removeFromSupernode() - } - } - } - } - - func asyncLayout() -> (_ item: WebSearchRecentQueryItem, _ params: ListViewItemLayoutParams, _ last: Bool, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, () -> (Signal?, (ListViewItemApply) -> Void)) { - let currentItem = self.item - - let textLayout = TextNode.asyncLayout(self.textNode) - - return { [weak self] item, params, last, firstWithHeader in - - let leftInset: CGFloat = 15.0 + params.leftInset - let rightInset: CGFloat = params.rightInset - - let attributedString = NSAttributedString(string: item.query, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor) - let textApply = textLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - - let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)) - - return (nodeLayout, { [weak self] in - var updatedTheme: PresentationTheme? - if currentItem?.theme !== item.theme { - updatedTheme = item.theme - } - - return (nil, { _ in - if let strongSelf = self { - strongSelf.item = item - strongSelf.layoutParams = params - - if let _ = updatedTheme { - strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor - strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor - } - - let (textLayout, textApply) = textApply - let textNode = textApply() - if strongSelf.textNode == nil { - strongSelf.textNode = textNode - strongSelf.addSubnode(textNode) - } - - let textFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((44.0 - textLayout.size.height) / 2.0)), size: textLayout.size) - textNode.frame = textFrame - - let separatorHeight = UIScreenPixel - let topHighlightInset: CGFloat = (firstWithHeader || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight - - strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) - strongSelf.separatorNode.isHidden = last - - strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) - } - }) - }) - } - } - - override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5) - } - - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false) - } - - override public func headers() -> [ListViewItemHeader]? { - if let item = self.item { - return item.header.flatMap { [$0] } - } else { - return nil - } - } - - override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { - super.updateRevealOffset(offset: offset, transition: transition) - - if let params = self.layoutParams, let textNode = self.textNode { - let leftInset: CGFloat = 15.0 + params.leftInset - - var textFrame = textNode.frame - textFrame.origin.x = leftInset + offset - transition.updateFrame(node: textNode, frame: textFrame) - } - } - - override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { - if let item = self.item { - switch option.key { - case RevealOptionKey.delete.rawValue: - item.deleted(item.query) - default: - break - } - } - self.setRevealOptionsOpened(false, animated: true) - self.revealOptionsInteractivelyClosed() - } -} diff --git a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift b/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift deleted file mode 100644 index 676cce6782..0000000000 --- a/submodules/WebSearchUI/Sources/WebSearchVideoGalleryItem.swift +++ /dev/null @@ -1,548 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import SwiftSignalKit -import TelegramCore -import Display -import TelegramPresentationData -import AccountContext -import RadialStatusNode -import GalleryUI -import TelegramUniversalVideoContent -import GalleryUI - -class WebSearchVideoGalleryItem: GalleryItem { - var id: AnyHashable { - return self.index - } - - let index: Int - - let context: AccountContext - let presentationData: PresentationData - let result: ChatContextResult - let content: UniversalVideoContent - let controllerInteraction: WebSearchGalleryControllerInteraction? - - init(context: AccountContext, presentationData: PresentationData, index: Int, result: ChatContextResult, content: UniversalVideoContent, controllerInteraction: WebSearchGalleryControllerInteraction?) { - self.context = context - self.presentationData = presentationData - self.index = index - self.result = result - self.content = content - self.controllerInteraction = controllerInteraction - } - - func node(synchronous: Bool) -> GalleryItemNode { - let node = WebSearchVideoGalleryItemNode(context: self.context, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction) - node.setupItem(self) - return node - } - - func updateNode(node: GalleryItemNode, synchronous: Bool) { - if let node = node as? WebSearchVideoGalleryItemNode { - node.setupItem(self) - } - } - - func thumbnailItem() -> (Int64, GalleryThumbnailItem)? { - return nil - } -} - -private struct FetchControls { - let fetch: () -> Void - let cancel: () -> Void -} - -final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode { - private let context: AccountContext - private let strings: PresentationStrings - private let controllerInteraction: WebSearchGalleryControllerInteraction? - - fileprivate let _ready = Promise() - - private let footerContentNode: WebSearchGalleryFooterContentNode - - private var videoNode: UniversalVideoNode? - private let statusButtonNode: HighlightableButtonNode - private let statusNode: RadialStatusNode - - private var isCentral = false - private var validLayout: (ContainerViewLayout, CGFloat)? - private var didPause = false - private var isPaused = true - - private var requiresDownload = false - - var item: WebSearchVideoGalleryItem? - - private let statusDisposable = MetaDisposable() - - private let fetchDisposable = MetaDisposable() - private var fetchStatus: EngineMediaResource.FetchStatus? - private var fetchControls: FetchControls? - - var playbackCompleted: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, controllerInteraction: WebSearchGalleryControllerInteraction?) { - self.context = context - self.strings = presentationData.strings - self.controllerInteraction = controllerInteraction - - self.footerContentNode = WebSearchGalleryFooterContentNode(context: context, presentationData: presentationData) - - self.statusButtonNode = HighlightableButtonNode() - self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5)) - - super.init() - - self.statusButtonNode.addSubnode(self.statusNode) - self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside) - - self.addSubnode(self.statusButtonNode) - - self.footerContentNode.cancel = { - controllerInteraction?.dismiss(true) - } - self.footerContentNode.send = { [weak self] in - if let strongSelf = self, let item = strongSelf.item { - controllerInteraction?.send(item.result) - } - } - } - - deinit { - self.statusDisposable.dispose() - } - - @objc override func contentTap(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { - if recognizer.state == .ended { - if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { - switch gesture { - case .tap: - if let item = self.item, let selectionState = item.controllerInteraction?.selectionState { - let legacyItem = legacyWebSearchItem(account: item.context.account, result: item.result) - selectionState.toggleItemSelection(legacyItem, success: nil) - } - case .doubleTap: - super.contentTap(recognizer) - default: - break - } - } - } - } - - override func ready() -> Signal { - return self._ready.get() - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - - self.validLayout = (layout, navigationBarHeight) - - let statusDiameter: CGFloat = 50.0 - let statusFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusDiameter) / 2.0), y: floor((layout.size.height - statusDiameter) / 2.0)), size: CGSize(width: statusDiameter, height: statusDiameter)) - transition.updateFrame(node: self.statusButtonNode, frame: statusFrame) - transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusFrame.size)) - } - - func setupItem(_ item: WebSearchVideoGalleryItem) { - if self.item?.content.id != item.content.id { - var isAnimated = false - var mediaResource: EngineMediaResource? - if let content = item.content as? NativeVideoContent { - isAnimated = content.fileReference.media.isAnimated - mediaResource = EngineMediaResource(content.fileReference.media.resource) - } - - if let videoNode = self.videoNode { - videoNode.canAttachContent = false - videoNode.removeFromSupernode() - } - - let mediaManager = item.context.sharedContext.mediaManager - - let videoNode = UniversalVideoNode(context: item.context, postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery) - let videoSize = CGSize(width: item.content.dimensions.width * 2.0, height: item.content.dimensions.height * 2.0) - videoNode.updateLayout(size: videoSize, transition: .immediate) - self.videoNode = videoNode - videoNode.isUserInteractionEnabled = false - videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335) - videoNode.canAttachContent = true - - self.requiresDownload = true - var mediaFileStatus: Signal = .single(nil) - if let mediaResource = mediaResource { - mediaFileStatus = item.context.account.postbox.mediaBox.resourceStatus(mediaResource._asResource()) - |> map { status in - return EngineMediaResource.FetchStatus(status) - } - |> map(Optional.init) - } - - self.statusDisposable.set((combineLatest(videoNode.status, mediaFileStatus) - |> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in - if let strongSelf = self { - var initialBuffering = false - var isPaused = true - if let value = value { - if let zoomableContent = strongSelf.zoomableContent, !value.dimensions.width.isZero && !value.dimensions.height.isZero { - let videoSize = CGSize(width: value.dimensions.width * 2.0, height: value.dimensions.height * 2.0) - if !zoomableContent.0.equalTo(videoSize) { - strongSelf.zoomableContent = (videoSize, zoomableContent.1) - strongSelf.videoNode?.updateLayout(size: videoSize, transition: .immediate) - } - } - switch value.status { - case .playing: - isPaused = false - case let .buffering(_, whilePlaying, _, _): - initialBuffering = true - isPaused = !whilePlaying - var isStreaming = false - if let fetchStatus = strongSelf.fetchStatus { - switch fetchStatus { - case .Local: - break - default: - isStreaming = true - } - } - if let content = item.content as? NativeVideoContent, !isStreaming { - initialBuffering = false - if !content.enableSound { - isPaused = false - } - } - default: - if let content = item.content as? NativeVideoContent, !content.streamVideo.enabled { - if !content.enableSound { - isPaused = false - } - } - } - } - - var fetching = false - if initialBuffering { - strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {}) - } else { - var state: RadialStatusNodeState = .none - - if let fetchStatus = fetchStatus { - if strongSelf.requiresDownload { - switch fetchStatus { - case let .Fetching(_, progress): - fetching = true - isPaused = true - state = .progress(color: .white, lineWidth: nil, value: CGFloat(max(0.027, progress)), cancelEnabled: false, animateRotation: true) - default: - break - } - } - } - strongSelf.statusNode.transitionToState(state, animated: false, completion: {}) - } - - strongSelf.isPaused = isPaused - strongSelf.fetchStatus = fetchStatus - - strongSelf.statusButtonNode.isHidden = !initialBuffering && !isPaused && !fetching - } - })) - - self.zoomableContent = (videoSize, videoNode) - - videoNode.playbackCompleted = { [weak videoNode] in - Queue.mainQueue().async { - if !isAnimated { - videoNode?.seek(0.0) - } - } - } - - self._ready.set(videoNode.ready) - } - - self.item = item - } - - override func centralityUpdated(isCentral: Bool) { - super.centralityUpdated(isCentral: isCentral) - - if self.isCentral != isCentral { - self.isCentral = isCentral - - if let videoNode = self.videoNode, videoNode.ownsContentNode { - if isCentral { - videoNode.play() - } else { - videoNode.pause() - } - } - } - } - - override func activateAsInitial() { - if self.isCentral { - self.videoNode?.play() - } - } - - override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { - guard let videoNode = self.videoNode else { - return - } - - if let node = node.0 as? OverlayMediaItemNode { - var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) - let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) - - videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - - transformedFrame.origin = CGPoint() - - let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) - videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) - - self.context.sharedContext.mediaManager.setOverlayVideoNode(nil) - } else { - var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view) - let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview) - let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view) - let transformedCopyViewFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) - - let surfaceCopyView = node.2().0! - let copyView = node.2().0! - - addToTransitionSurface(surfaceCopyView) - - var transformedSurfaceFrame: CGRect? - var transformedSurfaceFinalFrame: CGRect? - if let contentSurface = surfaceCopyView.superview { - transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface) - transformedSurfaceFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface) - } - - if let transformedSurfaceFrame = transformedSurfaceFrame { - surfaceCopyView.frame = transformedSurfaceFrame - } - - self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) - copyView.frame = transformedSelfFrame - - copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false) - - surfaceCopyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - - copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in - copyView?.removeFromSuperview() - }) - let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height) - copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) - - if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame { - surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in - surfaceCopyView?.removeFromSuperview() - }) - let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height) - surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) - } - - videoNode.allowsGroupOpacity = true - videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in - videoNode?.allowsGroupOpacity = false - }) - videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - - transformedFrame.origin = CGPoint() - - let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) - videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) - - self.statusButtonNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusButtonNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - self.statusButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - self.statusButtonNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - } - } - - override func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) { - guard let videoNode = self.videoNode else { - completion() - return - } - - var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view) - let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview) - let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view) - let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) - - var positionCompleted = false - var boundsCompleted = false - var copyCompleted = false - - let copyView = node.2().0! - let surfaceCopyView = node.2().0! - - addToTransitionSurface(surfaceCopyView) - - var transformedSurfaceFrame: CGRect? - var transformedSurfaceCopyViewInitialFrame: CGRect? - if let contentSurface = surfaceCopyView.superview { - transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface) - transformedSurfaceCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface) - } - - self.view.insertSubview(copyView, belowSubview: self.scrollNode.view) - copyView.frame = transformedSelfFrame - - let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in - if positionCompleted && boundsCompleted && copyCompleted { - copyView?.removeFromSuperview() - surfaceCopyView?.removeFromSuperview() - completion() - } - } - - copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false) - surfaceCopyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) - - copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height) - copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - copyCompleted = true - intermediateCompletion() - }) - - if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedCopyViewInitialFrame = transformedSurfaceCopyViewInitialFrame { - surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSurfaceFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSurfaceFrame.size.height) - surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false) - } - - videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - positionCompleted = true - intermediateCompletion() - }) - - videoNode.allowsGroupOpacity = true - videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in - videoNode?.allowsGroupOpacity = false - }) - - self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - //positionCompleted = true - //intermediateCompletion() - }) - self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) - - transformedFrame.origin = CGPoint() - - let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) - videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - boundsCompleted = true - intermediateCompletion() - }) - } - - func animateOut(toOverlay node: ASDisplayNode, completion: @escaping () -> Void) { - guard let videoNode = self.videoNode else { - completion() - return - } - - var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view) - let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview) - let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view) - let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view) - let transformedSelfTargetSuperFrame = videoNode.view.convert(videoNode.view.bounds, to: node.view.superview) - - var positionCompleted = false - var boundsCompleted = false - var copyCompleted = false - var nodeCompleted = false - - let copyView = node.view.snapshotContentTree()! - - videoNode.isHidden = true - copyView.frame = transformedSelfFrame - - let intermediateCompletion = { [weak copyView] in - if positionCompleted && boundsCompleted && copyCompleted && nodeCompleted { - copyView?.removeFromSuperview() - completion() - } - } - - copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false) - - copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height) - copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - copyCompleted = true - intermediateCompletion() - }) - - videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - positionCompleted = true - intermediateCompletion() - }) - - videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - - self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - //positionCompleted = true - //intermediateCompletion() - }) - self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) - - transformedFrame.origin = CGPoint() - - let videoTransform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0) - videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: videoTransform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - boundsCompleted = true - intermediateCompletion() - }) - - let nodeTransform = CATransform3DScale(node.layer.transform, videoNode.layer.bounds.size.width / transformedFrame.size.width, videoNode.layer.bounds.size.height / transformedFrame.size.height, 1.0) - node.layer.animatePosition(from: CGPoint(x: transformedSelfTargetSuperFrame.midX, y: transformedSelfTargetSuperFrame.midY), to: node.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring) - node.layer.animate(from: NSValue(caTransform3D: nodeTransform), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in - nodeCompleted = true - intermediateCompletion() - }) - } - - @objc func statusButtonPressed() { - if let videoNode = self.videoNode { - if let fetchStatus = self.fetchStatus, case .Local = fetchStatus { - self.toggleControlsVisibility() - } - - if let fetchStatus = self.fetchStatus { - switch fetchStatus { - case .Local: - videoNode.togglePlayPause() - case .Remote, .Paused: - if self.requiresDownload { - self.fetchControls?.fetch() - } else { - videoNode.togglePlayPause() - } - case .Fetching: - self.fetchControls?.cancel() - } - } else { - videoNode.togglePlayPause() - } - } - } - - override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> { - return .single((self.footerContentNode, nil)) - } -} diff --git a/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift b/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift index 5c96fd4112..ea6b1d2060 100644 --- a/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift +++ b/submodules/WebUI/Sources/WebAppAddToAttachmentController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index a20596acf5..ee39fd2940 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -3,7 +3,6 @@ import UIKit @preconcurrency import WebKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import ComponentFlow @@ -66,8 +65,8 @@ public struct WebAppParameters { } let source: Source - let peerId: PeerId - let botId: PeerId + let peerId: EnginePeer.Id + let botId: EnginePeer.Id let botName: String let botVerified: Bool let botAddress: String @@ -80,12 +79,13 @@ public struct WebAppParameters { let forceHasSettings: Bool let fullSize: Bool let isFullscreen: Bool + let sameOrigin: Bool let appSettings: BotAppSettings? public init( source: Source, - peerId: PeerId, - botId: PeerId, + peerId: EnginePeer.Id, + botId: EnginePeer.Id, botName: String, botVerified: Bool, botAddress: String, @@ -98,6 +98,7 @@ public struct WebAppParameters { forceHasSettings: Bool, fullSize: Bool, isFullscreen: Bool = false, + sameOrigin: Bool = false, appSettings: BotAppSettings? = nil ) { self.source = source @@ -115,6 +116,7 @@ public struct WebAppParameters { self.forceHasSettings = forceHasSettings self.fullSize = fullSize || isFullscreen self.isFullscreen = isFullscreen + self.sameOrigin = sameOrigin self.appSettings = appSettings } } @@ -344,7 +346,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return .single(nil) } |> mapToSignal { bot -> Signal<(FileMediaReference, Bool)?, NoError> in - if let bot = bot, let peerReference = PeerReference(bot.peer._asPeer()) { + if let bot = bot, let peerReference = PeerReference(bot.peer) { var imageFile: TelegramMediaFile? var isPlaceholder = false if let file = bot.icons[.placeholder] { @@ -470,6 +472,10 @@ public final class WebAppController: ViewController, AttachmentContainable { super.didLoad() self.setupWebView() + if let pendingExternalUrl = self.controller?.pendingExternalUrl { + self.controller?.pendingExternalUrl = nil + self.loadExternal(url: pendingExternalUrl) + } guard let webView = self.webView else { return @@ -516,9 +522,22 @@ public final class WebAppController: ViewController, AttachmentContainable { return } #endif*/ + + if self.controller?.sameOrigin == true { + self.webView?.bindTrustedOrigin(from: url) + } else { + self.webView?.setupEventProxySource() + } self.webView?.load(URLRequest(url: url)) } + fileprivate func loadExternal(url: String) { + guard let parsedUrl = URL(string: url) else { + return + } + self.load(url: parsedUrl) + } + func setupWebView() { guard let controller = self.controller else { return @@ -550,6 +569,7 @@ public final class WebAppController: ViewController, AttachmentContainable { } if let parsedUrl = URL(string: result.url) { strongSelf.queryId = result.queryId + strongSelf.controller?.sameOrigin = result.flags.contains(.sameOrigin) strongSelf.load(url: parsedUrl) } }) @@ -570,6 +590,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return } self.controller?.titleView?.title = WebAppTitle(title: botApp.title, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: controller.botVerified) + self.controller?.sameOrigin = result.flags.contains(.sameOrigin) self.load(url: parsedUrl) }) }) @@ -580,6 +601,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return } strongSelf.queryId = result.queryId + strongSelf.controller?.sameOrigin = result.flags.contains(.sameOrigin) strongSelf.load(url: parsedUrl) if let keepAliveSignal = result.keepAliveSignal { @@ -678,7 +700,7 @@ public final class WebAppController: ViewController, AttachmentContainable { @available(iOS 14.5, *) func downloadDidFinish(_ download: WKDownload) { if let (path, fileName) = self.downloadArguments { - let tempFile = TempBox.shared.file(path: path, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: path, fileName: fileName) let url = URL(fileURLWithPath: tempFile.path) if fileName.hasSuffix(".pkpass") { @@ -1092,7 +1114,7 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let controller = self.controller else { return } - guard message.frameInfo.isMainFrame else { + guard self.webView?.isTrustedMainFrameMessage(message) == true else { return } guard let body = message.body as? [String: Any] else { @@ -1240,8 +1262,9 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_open_tg_link": if let json = json, let path = json["path_full"] as? String { let forceRequest = json["force_request"] as? Bool ?? false - controller.openUrl("https://t.me\(path)", false, forceRequest, { - }) + if let url = makeWebAppTelegramLink(pathFull: path) { + controller.openUrl(url, false, forceRequest, {}) + } } case "web_app_open_invoice": if let json = json, let slug = json["slug"] as? String { @@ -1600,7 +1623,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controller?._isPanGestureEnabled = isPanGestureEnabled } case "web_app_share_to_story": - if let json, let mediaUrl = json["media_url"] as? String { + if let json, let mediaUrl = json["media_url"] as? String, isAllowedBotMediaUrl(mediaUrl) { let text = json["text"] as? String let link = json["widget_link"] as? [String: Any] @@ -1650,7 +1673,7 @@ public final class WebAppController: ViewController, AttachmentContainable { if let image = UIImage(data: data) { source = image } else { - let tempFile = TempBox.shared.tempFile(fileName: "image.mp4") + let tempFile = EngineTempBox.shared.tempFile(fileName: "image.mp4") if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) { source = tempFile.path } @@ -3047,7 +3070,7 @@ public final class WebAppController: ViewController, AttachmentContainable { tooltip.dismissWithCommitAction() } - let tempFile = TempBox.shared.file(path: resultUrl.absoluteString, fileName: fileName) + let tempFile = EngineTempBox.shared.file(path: resultUrl.absoluteString, fileName: fileName) let url = URL(fileURLWithPath: tempFile.path) try? FileManager.default.copyItem(at: resultUrl, to: url) @@ -3372,7 +3395,7 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let self, let controller = self.controller, let peer else { return } - if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { controller.parentController()?.push(infoController) } }) @@ -3531,8 +3554,8 @@ public final class WebAppController: ViewController, AttachmentContainable { private let context: AccountContext public let source: WebAppParameters.Source - private let peerId: PeerId - public let botId: PeerId + private let peerId: EnginePeer.Id + public let botId: EnginePeer.Id fileprivate let botName: String fileprivate let botVerified: Bool fileprivate let botAppSettings: BotAppSettings? @@ -3544,9 +3567,11 @@ public final class WebAppController: ViewController, AttachmentContainable { private let buttonText: String? private let forceHasSettings: Bool private let keepAliveSignal: Signal? - private let replyToMessageId: MessageId? + private let replyToMessageId: EngineMessage.Id? private let threadId: Int64? public var isFullscreen: Bool + private var sameOrigin: Bool + private var pendingExternalUrl: String? private var presentationData: PresentationData fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal)? @@ -3561,7 +3586,7 @@ public final class WebAppController: ViewController, AttachmentContainable { public var verifyAgeCompletion: ((Int) -> Void)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: MessageId?, threadId: Int64?) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, params: WebAppParameters, replyToMessageId: EngineMessage.Id?, threadId: Int64?) { self.context = context self.source = params.source self.peerId = params.peerId @@ -3580,6 +3605,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.replyToMessageId = replyToMessageId self.threadId = threadId self.isFullscreen = params.isFullscreen + self.sameOrigin = params.sameOrigin self.updatedPresentationData = updatedPresentationData @@ -3601,6 +3627,8 @@ public final class WebAppController: ViewController, AttachmentContainable { if case .attachMenu = self.source { + } else if self.isVerifyAgeBot { + } else { self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) @@ -3678,15 +3706,21 @@ public final class WebAppController: ViewController, AttachmentContainable { } private func updateNavigationButtons() { + var showGlassButtons = false if case .attachMenu = self.source { + showGlassButtons = true + } else if self.isVerifyAgeBot { + showGlassButtons = true + } + if showGlassButtons { let barButtonSize = CGSize(width: 44.0, height: 44.0) let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( id: "close", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: self.controllerNode.hasBackButton ? "back" : "close", component: AnyComponent( BundleIconComponent( name: self.controllerNode.hasBackButton ? "Navigation/Back" : "Navigation/Close", @@ -3703,9 +3737,9 @@ public final class WebAppController: ViewController, AttachmentContainable { id: "more", component: AnyComponent(GlassBarButtonComponent( size: barButtonSize, - backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + backgroundColor: nil, isDark: self.presentationData.theme.overallDarkAppearance, - state: .generic, + state: .glass, component: AnyComponentWithIdentity(id: "more", component: AnyComponent( LottieComponent( content: LottieComponent.AppBundleContent( @@ -3733,14 +3767,16 @@ public final class WebAppController: ViewController, AttachmentContainable { self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: cancelButtonNode) } - let moreButtonNode: BarComponentHostNode - if let current = self.moreBarButtonNode { - moreButtonNode = current - moreButtonNode.component = moreComponent - } else { - moreButtonNode = BarComponentHostNode(component: moreComponent, size: barButtonSize) - self.moreBarButtonNode = moreButtonNode - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: moreButtonNode) + if !self.isVerifyAgeBot { + let moreButtonNode: BarComponentHostNode + if let current = self.moreBarButtonNode { + moreButtonNode = current + moreButtonNode.component = moreComponent + } else { + moreButtonNode = BarComponentHostNode(component: moreComponent, size: barButtonSize) + self.moreBarButtonNode = moreButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: moreButtonNode) + } } } @@ -3749,7 +3785,9 @@ public final class WebAppController: ViewController, AttachmentContainable { private var isVerifyAgeBot: Bool { if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { - return self.botAddress == ageBotUsername + if self.botAddress == ageBotUsername { + return true + } } return false } @@ -3801,6 +3839,7 @@ public final class WebAppController: ViewController, AttachmentContainable { badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: self.presentationData.theme.list.itemCheckColors.fillColor, + accentDisabledButtonColor: self.presentationData.theme.chat.inputPanel.panelControlDisabledColor, accentForegroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor ), strings: NavigationBarStrings(back: "", close: "") @@ -4053,6 +4092,14 @@ public final class WebAppController: ViewController, AttachmentContainable { self.updateTabBarAlpha(1.0, .immediate) } + public func loadExternal(url: String) { + if self.isNodeLoaded { + self.controllerNode.loadExternal(url: url) + } else { + self.pendingExternalUrl = url + } + } + public func isContainerPanningUpdated(_ isPanning: Bool) { self.controllerNode.isContainerPanningUpdated(isPanning) } @@ -4096,23 +4143,18 @@ public final class WebAppController: ViewController, AttachmentContainable { public func requestDismiss(completion: @escaping () -> Void) { if self.controllerNode.needDismissConfirmation { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: self.presentationData.strings.WebApp_CloseConfirmation), - ActionSheetButtonItem(title: self.presentationData.strings.WebApp_CloseAnyway, color: .destructive, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - + let alertController = textAlertController( + context: self.context, + title: nil, + text: self.presentationData.strings.WebApp_CloseConfirmation, + actions: [ + TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), + TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.WebApp_CloseAnyway, action: { completion() }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - }) - ]) - ]) - self.present(actionSheet, in: .window(.root)) + ] + ) + self.present(alertController, in: .window(.root)) } else { completion() } @@ -4247,7 +4289,8 @@ public func standaloneWebAppController( didDismiss: @escaping () -> Void = {}, getNavigationController: @escaping () -> NavigationController? = { return nil }, getSourceRect: (() -> CGRect?)? = nil, - verifyAgeCompletion: ((Int) -> Void)? = nil + verifyAgeCompletion: ((Int) -> Void)? = nil, + onControllerCreated: @escaping (WebAppController) -> Void = { _ in } ) -> ViewController { let controller = AttachmentController( context: context, @@ -4268,6 +4311,7 @@ public func standaloneWebAppController( webAppController.getNavigationController = getNavigationController webAppController.requestSwitchInline = requestSwitchInline webAppController.verifyAgeCompletion = verifyAgeCompletion + onControllerCreated(webAppController) present(webAppController, webAppController.mediaPickerContext) return true } diff --git a/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift b/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift index ea8dc4ed73..6d64d0f270 100644 --- a/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift +++ b/submodules/WebUI/Sources/WebAppEmojiStatusAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import ComponentFlow import TelegramCore import TelegramPresentationData diff --git a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift index 2fa118db47..5233814842 100644 --- a/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift +++ b/submodules/WebUI/Sources/WebAppLaunchConfirmationController.swift @@ -4,7 +4,6 @@ import SwiftSignalKit import AsyncDisplayKit import Display import ComponentFlow -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/WebUI/Sources/WebAppLocationAlertController.swift b/submodules/WebUI/Sources/WebAppLocationAlertController.swift index 1fe62d6073..e1be2cf404 100644 --- a/submodules/WebUI/Sources/WebAppLocationAlertController.swift +++ b/submodules/WebUI/Sources/WebAppLocationAlertController.swift @@ -4,7 +4,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift index 69c0a2d14e..c7dac9573c 100644 --- a/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift +++ b/submodules/WebUI/Sources/WebAppMessageChatPreviewItem.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI @@ -20,9 +19,20 @@ final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemCo if lhs.text != rhs.text { return false } - if areMediaArraysEqual(lhs.media, rhs.media) { + if lhs.entities != rhs.entities { return false } + if lhs.richText != rhs.richText { + return false + } + if lhs.media.count != rhs.media.count { + return false + } + for i in 0 ..< lhs.media.count { + if !lhs.media[i].isEqual(to: rhs.media[i]) { + return false + } + } if lhs.botAddress != rhs.botAddress { return false } @@ -31,7 +41,8 @@ final class PeerNameColorChatPreviewItem: ListViewItem, ItemListItem, ListItemCo let text: String let entities: TextEntitiesMessageAttribute? - let media: [Media] + let richText: RichTextMessageAttribute? + let media: [EngineRawMedia] let replyMarkup: ReplyMarkupMessageAttribute? let botAddress: String } @@ -189,27 +200,30 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode { var items: [ListViewItem] = [] for messageItem in item.messageItems.reversed() { - let authorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(0)) - let botPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - - var peers = SimpleDictionary() - let messages = SimpleDictionary() + let authorPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(0)) + let botPeerId = EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)) + + var peers = EngineSimpleDictionary() + let messages = EngineSimpleDictionary() peers[authorPeerId] = TelegramUser(id: authorPeerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) peers[botPeerId] = TelegramUser(id: botPeerId, accessHash: nil, firstName: messageItem.botAddress, lastName: "", username: messageItem.botAddress, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil) let media = messageItem.media - var attributes: [MessageAttribute] = [] + var attributes: [EngineMessage.Attribute] = [] if let entities = messageItem.entities { attributes.append(entities) } + if let richText = messageItem.richText { + attributes.append(richText) + } if let replyMarkup = messageItem.replyMarkup { attributes.append(replyMarkup) } attributes.append(InlineBotMessageAttribute(peerId: botPeerId, title: nil)) - let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: authorPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message = EngineRawMessage(stableId: 1, stableVersion: 0, id: EngineMessage.Id(peerId: authorPeerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: messageItem.text, attributes: attributes, media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false, rank: nil, rankRole: nil)) } diff --git a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift index 5af46a886f..9362bf658f 100644 --- a/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift +++ b/submodules/WebUI/Sources/WebAppMessagePreviewScreen.swift @@ -4,7 +4,6 @@ import AsyncDisplayKit import Display import ComponentFlow import SwiftSignalKit -import Postbox import TelegramCore import Markdown import TextFormat @@ -138,7 +137,8 @@ private final class SheetContent: CombinedComponent { var text: String = "" var entities: TextEntitiesMessageAttribute? - var media: [Media] = [] + var richText: RichTextMessageAttribute? + var media: [EngineRawMedia] = [] var replyMarkup: ReplyMarkupMessageAttribute? switch component.preparedMessage.result { @@ -153,8 +153,9 @@ private final class SheetContent: CombinedComponent { media = [image] } replyMarkup = replyMarkupValue - case let .text(textValue, entitiesValue, disableUrlPreview, previewParameters, replyMarkupValue): + case let .text(textValue, entitiesValue, richTextValue, disableUrlPreview, previewParameters, replyMarkupValue): text = textValue + richText = richTextValue entities = entitiesValue let _ = disableUrlPreview let _ = previewParameters @@ -168,8 +169,10 @@ private final class SheetContent: CombinedComponent { case let .invoice(invoice, replyMarkupValue): media = [invoice] replyMarkup = replyMarkupValue - default: - break + case let .webpage(textValue, entitiesValue, _, _, replyMarkupValue): + text = textValue + entities = entitiesValue + replyMarkup = replyMarkupValue } case let .externalReference(reference): switch reference.message { @@ -180,9 +183,10 @@ private final class SheetContent: CombinedComponent { media = [content] } replyMarkup = replyMarkupValue - case let .text(textValue, entitiesValue, disableUrlPreview, previewParameters, replyMarkupValue): + case let .text(textValue, entitiesValue, richTextValue, disableUrlPreview, previewParameters, replyMarkupValue): text = textValue entities = entitiesValue + richText = richTextValue let _ = disableUrlPreview let _ = previewParameters replyMarkup = replyMarkupValue @@ -195,14 +199,17 @@ private final class SheetContent: CombinedComponent { case let .invoice(invoice, replyMarkupValue): media = [invoice] replyMarkup = replyMarkupValue - default: - break + case let .webpage(textValue, entitiesValue, _, _, replyMarkupValue): + text = textValue + entities = entitiesValue + replyMarkup = replyMarkupValue } } let messageItem = PeerNameColorChatPreviewItem.MessageItem( text: text, entities: entities, + richText: richText, media: media, replyMarkup: replyMarkup, botAddress: component.botAddress diff --git a/submodules/WebUI/Sources/WebAppSecureStorageTransferScreen.swift b/submodules/WebUI/Sources/WebAppSecureStorageTransferScreen.swift index 4b71d9be38..2d90cb02a1 100644 --- a/submodules/WebUI/Sources/WebAppSecureStorageTransferScreen.swift +++ b/submodules/WebUI/Sources/WebAppSecureStorageTransferScreen.swift @@ -17,6 +17,7 @@ import ListSectionComponent import ListActionItemComponent import AccountContext import AvatarNode +import GlassBarButtonComponent private final class SheetContent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -63,8 +64,7 @@ private final class SheetContent: CombinedComponent { } static var body: Body { - let closeButton = Child(Button.self) - + let closeButton = Child(GlassBarButtonComponent.self) let title = Child(MultilineTextComponent.self) let avatar = Child(AvatarComponent.self) let text = Child(MultilineTextComponent.self) @@ -87,22 +87,31 @@ private final class SheetContent: CombinedComponent { let boldTextFont = Font.semibold(13.0) let textColor = theme.actionSheet.primaryTextColor - var contentSize = CGSize(width: context.availableSize.width, height: 18.0) + var contentSize = CGSize(width: context.availableSize.width, height: 38.0) let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), - action: { [weak component] in - component?.dismiss() + component: GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in + component.dismiss() } ), - availableSize: CGSize(width: 100.0, height: 30.0), + availableSize: CGSize(width: 44.0, height: 44.0), transition: .immediate ) context.add(closeButton - .position(CGPoint(x: environment.safeInsets.left + 16.0 + closeButton.size.width / 2.0, y: 28.0)) + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) - + let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString(string: strings.WebApp_ImportData_Title, font: titleFont, textColor: textColor)), @@ -114,10 +123,10 @@ private final class SheetContent: CombinedComponent { transition: .immediate ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height)) ) contentSize.height += title.size.height - contentSize.height += 24.0 + contentSize.height += 16.0 let avatar = avatar.update( component: AvatarComponent( @@ -185,6 +194,7 @@ private final class SheetContent: CombinedComponent { ) items.append(AnyComponentWithIdentity(id: key.uuid, component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 3.0)), contentInsets: UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0), leftIcon: .check(ListActionItemComponent.LeftIcon.Check(isSelected: key.uuid == state.selectedUuid, isEnabled: true, toggle: nil)), @@ -201,6 +211,7 @@ private final class SheetContent: CombinedComponent { let keys = keys.update( component: ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.WebApp_ImportData_AccountHeader.uppercased(), @@ -221,9 +232,11 @@ private final class SheetContent: CombinedComponent { contentSize.height += keys.size.height contentSize.height += 24.0 + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) @@ -247,7 +260,7 @@ private final class SheetContent: CombinedComponent { } } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0), transition: context.transition ) context.add(button @@ -255,11 +268,8 @@ private final class SheetContent: CombinedComponent { .cornerRadius(10.0) ) contentSize.height += button.size.height - contentSize.height += 7.0 - - let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom - contentSize.height += 5.0 + effectiveBottomInset - + contentSize.height += buttonInsets.bottom + return contentSize } } @@ -325,6 +335,7 @@ private final class SheetContainerComponent: CombinedComponent { }) } )), + style: .glass, backgroundColor: .color(theme.list.blocksBackgroundColor), followContentSizeChanges: true, externalState: sheetExternalState, diff --git a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift index 5550078d1c..0f06104c94 100644 --- a/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift +++ b/submodules/WebUI/Sources/WebAppSetEmojiStatusScreen.swift @@ -222,8 +222,6 @@ private final class SheetContent: CombinedComponent { transition: .immediate ) context.add(button - .clipsToBounds(true) - .cornerRadius(10.0) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height diff --git a/submodules/WebUI/Sources/WebAppTermsAlertController.swift b/submodules/WebUI/Sources/WebAppTermsAlertController.swift index ba305d3bb7..92ccaac0c1 100644 --- a/submodules/WebUI/Sources/WebAppTermsAlertController.swift +++ b/submodules/WebUI/Sources/WebAppTermsAlertController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/WebUI/Sources/WebAppUtils.swift b/submodules/WebUI/Sources/WebAppUtils.swift new file mode 100644 index 0000000000..38ddd92cf6 --- /dev/null +++ b/submodules/WebUI/Sources/WebAppUtils.swift @@ -0,0 +1,144 @@ +import Foundation + +func makeWebAppTelegramLink(pathFull: String) -> String? { + guard pathFull.hasPrefix("/"), !pathFull.hasPrefix("//") else { + return nil + } + if pathFull.rangeOfCharacter(from: .whitespacesAndNewlines) != nil { + return nil + } + if pathFull.unicodeScalars.contains(where: { $0.value < 0x20 || $0.value == 0x7f }) { + return nil + } + if pathFull.contains("#") { + return nil + } + + let urlString = "https://t.me\(pathFull)" + guard let url = URL(string: urlString) else { + return nil + } + guard url.scheme?.lowercased() == "https" else { + return nil + } + guard url.host?.lowercased() == "t.me" else { + return nil + } + guard url.user == nil, url.password == nil, url.fragment == nil else { + return nil + } + return url.absoluteString +} + +func isAllowedBotMediaUrl(_ urlString: String) -> Bool { + guard let escaped = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let url = URL(string: escaped) else { + return false + } + guard url.scheme?.lowercased() == "https" else { + return false + } + if url.user != nil || url.password != nil { + return false + } + guard var host = url.host?.lowercased(), !host.isEmpty else { + return false + } + if host.hasPrefix("[") && host.hasSuffix("]") { + host = String(host.dropFirst().dropLast()) + } + + // Strict canonical dotted-decimal IPv4 (4 octets, no leading zeros, each 0-255). + // Do NOT use inet_pton here: Darwin's inet_pton accepts "0177.0.0.1" as + // decimal 177.0.0.1, but getaddrinfo (used by URLSession) interprets the + // same string as octal 127.0.0.1 — the divergence is a loopback bypass. + if let v4Bytes = parseCanonicalIPv4(host) { + return isPublicIPv4(v4Bytes) + } + + // IPv6 only — host must contain ":" so we don't accidentally hand a + // numeric-looking hostname to inet_pton. + if host.contains(":") { + var v6 = in6_addr() + if host.withCString({ inet_pton(AF_INET6, $0, &v6) }) == 1 { + let bytes = withUnsafeBytes(of: &v6) { ptr -> [UInt8] in + return Array(ptr) + } + return isPublicIPv6(bytes) + } + return false + } + + // Strict DNS-name validation. Anything that doesn't look like a real + // FQDN is rejected — this catches non-canonical numeric IP forms + // (decimal-32 like "2130706433", octal like "0177.0.0.1", hex like + // "0x7f.0.0.1", short forms like "127.1") that the OS resolver may + // still treat as 127.0.0.1 even when inet_pton would accept them as + // a different value or reject outright. + let labels = host.split(separator: ".", omittingEmptySubsequences: false) + guard labels.count >= 2 else { return false } + for label in labels { + guard !label.isEmpty, label.count <= 63 else { return false } + if label.first == "-" || label.last == "-" { return false } + for ch in label { + guard ch.isASCII else { return false } + if !(ch.isLetter || ch.isNumber || ch == "-") { return false } + } + } + guard let tld = labels.last, tld.count >= 2, tld.contains(where: { $0.isLetter }) else { + return false + } + + if host == "localhost" || host.hasSuffix(".localhost") || host.hasSuffix(".local") { + return false + } + return true +} + +private func parseCanonicalIPv4(_ host: String) -> [UInt8]? { + let parts = host.split(separator: ".", omittingEmptySubsequences: false) + guard parts.count == 4 else { return nil } + var bytes: [UInt8] = [] + bytes.reserveCapacity(4) + for part in parts { + guard !part.isEmpty, part.count <= 3 else { return nil } + if part.count > 1 && part.first == "0" { return nil } // no leading zeros (octal-spoof) + guard part.allSatisfy({ $0.isASCII && $0.isNumber }) else { return nil } + guard let value = UInt8(part) else { return nil } // also caps at 255 + bytes.append(value) + } + return bytes +} + +private func isPublicIPv4(_ bytes: [UInt8]) -> Bool { + guard bytes.count == 4 else { return false } + let a = bytes[0] + let b = bytes[1] + if a == 0 { return false } // 0.0.0.0/8 + if a == 10 { return false } // 10.0.0.0/8 + if a == 127 { return false } // 127.0.0.0/8 loopback + if a == 169 && b == 254 { return false } // 169.254.0.0/16 link-local + if a == 172 && (b & 0xf0) == 16 { return false } // 172.16.0.0/12 + if a == 192 && b == 168 { return false } // 192.168.0.0/16 + if a == 100 && (b & 0xc0) == 64 { return false } // 100.64.0.0/10 CGNAT + if a >= 224 { return false } // multicast + reserved + 255.255.255.255 + return true +} + +private func isPublicIPv6(_ bytes: [UInt8]) -> Bool { + guard bytes.count == 16 else { return false } + if bytes.allSatisfy({ $0 == 0 }) { return false } // :: + let loopback: [UInt8] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1] + if bytes == loopback { return false } // ::1 + if bytes[0] == 0xff { return false } // ff00::/8 multicast + if bytes[0] == 0xfe && (bytes[1] & 0xc0) == 0x80 { return false } // fe80::/10 link-local + if (bytes[0] & 0xfe) == 0xfc { return false } // fc00::/7 unique-local + let v4MappedPrefix: [UInt8] = [0,0,0,0,0,0,0,0,0,0,0xff,0xff] + if Array(bytes.prefix(12)) == v4MappedPrefix { // ::ffff:a.b.c.d + return isPublicIPv4(Array(bytes.suffix(4))) + } + if Array(bytes.prefix(12)).allSatisfy({ $0 == 0 }) { // ::a.b.c.d (deprecated) + return isPublicIPv4(Array(bytes.suffix(4))) + } + return true +} diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 436d8c36db..de27125c74 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -36,13 +36,41 @@ private class WebViewTouchGestureRecognizer: UITapGestureRecognizer { } } -private let eventProxySource = "var TelegramWebviewProxyProto = function() {}; " + - "TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " + - "window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " + - "}; " + -"var TelegramWebviewProxy = new TelegramWebviewProxyProto();" +private func jsStringLiteral(_ value: String) -> String { + if let data = try? JSONSerialization.data(withJSONObject: [value], options: []), let string = String(data: data, encoding: .utf8), string.hasPrefix("["), string.hasSuffix("]") { + return String(string.dropFirst().dropLast()) + } + return "\"\"" +} -private let selectionSource = "var css = '*{-webkit-touch-callout:none;} :not(input):not(textarea):not([\"contenteditable\"=\"true\"]){-webkit-user-select:none;}';" +private func eventProxySource() -> String { + return """ + (function() { + var TelegramWebviewProxyProto = function() {}; + TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { + window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); + }; + window.TelegramWebviewProxy = new TelegramWebviewProxyProto(); + })(); + """ +} + +private func securedEventProxySource(trustedOrigin: String) -> String { + return """ + (function() { + if (window.location.origin !== \(jsStringLiteral(trustedOrigin))) { + return; + } + var TelegramWebviewProxyProto = function() {}; + TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { + window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); + }; + window.TelegramWebviewProxy = new TelegramWebviewProxyProto(); + })(); + """ +} + +private let selectionSource = "var css = '*{-webkit-touch-callout:none;} :not(input):not(textarea):not([contenteditable=\"true\"]){-webkit-user-select:none;}';" + " var head = document.head || document.getElementsByTagName('head')[0];" + " var style = document.createElement('style'); style.type = 'text/css';" + " style.appendChild(document.createTextNode(css)); head.appendChild(style);" @@ -91,6 +119,7 @@ function tgBrowserDisconnectObserver() { final class WebAppWebView: WKWebView { var handleScriptMessage: (WKScriptMessage) -> Void = { _ in } + private(set) var trustedOrigin: String? var customInsets: UIEdgeInsets = .zero { didSet { @@ -134,8 +163,6 @@ final class WebAppWebView: WKWebView { let contentController = WKUserContentController() var handleScriptMessageImpl: ((WKScriptMessage) -> Void)? - let eventProxyScript = WKUserScript(source: eventProxySource, injectionTime: .atDocumentStart, forMainFrameOnly: true) - contentController.addUserScript(eventProxyScript) contentController.add(WeakGameScriptMessageHandler { message in handleScriptMessageImpl?(message) }, name: "performAction") @@ -187,6 +214,47 @@ final class WebAppWebView: WKWebView { print() } + var useSecuredEventProxy = true + func bindTrustedOrigin(from url: URL) { + guard self.trustedOrigin == nil else { + return + } + guard let origin = normalizedOrigin(url: url) else { + return + } + + self.trustedOrigin = origin + + let eventProxyScript = WKUserScript(source: securedEventProxySource(trustedOrigin: origin), injectionTime: .atDocumentStart, forMainFrameOnly: true) + self.configuration.userContentController.addUserScript(eventProxyScript) + } + + func setupEventProxySource() { + self.useSecuredEventProxy = false + + let eventProxyScript = WKUserScript(source: eventProxySource(), injectionTime: .atDocumentStart, forMainFrameOnly: true) + self.configuration.userContentController.addUserScript(eventProxyScript) + } + + func isTrustedMainFrameMessage(_ message: WKScriptMessage) -> Bool { + guard message.frameInfo.isMainFrame else { + return false + } + if !self.useSecuredEventProxy { + return true + } + guard let trustedOrigin = self.trustedOrigin else { + return false + } + guard message.frameInfo.securityOriginString == trustedOrigin else { + return false + } + if let currentOrigin = self.origin, currentOrigin != trustedOrigin { + return false + } + return true + } + override func didMoveToSuperview() { super.didMoveToSuperview() @@ -223,6 +291,11 @@ final class WebAppWebView: WKWebView { } func sendEvent(name: String, data: String?) { + if self.useSecuredEventProxy { + guard let trustedOrigin = self.trustedOrigin, self.origin == trustedOrigin else { + return + } + } let script = "window.TelegramGameProxy && window.TelegramGameProxy.receiveEvent && window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))" self.evaluateJavaScript(script, completionHandler: { _, _ in }) @@ -277,4 +350,41 @@ final class WebAppWebView: WKWebView { override var inputAccessoryView: UIView? { return nil } + + var origin: String? { + guard let url = self.url else { + return nil + } + return normalizedOrigin(url: url) + } +} + +extension WKFrameInfo { + var securityOriginString: String { + let securityOrigin = self.securityOrigin + return normalizedOrigin(scheme: securityOrigin.protocol, host: securityOrigin.host, port: securityOrigin.port == 0 ? nil : securityOrigin.port) ?? "" + } +} + +private func normalizedOrigin(url: URL) -> String? { + return normalizedOrigin(scheme: url.scheme, host: url.host, port: url.port) +} + +private func normalizedOrigin(scheme: String?, host: String?, port: Int?) -> String? { + guard let scheme = scheme?.lowercased(), !scheme.isEmpty, let host = host?.lowercased(), !host.isEmpty else { + return nil + } + + let includePort: Bool + if let port { + includePort = !(scheme == "http" && port == 80) && !(scheme == "https" && port == 443) + } else { + includePort = false + } + + if includePort, let port { + return "\(scheme)://\(host):\(port)" + } else { + return "\(scheme)://\(host)" + } } diff --git a/submodules/WebsiteType/BUILD b/submodules/WebsiteType/BUILD index 4b08a4a695..0aa10486c0 100644 --- a/submodules/WebsiteType/BUILD +++ b/submodules/WebsiteType/BUILD @@ -10,7 +10,6 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/Postbox", "//submodules/TelegramCore", ], visibility = [ diff --git a/submodules/WebsiteType/Sources/WebsiteType.swift b/submodules/WebsiteType/Sources/WebsiteType.swift index 69407581d2..4bccf86fd5 100644 --- a/submodules/WebsiteType/Sources/WebsiteType.swift +++ b/submodules/WebsiteType/Sources/WebsiteType.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore public enum WebsiteType { @@ -40,15 +39,15 @@ public func instantPageType(of webpage: TelegramMediaWebpageLoadedContent) -> In public func defaultWebpageImageSizeIsSmall(webpage: TelegramMediaWebpageLoadedContent) -> Bool { let type = websiteType(of: webpage.websiteName) - let mainMedia: Media? + let mainMedia: EngineMedia? switch type { case .instagram, .twitter: - mainMedia = webpage.story ?? webpage.image ?? webpage.file + mainMedia = webpage.story.map(EngineMedia.story) ?? webpage.image.map(EngineMedia.image) ?? webpage.file.map(EngineMedia.file) default: - mainMedia = webpage.story ?? webpage.file ?? webpage.image + mainMedia = webpage.story.map(EngineMedia.story) ?? webpage.file.map(EngineMedia.file) ?? webpage.image.map(EngineMedia.image) } - - if let image = mainMedia as? TelegramMediaImage { + + if case let .image(image) = mainMedia { if let type = webpage.type, (["photo", "video", "embed", "gif", "document", "telegram_album"] as [String]).contains(type) { } else if let type = webpage.type, (["article"] as [String]).contains(type) { return true diff --git a/submodules/ffmpeg/BUILD b/submodules/ffmpeg/BUILD index 2c1758dee2..9dad938d82 100644 --- a/submodules/ffmpeg/BUILD +++ b/submodules/ffmpeg/BUILD @@ -271,6 +271,13 @@ genrule( elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" VARIANT="debug" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + VARIANT="debug" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -301,24 +308,21 @@ cc_library( ] ) -objc_library( +cc_library( name = "ffmpeg", - module_name = "ffmpeg", - enable_modules = True, hdrs = ["Public/third_party/ffmpeg/" + x for x in ffmpeg_header_paths] + ["Public/" + x for x in ffmpeg_header_paths], includes = [ "Public", ], - sdk_dylibs = [ - "libbz2", - "libiconv", - "z", - ], - sdk_frameworks = [ - "AudioToolbox", - "CoreAudio", - "VideoToolbox" - ], + linkopts = select({ + "@platforms//os:linux": ["-lbz2", "-lz"], + "//conditions:default": [ + "-lbz2", "-liconv", "-lz", + "-framework AudioToolbox", + "-framework CoreAudio", + "-framework VideoToolbox", + ], + }), deps = [ ":ffmpeg_lib", "//third-party/libvpx:vpx", @@ -327,5 +331,5 @@ objc_library( ], visibility = [ "//visibility:public", - ] + ], ) diff --git a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh index a816a5ac02..5015e7489a 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh +++ b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -x @@ -7,7 +7,7 @@ ARCHS="" for RAW_ARCH in $RAW_ARCHS; do ARCH_NAME="$RAW_ARCH" - if [ "$ARCH_NAME" = "i386" -o "$ARCH_NAME" = "x86_64" -o "$ARCH_NAME" = "arm64" -o "$ARCH_NAME" = "armv7" -o "$ARCH_NAME" = "sim_arm64" ] + if [ "$ARCH_NAME" = "i386" -o "$ARCH_NAME" = "x86_64" -o "$ARCH_NAME" = "arm64" -o "$ARCH_NAME" = "armv7" -o "$ARCH_NAME" = "sim_arm64" -o "$ARCH_NAME" = "macos_arm64" -o "$ARCH_NAME" = "linux_arm64" -o "$ARCH_NAME" = "linux_x86_64" ] then ARCHS="$ARCHS $ARCH_NAME" else @@ -38,24 +38,21 @@ LIB_NAMES="libavcodec libavformat libavutil libswresample" set -e CONFIGURE_FLAGS="--enable-cross-compile --disable-programs \ - --disable-armv5te --disable-armv6 --disable-armv6t2 \ + --disable-armv5te --disable-armv6 --disable-armv6t2 \ --disable-doc --enable-pic --disable-all --disable-everything \ --enable-avcodec \ --enable-swresample \ --enable-avformat \ --disable-xlib \ --enable-libopus \ - --enable-libvpx \ - --enable-libdav1d \ - --enable-audiotoolbox \ + --enable-libvpx \ + --enable-libdav1d \ --enable-bsf=aac_adtstoasc,vp9_superframe,h264_mp4toannexb \ - --enable-decoder=h264,libvpx_vp9,hevc,libopus,flac,alac_at,pcm_s16le,pcm_s24le,pcm_f32le,gsm_ms_at,libdav1d,av1,mp3,aac_at \ - --enable-encoder=libvpx_vp9,aac_at \ + --enable-decoder=h264,libvpx_vp9,hevc,libopus,flac,pcm_s16le,pcm_s24le,pcm_f32le,libdav1d,av1,mp3 \ --enable-demuxer=aac,mov,m4v,mp3,ogg,libopus,flac,wav,aiff,matroska,mpegts, \ --enable-parser=aac,h264,mp3,libopus \ --enable-protocol=file \ --enable-muxer=mp4,matroska,ogg,mpegts \ - --enable-hwaccel=h264_videotoolbox,hevc_videotoolbox,av1_videotoolbox \ " #vorbis @@ -84,7 +81,7 @@ do do LIB="$THIN/$ARCH/lib/$LIB_NAME.a" if [ -f "$LIB" ]; then - LIB_DATE=`crc32 "$LIB"` + LIB_DATE=$(crc32 "$LIB" 2>/dev/null || cksum "$LIB" 2>/dev/null | cut -d' ' -f1 || echo "unknown") LIBS_HASH="$LIBS_HASH $ARCH/$LIB:$LIB_DATE" fi done @@ -99,7 +96,11 @@ then echo "PATH=$PATH" echo "pkg-config=$(which pkg-config)" fi - if [ ! `which "$GAS_PREPROCESSOR_PATH"` ]; then + IS_LINUX=false + for A in $ARCHS; do + case "$A" in linux_*) IS_LINUX=true ;; esac + done + if [ "$IS_LINUX" = "false" ] && [ ! `which "$GAS_PREPROCESSOR_PATH"` ]; then echo '$GAS_PREPROCESSOR_PATH not found.' exit 1 fi @@ -114,6 +115,8 @@ then ARCH="$RAW_ARCH" if [ "$RAW_ARCH" == "sim_arm64" ]; then ARCH="arm64" + elif [ "$RAW_ARCH" == "macos_arm64" ]; then + ARCH="arm64" fi echo "building $RAW_ARCH..." @@ -124,10 +127,26 @@ then LIBVPX_PATH="$SOURCE_DIR/libvpx" LIBDAV1D_PATH="$SOURCE_DIR/libdav1d" + if [ "$RAW_ARCH" = "linux_arm64" ]; then + ARCH="aarch64" + CFLAGS="$EXTRA_CFLAGS -fPIC" + CC="gcc" + AS="gcc" + PLATFORM="linux" + elif [ "$RAW_ARCH" = "linux_x86_64" ]; then + ARCH="x86_64" + CFLAGS="$EXTRA_CFLAGS -fPIC" + CC="gcc" + AS="nasm" + PLATFORM="linux" + else CFLAGS="$EXTRA_CFLAGS -arch $ARCH" if [ "$RAW_ARCH" = "sim_arm64" ]; then PLATFORM="iPhoneSimulator" CFLAGS="$CFLAGS -mios-simulator-version-min=$DEPLOYMENT_TARGET --target=arm64-apple-ios$DEPLOYMENT_TARGET-simulator" + elif [ "$RAW_ARCH" = "macos_arm64" ]; then + PLATFORM="MacOSX" + CFLAGS="$CFLAGS -mmacosx-version-min=14.0 --target=arm64-apple-macosx14.0" else PLATFORM="iPhoneOS" CFLAGS="$CFLAGS -mios-version-min=$DEPLOYMENT_TARGET" @@ -136,15 +155,18 @@ then EXPORT="GASPP_FIX_XCODE5=1" fi fi + fi - XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'` - CC="xcrun -sdk $XCRUN_SDK clang" + if [ "$PLATFORM" != "linux" ]; then + XCRUN_SDK=`echo $PLATFORM | tr '[:upper:]' '[:lower:]'` + CC="xcrun -sdk $XCRUN_SDK clang" - if [ "$RAW_ARCH" = "arm64" ] || [ "$RAW_ARCH" = "sim_arm64" ] - then - AS="$GAS_PREPROCESSOR_PATH -arch aarch64 -- $CC" - else - AS="$GAS_PREPROCESSOR_PATH -- $CC" + if [ "$RAW_ARCH" = "arm64" ] || [ "$RAW_ARCH" = "sim_arm64" ] || [ "$RAW_ARCH" = "macos_arm64" ] + then + AS="$GAS_PREPROCESSOR_PATH -arch aarch64 -- $CC" + else + AS="$GAS_PREPROCESSOR_PATH -- $CC" + fi fi CXXFLAGS="$CFLAGS" @@ -161,22 +183,42 @@ then echo "1" >/dev/null else mkdir -p "$THIN/$RAW_ARCH" - TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ - --target-os=darwin \ - --arch=$ARCH \ - --cc="$CC" \ - --as="$AS" \ - $CONFIGURE_FLAGS \ - --extra-cflags="$CFLAGS" \ - --extra-ldflags="$LDFLAGS" \ - --prefix="$THIN/$RAW_ARCH" \ - --pkg-config="$PKG_CONFIG" \ - --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ - || exit 1 + if [ "$PLATFORM" = "linux" ]; then + TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ + --target-os=linux \ + --arch=$ARCH \ + --cc="$CC" \ + --as="$AS" \ + $CONFIGURE_FLAGS \ + --enable-encoder=libvpx_vp9 \ + --extra-cflags="$CFLAGS" \ + --extra-ldflags="$LDFLAGS" \ + --prefix="$THIN/$RAW_ARCH" \ + --pkg-config="$PKG_CONFIG" \ + --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ + || exit 1 + else + TMPDIR=${TMPDIR/%\/} "$SOURCE/configure" \ + --target-os=darwin \ + --arch=$ARCH \ + --cc="$CC" \ + --as="$AS" \ + $CONFIGURE_FLAGS \ + --enable-audiotoolbox \ + --enable-decoder=alac_at,gsm_ms_at,aac_at \ + --enable-encoder=libvpx_vp9,aac_at \ + --enable-hwaccel=h264_videotoolbox,hevc_videotoolbox,av1_videotoolbox \ + --extra-cflags="$CFLAGS" \ + --extra-ldflags="$LDFLAGS" \ + --prefix="$THIN/$RAW_ARCH" \ + --pkg-config="$PKG_CONFIG" \ + --pkg-config-flags="--libopus_path $LIBOPUS_PATH --libvpx_path $LIBVPX_PATH --libdav1d_path $LIBDAV1D_PATH" \ + || exit 1 + fi echo "$CONFIGURE_FLAGS" > "$CONFIGURED_MARKER" fi - CORE_COUNT=`PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu` + CORE_COUNT=$(nproc 2>/dev/null || PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu 2>/dev/null || echo 4) make -j$CORE_COUNT install $EXPORT || exit 1 popd @@ -190,7 +232,7 @@ do do LIB="$THIN/$ARCH/lib/$LIB_NAME.a" if [ -f "$LIB" ]; then - LIB_DATE=`crc32 "$LIB"` + LIB_DATE=$(crc32 "$LIB" 2>/dev/null || cksum "$LIB" 2>/dev/null | cut -d' ' -f1 || echo "unknown") UPDATED_LIBS_HASH="$UPDATED_LIBS_HASH $ARCH/$LIB:$LIB_DATE" fi done @@ -215,7 +257,11 @@ then LIB_NAME="$(basename $LIB)" echo "LIPO_INPUT command find \"$THIN\" -name \"$LIB_NAME\"" LIPO_INPUT=`find "$THIN" -name "$LIB_NAME"` - lipo -create $LIPO_INPUT -output "$FAT/lib/$LIB_NAME" || exit 1 + if command -v lipo >/dev/null 2>&1; then + lipo -create $LIPO_INPUT -output "$FAT/lib/$LIB_NAME" || exit 1 + else + cp $LIPO_INPUT "$FAT/lib/$LIB_NAME" || exit 1 + fi done cp -rf "$THIN/$1/include" "$FAT" diff --git a/submodules/ffmpeg/Sources/FFMpeg/pkg-config b/submodules/ffmpeg/Sources/FFMpeg/pkg-config index 48e6014229..7a40f50389 100755 --- a/submodules/ffmpeg/Sources/FFMpeg/pkg-config +++ b/submodules/ffmpeg/Sources/FFMpeg/pkg-config @@ -1,4 +1,10 @@ -#!/bin/sh +#!/bin/bash + +# Strip version qualifiers (e.g. "dav1d >= 0.5.0" -> "dav1d") +# FFmpeg's configure passes these to pkg-config +strip_version() { + echo "$@" | sed 's/ *[><=!].*//' +} if [ "$1" == "--version" ]; then echo "0.29.2" @@ -7,8 +13,10 @@ elif [ "$1" == "--exists" ]; then NAME="$2" PRINT_ERRORS="0" if [ "$NAME" == "--print-errors" ]; then - NAME="$3" + NAME=$(strip_version "$3") PRINT_ERRORS="1" + else + NAME=$(strip_version "$2") fi if [ "$NAME" == "zlib" ]; then exit 0 @@ -87,17 +95,53 @@ elif [ "$1" == "--libs" ]; then echo "-lz" exit 0 elif [ "$NAME" == "opus" ]; then - echo "-L$LIBOPUS_PATH/lib -lopus" + echo "-L$LIBOPUS_PATH/lib -lopus -lm" exit 0 elif [ "$NAME" == "vpx" ]; then - echo "-L$LIBVPX_PATH/lib -lVPX" + echo "-L$LIBVPX_PATH/lib -lVPX -lm -lpthread" exit 0 elif [ "$NAME" == "dav1d" ]; then - echo "-L$LIBDAV1D_PATH/lib -ldav1d" + echo "-L$LIBDAV1D_PATH/lib -ldav1d -lm -lpthread -ldl" exit 0 else exit 1 fi +elif [[ "$1" == --variable=* ]]; then + # Handle --variable=includedir etc. + LIBOPUS_PATH="" + LIBVPX_PATH="" + LIBDAV1D_PATH="" + # Parse the library path flags to find NAME (last arg) + ARGS=("$@") + NAME="${ARGS[-1]}" + for ((i=1; i<${#ARGS[@]}-1; i++)); do + if [ "${ARGS[$i]}" == "--libopus_path" ]; then + LIBOPUS_PATH="${ARGS[$((i+1))]}" + elif [ "${ARGS[$i]}" == "--libvpx_path" ]; then + LIBVPX_PATH="${ARGS[$((i+1))]}" + elif [ "${ARGS[$i]}" == "--libdav1d_path" ]; then + LIBDAV1D_PATH="${ARGS[$((i+1))]}" + fi + done + VAR="${1#--variable=}" + if [ "$VAR" == "includedir" ]; then + if [ "$NAME" == "opus" ]; then + echo "$LIBOPUS_PATH/include" + exit 0 + elif [ "$NAME" == "vpx" ]; then + echo "$LIBVPX_PATH/include" + exit 0 + elif [ "$NAME" == "dav1d" ]; then + echo "$LIBDAV1D_PATH/include" + exit 0 + elif [ "$NAME" == "zlib" ]; then + echo "/usr/include" + exit 0 + fi + fi + # Return empty string for unhandled variables (non-fatal) + echo "" + exit 0 else exit 1 fi diff --git a/third-party/AppCenter/AppCenter.BUILD b/third-party/AppCenter/AppCenter.BUILD deleted file mode 100644 index ce677b8e72..0000000000 --- a/third-party/AppCenter/AppCenter.BUILD +++ /dev/null @@ -1,15 +0,0 @@ -load("@build_bazel_rules_apple//apple:apple.bzl", - "apple_static_framework_import", -) - -apple_static_framework_import( - name = "AppCenter", - framework_imports = glob(["AppCenter-SDK-Apple/iOS/AppCenter.framework/**"]), - visibility = ["//visibility:public"], -) - -apple_static_framework_import( - name = "AppCenterCrashes", - framework_imports = glob(["AppCenter-SDK-Apple/iOS/AppCenterCrashes.framework/**"]), - visibility = ["//visibility:public"], -) diff --git a/third-party/Swift2D/BUILD b/third-party/Swift2D/BUILD new file mode 100644 index 0000000000..cf88b4459a --- /dev/null +++ b/third-party/Swift2D/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "Swift2D", + module_name = "Swift2D", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-suppress-warnings", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/third-party/Swift2D/Sources/Point+CoreGraphics.swift b/third-party/Swift2D/Sources/Point+CoreGraphics.swift new file mode 100644 index 0000000000..2d85614228 --- /dev/null +++ b/third-party/Swift2D/Sources/Point+CoreGraphics.swift @@ -0,0 +1,19 @@ +#if canImport(CoreGraphics) +import CoreGraphics +#else +import Foundation +#endif + +public extension Point { + /// Initialize a `Point` using the provided `CGPoint` values. + init(_ point: CGPoint) { + self.init(x: point.x, y: point.y) + } +} + +public extension CGPoint { + /// Initialize a `CPPoint` using the provided `Point` values. + init(_ point: Point) { + self.init(x: point.x, y: point.y) + } +} diff --git a/third-party/Swift2D/Sources/Point.swift b/third-party/Swift2D/Sources/Point.swift new file mode 100644 index 0000000000..72170ad657 --- /dev/null +++ b/third-party/Swift2D/Sources/Point.swift @@ -0,0 +1,82 @@ +/// The representation of a single point in a two-dimensional plane. +public struct Point: Hashable, Codable, Sendable, CustomStringConvertible { + + public static let zero: Point = Point(x: 0, y: 0) + public static let nan: Point = Point(x: Double.nan, y: Double.nan) + public static let infinite: Point = Point(x: -Double.greatestFiniteMagnitude / 2, y: -Double.greatestFiniteMagnitude / 2) + public static let null: Point = Point(x: Double.infinity, y: Double.infinity) + + public let x: Double + public let y: Double + + public init(x: Double = 0.0, y: Double = 0.0) { + self.x = x + self.y = y + } + + public init(x: Float, y: Float) { + self.x = Double(x) + self.y = Double(y) + } + + public init(x: Int, y: Int) { + self.x = Double(x) + self.y = Double(y) + } + + public var description: String { "Point(x: \(x), y: \(y))" } + public var isZero: Bool { self == .zero } + public var isNaN: Bool { self == .nan } + public var isInfinite: Bool { self == .infinite } + public var isNull: Bool { self == .null } + + /// Create a new instance maintaining the `y` value while using the provided `x` value. + public func x(_ value: Double) -> Point { + Point(x: value, y: y) + } + + /// Create a new instance maintaining the `x` value while using the provided `y` value. + public func y(_ value: Double) -> Point { + Point(x: x, y: value) + } + + /// The _mirror_ of the instance, rotated 180° around the provided `point` + /// in a two-dimensional plane. + /// + /// - parameters: + /// - point: The anchor to use in calculating the reflection. + public func reflecting(around point: Point) -> Point { + let x = (2 * point.x) - x + let y = (2 * point.y) - y + return Point(x: x, y: y) + } + + public static func == (lhs: Point, rhs: Point) -> Bool { + if lhs.x.isNaN, rhs.x.isNaN, lhs.y.isNaN, rhs.y.isNaN { + return true + } + + if lhs.x.isInfinite, rhs.x.isInfinite, lhs.y.isInfinite, rhs.y.isInfinite { + return true + } + + return lhs.x == rhs.x && lhs.y == rhs.y + } +} + +public extension Point { + @available(*, deprecated, renamed: "x(_:)") + func with(x value: Double) -> Point { + x(value) + } + + @available(*, deprecated, renamed: "y(_:)") + func with(y value: Double) -> Point { + y(value) + } + + @available(*, deprecated, renamed: "reflecting(around:)") + func reflection(using point: Point) -> Point { + reflecting(around: point) + } +} diff --git a/third-party/Swift2D/Sources/Rect+CoreGraphics.swift b/third-party/Swift2D/Sources/Rect+CoreGraphics.swift new file mode 100644 index 0000000000..45eb402e3b --- /dev/null +++ b/third-party/Swift2D/Sources/Rect+CoreGraphics.swift @@ -0,0 +1,32 @@ +#if canImport(CoreGraphics) +import CoreGraphics +#else +import Foundation +#endif + +public extension Rect { + /// Initialize a `Rect` using the provided `CGRect` values. + init(_ rect: CGRect) { + self.init( + origin: Point(rect.origin), + size: Size(rect.size) + ) + } + + @available(*, deprecated, renamed: "CGRect(_:)", message: "Use CGRect initializer directly") + var cgRect: CGRect { + CGRect(self) + } +} + +public extension CGRect { + /// Initialize a `CGRect` using the provided `Rect` values. + init(_ rect: Rect) { + self.init( + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height + ) + } +} diff --git a/third-party/Swift2D/Sources/Rect.swift b/third-party/Swift2D/Sources/Rect.swift new file mode 100644 index 0000000000..1fd40a2701 --- /dev/null +++ b/third-party/Swift2D/Sources/Rect.swift @@ -0,0 +1,251 @@ +/// The location and dimensions of a rectangle. +public struct Rect: Hashable, Codable, Sendable, CustomStringConvertible { + + public static let zero: Rect = Rect(origin: .zero, size: .zero) + public static let nan: Rect = Rect(origin: .nan, size: .nan) + public static let infinite: Rect = Rect(origin: .infinite, size: .infinite) + public static let null: Rect = Rect(origin: .null, size: .zero) + + /// A point that specifies the coordinates of the rectangle’s origin. + public let origin: Point + /// A size that specifies the height and width of the rectangle. + public let size: Size + + public init(origin: Point = .zero, size: Size = .zero) { + self.origin = origin + self.size = size + } + + public init(x: Double, y: Double, width: Double, height: Double) { + origin = Point(x: x, y: y) + size = Size(width: width, height: height) + } + + public init(x: Float, y: Float, width: Float, height: Float) { + origin = Point(x: x, y: y) + size = Size(width: width, height: height) + } + + public init(x: Int, y: Int, width: Int, height: Int) { + origin = Point(x: x, y: y) + size = Size(width: width, height: height) + } + + public var description: String { "Rect(origin: \(origin), size: \(size))" } + public var isZero: Bool { self == .zero } + public var isNaN: Bool { self == .nan } + public var isInfinite: Bool { self == .infinite } + public var isNull: Bool { origin == .infinite || origin == .null } + public var isEmpty: Bool { isNull || width == 0.0 || height == 0.0 } + + /// The x-coordinate of the rectangle origin + public var x: Double { origin.x } + + /// The y-coordinate of the rectangle origin + public var y: Double { origin.y } + + /// The width of the rectangle. + public var width: Double { size.width } + + /// The height of the rectangle. + public var height: Double { size.height } + + /// The middle of the `Rect` both horizontally and vertically. + public var center: Point { Point(x: midX, y: midY) } + + /// The smallest value for the x-coordinate of the rectangle. + public var minX: Double { + if size.width < 0.0 { + return origin.x - abs(size.width) + } + + return origin.x + } + + /// The x-coordinate that establishes the center of a rectangle. + public var midX: Double { minX + size.widthRadius } + + /// The largest value of the x-coordinate for the rectangle. + public var maxX: Double { + if size.width < 0.0 { + return origin.x + } + + return origin.x + size.width + } + + /// The smallest value for the y-coordinate of the rectangle. + public var minY: Double { + if size.height < 0.0 { + return origin.y - abs(size.height) + } + + return origin.y + } + + /// The y-coordinate that establishes the center of the rectangle. + public var midY: Double { minY + size.heightRadius } + + /// The largest value for the y-coordinate of the rectangle. + public var maxY: Double { + if size.height < 0.0 { + return origin.y + } + + return origin.y + size.height + } + + /// Returns a rectangle with a positive width and height. + public var standardized: Rect { + guard !isNull else { + return .null + } + + return Rect(x: minX, y: minY, width: abs(width), height: abs(height)) + } + + public func origin(_ value: Point) -> Rect { + Rect(origin: value, size: size) + } + + public func size(_ value: Size) -> Rect { + Rect(origin: origin, size: value) + } + + public func contains(_ point: Point) -> Bool { + guard !isNull else { + return false + } + + guard !isEmpty else { + return false + } + + return (minX ..< maxX).contains(point.x) && (minY ..< maxY).contains(point.y) + } + + public func contains(_ rect: Rect) -> Bool { + union(rect) == self + } + + public func intersects(_ rect: Rect) -> Bool { + !intersection(rect).isNull + } + + public func intersection(_ rect: Rect) -> Rect { + guard !isNull else { + return .null + } + + guard !rect.isNull else { + return .null + } + + let r1 = standardized + let r1spanH = r1.minX ... r1.maxX + let r1spanV = r1.minY ... r1.maxY + + let r2 = rect.standardized + let r2spanH = r2.minX ... r2.maxX + let r2spanV = r2.minY ... r2.maxY + + guard r1spanH.overlaps(r2spanH), r2spanV.overlaps(r2spanV) else { + return .null + } + + let overlapH = r1spanH.clamped(to: r2spanH) + let width: Double = if overlapH == r1spanH { + r1.width + } else if overlapH == r2spanH { + r2.width + } else { + overlapH.upperBound - overlapH.lowerBound + } + + let overlapV = r1spanV.clamped(to: r2spanV) + let height: Double = if overlapV == r1spanV { + r1.height + } else if overlapV == r2spanV { + r2.height + } else { + overlapV.upperBound - overlapV.lowerBound + } + + return Rect(x: overlapH.lowerBound, y: overlapV.lowerBound, width: width, height: height) + } + + public func union(_ rect: Rect) -> Rect { + guard !isNull else { + return rect + } + + guard !rect.isNull else { + return self + } + + let r1 = standardized + let r2 = rect.standardized + + let minX = min(r1.minX, r2.minX) + let maxX = max(r1.maxX, r2.maxX) + let minY = min(r1.minY, r2.minY) + let maxY = max(r1.maxY, r2.maxY) + + return Rect(x: minX, y: minY, width: maxX - minX, height: maxY - minY) + } + + public func offsetBy(dx: Double, dy: Double) -> Rect { + guard !isNull else { + return self + } + + let rect = standardized + return Rect( + origin: Point( + x: rect.origin.x + dx, + y: rect.origin.y + dy + ), + size: rect.size + ) + } + + public func insetBy(dx: Double, dy: Double) -> Rect { + guard !isNull else { + return self + } + + let normalized = standardized + let rect = Rect( + origin: Point( + x: normalized.x + dx, + y: normalized.y + dy + ), + size: Size( + width: normalized.width - (dx * 2), + height: normalized.height - (dy * 2) + ) + ) + + guard rect.size.width >= 0, rect.size.height >= 0 else { + return .null + } + + return rect + } + + public func expandedBy(dx: Double, dy: Double) -> Rect { + insetBy(dx: -dx, dy: -dy) + } +} + +public extension Rect { + @available(*, deprecated, renamed: "origin(_:)") + func with(origin value: Point) -> Rect { + Rect(origin: value, size: size) + } + + @available(*, deprecated, renamed: "size(_:)") + func with(size value: Size) -> Rect { + Rect(origin: origin, size: value) + } +} diff --git a/third-party/Swift2D/Sources/Size+CoreGraphics.swift b/third-party/Swift2D/Sources/Size+CoreGraphics.swift new file mode 100644 index 0000000000..014ba8ace4 --- /dev/null +++ b/third-party/Swift2D/Sources/Size+CoreGraphics.swift @@ -0,0 +1,19 @@ +#if canImport(CoreGraphics) +import CoreGraphics +#else +import Foundation +#endif + +public extension Size { + /// Initialize a `Size` using the provided `CGSize` values. + init(_ size: CGSize) { + self.init(width: size.width, height: size.height) + } +} + +public extension CGSize { + /// Initialize a `CGSize` using the provided `Size` values. + init(_ size: Size) { + self.init(width: size.width, height: size.height) + } +} diff --git a/third-party/Swift2D/Sources/Size.swift b/third-party/Swift2D/Sources/Size.swift new file mode 100644 index 0000000000..202cfa5f36 --- /dev/null +++ b/third-party/Swift2D/Sources/Size.swift @@ -0,0 +1,87 @@ +/// A representation of two-dimensional width and height values. +public struct Size: Hashable, Codable, Sendable, CustomStringConvertible { + + public static let zero: Size = Size(width: 0.0, height: 0.0) + public static let nan: Size = Size(width: Double.nan, height: Double.nan) + public static let infinite: Size = Size(width: Double.greatestFiniteMagnitude, height: Double.greatestFiniteMagnitude) + + public let width: Double + public let height: Double + + public init(width: Double = 0.0, height: Double = 0.0) { + self.width = width + self.height = height + } + + public init(width: Float, height: Float) { + self.width = Double(width) + self.height = Double(height) + } + + public init(width: Int, height: Int) { + self.width = Double(width) + self.height = Double(height) + } + + public var description: String { "Size(width: \(width), height: \(height))" } + public var isZero: Bool { self == .zero } + public var isNaN: Bool { self == .nan } + public var isInfinite: Bool { self == .infinite } + + /// The radius defined by the `width` dimension. + public var widthRadius: Double { abs(width) / 2.0 } + + /// The radius defined by the `height` dimension. + public var heightRadius: Double { abs(height) / 2.0 } + + /// The largest of the width and height radii. + public var maxRadius: Double { max(widthRadius, heightRadius) } + + /// The smallest of the width and height radii. + public var minRadius: Double { min(widthRadius, heightRadius) } + + /// The `Point` at which the `widthRadius` & `heightRadius` intersect. + public var center: Point { Point(x: widthRadius, y: heightRadius) } + + /// Create a new instance maintaining the `height` value while using the provided `width` value. + public func width(_ value: Double) -> Size { + Size(width: value, height: height) + } + + /// Create a new instance maintaining the `width` value while using the provided `height` value. + public func height(_ value: Double) -> Size { + Size(width: width, height: value) + } + + public static func == (lhs: Size, rhs: Size) -> Bool { + if lhs.width.isNaN, rhs.width.isNaN, lhs.height.isNaN, rhs.height.isNaN { + return true + } + + return lhs.width == rhs.width && lhs.height == rhs.height + } +} + +public extension Size { + @available(*, deprecated, renamed: "widthRadius") + var xRadius: Double { abs(width) / 2.0 } + + @available(*, deprecated, renamed: "heightRadius") + var yRadius: Double { abs(height) / 2.0 } + + @available(*, deprecated, renamed: "widthRadius") + var horizontalRadius: Double { xRadius } + + @available(*, deprecated, renamed: "heightRadius") + var verticalRadius: Double { yRadius } + + @available(*, deprecated, renamed: "width(_:)") + func with(width value: Double) -> Size { + width(value) + } + + @available(*, deprecated, renamed: "height(_:)") + func with(height value: Double) -> Size { + height(value) + } +} diff --git a/third-party/SwiftMath/BUILD b/third-party/SwiftMath/BUILD new file mode 100644 index 0000000000..66634a8c41 --- /dev/null +++ b/third-party/SwiftMath/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SwiftMath", + module_name = "SwiftMath", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/third-party/SwiftMath/LICENSE b/third-party/SwiftMath/LICENSE new file mode 100755 index 0000000000..dddb1a7a28 --- /dev/null +++ b/third-party/SwiftMath/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Computer Inspirations + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party/SwiftMath/README.md b/third-party/SwiftMath/README.md new file mode 100644 index 0000000000..883c9038f5 --- /dev/null +++ b/third-party/SwiftMath/README.md @@ -0,0 +1,714 @@ +# SwiftMath + +`SwiftMath` provides a full Swift implementation of [iosMath](https://travis-ci.org/kostub/iosMath) +for displaying beautifully rendered math equations in iOS and MacOS applications. It typesets formulae written +using LaTeX in a `UILabel` equivalent class. It uses the same typesetting rules as LaTeX and +so the equations are rendered exactly as LaTeX would render them. + +Please also check out [SwiftMathDemo](https://github.com/mgriebling/SwiftMathDemo.git) for examples of how to use `SwiftMath` +from SwiftUI. + +`SwiftMath` is similar to [MathJax](https://www.mathjax.org) or +[KaTeX](https://github.com/Khan/KaTeX) for the web but for native iOS or MacOS +applications without having to use a `UIWebView` and Javascript. More +importantly, it is significantly faster than using a `UIWebView`. + +`SwiftMath` is a Swift translation of the latest `iosMath` v0.9.5 release but includes bug fixes +and enhancements like a new \lbar (lambda bar) character and cyrillic alphabet support. +The original `iosMath` test suites have also been translated to Swift and run without errors. +Note: Error test conditions are ignored to avoid tagging everything with silly `throw`s. +Please let me know of any bugs or bug fixes that you find. + +`SwiftMath` prepackages everything needed for direct access via the Swift Package Manager. + +## Examples +Here are screenshots of some formulae that were rendered with this library: + +```LaTeX +x = \frac{-b \pm \sqrt{b^2-4ac}}{2a} +``` + +![Quadratic Formula](img/quadratic-light.png#gh-light-mode-only) +![Quadratic Formula](img/quadratic-dark.png#gh-dark-mode-only) + +```LaTeX +f(x) = \int\limits_{-\infty}^\infty\!\hat f(\xi)\,e^{2 \pi i \xi x}\,\mathrm{d}\xi +``` + +![Calculus](img/calculus-light.png#gh-light-mode-only) +![Calculus](img/calculus-dark.png#gh-dark-mode-only) + +```LaTeX +\frac{1}{n}\sum_{i=1}^{n}x_i \geq \sqrt[n]{\prod_{i=1}^{n}x_i} +``` + +![AM-GM](img/amgm-light.png#gh-light-mode-only) +![AM-GM](img/amgm-dark.png#gh-dark-mode-only) + +```LaTex +\frac{1}{\left(\sqrt{\phi \sqrt{5}}-\phi\\right) e^{\frac25 \pi}} += 1+\frac{e^{-2\pi}} {1 +\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots} } } } +``` + +![Ramanujan Identity](img/ramanujan-light.png#gh-light-mode-only) +![Ramanujan Identity](img/ramanujan-dark.png#gh-dark-mode-only) + +More examples are included in [EXAMPLES](EXAMPLES.md) + +## Fonts +Here are previews of the included fonts: + +![](img/FontsPreview.png#gh-dark-mode-only) +![](img/FontsPreviewLight.png#gh-light-mode-only) + +## Requirements +`SwiftMath` works on iOS 11+ or MacOS 12+. It depends +on the following Apple frameworks: + +* Foundation.framework +* CoreGraphics.framework +* QuartzCore.framework +* CoreText.framework + +Additionally for iOS it requires: +* UIKit.framework + +Additionally for MacOS it requires: +* AppKit.framework + +## Installation + +### Swift Package + +`SwiftMath` is available from [SwiftMath](https://github.com/mgriebling/SwiftMath.git). +To use it in your code, just add the https://github.com/mgriebling/SwiftMath.git path to +XCode's package manager. + +## Usage + +The library provides a class `MTMathUILabel` which is a `UIView` that +supports rendering math equations. To display an equation simply create +an `MTMathUILabel` as follows: + +```swift + +import SwiftMath + +let label = MTMathUILabel() +label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}" + +``` +Adding `MTMathUILabel` as a sub-view of your `UIView` will render the +quadratic formula example shown above. + +The following code creates a SwiftUI component called `MathView` encapsulating the MTMathUILabel: + +```swift +import SwiftUI +import SwiftMath + +struct MathView: UIViewRepresentable { + var equation: String + var font: MathFont = .latinModernFont + var textAlignment: MTTextAlignment = .center + var fontSize: CGFloat = 30 + var labelMode: MTMathUILabelMode = .text + var insets: MTEdgeInsets = MTEdgeInsets() + + func makeUIView(context: Context) -> MTMathUILabel { + let view = MTMathUILabel() + view.setContentHuggingPriority(.required, for: .vertical) + view.setContentCompressionResistancePriority(.required, for: .vertical) + return view + } + + func updateUIView(_ view: MTMathUILabel, context: Context) { + view.latex = equation + let font = MTFontManager().font(withName: font.rawValue, size: fontSize) + font?.fallbackFont = UIFont.systemFont(ofSize: fontSize) + view.font = font + view.textAlignment = textAlignment + view.labelMode = labelMode + view.textColor = MTColor(Color.primary) + view.contentInsets = insets + view.invalidateIntrinsicContentSize() + } + + func sizeThatFits(_ proposal: ProposedViewSize, uiView: MTMathUILabel, context: Context) -> CGSize? { + // Enable line wrapping by passing proposed width to the label + if let width = proposal.width, width.isFinite, width > 0 { + uiView.preferredMaxLayoutWidth = width + let size = uiView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)) + return size + } + return nil + } +} +``` + +For code that works with SwiftUI running natively under MacOS use the following: + +```swift +import SwiftUI +import SwiftMath + +struct MathView: NSViewRepresentable { + var equation: String + var font: MathFont = .latinModernFont + var textAlignment: MTTextAlignment = .center + var fontSize: CGFloat = 30 + var labelMode: MTMathUILabelMode = .text + var insets: MTEdgeInsets = MTEdgeInsets() + + func makeNSView(context: Context) -> MTMathUILabel { + let view = MTMathUILabel() + view.setContentHuggingPriority(.required, for: .vertical) + view.setContentCompressionResistancePriority(.required, for: .vertical) + return view + } + + func updateNSView(_ view: MTMathUILabel, context: Context) { + view.latex = equation + let font = MTFontManager().font(withName: font.rawValue, size: fontSize) + font?.fallbackFont = NSFont.systemFont(ofSize: fontSize) + view.font = font + view.textAlignment = textAlignment + view.labelMode = labelMode + view.textColor = MTColor(Color.primary) + view.contentInsets = insets + view.invalidateIntrinsicContentSize() + } + + func sizeThatFits(_ proposal: ProposedViewSize, nsView: MTMathUILabel, context: Context) -> CGSize? { + // Enable line wrapping by passing proposed width to the label + if let width = proposal.width, width.isFinite, width > 0 { + nsView.preferredMaxLayoutWidth = width + let size = nsView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)) + return size + } + return nil + } +} +``` + +### Automatic Line Wrapping + +`SwiftMath` supports automatic line wrapping (multiline display) for mathematical content. The implementation uses **interatom line breaking** which breaks equations at atom boundaries (between mathematical elements) rather than within them, preserving the semantic structure of the mathematics. + +#### Using Line Wrapping with UIKit/AppKit + +For direct `MTMathUILabel` usage, set the `preferredMaxLayoutWidth` property: + +```swift +let label = MTMathUILabel() +label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5" +label.font = MTFontManager.fontManager.defaultFont + +// Enable line wrapping by setting a maximum width +label.preferredMaxLayoutWidth = 235 +``` + +You can also use `sizeThatFits` to calculate the size with a width constraint: + +```swift +let constrainedSize = label.sizeThatFits(CGSize(width: 235, height: .greatestFiniteMagnitude)) +``` + +#### Using Line Wrapping with SwiftUI + +The `MathView` examples above include `sizeThatFits()` which automatically enables line wrapping when SwiftUI proposes a width constraint. No additional configuration is needed: + +```swift +VStack(alignment: .leading, spacing: 8) { + MathView( + equation: "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5", + fontSize: 17, + labelMode: .text + ) +} +.frame(maxWidth: 235) // The equation will break across multiple lines +``` + +#### Line Wrapping Behavior and Capabilities + +SwiftMath implements **two complementary line breaking mechanisms**: + +##### 1. Interatom Line Breaking (Primary) +Breaks equations **between atoms** (mathematical elements) when content exceeds the width constraint. This is the preferred method as it maintains semantic integrity. + +##### 2. Universal Line Breaking (Fallback) +For very long text within single atoms, breaks at Unicode word boundaries using Core Text with number protection (prevents splitting numbers like "3.14"). + +See `MULTILINE_IMPLEMENTATION_NOTES.md` for implementation details and recent changes. + +#### Fully Supported Cases + +These atom types work perfectly with interatom line breaking: + +**✅ Variables and ordinary text:** +```swift +label.latex = "a b c d e f g h i j k l m n o p" +label.preferredMaxLayoutWidth = 150 +// Breaks between individual variables at natural boundaries +``` + +**✅ Binary operators (+, -, ×, ÷):** +```swift +label.latex = "a+b+c+d+e+f+g+h" +label.preferredMaxLayoutWidth = 100 +// Breaks cleanly: "a+b+c+d+" +// "e+f+g+h" +``` + +**✅ Relations (=, <, >, ≤, ≥, etc.):** +```swift +label.latex = "a=1, b=2, c=3, d=4, e=5" +label.preferredMaxLayoutWidth = 120 +// Breaks after commas and operators +``` + +**✅ Mixed text and simple math:** +```swift +label.latex = "\\text{Calculer }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1" +label.preferredMaxLayoutWidth = 200 +// Breaks between text and math atoms naturally +``` + +**✅ Punctuation (commas, periods):** +```swift +label.latex = "\\text{First, second, third, fourth, fifth}" +label.preferredMaxLayoutWidth = 150 +// Breaks at commas and spaces +``` + +**✅ Brackets and parentheses (simple):** +```swift +label.latex = "(a+b)+(c+d)+(e+f)" +label.preferredMaxLayoutWidth = 120 +// Breaks between parenthesized groups +``` + +**✅ Greek letters and symbols:** +```swift +label.latex = "\\alpha+\\beta+\\gamma+\\delta+\\epsilon+\\zeta" +label.preferredMaxLayoutWidth = 150 +// Breaks between Greek letters +``` + +**✅ Fractions (NEW!):** +```swift +label.latex = "a+\\frac{1}{2}+b+\\frac{3}{4}+c" +label.preferredMaxLayoutWidth = 150 +// Fractions stay inline if they fit, break to new line only when needed +// Example: "a + ½ + b" stays on one line if it fits +``` + +**✅ Radicals/Square roots (NEW!):** +```swift +label.latex = "x+\\sqrt{2}+y+\\sqrt{3}+z" +label.preferredMaxLayoutWidth = 150 +// Radicals stay inline if they fit, break to new line only when needed +// Example: "x + √2 + y" stays on one line if it fits +``` + +**✅ Mixed fractions and radicals (NEW!):** +```swift +label.latex = "a+\\frac{1}{2}+\\sqrt{3}+b" +label.preferredMaxLayoutWidth = 200 +// Breaks between complex mathematical elements +``` + +#### Limited Support Cases + +These cases work but with some constraints: + +**✅ Large operators (NEW!):** +```swift +label.latex = "a + \\sum_{i=1}^{n} x_i + \\int_{0}^{1} f(x)dx + b" +label.preferredMaxLayoutWidth = 200 +// Large operators stay inline when they fit! +// Includes height checking for operators with limits +``` + +**✅ Delimited expressions (NEW!):** +```swift +label.latex = "(a+b) + \\left(\\frac{c}{d}\\right) + e" +label.preferredMaxLayoutWidth = 200 +// Delimiters stay inline when they fit! +// Inner content respects width constraints and wraps naturally +``` + +**✅ Colored expressions (NEW!):** +```swift +label.latex = "a + \\color{red}{b + c + d} + e" +label.preferredMaxLayoutWidth = 200 +// Colored sections stay inline when they fit! +// Inner content respects width constraints and wraps properly +``` + +**✅ Matrices/tables (NEW!):** +```swift +label.latex = "A = \\begin{pmatrix} 1 & 2 \\end{pmatrix} + B" +label.preferredMaxLayoutWidth = 200 +// Small matrices stay inline when they fit! +``` + +**✅ Atoms with superscripts/subscripts (IMPROVED!):** +```swift +label.latex = "a^{2}+b^{2}+c^{2}+d^{2}+e^{2}" +label.preferredMaxLayoutWidth = 150 +// Now works with width-based breaking! +// Scripted atoms participate in smart line breaking decisions +``` + +**✅ Math accents:** +```swift +label.latex = "\\hat{x} + \\tilde{y} + \\bar{z} + \\vec{w}" +label.preferredMaxLayoutWidth = 150 +// Common accents (\hat, \tilde, \bar, \vec) work well +// Vector arrows (\overrightarrow, etc.) supported with stretching +``` + +**⚠️ Very long single text atoms:** +```swift +label.latex = "\\text{This is an extremely long piece of text within a single text command}" +label.preferredMaxLayoutWidth = 200 +// Uses Unicode word boundary breaking with Core Text +// Protects numbers from being split (e.g., "3.14" stays together) +``` +**Note**: Breaks within the text atom rather than between atoms, which is expected behavior for very long continuous text. + +#### Best Practices + +**DO:** +- Use interatom breaking for simple equations with operators and relations +- Use for mixed text and math where you want natural breaks +- Use for long sequences of variables, numbers, and operators +- Set appropriate `preferredMaxLayoutWidth` based on your layout needs + +**DON'T:** +- Expect natural breaking in expressions with large operators (∑, ∫, etc. - not yet optimized) +- Expect natural breaking in expressions with \left...\right delimiters (not yet optimized) +- Use extremely narrow widths (less than ~80pt) which may cause poor breaks + +#### Examples + +**Excellent use case (discriminant formula):** +```swift +label.latex = "\\text{Calculer le discriminant }\\Delta=b^{2}-4ac\\text{ avec }a=1\\text{, }b=-1\\text{, }c=-5" +label.preferredMaxLayoutWidth = 235 +// ✅ Breaks naturally at good points between atoms +``` + +**Good use case (simple arithmetic):** +```swift +label.latex = "5+10+15+20+25+30+35+40+45+50" +label.preferredMaxLayoutWidth = 150 +// ✅ Breaks between operators cleanly +``` + +**Excellent use case (fractions inline - NEW!):** +```swift +label.latex = "a+\\frac{1}{2}+b+\\frac{3}{4}+c" +label.preferredMaxLayoutWidth = 200 +// ✅ Fractions stay inline when they fit! +// Breaks: "a + ½ + b" on line 1, "+ ¾ + c" on line 2 +``` + +**Excellent use case (radicals inline - NEW!):** +```swift +label.latex = "x+\\sqrt{2}+y+\\sqrt{3}+z" +label.preferredMaxLayoutWidth = 150 +// ✅ Radicals stay inline when they fit! +// Example: "x + √2 + y" on line 1, "+ √3 + z" on line 2 +``` + +**Alternative for complex expressions:** +```swift +// Instead of trying to break this: +label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}" +// Consider it as a single display equation without width constraint +label.preferredMaxLayoutWidth = 0 // No breaking +``` + +#### Technical Details + +- **Line spacing**: Dynamic line height based on actual content (tall fractions get more space, regular content stays compact) +- **Breaking algorithm**: Greedy with look-ahead and break quality scoring - prefers breaking after operators over other positions +- **Width calculation**: Includes inter-element spacing according to TeX spacing rules +- **Number protection**: Numbers in patterns like "3.14", "1,000", etc. are kept intact +- **Supports locales**: English, French, Swiss number formats +- **Advanced features**: Tokenization infrastructure with phase-based processing (preprocessing → tokenization → line fitting → display generation) + +For complete implementation details including recent improvements (dynamic line height, break quality scoring, early exit optimization), see [MULTILINE_IMPLEMENTATION_NOTES.md](MULTILINE_IMPLEMENTATION_NOTES.md). + +### Included Features +This is a list of formula types that the library currently supports: + +* Simple algebraic equations +* Fractions and continued fractions (including `\frac`, `\dfrac`, `\tfrac`, `\cfrac`) +* Exponents and subscripts +* Trigonometric formulae (including inverse hyperbolic: `\arcsinh`, `\arccosh`, etc.) +* Square roots and n-th roots +* Calculus symbols - limits, derivatives, integrals (including `\iint`, `\iiint`, `\iiiint`) +* Big operators (e.g. product, sum) +* Big delimiters (using `\left` and `\right`) +* Manual delimiter sizing (`\big`, `\Big`, `\bigg`, `\Bigg` and variants) +* Greek alphabet +* Bold Greek symbols (`\boldsymbol`) +* Combinatorics (`\binom`, `\choose` etc.) +* Geometry symbols (e.g. angle, congruence etc.) +* Ratios, proportions, percentages +* Math spacing +* Overline and underline +* Math accents (including `\hat`, `\tilde`, `\bar`, `\vec`, `\dot`, `\ddot`, etc.) +* Vector arrows (`\vec`, `\overrightarrow`, `\overleftarrow`, `\overleftrightarrow` with automatic stretching) +* Wide accents (`\widehat`, `\widetilde`) +* Operator names (`\operatorname{name}`) +* Matrices (including `\smallmatrix` and starred variants like `pmatrix*` with alignment) +* Multi-line subscripts and limits (`\substack`) +* Equation alignment +* Change bold, roman, caligraphic and other font styles (`\bf`, `\text`, etc.) +* Style commands (`\displaystyle`, `\textstyle`) +* Custom operators (`\operatorname`, `\operatorname*`) +* Dirac notation (`\bra`, `\ket`, `\braket`) +* Most commonly used math symbols +* Colors for both text and background +* **Inline and display math mode delimiters** (see below) +* **Automatic line wrapping** (see Automatic Line Wrapping section) + +### LaTeX Math Delimiters + +`SwiftMath` now supports all standard LaTeX math delimiters for both inline and display modes. The parser automatically detects and handles these delimiters: + +#### Inline Math (Text Style) +Use these delimiters for inline math within text, which renders more compactly: + +```swift +// Dollar signs (TeX style) +label.latex = "$E = mc^2$" + +// Parentheses (LaTeX style) +label.latex = "\\(\\sum_{i=1}^{n} x_i\\)" + +// Cases environment in inline mode +label.latex = "\\(\\begin{cases} x + y = 5 \\\\ 2x - y = 1 \\end{cases}\\)" +``` + +#### Display Math (Display Style) +Use these delimiters for standalone equations with larger operators and limits: + +```swift +// Double dollar signs (TeX style) +label.latex = "$$\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$" + +// Square brackets (LaTeX style) +label.latex = "\\[\\sum_{k=1}^{n} k^2 = \\frac{n(n+1)(2n+1)}{6}\\]" + +// Equation environment +label.latex = "\\begin{equation} x^2 + y^2 = z^2 \\end{equation}" + +// Cases environment in display mode +label.latex = "\\begin{cases} x + y = 5 \\\\ 2x - y = 1 \\end{cases}" +``` + +**Note:** The difference between inline and display modes: +- **Inline mode** (`$...$` or `\(...\)`) renders compactly, suitable for math within text +- **Display mode** (`$$...$$`, `\[...\]`, or environments) renders with larger operators and limits positioned above/below + +All delimiters are automatically stripped during parsing, and the math mode is set appropriately. No additional configuration is needed! + +#### Backward Compatibility +Equations without explicit delimiters continue to work as before, defaulting to display mode: + +```swift +label.latex = "x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}" // Works as always +``` + +#### Programmatic API +For advanced use cases where you need to parse LaTeX and determine the detected style programmatically, use the `buildWithStyle` method: + +```swift +// Parse LaTeX and get both the math list and detected style +let (mathList, style) = MTMathListBuilder.buildWithStyle(fromString: "\\[x^2 + y^2 = z^2\\]") + +// style will be .display for \[...\] or $$...$$ +// style will be .text for \(...\) or $...$ + +// Create a display with the detected style +if let mathList = mathList { + let display = MTTypesetter.createLineForMathList(mathList, font: myFont, style: style) + // Use the display for rendering +} +``` + +This is particularly useful when building custom renderers or when you need to respect the user's choice of delimiter style. + +Note: SwiftMath only supports the commands in LaTeX's math mode. There is +also no language support for other than west European langugages and some +Cyrillic characters. There would be two ways to support more languages: + +1) Find a math font compatible with `SwiftMath` that contains all the glyphs +for that language. +2) Add support to `SwiftMath` for standard Unicode fonts that contain all +langauge glyphs. + +Of these two, the first is much easier. However, if you want a challenge, +try to tackle the second option. + +### Example + +The [SwiftMathDemo](https://github.com/mgriebling/SwiftMathDemo) is a SwiftUI version +of the Objective-C demo included in `iosMath` that uses `SwiftMath` as a Swift package dependency. + +### Advanced configuration + +`MTMathUILabel` supports some advanced configuration options: + +##### Math mode + +You can change the mode of the `MTMathUILabel` between Display Mode +(equivalent to `$$` or `\[` in LaTeX) and Text Mode (equivalent to `$` +or `\(` in LaTeX). The default style is Display. To switch to Text +simply: + +```swift +label.labelMode = .text +``` + +##### Text Alignment +The default alignment of the equations is left. This can be changed to +center or right as follows: + +```swift +label.textAlignment = .center +``` + +##### Font size +The default font-size is 30pt. You can change it as follows: + +```swift +label.fontSize = 25 +``` +##### Font +The default font is *Latin Modern Math*. This can be changed as: + +```swift +label.font = MTFontManager.fontmanager.termesFont(withSize:20) +``` + +This project has 12 fonts bundled with it, but you can use any OTF math +font. A python script is included that generates the `.plist` files +required for an `.otf` font to work with `SwiftMath`. If you generate +(and test) any other fonts please contribute them back to this project for +others to benefit. + + + +Note: The `KpMath-Light`, `KpMath-Sans`, `Asana` fonts currently incorrectly +render very large radicals. It appears that the font files do +not properly define the offsets required to typeset these glyphs. If +anyone can fix this, it would be greatly appreciated. + +##### Text Color +The default color of the rendered equation is black. You can change +it to any other color as follows: + +```swift +label.textColor = .red +``` + +It is also possible to set different colors for different parts of the +equation. Just access the `displayList` field and set the `textColor` +of the underlying displays of which you want to change the color. + +##### Fallback Font for Unicode Text +By default, math fonts only support a limited set of characters (Latin, Greek, common math symbols). +To display other Unicode characters like Chinese, Japanese, Korean, emoji, or other scripts in `\text{}` +commands, you can configure a fallback font: + +```swift +let mathFont = MTFontManager().font(withName: MathFont.latinModernFont.rawValue, size: 30) + +// Set a fallback font for unsupported characters (defaults to nil) +#if os(iOS) || os(visionOS) +let systemFont = UIFont.systemFont(ofSize: 30) +mathFont?.fallbackFont = CTFontCreateWithName(systemFont.fontName as CFString, 30, nil) +#elseif os(macOS) +let systemFont = NSFont.systemFont(ofSize: 30) +mathFont?.fallbackFont = CTFontCreateWithName(systemFont.fontName as CFString, 30, nil) +#endif + +label.font = mathFont +label.latex = "\\text{Hello 世界 🌍}" // English, Chinese, and emoji +``` + +When the main math font doesn't contain a glyph for a character, the fallback font will be used automatically. +This is particularly useful for: +- Chinese text: `\text{中文}` +- Japanese text: `\text{日本語}` +- Korean text: `\text{한국어}` +- Emoji: `\text{Math is fun! 🎉📐}` +- Mixed scripts: `\text{Equation: 方程式}` + +**Note**: The fallback font only applies to characters within `\text{}` commands, not regular math mode. + +##### Custom Commands +You can define your own commands that are not already predefined. This is +similar to macros is LaTeX. To define your own command use: + +```swift +MTMathAtomFactory.addLatexSymbol("lcm", value: MTMathAtomFactory.operator(withName: "lcm", limits: false)) +``` + +This creates an `\lcm` command that can be used in the LaTeX. + +##### Content Insets +The `MTMathUILabel` has `contentInsets` for finer control of placement of the +equation in relation to the view. + +If you need to set it you can do as follows: + +```swift +label.contentInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 20) +``` + +##### Error handling + +If the LaTeX text given to `MTMathUILabel` is +invalid or if it contains commands that aren't currently supported then +an error message will be displayed instead of the label. + +This error can be programmatically retrieved as `label.error`. If you +prefer not to display anything then set: + +```swift +label.displayErrorInline = true +``` + +## Future Enhancements + +Note this is not a complete implementation of LaTeX math mode. There are +some pieces that are missing and may be included in future updates: + +* `\middle` delimiter for use between `\left` and `\right` +* Some fine spacing commands (`\:`, `\;`, `\!` - note that `\,` works) + +For a complete list of features and their implementation status, see [MISSING_FEATURES.md](MISSING_FEATURES.md). + +## License + +`SwiftMath` is available under the MIT license. See the [LICENSE](./LICENSE) +file for more info. + +### Fonts +This distribution contains the following fonts. These fonts are +licensed as follows: +* Latin Modern Math: + [GUST Font License](GUST-FONT-LICENSE.txt) +* Tex Gyre Termes: + [GUST Font License](GUST-FONT-LICENSE.txt) +* [XITS Math](https://github.com/khaledhosny/xits-math): + [Open Font License](OFL.txt) +* [KpMath Light/KpMath Sans](http://scripts.sil.org/OFL): + [SIL Open Font License](OFL.txt) diff --git a/third-party/SwiftMath/Sources/MathBundle/MTFontMathTableV2.swift b/third-party/SwiftMath/Sources/MathBundle/MTFontMathTableV2.swift new file mode 100755 index 0000000000..beb2320aa0 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathBundle/MTFontMathTableV2.swift @@ -0,0 +1,194 @@ +// +// MTFontMathTableV2.swift +// +// +// Created by Peter Tang on 15/9/2023. +// + +import Foundation +import CoreGraphics +import CoreText + +internal class MTFontMathTableV2: MTFontMathTable { + private let mathFont: MathFont + private let fontSize: CGFloat + private let unitsPerEm: UInt + private let mTable: NSDictionary + init(mathFont: MathFont, size: CGFloat, unitsPerEm: UInt) { + self.mathFont = mathFont + self.fontSize = size + self.unitsPerEm = unitsPerEm + mTable = mathFont.rawMathTable() + super.init(withFont: mathFont.mtfont(size: fontSize), mathTable: mTable) + super._mathTable = nil + // disable all possible access to _mathTable in superclass! + } + override var _mathTable: NSDictionary? { + set { fatalError("\(#function) change to _mathTable \(mathFont.rawValue) not allowed.") } + get { mTable } + } + override var muUnit: CGFloat { fontSize/18 } + + override func fontUnitsToPt(_ fontUnits:Int) -> CGFloat { + CGFloat(fontUnits) * fontSize / CGFloat(unitsPerEm) + } + override func constantFromTable(_ constName: String) -> CGFloat { + guard let consts = mTable[kConstants] as? NSDictionary, let val = consts[constName] as? NSNumber else { + return .zero + } + return fontUnitsToPt(val.intValue) + } + override func percentFromTable(_ percentName: String) -> CGFloat { + guard let consts = mTable[kConstants] as? NSDictionary, let val = consts[percentName] as? NSNumber else { + return .zero + } + return CGFloat(val.floatValue) / 100 + } + /** Returns an Array of all the vertical variants of the glyph if any. If + there are no variants for the glyph, the array contains the given glyph. */ + override func getVerticalVariantsForGlyph(_ glyph: CGGlyph) -> [NSNumber?] { + guard let variants = mTable[kVertVariants] as? NSDictionary else { return [] } + return self.getVariantsForGlyph(glyph, inDictionary: variants) + } + /** Returns an Array of all the horizontal variants of the glyph if any. If + there are no variants for the glyph, the array contains the given glyph. */ + override func getHorizontalVariantsForGlyph(_ glyph: CGGlyph) -> [NSNumber?] { + guard let variants = mTable[kHorizVariants] as? NSDictionary else { return [] } + return self.getVariantsForGlyph(glyph, inDictionary:variants) + } + override func getVariantsForGlyph(_ glyph: CGGlyph, inDictionary variants: NSDictionary) -> [NSNumber?] { + let font = mathFont.mtfont(size: fontSize) + let glyphName = font.get(nameForGlyph: glyph) + + var glyphArray = [NSNumber]() + let variantGlyphs = variants[glyphName] as? NSArray + + guard let variantGlyphs = variantGlyphs, variantGlyphs.count != .zero else { + // There are no extra variants, so just add the current glyph to it. + let glyph = font.get(glyphWithName: glyphName) + glyphArray.append(NSNumber(value:glyph)) + return glyphArray + } + for gvn in variantGlyphs { + if let glyphVariantName = gvn as? String { + let variantGlyph = font.get(glyphWithName: glyphVariantName) + glyphArray.append(NSNumber(value:variantGlyph)) + } + } + return glyphArray + } + /** Returns a larger vertical variant of the given glyph if any. + If there is no larger version, this returns the current glyph. + + - Parameter glyph: The glyph to find a larger variant for + - Parameter forDisplayStyle: If true, selects the largest appropriate variant for display style. + If false, selects the next larger variant (incremental sizing). + - Returns: A larger glyph variant, or the original glyph if no variants exist + */ + override func getLargerGlyph(_ glyph: CGGlyph, forDisplayStyle: Bool = false) -> CGGlyph { + let font = mathFont.mtfont(size: fontSize) + let glyphName = font.get(nameForGlyph: glyph) + + guard let variants = mTable[kVertVariants] as? NSDictionary, + let variantGlyphs = variants[glyphName] as? NSArray, variantGlyphs.count != .zero else { + // There are no extra variants, so just returnt the current glyph. + return glyph + } + + if forDisplayStyle { + // For display style, select a large variant suitable for mathematical display mode + // Display integrals should be significantly larger (~2.2em) for visual prominence + let count = variantGlyphs.count + + // Strategy: Use the largest variant, but avoid extreme sizes for fonts with many variants + let targetIndex: Int + if count <= 2 { + // Small variant list: use the last one (e.g., integral.size1 at ~2.2em) + targetIndex = count - 1 + } else if count <= 4 { + // Medium variant list: use second-to-last to avoid extremes + targetIndex = count - 2 + } else { + // Large variant list (like texgyretermes with 6 variants): + // Use variant at ~60% position to get appropriate display size (~2.0em) + // For 7 variants (0-6), this gives index 4 + targetIndex = min(count - 2, Int(Double(count) * 0.6)) + } + + if let glyphVariantName = variantGlyphs[targetIndex] as? String { + let variantGlyph = font.get(glyphWithName: glyphVariantName) + return variantGlyph + } + } else { + // Text/inline style: use incremental sizing for moderate enlargement + // Find the first variant with a different name + for gvn in variantGlyphs { + if let glyphVariantName = gvn as? String, glyphVariantName != glyphName { + let variantGlyph = font.get(glyphWithName: glyphVariantName) + return variantGlyph + } + } + } + + // We did not find any variants of this glyph so return it. + return glyph + } + /** Returns the italic correction for the given glyph if any. If there + isn't any this returns 0. */ + override func getItalicCorrection(_ glyph: CGGlyph) -> CGFloat { + let font = mathFont.mtfont(size: fontSize) + let glyphName = font.get(nameForGlyph: glyph) + + guard let italics = mTable[kItalic] as? NSDictionary, let val = italics[glyphName] as? NSNumber else { + return .zero + } + // if val is nil, this returns 0. + return fontUnitsToPt(val.intValue) + } + override func getTopAccentAdjustment(_ glyph: CGGlyph) -> CGFloat { + let font = mathFont.mtfont(size: fontSize) + let glyphName = font.get(nameForGlyph: glyph) + + guard let accents = mTable[kAccents] as? NSDictionary, let val = accents[glyphName] as? NSNumber else { + // If no top accent is defined then it is the center of the advance width. + var glyph = glyph + var advances = CGSize.zero + CTFontGetAdvancesForGlyphs(font.ctFont, .horizontal, &glyph, &advances, 1) + return advances.width/2 + } + return fontUnitsToPt(val.intValue) + } + override func getVerticalGlyphAssembly(forGlyph glyph: CGGlyph) -> [GlyphPart] { + let font = mathFont.mtfont(size: fontSize) + let glyphName = font.get(nameForGlyph: glyph) + + guard let assemblyTable = mTable[kVertAssembly] as? NSDictionary, + let assemblyInfo = assemblyTable[glyphName] as? NSDictionary, + let parts = assemblyInfo[kAssemblyParts] as? NSArray else { + // No vertical assembly defined for glyph + // parts should always have been defined, but if it isn't return nil + return [] + } + + var rv = [GlyphPart]() + for part in parts { + guard let partInfo = part as? NSDictionary, + let adv = partInfo["advance"] as? NSNumber, + let end = partInfo["endConnector"] as? NSNumber, + let start = partInfo["startConnector"] as? NSNumber, + let ext = partInfo["extender"] as? NSNumber, + let glyphName = partInfo["glyph"] as? String else { continue } + let fullAdvance = fontUnitsToPt(adv.intValue) + let endConnectorLength = fontUnitsToPt(end.intValue) + let startConnectorLength = fontUnitsToPt(start.intValue) + let isExtender = ext.boolValue + let glyph = font.get(glyphWithName: glyphName) + let part = GlyphPart(glyph: glyph, fullAdvance: fullAdvance, + startConnectorLength: startConnectorLength, + endConnectorLength: endConnectorLength, + isExtender: isExtender) + rv.append(part) + } + return rv + } +} diff --git a/third-party/SwiftMath/Sources/MathBundle/MTFontV2.swift b/third-party/SwiftMath/Sources/MathBundle/MTFontV2.swift new file mode 100755 index 0000000000..14c09a063c --- /dev/null +++ b/third-party/SwiftMath/Sources/MathBundle/MTFontV2.swift @@ -0,0 +1,68 @@ +// +// MTFontV2.swift +// +// +// Created by Peter Tang on 15/9/2023. +// + +import Foundation +import CoreGraphics +import CoreText + +extension MathFont { + public func mtfont(size: CGFloat) -> MTFontV2 { + MTFontV2(font: self, size: size) + } +} +public final class MTFontV2: MTFont { + let font: MathFont + let size: CGFloat + private let _cgFont: CGFont + private let _ctFont: CTFont + private let unitsPerEm: UInt + private var _mathTab: MTFontMathTableV2? + init(font: MathFont = .latinModernFont, size: CGFloat) { + self.font = font + self.size = size + // MathFont cgfont and ctfont are fast & threadsafe, keep a local copy is cheaper than + // handling via NSLock + self._cgFont = font.cgFont() + self._ctFont = font.ctFont(withSize: size) + self.unitsPerEm = self._ctFont.unitsPerEm + super.init() + + super.defaultCGFont = nil + super.ctFont = nil + super.mathTable = nil + super.rawMathTable = nil + } + override var defaultCGFont: CGFont! { + set { fatalError("\(#function): change to \(font.fontName) not allowed.") } + get { _cgFont } + } + override var ctFont: CTFont! { + set { fatalError("\(#function): change to \(font.fontName) not allowed.") } + get { _ctFont } + } + private let mtfontV2LockOnMathTable = NSLock() + override var mathTable: MTFontMathTable? { + set { fatalError("\(#function): change to \(font.rawValue) not allowed.") } + get { + guard _mathTab == nil else { return _mathTab } + //Note: lazy _mathTab initialization is now threadsafe. + mtfontV2LockOnMathTable.lock() + defer { mtfontV2LockOnMathTable.unlock() } + if _mathTab == nil { + _mathTab = MTFontMathTableV2(mathFont: font, size: size, unitsPerEm: unitsPerEm) + } + return _mathTab + } + } + override var rawMathTable: NSDictionary? { + set { fatalError("\(#function): change to \(font.rawValue) not allowed.") } + get { fatalError("\(#function): access to \(font.rawValue) not allowed.") } + } + public override func copy(withSize size: CGFloat) -> MTFont { + MTFontV2(font: font, size: size) + } +} diff --git a/third-party/SwiftMath/Sources/MathBundle/MathFont.swift b/third-party/SwiftMath/Sources/MathBundle/MathFont.swift new file mode 100644 index 0000000000..650d8f6ee7 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathBundle/MathFont.swift @@ -0,0 +1,225 @@ +// +// MathFont.swift +// +// +// Created by Peter Tang on 10/9/2023. +// + +#if os(iOS) || os(visionOS) +import UIKit +#elseif os(macOS) +import AppKit +#endif + +/// Now available for everyone to use +public enum MathFont: String, CaseIterable, Identifiable { + + public var id: Self { self } // Makes things simpler for SwiftUI + + case latinModernFont = "latinmodern-math" + case kpMathLightFont = "KpMath-Light" + case kpMathSansFont = "KpMath-Sans" + case xitsFont = "xits-math" + case termesFont = "texgyretermes-math" + case asanaFont = "Asana-Math" + case eulerFont = "Euler-Math" + case firaFont = "FiraMath-Regular" + case notoSansFont = "NotoSansMath-Regular" + case libertinusFont = "LibertinusMath-Regular" + case garamondFont = "Garamond-Math" + case leteSansFont = "LeteSansMath" + + var fontFamilyName: String { + switch self { + case .latinModernFont: "Latin Modern Math" + case .kpMathLightFont: "KpMath" + case .kpMathSansFont: "KpMath" + case .xitsFont: "XITS Math" + case .termesFont: "TeX Gyre Termes Math" + case .asanaFont: "Asana Math" + case .eulerFont: "Euler Math" + case .firaFont: "Fira Math" + case .notoSansFont: "Noto Sans Math" + case .libertinusFont: "Libertinus Math" + case .garamondFont: "Garamond-Math" // PostScript name is "Garamond-Math", not "Garamond Math" + case .leteSansFont: "Lete Sans Math" + } + } + + var postScriptName: String { + switch self { + case .latinModernFont: "LatinModernMath-Regular" + case .kpMathLightFont: "KpMath-Light" + case .kpMathSansFont: "KpMath-Sans" + case .xitsFont: "XITSMath" + case .termesFont: "TeXGyreTermesMath-Regular" + case .asanaFont: "Asana-Math" + case .eulerFont: "Euler-Math" + case .firaFont: "FiraMath-Regular" + case .notoSansFont: "NotoSansMath-Regular" + case .libertinusFont: "LibertinusMath-Regular" + case .garamondFont: "Garamond-Math" + case .leteSansFont: "LeteSansMath" + } + } + + var fontName: String { self.rawValue } + + public func cgFont() -> CGFont { + BundleManager.manager.obtainCGFont(font: self) + } + public func ctFont(withSize size: CGFloat) -> CTFont { + BundleManager.manager.obtainCTFont(font: self, withSize: size) + } + internal func rawMathTable() -> NSDictionary { + BundleManager.manager.obtainRawMathTable(font: self) + } + + //Note: Below code are no longer supported, unable to tell if UIFont/NSFont is threadsafe, not used in SwiftMath. + // #if os(iOS) || os(visionOS) + // public func uiFont(withSize size: CGFloat) -> UIFont? { + // UIFont(name: fontName, size: size) + // } + // #endif + // #if os(macOS) + // public func nsFont(withSize size: CGFloat) -> NSFont? { + // NSFont(name: fontName, size: size) + // } + // #endif +} +internal extension CTFont { + /** The size of this font in points. */ + var fontSize: CGFloat { + CTFontGetSize(self) + } + var unitsPerEm: UInt { + return UInt(CTFontGetUnitsPerEm(self)) + } +} +private class BundleManager { + //Note: below should be lightweight and without threadsafe problem. + static internal let manager = BundleManager() + + private var cgFonts = [MathFont: CGFont]() + private var ctFonts = [CTFontSizePair: CTFont]() + private var rawMathTables = [MathFont: NSDictionary]() + + private let threadSafeQueue = DispatchQueue(label: "com.smartmath.mathfont.threadsafequeue", + qos: .userInitiated, + attributes: .concurrent) + + private func registerCGFont(mathFont: MathFont) throws { + guard let frameworkBundleURL = Bundle.main.url(forResource: "mathFonts", withExtension: "bundle"), + let resourceBundleURL = Bundle(url: frameworkBundleURL)?.path(forResource: mathFont.rawValue, ofType: "otf") else { + throw FontError.fontPathNotFound + } + guard let fontData = NSData(contentsOfFile: resourceBundleURL), let dataProvider = CGDataProvider(data: fontData) else { + throw FontError.invalidFontFile + } + guard let defaultCGFont = CGFont(dataProvider) else { + throw FontError.initFontError + } + + cgFonts[mathFont] = defaultCGFont + + /// This does not load the complete math font, it only has about half the glyphs of the full math font. + /// In particular it does not have the math italic characters which breaks our variable rendering. + /// So we first load a CGFont from the file and then convert it to a CTFont. + var errorRef: Unmanaged? = nil + guard CTFontManagerRegisterGraphicsFont(defaultCGFont, &errorRef) else { + throw FontError.registerFailed + } + let postsript = (defaultCGFont.postScriptName as? String) ?? "" + let cgfontName = (defaultCGFont.fullName as? String) ?? "" + let threadName = Thread.isMainThread ? "main" : "global" + debugPrint("mathFonts bundle resource: \(mathFont.rawValue), font: \(cgfontName), ps: \(postsript) registered on \(threadName).") + } + + private func registerMathTable(mathFont: MathFont) throws { + guard let frameworkBundleURL = Bundle.main.url(forResource: "mathFonts", withExtension: "bundle"), + let mathTablePlist = Bundle(url: frameworkBundleURL)?.url(forResource: mathFont.rawValue, withExtension:"plist") else { + throw FontError.fontPathNotFound + } + guard let rawMathTable = NSDictionary(contentsOf: mathTablePlist), + let version = rawMathTable["version"] as? String, + version == "1.3" else { + throw FontError.invalidMathTable + } + + rawMathTables[mathFont] = rawMathTable + + let threadName = Thread.isMainThread ? "main" : "global" + debugPrint("mathFonts bundle resource: \(mathFont.rawValue).plist registered on \(threadName).") + } + + private func onDemandRegistration(mathFont: MathFont) { + guard threadSafeQueue.sync(execute: { cgFonts[mathFont] }) == nil else { return } + // Note: resourceLoading is now serialized. + threadSafeQueue.sync(flags: .barrier, execute: { [weak self] in + if self?.cgFonts[mathFont] == nil { + do { + try BundleManager.manager.registerCGFont(mathFont: mathFont) + try BundleManager.manager.registerMathTable(mathFont: mathFont) + + } catch { + fatalError("MTMathFonts:\(#function) ondemand loading failed, mathFont \(mathFont.rawValue), reason \(error)") + } + } + }) + } + fileprivate func obtainCGFont(font: MathFont) -> CGFont { + onDemandRegistration(mathFont: font) + guard let cgFont = threadSafeQueue.sync(execute: { cgFonts[font] }) else { + fatalError("\(#function) unable to locate CGFont \(font.fontName)") + } + return cgFont + } + + fileprivate func obtainCTFont(font: MathFont, withSize size: CGFloat) -> CTFont { + onDemandRegistration(mathFont: font) + let fontSizePair = CTFontSizePair(font: font, size: size) + let ctFont = threadSafeQueue.sync(execute: { ctFonts[fontSizePair] }) + guard ctFont == nil else { return ctFont! } + guard let cgFont = threadSafeQueue.sync(execute: { cgFonts[font] }) else { + fatalError("\(#function) unable to locate CGFont \(font.fontName) to create CTFont") + } + //Note: ctfont creation and caching is now threadsafe. + guard threadSafeQueue.sync(execute: { ctFonts[fontSizePair] }) == nil else { return ctFonts[fontSizePair]! } + return threadSafeQueue.sync(flags: .barrier, execute: { + if let ctfont = ctFonts[fontSizePair] { + return ctfont + } else { + let result = CTFontCreateWithGraphicsFont(cgFont, size, nil, nil) + ctFonts[fontSizePair] = result + return result + } + }) + } + fileprivate func obtainRawMathTable(font: MathFont) -> NSDictionary { + onDemandRegistration(mathFont: font) + guard let mathTable = threadSafeQueue.sync(execute: { rawMathTables[font] } ) else { + fatalError("\(#function) unable to locate mathTable: \(font.rawValue).plist") + } + return mathTable + } + deinit { + ctFonts.removeAll() + var errorRef: Unmanaged? = nil + cgFonts.values.forEach { cgFont in + CTFontManagerUnregisterGraphicsFont(cgFont, &errorRef) + } + cgFonts.removeAll() + } + public enum FontError: Error { + case invalidFontFile + case fontPathNotFound + case initFontError + case registerFailed + case invalidMathTable + } + + private struct CTFontSizePair: Hashable { + let font: MathFont + let size: CGFloat + } +} diff --git a/third-party/SwiftMath/Sources/MathBundle/MathImage.swift b/third-party/SwiftMath/Sources/MathBundle/MathImage.swift new file mode 100755 index 0000000000..89a06cb5eb --- /dev/null +++ b/third-party/SwiftMath/Sources/MathBundle/MathImage.swift @@ -0,0 +1,123 @@ +// +// MathImage.swift +// +// +// Created by Peter Tang on 15/9/2023. +// + +import Foundation + +#if os(iOS) || os(visionOS) + import UIKit +#elseif os(macOS) + import AppKit +#endif + +public struct MathImage { + public var font: MathFont = .latinModernFont + public var fontSize: CGFloat + public var textColor: MTColor + + public var labelMode: MTMathUILabelMode + public var textAlignment: MTTextAlignment + + public var contentInsets: MTEdgeInsets = MTEdgeInsetsZero + + public let latex: String + + private(set) var intrinsicContentSize = CGSize.zero + + public init(latex: String, fontSize: CGFloat, textColor: MTColor, labelMode: MTMathUILabelMode = .display, textAlignment: MTTextAlignment = .center) { + self.latex = latex + self.fontSize = fontSize + self.textColor = textColor + self.labelMode = labelMode + self.textAlignment = textAlignment + } +} +extension MathImage { + public var currentStyle: MTLineStyle { + switch labelMode { + case .display: return .display + case .text: return .text + } + } + private func intrinsicContentSize(_ displayList: MTMathListDisplay) -> CGSize { + CGSize(width: displayList.width + contentInsets.left + contentInsets.right, + height: displayList.ascent + displayList.descent + contentInsets.top + contentInsets.bottom) + } + public struct LayoutInfo { + public var ascent: CGFloat = 0 + public var descent: CGFloat = 0 + + public init(ascent: CGFloat, descent: CGFloat) { + self.ascent = ascent + self.descent = descent + } + } + public mutating func asImage() -> (NSError?, MTImage?, LayoutInfo?) { + func layoutImage(size: CGSize, displayList: MTMathListDisplay) { + var textX = CGFloat(0) + switch self.textAlignment { + case .left: textX = contentInsets.left + case .center: textX = (size.width - contentInsets.left - contentInsets.right - displayList.width) / 2 + contentInsets.left + case .right: textX = size.width - displayList.width - contentInsets.right + } + let availableHeight = size.height - contentInsets.bottom - contentInsets.top + + // center things vertically + var height = displayList.ascent + displayList.descent + if height < fontSize/2 { + height = fontSize/2 // set height to half the font size + } + let textY = (availableHeight - height) / 2 + displayList.descent + contentInsets.bottom + displayList.position = CGPoint(x: textX, y: textY) + } + var error: NSError? + let mtfont: MTFont? = font.mtfont(size: fontSize) + + guard let mathList = MTMathListBuilder.build(fromString: latex, error: &error), error == nil, + let displayList = MTTypesetter.createLineForMathList(mathList, font: mtfont, style: currentStyle) else { + return (error, nil, nil) + } + + intrinsicContentSize = intrinsicContentSize(displayList) + displayList.textColor = textColor + + let size = intrinsicContentSize.regularized + layoutImage(size: size, displayList: displayList) + + #if os(iOS) || os(visionOS) + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { rendererContext in + rendererContext.cgContext.saveGState() + rendererContext.cgContext.concatenate(.flippedVertically(size.height)) + displayList.draw(rendererContext.cgContext) + rendererContext.cgContext.restoreGState() + } + return (nil, image, LayoutInfo(ascent: displayList.ascent, descent: displayList.descent)) + #endif + #if os(macOS) + let image = NSImage(size: size, flipped: false) { bounds in + guard let context = NSGraphicsContext.current?.cgContext else { return false } + context.saveGState() + displayList.draw(context) + context.restoreGState() + return true + } + return (nil, image, LayoutInfo(ascent: displayList.ascent, descent: displayList.descent)) + #endif + } +} +private extension CGAffineTransform { + static func flippedVertically(_ height: CGFloat) -> CGAffineTransform { + var transform = CGAffineTransform(scaleX: 1, y: -1) + transform = transform.translatedBy(x: 0, y: -height) + return transform + } +} +extension CGSize { + fileprivate var regularized: CGSize { + CGSize(width: ceil(width), height: ceil(height)) + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTBezierPath.swift b/third-party/SwiftMath/Sources/MathRender/MTBezierPath.swift new file mode 100755 index 0000000000..fb5e082a89 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTBezierPath.swift @@ -0,0 +1,35 @@ + +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by 安志钢. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +#if os(macOS) + +extension MTBezierPath { + func addLine(to point: CGPoint) { + self.line(to: point) + } +} + +extension MTView { + + var backgroundColor:MTColor? { + get { + MTColor(cgColor: self.layer?.backgroundColor ?? MTColor.clear.cgColor) + } + set { + self.layer?.backgroundColor = MTColor.clear.cgColor + self.wantsLayer = true + } + } + +} + +#endif + diff --git a/third-party/SwiftMath/Sources/MathRender/MTColor.swift b/third-party/SwiftMath/Sources/MathRender/MTColor.swift new file mode 100755 index 0000000000..88f8a0d24a --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTColor.swift @@ -0,0 +1,28 @@ + +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Markus Sähn. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +extension MTColor { + + public convenience init?(fromHexString hexString:String) { + if hexString.isEmpty { return nil } + if !hexString.hasPrefix("#") { return nil } + + var rgbValue = UInt64(0) + let scanner = Scanner(string: hexString) + scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") + scanner.scanHexInt64(&rgbValue) + self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16)/255.0, + green: CGFloat((rgbValue & 0xFF00) >> 8)/255.0, + blue: CGFloat((rgbValue & 0xFF))/255.0, + alpha: 1.0) + } + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTConfig.swift b/third-party/SwiftMath/Sources/MathRender/MTConfig.swift new file mode 100755 index 0000000000..19f339bf69 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTConfig.swift @@ -0,0 +1,40 @@ +import Foundation + +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by 安志钢. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +#if os(iOS) || os(visionOS) + +import UIKit + +public typealias MTView = UIView +public typealias MTColor = UIColor +public typealias MTBezierPath = UIBezierPath +public typealias MTLabel = UILabel +public typealias MTEdgeInsets = UIEdgeInsets +public typealias MTRect = CGRect +public typealias MTImage = UIImage + +let MTEdgeInsetsZero = UIEdgeInsets.zero +func MTGraphicsGetCurrentContext() -> CGContext? { UIGraphicsGetCurrentContext() } + +#else + +import AppKit + +public typealias MTView = NSView +public typealias MTColor = NSColor +public typealias MTBezierPath = NSBezierPath +public typealias MTEdgeInsets = NSEdgeInsets +public typealias MTRect = NSRect +public typealias MTImage = NSImage + +let MTEdgeInsetsZero = NSEdgeInsets.init(top: 0, left: 0, bottom: 0, right: 0) +func MTGraphicsGetCurrentContext() -> CGContext? { NSGraphicsContext.current?.cgContext } + +#endif diff --git a/third-party/SwiftMath/Sources/MathRender/MTFont.swift b/third-party/SwiftMath/Sources/MathRender/MTFont.swift new file mode 100644 index 0000000000..e1e418d821 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTFont.swift @@ -0,0 +1,81 @@ + +import Foundation +import CoreText +import AppBundle + +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +public class MTFont { + + var defaultCGFont: CGFont! + var ctFont: CTFont! + var mathTable: MTFontMathTable? + var rawMathTable: NSDictionary? + + /// Fallback font for characters not supported by the main math font. + /// Defaults to the system font at the same size. This is particularly useful + /// for rendering text in \text{} commands with characters outside the math font's coverage + /// (e.g., Chinese, Japanese, Korean, emoji, etc.) + public var fallbackFont: CTFont? + + init() {} + + /// `MTFont(fontWithName:)` does not load the complete math font, it only has about half the glyphs of the full math font. + /// In particular it does not have the math italic characters which breaks our variable rendering. + /// So we first load a CGFont from the file and then convert it to a CTFont. + convenience init(fontWithName name: String, size:CGFloat) { + self.init() + //print("Loading font \(name)") + let bundle = getAppBundle() + let fontPath = bundle.path(forResource: name, ofType: "otf") + let fontDataProvider = CGDataProvider(filename: fontPath!) + self.defaultCGFont = CGFont(fontDataProvider!)! + //print("Num glyphs: \(self.defaultCGFont.numberOfGlyphs)") + + self.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil); + + //print("Loading associated .plist") + let mathTablePlist = bundle.url(forResource:name, withExtension:"plist") + self.rawMathTable = NSDictionary(contentsOf: mathTablePlist!) + self.mathTable = MTFontMathTable(withFont:self, mathTable:rawMathTable!) + } + + static var fontBundle:Bundle { + // Uses bundle for class so that this can be access by the unit tests. + Bundle(url: Bundle.main.url(forResource: "mathFonts", withExtension: "bundle")!)! + } + + /** Returns a copy of this font but with a different size. */ + public func copy(withSize size: CGFloat) -> MTFont { + let newFont = MTFont() + newFont.defaultCGFont = self.defaultCGFont + newFont.ctFont = CTFontCreateWithGraphicsFont(self.defaultCGFont, size, nil, nil) + newFont.rawMathTable = self.rawMathTable + newFont.mathTable = MTFontMathTable(withFont: newFont, mathTable: newFont.rawMathTable!) + return newFont + } + + func get(nameForGlyph glyph:CGGlyph) -> String { + let name = defaultCGFont.name(for: glyph) as? String + return name ?? "" + } + + func get(glyphWithName name:String) -> CGGlyph { + defaultCGFont.getGlyphWithGlyphName(name: name as CFString) + } + + /** The size of this font in points. */ + public var fontSize:CGFloat { CTFontGetSize(self.ctFont) } + + deinit { + self.ctFont = nil + self.defaultCGFont = nil + } + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTFontManager.swift b/third-party/SwiftMath/Sources/MathRender/MTFontManager.swift new file mode 100755 index 0000000000..71f42cc477 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTFontManager.swift @@ -0,0 +1,93 @@ + +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +public class MTFontManager { + + static public private(set) var manager: MTFontManager = { + MTFontManager() + }() + + let kDefaultFontSize = CGFloat(20) + + static var fontManager : MTFontManager { + return manager + } + + public init() { } + + @RWLocked + var nameToFontMap = [String: MTFont]() + + public func font(withName name:String, size:CGFloat) -> MTFont? { + var f = self.nameToFontMap[name] + if f == nil { + f = MTFont(fontWithName: name, size: size) + self.nameToFontMap[name] = f + } + + if f!.fontSize == size { return f } + else { return f!.copy(withSize: size) } + } + + public func latinModernFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "latinmodern-math", size: size) + } + + public func kpMathLightFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "KpMath-Light", size: size) + } + + public func kpMathSansFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "KpMath-Sans", size: size) + } + + public func xitsFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "xits-math", size: size) + } + + public func termesFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "texgyretermes-math", size: size) + } + + public func asanaFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "Asana-Math", size: size) + } + + public func eulerFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "Euler-Math", size: size) + } + + public func firaRegularFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "FiraMath-Regular", size: size) + } + + public func notoSansRegularFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "NotoSansMath-Regular", size: size) + } + + public func libertinusRegularFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "LibertinusMath-Regular", size: size) + } + + public func garamondMathFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "Garamond-Math", size: size) + } + + public func leteSansFont(withSize size:CGFloat) -> MTFont? { + MTFontManager.fontManager.font(withName: "LeteSansMath", size: size) + } + + public var defaultFont: MTFont? { + MTFontManager.fontManager.latinModernFont(withSize: kDefaultFontSize) + } + + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTFontMathTable.swift b/third-party/SwiftMath/Sources/MathRender/MTFontMathTable.swift new file mode 100755 index 0000000000..35e252b1ad --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTFontMathTable.swift @@ -0,0 +1,355 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreText + +struct GlyphPart { + /// The glyph that represents this part + var glyph: CGGlyph! + + /// Full advance width/height for this part, in the direction of the extension in points. + var fullAdvance: CGFloat = 0 + + /// Advance width/ height of the straight bar connector material at the beginning of the glyph in points. + var startConnectorLength: CGFloat = 0 + + /// Advance width/ height of the straight bar connector material at the end of the glyph in points. + var endConnectorLength: CGFloat = 0 + + /// If this part is an extender. If set, the part can be skipped or repeated. + var isExtender: Bool = false +} + +/** This class represents the Math table of an open type font. + + The math table is documented here: https://www.microsoft.com/typography/otspec/math.htm + + How the constants in this class affect the display is documented here: + http://www.tug.org/TUGboat/tb30-1/tb94vieth.pdf + + Note: We don't parse the math table from the open type font. Rather we parse it + in python and convert it to a .plist file which is easily consumed by this class. + This approach is preferable to spending an inordinate amount of time figuring out + how to parse the returned NSData object using the open type rules. + + Remark: This class is not meant to be used outside of this library. + */ +class MTFontMathTable { + + // The font for this math table. + public private(set) weak var font:MTFont? // @property (nonatomic, readonly, weak) MTFont* font; + + var _unitsPerEm: UInt + var _fontSize: CGFloat + var _mathTable: NSDictionary! + + let kConstants = "constants" + + /** MU unit in points */ + var muUnit:CGFloat { _fontSize/18 } + + func fontUnitsToPt(_ fontUnits:Int) -> CGFloat { + CGFloat(fontUnits) * _fontSize / CGFloat(_unitsPerEm) + } + + init(withFont font: MTFont?, mathTable:NSDictionary) { + assert(font != nil, "font has nil value") + assert(font!.ctFont != nil, "font.ctFont has nil value") + self.font = font + // do domething with font + _unitsPerEm = UInt(CTFontGetUnitsPerEm(font!.ctFont)) + _fontSize = font!.fontSize; + _mathTable = mathTable + let version = _mathTable["version"] as! String + if version != "1.3" { + NSException(name: NSExceptionName.internalInconsistencyException, reason: "Invalid version of math table plist: \(version)").raise() + } + } + + func constantFromTable(_ constName:String) -> CGFloat { + let consts = _mathTable[kConstants] as! NSDictionary? + let val = consts![constName] as! NSNumber? + return fontUnitsToPt(val!.intValue) + } + + func percentFromTable(_ percentName:String) -> CGFloat { + let consts = _mathTable[kConstants] as! NSDictionary? + let val = consts![percentName] as! NSNumber? + return CGFloat(val!.floatValue) / 100 + } + + /// Math Font Metrics from the opentype specification + // MARK: - Fractions + var fractionNumeratorDisplayStyleShiftUp:CGFloat { constantFromTable("FractionNumeratorDisplayStyleShiftUp") } // \sigma_8 in TeX + var fractionNumeratorShiftUp:CGFloat { constantFromTable("FractionNumeratorShiftUp") } // \sigma_9 in TeX + var fractionDenominatorDisplayStyleShiftDown:CGFloat { constantFromTable("FractionDenominatorDisplayStyleShiftDown") } // \sigma_11 in TeX + var fractionDenominatorShiftDown:CGFloat { constantFromTable("FractionDenominatorShiftDown") } // \sigma_12 in TeX + var fractionNumeratorDisplayStyleGapMin:CGFloat { constantFromTable("FractionNumDisplayStyleGapMin") } // 3 * \xi_8 in TeX + var fractionNumeratorGapMin:CGFloat { constantFromTable("FractionNumeratorGapMin") } // \xi_8 in TeX + var fractionDenominatorDisplayStyleGapMin:CGFloat { constantFromTable("FractionDenomDisplayStyleGapMin") } // 3 * \xi_8 in TeX + var fractionDenominatorGapMin:CGFloat { constantFromTable("FractionDenominatorGapMin") } // \xi_8 in TeX + var fractionRuleThickness:CGFloat { constantFromTable("FractionRuleThickness") } // \xi_8 in TeX + var skewedFractionHorizonalGap:CGFloat { constantFromTable("SkewedFractionHorizontalGap") } // \sigma_20 in TeX + var skewedFractionVerticalGap:CGFloat { constantFromTable("SkewedFractionVerticalGap") } // \sigma_21 in TeX + + // MARK: - Non-standard + /// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants + /// specified in the OpenType Math specification. Rather these are proposed LuaTeX extensions + /// for the TeX parameters \sigma_20 (delim1) and \sigma_21 (delim2). Since these do not + /// exist in the fonts that we have, we use the same approach as LuaTeX and use the fontSize + /// to determine these values. The constants used match the metrics values of the original TeX fonts. + /// Note: An alternative approach is to use DelimitedSubFormulaMinHeight for \sigma21 and use a factor + /// of 2 to get \sigma 20 as proposed in Vieth paper. + /// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which + /// will produce smaller delimiters. + /// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately. + var fractionDelimiterSize: CGFloat { 1.01 * _fontSize } + + /// Modified constant from 2.4 to 2.39 for better visual appearance. + var fractionDelimiterDisplayStyleSize: CGFloat { 2.39 * _fontSize } + + // MARK: - Stacks + var stackTopDisplayStyleShiftUp:CGFloat { constantFromTable("StackTopDisplayStyleShiftUp") } // \sigma_8 in TeX + var stackTopShiftUp:CGFloat { constantFromTable("StackTopShiftUp") } // \sigma_10 in TeX + var stackDisplayStyleGapMin:CGFloat { constantFromTable("StackDisplayStyleGapMin") } // 7 \xi_8 in TeX + var stackGapMin:CGFloat { constantFromTable("StackGapMin") } // 3 \xi_8 in TeX + var stackBottomDisplayStyleShiftDown:CGFloat { constantFromTable("StackBottomDisplayStyleShiftDown") } // \sigma_11 in TeX + var stackBottomShiftDown:CGFloat { constantFromTable("StackBottomShiftDown") } // \sigma_12 in TeX + + var stretchStackBottomShiftDown:CGFloat { constantFromTable("StretchStackBottomShiftDown") } + var stretchStackGapAboveMin:CGFloat { constantFromTable("StretchStackGapAboveMin") } + var stretchStackGapBelowMin:CGFloat { constantFromTable("StretchStackGapBelowMin") } + var stretchStackTopShiftUp:CGFloat { constantFromTable("StretchStackTopShiftUp") } + + // MARK: - super/sub scripts + + var superscriptShiftUp:CGFloat { constantFromTable("SuperscriptShiftUp") } // \sigma_13, \sigma_14 in TeX + var superscriptShiftUpCramped:CGFloat { constantFromTable("SuperscriptShiftUpCramped") } // \sigma_15 in TeX + var subscriptShiftDown:CGFloat { constantFromTable("SubscriptShiftDown") } // \sigma_16, \sigma_17 in TeX + var superscriptBaselineDropMax:CGFloat { constantFromTable("SuperscriptBaselineDropMax") } // \sigma_18 in TeX + var subscriptBaselineDropMin:CGFloat { constantFromTable("SubscriptBaselineDropMin") } // \sigma_19 in TeX + var superscriptBottomMin:CGFloat { constantFromTable("SuperscriptBottomMin") } // 1/4 \sigma_5 in TeX + var subscriptTopMax:CGFloat { constantFromTable("SubscriptTopMax") } // 4/5 \sigma_5 in TeX + var subSuperscriptGapMin:CGFloat { constantFromTable("SubSuperscriptGapMin") } // 4 \xi_8 in TeX + var superscriptBottomMaxWithSubscript:CGFloat { constantFromTable("SuperscriptBottomMaxWithSubscript") } // 4/5 \sigma_5 in TeX + + var spaceAfterScript:CGFloat { constantFromTable("SpaceAfterScript") } + + // MARK: - radicals + var radicalExtraAscender:CGFloat { constantFromTable("RadicalExtraAscender") } // \xi_8 in Tex + var radicalRuleThickness:CGFloat { constantFromTable("RadicalRuleThickness") } // \xi_8 in Tex + var radicalDisplayStyleVerticalGap:CGFloat { constantFromTable("RadicalDisplayStyleVerticalGap") } // \xi_8 + 1/4 \sigma_5 in Tex + var radicalVerticalGap:CGFloat { constantFromTable("RadicalVerticalGap") } // 5/4 \xi_8 in Tex + var radicalKernBeforeDegree:CGFloat { constantFromTable("RadicalKernBeforeDegree") } // 5 mu in Tex + var radicalKernAfterDegree:CGFloat { constantFromTable("RadicalKernAfterDegree") } // -10 mu in Tex + var radicalDegreeBottomRaisePercent:CGFloat { percentFromTable("RadicalDegreeBottomRaisePercent") } // 60% in Tex + + // MARK: - Limits + var upperLimitBaselineRiseMin:CGFloat { constantFromTable("UpperLimitBaselineRiseMin") } // \xi_11 in TeX + var upperLimitGapMin:CGFloat { constantFromTable("UpperLimitGapMin") } // \xi_9 in TeX + var lowerLimitGapMin:CGFloat { constantFromTable("LowerLimitGapMin") } // \xi_10 in TeX + var lowerLimitBaselineDropMin:CGFloat { constantFromTable("LowerLimitBaselineDropMin") } // \xi_12 in TeX + var limitExtraAscenderDescender:CGFloat { 0 } // \xi_13 in TeX, not present in OpenType so we always set it to 0. + + // MARK: - Underline + var underbarVerticalGap:CGFloat { constantFromTable("UnderbarVerticalGap") } // 3 \xi_8 in TeX + var underbarRuleThickness:CGFloat { constantFromTable("UnderbarRuleThickness") } // \xi_8 in TeX + var underbarExtraDescender:CGFloat { constantFromTable("UnderbarExtraDescender") } // \xi_8 in TeX + + // MARK: - Overline + var overbarVerticalGap:CGFloat { constantFromTable("OverbarVerticalGap") } // 3 \xi_8 in TeX + var overbarRuleThickness:CGFloat { constantFromTable("OverbarRuleThickness") } // \xi_8 in TeX + var overbarExtraAscender:CGFloat { constantFromTable("OverbarExtraAscender") } // \xi_8 in TeX + + // MARK: - Constants + + var axisHeight:CGFloat { constantFromTable("AxisHeight") } // \sigma_22 in TeX + var scriptScaleDown:CGFloat { percentFromTable("ScriptPercentScaleDown") } + var scriptScriptScaleDown:CGFloat { percentFromTable("ScriptScriptPercentScaleDown") } + var mathLeading:CGFloat { constantFromTable("MathLeading") } + var delimitedSubFormulaMinHeight:CGFloat { constantFromTable("DelimitedSubFormulaMinHeight") } + + // MARK: - Accent + + var accentBaseHeight:CGFloat { constantFromTable("AccentBaseHeight") } // \fontdimen5 in TeX (x-height) + var flattenedAccentBaseHeight:CGFloat { constantFromTable("FlattenedAccentBaseHeight") } + + // MARK: - Variants + + let kVertVariants = "v_variants" + let kHorizVariants = "h_variants" + + /** Returns an Array of all the vertical variants of the glyph if any. If + there are no variants for the glyph, the array contains the given glyph. */ + func getVerticalVariantsForGlyph( _ glyph:CGGlyph) -> [NSNumber?] { + let variants = _mathTable[kVertVariants] as! NSDictionary? + return self.getVariantsForGlyph(glyph, inDictionary: variants!) + } + + /** Returns an Array of all the horizontal variants of the glyph if any. If + there are no variants for the glyph, the array contains the given glyph. */ + func getHorizontalVariantsForGlyph( _ glyph:CGGlyph) -> [NSNumber?] { + let variants = _mathTable[kHorizVariants] as! NSDictionary + return self.getVariantsForGlyph(glyph, inDictionary:variants) + } + + func getVariantsForGlyph(_ glyph: CGGlyph, inDictionary variants:NSDictionary) -> [NSNumber?] { + let glyphName = self.font!.get(nameForGlyph: glyph) + let variantGlyphs = variants[glyphName] as! NSArray? + var glyphArray = [NSNumber]() + if variantGlyphs == nil || variantGlyphs?.count == 0 { + // There are no extra variants, so just add the current glyph to it. + let glyph = self.font!.get(glyphWithName: glyphName) + glyphArray.append(NSNumber(value:glyph)) + return glyphArray + } + for gvn in variantGlyphs! { + let glyphVariantName = gvn as! String? + let variantGlyph = self.font?.get(glyphWithName: glyphVariantName!) + glyphArray.append(NSNumber(value:variantGlyph!)) + } + return glyphArray + } + + /** Returns a larger vertical variant of the given glyph if any. + If there is no larger version, this returns the current glyph. + + - Parameter glyph: The glyph to find a larger variant for + - Parameter forDisplayStyle: If true, selects the largest appropriate variant for display style. + If false, selects the next larger variant (incremental sizing). + - Returns: A larger glyph variant, or the original glyph if no variants exist + */ + func getLargerGlyph(_ glyph:CGGlyph, forDisplayStyle: Bool = false) -> CGGlyph { + let variants = _mathTable[kVertVariants] as! NSDictionary? + let glyphName = self.font?.get(nameForGlyph: glyph) + let variantGlyphs = variants![glyphName!] as! NSArray? + if variantGlyphs == nil || variantGlyphs?.count == 0 { + // There are no extra variants, so just returnt the current glyph. + return glyph + } + + if forDisplayStyle { + // For display style, select a large variant suitable for mathematical display mode + // Display integrals should be significantly larger (~2.2em) for visual prominence + let count = variantGlyphs!.count + + // Strategy: Use the largest variant, but avoid extreme sizes for fonts with many variants + let targetIndex: Int + if count <= 2 { + // Small variant list: use the last one (e.g., integral.size1 at ~2.2em) + targetIndex = count - 1 + } else if count <= 4 { + // Medium variant list: use second-to-last to avoid extremes + targetIndex = count - 2 + } else { + // Large variant list (like texgyretermes with 6 variants): + // Use variant at ~60% position to get appropriate display size (~2.0em) + // For 7 variants (0-6), this gives index 4 + targetIndex = min(count - 2, Int(Double(count) * 0.6)) + } + + if let glyphVariantName = variantGlyphs![targetIndex] as? String { + let variantGlyph = self.font?.get(glyphWithName: glyphVariantName) + return variantGlyph! + } + } else { + // Text/inline style: use incremental sizing for moderate enlargement + // Find the first variant with a different name + for gvn in variantGlyphs! { + let glyphVariantName = gvn as! String? + if glyphVariantName != glyphName { + let variantGlyph = self.font?.get(glyphWithName: glyphVariantName!) + return variantGlyph! + } + } + } + + // We did not find any variants of this glyph so return it. + return glyph; + } + + // MARK: - Italic Correction + + let kItalic = "italic" + + /** Returns the italic correction for the given glyph if any. If there + isn't any this returns 0. */ + func getItalicCorrection(_ glyph: CGGlyph) -> CGFloat { + let italics = _mathTable[kItalic] as! NSDictionary? + let glyphName = self.font?.get(nameForGlyph: glyph) + let val = italics![glyphName!] as! NSNumber? + // if val is nil, this returns 0. + return self.fontUnitsToPt(val?.intValue ?? 0) + } + + // MARK: - Accents + + let kAccents = "accents" + + /** Returns the adjustment to the top accent for the given glyph if any. + If there isn't any this returns -1. */ + func getTopAccentAdjustment(_ glyph: CGGlyph) -> CGFloat { + var glyph = glyph + let accents = _mathTable[kAccents] as! NSDictionary? + let glyphName = self.font?.get(nameForGlyph: glyph) + let val = accents![glyphName!] as! NSNumber? + if let val = val { + return self.fontUnitsToPt(val.intValue) + } else { + // If no top accent is defined then it is the center of the advance width. + var advances = CGSize.zero + CTFontGetAdvancesForGlyphs(self.font!.ctFont, .horizontal, &glyph, &advances, 1) + return advances.width/2 + } + } + + // MARK: - Glyph Construction + + /** Minimum overlap of connecting glyphs during glyph construction */ + var minConnectorOverlap:CGFloat { constantFromTable("MinConnectorOverlap") } + + let kVertAssembly = "v_assembly" + let kAssemblyParts = "parts" + + /** Returns an array of the glyph parts to be used for constructing vertical variants + of this glyph. If there is no glyph assembly defined, returns an empty array. */ + func getVerticalGlyphAssembly(forGlyph glyph:CGGlyph) -> [GlyphPart] { + let assemblyTable = _mathTable[kVertAssembly] as! NSDictionary? + let glyphName = self.font?.get(nameForGlyph: glyph) + let assemblyInfo = assemblyTable![glyphName!] as! NSDictionary? + if assemblyInfo == nil { + // No vertical assembly defined for glyph + return [] + } + let parts = assemblyInfo![kAssemblyParts] as! NSArray? + if parts == nil { + // parts should always have been defined, but if it isn't return nil + return [] + } + var rv = [GlyphPart]() + for part in parts! { + let partInfo = part as! NSDictionary? + var part = GlyphPart() + let adv = partInfo!["advance"] as! NSNumber? + part.fullAdvance = self.fontUnitsToPt(adv!.intValue) + let end = partInfo!["endConnector"] as! NSNumber? + part.endConnectorLength = self.fontUnitsToPt(end!.intValue) + let start = partInfo!["startConnector"] as! NSNumber? + part.startConnectorLength = self.fontUnitsToPt(start!.intValue) + let ext = partInfo!["extender"] as! NSNumber? + part.isExtender = ext!.boolValue + let glyphName = partInfo!["glyph"] as! String? + part.glyph = self.font?.get(glyphWithName: glyphName!) + rv.append(part) + } + return rv + } + + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTLabel.swift b/third-party/SwiftMath/Sources/MathRender/MTLabel.swift new file mode 100755 index 0000000000..8964a9027b --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTLabel.swift @@ -0,0 +1,37 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by 安志钢. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import SwiftUI + +#if os(macOS) + +public class MTLabel : NSTextField { + + init() { + super.init(frame: .zero) + self.stringValue = "" + self.isBezeled = false + self.drawsBackground = false + self.isEditable = false + self.isSelectable = false + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // MARK: - Customized getter and setter methods for property text. + var text:String? { + get { super.stringValue } + set { super.stringValue = newValue! } + } + +} + +#endif diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathAtomFactory.swift b/third-party/SwiftMath/Sources/MathRender/MTMathAtomFactory.swift new file mode 100644 index 0000000000..8d35a93dc3 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathAtomFactory.swift @@ -0,0 +1,1141 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +/** A factory to create commonly used MTMathAtoms. */ +public class MTMathAtomFactory { + + public static let aliases = [ + "lnot" : "neg", + "land" : "wedge", + "lor" : "vee", + "ne" : "neq", + "le" : "leq", + "ge" : "geq", + "lbrace" : "{", + "rbrace" : "}", + "Vert" : "|", + "gets" : "leftarrow", + "to" : "rightarrow", + "iff" : "Longleftrightarrow", + "AA" : "angstrom" + ] + + public static let delimiters = [ + "." : "", // . means no delimiter + "(" : "(", + ")" : ")", + "[" : "[", + "]" : "]", + "<" : "\u{2329}", + ">" : "\u{232A}", + "/" : "/", + "\\" : "\\", + "|" : "|", + "lgroup" : "\u{27EE}", + "rgroup" : "\u{27EF}", + "||" : "\u{2016}", + "Vert" : "\u{2016}", + "vert" : "|", + "uparrow" : "\u{2191}", + "downarrow" : "\u{2193}", + "updownarrow" : "\u{2195}", + "Uparrow" : "\u{21D1}", + "Downarrow" : "\u{21D3}", + "Updownarrow" : "\u{21D5}", + "backslash" : "\\", + "rangle" : "\u{232A}", + "langle" : "\u{2329}", + "rbrace" : "}", + "}" : "}", + "{" : "{", + "lbrace" : "{", + "lceil" : "\u{2308}", + "rceil" : "\u{2309}", + "lfloor" : "\u{230A}", + "rfloor" : "\u{230B}", + // Corner brackets (amssymb) + "ulcorner" : "\u{231C}", // upper left corner + "urcorner" : "\u{231D}", // upper right corner + "llcorner" : "\u{231E}", // lower left corner + "lrcorner" : "\u{231F}", // lower right corner + // Double square brackets (strachey brackets) + "llbracket" : "\u{27E6}", // left double bracket + "rrbracket" : "\u{27E7}", // right double bracket + ] + + private static let delimValueLock = NSLock() + static var _delimValueToName = [String: String]() + public static var delimValueToName: [String: String] { + if _delimValueToName.isEmpty { + var output = [String: String]() + for (key, value) in Self.delimiters { + if let existingValue = output[value] { + if key.count > existingValue.count { + continue + } else if key.count == existingValue.count { + if key.compare(existingValue) == .orderedDescending { + continue + } + } + } + output[value] = key + } + // protect lazily loading table in a multi-thread concurrent environment + delimValueLock.lock() + defer { delimValueLock.unlock() } + if _delimValueToName.isEmpty { + _delimValueToName = output + } + } + return _delimValueToName + } + + public static let accents = [ + "grave" : "\u{0300}", + "acute" : "\u{0301}", + "hat" : "\u{0302}", // In our implementation hat and widehat behave the same. + "tilde" : "\u{0303}", // In our implementation tilde and widetilde behave the same. + "bar" : "\u{0304}", + "breve" : "\u{0306}", + "dot" : "\u{0307}", + "ddot" : "\u{0308}", + "check" : "\u{030C}", + "vec" : "\u{20D7}", + "widehat" : "\u{0302}", + "widetilde" : "\u{0303}", + "overleftarrow" : "\u{20D6}", // Combining left arrow above + "overrightarrow" : "\u{20D7}", // Combining right arrow above (same as vec) + "overleftrightarrow" : "\u{20E1}" // Combining left right arrow above + ] + + private static let accentValueLock = NSLock() + static var _accentValueToName: [String: String]? = nil + public static var accentValueToName: [String: String] { + if _accentValueToName == nil { + var output = [String: String]() + + for (key, value) in Self.accents { + if let existingValue = output[value] { + if key.count > existingValue.count { + continue + } else if key.count == existingValue.count { + if key.compare(existingValue) == .orderedDescending { + continue + } + } + } + output[value] = key + } + // protect lazily loading table in a multi-thread concurrent environment + accentValueLock.lock() + defer { accentValueLock.unlock() } + if _accentValueToName == nil { + _accentValueToName = output + } + } + return _accentValueToName! + } + + static var supportedLatexSymbolNames:[String] { + let commands = MTMathAtomFactory.supportedLatexSymbols + return commands.keys.map { String($0) } + } + + static var supportedLatexSymbols: [String: MTMathAtom] = [ + "square" : MTMathAtomFactory.placeholder(), + + // Greek characters + "alpha" : MTMathAtom(type: .variable, value: "\u{03B1}"), + "beta" : MTMathAtom(type: .variable, value: "\u{03B2}"), + "gamma" : MTMathAtom(type: .variable, value: "\u{03B3}"), + "delta" : MTMathAtom(type: .variable, value: "\u{03B4}"), + "varepsilon" : MTMathAtom(type: .variable, value: "\u{03B5}"), + "zeta" : MTMathAtom(type: .variable, value: "\u{03B6}"), + "eta" : MTMathAtom(type: .variable, value: "\u{03B7}"), + "theta" : MTMathAtom(type: .variable, value: "\u{03B8}"), + "iota" : MTMathAtom(type: .variable, value: "\u{03B9}"), + "kappa" : MTMathAtom(type: .variable, value: "\u{03BA}"), + "lambda" : MTMathAtom(type: .variable, value: "\u{03BB}"), + "mu" : MTMathAtom(type: .variable, value: "\u{03BC}"), + "nu" : MTMathAtom(type: .variable, value: "\u{03BD}"), + "xi" : MTMathAtom(type: .variable, value: "\u{03BE}"), + "omicron" : MTMathAtom(type: .variable, value: "\u{03BF}"), + "pi" : MTMathAtom(type: .variable, value: "\u{03C0}"), + "rho" : MTMathAtom(type: .variable, value: "\u{03C1}"), + "varsigma" : MTMathAtom(type: .variable, value: "\u{03C2}"), + "sigma" : MTMathAtom(type: .variable, value: "\u{03C3}"), + "tau" : MTMathAtom(type: .variable, value: "\u{03C4}"), + "upsilon" : MTMathAtom(type: .variable, value: "\u{03C5}"), + "varphi" : MTMathAtom(type: .variable, value: "\u{03C6}"), + "chi" : MTMathAtom(type: .variable, value: "\u{03C7}"), + "psi" : MTMathAtom(type: .variable, value: "\u{03C8}"), + "omega" : MTMathAtom(type: .variable, value: "\u{03C9}"), + // We mark the following greek chars as ordinary so that we don't try + // to automatically italicize them as we do with variables. + // These characters fall outside the rules of italicization that we have defined. + "epsilon" : MTMathAtom(type: .ordinary, value: "\u{0001D716}"), + "vartheta" : MTMathAtom(type: .ordinary, value: "\u{0001D717}"), + "phi" : MTMathAtom(type: .ordinary, value: "\u{0001D719}"), + "varrho" : MTMathAtom(type: .ordinary, value: "\u{0001D71A}"), + "varpi" : MTMathAtom(type: .ordinary, value: "\u{0001D71B}"), + "varkappa" : MTMathAtom(type: .ordinary, value: "\u{03F0}"), + // Note: digamma (U+03DD) and Digamma (U+03DC) are not supported by Latin Modern Math font + + // Capital greek characters + "Gamma" : MTMathAtom(type: .variable, value: "\u{0393}"), + "Delta" : MTMathAtom(type: .variable, value: "\u{0394}"), + "Theta" : MTMathAtom(type: .variable, value: "\u{0398}"), + "Lambda" : MTMathAtom(type: .variable, value: "\u{039B}"), + "Xi" : MTMathAtom(type: .variable, value: "\u{039E}"), + "Pi" : MTMathAtom(type: .variable, value: "\u{03A0}"), + "Sigma" : MTMathAtom(type: .variable, value: "\u{03A3}"), + "Upsilon" : MTMathAtom(type: .variable, value: "\u{03A5}"), + "Phi" : MTMathAtom(type: .variable, value: "\u{03A6}"), + "Psi" : MTMathAtom(type: .variable, value: "\u{03A8}"), + "Omega" : MTMathAtom(type: .variable, value: "\u{03A9}"), + + // Open + "lceil" : MTMathAtom(type: .open, value: "\u{2308}"), + "lfloor" : MTMathAtom(type: .open, value: "\u{230A}"), + "langle" : MTMathAtom(type: .open, value: "\u{27E8}"), + "lgroup" : MTMathAtom(type: .open, value: "\u{27EE}"), + + // Close + "rceil" : MTMathAtom(type: .close, value: "\u{2309}"), + "rfloor" : MTMathAtom(type: .close, value: "\u{230B}"), + "rangle" : MTMathAtom(type: .close, value: "\u{27E9}"), + "rgroup" : MTMathAtom(type: .close, value: "\u{27EF}"), + + // Arrows + "leftarrow" : MTMathAtom(type: .relation, value: "\u{2190}"), + "uparrow" : MTMathAtom(type: .relation, value: "\u{2191}"), + "rightarrow" : MTMathAtom(type: .relation, value: "\u{2192}"), + "downarrow" : MTMathAtom(type: .relation, value: "\u{2193}"), + "leftrightarrow" : MTMathAtom(type: .relation, value: "\u{2194}"), + "updownarrow" : MTMathAtom(type: .relation, value: "\u{2195}"), + "nwarrow" : MTMathAtom(type: .relation, value: "\u{2196}"), + "nearrow" : MTMathAtom(type: .relation, value: "\u{2197}"), + "searrow" : MTMathAtom(type: .relation, value: "\u{2198}"), + "swarrow" : MTMathAtom(type: .relation, value: "\u{2199}"), + "mapsto" : MTMathAtom(type: .relation, value: "\u{21A6}"), + "Leftarrow" : MTMathAtom(type: .relation, value: "\u{21D0}"), + "Uparrow" : MTMathAtom(type: .relation, value: "\u{21D1}"), + "Rightarrow" : MTMathAtom(type: .relation, value: "\u{21D2}"), + "Downarrow" : MTMathAtom(type: .relation, value: "\u{21D3}"), + "Leftrightarrow" : MTMathAtom(type: .relation, value: "\u{21D4}"), + "Updownarrow" : MTMathAtom(type: .relation, value: "\u{21D5}"), + "longleftarrow" : MTMathAtom(type: .relation, value: "\u{27F5}"), + "longrightarrow" : MTMathAtom(type: .relation, value: "\u{27F6}"), + "longleftrightarrow" : MTMathAtom(type: .relation, value: "\u{27F7}"), + "Longleftarrow" : MTMathAtom(type: .relation, value: "\u{27F8}"), + "Longrightarrow" : MTMathAtom(type: .relation, value: "\u{27F9}"), + "Longleftrightarrow" : MTMathAtom(type: .relation, value: "\u{27FA}"), + "longmapsto" : MTMathAtom(type: .relation, value: "\u{27FC}"), + "hookrightarrow" : MTMathAtom(type: .relation, value: "\u{21AA}"), + "hookleftarrow" : MTMathAtom(type: .relation, value: "\u{21A9}"), + + + // Relations + "leq" : MTMathAtom(type: .relation, value: UnicodeSymbol.lessEqual), + "geq" : MTMathAtom(type: .relation, value: UnicodeSymbol.greaterEqual), + "leqslant" : MTMathAtom(type: .relation, value: "\u{2A7D}"), + "geqslant" : MTMathAtom(type: .relation, value: "\u{2A7E}"), + "neq" : MTMathAtom(type: .relation, value: UnicodeSymbol.notEqual), + "in" : MTMathAtom(type: .relation, value: "\u{2208}"), + "notin" : MTMathAtom(type: .relation, value: "\u{2209}"), + "ni" : MTMathAtom(type: .relation, value: "\u{220B}"), + "propto" : MTMathAtom(type: .relation, value: "\u{221D}"), + "mid" : MTMathAtom(type: .relation, value: "\u{2223}"), + "parallel" : MTMathAtom(type: .relation, value: "\u{2225}"), + "sim" : MTMathAtom(type: .relation, value: "\u{223C}"), + "simeq" : MTMathAtom(type: .relation, value: "\u{2243}"), + "cong" : MTMathAtom(type: .relation, value: "\u{2245}"), + "approx" : MTMathAtom(type: .relation, value: "\u{2248}"), + "asymp" : MTMathAtom(type: .relation, value: "\u{224D}"), + "doteq" : MTMathAtom(type: .relation, value: "\u{2250}"), + "equiv" : MTMathAtom(type: .relation, value: "\u{2261}"), + "gg" : MTMathAtom(type: .relation, value: "\u{226B}"), + "ll" : MTMathAtom(type: .relation, value: "\u{226A}"), + "prec" : MTMathAtom(type: .relation, value: "\u{227A}"), + "succ" : MTMathAtom(type: .relation, value: "\u{227B}"), + "preceq" : MTMathAtom(type: .relation, value: "\u{2AAF}"), + "succeq" : MTMathAtom(type: .relation, value: "\u{2AB0}"), + "subset" : MTMathAtom(type: .relation, value: "\u{2282}"), + "supset" : MTMathAtom(type: .relation, value: "\u{2283}"), + "subseteq" : MTMathAtom(type: .relation, value: "\u{2286}"), + "supseteq" : MTMathAtom(type: .relation, value: "\u{2287}"), + "sqsubset" : MTMathAtom(type: .relation, value: "\u{228F}"), + "sqsupset" : MTMathAtom(type: .relation, value: "\u{2290}"), + "sqsubseteq" : MTMathAtom(type: .relation, value: "\u{2291}"), + "sqsupseteq" : MTMathAtom(type: .relation, value: "\u{2292}"), + "models" : MTMathAtom(type: .relation, value: "\u{22A7}"), + "vdash" : MTMathAtom(type: .relation, value: "\u{22A2}"), + "dashv" : MTMathAtom(type: .relation, value: "\u{22A3}"), + "bowtie" : MTMathAtom(type: .relation, value: "\u{22C8}"), + "perp" : MTMathAtom(type: .relation, value: "\u{27C2}"), + "implies" : MTMathAtom(type: .relation, value: "\u{27F9}"), + + // Negated relations (amssymb) + // Inequality negations + "nless" : MTMathAtom(type: .relation, value: "\u{226E}"), + "ngtr" : MTMathAtom(type: .relation, value: "\u{226F}"), + "nleq" : MTMathAtom(type: .relation, value: "\u{2270}"), + "ngeq" : MTMathAtom(type: .relation, value: "\u{2271}"), + "nleqslant" : MTMathAtom(type: .relation, value: "\u{2A87}"), + "ngeqslant" : MTMathAtom(type: .relation, value: "\u{2A88}"), + "lneq" : MTMathAtom(type: .relation, value: "\u{2A87}"), + "gneq" : MTMathAtom(type: .relation, value: "\u{2A88}"), + "lneqq" : MTMathAtom(type: .relation, value: "\u{2268}"), + "gneqq" : MTMathAtom(type: .relation, value: "\u{2269}"), + "lnsim" : MTMathAtom(type: .relation, value: "\u{22E6}"), + "gnsim" : MTMathAtom(type: .relation, value: "\u{22E7}"), + "lnapprox" : MTMathAtom(type: .relation, value: "\u{2A89}"), + "gnapprox" : MTMathAtom(type: .relation, value: "\u{2A8A}"), + + // Ordering negations + "nprec" : MTMathAtom(type: .relation, value: "\u{2280}"), + "nsucc" : MTMathAtom(type: .relation, value: "\u{2281}"), + "npreceq" : MTMathAtom(type: .relation, value: "\u{22E0}"), + "nsucceq" : MTMathAtom(type: .relation, value: "\u{22E1}"), + "precneqq" : MTMathAtom(type: .relation, value: "\u{2AB5}"), + "succneqq" : MTMathAtom(type: .relation, value: "\u{2AB6}"), + "precnsim" : MTMathAtom(type: .relation, value: "\u{22E8}"), + "succnsim" : MTMathAtom(type: .relation, value: "\u{22E9}"), + "precnapprox" : MTMathAtom(type: .relation, value: "\u{2AB9}"), + "succnapprox" : MTMathAtom(type: .relation, value: "\u{2ABA}"), + + // Similarity/congruence negations + "nsim" : MTMathAtom(type: .relation, value: "\u{2241}"), + "ncong" : MTMathAtom(type: .relation, value: "\u{2247}"), + "nmid" : MTMathAtom(type: .relation, value: "\u{2224}"), + "nshortmid" : MTMathAtom(type: .relation, value: "\u{2224}"), + "nparallel" : MTMathAtom(type: .relation, value: "\u{2226}"), + "nshortparallel" : MTMathAtom(type: .relation, value: "\u{2226}"), + + // Set relation negations + "nsubseteq" : MTMathAtom(type: .relation, value: "\u{2288}"), + "nsupseteq" : MTMathAtom(type: .relation, value: "\u{2289}"), + "subsetneq" : MTMathAtom(type: .relation, value: "\u{228A}"), + "supsetneq" : MTMathAtom(type: .relation, value: "\u{228B}"), + "subsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACB}"), + "supsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACC}"), + "varsubsetneq" : MTMathAtom(type: .relation, value: "\u{228A}"), + "varsupsetneq" : MTMathAtom(type: .relation, value: "\u{228B}"), + "varsubsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACB}"), + "varsupsetneqq" : MTMathAtom(type: .relation, value: "\u{2ACC}"), + "notni" : MTMathAtom(type: .relation, value: "\u{220C}"), + "nni" : MTMathAtom(type: .relation, value: "\u{220C}"), + + // Triangle negations + "ntriangleleft" : MTMathAtom(type: .relation, value: "\u{22EA}"), + "ntriangleright" : MTMathAtom(type: .relation, value: "\u{22EB}"), + "ntrianglelefteq" : MTMathAtom(type: .relation, value: "\u{22EC}"), + "ntrianglerighteq" : MTMathAtom(type: .relation, value: "\u{22ED}"), + + // Turnstile negations + "nvdash" : MTMathAtom(type: .relation, value: "\u{22AC}"), + "nvDash" : MTMathAtom(type: .relation, value: "\u{22AD}"), + "nVdash" : MTMathAtom(type: .relation, value: "\u{22AE}"), + "nVDash" : MTMathAtom(type: .relation, value: "\u{22AF}"), + + // Square subset negations + "nsqsubseteq" : MTMathAtom(type: .relation, value: "\u{22E2}"), + "nsqsupseteq" : MTMathAtom(type: .relation, value: "\u{22E3}"), + + // operators + "times" : MTMathAtomFactory.times(), + "div" : MTMathAtomFactory.divide(), + "pm" : MTMathAtom(type: .binaryOperator, value: "\u{00B1}"), + "dagger" : MTMathAtom(type: .binaryOperator, value: "\u{2020}"), + "ddagger" : MTMathAtom(type: .binaryOperator, value: "\u{2021}"), + "mp" : MTMathAtom(type: .binaryOperator, value: "\u{2213}"), + "setminus" : MTMathAtom(type: .binaryOperator, value: "\u{2216}"), + "ast" : MTMathAtom(type: .binaryOperator, value: "\u{2217}"), + "circ" : MTMathAtom(type: .binaryOperator, value: "\u{2218}"), + "bullet" : MTMathAtom(type: .binaryOperator, value: "\u{2219}"), + "wedge" : MTMathAtom(type: .binaryOperator, value: "\u{2227}"), + "vee" : MTMathAtom(type: .binaryOperator, value: "\u{2228}"), + "cap" : MTMathAtom(type: .binaryOperator, value: "\u{2229}"), + "cup" : MTMathAtom(type: .binaryOperator, value: "\u{222A}"), + "wr" : MTMathAtom(type: .binaryOperator, value: "\u{2240}"), + "uplus" : MTMathAtom(type: .binaryOperator, value: "\u{228E}"), + "sqcap" : MTMathAtom(type: .binaryOperator, value: "\u{2293}"), + "sqcup" : MTMathAtom(type: .binaryOperator, value: "\u{2294}"), + "oplus" : MTMathAtom(type: .binaryOperator, value: "\u{2295}"), + "ominus" : MTMathAtom(type: .binaryOperator, value: "\u{2296}"), + "otimes" : MTMathAtom(type: .binaryOperator, value: "\u{2297}"), + "oslash" : MTMathAtom(type: .binaryOperator, value: "\u{2298}"), + "odot" : MTMathAtom(type: .binaryOperator, value: "\u{2299}"), + "star" : MTMathAtom(type: .binaryOperator, value: "\u{22C6}"), + "cdot" : MTMathAtom(type: .binaryOperator, value: "\u{22C5}"), + "diamond" : MTMathAtom(type: .binaryOperator, value: "\u{22C4}"), + "amalg" : MTMathAtom(type: .binaryOperator, value: "\u{2A3F}"), + + // Additional binary operators (amssymb) + "ltimes" : MTMathAtom(type: .binaryOperator, value: "\u{22C9}"), // left semidirect product + "rtimes" : MTMathAtom(type: .binaryOperator, value: "\u{22CA}"), // right semidirect product + "circledast" : MTMathAtom(type: .binaryOperator, value: "\u{229B}"), + "circledcirc" : MTMathAtom(type: .binaryOperator, value: "\u{229A}"), + "circleddash" : MTMathAtom(type: .binaryOperator, value: "\u{229D}"), + "boxdot" : MTMathAtom(type: .binaryOperator, value: "\u{22A1}"), + "boxminus" : MTMathAtom(type: .binaryOperator, value: "\u{229F}"), + "boxplus" : MTMathAtom(type: .binaryOperator, value: "\u{229E}"), + "boxtimes" : MTMathAtom(type: .binaryOperator, value: "\u{22A0}"), + "divideontimes" : MTMathAtom(type: .binaryOperator, value: "\u{22C7}"), + "dotplus" : MTMathAtom(type: .binaryOperator, value: "\u{2214}"), + "lhd" : MTMathAtom(type: .binaryOperator, value: "\u{22B2}"), // left normal subgroup + "rhd" : MTMathAtom(type: .binaryOperator, value: "\u{22B3}"), // right normal subgroup + "unlhd" : MTMathAtom(type: .binaryOperator, value: "\u{22B4}"), // left normal subgroup or equal + "unrhd" : MTMathAtom(type: .binaryOperator, value: "\u{22B5}"), // right normal subgroup or equal + "intercal" : MTMathAtom(type: .binaryOperator, value: "\u{22BA}"), + "barwedge" : MTMathAtom(type: .binaryOperator, value: "\u{22BC}"), + "veebar" : MTMathAtom(type: .binaryOperator, value: "\u{22BB}"), + "curlywedge" : MTMathAtom(type: .binaryOperator, value: "\u{22CF}"), + "curlyvee" : MTMathAtom(type: .binaryOperator, value: "\u{22CE}"), + "doublebarwedge" : MTMathAtom(type: .binaryOperator, value: "\u{2A5E}"), + "centerdot" : MTMathAtom(type: .binaryOperator, value: "\u{22C5}"), // alias for cdot + + // No limit operators + "log" : MTMathAtomFactory.operatorWithName( "log", limits: false), + "lg" : MTMathAtomFactory.operatorWithName( "lg", limits: false), + "ln" : MTMathAtomFactory.operatorWithName( "ln", limits: false), + "sin" : MTMathAtomFactory.operatorWithName( "sin", limits: false), + "arcsin" : MTMathAtomFactory.operatorWithName( "arcsin", limits: false), + "sinh" : MTMathAtomFactory.operatorWithName( "sinh", limits: false), + "cos" : MTMathAtomFactory.operatorWithName( "cos", limits: false), + "arccos" : MTMathAtomFactory.operatorWithName( "arccos", limits: false), + "cosh" : MTMathAtomFactory.operatorWithName( "cosh", limits: false), + "tan" : MTMathAtomFactory.operatorWithName( "tan", limits: false), + "arctan" : MTMathAtomFactory.operatorWithName( "arctan", limits: false), + "tanh" : MTMathAtomFactory.operatorWithName( "tanh", limits: false), + "cot" : MTMathAtomFactory.operatorWithName( "cot", limits: false), + "coth" : MTMathAtomFactory.operatorWithName( "coth", limits: false), + "sec" : MTMathAtomFactory.operatorWithName( "sec", limits: false), + "csc" : MTMathAtomFactory.operatorWithName( "csc", limits: false), + // Additional inverse trig functions + "arccot" : MTMathAtomFactory.operatorWithName( "arccot", limits: false), + "arcsec" : MTMathAtomFactory.operatorWithName( "arcsec", limits: false), + "arccsc" : MTMathAtomFactory.operatorWithName( "arccsc", limits: false), + // Additional hyperbolic functions + "sech" : MTMathAtomFactory.operatorWithName( "sech", limits: false), + "csch" : MTMathAtomFactory.operatorWithName( "csch", limits: false), + // Inverse hyperbolic functions + "arcsinh" : MTMathAtomFactory.operatorWithName( "arcsinh", limits: false), + "arccosh" : MTMathAtomFactory.operatorWithName( "arccosh", limits: false), + "arctanh" : MTMathAtomFactory.operatorWithName( "arctanh", limits: false), + "arccoth" : MTMathAtomFactory.operatorWithName( "arccoth", limits: false), + "arcsech" : MTMathAtomFactory.operatorWithName( "arcsech", limits: false), + "arccsch" : MTMathAtomFactory.operatorWithName( "arccsch", limits: false), + "arg" : MTMathAtomFactory.operatorWithName( "arg", limits: false), + "ker" : MTMathAtomFactory.operatorWithName( "ker", limits: false), + "dim" : MTMathAtomFactory.operatorWithName( "dim", limits: false), + "hom" : MTMathAtomFactory.operatorWithName( "hom", limits: false), + "exp" : MTMathAtomFactory.operatorWithName( "exp", limits: false), + "deg" : MTMathAtomFactory.operatorWithName( "deg", limits: false), + "mod" : MTMathAtomFactory.operatorWithName("mod", limits: false), + + // Limit operators + "lim" : MTMathAtomFactory.operatorWithName( "lim", limits: true), + "limsup" : MTMathAtomFactory.operatorWithName( "lim sup", limits: true), + "liminf" : MTMathAtomFactory.operatorWithName( "lim inf", limits: true), + "max" : MTMathAtomFactory.operatorWithName( "max", limits: true), + "min" : MTMathAtomFactory.operatorWithName( "min", limits: true), + "sup" : MTMathAtomFactory.operatorWithName( "sup", limits: true), + "inf" : MTMathAtomFactory.operatorWithName( "inf", limits: true), + "det" : MTMathAtomFactory.operatorWithName( "det", limits: true), + "Pr" : MTMathAtomFactory.operatorWithName( "Pr", limits: true), + "gcd" : MTMathAtomFactory.operatorWithName( "gcd", limits: true), + + // Large operators + "prod" : MTMathAtomFactory.operatorWithName( "\u{220F}", limits: true), + "coprod" : MTMathAtomFactory.operatorWithName( "\u{2210}", limits: true), + "sum" : MTMathAtomFactory.operatorWithName( "\u{2211}", limits: true), + "int" : MTMathAtomFactory.operatorWithName( "\u{222B}", limits: false), + "iint" : MTMathAtomFactory.operatorWithName( "\u{222C}", limits: false), + "iiint" : MTMathAtomFactory.operatorWithName( "\u{222D}", limits: false), + "iiiint" : MTMathAtomFactory.operatorWithName( "\u{2A0C}", limits: false), + "oint" : MTMathAtomFactory.operatorWithName( "\u{222E}", limits: false), + "bigwedge" : MTMathAtomFactory.operatorWithName( "\u{22C0}", limits: true), + "bigvee" : MTMathAtomFactory.operatorWithName( "\u{22C1}", limits: true), + "bigcap" : MTMathAtomFactory.operatorWithName( "\u{22C2}", limits: true), + "bigcup" : MTMathAtomFactory.operatorWithName( "\u{22C3}", limits: true), + "bigodot" : MTMathAtomFactory.operatorWithName( "\u{2A00}", limits: true), + "bigoplus" : MTMathAtomFactory.operatorWithName( "\u{2A01}", limits: true), + "bigotimes" : MTMathAtomFactory.operatorWithName( "\u{2A02}", limits: true), + "biguplus" : MTMathAtomFactory.operatorWithName( "\u{2A04}", limits: true), + "bigsqcup" : MTMathAtomFactory.operatorWithName( "\u{2A06}", limits: true), + + // Latex command characters + "{" : MTMathAtom(type: .open, value: "{"), + "}" : MTMathAtom(type: .close, value: "}"), + "$" : MTMathAtom(type: .ordinary, value: "$"), + "&" : MTMathAtom(type: .ordinary, value: "&"), + "#" : MTMathAtom(type: .ordinary, value: "#"), + "%" : MTMathAtom(type: .ordinary, value: "%"), + "_" : MTMathAtom(type: .ordinary, value: "_"), + " " : MTMathAtom(type: .ordinary, value: " "), + "backslash" : MTMathAtom(type: .ordinary, value: "\\"), + + // Punctuation + // Note: \colon is different from : which is a relation + "colon" : MTMathAtom(type: .punctuation, value: ":"), + "cdotp" : MTMathAtom(type: .punctuation, value: "\u{00B7}"), + + // Other symbols + "degree" : MTMathAtom(type: .ordinary, value: "\u{00B0}"), + "neg" : MTMathAtom(type: .ordinary, value: "\u{00AC}"), + "angstrom" : MTMathAtom(type: .ordinary, value: "\u{00C5}"), + "aa" : MTMathAtom(type: .ordinary, value: "\u{00E5}"), // NEW å + "ae" : MTMathAtom(type: .ordinary, value: "\u{00E6}"), // NEW æ + "o" : MTMathAtom(type: .ordinary, value: "\u{00F8}"), // NEW ø + "oe" : MTMathAtom(type: .ordinary, value: "\u{0153}"), // NEW œ + "ss" : MTMathAtom(type: .ordinary, value: "\u{00DF}"), // NEW ß + "cc" : MTMathAtom(type: .ordinary, value: "\u{00E7}"), // NEW ç + "CC" : MTMathAtom(type: .ordinary, value: "\u{00C7}"), // NEW Ç + "O" : MTMathAtom(type: .ordinary, value: "\u{00D8}"), // NEW Ø + "AE" : MTMathAtom(type: .ordinary, value: "\u{00C6}"), // NEW Æ + "OE" : MTMathAtom(type: .ordinary, value: "\u{0152}"), // NEW Œ + "|" : MTMathAtom(type: .ordinary, value: "\u{2016}"), + "vert" : MTMathAtom(type: .ordinary, value: "|"), + "ldots" : MTMathAtom(type: .ordinary, value: "\u{2026}"), + "prime" : MTMathAtom(type: .ordinary, value: "\u{2032}"), + "hbar" : MTMathAtom(type: .ordinary, value: "\u{210F}"), + "lbar" : MTMathAtom(type: .ordinary, value: "\u{019B}"), // NEW ƛ + "Im" : MTMathAtom(type: .ordinary, value: "\u{2111}"), + "ell" : MTMathAtom(type: .ordinary, value: "\u{2113}"), + "wp" : MTMathAtom(type: .ordinary, value: "\u{2118}"), + "Re" : MTMathAtom(type: .ordinary, value: "\u{211C}"), + "mho" : MTMathAtom(type: .ordinary, value: "\u{2127}"), + "aleph" : MTMathAtom(type: .ordinary, value: "\u{2135}"), + "beth" : MTMathAtom(type: .ordinary, value: "\u{2136}"), + "gimel" : MTMathAtom(type: .ordinary, value: "\u{2137}"), + "daleth" : MTMathAtom(type: .ordinary, value: "\u{2138}"), + "forall" : MTMathAtom(type: .ordinary, value: "\u{2200}"), + "exists" : MTMathAtom(type: .ordinary, value: "\u{2203}"), + "nexists" : MTMathAtom(type: .ordinary, value: "\u{2204}"), + "emptyset" : MTMathAtom(type: .ordinary, value: "\u{2205}"), + "varnothing" : MTMathAtom(type: .ordinary, value: "\u{2205}"), + "nabla" : MTMathAtom(type: .ordinary, value: "\u{2207}"), + "infty" : MTMathAtom(type: .ordinary, value: "\u{221E}"), + "angle" : MTMathAtom(type: .ordinary, value: "\u{2220}"), + "measuredangle" : MTMathAtom(type: .ordinary, value: "\u{2221}"), + "top" : MTMathAtom(type: .ordinary, value: "\u{22A4}"), + "bot" : MTMathAtom(type: .ordinary, value: "\u{22A5}"), + "vdots" : MTMathAtom(type: .ordinary, value: "\u{22EE}"), + "cdots" : MTMathAtom(type: .ordinary, value: "\u{22EF}"), + "ddots" : MTMathAtom(type: .ordinary, value: "\u{22F1}"), + "triangle" : MTMathAtom(type: .ordinary, value: "\u{25B3}"), + "Box" : MTMathAtom(type: .ordinary, value: "\u{25A1}"), + "imath" : MTMathAtom(type: .ordinary, value: "\u{0001D6A4}"), + "jmath" : MTMathAtom(type: .ordinary, value: "\u{0001D6A5}"), + "upquote" : MTMathAtom(type: .ordinary, value: "\u{0027}"), + "partial" : MTMathAtom(type: .ordinary, value: "\u{0001D715}"), + + // Spacing + "," : MTMathSpace(space: 3), + ">" : MTMathSpace(space: 4), + ";" : MTMathSpace(space: 5), + "!" : MTMathSpace(space: -3), + "quad" : MTMathSpace(space: 18), // quad = 1em = 18mu + "qquad" : MTMathSpace(space: 36), // qquad = 2em + + // Style + "displaystyle" : MTMathStyle(style: .display), + "textstyle" : MTMathStyle(style: .text), + "scriptstyle" : MTMathStyle(style: .script), + "scriptscriptstyle" : MTMathStyle(style: .scriptOfScript), + ] + + static var supportedAccentedCharacters: [Character: (String, String)] = [ + // Acute accents + "á": ("acute", "a"), "é": ("acute", "e"), "í": ("acute", "i"), + "ó": ("acute", "o"), "ú": ("acute", "u"), "ý": ("acute", "y"), + + // Grave accents + "à": ("grave", "a"), "è": ("grave", "e"), "ì": ("grave", "i"), + "ò": ("grave", "o"), "ù": ("grave", "u"), + + // Circumflex + "â": ("hat", "a"), "ê": ("hat", "e"), "î": ("hat", "i"), + "ĵ": ("hat", "j"), // j with circumflex (Esperanto) + "ô": ("hat", "o"), "û": ("hat", "u"), + + // Umlaut/dieresis + "ä": ("ddot", "a"), "ë": ("ddot", "e"), "ï": ("ddot", "i"), + "ö": ("ddot", "o"), "ü": ("ddot", "u"), "ÿ": ("ddot", "y"), + + // Tilde + "ã": ("tilde", "a"), "ñ": ("tilde", "n"), "õ": ("tilde", "o"), + + // Special characters + "ç": ("cc", ""), "ø": ("o", ""), "å": ("aa", ""), "æ": ("ae", ""), + "œ": ("oe", ""), "ß": ("ss", ""), + "'": ("upquote", ""), // this may be dangerous in math mode + + // Upper case variants + "Á": ("acute", "A"), "É": ("acute", "E"), "Í": ("acute", "I"), + "Ó": ("acute", "O"), "Ú": ("acute", "U"), "Ý": ("acute", "Y"), + "À": ("grave", "A"), "È": ("grave", "E"), "Ì": ("grave", "I"), + "Ò": ("grave", "O"), "Ù": ("grave", "U"), + "Â": ("hat", "A"), "Ê": ("hat", "E"), "Î": ("hat", "I"), + "Ô": ("hat", "O"), "Û": ("hat", "U"), + "Ä": ("ddot", "A"), "Ë": ("ddot", "E"), "Ï": ("ddot", "I"), + "Ö": ("ddot", "O"), "Ü": ("ddot", "U"), + "Ã": ("tilde", "A"), "Ñ": ("tilde", "N"), "Õ": ("tilde", "O"), + "Ç": ("CC", ""), + "Ø": ("O", ""), + "Å": ("AA", ""), + "Æ": ("AE", ""), + "Œ": ("OE", ""), + ] + + private static let textToLatexLock = NSLock() + static var _textToLatexSymbolName: [String: String]? = nil + public static var textToLatexSymbolName: [String: String] { + get { + if self._textToLatexSymbolName == nil { + var output = [String: String]() + for (key, atom) in Self.supportedLatexSymbols { + if atom.nucleus.count == 0 { + continue + } + if let existingText = output[atom.nucleus] { + // If there are 2 key for the same symbol, choose one deterministically. + if key.count > existingText.count { + // Keep the shorter command + continue + } else if key.count == existingText.count { + // If the length is the same, keep the alphabetically first + if key.compare(existingText) == .orderedDescending { + continue + } + } + } + output[atom.nucleus] = key + } + // protect lazily loading table in a multi-thread concurrent environment + textToLatexLock.lock() + defer { textToLatexLock.unlock() } + if self._textToLatexSymbolName == nil { + self._textToLatexSymbolName = output + } + } + return self._textToLatexSymbolName! + } + // make textToLatexSymbolName readonly (allows internal load) + // entries can be lazily added with NSLock protection. + // set { + // self._textToLatexSymbolName = newValue + // } + } + + // public static let sharedInstance = MTMathAtomFactory() + + static let fontStyles : [String: MTFontStyle] = [ + "mathnormal" : .defaultStyle, + "mathrm": .roman, + "textrm": .roman, + "rm": .roman, + "mathbf": .bold, + "bf": .bold, + "textbf": .bold, + "mathcal": .caligraphic, + "cal": .caligraphic, + "mathtt": .typewriter, + "texttt": .typewriter, + "mathit": .italic, + "textit": .italic, + "mit": .italic, + "mathsf": .sansSerif, + "textsf": .sansSerif, + "mathfrak": .fraktur, + "frak": .fraktur, + "mathbb": .blackboard, + "mathbfit": .boldItalic, + "bm": .boldItalic, + "boldsymbol": .boldItalic, + "text": .roman, + // Note: operatorname is handled specially in MTMathListBuilder to create proper operators + ] + + public static func fontStyleWithName(_ fontName:String) -> MTFontStyle? { + fontStyles[fontName] + } + + public static func fontNameForStyle(_ fontStyle:MTFontStyle) -> String { + switch fontStyle { + case .defaultStyle: return "mathnormal" + case .roman: return "mathrm" + case .bold: return "mathbf" + case .fraktur: return "mathfrak" + case .caligraphic: return "mathcal" + case .italic: return "mathit" + case .sansSerif: return "mathsf" + case .blackboard: return "mathbb" + case .typewriter: return "mathtt" + case .boldItalic: return "bm" + } + } + + /// Returns an atom for the multiplication sign (i.e., \times or "*") + public static func times() -> MTMathAtom { + MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication) + } + + /// Returns an atom for the division sign (i.e., \div or "/") + public static func divide() -> MTMathAtom { + MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.division) + } + + /// Returns an atom which is a placeholder square + public static func placeholder() -> MTMathAtom { + MTMathAtom(type: .placeholder, value: UnicodeSymbol.whiteSquare) + } + + /** Returns a fraction with a placeholder for the numerator and denominator */ + public static func placeholderFraction() -> MTFraction { + let frac = MTFraction() + frac.numerator = MTMathList() + frac.numerator?.add(placeholder()) + frac.denominator = MTMathList() + frac.denominator?.add(placeholder()) + return frac + } + + /** Returns a square root with a placeholder as the radicand. */ + public static func placeholderSquareRoot() -> MTRadical { + let rad = MTRadical() + rad.radicand = MTMathList() + rad.radicand?.add(placeholder()) + return rad + } + + /** Returns a radical with a placeholder as the radicand. */ + public static func placeholderRadical() -> MTRadical { + let rad = MTRadical() + rad.radicand = MTMathList() + rad.degree = MTMathList() + rad.radicand?.add(placeholder()) + rad.degree?.add(placeholder()) + return rad + } + + /// Latin Small Letter Dotless I (U+0131) - base character that can be styled + private static let dotlessI: Character = "\u{0131}" + /// Latin Small Letter Dotless J (U+0237) - base character that can be styled + private static let dotlessJ: Character = "\u{0237}" + + public static func atom(fromAccentedCharacter ch: Character) -> MTMathAtom? { + if let symbol = supportedAccentedCharacters[ch] { + // first handle any special characters + if let atom = atom(forLatexSymbol: symbol.0) { + return atom + } + + if let accent = MTMathAtomFactory.accent(withName: symbol.0) { + // The command is an accent + let list = MTMathList() + let baseChar = Array(symbol.1)[0] + // Use dotless variants for 'i' and 'j' to avoid double dots with accents. + // We use the base Latin dotless characters (U+0131, U+0237) rather than + // the pre-styled mathematical italic versions (U+1D6A4, U+1D6A5) so that + // font style (roman, bold, etc.) is properly applied during rendering. + if baseChar == "i" { + list.add(MTMathAtom(type: .ordinary, value: String(dotlessI))) + } else if baseChar == "j" { + list.add(MTMathAtom(type: .ordinary, value: String(dotlessJ))) + } else { + list.add(atom(forCharacter: baseChar)) + } + accent.innerList = list + return accent + } + } + return nil + } + + // MARK: - + /** Gets the atom with the right type for the given character. If an atom + cannot be determined for a given character this returns nil. + This function follows latex conventions for assigning types to the atoms. + The following characters are not supported and will return nil: + - Any non-ascii character. + - Any control character or spaces (< 0x21) + - Latex control chars: $ % # & ~ ' + - Chars with special meaning in latex: ^ _ { } \ + All other characters, including those with accents, will have a non-nil atom returned. + */ + public static func atom(forCharacter ch: Character) -> MTMathAtom? { + let chStr = String(ch) + switch chStr { + case "\u{0410}"..."\u{044F}": + // Cyrillic alphabet + return MTMathAtom(type: .ordinary, value: chStr) + case _ where supportedAccentedCharacters.keys.contains(ch): + // support for áéíóúýàèìòùâêîôûäëïöüÿãñõçøåæœß'ÁÉÍÓÚÝÀÈÌÒÙÂÊÎÔÛÄËÏÖÜÃÑÕÇØÅÆŒ + return atom(fromAccentedCharacter: ch) + case _ where ch.utf32Char < 0x0021 || ch.utf32Char > 0x007E: + return nil + case "$", "%", "#", "&", "~", "\'", "^", "_", "{", "}", "\\": + return nil + case "(", "[": + return MTMathAtom(type: .open, value: chStr) + case ")", "]", "!", "?": + return MTMathAtom(type: .close, value: chStr) + case ",", ";": + return MTMathAtom(type: .punctuation, value: chStr) + case "=", ">", "<": + return MTMathAtom(type: .relation, value: chStr) + case ":": + // Math colon is ratio. Regular colon is \colon + return MTMathAtom(type: .relation, value: "\u{2236}") + case "-": + return MTMathAtom(type: .binaryOperator, value: "\u{2212}") + case "+", "*": + return MTMathAtom(type: .binaryOperator, value: chStr) + case ".", "0"..."9": + return MTMathAtom(type: .number, value: chStr) + case "a"..."z", "A"..."Z": + return MTMathAtom(type: .variable, value: chStr) + case "\"", "/", "@", "`", "|": + return MTMathAtom(type: .ordinary, value: chStr) + default: + assertionFailure("Unknown ASCII character '\(ch)'. Should have been handled earlier.") + return nil + } + } + + /** Returns a `MTMathList` with one atom per character in the given string. This function + does not do any LaTeX conversion or interpretation. It simply uses `atom(forCharacter:)` to + convert the characters to atoms. Any character that cannot be converted is ignored. */ + public static func atomList(for string: String) -> MTMathList { + let list = MTMathList() + for character in string { + if let newAtom = atom(forCharacter: character) { + list.add(newAtom) + } + } + return list + } + + /** Returns an atom with the right type for a given latex symbol (e.g. theta) + If the latex symbol is unknown this will return nil. This supports LaTeX aliases as well. + */ + public static func atom(forLatexSymbol name: String) -> MTMathAtom? { + var name = name + if let canonicalName = aliases[name] { + name = canonicalName + } + if let atom = supportedLatexSymbols[name] { + return atom.copy() + } + return nil + } + + /** Finds the name of the LaTeX symbol name for the given atom. This function is a reverse + of the above function. If no latex symbol name corresponds to the atom, then this returns `nil` + If nucleus of the atom is empty, then this will return `nil`. + Note: This is not an exact reverse of the above in the case of aliases. If an LaTeX alias + points to a given symbol, then this function will return the original symbol name and not the + alias. + Note: This function does not convert MathSpaces to latex command names either. + */ + public static func latexSymbolName(for atom: MTMathAtom) -> String? { + guard !atom.nucleus.isEmpty else { return nil } + return Self.textToLatexSymbolName[atom.nucleus] + } + + /** Define a latex symbol for rendering. This function allows defining custom symbols that are + not already present in the default set, or override existing symbols with new meaning. + e.g. to define a symbol for "lcm" one can call: + `MTMathAtomFactory.add(latexSymbol:"lcm", value:MTMathAtomFactory.operatorWithName("lcm", limits: false))` */ + public static func add(latexSymbol name: String, value: MTMathAtom) { + let _ = Self.textToLatexSymbolName + // above force textToLatexSymbolName to initialise first, _textToLatexSymbolName also initialized. + // protect lazily loading table in a multi-thread concurrent environment + textToLatexLock.lock() + defer { textToLatexLock.unlock() } + supportedLatexSymbols[name] = value + Self._textToLatexSymbolName?[value.nucleus] = name + } + + /** Returns a large opertor for the given name. If limits is true, limits are set up on + the operator and displayed differently. */ + public static func operatorWithName(_ name: String, limits: Bool) -> MTLargeOperator { + MTLargeOperator(value: name, limits: limits) + } + + /** Returns an accent with the given name. The name of the accent is the LaTeX name + such as `grave`, `hat` etc. If the name is not a recognized accent name, this + returns nil. The `innerList` of the returned `MTAccent` is nil. + */ + public static func accent(withName name: String) -> MTAccent? { + if let accentValue = accents[name] { + let accent = MTAccent(value: accentValue) + // Mark stretchy arrow accents (\overleftarrow, \overrightarrow, \overleftrightarrow) + // These should stretch to match content width + // \vec is NOT stretchy - it should use a small fixed-size arrow + let stretchyAccents: Set = ["overleftarrow", "overrightarrow", "overleftrightarrow"] + accent.isStretchy = stretchyAccents.contains(name) + + // Mark wide accents (\widehat, \widetilde, \widecheck) + // These should stretch horizontally to cover content width + // \hat, \tilde, \check are NOT wide - they use fixed-size accents + let wideAccents: Set = ["widehat", "widetilde", "widecheck"] + accent.isWide = wideAccents.contains(name) + + return accent + } + return nil + } + + /** Returns the accent name for the given accent. This is the reverse of the above + function. */ + public static func accentName(_ accent: MTAccent) -> String? { + accentValueToName[accent.nucleus] + } + + /** Creates a new boundary atom for the given delimiter name. If the delimiter name + is not recognized it returns nil. A delimiter name can be a single character such + as '(' or a latex command such as 'uparrow'. + @note In order to distinguish between the delimiter '|' and the delimiter '\|' the delimiter '\|' + the has been renamed to '||'. + */ + public static func boundary(forDelimiter name: String) -> MTMathAtom? { + if let delimValue = Self.delimiters[name] { + return MTMathAtom(type: .boundary, value: delimValue) + } + return nil + } + + /** Returns the delimiter name for a boundary atom. This is a reverse of the above function. + If the atom is not a boundary atom or if the delimiter value is unknown this returns `nil`. + @note This is not an exact reverse of the above function. Some delimiters have two names (e.g. + `<` and `langle`) and this function always returns the shorter name. + */ + public static func getDelimiterName(of boundary: MTMathAtom) -> String? { + guard boundary.type == .boundary else { return nil } + return Self.delimValueToName[boundary.nucleus] + } + + /** Returns a fraction with the given numerator and denominator. */ + public static func fraction(withNumerator num: MTMathList, denominator denom: MTMathList) -> MTFraction { + let frac = MTFraction() + frac.numerator = num + frac.denominator = denom + return frac + } + + public static func mathListForCharacters(_ chars:String) -> MTMathList? { + let list = MTMathList() + for ch in chars { + if let atom = self.atom(forCharacter: ch) { + list.add(atom) + } + } + return list + } + + /** Simplification of above function when numerator and denominator are simple strings. + This function converts the strings to a `MTFraction`. */ + public static func fraction(withNumeratorString numStr: String, denominatorString denomStr: String) -> MTFraction { + let num = Self.atomList(for: numStr) + let denom = Self.atomList(for: denomStr) + return Self.fraction(withNumerator: num, denominator: denom) + } + + + static let matrixEnvs = [ + "matrix": [], + "pmatrix": ["(", ")"], + "bmatrix": ["[", "]"], + "Bmatrix": ["{", "}"], + "vmatrix": ["vert", "vert"], + "Vmatrix": ["Vert", "Vert"], + "smallmatrix": [], + // Starred versions with optional alignment + "matrix*": [], + "pmatrix*": ["(", ")"], + "bmatrix*": ["[", "]"], + "Bmatrix*": ["{", "}"], + "vmatrix*": ["vert", "vert"], + "Vmatrix*": ["Vert", "Vert"] + ] + + /** Builds a table for a given environment with the given rows. Returns a `MTMathAtom` containing the + table and any other atoms necessary for the given environment. Returns nil and sets error + if the table could not be built. + @param env The environment to use to build the table. If the env is nil, then the default table is built. + @note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some + matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms. + + Column constraints by environment (matching KaTeX behavior): + - `aligned`, `eqalign`: Any number of columns (1, 2, 3, 4+) with r-l-r-l alignment pattern + - `split`: Maximum 2 columns with r-l alignment + - `gather`, `displaylines`: Exactly 1 column, centered + - `cases`: 1 or 2 columns, left-aligned + - `eqnarray`: Exactly 3 columns with r-c-l alignment + */ + public static func table(withEnvironment env: String?, alignment: MTColumnAlignment? = nil, rows: [[MTMathList]], error:inout NSError?) -> MTMathAtom? { + let table = MTMathTable(environment: env) + + for i in 0.. 2 { + let message = "split environment can have at most 2 columns" + if error == nil { + error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message]) + } + return nil + } + + let spacer = MTMathAtom(type: .ordinary, value: "") + + // Add spacer at beginning of odd-indexed columns (1, 3, 5, ...) + // This matches KaTeX behavior for binary operator spacing + for i in 0.. CGSize { + CGSize(width: displayList.width + contentInsets.left + contentInsets.right, + height: displayList.ascent + displayList.descent + contentInsets.top + contentInsets.bottom) + } + public func asImage() -> (NSError?, MTImage?) { + func layoutImage(size: CGSize, displayList: MTMathListDisplay) { + var textX = CGFloat(0) + switch self.textAlignment { + case .left: textX = contentInsets.left + case .center: textX = (size.width - contentInsets.left - contentInsets.right - displayList.width) / 2 + contentInsets.left + case .right: textX = size.width - displayList.width - contentInsets.right + } + let availableHeight = size.height - contentInsets.bottom - contentInsets.top + + // center things vertically + var height = displayList.ascent + displayList.descent + if height < fontSize/2 { + height = fontSize/2 // set height to half the font size + } + let textY = (availableHeight - height) / 2 + displayList.descent + contentInsets.bottom + displayList.position = CGPoint(x: textX, y: textY) + } + + var error: NSError? + guard let mathList = MTMathListBuilder.build(fromString: latex, error: &error), error == nil, + let displayList = MTTypesetter.createLineForMathList(mathList, font: font, style: currentStyle) else { + return (error, nil) + } + + intrinsicContentSize = intrinsicContentSize(displayList) + displayList.textColor = textColor + + let size = intrinsicContentSize + layoutImage(size: size, displayList: displayList) + + #if os(iOS) || os(visionOS) + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { rendererContext in + rendererContext.cgContext.saveGState() + rendererContext.cgContext.concatenate(.flippedVertically(size.height)) + displayList.draw(rendererContext.cgContext) + rendererContext.cgContext.restoreGState() + } + return (nil, image) + #endif + #if os(macOS) + let image = NSImage(size: size, flipped: false) { bounds in + guard let context = NSGraphicsContext.current?.cgContext else { return false } + context.saveGState() + displayList.draw(context) + context.restoreGState() + return true + } + return (nil, image) + #endif + } +} +private extension CGAffineTransform { + static func flippedVertically(_ height: CGFloat) -> CGAffineTransform { + var transform = CGAffineTransform(scaleX: 1, y: -1) + transform = transform.translatedBy(x: 0, y: -height) + return transform + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathList.swift b/third-party/SwiftMath/Sources/MathRender/MTMathList.swift new file mode 100644 index 0000000000..9feb02db5c --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathList.swift @@ -0,0 +1,1058 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +/** + The type of atom in a `MTMathList`. + + The type of the atom determines how it is rendered, and spacing between the atoms. + */ +public enum MTMathAtomType: Int, CustomStringConvertible, Comparable { + /// A number or text in ordinary format - Ord in TeX + case ordinary = 1 + /// A number - Does not exist in TeX + case number + /// A variable (i.e. text in italic format) - Does not exist in TeX + case variable + /// A large operator such as (sin/cos, integral etc.) - Op in TeX + case largeOperator + /// A binary operator - Bin in TeX + case binaryOperator + /// A unary operator - Does not exist in TeX. + case unaryOperator + /// A relation, e.g. = > < etc. - Rel in TeX + case relation + /// Open brackets - Open in TeX + case open + /// Close brackets - Close in TeX + case close + /// A fraction e.g 1/2 - generalized fraction node in TeX + case fraction + /// A radical operator e.g. sqrt(2) + case radical + /// Punctuation such as , - Punct in TeX + case punctuation + /// A placeholder square for future input. Does not exist in TeX + case placeholder + /// An inner atom, i.e. an embedded math list - Inner in TeX + case inner + /// An underlined atom - Under in TeX + case underline + /// An overlined atom - Over in TeX + case overline + /// An accented atom - Accent in TeX + case accent + + // Atoms after this point do not support subscripts or superscripts + + /// A left atom - Left & Right in TeX. We don't need two since we track boundaries separately. + case boundary = 101 + + // Atoms after this are non-math TeX nodes that are still useful in math mode. They do not have + // the usual structure. + + /// Spacing between math atoms. This denotes both glue and kern for TeX. We do not + /// distinguish between glue and kern. + case space = 201 + + /// Denotes style changes during rendering. + case style + case color + case textcolor + case colorBox + + // Atoms after this point are not part of TeX and do not have the usual structure. + + /// An table atom. This atom does not exist in TeX. It is equivalent to the TeX command + /// halign which is handled outside of the TeX math rendering engine. We bring it into our + /// math typesetting to handle matrices and other tables. + case table = 1001 + + func isNotBinaryOperator() -> Bool { + switch self { + case .binaryOperator, .relation, .open, .punctuation, .largeOperator: return true + default: return false + } + } + + func isScriptAllowed() -> Bool { self < .boundary } + + // we want string representations to be capitalized + public var description: String { + switch self { + case .ordinary: return "Ordinary" + case .number: return "Number" + case .variable: return "Variable" + case .largeOperator: return "Large Operator" + case .binaryOperator: return "Binary Operator" + case .unaryOperator: return "Unary Operator" + case .relation: return "Relation" + case .open: return "Open" + case .close: return "Close" + case .fraction: return "Fraction" + case .radical: return "Radical" + case .punctuation: return "Punctuation" + case .placeholder: return "Placeholder" + case .inner: return "Inner" + case .underline: return "Underline" + case .overline: return "Overline" + case .accent: return "Accent" + case .boundary: return "Boundary" + case .space: return "Space" + case .style: return "Style" + case .color: return "Color" + case .textcolor: return "TextColor" + case .colorBox: return "Colorbox" + case .table: return "Table" + } + } + + // comparable support + public static func < (lhs: MTMathAtomType, rhs: MTMathAtomType) -> Bool { lhs.rawValue < rhs.rawValue } +} + +/** + The font style of a character. + + The fontstyle of the atom determines what style the character is rendered in. This only applies to atoms + of type kMTMathAtomVariable and kMTMathAtomNumber. None of the other atom types change their font style. + */ +public enum MTFontStyle:Int { + /// The default latex rendering style. i.e. variables are italic and numbers are roman. + case defaultStyle = 0, + /// Roman font style i.e. \mathrm + roman, + /// Bold font style i.e. \mathbf + bold, + /// Caligraphic font style i.e. \mathcal + caligraphic, + /// Typewriter (monospace) style i.e. \mathtt + typewriter, + /// Italic style i.e. \mathit + italic, + /// San-serif font i.e. \mathss + sansSerif, + /// Fractur font i.e \mathfrak + fraktur, + /// Blackboard font i.e. \mathbb + blackboard, + /// Bold italic + boldItalic +} + +// MARK: - MTMathAtom + +/** A `MTMathAtom` is the basic unit of a math list. Each atom represents a single character + or mathematical operator in a list. However certain atoms can represent more complex structures + such as fractions and radicals. Each atom has a type which determines how the atom is rendered and + a nucleus. The nucleus contains the character(s) that need to be rendered. However the nucleus may + be empty for certain types of atoms. An atom has an optional subscript or superscript which represents + the subscript or superscript that is to be rendered. + + Certain types of atoms inherit from `MTMathAtom` and may have additional fields. + */ +public class MTMathAtom: NSObject { + /** The type of the atom. */ + public var type = MTMathAtomType.ordinary + /** An optional subscript. */ + public var subScript: MTMathList? { + didSet { + if subScript != nil && !self.isScriptAllowed() { + subScript = nil + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Subscripts not allowed for atom of type \(self.type)").raise() + } + } + } + /** An optional superscript. */ + public var superScript: MTMathList? { + didSet { + if superScript != nil && !self.isScriptAllowed() { + superScript = nil + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Superscripts not allowed for atom of type \(self.type)").raise() + } + } + } + + /** The nucleus of the atom. */ + public var nucleus: String = "" + + /// The index range in the MTMathList this MTMathAtom tracks. This is used by the finalizing and preprocessing steps + /// which fuse MTMathAtoms to track the position of the current MTMathAtom in the original list. + public var indexRange = NSRange(location: 0, length: 0) // indexRange in list that this atom tracks: + + /** The font style to be used for the atom. */ + var fontStyle: MTFontStyle = .defaultStyle + + /// If this atom was formed by fusion of multiple atoms, then this stores the list of atoms that were fused to create this one. + /// This is used in the finalizing and preprocessing steps. + var fusedAtoms = [MTMathAtom]() + + init(_ atom:MTMathAtom?) { + guard let atom = atom else { return } + self.type = atom.type + self.nucleus = atom.nucleus + self.subScript = MTMathList(atom.subScript) + self.superScript = MTMathList(atom.superScript) + self.indexRange = atom.indexRange + self.fontStyle = atom.fontStyle + self.fusedAtoms = atom.fusedAtoms + } + + override init() { } + + /// Factory function to create an atom with a given type and value. + /// - parameter type: The type of the atom to instantiate. + /// - parameter value: The value of the atoms nucleus. The value is ignored for fractions and radicals. + init(type:MTMathAtomType, value:String) { + self.type = type + self.nucleus = type == .radical ? "" : value + } + + /// Returns a copy of `self`. + public func copy() -> MTMathAtom { + switch self.type { + case .largeOperator: + return MTLargeOperator(self as? MTLargeOperator) + case .fraction: + return MTFraction(self as? MTFraction) + case .radical: + return MTRadical(self as? MTRadical) + case .style: + return MTMathStyle(self as? MTMathStyle) + case .inner: + return MTInner(self as? MTInner) + case .underline: + return MTUnderLine(self as? MTUnderLine) + case .overline: + return MTOverLine(self as? MTOverLine) + case .accent: + return MTAccent(self as? MTAccent) + case .space: + return MTMathSpace(self as? MTMathSpace) + case .color: + return MTMathColor(self as? MTMathColor) + case .textcolor: + return MTMathTextColor(self as? MTMathTextColor) + case .colorBox: + return MTMathColorbox(self as? MTMathColorbox) + case .table: + return MTMathTable(self as! MTMathTable) + default: + return MTMathAtom(self) + } + } + + public override var description: String { + var string = "" + string += self.nucleus + if self.superScript != nil { + string += "^{\(self.superScript!.description)}" + } + if self.subScript != nil { + string += "_{\(self.subScript!.description)}" + } + return string + } + + /// Returns a finalized copy of the atom + public var finalized: MTMathAtom { + let finalized : MTMathAtom = self.copy() + finalized.superScript = finalized.superScript?.finalized + finalized.subScript = finalized.subScript?.finalized + return finalized + } + + public var string:String { + var str = self.nucleus + if let superScript = self.superScript { + str.append("^{\(superScript.string)}") + } + if let subScript = self.subScript { + str.append("_{\(subScript.string)}") + } + return str + } + + // Fuse the given atom with this one by combining their nucleii. + func fuse(with atom: MTMathAtom) { + assert(self.subScript == nil, "Cannot fuse into an atom which has a subscript: \(self)"); + assert(self.superScript == nil, "Cannot fuse into an atom which has a superscript: \(self)"); + assert(atom.type == self.type, "Only atoms of the same type can be fused. \(self), \(atom)"); + guard self.subScript == nil, self.superScript == nil, self.type == atom.type + else { return } + + // Update the fused atoms list + if self.fusedAtoms.isEmpty { + self.fusedAtoms.append(MTMathAtom(self)) + } + if atom.fusedAtoms.count > 0 { + self.fusedAtoms.append(contentsOf: atom.fusedAtoms) + } else { + self.fusedAtoms.append(atom) + } + + // Update nucleus: + self.nucleus += atom.nucleus + + // Update range: + self.indexRange.length += atom.indexRange.length + + // Update super/subscript: + self.superScript = atom.superScript + self.subScript = atom.subScript + } + + /** Returns true if this atom allows scripts (sub or super). */ + func isScriptAllowed() -> Bool { self.type.isScriptAllowed() } + + func isNotBinaryOperator() -> Bool { self.type.isNotBinaryOperator() } + +} + +func isNotBinaryOperator(_ prevNode:MTMathAtom?) -> Bool { + guard let prevNode = prevNode else { return true } + return prevNode.type.isNotBinaryOperator() +} + +// MARK: - MTFraction + +public class MTFraction: MTMathAtom { + public var hasRule: Bool = true + public var leftDelimiter = "" + public var rightDelimiter = "" + public var numerator: MTMathList? + public var denominator: MTMathList? + + // Continued fraction properties + public var isContinuedFraction: Bool = false + public var alignment: String = "c" // "l", "r", "c" for left, right, center + + init(_ frac: MTFraction?) { + super.init(frac) + self.type = .fraction + if let frac = frac { + self.numerator = MTMathList(frac.numerator) + self.denominator = MTMathList(frac.denominator) + self.hasRule = frac.hasRule + self.leftDelimiter = frac.leftDelimiter + self.rightDelimiter = frac.rightDelimiter + self.isContinuedFraction = frac.isContinuedFraction + self.alignment = frac.alignment + } + } + + init(hasRule rule:Bool = true) { + super.init() + self.type = .fraction + self.hasRule = rule + } + + override public var description: String { + var string = self.hasRule ? "\\frac" : "\\atop" + if !self.leftDelimiter.isEmpty { + string += "[\(self.leftDelimiter)]" + } + if !self.rightDelimiter.isEmpty { + string += "[\(self.rightDelimiter)]" + } + string += "{\(self.numerator?.description ?? "placeholder")}{\(self.denominator?.description ?? "placeholder")}" + if self.superScript != nil { + string += "^{\(self.superScript!.description)}" + } + if self.subScript != nil { + string += "_{\(self.subScript!.description)}" + } + return string + } + + override public var finalized: MTMathAtom { + let newFrac = super.finalized as! MTFraction + newFrac.numerator = newFrac.numerator?.finalized + newFrac.denominator = newFrac.denominator?.finalized + return newFrac + } + +} + +// MARK: - MTRadical +/** An atom of type radical (square root). */ +public class MTRadical: MTMathAtom { + /// Denotes the term under the square root sign + public var radicand: MTMathList? + + /// Denotes the degree of the radical, i.e. the value to the top left of the radical sign + /// This can be null if there is no degree. + public var degree: MTMathList? + + init(_ rad:MTRadical?) { + super.init(rad) + self.type = .radical + self.radicand = MTMathList(rad?.radicand) + self.degree = MTMathList(rad?.degree) + self.nucleus = "" + } + + override init() { + super.init() + self.type = .radical + self.nucleus = "" + } + + override public var description: String { + var string = "\\sqrt" + if self.degree != nil { + string += "[\(self.degree!.description)]" + } + if self.radicand != nil { + string += "{\(self.radicand?.description ?? "placeholder")}" + } + if self.superScript != nil { + string += "^{\(self.superScript!.description)}" + } + if self.subScript != nil { + string += "_{\(self.subScript!.description)}" + } + return string + } + + override public var finalized: MTMathAtom { + let newRad = super.finalized as! MTRadical + newRad.radicand = newRad.radicand?.finalized + newRad.degree = newRad.degree?.finalized + return newRad + } +} + +// MARK: - MTLargeOperator +/** A `MTMathAtom` of type `kMTMathAtom.largeOperator`. */ +public class MTLargeOperator: MTMathAtom { + + /** Indicates whether the limits (if present) should be displayed + above and below the operator in display mode. If limits is false + then the limits (if present) are displayed like a regular subscript/superscript. + */ + public var limits: Bool = false + + init(_ op:MTLargeOperator?) { + super.init(op) + self.type = .largeOperator + self.limits = op!.limits + } + + init(value: String, limits: Bool) { + super.init(type: .largeOperator, value: value) + self.limits = limits + } +} + +// MARK: - MTInner +/** An inner atom. This denotes an atom which contains a math list inside it. An inner atom + has optional boundaries. Note: Only one boundary may be present, it is not required to have + both. */ +public class MTInner: MTMathAtom { + /// The inner math list + public var innerList: MTMathList? + /// The left boundary atom. This must be a node of type kMTMathAtomBoundary + public var leftBoundary: MTMathAtom? { + didSet { + if let left = leftBoundary, left.type != .boundary { + leftBoundary = nil + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Left boundary must be of type .boundary").raise() + } + } + } + /// The right boundary atom. This must be a node of type kMTMathAtomBoundary + public var rightBoundary: MTMathAtom? { + didSet { + if let right = rightBoundary, right.type != .boundary { + rightBoundary = nil + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Right boundary must be of type .boundary").raise() + } + } + } + + /// Optional explicit delimiter height (in points). When set, this overrides the automatic + /// delimiter sizing based on inner content. Used by \big, \Big, \bigg, \Bigg commands. + public var delimiterHeight: CGFloat? + + init(_ inner:MTInner?) { + super.init(inner) + self.type = .inner + self.innerList = MTMathList(inner?.innerList) + self.leftBoundary = MTMathAtom(inner?.leftBoundary) + self.rightBoundary = MTMathAtom(inner?.rightBoundary) + self.delimiterHeight = inner?.delimiterHeight + } + + override init() { + super.init() + self.type = .inner + } + + override public var description: String { + var string = "\\inner" + if self.leftBoundary != nil { + string += "[\(self.leftBoundary!.nucleus)]" + } + string += "{\(self.innerList!.description)}" + if self.rightBoundary != nil { + string += "[\(self.rightBoundary!.nucleus)]" + } + if self.superScript != nil { + string += "^{\(self.superScript!.description)}" + } + if self.subScript != nil { + string += "_{\(self.subScript!.description)}" + } + return string + } + + override public var finalized: MTMathAtom { + let newInner = super.finalized as! MTInner + newInner.innerList = newInner.innerList?.finalized + return newInner + } +} + +// MARK: - MTOverLIne +/** An atom with a line over the contained math list. */ +public class MTOverLine: MTMathAtom { + public var innerList: MTMathList? + + override public var finalized: MTMathAtom { + let newOverline = MTOverLine(self) + newOverline.innerList = newOverline.innerList?.finalized + return newOverline + } + + init(_ over: MTOverLine?) { + super.init(over) + self.type = .overline + self.innerList = MTMathList(over!.innerList) + } + + override init() { + super.init() + self.type = .overline + } +} + +// MARK: - MTUnderLine +/** An atom with a line under the contained math list. */ +public class MTUnderLine: MTMathAtom { + public var innerList: MTMathList? + + override public var finalized: MTMathAtom { + let newUnderline = super.finalized as! MTUnderLine + newUnderline.innerList = newUnderline.innerList?.finalized + return newUnderline + } + + init(_ under: MTUnderLine?) { + super.init(under) + self.type = .underline + self.innerList = MTMathList(under?.innerList) + } + + override init() { + super.init() + self.type = .underline + } +} + +// MARK: - MTAccent + +public class MTAccent: MTMathAtom { + public var innerList: MTMathList? + /// Indicates if this accent should use stretchy arrow behavior (for \overrightarrow, etc.) + /// vs short accent behavior (for \vec). Only applies to arrow accents. + public var isStretchy: Bool = false + /// Indicates if this accent should use wide stretching behavior (for \widehat, \widetilde) + /// vs regular fixed-size accent behavior (for \hat, \tilde). + public var isWide: Bool = false + + override public var finalized: MTMathAtom { + let newAccent = super.finalized as! MTAccent + newAccent.innerList = newAccent.innerList?.finalized + newAccent.isStretchy = self.isStretchy + newAccent.isWide = self.isWide + return newAccent + } + + init(_ accent: MTAccent?) { + super.init(accent) + self.type = .accent + self.innerList = MTMathList(accent?.innerList) + self.isStretchy = accent?.isStretchy ?? false + self.isWide = accent?.isWide ?? false + } + + init(value: String) { + super.init() + self.type = .accent + self.nucleus = value + } +} + +// MARK: - MTMathSpace +/** An atom representing space. + Note: None of the usual fields of the `MTMathAtom` apply even though this + class inherits from `MTMathAtom`. i.e. it is meaningless to have a value + in the nucleus, subscript or superscript fields. */ +public class MTMathSpace: MTMathAtom { + /** The amount of space represented by this object in mu units. */ + public var space: CGFloat = 0 + + /// Creates a new `MTMathSpace` with the given spacing. + /// - parameter space: The amount of space in mu units. + init(_ space: MTMathSpace?) { + super.init(space) + self.type = .space + self.space = space?.space ?? 0 + } + + init(space:CGFloat) { + super.init() + self.type = .space + self.space = space + } +} + +/** + Styling of a line of math + */ +public enum MTLineStyle:Int, Comparable { + /// Display style + case display + /// Text style (inline) + case text + /// Script style (for sub/super scripts) + case script + /// Script script style (for scripts of scripts) + case scriptOfScript + + public func inc() -> MTLineStyle { + let raw = self.rawValue + 1 + if let style = MTLineStyle(rawValue: raw) { return style } + return .display + } + + public var isNotScript:Bool { self < .script } + public static func < (lhs: MTLineStyle, rhs: MTLineStyle) -> Bool { lhs.rawValue < rhs.rawValue } +} + +// MARK: - MTMathStyle +/** An atom representing a style change. + Note: None of the usual fields of the `MTMathAtom` apply even though this + class inherits from `MTMathAtom`. i.e. it is meaningless to have a value + in the nucleus, subscript or superscript fields. */ +public class MTMathStyle: MTMathAtom { + public var style: MTLineStyle = .display + + init(_ style:MTMathStyle?) { + super.init(style) + self.type = .style + self.style = style!.style + } + + init(style:MTLineStyle) { + super.init() + self.type = .style + self.style = style + } +} + +// MARK: - MTMathColor +/** An atom representing an color element. + Note: None of the usual fields of the `MTMathAtom` apply even though this + class inherits from `MTMathAtom`. i.e. it is meaningless to have a value + in the nucleus, subscript or superscript fields. */ +public class MTMathColor: MTMathAtom { + public var colorString:String="" + public var innerList:MTMathList? + + init(_ color: MTMathColor?) { + super.init(color) + self.type = .color + self.colorString = color?.colorString ?? "" + self.innerList = MTMathList(color?.innerList) + } + + override init() { + super.init() + self.type = .color + } + + public override var string: String { + "\\color{\(self.colorString)}{\(self.innerList!.string)}" + } + + override public var finalized: MTMathAtom { + let newColor = super.finalized as! MTMathColor + newColor.innerList = newColor.innerList?.finalized + return newColor + } +} + +// MARK: - MTMathTextColor +/** An atom representing an textcolor element. + Note: None of the usual fields of the `MTMathAtom` apply even though this + class inherits from `MTMathAtom`. i.e. it is meaningless to have a value + in the nucleus, subscript or superscript fields. */ +public class MTMathTextColor: MTMathAtom { + public var colorString:String="" + public var innerList:MTMathList? + + init(_ color: MTMathTextColor?) { + super.init(color) + self.type = .textcolor + self.colorString = color?.colorString ?? "" + self.innerList = MTMathList(color?.innerList) + } + + override init() { + super.init() + self.type = .textcolor + } + + public override var string: String { + "\\textcolor{\(self.colorString)}{\(self.innerList!.string)}" + } + + override public var finalized: MTMathAtom { + let newColor = super.finalized as! MTMathTextColor + newColor.innerList = newColor.innerList?.finalized + return newColor + } +} + +// MARK: - MTMathColorbox +/** An atom representing an colorbox element. + Note: None of the usual fields of the `MTMathAtom` apply even though this + class inherits from `MTMathAtom`. i.e. it is meaningless to have a value + in the nucleus, subscript or superscript fields. */ +public class MTMathColorbox: MTMathAtom { + public var colorString="" + public var innerList:MTMathList? + + init(_ cbox: MTMathColorbox?) { + super.init(cbox) + self.type = .colorBox + self.colorString = cbox?.colorString ?? "" + self.innerList = MTMathList(cbox?.innerList) + } + + override init() { + super.init() + self.type = .colorBox + } + + public override var string: String { + "\\colorbox{\(self.colorString)}{\(self.innerList!.string)}" + } + + override public var finalized: MTMathAtom { + let newColor = super.finalized as! MTMathColorbox + newColor.innerList = newColor.innerList?.finalized + return newColor + } +} + +/** + Alignment for a column of MTMathTable + */ +public enum MTColumnAlignment { + case left + case center + case right +} + +// MARK: - MTMathTable +/** An atom representing an table element. This atom is not like other + atoms and is not present in TeX. We use it to represent the `\halign` command + in TeX with some simplifications. This is used for matrices, equation + alignments and other uses of multiline environments. + + The cells in the table are represented as a two dimensional array of + `MTMathList` objects. The `MTMathList`s could be empty to denote a missing + value in the cell. Additionally an array of alignments indicates how each + column will be aligned. + */ +public class MTMathTable: MTMathAtom { + /// The alignment for each column (left, right, center). The default alignment + /// for a column (if not set) is center. + public var alignments = [MTColumnAlignment]() + /// The cells in the table as a two dimensional array. + public var cells = [[MTMathList]]() + /// The name of the environment that this table denotes. + public var environment = "" + /// Spacing between each column in mu units. + public var interColumnSpacing: CGFloat = 0 + /// Additional spacing between rows in jots (one jot is 0.3 times font size). + /// If the additional spacing is 0, then normal row spacing is used are used. + public var interRowAdditionalSpacing: CGFloat = 0 + + override public var finalized: MTMathAtom { + let table = super.finalized as! MTMathTable + for var row in table.cells { + for i in 0.. MTColumnAlignment { + if self.alignments.count <= col { + return MTColumnAlignment.center + } else { + return self.alignments[col] + } + } + + public var numColumns: Int { + var numberOfCols = 0 + for row in self.cells { + numberOfCols = max(numberOfCols, row.count) + } + return numberOfCols + } + + public var numRows: Int { self.cells.count } +} + +// MARK: - MTMathList + +extension MTMathList { + public override var description: String { self.atoms.description } + /// converts the MTMathList to a string form. Note: This is not the LaTeX form. + public var string: String { self.description } +} + +/** A representation of a list of math objects. + + This list can be constructed directly or built with + the help of the MTMathListBuilder. It is not required that the mathematics represented make sense + (i.e. this can represent something like "x 2 = +". This list can be used for display using MTLine + or can be a list of tokens to be used by a parser after finalizedMathList is called. + + Note: This class is for **advanced** usage only. + */ +public class MTMathList : NSObject { + + init?(_ list:MTMathList?) { + guard let list = list else { return nil } + for atom in list.atoms { + self.atoms.append(atom.copy()) + } + } + + /// A list of MathAtoms + public var atoms = [MTMathAtom]() + + /// Create a new math list as a final expression and update atoms + /// by combining like atoms that occur together and converting unary operators to binary operators. + /// This function does not modify the current MTMathList + public var finalized: MTMathList { + let finalizedList = MTMathList() + let zeroRange = NSMakeRange(0, 0) + + var prevNode: MTMathAtom? = nil + for atom in self.atoms { + let newNode = atom.finalized + + if NSEqualRanges(zeroRange, atom.indexRange) { + // CRITICAL FIX: Check if prevNode has a valid range location before using it + // If location is NSNotFound, treat as if there's no prevNode + // This prevents negative overflow when creating NSMakeRange + let index: Int + if prevNode == nil || prevNode!.indexRange.location == NSNotFound { + index = 0 + } else { + // Additional safety: check for potential overflow + let location = prevNode!.indexRange.location + let length = prevNode!.indexRange.length + // If either value is suspicious (negative or too large), reset to 0 + if location < 0 || length < 0 || location > Int.max - length { + index = 0 + } else { + index = location + length + } + } + newNode.indexRange = NSMakeRange(index, 1) + } + + switch newNode.type { + case .binaryOperator: + if isNotBinaryOperator(prevNode) { + newNode.type = .unaryOperator + } + case .relation, .punctuation, .close: + if prevNode != nil && prevNode!.type == .binaryOperator { + prevNode!.type = .unaryOperator + } + case .number: + if prevNode != nil && prevNode!.type == .number && prevNode!.subScript == nil && prevNode!.superScript == nil { + prevNode!.fuse(with: newNode) + continue // skip the current node, we are done here. + } + default: break + } + finalizedList.add(newNode) + prevNode = newNode + } + if prevNode != nil && prevNode!.type == .binaryOperator { + prevNode!.type = .unaryOperator + } + return finalizedList + } + + public init(atoms: [MTMathAtom]) { + self.atoms.append(contentsOf: atoms) + } + + public init(atom: MTMathAtom) { + self.atoms.append(atom) + } + + public override init() { super.init() } + + func NSParamException(_ param:Any?) { + if param == nil { + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Parameter cannot be nil").raise() + } + } + + func NSIndexException(_ array:[Any], index: Int) { + guard !array.indices.contains(index) else { return } + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Index \(index) out of bounds").raise() + } + + /// Add an atom to the end of the list. + /// - parameter atom: The atom to be inserted. This cannot be `nil` and cannot have the type `kMTMathAtomBoundary`. + /// - throws NSException if the atom is of type `kMTMathAtomBoundary` + public func add(_ atom: MTMathAtom?) { + guard let atom = atom else { return } + if self.isAtomAllowed(atom) { + self.atoms.append(atom) + } else { + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Cannot add atom of type \(atom.type.rawValue) into mathlist").raise() + } + } + + /// Inserts an atom at the given index. If index is already occupied, the objects at index and beyond are + /// shifted by adding 1 to their indices to make room. An insert to an `index` greater than the number of atoms + /// is ignored. Insertions of nil atoms is ignored. + /// - parameter atom: The atom to be inserted. This cannot be `nil` and cannot have the type `kMTMathAtom.boundary`. + /// - parameter index: The index where the atom is to be inserted. The index should be less than or equal to the + /// number of elements in the math list. + /// - throws NSException if the atom is of type kMTMathAtomBoundary + public func insert(_ atom: MTMathAtom?, at index: Int) { + // NSParamException(atom) + guard let atom = atom else { return } + guard self.atoms.indices.contains(index) || index == self.atoms.endIndex else { return } + // guard self.atoms.endIndex >= index else { NSIndexException(); return } + if self.isAtomAllowed(atom) { + // NSIndexException(self.atoms, index: index) + self.atoms.insert(atom, at: index) + } else { + NSException(name: NSExceptionName(rawValue: "Error"), reason: "Cannot add atom of type \(atom.type.rawValue) into mathlist").raise() + } + } + + /// Append the given list to the end of the current list. + /// - parameter list: The list to append. + public func append(_ list: MTMathList?) { + guard let list = list else { return } + self.atoms += list.atoms + } + + /** Removes the last atom from the math list. If there are no atoms in the list this does nothing. */ + public func removeLastAtom() { + if !self.atoms.isEmpty { + self.atoms.removeLast() + } + } + + /// Removes the atom at the given index. + /// - parameter index: The index at which to remove the atom. Must be less than the number of atoms + /// in the list. + public func removeAtom(at index: Int) { + NSIndexException(self.atoms, index:index) + self.atoms.remove(at: index) + } + + /** Removes all the atoms within the given range. */ + public func removeAtoms(in range: ClosedRange) { + NSIndexException(self.atoms, index: range.lowerBound) + NSIndexException(self.atoms, index: range.upperBound) + self.atoms.removeSubrange(range) + } + + func isAtomAllowed(_ atom: MTMathAtom?) -> Bool { atom?.type != .boundary } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathListBuilder.swift b/third-party/SwiftMath/Sources/MathRender/MTMathListBuilder.swift new file mode 100644 index 0000000000..4083fd90d9 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathListBuilder.swift @@ -0,0 +1,1613 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +/** `MTMathListBuilder` is a class for parsing LaTeX into an `MTMathList` that + can be rendered and processed mathematically. + */ +struct MTEnvProperties { + var envName: String? + var ended: Bool + var numRows: Int + var alignment: MTColumnAlignment? // Optional alignment for starred matrix environments + + init(name: String?, alignment: MTColumnAlignment? = nil) { + self.envName = name + self.numRows = 0 + self.ended = false + self.alignment = alignment + } +} + +/** + The error encountered when parsing a LaTeX string. + + The `code` in the `NSError` is one of the following indicating why the LaTeX string + could not be parsed. + */ +enum MTParseErrors:Int { + /// The braces { } do not match. + case mismatchBraces = 1 + /// A command in the string is not recognized. + case invalidCommand + /// An expected character such as ] was not found. + case characterNotFound + /// The \left or \right command was not followed by a delimiter. + case missingDelimiter + /// The delimiter following \left or \right was not a valid delimiter. + case invalidDelimiter + /// There is no \right corresponding to the \left command. + case missingRight + /// There is no \left corresponding to the \right command. + case missingLeft + /// The environment given to the \begin command is not recognized + case invalidEnv + /// A command is used which is only valid inside a \begin,\end environment + case missingEnv + /// There is no \begin corresponding to the \end command. + case missingBegin + /// There is no \end corresponding to the \begin command. + case missingEnd + /// The number of columns do not match the environment + case invalidNumColumns + /// Internal error, due to a programming mistake. + case internalError + /// Limit control applied incorrectly + case invalidLimits +} + +let MTParseError = "ParseError" + +/** `MTMathListBuilder` is a class for parsing LaTeX into an `MTMathList` that + can be rendered and processed mathematically. + */ +public struct MTMathListBuilder { + /// The math mode determines rendering style (inline vs display) + enum MathMode { + /// Display style - larger operators, limits above/below (e.g., $$...$$, \[...\]) + case display + /// Inline/text style - compact operators, limits to the side (e.g., $...$, \(...\)) + case inline + + /// Convert MathMode to MTLineStyle for rendering + func toLineStyle() -> MTLineStyle { + switch self { + case .display: + return .display + case .inline: + return .text + } + } + } + + var string: String + var currentCharIndex: String.Index + var currentInnerAtom: MTInner? + var currentEnv: MTEnvProperties? + var currentFontStyle:MTFontStyle + var spacesAllowed:Bool + var mathMode: MathMode = .display + + /** Contains any error that occurred during parsing. */ + var error:NSError? + + // MARK: - Character-handling routines + + var hasCharacters: Bool { currentCharIndex < string.endIndex } + + // gets the next character and increments the index + mutating func getNextCharacter() -> Character { + assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)") + let ch = string[currentCharIndex] + currentCharIndex = string.index(after: currentCharIndex) + return ch + } + + mutating func unlookCharacter() { + assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.") + if currentCharIndex > string.startIndex { + currentCharIndex = string.index(before: currentCharIndex) + } + } + + // Peek at next command without consuming it (for \not lookahead) + mutating func peekNextCommand() -> String { + let savedIndex = currentCharIndex + skipSpaces() + + guard hasCharacters else { + currentCharIndex = savedIndex + return "" + } + + let char = getNextCharacter() + let command: String + + if char == "\\" { + command = readCommand() + } else { + command = "" + } + + // Restore position + currentCharIndex = savedIndex + return command + } + + // Consume the next command (after peeking) + mutating func consumeNextCommand() { + skipSpaces() + + guard hasCharacters else { return } + + let char = getNextCharacter() + if char == "\\" { + _ = readCommand() + } + } + + + + mutating func expectCharacter(_ ch: Character) -> Bool { + MTAssertNotSpace(ch) + self.skipSpaces() + + if self.hasCharacters { + let nextChar = self.getNextCharacter() + MTAssertNotSpace(nextChar) + if nextChar == ch { + return true + } else { + self.unlookCharacter() + return false + } + } + return false + } + + public static let spaceToCommands: [CGFloat: String] = [ + 3 : ",", + 4 : ">", + 5 : ";", + (-3) : "!", + 18 : "quad", + 36 : "qquad", + ] + + public static let styleToCommands: [MTLineStyle: String] = [ + .display: "displaystyle", + .text: "textstyle", + .script: "scriptstyle", + .scriptOfScript: "scriptscriptstyle" + ] + + // Comprehensive mapping of \not command combinations to Unicode negated symbols + public static let notCombinations: [String: String] = [ + // Primary targets (user requested) + "equiv": "\u{2262}", // ≢ Not equivalent + "subset": "\u{2284}", // ⊄ Not subset + "in": "\u{2209}", // ∉ Not element of + + // Additional standard negations + "sim": "\u{2241}", // ≁ Not similar + "approx": "\u{2249}", // ≉ Not approximately equal + "cong": "\u{2247}", // ≇ Not congruent + "parallel": "\u{2226}", // ∦ Not parallel + "subseteq": "\u{2288}", // ⊈ Not subset or equal + "supset": "\u{2285}", // ⊅ Not superset + "supseteq": "\u{2289}", // ⊉ Not superset or equal + "=": "\u{2260}", // ≠ Not equal (alternative to \neq) + ] + + /// Delimiter sizing commands with their size multipliers (relative to font size). + /// Values based on standard TeX: at 10pt, \big=8.5pt, \Big=11.5pt, \bigg=14.5pt, \Bigg=17.5pt + /// These translate to approximately 0.85x, 1.15x, 1.45x, 1.75x of font size. + /// We use slightly larger values to ensure visible size differences. + public static let delimiterSizeCommands: [String: CGFloat] = [ + // Basic sizing commands + "big": 1.0, + "Big": 1.4, + "bigg": 1.8, + "Bigg": 2.2, + // Left variants (same sizes, just semantic distinction in LaTeX) + "bigl": 1.0, + "Bigl": 1.4, + "biggl": 1.8, + "Biggl": 2.2, + // Right variants + "bigr": 1.0, + "Bigr": 1.4, + "biggr": 1.8, + "Biggr": 2.2, + // Middle variants (used between delimiters) + "bigm": 1.0, + "Bigm": 1.4, + "biggm": 1.8, + "Biggm": 2.2, + ] + + init(string: String) { + self.error = nil + self.string = string + self.currentCharIndex = string.startIndex + self.currentFontStyle = .defaultStyle + self.spacesAllowed = false + } + + // MARK: - Delimiter Detection + + /// Detects and strips LaTeX math delimiters from the input string. + /// Returns the cleaned content and the detected math mode. + /// Supports: $...$ \(...\) $$...$$ \[...\] and environments + func detectAndStripDelimiters(from str: String) -> (String, MathMode) { + let trimmed = str.trimmingCharacters(in: .whitespacesAndNewlines) + + // Check display delimiters first (more specific patterns) + + // \[...\] - LaTeX display math + if trimmed.hasPrefix("\\[") && trimmed.hasSuffix("\\]") && trimmed.count > 4 { + let content = String(trimmed.dropFirst(2).dropLast(2)) + return (content, .display) + } + + // $$...$$ - TeX display math (check before single $) + if trimmed.hasPrefix("$$") && trimmed.hasSuffix("$$") && trimmed.count > 4 { + let content = String(trimmed.dropFirst(2).dropLast(2)) + return (content, .display) + } + + // Check inline delimiters + + // \(...\) - LaTeX inline math + if trimmed.hasPrefix("\\(") && trimmed.hasSuffix("\\)") && trimmed.count > 4 { + let content = String(trimmed.dropFirst(2).dropLast(2)) + return (content, .inline) + } + + // $...$ - TeX inline math (must check after $$) + if trimmed.hasPrefix("$") && trimmed.hasSuffix("$") && trimmed.count > 2 && !trimmed.hasPrefix("$$") { + let content = String(trimmed.dropFirst(1).dropLast(1)) + return (content, .inline) + } + + // Check if it's an environment (\begin{...}\end{...}) + // These are handled by existing logic and are display mode by default + if trimmed.hasPrefix("\\begin{") { + return (str, .display) + } + + // No delimiters found - default to display mode (current behavior for backward compatibility) + return (str, .display) + } + + // MARK: - MTMathList builder functions + + /// Builds a mathlist from the internal `string`. Returns nil if there is an error. + public mutating func build() -> MTMathList? { + // Detect and strip delimiters, updating the string and mode + let (cleanedString, mode) = detectAndStripDelimiters(from: self.string) + self.string = cleanedString + self.currentCharIndex = cleanedString.startIndex + self.mathMode = mode + + // If inline mode, we could optionally prepend a \textstyle command + // to force inline rendering of operators. For now, just track the mode. + + let list = self.buildInternal(false) + if self.hasCharacters && error == nil { + self.setError(.mismatchBraces, message: "Mismatched braces: \(self.string)") + return nil + } + if error != nil { + return nil + } + + // Note: For inline mode, we insert \textstyle to match LaTeX behavior. + // However, fractionStyle() has been modified to keep fractions at the + // same font size in both display and text modes (not one level smaller). + // Large operators show limits above/below in text style due to the updated + // condition in makeLargeOp() that checks both .display and .text styles. + if mode == .inline && list != nil && !list!.atoms.isEmpty { + // Prepend \textstyle to force inline rendering + let styleAtom = MTMathStyle(style: .text) + list!.atoms.insert(styleAtom, at: 0) + } + + return list + } + + /** Construct a math list from a given string. If there is parse error, returns + nil. To retrieve the error use the function `MTMathListBuilder.build(fromString:error:)`. + */ + public static func build(fromString string: String) -> MTMathList? { + var builder = MTMathListBuilder(string: string) + return builder.build() + } + + /** Construct a math list from a given string. If there is an error while + constructing the string, this returns nil. The error is returned in the + `error` parameter. + */ + public static func build(fromString string: String, error:inout NSError?) -> MTMathList? { + var builder = MTMathListBuilder(string: string) + let output = builder.build() + if builder.error != nil { + error = builder.error + return nil + } + return output + } + + /** Construct a math list from a given string and return the detected style. + This method detects LaTeX delimiters like \[...\], $$...$$, $...$, \(...\) + and returns the appropriate rendering style (.display or .text). + + If there is a parse error, returns nil for the MathList. + + - Parameter string: The LaTeX string to parse + - Returns: A tuple containing the parsed MathList and the detected MTLineStyle + */ + public static func buildWithStyle(fromString string: String) -> (mathList: MTMathList?, style: MTLineStyle) { + var builder = MTMathListBuilder(string: string) + let mathList = builder.build() + let style = builder.mathMode.toLineStyle() + return (mathList, style) + } + + /** Construct a math list from a given string and return the detected style. + This method detects LaTeX delimiters like \[...\], $$...$$, $...$, \(...\) + and returns the appropriate rendering style (.display or .text). + + If there is an error while constructing the string, this returns nil for the MathList. + The error is returned in the `error` parameter. + + - Parameters: + - string: The LaTeX string to parse + - error: An inout parameter that will contain any parse error + - Returns: A tuple containing the parsed MathList and the detected MTLineStyle + */ + public static func buildWithStyle(fromString string: String, error: inout NSError?) -> (mathList: MTMathList?, style: MTLineStyle) { + var builder = MTMathListBuilder(string: string) + let output = builder.build() + let style = builder.mathMode.toLineStyle() + if builder.error != nil { + error = builder.error + return (nil, style) + } + return (output, style) + } + + public mutating func buildInternal(_ oneCharOnly: Bool) -> MTMathList? { + self.buildInternal(oneCharOnly, stopChar: nil) + } + + public mutating func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? { + let list = MTMathList() + assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.") + var prevAtom: MTMathAtom? = nil + while self.hasCharacters { + if error != nil { return nil } // If there is an error thus far then bail out. + + var atom: MTMathAtom? = nil + let char = self.getNextCharacter() + + if oneCharOnly { + if char == "^" || char == "}" || char == "_" || char == "&" { + // this is not the character we are looking for. + // They are meant for the caller to look at. + self.unlookCharacter() + return list + } + } + // If there is a stop character, keep scanning 'til we find it + if stop != nil && char == stop! { + return list + } + + if char == "^" { + assert(!oneCharOnly, "This should have been handled before") + if (prevAtom == nil || prevAtom!.superScript != nil || !prevAtom!.isScriptAllowed()) { + // If there is no previous atom, or if it already has a superscript + // or if scripts are not allowed for it, then add an empty node. + prevAtom = MTMathAtom(type: .ordinary, value: "") + list.add(prevAtom!) + } + // this is a superscript for the previous atom + // note: if the next char is the stopChar it will be consumed by the ^ and so it doesn't count as stop + prevAtom!.superScript = self.buildInternal(true) + continue + } else if char == "_" { + assert(!oneCharOnly, "This should have been handled before") + if (prevAtom == nil || prevAtom!.subScript != nil || !prevAtom!.isScriptAllowed()) { + // If there is no previous atom, or if it already has a subcript + // or if scripts are not allowed for it, then add an empty node. + prevAtom = MTMathAtom(type: .ordinary, value: "") + list.add(prevAtom!) + } + // this is a subscript for the previous atom + // note: if the next char is the stopChar it will be consumed by the _ and so it doesn't count as stop + prevAtom!.subScript = self.buildInternal(true) + continue + } else if char == "{" { + // this puts us in a recursive routine, and sets oneCharOnly to false and no stop character + if let subList = self.buildInternal(false, stopChar: "}") { + prevAtom = subList.atoms.last + list.append(subList) + if oneCharOnly { + return list + } + } + continue + } else if char == "}" { + // \ means a command + assert(!oneCharOnly, "This should have been handled before") + assert(stop == nil, "This should have been handled before") + // Special case: } terminates implicit table (envName == nil) created by \\ + // This happens when \\ is used inside braces: \substack{a \\ b} + if self.currentEnv != nil && self.currentEnv!.envName == nil { + // Mark environment as ended, don't consume the } + self.currentEnv!.ended = true + return list + } + // We encountered a closing brace when there is no stop set, that means there was no + // corresponding opening brace. + self.setError(.mismatchBraces, message:"Mismatched braces.") + return nil + } else if char == "\\" { + let command = readCommand() + let done = stopCommand(command, list:list, stopChar:stop) + if done != nil { + return done + } else if error != nil { + return nil + } + if self.applyModifier(command, atom:prevAtom) { + continue + } + + if let fontStyle = MTMathAtomFactory.fontStyleWithName(command) { + let oldSpacesAllowed = spacesAllowed + // Text has special consideration where it allows spaces without escaping. + spacesAllowed = command == "text" + let oldFontStyle = currentFontStyle + currentFontStyle = fontStyle + if let sublist = self.buildInternal(true) { + // Restore the font style. + currentFontStyle = oldFontStyle + spacesAllowed = oldSpacesAllowed + + prevAtom = sublist.atoms.last + list.append(sublist) + if oneCharOnly { + return list + } + } + continue + } + atom = self.atomForCommand(command) + if atom == nil { + // this was an unknown command, + // we flag an error and return + // (note setError will not set the error if there is already one, so we flag internal error + // in the odd case that an _error is not set. + self.setError(.internalError, message:"Internal error") + return nil + } + } else if char == "&" { + // used for column separation in tables + assert(!oneCharOnly, "This should have been handled before") + if self.currentEnv != nil { + return list + } else { + // Create a new table with the current list and a default env + if let table = self.buildTable(env: nil, firstList: list, isRow: false) { + return MTMathList(atom: table) + } else { + return nil + } + } + } else if spacesAllowed && char == " " { + // If spaces are allowed then spaces do not need escaping with a \ before being used. + atom = MTMathAtomFactory.atom(forLatexSymbol: " ") + } else { + atom = MTMathAtomFactory.atom(forCharacter: char) + if atom == nil { + // Not a recognized character in standard math mode + // In text mode (spacesAllowed && roman style), accept any Unicode character for fallback font support + // This enables Chinese, Japanese, Korean, emoji, etc. in \text{} commands + if spacesAllowed && currentFontStyle == .roman { + atom = MTMathAtom(type: .ordinary, value: String(char)) + } else { + // In math mode or non-text commands, skip unrecognized characters + continue + } + } + } + + assert(atom != nil, "Atom shouldn't be nil") + atom?.fontStyle = currentFontStyle + // If this is an accent atom (e.g., from an accented character like "é"), + // propagate the font style to the inner list atoms that don't already have + // an explicit font style. This handles Unicode accented characters which are + // converted to accents by atom(fromAccentedCharacter:) without font style context. + // We only set font style on atoms with .defaultStyle to avoid overriding + // explicit font style commands like \textbf inside accents. + if let accent = atom as? MTAccent, let innerList = accent.innerList { + for innerAtom in innerList.atoms { + if innerAtom.fontStyle == .defaultStyle { + innerAtom.fontStyle = currentFontStyle + } + } + } + list.add(atom) + prevAtom = atom + + if oneCharOnly { + return list + } + } + if stop != nil { + if stop == "}" { + // We did not find a corresponding closing brace. + self.setError(.mismatchBraces, message:"Missing closing brace") + } else { + // we never found our stop character + let errorMessage = "Expected character not found: \(stop!)" + self.setError(.characterNotFound, message:errorMessage) + } + } + return list + } + + + // MARK: - MTMathList to LaTeX conversion + + /// This converts the MTMathList to LaTeX. + public static func mathListToString(_ ml: MTMathList?) -> String { + var str = "" + var currentfontStyle = MTFontStyle.defaultStyle + if let atomList = ml { + for atom in atomList.atoms { + if currentfontStyle != atom.fontStyle { + if currentfontStyle != .defaultStyle { + str += "}" + } + if atom.fontStyle != .defaultStyle { + let fontStyleName = MTMathAtomFactory.fontNameForStyle(atom.fontStyle) + str += "\\\(fontStyleName){" + } + currentfontStyle = atom.fontStyle + } + if atom.type == .fraction { + if let frac = atom as? MTFraction { + if frac.isContinuedFraction { + // Generate \cfrac with optional alignment + if frac.alignment != "c" { + str += "\\cfrac[\(frac.alignment)]{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" + } else { + str += "\\cfrac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" + } + } else if frac.hasRule { + str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}" + } else { + let command: String + if frac.leftDelimiter.isEmpty && frac.rightDelimiter.isEmpty { + command = "atop" + } else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" { + command = "choose" + } else if frac.leftDelimiter == "{" && frac.rightDelimiter == "}" { + command = "brace" + } else if frac.leftDelimiter == "[" && frac.rightDelimiter == "]" { + command = "brack" + } else { + command = "atopwithdelims\(frac.leftDelimiter)\(frac.rightDelimiter)" + } + str += "{\(mathListToString(frac.numerator!)) \\\(command) \(mathListToString(frac.denominator!))}" + } + } + } else if atom.type == .radical { + str += "\\sqrt" + if let rad = atom as? MTRadical { + if rad.degree != nil { + str += "[\(mathListToString(rad.degree!))]" + } + str += "{\(mathListToString(rad.radicand!))}" + } + } else if atom.type == .inner { + if let inner = atom as? MTInner { + if inner.leftBoundary != nil || inner.rightBoundary != nil { + if inner.leftBoundary != nil { + str += "\\left\(delimToString(delim: inner.leftBoundary!)) " + } else { + str += "\\left. " + } + + str += mathListToString(inner.innerList!) + + if inner.rightBoundary != nil { + str += "\\right\(delimToString(delim: inner.rightBoundary!)) " + } else { + str += "\\right. " + } + } else { + str += "{\(mathListToString(inner.innerList!))}" + } + } + } else if atom.type == .table { + if let table = atom as? MTMathTable { + if !table.environment.isEmpty { + str += "\\begin{\(table.environment)}" + } + + for i in 0..= 1 && cell.atoms[0].type == .style { + // remove first atom + cell.atoms.removeFirst() + } + } + if table.environment == "eqalign" || table.environment == "aligned" || table.environment == "split" { + if j == 1 && cell.atoms.count >= 1 && cell.atoms[0].type == .ordinary && cell.atoms[0].nucleus.count == 0 { + // remove empty nucleus added for spacing + cell.atoms.removeFirst() + } + } + str += mathListToString(cell) + if j < row.count - 1 { + str += "&" + } + } + if i < table.numRows - 1 { + str += "\\\\ " + } + } + if !table.environment.isEmpty { + str += "\\end{\(table.environment)}" + } + } + } else if atom.type == .overline { + if let overline = atom as? MTOverLine { + str += "\\overline" + str += "{\(mathListToString(overline.innerList!))}" + } + } else if atom.type == .underline { + if let underline = atom as? MTUnderLine { + str += "\\underline" + str += "{\(mathListToString(underline.innerList!))}" + } + } else if atom.type == .accent { + if let accent = atom as? MTAccent { + str += "\\\(MTMathAtomFactory.accentName(accent)!){\(mathListToString(accent.innerList!))}" + } + } else if atom.type == .largeOperator { + let op = atom as! MTLargeOperator + let command = MTMathAtomFactory.latexSymbolName(for: atom) + let originalOp = MTMathAtomFactory.atom(forLatexSymbol: command!) as! MTLargeOperator + str += "\\\(command!) " + if originalOp.limits != op.limits { + if op.limits { + str += "\\limits " + } else { + str += "\\nolimits " + } + } + } else if atom.type == .space { + if let space = atom as? MTMathSpace { + if let command = Self.spaceToCommands[space.space] { + str += "\\\(command) " + } else { + str += String(format: "\\mkern%.1fmu", space.space) + } + } + } else if atom.type == .style { + if let style = atom as? MTMathStyle { + if let command = Self.styleToCommands[style.style] { + str += "\\\(command) " + } + } + } else if atom.nucleus.isEmpty { + str += "{}" + } else if atom.nucleus == "\u{2236}" { + // math colon + str += ":" + } else if atom.nucleus == "\u{2212}" { + // math minus + str += "-" + } else { + if let command = MTMathAtomFactory.latexSymbolName(for: atom) { + str += "\\\(command) " + } else { + str += "\(atom.nucleus)" + } + } + + if atom.superScript != nil { + str += "^{\(mathListToString(atom.superScript!))}" + } + + if atom.subScript != nil { + str += "_{\(mathListToString(atom.subScript!))}" + } + } + } + if currentfontStyle != .defaultStyle { + str += "}" + } + return str + } + + public static func delimToString(delim: MTMathAtom) -> String { + if let command = MTMathAtomFactory.getDelimiterName(of: delim) { + let singleChars = [ "(", ")", "[", "]", "<", ">", "|", ".", "/"] + if singleChars.contains(command) { + return command + } else if command == "||" { + return "\\|" + } else { + return "\\\(command)" + } + } + return "" + } + + mutating func atomForCommand(_ command:String) -> MTMathAtom? { + if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { + return atom + } + if let accent = MTMathAtomFactory.accent(withName: command) { + // The command is an accent + accent.innerList = self.buildInternal(true) + return accent; + } else if command == "frac" { + // A fraction command has 2 arguments + let frac = MTFraction() + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac; + } else if command == "cfrac" { + // A continued fraction command with optional alignment and 2 arguments + let frac = MTFraction() + frac.isContinuedFraction = true + + // Parse optional alignment parameter [l], [r], [c] + skipSpaces() + if hasCharacters && string[currentCharIndex] == "[" { + _ = getNextCharacter() // consume '[' + if hasCharacters { + let alignmentChar = getNextCharacter() + if alignmentChar == "l" || alignmentChar == "r" || alignmentChar == "c" { + frac.alignment = String(alignmentChar) + } + } + // Consume closing ']' + if hasCharacters && string[currentCharIndex] == "]" { + _ = getNextCharacter() + } + } + + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac; + } else if command == "dfrac" { + // Display-style fraction command has 2 arguments + let frac = MTFraction() + let numerator = self.buildInternal(true) + let denominator = self.buildInternal(true) + + // Prepend \displaystyle to force display mode rendering + let displayStyle = MTMathStyle(style: .display) + numerator?.insert(displayStyle, at: 0) + denominator?.insert(displayStyle, at: 0) + + frac.numerator = numerator + frac.denominator = denominator + return frac; + } else if command == "tfrac" { + // Text-style fraction command has 2 arguments + let frac = MTFraction() + let numerator = self.buildInternal(true) + let denominator = self.buildInternal(true) + + // Prepend \textstyle to force text mode rendering + let textStyle = MTMathStyle(style: .text) + numerator?.insert(textStyle, at: 0) + denominator?.insert(textStyle, at: 0) + + frac.numerator = numerator + frac.denominator = denominator + return frac; + } else if command == "binom" { + // A binom command has 2 arguments + let frac = MTFraction(hasRule: false) + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + frac.leftDelimiter = "("; + frac.rightDelimiter = ")"; + return frac; + } else if command == "bra" { + // Dirac bra notation: \bra{psi} -> ⟨psi| + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "langle") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "|") + inner.innerList = self.buildInternal(true) + return inner + } else if command == "ket" { + // Dirac ket notation: \ket{psi} -> |psi⟩ + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "|") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "rangle") + inner.innerList = self.buildInternal(true) + return inner + } else if command == "braket" { + // Dirac braket notation: \braket{phi}{psi} -> ⟨phi|psi⟩ + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "langle") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "rangle") + // Build the inner content: phi | psi + let phi = self.buildInternal(true) + let psi = self.buildInternal(true) + let content = MTMathList() + if let phiList = phi { + for atom in phiList.atoms { + content.add(atom) + } + } + // Add the | separator + content.add(MTMathAtom(type: .ordinary, value: "|")) + if let psiList = psi { + for atom in psiList.atoms { + content.add(atom) + } + } + inner.innerList = content + return inner + } else if command == "operatorname" || command == "operatorname*" { + // \operatorname{name} creates a custom operator with proper spacing + // \operatorname*{name} creates an operator with limits above/below + let hasLimits = command.hasSuffix("*") + + // Parse the operator name + let content = self.buildInternal(true) + + // Convert the parsed content to a string + var operatorName = "" + if let atoms = content?.atoms { + for atom in atoms { + operatorName += atom.nucleus + } + } + + if operatorName.isEmpty { + let errorMessage = "Missing operator name for \\operatorname" + self.setError(.invalidCommand, message: errorMessage) + return nil + } + + return MTLargeOperator(value: operatorName, limits: hasLimits) + } else if command == "sqrt" { + // A sqrt command with one argument + let rad = MTRadical() + guard self.hasCharacters else { + rad.radicand = self.buildInternal(true) + return rad + } + let ch = self.getNextCharacter() + if (ch == "[") { + // special handling for sqrt[degree]{radicand} + rad.degree = self.buildInternal(false, stopChar:"]") + rad.radicand = self.buildInternal(true) + } else { + self.unlookCharacter() + rad.radicand = self.buildInternal(true) + } + return rad; + } else if command == "left" { + // Save the current inner while a new one gets built. + let oldInner = currentInnerAtom + currentInnerAtom = MTInner() + currentInnerAtom!.leftBoundary = self.getBoundaryAtom("left") + if currentInnerAtom!.leftBoundary == nil { + return nil; + } + currentInnerAtom!.innerList = self.buildInternal(false) + if currentInnerAtom!.rightBoundary == nil { + // A right node would have set the right boundary so we must be missing the right node. + let errorMessage = "Missing \\right" + self.setError(.missingRight, message:errorMessage) + return nil + } + // reinstate the old inner atom. + let newInner = currentInnerAtom; + currentInnerAtom = oldInner; + return newInner; + } else if command == "overline" { + // The overline command has 1 arguments + let over = MTOverLine() + over.innerList = self.buildInternal(true) + return over + } else if command == "underline" { + // The underline command has 1 arguments + let under = MTUnderLine() + under.innerList = self.buildInternal(true) + return under + } else if command == "substack" { + // \substack reads ONE braced argument containing rows separated by \\ + // Similar to how \frac reads {numerator}{denominator} + + // Read the braced content using standard pattern + let content = self.buildInternal(true) + + if content == nil { + return nil + } + + // The content may already be a table if \\ was encountered + // Check if we got a table from the \\ parsing + if content!.atoms.count == 1, let tableAtom = content!.atoms.first as? MTMathTable { + return tableAtom + } + + // Otherwise, single row - wrap in table + var rows = [[MTMathList]]() + rows.append([content!]) + + var error: NSError? = self.error + let table = MTMathAtomFactory.table(withEnvironment: nil, rows: rows, error: &error) + if table == nil && self.error == nil { + self.error = error + return nil + } + + return table + } else if command == "begin" { + let env = self.readEnvironment() + if env == nil { + return nil; + } + let table = self.buildTable(env: env, firstList:nil, isRow:false) + return table + } else if command == "color" { + // A color command has 2 arguments + let mathColor = MTMathColor() + let color = self.readColor() + if color == nil { + return nil; + } + mathColor.colorString = color! + mathColor.innerList = self.buildInternal(true) + return mathColor + } else if command == "textcolor" { + // A textcolor command has 2 arguments + let mathColor = MTMathTextColor() + let color = self.readColor() + if color == nil { + return nil; + } + mathColor.colorString = color! + mathColor.innerList = self.buildInternal(true) + return mathColor + } else if command == "colorbox" { + // A color command has 2 arguments + let mathColorbox = MTMathColorbox() + let color = self.readColor() + if color == nil { + return nil; + } + mathColorbox.colorString = color! + mathColorbox.innerList = self.buildInternal(true) + return mathColorbox + } else if command == "pmod" { + // A pmod command has 1 argument - creates (mod n) + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "(") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: ")") + + let innerList = MTMathList() + + // Add the "mod" operator (upright text) + let modOperator = MTMathAtomFactory.atom(forLatexSymbol: "mod")! + innerList.add(modOperator) + + // Add medium space between "mod" and argument (6mu) + let space = MTMathSpace(space: 6.0) + innerList.add(space) + + // Parse the argument from braces + let argument = self.buildInternal(true) + if let argList = argument { + innerList.append(argList) + } + + inner.innerList = innerList + return inner + } else if command == "not" { + // Handle \not command with lookahead for comprehensive negation support + let nextCommand = self.peekNextCommand() + + if let negatedUnicode = Self.notCombinations[nextCommand] { + self.consumeNextCommand() // Remove base symbol from stream + return MTMathAtom(type: .relation, value: negatedUnicode) + } else { + let errorMessage = "Unsupported \\not\\\(nextCommand) combination" + self.setError(.invalidCommand, message: errorMessage) + return nil + } + } else if let sizeMultiplier = Self.delimiterSizeCommands[command] { + // Handle \big, \Big, \bigg, \Bigg and their variants + let delim = self.readDelimiter() + if delim == nil { + let errorMessage = "Missing delimiter for \\\(command)" + self.setError(.missingDelimiter, message: errorMessage) + return nil + } + let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!) + if boundary == nil { + let errorMessage = "Invalid delimiter for \\\(command): \(delim!)" + self.setError(.invalidDelimiter, message: errorMessage) + return nil + } + + // Create MTInner with explicit delimiter height + let inner = MTInner() + + // Determine if this is a left, right, or middle delimiter based on command suffix + let isLeft = command.hasSuffix("l") + let isRight = command.hasSuffix("r") + // let isMiddle = command.hasSuffix("m") // For future use + + if isLeft { + inner.leftBoundary = boundary + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: ".") + } else if isRight { + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: ".") + inner.rightBoundary = boundary + } else { + // For \big, \Big, \bigg, \Bigg and \bigm variants, use the delimiter on both sides + // but with empty inner content - it's just a sized delimiter + inner.leftBoundary = boundary + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: ".") + } + + inner.innerList = MTMathList() + inner.delimiterHeight = sizeMultiplier // Store multiplier, typesetter will compute actual height + + return inner + } else { + let errorMessage = "Invalid command \\\(command)" + self.setError(.invalidCommand, message:errorMessage) + return nil; + } + } + + mutating func readColor() -> String? { + if !self.expectCharacter("{") { + // We didn't find an opening brace, so no env found. + self.setError(.characterNotFound, message:"Missing {") + return nil; + } + + // Ignore spaces and nonascii. + self.skipSpaces() + + // a string of all upper and lower case characters. + var mutable = "" + while self.hasCharacters { + let ch = self.getNextCharacter() + if ch == "#" || (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9") { + mutable.append(ch) // appendString:[NSString stringWithCharacters:&ch length:1]]; + } else { + // we went too far + self.unlookCharacter() + break; + } + } + + if !self.expectCharacter("}") { + // We didn't find an closing brace, so invalid format. + self.setError(.characterNotFound, message:"Missing }") + return nil; + } + return mutable; + } + + mutating func skipSpaces() { + while self.hasCharacters { + let ch = self.getNextCharacter().utf32Char + if ch < 0x21 || ch > 0x7E { + // skip non ascii characters and spaces + continue; + } else { + self.unlookCharacter() + return; + } + } + } + + static var fractionCommands: [String:[Character]] { + [ + "over": [], + "atop" : [], + "choose" : [ "(", ")"], + "brack" : [ "[", "]"], + "brace" : [ "{", "}"] + ] + } + + mutating func stopCommand(_ command: String, list:MTMathList, stopChar:Character?) -> MTMathList? { + if command == "right" { + if currentInnerAtom == nil { + let errorMessage = "Missing \\left"; + self.setError(.missingLeft, message:errorMessage) + return nil; + } + currentInnerAtom!.rightBoundary = self.getBoundaryAtom("right") + if currentInnerAtom!.rightBoundary == nil { + return nil; + } + // return the list read so far. + return list + } else if let delims = Self.fractionCommands[command] { + var frac:MTFraction! = nil; + if command == "over" { + frac = MTFraction() + } else { + frac = MTFraction(hasRule: false) + } + if delims.count == 2 { + frac.leftDelimiter = String(delims[0]) + frac.rightDelimiter = String(delims[1]) + } + frac.numerator = list; + frac.denominator = self.buildInternal(false, stopChar: stopChar) + if error != nil { + return nil; + } + let fracList = MTMathList() + fracList.add(frac) + return fracList + } else if command == "\\" || command == "cr" { + if currentEnv != nil { + // Stop the current list and increment the row count + currentEnv!.numRows+=1 + return list + } else { + // Create a new table with the current list and a default env + if let table = self.buildTable(env: nil, firstList:list, isRow:true) { + return MTMathList(atom: table) + } + } + } else if command == "end" { + if currentEnv == nil { + let errorMessage = "Missing \\begin"; + self.setError(.missingBegin, message:errorMessage) + return nil + } + let env = self.readEnvironment() + if env == nil { + return nil + } + if env! != currentEnv!.envName { + let errorMessage = "Begin environment name \(currentEnv!.envName ?? "(none)") does not match end name: \(env!)" + self.setError(.invalidEnv, message:errorMessage) + return nil + } + // Finish the current environment. + currentEnv!.ended = true + return list + } + return nil + } + + // Applies the modifier to the atom. Returns true if modifier applied. + mutating func applyModifier(_ modifier:String, atom:MTMathAtom?) -> Bool { + if modifier == "limits" { + if atom?.type != .largeOperator { + let errorMessage = "Limits can only be applied to an operator." + self.setError(.invalidLimits, message:errorMessage) + } else { + let op = atom as! MTLargeOperator + op.limits = true + } + return true + } else if modifier == "nolimits" { + if atom?.type != .largeOperator { + let errorMessage = "No limits can only be applied to an operator." + self.setError(.invalidLimits, message:errorMessage) + } else { + let op = atom as! MTLargeOperator + op.limits = false + } + return true + } + return false + } + + mutating func setError(_ code:MTParseErrors, message:String) { + // Only record the first error. + if error == nil { + error = NSError(domain: MTParseError, code: code.rawValue, userInfo: [ NSLocalizedDescriptionKey : message ]) + } + } + + mutating func atom(forCommand command: String) -> MTMathAtom? { + if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) { + return atom + } + if let accent = MTMathAtomFactory.accent(withName: command) { + accent.innerList = self.buildInternal(true) + return accent + } else if command == "frac" { + let frac = MTFraction() + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac + } else if command == "cfrac" { + let frac = MTFraction() + frac.isContinuedFraction = true + + // Parse optional alignment parameter [l], [r], [c] + skipSpaces() + if hasCharacters && string[currentCharIndex] == "[" { + _ = getNextCharacter() // consume '[' + if hasCharacters { + let alignmentChar = getNextCharacter() + if alignmentChar == "l" || alignmentChar == "r" || alignmentChar == "c" { + frac.alignment = String(alignmentChar) + } + } + // Consume closing ']' + if hasCharacters && string[currentCharIndex] == "]" { + _ = getNextCharacter() + } + } + + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + return frac + } else if command == "dfrac" { + // Display-style fraction command has 2 arguments + let frac = MTFraction() + let numerator = self.buildInternal(true) + let denominator = self.buildInternal(true) + + // Prepend \displaystyle to force display mode rendering + let displayStyle = MTMathStyle(style: .display) + numerator?.insert(displayStyle, at: 0) + denominator?.insert(displayStyle, at: 0) + + frac.numerator = numerator + frac.denominator = denominator + return frac + } else if command == "tfrac" { + // Text-style fraction command has 2 arguments + let frac = MTFraction() + let numerator = self.buildInternal(true) + let denominator = self.buildInternal(true) + + // Prepend \textstyle to force text mode rendering + let textStyle = MTMathStyle(style: .text) + numerator?.insert(textStyle, at: 0) + denominator?.insert(textStyle, at: 0) + + frac.numerator = numerator + frac.denominator = denominator + return frac + } else if command == "binom" { + let frac = MTFraction(hasRule: false) + frac.numerator = self.buildInternal(true) + frac.denominator = self.buildInternal(true) + frac.leftDelimiter = "(" + frac.rightDelimiter = ")" + return frac + } else if command == "bra" { + // Dirac bra notation: \bra{psi} -> ⟨psi| + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "langle") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "|") + inner.innerList = self.buildInternal(true) + return inner + } else if command == "ket" { + // Dirac ket notation: \ket{psi} -> |psi⟩ + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "|") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "rangle") + inner.innerList = self.buildInternal(true) + return inner + } else if command == "braket" { + // Dirac braket notation: \braket{phi}{psi} -> ⟨phi|psi⟩ + let inner = MTInner() + inner.leftBoundary = MTMathAtomFactory.boundary(forDelimiter: "langle") + inner.rightBoundary = MTMathAtomFactory.boundary(forDelimiter: "rangle") + let phi = self.buildInternal(true) + let psi = self.buildInternal(true) + let content = MTMathList() + if let phiList = phi { + for atom in phiList.atoms { + content.add(atom) + } + } + content.add(MTMathAtom(type: .ordinary, value: "|")) + if let psiList = psi { + for atom in psiList.atoms { + content.add(atom) + } + } + inner.innerList = content + return inner + } else if command == "operatorname" || command == "operatorname*" { + // \operatorname{name} creates a custom operator with proper spacing + // \operatorname*{name} creates an operator with limits above/below + let hasLimits = command.hasSuffix("*") + + let content = self.buildInternal(true) + var operatorName = "" + if let atoms = content?.atoms { + for atom in atoms { + operatorName += atom.nucleus + } + } + if operatorName.isEmpty { + self.setError(.invalidCommand, message: "Missing operator name for \\operatorname") + return nil + } + return MTLargeOperator(value: operatorName, limits: hasLimits) + } else if command == "sqrt" { + let rad = MTRadical() + guard self.hasCharacters else { + rad.radicand = self.buildInternal(true) + return rad + } + let char = self.getNextCharacter() + if char == "[" { + rad.degree = self.buildInternal(false, stopChar: "]") + rad.radicand = self.buildInternal(true) + } else { + self.unlookCharacter() + rad.radicand = self.buildInternal(true) + } + return rad + } else if command == "left" { + let oldInner = self.currentInnerAtom + self.currentInnerAtom = MTInner() + self.currentInnerAtom?.leftBoundary = self.getBoundaryAtom("left") + if self.currentInnerAtom?.leftBoundary == nil { + return nil + } + self.currentInnerAtom!.innerList = self.buildInternal(false) + if self.currentInnerAtom?.rightBoundary == nil { + self.setError(.missingRight, message: "Missing \\right") + return nil + } + let newInner = self.currentInnerAtom + currentInnerAtom = oldInner + return newInner + } else if command == "overline" { + let over = MTOverLine() + over.innerList = self.buildInternal(true) + + return over + } else if command == "underline" { + let under = MTUnderLine() + under.innerList = self.buildInternal(true) + + return under + } else if command == "begin" { + if let env = self.readEnvironment() { + // Check if this is a starred matrix environment and read optional alignment + var alignment: MTColumnAlignment? = nil + if env.hasSuffix("*") { + alignment = self.readOptionalAlignment() + if self.error != nil { + return nil + } + } + + let table = self.buildTable(env: env, alignment: alignment, firstList: nil, isRow: false) + return table + } else { + return nil + } + } else if command == "color" { + // A color command has 2 arguments + let mathColor = MTMathColor() + mathColor.colorString = self.readColor()! + mathColor.innerList = self.buildInternal(true) + return mathColor + } else if command == "colorbox" { + // A color command has 2 arguments + let mathColorbox = MTMathColorbox() + mathColorbox.colorString = self.readColor()! + mathColorbox.innerList = self.buildInternal(true) + return mathColorbox + } else { + self.setError(.invalidCommand, message: "Invalid command \\\(command)") + return nil + } + } + + mutating func readEnvironment() -> String? { + if !self.expectCharacter("{") { + // We didn't find an opening brace, so no env found. + self.setError(.characterNotFound, message: "Missing {") + return nil + } + + self.skipSpaces() + let env = self.readString() + + if !self.expectCharacter("}") { + // We didn"t find an closing brace, so invalid format. + self.setError(.characterNotFound, message: "Missing }") + return nil; + } + return env + } + + /// Reads optional alignment parameter for starred matrix environments: [r], [l], or [c] + mutating func readOptionalAlignment() -> MTColumnAlignment? { + self.skipSpaces() + + // Check if there's an opening bracket + guard hasCharacters && string[currentCharIndex] == "[" else { + return nil + } + + _ = getNextCharacter() // consume '[' + self.skipSpaces() + + guard hasCharacters else { + self.setError(.characterNotFound, message: "Missing alignment specifier after [") + return nil + } + + let alignChar = getNextCharacter() + let alignment: MTColumnAlignment? + + switch alignChar { + case "l": + alignment = .left + case "c": + alignment = .center + case "r": + alignment = .right + default: + self.setError(.invalidEnv, message: "Invalid alignment specifier: \(alignChar). Must be l, c, or r") + return nil + } + + self.skipSpaces() + + if !self.expectCharacter("]") { + self.setError(.characterNotFound, message: "Missing ] after alignment specifier") + return nil + } + + return alignment + } + + func MTAssertNotSpace(_ ch: Character) { + assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)") + } + + mutating func buildTable(env: String?, alignment: MTColumnAlignment? = nil, firstList: MTMathList?, isRow: Bool) -> MTMathAtom? { + // Save the current env till an new one gets built. + let oldEnv = self.currentEnv + + currentEnv = MTEnvProperties(name: env, alignment: alignment) + + var currentRow = 0 + var currentCol = 0 + + var rows = [[MTMathList]]() + rows.append([MTMathList]()) + if firstList != nil { + rows[currentRow].append(firstList!) + if isRow { + currentEnv!.numRows+=1 + currentRow+=1 + rows.append([MTMathList]()) + } else { + currentCol+=1 + } + } + while !currentEnv!.ended && self.hasCharacters { + let list = self.buildInternal(false) + if list == nil { + // If there is an error building the list, bail out early. + return nil + } + rows[currentRow].append(list!) + currentCol+=1 + if currentEnv!.numRows > currentRow { + currentRow = currentEnv!.numRows + rows.append([MTMathList]()) + currentCol = 0 + } + } + + if !currentEnv!.ended && currentEnv!.envName != nil { + self.setError(.missingEnd, message: "Missing \\end") + return nil + } + + var error:NSError? = self.error + let table = MTMathAtomFactory.table(withEnvironment: currentEnv?.envName, alignment: currentEnv?.alignment, rows: rows, error: &error) + if table == nil && self.error == nil { + self.error = error + return nil + } + self.currentEnv = oldEnv + return table + } + + mutating func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? { + let delim = self.readDelimiter() + if delim == nil { + let errorMessage = "Missing delimiter for \\\(delimiterType)" + self.setError(.missingDelimiter, message:errorMessage) + return nil + } + let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!) + if boundary == nil { + let errorMessage = "Invalid delimiter for \(delimiterType): \(delim!)" + self.setError(.invalidDelimiter, message:errorMessage) + return nil + } + return boundary + } + + mutating func readDelimiter() -> String? { + self.skipSpaces() + while self.hasCharacters { + let char = self.getNextCharacter() + MTAssertNotSpace(char) + if char == "\\" { + let command = self.readCommand() + if command == "|" { + return "||" + } + return command + } else { + return String(char) + } + } + return nil + } + + mutating func readCommand() -> String { + let singleChars = "{}$#%_| ,>;!\\" + if self.hasCharacters { + let char = self.getNextCharacter() + if let _ = singleChars.firstIndex(of: char) { + return String(char) + } else { + self.unlookCharacter() + } + } + return self.readString() + } + + mutating func readString() -> String { + // a string of all upper and lower case characters (and asterisks for starred environments) + var output = "" + while self.hasCharacters { + let char = self.getNextCharacter() + if char.isLowercase || char.isUppercase || char == "*" { + output.append(char) + } else { + self.unlookCharacter() + break + } + } + return output + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathListDisplay.swift b/third-party/SwiftMath/Sources/MathRender/MTMathListDisplay.swift new file mode 100755 index 0000000000..a0fa47c879 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathListDisplay.swift @@ -0,0 +1,881 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import QuartzCore +import CoreText +import SwiftUI + +func isIos6Supported() -> Bool { + if !MTDisplay.initialized { +#if os(iOS) || os(visionOS) + let reqSysVer = "6.0" + let currSysVer = UIDevice.current.systemVersion + if currSysVer.compare(reqSysVer, options: .numeric) != .orderedAscending { + MTDisplay.supported = true + } +#else + MTDisplay.supported = true +#endif + MTDisplay.initialized = true + } + return MTDisplay.supported +} + +// The Downshift protocol allows an MTDisplay to be shifted down by a given amount. +protocol DownShift { + var shiftDown:CGFloat { set get } +} + +// MARK: - MTDisplay + +/// The base class for rendering a math equation. +public class MTDisplay:NSObject { + + // needed for isIos6Supported() func above + static var initialized = false + static var supported = false + + /// Draws itself in the given graphics context. + public func draw(_ context:CGContext) { + if self.localBackgroundColor != nil { + context.saveGState() + context.setBlendMode(.normal) + context.setFillColor(self.localBackgroundColor!.cgColor) + context.fill(self.displayBounds()) + context.restoreGState() + } + } + + /// Gets the bounding rectangle for the MTDisplay + func displayBounds() -> CGRect { + CGRectMake(self.position.x, self.position.y - self.descent, self.width, self.ascent + self.descent) + } + + /// For debugging. Shows the object in quick look in Xcode. +#if os(iOS) || os(visionOS) + func debugQuickLookObject() -> Any { + let size = CGSizeMake(self.width, self.ascent + self.descent); + UIGraphicsBeginImageContext(size); + + // get a reference to that context we created + let context = UIGraphicsGetCurrentContext()! + // translate/flip the graphics context (for transforming from CG* coords to UI* coords + context.translateBy(x: 0, y: size.height); + context.scaleBy(x: 1.0, y: -1.0); + // move the position to (0,0) + context.translateBy(x: -self.position.x, y: -self.position.y); + + // Move the line up by self.descent + context.translateBy(x: 0, y: self.descent); + // Draw self on context + self.draw(context) + + // generate a new UIImage from the graphics context we drew onto + let img = UIGraphicsGetImageFromCurrentImageContext() + return img as Any + } +#endif + + /// The distance from the axis to the top of the display + public var ascent:CGFloat = 0 + /// The distance from the axis to the bottom of the display + public var descent:CGFloat = 0 + /// The width of the display + public var width:CGFloat = 0 + /// Position of the display with respect to the parent view or display. + var position = CGPoint.zero + /// The range of characters supported by this item + public var range:NSRange=NSMakeRange(0, 0) + /// Whether the display has a subscript/superscript following it. + public var hasScript:Bool = false + /// The text color for this display + var textColor: MTColor? + /// The local color, if the color was mutated local with the color command + var localTextColor: MTColor? + /// The background color for this display + var localBackgroundColor: MTColor? + +} + +/// Special class to be inherited from that implements the DownShift protocol +class MTDisplayDS : MTDisplay, DownShift { + + var shiftDown: CGFloat = 0 + +} + +// MARK: - MTCTLineDisplay + +/// A rendering of a single CTLine as an MTDisplay +public class MTCTLineDisplay : MTDisplay { + + /// The CTLine being displayed + public var line:CTLine! + /// The attributed string used to generate the CTLineRef. Note setting this does not reset the dimensions of + /// the display. So set only when + var attributedString:NSAttributedString? { + didSet { + line = CTLineCreateWithAttributedString(attributedString!) + } + } + + /// An array of MTMathAtoms that this CTLine displays. Used for indexing back into the MTMathList + public fileprivate(set) var atoms = [MTMathAtom]() + + init(withString attrString:NSAttributedString?, position:CGPoint, range:NSRange, font:MTFont?, atoms:[MTMathAtom]) { + super.init() + self.position = position + self.attributedString = attrString + self.line = CTLineCreateWithAttributedString(attrString!) + self.range = range + self.atoms = atoms + // We can't use typographic bounds here as the ascent and descent returned are for the font and not for the line. + // CRITICAL FIX for accented character clipping: + // Use the MAXIMUM of typographic width and visual width to account for glyph overhang. + // - Typographic width = advance width (how far the cursor moves) + // - Visual width = actual glyph extent (CGRectGetMaxX of glyph path bounds) + // Some glyphs (especially italic/oblique accented characters) extend beyond their advance width. + // Using max() ensures we account for overhang while maintaining proper spacing for normal glyphs. + let typographicWidth = CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil)) + if isIos6Supported() { + let bounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds) + self.ascent = max(0, CGRectGetMaxY(bounds) - 0); + self.descent = max(0, 0 - CGRectGetMinY(bounds)); + // Use the maximum of visual and typographic width to handle both: + // 1. Overhanging glyphs (visual > typographic) - prevents clipping + // 2. Normal glyphs (typographic >= visual) - maintains correct spacing + let visualWidth = CGRectGetMaxX(bounds) + self.width = max(typographicWidth, visualWidth); + } else { + // Our own implementation of the ios6 function to get glyph path bounds. + self.computeDimensions(font, typographicWidth: typographicWidth) + } + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + let attrStr = attributedString!.mutableCopy() as! NSMutableAttributedString + let foregroundColor = NSAttributedString.Key(kCTForegroundColorAttributeName as String) + attrStr.addAttribute(foregroundColor, value:self.textColor!.cgColor, range:NSMakeRange(0, attrStr.length)) + self.attributedString = attrStr + } + get { super.textColor } + } + + func computeDimensions(_ font:MTFont?, typographicWidth: CGFloat) { + let runs = CTLineGetGlyphRuns(line) as NSArray + var maxVisualWidth: CGFloat = 0 + var currentX: CGFloat = 0 + + for obj in runs { + let run = obj as! CTRun? + let numGlyphs = CTRunGetGlyphCount(run!) + var glyphs = [CGGlyph]() + glyphs.reserveCapacity(numGlyphs) + CTRunGetGlyphs(run!, CFRangeMake(0, numGlyphs), &glyphs); + let bounds = CTFontGetBoundingRectsForGlyphs(font!.ctFont, .horizontal, glyphs, nil, numGlyphs); + let ascent = max(0, CGRectGetMaxY(bounds) - 0); + // Descent is how much the line goes below the origin. However if the line is all above the origin, then descent can't be negative. + let descent = max(0, 0 - CGRectGetMinY(bounds)); + if (ascent > self.ascent) { + self.ascent = ascent; + } + if (descent > self.descent) { + self.descent = descent; + } + + // Calculate visual width using glyph extent + // Get the rightmost edge of this run's glyphs + let runVisualWidth = CGRectGetMaxX(bounds) + let runRightEdge = currentX + runVisualWidth + + // Get advances to know where next run starts + var advances = [CGSize](repeating: CGSize.zero, count: numGlyphs) + CTRunGetAdvances(run!, CFRangeMake(0, numGlyphs), &advances) + for advance in advances { + currentX += advance.width + } + + if (runRightEdge > maxVisualWidth) { + maxVisualWidth = runRightEdge + } + } + + // Use maximum of typographic and visual width + self.width = max(typographicWidth, maxVisualWidth) + } + + override public func draw(_ context: CGContext) { + super.draw(context) + context.saveGState() + + context.textPosition = self.position + CTLineDraw(line, context) + + context.restoreGState() + } + +} + +// MARK: - MTMathListDisplay + +/// An MTLine is a rendered form of MTMathList in one line. +/// It can render itself using the draw method. +public class MTMathListDisplay : MTDisplay { + + /** + The type of position for a line, i.e. subscript/superscript or regular. + */ + public enum LinePosition : Int { + /// Regular + case regular + /// Positioned at a subscript + case ssubscript + /// Positioned at a superscript + case superscript + } + + /// Where the line is positioned + public var type:LinePosition = .regular + /// An array of MTDisplays which are positioned relative to the position of the + /// the current display. + public fileprivate(set) var subDisplays = [MTDisplay]() + /// If a subscript or superscript this denotes the location in the parent MTList. For a + /// regular list this is NSNotFound + public var index: Int = 0 + + init(withDisplays displays:[MTDisplay], range:NSRange) { + super.init() + self.subDisplays = displays + self.position = CGPoint.zero + self.type = .regular + self.index = NSNotFound + self.range = range + self.recomputeDimensions() + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + for displayAtom in self.subDisplays { + if displayAtom.localTextColor == nil { + displayAtom.textColor = newValue + } else { + displayAtom.textColor = displayAtom.localTextColor + } + } + } + get { super.textColor } + } + + override public func draw(_ context: CGContext) { + super.draw(context) + context.saveGState() + + // Make the current position the origin as all the positions of the sub atoms are relative to the origin. + context.translateBy(x: self.position.x, y: self.position.y) + context.textPosition = CGPoint.zero + + // draw each atom separately + for displayAtom in self.subDisplays { + displayAtom.draw(context) + } + + context.restoreGState() + } + + func recomputeDimensions() { + var max_ascent:CGFloat = 0 + var max_descent:CGFloat = 0 + var max_width:CGFloat = 0 + for atom in self.subDisplays { + let ascent = max(0, atom.position.y + atom.ascent); + if (ascent > max_ascent) { + max_ascent = ascent; + } + + let descent = max(0, 0 - (atom.position.y - atom.descent)); + if (descent > max_descent) { + max_descent = descent; + } + let width = atom.width + atom.position.x; + if (width > max_width) { + max_width = width; + } + } + self.ascent = max_ascent; + self.descent = max_descent; + self.width = max_width; + } + +} + +// MARK: - MTFractionDisplay + +/// Rendering of an MTFraction as an MTDisplay +public class MTFractionDisplay : MTDisplay { + + /** A display representing the numerator of the fraction. Its position is relative + to the parent and is not treated as a sub-display. + */ + public fileprivate(set) var numerator:MTMathListDisplay? + /** A display representing the denominator of the fraction. Its position is relative + to the parent is not treated as a sub-display. + */ + public fileprivate(set) var denominator:MTMathListDisplay? + + var numeratorUp:CGFloat=0 { didSet { self.updateNumeratorPosition() } } + var denominatorDown:CGFloat=0 { didSet { self.updateDenominatorPosition() } } + var linePosition:CGFloat=0 + var lineThickness:CGFloat=0 + + init(withNumerator numerator:MTMathListDisplay?, denominator:MTMathListDisplay?, position:CGPoint, range:NSRange) { + super.init() + self.numerator = numerator; + self.denominator = denominator; + self.position = position; + + // CRITICAL FIX: Handle invalid ranges gracefully + // When table cells are typeset independently with maxWidth, atoms may have + // ranges that are invalid in the cell's context (e.g., (0,0) or other issues) + // Instead of crashing with assertion, normalize the range + if range.length == 0 { + // Create a dummy range with length 1 at the given location + self.range = NSMakeRange(range.location, 1) + } else { + self.range = range; + // Still assert for debugging if range length is something unexpected + assert(self.range.length == 1, "Fraction range length not 1 - range (\(range.location), \(range.length)") + } + } + + override public var ascent:CGFloat { + set { super.ascent = newValue } + get { (numerator?.ascent ?? 0) + self.numeratorUp } + } + + override public var descent:CGFloat { + set { super.descent = newValue } + get { (denominator?.descent ?? 0) + self.denominatorDown } + } + + override public var width:CGFloat { + set { super.width = newValue } + get { max(numerator?.width ?? 0, denominator?.width ?? 0) } + } + + func updateDenominatorPosition() { + guard denominator != nil else { return } + denominator!.position = CGPointMake(self.position.x + (self.width - denominator!.width)/2, self.position.y - self.denominatorDown) + } + + func updateNumeratorPosition() { + guard numerator != nil else { return } + numerator!.position = CGPointMake(self.position.x + (self.width - numerator!.width)/2, self.position.y + self.numeratorUp) + } + + override var position: CGPoint { + set { + super.position = newValue + self.updateDenominatorPosition() + self.updateNumeratorPosition() + } + get { super.position } + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + numerator?.textColor = newValue + denominator?.textColor = newValue + } + get { super.textColor } + } + + override public func draw(_ context:CGContext) { + super.draw(context) + numerator?.draw(context) + denominator?.draw(context) + + context.saveGState() + + self.textColor?.setStroke() + + // draw the horizontal line + // Note: line thickness of 0 draws the thinnest possible line - we want no line so check for 0s + if self.lineThickness > 0 { + let path = MTBezierPath() + path.move(to: CGPointMake(self.position.x, self.position.y + self.linePosition)) + path.addLine(to: CGPointMake(self.position.x + self.width, self.position.y + self.linePosition)) + path.lineWidth = self.lineThickness + path.stroke() + } + + context.restoreGState() + } + +} + +// MARK: - MTRadicalDisplay + +/// Rendering of an MTRadical as an MTDisplay +class MTRadicalDisplay : MTDisplay { + + /** A display representing the radicand of the radical. Its position is relative + to the parent is not treated as a sub-display. + */ + public fileprivate(set) var radicand:MTMathListDisplay? + /** A display representing the degree of the radical. Its position is relative + to the parent is not treated as a sub-display. + */ + public fileprivate(set) var degree:MTMathListDisplay? + + override var position: CGPoint { + set { + super.position = newValue + self.updateRadicandPosition() + } + get { super.position } + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + self.radicand?.textColor = newValue + self.degree?.textColor = newValue + } + get { super.textColor } + } + + private var _radicalGlyph:MTDisplay? + private var _radicalShift:CGFloat=0 + + var topKern:CGFloat=0 + var lineThickness:CGFloat=0 + + init(withRadicand radicand:MTMathListDisplay?, glyph:MTDisplay, position:CGPoint, range:NSRange) { + super.init() + self.radicand = radicand + _radicalGlyph = glyph + _radicalShift = 0 + + self.position = position + self.range = range + } + + func setDegree(_ degree:MTMathListDisplay?, fontMetrics:MTFontMathTable?) { + // sets up the degree of the radical + var kernBefore = fontMetrics!.radicalKernBeforeDegree; + let kernAfter = fontMetrics!.radicalKernAfterDegree; + let raise = fontMetrics!.radicalDegreeBottomRaisePercent * (self.ascent - self.descent); + + // The layout is: + // kernBefore, raise, degree, kernAfter, radical + self.degree = degree; + + // the radical is now shifted by kernBefore + degree.width + kernAfter + _radicalShift = kernBefore + degree!.width + kernAfter; + if _radicalShift < 0 { + // we can't have the radical shift backwards, so instead we increase the kernBefore such + // that _radicalShift will be 0. + kernBefore -= _radicalShift; + _radicalShift = 0; + } + + // Note: position of degree is relative to parent. + self.degree!.position = CGPointMake(self.position.x + kernBefore, self.position.y + raise); + // Update the width by the _radicalShift + self.width = _radicalShift + _radicalGlyph!.width + self.radicand!.width; + // update the position of the radicand + self.updateRadicandPosition() + } + + func updateRadicandPosition() { + // The position of the radicand includes the position of the MTRadicalDisplay + // This is to make the positioning of the radical consistent with fractions and + // have the cursor position finding algorithm work correctly. + // move the radicand by the width of the radical sign + self.radicand!.position = CGPointMake(self.position.x + _radicalShift + _radicalGlyph!.width, self.position.y); + } + + override public func draw(_ context: CGContext) { + super.draw(context) + + // draw the radicand & degree at its position + self.radicand?.draw(context) + self.degree?.draw(context) + + context.saveGState(); + self.textColor?.setStroke() + self.textColor?.setFill() + + // Make the current position the origin as all the positions of the sub atoms are relative to the origin. + context.translateBy(x: self.position.x + _radicalShift, y: self.position.y); + context.textPosition = CGPoint.zero + + // Draw the glyph. + _radicalGlyph?.draw(context) + + // Draw the VBOX + // for the kern of, we don't need to draw anything. + let heightFromTop = topKern; + + // draw the horizontal line with the given thickness + let path = MTBezierPath() + let lineStart = CGPointMake(_radicalGlyph!.width, self.ascent - heightFromTop - self.lineThickness / 2); // subtract half the line thickness to center the line + let lineEnd = CGPointMake(lineStart.x + self.radicand!.width, lineStart.y); + path.move(to: lineStart) + path.addLine(to: lineEnd) + path.lineWidth = lineThickness + path.lineCapStyle = .round + path.stroke() + + context.restoreGState(); + } +} + +// MARK: - MTGlyphDisplay + +/// Rendering a glyph as a display +class MTGlyphDisplay : MTDisplayDS { + + var glyph:CGGlyph! + var font:MTFont? + /// Horizontal scale factor for stretching glyphs (1.0 = no scaling) + var scaleX: CGFloat = 1.0 + + init(withGlpyh glyph:CGGlyph, range:NSRange, font:MTFont?) { + super.init() + self.font = font + self.glyph = glyph + + self.position = CGPoint.zero + self.range = range + } + + override public func draw(_ context: CGContext) { + super.draw(context) + context.saveGState() + + self.textColor?.setFill() + + // Make the current position the origin as all the positions of the sub atoms are relative to the origin. + + context.translateBy(x: self.position.x, y: self.position.y - self.shiftDown); + + // Apply horizontal scaling if needed (for stretchy arrows) + if scaleX != 1.0 { + context.scaleBy(x: scaleX, y: 1.0) + } + + context.textPosition = CGPoint.zero + + var pos = CGPoint.zero + CTFontDrawGlyphs(font!.ctFont, &glyph, &pos, 1, context); + + context.restoreGState(); + } + + override var ascent:CGFloat { + set { super.ascent = newValue } + get { super.ascent - self.shiftDown } + } + + override var descent:CGFloat { + set { super.descent = newValue } + get { super.descent + self.shiftDown } + } +} + +// MARK: - MTGlyphConstructionDisplay + +class MTGlyphConstructionDisplay:MTDisplayDS { + var glyphs = [CGGlyph]() + var positions = [CGPoint]() + var font:MTFont? + var numGlyphs:Int=0 + + init(withGlyphs glyphs:[NSNumber?], offsets:[NSNumber?], font:MTFont?) { + super.init() + assert(glyphs.count == offsets.count, "Glyphs and offsets need to match") + self.numGlyphs = glyphs.count; + self.glyphs = [CGGlyph](repeating: CGGlyph(), count: self.numGlyphs) //malloc(sizeof(CGGlyph) * _numGlyphs); + self.positions = [CGPoint](repeating: CGPoint.zero, count: self.numGlyphs) //malloc(sizeof(CGPoint) * _numGlyphs); + for i in 0 ..< self.numGlyphs { + self.glyphs[i] = glyphs[i]!.uint16Value + self.positions[i] = CGPointMake(0, CGFloat(offsets[i]!.floatValue)) + } + self.font = font + self.position = CGPoint.zero + } + + override public func draw(_ context: CGContext) { + super.draw(context) + context.saveGState() + + self.textColor?.setFill() + + // Make the current position the origin as all the positions of the sub atoms are relative to the origin. + context.translateBy(x: self.position.x, y: self.position.y - self.shiftDown) + context.textPosition = CGPoint.zero + + // Draw the glyphs. + CTFontDrawGlyphs(font!.ctFont, glyphs, positions, numGlyphs, context) + + context.restoreGState() + } + + override var ascent:CGFloat { + set { super.ascent = newValue } + get { super.ascent - self.shiftDown } + } + + override var descent:CGFloat { + set { super.descent = newValue } + get { super.descent + self.shiftDown } + } + +} + +// MARK: - MTLargeOpLimitsDisplay + +/// Rendering a large operator with limits as an MTDisplay +class MTLargeOpLimitsDisplay : MTDisplay { + + /** A display representing the upper limit of the large operator. Its position is relative + to the parent is not treated as a sub-display. + */ + var upperLimit:MTMathListDisplay? + /** A display representing the lower limit of the large operator. Its position is relative + to the parent is not treated as a sub-display. + */ + var lowerLimit:MTMathListDisplay? + + var limitShift:CGFloat=0 + var upperLimitGap:CGFloat=0 { didSet { self.updateUpperLimitPosition() } } + var lowerLimitGap:CGFloat=0 { didSet { self.updateLowerLimitPosition() } } + var extraPadding:CGFloat=0 + + var nucleus:MTDisplay? + + init(withNucleus nucleus:MTDisplay?, upperLimit:MTMathListDisplay?, lowerLimit:MTMathListDisplay?, limitShift:CGFloat, extraPadding:CGFloat) { + super.init() + self.upperLimit = upperLimit; + self.lowerLimit = lowerLimit; + self.nucleus = nucleus; + + var maxWidth = max(nucleus!.width, upperLimit?.width ?? 0) + maxWidth = max(maxWidth, lowerLimit?.width ?? 0) + + self.limitShift = limitShift; + self.upperLimitGap = 0; + self.lowerLimitGap = 0; + self.extraPadding = extraPadding; // corresponds to \xi_13 in TeX + self.width = maxWidth; + } + + override var ascent:CGFloat { + set { super.ascent = newValue } + get { + if self.upperLimit != nil { + return nucleus!.ascent + extraPadding + self.upperLimit!.ascent + upperLimitGap + self.upperLimit!.descent + } else { + return nucleus!.ascent + } + } + } + + override var descent:CGFloat { + set { super.descent = newValue } + get { + if self.lowerLimit != nil { + return nucleus!.descent + extraPadding + lowerLimitGap + self.lowerLimit!.descent + self.lowerLimit!.ascent; + } else { + return nucleus!.descent; + } + } + } + + override var position: CGPoint { + set { + super.position = newValue + self.updateLowerLimitPosition() + self.updateUpperLimitPosition() + self.updateNucleusPosition() + } + get { super.position } + } + + func updateLowerLimitPosition() { + if self.lowerLimit != nil { + // The position of the lower limit includes the position of the MTLargeOpLimitsDisplay + // This is to make the positioning of the radical consistent with fractions and radicals + // Move the starting point to below the nucleus leaving a gap of _lowerLimitGap and subtract + // the ascent to to get the baseline. Also center and shift it to the left by _limitShift. + self.lowerLimit!.position = CGPointMake(self.position.x - limitShift + (self.width - lowerLimit!.width)/2, + self.position.y - nucleus!.descent - lowerLimitGap - self.lowerLimit!.ascent); + } + } + + func updateUpperLimitPosition() { + if self.upperLimit != nil { + // The position of the upper limit includes the position of the MTLargeOpLimitsDisplay + // This is to make the positioning of the radical consistent with fractions and radicals + // Move the starting point to above the nucleus leaving a gap of _upperLimitGap and add + // the descent to to get the baseline. Also center and shift it to the right by _limitShift. + self.upperLimit!.position = CGPointMake(self.position.x + limitShift + (self.width - self.upperLimit!.width)/2, + self.position.y + nucleus!.ascent + upperLimitGap + self.upperLimit!.descent); + } + } + + func updateNucleusPosition() { + // Center the nucleus + nucleus?.position = CGPointMake(self.position.x + (self.width - nucleus!.width)/2, self.position.y); + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + self.upperLimit?.textColor = newValue + self.lowerLimit?.textColor = newValue + nucleus?.textColor = newValue + } + get { super.textColor } + } + + override func draw(_ context:CGContext) { + super.draw(context) + // Draw the elements. + self.upperLimit?.draw(context) + self.lowerLimit?.draw(context) + nucleus?.draw(context) + } + +} + +// MARK: - MTLineDisplay + +/// Rendering of an list with an overline or underline +class MTLineDisplay : MTDisplay { + + /** A display representing the inner list that is underlined. Its position is relative + to the parent is not treated as a sub-display. + */ + var inner:MTMathListDisplay? + var lineShiftUp:CGFloat=0 + var lineThickness:CGFloat=0 + + init(withInner inner:MTMathListDisplay?, position:CGPoint, range:NSRange) { + super.init() + self.inner = inner; + + self.position = position; + self.range = range; + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + inner?.textColor = newValue + } + get { super.textColor } + } + + override var position: CGPoint { + set { + super.position = newValue + self.updateInnerPosition() + } + get { super.position } + } + + override func draw(_ context:CGContext) { + super.draw(context) + self.inner?.draw(context) + + context.saveGState(); + + self.textColor?.setStroke() + + // draw the horizontal line + let path = MTBezierPath() + let lineStart = CGPointMake(self.position.x, self.position.y + self.lineShiftUp); + let lineEnd = CGPointMake(lineStart.x + self.inner!.width, lineStart.y); + path.move(to:lineStart) + path.addLine(to: lineEnd) + path.lineWidth = self.lineThickness; + path.stroke() + + context.restoreGState(); + } + + func updateInnerPosition() { + self.inner?.position = CGPointMake(self.position.x, self.position.y); + } + +} + +// MARK: - MTAccentDisplay + +/// Rendering an accent as a display +class MTAccentDisplay : MTDisplay { + + /** A display representing the inner list that is accented. Its position is relative + to the parent is not treated as a sub-display. + */ + var accentee:MTMathListDisplay? + + /** A display representing the accent. Its position is relative to the current display. + */ + var accent:MTGlyphDisplay? + + init(withAccent glyph:MTGlyphDisplay?, accentee:MTMathListDisplay?, range:NSRange) { + super.init() + self.accent = glyph + self.accentee = accentee + self.accentee?.position = CGPoint.zero + self.range = range + } + + override var textColor: MTColor? { + set { + super.textColor = newValue + accentee?.textColor = newValue + accent?.textColor = newValue + } + get { super.textColor } + } + + override var position: CGPoint { + set { + super.position = newValue + self.updateAccenteePosition() + } + get { super.position } + } + + func updateAccenteePosition() { + self.accentee?.position = CGPointMake(self.position.x, self.position.y); + } + + override func draw(_ context:CGContext) { + super.draw(context) + self.accentee?.draw(context) + + context.saveGState(); + context.translateBy(x: self.position.x, y: self.position.y); + context.textPosition = CGPoint.zero + + self.accent?.draw(context) + + context.restoreGState(); + } + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathListIndex.swift b/third-party/SwiftMath/Sources/MathRender/MTMathListIndex.swift new file mode 100755 index 0000000000..34bd6bfbb0 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathListIndex.swift @@ -0,0 +1,197 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +/** + * An index that points to a particular character in the MTMathList. The index is a LinkedList that represents + * a path from the beginning of the MTMathList to reach a particular atom in the list. The next node of the path + * is represented by the subIndex. The path terminates when the subIndex is nil. + * + * If there is a subIndex, the subIndexType denotes what branch the path takes (i.e. superscript, subscript, + * numerator, denominator etc.). + * e.g in the expression 25^{2/4} the index of the character 4 is represented as: + * (1, superscript) -> (0, denominator) -> (0, none) + * This can be interpreted as start at index 1 (i.e. the 5) go up to the superscript. + * Then look at index 0 (i.e. 2/4) and go to the denominator. Then look up index 0 (i.e. the 4) which this final + * index. + * + * The level of an index is the number of nodes in the LinkedList to get to the final path. + */ +public class MTMathListIndex { + + /** + The type of the subindex. + + The type of the subindex denotes what branch the path to the atom that this index points to takes. + */ + public enum MTMathListSubIndexType: Int { + /// The index denotes the whole atom, subIndex is nil. + case none = 0 + /// The position in the subindex is an index into the nucleus + case nucleus + /// The subindex indexes into the superscript. + case superscript + /// The subindex indexes into the subscript + case ssubscript + /// The subindex indexes into the numerator (only valid for fractions) + case numerator + /// The subindex indexes into the denominator (only valid for fractions) + case denominator + /// The subindex indexes into the radicand (only valid for radicals) + case radicand + /// The subindex indexes into the degree (only valid for radicals) + case degree + } + + /// The index of the associated atom. + var atomIndex: Int + + /// The type of subindex, e.g. superscript, numerator etc. + var subIndexType: MTMathListSubIndexType = .none + + /// The index into the sublist. + var subIndex: MTMathListIndex? + + var finalIndex: Int { + if self.subIndexType == .none { + return self.atomIndex + } else { + return self.subIndex?.finalIndex ?? 0 + } + } + + /// Returns the previous index if present. Returns `nil` if there is no previous index. + func prevIndex() -> MTMathListIndex? { + if self.subIndexType == .none { + if self.atomIndex > 0 { + return MTMathListIndex(level0Index: self.atomIndex - 1) + } + } else { + if let prevSubIndex = self.subIndex?.prevIndex() { + return MTMathListIndex(at: self.atomIndex, with: prevSubIndex, type: self.subIndexType) + } + } + return nil + } + + /// Returns the next index. + func nextIndex() -> MTMathListIndex { + if self.subIndexType == .none { + return MTMathListIndex(level0Index: self.atomIndex + 1) + } else if self.subIndexType == .nucleus { + return MTMathListIndex(at: self.atomIndex + 1, with: self.subIndex, type: self.subIndexType) + } else { + return MTMathListIndex(at: self.atomIndex, with: self.subIndex?.nextIndex(), type: self.subIndexType) + } + } + + /** + * Returns true if this index represents the beginning of a line. Note there may be multiple lines in a MTMathList, + * e.g. a superscript or a fraction numerator. This returns true if the innermost subindex points to the beginning of a + * line. + */ + func isBeginningOfLine() -> Bool { self.finalIndex == 0 } + + func isAtSameLevel(with index: MTMathListIndex?) -> Bool { + if self.subIndexType != index?.subIndexType { + return false + } else if self.subIndexType == .none { + // No subindexes, they are at the same level. + return true + } else if (self.atomIndex != index?.atomIndex) { + return false + } else { + return self.subIndex?.isAtSameLevel(with: index?.subIndex) ?? false + } + } + + /** Returns the type of the innermost sub index. */ + func finalSubIndexType() -> MTMathListSubIndexType { + if self.subIndex?.subIndex != nil { + return self.subIndex!.finalSubIndexType() + } else { + return self.subIndexType + } + } + + /** Returns true if any of the subIndexes of this index have the given type. */ + func hasSubIndex(ofType type: MTMathListSubIndexType) -> Bool { + if self.subIndexType == type { + return true + } else { + return self.subIndex?.hasSubIndex(ofType: type) ?? false + } + } + + func levelUp(with subIndex: MTMathListIndex?, type: MTMathListSubIndexType) -> MTMathListIndex { + if self.subIndexType == .none { + return MTMathListIndex(at: self.atomIndex, with: subIndex, type: type) + } + + return MTMathListIndex(at: self.atomIndex, with: self.subIndex?.levelUp(with: subIndex, type: type), type: self.subIndexType) + } + + func levelDown() -> MTMathListIndex? { + if self.subIndexType == .none { + return nil + } + + if let subIndexDown = self.subIndex?.levelDown() { + return MTMathListIndex(at: self.atomIndex, with: subIndexDown, type: self.subIndexType) + } else { + return MTMathListIndex(level0Index: self.atomIndex) + } + } + + /** Factory function to create a `MTMathListIndex` with no subindexes. + @param index The index of the atom that the `MTMathListIndex` points at. + */ + public init(level0Index: Int) { + self.atomIndex = level0Index + } + + public convenience init(at location: Int, with subIndex: MTMathListIndex?, type: MTMathListSubIndexType) { + self.init(level0Index: location) + self.subIndexType = type + self.subIndex = subIndex + } +} + +extension MTMathListIndex: CustomStringConvertible { + public var description: String { + if self.subIndex != nil { + return "[\(self.atomIndex), \(self.subIndexType.rawValue):\(self.subIndex!)]" + } + return "[\(self.atomIndex)]" + } +} + +extension MTMathListIndex: Hashable { + + public func hash(into hasher: inout Hasher) { + hasher.combine(self.atomIndex) + hasher.combine(self.subIndexType) + hasher.combine(self.subIndex) + } + +} + +extension MTMathListIndex: Equatable { + public static func ==(lhs: MTMathListIndex, rhs: MTMathListIndex) -> Bool { + if lhs.atomIndex != rhs.atomIndex || lhs.subIndexType != rhs.subIndexType { + return false + } + + if rhs.subIndex != nil { + return rhs.subIndex == lhs.subIndex + } else { + return lhs.subIndex == nil + } + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathRenderer.swift b/third-party/SwiftMath/Sources/MathRender/MTMathRenderer.swift new file mode 100644 index 0000000000..8243148734 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathRenderer.swift @@ -0,0 +1,83 @@ +import Foundation + +#if os(iOS) || os(visionOS) +import UIKit +#endif + +#if os(macOS) +import AppKit +#endif + +public struct MTMathRenderedFormula { + public let image: MTImage + public let size: CGSize + public let width: CGFloat + public let ascent: CGFloat + public let descent: CGFloat + + public init(image: MTImage, size: CGSize, width: CGFloat, ascent: CGFloat, descent: CGFloat) { + self.image = image + self.size = size + self.width = width + self.ascent = ascent + self.descent = descent + } +} + +public enum MTMathRenderer { + public static func render(latex: String, fontSize: CGFloat, textColor: MTColor, mode: MTMathUILabelMode = .display) -> MTMathRenderedFormula? { + guard let font = MTFontManager.fontManager.defaultFont?.copy(withSize: fontSize) else { + return nil + } + + var error: NSError? + guard let mathList = MTMathListBuilder.build(fromString: latex, error: &error), error == nil else { + return nil + } + + let style: MTLineStyle + switch mode { + case .display: + style = .display + case .text: + style = .text + } + + guard let displayList = MTTypesetter.createLineForMathList(mathList, font: font, style: style) else { + return nil + } + + displayList.textColor = textColor + + let width = max(1.0, ceil(displayList.width)) + let height = max(1.0, ceil(displayList.ascent + displayList.descent)) + let size = CGSize(width: width, height: height) + displayList.position = CGPoint(x: 0.0, y: displayList.descent) + + #if os(iOS) || os(visionOS) + let renderer = UIGraphicsImageRenderer(size: size) + let image = renderer.image { rendererContext in + rendererContext.cgContext.saveGState() + var transform = CGAffineTransform(scaleX: 1.0, y: -1.0) + transform = transform.translatedBy(x: 0.0, y: -size.height) + rendererContext.cgContext.concatenate(transform) + displayList.draw(rendererContext.cgContext) + rendererContext.cgContext.restoreGState() + } + return MTMathRenderedFormula(image: image, size: size, width: displayList.width, ascent: displayList.ascent, descent: displayList.descent) + #endif + + #if os(macOS) + let image = NSImage(size: size, flipped: false) { _ in + guard let context = NSGraphicsContext.current?.cgContext else { + return false + } + context.saveGState() + displayList.draw(context) + context.restoreGState() + return true + } + return MTMathRenderedFormula(image: image, size: size, width: displayList.width, ascent: displayList.ascent, descent: displayList.descent) + #endif + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTMathUILabel.swift b/third-party/SwiftMath/Sources/MathRender/MTMathUILabel.swift new file mode 100644 index 0000000000..b19aabba33 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTMathUILabel.swift @@ -0,0 +1,587 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreText + +/** + Different display styles supported by the `MTMathUILabel`. + + The only significant difference between the two modes is how fractions + and limits on large operators are displayed. + */ +public enum MTMathUILabelMode { + /// Display mode. Equivalent to $$ in TeX + case display + /// Text mode. Equivalent to $ in TeX. + case text +} + +/** + Horizontal text alignment for `MTMathUILabel`. + */ +public enum MTTextAlignment : UInt { + /// Align left. + case left + /// Align center. + case center + /// Align right. + case right +} + +/** The main view for rendering math. + + `MTMathLabel` accepts either a string in LaTeX or an `MTMathList` to display. Use + `MTMathList` directly only if you are building it programmatically (e.g. using an + editor), otherwise using LaTeX is the preferable method. + + The math display is centered vertically in the label. The default horizontal alignment is + is left. This can be changed by setting `textAlignment`. The math is default displayed in + *Display* mode. This can be changed using `labelMode`. + + When created it uses `[MTFontManager defaultFont]` as its font. This can be changed using + the `font` parameter. + */ +@IBDesignable +public class MTMathUILabel : MTView { + + /** The `MTMathList` to render. Setting this will remove any + `latex` that has already been set. If `latex` has been set, this will + return the parsed `MTMathList` if the `latex` parses successfully. Use this + setting if the `MTMathList` has been programmatically constructed, otherwise it + is preferred to use `latex`. + */ + public var mathList:MTMathList? { + set { + _mathList = newValue + _error = nil + _latex = MTMathListBuilder.mathListToString(newValue) + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _mathList } + } + private var _mathList:MTMathList? + + /** The latex string to be displayed. Setting this will remove any `mathList` that + has been set. If latex has not been set, this will return the latex output for the + `mathList` that is set. + @see error */ + @IBInspectable + public var latex:String { + set { + _latex = newValue + _error = nil + var error : NSError? = nil + _mathList = MTMathListBuilder.build(fromString: newValue, error: &error) + if error != nil { + _mathList = nil + _error = error + self.errorLabel?.text = error!.localizedDescription + self.errorLabel?.frame = self.bounds + self.errorLabel?.isHidden = !self.displayErrorInline + } else { + self.errorLabel?.isHidden = true + } + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _latex } + } + private var _latex = "" + + /** This contains any error that occurred when parsing the latex. */ + public var error:NSError? { _error } + private var _error:NSError? + + /** If true, if there is an error it displays the error message inline. Default true. */ + public var displayErrorInline = true + + /** If true, enables detailed debug logging for intrinsicContentSize calculations. + This helps diagnose rendering issues like missing row breaks in aligned environments. + Default false. */ + public var debugLogging = false + + /** The MTFont to use for rendering. */ + public var font:MTFont? { + set { + guard newValue != nil else { return } + _font = newValue + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _font } + } + private var _font:MTFont? + + /** Convenience method to just set the size of the font without changing the fontface. */ + @IBInspectable + public var fontSize:CGFloat { + set { + _fontSize = newValue + let font = font?.copy(withSize: newValue) + self.font = font // also forces an update + } + get { _fontSize } + } + private var _fontSize:CGFloat=0 + + /** This sets the text color of the rendered math formula. The default color is black. */ + @IBInspectable + public var textColor:MTColor? { + set { + guard newValue != nil else { return } + _textColor = newValue + self.displayList?.textColor = newValue + self.setNeedsDisplay() + } + get { _textColor } + } + private var _textColor:MTColor? + + /** The minimum distance from the margin of the view to the rendered math. This value is + `UIEdgeInsetsZero` by default. This is useful if you need some padding between the math and + the border/background color. sizeThatFits: will have its returned size increased by these insets. + */ + @IBInspectable + public var contentInsets:MTEdgeInsets { + set { + _contentInsets = newValue + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _contentInsets } + } + private var _contentInsets = MTEdgeInsetsZero + + /** The Label mode for the label. The default mode is Display */ + public var labelMode:MTMathUILabelMode { + set { + _labelMode = newValue + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _labelMode } + } + private var _labelMode = MTMathUILabelMode.display + + /** Horizontal alignment for the text. The default is align left. */ + public var textAlignment:MTTextAlignment { + set { + _textAlignment = newValue + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _textAlignment } + } + private var _textAlignment = MTTextAlignment.left + + /** The internal display of the MTMathUILabel. This is for advanced use only. */ + public var displayList: MTMathListDisplay? { _displayList } + private var _displayList:MTMathListDisplay? + + /** The preferred maximum width (in points) for a multiline label. + Set this property to enable line wrapping based on available width. */ + public var preferredMaxLayoutWidth: CGFloat { + set { + _preferredMaxLayoutWidth = newValue + _displayList = nil // Clear cached display list when width constraint changes + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + get { _preferredMaxLayoutWidth } + } + private var _preferredMaxLayoutWidth: CGFloat = 0 + + public var currentStyle:MTLineStyle { + switch _labelMode { + case .display: return .display + case .text: return .text + } + } + + public var errorLabel: MTLabel? + + public override init(frame: CGRect) { + super.init(frame: frame) + self.initCommon() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + self.initCommon() + } + + func initCommon() { +#if os(macOS) + self.layer?.isGeometryFlipped = true +#else + self.layer.isGeometryFlipped = true + self.clipsToBounds = true +#endif + _fontSize = 20 + _contentInsets = MTEdgeInsetsZero + _labelMode = .display + let font = MTFontManager.fontManager.defaultFont + self.font = font + _textAlignment = .left + _displayList = nil + displayErrorInline = true + self.backgroundColor = MTColor.clear + + _textColor = MTColor.black + let label = MTLabel() + self.errorLabel = label +#if os(macOS) + label.layer?.isGeometryFlipped = true +#else + label.layer.isGeometryFlipped = true +#endif + label.isHidden = true + label.textColor = MTColor.red + self.addSubview(label) + } + + override public func draw(_ dirtyRect: MTRect) { + super.draw(dirtyRect) + if self.mathList == nil { return } + if self.font == nil { return } + + // Ensure display list is created before drawing + if _displayList == nil { + _layoutSubviews() + } + + guard let displayList = _displayList else { return } + + // drawing code + let context = MTGraphicsGetCurrentContext()! + context.saveGState() + + // CRITICAL FIX for clipping: If the displayList is wider than our bounds, + // expand the clipping rect to prevent content clipping. + // This handles cases where preferredMaxLayoutWidth is a hint but the content + // cannot fit within it even with line breaking. + let contentWidth = displayList.width + contentInsets.left + contentInsets.right + if contentWidth > bounds.size.width { + // Content is wider than bounds - expand clip rect + let expandedRect = CGRect( + x: bounds.origin.x, + y: bounds.origin.y, + width: contentWidth, + height: bounds.size.height + ) + context.clip(to: expandedRect) + } + + displayList.draw(context) + context.restoreGState() + } + + func _layoutSubviews() { + guard _mathList != nil && self.font != nil else { + _displayList = nil + errorLabel?.frame = self.bounds + self.setNeedsDisplay() + return + } + // Ensure we have a valid font before attempting to typeset + if self.font == nil { + // No valid font - try to get default font + if let defaultFont = MTFontManager.fontManager.defaultFont { + self._font = defaultFont + } else { + // Cannot typeset without a font, clear display list + _displayList = nil + errorLabel?.frame = self.bounds + self.setNeedsDisplay() + return + } + } + + // Use the effective width for layout + let effectiveWidth = _preferredMaxLayoutWidth > 0 ? _preferredMaxLayoutWidth : bounds.size.width + var availableWidth = effectiveWidth - contentInsets.left - contentInsets.right + // CRITICAL FIX: Ensure availableWidth is never negative + // Negative maxWidth passed to MTTypesetter can cause "Negative value is not representable" crashes + availableWidth = max(0, availableWidth) + + _displayList = MTTypesetter.createLineForMathList(_mathList, font: self.font, style: currentStyle, maxWidth: availableWidth) + + guard let displayList = _displayList else { + // Empty or invalid input - nothing to display + return + } + + displayList.textColor = textColor + var textX = CGFloat(0) + switch self.textAlignment { + case .left: textX = contentInsets.left + case .center: textX = (bounds.size.width - contentInsets.left - contentInsets.right - displayList.width) / 2 + contentInsets.left + case .right: textX = bounds.size.width - displayList.width - contentInsets.right + } + let availableHeight = bounds.size.height - contentInsets.bottom - contentInsets.top + + // center things vertically + var height = displayList.ascent + displayList.descent + if height < fontSize/2 { + height = fontSize/2 // set height to half the font size + } + let textY = (availableHeight - height) / 2 + displayList.descent + contentInsets.bottom + + displayList.position = CGPointMake(textX, textY) + errorLabel?.frame = self.bounds + self.setNeedsDisplay() + } + + func _sizeThatFits(_ size:CGSize) -> CGSize { + // Check if we have empty latex (empty string case) + if _latex.isEmpty { + // Empty latex - return zero size + return CGSize(width: 0, height: 0) + } + + guard _mathList != nil else { + // No content - return no-intrinsic-size marker + return CGSize(width: -1, height: -1) + } + + // Ensure we have a valid font before attempting to typeset + if self.font == nil { + // No valid font - try to get default font + if let defaultFont = MTFontManager.fontManager.defaultFont { + self._font = defaultFont + } else { + // Cannot typeset without a font + return CGSize(width: -1, height: -1) + } + } + + // Determine the maximum width to use + var maxWidth: CGFloat = 0 + if _preferredMaxLayoutWidth > 0 { + maxWidth = _preferredMaxLayoutWidth - contentInsets.left - contentInsets.right + // CRITICAL FIX: Ensure maxWidth is never negative + // If contentInsets exceed available width, clamp to 0 + maxWidth = max(0, maxWidth) + } else if size.width > 0 { + maxWidth = size.width - contentInsets.left - contentInsets.right + // CRITICAL FIX: Ensure maxWidth is never negative + maxWidth = max(0, maxWidth) + } + + var displayList:MTMathListDisplay? = nil + displayList = MTTypesetter.createLineForMathList(_mathList, font: self.font, style: currentStyle, maxWidth: maxWidth) + + guard displayList != nil else { + // Failed to create display list + return CGSize(width: -1, height: -1) + } + + var resultWidth = displayList!.width + contentInsets.left + contentInsets.right + var resultHeight = displayList!.ascent + displayList!.descent + contentInsets.top + contentInsets.bottom + + // DEBUG LOGGING for width calculation + if debugLogging { + print("\n=== MTMathUILabel intrinsicContentSize DEBUG ===") + print("LaTeX: \(self.latex)") + // Show raw bytes to help debug escaping issues + let latexBytes = Array(self.latex.utf8.prefix(100)) + print("LaTeX bytes (first 100): \(latexBytes)") + print("preferredMaxLayoutWidth: \(_preferredMaxLayoutWidth)") + print("size constraint: \(size)") + print("maxWidth passed to typesetter: \(maxWidth)") + print("displayList.width: \(displayList!.width)") + print("displayList.ascent: \(displayList!.ascent)") + print("displayList.descent: \(displayList!.descent)") + print("Number of subDisplays: \(displayList!.subDisplays.count)") + // Count lines by unique Y positions + let yPositions = Set(displayList!.subDisplays.map { $0.position.y }) + print("Number of lines (unique Y positions): \(yPositions.count)") + print("contentInsets: \(contentInsets)") + print("resultWidth (before clamping): \(resultWidth)") + print("resultHeight (before clamping): \(resultHeight)") + + // HELPFUL WARNING: Detect common escaping mistake + // Check for environments that typically have multiple rows but only 1 row is rendered + let multiRowEnvs = ["aligned", "align", "eqnarray", "gather", "split"] + let latexLower = self.latex.lowercased() + for env in multiRowEnvs { + if latexLower.contains("\\begin{\(env)}") { + // Check if there's a table with only 1 row + if let ml = _mathList { + for atom in ml.atoms { + if let table = atom as? MTMathTable, table.numRows == 1 { + // Check if latex contains single backslash (spacing) where double might be intended + // Pattern: \\ followed by non-backslash (like space or letter) + if self.latex.contains("\\ ") || self.latex.range(of: #"\\[a-zA-Z]"#, options: .regularExpression) != nil { + print("⚠️ WARNING: '\(env)' environment has only 1 row.") + print(" If you intended multiple rows, use '\\\\\\\\' (4 backslashes in Swift strings)") + print(" or use raw strings: #\"...42\\\\ \\\\dfrac...\"#") + print(" Current input has '\\\\ ' which is a SPACING command, not a row break.") + } + } + } + } + } + } + } + + // CRITICAL FIX: Ensure dimensions are never negative + // Negative values cause crashes in NSRange calculations and SwiftUI layout + resultWidth = max(0, resultWidth) + resultHeight = max(0, resultHeight) + + // CRITICAL FIX for accented character clipping: + // The preferredMaxLayoutWidth is a HINT for line breaking, NOT a hard constraint. + // If the typesetter cannot fit content within that width (even with line breaking), + // we MUST return the actual content width to prevent clipping. + // + // ONLY clamp in extreme cases to prevent layout explosion (>50% over or >100pt over) + if _preferredMaxLayoutWidth > 0 && resultWidth > _preferredMaxLayoutWidth { + let overflow = resultWidth - _preferredMaxLayoutWidth + let overflowPercent = (overflow / _preferredMaxLayoutWidth) * 100 + + if debugLogging { + print(" Content exceeds preferredMaxLayoutWidth:") + print(" preferredMaxLayoutWidth: \(_preferredMaxLayoutWidth)") + print(" resultWidth: \(resultWidth)") + print(" overflow: \(overflow) (\(String(format: "%.1f", overflowPercent))%)") + + // Check line breaking + let yPositions = Set(displayList!.subDisplays.map { $0.position.y }) + print(" hasMultipleLines: \(yPositions.count > 1) (yPositions: \(yPositions.count))") + + // Check if any content would be clipped at different widths + print(" SubDisplay analysis:") + for (i, sub) in displayList!.subDisplays.enumerated() { + let rightEdge = sub.position.x + sub.width + let clippedAtPreferred = rightEdge > _preferredMaxLayoutWidth + let clippedAtResult = rightEdge > resultWidth + print(" Sub[\(i)]: rightEdge=\(rightEdge) clippedAt\(_preferredMaxLayoutWidth)=\(clippedAtPreferred) clippedAt\(resultWidth)=\(clippedAtResult)") + } + } + + // ONLY clamp for truly excessive overflow (>50% or >100pt) + // This prevents layout explosion while allowing normal overflow + let extremeOverflowThreshold: CGFloat = max(_preferredMaxLayoutWidth * 0.5, 100.0) + + if overflow > extremeOverflowThreshold { + // Extreme overflow - clamp to prevent layout issues + let clampedWidth = _preferredMaxLayoutWidth + extremeOverflowThreshold + if debugLogging { + print(" ⚠️ EXTREME OVERFLOW - clamping from \(resultWidth) to \(clampedWidth)") + print(" ⚠️ WARNING: This will cause content clipping!") + } + resultWidth = clampedWidth + } else { + // Normal overflow - keep actual content width to prevent clipping + if debugLogging { + print(" ✓ Normal overflow - keeping actual width \(resultWidth) to prevent clipping") + } + // resultWidth stays as is - NO CLAMPING + } + } else if _preferredMaxLayoutWidth == 0 && size.width > 0 && resultWidth > size.width { + // Similar tolerance for size.width constraint + let tolerance = max(size.width * 0.05, 10.0) + let maxAllowedWidth = size.width + tolerance + + if debugLogging { + print(" Exceeds size.width constraint:") + print(" tolerance: \(tolerance)") + print(" maxAllowedWidth: \(maxAllowedWidth)") + print(" overflow amount: \(resultWidth - size.width)") + } + + if resultWidth <= maxAllowedWidth { + // Within tolerance - use actual content width + // resultWidth stays as is + if debugLogging { + print(" ✓ Within tolerance - keeping actual width") + } + } else { + if debugLogging { + print(" ⚠️ CLAMPING to maxAllowedWidth (may clip content!)") + } + resultWidth = maxAllowedWidth + } + } + + if debugLogging { + print(" Final resultWidth: \(resultWidth)") + print(" Final resultHeight: \(resultHeight)") + + // Check if any display elements would be clipped + if let display = displayList { + print(" Display subdisplays: \(display.subDisplays.count)") + let yPositions = Set(display.subDisplays.map { $0.position.y }).sorted() + print(" Unique Y positions (lines): \(yPositions.count) -> \(yPositions)") + + for (i, sub) in display.subDisplays.enumerated() { + let rightEdge = sub.position.x + sub.width + let clipped = rightEdge > resultWidth + + // Extract text content if this is a CTLineDisplay + var textContent = "" + if let ctLineDisplay = sub as? MTCTLineDisplay, + let attrString = ctLineDisplay.attributedString { + textContent = " text=\"\(attrString.string)\"" + } + + print(" Sub[\(i)]: type=\(type(of: sub)), y=\(sub.position.y), x=\(sub.position.x), width=\(sub.width), rightEdge=\(rightEdge)\(textContent)\(clipped ? " ⚠️ CLIPPED" : "")") + + // Show internal structure for MTMathListDisplay + if let mathListDisplay = sub as? MTMathListDisplay, !mathListDisplay.subDisplays.isEmpty { + print(" → Contains \(mathListDisplay.subDisplays.count) sub-displays:") + for (j, innerSub) in mathListDisplay.subDisplays.enumerated() { + var innerTextContent = "" + if let innerCTLineDisplay = innerSub as? MTCTLineDisplay, + let innerAttrString = innerCTLineDisplay.attributedString { + innerTextContent = " text=\"\(innerAttrString.string)\"" + } + print(" [\(j)]: type=\(type(of: innerSub)), y=\(innerSub.position.y), x=\(innerSub.position.x), width=\(innerSub.width)\(innerTextContent)") + } + } + + if clipped { + print(" ⚠️ CLIPPING: rightEdge \(rightEdge) > resultWidth \(resultWidth)") + print(" Clipped amount: \(rightEdge - resultWidth)") + } + } + } + print("=== END DEBUG ===\n") + } + + return CGSize(width: resultWidth, height: resultHeight) + } + + #if os(macOS) + public func sizeThatFits(_ size: CGSize) -> CGSize { + return _sizeThatFits(size) + } + #else + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return _sizeThatFits(size) + } + #endif + +#if os(macOS) + func setNeedsDisplay() { self.needsDisplay = true } + func setNeedsLayout() { self.needsLayout = true } + public override var fittingSize: CGSize { _sizeThatFits(CGSizeZero) } + public override var intrinsicContentSize: CGSize { _sizeThatFits(CGSizeZero) } + override public func layout() { + self._layoutSubviews() + super.layout() + } +#else + public override var intrinsicContentSize: CGSize { _sizeThatFits(CGSizeZero) } + override public func layoutSubviews() { _layoutSubviews() } +#endif + +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTTypesetter+Tokenization.swift b/third-party/SwiftMath/Sources/MathRender/MTTypesetter+Tokenization.swift new file mode 100644 index 0000000000..3911df6499 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTTypesetter+Tokenization.swift @@ -0,0 +1,68 @@ +// +// MTTypesetter+Tokenization.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics + +extension MTTypesetter { + + /// Create a line for a math list using the new tokenization approach + /// This is an alternative to the existing createLineForMathList that uses + /// pre-tokenization and greedy line fitting + static func createLineForMathListWithTokenization( + _ mathList: MTMathList?, + font: MTFont?, + style: MTLineStyle, + cramped: Bool, + spaced: Bool, + maxWidth: CGFloat + ) -> MTMathListDisplay? { + guard let mathList = mathList else { return nil } + guard let font = font else { return nil } + guard !mathList.atoms.isEmpty else { + // Return empty display instead of nil (matches KaTeX behavior) + return MTMathListDisplay(withDisplays: [], range: NSMakeRange(0, 0)) + } + + // Phase 0: Preprocess atoms to fuse ordinary characters + // This is critical for accents and other structures where multi-character + // text like "xyzw" should stay together as a single atom + let preprocessedAtoms = MTTypesetter.preprocessMathList(mathList) + + // Phase 1: Tokenize atoms into breakable elements + let tokenizer = MTAtomTokenizer(font: font, style: style, cramped: cramped, maxWidth: maxWidth) + let elements = tokenizer.tokenize(preprocessedAtoms) + + guard !elements.isEmpty else { return nil } + + // Phase 2: Fit elements into lines + let margin = spaced ? font.mathTable?.muUnit ?? 0 : 0 + let fitter = MTLineFitter(maxWidth: maxWidth, margin: margin) + let fittedLines = fitter.fitLines(elements) + + // Phase 3: Generate displays from fitted lines + let generator = MTDisplayGenerator(font: font, style: style) + let displays = generator.generateDisplays(from: fittedLines, startPosition: CGPoint.zero) + + // Determine range from atoms + let range: NSRange + if let firstAtom = mathList.atoms.first, let lastAtom = mathList.atoms.last { + let start = firstAtom.indexRange.location + let end = NSMaxRange(lastAtom.indexRange) + range = NSMakeRange(start, end - start) + } else { + range = NSMakeRange(0, 0) + } + + // Create and return the math list display + let mathListDisplay = MTMathListDisplay(withDisplays: displays, range: range) + + return mathListDisplay + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTTypesetter.swift b/third-party/SwiftMath/Sources/MathRender/MTTypesetter.swift new file mode 100644 index 0000000000..72d67a2315 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTTypesetter.swift @@ -0,0 +1,1927 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreText + +// MARK: - Inter Element Spacing + +enum InterElementSpaceType : Int { + case invalid = -1 + case none = 0 + case thin + case nsThin // Thin but not in script mode + case nsMedium + case nsThick +} + +var interElementSpaceArray = [[InterElementSpaceType]]() +private let interElementLock = NSLock() + +func getInterElementSpaces() -> [[InterElementSpaceType]] { + if interElementSpaceArray.isEmpty { + + interElementLock.lock() + defer { interElementLock.unlock() } + guard interElementSpaceArray.isEmpty else { return interElementSpaceArray } + + interElementSpaceArray = + // ordinary operator binary relation open close punct fraction + [ [.none, .thin, .nsMedium, .nsThick, .none, .none, .none, .nsThin], // ordinary + [.thin, .thin, .invalid, .nsThick, .none, .none, .none, .nsThin], // operator + [.nsMedium, .nsMedium, .invalid, .invalid, .nsMedium, .invalid, .invalid, .nsMedium], // binary + [.nsThick, .nsThick, .invalid, .none, .nsThick, .none, .none, .nsThick], // relation + [.none, .none, .invalid, .none, .none, .none, .none, .none], // open + [.none, .thin, .nsMedium, .nsThick, .none, .none, .none, .nsThin], // close + [.nsThin, .nsThin, .invalid, .nsThin, .nsThin, .nsThin, .nsThin, .nsThin], // punct + [.nsThin, .thin, .nsMedium, .nsThick, .nsThin, .none, .nsThin, .nsThin], // fraction + [.nsMedium, .nsThin, .nsMedium, .nsThick, .none, .none, .none, .nsThin]] // radical + } + return interElementSpaceArray +} + + +// Get's the index for the given type. If row is true, the index is for the row (i.e. left element) otherwise it is for the column (right element) +func getInterElementSpaceArrayIndexForType(_ type:MTMathAtomType, row:Bool) -> Int { + switch type { + case .color, .textcolor, .colorBox, .ordinary, .placeholder: // A placeholder is treated as ordinary + return 0 + case .largeOperator: + return 1 + case .binaryOperator: + return 2; + case .relation: + return 3; + case .open: + return 4; + case .close: + return 5; + case .punctuation: + return 6; + case .fraction, // Fraction and inner are treated the same. + .inner: + return 7; + case .radical: + if row { + // Radicals have inter element spaces only when on the left side. + // Note: This is a departure from latex but we don't want \sqrt{4}4 to look weird so we put a space in between. + // They have the same spacing as ordinary except with ordinary. + return 8; + } else { + // Treat radical as ordinary on the right side + return 0 + } + // Numbers, variables, and unary operators are treated as ordinary + case .number, .variable, .unaryOperator: + return 0 + // Decorative types (accent, underline, overline) are treated as ordinary + case .accent, .underline, .overline: + return 0 + // Special types that don't typically participate in spacing are treated as ordinary + case .boundary, .space, .style, .table: + return 0 + } +} + +// MARK: - Italics +// mathit +func getItalicized(_ ch:Character) -> UTF32Char { + var unicode = ch.utf32Char + + // Special cases for italics + if ch == "h" { return UnicodeSymbol.planksConstant } + // Dotless i (U+0131) → Mathematical Italic Small Dotless I (U+1D6A4) + if ch == "\u{0131}" { return 0x1D6A4 } + // Dotless j (U+0237) → Mathematical Italic Small Dotless J (U+1D6A5) + if ch == "\u{0237}" { return 0x1D6A5 } + + if ch.isUpperEnglish { + unicode = UnicodeSymbol.capitalItalicStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + unicode = UnicodeSymbol.lowerItalicStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isCapitalGreek { + // Capital Greek characters + unicode = UnicodeSymbol.greekCapitalItalicStart + (ch.utf32Char - UnicodeSymbol.capitalGreekStart) + } else if ch.isLowerGreek { + // Greek characters + unicode = UnicodeSymbol.greekLowerItalicStart + (ch.utf32Char - UnicodeSymbol.lowerGreekStart) + } else if ch.isGreekSymbol { + return UnicodeSymbol.greekSymbolItalicStart + ch.greekSymbolOrder! + } + // Note there are no italicized numbers in unicode so we don't support italicizing numbers. + return unicode +} + +// mathbf +func getBold(_ ch:Character) -> UTF32Char { + var unicode = ch.utf32Char + if ch.isUpperEnglish { + unicode = UnicodeSymbol.mathCapitalBoldStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + unicode = UnicodeSymbol.mathLowerBoldStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isCapitalGreek { + // Capital Greek characters + unicode = UnicodeSymbol.greekCapitalBoldStart + (ch.utf32Char - UnicodeSymbol.capitalGreekStart); + } else if ch.isLowerGreek { + // Greek characters + unicode = UnicodeSymbol.greekLowerBoldStart + (ch.utf32Char - UnicodeSymbol.lowerGreekStart); + } else if ch.isGreekSymbol { + return UnicodeSymbol.greekSymbolBoldStart + ch.greekSymbolOrder! + } else if ch.isNumber { + unicode = UnicodeSymbol.numberBoldStart + (ch.utf32Char - Character("0").utf32Char) + } + return unicode +} + +// mathbfit +func getBoldItalic(_ ch:Character) -> UTF32Char { + var unicode = ch.utf32Char + if ch.isUpperEnglish { + unicode = UnicodeSymbol.mathCapitalBoldItalicStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + unicode = UnicodeSymbol.mathLowerBoldItalicStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isCapitalGreek { + // Capital Greek characters + unicode = UnicodeSymbol.greekCapitalBoldItalicStart + (ch.utf32Char - UnicodeSymbol.capitalGreekStart); + } else if ch.isLowerGreek { + // Greek characters + unicode = UnicodeSymbol.greekLowerBoldItalicStart + (ch.utf32Char - UnicodeSymbol.lowerGreekStart); + } else if ch.isGreekSymbol { + return UnicodeSymbol.greekSymbolBoldItalicStart + ch.greekSymbolOrder! + } else if ch.isNumber { + // No bold italic for numbers so we just bold them. + unicode = getBold(ch); + } + return unicode; +} + +// LaTeX default +func getDefaultStyle(_ ch:Character) -> UTF32Char { + if ch.isLowerEnglish || ch.isUpperEnglish || ch.isLowerGreek || ch.isGreekSymbol { + return getItalicized(ch); + } else if ch == "\u{0131}" || ch == "\u{0237}" { + // Dotless i (U+0131) and dotless j (U+0237) should be italicized in default style + return getItalicized(ch); + } else if ch.isNumber || ch.isCapitalGreek { + // In the default style numbers and capital greek is roman + return ch.utf32Char + } else if ch == "." { + // . is treated as a number in our code, but it doesn't change fonts. + return ch.utf32Char + } else { + NSException(name: NSExceptionName("IllegalCharacter"), reason: "Unknown character \(ch) for default style.").raise() + } + return ch.utf32Char +} + +// mathcal/mathscr (caligraphic or script) +func getCaligraphic(_ ch:Character) -> UTF32Char { + // Caligraphic has lots of exceptions: + switch ch { + case "B": + return 0x212C; // Script B (bernoulli) + case "E": + return 0x2130; // Script E (emf) + case "F": + return 0x2131; // Script F (fourier) + case "H": + return 0x210B; // Script H (hamiltonian) + case "I": + return 0x2110; // Script I + case "L": + return 0x2112; // Script L (laplace) + case "M": + return 0x2133; // Script M (M-matrix) + case "R": + return 0x211B; // Script R (Riemann integral) + case "e": + return 0x212F; // Script e (Natural exponent) + case "g": + return 0x210A; // Script g (real number) + case "o": + return 0x2134; // Script o (order) + default: + break; + } + var unicode:UTF32Char + if ch.isUpperEnglish { + unicode = UnicodeSymbol.mathCapitalScriptStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + // Latin Modern Math does not have lower case caligraphic characters, so we use + // the default style instead of showing a ? + unicode = getDefaultStyle(ch) + } else { + // Caligraphic characters don't exist for greek or numbers, we give them the + // default treatment. + unicode = getDefaultStyle(ch) + } + return unicode; +} + +// mathtt (monospace) +func getTypewriter(_ ch:Character) -> UTF32Char { + if ch.isUpperEnglish { + return UnicodeSymbol.mathCapitalTTStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + return UnicodeSymbol.mathLowerTTStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isNumber { + return UnicodeSymbol.numberTTStart + (ch.utf32Char - Character("0").utf32Char) + } + // Monospace characters don't exist for greek, we give them the + // default treatment. + return getDefaultStyle(ch); +} + +// mathsf +func getSansSerif(_ ch:Character) -> UTF32Char { + if ch.isUpperEnglish { + return UnicodeSymbol.mathCapitalSansSerifStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + return UnicodeSymbol.mathLowerSansSerifStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isNumber { + return UnicodeSymbol.numberSansSerifStart + (ch.utf32Char - Character("0").utf32Char) + } + // Sans-serif characters don't exist for greek, we give them the + // default treatment. + return getDefaultStyle(ch); +} + +// mathfrak +func getFraktur(_ ch:Character) -> UTF32Char { + // Fraktur has exceptions: + switch ch { + case "C": + return 0x212D; // C Fraktur + case "H": + return 0x210C; // Hilbert space + case "I": + return 0x2111; // Imaginary + case "R": + return 0x211C; // Real + case "Z": + return 0x2128; // Z Fraktur + default: + break; + } + if ch.isUpperEnglish { + return UnicodeSymbol.mathCapitalFrakturStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + return UnicodeSymbol.mathLowerFrakturStart + (ch.utf32Char - Character("a").utf32Char) + } + // Fraktur characters don't exist for greek & numbers, we give them the + // default treatment. + return getDefaultStyle(ch); +} + +// mathbb (double struck) +func getBlackboard(_ ch:Character) -> UTF32Char { + // Blackboard has lots of exceptions: + switch(ch) { + case "C": + return 0x2102; // Complex numbers + case "H": + return 0x210D; // Quarternions + case "N": + return 0x2115; // Natural numbers + case "P": + return 0x2119; // Primes + case "Q": + return 0x211A; // Rationals + case "R": + return 0x211D; // Reals + case "Z": + return 0x2124; // Integers + default: + break; + } + if ch.isUpperEnglish { + return UnicodeSymbol.mathCapitalBlackboardStart + (ch.utf32Char - Character("A").utf32Char) + } else if ch.isLowerEnglish { + return UnicodeSymbol.mathLowerBlackboardStart + (ch.utf32Char - Character("a").utf32Char) + } else if ch.isNumber { + return UnicodeSymbol.numberBlackboardStart + (ch.utf32Char - Character("0").utf32Char) + } + // Blackboard characters don't exist for greek, we give them the + // default treatment. + return getDefaultStyle(ch); +} + +func styleCharacter(_ ch:Character, fontStyle:MTFontStyle) -> UTF32Char { + switch fontStyle { + case .defaultStyle: + return getDefaultStyle(ch); + case .roman: + return ch.utf32Char + case .bold: + return getBold(ch); + case .italic: + return getItalicized(ch); + case .boldItalic: + return getBoldItalic(ch); + case .caligraphic: + return getCaligraphic(ch); + case .typewriter: + return getTypewriter(ch); + case .sansSerif: + return getSansSerif(ch); + case .fraktur: + return getFraktur(ch); + case .blackboard: + return getBlackboard(ch); + } +} + +func changeFont(_ str:String, fontStyle:MTFontStyle) -> String { + var retval = "" + let codes = Array(str) + for i in 0.. MTMathListDisplay? { + let finalizedList = mathList?.finalized + // default is not cramped, no width constraint + return self.createLineForMathList(finalizedList, font:font, style:style, cramped:false, maxWidth: 0) + } + + static func createLineForMathList(_ mathList:MTMathList?, font:MTFont?, style:MTLineStyle, maxWidth:CGFloat) -> MTMathListDisplay? { + let finalizedList = mathList?.finalized + // default is not cramped + return self.createLineForMathList(finalizedList, font:font, style:style, cramped:false, maxWidth: maxWidth) + } + + // Internal + static func createLineForMathList(_ mathList:MTMathList?, font:MTFont?, style:MTLineStyle, cramped:Bool) -> MTMathListDisplay? { + return self.createLineForMathList(mathList, font:font, style:style, cramped:cramped, spaced:false, maxWidth: 0) + } + + // Internal + static func createLineForMathList(_ mathList:MTMathList?, font:MTFont?, style:MTLineStyle, cramped:Bool, maxWidth:CGFloat) -> MTMathListDisplay? { + return self.createLineForMathList(mathList, font:font, style:style, cramped:cramped, spaced:false, maxWidth: maxWidth) + } + + // Internal + static func createLineForMathList(_ mathList:MTMathList?, font:MTFont?, style:MTLineStyle, cramped:Bool, spaced:Bool) -> MTMathListDisplay? { + return self.createLineForMathList(mathList, font:font, style:style, cramped:cramped, spaced:spaced, maxWidth: 0) + } + + // Internal + static func createLineForMathList(_ mathList:MTMathList?, font:MTFont?, style:MTLineStyle, cramped:Bool, spaced:Bool, maxWidth:CGFloat) -> MTMathListDisplay? { + assert(font != nil) + + // Always use tokenization approach + // The tokenization path handles both constrained (maxWidth > 0) and unconstrained (maxWidth = 0) rendering + return createLineForMathListWithTokenization( + mathList, + font: font, + style: style, + cramped: cramped, + spaced: spaced, + maxWidth: maxWidth + ) + } + + static var placeholderColor: MTColor { MTColor.blue } + + init(withFont font:MTFont?, style:MTLineStyle, cramped:Bool, spaced:Bool, maxWidth:CGFloat = 0) { + self.font = font + self.displayAtoms = [MTDisplay]() + self.currentPosition = CGPoint.zero + self.cramped = cramped + self.spaced = spaced + self.maxWidth = maxWidth + self.style = style + } + + static func preprocessMathList(_ ml:MTMathList?) -> [MTMathAtom] { + // Note: Some of the preprocessing described by the TeX algorithm is done in the finalize method of MTMathList. + // Specifically rules 5 & 6 in Appendix G are handled by finalize. + // This function does not do a complete preprocessing as specified by TeX either. It removes any special atom types + // that are not included in TeX and applies Rule 14 to merge ordinary characters. + var preprocessed = [MTMathAtom]() // arrayWithCapacity:ml.atoms.count) + var prevNode:MTMathAtom! = nil + preprocessed.reserveCapacity(ml!.atoms.count) + for atom in ml!.atoms { + if atom.type == .variable || atom.type == .number { + // This is not a TeX type node. TeX does this during parsing the input. + // switch to using the italic math font + // We convert it to ordinary + let newFont = changeFont(atom.nucleus, fontStyle: atom.fontStyle) // mathItalicize(atom.nucleus) + atom.type = .ordinary + atom.nucleus = newFont + } else if atom.type == .unaryOperator { + // Neither of these are TeX nodes. TeX treats these as Ordinary. So will we. + atom.type = .ordinary + } else if atom.type == .binaryOperator { + // CRITICAL FIX: Convert binary operators to ordinary (unary) in appropriate contexts + // According to TeX rules (TeXbook Appendix G, Rule 5), a binary operator is converted + // to ordinary when it appears after: Bin, Op, Rel, Open, Punct, or at the beginning + // This handles cases like "=-2" where minus should be unary, not binary + let shouldConvertToOrdinary: Bool + if prevNode == nil { + // At the beginning of the list + shouldConvertToOrdinary = true + } else { + switch prevNode.type { + case .binaryOperator, .relation, .open, .punctuation, .largeOperator: + shouldConvertToOrdinary = true + default: + shouldConvertToOrdinary = false + } + } + + if shouldConvertToOrdinary { + atom.type = .ordinary + } + } + + if atom.type == .ordinary { + // This is Rule 14 to merge ordinary characters. + // combine ordinary atoms together + // CRITICAL FIX: Only fuse atoms with the same fontStyle + // This prevents fusing roman text (\text{...}) with italic math variables (A, B, etc.) + // which would cause incorrect line breaking when the combined string is tokenized + if prevNode != nil && prevNode.type == .ordinary && prevNode.subScript == nil && prevNode.superScript == nil && prevNode.fontStyle == atom.fontStyle { + prevNode.fuse(with: atom) + // skip the current node, we are done here. + continue + } + } + + // TODO: add italic correction here or in second pass? + prevNode = atom + preprocessed.append(atom) + } + return preprocessed + } + + // returns the size of the font in this style + static func getStyleSize(_ style:MTLineStyle, font:MTFont?) -> CGFloat { + let original = font!.fontSize + let scaled: CGFloat + switch style { + case .display, .text: + scaled = original + case .script: + scaled = original * font!.mathTable!.scriptScaleDown + case .scriptOfScript: + scaled = original * font!.mathTable!.scriptScriptScaleDown + } + // Apply minimum font size threshold to prevent deeply nested exponents + // from becoming unreadable (common for expressions like 2^{2^{2^2}}) + // Minimum of 6pt ensures readability while maintaining proper hierarchy + return max(scaled, 6.0) + } + + // MARK: - Spacing + + // Returned in units of mu = 1/18 em. + func getSpacingInMu(_ type: InterElementSpaceType) -> Int { + // let valid = [MTLineStyle.display, .text] + switch type { + case .invalid: return -1 + case .none: return 0 + case .thin: return 3 + case .nsThin: return style.isNotScript ? 3 : 0; + case .nsMedium: return style.isNotScript ? 4 : 0; + case .nsThick: return style.isNotScript ? 5 : 0; + } + } + + func getInterElementSpace(_ left: MTMathAtomType, right:MTMathAtomType) -> CGFloat { + let leftIndex = getInterElementSpaceArrayIndexForType(left, row: true) + let rightIndex = getInterElementSpaceArrayIndexForType(right, row: false) + let spaceArray = getInterElementSpaces()[Int(leftIndex)] + let spaceTypeObj = spaceArray[Int(rightIndex)] + let spaceType = spaceTypeObj + assert(spaceType != .invalid, "Invalid space between \(left) and \(right)") + + let spaceMultipler = self.getSpacingInMu(spaceType) + if spaceMultipler > 0 { + // 1 em = size of font in pt. space multipler is in multiples mu or 1/18 em + return CGFloat(spaceMultipler) * styleFont.mathTable!.muUnit + } + return 0 + } + + + // MARK: - Subscript/Superscript + + func scriptStyle() -> MTLineStyle { + switch style { + case .display, .text: return .script + case .script, .scriptOfScript: return .scriptOfScript + } + } + + // subscript is always cramped + func subscriptCramped() -> Bool { true } + + // superscript is cramped only if the current style is cramped + func superScriptCramped() -> Bool { cramped } + + func superScriptShiftUp() -> CGFloat { + if cramped { + return styleFont.mathTable!.superscriptShiftUpCramped; + } else { + return styleFont.mathTable!.superscriptShiftUp; + } + } + + // make scripts for the last atom + // index is the index of the element which is getting the sub/super scripts. + func makeScripts(_ atom: MTMathAtom?, display:MTDisplay?, index:UInt, delta:CGFloat) { + guard let atom = atom else { return } + guard atom.subScript != nil || atom.superScript != nil else { return } + guard let mathTable = styleFont.mathTable else { return } + + var superScriptShiftUp = 0.0 + var subscriptShiftDown = 0.0 + + display?.hasScript = true + if !(display is MTCTLineDisplay), let display = display { + // get the font in script style + let scriptFontSize = Self.getStyleSize(self.scriptStyle(), font:font) + let scriptFont = font.copy(withSize: scriptFontSize) + let scriptFontMetrics = scriptFont.mathTable + + // if it is not a simple line then + if let scriptFontMetrics = scriptFontMetrics { + superScriptShiftUp = display.ascent - scriptFontMetrics.superscriptBaselineDropMax + subscriptShiftDown = display.descent + scriptFontMetrics.subscriptBaselineDropMin + } + } + + if atom.superScript == nil { + guard let _subscript = MTTypesetter.createLineForMathList(atom.subScript, font:font, style:self.scriptStyle(), cramped:self.subscriptCramped()) else { return } + _subscript.type = .ssubscript + _subscript.index = Int(index) + + subscriptShiftDown = fmax(subscriptShiftDown, mathTable.subscriptShiftDown); + subscriptShiftDown = fmax(subscriptShiftDown, _subscript.ascent - mathTable.subscriptTopMax); + // add the subscript + _subscript.position = CGPointMake(currentPosition.x, currentPosition.y - subscriptShiftDown); + displayAtoms.append(_subscript) + // update the position + currentPosition.x += _subscript.width + mathTable.spaceAfterScript; + return; + } + + guard let superScript = MTTypesetter.createLineForMathList(atom.superScript, font:font, style:self.scriptStyle(), cramped:self.superScriptCramped()) else { return } + superScript.type = .superscript + superScript.index = Int(index); + superScriptShiftUp = fmax(superScriptShiftUp, self.superScriptShiftUp()); + superScriptShiftUp = fmax(superScriptShiftUp, superScript.descent + mathTable.superscriptBottomMin); + + if atom.subScript == nil { + superScript.position = CGPointMake(currentPosition.x, currentPosition.y + superScriptShiftUp); + displayAtoms.append(superScript) + // update the position + currentPosition.x += superScript.width + mathTable.spaceAfterScript; + return; + } + guard let ssubscript = MTTypesetter.createLineForMathList(atom.subScript, font:font, style:self.scriptStyle(), cramped:self.subscriptCramped()) else { return } + ssubscript.type = .ssubscript + ssubscript.index = Int(index) + subscriptShiftDown = fmax(subscriptShiftDown, mathTable.subscriptShiftDown); + + // joint positioning of subscript & superscript + let subSuperScriptGap = (superScriptShiftUp - superScript.descent) + (subscriptShiftDown - ssubscript.ascent); + if (subSuperScriptGap < mathTable.subSuperscriptGapMin) { + // Set the gap to atleast as much + subscriptShiftDown += mathTable.subSuperscriptGapMin - subSuperScriptGap; + let superscriptBottomDelta = mathTable.superscriptBottomMaxWithSubscript - (superScriptShiftUp - superScript.descent); + if (superscriptBottomDelta > 0) { + // superscript is lower than the max allowed by the font with a subscript. + superScriptShiftUp += superscriptBottomDelta; + subscriptShiftDown -= superscriptBottomDelta; + } + } + // The delta is the italic correction above that shift superscript position + superScript.position = CGPointMake(currentPosition.x + delta, currentPosition.y + superScriptShiftUp); + displayAtoms.append(superScript) + ssubscript.position = CGPointMake(currentPosition.x, currentPosition.y - subscriptShiftDown); + displayAtoms.append(ssubscript) + currentPosition.x += max(superScript.width + delta, ssubscript.width) + mathTable.spaceAfterScript; + } + + // MARK: - Helper Functions + + /// Safely converts an NSRange location to UInt, returning 0 if the location is invalid (NSNotFound) + /// This prevents "Negative value is not representable" crashes when converting NSNotFound to UInt + private func safeUIntFromLocation(_ location: Int) -> UInt { + if location == NSNotFound || location < 0 { + return 0 + } + return UInt(location) + } + + // MARK: - Fractions + + func numeratorShiftUp(_ hasRule:Bool) -> CGFloat { + if hasRule { + if style == .display { + return styleFont.mathTable!.fractionNumeratorDisplayStyleShiftUp + } else { + return styleFont.mathTable!.fractionNumeratorShiftUp + } + } else { + if style == .display { + return styleFont.mathTable!.stackTopDisplayStyleShiftUp + } else { + return styleFont.mathTable!.stackTopShiftUp + } + } + } + + func numeratorGapMin() -> CGFloat { + if style == .display { + return styleFont.mathTable!.fractionNumeratorDisplayStyleGapMin; + } else { + return styleFont.mathTable!.fractionNumeratorGapMin; + } + } + + func denominatorShiftDown(_ hasRule:Bool) -> CGFloat { + if hasRule { + if style == .display { + return styleFont.mathTable!.fractionDenominatorDisplayStyleShiftDown; + } else { + return styleFont.mathTable!.fractionDenominatorShiftDown; + } + } else { + if style == .display { + return styleFont.mathTable!.stackBottomDisplayStyleShiftDown; + } else { + return styleFont.mathTable!.stackBottomShiftDown; + } + } + } + + func denominatorGapMin() -> CGFloat { + if style == .display { + return styleFont.mathTable!.fractionDenominatorDisplayStyleGapMin; + } else { + return styleFont.mathTable!.fractionDenominatorGapMin; + } + } + + func stackGapMin() -> CGFloat { + if style == .display { + return styleFont.mathTable!.stackDisplayStyleGapMin; + } else { + return styleFont.mathTable!.stackGapMin; + } + } + + func fractionDelimiterHeight()-> CGFloat { + if style == .display { + return styleFont.mathTable!.fractionDelimiterDisplayStyleSize; + } else { + return styleFont.mathTable!.fractionDelimiterSize; + } + } + + func fractionStyle() -> MTLineStyle { + // Keep fractions at the same style level instead of incrementing. + // This ensures that fraction numerators/denominators have the same + // font size as regular text, preventing them from appearing too small + // in inline mode or when nested. + return style + } + + func makeFraction(_ frac:MTFraction?) -> MTDisplay? { + guard let frac = frac else { return nil } + + // lay out the parts of the fraction + let numeratorStyle: MTLineStyle + let denominatorStyle: MTLineStyle + + if frac.isContinuedFraction { + // Continued fractions always use display style + numeratorStyle = .display + denominatorStyle = .display + } else { + // Regular fractions use adaptive style + let fractionStyle = self.fractionStyle; + numeratorStyle = fractionStyle() + denominatorStyle = fractionStyle() + } + + let numeratorDisplay = MTTypesetter.createLineForMathList(frac.numerator, font:font, style:numeratorStyle, cramped:false) + let denominatorDisplay = MTTypesetter.createLineForMathList(frac.denominator, font:font, style:denominatorStyle, cramped:true) + + // Handle empty numerator or denominator by creating empty displays + let numDisplay = numeratorDisplay ?? MTMathListDisplay(withDisplays: [], range: NSMakeRange(0, 0)) + let denomDisplay = denominatorDisplay ?? MTMathListDisplay(withDisplays: [], range: NSMakeRange(0, 0)) + + // determine the location of the numerator + var numeratorShiftUp = self.numeratorShiftUp(frac.hasRule) + var denominatorShiftDown = self.denominatorShiftDown(frac.hasRule) + let barLocation = styleFont.mathTable!.axisHeight + let barThickness = frac.hasRule ? styleFont.mathTable!.fractionRuleThickness : 0 + + if frac.hasRule { + // This is the difference between the lowest edge of the numerator and the top edge of the fraction bar + let distanceFromNumeratorToBar = (numeratorShiftUp - numDisplay.descent) - (barLocation + barThickness/2); + // The distance should at least be displayGap + let minNumeratorGap = self.numeratorGapMin; + if distanceFromNumeratorToBar < minNumeratorGap() { + // This makes the distance between the bottom of the numerator and the top edge of the fraction bar + // at least minNumeratorGap. + numeratorShiftUp += (minNumeratorGap() - distanceFromNumeratorToBar); + } + + // Do the same for the denominator + // This is the difference between the top edge of the denominator and the bottom edge of the fraction bar + let distanceFromDenominatorToBar = (barLocation - barThickness/2) - (denomDisplay.ascent - denominatorShiftDown); + // The distance should at least be denominator gap + let minDenominatorGap = self.denominatorGapMin; + if distanceFromDenominatorToBar < minDenominatorGap() { + // This makes the distance between the top of the denominator and the bottom of the fraction bar to be exactly + // minDenominatorGap + denominatorShiftDown += (minDenominatorGap() - distanceFromDenominatorToBar); + } + } else { + // This is the distance between the numerator and the denominator + let clearance = (numeratorShiftUp - numDisplay.descent) - (denomDisplay.ascent - denominatorShiftDown); + // This is the minimum clearance between the numerator and denominator. + // For ruleless fractions (like binom, choose, atop), use 1.5x the standard gap + // for better visual separation, following TeX's approach for binomial coefficients + let minGap = self.stackGapMin() * 1.5 + if clearance < minGap { + numeratorShiftUp += (minGap - clearance)/2; + denominatorShiftDown += (minGap - clearance)/2; + } + } + + let display = MTFractionDisplay(withNumerator: numDisplay, denominator: denomDisplay, position: currentPosition, range: frac.indexRange) + + display.numeratorUp = numeratorShiftUp; + display.denominatorDown = denominatorShiftDown; + display.lineThickness = barThickness; + display.linePosition = barLocation; + if frac.leftDelimiter.isEmpty && frac.rightDelimiter.isEmpty { + return display + } else { + return self.addDelimitersToFractionDisplay(display, forFraction: frac) + } + } + + func addDelimitersToFractionDisplay(_ display: MTFractionDisplay, forFraction frac: MTFraction) -> MTDisplay { + assert(!frac.leftDelimiter.isEmpty || !frac.rightDelimiter.isEmpty, "Fraction should have delimiters to call this function") + + var innerElements = [MTDisplay]() + let glyphHeight = self.fractionDelimiterHeight + var position = CGPoint.zero + if !frac.leftDelimiter.isEmpty { + if let leftGlyph = self.findGlyphForBoundary(frac.leftDelimiter, withHeight: glyphHeight()) { + leftGlyph.position = position + position.x += leftGlyph.width + innerElements.append(leftGlyph) + } + } + + display.position = position + position.x += display.width + innerElements.append(display) + + if !frac.rightDelimiter.isEmpty { + if let rightGlyph = self.findGlyphForBoundary(frac.rightDelimiter, withHeight: glyphHeight()) { + rightGlyph.position = position + position.x += rightGlyph.width + innerElements.append(rightGlyph) + } + } + let innerDisplay = MTMathListDisplay(withDisplays: innerElements, range: frac.indexRange) + innerDisplay.position = currentPosition + return innerDisplay + } + + // MARK: - Radicals + + func radicalVerticalGap() -> CGFloat { + if style == .display { + return styleFont.mathTable!.radicalDisplayStyleVerticalGap + } else { + return styleFont.mathTable!.radicalVerticalGap + } + } + + func getRadicalGlyphWithHeight(_ radicalHeight:CGFloat) -> MTDisplayDS? { + var glyphAscent=CGFloat(0), glyphDescent=CGFloat(0), glyphWidth=CGFloat(0) + + let radicalGlyph = self.findGlyphForCharacterAtIndex("\u{221A}".startIndex, inString:"\u{221A}") + let glyph = self.findGlyph(radicalGlyph, withHeight:radicalHeight, glyphAscent:&glyphAscent, glyphDescent:&glyphDescent, glyphWidth:&glyphWidth) + + var glyphDisplay:MTDisplayDS? + if glyphAscent + glyphDescent < radicalHeight { + // the glyphs is not as large as required. A glyph needs to be constructed using the extenders. + glyphDisplay = self.constructGlyph(radicalGlyph, withHeight:radicalHeight) + } + + if glyphDisplay == nil { + // No constructed display so use the glyph we got. + glyphDisplay = MTGlyphDisplay(withGlpyh: glyph, range: NSMakeRange(NSNotFound, 0), font:styleFont) + glyphDisplay!.ascent = glyphAscent; + glyphDisplay!.descent = glyphDescent; + glyphDisplay!.width = glyphWidth; + } + return glyphDisplay; + } + + func makeRadical(_ radicand:MTMathList?, range:NSRange) -> MTRadicalDisplay? { + let innerDisplay = MTTypesetter.createLineForMathList(radicand, font:font, style:style, cramped:true)! + var clearance = self.radicalVerticalGap() + let radicalRuleThickness = styleFont.mathTable!.radicalRuleThickness + let radicalHeight = innerDisplay.ascent + innerDisplay.descent + clearance + radicalRuleThickness + + let glyph = self.getRadicalGlyphWithHeight(radicalHeight)! + + // Note this is a departure from Latex. Latex assumes that glyphAscent == thickness. + // Open type math makes no such assumption, and ascent and descent are independent of the thickness. + // Latex computes delta as descent - (h(inner) + d(inner) + clearance) + // but since we may not have ascent == thickness, we modify the delta calculation slightly. + // If the font designer followes Latex conventions, it will be identical. + let delta = (glyph.descent + glyph.ascent) - (innerDisplay.ascent + innerDisplay.descent + clearance + radicalRuleThickness) + if delta > 0 { + clearance += delta/2 // increase the clearance to center the radicand inside the sign. + } + + // we need to shift the radical glyph up, to coincide with the baseline of inner. + // The new ascent of the radical glyph should be thickness + adjusted clearance + h(inner) + let radicalAscent = radicalRuleThickness + clearance + innerDisplay.ascent + let shiftUp = radicalAscent - glyph.ascent // Note: if the font designer followed latex conventions, this is the same as glyphAscent == thickness. + glyph.shiftDown = -shiftUp + + let radical = MTRadicalDisplay(withRadicand: innerDisplay, glyph: glyph, position: currentPosition, range: range) + radical.ascent = radicalAscent + styleFont.mathTable!.radicalExtraAscender + radical.topKern = styleFont.mathTable!.radicalExtraAscender + radical.lineThickness = radicalRuleThickness + // Note: Until we have radical construction from parts, it is possible that glyphAscent+glyphDescent is less + // than the requested height of the glyph (i.e. radicalHeight), so in the case the innerDisplay has a larger + // descent we use the innerDisplay's descent. + radical.descent = max(glyph.ascent + glyph.descent - radicalAscent, innerDisplay.descent) + radical.width = glyph.width + innerDisplay.width + return radical + } + + // MARK: - Glyphs + + func findGlyph(_ glyph:CGGlyph, withHeight height:CGFloat, glyphAscent:inout CGFloat, glyphDescent:inout CGFloat, glyphWidth:inout CGFloat) -> CGGlyph { + let variants = styleFont.mathTable!.getVerticalVariantsForGlyph(glyph) + let numVariants = variants.count; + var glyphs = [CGGlyph]()// numVariants) + glyphs.reserveCapacity(numVariants) + for i in 0 ..< numVariants { + let glyph = variants[i]!.uint16Value + glyphs.append(glyph) + } + + var bboxes = [CGRect](repeating: CGRect.zero, count: numVariants) + var advances = [CGSize](repeating: CGSize.zero, count: numVariants) + + // Get the bounds for these glyphs + CTFontGetBoundingRectsForGlyphs(styleFont.ctFont, .horizontal, glyphs, &bboxes, numVariants) + CTFontGetAdvancesForGlyphs(styleFont.ctFont, .horizontal, glyphs, &advances, numVariants); + var ascent=CGFloat(0), descent=CGFloat(0), width=CGFloat(0) + for i in 0..= height) { + glyphAscent = ascent; + glyphDescent = descent; + glyphWidth = width; + return glyphs[i] + } + } + glyphAscent = ascent; + glyphDescent = descent; + glyphWidth = width; + return glyphs[numVariants - 1] + } + + func constructGlyph(_ glyph:CGGlyph, withHeight glyphHeight:CGFloat) -> MTGlyphConstructionDisplay? { + let parts = styleFont.mathTable!.getVerticalGlyphAssembly(forGlyph: glyph) + if parts.count == 0 { + return nil + } + var glyphs = [NSNumber](), offsets = [NSNumber]() + var height:CGFloat=0 + self.constructGlyphWithParts(parts, glyphHeight:glyphHeight, glyphs:&glyphs, offsets:&offsets, height:&height) + var first = glyphs[0].uint16Value + let width = CTFontGetAdvancesForGlyphs(styleFont.ctFont, .horizontal, &first, nil, 1); + let display = MTGlyphConstructionDisplay(withGlyphs: glyphs, offsets: offsets, font: styleFont) + display.width = width; + display.ascent = height; + display.descent = 0; // it's upto the rendering to adjust the display up or down. + return display; + } + + func constructGlyphWithParts(_ parts:[GlyphPart], glyphHeight:CGFloat, glyphs:inout [NSNumber], offsets:inout [NSNumber], height:inout CGFloat) { + // Loop forever until the glyph height is valid + for numExtenders in 0..= glyphHeight) { + // we are done + glyphs = glyphsRv; + offsets = offsetsRv; + height = minHeight; + return; + } else if (glyphHeight <= maxHeight) { + // spread the delta equally between all the connectors + let delta = glyphHeight - minHeight; + let deltaIncrease = Float(delta) / Float(glyphsRv.count - 1) + var lastOffset = CGFloat(0) + for i in 0.. CGGlyph { + // Get the character at index taking into account UTF-32 characters + var chars = Array(str[index].utf16) + + // Get the glyph from the font + var glyph = [CGGlyph](repeating: CGGlyph.zero, count: chars.count) + let found = CTFontGetGlyphsForCharacters(styleFont.ctFont, &chars, &glyph, chars.count) + if !found { + // Try fallback font if available + if let fallbackFont = styleFont.fallbackFont { + let fallbackFound = CTFontGetGlyphsForCharacters(fallbackFont, &chars, &glyph, chars.count) + if fallbackFound { + return glyph[0] + } + } + // the font did not contain a glyph for our character, so we just return 0 (notdef) + return 0 + } + return glyph[0] + } + + // MARK: - Large Operators + + func makeLargeOp(_ op:MTLargeOperator!) -> MTDisplay? { + // Show limits above/below in display mode + // For inline mode, we still center limits below for operators like \lim, but with tighter spacing + let limits = op.limits && (style == .display || style == .text) + var delta = CGFloat(0) + if op.nucleus.count == 1 { + var glyph = self.findGlyphForCharacterAtIndex(op.nucleus.startIndex, inString:op.nucleus) + if glyph != 0 { + // Enlarge large operators to make them visually distinctive + if style == .display { + // Display style: use large variant for mathematical display mode (~2.2em) + glyph = styleFont.mathTable!.getLargerGlyph(glyph, forDisplayStyle: true) + } else if style == .text { + // Text/inline style: use moderately larger variant to ensure operator is taller than surrounding text + glyph = styleFont.mathTable!.getLargerGlyph(glyph, forDisplayStyle: false) + } + // Script and scriptOfScript styles keep base size (compact rendering) + } + // This is be the italic correction of the character. + delta = styleFont.mathTable!.getItalicCorrection(glyph) + + // vertically center + let bbox = CTFontGetBoundingRectsForGlyphs(styleFont.ctFont, .horizontal, &glyph, nil, 1); + let width = CTFontGetAdvancesForGlyphs(styleFont.ctFont, .horizontal, &glyph, nil, 1); + var ascent=CGFloat(0), descent=CGFloat(0) + getBboxDetails(bbox, ascent: &ascent, descent: &descent) + let shiftDown = 0.5*(ascent - descent) - styleFont.mathTable!.axisHeight; + let glyphDisplay = MTGlyphDisplay(withGlpyh: glyph, range: op.indexRange, font: styleFont) + glyphDisplay.ascent = ascent; + glyphDisplay.descent = descent; + glyphDisplay.width = width; + if (op.subScript != nil) && !limits { + // Remove italic correction from the width of the glyph if + // there is a subscript and limits is not set. + glyphDisplay.width -= delta; + } + glyphDisplay.shiftDown = shiftDown; + glyphDisplay.position = currentPosition; + return self.addLimitsToDisplay(glyphDisplay, forOperator:op, delta:delta) + } else { + // Create a regular node + let line = NSMutableAttributedString(string: op.nucleus) + // add the font + line.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value:styleFont.ctFont!, range:NSMakeRange(0, line.length)) + let displayAtom = MTCTLineDisplay(withString: line, position: currentPosition, range: op.indexRange, font: styleFont, atoms: [op]) + return self.addLimitsToDisplay(displayAtom, forOperator:op, delta:0) + } + } + + func addLimitsToDisplay(_ display:MTDisplay?, forOperator op:MTLargeOperator, delta:CGFloat) -> MTDisplay? { + // If there is no subscript or superscript, just return the current display + if op.subScript == nil && op.superScript == nil { + currentPosition.x += display!.width + return display; + } + // Show limits above/below in both display and text (inline) modes + if op.limits && (style == .display || style == .text) { + // make limits (above/below positioning) + var superScript:MTMathListDisplay? = nil, subScript:MTMathListDisplay? = nil + + // Scale font for script style before creating scripts + // This matches how MTDisplayPreRenderer.renderScript() handles script sizing + let scriptStyle = self.scriptStyle() + let scriptFontSize = MTTypesetter.getStyleSize(scriptStyle, font: font) + let scriptFont = font.copy(withSize: scriptFontSize) + + if op.superScript != nil { + superScript = MTTypesetter.createLineForMathList(op.superScript, font:scriptFont, style:scriptStyle, cramped:self.superScriptCramped()) + } + if op.subScript != nil { + subScript = MTTypesetter.createLineForMathList(op.subScript, font:scriptFont, style:scriptStyle, cramped:self.subscriptCramped()) + } + assert((superScript != nil) || (subScript != nil), "At least one of superscript or subscript should have been present."); + let opsDisplay = MTLargeOpLimitsDisplay(withNucleus:display, upperLimit:superScript, lowerLimit:subScript, limitShift:delta/2, extraPadding:0) + + // Use standard OpenType MATH metrics for limit spacing + if superScript != nil { + let upperLimitGap = max(styleFont.mathTable!.upperLimitGapMin, styleFont.mathTable!.upperLimitBaselineRiseMin - superScript!.descent); + opsDisplay.upperLimitGap = upperLimitGap; + } + if subScript != nil { + let lowerLimitGap = max(styleFont.mathTable!.lowerLimitGapMin, styleFont.mathTable!.lowerLimitBaselineDropMin - subScript!.ascent); + opsDisplay.lowerLimitGap = lowerLimitGap; + } + opsDisplay.position = currentPosition; + opsDisplay.range = op.indexRange; + currentPosition.x += opsDisplay.width; + return opsDisplay; + } else { + currentPosition.x += display!.width; + self.makeScripts(op, display:display, index:safeUIntFromLocation(op.indexRange.location), delta:delta) + return display; + } + } + + // MARK: - Large delimiters + + // Delimiter shortfall from plain.tex + static let kDelimiterFactor = CGFloat(901) + static let kDelimiterShortfallPoints = CGFloat(5) + + func makeLeftRight(_ inner: MTInner?, maxWidth: CGFloat = 0) -> MTDisplay? { + assert(inner!.leftBoundary != nil || inner!.rightBoundary != nil, "Inner should have a boundary to call this function"); + + let glyphHeight: CGFloat + + // Check if we have an explicit delimiter height (from \big, \Big, etc.) + if let delimiterMultiplier = inner!.delimiterHeight { + // delimiterHeight is a multiplier (e.g., 1.2, 1.8, 2.4, 3.0) + // Multiply by font size to get actual height + glyphHeight = styleFont.fontSize * delimiterMultiplier + } else { + // Calculate height based on inner content (for \left...\right) + let innerListDisplay = MTTypesetter.createLineForMathList(inner!.innerList, font:font, style:style, cramped:cramped, spaced:true, maxWidth:maxWidth) + let axisHeight = styleFont.mathTable!.axisHeight + // delta is the max distance from the axis + let delta = max(innerListDisplay!.ascent - axisHeight, innerListDisplay!.descent + axisHeight); + let d1 = (delta / 500) * MTTypesetter.kDelimiterFactor; // This represents atleast 90% of the formula + let d2 = 2 * delta - MTTypesetter.kDelimiterShortfallPoints; // This represents a shortfall of 5pt + // The size of the delimiter glyph should cover at least 90% of the formula or + // be at most 5pt short. + glyphHeight = max(d1, d2); + } + + var innerElements = [MTDisplay]() + var position = CGPoint.zero + + // Add horizontal padding between delimiters and content + // Use 2 mu (about 1/9 em) for breathing room, matching TeX standards + let delimiterPadding = styleFont.mathTable!.muUnit * 2 + + if inner!.leftBoundary != nil && !inner!.leftBoundary!.nucleus.isEmpty { + let leftGlyph = self.findGlyphForBoundary(inner!.leftBoundary!.nucleus, withHeight:glyphHeight) + leftGlyph!.position = position + position.x += leftGlyph!.width + innerElements.append(leftGlyph!) + // Add padding after left delimiter + position.x += delimiterPadding + } + + // Only include inner content if not using explicit delimiter height + // (explicit height commands like \big produce standalone delimiters) + if inner!.delimiterHeight == nil { + let innerListDisplay = MTTypesetter.createLineForMathList(inner!.innerList, font:font, style:style, cramped:cramped, spaced:true, maxWidth:maxWidth) + innerListDisplay!.position = position; + position.x += innerListDisplay!.width; + innerElements.append(innerListDisplay!) + } + + if inner!.rightBoundary != nil && !inner!.rightBoundary!.nucleus.isEmpty { + // Add padding before right delimiter + position.x += delimiterPadding + let rightGlyph = self.findGlyphForBoundary(inner!.rightBoundary!.nucleus, withHeight:glyphHeight) + rightGlyph!.position = position; + position.x += rightGlyph!.width; + innerElements.append(rightGlyph!) + } + let innerDisplay = MTMathListDisplay(withDisplays: innerElements, range: inner!.indexRange) + return innerDisplay + } + + func findGlyphForBoundary(_ delimiter:String, withHeight glyphHeight:CGFloat) -> MTDisplay? { + var glyphAscent=CGFloat(0), glyphDescent=CGFloat(0), glyphWidth=CGFloat(0) + let leftGlyph = self.findGlyphForCharacterAtIndex(delimiter.startIndex, inString:delimiter) + let glyph = self.findGlyph(leftGlyph, withHeight:glyphHeight, glyphAscent:&glyphAscent, glyphDescent:&glyphDescent, glyphWidth:&glyphWidth) + + var glyphDisplay:MTDisplayDS? + if (glyphAscent + glyphDescent < glyphHeight) { + // we didn't find a pre-built glyph that is large enough + glyphDisplay = self.constructGlyph(leftGlyph, withHeight:glyphHeight) + } + + if glyphDisplay == nil { + // Create a glyph display + glyphDisplay = MTGlyphDisplay(withGlpyh: glyph, range: NSMakeRange(NSNotFound, 0), font:styleFont) + glyphDisplay!.ascent = glyphAscent; + glyphDisplay!.descent = glyphDescent; + glyphDisplay!.width = glyphWidth; + } + // Center the glyph on the axis + let shiftDown = 0.5*(glyphDisplay!.ascent - glyphDisplay!.descent) - styleFont.mathTable!.axisHeight; + glyphDisplay!.shiftDown = shiftDown; + return glyphDisplay; + } + + // MARK: - Underline/Overline + + func makeUnderline(_ under:MTUnderLine?) -> MTDisplay? { + let innerListDisplay = MTTypesetter.createLineForMathList(under!.innerList, font:font, style:style, cramped:cramped) + let underDisplay = MTLineDisplay(withInner: innerListDisplay, position: currentPosition, range: under!.indexRange) + // Move the line down by the vertical gap. + underDisplay.lineShiftUp = -(innerListDisplay!.descent + styleFont.mathTable!.underbarVerticalGap); + underDisplay.lineThickness = styleFont.mathTable!.underbarRuleThickness; + underDisplay.ascent = innerListDisplay!.ascent + underDisplay.descent = innerListDisplay!.descent + styleFont.mathTable!.underbarVerticalGap + styleFont.mathTable!.underbarRuleThickness + styleFont.mathTable!.underbarExtraDescender; + underDisplay.width = innerListDisplay!.width; + return underDisplay; + } + + func makeOverline(_ over:MTOverLine?) -> MTDisplay? { + let innerListDisplay = MTTypesetter.createLineForMathList(over!.innerList, font:font, style:style, cramped:true) + let overDisplay = MTLineDisplay(withInner:innerListDisplay, position:currentPosition, range:over!.indexRange) + overDisplay.lineShiftUp = innerListDisplay!.ascent + styleFont.mathTable!.overbarVerticalGap; + overDisplay.lineThickness = styleFont.mathTable!.underbarRuleThickness; + overDisplay.ascent = innerListDisplay!.ascent + styleFont.mathTable!.overbarVerticalGap + styleFont.mathTable!.overbarRuleThickness + styleFont.mathTable!.overbarExtraAscender; + overDisplay.descent = innerListDisplay!.descent; + overDisplay.width = innerListDisplay!.width; + return overDisplay; + } + + // MARK: - Accents + + func isSingleCharAccentee(_ accent:MTAccent?) -> Bool { + guard let accent = accent else { return false } + if accent.innerList!.atoms.count != 1 { + // Not a single char list. + return false + } + let innerAtom = accent.innerList!.atoms[0] + if innerAtom.nucleus.count != 1 { + // A complex atom, not a simple char. + return false + } + if innerAtom.subScript != nil || innerAtom.superScript != nil { + return false + } + return true + } + + // The distance the accent must be moved from the beginning. + func getSkew(_ accent: MTAccent?, accenteeWidth width:CGFloat, accentGlyph:CGGlyph) -> CGFloat { + guard let accent = accent else { return 0 } + if accent.nucleus.isEmpty { + // No accent + return 0 + } + let accentAdjustment = styleFont.mathTable!.getTopAccentAdjustment(accentGlyph) + var accenteeAdjustment = CGFloat(0) + if !self.isSingleCharAccentee(accent) { + // use the center of the accentee + accenteeAdjustment = width/2 + } else { + let innerAtom = accent.innerList!.atoms[0] + let accenteeGlyph = self.findGlyphForCharacterAtIndex(innerAtom.nucleus.index(innerAtom.nucleus.endIndex, offsetBy:-1), inString:innerAtom.nucleus) + accenteeAdjustment = styleFont.mathTable!.getTopAccentAdjustment(accenteeGlyph) + } + // The adjustments need to aligned, so skew is just the difference. + return (accenteeAdjustment - accentAdjustment) + } + + // Find the largest horizontal variant if exists, with width less than max width. + func findVariantGlyph(_ glyph:CGGlyph, withMaxWidth maxWidth:CGFloat, maxWidth glyphAscent:inout CGFloat, glyphDescent:inout CGFloat, glyphWidth:inout CGFloat, glyphMinY:inout CGFloat) -> CGGlyph { + let variants = styleFont.mathTable!.getHorizontalVariantsForGlyph(glyph) + let numVariants = variants.count + assert(numVariants > 0, "A glyph is always it's own variant, so number of variants should be > 0"); + var glyphs = [CGGlyph]() // [numVariants) + glyphs.reserveCapacity(numVariants) + for i in 0 ..< numVariants { + let glyph = variants[i]!.uint16Value + glyphs.append(glyph) + } + + var curGlyph = glyphs[0] // if no other glyph is found, we'll return the first one. + var bboxes = [CGRect](repeating: CGRect.zero, count: numVariants) // [numVariants) + var advances = [CGSize](repeating: CGSize.zero, count:numVariants) + // Get the bounds for these glyphs + CTFontGetBoundingRectsForGlyphs(styleFont.ctFont, .horizontal, &glyphs, &bboxes, numVariants); + CTFontGetAdvancesForGlyphs(styleFont.ctFont, .horizontal, &glyphs, &advances, numVariants); + for i in 0.. maxWidth) { + if (i == 0) { + // glyph dimensions are not yet set + glyphWidth = advances[i].width; + glyphAscent = ascent; + glyphDescent = descent; + glyphMinY = bounds.minY; + } + return curGlyph; + } else { + curGlyph = glyphs[i] + glyphWidth = advances[i].width; + glyphAscent = ascent; + glyphDescent = descent; + glyphMinY = bounds.minY; + } + } + // We exhausted all the variants and none was larger than the width, so we return the largest + return curGlyph; + } + + /// Gets the proper glyph name for arrow accents that have stretchy variants in the font. + /// Returns different glyphs based on the LaTeX command used: + /// - \vec: use combining character glyph (uni20D7) for small fixed-size arrow + /// - \overrightarrow: use non-combining arrow (arrowright) which can be stretched + func getArrowAccentGlyphName(_ accent: MTAccent) -> String? { + // Check if this is a stretchy arrow accent (set by the factory based on LaTeX command) + let useStretchy = accent.isStretchy + + // Map Unicode combining characters to appropriate glyph names + switch accent.nucleus { + case "\u{20D6}": // Combining left arrow above + return useStretchy ? "arrowleft" : "uni20D6" + case "\u{20D7}": // Combining right arrow above (\vec or \overrightarrow) + return useStretchy ? "arrowright" : "uni20D7" + case "\u{20E1}": // Combining left right arrow above + return useStretchy ? "arrowboth" : "uni20E1" + default: + return nil + } + } + + /// Gets the proper glyph name for wide accents that should stretch to cover content. + /// Returns different glyphs based on the LaTeX command used: + /// - \hat: use combining character for fixed-size accent + /// - \widehat: use non-combining circumflex which can be stretched + func getWideAccentGlyphName(_ accent: MTAccent) -> String? { + // Only apply to wide accents (set by factory based on LaTeX command) + guard accent.isWide else { return nil } + + // Map Unicode combining characters to non-combining glyph names with stretchy variants + switch accent.nucleus { + case "\u{0302}": // COMBINING CIRCUMFLEX ACCENT (\hat or \widehat) + return "circumflex" + case "\u{0303}": // COMBINING TILDE (\tilde or \widetilde) + return "tilde" + case "\u{030C}": // COMBINING CARON (\check or \widecheck) + return "caron" + default: + return nil + } + } + + /// Counts the approximate character length of the content under a wide accent. + /// This is used to select the appropriate glyph variant. + func getWideAccentContentLength(_ accent: MTAccent) -> Int { + guard let innerList = accent.innerList else { return 0 } + + var charCount = 0 + for atom in innerList.atoms { + switch atom.type { + case .variable, .number: + // Count actual characters + charCount += atom.nucleus.count + case .ordinary, .binaryOperator, .relation: + // Count as single character + charCount += 1 + case .fraction: + // Fractions count as 2 units + charCount += 2 + case .radical: + // Radicals count as 2 units + charCount += 2 + case .largeOperator: + // Large operators count as 2 units + charCount += 2 + default: + // Other types count as 1 unit + charCount += 1 + } + } + return charCount + } + + /// Determines which glyph variant to use for a wide accent based on content length. + /// Returns a multiplier for the requested width (1.0, 1.5, 2.0, or 2.5) + /// Selects variants based on character count to ensure proper coverage. + func getWideAccentVariantMultiplier(_ accent: MTAccent) -> CGFloat { + let charCount = getWideAccentContentLength(accent) + + // Map character count to variant width request multiplier + // This helps select larger glyph variants from the font's MATH table + // 1-2 chars: request 1.0x (smallest variant) + // 3-4 chars: request 1.5x (medium variant) + // 5-6 chars: request 2.0x (large variant) + // 7+ chars: request 2.5x (largest variant) + if charCount <= 2 { + return 1.0 + } else if charCount <= 4 { + return 1.5 + } else if charCount <= 6 { + return 2.0 + } else { + return 2.5 + } + } + + func makeAccent(_ accent:MTAccent?) -> MTDisplay? { + guard let accent = accent else { return nil } + + let accentee = MTTypesetter.createLineForMathList(accent.innerList, font:font, style:style, cramped:true) + if accent.nucleus.isEmpty { + // no accent! + return accentee + } + + // If accentee is nil (empty content), create an empty display + guard let accentee = accentee else { + // Return an empty display for empty content + let emptyDisplay = MTMathListDisplay(withDisplays: [], range: accent.indexRange) + emptyDisplay.position = currentPosition + return emptyDisplay + } + + var accentGlyph: CGGlyph + let isArrowAccent = getArrowAccentGlyphName(accent) != nil + let isWideAccent = getWideAccentGlyphName(accent) != nil + + // Check for special accent types that need non-combining glyphs + if let wideGlyphName = getWideAccentGlyphName(accent) { + // For wide accents, use non-combining glyphs (e.g., "circumflex", "tilde") + // These have horizontal variants that can stretch + accentGlyph = styleFont.get(glyphWithName: wideGlyphName) + } else if let arrowGlyphName = getArrowAccentGlyphName(accent) { + // For arrow accents, use non-combining arrow glyphs (e.g., "arrowright") + // These have larger horizontal variants than the combining versions + accentGlyph = styleFont.get(glyphWithName: arrowGlyphName) + } else { + // For regular accents, use Unicode character lookup + let end = accent.nucleus.index(before: accent.nucleus.endIndex) + accentGlyph = self.findGlyphForCharacterAtIndex(end, inString:accent.nucleus) + } + + let accenteeWidth = accentee.width; + var glyphAscent=CGFloat(0), glyphDescent=CGFloat(0), glyphWidth=CGFloat(0), glyphMinY=CGFloat(0) + + // Adjust requested width based on accent type: + // - Wide accents (\widehat): request width based on content length (variant selection) + // - Arrow accents (\overrightarrow): request extra width for stretching + // - Regular accents: request exact content width + let requestedWidth: CGFloat + if isWideAccent { + // For wide accents, request width based on content length to select appropriate variant + let multiplier = getWideAccentVariantMultiplier(accent) + requestedWidth = accenteeWidth * multiplier + } else if isArrowAccent { + if accent.isStretchy { + requestedWidth = accenteeWidth * 1.1 // Request extra width for stretching + } else { + requestedWidth = 1.0 // Get smallest non-zero variant (typically .h1) + } + } else { + requestedWidth = accenteeWidth + } + + accentGlyph = self.findVariantGlyph(accentGlyph, withMaxWidth:requestedWidth, maxWidth:&glyphAscent, glyphDescent:&glyphDescent, glyphWidth:&glyphWidth, glyphMinY:&glyphMinY) + + // For non-stretchy arrow accents (\vec): if we got a zero-width glyph (base combining char), + // manually select the first variant which is the proper accent size + if isArrowAccent && !accent.isStretchy && glyphWidth == 0 { + guard let mathTable = styleFont.mathTable else { return nil } + let variants = mathTable.getHorizontalVariantsForGlyph(accentGlyph) + if variants.count > 1, let variantNum = variants[1] { + // Use the first variant (.h1) which has proper width + accentGlyph = CGGlyph(variantNum.uint16Value) + var glyph = accentGlyph + var advances = CGSize.zero + CTFontGetAdvancesForGlyphs(styleFont.ctFont, .horizontal, &glyph, &advances, 1) + glyphWidth = advances.width + // Recalculate ascent and descent for the variant glyph + var boundingRects = CGRect.zero + CTFontGetBoundingRectsForGlyphs(styleFont.ctFont, .horizontal, &glyph, &boundingRects, 1) + glyphMinY = boundingRects.minY + glyphAscent = boundingRects.maxY + glyphDescent = -boundingRects.minY + } + } + + // Special accents (arrows and wide accents) need more vertical space and different positioning + let delta: CGFloat + let height: CGFloat + let skew: CGFloat + + if isWideAccent { + // Wide accents (\widehat, \widetilde): use same vertical spacing as stretchy arrows + delta = 0 // No compression for wide accents + guard let mathTable = styleFont.mathTable else { return nil } + let wideAccentSpacing = mathTable.upperLimitGapMin // Same as stretchy arrows + // Compensate for internal glyph whitespace (minY > 0) + let minYCompensation = max(0, glyphMinY) + height = accentee.ascent + wideAccentSpacing - minYCompensation + + // For wide accents: if the largest glyph variant is still smaller than content width, + // scale it horizontally to fully cover the content + if glyphWidth < accenteeWidth { + // Add padding to make accent extend slightly beyond content + // Use ~0.1em padding (less than arrows which use ~0.167em) + let widePadding = styleFont.fontSize / 10 // Approximately 0.1em + let targetWidth = accenteeWidth + widePadding + + let scaleX = targetWidth / glyphWidth + let accentGlyphDisplay = MTGlyphDisplay(withGlpyh: accentGlyph, range: accent.indexRange, font: styleFont) + accentGlyphDisplay.scaleX = scaleX // Apply horizontal scaling + accentGlyphDisplay.ascent = glyphAscent + accentGlyphDisplay.descent = glyphDescent + accentGlyphDisplay.width = targetWidth // Set width to include padding + accentGlyphDisplay.position = CGPointMake(0, height) // Align to left edge + + var finalAccentee = accentee + if self.isSingleCharAccentee(accent) && (accent.subScript != nil || accent.superScript != nil) { + // Attach the super/subscripts to the accentee instead of the accent. + guard let innerList = accent.innerList, + !innerList.atoms.isEmpty else { return nil } + let innerAtom = innerList.atoms[0] + innerAtom.superScript = accent.superScript + innerAtom.subScript = accent.subScript + accent.superScript = nil + accent.subScript = nil + if let remadeAccentee = MTTypesetter.createLineForMathList(accent.innerList, font:font, style:style, cramped:cramped) { + finalAccentee = remadeAccentee + } + } + + let display = MTAccentDisplay(withAccent:accentGlyphDisplay, accentee:finalAccentee, range:accent.indexRange) + display.width = finalAccentee.width + display.descent = finalAccentee.descent + let ascent = height + glyphAscent + display.ascent = max(finalAccentee.ascent, ascent) + display.position = currentPosition + return display + } else { + // Wide accent glyph is wide enough: center it over the content + skew = (accenteeWidth - glyphWidth) / 2 + } + } else if isArrowAccent { + // Arrow accents spacing depends on whether they're stretchy or not + guard let mathTable = styleFont.mathTable else { return nil } + if accent.isStretchy { + // Stretchy arrows (\overrightarrow): use full ascent + additional spacing + delta = 0 // No compression for stretchy arrows + let arrowSpacing = mathTable.upperLimitGapMin // Use standard gap + // Compensate for internal glyph whitespace (minY > 0) + let minYCompensation = max(0, glyphMinY) + height = accentee.ascent + arrowSpacing - minYCompensation + } else { + // Non-stretchy arrows (\vec): use tight spacing like regular accents + // This gives a more compact appearance suitable for single-character vectors + delta = min(accentee.ascent, mathTable.accentBaseHeight) + // Use same formula as regular accents (no minYCompensation adjustment) + // This places the arrow properly above the character + height = accentee.ascent - delta + } + + // For stretchy arrow accents (\overrightarrow): if the largest glyph variant is still smaller than content width, + // scale it horizontally to fully cover the content + // Add small padding to make arrow tip extend slightly beyond content + // For non-stretchy accents (\vec): always center without scaling + if accent.isStretchy && glyphWidth < accenteeWidth { + // Add padding to make arrow extend beyond content on the tip side + // Use approximately 0.15-0.2em extra width + let arrowPadding = styleFont.fontSize / 6 // Approximately 0.167em at typical font sizes + let targetWidth = accenteeWidth + arrowPadding + + let scaleX = targetWidth / glyphWidth + let accentGlyphDisplay = MTGlyphDisplay(withGlpyh: accentGlyph, range: accent.indexRange, font: styleFont) + accentGlyphDisplay.scaleX = scaleX // Apply horizontal scaling + accentGlyphDisplay.ascent = glyphAscent + accentGlyphDisplay.descent = glyphDescent + accentGlyphDisplay.width = targetWidth // Set width to include padding + accentGlyphDisplay.position = CGPointMake(0, height) // Align to left edge + + var finalAccentee = accentee + if self.isSingleCharAccentee(accent) && (accent.subScript != nil || accent.superScript != nil) { + // Attach the super/subscripts to the accentee instead of the accent. + guard let innerList = accent.innerList, + !innerList.atoms.isEmpty else { return nil } + let innerAtom = innerList.atoms[0] + innerAtom.superScript = accent.superScript + innerAtom.subScript = accent.subScript + accent.superScript = nil + accent.subScript = nil + if let remadeAccentee = MTTypesetter.createLineForMathList(accent.innerList, font:font, style:style, cramped:cramped) { + finalAccentee = remadeAccentee + } + } + + let display = MTAccentDisplay(withAccent:accentGlyphDisplay, accentee:finalAccentee, range:accent.indexRange) + display.width = finalAccentee.width + display.descent = finalAccentee.descent + let ascent = height + glyphAscent + display.ascent = max(finalAccentee.ascent, ascent) + display.position = currentPosition + return display + } else { + // Arrow glyph is wide enough or is non-stretchy (\vec): center it over the content + skew = (accenteeWidth - glyphWidth) / 2 + } + } else { + // For regular accents: use traditional tight positioning + guard let mathTable = styleFont.mathTable else { return nil } + delta = min(accentee.ascent, mathTable.accentBaseHeight) + skew = self.getSkew(accent, accenteeWidth:accenteeWidth, accentGlyph:accentGlyph) + height = accentee.ascent - delta // This is always positive since delta <= height. + } + + let accentPosition = CGPointMake(skew, height); + let accentGlyphDisplay = MTGlyphDisplay(withGlpyh: accentGlyph, range: accent.indexRange, font: styleFont) + accentGlyphDisplay.ascent = glyphAscent; + accentGlyphDisplay.descent = glyphDescent; + accentGlyphDisplay.width = glyphWidth; + accentGlyphDisplay.position = accentPosition; + + var finalAccentee = accentee + if self.isSingleCharAccentee(accent) && (accent.subScript != nil || accent.superScript != nil) { + // Attach the super/subscripts to the accentee instead of the accent. + guard let innerList = accent.innerList, + !innerList.atoms.isEmpty else { return nil } + let innerAtom = innerList.atoms[0] + innerAtom.superScript = accent.superScript; + innerAtom.subScript = accent.subScript; + accent.superScript = nil; + accent.subScript = nil; + // Remake the accentee (now with sub/superscripts) + // Note: Latex adjusts the heights in case the height of the char is different in non-cramped mode. However this shouldn't be the case since cramping + // only affects fractions and superscripts. We skip adjusting the heights. + if let remadeAccentee = MTTypesetter.createLineForMathList(accent.innerList, font:font, style:style, cramped:cramped) { + finalAccentee = remadeAccentee + } + } + + let display = MTAccentDisplay(withAccent:accentGlyphDisplay, accentee:finalAccentee, range:accent.indexRange) + display.width = finalAccentee.width; + display.descent = finalAccentee.descent; + + // Calculate total ascent based on positioning + // For arrows: height already includes spacing, so ascent = height + glyphAscent + // For regular accents: ascent = accentee.ascent - delta + glyphAscent (existing formula) + let ascent = height + glyphAscent; + display.ascent = max(finalAccentee.ascent, ascent); + display.position = currentPosition; + + return display; + } + + /// Determines if an accent can use Unicode composition for inline rendering. + /// Unicode combining characters only work correctly for single base characters. + /// Multi-character expressions and arrow accents need font-based rendering. + func canUseUnicodeComposition(_ accent: MTAccent) -> Bool { + // Check if innerList has exactly one simple character + guard let innerList = accent.innerList, + innerList.atoms.count == 1, + let firstAtom = innerList.atoms.first else { + return false + } + + // Only allow simple variable/number atoms + guard firstAtom.type == .variable || firstAtom.type == .number else { + return false + } + + // Check that the atom doesn't have subscripts/superscripts + guard firstAtom.subScript == nil && firstAtom.superScript == nil else { + return false + } + + // Exclude arrow accents - they need stretching from font glyphs + // These Unicode combining characters only apply to single preceding characters + let arrowAccents: Set = [ + "\u{20D6}", // overleftarrow + "\u{20D7}", // overrightarrow / vec + "\u{20E1}" // overleftrightarrow + ] + + if arrowAccents.contains(accent.nucleus) { + return false + } + + return true + } + + // MARK: - Table + + let kBaseLineSkipMultiplier = CGFloat(1.2) // default base line stretch is 12 pt for 10pt font. + let kLineSkipMultiplier = CGFloat(0.1) // default is 1pt for 10pt font. + let kLineSkipLimitMultiplier = CGFloat(0) + let kJotMultiplier = CGFloat(0.3) // A jot is 3pt for a 10pt font. + + func makeTable(_ table:MTMathTable?) -> MTDisplay? { + let numColumns = table!.numColumns; + if numColumns == 0 || table!.numRows == 0 { + // Empty table + return MTMathListDisplay(withDisplays: [MTDisplay](), range: table!.indexRange) + } + + var columnWidths = [CGFloat](repeating: 0, count: numColumns) + let displays = self.typesetCells(table, columnWidths:&columnWidths) + + // Position all the columns in each row + var rowDisplays = [MTDisplay]() + for row in displays { + if let rowDisplay = self.makeRowWithColumns(row, forTable:table, columnWidths:columnWidths) { + rowDisplays.append(rowDisplay) + } + } + + // Position all the rows + self.positionRows(rowDisplays, forTable:table) + let tableDisplay = MTMathListDisplay(withDisplays: rowDisplays, range: table!.indexRange) + tableDisplay.position = currentPosition; + return tableDisplay; + } + + // Typeset every cell in the table. As a side-effect calculate the max column width of each column. + func typesetCells(_ table:MTMathTable?, columnWidths: inout [CGFloat]) -> [[MTDisplay]] { + var displays = [[MTDisplay]]() + for row in table!.cells { + var colDisplays = [MTDisplay]() + for i in 0.. MTMathListDisplay? { + var columnStart = CGFloat(0) + var rowRange = NSMakeRange(NSNotFound, 0); + for i in 0.. 0 { + if rowRange.location != NSNotFound { + rowRange = NSUnionRange(rowRange, col.range); + } else { + rowRange = col.range; + } + } + // If col.range is invalid or has zero length, skip it - don't update rowRange + + col.position = CGPointMake(cellPos, 0); + columnStart += colWidth + table!.interColumnSpacing * styleFont.mathTable!.muUnit; + } + + // If no valid ranges were found (all cells had zero-length ranges), use a synthetic range + // This represents the row conceptually spanning all its columns + if rowRange.location == NSNotFound { + rowRange = NSMakeRange(0, cols.count); + } + + // Create a display for the row + let rowDisplay = MTMathListDisplay(withDisplays: cols, range:rowRange) + return rowDisplay + } + + func positionRows(_ rows:[MTDisplay], forTable table:MTMathTable?) { + // Position the rows + // We will first position the rows starting from 0 and then in the second pass center the whole table vertically. + var currPos = CGFloat(0) + let openup = table!.interRowAdditionalSpacing * kJotMultiplier * styleFont.fontSize; + let baselineSkip = openup + kBaseLineSkipMultiplier * styleFont.fontSize; + let lineSkip = openup + kLineSkipMultiplier * styleFont.fontSize; + let lineSkipLimit = openup + kLineSkipLimitMultiplier * styleFont.fontSize; + var prevRowDescent = CGFloat(0) + var ascent = CGFloat(0) + var first = true + for row in rows { + if first { + row.position = CGPointZero; + ascent += row.ascent; + first = false; + } else { + var skip = baselineSkip; + if (skip - (prevRowDescent + row.ascent) < lineSkipLimit) { + // rows are too close to each other. Space them apart further + skip = prevRowDescent + row.ascent + lineSkip; + } + // We are going down so we decrease the y value. + currPos -= skip; + row.position = CGPointMake(0, currPos); + } + prevRowDescent = row.descent; + } + + // Vertically center the whole structure around the axis + // The descent of the structure is the position of the last row + // plus the descent of the last row. + let descent = -currPos + prevRowDescent; + let shiftDown = 0.5*(ascent - descent) - styleFont.mathTable!.axisHeight; + + for row in rows { + row.position = CGPointMake(row.position.x, row.position.y - shiftDown); + } + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/MTUnicode.swift b/third-party/SwiftMath/Sources/MathRender/MTUnicode.swift new file mode 100755 index 0000000000..a1fc48d41c --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/MTUnicode.swift @@ -0,0 +1,89 @@ +// +// Created by Mike Griebling on 2022-12-31. +// Translated from an Objective-C implementation by Kostub Deshmukh. +// +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation + +public struct UnicodeSymbol { + static let multiplication = "\u{00D7}" + static let division = "\u{00F7}" + static let fractionSlash = "\u{2044}" + static let whiteSquare = "\u{25A1}" + static let blackSquare = "\u{25A0}" + static let lessEqual = "\u{2264}" + static let greaterEqual = "\u{2265}" + static let notEqual = "\u{2260}" + static let squareRoot = "\u{221A}" // \sqrt + static let cubeRoot = "\u{221B}" + static let infinity = "\u{221E}" // \infty + static let angle = "\u{2220}" // \angle + static let degree = "\u{00B0}" // \circ + + static let capitalGreekStart = UInt32(0x0391) + static let capitalGreekEnd = UInt32(0x03A9) + static let lowerGreekStart = UInt32(0x03B1) + static let lowerGreekEnd = UInt32(0x03C9) + static let planksConstant = UInt32(0x210e) + static let lowerItalicStart = UInt32(0x1D44E) + static let capitalItalicStart = UInt32(0x1D434) + static let greekLowerItalicStart = UInt32(0x1D6FC) + static let greekCapitalItalicStart = UInt32(0x1D6E2) + static let greekSymbolItalicStart = UInt32(0x1D716) + + static let mathCapitalBoldStart = UInt32(0x1D400) + static let mathLowerBoldStart = UInt32(0x1D41A) + static let greekCapitalBoldStart = UInt32(0x1D6A8) + static let greekLowerBoldStart = UInt32(0x1D6C2) + static let greekSymbolBoldStart = UInt32(0x1D6DC) + static let numberBoldStart = UInt32(0x1D7CE) + + static let mathCapitalBoldItalicStart = UInt32(0x1D468) + static let mathLowerBoldItalicStart = UInt32(0x1D482) + static let greekCapitalBoldItalicStart = UInt32(0x1D71C) + static let greekLowerBoldItalicStart = UInt32(0x1D736) + static let greekSymbolBoldItalicStart = UInt32(0x1D750) + + static let mathCapitalScriptStart = UInt32(0x1D49C) + static let mathCapitalTTStart = UInt32(0x1D670) + static let mathLowerTTStart = UInt32(0x1D68A) + static let numberTTStart = UInt32(0x1D7F6) + static let mathCapitalSansSerifStart = UInt32(0x1D5A0) + static let mathLowerSansSerifStart = UInt32(0x1D5BA) + static let numberSansSerifStart = UInt32(0x1D7E2) + static let mathCapitalFrakturStart = UInt32(0x1D504) + static let mathLowerFrakturStart = UInt32(0x1D51E) + static let mathCapitalBlackboardStart = UInt32(0x1D538) + static let mathLowerBlackboardStart = UInt32(0x1D552) + static let numberBlackboardStart = UInt32(0x1D7D8) +} + +extension Character { + + var utf32Char: UTF32Char { self.unicodeScalars.map { $0.value }.reduce(0, +) } + var isLowerEnglish : Bool { self >= "a" && self <= "z" } + var isUpperEnglish : Bool { self >= "A" && self <= "Z" } + var isNumber : Bool { self >= "0" && self <= "9" } + + var isLowerGreek : Bool { + let uch = self.utf32Char + return uch >= UnicodeSymbol.lowerGreekStart && uch <= UnicodeSymbol.lowerGreekEnd + } + + var isCapitalGreek : Bool { + let uch = self.utf32Char + return uch >= UnicodeSymbol.capitalGreekStart && uch <= UnicodeSymbol.capitalGreekEnd + } + + var greekSymbolOrder : UInt32? { + let greekSymbols : [UTF32Char] = [0x03F5, 0x03D1, 0x03F0, 0x03D5, 0x03F1, 0x03D6] + let index = greekSymbols.firstIndex(of: self.utf32Char) + if let pos = index { return UInt32(pos) } + return nil + } + + var isGreekSymbol : Bool { self.greekSymbolOrder != nil } +} diff --git a/third-party/SwiftMath/Sources/MathRender/RWLock.swift b/third-party/SwiftMath/Sources/MathRender/RWLock.swift new file mode 100644 index 0000000000..ccca3976e0 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/RWLock.swift @@ -0,0 +1,58 @@ +import Foundation + +final class RWLock { + init() { + pthread_rwlock_init(&lock, nil) + } + + deinit { + pthread_rwlock_destroy(&lock) + } + + func read(_ block: () -> T) -> T { + pthread_rwlock_rdlock(&lock) + defer { pthread_rwlock_unlock(&lock) } + return block() + } + + func readWrite(_ block: () -> T) -> T { + pthread_rwlock_wrlock(&lock) + defer { pthread_rwlock_unlock(&lock) } + return block() + } + + private var lock = pthread_rwlock_t() +} + +@propertyWrapper +struct RWLocked { + init(wrappedValue: T) { + value = wrappedValue + } + + var wrappedValue: T { + get { + lock.read { + value + } + } + set { + lock.readWrite { + value = newValue + } + } + } + + @discardableResult + mutating func readWrite(_ block: (inout T) -> Void) -> (oldValue: T, newValue: T) { + lock.readWrite { + let old = value + block(&value) + return (old, value) + } + } + + private var value: T + private let lock = RWLock() +} + diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTAtomTokenizer.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTAtomTokenizer.swift new file mode 100644 index 0000000000..2a76a661a4 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTAtomTokenizer.swift @@ -0,0 +1,1145 @@ +// +// MTAtomTokenizer.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics + +/// Tokenizes MTMathAtom lists into breakable elements +class MTAtomTokenizer { + + // MARK: - Properties + + let font: MTFont + let style: MTLineStyle + let cramped: Bool + let maxWidth: CGFloat + let widthCalculator: MTElementWidthCalculator + let displayRenderer: MTDisplayPreRenderer + + // MARK: - Initialization + + init(font: MTFont, style: MTLineStyle, cramped: Bool = false, maxWidth: CGFloat = 0) { + self.font = font + self.style = style + self.cramped = cramped + self.maxWidth = maxWidth + self.widthCalculator = MTElementWidthCalculator(font: font, style: style) + self.displayRenderer = MTDisplayPreRenderer(font: font, style: style, cramped: cramped) + } + + // MARK: - Main Tokenization + + /// Tokenize a list of atoms into breakable elements + func tokenize(_ atoms: [MTMathAtom]) -> [MTBreakableElement] { + var elements: [MTBreakableElement] = [] + var index = 0 + var currentStyle = self.style + + while index < atoms.count { + let atom = atoms[index] + let prevAtom = index > 0 ? atoms[index - 1] : nil + + // Check for style change atoms + if atom.type == .style, let styleAtom = atom as? MTMathStyle { + // Update style for subsequent atoms + currentStyle = styleAtom.style + index += 1 + continue + } + + // Create a tokenizer with the current style for this atom + let atomTokenizer: MTAtomTokenizer + if currentStyle != self.style { + atomTokenizer = MTAtomTokenizer(font: font, style: currentStyle, cramped: cramped, maxWidth: maxWidth) + } else { + atomTokenizer = self + } + + // Handle scripts (subscript/superscript) - these must be grouped with their base + if atom.superScript != nil || atom.subScript != nil { + let baseElements = atomTokenizer.tokenizeAtomWithScripts(atom, prevAtom: prevAtom, atomIndex: index, allAtoms: atoms) + elements.append(contentsOf: baseElements) + } else { + // Check if this is a multi-character text atom that needs character-level tokenization + let isTextAtom = atom.fontStyle == .roman + let isMultiChar = atom.nucleus.count > 1 + + if isTextAtom && isMultiChar { + // Break down multi-character text into individual characters for punctuation rules + let charElements = atomTokenizer.tokenizeMultiCharText(atom, prevElements: elements, atomIndex: index, allAtoms: atoms) + elements.append(contentsOf: charElements) + } else { + // Regular atom without scripts + if let element = atomTokenizer.tokenizeAtom(atom, prevAtom: prevAtom, atomIndex: index, allAtoms: atoms) { + elements.append(element) + } + } + } + + index += 1 + } + + return elements + } + + // MARK: - Atom Tokenization + + /// Tokenize a single atom (without scripts) + private func tokenizeAtom(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int, allAtoms: [MTMathAtom]) -> MTBreakableElement? { + switch atom.type { + // Simple text and variables + case .ordinary, .variable, .number: + return tokenizeTextAtom(atom, prevAtom: prevAtom, atomIndex: atomIndex, allAtoms: allAtoms) + + // Operators + case .binaryOperator, .relation, .unaryOperator: + return tokenizeOperator(atom, prevAtom: prevAtom, atomIndex: atomIndex) + + // Delimiters + case .open: + return tokenizeOpenDelimiter(atom, prevAtom: prevAtom, atomIndex: atomIndex) + + case .close: + return tokenizeCloseDelimiter(atom, prevAtom: prevAtom, atomIndex: atomIndex) + + // Punctuation + case .punctuation: + return tokenizePunctuation(atom, prevAtom: prevAtom, atomIndex: atomIndex) + + // Complex structures (atomic) + case .fraction: + return tokenizeFraction(atom as! MTFraction, prevAtom: prevAtom, atomIndex: atomIndex) + + case .radical: + return tokenizeRadical(atom as! MTRadical, prevAtom: prevAtom, atomIndex: atomIndex) + + case .largeOperator: + return tokenizeLargeOperator(atom as! MTLargeOperator, prevAtom: prevAtom, atomIndex: atomIndex) + + case .accent: + return tokenizeAccent(atom as! MTAccent, prevAtom: prevAtom, atomIndex: atomIndex, allAtoms: allAtoms) + + case .underline: + return tokenizeUnderline(atom as! MTUnderLine, prevAtom: prevAtom, atomIndex: atomIndex) + + case .overline: + return tokenizeOverline(atom as! MTOverLine, prevAtom: prevAtom, atomIndex: atomIndex) + + case .table: + return tokenizeTable(atom as! MTMathTable, prevAtom: prevAtom, atomIndex: atomIndex) + + case .inner: + return tokenizeInner(atom as! MTInner, prevAtom: prevAtom, atomIndex: atomIndex) + + // Spacing + case .space: + return tokenizeSpace(atom, prevAtom: prevAtom, atomIndex: atomIndex) + + // Style changes - these don't create elements + case .style: + return nil + + // Color - extract inner content with color attribute + case .color, .colorBox, .textcolor: + // For now, treat as ordinary (color will be handled in display generation) + return tokenizeTextAtom(atom, prevAtom: prevAtom, atomIndex: atomIndex, allAtoms: allAtoms) + + default: + // Treat unknown types as ordinary + return tokenizeTextAtom(atom, prevAtom: prevAtom, atomIndex: atomIndex, allAtoms: allAtoms) + } + } + + // MARK: - Text Atom Tokenization + + private func tokenizeTextAtom(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int, allAtoms: [MTMathAtom]) -> MTBreakableElement? { + let text = atom.nucleus + guard !text.isEmpty else { return nil } + + // Calculate width + let width = widthCalculator.measureText(text) + + // Calculate ascent/descent (approximate using font metrics) + let ascent = font.mathTable?.axisHeight ?? font.fontSize * 0.5 + let descent = font.fontSize * 0.2 + let height = ascent + descent + + // Determine break rules using Unicode word boundary detection + var isBreakBefore = true + var isBreakAfter = true + var penaltyBefore = MTBreakPenalty.good + var penaltyAfter = MTBreakPenalty.good + + let isTextAtom = atom.fontStyle == .roman + + // Only apply word boundary logic to text atoms (not math variables) + if isTextAtom { + // First apply punctuation rules for single-character text + // This handles cases where punctuation appears in roman text rather than as separate punctuation atoms + if text.count == 1, let char = text.first { + let (punctBreakBefore, punctBreakAfter, punctPenaltyBefore, punctPenaltyAfter) = punctuationBreakRules(char) + + // Apply punctuation rules + isBreakBefore = punctBreakBefore + penaltyBefore = punctPenaltyBefore + isBreakAfter = punctBreakAfter + penaltyAfter = punctPenaltyAfter + } + + // Then apply word boundary logic - this ANDs with punctuation rules + // Both rules must allow breaking for a break to be permitted + + // Check if we should break BEFORE this atom + if let prevAtom = prevAtom { + // Handle previous accent atoms (e.g., "é" before "r" in "bactéries") + if prevAtom.type == .accent && isTextLetterAtom(prevAtom) { + // Previous is a text accent - don't break if current is a letter + if text.first?.isLetter == true { + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } else if prevAtom.fontStyle == .roman { + let prevText = prevAtom.nucleus + if !prevText.isEmpty && !text.isEmpty { + // Use Unicode word boundary detection + if !hasWordBoundaryBetween(prevText, and: text) { + // No word boundary = we're in the middle of a word + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } + } + } + + // Check if we should break AFTER this atom + if atomIndex + 1 < allAtoms.count { + let nextAtom = allAtoms[atomIndex + 1] + // Handle next accent atoms (e.g., "t" before "é" in "bactéries") + if nextAtom.type == .accent && isTextLetterAtom(nextAtom) { + // Next is a text accent - don't break if current is a letter + if text.last?.isLetter == true { + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } else if nextAtom.fontStyle == .roman { + let nextText = nextAtom.nucleus + if !text.isEmpty && !nextText.isEmpty { + // Use Unicode word boundary detection + if !hasWordBoundaryBetween(text, and: nextText) { + // No word boundary = next atom is part of same word + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } + } + } + } + + return MTBreakableElement( + content: .text(text), + width: width, + height: height, + ascent: ascent, + descent: descent, + isBreakBefore: isBreakBefore, + isBreakAfter: isBreakAfter, + penaltyBefore: penaltyBefore, + penaltyAfter: penaltyAfter, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + /// Tokenize a multi-character text atom into individual character elements + /// This enables character-level line breaking with proper punctuation rules + private func tokenizeMultiCharText(_ atom: MTMathAtom, prevElements: [MTBreakableElement], atomIndex: Int, allAtoms: [MTMathAtom]) -> [MTBreakableElement] { + let text = atom.nucleus + guard text.count > 1 else { return [] } + + let debugTokenization = !"".isEmpty // Enable to debug text tokenization + if debugTokenization { + print("\n=== Tokenizing multi-char text: '\(text)' ===") + } + + var charElements: [MTBreakableElement] = [] + let characters = Array(text) + + for (charIndex, char) in characters.enumerated() { + let charString = String(char) + + // Calculate width for this character + let width = widthCalculator.measureText(charString) + + // Calculate ascent/descent (approximate using font metrics) + let ascent = font.mathTable?.axisHeight ?? font.fontSize * 0.5 + let descent = font.fontSize * 0.2 + let height = ascent + descent + + // Determine break rules for this character + let (isBreakBefore, isBreakAfter, penaltyBefore, penaltyAfter) = characterBreakRules( + char: char, + prevChar: charIndex > 0 ? characters[charIndex - 1] : nil, + nextChar: charIndex < characters.count - 1 ? characters[charIndex + 1] : nil, + isFirstInAtom: charIndex == 0, + isLastInAtom: charIndex == characters.count - 1, + prevElements: prevElements, + nextAtom: atomIndex + 1 < allAtoms.count ? allAtoms[atomIndex + 1] : nil + ) + + let element = MTBreakableElement( + content: .text(charString), + width: width, + height: height, + ascent: ascent, + descent: descent, + isBreakBefore: isBreakBefore, + isBreakAfter: isBreakAfter, + penaltyBefore: penaltyBefore, + penaltyAfter: penaltyAfter, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + + if debugTokenization { + print(" [\(charIndex)] '\(charString)' breakBefore=\(isBreakBefore) breakAfter=\(isBreakAfter) penaltyBefore=\(penaltyBefore) penaltyAfter=\(penaltyAfter) width=\(width)") + } + + charElements.append(element) + } + + return charElements + } + + /// Determine break rules for a character in a multi-character text string + private func characterBreakRules( + char: Character, + prevChar: Character?, + nextChar: Character?, + isFirstInAtom: Bool, + isLastInAtom: Bool, + prevElements: [MTBreakableElement], + nextAtom: MTMathAtom? + ) -> (isBreakBefore: Bool, isBreakAfter: Bool, penaltyBefore: Int, penaltyAfter: Int) { + + // Apply punctuation rules + let (punctBreakBefore, punctBreakAfter, punctPenaltyBefore, punctPenaltyAfter) = punctuationBreakRules(char) + + var isBreakBefore = punctBreakBefore + var isBreakAfter = punctBreakAfter + var penaltyBefore = punctPenaltyBefore + var penaltyAfter = punctPenaltyAfter + + // Apply word boundary logic + // Don't break in the middle of a word (but CJK characters CAN break between each other) + if let prevChar = prevChar { + if char.isLetter && prevChar.isLetter { + // Check if either character is CJK - CJK allows breaks between characters + let isCJKBreak = isCJKCharacter(char) || isCJKCharacter(prevChar) + + if !isCJKBreak { + // Both letters in same non-CJK script - middle of word, don't break + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + // else: At least one is CJK - allow break (keep punctBreakBefore value) + } else if prevChar == "'" || prevChar == "-" { + // Apostrophe or hyphen - part of word + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } else if isFirstInAtom { + // First character - check against previous element + if let lastElement = prevElements.last { + switch lastElement.content { + case .text(let prevText): + if let prevLastChar = prevText.last { + if char.isLetter && prevLastChar.isLetter { + // Check if either character is CJK + let isCJKBreak = isCJKCharacter(char) || isCJKCharacter(prevLastChar) + + if !isCJKBreak { + // Both non-CJK letters - don't break + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } else if prevLastChar == "'" || prevLastChar == "-" { + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } + case .display: + // Check if previous element is a text-mode accent (e.g., "é") + // Accents in text mode should not allow breaks after them if current is a letter + if lastElement.originalAtom.type == .accent, + isTextLetterAtom(lastElement.originalAtom), + char.isLetter { + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + default: + break + } + } + } + + if let nextChar = nextChar { + if char.isLetter && nextChar.isLetter { + // Check if either character is CJK + let isCJKBreak = isCJKCharacter(char) || isCJKCharacter(nextChar) + + if !isCJKBreak { + // Both non-CJK letters - middle of word, don't break + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } else if nextChar == "'" || nextChar == "-" { + // Before apostrophe or hyphen - part of word + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } else if isLastInAtom { + // Last character - check against next atom + if let nextAtom = nextAtom { + // Handle next accent atoms (e.g., "t" before "é" in "bactéries") + if nextAtom.type == .accent && isTextLetterAtom(nextAtom) { + if char.isLetter { + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } else if nextAtom.fontStyle == .roman, + let nextFirstChar = nextAtom.nucleus.first { + if char.isLetter && nextFirstChar.isLetter { + // Check if either character is CJK + let isCJKBreak = isCJKCharacter(char) || isCJKCharacter(nextFirstChar) + + if !isCJKBreak { + // Both non-CJK letters - don't break + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } + } + } + } + + return (isBreakBefore, isBreakAfter, penaltyBefore, penaltyAfter) + } + + // MARK: - Word Boundary Detection + + /// Determines if a character is a CJK (Chinese, Japanese, Korean) character + /// CJK characters can break between each other even though they are technically "letters" + private func isCJKCharacter(_ char: Character) -> Bool { + guard let scalar = char.unicodeScalars.first else { return false } + let value = scalar.value + + // CJK Unified Ideographs and extensions + return (value >= 0x4E00 && value <= 0x9FFF) || // CJK Unified Ideographs (most common Chinese/Japanese kanji) + (value >= 0x3400 && value <= 0x4DBF) || // CJK Unified Ideographs Extension A + (value >= 0x20000 && value <= 0x2A6DF) || // CJK Unified Ideographs Extension B + (value >= 0x3040 && value <= 0x309F) || // Hiragana (Japanese) + (value >= 0x30A0 && value <= 0x30FF) || // Katakana (Japanese) + (value >= 0xAC00 && value <= 0xD7AF) // Hangul Syllables (Korean) + } + + /// Determines if there's a word boundary between two text fragments + /// Combines Unicode word segmentation with special handling for contractions and hyphenated words + private func hasWordBoundaryBetween(_ text1: String, and text2: String) -> Bool { + // RULE 1: Check for apostrophes and hyphens between letters (contractions and hyphenated words) + // These should NOT be treated as word boundaries even though Unicode does + if let lastChar1 = text1.last, let firstChar2 = text2.first { + // Pattern: letter + apostrophe|hyphen + letter → NOT a word boundary + if lastChar1.isLetter && (firstChar2 == "'" || firstChar2 == "-") { + return false // Don't break before apostrophe/hyphen + } + if (lastChar1 == "'" || lastChar1 == "-") && firstChar2.isLetter { + return false // Don't break after apostrophe/hyphen + } + } + + // RULE 2: Use Unicode word boundary detection for everything else + // This properly handles: + // - International text (café, naïve, etc.) + // - Various Unicode whitespace characters + // - Em-dashes, ellipses, and other Unicode punctuation + // - Complex scripts (Thai, Japanese, etc.) + let combined = text1 + text2 + let junctionIndex = text1.endIndex + + var wordBoundaries: Set = [] + combined.enumerateSubstrings(in: combined.startIndex.. PunctuationClass { + let scalar = String(char).unicodeScalars.first?.value ?? 0 + + // Latin opening punctuation - never break after + if "([{".contains(char) { return .openingPunctuation } + + // Latin closing punctuation and sentence-ending - never break before + if ")]}".contains(char) { return .closingPunctuation } + if ".,;:!?".contains(char) { return .sentenceEnding } + + // Latin quotation marks - opening quotes + // U+0022 " QUOTATION MARK, U+0027 ' APOSTROPHE + // U+2018 ' LEFT SINGLE QUOTATION MARK, U+201C " LEFT DOUBLE QUOTATION MARK + // U+00AB « LEFT-POINTING DOUBLE ANGLE QUOTATION MARK, U+2039 ‹ SINGLE LEFT-POINTING ANGLE QUOTATION MARK + if scalar == 0x0022 || scalar == 0x0027 || // Basic quotes + scalar == 0x2018 || scalar == 0x201C || // Curly left quotes + scalar == 0x00AB || scalar == 0x2039 { // Guillemets + return .openingPunctuation + } + + // Latin quotation marks - closing quotes + // U+2019 ' RIGHT SINGLE QUOTATION MARK, U+201D " RIGHT DOUBLE QUOTATION MARK + // U+00BB » RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK, U+203A › SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + if scalar == 0x2019 || scalar == 0x201D || // Curly right quotes + scalar == 0x00BB || scalar == 0x203A { // Guillemets + return .closingPunctuation + } + + // CJK opening brackets (禁則: line-start prohibited) + // Japanese/Chinese full-width brackets and corner brackets + if "「『(【〔〈《".contains(char) { + return .openingPunctuation + } + + // CJK closing brackets (禁則: line-end prohibited) + if "」』)】〕〉》".contains(char) { + return .closingPunctuation + } + + // CJK sentence-ending punctuation (禁則: line-end prohibited) + // Japanese/Chinese full-width periods, commas, and other punctuation + if "。、!?:;".contains(char) { + return .sentenceEnding + } + + // CJK small kana (禁則: line-end prohibited) + // These are smaller versions of hiragana/katakana that must not start a line + if "ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮ".contains(char) { + return .cjkSmallKana + } + + // CJK iteration marks (禁則: line-end prohibited) + if "ゝゞヽヾ々〻".contains(char) { + return .cjkSmallKana // Same rules as small kana + } + + // CJK prolonged sound mark (禁則: line-end prohibited) + if char == "ー" { + return .cjkSmallKana // Same rules as small kana + } + + return .neutral + } + + /// Determine break rules for punctuation based on its classification + private func punctuationBreakRules(_ char: Character) -> (isBreakBefore: Bool, isBreakAfter: Bool, penaltyBefore: Int, penaltyAfter: Int) { + let classification = classifyPunctuation(char) + + switch classification { + case .openingPunctuation: + // Opening punctuation: can break before, NEVER after + // Examples: ( [ { " ' « 「『 + return (true, false, MTBreakPenalty.good, MTBreakPenalty.never) + + case .closingPunctuation: + // Closing punctuation: NEVER before, can break after + // Examples: ) ] } " ' » 」』 + return (false, true, MTBreakPenalty.never, MTBreakPenalty.good) + + case .sentenceEnding: + // Sentence-ending punctuation: NEVER before, good break after + // Examples: . , ; : ! ? 。、 + return (false, true, MTBreakPenalty.never, MTBreakPenalty.best) + + case .cjkSmallKana: + // CJK small kana and iteration marks: NEVER before, can break after + // Examples: っゃゅょゎ ゝゞ ー + return (false, true, MTBreakPenalty.never, MTBreakPenalty.good) + + case .neutral: + // Other punctuation: use default rules + return (true, true, MTBreakPenalty.good, MTBreakPenalty.good) + } + } + + // MARK: - Operator Tokenization + + private func tokenizeOperator(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let op = atom.nucleus + guard !op.isEmpty else { return nil } + + // Calculate width with operator spacing + let width = widthCalculator.measureOperator(op, type: atom.type) + + let ascent = font.fontSize * 0.5 + let descent = font.fontSize * 0.2 + let height = ascent + descent + + return MTBreakableElement( + content: .operator(op, type: atom.type), + width: width, + height: height, + ascent: ascent, + descent: descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.best, // Operators are best break points + penaltyAfter: MTBreakPenalty.best, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + // MARK: - Delimiter Tokenization + + private func tokenizeOpenDelimiter(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let delimiter = atom.nucleus + let width = widthCalculator.measureText(delimiter) + let ascent = font.fontSize * 0.6 + let descent = font.fontSize * 0.2 + + return MTBreakableElement( + content: .text(delimiter), + width: width, + height: ascent + descent, + ascent: ascent, + descent: descent, + isBreakBefore: true, + isBreakAfter: false, // NEVER break after open delimiter + penaltyBefore: MTBreakPenalty.acceptable, + penaltyAfter: MTBreakPenalty.bad, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + private func tokenizeCloseDelimiter(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let delimiter = atom.nucleus + let width = widthCalculator.measureText(delimiter) + let ascent = font.fontSize * 0.6 + let descent = font.fontSize * 0.2 + + return MTBreakableElement( + content: .text(delimiter), + width: width, + height: ascent + descent, + ascent: ascent, + descent: descent, + isBreakBefore: false, // NEVER break before close delimiter + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.bad, + penaltyAfter: MTBreakPenalty.acceptable, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + // MARK: - Punctuation Tokenization + + private func tokenizePunctuation(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let punct = atom.nucleus + let width = widthCalculator.measureText(punct) + let ascent = font.fontSize * 0.5 + let descent = font.fontSize * 0.2 + + // Apply proper punctuation breaking rules based on character classification + // Default rules for multi-character punctuation or empty + var isBreakBefore = false + var isBreakAfter = true + var penaltyBefore = MTBreakPenalty.bad + var penaltyAfter = MTBreakPenalty.good + + // For single-character punctuation, use classification rules + if punct.count == 1, let char = punct.first { + (isBreakBefore, isBreakAfter, penaltyBefore, penaltyAfter) = punctuationBreakRules(char) + } + + return MTBreakableElement( + content: .text(punct), + width: width, + height: ascent + descent, + ascent: ascent, + descent: descent, + isBreakBefore: isBreakBefore, + isBreakAfter: isBreakAfter, + penaltyBefore: penaltyBefore, + penaltyAfter: penaltyAfter, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + // MARK: - Script Tokenization + + private func tokenizeAtomWithScripts(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int, allAtoms: [MTMathAtom]) -> [MTBreakableElement] { + var elements: [MTBreakableElement] = [] + let groupId = UUID() // All elements in this group must stay together + + // First, create the base element + if let baseElement = tokenizeAtom(atom, prevAtom: prevAtom, atomIndex: atomIndex, allAtoms: allAtoms) { + var modifiedBase = baseElement + // Modify to be part of group + modifiedBase = MTBreakableElement( + content: baseElement.content, + width: baseElement.width, + height: baseElement.height, + ascent: baseElement.ascent, + descent: baseElement.descent, + isBreakBefore: baseElement.isBreakBefore, + isBreakAfter: false, // Cannot break after base - must include scripts + penaltyBefore: baseElement.penaltyBefore, + penaltyAfter: MTBreakPenalty.never, + groupId: groupId, + parentId: nil, + originalAtom: baseElement.originalAtom, + indexRange: baseElement.indexRange, + color: baseElement.color, + backgroundColor: baseElement.backgroundColor, + indivisible: baseElement.indivisible + ) + elements.append(modifiedBase) + } + + // Add superscript first if present (matches legacy typesetter order) + if let superScript = atom.superScript { + if let scriptDisplay = displayRenderer.renderScript(superScript, isSuper: true) { + let scriptElement = MTBreakableElement( + content: .script(scriptDisplay, isSuper: true), + width: scriptDisplay.width, + height: scriptDisplay.ascent + scriptDisplay.descent, + ascent: scriptDisplay.ascent, + descent: scriptDisplay.descent, + isBreakBefore: false, // Must stay with base + isBreakAfter: atom.subScript == nil, // Can break after if last script + penaltyBefore: MTBreakPenalty.never, + penaltyAfter: atom.subScript == nil ? MTBreakPenalty.good : MTBreakPenalty.never, + groupId: groupId, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + elements.append(scriptElement) + } + } + + // Add subscript after superscript (matches legacy typesetter order) + if let subScript = atom.subScript { + if let scriptDisplay = displayRenderer.renderScript(subScript, isSuper: false) { + let scriptElement = MTBreakableElement( + content: .script(scriptDisplay, isSuper: false), + width: scriptDisplay.width, + height: scriptDisplay.ascent + scriptDisplay.descent, + ascent: scriptDisplay.ascent, + descent: scriptDisplay.descent, + isBreakBefore: false, // Must stay with base + isBreakAfter: true, // Can break after subscript (it's always last) + penaltyBefore: MTBreakPenalty.never, + penaltyAfter: MTBreakPenalty.good, + groupId: groupId, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + elements.append(scriptElement) + } + } + + return elements + } + + // MARK: - Complex Structure Tokenization + + private func tokenizeFraction(_ fraction: MTFraction, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + // Create a temporary typesetter to render the fraction + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeFraction(fraction) else { return nil } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.moderate, + penaltyAfter: MTBreakPenalty.moderate, + groupId: nil, + parentId: nil, + originalAtom: fraction, + indexRange: fraction.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true // Fractions are atomic + ) + } + + private func tokenizeRadical(_ radical: MTRadical, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeRadical(radical.radicand, range: radical.indexRange) else { return nil } + + // Add degree if present + if radical.degree != nil { + // Use .script style (71% size) instead of .scriptOfScript (50% size) + // This matches TeX standard for radical degrees + let degree = MTTypesetter.createLineForMathList(radical.degree, font: font, style: .script) + display.setDegree(degree, fontMetrics: font.mathTable) + } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.good, + penaltyAfter: MTBreakPenalty.good, + groupId: nil, + parentId: nil, + originalAtom: radical, + indexRange: radical.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true // Radicals are atomic + ) + } + + private func tokenizeLargeOperator(_ op: MTLargeOperator, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + // CRITICAL DISTINCTION: + // - If op.limits=true (e.g., \sum, \prod, \lim in text mode): Scripts go ABOVE/BELOW + // → makeLargeOp() creates MTLargeOpLimitsDisplay, which is self-contained + // → We should NOT clear scripts, let makeLargeOp() handle everything + // + // - If op.limits=false (e.g., \int in text mode): Scripts go TO THE SIDE + // → makeLargeOp() would create scripts via makeScripts(), causing duplication + // → We MUST clear scripts and let tokenizeAtomWithScripts() handle them separately + + let limits = op.limits && (style == .display || style == .text) + + let originalSuperScript = op.superScript + let originalSubScript = op.subScript + + // Only clear scripts for side-script operators (limits=false) + if !limits && (originalSuperScript != nil || originalSubScript != nil) { + op.superScript = nil + op.subScript = nil + } + + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let operatorDisplay = typesetter.makeLargeOp(op) else { + // Restore scripts before returning + op.superScript = originalSuperScript + op.subScript = originalSubScript + return nil + } + + // CRITICAL: Handle scripts based on positioning mode + if !limits { + // Side-script operators (limits=false): Restore scripts for tokenizeAtomWithScripts to handle + op.superScript = originalSuperScript + op.subScript = originalSubScript + } else { + // Limit operators (limits=true): Scripts are already rendered in MTLargeOpLimitsDisplay + // MUST clear them from atom to prevent tokenizeAtomWithScripts from rendering them again + op.superScript = nil + op.subScript = nil + } + + // CRITICAL: Handle italic correction (delta) for side-script operators + // When scripts are present and limits is false, the operator width is reduced by delta + // (see MTTypesetter.makeLargeOp line 1046-1050) + // Since we cleared scripts for side-script operators, makeLargeOp() didn't apply this reduction + var finalWidth = operatorDisplay.width + + if !limits && (originalSubScript != nil) { + // Get the italic correction for the operator glyph + if let glyphDisplay = operatorDisplay as? MTGlyphDisplay, + let mathTable = font.mathTable { + let delta = mathTable.getItalicCorrection(glyphDisplay.glyph) + finalWidth -= delta + } + } + + let finalDisplay = operatorDisplay + + return MTBreakableElement( + content: .display(finalDisplay), + width: finalWidth, + height: finalDisplay.ascent + finalDisplay.descent, + ascent: finalDisplay.ascent, + descent: finalDisplay.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.good, + penaltyAfter: MTBreakPenalty.good, + groupId: nil, + parentId: nil, + originalAtom: op, + indexRange: op.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } + + private func tokenizeAccent(_ accent: MTAccent, prevAtom: MTMathAtom?, atomIndex: Int, allAtoms: [MTMathAtom]) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeAccent(accent) else { return nil } + + // Determine break rules - accents in text mode should respect word boundaries + var isBreakBefore = true + var isBreakAfter = true + var penaltyBefore = MTBreakPenalty.good + var penaltyAfter = MTBreakPenalty.good + + // Check if this accent is in a text context (the accented character is roman text) + // An accent's innerList contains the base character(s) + let isTextAccent = accent.innerList?.atoms.first?.fontStyle == .roman + + if isTextAccent { + // Check previous atom - if it's a letter in text mode, don't break before + if let prevAtom = prevAtom { + let prevIsTextLetter = isTextLetterAtom(prevAtom) + if prevIsTextLetter { + isBreakBefore = false + penaltyBefore = MTBreakPenalty.never + } + } + + // Check next atom - if it's a letter in text mode, don't break after + if atomIndex + 1 < allAtoms.count { + let nextAtom = allAtoms[atomIndex + 1] + let nextIsTextLetter = isTextLetterAtom(nextAtom) + if nextIsTextLetter { + isBreakAfter = false + penaltyAfter = MTBreakPenalty.never + } + } + } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: isBreakBefore, + isBreakAfter: isBreakAfter, + penaltyBefore: penaltyBefore, + penaltyAfter: penaltyAfter, + groupId: nil, + parentId: nil, + originalAtom: accent, + indexRange: accent.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } + + /// Helper to check if an atom is a letter in text mode (roman style) + private func isTextLetterAtom(_ atom: MTMathAtom) -> Bool { + // For accent atoms, check their inner content FIRST + // (accent atom's nucleus contains the accent mark, not the letter) + if let accent = atom as? MTAccent { + if let firstInner = accent.innerList?.atoms.first { + return isTextLetterAtom(firstInner) + } + return false + } + // For regular text atoms with roman style + if atom.fontStyle == .roman { + // Check if the nucleus contains only letters + let nucleus = atom.nucleus + if !nucleus.isEmpty { + return nucleus.allSatisfy { $0.isLetter } + } + } + return false + } + + private func tokenizeUnderline(_ underline: MTUnderLine, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeUnderline(underline) else { return nil } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.good, + penaltyAfter: MTBreakPenalty.good, + groupId: nil, + parentId: nil, + originalAtom: underline, + indexRange: underline.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } + + private func tokenizeOverline(_ overline: MTOverLine, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeOverline(overline) else { return nil } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.good, + penaltyAfter: MTBreakPenalty.good, + groupId: nil, + parentId: nil, + originalAtom: overline, + indexRange: overline.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } + + private func tokenizeTable(_ table: MTMathTable, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false, maxWidth: maxWidth) + guard let display = typesetter.makeTable(table) else { return nil } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.moderate, + penaltyAfter: MTBreakPenalty.moderate, + groupId: nil, + parentId: nil, + originalAtom: table, + indexRange: table.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } + + private func tokenizeInner(_ inner: MTInner, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + let typesetter = MTTypesetter(withFont: font, style: style, cramped: cramped, spaced: false) + guard let display = typesetter.makeLeftRight(inner) else { return nil } + + return MTBreakableElement( + content: .display(display), + width: display.width, + height: display.ascent + display.descent, + ascent: display.ascent, + descent: display.descent, + isBreakBefore: true, + isBreakAfter: true, + penaltyBefore: MTBreakPenalty.good, + penaltyAfter: MTBreakPenalty.good, + groupId: nil, + parentId: nil, + originalAtom: inner, + indexRange: inner.indexRange, + color: nil, + backgroundColor: nil, + indivisible: false + ) + } + + private func tokenizeSpace(_ atom: MTMathAtom, prevAtom: MTMathAtom?, atomIndex: Int) -> MTBreakableElement? { + // Space atoms typically don't participate in breaking + // They are rendered as-is + let width = widthCalculator.measureSpace(atom.type) + + return MTBreakableElement( + content: .space(width), + width: width, + height: 0, + ascent: 0, + descent: 0, + isBreakBefore: false, + isBreakAfter: false, + penaltyBefore: MTBreakPenalty.never, + penaltyAfter: MTBreakPenalty.never, + groupId: nil, + parentId: nil, + originalAtom: atom, + indexRange: atom.indexRange, + color: nil, + backgroundColor: nil, + indivisible: true + ) + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTBreakableElement.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTBreakableElement.swift new file mode 100644 index 0000000000..163a86871f --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTBreakableElement.swift @@ -0,0 +1,115 @@ +// +// MTBreakableElement.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics + +// MARK: - MTElementContent + +/// Represents the content type of a breakable element +enum MTElementContent { + /// Simple text content + case text(String) + /// Pre-rendered display (fraction, radical, etc.) + case display(MTDisplay) + /// Math operator with spacing + case `operator`(String, type: MTMathAtomType) + /// Explicit spacing + case space(CGFloat) + /// Superscript or subscript display + case script(MTDisplay, isSuper: Bool) +} + +// MARK: - MTBreakableElement + +/// Represents a breakable element with pre-calculated width and break rules +struct MTBreakableElement { + // MARK: Display properties + + /// The content of this element + let content: MTElementContent + + /// Pre-calculated width (cached) + let width: CGFloat + + /// Height of the element + let height: CGFloat + + /// Distance from baseline to top + let ascent: CGFloat + + /// Distance from baseline to bottom + let descent: CGFloat + + // MARK: Breaking rules + + /// Can break BEFORE this element? + let isBreakBefore: Bool + + /// Can break AFTER this element? + let isBreakAfter: Bool + + /// Penalty for breaking before (0=good, 100=bad, 150=never) + let penaltyBefore: Int + + /// Penalty for breaking after (0=good, 100=bad, 150=never) + let penaltyAfter: Int + + // MARK: Relationship tracking + + /// Elements with same groupId must stay together + let groupId: UUID? + + /// Parent element ID (for scripts) + let parentId: UUID? + + // MARK: Source tracking + + /// Original atom this element was created from + let originalAtom: MTMathAtom + + /// Index range in the original math list + let indexRange: NSRange + + // MARK: Optional attributes + + /// Text color for this element + let color: MTColor? + + /// Background color for this element + let backgroundColor: MTColor? + + // MARK: Atomicity flag + + /// If true, NEVER break this element internally + let indivisible: Bool +} + +// MARK: - Penalty Constants + +/// Penalty values for line breaking decisions +enum MTBreakPenalty { + /// Best break points (operators, relations) + static let best = 0 + + /// Good break points (ordinary atoms, after scripts) + static let good = 10 + + /// Moderate penalty (before fractions, radicals) + static let moderate = 15 + + /// Acceptable break points + static let acceptable = 50 + + /// Bad break points (avoid if possible) + static let bad = 100 + + /// Never break here (grouped elements) + static let never = 150 +} diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayGenerator.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayGenerator.swift new file mode 100644 index 0000000000..b9faf4f267 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayGenerator.swift @@ -0,0 +1,389 @@ +// +// MTDisplayGenerator.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics +import CoreText + +/// Generates MTDisplay objects from fitted lines of breakable elements +class MTDisplayGenerator { + + // MARK: - Properties + + let font: MTFont + let style: MTLineStyle + let widthCalculator: MTElementWidthCalculator + + // MARK: - Initialization + + init(font: MTFont, style: MTLineStyle) { + self.font = font + self.style = style + self.widthCalculator = MTElementWidthCalculator(font: font, style: style) + } + + // MARK: - Display Generation + + /// Generate displays from fitted lines + func generateDisplays(from lines: [[MTBreakableElement]], startPosition: CGPoint) -> [MTDisplay] { + var allDisplays: [MTDisplay] = [] + var currentY = startPosition.y + + // Minimum spacing between lines (20% of font size for breathing room) + let minimumLineSpacing = font.fontSize * 0.2 + + for (index, line) in lines.enumerated() { + let (lineDisplays, currentLineMetrics) = generateLine(line, at: CGPoint(x: startPosition.x, y: currentY)) + allDisplays.append(contentsOf: lineDisplays) + + // Calculate spacing for next line based on actual content heights + if index < lines.count - 1 { + let nextLine = lines[index + 1] + let nextLineAscent = nextLine.map { $0.ascent }.max() ?? 0 + + // Space needed = current line's descent + minimum spacing + next line's ascent + let spaceNeeded = currentLineMetrics.descent + minimumLineSpacing + nextLineAscent + + // Ensure minimum spacing of 1.2x font size for readability + let minSpacing = font.fontSize * 1.2 + currentY -= max(spaceNeeded, minSpacing) + } + } + + return allDisplays + } + + /// Line metrics for spacing calculation + struct LineMetrics { + let ascent: CGFloat + let descent: CGFloat + var height: CGFloat { ascent + descent } + } + + /// Generate displays for a single line + private func generateLine(_ elements: [MTBreakableElement], at position: CGPoint) -> ([MTDisplay], LineMetrics) { + var displays: [MTDisplay] = [] + var xOffset: CGFloat = 0 + + // Calculate line metrics + let lineAscent = elements.map { $0.ascent }.max() ?? 0 + let lineDescent = elements.map { $0.descent }.max() ?? 0 + + // Baseline y position + let baseline = position.y + + var i = 0 + while i < elements.count { + let element = elements[i] + + // Check if this is part of a group (base + scripts) + if let groupId = element.groupId { + // Collect all elements in this group + var groupElements: [MTBreakableElement] = [] + var j = i + while j < elements.count && elements[j].groupId == groupId { + groupElements.append(elements[j]) + j += 1 + } + + // Render the group + let groupAdvance = renderGroup(groupElements, at: CGPoint(x: position.x + xOffset, y: baseline), displays: &displays) + xOffset += groupAdvance + i = j + } else { + // Regular element (not part of a group) + + // CRITICAL: For operators, spacing should be split evenly before and after + // The element.width includes both spacing, but we need to position the operator + // with half spacing before it + var spacingBefore: CGFloat = 0 + if case .operator(let op, _) = element.content { + // Get the actual text width vs element width to calculate spacing + let textWidth = widthCalculator.measureText(op) + let totalSpacing = element.width - textWidth + spacingBefore = totalSpacing / 2 + } + + let elementPosition = CGPoint(x: position.x + xOffset + spacingBefore, y: baseline) + + switch element.content { + case .text(let text): + let display = createTextDisplay(text, at: elementPosition, element: element) + displays.append(display) + + case .display(let preRenderedDisplay): + // Use pre-rendered display (fraction, radical, etc.) + let mutableDisplay = preRenderedDisplay + mutableDisplay.position = elementPosition + displays.append(mutableDisplay) + + case .operator(let op, _): + let display = createTextDisplay(op, at: elementPosition, element: element) + displays.append(display) + + case .script: + // Standalone script (shouldn't happen, but handle gracefully) + break + + case .space: + // No display for space, just advance position + break + } + + xOffset += element.width + i += 1 + } + } + + return (displays, LineMetrics(ascent: lineAscent, descent: lineDescent)) + } + + /// Render a group of elements (base + scripts) and return the horizontal advance + private func renderGroup(_ groupElements: [MTBreakableElement], at position: CGPoint, displays: inout [MTDisplay]) -> CGFloat { + var baseWidth: CGFloat = 0 + var superscriptWidth: CGFloat = 0 + var subscriptWidth: CGFloat = 0 + var baseXOffset: CGFloat = 0 + + // Check if this group has any scripts + let hasScripts = groupElements.contains { element in + if case .script = element.content { + return true + } + return false + } + + // Track the start index of base displays for dimension adjustment + let baseDisplayStartIndex = displays.count + + // First pass: render base elements and collect script widths + for element in groupElements { + switch element.content { + case .script: + // Skip scripts in first pass + break + default: + // Render base element + let basePosition = CGPoint(x: position.x + baseXOffset, y: position.y) + + switch element.content { + case .text(let text): + let display = createTextDisplay(text, at: basePosition, element: element, hasScript: hasScripts) + displays.append(display) + case .display(let preRenderedDisplay): + let mutableDisplay = preRenderedDisplay + mutableDisplay.position = basePosition + displays.append(mutableDisplay) + case .operator(let op, _): + let display = createTextDisplay(op, at: basePosition, element: element, hasScript: hasScripts) + displays.append(display) + default: + break + } + + baseWidth += element.width + baseXOffset += element.width + } + } + + // Second pass: collect script information for joint positioning + var superscriptDisplay: MTDisplay? = nil + var subscriptDisplay: MTDisplay? = nil + var hasBothScripts = false + + for element in groupElements { + if case .script(let scriptDisplay, let isSuper) = element.content { + if isSuper { + superscriptDisplay = scriptDisplay + } else { + subscriptDisplay = scriptDisplay + } + } + } + + hasBothScripts = superscriptDisplay != nil && subscriptDisplay != nil + + // Third pass: render scripts with proper positioning + var superScriptShiftUp: CGFloat = 0 + var subscriptShiftDown: CGFloat = 0 + + // Check if base is a glyph (not CTLineDisplay) for special positioning + // For glyphs (like large operators), position scripts relative to glyph edges + var isGlyphBase = false + for disp in displays[baseDisplayStartIndex.. 0 { + // Superscript is lower than the max allowed by the font with a subscript + superScriptShiftUp += superscriptBottomDelta + subscriptShiftDown -= superscriptBottomDelta + } + } + } + + // Calculate italic correction (delta) for superscript positioning + // Superscripts are positioned at baseWidth + delta, subscripts at baseWidth + var delta: CGFloat = 0 + if superscriptDisplay != nil { + // Get italic correction from the base display if it's a glyph + for disp in displays[baseDisplayStartIndex.. baseDisplayStartIndex { + // Calculate the full extent of the group including scripts + var maxAscent: CGFloat = 0 + var maxDescent: CGFloat = 0 + + for i in baseDisplayStartIndex.. MTDisplay { + let attrString = NSMutableAttributedString(string: text) + attrString.addAttribute( + NSAttributedString.Key(kCTFontAttributeName as String), + value: font.ctFont as Any, + range: NSMakeRange(0, attrString.length) + ) + + // If the atom was fused (multiple ordinary chars combined), use fusedAtoms + // Otherwise, use the original atom + let atoms: [MTMathAtom] + if !element.originalAtom.fusedAtoms.isEmpty { + atoms = element.originalAtom.fusedAtoms + } else { + atoms = [element.originalAtom] + } + + let display = MTCTLineDisplay( + withString: attrString, + position: position, + range: element.indexRange, + font: font, + atoms: atoms + ) + + // Mark if this base element has associated scripts + display.hasScript = hasScript + + return display + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayPreRenderer.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayPreRenderer.swift new file mode 100644 index 0000000000..d6af072a60 --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTDisplayPreRenderer.swift @@ -0,0 +1,88 @@ +// +// MTDisplayPreRenderer.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics + +/// Pre-renders complex atoms (fractions, radicals, etc.) as MTDisplay objects during tokenization +class MTDisplayPreRenderer { + + // MARK: - Properties + + let font: MTFont + let style: MTLineStyle + let cramped: Bool + + // MARK: - Initialization + + init(font: MTFont, style: MTLineStyle, cramped: Bool) { + self.font = font + self.style = style + self.cramped = cramped + } + + // MARK: - Script Rendering + + /// Render a script (superscript or subscript) as a display + func renderScript(_ mathList: MTMathList, isSuper: Bool) -> MTDisplay? { + let scriptStyle = getScriptStyle() + let scriptCramped = isSuper ? cramped : true // Subscripts are always cramped + + // Scale the font for the script style + let scriptFontSize = MTTypesetter.getStyleSize(scriptStyle, font: font) + let scriptFont = font.copy(withSize: scriptFontSize) + + guard let display = MTTypesetter.createLineForMathList( + mathList, + font: scriptFont, + style: scriptStyle, + cramped: scriptCramped, + spaced: false + ) else { + return nil + } + + // If the result is a MTMathListDisplay with a single subdisplay, unwrap it + // This matches the behavior of the legacy typesetter + if display.subDisplays.count == 1 { + return display.subDisplays[0] + } + + return display + } + + /// Get the appropriate style for scripts + private func getScriptStyle() -> MTLineStyle { + switch style { + case .display, .text: + return .script + case .script, .scriptOfScript: + return .scriptOfScript + } + } + + // MARK: - Helper Methods + + /// Pre-render a simple math list without width constraints + /// Used for rendering content inside fractions, radicals, etc. + func renderMathList(_ mathList: MTMathList?, style renderStyle: MTLineStyle? = nil, cramped renderCramped: Bool? = nil) -> MTDisplay? { + guard let mathList = mathList else { return nil } + + let actualStyle = renderStyle ?? style + let actualCramped = renderCramped ?? cramped + + return MTTypesetter.createLineForMathList( + mathList, + font: font, + style: actualStyle, + cramped: actualCramped, + spaced: false + ) + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTElementWidthCalculator.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTElementWidthCalculator.swift new file mode 100644 index 0000000000..258eb768be --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTElementWidthCalculator.swift @@ -0,0 +1,165 @@ +// +// MTElementWidthCalculator.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreText +import CoreGraphics + +/// Calculates widths for breakable elements with appropriate spacing +class MTElementWidthCalculator { + + // MARK: - Properties + + let font: MTFont + let style: MTLineStyle + + // MARK: - Initialization + + init(font: MTFont, style: MTLineStyle) { + self.font = font + self.style = style + } + + // MARK: - Text Width Measurement + + /// Measure width of simple text + func measureText(_ text: String) -> CGFloat { + guard !text.isEmpty else { return 0 } + + let attrString = NSAttributedString(string: text, attributes: [ + kCTFontAttributeName as NSAttributedString.Key: font.ctFont as Any + ]) + let line = CTLineCreateWithAttributedString(attrString as CFAttributedString) + return CGFloat(CTLineGetTypographicBounds(line, nil, nil, nil)) + } + + // MARK: - Operator Width Measurement + + /// Measure width of operator with appropriate spacing + func measureOperator(_ op: String, type: MTMathAtomType) -> CGFloat { + let baseWidth = measureText(op) + let spacing = getOperatorSpacing(type) + return baseWidth + spacing + } + + /// Get spacing for an operator (both sides) + private func getOperatorSpacing(_ type: MTMathAtomType) -> CGFloat { + guard let mathTable = font.mathTable else { return 0 } + let muUnit = mathTable.muUnit + + switch type { + case .binaryOperator: + // Binary operators: 4mu on each side = 8mu total + return 2 * muUnit * 4 + + case .relation: + // Relations: 5mu on each side = 10mu total + return 2 * muUnit * 5 + + case .largeOperator: + // Large operators in inline mode: 1mu on each side + if style == .display || style == .text { + return 0 // In display mode, handled by MTLargeOpLimitsDisplay + } + return 2 * muUnit * 1 + + default: + return 0 + } + } + + // MARK: - Display Width Measurement + + /// Measure width of a pre-rendered display + func measureDisplay(_ display: MTDisplay) -> CGFloat { + return display.width + } + + // MARK: - Space Width Measurement + + /// Get width of explicit spacing command + func measureSpace(_ spaceType: MTMathAtomType) -> CGFloat { + guard let mathTable = font.mathTable else { return 0 } + let muUnit = mathTable.muUnit + + // Note: These are the explicit spacing commands in LaTeX + // \, = thin space (3mu) + // \: = medium space (4mu) + // \; = thick space (5mu) + // \quad = 1em + // \qquad = 2em + + switch spaceType { + case .space: + // Default space - context dependent + // For now, use thin space + return muUnit * 3 + default: + return 0 + } + } + + /// Measure explicit space value + func measureExplicitSpace(_ width: CGFloat) -> CGFloat { + return width + } + + // MARK: - Inter-element Spacing + + /// Get inter-element spacing between two atom types + func getInterElementSpacing(left: MTMathAtomType, right: MTMathAtomType) -> CGFloat { + let leftIndex = getInterElementSpaceArrayIndexForType(left, row: true) + let rightIndex = getInterElementSpaceArrayIndexForType(right, row: false) + let spaceArray = getInterElementSpaces()[Int(leftIndex)] + let spaceType = spaceArray[Int(rightIndex)] + + guard spaceType != .invalid else { + // Should not happen in well-formed math + return 0 + } + + let spaceMultiplier = getSpacingInMu(spaceType) + if spaceMultiplier > 0, let mathTable = font.mathTable { + return CGFloat(spaceMultiplier) * mathTable.muUnit + } + return 0 + } + + /// Get spacing multiplier in mu units + private func getSpacingInMu(_ spaceType: InterElementSpaceType) -> Int { + switch style { + case .display, .text: + switch spaceType { + case .none, .invalid: + return 0 + case .thin: + return 3 + case .nsThin, .nsMedium, .nsThick: + // ns = non-script, same as regular in display/text mode + switch spaceType { + case .nsThin: return 3 + case .nsMedium: return 4 + case .nsThick: return 5 + default: return 0 + } + } + + case .script, .scriptOfScript: + switch spaceType { + case .none, .invalid: + return 0 + case .thin: + return 3 + case .nsThin, .nsMedium, .nsThick: + // In script mode, ns types don't add space + return 0 + } + } + } +} diff --git a/third-party/SwiftMath/Sources/MathRender/Tokenization/MTLineFitter.swift b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTLineFitter.swift new file mode 100644 index 0000000000..5afdc36a2a --- /dev/null +++ b/third-party/SwiftMath/Sources/MathRender/Tokenization/MTLineFitter.swift @@ -0,0 +1,266 @@ +// +// MTLineFitter.swift +// SwiftMath +// +// Created by Claude Code on 2025-12-16. +// This software may be modified and distributed under the terms of the +// MIT license. See the LICENSE file for details. +// + +import Foundation +import CoreGraphics + +/// Fits breakable elements into lines respecting width constraints and break rules +class MTLineFitter { + + // MARK: - Properties + + let maxWidth: CGFloat + let margin: CGFloat + + // MARK: - Initialization + + init(maxWidth: CGFloat, margin: CGFloat = 0) { + self.maxWidth = maxWidth + self.margin = margin + } + + // MARK: - Line Fitting + + /// Fit elements into lines using greedy algorithm with backtracking + func fitLines(_ elements: [MTBreakableElement]) -> [[MTBreakableElement]] { + guard !elements.isEmpty else { return [] } + guard maxWidth > 0 else { return [elements] } // No width constraint + + let debugPunctuation = !"".isEmpty // Enable to debug line breaking issues + + if debugPunctuation { + print("\n=== MTLineFitter: fitting \(elements.count) elements, maxWidth=\(maxWidth) ===") + for (idx, elem) in elements.enumerated() { + if case .text(let t) = elem.content { + print("[\(idx)] '\(t)' breakBefore=\(elem.isBreakBefore) breakAfter=\(elem.isBreakAfter) width=\(elem.width)") + } + } + } + + var lines: [[MTBreakableElement]] = [[]] + var currentWidth: CGFloat = 0 + var i = 0 + + while i < elements.count { + let element = elements[i] + + if debugPunctuation, case .text(let t) = element.content { + print("\n Processing element[\(i)]: '\(t)' breakBefore=\(element.isBreakBefore)") + } + + // Handle grouped elements (base + scripts) + if let groupId = element.groupId { + let (groupElements, nextIndex) = collectGroup(elements, startIndex: i, groupId: groupId) + + // Calculate group width correctly for scripts + // Scripts overlap vertically, so width = max(script widths), not sum + let groupWidth = calculateGroupWidth(groupElements) + + // Check if group fits on current line + if !lines.last!.isEmpty && currentWidth + groupWidth > maxWidth - margin { + // Group doesn't fit - check if first element of group can start a new line + if groupElements.first?.isBreakBefore ?? true { + // Can start new line + lines.append([]) + currentWidth = 0 + } else { + // Cannot start new line - keep with previous line (allow overflow) + // This handles cases like punctuation after base+script groups + } + } + + // Add entire group to current line + lines[lines.count - 1].append(contentsOf: groupElements) + currentWidth += groupWidth + i = nextIndex + continue + } + + // Check if element fits on current line + if !lines.last!.isEmpty && currentWidth + element.width > maxWidth - margin { + if debugPunctuation, case .text = element.content { + print(" Doesn't fit (width=\(currentWidth) + \(element.width) > \(maxWidth)), current line has \(lines.last!.count) elements") + } + // Element doesn't fit - find best break point in current line + if let breakIndex = findBestBreak(in: lines[lines.count - 1]) { + if debugPunctuation { + print(" Found break at index \(breakIndex) out of \(lines.last!.count) elements") + if breakIndex < lines.last!.count { + if case .text(let t) = lines.last![breakIndex].content { + print(" Break at element: '\(t)'") + } + } + } + // Found a break point - move elements from breakIndex onward to next line + let moveElements = Array(lines[lines.count - 1][breakIndex...]) + let oldLine = Array(lines[lines.count - 1][.. Adding '\(t)' to new line (part of unbreakable sequence)") + } + lines[lines.count - 1].append(element) + currentWidth += element.width + i += 1 + continue + } else { + if debugPunctuation, case .text(let t) = element.content { + print(" -> Adding '\(t)' to new line (can start line)") + } + } + // Current element can start a line, will be added to new line below + } else { + // Should not happen if findBestBreak is correct, but handle gracefully + // Keep elements on current line (allow overflow) + lines[lines.count - 1].append(contentsOf: moveElements) + currentWidth += moveElements.reduce(0) { $0 + $1.width } + } + } else { + // No good break point found + // Check if current element can start a new line + if element.isBreakBefore { + // Element can start new line + lines.append([]) + currentWidth = 0 + } else { + // Element cannot start a new line (e.g., closing punctuation) + // Keep it on current line even if it causes overflow + // This respects punctuation rules over width constraints + lines[lines.count - 1].append(element) + currentWidth += element.width + i += 1 + continue + } + } + } + + // Add element to current line (may overflow if indivisible and too wide) + lines[lines.count - 1].append(element) + currentWidth += element.width + i += 1 + } + + let finalLines = lines.filter { !$0.isEmpty } + + if debugPunctuation { + print("\n=== Final lines: ===") + for (lineIdx, line) in finalLines.enumerated() { + print("Line \(lineIdx):") + for elem in line { + if case .text(let t) = elem.content { + print(" '\(t)'", terminator: "") + } + } + print() + } + } + + return finalLines + } + + // MARK: - Helper Methods + + /// Calculate the correct width for a group of elements (e.g., base + scripts) + /// Scripts overlap vertically, so the group width is not the sum of all widths + private func calculateGroupWidth(_ groupElements: [MTBreakableElement]) -> CGFloat { + // For grouped elements (base + scripts), just sum all widths + // The display generator will handle the actual positioning and overlap + // This is just for line fitting purposes + return groupElements.reduce(0) { $0 + $1.width } + } + + /// Collect all elements that share the same groupId + private func collectGroup(_ elements: [MTBreakableElement], startIndex: Int, groupId: UUID) -> ([MTBreakableElement], Int) { + var groupElements: [MTBreakableElement] = [] + var index = startIndex + + while index < elements.count && elements[index].groupId == groupId { + groupElements.append(elements[index]) + index += 1 + } + + return (groupElements, index) + } + + /// Find the best break point in a line + /// Returns the index where the break should occur (elements from this index move to next line) + private func findBestBreak(in line: [MTBreakableElement]) -> Int? { + var bestIndex: Int? = nil + var lowestPenalty = Int.max + + let debugBreak = !"".isEmpty // Enable to debug break point selection + + // Scan from right to left to prefer breaking later in the line + // Note: Skip the last element (idx == line.count - 1) because breaking after it + // would move 0 elements to the next line, which is pointless + for (idx, element) in line.enumerated().reversed() { + // Skip the last element - we need to move at least 1 element to the next line + if idx >= line.count - 1 { + continue + } + + // Can we break after this element? + let canBreakAfter = element.isBreakAfter + let penaltyAfter = element.penaltyAfter + + // Check if next element (which would move to new line) allows breaking before it + let canBreakBeforeNext = line[idx + 1].isBreakBefore + let penaltyBeforeNext = line[idx + 1].penaltyBefore + + // We can break here only if BOTH: + // 1. Current element allows breaking after it + // 2. Next element allows breaking before it + if canBreakAfter && canBreakBeforeNext { + let totalPenalty = max(penaltyAfter, penaltyBeforeNext) + if totalPenalty < lowestPenalty { + if debugBreak && idx < line.count - 1 { + let currText = if case .text(let t) = element.content { t } else { "?" } + let nextText = if case .text(let t) = line[idx + 1].content { t } else { "?" } + print(" Considering break: '\(currText)' | '\(nextText)' at idx=\(idx), penalty=\(totalPenalty)") + } + bestIndex = idx + 1 + lowestPenalty = totalPenalty + } + } + } + + if debugBreak { + print(" Best break: index=\(bestIndex ?? -1), penalty=\(lowestPenalty)") + } + + // Only return if we found an acceptable break point + if let index = bestIndex, lowestPenalty <= MTBreakPenalty.bad { + return index + } + + return nil + } + + /// Check if a line width exceeds the maximum + private func exceedsMaxWidth(_ width: CGFloat) -> Bool { + return width > maxWidth - margin + } +} diff --git a/third-party/XMLCoder/BUILD b/third-party/XMLCoder/BUILD new file mode 100644 index 0000000000..13656e4a0c --- /dev/null +++ b/third-party/XMLCoder/BUILD @@ -0,0 +1,17 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "XMLCoder", + module_name = "XMLCoder", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-suppress-warnings", + ], + deps = [ + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Attribute.swift b/third-party/XMLCoder/Sources/Auxiliaries/Attribute.swift new file mode 100644 index 0000000000..a908bee045 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Attribute.swift @@ -0,0 +1,99 @@ +// +// XMLAttribute.swift +// XMLCoder +// +// Created by Benjamin Wetherfield on 6/3/20. +// + +protocol XMLAttributeProtocol {} + +/** Property wrapper specifying that a given property should be encoded and decoded as an XML attribute. + + For example, this type + ```swift + struct Book: Codable { + @Attribute var id: Int + } + ``` + + will encode value `Book(id: 42)` as ``. And vice versa, + it will decode the former into the latter. + */ +@propertyWrapper +public struct Attribute: XMLAttributeProtocol { + public var wrappedValue: Value + + public init(_ wrappedValue: Value) { + self.wrappedValue = wrappedValue + } +} + +extension Attribute: Codable where Value: Codable { + public func encode(to encoder: Encoder) throws { + try wrappedValue.encode(to: encoder) + } + + public init(from decoder: Decoder) throws { + try wrappedValue = .init(from: decoder) + } +} + +extension Attribute: Equatable where Value: Equatable {} +extension Attribute: Hashable where Value: Hashable {} +extension Attribute: Sendable where Value: Sendable {} + +extension Attribute: ExpressibleByIntegerLiteral where Value: ExpressibleByIntegerLiteral { + public typealias IntegerLiteralType = Value.IntegerLiteralType + + public init(integerLiteral value: Value.IntegerLiteralType) { + wrappedValue = Value(integerLiteral: value) + } +} + +extension Attribute: ExpressibleByUnicodeScalarLiteral where Value: ExpressibleByUnicodeScalarLiteral { + public init(unicodeScalarLiteral value: Value.UnicodeScalarLiteralType) { + wrappedValue = Value(unicodeScalarLiteral: value) + } + + public typealias UnicodeScalarLiteralType = Value.UnicodeScalarLiteralType +} + +extension Attribute: ExpressibleByExtendedGraphemeClusterLiteral where Value: ExpressibleByExtendedGraphemeClusterLiteral { + public typealias ExtendedGraphemeClusterLiteralType = Value.ExtendedGraphemeClusterLiteralType + + public init(extendedGraphemeClusterLiteral value: Value.ExtendedGraphemeClusterLiteralType) { + wrappedValue = Value(extendedGraphemeClusterLiteral: value) + } +} + +extension Attribute: ExpressibleByStringLiteral where Value: ExpressibleByStringLiteral { + public typealias StringLiteralType = Value.StringLiteralType + + public init(stringLiteral value: Value.StringLiteralType) { + wrappedValue = Value(stringLiteral: value) + } +} + +extension Attribute: ExpressibleByBooleanLiteral where Value: ExpressibleByBooleanLiteral { + public typealias BooleanLiteralType = Value.BooleanLiteralType + + public init(booleanLiteral value: Value.BooleanLiteralType) { + wrappedValue = Value(booleanLiteral: value) + } +} + +extension Attribute: ExpressibleByNilLiteral where Value: ExpressibleByNilLiteral { + public init(nilLiteral: ()) { + wrappedValue = Value(nilLiteral: ()) + } +} + +protocol XMLOptionalAttributeProtocol: XMLAttributeProtocol { + init() +} + +extension Attribute: XMLOptionalAttributeProtocol where Value: AnyOptional { + init() { + wrappedValue = Value() + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/BoolBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/BoolBox.swift new file mode 100644 index 0000000000..5997804eda --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/BoolBox.swift @@ -0,0 +1,53 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct BoolBox: Equatable { + typealias Unboxed = Bool + + let unboxed: Unboxed + + init(_ unboxed: Unboxed) { + self.unboxed = unboxed + } + + init?(xmlString: String) { + switch xmlString.lowercased() { + case "false", "0", "n", "no": self.init(false) + case "true", "1", "y", "yes": self.init(true) + case _: return nil + } + } +} + +extension BoolBox: Box { + var isNull: Bool { + return false + } + + /// # Lexical representation + /// Boolean has a lexical representation consisting of the following + /// legal literals {`true`, `false`, `1`, `0`}. + /// + /// # Canonical representation + /// The canonical representation for boolean is the set of literals {`true`, `false`}. + /// + /// --- + /// + /// [Schema definition](https://www.w3.org/TR/xmlschema-2/#boolean) + var xmlString: String? { + return (unboxed) ? "true" : "false" + } +} + +extension BoolBox: SimpleBox {} + +extension BoolBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/Box.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/Box.swift new file mode 100644 index 0000000000..71ecf82267 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/Box.swift @@ -0,0 +1,32 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +protocol Box { + var isNull: Bool { get } + var xmlString: String? { get } +} + +/// A box that only describes a single atomic value. +protocol SimpleBox: Box { + // A simple tagging protocol, for now. +} + +protocol TypeErasedSharedBoxProtocol { + func typeErasedUnbox() -> Box +} + +protocol SharedBoxProtocol: TypeErasedSharedBoxProtocol { + associatedtype B: Box + func unbox() -> B +} + +extension SharedBoxProtocol { + func typeErasedUnbox() -> Box { + return unbox() + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/ChoiceBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/ChoiceBox.swift new file mode 100644 index 0000000000..4d2f9b0289 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/ChoiceBox.swift @@ -0,0 +1,41 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by James Bean on 7/18/19. +// + +/// A `Box` which represents an element which is known to contain an XML choice element. +struct ChoiceBox { + var key: String = "" + var element: Box = NullBox() +} + +extension ChoiceBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return nil + } +} + +extension ChoiceBox: SimpleBox {} + +extension ChoiceBox { + init?(_ keyedBox: KeyedBox) { + guard + let firstKey = keyedBox.elements.keys.first, + let firstElement = keyedBox.elements[firstKey].first + else { + return nil + } + self.init(key: firstKey, element: firstElement) + } + + init(_ singleKeyedBox: SingleKeyedBox) { + self.init(key: singleKeyedBox.key, element: singleKeyedBox.element) + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/DataBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/DataBox.swift new file mode 100644 index 0000000000..dfa7f94217 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/DataBox.swift @@ -0,0 +1,57 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/19/18. +// + +import Foundation + +struct DataBox: Equatable { + enum Format: Equatable { + case base64 + } + + typealias Unboxed = Data + + let unboxed: Unboxed + let format: Format + + init(_ unboxed: Unboxed, format: Format) { + self.unboxed = unboxed + self.format = format + } + + init?(base64 string: String) { + guard let data = Data(base64Encoded: string) else { + return nil + } + self.init(data, format: .base64) + } + + func xmlString(format: Format) -> String { + switch format { + case .base64: + return unboxed.base64EncodedString() + } + } +} + +extension DataBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return xmlString(format: format) + } +} + +extension DataBox: SimpleBox {} + +extension DataBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/DateBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/DateBox.swift new file mode 100644 index 0000000000..c18957b569 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/DateBox.swift @@ -0,0 +1,99 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/18/18. +// + +import Foundation + +struct DateBox: Equatable, Sendable { + enum Format: Equatable { + case secondsSince1970 + case millisecondsSince1970 + case iso8601 + case formatter(DateFormatter) + } + + typealias Unboxed = Date + + let unboxed: Unboxed + let format: Format + + init(_ unboxed: Unboxed, format: Format) { + self.unboxed = unboxed + self.format = format + } + + init?(secondsSince1970 string: String) { + guard let seconds = TimeInterval(string) else { + return nil + } + let unboxed = Date(timeIntervalSince1970: seconds) + self.init(unboxed, format: .secondsSince1970) + } + + init?(millisecondsSince1970 string: String) { + guard let milliseconds = TimeInterval(string) else { + return nil + } + let unboxed = Date(timeIntervalSince1970: milliseconds / 1000.0) + self.init(unboxed, format: .millisecondsSince1970) + } + + init?(iso8601 string: String) { + if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { + guard let unboxed = ISO8601DateFormatter.xmlCoderFormatter().date(from: string) else { + return nil + } + self.init(unboxed, format: .iso8601) + } else { + fatalError("ISO8601DateFormatter is unavailable on this platform.") + } + } + + init?(xmlString: String, formatter: DateFormatter) { + guard let date = formatter.date(from: xmlString) else { + return nil + } + self.init(date, format: .formatter(formatter)) + } + + func xmlString(format: Format) -> String { + switch format { + case .secondsSince1970: + let seconds = unboxed.timeIntervalSince1970 + return seconds.description + case .millisecondsSince1970: + let milliseconds = unboxed.timeIntervalSince1970 * 1000.0 + return milliseconds.description + case .iso8601: + if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { + return ISO8601DateFormatter.xmlCoderFormatter().string(from: self.unboxed) + } else { + fatalError("ISO8601DateFormatter is unavailable on this platform.") + } + case let .formatter(formatter): + return formatter.string(from: unboxed) + } + } +} + +extension DateBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return xmlString(format: format) + } +} + +extension DateBox: SimpleBox {} + +extension DateBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/DecimalBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/DecimalBox.swift new file mode 100644 index 0000000000..35e09443da --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/DecimalBox.swift @@ -0,0 +1,62 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +import Foundation + +struct DecimalBox: Equatable { + typealias Unboxed = Decimal + + let unboxed: Unboxed + + init(_ unboxed: Unboxed) { + self.unboxed = unboxed + } + + init?(xmlString: String) { + guard let unboxed = Unboxed(string: xmlString) else { + return nil + } + self.init(unboxed) + } +} + +extension DecimalBox: Box { + var isNull: Bool { + return false + } + + /// # Lexical representation + /// Decimal has a lexical representation consisting of a finite-length sequence of + /// decimal digits separated by a period as a decimal indicator. + /// An optional leading sign is allowed. If the sign is omitted, `"+"` is assumed. + /// Leading and trailing zeroes are optional. If the fractional part is zero, + /// the period and following zero(es) can be omitted. + /// For example: `-1.23`, `12678967.543233`, `+100000.00`, `210`. + /// + /// # Canonical representation + /// The canonical representation for decimal is defined by prohibiting certain + /// options from the Lexical representation. Specifically, the preceding optional + /// `"+"` sign is prohibited. The decimal point is required. Leading and trailing + /// zeroes are prohibited subject to the following: there must be at least one + /// digit to the right and to the left of the decimal point which may be a zero. + /// + /// --- + /// + /// [Schema definition](https://www.w3.org/TR/xmlschema-2/#decimal) + var xmlString: String? { + return "\(unboxed)" + } +} + +extension DecimalBox: SimpleBox {} + +extension DecimalBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/DoubleBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/DoubleBox.swift new file mode 100644 index 0000000000..42302608d0 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/DoubleBox.swift @@ -0,0 +1,49 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Max Desiatov on 05/10/2019. +// + +struct DoubleBox: Equatable, ValueBox { + typealias Unboxed = Double + + let unboxed: Unboxed + + init(_ value: Unboxed) { + unboxed = value + } + + init?(xmlString: String) { + guard let unboxed = Double(xmlString) else { return nil } + + self.init(unboxed) + } +} + +extension DoubleBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + guard !unboxed.isNaN else { + return "NaN" + } + + guard !unboxed.isInfinite else { + return (unboxed > 0.0) ? "INF" : "-INF" + } + + return unboxed.description + } +} + +extension DoubleBox: SimpleBox {} + +extension DoubleBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/FloatBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/FloatBox.swift new file mode 100644 index 0000000000..3f0e283af7 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/FloatBox.swift @@ -0,0 +1,78 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct FloatBox: Equatable, ValueBox { + typealias Unboxed = Float + + let unboxed: Unboxed + + init(_ unboxed: Float) { + self.unboxed = Unboxed(unboxed) + } + + init?(xmlString: String) { + guard let unboxed = Unboxed(xmlString) else { + return nil + } + self.init(unboxed) + } +} + +extension FloatBox: Box { + var isNull: Bool { + return false + } + + /// # Lexical representation + /// float values have a lexical representation consisting of a mantissa followed, optionally, + /// by the character `"E"` or `"e"`, followed by an exponent. The exponent **must** be an integer. + /// The mantissa **must** be a decimal number. The representations for exponent and mantissa **must** + /// follow the lexical rules for integer and decimal. If the `"E"` or `"e"` and the following + /// exponent are omitted, an exponent value of `0` is assumed. + /// + /// The special values positive and negative infinity and not-a-number have lexical + /// representations `INF`, `-INF` and `NaN`, respectively. Lexical representations for zero + /// may take a positive or negative sign. + /// + /// For example, `-1E4`, `1267.43233E12`, `12.78e-2`, `12` , `-0`, `0` and `INF` are all + /// legal literals for float. + /// + /// # Canonical representation + /// The canonical representation for float is defined by prohibiting certain options from the + /// Lexical representation. Specifically, the exponent must be indicated by `"E"`. + /// Leading zeroes and the preceding optional `"+"` sign are prohibited in the exponent. + /// If the exponent is zero, it must be indicated by `"E0"`. For the mantissa, the preceding + /// optional `"+"` sign is prohibited and the decimal point is required. Leading and trailing + /// zeroes are prohibited subject to the following: number representations must be normalized + /// such that there is a single digit which is non-zero to the left of the decimal point and + /// at least a single digit to the right of the decimal point unless the value being represented + /// is zero. The canonical representation for zero is `0.0E0`. + /// + /// --- + /// + /// [Schema definition](https://www.w3.org/TR/xmlschema-2/#float) + var xmlString: String? { + guard !unboxed.isNaN else { + return "NaN" + } + + guard !unboxed.isInfinite else { + return (unboxed > 0.0) ? "INF" : "-INF" + } + + return unboxed.description + } +} + +extension FloatBox: SimpleBox {} + +extension FloatBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/IntBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/IntBox.swift new file mode 100644 index 0000000000..35380a6455 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/IntBox.swift @@ -0,0 +1,59 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct IntBox: Equatable { + typealias Unboxed = Int64 + + let unboxed: Unboxed + + init(_ unboxed: Integer) { + self.unboxed = Unboxed(unboxed) + } + + init?(xmlString: String) { + guard let unboxed = Unboxed(xmlString) else { + return nil + } + self.init(unboxed) + } + + func unbox() -> Integer? { + return Integer(exactly: unboxed) + } +} + +extension IntBox: Box { + var isNull: Bool { + return false + } + + /// # Lexical representation + /// Integer has a lexical representation consisting of a finite-length sequence of + /// decimal digits with an optional leading sign. If the sign is omitted, `"+"` is assumed. + /// For example: `-1`, `0`, `12678967543233`, `+100000`. + /// + /// # Canonical representation + /// The canonical representation for integer is defined by prohibiting certain + /// options from the Lexical representation. Specifically, the preceding optional + /// `"+"` sign is prohibited and leading zeroes are prohibited. + /// + /// --- + /// + /// [Schema definition](https://www.w3.org/TR/xmlschema-2/#integer) + var xmlString: String? { + return unboxed.description + } +} + +extension IntBox: SimpleBox {} + +extension IntBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/KeyedBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/KeyedBox.swift new file mode 100644 index 0000000000..271f665d92 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/KeyedBox.swift @@ -0,0 +1,57 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 11/19/18. +// + +struct KeyedBox { + typealias Key = String + typealias Attribute = SimpleBox + typealias Element = Box + + typealias Attributes = KeyedStorage + typealias Elements = KeyedStorage + + var elements = Elements() + var attributes = Attributes() + + var unboxed: (elements: Elements, attributes: Attributes) { + return ( + elements: elements, + attributes: attributes + ) + } + + var value: SimpleBox? { + return elements.values.first as? SimpleBox + } +} + +extension KeyedBox { + init(elements: E, attributes: A) + where E: Sequence, E.Element == (Key, Element), + A: Sequence, A.Element == (Key, Attribute) + { + let elements = Elements(elements) + let attributes = Attributes(attributes) + self.init(elements: elements, attributes: attributes) + } +} + +extension KeyedBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return nil + } +} + +extension KeyedBox: CustomStringConvertible { + var description: String { + return "{attributes: \(attributes), elements: \(elements)}" + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/NullBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/NullBox.swift new file mode 100644 index 0000000000..e5dd66b517 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/NullBox.swift @@ -0,0 +1,33 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct NullBox {} + +extension NullBox: Box { + var isNull: Bool { + return true + } + + var xmlString: String? { + return nil + } +} + +extension NullBox: SimpleBox {} + +extension NullBox: Equatable { + static func ==(_: NullBox, _: NullBox) -> Bool { + return true + } +} + +extension NullBox: CustomStringConvertible { + var description: String { + return "null" + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/SharedBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/SharedBox.swift new file mode 100644 index 0000000000..d05af76f8d --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/SharedBox.swift @@ -0,0 +1,35 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/22/18. +// + +class SharedBox { + private(set) var unboxed: Unboxed + + init(_ wrapped: Unboxed) { + unboxed = wrapped + } + + func withShared(_ body: (inout Unboxed) throws -> T) rethrows -> T { + return try body(&unboxed) + } +} + +extension SharedBox: Box { + var isNull: Bool { + return unboxed.isNull + } + + var xmlString: String? { + return unboxed.xmlString + } +} + +extension SharedBox: SharedBoxProtocol { + func unbox() -> Unboxed { + return unboxed + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/SingleKeyedBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/SingleKeyedBox.swift new file mode 100644 index 0000000000..47c6370d56 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/SingleKeyedBox.swift @@ -0,0 +1,25 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by James Bean on 7/15/19. +// + +/// A `Box` which contains a single `key` and `element` pair. This is useful for disambiguating elements which could either represent +/// an element nested in a keyed or unkeyed container, or an choice between multiple known-typed values (implemented in Swift using +/// enums with associated values). +struct SingleKeyedBox: SimpleBox { + var key: String + var element: Box +} + +extension SingleKeyedBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return nil + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/StringBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/StringBox.swift new file mode 100644 index 0000000000..c33629c99a --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/StringBox.swift @@ -0,0 +1,39 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct StringBox: Equatable { + typealias Unboxed = String + + let unboxed: Unboxed + + init(_ unboxed: Unboxed) { + self.unboxed = unboxed + } + + init(xmlString: Unboxed) { + self.init(xmlString) + } +} + +extension StringBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return unboxed.description + } +} + +extension StringBox: SimpleBox {} + +extension StringBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/UIntBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/UIntBox.swift new file mode 100644 index 0000000000..2fd70ac156 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/UIntBox.swift @@ -0,0 +1,62 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/17/18. +// + +struct UIntBox: Equatable { + typealias Unboxed = UInt64 + + let unboxed: Unboxed + + init(_ unboxed: Integer) { + self.unboxed = Unboxed(unboxed) + } + + init?(xmlString: String) { + guard let unboxed = Unboxed(xmlString) else { + return nil + } + self.init(unboxed) + } + + func unbox() -> Integer? { + return Integer(exactly: unboxed) + } +} + +extension UIntBox: Box { + var isNull: Bool { + return false + } + + /// # Lexical representation + /// Unsigned integer has a lexical representation consisting of an optional + /// sign followed by a finite-length sequence of decimal digits. + /// If the sign is omitted, the positive sign (`"+"`) is assumed. + /// If the sign is present, it must be `"+"` except for lexical forms denoting zero, + /// which may be preceded by a positive (`"+"`) or a negative (`"-"`) sign. + /// For example: `1`, `0`, `12678967543233`, `+100000`. + /// + /// # Canonical representation + /// The canonical representation for nonNegativeInteger is defined by prohibiting + /// certain options from the Lexical representation. Specifically, + /// the the optional `"+"` sign is prohibited and leading zeroes are prohibited. + /// + /// --- + /// + /// [Schema definition](https://www.w3.org/TR/xmlschema-2/#nonNegativeInteger) + var xmlString: String? { + return unboxed.description + } +} + +extension UIntBox: SimpleBox {} + +extension UIntBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/URLBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/URLBox.swift new file mode 100644 index 0000000000..6c12061783 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/URLBox.swift @@ -0,0 +1,44 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/21/18. +// + +import Foundation + +struct URLBox: Equatable { + typealias Unboxed = URL + + let unboxed: Unboxed + + init(_ unboxed: Unboxed) { + self.unboxed = unboxed + } + + init?(xmlString: String) { + guard let unboxed = Unboxed(string: xmlString) else { + return nil + } + self.init(unboxed) + } +} + +extension URLBox: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return unboxed.absoluteString + } +} + +extension URLBox: SimpleBox {} + +extension URLBox: CustomStringConvertible { + var description: String { + return unboxed.description + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/UnkeyedBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/UnkeyedBox.swift new file mode 100644 index 0000000000..203a21674c --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/UnkeyedBox.swift @@ -0,0 +1,19 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 11/20/18. +// + +typealias UnkeyedBox = [Box] + +extension Array: Box { + var isNull: Bool { + return false + } + + var xmlString: String? { + return nil + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Box/ValueBox.swift b/third-party/XMLCoder/Sources/Auxiliaries/Box/ValueBox.swift new file mode 100644 index 0000000000..a6739c471d --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Box/ValueBox.swift @@ -0,0 +1,13 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Max Desiatov on 05/10/2019. +// + +protocol ValueBox: SimpleBox { + associatedtype Unboxed + + init(_ value: Unboxed) +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Element.swift b/third-party/XMLCoder/Sources/Auxiliaries/Element.swift new file mode 100644 index 0000000000..8728e4ac74 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Element.swift @@ -0,0 +1,43 @@ +// +// XMLElementNode.swift +// XMLCoder +// +// Created by Benjamin Wetherfield on 6/4/20. +// + +protocol XMLElementProtocol {} + +/** Property wrapper specifying that a given property should be encoded and decoded as an XML element. + + For example, this type + ```swift + struct Book: Codable { + @Element var id: Int + } + ``` + + will encode value `Book(id: 42)` as `42`. And vice versa, + it will decode the former into the latter. + */ +@propertyWrapper +public struct Element: XMLElementProtocol { + public var wrappedValue: Value + + public init(_ wrappedValue: Value) { + self.wrappedValue = wrappedValue + } +} + +extension Element: Codable where Value: Codable { + public func encode(to encoder: Encoder) throws { + try wrappedValue.encode(to: encoder) + } + + public init(from decoder: Decoder) throws { + try wrappedValue = .init(from: decoder) + } +} + +extension Element: Equatable where Value: Equatable {} +extension Element: Hashable where Value: Hashable {} +extension Element: Sendable where Value: Sendable {} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/ElementAndAttribute.swift b/third-party/XMLCoder/Sources/Auxiliaries/ElementAndAttribute.swift new file mode 100644 index 0000000000..b04cfc5629 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/ElementAndAttribute.swift @@ -0,0 +1,44 @@ +// +// XMLBothNode.swift +// XMLCoder +// +// Created by Benjamin Wetherfield on 6/7/20. +// + +protocol XMLElementAndAttributeProtocol {} + +/** Property wrapper specifying that a given property should be decoded from either an XML element + or an XML attribute. When encoding, the value will be present as both an attribute, and an element. + + For example, this type + ```swift + struct Book: Codable { + @ElementAndAttribute var id: Int + } + ``` + + will encode value `Book(id: 42)` as `42`. It will decode both + `42` and `` as `Book(id: 42)`. + */ +@propertyWrapper +public struct ElementAndAttribute: XMLElementAndAttributeProtocol { + public var wrappedValue: Value + + public init(_ wrappedValue: Value) { + self.wrappedValue = wrappedValue + } +} + +extension ElementAndAttribute: Codable where Value: Codable { + public func encode(to encoder: Encoder) throws { + try wrappedValue.encode(to: encoder) + } + + public init(from decoder: Decoder) throws { + try wrappedValue = .init(from: decoder) + } +} + +extension ElementAndAttribute: Equatable where Value: Equatable {} +extension ElementAndAttribute: Hashable where Value: Hashable {} +extension ElementAndAttribute: Sendable where Value: Sendable {} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/ISO8601DateFormatter.swift b/third-party/XMLCoder/Sources/Auxiliaries/ISO8601DateFormatter.swift new file mode 100644 index 0000000000..a431b23e64 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/ISO8601DateFormatter.swift @@ -0,0 +1,17 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/21/17. +// + +import Foundation + +extension ISO8601DateFormatter { + static func xmlCoderFormatter() -> ISO8601DateFormatter { + let formatter = ISO8601DateFormatter() + formatter.formatOptions = .withInternetDateTime + return formatter + } +} \ No newline at end of file diff --git a/third-party/XMLCoder/Sources/Auxiliaries/KeyedStorage.swift b/third-party/XMLCoder/Sources/Auxiliaries/KeyedStorage.swift new file mode 100644 index 0000000000..808a38dce7 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/KeyedStorage.swift @@ -0,0 +1,82 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Max Desiatov on 07/04/2019. +// + +struct KeyedStorage { + typealias Buffer = [(Key, Value)] + typealias KeyMap = [Key: [Int]] + + fileprivate var keyMap = KeyMap() + fileprivate var buffer = Buffer() + + var isEmpty: Bool { + return buffer.isEmpty + } + + var count: Int { + return buffer.count + } + + var keys: [Key] { + return buffer.map { $0.0 } + } + + var values: [Value] { + return buffer.map { $0.1 } + } + + init(_ sequence: S) where S: Sequence, S.Element == (Key, Value) { + buffer = Buffer() + keyMap = KeyMap() + sequence.forEach { key, value in append(value, at: key) } + } + + subscript(key: Key) -> [Value] { + return keyMap[key]?.map { buffer[$0].1 } ?? [] + } + + mutating func append(_ value: Value, at key: Key) { + let i = buffer.count + buffer.append((key, value)) + if keyMap[key] != nil { + keyMap[key]?.append(i) + } else { + keyMap[key] = [i] + } + } + + func map(_ transform: (Key, Value) throws -> T) rethrows -> [T] { + return try buffer.map(transform) + } + + func compactMap( + _ transform: ((Key, Value)) throws -> T? + ) rethrows -> [T] { + return try buffer.compactMap(transform) + } + + mutating func reserveCapacity(_ capacity: Int) { + buffer.reserveCapacity(capacity) + keyMap.reserveCapacity(capacity) + } + + init() {} +} + +extension KeyedStorage: Sequence { + func makeIterator() -> Buffer.Iterator { + return buffer.makeIterator() + } +} + +extension KeyedStorage: CustomStringConvertible { + var description: String { + let result = buffer.map { "\"\($0)\": \($1)" }.joined(separator: ", ") + + return "[\(result)]" + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Metatypes.swift b/third-party/XMLCoder/Sources/Auxiliaries/Metatypes.swift new file mode 100644 index 0000000000..40be7f3322 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Metatypes.swift @@ -0,0 +1,31 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Max Desiatov on 30/12/2018. +// + +/// Type-erased protocol helper for a metatype check in generic `decode` +/// overload. If you custom sequence type is not decoded correctly, try +/// making it confirm to `XMLDecodableSequence`. Default conformances for +/// `Array` and `Dictionary` are already provided by the XMLCoder library. +public protocol XMLDecodableSequence { + init() +} + +extension Array: XMLDecodableSequence {} + +extension Dictionary: XMLDecodableSequence {} + +/// Type-erased protocol helper for a metatype check in generic `decode` +/// overload. +protocol AnyOptional { + init() +} + +extension Optional: AnyOptional { + init() { + self = nil + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/String+Extensions.swift b/third-party/XMLCoder/Sources/Auxiliaries/String+Extensions.swift new file mode 100644 index 0000000000..59ce223f81 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/String+Extensions.swift @@ -0,0 +1,52 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/18/18. +// + +import Foundation + +extension StringProtocol where Self.Index == String.Index { + func escape(_ characterSet: [(character: String, escapedCharacter: String)]) -> String { + var string = String(self) + + for set in characterSet { + string = string.replacingOccurrences(of: set.character, with: set.escapedCharacter, options: .literal) + } + + return string + } +} + +extension StringProtocol { + func capitalizingFirstLetter() -> Self { + guard !isEmpty else { + return self + } + return Self(prefix(1).uppercased() + dropFirst())! + } + + mutating func capitalizeFirstLetter() { + self = capitalizingFirstLetter() + } + + func lowercasingFirstLetter() -> Self { + // avoid lowercasing single letters (I), or capitalized multiples (AThing ! to aThing, leave as AThing) + guard count > 1, !(String(prefix(2)) == prefix(2).lowercased()) else { + return self + } + return Self(prefix(1).lowercased() + dropFirst())! + } + + mutating func lowercaseFirstLetter() { + self = lowercasingFirstLetter() + } +} + +extension String { + func isAllWhitespace() -> Bool { + return unicodeScalars.allSatisfy(CharacterSet.whitespacesAndNewlines.contains) + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/Utils.swift b/third-party/XMLCoder/Sources/Auxiliaries/Utils.swift new file mode 100644 index 0000000000..747f327f6e --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/Utils.swift @@ -0,0 +1,13 @@ +// Copyright (c) 2018-2023 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Alkenso (Vladimir Vashurkin) on 08.06.2023. +// + +import Foundation + +extension CodingKey { + internal var isInlined: Bool { stringValue == "" } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLChoiceCodingKey.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLChoiceCodingKey.swift new file mode 100644 index 0000000000..aa3dbff25f --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLChoiceCodingKey.swift @@ -0,0 +1,59 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Benjamin Wetherfield on 7/17/19. +// + +/// An empty marker protocol that can be used in place of `CodingKey`. It must be used when +/// attempting to encode and decode union-type–like enums with associated values to and from `XML` +/// choice elements. +/// +/// - Important: In order for your `XML`-destined `Codable` type to be encoded and/or decoded +/// properly, you must conform your custom `CodingKey` type additionally to `XMLChoiceCodingKey`. +/// +/// For example, say you have defined a type which can hold _either_ an `Int` _or_ a `String`: +/// +/// enum IntOrString { +/// case int(Int) +/// case string(String) +/// } +/// +/// Implementing the requirements for the `Codable` protocol like this: +/// +/// extension IntOrString: Codable { +/// enum CodingKeys: String, XMLChoiceCodingKey { +/// case int +/// case string +/// } +/// +/// func encode(to encoder: Encoder) throws { +/// var container = encoder.container(keyedBy: CodingKeys.self) +/// switch self { +/// case let .int(value): +/// try container.encode(value, forKey: .int) +/// case let .string(value): +/// try container.encode(value, forKey: .string) +/// } +/// } +/// +/// init(from decoder: Decoder) throws { +/// let container = try decoder.container(keyedBy: CodingKeys.self) +/// do { +/// self = .int(try container.decode(Int.self, forKey: .int)) +/// } catch { +/// self = .string(try container.decode(String.self, forKey: .string)) +/// } +/// } +/// } +/// +/// Retroactively conform the `CodingKeys` enum to `XMLChoiceCodingKey` when targeting `XML` as your +/// encoded format. +/// +/// extension IntOrString.CodingKeys: XMLChoiceCodingKey {} +/// +/// - Note: The `XMLChoiceCodingKey` marker protocol allows the `XMLEncoder` / `XMLDecoder` to +/// resolve ambiguities particular to the `XML` format between nested unkeyed container elements and +/// choice elements. +public protocol XMLChoiceCodingKey: CodingKey {} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLCoderElement.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLCoderElement.swift new file mode 100644 index 0000000000..1b1d8a0ffd --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLCoderElement.swift @@ -0,0 +1,451 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/18/18. +// + +import Foundation + +struct XMLCoderElement: Equatable, Sendable { + struct Attribute: Equatable, Sendable { + let key: String + let value: String + } + + let key: String + private(set) var stringValue: String? + private(set) var elements: [XMLCoderElement] = [] + private(set) var attributes: [Attribute] = [] + private(set) var containsTextNodes: Bool = false + + var isStringNode: Bool { + return key == "" + } + + var isCDATANode: Bool { + return key == "#CDATA" + } + + var isTextNode: Bool { + return isStringNode || isCDATANode + } + + private var isInlined: Bool { + return key.isEmpty + } + + init( + key: String, + elements: [XMLCoderElement] = [], + attributes: [Attribute] = [] + ) { + self.key = key + stringValue = nil + self.elements = elements + self.attributes = attributes + } + + init( + key: String, + stringValue string: String, + attributes: [Attribute] = [] + ) { + self.key = key + elements = [XMLCoderElement(stringValue: string)] + self.attributes = attributes + containsTextNodes = true + } + + init( + key: String, + cdataValue string: String, + attributes: [Attribute] = [] + ) { + self.key = key + elements = [XMLCoderElement(cdataValue: string)] + self.attributes = attributes + containsTextNodes = true + } + + init(stringValue string: String) { + key = "" + stringValue = string + } + + init(cdataValue string: String) { + key = "#CDATA" + stringValue = string + } + + mutating func append(element: XMLCoderElement) { + elements.append(element) + containsTextNodes = containsTextNodes || element.isTextNode + } + + mutating func append(string: String) { + if elements.last?.isTextNode == true { + let oldValue = elements[elements.count - 1].stringValue ?? "" + elements[elements.count - 1].stringValue = oldValue + string + } else { + elements.append(XMLCoderElement(stringValue: string)) + } + containsTextNodes = true + } + + mutating func append(cdata string: String) { + if elements.last?.isCDATANode == true { + let oldValue = elements[elements.count - 1].stringValue ?? "" + elements[elements.count - 1].stringValue = oldValue + string + } else { + elements.append(XMLCoderElement(cdataValue: string)) + } + containsTextNodes = true + } + + mutating func trimTextNodes() { + guard containsTextNodes else { return } + for idx in elements.indices { + elements[idx].stringValue = elements[idx].stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) + } + } + + func transformToBoxTree() -> Box { + if isTextNode { + return StringBox(stringValue!) + } + + let attributes = KeyedStorage(self.attributes.map { attribute in + (key: attribute.key, value: StringBox(attribute.value) as SimpleBox) + }) + + var storage = KeyedStorage() + // storage.reserveCapacity(self.elements) + + for element in self.elements { + + let hasElements = !element.elements.isEmpty + let hasAttributes = !element.attributes.isEmpty + let hasText = element.stringValue != nil + + if hasElements || hasAttributes { + storage.append(element.transformToBoxTree(), at: element.key) + } else if hasText { + storage.append(element.transformToBoxTree(), at: element.key) + } else { + storage.append(SingleKeyedBox(key: element.key, element: NullBox()), at: element.key) + } + } + + return KeyedBox(elements: storage, attributes: attributes) + } + + func toXMLString( + with header: XMLHeader?, + doctype: XMLDocumentType?, + escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + formatting: XMLEncoder.OutputFormatting, + indentation: XMLEncoder.PrettyPrintIndentation + ) -> String { + var base = "" + + if let header = header, let headerXML = header.toXML() { + base += headerXML + } + + if let doctype = doctype { + base += doctype.toXML() + } + + return base + _toXMLString(escapedCharacters, formatting, indentation) + } + + private func formatUnsortedXMLElements( + _ string: inout String, + _ level: Int, + _ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + _ formatting: XMLEncoder.OutputFormatting, + _ indentation: XMLEncoder.PrettyPrintIndentation, + _ prettyPrinted: Bool + ) { + formatXMLElements( + from: elements, + into: &string, + at: level, + escapedCharacters: escapedCharacters, + formatting: formatting, + indentation: indentation, + prettyPrinted: prettyPrinted + ) + } + + fileprivate func elementString( + for element: XMLCoderElement, + at level: Int, + formatting: XMLEncoder.OutputFormatting, + indentation: XMLEncoder.PrettyPrintIndentation, + escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + prettyPrinted: Bool + ) -> String { + if let stringValue = element.stringValue { + if element.isCDATANode { + return "" + } else { + return stringValue.escape(escapedCharacters.elements) + } + } + + var string = "" + let indentLevel = isInlined ? level : level + 1 + string += element._toXMLString(indented: indentLevel, escapedCharacters, formatting, indentation) + string += prettyPrinted && !isInlined ? "\n" : "" + return string + } + + fileprivate func formatSortedXMLElements( + _ string: inout String, + _ level: Int, + _ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + _ formatting: XMLEncoder.OutputFormatting, + _ indentation: XMLEncoder.PrettyPrintIndentation, + _ prettyPrinted: Bool + ) { + formatXMLElements(from: elements.sorted { $0.key < $1.key }, + into: &string, + at: level, + escapedCharacters: escapedCharacters, + formatting: formatting, + indentation: indentation, + prettyPrinted: prettyPrinted) + } + + fileprivate func formatXMLAttributes( + from attributes: [Attribute], + into string: inout String, + charactersEscapedInAttributes: [(String, String)] + ) { + for attribute in attributes { + string += " \(attribute.key)=\"\(attribute.value.escape(charactersEscapedInAttributes))\"" + } + } + + fileprivate func formatXMLElements( + from elements: [XMLCoderElement], + into string: inout String, + at level: Int, + escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + formatting: XMLEncoder.OutputFormatting, + indentation: XMLEncoder.PrettyPrintIndentation, + prettyPrinted: Bool + ) { + for element in elements { + string += elementString(for: element, + at: level, + formatting: formatting, + indentation: indentation, + escapedCharacters: escapedCharacters, + prettyPrinted: prettyPrinted && !containsTextNodes) + } + } + + private func formatXMLAttributes( + _ formatting: XMLEncoder.OutputFormatting, + _ string: inout String, + _ charactersEscapedInAttributes: [(String, String)] + ) { + let attributesBelongingToContainer = self.elements.filter { + $0.key.isEmpty && !$0.attributes.isEmpty + }.flatMap { + $0.attributes + } + let allAttributes = self.attributes + attributesBelongingToContainer + + let attributes = formatting.contains(.sortedKeys) ? + allAttributes.sorted(by: { $0.key < $1.key }) : + allAttributes + formatXMLAttributes( + from: attributes, + into: &string, + charactersEscapedInAttributes: charactersEscapedInAttributes + ) + } + + private func formatXMLElements( + _ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + _ formatting: XMLEncoder.OutputFormatting, + _ indentation: XMLEncoder.PrettyPrintIndentation, + _ string: inout String, + _ level: Int, + _ prettyPrinted: Bool + ) { + if formatting.contains(.sortedKeys) { + formatSortedXMLElements( + &string, level, escapedCharacters, formatting, indentation, prettyPrinted + ) + return + } + formatUnsortedXMLElements( + &string, level, escapedCharacters, formatting, indentation, prettyPrinted + ) + } + + private func _toXMLString( + indented level: Int = 0, + _ escapedCharacters: (elements: [(String, String)], attributes: [(String, String)]), + _ formatting: XMLEncoder.OutputFormatting, + _ indentation: XMLEncoder.PrettyPrintIndentation + ) -> String { + let prettyPrinted = formatting.contains(.prettyPrinted) + let prefix: String + switch indentation { + case let .spaces(count) where prettyPrinted && !isInlined: + prefix = String(repeating: " ", count: level * count) + case let .tabs(count) where prettyPrinted && !isInlined: + prefix = String(repeating: "\t", count: level * count) + default: + prefix = "" + } + var string = prefix + + if !key.isEmpty { + string += "<\(key)" + formatXMLAttributes(formatting, &string, escapedCharacters.attributes) + } + + if !elements.isEmpty || formatting.contains(.noEmptyElements) { + let hasOnlyIntrinsicContent = elements.allSatisfy { element in + element.key.isEmpty && !element.elements.contains { !$0.key.isEmpty } + } + let prettyPrintElements = prettyPrinted && !containsTextNodes && !hasOnlyIntrinsicContent + if !key.isEmpty { + string += prettyPrintElements ? ">\n" : ">" + } + if !elements.isEmpty { + formatXMLElements(escapedCharacters, formatting, indentation, &string, level, prettyPrintElements) + } + + if prettyPrintElements { string += prefix } + if !key.isEmpty { + string += "" + } + } else { + if !key.isEmpty { + string += " />" + } + } + + return string + } +} + +// MARK: - Convenience Initializers + +extension XMLCoderElement { + init(key: String, isStringBoxCDATA isCDATA: Bool, box: UnkeyedBox, attributes: [Attribute] = []) { + if let containsChoice = box as? [ChoiceBox] { + self.init( + key: key, + elements: containsChoice.map { + XMLCoderElement(key: $0.key, isStringBoxCDATA: isCDATA, box: $0.element) + }, + attributes: attributes + ) + } else { + self.init( + key: key, + elements: box.map { XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: $0) }, + attributes: attributes + ) + } + } + + init(key: String, isStringBoxCDATA: Bool, box: ChoiceBox, attributes: [Attribute] = []) { + self.init( + key: key, + elements: [ + XMLCoderElement(key: box.key, isStringBoxCDATA: isStringBoxCDATA, box: box.element), + ], + attributes: attributes + ) + } + + init(key: String, isStringBoxCDATA isCDATA: Bool, box: KeyedBox, attributes: [Attribute] = []) { + var elements: [XMLCoderElement] = [] + + for (key, box) in box.elements { + let fail = { + preconditionFailure("Unclassified box: \(type(of: box))") + } + + switch box { + case let sharedUnkeyedBox as SharedBox: + let box = sharedUnkeyedBox.unboxed + elements.append(contentsOf: box.map { + XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: $0) + }) + case let unkeyedBox as UnkeyedBox: + // This basically injects the unkeyed children directly into self: + elements.append(contentsOf: unkeyedBox.map { + XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: $0) + }) + case let sharedKeyedBox as SharedBox: + let box = sharedKeyedBox.unboxed + elements.append(XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: box)) + case let keyedBox as KeyedBox: + elements.append(XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: keyedBox)) + case let simpleBox as SimpleBox: + elements.append(XMLCoderElement(key: key, isStringBoxCDATA: isCDATA, box: simpleBox)) + default: + fail() + } + } + + let attributes: [Attribute] = attributes + box.attributes.compactMap { key, box in + guard let value = box.xmlString else { + return nil + } + return Attribute(key: key, value: value) + } + + self.init(key: key, elements: elements, attributes: attributes) + } + + init(key: String, isStringBoxCDATA: Bool, box: SimpleBox) { + if isStringBoxCDATA, let stringBox = box as? StringBox { + self.init(key: key, cdataValue: stringBox.unboxed) + } else if let value = box.xmlString { + self.init(key: key, stringValue: value) + } else { + self.init(key: key) + } + } + + init(key: String, isStringBoxCDATA isCDATA: Bool, box: Box, attributes: [Attribute] = []) { + switch box { + case let sharedUnkeyedBox as SharedBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: sharedUnkeyedBox.unboxed, attributes: attributes) + case let sharedKeyedBox as SharedBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: sharedKeyedBox.unboxed, attributes: attributes) + case let sharedChoiceBox as SharedBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: sharedChoiceBox.unboxed, attributes: attributes) + case let unkeyedBox as UnkeyedBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: unkeyedBox, attributes: attributes) + case let keyedBox as KeyedBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: keyedBox, attributes: attributes) + case let choiceBox as ChoiceBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: choiceBox, attributes: attributes) + case let simpleBox as SimpleBox: + self.init(key: key, isStringBoxCDATA: isCDATA, box: simpleBox) + case let box: + preconditionFailure("Unclassified box: \(type(of: box))") + } + } +} + +extension XMLCoderElement { + func isWhitespaceWithNoElements() -> Bool { + let stringValueIsWhitespaceOrNil = stringValue?.isAllWhitespace() ?? true + return self.key == "" && stringValueIsWhitespaceOrNil && self.elements.isEmpty + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLDocumentType.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLDocumentType.swift new file mode 100644 index 0000000000..ec257ebc49 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLDocumentType.swift @@ -0,0 +1,65 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Joannis Orlandos on 8/11/22. +// + +import Foundation + +public struct XMLDocumentType { + public enum External: String { + case `public` = "PUBLIC" + case system = "SYSTEM" + } + + public let rootElement: String + public let external: External + public let dtdName: String? + public let dtdLocation: String + + internal init( + rootElement: String, + external: External, + dtdName: String?, + dtdLocation: String + ) { + self.rootElement = rootElement + self.external = external + self.dtdName = dtdName + self.dtdLocation = dtdLocation + } + + public static func `public`(rootElement: String, dtdName: String, dtdLocation: String) -> XMLDocumentType { + XMLDocumentType( + rootElement: rootElement, + external: .public, + dtdName: dtdName, + dtdLocation: dtdLocation + ) + } + + public static func system(rootElement: String, dtdLocation: String) -> XMLDocumentType { + XMLDocumentType( + rootElement: rootElement, + external: .system, + dtdName: nil, + dtdLocation: dtdLocation + ) + } + + func toXML() -> String { + var string = "\n" + + return string + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLHeader.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLHeader.swift new file mode 100644 index 0000000000..6770a20c76 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLHeader.swift @@ -0,0 +1,57 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 12/18/18. +// + +import Foundation + +/// Type that allows overriding XML header during encoding. Pass a value of this type to the `encode` +/// function of `XMLEncoder` to specify the exact value of the header you'd like to see in the encoded +/// data. +public struct XMLHeader: Sendable { + /// The XML standard that the produced document conforms to. + public let version: Double? + + /// The encoding standard used to represent the characters in the produced document. + public let encoding: String? + + /// Indicates whether a document relies on information from an external source. + public let standalone: String? + + public init(version: Double? = nil, encoding: String? = nil, standalone: String? = nil) { + self.version = version + self.encoding = encoding + self.standalone = standalone + } + + func isEmpty() -> Bool { + return version == nil && encoding == nil && standalone == nil + } + + func toXML() -> String? { + guard !isEmpty() else { + return nil + } + + var string = "\n" + + return string + } +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLKey.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLKey.swift new file mode 100644 index 0000000000..f7f31f3291 --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLKey.swift @@ -0,0 +1,38 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/21/17. +// + +import Foundation + +/// Shared Key Types +struct XMLKey: CodingKey, Sendable { + public let stringValue: String + public let intValue: Int? + + public init?(stringValue: String) { + self.init(key: stringValue) + } + + public init?(intValue: Int) { + self.init(index: intValue) + } + + public init(stringValue: String, intValue: Int?) { + self.stringValue = stringValue + self.intValue = intValue + } + + init(key: String) { + self.init(stringValue: key, intValue: nil) + } + + init(index: Int) { + self.init(stringValue: "\(index)", intValue: index) + } + + static let `super` = XMLKey(stringValue: "super")! +} diff --git a/third-party/XMLCoder/Sources/Auxiliaries/XMLStackParser.swift b/third-party/XMLCoder/Sources/Auxiliaries/XMLStackParser.swift new file mode 100644 index 0000000000..c18a4fd91a --- /dev/null +++ b/third-party/XMLCoder/Sources/Auxiliaries/XMLStackParser.swift @@ -0,0 +1,200 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/14/17. +// + +import Foundation +#if canImport(FoundationXML) +import FoundationXML +#endif + +class XMLStackParser: NSObject { + var root: XMLCoderElement? + private var stack: [XMLCoderElement] = [] + private let trimValueWhitespaces: Bool + private let removeWhitespaceElements: Bool + + init(trimValueWhitespaces: Bool = true, removeWhitespaceElements: Bool = false) { + self.trimValueWhitespaces = trimValueWhitespaces + self.removeWhitespaceElements = removeWhitespaceElements + super.init() + } + + static func parse( + with data: Data, + errorContextLength length: UInt, + shouldProcessNamespaces: Bool, + trimValueWhitespaces: Bool, + removeWhitespaceElements: Bool + ) throws -> Box { + let parser = XMLStackParser(trimValueWhitespaces: trimValueWhitespaces, + removeWhitespaceElements: removeWhitespaceElements) + + let node = try parser.parse( + with: data, + errorContextLength: length, + shouldProcessNamespaces: shouldProcessNamespaces + ) + + return node.transformToBoxTree() + } + + func parse( + with data: Data, + errorContextLength: UInt, + shouldProcessNamespaces: Bool + ) throws -> XMLCoderElement { + let xmlParser = XMLParser(data: data) + xmlParser.shouldProcessNamespaces = shouldProcessNamespaces + xmlParser.delegate = self + + guard !xmlParser.parse() || root == nil else { + return root! + } + + guard let error = xmlParser.parserError else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: [], + debugDescription: "The given data could not be parsed into XML." + )) + } + + // `lineNumber` isn't 0-indexed, so 0 is an invalid value for context + guard errorContextLength > 0 && xmlParser.lineNumber > 0 else { + throw error + } + + let string = String(data: data, encoding: .utf8) ?? "" + let lines = string.split(separator: "\n") + var errorPosition = 0 + let offset = Int(errorContextLength / 2) + for i in 0.. 0 { + lowerBoundIndex = errorPosition - offset + } + + var upperBoundIndex = string.count + if errorPosition + offset < string.count { + upperBoundIndex = errorPosition + offset + } + + let lowerBound = String.Index(utf16Offset: lowerBoundIndex, in: string) + let upperBound = String.Index(utf16Offset: upperBoundIndex, in: string) + + let context = string[lowerBound.. ()) rethrows { + guard !stack.isEmpty else { + return + } + try body(&stack[stack.count - 1]) + } + + func trimWhitespacesIfNeeded(_ string: String) -> String { + return trimValueWhitespaces + ? string.trimmingCharacters(in: .whitespacesAndNewlines) + : string + } +} + +extension XMLStackParser: XMLParserDelegate { + func parserDidStartDocument(_: XMLParser) { + root = nil + stack = [] + } + + func parser(_: XMLParser, + didStartElement elementName: String, + namespaceURI: String?, + qualifiedName: String?, + attributes attributeDict: [String: String] = [:]) + { + let attributes = attributeDict.map { key, value in + XMLCoderElement.Attribute(key: key, value: value) + } + let element = XMLCoderElement(key: elementName, attributes: attributes) + stack.append(element) + } + + func parser(_: XMLParser, + didEndElement _: String, + namespaceURI _: String?, + qualifiedName _: String?) + { + guard var element = stack.popLast() else { + return + } + if trimValueWhitespaces && element.containsTextNodes { + element.trimTextNodes() + } + + let updatedElement = removeWhitespaceElements ? elementWithFilteredElements(element: element) : element + + withCurrentElement { currentElement in + currentElement.append(element: updatedElement) + } + + if stack.isEmpty { + root = updatedElement + } + } + + func elementWithFilteredElements(element: XMLCoderElement) -> XMLCoderElement { + var hasWhitespaceElements = false + var hasNonWhitespaceElements = false + var filteredElements: [XMLCoderElement] = [] + for ele in element.elements { + if ele.isWhitespaceWithNoElements() { + hasWhitespaceElements = true + } else { + hasNonWhitespaceElements = true + filteredElements.append(ele) + } + } + + if hasWhitespaceElements && hasNonWhitespaceElements { + return XMLCoderElement(key: element.key, elements: filteredElements, attributes: element.attributes) + } + return element + } + + func parser(_: XMLParser, foundCharacters string: String) { + let processedString = trimWhitespacesIfNeeded(string) + guard processedString.count > 0, string.count != 0 else { + return + } + + withCurrentElement { currentElement in + currentElement.append(string: string) + } + } + + func parser(_: XMLParser, foundCDATA CDATABlock: Data) { + guard let string = String(data: CDATABlock, encoding: .utf8) else { + return + } + + withCurrentElement { currentElement in + currentElement.append(cdata: string) + } + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/DecodingErrorExtension.swift b/third-party/XMLCoder/Sources/Decoder/DecodingErrorExtension.swift new file mode 100644 index 0000000000..011477ec2c --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/DecodingErrorExtension.swift @@ -0,0 +1,54 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/21/17. +// + +import Foundation + +// MARK: - Error Utilities + +extension DecodingError { + /// Returns a `.typeMismatch` error describing the expected type. + /// + /// - parameter path: The path of `CodingKey`s taken to decode a value of this type. + /// - parameter expectation: The type expected to be encountered. + /// - parameter reality: The value that was encountered instead of the expected type. + /// - returns: A `DecodingError` with the appropriate path and debug description. + static func typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: Box) -> DecodingError { + let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead." + return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description)) + } + + /// Returns a description of the type of `value` appropriate for an error message. + /// + /// - parameter value: The value whose type to describe. + /// - returns: A string describing `value`. + /// - precondition: `value` is one of the types below. + static func _typeDescription(of box: Box) -> String { + switch box { + case is NullBox: + return "a null value" + case is BoolBox: + return "a boolean value" + case is DecimalBox: + return "a decimal value" + case is IntBox: + return "a signed integer value" + case is UIntBox: + return "an unsigned integer value" + case is FloatBox: + return "a floating-point value" + case is DoubleBox: + return "a double floating-point value" + case is UnkeyedBox: + return "a array value" + case is KeyedBox: + return "a dictionary value" + case _: + return "\(type(of: box))" + } + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/DynamicNodeDecoding.swift b/third-party/XMLCoder/Sources/Decoder/DynamicNodeDecoding.swift new file mode 100644 index 0000000000..9c87ba4326 --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/DynamicNodeDecoding.swift @@ -0,0 +1,44 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Max Desiatov on 01/03/2019. +// + +/** Allows conforming types to specify how its properties will be decoded. + + For example: + ```swift + struct Book: Codable, Equatable, DynamicNodeDecoding { + let id: UInt + let title: String + let categories: [Category] + + enum CodingKeys: String, CodingKey { + case id + case title + case categories = "category" + } + + static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding { + switch key { + case Book.CodingKeys.id: return .attribute + default: return .element + } + } + } + ``` + allows XML of this form to be decoded into values of type `Book`: + + ```xml + + Cat in the Hat + Kids + Wildlife + + ``` + */ +public protocol DynamicNodeDecoding: Decodable { + static func nodeDecoding(for key: CodingKey) -> XMLDecoder.NodeDecoding +} diff --git a/third-party/XMLCoder/Sources/Decoder/SingleValueDecodingContainer.swift b/third-party/XMLCoder/Sources/Decoder/SingleValueDecodingContainer.swift new file mode 100644 index 0000000000..0f08b22082 --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/SingleValueDecodingContainer.swift @@ -0,0 +1,57 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/20/17. +// + +import Foundation + +extension XMLDecoderImplementation: SingleValueDecodingContainer { + // MARK: SingleValueDecodingContainer Methods + + public func decodeNil() -> Bool { + return (try? topContainer().isNull) ?? true + } + + public func decode(_: Bool.Type) throws -> Bool { + return try unbox(try topContainer()) + } + + public func decode(_: Decimal.Type) throws -> Decimal { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } + + public func decode(_: Float.Type) throws -> Float { + return try unbox(try topContainer()) + } + + public func decode(_: Double.Type) throws -> Double { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> String { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> Date { + return try unbox(try topContainer()) + } + + public func decode(_: String.Type) throws -> Data { + return try unbox(try topContainer()) + } + + public func decode(_: T.Type) throws -> T { + return try unbox(try topContainer()) + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/XMLChoiceDecodingContainer.swift b/third-party/XMLCoder/Sources/Decoder/XMLChoiceDecodingContainer.swift new file mode 100644 index 0000000000..7d4c1026b5 --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLChoiceDecodingContainer.swift @@ -0,0 +1,105 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by James Bean on 7/18/19. +// + +/// Container specialized for decoding XML choice elements. +struct XMLChoiceDecodingContainer: KeyedDecodingContainerProtocol { + typealias Key = K + + // MARK: Properties + + /// A reference to the decoder we're reading from. + private let decoder: XMLDecoderImplementation + + /// A reference to the container we're reading from. + private let container: SharedBox + + /// The path of coding keys taken to get to this point in decoding. + public private(set) var codingPath: [CodingKey] + + // MARK: - Initialization + + /// Initializes `self` by referencing the given decoder and container. + init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox) { + self.decoder = decoder + container.withShared { $0.key = decoder.keyTransform($0.key) } + self.container = container + codingPath = decoder.codingPath + } + + // MARK: - KeyedDecodingContainerProtocol Methods + + public var allKeys: [Key] { + return container.withShared { [Key(stringValue: $0.key)!] } + } + + public func contains(_ key: Key) -> Bool { + return container.withShared { $0.key == key.stringValue } + } + + public func decodeNil(forKey key: Key) throws -> Bool { + return container.withShared { $0.element.isNull } + } + + public func decode(_ type: T.Type, forKey key: Key) throws -> T { + guard container.withShared({ $0.key == key.stringValue }), key is XMLChoiceCodingKey else { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: type, + reality: container + ) + } + return try decoder.unbox(container.withShared { $0.element }) + } + + public func nestedContainer( + keyedBy _: NestedKey.Type, forKey key: Key + ) throws -> KeyedDecodingContainer { + guard container.unboxed.key == key.stringValue else { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: NestedKey.self, + reality: container + ) + } + + let value = container.unboxed.element + guard let container = XMLKeyedDecodingContainer(box: value, decoder: decoder) else { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [String: Any].self, + reality: value + ) + } + + return KeyedDecodingContainer(container) + } + + public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: Key.self, + reality: container + ) + } + + public func superDecoder() throws -> Decoder { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: Key.self, + reality: container + ) + } + + public func superDecoder(forKey key: Key) throws -> Decoder { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: Key.self, + reality: container + ) + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/XMLDecoder.swift b/third-party/XMLCoder/Sources/Decoder/XMLDecoder.swift new file mode 100644 index 0000000000..1f67b26975 --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLDecoder.swift @@ -0,0 +1,405 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/20/17. +// + +import Foundation + +// MARK: - XML Decoder + +/// `XMLDecoder` facilitates the decoding of XML into semantic `Decodable` types. +open class XMLDecoder { + // MARK: Options + + /// The strategy to use for decoding `Date` values. + public enum DateDecodingStrategy { + /// Defer to `Date` for decoding. This is the default strategy. + case deferredToDate + + /// Decode the `Date` as a UNIX timestamp from a XML number. This is the default strategy. + case secondsSince1970 + + /// Decode the `Date` as UNIX millisecond timestamp from a XML number. + case millisecondsSince1970 + + /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + case iso8601 + + /// Decode the `Date` as a string parsed by the given formatter. + case formatted(DateFormatter) + + /// Decode the `Date` as a custom box decoded by the given closure. + case custom((_ decoder: Decoder) throws -> Date) + + /// Decode the `Date` as a string parsed by the given formatter for the give key. + static func keyFormatted( + _ formatterForKey: @escaping (CodingKey) throws -> DateFormatter? + ) -> XMLDecoder.DateDecodingStrategy { + return .custom { decoder -> Date in + guard let codingKey = decoder.codingPath.last else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "No Coding Path Found" + )) + } + + guard let container = try? decoder.singleValueContainer(), + let text = try? container.decode(String.self) + else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Could not decode date text" + )) + } + + guard let dateFormatter = try formatterForKey(codingKey) else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "No date formatter for date text" + ) + } + + if let date = dateFormatter.date(from: text) { + return date + } else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Cannot decode date string \(text)" + ) + } + } + } + } + + /// The strategy to use for decoding `Data` values. + public enum DataDecodingStrategy { + /// Defer to `Data` for decoding. + case deferredToData + + /// Decode the `Data` from a Base64-encoded string. This is the default strategy. + case base64 + + /// Decode the `Data` as a custom box decoded by the given closure. + case custom((_ decoder: Decoder) throws -> Data) + + /// Decode the `Data` as a custom box by the given closure for the give key. + static func keyFormatted( + _ formatterForKey: @escaping (CodingKey) throws -> Data? + ) -> XMLDecoder.DataDecodingStrategy { + return .custom { decoder -> Data in + guard let codingKey = decoder.codingPath.last else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "No Coding Path Found" + )) + } + + guard let container = try? decoder.singleValueContainer(), + let text = try? container.decode(String.self) + else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Could not decode date text" + )) + } + + guard let data = try formatterForKey(codingKey) else { + throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Cannot decode data string \(text)" + ) + } + + return data + } + } + } + + /// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN). + public enum NonConformingFloatDecodingStrategy { + /// Throw upon encountering non-conforming values. This is the default strategy. + case `throw` + + /// Decode the values from the given representation strings. + case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String) + } + + /// The strategy to use for automatically changing the box of keys before decoding. + public enum KeyDecodingStrategy { + /// Use the keys specified by each type. This is the default strategy. + case useDefaultKeys + + /// Convert from "snake_case_keys" to "camelCaseKeys" before attempting + /// to match a key with the one specified by each type. + /// + /// The conversion to upper case uses `Locale.system`, also known as + /// the ICU "root" locale. This means the result is consistent + /// regardless of the current user's locale and language preferences. + /// + /// Converting from snake case to camel case: + /// 1. Capitalizes the word starting after each `_` + /// 2. Removes all `_` + /// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata). + /// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`. + /// + /// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character. + case convertFromSnakeCase + + /// Convert from "kebab-case" to "kebabCase" before attempting + /// to match a key with the one specified by each type. + case convertFromKebabCase + + /// Convert from "CodingKey" to "codingKey" + case convertFromCapitalized + + /// Convert from "CODING_KEY" to "codingKey" + case convertFromUppercase + + /// Provide a custom conversion from the key in the encoded XML to the + /// keys specified by the decoded types. + /// The full path to the current decoding position is provided for + /// context (in case you need to locate this key within the payload). + /// The returned key is used in place of the last component in the + /// coding path before decoding. + /// If the result of the conversion is a duplicate key, then only one + /// box will be present in the container for the type to decode from. + case custom((_ codingPath: [CodingKey]) -> CodingKey) + + static func _convertFromCapitalized(_ stringKey: String) -> String { + guard !stringKey.isEmpty else { + return stringKey + } + let firstLetter = stringKey.prefix(1).lowercased() + let result = firstLetter + stringKey.dropFirst() + return result + } + + static func _convertFromUppercase(_ stringKey: String) -> String { + _convert(stringKey.lowercased(), usingSeparator: "_") + } + + static func _convertFromSnakeCase(_ stringKey: String) -> String { + return _convert(stringKey, usingSeparator: "_") + } + + static func _convertFromKebabCase(_ stringKey: String) -> String { + return _convert(stringKey, usingSeparator: "-") + } + + static func _convert(_ stringKey: String, usingSeparator separator: Character) -> String { + guard !stringKey.isEmpty else { + return stringKey + } + + // Find the first non-separator character + guard let firstNonSeparator = stringKey.firstIndex(where: { $0 != separator }) else { + // Reached the end without finding a separator character + return stringKey + } + + // Find the last non-separator character + var lastNonSeparator = stringKey.index(before: stringKey.endIndex) + while lastNonSeparator > firstNonSeparator, stringKey[lastNonSeparator] == separator { + stringKey.formIndex(before: &lastNonSeparator) + } + + let keyRange = firstNonSeparator...lastNonSeparator + let leadingSeparatorRange = stringKey.startIndex..value`. + case element + /// Decodes a node from either elements of form `value` or attributes + /// of form `nodeName="value"`, with elements taking priority. + case elementOrAttribute + } + + /// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`. + open var nodeDecodingStrategy: NodeDecodingStrategy = .deferredToDecoder + + /// Set of strategies to use for encoding of nodes. + public enum NodeDecodingStrategy { + /// Defer to `Encoder` for choosing an encoding. This is the default strategy. + case deferredToDecoder + + /// Return a closure computing the desired node encoding for the value by its coding key. + case custom((Decodable.Type, Decoder) -> ((CodingKey) -> NodeDecoding)) + + func nodeDecodings( + forType codableType: Decodable.Type, + with decoder: Decoder + ) -> ((CodingKey) -> NodeDecoding?) { + switch self { + case .deferredToDecoder: + guard let dynamicType = codableType as? DynamicNodeDecoding.Type else { + return { _ in nil } + } + return dynamicType.nodeDecoding(for:) + case let .custom(closure): + return closure(codableType, decoder) + } + } + } + + /// Contextual user-provided information for use during decoding. + open var userInfo: [CodingUserInfoKey: Any] = [:] + + /// The error context length. Non-zero length makes an error thrown from + /// the XML parser with line/column location repackaged with a context + /// around that location of specified length. For example, if an error was + /// thrown indicating that there's an unexpected character at line 3, column + /// 15 with `errorContextLength` set to 10, a new error type is rethrown + /// containing 5 characters before column 15 and 5 characters after, all on + /// line 3. Line wrapping should be handled correctly too as the context can + /// span more than a few lines. + open var errorContextLength: UInt = 0 + + /** A boolean value that determines whether the parser reports the + namespaces and qualified names of elements. The default value is `false`. + */ + open var shouldProcessNamespaces: Bool = false + + /** A boolean value that determines whether the parser trims whitespaces + and newlines from the end and the beginning of string values. The default + value is `true`. + */ + open var trimValueWhitespaces: Bool + + /** A boolean value that determines whether to remove pure whitespace elements + that have sibling elements that aren't pure whitespace. The default value + is `false`. + */ + open var removeWhitespaceElements: Bool + + /// Options set on the top-level encoder to pass down the decoding hierarchy. + struct Options { + let dateDecodingStrategy: DateDecodingStrategy + let dataDecodingStrategy: DataDecodingStrategy + let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy + let keyDecodingStrategy: KeyDecodingStrategy + let nodeDecodingStrategy: NodeDecodingStrategy + let userInfo: [CodingUserInfoKey: Any] + } + + /// The options set on the top-level decoder. + var options: Options { + return Options( + dateDecodingStrategy: dateDecodingStrategy, + dataDecodingStrategy: dataDecodingStrategy, + nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy, + keyDecodingStrategy: keyDecodingStrategy, + nodeDecodingStrategy: nodeDecodingStrategy, + userInfo: userInfo + ) + } + + // MARK: - Constructing a XML Decoder + + /// Initializes `self` with default strategies. + public init(trimValueWhitespaces: Bool = true, removeWhitespaceElements: Bool = false) { + self.trimValueWhitespaces = trimValueWhitespaces + self.removeWhitespaceElements = removeWhitespaceElements + } + + // MARK: - Decoding Values + + /// Decodes a top-level box of the given type from the given XML representation. + /// + /// - parameter type: The type of the box to decode. + /// - parameter data: The data to decode from. + /// - returns: A box of the requested type. + /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid XML. + /// - throws: An error if any box throws an error during decoding. + open func decode( + _ type: T.Type, + from data: Data + ) throws -> T { + let topLevel: Box = try XMLStackParser.parse( + with: data, + errorContextLength: errorContextLength, + shouldProcessNamespaces: shouldProcessNamespaces, + trimValueWhitespaces: trimValueWhitespaces, + removeWhitespaceElements: removeWhitespaceElements + ) + + let decoder = XMLDecoderImplementation( + referencing: topLevel, + options: options, + nodeDecodings: [] + ) + decoder.nodeDecodings = [ + options.nodeDecodingStrategy.nodeDecodings( + forType: T.self, + with: decoder + ), + ] + + defer { + _ = decoder.nodeDecodings.removeLast() + } + + return try decoder.unbox(topLevel) + } +} + +// MARK: TopLevelDecoder + +#if canImport(Combine) +import protocol Combine.TopLevelDecoder +import protocol Combine.TopLevelEncoder +#elseif canImport(OpenCombine) +import protocol OpenCombine.TopLevelDecoder +import protocol OpenCombine.TopLevelEncoder +#endif + +#if canImport(Combine) || canImport(OpenCombine) +extension XMLDecoder: TopLevelDecoder {} +extension XMLEncoder: TopLevelEncoder {} +#endif diff --git a/third-party/XMLCoder/Sources/Decoder/XMLDecoderImplementation.swift b/third-party/XMLCoder/Sources/Decoder/XMLDecoderImplementation.swift new file mode 100644 index 0000000000..961322f35d --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLDecoderImplementation.swift @@ -0,0 +1,497 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/20/17. +// + +import Foundation + +class XMLDecoderImplementation: Decoder { + // MARK: Properties + + /// The decoder's storage. + var storage = XMLDecodingStorage() + + /// Options set on the top-level decoder. + let options: XMLDecoder.Options + + /// The path to the current point in encoding. + public internal(set) var codingPath: [CodingKey] + + public var nodeDecodings: [(CodingKey) -> XMLDecoder.NodeDecoding?] + + /// Contextual user-provided information for use during encoding. + public var userInfo: [CodingUserInfoKey: Any] { + return options.userInfo + } + + // The error context length + open var errorContextLength: UInt = 0 + + // MARK: - Initialization + + /// Initializes `self` with the given top-level container and options. + init( + referencing container: Box, + options: XMLDecoder.Options, + nodeDecodings: [(CodingKey) -> XMLDecoder.NodeDecoding?], + codingPath: [CodingKey] = [] + ) { + storage.push(container: container) + self.codingPath = codingPath + self.nodeDecodings = nodeDecodings + self.options = options + } + + // MARK: - Decoder Methods + + internal func topContainer() throws -> Box { + guard let topContainer = storage.topContainer() else { + throw DecodingError.valueNotFound(Box.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get decoding container -- empty container stack." + )) + } + return topContainer + } + + private func popContainer() throws -> Box { + guard let topContainer = storage.popContainer() else { + throw DecodingError.valueNotFound(Box.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: + """ + Cannot get decoding container -- empty container stack. + """ + )) + } + return topContainer + } + + public func container(keyedBy keyType: Key.Type) throws -> KeyedDecodingContainer { + if let keyed = try topContainer() as? SharedBox { + return KeyedDecodingContainer(XMLKeyedDecodingContainer( + referencing: self, + wrapping: keyed + )) + } + if Key.self is XMLChoiceCodingKey.Type { + return try choiceContainer(keyedBy: keyType) + } else { + return try keyedContainer(keyedBy: keyType) + } + } + + public func unkeyedContainer() throws -> UnkeyedDecodingContainer { + let topContainer = try self.topContainer() + + guard !topContainer.isNull else { + throw DecodingError.valueNotFound( + UnkeyedDecodingContainer.self, + DecodingError.Context( + codingPath: codingPath, + debugDescription: + """ + Cannot get unkeyed decoding container -- found null box instead. + """ + ) + ) + } + + switch topContainer { + case let unkeyed as SharedBox: + return XMLUnkeyedDecodingContainer(referencing: self, wrapping: unkeyed) + case let keyed as SharedBox: + return XMLUnkeyedDecodingContainer( + referencing: self, + wrapping: SharedBox(keyed.withShared { $0.elements.map(SingleKeyedBox.init) }) + ) + default: + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [Any].self, + reality: topContainer + ) + } + } + + public func singleValueContainer() throws -> SingleValueDecodingContainer { + return self + } + + private func keyedContainer(keyedBy _: Key.Type) throws -> KeyedDecodingContainer { + let topContainer = try self.topContainer() + let keyedBox: KeyedBox + switch topContainer { + case _ where topContainer.isNull: + throw DecodingError.valueNotFound( + KeyedDecodingContainer.self, + DecodingError.Context( + codingPath: codingPath, + debugDescription: + """ + Cannot get keyed decoding container -- found null box instead. + """ + ) + ) + case let string as StringBox: + keyedBox = KeyedBox( + elements: KeyedStorage([("", string)]), + attributes: KeyedStorage() + ) + case let containsEmpty as SingleKeyedBox where containsEmpty.element is NullBox: + keyedBox = KeyedBox( + elements: KeyedStorage([("", StringBox(""))]), + attributes: KeyedStorage() + ) + case let unkeyed as SharedBox: + guard let keyed = unkeyed.withShared({ $0.first }) as? KeyedBox else { + fallthrough + } + keyedBox = keyed + default: + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [String: Any].self, + reality: topContainer + ) + } + let container = XMLKeyedDecodingContainer( + referencing: self, + wrapping: SharedBox(keyedBox) + ) + return KeyedDecodingContainer(container) + } + + /// - Returns: A `KeyedDecodingContainer` for an XML choice element. + private func choiceContainer(keyedBy _: Key.Type) throws -> KeyedDecodingContainer { + let topContainer = try self.topContainer() + let choiceBox: ChoiceBox + switch topContainer { + case let choice as ChoiceBox: + choiceBox = choice + case let singleKeyed as SingleKeyedBox: + choiceBox = ChoiceBox(singleKeyed) + default: + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [String: Any].self, + reality: topContainer + ) + } + let container = XMLChoiceDecodingContainer( + referencing: self, + wrapping: SharedBox(choiceBox) + ) + return KeyedDecodingContainer(container) + } +} + +// MARK: - Concrete Value Representations + +extension XMLDecoderImplementation { + /// Returns the given box unboxed from a container. + private func typedBox(_ box: Box, for valueType: T.Type) throws -> B { + let error = DecodingError.valueNotFound(valueType, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected \(valueType) but found null instead." + )) + switch box { + case let typedBox as B: + return typedBox + case let unkeyedBox as SharedBox: + guard let value = unkeyedBox.withShared({ + $0.first as? B + }) else { throw error } + return value + case let keyedBox as SharedBox: + guard + let value = keyedBox.withShared({ $0.value as? B }) + else { throw error } + return value + case let singleKeyedBox as SingleKeyedBox: + if let value = singleKeyedBox.element as? B { + return value + } else if let box = singleKeyedBox.element as? KeyedBox, let value = box.elements[""].first as? B { + return value + } else { + throw error + } + case is NullBox: + throw error + case let keyedBox as KeyedBox: + guard + let value = keyedBox.value as? B + else { fallthrough } + return value + default: + throw DecodingError.typeMismatch( + at: codingPath, + expectation: valueType, + reality: box + ) + } + } + + func unbox(_ box: Box) throws -> Bool { + let stringBox: StringBox = try typedBox(box, for: Bool.self) + let string = stringBox.unboxed + + guard let boolBox = BoolBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: Bool.self, reality: box) + } + + return boolBox.unboxed + } + + func unbox(_ box: Box) throws -> Decimal { + let stringBox: StringBox = try typedBox(box, for: Decimal.self) + let string = stringBox.unboxed + + guard let decimalBox = DecimalBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: Decimal.self, reality: box) + } + + return decimalBox.unboxed + } + + func unbox(_ box: Box) throws -> T { + let stringBox: StringBox = try typedBox(box, for: T.self) + let string = stringBox.unboxed + + guard let intBox = IntBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box) + } + + guard let int: T = intBox.unbox() else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." + )) + } + + return int + } + + func unbox(_ box: Box) throws -> T { + let stringBox: StringBox = try typedBox(box, for: T.self) + let string = stringBox.unboxed + + guard let uintBox = UIntBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: T.self, reality: box) + } + + guard let uint: T = uintBox.unbox() else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Parsed XML number <\(string)> does not fit in \(T.self)." + )) + } + + return uint + } + + func unbox(_ box: Box) throws -> Float { + let stringBox: StringBox = try typedBox(box, for: Float.self) + let string = stringBox.unboxed + + guard let floatBox = FloatBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: Float.self, reality: box) + } + + return floatBox.unboxed + } + + func unbox(_ box: Box) throws -> Double { + let stringBox: StringBox = try typedBox(box, for: Double.self) + let string = stringBox.unboxed + + guard let doubleBox = DoubleBox(xmlString: string) else { + throw DecodingError.typeMismatch(at: codingPath, expectation: Double.self, reality: box) + } + + return doubleBox.unboxed + } + + func unbox(_ box: Box) throws -> String { + do { + let stringBox: StringBox = try typedBox(box, for: String.self) + return stringBox.unboxed + } catch { + if box is NullBox { + return "" + } + } + + return "" + } + + func unbox(_ box: Box) throws -> Date { + switch options.dateDecodingStrategy { + case .deferredToDate: + storage.push(container: box) + defer { storage.popContainer() } + return try Date(from: self) + + case .secondsSince1970: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unboxed + + guard let dateBox = DateBox(secondsSince1970: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be formatted in seconds since 1970." + )) + } + return dateBox.unboxed + case .millisecondsSince1970: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unboxed + + guard let dateBox = DateBox(millisecondsSince1970: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be formatted in milliseconds since 1970." + )) + } + return dateBox.unboxed + case .iso8601: + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unboxed + + guard let dateBox = DateBox(iso8601: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Expected date string to be ISO8601-formatted." + )) + } + return dateBox.unboxed + case let .formatted(formatter): + let stringBox: StringBox = try typedBox(box, for: Date.self) + let string = stringBox.unboxed + + guard let dateBox = DateBox(xmlString: string, formatter: formatter) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Date string does not match format expected by formatter." + )) + } + return dateBox.unboxed + case let .custom(closure): + storage.push(container: box) + defer { storage.popContainer() } + return try closure(self) + } + } + + func unbox(_ box: Box) throws -> Data { + switch options.dataDecodingStrategy { + case .deferredToData: + storage.push(container: box) + defer { storage.popContainer() } + return try Data(from: self) + case .base64: + let stringBox: StringBox = try typedBox(box, for: Data.self) + let string = stringBox.unboxed + + guard let dataBox = DataBox(base64: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Encountered Data is not valid Base64" + )) + } + return dataBox.unboxed + case let .custom(closure): + storage.push(container: box) + defer { storage.popContainer() } + return try closure(self) + } + } + + func unbox(_ box: Box) throws -> URL { + let stringBox: StringBox = try typedBox(box, for: URL.self) + let string = stringBox.unboxed + + guard let urlBox = URLBox(xmlString: string) else { + throw DecodingError.dataCorrupted(DecodingError.Context( + codingPath: codingPath, + debugDescription: "Encountered Data is not valid Base64" + )) + } + + return urlBox.unboxed + } + + func unbox(_ box: Box) throws -> T { + let decoded: T? + let type = T.self + + if type == Date.self || type == NSDate.self { + let date: Date = try unbox(box) + decoded = date as? T + } else if type == Data.self || type == NSData.self { + let data: Data = try unbox(box) + decoded = data as? T + } else if type == URL.self || type == NSURL.self { + let data: URL = try unbox(box) + decoded = data as? T + } else if type == Decimal.self || type == NSDecimalNumber.self { + let decimal: Decimal = try unbox(box) + decoded = decimal as? T + } else if + type == String.self || type == NSString.self, + let value = (try unbox(box) as String) as? T + { + decoded = value + } else { + storage.push(container: box) + defer { + storage.popContainer() + } + + do { + decoded = try type.init(from: self) + } catch { + guard case DecodingError.valueNotFound = error, + let type = type as? AnyOptional.Type, + let result = type.init() as? T + else { + throw error + } + + return result + } + } + + guard let result = decoded else { + throw DecodingError.typeMismatch( + at: codingPath, expectation: type, reality: box + ) + } + + return result + } +} + +extension XMLDecoderImplementation { + var keyTransform: (String) -> String { + switch options.keyDecodingStrategy { + case .convertFromSnakeCase: + return XMLDecoder.KeyDecodingStrategy._convertFromSnakeCase + case .convertFromCapitalized: + return XMLDecoder.KeyDecodingStrategy._convertFromCapitalized + case .convertFromUppercase: + return XMLDecoder.KeyDecodingStrategy._convertFromUppercase + case .convertFromKebabCase: + return XMLDecoder.KeyDecodingStrategy._convertFromKebabCase + case .useDefaultKeys: + return { key in key } + case let .custom(converter): + return { key in + converter(self.codingPath + [XMLKey(stringValue: key, intValue: nil)]).stringValue + } + } + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/XMLDecodingStorage.swift b/third-party/XMLCoder/Sources/Decoder/XMLDecodingStorage.swift new file mode 100644 index 0000000000..18d3bdd0ab --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLDecodingStorage.swift @@ -0,0 +1,52 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/20/17. +// + +import Foundation + +// MARK: - Decoding Storage + +struct XMLDecodingStorage { + // MARK: Properties + + /// The container stack. + /// Elements may be any one of the XML types (StringBox, KeyedBox). + private var containers: [Box] = [] + + // MARK: - Initialization + + /// Initializes `self` with no containers. + init() {} + + // MARK: - Modifying the Stack + + var count: Int { + return containers.count + } + + func topContainer() -> Box? { + return containers.last + } + + mutating func push(container: Box) { + if let keyedBox = container as? KeyedBox { + containers.append(SharedBox(keyedBox)) + } else if let unkeyedBox = container as? UnkeyedBox { + containers.append(SharedBox(unkeyedBox)) + } else { + containers.append(container) + } + } + + @discardableResult + mutating func popContainer() -> Box? { + guard !containers.isEmpty else { + return nil + } + return containers.removeLast() + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/XMLKeyedDecodingContainer.swift b/third-party/XMLCoder/Sources/Decoder/XMLKeyedDecodingContainer.swift new file mode 100644 index 0000000000..826b92b90a --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLKeyedDecodingContainer.swift @@ -0,0 +1,417 @@ +// Copyright (c) 2017-2021 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/21/17. +// + +import Foundation + +// MARK: Decoding Containers + +struct XMLKeyedDecodingContainer: KeyedDecodingContainerProtocol { + typealias Key = K + typealias KeyedContainer = SharedBox + typealias UnkeyedContainer = SharedBox + + // MARK: Properties + + /// A reference to the decoder we're reading from. + private let decoder: XMLDecoderImplementation + + /// A reference to the container we're reading from. + private let container: KeyedContainer + + /// The path of coding keys taken to get to this point in decoding. + public private(set) var codingPath: [CodingKey] + + // MARK: - Initialization + + /// Initializes `self` by referencing the given decoder and container. + init( + referencing decoder: XMLDecoderImplementation, + wrapping container: KeyedContainer + ) { + self.decoder = decoder + container.withShared { + $0.elements = .init($0.elements.map { (decoder.keyTransform($0), $1) }) + $0.attributes = .init($0.attributes.map { (decoder.keyTransform($0), $1) }) + } + self.container = container + codingPath = decoder.codingPath + } + + // MARK: - KeyedDecodingContainerProtocol Methods + + public var allKeys: [Key] { + let elementKeys = container.withShared { keyedBox in + keyedBox.elements.keys.compactMap { Key(stringValue: $0) } + } + + let attributeKeys = container.withShared { keyedBox in + keyedBox.attributes.keys.compactMap { Key(stringValue: $0) } + } + + return attributeKeys + elementKeys + } + + public func contains(_ key: Key) -> Bool { + let elements = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributes = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + return !elements.isEmpty || !attributes.isEmpty + } + + public func decodeNil(forKey key: Key) throws -> Bool { + let elements = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributes = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + let box = elements.first ?? attributes.first + + if box is SingleKeyedBox { + return false + } + + return box?.isNull ?? true + } + + public func decode( + _ type: T.Type, forKey key: Key + ) throws -> T { + let attributeFound = container.withShared { keyedBox in + !keyedBox.attributes[key.stringValue].isEmpty + } + + let elementFound = container.withShared { keyedBox in + !keyedBox.elements[key.stringValue].isEmpty || keyedBox.value != nil + } + + if let type = type as? XMLDecodableSequence.Type, + !attributeFound, + !elementFound, + let result = type.init() as? T + { + return result + } + + return try decodeConcrete(type, forKey: key) + } + + public func nestedContainer( + keyedBy _: NestedKey.Type, forKey key: Key + ) throws -> KeyedDecodingContainer { + decoder.codingPath.append(key) + defer { decoder.codingPath.removeLast() } + + let elements = self.container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributes = self.container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + guard let value = elements.first ?? attributes.first else { + throw DecodingError.keyNotFound(key, DecodingError.Context( + codingPath: codingPath, + debugDescription: + """ + Cannot get \(KeyedDecodingContainer.self) -- \ + no value found for key \"\(key.stringValue)\" + """ + )) + } + + guard let container = XMLKeyedDecodingContainer(box: value, decoder: decoder) else { + throw DecodingError.typeMismatch( + at: codingPath, + expectation: [String: Any].self, + reality: value + ) + } + + return KeyedDecodingContainer(container) + } + + public func nestedUnkeyedContainer( + forKey key: Key + ) throws -> UnkeyedDecodingContainer { + decoder.codingPath.append(key) + defer { decoder.codingPath.removeLast() } + + let elements = container.unboxed.elements[key.stringValue] + + if let containsKeyed = elements as? [KeyedBox], containsKeyed.count == 1, let keyed = containsKeyed.first { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(keyed.elements.map(SingleKeyedBox.init)) + ) + } else { + return XMLUnkeyedDecodingContainer( + referencing: decoder, + wrapping: SharedBox(elements) + ) + } + } + + public func superDecoder() throws -> Decoder { + return try _superDecoder(forKey: XMLKey.super) + } + + public func superDecoder(forKey key: Key) throws -> Decoder { + return try _superDecoder(forKey: key) + } +} + +extension XMLKeyedDecodingContainer { + internal init?(box: Box, decoder: XMLDecoderImplementation) { + switch box { + case let keyedContainer as KeyedContainer: + self.init( + referencing: decoder, + wrapping: keyedContainer + ) + case let keyedBox as KeyedBox: + self.init( + referencing: decoder, + wrapping: SharedBox(keyedBox) + ) + case let singleBox as SingleKeyedBox: + let element = (singleBox.key, singleBox.element) + let keyedContainer = KeyedBox(elements: [element], attributes: []) + self.init( + referencing: decoder, + wrapping: SharedBox(keyedContainer) + ) + default: + return nil + } + } +} + +/// Private functions +extension XMLKeyedDecodingContainer { + private func _errorDescription(of key: CodingKey) -> String { + switch decoder.options.keyDecodingStrategy { + case .convertFromSnakeCase: + // In this case we can attempt to recover the original value by + // reversing the transform + let original = key.stringValue + let converted = XMLEncoder.KeyEncodingStrategy + ._convertToSnakeCase(original) + if converted == original { + return "\(key) (\"\(original)\")" + } else { + return "\(key) (\"\(original)\"), converted to \(converted)" + } + default: + // Otherwise, just report the converted string + return "\(key) (\"\(key.stringValue)\")" + } + } + + private func decodeSignedInteger(_ type: T.Type, + forKey key: Key) throws -> T + where T: BinaryInteger & SignedInteger & Decodable + { + return try decodeConcrete(type, forKey: key) + } + + private func decodeUnsignedInteger(_ type: T.Type, + forKey key: Key) throws -> T + where T: BinaryInteger & UnsignedInteger & Decodable + { + return try decodeConcrete(type, forKey: key) + } + + private func decodeFloatingPoint(_ type: T.Type, + forKey key: Key) throws -> T + where T: BinaryFloatingPoint & Decodable + { + return try decodeConcrete(type, forKey: key) + } + + private func decodeConcrete( + _ type: T.Type, + forKey key: Key + ) throws -> T { + guard let strategy = decoder.nodeDecodings.last else { + preconditionFailure( + """ + Attempt to access node decoding strategy from empty stack. + """ + ) + } + + let elements = container + .withShared { keyedBox -> [KeyedBox.Element] in + return (key.isInlined ? keyedBox.elements.values : keyedBox.elements[key.stringValue]).map { + if let singleKeyed = $0 as? SingleKeyedBox { + return singleKeyed.element.isNull ? singleKeyed : singleKeyed.element + } else { + return $0 + } + } + } + + let attributes = container.withShared { keyedBox in + key.isInlined ? keyedBox.attributes.values : keyedBox.attributes[key.stringValue] + } + + decoder.codingPath.append(key) + let nodeDecodings = decoder.options.nodeDecodingStrategy.nodeDecodings( + forType: T.self, + with: decoder + ) + decoder.nodeDecodings.append(nodeDecodings) + defer { + _ = decoder.nodeDecodings.removeLast() + decoder.codingPath.removeLast() + } + + // You can't decode sequences from attributes, but other strategies + // need special handling for empty sequences. + if strategy(key) != .attribute && elements.isEmpty, + let empty = (type as? XMLDecodableSequence.Type)?.init() as? T + { + return empty + } + + // If we are looking at a coding key value intrinsic where the expected type is `String` and + // the value is empty, return CDATA if present otherwise `""`. + if strategy(key) != .attribute, elements.isEmpty, attributes.isEmpty, type == String.self, key.stringValue == "", let emptyString = "" as? T { + let cdata = container.withShared { keyedBox in + keyedBox.elements["#CDATA"].map { + return ($0 as? KeyedBox)?.value ?? $0 + } + }.first + return ((cdata as? StringBox)?.unboxed as? T) ?? emptyString + } + + let box: Box + if key.isInlined { + box = container.typeErasedUnbox() + } else { + switch strategy(key) { + case .attribute?: + box = try getAttributeBox(for: type, attributes, key) + case .element?: + box = try getElementBox(for: type, elements, key) + case .elementOrAttribute?: + box = try getAttributeOrElementBox(attributes, elements, key) + default: + switch type { + case is XMLAttributeProtocol.Type: + box = try getAttributeBox(for: type, attributes, key) + case is XMLElementProtocol.Type: + box = try getElementBox(for: type, elements, key) + default: + box = try getAttributeOrElementBox(attributes, elements, key) + } + } + } + + let value: T? + if !(type is XMLDecodableSequence.Type), let unkeyedBox = box as? UnkeyedBox, + let first = unkeyedBox.first + { + // Handle case where we have held onto a `SingleKeyedBox` + if let singleKeyed = first as? SingleKeyedBox { + if singleKeyed.element.isNull { + value = try decoder.unbox(singleKeyed) + } else { + value = try decoder.unbox(singleKeyed.element) + } + } else { + value = try decoder.unbox(first) + } + } else if box.isNull, let type = type as? XMLOptionalAttributeProtocol.Type, let nullAttribute = type.init() as? T { + value = nullAttribute + } else { + value = try decoder.unbox(box) + } + + if value == nil, let type = type as? AnyOptional.Type, + let result = type.init() as? T + { + return result + } + + guard let unwrapped = value else { + throw DecodingError.valueNotFound(type, DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: + "Expected \(type) value but found null instead." + )) + } + return unwrapped + } + + private func getAttributeBox(for type: T.Type, _ attributes: [KeyedBox.Attribute], _ key: Key) throws -> Box { + if let box = attributes.first { return box } + if type is AnyOptional.Type || type is XMLOptionalAttributeProtocol.Type { return NullBox() } + + throw DecodingError.keyNotFound(key, DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "No attribute found for key \(_errorDescription(of: key))." + )) + } + + private func getElementBox(for type: T.Type, _ elements: [KeyedBox.Element], _ key: Key) throws -> Box { + guard elements.isEmpty else { return elements } + if type is AnyOptional.Type || type is XMLDecodableSequence.Type { return elements } + + throw DecodingError.keyNotFound(key, DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "No element found for key \(_errorDescription(of: key))." + )) + } + + private func getAttributeOrElementBox(_ attributes: [KeyedBox.Attribute], _ elements: [KeyedBox.Element], _ key: Key) throws -> Box { + guard + let anyBox = elements.isEmpty ? attributes.first : elements as Box? + else { + throw DecodingError.keyNotFound(key, DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: + """ + No attribute or element found for key \ + \(_errorDescription(of: key)). + """ + )) + } + return anyBox + } + + private func _superDecoder(forKey key: CodingKey) throws -> Decoder { + decoder.codingPath.append(key) + defer { decoder.codingPath.removeLast() } + + let elements = container.withShared { keyedBox in + keyedBox.elements[key.stringValue] + } + + let attributes = container.withShared { keyedBox in + keyedBox.attributes[key.stringValue] + } + + let box: Box = elements.first ?? attributes.first ?? NullBox() + return XMLDecoderImplementation( + referencing: box, + options: decoder.options, + nodeDecodings: decoder.nodeDecodings, + codingPath: decoder.codingPath + ) + } +} diff --git a/third-party/XMLCoder/Sources/Decoder/XMLUnkeyedDecodingContainer.swift b/third-party/XMLCoder/Sources/Decoder/XMLUnkeyedDecodingContainer.swift new file mode 100644 index 0000000000..0ba8b3a204 --- /dev/null +++ b/third-party/XMLCoder/Sources/Decoder/XMLUnkeyedDecodingContainer.swift @@ -0,0 +1,232 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/21/17. +// + +import Foundation + +struct XMLUnkeyedDecodingContainer: UnkeyedDecodingContainer { + // MARK: Properties + + /// A reference to the decoder we're reading from. + private let decoder: XMLDecoderImplementation + + /// A reference to the container we're reading from. + private let container: SharedBox + + /// The path of coding keys taken to get to this point in decoding. + public private(set) var codingPath: [CodingKey] + + /// The index of the element we're about to decode. + public private(set) var currentIndex: Int + + // MARK: - Initialization + + /// Initializes `self` by referencing the given decoder and container. + init(referencing decoder: XMLDecoderImplementation, wrapping container: SharedBox) { + self.decoder = decoder + self.container = container + codingPath = decoder.codingPath + currentIndex = 0 + } + + // MARK: - UnkeyedDecodingContainer Methods + + public var count: Int? { + return container.withShared { unkeyedBox in + unkeyedBox.count + } + } + + public var isAtEnd: Bool { + return currentIndex >= count! + } + + public mutating func decodeNil() throws -> Bool { + guard !isAtEnd else { + throw DecodingError.valueNotFound(Any?.self, DecodingError.Context( + codingPath: decoder.codingPath + [XMLKey(index: currentIndex)], + debugDescription: "Unkeyed container is at end." + )) + } + + let isNull = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex].isNull + } + + if isNull { + currentIndex += 1 + return true + } else { + return false + } + } + + public mutating func decode(_ type: T.Type) throws -> T { + return try decode(type) { decoder, box in + try decoder.unbox(box) + } + } + + private mutating func decode( + _ type: T.Type, + decode: (XMLDecoderImplementation, Box) throws -> T? + ) throws -> T { + decoder.codingPath.append(XMLKey(index: currentIndex)) + let nodeDecodings = decoder.options.nodeDecodingStrategy.nodeDecodings( + forType: T.self, + with: decoder + ) + decoder.nodeDecodings.append(nodeDecodings) + defer { + _ = decoder.nodeDecodings.removeLast() + _ = decoder.codingPath.removeLast() + } + guard !isAtEnd else { + throw DecodingError.valueNotFound(type, DecodingError.Context( + codingPath: decoder.codingPath + [XMLKey(index: currentIndex)], + debugDescription: "Unkeyed container is at end." + )) + } + + decoder.codingPath.append(XMLKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + let box = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } + + var value: T? + if let singleKeyed = box as? SingleKeyedBox { + do { + value = try decode(decoder, singleKeyed) + } catch { + do { + // Drill down to the element in the case of an nested unkeyed element + value = try decode(decoder, singleKeyed.element) + } catch { + // Specialize for choice elements + value = try decode(decoder, ChoiceBox(key: singleKeyed.key, element: singleKeyed.element)) + } + } + } else { + value = try decode(decoder, box) + } + + defer { currentIndex += 1 } + + if value == nil, let type = type as? AnyOptional.Type, + let result = type.init() as? T + { + return result + } + + guard let decoded: T = value else { + throw DecodingError.valueNotFound(type, DecodingError.Context( + codingPath: decoder.codingPath + [XMLKey(index: currentIndex)], + debugDescription: "Expected \(type) but found null instead." + )) + } + + return decoded + } + + public mutating func nestedContainer( + keyedBy _: NestedKey.Type + ) throws -> KeyedDecodingContainer { + decoder.codingPath.append(XMLKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !isAtEnd else { + throw DecodingError.valueNotFound( + KeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get nested keyed container -- unkeyed container is at end." + ) + ) + } + + let value = self.container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } + guard !value.isNull else { + throw DecodingError.valueNotFound(KeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get keyed decoding container -- found null value instead." + )) + } + + guard let keyedContainer = value as? SharedBox else { + throw DecodingError.typeMismatch(at: codingPath, + expectation: [String: Any].self, + reality: value) + } + + currentIndex += 1 + let container = XMLKeyedDecodingContainer( + referencing: decoder, + wrapping: keyedContainer + ) + return KeyedDecodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { + decoder.codingPath.append(XMLKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !isAtEnd else { + throw DecodingError.valueNotFound( + UnkeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get nested keyed container -- unkeyed container is at end." + ) + ) + } + + let value = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } + guard !value.isNull else { + throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get keyed decoding container -- found null value instead." + )) + } + + guard let unkeyedContainer = value as? SharedBox else { + throw DecodingError.typeMismatch(at: codingPath, + expectation: UnkeyedBox.self, + reality: value) + } + + currentIndex += 1 + return XMLUnkeyedDecodingContainer(referencing: decoder, wrapping: unkeyedContainer) + } + + public mutating func superDecoder() throws -> Decoder { + decoder.codingPath.append(XMLKey(index: currentIndex)) + defer { self.decoder.codingPath.removeLast() } + + guard !isAtEnd else { + throw DecodingError.valueNotFound(Decoder.self, DecodingError.Context( + codingPath: codingPath, + debugDescription: "Cannot get superDecoder() -- unkeyed container is at end." + )) + } + + let value = container.withShared { unkeyedBox in + unkeyedBox[self.currentIndex] + } + currentIndex += 1 + + return XMLDecoderImplementation( + referencing: value, + options: decoder.options, + nodeDecodings: decoder.nodeDecodings, + codingPath: decoder.codingPath + ) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/DynamicNodeEncoding.swift b/third-party/XMLCoder/Sources/Encoder/DynamicNodeEncoding.swift new file mode 100644 index 0000000000..bbb79fa82b --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/DynamicNodeEncoding.swift @@ -0,0 +1,57 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Joseph Mattiello on 1/24/19. +// + +/** Allows conforming types to specify how its properties will be encoded. + + For example: + ```swift + struct Book: Codable, Equatable, DynamicNodeEncoding { + let id: UInt + let title: String + let categories: [Category] + + enum CodingKeys: String, CodingKey { + case id + case title + case categories = "category" + } + + static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding { + switch key { + case Book.CodingKeys.id: return .both + default: return .element + } + } + } + ``` + produces XML of this form for values of type `Book`: + + ```xml + + 123 + Cat in the Hat + Kids + Wildlife + + ``` + */ +public protocol DynamicNodeEncoding: Encodable, Sendable { + static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding +} + +extension Array: DynamicNodeEncoding where Element: DynamicNodeEncoding { + public static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding { + return Element.nodeEncoding(for: key) + } +} + +public extension DynamicNodeEncoding where Self: Collection, Self.Iterator.Element: DynamicNodeEncoding { + static func nodeEncoding(for key: CodingKey) -> XMLEncoder.NodeEncoding { + return Element.nodeEncoding(for: key) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/EncodingErrorExtension.swift b/third-party/XMLCoder/Sources/Encoder/EncodingErrorExtension.swift new file mode 100644 index 0000000000..a364159669 --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/EncodingErrorExtension.swift @@ -0,0 +1,36 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/22/17. +// + +import Foundation + +/// Error Utilities +extension EncodingError { + /// Returns a `.invalidValue` error describing the given invalid floating-point value. + /// + /// + /// - parameter value: The value that was invalid to encode. + /// - parameter path: The path of `CodingKey`s taken to encode this value. + /// - returns: An `EncodingError` with the appropriate path and debug description. + static func _invalidFloatingPointValue(_ value: T, at codingPath: [CodingKey]) -> EncodingError { + let valueDescription: String + if value == T.infinity { + valueDescription = "\(T.self).infinity" + } else if value == -T.infinity { + valueDescription = "-\(T.self).infinity" + } else { + valueDescription = "\(T.self).nan" + } + + let debugDescription = """ + Unable to encode \(valueDescription) directly in XML. \ + Use XMLEncoder.NonConformingFloatEncodingStrategy.convertToString \ + to specify how the value should be encoded. + """ + return .invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: debugDescription)) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/SingleValueEncodingContainer.swift b/third-party/XMLCoder/Sources/Encoder/SingleValueEncodingContainer.swift new file mode 100644 index 0000000000..2d96fdc771 --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/SingleValueEncodingContainer.swift @@ -0,0 +1,103 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/22/17. +// + +import Foundation + +extension XMLEncoderImplementation: SingleValueEncodingContainer { + // MARK: - SingleValueEncodingContainer Methods + + func assertCanEncodeNewValue() { + precondition( + canEncodeNewValue, + """ + Attempt to encode value through single value container when \ + previously value already encoded. + """ + ) + } + + public func encodeNil() throws { + assertCanEncodeNewValue() + storage.push(container: box()) + } + + public func encode(_ value: Bool) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Int64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt8) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt16) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt32) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: UInt64) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: String) throws { + assertCanEncodeNewValue() + storage.push(container: box(value)) + } + + public func encode(_ value: Float) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } + + public func encode(_ value: Double) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } + + public func encode(_ value: T) throws { + assertCanEncodeNewValue() + try storage.push(container: box(value)) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLChoiceEncodingContainer.swift b/third-party/XMLCoder/Sources/Encoder/XMLChoiceEncodingContainer.swift new file mode 100644 index 0000000000..5a07e4d1d3 --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLChoiceEncodingContainer.swift @@ -0,0 +1,208 @@ +// Copyright (c) 2019-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Benjamin Wetherfield on 7/17/19. +// + +struct XMLChoiceEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = K + + // MARK: Properties + + /// A reference to the encoder we're writing to. + private let encoder: XMLEncoderImplementation + + /// A reference to the container we're writing to. + private var container: SharedBox + + /// The path of coding keys taken to get to this point in encoding. + public private(set) var codingPath: [CodingKey] + + // MARK: - Initialization + + /// Initializes `self` with the given references. + init( + referencing encoder: XMLEncoderImplementation, + codingPath: [CodingKey], + wrapping container: SharedBox + ) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - Coding Path Operations + + private func converted(_ key: CodingKey) -> CodingKey { + switch encoder.options.keyEncodingStrategy { + case .useDefaultKeys: + return key + case .convertToSnakeCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToSnakeCase(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .convertToKebabCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToKebabCase(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case let .custom(converter): + return converter(codingPath + [key]) + case .capitalized: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToCapitalized(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .uppercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToUppercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .lowercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToLowercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + } + } + + // MARK: - KeyedEncodingContainerProtocol Methods + + public mutating func encodeNil(forKey key: Key) throws { + container.withShared { + $0.key = converted(key).stringValue + $0.element = NullBox() + } + } + + public mutating func encode( + _ value: T, + forKey key: Key + ) throws { + return try encode(value, forKey: key) { encoder, value in + try encoder.box(value) + } + } + + private mutating func encode( + _ value: T, + forKey key: Key, + encode: (XMLEncoderImplementation, T) throws -> Box + ) throws { + defer { + _ = self.encoder.nodeEncodings.removeLast() + self.encoder.codingPath.removeLast() + } + encoder.codingPath.append(key) + let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings( + forType: T.self, + with: encoder + ) + encoder.nodeEncodings.append(nodeEncodings) + let box = try encode(encoder, value) + + let oldSelf = self + let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in + oldSelf.container.withShared { container in + container.element = box + container.key = oldSelf.converted(key).stringValue + } + } + + defer { + self = oldSelf + } + + try elementEncoder(value, key, box) + } + + public mutating func nestedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + if NestedKey.self is XMLChoiceCodingKey.Type { + return nestedChoiceContainer(keyedBy: NestedKey.self, forKey: key) + } else { + return nestedKeyedContainer(keyedBy: NestedKey.self, forKey: key) + } + } + + mutating func nestedKeyedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + let sharedKeyed = SharedBox(KeyedBox()) + + self.container.withShared { container in + container.element = sharedKeyed + container.key = converted(key).stringValue + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + + let container = XMLKeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedKeyed + ) + return KeyedEncodingContainer(container) + } + + mutating func nestedChoiceContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + let sharedChoice = SharedBox(ChoiceBox()) + + self.container.withShared { container in + container.element = sharedChoice + container.key = converted(key).stringValue + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + + let container = XMLChoiceEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedChoice + ) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer( + forKey key: Key + ) -> UnkeyedEncodingContainer { + let sharedUnkeyed = SharedBox(UnkeyedBox()) + + container.withShared { container in + container.element = sharedUnkeyed + container.key = converted(key).stringValue + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + return XMLUnkeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedUnkeyed + ) + } + + public mutating func superEncoder() -> Encoder { + return XMLReferencingEncoder( + referencing: encoder, + key: XMLKey.super, + convertedKey: converted(XMLKey.super), + wrapping: container + ) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return XMLReferencingEncoder( + referencing: encoder, + key: key, + convertedKey: converted(key), + wrapping: container + ) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLEncoder.swift b/third-party/XMLCoder/Sources/Encoder/XMLEncoder.swift new file mode 100644 index 0000000000..3444c7330e --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLEncoder.swift @@ -0,0 +1,451 @@ +// Copyright © 2017-2021 Shawn Moore and XMLCoder contributors. +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/22/17. +// + +import Foundation + +/// `XMLEncoder` facilitates the encoding of `Encodable` values into XML. +open class XMLEncoder { + // MARK: Options + + /// The formatting of the output XML data. + public struct OutputFormatting: OptionSet, Sendable { + /// The format's default value. + public let rawValue: UInt + + /// Creates an OutputFormatting value with the given raw value. + public init(rawValue: UInt) { + self.rawValue = rawValue + } + + /// Produce human-readable XML with indented output. + public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0) + + /// Produce XML with keys sorted in lexicographic order. + public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) + + /// Produce XML with no short-hand annotation for empty elements, e.g., use `

` over `

` + public static let noEmptyElements = OutputFormatting(rawValue: 1 << 2) + } + + /// The indentation to use when XML is pretty-printed. + public enum PrettyPrintIndentation { + case spaces(Int) + case tabs(Int) + } + + /// A node's encoding type. Specifies how a node will be encoded. + public enum NodeEncoding : Sendable { + case attribute + case element + case both + + public static let `default`: NodeEncoding = .element + } + + /// The strategy to use for encoding `Date` values. + public enum DateEncodingStrategy { + /// Defer to `Date` for choosing an encoding. This is the default strategy. + case deferredToDate + + /// Encode the `Date` as a UNIX timestamp (as a XML number). + case secondsSince1970 + + /// Encode the `Date` as UNIX millisecond timestamp (as a XML number). + case millisecondsSince1970 + + /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). + @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) + case iso8601 + + /// Encode the `Date` as a string formatted by the given formatter. + case formatted(DateFormatter) + + /// Encode the `Date` as a custom value encoded by the given closure. + /// + /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + case custom((Date, Encoder) throws -> ()) + } + + /// The strategy to use for encoding `String` values. + public enum StringEncodingStrategy { + /// Defer to `String` for choosing an encoding. This is the default strategy. + case deferredToString + + /// Encode the `String` as a CData-encoded string. + case cdata + } + + /// The strategy to use for encoding `Data` values. + public enum DataEncodingStrategy { + /// Defer to `Data` for choosing an encoding. + case deferredToData + + /// Encoded the `Data` as a Base64-encoded string. This is the default strategy. + case base64 + + /// Encode the `Data` as a custom value encoded by the given closure. + /// + /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + case custom((Data, Encoder) throws -> ()) + } + + /// The strategy to use for non-XML-conforming floating-point values (IEEE 754 infinity and NaN). + public enum NonConformingFloatEncodingStrategy { + /// Throw upon encountering non-conforming values. This is the default strategy. + case `throw` + + /// Encode the values using the given representation strings. + case convertToString(positiveInfinity: String, negativeInfinity: String, nan: String) + } + + /// The strategy to use for automatically changing the value of keys before encoding. + public enum KeyEncodingStrategy { + /// Use the keys specified by each type. This is the default strategy. + case useDefaultKeys + + /// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key to XML payload. + /// + /// Capital characters are determined by testing membership in + /// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters` + /// (Unicode General Categories Lu and Lt). + /// The conversion to lower case uses `Locale.system`, also known as + /// the ICU "root" locale. This means the result is consistent + /// regardless of the current user's locale and language preferences. + /// + /// Converting from camel case to snake case: + /// 1. Splits words at the boundary of lower-case to upper-case + /// 2. Inserts `_` between words + /// 3. Lowercases the entire string + /// 4. Preserves starting and ending `_`. + /// + /// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`. + /// + /// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted. + case convertToSnakeCase + + /// Same as convertToSnakeCase, but using `-` instead of `_` + /// For example, `oneTwoThree` becomes `one-two-three`. + case convertToKebabCase + + /// Capitalize the first letter only + /// `oneTwoThree` becomes `OneTwoThree` + case capitalized + + /// Uppercase ize all letters + /// `oneTwoThree` becomes `ONETWOTHREE` + case uppercased + + /// Lowercase all letters + /// `oneTwoThree` becomes `onetwothree` + case lowercased + + /// Provide a custom conversion to the key in the encoded XML from the + /// keys specified by the encoded types. + /// The full path to the current encoding position is provided for + /// context (in case you need to locate this key within the payload). + /// The returned key is used in place of the last component in the + /// coding path before encoding. + /// If the result of the conversion is a duplicate key, then only one + /// value will be present in the result. + case custom((_ codingPath: [CodingKey]) -> CodingKey) + + static func _convertToSnakeCase(_ stringKey: String) -> String { + return _convert(stringKey, usingSeparator: "_") + } + + static func _convertToKebabCase(_ stringKey: String) -> String { + return _convert(stringKey, usingSeparator: "-") + } + + static func _convert(_ stringKey: String, usingSeparator separator: String) -> String { + guard !stringKey.isEmpty else { + return stringKey + } + + var words: [Range] = [] + // The general idea of this algorithm is to split words on + // transition from lower to upper case, then on transition of >1 + // upper case characters to lowercase + // + // myProperty -> my_property + // myURLProperty -> my_url_property + // + // We assume, per Swift naming conventions, that the first character of the key is lowercase. + var wordStart = stringKey.startIndex + var searchRange = stringKey.index(after: wordStart)..1 capital letters. Turn those into a word, stopping at the capital before the lower case character. + let beforeLowerIndex = stringKey.index(before: lowerCaseRange.lowerBound) + words.append(upperCaseRange.lowerBound.. String { + return stringKey.capitalizingFirstLetter() + } + + static func _convertToLowercased(_ stringKey: String) -> String { + return stringKey.lowercased() + } + + static func _convertToUppercased(_ stringKey: String) -> String { + return stringKey.uppercased() + } + } + + @available(*, deprecated, renamed: "NodeEncodingStrategy") + public typealias NodeEncodingStrategies = NodeEncodingStrategy + + public typealias XMLNodeEncoderClosure = @Sendable (CodingKey) -> NodeEncoding? + public typealias XMLEncodingClosure = @Sendable (Encodable.Type, Encoder) -> XMLNodeEncoderClosure + + /// Set of strategies to use for encoding of nodes. + public enum NodeEncodingStrategy { + /// Defer to `Encoder` for choosing an encoding. This is the default strategy. + case deferredToEncoder + + /// Return a closure computing the desired node encoding for the value by its coding key. + case custom(XMLEncodingClosure) + + func nodeEncodings( + forType codableType: Encodable.Type, + with encoder: Encoder + ) -> ((CodingKey) -> NodeEncoding?) { + return encoderClosure(codableType, encoder) + } + + var encoderClosure: XMLEncodingClosure { + switch self { + case .deferredToEncoder: return NodeEncodingStrategy.defaultEncoder + case let .custom(closure): return closure + } + } + + static let defaultEncoder: XMLEncodingClosure = { codableType, _ in + guard let dynamicType = codableType as? DynamicNodeEncoding.Type else { + return { _ in nil } + } + #if compiler(>=6.1) + return dynamicType.nodeEncoding(for:) + #else + return { (@Sendable key: CodingKey) in + dynamicType.nodeEncoding(for: key) + } + #endif + } + } + + /// Characters and their escaped representations to be escaped in attributes + open var charactersEscapedInAttributes = [ + ("&", "&"), + ("<", "<"), + (">", ">"), + ("'", "'"), + ("\"", """), + ] + + /// Characters and their escaped representations to be escaped in elements + open var charactersEscapedInElements = [ + ("&", "&"), + ("<", "<"), + (">", ">"), + ("'", "'"), + ("\"", """), + ] + + /// The output format to produce. Defaults to `[]`. + open var outputFormatting: OutputFormatting = [] + + /// The indentation to use when XML is printed. Defaults to `.spaces(4)`. + open var prettyPrintIndentation: PrettyPrintIndentation = .spaces(4) + + /// The strategy to use in encoding dates. Defaults to `.deferredToDate`. + open var dateEncodingStrategy: DateEncodingStrategy = .deferredToDate + + /// The strategy to use in encoding binary data. Defaults to `.base64`. + open var dataEncodingStrategy: DataEncodingStrategy = .base64 + + /// The strategy to use in encoding non-conforming numbers. Defaults to `.throw`. + open var nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy = .throw + + /// The strategy to use for encoding keys. Defaults to `.useDefaultKeys`. + open var keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys + + /// The strategy to use in encoding encoding attributes. Defaults to `.deferredToEncoder`. + open var nodeEncodingStrategy: NodeEncodingStrategy = .deferredToEncoder + + /// The strategy to use in encoding strings. Defaults to `.deferredToString`. + open var stringEncodingStrategy: StringEncodingStrategy = .deferredToString + + /// Contextual user-provided information for use during encoding. + open var userInfo: [CodingUserInfoKey: Any] = [:] + + /// Options set on the top-level encoder to pass down the encoding hierarchy. + struct Options { + let dateEncodingStrategy: DateEncodingStrategy + let dataEncodingStrategy: DataEncodingStrategy + let nonConformingFloatEncodingStrategy: NonConformingFloatEncodingStrategy + let keyEncodingStrategy: KeyEncodingStrategy + let nodeEncodingStrategy: NodeEncodingStrategy + let stringEncodingStrategy: StringEncodingStrategy + let userInfo: [CodingUserInfoKey: Any] + } + + /// The options set on the top-level encoder. + var options: Options { + return Options(dateEncodingStrategy: dateEncodingStrategy, + dataEncodingStrategy: dataEncodingStrategy, + nonConformingFloatEncodingStrategy: nonConformingFloatEncodingStrategy, + keyEncodingStrategy: keyEncodingStrategy, + nodeEncodingStrategy: nodeEncodingStrategy, + stringEncodingStrategy: stringEncodingStrategy, + userInfo: userInfo) + } + + // MARK: - Constructing a XML Encoder + + /// Initializes `self` with default strategies. + public init() {} + + // MARK: - Encoding Values + + /// Encodes the given top-level value and returns its XML representation. + /// + /// - parameter value: The value to encode. + /// - parameter withRootKey: the key used to wrap the encoded values. The + /// default value is inferred from the name of the root type. + /// - parameter rootAttributes: the list of attributes to be added to the root node + /// - parameter header: the XML header to start the encoded data with. + /// - returns: A new `Data` value containing the encoded XML data. + /// - throws: `EncodingError.invalidValue` if a non-conforming + /// floating-point value is encountered during encoding, and the encoding + /// strategy is `.throw`. + /// - throws: An error if any value throws an error during encoding. + open func encode(_ value: T, + withRootKey rootKey: String? = nil, + rootAttributes: [String: String]? = nil, + header: XMLHeader? = nil, + doctype: XMLDocumentType? = nil) throws -> Data + { + let encoder = XMLEncoderImplementation(options: options, nodeEncodings: []) + encoder.nodeEncodings.append(options.nodeEncodingStrategy.nodeEncodings(forType: T.self, with: encoder)) + + let topLevel = try encoder.box(value) + let attributes = rootAttributes?.map(XMLCoderElement.Attribute.init) ?? [] + + let elementOrNone: XMLCoderElement? + + let rootKey = rootKey ?? "\(T.self)".convert(for: keyEncodingStrategy) + + let isStringBoxCDATA = stringEncodingStrategy == .cdata + + if let keyedBox = topLevel as? KeyedBox { + elementOrNone = XMLCoderElement( + key: rootKey, + isStringBoxCDATA: isStringBoxCDATA, + box: keyedBox, + attributes: attributes + ) + } else if let unkeyedBox = topLevel as? UnkeyedBox { + elementOrNone = XMLCoderElement( + key: rootKey, + isStringBoxCDATA: isStringBoxCDATA, + box: unkeyedBox, + attributes: attributes + ) + } else if let choiceBox = topLevel as? ChoiceBox { + elementOrNone = XMLCoderElement( + key: rootKey, + isStringBoxCDATA: isStringBoxCDATA, + box: choiceBox, + attributes: attributes + ) + } else { + fatalError("Unrecognized top-level element of type: \(type(of: topLevel))") + } + + guard let element = elementOrNone else { + throw EncodingError.invalidValue(value, EncodingError.Context( + codingPath: [], + debugDescription: "Unable to encode the given top-level value to XML." + )) + } + + return element.toXMLString( + with: header, + doctype: doctype, + escapedCharacters: ( + elements: charactersEscapedInElements, + attributes: charactersEscapedInAttributes + ), + formatting: outputFormatting, + indentation: prettyPrintIndentation + ).data(using: .utf8, allowLossyConversion: true)! + } + + // MARK: - TopLevelEncoder + + #if canImport(Combine) || canImport(OpenCombine) + open func encode(_ value: T) throws -> Data where T: Encodable { + return try encode(value, withRootKey: nil, rootAttributes: nil, header: nil) + } + #endif +} + +private extension String { + func convert(for encodingStrategy: XMLEncoder.KeyEncodingStrategy) -> String { + switch encodingStrategy { + case .useDefaultKeys: + return self + case .convertToSnakeCase: + return XMLEncoder.KeyEncodingStrategy._convertToSnakeCase(self) + case .convertToKebabCase: + return XMLEncoder.KeyEncodingStrategy._convertToKebabCase(self) + case .custom: + return self + case .capitalized: + return XMLEncoder.KeyEncodingStrategy._convertToCapitalized(self) + case .uppercased: + return XMLEncoder.KeyEncodingStrategy._convertToUppercased(self) + case .lowercased: + return XMLEncoder.KeyEncodingStrategy._convertToLowercased(self) + } + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLEncoderImplementation.swift b/third-party/XMLCoder/Sources/Encoder/XMLEncoderImplementation.swift new file mode 100644 index 0000000000..94e880cb2c --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLEncoderImplementation.swift @@ -0,0 +1,292 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/22/17. +// + +import Foundation + +class XMLEncoderImplementation: Encoder { + // MARK: Properties + + /// The encoder's storage. + var storage: XMLEncodingStorage + + /// Options set on the top-level encoder. + let options: XMLEncoder.Options + + /// The path to the current point in encoding. + public var codingPath: [CodingKey] + + public var nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding?] + + /// Contextual user-provided information for use during encoding. + public var userInfo: [CodingUserInfoKey: Any] { + return options.userInfo + } + + // MARK: - Initialization + + /// Initializes `self` with the given top-level encoder options. + init( + options: XMLEncoder.Options, + nodeEncodings: [(CodingKey) -> XMLEncoder.NodeEncoding?], + codingPath: [CodingKey] = [] + ) { + self.options = options + storage = XMLEncodingStorage() + self.codingPath = codingPath + self.nodeEncodings = nodeEncodings + } + + /// Returns whether a new element can be encoded at this coding path. + /// + /// `true` if an element has not yet been encoded at this coding path; `false` otherwise. + var canEncodeNewValue: Bool { + // Every time a new value gets encoded, the key it's encoded for is + // pushed onto the coding path (even if it's a nil key from an unkeyed container). + // At the same time, every time a container is requested, a new value + // gets pushed onto the storage stack. + // If there are more values on the storage stack than on the coding path, + // it means the value is requesting more than one container, which + // violates the precondition. + // + // This means that anytime something that can request a new container + // goes onto the stack, we MUST push a key onto the coding path. + // Things which will not request containers do not need to have the + // coding path extended for them (but it doesn't matter if it is, + // because they will not reach here). + return storage.count == codingPath.count + } + + // MARK: - Encoder Methods + + public func container(keyedBy _: Key.Type) -> KeyedEncodingContainer { + guard canEncodeNewValue else { + return mergeWithExistingKeyedContainer(keyedBy: Key.self) + } + if Key.self is XMLChoiceCodingKey.Type { + return choiceContainer(keyedBy: Key.self) + } else { + return keyedContainer(keyedBy: Key.self) + } + } + + public func unkeyedContainer() -> UnkeyedEncodingContainer { + // If an existing unkeyed container was already requested, return that one. + let topContainer: SharedBox + if canEncodeNewValue { + // We haven't yet pushed a container at this level; do so here. + topContainer = storage.pushUnkeyedContainer() + } else { + guard let container = storage.lastContainer as? SharedBox else { + preconditionFailure( + """ + Attempt to push new unkeyed encoding container when already previously encoded \ + at this path. + """ + ) + } + + topContainer = container + } + + return XMLUnkeyedEncodingContainer(referencing: self, codingPath: codingPath, wrapping: topContainer) + } + + public func singleValueContainer() -> SingleValueEncodingContainer { + return self + } + + private func keyedContainer(keyedBy _: Key.Type) -> KeyedEncodingContainer { + let container = XMLKeyedEncodingContainer( + referencing: self, + codingPath: codingPath, + wrapping: storage.pushKeyedContainer() + ) + return KeyedEncodingContainer(container) + } + + private func choiceContainer(keyedBy _: Key.Type) -> KeyedEncodingContainer { + let container = XMLChoiceEncodingContainer( + referencing: self, + codingPath: codingPath, + wrapping: storage.pushChoiceContainer() + ) + return KeyedEncodingContainer(container) + } + + private func mergeWithExistingKeyedContainer(keyedBy _: Key.Type) -> KeyedEncodingContainer { + switch storage.lastContainer { + case let keyed as SharedBox: + let container = XMLKeyedEncodingContainer( + referencing: self, + codingPath: codingPath, + wrapping: keyed + ) + return KeyedEncodingContainer(container) + case let choice as SharedBox: + _ = storage.popContainer() + let keyed = KeyedBox( + elements: KeyedBox.Elements([choice.withShared { ($0.key, $0.element) }]), + attributes: [] + ) + let container = XMLKeyedEncodingContainer( + referencing: self, + codingPath: codingPath, + wrapping: storage.pushKeyedContainer(keyed) + ) + return KeyedEncodingContainer(container) + default: + preconditionFailure( + """ + No existing keyed encoding container to merge with. + """ + ) + } + } +} + +extension XMLEncoderImplementation { + /// Returns the given value boxed in a container appropriate for pushing onto the container stack. + func box() -> SimpleBox { + return NullBox() + } + + func box(_ value: Bool) -> SimpleBox { + return BoolBox(value) + } + + func box(_ value: Decimal) -> SimpleBox { + return DecimalBox(value) + } + + func box(_ value: T) -> SimpleBox { + return IntBox(value) + } + + func box(_ value: T) -> SimpleBox { + return UIntBox(value) + } + + func box(_ value: Float) throws -> SimpleBox { + return try box(value, FloatBox.self) + } + + func box(_ value: Double) throws -> SimpleBox { + return try box(value, DoubleBox.self) + } + + func box( + _ value: T, + _: B.Type + ) throws -> SimpleBox where B.Unboxed == T { + guard value.isInfinite || value.isNaN else { + return B(value) + } + guard case let .convertToString( + positiveInfinity: posInfString, + negativeInfinity: negInfString, + nan: nanString + ) = options.nonConformingFloatEncodingStrategy else { + throw EncodingError._invalidFloatingPointValue(value, at: codingPath) + } + if value == T.infinity { + return StringBox(posInfString) + } else if value == -T.infinity { + return StringBox(negInfString) + } else { + return StringBox(nanString) + } + } + + func box(_ value: String) -> SimpleBox { + return StringBox(value) + } + + func box(_ value: Date) throws -> Box { + switch options.dateEncodingStrategy { + case .deferredToDate: + try value.encode(to: self) + return storage.popContainer() + case .secondsSince1970: + return DateBox(value, format: .secondsSince1970) + case .millisecondsSince1970: + return DateBox(value, format: .millisecondsSince1970) + case .iso8601: + return DateBox(value, format: .iso8601) + case let .formatted(formatter): + return DateBox(value, format: .formatter(formatter)) + case let .custom(closure): + let depth = storage.count + try closure(value, self) + + guard storage.count > depth else { + return KeyedBox() + } + + return storage.popContainer() + } + } + + func box(_ value: Data) throws -> Box { + switch options.dataEncodingStrategy { + case .deferredToData: + try value.encode(to: self) + return storage.popContainer() + case .base64: + return DataBox(value, format: .base64) + case let .custom(closure): + let depth = storage.count + try closure(value, self) + + guard storage.count > depth else { + return KeyedBox() + } + + return storage.popContainer() + } + } + + func box(_ value: URL) -> SimpleBox { + return URLBox(value) + } + + func box(_ value: T) throws -> Box { + if T.self == Date.self || T.self == NSDate.self, + let value = value as? Date + { + return try box(value) + } else if T.self == Data.self || T.self == NSData.self, + let value = value as? Data + { + return try box(value) + } else if T.self == URL.self || T.self == NSURL.self, + let value = value as? URL + { + return box(value) + } else if T.self == Decimal.self || T.self == NSDecimalNumber.self, + let value = value as? Decimal + { + return box(value) + } + + let depth = storage.count + try value.encode(to: self) + + // The top container should be a new container. + guard storage.count > depth else { + return KeyedBox() + } + + let lastContainer = storage.popContainer() + + guard let sharedBox = lastContainer as? TypeErasedSharedBoxProtocol else { + return lastContainer + } + + return sharedBox.typeErasedUnbox() + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLEncodingStorage.swift b/third-party/XMLCoder/Sources/Encoder/XMLEncodingStorage.swift new file mode 100644 index 0000000000..9a33a9a1eb --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLEncodingStorage.swift @@ -0,0 +1,66 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/22/17. +// + +import Foundation + +// MARK: - Encoding Storage and Containers + +struct XMLEncodingStorage { + // MARK: Properties + + /// The container stack. + private var containers: [Box] = [] + + // MARK: - Initialization + + /// Initializes `self` with no containers. + init() {} + + // MARK: - Modifying the Stack + + var count: Int { + return containers.count + } + + var lastContainer: Box? { + return containers.last + } + + mutating func pushKeyedContainer(_ keyedBox: KeyedBox = KeyedBox()) -> SharedBox { + let container = SharedBox(keyedBox) + containers.append(container) + return container + } + + mutating func pushChoiceContainer() -> SharedBox { + let container = SharedBox(ChoiceBox()) + containers.append(container) + return container + } + + mutating func pushUnkeyedContainer() -> SharedBox { + let container = SharedBox(UnkeyedBox()) + containers.append(container) + return container + } + + mutating func push(container: Box) { + if let keyedBox = container as? KeyedBox { + containers.append(SharedBox(keyedBox)) + } else if let unkeyedBox = container as? UnkeyedBox { + containers.append(SharedBox(unkeyedBox)) + } else { + containers.append(container) + } + } + + mutating func popContainer() -> Box { + precondition(!containers.isEmpty, "Empty container stack.") + return containers.popLast()! + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLKeyedEncodingContainer.swift b/third-party/XMLCoder/Sources/Encoder/XMLKeyedEncodingContainer.swift new file mode 100644 index 0000000000..441d7ff40b --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLKeyedEncodingContainer.swift @@ -0,0 +1,267 @@ +// Copyright (c) 2018-2021 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 11/20/18. +// + +import Foundation + +struct XMLKeyedEncodingContainer: KeyedEncodingContainerProtocol { + typealias Key = K + + // MARK: Properties + + /// A reference to the encoder we're writing to. + private let encoder: XMLEncoderImplementation + + /// A reference to the container we're writing to. + private var container: SharedBox + + /// The path of coding keys taken to get to this point in encoding. + public private(set) var codingPath: [CodingKey] + + // MARK: - Initialization + + /// Initializes `self` with the given references. + init( + referencing encoder: XMLEncoderImplementation, + codingPath: [CodingKey], + wrapping container: SharedBox + ) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - Coding Path Operations + + private func converted(_ key: CodingKey) -> CodingKey { + switch encoder.options.keyEncodingStrategy { + case .useDefaultKeys: + return key + case .convertToSnakeCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToSnakeCase(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .convertToKebabCase: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToKebabCase(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case let .custom(converter): + return converter(codingPath + [key]) + case .capitalized: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToCapitalized(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .uppercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToUppercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + case .lowercased: + let newKeyString = XMLEncoder.KeyEncodingStrategy + ._convertToLowercased(key.stringValue) + return XMLKey(stringValue: newKeyString, intValue: key.intValue) + } + } + + // MARK: - KeyedEncodingContainerProtocol Methods + + public mutating func encodeNil(forKey key: Key) throws { + container.withShared { + $0.elements.append(NullBox(), at: converted(key).stringValue) + } + } + + public mutating func encode( + _ value: T, + forKey key: Key + ) throws { + return try encode(value, forKey: key) { encoder, value in + try encoder.box(value) + } + } + + private mutating func encode( + _ value: T, + forKey key: Key, + encode: (XMLEncoderImplementation, T) throws -> Box + ) throws { + defer { + _ = self.encoder.nodeEncodings.removeLast() + self.encoder.codingPath.removeLast() + } + guard let strategy = encoder.nodeEncodings.last else { + preconditionFailure( + "Attempt to access node encoding strategy from empty stack." + ) + } + encoder.codingPath.append(key) + let nodeEncodings = encoder.options.nodeEncodingStrategy.nodeEncodings( + forType: T.self, + with: encoder + ) + encoder.nodeEncodings.append(nodeEncodings) + let box = try encode(encoder, value) + + let oldSelf = self + let attributeEncoder: (T, Key, Box) throws -> () = { value, key, box in + guard let attribute = box as? SimpleBox else { + throw EncodingError.invalidValue(value, EncodingError.Context( + codingPath: [], + debugDescription: "Complex values cannot be encoded as attributes." + )) + } + oldSelf.container.withShared { container in + container.attributes.append(attribute, at: oldSelf.converted(key).stringValue) + } + } + + let elementEncoder: (T, Key, Box) throws -> () = { _, key, box in + oldSelf.container.withShared { container in + container.elements.append(box, at: oldSelf.converted(key).stringValue) + } + } + + defer { + self = oldSelf + } + + switch strategy(key) { + case .attribute?: + try attributeEncoder(value, key, box) + case .element?: + try elementEncoder(value, key, box) + case .both?: + try attributeEncoder(value, key, box) + try elementEncoder(value, key, box) + default: + switch value { + case is XMLElementProtocol: + encodeElement(forKey: key, box: box) + case is XMLAttributeProtocol: + try encodeAttribute(value, forKey: key, box: box) + case is XMLElementAndAttributeProtocol: + try encodeAttribute(value, forKey: key, box: box) + encodeElement(forKey: key, box: box) + default: + encodeElement(forKey: key, box: box) + } + } + } + + private mutating func encodeAttribute( + _ value: T, + forKey key: Key, + box: Box + ) throws { + guard let attribute = box as? SimpleBox else { + throw EncodingError.invalidValue(value, EncodingError.Context( + codingPath: [], + debugDescription: "Complex values cannot be encoded as attributes." + )) + } + container.withShared { container in + container.attributes.append(attribute, at: self.converted(key).stringValue) + } + } + + private mutating func encodeElement( + forKey key: Key, + box: Box + ) { + container.withShared { container in + container.elements.append(box, at: self.converted(key).stringValue) + } + } + + public mutating func nestedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + if NestedKey.self is XMLChoiceCodingKey.Type { + return nestedChoiceContainer(keyedBy: NestedKey.self, forKey: key) + } else { + return nestedKeyedContainer(keyedBy: NestedKey.self, forKey: key) + } + } + + mutating func nestedKeyedContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + let sharedKeyed = SharedBox(KeyedBox()) + + self.container.withShared { container in + container.elements.append(sharedKeyed, at: converted(key).stringValue) + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + + let container = XMLKeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedKeyed + ) + return KeyedEncodingContainer(container) + } + + mutating func nestedChoiceContainer( + keyedBy _: NestedKey.Type, + forKey key: Key + ) -> KeyedEncodingContainer { + let sharedChoice = SharedBox(ChoiceBox()) + + self.container.withShared { container in + container.elements.append(sharedChoice, at: converted(key).stringValue) + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + + let container = XMLChoiceEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedChoice + ) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer( + forKey key: Key + ) -> UnkeyedEncodingContainer { + let sharedUnkeyed = SharedBox(UnkeyedBox()) + + container.withShared { container in + container.elements.append(sharedUnkeyed, at: converted(key).stringValue) + } + + codingPath.append(key) + defer { self.codingPath.removeLast() } + return XMLUnkeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedUnkeyed + ) + } + + public mutating func superEncoder() -> Encoder { + return XMLReferencingEncoder( + referencing: encoder, + key: XMLKey.super, + convertedKey: converted(XMLKey.super), + wrapping: container + ) + } + + public mutating func superEncoder(forKey key: Key) -> Encoder { + return XMLReferencingEncoder( + referencing: encoder, + key: key, + convertedKey: converted(key), + wrapping: container + ) + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLReferencingEncoder.swift b/third-party/XMLCoder/Sources/Encoder/XMLReferencingEncoder.swift new file mode 100644 index 0000000000..ff88334717 --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLReferencingEncoder.swift @@ -0,0 +1,130 @@ +// Copyright (c) 2017-2020 Shawn Moore and XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Shawn Moore on 11/25/17. +// + +import Foundation + +/// XMLReferencingEncoder is a special subclass of _XMLEncoder which has its +/// own storage, but references the contents of a different encoder. +/// It's used in superEncoder(), which returns a new encoder for encoding a +// superclass -- the lifetime of the encoder should not escape the scope it's +/// created in, but it doesn't necessarily know when it's done being used +/// (to write to the original container). +class XMLReferencingEncoder: XMLEncoderImplementation { + // MARK: Reference types. + + /// The type of container we're referencing. + private enum Reference { + /// Referencing a specific index in an unkeyed container. + case unkeyed(SharedBox, Int) + + /// Referencing a specific key in a keyed container. + case keyed(SharedBox, String) + + /// Referencing a specific key in a keyed container. + case choice(SharedBox, String) + } + + // MARK: - Properties + + /// The encoder we're referencing. + let encoder: XMLEncoderImplementation + + /// The container reference itself. + private let reference: Reference + + // MARK: - Initialization + + /// Initializes `self` by referencing the given array container in the given encoder. + init( + referencing encoder: XMLEncoderImplementation, + at index: Int, + wrapping sharedUnkeyed: SharedBox + ) { + self.encoder = encoder + reference = .unkeyed(sharedUnkeyed, index) + super.init( + options: encoder.options, + nodeEncodings: encoder.nodeEncodings, + codingPath: encoder.codingPath + ) + + codingPath.append(XMLKey(index: index)) + } + + /// Initializes `self` by referencing the given dictionary container in the given encoder. + init( + referencing encoder: XMLEncoderImplementation, + key: CodingKey, + convertedKey: CodingKey, + wrapping sharedKeyed: SharedBox + ) { + self.encoder = encoder + reference = .keyed(sharedKeyed, convertedKey.stringValue) + super.init( + options: encoder.options, + nodeEncodings: encoder.nodeEncodings, + codingPath: encoder.codingPath + ) + + codingPath.append(key) + } + + init( + referencing encoder: XMLEncoderImplementation, + key: CodingKey, + convertedKey: CodingKey, + wrapping sharedKeyed: SharedBox + ) { + self.encoder = encoder + reference = .choice(sharedKeyed, convertedKey.stringValue) + super.init( + options: encoder.options, + nodeEncodings: encoder.nodeEncodings, + codingPath: encoder.codingPath + ) + + codingPath.append(key) + } + + // MARK: - Coding Path Operations + + override var canEncodeNewValue: Bool { + // With a regular encoder, the storage and coding path grow together. + // A referencing encoder, however, inherits its parents coding path, as well as the key it was created for. + // We have to take this into account. + return storage.count == codingPath.count - encoder.codingPath.count - 1 + } + + // MARK: - Deinitialization + + // Finalizes `self` by writing the contents of our storage to the referenced encoder's storage. + deinit { + let box: Box + switch self.storage.count { + case 0: box = KeyedBox() + case 1: box = self.storage.popContainer() + default: fatalError("Referencing encoder deallocated with multiple containers on stack.") + } + + switch self.reference { + case let .unkeyed(sharedUnkeyedBox, index): + sharedUnkeyedBox.withShared { unkeyedBox in + unkeyedBox.insert(box, at: index) + } + case let .keyed(sharedKeyedBox, key): + sharedKeyedBox.withShared { keyedBox in + keyedBox.elements.append(box, at: key) + } + case let .choice(sharedChoiceBox, key): + sharedChoiceBox.withShared { choiceBox in + choiceBox.element = box + choiceBox.key = key + } + } + } +} diff --git a/third-party/XMLCoder/Sources/Encoder/XMLUnkeyedEncodingContainer.swift b/third-party/XMLCoder/Sources/Encoder/XMLUnkeyedEncodingContainer.swift new file mode 100644 index 0000000000..87f72dc3c0 --- /dev/null +++ b/third-party/XMLCoder/Sources/Encoder/XMLUnkeyedEncodingContainer.swift @@ -0,0 +1,134 @@ +// Copyright (c) 2018-2020 XMLCoder contributors +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT +// +// Created by Vincent Esche on 11/20/18. +// + +import Foundation + +struct XMLUnkeyedEncodingContainer: UnkeyedEncodingContainer { + // MARK: Properties + + /// A reference to the encoder we're writing to. + private let encoder: XMLEncoderImplementation + + /// A reference to the container we're writing to. + private let container: SharedBox + + /// The path of coding keys taken to get to this point in encoding. + public private(set) var codingPath: [CodingKey] + + /// The number of elements encoded into the container. + public var count: Int { + return container.withShared { $0.count } + } + + // MARK: - Initialization + + /// Initializes `self` with the given references. + init( + referencing encoder: XMLEncoderImplementation, + codingPath: [CodingKey], + wrapping container: SharedBox + ) { + self.encoder = encoder + self.codingPath = codingPath + self.container = container + } + + // MARK: - UnkeyedEncodingContainer Methods + + public mutating func encodeNil() throws { + container.withShared { container in + container.append(encoder.box()) + } + } + + public mutating func encode(_ value: T) throws { + try encode(value) { encoder, value in + try encoder.box(value) + } + } + + private mutating func encode( + _ value: T, + encode: (XMLEncoderImplementation, T) throws -> Box + ) rethrows { + encoder.codingPath.append(XMLKey(index: count)) + defer { self.encoder.codingPath.removeLast() } + + try container.withShared { container in + container.append(try encode(encoder, value)) + } + } + + public mutating func nestedContainer( + keyedBy _: NestedKey.Type + ) -> KeyedEncodingContainer { + if NestedKey.self is XMLChoiceCodingKey.Type { + return nestedChoiceContainer(keyedBy: NestedKey.self) + } else { + return nestedKeyedContainer(keyedBy: NestedKey.self) + } + } + + public mutating func nestedKeyedContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer { + codingPath.append(XMLKey(index: count)) + defer { self.codingPath.removeLast() } + + let sharedKeyed = SharedBox(KeyedBox()) + self.container.withShared { container in + container.append(sharedKeyed) + } + + let container = XMLKeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedKeyed + ) + return KeyedEncodingContainer(container) + } + + public mutating func nestedChoiceContainer(keyedBy _: NestedKey.Type) -> KeyedEncodingContainer { + codingPath.append(XMLKey(index: count)) + defer { self.codingPath.removeLast() } + + let sharedChoice = SharedBox(ChoiceBox()) + self.container.withShared { container in + container.append(sharedChoice) + } + + let container = XMLChoiceEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedChoice + ) + return KeyedEncodingContainer(container) + } + + public mutating func nestedUnkeyedContainer() -> UnkeyedEncodingContainer { + codingPath.append(XMLKey(index: count)) + defer { self.codingPath.removeLast() } + + let sharedUnkeyed = SharedBox(UnkeyedBox()) + container.withShared { container in + container.append(sharedUnkeyed) + } + + return XMLUnkeyedEncodingContainer( + referencing: encoder, + codingPath: codingPath, + wrapping: sharedUnkeyed + ) + } + + public mutating func superEncoder() -> Encoder { + return XMLReferencingEncoder( + referencing: encoder, + at: count, + wrapping: container + ) + } +} diff --git a/third-party/boringssl/src/crypto/internal.h b/third-party/boringssl/src/crypto/internal.h index 8e7fc86de1..d1f0b3ed2b 100644 --- a/third-party/boringssl/src/crypto/internal.h +++ b/third-party/boringssl/src/crypto/internal.h @@ -1170,7 +1170,8 @@ static inline uint64_t CRYPTO_rotr_u64(uint64_t value, int shift) { // CRYPTO_addc_* returns |x + y + carry|, and sets |*out_carry| to the carry // bit. |carry| must be zero or one. -#if OPENSSL_HAS_BUILTIN(__builtin_addc) +// _Generic is C11-only and not available in C++ mode with GCC. +#if OPENSSL_HAS_BUILTIN(__builtin_addc) && !defined(__cplusplus) #define CRYPTO_GENERIC_ADDC(x, y, carry, out_carry) \ (_Generic((x), \ @@ -1222,7 +1223,7 @@ static inline uint64_t CRYPTO_addc_u64(uint64_t x, uint64_t y, uint64_t carry, // CRYPTO_subc_* returns |x - y - borrow|, and sets |*out_borrow| to the borrow // bit. |borrow| must be zero or one. -#if OPENSSL_HAS_BUILTIN(__builtin_subc) +#if OPENSSL_HAS_BUILTIN(__builtin_subc) && !defined(__cplusplus) #define CRYPTO_GENERIC_SUBC(x, y, borrow, out_borrow) \ (_Generic((x), \ diff --git a/third-party/dav1d/BUILD b/third-party/dav1d/BUILD index ba022d0420..c62fdeea97 100644 --- a/third-party/dav1d/BUILD +++ b/third-party/dav1d/BUILD @@ -49,6 +49,12 @@ genrule( BUILD_ARCH="arm64" elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -57,15 +63,20 @@ genrule( rm -rf "$$BUILD_DIR" mkdir -p "$$BUILD_DIR" - MESON_DIR="$$(pwd)/$$BUILD_DIR/meson" - rm -rf "$$MESON_DIR" - mkdir -p "$$MESON_DIR" - tar -xzf "$(location @meson_tar_gz//file)" -C "$$MESON_DIR" + if [ "$${BUILD_ARCH}" = "linux_x86_64" ] || [ "$${BUILD_ARCH}" = "linux_arm64" ]; then + EXTRA_PATH="" + else + MESON_DIR="$$(pwd)/$$BUILD_DIR/meson" + rm -rf "$$MESON_DIR" + mkdir -p "$$MESON_DIR" + tar -xzf "$(location @meson_tar_gz//file)" -C "$$MESON_DIR" - NINJA_DIR="$$(pwd)/$$BUILD_DIR/ninja" - rm -rf "$$NINJA_DIR" - mkdir -p "$$NINJA_DIR" - unzip "$(location @ninja-mac_zip//file)" -d "$$NINJA_DIR" + NINJA_DIR="$$(pwd)/$$BUILD_DIR/ninja" + rm -rf "$$NINJA_DIR" + mkdir -p "$$NINJA_DIR" + unzip "$(location @ninja-mac_zip//file)" -d "$$NINJA_DIR" + EXTRA_PATH="$$MESON_DIR/meson-1.6.0:$$NINJA_DIR" + fi cp $(location :build-dav1d-bazel.sh) "$$BUILD_DIR/" cp $(location :arm64-iPhoneSimulator.meson) "$$BUILD_DIR/" @@ -78,7 +89,11 @@ genrule( mkdir -p "$$BUILD_DIR/Public/compat" mkdir -p "$$BUILD_DIR/Public/common" - PATH="$$PATH:$$MESON_DIR/meson-1.6.0:$$NINJA_DIR" sh $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + if [ -n "$$EXTRA_PATH" ]; then + PATH="$$PATH:$$EXTRA_PATH" bash $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + else + bash $$BUILD_DIR/build-dav1d-bazel.sh $$BUILD_ARCH "$$BUILD_DIR" + fi """ + "\n".join([ "cp -f \"$$BUILD_DIR/dav1d/build/include/{}\" \"$(location Public/{})\"".format(header, header) for header in generated_headers @@ -104,10 +119,8 @@ cc_library( srcs = [":Public/dav1d/lib/lib" + x + ".a" for x in libs] ) -objc_library( +cc_library( name = "dav1d", - module_name = "dav1d", - enable_modules = True, hdrs = [":Public/" + x for x in generated_headers], includes = [ "Public", diff --git a/third-party/dav1d/build-dav1d-bazel.sh b/third-party/dav1d/build-dav1d-bazel.sh index 6df4bd50fa..7bdb31cb5a 100644 --- a/third-party/dav1d/build-dav1d-bazel.sh +++ b/third-party/dav1d/build-dav1d-bazel.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e @@ -18,6 +18,43 @@ elif [ "$ARCH" = "sim_arm64" ]; then custom_xcode_path="$(xcode-select -p)/" sed -i '' "s|/Applications/Xcode.app/Contents/Developer/|$custom_xcode_path|g" "$TARGET_CROSSFILE" CROSSFILE="../package/crossfiles/arm64-iPhoneSimulator-custom.meson" +elif [ "$ARCH" = "macos_arm64" ]; then + TARGET_CROSSFILE="$BUILD_DIR/dav1d/package/crossfiles/arm64-MacOSX-custom.meson" + custom_xcode_path="$(xcode-select -p)" + MACOS_SYSROOT="$custom_xcode_path/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + cat > "$TARGET_CROSSFILE" << MESONEOF +[binaries] +c = ['clang', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +cpp = ['clang++', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +objc = ['clang', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +objcpp = ['clang++', '-arch', 'arm64', '-isysroot', '$MACOS_SYSROOT'] +ar = 'ar' +strip = 'strip' + +[built-in options] +c_args = ['-mmacosx-version-min=14.0'] +cpp_args = ['-mmacosx-version-min=14.0'] +c_link_args = ['-mmacosx-version-min=14.0'] +cpp_link_args = ['-mmacosx-version-min=14.0'] +objc_args = ['-mmacosx-version-min=14.0'] +objcpp_args = ['-mmacosx-version-min=14.0'] + +[properties] +root = '$custom_xcode_path/Platforms/MacOSX.platform/Developer' +needs_exe_wrapper = false + +[host_machine] +system = 'darwin' +subsystem = 'macos' +kernel = 'xnu' +cpu_family = 'aarch64' +cpu = 'arm64' +endian = 'little' +MESONEOF + CROSSFILE="../package/crossfiles/arm64-MacOSX-custom.meson" +elif [ "$ARCH" = "linux_arm64" ] || [ "$ARCH" = "linux_x86_64" ]; then + # Native Linux build - no cross file needed + CROSSFILE="" else echo "Unsupported architecture $ARCH" exit 1 @@ -28,7 +65,13 @@ rm -rf build mkdir build pushd build -meson.py setup .. --cross-file="$CROSSFILE" $MESON_OPTIONS +MESON_CMD=$(command -v meson.py 2>/dev/null || command -v meson 2>/dev/null || echo meson.py) + +if [ -n "$CROSSFILE" ]; then + $MESON_CMD setup .. --cross-file="$CROSSFILE" $MESON_OPTIONS +else + $MESON_CMD setup .. $MESON_OPTIONS +fi ninja popd diff --git a/third-party/libjxl/BUILD b/third-party/libjxl/BUILD index 58ab6bebee..d1c7975ef7 100644 --- a/third-party/libjxl/BUILD +++ b/third-party/libjxl/BUILD @@ -57,6 +57,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/libjxl/build-libjxl-bazel.sh b/third-party/libjxl/build-libjxl-bazel.sh index d63765f2af..5e35ba1aca 100755 --- a/third-party/libjxl/build-libjxl-bazel.sh +++ b/third-party/libjxl/build-libjxl-bazel.sh @@ -33,7 +33,24 @@ elif [ "$ARCH" = "sim_arm64" ]; then IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk) export CFLAGS="-Wall -arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -funwind-tables" export CXXFLAGS="$CFLAGS" - + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + + cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $CMAKE_OPTIONS ../libjxl + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + export CXXFLAGS="$CFLAGS" + cd "$BUILD_DIR" mkdir build cd build diff --git a/third-party/libvpx/BUILD b/third-party/libvpx/BUILD index 9040681907..5c34bd2ad9 100644 --- a/third-party/libvpx/BUILD +++ b/third-party/libvpx/BUILD @@ -49,6 +49,15 @@ genrule( elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" PLATFORM_HEADER_DIR="x86_64-iphonesimulator-gcc" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" + PLATFORM_HEADER_DIR="arm64-darwin22-gcc" + elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then + BUILD_ARCH="linux_x86_64" + PLATFORM_HEADER_DIR="x86_64-linux-gcc" + elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then + BUILD_ARCH="linux_arm64" + PLATFORM_HEADER_DIR="arm64-linux-gcc" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -76,7 +85,7 @@ genrule( mkdir -p "$$BUILD_DIR/Public/libvpx" - PATH="$$PATH:$$ABS_YASM_DIR" sh $$BUILD_DIR/build-libvpx-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libvpx" "$$BUILD_DIR" + PATH="$$PATH:$$ABS_YASM_DIR" bash $$BUILD_DIR/build-libvpx-bazel.sh $$BUILD_ARCH "$$BUILD_DIR/libvpx" "$$BUILD_DIR" """ + "\n".join([ "cp -f \"$$BUILD_DIR/VPX.framework/Headers/vpx/{}\" \"$(location Public/vpx/{})\"".format(header, header) for header in headers @@ -102,10 +111,8 @@ cc_library( srcs = [":Public/vpx/lib" + x + ".a" for x in libs], ) -objc_library( +cc_library( name = "vpx", - module_name = "vpx", - enable_modules = True, hdrs = [":Public/vpx/" + x for x in headers], includes = [ "Public", diff --git a/third-party/libvpx/build-libvpx-bazel.sh b/third-party/libvpx/build-libvpx-bazel.sh old mode 100755 new mode 100644 index 991aab1293..775886ba71 --- a/third-party/libvpx/build-libvpx-bazel.sh +++ b/third-party/libvpx/build-libvpx-bazel.sh @@ -1,4 +1,4 @@ -#! /bin/sh +#!/bin/bash set -e set -x @@ -33,18 +33,34 @@ SCRIPT_DIR="$SOURCE_DIR" LIBVPX_SOURCE_DIR="$SOURCE_DIR" -LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) - ORIG_PWD="$(pwd)" +EXTRA_CONFIGURE_ARGS="" + if [ "$ARCH" = "armv7" ]; then TARGETS="armv7-darwin-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) elif [ "$ARCH" = "arm64" ]; then TARGETS="arm64-darwin-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) elif [ "$ARCH" = "sim_arm64" ]; then TARGETS="arm64-iphonesimulator-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) +elif [ "$ARCH" = "macos_arm64" ]; then + TARGETS="arm64-darwin22-gcc" + LIPO=$(xcrun -sdk macosx -find lipo) + export SDKROOT=$(xcrun --sdk macosx --show-sdk-path) +elif [ "$ARCH" = "linux_arm64" ]; then + TARGETS="arm64-linux-gcc" + LIPO="cp" + EXTRA_CONFIGURE_ARGS="--enable-pic" +elif [ "$ARCH" = "linux_x86_64" ]; then + TARGETS="x86_64-linux-gcc" + LIPO="cp" + EXTRA_CONFIGURE_ARGS="--enable-pic" elif [ "$ARCH" = "x86_64" ]; then TARGETS="x86_64-iphonesimulator-gcc" + LIPO=$(xcrun -sdk iphoneos${SDK} -find lipo) else echo "Unsupported architecture $ARCH" exit 1 @@ -167,7 +183,11 @@ build_framework() { cp -p "${target_dist_dir}"/include/vpx/* "${HEADER_DIR}" # Build the fat library. - ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX + if [ "$LIPO" = "cp" ]; then + cp ${lib_list} ${FRAMEWORK_DIR}/VPX + else + ${LIPO} -create ${lib_list} -output ${FRAMEWORK_DIR}/VPX + fi # Create the vpx_config.h shim that allows usage of vpx_config.h from # within VPX.framework. diff --git a/third-party/libyuv/BUILD b/third-party/libyuv/BUILD index 14e2255c5e..dbabb4e81f 100644 --- a/third-party/libyuv/BUILD +++ b/third-party/libyuv/BUILD @@ -25,7 +25,9 @@ arch_specific_cflags = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, "@build_bazel_rules_apple//apple:ios_x86_64": common_flags + x86_64_specific_flags, - "//conditions:default": common_flags, + "//build-system:linux_arm64": common_flags + arm64_specific_flags, + "//build-system:linux_x86_64": common_flags + x86_64_specific_flags, + "//conditions:default": common_flags + arm64_specific_flags, }) cc_library( diff --git a/third-party/mozjpeg/BUILD b/third-party/mozjpeg/BUILD index b74836738d..2c31552b90 100644 --- a/third-party/mozjpeg/BUILD +++ b/third-party/mozjpeg/BUILD @@ -36,6 +36,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/mozjpeg/build-mozjpeg-bazel.sh b/third-party/mozjpeg/build-mozjpeg-bazel.sh index c115e26c97..1ebdf75e3f 100755 --- a/third-party/mozjpeg/build-mozjpeg-bazel.sh +++ b/third-party/mozjpeg/build-mozjpeg-bazel.sh @@ -37,6 +37,22 @@ elif [ "$ARCH" = "sim_arm64" ]; then echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg make else diff --git a/third-party/ogg/BUILD b/third-party/ogg/BUILD index 1da3eaa02d..5c11a1409b 100644 --- a/third-party/ogg/BUILD +++ b/third-party/ogg/BUILD @@ -1,8 +1,6 @@ - -objc_library( + +cc_library( name = "ogg", - enable_modules = True, - module_name = "ogg", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/ogg/include/ogg/config_types.h b/third-party/ogg/include/ogg/config_types.h new file mode 100644 index 0000000000..de228d380f --- /dev/null +++ b/third-party/ogg/include/ogg/config_types.h @@ -0,0 +1,15 @@ +#ifndef __CONFIG_TYPES_H__ +#define __CONFIG_TYPES_H__ + +/* Generated for Linux builds */ + +#include + +typedef int16_t ogg_int16_t; +typedef uint16_t ogg_uint16_t; +typedef int32_t ogg_int32_t; +typedef uint32_t ogg_uint32_t; +typedef int64_t ogg_int64_t; +typedef uint64_t ogg_uint64_t; + +#endif diff --git a/third-party/openh264/BUILD b/third-party/openh264/BUILD index cb340b6fb7..11f3b912e1 100644 --- a/third-party/openh264/BUILD +++ b/third-party/openh264/BUILD @@ -57,18 +57,21 @@ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_sources, "//build-system:ios_sim_arm64": arm64_specific_sources, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_sources, }) arch_specific_copts = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_copts, "//build-system:ios_sim_arm64": arm64_specific_copts, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_copts, }) arch_specific_textual_hdrs = select({ "@build_bazel_rules_apple//apple:ios_arm64": arm64_specific_textual_hdrs, "//build-system:ios_sim_arm64": arm64_specific_textual_hdrs, "@build_bazel_rules_apple//apple:ios_x86_64": [], + "//conditions:default": arm64_specific_textual_hdrs, }) all_sources = arch_specific_sources + [ diff --git a/third-party/opus/BUILD b/third-party/opus/BUILD index 873cdda53d..d80e343284 100644 --- a/third-party/opus/BUILD +++ b/third-party/opus/BUILD @@ -33,6 +33,8 @@ genrule( BUILD_ARCH="linux_x86_64" elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then BUILD_ARCH="linux_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi @@ -81,10 +83,8 @@ cc_library( ], ) -objc_library( +cc_library( name = "opus", - module_name = "opus", - enable_modules = True, hdrs = [":Public/opus/" + x for x in headers], includes = [ "Public", diff --git a/third-party/opus/build-opus-bazel.sh b/third-party/opus/build-opus-bazel.sh index bb79c9efc6..74e6692762 100755 --- a/third-party/opus/build-opus-bazel.sh +++ b/third-party/opus/build-opus-bazel.sh @@ -54,6 +54,10 @@ elif [ "${ARCH}" == "sim_arm64" ]; then PLATFORM="iphonesimulator" EXTRA_CFLAGS="-arch arm64 --target=arm64-apple-ios$MINIOSVERSION-simulator" EXTRA_CONFIG="--host=arm-apple-darwin20" +elif [ "${ARCH}" == "macos_arm64" ]; then + PLATFORM="macosx" + EXTRA_CFLAGS="-arch arm64 --target=arm64-apple-macosx14.0" + EXTRA_CONFIG="--host=arm-apple-darwin20" else PLATFORM="iphoneos" EXTRA_CFLAGS="-arch ${ARCH}" diff --git a/third-party/opusfile/BUILD b/third-party/opusfile/BUILD index 0e62d353df..f28b1d6e46 100644 --- a/third-party/opusfile/BUILD +++ b/third-party/opusfile/BUILD @@ -1,8 +1,6 @@ - -objc_library( + +cc_library( name = "opusfile", - enable_modules = True, - module_name = "opusfile", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/rnnoise/BUILD b/third-party/rnnoise/BUILD index e1066c862b..d260114645 100644 --- a/third-party/rnnoise/BUILD +++ b/third-party/rnnoise/BUILD @@ -24,11 +24,9 @@ replace_symbol_list = [ "pitch_search", "remove_doubling", ] - -objc_library( + +cc_library( name = "rnnoise", - enable_modules = True, - module_name = "rnnoise", srcs = glob([ "Sources/*.c", "Sources/*.h", diff --git a/third-party/td/BUILD b/third-party/td/BUILD index 365f56ee82..f84f29c97b 100644 --- a/third-party/td/BUILD +++ b/third-party/td/BUILD @@ -38,6 +38,8 @@ genrule( BUILD_ARCH="arm64" elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then BUILD_ARCH="sim_arm64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/td/build-td-bazel.sh b/third-party/td/build-td-bazel.sh index a977bbb10d..fc6f43b156 100755 --- a/third-party/td/build-td-bazel.sh +++ b/third-party/td/build-td-bazel.sh @@ -29,15 +29,20 @@ cd .. if [ "$ARCH" = "arm64" ]; then IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneOS.platform" IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneOS*.sdk) - export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0 -miphoneos-version-min=13.0" + export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0 -miphoneos-version-min=13.0 -w" elif [ "$ARCH" = "sim_arm64" ]; then IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform" IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk) - export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0" + export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -w" +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -w" else echo "Unsupported architecture $ARCH" exit 1 fi +export CXXFLAGS="$CFLAGS" # Common build steps mkdir build diff --git a/third-party/webp/BUILD b/third-party/webp/BUILD index 82b7278c8e..2541873587 100644 --- a/third-party/webp/BUILD +++ b/third-party/webp/BUILD @@ -35,6 +35,8 @@ genrule( BUILD_ARCH="sim_arm64" elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then BUILD_ARCH="x86_64" + elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then + BUILD_ARCH="macos_arm64" else echo "Unsupported architecture $(TARGET_CPU)" fi diff --git a/third-party/webp/build-webp-bazel.sh b/third-party/webp/build-webp-bazel.sh index 7a3d123187..dcbb665938 100755 --- a/third-party/webp/build-webp-bazel.sh +++ b/third-party/webp/build-webp-bazel.sh @@ -41,6 +41,22 @@ elif [ "$ARCH" = "sim_arm64" ]; then echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $COMMON_ARGS ../libwebp + make +elif [ "$ARCH" = "macos_arm64" ]; then + IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/MacOSX.platform" + IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/MacOSX*.sdk) + export CFLAGS="-Wall -arch arm64 --target=arm64-apple-macosx14.0 -mmacosx-version-min=14.0 -funwind-tables" + + cd "$BUILD_DIR" + mkdir build + cd build + + touch toolchain.cmake + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake + echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake + echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake + cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} $COMMON_ARGS ../libwebp make elif [ "$ARCH" = "x86_64" ]; then diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index 37837b1606..2fcf1867a2 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -15,7 +15,6 @@ optimization_flags = select({ }) webrtc_objcpp_sources = [ - "rtc_base/system/cocoa_threading.mm", ] webrtc_headers = [ @@ -2767,46 +2766,58 @@ arm64_specific_sources = ["webrtc/" + path for path in [ arch_specific_sources = select({ "@build_bazel_rules_apple//apple:ios_arm64": common_arm_specific_sources + arm64_specific_sources, "//build-system:ios_sim_arm64": common_arm_specific_sources + arm64_specific_sources, + "//conditions:default": common_arm_specific_sources + arm64_specific_sources, }) -common_flags = [ - "-DWEBRTC_IOS", - "-DWEBRTC_MAC", +platform_shared_flags = [ "-DWEBRTC_POSIX", "-DHAVE_WEBRTC_VIDEO", "-DRTC_ENABLE_VP9", "-DRTC_ENABLE_H265", "-DWEBRTC_USE_H264", + "-DWEBRTC_USE_H264_DECODER", "-DHAVE_SCTP", "-DWEBRTC_HAVE_DCSCTP", "-DWEBRTC_HAVE_SCTP", "-DWEBRTC_NS_FLOAT", "-DRTC_DISABLE_TRACE_EVENTS", - #"-DWEBRTC_OPUS_SUPPORT_120MS_PTIME=1", "-DWEBRTC_APM_DEBUG_DUMP=0", "-DBWE_TEST_LOGGING_COMPILE_TIME_ENABLE=0", "-DABSL_ALLOCATOR_NOTHROW=1", "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - #"-DNS_BLOCK_ASSERTIONS=1", "-DWEBRTC_ENABLE_PROTOBUF=0", - "-DWEBRTC_ENABLE_AVX2", "-DWEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0", - "-Wno-shorten-64-to-32", - "-Wno-macro-redefined", - "-D__APPLE__", "-DWEBRTC_OPUS_USE_CODEC_PLC", "-DWEBRTC_OPUS_SUPPORT_DRED", ] +apple_specific_flags = [ + "-DWEBRTC_IOS", + "-DWEBRTC_MAC", + "-D__APPLE__", + "-D__Userspace_os_Darwin", + "-DWEBRTC_ENABLE_AVX2", + "-Wno-shorten-64-to-32", + "-Wno-macro-redefined", +] + +linux_specific_flags = [ + "-DWEBRTC_LINUX", + "-D__Userspace_os_Linux", +] + arm64_specific_flags = [ "-DWEBRTC_ARCH_ARM64", "-DWEBRTC_HAS_NEON", "-DLIBYUV_NEON", ] +# Flatten platform + arch flags into a single select to avoid nested selects arch_specific_cflags = select({ - "@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags, - "//build-system:ios_sim_arm64": common_flags + arm64_specific_flags, + "@build_bazel_rules_apple//apple:ios_arm64": platform_shared_flags + apple_specific_flags + arm64_specific_flags, + "//build-system:ios_sim_arm64": platform_shared_flags + apple_specific_flags + arm64_specific_flags, + "@platforms//os:linux": platform_shared_flags + linux_specific_flags + arm64_specific_flags, + "//conditions:default": platform_shared_flags + apple_specific_flags + arm64_specific_flags, }) dcsctp_sources = [ "webrtc/net/dcsctp/" + path for path in [ @@ -2981,11 +2992,44 @@ fft4g_sources = [ "fft4g/fft4g.cc", ] -raw_combined_sources = webrtc_headers + webrtc_sources + webrtc_objcpp_sources -combined_sources = [ - "webrtc/" + path for path in raw_combined_sources +raw_combined_cpp_sources = webrtc_headers + webrtc_sources +raw_combined_objcpp_sources = webrtc_objcpp_sources + +# Platform-specific source files: GCD task queue on Apple, stdlib on Linux +apple_only_cpp_sources = [ + "webrtc/rtc_base/task_queue_gcd.cc", + "webrtc/rtc_base/task_queue_gcd.h", + "webrtc/api/task_queue/default_task_queue_factory_gcd.cc", + "webrtc/rtc_base/mac_ifaddrs_converter.cc", +] + +linux_only_cpp_sources = [ + "webrtc/rtc_base/task_queue_stdlib.cc", + "webrtc/rtc_base/task_queue_stdlib.h", + "webrtc/api/task_queue/default_task_queue_factory_stdlib.cc", +] + +# Files excluded from common sources because they are platform-specific +platform_specific_excludes = [ + "rtc_base/task_queue_gcd.cc", "rtc_base/task_queue_gcd.h", + "rtc_base/task_queue_stdlib.cc", "rtc_base/task_queue_stdlib.h", + "rtc_base/mac_ifaddrs_converter.cc", + "api/task_queue/default_task_queue_factory_gcd.cc", + "api/task_queue/default_task_queue_factory_stdlib.cc", + "api/task_queue/default_task_queue_factory_libevent.cc", + "api/task_queue/default_task_queue_factory_win.cc", + "api/task_queue/default_task_queue_factory_stdlib_or_libevent_experiment.cc", +] + +combined_cpp_sources = [ + "webrtc/" + path for path in raw_combined_cpp_sources + if path not in platform_specific_excludes ] + arch_specific_sources + fft4g_sources + dcsctp_sources +combined_objcpp_sources = [ + "webrtc/" + path for path in raw_combined_objcpp_sources +] + genrule( name = "generate_field_trials_header", srcs = [ @@ -3118,13 +3162,15 @@ objc_library( visibility = ["//visibility:public"], ) -objc_library( +cc_library( name = "webrtc", - enable_modules = True, - module_name = "webrtc", - srcs = combined_sources, + srcs = combined_cpp_sources + select({ + "@platforms//os:linux": linux_only_cpp_sources, + "//conditions:default": combined_objcpp_sources + apple_only_cpp_sources, + }), copts = [ "-w", + "-include", "stdint.h", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/include", "-Ithird-party/webrtc/libsrtp/third_party/libsrtp/crypto/include", "-Ithird-party/webrtc/libsrtp", @@ -3140,7 +3186,6 @@ objc_library( "-DSCTP_SIMPLE_ALLOCATOR", "-DSCTP_PROCESS_LEVEL_LOCKS", "-D__Userspace__", - "-D__Userspace_os_Darwin", "-DPACKAGE_VERSION=\\\"\\\"", "-DHAVE_SCTP", "-DWEBRTC_HAVE_DCSCTP", @@ -3168,20 +3213,51 @@ objc_library( "//third-party/webrtc/pffft", "//third-party/webrtc/libsrtp", "//third-party/webrtc/absl", + ] + select({ + "@platforms//os:linux": [], + "//conditions:default": [":webrtc_platform_helpers"], + }), + linkopts = select({ + "@platforms//os:linux": ["-lpthread", "-lm"], + "//conditions:default": [ + "-framework AVFoundation", + "-framework AudioToolbox", + "-framework VideoToolbox", + "-framework CoreMedia", + "-framework CoreVideo", + "-framework CoreGraphics", + "-framework QuartzCore", + "-weak_framework Network", + "-weak_framework Metal", + ], + }) + select({ + "@platforms//os:macos": ["-framework AppKit"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +# Minimal helpers needed by tgcalls C++ code on non-iOS platforms. +# Separate from webrtc_objc to avoid pulling in iOS SDK dependencies. +objc_library( + name = "webrtc_platform_helpers", + srcs = [ + "webrtc/rtc_base/system/gcd_helpers.m", + "webrtc/rtc_base/system/cocoa_threading.mm", ], - sdk_frameworks = [ - "AVFoundation", - "AudioToolbox", - "VideoToolbox", - "UIKit", - "CoreMedia", - "CoreVideo", - "CoreGraphics", - "QuartzCore", + hdrs = [ + "webrtc/rtc_base/system/gcd_helpers.h", + "webrtc/rtc_base/system/cocoa_threading.h", ], - weak_sdk_frameworks = [ - "Network", - "Metal", + copts = [ + "-w", + "-Ithird-party/webrtc/webrtc/", + "-Ithird-party/webrtc/absl", + "-DWEBRTC_MAC", + "-DWEBRTC_IOS", + ], + deps = [ + "//third-party/webrtc/absl", ], visibility = ["//visibility:public"], ) diff --git a/third-party/webrtc/absl/absl/base/attributes.h b/third-party/webrtc/absl/absl/base/attributes.h index 38086a82e4..6f61871041 100644 --- a/third-party/webrtc/absl/absl/base/attributes.h +++ b/third-party/webrtc/absl/absl/base/attributes.h @@ -808,13 +808,8 @@ // // See also the upstream documentation: // https://clang.llvm.org/docs/AttributeReference.html#lifetimebound -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::lifetimebound) -#define ABSL_ATTRIBUTE_LIFETIME_BOUND [[clang::lifetimebound]] -#elif ABSL_HAVE_ATTRIBUTE(lifetimebound) -#define ABSL_ATTRIBUTE_LIFETIME_BOUND __attribute__((lifetimebound)) -#else +// Disabled: newer clang rejects lifetimebound on void-returning functions #define ABSL_ATTRIBUTE_LIFETIME_BOUND -#endif // ABSL_ATTRIBUTE_TRIVIAL_ABI // Indicates that a type is "trivially relocatable" -- meaning it can be diff --git a/third-party/webrtc/webrtc b/third-party/webrtc/webrtc index d5c77d3588..3817e906cb 160000 --- a/third-party/webrtc/webrtc +++ b/third-party/webrtc/webrtc @@ -1 +1 @@ -Subproject commit d5c77d3588c9353dd48b80430d2ffb41dafef177 +Subproject commit 3817e906cb6c22ec9cc62023b073e1a668d9cb33 diff --git a/third-party/yasm/BUILD b/third-party/yasm/BUILD index ecfef261b0..1d0e523fba 100644 --- a/third-party/yasm/BUILD +++ b/third-party/yasm/BUILD @@ -16,7 +16,7 @@ genrule( cmd_bash = """ set -x - core_count=`PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu` + core_count=$$(nproc 2>/dev/null || PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu 2>/dev/null || echo 4) BUILD_DIR="$(RULEDIR)/build" rm -rf "$$BUILD_DIR" mkdir -p "$$BUILD_DIR" @@ -25,14 +25,24 @@ set -x rm -rf "$$CMAKE_DIR" mkdir -p "$$CMAKE_DIR" tar -xf "$(location @cmake_tar_gz//file)" -C "$$CMAKE_DIR" - + + # Find cmake: try system cmake first, then downloaded macOS cmake + if command -v cmake >/dev/null 2>&1; then + CMAKE_BIN="cmake" + elif [ -d "$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin" ]; then + CMAKE_BIN="$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin/cmake" + else + echo "cmake not found" + exit 1 + fi + SOURCE_PATH="third-party/yasm/yasm-1.3.0" cp -R "$$SOURCE_PATH" "$$BUILD_DIR/" pushd "$$BUILD_DIR/yasm-1.3.0" mkdir build cd build - PATH="$$CMAKE_DIR/cmake-4.1.2-macos-universal/CMake.app/Contents/bin:$$PATH" cmake .. -DYASM_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DPYTHON_EXECUTABLE="$$(which python3)" + "$$CMAKE_BIN" .. -DYASM_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DPYTHON_EXECUTABLE="$$(which python3)" make -j $$core_count popd diff --git a/versions.json b/versions.json index 3fe2d79551..4663def5b0 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,6 @@ { - "app": "12.6.2", - "xcode": "26.4", + "app": "12.8", + "xcode": "26.2", "deploy_xcode": "26.2", "bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa", "macos": "26"